Skip to content

The T14's outbound rows: its router and the internet, judged from the stick (lands behind the move; do not merge before it) - #784

Closed
Japabu wants to merge 1 commit into
mainfrom
wt/toyos-move-rows
Closed

Japabu wants to merge 1 commit into
mainfrom
wt/toyos-move-rows

Conversation

@Japabu

@Japabu Japabu commented Oct 8, 2026 •

Copy link
Copy Markdown
Collaborator

This lands behind the move of netstack onto ToyOS's own stack and must not be merged before it. The owner ruled "no smoltcp.": no new test is built on the old stack. It is a draft on purpose; it is flashed once as it stands (the base netstack) for the move's "before", and again stacked on the move.

Head ac7d915f2, on origin/main 35d35859e.

What it is

The owner: "the t14 ... has the router in lan it can connect to and it should be able to connect to the internet. lets change the tests so it tries to connect to the internet and writes its log to the stick which can be tested."

  • One boot, outbound, on a new tests/outboundcase: netstack on pci:8086:15fc, the row no config has had since lantalkcase went, and one job.
  • The job, outbound: waits for netstack's own word on its lease; asks inspect once; for dns.google and dns.quad9.net side by side, one name lookup and one connect to port 443 of the first address with 5 s, closed with no byte written; asks inspect again for the router's neighbour entry; exits 0. Each fact is a line the moment it is known.
  • outbound_router is red by the first step that failed, in the order a frame needs them: the kernel handed 8086:15fc over; netstack found a card; the link is up; a lease is held; it names a router; the router answered for its link address, where anything left by it; a resolver that is the router or on the link gave one lookup any answer but silence; the job finished.
  • outbound_internet is green when at least one anchor connected and says which. It is judged only over a green router row; over a red one it prints "not judged" and passes, so one cause reds one row. When neither connected: an answer that was "no" (a refused or reset connect, a resolver's negative answer) says "the uplink or the service"; netstack ending a request itself says ToyOS; silence says plainly that this log cannot tell ToyOS from the uplink. No reading is taken on the machine's other operating system.

No button is pressed: the boot ends itself by its job list like every metal boot.

Decisions the brief left open, or that differ from it

  • netstack's word is read on logkeeper's log port, not on the log capability. The brief said to wait "through the log capability as inbox_log_post.rs does". That cursor serves the kernel's records only: a program's lines are in its own ring (toyos/src/log/mod.rs), and netstack's lease line is say!. The tree's reader of program lines from a job is counters_metal's Log, woken by the pipe and bounded by a ceiling that panics by name. It moves to tests/toyos-rust-tests/src/served_log.rs and both jobs use it, so there is one. So the config's test-runner holds log and no syscap at all, not logread.
  • The job speaks toyos::net directly, not std. std folds a name with no address and every lookup failure into one Other (sdk/std/sys/net/connection.rs), and the router row needs "answered no" apart from silence.
  • One vocabulary, compiled into both ends. tests/toyos-rust-tests/src/outbound_said.rs is #[path]-included by the job and by tests/common/outbound.rs. A Line holds words of closed lists and counts; the job can write nothing else, and the judges write every verdict from what a line read as, never from the log's text. A line outside the vocabulary is counted and not quoted.
  • No DISCOVER and OFFER counts. The plan's sample line had discover=1 offer=1; netstack's inspect answer counts no DHCP message on either stack. The row uses what the Intel driver already answers, net.wire.sent and net.wire.received: no lease with nothing received says the wire; no lease with frames received says the log cannot tell a silent DHCP server from ToyOS not taking an offer. That is weaker than the plan asked and is a present weakness of the row.
  • not-asked, one word, for the neighbour entry a stack does not say: every value of a line is one word.
  • A third word from netstack is waited on: netstack: no NIC on this machine, exiting, so a boot whose netstack was endowed nothing says no-card at once instead of running into the ceiling.
  • One line outside tests/: src/build.rs's ALL_CONFIGS gains tests/outboundcase/system.toml. every_shipped_boot_config_is_covered reds on a config the list does not name, so the config the brief asks for cannot exist without it.

What the rows could not get from the pipe ABI

  • DHCP messages sent and received (above).
  • The router's neighbour entry, on the base netstack: smoltcp's cache is private.
  • Why a connect was refused: the base answers ERR_CONNECTION_REFUSED for a reset and has no word for an ICMP unreachable.
  • Whether a lookup's ERR_OTHER came from a reply. On the base it does (Truncated, ServerFailed, TooManyAliases), and the router row reads it as an answer.

What the move must keep for this branch to build and judge on it

  • The lines netstack: DHCP: lease , netstack: DHCP: no lease as and netstack: no NIC on this machine, exiting, under the tag netstack.
  • The inspect keys net.driver (i219 on the T14), net.link.state, net.lease.held, net.lease.address as address/prefix, net.lease.router (absent for none), net.lease.dns (space between two), and net.wire.sent and net.wire.received as counts.
  • toyos::net::dns_lookup and tcp_connect with their present signatures; NetError::{TimedOut, Io, NotConnected, ConnectionRefused, ConnectionReset} meaning what they mean now. The job matches the rest with a wildcard, so a new variant builds.
  • ERR_OTHER from a lookup only where a reply arrived, or a new word for the rest.
  • The new key is net.neighbour.router, text, one of reachable stale delay probe incomplete unreachable failed none. Any other word panics the job by name.
  • logkeeper serving log.

Time

By arithmetic from constants, measured from boot against the runner's 60 s (toyos_tco::JOB_BOUND_MS): the T14's recorded Boot: complete is 1.2 s (tests/metal/lenovo-20w0003amz.toml); netstack says it has no lease 20 s after it came up; a lookup is at most 3 resolvers × 3 rounds × 2 s = 18 s; a connect has 5 s; the anchors run side by side. 1.2 + 20 + 18 + 5 = 44.2 s. The ceiling on netstack's word is 30 s (its 20 s and two of logkeeper's 5 s rounds), and that path runs no anchor.

