BPF 和 XDP 作為 Linux 核心技術,為系統開發者提供了強大的工具,用於監控網路流量、分析系統效能和提升安全防護。從網路封包過濾到系統呼叫追蹤,BPF 程式可以深入核心層面,擷取關鍵資訊,並根據需求進行處理。搭配 BPF Maps,更能實作核心空間和使用者空間的資料交換,為開發者打造更靈活的應用程式提供了可能性。實際應用案例包含監控系統、DDoS 緩解、負載平衡和防火牆等,展現了 BPF 技術的多樣性和應用價值。

實際應用案例

以下是一些實際應用案例:

  • 監控系統:使用 XDP 和 Seccomp 來監控網路流量,偵測異常行為和安全威脅。
  • DDoS 緩解系統:使用 XDP 和 Seccomp 來緩解 DDoS 攻擊,過濾掉惡意流量。
  • 負載平衡系統:使用 XDP 和 Seccomp 來實作負載平衡,將流量分配到多個伺服器。
  • 防火牆系統:使用 XDP 和 Seccomp 來實作防火牆功能,過濾掉惡意流量。

圖表翻譯:

  graph LR
    A[XDP] -->|過濾|> B[DDoS 緩解]
    A -->|監控|> C[監控系統]
    A -->|負載平衡|> D[負載平衡系統]
    A -->|防火牆|> E[防火牆系統]
    B -->|過濾|> F[網路流量]
    C -->|監控|> F
    D -->|負載平衡|> F
    E -->|防火牆|> F

這個圖表展示了 XDP 和 Seccomp 的實際應用案例,包括監控系統、DDoS 緩解系統、負載平衡系統和防火牆系統。

前言

作為一名程式設計師和自稱的書呆子,我喜歡隨時瞭解最新的核心技術和電腦研究。當我第一次接觸到 Berkeley Packet Filter(BPF)和 Express Data Path(XDP)在 Linux 中時,我就深深地愛上了它們。這些工具非常棒,我很高興這本文能將 BPF 和 XDP 放在中心舞臺上,以便更多人能夠在他們的專案中使用它們。

讓我詳細介紹一下我的背景和為什麼我愛上了這些核心介面。我曾經作為 Docker 的核心維護者,與 David 一起工作。Docker,如果你不熟悉,它會將很多篩選和路由邏輯外包給 iptables。我的第一個 Docker 修復補丁是解決一個問題,即某個版本的 iptables 在 CentOS 上沒有相同的命令列標誌,因此寫入 iptables 失敗了。還有很多奇怪的問題,任何曾經在軟體中外包工具的人都可能會感同身受。不僅如此,在主機上有成千上萬條規則對於 iptables 來說不是它的設計初衷,結果會導致效能副作用。

然後我聽說了 BPF 和 XDP。這對我的耳朵來說是音樂!我的 iptables 傷口不再會因為另一個 bug 而出血!核心社群甚至正在努力用 BPF 取代 iptables!哈利路亞!Cilium,一個容器網路工具,正在使用 BPF 和 XDP 作為其專案的內部實作。

但這還不是全部!BPF 可以做的遠遠不止於取代 iptables 的使用案例。有了 BPF,你可以追蹤任何系統呼叫或核心函式,以及任何使用者空間程式。bpftrace 給使用者提供了類別似 DTrace 的能力,可以從命令列追蹤所有被開啟的檔案和呼叫 open 的程式,計算系統呼叫數量,追蹤 OOM 殺手等等……BPF 和 XDP 還被 Cloudflare 和 Facebook 的負載平衡器用於防止分散式拒絕服務攻擊。我不會透露為什麼 XDP 很擅長丟棄包,因為你會在這本文的 XDP 和網路章節中學到這些知識!

我有幸透過 Kubernetes 社群認識了 Lorenzo。他的工具 kubectl-trace 允許使用者在其 Kubernetes 叢集中輕鬆執行自定義追蹤程式。

對我來說,BPF 最好的使用案例是編寫自定義追蹤器,以證明其他人的軟體效能不佳或使得非常昂貴的系統呼叫數量。不要低估用硬資料證明別人錯誤的力量。不要擔心,這本文會引導你編寫你的第一個追蹤程式,以便你也能做到這一點。BPF 的美妙之處在於,它以前的工具使用了有損失的佇列將樣本集傳送到使用者空間進行彙總,而 BPF 則允許在事件源直接構建直方圖和篩選。

