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 <noreply@anthropic.com>
This commit is contained in:
parent
a9ff1166b5
commit
042ca7fb70
1 changed files with 38 additions and 18 deletions
|
|
@ -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
|
- [ ] `SCM_RIGHTS` for passing descriptors. Possibly unnecessary: an shm id is
|
||||||
an integer and travels fine over a stream socket, so this waits until
|
an integer and travels fine over a stream socket, so this waits until
|
||||||
something actually needs to pass an open file
|
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.
|
317453. MAC from EEPROM, transmit and receive rings, link detection.
|
||||||
Run with `-netdev user,id=n0 -device e1000,netdev=n0`.
|
Run with `-netdev user,id=n0 -device e1000,netdev=n0`.
|
||||||
**Verified to carry real traffic**: `echo n > /proc/sysrq` sends a
|
**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
|
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
|
(`kernel/net/net.c`, started by `netinit`) polls the NIC and runs its
|
||||||
timers. DHCP obtains an address at boot.
|
timers. DHCP obtains an address at boot.
|
||||||
**Verified end to end**: `nettest 10.0.2.2 9000` opens a TCP connection
|
DHCP obtains an address at boot and `/proc/netif` reports it, along with
|
||||||
out of the guest, and the listener on the host receives
|
the MAC, the gateway and frame counts taken at the lwIP seam.
|
||||||
`HELLO-FROM-SCHOS` while the guest receives the host's reply -- so the
|
**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
|
handshake, both data directions and the close all work, not just the
|
||||||
driver. `kthread_create` was added for this and is generally useful.
|
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
|
The reasoning behind not writing it, kept because it is the reason to
|
||||||
a bad trade: it is not the packet format that is hard, it is congestion
|
resist doing so later as well: it is not the packet format that is hard,
|
||||||
control, retransmission, window management and twenty years of accumulated
|
it is congestion control, retransmission, window management and twenty
|
||||||
corner cases that only appear against a real peer.
|
years of accumulated corner cases that only appear against a real peer.
|
||||||
**Preferred base: lwIP** (BSD-3, no advertising clause, verified). It was
|
lwIP was preferred over a BSD stack on structure rather than licence --
|
||||||
written for exactly this situation -- a small OS with a raw Ethernet
|
FreeBSD's `sys/netinet` is BSD-3 too, but it is not a library: it reaches
|
||||||
driver -- and its port layer is a handful of files.
|
into mbufs, epoch(9), netisr, VNET and uma(9), so taking it means porting
|
||||||
**If BSD instead: FreeBSD, not NetBSD.** NetBSD's `tcp_input.c` carries
|
FreeBSD's kernel infrastructure first. NetBSD was ruled out on licence:
|
||||||
the WIDE Project's 4-clause advertising clause; FreeBSD's has none. But
|
its `tcp_input.c` carries the WIDE Project's 4-clause advertising clause.
|
||||||
FreeBSD's `sys/netinet` is not a library -- it reaches into mbufs,
|
See docs/licensing.md §3.
|
||||||
epoch(9), netisr, VNET and uma(9), so taking it means porting FreeBSD's
|
|
||||||
kernel infrastructure first. See docs/licensing.md §3.
|
|
||||||
- [x] `AF_INET` on top of the same socket layer -- done, exactly as planned:
|
- [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
|
`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
|
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
|
both work.
|
||||||
of the current tick-poll are the two things still owed.
|
- [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
|
### Phase 3 — GPU
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue