Linux 提供強大的流量控制機制,允許開發者精細調整網路行為。本文將探討如何結合 BPF 程式設計,強化 Linux 的流量控制能力,並深入研究 XDP 技術,實作更精確、高效的封包處理。從 qdisc 的基本概念到 cls_bpf 分類別器的應用,再到 XDP 的原理和實作,逐步引導讀者掌握 BPF 在網路效能最佳化中的關鍵作用。同時,文章也提供程式碼範例和實務應用說明,幫助讀者將理論知識轉化為實際操作能力,提升網路程式設計的技能水平。
BPF 根據的流量控制分類別器
流量控制(Traffic Control)是 Linux 內核的封包排程子系統架構。它由機制和排隊系統組成,可以決定封包如何流動和如何被接受。流量控制的一些用途包括:
- 將某些型別的封包優先處理
- 丟棄特定型別的封包
- 頻寬分配
一般來說,當您需要重新分配網路資源時,流量控制是實作這一目的的途徑。為了充分利用流量控制,您應該根據要執行的應用程式型別佈署特定的流量控制組態。
流量控制提供了一個可程式設計的分類別器,稱為 cls_bpf
,允許 hook 到不同的排程操作級別,在那裡可以讀取和更新 socket 緩衝區和封包的中繼資料,以實作流量整形、追蹤、預處理等功能。
對於 cls_bpf
的 eBPF 支援是在 Linux 核心 4.1 中實作的,這意味著這種程式可以存取 eBPF 地圖、具有尾部呼叫支援、可以存取 IPv4/IPv6 隧道中繼資料,並且可以使用 eBPF 提供的幫助程式和公用程式。
用於與流量控制相關的網路組態互動的工具是 iproute2
套件的一部分,包括 ip
和 tc
,分別用於操作網路介面和流量控制組態。
名詞解釋
由於流量控制和 BPF 程式之間存在互動點,因此您需要了解一些流量控制概念。如果您已經掌握了流量控制,可以跳過此名稱解釋部分,直接進入範例。
排隊紀律
排隊紀律(qdisc)定義了用於將封包排隊到介面的排程物件;這些物件可以是無類別或有類別的。
預設的 qdisc 是 pfifo_fast
,它是無類別的,並將封包排隊到三個先進先出(FIFO)佇列中,根據優先順序進行_dequeue_;這個 qdisc 不適用於虛擬裝置,如環迴(lo)或虛擬乙太網(veth)裝置,它們使用 noqueue
。除了其排程演算法很好之外,pfifo_fast
還不需要任何組態即可工作。
虛擬介面可以透過 /sys/class/net
偽檔案系統與實體介面(裝置)區分:
ls -la /sys/class/net
虛擬介面和實體介面
虛擬介面可以透過以下命令區分:
ip a
結果顯示了當前系統中組態的網路介面的列表。
qdisc 的型別
noqueue
:不具有類別、排程器或分類別器的 qdisc。它嘗試立即傳送封包。fq_codel
:一種無類別的 qdisc,使用隨機模型對入站封包進行分類別,以便公平地排隊流量流。
使用 tc
工具列出 qdisc
您可以使用 tc
工具的 qdisc
子命令來列出 qdisc:
tc qdisc
網路流量控制與 BPF 程式設計
在 Linux 中,網路流量控制(Traffic Control)是一個強大的機制,允許使用者定義網路流量的優先順序和處理方式。其中,qdisc(佇列規範)是一個重要的概念,用於管理網路流量的傳輸。
qdisc 介紹
使用 tc qdisc ls
命令可以檢視系統中目前的 qdisc 設定。例如:
qdisc noqueue 0: dev lo root refcnt 2
qdisc fq_codel 0: dev enp0s31f6 root refcnt 2 limit 10240p flows 1024 quantum 1514
target 5.0ms interval 100.0ms memory_limit 32Mb ecn
qdisc noqueue 0: dev docker0 root refcnt 2
這裡我們可以看到三個 qdisc 的設定:noqueue
、fq_codel
和 noqueue
。其中,fq_codel
是一個具有緩衝區的 qdisc,可以處理多達 10,240 個封包,並且具有 1,024 個流程。
類別化 qdisc
類別化 qdisc(classful qdisc)允許使用者定義不同的類別,以便對不同類別的流量進行不同的處理。類別化 qdisc 可以包含其他 qdisc,因此可以建立一個階層式的流量控制結構。
篩選器(Filters)
篩選器(Filters)用於將封包分配到特定的類別中。篩選器可以根據封包的屬性進行分類別,例如源 IP 地址、目的 IP 地址、埠號等。
BPF 程式設計
BPF(Berkeley Packet Filter)是一種程式設計語言,允許使用者在 Linux 核心中執行自定義的程式碼。BPF 程式可以用於實作網路流量控制、封包過濾等功能。
cls_bpf 分類別器
cls_bpf 分類別器是一種特殊的分類別器,允許使用者執行 BPF 程式以進行封包分類別。cls_bpf 分類別器可以用於實作複雜的流量控制邏輯。
例項:使用 cls_bpf 分類別器進行封包分類別
以下是一個使用 cls_bpf 分類別器進行封包分類別的例項:
SEC("classifier")
static inline int classification(struct __sk_buff *skb) {
void *data_end = (void *)(long)skb->data_end;
void *data = (void *)(long)skb->data;
struct ethhdr *eth = data;
__u16 h_proto;
__u64 nh_off = 0;
nh_off = sizeof(*eth);
if (data + nh_off > data_end) {
return TC_ACT_OK;
}
if (h_proto == bpf_htons(ETH_P_IP)) {
// 封包是 IP 封包
}
}
這個例項中,我們定義了一個 cls_bpf 分類別器,該分類別器可以根據封包的 Ethernet 首部進行分類別。如果封包是 IP 封包,則進行特定的處理。
BPF-Based Traffic Control Classifier
在 Linux 網路和 BPF 中,Traffic Control Classifier 是一個重要的元件,負責分類別網路封包並根據特定規則進行處理。在本文中,我們將探討如何使用 BPF 實作一個簡單的 Traffic Control Classifier。
Classifier 程式碼
以下是Classifier程式碼的核心部分:
if (is_http(skb, nh_off) == 1) {
trace_printk("Yes! It is HTTP!\n");
}
這段程式碼使用 is_http
函式來檢查是否為 HTTP 封包,如果是則印出 debug 訊息。
is_http 函式
is_http
函式的實作如下:
int is_http(struct __sk_buff *skb, int nh_off) {
void *data_end = (void *)(long)skb->data_end;
void *data = (void *)(long)skb->data;
struct iphdr *iph = data + nh_off;
if (iph + 1 > data_end) {
return 0;
}
if (iph->protocol!= IPPROTO_TCP) {
return 0;
}
__u32 tcp_hlen = 0;
//...
int *value;
if ((p[0] == 'H') && (p[1] == 'T') && (p[2] == 'T') && (p[3] == 'P')) {
return 1;
}
return 0;
}
這個函式首先檢查 IP 封包的 protocol 是否為 TCP,如果不是則傳回 0。然後,它檢查 TCP 封包的 payload 是否包含 “HTTP” 字串,如果是則傳回 1,否則傳回 0。
編譯和載入
Classifier 程式碼可以使用 Clang 編譯器編譯成 BPF 物件檔案:
clang -O2 -target bpf -c classifier.c -o classifier.o
然後,可以使用 tc
命令載入 BPF 物件檔案到 Traffic Control 中。
Traffic Control Return Codes
Traffic Control Classifier 可以傳回以下幾種程式碼:
TC_ACT_OK
(0): 允許封包透過TC_ACT_SHOT
(2): 拒絕封包TC_ACT_UNSPEC
(-1): 使用預設動作TC_ACT_PIPE
(3): 繼續處理下一個動作TC_ACT_RECLASSIFY
(1): 重新分類別封包
在本例中,Classifier 程式碼傳回 TC_ACT_OK
(0) 允許所有封包透過,並在收到 HTTP 封包時印出 debug 訊息。
BPF 程式在 Linux 網路流量控制中的應用
在 Linux 中,BPF(Berkeley Packet Filter)是一種強大的工具,允許使用者定義自己的網路流量控制邏輯。這個章節將介紹如何使用 BPF 程式來實作網路流量控制,並將其應用於 Linux 的 Traffic Control 中。
安裝 BPF 程式
首先,我們需要安裝 BPF 程式。假設我們已經編譯好了 BPF 程式,並將其儲存為 classifier.o
檔案。接下來,我們需要將其安裝到 Linux 的 Traffic Control 中。
# tc qdisc add dev eth0 handle 0: ingress
# tc filter add dev eth0 ingress bpf obj classifier.o flowid 0:
這兩個命令分別用於替換預設的 qdisc 和載入 BPF 分類別器。
測試 BPF 程式
接下來,我們需要測試 BPF 程式是否正常工作。首先,我們需要在目標介面上啟動一個 HTTP 伺服器。假設我們使用 Python 3 的 http.server
模組來啟動一個簡單的 HTTP 伺服器:
python3 -m http.server
然後,我們可以使用 curl
命令來測試 HTTP 伺服器:
curl http://localhost:8000
如果一切正常,我們應該可以看到 HTTP 回應。
偵錯 BPF 程式
如果我們需要偵錯 BPF 程式,可以使用 tc exec bpf dbg
命令來檢視偵錯資訊:
# tc exec bpf dbg
這個命令會顯示 BPF 程式的偵錯資訊,包括程式的執行狀態和任何錯誤資訊。
解除安裝 BPF 程式
當我們完成了 BPF 程式的測試和偵錯後,可以使用以下命令來解除安裝它:
# tc qdisc del dev eth0 ingress
這個命令會刪除我們之前安裝的 BPF 分類別器。
act_bpf 和 cls_bpf 的差異
在 Linux 中,還有一種叫做 act_bpf
的 BPF 物件,它是一種動作(action),而不是分類別器(classifier)。雖然 act_bpf
也可以用於網路流量控制,但它與 cls_bpf
有一些差異。主要差異在於 act_bpf
需要附加到一個分類別器上,而 cls_bpf
可以獨立工作。
Traffic Control 和 XDP 的差異
Traffic Control 和 XDP(eXpress Data Path)都是 Linux 中的網路流量控制工具,但它們有不同的設計目標和實作方式。Traffic Control 主要用於網路流量控制和管理,而 XDP 則著重於高效能的網路封包處理。XDP 程式可以在網路封包進入核心之前執行,因此可以更有效地處理網路流量。
Express Data Path(XDP)簡介
Express Data Path(XDP)是一種安全、可程式設計、高效能的Linux網路資料路徑封包處理器,當網路介面卡(NIC)驅動程式收到封包時,XDP會執行BPF程式。這使得XDP程式可以在最早的時間點對收到的封包做出決定(丟棄、修改或允許)。
XDP的設計特點
XDP程式的執行速度不僅取決於執行點,還有其他設計特點:
- 沒有記憶體組態:XDP在封包處理過程中不進行記憶體組態。
- 僅處理線性、未分片的封包:XDP程式僅與線性、未分片的封包合作,並具有封包的起始和結束指標。
- 無法存取完整的封包後設資料:由於這種限制,XDP程式接收的輸入上下文型別為
xdp_buff
,而不是sk_buff
結構。 - 有界執行時間:作為eBPF程式,XDP程式具有有界執行時間,這意味著其使用在網路管道中具有固定成本。
XDP和eBPF的關係
XDP程式透過bpf
系統呼叫控制,並使用BPF_PROG_TYPE_XDP
程式型別載入。此外,執行驅動hook執行BPF bytecode。
運作模式
XDP具有三種運作模式,以便於測試功能、自定義硬體和沒有自定義硬體的常規核心:
- Native XDP:預設模式,在此模式下,XDP BPF程式直接從網路驅動程式的早期接收路徑執行。
- Offloaded XDP:此模式將XDP程式解除安裝到硬體中。
- Generic XDP:此模式允許在沒有自定義硬體的情況下執行XDP程式。
XDP程式編譯和載入
XDP程式可以使用clang
編譯器編譯,並使用bpf
系統呼叫載入到核心中。
實際應用案例
XDP在多個領域具有廣泛的應用,包括網路安全、流量控制和效能最佳化。透過使用XDP,開發人員可以建立高效能、可程式設計的網路處理程式,以滿足特定的需求和要求。
網路驅動程式與 XDP 程式概覽
Linux 核心 4.18 支援多種網路驅動程式,包括:
- Broadcom NetXtreme-C/E 網路驅動程式(bnxt)
- Cavium 雷電驅動程式(thunderx)
- Intel i40 驅動程式
- Intel ixgbe 和 ixgbevf 驅動程式
- Mellanox mlx4 和 mlx5 驅動程式
- Netronome 網路流程處理器驅動程式
- QLogic qede 網路介面卡驅動程式
- TUN/TAP
- Virtio
這些驅動程式提供了對不同網路介面卡和裝置的支援,讓 Linux 核心可以與各種網路硬體進行通訊。
XDP(eXpress Data Path)是一種 Linux 核心網路框架,允許使用者空間程式直接與網路介面卡進行通訊,無需經過傳統的網路堆積疊。XDP 程式可以用於實作高效能的網路處理和封包轉發。
下面是 XDP 程式的概覽:
XDP 程式架構
XDP 程式由兩部分組成:使用者空間程式和核心空間程式。使用者空間程式負責定義 XDP 程式的邏輯和行為,而核心空間程式則負責執行 XDP 程式並與網路介面卡進行通訊。
XDP 程式型別
XDP 程式有兩種型別:原生 XDP 程式和解除安裝 XDP 程式。原生 XDP 程式直接在核心空間執行,而解除安裝 XDP 程式則在使用者空間執行,並使用核心提供的 API 與網路介面卡進行通訊。
XDP 程式優點
XDP 程式具有多個優點,包括:
- 高效能:XDP 程式可以直接與網路介面卡進行通訊,無需經過傳統的網路堆積疊,從而提高網路處理效能。
- 低延遲:XDP 程式可以實作低延遲的網路處理和封包轉發。
- 靈活性:XDP 程式可以用於實作各種網路功能,包括封包過濾、封包轉發和網路安全等。
XDP 程式設計與封包處理器
在 XDP(Express Data Path)模式中,BPF 程式可以直接在網路卡(NIC)上執行,或者在主機 CPU 上執行。當 XDP 程式在 NIC 上執行時,可以獲得更高的效能。要檢查哪些 NIC 驅動程式支援硬體解除安裝, podemos 使用以下命令:
git grep -l XDP_SETUP_PROG_HW drivers/
這個命令會輸出支援硬體解除安裝的 NIC 驅動程式清單。
通用 XDP
如果您沒有支援 XDP 的網路卡,可以使用通用 XDP 模式。這個模式允許您在不需要特定硬體的情況下撰寫和執行 XDP 程式。通用 XDP 支援自 Linux 4.12 版本開始,您可以在虛擬網路裝置(veth)上使用它。
封包處理器
封包處理器是 XDP 的核心元件,負責執行 BPF 程式並協調封包處理。它可以在接收(RX)佇列上直接處理封包,並允許您附加後處理判斷結果。封包處理器支援原子性程式更新和新程式載入,無需中斷網路服務。
XDP 可以在兩種模式下運作:忙碌輪詢(busy polling)模式和中斷驅動(interrupt driven)模式。在忙碌輪詢模式下,CPU 會被保留以處理每個 RX 佇列的封包,而在中斷驅動模式下,CPU 會被通知有新的封包需要處理。
XDP 結果碼
XDP 結果碼是封包處理器的輸出,指示如何處理封包。有五種結果碼:
- Drop(XDP_DROP):丟棄封包。
- Forward(XDP_TX):轉發封包。
- Redirect(XDP_REDIRECT):重新導向封包到另一個 NIC 或 BPF cpumap。
- Pass(XDP_PASS):將封包傳遞給正常的網路堆積疊進行處理。
每個結果碼都對應著不同的封包處理行為,允許您根據不同的需求進行封包處理。
程式碼範例
以下是使用 XDP 的簡單程式碼範例:
#include <linux/bpf.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
SEC("xdp")
int xdp_prog(struct xdp_md *ctx)
{
void *data = (void *)(long)ctx->data;
void *data_end = (void *)(long)ctx->data_end;
struct ethhdr *eth = data;
if (data + sizeof(*eth) > data_end)
return XDP_DROP;
// 封包處理邏輯
if (eth->h_proto == htons(ETH_P_IP)) {
struct iphdr *iph = data + sizeof(*eth);
if (iph->protocol == IPPROTO_TCP) {
return XDP_TX; // 轉發 TCP 封包
}
}
return XDP_DROP; // 丟棄其他封包
}
這個範例展示瞭如何使用 XDP 來過濾 TCP 封包並將其轉發。
圖表翻譯
以下是使用 Mermaid 語法繪製的 XDP 封包處理流程圖:
flowchart TD A[封包接收] --> B[封包處理器] B --> C{XDP 程式} C -->|轉發|> D[轉發封包] C -->|丟棄|> E[丟棄封包] C -->|重新導向|> F[重新導向封包] D --> G[正常網路堆積疊] E --> H[封包丟棄] F --> I[重新導向到另一個 NIC 或 BPF cpumap]
這個圖表展示了 XDP 封包處理流程,包括封包接收、封包處理器、XDP 程式、轉發、丟棄和重新導向等步驟。
XDP 程式設計概覽
在 Linux 網路封包處理中,eBPF (Extended Berkeley Packet Filter) 是一種強大的技術,允許開發人員建立自定義的網路封包處理程式。其中,XDP (eXpress Data Path) 是 eBPF 的一部分,專注於高效的網路封包處理。
XDP 動作碼
XDP 程式可以傳回不同的動作碼,以決定封包的命運。這些動作碼包括:
XDP_ABORTED
:表示 eBPF 程式錯誤,導致封包被丟棄。XDP_DROP
:封包被丟棄。XDP_PASS
:封包透過並繼續處理。XDP_TX
:封包被傳送回原始介面。XDP_REDIRECT
:封包被重定向到另一個介面。
這些動作碼在 linux/bpf.h
標頭檔中以列舉型別 (enum xdp_action
) 定義。
深入剖析 XDP 技術的核心架構後,我們可以發現,從底層網路驅動程式到 eBPF 程式設計的協同運作,展現了 XDP 在高效能網路封包處理上的優勢。藉由在網路卡上直接執行 BPF 程式,XDP 繞過了傳統網路堆積疊,大幅降低了處理延遲,並提升了吞吐量。多種 XDP 動作碼,例如 XDP_DROP
、XDP_TX
和 XDP_REDIRECT
,賦予了開發者高度的彈性,可以根據需求客製化封包處理邏輯。然而,XDP 也存在一些限制,例如有限的封包中繼資料存取和程式碼複雜度。
與傳統的 Traffic Control 相比,XDP 提供了更早期的封包處理時機和更低的處理開銷。不同於需要依賴 qdisc 和 filter 的 Traffic Control,XDP 直接在網路卡上執行程式碼,避免了不必要的資料複製和核心空間與使用者空間的切換。此外,XDP 的通用模式降低了使用門檻,即使沒有支援 XDP 的硬體,也能夠體驗其優勢。然而,XDP 程式設計的複雜度較高,需要開發者具備一定的 eBPF 程式設計經驗。
展望未來,隨著更多網路卡廠商支援 XDP 硬體解除安裝,以及 eBPF 技術的持續發展,XDP 的應用場景將更加廣泛。預計 XDP 將在網路安全、負載平衡、流量整形等領域發揮更大的作用,成為下一代高效能網路封包處理的關鍵技術。對於追求極致效能的網路應用,玄貓認為,XDP 值得深入研究和應用。技術團隊應著重於克服 XDP 程式設計的挑戰,才能充分釋放這項技術的巨大潛力。