书接上回 eBPF Talk: 解密 XDP generic 模式,本文从源代码层面剖析 XDP 转发网络包的实现。

demo

按需将网络包从另一张网卡转发走。

1
2
3
4
5
6
7
8
9
static volatile const u32 REDIRECT_IFINDEX = 0xFFFFFFFF;

SEC("xdp")
int xdp_redirect(struct xdp_md *xdp)
{
    // filter skb

    return bpf_redirect(REDIRECT_IFINDEX, 0);
}

其中,REDIRECT_IFINDEX 常量需要用户应用程序重写成目标网口的 ifindex。

bpf_xdp_redirect()

咦,为什么 XDP 具体的 bpf_redirect() 函数的实现是 bpf_xdp_redirect() 而不是 bpf_redirect() 呢?

好问题,又有一个可以深入挖掘的地方。

 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
// ${KERNEL}/net/core/filter.c

static const struct bpf_func_proto *
xdp_func_proto(enum bpf_func_id func_id, const struct bpf_prog *prog)
{
    switch (func_id) {
    // ...
    case BPF_FUNC_redirect:
        return &bpf_xdp_redirect_proto;
    case BPF_FUNC_redirect_map:
        return &bpf_xdp_redirect_map_proto;
    // ...
    default:
        return bpf_sk_base_func_proto(func_id);
    }
}

static const struct bpf_func_proto bpf_xdp_redirect_proto = {
    .func           = bpf_xdp_redirect,
    .gpl_only       = false,
    .ret_type       = RET_INTEGER,
    .arg1_type      = ARG_ANYTHING,
    .arg2_type      = ARG_ANYTHING,
};

static const struct bpf_func_proto bpf_xdp_redirect_map_proto = {
    .func           = bpf_xdp_redirect_map,
    .gpl_only       = false,
    .ret_type       = RET_INTEGER,
    .arg1_type      = ARG_CONST_MAP_PTR,
    .arg2_type      = ARG_ANYTHING,
    .arg3_type      = ARG_ANYTHING,
};

从如上代码片段可知,对于 XDP 程序而言,为 bpf_redirect() BPF 函数 (BPF_FUNC_redirect)注册的函数是 bpf_xdp_redirect()。所以,不要被 C 代码里的 bpf_redirect() 函数名称给欺骗了哦。

bpf_xdp_redirect() syscall

其源代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// ${KERNEL}/net/core/filter.c

BPF_CALL_2(bpf_xdp_redirect, u32, ifindex, u64, flags)
{
    struct bpf_redirect_info *ri = this_cpu_ptr(&bpf_redirect_info);

    if (unlikely(flags))
        return XDP_ABORTED;

    ri->flags = flags;
    ri->tgt_index = ifindex;
    ri->tgt_value = NULL;
    WRITE_ONCE(ri->map, NULL);

    return XDP_REDIRECT;
}

如上代码片段做了如下处理:

  1. 取出每个 CPU 的 struct bpf_redirect_info
  2. 将 ifindex 和 flags 保存到 struct bpf_redirect_info 中。
  3. 返回 XDP_REDIRECT

bpf_xdp_redirect_map()

其源代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// ${KERNEL}/net/core/filter.c

BPF_CALL_3(bpf_xdp_redirect_map, struct bpf_map *, map, u32, ifindex,
       u64, flags)
{
    struct bpf_redirect_info *ri = this_cpu_ptr(&bpf_redirect_info);

    // ...

    ri->flags = flags;
    ri->tgt_index = ifindex;
    WRITE_ONCE(ri->map, map);

    return XDP_REDIRECT;
}

处理逻辑类似 bpf_xdp_recirect(),这里多了一步:保存 map 到 struct bpf_redirect_info 中。

BPF_REDIRECT

此时再来看 eBPF Talk: 解密 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
 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
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
// ${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;
}

static int xdp_do_generic_redirect_map(struct net_device *dev,
                       struct sk_buff *skb,
                       struct xdp_buff *xdp,
                       struct bpf_prog *xdp_prog,
                       struct bpf_map *map)
{
    struct bpf_redirect_info *ri = this_cpu_ptr(&bpf_redirect_info);
    u32 index = ri->tgt_index;
    void *fwd = ri->tgt_value;
    int err = 0;