Measured in a guest, as a development aid (x86-64 under TCG on an arm64 host, tests/outboundcase with the virtio id, a temporary unregistered machine test, reverted). Times are the guest's own log clock from the job's spawn record to outbound: done; the host's uptime load averages are beside each because the machine was in power-saving mode with several agents building.

guest network job load (1 m, before → after) what it said
slirp 2.24 s 55.19 → 79.01 netstack said=lease; card driver=virtio-net link=unreported sent=unreported received=unreported; lease held=yes router=named resolver=on-link; both anchors lookup=addresses connect=connected; gateway neighbour=not-asked; done
slirp, restrict=on 0.34 s 79.01 → 75.03 netstack said=lease; lease held=yes router=none resolver=none; both anchors lookup=no-resolver connect=not-tried; gateway neighbour=not-asked; done
a wire nothing answers on 20.39 s 75.03 → 61.41 netstack said=no-lease; lease held=no router=none resolver=none; done

The longest measured is 20.39 s: netstack's own 20 s bound, and its word reached the job 340 ms after netstack said it. All three exited 0; none hit a ceiling. Not measured: a lookup that times out and a connect that times out. slirp answers both, so those 18 s and 5 s are arithmetic only until the T14 or the move's guest arms show them.

Gates

Each is the command's own exit code, at ac7d915f2 unless said.

command exit
cargo test --test toyos-checks (39 tests, the three new ones among them) 0
cargo test --lib (352 passed; every_shipped_boot_config_is_covered, the config gates and the source gate among them) 0
cargo run -- --clippy (24 invocations clean) 0
cargo run -- --build-only 0
cargo test --test toyos-build -- --metal --list, base: 64 registrations, 229 shared members, 26 boots 0
the same, this branch: 66 registrations, 229 shared members, 27 boots 0
--metal --metal-readback <hand-made green> boot:outbound: both rows PASS 0
--metal --metal-readback <hand-made, both anchors silent> boot:outbound: router PASS, internet FAIL "this log cannot tell ToyOS from the uplink" 1
--metal --metal-readback <hand-made, no lease> boot:outbound: router FAIL "nothing on this wire answered", internet "not judged" PASS 1
--metal --metal-readback <dir> boot:outbound, staging the image 2 (staged)

The checks, the library tests, clippy and the image build ran on the tree that became ac7d915f2, before the commit. Not run, by the brief: cargo run -- --ci host and the guest suite. counters_metal changed by a move only and was compiled, not run: its row is the T14's.

No guest test is added. The rows are metal rows because their subject is the I219 and the bench's network, which no guest has.

Negative controls

Ten mutations, each a checked patch applied to ac7d915f2, built, run with cargo test --test toyos-checks -- outbound, and reversed; the patches are in the comment below.

mutation exit the test that reds
the link is not read 101 metal_outbound_rows_are_red_by_their_cause
the internet is judged over a red router 101 the same
words may ride a line after its own 101 metal_outbound_line_holds_no_address and the same
a verdict quotes the log 101 the same, at "the router row's verdict holds an address"
the neighbour entry is not read 101 the same
any program's outbound: lines are the job's 101 the same
every resolver is on the link 101 metal_outbound_resolver_stands_where_the_lease_puts_it
a silent resolver counts as an answer 101 metal_outbound_rows_are_red_by_their_cause
no lease is green 101 the same
an unfinished job is green 101 the same

