Try   HackMD

計算機網路 - XDP

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

(圖片來源)

參考影片

在這之前,如果需要看一下 eBPF 的簡介,那可以去 YouTube 上找。比如說 "eBPF 101" - Muhammad Falak R Wani (LCA 2022 Online)。2016 年開始每年各大研討會都會有,會放這個是因為年份看起來最新。

Get start with XDP: write a network filter in 10m - DevConf.CZ 2021

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

eCHO episode 13: XDP Hands-on Tutorial

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

這個教學的原始版本是 2019 年 netdev 中的 XDP Hands On Tutorial。不過這個工作坊當中比較像是參與者自己看文件做一遍,有問題自己發問,幾乎沒有講解。

Netdev 0x13 - Veth XDP: XDP for containers

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

上一個影片是使用 network namespace 搭配 veth 做實驗。更多 XDP 在 container 中的應用可以參考這個影片。

Hello [World] XDP_DROP! Writing Your First XDP/eBPF Program

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

Netdev 0x14 - Tutorial: XDP and the cloud : Using XDP on hosts and VMs

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

Low-Latency, Deterministic Networking with Standard Linux using XDP Sockets Magnus Karlsson & Björn Töpel, Intel

AF_XDP 的介紹,

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

環境設置

這裡使用的是 xdp-project/xdp-tutorial 中的例子。除了該文件上提到的套件外,如果是在 Raspberry Pi 4B 上使用 Ubuntu 21.10,那麼要多安裝以下套件:

$ sudo apt-get install libbpf-dev

否則會出現下面的警告:

xdp_pass_kern.c:3:10: fatal error: 'bpf/bpf_helpers.h' file not found
#include <bpf/bpf_helpers.h>
         ^~~~~~~~~~~~~~~~~~~
1 error generated.

