本文将介绍如何利用dpdk实现udp的发包和收包。 从代码层次来看一共分为四部分——初始化,收包,整合,发包。下面将按照这个顺序进行介绍。本文均为个人观点,如有错误请纠正。
一.初始化。
1.参数初始化:
if(rte_eal_init(argc,argv)<0){rte_exit(EXIT_FAILURE,"Error");//dpdk中失败退出的函数}//参数初始化这个函数成功的返回值是大于等于0。之后创建用于存放网络数据包的内存池。函数是
structrte_mempool*mbuf_pool=rte_pktmbuf_pool_create("mbuf pool",NUM_MBUFS,0,0,RTE_MBUF_DEFAULT_BUF_SIZE,rte_socket_id());这个函数一共有6个参数。依次是名称;内存池内部有多少内存格;每个CPU核的缓存大小;每个内存格的私有大小;每个内存格的数据区大小;内存分配在哪个 NUMA 节点上。举个例子:这个内存池就好比一个柜子,它的名字是什么?内部有多少个收纳区(在dpdk中每个收纳区大小都一样)?每个 CPU 核可以私自偷偷缓存几个格子(0 表示不私藏,用一个取一个)?每个收纳格除了放东西之外,还能贴多大的私人便利贴(0 表示不贴)?每个收纳区最多能放多少东西?这个柜子在当前 CPU 所在的哪个房间?
之后进行硬件初始化:
1.先查看有几个网卡可用
uint16_tnb_sys_ports=rte_eth_dev_count_avail();2.之后拿到这个对应网卡的设备信息
structrte_eth_dev_infodev_info;rte_eth_dev_info_get(globle_portid,&dev_info);// globle_portid = 0 代表第1个网卡 1-->2 以此类推3.初始化网卡:
(1).这里首先配置接收模式(rxmode)中,最大能接收的包长度 = 标准以太网最大帧长(1518 字节)。其余配置项没写,全部用默认值。
staticconststructrte_eth_confport_conf_default={.rxmode={.max_rx_pkt_len=RTE_ETHER_MAX_LEN}//最大能接收的包长度 = 标准以太网最大帧长};(2).然后把接收和发送队列全部设置只有一列。
constintnum_rx_queues=1;constintnum_tx_queues=1;(3).配置网口。四个参数分别是哪个网卡?开几个接收队列?开几个发送队列?具体怎么配置?
rte_eth_dev_configure(globle_portid,num_rx_queues,num_tx_queues,&port_conf_default);(4).队列初始化:
接收队列——rte_eth_rx_queue_setup六个参数依次是:哪个网卡?初始化几号队列?这个队列能放多少描述符?内存分配在哪个 NUMA 节点?接收队列的额外配置(NULL是默认)?收到的包往哪个内存池里放?
发送队列——rte_eth_tx_queue_setup五个参数依次是:哪个网卡?初始化几号队列?这个队列能放多少描述符?内存分配在哪个 NUMA 节点?发送队列的额外配置?这里之所以单独设置,是因为在前面设置了rxmode,为了让收发两侧的 offload 配置保持一致,满足 DPDK 网卡驱动的要求,避免初始化失败或运行异常。
if(rte_eth_rx_queue_setup(globle_portid,0,128,rte_eth_dev_socket_id(globle_portid),NULL,mbuf_pool)<0){rte_exit(EXIT_FAILURE,"could not setup RX queue\n");}structrte_eth_txconftxq_conf=dev_info.default_txconf;txq_conf.offloads=port_conf_default.rxmode.offloads;if(rte_eth_tx_queue_setup(globle_portid,0,512,rte_eth_dev_socket_id(globle_portid),&txq_conf)<0){rte_exit(EXIT_FAILURE,"could not setup TX queue\n");}(5).启动
if(rte_eth_dev_start(globle_portid)<0){rte_exit(EXIT_FAILURE,"could not start\n");}二.收包
1.接收。
四个参数依次是:哪个网卡?从第几个接收队列收?收到的包存到哪里?最多收几个包?
uint16_tnum_recvd=rte_eth_rx_burst(globle_portid,0,mbufs,BURST_SIZE);if(num_recvd>BURST_SIZE){rte_exit(EXIT_FAILURE,"recv error\n");}2.判断。
先判断以太网头里是不是ipv4协议,不是跳出循环。接着看ip头下一个是不是udp头。是则把数据拷贝便于后续发包。
inti=0;for(i=0;i<num_recvd;i++){structrte_ether_hdr*ethhdr=rte_pktmbuf_mtod(mbufs[i],structrte_ether_hdr*);if(ethhdr->ether_type!=rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4)){continue;}structrte_ipv4_hdr*iphdr=rte_pktmbuf_mtod_offset(mbufs[i],structrte_ipv4_hdr*,sizeof(structrte_ether_hdr));if(iphdr->next_proto_id==IPPROTO_UDP){structrte_udp_hdr*udphdr=(structrte_udp_hdr*)(iphdr+1);rte_memcpy(global_smac,ethhdr->d_addr.addr_bytes,RTE_ETHER_ADDR_LEN);rte_memcpy(global_dmac,ethhdr->s_addr.addr_bytes,RTE_ETHER_ADDR_LEN);rte_memcpy(&global_sip,&iphdr->dst_addr,sizeof(uint32_t));rte_memcpy(&global_dip,&iphdr->src_addr,sizeof(uint32_t));rte_memcpy(&global_sport,&udphdr->dst_port,sizeof(uint16_t));rte_memcpy(&global_dport,&udphdr->src_port,sizeof(uint16_t));uint16_tlength=ntohs(udphdr->dgram_len);uint16_ttotal_length=length+sizeof(structrte_ipv4_hdr)+sizeof(structrte_ether_hdr);}rte_pktmbuf_mtod:作用是从 mbuf 里取出数据包的起始地址,并转成你指定的类型指针。rte_pktmbuf_mtod_offset就是带偏移量版本的。ntohs(udphdr->dgram_len);dgram_len这个长度是udp头+数据的总长。
三.整合
以太网头
ip头
udp头
只需要将上图每种包的内容,按照顺序(以太网头,ip头,udp头+数据)粘贴进去就行了
staticintustack_encode_udp_pkt(uint8_t*msg,uint8_t*data,uint16_ttotal_len){//ether headerstructrte_ether_hdr*eth=(structrte_ether_hdr*)msg;rte_memcpy(eth->d_addr.addr_bytes,global_dmac,RTE_ETHER_ADDR_LEN);rte_memcpy(eth->s_addr.addr_bytes,global_smac,RTE_ETHER_ADDR_LEN);eth->ether_type=htons(RTE_ETHER_TYPE_IPV4);//ip header// struct rte_ipv4_hdr *ip = msg + sizeof(struct rte_ether_hdr);structrte_ipv4_hdr*ip=(structrte_ipv4_hdr*)(eth+1);ip->version_ihl=0x45;ip->type_of_service=0;ip->total_length=htons(total_len-sizeof(structrte_ether_hdr));ip->packet_id=0;ip->fragment_offset=0;ip->time_to_live=64;ip->next_proto_id=IPPROTO_UDP;ip->src_addr=global_sip;ip->dst_addr=global_dip;ip->hdr_checksum=0;ip->hdr_checksum=rte_ipv4_cksum(ip);// udp headerstructrte_udp_hdr*udp=(structrte_udp_hdr*)(ip+1);udp->src_port=global_sport;udp->dst_port=global_dport;uint16_tudplen=total_len-sizeof(structrte_ether_hdr)-sizeof(structrte_ipv4_hdr);udp->dgram_len=htons(udplen);rte_memcpy((uint8_t*)(udp+1),data,udplen);udp->dgram_cksum=0;udp->dgram_cksum=rte_ipv4_udptcp_cksum(ip,udp);return0;}四.发包
先要在内存池中开一个内存区用来存放整合包的位置,pkt_len 是整个包的总长度,data_len 是当前这段数据的长度。拿到 mbuf 数据区的起始地址,转成 uint8_t *,后续往这里写数据。把以太网头、IP头、UDP头、payload 整合成一个完整的 UDP 包,写进 msg 指向的内存。udphdr + 1 指向的是 UDP 头之后的 payload 数据。
structrte_mbuf*mbuf=rte_pktmbuf_alloc(mbuf_pool);if(!mbuf){rte_exit(EXIT_FAILURE,"alloc\n");}mbuf->pkt_len=total_length;mbuf->data_len=total_length;uint8_t*msg=rte_pktmbuf_mtod(mbuf,uint8_t*);ustack_encode_udp_pkt(msg,(uint8_t*)(udphdr+1),total_length);rte_eth_tx_burst(globle_portid,0,&mbuf,1);五.整体代码和运行结果
#include<stdio.h>#include<rte_eal.h>#include<rte_ethdev.h>#include<arpa/inet.h>#defineNUM_MBUFS4096#defineBURST_SIZE128uint8_tglobal_smac[RTE_ETHER_ADDR_LEN];uint8_tglobal_dmac[RTE_ETHER_ADDR_LEN];uint32_tglobal_sip;uint32_tglobal_dip;uint16_tglobal_sport;uint16_tglobal_dport;intgloble_portid=0;//网口从0开始staticconststructrte_eth_confport_conf_default={.rxmode={.max_rx_pkt_len=RTE_ETHER_MAX_LEN}};staticintustack_init_port(structrte_mempool*mbuf_pool){uint16_tnb_sys_ports=rte_eth_dev_count_avail();//有几个网卡可用// printf("nb_sys_ports: %d\n",nb_sys_ports);if(nb_sys_ports==0){rte_exit(EXIT_FAILURE,"No support eth\n");}structrte_eth_dev_infodev_info;rte_eth_dev_info_get(globle_portid,&dev_info);constintnum_rx_queues=1;constintnum_tx_queues=1;rte_eth_dev_configure(globle_portid,num_rx_queues,num_tx_queues,&port_conf_default);if(rte_eth_rx_queue_setup(globle_portid,0,128,rte_eth_dev_socket_id(globle_portid),NULL,mbuf_pool)<0){rte_exit(EXIT_FAILURE,"could not setup RX queue\n");}#ifENABLE_SENDstructrte_eth_txconftxq_conf=dev_info.default_txconf;txq_conf.offloads=port_conf_default.rxmode.offloads;if(rte_eth_tx_queue_setup(globle_portid,0,512,rte_eth_dev_socket_id(globle_portid),&txq_conf)<0){rte_exit(EXIT_FAILURE,"could not setup TX queue\n");}#endifif(rte_eth_dev_start(globle_portid)<0){rte_exit(EXIT_FAILURE,"could not start\n");}return0;}staticintustack_encode_udp_pkt(uint8_t*msg,uint8_t*data,uint16_ttotal_len){//ether headerstructrte_ether_hdr*eth=(structrte_ether_hdr*)msg;rte_memcpy(eth->d_addr.addr_bytes,global_dmac,RTE_ETHER_ADDR_LEN);rte_memcpy(eth->s_addr.addr_bytes,global_smac,RTE_ETHER_ADDR_LEN);eth->ether_type=htons(RTE_ETHER_TYPE_IPV4);//ip header// struct rte_ipv4_hdr *ip = msg + sizeof(struct rte_ether_hdr);structrte_ipv4_hdr*ip=(structrte_ipv4_hdr*)(eth+1);ip->version_ihl=0x45;ip->type_of_service=0;ip->total_length=htons(total_len-sizeof(structrte_ether_hdr));ip->packet_id=0;ip->fragment_offset=0;ip->time_to_live=64;ip->next_proto_id=IPPROTO_UDP;ip->src_addr=global_sip;ip->dst_addr=global_dip;ip->hdr_checksum=0;ip->hdr_checksum=rte_ipv4_cksum(ip);// udp headerstructrte_udp_hdr*udp=(structrte_udp_hdr*)(ip+1);udp->src_port=global_sport;udp->dst_port=global_dport;uint16_tudplen=total_len-sizeof(structrte_ether_hdr)-sizeof(structrte_ipv4_hdr);udp->dgram_len=htons(udplen);rte_memcpy((uint8_t*)(udp+1),data,udplen);udp->dgram_cksum=0;udp->dgram_cksum=rte_ipv4_udptcp_cksum(ip,udp);return0;}intmain(intargc,charconst*argv[]){if(rte_eal_init(argc,argv)<0){rte_exit(EXIT_FAILURE,"Error");}//参数初始化structrte_mempool*mbuf_pool=rte_pktmbuf_pool_create("mbuf pool",NUM_MBUFS,0,0,RTE_MBUF_DEFAULT_BUF_SIZE,rte_socket_id());if(mbuf_pool==NULL){rte_exit(EXIT_FAILURE,"mbuf\n");}ustack_init_port(mbuf_pool);while(1){structrte_mbuf*mbufs[BURST_SIZE]={0};uint16_tnum_recvd=rte_eth_rx_burst(globle_portid,0,mbufs,BURST_SIZE);if(num_recvd>BURST_SIZE){rte_exit(EXIT_FAILURE,"recv error\n");}inti=0;for(i=0;i<num_recvd;i++){structrte_ether_hdr*ethhdr=rte_pktmbuf_mtod(mbufs[i],structrte_ether_hdr*);if(ethhdr->ether_type!=rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4)){continue;}structrte_ipv4_hdr*iphdr=rte_pktmbuf_mtod_offset(mbufs[i],structrte_ipv4_hdr*,sizeof(structrte_ether_hdr));if(iphdr->next_proto_id==IPPROTO_UDP){structrte_udp_hdr*udphdr=(structrte_udp_hdr*)(iphdr+1);rte_memcpy(global_smac,ethhdr->d_addr.addr_bytes,RTE_ETHER_ADDR_LEN);rte_memcpy(global_dmac,ethhdr->s_addr.addr_bytes,RTE_ETHER_ADDR_LEN);rte_memcpy(&global_sip,&iphdr->dst_addr,sizeof(uint32_t));rte_memcpy(&global_dip,&iphdr->src_addr,sizeof(uint32_t));rte_memcpy(&global_sport,&udphdr->dst_port,sizeof(uint16_t));rte_memcpy(&global_dport,&udphdr->src_port,sizeof(uint16_t));uint16_tlength=ntohs(udphdr->dgram_len);uint16_ttotal_length=length+sizeof(structrte_ipv4_hdr)+sizeof(structrte_ether_hdr);structrte_mbuf*mbuf=rte_pktmbuf_alloc(mbuf_pool);if(!mbuf){rte_exit(EXIT_FAILURE,"alloc\n");}mbuf->pkt_len=total_length;mbuf->data_len=total_length;uint8_t*msg=rte_pktmbuf_mtod(mbuf,uint8_t*);ustack_encode_udp_pkt(msg,(uint8_t*)(udphdr+1),total_length);rte_eth_tx_burst(globle_portid,0,&mbuf,1);printf("udp recv:%s\n",(char*)(udphdr+1));}}}return0;}
零声社区资源链接:https:github.com/0voice