Bcc

Bcc是ebpf的编译工具集合,前端提供python/lua调用,本身通过c语言实现,集成llvm/clang,将ebpf代码注入,提供一些更人性化的函数给用户使用,比如函数的注入等,里面提供了很多xdp的例子,本文将从bcc xdp例子为入口,研究整个xdp的工作流程
附:ebpf框架(map作为用户态和内核态注入代码的通信(目前upstream xdp支持devmap、cpumap、xskmap),通过verfier检查,通过JIT即时翻译,底层有各种静态探针点,比如xdp,tracepoint,perf,也有动态探针点,比如kprobe)

xdp注入流程

以bcc的开源xdp_redirect_map为例讲述整个xdp的流程
https://github.com/iovisor/bcc/blob/b0bf04ac4042a6c004b15dcc5e40e12ae78020f9/examples/networking/xdp/xdp_redirect_map.py

int xdp_redirect_map(struct xdp_md *ctx) {
    void* data_end = (void*)(long)ctx->data_end;
    void* data = (void*)(long)ctx->data;
    struct ethhdr *eth = data;
    uint32_t key = 0;
    long *value;
    uint64_t nh_off;
    nh_off = sizeof(*eth);
    if (data + nh_off  > data_end)
        return XDP_DROP;
    value = rxcnt.lookup(&key);
    if (value)
        *value += 1;
    swap_src_dst_mac(data);
    return tx_port.redirect_map(0, 0);
}
int xdp_dummy(struct xdp_md *ctx) {
    return XDP_PASS;
}
""", cflags=["-w"])

tx_port = b.get_table("tx_port")
tx_port[0] = ct.c_int(out_idx)

in_fn = b.load_func("xdp_redirect_map", BPF.XDP)
out_fn = b.load_func("xdp_dummy", BPF.XDP)

b.attach_xdp(in_if, in_fn, flags)
b.attach_xdp(out_if, out_fn, flags)

这个sample讲的是将in ifdev的包文redirect到out ifdev发出去(比如in为eth0,out为eth1),
in_fn = b.load_func(“xdp_redirect_map”, BPF.XDP) //加载xdp_redirect_map函数,返回prog_fd给in_fn
in_if就是eth0 dev设备
b.attach_xdp(in_if, in_fn, flags),//attach xdp

attach_xdp

调用逻辑
(bcc)/src/cc/libbpf.c bpf_attach_xdp(const char *dev_name, int progfd, uint32_t flags)
—(linux)/tools/lib/bpf/netlink.c bpf_set_link_xdp_fd(int ifindex, int fd, __u32 flags)
建立netlink socket,发送
req.nh.nlmsg_type = RTM_SETLINK
req.ifinfo.ifi_family = AF_UNSPEC
req.ifinfo.ifi_index = ifindex; //传入out ifindex
nla->nla_type = NLA_F_NESTED | IFLA_XDP
memcpy((char *)nla_xdp + NLA_HDRLEN, &fd, sizeof(fd)); //传入xdp_redirect_map的prog_fd
接受端:
(linux)/net/core/rtnetlink.c
rtnl_setlink
根据ifm->ifi_index查询本net namespace的dev(后面简称outdev)
-do_setlink
从xdp[IFLA_XDP_PROG_ID中获取到prog_fd
–dev_change_xdp_fd

dev_change_xdp_fd

调用dev_xdp_install安装xdp函数
这里会调用dev->netdev_ops->ndo_bpf(XDP_SETUP_PROG)
以i40e网卡驱动为例,调用:
i40e_xdp_setup
将vsi->xdp_prog赋值为prog_fd
将vsi->rx_rings[i]->xdp_prog赋值为prog_fd
这样后面调用vsi->rx_rings[i]->xdp_prog时,就会调用到用户注入的代码

xdp收包

以i40e为例,底层驱动收包流程如下
i40e_napi_poll
—i40e_clean_rx_irq_zc
——i40e_run_xdp
———bpf_prog_run_xdp
————BPF_PROG_RUN
—————(*(prog)->bpf_func)(ctx, (prog)->insnsi)//这里就是用户态注入的函数本例注入的xdp_redirect_map函数(vsi->rx_rings[i]->xdp_prog->bpf_func)
——————swap_src_dst_mac(data);//交换src dst的mac,这样src为eth0 mac,dst为原nc的mac
——————tx_port.redirect_map//tx_port的设置是通过map机制,为eth2的ifdex
———————bpf_xdp_redirect_map(这个怎么来的?xdp_verifier_ops->xdp_func_proto->BPF_FUNC_redirect_map->bpf_xdp_redirect_map_proto->bpf_xdp_redirect_map)
————————struct bpf_redirect_info *ri = this_cpu_ptr(&bpf_redirect_info);
————————ri->ifindex = ifindex;
————————WRITE_ONCE(ri->map, map);
————————返回XDP_REDIRECT
———xdp_do_redirect
————xdp_do_redirect_map
————— __xdp_map_lookup_elem//根据index和map获取到目标端口(eth1)的信息
—————__bpf_tx_xdp_map  //将xdp数据赋值到目标eth1 dev的xdp_bluk_quue(this_cpu_ptr(obj->bulkq)->q[blukq->count])里面,这里遗留个flag MEM_TYPE_ZERO_COPY用于零拷贝(这个feature引入的文章https://lwn.net/Articles/754659/)
—————ri->map_to_flush = map;//设置map_to_flush标志
—— i40e_finalize_xdp_rx(rx_ring, xdp_xmit);//将xdp_bluk_quue里面的
——— xdp_do_flush_map
————__dev_map_flush
————— bq_xmit_all
—————— ndo_xdp_xmit(dev, count,q) //调用目标eth1的xdp发包

xdp发包

ndo_xdp_xmit

i40e网卡对应i40e_xdp_xmit
调用i40e_xmit_xdp_ring往DMA发包,这样从eth0收到的包文,就直接通过eth1发出去啦。

参考文献

https://qmonnet.github.io/whirl-offload/2016/09/01/dive-into-bpf/