8000 fix(minimald): admit allow-exposed and ask-yes ports at the box's relay gate by norrietaylor · Pull Request #2028 · gominimal/minimal · GitHub
[go: up one dir, main page]

Skip to content

fix(minimald): admit allow-exposed and ask-yes ports at the box's relay gate - #2028

Merged
norrietaylor merged 5 commits into
mainfrom
fix/guest-relay-admit-exposed-ports
Oct 7, 2026
Merged

norrietaylor merged 5 commits into
mainfrom
fix/guest-relay-admit-exposed-ports

Conversation

@norrietaylor
@norrietaylor norrietaylor commented Oct 6, 2026 •
Copy link
Copy Markdown
Member

Symptom

A port published from inside a box with min net expose under dynamic_ingress = allow was bound on the host and listed by min session policy, but a connection to it never reached the box. The live row read (pending; not yet reachable) and the expose reply ended ; not yet reachable. An ask answered yes had the same shape.

Cause

The box's relay gate (SessionGate::admits_tcp in crates/minimald/src/net/switch.rs) admitted only declared ports and listen-published ports. Only the listen watcher filled the listen-published set. The expose path, for allow and for an answered ask, bound the forward and never admitted the port, so the gate refused the SYN. When a listener later appeared on an expose-held port, the watcher settled it without admitting it either.

Fix

  • The gate keeps a third set, exposed, beside the declared and listen-published sets. admits_tcp and the inbound SYN check read all three.
  • publish_exposed_port (shared by allow and an answered ask) admits the port at the box's live gate once the forward stands and the mapping is recorded, in the same actor turn.
  • The box's stop, which revokes every runtime expose, withdraws the admission before it unbinds the forwards. A listener closing never touches the exposed set, so NET-017's withdraw-on-close still applies only to listen-published ports.
  • The expose reply is now a plain published port N at ADDR. GetLiveIngress serves the rows as recorded (pending: false). The wire field stays so a client can still read an older daemon's rows, and the CLI's rendering of pending: true is kept for that case only.
  • Spec 18: the NET-044 annotation says "publish" means reachable (as in NET-040), for both allow and a yes-answered ask. The NET-138 annotation is left as on main: #1898 rewrites it and covers exposed ports there.
  • The admit site asserts (debug_assert_eq!) that the admitted port is the forward's internal port, the one the stop withdraws, so a future host-port remap cannot split the admission from its withdrawal.
  • minimald-rpc: the doc comment on LiveMapping::pending marks the field deprecated. Daemons from this change onward always send Some(false); the field can go when the support window for older daemons ends.
  • scripts/session-e2e.sh: in proof_expose_from_inside_box, the VM lane's host probe was carried as a known gap and is now a hard pass. The native lane's allow leg used to require the host probe to fail and the row to read pending. It now requires the probe to reach the in-box server and the row to carry no caveat. Every expose reply check now rejects the old caveat.

Requirement

NET-044: WHEN a dynamic ingress request is decided allow THE SYSTEM SHALL publish the port. In spec 18, "publish" means a request to the port returns the in-box server's response (NET-040). NET-047 covers only rejected requests and does not allow a pending window.

