可編程網絡DataPath
大部分網絡數據的最終生產者和消費者都是應用程序,在一個計算機中,網絡數據包需要經過網卡 <=> 系統內核 <=> 應用程序,才能完成傳輸。
Linux 有嚴格的內核和用戶空間隔離,網絡數據在內核和應用程序之間的傳輸需要頻繁的進行上下文切換,隨之帶來額外的CPU cycle 開銷。所以為了提升網絡性能,在越來越多的SDN場景都采用了kernel-bypass的技術。其中具有代表性就是DPDK,不過DPDK 在帶來性能提升的同時,也有一些問題:
- 首先,因為改變了現有操作系統的工作方式,很難與現有操作系統集成
- 因為網絡路徑中沒有了操作系統,相關的網絡應用程序需要重新實現之前由操作系統提供的一些功能,例如路由表,4-7層網絡協議
- 一些由操作系統提供的熟悉的管理部署工具將不再可用,因為操作系統現在沒有相關網絡硬件的控制權
- 因為上面的原因帶來的復雜性
- 破壞了原有操作系統內核提供了的安全性,這一點在容器場景尤其重要,因為在容器場景中,資源的抽象和隔離主要是由操作系統內核提供的
- 需要消耗1個或者多個CPU核來專門處理網絡包
相對於DPDK,XDP具有以下優點
- 無需第三方代碼庫和許可
- 同時支持輪詢式和中斷式網絡
- 無需分配大頁
- 無需專用的CPU
- 無需定義新的安全網絡模型
XDP(eXpress Data Path)是近年興起的網絡數據面技術,為Linux內核提供了高性能、可編程的網絡數據通路。
不同於kernel-bypass技術,XDP 是在網絡包在還未進入網絡協議棧之前就處理,所以既沒有內核-用戶空間的切換開銷,又沒有保留了操作系統控制網絡硬件的能力。
XDP 的基本架構
XDP(eXpress Data Path)提供了一個內核態、高性能、可編程 BPF 包處理框架。
XDP 的處理方式在內核的RX 路徑上添加一個早期hook,讓用戶可以使用eBPF程序控制數據包。該hook 在中斷處理之后放置在NIC驅動程序中,並且在網絡堆棧本身的所有內存分配之前,因為內存分配可能是一項高成本的操作。由於這種設計,XDP 可以使用商用硬件每秒每核丟棄 2600 萬個數據包。
XDP 數據包進程(Packet Processor)包含一個內核組件,該組件通過功能接口直接從驅動程序處理 RX 數據包頁(packet-pages),而無需提前分配 skbuff 或軟件隊列(software queues)。通常,每個 RX 隊列分配一個 CPU,但在此模型中,沒有加鎖 RX 隊列,CPU 可以專用於忙輪詢或中斷模型。BPF 程序執行諸如數據包解析、表查找、創建/管理有狀態過濾器、封裝/去封裝數據包等處理。
XDP 系統由4個主要部分組成:
- XDP driver hook:這是XDP程序的接入點,當網絡數據包從硬件中收到時會被執行。
- eBPF virtual machine:執行XDP 程序的字節碼,並且JIT 編譯到機器碼
- BPF maps:key/value store,用來在整個XDP 系統中做數據的交互
- eBPF verifier:在程序加載到內核之前靜態的分析、檢查代碼,以確保代碼會Crash 或者損壞運行的內核。
GRO(Generic receive offload):通用receive offload,offload 詳見XDP 硬件要求小節。
RPS/RFS(Receive Package Steering / Receive Flow Steering):用以在軟件層面實現報文在多個cpu之間的負載均衡以及提高報文處理的緩存命中率。
XDP 的軟件要求
Linux kernel 4.8 開始支持XDP,XDP 依賴於eBPF ,所以需求較新的內核支持eBPF,可以參考eBPF 基礎架構及使用。
大部分支持 XDP 的驅動都支持在不會引起流量中斷(traffic interrupt)的前提下原子地替換運行中的程序。出於性能考慮,支持 XDP 的驅動只允許 attach 一個程序 ,不支持程序鏈(a chain of programs)。如果有必要的話,可以通過尾調用來對程序進行拆分,以達到與程序鏈類似的效果。
XDP 的硬件要求
使用XDP 對網卡有一些要求
- 支持多隊列的網卡
- 一般的協議通用offload
- TX/RX checksum offload,即校驗offload,利用網卡計算校驗和,而不是。
- Receive Side Scaling,RSS 即接收端伸縮,是一種網絡驅動程序技術,可在多處理器或多處理器核心之間有效分配接收到的網絡數據包並處理。
- Transport Segmentation Offload,TSO,即,是一種利用網卡替代CPU對大數據包進行分片,降低CPU負載的技術。
- 最好支持LRO,aRFS
目前越來越多的網卡設備開始支持offload特性,以便提升網絡收發和處理的性能。本文所描述的offload特性,主要是指將原本在協議棧中進行的IP分片、TCP分段、重組、checksum校驗等操作,轉移到網卡硬件中進行,降低系統CPU的消耗,提高處理性能。
XDP 的工作流程及使用
XDP 的工作模式
XDP 總共支持三種工作模式(operation mode):
- xdpdrv
xdpdrv 表示 native XDP(原生 XDP), 意味着 BPF 程序直接在驅動的接收路 徑上運行,理論上這是軟件層最早可以處理包的位置(the earliest possible point)。這是常規/傳統的 XDP 模式,需要驅動實現對 XDP 的支持,目前 Linux 內核中主流的 10G/40G 網卡都已經支持。
- xdpgeneric
xdpgeneric 表示 generic XDP(通用 XDP),用於給那些還沒有原生支持 XDP 的驅動進行試驗性測試。generic XDP hook 位於內核協議棧的主接收路徑(main receive path)上,接受的是 skb 格式的包,但由於 這些 hook 位於 ingress 路 徑的很后面(a much later point),因此與 native XDP 相比性能有明顯下降。因 此,xdpgeneric 大部分情況下只能用於試驗目的,很少用於生產環境。
- xdpoffload
最后,一些智能網卡(例如支持 Netronome’s nfp 驅動的網卡)實現了 xdpoffload 模式 ,允許將整個 BPF/XDP 程序 offload 到硬件,因此程序在網卡收到包時就直接在網卡進行處理。這提供了比native XDP 更高的性能,雖然在這種模式中某些 BPF map 類型和BPF 輔助函數是不能用的。BPF 校驗器檢測到這種情況時會直接報錯,告訴用戶哪些東西是不支持的。除了這些不支持的 BPF 特性之外,其他方面與 native XDP 都是一樣的。
引入XDP 前的DataPath:
引入XDP 之后的DataPath:包含native 模式、generic 模式和offload 模式
這三種模式 iproute2 都實現了,執行 ip link set dev em1 xdp obj [...] 命令時,內核會先嘗試以 native XDP 模 式加載程序,如果驅動不支持再自動回退到 generic XDP 模式。如果顯式指定了 xdpdrv 而不是 xdp,那驅動不支持 native XDP 時加載就會直接失敗,而不再嘗試 generic XDP 模式。
XDP 的工作流程
包含XDP 的Linux 網絡棧:
簡單的說,XDP 就是在網卡驅動中的hook點,在該hook點處打入eBPF 程序,利用eBPF 的事件驅動來完成網絡數據包處理:
- 高級語言程序設計,例如C 來完成。
- 編譯成eBPF 字節碼,llvm等工具已經支持了將C 語言編譯成eBPF 字節碼。
- 在加載到內核之前,會交給eBPF verifier 靜態的分析代碼的安全性。
- 加載到內核。
- 在收到網絡包時,通過JIT(Just In Time)編譯器翻譯成機器指令並執行。
在XDP 程序的結束,需要對packet做出一個結論。結論有4+1 種可能:
- XDP_DROP:直接丟包
- XDP_ABORTED:也是丟包,不過會觸發一個eBPF 程序錯誤,可以通過調試工具查看
- XDP_TX:將處理后的packet 發回給相同的網卡
- XDP_PASS:將處理后的packet 傳遞給內核協議棧
- XDP_REDIRECT 稍微復雜點,它會需要一個額外的參數來表明Redirect 的目的地,這個額外的參數是在XDP 程序返回之前通過一個helper 函數設置。這種方式使得Redirect 可以非常方便的擴展,增加新的Redirect目的地只需要再增加一個參數值即可。目前Redirect的目的地包含了以下幾種可能:
- 將處理后的packet轉發給一個不同的網卡,包括了轉發給連接虛擬機或者容器的虛擬網卡
- 將處理后的packet轉發給一個不同的CPU做進一步處理
- 將處理后的packet轉發給一個特定的用戶空間socket(AF_XDP),這種方式使得XDP也可以直接bypass網絡協議棧,甚至進一步結合zero-copy技術降低包處理的overhead
Hello World
利用eBPF ,嵌入eBPF 網絡數據包處理程序至XDP hook 點。
所以重點是如何編寫處理網絡數據包的程序。
// ping_drop.c
#include <linux/bpf.h>
#include <linux/in.h>
#include <linux/if_ether.h>
#include <linux/if_packet.h>
#include <linux/ip.h>
#define SEC(NAME) __attribute__((section(NAME), used))
SEC("prog") //prog 作為一個hook ,介於自定義處理程序和XDP之間
int ping_drop(struct xdp_md *ctx)
{
void *data = (void*)(long)ctx->data; //報文數據開始處
void *end = (void*)(long)ctx->data_end; //報文數據結束點
struct ethhdr *eh; //以太頭
eh = data;
if (data > end) //這個檢測有點多余,一個合格驅動會保證
return XDP_PASS; //data一定是小於end的
if ((void*)(eh+1) > end) //這個檢測非常重要,否則在下面讀取 eh->h_proto
return XDP_PASS; //的時候,無法通過bpf verifier的驗證,程序就無法加載
if (eh->h_proto != __constant_htons(ETH_P_IP)) //不是IP報文,放過
return XDP_PASS;
struct iphdr *iph;
iph = (void*)(eh + 1);
if ((void*)(iph+1) > end) //這里的檢測也非常重要,原因同上
return XDP_PASS;
if (iph->protocol == IPPROTO_ICMP) //判斷如果是ping報文,丟棄
return XDP_DROP; //返回 XDP_DROP,會導致無法ping通主機,其他如ssh等不受影響
return XDP_PASS;
}
char __license[] SEC("license") = "GPL";
XDP hook 點在網絡驅動中,基於eBPF 的事件驅動機制,當XDP 收到網絡數據包時,我們的處理程序就會被執行。
傳入eBPF 處理程序的ctx 其實就是XDP 元數據,沒有sk_buff結構,只有一個 struct xdp_md 指針
/* user accessible metadata for XDP packet hook
* new fields must be added to the end of this structure
*/
struct xdp_md {
__u32 data; //數據包開始指針
__u32 data_end; //數據包結束指針
__u32 data_meta; //初始階段它是一個空閑的內存地址,供XDP程序與其他層交換數據包元數據時使用。
/*
分別是接收數據包接口的索引和對應的RX 隊列的索引。當訪問這兩個值時,BPF 代碼會在內核內部重寫,
以訪問實際持有這些值的內核結構struct xdp_rxq_info。
*/
/* Below access go through struct xdp_rxq_info */
__u32 ingress_ifindex; /* rxq->dev->ifindex */
__u32 rx_queue_index; /* rxq->queue_index */
};
Clang 編譯生成對象文件,並加載
[root@dev ~]# clang -Wall -target bpf -c ping_drop.c -o ping_drop.o
然后用iproute2 里面的 ip link 命令加載到某個NIC 上,如ens192
[root@dev ~]# ip link set dev ens192 xdp object ping_drop.o
iproute2 需要打開 HAVE_ELF 這個宏,默認CentOS 7 並沒有,需要編譯iproute2 並打開。
git clone git://git.kernel.org/pub/pub/scm/network/iproute2/iproute2.git
cd iproute2/
./configure --prefix=/usr
make -j8 && make install
BPF map 和程序作為內核資源只能通過文件描述符訪問,其背后是內核中的匿名 inode。如 iproute2,其中的 tc 或 XDP 在准備 環境、加載程序到內核之后最終會退出。在這種情況下,從用戶空間也無法訪問這些 map 了,而本來這些 map 其實是很有用的,所以內核實現了一個最小內核空間 BPF 文件系統,BPF map 和 BPF 程序 都可以釘到(pin)這個文件系統內,這個過程稱為 object pinning(釘住對象)。
掛載BPF FS,允許BPF 程序從虛擬文件系統固定和獲取map
mount -t bpf /sys/fs/bpf /sys/fs/bpf
ip link set dev ens192 xdp object ping_drop.o # 之后,在其他節點ping 當前節點就會顯示無法ping 通。
ip link set dev ens192 xdp off 之后,ping # 之后,恢復正常。
XDP 的應用
借助XDP/BPF,可以快速、高效、超低副作用的處理網絡數據包,可以在以下幾個方面應用:
- 防御DDoS 攻擊
- CDN 服務調優:CDN 服務突刺分析及處理
- QoS:控制網絡傳輸速率,如 tc
參考
- https://github.com/xdp-project/xdp-tutorial
- https://github.com/iovisor/bpf-docs/blob/master/Express_Data_Path.pdf
- https://zhuanlan.zhihu.com/p/321387418
- https://www.iovisor.org/technology/xdp
- https://docs.cilium.io/en/v1.10/bpf/
- https://arthurchiao.art/blog/cilium-bpf-xdp-reference-guide-zh/
- https://www.kernel.org/doc/html/latest/networking/checksum-offloads.html
- https://blogs.igalia.com/dpino/2019/01/10/the-express-data-path/
- https://cloud.tencent.com/developer/article/1626925
進階參考
- cilium 容器網絡:https://github.com/cilium/cilium
- polycube 網絡加速:https://github.com/polycube-network/polycube