veth 是一种虚拟网络设备,并支持在驱动里运行 XDP 程序。

与 Mellanox 物理网卡对比,veth 上运行 XDP 程序有和区别呢?

ndo_xdp on veth

这是将 XDP 程序下发到 veth 驱动里的函数。

 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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
// ${KERNEL}/drivers/net/veth.c

static const struct net_device_ops veth_netdev_ops = {
    // ...
    .ndo_bpf        = veth_xdp,
    // ...
};

static int veth_xdp(struct net_device *dev, struct netdev_bpf *xdp)
{
    switch (xdp->command) {
    case XDP_SETUP_PROG:
        return veth_xdp_set(dev, xdp->prog, xdp->extack);
    default:
        return -EINVAL;
    }
}

static int veth_xdp_set(struct net_device *dev, struct bpf_prog *prog,
            struct netlink_ext_ack *extack)
{
    // ...

    old_prog = priv->_xdp_prog;
    priv->_xdp_prog = prog;
    peer = rtnl_dereference(priv->peer);

    if (prog) {
        // ...

        if (dev->flags & IFF_UP) {
            err = veth_enable_xdp(dev);
            // ...
        }

        // ...
    }

    // ...

    return 0;
err:
    priv->_xdp_prog = old_prog;

    return err;
}

static int veth_enable_xdp(struct net_device *dev)
{
    bool napi_already_on = veth_gro_requested(dev) && (dev->flags & IFF_UP);
    struct veth_priv *priv = netdev_priv(dev);
    int err, i;

    if (!xdp_rxq_info_is_reg(&priv->rq[0].xdp_rxq)) {
        err = veth_enable_xdp_range(dev, 0, dev->real_num_rx_queues, napi_already_on);
        if (err)
            return err;

        if (!napi_already_on) {
            err = __veth_napi_enable(dev);
            // ...
        }
    }

    // ...

    return 0;
}

static int veth_enable_xdp_range(struct net_device *dev, int start, int end,
                 bool napi_already_on)
{
    struct veth_priv *priv = netdev_priv(dev);
    int err, i;

    for (i = start; i < end; i++) {
        struct veth_rq *rq = &priv->rq[i];

        if (!napi_already_on)
            netif_napi_add(dev, &rq->xdp_napi, veth_poll);
        // ...

        /* Save original mem info as it can be overwritten */
        rq->xdp_mem = rq->xdp_rxq.mem;
    }
    return 0;

    // ...

    return err;
}

以上代码的主要处理逻辑如下:

  1. 更新 priv->_xdp_prog
  2. 准备好 rq,并将 rq 添加到 veth 的 NAPI。
  3. 开启 veth 的 NAPI。

当在 veth 驱动里运行 XDP 程序时,需要使用 NAPI 从对端 veth 设备接收网络包。

poll on veth

由前面 ndo_bpf 可知,veth 驱动的 poll 函数是 veth_poll()

veth_poll() 接收到网络包时,会做哪些处理呢?

 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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
// ${KERNEL}/drivers/net/veth.c

static int veth_poll(struct napi_struct *napi, int budget)
{
    // ...
    done = veth_xdp_rcv(rq, budget, &bq, &stats);

    // ...

    return done;
}


static int veth_xdp_rcv(struct veth_rq *rq, int budget,
            struct veth_xdp_tx_bq *bq,
            struct veth_stats *stats)
{
    int i, done = 0, n_xdpf = 0;
    void *xdpf[VETH_XDP_BATCH];

