我们都知道写 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。
参考资料
bpf: introduce BPF_PROG_TEST_RUN command: https://github.com/torvalds/linux/commit/1cf1cae963c2e6032aebe1637e995bc2f5d330f4