The TCP/IP stack landed with its end-to-end test performed by hand: start a listener, boot, type nettest, read the output. That is evidence once and nothing afterwards, and it is exactly the kind of check that quietly stops being run. bootcheck -Net does the whole exchange. A listener runs on this host on a port picked by binding one and letting go, the guest is told to connect out to 10.0.2.2 at that port, and both ends are asserted: the guest reports what it received, the listener reports what arrived. One side alone proves much less than it looks -- a stack that completes a handshake and then drops every byte satisfies any check made from inside the guest. The listener is a background job, a separate process, because this harness has one hard rule: nothing may block the writer feeding QEMU's stdin. A socket accept is precisely the wait that deadlocked two earlier attempts at reading QEMU's output. It answers the protocol nettest speaks and then closes -- the close is load-bearing, since nettest reads until end of file and a listener that stayed open would hang the guest rather than fail it. The NIC is spelled out (-netdev user + -device e1000) rather than left to QEMU's default. Other runs can take the default and still answer "did a driver bind"; a run that asserts on an address should be reading its own configuration. /proc/netif is new, and the address is why. Until now the only report was a line on the console at boot, which userland cannot read and a test can only find by scraping the transcript. It gives interface, MAC, state, address, netmask, gateway, MTU and frame counts. Named netif rather than net on purpose: /proc/net is a *directory* on Linux, and a plain file there would give ENOTDIR to anything reaching for /proc/net/dev, which is worse than not having it. The counters are kept at the lwIP seam rather than in the driver, because a frame the driver received and the netif dropped never reached the stack. Verified: 38 [ok], no failures. Eleven of them are new -- lwIP up, DHCP obtaining 10.0.2.15 from SLIRP (a number nothing in the guest knows in advance, so it is the exchange completing rather than a guess), the four /proc/netif fields, frames counted in both directions, the handshake, the reply read back, and the listener confirming HELLO-FROM-SCHOS arrived. One bug found by writing it: $netPort was chosen after the command list was built, so the guest ran `nettest 10.0.2.2` with no port and printed its usage. An unset PowerShell variable interpolates to nothing rather than failing, which is worth remembering next time a command in this file looks right and behaves oddly. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
263 lines
7.7 KiB
C
263 lines
7.7 KiB
C
// SPDX-License-Identifier: Apache-2.0
|
|
//
|
|
// The network thread, and the one place the TCP/IP stack is started.
|
|
//
|
|
// lwIP with NO_SYS=1 has no threads of its own, so something has to feed it
|
|
// received frames and run its timers. That something is a kernel thread rather
|
|
// than the e1000 interrupt handler, and the reason is the lock: a call into
|
|
// lwIP can reach the driver, the driver can sleep, and an interrupt handler
|
|
// cannot. So the interrupt keeps doing what it always did -- clear the cause
|
|
// register -- and this thread polls.
|
|
//
|
|
// Polling costs a wakeup every few milliseconds when the link is idle. The
|
|
// alternative, waking this thread from the interrupt, is a worthwhile change
|
|
// later; it is deliberately not the first version, because a receive path that
|
|
// is woken from an interrupt is much harder to debug than one that is asked.
|
|
|
|
#include "types.h"
|
|
#include "defs.h"
|
|
#include "param.h"
|
|
#include "spinlock.h"
|
|
#include "sleeplock.h"
|
|
#include "proc.h"
|
|
|
|
#include "lwip/opt.h"
|
|
#include "lwip/init.h"
|
|
#include "lwip/timeouts.h"
|
|
#include "lwip/netif.h"
|
|
#include "lwip/dhcp.h"
|
|
#include "lwip/ip4_addr.h"
|
|
|
|
// Wait until the next timer tick. `ticks` advances at 100 Hz, so this is a
|
|
// sleep of up to 10 ms -- and, more importantly, it is the kernel's only timed
|
|
// sleep: there is no higher-resolution timer to wait on. The lwIP lock must not
|
|
// be held across it, or nothing else could touch the stack while the link is
|
|
// idle, which is most of the time.
|
|
static void
|
|
wait_tick(void)
|
|
{
|
|
acquire(&tickslock);
|
|
uint start = ticks;
|
|
while (ticks == start)
|
|
sleep(&ticks, &tickslock);
|
|
release(&tickslock);
|
|
}
|
|
|
|
static int net_started;
|
|
|
|
// Set once DHCP has bound, so `ifconfig`-ish output and /proc/net have
|
|
// something true to say rather than reporting 0.0.0.0 forever.
|
|
static int net_bound;
|
|
|
|
int
|
|
net_is_up(void)
|
|
{
|
|
return net_bound;
|
|
}
|
|
|
|
static void
|
|
net_thread(void)
|
|
{
|
|
// lwIP's own initialisation has to happen in this thread, not in main():
|
|
// it allocates from lwIP's pools and registers timers, and doing that
|
|
// before there is a process context means doing it somewhere that cannot
|
|
// sleep if the allocator ever needs to.
|
|
lwip_sys_init();
|
|
|
|
lwip_sys_lock();
|
|
lwip_init();
|
|
if (net_e1000_attach() != 0) {
|
|
lwip_sys_unlock();
|
|
cprintf("net: no e1000; the network thread has nothing to drive\n");
|
|
// Not a panic and not an exit: a kernel thread must never return, and
|
|
// a machine with no NIC is an ordinary machine.
|
|
for (;;)
|
|
wait_tick();
|
|
}
|
|
struct netif *nif = net_e1000_netif();
|
|
dhcp_start(nif);
|
|
lwip_sys_unlock();
|
|
|
|
cprintf("net: lwIP %d.%d.%d up, requesting an address\n",
|
|
LWIP_VERSION_MAJOR, LWIP_VERSION_MINOR, LWIP_VERSION_REVISION);
|
|
|
|
int announced = 0;
|
|
|
|
for (;;) {
|
|
int moved;
|
|
|
|
lwip_sys_lock();
|
|
moved = net_e1000_poll();
|
|
sys_check_timeouts();
|
|
|
|
// Report the address once, when it first arrives. Anyone who has spent
|
|
// an evening wondering whether the guest has an address at all wants
|
|
// this on the console rather than in a file they have to know about.
|
|
if (!announced && !ip4_addr_isany_val(*netif_ip4_addr(nif))) {
|
|
const ip4_addr_t *a = netif_ip4_addr(nif);
|
|
const ip4_addr_t *m = netif_ip4_netmask(nif);
|
|
const ip4_addr_t *gw = netif_ip4_gw(nif);
|
|
cprintf("net: %d.%d.%d.%d/%d.%d.%d.%d via %d.%d.%d.%d\n",
|
|
ip4_addr1(a), ip4_addr2(a), ip4_addr3(a), ip4_addr4(a),
|
|
ip4_addr1(m), ip4_addr2(m), ip4_addr3(m), ip4_addr4(m),
|
|
ip4_addr1(gw), ip4_addr2(gw), ip4_addr3(gw), ip4_addr4(gw));
|
|
announced = 1;
|
|
net_bound = 1;
|
|
}
|
|
lwip_sys_unlock();
|
|
|
|
// Busy while there is traffic, idle otherwise. Sleeping even 1 ms with
|
|
// frames waiting would cap throughput at one burst per tick.
|
|
if (moved == 0)
|
|
wait_tick();
|
|
}
|
|
}
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* /proc/netif */
|
|
/* ------------------------------------------------------------------ */
|
|
//
|
|
// Named netif rather than net, and that is deliberate: on Linux /proc/net is a
|
|
// *directory* of a dozen files, and putting a plain file there would mean
|
|
// anything reaching for the familiar /proc/net/dev got ENOTDIR from a path that
|
|
// looks like it exists. This is one interface's state, so it is named for what
|
|
// it holds and leaves the Linux name free for the day there is a directory to
|
|
// put under it.
|
|
//
|
|
// No snprintf in the kernel, so this appends by hand in the same style as
|
|
// kernel/dev/pci.c.
|
|
|
|
static int
|
|
put_str(char *out, const char *s)
|
|
{
|
|
int n = 0;
|
|
while (s[n]) { out[n] = s[n]; n++; }
|
|
return n;
|
|
}
|
|
|
|
static int
|
|
put_u(char *out, uint64 v)
|
|
{
|
|
char tmp[24];
|
|
int i = 0, n = 0;
|
|
|
|
if (v == 0) { out[0] = '0'; return 1; }
|
|
while (v && i < (int)sizeof(tmp)) { tmp[i++] = '0' + (char)(v % 10); v /= 10; }
|
|
while (i--) out[n++] = tmp[i];
|
|
return n;
|
|
}
|
|
|
|
static int
|
|
put_ip4(char *out, const ip4_addr_t *a)
|
|
{
|
|
int n = 0;
|
|
n += put_u(out + n, ip4_addr1(a)); out[n++] = '.';
|
|
n += put_u(out + n, ip4_addr2(a)); out[n++] = '.';
|
|
n += put_u(out + n, ip4_addr3(a)); out[n++] = '.';
|
|
n += put_u(out + n, ip4_addr4(a));
|
|
return n;
|
|
}
|
|
|
|
static int
|
|
put_mac(char *out, const unsigned char *m)
|
|
{
|
|
static const char d[] = "0123456789abcdef";
|
|
int n = 0;
|
|
for (int i = 0; i < 6; i++) {
|
|
if (i) out[n++] = ':';
|
|
out[n++] = d[m[i] >> 4];
|
|
out[n++] = d[m[i] & 0xF];
|
|
}
|
|
return n;
|
|
}
|
|
|
|
// One "name value" pair per line, which is the shape /proc/schfs and
|
|
// /proc/fsstat already use here -- a reader that can scan one can scan all of
|
|
// them.
|
|
static int
|
|
put_field(char *out, const char *name)
|
|
{
|
|
int n = put_str(out, name);
|
|
while (n < 12)
|
|
out[n++] = ' ';
|
|
return n;
|
|
}
|
|
|
|
int
|
|
net_info(char *buf, int cap)
|
|
{
|
|
int len = 0;
|
|
|
|
if (cap < 512)
|
|
return 0;
|
|
|
|
if (!net_started) {
|
|
len += put_field(buf + len, "state");
|
|
len += put_str(buf + len, e1000_present() ? "down\n" : "no interface\n");
|
|
return len;
|
|
}
|
|
|
|
struct netif *nif = net_e1000_netif();
|
|
|
|
lwip_sys_lock();
|
|
|
|
len += put_field(buf + len, "interface");
|
|
buf[len++] = nif->name[0];
|
|
buf[len++] = nif->name[1];
|
|
len += put_u(buf + len, nif->num);
|
|
buf[len++] = '\n';
|
|
|
|
len += put_field(buf + len, "mac");
|
|
len += put_mac(buf + len, nif->hwaddr);
|
|
buf[len++] = '\n';
|
|
|
|
len += put_field(buf + len, "state");
|
|
len += put_str(buf + len, net_bound ? "up\n" : "no address\n");
|
|
|
|
len += put_field(buf + len, "address");
|
|
len += put_ip4(buf + len, netif_ip4_addr(nif));
|
|
buf[len++] = '\n';
|
|
|
|
len += put_field(buf + len, "netmask");
|
|
len += put_ip4(buf + len, netif_ip4_netmask(nif));
|
|
buf[len++] = '\n';
|
|
|
|
len += put_field(buf + len, "gateway");
|
|
len += put_ip4(buf + len, netif_ip4_gw(nif));
|
|
buf[len++] = '\n';
|
|
|
|
len += put_field(buf + len, "mtu");
|
|
len += put_u(buf + len, nif->mtu);
|
|
buf[len++] = '\n';
|
|
|
|
lwip_sys_unlock();
|
|
|
|
uint64 rx, tx;
|
|
net_e1000_counts(&rx, &tx);
|
|
|
|
len += put_field(buf + len, "rx_frames");
|
|
len += put_u(buf + len, rx);
|
|
buf[len++] = '\n';
|
|
|
|
len += put_field(buf + len, "tx_frames");
|
|
len += put_u(buf + len, tx);
|
|
buf[len++] = '\n';
|
|
|
|
return len;
|
|
}
|
|
|
|
// Start the stack. Called from main() once processes exist.
|
|
void
|
|
netinit(void)
|
|
{
|
|
if (net_started)
|
|
return;
|
|
if (!e1000_present()) {
|
|
// Silent: a machine without an e1000 is not misconfigured, and this
|
|
// runs on every boot.
|
|
return;
|
|
}
|
|
net_started = 1;
|
|
if (kthread_create(net_thread, "netd") < 0)
|
|
cprintf("net: could not start the network thread\n");
|
|
}
|