8000 T121 — Steer a host-address grant box's lookups and admit dns on a VM host by minimal-foundry[bot] · Pull Request #3079 · gominimal/minimal · GitHub
[go: up one dir, main page]

Skip to content

T121 — Steer a host-address grant box's lookups and admit dns on a VM host - #3079

Merged
norrietaylor merged 5 commits into
mainfrom
agent/2857-steer-a-host-address-grant-box-s-lookups-and-adm
Nov 6, 2026
Merged

norrietaylor merged 5 commits into
mainfrom
agent/2857-steer-a-host-address-grant-box-s-lookups-and-adm

Conversation

@minimal-foundry
Copy link
Copy Markdown
Contributor

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_ip on a Mac shows steering = "dns" resolved and no refusal; inside a host-address grant box getent hosts 8000 github.com prints the proxy's address, and inside a host-address box with no grant it prints GitHub's real address
observability: 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_ip box
layers:
- resolver:
- crates/minimald/src/net/provider.rs: a host_ip box with a credentialed upstream and dns or both steering gets a resolver plan that names the steering answerer; every other host_ip box 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_names is true on a VM host for own_ip and host_ip boxes, so BEP-017 refuses dns and both only on a host with no VM
- crates/minimal/src/cmd/box_spec.rs: the preview reads the same host fact for host_ip
test: 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_ip and min session activate now admit a grant box with no steering line, or with dns or both, and show steering 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