    ri->tgt_index = 0;
    ri->tgt_value = NULL;
    WRITE_ONCE(ri->map, NULL);

    if (map->map_type == BPF_MAP_TYPE_DEVMAP ||
        map->map_type == BPF_MAP_TYPE_DEVMAP_HASH) {
        struct bpf_dtab_netdev *dst = fwd;

        err = dev_map_generic_redirect(dst, skb, xdp_prog);
        if (unlikely(err))
            goto err;
    } else if (map->map_type == BPF_MAP_TYPE_XSKMAP) {
        struct xdp_sock *xs = fwd;

        err = xsk_generic_rcv(xs, xdp);
        if (err)
            goto err;
        consume_skb(skb);
    } else {
        /* TODO: Handle BPF_MAP_TYPE_CPUMAP */
        err = -EBADRQC;
        goto err;
    }

    return 0;
err:
    return err;
}

int xdp_do_generic_redirect(struct net_device *dev, struct sk_buff *skb,
                struct xdp_buff *xdp, struct bpf_prog *xdp_prog)
{
    struct bpf_redirect_info *ri = this_cpu_ptr(&bpf_redirect_info);
    struct bpf_map *map = READ_ONCE(ri->map);
    u32 index = ri->tgt_index;

    if (map)
        return xdp_do_generic_redirect_map(dev, skb, xdp, xdp_prog,
                           map);

    ri->tgt_index = 0;
    fwd = dev_get_by_index_rcu(dev_net(dev), index);
    err = xdp_ok_fwd_dev(fwd, skb->len);
    skb->dev = fwd;
    generic_xdp_tx(skb, xdp_prog);
    return 0;
err:
    return err;
}

// ${KERNEL}/kernel/bpf/devmap.c

int dev_map_generic_redirect(struct bpf_dtab_netdev *dst, struct sk_buff *skb,
                 struct bpf_prog *xdp_prog)
{
    int err;

    err = xdp_ok_fwd_dev(dst->dev, skb->len);
    if (unlikely(err))
        return err;
    skb->dev = dst->dev;
    generic_xdp_tx(skb, xdp_prog);

    return 0;
}

其中的 BPF_MAP_TYPE_XSKMAP 涉及 AF_XDP,先跳过吧。

对于 XDP bpf_redirect_map() 中向另一张网卡转发网络包的处理逻辑跟 bpf_redirect() 类似,最终都会

  1. 从每个 CPU 的 struct bpf_redirect_info 中获取目标 ifindex。
  2. 重置 struct bpf_redirect_info
  3. 检查能否从目标网卡发包出去。
  4. 设置 skb->dev 为目标网卡设备。
  5. 调用 generic_xdp_tx() 函数进行发包。

对于 XDP_TX 的处理,不就是调用 generic_xdp_tx() 函数进行发包吗?哈哈。

generic_xdp_tx()

其源代码如下:

 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
// ${KERNEL}/net/core/dev.c

/* When doing generic XDP we have to bypass the qdisc layer and the
 * network taps in order to match in-driver-XDP behavior.
 */
void generic_xdp_tx(struct sk_buff *skb, struct bpf_prog *xdp_prog)
{
    struct net_device *dev = skb->dev;
    struct netdev_queue *txq;
    bool free_skb = true;
    int cpu, rc;

    txq = netdev_core_pick_tx(dev, skb, NULL);
    cpu = smp_processor_id();
    HARD_TX_LOCK(dev, txq, cpu);
    if (!netif_xmit_stopped(txq)) {
        rc = netdev_start_xmit(skb, dev, txq, 0);
        if (dev_xmit_complete(rc))
            free_skb = false;
    }
    HARD_TX_UNLOCK(dev, txq);
    if (free_skb) {
        trace_xdp_exception(dev, xdp_prog, XDP_TX);
        kfree_skb(skb);
    }
}

该函数的处理逻辑:

  1. 取出 skb->devbpf_xdp_redirect()bpf_xdp_redirect_map() 都将 skb->dev 设置为目标网卡设备了。
  2. 选择发包队列 txq
  3. 调用 dev_xmit_start_xmit() 进行发包。

小结

至此,XDP 转发网络包的实现分析完毕。

填了 XDP 转发网络包这坑后,又留了 BPF_FUNC_xxx 的坑;这真的是,知道的越多、未知的更多。