XDP · multi-layer shield · idle until the NIC is on fire

voidGate

A multi-layer XDP shield for a single Linux VM. Silent unless the instance is under attack. Packets stay in the kernel: XDP_PASS or XDP_DROP. IPv4 and IPv6 together.

cfg.armed
0
rx_pkts
0
passed
0
dropped
0
  1. NIC
  2. rx++
  3. parse
  4. allow
  5. drop LPM
  6. count
  7. XDP_PASS
  8. XDP_DROP

On-page visualization of voidgate.bpf.c. Not live telemetry. Keys: I idle, A active.

What stays true

01 / invariants

Idle path

One load, then pass

If cfg.armed == 0, XDP does not parse Ethernet, does not LPM, and does not touch host or remote maps. Only metrics.rx_*, then XDP_PASS.

Stay attached

Arming is a map write

XDP stays on the NIC in IDLE. cfg.armed = 1 turns the gate on. Detach only on process exit — not every quiet cycle.

Split brain

Userspace decides drops

Thresholds live in the control plane. The BPF program only looks prefixes up. Default verdict on a shared NIC is XDP_PASS.

Four layers

02 / shield

One program, stacked. Watch first. Keep the VM reachable. Cut attacker hosts. Widen the prefix when the cluster is dense.

  1. 01

    Watch

    IDLE. XDP stays attached. Only rx_*, then XDP_PASS — no parse, no LPM. Arm on wake_pps / wake_mbps, or with voidgatectl arm.

  2. 02

    Keep

    Never drop the VM. Allow CIDRs, TCP allow_ports on the local side, DHCP 67/68 and 546/547, IPv6 NDP 133–137, fe80::/10, ff02::/16.

  3. 03

    Cut

    Drop LPM. Policy inserts a remote over threshold as /32 or /128. voidgatectl drop is the same layer, and does not auto-expire.

  4. 04

    Widen

    When aggregate_k dropped hosts sit in one /24 or /64, install that prefix. Policy and aggregate expire with ban_time.

State machine

03 / idle ↔ active

Idle · armed = 0

XDP attached. Coarse rx counters only. Userspace polls wake_pps / wake_mbps.

  • No drop tree
  • No per-source maps written
  • voidgatectl arm forces ACTIVE
→ ACTIVE wake_pps / wake_mbps or ctl arm / drop
← IDLE quiet clear_seconds and empty drops

Active · armed = 1

Parse IPv4/IPv6. Whitelist. Drop LPM. Count local hosts and remote sources.

  • Top remotes → /32 or /128
  • Dense clusters → /24 or /64
  • voidgatectl disarm flushes drops

Control socket

04 / voidgatectl
/run/voidgate.sock
$ sudo voidgatectl status
state=idle armed=0 rx_pps=412 rx_bps=2800000 drops=0 iface=eth0

$ sudo voidgatectl drop 203.0.113.0/24
ok

$ sudo voidgatectl status
state=active armed=1 rx_pps=184200 rx_bps=980000000 drops=1 iface=eth0

$ sudo voidgatectl disarm
ok

One line in, text out: status, stats, drops, arm, disarm, drop <cidr>, undrop <cidr>, reload. Prometheus, if enabled, is 127.0.0.1:9105/metrics.