---
# System prepended metadata

title: 以 Linux XDP 為基礎的高效率網路負載平衡器
tags: [linux2020]

---

# 以 Linux XDP 為基礎的高效率網路負載平衡器
contributed by < `foxhoundsk` >
###### tags: `linux2020`

## Benchmarking
HAProxy:
```
./wrk -t8 -c250 -d60s -R20000 --latency http://10.10.0.2 

  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     1.08ms  480.11us   8.56ms   66.27%
    Req/Sec     2.64k   295.71     4.89k    71.14%
  Latency Distribution (HdrHistogram - Recorded Latency)
 50.000%    1.06ms
 75.000%    1.40ms
 90.000%    1.71ms
 99.000%    2.28ms
 99.900%    2.95ms
 99.990%    4.11ms
 99.999%    7.35ms
100.000%    8.57ms

#[Mean    =        1.080, StdDeviation   =        0.480]
#[Max     =        8.560, Total count    =       996911]
#[Buckets =           27, SubBuckets     =         2048]
----------------------------------------------------------
  1198537 requests in 1.00m, 428.63MB read
Requests/sec:  19975.33
Transfer/sec:      7.14MB
```
XDP:
```
./wrk -t8 -c250 -d60s -R20000 --latency http://10.10.0.5

Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     0.89ms  413.29us   8.80ms   64.24%
    Req/Sec     2.65k   280.36     4.20k    73.29%
  Latency Distribution (HdrHistogram - Recorded Latency)
 50.000%    0.88ms
 75.000%    1.19ms
 90.000%    1.42ms
 99.000%    1.86ms
 99.900%    2.16ms
 99.990%    2.62ms
 99.999%    6.59ms
100.000%    8.81ms

#[Mean    =        0.885, StdDeviation   =        0.413]
#[Max     =        8.800, Total count    =       996909]
#[Buckets =           27, SubBuckets     =         2048]
----------------------------------------------------------
  1198574 requests in 1.00m, 456.02MB read
Requests/sec:  19976.16
Transfer/sec:      7.60MB
```
參數 `-R20000` 為目前實驗的瓶頸，應再調高，方可展現較低 latency 所帶來的效益(higher throughput)。

