基于DPDK实现UDP收发理解网络协议

TCP/IP协议栈的具体封装

如果需要解封装出UDP的数据,就需要先解析出以太网头部(ethhdr),IP头部(iphdr),UDP头部(udphdr)。

因为使用的是UDP协议,所以发送数据时需要自行封装UDP头部,IP头部,以太网头部,将其各个协议头部与数据进行拼接然后发送。

以太网数据报

以太网头部一共6+6+2=14个字节

封装以太网头部代码:

复制代码
	//1 ether
	struct rte_ether_hdr *eth = (struct rte_ether_hdr*)msg;
	rte_memcpy(eth->s_addr.addr_bytes, gSrcMac, RTE_ETHER_ADDR_LEN);// 源Mac地址
	rte_memcpy(eth->d_addr.addr_bytes, gDstMac, RTE_ETHER_ADDR_LEN);// 目的Mac地址
	eth->ether_type = htons(RTE_ETHER_TYPE_IPV4);// 以太网类型

IP 数据报

IP头部一共20个字节

封装IP头部代码

复制代码
	//1 iphdr
	struct rte_ipv4_hdr *iphdr = (struct rte_ipv4_hdr *)(eth+1); 
	/*
	version_ihl字段:高4位是版本号(IPv4=4),低4位是首部长度(以4字节为单位)
	0x5= 5个32位字 = 20字节(标准IPv4头部长度)
	0x4= 版本4 ; 	所以 0x45表示 IPv4 + 20字节首部
	*/
	iphdr->version_ihl = 0x45; 
	iphdr->type_of_service = 0x0; // 服务类型TOS字段,默认服务
	iphdr->total_length = htons(total_length - sizeof(struct rte_ether_hdr)); // IP包总长度
	iphdr->packet_id = 0;	// 包标识符,用于分片重组
	iphdr->fragment_offset = 0;  // 分片偏移,0表示未分片 // 标志(3位) + 分片偏移(13位) 
	iphdr->time_to_live = 0; // 生存时间TTL
	iphdr->next_proto_id = IPPROTO_UDP; // 选择UDP协议
	iphdr->src_addr = gSrcIp;
	iphdr->dst_addr = gDstIp;

	iphdr->hdr_checksum = 0; // 校验和,必须先赋零再计算
	iphdr->hdr_checksum = rte_ipv4_cksum(iphdr);

UDP数据报

UDP头部一共8个字节

UDP头部封装代码

复制代码
	//1 udphdr

	struct rte_udp_hdr *udphdr =  (struct rte_udp_hdr *)(iphdr+1);
	udphdr->src_port = gSrcPort; // 源端口
	udphdr->dst_port = gDstPort; // 目标端口
	
	uint16_t udplen = total_length - sizeof(struct rte_ether_hdr) - sizeof(struct rte_ipv4_hdr); // UDP数据部分长度
	udphdr->dgram_len = htons(udplen); // 数据长度 转为网络字节序
	rte_memcpy((uint8_t*)(udphdr+1), data, udplen-sizeof(struct rte_udp_hdr));// 应用层数据拷贝到 UDP 数据部分

	udphdr->dgram_cksum = 0; // 校验和
	udphdr->dgram_cksum = rte_ipv4_udptcp_cksum(iphdr, udphdr);

大端序与小端序

htons,htonl,ntohs,ntohl

大端序(Big-endian):高位字节存储在低内存地址

小端序(Little-endian):低位字节存储在低内存地址

网络协议、网络通信统一使用大端序

大多数计算机和服务器使用小端序

CPU在处理数据时先处理低内存地址,

网络协议都是从从高位开始封装的协议帧,所以使用大端序可以优先处理高位数据

而计算机需要大量的数据运算,数据运算需要从低位开始,所以计算机通常使用小端序

复制代码
uint16_t htons(uint16_t hostshort);
Host TO Network Short:将16位主机字节序转换为网络字节序

