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 套件的一部分,包括 iptc,分別用於操作網路介面和流量控制組態。

名詞解釋

由於流量控制和 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 的設定:noqueuefq_codelnoqueue。其中,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具有三種運作模式,以便於測試功能、自定義硬體和沒有自定義硬體的常規核心:

  1. Native XDP:預設模式,在此模式下,XDP BPF程式直接從網路驅動程式的早期接收路徑執行。
  2. Offloaded XDP:此模式將XDP程式解除安裝到硬體中。
  3. 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_DROPXDP_TXXDP_REDIRECT,賦予了開發者高度的彈性,可以根據需求客製化封包處理邏輯。然而,XDP 也存在一些限制,例如有限的封包中繼資料存取和程式碼複雜度。

與傳統的 Traffic Control 相比,XDP 提供了更早期的封包處理時機和更低的處理開銷。不同於需要依賴 qdisc 和 filter 的 Traffic Control,XDP 直接在網路卡上執行程式碼,避免了不必要的資料複製和核心空間與使用者空間的切換。此外,XDP 的通用模式降低了使用門檻,即使沒有支援 XDP 的硬體,也能夠體驗其優勢。然而,XDP 程式設計的複雜度較高,需要開發者具備一定的 eBPF 程式設計經驗。

展望未來,隨著更多網路卡廠商支援 XDP 硬體解除安裝,以及 eBPF 技術的持續發展,XDP 的應用場景將更加廣泛。預計 XDP 將在網路安全、負載平衡、流量整形等領域發揮更大的作用,成為下一代高效能網路封包處理的關鍵技術。對於追求極致效能的網路應用,玄貓認為,XDP 值得深入研究和應用。技術團隊應著重於克服 XDP 程式設計的挑戰,才能充分釋放這項技術的巨大潛力。