其乐融融的IT技术小站

抽丝剥茧:从 Linux 源码探索 eBPF 的实现

去年学习 eBPF,分享过 几篇 eBPF 方面的学习笔记[1],都是面向 eBPF 的应用。为了准备下一篇文章,这次决定从 Linux 源码入手,深入了解 eBPF 的工作原理。因此这篇又是一篇学习笔记,假如你对 eBPF 的工作原理也感兴趣,不如跟随我的脚步一起。文章中若有任何问题,请不吝赐教。

这里不会再对 eBPF 进行过多的介绍,可以参考我的另一篇 使用 eBPF 技术实现更快的网络数据包传输[2],结合 追踪 Kubernetes 中的数据包[3] 可以了解 eBPF 的基本内容以及其在网络加速方面的应用。

接下来我们还是使用 eBPF sockops[4] 中的程序 bpf_sockops[5] 为例, 配合 Linux v6.8[6] 源码探索 eBPF 的工作原理。

注:由于公众号排版问题阅读可能不友好,可以点击阅读原文跳转到博客阅读。

图片图片

BPF 程序操作

在 load.sh[7] 脚本中,完成了程序的加载和挂载操作,下面的命令使用 bpftool[8] 分别完成 BPF 程序的加载和挂载。

#load
sudo bpftool prog load bpf_sockops.o "/sys/fs/bpf/bpf_sockop"
#attach
sudo bpftool cgroup attach "/sys/fs/cgroup/unified/" sock_ops pinned "/sys/fs/bpf/bpf_sockop"

这里 bpftool 是对内核函数 bpf() 封装的命令行工具,用于管理和操作 BPF 程序与 Map。

加载

sudo bpftool prog load bpf_sockops.o "/sys/fs/bpf/bpf_sockop"

命令 bpftool prog load 将 bpf_sockops.o 加载到路径 /sys/fs/bpf/bpf_sockop 中。

bpftool 对 BPF 程序的加载是由调用 `bpf()`[9] 指定命令 BPF_PROG_LOAD 并传入 加载选项`bpf_prog_load_opts`[10] 来完成的:

syscall(__NR_bpf, BPF_PROG_LOAD, &attr, sizeof(attr))
  • syscall bpf()[11] bpf 系统函数
  • bpf_prog_load[13] 为程序分配内存、初始化、检查证书、运行 verifier、创建文件描述符(fd)等
  • \_\_sys_bpf[12] 执行 bpf 命令 BPF_PROG_LOAD

加载成功后的程序,然后就可以进行挂载了。

挂载

sudo bpftool cgroup attach "/sys/fs/cgroup/unified/" sock_ops pinned "/sys/fs/bpf/bpf_sockop"

命令 bpftool cgroup attach 将加载(pin 到文件系统中)的程序 /sys/fs/bpf/bpf_sockop 挂载到 cgroup /sys/fs/cgroup/unified/,挂载的类型为 sock_ops。这个 sock_ops 是 bpftool 所使用的库 libbpf 定义,也被是 ELF 部件名,对应着 BPF 程序类型[14] BPF_PROG_TYPE_SOCK_OPS,挂载类型[15] 为 BPF_CGROUP_SOCK_OPS。

在 eBPF 编程中,ELF(Executable and Linkable Format)文件用于存储编译后的 eBPF 程序和相关数据。ELF 文件由多个部分(sections)组成,每个部分包含不同类型的信息,比如程序代码、符号表、调试信息等。

libbpf 类型 sock_ops => BPF 程序类型 BPF_PROG_TYPE_SOCK_OPS => 挂载类型 BPF_CGROUP_SOCK_OPS,对应到程序 bpf_sockops.c 中部件名(__section)为 sockops 的代码块。

关于 sock_ops 挂载点:

sock_ops 通常指的是在 Linux 内核中处理套接字操作的一系列函数和操作。

sock_ops 具体可以包括一系列的操作,如创建套接字、绑定套接字到特定地址和端口、监听来自其他套接字的连接请求、接受连接请求、发送和接收数据、以及关闭套接字等。这些操作通常通过一组预定义的 API 来提供,例如 POSIX 套接字 API,它定义了一系列函数,如 socket()、bind()、listen()、accept()、send()、recv() 和 close() 等,供应用程序调用。

这次 bpftool 是通过 bpf() 执行执行 BPF_PROG_ATTACH 并传入 挂载选项 `bpf_prog_attach_opts`[16] 来完成的。

syscall(__NR_bpf, BPF_PROG_ATTACH, &attr, sizeof(attr))
  • syscall bpf()[17] bpf 系统函数

cgroup_bpf_prog_attach[19]

\_\_cgroup_bpf_attach[21]