uint32_t htonl(uint32_t hostlong);
Host TO Network Long:将32位主机字节序转换为网络字节序

uint16_t ntohs(uint16_t netshort);
Network TO Host Short:将16位网络字节序转换为主机字节序

uint32_t ntohl(uint32_t netlong);
Network TO Host Long:将32位网络字节序转换为主机字节序

常见缩写

有助于阅读代码

  1. g:开头表示global全局

  2. RTE: Run-Time Environment运行时环境,

    是DPDK的核心命名前缀

  3. ETHER:Ethernet以太网

  4. Src:source源

  5. Dst:destination目的

  6. Mac:Mac地址

  7. Ip:Ip地址

  8. Port:端口

  9. eth: ethernet以太网

  10. conf: configuration配置

  11. pkt: packet数据包

  12. argc: Argument Count 参数数量

  13. argv: Argument Vector 参数内容

  14. eal 是:Environment Abstraction Layer 环境抽象层,是 DPDK 框架的核心基础组件。

  15. dev:device设备

  16. nb:number数量

  17. sys:system系统

  18. mempool:memory pool 内存池

  19. mbuf:memory buffer内存缓冲区

  20. desc:Descriptor描述符

  21. txq: Tx Queue 发送队列

  22. hdr:header头部

  23. mtod:mbuf to data 从mbuf结构体获取数据区指针

  24. IP:Internet Protocol 互联网协议

  25. IPPROTO:Internet Protocol Protocol互联网协议下的子协议,TCP、UDP、ICMP等

  26. dgram:datagram数据报

  27. ustack:user stack 用户态协议栈

  28. alloc:allocate分配,申请

完整代码

复制代码
#include <stdio.h>
#include <unistd.h>
#include <rte_eal.h>
#include <rte_ethdev.h>
#include <rte_mbuf.h>
#include <arpa/inet.h>

// DPDK mbuf 内存池的容量
#define NUM_MBUFS		2048
// DPDK 网卡单次 收 / 发 的最大数据包数量
#define BURST_SIZE		128

#define ENABLE_SEND		1

int gDpdkPortId = 0;

