1. Go 语言管理 eBPF 程序库

1.1 cilium/ebpf

cilium/ebpf 库是 Cilium 项目的一个子项目。仅使用 Go 语言编写的库,提供了加载、编译和调试 eBPF 程序的功能。它具有最小的外部依赖性,适合在长期运行的进程中使用。库主要有由 CloudflareCilium 两家公司维护,由于 Cilium 产品的火爆程度,该库的活跃度在社区层面还是会持续演进和发展。

cilium/ebpf 已经满足生产可用,但 API 现在显然是不稳定的,编写的程序升级时可能需要进行部分调整。cilium/ebpf 使用样例参考这里。该库提供的 cmd/bpf2go 工具允许在 Go 代码中编译和嵌入 eBPF 程序。

通过 go 语言注解来自动进行 eBPF 代码的编译。

1.2 iovisor/gobpf

iovisor/gobpf 项目为著名 eBPF 项目 BCC 的一个子项目。该库提供了 bcc 框架的 Go 语言绑定,可以在底层使用 .elf 格式文件加载和调试 eBPF 程序,底层依赖于 libbcc 库,使用前需要提前安装。iovisor/gobpf 使用样例参考这里

可以在 weaveworks/tcptracer-bpf 项目中查看到使用方式。

1.3 dropbox/goebpf

dropbox/goebpf 一个使用方便操作 eBPF 程序和读取 Perf 事件的 Go 语言库。dropbox/goebpf 使用样例参考这里

1.4 aquasecurity/libbpfgo

libbpfgo 是 Linux 的 eBPF 项目的 Go 库,目前主要在 Tracee 项目中使用,用于创建开源运行时安全性和 eBPF 跟踪工具。

1.5 仓库对比(截止到 2021-08-10)

update: 2021-11-29

仓库名 描述 贡献者/核心贡献者 创建时间 更新时间 stars forks open issues size(Byte) 备注
cilium/ebpf eBPF Library for Go 43/7 2019-09-05 2021-08-10 1523 149 21 9655K 纯 Go 语言实现
dropbox/goebpf Library to work with eBPF programs from Go 8/1 2019-01-24 2021-08-08 723 51 6 1786K 底层依赖libbpf( cgo)
iovisor/gobpf Go bindings for creating BPF programs. 49/3 2016-11-18 2021-08-10 1409 250 58 520K 底层依赖 libbcc/libbpf(cgo)
aquasecurity/libbpfgo eBPF library for Go, wrapping libbpf 8/2 2020-11-8 2012-08-10 150 28 23 475K 底层依赖 libpf(cgo)

核心贡献者,按照提交 commits 超过 10 个以上计算

库贡献者的活跃度

总结: cilium/ebpf > iovisor/gobpf > dropbox/goebpf > aquasecurity/libbpfgo

contributors_cilium_ebpf.png

iovisor/gobpf

contributions_iovisor_gobpf

dropbox/goebpf

contributions_dropbox_goebpf

aquasecurity/libbpfgo (update 2021-11-29)

image-20211129104210444

2. 样例代码测试

2.1 库选择

这里,我们使用 cilium/ebpf 来进行测试,主要是因为项目活跃度高并且为纯 Go 程序编写,从而实现了程序最小依赖;于此同时其还提供了 bpf2go 工具,可用来将 eBPF 程序编译成 Go 语言中的一部分,使得交付更加方便,后续如果配合 CO-RE 功能则威力大增。

2.2 样例代码结构

eBPF 程序一般有两部分组成:

  1. 基于 C 语言的 eBPF 程序,最终使用 clang/llvm 编译成 elf 格式的文件,为内核中需要加载的程序;
  2. Go 语言程序用于加载、调试 eBPF 程序,为用户空间的程序,用于配置或者读取 eBPF 程序生成的数据。

样例测试环境如下:

1
2
3
4
5
$ cat /etc/issue
Ubuntu 20.04 LTS

$ uname -a
Linux VM-0-14-ubuntu 5.4.0-42-generic

