Repository navigation
fix(net): answer a macOS box's ports at the address its name resolves to (0.7.0 backport) - #2740
Closed
norrietaylor wants to merge 9 commits into
Closed
norrietaylor wants to merge 9 commits into
norrietaylor wants to merge 9 commits into
Conversation
* fix(minimal,minvmd): hold the answerer channel socket owner-only The box-zone answerer's channel socket was world-connectable: the launchd plist bound it SockPathMode 0666 and the systemd socket unit SocketMode=0666, leaving the peer-uid gate the only check on who may connect. Both units now bind it owner-only -- SockPathOwner the operator, SockPathMode 0600 on launchd; SocketUser the operator, SocketMode=0600 on systemd -- with the uid gate kept as defence in depth. An install whose socket node carries group or other bits -- one that predates the mode, or a hand that relaxed it -- now fails custody through a channel_mode fact, so the advisory re-surfaces and a step re-run re-binds the socket. The accept loop's foreign-refusal warn is rate-limited to a few lines a second, so a connect storm cannot flood the log; the refusals themselves are never limited. * fix(minimal,minimald,minvmd): name the other account on a refused channel connect A daemon whose connect the owner-only channel socket refuses now says the host's DNS service belongs to another account, not a bare Permission denied. The answerer step needs the operator's account to resolve, with no stand-in uid, and custody fails a channel socket the operator does not own. * fix(minimal,minvmd): leave another operator's DNS service alone A channel socket owned by another existing, non-root account is that operator's service: the answerer step is now unavailable with the same sentence the daemons say on a refused connect, instead of a custody failure whose step would reinstall the unit and take the service over. A root-owned socket, or one whose uid names no account, still fails custody so the step re-binds it. The minvmd tests that take the process-global foreign-refusal slots now run one at a time and start and end with every slot free, and the warn-rate test bounds the burst at two windows' worth of lines, so a slow runner that spreads the burst does not fail it. * fix(minimal): offer the step over a socket another account left behind A channel socket another account owns refuses the answerer step only when a service is installed; with none, the step's install removes the leftover socket before it loads the unit, so it is offered. --------- Co-authored-by: gominimal-aw-bot[bot] <281738952+gominimal-aw-bot[bot]@users.noreply.github.com> (cherry picked from commit 746770d)
…er (#2300) * fix(net): refuse out-of-zone names at a guest deny-subtree DNS listener A deny-all host-address box on a VM-backed guest resolved out-of-zone names at the switch gateway: the interim NET-079 carve-out admitted the gateway's port 53 whole, UDP and TCP, so a box that admits no destination could still ask a resolver beyond the machine — and DNS over TCP could retry past the verdict. The guest's deny subtree now steers its UDP-53 lookups onto a loopback listener on the answerer's port (NET-141 slice 2): deny_out admits the listener beside the reply direction, the dstnat match carries no destination address (the box asks the gateway like every other host-address box; the steer is the carve-out), and the listener itself answers REFUSED for every name outside the box zone — apex and a non-lowercase zone suffix among them — NODATA for an in-zone non-A question, and forwards the zone's A questions to the switch gateway alone. DNS over TCP is neither steered nor admitted. The guest daemon starts the listener after its classifier table loads; the daemon's own lookups are untouched (cgroup-keyed match, deny path only). The interim --gateway-resolver installer flag and every test, digest and pin that spelled the interim carve-out follow the new shape. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(net): bound and pin the guest DNS listener's forwards Connect each forward socket to the gateway so a datagram from any other sender is dropped, cap forwards in flight so a flood cannot exhaust the daemon's descriptors, and drop the needless to_string calls clippy rejects in the guest classifier integration test. * fix(net): refuse questionless and non-QUERY messages at the guest DNS listener decide() forwarded a message with no question, and an UPDATE or NOTIFY whose zone section named an in-zone A, because every per-question check passed vacuously. Answer the first REFUSED and the rest NOTIMP, so the forward path carries only standard lookups. Bind the listener directly so its log does not read as the box-zone answerer, pin the integration test's boot-log check to the exact example.com REFUSED line, and cite NET-079 (the deny-all case) rather than NET-141 at the lines this change added. --------- Co-authored-by: gominimal-aw-bot[bot] <281738952+gominimal-aw-bot[bot]@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> (cherry picked from commit 2c2f8cc)
crate::traced landed on main after the release branch was cut, so the cherry-pick of #2300 did not compile here.
v0.7.2 is tagged at the branch head, so the version check requires the next patch release.
…he box range (#2332) * fix(minvmd): hand boxes the interim when the host cannot bind the range Allocation drew an address out of the reserved 127.0.64.0/24 without measuring whether the host can bind it. Linux routes the whole of 127.0.0.0/8, but macOS answers only the addresses lo0 carries, so on a host without the range aliases the hand was dead: the zone answered an address at which nothing listens, while the box actually publishes at 127.0.0.1. A publish may not claim an address the host cannot bind, so allocation now probes the freshly resolved address with a TcpListener bind to port 0 and hands the 127.0.0.1 interim instead when the bind refuses; the address the guest then publishes is the one the zone answers, and every part agrees. A host that gains the aliases mid-way hands range addresses out on the next allocation. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(minvmd): keep a box's address across the bind probe A box created at the 127.0.0.1 interim now keeps it on resume: the resume does not ask the answerer, which records nothing for an interim box and would hand a range address once the host binds the range. A box's own held or quarantined address comes back without the probe, so its address never moves under it. The probe reads only EADDRNOTAVAIL as an unaliased range and logs a warning with the error. Any other bind error refuses the allocation. Unit tests take a probe that always binds, so hosts without the lo0 aliases still hand distinct range addresses in the service-path tests. * fix(minvmd): read the box range through the one whole-range probe The address book now hands a fresh box address only when switch::loopback::probe reads the whole box range present, so a partial alias set gets the 127.0.0.1 interim, as min list advises finalize-install. The per-address bind copy is gone. A probe refused for any reason other than an absent address still refuses the allocation. The probe is a field of AddressBook: the live probe in production, a present range under test, so the service-path tests hand range addresses on a Mac without the lo0 aliases. The warning that the range is absent is logged once per episode, with the probe's summary. A control test proves a box created at the interim resumes at 127.0.0.1 after the host gains the range while a new box gets a range address, and the book test now asserts the range address only for a new box. * fix(minvmd): keep a range box's address on resume after a restart A restarted answerer holds no record of a dormant box. Where the host could not bind the box range, a resume of a range box was handed the 127.0.0.1 interim and refused as LoopbackMoved. The resume now sends the address the box was created at. The book hands it back without the range probe and records it, while no other box holds it or released it inside its quarantine. An answerer that predates the field ignores it. * fix(minvmd): build #2348's dormant-creation test against allocate's keep The merged test calls RegisteredTables::allocate without the keep argument this branch added. Pass None there, and say at the fresh registration's call site why it keeps no address. --------- Co-authored-by: gominimal-aw-bot[bot] <281738952+gominimal-aw-bot[bot]@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: minimal-foundry[bot] <327719912+minimal-foundry[bot]@users.noreply.github.com> Co-authored-by: Norrie Taylor <91171431+norrietaylor@users.noreply.github.com> (cherry picked from commit 379c2f0)
…olves to (T98) (#2713) * fix(minvmd): hand the interim when the host cannot bind the range Bring in the work of #2332 from its merge-queue commit. Allocation reads the whole box range through switch::loopback::probe and hands the 127.0.0.1 interim when the host cannot bind it, so the zone never answers an address that nothing listens at. A resume keeps the range or interim address the box was created at across an answerer restart. * fix(minimald): re-walk the box range at each session start on a VM A microVM daemon walked the host's loopback once, at its start. After `min finalize-install` the host binds the range, but the daemon kept the absent verdict, so new boxes stood at the 127.0.0.1 interim until the VM restarted. The create handler on a VM-backed daemon now asks the forwarder's walk, not the guest's own lo, so the reply's interim flag is the host's verdict. While the landed verdict is not present, each session start walks again. A present landing moves every box at the interim onto its range address through land_range_verdict's present arm. Walks run one at a time on the shared probe port. * test(minimald): two boxes created after the range lands hold their own The boxes in each_box_gets_own_loopback_address are now created after a session-start walk lands the range present over an absent daemon-start verdict. The test checks that each box's forwards bind at its own range address. * test(e2e): assert a macOS box answers at its range address On the macOS lane the fresh-install case runs the script that min finalize-install runs on a running VM. It then asserts that a new own-IP box answers at <name>.min.internal:8080 and at its 127.0.64.N address, and that min ls names that address. * fix(minimald): bind-probe the range at every session start The range can go away under a running VM. A present verdict no longer skips the session-start walk, so a present-to-absent walk lands the absent arm and moves the boxes in the range onto the 127.0.0.1 interim. * docs(specs): publish NET-040 ports at the address the box name resolves to * fix(minimald): count a held probe port as a bound range address The session-start range walk binds port 21064 on each range address. A box forward or a host_ip mirror on that port made the forwarder refuse the round, and the walk read the whole range absent. Every box then left its session-stable address. post_json_within now gives the forwarder's in-use refusal the AddrInUse kind. The walk counts such a round as bound and sends no unexpose for it. * fix(minimald): keep a present range over a walk with no answer A session-start walk that times out or cannot reach the forwarder is a fault of the channel, not an absent range. Over a landed present verdict it now lands nothing, so running boxes keep their range addresses. Only a refusal the forwarder answered lands the absent arm. The absent landing also moves the node's own address off the range onto the 127.0.0.1 interim, so host-address names stop answering an address the host cannot bind. * fix(minimald): release a leftover range probe forward before reading it The forwarder answers 'already running' from its own forward table, so a probe forward that an earlier walk left bound read as a bound address on every later walk. The walk now marks each address before its expose and clears the mark once nothing of its own is held there. At a marked address an in-use answer releases the leftover and asks again. --------- Co-authored-by: minimal-foundry[bot] <327719912+minimal-foundry[bot]@users.noreply.github.com> Co-authored-by: Norrie Taylor <91171431+norrietaylor@users.noreply.github.com> (cherry picked from commit 64d3358)
The two cherry-picks before this one keep main's text. Main renamed and added code that release/0.7.0 does not have, so this commit adapts it: - The box_manager module is sessions.rs here. The cherry-pick placed #2713's box_manager.rs and box_manager/handle.rs hunks in sessions.rs: RangeWalk, RangeWalkSource and RangeWalkAnswer, the range_walk cell on ManagerHandle and WeakManagerHandle, session_start_range_walk and install_range_walk_standin. BoxManager::init is Manager::init, and the create handler's answer type is crate::sessions::RangeWalkAnswer. - The rpc tests live inline in rpc.rs here, so session_start_rewalks_range_after_finalize_install and its spawn_holding_forwarder joined that module, reformatted for its indent. - crate::traced is not on release. The stand-in forwarders in the policy and rpc tests spawn with tokio::spawn. - The boxes crate is sessions here, so the answerer imports sessions::core::zone_answer. - ADDRESS_IN_USE_BODY came with #2352's held-port refusal, which release lacks. post_json_within reads it to give a held listener the AddrInUse kind, so this adds the constant alone, with main's text, beside ALREADY_RUNNING_BODY. - #2332 also passed None to RegisteredTables::allocate in the dormant creation tests that #2348 added on main. Release has neither those tests nor the dormant rows, so the cherry-pick kept the release's tests there. Every test both pull requests add is kept, with no assertion dropped. The NET-040 spec line applies to release's spec unchanged and is kept. The behaviour is #2332's and #2713's: a box's ports answer at the address its name resolves to.
Contributor
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the
|
Contributor
|
Approved: no findings at Nothing to do. Clobber: none seen. |
…s-box-address-0.7.0 # Conflicts: # scripts/install-host-classifier.sh
norrietaylor
pushed a commit
that referenced
this pull request
Nov 4, 2026
…he box range (#2332) Prerequisite for T98. Allocation drew an address out of the reserved 127.0.64.0/24 without measuring whether the host can bind it. Linux routes all of 127.0.0.0/8, but macOS answers only the addresses lo0 carries, so on a host without the range aliases the zone answered an address nothing listens at. The address book now hands a fresh box address only when switch::loopback::probe reads the whole range present, and hands the 127.0.0.1 interim otherwise. A probe refused for any reason but an absent address refuses the allocation. A box keeps its address across the probe: an interim box resumes at the interim, and a resume sends the address it was created at, so a restarted answerer hands it back. Backport of #2332, folded from #2740. Adapted to the line: the answerer imports sessions::core::zone_answer. Dropped: the edit to main's dormant-creation tests, since the line has neither those tests nor dormant rows. (cherry picked from commit 379c2f0)
norrietaylor
pushed a commit
that referenced
this pull request
Nov 4, 2026
…olves to (#2713) T98. A microVM daemon walked the host's loopback once, at its start, so after min finalize-install new boxes stood at the 127.0.0.1 interim until the VM restarted. The create handler on a VM-backed daemon now asks the forwarder's walk, not the guest's own lo, and every session start walks again, so a present landing moves boxes at the interim onto their range addresses and a present-to-absent walk moves them back. Walks run one at a time on the shared probe port. A held probe port counts as bound, a leftover probe forward is released before it is read, and a walk that gets no answer keeps a present verdict. The absent landing also moves the node's own address to the interim. The NET-040 spec line and the macOS e2e case come with the change. Backport of #2713, folded from #2740. Adapted to the line: - box_manager.rs and box_manager/handle.rs hunks land in sessions.rs: RangeWalk, RangeWalkSource and RangeWalkAnswer, the range_walk cell on ManagerHandle and WeakManagerHandle, session_start_range_walk and install_range_walk_standin. - rpc/tests.rs hunks land in the inline tests in rpc.rs. - crate::traced::spawn becomes tokio::spawn in the stand-in forwarders. - ADDRESS_IN_USE_BODY is shared with #2352: one copy stays in net/policy.rs. (cherry picked from commit 64d3358)
Merged
8000
norrietaylor
pushed a commit
that referenced
this pull request
Nov 5, 2026
…he box range (#2332) Prerequisite for T98. Allocation drew an address out of the reserved 127.0.64.0/24 without measuring whether the host can bind it. Linux routes all of 127.0.0.0/8, but macOS answers only the addresses lo0 carries, so on a host without the range aliases the zone answered an address nothing listens at. The address book now hands a fresh box address only when switch::loopback::probe reads the whole range present, and hands the 127.0.0.1 interim otherwise. A probe refused for any reason but an absent address refuses the allocation. A box keeps its address across the probe: an interim box resumes at the interim, and a resume sends the address it was created at, so a restarted answerer hands it back. Backport of #2332, folded from #2740. Adapted to the line: the answerer imports sessions::core::zone_answer. Dropped: the edit to main's dormant-creation tests, since the line has neither those tests nor dormant rows. (cherry picked from commit 379c2f0)
norrietaylor
pushed a commit
that referenced
this pull request
Nov 5, 2026
…olves to (#2713) T98. A microVM daemon walked the host's loopback once, at its start, so after min finalize-install new boxes stood at the 127.0.0.1 interim until the VM restarted. The create handler on a VM-backed daemon now asks the forwarder's walk, not the guest's own lo, and every session start walks again, so a present landing moves boxes at the interim onto their range addresses and a present-to-absent walk moves them back. Walks run one at a time on the shared probe port. A held probe port counts as bound, a leftover probe forward is released before it is read, and a walk that gets no answer keeps a present verdict. The absent landing also moves the node's own address to the interim. The NET-040 spec line and the macOS e2e case come with the change. Backport of #2713, folded from #2740. Adapted to the line: - box_manager.rs and box_manager/handle.rs hunks land in sessions.rs: RangeWalk, RangeWalkSource and RangeWalkAnswer, the range_walk cell on ManagerHandle and WeakManagerHandle, session_start_range_walk and install_range_walk_standin. - rpc/tests.rs hunks land in the inline tests in rpc.rs. - crate::traced::spawn becomes tokio::spawn in the stand-in forwarders. - ADDRESS_IN_USE_BODY is shared with #2352: one copy stays in net/policy.rs. (cherry picked from commit 64d3358)
Member
Author
|
Folded into #3019, which landed on |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Backports #2713 (task T98, #2687) to
release/0.7.0, together with #2332, which it builds on.On macOS, a box's name resolves to its own loopback address (127.0.64.N), but the guest walked that range once at VM start and fell back to publishing every port on 127.0.0.1. So
curl http://<box>.min.internal:<port>andcurl 127.0.64.N:<port>failed while127.0.0.1:<port>worked. With this change a box's ports answer at the address its name resolves to: the range is walked again at box start, so a range made bindable after the VM started (min finalize-install) is picked up. A host that cannot bind the range hands the box the loopback interim and says so.Stacked on #2678; merge that first. #2678 carries the 0.7.3 version bump and the
pulldown-cmarkadvisory fix. Until it merges, this diff also shows #2678's commits.Commits
d64e5630fcherry-picks fix(net): hand boxes the loopback interim when the host cannot bind the box range #2332. Its edit to main's dormant-creation tests is left out, because this branch has neither those tests nor dormant rows.1266ba62acherry-picks feat(minimald): answer a macOS box's ports at the address its name resolves to (T98) #2713, with the subject'sfeatchanged tofix(the release version check rejects afeatcommit).box_manager.rs,box_manager/handle.rsandrpc/tests.rsare not on this branch, so their hunks go intosessions.rsand the inline tests inrpc.rs, withmain's text. The NET-040 spec hunk applies as is.a3392daa2adapts it to the release branch:RangeWalk,session_start_range_walk, the stand-in hook) sits onManagerHandle, this branch'sBoxManager.tokio::spawnreplacescrate::traced, which is not on this branch.sessions::core::zone_answer.ADDRESS_IN_USE_BODYis added withmain's text; it came with fix(minimald): fail activation when a declared host port is already taken #2352, which is not on this branch.Squash-merge the three. All tests from both PRs are kept, with no assertion dropped. User-facing text is
main's.Conflict with #2738: both add the same
ADDRESS_IN_USE_BODYconstant tonet/policy.rs. Whichever lands second needs a one-hunk resolution: keep one copy. Clippy is clean on the merged tree.Verification (Linux, Lima)
just clippyandjust fmt-checkare clean.session_start_rewalks_range_after_finalize_install,each_box_gets_own_loopback_addressand the policy probe tests (minimald); f 10BC8 ix(net): hand boxes the loopback interim when the host cannot bind the box range #2332's answerer and control tests (minvmd).minvmd: 542 passed.minimald: 999 passed, 1 failed, the VM-only cgroup EACCES failuresession_host::tests::a_hook_launch_does_not_advise_unenforced_placement.fresh_install_range_lands_on_macos, which runs on the macOS lane.