可程式設計網路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 點在網路驅動中,基於eBP 的事件驅動機制,當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/
進階參考
- cilium 容器網路:https://github.com/cilium/cilium
- polycube 網路加速:https://github.com/polycube-network/polycube