我花了我職業生涯的一半時間在為開發人員打造工具上。最好的工具允許開發人員在其介面中具有自主性,以便使用者可以以即使作者未曾想象到的方式使用它們。參照理查德·費曼的話,“我很早就學會了知道某事物的名稱和真正知道某事物之間的區別。” 在此之前,你可能只知道 BPF 的名稱,並且它可能對你有用。

我喜歡這本文的地方是,它給了你建立全新工具使用 BPF 所需的知識。在閱讀並完成練習後,你將被賦予使用 BPF 的超能力。你可以將它放在你的工具箱中,在需要時使用它。你不僅會學習 BPF,你還會理解它。這本文是一條通往開啟你心智、探索使用 BPF 可以建立什麼可能性的大門。

這個正在發展的生態系統非常令人興奮!我希望它會隨著更多人開始使用 BPF 的力量而變得更加龐大。我很期待學習到這本文的讀者最終會建立什麼。我們一起期待吧!

1. 關於觀測性(Observability)的概念

觀測性是一種新的方法,透過收集和分析系統資料,來瞭解系統的行為和效能。它與日誌(logs)和指標(metrics)不同,日誌提供了系統的明確資料,而指標則聚合了資料以衡量系統在特定時間點的行為。觀測性允許使用者問任意問題並從系統中收到複雜的答案。

2. 黑天鵝事件(Black Swan Events)的概念

黑天鵝事件是指那些意外且具有重大後果的事件,它們可能本可以被預見和預防,如果我們事先觀察到了相關的資料。觀測性有助於構建強大的系統,並減輕未來黑天鵝事件的影響,因為它根據收集能夠回答任何未來問題的資料的前提。

3. Linux 容器和核心功能

Linux 容器是一種根據 Linux 核心功能的抽象,例如名稱空間(namespaces)和控制組(cgroups),用於隔離和管理計算過程。這些功能提供了任務隔離、安全性和資源管理。

4. BPF(Berkeley Packet Filter)的簡介

BPF 是一個內核子系統,允許使用者編寫程式以檢查核心事件。它提供了強大的安全保證,以防止系統當機和惡意行為。BPF 啟用了一波新的工具,幫助系統開發人員觀察和與這些新平臺合作。

5. BPF 的歷史

BPF 最初於 1992 年作為一個網路包過濾器實作。2014 年,Alexei Starovoitov 引入了擴充套件的 BPF 實作,最佳化了現代硬體,增加了暫存器數量和寬度,使其能夠編寫更複雜的程式。

6. BPF 的架構

BPF 的架構包括一個虛擬機器(VM),用於在隔離環境中執行程式碼指令。編譯器如 LLVM 支援 BPF,允許將 C 程式碼編譯為 BPF 指令。BPF 驗證器確保程式在執行前是安全的。Linux 核心還包含一個即時編譯器(JIT),將 BPF Bytecode 轉換為機器碼。

7. BPF 程式和對映

BPF 程式可以附加到內核的多個執行點,核心會提供特定的函式幫助器來處理接收到的資料。BPF 對映是用於在內核和使用者空間之間分享資料的結構。它們是雙向的,可以從兩側讀寫。

BPF 程式設計基礎

BPF(Berkeley Packet Filter)是一種 Linux 子系統,允許開發人員在核心中執行小型程式,以便對網路封包、系統呼叫等進行監控和分析。要開始使用 BPF,您需要了解 BPF 的基本概念和開發流程。

BPF 程式結構

BPF 程式通常由 C 語言編寫,然後使用 LLVM 編譯器編譯成 BPF bytecode。這些 bytecode 可以被 BPF 虛擬機器(BPF VM)執行。BPF VM 提供了一個安全的環境,讓開發人員可以在核心中執行自定義的程式碼。

BPF 程式型別

BPF 程式可以分為多種型別,包括:

  • tracepoint 程式:這類別程式可以附加到特定的核心事件(如系統呼叫、函式呼叫等),並在事件發生時執行。
  • kprobe 程式:這類別程式可以附加到特定的核心函式,並在函式呼叫時執行。
  • xdp 程式:這類別程式可以直接處理網路封包,並在網路接收或傳送時執行。

BPF 程式開發流程

