导读:本期聚焦于柬埔寨程序员创作的《如何使用bpf_get_attach_cookie标识eBPF程序附加点网络钩子?》,敬请观看详情。eBPF程序在网络钩子上的应用日益广泛,同一个程序经常被挂载到多个不同的挂载点,比如不同的cgroup或不同的netfilter位置。这时程序内部如何知道自己当前运行在哪一个附加点上呢?内核提供的bpf_get_attach_cookie辅助函数就是为此设计的。它能够返回用户态在加载程序时通过属性设置的自定义cookie值,从而让eBPF程序在运行时区分不同的挂载上下文。本文将深入解析这一机制的实现原理,通过一个完整的tc BPF示例展示如何设置和读取attach cookie,并探讨它在网络策略、流量分类等场景中的实际价值,同时说明内核版本要求和常见的使用误区。

当我们在Linux内核中部署eBPF程序时,经常会遇到这样的需求:同一个eBPF字节码需要附加到多个不同的网络钩子上,例如多个tc队列、多个cgroup或者不同的netfilter挂载点。程序逻辑基本一致,但在某些分支上需要根据具体的附加位置执行不同策略。传统做法是编写多个几乎相同的eBPF程序,或者通过映射(map)来传递上下文信息,但这些方法要么增加维护成本,要么引入额外的运行时开销。内核从5.15版本开始为部分程序类型提供了attach cookie机制,允许用户在加载程序时指定一个自定义的整数值,程序运行期间通过bpf_get_attach_cookie辅助函数读取该值,从而轻松识别自己所在的附加点。

如何使用bpf_get_attach_cookie标识eBPF程序附加点网络钩子?

attach cookie本质上是一个用户态可控的元数据,它被保存在内核的bpf_prog_aux结构中,与程序实例绑定。对于支持该特性的程序类型,同一个bpf_prog可以多次附加到不同位置,每次附加时都允许携带不同的cookie值。这样,eBPF程序在运行时调用bpf_get_attach_cookie就能获取到当前执行上下文对应的cookie,而不需要修改程序本身或者额外查询映射。下面我们深入探讨这一机制的工作原理和具体用法。

理解attach cookie的工作机制

bpf_get_attach_cookie是内核提供的eBPF辅助函数,其函数签名为__u64 bpf_get_attach_cookie(void *ctx)。对于大多数程序类型,参数ctx可以传入当前程序的上下文指针,例如tc程序的struct __sk_buff *,也可以传入空指针。该函数返回一个64位无符号整数,表示用户态在附加程序时设置的cookie值。如果调用者不支持attach cookie或者用户态没有显式设置,返回值通常为0。

设置cookie的方式因程序类型而异。对于基于cgroup的BPF程序(如BPF_PROG_TYPE_CGROUP_SKB),用户态通过BPF_PROG_ATTACH命令附加程序时,可以在union bpf_attrattach_cookie字段中指定值。对于tc BPF程序,由于使用netlink接口附加,早期内核并不直接支持cookie,但从5.15版本开始,可以通过tc工具的新选项或者使用libbpf的封装来设置。libbpf提供了bpf_program__set_attach_cookie函数,在加载对象前设置程序级别的默认cookie,也可以在bpf_prog_attach调用时通过struct bpf_prog_attach_opts中的attach_cookie字段覆盖。

bpf_get_attach_type相比,attach cookie提供了更大的灵活性。bpf_get_attach_type返回的是内核预定义的枚举值(如BPF_CGROUP_INET_EGRESS),它只能区分大类的附加类型,而无法区分同一类型下的不同实例。attach cookie则完全由用户自定义,可以是一个容器ID、接口索引、策略版本号等任意语义化数值。内核本身并不关心cookie的具体含义,它只负责存储和返回,这为上层应用提供了极大的设计空间。

从内核实现角度看,每次bpf_prog_attach操作都会创建一个bpf_prog_aux的引用,并在这个引用中保存attach_cookie字段。当eBPF程序运行并调用辅助函数时,内核通过当前正在执行的bpf_prog实例找到对应的bpf_prog_aux,读取其中的cookie值。需要注意的是,如果一个程序被附加到多个位置且每个位置的cookie不同,内核在运行时会自动选择与当前执行上下文匹配的那一个,这得益于程序运行时携带的bpf_prog对象引用是附加时创建的副本。

使用bpf_get_attach_cookie的完整示例

下面通过一个tc egress方向的BPF程序来演示cookie的实际用法。假设我们有两个网络接口eth0eth1,它们都挂载同一个tc BPF程序用于流量统计,但希望根据接口不同将统计结果写入不同的map条目。传统做法需要分别加载两个程序或者通过外部传入接口信息,而使用attach cookie后,内核态代码只需一次判断即可区分。

首先编写内核态eBPF程序tc_egress.c

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

struct {
    __uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
    __uint(max_entries, 2);
    __type(key, __u32);
    __type(value, __u64);
} pkt_count SEC(".maps");

SEC("tc")
int tc_egress(struct __sk_buff *skb) {
    __u64 cookie = bpf_get_attach_cookie(skb);
    __u32 idx;

    if (cookie == 100) {
        idx = 0; /* eth0 */
    } else if (cookie == 200) {
        idx = 1; /* eth1 */
    } else {
        return TC_ACT_OK;
    }

    __u64 *counter = bpf_map_lookup_elem(&pkt_count, &idx);
    if (counter) {
        __sync_fetch_and_add(counter, 1);
    }

    return TC_ACT_OK;
}

