Data plane
src/bpf/voidgate.bpf.c
GPL-2.0-only XDP program. Verdicts are XDP_PASS or XDP_DROP only. No AF_XDP, no redirect, no userspace packet path.
01 / architecture
A multi-layer XDP shield. Userspace owns state. The program reads cfg.armed and, when armed, enforces a prefix-tree drop list. Thresholds do not live in BPF. Default verdict is XDP_PASS.
IDLE. XDP stays attached. rx_* then XDP_PASS. No Ethernet parse, no LPM, no host or remote maps. Arm on wake_pps / wake_mbps or ctl.
Allow LPM, local-side TCP allow_ports, DHCP, IPv6 NDP 133–137 / fe80::/10 / ff02::/16. Userspace refuses a drop CIDR that covers local_* or allow_*.
Drop LPM. A remote over threshold_pps / threshold_mbps becomes /32 or /128. Manual voidgatectl drop is this layer and does not expire.
aggregate_k dropped hosts in a /24 or /64 install that prefix. Policy and aggregate live ban_time seconds. IPv4 and IPv6 together, always.
Data plane
src/bpf/voidgate.bpf.cGPL-2.0-only XDP program. Verdicts are XDP_PASS or XDP_DROP only. No AF_XDP, no redirect, no userspace packet path.
Control plane
src/voidgate.c + policy.cWatches coarse rx rates, arms the gate, walks remote maps, and writes CIDRs into LPM drop tries. Apache-2.0.
The cheap path is the product. On every packet the program bumps metrics.rx_pkts / rx_bytes, loads cfg.armed, and if it is zero returns XDP_PASS. It must not parse Ethernet, must not LPM, and must not touch host or remote maps.
Whitelist checks run before the drop tree. Default verdict is still XDP_PASS.
parse_err and still pass.fe80::/10, ff02::/16, and ICMPv6 NDP types 133–137 skip the drop tree. IPv4 has no extra hard-pass here; link-local v4 is an allow-list entry.allow_v4 / allow_v6 skips the drop tree.dport and dest in local_*, or sport and src in local_*. A remote source port of 22 is not a whitelist hit.drop_v4 / drop_v6 returns XDP_DROP.host_* and remote_*; outbound from a local src updates host_*.LPM lookup keys use prefixlen 32 or 128 for a host query. LPM_TRIE maps are created with BPF_F_NO_PREALLOC.
| Map | Type | Written when | Notes |
|---|---|---|---|
| cfg | ARRAY[1] | userspace | armed, allow_ports |
| metrics | PERCPU_ARRAY[1] | always (rx); armed (dropped, …) | userspace sums CPUs |
| drop_v4 / drop_v6 | LPM_TRIE | ACTIVE | empty in IDLE after disarm |
| allow_v4 / allow_v6 | LPM_TRIE | start / reload | metadata, localhost, link-local by default |
| local_v4 / local_v6 | LPM_TRIE | start / reload | this VM’s CIDRs |
| host_v4 / host_v6 | PERCPU_HASH | armed | local IPs |
| remote_v4 / remote_v6 | LRU_PERCPU_HASH | armed | attack sources; not flushed on disarm |
In ACTIVE, userspace walks remotes. A source over threshold_pps or threshold_mbps is inserted as /32 or /128 (reason=policy). If at least aggregate_k dropped hosts sit in the same /24 or /64, that prefix is inserted too (reason=aggregate). Policy and aggregate drops expire after ban_time. Manual drops do not.
ACTIVE returns to IDLE when the NIC has been quiet for clear_seconds and the drop tree is empty. voidgatectl drop forces ACTIVE. voidgatectl disarm flushes drops and returns IDLE.
local_* or allow_*.