eBPF 程序类型

本页目录

先问需要在哪个时刻看到什么对象,再选择 BPF 类型。context、helper/kfunc 和返回值各有契约,只修改 ELF section 名称不能完成类型转换。

下表是常见类型导航,完整对应关系见 libbpf 程序类型表。

查阅目标阅读位置
类型全景与 XDP 实验第 1~2 节
网络层与函数事件追踪第 3~4 节
LSM、struct_ops 与接口限制第 5~6 节

1. 常见类型速查

类型/家族执行位置与上下文主要目的
SOCKET_FILTERsocket 接收过滤,__sk_buff决定交给 socket 的数据长度
XDP支持的 XDP 数据路径,xdp_md早期报文处理与重定向
SCHED_CLS / SCHED_ACTTC,__sk_buff分类、动作、报文修改
KPROBEkprobe/kretprobe、uprobe/uretprobe动态函数探测,依架构寄存器上下文
TRACEPOINT / RAW_TRACEPOINT预定义事件或原始事件参数事件观测
TRACINGfentry/fexit、tp_btf 等BTF 类型化的函数/事件观测
PERF_EVENT软件/硬件性能事件周期或事件驱动采样
CGROUP_SKB / SOCK_ADDR 等cgroup 相关网络操作socket/地址/流量策略
SOCK_OPS / SK_MSG / SK_SKBsocket 回调或消息路径连接处理、消息重定向等
SK_LOOKUPsocket 查找在受支持查找路径选择目标 socket
LSM受支持 LSM hook审计与访问控制扩展
STRUCT_OPS特定内核操作表实现受支持回调,如拥塞控制、调度扩展
SYSCALL用户态通过相应 BPF 运行接口调用特定 BPF 操作/加载逻辑,不是 seccomp
返回值的含义由所挂接接口决定 XDP return XDP_PASS 继续进入协议栈 TC direct-action return TC_ACT_OK 按 TC 动作语义处理 LSM MAC hook return 0 本次检查不拒绝 程序类型、attach 类型、上下文和返回契约,必须一起选择。

2. XDP:模式决定“有多早”

native/driver XDP 常在驱动接收路径、构造常规 skb 之前运行。generic/SKB 模式则使用更晚的 skb 路径;硬件 offload 又受设备支持限制。因此不能把所有 XDP 都写成“NAPI 中零 skb 分配且无函数调用”。

动作后续含义
XDP_ABORTED / DROP异常终止 / 丢弃的相应处理
XDP_PASS继续后续栈路径
XDP_TX从接收设备发回
XDP_REDIRECT按准备好的目标重定向

REDIRECT 通常需先通过 helper 等设置设备、CPU 或 AF_XDP 等目标。仅返回常数,不能自动生成目标或补齐转发协议。

最小边界检查示例

下面只检查当前可直接访问的数据至少包含一个以太网头。它不解析 VLAN、IP 或多缓冲区完整报文,也不是可部署的完整过滤策略。

#include <linux/bpf.h>
#include <linux/if_ether.h>

__attribute__((section("xdp"), used))
int check_ethernet_header(struct xdp_md *ctx)
{
    void *data = (void *)(long)ctx->data;
    void *end = (void *)(long)ctx->data_end;
    struct ethhdr *eth = data;

    if ((void *)(eth + 1) > end)
        return XDP_DROP;
    return XDP_PASS;
}

char bpf_license[] __attribute__((section("license"), used)) = "GPL";

保存为 header.bpf.c,在有 Linux UAPI 头文件与 BPF 后端的 Clang 环境可编译:

clang -O2 -g -target bpf -Wall -Wextra -Werror -c header.bpf.c -o header.bpf.o

生成 ELF 只验证编译过程;真正加载还要通过 verifier,挂接还需权限和设备支持。某些发行版需补充对应架构 UAPI include 路径。调整 packet head 等 helper 可能改变数据指针有效性,后续访问应重新取得指针并重新证明边界。