嘗試參數:
```
./wrk -t6 -c250 -d60s -R150000 --latency http://10.10.0.5
```
XDP:
```
Thread Stats   Avg      Stdev     Max   +/- Stdev 
    Latency     4.09s     1.79s   11.73s    63.18% 
    Req/Sec    22.22k   802.67    24.06k    66.36%                
  Latency Distribution (HdrHistogram - Recorded Latency)                                                                               
 50.000%    3.91s      
 75.000%    5.37s 
 90.000%    6.59s 
 99.000%    8.34s 
 99.900%    9.58s 
 99.990%   11.65s 
 99.999%   11.72s 
100.000%   11.74s
----------------------------------------------------------
  7948712 requests in 1.00m, 2.97GB read
Requests/sec: 132479.84
Transfer/sec:     50.66MB
```
haproxy:
```
Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    17.37s     7.55s   36.01s    59.98%      
    Req/Sec    12.40k   328.25    13.15k    66.67%      
  Latency Distribution (HdrHistogram - Recorded Latency)
 50.000%   17.20s                                                  
 75.000%   23.63s                                                  
 90.000%   27.16s                                                  
 99.000%   34.50s                                                  
 99.900%   35.78s                                                  
 99.990%   35.95s                   
 99.999%   36.01s
----------------------------------------------------------
  4461288 requests in 1.00m, 1.57GB read
Requests/sec:  74353.80
Transfer/sec:     26.73MB
```
## 筆記
### 實驗環境佈置
目前架構為，經過 [IP in IP](https://en.wikipedia.org/wiki/IP_in_IP) 包裝過的封包到達其他 LB 時，其他 LB 會將 IPIP header 截斷，使得封包在該 LB 上 (application, network stack 等等) 看起來就只是一個普通的 IP packet，最後，封包處理完後將直接以 [VIP](https://en.wikipedia.org/wiki/Virtual_IP_address) 的 IP 位置回傳給 client，而非實際 IP。

在此環境下，每台 LB 要設定其負責的 VIP，設定方式為將 VIP 新增到 loopback device 上，即：
```
$ ip addr add 10.10.0.5/8 dev lo
```
其中 `10.10.0.5` 即為 VIP。

此外，我們還要對 ARP 做相關設定：
```
sysctl net.ipv4.conf.all.arp_ignore=1
sysctl net.ipv4.conf.eth0.arp_ignore=1
sysctl net.ipv4.conf.all.arp_announce=2
sysctl net.ipv4.conf.eth0.arp_announce=2
```
如此一來 VIP 才能正確做出 DSR (Direct Server Return)，也就是以 VIP 作為 L3 中的 source address。
#### [ECMP](https://en.wikipedia.org/wiki/Equal-cost_multi-path_routing) Linux 設定
此機器用於將 ingress 的流量平均分發至個別 XDP LB，關於 ECMP 在 vanilla Linux 上的發展歷史可見此[連結](https://cumulusnetworks.com/blog/celebrating-ecmp-part-two/)。

首先，我們假設 `10.10.0.7` 為 [VIP](https://en.wikipedia.org/wiki/Virtual_IP_address)、做 ECMP 的機器的位置為 `10.10.0.3`、XDP LB \ application 機器的位置為 `10.10.0.4`, `10.10.0.5` 以及 `10.10.0.6`、做 benchmarking 的 client 的位置為 `10.10.0.2`。

- ECMP 的機器主要會有以下設定：
    ```
    ip route add 10.10.0.7/32 nexthop via 10.10.0.4 weight 1 \
    nexthop via 10.10.0.5 weight 1 \
    nexthop via 10.10.0.6 weight 1

    sysctl net.ipv4.ip_forward=1
    ```
    其中需注意 nexthop 的位置必須是 reachable 的，倘若不是，可參考 `onlink` 這個 `iproute2 route` 的其中一個參數是否對你的環境設定有幫助。
    
    `ip_forward` 用於將 MAC address 指向本機上的 interface 但 L3 的位置不屬於本機的封包轉送至其他機器。
    
    設定完後執行命令 `$ ip r` 預期會見到以下輸出：
    ```
    # ip r
    10.10.0.0/24 dev eth0 proto kernel scope link src 10.10.0.3 
    10.10.0.7 
    nexthop via 10.10.0.4 dev eth0 weight 1 
    nexthop via 10.10.0.5 dev eth0 weight 1 
    nexthop via 10.10.0.6 dev eth0 weight 1
    ```
    
- Benchmarking 機器設定：
    ```
    ip route add 10.10.0.7 via 10.10.0.3
    ```
    強制將指向 `10.10.0.7` 的封包發送給 `10.10.0.3` 這台機器(做 ECMP 的機器)，讓它去做 routing。

- XDP LB / application 機器設定請參考[上一段落](#實驗環境佈置)的介紹。


### LACP vs. ECMP
雖然兩者均用於負載平衡以及增加線路的冗餘性，但前者是作用於 layer 2，而後者則是作用於 layer 3。
### netlink
建立 (socket(2)) [netlink](https://man7.org/linux/man-pages/man7/netlink.7.html) socket 時，SOCK_RAW 以及 SOCK_DGRAM 這兩種 socket 類型均可使用，因為 netlink 將其視為等價。

注意，載入 XDP program 時，相關 bpf 系統呼叫的成功僅是代表 XDP program 已載入到核心中(通過 in-kernel verifier 的檢測)，此時 XDP program 尚未掛載(hook)到預期的 network interface 上，我們需要使用 netlink 中的 [NETLINK_ROUTE](https://man7.org/linux/man-pages/man7/rtnetlink.7.html) 相關功能方能將 XDP program 掛載到預期的 interface 上。

值得注意的是，[libbpf](https://github.com/libbpf/libbpf/blob/master/src/netlink.c) 已經實做了 XDP program 相關的 netlink 的 helper。
### capabilities negotiation
因為每個 driver 所支援的 XDP capabilities 不一，所以當載入 XDP program 時，需檢查對應的 NIC device driver 是否支援此 XDP program 需要用到的功能。
### ebpf helper function
epbf program 若需使用 kernel API，則該 API 需經類似 `EXPORT_SYMBOL` 的巨集處理。

每種 ebpf program type 有個別可使用的 helper function 子集。
### packet redirection
![](https://i.imgur.com/9g4YpAL.png)
![](https://i.imgur.com/2oGcHyH.png)

([Facebook netdevconf2.1](https://netdevconf.info/2.1/slides/apr6/zhou-netdev-xdp-2017.pdf))

### HW offload
原生 NIC driver (eBPF driver mode) 對 eBPF 的應用有些限制，例如：僅能有 31 個 NIC ring buffer (單埠網卡)，雙埠網卡的話 ring 的數量限制則僅有 15 個。

若將 NIC 的 driver 更換 (載入對應核心模組) 為對 eBPF 提供支援的 driver，則可以不受此限制的約束。

### OSI
src & dst IP 位於 L3，src & dst port 位於 L4。

### IPv4 header
因 header 中的 `option` 成員的關係，header 的長度不定。長度由 4-bit 的 bitfield (在結構體中名為 `ihl`) 來定義，`ihl` 表示 header 長度為多少個 dword (32-bit)。

由於長度使用 4 個位元表示，並且除了 `option` 成員外 header 還有必要的長度。因此 IPv4 長度最小為 20 bytes，最大為 60 bytes (0b1111 * size_of_qword)。

## 參考資料
- [History & Usage of Linux Native ECMP Functionality](http://codecave.cc/multipath-routing-in-linux-part-2.html)
- [Prototype Kernel](https://prototype-kernel.readthedocs.io/en/latest/networking/XDP/index.html)
- [XDP/AF_XDP and its potential ](https://pantheon.tech/what-is-af_xdp/)
- [XDP patch for e1000e driver support](https://github.com/adjavon/e1000e_xdp) (the URL is given by [bcc](https://github.com/iovisor/bcc/blob/master/docs/kernel-versions.md#xdp))
- [eBPF HW offload](https://www.netronome.com/m/documents/UG_Getting_Started_with_eBPF_Offload.pdf)
- [Nginx cookbook 2019 ed.](https://www.nginx.com/resources/library/nginx-cookbook-2019-edition/)
- [consistent hash](https://medium.com/@chyeh/consistent-hashing-algorithm-%E6%87%89%E7%94%A8%E6%83%85%E5%A2%83-%E5%8E%9F%E7%90%86%E8%88%87%E5%AF%A6%E4%BD%9C%E7%AF%84%E4%BE%8B-41fd16ad334a)
- [netlink](https://man7.org/linux/man-pages/man7/netlink.7.html)
- [Create bridge with iproute2](https://wiki.archlinux.org/index.php/Network_bridge#With_iproute2)
- 實驗環境佈置
    - [adding VIP into lo device](https://askubuntu.com/questions/585468/how-do-i-add-an-additional-ip-address-to-an-interface-in-ubuntu-14)
    - [ARP related stuff](https://lyt0112.pixnet.net/blog/post/312242545-linux%E4%B8%8B-proc%E8%A3%A1%E9%9D%A2%E7%9A%84arp_announce%E5%92%8Carp_ignore)
    - [ARP setting](https://support.kemptechnologies.com/hc/en-us/articles/203861685-Configuring-DSR)