浅入浅出eBPF

前言

最近因为一些原因开始学习eBPF,后续也将持续学习eBPF的一些具体应用。

BPF发展史

3

BPF介绍

BPF(伯克利包过滤器),也称为cBPF,在1992年提出,目的是为了提供一种过滤包的方法,并且要避免从内核空间到用户空间的无用的数据包复制行为。最初,BPF是在BSD内核实现的, 后来,由于其出色的设计思想,其他操作系统也将其引入, 包括Linux。

BPF架构如下图所示,从图中可以看到,BPF是作为内核报文传输路径的一个旁路存在,当报文到达内核驱动程序后,内核在将报文上送协议栈的同时,会额外将报文的副本交给BPF,之后报文会经过BPF内部逻辑的过滤。

1

eBPF介绍

eBPF是扩展的BPF,2014 年初,Alexei Starovoitov 实现了 eBPF(extended Berkeley Packet Filter)。经过重新设计,eBPF 演进为一个通用执行引擎,可基于此开发性能分析工具、软件定义网络等诸多场景。eBPF 最早出现在 3.18 内核中,此后原来的 BPF 就被称为经典 BPF,缩写 cBPF(classic BPF),cBPF 现在已经基本废弃。现在,Linux 内核只运行 eBPF,内核会将加载的 cBPF 字节码透明地转换成 eBPF 再执行。

从eBPF官网摘录下段文字说明

eBPF is a revolutionary technology with origins in the Linux kernel that can run sandboxed programs in an operating system kernel. It is used to safely and efficiently extend the capabilities of the kernel without requiring to change kernel source code or load kernel modules.(一项革新性技术!!!有苹果发布会内味。起源于Linux内核,可以在操作系统内核中运行沙箱程序,被用来安全的扩展内核功能,不用去更改内核源码或加载内核模块)

Historically, the operating system has always been an ideal place to implement observability, security, and networking functionality due to the kernel’s privileged ability to oversee and control the entire system. At the same time, an operating system kernel is hard to evolve due to its central role and high requirement towards stability and security. The rate of innovation at the operating system level has thus traditionally been lower compared to functionality implemented outside of the operating system.(操作系统一直是实现可观测性、安全性和网络功能的最佳场所,因为内核具有监视和控制整个系统的权限。同时,由于其核心作用和对于稳定性和安全性的高要求,使得它很难进化。因此,和在操作系统之外实现的功能相比,操作系统级别的创新率就会偏低)

eBPF changes this formula fundamentally. By allowing to run sandboxed programs within the operating system, application developers can run eBPF programs to add additional capabilities to the operating system at runtime. The operating system then guarantees safety and execution efficiency as if natively compiled with the aid of a Just-In-Time (JIT) compiler and verification engine. This has led to a wave of eBPF-based projects covering a wide array of use cases, including next-generation networking, observability, and security functionality.(eBPF从根本上改变了这个功能,通过允许在操作系统内运行沙箱程序,应用开发者可以通过运行eBPF程序在操作系统运行时添加额外功能。操作系统会保证安全性和执行效率,就像在JIT编译器和验证器的帮助下进行本机编译一样。接着就出现了一系列基于eBPF的项目,例如下一代网络、可观察性和安全功能等)

Today, eBPF is used extensively to drive a wide variety of use cases: Providing high-performance networking and load-balancing in modern data centers and cloud native environments, extracting fine-grained security observability data at low overhead, helping application developers trace applications, providing insights for performance troubleshooting, preventive application and container runtime security enforcement, and much more. The possibilities are endless, and the innovation that eBPF is unlocked has only just begun.(今天,eBPF被用于驱动各种各样的用例,例如在数据中心和云本机环境提供高性能网络和负载均衡、以较低的开销提取细粒度安全可观测性数据、帮助应用程序开发人员跟踪应用程序、为性能故障排除提供一些方法,预防应用程序和容器运行时的安全实施等等,eBPF有无限可能,eBPF才刚刚开始)

eBPF对比cBPF

