Networking
Networking lives in sys-io, on moto-netstack, the Motor OS networking
stack. It is configured from one file, /system/cfg/sys-net.toml, read when
sys-io starts. As of August 2026 it is considered ready for production use within its
supported feature set: static IPv4 and IPv6, DHCPv4, TCP and UDP, native DNS, and
fail-stop diagnostics.
Configuration
A malformed file prevents sys-io from starting. A valid file that matches no network device deliberately leaves only loopback and logs the unmatched devices. The file is an image input, so use DHCP for one reusable cloud image, or provision a distinct file into each static-address image.
Interfaces
loopback enables the built-in 127.0.0.1/8 and
::1/128 interface. auto_icmp_echo_reply controls automatic echo
replies; it does not bypass the rules that suppress multicast, broadcast, and
foreign-destination replies.
Each [devices.NAME] table consumes one virtio-net device. With
mac, the entry selects that exact hardware address; without it, entries
consume the remaining devices in table-name order. Unmatched entries and unconfigured
devices are logged. A static device looks like this:
[devices.net0]
cidrs = ["192.0.2.10/24", "2001:db8::10/64"]
dns_servers = ["192.0.2.53", "2001:db8::53"]
[[devices.net0.routes]]
ip_network = "0.0.0.0/0"
gateway = "192.0.2.1"
[[devices.net0.routes]]
ip_network = "::/0"
gateway = "2001:db8::1"
dns_servers supplies resolver addresses for a static device. Each route's
gateway must be reachable through a CIDR on the same device. Route selection uses the
longest matching prefix and prefers a connected route over an equally specific gateway
route.
For a reusable DHCPv4 image:
[devices.net0]
dhcp = true
DHCP owns the device's IPv4 address, default route, and advertised DNS servers. Static
IPv4 CIDRs, routes, or DNS servers on the same device are rejected, while static IPv6
configuration may coexist. On renewal, replacement, or lease loss, sys-io updates the
address and route and atomically republishes the aggregate active DNS server list in
/system/cfg/libc/resolv.conf. Wildcard TCP listeners begin accepting on an
address when a lease installs it and stop accepting new connections when the address
disappears; established connections are not aborted only because a lease was lost, and
applications must still handle the ordinary I/O failure that follows when the old address
is no longer usable.
Admission limits
max_half_open_global and max_half_open_per_listener (defaults
128 and 32) bound sockets in SYN-RECEIVED. max_backlog_global and
max_backlog_per_listener (defaults 128 and 32) bound the pools of listening
sockets behind listeners: a pool starts at the size the program asked for, 4 by default,
and doubles whenever a burst of simultaneous connections empties it, because a
connection that finds no listening socket is refused rather than queued. All four must be
nonzero. Each socket reserves receive and transmit rings, so large values have a direct
memory cost; lower them on a small VM, raise them for a server that meets large
connection bursts or many slow, distant clients.
Response rate limits
max_icmp_error_rate, max_rst_rate, and
max_syn_cookie_rate (defaults 200, 200, and 1000 per second, with a
one-second burst) limit, per external device, the replies that go wherever a packet's
source address points: automatic ICMP errors, resets answering traffic at closed ports,
and the stateless SYN|ACKs a flooded listener answers with once SYN cookies engage. That
source address is the sender's to forge, so an unlimited rate would let anyone use the
machine as a reflector. A suppressed reply is simply not sent; loopback is exempt. Zero is
rejected; omit a key to keep its default.
What the stack supports
| Area | Supported | Not supported yet |
|---|---|---|
| IPv4 | Static addresses, DHCPv4, ARP, fragmentation and reassembly, path MTU discovery, longest-prefix routing. | |
| IPv6 | Static addresses and routes, neighbor discovery, fragmentation, a bounded 64-entry neighbor cache, a reduced source-address selection policy. | DHCPv6, SLAAC, router advertisements, and general extension-header support. |
| TCP | CUBIC congestion control with a 10-segment initial window; SACK; RACK-TLP loss recovery (RFC 8985); RFC 7323 timestamps and window scaling; SYN cookies under flood; keepalive; zero-window probing; TIME-WAIT retention; per-connection linger on close; family-correct wildcard listeners. | SO_KEEPALIVE is always on and not yet configurable. |
| UDP | Unicast, connected and unconnected, IPv4 and IPv6, with bounded receive queues. | Multicast and broadcast. |
| ICMP | Echo request and reply (ping), with automatic replies configurable; incoming errors are validated before they affect a flow. | Raw sockets. |
| Names | The DNS resolver service, a hosts file, and resolv.conf maintained by sys-io. |
Socket options exposed to programs: TCP_NODELAY, TTL, SO_LINGER,
receive and send buffer sizes, IPV6_V6ONLY, and shutdown of either direction.
TCP buffers default to 128 KiB per direction and can be set between 16 KiB and 8 MiB. A
graceful close of a connection that never sent a byte is reset rather than FIN-closed;
this is an accepted non-POSIX exception.
Capacity and performance
- Host-to-guest TCP throughput is about 10 Gbps under Cloud Hypervisor and somewhat less under QEMU. The transmit path uses checksum and TSO offload; receive coalescing and jumbo frames are deliberately not used yet.
- sys-io serves at most 896 network channels, each with four sub-channels, which is roughly 3,500 concurrent sockets per machine, and one 64 KiB-stack client thread per channel. There is no per-process quota yet: one process can hold every channel, and any role can bind any port.
- Under memory pressure, new listeners, connects, UDP binds, echo requests, and client connections are refused until memory is available again (see Memory).
- The MTU comes from the device (
VIRTIO_NET_F_MTU) or is 1500. Link status changes are not yet tracked.
From a program's point of view
std::net works: TcpListener, TcpStream,
UdpSocket, name resolution, nonblocking mode, timeouts, and shutdown. The
Motor OS ports of mio and Tokio sit on an edge-triggered readiness interface in the
runtime, so Tokio and everything built on it (hyper, axum, russh) works. Programs on
Motor OS get real error kinds for a reset connection; a few conditions (refused,
unreachable, broken pipe) still surface as NotConnected, and giving them
their own codes is on the follow-up list. A connect through std currently
tries only the first resolved address.
Failure and diagnostics
sys-io owns live filesystem and networking state that cannot be reconstructed safely in a replacement process, so Motor OS uses a fail-stop policy: if sys-io exits or is killed, the kernel logs its status and halts the VM instead of attempting an unsafe partial restart.
Configure the hypervisor to retain the VM's serial console. Kernel, sys-io, sys-init,
and headless service logs are emitted there, and it remains the only diagnostic channel
when networking or sys-io itself has failed. For QEMU, run-qemu.sh uses
-nographic, so redirect its stdout and stderr to a retained log; cloud
deployments should enable their provider's serial-console capture before first boot.
ss lists TCP sockets and stats reads the net.*
counters (packets, drops, half-open and backlog counts, SYN cookie and suppression
counters, reassembly outcomes); see Logs and diagnostics.
What is deliberately accepted
- ARP and NDP spoofing on the local link is accepted as Linux parity: an unsolicited request or advertisement can rewrite a cached neighbor entry. Static neighbor entries for gateways are a possible future option.
- Deployments with mutually untrusted local processes, or on an untested hypervisor, need a separate review of the per-process quota and platform items on the follow-up list.
The remaining networking work, about fifty robustness, completeness, and test items,
is tracked in docs/plans/networking-remaining-steps.md in the repository.