深入理解 XDP 全景指南

譯者序

本文翻譯自 2018 年 ACM CoNEXT 大會上的一篇文章:

The eXpress Data Path: Fast Programmable Packet Processing in the Operating System Kernel

作者陣容豪華,包括來自 Cilium 的 Daniel Borkmann、John Fastabend 等。

論文引用信息:

Toke Høiland-Jørgensen, Jesper Dangaard Brouer, Daniel Borkmann, John Fastabend, Tom Herbert, David Ahern, and David Miller. 2018. The eXpress Data Path: Fast Programmable Packet Processing in the Operating System Kernel. In CoNEXT ’18: International Conference on emerging Networking EXperiments and Technologies, December 4–7, 2018, Heraklion, Greece. ACM, New York, NY, USA, 13 pages. https://doi.org/10.1145/3281411.3281443

由於譯者水平有限,本文不免存在遺漏或錯誤之處。如有疑問,請查閱原文。

以下是譯文。

摘要

近些年業界流行通過內核旁路(kernel bypass)的方式實現 可編程的包處理過程(programmable packet processing)。實現方式是 將網絡硬件完全交由某個專門的用戶空間應用(userspace application) 接管,從而避免內核和用戶態上下文切換的昂貴性能開銷。

但是,操作系統被旁路(繞過)之後,它的應用隔離(application isolation) 和安全機制(security mechanisms)就都失效了;一起失效的還有各種經過已經 充分測試的配置、部署和管理工具。

爲解決這個問題,我們提出一種新的可編程包處理方式:eXpress Data Path (XDP)。

爲展示 XDP 靈活的編程模型,本文還將給出三個程序示例,

  1. layer-3 routing(三層路由轉發)

  2. inline DDoS protection(DDoS 防護)

  3. layer-4 load balancing(四層負載均衡)

1 引言

軟件實現高性能包處理的場景,對每個包的處理耗時有着極高的要求。通用目的操作系統中 的網絡棧更多是針對靈活性的優化,這意味着它們花在每個包上 的指令太多了,不適合網絡高吞吐的場景。

因此,隨後出現了一些專門用於包處理的軟件開發工具,例如 Data Plane Development Kit (DPDK) [16]。這些工具一般都會完全繞過內核,將網絡硬件直接交 給用戶態的網絡應用,並需要獨佔一個或多個 CPU。

1.1 現有方案(kernel bypass)存在的問題

內核旁路方式可以顯著提升性能,但缺點也很明顯:

1.2 新方案:給內核網絡棧添加可編程能力

對此,本文提供了另一種解決方案:給內核網絡棧添加可編程能力。這使得我們能在 兼容各種現有系統、複用已有網絡基礎設施的前提下,仍然實現高速包處理。 這個框架稱爲 XDP,

XDP 已經在過去的幾個內核 release 中逐漸合併到內核,但在本文之前,還沒有關於 XDP 系統的 完整架構介紹。本文將對 XDP 做一個高層介紹。

1.3 新方案(XDP)的優點

我們的測試結果顯示 XDP 能取得 24Mpps/core 的處理性能,這雖然與 DPDK 還有差距, 但相比於後者這種 kernel bypass 的方式,XDP 有非常多的優勢。

具體地,XDP:

  1. 與內核網絡棧協同工作,將硬件的控制權完全留在內核範圍內。帶來的好處:
  1. 無需任何特殊硬件特性,任何有 Linux 驅動的網卡都可以支持, 現有的驅動只需做一些修改,就能支持 XDP hooks。

  2. 可以選擇性地複用內核網絡棧中的現有功能,例如路由表或 TCP/IP 協議棧,在保持配置接口不變的前提下,加速關鍵性能路徑(critical performance paths)。

  3. 保證 eBPF 指令集和 XDP 相關的編程接口(API)的穩定性。

  4. 與常規 socket 層交互時,沒有從用戶態將包重新注入內核的昂貴開銷。

  5. 對應用透明。這創造了一些新的部署場景 / 方式,例如直接在應用所 在的服務器上部署 DoS 防禦(而非中心式 / 網關式 DoS 防禦)。

  6. 服務不中斷的前提下動態重新編程(dynamically re-program), 這意味着可以按需加入或移除功能,而不會引起任何流量中斷,也能動態響應系統其他部分的的變化。

  7. 無需預留專門的 CPU 做包處理,這意味着 CPU 功耗與流量高低直接相關,更節能。

1.4 本文組織結構

接下來的內容介紹 XDP 的設計,並做一些性能分析。結構組織如下:

2 相關工作

XDP 當然不是第一個支持可編程包處理的系統 —— 這一領域在過去幾年發展勢頭良好, 並且趨勢還在持續。業內已經有了幾種可編程包處理框架,以及基於這些框架的新型應用,包括:

要基於通用(Common Off The Shelf,COTS)硬件實現高性能包處理,就必須解決 網卡(NIC)和包處理程序之間的所有瓶頸。由於性能瓶頸主要來源於內核和用戶態應用之間的接口(系統調用開銷非常大,另外,內核功能豐富,但也非常複雜), 低層(low-level)框架必須通過這樣或那樣的方式來降低這些開銷。

現有的一些框架通過幾種不同的方式實現了高性能,XDP 構建在其中一些技術之上。 接下來對 XDP 和它們的異同做一些比較分析。

2.1 用戶態輪詢 vs. XDP

在現有的所有框架中,內核旁路方式性能是最高的 [18];但如引言中指 出,這種方式在管理、維護和安全方面都存在不足。

XDP 採用了一種與內核旁路截然相反的方式:相比於將網絡硬件的控制權上移到用戶空間, XDP 將性能攸關的包處理操作直接放在內核中,在操作系統的網絡棧之前執行。

這裏的主要創新是:使用了一個虛擬執行環境,它能對加載的 程序進行校驗,確保它們不會對內核造成破壞。

2.2 內核模塊 vs. XDP

在 XDP 之前,以內核模塊(kernel module)方式實現包處理功能代價非常高, 因爲程序執行出錯時可能會導致整個系統崩潰,而且內核的內部 API 也會隨着時間發生變化。 因此也就不難理解爲什麼只有很少的系統採用了這種方式。其中做的比較好包括

這幾個系統都支持靈活的配置,適用於多種場景,取得比較小的平攤代價。

XDP 通過:

  1. 提供一個安全的執行環境,以及內核社區支持,提供與那些暴露到用戶空間一樣穩定的內核 API

    極大地降低了那些將處理過程下沉到內核的應用(applications of moving processing into the kernel)的成本。

  2. 此外,XDP 程序也能夠完全繞過內核網絡棧(completely bypass), 與在內核網絡棧中做 hook 的傳統內核模塊相比,性能也更高。

  3. XDP 除了能將處理過程下沉到內核以獲得最高性能之外,還支持在程序中執行重定向 (redirection)操作,完全繞過內核網絡棧,將包送到特殊類型的用戶空間 socket; 甚至能工作在 zero-copy 模式,進一步降低開銷。

內核模塊方式的另一個例子是 Packet I/O engine,這是 PacketShader [19] 的組成部分, 後者專用於 Arrakis [43] and ClickOS [36] 之類的特殊目的操作系統。

2.3 可編程硬件 vs. XDP

可編程硬件設備也是一種實現高性能包處理的方式。

某種意義上來說,XDP 可以認爲是一種 offload 方式:

  1. 性能敏感的處理邏輯下放到網卡驅動中,以提升性能;

  2. 其他的處理邏輯仍然走內核網絡棧;

  3. 如果沒有用到內核 helper 函數,那整個 XDP 程序都可以 offload 到網卡(目前 Netronome smart-NICs [27] 已經支持)。

2.4 小結

XDP 提供了一種高性能包處理方式,與已有方式相比,在性能、與現有系統的集成、靈活性 等方面取得了更好的平衡。接下來介紹 XDP 是如何取得這種平衡的。

3 XDP 設計

XDP 的設計理念:

這種與內核的深度集成顯然會給設計帶來一些限制,在 XDP 組件合併到 Linux 的過程中,我們也收到了許多來自社區的反饋,促使我們不斷調整 XDP 的設計,但 這些設計反思不在本文討論範圍之內。

3.0 XDP 系統架構

圖 1 描繪了整個 XDP 系統,四個主要組成部分:

  1. XDP driver hook:XDP 程序的主入口,在網卡收到包執行。

  2. eBPF virtual machine:執行 XDP 程序的字節碼,以及對字節碼執行 JIT 以提升性能。

  3. BPF maps:內核中的 key/value 存儲,作爲圖中各系統的主要通信通道。

  4. eBPF verifier:加載程序時對其執行靜態驗證,以確保它們不會導致內核崩潰。

Fig 1. XDP 與 Linux 網絡棧的集成。這裏只畫了 ingress 路徑,以免圖過於複雜。

上圖是 ingress 流程。網卡收到包之後,在處理包數據(packet data)之前,會先執行 main XDP hook 中的 eBPF 程序。 這段程序可以選擇:

  1. 丟棄(drop)這個包;或者

  2. 通過當前網卡將包再發送(send)出去;或者

  3. 將包重定向(redirect)到其他網絡接口(包括虛擬機的虛擬網卡),或者通過 AF_XDP socket 重定向到用戶空間;或者

  4. 放行(allow)這個包,如果後面沒有其他原因導致的 drop,這個包就會進入常規的內核網絡棧。如果是這種情況,也就是放行包進入內核網絡棧,那接下來在將包放到發送隊列之前(before packets are queued for transmission), 還有一個能執行 BPF 程序的地方:TC BPF hook。

此外,圖 1 中還可以看出,不同的 eBPF 程序之間、eBPF 程序和用戶空間應用之間,都能夠通過 BPF maps 進行通信。

3.1 XDP driver hook

在設備驅動中執行,無需上下文切換

XDP 程序在網絡設備驅動中執行,網絡設備每收到一個包,程序就執行一次。