#if ENABLE_SEND
// g开头表示global全局
// RTE: 运行时环境,是DPDK的核心命名前缀
// ETHER:Ethernet以太网
// Src:source源 自身为源
// Dst:destination目的 对端为目的
// Mac:Mac地址   Ip:Ip地址   Port:端口
uint8_t gSrcMac[RTE_ETHER_ADDR_LEN];
uint8_t gDstMac[RTE_ETHER_ADDR_LEN];
uint32_t gSrcIp;
uint32_t gDstIp;
uint16_t gSrcPort;
uint16_t gDstPort;
#endif
/*
htons,htonl,ntohs,ntohl
大端序(Big-endian):高位字节存储在低内存地址
小端序(Little-endian):低位字节存储在低内存地址

网络协议、网络通信统一使用大端序
大多数计算机和服务器使用小端序

CPU在处理数据时先处理低内存地址,
网络协议都是从从高位开始封装的协议帧,所以使用大端序可以优先处理高位数据
而计算机需要大量的数据运算,数据运算需要从低位开始,所以计算机通常使用小端序

uint16_t htons(uint16_t hostshort);
Host TO Network Short:将16位主机字节序转换为网络字节序

uint32_t htonl(uint32_t hostlong);
Host TO Network Long:将32位主机字节序转换为网络字节序

uint16_t ntohs(uint16_t netshort);
Network TO Host Short:将16位网络字节序转换为主机字节序

uint32_t ntohl(uint32_t netlong);
Network TO Host Long:将32位网络字节序转换为主机字节序
*/
/*
	uint8_t *msg​ - 数据包缓冲区指针
	char *data​ - 应用层数据指针
	uint16_t total_length​ - 数据包总长度
*/
// 构建UDP数据包,在msg中构造一个完整的以太网帧
static int ustack_encode_udp_pkt(uint8_t *msg,  char *data, uint16_t total_length) {

	//1 ether
	struct rte_ether_hdr *eth = (struct rte_ether_hdr*)msg;
	rte_memcpy(eth->s_addr.addr_bytes, gSrcMac, RTE_ETHER_ADDR_LEN);// 源Mac地址
	rte_memcpy(eth->d_addr.addr_bytes, gDstMac, RTE_ETHER_ADDR_LEN);// 目的Mac地址
	eth->ether_type = htons(RTE_ETHER_TYPE_IPV4);// 以太网类型

	//1 iphdr
	struct rte_ipv4_hdr *iphdr = (struct rte_ipv4_hdr *)(eth+1); 
	/*
	version_ihl字段:高4位是版本号(IPv4=4),低4位是首部长度(以4字节为单位)
	0x5= 5个32位字 = 20字节(标准IPv4头部长度)
	0x4= 版本4 ; 	所以 0x45表示 IPv4 + 20字节首部
	*/
	iphdr->version_ihl = 0x45; 
	iphdr->type_of_service = 0x0; // 服务类型TOS字段,默认服务
	iphdr->total_length = htons(total_length - sizeof(struct rte_ether_hdr)); // IP包总长度
	iphdr->packet_id = 0;	// 包标识符,用于分片重组
	iphdr->fragment_offset = 0;  // 分片偏移,0表示未分片 // 标志(3位) + 分片偏移(13位) 
	iphdr->time_to_live = 0; // 生存时间TTL
	iphdr->next_proto_id = IPPROTO_UDP; // 选择UDP协议
	iphdr->src_addr = gSrcIp;
	iphdr->dst_addr = gDstIp;

	iphdr->hdr_checksum = 0; // 校验和,必须先赋零再计算
	iphdr->hdr_checksum = rte_ipv4_cksum(iphdr);

	//1 udphdr

	struct rte_udp_hdr *udphdr =  (struct rte_udp_hdr *)(iphdr+1);
	udphdr->src_port = gSrcPort; // 源端口
	udphdr->dst_port = gDstPort; // 目标端口
	
	uint16_t udplen = total_length - 
	sizeof(struct rte_ether_hdr) - sizeof(struct rte_ipv4_hdr); // UDP数据部分长度
	udphdr->dgram_len = htons(udplen); // 数据长度 转为网络字节序
	rte_memcpy((uint8_t*)(udphdr+1), data, udplen-sizeof(struct rte_udp_hdr));// 应用层数据拷贝到 UDP 数据部分

	udphdr->dgram_cksum = 0; // 校验和
	udphdr->dgram_cksum = rte_ipv4_udptcp_cksum(iphdr, udphdr);

	return total_length;
}

// 
static struct rte_mbuf *ustack_send(struct rte_mempool *mbuf_pool, char *data, uint16_t length) {

	const unsigned total_length = length + 
	sizeof(struct rte_ether_hdr) + 
	sizeof(struct rte_ipv4_hdr) + sizeof(struct rte_udp_hdr);
	
	// 从内存池中分配一个rte_mbuf结构体,mbuf_pool是内存池指针
	struct rte_mbuf *mbuf = rte_pktmbuf_alloc(mbuf_pool);
	if (!mbuf) {
		rte_exit(EXIT_FAILURE, "Error with EAL init\n");
	}
/*
	uint16_t data_len;  // 当前 mbuf 中有效数据包数据的实际长度
						// 若数据包被分段(如跨多个 mbuf 链表),仅表示当前分段的数据长度
	uint16_t pkt_len;   // 整个数据包的总长度(包含所有分段 mbuf)
						// 仅在链表头 mbuf(第一个 mbuf)中有效,分段 mbuf 该字段无意义
*/
	mbuf->pkt_len = total_length;
	mbuf->data_len = total_length;
	// 获取rte_mbuf结构体mbuf中数据区域的指针
	// pktdata就是整个以太网数据帧的头指针
	uint8_t *pktdata = rte_pktmbuf_mtod(mbuf, uint8_t*);

