共计 3894 个字符,预计需要花费 10 分钟才能阅读完成。
eBPF(Extended Berkeley Packet Filter)的外围是驻留在 kernel 的高效虚拟机。最后的目标是高效网络过滤框架,前身是 BPF,所以咱们先理解下 BPF
BPF
框架
上图是 BPF 的地位和框架,须要留神的是 kernel 和 user 应用了 buffer 来传输数据,防止频繁上下文切换。BPF 虚拟机十分简洁,由累加器、索引寄存器、存储、隐式程序计数器组成。
示例
接下来咱们看一下示例,过滤所有 ip 报文,能够应用 tcpdump -d ip 查看:
(000) ldh [12] // 链路层第 12 字节的数据加载到寄存器,ethertype 字段
(001) jeq #0x800 jt 2 jf 3 // 比拟寄存器的 ethertype 字段是否为 IP 类型,true 跳到 2,false 跳到 3
(002) ret #65535 // 返回 true
(003) ret #0 // 返回 0
BPF 只应用了 4 条虚拟机指令,就能提供十分有用的 IP 报文过滤。
tcpdump -d tcp
(000) ldh [12] // 链路层第 12 字节的数据 (2 字节) 加载到寄存器,ethertype 字段
(001) jeq #0x86dd jt 2 jf 7 // 判断是否为 IPv6 类型,true 跳到 2,false 跳到 7
(002) ldb [20] // 链路层第 20 字节的数据 (1 字节) 加载到寄存器,IPv6 的 next header 字段
(003) jeq #0x6 jt 10 jf 4 // 判断是否为 TCP,true 跳到 10,false 跳到 4
(004) jeq #0x2c jt 5 jf 11 // 可能是 IPv6 分片标记,true 跳到 5,false 跳到 11
(005) ldb [54] // 我编不上来了...
(006) jeq #0x6 jt 10 jf 11 // 判断是否为 TCP,true 跳到 10,false 跳到 11
(007) jeq #0x800 jt 8 jf 11 // 判断是否为 IP 类型,true 跳到 8,false 跳到 11
(008) ldb [23] // 链路层第 23 字节的数据 (1 字节) 加载到寄存器,next proto 字段
(009) jeq #0x6 jt 10 jf 11 // 判断是否为 TCP,true 跳到 10,false 跳到 11
(010) ret #65535 // 返回 true
(011) ret #0 // 返回 0
以上是 freebsd 的 BPF,Linux 中应该不叫这个,叫 LSF,本人看吧。
eBPF
eBPF 初识
Linux kernel 3.18 版本开始蕴含了 eBPF,绝对于 BPF 做了一些重要改良,首先是效率,这要归功于 JIB 编译 eBPF 代码;其次是利用范畴,从网络报文扩大到个别事件处理;最初不再应用 socket,应用 map 进行高效的数据存储。
依据以上的改良,内核开发人员在不到两年半的事件,做出了包含网络监控、限速和系统监控。
目前 eBPF 能够合成为三个过程:
- 以字节码的模式创立 eBPF 的程序。编写 C 代码,将 LLVM 编译成驻留在 ELF 文件中的 eBPF 字节码。
- 将程序加载到内核中,并创立必要的 eBPF-maps。eBPF 具备用作 socket filter,kprobe 处理器,流量管制调度,流量管制操作,tracepoint 解决,eXpress Data
Path(XDP),性能监测,cgroup 限度,轻量级 tunnel 的程序类型。 - 将加载的程序 attach 到零碎中。依据不同的程序类型 attach 到不同的内核零碎中。程序运行的时候,启动状态并且开始过滤,剖析或者捕捉信息。
2016 年 10 月的 NetDev 1.2 大会上,Netronome 的 Jakub Kicinski 和 Nic Viljoen 发表了题目为“eBPF / XDP 硬件卸载到 SmartNIC”。Nic Viljoen 在其中介绍了 Netronome SmartNIC 上每个 FPC 每秒达到 300 万个数据包,每个 SmartNIC 有 72 到 120 个 FPC,可能最大反对 eBPF 吞吐量 4.3 Tbps!(实践上)
eBPF 入口
接下来咱们以内核版本 4.14 版本为例进行查看。
bpf 的零碎调用
kernel/bpf/syscall.c
bpf 零碎调用的头文件
include/uapi/linux/bpf.h
入口函数
int bpf(int cmd, union bpf_attr *attr, unsigned int size);
从 kernel/bpf/syscall.c
中的宏定义开展。
eBPF 命令
Linux 零碎的 BPF 零碎调用有 10 个命令,其中 man page 中列出了 6 个:
BPF_PROG_LOAD
验证并且加载 eBPF 程序,返回一个新的文件描述符。BPF_MAP_CREATE
创立 map 并且返回指向 map 的文件描述符BPF_MAP_LOOKUP_ELEM
通过 key 从指定的 map 中查找元素,并且返回 value 值BPF_MAP_UPDATE_ELEM
在指定的 map 中创立或者更新元素(key/value 配对)BPF_MAP_DELETE_ELEM
通过 key 从指定的 map 中找到元素并且删除BPF_MAP_GET_NEXT_KEY
通过 key 从指定的 map 中找到元素,并且返回下个 key 值
以上的命令能够分为两大类,加载 eBPF 程序和 eBPF-maps 操作。eBPF-maps 操作有很大的自主性,用于创立 eBPF-maps,从中查找、更新和删除元素,遍历 eBPF-maps(BPF_MAP_GET_NEXT_KEY)
接下来列一下剩下的 4 个命令,在代码中能够看到:
- BPF_OBJ_PIN 4.4 版本新加的,属于持久性 eBPF。有了这个,eBPF-maps 和 eBPF 程序能够放入 /sys/fs/bpf
- BPF_OBJ_GET 同上,在这之前,没有工具能创立 eBPF 程序,并且完结,因为会毁坏 filter,而文件系统能够在创立他们的程序退出后仍然保留 eBPF-maps 和 eBPF 程序
- BPF_PROG_ATTACH 4.10 版本中增加的,将 eBPF 程序 attach 到 cgroup,这样实用于 container
- BPF_PROG_DETACH 同上。
eBPF-map 类型
Linux 零碎的 BPF 零碎调用有 10 个命令,其中 man page 中列出了 6 个:
BPF_PROG_LOAD
验证并且加载 eBPF 程序,返回一个新的文件描述符。
BPF_MAP_CREATE
创立 map 并且返回指向 map 的文件描述符
BPF_MAP_LOOKUP_ELEM
通过 key 从指定的 map 中查找元素,并且返回 value 值
BPF_MAP_UPDATE_ELEM
在指定的 map 中创立或者更新元素(key/value 配对)
BPF_MAP_DELETE_ELEM
通过 key 从指定的 map 中找到元素并且删除
BPF_MAP_GET_NEXT_KEY
通过 key 从指定的 map 中找到元素,并且返回下个 key 值
以上的命令能够分为两大类,加载 eBPF 程序和 eBPF-maps 操作。eBPF-maps 操作有很大 的自主性,用于创立 eBPF-maps,从中查找、更新和删除元素,遍历 eBPF-maps(BPF_MAP_GET_NEXT_KEY)
接下来列一下剩下的 4 个命令,在代码中能够看到:
- BPF_OBJ_PIN 4.4 版本新加的,属于持久性 eBPF。有了这个,eBPF-maps 和 eBPF 程序能够放入 /sys/fs/bpf
- BPF_OBJ_GET 同上,在这之前,没有工具能创立 eBPF 程序,并且完结,因为会毁坏 filter,而文件系统能够在创立他们的程序退出后仍然保留 eBPF-maps 和 eBPF 程序
- BPF_PROG_ATTACH 4.10 版本中增加的,将 eBPF 程序 attach 到 cgroup,这样实用于 container
- BPF_PROG_DETACH 同上。
eBPF-map 类型
BPF_MAP_TYPE_UNSPEC
BPF_MAP_TYPE_HASH
eBPF-maps hash 表,是次要用的前两种形式之一BPF_MAP_TYPE_ARRAY
和下面相似,除了索引像数组一样BPF_MAP_TYPE_PROG_ARRAY
将加载的 eBPF 程序的文件描述符保留其值,罕用的是应用数字辨认不同的 eBPF 程序类型,也能够从一个给定 key 值的 eBPF-maps 找到 eBPF 程序,并且跳转到程序中去BPF_MAP_TYPE_PERF_EVENT_ARRAY
配合 perf 工具,CPU 性能计数器,tracepoints,kprobes 和 uprobes。能够查看门路 samples/bpf/ 下的 tracex6_kern.c,tracex6_user.c,tracex6_kern.c,tracex6_user.cBPF_MAP_TYPE_PERCPU_HASH
和 BPF_MAP_TYPE_HASH 一样,除了是为每个 CPU 创立BPF_MAP_TYPE_PERCPU_ARRAY 和 BPF_MAP_TYPE_ARRAY
一样,除了是为每个 CPU 创立
BPF_MAP_TYPE_STACK_TRACE
用于存储 stack-traces
BPF_MAP_TYPE_CGROUP_ARRAY
查看 skb 的 croup 归属
BPF_MAP_TYPE_LRU_HASH
BPF_MAP_TYPE_LRU_PERCPU_HASH
BPF_MAP_TYPE_LPM_TRIE
最业余的用法,LPM(Longest Prefix Match)的一种 trieBPF_MAP_TYPE_ARRAY_OF_MAPS
可能是针对每个 port 的BPF_MAP_TYPE_HASH_OF_MAPS
可能是针对每个 port 的BPF_MAP_TYPE_DEVMAP
可能是定向报文到 dev 的BPF_MAP_TYPE_SOCKMAP
可能是连贯 socket 的