XDP Linux: Xây dựng bộ lọc gói tin siêu tốc tại tầng Driver để chặn DDoS

Network tutorial - IT technology blog
Network tutorial - IT technology blog

Bối cảnh: Khi iptables không còn đủ nhanh

Mình quản lý network cho office 50 người và một datacenter nhỏ — và mình đã từng phải chứng kiến cảnh server WordPress tê liệt hoàn toàn vì một đợt UDP flood khoảng 800Mbps. CPU lên 100%, iptables vẫn đang cố parse từng packet qua kernel stack trong khi service chết dần. Sau sự cố đó mình mới bắt đầu tìm hiểu XDP và thấy đây là thứ mình ước đã biết sớm hơn vài năm.

Vấn đề cốt lõi: iptables hay nftables đều xử lý packet sau khi chúng đã đi qua toàn bộ kernel network stack — socket buffer, netfilter hooks, routing table. Traffic bình thường thì không ai để ý. Nhưng khi flood đến hàng triệu packet/giây thì mỗi bước đó đều ngốn CPU như không có ngày mai.

XDP (eXpress Data Path) giải quyết bằng cách xử lý gói tin ngay tại tầng driver của NIC — trước khi packet kịp chui vào kernel stack. Packet bị drop trong vài nanosecond. CPU tiêu thụ giảm 80-90% so với iptables trong cùng điều kiện traffic.

XDP so với các giải pháp khác

  • iptables/nftables: Xử lý sau kernel stack, dễ config nhưng chậm khi traffic cao
  • XDP: Xử lý tại driver, nhanh hơn 10-100x, tích hợp liền mạch với kernel stack hiện tại
  • DPDK: Nhanh như XDP nhưng bypass hoàn toàn kernel — cần driver riêng, khó tích hợp với hệ thống đang chạy

Trong thực tế, XDP là điểm ngọt giữa hai thái cực: đủ nhanh để xử lý DDoS quy mô lớn, nhưng không cần rebuild cả network stack như DPDK.

XDP chạy trên nền eBPF — cùng công nghệ mà Cilium, Cloudflare firewall, và bpftrace đang dùng. Mỗi XDP program trả về một trong bốn action: XDP_DROP (xóa packet ngay tại chỗ), XDP_PASS (cho đi qua kernel stack bình thường), XDP_TX (gửi lại qua cùng interface), hoặc XDP_REDIRECT (chuyển sang interface khác). Với bài toán DDoS, XDP_DROP là action dùng nhiều nhất.

Cài đặt môi trường

Kernel 4.8+ đã hỗ trợ XDP cơ bản, nhưng mình khuyến nghị dùng kernel 5.10+ để có đầy đủ tính năng. Kiểm tra trước:

uname -r
# Ví dụ: 5.15.0-91-generic

Cài đặt dependencies trên Ubuntu/Debian

sudo apt update
sudo apt install -y clang llvm libelf-dev libbpf-dev \
    linux-headers-$(uname -r) iproute2 bpftool gcc make

Cài đặt trên RHEL/AlmaLinux/Rocky

sudo dnf install -y clang llvm elfutils-libelf-devel \
    libbpf-devel kernel-devel bpftool iproute

Kiểm tra libbpf đã sẵn sàng:

pkg-config --libs libbpf
# Output: -lbpf

Bước này thường bị bỏ qua nhưng quan trọng: kiểm tra NIC có hỗ trợ XDP native mode không. Native mode nhanh hơn generic mode đáng kể — thường gấp 3-5 lần trên cùng phần cứng:

ethtool -i eth0 | grep driver
# driver: ixgbe  → native XDP OK
# driver: i40e   → native XDP OK
# driver: virtio_net → chỉ dùng generic mode (máy ảo)

Cấu hình chi tiết: Viết XDP Program chặn DDoS

Tổ chức project trong một thư mục riêng để dễ quản lý:

mkdir ~/xdp-filter && cd ~/xdp-filter

Viết XDP program bằng C

Tạo file xdp_block.c. Đây là eBPF program viết bằng restricted C — một subset của C chạy trực tiếp trên eBPF VM bên trong kernel, qua bộ verifier kiểm tra an toàn trước khi chạy:

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

// BPF Map: lưu danh sách IP bị block (key = IP uint32, value = 1)
struct {
    __uint(type, BPF_MAP_TYPE_HASH);
    __type(key, __u32);
    __type(value, __u8);
    __uint(max_entries, 65536);
    __uint(pinning, LIBBPF_PIN_BY_NAME);
} blocked_ips SEC(".maps");

// BPF Map: đếm số packet đã drop (per-CPU để tránh lock)
struct {
    __uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
    __type(key, __u32);
    __type(value, __u64);
    __uint(max_entries, 1);
    __uint(pinning, LIBBPF_PIN_BY_NAME);
} drop_counter SEC(".maps");

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

    // Parse Ethernet header — bounds check bắt buộc với eBPF verifier
    struct ethhdr *eth = data;
    if ((void *)(eth + 1) > data_end)
        return XDP_PASS;

    // Chỉ xử lý IPv4
    if (eth->h_proto != __constant_htons(ETH_P_IP))
        return XDP_PASS;

    // Parse IP header
    struct iphdr *ip = (void *)(eth + 1);
    if ((void *)(ip + 1) > data_end)
        return XDP_PASS;

    // Tra cứu IP nguồn trong map
    __u8 *blocked = bpf_map_lookup_elem(&blocked_ips, &ip->saddr);
    if (blocked && *blocked == 1) {
        __u32 key = 0;
        __u64 *cnt = bpf_map_lookup_elem(&drop_counter, &key);
        if (cnt) (*cnt)++;
        return XDP_DROP;
    }

    return XDP_PASS;
}

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

