Bali, Indonesia
Insights

Bypassing the Kernel Network Stack: Building Line-Rate DDoS Mitigation with eBPF and XDP

October 12, 2026
As web hosting infrastructure scales, standard netfilter and iptables rules often buckle under high-volume volumetric DDoS attacks. When millions of packets per second hit a server, the traditional Linux kernel network stack spends excessive CPU cycles allocating SKBs (socket buffers), handling soft IRQs, and traversing traversal tables before dropping malicious payloads. By leveraging eBPF (Extended Berkeley Packet Filter) and XDP (Express Data Path), systems engineers can execute filtering logic directly inside the network interface card driver, achieving line-rate packet drops long before the operating system kernel feels the pressure.
Traditional mitigation relies on iptables or nftables modules operating within the netfilter framework. While powerful, netfilter executes after the NIC driver has already performed DMA (Direct Memory Access) transfers and allocated kernel memory structures for every single incoming packet. During a 10 million packet-per-second UDP amplification attack, this memory churn exhausts CPU cache lines and starves legitimate TCP connections of compute resources. XDP solves this by attaching custom bytecode programs directly to the NIC driver's receive hook. When a packet arrives, the XDP hook inspects the raw frame data in the ring buffer and returns an action code: XDP_DROP, XDP_PASS, XDP_TX, or XDP_REDIRECT.
Packet Processing StageFrameworkCPU OverheadMemory AllocationMitigation Limit
Driver Ring BufferXDP / eBPFMinimalNone (Zero-Copy)Line-Rate (100Gbps+)
Netfilter Hookiptables / nftablesModerateHigh (SKB Created)Moderate (2M-5M pps)
Socket LayerUser-Space (epoll)ExtremeFull Application StackLow (Application Crash)
To intercept and drop malicious traffic at the driver level, we write a compact C program compiled into BPF bytecode using Clang and LLVM. Below is a minimal example that drops incoming UDP traffic targeted at a specific reflection port, such as Memcached or NTP amplification vectors.
#include <linux/bpf.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <linux/udp.h>
#include <bpf/bpf_helpers.h>

SEC("xdp")
int xdp_drop_reflection(struct xdp_md *ctx) {
    void *data = (void *)(long)ctx->data;
    void *data_end = (void *)(long)ctx->data_end;

    struct ethhdr *eth = data;
    if ((void *)(eth + 1) > data_end)
        return XDP_PASS;

    if (eth->h_proto != __constant_htons(ETH_P_IP))
        return XDP_PASS;

    struct iphdr *iph = data + sizeof(*eth);
    if ((void *)(iph + 1) > data_end)
        return XDP_PASS;

    if (iph->protocol == IPPROTO_UDP) {
        struct udphdr *udph = (void *)iph + (iph->ihl * 4);
        if ((void *)(udph + 1) > data_end)
            return XDP_PASS;

        // Drop common amplification vector ports (e.g., NTP 123)
        if (udph->dest == __constant_htons(123)) {
            return XDP_DROP;
        }
    }

    return XDP_PASS;
}

char _license[] SEC("license") = "GPL";
Once the C source file is written, you must compile it into an ELF object file using Clang. Ensure your kernel headers match your running environment to prevent verification failures.
# Compile the XDP C code into BPF bytecode
clang -O2 -target bpf -c xdp_drop.c -o xdp_drop.o

# Verify the bytecode loads correctly into the kernel verifier
sudo ip link set dev eth0 xdp obj xdp_drop.o sec xdp
To check if the XDP program is successfully attached and running in native driver mode, inspect the interface status using the ip tool:
ip link show dev eth0
You should see output confirming that an XDP program is attached with driver (native) or offloaded support, ensuring that packet inspection happens instantaneously upon hardware reception.
Static port-dropping rules are insufficient against modern adaptive botnets. Production infrastructure requires dynamic blocklists stored inside eBPF maps, which can be updated by user-space daemons without restarting the packet filter.
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>

struct {
    __uint(type, BPF_MAP_TYPE_HASH);
    __uint(max_entries, 65536);
    __type(key, __u32); // Source IP address
    __type(value, __u64); // Drop counter
} blocked_ips SEC(".maps");

SEC("xdp")
int xdp_dynamic_blacklist(struct xdp_md *ctx) {
    // Packet parsing logic omitted for brevity
    __u32 src_ip = 0; // Extracted from IP header
    
    __u64 *drop_count = bpf_map_lookup_elem(&blocked_ips, &src_ip);
    if (drop_count) {
        __sync_fetch_and_add(drop_count, 1);
        return XDP_DROP;
    }

    return XDP_PASS;
}
While XDP and eBPF provide unmatched throughput for web hosting edge nodes, strict validation must be performed. If a NIC driver lacks native XDP support, the kernel falls back to generic XDP mode, which executes after SKB allocation and neutralizes performance gains. Always deploy XDP on supported enterprise network interface cards with dedicated driver support, and maintain out-of-band management access in case a faulty bytecode filter accidentally locks out legitimate administrative traffic.