	ustack_encode_udp_pkt(pktdata, data, total_length);
	
	return mbuf;
}
// eth: ethernet以太网
// conf: configuration配置
// pkt: packet数据包
// 默认网卡配置,只设置了最大接收包长度
static const struct rte_eth_conf port_conf_default = {
	.rxmode = {.max_rx_pkt_len = RTE_ETHER_MAX_LEN }
};

int main(int argc, char *argv[]) {

	if (rte_eal_init(argc, argv) < 0) {
		rte_exit(EXIT_FAILURE, "Error with EAL init\n");
	}

	// 可用端口数量
	uint16_t nb_sys_ports = rte_eth_dev_count_avail();
	if (nb_sys_ports ==  0) {
		rte_exit(EXIT_FAILURE, "No Support eth found\n");
	}
	printf("nb_sys_ports: %d\n", nb_sys_ports);

	// 创建mbuf内存池
	struct rte_mempool *mbuf_pool = 
	rte_pktmbuf_pool_create("mbufpool", NUM_MBUFS, 0, 0, 
		RTE_MBUF_DEFAULT_BUF_SIZE, rte_socket_id());
	if (!mbuf_pool) {
		rte_exit(EXIT_FAILURE, "Could not create mbuf pool\n");
	}

	// 获取
	struct rte_eth_dev_info dev_info;
	rte_eth_dev_info_get(gDpdkPortId, &dev_info);

	const int num_rx_queues = 1;
	const int num_tx_queues = 1;
	struct rte_eth_conf port_conf = port_conf_default;
	// 配置
	if (rte_eth_dev_configure(gDpdkPortId, num_rx_queues, num_tx_queues, &port_conf) < 0) {
		rte_exit(EXIT_FAILURE, "Could not configure\n");
	}
	// 设置接受队列
	if (rte_eth_rx_queue_setup(gDpdkPortId, 0, 128, 
		rte_eth_dev_socket_id(gDpdkPortId), 
		NULL, mbuf_pool) < 0) {
		rte_exit(EXIT_FAILURE, "Could not setup RX queue\n");
	}

#if ENABLE_SEND
	struct rte_eth_txconf txq_conf = dev_info.default_txconf;
	txq_conf.offloads = port_conf.rxmode.offloads;
	// 设置发送队列
	if (rte_eth_tx_queue_setup(gDpdkPortId, 0, 512, 
		rte_eth_dev_socket_id(gDpdkPortId), &txq_conf) < 0) {
		rte_exit(EXIT_FAILURE, "Could not setup TX queue\n");
	}

#endif
	// 启动设备
	if (rte_eth_dev_start(gDpdkPortId) < 0) {
		rte_exit(EXIT_FAILURE, "Could not start\n");
	}

	printf("dev start success\n");
	while (1) {

		struct rte_mbuf *mbufs[BURST_SIZE];
		// 接收数据
		unsigned nb_recvd = rte_eth_rx_burst(gDpdkPortId, 0,
			 mbufs, BURST_SIZE);
		if (nb_recvd > BURST_SIZE) {
			rte_exit(EXIT_FAILURE, "Error with rte_eth_rx_burst\n");
		}
/*
+------------+---------------+-------------------+--------------+
|   ethhdr   |     iphdr     |   udphdr/tcphdr   |   payload    |  
+------------+---------------+-------------------+--------------+
*/

		unsigned i = 0;
		for (i = 0;i < nb_recvd;i ++) {
			// 解析以太网头,ethhdr
			struct rte_ether_hdr *ehdr =  rte_pktmbuf_mtod(
				mbufs[i], struct rte_ether_hdr *);
			if (ehdr->ether_type != rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4)) {
				continue;
			}
			// 解析ip头,iphdr
			struct rte_ipv4_hdr *iphdr = rte_pktmbuf_mtod_offset(
				mbufs[i], struct rte_ipv4_hdr *, sizeof(struct rte_ether_hdr));
			if (iphdr->next_proto_id == IPPROTO_UDP) {
				
				struct rte_udp_hdr *udphdr = (struct rte_udp_hdr *)(iphdr+1);

#if ENABLE_SEND
				// 将发送端与接收端信息互换,为了后续的发送
				rte_memcpy(gSrcMac, ehdr->d_addr.addr_bytes, RTE_ETHER_ADDR_LEN);
				rte_memcpy(gDstMac, ehdr->s_addr.addr_bytes, RTE_ETHER_ADDR_LEN);

				rte_memcpy(&gSrcIp, &iphdr->dst_addr, sizeof(uint32_t));
				rte_memcpy(&gDstIp, &iphdr->src_addr, sizeof(uint32_t));

				rte_memcpy(&gSrcPort, &udphdr->dst_port, sizeof(uint16_t));
				rte_memcpy(&gDstPort, &udphdr->src_port, sizeof(uint16_t));

#endif			
				// length接收数据的长度
				uint16_t length = ntohs(udphdr->dgram_len) - sizeof(struct rte_udp_hdr);
				printf("length: %d, content: %s\n", length, (char *)(udphdr+1));
#if ENABLE_SEND
				// 构造一个新的 mbuf 数据包
				struct rte_mbuf *txbuf = ustack_send(mbuf_pool, (char *)(udphdr+1), length);
				// 发送数据包
				rte_eth_tx_burst(gDpdkPortId, 0, &txbuf, 1);
				//printf("ustack_send\n");
				rte_pktmbuf_free(txbuf);
#endif
			}
			//rte_eth_tx_burst(gDpdkPortId, 0, &mbufs[i], 1);
		}
	}
	return 0;
}

