03 / operations
One line in. Text out.
voidgatectl talks to /run/voidgate.sock. Prometheus, if metrics_port > 0, binds 127.0.0.1:9105/metrics.
voidgatectl
| Command | Effect |
|---|---|
| status | state, armed, rx_pps, rx_bps, drop count, iface |
| stats | XDP counters: rx_pkts, passed, dropped, non_ip, map_full, parse_err |
| drops | installed prefixes with reason and age |
| arm | cfg.armed = 1; re-baseline remote rate snapshots |
| disarm | flush drop_*, cfg.armed = 0, back to IDLE |
| drop <cidr> | manual insert; forces ACTIVE. Reason 1. Not auto-expired. |
| drop <cidr> ttl=<sec> | timed insert for OpenResty bans; forces ACTIVE. Reason 4. Lifted after ttl (1 s to one year); a re-ban only extends it. Never upgrades a manual drop and never counts toward aggregate_k. |
| undrop <cidr> | remove one prefix from the drop tree |
| reload | re-read the config file; replace allow/local maps and ports |
Drop reasons: 1 manual, 2 policy, 3 aggregate.
Config
One key = value per line; # starts a comment. Every key is optional, and the defaults below are also what ships in configs/voidgate.conf. The commented file explains each one in more depth.
| Key | Default | What it does | reload |
|---|---|---|---|
| Interface and files | |||
| interface | eth0 |
RX NIC to attach XDP to. -i overrides. Never lo. |
restart |
| xdp_mode | auto |
auto tries native driver XDP, then SKB. native / skb pin one. |
restart |
| log_file | /var/log/voidgate.log |
Append-only log, with -d only; under the unit the log is the journal. Parent directory must exist. |
restart |
| pid_file | /run/voidgate.pid |
Locked for the daemon's life; -s stop reads it. |
restart |
| Waking up | |||
| wake_pps | 2000 |
Aggregate NIC packets/s that arms the gate (IDLE → ACTIVE). | live |
| wake_mbps | 25 |
Aggregate NIC Mbit/s that arms the gate. Either wake_* arms. |
live |
| idle_poll_ms | 1000 |
Userspace tick in ms. <= 0 becomes 1000. |
live |
| clear_seconds | 30 |
Quiet seconds, with an empty drop tree, before ACTIVE → IDLE. | live |
| Dropping | |||
| threshold_pps | 500 |
Per-remote packets/s that earns a /32 or /128 drop. Keep below wake_pps. |
live |
| threshold_mbps | 5 |
Per-remote Mbit/s, same effect. Keep below wake_mbps. |
live |
| ban_time | 1900 |
Seconds a policy or aggregate drop lives. Manual drops never expire. | live |
| aggregate_k | 8 |
Dropped hosts in one /24 or /64 before the whole prefix is dropped. |
live |
| Never drop | |||
| local_networks | (auto) |
This VM's CIDRs. Empty takes the NIC's addresses as /32 and /128. Don't list the on-link LAN prefix. |
live |
| allow_networks | metadata, loopback, link-local |
Never-drop CIDRs. A non-empty list replaces the built-in one; it does not merge. Repeated lines add up. | live |
| allow_ports | 22 |
TCP ports whitelisted on the local side. CSV, at most 8. | live |
| Capacity and metrics | |||
| remote_map_size | 262144 |
Cap on tracked attack sources (LRU, not preallocated). | restart |
| drop_map_size | 65536 |
Cap on drop-tree prefixes (not preallocated). | restart |
| metrics_port | 9105 |
Prometheus on 127.0.0.1. 0 disables it. |
restart |
live: voidgatectl reload applies it. restart: fixed when the daemon starts. The built-in allow list is 169.254.169.254/32, 127.0.0.0/8, ::1/128, fe80::/10, ff02::/16.
Prometheus
When metrics_port is positive, the daemon listens on localhost and serves GET /metrics:
# TYPE voidgate_armed gauge
voidgate_armed 0
# TYPE voidgate_rx_packets_total counter
voidgate_rx_packets_total 0
# TYPE voidgate_rx_bytes_total counter
voidgate_rx_bytes_total 0
# TYPE voidgate_dropped_packets_total counter
voidgate_dropped_packets_total 0
# TYPE voidgate_map_full_total counter
voidgate_map_full_total 0
# TYPE voidgate_drop_prefixes gauge
voidgate_drop_prefixes 0
# TYPE voidgate_rx_pps gauge
voidgate_rx_pps 0
OpenResty client
voidgate is not a WAF. Let OpenResty, or a gateway built on it such as Kong or APISIX, decide who is abusive and push that address down to XDP with a timed drop. lua/resty/voidgate.lua talks to the socket over a non-blocking cosocket. Install it with sudo make install-lua (OpenResty's site lualib, or set LUA_DIR), and add the worker user to the group voidgate, which the shipped unit gives the socket to with -g voidgate, so nginx can reach it: commonly nobody for OpenResty, kong for Kong, and nobody or apisix (Docker image) for APISIX.
lua_shared_dict voidgate_ban 1m;
location / {
limit_req zone=api burst=20 nodelay;
limit_req_status 429;
log_by_lua_block {
if ngx.status == 429 then
require("resty.voidgate").ban(ngx.var.remote_addr, 600, -- 10m
{ dict = "voidgate_ban" })
end
}
}
ban(ip, ttl, opt) works from every phase but init_by_lua*, log included, and sends one ban per address per window. ban_now(ip, ttl) sends at once and returns the daemon's answer, in rewrite, access, content or timer context. Every drop from Lua needs a ttl: a permanent drop is voidgatectl drop's job. The other methods (status(), stats(), drops(), drop(cidr, ttl), undrop(), arm(), disarm(), reload()) run in rewrite, access, content or timer context and return nil, err on failure. stats() returns 64-bit counters as decimal strings.
ACTIVE policy, short
Each tick, remotes over threshold become /32 or /128. Dense clusters become /24 or /64. Userspace will not install a prefix that covers the VM itself or the allow list. On disarm, remote_* stays; the next arm re-baselines snapshots so leftover counters are not a phantom flood.