gobpf 编译
编译过程
安装 llvm-10,clang-10
apt-install llvm-10 clang-10
下载 bpf2go
1 | go install github.com/cilium/ebpf/cmd/bpf2go@latest |
修改 bpf 程序的 include
1 |
编译时将 bpd 的 headers 包含进来
1 | GOPACKAGE=main bpf2go -cc clang-10 -cflags '-O2 -g -Wall -Werror' -target bpfel,bpfeb bpf helloworld.bpf.c -- -I /root/ebpf/examples/headers |
得到大端和小端两个版本的 ELF 文件,之后在 go 程序里加载即可。cpu 一般都是小端。
内核版本要求
经测试一些 gobpf 的一些 syscall 不适配较低版本的内核(例如 5.8 的 BPF_LINK_CREATE 会报参数错误),建议使用最新版本内核 5.19
bpf_map
用户态程序首先加载 bpf maps,再将 bpf maps 绑定到 fd 上。elf 文件中的 realocation table 用来将代码中的 bpf maps 重定向至正确的 fd 上,用户程序在 fd 上发起 bpf syscall
map 的 value 尽量不要存复合数据结构,若 bpf 程序和用户态程序共用一个头文件,用户态程序调用 bpf.Lookup 时由于结构体变量 unexported 而反射失败
pinning object
将 map 挂载到/sys/fs/bpf
1 | ebpf.CollectionOptions{ |
其他用户态程序获取 pinned map 的 fd
1 | ebpf.LoadPinnedMap |
packet processing
使用系统/usr/include 下的包头解析 lib
1 |
其中 ubuntu 缺失 asm 库,需要安装 gcc-multilib
1 | apt-get install -y gcc-multilib |
ctx 存放 dma 区报文的指针,同时存放报文的设备号和队列号
1 | struct xdp_md { |
由于 ctx 中的指针会发生变动,一般创建两个局部变量来存
1 | void *data_end = (void *)(long)ctx->data_end; |
为了缩减报文边界检查次数(例如,先检查 eth 头长度是否合法,再检查 ip 头长度是否合法,会造成多次检查,影响性能),检查器在加载 bpf 程序时就会根据针对 data_end 的 if 语句进行静态检查。例如某 bpf 函数内需要读 data 指针后 10 个地址的数据,需要进行如下的校验,否则检查器会拒绝加载 XDP 子节代码
1 | if (data + 10 < data_end) |
Example
按照xdp-project/xdp-tutorial: XDP tutorial (github.com)移植的 gobpf 程序(还未完成):
go-base/src/ebpf/xdp-ex at main · scottlx/go-base (github.com)
参考文档
BPF 进阶笔记(一):BPF 程序(BPF Prog)类型详解:使用场景、函数签名、执行位置及程序示例 (arthurchiao.art)
tc/BPF and XDP/BPF - Hangbin Liu’s blog (liuhangbin.netlify.app)
[BPF and XDP Reference Guide — Cilium 1.12.2 documentation](https://docs.cilium.io/en/stable/bpf/#:~:text=cBPF is known,before program execution.)