line exit tail
cargo nextest run -p minimald host_ip_grant_box_steered_names_resolve_to_proxy 0 Applying loadouts: default Running task runtime-implement-test-0 in session task-runtime-implement-test-0-c172... Compiling paths v0.0.1 (/workbench/crates/paths) Compiling args v0.0.1 (/workbench/crates/args) Compiling ot v0.0.1 (/workbench/crates/ot) Compiling client-config v0.0.1 (/wo
cargo nextest run -p minimald host_ip_lookup_outside_a_steered_box_resolves_real 0 Applying loadouts: default Running task runtime-implement-test-1 in session task-runtime-implement-test-1-4a8e... Compiling stdlib v0.0.22 (/workbench/crates/stdlib) Compiling version v0.8.0 (/workbench/crates/version) Compiling decode v0.0.1 (/workbench/crates/decode) Compiling mlog v0.
cargo nextest run -p minimal vm_host_admits_dns_and_both_for_own_ip_and_host_ip 0 Applying loadouts: default Running task runtime-implement-test-2 in session task-runtime-implement-test-2-607f... Compiling paths v0.0.1 (/workbench/crates/paths) Compiling args v0.0.1 (/workbench/crates/args) Compiling ot v0.0.1 (/workbench/crates/ot) Compiling client-config v0.0.1 (/wo

Verify

min task run agent-verify exited 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.

ca2",
                "span_id": "2287fac11a650e30",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "session.message",
            fields: {
                "kind": "GetPaths",
                "parent_span_id": "e307573fe9091ca2",
                "span_id": "ef7d8a7772d0c053",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "sessions.manager.message",
            fields: {
                "kind": "GetRecord",
                "parent_span_id": "e307573fe9091ca2",
                "queued_behind": "0",
                "span_id": "6c59d1000c91acc5",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "sessions.manager.message",
            fields: {
                "kind": "DeleteSession",
                "parent_span_id": "e307573fe9091ca2",
                "queued_behind": "0",
                "span_id": "ee216a3b408fe4e9",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "session.message",
            fields: {
                "kind": "Destroy",
                "parent_span_id": "ee216a3b408fe4e9",
                "span_id": "62b49319ac7d1ea7",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "sessions.manager.message",
            fields: {
                "kind": "HandOverName",
                "parent_span_id": "ee216a3b408fe4e9",
                "queued_behind": "0",
                "span_id": "4a731d00b7979028",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
    ]
    note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

  Cancelling due to test failure: 3 tests still running
        PASS [   2.538s] ( 969/2338) minimald box_actor::tests::a_box_re_registered_inside_the_landing_window_keeps_its_grant
        PASS [   2.799s] ( 970/2338) minimald box_actor::tests::a_box_nobody_handed_an_address_publishes_at_the_answerers_grant
        PASS [   2.512s] ( 971/2338) minimald box_actor::tests::a_contended_walk_never_replaces_a_present_verdict
────────────
     Summary [  75.934s] 971/2338 tests run: 970 passed, 1 failed, 27 skipped
        FAIL [   3.887s] ( 968/2338) minimal::otel_cli exec_run_attach_and_task_run_share_the_commands_trace
warning: 1367/2338 tests were not run due to test failure (run with --no-fail-fast to run all tests, or run with --max-fail)
error: test run failed

Closes #2857

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.
@coderabbitai
coderabbitai Bot commented Nov 6, 2026 •
Copy link
Copy Markdown
Contributor

Important

Review skipped

Bot user detected.

To trigger a single review, invoke the @coderabbitai review command.

Run configuration

  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 215d882f-f7d7-44dd-95e3-45efb43bafb8

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Tip

🎯 Customize your risk assessments

Guide how CodeRabbit assesses merge risk and security architecture risk. Highlight sensitive changes and explain your security boundaries using Merge Risk Instructions and Security Risk Instructions in your review settings. Security architecture risk assessments require the Advanced plan or above.

✅ Automatic approvals, with your rules

Let CodeRabbit approve pull requests once required threads are resolved, pre-merge checks pass, and your risk limits are met. Enable Automatic approvals in review settings, with overrides for teams or file paths.

@minimal-foundry
minimal-foundry Bot commented Nov 6, 2026 •
Copy link
Copy Markdown
Contributor Author

Approved: every review thread resolved at 23f3537221a0.

@norrietaylor resolved the last thread, which releases the approval this review had withheld.

Nothing to do.

@minimal-foundry minimal-foundry Bot left a comment
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minimal-review — advisory review

Verdict COMMENT — 3 findings, 3 inline.

Panel: 3 of 3 personas ran (correctness, security, supply-chain).

Comment thread crates/minimald/src/net/provider.rs Resolved
Comment thread crates/minimald/src/net/dns.rs Resolved
Comment thread crates/minimald/src/net/classifier.rs Resolved
@minimal-arch minimal-arch Bot left a comment
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread crates/minimald/src/net/provider.rs Resolved
Comment thread crates/minimald/src/net/provider.rs Resolved
Comment thread crates/minimald/src/net/provider.rs Resolved
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.
@minimal-foundry minimal-foundry Bot left a comment
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minimal-review — advisory review

Verdict COMMENT — 1 finding, 1 inline.

Panel: 3 of 3 personas ran (correctness, security, supply-chain).

Comment thread crates/minimald/src/net/dns.rs Resolved
@minimal-arch minimal-arch Bot left a comment
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@minimal-foundry
Copy link
Copy Markdown
Contributor Author

Round 1 of 10 on the runtime's review findings, pushed as 8454d5108a2d.

line exit tail
cargo nextest run -p minimald host_ip_grant_box_steered_names_resolve_to_proxy 0 Applying loadouts: default Running task runtime-implement-test-0 in session task-runtime-implement-test-0-fef4... Compiling paths v0.0.1 (/workbench/crates/paths) Compiling args v0.0.1 (/workbench/crates/args) Compiling ot v0.0.1 (/workbench/crates/ot) Compiling client-config v0.0.1 (/wo
cargo nextest run -p minimald host_ip_lookup_outside_a_steered_box_resolves_real 0 Applying loadouts: default Running task runtime-implement-test-1 in session task-runtime-implement-test-1-b209... Compiling stdlib v0.0.22 (/workbench/crates/stdlib) Compiling version v0.8.0 (/workbench/crates/version) Compiling decode v0.0.1 (/workbench/crates/decode) Compiling mlog v0.
cargo nextest run -p minimal vm_host_admits_dns_and_both_for_own_ip_and_host_ip 0 Applying loadouts: default Running task runtime-implement-test-2 in session task-runtime-implement-test-2-bf56... Compiling paths v0.0.1 (/workbench/crates/paths) Compiling ot v0.0.1 (/workbench/crates/ot) Compiling args v0.0.1 (/workbench/crates/args) Compiling client-config v0.0.1 (/wo

Verify min task run agent-verify exited 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.

b2d",
                "span_id": "a49f9f967fd2d2d9",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "session.message",
            fields: {
                "kind": "GetPaths",
                "parent_span_id": "75baee56c4d50b2d",
                "span_id": "5c4540f0bc72ab05",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "sessions.manager.message",
            fields: {
                "kind": "GetRecord",
                "parent_span_id": "75baee56c4d50b2d",
                "queued_behind": "0",
                "span_id": "f820cb8097bdbf98",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "sessions.manager.message",
            fields: {
                "kind": "DeleteSession",
                "parent_span_id": "75baee56c4d50b2d",
                "queued_behind": "0",
                "span_id": "d0905df9c74d6faf",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "session.message",
            fields: {
                "kind": "Destroy",
                "parent_span_id": "d0905df9c74d6faf",
                "span_id": "ba463c05b6933559",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "sessions.manager.message",
            fields: {
                "kind": "HandOverName",
                "parent_span_id": "d0905df9c74d6faf",
                "queued_behind": "0",
                "span_id": "178b2d9bb0678dec",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
    ]
    note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

  Cancelling due to test failure: 3 tests still running
        PASS [   2.462s] ( 969/2338) minimald box_actor::tests::a_box_nobody_handed_an_address_publishes_at_the_answerers_grant
        PASS [   2.428s] ( 970/2338) minimald box_actor::tests::a_box_re_registered_inside_the_landing_window_keeps_its_grant
        PASS [   2.484s] ( 971/2338) minimald box_actor::tests::a_contended_walk_never_replaces_a_present_verdict
────────────
     Summary [  66.818s] 971/2338 tests run: 970 passed, 1 failed, 27 skipped
        FAIL [   3.508s] ( 968/2338) minimal::otel_cli exec_run_attach_and_task_run_share_the_commands_trace
warning: 1367/2338 tests were not run due to test failure (run with --no-fail-fast to run all tests, or run with --max-fail)
error: test run failed

Review threads the coder judged:

  • 👍 valid — PRRT_kwDOPdfcxM60VQ16: Plan now closes the answerer to every leaf right after it binds, and attach admits only the box leaf. A refused fence at either step stops the answerer, removes its chain and fails the box. (not posted on the thread: not an open thread on the pull request)
  • 👍 valid — PRRT_kwDOPdfcxM60VQ1-: Oversized or unparseable datagrams (and a reply that cannot be encoded) now get SteeringVerdict::Drop, never Forward. They get no reply, not SERVFAIL, because an unreadable message has no id to answer. (not posted on the thread: not an open thread on the pull request)
  • 👍 valid — PRRT_kwDOPdfcxM60VQ1_: fence_steering_answerer now refuses a leaf path outside [A-Za-z0-9._-@:+/] through is_safe_leaf_path before it renders the nft script. The classifier test pins quote, newline, space and semicolon cases. (not posted on the thread: not an open thread on the pull request)

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.

@norrietaylor
Copy link
Copy Markdown
Member

/revise Review finding PRRT_kwDOPdfcxM60Vjwb at 8454d51 (crates/minimald/src/net/dns.rs, SteeringAnswerer::start). The steered box's only resolver is the answerer, and the answerer binds UDP only. The fence admits TCP and UDP to port 53. A forwarded reply with the truncated bit set makes the box retry over TCP, the connection is refused, and the lookup fails. Before this change the box reached the gateway resolver, which takes TCP. Do these two things:

  1. Serve TCP on the same address and port. Answer the steered names over TCP as over UDP (the proxy's address, NODATA for AAAA, HTTPS and SVCB, SERVFAIL while the listener is down), and forward every other name to the node's resolver over TCP.
  2. Make Drop release the address at once. The forward tasks must not keep the socket alive after the loop is aborted, so a restart does not fail with AddrInUse.

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 Drop. Run the T121 test lines and the new tests. Keep each commit message line at 100 characters or fewer.

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.
@minimal-foundry minimal-foundry Bot left a comment
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minimal-review — approval withheld

Verdict COMMENT — APPROVE withheld: 1 unresolved thread — minimal-foundry[bot] crates/minimald/src/net/dns.rs. No findings.

Panel: 3 of 3 personas ran (correctness, security, supply-chain).

@minimal-arch minimal-arch Bot left a comment
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@minimal-foundry minimal-foundry Bot left a comment
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@minimal-foundry minimal-foundry Bot added the review:approved minimal-review approved the current head label Nov 6, 2026
@minimal-foundry
Copy link
Copy Markdown
Contributor Author

Round 2 of 10 on a /revise note, pushed as 23f3537221a0.

line exit tail
cargo nextest run -p minimald host_ip_grant_box_steered_names_resolve_to_proxy 0 Applying loadouts: default Running task runtime-implement-test-0 in session task-runtime-implement-test-0-9d14... Compiling paths v0.0.1 (/workbench/crates/paths) Compiling args v0.0.1 (/workbench/crates/args) Compiling ot v0.0.1 (/workbench/crates/ot) Compiling client-config v0.0.1 (/wo
cargo nextest run -p minimald host_ip_lookup_outside_a_steered_box_resolves_real 0 Applying loadouts: default Running task runtime-implement-test-1 in session task-runtime-implement-test-1-5b5c... Compiling stdlib v0.0.22 (/workbench/crates/stdlib) Compiling version v0.8.0 (/workbench/crates/version) Compiling decode v0.0.1 (/workbench/crates/decode) Compiling mlog v0.
cargo nextest run -p minimal vm_host_admits_dns_and_both_for_own_ip_and_host_ip 0 Applying loadouts: default Running task runtime-implement-test-2 in session task-runtime-implement-test-2-5cba... Compiling paths v0.0.1 (/workbench/crates/paths) Compiling args v0.0.1 (/workbench/crates/args) Compiling ot v0.0.1 (/workbench/crates/ot) Compiling client-config v0.0.1 (/wo

Verify min task run agent-verify exited 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.


                "span_id": "15dbd3f9afaa6faf",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "rpc",
            fields: {
                "parent_span_id": "18815ef62c34606d",
                "rpc": "minimald-v1-DestroySession",
                "span_id": "106c4a8cb235165a",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "sessions.manager.message",
            fields: {
                "kind": "GetRecord",
                "parent_span_id": "106c4a8cb235165a",
                "queued_behind": "0",
                "span_id": "1aefc6baa626ee14",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "sessions.manager.message",
            fields: {
                "kind": "DeleteSession",
                "parent_span_id": "106c4a8cb235165a",
                "queued_behind": "0",
                "span_id": "e667b97919549450",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "session.message",
            fields: {
                "kind": "Destroy",
                "parent_span_id": "e667b97919549450",
                "span_id": "4b7f30bbbbc88edc",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "sessions.manager.message",
            fields: {
                "kind": "HandOverName",
                "parent_span_id": "e667b97919549450",
                "queued_behind": "0",
                "span_id": "d1cf4862d511faab",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
    ]
    note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

  Cancelling due to test failure: 3 tests still running
        PASS [   2.526s] ( 972/2364) minimald box_actor::tests::a_box_re_registered_inside_the_landing_window_keeps_its_grant
        PASS [   2.600s] ( 973/2364) minimald box_actor::tests::a_box_nobody_handed_an_address_publishes_at_the_answerers_grant
        PASS [   2.609s] ( 974/2364) minimald box_actor::tests::a_contended_walk_never_replaces_a_present_verdict
────────────
     Summary [  75.861s] 974/2364 tests run: 973 passed, 1 failed, 27 skipped
        FAIL [   4.134s] ( 971/2364) minimal::otel_cli exec_run_attach_and_task_run_share_the_commands_trace
warning: 1390/2364 tests were not run due to test failure (run with --no-fail-fast to run all tests, or run with --max-fail)
error: test run failed

Review threads the coder judged:

  • 👍 valid — PRRT_kwDOPdfcxM60Vjwb: SteeringAnswerer now serves TCP at its UDP address and port: the same verdicts for steered names, and TCP forwards to the node's resolver for other names. Tasks hold only Weak socket refs and Drop cancels them, so a restart binds again at once. Tests: steering_answerer_answers_steered_names_over_tcp (not posted on the thread: not an open thread on the pull request)

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.

@norrietaylor norrietaylor left a comment
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gate passed at 23f3537 under the standing gate-4 rule.

@norrietaylor
norrietaylor added this pull request to the merge queue Nov 6, 2026
Merged via the queue into main with commit e04514e Nov 6, 2026
36 checks passed
@norrietaylor
norrietaylor deleted the agent/2857-steer-a-host-address-grant-box-s-lookups-and-adm branch November 6, 2026 16:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

review:approved minimal-review approved the current head

Projects

None yet

Development

Successfully merging this pull request may close these issues.

T121 — Steer a host-address grant box's lookups and admit dns on a VM host

1 participant

0