eBPF XDP lets you drop Distributed Denial of Service (DDoS) attack packets at the earliest possible moment, right on the network interface card (NIC) before they even hit the kernel’s network stack.

Let’s see XDP in action. Imagine a simple XDP program that drops all packets destined for a specific IP address.

#include <linux/bpf.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <bpf/bpf_helpers.h>

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

    struct ethhdr *eth = data;
    if ((void *)(eth + 1) > data_end) {
        return XDP_PASS; // Not enough space for Ethernet header
    }

    if (eth->h_proto == bpf_htons(ETH_P_IP)) { // Check if it's an IP packet
        struct iphdr *ip = data + sizeof(struct ethhdr);
        if ((void *)(ip + 1) > data_end) {
            return XDP_PASS; // Not enough space for IP header
        }

        // Target IP address to drop (e.g., 192.168.1.100)
        __be32 target_ip = bpf_htonl(0xc0a80164); // 192.168.1.100 in hex

        if (ip->daddr == target_ip) {
            bpf_printk("Dropping packet to %pI4\n", ip->daddr);
            return XDP_DROP; // Drop the packet
        }
    }

    return XDP_PASS; // Let other packets pass
}

char _license[] SEC("license") = "GPL";

To load this program, you’d typically use the ip command:

# Compile the C code to BPF bytecode (e.g., using clang)
clang -target bpf -O2 -c xdp_drop_specific_ip.c -o xdp_drop_specific_ip.o

# Load the program onto the eth0 interface in native XDP mode
ip link set dev eth0 xdp obj xdp_drop_specific_ip.o sec xdp

Now, any IP packet arriving at eth0 and destined for 192.168.1.100 will be dropped by the NIC’s XDP hook. The xdp_md structure provides context about the packet, like its data start and end pointers. We navigate through the Ethernet and IP headers to inspect the destination IP address. If it matches our target, XDP_DROP is returned, signaling the NIC to discard the packet. XDP_PASS allows the packet to continue to the kernel.

The core problem XDP XDP solves is the sheer volume of traffic that can overwhelm a server’s CPU, even before the kernel’s network stack can identify it as malicious. Traditional firewalls or iptables rules operate in software, after the packet has already consumed CPU cycles and memory. XDP, running directly in the NIC’s driver or firmware, intercepts packets at the hardware level. This is crucial for DDoS mitigation because it stops unwanted traffic at the perimeter, preventing it from impacting legitimate traffic or consuming valuable server resources.

Internally, XDP hooks into the network driver’s receive path. When a packet arrives, the driver can be configured to execute an eBPF program. This program, written in a restricted C subset and compiled to BPF bytecode, has access to packet metadata and can perform actions like dropping, redirecting, or passing the packet. The key is that these actions happen before the kernel’s full network stack processing. The xdp_md context structure is your window into the packet’s contents. You can access headers, check fields, and make decisions. The return codes (XDP_DROP, XDP_PASS, XDP_TX, XDP_REDIRECT) dictate the packet’s fate.

The levers you control are primarily the logic within your eBPF program and the mode in which XDP is loaded. The modes are: xdpgeneric (software fallback, slower), xdpdrv (driver mode, fastest, requires NIC support), and xdpoffload (offloaded to NIC hardware, fastest, requires specific NIC capabilities). For maximum performance against DDoS, xdpdrv or xdpoffload are preferred. Your eBPF program can inspect any part of the packet up to the maximum packet size, allowing for complex filtering based on source/destination IPs, ports, protocols, TCP flags, and even custom packet headers.

Many people assume that XDP_PASS is always the default and that you must explicitly XDP_DROP everything else. However, the default behavior of the network driver after the XDP hook, if your program doesn’t return a specific action, is often to pass the packet along. This means you only need to explicitly XDP_DROP or XDP_PASS packets that require special handling; otherwise, the packet will naturally proceed through the driver’s normal receive path. This can simplify some XDP programs where the intent is to only intercept and modify a small subset of traffic.

The next hurdle after successfully dropping DDoS packets is often managing legitimate traffic that might be inadvertently dropped by overly aggressive XDP rules.

Want structured learning?

Take the full Ebpf course →