eBPF相对于cBPF的增强如下:

  • 处理器原生指令集建模,因此更接近底层处理器架构, 性能相比cBPF提升4倍
  • 指令集从33个扩展到了114多个,依然保持了足够的简洁
  • 寄存器从2个32位寄存器扩展到了11个 64 位的寄存器 (其中1个只读的栈指针)
  • 引入 bpf_call 指令和寄存器传参约定,实现零(额外)开销内核函数调用
  • 虚拟机的最大栈空间是 512 字节(cBPF 为 16 个字节)
  • 引入了 map 结构,用于用户空间程序与内核中的 eBPF 程序数据交换
  • 最大指令数初期为 4096,现在已经将这个限制放大到了100万条

eBPF工作机制

2

eBPF分为用户空间和内核空间,用户空间和内核空间的交互有两种方式

  • BPF map:用于将内核中实现的统计摘要信息(比如测量延迟、堆栈信息)等回传至用户空间
  • perf-event:用于将内核采集的事件实时发送至用户空间,用户空间程序实时读取分析

eBPF的工作逻辑是

  • BPF程序通过LLVM/Clang编译成eBPF定义的字节码prog.bpf
  • 通过bpf系统调用将bpf字节码指令传入内核
  • 经过验证器检验字节码的安全性
    • 加载eBPF程序的进程具有所需的权限,除非启用了非特权eBPF,否则只有特权进程才能加载eBPF程序
    • 程序不会崩溃或以其它方式损坏系统
    • 程序始终可以运行完成
  • 在确认字节码安全后将其加载对应的内核模块执行,在BPF虚拟机中会判断是否开启JIT(即时编译),如果开启了JIT,则会通过JIT解释器将程序字节码转为特定的机器码执行,如果没有开启JIT,则通过内核解释器执行

eBPF观测技术相关的程序类型有kprobesuporbestracepointperf_event

  • kprobes:实现内核中动态跟踪。kprobes可以跟踪到Linux内核中的函数入口或返回点,但是不是稳定ABI接口,可能会因为内核版本变化导致,导致跟踪失效。理论上可以跟踪到所有导出的符号/proc/kallsyms
  • uprobes:用户级别的动态跟踪。与kprobes类似,只是跟踪的函数为用户程序中的函数。
  • tracepoints:内核中静态跟踪。tracepoints是内核开发人员维护的跟踪点,能够提供稳定的ABI接口,但是由于是研发人员维护,数量和场景可能受限。
  • perf_events:定时采样和PMC。

eBPF使用场景

  • 系统性能监控/分析工具:能够实现性能监控工具、分析工具等常用的系统分析工具,比如 sysstate 工具集,里面提供了 vmstate,pidstat 等多种工具,一些常用的 top、netstat(netstat 可被 SS 替换掉),uptime、iostat 等这些工具多数都是从 /proc、/sys、/dev 中获取的会对系统产生一定的开销,不适合频繁的调用。比如在使用 top 的时候通过 cpu 排序可以看到 top cpu 占用也是挺高的,使用 eBPF 可以在开销相对小的情况下获取系统信息,定时将 eBPF 采集的数据 copy 到用户态,然后将其发送到分析监控平台。
  • 用户程序活体分析:做用户程序活体分析,比如 openresty 中 lua 火焰图绘制,程序内存使用监控,cdn 服务异常请求分析,程序运行状态的查看,这些操作都可以在程序无感的情况下做到,可以有效提供服务质量。
  • 防御攻击:比如 DDoS 攻击,DDoS 攻击主要是在第七层、第三层以及第四层。第七层的攻击如 http 攻击,需要应用服务这边处理。第四层攻击,如 tcp syn 可以通过 iptable 拒绝异常的 ip,当然前提是能发现以及难点是如何区分正常流量和攻击流量,简单的防攻击会导致一些误伤,另外 tcp syn 也可以通过内核参数保护应用服务。第 3 层攻击,如 icmp。对于攻击一般会通过一些特殊的途径去发现攻击,而攻击的防御则可以通过 XDP 直接在网络包未到网络栈之前就处理掉,性能非常的优秀。
  • 流控:可以控制网络传输速率,比如 tc。
  • 替换 iptable:在 k8s 中iptable的规则往往会相当庞大,而iptable规则越多,性能也越差,使用eBPF就可以解决
  • 服务调优:在cdn服务中难免会出现一些指标突刺的情况,这种突刺拉高整体的指标,对于这种突刺时常会因为找不到切入点而无从下手,eBPF存在这种潜力能帮助分析解决该问题,当eBPF发现网络抖动,会主动采集当时应用的运行状态。