相關代碼實現爲一個內核庫函數(library function),因此程序直接 在設備驅動中執行,無需切換到用戶空間上下文。

在軟件最早能處理包的位置執行,性能最優

回到上面圖 1 可以看到:程序在網卡收到包之後最早能處理包的位置 執行 —— 此時內核還沒有爲包分配 struct sk_buff 結構體, 也沒有執行任何解析包的操作。

XDP 程序典型執行流

下圖是一個典型的 XDP 程序執行流:

Fig 2. 典型 XDP 程序的執行流。

網卡收到一個包時,XDP 程序依次執行:

  1. 提取包頭中的信息(例如 IP、MAC、Port、Proto 等),

    執行到程序時,系統會傳遞給它一個上下文對象(context object)作爲參賽 (即 struct xdp_md *ctx,後面有例子),其中包括了指向原 始包數據的指針,以及描述這個包是從哪個網卡的哪個接口接收上來的等元數據字段。

  2. 讀取或更新一些資源的元信息(例如更新統計信息);

    解析包數據之後,XDP 程序可以讀取 ctx 中的包元數據(packet metadata) 字段,例如從哪個網卡的哪個接口收上來的(ifindex)。除此之外,ctx 對象還允許 程序訪問與包數據毗鄰的一塊特殊內存區域(cb, control buffer), 在包穿越整個系統的過程中,可以將自定義的數據塞在這裏。

    除了 per-packet metadata,XDP 程序還可以通過 BPF map 定義和訪問自己的持久數據 ,以及通過各種 helper 函數訪問內核基礎設施。

  1. 如果有需要,對這個包進行 rewrite header 操作,

    程序能修改包數據的任何部分,包括添加或刪除包頭。這使得 XDP 程序能執行封裝 / 接封裝操作,以及重寫(rewrite)地址字段然後轉發等操作。

    內核 helper 函數各有不同用途,例如修改一個包之後,計算新的校驗和(checksum)。

  2. 進行最後的判決(verdict),確定接下來對這個包執行什麼操作;

    判決結果包括:

    重定向功能的用途:

    這些不同的路徑,在圖 1 對應的是幾條實線。

    將重定向判決(verdict)與重定向目標(target)分開,使得重定向目標類型很容易擴展; 另外,由於重定向參數(目標)是通過 BPF map 查詢的,因此無需修 改 XDP 程序,就能動態修改重定向目標。

  1. 將原始包通過另一個網卡(包括虛擬機的虛擬網卡)發送出去;

  2. 轉發給指定 CPU 做進一步處理;

  3. 轉發給 AF_XDP 類型的 socket 做進一步處理;

程序還能通過尾調用(tail call),將控制權交給另一個 XDP 程序; 通過這種方式,可以將一個大程序拆分成幾個邏輯上的小程序(例如,根據 IPv4/IPv6)。

由於 XDP 程序可包含任意指令,因此前三步(讀取包數據、處理元數據、重寫包數據) 順序可以是任意的,而且支持多層嵌套。 但實際中爲了獲得高性能,大部分情況下還是將執行結構組織成這順序的三步。

3.2 eBPF 虛擬機

XDP 程序在 Extended BPF (eBPF) 虛擬機中執行。eBPF 是早期 BSD packet filter (BPF) [37] 的擴展,後者在過去的幾十年中廣泛 應用於各種包處理工具。

BPF 使用 基於寄存器的(register-based) virtual machine 來描述 過濾動作(filtering actions)。

eBPF 虛擬機支持動態加載(loading)和重加載(re-loading)程序,內核管理所有 BPF 程序的生命週期。

3.3 BPF maps

eBPF 程序在觸發內核事件時執行(例如,觸發 XDP 程序執行的,是收包事件)。 程序每次執行時,初始狀態都是相同的(即程序是無狀態的),它們無法直接訪問 內核中的持久存儲(BPF map)。爲此,內核提供了訪問 BPF map 的 helper 函數。

BPF map 是 key/value 存儲,在加載 eBPF 程序時定義(defined upon loading an eBPF program)。

用途:

  1. 持久存儲。例如一個 eBPF 程序每次執行時,都會從裏面獲取上一次的狀態。

  2. 用於協調兩個或多個 eBPF 程序。例如一個往裏面寫數據,一個從裏面讀數據。

  3. 用於用戶態程序和內核 eBPF 程序之間的通信。

3.4 eBPF verifier

唯一加載入口:bpf() 系統調用

由於 eBPF 代碼直接運行在內核地址空間,因此它能直接訪問 —— 也可 能是破壞 —— 任何內存。爲防止這種情況發生,內核規定只能通過唯一入口( bpf() 系統調用)加載 BPF 程序。

加載 BPF 程序時,位於內核中的校驗器首先會對字節碼程序進行靜態分析,以確保

校驗器工作原理:two-pass DAG

校驗器的工作原理:首先根據程序的控制流構建一個有向無環圖(DAG), 然後對 DAG 執行如下校驗:

內存越界和空指針檢查:職責上移到程序自身 / 開發者

這種跟蹤寄存器狀態的機制是爲了在無法預知內存邊界的情況下,仍然確保程序 的內存訪問不會越界。無法預知內存邊界是因爲:

爲解決這個問題,校驗器會檢查已加載的程序自身是否會做如下檢查:

  1. 解引用指針前做了內存邊界檢查,

  2. 查詢 map 之前是檢查了 map 指針是否爲空。

這種方式將處理邏輯中的安全檢查和遇到錯誤時如何處理的控制權都 交給了 BPF 程序的編寫者。

跟蹤數據訪問操作和值範圍

爲跟蹤數據訪問,校驗器會跟蹤

  1. 數據類型

  2. 指針偏置(pointer offsets)

  3. 所有寄存器的可能值範圍

程序開始時,

接下來程序每執行一步,寄存器狀態就會更新一次。當寄存器中存入一個新值時,這個寄存器 還會繼承與這個值相關的狀態變量(inherits the state variables from the source of the value)。

算術操作會影響標量類型的值的範圍(value ranges of scalar types),以及指針類型的 offset。 可能的最大範圍(max possible range)存儲在狀態變量中,例如往寄存器中 load 一個字節時, 這個寄存器的可能值範圍就設置爲 0~255。指令圖(instruction graph)中的 各邏輯分支就會根據操作結果更新寄存器狀態。例如,比較操作 R1 > 10

不同類型數據的校驗信息來源(source of truth)

利用狀態變量中存儲的範圍信息,校驗器就能預測每個 load 指令能訪問的所有 內存範圍,確保它執行的都是合法內存訪問。

  1. 對於包數據(packet data)的訪問,會與 context 對象中的 data_end 變量做比較;

  2. 對於 BPF map 中獲取的值,或用到 map 定義中聲明的 data size 信息;

  3. 對於棧上存儲的值,會檢查狀態變量中記錄的值範圍;

  4. 對於指針算術操作(pointer arithmetic)還會施加額外的限制,指針通常不能被轉換成整形值。

只要校驗器無法證明某個操作是安全,該 BPF 程序在加載時(load time)就會被拒絕。 除此之外,校驗器還會利用範圍信息確保內存的對齊訪問(enforce aligned memory access)。

校驗器的目的

需要說明的是,校驗器的目的是避免將內核內部(the internals of the kernel )暴露給惡意或有缺陷的 eBPF 程序,而非確保程序中函數的實現已經是最高效的。

換句話說,如果 XDP 程序中處理邏輯過多,也可能會導致機器變慢 ;如果代碼寫的有問題,也可能會破壞包數據。出於這些原因,加載 BPF 程序需要 管理員權限(root)。避免這些 bug 的責任在程序員,但選擇將哪些程序加載 到系統的權限在管理員。

3.5 XDP 程序示例

下面是一個簡單的 XDP 程序,展示了前面介紹的一些特性。 程序會解析包數據,判斷如果是 UDP 包,直接交換源和目的 MAC 地址,然後將包從相同網卡再發送回去,

雖然這是一個非常簡單的例子,但真實世界中的 XDP 程序用到的組件和特性,這裏基本都具備了。

// 從內核 BPF 代碼示例 xdp2_kern.c 修改而來。

1 // 用於統計包數
2 struct bpf_map_def SEC("maps") rxcnt = {
3     .type = BPF_MAP_TYPE_PERCPU_ARRAY,
4     .key_size = sizeof(u32),     // IP 協議類型,即 IPv4/IPv6
5     .value_size = sizeof(long),  // 包數
6     .max_entries = 256,
7 };
8
9 // 直接操作包數據(direct packet data access),交換 MAC 地址
10 static void swap_src_dst_mac(void *data)
11 {
12     unsigned short *p = data;
13     unsigned short dst[3];
14     dst[0] = p[0]; dst[1] = p[1]; dst[2] = p[2];
15     p[0] = p[3]; p[1] = p[4]; p[2] = p[5];
16     p[3] = dst[0]; p[4] = dst[1]; p[5] = dst[2];
17 }
18
19 static int parse_ipv4(void *data, u64 nh_off, void *data_end)
20 {
21     struct iphdr *iph = data + nh_off;
22     if (iph + 1 > data_end)
23         return 0;
24     return iph->protocol;
25 }
26
27 SEC("xdp1") // marks main eBPF program entry point
28 int xdp_prog1(struct xdp_md *ctx)
29 {
30     void *data_end = (void *)(long)ctx->data_end;
31     void *data = (void *)(long)ctx->data;
32     struct ethhdr *eth = data; int rc = XDP_DROP;
33     long *value; u16 h_proto; u64 nh_off; u32 ipproto;
34
35     nh_off = sizeof(*eth);
36     if (data + nh_off > data_end)
37         return rc;
38
39     h_proto = eth->h_proto;
40
41     /* check VLAN tag; could be repeated to support double-tagged VLAN */
42     if (h_proto == htons(ETH_P_8021Q) || h_proto == htons(ETH_P_8021AD)) {
43         struct vlan_hdr *vhdr;
44
45         vhdr = data + nh_off;
46         nh_off += sizeof(struct vlan_hdr);
47         if (data + nh_off > data_end)
48             return rc;
49         h_proto = vhdr->h_vlan_encapsulated_proto;
50     }
51
52     if (h_proto == htons(ETH_P_IP))
53         ipproto = parse_ipv4(data, nh_off, data_end);
54     else if (h_proto == htons(ETH_P_IPV6))
55         ipproto = parse_ipv6(data, nh_off, data_end);
56     else
57         ipproto = 0;
58
59     /* lookup map element for ip protocol, used for packet counter */
60     value = bpf_map_lookup_elem(&rxcnt, &ipproto);
61     if (value)
62         *value += 1;
63
64     /* swap MAC addrs for UDP packets, transmit out this interface */
65     if (ipproto == IPPROTO_UDP) {
66         swap_src_dst_mac(data);
67         rc = XDP_TX;
68     }
69     return rc;
70 }