要開發 BPF 程式,您需要:

  1. 安裝 LLVM 編譯器和 BPF 相關工具。
  2. 編寫 C 語言程式碼,定義 BPF 程式的邏輯。
  3. 使用 LLVM 編譯器編譯程式碼成 BPF bytecode。
  4. 使用 BPF 載入工具(如 bpf_load)將 bytecode 載入核心。
  5. 執行 BPF 程式,並檢視其輸出結果。

BPF 程式範例

以下是一個簡單的 BPF 程式範例,展示瞭如何使用 tracepoint 來監控系統呼叫:

#include <linux/bpf.h>

#define SEC(NAME) __attribute__((section(NAME), used))

SEC("tracepoint/syscalls/sys_enter_execve")
int bpf_prog(void *ctx) {
    char msg[] = "Hello, BPF World!";
    bpf_trace_printk(msg, sizeof(msg));
    return 0;
}

char _license[] SEC("license") = "GPL";

這個範例使用 SEC 宏定義了一個 tracepoint 程式,當系統呼叫 execve 時執行。程式內容很簡單,只是印出了一條訊息。

載入和執行 BPF 程式

要載入和執行這個 BPF 程式,您需要使用 bpf_load 工具:

clang -O2 -target bpf -c bpf_program.c -o bpf_program.o
sudo./bpf_load bpf_program.o

然後,您就可以看到 BPF 程式的輸出結果了。

BPF 程式型別

BPF(Berkeley Packet Filter)是一種用於 Linux 核心的程式設計框架,允許開發人員建立自定義的程式來處理網路封包、追蹤系統行為和最佳化系統效能。BPF 程式型別可以分為兩大類別:追蹤(Tracing)和網路(Networking)。

追蹤(Tracing)

追蹤型別的 BPF 程式主要用於瞭解系統的行為和硬體的運作。它們可以存取特定程式的記憶體區域、提取執行追蹤和存取每個程式的資源分配。這些程式可以幫助開發人員診斷和最佳化系統的效能。

網路(Networking)

網路型別的 BPF 程式允許開發人員檢查和操控網路流量。它們可以過濾從網路介面卡接收的封包或完全拒絕這些封包。不同的 BPF 程式型別可以附加到網路處理的不同階段,從而提供了靈活性和控制性。

BPF 程式型別列表

以下是 BPF 程式型別列表,按時間順序介紹:

  1. Socket Filter 程式:BPF_PROG_TYPE_SOCKET_FILTER 是第一種被新增到 Linux 核心的 BPF 程式型別。它允許開發人員附加 BPF 程式到 raw socket,從而存取所有由核心處理的封包。
  2. Kprobe 程式:Kprobe 程式允許開發人員動態地附加 BPF 程式到核心的特定呼叫點。它們被定義為 BPF_PROG_TYPE_KPROBE 型別。
  3. Tracepoint 程式:Tracepoint 程式允許開發人員附加 BPF 程式到核心的追蹤點。它們被定義為 BPF_PROG_TYPE_TRACEPOINT 型別。
  4. XDP 程式:XDP 程式允許開發人員撰寫在封包抵達核心時執行的程式。它們被定義為 BPF_PROG_TYPE_XDP 型別。
  5. Perf Event 程式:Perf Event 程式允許開發人員附加 BPF 程式到 Perf 事件。它們被定義為 BPF_PROG_TYPE_PERF_EVENT 型別。
  6. Cgroup Socket 程式:Cgroup Socket 程式允許開發人員附加 BPF 邏輯到控制群組(cgroups)。它們被定義為 BPF_PROG_TYPE_CGROUP_SKB 型別。
  7. Cgroup Open Socket 程式:Cgroup Open Socket 程式允許開發人員執行程式碼當任何程式在控制群組中開啟網路 socket。它們被定義為 BPF_PROG_TYPE_CGROUP_SOCK 型別。
  8. Socket Option 程式:Socket Option 程式允許開發人員修改 socket 連線選項在封包透過核心網路堆積疊的不同階段。它們被定義為 BPF_PROG_TYPE_SOCK_OPS 型別。
  9. Socket Map 程式:Socket Map 程式允許開發人員存取 socket 地圖和 socket 重導向。它們被定義為 BPF_PROG_TYPE_SK_SKB 型別。

每種 BPF 程式型別都有其特定的用途和應用場景,開發人員可以根據需要選擇合適的型別來實作其功能。

BPF 程式型別概覽

BPF(Berkeley Packet Filter)是一種強大的程式設計框架,允許開發人員在 Linux 核心中執行自定義程式。BPF 程式可以用於各種應用,包括網路封包過濾、系統監控和安全性增強。以下是 BPF 程式型別的概覽:

