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位网络字节序转换为主机字节序
常见缩写
有助于阅读代码
-
g:开头表示global全局
-
RTE: Run-Time Environment运行时环境,
是DPDK的核心命名前缀
-
ETHER:Ethernet以太网
-
Src:source源
-
Dst:destination目的
-
Mac:Mac地址
-
Ip:Ip地址
-
Port:端口
-
eth: ethernet以太网
-
conf: configuration配置
-
pkt: packet数据包
-
argc: Argument Count 参数数量
-
argv: Argument Vector 参数内容
-
eal 是:Environment Abstraction Layer 环境抽象层,是 DPDK 框架的核心基础组件。
-
dev:device设备
-
nb:number数量
-
sys:system系统
-
mempool:memory pool 内存池
-
mbuf:memory buffer内存缓冲区
-
desc:Descriptor描述符
-
txq: Tx Queue 发送队列
-
hdr:header头部
-
mtod:mbuf to data 从mbuf结构体获取数据区指针
-
IP:Internet Protocol 互联网协议
-
IPPROTO:Internet Protocol Protocol互联网协议下的子协议,TCP、UDP、ICMP等
-
dgram:datagram数据报
-
ustack:user stack 用户态协议栈
-
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;
}