eBPF 网络数据面:XDP、tc 与可编程转发

系统讲解 eBPF 网络数据面:从验证器与加载流程到 XDP、tc 两大挂载点的处理时机与返回值语义,BPF map 与用户态交互、可编程转发的实战写法、bpftool 观测、性能特征与常见坑。

在 DPDK 需要独占网卡的同时,eBPF 给出了另一种答案:不旁路内核,而是在内核里插入可编程的钩子。报文仍然走内核协议栈,但你能在最早的时刻(网卡驱动收到包的瞬间)执行自己的逻辑——丢弃、重定向、改写,全部由验证器保证安全。

本文从 eBPF 的定位讲起,梳理验证器与加载流程、XDP 与 tc 两大挂载点的语义差异、map 的类型与用法、可编程转发的实战写法与观测手段,最后落到性能特征与常见坑。

一、eBPF 与可编程数据面的定位

1.1 为什么是 eBPF

传统内核模块能改数据面,但风险极高:一行空指针就能 panic 整个内核。eBPF 用「受限指令集 + 验证器」把风险关进笼子——程序必须通过静态检查才能加载,运行时有边界检查与超时保护。

eBPF 相对内核模块的优势:
  安全性:验证器静态检查,禁止越界与死循环
  可热插拔:无需重启内核,attach/detach 即时生效
  可观测:配合 map 与 perf ring buffer 导出数据
  可移植:CO-RE 让同一字节码适配不同内核版本

代价:
  指令集受限(无浮点、栈 512 字节)
  不能直接调用任意内核函数(只能用 helper)

1.2 数据面挂载点全景

内核提供了多个 eBPF 挂载点,越靠近网卡越早、越省资源,但可用的上下文越少。选择挂载点,本质是在「性能」与「信息量」之间权衡。

挂载点时机上下文典型用途
XDP驱动收包后、skb 分配前原始帧,无 skbDDoS 清洗、负载均衡
tc ingress进入协议栈前skb,可访问元数据策略路由、NAT
tc egress发包前skb流量整形、限速
socket filtersocket 层部分报文抓包(tcpdump)
cgroup/socksocket 创建时socket 元数据容器网络策略

二、eBPF 程序的加载与验证器

2.1 加载流程与字节码

eBPF 程序通常用 C 写(clang 编译成 BPF 字节码),再通过 bpf() 系统调用加载。加载时内核会做验证、JIT 编译,成功后返回一个 fd,attach 到挂载点后生效。

# 用 clang 把 C 编译成 eBPF 字节码
clang -O2 -g -target bpf -c xdp_drop.c -o xdp_drop.o

# 用 iproute2 加载并挂到网卡(XDP 模式)
ip link set dev eth0 xdp obj xdp_drop.o sec xdp

# 查看已加载的程序
bpftool prog show

# 卸载
ip link set dev eth0 xdp off

2.2 验证器的安全约束

验证器是 eBPF 的安全基石,它在加载前做数据流分析:所有内存访问必须有边界证明,所有循环必须可判定终止,栈使用不超过 512 字节。

验证器会拒绝的典型情况:
  1. 越界访问:指针未做范围检查就解引用
  2. 无限循环:循环上界无法静态确定(新内核支持有界循环)
  3. 未初始化读取:读栈上未赋值的内存
  4. 非法 helper 调用:在不允许的挂载点调用
  5. 栈溢出:局部变量超过 512 字节

排错手段:
  bpftool prog load ... type xdp   # 看验证器报错行号
  bpftool prog dump xlated id <id> # 看翻译后的指令

三、XDP:最早的数据面钩子

3.1 XDP 的处理时机与返回值

XDP 在网卡驱动收到包、尚未分配 skb 时执行,因此开销极低,但也拿不到 skb 元数据。程序的返回值决定报文命运,这是 XDP 最核心的语义。

返回值含义后续动作
XDP_DROP丢弃报文就地释放,不进协议栈
XDP_PASS放行继续走内核协议栈
XDP_TX从同一网卡发回立即反弹回去
XDP_REDIRECT重定向到其他网卡/CPU走 AF_XDP 或另一网口
XDP_ABORTED出错丢弃并记录 trace
/* XDP 程序骨架:丢弃所有 UDP 端口 53 之外的报文(示意) */
#include <linux/bpf.h>
#include <linux/if_ether.h>
#include <linux/ip.h>