    for (i = 0; i < budget; i++) {
        void *ptr = __ptr_ring_consume(&rq->xdp_ring);

        if (!ptr)
            break;

        if (veth_is_xdp_frame(ptr)) {
            /* ndo_xdp_xmit */
            struct xdp_frame *frame = veth_ptr_to_xdp(ptr);

            stats->xdp_bytes += xdp_get_frame_len(frame);
            frame = veth_xdp_rcv_one(rq, frame, bq, stats);
            if (frame) {
                /* XDP_PASS */
                xdpf[n_xdpf++] = frame;
                if (n_xdpf == VETH_XDP_BATCH) {
                    veth_xdp_rcv_bulk_skb(rq, xdpf, n_xdpf,
                                  bq, stats);
                    n_xdpf = 0;
                }
            }
        } else {
            /* ndo_start_xmit */
            struct sk_buff *skb = ptr;

            stats->xdp_bytes += skb->len;
            skb = veth_xdp_rcv_skb(rq, skb, bq, stats);
            if (skb) {
                if (skb_shared(skb) || skb_unclone(skb, GFP_ATOMIC))
                    netif_receive_skb(skb);
                else
                    napi_gro_receive(&rq->xdp_napi, skb);
            }
        }
        done++;
    }

    if (n_xdpf)
        veth_xdp_rcv_bulk_skb(rq, xdpf, n_xdpf, bq, stats);

    u64_stats_update_begin(&rq->stats.syncp);
    rq->stats.vs.xdp_redirect += stats->xdp_redirect;
    rq->stats.vs.xdp_bytes += stats->xdp_bytes;
    rq->stats.vs.xdp_drops += stats->xdp_drops;
    rq->stats.vs.rx_drops += stats->rx_drops;
    rq->stats.vs.xdp_packets += done;
    u64_stats_update_end(&rq->stats.syncp);

    return done;
}

以上代码片段的主要处理逻辑:

  1. rq->xdp_ring 里取出网络包。
  2. 判断网络包是不是 xdp_frame
  3. 如果是,则调用 veth_xdp_rcv_one() 去运行 XDP 程序处理网络包。
  4. 否则,调用 veth_xdp_rcv_skb() 去运行 XDP 程序处理网络包。
  5. 调用 veth_xdp_rcv_bulk_skb() 批量处理 xdp_frame
  6. 更新当前 rq 统计。

Run xdp_prog with xdp_frame

 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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
// ${KERNEL}/drivers/net/veth.c

static struct xdp_frame *veth_xdp_rcv_one(struct veth_rq *rq,
                      struct xdp_frame *frame,
                      struct veth_xdp_tx_bq *bq,
                      struct veth_stats *stats)
{
    struct xdp_frame orig_frame;
    struct bpf_prog *xdp_prog;

    rcu_read_lock();
    xdp_prog = rcu_dereference(rq->xdp_prog);
    if (likely(xdp_prog)) {
        struct xdp_buff xdp;
        u32 act;

        xdp_convert_frame_to_buff(frame, &xdp);
        xdp.rxq = &rq->xdp_rxq;

        act = bpf_prog_run_xdp(xdp_prog, &xdp);

        switch (act) {
        case XDP_PASS:
            if (xdp_update_frame_from_buff(&xdp, frame))
                goto err_xdp;
            break;
        case XDP_TX:
            orig_frame = *frame;
            xdp.rxq->mem = frame->mem;
            if (unlikely(veth_xdp_tx(rq, &xdp, bq) < 0)) {
                trace_xdp_exception(rq->dev, xdp_prog, act);
                frame = &orig_frame;
                stats->rx_drops++;
                goto err_xdp;
            }
            stats->xdp_tx++;
            rcu_read_unlock();
            goto xdp_xmit;
        case XDP_REDIRECT:
            orig_frame = *frame;
            xdp.rxq->mem = frame->mem;
            if (xdp_do_redirect(rq->dev, &xdp, xdp_prog)) {
                frame = &orig_frame;
                stats->rx_drops++;
                goto err_xdp;
            }
            stats->xdp_redirect++;
            rcu_read_unlock();
            goto xdp_xmit;
        default:
            bpf_warn_invalid_xdp_action(rq->dev, xdp_prog, act);
            fallthrough;
        case XDP_ABORTED:
            trace_xdp_exception(rq->dev, xdp_prog, act);
            fallthrough;
        case XDP_DROP:
            stats->xdp_drops++;
            goto err_xdp;
        }
    }
    rcu_read_unlock();

    return frame;
err_xdp:
    rcu_read_unlock();
    xdp_return_frame(frame);
xdp_xmit:
    return NULL;
}

