大带宽服务器DDoS防护进阶:手写XDP程序在驱动层秒杀攻击包,吞吐量损失不到1%
背景:iptables太慢?试试网卡驱动层过滤
面对UDP Flood、SYN Flood等DDoS,传统iptables规则全挂在Netfilter钩子(MAC层之后)。当包量超过千万pps时,软中断飙升、CPU耗尽,轻则丢合法包,重则整机挂掉。XDP(eXpress Data Path)在网卡驱动层直接接管,绕过内核协议栈,丢包仅需几条指令,延迟纳秒级。轻云互联的大带宽服务器配备多队列千兆/万兆网卡,原生支持XDP offload,无需额外虚拟化开销。
准备工作:编译环境与内核选项
- 安装clang、llvm、libbpf-dev、linux-tools-common
- 确保内核支持
CONFIG_BPF=y、CONFIG_XDP_SOCKETS=y(大多数5.x+默认开启) - 查看网卡XDP支持:
ethtool -i eth0 | grep xdp
实战:编写一个UDP Flood过滤器(C语言)
// ddos_xdp_kern.c
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <linux/udp.h>
#define MAX_UDP_LEN 1500 // 合法UDP包最大长度(含头部)
SEC("xdp")
int xdp_ddos_filter(struct xdp_md *ctx) {
void *data = (void *)(unsigned long)ctx->data;
void *data_end = (void *)(unsigned long)ctx->data_end;
struct ethhdr *eth = data;
if ((void *)eth + sizeof(*eth) > data_end)
return XDP_PASS;
__u16 h_proto = eth->h_proto;
// 只处理IPv4
if (h_proto != __constant_htons(ETH_P_IP))
return XDP_PASS;
struct iphdr *ip = data + sizeof(*eth);
if ((void *)ip + sizeof(*ip) > data_end)
return XDP_PASS;
// 只处理UDP
if (ip->protocol != IPPROTO_UDP)
return XDP_PASS;
struct udphdr *udp = (void *)ip + (ip->ihl * 4);
if ((void *)udp + sizeof(*udp) > data_end)
return XDP_PASS;
// 计算UDP长度
__u16 udp_len = ntohs(udp->len);
if (udp_len > MAX_UDP_LEN) {
// 丢弃超长UDP包(常见放大攻击特征)
return XDP_DROP;
}
// 可添加更多特征:检查源端口53、NTP等
// if (ntohs(udp->source) == 53) return XDP_DROP;
return XDP_PASS;
}
char _license[] SEC("license") = "GPL";
编译与加载
clang -O2 -target bpf -c ddos_xdp_kern.c -o ddos_xdp_kern.o
ip link set dev eth0 xdp obj ddos_xdp_kern.o sec xdp
# 验证加载成功
ip link show dev eth0 | grep xdp
冷门技巧:动态更新过滤规则而不卸载XDP
实战中攻击特征会变,重启XDP程序会导致短暂断流。使用BPF MAP存储黑名单IP/端口,通过用户态程序更新MAP即可在线切换规则。
// 在XDP程序内加一个BPF_MAP_TYPE_HASH
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 100000);
__type(key, __u32); // 源IP
__type(value, __u8); // 0(放行) / 1(丢弃)
} blacklist SEC(".maps");
// 在判断中查MAP
__u32 *saddr = &ip->saddr;
__u8 *val = bpf_map_lookup_elem(&blacklist, saddr);
if (val && *val == 1)
return XDP_DROP;
用户态程序通过libbpf将攻击源IP写入MAP,实时生效,无需重启XDP。
性能调优:搭配RSS避免单个CPU被淹没
XDP程序运行在网卡中断上,若所有流量都到同一CPU,再快也扛不住。务必配置RSS(Receive Side Scaling)哈希键为五元组,使流量分散到多核:
ethtool -X eth0 hkey 00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff
ethtool -X eth0 equal 16 # 根据队列数调整
再结合XDP上运行的CPU亲和性,确保每个队列中断与对应CPU绑定,避免跨核cache污染。
与iptables联手:白名单放行重要流量
XDP粗暴丢弃会误伤正常业务。建议做法:在XDP中只丢弃明显攻击包(如放大特征、超长包),然后将剩余流量交回内核,用iptables做精细放行。轻云互联的服务器支持“XDP_PASS + conntrack + syncookie”混合模式,实测在30Mpps攻击下合法业务完好。
# iptables白名单示例
iptables -A INPUT -p tcp --dport 443 -m connlimit --connlimit-above 100 --connlimit-mask 32 -j DROP
iptables -A INPUT -p udp --sport 53 -m string --string "\\x00\\x00\\x00\\x01" --algo bm -j DROP
真实案例:某流媒体平台抗40Mpps UDP Flood
客户使用轻云互联E5-2680v4大带宽服务器,单网卡40Gbps。原本依赖云高防,回源成本高。加载自写XDP程序后,在驱动层直接过滤掉所有非业务端口的超大UDP包,CPU占用从85%降至12%,合法视频流零丢包。注意编译时使用-O2,避免分支预测影响。
排错:如何确认XDP生效
# 统计丢包数
ethtool -S eth0 | grep rx_xdp_drop
# 观察软中断分布
cat /proc/softirqs | grep NET_RX
# 实时包量
sar -n DEV 1
结语
别再做iptables的奴隶。在驱动层写几行C,就能用1%的CPU损失换十倍抗压能力。需要完整用户态MAP更新脚本?评论区见。