Compile XDP program

clang -O2 -g -Wall -target bpf \
    -I/usr/include/$(uname -m)-linux-gnu \
    -c xdp_block.c -o xdp_block.o

# Kiểm tra output
file xdp_block.o
# xdp_block.o: ELF 64-bit LSB relocatable, eBPF, version 1 (SYSV)

Load XDP vào kernel và attach vào interface

# Mount BPF filesystem nếu chưa có
sudo mount -t bpf bpf /sys/fs/bpf 2>/dev/null || true
sudo mkdir -p /sys/fs/bpf/xdp-filter

# Xem tên interface thực tế trên máy
ip link show

# Load XDP native mode (NIC driver hỗ trợ — nhanh nhất)
sudo ip link set eth0 xdpdrv obj xdp_block.o sec xdp \
    pinpath /sys/fs/bpf/xdp-filter

# Nếu NIC không hỗ trợ native, dùng generic mode
sudo ip link set eth0 xdpgeneric obj xdp_block.o sec xdp \
    pinpath /sys/fs/bpf/xdp-filter

Quản lý danh sách IP bị chặn — không cần restart

Cái hay của BPF Map: thêm hoặc xóa IP khỏi block list hoàn toàn realtime, không cần reload hay restart program. XDP vẫn đang chạy trong kernel — mình chỉ update dữ liệu bên dưới nó:

#!/usr/bin/env python3
# manage_block.py
import socket
import subprocess
import sys

MAP_PATH = "/sys/fs/bpf/xdp-filter/blocked_ips"

def ip_to_hex(ip):
    packed = socket.inet_aton(ip)
    return ' '.join(f'{b:02x}' for b in packed)

def block_ip(ip):
    key = ip_to_hex(ip)
    subprocess.run(
        f"bpftool map update pinned {MAP_PATH} key hex {key} value hex 01",
        shell=True, check=True
    )
    print(f"Blocked: {ip}")

def unblock_ip(ip):
    key = ip_to_hex(ip)
    subprocess.run(
        f"bpftool map delete pinned {MAP_PATH} key hex {key}",
        shell=True, check=True
    )
    print(f"Unblocked: {ip}")

if __name__ == "__main__":
    if len(sys.argv) != 3:
        print("Usage: sudo python3 manage_block.py [block|unblock] <IP>")
        sys.exit(1)
    action, ip = sys.argv[1], sys.argv[2]
    if action == "block":
        block_ip(ip)
    elif action == "unblock":
        unblock_ip(ip)
# Block IP đang tấn công
sudo python3 manage_block.py block 203.0.113.50

# Unblock
sudo python3 manage_block.py unblock 203.0.113.50

# Xem toàn bộ danh sách đang block
sudo bpftool map dump pinned /sys/fs/bpf/xdp-filter/blocked_ips

Kiểm tra & Monitoring

Xác nhận XDP đã attach thành công

ip link show eth0
# Nếu thành công sẽ thấy: xdp/id:42 hoặc xdpgeneric/id:42

# Xem chi tiết program đang chạy
sudo bpftool prog list | grep xdp
sudo bpftool prog show id 42

Theo dõi số packet bị drop

# Xem drop counter từ BPF Map
sudo bpftool map dump pinned /sys/fs/bpf/xdp-filter/drop_counter

# Xem XDP stats từ driver (nếu NIC hỗ trợ)
ethtool -S eth0 | grep -i xdp

# Theo dõi realtime mỗi 1 giây
watch -n 1 'sudo bpftool map dump pinned /sys/fs/bpf/xdp-filter/drop_counter'

Test thực tế với hping3

# Từ máy test — gửi flood SYN
hping3 -S --flood -V -p 80 <server-ip>

# Trên server — quan sát CPU và packet stats
watch -n 1 'cat /proc/net/dev | grep eth0'
# rx packets tăng nhanh nhưng CPU vẫn thấp → XDP đang drop tại driver

Gỡ XDP khi không cần

# Detach XDP
sudo ip link set eth0 xdpdrv off
# hoặc
sudo ip link set eth0 xdpgeneric off

# Dọn BPF maps khỏi pinned filesystem
sudo rm -rf /sys/fs/bpf/xdp-filter

Kết quả thực tế từ production

Sau khi deploy XDP trên server gateway của datacenter, số liệu nói lên tất cả: cùng mức flood ~800Mbps, iptables đẩy CPU lên 85-90% và latency tăng vọt. Chuyển sang XDP native mode trên card Intel i40e, CPU xuống còn 15-20%. Service phía trong không bị ảnh hưởng gì — XDP đã drop packet trước khi chúng kịp chạm vào kernel stack.

65536 entries trong blocked_ips thực tế là quá đủ — ngay cả khi đang bị tấn công, mình chưa bao giờ cần block quá 3.000 IP cùng lúc. Bước tiếp theo nên làm là tích hợp monitoring tự động: dùng bpftrace để detect pattern bất thường, rồi gọi script Python ở trên để thêm IP vào block list mà không cần thao tác thủ công. Tất cả xảy ra trong vài millisecond — đủ nhanh để đón đầu hầu hết các đợt tấn công tự động.

Share: