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>
152 lines
4.7 KiB
C
152 lines
4.7 KiB
C
// SPDX-License-Identifier: Apache-2.0
|
|
//
|
|
// The lwIP network interface backed by the e1000 driver.
|
|
//
|
|
// Two directions and almost nothing else: a linkoutput that flattens a pbuf
|
|
// chain into a frame and hands it to the driver, and a poll that takes frames
|
|
// off the driver and hands them up. Everything about IP, ARP and TCP lives in
|
|
// lwIP; everything about descriptors and registers lives in the driver; this
|
|
// file is the seam.
|
|
|
|
#include "types.h"
|
|
#include "defs.h"
|
|
#include "param.h"
|
|
|
|
#include "lwip/opt.h"
|
|
#include "lwip/netif.h"
|
|
#include "lwip/pbuf.h"
|
|
#include "lwip/etharp.h"
|
|
#include "lwip/ethip6.h"
|
|
#include "netif/ethernet.h"
|
|
|
|
#define ETH_MTU 1500
|
|
#define ETH_FRAMESZ 1518 // 1500 payload + 14 header + 4 FCS allowance
|
|
|
|
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.
|
|
// Safe as a single static because every caller holds the lwIP lock.
|
|
static char txframe[ETH_FRAMESZ];
|
|
static char rxframe[ETH_FRAMESZ];
|
|
|
|
// Send one packet. lwIP hands over a pbuf chain; the driver wants one flat
|
|
// buffer, so this walks the chain into txframe.
|
|
static err_t
|
|
e1000_linkoutput(struct netif *netif, struct pbuf *p)
|
|
{
|
|
(void)netif;
|
|
|
|
if (p->tot_len > sizeof(txframe))
|
|
return ERR_IF;
|
|
|
|
u16_t off = 0;
|
|
for (struct pbuf *q = p; q != NULL; q = q->next) {
|
|
if (off + q->len > sizeof(txframe))
|
|
return ERR_IF;
|
|
memmove(txframe + off, q->payload, q->len);
|
|
off += q->len;
|
|
}
|
|
|
|
// A full transmit ring is congestion, not failure. Reporting ERR_IF would
|
|
// make lwIP drop the segment and wait for a retransmission timer; ERR_WOULDBLOCK
|
|
// lets it try again, which for TCP is the difference between a stall of
|
|
// milliseconds and one of seconds.
|
|
if (e1000_transmit(txframe, off) != 0)
|
|
return ERR_WOULDBLOCK;
|
|
|
|
tx_frames++;
|
|
return ERR_OK;
|
|
}
|
|
|
|
// Bring the interface up. Called once, by the network thread.
|
|
static err_t
|
|
e1000_netif_init(struct netif *netif)
|
|
{
|
|
const unsigned char *mac = e1000_mac();
|
|
|
|
netif->name[0] = 'e';
|
|
netif->name[1] = 'n';
|
|
netif->output = etharp_output; // IP -> ARP -> linkoutput
|
|
netif->linkoutput = e1000_linkoutput;
|
|
netif->mtu = ETH_MTU;
|
|
netif->hwaddr_len = 6;
|
|
for (int i = 0; i < 6; i++)
|
|
netif->hwaddr[i] = mac[i];
|
|
netif->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_LINK_UP;
|
|
return ERR_OK;
|
|
}
|
|
|
|
struct netif *
|
|
net_e1000_netif(void)
|
|
{
|
|
return &e1000_netif;
|
|
}
|
|
|
|
// Attach the interface with no address yet -- DHCP fills it in.
|
|
int
|
|
net_e1000_attach(void)
|
|
{
|
|
ip4_addr_t any;
|
|
IP4_ADDR(&any, 0, 0, 0, 0);
|
|
|
|
if (netif_add(&e1000_netif, &any, &any, &any, NULL,
|
|
e1000_netif_init, netif_input) == NULL)
|
|
return -1;
|
|
|
|
netif_set_default(&e1000_netif);
|
|
netif_set_up(&e1000_netif);
|
|
netif_set_link_up(&e1000_netif);
|
|
return 0;
|
|
}
|
|
|
|
// Take whatever the driver has and push it into lwIP. Returns how many frames
|
|
// moved, so the caller can tell a busy poll from an idle one.
|
|
//
|
|
// Bounded rather than draining: a receive loop with no limit starves everything
|
|
// else on a busy network, and the caller comes straight back anyway.
|
|
int
|
|
net_e1000_poll(void)
|
|
{
|
|
int moved = 0;
|
|
|
|
for (int i = 0; i < 32; i++) {
|
|
int len = e1000_receive(rxframe, sizeof(rxframe));
|
|
if (len <= 0)
|
|
break;
|
|
|
|
// PBUF_POOL, so this comes from lwIP's own pool rather than a heap it
|
|
// does not have. A null return means the pool is exhausted, which is a
|
|
// dropped packet -- entirely normal under load, and what the protocol
|
|
// above is designed to survive.
|
|
struct pbuf *p = pbuf_alloc(PBUF_RAW, (u16_t)len, PBUF_POOL);
|
|
if (p == NULL)
|
|
break;
|
|
|
|
pbuf_take(p, rxframe, (u16_t)len);
|
|
|
|
// netif->input is netif_input, set at netif_add: it dispatches on the
|
|
// Ethernet type. Ownership of the pbuf passes with the call, including
|
|
// when it fails, so there is nothing to free here.
|
|
if (e1000_netif.input(p, &e1000_netif) != ERR_OK)
|
|
pbuf_free(p);
|
|
|
|
rx_frames++;
|
|
moved++;
|
|
}
|
|
return moved;
|
|
}
|