具體地:

將這段程序安裝到網卡接口上時,它首先會被編譯成 eBPF 字節碼,然後經受校驗器檢查。 這裏的檢查項包括:

  1. 無循環操作;程序大小(指令數量);

  2. 訪問包數據之前,做了內存邊界檢查;

  3. 傳遞給 map lookup 函數的參數,類型與 map 定義相匹配;

  4. map lookup 的返回值(value 的內存地址)在使用之前,檢查了是否爲 NULL。

3.6 小結

XDP 系統由四個主要部分組成:

  1. XDP device driver hook:網卡收到包之後直接運行;

  2. eBPF 虛擬機:執行 XDP 程序(以及內核其他模塊加載的 BPF 程序);

  3. BPF maps:使不同 BPF 程序之間、BPF 程序與用戶空間應用之間能夠通信;

  4. eBPF verifier:確保程序不包含任何可能會破壞內核的操作。

這四部分加在一起,創造了一個編寫自定義包處理應用的強大環境,它能加速包處理的關鍵 路徑,同時還與內核及現有基礎設施密切集成。

接下來看一下 XDP 應用的性能。

4 性能評估

DPDK 是目前性能最高的包處理框架 [18],因此本文將 XDP 與 DPDK 及 Linux 內核網絡 棧的性能做一個對比。測試機器環境:

在測試中,我們主要關心三個 metric:

我們已經驗證,使用 MTU(1500 字節)包時,我們的系統單核就能達到線速(100 Gbps), 而且 CPU 有 50% 是空閒的。顯然,真正的挑戰在於 PPS,而非帶寬,其他一些測試也已經指出了這一點 [46]。 出於這個原因,我們用最小包(64 字節)測試,衡量指標是 PPS。

對於 XDP 和 Linux 內核網絡棧的測試,由於它們沒有顯式指定某些 CPU 來處理網絡包的方式,因此我們通過配置硬件 RSS(Receive Side Scaling)來講流量定向到指定 CPU。

對網卡、內核的一些配置調優,見代碼倉庫 [22]。

4.1 直接棄包(packet drop)性能

Fig 3. 直接棄包(packet drop)性能。DPDK 需要預留一個 CPU 運行控制任務,因此只剩下 5 個 CPU 做包處理。

上圖是性能與 CPU 數量的關係。

再看圖中 Linux 網絡棧在兩種配置下的性能:

  1. 通過 iptables 的 raw table 丟棄流量,這是 Linux 網絡棧中最早能丟棄包的地方;

  2. 通過 conntrack(連接跟蹤)模塊,這個模塊的開銷非常大,但在很多 Linux 發行版中都是默認開啓的。

conntrack 模式達到了 1.8Mpps/core,raw 模式是 4.8Mpps/core ;這兩種模式均未達到硬件瓶頸。 最終的性能,XDP 比常規網絡棧的最快方式快了 5 倍。

Linux raw mode test 中,我們還測量了 XDP 程序不丟棄包,而是更新包數統計然後將包 送到內核網絡棧的場景。 這種情況下,XDP 單核的處理性能會下降到 4.5Mpps/core,有 13.3ns 處理延遲。 圖中並未給出這個測試結果,因爲這個開銷太小了。

4.2 CPU Usage

Fig 4. 直接棄包(packet drop)場景下的 CPU 利用率。

用系統提供的 mpstat 命令測量 CPU 利用率。結果如圖 4 。

4.3 包轉發性能

這個測試中,轉發應用執行非常簡單的 MAC 地址重寫:直接交換源和目的 MAC 地址,然後轉發。 這是轉發場景下最精簡的步驟了,因此結果代表了所有真實轉發應用的性能上限。

轉發吞吐(pps)

Fig 5. 轉發性能。在同一網卡接口上收發會佔用同一 PCI port 的帶寬, 這意味着在 70Mpps XDP same-nic 組就已經達到了 PCI 總線的瓶頸

如圖 5 所示,性能隨 CPU 數量線性擴展,直到達到全局性能瓶頸。XDP 在同網卡轉發的性能遠高於 DPDK 異網卡性能,原因是內存處理方式不同:

轉發延遲

表 1. 轉發延遲。機器網卡的兩個接口直連,在轉發速率分別爲 100pps 和 1Mpps 的條件下,持續 50s 測量端到端延遲

高 pps 場景下,XDP 的延遲已經接近 DPDK。但在低 pps 場景下,XDP 延遲比 DPDK 大的多,原因是 XDP 是基於中斷的,中斷處理時間( interrupt processing time)此時佔大頭;而 DPDK 是輪詢模式,延遲相對比較固定。

4.4 討論:XDP 性能與 DPDK 還有差距的原因

XDP 未做底層代碼優化

上一節已經看到,XDP 相比於常規 Linux 網絡棧性能有了顯著提升。但對於大部分 XDP 場景來說,性能還是與 DPDK 有差距。我們認爲,這是主要是因爲 DPDK 做了相當多的底層 代碼優化。舉個例子來解釋,考慮 packet drop 例子:

多出來的 18.7ns 在我們的 3.6GHz 機器上對應 67 個時鐘週期。因此,很顯然 每個很小的優化在這裏都會產生很大的影響。例如,我們測量出在測試 機器上,每次函數調用需要 1.3ns。mlx5 驅動處理每個包都有 10 次 函數調用,總計就是 13ns。

通用目的操作系統,首要目標:更好的擴展和配置,而非極致性能

另外,在 Linux 這樣的通用目的操作系統中,某些開銷是不可避免的, 因爲設備驅動或子系統的組織方式是爲了實現更好的擴展和配置,而非極致性能。

但是,我們認爲有些優化還是有必要的。例如,我們嘗試將內核中與測試網卡無關的 DMA 函數調用刪掉, 這樣將前面提到的 10 個函數調用降低到了 6 個,測試結果顯示這將單核性能提升到了 29Mpps/core。 依此推測的話,將另外 6 個函數調用也優化掉,能將 XDP 的性能提升到 37.6Mpps。 實際上我們不可能將 6 個全部去掉,但去掉其中幾個,再加上一些其他優化,我 們相信 XDP 和 DPDK 的性能差距將越來越小。

其他驅動的測試結果也是類似的,例如 i40e driver for 40 Gbps Intel cards。

基於以上討論,我們相信未來 XDP 與 DPDK 的性能差距將越來越小。

另一方面,考慮到 XDP 在靈活性和與內核集成方面的優勢, XDP 已經是很多實際場景中的非常有競爭力的方式。下文給出幾個例子。

5 真實場景使用案例

本節給出三個例子來具體展示 XDP 在真實世界中的應用。 這幾個案例都是已經真實在用的,但本文出於解釋目的,將使用簡化的版本。 同時也建議讀者參考 [38],後者是獨立的文章,介紹使用 XDP 解決實際工作中網絡服務所面臨的一些挑戰。

本節目的是展示真實 XDP 方案的可行性,因此不會將重點放在與業界最新的實現做詳盡性能對比上。 我們會拿常規的 Linux 內核網絡棧的性能作爲 baseline,來對比 XDP 應用的性能。

5.1 案例一:軟件路由(software routing)

內核數據平面 & 控制平面(BIRD/FRR)

Linux 內核實現了一個功能完整的路由表,作爲數據平面,支持

對於控制平面,Bird [10] 或 FRR [17] 這樣的路由守護進程( routing daemons)實現了多種路由控制平面協議。Linux 提供的這套生態系統功能如此豐富 ,因此再在另一個包處理框架中重新實現一套類似的路由棧代價將非常高, 更實際的方式是對 Linux 內核的數據平面進行優化。

XDP:直接查詢內核路由表並轉發

XDP 非常適合做這件事情,尤其是它提供了一個 helper 函數,能從 XDP 程序中直接查詢內核路由表。

測試:XDP routing + 全球 BGP 路由表

爲展示 XDP 路由的性能,我們用 Linux 內核代碼中的 XDP routing 例子 [1],與常規 Linux 內核網絡棧的性能做對比。 兩組測試:

  1. 路由表中只有一條路由;

  2. 路由表中有從 routeviews.org 中 dump 而來的全球 BGP 路由表(global BGP routing table)。 包含 752,138 條路由。隨機生成 4000 個目的 IP 地址,以確保能充分利用到這種路由表。

    如果目的 IP 地址少於 4000 個,實際用到的路由表部分會較小,能夠保存在 CPU 緩存中,使得結果不準確。 增大 IP 數量至 4000 個以上,不會對轉發性能造成影響,但可以避免緩存導致的結果不准問題。

對於兩組測試,下一跳 MAC 地址都是與我們的發送網卡直接相關的接口的地址。

性能:2.5x

Fig 6. 軟件路由的性能。由於性能隨核數線性增加,這裏只給出單核的結果。

測試結果如上圖所示。

這說明,XDP 路由程序 + 單核 + 10Gbps 網卡 的軟硬件配置,就能 處理整張全球 BGP 路由表(保守估計每個包平均 300 字節)。

5.2 案例二:Inline DoS Mitigation

DoS 攻擊還是像瘟疫一樣糾纏着互聯網,現在通常的方式是:通過已經入侵的大量設備發起分佈式(DDoS)攻擊。

有了 XDP 之後,我們能直接在應用服務器(application servers)上 部署包過濾程序來防禦此類攻擊(inline DoS mitigation), 無需修改應用代碼。如果應用是部署在虛擬機裏,那 XDP 程序還可以 部署在宿主機(hypervisor)上,這樣單個程序就能保護機器上所有的虛擬機。

模擬 Cloudflare 防禦架構

爲展示工作原理,我們用 XDP 作爲過濾機制,模擬 Cloudflare 的 DDoS 防禦架構 [6]。 他們的 Gatebot architecture ,首先在各 PoP 點機器上採樣,然後統一收起來做分析, 根據分析結果生成防禦規則。

防禦規則的形式是對包數據(payload)進行一系列簡單檢查, 能直接編譯成 eBPF 代碼然後分發到 PoP 點的所有服務器上。這裏說的代碼是 XDP 程序 ,它會將匹配到規則的所有流量丟棄,同時將統計信息更新到 BPF map。

程序邏輯

爲驗證這種方案的性能,我們編寫一個 XDP 程序,它

  1. 解析包頭,執行一些簡單驗證。對每個包:執行四次讀取操作,以解析外層包頭。

  2. 將符合攻擊特性的流量丟棄。具體:丟棄 UDP + 特定端口的流量。

  3. 將其他流量通過 CPU redirect 方式重定向給另一個 CPU 做進一步處理;

性能

我們用 netperf 做性能壓測 [26]。

Fig 7. DDoS 性能。業務吞吐(TPS)隨攻擊流量的變化。

結果如上圖所示,

以上結果表明,XDP 防禦 DDoS 攻擊在實際中是完全可行的,單核就能輕鬆處理 10Gbps 的、都是最小包(minimum-packet)的 DoS 流量。 這種 DDoS 防禦的部署更加靈活,無需硬件或應用做任何改動。

5.3 案例三:負載均衡(load balancing)

Facebook Katran

負載均衡的場景,我們用 Facebook 開源的 Katran 作爲例子 [15]。Katran 的工作原理是對外通告服務的 IP,這樣目標是這個 IP 的流量就會被路由到 XDP 實現的負載均衡器。

在這個過程中,XDP 程序負責哈希、封裝以及將包從接收網卡再發出去的任務。 配置信息存儲在 BPF map 中,整個封裝邏輯是完全在 eBPF 中實現的。

性能

爲測試性能,我們給 Katran XDP 程序配置幾個固定的目標機器。 對照組是 IPVS,它是 Linux 內核的一部分。性能如表 2 所示,隨 CPU 數量線性增長, XDP 比 IPVS 性能高 4.3 倍。

表 2. 負載均衡器性能(Mpps)

配置:1 VIP/core, 100 DstIPs/VIP.

6 XDP 的未來方向

XDP 已經能用於解決真實問題,但作爲 Linux 內核的一部分,XDP 還在快速開發過程中。

6.1 eBPF 程序的限制

前面提到,加載到 eBPF 虛擬機的程序必須保證其安全性(不會破壞內核),因此對 eBPF 程序作了一下限制,歸結爲兩方面:

  1. 確保程序會終止:在實現上是通過禁止循環和限制程序的最大指令數(max size of the program);

  2. 確保內存訪問的安全:通過 3.4 小結介紹的寄存器狀態跟蹤(register state tracking)來實現。

校驗邏輯偏保守

由於校驗器的首要職責是保證內核的安全,因此其校驗邏輯比較保守, 凡是它不能證明爲安全的,一律都拒絕。有時這會導致假陰性(false negatives), 即某些實際上是安全的程序被拒絕加載;這方面在持續改進。

缺少標準庫

相比於用戶空間 C 程序,eBPF 程序的另一個限制是缺少標準庫,包括 內存分配、線程、鎖等等庫。

  1. 內核的生命週期和執行上下文管理(life cycle and execution context management )部分地彌補了這一不足,(例如,加載的 XDP 程序會爲每個收到的包執行),

  2. 內核提供的 helper 函數也部分地彌補了一不足。

一個網卡接口只能 attach 一個 XDP 程序

這個限制其實也是可以繞過的:將 XDP 程序組織成程序數組,通過尾 調用,根據包上下文在程序之間跳轉,或者是將幾個程序做 chaining。

6.2 用戶體驗和調試

XDP 程序運行在內核,因此常規的用戶空間 debug 工具是用不了的,但內核自帶的 debug 和 introspection 功能是可以用在 XDP (及其他 eBPF 程序)上的。 包括:

但不熟悉內核生態系統的開發者可能會對這些工具感到非常陌生,難以使用。因此,也出 現了一些更方便普通開發者的工具,包括 BCC [50]、bpftool [8]、libbpf 函數庫 [30] 等等。

6.3 驅動支持

設備要支持 XDP,需要實現內核核心網絡棧暴露出的一個 API。 寫作本文時 Linux 4.18 已經有 12 種驅動支持 XDP,包括了大部分高速網卡。 最新列表見 [2]。

隨着 XDP 系統的不斷成熟,核心代碼逐漸上移到內核中,驅動需要維護的代碼越 來越少。例如,redirection action 支持新的 target 時,無需驅動做任何改動。

最後,對於那些不支持 XDP 的驅動,內核提供了 Generic XDP feature [39],這是軟件實現的 XDP,性能會低一些, 在實現上就是將 XDP 的執行上移到了核心網絡棧(core networking stack)。

XDP 在內核收包函數 receive_skb() 之前,

Generic XDP 在 receive_skb() 之後,

更多關於 Generic XDP,可參考參考:容器網絡 | 深入理解 Cilium

6.4 性能提升

XDP 和 DPDK 之間還有一些性能差距,一些改進工作正在進行中:

6.5 QoS 和 Rate Transitions

當前,XDP 還沒有任何 QoS 機制。 尤其是,如果對端已經過載(例如兩端的網絡速度或特性不匹配),XDP 程序是收不到任何背壓(back-pressure)的,

雖然 XDP 中缺少 QoS,但 Linux 內核網絡棧中卻有很多業界最佳的 Active Queue Management (AQM) 特性和 packet scheduling algorithms [23]。 這些特性中,部分並不適用於 XDP,但我們相信能夠 以一種對包處理應用完全透明的方式,選擇其中部分集成到 XDP。 我們計劃對這一方向進行更深入研究。

6.6 加速傳輸層協議

我們已經證明 XDP 能在保留操作系統原有功能的前提下,集成到操作系統中,實現高速包數據。

目前的 XDP 還是用於無狀態包處理(stateless packet processing) ,如果將這個模型擴展到有狀態傳輸層協議(stateful transport protocols),例如 TCP,它能給依賴可靠 / 有狀態傳輸的應用提供類似的性能提升。

實際上,已經有一些研究證明,相比於操作系統的協議棧,accelerated transport protocols 能顯著提升性能 [5, 25, 35, 52]。其中的一個解決方案 [52] 表明,在保留內 核 TCP 協議棧的的前提下,原始包處理性能(raw packet processing)存在巨大的提升 空間。

XDP 非常適用於這種場景,目前也已經有一些關於如何實現的初步討論 [21], 雖然離實際使用還很遠,但仍然是一個令人振奮的、擴展 XDP 系統 scope 的方向。

6.7 內核 - 用戶空間零拷貝(zero-copy to userspace)

3.1 小節提到,XDP 程序能將數據包重定向到用戶空間應用(userspace application)打 開的特殊類型 socket。這可以用於加速客戶端和服務端在同一臺機器 的網絡密集型應用(network-heavy applications running on the local machine)。

更多信息可參考: (譯) 利用 ebpf sockmap/redirection 提升 socket 性能(2020)。 這裏使用的是 BPF 而非 XDP,但核心原理是一樣的,只是程序執行的位置(hook)不同。 譯註。

但在目前的實現中,這種方式在底層仍然需要拷貝包數據,因此性能會打折扣。

目前已經有工作在進行,通過 AF_XDP 實現真正的數據零拷貝。但這項工作需要 對網絡設備的內存處理過程有一些限制,因此需要設備驅動的顯式支持。 第一個支持這個功能的 patch 已經合併到 4.19 內核,更多驅動的支持 正在添加中。初步的性能測試結果還是很樂觀的,顯示能達到 20Mpps/core 的內核到用戶 空間傳遞(transfer)速度。

6.8 XDP 作爲基礎構建模塊(XDP as a building block)

正如 DPDK 用於高層包處理框架的底層構建模塊(例如 [31]),XDP 有望成爲高層應用的運行時環境 (runtime environment for higher-level applications)。

實際上,我們看到一些基於 XDP 的應用和框架已經出現了。包括

甚至還有人嘗試將 XDP 作爲 DPDK 的一種底層驅動 [53]。

7 總結

本文描述了 XDP,一個安全、快速、可編程、集成到操作系統內核的包處理框架。 測試結果顯示,XDP 能提供 24Mpps/core 的高處理性能,這一數字雖然與基於 kernel bypass 的 DPDK 仍有差距,但提供了其他一些非常有競爭力的優勢:

  1. 兼容內核安全和管理框架(kernel bypass 方式在 bypass 內核網絡棧的同時,也將安全和設備管理等這些極其重要的基礎設施 bypass 了);

  2. 兼容內核網絡棧,可選擇性利用內核已有的基礎設施和功能;

  3. 提供與內核 API 一樣穩定的編程接口;

  4. 對應用完全透明;

  5. 更新、替換程序的過程不會引起服務中斷;

  6. 無需專門硬件,無需獨佔 CPU 等資源。

相比於 kernel bypass 這種非此即彼、完全繞開內核的方式,我們相信 XDP 有更廣闊的的應用前景。Facebook、Cloudflare 等公司實際落地的 XDP 應用,更加增強了我們的這種信心。

最後,XDP 系統還在快速發展,前面也列出了一些正在未來可能會做的開發 / 優化工作。

