学习了 XDP metadata 和 AF_XDP,是否可以使用 metadata 将信息从 XDP 传给 AF_XDP 呢?

TL;DR 答案是可以的,复制网络包内容时将 metadata 一起进行复制了。

实验 demo

P.S. demo 源代码:ping latency injection with XDP metadata

以 “使用 AF_XDP 注入延时” 为例,将延时信息放在 metadata 里,然后在 AF_XDP 程序里读取 metadata 当作延时值。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#define LATENCY_MS 200

SEC("xdp")
int xdp_fn(struct xdp_md *ctx)
{
    // ...

    __u32 *val;
    const int siz = sizeof(*val);

    if (bpf_xdp_adjust_meta(ctx, -siz) != 0)
        return XDP_PASS;

    data = ctx_ptr(ctx, data); // required to re-obtain data pointer
    void *data_meta = ctx_ptr(ctx, data_meta);

    val = (typeof(val))data_meta;
    if ((void *)(val + 1) > data)
        return XDP_PASS;

    *val = LATENCY_MS; // 写入延时信息

    return bpf_redirect_map(&xdp_sockets, 0, 0);
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
func delayPackets(xsk *xdp.Socket, descs []xdp.Desc) {
    for _, desc := range descs {
        desc := desc
        latency := readLatency(xsk, desc) // 从 metadata 里读取延时信息
        log.Printf("Delaying packet by %s", latency)
        delayPacket(xsk, desc, latency)
    }
}

func readLatency(xsk *xdp.Socket, desc xdp.Desc) time.Duration {
    frame := xsk.GetFrame(desc)

    sh := (*reflect.SliceHeader)(unsafe.Pointer(&frame))
    sh.Data -= 4
    sh.Len += 4
    sh.Cap += 4

    lat := *(*uint32)(unsafe.Pointer(&frame[0]))
    return time.Duration(lat) * time.Millisecond
}

func delayPacket(xsk *xdp.Socket, desc xdp.Desc, latency time.Duration) {
    _ = time.AfterFunc(latency, func() {
        transformPacket(xsk.GetFrame(desc))
        _ = xsk.Transmit([]xdp.Desc{desc})
    })
}

实验效果:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# ./metadata_xdp2afxdp -D enp0s8
2023/04/21 15:04:51 Attached XDP to enp0s8
2023/04/21 15:04:53 Delaying packet by 200ms
2023/04/21 15:04:54 Delaying packet by 200ms
2023/04/21 15:04:55 Delaying packet by 200ms
2023/04/21 15:04:56 Delaying packet by 200ms
2023/04/21 15:04:57 Delaying packet by 200ms

# ping 192.168.1.138
PING 192.168.1.138 (192.168.1.138): 56 data bytes
64 bytes from 192.168.1.138: icmp_seq=0 ttl=64 time=202.720 ms
64 bytes from 192.168.1.138: icmp_seq=1 ttl=64 time=201.904 ms
64 bytes from 192.168.1.138: icmp_seq=2 ttl=64 time=201.344 ms
64 bytes from 192.168.1.138: icmp_seq=3 ttl=64 time=201.270 ms
64 bytes from 192.168.1.138: icmp_seq=4 ttl=64 time=201.258 ms

底层源代码分析

eBPF Talk: XDP redirect to AF_XDP 中,我们知道 XDP redirect 到 AF_XDP 的底层实现会调用 xsk_copy_xdp(xsk_xdp, xdp, len); 进行网络包复制。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// ${KERNEL}/net/xdp/xsk.c

static void xsk_copy_xdp(struct xdp_buff *to, struct xdp_buff *from, u32 len)
{
    void *from_buf, *to_buf;
    u32 metalen;

    if (unlikely(xdp_data_meta_unsupported(from))) {
        from_buf = from->data;
        to_buf = to->data;
        metalen = 0;
    } else {
        from_buf = from->data_meta;
        metalen = from->data - from->data_meta;
        to_buf = to->data - metalen;
    }

    memcpy(to_buf, from_buf, len + metalen);
}

xsk_copy_xdp 会先判断是否存在 metadata,如果存在,则将 metadata 一起复制到 AF_XDPbuffer 中。

因此,在 AF_XDP 应用程序中,我们可以通过 xdp_descaddr - metadata length 来获取 metadata 的值。

AF_XDP 使用 COPY 模式时会调用 xsk_copy_xdp 函数复制网络包,使用 ZEROCOPY 模式时就不需要复制网络包了。

小结

通过 metadata,我们可以将信息从 XDP 传给 AF_XDP,从而实现一些有趣的功能。