bpf_prog_put[22] 检查 cgroup 上是否存在相同挂载类型的程序,如果存在,则进行替换。

static_branch_inc[23] 如果不存在,则将 cgroup_bpf_enabled_key 计数器中,该挂载类型的计数 +1。

cgroup_bpf_prog_attach[20]

bpf_prog_attach[18]

cgroup_bpf_enabled_key 特定类型 cgroup BPF 程序的计数器。

!!! 在运行时,会用到该计数器。

到此,我们已经成功将程序挂载到 cgroup 的 sock_ops 上。

套接字操作 sock_ops

套接字的操作很多,这里以连接建立过程中服务端 accept 操作为例。

图片图片

依然是从系统调用 accept 开始。

  • accept[24]

do_accept[26] 此处 ops->accept() 中的 ops 对应着 proto_ops inet_stream_ops[27] 有状态的 socket(如 TCP) 的相关操作

inet_accept[29]  sk1->sk_prot->accept() 这里的 sk_prot 提供了 TCP 协议 `proto tcp_prot`[30] 的具体操作

tcp_v4_rcv[34] 此时第一次握手刚开始,sock(套接字在内核协议栈这层的体现) 的状态还是 TCP_LISTEN

tcp_v4_do_rcv[35] 在连接成功建立前,每次握手都会对状态进行处理。

tcp_init_transfer[37] sock 的状态被设置为 BPF_SOCK_OPS_PASSIVE_ESTABLISHED_CB,开始进行数据传输。

BPF_CGROUP_RUN_PROG_SOCK_OPS[39] 执行挂载类型为 BPF_CGROUP_SOCK_OPS 的 BPF 程序。

bpf_skops_established[38]

tcp_rcv_state_process[36] 我们直接看最后一次握手,也就是收到客户端的 ACK,完成与客户端连接的建立。

tcp_prot.accept[31]

inet_csk_accept 开始处理三次握手,调用 TCP 协议的实现来处理。inet_init[32] 注册了 IPPROTO_TCP 也就是 TCP 协议的实现,也就是 net_protocol tcp_protocol[33],其 handler 为 tcp_v4_rcv。

inet_stream_ops.accept[28]

\_\_sys_accept4_file[25]

BPF_SOCK_OPS_PASSIVE_ESTABLISHED_CB 是 socket.accept() 接受连接请求并完成连接建立的操作符,也是众多 `sock_ops` 操作符[40] 中的一个。这些操作符,可以被看作是 事件 Event[41],程序的触发则是由事件驱动的。例如:

  • 如果客户端发起连接请求并完成三次握手后的操作符是 BPF_SOCK_OPS_ACTIVE_ESTABLISHED_CB;
  • 套接字进入监听状态时的操作符是 BPF_SOCK_OPS_TCP_LISTEN_CB;
  • 数据被确认 BPF_SOCK_OPS_DATA_ACK_CB
  • TCP 状态改变 BPF_SOCK_OPS_STATE_CB

最后就是 BPF 程序的执行了,不多做赘述,有兴趣的看这里的 分析[42]。

参考资料

[1] 几篇 eBPF 方面的学习笔记: https://atbug.com/search?s=ebpf

[2] 使用 eBPF 技术实现更快的网络数据包传输: https://atbug.com/accelerate-network-packets-transmission/

[3] 追踪 Kubernetes 中的数据包: https://atbug.com/tracing-network-packets-in-kubernetes/

[4] eBPF sockops: https://github.com/addozhang/ebpf-sockops/

[5] bpf_sockops: https://github.com/addozhang/ebpf-sockops/blob/master/bpf_sockops.c#L28

[6] Linux v6.8: https://github.com/torvalds/linux/tree/v6.8

[7] load.sh: https://github.com/addozhang/ebpf-sockops/blob/master/load.sh

[8] bpftool: https://github.com/libbpf/bpftool

[9] bpf(): https://github.com/torvalds/linux/blob/715d82ba636cb3629a6e18a33bb9dbe53f9936ee/kernel/bpf/syscall.c#L5559

[10] 加载选项bpf_prog_load_opts: https://github.com/torvalds/linux/blob/05c31b4ab20527c4d1695130aaecc54ef59a0e54/tools/lib/bpf/bpf.h#L64

[11] syscall bpf(): https://github.com/torvalds/linux/blob/715d82ba636cb3629a6e18a33bb9dbe53f9936ee/kernel/bpf/syscall.c#L5559

[12] __sys_bpf: https://github.com/torvalds/linux/blob/715d82ba636cb3629a6e18a33bb9dbe53f9936ee/kernel/bpf/syscall.c#L5456

[13] bpf_prog_load: https://github.com/torvalds/linux/blob/715d82ba636cb3629a6e18a33bb9dbe53f9936ee/kernel/bpf/syscall.c#L2595

