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.

