分类
ebpf

xdp ebpf程序执行流程

xdp 使用ebpf 做 包过滤,相对于dpdk将数据包直接送到用户态,用用户态当做快速数据处理平面,xdp是在驱动层创建了一个数据快速平面。在数据被网卡硬件dma到内存,分配skb之前,对数据包进行处理。由于完全不存在锁操作。且bypass了协议栈,非常适合用修改数据包并转发,数据探针,执行丢包。
xdp 程序执行的位置实在ixgbe_clean_rx_irq函数中。网卡刚把数据dma到内存时。

        while (likely(total_rx_packets < budget)) {
                union ixgbe_adv_rx_desc *rx_desc;
                struct ixgbe_rx_buffer *rx_buffer;
                struct sk_buff *skb;
                unsigned int size;

                /* return some buffers to hardware, one at a time is too slow */
                if (cleaned_count >= IXGBE_RX_BUFFER_WRITE) {
                        ixgbe_alloc_rx_buffers(rx_ring, cleaned_count);
                        cleaned_count = 0;
                }

                rx_desc = IXGBE_RX_DESC(rx_ring, rx_ring->next_to_clean);
                size = le16_to_cpu(rx_desc->wb.upper.length);
                if (!size)
                        break;

                /* This memory barrier is needed to keep us from reading
                 * any other fields out of the rx_desc until we know the
                 * descriptor has been written back
                 */
                dma_rmb();

                rx_buffer = ixgbe_get_rx_buffer(rx_ring, rx_desc, &skb, size);

                /* retrieve a buffer from the ring */
                if (!skb) {
                        xdp.data = page_address(rx_buffer->page) +
                                   rx_buffer->page_offset;
                        xdp.data_meta = xdp.data;
                        xdp.data_hard_start = xdp.data -
                                              ixgbe_rx_offset(rx_ring);
                        xdp.data_end = xdp.data + size;

                        skb = ixgbe_run_xdp(adapter, rx_ring, &xdp);            //执行run_xdp函数
                }

ixgbe_run_xdp 函数。

static struct sk_buff *ixgbe_run_xdp(struct ixgbe_adapter *adapter,
                                     struct ixgbe_ring *rx_ring,
                                     struct xdp_buff *xdp)
{
        int err, result = IXGBE_XDP_PASS;
        struct bpf_prog *xdp_prog;
        u32 act;

        rcu_read_lock();
        xdp_prog = READ_ONCE(rx_ring->xdp_prog);

        if (!xdp_prog)              //如果没有注册xdp程序,则直接跳出处理流程
                goto xdp_out;

        act = bpf_prog_run_xdp(xdp_prog, xdp);                  //执行xdp虚拟机。 xdp_prog 是 ebpf程序体, xdp是出入给ebpf程序的参数
        switch (act) {
        case XDP_PASS:              //xdp处理结果是将数据上送协议栈
                break;
        case XDP_TX:                //xdp处理结果是将数据通过同一个口转发出去
                result = ixgbe_xmit_xdp_ring(adapter, xdp);
                break;
        case XDP_REDIRECT:          //xdp处理结果是从另一个网口转发出去
                err = xdp_do_redirect(adapter->netdev, xdp, xdp_prog);
                if (!err)
                        result = IXGBE_XDP_TX;
                else    
                        result = IXGBE_XDP_CONSUMED;
                break;
        default:
                bpf_warn_invalid_xdp_action(act);
                /* fallthrough */
        case XDP_ABORTED:               //xdp程序异常丢掉数据包
                trace_xdp_exception(rx_ring->netdev, xdp_prog, act);
                /* fallthrough -- handle aborts by dropping packet */
        case XDP_DROP:                  //数据包被丢弃。
                result = IXGBE_XDP_CONSUMED;
                break;
        }
xdp_out:
        rcu_read_unlock();
        return ERR_PTR(-result);
}

设置xdp程序函数。在intel 82599网卡中使用的是 ixgbe_xdp函数。 ixgbe_xdp函数最后赋值给 net_device_ops结构体的ndo_bpf函数变量中。

static int ixgbe_xdp(struct net_device *dev, struct netdev_bpf *xdp)
{       
        struct ixgbe_adapter *adapter = netdev_priv(dev);

        switch (xdp->command) {
        case XDP_SETUP_PROG:
                return ixgbe_xdp_setup(dev, xdp->prog);         //设置xdp程序,把xdp程序设置到rx_ring的 xdp_prog变量中。
        case XDP_QUERY_PROG:
                xdp->prog_attached = !!(adapter->xdp_prog);
                xdp->prog_id = adapter->xdp_prog ?
                        adapter->xdp_prog->aux->id : 0;
                return 0;
        default:
                return -EINVAL;
        }
}

在 net/core/rtnetlink.c文件中的rtnl_setlink函数中会调用最后会调用 do_setlink函数。 rtnl_setlink 关联到 PF_UNSPEC netlink 的 RTM_SETLINK 中,用户态发送这类消息时,直接调用rtnl_setlink函数处理。
do_setlink函数 最后会调用dev_change_xdp_fd函数

        if (tb[IFLA_XDP]) {
                struct nlattr *xdp[IFLA_XDP_MAX + 1];
                u32 xdp_flags = 0;

                err = nla_parse_nested(xdp, IFLA_XDP_MAX, tb[IFLA_XDP],
                                       ifla_xdp_policy, NULL);
                if (err < 0)
                        goto errout;

                if (xdp[IFLA_XDP_ATTACHED] || xdp[IFLA_XDP_PROG_ID]) {
                        err = -EINVAL;
                        goto errout;
                }

                if (xdp[IFLA_XDP_FLAGS]) {
                        xdp_flags = nla_get_u32(xdp[IFLA_XDP_FLAGS]);
                        if (xdp_flags & ~XDP_FLAGS_MASK) {
                                err = -EINVAL;
                                goto errout;
                        }
                        if (hweight32(xdp_flags & XDP_FLAGS_MODES) > 1) {
                                err = -EINVAL;
                                goto errout;
                        }
                }

                if (xdp[IFLA_XDP_FD]) {
                        err = dev_change_xdp_fd(dev, extack,
                                                nla_get_s32(xdp[IFLA_XDP_FD]),
                                                xdp_flags);         //修改指定dev的 prog
                        if (err)
                                goto errout;
                        status |= DO_SETLINK_NOTIFY;
                }
        }

dev_change_xdp_fd函数做了一系列检查后最后调用 dev_xdp_install函数

int dev_change_xdp_fd(struct net_device *dev, struct netlink_ext_ack *extack,
                      int fd, u32 flags)
{       
        const struct net_device_ops *ops = dev->netdev_ops;
        struct bpf_prog *prog = NULL;
        bpf_op_t bpf_op, bpf_chk;
        int err;

        ASSERT_RTNL();

        bpf_op = bpf_chk = ops->ndo_bpf;                //获取网卡驱动ndo_bpf程序 ixgbe网卡的函数是 ixgbe_bpf
        if (!bpf_op && (flags & (XDP_FLAGS_DRV_MODE | XDP_FLAGS_HW_MODE)))
                return -EOPNOTSUPP;
        if (!bpf_op || (flags & XDP_FLAGS_SKB_MODE))
                bpf_op = generic_xdp_install;
        if (bpf_op == bpf_chk)
                bpf_chk = generic_xdp_install;

        if (fd >= 0) {
                if (bpf_chk && __dev_xdp_attached(dev, bpf_chk))
                        return -EEXIST;
                if ((flags & XDP_FLAGS_UPDATE_IF_NOEXIST) &&
                    __dev_xdp_attached(dev, bpf_op))
                        return -EBUSY;

                prog = bpf_prog_get_type_dev(fd, BPF_PROG_TYPE_XDP,
                                             bpf_op == ops->ndo_bpf);
                if (IS_ERR(prog))
                        return PTR_ERR(prog);

                if (!(flags & XDP_FLAGS_HW_MODE) &&
                    bpf_prog_is_dev_bound(prog->aux)) {
                        NL_SET_ERR_MSG(extack, "using device-bound program without HW_MODE flag is not supported");
                        bpf_prog_put(prog);
                        return -EINVAL;
                }
        }

        err = dev_xdp_install(dev, bpf_op, extack, flags, prog);        //将xdp ebpf程序安装到网卡的 rx_ring中
        if (err < 0 && prog)
                bpf_prog_put(prog);

        return err;
}

dev_xdp_install 函数代码如下

static int dev_xdp_install(struct net_device *dev, bpf_op_t bpf_op,
                           struct netlink_ext_ack *extack, u32 flags,
                           struct bpf_prog *prog)
{               
        struct netdev_bpf xdp;

        memset(&xdp, 0, sizeof(xdp));
        if (flags & XDP_FLAGS_HW_MODE)
                xdp.command = XDP_SETUP_PROG_HW;
        else    
                xdp.command = XDP_SETUP_PROG;
        xdp.extack = extack;
        xdp.flags = flags;
        xdp.prog = prog;

        return bpf_op(dev, &xdp);           //执行ndo_xdp函数, ixgbe驱动的函数为ixgbe_bpf
} 

至此,xdp 运行位置及用户程序将ebpf代码attach到网卡的流程都分析完毕。

发表评论

电子邮件地址不会被公开。 必填项已用*标注