XDP/BPF Github教程

  • Post author:
  • Post category:其他


参考:

xdp-tutorial



basic01

主要介绍了如何编写简单的xdp程序以及通过libbpf库或者iproute2将xdp程序加载到内核并将内核代码挂载到指定的网络接口上。



Simple XDP code:

SEC("xdp")
int  xdp_prog_simple(struct xdp_md *ctx)
{
        return XDP_PASS;
}

section本身的作用就是来自汇编中的声明,汇编每一段开头都有不同的声明,表示接下来这一段的内容是什么。如下图所示,BPF字节码存储在ELF文件中。为了将BPF字节码加载到内核中,用户空间需要ELF loader来读取ELF文件并将其以正确的格式加载到内核,

libbpf

库即提供了这样的ELF loader以及XDP辅助函数。

image-20200914190739748



Loading and the XDP hook

第一种是使用iproute2工具来加载BPF程序到内核,标准的iproute2工具包含BPF ELF loader,但是因为这个加载器并不是基于libbpf,当开始使用BPF映射时将会出现不兼容的情况。

image-20200914192431968

第二种是基于libbpf API。


xdp_pass_user.c

这个文件干的事情就是将.o文件(bpf经过llvm+clang编译生成的字节码加载到内核并attach到内核对用设备上)

两个关键函数:

bpf_prog_load 返回加载到内核的文件句柄

bpf_set_link_xdp_fd将内核文件挂载到hook点



basic02

主要介绍了对于包含多个section的BPF字节码文件(obj文件),在libbpf中如何将obj文件加载到内核并根据section name选择将对应的section内核代码挂载到hook点



任务一

image-20200915153833437

可以使用下列命令查看指定网络接口上的xdp程序:

ip link list dev veth-basic02
bpftool net list dev veth-basic02

image-20200915154117762

使用命令

sudo ./xdp_loader --dev veth-basic02 --unload --progsec xdp_drop

即可在网络接口上卸载指定的xdp程序。



任务二

比较简单,可参考原github链接

整个流程:



basic03



创建BPF映射

创建BPF映射是通过定义如下全局结构变量

struct bpf_map_def

实现的,

struct bpf_map_def SEC("maps") xdp_stats_map = {
	.type        = BPF_MAP_TYPE_ARRAY,
	.key_size    = sizeof(__u32),
	.value_size  = sizeof(struct datarec),
	.max_entries = XDP_ACTION_MAX,
};

需要注意的是,一切都通过bpf系统调用进行,这就意味着用户空间程序需要通过调用不同的bpf系统调用分别创建BPF映射和BPF程序。那么一个BPF程序如何引用BPF映射呢——首先加载所有的BPF映射并存储其对应的文件描述符,然后通过ELF重定位表来确定BPF程序对给定的BPF映射的引用,每个引用都会被重写,因此BPF字节码指令能够使用正确的映射文件描述符。所有这些需要在BPF程序加载到内核之前完成。

注释:


Helper Functions(BPF辅助函数)

:辅助函数对应着一系列内核定义的函数调用,使得BPF程序可以从内核检索数据或将数据推送到内核。不同类型的BPF程序可用的辅助函数有所区别,例如,挂载到sockets上的BPF程序所能调用的辅助函数是挂载到tc层的BPF程序的一个子集(举个栗子,用于构建轻量级隧道的封装和解封装的辅助函数只对较低tc层(lower tc layers)的BPF程序适用,而用于将通知推送到用户空间的辅助函数的事件输出辅助函数可用于tc和XDP的BPF程序)。BPF辅助函数定义在bpf.c文件中,bpf_attr联合中包含不同的匿名结构体用于向系统调用传递不同的参数信息,比如可指定创建映射所需要的信息、对映射进行操作所需的参数信息或者加载BPF程序到内核所需的参数信息等等。以bpf_map_update_elem为例,其调用过程如下:

image-20200916143624800

image-20200916144544070

第一个参数__NR_bpf不同对应不同的架构指令集,cmd是枚举变量,指明进行的操作,后面两个参数传递必要的参数信息。



定位bpf映射的文件描述符

image-20200916162223712

即通过libbpf库中实现的bpf_object__find_map_by_name来获取struct bpf_map类型结构体,并通过bpf_map_fd从中获取映射的文件描述符。



检查映射信息(如datarec是否是期望大小)


关键函数调用


err = bpf_obj_get_info_by_fd(map_fd, info, &info_len);

该函数是bpf辅助函数,通过映射的文件描述符获取映射信息并存到info结构体。



用户空间程序读取映射内容

image-20200916192659894

任务一运行结果:

image-20200916192734974

image-20200916192806313

image-20200916195406168



basic04

在这之前的第三节,用户空间程序

xdp_load_and_stats.c

既进行了BPF程序的加载又从BPF映射中读取数据包的统计数据,通常只有加载BPF程序的同一个程序才能访问BPF map。在这一节将了解到从“外部”程序来访问BPF映射,这是通过BPF虚拟文件系统实现的。

这部分的用户空间程序将分为两个:

  • xdp_loader.c: 将BPF/XDP程序加载到内核
  • xdp_stats.c:从内核BPF映射获取并打印数据

接下来阅读相关代码并做笔记以供之后实践参考:



xdp_prog_kern.c文件的修改

  1. image-20200917093704356

    映射类型更改为

    BPF_MAP_TYPE_PERCPU_ARRAY

    ,即在每个CPU上都维护一个BPF映射存放数据包统计信息,最后在用户空间程序打印数据包统计信息时需要对所有CPU上的统计信息手动汇总。

  2. image-20200917094212330

    实现

    xdp_stats_record_action

    (强制内联)进行存储在BPF映射中不同类型数据包统计信息的更新。

  3. image-20200917094645128

    新增

    xdp_drop

    ,

    xdp_abort

    的BPF program。当指定其中的一个SEC(如xdp_drop)挂载到特定hook点时,数据包到达该hook点由于XDP程序返回XDP_DROP,数据包被丢弃。



loader.c文件的实现


  1. load_bpf_and_xdp_attach

    函数内部新增:

image-20200917110945228

接下来看

load_bpf_object_file_reuse_maps

函数的实现:

image-20200917132440662

image-20200917133119697


  1. loader.c

    文件新增将BPF程序和BPF映射保存到虚拟文件系统。

image-20200917111114783

接下来查看

pin_maps_in_bpf_object

的具体实现,其传入的第一个参数是struct bpf_object类型结构体,第二个参数是挂载bpf程序的网络接口名(如veth-basic02)。在

/sys/fs/bpf/cfg.ifname

路径下保存两种类型的对象:BPF映射和完整的BPF程序。

进入此函数,首先检查

/sys/fs/bpf/map_filename

对应的映射持久化文件是否存在,如果存在说明之前的XDP程序没有被清理,调用

libbpf

中的

bpf_object__unpin_maps(bpf_obj, pin_dir)

来清理该目录

/sys/fs/bpf/cfg.ifname

下面的所有map映射持久化文件。然后通过

bpf_object__pin_maps(bpf_obj, pin_dir);

将该BPF对象中的所有映射保存到文件系统。



packet01

image-20200917170103009

+-----------------------------+                          +-----------------------------+
| Root namespace              |                          |Testenv namespace 'package'  |
|                             |      From 'test01'       |                             |
|                    +--------+ TX->                RX-> +--------+                    |
|                    |package +--------------------------+  veth0 |                    |
|                    +--------+ <-RX                <-TX +--------+                    |
|                             |       From 'veth0'       |                             |
+-----------------------------+                          +-----------------------------+
key(src ip) value (1)
192.168.11.168 1
192.168.11.169 1
192.168.11.190 1
1



packet 02

实现效果:

image-20200921155113353

image-20200921155129865



packet 03-对数据包进行重定向


XDP_TX

:转发数据包,这可能在数据包被修改前或修改后发生,转发数据包意味着将接收到的数据包发送回数据包到达的同一网卡。



任务一实验效果:

image-20200922092655155

image-20200922092704531



任务二:

除了能够将数据包从相同的网络接口发送回去,XDP还能将数据包直接发送到其他网络接口的出口(egress ports of other interfaces),这可以通过

bpf_redirect

或者

bpf_redirect_map

辅助函数(返回XDP_REDIRECT)来实现,实验效果如下:

image-20200922104505784

image-20200922104526500



任务三:


bpf_redirect_map

:将数据包重定向其他端口,是任务二使用的

bpf_redirect

一般化版本,更为常用且高效。

这个时候在right网络接口的rx端和left网络接口的rx端都附加上xdp_redirect_map对应的BPF程序,这个时候,在/sys/fs/bpf/left和/sys/fs/bpf/right虚拟文件夹下面都会产生对应的持久化映射文件,如下图所示:

image-20200922151216551

  • 映射tx_port存储的是虚拟端口到重定向网络接口索引的映射关系,映射类型为

    BPF_MAP_TYPE_DEVMAP

    , 在BPF程序中通过语句

    action = bpf_redirect_map(&tx_port, 0, 0);

    将数据包转发到另一个接口的egress端,其第一个参数是Map,第二个参数是key,第三个参数是flag;
  • 映射redirect_params存储的是源mac地址到目的mac地址的映射关系,也就是说,当数据包从子命名空间的veth0接口发出来到达left网络接口,其上面的BPF程序就会修改以太帧的头部字段,将报文中实际的目的mac地址(left网络接口mac地址)替换成right子命名空间下的veth0网络接口的mac地址,最后执行

    bpf_redirect_map

    完成数据包的转发;

总结起来就是干了两件事情,一是修改链路层数据包头,二是转发。

xdp_prog_user.c

的作用就是分别设置left和right网络接口对应的映射文件所存储的参数信息。

image-20200922152501083

为方便起见,提供了

t redirect right left

来简化操作步骤,

bpf_redirect_map做的事情就是将数据包从left网口(网络接口索引为18)的入口直接转发到right网口的出口(网络接口索引为20),修改mac地址是将目的mac地址修改为right子空间veth0网口的mac地址,这样就不会被veth0网口丢弃

image-20200922153244708

实验效果:发现是可以ping通的。

image-20200922153848886

image-20200922153923408



任务四

之前都是通过硬编码的方式来实现数据包重定向实现内部接口之间的数据包转发。

bpf_fib_lookup

辅助函数能够帮助XDP和TC程序访问内核中的路由表,并返回要将数据包转发到的接口的网络索引值。效果如下:

image-20200922161319822

image-20200922161335017



版权声明:本文为hbhgyu原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。