下面只取源和目的端口这四个字节,逐项计算它们需要的边界。IHL 改变后,之前针对最小 IPv4 头成立的检查,未必覆盖新的端口位置。

正在呈现知识画面
固定检查 20 字节,为何不足以找到端口?

IPv4 头长是 4×IHL,端口位置不能始终写死为 Ethernet+20。读取端口前,需要合法 IHL 和相应 data_end 边界证明。

3. TC 与 cgroup 不是同一层的网络 hook

TC 处于 skb 路径,可利用更丰富的网络上下文。SCHED_CLS 的返回解释取决于挂接模式,direct-action 使用 TC_ACT_* 语义,传统 classifier 的分类结果不能直接套用同一解释。更新 attachment 还应考虑目标系统使用传统 TC 还是支持的较新 link/TCX 等接口。

cgroup 网络程序围绕 cgroup 与 socket 关联提供检查,例如创建、bind/connect 地址、发送接收流量等。socket 的关联生命周期和继承规则会影响它属于哪个 cgroup;移动进程并不等于所有已存在 socket 立即按直觉重新归属。

CGROUP_SKB 不是挂在 cgroup v1 的 net_cls 控制器上,也不能概括为“容器所有包必经唯一 hook”。实际网络产品往往组合 TC、XDP、cgroup 和 socket 层程序,不能只凭产品名断言其全部策略由一种类型实现。

4. 函数与事件追踪:稳定性与开销要分开看

kprobe 使用架构相关寄存器状态;BPF 的 R0–R10 虚拟寄存器不是被探测 C 函数的原始参数寄存器。应通过相应 tracing 宏/上下文访问参数,而不是写“R0–R5 就是函数入参”。

kretprobe 观测返回,可能需要与入口状态关联;函数内联、禁止探测位置、尾调用和目标实现变化都会影响覆盖。uprobe/uretprobe 对用户态代码进行类似探测,适合与否取决于触发频率和测得开销,并非一律不能用于生产。

tracepoint 提供显式命名的事件及字段,比依赖任意内部函数通常更容易维护;但 trace event 格式不等于所有版本永不改变的稳定 UAPI。实际字段可查 tracefs 的 events/.../format。raw tracepoint、常规 tracepoint 和 tp_btf 的上下文表示也不同。

类型化追踪:有 BTF 还要有可挂接目标

fentry/fexit 利用 BPF trampoline 与目标 BTF,通常便于按类型读取函数参数和返回信息,但需要相应内核、目标函数和 BTF 支持。不能把“有 BTF”理解成所有函数都可挂接。见追踪与调试。

5. LSM、struct_ops 与 SYSCALL

LSM 程序按具体 hook 参数定义上下文,并无一份可以覆盖所有 hook 字段的通用 bpf_lsm_ctx 结构。系统范围 MAC 与 cgroup LSM 的挂接和返回语义应分别查阅,不能把 0/1/-errno 的含义混用。见 LSM BPF。

struct_ops 只实现内核明确支持的操作表及回调,不允许任意替换任何内核函数指针。回调是否允许睡眠、如何验证对象、谁管理注册寿命,均由目标接口规定。

BPF_PROG_TYPE_SYSCALL 的名字容易误导:它不是在每个系统调用入口过滤用户进程,而是由用户态通过相应运行接口执行、具备特定 helper/kfunc 能力的程序类别。源码可从 kernel/bpf/syscall.c 的 bpf_syscall_prog_ops 与 test-run 路径阅读。系统调用限制应查 seccomp;系统调用观测可选 tracepoint 等。

6. Helper/kfunc 可用性不能只看名字

同一个 helper 是否允许取决于程序类型、attach 类型、上下文、权限和内核能力;kfunc 还可能有版本更强的内部接口约束。sleepable BPF 也只在支持的程序与调用点中合法。

归因边界:当前任务不一定是报文的业务所有者

网络软中断里取得的 current PID/comm,不一定是产生该报文的业务进程。即使 helper 调用合法,观测语义也可能不符合预期。先明确对象归属,再决定统计 key,才能避免做出看似精确但归因错误的图表。