对 XDP 了解得越多,对 XDP 的信心越是膨胀。最近在 XDP on Mellanox 上踩了个坑,让我对 XDP 的认识又有了新的提升:就是需要保持对技术的敬畏之心,不要过于自信。

P.S. Mellanox 网卡使用 mlx5_core 驱动。

使用 metadata 将信息从 XDP 传到 tc-bpf

掌握了 XDP metadata 技术,就可以将信息从 XDP 传到 tc-bpf,从而实现 XDP 与 tc-bpf 的配合工作。

是,也不是。

以上,的确可以使用 metadata 将信息从 XDP 传到 tc-bpf。

不过,XDP on Mellanox 上,该方式是否真的可行?

bpf_xdp_adjust_meta() 失败了

在 XDP on Mellanox 上,调用 bpf_xdp_adjust_meta() 失败了,返回 -524

T_T,这是我在 XDP 程序里在每条代码逻辑分支上使用 bpf_printk() 打 log 才发现的。

 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
28
29
30
31
32
// ${KERNEL}/net/core/filter.c

BPF_CALL_2(bpf_xdp_adjust_meta, struct xdp_buff *, xdp, int, offset)
{
    void *xdp_frame_end = xdp->data_hard_start + sizeof(struct xdp_frame);
    void *meta = xdp->data_meta + offset;
    unsigned long metalen = xdp->data - meta;

    if (xdp_data_meta_unsupported(xdp))
        return -ENOTSUPP;
    if (unlikely(meta < xdp_frame_end ||
             meta > xdp->data))
        return -EINVAL;
    if (unlikely(xdp_metalen_invalid(metalen)))
        return -EACCES;

    xdp->data_meta = meta;

    return 0;
}

// ${KERNEL}/include/net/xdp.h

static __always_inline bool
xdp_data_meta_unsupported(const struct xdp_buff *xdp)
{
    return unlikely(xdp->data_meta > xdp->data);
}

// ${KERNEL}/include/linux/errno.h

#define ENOTSUPP    524 /* Operation is not supported */

?_?,为什么 XDP on Mellanox 上,bpf_xdp_adjust_meta() 失败了?

真相

真相就是 Mellanox mlx5_core 驱动不支持 XDP metadata。

参考上文,看下 mlx5_core 驱动里在执行 XDP 程序前,是如何准备 struct xdp_buff 的。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// ${KERNEL}/drivers/net/ethernet/mellanox/mlx5/core/en_rx.c

// mlx5e_fill_xdp_buff(rq, va, rx_headroom, cqe_bcnt, &xdp);

static void mlx5e_fill_xdp_buff(struct mlx5e_rq *rq, void *va, u16 headroom,
                u32 len, struct xdp_buff *xdp)
{
    xdp_init_buff(xdp, rq->buff.frame0_sz, &rq->xdp_rxq);
    xdp_prepare_buff(xdp, va, headroom, len, true);
}

// ${KERNEL}/include/linux/xdp.h

static __always_inline void
xdp_prepare_buff(struct xdp_buff *xdp, unsigned char *hard_start,
         int headroom, int data_len, const bool meta_valid)
{
    unsigned char *data = hard_start + headroom;

    xdp->data_hard_start = hard_start;
    xdp->data = data;
    xdp->data_end = data + data_len;
    xdp->data_meta = meta_valid ? data : data + 1; // <--- 问题出在这里
}

?_?,为什么 5.15 kernel 里不支持 XDP metadata,而 6.0+ kernel 里支持了呢?

好吧,mlx5_core 驱动从 5.18 kernel 开始才支持 XDP metadata。

真相:如果不支持 XDP metadata,xdp->data_meta = xdp->data + 1,最终在 xdp_data_meta_unsupported() 里判断为不支持 metadata,从而导致 bpf_xdp_adjust_meta() 返回 -524。

奇葩小需求:非要将信息从 XDP on Mellanox 传到 tc-bpf 呢?

在 Mellanox 驱动不支持 XDP metadata 的情况下,如何将信息从 XDP on Mellanox 传到 tc-bpf 呢?

一个大胆且实用的办法:使用 struct iphdr IP 头里的 version 字段。

已知需要处理的流量是 IPv4,那么 version 字段固定是 4,就这么处理:

  1. 在 XDP 程序里,将 version 字段的值设置为非 4/6 的值,比如 0。
  2. 在 tc-bpf 程序里,判断 version 字段的值是否为 0,如果是,说明这是需要特殊处理的流量,然后将 version 字段的值设置为 4。

因为在 XDP on Mellanox 到 tc-bpf 的路径上,网络包不会被修改,所以在这两个地方都不需要计算 IP 头部的 checksum。

小结

即使是网卡驱动,也在不断地支持 XDP 一些特性,比如 XDP metadata。

所以,不能拿着 XDP generic mode 的知识去对待 XDP native mode,它们之间还是有所区别的。

不要过于自信,保持对技术的敬畏之心。