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 檔案中選擇給定的資料包以繼續。 因此,在下一個 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 啟用。 如果 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 駭客或安全審計員能夠更好地瞭解底層架構。