另外,如果是使用 xdp-project/xdp-tutorial 中的 Makefile,而且要在 Raspberry Pi 4B 上編譯,那要在 common/common.mk 中的 BPF_CFLAGS 後面多加上 BPF_CFLAGS += -I/usr/include/aarch64-linux-gnu 選項 (參考 Update setup_dependencies.org #201 這個 PR):

...
BPF_CFLAGS ?= -I$(LIBBPF_DIR)/build/usr/include/ -I../headers/
+BPF_CFLAGS += -I/usr/include/aarch64-linux-gnu
...

而如果是在命令列直接用 clang 編譯,那要加上 -I/usr/include/aarch64-linux-gnu 選項。

basic01

不用 Makefile 編譯與載入

如果是在 Raspberry Pi 4B 上編譯,但是不想使用它的 Makefile,那麼可以使用以下命令:

$ clang -O2 -g -Wall \
    -target bpf -c xdp_pass_kern.c \
    -I/usr/include/aarch64-linux-gnu \
    -o xdp_kern_pass.o

然後使用以下命令來載入:

$ sudo ip link set dev lo xdpgeneric obj xdp_pass_kern.o sec xdp

這裡的 xdpgeneric 選項有一些細節,不過用下一個例子說明會比較清楚。

basic02

例子裡面提到會藉由兩個 network namespace 之間的溝通作為例子,使用當中的 testenv.sh 會建立具有以下的 namespace:

+-----------------------------+                          +-----------------------------+
| Root namespace              |                          | namespace 'veth-basic02'    |
|                             |   From 'veth-basic02'    |                             |
|              +--------------+ TX->                RX-> +--------+                    |
|              | veth-basic02 +--------------------------+  veth0 |                    |
|              +--------------+ <-RX                <-TX +--------+                    |
|                             |       From 'veth0'       |                             |
+-----------------------------+                          +-----------------------------+

手動設置 Network Namespace

如果不想用那個 testenv.sh 來建立 namespace,也可以用以下方法建立類似的實驗環境。首先,新增一個名稱為 test01 的 network namespace:

$ sudo ip netns add veth-basic02

然後新增一對 veth-pair。其中,令這個 veth-pair 中的兩個 interface 的名稱各自為 veth-basic02veth0

$ sudo ip link add veth0 type veth peer name veth-basic02

veth0 那個 interface 加入 veth-basic02 這個 network namespace 中:

$ sudo ip link set veth0 netns veth-basic02

接著幫這兩個 interface 各自設定 IPv6 位址:

$ sudo ip addr add fc00:dead:cafe:1::1/64 dev veth-basic02 scope global
$ sudo ip addr add fe80::cc4c:c0ff:fe33:bad6/64 dev veth-basic02 scope link
$ sudo ip -n veth-basic02 addr add fc00:dead:cafe:1::2/64 dev veth0 scope global
$ # use default local unicast addree for veth-basic02 

最後,啟動這兩個 interface:

$ sudo ip link set veth-basic02 up
$ sudo ip -n veth-basic02 link set veth0 up

就可以在 veth-basic02 這個 namespace 中載入剛剛的 XDP 程式。比如說如果載入 xdp_drop 這個 section:

$ sudo ip -n veth-basic02 link set dev veth0 xdpgeneric obj xdp_prog_kern.o sec xdp_drop

這時候,如果在 Root namespace 中:

$ ip a

會發現 veth-basic02 這個 interface:

13: veth-basic02@if14: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether ce:4c:c0:33:ba:d6 brd ff:ff:ff:ff:ff:ff link-netns veth-basic02
    inet6 fc00:dead:cafe:1::1/64 scope global 
       valid_lft forever preferred_lft forever
    inet6 fe80::cc4c:c0ff:fe33:bad6/64 scope link 
       valid_lft forever preferred_lft forever

而如果是在 veth-basic02 裡面:

$ sudo ip -n veth-basic02 a

則會看到以下的:

14: veth0@if13: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 xdpgeneric/id:24 qdisc noqueue state UP group default qlen 1000
    link/ether 2a:8e:bc:e7:dd:ad brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet6 fc00:dead:cafe:1::2/64 scope global 
       valid_lft forever preferred_lft forever
    inet6 fe80::288e:bcff:fee7:ddad/64 scope link 
       valid_lft forever preferred_lft forever

如果之後要在 veth-basic02 這個 network namespace 中執行命令,可以使用:

$ ip netns exec veth-basic02 <COMMAND>

比如說如果要從 veth-basic02 這個 namespace ping 到 Root namespace,那麼就:

$ sudo ip netns exec veth-basic02 ping fc00:dead:cafe:1::1

其他工具 Hook 住 Tracepoint - 以 ftrace 為例

另外,原先的文章中提到使用 XDP_ABORTED 會觸發 xdp:xdp_exception 這個 tracepoint,然後有使用 perf 去 hook 這個 tracepoint。事實上也可以用 trace-cmd (ftrace) 去 hook。比如說在背景 ping 之後,使用以下命令:

$ sudo trace-cmd record -e "xdp:xdp_exception"

一段時間後終止它,再生成報告:

$ trace-cmd report -l

就會看到被觸發的事件:

          <idle>-0     [002]  5034.925574: xdp_exception:        prog_id=36 action=ABORTED ifindex=14
     ksoftirqd/1-21    [001]  5035.021677: xdp_exception:        prog_id=36 action=ABORTED ifindex=14
     ksoftirqd/2-27    [002]  5035.949502: xdp_exception:        prog_id=36 action=ABORTED ifindex=14
     ksoftirqd/1-21    [001]  5036.045543: xdp_exception:        prog_id=36 action=ABORTED ifindex=14
            ping-3522  [002]  5037.069633: xdp_exception:        prog_id=36 action=ABORTED ifindex=14

順帶一提,如果使用 -T 選項:

$ sudo trace-cmd record -e "xdp:xdp_exception" -T

那麼在 trace-cmd report 的時候,可以產生 stack trace。比如說:

ksoftirq-21      1..s1  5185.541837: xdp_exception:        prog_id=36 action=ABORTED ifindex=14
ksoftirq-21      1..s1  5185.541845: kernel_stack:         <stack trace >
=> __traceiter_xdp_exception (ffffb8170f80cd08)
=> netif_receive_generic_xdp (ffffb817100d9ea0)
=> do_xdp_generic.part.0 (ffffb817100e2204)
=> __netif_receive_skb_core.constprop.0 (ffffb817100e245c)
=> __netif_receive_skb_one_core (ffffb817100e4e84)
=> __netif_receive_skb (ffffb817100e4f40)
=> process_backlog (ffffb817100e5280)
=> __napi_poll.constprop.0 (ffffb817100e4490)
=> net_rx_action (ffffb817100e4964)
=> __do_softirq (ffffb8170f6104f0)
=> run_ksoftirqd (ffffb8170f698e3c)
=> smpboot_thread_fn (ffffb8170f6c794c)
=> kthread (ffffb8170f6bfb6c)
=> ret_from_fork (ffffb8170f616604)

或是像這個:

    ping-3522    3..s2  5181.446105: xdp_exception:        prog_id=36 action=ABORTED ifindex=14
    ping-3522    3..s2  5181.446111: kernel_stack:         <stack trace >
=> __traceiter_xdp_exception (ffffb8170f80cd08)
=> netif_receive_generic_xdp (ffffb817100d9ea0)
=> do_xdp_generic.part.0 (ffffb817100e2204)
=> __netif_receive_skb_core.constprop.0 (ffffb817100e245c)
=> __netif_receive_skb_one_core (ffffb817100e4e84)
=> __netif_receive_skb (ffffb817100e4f40)
=> process_backlog (ffffb817100e5280)
=> __napi_poll.constprop.0 (ffffb817100e4490)
=> net_rx_action (ffffb817100e4964)
=> __do_softirq (ffffb8170f6104f0)
=> do_softirq (ffffb8170f698c90)
=> __local_bh_enable_ip (ffffb8170f698db0)
=> ip6_finish_output2 (ffffb81710247994)
=> __ip6_finish_output (ffffb81710249bf4)
=> ip6_finish_output (ffffb81710249d60)
=> ip6_output (ffffb81710249e80)
=> ip6_local_out (ffffb817102a8974)
=> ip6_send_skb (ffffb8171024a724)
=> ip6_push_pending_frames (ffffb8171024a874)
=> rawv6_push_pending_frames (ffffb81710279a00)
=> rawv6_sendmsg (ffffb8171027ba84)
=> inet_sendmsg (ffffb817101ee430)
=> sock_sendmsg (ffffb817100ae140)
=> __sys_sendto (ffffb817100b1be4)
=> __arm64_sys_sendto (ffffb817100b1c44)
=> invoke_syscall (ffffb8170f62a8f0)
=> el0_svc_common.constprop.0 (ffffb8170f62aa2c)
=> do_el0_svc (ffffb8170f62ab94)
=> el0_svc (ffffb81710300dbc)
=> el0_sync_handler (ffffb81710301664)
=> el0_sync (ffffb8170f61269c)

上面這兩個狀況都是在 softirq 中觸發到注入的 XDP 程式:前者是給 ksoftirqd 做,後者則是在重新啟動 softirq 時被執行。但不管是哪一個,都可以發現發生在處理封包的 softirq 中觸發了這個 XDP 程式。

另外,這個 XDP 程式被執行到的時候,已經是 sk_buff 處理到一半的時候。這跟上面某些演講提到「若驅動程式中有支援 XDP,則可以在建立 sk_buuff 之前就過濾封包」似乎不同。這個理由是:前面的例子中使用的是 generic XDP,也就是剛剛使用 ip 命令載入時使用的 xdpgeneric 選項,它觸發的時機是出現在 sk_buff 建立之後,所以會發現過濾並非在驅動程式中發生,而是在 network stack 中。ip-link(8) 的文件中的 ip link set - change device attributes 中則有提到:

xdp object | pinned | off
If the driver does not have native XDP support, the kernel will fall back to a slower, driver-independent "generic" XDP variant. The ip link output will in that case indicate xdpgeneric instead of xdp only.

除此之外,可以參考 LWN 中的 Generic XDP 的說明。

Generic XDP vs. Driver XDP

如果希望在驅動程式中就能使用 XDP,則需要該驅動程式有支援,並且在載入時使用 xdpdrv 選項。在有些上面的演講裡面,這會叫作 driver XDP 或 native XDP。而 veth 對此也有支援。想做到這件事,首先把原來的 XDP 程式卸載:

$ sudo ip -n veth-basic02 link set dev veth0 xdpgeneric off

並且將改為:

$ sudo ip -n veth-basic02 link set dev veth0 xdpdrv obj xdp_prog_kern.o sec xdp_aborted

然後重複剛剛的 pingtrace-cmd。可以用 tmux 開多個分割,或是把 hook 住 tracepoint 的程式先用 & 放到背景執行,ping 完之後再把它用 fg 恢復到前景並終止:

$ sudo trace-cmd record -e "xdp:xdp_exception" -T &
$ sudo ip netns exec veth-basic02 ping fc00:dead:cafe:1::1
PING fc00:dead:cafe:1::1(fc00:dead:cafe:1::1) 56 data bytes
^C
--- fc00:dead:cafe:1::1 ping statistics ---
12 packets transmitted, 0 received, 100% packet loss, time 11244ms

$ fg
$ sudo trace-cmd record -e "xdp:xdp_exception" -T
^C

結束後產生報告 (使用 trace-cmd report -l) 會發現:這個 XDP 程式被觸發的地方並不是在處理 sk_buff__netif_receive_skb_core 中,而是在那之前,在 __napi_poll 裡面就觸發了:

ksoftirq-21      1..s.   894.522772: xdp_exception:        prog_id=102 action=ABORTED ifindex=5
ksoftirq-21      1..s.   894.522779: kernel_stack:         <stack trace >
=> __traceiter_xdp_exception (ffffbc6c96c0cd08)
=> veth_xdp_rcv_skb (ffffbc6c79953610)
=> veth_xdp_rcv (ffffbc6c79953cc4)
=> veth_poll (ffffbc6c79953e70)
=> __napi_poll.constprop.0 (ffffbc6c974e4490)
=> net_rx_action (ffffbc6c974e4964)
=> __do_softirq (ffffbc6c96a104f0)
=> run_ksoftirqd (ffffbc6c96a98e3c)
=> smpboot_thread_fn (ffffbc6c96ac794c)
=> kthread (ffffbc6c96abfb6c)
=> ret_from_fork (ffffbc6c96a16604)

另外一部分對應的 stack trace 如下,可以觀察到相同的結論。只是為了完整而附上。

    ping-2820    1..s1   893.518789: xdp_exception:        prog_id=102 action=ABORTED ifindex=5
    ping-2820    1..s1   893.518795: kernel_stack:         <stack trace >
=> __traceiter_xdp_exception (ffffbc6c96c0cd08)
=> veth_xdp_rcv_skb (ffffbc6c79953610)
=> veth_xdp_rcv (ffffbc6c79953cc4)
=> veth_poll (ffffbc6c79953e70)
=> __napi_poll.constprop.0 (ffffbc6c974e4490)
=> net_rx_action (ffffbc6c974e4964)
=> __do_softirq (ffffbc6c96a104f0)
=> do_softirq (ffffbc6c96a98c90)
=> __local_bh_enable_ip (ffffbc6c96a98db0)
=> ip6_finish_output2 (ffffbc6c97647994)
=> __ip6_finish_output (ffffbc6c97649bf4)
=> ip6_finish_output (ffffbc6c97649d60)
=> ip6_output (ffffbc6c97649e80)
=> ip6_local_out (ffffbc6c976a8974)
=> ip6_send_skb (ffffbc6c9764a724)
=> ip6_push_pending_frames (ffffbc6c9764a874)
=> rawv6_push_pending_frames (ffffbc6c97679a00)
=> rawv6_sendmsg (ffffbc6c9767ba84)
=> inet_sendmsg (ffffbc6c975ee430)
=> sock_sendmsg (ffffbc6c974ae140)
=> __sys_sendto (ffffbc6c974b1be4)
=> __arm64_sys_sendto (ffffbc6c974b1c44)
=> invoke_syscall (ffffbc6c96a2a8f0)
=> el0_svc_common.constprop.0 (ffffbc6c96a2aa2c)
=> do_el0_svc (ffffbc6c96a2ab94)
=> el0_svc (ffffbc6c97700dbc)
=> el0_sync_handler (ffffbc6c97701664)
=> el0_sync (ffffbc6c96a1269c)