Post

eBPF 动态指令重写技术:原理与实践

eBPF 动态指令重写技术:原理与实践

eBPF 动态指令重写技术:原理与实践

本文详细解析了 pwru 核心黑科技——内核级 PCAP 过滤器注入所采用的指令重写技术。


1. 为什么需要指令重写?

在 eBPF 程序中,传统的交互方式是使用 BPF Maps。用户态更新 Map,内核态读取 Map 并根据配置执行不同的 if/else 分支。

传统 Map 方式的局限性:

  • 性能损耗:每次包处理都需要发起 bpf_map_lookup_elem 调用,涉及哈希计算、内存访问和锁竞争(如果是哈希表)。
  • 表达力受限:无法在预编译的 C 代码中穷举用户可能输入的所有逻辑组合(例如:tcp and (src 1.1.1.1 or dst 2.2.2.2) and not port 80)。
  • 冗余计算:即使某些过滤条件未开启,内核依然要执行判断逻辑。

指令重写(Injection)的优势:

  • 零额外开销:逻辑直接编译为机器码,没有 Map 查找,没有冗余判断。
  • 动态生成:在程序加载前,根据用户输入的表达式动态“织入”代码。
  • 原生性能:注入后的代码享受内核 JIT (Just-In-Time) 优化,速度等同于原生 C 代码。

2. 核心工作流:从字符串到内核机器码

第一步:表达式编译 (cBPF)

用户输入的 PCAP 字符串(如 host 1.1.1.1)在用户态通过 libpcap 库编译为 Classic BPF (cBPF) 字节码。cBPF 是专门为包过滤设计的伪指令集。

第二步:指令转换 (cBPF -> eBPF)

由于内核现在运行的是 eBPF,需要将 cBPF 指令序列转换为 eBPF 指令。

  • 逻辑映射:将 cBPF 的 LD_ABS(绝对偏移加载)指令转换为 eBPF 对 skb->data 的指针访问。
  • 寄存器保护:eBPF 有 10 个寄存器,而 cBPF 只有 2 个。转换时需要确保不破坏原程序的寄存器状态(通常使用栈暂存)。

第三步:代码占位 (The Placeholder)

pwru.bpf.c 的源代码中,我们预留一个特殊的逻辑区域:

1
2
3
4
5
6
// 源代码中的占位逻辑
if (pcap_filter_enabled) {
    // 编译器会生成一段可识别的特征码
    // 用户态加载器会找到这段特征码并将其替换为真正的 PCAP 过滤指令
    if (!do_injected_filter(skb)) return 0;
}

第四步:二进制补丁 (Patching)

bpf_object__load() 执行之前,用户态程序会直接操作 bpf_insn 指令数组:

  1. 扫描特征:定位到占位指令的位置。
  2. 暴力覆盖:将转换后的 PCAP eBPF 指令写入该位置。
  3. 跳转重定向:修正跳转指令的偏移量,确保注入的代码能正确返回主逻辑。

3. 在 XDP 场景下的应用

XDP 是这种技术最能发挥威力的地方,但也面临更严苛的挑战。

挑战:边界检查 (Boundary Checks)

XDP 的验证器(Verifier)要求任何包数据访问必须先进行安全检查。PCAP 编译出的 cBPF 默认不带检查。

  • 解决方案:转换引擎必须为每条读取指令自动插入安全围栏:
    1
    2
    3
    4
    
    // 注入的代码逻辑
    if (data + offset + size > data_end) 
        goto pass; // 无法解析则放行
    val = *(u32 *)(data + offset);
    

价值:早期丢弃 (Early Drop)

在 XDP 中注入 PCAP 过滤器,可以实现每秒千万级包的实时过滤。在 DDoS 防御场景下,这种“直接修改网卡逻辑”的方式是目前 Linux 性能的天花板。


4. 技术挑战总结

  1. 上下文不匹配kprobe 上下文是 pt_regsXDPxdp_md,注入代码需要手动从寄存器定位到网络包起始地址。
  2. 验证器限制:注入的代码必须极其精简且符合 eBPF 指令集的严格安全约束(如最大指令数、堆栈深度、无循环等)。
  3. 重写复杂性:需要深厚的汇编功底,手动计算每一个 jmp 指令的偏移量。

5. Map 交互 vs 指令重写 选型表

特性BPF Map (配置模式)指令重写 (补丁模式)
实时性极高(运行时动态修改 Map)较低(修改规则需重新加载程序)
过滤复杂度仅限预定义字段任意复杂逻辑 (PCAP 语法)
CPU 开销每个包增加几百个时钟周期几乎为零
实现难度中等极高 (黑客级)
典型代表Cilium (数据面状态)pwru (动态追踪), L4Drop

This post is licensed under CC BY 4.0 by the author.