项目实战三:DPDK
一. 初始DPDK:
参考Linux c网络专栏第三章DPDK-CSDN博客
二.用DPDK实现一个发包工具pktgen
发UDP包:
在之前的DPDK实现UDP收发数据包的基础上,注释掉服务端根据用户发来的包进行地址转换的代码。直接服务端单方面向客户端发送数据包,所以需要加上 指定服务端为源,客户端为目的的参数即可。
// 服务端 客户端
// ./pktgen -- -s 192.168.88.21 -d 192.168.88.1 -m 00:0c:29:18:ef:9d -S 8080 -D 9096
int main(int argc, char *argv[]) {int ret = rte_eal_init(argc, argv); //dpdk环境初始化if (ret < 0) { //检测网卡参数信息rte_exit(EXIT_FAILURE, "error with eal init\n");}//去掉dpdk运行自带的参数
#if ENABLE_PKTGENargc -= ret;argv += ret;char opt;while ((opt = getopt_long(argc, argv, "s:d:m:S:D:?", NULL, NULL)) != -1) {printf("???\n");//逐个解析用户输入的选项 如-i -p -m等struct in_addr addr;switch (opt) {case 's':inet_pton(AF_INET, optarg, &addr);global_sip = addr.s_addr;printf("sip : %s\n", inet_ntoa(addr) );break;case 'd':inet_pton(AF_INET, optarg, &addr);global_dip = addr.s_addr;break;case 'm': {int ret;ret = sscanf(optarg, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", &global_dmac[0], &global_dmac[1], &global_dmac[2],&global_dmac[3],&global_dmac[4], &global_dmac[5]);if (ret != 6)printf("MAC error\n");break;}case 'S':global_sport = atoi(optarg);break;case 'D':global_dport = atoi(optarg);break;}}//获取源macstruct rte_ether_addr mac_addr;rte_eth_macaddr_get(portid, &mac_addr);memcpy(global_smac, mac_addr.addr_bytes, RTE_ETHER_ADDR_LEN);#endifstruct rte_mempool *mbuf_pool = rte_pktmbuf_pool_create("mbuff_pool", MEM_BUFF_SIZE, 0, 0, RTE_MBUF_DEFAULT_BUF_SIZE,rte_socket_id());if (mbuf_pool == NULL) {rte_exit(EXIT_FAILURE, "create mbuf pool error\n");}init_my_port(mbuf_pool);while (1) {//接收数据struct rte_mbuf *mbufs[BURST_SIZE] = {0};uint16_t recv_cnt = rte_eth_rx_burst(portid, 0, mbufs, BURST_SIZE);if (recv_cnt > BURST_SIZE) {rte_exit(EXIT_FAILURE, "recv error\n");}//解析数据包int i = 0;for (i = 0; i < recv_cnt; i++) {//处理以太网头(物理层mac地址)struct rte_ether_hdr *eth_header = rte_pktmbuf_mtod(mbufs[i], struct rte_ether_hdr *);if (eth_header->ether_type != rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4)) {continue; //转大小端}//处理ip头(网络层ip地址)struct rte_ipv4_hdr *ip_header = rte_pktmbuf_mtod_offset(mbufs[i], struct rte_ipv4_hdr *,sizeof(struct rte_ether_hdr));if (ip_header->next_proto_id == IPPROTO_UDP) {//处理udp头(传输层端口号)struct rte_udp_hdr *udp_header = (struct rte_udp_hdr *)(ip_header + 1);printf("udp : %s\n", (char *)(udp_header + 1));
#if 0//获取发送包的 源与目的rte_memcpy(global_smac, eth_header->d_addr.addr_bytes, RTE_ETHER_ADDR_LEN);rte_memcpy(global_dmac, eth_header->s_addr.addr_bytes, RTE_ETHER_ADDR_LEN);rte_memcpy(&global_sip, &ip_header->dst_addr, sizeof(uint32_t));rte_memcpy(&global_dip, &ip_header->src_addr, sizeof(uint32_t));rte_memcpy(&global_sport, &udp_header->dst_port, sizeof(uint16_t));rte_memcpy(&global_dport, &udp_header->src_port, sizeof(uint16_t));
#endifstruct in_addr addr;addr.s_addr = ip_header->src_addr;printf("udp: sip %s:%d --> ", inet_ntoa(addr), ntohs(udp_header->src_port));addr.s_addr = ip_header->dst_addr;printf("dip %s:%d \n", inet_ntoa(addr), ntohs(udp_header->dst_port));//准备发送包uint16_t length = ntohs(udp_header->dgram_len);//udp包长度uint16_t total_len = length + sizeof(struct rte_ipv4_hdr) + sizeof(struct rte_ether_hdr);struct rte_mbuf *mbuf = rte_pktmbuf_alloc(mbuf_pool);if (!mbuf) {rte_exit(EXIT_FAILURE, "Error rte_pktmbuf_alloc\n");}mbuf->pkt_len = total_len;mbuf->data_len = total_len;uint8_t *msg = rte_pktmbuf_mtod(mbuf, uint8_t *);//msg指向从内存池申请的用来存发送包的mbufencode_udp_package(msg, (uint8_t *)(udp_header + 1), total_len);rte_eth_tx_burst(portid, 0, &mbuf, 1);} }}return 0;}
再精简,删除接收数据并处理部分的代码,可以直接得到一个发送udp数据包的代码
#include <stdio.h>#include <rte_eal.h>
#include <rte_ethdev.h>
#include <arpa/inet.h>
#include <getopt.h>
#include <rte_mempool.h> // 内存池核心头文件
#include <rte_mbuf.h> // 数据包缓冲区头文件int portid = 0;
#define MEM_BUFF_SIZE 4096
#define BURST_SIZE 128
#define ENABLE_SEND 1
uint8_t global_smac[RTE_ETHER_ADDR_LEN];
uint8_t global_dmac[RTE_ETHER_ADDR_LEN];uint32_t global_sip;
uint32_t global_dip;uint16_t global_sport;
uint16_t global_dport;
static int init_my_port(struct rte_mempool *mbuf_pool);
int encode_udp_package(uint8_t *msg, uint8_t *data, uint16_t total_len);
int encode_tcp_package(uint8_t *msg, uint16_t total_len);
static const struct rte_eth_conf port_conf_default = { //网卡配置信息.rxmode = {.max_rx_pkt_len = RTE_ETHER_MAX_LEN}
};static int init_my_port(struct rte_mempool *mbuf_pool) {uint16_t port_cnt = rte_eth_dev_count_avail();if (port_cnt == 0) {rte_exit(EXIT_FAILURE, "No available eth bind\n");}struct rte_eth_dev_info port_info;rte_eth_dev_info_get(portid, &port_info);const int recv_queue = 1;const int send_queue = 1;rte_eth_dev_configure(portid, recv_queue, send_queue, &port_conf_default);if (rte_eth_rx_queue_setup(portid, 0, 128, rte_eth_dev_socket_id(portid), NULL, mbuf_pool) < 0) {//设置网卡接收队列,为portid号网卡的0号队列 配置一个128长度的接收队列,并且绑定内存池用于存放收到的数据包rte_exit(EXIT_FAILURE, "Setup RX queue error\n");}struct rte_eth_txconf tx_conf = port_info.default_txconf;tx_conf.offloads = port_conf_default.rxmode.offloads;if (rte_eth_tx_queue_setup(portid, 0, 512, rte_eth_dev_socket_id(portid), &tx_conf) < 0) {//设置网卡发送队列,为portid号网卡的0号队列 配置一个512长度的发送队列rte_exit(EXIT_FAILURE, "Setup TX queue error\n");}if (rte_eth_dev_start(portid) < 0) {//让网卡进入工作状态(相当于通电)rte_exit(EXIT_FAILURE, "start port error\n");}return 0;
}int encode_udp_package(uint8_t *msg, uint8_t *data, uint16_t total_len) {//msg表示装包的内存,data是数据,total_len是数据包总长度//以太网头(mac头)struct rte_ether_hdr *eth_header = (struct rte_ether_hdr *)msg; //在msg所指的内存空间,开辟出一个固定大小的mac头rte_memcpy(eth_header->d_addr.addr_bytes, global_dmac, RTE_ETHER_ADDR_LEN);rte_memcpy(eth_header->s_addr.addr_bytes, global_smac, RTE_ETHER_ADDR_LEN);eth_header->ether_type = htons(RTE_ETHER_TYPE_IPV4);printf("mac :%x:%x:%x:%x:%x:%x -->", global_dmac[0], global_dmac[1], global_dmac[2], global_dmac[3],global_dmac[4], global_dmac[5]);//ip头struct rte_ipv4_hdr *ip_header = (struct rte_ipv4_hdr *)(eth_header + 1);//msg + sizeof(struct rte_ether_hdr)ip_header->version_ihl = 0x45;ip_header->type_of_service = 0;ip_header->total_length = htons(total_len - sizeof(struct rte_ether_hdr));ip_header->packet_id = 0;ip_header->fragment_offset = 0;ip_header->time_to_live = 64;//TTL 最多经过路由数量ip_header->next_proto_id = IPPROTO_UDP;ip_header->src_addr = global_sip;ip_header->dst_addr = global_dip;ip_header->hdr_checksum = 0;ip_header->hdr_checksum = rte_ipv4_cksum(ip_header);//计算检验和struct in_addr addr;addr.s_addr = global_sip;printf("udp --> src : %s:%d", inet_ntoa(addr), global_sport);addr.s_addr = global_dip;printf(" ; src : %s:%d\n", inet_ntoa(addr), global_dport);//udp头·struct rte_udp_hdr *udp_header = (struct rte_udp_hdr *)(ip_header + 1);udp_header->src_port = htons(global_sport);udp_header->dst_port = htons(global_dport);uint16_t udplen = total_len - sizeof(struct rte_ether_hdr) - sizeof(struct rte_ipv4_hdr);udp_header->dgram_len = htons(udplen);//数据部分rte_memcpy((uint8_t *)(udp_header + 1), data, udplen);udp_header->dgram_cksum = 0;udp_header->dgram_cksum = rte_ipv4_udptcp_cksum(ip_header, udp_header);return 0;
}// 服务端 客户端
// ./pktgen -- -s 192.168.88.21 -d 192.168.88.1 -m 00:0c:29:18:ef:9d -S 8080 -D 9096
int main(int argc, char *argv[]) {int ret = rte_eal_init(argc, argv); //dpdk环境初始化if (ret < 0) { //检测网卡参数信息rte_exit(EXIT_FAILURE, "error with eal init\n");}//去掉dpdk运行自带的参数argc -= ret;argv += ret;char opt;while ((opt = getopt_long(argc, argv, "s:d:m:S:D:?", NULL, NULL)) != -1) {//逐个解析用户输入的选项 如-i -p -m等struct in_addr addr;switch (opt) {case 's':inet_pton(AF_INET, optarg, &addr);global_sip = addr.s_addr;printf("sip : %s\n", inet_ntoa(addr) );break;case 'd':inet_pton(AF_INET, optarg, &addr);global_dip = addr.s_addr;break;case 'm': {int ret;ret = sscanf(optarg, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", &global_dmac[0], &global_dmac[1], &global_dmac[2],&global_dmac[3],&global_dmac[4], &global_dmac[5]);if (ret != 6)printf("MAC error\n");break;}case 'S':global_sport = atoi(optarg);break;case 'D':global_dport = atoi(optarg);break;}}//获取源macstruct rte_ether_addr mac_addr;rte_eth_macaddr_get(portid, &mac_addr);memcpy(global_smac, mac_addr.addr_bytes, RTE_ETHER_ADDR_LEN);//初始化内存池struct rte_mempool *mbuf_pool = rte_pktmbuf_pool_create("mbuff_pool", MEM_BUFF_SIZE, 0, 0, RTE_MBUF_DEFAULT_BUF_SIZE,rte_socket_id());if (mbuf_pool == NULL) {rte_exit(EXIT_FAILURE, "create mbuf pool error\n");}init_my_port(mbuf_pool);//从内存池分配一块空间 并且用msg指向struct rte_mbuf *mbuf = rte_pktmbuf_alloc(mbuf_pool);uint8_t *msg = rte_pktmbuf_mtod(mbuf, uint8_t *);//创建发送数据 计算长度 方便打包uint8_t *data = malloc(strlen("aaa") + 1);strcpy((char *)data, "aaa");int total_length = sizeof(struct rte_ether_hdr) + sizeof(struct rte_ipv4_hdr) + strlen(data) + sizeof(struct rte_udp_hdr);mbuf->pkt_len = total_length; //一开始没把这两个赋值导致下面的length of pkg:0mbuf->data_len = total_length;//打包发送encode_udp_package(msg, data, total_length);int sent = rte_eth_tx_burst(portid, 0, &mbuf, 1);printf("length of pkg:%d\n", mbuf->pkt_len);printf("Sent %d packets\n", sent);free(data);return 0;
}
运行结果:
三.用DPDK实现DNS解析
在DPDK实现UDP能相互发送消息的前提下,引入一个DNS.c,具体做法是rx_burst后解析到是UDP协议且端口为53,通过udp_header + 1使buffer指向数据部分,以及udp长度-udp头长度获取数据部分长度buffer_length,通过decode_msg把buffer数据部分解码赋值到msg对象,然后再对msg进行resolve_msg,最后用一个新指针p指向buffer起始位置,再encode_msg后通过p指针的偏移量得到编码后的 DNS 消息的实际长度,加上三个首部的长度,即可得到包的总长度,进行tx_burst发送。
核心代码:
if (iphdr->next_proto_id == IPPROTO_UDP) {struct rte_udp_hdr *udphdr = (struct rte_udp_hdr *)(iphdr + 1);uint16_t length = ntohs(udphdr->dgram_len);*((char *)udphdr + length) = '\0';printf("msg: %s\n", (char *)(udphdr + 1));struct Message msg;memset(&msg, 0, sizeof(struct Message));if (DNS_PORT == rte_cpu_to_be_16(udphdr->dst_port)) {printf("dns protocol\n");uint8_t *buffer = (uint8_t *)(udphdr + 1); // 指向UDP头后面的数据int nbytes = length - sizeof(struct rte_udp_hdr); //UDP头后面的数据长度if (decode_msg(&msg, buffer, nbytes) != 0) {continue;}resolve_query(&msg);uint8_t *p = buffer;if (encode_msg(&msg, &p) != 0) {continue;}int buflen = p - buffer;mbuf[i]->pkt_len = buflen + sizeof(struct rte_udp_hdr) + sizeof(struct rte_ipv4_hdr) + sizeof(struct rte_ether_hdr);mbuf[i]->data_len = buflen + sizeof(struct rte_udp_hdr) + sizeof(struct rte_ipv4_hdr) + sizeof(struct rte_ether_hdr);}pkt_echo(ehdr, iphdr, udphdr);rte_eth_tx_burst(dpdk_port_id, 0, mbuf + i, 1);free_questions(msg.questions);free_resource_records(msg.answers);free_resource_records(msg.authorities);free_resource_records(msg.additionals);
}
代码参考:
https://github.com/0voice
代码存储:
He Jiangtao / 9.3_DPDK · GitLab