在 Ubuntu 21.10 中编写 eBPF tc 程序

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!

猜你喜欢

转载自blog.csdn.net/woay2008/article/details/125754880
tc