致謝

XDP has been developed by the Linux networking community for a number of years, and the authors would like to thank everyone who has been involved. In particular,

We also wish to extend our thanks to the anonymous reviewers, and to our shepherd Srinivas Narayana, for their helpful comments.

參考文獻

https://arthurchiao.art/blog/xdp-paper-acm-2018-zh/

的昂貴性能開銷。

但是,操作系統被旁路(繞過)之後,它的應用隔離(application isolation) 和安全機制(security mechanisms)就都失效了;一起失效的還有各種經過已經 充分測試的配置、部署和管理工具。

爲解決這個問題,我們提出一種新的可編程包處理方式:eXpress Data Path (XDP)。

爲展示 XDP 靈活的編程模型,本文還將給出三個程序示例,

  1. layer-3 routing(三層路由轉發

  2. inline DDoS protection(DDoS 防護

  3. layer-4 load balancing(四層負載均衡

1 引言

軟件實現高性能包處理的場景,對每個包的處理耗時有着極高的要求。通用目的操作系統中 的網絡棧更多是針對靈活性的優化,這意味着它們花在每個包上 的指令太多了,不適合網絡高吞吐的場景。

因此,隨後出現了一些專門用於包處理的軟件開發工具,例如 Data Plane Development Kit (DPDK) [16]。這些工具一般都會完全繞過內核,將網絡硬件直接交 給用戶態的網絡應用,並需要獨佔一個或多個 CPU。

1.1 現有方案(kernel bypass)存在的問題

內核旁路方式可以顯著提升性能,但缺點也很明顯:

1.2 新方案:給內核網絡棧添加可編程能力

對此,本文提供了另一種解決方案:給內核網絡棧添加可編程能力。這使得我們能在 兼容各種現有系統、複用已有網絡基礎設施的前提下,仍然實現高速包處理。 這個框架稱爲 XDP,

XDP 已經在過去的幾個內核 release 中逐漸合併到內核,但在本文之前,還沒有關於 XDP 系統的 完整架構介紹。本文將對 XDP 做一個高層介紹。

1.3 新方案(XDP)的優點

我們的測試結果顯示 XDP 能取得 24Mpps/core 的處理性能,這雖然與 DPDK 還有差距, 但相比於後者這種 kernel bypass 的方式,XDP 有非常多的優勢。

本文是 2018 年的測試結果,更新的一些性能(及場景)對比可參考 (譯) 爲容器時代設計的高級 eBPF 內核特性(FOSDEM, 2021)。 譯註。

具體地,XDP

  1. 與內核網絡棧協同工作,將硬件的控制權完全留在內核範圍內。帶來的好處:
  1. 無需任何特殊硬件特性,任何有 Linux 驅動的網卡都可以支持, 現有的驅動只需做一些修改,就能支持 XDP hooks。

  2. 可以選擇性地複用內核網絡棧中的現有功能,例如路由表或 TCP/IP 協議棧,在保持配置接口不變的前提下,加速關鍵性能路徑(critical performance paths)。

  3. 保證 eBPF 指令集和 XDP 相關的編程接口(API)的穩定性。

  4. 與常規 socket 層交互時,沒有從用戶態將包重新注入內核的昂貴開銷。

  5. 對應用透明。這創造了一些新的部署場景 / 方式,例如直接在應用所 在的服務器上部署 DoS 防禦(而非中心式 / 網關式 DoS 防禦)。

  6. 服務不中斷的前提下動態重新編程(dynamically re-program), 這意味着可以按需加入或移除功能,而不會引起任何流量中斷,也能動態響應系統其他部分的的變化。

  7. 無需預留專門的 CPU 做包處理,這意味着 CPU 功耗與流量高低直接相關,更節能。

1.4 本文組織結構

接下來的內容介紹 XDP 的設計,並做一些性能分析。結構組織如下:

2 相關工作

XDP 當然不是第一個支持可編程包處理的系統 —— 這一領域在過去幾年發展勢頭良好, 並且趨勢還在持續。業內已經有了幾種可編程包處理框架,以及基於這些框架的新型應用,包括:

要基於通用(Common Off The Shelf,COTS)硬件實現高性能包處理,就必須解決 網卡(NIC)和包處理程序之間的所有瓶頸。由於性能瓶頸主要來源於內核和用戶態應用之 間的接口(系統調用開銷非常大,另外,內核功能豐富,但也非常複雜), 低層(low-level)框架必須通過這樣或那樣的方式來降低這些開銷。

現有的一些框架通過幾種不同的方式實現了高性能,XDP 構建在其中一些技術之上。 接下來對 XDP 和它們的異同做一些比較分析。

2.1 用戶態輪詢 vs. XDP

在現有的所有框架中,內核旁路方式性能是最高的 [18];但如引言中指 出,這種方式在管理、維護和安全方面都存在不足。

XDP 採用了一種與內核旁路截然相反的方式:相比於將網絡硬件的控制權上移到用戶空間, XDP 將性能攸關的包處理操作直接放在內核中,在操作系統的網絡棧之前執行。

2.2 內核模塊 vs. XDP

在 XDP 之前,以內核模塊(kernel module)方式實現包處理功能代價非常高, 因爲程序執行出錯時可能會導致整個系統崩潰,而且內核的內部 API 也會隨着時間發生變化。 因此也就不難理解爲什麼只有很少的系統採用了這種方式。其中做的比較好包括

這幾個系統都支持靈活的配置,適用於多種場景,取得比較小的平攤代價。

XDP 通過:

  1. 提供一個安全的執行環境,以及內核社區支持,提供與那些暴露到用戶空間一樣穩定的內核 API

    極大地降低了那些將處理過程下沉到內核的應用(applications of moving processing into the kernel)的成本。

  2. 此外,XDP 程序也能夠完全繞過內核網絡棧(completely bypass), 與在內核網絡棧中做 hook 的傳統內核模塊相比,性能也更高。

  3. XDP 除了能將處理過程下沉到內核以獲得最高性能之外,還支持在程序中執行重定向 (redirection)操作,完全繞過內核網絡棧,將包送到特殊類型的用戶空間 socket; 甚至能工作在 zero-copy 模式,進一步降低開銷。

  4. 這種模式與 Netmap [46] 和 PF_RING [11] 方式類似,但後者是在沒有完全繞過內 核的情況下,通過降低從網絡設備到用戶態應用(network device to userspace application)之間的傳輸開銷,實現高性能包處理。

內核模塊方式的另一個例子是 Packet I/O engine,這是 PacketShader [19] 的組成部分, 後者專用於 Arrakis [43] and ClickOS [36] 之類的特殊目的操作系統。

2.3 可編程硬件 vs. XDP

可編程硬件設備也是一種實現高性能包處理的方式。

某種意義上來說,XDP 可以認爲是一種 offload 方式:

  1. 性能敏感的處理邏輯下放到網卡驅動中,以提升性能;

  2. 其他的處理邏輯仍然走內核網絡棧;

  3. 如果沒有用到內核 helper 函數,那整個 XDP 程序都可以 offload 到網卡(目前 Netronome smart-NICs [27] 已經支持)。

2.4 小結

XDP 提供了一種高性能包處理方式,與已有方式相比,在性能、與現有系統的集成、靈活性 等方面取得了更好的平衡。接下來介紹 XDP 是如何取得這種平衡的。

3 XDP 設計

XDP 的設計理念:

這種與內核的深度集成顯然會給設計帶來一些限制,在 XDP 組件合併到 Linux 的過程中,我們也收到了許多來自社區的反饋,促使我們不斷調整 XDP 的設計,但 這些設計反思不在本文討論範圍之內。

3.0 XDP 系統架構

圖 1 描繪了整個 XDP 系統,四個主要組成部分:

  1. XDP driver hook:XDP 程序的主入口,在網卡收到包執行。

  2. eBPF virtual machine:執行 XDP 程序的字節碼,以及對字節碼執行 JIT 以提升性能。

  3. BPF maps:內核中的 key/value 存儲,作爲圖中各系統的主要通信通道。

  4. eBPF verifier:加載程序時對其執行靜態驗證,以確保它們不會導致內核崩潰。

Fig 1. XDP 與 Linux 網絡棧的集成。這裏只畫了 ingress 路徑,以免圖過於複雜。

上圖是 ingress 流程。網卡收到包之後,在處理包數據(packet data)之前,會先執行 main XDP hook 中的 eBPF 程序。 這段程序可以選擇:

  1. 丟棄(drop)這個包;或者

  2. 通過當前網卡將包再發送(send)出去;或者

  3. 將包重定向(redirect)到其他網絡接口(包括虛擬機的虛擬網卡),或者通過 AF_XDP socket 重定向到用戶空間;或者

  4. 放行(allow)這個包,如果後面沒有其他原因導致的 drop,這個包就會進入常規的內核網絡棧。

    如果是這種情況,也就是放行包進入內核網絡棧,那接下來在將包放到發送隊列之前(before packets are queued for transmission), 還有一個能執行 BPF 程序的地方:TC BPF hook。

    更多信息,可參考 (譯) [論文] 邁向完全可編程 tc 分類器(cls_bpf)(NetdevConf,2016)。 譯註。

此外,圖 1 中還可以看出,不同的 eBPF 程序之間、eBPF 程序和用戶空間應用之間,都能夠通過 BPF maps 進行通信。

3.1 XDP driver hook

在設備驅動中執行,無需上下文切換

XDP 程序在網絡設備驅動中執行,網絡設備每收到一個包,程序就執行一次。

相關代碼實現爲一個內核庫函數(library function),因此程序直接 在設備驅動中執行,無需切換到用戶空間上下文。

在軟件最早能處理包的位置執行,性能最優

回到上面圖 1 可以看到:程序在網卡收到包之後最早能處理包的位置 執行 —— 此時內核還沒有爲包分配 struct sk_buff 結構體, 也沒有執行任何解析包的操作。

XDP 程序典型執行流

下圖是一個典型的 XDP 程序執行流:

Fig 2. 典型 XDP 程序的執行流。

網卡收到一個包時,XDP 程序依次執行:

  1. 提取包頭中的信息(例如 IP、MAC、Port、Proto 等),

    執行到程序時,系統會傳遞給它一個上下文對象(context object)作爲參賽 (即 struct xdp_md *ctx,後面有例子),其中包括了指向原 始包數據的指針,以及描述這個包是從哪個網卡的哪個接口接收上來的等元數據字段。

  2. 讀取或更新一些資源的元信息(例如更新統計信息);

    解析包數據之後,XDP 程序可以讀取 ctx 中的包元數據(packet metadata) 字段,例如從哪個網卡的哪個接口收上來的(ifindex)。除此之外,ctx 對象還允許 程序訪問與包數據毗鄰的一塊特殊內存區域(cb, control buffer), 在包穿越整個系統的過程中,可以將自定義的數據塞在這裏。

    除了 per-packet metadata,XDP 程序還可以通過 BPF map 定義和訪問自己的持久數據 ,以及通過各種 helper 函數訪問內核基礎設施。

  1. 如果有需要,對這個包進行 rewrite header 操作,

    程序能修改包數據的任何部分,包括添加或刪除包頭。這使得 XDP 程序能執行封裝 / 接封裝操作,以及重寫(rewrite)地址字段然後轉發等操作。

    內核 helper 函數各有不同用途,例如修改一個包之後,計算新的校驗和(checksum)。

  2. 進行最後的判決(verdict),確定接下來對這個包執行什麼操作;

    判決結果包括:

    重定向功能的用途:

    這些不同的路徑,在圖 1 對應的是幾條實線。

    將重定向判決(verdict)與重定向目標(target)分開,使得重定向目標類型很容易擴展; 另外,由於重定向參數(目標)是通過 BPF map 查詢的,因此無需修 改 XDP 程序,就能動態修改重定向目標。

  1. 將原始包通過另一個網卡(包括虛擬機的虛擬網卡)發送出去;

  2. 轉發給指定 CPU 做進一步處理;

  3. 轉發給 AF_XDP 類型的 socket 做進一步處理;

程序還能通過尾調用(tail call),將控制權交給另一個 XDP 程序; 通過這種方式,可以將一個大程序拆分成幾個邏輯上的小程序(例如,根據 IPv4/IPv6)。

由於 XDP 程序可包含任意指令,因此前三步(讀取包數據、處理元數據、重寫包數據) 順序可以是任意的,而且支持多層嵌套。 但實際中爲了獲得高性能,大部分情況下還是將執行結構組織成這順序的三步。

3.2 eBPF 虛擬機

XDP 程序在 Extended BPF (eBPF) 虛擬機中執行。eBPF 是早期 BSD packet filter (BPF) [37] 的擴展,後者在過去的幾十年中廣泛 應用於各種包處理工具。

BPF 使用 基於寄存器的(register-based) virtual machine 來描述 過濾動作(filtering actions)。

eBPF 虛擬機支持動態加載(loading)和重加載(re-loading)程序,內核管理所有 BPF 程序的生命週期。

3.3 BPF maps

eBPF 程序在觸發內核事件時執行(例如,觸發 XDP 程序執行的,是收包事件)。 程序每次執行時,初始狀態都是相同的(即程序是無狀態的),它們無法直接訪問 內核中的持久存儲(BPF map)。爲此,內核提供了訪問 BPF map 的 helper 函數。

BPF map 是 key/value 存儲,在加載 eBPF 程序時定義(defined upon loading an eBPF program)。

用途:

  1. 持久存儲。例如一個 eBPF 程序每次執行時,都會從裏面獲取上一次的狀態。

  2. 用於協調兩個或多個 eBPF 程序。例如一個往裏面寫數據,一個從裏面讀數據。

  3. 用於用戶態程序和內核 eBPF 程序之間的通信。

3.4 eBPF verifier

唯一加載入口:bpf() 系統調用

由於 eBPF 代碼直接運行在內核地址空間,因此它能直接訪問 —— 也可 能是破壞 —— 任何內存。爲防止這種情況發生,內核規定只能通過唯一入口( bpf() 系統調用)加載 BPF 程序。

加載 BPF 程序時,位於內核中的校驗器首先會對字節碼程序進行靜態分析,以確保

校驗器工作原理:two-pass DAG

校驗器的工作原理:首先根據程序的控制流構建一個有向無環圖(DAG), 然後對 DAG 執行如下校驗:

內存越界和空指針檢查:職責上移到程序自身 / 開發者

這種跟蹤寄存器狀態的機制是爲了在無法預知內存邊界的情況下,仍然確保程序 的內存訪問不會越界。無法預知內存邊界是因爲:

爲解決這個問題,校驗器會檢查已加載的程序自身是否會做如下檢查:

  1. 解引用指針前做了內存邊界檢查,

  2. 查詢 map 之前是檢查了 map 指針是否爲空。

這種方式將處理邏輯中的安全檢查和遇到錯誤時如何處理的控制權都 交給了 BPF 程序的編寫者。

跟蹤數據訪問操作和值範圍

爲跟蹤數據訪問,校驗器會跟蹤

  1. 數據類型

  2. 指針偏置(pointer offsets)

  3. 所有寄存器的可能值範圍

程序開始時:

接下來程序每執行一步,寄存器狀態就會更新一次。當寄存器中存入一個新值時,這個寄存器 還會繼承與這個值相關的狀態變量(inherits the state variables from the source of the value)。

算術操作會影響標量類型的值的範圍(value ranges of scalar types),以及指針類型的 offset。 可能的最大範圍(max possible range)存儲在狀態變量中,例如往寄存器中 load 一個字節時, 這個寄存器的可能值範圍就設置爲 0~255。指令圖(instruction graph)中的 各邏輯分支就會根據操作結果更新寄存器狀態。例如,比較操作 R1 > 10,

不同類型數據的校驗信息來源(source of truth)

利用狀態變量中存儲的範圍信息,校驗器就能預測每個 load 指令能訪問的所有 內存範圍,確保它執行的都是合法內存訪問。

  1. 對於包數據(packet data)的訪問,會與 context 對象中的 data_end 變量做比較;

  2. 對於 BPF map 中獲取的值,或用到 map 定義中聲明的 data size 信息;

  3. 對於棧上存儲的值,會檢查狀態變量中記錄的值範圍;

  4. 對於指針算術操作(pointer arithmetic)還會施加額外的限制,指針通常不能被轉換成整形值。

只要校驗器無法證明某個操作是安全,該 BPF 程序在加載時(load time)就會被拒絕。 除此之外,校驗器還會利用範圍信息確保內存的對齊訪問(enforce aligned memory access)。

校驗器的目的

需要說明的是,校驗器的目的是避免將內核內部(the internals of the kernel )暴露給惡意或有缺陷的 eBPF 程序,而非確保程序中函數的實現已經是最高效的。

換句話說,如果 XDP 程序中處理邏輯過多,也可能會導致機器變慢 ;如果代碼寫的有問題,也可能會破壞包數據。出於這些原因,加載 BPF 程序需要 管理員權限(root)。避免這些 bug 的責任在程序員,但選擇將哪些程序加載 到系統的權限在管理員。

3.5 XDP 程序示例

下面是一個簡單的 XDP 程序,展示了前面介紹的一些特性。 程序會解析包數據,判斷如果是 UDP 包,直接交換源和目的 MAC 地址,然後將包從相同網卡再發送回去,

雖然這是一個非常簡單的例子,但真實世界中的 XDP 程序用到的組件和特性,這裏基本都具備了。

// 從內核 BPF 代碼示例 xdp2_kern.c 修改而來。

1 // 用於統計包數
2 struct bpf_map_def SEC("maps") rxcnt = {
3     .type = BPF_MAP_TYPE_PERCPU_ARRAY,
4     .key_size = sizeof(u32),     // IP 協議類型,即 IPv4/IPv6
5     .value_size = sizeof(long),  // 包數
6     .max_entries = 256,
7 };
8
9 // 直接操作包數據(direct packet data access),交換 MAC 地址
10 static void swap_src_dst_mac(void *data)
11 {
12     unsigned short *p = data;
13     unsigned short dst[3];
14     dst[0] = p[0]; dst[1] = p[1]; dst[2] = p[2];
15     p[0] = p[3]; p[1] = p[4]; p[2] = p[5];
16     p[3] = dst[0]; p[4] = dst[1]; p[5] = dst[2];
17 }
18
19 static int parse_ipv4(void *data, u64 nh_off, void *data_end)
20 {
21     struct iphdr *iph = data + nh_off;
22     if (iph + 1 > data_end)
23         return 0;
24     return iph->protocol;
25 }
26
27 SEC("xdp1") // marks main eBPF program entry point
28 int xdp_prog1(struct xdp_md *ctx)
29 {
30     void *data_end = (void *)(long)ctx->data_end;
31     void *data = (void *)(long)ctx->data;
32     struct ethhdr *eth = data; int rc = XDP_DROP;
33     long *value; u16 h_proto; u64 nh_off; u32 ipproto;
34
35     nh_off = sizeof(*eth);
36     if (data + nh_off > data_end)
37         return rc;
38
39     h_proto = eth->h_proto;
40
41     /* check VLAN tag; could be repeated to support double-tagged VLAN */
42     if (h_proto == htons(ETH_P_8021Q) || h_proto == htons(ETH_P_8021AD)) {
43         struct vlan_hdr *vhdr;
44
45         vhdr = data + nh_off;
46         nh_off += sizeof(struct vlan_hdr);
47         if (data + nh_off > data_end)
48             return rc;
49         h_proto = vhdr->h_vlan_encapsulated_proto;
50     }
51
52     if (h_proto == htons(ETH_P_IP))
53         ipproto = parse_ipv4(data, nh_off, data_end);
54     else if (h_proto == htons(ETH_P_IPV6))
55         ipproto = parse_ipv6(data, nh_off, data_end);
56     else
57         ipproto = 0;
58
59     /* lookup map element for ip protocol, used for packet counter */
60     value = bpf_map_lookup_elem(&rxcnt, &ipproto);
61     if (value)
62         *value += 1;
63
64     /* swap MAC addrs for UDP packets, transmit out this interface */
65     if (ipproto == IPPROTO_UDP) {
66         swap_src_dst_mac(data);
67         rc = XDP_TX;
68     }
69     return rc;
70 }

具體地:

將這段程序安裝到網卡接口上時,它首先會被編譯成 eBPF 字節碼,然後經受校驗器檢查。 這裏的檢查項包括:

  1. 無循環操作;程序大小(指令數量);

  2. 訪問包數據之前,做了內存邊界檢查;

  3. 傳遞給 map lookup 函數的參數,類型與 map 定義相匹配;

  4. map lookup 的返回值(value 的內存地址)在使用之前,檢查了是否爲 NULL。

3.6 小結

XDP 系統由四個主要部分組成:

  1. XDP device driver hook:網卡收到包之後直接運行;

  2. eBPF 虛擬機:執行 XDP 程序(以及內核其他模塊加載的 BPF 程序);

  3. BPF maps:使不同 BPF 程序之間、BPF 程序與用戶空間應用之間能夠通信;

  4. eBPF verifier:確保程序不包含任何可能會破壞內核的操作。

這四部分加在一起,創造了一個編寫自定義包處理應用的強大環境,它能加速包處理的關鍵 路徑,同時還與內核及現有基礎設施密切集成。

接下來看一下 XDP 應用的性能。

4 性能評估

DPDK 是目前性能最高的包處理框架 [18],因此本文將 XDP 與 DPDK 及 Linux 內核網絡 棧的性能做一個對比。測試機器環境:

在測試中,我們主要關心三個 metric:

我們已經驗證,使用 MTU(1500 字節)包時,我們的系統單核就能達到線速(100 Gbps), 而且 CPU 有 50% 是空閒的。顯然,真正的挑戰在於 PPS,而非帶寬,其他一些測試也已經指出了這一點 [46]。 出於這個原因,我們用最小包(64 字節)測試,衡量指標是 PPS。

對於 XDP 和 Linux 內核網絡棧的測試,由於它們沒有顯式指定某些 CPU 來處理網絡包的方式,因此我們通過配置硬件 RSS(Receive Side Scaling)來講流量定向到指定 CPU。

對網卡、內核的一些配置調優,見代碼倉庫 [22]。

4.1 直接棄包(packet drop)性能

Fig 3. 直接棄包(packet drop)性能。DPDK 需要預留一個 CPU 運行控制任務,因此只剩下 5 個 CPU 做包處理。

上圖是性能與 CPU 數量的關係。

再看圖中 Linux 網絡棧在兩種配置下的性能:

  1. 通過 iptables 的 raw table 丟棄流量,這是 Linux 網絡棧中最早能丟棄包的地方;

  2. 通過 conntrack(連接跟蹤)模塊,這個模塊的開銷非常大,但在很多 Linux 發行版中都是默認開啓的。

conntrack 模式達到了 1.8Mpps/core,raw 模式是 4.8Mpps/core ;這兩種模式均未達到硬件瓶頸。 最終的性能,XDP 比常規網絡棧的最快方式快了 5 倍。

Linux raw mode test 中,我們還測量了 XDP 程序不丟棄包,而是更新包數統計然後將包 送到內核網絡棧的場景。 這種情況下,XDP 單核的處理性能會下降到 4.5Mpps/core,有 13.3ns 處理延遲。 圖中並未給出這個測試結果,因爲這個開銷太小了。

4.2 CPU Usage

Fig 4. 直接棄包(packet drop)場景下的 CPU 利用率。

用系統提供的 mpstat 命令測量 CPU 利用率。結果如圖 4 。

4.3 包轉發性能

這個測試中,轉發應用執行非常簡單的 MAC 地址重寫:直接交換源和目的 MAC 地址,然後轉發。 這是轉發場景下最精簡的步驟了,因此結果代表了所有真實轉發應用的性能上限。

轉發吞吐(pps)

Fig 5. 轉發性能。在同一網卡接口上收發會佔用同一 PCI port 的帶寬, 這意味着在 70Mpps XDP same-nic 組就已經達到了 PCI 總線的瓶頸

如圖 5 所示,性能隨 CPU 數量線性擴展,直到達到全局性能瓶頸。XDP 在同網卡轉發的性能遠高於 DPDK 異網卡性能,原因是內存處理方式不同:

轉發延遲

表 1. 轉發延遲。機器網卡的兩個接口直連,在轉發速率分別爲 100pps 和 1Mpps 的條件下,持續 50s 測量端到端延遲

高 pps 場景下,XDP 的延遲已經接近 DPDK。但在低 pps 場景下,XDP 延遲比 DPDK 大的多,原因是 XDP 是基於中斷的,中斷處理時間( interrupt processing time)此時佔大頭;而 DPDK 是輪詢模式,延遲相對比較固定。

4.4 討論:XDP 性能與 DPDK 還有差距的原因

XDP 未做底層代碼優化

上一節已經看到,XDP 相比於常規 Linux 網絡棧性能有了顯著提升。但對於大部分 XDP 場景來說,性能還是與 DPDK 有差距。我們認爲,這是主要是因爲 DPDK 做了相當多的底層 代碼優化。舉個例子來解釋,考慮 packet drop 例子:

多出來的 18.7ns 在我們的 3.6GHz 機器上對應 67 個時鐘週期。因此,很顯然 每個很小的優化在這裏都會產生很大的影響。例如,我們測量出在測試 機器上,每次函數調用需要 1.3ns。mlx5 驅動處理每個包都有 10 次 函數調用,總計就是 13ns。

通用目的操作系統,首要目標:更好的擴展和配置,而非極致性能

另外,在 Linux 這樣的通用目的操作系統中,某些開銷是不可避免的, 因爲設備驅動或子系統的組織方式是爲了實現更好的擴展和配置,而非極致性能。

但是,我們認爲有些優化還是有必要的。例如,我們嘗試將內核中與測試網卡無關的 DMA 函數調用刪掉, 這樣將前面提到的 10 個函數調用降低到了 6 個,測試結果顯示這將單核性能提升到了 29Mpps/core。 依此推測的話,將另外 6 個函數調用也優化掉,能將 XDP 的性能提升到 37.6Mpps。 實際上我們不可能將 6 個全部去掉,但去掉其中幾個,再加上一些其他優化,我 們相信 XDP 和 DPDK 的性能差距將越來越小。

其他驅動的測試結果也是類似的,例如 i40e driver for 40 Gbps Intel cards。

基於以上討論,我們相信未來 XDP 與 DPDK 的性能差距將越來越小。

另一方面,考慮到 XDP 在靈活性和與內核集成方面的優勢, XDP 已經是很多實際場景中的非常有競爭力的方式。下文給出幾個例子。

5 真實場景使用案例

本節給出三個例子來具體展示 XDP 在真實世界中的應用。 這幾個案例都是已經真實在用的,但本文出於解釋目的,將使用簡化的版本。 同時也建議讀者參考 [38],後者是獨立的文章,介紹使用 XDP 解決實際工作中網絡服務所面臨的一些挑戰。

本節目的是展示真實 XDP 方案的可行性,因此不會將重點放在與業界最新的實現做詳盡性能對比上。 我們會拿常規的 Linux 內核網絡棧的性能作爲 baseline,來對比 XDP 應用的性能。

5.1 案例一:軟件路由(software routing)

內核數據平面 & 控制平面(BIRD/FRR)

Linux 內核實現了一個功能完整的路由表,作爲數據平面,支持

對於控制平面,Bird [10] 或 FRR [17] 這樣的路由守護進程( routing daemons)實現了多種路由控制平面協議。Linux 提供的這套生態系統功能如此豐富 ,因此再在另一個包處理框架中重新實現一套類似的路由棧代價將非常高, 更實際的方式是對 Linux 內核的數據平面進行優化。

XDP:直接查詢內核路由表並轉發

XDP 非常適合做這件事情,尤其是它提供了一個 helper 函數,能從 XDP 程序中直接查詢內核路由表。

測試:XDP routing + 全球 BGP 路由表

爲展示 XDP 路由的性能,我們用 Linux 內核代碼中的 XDP routing 例子 [1],與常規 Linux 內核網絡棧的性能做對比。 兩組測試:

  1. 路由表中只有一條路由;

  2. 路由表中有從 routeviews.org 中 dump 而來的全球 BGP 路由表(global BGP routing table)。 包含 752,138 條路由。隨機生成 4000 個目的 IP 地址,以確保能充分利用到這種路由表。

    如果目的 IP 地址少於 4000 個,實際用到的路由表部分會較小,能夠保存在 CPU 緩存中,使得結果不準確。 增大 IP 數量至 4000 個以上,不會對轉發性能造成影響,但可以避免緩存導致的結果不准問題。

對於兩組測試,下一跳 MAC 地址都是與我們的發送網卡直接相關的接口的地址。

性能:2.5x

Fig 6. 軟件路由的性能。由於性能隨核數線性增加,這裏只給出單核的結果。

測試結果如上圖所示。

這說明,XDP 路由程序 + 單核 + 10Gbps 網卡 的軟硬件配置,就能 處理整張全球 BGP 路由表(保守估計每個包平均 300 字節)。

5.2 案例二:Inline DoS Mitigation

DoS 攻擊還是像瘟疫一樣糾纏着互聯網,現在通常的方式是:通過已經入侵的大量設備發起分佈式(DDoS)攻擊。

有了 XDP 之後,我們能直接在應用服務器(application servers)上 部署包過濾程序來防禦此類攻擊(inline DoS mitigation), 無需修改應用代碼。如果應用是部署在虛擬機裏,那 XDP 程序還可以 部署在宿主機(hypervisor)上,這樣單個程序就能保護機器上所有的虛擬機。

模擬 Cloudflare 防禦架構

爲展示工作原理,我們用 XDP 作爲過濾機制,模擬 Cloudflare 的 DDoS 防禦架構 [6]。 他們的 Gatebot architecture ,首先在各 PoP 點機器上採樣,然後統一收起來做分析, 根據分析結果生成防禦規則。

防禦規則的形式是對包數據(payload)進行一系列簡單檢查, 能直接編譯成 eBPF 代碼然後分發到 PoP 點的所有服務器上。這裏說的代碼是 XDP 程序 ,它會將匹配到規則的所有流量丟棄,同時將統計信息更新到 BPF map。

程序邏輯

爲驗證這種方案的性能,我們編寫一個 XDP 程序,它

  1. 解析包頭,執行一些簡單驗證。對每個包:執行四次讀取操作,以解析外層包頭。

  2. 將符合攻擊特性的流量丟棄。具體:丟棄 UDP + 特定端口的流量。

  3. 將其他流量通過 CPU redirect 方式重定向給另一個 CPU 做進一步處理;

性能

我們用 netperf 做性能壓測 [26]。

Fig 7. DDoS 性能。業務吞吐(TPS)隨攻擊流量的變化。

結果如上圖所示,

以上結果表明,XDP 防禦 DDoS 攻擊在實際中是完全可行的,單核就能輕鬆處理 10Gbps 的、都是最小包(minimum-packet)的 DoS 流量。 這種 DDoS 防禦的部署更加靈活,無需硬件或應用做任何改動。

5.3 案例三:負載均衡(load balancing)

Facebook Katran

負載均衡的場景,我們用 Facebook 開源的 Katran 作爲例子 [15]。Katran 的工作原理是對外通告服務的 IP,這樣目標是這個 IP 的流量就會被路由到 XDP 實現的負載均衡器。

在這個過程中,XDP 程序負責哈希、封裝以及將包從接收網卡再發出去的任務。 配置信息存儲在 BPF map 中,整個封裝邏輯是完全在 eBPF 中實現的。

性能

爲測試性能,我們給 Katran XDP 程序配置幾個固定的目標機器。 對照組是 IPVS,它是 Linux 內核的一部分。性能如表 2 所示,隨 CPU 數量線性增長, XDP 比 IPVS 性能高 4.3 倍。

表 2. 負載均衡器性能(Mpps)

配置:1 VIP/core, 100 DstIPs/VIP.

6 XDP 的未來方向

XDP 已經能用於解決真實問題,但作爲 Linux 內核的一部分,XDP 還在快速開發過程中。

6.1 eBPF 程序的限制

前面提到,加載到 eBPF 虛擬機的程序必須保證其安全性(不會破壞內核),因此對 eBPF 程序作了一下限制,歸結爲兩方面:

  1. 確保程序會終止:在實現上是通過禁止循環和限制程序的最大指令數(max size of the program);

  2. 確保內存訪問的安全:通過 3.4 小結介紹的寄存器狀態跟蹤(register state tracking)來實現。

校驗邏輯偏保守

由於校驗器的首要職責是保證內核的安全,因此其校驗邏輯比較保守, 凡是它不能證明爲安全的,一律都拒絕。有時這會導致假陰性(false negatives), 即某些實際上是安全的程序被拒絕加載;這方面在持續改進。

缺少標準庫

相比於用戶空間 C 程序,eBPF 程序的另一個限制是缺少標準庫,包括 內存分配、線程、鎖等等庫。

  1. 內核的生命週期和執行上下文管理(life cycle and execution context management )部分地彌補了這一不足,(例如,加載的 XDP 程序會爲每個收到的包執行),

  2. 內核提供的 helper 函數也部分地彌補了一不足。

一個網卡接口只能 attach 一個 XDP 程序

這個限制其實也是可以繞過的:將 XDP 程序組織成程序數組,通過尾 調用,根據包上下文在程序之間跳轉,或者是將幾個程序做 chaining。

6.2 用戶體驗和調試

XDP 程序運行在內核,因此常規的用戶空間 debug 工具是用不了的,但內核自帶的 debug 和 introspection 功能是可以用在 XDP (及其他 eBPF 程序)上的。 包括:

但不熟悉內核生態系統的開發者可能會對這些工具感到非常陌生,難以使用。因此,也出 現了一些更方便普通開發者的工具,包括 BCC [50]、bpftool [8]、libbpf 函數庫 [30] 等等。

6.3 驅動支持

設備要支持 XDP,需要實現內核核心網絡棧暴露出的一個 API。 寫作本文時 Linux 4.18 已經有 12 種驅動支持 XDP,包括了大部分高速網卡。 最新列表見 [2]。

隨着 XDP 系統的不斷成熟,核心代碼逐漸上移到內核中,驅動需要維護的代碼越 來越少。例如,redirection action 支持新的 target 時,無需驅動做任何改動。

最後,對於那些不支持 XDP 的驅動,內核提供了 Generic XDP feature [39],這是軟件實現的 XDP,性能會低一些, 在實現上就是將 XDP 的執行上移到了核心網絡棧(core networking stack)。

XDP 在內核收包函數 receive_skb() 之前,

Generic XDP 在 receive_skb() 之後,

更多關於 Generic XDP,可參考參考:容器網絡 | 深入理解 Cilium

6.4 性能提升

XDPDPDK 之間還有一些性能差距,一些改進工作正在進行中:

6.5 QoS 和 Rate Transitions

當前,XDP 還沒有任何 QoS 機制。 尤其是,如果對端已經過載(例如兩端的網絡速度或特性不匹配),XDP 程序是收不到任何背壓(back-pressure)的,

雖然 XDP 中缺少 QoS,但 Linux 內核網絡棧中卻有很多業界最佳的 Active Queue Management (AQM) 特性和 packet scheduling algorithms [23]。 這些特性中,部分並不適用於 XDP,但我們相信能夠 以一種對包處理應用完全透明的方式,選擇其中部分集成到 XDP。 我們計劃對這一方向進行更深入研究。

6.6 加速傳輸層協議

我們已經證明 XDP 能在保留操作系統原有功能的前提下,集成到操作系統中,實現高速包數據。

目前的 XDP 還是用於無狀態包處理(stateless packet processing) ,如果將這個模型擴展到有狀態傳輸層協議(stateful transport protocols),例如 TCP,它能給依賴可靠 / 有狀態傳輸的應用提供類似的性能提升。

實際上,已經有一些研究證明,相比於操作系統的協議棧,accelerated transport protocols 能顯著提升性能 [5, 25, 35, 52]。其中的一個解決方案 [52] 表明,在保留內 核 TCP 協議棧的的前提下,原始包處理性能(raw packet processing)存在巨大的提升 空間。

XDP 非常適用於這種場景,目前也已經有一些關於如何實現的初步討論 [21], 雖然離實際使用還很遠,但仍然是一個令人振奮的、擴展 XDP 系統 scope 的方向。

6.7 內核 - 用戶空間零拷貝(zero-copy to userspace)

3.1 小節提到,XDP 程序能將數據包重定向到用戶空間應用(userspace application)打 開的特殊類型 socket。這可以用於加速客戶端和服務端在同一臺機器 的網絡密集型應用(network-heavy applications running on the local machine)。

更多信息可參考: (譯) 利用 ebpf sockmap/redirection 提升 socket 性能(2020)。 這裏使用的是 BPF 而非 XDP,但核心原理是一樣的,只是程序執行的位置(hook)不同。 譯註。

但在目前的實現中,這種方式在底層仍然需要拷貝包數據,因此性能會打折扣。

目前已經有工作在進行,通過 AF_XDP 實現真正的數據零拷貝。但這項工作需要 對網絡設備的內存處理過程有一些限制,因此需要設備驅動的顯式支持。 第一個支持這個功能的 patch 已經合併到 4.19 內核,更多驅動的支持 正在添加中。初步的性能測試結果還是很樂觀的,顯示能達到 20Mpps/core 的內核到用戶 空間傳遞(transfer)速度。

6.8 XDP 作爲基礎構建模塊(XDP as a building block)

正如 DPDK 用於高層包處理框架的底層構建模塊(例如 [31]),XDP 有望成爲高層應用的運行時環境 (runtime environment for higher-level applications)。

實際上,我們看到一些基於 XDP 的應用和框架已經出現了。包括

甚至還有人嘗試將 XDP 作爲 DPDK 的一種底層驅動 [53]。

7 總結

本文描述了 XDP,一個安全、快速、可編程、集成到操作系統內核的包處理框架。 測試結果顯示,XDP 能提供 24Mpps/core 的高處理性能,這一數字雖然與基於 kernel bypass 的 DPDK 仍有差距,但提供了其他一些非常有競爭力的優勢:

  1. 兼容內核安全管理框架(kernel bypass 方式在 bypass 內核網絡棧的同時,也將安全和設備管理等這些極其重要的基礎設施 bypass 了);

  2. 兼容內核網絡棧,可選擇性利用內核已有的基礎設施和功能;

  3. 提供與內核 API 一樣穩定的編程接口;

  4. 應用完全透明

  5. 更新、替換程序的過程不會引起服務中斷;

  6. 無需專門硬件,無需獨佔 CPU 等資源。

相比於 kernel bypass 這種非此即彼、完全繞開內核的方式,我們相信 XDP 有更廣闊的的應用前景。Facebook、Cloudflare 等公司實際落地的 XDP 應用,更加增強了我們的這種信心。

最後,XDP 系統還在快速發展,前面也列出了一些正在未來可能會做的開發 / 優化工作。

致謝

XDP has been developed by the Linux networking community for a number of years, and the authors would like to thank everyone who has been involved. In particular,

We also wish to extend our thanks to the anonymous reviewers, and to our shepherd Srinivas Narayana, for their helpful comments.

參考文獻

https://arthurchiao.art/blog/xdp-paper-acm-2018-zh/

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/ECdw7Q_yGccGLXUxz4E7_Q