Conversation
The interface carried `10.0.2.15/24`, a default route to `10.0.2.2` and a resolver at `10.0.2.3`, written into `main` as literals. That configuration was one network's — QEMU's user-mode backend — and it was right about a machine nobody had asked. The bench's router leases something else, so the T14 could never have had an address at all. netd now runs smoltcp's DHCPv4 client and applies what it is leased: the address and its prefix, the default route, and the resolvers the DNS socket queries, all three replaced together on a lease and dropped together when one is lost. A route left standing over an address that is gone sends frames out with a source nothing will answer, which is why they move as one. Every DISCOVER and REQUEST carries the host-name option (RFC 2132 §3.14) with `toyos-t14` in it: the bench's router is the only DHCP server in reach that records a client's name, and what it records this machine under is what that name then resolves to. `netd: ready, at most N piped connections` moves from before the loop to the first pass on which the address question is settled — a lease landed, or twenty seconds passed with none. Sixteen arms in this suite wait for that line and then connect; a netd that announced itself before it had an address would hand each of them a stack with none. A machine that gets no lease says so in one line and goes on serving, and its clients get their connects refused one at a time, which is what they are already written to survive. Measured against QEMU's user-mode DHCP server, which is an implementation of RFC 2131 this repository did not write and whose lease is known field by field: `lan_dhcp_lease` reads `10.0.2.15/24` from `10.0.2.2`, gateway `10.0.2.2`, resolver `10.0.2.3`, eleven milliseconds after netd came up, and holds the readiness line to arriving after it. The other fifteen network arms — `netd_*`, `https_tls13`, `https_tls13_e1000e`, the five `log_stream` arms and the four `sshd` ones — are green on the same tree. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GGQ2H2aCwd1jfiNmjsiUvz
`tests/lancase` is the boot that runs netd in front of the ThinkPad's onboard I219 at `00:1f.6`, `8086:15fc`. It is a directory of its own for the reason `tests/e1000case` is: a program that names a card a machine does not have costs an `init:` refusal line on every boot of the config that does, and no machine has both. Nothing on it reaches the internal NVMe — there is no `[disks]` row, and the PCI function netd claims is the only device any program on it names. `lan_hold` is the one job on that boot and it asserts nothing: a metal boot is about a second long, and nothing on a network can be asked of a machine that is up for that long — the I219's link takes seconds to negotiate before a DHCP discover can go out. It holds the machine up for twenty seconds and exits, and its exit record is what says the machine stayed up for the whole window. It is on `RUST_SKIP` because on any other boot it is twenty seconds of nothing. The metal loop now pings the machine's own address across the window between its two operating systems, and writes what it saw into the readback beside `back_secs` and `stick_secs`. Three things make that a fact about the boot: - The window opens when `ssh` stops answering and closes when it answers again, and inside it the only thing that can be running is the image this loop wrote. - A reply counts only after the address has been silent for five seconds. `reboot` takes `sshd` down before the interface, so the machine that is already "down" by this loop's reckoning still answers ICMP for a moment, and a reply counted there is the operating system that is leaving. - The address is resolved before the flash, through the same router's DNS that issues the lease, and the boot's own lease record is held to it afterwards: a run where the leased address and the pinged one differ is a ping something else answered. `ping` is the host's own ICMP client, declared in `src/sourcegate.rs` beside `ssh` and used nowhere else. It is an implementation of ICMP nobody here wrote, which is what makes it an oracle for the stack under test rather than a second opinion from it — and it is the only question this repository can ask a metal boot while that boot is still running, everything else being on a stick read minutes later. The number is taken on every metal boot and claimed by one. Every other boot in the suite runs no netd on that card, so its reading is Ubuntu answering on the way back up — which is the separation `boot.lancase.ping_secs`'s ceiling will be derived from. Until the machine has answered once, that row bounds the loop's own wait and says so; the run that takes the first reading is the run that tightens it, and every other row in the lancase block says the same. `lan_dhcp_lease` is one name with two judges, as `metal_device_probe` is: under QEMU it certifies netd's DHCP client against the user-mode backend's server, and on the T14 it certifies the whole path — the kernel handing netd the I219's function, the link, the lease from the bench's router under this machine's name, and the host's ping answered at the leased address. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GGQ2H2aCwd1jfiNmjsiUvz
`pcidev::bring_up` armed exactly one mechanism — `enable_msix(...).ok_or(Refusal::NoMsix)?` — so a function that publishes no MSI-X capability was refused by name and its holder never ran. Every function this project had handed to a process so far was a virtio one and every one of those has MSI-X, so the refusal had only ever been reached by `virtio_net_no_msix`'s deliberate `vectors=0`. The ThinkPad T14's onboard NIC is not one of those. Measured on the machine, not assumed: `/proc/interrupts` names its interrupt `IR-PCI-MSI-0000:00:1f.6 ... enp0s31f6` and `/sys/bus/pci/devices/0000:00:1f.6/msi_irqs/162` reads `mode=msi`, so Linux drives that function on MSI. Flashed and booted (run 28), this kernel wrote `pcidev: PCI 00:1f.6 NOT HANDED OVER — its MSI-X could not be armed`, netd found no endowment and exited, and the bench's one cable stayed dark. `bring_up` now arms MSI-X and falls back to MSI, and `Bound` holds whichever it got. **The driver above the boundary cannot tell which one it is and does not have to**: both deliver the same vector into the same `Interrupt`, and the claim answers the same handle either way. What differs is where the message lives, and therefore what a hand-over back has to write to silence it — `Armed::silence` masks an MSI-X table entry or clears MSI Enable, and `Armed::undo` puts the capability itself back off for a hand-over that armed a vector and was then refused. **MSI is not the weaker mechanism here, and the security argument is the same one.** MSI-X's table is kept out of what the holder maps because a holder that could rewrite it could point the device's message at any address the LAPIC decodes. An MSI function's message is in its own config space, which `pcidev` keeps: `config_read` is read-only and there is no writing counterpart. So MSI needs no BAR withheld — the same rule reaching a different register file. `Refusal::NoMsix` becomes `NoInterrupt` and says both mechanisms, because that is now what it means. `PciDevice::disable_msi` is new and is `disable_msix`'s counterpart: it sets the per-vector mask where the capability implements one and clears MSI Enable, which every function has. `toyos_pci::msi` grows `disabled` and the `MASKED`/`UNMASKED` mask values, host-tested — `disabled` deliberately does not restore the Multiple Message Enable field an arming zeroed, or a function would come back armed for as many vectors as it can raise. The hand-over record now names the mechanism (`vector 0x28 on MSI-X`): it is the first thing a machine that never heard from its device is asked, and it is not something the driver above the boundary can see. The two checks. - **Negative control.** Run 28 on the T14 is this change reverted whole, on the base the granted claim will be measured against: the same `tests/lancase` image on this machine's own I219 with `bring_up` arming MSI-X alone. It refused the claim by name at 1.349 s, netd exited `code=0` at 2.268 s, and nothing answered on the cable for the twenty seconds the boot stayed up. - **Independent oracles, two.** Linux's own reading of the same function, above — a driver nobody here wrote, saying that function is an MSI part. And the capability's register layout, which is the PCI spec's and which `toyos-pci/src/msi.rs`'s tests encode: the offsets of the address, data and mask registers all move with the 64-bit address bit, and writing a vector at the wrong one of them lands in whatever capability comes next in the list. Beside them, this kernel already produces the shape MSI must reproduce — `PCI 00:1f.3: msi address=0xfee00098 data=0x00000000` for the T14's HDA. Green: `cargo test -p toyos-pci` (41), `cargo test --lib` (296), `cargo run -- --clippy` (all five invocations), and the four guest arms that read this module — `virtio_net_no_msix`, `iommu_virtio_platform`, `pci_function_is_exclusive` and `userdev_dma_fault`. The MSI arm itself is exercised on the T14 alone: no device QEMU models that a process may claim publishes MSI without MSI-X, so there is no guest that can take that branch. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GGQ2H2aCwd1jfiNmjsiUvz
…'s name Run 28 on the T14 pinged `100.92.92.12` and got an answer 64 seconds into the window, one second before `ssh` came back. That is the machine's Tailscale address: `t14` resolves to it on this host, `Target::address` resolved the name, and only the operating system that is leaving and the one coming back ever hold it. The reply was Ubuntu's, and the five seconds of silence the loop waits for did not catch it because Tailscale comes up late in Ubuntu's boot — a boot whose netd never ran at all would have been reported the same way. The name was the wrong question. What a boot of a metal image can answer on is the address held on the PCI function that image *claims*, so that is what is read: `Target::nic` names `0000:00:1f.6`, and `Driver::wire` asks the machine, before the flash, which interface that function is, what MAC it has, and what address `ip -4 -brief addr show` reports for it. Three reads, none of them a root command and none of them a write. Measured just now over ssh: `enp0s31f6`, `192.168.1.46/24`, gateway `192.168.1.1`, with the Mac on `192.168.1.47` — the same LAN, which the Tailscale address is not. The MAC is carried out beside the address and written into the readback, and `lan::on_metal` holds the boot's own `netd: MAC` record to it. That is what turns "something answered" into "this boot answered": a MAC does not change with the operating system, so a driver reporting this one is the driver holding that address, and a reply from any other interface at it is somebody else's. An interface with no address at all is refused by name rather than read as the next line's — `an_interface_with_no_address_is_refused_by_name` stages the T14's own four-interface listing, Tailscale address included. `Refusal::Unresolved` becomes `Refusal::Wire`, because the thing that can now fail is the machine's account of one function rather than a name lookup. Run 28's other numbers are recorded against their rows: `complete_ms` 1223, `back_secs` 65, `stick_secs` 0. They are facts about a boot whose claim the kernel refused, which makes them facts about the boot and not about the claim. `ping_secs` gets none — no run has pinged the wire's address yet — and the comment above the block says so rather than leaving a reader to assume the row was simply not reached. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GGQ2H2aCwd1jfiNmjsiUvz
… is reachable
Run 29 on the T14 armed the I219's MSI and then refused the hand-over:
pcidev: 24 functions; a 32-bit window comes from 0x0..0x0, a 64-bit one from 0x603dc00000..0x6040c00000
PCI 00:1f.6: msi address=0xfee000b8 data=0x00000000
pcidev: PCI 00:1f.6 NOT HANDED OVER — this machine has no 2 MiB-aligned address space above what firmware assigned to put a BAR in
The refusal is correct and its wording was not. `window` places a BAR above
everything firmware described; in 64 bits that always exists, and below 4 GiB it
never does, because the platform's fixed MMIO is at `0xFEC00000` and the UEFI map
reaches it. So the message read as "this machine is full" where the truth is
"this module only ever looks above everything, and there is no above down there".
A 32-bit window is a free run *between* things.
Leaving the BAR where firmware put it is not the alternative: on this machine the
I219's `0xbcf00000` and the internal NVMe's `0xbce00000` are in one 2 MiB page,
and 2 MiB is the only page this kernel maps, so handing it over unmoved would put
a disk controller's registers inside netd's mapping.
What this change does is make the machine say what it has left, and name what is
missing.
`toyos_pci::bridge` decodes a Type 1 header's forwarded memory windows — bits
31:20 in the top twelve of each half, a limit that names the last megabyte, and
the base-above-limit encoding for a bridge that forwards nothing, which read
literally is a range that wraps. `PciDevice::forwarded_below_4g` reads them, and
the prefetchable one is dropped where its upper base puts it above 4 GiB.
`survey_low_space` runs on the machine where the low window comes out empty, and
only there: a boot with room says nothing, and thirty lines on every boot is a
log that has one channel off this bench. It merges the firmware map, this bus's
assigned BARs and every forwarded bridge window, prints the free runs of 2 MiB or
more, and ends with the sentence that matters —
pcidev: a run above is a candidate and not a claim — what says whether an address below
4 GiB reaches this bus at all is the host bridge's own aperture, which is ACPI's `_CRS`,
and this kernel runs no AML
**Nothing here hands a run out**, and that is deliberate. An address outside the
host bridge's aperture is not free space, it is unrouted, and a read of it
answers ones — which `Refusal::Dead` cannot tell from a device that is not there.
Linux reads `_CRS` for exactly this and keeps its chipset registers as quirks for
firmware that gets `_CRS` wrong, not as an alternative to it. Guessing a range
here would be guessing whether a process's mapping reaches the bus or the void.
`issues/kernel/a-32-bit-bar-needs-the-host-bridges-aperture.md` carries the three
ways out and their prices.
`Refusal::NoWindow` was three facts under one name and is now three: `NoWindow
{ wide }` for a machine that published no window of that width — with the 32-bit
arm naming the aperture — `WindowFull { wide }` for one this module used up, and
`NoMappableBar` for a function with nothing to map, which is not about address
space at all and sent a reader to the allocator.
The two checks.
- **Negative control.** Run 29 is this change reverted whole, on this image and
this machine: the same boot, the same claim, and a refusal that named no free
run and no missing input. The next run on the T14 is the arm — the survey
either prints runs or prints none, and either is a reading this repository does
not have today.
- **Independent oracle.** The bridge window layout is the PCI-to-PCI Bridge
Architecture Specification's, encoded in `toyos-pci/src/bridge.rs`'s tests: the
address is bits 31:20 of each half and not the field's own offset, the limit
names the last megabyte, and base-above-limit is "forwards nothing" rather than
a wrapped range. Beside it, the T14's own enumeration is the check on the
survey's arithmetic: `04:00.0` at `0xbce00000` and `00:1f.6` at `0xbcf00000`
are behind bridges, so a survey that reported the page holding them free would
be contradicted by the enumeration in the same log.
Green: `cargo test -p toyos-pci` 44, `cargo test --lib` 297,
`cargo run -- --clippy` all five invocations, and the four guest arms that read
`pcidev` — `virtio_net_no_msix`, `iommu_virtio_platform`,
`pci_function_is_exclusive`, `userdev_dma_fault`.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GGQ2H2aCwd1jfiNmjsiUvz
The green arm's claim stops at the 32-bit window, and the branch that says what the machine has left below 4 GiB is stacked on #443. Merged here so the image the T14 runs carries the survey; when both land, merging main is the same commits again.
Run 29 armed the I219's MSI and stopped at the 32-bit BAR; run 30, the negative control with the claim pointed at a card this machine does not have, did the same boot with no netd at all. Both are recorded here. `boot.lancase.complete_ms` 1257 and `back_secs` 61 are the machine's own numbers off boots whose claim was refused — facts about the boot rather than about the claim, so the readings stand. The two `lan.` rows still have none, because netd never came up on either. **Run 30 changed what `ping_secs` is for.** The control pinged 192.168.1.46 once a second for its whole 61-second window and got nothing: this machine's wire answers no earlier than its ssh, and the loop stops pinging when ssh answers. So the number that row will hold is not "earlier than Ubuntu" — it is "at all", because nothing answers at that address unless the boot brings it up. That is a stronger control than the one the row was written for, and the `ceiling_from` says so. It still waits on a green reading before it can be tightened to the span a boot is actually up for. The track file is brought up to what these three runs established: sshd's half landed as #440, the I219 driver and DHCP are built and green under QEMU, and what is left is the laptop — the claim, and the two things that wait on it. Its constraints list carries three measurements instead of two guesses: the I219 is an MSI part (`/proc/interrupts`, `msi_irqs/162`), `toyos-t14` resolves to nothing on this LAN and `t14` resolves to a Tailscale address only Ubuntu holds, and the 32-bit BAR shares its 2 MiB page with the internal NVMe — which is why leaving it where firmware put it is not the way out either. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GGQ2H2aCwd1jfiNmjsiUvz
Run 30's window was dark and I wrote a judge on that one sample: a reply anywhere in the window is the boot's. Run 31 falsified it. That boot's claim was refused at the 32-bit BAR, netd never held the card, and the loop still reported `192.168.1.46 answered a ping 57 s into the window` — two seconds before the machine's own `sshd` came back. The wire comes up before ssh does, and the loop stops probing when ssh answers, so the reply was inside the window, inside the ceiling, and belonged to the operating system after the boot. A ceiling on `ping_secs` could never have told the two apart, whatever number it held. What tells them apart is time against the boot's own timeline. `logd` writes a wall clock on every record and the loop now writes one beside the reply; both are UTC, the T14's clock being Ubuntu's from the network and this host's NTP's. `bootlog::record_unix_secs` reads the one field in a log a host clock can be held against, and `lan::the_boot_answered` is the judge: - the reply is inside the span this boot's records bracket, ending at `Rebooting.` — anything after that is the next operating system, whatever its timing; and - it is at or after the `netd: DHCP: lease` record, because a machine with no address answers nothing at that address. Both halves are needed. The first alone would accept a reply from before netd had an address; the second alone would accept run 31's, which came thirty-four seconds after the boot had handed the machine back. The bracket is about twenty-three seconds wide on this boot and the two clocks agree to within a second, which is what makes comparing them admissible; a machine whose clocks drifted further would show it as a reply just outside the bracket rather than as a mystery, and the refusal prints both numbers. `ping_secs` stays, priced, as the cost of a reply — it is a number worth watching move and it is not a verdict, and its `ceiling_from` says so now rather than claiming a separation it cannot make. `boot.txt` gains `ping_at`, written only where something answered: a reply has a time or it did not happen. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GGQ2H2aCwd1jfiNmjsiUvz
… name The judge that says a host-side reply belongs to this boot lived in the integration-test tree, where nothing could host-test it, beside a module that declares itself the home for exactly its shape. It is now `bootlog::host_second_inside_this_boot`: a log, the two ends of the host's own window, the record the observation may not precede, and one second in; a verdict out. Four tests cover both arms, and the fixture is three lines copied out of the stick the T14 wrote rather than three runs' numbers blended together. The one assertion that could not fail — `x + 57 > x + 23` over string constants — is gone with it. The clock skew the bracket rested on is measured instead of asserted. The loop records the host's clock at both ends of the window in which the machine is running neither of its operating systems, and the judge refuses a boot whose own records do not lie inside it before it places anything against them. Nothing claims the two clocks agree to within a second any more; the run's own data bounds their disagreement. The four cable fields in a readback are one `Cable`, and reading one refuses every partial set by name. A file naming seconds and no wall clock used to fall to a `_` arm and be reported as "nothing answered a ping" — the opposite of what the run recorded, and the shape of every readback taken so far. The reading and the probe belong to the boot that asks for them. `Arm::nic` carries the PCI function into the driver's invocation as `--nic`, and only the lancase arm sets it: `metal_device_probe`, `blackbox_unclaimed_page`, `ccorpus` and every other registration no longer take three `ssh` reads and a ping thread for a fact none of their judges reads. A host with no `ping` is now `Refusal::Probe` rather than a dark cable, and `ping_secs` is judged once, by `lan::on_metal`, instead of twice through a string compare that holed `Unfit::Unpriced` for one field name. `measured` on the lancase rows was run 29's boot time and the negative control's `back_secs`. It is run 31's, the last boot of that config: 1258 ms and 59 s. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GGQ2H2aCwd1jfiNmjsiUvz
…ame on a wire that is read `dns::Socket::update_servers` truncates to `DNS_MAX_SERVER_COUNT` and says nothing, and smoltcp's default for it is one — so a lease offering three left netd's own record naming two resolvers the stack did not have. The count is raised in `userland/.cargo/config.toml` to the three a lease can carry (`smoltcp::wire::DHCP_MAX_DNS_SERVER_COUNT`), and `dhcp.rs` holds the two together with a `const` assertion: a build that lowers it again does not compile. Nothing is dropped and nothing has to be reported as dropped. `report_within` is deleted, and so is the `timeout.min` it fed. Its premise was that a machine whose network never answers produces no timer, so the loop's own delay is unbounded. `dhcpv4::Socket::poll_at` returns `PollAt::Time(retry_at)` in every state, and netd's delay comes from `iface.poll_delay`, so a discovering client already wakes the loop every ten seconds. The report now prints the seconds it actually fired at instead of the constant it was written for. The lease path and the lease-lost path are one writer. `Dhcp::write` takes the lease or its absence and replaces the address, the default route and the resolvers together, so the code that drops a lease is the code every boot already runs. Two things the lease boot cannot ask are now asked. `lan_no_lease` boots the same config on an `e1000e` plugged into a hub with nothing else on it — the only machine in this suite where a DHCP client gets no answer — and holds netd to saying it has no address and then announcing itself anyway, which is what keeps every arm waiting on that line from hanging. And `filter-dump` writes the frames the client actually sent, so the host-name option is read as bytes on the wire: a server that ignores it writes nothing about it, and there was no other place the owner's hostname decision could be checked at all. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GGQ2H2aCwd1jfiNmjsiUvz
Every prose finding in the review is answered by deletion rather than by a rewrite. What went: what an earlier implementation bought, the alternatives that were rejected, the borrow checker's opinion, a measurement with no command behind it, the run numbers and the story of which run falsified which judge, the comments that restate the line beneath them, and the fourteen-line header over one `sleep`. `tests/lancase/system.toml` claimed it changes "the pair of identifiers" against `tests/e1000case`; it changes one. It also carried a boot parameter line no config on this branch sets, copied in from its neighbour. The track file had grown a stage table with a rationale per bullet, which is a plan again. It is a paragraph, and what remains of the growth is two constraints the bench measured: the I219 publishes MSI and no MSI-X, and its 32-bit BAR shares a 2 MiB page with the internal NVMe. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GGQ2H2aCwd1jfiNmjsiUvz
…mes it `installing_the_rule_is_not_also_a_boot` enumerates every boot-describing flag and asserts each one is refused beside `--install-sudoers`. `--nic` pushes onto `about_a_boot` like the rest and was missing from that list. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GGQ2H2aCwd1jfiNmjsiUvz
`Arm::nic`'s doc says why only the boot that names a function asks the cable, and `invocation` said it again a screen later; `BootOptions::wire_dump`'s doc says what the dump is for, and the argv said it again; and the partial-cable test's own doc says which half would otherwise read as no answer, twice. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GGQ2H2aCwd1jfiNmjsiUvz
…ne's name `toyos-t14` resolves to nothing on the bench LAN. netd asks under the name and the wire is read for the option; whether any server records it is not a claim this repository can make, and the metal registration made it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GGQ2H2aCwd1jfiNmjsiUvz
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.
Stage 2's laptop half. The claim has been refused on every boot of it so far, so nothing here has a true positive yet — see What the machine has answered below.
netd takes this machine's address from the network. The interface carried
10.0.2.15/24and a route to10.0.2.2as literals — one network's answer, and not the bench's. netd runs smoltcp's DHCPv4 client, asks under the host nametoyos-t14, and writes the address, the default route and the resolvers together on a lease and their absence when one is lost.netd: readyis said on the first pass that settles the address question — a lease, or twenty seconds with none — because every arm in this suite that waits for that line connects right after it.tests/lancaseis the boot that runs netd in front of the T14's onboard I219 (8086:15fcat00:1f.6), with one job that holds the machine up for twenty seconds. Only that arm names a PCI function (Arm::nic→toyos-metal --nic), so only that arm reads the wire before the flash and pings across the window between the machine's two operating systems.What identifies a reply, and what does not
How far into the window a reply came identifies nothing: the window holds the operating system that is leaving, this boot, and the one coming back. The T14 measured that — run 31 answered 57 s into the window on a boot whose claim was refused and whose netd never held the card, two seconds before its own
sshdreturned.What identifies it is
bootlog::host_second_inside_this_boot: the reply's wall clock must fall inside the span this boot's own records bracket, ending atRebooting., and at or after the lease record.logdwrites a wall clock on every record and the loop writes one beside the reply, and the two clocks' disagreement is bounded by the run's own data, not assumed: the loop records the host's clock at both ends of the down-window, and a boot whose records fall outside it is refused before anything is placed against them.boot.lancase.ping_secsstays priced as a cost and itsceiling_fromsays it is not a verdict.What the machine has answered
Four boots, runs 28–31, all with the claim refused — first for want of MSI, then at the 32-bit BAR (
issues/kernel/a-32-bit-bar-needs-the-host-bridges-aperture.md). So no boot has yet leased an address on that card, and no green reading exists for anylan.row. The threeboot.lancaserows carry run 31's numbers, which are facts about the boot rather than about the claim:complete_ms1258 (kernel.log:279),back_secs59 andstick_secs0 (boot.txt).ping_atwas added by this branch, so no readback carries one andhost_second_inside_this_boothas never run on real host-clock evidence.The two checks
Negative control. The judge this branch replaces was a ceiling on
ping_secs, and run 31 is the recorded real failure that reverting to it reproduces: that reply was inside the window and inside the ceiling and belonged to the next operating system. The judge that replaces it is shown refusing run 31's own records ina_second_past_the_reboot_record_is_the_next_operating_systems— the window opens no later than the boot's first record, so a reply 57 s in came at least 34 s afterRebooting.For the T14 arm itself,target/metal-lancontrolis this branch withpci:8086:15fc→pci:8086:10d3, a card the machine does not have; run 30 is that arm, and its window was dark.Oracles, none of them this tree's. QEMU's user-mode DHCP server, an RFC 2131 implementation nobody here wrote, checked field by field by
lan_dhcp_lease. QEMU's ownfilter-dumpof the wire, which is where the host-name option is read as bytes — no server logs it, so the frames are the only evidence the client asked at all. The development host'sping, an ICMP implementation nobody here wrote, pending the machine. Andsmoltcp's own limits, held at compile time:DNS_MAX_SERVER_COUNT >= DHCP_MAX_DNS_SERVER_COUNT, so netd's lease record can no longer name a resolver the stack dropped.What is claimed and not measured
lan::on_metalrequires the boot's lease address to equal the address Ubuntu held on the same function. That assumes the bench's router repeats a lease across the two operating systems — netd sends smoltcp's client identifier (option 61) and Ubuntu's client need not. Nothing has measured it. The refusal says so rather than concluding; what actually ties a reply to the boot is the MAC record and the wall-clock bracket, neither of which depends on it.toyos-t14resolves to nothing on the bench LAN (measured: the T14's DHCP resolvers are the ISP's). The option is sent and read off the wire; that it is recorded by any server is not claimed.Dependencies
pingis a new host binary, outsideCLAUDE.md's Rust-and-QEMU bar, declared insrc/sourcegate.rs'sHOST_SPAWNSbesidesshwith its argument. It is reached only bysrc/metal.rs, only on the arm that names a NIC, and only across the window in which the T14 is running neither of its operating systems. No new crate, no new third-party file, no fetch.hubportandfilter-dumpare QEMU's own.Gates
cargo test --libcargo test --workspace --exclude toyos-buildcargo run -- --clippycargo check -p netd(inuserland/)constassertion holdsqemu-system-x86_64 ... -netdev hubport ... -object filter-dump ...cargo test --test toyos-build -- lan_lan_dhcp_lease,lan_no_leaseand the other network registrations — are unrun here and are CI's to render.Draft: landing waits on the card being handed over on the machine.
🤖 Generated with Claude Code
https://claude.ai/code/session_01GGQ2H2aCwd1jfiNmjsiUvz