当前位置: 首页 > 科技观察

XDP重定向机制简析

时间:2023-03-12 02:23:38 科技观察

1.XDPSocket示例解析源码参考:https://img.ydisp.cn/news/20220902/jhicc10qlio本示例演示如何将网络包从XDPHook点通过BPF发送到用户态的XDPSocket,为了突出解析过程中的关键点,只关注关键的代码段,并简化一些功能,如:错误处理等。2.BPF程序af_xdp_kern.cBPF程序是运行在内核态的一段代码,如下:structbpf_map_defSEC("maps")xsks_map={.type=BPF_MAP_TYPE_XSKMAP,.key_size=sizeof(int),.value_size=sizeof(int),.max_entries=64,/*假设netdev有不超过64个队列*/};SEC("xdp_sock")intxdp_sock_prog(structxdp_md*ctx){intindex=ctx->rx_queue_index;如果(bpf_map_lookup_elem(&xsks_map,&index))返回bpf_redirect_map(&xsks_map,index,0);returnXDP_PASS;}structbpf_map_defSEC("maps")xsks_map:定义一个BPF_MAP_TYPE_XSKMAP类型的映射表,当使用SEC("maps")显示定义时,会看到生成的bpf的ELF格式的相关描述目标文件。BPF程序加载到内核时,会自动创建一个名为“xsks_map”的描述符。用户态可以通过搜索“xsks_map”来获取地图。描述符,以便userland和内核BPF程序可以共同访问映射。type=BPF_MAP_TYPE_XSKMAP:指定map的类型,与bpf_redirect_map()配合使用,将接收到的帧投递到指定的socket。key_size=sizeof(int),value_size=sizeof(int):指定key和value的长度。对于上面的key和value,需要说明一下:对于BPF_MAP_TYPE_XSKMAP类型的map,value必须是XDP套接字描述符,key必须是int类型。原因是bpf_redirect_map()的第二个参数,见下面2.10。max_entries=64:指定地图最多存储64个元素。SEC("xdp_sock"):指定prog函数符号,应用层可以通过搜索"xdp_sock"来加载prog并绑定到指定的网卡上。intxdp_sock_prog(structxdp_md*ctx):当网卡收到一个数据包时,会在xdp挂钩点调用这个函数。intindex=ctx->rx_queue_index:获取数据包来自网卡的rx队列ID,ctx有很多成员,如:网卡ID,数据帧等。if(bpf_map_lookup_elem(&xsks_map,&index)):判断xsks_map是否有key为index(即rx队列号)的数据。注意这里其实是判断网卡是否绑定了xdpSocket。bpf_redirect_map(&xsks_map,index,0):bpf_redirect_map的作用是重定向,如:将数据重定向到网卡、CPU、Socket等;当bpf_redirect_map函数第一个参数的映射类型为BPF_MAP_TYPE_XSKMAP时,表示将数据重定向到XDPSocket。bpf_redirect_map()会检查参数1,即xsks_map中key为index的value是否存在,如果存在则检查该value是否为XDPSocket,是否绑定网卡(可以绑定到任何有效队列)。基于以上,bpf程序的作用就是将接收到的数据包重定向到xsks_map中指定的XDPSocket。3、用户态程序af_xdp_user.c该程序加载bpf到网卡,创建XDPSocket并绑定到网卡的指定队列,通过XDPSocket发送和接收数据。这里只分析xXDPSocket的相关部分。intmain(intargc,char**argv){...bpf_obj=load_bpf_and_xdp_attach(&cfg);map=bpf_object__find_map_by_name(bpf_obj,"xsks_map");...xsks_map_fd=bpf_map__fd(地图);...umem=configure_xsk_umem(packet_buffer,packet_buffer_size);...xsk_socket=xsk_configure_socket(&cfg,umem);...rx_and_process(&cfg,xsk_socket);...}staticstructxsk_socket_info*xsk_configure_socket(structconfig*cfg,structxsk_umem_info.*umem.ret=xsk_socket__create(&xsk_info->xsk,cfg->ifname,cfg->xsk_if_queue,umem->umem,&xsk_info->rx,&xsk_info->tx,&xsk_cfg);...bpf_obj=load_bpf_and_xdp_attach(&cfg):加载bpf程序,绑定网卡map=bpf_object__find_map_by_name(bpf_obj,"xsks_map"):查找bpf程序中定义的xsks_map。umem=configure_xsk_umem(packet_buffer,packet_buffer_size):为XDPScoket准备UMEM。xsk_configure_socket()通过调用bpf辅助函数xsk_socket__create()创建一个XDPSocket,并绑定到cfg->ifname网卡的cfg->xsk_if_queue队列。默认情况下,这个[cfg->xsk_if_queue,xsk_info->xskfd]被添加到xsks_map中,这样bpf程序就可以重定向到XDPSocket(见2.9,2.10),除非指定了XSK_LIBBPF_FLAGS__INHIBIT_PROG_LOAD标志。staticvoidrx_and_process(structconfig*cfg,structxsk_socket_info*xsk_socket){structpollfdfds[2];intret,nfds=1;memset(fds,0,sizeof(fds));fds[0].fd=xsk_socket__fd(xsk_socket->xsk);fds[0].events=POLLIN;while(!global_exit){if(cfg->xsk_poll_mode){ret=poll(fds,nfds,-1);如果(ret<=0||ret>1)继续;}handle_receive_packets(xsk_socket);}}XDPSocket也是一个文件描述符,所以可以通过poll/epoll/select等待IO事件。需要说明的是:接收/发送的数据包都是原始以太网帧,所以在包处理上稍微麻烦一点。4.总结以上简单分析了bpf程序如何将数据重定向到用户态程序,通过xsks_map实现bpf与用户态程序的交互;BPF是如何读写数据到XDPSocket发送接收缓冲区的?其实是通过创建共享内存,关联XDPSocket的rx_ring、tx_ring、umem来实现的,后面继续分析。bpf程序一般都很简单,复杂的是用户态程序。另外BPF还有很多技术细节,这里限于篇幅和话题就不展开了。