# 以 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


([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)