SEC("xdp")
int xdp_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;          /* 边界检查,验证器要求 */
    if (eth->h_proto != __constant_htons(ETH_P_IP))
        return XDP_PASS;
    return XDP_PASS;
}

3.2 XDP 三种模式

XDP 可以运行在三种模式,性能与兼容性各不相同。生产环境优先用 native 或 offload,generic 仅作兼容兜底。

模式执行位置性能兼容性
native驱动内,收包早期最高需驱动支持
offload网卡硬件极高,几乎零 CPU仅少数智能网卡
generic协议栈入口(skb 后)较低所有网卡
# 指定 XDP 模式加载
ip link set dev eth0 xdpdrv obj prog.o sec xdp   # native
ip link set dev eth0 xdpgeneric obj prog.o sec xdp # generic
ip link set dev eth0 xdpoffload obj prog.o sec xdp # offload

# 查看当前模式
ip link show dev eth0 | grep xdp

四、tc 与流量控制钩子

4.1 tc ingress/egress

tc(traffic control)的 cls_bpf 分类器支持在 ingress 与 egress 两个方向挂 eBPF 程序。相比 XDP,tc 拿到的已经是 skb,可以读写更多元数据(如 mark、优先级、VLAN),也支持更复杂的重定向与封装。

# 在 eth0 的 ingress 挂 eBPF 分类器
tc qdisc add dev eth0 clsact
tc filter add dev eth0 ingress bpf da obj tc_prog.o sec ingress

# 在 egress 挂
tc filter add dev eth0 egress bpf da obj tc_prog.o sec egress

# 查看
tc filter show dev eth0 ingress

4.2 XDP 与 tc 的分工

XDP 与 tc 不是替代关系,而是不同层次的分工:XDP 做「最快的粗筛」,tc 做「更细的加工」。很多生产系统两者配合使用。

分层协作的典型架构:
  XDP(最早):
    - DDoS 清洗:识别并丢弃攻击流量
    - 四层负载均衡:改写目的地址并 XDP_TX 转发
    - 快速 ACL:按源 IP/端口黑名单丢弃

  tc ingress/egress:
    - NAT / 连接跟踪
    - 封装(VXLAN、Geneve)
    - 限速与流量整形
    - 配合 kube-proxy 替代方案做 Service 转发

五、map 与核间数据共享

5.1 map 类型

map 是 eBPF 程序与用户态、以及不同程序之间共享数据的唯一通道。不同 map 类型针对不同访问模式优化,选错类型会显著影响性能。

map 类型结构适用场景
BPF_MAP_TYPE_HASH哈希表通用键值查询(连接表)
BPF_MAP_TYPE_ARRAY数组固定索引、最快查找
BPF_MAP_TYPE_LRU_HASHLRU 哈希有上限的会话表
BPF_MAP_TYPE_PERCPU_HASH每 CPU 分片高频计数,免锁
BPF_MAP_TYPE_RINGBUF环形缓冲高效向用户态推事件
BPF_MAP_TYPE_LPM_TRIE最长前缀匹配路由表、CIDR 匹配

5.2 与用户态交互

用户态用 bpf() 系统调用或 libbpf 读写 map;ringbuf 提供了比 perf buffer 更高效的事件通道。控制面程序据此下发规则、读取统计。

/* 定义并查表(内核侧,示意) */
struct bpf_map_def SEC("maps") conn_map = {
    .type = BPF_MAP_TYPE_LRU_HASH,
    .key_size = sizeof(struct flow_key),
    .value_size = sizeof(struct flow_stat),
    .max_entries = 65536,
};

/* 查找;未命中则插入 */
struct flow_stat *s = bpf_map_lookup_elem(&conn_map, &key);
if (!s) {
    struct flow_stat init = {0};
    bpf_map_update_elem(&conn_map, &key, &init, BPF_NOEXIST);
}
# 用户态查看 map 内容
bpftool map show
bpftool map dump id 12

六、可编程转发实战

6.1 一个最小的四层转发程序

四层负载均衡的经典做法:XDP 程序查表得到后端地址,改写目的 IP/MAC 后用 XDP_TX 直接发回。整个转发不经过内核协议栈,单核可达百万级 QPS。

XDP 四层转发的处理步骤:
  1. 解析以太头、IP 头、TCP/UDP 头(每步做边界检查)
  2. 用五元组查 map,得到后端 VIP/后端地址
  3. 改写目的 MAC、目的 IP、目的端口
  4. 重算 IP 与传输层校验和
  5. 交换源/目的,返回 XDP_TX 发回

注意:
  - 校验和增量更新(bpf_csum_diff)比全量重算快
  - 回程流量需要反向映射,通常在另一张 map
  - 需处理分片与 ICMP 错误报文

6.2 用 bpftool 与 bpftrace 观测

调试 eBPF 数据面时,bpftool 看程序与 map,bpftrace 做动态追踪,perf 看 CPU 周期,三者结合能定位绝大多数问题。

# 查看 attach 情况
bpftool net show

# 统计 XDP 各返回值的计数(若程序写了统计 map)
bpftool map dump name xdp_stats

# 用 bpftrace 观察丢包点
bpftrace -e 'tracepoint:xdp:xdp_exception { @[args->act] = count(); }'

# 用 perf 看每包周期
perf stat -e cycles,instructions -a sleep 5

七、性能、卸载与常见坑

7.1 性能特征

eBPF 的性能优势来自「早」和「省」:XDP 在 skb 之前处理,避免了 skb 分配;JIT 编译后是原生指令;per-CPU map 免锁。但它的上限受限于「不能取代整个协议栈」。

性能参考(数量级,随硬件与程序复杂度变化):
  XDP_DROP(最简单):单核 10+ Mpps
  XDP 四层转发:单核 1~5 Mpps(含校验和与查表)
  tc eBPF:单核 1~3 Mpps(已有 skb 开销)
  对比:内核 iptables 转发 单核 0.3~1 Mpps

影响因素:
  程序指令数(验证器上限 100 万条,但越短越快)
  map 查找次数与类型
  是否命中 per-CPU cache
  是否触发 tail call 链

7.2 常见坑与对策

现象原因对策
加载失败验证器拒绝(越界/循环)补边界检查,看 bpftool 报错
程序不生效attach 到错误网卡/方向ip link / tc filter show 核对
丢包但无日志直接 XDP_DROP 无统计加 per-CPU 计数 map
性能不及预期map 选型不当高频路径用 ARRAY/PERCPU
内核升级后失效依赖内核结构体偏移用 CO-RE + vmlinux.h
报文被截断未处理分片或非线性区用 bpf_xdp_adjust_head 等 helper

八、总结

主题核心知识点落地建议
定位内核内可编程钩子,非旁路优先于内核模块,安全可热插拔
验证器静态检查保证安全每步指针都做边界检查
XDP最早钩子,五类返回值粗筛与四层转发首选
tcskb 级钩子,元数据丰富精细加工与封装用 tc
map数据共享唯一通道按访问模式选类型,热点用 PERCPU
观测bpftool / bpftrace / perf先加统计 map 再调优

eBPF 的真正突破在于把内核数据面从「编译期固化」变成了「运行时可编程」:过去要改转发逻辑得写内核模块、重启内核,现在一段通过验证的字节码就能即时生效。它没有 DPDK 那样极致的性能上限,但保留了内核的管理能力与协议栈,工程上更可控。对后端工程师来说,XDP 与 tc 已经不只是网络团队的工具——Service Mesh 的数据面、可观测性采集、容器网络策略都在用它,理解挂载点与 map,就是理解云原生数据面的通用语言。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「network」更多文章

  1. SDN 与 OpenFlow:控制与转发分离、控制器与南向接口
  2. RDMA 与 RoCE:数据中心高性能网络、无损网络与拥塞控制
  3. 网络时间同步:NTP、PTP 与高精度时钟