eBPF hooks

eBPF hooks即eBPF钩子,指的是在内核中哪些地方可以加载eBPF程序,在目前的Linux内核中已经有近10中钩子

1
2
3
4
5
6
7
8
9
kernel functions(kprobes)
userspace functions(uprobes)
system calls
fentry/fexit
Tracepoints
network devices(tc/xdp)
network routes
TCP congestion algorithms
sockets(data level)

eBPF Map

在eBPF中可以利用map在eBPF程序调用之间保存状态信息,也可以利用map在用户态程序和内核之间共享数据等。内核提供了一个系统调用bpf(),以让用户态程序可以根据使用场景来创建合适的map。这个系统调用会返回一个关联了这个map对象的文件描述符,后续用户态程序可以用这个文件描述符来对相应的map对象进行一些操作,如查询、更新和删除,这部分的接口在tools/lib/bpf/bpf.h中定义了。关于这个bpf()系统调用以及map操作接口的详细信息,可以参考相关资料,其中bpf()系统调用相关的信息可以在man page中找到,而map操作相关的接口可以在 tools/lib/bpf/bpf.h 中看到具体的实现。

eBPF支持的map类型如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
BPF_MAP_TYPE_HASH:哈希表
BPF_MAP_TYPE_ARRAY:数组映射,已针对快速查找速度进行了优化,通常用于计数器
BPF_MAP_TYPE_PROG_ARRAY:对应eBPF程序的文件描述符数组;用于实现跳转表和子程序以处理特定的数据包协议
BPF_MAP_TYPE_PERCPU_ARRAY:每个CPU的阵列,用于实现延迟的直方图
BPF_MAP_TYPE_PERF_EVENT_ARRAY:存储指向struct perf_event的指针,用于读取和存储perf事件计数器
BPF_MAP_TYPE_CGROUP_ARRAY:存储指向控制组的指针
BPF_MAP_TYPE_PERCPU_HASH:每个CPU的哈希表
BPF_MAP_TYPE_LRU_HASH:仅保留最近使用项目的哈希表
BPF_MAP_TYPE_LRU_PERCPU_HASH:每个CPU的哈希表,仅保留最近使用的项目
BPF_MAP_TYPE_LPM_TRIE:最长前缀匹配树,适用于将IP地址匹配到某个范围
BPF_MAP_TYPE_STACK_TRACE:存储堆栈跟踪
BPF_MAP_TYPE_ARRAY_OF_MAPS:地图中地图数据结构
BPF_MAP_TYPE_HASH_OF_MAPS:地图中地图数据结构
BPF_MAP_TYPE_DEVICE_MAP:用于存储和查找网络设备引用
BPF_MAP_TYPE_SOCKET_MAP:存储和查找套接字,并允许使用BPF辅助函数进行套接字重定向

eBPF Helper Function

eBPF程序不能调用任意内核函数。如果允许这样做,会将eBPF程序绑定到特定的内核版本,并且会使程序的兼容性复杂化。相反,eBPF程序可以对helper函数进行函数调用,helper函数是内核提供的一种稳定的API。

5

一些可用于辅助调用的例子有

  • 生成随机数
  • 获取当前时间和日期
  • eBPF map访问
  • 获取进程/cgroup上下文
  • 操作网络数据包和转发逻辑

BCC

bcc介绍

源码地址:https://github.com/iovisor/bcc

BCC工具全称BPF Compiler Collection (BCC),是一个很强大的库,强大的内核分析工具eBPF就是基于bcc开发的,利用这个库可以从底层获取操作系统性能信息,网络性能信息等许多与内核交互的信息。bcc使得bpf程序更容易被书写,bcc使用 Python和Lua,虽然核心依旧是一部分C语言代码(BPF C代码)。但是我们很快就可以体验了,这比手动安装 C 语言依赖、编译、插入内核要方便的多。

