Linux 套接字过滤,又称伯克利数据包过滤器 (BPF)

注意

此文件曾经记录 eBPF 的格式和机制,即使它们与套接字过滤无关。 BPF 文档 提供了关于 eBPF 的更多详细信息。

简介

Linux 套接字过滤 (LSF) 源于伯克利数据包过滤器。虽然 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 会显示放入此结构中的内容。

尽管我们在这里只讨论套接字,但 BPF 在 Linux 中被用于更多地方。有用于 netfilter 的 xt_bpf,内核 qdisc 层中的 cls_bpf,SECCOMP-BPF(安全计算 [1]),以及许多其他地方,如团队驱动程序、PTP 代码等,都在使用 BPF。

原始 BPF 论文

Steven McCanne 和 Van Jacobson。1993 年。《BSD 数据包过滤器:用于用户级数据包捕获的新架构》。在《USENIX 1993 年冬季会议论文集》(USENIX'93)中。USENIX Association,伯克利,加利福尼亚州,美国,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 */
};

这种结构被组装成一个包含代码、jt、jf 和 k 值的 4 元组数组。 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 个 32 位宽的杂项寄存器,又称“暂存内存存储”,可从 0 到 15 寻址

由 bpf_asm 转换为“操作码”的程序是一个数组,它由以下元素组成(如前所述)

op:16, jt:8, jf:8, k:32

元素 op 是一个 16 位宽的操作码,其中编码了特定的指令。 jt 和 jf 是两个 8 位宽的跳转目标,一个用于条件“如果为真则跳转”,另一个用于“如果为假则跳转”。最后,元素 k 包含一个杂项参数,可以根据 op 中给定的指令以不同的方式解释。

指令集由加载、存储、分支、alu、杂项和返回指令组成,这些指令也以 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

返回

下一个表格显示了第二列的寻址格式

寻址模式

语法

描述

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 处的低 nibble * 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

扩展

BPF 扩展

Linux 内核还有几个 BPF 扩展,它们与加载指令类一起使用,通过用负偏移量 + 特定扩展偏移量“重载” k 参数。此类 BPF 扩展的结果被加载到 A 中。

下表显示了可能的 BPF 扩展

扩展

描述

len

skb->len

proto

skb->protocol

type

skb->pkt_type

poff

有效负载起始偏移量

ifidx

skb->dev->ifindex

nla

具有偏移量 A 的类型 X 的 Netlink 属性

nlan

具有偏移量 A 的类型 X 的嵌套 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 随机数据包采样,四分之一:

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 进行,该 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 时,都会发出上述寄存器转储。这可以在时间上向前和向后移动,普通的 step 将在下一条 BPF 指令处中断,即 +1。(此处不需要执行 run。)

  • select <n>

    从 pcap 文件中选择给定的数据包以继续。因此,在下一个 runstep 时,将针对用户预选的数据包评估 BPF 程序。编号从 Wireshark 的索引 1 开始。

  • quit

    退出 bpf_dbg。

JIT 编译器

Linux 内核为 x86_64、SPARC、PowerPC、ARM、ARM64、MIPS、RISC-V、s390 和 ARC 提供了内置的 BPF JIT 编译器,可以通过 CONFIG_BPF_JIT 启用。如果根用户先前已启用,则对于来自用户空间的每个附加过滤器或对于内部内核用户,会透明地调用 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 黑客或安全审计员更好地了解底层架构。