The Network Stack

July 8, 2026 · View on GitHub

The TCP/IP stack in NØNOS runs in a capsule, not the kernel. One capsule, net_core, holds a full smoltcp interface, ARP, IPv4, TCP, UDP, DHCP, and DNS, and drives it over a device that bridges to the NIC driver capsule by IPC. This page documents that stack and how it is assembled. The code is userland/capsule_net_core/.

smoltcp in a capsule

net_core builds on smoltcp 0.11 (Cargo.toml), the no_std Rust TCP/IP stack. Its interface is constructed in iface/build.rs:

  build(mac, port):
      seed  = crypto_random(8)                 // random_seed for the stack
      config = Config::new(Ethernet(mac)); config.random_seed = seed
      device = NicDevice { port }              // the bridge to the NIC driver capsule
      iface  = Interface::new(config, device, now)
      sockets = SocketSet::new(...)

So net_core is a real TCP/IP stack: it owns a smoltcp Interface and a SocketSet, seeds it with kernel randomness, and polls it forward. The layers, Ethernet and ARP, IPv4, and the TCP and UDP transports, are smoltcp's, and net_core adds the NØNOS-specific pieces around them: the device bridge, the DHCP client, the DNS resolver, and the IPC server that other capsules call.

The device bridge

smoltcp talks to hardware through a Device, and net_core's device is NicDevice (src/device/), which does not touch hardware itself: it exchanges frames with the NIC driver capsule over IPC, keyed by a port. The device implements the standard token model, an rx_token yields a received frame to the stack and a tx_token accepts a frame to transmit, and the tokens move the bytes to and from the driver capsule. This is the seam that keeps the TCP/IP stack and the device driver in separate capsules: net_core speaks IP and TCP, the driver speaks DMA rings and registers, and they meet at a frame over IPC.

The server and the poll loop

net_core runs a request server (src/server/) and polls the interface. The server parses an incoming request (parse_req), runs the operation against the socket set, and responds (respond); the poll (iface/poll.rs) advances smoltcp, processing received frames, running the protocol timers, and emitting frames to transmit. The protocol wire types for TCP, UDP, and DNS are in src/protocol/. The result is one capsule that, given a MAC address and a driver port, brings up a full IP host: it obtains an address by DHCP, resolves names by DNS, and serves TCP and UDP.

Consolidated and decomposed

There are two forms of the stack, and it is worth being clear which runs. The live form is the consolidated net_core above, selected by the nonos-capsule-net-core feature. There is also a decomposed form, separate net_l2, net_ip, net_tcp, net_udp, net_dhcp, and net_dns capsules, each a layer of the stack talking to its neighbors over IPC; those per-layer spawns are compiled out when net-core is enabled (src/userspace/init/spawn_plan/network/mod.rs). The decomposed form is the maximal-isolation design (one capsule per protocol layer); the consolidated net_core is the form that is runtime-proven, bringing the stack up to a bound DHCP lease on a desktop boot.

Source

  userland/capsule_net_core/src/iface/build.rs   the smoltcp Interface construction
  userland/capsule_net_core/src/device/          NicDevice, the rx/tx tokens over IPC
  userland/capsule_net_core/src/iface/poll.rs    the poll loop
  userland/capsule_net_core/src/server/          the request server
  userland/capsule_net_core/src/protocol/        TCP, UDP, DNS wire types
  src/userspace/init/spawn_plan/network/         the consolidated vs decomposed spawn