eBPF Talk: 給 XDP 程序寫 unittest

我們都知道寫 unittest 是非常必要的,但是在 eBPF 程序裏,該如何給 XDP 程序寫 unittest 呢?

在 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)
    }
}

在該代碼片段裏,需要注意的是:

  1. preparePacketData() 準備好網絡包內容。

  2. xdp_sockets bpf map 寫入一個 XDP socket fd,否則 bpf_redirect_map 會報錯,因爲 bpf_redirect_map() 會查詢 bpf map 裏的值。

  3. XDP 程序的最終結果時 XDP_REDIRECT,並且將 latency 寫入 metadata。

  4. 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