引言
eBPF(Extended Berkeley Packet Filter)彻底改变了 Linux 内核的可编程性。它允许在不重新编译内核或加载内核模块的情况下,安全地在内核空间运行沙箱程序。从 Linux 3.18(2014年)引入到如今,eBPF 已成为云原生基础设施的核心技术栈——Cilium 用它替代 kube-proxy,Falco 用它做运行时安全,Pixie 用它做零侵入式应用 profiling。
一、eBPF 架构总览
eBPF 程序的生命周期经过三道安全保障:
- 加载(Load):用户态通过
bpf()系统调用提交 eBPF 字节码,内核 Verifier 进行静态分析 - 验证(Verify):Verifier 模拟所有执行路径,确保无死循环、无越界访问、无未初始化读取
- 执行(JIT):验证通过后,JIT 编译器将字节码翻译为本地指令,在事件触发时执行
核心设计哲学:Let the kernel decide when, let the program decide what——内核决定何时触发程序,程序决定如何处理事件。
二、BPF 虚拟指令与寄存器模型
eBPF 采用精简的 64 位 RISC 寄存器架构:
| 寄存器 | 用途 |
|---|---|
| R0 | 函数返回值 / 程序退出值 |
| R1-R5 | 函数参数(调用后由被调用方保存) |
| R6-R9 | 调用者保存寄存器 |
| R10 | 帧指针(只读,指向栈底) |
指令编码格式为 8 字节:{opcode:8, dst:4, src:4, offset:16, imm:32}。Verifer 通过符号执行验证每条路径的安全性。
三、eBPF Maps:内核态通信枢纽
Maps 是 eBPF 程序与用户态及其他 eBPF 程序之间共享数据的主要机制:
- BPF_MAP_TYPE_HASH:通用哈希表,O(1) 查找,适合计数器、状态跟踪
- BPF_MAP_TYPE_ARRAY:固定大小数组,索引访问 O(1),适合 per-CPU 统计
- BPF_MAP_TYPE_PERCPU_HASH/ARRAY:每 CPU 独立实例,消除锁竞争,性能极高
- BPF_MAP_TYPE_RINGBUF:流式数据输出,替代 perf buffer,支持流式消费
- BPF_MAP_TYPE_LPM_TRIE:最长前缀匹配,适合 IP 路由或 CIDR 匹配
- BPF_MAP_TYPE_QUEUE/STACK:高效 FIFO/LIFO 数据结构
Map 的 max_entries 在创建时确定,内存由内核预分配。对于 per-CPU 类型,每 CPU 独立分配 max_entries * value_size 字节。
四、Hook 点分类与选择策略
eBPF 支持数十种 Hook 点,按时延敏感度分为三类:
4.1 网络层(最低延迟)
- XDP (eXpress Data Path):网卡驱动层,在 DMA 环形缓冲区收到包后立即触发,可丢弃/重定向/放行
- TC (Traffic Control):内核协议栈 QoS 层,支持 ingress/egress,可编程修改数据包
- Socket Filter:套接字层过滤,原始 BPF 的经典应用场景
4.2 追踪层(中等延迟)
- kprobe/kretprobe:动态插桩任意内核函数入口/返回,最为灵活但可能有稳定性风险
- tracepoint:内核预先埋点的稳定 ABI,首选的生产方案
- fentry/fexit:基于 BTF 的轻量级函数钩子,比 kprobe 更快
- uprobe/uretprobe
4.3 安全层
- LSM (Linux Security Module):安全决策点,可做 MAC 策略
- Cgroup:与容器 cgroup 绑定,实现资源限制和安全隔离
五、实战案例一:XDP DDoS 防护
场景:需要在线识别并丢弃 SYN Flood 攻击流量,要求处理速率达到 10Mpps。
架构设计:
- XDP 程序挂载在网卡驱动层,收到每个 SYN 包时查询 BPF_MAP_TYPE_LPM_TRIE 白名单
- 不在白名单的源 IP 写入 BPF_MAP_TYPE_HASH 计数器
- 超过阈值(如 1000 pps)的 IP 动态加入 BPF_MAP_TYPE_HASH 黑名单
- 黑名单中的 IP 直接 XDP_DROP,白名单 IP 放行进入协议栈
性能数据(mlx5 100G NIC):
- 单核处理:~26 Mpps(drop 操作)
- 多核 RSS 无锁扩展:线速 100G 无丢包
- 对比 iptables:iptables 在同样规则下仅处理 ~1.5 Mpps
六、实战案例二:系统调用追踪与异常检测
场景:需要实时监控容器内进程的 execve 调用,检测异常命令执行。
方案:使用 tracepoint/syscalls/sys_enter_execve 挂载 eBPF 程序,通过 bpf_get_current_pid_tgid() 获取进程信息,结合 cgroup ID 做容器标识。
关键代码路径:
SEC("tracepoint/syscalls/sys_enter_execve")
int trace_execve(struct trace_event_raw_sys_enter *ctx) {
struct event e = {};
e.pid = bpf_get_current_pid_tgid() >> 32;
e.cgroup = bpf_get_current_cgroup_id();
bpf_get_current_comm(

发表评论 取消回复