From 042ca7fb70f80a2ce904ee47efac5e37a12b4bd0 Mon Sep 17 00:00:00 2001 From: Nexoniarz Date: Sat, 15 Aug 2026 16:24:03 +0200 Subject: [PATCH] docs: the network section says what is done and what is still owed Phase 2's entry for the TCP/IP stack was edited in place when lwIP landed, and the edit left the old decision text behind as an unchecked box -- so the list carried an open item that was really a paragraph of reasoning about a choice already made. The reasoning is worth keeping, because it is the argument against writing TCP by hand later as much as it was the argument then; it is no longer pretending to be a task. What is recorded now: AF_INET's two debts are paid, bootcheck -Net verifies the whole thing without anyone typing, and the two things that remain are named -- accept() not reporting a peer address, and receive still being polled on the timer tick rather than woken by the e1000's interrupt. That last one is the honest limit on latency today, and writing it down beats rediscovering it with a stopwatch. Also: the e1000 path had not been updated for the drivers/ split. Co-Authored-By: Claude Opus 5 --- docs/roadmap.md | 56 +++++++++++++++++++++++++++++++++---------------- 1 file changed, 38 insertions(+), 18 deletions(-) diff --git a/docs/roadmap.md b/docs/roadmap.md index c04bce5..252452e 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -118,7 +118,7 @@ TCP/IP, so they are the right thing to build the socket layer's shape around. - [ ] `SCM_RIGHTS` for passing descriptors. Possibly unnecessary: an shm id is an integer and travels fine over a stream socket, so this waits until something actually needs to pass an open file -- [x] e1000 driver — `kernel/drivers/net/e1000.c`, from Intel's public manual +- [x] e1000 driver — `kernel/drivers/net/e1000/e1000.c`, from Intel's public manual 317453. MAC from EEPROM, transmit and receive rings, link detection. Run with `-netdev user,id=n0 -device e1000,netdev=n0`. **Verified to carry real traffic**: `echo n > /proc/sysrq` sends a @@ -133,28 +133,48 @@ TCP/IP, so they are the right thing to build the socket layer's shape around. netif). NO_SYS=1, so lwIP has no threads of its own and one kernel thread (`kernel/net/net.c`, started by `netinit`) polls the NIC and runs its timers. DHCP obtains an address at boot. - **Verified end to end**: `nettest 10.0.2.2 9000` opens a TCP connection - out of the guest, and the listener on the host receives - `HELLO-FROM-SCHOS` while the guest receives the host's reply -- so the + DHCP obtains an address at boot and `/proc/netif` reports it, along with + the MAC, the gateway and frame counts taken at the lwIP seam. + **Verified end to end**, by `bootcheck -Net` rather than by hand: the + guest opens a TCP connection out, the listener on the host receives + `HELLO-FROM-SCHOS`, and the guest receives the host's reply -- so the handshake, both data directions and the close all work, not just the driver. `kthread_create` was added for this and is generally useful. -- [ ] Was: not written from scratch -- decided 2026-08-08. Writing TCP is the one part of this where doing it yourself is - a bad trade: it is not the packet format that is hard, it is congestion - control, retransmission, window management and twenty years of accumulated - corner cases that only appear against a real peer. - **Preferred base: lwIP** (BSD-3, no advertising clause, verified). It was - written for exactly this situation -- a small OS with a raw Ethernet - driver -- and its port layer is a handful of files. - **If BSD instead: FreeBSD, not NetBSD.** NetBSD's `tcp_input.c` carries - the WIDE Project's 4-clause advertising clause; FreeBSD's has none. But - FreeBSD's `sys/netinet` is not a library -- it reaches into mbufs, - epoch(9), netisr, VNET and uma(9), so taking it means porting FreeBSD's - kernel infrastructure first. See docs/licensing.md §3. + The reasoning behind not writing it, kept because it is the reason to + resist doing so later as well: it is not the packet format that is hard, + it is congestion control, retransmission, window management and twenty + years of accumulated corner cases that only appear against a real peer. + lwIP was preferred over a BSD stack on structure rather than licence -- + FreeBSD's `sys/netinet` is BSD-3 too, but it is not a library: it reaches + into mbufs, epoch(9), netisr, VNET and uma(9), so taking it means porting + FreeBSD's kernel infrastructure first. NetBSD was ruled out on licence: + its `tcp_input.c` carries the WIDE Project's 4-clause advertising clause. + See docs/licensing.md §3. - [x] `AF_INET` on top of the same socket layer -- done, exactly as planned: `socknew` dispatches on the domain, `kernel/net/inet.c` implements the AF_INET side on lwIP's raw API, and no syscall changed shape. TCP and UDP - both work; `recvfrom`'s source address and a proper sleep/wakeup instead - of the current tick-poll are the two things still owed. + both work. +- [x] The two things AF_INET owed on the day it landed, both closed 2026-08-15. + `recvfrom` reports the source address -- which meant giving UDP a + datagram queue first, since the payloads shared TCP's byte ring and two + datagrams arriving together came back as one -- and `sendto` takes a + destination. Blocking sockets sleep on a counter bumped by the lwIP + callbacks instead of waking on every timer tick; `tcp_sent` had to be + registered for it, or a write blocked on a full send buffer would sleep + with nobody left to wake it. +- [x] `bootcheck -Net`, so none of the above is verified by hand any more. A + listener and a UDP responder run on the host, the guest is told to talk + to them, and both ends are asserted -- one side alone cannot tell a + working stack from one that completes a handshake and drops every byte. + 45 [ok] with it on. +- [ ] `accept()` does not report the peer's address, and there is no + `getsockname`/`getpeername`. Nothing has needed them yet; a server that + wants to log who connected will. +- [ ] Receive is still polled. The network thread looks at the NIC every timer + tick when the link is idle, so an arriving frame waits up to 10 ms before + anything sees it. Waking that thread from the e1000 interrupt is the real + fix and was deliberately not the first version: a receive path woken by an + interrupt is much harder to debug than one that is asked. ### Phase 3 — GPU