> For the complete documentation index, see [llms.txt](https://marswang.gitbook.io/blog/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://marswang.gitbook.io/blog/ebpf/ebpf-ru-men-xi-lie-xue-xi-zi-liao-ji-gai-nian.md).

# eBPF入门系列：学习资料及概念

## 学习资料

* <https://www.ebpf.top/>
* <https://github.com/DavadDi/bpf\\_study>
* <https://github.com/nevermosby/linux-bpf-learning>
* <https://davidlovezoe.club/wordpress/archives/tag/bpf>
* &#x20;[eBPF 完全入门指南.pdf](https://mp.weixin.qq.com/s/zCjk5WmnwLD0J3J9gC4e0Q)
* [ebpf talk资料](https://asphaltt.github.io/post/)
* <https://github.com/Asphaltt/learn-by-example>
* cilium文档：[BPF和XDP参考指南](https://docs.cilium.io/en/stable/bpf/)
* iovisor [非官方BPF规范](https://github.com/iovisor/bpf-docs/blob/master/eBPF.md)
* XDP教程：<https://github.com/xdp-project>
* eBPF和GO: <https://networkop.co.uk/post/2021-03-ebpf-intro/>

## eBPF介绍

使用LLVM/clang编译bpf程序为bpf字节码；

可以通过bcc/bpftrace开发bpf程序；

### BPF Maps

BPF Maps可以被内核和用户空间同时访问，可用于用户态程序读取内核的数据，也可以从用户态向内核态bpf程序传配置；

源码定义：<https://elixir.bootlin.com/linux/v5.15.86/source/include/uapi/linux/bpf.h#L878>

内核文档定义：<https://docs.kernel.org/bpf/maps.html>

### BPF Program

bpf程序类型：kprobe（性能低、灵活性高）、tracepoints(性能高、灵活性低)

helper函数：<https://man7.org/linux/man-pages/man7/bpf-helpers.7.html>

bpf系统调用函数：<https://man7.org/linux/man-pages/man2/bpf.2.html>

#### 编译

**注意点**

1、clang编译器可以通过 -g 参数生成CO-RE需要的内核数据结构BTF文件；

2、gcc 12版本编译 BPF 程序支持 CO-RE 特性；

#### 检查 eBPF 对象文件

```bash
$ file hello.bpf.o
hello.bpf.o: ELF 64-bit LSB relocatable, eBPF, version 1 (SYSV), with debug_info, not stripped
```

```bash
$ llvm-objdump -S hello.bpf.o
hello.bpf.o: file format elf64-bpf 
Disassembly of section xdp: 
0000000000000000 <hello>: 
; bpf_printk("Hello World %d", counter"); 
 0: 18 06 00 00 00 00 00 00 00 00 00 00 00 00 00 00 r6 = 0
ll
 2: 61 63 00 00 00 00 00 00 r3 = *(u32 *)(r6 + 0)
 3: 18 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 r1 = 0
ll
 5: b7 02 00 00 0f 00 00 00 r2 = 15
 6: 85 00 00 00 06 00 00 00 call 6
; counter++; 
 7: 61 61 00 00 00 00 00 00 r1 = *(u32 *)(r6 + 0)
 8: 07 01 00 00 01 00 00 00 r1 += 1
 9: 63 16 00 00 00 00 00 00 *(u32 *)(r6 + 0) = r1
; return XDP_PASS; 
 10: b7 00 00 00 02 00 00 00 r0 = 2
 11: 95 00 00 00 00 00 00 00 exit
```

#### 加载并挂载程序

```bash
// xdp类型程序
$ ip link set dev eth0 xdp obj hello.bpf.o sec xdp
$ ip link set dev eth0 xdp off
```

### BPF程序和挂载类型

BPF程序类型、挂载类型和ELF Sections 对应关系 [libbpf 文档](https://docs.kernel.org/bpf/libbpf/program_types.html)

#### Tracing（跟踪类型）

* Kprobes 和 Kretprobes

  kprobes 用于挂载到内核函数入口点，也可以通过指令挂载到入口点偏移量位置(不稳定)；kretprobes 用于挂载到内核函数退出点。

  bpf程序参数类型根据挂载点类型而不同，函数第一个参数为函数名。

  ```c
  # 挂载kprobe程序到execve系统调用入口
  SEC("ksyscall/execve")
  int BPF_KPROBE_SYSCALL(kprobe_sys_execve, char *pathname)
    
  # 挂载kprobe程序到内核函数do_execve入口
  SEC("kprobe/do_execve")
  int BPF_KPROBE(kprobe_do_execve, struct filename *filename)
  ```
* Fentry 和 Fexit

  内核版本5.5引入的**更高效**的挂载到内核函数入口点和出口点的程序类型。

  kprobe类型程序代码可以无缝迁移到fentry。

  fexit类型程序还可以访问函数入参，而kretprobes不能

  ```c
  # 挂载kentry程序到内核函数do_execve入口
  SEC("fentry/do_execve")
  int BPF_PROG(fentry_execve, struct filename *filename)
   
  # kretprobe和fexit挂载相同内核函数do_unlinkat，fexit可以拿到函数入参
  SEC("kretprobe/do_unlinkat")
  int BPF_KRETPROBE(do_unlinkat_exit, long ret)
   
  SEC("fexit/do_unlinkat")
  int BPF_PROG(do_unlinkat_exit, int dfd, struct filename *name, long ret)
  ```
* Tracepoints

  tracepoints 是跟踪内核中被标记的代码位置。tracepoints在不同内核版本相对稳定

  查看当前版本内核可用的子系统跟踪点通过文件 */sys/kernel/tracing/available\_events*。

  查看跟踪点函数参数方式，以execve函数入口点系统调用为例：`cat /sys/kernel/tracing/events/syscalls/sys_enter_execve/format`。

  Raw\_tracepoints类型程序相对于tracepoints可以获得更好的性能，通过修改 raw\_tp/raw\_tracepoint代替tp。

  ```c
  # tracepoints类型程序SEC配置格式
  SEC("tp/tracingsubsystem/tracepoint name")

  SEC("tp/syscalls/sys_enter_execve")

  ```
* BTF-Enabled Tracepoints

  启动BTD的tracepoints，解决内核数据接口在不同版本定义发生变化问题。

  ```c
  # SEC定义格式：
  SEC("tp_btf/*tracepoint name*") 

  # 示例
  SEC("tp_btf/sched_process_exec")
  int handle_exec(struct trace_event_raw_sched_process_exec *ctx)
  ```

  函数第一个参数结构体名字格式：trace\_event\_raw\_+跟踪点名
* Uprobes and Uretprobes

  uprobes和uretprobes挂载用户空间函数入口点和退出点，USDTs (user statically defined tracepoints)挂载特殊的tracepoints到用户空间的应用代码/库文件。

  复用 kprobes 程序类型定义。

  ```c
  SEC("uprobe/usr/lib/aarch64-linux-gnu/libssl.so.3/SSL_write")
  ```

  在检测⽤⼾空间代码时需要注意⼀些问题：...
* LSM

  BPF\_PROG\_TYPE\_LSM类型程序用于挂载到Linux安全模块API，

#### Networking

需要的Linux capabilities: CAP\_NET\_ADMIN, CAP\_BPF, CAP\_SYS\_ADMIN。

BPF程序类型在网络协议栈的钩子

<figure><img src="/files/wNFZQwIjKF6VHP6wRCQo" alt=""><figcaption><p>BPF-proggram-typs-hook-in-network-stack</p></figcaption></figure>

使用场景：

1、通过 bpf 程序告诉内核如何处理数据包：重定向、丢弃、正常处理；

2、修改数据包或者socket参数；

* Sockets

  程序类型：

  * BPF\_PROG\_TYPE\_SOCKET\_FILTER：过滤复制到可观察工具（tcpdump）sockets。
  * BPF\_PROG\_TYPE\_SOCK\_OPS：允许拦截或者操作socket，例如设置socket各种参数
  * BPF\_PROG\_TYPE\_SK\_SKB：与sockmap一起使用，用于重定向socket流量
* Traffic Control

  eBPF程序可以挂载到自定义的filters或者classifiers，出入向都可以。

  使用tc命令操作ebpf 程序

  ```
  # 创建 clsact 类型的排队规则
  sudo tc qdisc add dev eth0 clsact
  # 加载接收方向的 eBPF 程序
  sudo tc filter add dev eth0 ingress bpf da obj tc-example.o sec ingress
  # 加载发送方向的 eBPF 程序
  sudo tc filter add dev eth0 egress bpf da obj tc-example.o sec egress
  # 查看eBPF程序
  tc filter show dev lxc17451654ad87 ingress
  tc filter show dev lxc17451654ad87 egress
  ```
* XDP

  可以使用ip link命令操作XDP程序

  只能作用于ingress方向数据包，无法针对egress方向数据包
* Flow Dissector（流解剖器）

  BPF\_PROG\_TYPE\_FLOW\_DISSECTOR: 可以自定义解剖数据包
* Lightweight Tunnels

  BPF\_PROG\_TYPE\_LWT\_\* 程序类型 ： 实现数据包封装
* Cgroups

  用于控制一个Cgroup中的进程组的行为，比如控制一个Cgroup的socket是否被请求或者传输

  程序类型：

  * BPF\_CGROUP\_SYSCTL：控制sysctl命令
  * BPF\_PROG\_TYPE\_CGROUP\_SOCK ：
  * BPF\_PROG\_TYPE\_CGROUP\_SKB
* Infrared Controllers

## 环境依赖

### BTF([BPF Type Format](https://www.kernel.org/doc/html/latest/bpf/btf.html))

是BPF程序一次编译随处运行（CO-RE：Compile Once-Run Everywhere）的关键，解决了 eBPF 程序在不同内核版本之间可移植的问题.

```bash
# cat /boot/config-`uname -r` | grep CONFIG_DEBUG_INFO_BTF
CONFIG_DEBUG_INFO_BTF=y
```

##
