DEV Community

Rahad Bhuiya
Rahad Bhuiya

Posted on

Building a Line-Rate Linux Kernel Firewall in Pure Rust with Aya and eBPF/XDP

Volumetric Distributed Denial of Service (DDoS) and transport-layer flooding attacks pose an existential challenge to modern cloud infrastructure, edge gateways, and container workloads.

Conventional Linux packet filtering utilities such as Netfilter (iptables and nftables) evaluate incoming frames only after the kernel has allocated a socket buffer (sk_buff) descriptor and scheduled softirq processing through the core TCP/IP stack. Under multi-gigabit line-rate flood conditions (millions of packets per second), the operating system spends the vast majority of its CPU cycles simply allocating and freeing sk_buff memory metadata, triggering kernel starvation and packet drop bottlenecks even before firewall rules are evaluated.

To address this fundamental OS-level bottleneck, I built xdp-guard: a production-grade, memory-safe in-kernel defense engine and per-IP rate limiter implemented strictly in Pure Rust using the Aya eBPF framework.


Why eBPF / XDP at the Driver Layer?

The eXpress Data Path (XDP) provides a programmable, bare-metal packet processing execution hook directly inside the Network Interface Card (NIC) driver subsystem before any memory descriptor allocation occurs.

Conventional Path:
[NIC RX] -> [IRQ / SoftIRQ] -> [Allocate sk_buff] -> [Netfilter/iptables] -> [Socket / App]
                                      ^
                           Severe CPU Allocation Overhead

xdp-guard Path:
[NIC RX] -> [eBPF / XDP Driver Hook] ---> (Match Blocklist) ---> [ XDP_DROP ] (Near-Zero CPU)
                             |
                      (Match Allowlist)
                             |
                             v
                   [ Pass to OS Stack ]
Enter fullscreen mode Exit fullscreen mode

When hostile or unapproved packets are identified, returning XDP_DROP causes the network driver to immediately recycle the packet ring buffer without allocating a single byte of kernel socket memory.


Architectural Highlights

1. Zero C Dependencies (Pure Rust)

Traditional eBPF tools rely heavily on C toolchains (clang, llvm, libbpf). xdp-guard is built entirely in Rust from the user-space management CLI down to the no_std kernel eBPF bytecode using Aya.

2. Strict 64-Bit Memory Boundary Alignments

To ensure safe zero-copy communication across the kernel-userspace boundary, shared structures use explicit padding fields to guarantee 8-byte alignment:

#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct RuleValue {
    pub action: u32,       // 0 = Pass, 1 = Drop
    pub _pad: u32,         // Explicit 4-byte padding for 8-byte boundary
    pub created_at: u64,   // Monotonic timestamp
    pub ttl_secs: u64,     // Time-to-live expiration
}
Enter fullscreen mode Exit fullscreen mode

The presence of _pad: u32 is critical: it prevents architecture-specific compiler padding discrepancies and satisfies the Linux Kernel BPF Verifier.

3. Dynamic BPF Virtual Filesystem (bpffs) Synchronization

Firewall rules (blocklist and allowlist) are dynamically synchronized using pinned BPF hash maps located in /sys/fs/bpf/xdp_guard. Rules can be inserted or deleted instantaneously from the CLI without tearing down network interfaces or interrupting traffic flows.

4. In-Kernel Token-Bucket Rate Limiter

In addition to static blocklists, xdp-guard embeds a stateful token-bucket rate limiter directly in kernel space. Monotonic arrival intervals ($\Delta t = t_{now} - t_{last}$) are computed using nanosecond bpf_ktime_get_ns() clocks, discarding over-limit burst floods at line-rate.


Empirical Verification: Real Linux Kernel Telemetry

To validate xdp-guard under realistic operating conditions, we set up an automated Linux network namespace testbed over virtual ethernet (veth) pairs (xdp_client at 10.10.0.2 and xdp_server at 10.10.0.1).

Telemetry streamed directly from the kernel STATS_MAP array demonstrated instantaneous 100.00% packet mitigation upon inserting an attacker IP into the kernel blocklist, followed by zero-loss recovery:

TIMESTAMP            PASSED (PKTS)   DROPPED (PKTS)  DROP RATIO     
-----------------------------------------------------------------
09:56:56 | Pass: 1        (30.2 KB) | Drop: 0        (0 B)   | Ratio: 0.00%
[!] Inserting IP 10.10.0.2 into BLOCKLIST_MAP... [SUCCESS]
09:56:57 | Pass: 0        (30.2 KB) | Drop: 1        (98 B)  | Ratio: 100.00%
09:56:58 | Pass: 0        (30.2 KB) | Drop: 1        (196 B) | Ratio: 100.00%
09:56:59 | Pass: 0        (30.2 KB) | Drop: 1        (294 B) | Ratio: 100.00%
...
09:57:55 | Pass: 0        (30.2 KB) | Drop: 1        (5.7 KB)| Ratio: 100.00%
[!] Removing IP 10.10.0.2 from BLOCKLIST_MAP... [SUCCESS]
09:57:56 | Pass: 1        (30.3 KB) | Drop: 0        (5.7 KB)| Ratio: 0.00%
09:57:57 | Pass: 1        (30.4 KB) | Drop: 0        (5.7 KB)| Ratio: 0.00%
Enter fullscreen mode Exit fullscreen mode

Every single drop occurred directly within the NIC driver hook, requiring zero kernel TCP/IP stack overhead.


Source Code & Research Paper

The complete project, documentation, and formal research paper are fully open-source:

If you are working on Linux kernel systems, eBPF, or Rust networking, I would love to hear your thoughts, feedback, or suggestions!

Top comments (0)