bcc-tools安装
1
sudo yum -y install bcc-tools
HelloWorld

代码如下

1
2
3
from bcc import BPF

BPF(text='int kprobe__sys_clone(void *ctx){bpf_trace_printk("Hello,World!\\n"); return 0;}').trace_print()

执行如下

4

分析如下

  • text定义了一个嵌入的用C语言写的BPF程序

  • kprobe__sys_clone()是一个通过内核探针(kprobe)进行内核动态跟踪的快捷方式。如果一个C函数名开头为kprobe__,则后面部分实际为设备的内核函数名,这里是sys_clone()

  • bpf_trace_printk()用于printf()trace_pipe。一般用来快速调试

  • return 0用来关闭凭证

  • .trace_print(),一个bcc实例会通过这个读取trace_pipe并打印

利用eBPF提升socket性能

实验介绍

本实验主要是利用ebpf sockmap/redirection来提升socket的性能。sockmap是 eBPF 提供的一个特殊的eBPF MAP类型,主要用于socket redirection,在 socket redirection中,socket被添加到sockmap中并由key(主要是四元组)引用,然后该 socket 在调用bpf_sockmap_redirect()时进行重定向。对于本地通信方式而言,这样可以绕过整个 TCP/IP 协议栈,直接将数据发送到 socket 对端,从而提高性能。

实验代码

https://github.com/cyralinc/os-eBPF

实验环境
  • Ubuntu Linux 18.04 with 5.3.0-40-generic
实验准备
安装相应包
  • sudo 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
  • sudo apt-get install iproute2
  • sudo apt install libbfd-dev libcap-dev zlib1g-dev libelf-dev libssl-dev
修改apt源

https://blog.csdn.net/weixin_44143222/article/details/88592193

1
2
apt-get source linux-image-$(uname -r)
apt-get source linux-image-unsigned-$(uname -r)
或直接下载对应源码
编译 bpftool 工具
1
2
3
cd linux-5.3/tools/bpf/bpftool
make
sudo make install
编译bpf字节码
1
root@ubuntu:~/os-eBPF/sockredir$ clang -O2 -g -target bpf -I /usr/include/linux/ -I /usr/src/linux-headers-5.3.0-40/include/ -c bpf_sockops_v4.c  -o bpf_sockops_v4.o
加载bpf字节码
1
2
root@ubuntu:~/os-eBPF/sockredir$ sudo bpftool prog load bpf_sockops_v4.o "/sys/fs/bpf/bpf_sockops"
root@ubuntu:~/os-eBPF/sockredir$ sudo bpftool cgroup attach "/sys/fs/cgroup/unified/" sock_ops pinned "/sys/fs/bpf/bpf_sockops"
查看系统中已经加载的所有 BPF 程序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
root@ubuntu:~/os-eBPF/sockredir$ sudo bpftool prog show
2: cgroup_skb tag 7be49e3934a125ba gpl
loaded_at 2022-01-26T08:39:44+0000 uid 0
xlated 296B jited 200B memlock 4096B map_ids 2,3
3: cgroup_skb tag 2a142ef67aaad174 gpl
loaded_at 2022-01-26T08:39:44+0000 uid 0
xlated 296B jited 200B memlock 4096B map_ids 2,3
4: cgroup_skb tag 7be49e3934a125ba gpl
loaded_at 2022-01-26T08:39:44+0000 uid 0
xlated 296B jited 200B memlock 4096B map_ids 4,5
5: cgroup_skb tag 2a142ef67aaad174 gpl
loaded_at 2022-01-26T08:39:44+0000 uid 0
xlated 296B jited 200B memlock 4096B map_ids 4,5
6: cgroup_skb tag 7be49e3934a125ba gpl
loaded_at 2022-01-26T08:41:20+0000 uid 0
xlated 296B jited 200B memlock 4096B map_ids 6,7
7: cgroup_skb tag 2a142ef67aaad174 gpl
loaded_at 2022-01-26T08:41:20+0000 uid 0
xlated 296B jited 200B memlock 4096B map_ids 6,7
18: sock_ops name bpf_sockops_v4 tag 8fb64d4d0f48a1a4 gpl
loaded_at 2022-01-26T08:59:51+0000 uid 0
xlated 688B jited 399B memlock 4096B map_ids 14
查看系统中所有的 map
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
root@ubuntu:~/os-eBPF/sockredir$ sudo bpftool map show
2: lpm_trie flags 0x1
key 8B value 8B max_entries 1 memlock 4096B
3: lpm_trie flags 0x1
key 20B value 8B max_entries 1 memlock 4096B
4: lpm_trie flags 0x1
key 8B value 8B max_entries 1 memlock 4096B
5: lpm_trie flags 0x1
key 20B value 8B max_entries 1 memlock 4096B
6: lpm_trie flags 0x1
key 8B value 8B max_entries 1 memlock 4096B
7: lpm_trie flags 0x1
key 20B value 8B max_entries 1 memlock 4096B
14: sockhash name sock_ops_map flags 0x0
key 24B value 4B max_entries 65535 memlock 0B
查看map的详情
1
2
3
4
5
6
7
8
9
10
11
root@ubuntu:~$ sudo bpftool -p map show id 14
{
"id": 14,
"type": "sockhash",
"name": "sock_ops_map",
"flags": 0,
"bytes_key": 24,
"bytes_value": 4,
"max_entries": 65535,
"bytes_memlock": 0
}
打印map中的内容
1
2
3
4
5
6
7
8
9
root@ubuntu:~$ sudo bpftool -p map dump id 14
[{
"key": ["0xc0","0xa8","0x13","0x55","0x0a","0x34","0x23","0xa1","0x01","0x00","0x00","0x00","0x00","0x00","0x00","0x00","0x00","0x16","0x00","0x00","0xc4","0x08","0x00","0x00"
],
"value": {
"error": "Operation not supported"
}
}
]
具体测试步骤
执行load.sh脚本

