当你的服务每秒需要处理数百万甚至数千万个数据包时,内核协议栈的开销就成了瓶颈——每个数据包都要穿越 Netfilter 钩子、路由表查找、连接跟踪,即使最终只是简单地转发。XDP(eXpress Data Path)通过在网卡驱动层插入 eBPF 程序,在数据包进入内核协议栈之前就做出决策,将处理延迟从微秒级降到纳秒级。
XDP 是 eBPF 技术最成功的应用之一——Facebook(Meta)的 Katran 负载均衡器、Cloudflare 的 DDoS 防护、Cilium 的 NodePort 加速,都建立在 XDP 之上。
一、XDP 的架构
1.1 XDP 在网络栈中的位置
XDP 的核心优势在于它的介入时机——在网卡驱动收到数据包后、内核协议栈处理之前。这意味着:
- 不经过
sk_buff分配(节省内存和 CPU) - 不经过 Netfilter 钩子(节省规则匹配开销)
- 不经过路由表查找(节省查表开销)
- 不经过连接跟踪(节省状态查找开销)
1.2 XDP 与传统网络处理的性能对比
| 处理方式 | 处理延迟 | 吞吐量 | 灵活性 |
|---|---|---|---|
| 传统内核协议栈 | ~10-50μs | ~1-5 MPPS | 高 |
| XDP(native 模式) | ~0.1-1μs | ~10-40 MPPS | 中 |
| DPDK | ~0.05-0.5μs | ~40-80 MPPS | 低 |
| AF_XDP | ~0.2-2μs | ~10-30 MPPS | 中 |
1.3 XDP 的上下文结构
XDP 程序接收 struct xdp_md 作为上下文:
// XDP 上下文结构struct xdp_md { __u32 data; // 数据包起始地址 __u32 data_end; // 数据包结束地址 __u32 data_meta; // 元数据起始地址 __u32 ingress_ifindex; // 入接口索引 __u32 rx_queue_index; // 接收队列索引 __u32 egress_ifindex; // 出接口索引(5.19+)};二、XDP 的四种返回动作
2.1 动作详解
| 返回值 | 名称 | 语义 | 典型用途 |
|---|---|---|---|
| 0 | XDP_PASS | 交给内核协议栈继续处理 | 正常流量 |
| 1 | XDP_DROP | 在驱动层直接丢弃 | DDoS 防护、黑洞路由 |
| 2 | XDP_TX | 从接收网卡原路发送回去 | 负载均衡(DSR 模式) |
| 3 | XDP_REDIRECT | 重定向到其他接口或 CPU | 负载均衡、流量镜像 |
| 4 | XDP_ABORTED | 错误丢弃(仅用于调试) | 程序错误处理 |
2.2 动作选择决策树
三、XDP 的三种运行模式
3.1 模式对比
| 模式 | 执行位置 | 性能 | 灵活性 | 要求 |
|---|---|---|---|---|
| Native(原生) | 网卡驱动层 | 最高 | 中 | 网卡驱动支持 |
| SKB(通用) | 内核协议栈入口 | 中 | 高 | 无特殊要求 |
| Offload(卸载) | 网卡硬件 | 极高 | 低 | 智能网卡支持 |
3.2 检查网卡 XDP 支持
# 查看网卡是否支持 XDP native 模式ethtool -i eth0
# 检查 XDP 特性bpftool feature probe | grep xdp
# 附加 XDP 程序时指定模式sudo ip link set dev eth0 xdp obj xdp_prog.o sec xdp mode nativesudo ip link set dev eth0 xdp obj xdp_prog.o sec xdp mode skbsudo ip link set dev eth0 xdp offload obj xdp_prog.o sec xdp四、XDP 程序开发
4.1 基础 XDP 程序:DDoS 防护
#include "vmlinux.h"#include <bpf/bpf_helpers.h>#include <bpf/bpf_endian.h>
#define XDP_PASS 0#define XDP_DROP 1
// 黑名单 Mapstruct { __uint(type, BPF_MAP_TYPE_HASH); __uint(max_entries, 100000); __type(key, __u32); // 源 IP __type(value, __u32); // 动作} blacklist SEC(".maps");
// 速率限制 Mapstruct { __uint(type, BPF_MAP_TYPE_LRU_HASH); __uint(max_entries, 65536); __type(key, __u32); // 源 IP __type(value, __u64); // 上次允许的时间} rate_limit SEC(".maps");
SEC("xdp")int xdp_ddos_filter(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;
// 只处理 IPv4 if (eth->h_proto != bpf_htons(ETH_P_IP)) return XDP_PASS;
// 解析 IP 头 struct iphdr *iph = (void *)(eth + 1); if ((void *)(iph + 1) > data_end) return XDP_PASS;
__u32 src_ip = iph->saddr;
// 检查黑名单 __u32 *action = bpf_map_lookup_elem(&blacklist, &src_ip); if (action && *action == XDP_DROP) return XDP_DROP;
// 速率限制:每秒最多 1000 个包 __u64 *last_time = bpf_map_lookup_elem(&rate_limit, &src_ip); __u64 now = bpf_ktime_get_ns();
if (last_time) { if (now - *last_time < 1000000) // 1ms 内 return XDP_DROP; }
bpf_map_update_elem(&rate_limit, &src_ip, &now, BPF_ANY);
return XDP_PASS;}
char LICENSE[] SEC("license") = "GPL";4.2 XDP 负载均衡器
#include "vmlinux.h"#include <bpf/bpf_helpers.h>#include <bpf/bpf_endian.h>
// 后端服务器列表struct backend { __u32 ip; __u8 mac[6]; __u16 port;};
struct { __uint(type, BPF_MAP_TYPE_ARRAY); __uint(max_entries, 16); __type(key, __u32); __type(value, struct backend);} backends SEC(".maps");
// 连接状态表(一致性哈希)struct { __uint(type, BPF_MAP_TYPE_HASH); __uint(max_entries, 65536); __type(key, __u32); // 客户端 IP __type(value, __u32); // 后端索引} conn_table SEC(".maps");
SEC("xdp")int xdp_lb(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 != bpf_htons(ETH_P_IP)) return XDP_PASS;
struct iphdr *iph = (void *)(eth + 1); if ((void *)(iph + 1) > data_end) return XDP_PASS;
// 查找连接状态 __u32 client_ip = iph->saddr; __u32 *backend_idx = bpf_map_lookup_elem(&conn_table, &client_ip); __u32 idx;
if (backend_idx) { idx = *backend_idx; } else { // 简单哈希选择后端 idx = client_ip % 4; // 假设 4 个后端 bpf_map_update_elem(&conn_table, &client_ip, &idx, BPF_ANY); }
// 查找后端信息 struct backend *be = bpf_map_lookup_elem(&backends, &idx); if (!be) return XDP_PASS;
// 修改目标 IP 和 MAC iph->daddr = be->ip; __builtin_memcpy(eth->h_dest, be->mac, 6);
// 重新计算 IP 校验和 iph->check = 0; iph->check = bpf_csum_diff(0, 0, (void *)iph, sizeof(*iph), 0);
// 重定向到后端所在接口 return bpf_redirect_map(&backends, idx, 0);}
char LICENSE[] SEC("license") = "GPL";4.3 XDP 数据包修改
XDP 程序可以修改数据包内容——增删头部、修改字段:
SEC("xdp")int xdp_modify(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;
// 增加 VLAN 头部 // bpf_xdp_adjust_head 调整数据包起始位置 int offset = -4; // 向前扩展 4 字节 if (bpf_xdp_adjust_head(ctx, offset)) return XDP_PASS;
// 重新获取指针(adjust_head 后指针可能变化) data = (void *)(long)ctx->data; data_end = (void *)(long)ctx->data_end;
// 填充 VLAN 头部 // ...
return XDP_PASS;}五、XDP 重定向
5.1 重定向方式
| 方式 | Map 类型 | 说明 |
|---|---|---|
| bpf_redirect | 无 | 重定向到指定接口索引 |
| bpf_redirect_map | DEVMAP | 重定向到 Map 中的设备 |
| bpf_redirect_map | CPUMAP | 重定向到指定 CPU 处理 |
| bpf_redirect_map | XSKMAP | 重定向到 AF_XDP Socket |
5.2 DEVMAP 重定向
// DEVMAP:将数据包重定向到其他网卡struct { __uint(type, BPF_MAP_TYPE_DEVMAP); __uint(max_entries, 16); __type(key, __u32); // 接口索引 __type(value, __u32); // 目标接口索引} dev_map SEC(".maps");
SEC("xdp")int xdp_redirect(struct xdp_md *ctx){ __u32 key = ctx->ingress_ifindex; return bpf_redirect_map(&dev_map, key, 0);}5.3 CPUMAP 重定向
// CPUMAP:将数据包重定向到指定 CPU 处理struct { __uint(type, BPF_MAP_TYPE_CPUMAP); __uint(max_entries, 128); __type(key, __u32); // CPU 编号 __type(value, __u32); // 队列大小} cpu_map SEC(".maps");
SEC("xdp")int xdp_cpu_redirect(struct xdp_md *ctx){ // 将数据包重定向到 CPU 0 处理 __u32 cpu = 0; return bpf_redirect_map(&cpu_map, cpu, 0);}六、XDP 与 DPDK 的对比
| 维度 | XDP | DPDK |
|---|---|---|
| 架构 | 内核内(驱动层) | 用户态(绕过内核) |
| 开发语言 | C(eBPF 限制) | C/C++(无限制) |
| 性能 | 10-40 MPPS | 40-80 MPPS |
| 灵活性 | 受验证器限制 | 完全自由 |
| 部署 | 无需独占网卡 | 需要独占网卡 |
| 生态 | Linux 内核原生 | 独立用户态库 |
| 调试 | bpftool/bpftrace | 传统调试器 |
| 网络管理 | 与内核协议栈共存 | 完全接管网络 |
| 适用场景 | DDoS 防护、LB、防火墙 | NFV、SDN、高性能网关 |
XDP 和 DPDK 不是互斥的。AF_XDP 提供了 XDP 与用户态程序的桥梁——XDP 程序可以将数据包重定向到 AF_XDP Socket,用户态程序通过该 Socket 接收数据包,实现类似 DPDK 的高性能用户态处理,同时保留与内核协议栈的兼容性。
七、XDP 的生产案例
7.1 Meta Katran
Katran 是 Meta 开源的 L4 负载均衡器,基于 XDP 实现:
- 每秒处理 4000 万+ 数据包
- 使用 IPIP 封装实现 DSR(Direct Server Return)
- Consistent Hashing 实现会话保持
- 已在 Facebook/Instagram 基础设施中大规模部署
7.2 Cloudflare DDoS 防护
Cloudflare 使用 XDP 实现 DDoS 防护:
- XDP_DROP 在驱动层丢弃攻击流量
- 速率限制基于源 IP + 端口
- 与 iptables 规则联动
- 全球 300+ 数据中心部署
7.3 Cilium NodePort 加速
Cilium 使用 XDP 加速 NodePort 和 LoadBalancer Service:
- XDP 在驱动层处理 NodePort 流量
- 直接转发到后端 Pod,绕过内核协议栈
- 性能比 kube-proxy iptables 模式提升 5-10 倍
八、动手实践
8.1 最简 XDP 程序
# 使用 bpftrace 写一个 XDP 计数器sudo bpftrace -e 'xdp:eth0 { @xdp_packets = count();}'
# 或者使用 C 编写cat > xdp_count.bpf.c << 'EOF'#include <linux/bpf.h>#include <bpf/bpf_helpers.h>
struct { __uint(type, BPF_MAP_TYPE_PERCPU_ARRAY); __uint(max_entries, 1); __type(key, __u32); __type(value, __u64);} pkt_count SEC(".maps");
SEC("xdp")int xdp_counter(struct xdp_md *ctx){ __u32 key = 0; __u64 *count = bpf_map_lookup_elem(&pkt_count, &key); if (count) *count += 1; return 0; // XDP_PASS}
char LICENSE[] SEC("license") = "GPL";EOF8.2 附加 XDP 程序到网卡
# 编译clang -O2 -target bpf -c xdp_count.bpf.c -o xdp_count.bpf.o
# 附加到网卡(native 模式)sudo ip link set dev eth0 xdp obj xdp_count.bpf.o sec xdp
# 查看附加的 XDP 程序ip link show dev eth0
# 查看 XDP 程序统计sudo bpftool prog show
# 卸载 XDP 程序sudo ip link set dev eth0 xdp off8.3 XDP 性能测试
# 使用 pktgen 生成测试流量sudo modprobe pktgenecho "add_device eth0" > /proc/net/pktgen/kpktgend_0
# 使用 tcpreplay 回放 pcapsudo tcpreplay -i eth0 -K -l 1000000 capture.pcap
# 查看 XDP 处理统计sudo bpftool prog show id <prog_id># 输出包含:run_time_ns, run_cnt, etc.XDP 程序运行在驱动层,早于内核协议栈处理。这意味着 XDP 无法访问 socket、路由表等内核数据结构。如果需要这些信息,应使用 TC eBPF 程序代替。此外,并非所有网卡驱动都支持 XDP——确认你的驱动在 内核文档 的支持列表中。
九、本章小结
上一章理解了CO-RE 可移植性机制。 本章详解了 XDP 的完整技术栈:
| 主题 | 核心要点 | 关键词 |
|---|---|---|
| 架构 | XDP 在网卡驱动层执行,绕过内核协议栈,实现纳秒级处理延迟 | 架构 |
| 四种动作 | PASS(交给协议栈)、DROP(丢弃)、TX(原路发送)、REDIRECT(重定向) | 四种动作 |
| 三种模式 | Native(驱动层,最快)、SKB(通用兼容)、Offload(硬件卸载) | 三种模式 |
| 重定向 | DEVMAP(接口重定向)、CPUMAP(CPU 重定向)、XSKMAP(AF_XDP) | 重定向 |
| 与 DPDK 对比 | XDP 更易部署,DPDK 性能更高,AF_XDP 桥接两者 | 与 DPDK 对比 |
| 生产案例 | Katran(LB)、Cloudflare(DDoS)、Cilium(NodePort) | 生产案例 |
参考
支持与分享
如果这篇文章对你有帮助,欢迎支持作者或分享给更多人
部分信息可能已经过时






