bpf link 的引入是为了解决 bpf prog attachment 持久化的问题,并由此引入了 bpf link fd。

在引入了 bpf link 之后,常用的 bpf prog attachment 都实现了对应的 bpf link,比如 TRACING, XDP, CGROUP, PERF_EVENT 等。

其实,在将 bpf link 引入内核之前,libbpf 便已经引入了 bpf link 的概念。

本文将基于 kernel 6.9 版本的代码来讲解 bpf link 的实现原理。

以 XDP bpf prog 为例,创建 bpf link 的过程如下:

1
2
3
4
SYSCALL_DEFINE3(bpf)                // ${KERNEL}/kernel/bpf/syscall.c
|-->__sys_bpf()
    |-->link_create()
        |-->bpf_xdp_link_attach()   // ${KERNEL}/net/core/dev.c

其中 bpf_xdp_link_attach() 是 XDP bpf prog 的 attachment 函数,其源代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
// ${KERNEL}/net/core/dev.c

int bpf_xdp_link_attach(const union bpf_attr *attr, struct bpf_prog *prog)
{
    struct net *net = current->nsproxy->net_ns;
    struct bpf_link_primer link_primer;
    struct netlink_ext_ack extack = {};
    struct bpf_xdp_link *link;
    struct net_device *dev;
    int err, fd;

    rtnl_lock();

    // 通过 ifindex 找到对应的 net_device
    dev = dev_get_by_index(net, attr->link_create.target_ifindex);
    if (!dev) {
        rtnl_unlock();
        return -EINVAL;
    }

    // 创建 bpf link 使用的内存
    link = kzalloc(sizeof(*link), GFP_USER);
    if (!link) {
        err = -ENOMEM;
        goto unlock;
    }

    // 初始化 bpf link
    bpf_link_init(&link->link, BPF_LINK_TYPE_XDP, &bpf_xdp_link_lops, prog);
    link->dev = dev;
    link->flags = attr->link_create.flags;

    // 分配 fd, ID,和创建一个匿名 file
    err = bpf_link_prime(&link->link, &link_primer);
    if (err) {
        kfree(link);
        goto unlock;
    }

    // XDP bpf prog 的 attach 过程
    err = dev_xdp_attach_link(dev, &extack, link);
    rtnl_unlock();

    if (err) {
        link->dev = NULL;
        bpf_link_cleanup(&link_primer);
        trace_bpf_xdp_link_attach_failed(extack._msg);
        goto out_put_dev;
    }

    // 将 fd 和 file 关联起来,后面便能通过 fd 来找到对应的 link 了
    fd = bpf_link_settle(&link_primer);
    /* link itself doesn't hold dev's refcnt to not complicate shutdown */
    dev_put(dev);
    return fd;

unlock:
    rtnl_unlock();

out_put_dev:
    dev_put(dev);
    return err;
}

bpf_xdp_link_attach() 可以看出,创建 bpf link 的过程主要包括以下几个步骤:

  1. 创建 bpf link 使用的内存。
  2. 初始化 bpf link。
  3. 分配 fd, ID,和创建一个匿名 file,缓存到 bpf prime 里。
  4. bpf prog 的 attach 过程。
  5. 将 ID 转给 bpf link,并将 fd 和 file 关联起来。

更新 XDP bpf prog 的实现过程如下:

1
2
3
4
SYSCALL_DEFINE3(bpf)                // ${KERNEL}/kernel/bpf/syscall.c
|-->__sys_bpf()
    |-->link_update()
        |-->link->ops->update_prog()

由 “bpf link 创建过程” 可知,link->ops&bpf_xdp_link_lops,因此 link->ops->update_prog() 实际上是调用了 bpf_xdp_link_update() 函数。

link->ops->update_prog() 不涉及 bpf link 本身的更新,而是由各个 bpf link 类型自己实现的。在此,就不展开各个类型的具体实现了。

卸载 XDP bpf prog 的实现过程如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
SYSCALL_DEFINE3(bpf)                // ${KERNEL}/kernel/bpf/syscall.c
|-->__sys_bpf()
    |-->link_detach()
        |-->link->ops->detach()
        |**>bpf_xdp_link_detach()   // ${KERNEL}/net/core/dev.c
        |   |-->bpf_xdp_link_release()
        |       |-->dev_xdp_detach_link()   // 具体的 detach 过程
        |-->bpf_link_put_direct()
            |-->bpf_link_free()
                |-->link->ops->dealloc()
                |**>bpf_xdp_link_dealloc()   // ${KERNEL}/net/core/dev.c
                    |-->kfree(link)

在卸载过程中,具体的 detach 过程由各个 bpf link 类型自己实现。

类似于 bpf prog,bpf link 也有自己的对象信息,即通过 bpftool link 所看到的信息。

1
2
3
4
5
6
# bpftool link
1: tracing  prog 2
    prog_type tracing  attach_type modify_return
    target_obj_id 1  target_btf_id 113304
42: xdp  prog 331
    ifindex ens33(2)

还是以 XDP bpf prog 为例,获取 bpf link 的对象信息的过程如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
SYSCALL_DEFINE3(bpf)                // ${KERNEL}/kernel/bpf/syscall.c
|-->__sys_bpf()
    |-->bpf_obj_get_info_by_fd()
        |-->bpf_link_get_info_by_fd() {
        |   info.type = link->type;
        |   info.id = link->id;
        |   if (link->prog)
        |       info.prog_id = link->prog->aux->id;
        |   link->ops->fill_link_info(link, &info);
        }
        |**>bpf_xdp_link_fill_link_info()   // ${KERNEL}/net/core/dev.c
            |-->info->xdp.ifindex = link->dev->ifindex;

以上代码片段展示了获取 XDP bpf link 的对象信息的过程。

  1. 通过 bpf link 获取基本的 typeid 信息。
  2. 如果 link 有 prog,获取 prog 的 id 信息。
  3. 调用 link->ops->fill_link_info() 来填充 link 具体类型对应的详细信息。

总结

bpf link 的引入是为了解决 bpf prog attachment 持久化的问题,并由此引入了 bpf link fd。

bpf link 的创建过程主要包括以下几个步骤:

  1. 创建 bpf link 使用的内存。
  2. 初始化 bpf link。
  3. 分配 fd, ID,和创建一个匿名 file,缓存到 bpf prime 里。
  4. bpf prog 的 attach 过程。
  5. 将 ID 转给 bpf link,并将 fd 和 file 关联起来。

bpf link 的更新过程由各个 bpf link 类型自己实现。

bpf link 的卸载过程由各个 bpf link 类型自己实现。

bpf link 的对象信息通过 bpftool link 来获取。其实,也可以通过 fdinfo 来获取:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# cat /proc/728267/fdinfo/10
pos:        0
flags:      02000000
mnt_id:     16
ino:        68
link_type:  xdp
link_id:    43
prog_tag:   3b185187f1855c4c
prog_id:    343
ifindex:    2

《XDP 进阶手册》里详细讲解了 XDP attachment 的源码细节。

欢迎加入「eBPF Talk」知识星球,学习更多 XDP 知识。

eBPF Talk 知识星球