1. Cgroup Device 程式

Cgroup Device 程式允許您控制 cgroup 中的裝置操作。這類別程式的型別為 BPF_PROG_TYPE_CGROUP_DEVICE。它們提供了更大的靈活性來設定裝置許可權,尤其是在 cgroup v2 中。

2. Socket 訊息傳遞程式

Socket 訊息傳遞程式控制是否將訊息傳遞給 socket。這類別程式的型別為 BPF_PROG_TYPE_SK_MSG。當 kernel 建立一個 socket 時,它會將 socket 儲存在 socket 地圖中。當您將 socket 訊息 BPF 程式附加到 socket 地圖時,所有傳送到這些 socket 的訊息都會被過濾。

3. Raw Tracepoint 程式

Raw Tracepoint 程式允許您以原始格式存取 kernel 中的 tracepoint 引數。這類別程式的型別為 BPF_PROG_TYPE_RAW_TRACEPOINT。雖然它們提供了更詳細的資訊,但也有一定的效能 overhead。

4. Cgroup Socket 地址程式

Cgroup Socket 地址程式允許您操控由 cgroup 控制的使用者空間程式的 IP 地址和埠號。這類別程式的型別為 BPF_PROG_TYPE_CGROUP_SOCK_ADDR。它們提供了將多個使用者空間程式繫結到相同 IP 地址和埠號的靈活性。

5. Socket 重新使用埠程式

Socket 重新使用埠程式允許您控制 kernel 是否重新使用埠。這類別程式的型別為 BPF_PROG_TYPE_SK_REUSEPORT。您可以透過傳回 SK_DROPSK_PASS 來控制埠的重新使用。

6. 流解析程式

流解析程式允許您控制網路封包的流向。這類別程式的型別為 BPF_PROG_TYPE_FLOW_DISSECTOR。它們提供了安全性保證,例如確保程式總是終止。

7. 其他 BPF 程式

還有其他幾種 BPF 程式型別,包括流量分類別程式 (BPF_PROG_TYPE_SCHED_CLSBPF_PROG_TYPE_SCHED_ACT),它們允許您分類別網路流量和修改封包屬性。

內容解密:

每種 BPF 程式型別都有其特定的應用場景和優點。瞭解這些型別可以幫助您選擇合適的工具來解決特定的問題。例如,Cgroup Device 程式可以用於控制 cgroup 中的裝置操作,而 Socket 訊息傳遞程式可以用於過濾 socket 訊息。

圖表翻譯:

  graph LR
    A[BPF 程式型別] -->|包含|> B[Cgroup Device]
    A -->|包含|> C[Socket 訊息傳遞]
    A -->|包含|> D[Raw Tracepoint]
    A -->|包含|> E[Cgroup Socket 地址]
    A -->|包含|> F[Socket 重新使用埠]
    A -->|包含|> G[流解析]
    A -->|包含|> H[其他 BPF 程式]

這個圖表展示了不同 BPF 程式型別之間的關係,幫助您快速瞭解 BPF 程式的結構和分類別。

BPF 程式驗證機制與輕量級隧道程式

BPF 程式是一種在 Linux 核心中執行的程式,允許開發者附加自訂程式碼到核心的各個層面。然而,允許任意程式碼在核心中執行也帶來了安全風險。為了減少這種風險,BPF 驗證機制被引入,以確保 BPF 程式的安全性。

BPF 驗證機制

BPF 驗證機制是一種靜態分析工具,負責檢查 BPF 程式的安全性。它會執行兩個主要檢查:靜態分析和乾跑(dry run)。

  1. 靜態分析:驗證機制首先會對 BPF 程式進行靜態分析,確保程式有預期的結束點。它會建立一個有向非迴圈圖(DAG),以確保程式不會陷入無限迴圈。
  2. 乾跑:如果靜態分析透過,驗證機制會執行乾跑,以模擬 BPF 程式的執行。這個過程會檢查每一條指令,確保它們不會執行任何無效操作,並且所有記憶體存取都正確。

如果 BPF 程式透過了這兩個檢查,驗證機制會將其標記為安全,可以在核心中執行。

輕量級隧道程式

輕量級隧道程式是一種特殊的 BPF 程式,允許開發者附加自訂程式碼到核心的輕量級隧道基礎設施。這些程式可以用於實作各種功能,例如封包處理和網路流量控制。