这里需要根据内核版本对应修改一下load.sh中的代码,如下图所示

6

执行结果如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
root@ubuntu:~/os-eBPF/sockredir$ ./load.sh 
+ set -e
+ sudo mount -t bpf bpf /sys/fs/bpf/
+ clang -O2 -g -target bpf -I/usr/include/linux/ -I/usr/src/linux-headers-5.3.0-40/include/ -c bpf_sockops_v4.c -o bpf_sockops_v4.o
+ sudo bpftool prog load bpf_sockops_v4.o /sys/fs/bpf/bpf_sockops
+ sudo bpftool cgroup attach /sys/fs/cgroup/unified/ sock_ops pinned /sys/fs/bpf/bpf_sockops
++ sudo bpftool prog show pinned /sys/fs/bpf/bpf_sockops
++ grep -o -E 'map_ids [0-9]+'
++ cut -d ' ' -f2-
+ MAP_ID=42
+ sudo bpftool map pin id 42 /sys/fs/bpf/sock_ops_map
+ clang -O2 -g -Wall -target bpf -I/usr/include/linux/ -I/usr/src/linux-headers-5.3.0-40/include/ -c bpf_tcpip_bypass.c -o bpf_tcpip_bypass.o
+ sudo bpftool prog load bpf_tcpip_bypass.o /sys/fs/bpf/bpf_tcpip_bypass map name sock_ops_map pinned /sys/fs/bpf/sock_ops_map
+ sudo bpftool prog attach pinned /sys/fs/bpf/bpf_tcpip_bypass msg_verdict pinned /sys/fs/bpf/sock_ops_map
确认BPF程序已经被加载进内核
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
root@ubuntu:~/os-eBPF/sockredir$ sudo bpftool prog show
18: sock_ops name bpf_sockops_v4 tag 8fb64d4d0f48a1a4 gpl
loaded_at 2022-01-26T08:59:51+0000 uid 0
xlated 688B jited 399B memlock 4096B map_ids 14
41: cgroup_skb tag 7be49e3934a125ba gpl
loaded_at 2022-01-27T01:11:55+0000 uid 0
xlated 296B jited 200B memlock 4096B map_ids 35,36
42: cgroup_skb tag 2a142ef67aaad174 gpl
loaded_at 2022-01-27T01:11:55+0000 uid 0
xlated 296B jited 200B memlock 4096B map_ids 35,36
43: cgroup_skb tag 7be49e3934a125ba gpl
loaded_at 2022-01-27T01:11:55+0000 uid 0
xlated 296B jited 200B memlock 4096B map_ids 37,38
44: cgroup_skb tag 2a142ef67aaad174 gpl
loaded_at 2022-01-27T01:11:55+0000 uid 0
xlated 296B jited 200B memlock 4096B map_ids 37,38
45: cgroup_skb tag 7be49e3934a125ba gpl
loaded_at 2022-01-27T01:11:55+0000 uid 0
xlated 296B jited 200B memlock 4096B map_ids 39,40
46: cgroup_skb tag 2a142ef67aaad174 gpl
loaded_at 2022-01-27T01:11:55+0000 uid 0
xlated 296B jited 200B memlock 4096B map_ids 39,40
50: sock_ops name bpf_sockops_v4 tag 8fb64d4d0f48a1a4 gpl
loaded_at 2022-01-27T01:32:15+0000 uid 0
xlated 688B jited 399B memlock 4096B map_ids 42
54: sk_msg name bpf_tcpip_bypas tag 550f6d3cfcae2157 gpl
loaded_at 2022-01-27T01:32:16+0000 uid 0
xlated 224B jited 151B memlock 4096B map_ids 42
查看固定在文件系统上的SOCKHASH映射
1
2
3
4
5
6
7
root@ubuntu:~/os-eBPF/sockredir$ sudo tree /sys/fs/bpf/
/sys/fs/bpf/
├── bpf_sockops
├── bpf_tcpip_bypass
└── sock_ops_map