The oracle is real hardware and a network ToyOS did not write: the T14's I219, the bench's router and DHCP server, and two public services. That reading is the orchestrator's and is not in this body yet.

Privacy

The job prints Lines, which hold no address, MAC or host name. The fixtures use RFC 5737 addresses and a locally administered MAC, carry netstack's own lease line, and every verdict over them is scanned for a dotted quad, a MAC and netstack's host name. netstack's lines stay on the stick and the development machine.

Unsure, and owed at landing

  • The T14 has not run this. Whether the I219 claim path, moved by A device call acts on its claim's binding or is refused: a claim lends a &Binding or &isa::Row under the lock its release takes #763 and unread on hardware since, hands the card over is the first thing the flash says.
  • issues/toyos-has-its-own-network-stack.md says no T14 row reads the wired card, and issues/the-host-cannot-reach-the-t14-while-it-runs-toyos.md says the same under "What stands in the meantime". Both become false when this lands. They are not edited here: the track is rewritten by the stages in front of this, and the edit belongs to the rebase onto the move.
  • The missing DHCP counts are recorded nowhere but here. They want a line in the track at the move.
  • A green flash adds boot.outbound.* to tests/metal/lenovo-20w0003amz.toml, which is committed with the rows.
  • Size: +1318 −47 (git diff --shortstat origin/main...HEAD), nearly all of it under tests/: the job, its vocabulary and the moved reader, the judges, and the fixtures.

🤖 Generated with Claude Code

https://claude.ai/code/session_01RvnWQFcMuGqTHYhvSnTe8A

… stick