BPF 型別格式(BTF)

BPF 型別格式(BTF)是一種用於描述 BPF 程式、對映和函式的中繼資料結構。BTF 包含來源資訊,允許工具如 BPFTool 顯示更豐富的 BPF 資料解釋。這些中繼資料儲存在二進製程式的特殊 “.BTF” 中繼資料段中。

BPF 尾部呼叫

BPF 尾部呼叫是一種允許 BPF 程式呼叫其他 BPF 程式的機制。這個功能強大,因為它允許開發者組裝更複雜的程式。從 Linux 核心版本 5.2 開始,BPF 尾部呼叫的指令限制增加到一百萬指令,並且尾部呼叫巢狀限制為 32 層。

BPF Maps 的概念與應用

BPF Maps 是一種儲存在核心中的 key/value 儲存,允許 BPF 程式和使用者空間程式之間進行通訊。這些 Maps 可以被用來交換資訊和修改行為。

建立 BPF Maps

建立 BPF Maps 的最直接方法是使用 bpf 系統呼叫,並將第一個引數設定為 BPF_MAP_CREATE。這個呼叫會傳回新建立的 Map 的檔案描述符。

union bpf_attr {
    struct {
        __u32 map_type; /* one of the values from bpf_map_type */
        __u32 key_size; /* size of the keys, in bytes */
        __u32 value_size; /* size of the values, in bytes */
        __u32 max_entries; /* maximum number of entries in the map */
        __u32 map_flags; /* flags to modify how we create the map */
    };
}

例如,建立一個雜湊表 Map 來儲存無符號整數作為鍵和值,可以使用以下程式碼:

union bpf_attr my_map = {
   .map_type = BPF_MAP_TYPE_HASH,
   .key_size = sizeof(int),
   .value_size = sizeof(int),
   .max_entries = 100,
   .map_flags = BPF_F_NO_PREALLOC,
};
int fd = bpf(BPF_MAP_CREATE, &my_map, sizeof(my_map));

如果呼叫失敗,核心會傳回 -1,並設定 errno 變數為相應的錯誤碼。

ELF Conventions

核心還提供了 ELF Conventions 來建立 BPF Maps,這些約定比直接使用系統呼叫更容易閱讀和理解。其中,bpf_map_create 函式包裝了建立 Map 的程式碼,使其更容易初始化 Maps。

int fd;
fd = bpf_create_map(BPF_MAP_TYPE_HASH, sizeof(int), sizeof(int), 100, 0);

BPF Maps 的安全性

BPF 驗證器包含了多個安全機制,以確保建立和存取 Maps 的安全性。這些機制包括檢查 Map 的組態和存取許可權,以防止非法存取和修改。

圖表翻譯:

  graph LR
    A[建立 BPF Map] -->|使用 bpf 系統呼叫|> B[初始化 Map]
    B -->|設定 Map 屬性|> C[組態 Map]
    C -->|檢查安全性|> D[驗證器檢查]
    D -->|傳回檔案描述符|> E[存取 Map]

內容解密:

上述程式碼示範瞭如何建立一個 BPF Map,並設定其屬性。bpf 系統呼叫會傳回新建立的 Map 的檔案描述符,然後可以使用這個描述符來存取 Map。BPF 驗證器會檢查 Map 的組態和存取許可權,以確保安全性。

從技術架構視角來看,BPF 和 XDP 作為 Linux 核心技術的革新,為系統監控、網路效能最佳化和安全防禦提供了強大的工具。深入分析其核心機制,可以發現 BPF 程式驗證機制和輕量級隧道程式設計模型的結合,在確保系統安全性的同時,也賦予了開發者高度的靈活性。BPF maps 的引入,更進一步提升了核心空間與使用者空間資料交換的效率,為複雜應用場景的實作奠定了基礎。然而,BPF 程式設計的複雜性和效能調校仍然是技術團隊需要克服的挑戰。從技術演進預測來看,eBPF 的持續發展和社群的積極貢獻,將推動其應用範圍不斷擴充套件,尤其是在雲原生環境和邊緣計算領域。玄貓認為,深入理解 BPF 和 XDP 的核心原理及應用場景,對於構建高效能、安全可靠的現代化基礎設施至關重要。對於追求技術創新的團隊,積極探索 BPF 和 XDP 的潛力將帶來顯著的競爭優勢。