char LICENSE[] SEC("license") = "GPL";

在这个程序中,bpf_get_attach_cookie(skb)返回附加时设置的cookie值。如果cookie等于100,说明数据包来自eth0的egress方向;如果等于200则来自eth1。程序使用一个percpu数组map pkt_count,根据cookie选择不同的索引来累加计数。这样同一个程序就能同时服务于两个接口,而无需重复加载。

接下来是用户态加载逻辑,使用libbpf来设置cookie并附加程序。以下代码片段展示了核心步骤:

#include <bpf/libbpf.h>
#include <linux/bpf.h>
#include <net/if.h>

int main() {
    struct bpf_object *obj = bpf_object__open_file("tc_egress.o", NULL);
    if (!obj) return 1;

    struct bpf_program *prog = bpf_object__find_program_by_name(obj, "tc_egress");
    if (!prog) {
        bpf_object__close(obj);
        return 1;
    }

    /* 设置程序默认附加cookie,也可以后续在attach时覆盖 */
    bpf_program__set_attach_cookie(prog, 100);

    if (bpf_object__load(obj)) {
        bpf_object__close(obj);
        return 1;
    }

    int prog_fd = bpf_program__fd(prog);
    int ifindex_eth0 = if_nametoindex("eth0");
    int ifindex_eth1 = if_nametoindex("eth1");

    /* 附加到eth0,cookie=100 */
    DECLARE_LIBBPF_OPTS(bpf_tc_opts, opts0,
        .handle = 1,
        .priority = 1,
        .prog_fd = prog_fd,
    );
    if (bpf_tc_attach(ifindex_eth0, BPF_TC_EGRESS, &opts0)) {
        return 1;
    }

    /* 附加到eth1,cookie=200 */
    DECLARE_LIBBPF_OPTS(bpf_tc_opts, opts1,
        .handle = 2,
        .priority = 1,
        .prog_fd = prog_fd,
        .attach_cookie = 200,
    );
    if (bpf_tc_attach(ifindex_eth1, BPF_TC_EGRESS, &opts1)) {
        return 1;
    }

    /* 保持运行 */
    pause();
    return 0;
}

在这个例子中,程序在bpf_object__open_file之后、bpf_object__load之前通过bpf_program__set_attach_cookie设置了默认cookie为100。当附加到eth1时,通过bpf_tc_opts中的attach_cookie字段覆盖为200。libbpf会在内部调用BPF_PROG_ATTACH系统调用时传递相应的cookie值。注意tc BPF的附加机制从5.15内核开始才支持cookie,libbpf需要较新版本(建议1.0以上)。

完成加载后,我们可以通过查看map内容来验证cookie是否生效。在内核态程序中,bpf_get_attach_cookie返回的确实是用户态设置的值,程序据此正确地将计数累加到不同索引。这个示例展示了attach cookie在网络钩子场景中的实际效用——同一个程序体,不同附加点呈现不同行为。

适用场景与注意事项

attach cookie特别适合那些需要在多个实例间共享同一份eBPF字节码,但每个实例又有细微差异的场景。除了上述的接口区分外,常见的应用还包括:在cgroup级别实施网络策略时,为每个容器或pod分配一个唯一cookie,eBPF程序根据cookie查找对应的规则集;在XDP程序中,为不同的网卡队列设置不同cookie以实施差异化限速;在socket过滤器中,根据附加的socket归属来执行不同的审计逻辑。由于cookie是一个静态的整数值,它比动态查询map更加高效,避免了额外的查表开销。

使用attach cookie时需要注意几个关键点。第一,并非所有程序类型都支持attach cookie,目前支持的主要包括cgroup系列、tc、sk_lookup、flow dissector等,XDP程序在较新内核中也可以通过bpf_xdp_attach的选项设置cookie。第二,cookie的位宽在不同程序类型中可能不同,有些早期实现只支持32位,但bpf_get_attach_cookie统一返回64位,高位补零。第三,如果用户态忘记设置cookie,返回值默认为0,这可能导致程序误判,因此建议在代码中显式处理cookie为0的情况。第四,同一bpf_prog附加到多个位置时,每次附加的cookie是独立存储的,修改其中一个附加点的cookie不会影响其他附加点。

另一个常见的误区是将attach cookie与map索引直接关联。虽然示例中我们使用cookie来索引map,但这要求cookie值必须是连续的或者预先定义的。如果cookie值动态变化且范围很大,直接作为map索引会浪费内存,此时更好的做法是将cookie作为key的一部分,搭配hash map存储精细化的上下文信息。此外,attach cookie在程序运行期间是只读的,无法通过eBPF程序修改,如果需要动态调整不同附加点的行为,仍然需要借助map或全局变量。

总的来说,bpf_get_attach_cookie为eBPF网络钩子程序提供了一种轻量级的附加点标识方案,有效减少了程序重复加载的负担,提高了代码复用率。结合libbpf的现代封装,开发者可以很方便地在用户态控制每个附加点的行为差异。随着内核的持续演进,这一机制有望在更多程序类型中得到支持,成为eBPF程序多实例部署的标准实践之一。

eBPFbpf_get_attach_cookie网络钩子修改时间:2026-08-25 21:35:27

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。