One boot, `outbound`, on a new `tests/outboundcase`: netstack on the T14's
I219 and one job, `outbound`, which waits for netstack's own word on its
lease, asks `inspect` what netstack holds, looks up two public resolver
services by name and opens one connection to port 443 of each, writing no
byte, and says each fact as a line the moment it knows it. Two rows read that
boot: `outbound_router` is red by the first step that failed (the card, its
link, the lease, the router's link address, the resolver on the link), and
`outbound_internet` is green when one anchor connected and is judged only
over a green router row.

The job is written against the pipe ABI and the inspect keys alone, so the
same source runs on the stack `main` ships and on the one that replaces it.
The router's neighbour entry is a key the replacement adds; where it is
absent the job says `not-asked` and the row does not judge it.

netstack's lines are in its own ring and in no record the kernel's cursor
serves, so the job reads them where `counters_metal` reads its own: on
logkeeper's `log` port, woken by the pipe. That reader moves out of
`counters_metal` into `served_log.rs` for both.

A line is a `Line` (`outbound_said.rs`), compiled into the job and into the
judges: words of a closed list and counts. The judges write a verdict from
what a line read as and never from the log's text, and a line outside the
vocabulary is counted and not quoted.

`src/build.rs`'s `ALL_CONFIGS` gains the new config's row: the gate beside it
reds on a `system.toml` the list does not name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RvnWQFcMuGqTHYhvSnTe8A
@Japabu

Japabu commented Oct 8, 2026

Copy link
Copy Markdown
Collaborator Author

Negative controls at ac7d915f2: each applied with git apply --check and git apply, run with cargo test --test toyos-checks -- outbound, and reversed with git apply -R. Results:

m1-link-unread built=1 EXIT=101 test checks::metal_outbound_rows_are_red_by_their_cause ... FAILED 
m10-an-unfinished-job-is-green built=1 EXIT=101 test checks::metal_outbound_rows_are_red_by_their_cause ... FAILED 
m2-internet-judged-over-a-red-router built=1 EXIT=101 test checks::metal_outbound_rows_are_red_by_their_cause ... FAILED 
m3-words-ride-a-line built=1 EXIT=101 test checks::metal_outbound_line_holds_no_address ... FAILED test checks::metal_outbound_rows_are_red_by_their_cause ... FAILED 
m4-verdict-quotes-the-log built=1 EXIT=101 test checks::metal_outbound_rows_are_red_by_their_cause ... FAILED 
m5-neighbour-unread built=1 EXIT=101 test checks::metal_outbound_rows_are_red_by_their_cause ... FAILED 
m6-any-program-speaks-for-the-job built=1 EXIT=101 test checks::metal_outbound_rows_are_red_by_their_cause ... FAILED 
m7-every-resolver-on-link built=1 EXIT=101 test checks::metal_outbound_resolver_stands_where_the_lease_puts_it ... FAILED 
m8-silence-read-as-an-answer built=1 EXIT=101 test checks::metal_outbound_rows_are_red_by_their_cause ... FAILED 
m9-no-lease-is-green built=1 EXIT=101 test checks::metal_outbound_rows_are_red_by_their_cause ... FAILED 

m1-link-unread.patch

diff --git a/tests/common/outbound.rs b/tests/common/outbound.rs
index 7cb70ad1e..0343b43fe 100644
--- a/tests/common/outbound.rs
+++ b/tests/common/outbound.rs
@@ -185,7 +185,7 @@ fn reached_router(kernel: &Serial, said: &Said) -> Result<(), String> {
         return Err(format!("netstack drives `{}`, which is not this machine's wired card", card.driver.word()));
     }
     let frames = format!("the card counts {} frame(s) sent and {} received", card.sent, card.received);
-    if card.link != Link::Up {
+    if false {
         return Err(format!("no link: the card's link is `{}`; {frames}", card.link.word()));
     }
     let lease = said.lease()?;

m10-an-unfinished-job-is-green.patch

diff --git a/tests/common/outbound.rs b/tests/common/outbound.rs
index 7cb70ad1e..43f4904ce 100644
--- a/tests/common/outbound.rs
+++ b/tests/common/outbound.rs
@@ -235,7 +235,7 @@ fn reached_router(kernel: &Serial, said: &Said) -> Result<(), String> {
             return Err(format!("the resolver the lease names {stands}, and {whose}"));
         }
     }
-    if !said.lines.contains(&Line::Done) {
+    if false {
         return Err(said.ended_before("its last line"));
     }
     Ok(())

m2-internet-judged-over-a-red-router.patch

diff --git a/tests/common/outbound.rs b/tests/common/outbound.rs
index 7cb70ad1e..b1801261d 100644
--- a/tests/common/outbound.rs
+++ b/tests/common/outbound.rs
@@ -248,7 +248,7 @@ pub fn internet(kernel: &Serial, log: &Serial) -> Result<(), String> {
         eprintln!("  [outbound] the internet is not judged: the router row is red");
         return Ok(());
     };
-    if reached_router(kernel, &said).is_err() {
+    if false {
         eprintln!("  [outbound] the internet is not judged: the router row is red");
         return Ok(());
     }

m3-words-ride-a-line.patch

diff --git a/tests/toyos-rust-tests/src/outbound_said.rs b/tests/toyos-rust-tests/src/outbound_said.rs
index 65107111f..01a44b825 100644
--- a/tests/toyos-rust-tests/src/outbound_said.rs
+++ b/tests/toyos-rust-tests/src/outbound_said.rs
@@ -222,7 +222,7 @@ impl Line {
         let said = text.strip_prefix(HEAD)?;
         let line = Self::read_said(said);
         // Byte for byte what it reads as, so nothing rides a line beside its words.
-        Some(line.filter(|line| line.to_string() == text))
+        Some(line)
     }
 
     fn read_said(said: &str) -> Option<Self> {

m4-verdict-quotes-the-log.patch

diff --git a/tests/common/outbound.rs b/tests/common/outbound.rs
index 7cb70ad1e..50fb50035 100644
--- a/tests/common/outbound.rs
+++ b/tests/common/outbound.rs
@@ -68,7 +68,7 @@ impl Said {
             .filter_map(|said| Line::read(said.text));
         for (nth, line) in said.enumerate() {
             let line = line.ok_or_else(|| {
-                format!(
+                format!("{}", log.text()) + &format!(
                     "the job's `{}` line {} is none its vocabulary writes. It is not quoted: a line \
                      outside the vocabulary can hold anything",
                     said::HEAD.trim_end(),

m5-neighbour-unread.patch

diff --git a/tests/common/outbound.rs b/tests/common/outbound.rs
index 7cb70ad1e..d4a70d1ec 100644
--- a/tests/common/outbound.rs
+++ b/tests/common/outbound.rs
@@ -212,7 +212,7 @@ fn reached_router(kernel: &Serial, said: &Said) -> Result<(), String> {
     let through = matches!(lease.resolver, Resolver::Router | Resolver::OffLink)
         || anchors.iter().any(|a| a.connect != Connect::NotTried);
     let gateway = said.gateway()?;
-    if through && gateway.answered() == Some(false) {
+    if false {
         return Err(format!(
             "the router did not answer for its link address: netstack's neighbour entry for it is `{}`",
             gateway.word()

m6-any-program-speaks-for-the-job.patch

diff --git a/tests/common/outbound.rs b/tests/common/outbound.rs
index 7cb70ad1e..26e36a54f 100644
--- a/tests/common/outbound.rs
+++ b/tests/common/outbound.rs
@@ -64,7 +64,6 @@ impl Said {
             .text()
             .lines()
             .filter_map(toyos_logstream::program_line)
-            .filter(|said| said.tag == RUNNER)
             .filter_map(|said| Line::read(said.text));
         for (nth, line) in said.enumerate() {
             let line = line.ok_or_else(|| {

m7-every-resolver-on-link.patch

diff --git a/tests/toyos-rust-tests/src/outbound_said.rs b/tests/toyos-rust-tests/src/outbound_said.rs
index 65107111f..882d98f0b 100644
--- a/tests/toyos-rust-tests/src/outbound_said.rs
+++ b/tests/toyos-rust-tests/src/outbound_said.rs
@@ -277,7 +277,7 @@ pub fn resolver(address: &str, router: Option<&str>, dns: &str) -> Option<Resolv
         let server: Ipv4Addr = server.parse().ok()?;
         let stands = if Some(server) == router {
             Resolver::Router
-        } else if u32::from(server) & mask == address & mask {
+        } else if true {
             Resolver::OnLink
         } else {
             Resolver::OffLink

m8-silence-read-as-an-answer.patch

diff --git a/tests/common/outbound.rs b/tests/common/outbound.rs
index 7cb70ad1e..474b0cad8 100644
--- a/tests/common/outbound.rs
+++ b/tests/common/outbound.rs
@@ -221,7 +221,7 @@ fn reached_router(kernel: &Serial, said: &Said) -> Result<(), String> {
     if matches!(lease.resolver, Resolver::Router | Resolver::OnLink) {
         // Any answer but silence: a resolver that says no has answered.
         let answered =
-            |a: &Reached| matches!(a.lookup, Lookup::Addresses | Lookup::NoAddress | Lookup::Failed);
+            |a: &Reached| matches!(a.lookup, Lookup::Addresses | Lookup::NoAddress | Lookup::Failed | Lookup::Timeout);
         if !anchors.iter().any(answered) {
             let stands = match lease.resolver {
                 Resolver::Router => "is the router",

m9-no-lease-is-green.patch

diff --git a/tests/common/outbound.rs b/tests/common/outbound.rs
index 7cb70ad1e..40eb9745f 100644
--- a/tests/common/outbound.rs
+++ b/tests/common/outbound.rs
@@ -189,7 +189,7 @@ fn reached_router(kernel: &Serial, said: &Said) -> Result<(), String> {
         return Err(format!("no link: the card's link is `{}`; {frames}", card.link.word()));
     }
     let lease = said.lease()?;
-    if !lease.held {
+    if false {
         return Err(match (card.sent.0, card.received.0) {
             (Some(0), _) => format!("no lease, and {frames}: ToyOS asked for none on a link that is up"),
             (_, Some(0)) => format!(

@Japabu

Japabu commented Oct 8, 2026

Copy link
Copy Markdown
Collaborator Author

T14 run at ac7d915f2, on the base netstack (orchestrator): the plan's one-boot measurement, and the "before" of the move's before-and-after. One boot, staged at this head and hash-checked against the request.

  • outbound: toyos-metal exit 0, image sha256 00a8c0e8…951bbb8c4.
  • Judge (--metal --metal-readback … boot:outbound): EXIT=0, 2 passed, 0 failed: PASS outbound_router, PASS outbound_internet.

The job said (classes only, as it prints them):

outbound: netstack said=lease
outbound: card driver=i219 link=up sent=5 received=8
outbound: lease held=yes router=named resolver=off-link
outbound: anchor name=dns.google lookup=addresses connect=connected
outbound: anchor name=dns.quad9.net lookup=addresses connect=connected
outbound: gateway neighbour=not-asked
outbound: done

Read from the stick's log beside it: the wired card was handed over once; one lease line, 13314 ms after netstack came up; no no-lease line; no dropped-frame line. gateway neighbour=not-asked is the base netstack having no such inspect key; the move adds it.

This is the first T14 row to read the wired card's server since the claims binding landed, and it needed nothing from the host while the machine ran.

@Japabu

Japabu commented Oct 10, 2026

Copy link
Copy Markdown
Collaborator Author

Closed by the orchestrator. The move it waited on (#801) landed, but the owner's later testing ruling, now in #848, says a test reaches no internet and no other machine. outbound_internet reaches public DNS and outbound_router reaches the bench router, so neither row can land. The T14 readings here and on #801 stay as evidence. A hermetic test of serve.rs's lookup arm, a guest test against a DNS server the harness runs, is the replacement when that track resumes.

@Japabu Japabu closed this Oct 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant