← 返回 Atlas
Linux 内核

eBPF

2020 年某天,Cloudflare 的网卡每秒要面对数千万个攻击包。他们没有改内核,只是把一段编译好的程序 attach 到驱动收包那一刻——丢包决定在 sk_buff 分配之前就做完,官方口径比 iptables 高效约 10 倍,CPU 开销不到 5%。这套东西叫 eBPF,前身是 1992 年 tcpdump 用来过滤报文的 2 寄存器、32 位指令集虚拟机。三十年后,它从抓包优化长成能写进内核数据路径、顺带把 kube-proxy 几万条 iptables 规则整个干掉的通用沙箱。

内核 · 追踪 · XDP · kprobe

从 tcpdump 的过滤器到通用内核沙箱

1992 年,Van Jacobson 和 Steven McCanne 在 USENIX 发表《The BSD Packet Filter》:tcpdump 在用户态过滤太慢,他们造了一台 2 寄存器、32 位指令集的虚拟机塞进内核,只把匹配的包拷上来。Linux 在 1997 年的 2.1.75 里抄了同一套设计,这就是 cBPF。2011 年高速网络让解释执行成了开销,3.2 内核首次给 BPF 加 x86 JIT(Eric Dumazet 提交)。真正翻篇是 2014 年:Alexei Starovoitov 3 月贴出 Extended BPF——64 位、11 个寄存器、固定 8 字节指令,天生为 JIT 设计;5 月 15 日 commit 622582786c9e 合入,合成基准最高提速 20 倍。12 月发布的 3.18 才是首个原生 eBPF 内核,别把年份记成 3.17。

verifier:内核里那台爱找茬的证明机

把任意代码放进内核,第一道关卡是 verifier:它逐条指令做符号执行,证明你不越界、不死循环、不写坏内核内存——证不出来,load 直接失败。代价很具体:栈只有 512 字节(函数嵌套还会叠加,报错 'combined stack size of 7 calls is 688'),指令上限 100 万,5.3 之前连循环都不许写,只能 #pragma unroll 手工展开。2014 年 verifier 改成剪枝后,单个状态从 12464 字节瘦到 1712 字节,不然内存先爆炸。5.3 起它能逐路径证明循环终止;5.17 加 bpf_loop,大型循环不再逐条展开。注意 verifier 不是越新越松,5.4-5.10 的边界检查最狠,同一份程序老内核挂、新内核过很常见,排错先在 6.6+ 上跑。

XDP:在 sk_buff 之前动手

内核收包的默认路径很贵:NAPI 轮询拿到包、分配 sk_buff、过 GRO 合并、再一层层走协议栈——哪怕最终要丢的包,这趟路也一步没省。XDP 在 2016 年的 4.8 里合入(Brenden Blanco 牵头,Google、Amazon、Intel 跟进),程序 attach 在驱动层,sk_buff 分配之前就拿到原始 rx_ring 缓冲,当场决定:XDP_DROP 丢、XDP_PASS 放行、XDP_TX 原路退回、XDP_REDIRECT 转给别的网卡。没有驱动支持只能退到 generic 模式(xdpgeneric),走 sk_buff 路径,性能差一个量级。账很好算:Meta 的 Katran 纯 XDP 扛单机 100-200Gbps、CPU 使用率不到 5%;Cloudflare 用 XDP 秒级丢弃数千万个包,官方口径比 iptables 高效约 10 倍,省下的 CPU 折成约 30% 的成本下降。

观测与把 iptables 请下台

除了写进数据路径,eBPF 还接管了观测。一条 bpftrace 就能挂在 do_sys_open 上打印进程打开的路径:kprobe:do_sys_open { printf("%s: %s\n", comm, str(arg1)) }——arg1 是 pathname 指针,要用 str() 解引用;函数名跨版本会变,kprobe 不稳定,生产请用 tracepoint。替换 iptables 靠的是 BPF map:内核程序把 Service 三元组的查表结果放进哈希表,查一次 O(1),Cilium 用这套在 Kubernetes 里整个干掉 kube-proxy 的 iptables 规则链,Google GKE Dataplane V2 就是它。2018 年 BTF 进 4.18 后,CO-RE 让程序一次编译到处运行,不再每次内核升级重编——这正是它从创业公司工具变成基础设施标配的原因。

实战示例

一条探针看文件读得怎么样:sudo bpftrace -e 'kretprobe:vfs_read { @bytes = lhist(retval, 0, 2000, 200); }',Ctrl-C 后输出 @bytes: 直方图,[0,200) 区间 66 次、[2000,...) 39 次,大读和小读一屏就看出来;负值区间是 read 返回的错误码,看到 -1 先别慌,那是排查错误返回的直观信号。

子关键词

BPF map 内核与用户态之间的共享键值通道
用户态先建 map,内核程序按 key 读写的哈希或数组结构。Cilium 用它存 Service 端点,查表 O(1)。per-CPU 变体还能当『512 字节之外的大栈』用,解决栈空间不足的问题。
verifier 状态剪枝 让验证从 O(n) 状态机存活下来
2014 年 verifier 从 O(n) 状态机改成剪枝后,单个状态从 12464 字节瘦到 1712 字节,否则内存先爆炸。剪枝靠『状态相同就不再探索』,这才让 100 万指令上限变得可行。
BTF 与 CO-RE 一次编译,到处运行
BTF 在 4.18 提供稳定的类型元数据,libbpf 据此做 CO-RE:程序记录字段偏移,加载时按目标内核重定位。从此不用每次内核升级重编工具链,是 eBPF 能大规模铺开的前提。
XDP 动作码 收包瞬间的五种命运
XDP_DROP 丢、XDP_PASS 放行进协议栈、XDP_TX 原网卡退回、XDP_REDIRECT 转给别的队列或网卡,加上 XDP_ABORTED。无驱动支持时退 xdpgeneric,走 sk_buff 路径,性能差一个量级。
有界循环与 bpf_loop verifier 认可『能证明终止的循环』
5.3 起 verifier 逐条路径证明循环可终止,循环体复杂度计入 1M 上限;大型循环仍会吃爆,5.17 的 bpf_loop 让循环体不再逐条验证,支持约 800 万次迭代。
seccomp 与 tcpdump 的 eBPF 最常见的 eBPF 用户,低调到看不见
tcpdump 的 cBPF 过滤器在 2014 年 JIT 合并后由 eBPF 承载;seccomp-bpf 也是 eBPF 程序,只是可用指令很少。你每跑一次 tcpdump,verifier 都默默验一遍。

衍生角度

设计模式:把安全检查前置到加载时

verifier 在程序执行前用符号执行证明其安全,运行时零额外负担;对比传统内核模块的『信任开发者』,这是把信任边界从人移到了机器证明。

概念拆解:XDP 与 TC 一对兄弟

XDP 挂在驱动收包的最早处,拿原始缓冲;TC 挂在协议栈入口和出口,能看到 sk_buff。同一个 eBPF 虚拟机,一个管『别收』,一个管『怎么走』。

风格架构:探针散布加一张 map

bpftrace 一行挂一个 kprobe 或 tracepoint,数据写进共享 map,用户态去读;整个观测栈就是『插桩点 + 环形缓冲 + 哈希表』三件套,零重启、零改业务代码。

原理分析:map 查表为什么顶掉规则链

iptables 每条包都要线性过一遍规则,Service 一多就是 O(n);eBPF 把结果预先算进哈希,单次查表 O(1),这正是 Katran 单机 200Gbps 的原因。

实际用法与坑

  • 先确认 JIT:sysctl net.core.bpf_jit_enable=1,现代发行版默认已开,别手动折腾。
  • 循环上界别拿 16 位字段当上界,先 if (len > 100) len = 100; 截断,否则打爆 1M 复杂度上限。
  • 大缓冲别硬塞栈,用 per-CPU array map 当『更大的栈』,绕开 512 字节硬上限。
  • 生产探针用 tracepoint 不用 kprobe——函数名随内核版本变,换内核脚本就全挂。
  • 先在 6.6+ 内核上排错,老内核 verifier 更严,同一份程序可能 load 失败。

下一步动手做

装 bpftrace 跑一条 sys_enter_execve 探针看进程启动;用 bpftool prog show 读已加载程序的 jited/xlated 字段;写一个 10 行 XDP_DROP 程序,ip link set dev eth0 xdp obj x.o sec xdp 挂载再卸载;对照报错读 verifier 验证流程,把循环上界截断这类坑记进笔记。

相关书籍

BPF Performance Tools

Gregg《BPF Performance Tools》第 1-3 章讲演进与上手、第 8 章网络排查,和文中 bpftrace 片段同源。

深入理解 Linux 网络技术内幕

Benvenuti《Understanding Linux Network Internals》读 NAPI 与 sk_buff 接收路径,才能讲清 XDP 到底绕过了什么。

关联关键词