eBPF Talk: 給 XDP 程序寫 unittest
我們都知道寫 unittest 是非常必要的,但是在 eBPF 程序裏,該如何給 XDP 程序寫 unittest 呢?
- bpf: introduce BPF_PROG_TEST_RUN command[1] since 4.12 kernel
在 4.12 內核中,引入了 BPF_PROG_TEST_RUN
命令,可以用來給 XDP 和 tc-bpf 等 eBPF 程序寫 unittest。
如果給 eBPF Talk: 使用 metadata 將信息從 XDP 傳給 AF_XDP 寫 unittest,該怎麼寫呢?
cilium/ebpf 裏的 unittest 例子
// https://github.com/cilium/ebpf/blob/master/prog_test.go#L93
buf := internal.EmptyBPFContext
xdp := sys.XdpMd{
Data: 0,
DataEnd: uint32(len(buf)),
}
xdpOut := sys.XdpMd{}
opts := RunOptions{
Data: buf,
Context: xdp,
ContextOut: &xdpOut,
}
ret, err := prog.Run(&opts)
testutils.SkipIfNotSupported(t, err)
if err != nil {
t.Fatal(err)
}
if ret != 0 {
t.Error("Expected return value to be 0, got", ret)
}
因爲 eBPF Talk: 使用 metadata 將信息從 XDP 傳給 AF_XDP 的 XDP 程序裏涉及網絡包內容的檢查,以及使用了 XDP metadata,所以該例子並不足以指導我們寫 unittest。
如何獲取 XDP 程序裏寫入的 metadata?
先看一下 RunOptions
的定義:
type RunOptions struct {
// Program's data input. Required field.
//
// The kernel expects at least 14 bytes input for an ethernet header for
// XDP and SKB programs.
Data []byte
// Program's data after Program has run. Caller must allocate. Optional field.
DataOut []byte
// ...
}
其中 Data
是提供給 XDP 程序處理的網絡包內容,DataOut
是 XDP 程序處理後的網絡包內容。
metadata 是否在 DataOut
裏呢?我們來看一下 BPF_PROG_TEST_RUN
命令的實現:
__sys_bpf() // ${KERNEL}/kernel/bpf/syscall.c
|-->bpf_prog_test_run()
|-->ret = prog->aux->ops->test_run(prog, attr, uattr);
\
\
\
|-->bpf_prog_test_run_xdp() // ${KERNEL}/net/bpf/test_run.c
|-->ret = bpf_test_finish(kattr, uattr, xdp.data_meta, sinfo, size, retval, duration);
|-->copy_to_user(data_out, data, len);
可以看到,BPF_PROG_TEST_RUN
命令的實現,將包括 metadata 的整個網絡包的內容拷貝到 data_out
裏。
在 unittest 裏檢查 metadata
func preparePacketData() []byte {
buf := make([]byte, 14+20+8) // eth + iph + icmph
be.PutUint16(buf[12:14], 0x0800) // ethertype = IPv4
iph := buf[14:]
iph[0] = 0x45 // version = 4, ihl = 5
iph[9] = 1 // protocol = ICMP
icmph := iph[20:]
icmph[0] = 8 // type = ECHO
return buf
}
func TestXDPProgRun(t *testing.T) {
ifi, err := netlink.LinkByName("lo")
if err != nil {
t.Fatalf("Failed to get device info: %v", err)
}
xsk, err := xdp.NewSocket(ifi.Attrs().Index, 0, nil)
if err != nil {
t.Fatalf("Failed to new XDP socket: %v", err)
}
defer xsk.Close()
var obj xdpfnObjects
if err := loadXdpfnObjects(&obj, nil); err != nil {
t.Fatalf("Failed to load XDP bpf obj: %v", err)
}
defer obj.Close()
// Map is required to be populated before running the program for `bpf_redirect_map`.
if err := obj.XdpSockets.Put(uint32(0), uint32(xsk.FD())); err != nil {
t.Fatalf("Failed to update XDP socket bpf map: %v", err)
return
}
data := preparePacketData()
dataOut := make([]byte, len(data)+4)
act, err := obj.XdpFn.Run(&ebpf.RunOptions{
Data: data,
DataOut: dataOut,
})
if err != nil {
t.Fatalf("Failed to run XDP bpf prog: %v", err)
}
if act != 4 { // XDP_REDIRECT
t.Fatalf("Expected action %d, got %d", 4, act)
}
lat := *(*uint32)(unsafe.Pointer(&dataOut[0]))
if lat != 200 {
t.Fatalf("Expected latency %d, got %d", 200, lat)
}
}
在該代碼片段裏,需要注意的是:
-
preparePacketData()
準備好網絡包內容。 -
給
xdp_sockets
bpf map 寫入一個 XDP socket fd,否則bpf_redirect_map
會報錯,因爲bpf_redirect_map()
會查詢 bpf map 裏的值。 -
XDP 程序的最終結果時
XDP_REDIRECT
,並且將 latency 寫入 metadata。 -
DataOut
起始內容是 metadata,後面是網絡包內容。
小結
本文介紹瞭如何給 XDP 程序寫 unittest,以及如何在 unittest 裏檢查 XDP 程序寫入的 metadata。
參考資料
[1]
bpf: introduce BPF_PROG_TEST_RUN command: https://github.com/torvalds/linux/commit/1cf1cae963c2e6032aebe1637e995bc2f5d330f4
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/d3LIi4d97rB0N5baomaz1g