0 directories, 3 files
1
2
3
root@ubuntu:~/os-eBPF/sockredir$ sudo bpftool map show id 42 -f
42: sockhash name sock_ops_map flags 0x0
key 24B value 4B max_entries 65535 memlock 0B
确认应用程序绕过TCP/IP协议栈

首先打开日志追踪

root模式下执行如下命令

1
root@ubuntu:~/os-eBPF/sockredir# echo 1 > /sys/kernel/debug/tracing/tracing_on

接着在shell中对内核实时流跟踪文件trace_pipe进行cat查询,用来监视通过eBPF的TCP通信

1
2
3
4
5
root@ubuntu:~/os-eBPF/sockredir# cat /sys/kernel/debug/tracing/trace_pipe
<idle>-0 [001] ..s. 30406.252054: 0: <<< ipv4 op = 4, port 47750 --> 443
<idle>-0 [001] ..s. 32211.552998: 0: <<< ipv4 op = 4, port 46724 --> 443
<idle>-0 [001] ..s. 44205.364961: 0: <<< ipv4 op = 4, port 36448 --> 443
<idle>-0 [000] ..s. 57704.968149: 0: <<< ipv4 op = 4, port 60816 --> 443

使用socat生成的TCP监听器模拟echo服务器,并使用nc发送连接请求

1
2
root@ubuntu:~$ sudo socat TCP4-LISTEN:9999,fork exec:cat
root@ubuntu:~$ nc localhost 9999

随后我们就可以在内核追踪管道中看到在eBPF程序打印的日志

1
2
3
4
root@ubuntu:~/os-eBPF/sockredir# cat /sys/kernel/debug/tracing/trace_pipe
<idle>-0 [003] ..s. 61937.626701: 0: <<< ipv4 op = 5, port 22 --> 51324
nc-4486 [001] .... 61949.838226: 0: <<< ipv4 op = 4, port 43062 --> 9999
nc-4486 [001] .Ns1 61949.838279: 0: <<< ipv4 op = 5, port 9999 --> 43062
代码步骤梳理
  • bpf_sockops_v4.c
    • 监听socket事件,当事件触发的时候执行
    • 提取 socket 信息,并以 key & value 形式存储到 sockmap
  • bpf_tcpip_bypass.c
    • 拦截所有的 sendmsg 系统调用,从消息中提取 key
    • 根据key查询sockmap,找到这个socket的对端,然后绕过 TCP/IP 协议栈,将数据重定向
网络延迟测试

