test: the network is checked by the suite now, and /proc/netif says what it did
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>
This commit is contained in:
parent
c0806d1b64
commit
f46fb41966
6 changed files with 268 additions and 1 deletions
|
|
@ -24,6 +24,19 @@
|
|||
|
||||
static struct netif e1000_netif;
|
||||
|
||||
// Frames in and out, for /proc/netif. Counted here rather than in the driver
|
||||
// because this is the seam lwIP actually uses: a frame the driver received and
|
||||
// this file dropped never reached the stack, and a counter kept one layer down
|
||||
// would say otherwise. Only the network thread touches them, so no lock.
|
||||
static uint64 rx_frames, tx_frames;
|
||||
|
||||
void
|
||||
net_e1000_counts(uint64 *rx, uint64 *tx)
|
||||
{
|
||||
*rx = rx_frames;
|
||||
*tx = tx_frames;
|
||||
}
|
||||
|
||||
// One staging buffer, not one per call: a frame is 1518 bytes and the kernel
|
||||
// stack is 4096 in total, so a local of this size on a path that is several
|
||||
// frames deep is exactly the overflow the kstack canary exists to catch.
|
||||
|
|
@ -56,6 +69,7 @@ e1000_linkoutput(struct netif *netif, struct pbuf *p)
|
|||
if (e1000_transmit(txframe, off) != 0)
|
||||
return ERR_WOULDBLOCK;
|
||||
|
||||
tx_frames++;
|
||||
return ERR_OK;
|
||||
}
|
||||
|
||||
|
|
@ -131,6 +145,7 @@ net_e1000_poll(void)
|
|||
if (e1000_netif.input(p, &e1000_netif) != ERR_OK)
|
||||
pbuf_free(p);
|
||||
|
||||
rx_frames++;
|
||||
moved++;
|
||||
}
|
||||
return moved;
|
||||
|
|
|
|||
133
kernel/net/net.c
133
kernel/net/net.c
|
|
@ -113,6 +113,139 @@ net_thread(void)
|
|||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* /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)
|
||||
|
|
|
|||
Loading…
Reference in a new issue