Running box setup-host inside a box creates a nested boxnet on the same hardcoded 10.88.0.0/24 as the guest's own uplink, causing intermittent egress blackouts
#80
Labels
No labels
blocked
blocker:ci-red
blocker:conflict
blocker:drill-pending
blocker:unrequested
bug
claimed
documentation
enhancement
epic
merge-next
needs-triage
ready
release
scope:cli
scope:drill
scope:host
scope:installer
scope:templates
scope:tiers
stale
state:addressing
state:bots-reviewing
state:building
state:needs-human
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: heavy-duty/box#80
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
The host stack's subnet (
10.88.0.0/24, gateway10.88.0.1) is hardcoded. Ifbox setup-host(or the drill) is ever run inside a box, the guest gets a nestedboxnetbridge claiming the exact subnet and gateway address of its own uplink. The guest then holds10.88.0.1/24as a local address while its default route points at10.88.0.1as its gateway, and carries two conflicting10.88.0.0/24connected routes. The result is intermittent, self-recovering egress blackouts (tens of seconds, roughly periodic) that are extremely hard to attribute — the host looks healthy,box doctoron the host reports clean, and the symptom presents as flaky internet for whatever runs inside the box (in my case, Claude Code showing "Waiting for API response… check your network" about once an hour).This is a realistic footgun for this project in particular: the flagship use case is a coding agent working on a repo inside a box, and the repo most likely to be worked on by this project's own users is box itself. An agent (or a human) following the README inside a box will run
box setup-hostordrill/drill.shand silently poison the guest's networking.Environment
boxnet/box-isolate/box-firewallon10.88.0.0/24)claudetemplate VM (dan-claude), Debian 13, minted normally, uplinkenp5s0via DHCP from the host'sboxnetSymptoms
ping 1.1.1.1= 0% loss, boxping 1.1.1.1= 66% loss andcurl https://api.anthropic.com=000(connection failure) while the host got the expected404.A continuous
ping -i 0.3 1.1.1.1from inside the box captured two full outages back to back:Notes on the capture: sequence numbers kept advancing through the gaps, so the guest was scheduled and transmitting the whole time (not CPU starvation), and the descending-latency staircase on recovery shows ~2 s of queued packets flushing when the path came back.
box doctorrun as an unprivileged user (which probes a live box) had already caught the signature without my understanding it at the time:"Egress broken but DNS fine" is itself a fingerprint of this bug — see mechanism below. Meanwhile
box doctoras root on the host reported fully clean (it validated the host stack's config, and had no instance in its project to probe).Root cause
incus list dan-claude -c ns4gave it away:At some point the box stack was installed inside the box (almost certainly by the coding agent while working on this repo — running
setup-host/drill per the README). Inside the guest:So the guest simultaneously has:
localrouting table is consulted before the main table, so packets addressed to10.88.0.1(DNS queries to the bridge resolver, unicast DHCP renewals to the DHCP server) can be delivered to the guest itself instead of the real gateway.10.88.0.1, intercepting the guest's DNS. This explains the doctor's "DNS works, egress broken" split: DNS was being served by the impostor (which forwarded upstream successfully via the default route), while direct IP egress suffered.10.88.0.0/24— one via the real uplink (metric 1024), one via the nested bridge (no metric, i.e. metric 0, which wins whenever the nested bridge has carrier). The nested bridge showslinkdownwhen idle, which keeps its routes inactive — but any activity that brings carrier up (a nested box starting, the drill, bridge churn) instantly makes the nested route preferred and blackholes the subnet.Why it's intermittent and roughly periodic
The guest's DHCP lease from the host bridge is short (
valid_lft 1143secobserved, i.e. ~19 min lease). DHCP renewal at T1 is a unicast to the server —10.88.0.1— which the local table can capture, so renewals silently fail; recovery happens later via broadcast (rebind), which reaches the real server. Depending on timing and on whether the nested dnsmasq also answers the broadcast (it is a DHCP server for the same subnet!), the guest experiences windows of tens of seconds with broken or churning addressing/ARP state, then self-heals. This matches the observed cadence ("about once an hour", outages of 24 s and 36 s) without any fault on the host, the NIC, or the uplink. I did not pin down the exact packet-level sequence of each outage; the observations above (two outage windows, lease timing, local-table capture, duplicate routes, impostor dnsmasq) are the measured facts, and each listed mechanism is sufficient on its own to break egress once the nested bridge gains carrier.Workaround (verified)
Moving the nested bridge off the colliding subnet immediately and durably fixed the blips:
(Deleting the nested stack entirely —
box teardown-hostinside the guest — should work equally well.)Suggested fixes
Any one of these would have prevented the failure; together they'd make the footgun essentially impossible:
setup-hostshould detect it is running on a box (or more generally: that any existing interface/route already owns an address inside10.88.0.0/24, e.g. the default route's gateway is in it) and refuse with a clear message — or automatically select a non-colliding subnet.BOX_SUBNET, alongside the existingBOX_DNS), so a nested or otherwise-conflicted install has a sanctioned way out. Nested box-in-box may be a non-goal, but the tool's own README funnels agents toward doing exactly this, so failing loudly or adapting seems in scope.box doctor(run inside a guest, or the host doctor probing a guest) to recognize the signature: a local address equal to the default gateway, and/or duplicate connected routes for the uplink subnet. The doctor already caught the symptom (DIRTY cannot reach 1.1.1.1 by addresswhile DNS resolves) but attributed it to the host admin; this exact split (IP egress broken, DNS fine) could hint at "is something inside this box squatting on the gateway address?"claude/codex/groktemplates' agent-context file with a note thatsetup-host/the drill must never be run inside a box — the drill's own docs say "use a machine you own," but an agent inside a box arguably believes it owns the machine.Reproduction
box new --name victim --template claude(VM mode), enter it.curl … install.sh | bash), runbox setup-host(and optionally mint a nested box or run the drill to give the nested bridge carrier).ip routeshows two10.88.0.0/24connected routes and10.88.0.1held locally; DNS is now served by the nested dnsmasq; egress degrades intermittently, correlating with nested-bridge carrier and DHCP lease events.sudo incus network set boxnet ipv4.address 10.89.0.1/24inside the guest → symptoms stop.Credits / trail
Diagnosed by bisecting live incidents: simultaneous host-vs-guest probes proved the host path clean while the guest lost 66% of packets; a timestamped ping capture ruled out CPU starvation and showed queue-flush recovery;
incus listexposed the nested bridge;ip routeconfirmed the collision; changing the nested subnet resolved it. Happy to provide the full captures or re-test a patchedsetup-host.