Repository navigation
toyos-net-node: a datagram socket's broadcast permission, which each datagram keeps to the route it leaves by; a logged refusal out of the call that met it; and the name announced again when the link returns - #788
Conversation
…fusal out of the send that met it, and the name announced again when the link returns Three rules of the node stage of the move to ToyOS's own stack, each of which the track said the move does not land without. The node ships in nothing yet; toyos-mdns ships, and gains one call netstack on smoltcp does not make. The broadcast permission. Node::udp_set_broadcast hands a client's permission through Stack and Shard to Udp::set_broadcast, and udp.broadcast-not-permitted is answered Refused::PermissionDenied where it was invalid input: the request was well formed. US-21, decision U-4, by the owner's ruling that the permission is carried. A logged refusal. Only receive, fire and link passed over the stack's reports, so a line [udp] logged against a client's send waited for the next frame or deadline. udp_send_to now ends by taking the log lines out of what the shard reported; a lease report among them keeps its turn, since only the node's pass reads one against the state. No other client call of a datagram socket meets a logged rule. The name on a link that returns. The node keeps its lease across the link, so the responder saw no new address and announced nothing, where RFC 6762 section 8 asks for the announcing step on a link change. Responder::link_returned owes the record to the group twice, a second apart (section 8.3), the first no sooner than a second after the record's last multicast (section 6); Node::link calls it. A new address is announced through the same two-step, so the second announcement is one rule and not two. The track's three lines say what now holds. The line on a logged refusal is closed and deleted; the line on the name loses its clause that netstack announces again today, which it does not: it restarts DHCP on link-up only with no lease held. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RvnWQFcMuGqTHYhvSnTe8A
… into the node stage One conflict, in the track, in two places. The stage 5 paragraph: main said the broadcast permission was still to build on the node; this branch builds it, so the branch's sentence stands. The line on a client's datagram to a broadcast address: main's is kept sentence for sentence, the ruling, the pipe ABI's request and word, the search and its commands, the socket2 fork's issue, and the exit after the move with its guest test and C case, with three corrections for what this branch makes true. Its opening no longer says the send is answered invalid input. "Nothing enforces it yet ... the node has no call" becomes what the node now does and which tests hold it, with what still ships unchanged. Its exit before the move, a slice on the node that turns the two sends of each_refusal_is_answered_in_the_pipes_word_for_it, is met by this branch and replaced by what is left for the move itself: answering the option's request by Node::udp_set_broadcast and writing ERR_PERMISSION_DENIED for Refused::PermissionDenied. The line on a logged refusal, which main did not change and this branch closed, stays deleted. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RvnWQFcMuGqTHYhvSnTe8A
|
Mutation patches of round 1, each applied to the clean head
m1-the-refusal-is-invalid-input-againdiff --git a/userland/netstack/node/src/datagram.rs b/userland/netstack/node/src/datagram.rs
index b59680619..b4f73140d 100644
--- a/userland/netstack/node/src/datagram.rs
+++ b/userland/netstack/node/src/datagram.rs
@@ -77,7 +77,7 @@ fn refused(error: Error) -> Refused {
| Counter::SendPortZero
| Counter::SendUnspecifiedDestination
| Counter::ExceedsMtu => Refused::InvalidInput,
- Counter::BroadcastNotPermitted => Refused::PermissionDenied,
+ Counter::BroadcastNotPermitted => Refused::InvalidInput,
Counter::TxQueueFull => Refused::ResourceExhausted,
Counter::ConnectUnspecified
| Counter::ConnectGroupm2-the-node-hands-the-permission-to-nobodydiff --git a/userland/netstack/node/src/datagram.rs b/userland/netstack/node/src/datagram.rs
index b59680619..8371bb2f2 100644
--- a/userland/netstack/node/src/datagram.rs
+++ b/userland/netstack/node/src/datagram.rs
@@ -121,7 +121,7 @@ impl Node {
/// Whether the socket's datagrams may go to a broadcast address, the limited one or the
/// directed one of a prefix the machine holds, from here on.
pub fn udp_set_broadcast(&mut self, id: DatagramId, permitted: bool) -> Result<(), Refused> {
- self.stack.set_broadcast(id.0, permitted).map_err(refused)
+ self.stack.set_broadcast(id.0, permitted && false).map_err(refused)
}
/// Queues `payload` for `destination:port`; it leaves in a later [`Node::transmit`].m3-a-permission-given-is-never-taken-backdiff --git a/toyos-net-udp/src/lib.rs b/toyos-net-udp/src/lib.rs
index 55737e4e0..5026ee3e7 100644
--- a/toyos-net-udp/src/lib.rs
+++ b/toyos-net-udp/src/lib.rs
@@ -373,7 +373,7 @@ impl Udp {
/// POSIX's `SO_BROADCAST`: without it a send to a broadcast address is refused.
pub fn set_broadcast(&mut self, id: SocketId, permitted: bool) -> Result<(), Error> {
- self.socket(id).map(|s| s.broadcast = permitted)
+ self.socket(id).map(|s| s.broadcast |= permitted)
}
pub fn set_ttl(&mut self, id: SocketId, unicast: Ttl, multicast: Ttl) -> Result<(), Error> {m4-udp-asks-no-permissiondiff --git a/toyos-net-udp/src/lib.rs b/toyos-net-udp/src/lib.rs
index 55737e4e0..c7954fd50 100644
--- a/toyos-net-udp/src/lib.rs
+++ b/toyos-net-udp/src/lib.rs
@@ -485,7 +485,7 @@ impl Udp {
Some(Counter::SendUnspecifiedDestination)
} else if let Some(rule) = Self::unusable(ip, destination) {
Some(rule)
- } else if broadcast && !broadcast_permitted {
+ } else if broadcast && !broadcast_permitted && false {
Some(Counter::BroadcastNotPermitted)
} else if payload.len() > limits::MAX_PAYLOAD {
Some(Counter::ExceedsMtu)m5-a-send-ends-without-the-pass-over-the-log-linesdiff --git a/userland/netstack/node/src/datagram.rs b/userland/netstack/node/src/datagram.rs
index b59680619..41c23ca91 100644
--- a/userland/netstack/node/src/datagram.rs
+++ b/userland/netstack/node/src/datagram.rs
@@ -128,7 +128,6 @@ impl Node {
pub fn udp_send_to(&mut self, now: Instant, id: DatagramId, destination: Ipv4Addr, port: u16, payload: &[u8]) -> Result<(), Refused> {
let sent = self.stack.send_to(now, id.0, destination, port, payload).map_err(refused);
let events = &mut self.events;
- self.stack.refusals(|refusal, suppressed| events.push(crate::Event::Stack { refusal, suppressed }));
sent
}
m6-the-node-tells-the-name-nothing-of-the-linkdiff --git a/userland/netstack/node/src/lib.rs b/userland/netstack/node/src/lib.rs
index 62aeabd85..34717685d 100644
--- a/userland/netstack/node/src/lib.rs
+++ b/userland/netstack/node/src/lib.rs
@@ -164,7 +164,6 @@ impl Node {
self.stack.link(now, up);
if up {
if let Some(name) = &mut self.name {
- name.link_returned(now);
}
let out = self.client.link_up(now, &mut draw);
self.carry_out(now, out, None, &mut draw);m7-a-returned-link-is-announced-inside-the-second-of-the-last-multicastdiff --git a/userland/netstack/mdns/src/lib.rs b/userland/netstack/mdns/src/lib.rs
index 66b16f210..be015f6be 100644
--- a/userland/netstack/mdns/src/lib.rs
+++ b/userland/netstack/mdns/src/lib.rs
@@ -231,7 +231,7 @@ impl<'a> Responder<'a> {
/// comes is new.
pub fn link_returned(&mut self, now_ms: u64) {
if self.link.is_some() {
- self.owed_ms = Some(self.group_free_ms(now_ms));
+ self.owed_ms = Some(now_ms);
self.again = true;
}
}m8-a-returned-link-is-announced-oncediff --git a/userland/netstack/mdns/src/lib.rs b/userland/netstack/mdns/src/lib.rs
index 66b16f210..32235f285 100644
--- a/userland/netstack/mdns/src/lib.rs
+++ b/userland/netstack/mdns/src/lib.rs
@@ -232,7 +232,6 @@ impl<'a> Responder<'a> {
pub fn link_returned(&mut self, now_ms: u64) {
if self.link.is_some() {
self.owed_ms = Some(self.group_free_ms(now_ms));
- self.again = true;
}
}
m9-a-returned-link-owes-a-record-with-no-addressdiff --git a/userland/netstack/mdns/src/lib.rs b/userland/netstack/mdns/src/lib.rs
index 66b16f210..4ecfd7633 100644
--- a/userland/netstack/mdns/src/lib.rs
+++ b/userland/netstack/mdns/src/lib.rs
@@ -230,7 +230,7 @@ impl<'a> Responder<'a> {
/// be multicast. With no address held nothing is owed: the one that
/// comes is new.
pub fn link_returned(&mut self, now_ms: u64) {
- if self.link.is_some() {
+ if true {
self.owed_ms = Some(self.group_free_ms(now_ms));
self.again = true;
}m10-a-new-address-is-announced-oncediff --git a/userland/netstack/mdns/src/lib.rs b/userland/netstack/mdns/src/lib.rs
index 66b16f210..60a700ce9 100644
--- a/userland/netstack/mdns/src/lib.rs
+++ b/userland/netstack/mdns/src/lib.rs
@@ -213,7 +213,6 @@ impl<'a> Responder<'a> {
};
if self.link.is_none_or(|was| was.addr != link.addr) {
self.owed_ms = Some(now_ms);
- self.again = true;
}
self.link = Some(link);
if !self.owed_ms.is_some_and(|at| now_ms >= at) {m11-the-pass-over-the-log-lines-drops-every-other-reportdiff --git a/userland/netstack/node/src/lease.rs b/userland/netstack/node/src/lease.rs
index 23d957a70..579d2788f 100644
--- a/userland/netstack/node/src/lease.rs
+++ b/userland/netstack/node/src/lease.rs
@@ -184,7 +184,7 @@ impl Stack {
line(refusal, suppressed);
false
}
- Event::Verified(_) | Event::Conflict { .. } | Event::Lost { .. } | Event::NotVerified(_) => true,
+ Event::Verified(_) | Event::Conflict { .. } | Event::Lost { .. } | Event::NotVerified(_) => false,
});
}
The mutation script: #!/bin/sh
# Applies each mutation to a clean tree, builds, runs the five crates' suites, and restores.
# usage: mutate.sh <worktree> <patch dir> <log dir> <round>
set -u
W=$1; P=$2; L=$3; R=$4
cd "$W" || exit 2
CRATES="-p toyos-mdns -p toyos-net-node -p toyos-net-udp -p toyos-net-shard -p toyos-net-ip"
SUMMARY=$L/$R-mutations.summary
: > "$SUMMARY"
clean() { [ -z "$(git status --porcelain --ignore-submodules=none)" ]; }
clean || { echo "dirty tree before" >> "$SUMMARY"; exit 2; }
echo "head $(git rev-parse HEAD)" >> "$SUMMARY"
for patch in "$P"/m*.patch; do
name=$(basename "$patch" .patch)
git apply --check --recount "$patch" || { echo "$name: does not apply" >> "$SUMMARY"; exit 2; }
git apply --recount "$patch"
git diff > "$L/$R-$name.diff"
cargo test --locked --no-run $CRATES > "$L/$R-$name-builds.log" 2>&1; b=$?
echo "EXIT=$b" >> "$L/$R-$name-builds.log"
if [ $b -eq 0 ]; then
cargo test --locked --no-fail-fast $CRATES > "$L/$R-$name.log" 2>&1; t=$?
echo "EXIT=$t" >> "$L/$R-$name.log"
red=$(grep -E '^test .* \.\.\. FAILED$' "$L/$R-$name.log" | sed -E 's/^test (.*) \.\.\. FAILED$/\1/' | tr '\n' ' ')
else
t="not run"; red=""
fi
git apply -R --recount "$patch"
clean || { echo "$name: dirty tree after" >> "$SUMMARY"; exit 2; }
echo "$name | build $b | tests $t | $red" >> "$SUMMARY"
done
# m0: every source file of the change as on the base, the tests as they stand.
BASE=$(git merge-base HEAD origin/main)
SRC="toyos-net-shard/src toyos-net-udp/src userland/netstack/node/src"
git diff "$BASE" HEAD -- $SRC userland/netstack/mdns/src/lib.rs > "$L/$R-m0-forward.diff"
git checkout -q "$BASE" -- $SRC
cargo test --locked --no-run $CRATES > "$L/$R-m0-builds.log" 2>&1; b=$?
echo "EXIT=$b" >> "$L/$R-m0-builds.log"
git checkout -q HEAD -- $SRC
clean || { echo "m0: dirty tree after" >> "$SUMMARY"; exit 2; }
echo "m0-the-node-shard-and-udp-sources-as-on-the-base | build $b | tests not run |" >> "$SUMMARY"
echo DONE >> "$SUMMARY" |
|
Review, round 1, of Growth: BLOCKER
NOTE
Asked, not findingsBroadcast permission. Against US-21: both destinations are refused without the permission and leave in link broadcasts with it, read off the wire by a parser that is not ours; m1 to m4 are red at tests that name the claim, and m4 also at [udp]'s own US-21. The permission is written in three places only: Logged refusal. Once, at the call: Re-announce. RFC 6762 §8 asks for both steps on a link change, probing (§8.1) and then announcing (§8.3), and §8.3 announces unique records "that have completed the probing step". The stage announces only: a conformance defect against §8 and §8.1, older than this branch, which the track's line records. What it adds is occasions: each return of the link multicasts an unprobed record with the cache-flush bit (§10.2), and two machines named alike each flush the other's record from every cache on the link when their link returns. Counts and intervals are §8.3's minimum: two, one second apart (m8, m10). §6 holds on every path a returned link takes: the first announcement is owed at the later of now and one second after the record's last multicast (m7), and the second exactly one second after the first. A link that flaps without end is answered with one multicast a second and two after its last return; no path multiplies it. A query held back by §6 while an announcement is owed is answered by that announcement, at the same instant it was owed. Nothing is being probed when a link drops, since nothing probes. One thing a reader should know: an announcement that falls due while the link is down is handed to [udp], refused for want of a route, counted What is not built. Stopping was right. On this head
What the landing head must show. The pull request ready, and on the exact commit that lands: SEND BACK |
… to the route it leaves by, and a log line has one way out of the stack The broadcast permission was asked once, in [udp]'s submit, against the prefixes held then, and the datagram was routed again when it left, against the prefixes held then. `Ip::add_address` rewrites a held address's prefix in place, with whatever prefix a DHCP server's acknowledgement of a renewal names, so a datagram a socket without the permission had accepted for one host left as a link broadcast once the address was renewed with a prefix whose directed broadcast that host's address is; a widened prefix did the same to a datagram accepted for the router. The value that opened it is a server's, off the wire. What the socket held at the call now goes with the datagram: [udp]'s `Queued` carries it, a closed socket's datagrams included, `UdpOut` hands it to [ip], and `Ip::send_udp` refuses a link broadcast to a datagram without it: `ip.broadcast-not-permitted`, counted, logged against the destination, the flow told it is unreachable as for any refusal there. `UdpOut` is a struct every field of which its maker names, so no transport hands [ip] a datagram without saying. `toyos-net-shard/tests/udp.rs` holds it across a renewal that narrows the prefix, with the socket open and closed, across one that widens it, and for the permission of the call against the permission of the moment; run at 07fbe3c with its counter assertions taken out, all three are red on a frame to ff:ff:ff:ff:ff:ff. The node's test reaches it from a server's ACK, and [ip]'s test holds the rule at `send_udp`. `Node::transmit` now ends in the pass over the stack's log lines, since a datagram can be refused as it leaves. One way out of `Stack` for a log line: `Stack::refusals` is the one drain of the shard, `Report::Refused` and `report`'s own drain are gone, and the inbox holds [ip]'s reports on the address and nothing else. An announcement of the name that falls due with the link down is refused by [udp] for want of a route and counted `node.name-unsent`, and the responder holds it sent; the link's return owes both again. RFC 6762 §6 is a least interval, which that only lengthens, and §8.3's two leave after the return, so it stays, said at `name.rs`'s header and held by a test. `issues/netstack-answers-for-its-name-without-claiming-it.md` described `userland/netd/src/mdns.rs`, which the tree no longer has: it now says what `toyos-mdns` does, what a returning link multiplies, its owner and an exit a test can fail. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RvnWQFcMuGqTHYhvSnTe8A
…stage One conflict, in the track. Both sides rewrote the paragraph that says what is still to build on the node: #781's sentence stands less the broadcast permission, which this branch builds, so what is left is the datagram senders that wait on the hop, and then the move. Both sides rewrote the lines after the name's: this branch's line on the name stands, and #781's two lines replace the two on a query that never reached its resolver and on a burst at an unresolved resolver, whose exits #781 met. #781's departure line on NUD-04 and this branch's on the carried permission both stand. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RvnWQFcMuGqTHYhvSnTe8A
|
Round 2, answering the round-1 review (#788 (comment)) finding by finding. Head BLOCKER 1: a socket that never held the permission sends a link broadcast — fixedMeasured first. The test the review names, at the shard, run on The control is the branch's tests with one function emptied: Then closed, by what is queued and not by a second check.
BLOCKER 2: no guest measurement at this head — CI's, and run once locally
NOTEs
Mutations, round 2Each applied to the clean head
The control patch, on
|
|
Review, round 2, of Growth: Round 1's BLOCKERs
BLOCKERNone. NOTE
Asked, not findingsIs the wrong state unrepresentable. For a transport, yes. A connected socket hears The departure. Justified. US-21 is met unchanged: the permission is still asked at the call, The down-link announcement. Right. RFC 6762 §6 is a least interval between multicasts of a record, so a record held multicast that never left can only lengthen it; §8.3's two, a second apart, both leave after the return, which §8 asks. The NOTEs of round 1. Closed. #787. It touches no crate of this stage: its sources are What the landing head must show. The first NOTE done, with the five crates' suites and m12, m16 and m18 at that head in the body; if #787 is under it, the merge as above. Then, the pull request ready, on the exact commit that lands: LAND AFTER NAMED CHANGES |
…sen, and [ip]'s own messages never carry the permission The broadcast permission was asked in `send_udp`, one call above the arm of `datagram` that writes the link's broadcast address, and `own`, the path of [ip]'s ICMP messages, reached that arm unasked: only `admit`'s source rule, read when the datagram arrived, kept an echo reply or an error from it. The refusal is now `datagram`'s `NextHop::Broadcast` arm itself, with the permission its argument. `send_udp` passes the datagram's and `own` passes `false`, so no sender inside [ip] picks that address without saying so. `datagram` takes a transport's flow and frame as one argument, which they always were together. An error is routed when [udp] asks for it, after its datagram was admitted: a prefix that becomes usable in between can make the datagram's source its directed broadcast. `an_error_whose_next_hop_became_a_broadcast_is_refused_and_counted` builds that state through the public calls and finds the error counted, logged and not sent. The track's departure line said the readers' list of logged rules has none for a datagram; it has none for one that leaves. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RvnWQFcMuGqTHYhvSnTe8A
One conflict, in the track's line on the broadcast permission. #787 changed that line's last sentence only: the C case now runs the sequence without `bind` too, held by `tests/netcase/sendto_unbound.c`, where the sentence waited on an issue #787 closes and deletes. This branch rewrote the rest of the line. The line is this branch's with #787's ending. The line after it, on a logged refusal waiting in the stack, stays deleted: #787 did not touch it, and this branch met its exit. #787's other hunk in the track, the listener's `TCP_NODELAY` issue named among the listener defects, merged by itself. No line of the merged tree names any of the four issue files #787 deleted, by path or by bare name. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RvnWQFcMuGqTHYhvSnTe8A
|
Round 3, the named changes of the round-2 review, at The egress hunk (
|
|
CI at |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RvnWQFcMuGqTHYhvSnTe8A
Stage "node" of the move to ToyOS's own network stack: the three lines of
issues/toyos-has-its-own-network-stack.mdthat the plan gives the node between the listeners stage (#777) and the move.toyos-net-node,toyos-net-shard,toyos-net-udpandtoyos-net-ipship in nothing, so no guest test and no T14 row reaches these rules and none is added;toyos-mdnsships, and gains one call that netstack on smoltcp does not make.Head
108232f24: the stage's commit422292c38, round 2's436570ec9, round 3'se8a806cc8, andorigin/mainat9cf2c4e5c(#787, libc's sockets) merged in. Every gate below was run at108232f24.One thing briefed in round 1 is not built: datagram senders that wait on the next hop, with
PENDING_PER_NEIGHBOURback to 8. See "Not built" below; it is its own stage, now that #781 is in.What changed, per rule
1. A datagram socket's broadcast permission, which a datagram keeps (US-21, decision U-4; POSIX's
SO_BROADCAST).Node::udp_set_broadcast(id, permitted)hands the permission throughStack::set_broadcastandShard::udp_set_broadcasttoUdp::set_broadcast, which [udp] already had for the DHCP client's socket alone. A socket is bound without it.udp.broadcast-not-permittedis answeredRefused::PermissionDenied, where it wasInvalidInput: the request was well formed. The word is the node's name fortoyos::net'sERR_PERMISSION_DENIED, which The pipe ABI carries a datagram socket's broadcast permission from std and libc to netstack, and the option read is gone #783 added.submit, against the prefixes held then, and the datagram was routed again when it left, against the prefixes held then.Ip::add_addressrewrites a held address's prefix in place with whatever prefix a DHCP server's acknowledgement names, so a datagram accepted for one host left as a link broadcast from a socket that never held the permission. Now:Queuedcarries what its socket held at the call. The closed sender's queue isQueueds, so a closed socket's datagrams carry it with no code of their own.toyos_net_ip::UdpOuthas the fieldbroadcast. It is a struct whose maker names every field, and [udp]'sserveis its one maker outside tests: no transport hands [ip] a datagram without saying.Ip::send_udproutes the datagram as before and refuses aNextHop::Broadcastto one that does not carry the permission:ip.broadcast-not-permitted, counted, logged throughLog::refuseagainst the destination like [ip]'s other logged rules, and the flow told it is unreachable like every refusal of that call. The refusal is theNextHop::Broadcastarm ofdatagramitself (toyos-net-ip/src/egress.rs), the one function that writes a link destination, with the permission its argument (round 3, the review's NOTE):send_udppasses the datagram's, andown, the path of [ip]'s ICMP messages, passesfalse, so no sender inside [ip] reaches the link's broadcast address without saying so, and the check reads the route the frame would take and not a second predicate of [udp]'s.datagramtakes a transport's flow and frame as oneOptionof the pair, which they always were together; that keeps it at seven arguments. One order moved with the check: a datagram both too long and refused the broadcast is now countedip.exceeds-mtu, the length being read first (read, not tested).admitrefuses a source at an edge of a held prefix, but an error is routed when [udp] asks for it, afterreceivereturned: a prefix that becomes usable in between can make the admitted source its directed broadcast. On the shard the two calls are one step with nothing between; at [ip]'s interface the state is built through public calls withadmitas it is.Node::transmitends in the pass over the stack's log lines: a datagram can now be refused as it leaves, and its line is inNode::drain_eventswhen that opportunity returns.SO_BROADCAST: given later it covers nothing accepted before, and taken back it still covers what was accepted under it.a_broadcast_needs_its_permission(userland/netstack/node/tests/datagram.rs), as in round 1: refused without the permission in the word that says so, both broadcast addresses on the wire in link-broadcast frames with it, another socket still refused, refused again once it is taken back.s_udp_us_021_shard_a_datagram_accepted_for_a_host_is_no_broadcast_after_a_renewal_narrows_the_prefix(toyos-net-shard/tests/udp.rs): verified A/24, bind, a send to 192.0.2.127 accepted,add_address(A, 25), one transmit opportunity: no frame,ip.broadcast-not-permittedat 1, the shard's one line naming 192.0.2.127, and nothing in a second opportunity. Run twice, the second time with the socket closed before the renewal.…_accepted_for_the_router_is_no_broadcast_after_a_renewal_widens_the_prefix: A/25 with a router, a send to 192.0.2.255 accepted for the router,add_address(A, 24), the same.…_a_datagram_carries_the_permission_of_its_call: two sockets each with a datagram to 192.0.2.127 accepted, one permitted at the call and the permission taken back after, one not permitted and given it after; after the renewal exactly one frame leaves, to ff:ff:ff:ff:ff:ff from the first socket's port.a_datagram_accepted_for_a_host_is_no_broadcast_after_the_server_renews_a_narrower_prefix(the node'stests/datagram.rs): the same from the trust boundary, a server's ACK of the node's own renewal naming 255.255.255.128. The lease's prefix is 25, no frame leaves, the counter is 1, and the line is indrain_eventsafter the opportunity with noreceive,fireorlinkbehind it.s_udp_us_021_ip_no_link_broadcast_without_the_datagrams_permission(toyos-net-ip/tests/addr.rs):send_udpwith the field false, to the limited address and the /24's directed one: the refusal, the count, the two events, no frame and no ARP request; the same datagram to a host is held for its link address.an_error_whose_next_hop_became_a_broadcast_is_refused_and_counted(toyos-net-ip/tests/icmp.rs, round 3): a datagram from 192.0.2.127 admitted under A/24, 192.0.2.3/25 added and run to assigned, thenport_unreachablefor the arrival: no frame,ip.broadcast-not-permittedat 1 and logged against 192.0.2.127,icmp.error-suppressedandicmp.errors-sentat 0. Red under m12, m16 and m18.07fbe3c7b: exit 101, all three red on(MacAddr([255, 255, 255, 255, 255, 255]), "UDP 192.0.2.127 from port 5000")where no frame is expected (192.0.2.255 in the widened case, and both sockets' frames in the third). The patch and the log's failures are in the round-2 comment below.datagramand were run at108232f24: each builds (exit 0) and is red (exit 101) at the five tests it was red at in round 2 and at the new ICMP test. The other fifteen and m0 were not run again: their last run is at65961d1aa.SO_BROADCASTand RFC 919 §7 and RFC 922 §7 for what a broadcast is; at the nodeetherparsereads every frame, the control's broadcast frame under m12 included. At the shard the frames are read bytoyos-net-wire's parsers, which are ours.s_udp_us_021_broadcast_needs_permissionintoyos-net-udpis unchanged and red under m4.s_ip_mod_002_ordinary_refusals_are_not_loggedlists the rule beside the readers'.2. A refusal the stack logs is out of the node when the call that met it returns.
Node::udp_send_toand, from round 2,Node::transmitend in the pass over the stack's log lines, likereceive,fireandlink.Stackfor a log line (round 2, the review's NOTE).Stack::refusalsis the one drain of the shard: it hands each log line over and queues each of [ip]'s reports on the address forStack::report.Report::Refused, its arm insettleandreport's own drain are gone; the inbox is a queue of a type that has no log line in it.Node::logis the one caller.logged,udp.send-unspecified-destination(US-20) andudp.broadcast-not-permitted(US-21), and both aresubmit's.udp_bind,udp_recv_from,udp_closeandudp_set_broadcasttherefore take no pass.a_refusal_is_logged_by_the_call_that_met_it, as in round 1.the_log_lines_are_taken_and_the_report_before_them_keeps_its_turn, the unit test inlease.rs: a verification unread and a refused send behind it, the line taken, the verification still the next report, and a second pass handing neither over again.3. The machine's name is announced again when the link returns (RFC 6762 §8, §8.3, §6).
toyos_mdns::Responder::link_returned(now_ms)owes the record to the group twice, a second apart (§8.3), the first no sooner than a second after the record's last multicast (§6). With no address held it owes nothing: the address that comes is new and announced as before.Node::linkcalls it on a link that came up.onsends what is owed and owes the second, where it had one arm for a new address and one for a due one.onandowed_atonly; and the guest suite, whose every network test boots that responder, was run once locally at this head (below). The reading the review asks for is CI'sguest / suiteon the head that lands, which a draft does not run.toyos-mdns, not this one. §8 asks for probing first and §8.3 announces a record that "completed the probing step";toyos-mdnsprobes nowhere. What this stage adds to that defect is occasions: each return of the link is two more multicasts, with the cache-flush bit, of a record nobody probed.issues/netstack-answers-for-its-name-without-claiming-it.mddescribeduserland/netd/src/mdns.rs, which the tree no longer has; it is rewritten to be true oftoyos-mdns, with what a returning link multiplies, its owner and an exit a test can fail, and the track's line rests on it.node.name-unsent, and the responder holds it sent; the link's return owes two more, the first up to a second later than §6 needs. I read it as the rule and not a defect: §6 is a least interval, which a multicast held sent and never sent only lengthens, and §8.3's two leave after the return. Telling the responder would take a second call for a second that is spent once a link change. It is said atuserland/netstack/node/src/name.rs's header and held byan_announcement_due_with_the_link_down_is_counted_and_the_links_return_owes_both: the second announcement due 1 s after the first with the link down is counted and not sent, a return 300 ms later announces 1 s and 2 s after the unsent one, and never a third time.a_link_that_returns_is_announced_twice_a_second_apart_and_no_sooner_than_a_second_after_the_last_multicastintoyos-mdns,a_held_name_is_announced_again_when_the_link_returnsand the one above (userland/netstack/node/tests/name.rs).toyos-mdns;etherparsereads every frame.Not built: datagram senders that wait on the next hop
The table gives the node rows 2, 6 and 8, which are the three rules above; this slice is #781's recorded remainder, and the track now names it as what is still to build before the move, with #781's two lines for its owner and exit. The review's reading of its three open questions is in the round-1 review for that stage's brief.
What the move must still do with each
Node::udp_set_broadcastMsgType::UdpSetOptionwithOPT_BROADCASTby this call, the value's zero asfalse; a socket it does not hold is the call'sNotConnectedRefused::PermissionDeniedERR_PERMISSION_DENIED; the guest testudp_broadcast_needs_its_permissionand the C case the track names come behind the movedrain_eventseach turn of its loop, which it owes the log anywayip.broadcast-not-permitteddrain_events'Responder::link_returnedNode::link, which it must for the leaseChecks (a trust boundary and a device's protocol)
TX_DATAGRAMSlike any send. A socket without it sends none, whatever a server renews. A client's refused sends and dropped datagrams cost the log one line a rule in 10 s, the shard's bound, unchanged.TX_DATAGRAMSa socket andCLOSED_DATAGRAMSfor the closed, each counted; a connected socket among them hears its flow is unreachable, as for any datagram [ip] refuses.07fbe3c7babove. Eighteen mutations and m0, each a checked patch on the clean head, built, run, reported with its exit code and reversed by one script that refuses a dirty tree before the first and after each; patches, script and table are in the round-2 comment, and the three patches cut again for round 3 in the round-3 comment. At65961d1aaevery one was red at the tests named above (exit 101), and m0, the node's, shard's, [udp]'s and [ip]'s sources as onmainunder these tests, did not build (exit 101); at108232f24m12, m16 and m18 are red again (exit 101), the tree clean after each.Gates, at
108232f24cargo test --locked -p toyos-mdns(13 passed, 0 failed)cargo test --locked -p toyos-net-node(141 passed, 0 failed)cargo test --locked -p toyos-net-udp(59 passed, 0 failed)cargo test --locked -p toyos-net-shard(50 passed, 0 failed)cargo test --locked -p toyos-net-ip(273 passed, 0 failed)cargo test --locked --manifest-path userland/netstack/Cargo.toml(29 passed, 0 failed; the shipped shell, which linkstoyos-mdns)cargo clippy --locked -p toyos-mdns -p toyos-net-node -p toyos-net-udp -p toyos-net-shard -p toyos-net-ip --all-targets --keep-going -- <src/clippy.rs's ADOPTED as -W> -D warningscargo test --locked --lib -- hostws:: userlandhost:: sourcegate:: licence::from the root package (45 passed)cargo run -- --ci host(78 steps, all green)cargo run -- --build-onlycargo test --test toyos-build, the whole guest suite, once (37 passed, 37 total,libc_socketsamong them)cargo run -- --ci hostwas run twice at this head. The first run exited 1 with 1 of 78 steps red: the licences step'scargo metadatacould not resolve the crates.io index's host name, in a network outage on the development machine, and the other 77 steps were green. The second, with the network back, is the row above. The first run's log is kept asr3-ci-host-red-on-a-network-outage.log.The guest suite ran on a loaded, shared host:
uptimeread load averages 46.68 57.64 59.42 before it and 54.70 57.26 59.08 after, on 14 cores; the harness paid its liveness ceilings at 1.00x (fastest boot 456 ms against its reference 1424 ms). It is one local run, not CI'sguest / suiteunder KVM, which runs on the head that lands.Every step's log, its command first and its exit code last, is in the orchestrator's scratchpad under the stage's
logs/, namedr3-<step>.log; round 2's at65961d1aaarer2b-<step>.log, and the control isr2-control-on-07fbe3c7b.log.The merge of
9cf2c4e5c. One conflict, in the track's line on the broadcast permission: #787 changed its last sentence only, and the line is this branch's with #787's ending (tests/netcase/sendto_unbound.cholds the send withoutbind). #787's other hunk in the track, the listener'sTCP_NODELAYissue among the listener defects, merged by itself. No line of the tree names any of the four issue files #787 deleted, searched by path and by bare name.Not run, as briefed: anything on metal.
Not measured
Unsure of
send_udpreports every refusal to the flow as unreachable, which since toyos-net-node: a query known not to have reached its resolver is not waited out, an ICMP error ends nothing, and a burst of lookups reaches the wire #781 [udp] hands a connected socket asSocketError::NextHopFailed. For a peer whose address a renewal turned into a directed broadcast that word is inexact: nothing left, which is what it promises, but no link address was looked for. No client's socket is connected, and the resolver's, which are, ask the next resolver on it.nud::leavewrites the resolved MAC). Read, not tested.the_log_lines_are_taken_and_the_report_before_them_keeps_its_turnholds an order no call ofNode's can reach today. I kept the order and its test rather than anunreachable!in a path a client's send runs.Growth
git diff --shortstat origin/main...108232f24: 19 files, +631 -86. Production +154 -51:toyos-mdns+40 -17, the node'slease.rs+35 -15,datagram.rs+22 -4,lib.rs+15 -5,name.rs+10, [ip] +20 -8, [udp] +7 -2, the shard +5. Tests +441 -21: the node'stests/datagram.rs+123 -15, the shard'stests/udp.rs+102 -2, the node'stests/name.rs+70, the unit test inlease.rs+47,toyos-mdns's +47, [ip]'s tests +52 -4. The track and the issue +36 -14.Round 2 alone (
07fbe3c7bto436570ec9): production +67 -39, of which the carried permission is [ip]'s +12 -3 and [udp]'s +7 -2, and the one way out ofStackislease.rs+28 -25 withlib.rs+10 -4 anddatagram.rs+7 -5; tests +199 -8. Round 3 alone (65961d1aatoe8a806cc8):egress.rs+12 -9, the ICMP test +19, the track one sentence.🤖 Generated with Claude Code
https://claude.ai/code/session_01RvnWQFcMuGqTHYhvSnTe8A