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;
}
|
以上代码的主要处理逻辑如下:
- 更新
priv->_xdp_prog
。
- 准备好
rq
,并将 rq
添加到 veth 的 NAPI。
- 开启 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;
}
|
以上代码片段的主要处理逻辑:
- 从
rq->xdp_ring
里取出网络包。
- 判断网络包是不是
xdp_frame
。
- 如果是,则调用
veth_xdp_rcv_one()
去运行 XDP 程序处理网络包。
- 否则,调用
veth_xdp_rcv_skb()
去运行 XDP 程序处理网络包。
- 调用
veth_xdp_rcv_bulk_skb()
批量处理 xdp_frame
。
- 更新当前
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;
}
|
以上代码片段的主要处理逻辑:
- 调用
xdp_convert_frame_to_buff()
将 xdp_frame
转为 XDP 程序能够处理的 xdp_buff
。
- 调用
bpf_prog_run_xdp()
运行 XDP 程序。
- 根据 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;
// ...
}
|
以上代码片段的主要处理逻辑:
- 调用
veth_convert_skb_to_xdp_buff()
将 skb
转为 XDP 程序能够处理的 xdp_buff
。
- 调用
bpf_prog_run_xdp()
运行 XDP 程序。
- 根据 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 给内核。