1. 安装基本依赖
apt-get install -y make gcc libssl-dev bc libelf-dev libcap-dev \
clang gcc-multilib llvm libncurses5-dev git pkg-config libmnl-dev bison flex \
graphviz
2. 编译安装 libbpf
wget https://github.com/libbpf/libbpf/archive/refs/tags/v0.6.0.tar.gz
tar xzf v0.6.0.tar.gz
cd libbpf-0.6.0/src/
make
make install
说明:libbpf 是包含在内核代码库中的,尽量选择与内核版本匹配的版本。因为 Ubuntu 21.10 内核版本是 5.13 ,所以我选的版本是 0.6.0。
安装后, /usr/include/bpf/ 目录下就多了一些头文件:
total 324K
-rw-r--r-- 1 root root 14K Oct 28 15:04 bpf.h
-rw-r--r-- 1 root root 18K Oct 28 15:04 bpf_core_read.h
-rw-r--r-- 1 root root 3.7K Oct 28 15:04 bpf_endian.h
-rw-r--r-- 1 root root 149K Oct 28 15:04 bpf_helper_defs.h
-rw-r--r-- 1 root root 8.7K Oct 28 15:04 bpf_helpers.h
-rw-r--r-- 1 root root 21K Oct 28 15:04 bpf_tracing.h
-rw-r--r-- 1 root root 21K Oct 28 15:04 btf.h
-rw-r--r-- 1 root root 41K Oct 28 15:04 libbpf.h
-rw-r--r-- 1 root root 2.9K Oct 28 15:04 libbpf_common.h
-rw-r--r-- 1 root root 2.3K Oct 28 15:04 libbpf_legacy.h
-rw-r--r-- 1 root root 242 Oct 28 15:04 libbpf_version.h
-rw-r--r-- 1 root root 3.1K Oct 28 15:04 skel_internal.h
-rw-r--r-- 1 root root 9.6K Oct 28 15:04 xsk.h
3. 写一个 tc 程序
以下是 tc-example.c,一个示例程序的源码,改造自这篇文章:【这个程序定义了两个 section,一个会加载到 tc ingress,一个会加载到 tc egress,并且两者都使用一个全局的 map ,用来对进入接口和出接口的报文做字节数统计】
#include <linux/bpf.h>
#include <linux/pkt_cls.h>
#include <stdint.h>
#include <iproute2/bpf_elf.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_endian.h>
struct bpf_elf_map SEC("maps") acc_map = {
.type = BPF_MAP_TYPE_ARRAY,
.size_key = sizeof(uint32_t),
.size_value = sizeof(uint32_t),
.max_elem = 2,
.pinning = PIN_GLOBAL_NS,
};
static __always_inline int account_data(struct __sk_buff *skb, uint32_t dir)
{
uint32_t *bytes;
bytes = bpf_map_lookup_elem(&acc_map, &dir);
if (bytes)
__sync_fetch_and_add(bytes, skb->len);
return TC_ACT_OK;
}
SEC("ingress")
int tc_ingress(struct __sk_buff *skb)
{
return account_data(skb, 0);
}
SEC("egress")
int tc_egress(struct __sk_buff *skb)
{
return account_data(skb, 1);
}
char __license[] SEC("license") = "GPL";
重要说明:<iproute2/bpf_elf.h>
头文件定义了 bpf_elf_map
结构。在 Ubuntu 21.10 中,这个头文件已经被系统包含了,完整路径是/usr/include/iproute2/bpf_elf.h
。如果是低版本的系统,可能没有这个头文件,那么就需要添加如下的结构体和宏定义【有可能还需要重新编译 iproute2 和 libbpf,所以尽量选择高版本的系统 】:
#define PIN_GLOBAL_NS 2
struct bpf_elf_map {
__u32 type;
__u32 size_key;
__u32 size_value;
__u32 max_elem;
__u32 flags;
__u32 id;
__u32 pinning;
};
要用这个结构体是因为后面我们要使用 tc
这个命令将程序加载到内核中,如果不使用 tc
命令而是自己写程序加载,那么就不需要使用这个结构体,而是使用在 libbpf 中定义的 bpf_map_def
结构【/usr/include/bpf/bpf_helpers.h 头文件中定义的】,相应的 map 定义改为如下:
struct bpf_map_def SEC("maps") acc_map = {
.type = BPF_MAP_TYPE_ARRAY,
.key_size = sizeof(uint32_t),
.value_size = sizeof(uint32_t),
.max_entries = 2,
.map_flags = LIBBPF_PIN_BY_NAME
};
4. 编译并加载程序
用 clang
编译出 ebpf 程序:
clang -O2 -Wall -target bpf -c tc-example.c -o tc-example.o
用 tc
分别加载 ebpf 程序的 ingress section 和 egress section 到一个接口的 tc ingress 和 tc egress:
tc qdisc add dev ens33 clsact
tc filter add dev ens33 ingress bpf da obj tc-example.o sec ingress
tc filter add dev ens33 egress bpf da obj tc-example.o sec egress
5. 验证程序效果
先安装 bpftool
:
apt-get install linux-tools-$(uname -r)
然后用 bpftool map 命令可以看到程序创建的 map:
root@ubuntu21:~/test# bpftool map
1: array name acc_map flags 0x0
key 4B value 4B max_entries 2 memlock 4096B
还可以查看 map 中的数据:
root@ubuntu21:~/test# bpftool map dump id 1
key: 00 00 00 00 value: 80 21 00 00
key: 01 00 00 00 value: 02 21 00 00
Found 2 elements
root@ubuntu21:~/test# bpftool map dump id 1
key: 00 00 00 00 value: a8 22 00 00
key: 01 00 00 00 value: 2a 22 00 00
Found 2 elements
统计值在增加就证明我们的 tc 程序在工作了。Done!