Verification

  • New unit tests:
    • net::switch::tests::gate_admits_an_exposed_port_until_the_expose_is_revoked: the exposed port is admitted and passes the SYN gate, an unadmitted port stays refused, a listener withdrawal leaves it admitted, and the revocation withdraws it.
    • session::tests::expose_allow_admits_the_port_at_the_box_gate_until_stop: an allow expose admits the port at the box's registered gate, an unpublished port stays refused, and the stop withdraws the admission.
    • session_host::tests::expose_ask_yes_admits_the_port_at_the_box_gate: an ask answered yes through a real attach admits the port, and an unpublished port stays refused.
    • net::switch::tests::withdrawing_an_exposed_port_a_declaration_holds_keeps_its_connections: revoking an expose on a port a declared mapping also holds returns 0, keeps the declaration's admission, and leaves its connection's reply-flow record in place.
    • net::listeners::tests::a_port_the_runtime_expose_published_is_never_bound_or_withdrawn now also asserts that the watcher leaves the exposed admission in place after the listener closes.
  • Updated tests that pinned the old pending and caveat behaviour: expose_allow_publishes_and_lists, the env channel reply tests, and the live-ingress listings.
  • just test-cross (musl in Docker): clippy with -D warnings is clean. Because cargo stops at the first failing binary, the minimald, minimal and minimald-rpc tests were then run through cross test with the recipe's flags plus --no-fail-fast. Every new and updated test passes. The failures left in that run (13 in minimald lib: relay, hooks and nsenter tests; 6 in the minimal lib: resolver, stopped-VM timing and a stderr hint; 2 in netns_root_integration) are environmental under emulation. The same 13 minimald tests fail the same way on main in this container.
  • After merging main and adding the review follow-ups: cross clippy -p minimald -p minimald-rpc --all-targets -- -D warnings is clean, and cross test -p minimald --lib filtered to the expose, withdraw and gate tests ran 59: 58 passed, including the new withdrawing_an_exposed_port_a_declaration_holds_keeps_its_connections. The one failure, expose_ask_shed_mid_dialog_is_the_daemons_refusal (from main, untouched here), hit its 30 s timeout under emulation while the batch ran and passed when rerun alone.
  • just lint-shell passes. just lint-prose reports no alerts on the changed lines.

