// 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 a driver's 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. // // Nothing here names a driver. Interfaces come from the netdev class // (include/netdev.h): every bound network device is attached, and a machine // with two NICs gets two interfaces without a line changing in this file. // // 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 "device.h" #include "netdev.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); } // --------------------------------------------------------------------------- // Waking on a received frame // // The thread used to wait for the clock and nothing else, so a frame arriving // just after a poll sat in the ring for up to 10 ms. That was deliberate at the // time -- a receive path woken by an interrupt is much harder to debug than one // that is asked, and the stack had to be proven first -- and it is now the // largest fixed latency in the system. // // The wait stays on `ticks`, rather than moving to a channel of its own, and // that is the whole design. lwIP's timers must still run when the link is // silent, so the clock has to remain a floor under the wait; a second channel // would mean sleeping on two things at once, which sleep() cannot do. Joining // the tick's existing wakeup costs nothing and keeps one wait, one predicate. // // `rx_signal` lives under tickslock for the same reason it is not its own lock: // sleep() releases tickslock atomically, so a driver that sets the flag and // wakes under that lock cannot lose the wakeup by arriving in the window // between the predicate test and the sleep. A private lock would not be held // across the sleep, and that window would be back. // // Called from interrupt context. acquire() disables interrupts, so a CPU // holding tickslock cannot be interrupted into this; another CPU spins briefly. // That is the same arrangement the timer handler itself uses. // --------------------------------------------------------------------------- static int rx_signal; // Counted so the mechanism is observable rather than assumed: if `rx` stays // zero while traffic flows, the interrupt is not arriving and the thread is // running on the clock exactly as it did before. static uint64 wake_rx, wake_tick; void netdev_rx_signal(void) { acquire(&tickslock); rx_signal = 1; wakeup(&ticks); release(&tickslock); } void net_wake_counts(uint64 *rx, uint64 *tick) { if (rx) *rx = wake_rx; if (tick) *tick = wake_tick; } // Sleep until there is something to do: a frame has been signalled, or the // clock has moved and lwIP's timers are due. static void wait_work(void) { acquire(&tickslock); uint start = ticks; while (ticks == start && !rx_signal) sleep(&ticks, &tickslock); if (rx_signal) { wake_rx++; rx_signal = 0; } else { wake_tick++; } 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; // Set once lwIP is initialised and this thread is running its timers. Distinct // from net_bound, and the distinction is not academic: inet_new() used to // require an *address* before it would hand out a socket, so every AF_INET // socket() failed until DHCP answered -- reported as ENFILE, which sends you to // look at the file table. It is also simply wrong for 127.0.0.1, which needs no // lease and no NIC. // // Creating a socket is not the same as having somewhere to send: a bind or a // connect with no route fails on its own, with an error that says so. static int net_ready; int net_is_up(void) { return net_bound; } int net_is_ready(void) { return net_ready; } // An interface's address and netmask, in network byte order, for callers that // have no business including lwIP's headers -- kernel/net/netlink.c is the one // that needs them. Zero when nothing is bound yet, which is a real answer: an // interface without a lease has no address to report. uint netif_addr4(struct netif *nif) { return nif ? ip4_addr_get_u32(netif_ip4_addr(nif)) : 0; } uint netif_mask4(struct netif *nif) { return nif ? ip4_addr_get_u32(netif_ip4_netmask(nif)) : 0; } // Network to host and back, for a 32-bit quantity. The kernel is little-endian // and the wire is big-endian; lwIP's own macros are not reachable outside the // files that include its headers. uint ntohl_u32(uint v) { return ((v & 0xFF) << 24) | ((v & 0xFF00) << 8) | ((v >> 8) & 0xFF00) | ((v >> 24) & 0xFF); } uint htonl_u32(uint v) { return ntohl_u32(v); } // Attach an interface that appeared after boot. // // netinit() walks the devices that exist when it runs and starts DHCP on each. // An interface created later -- by a process claiming /dev/netdev, which is // how an LDLS-hosted driver arrives -- misses that entirely, and would come up // with no address and no way to ever get one. // // It lives here rather than in kernel/dev/netuser.c because this is the only // file allowed to include lwIP's headers; reaching into the stack from a // device driver would put them on every kernel file's include path. See the // note on /proc/netif in kernel/dev/procfs.c, which is the same rule. int net_attach_late(struct device *d) { int idx; if (!net_ready) return -1; // netinit() has not run; it will pick this up lwip_sys_lock(); idx = net_eth_attach(d); if (idx >= 0) dhcp_start(net_eth_netif(idx)); lwip_sys_unlock(); if (idx >= 0) cprintf("net: %s attached as interface %d, asking for an address\n", d->name, idx); return idx; } 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(); // From here a socket can be created: the pools exist and the timers are // about to run. An address may still be minutes away, or never arrive. net_ready = 1; lwip_sys_lock(); lwip_init(); // Every bound network device becomes an interface. DHCP is started on each // of them: which one answers is the network's business, not ours, and an // interface left unconfigured because it happened to be second would be a // rule nobody could see. int attached = 0; for (int i = 0; i < netdev_count(); i++) { struct device *d = netdev_at(i); int idx = net_eth_attach(d); if (idx < 0) continue; dhcp_start(net_eth_netif(idx)); attached++; cprintf("net: %s attached as interface %d\n", d->name, idx); } if (attached == 0) { lwip_sys_unlock(); cprintf("net: no network device; the 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(); } lwip_sys_unlock(); cprintf("net: lwIP %d.%d.%d up on %d interface%s, requesting an address\n", LWIP_VERSION_MAJOR, LWIP_VERSION_MINOR, LWIP_VERSION_REVISION, attached, attached == 1 ? "" : "s"); // One bit per interface, so a second one binding later is announced too. int announced[NETIF_MAX] = { 0 }; for (;;) { int moved; lwip_sys_lock(); moved = net_eth_poll(); sys_check_timeouts(); // Loopback has no driver and no interrupt: a packet sent to 127.0.0.1 // sits on the loopif's queue until something drains it. With NO_SYS=1 // that something is this thread. netif_poll_all(); // Report each 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. for (int i = 0; i < attached; i++) { struct netif *nif = net_eth_netif(i); if (announced[i] || ip4_addr_isany_val(*netif_ip4_addr(nif))) continue; 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); // klog, not cprintf: a lease arrives seconds after the shell // prompt does, so on the display this lands in the middle of // whatever the user is doing. /proc/netif is where a person looks // for an address; the serial line is where the log belongs. klog("net: %s %d.%d.%d.%d/%d.%d.%d.%d via %d.%d.%d.%d\n", net_eth_device(i)->name, 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[i] = 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_work(); } } /* ------------------------------------------------------------------ */ /* /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; } // One block of fields per interface, separated by a blank line. A reader that // can parse one can parse any number, which is the whole reason it is not a // single flat list of values: with two NICs a flat list has no way to say which // address belongs to which. int net_info(char *buf, int cap) { int len = 0; // Room for the largest block, so the loop below never needs a bounds check // in the middle of writing one. if (cap < 256 * (NETIF_MAX + 1)) return 0; if (!net_started || net_eth_count() == 0) { len += put_field(buf + len, "state"); len += put_str(buf + len, netdev_count() ? "down\n" : "no interface\n"); return len; } for (int i = 0; i < net_eth_count(); i++) { struct netif *nif = net_eth_netif(i); struct device *dev = net_eth_device(i); if (i) buf[len++] = '\n'; lwip_sys_lock(); len += put_field(buf + len, "interface"); len += put_str(buf + len, dev->name); buf[len++] = '\n'; len += put_field(buf + len, "driver"); len += put_str(buf + len, dev->driver ? dev->driver : "-"); buf[len++] = '\n'; len += put_field(buf + len, "mac"); len += put_mac(buf + len, nif->hwaddr); buf[len++] = '\n'; // Per interface, not per system: with two NICs one can be configured // while the other is still asking. len += put_field(buf + len, "state"); len += put_str(buf + len, ip4_addr_isany_val(*netif_ip4_addr(nif)) ? "no address\n" : "up\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_eth_counts(i, &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'; } // How the network thread is being woken, for the whole stack rather than // per interface. `wake_rx` staying at zero while frames arrive means the // receive interrupt is not reaching us and the thread is running on the // clock exactly as it did before -- which works, and is 10 ms slower. { uint64 wrx, wtick; net_wake_counts(&wrx, &wtick); len += put_field(buf + len, "wake_rx"); len += put_u(buf + len, wrx); buf[len++] = '\n'; len += put_field(buf + len, "wake_tick"); len += put_u(buf + len, wtick); buf[len++] = '\n'; } return len; } // Start the stack. Called from main() once processes exist. void netinit(void) { if (net_started) return; // Before the check below, not after: the lock it initialises has to be // valid on a machine with no NIC too. Nothing can reach a wait there -- // inet_new refuses while the stack is down -- but a lock whose validity // depends on that reasoning holding forever is not worth the two lines // saved. inet_init(); if (netdev_count() == 0) { // Silent: a machine with no network device 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"); }