[14] BPF 程序类型: https://github.com/torvalds/linux/blob/d17aff807f845cf93926c28705216639c7279110/tools/include/uapi/linux/bpf.h#L964

[15] 挂载类型: https://github.com/torvalds/linux/blob/d17aff807f845cf93926c28705216639c7279110/tools/include/uapi/linux/bpf.h#L1000

[16] 挂载选项 bpf_prog_attach_opts: https://github.com/torvalds/linux/blob/05c31b4ab20527c4d1695130aaecc54ef59a0e54/tools/lib/bpf/bpf.h#L321C8-L321C28

[17] syscall bpf(): https://github.com/torvalds/linux/blob/715d82ba636cb3629a6e18a33bb9dbe53f9936ee/kernel/bpf/syscall.c#L5466

[18] bpf_prog_attach: https://github.com/torvalds/linux/blob/715d82ba636cb3629a6e18a33bb9dbe53f9936ee/kernel/bpf/syscall.c#L3936

[19] cgroup_bpf_prog_attach: https://github.com/torvalds/linux/blob/v6.8/kernel/bpf/cgroup.c#L1130

[20] cgroup_bpf_prog_attach: https://github.com/torvalds/linux/blob/v6.8/kernel/bpf/cgroup.c#L733

[21] __cgroup_bpf_attach: https://github.com/torvalds/linux/blob/v6.8/kernel/bpf/cgroup.c#L607

[22] bpf_prog_put: https://github.com/torvalds/linux/blob/v6.8/kernel/bpf/cgroup.c#L700

[23] static_branch_inc: https://github.com/torvalds/linux/blob/v6.8/kernel/bpf/cgroup.c#L702

[24] accept: https://github.com/torvalds/linux/blob/v6.8/net/socket.c#L2016

[25] __sys_accept4_file: https://github.com/torvalds/linux/blob/v6.8/net/socket.c#L1969

[26] do_accept: https://github.com/torvalds/linux/blob/v6.8/net/socket.c#L1929

[27] proto_ops inet_stream_ops: https://github.com/torvalds/linux/blob/eef00a82c568944f113f2de738156ac591bbd5cd/net/ipv4/af_inet.c#L1051

[28] inet_stream_ops.accept: https://github.com/torvalds/linux/blob/eef00a82c568944f113f2de738156ac591bbd5cd/net/ipv4/af_inet.c#L1058

[29] inet_accept: https://github.com/torvalds/linux/blob/eef00a82c568944f113f2de738156ac591bbd5cd/net/ipv4/af_inet.c#L780

[30] proto tcp_prot: https://github.com/torvalds/linux/blob/da7dfaa6d6f731c30eca6ffa808b83634d43e26f/net/ipv4/tcp_ipv4.c#L3314

[31] tcp_prot.accept: https://github.com/torvalds/linux/blob/da7dfaa6d6f731c30eca6ffa808b83634d43e26f/net/ipv4/tcp_ipv4.c#L3314

[32] inet_init: https://github.com/torvalds/linux/blob/eef00a82c568944f113f2de738156ac591bbd5cd/net/ipv4/af_inet.c#L1997

[33] net_protocol tcp_protocol: https://github.com/torvalds/linux/blob/eef00a82c568944f113f2de738156ac591bbd5cd/net/ipv4/af_inet.c#L1754

[34] tcp_v4_rcv: https://github.com/torvalds/linux/blob/da7dfaa6d6f731c30eca6ffa808b83634d43e26f/net/ipv4/tcp_ipv4.c#L2319

[35] tcp_v4_do_rcv: https://github.com/torvalds/linux/blob/da7dfaa6d6f731c30eca6ffa808b83634d43e26f/net/ipv4/tcp_ipv4.c#L1929

[36] tcp_rcv_state_process: https://github.com/torvalds/linux/blob/v6.8/net/ipv4/tcp_input.c#L6724

[37] tcp_init_transfer: https://github.com/torvalds/linux/blob/v6.8/net/ipv4/tcp_input.c#L6208

[38] bpf_skops_established: https://github.com/torvalds/linux/blob/v6.8/net/ipv4/tcp_input.c#L192

[39] BPF_CGROUP_RUN_PROG_SOCK_OPS: https://github.com/torvalds/linux/blob/v6.8/include/linux/bpf-cgroup.h#L350

[40] sock_ops 操作符: sock_ops

[41] 事件 Event: https://atbug.com/accelerate-network-packets-transmission/#事件驱动

[42] 分析: https://atbug.com/accelerate-network-packets-transmission/#实现

赞 ()
分享到:更多 ()

相关推荐

内容页底部广告位3
留言与评论(共有 0 条评论)
   
验证码: