近期又得搞 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_REDIRECTXDP_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;
}

以上代码片段做了以下工作:

  1. 准备 XDP 元数据
  2. xdp->data 网络包起始地址指向二层头部
  3. 运行 XDP 程序
  4. 对 XDP 程序运行结果进行简单处理,其中 XDP_ABORTEDXDP_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 程序中,可对当前网络包进行以下处理:

  1. XDP_ABORTED:中断网络包处理流程
  2. XDP_DROP:丢包,同时不会继续后面的网络包处理流程
  3. XDP_PASS:继续后面的网络包处理流程
  4. XDP_TX:将网络包从当前网卡发包出去
  5. XDP_REDIRECT:将网络包从其他网卡发包出去,即转发网络包

P.S. 网络上有不少 XDP 例子,在此就不提供 demo 啦。