近期又得搞 XDP 了,就顺手研究了下 XDP generic 模式的源代码。
XDP generic 模式的函数位置
已知,内核协议栈会对每个 skb 都执行一次 XDP generic 模式处理,当然是启用了 XDP generic 模式的情况下。
参考:Generic XDP 处理(软件 XDP)。
相关源代码如下:
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
|
// ${KERNEL}/net/core/dev.c
static int __netif_receive_skb_core(struct sk_buff **pskb, bool pfmemalloc,
struct packet_type **ppt_prev)
{
// ...
if (static_branch_unlikely(&generic_xdp_needed_key)) {
int ret2;
preempt_disable();
ret2 = do_xdp_generic(rcu_dereference(skb->dev->xdp_prog), skb);
preempt_enable();
if (ret2 != XDP_PASS) {
ret = NET_RX_DROP;
goto out;
}
skb_reset_mac_len(skb);
}
// ...
out:
*pskb = skb;
return ret;
}
|
调用 do_xdp_generic()
函数,如果函数返回结果不是 XDP_PASS
,则结束当前 skb 的处理流程。
为什么不是 XDP_PASS
就结束当前的处理流程了呢?
先看一下 do_xdp_generic()
函数有哪些返回结果:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
// ${KERNEL}/include/uapi/linux/bpf.h
/* User return codes for XDP prog type.
* A valid XDP program must return one of these defined values. All other
* return codes are reserved for future use. Unknown return codes will
* result in packet drops and a warning via bpf_warn_invalid_xdp_action().
*/
enum xdp_action {
XDP_ABORTED = 0,
XDP_DROP,
XDP_PASS,
XDP_TX,
XDP_REDIRECT,
};
|
而这些返回结果对应的处理流程,稍候有详细讲解。
do_xdp_generic
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
|
// ${KERNEL}/net/core/dev.c
int do_xdp_generic(struct bpf_prog *xdp_prog, struct sk_buff *skb)
{
if (xdp_prog) {
struct xdp_buff xdp;
u32 act;
int err;
act = netif_receive_generic_xdp(skb, &xdp, xdp_prog);
if (act != XDP_PASS) {
switch (act) {
case XDP_REDIRECT: // 对 skb 进行转发处理
err = xdp_do_generic_redirect(skb->dev, skb,
&xdp, xdp_prog);
if (err)
goto out_redir;
break;
case XDP_TX: // 对 skb 进行发包处理
generic_xdp_tx(skb, xdp_prog);
break;
}
return XDP_DROP;
}
}
return XDP_PASS;
out_redir:
kfree_skb(skb);
return XDP_DROP;
}
|
调用 netif_receive_generic_xdp()
函数去实现 XDP generic 模式,并处理 XDP_REDIRECT
和 XDP_TX
这两种结果。
XDP_REDIRECT
:调用 xdp_do_generic_redirect()
函数进行转发处理。
XDP_TX
:调用 generic_xdp_tx()
函数进行发包处理。
如果 netif_receive_generic_xdp()
函数的返回结果不是 XDP_PASS
,则返回 XDP_DROP
。意即,所有非 XDP_PASS
的网络包都已经处理完毕。
netif_receive_generic_xdp
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
|
// ${KERNEL}/net/core/dev.c
static u32 netif_receive_generic_xdp(struct sk_buff *skb,
struct xdp_buff *xdp,
struct bpf_prog *xdp_prog)
{
// ...
// 准备 XDP 元数据,即准备 XDP 程序的第一个参数
/* The XDP program wants to see the packet starting at the MAC
* header.
*/
mac_len = skb->data - skb_mac_header(skb);
hlen = skb_headlen(skb) + mac_len;
xdp->data = skb->data - mac_len;
xdp->data_meta = xdp->data;
xdp->data_end = xdp->data + hlen;
xdp->data_hard_start = skb->data - skb_headroom(skb);
/* SKB "head" area always have tailroom for skb_shared_info */
xdp->frame_sz = (void *)skb_end_pointer(skb) - xdp->data_hard_start;
xdp->frame_sz += SKB_DATA_ALIGN(sizeof(struct skb_shared_info));
orig_data_end = xdp->data_end;
orig_data = xdp->data;
eth = (struct ethhdr *)xdp->data;
orig_bcast = is_multicast_ether_addr_64bits(eth->h_dest);
orig_eth_type = eth->h_proto;
rxqueue = netif_get_rxqueue(skb);
xdp->rxq = &rxqueue->xdp_rxq;
act = bpf_prog_run_xdp(xdp_prog, xdp);
// ...
switch (act) {
case XDP_REDIRECT:
case XDP_TX:
__skb_push(skb, mac_len);
break;
case XDP_PASS:
metalen = xdp->data - xdp->data_meta;
if (metalen)
skb_metadata_set(skb, metalen);
break;
default:
bpf_warn_invalid_xdp_action(act);
fallthrough;
case XDP_ABORTED:
trace_xdp_exception(skb->dev, xdp_prog, act);
fallthrough;
case XDP_DROP:
do_drop:
kfree_skb(skb);
break;
}
return act;
}
|
以上代码片段做了以下工作:
- 准备 XDP 元数据
xdp->data
网络包起始地址指向二层头部
- 运行 XDP 程序
- 对 XDP 程序运行结果进行简单处理,其中
XDP_ABORTED
和 XDP_DROP
直接释放 skb
结合 do_xdp_generic()
函数处理逻辑可知,do_xdp_generic()
+ netif_receive_generic_xdp()
处理了所有非 XDP_PASS
的情况;
所以在 __netif_receive_skb_core()
函数中,所有非 XDP_PASS
的情况都结束当前 skb 的处理流程。
bpf_prog_run_xdp
1
2
3
4
5
6
7
8
9
10
11
12
13
|
// ${KERNEL}/include/linux/filter.h
static __always_inline u32 bpf_prog_run_xdp(const struct bpf_prog *prog,
struct xdp_buff *xdp)
{
/* Caller needs to hold rcu_read_lock() (!), otherwise program
* can be released while still running, or map elements could be
* freed early while still having concurrent users. XDP fastpath
* already takes rcu_read_lock() when fetching the program, so
* it's not necessary here anymore.
*/
return __BPF_PROG_RUN(prog, xdp, BPF_DISPATCHER_FUNC(xdp));
}
|
直接运行指定的 BPF 程序。此处不再展开 __BPF_PROG_RUN()
。
小结
从源代码可知,XDP eBPF 程序处理的网络包是从二层开始的。因此,就有了一个类似 tcpdump 的抓包工具 xdp-dump。
而在 XDP eBPF 程序中,可对当前网络包进行以下处理:
XDP_ABORTED
:中断网络包处理流程
XDP_DROP
:丢包,同时不会继续后面的网络包处理流程
XDP_PASS
:继续后面的网络包处理流程
XDP_TX
:将网络包从当前网卡发包出去
XDP_REDIRECT
:将网络包从其他网卡发包出去,即转发网络包
P.S. 网络上有不少 XDP 例子,在此就不提供 demo 啦。