以上代码片段的主要处理逻辑:

  1. 调用 xdp_convert_frame_to_buff()xdp_frame 转为 XDP 程序能够处理的 xdp_buff
  2. 调用 bpf_prog_run_xdp() 运行 XDP 程序。
  3. 根据 XDP 程序结果处理当前网络包。

Run xdp_prog with skb

 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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
// ${KERNEL}/dirvers/net/veth.c

static struct sk_buff *veth_xdp_rcv_skb(struct veth_rq *rq,
                    struct sk_buff *skb,
                    struct veth_xdp_tx_bq *bq,
                    struct veth_stats *stats)
{
    // ...

    if (veth_convert_skb_to_xdp_buff(rq, &xdp, &skb))
        goto drop;

    orig_data = xdp.data;
    orig_data_end = xdp.data_end;

    act = bpf_prog_run_xdp(xdp_prog, &xdp);

    switch (act) {
    case XDP_PASS:
        break;
    case XDP_TX:
        veth_xdp_get(&xdp);
        consume_skb(skb);
        xdp.rxq->mem = rq->xdp_mem;
        if (unlikely(veth_xdp_tx(rq, &xdp, bq) < 0)) {
            trace_xdp_exception(rq->dev, xdp_prog, act);
            stats->rx_drops++;
            goto err_xdp;
        }
        stats->xdp_tx++;
        rcu_read_unlock();
        goto xdp_xmit;
    case XDP_REDIRECT:
        veth_xdp_get(&xdp);
        consume_skb(skb);
        xdp.rxq->mem = rq->xdp_mem;
        if (xdp_do_redirect(rq->dev, &xdp, xdp_prog)) {
            stats->rx_drops++;
            goto err_xdp;
        }
        stats->xdp_redirect++;
        rcu_read_unlock();
        goto xdp_xmit;
    default:
        bpf_warn_invalid_xdp_action(rq->dev, xdp_prog, act);
        fallthrough;
    case XDP_ABORTED:
        trace_xdp_exception(rq->dev, xdp_prog, act);
        fallthrough;
    case XDP_DROP:
        stats->xdp_drops++;
        goto xdp_drop;
    }
    // ...

    metalen = xdp.data - xdp.data_meta;
    if (metalen)
        skb_metadata_set(skb, metalen);
out:
    return skb;

    // ...
}

以上代码片段的主要处理逻辑:

  1. 调用 veth_convert_skb_to_xdp_buff()skb 转为 XDP 程序能够处理的 xdp_buff
  2. 调用 bpf_prog_run_xdp() 运行 XDP 程序。
  3. 根据 XDP 程序结果处理当前网络包。

XDP_REDIRECT on veth

XDP_REDIRECT 的处理过程请参考 eBPF Talk: XDP on Mellanox 里的 XDP_REDIRECT on Mellanox

napi_gro_receive()

在运行 XDP 完毕之后,就调用 napi_gro_receive() 函数进入内核协议栈处理流程。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
veth_poll(struct napi_struct *napi, int budget)             // ${KERNEL}/drivers/net/veth.c
|-->veth_xdp_rcv()
    |-->veth_xdp_rcv_bulk_skb()
        |-->skb = __xdp_build_skb_from_frame();             // ${KERNEL}/net/core/xdp.c
        |   |-->skb = build_skb_around(skb, hard_start, frame_size);
        |-->napi_gro_receive(&rq->xdp_napi, skb);           // ${KERNEL}/net/core/dev.c
            |-->napi_skb_finish()
                |-->gro_normal_one()
                    |-->gro_normal_list()
                        |-->netif_receive_skb_list_internal()
                            |-->__netif_receive_skb_list()
                                |-->...

参考:eBPF Talk: veth, XDP, GRO ?

总结

快速过了一遍 XDP on veth 的源代码,似乎不知所谓,就权当整理了一份技术资料吧。

  • XDP on veth 能够处理别的网络设备 XDP_REDIRECT 过来的 xdp_frame;且能够将 adjust 后的网络包 XDP_PASS 给内核。
  • XDP on veth 能够处理对端网络设备传递过来的 skb;不能将 adjust 后的网络包 XDP_PASS 给内核。