前置条件需要安装 clang/llvm 编译器:

1
2
3
4
5
6
7
8
9
# 安装 llvm 编译器,至少要求 clang 9.0 版本以上
$ sudo apt update -y
$ sudo apt install -y llvm
$ sudo apt install -y clang

# 安装 Go 
$ sudo apt install -y golang-go # golang-1.13

$ git clone https://github.com/cilium/ebpf.git

example 目录中,kprobe 代码结构如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
~/ebpf/examples$ tree
.
├── README.md
├── go.mod
├── go.sum
├── headers
│   ├── bpf_helper_defs.h
│   ├── bpf_helpers.h
│   └── common.h
├── kprobe
│   ├── bpf
│   │   └── kprobe_example.c         # bpf 程序
│   ├── kprobeexample_bpfeb.go       # 自动生成代码
│   ├── kprobeexample_bpfel.go       # 自动生成代码
│   └── main.go                      # go 用户态程序

主要为两个目录, headerskprobe ,其中:

  • headers 保存了 bpf 程序的头部依赖,为生成 CO-RE 方式下生成 vmlinux.h 文件的精简版;
  • kprobe 中分为 bpf 程序 bpf/kprobe_example 和用户态程序 main.go

在 main.go 文件中,有行注解:

//go:generate go run github.com/cilium/ebpf/cmd/bpf2go -cc clang-11 KProbeExample ./bpf/kprobe_example.c -- -I../headers

该注解使用 bpf2go 程序将 kprobe_example.c 文件编译成 kprobeexample_bpfeb.gokprobeexample_bpfel.go 两个文件,分别为 bigendianlittleendian 两种平台的程序。

其中参数中的 KProbeExample 参数为 main.go 文件中函数调用的名称,例如 objs := KProbeExampleObjects{}LoadKProbeExampleObjects(&objs, nil);

2.3 kprobe 样例代码测试

examples 目录执行 go run -exec sudo ./kprobe 即可运行 eBPF 程序:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
$ cd ebpf/examples
$ go run -exec sudo ./kprobe
go: downloading github.com/cilium/ebpf v0.6.1-0.20210610105443-1e7f01c7124c
go: extracting github.com/cilium/ebpf v0.6.1-0.20210610105443-1e7f01c7124c
go: downloading golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c
go: extracting golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c
go: finding github.com/cilium/ebpf v0.6.1-0.20210610105443-1e7f01c7124c
go: finding golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c
2021/08/10 15:22:46 Waiting for events..         # 已经开始执行
2021/08/10 15:22:47 sys_execve called 0 times    # 从程序 map 中读取
2021/08/10 15:22:48 sys_execve called 0 times

我们也可以到 examples/kprobe 目录下执行 go build -o main 命令,生成可交付文件 main ,后续只需要拷贝该文件即可实现二进制程序的分发 ,但是请注意的是我们没有使用 CO-RE 特性,因此还是需要内核环境一致情况下才能正常运行。

2.4 错误解决

1
2
$ go run -exec sudo ./kprobe
go: github.com/cilium/[email protected]: Get https://proxy.golang.org/github.com/cilium/ebpf/@v/v0.6.1-0.20210610105443-1e7f01c7124c.mod: dial tcp 172.217.160.113:443: connect: connection timed out

在当前终端执行:

1
$ go env -w GOPROXY=https://goproxy.cn

再次执行,就可以正常工作。

3. 总结

cilium/ebpf 通过生成中间代码的思路与 libbpf-bootstrap 的思路基本一致,简化了部分中间过程,让开发者更加聚焦。

libbpf-bootstrap 主要是还是聚焦在 C 代码层面,需要用户自己编写 C 语言生成 elf 文件的 Makefile 文件,从这点看 cilium/ebpf 中的 cmd/bpf2go 工具通过注解的方式解决了这个问题,屏蔽了细节,利于快速上手,但是从某种程度上对于高级用户也屏蔽了一定的灵活性。

libbpf-bootstrap 脚手架的使用可以阅读这篇博客