引言

eBPF(Extended Berkeley Packet Filter)彻底改变了 Linux 内核的可编程性。它允许在不重新编译内核或加载内核模块的情况下,安全地在内核空间运行沙箱程序。从 Linux 3.18(2014年)引入到如今,eBPF 已成为云原生基础设施的核心技术栈——Cilium 用它替代 kube-proxy,Falco 用它做运行时安全,Pixie 用它做零侵入式应用 profiling。

一、eBPF 架构总览

eBPF 程序的生命周期经过三道安全保障:

  1. 加载(Load):用户态通过 bpf() 系统调用提交 eBPF 字节码,内核 Verifier 进行静态分析
  2. 验证(Verify):Verifier 模拟所有执行路径,确保无死循环、无越界访问、无未初始化读取
  3. 执行(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。

架构设计:

  1. XDP 程序挂载在网卡驱动层,收到每个 SYN 包时查询 BPF_MAP_TYPE_LPM_TRIE 白名单
  2. 不在白名单的源 IP 写入 BPF_MAP_TYPE_HASH 计数器
  3. 超过阈值(如 1000 pps)的 IP 动态加入 BPF_MAP_TYPE_HASH 黑名单
  4. 黑名单中的 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(                        
                    
点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论