XDP and eBPF packet filtering
Arc’s kernel program (arc-xdp) attaches to a NIC at the XDP hook point and processes every inbound packet before the kernel TCP stack sees it. It runs entirely in the kernel with no context switch to userspace per packet.
What it checks
For each packet:- Parse Ethernet + optional VLAN headers
- Parse IPv4 or IPv6 to extract the source IP
- Check the whitelist — IP in whitelist →
XDP_PASSimmediately - Check the blacklist — IP in blacklist (not expired) →
XDP_DROP - For TCP packets:
- SYN flood scoring (exponential decay per IP)
- SYN proxy (syncookie SYN-ACK, validate returning ACK)
- RST validation (sequence number window check)
- ACK flood scoring
- For UDP packets: per-port PPS/BPS rate limiting
BPF maps
| Map | Type | Capacity | Description |
|---|---|---|---|
arc_whitelist | HASH | 65,536 | Always-pass IPs |
arc_blacklist | LRU_HASH | 1,000,000 | Blocked IPs with TTL |
arc_syn_state | PERCPU_HASH | 500,000 | Per-IP SYN scoring state |
arc_global_stats | PERCPU_ARRAY | 1 (per-CPU) | Global packet statistics |
arc_conntrack | LRU_HASH | 2,000,000 | TCP connection tracking |
arc_config | ARRAY | 1 | Runtime config flags (written by userspace) |
arc_events | RINGBUF | 4 MiB | Events to userspace |
arc_port_stats | PERCPU_ARRAY | 65,536 | UDP per-port statistics |
/sys/fs/bpf/arc/.
Feature flags
Enable or disable XDP features at runtime usingPOST /v1/xdp/config on the control plane:
| Flag | Effect |
|---|---|
CFG_F_ENABLE_SYN_FLOOD | Per-IP SYN scoring; drop when threshold exceeded |
CFG_F_ENABLE_SYN_PROXY | Respond with syncookie SYN-ACK; validate returning ACKs |
CFG_F_ENABLE_RST_VALIDATE | Drop RSTs outside the tracked sequence window |
CFG_F_ENABLE_ACK_FLOOD | Per-IP ACK-only scoring and drop |
CFG_F_GLOBAL_DEFENSE_MODE | Tighten all thresholds (auto-activated on attack detection) |
CFG_F_ENABLE_UDP_STATS | Count UDP packets per destination port |
CFG_F_ENABLE_UDP_RATE_LIMIT | Drop UDP packets exceeding per-port PPS/BPS limits |
CFG_F_ENABLE_CIDR_LOOKUP | Enable CIDR prefix matching (additional map lookups) |
CFG_F_DROP_IPV4_FRAGS | Drop IPv4 fragments to prevent L4 header bypass |
Dynamic threshold calculation
The userspace manager runs a background task every 100ms that uses a Welford online algorithm to compute a running mean and standard deviation of the observed SYN rate. The dynamic threshold is:Managing blacklist and whitelist
Use the Gateway control plane API reference to manage XDP lists at runtime:Kubernetes note
XDP requiresNET_ADMIN and SYS_ADMIN capabilities plus privileged: true. This is incompatible with most managed Kubernetes platforms. The deployment manifest includes the required YAML as comments only.
Rate limiting
Arc has two independent rate limiting systems.Per-route rate limiting (GCRA)
The simplest rate limiting is configured directly on a route:HashMap. No cross-thread coordination.
Two-tier global rate limiting
For cluster-wide enforcement across multiple Arc nodes, theGlobalRateLimiter provides a two-tier design:
L1 (worker-local): Each worker keeps a HashMap of per-key token buckets. No locks on the hot path. try_acquire is called inline on every request.
L2 (Redis backend): A dedicated backend thread runs Lua scripts against Redis to enforce cluster-wide limits. Workers request token refills asynchronously.
Circuit breaker: When the Redis backend is unreachable, the circuit breaker opens and workers fall back to L1-only operation automatically. Default circuit open duration: 500ms.
The hot path on a worker:
- Drain up to 8 pending refill responses from the backend
- Look up or create a per-key
Entryin locall1HashMap - If backend is healthy and global tokens remain → consume one global token
- If backend healthy but tokens are exhausted → reject with 429
- If backend is down → use local L1 token bucket
- Schedule a refill if tokens are below the low-watermark
L7 protection
Arc applies three L7-level guards on every connection before rate limiting.SlowlorisGuard
Detects Slowloris attacks — connections that send HTTP headers extremely slowly to exhaust server connection slots.| Check | Trigger | Action |
|---|---|---|
| Max incomplete per IP | On new connection | Drop if over per-IP limit |
| Header timeout | Per-byte received | Drop if elapsed > headers_timeout_ns |
| Min receive rate | Per-byte received | Drop if bytes/elapsed < min_recv_rate_bps |
TlsFloodGuard
Limits TLS handshake rate per IP per second. State is stored in a fixed-size array ofAtomicU64 cells — each cell packs (second << 32) | count. When the count for the current second exceeds max_handshakes_per_ip_per_sec, the connection is rejected.
H2StreamFloodGuard
Per-connection HTTP/2 protection against stream flood and RST flood attacks.| Event | Check | Response |
|---|---|---|
HEADERS frame (new stream) | open_streams > max_concurrent_streams | Send GOAWAY |
HEADERS frame (new stream) | streams_created_in_window > max_streams_per_sec | Send GOAWAY |
RST_STREAM received | rsts_in_window > max_rst_per_sec | Send GOAWAY |
Request timeouts
Arc tracks per-request deadlines at nanosecond precision without extra timer allocations. Four timeout boundaries are enforced for every request:| Timeout | When applied |
|---|---|
connect | TCP connection establishment to upstream |
response_header | Time to first byte from upstream after request sent |
per_try | Maximum duration of a single retry attempt |
total | Maximum end-to-end request duration |
timeouts:
Troubleshooting
XDP is not loading (XDP attach failed)
XDP is not loading (XDP attach failed)
XDP requires a network driver that supports XDP (most modern drivers do). Verify with
ip link show <iface>. The fallback chain tries native → generic → skb. If all fail, the gateway starts without XDP and logs a warning. Confirm via GET /v1/xdp/status.IPs are being blocked unexpectedly
IPs are being blocked unexpectedly
Check the XDP blacklist:
curl http://localhost:22100/v1/xdp/blacklist. Dynamic thresholds may have auto-blacklisted a legitimate source during a traffic spike. Remove with DELETE /v1/xdp/blacklist and consider increasing sigma_multiplier via POST /v1/xdp/config (e.g. {"sigma_multiplier": 5.0}) to widen the threshold before traffic triggers auto-blacklisting. Check arc_xdp_blacklisted_total in /metrics.Rate limiting is not working
Rate limiting is not working
Ensure
control_plane.enabled: true and observability.metrics_bind is up. Per-route rate limiting uses GCRA locally — no Redis needed. Cluster-wide rate limiting requires a reachable Redis and the redis feature compiled in. Check arc_ratelimit_rejected_total in /metrics to confirm the limiter is active.SlowlorisGuard is blocking legitimate slow clients
SlowlorisGuard is blocking legitimate slow clients
The guard triggers on incomplete HTTP headers that linger past
header_timeout. Increase the threshold in the security config or disable it for specific listener addresses by not enabling L7 protection on internal-only listeners.Two-tier rate limit circuit breaker is open
Two-tier rate limit circuit breaker is open
arc_ratelimit_circuit_open will be 1 in metrics. This means the Redis backend is unreachable and Arc has fallen back to L1 (per-worker) rate limiting. Check Redis connectivity and inspect the circuit breaker open_until_ns via the control plane.