e_udp_hdr);

printf("length: %d, content: %s\n", length, (char *)(udphdr+1));

#if ENABLE_SEND

// 构造一个新的 mbuf 数据包

struct rte_mbuf *txbuf = ustack_send(mbuf_pool, (char *)(udphdr+1), length);

// 发送数据包

rte_eth_tx_burst(gDpdkPortId, 0, &txbuf, 1);

//printf("ustack_send\n");

rte_pktmbuf_free(txbuf);

#endif

}

//rte_eth_tx_burst(gDpdkPortId, 0, &mbufs[i], 1);

}

}

return 0;

}

复制代码
相关推荐
阿巴~阿巴~31 分钟前
NAT技术:互联网连接的隐形桥梁
服务器·网络·网络协议·架构·智能路由器·nat·正反向代理
DevOps-IT34 分钟前
HTTP状态码(常见 HTTP Status Code 查询)
运维·服务器·网络·网络协议·http
普马萨特37 分钟前
移动网络信号指标与单位整理(2G/3G/4G/5G Android vs IoT)
android·网络·物联网
阿巴~阿巴~41 分钟前
打通局域网“最后一公里”:ARP协议原理、流程与安全解析
服务器·网络·网络协议·tcp/ip·tcp·ipv4·arp
阿巴~阿巴~1 小时前
从不可靠到100%可靠:TCP与网络设计的工程智慧全景解析
运维·服务器·网络·网络协议·tcp/ip·智能路由器
持续升级打怪中1 小时前
WebSocket:从“写信”到“打电话”的实时通信革命
网络·websocket·网络协议
learning-striving1 小时前
eNSP中OSPF协议多区域的配置实验
网络·智能路由器·ensp·通信
三两肉1 小时前
深入理解 HTTPS RSA 握手:从原理到流程的完整解析
网络协议·http·https·rsa·tls四次握手
食咗未1 小时前
Linux iptables工具的使用
linux·运维·服务器·驱动开发·网络协议·信息与通信
阿巴~阿巴~1 小时前
从IP到MAC,从内网到公网:解密局域网通信与互联网连接的完整路径
服务器·网络·网络协议·架构·智能路由器·tcp·arp