Performance¶
Measured on 2026-10-19. The Go benchmarks are on the development machine
(14 threads); the box's numbers on the lab VMs (lab.md), so the routed
numbers compare choices rather than promise a speed.
strongholdd's work per change¶
go test -bench . ./internal/... (one run of each):
| What | Time | Allocated |
|---|---|---|
Load and validate the example config (config.Load) |
0.83 ms | 0.26 MB |
| Validate a big config (1000 more rules, 60 reservations) | 0.46 ms | 0.13 MB |
| Marshal that big config to YAML (each change does this twice) | 13.6 ms | 15.9 MB |
| Render firewalld's zones and policies (example) | 0.28 ms | 0.22 MB |
| ... the big config (1000 more rules) | 2.6 ms | 1.3 MB |
| Render the NAT table | 0.005 ms | 4 KB |
| Render Kea, Knot and Unbound (example) | 0.13 ms | 60 KB |
| Render NetworkManager's profiles (example) | 0.09 ms | 48 KB |
Marshalling is the one heavy part, all of it in the YAML library's emitter; 14 ms for a thousand rules is nothing next to a person making the change, so it stays.
The API¶
On stronghold-fedora (2 vCPUs), over the Unix socket, 50 requests each;
the floor is curl's own start (~4.5 ms):
| Route | Per request |
|---|---|
GET /v1/version |
4.5 ms |
GET /v1/status |
5.0 ms |
GET /v1/config |
6.8 ms |
GET /v1/system/metrics |
6.9 ms |
GET /v1/firewall/counters |
18 ms (runs nft -j) |
GET /v1/functions |
29 ms (was 82: one systemctl is-active for all the programs on the host, not one each) |
strongholdd itself: 20 MB resident, 9 MB in its cgroup, idle at 0.1% CPU.
Routed traffic¶
Through the package-mode box (pkg, 2 vCPUs): pkgclient on its LAN,
iperf3 server on dhcpsrv beyond its WAN, NATed; each figure the mean
of two 10 s runs.
| TCP, 1 stream | 64-byte UDP received | |
|---|---|---|
| LAN on a port | 17.6 Gbit/s | 173 kpps |
| ... with a flowtable | 17.8 Gbit/s | 295 kpps |
| LAN on a bridge | 17.1 Gbit/s | 107 kpps |
| ... with a flowtable on its lower port | 17.6 Gbit/s | 293 kpps |
What it says for the design is in interfaces.md ("Measured").
The image-mode pair (2026-10-20)¶
gw1 of the lab's active-active pair on the host image without functions
(D23; stronghold:44.20261020.0049, stronghold 0.2.2, its functions'
images 0.2.1), 2 vCPUs, 2 GiB, virtio: client1 on its LAN, an iperf3
server beyond its WAN (libvirt's default), NATed; 20 s runs (UDP 15 s),
ping every 0.2 s beside each. The data and the report: out/perf/ in
the workspace (perf.json).
| WAN | TCP up, 1 stream | TCP down | 64-byte UDP | ping p50 / p95 under TCP load |
|---|---|---|---|---|
| shaped (CAKE, 20/50 Mbit), IDS preventing | 19 Mbit/s | 47 Mbit/s | 19 kpps | 0.7 / 1.1 ms |
| unshaped, IDS preventing (nfqueue) | 0.64 Gbit/s | 0.72 Gbit/s | 149 kpps | 44 / 62 ms |
| unshaped, IDS detecting | 7.5 Gbit/s | 19.9 Gbit/s | 133 kpps | 1.1 / 2.9 ms |
- The IDS in prevent mode is what limits a box: every WAN packet goes
through Suricata (two vCPUs, ET Open's 53214 rules), about 0.65 Gbit/s
either way, with the queue's latency; detecting, the box routes 7.5
Gbit/s up and 19.9 down. A box with prevent mode needs its cores for
Suricata, but not all of them: on four vCPUs, Suricata on two of them
(
ids.cpus: 0-1) forwarded 3–4.4 Gbit/s up where on all four it did 0.6–0.8 (ids.md, "On four CPUs"). - CAKE keeps latency where it was idle (0.9 ms) at the shaped rate; only a flood of UDP that no shaper refuses (64-byte packets, unlimited) reaches 65 ms at the 95th percentile.
- DNS: the resolver's container answered 77,300 queries a second from its cache (0.68 ms on average, none lost) to one client of one vCPU: the client's limit, not the box's (the container at 2% CPU).
- Failover (each three runs alike): a WAN's link lost costs 5.8 s of pings, its return none; a handover 0.4 s, a takeover 0.2 s; a node holding both groups losing its WAN, 0.4 s, and the peer's group going home after, 0.2 s (55 s before 0.2.3's fix).
- Functions: installed in 2 s from the box's cache, 3–4.5 s from the registry (images of 210–265 MB); eight installed at the first start after the move to the image, 15 s.
- Updates: 34 s (soft reboot) to 66 s (reboot) from
stronghold update applyto the node back and healthy; a rollback a minute. - Footprint: strongholdd 21 MB; the containers 3–19 MB each, the
resolver 160 MB (its blocklists), Suricata 580 MB; the host image 841
MB compressed. The API on its socket: 0.2–0.5 ms,
/v1/functions8 ms,/v1/firewall/counters45 ms (it runsnft).