02 / install

Build it. Attach it. Leave it idle.

clang compiles the XDP object, bpftool emits the skeleton, gcc links the daemon. Tests use SKB / bpf_prog_test_run and do not need a real NIC.

Requirements

  • Linux 5.8+ with BTF (/sys/kernel/btf/vmlinux)
  • clang, llvm, libbpf, bpftool, libelf, zlib
  • CAP_BPF + CAP_NET_ADMIN (root is fine)

Ubuntu 24.04

shell
sudo apt install clang llvm libbpf-dev libelf-dev zlib1g-dev \
    linux-tools-generic make gcc libtest-base-perl
git clone https://github.com/seawallnetworks/voidgate.git
cd voidgate
make
make test

make test runs the unit tests in t/unit/, then t/bin/run: the real daemon on a veth pair in private namespaces, driven by the Perl Test::Base suite in t/ (needs sudo). If a BPF change compiles but the daemon cannot load the object, the verifier rejected it — fix the program, do not weaken the test.

Run

shell
sudo ./voidgate -c configs/voidgate.conf -i eth0
sudo ./voidgatectl status

Verify it works

The daemon starts in IDLE. status should show the interface you attached and a nonzero rx_pps once traffic flows. stats shows the raw XDP counters.

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

$ sudo ./voidgatectl stats
rx_pkts=18240 rx_bytes=12501440 passed=0 dropped=0 non_ip=0 map_full=0 parse_err=0
rx_pps=412 rx_bps=2800000 state=idle prefixes=0

In IDLE, passed stays at zero because the program does not parse packets. It only counts rx_* and returns XDP_PASS.

Attach mode

xdp_mode = auto tries native (XDP_FLAGS_DRV_MODE) then falls back to SKB. You can pin native or skb in the config. Tests do not require a vendor driver.

Run as a daemon

-d detaches into the background. The command waits for startup, then prints the daemon PID to stderr and exits 0. A startup failure exits nonzero. -s stop reads the pid file and stops the daemon, which detaches XDP on the way out.

shell
sudo ./voidgate -d -c configs/voidgate.conf
sudo ./voidgate -s stop
  • Logs go to stderr in the foreground, so under the systemd service to the journal (journalctl -u voidgate), each line at its own priority. With -d they go to log_file (default /var/log/voidgate.log), append-only: after detaching, check there for startup diagnostics, and restart after rotating the file.
  • Pid file is pid_file (default /run/voidgate.pid). The daemon holds a lock on it for its whole life, so a second voidgate exits with already running before it touches the NIC.
  • After a crash the kernel releases the lock, and the next start takes over the leftover socket and XDP program. -s stop refuses a stale pid file and never signals the pid inside it.
  • -v / -vv raise verbosity. Relative config, log and pid paths resolve against the launch directory.

systemd

make install installs into the Seawall stack's prefix, /usr/local/seawall: sbin/voidgate, bin/voidgatectl, the default config in share/voidgate/voidgate.conf, libexec/voidgate-mkconf, the unit in /etc/systemd/system, and the OpenResty client in the stack's OpenResty. PREFIX, CONFDIR, LUA_DIR and DESTDIR change the locations; the unit runs /usr/local/seawall/sbin/voidgate whatever PREFIX is.

shell
sudo make install
sudo systemctl enable --now voidgate

The unit runs the daemon in the foreground, without -d. Its config is /usr/local/seawall/etc/voidgate/voidgate.conf. On the first start voidgate-mkconf makes it from the default, with interface set to the one the default route goes out of, and never changes it after that: edit it there. make install, upgrades and make uninstall keep it.

voidgate.service
[Unit]
Description=voidgate XDP DDoS gate
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
# "+": as root, before the capabilities below are dropped. Makes the
# config from the default on the first start, for this machine's
# interface.
ExecStartPre=+/usr/local/seawall/libexec/voidgate-mkconf
ExecStart=/usr/local/seawall/sbin/voidgate -c /usr/local/seawall/etc/voidgate/voidgate.conf
Restart=on-failure
RestartSec=2
AmbientCapabilities=CAP_BPF CAP_NET_ADMIN CAP_PERFMON
CapabilityBoundingSet=CAP_BPF CAP_NET_ADMIN CAP_PERFMON CAP_DAC_OVERRIDE CAP_CHOWN
LimitMEMLOCK=infinity

[Install]
WantedBy=multi-user.target

Layout

tree
src/bpf/voidgate.bpf.c   XDP program
src/bpf/voidgate.h       shared map / packet structs
src/voidgate.c           daemon
src/voidgatectl.c        unix-socket client
lua/resty/voidgate.lua   OpenResty control client
src/policy.c             IDLE / ACTIVE policy
src/maps.c               libbpf attach + LPM helpers
configs/voidgate.conf    key=value
voidgate.service         systemd unit
voidgate-mkconf          makes the config on the first start

Generated, do not edit or commit: src/bpf/voidgate.skel.h, *.o, binaries.