Not verified here: the scripts/session-e2e.sh changes (the VM-lane reach check, now a hard pass, and the native allow leg's reach) need a real VM or KVM lane. The CI e2e lanes prove them. Running just e2e was out of scope on this host.

Merge-order note

#2027 (bounded audit log, fail closed) adds withdraw_unaudited in crates/minimald/src/session.rs. Whichever of #2027 and this PR merges second must:

  • make withdraw_unaudited call live_gate(switch_address).withdraw_exposed(port) before the unexpose, and
  • add an assert to expose_unrecordable_allow_is_refused_and_deny_still_refuses that a connection opened to the exposed port before the withdrawal is reset.

#2027 was not on main when this branch last merged it, so this PR does not carry that wiring.

…ay gate

A port published with min net expose under dynamic_ingress = allow, or
an ask answered yes, was bound and listed but refused at the box's relay
gate: the gate admitted only declared and listen-published ports. The
gate now keeps a third, explicitly exposed set. The publish admits the
port once its forward stands, the box's stop withdraws it, and a
listener closing never touches it (NET-044, NET-017).

The expose reply and the live-ingress rows no longer carry the
'not yet reachable' caveat, the spec 18 annotations name exposed ports
in the admitted set, and the session e2e requires the host to reach
the allow-exposed port instead of tolerating a miss.
@norrietaylor
norrietaylor requested a review from a team as a code owner October 6, 2026 22:42
@coderabbitai
coderabbitai Bot commented Oct 6, 2026 •
Copy link
Copy Markdown
Contributor

Warning

Review limit reached

  • Run on-demand review

This review includes 12 billable files and costs up to $3.00.

  • Ask an admin to make reviews automatic

Open in CodeRabbit

Reviews can continue after your included limit without a manual trigger. An admin must approve usage-based billing.

Or wait 7 minutes for your next included review.

Check out review usage here.

View limit details

Limit details: You’ve used all 2 included reviews currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Essentials
  • Run ID: bbc5e74c-4fc0-4151-96b6-e034a6190f08
📥 Commits

Reviewing files that changed from the base of the PR and between 95dcab8 and bc96067.

📒 Files selected for processing (12)
  • crates/minimal/src/cmd/session.rs
  • crates/minimald-rpc/src/lib.rs
  • crates/minimald/src/env.rs
  • crates/minimald/src/net/listeners.rs
  • crates/minimald/src/net/switch.rs
  • crates/minimald/src/rpc.rs
  • crates/minimald/src/session.rs
  • crates/minimald/src/session/tests.rs
  • crates/minimald/src/session_host/tests.rs
  • docs/reference/cli-min.md
  • docs/specs/18-spec-box-networking/18-spec-box-networking.md
  • scripts/session-e2e.sh
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

@github-actions
github-actions Bot commented Oct 6, 2026
Copy link
Copy Markdown
Contributor

Spec lint

docs/specs/18-spec-box-networking/18-spec-box-networking.md — 11 finding(s)

docs/specs/18-spec-box-networking/18-spec-box-networking.md:159: P004 NET-009 names test 'native_resolution_without_proxy_env', which was not found in the repository
docs/specs/18-spec-box-networking/18-spec-box-networking.md:304: P004 NET-040 names test 'fresh_install_own_ip_ingress_publishes_loopback', which was not found in the repository
docs/specs/18-spec-box-networking/18-spec-box-networking.md:309: P004 NET-041 names test 'installer_switch_binary_executable', which was not found in the repository
docs/specs/18-spec-box-networking/18-spec-box-networking.md:347: P004 NET-048 names test 'linux_amd64_manifest_ships_vm_stack', which was not found in the repository
docs/specs/18-spec-box-networking/18-spec-box-networking.md:352: P004 NET-049 names test 'fresh_linux_kvm_activate_local_minvmd', which was not found in the repository
docs/specs/18-spec-box-networking/18-spec-box-networking.md:357: P004 NET-050 names test 'linux_arm64_manifest_ships_vm_stack', which was not found in the repository
docs/specs/18-spec-box-networking/18-spec-box-networking.md:362: P004 NET-051 names test 'fresh_arm64_kvm_activate_local_minvmd', which was not found in the repository
docs/specs/18-spec-box-networking/18-spec-box-networking.md:652: P004 NET-139 names test 'vsock_rpc_refuses_non_host_cid', which was not found in the repository
docs/specs/18-spec-box-networking/18-spec-box-networking.md:669: P004 NET-132 names test 'proxy_sees_each_vm_box_by_its_switch_address', which was not found in the repository
docs/specs/18-spec-box-networking/18-spec-box-networking.md:695: P004 NET-134 names test 'deny_all_box_reaches_proxy_and_no_other_host_port', which was not found in the repository
docs/specs/18-spec-box-networking/18-spec-box-networking.md:719: P004 NET-107 names test 'session_outbound_request', which was not found in the repository

Advisory. None of this blocks the merge; the review decides.

Advisory, and never a merge gate. Linter: gominimal/foundry@b913de94, spec format: How Minimal Writes Specs.

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

Approved: no findings at bc960674ee35.

Nothing to do.

Clobber: none: git log on the two touched core files (switch.rs, session.rs) shows only this feature branch's own commits and its main merges; no unrelated revert.

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

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 — No findings.

@minimal-foundry minimal-foundry Bot added the review:approved minimal-review approved the current head label Oct 6, 2026
…al port

Revert the NET-138 annotation to main's text; a separate spec PR rewrites it
and already covers exposed ports. The NET-044 annotation edit stays.

Assert at the admit site that the admitted port is the forward's internal
port, the one the box's stop withdraws, so a host-port remap cannot split
them. Add a gate test: revoking an expose on a port a declared mapping also
holds keeps the declaration's admission and its connection, and returns 0.
Mark LiveMapping::pending deprecated in its doc comment.
@minimal-foundry minimal-foundry Bot left a comment
Copy link
Copy Markdown
Contributor

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 — No findings.

@norrietaylor
norrietaylor enabled auto-merge October 7, 2026 02:36
@minimal-foundry minimal-foundry Bot left a comment
Copy link
Copy Markdown
Contributor

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 — No findings.

@norrietaylor
norrietaylor added this pull request to the merge queue Oct 7, 2026
Merged via the queue into main with commit 584cb47 Oct 7, 2026
37 checks passed
@norrietaylor
norrietaylor deleted the fix/guest-relay-admit-exposed-ports branch October 7, 2026 04:01
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.

2 participants

0