使用netperf命令,执行时长参数为60秒的各种请求和响应消息大小,进行延迟测量(分别采用p50、p90 和 p99,其中P50表示中位数。P90表示包含90%的值。P99表示包含99%的值。):

1
2
root@ubuntu:~$ netserver -p 1000
Unable to start netserver with 'IN(6)ADDR_ANY' port '1000' and family AF_UNSPEC

这里是因为端口被占用的问题,我们换一个端口即可

1
2
root@ubuntu:~$ sudo netserver -p 9998
Starting netserver with host 'IN(6)ADDR_ANY' port '9998' and family AF_UNSPEC

执行nperf_latency.sh

1
root@ubuntu:~/os-eBPF/sockredir# ./nperf_latency.sh

查看结果

其中第一行和第三行是原生TCP的网络延迟,第二行和第四行是eBPF重定向之后的网络延迟

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
root@ubuntu:~/os-eBPF/sockredir$ cat result_lat.txt 
Req/Resp size: 64 128
45,89,117
17,51,69
46,89,125
26,48,70

Req/Resp size: 64 256
75,101,129
18,48,71
65,101,134
23,58,81

Req/Resp size: 64 512
52,103,139
20,60,85
58,105,143
16,57,77

Req/Resp size: 64 1024
64,109,149
14,58,79
99,113,152
22,63,86

Req/Resp size: 64 2048
79,109,148
23,64,86
73,105,139
25,63,87

Req/Resp size: 64 4096
88,114,144
19,62,81
51,107,144
22,65,90

Req/Resp size: 64 8192
53,110,149
24,69,93
53,114,148

Req/Resp size: 64 16384
58,118,156
98,126,159

Req/Resp size: 64 32768
48,124,157
18,45,104
44,125,158
19,83,111
网络事务测试

使用netperf命令来测试60秒运行的各种请求和响应消息大小的事务率:

1
root@ubuntu:~/os-eBPF/sockredir$ ./nperf_trans.sh

查看结果

第一行为原生TCP的事务率,第二行是eBPF重定向之后的事务率

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
Req/Resp size: 64 128
12.8764
37.7448

Req/Resp size: 64 256
15.4409
26.7399

Req/Resp size: 64 512
19.6885
51.1781

Req/Resp size: 64 1024
19.7911
54.3722

Req/Resp size: 64 2048
16.2438
48.3493

Req/Resp size: 64 4096
17.0712
39.6384

Req/Resp size: 64 8192
19.775

Req/Resp size: 64 16384
14.8864
26.6844

Req/Resp size: 64 32768
12.8528
42.2359
网络吞吐测试

使用netserver服务端和netperf客户端进行60秒的各种发送消息大小的吞吐量测试:

1
root@ubuntu:~/os-eBPF/sockredir$ ./nperf_thruput.sh

查看结果

第一行为原生TCP的吞吐量,第二行是eBPF重定向之后的吞吐量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
30 tx=256 rx=256
0.3601
0.66732
60 tx=256 rx=256
0.36194
0.66343

30 tx=512 rx=512
0.70011
1.35457
60 tx=512 rx=512
0.73094
1.26444

30 tx=1024 rx=1024
1.16659
2.3709
60 tx=1024 rx=1024
1.05398
2.27725

30 tx=2048 rx=2048
2.40606
4.071
60 tx=2048 rx=2048
1.91763
4.04238

30 tx=3072 rx=3072
3.34056
5.38908
60 tx=3072 rx=3072
3.41499
5.47481
遇到的一些问题
编译bpftool工具出错

/tools/bpf/bpftool目录下执行make时会自动检查系统特征,而对于libbfd库,linux内核为4.x的版本是检测不到的。在5.3版本下会显示on

编译bpf字节码出错
1
root@ubuntu:~/os-eBPF/sockredir$ clang -O2 -g -target bpf -I /usr/include/linux/ -I /usr/src/linux-headers-4.18.0-13/include/ -c bpf_sockops_v4.c  -o bpf_sockops_v4.o

具体表现在clang编译的时候会报错use of undeclared identifier BPF_XXX,这里需要注意一下,对于bpf中的一些函数也有内核版本的限制,具体的版本可以参考如下链接

BPF Features by Linux Kernel Version

