Repository navigation
T121 — Steer a host-address grant box's lookups and admit dns on a VM host - #3079
norrietaylor merged 5 commits into
Conversation
On a VM host a host_ip box resolves through a steering answerer of its own (BEP-086), so the host fact that BEP-017 reads is true for own_ip and host_ip there (BEP-081). A host with no VM keeps the refusal.
A host_ip box whose own spec declares a credentialed upstream with dns or both steering resolves through a steering answerer of its own, at a loopback address in 127.53.0.0/16 (BEP-086). It answers the box's steered names with the proxy's address, NODATA for AAAA, HTTPS and SVCB, SERVFAIL while the listener is down, and forwards every other name to the node's resolver. Attach fences the answerer to the box's own cgroup leaf with an nft chain of its own. Every other host_ip box keeps the gateway.
|
Important Review skippedBot user detected. To trigger a single review, invoke the
|
|
Approved: every review thread resolved at @norrietaylor resolved the last thread, which releases the approval this review had withheld. Nothing to do. |
There was a problem hiding this comment.
arch-review: CONDITIONS · 4 conditions · the full ruling
The answer half of BEP-086 conforms, and BEP-081 conforms. The fence that keeps other processes from the box's steering answerer fails open. attach() logs a warning and starts the box when nft refuses the fence. From plan() to attach(), the answerer has no fence. Section 4.1 line 54 and section 7.1 line 254 require the per-box gate to hold, with a surfaced error and no silent fallback. C1 and C2 make both paths refuse the launch. C3 and C4 add tests that run the fence, not only its rendered text.
Ruled at 9b5d526 against gominimal/arch@7a86ec7bb3c0 by claude-opus-5-5.
The plan closes the answerer to every leaf as soon as it binds, and attach admits the box's own leaf. A refused fence at either step stops the answerer and fails the box. A leaf path with a character an nft script cannot carry is refused. A datagram the answerer cannot read is dropped, not forwarded to the node's resolver.
There was a problem hiding this comment.
arch-review: PASS · the full ruling
The change conforms. The compare makes the steering answerer fail closed. plan() closes the answerer as soon as it binds, and attach() fails the box when the leaf fence cannot be set. This matches default-deny in §7.1 line 244 and per-box steering in §5.4 lines 157 and 175. The CLI admits dns and both for host_ip only on a VM host (BEP-081). No condition is open.
Ruled at 8454d51 against gominimal/arch@7a86ec7bb3c0 by claude-opus-5-5.
|
Round 1 of 10 on the runtime's review findings, pushed as
Verify Review threads the coder judged:
On a Mac, a host-address box with a grant and no steering line resolves github.com to the proxy through its own answerer. That answerer is closed to every other process from the moment it binds. A host-address box with no grant resolves github.com to GitHub's real address. The unit tests prove the resolver half. This round did not run the clone through the proxy on a real VM host. Notes from the agent: Revise round 1. All three findings hold, and this round fixes each one. The plan now sets a closed fence (reject for every leaf) as soon as the answerer binds. Attach replaces it with the box-leaf fence. A failure at either step stops the answerer, removes the chain and fails the box. Abandon and teardown now always remove the chain. The provider tests now use a stand-in nft script in place of /nonexistent/nft, because a plan with a steered box now runs nft. Unreadable or oversized datagrams get a new SteeringVerdict::Drop and no reply. They do not get SERVFAIL, because an unparseable message has no id to reply to. Not run: just e2e and the VM lane, so the clone-through-proxy half of the proof is still unverified on a real Mac. Not run: just clippy-strict. |
|
/revise Review finding PRRT_kwDOPdfcxM60Vjwb at 8454d51 (
Add tests: a TCP query for a steered name gets the proxy's address, a TCP query for another name is forwarded, and the answerer binds again at once after |
…st-address-grant-box-s-lookups-and-adm
A steered box's only resolver is its steering answerer. A truncated forwarded reply makes the box retry over TCP, and the answerer bound UDP only, so the lookup failed. The answerer now listens on TCP at the same address and port, answers steered names with the same verdicts, and forwards every other name to the node's resolver over TCP. The answerer holds the only strong reference to each socket, and its tasks hold weak ones, so dropping it frees the address and port at once. Drop also cancels in-flight forwards and TCP connections.
There was a problem hiding this comment.
arch-review: PASS · the full ruling
This push adds TCP to the steering answerer and merges main. TCP lookups use the same verdict as UDP, so steered names get the proxy's address and never reach real DNS (§5.3 line 151). The existing fence already covers TCP and UDP, so only the declaring box reaches the answerer (§5.4 line 175). The merge changes only a comment in the files of this PR. The PASS on 8454d510 stands for everything else. There is nothing more to do.
Ruled at 23f3537 against gominimal/arch@7a86ec7bb3c0 by claude-opus-5-5.
There was a problem hiding this comment.
minimal-review — approved
Verdict APPROVE — every review thread is resolved; @norrietaylor closed the last one. The review of 23f3537221a0 withheld this approval on the threads open then. Posted as a comment: the App cannot approve its own pull request; the check and the label carry it.
|
Round 2 of 10 on a /revise note, pushed as
Verify Review threads the coder judged:
A host-address grant box on a Mac resolves github.com to the proxy through its own steering answerer, over UDP and now over TCP too, so a truncated reply that makes the box retry over TCP still resolves. A host-address box with no grant keeps the gateway resolver and gets GitHub's real address. A restarted answerer binds its address and port again at once. Notes from the agent: Revise round 2. The steering answerer in crates/minimald/src/net/dns.rs now binds a TCP listener at the UDP socket's address and port. It applies SteeringPlan::verdict to each length-prefixed message and forwards other names to the node's resolver over a TCP connection of its own, with the 5 s forward timeout. A message it cannot read closes the connection, so it never reaches real DNS. TCP connections are capped at 64 and close after 10 s idle. The answerer holds the only Arc of each socket. Tasks hold a Weak and upgrade only inside each poll, so Drop frees the port at once. Drop also cancels a CancellationToken that stops in-flight forwards and connections. With port 0, the bind retries a new port if TCP already holds the UDP one. The SERVFAIL warning clock is shared by the UDP and TCP halves. The new tests are in the dns.rs test module. The end-to-end proof on a Mac (getent inside a real VM box) was not run on this Linux box. clippy -p minimald --all-targets is clean. just clippy-strict was not run. |
There was a problem hiding this comment.
Gate passed at 23f3537 under the standing gate-4 rule.
T121 — Steer a host-address grant box's lookups and admit dns on a VM host
slice: S17
kind: task
covers: BEP-081, BEP-086
size: L
est: 340 code, 380 tests
executor: agent
difficulty: unknown ground
model: strongest
diagnostics:
min box spec <entry> --network host_ipon a Mac showssteering = "dns"resolved and no refusal; inside a host-address grant boxgetent hosts 8000 github.comprints the proxy's address, and inside a host-address box with no grant it prints GitHub's real addressobservability: the steering answerer's debug line for a steered answer names the box and the steered name, as the switch's DNS gate does for an
own_ipboxlayers:
- resolver:
- crates/minimald/src/net/provider.rs: a
host_ipbox with a credentialed upstream anddnsorbothsteering gets a resolver plan that names the steering answerer; every otherhost_ipbox keeps the resolver it has today- crates/minimald/src/net/dns.rs: a steering answerer in the guest's namespace answers the box's module host set with the proxy's address, NODATA for AAAA, HTTPS and SVCB, SERVFAIL while the listener is down, and forwards every other name to the node's resolver
- crates/minimald/src/net/classifier.rs: the box's cgroup leaf may reach the steering answerer's port, and no other leaf may
test: cargo nextest run -p minimald host_ip_grant_box_steered_names_resolve_to_proxy
test: cargo nextest run -p minimald host_ip_lookup_outside_a_steered_box_resolves_real
- activation:
- crates/minimal/src/cmd/boxes/create.rs:
host_resolves_box_namesis true on a VM host forown_ipandhost_ipboxes, so BEP-017 refusesdnsandbothonly on a host with no VM- crates/minimal/src/cmd/box_spec.rs: the preview reads the same host fact for
host_iptest: cargo nextest run -p minimal vm_host_admits_dns_and_both_for_own_ip_and_host_ip
produces:
- per-box dns steering for a host-address box
consumes:
- the in-effect rule for a host-address lane
- cohort-attributed delivery
proof: On a Mac, a host-address grant box with no steering line resolves github.com to the proxy and clones through it, while a host-address box with no grant resolves github.com to GitHub's real address.
depends on:
- blocked by #2805
- blocked by #2855
- blocked by #2856
Proof
On a VM host (a Mac),
min box spec --network host_ipandmin session activatenow admit a grant box with no steering line, or withdnsorboth, and showsteering dns. A native host still refuses. Inside such a host_ip grant box, /etc/resolv.conf names the box's own steering answerer at 127.53.x.y. That answerer resolves github.com to the Box Egress Proxy's address, so git reaches GitHub through the proxy on port 443. A host_ip box with no grant, or with proxy_env or off steering, still resolves through the gateway and gets GitHub's real address. The unit tests show this against a stand-in resolver. Nobody ran the clone on a real Mac in this session.Notes from the agent: Design: plan() on a VM host starts a per-box steering answerer for a box whose own policy declares a credentialed upstream with dns or both steering. The answerer binds a free address in 127.53.0.0/16 at port 53. The plan names that address as the only nameserver. The answerer forwards every name that is not steered to the gateway's resolver (subnet.dns_server():53). attach() reads the box's leaf from /proc//cgroup and loads a base chain steer_ into inet minimal_class through
nft -f -. That chain returns for the box's leaf and rejects every other socket's packets to the answerer. teardown and abandon remove the chain and stop the answerer. Limits a reviewer must know: (1) The fence fails open. If nft refuses, or the process is not in a box leaf within 1 s, attach logs a warning and the box starts. The box's own lookups are steered either way. Between plan and attach the answerer is not fenced. (2) No test ran the fence against a real nft, because this box has no nft. The test uses a stand-in binary and checks the rendered rules. (3) A deny-all host_ip grant box gets no steered answers. The guest table dstnats its port-53 lookups to the guest DNS listener before they reach the answerer. (4) The answerer must bind port 53, so it needs the root guest daemon. That is …[truncated]Tests
cargo nextest run -p minimald host_ip_grant_box_steered_names_resolve_to_proxycargo nextest run -p minimald host_ip_lookup_outside_a_steered_box_resolves_realcargo nextest run -p minimal vm_host_admits_dns_and_both_for_own_ip_and_host_ipVerify
min task run agent-verifyexited 100 under rustc 1.99.0, which the repository does not pin (1.99.1 in rust-toolchain.toml); the PR is not a draft on that alone — CI, which honours the pin, is the gate.Closes #2857