Network
Networking stack
Native network modes on Linux, built-in user-mode NAT on macOS and Windows, and network cards built ahead of time.
The FlashVMM networking stack gives each FlashVM up to eight virtio-net NICs, wired on Linux by flashnet in bridge, NAT or routed mode on host taps (and pre-built in NIC pools by flashnetd), and on macOS and Windows by a built-in user-mode NAT, with per-NIC token-bucket rate caps that change on a running VM.
The VMM process leaves host networking to dedicated tools: on Linux it runs without CAP_NET_ADMIN and opens taps that already exist. Network setup is a separate, root-owned job (flashnet for one VM, flashnetd for pools), so the process that runs guest code stays unprivileged. Every frame passes through the VMM, which is where rate caps, the egress engine and the secret relay act.
Key facts
| Runs on | Linux: flashnet bridge, NAT and routed modes plus NIC pools on host taps · macOS and Windows: built-in user-mode NAT |
|---|---|
| Interfaces | CLI flash run --nic, flash set --nic, flashnet, flashnet pool · MCP flash_cli, flash_api · REST /v1/flashvm/nics, flashnetd /v1/nic-pools |
| NICs per VM | Up to 8, each its own virtio-mmio register block and interrupt line |
| Rate caps per NIC | mbps (egress), pps, ingress-mbps; uncapped unless given; 0 removes a cap live |
| Linux NAT addressing | One /30 per VM from 10.200.0.0/13, derived from the VM id, with its own nftables table |
| User-mode NAT segment | 192.168.127.0/24; gateway and DNS at .1, host alias at .254 |
| NIC-pool API (flashnetd) | 5 resources, 7 operations, on /run/flashnetd.sock |
How it works
The guest NIC
Each NIC is a modern (v2) virtio-mmio virtio-net device with one RX and one TX queue, advertising VIRTIO_F_VERSION_1 and VIRTIO_NET_F_MAC, so the datapath is a straight frame copy. The guest's MAC is derived from the four octets of the NIC's assigned IP. The guest agent configures every addressed NIC at boot, before the workload starts; only NIC 0's gateway becomes the default route. On Linux the guest's TX kick reaches the network pump through a KVM ioeventfd, without a VM exit.
Rate control
Each NIC carries optional token buckets: egress bytes (mbps), egress packets (pps) and ingress bytes (ingress-mbps). A cap applies only when given. flash set ID --nic N --mbps … or PATCH /v1/flashvm/nics/{nic_index} re-rates a running NIC on every OS; 0 deletes that bucket.
Linux: flashnet modes
flashnet creates a tap, wires it to an uplink, runs the given command with {NIC} replaced by the resulting --nic value, and undoes every step in reverse order when the VM exits (or on SIGINT, SIGTERM, SIGHUP). All wiring is native netlink and nftables.
- NAT. The VM gets a
/30carved from10.200.0.0/13by hashing its--vm-id, so a restore of the same VM gets the same subnet its guest already holds. flashnet refuses a/30already routed elsewhere. Masquerade lives in a dedicated nftables table per tap. The tap's MAC is derived from the gateway address, so a restored guest's cached gateway entry stays valid. The uplink defaults to the interface carrying the default route. - Bridge. The tap joins a Linux bridge on the uplink; the guest sits on the LAN and runs its own DHCP client. The bridge takes the uplink's MAC and IPv6 settings, is shared by every flashnet VM on that uplink under a lock, and the host's network is put back when its last user exits.
- Routed. flashnet obtains a LAN address for the guest by DHCP through a short-lived macvlan probe, installs a
/32host route to the tap and answers for the guest with proxy ARP.
Linux: NIC pools
flashnetd is one root daemon on /run/flashnetd.sock that builds NICs before any VM starts. A pool is up to 1,000 NICs of one mode, each built by the same per-mode setup as a single flashnet run, with taps named fnp<id>-<n> and owned by the UID allowed to claim them. flash run --nic pool=ID (also fork and rewind) claims one; it returns to the pool when its VMM lets go of the tap (the tap loses carrier), when nothing opens it within 30 s, or on release, and it is rebuilt clean: NAT and routed NICs get a new address and fresh firewall state, bridge NICs cycle their link. A pool can carry a default egress policy for every VM that claims from it. Pool state is on disk, so a restarted daemon re-adopts its NICs. See pools.
macOS and Windows: built-in user-mode NAT
Each OS uses its native unprivileged path: the HVF engine on macOS and the WHP engine on Windows exchange Ethernet frames with a bundled user-mode NAT process over a pipe, so a FlashVM reaches the network with no host privilege and no host interface changes. The guest's default segment is 192.168.127.0/24; the gateway at .1 also answers DNS, and .254 maps to the host's loopback, which is where the secret relay listens. The Virtualization.framework engine on macOS uses the same datapath and rate caps.
Interfaces
CLI
# Linux: one VM on NAT; flashnet fills in {NIC} and tears it all down on exit
sudo flashnet --mode nat --vm-id web1 -- \
flash run --bundle ./bundle --vcpus 2 --mem-mib 1024 --id web1 --nic {NIC},mbps=100
# Linux: a pool of 50 NAT NICs owned by UID 1000, then claim one
sudo flashnet pool create --mode nat --count 50 --id web --owner 1000
flash run --bundle ./bundle --vcpus 2 --mem-mib 1024 --nic pool=web,mbps=200
flashnet pool ls
flashnet pool show web
# Any OS: caps at create, then re-rate live
flash run --bundle ./bundle --vcpus 2 --mem-mib 1024 --id app \
--nic tap=t0,mbps=100,pps=10000,ingress-mbps=100
flash set app --nic 0 --mbps 500 --ingress-mbps 0
MCP
flash_cli(args: ["set", "app", "--nic", "0", "--mbps", "500"])
flash_api(vm_id: "app", method: "GET", path: "/v1/flashvm/nics/0")
REST
GET /v1/flashvm/nics
GET /v1/flashvm/nics/{nic_index}
PATCH /v1/flashvm/nics/{nic_index} {"mbps": 500, "pps": 0, "ingress_mbps": 100}
flashnetd (/run/flashnetd.sock):
POST /v1/nic-pools {"id": "web", "mode": "nat", "count": 50}
GET /v1/nic-pools
GET /v1/nic-pools/{pool_id}
DELETE /v1/nic-pools/{pool_id}
POST /v1/nic-pools/{pool_id}/claims {"vm_id": "app"} -> 201 {"tap", "nic"}
DELETE /v1/nic-pools/{pool_id}/claims/{tap}
NIC byte counters are the tap's, from the host's perspective (rx_bytes is what the host received from the guest), and are omitted when the tap cannot be read. Contract:
PATCHwith none of the rate fields returns422 nothing_to_change; an index the VM lacks returns404 unknown_nic.- A NIC in the launch config whose tap failed to open returns
409 nic_not_attached. - NICs are fixed at boot:
POST /v1/flashvm/nicsandDELETE /v1/flashvm/nics/{nic_index}return409 hotplug_unsupported. To take a NIC out of service, re-rate it. - Pool ids are 1 to 6 characters of
a-z0-9(they name the taps).
Guarantees
- An unprivileged VMM. Host network changes are made by a separate root tool; the process running guest code opens a ready tap or pipe.
- Clean teardown. Every flashnet step is undone in reverse, including on signals and partial failures.
- Stable across restore. A restored VM gets back the NAT subnet and gateway MAC its guest already holds.
- Clean reuse. A pooled NIC is rebuilt before its next VM gets it.
- Live control. Rate caps and egress policy change on a running NIC.
See networking, NIC pools and egress control.