加载bpf字节码出错
1
2
3
root@ubuntu:~/os-eBPF/sockredir$ ~/linux-4.18.13/tools/bpf/bpftool/./bpftool prog load bpf_sockops_v4.o /sys/fs/bpf/bpf_sockops
libbpf: Program '"sockops"' contains non-map related relo data pointing to section 5
Error: failed to load program

问题google后发现,可能是低内核版本不支持bpf程序静态全局变量的定义,

https://stackoverflow.com/questions/48653061/ebpf-global-variables-and-structs

安装指定版本的Linux内核
查询当前内核版本
1
2
root@ubuntu:~$ uname -r
4.18.0-13-generic
查询当前安装的内核镜像
1
2
3
4
5
root@ubuntu:~$ dpkg --get-selections |grep linux-image
linux-image-4.15.0-162-generic install
linux-image-4.15.0-55-generic deinstall
linux-image-4.18.0-13-generic install
linux-image-generic install
查询指定版本的Linux镜像包

这里以5.3.0-40版本内核为例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
root@ubuntu:~$ apt-cache search linux| grep 5.3.0-40
linux-buildinfo-5.3.0-40-generic - Linux kernel buildinfo for version 5.3.0 on 64 bit x86 SMP
linux-buildinfo-5.3.0-40-lowlatency - Linux kernel buildinfo for version 5.3.0 on 64 bit x86 SMP
linux-cloud-tools-5.3.0-40-generic - Linux kernel version specific cloud tools for version 5.3.0-40
linux-cloud-tools-5.3.0-40-lowlatency - Linux kernel version specific cloud tools for version 5.3.0-40
linux-headers-5.3.0-40 - Header files related to Linux kernel version 5.3.0
linux-headers-5.3.0-40-generic - Linux kernel headers for version 5.3.0 on 64 bit x86 SMP
linux-headers-5.3.0-40-lowlatency - Linux kernel headers for version 5.3.0 on 64 bit x86 SMP
linux-hwe-cloud-tools-5.3.0-40 - Linux kernel version specific cloud tools for version 5.3.0-40
linux-hwe-tools-5.3.0-40 - Linux kernel version specific tools for version 5.3.0-40
linux-image-5.3.0-40-generic - Signed kernel image generic
linux-image-5.3.0-40-lowlatency - Signed kernel image lowlatency
linux-image-unsigned-5.3.0-40-generic - Linux kernel image for version 5.3.0 on 64 bit x86 SMP
linux-image-unsigned-5.3.0-40-lowlatency - Linux kernel image for version 5.3.0 on 64 bit x86 SMP
linux-modules-5.3.0-40-generic - Linux kernel extra modules for version 5.3.0 on 64 bit x86 SMP
linux-modules-5.3.0-40-lowlatency - Linux kernel extra modules for version 5.3.0 on 64 bit x86 SMP
linux-modules-extra-5.3.0-40-generic - Linux kernel extra modules for version 5.3.0 on 64 bit x86 SMP
linux-tools-5.3.0-40-generic - Linux kernel version specific tools for version 5.3.0-40
linux-tools-5.3.0-40-lowlatency - Linux kernel version specific tools for version 5.3.0-40
linux-modules-nvidia-390-5.3.0-40-generic - Linux kernel nvidia modules for version 5.3.0-40
linux-modules-nvidia-390-5.3.0-40-lowlatency - Linux kernel nvidia modules for version 5.3.0-40
linux-modules-nvidia-430-5.3.0-40-generic - Linux kernel nvidia modules for generic version 5.3.0-40
linux-modules-nvidia-430-5.3.0-40-lowlatency - Linux kernel nvidia modules for lowlatency version 5.3.0-40
linux-modules-nvidia-435-5.3.0-40-generic - Linux kernel nvidia modules for generic version 5.3.0-40
linux-modules-nvidia-435-5.3.0-40-lowlatency - Linux kernel nvidia modules for lowlatency version 5.3.0-40
安装
1
sudo apt-get install linux-headers-5.3.0-40-generic linux-image-5.3.0-40-generic
重启后查询内核版本
1
2
root@ubuntu:~$ uname -r
5.3.0-40-generic

参考