Linux Socket Filtering aka Berkeley Packet Filter (BPF)¶
注意¶
此文件以前用于记录 eBPF 格式和机制,即使与套接字过滤无关。 BPF 文档 有关 eBPF 的更多详细信息。
介绍¶
Linux 套接字过滤 (LSF) 派生自 Berkeley 数据包过滤器。虽然 BSD 和 Linux 内核过滤之间存在一些明显的差异,但在 Linux 上下文中,当我们谈论 BPF 或 LSF 时,我们指的是 Linux 内核中相同的过滤机制。
BPF 允许用户空间程序将过滤器附加到任何套接字,并允许或禁止某些类型的数据通过该套接字。 LSF 完全遵循与 BSD 的 BPF 相同的过滤器代码结构,因此参考 BSD 的 bpf.4 手册页对于创建过滤器非常有用。
在 Linux 上,BPF 比在 BSD 上简单得多。 您不必担心设备或类似的东西。 您只需创建过滤器代码,通过 SO_ATTACH_FILTER 选项将其发送到内核,如果您的过滤器代码通过了内核检查,您将立即开始过滤该套接字上的数据。
您还可以通过 SO_DETACH_FILTER 选项从套接字分离过滤器。 这可能不会经常使用,因为当您关闭一个带有过滤器的套接字时,该过滤器会自动删除。 另一个不太常见的情况可能是在同一个套接字上添加不同的过滤器,而该套接字上还有一个仍在运行的过滤器:内核会负责删除旧的过滤器并将您的新过滤器放置在它的位置,假设您的过滤器通过了检查,否则如果失败,旧的过滤器将保留在该套接字上。
SO_LOCK_FILTER 选项允许锁定附加到套接字的过滤器。 设置后,无法删除或更改过滤器。 这允许一个进程设置一个套接字,附加一个过滤器,锁定它,然后放弃权限,并确保该过滤器将被保留,直到套接字关闭。
此构造的最大用户可能是 libpcap。 发出高级过滤器命令,例如 tcpdump -i em1 port 22 通过 libpcap 内部编译器,该编译器生成一个结构,该结构最终可以通过 SO_ATTACH_FILTER 加载到内核中。 tcpdump -i em1 port 22 -ddd 显示正在放置到此结构中的内容。
虽然我们只在这里谈论套接字,但 Linux 中的 BPF 在更多的地方使用。 netfilter 有 xt_bpf,内核 qdisc 层有 cls_bpf,SECCOMP-BPF (SECure COMPuting [1]),以及许多其他地方,例如团队驱动程序、PTP 代码等,都在使用 BPF。
原始 BPF 论文
Steven McCanne 和 Van Jacobson。 1993. BSD 数据包过滤器:用于用户级数据包捕获的新架构。 在 USENIX Winter 1993 Conference Proceedings on USENIX Winter 1993 Conference Proceedings (USENIX'93) 中。 USENIX Association, Berkeley, CA, USA, 2-2. [http://www.tcpdump.org/papers/bpf-usenix93.pdf]
结构¶
用户空间应用程序包括 <linux/filter.h>,其中包含以下相关结构
struct sock_filter { /* Filter block */
__u16 code; /* Actual filter code */
__u8 jt; /* Jump true */
__u8 jf; /* Jump false */
__u32 k; /* Generic multiuse field */
};
这样的结构被组装成一个 4 元组数组,其中包含一个代码、jt、jf 和 k 值。 jt 和 jf 是跳转偏移量,k 是一个通用值,用于提供的代码
struct sock_fprog { /* Required for SO_ATTACH_FILTER. */
unsigned short len; /* Number of filter blocks */
struct sock_filter __user *filter;
};
对于套接字过滤,指向此结构的指针(如后续示例所示)通过 setsockopt(2) 传递给内核。
示例¶
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <linux/if_ether.h>
/* ... */
/* From the example above: tcpdump -i em1 port 22 -dd */
struct sock_filter code[] = {
{ 0x28, 0, 0, 0x0000000c },
{ 0x15, 0, 8, 0x000086dd },
{ 0x30, 0, 0, 0x00000014 },
{ 0x15, 2, 0, 0x00000084 },
{ 0x15, 1, 0, 0x00000006 },
{ 0x15, 0, 17, 0x00000011 },
{ 0x28, 0, 0, 0x00000036 },
{ 0x15, 14, 0, 0x00000016 },
{ 0x28, 0, 0, 0x00000038 },
{ 0x15, 12, 13, 0x00000016 },
{ 0x15, 0, 12, 0x00000800 },
{ 0x30, 0, 0, 0x00000017 },
{ 0x15, 2, 0, 0x00000084 },
{ 0x15, 1, 0, 0x00000006 },
{ 0x15, 0, 8, 0x00000011 },
{ 0x28, 0, 0, 0x00000014 },
{ 0x45, 6, 0, 0x00001fff },
{ 0xb1, 0, 0, 0x0000000e },
{ 0x48, 0, 0, 0x0000000e },
{ 0x15, 2, 0, 0x00000016 },
{ 0x48, 0, 0, 0x00000010 },
{ 0x15, 0, 1, 0x00000016 },
{ 0x06, 0, 0, 0x0000ffff },
{ 0x06, 0, 0, 0x00000000 },
};
struct sock_fprog bpf = {
.len = ARRAY_SIZE(code),
.filter = code,
};
sock = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
if (sock < 0)
/* ... bail out ... */
ret = setsockopt(sock, SOL_SOCKET, SO_ATTACH_FILTER, &bpf, sizeof(bpf));
if (ret < 0)
/* ... bail out ... */
/* ... */
close(sock);
上面的示例代码附加一个 PF_PACKET 套接字的套接字过滤器,以便让所有端口 22 的 IPv4/IPv6 数据包通过。 其余的将被丢弃用于此套接字。
对 SO_DETACH_FILTER 的 setsockopt(2) 调用不需要任何参数,而用于防止过滤器分离的 SO_LOCK_FILTER 采用整数值 0 或 1。
请注意,套接字过滤器不限于 PF_PACKET 套接字,也可以在其他套接字族上使用。
系统调用摘要
setsockopt(sockfd, SOL_SOCKET, SO_ATTACH_FILTER, &val, sizeof(val));
setsockopt(sockfd, SOL_SOCKET, SO_DETACH_FILTER, &val, sizeof(val));
setsockopt(sockfd, SOL_SOCKET, SO_LOCK_FILTER, &val, sizeof(val));
通常,libpcap 将以高级语法涵盖数据包套接字上套接字过滤的大多数用例,因此作为应用程序开发人员,您应该坚持使用它。 libpcap 在所有这些之上都封装了自己的层。
除非 i) 使用/链接到 libpcap 不是一种选择,ii) 所需的 BPF 过滤器使用 libpcap 编译器不支持的 Linux 扩展,iii) 过滤器可能更复杂,并且无法使用 libpcap 编译器干净地实现,或者 iv) 特定过滤器代码的优化方式应与 libpcap 内部编译器不同;那么在这种情况下,“手动”编写这样的过滤器可能是一种选择。 例如,xt_bpf 和 cls_bpf 用户可能具有导致更复杂过滤器代码的要求,或者无法用 libpcap 表达的代码(例如,各种代码路径的不同返回代码)。 此外,BPF JIT 实现者可能希望手动编写测试用例,因此也需要对 BPF 代码的低级访问。
BPF 引擎和指令集¶
在 tools/bpf/ 下,有一个名为 bpf_asm 的小型辅助工具,可用于为上一节中提到的示例场景编写低级过滤器。 这里提到的类汇编语法已经在 bpf_asm 中实现,并将用于进一步的解释(而不是直接处理可读性较差的操作码,原理是相同的)。 该语法与 Steven McCanne 和 Van Jacobson 的 BPF 论文非常相似。
BPF 架构包含以下基本元素
元素
描述
A
32 位宽累加器
X
32 位宽 X 寄存器
M[]
16 x 32 位宽的杂项寄存器,也称为“暂存内存存储”,可从 0 到 15 寻址
一个程序,由 bpf_asm 翻译成“操作码”,是一个由以下元素组成的数组(如前所述)
op:16, jt:8, jf:8, k:32
元素 op 是一个 16 位宽的操作码,其中编码了一个特定的指令。 jt 和 jf 是两个 8 位宽的跳转目标,一个用于条件“如果为真则跳转”,另一个用于“如果为假则跳转”。 最终,元素 k 包含一个杂项参数,可以以不同的方式解释,具体取决于 op 中的给定指令。
指令集由加载、存储、分支、算术逻辑单元、杂项和返回指令组成,这些指令也以 bpf_asm 语法表示。 此表列出了所有可用的 bpf_asm 指令,以及它们在 linux/filter.h 中定义的底层操作码
指令
寻址模式
描述
ld
1, 2, 3, 4, 12
将字加载到 A 中
ldi
4
将字加载到 A 中
ldh
1, 2
将半字加载到 A 中
ldb
1, 2
将字节加载到 A 中
ldx
3, 4, 5, 12
将字加载到 X 中
ldxi
4
将字加载到 X 中
ldxb
5
将字节加载到 X 中
st
3
将 A 存储到 M[] 中
stx
3
将 X 存储到 M[] 中
jmp
6
跳转到标签
ja
6
跳转到标签
jeq
7, 8, 9, 10
当 A == <x> 时跳转
jneq
9, 10
当 A != <x> 时跳转
jne
9, 10
当 A != <x> 时跳转
jlt
9, 10
当 A < <x> 时跳转
jle
9, 10
当 A <= <x> 时跳转
jgt
7, 8, 9, 10
当 A > <x> 时跳转
jge
7, 8, 9, 10
当 A >= <x> 时跳转
jset
7, 8, 9, 10
当 A & <x> 时跳转
add
0, 4
A + <x>
sub
0, 4
A - <x>
mul
0, 4
A * <x>
div
0, 4
A / <x>
mod
0, 4
A % <x>
neg
!A
and
0, 4
A & <x>
or
0, 4
A | <x>
xor
0, 4
A ^ <x>
lsh
0, 4
A << <x>
rsh
0, 4
A >> <x>
tax
将 A 复制到 X 中
txa
将 X 复制到 A 中
ret
4, 11
返回
下表显示了第 2 列中的寻址格式
寻址模式
语法
描述
0
x/%x
寄存器 X
1
[k]
数据包中字节偏移量 k 处的 BHW
2
[x + k]
数据包中偏移量 X + k 处的 BHW
3
M[k]
M[] 中偏移量 k 处的字
4
#k
存储在 k 中的文字值
5
4*([k]&0xf)
数据包中字节偏移量 k 处的低半字节 * 4
6
L
跳转标签 L
7
#k,Lt,Lf
如果为真,则跳转到 Lt,否则跳转到 Lf
8
x/%x,Lt,Lf
如果为真,则跳转到 Lt,否则跳转到 Lf
9
#k,Lt
如果谓词为真,则跳转到 Lt
10
x/%x,Lt
如果谓词为真,则跳转到 Lt
11
a/%a
累加器 A
12
extension
BPF 扩展
Linux 内核还有几个 BPF 扩展,它们与加载指令类一起使用,方法是用负偏移量 + 特定扩展偏移量“重载” k 参数。 此类 BPF 扩展的结果将加载到 A 中。
下表显示了可能的 BPF 扩展
扩展
描述
len
skb->len
proto
skb->protocol
type
skb->pkt_type
poff
有效负载起始偏移量
ifidx
skb->dev->ifindex
nla
类型为 X 且偏移量为 A 的 Netlink 属性
nlan
类型为 X 且偏移量为 A 的嵌套 Netlink 属性
mark
skb->mark
queue
skb->queue_mapping
hatype
skb->dev->type
rxhash
skb->hash
cpu
raw_smp_processor_id()
vlan_tci
skb_vlan_tag_get(skb)
vlan_avail
skb_vlan_tag_present(skb)
vlan_tpid
skb->vlan_proto
rand
get_random_u32()
这些扩展也可以以“#”为前缀。 低级 BPF 的示例
ARP 数据包:
ldh [12]
jne #0x806, drop
ret #-1
drop: ret #0
IPv4 TCP 数据包:
ldh [12]
jne #0x800, drop
ldb [23]
jneq #6, drop
ret #-1
drop: ret #0
icmp 随机数据包采样,1/4:
ldh [12]
jne #0x800, drop
ldb [23]
jneq #1, drop
# get a random uint32 number
ld rand
mod #4
jneq #1, drop
ret #-1
drop: ret #0
SECCOMP 过滤器示例:
ld [4] /* offsetof(struct seccomp_data, arch) */
jne #0xc000003e, bad /* AUDIT_ARCH_X86_64 */
ld [0] /* offsetof(struct seccomp_data, nr) */
jeq #15, good /* __NR_rt_sigreturn */
jeq #231, good /* __NR_exit_group */
jeq #60, good /* __NR_exit */
jeq #0, good /* __NR_read */
jeq #1, good /* __NR_write */
jeq #5, good /* __NR_fstat */
jeq #9, good /* __NR_mmap */
jeq #14, good /* __NR_rt_sigprocmask */
jeq #13, good /* __NR_rt_sigaction */
jeq #35, good /* __NR_nanosleep */
bad: ret #0 /* SECCOMP_RET_KILL_THREAD */
good: ret #0x7fff0000 /* SECCOMP_RET_ALLOW */
低级 BPF 扩展的示例
接口索引为 13 的数据包:
ld ifidx
jneq #13, drop
ret #-1
drop: ret #0
(加速的)VLAN,ID 为 10:
ld vlan_tci
jneq #10, drop
ret #-1
drop: ret #0
上面的示例代码可以放置在一个文件中(此处称为“foo”),然后传递给 bpf_asm 工具以生成操作码,xt_bpf 和 cls_bpf 可以理解并可以直接加载的输出。 上面 ARP 代码的示例
$ ./bpf_asm foo
4,40 0 0 12,21 0 1 2054,6 0 0 4294967295,6 0 0 0,
以复制和粘贴的类似 C 的输出
$ ./bpf_asm -c foo
{ 0x28, 0, 0, 0x0000000c },
{ 0x15, 0, 1, 0x00000806 },
{ 0x06, 0, 0, 0xffffffff },
{ 0x06, 0, 0, 0000000000 },
特别是,由于与 xt_bpf 或 cls_bpf 一起使用可能会导致起初不太明显的更复杂的 BPF 过滤器,因此最好在附加到实时系统之前测试过滤器。 为此,内核源代码目录中的 tools/bpf/ 下有一个名为 bpf_dbg 的小型工具。 此调试器允许针对给定的 pcap 文件测试 BPF 过滤器,在 pcap 的数据包上单步执行 BPF 代码,并进行 BPF 机器寄存器转储。
启动 bpf_dbg 非常简单,只需要发出
# ./bpf_dbg
如果输入和输出不等于 stdin/stdout,则 bpf_dbg 将备用 stdin 源作为第一个参数,并将备用 stdout 接收器作为第二个参数,例如 ./bpf_dbg test_in.txt test_out.txt。
除此之外,可以通过文件“~/.bpf_dbg_init”设置特定的 libreadline 配置,并且命令历史记录存储在文件“~/.bpf_dbg_history”中。
在 bpf_dbg 中的交互通过一个也具有自动完成支持的 shell 发生(以下以 '>' 开头的示例命令表示 bpf_dbg shell)。 通常的工作流程是...
load bpf 6,40 0 0 12,21 0 3 2048,48 0 0 23,21 0 1 1,6 0 0 65535,6 0 0 0 从 bpf_asm 的标准输出加载 BPF 过滤器,或通过例如
tcpdump -iem1 -ddd port 22 | tr '\n' ','
转换。 请注意,对于 JIT 调试(下一节),此命令会创建一个临时套接字并将 BPF 代码加载到内核中。 因此,这对 JIT 开发人员也很有用。load pcap foo.pcap
加载标准 tcpdump pcap 文件。
run [<n>]
- bpf passes:1 fails:9
遍历 pcap 中的所有数据包,以统计过滤器将生成多少个通过和失败。 可以给出要遍历的数据包的限制。
disassemble
l0: ldh [12] l1: jeq #0x800, l2, l5 l2: ldb [23] l3: jeq #0x1, l4, l5 l4: ret #0xffff l5: ret #0
打印出 BPF 代码反汇编。
dump
/* { op, jt, jf, k }, */ { 0x28, 0, 0, 0x0000000c }, { 0x15, 0, 3, 0x00000800 }, { 0x30, 0, 0, 0x00000017 }, { 0x15, 0, 1, 0x00000001 }, { 0x06, 0, 0, 0x0000ffff }, { 0x06, 0, 0, 0000000000 },
打印出类似 C 的 BPF 代码转储。
breakpoint 0
breakpoint at: l0: ldh [12]
breakpoint 1
breakpoint at: l1: jeq #0x800, l2, l5
...
在特定的 BPF 指令处设置断点。 发出 run 命令将从当前数据包开始遍历 pcap 文件,并在命中断点时中断(另一个 run 将从当前活动的断点开始继续执行下一个指令)
run
-- register dump -- pc: [0] <-- program counter code: [40] jt[0] jf[0] k[12] <-- plain BPF code of current instruction curr: l0: ldh [12] <-- disassembly of current instruction A: [00000000][0] <-- content of A (hex, decimal) X: [00000000][0] <-- content of X (hex, decimal) M[0,15]: [00000000][0] <-- folded content of M (hex, decimal) -- packet dump -- <-- Current packet from pcap (hex) len: 42 0: 00 19 cb 55 55 a4 00 14 a4 43 78 69 08 06 00 01 16: 08 00 06 04 00 01 00 14 a4 43 78 69 0a 3b 01 26 32: 00 00 00 00 00 00 0a 3b 01 01 (breakpoint) >
breakpoint
breakpoints: 0 1
打印当前设置的断点。
step [-<n>, +<n>]
从当前的 pc 偏移量执行 BPF 程序的单步执行。 因此,在每次步骤调用时,都会发出上面的寄存器转储。 这可以及时前进和后退,一个简单的 step 将在下一个 BPF 指令处中断,因此 +1。(此处不需要发出 run。)
select <n>
从 pcap 文件中选择给定的数据包以继续。 因此,在下一个 run 或 step 中,BPF 程序将针对用户预选的数据包进行评估。 编号与 Wireshark 中的编号方式相同,从索引 1 开始。
quit
退出 bpf_dbg。
JIT 编译器¶
Linux 内核具有适用于 x86_64、SPARC、PowerPC、ARM、ARM64、MIPS、RISC-V、s390 和 ARC 的内置 BPF JIT 编译器,可以通过 CONFIG_BPF_JIT 启用。 如果 root 用户先前启用了 JIT 编译器,则会为来自用户空间的每个附加过滤器或内部内核用户透明地调用该编译器
echo 1 > /proc/sys/net/core/bpf_jit_enable
对于 JIT 开发人员,进行审计等,每次编译运行都可以通过以下方式将生成的操作码映像输出到内核日志中
echo 2 > /proc/sys/net/core/bpf_jit_enable
来自 dmesg 的示例输出
[ 3389.935842] flen=6 proglen=70 pass=3 image=ffffffffa0069c8f
[ 3389.935847] JIT code: 00000000: 55 48 89 e5 48 83 ec 60 48 89 5d f8 44 8b 4f 68
[ 3389.935849] JIT code: 00000010: 44 2b 4f 6c 4c 8b 87 d8 00 00 00 be 0c 00 00 00
[ 3389.935850] JIT code: 00000020: e8 1d 94 ff e0 3d 00 08 00 00 75 16 be 17 00 00
[ 3389.935851] JIT code: 00000030: 00 e8 28 94 ff e0 83 f8 01 75 07 b8 ff ff 00 00
[ 3389.935852] JIT code: 00000040: eb 02 31 c0 c9 c3
启用 CONFIG_BPF_JIT_ALWAYS_ON 后,bpf_jit_enable 将永久设置为 1,并且设置除此以外的任何其他值都会导致失败。 即使对于将 bpf_jit_enable 设置为 2 也是如此,因为不鼓励将最终的 JIT 映像转储到内核日志中,而通常建议使用 bpftool(在 tools/bpf/bpftool/ 下)进行内省。
对于在内核源代码树下的 tools/bpf/ 中,有一个 bpf_jit_disasm 用于从内核日志的 hexdump 生成反汇编
# ./bpf_jit_disasm
70 bytes emitted from JIT compiler (pass:3, flen:6)
ffffffffa0069c8f + <x>:
0: push %rbp
1: mov %rsp,%rbp
4: sub $0x60,%rsp
8: mov %rbx,-0x8(%rbp)
c: mov 0x68(%rdi),%r9d
10: sub 0x6c(%rdi),%r9d
14: mov 0xd8(%rdi),%r8
1b: mov $0xc,%esi
20: callq 0xffffffffe0ff9442
25: cmp $0x800,%eax
2a: jne 0x0000000000000042
2c: mov $0x17,%esi
31: callq 0xffffffffe0ff945e
36: cmp $0x1,%eax
39: jne 0x0000000000000042
3b: mov $0xffff,%eax
40: jmp 0x0000000000000044
42: xor %eax,%eax
44: leaveq
45: retq
Issuing option `-o` will "annotate" opcodes to resulting assembler
instructions, which can be very useful for JIT developers:
# ./bpf_jit_disasm -o
70 bytes emitted from JIT compiler (pass:3, flen:6)
ffffffffa0069c8f + <x>:
0: push %rbp
55
1: mov %rsp,%rbp
48 89 e5
4: sub $0x60,%rsp
48 83 ec 60
8: mov %rbx,-0x8(%rbp)
48 89 5d f8
c: mov 0x68(%rdi),%r9d
44 8b 4f 68
10: sub 0x6c(%rdi),%r9d
44 2b 4f 6c
14: mov 0xd8(%rdi),%r8
4c 8b 87 d8 00 00 00
1b: mov $0xc,%esi
be 0c 00 00 00
20: callq 0xffffffffe0ff9442
e8 1d 94 ff e0
25: cmp $0x800,%eax
3d 00 08 00 00
2a: jne 0x0000000000000042
75 16
2c: mov $0x17,%esi
be 17 00 00 00
31: callq 0xffffffffe0ff945e
e8 28 94 ff e0
36: cmp $0x1,%eax
83 f8 01
39: jne 0x0000000000000042
75 07
3b: mov $0xffff,%eax
b8 ff ff 00 00
40: jmp 0x0000000000000044
eb 02
42: xor %eax,%eax
31 c0
44: leaveq
c9
45: retq
c3
对于 BPF JIT 开发人员,bpf_jit_disasm、bpf_asm 和 bpf_dbg 提供了一个有用的工具链,用于开发和测试内核的 JIT 编译器。
BPF 内核内部结构¶
在内部,对于内核解释器,使用了一种不同的指令集格式,其底层原理与前面段落中描述的 BPF 相似。 但是,该指令集格式更接近底层架构进行建模,以模仿本机指令集,以便可以实现更好的性能(稍后会详细介绍)。 这个新的 ISA 被称为 eBPF。 有关详细信息,请参见 BPF 文档。 (注意:源自 [e]xtended BPF 的 eBPF 与 BPF 扩展不同!虽然 eBPF 是一种 ISA,但 BPF 扩展可以追溯到经典 BPF 的“重载” BPF_LD | BPF_{B,H,W} | BPF_ABS 指令。)
最初设计新指令集时,考虑了以“受限 C”编写程序并通过可选的 GCC/LLVM 后端编译为 eBPF 的可能目标,以便它可以及时映射到现代 64 位 CPU,与本机代码相比,性能开销最小,分为两个步骤,即 C -> eBPF -> 本机代码。
当前,新格式用于运行用户 BPF 程序,其中包括 seccomp BPF、经典套接字过滤器、cls_bpf 流量分类器、团队驱动程序的负载平衡模式分类器、netfilter 的 xt_bpf 扩展、PTP 分割器/分类器等等。 它们都在内核内部转换为新的指令集表示形式,并在 eBPF 解释器中运行。 对于内核内处理程序,通过使用 bpf_prog_create()
设置过滤器,以及使用 bpf_prog_destroy() 销毁过滤器,所有这些都可以透明地工作。 函数 bpf_prog_run(filter, ctx) 透明地调用 eBPF 解释器或 JIT 代码来运行过滤器。 ‘filter’ 是指向我们从 bpf_prog_create()
获得的 struct bpf_prog 的指针,‘ctx’ 是给定的上下文(例如 skb 指针)。 在幕后进行转换为新布局之前,所有来自 bpf_check_classic() 的约束和限制都适用!
当前,经典 BPF 格式用于在大多数 32 位架构上进行 JIT 处理,而 x86-64、aarch64、s390x、powerpc64、sparc64、arm32、riscv64、riscv32、loongarch64、arc 从 eBPF 指令集执行 JIT 编译。
测试¶
除了 BPF 工具链之外,内核还附带一个测试模块,其中包含经典和 eBPF 的各种测试用例,这些测试用例可以针对 BPF 解释器和 JIT 编译器执行。 它可以在 lib/test_bpf.c 中找到,并通过 Kconfig 启用
CONFIG_TEST_BPF=m
构建并安装该模块后,可以通过 insmod 或 modprobe 针对“test_bpf”模块执行测试套件。 测试用例的结果(包括以纳秒为单位的计时)可以在内核日志 (dmesg) 中找到。
其他¶
Linux 系统调用模糊测试器 Trinity 也内置了对 BPF 和 SECCOMP-BPF 内核模糊测试的支持。
作者¶
编写本文档的目的是希望它有用,并使潜在的 BPF 黑客或安全审计员能够更好地了解底层架构。
Jay Schulist <jschlst@samba.org>
Daniel Borkmann <daniel@iogearbox.net>
Alexei Starovoitov <ast@kernel.org>