一、协议结构
以太网协议
在DPDK中,采用struct rte_ether_hdr定义以太网协议头
以下是DPDK中以太网头的结构体
struct rte_ether_hdr {
struct rte_ether_addr dst_addr; //目的地址
struct rte_ether_addr src_addr; //源地址
rte_be16_t ether_type; //协议类型
} __rte_aligned(2);
IP协议头
DPDK中IP协议头的结构体
**
* IPv4 Header
*/
struct rte_ipv4_hdr {
__extension__
union {
uint8_t version_ihl; //协议长度
struct {
#if RTE_BYTE_ORDER == RTE_LITTLE_ENDIAN
uint8_t ihl:4; //头长度
uint8_t version:4; //协议版本
#elif RTE_BYTE_ORDER == RTE_BIG_ENDIAN
uint8_t version:4; //版本
uint8_t ihl:4; //头长度
#endif
};
};
uint8_t type_of_service; //服务类型
rte_be16_t total_length; //包长度
rte_be16_t packet_id; //包id
rte_be16_t fragment_offset; //
uint8_t time_to_live; /**< time to live */
uint8_t next_proto_id; //协议IP
rte_be16_t hdr_checksum; //校验头
rte_be32_t src_addr; //源地址
rte_be32_t dst_addr; //目的地址
} __rte_packed;
TCP/IP协议头
DPDK中IP协议头的结构体
struct rte_tcp_hdr {
rte_be16_t src_port; //tcp源端口
rte_be16_t dst_port; //tcp目的端口
rte_be32_t sent_seq; //发送数据队列
rte_be32_t recv_ack; //接收数据队列
uint8_t data_off; //数据偏移指示了TCP段中数据的起始位置
uint8_t tcp_flags; //tcp标志
rte_be16_t rx_win; //流量控制窗口,用于流量控制和拥塞控制
rte_be16_t cksum; //tcp校验码
rte_be16_t tcp_urp; //Urgent Pointer(紧急指针)是用于标识紧急数据的位置
} __rte_packed;
二、封装TCP协议的步骤
- 生成要传输的数据Data
- 在传输层为要传输的数据Data添加TCP头部信息,形成TCP段
- 计算TCP段校验和,用于确保数据在传输过程中未被损害
- 讲TCP段传输给网络层,在网络层添加IP头部,形成IP数据报
- 传输数据
三、组包代码示例
#include <rte_ethdev.h>
static void build_tcpPackget(struct rte_mempool *mbuf_pool, uint16_t port)
{
struct rte_mbuf *mbuf = rte_pktmbuf_alloc(mbuf_pool);
if (mbuf == NULL) {
printf("Failed to allocate mbuf\n");
return;
}
struct rte_ether_hdr *eth_hdr = rte_pktmbuf_mtod(mbuf, struct rte_ether_hdr *);
//
eth_hdr->ether_type = rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4);
struct rte_ipv4_hdr *ip_hdr = (struct rte_ipv4_hdr *)(eth_hdr + 1);
ip_hdr->version_ihl = RTE_IPV4_VHL_DEF;
ip_hdr->type_of_service = 0;
ip_hdr->total_length = rte_cpu_to_be_16(sizeof(struct rte_ipv4_hdr) + sizeof(struct rte_tcp_hdr));
ip_hdr->packet_id = rte_cpu_to_be_16(1);
ip_hdr->fragment_offset = 0;
ip_hdr->time_to_live = 64;
ip_hdr->next_proto_id = IPPROTO_TCP;
ip_hdr->hdr_checksum = 0;
ip_hdr->src_addr = rte_cpu_to_be_32(RTE_IPV4(192, 168, 0, 1));
ip_hdr->dst_addr = rte_cpu_to_be_32(RTE_IPV4(192, 168, 0, 2));
ip_hdr->hdr_checksum = rte_ipv4_cksum(ip_hdr);
struct rte_tcp_hdr *tcp_hdr = (struct rte_tcp_hdr *)(ip_hdr + 1);
tcp_hdr->src_port = rte_cpu_to_be_16(1234);
tcp_hdr->dst_port = rte_cpu_to_be_16(5678);
tcp_hdr->sent_seq = rte_cpu_to_be_32(0);
tcp_hdr->recv_ack = rte_cpu_to_be_32(0);
tcp_hdr->data_off = (sizeof(struct rte_tcp_hdr) / 4) << 4;
tcp_hdr->tcp_flags = RTE_TCP_SYN_FLAG;
tcp_hdr->rx_win = rte_cpu_to_be_16(8192);
tcp_hdr->cksum = 0;
tcp_hdr->tcp_urp = 0;
char *payload = (char *)(tcp_hdr + 1);
strcpy(payload, "Hello, DPDK!");
// Calculate TCP checksum
tcp_hdr->cksum = rte_ipv4_udptcp_cksum(ip_hdr, tcp_hdr);
// Set the packet length
mbuf->data_len = 14 + sizeof(struct rte_ipv4_hdr) + sizeof(struct rte_tcp_hdr) + strlen(payload);
mbuf->pkt_len = mbuf->data_len;
}
四、头部协议结构
IPv4数据包的标准头部结构如下(20字节):
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|Version| IHL |Type of Service| Total Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Identification |Flags| Fragment Offset |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Time to Live | Protocol | Header Checksum |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Source Address |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Destination Address |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Options | Padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
字段说明
- Version(4位):IP协议版本号,IPv4中为4。
- IHL(4位):IP头部长度,表示IP头部的32位字的数量(最小值为5)。
- Type of Service(8位):服务类型,用于指定分组的优先级。
- Total Length(16位):整个IP数据包的长度(头部和数据
- Identification(16位):用于标识数据包的唯一值。
- Flags(3位):标志位,控制分片。
- Fragment Offset(13位):用于标识分片数据在原始数据中的位置
- Time to Live(8位):数据包的生存时间,防止数据包在网络中无限循环。
- Protocol(8位):表示使用的传输协议,如TCP(6)或UDP(17)
- Header Checksum(16位):头部校验和,用于校验头部数据的完整性。
- Source Address(32位):源IP地址。
- Destination Address(32位):目的IP地址。
- Options(可变长度):可选项,灵活的附加信息。
- Padding:为了对齐选项字段而填充的字节。
TCP头部结构
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 源端口(16位) | 目的端口(16位)
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 序列号(32位)
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 确认号(32位)
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 数据 | 保 |U|A|P|R|S|F|
| 偏移 | 留 |R|C|S|S|Y|I| 窗口大小(16位)
| 4位 | 3 位 |G|K|H|T|N|N|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 校验和(16位) | 紧急指针(16位)
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 选项 | 填充
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 应用数据
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
详细字段说明:
- 源端口:发送方应用程序的端口号,用于识别应用进程。
- 目的端口:接收方应用程序的端口号
- 序列号:表示本报文段数据的第一个字节在整个数据流中的序号。
- 确认号:期待接收的下一个字节的序列号,即对上一个报文段的确认。
- 数据偏移:TCP头部的长度(以32位字为单位),用于确定数据部分的起始位置。
- 保留位:保留为将来使用,应置为0。
- Flags(标志位,9位):
- URG(紧急位):紧急指针有效。
- ACK(确认位):确认号有效。
- PSH(推送位):要求接收方立即将数据推送给应用层。
- RST(复位位):重置连接。
- SYN(同步|位):请求建立连接。
- FIN(结束位):释放连接。
- 窗口大小:接收方用于流量控制,指明可以接收的字节数。
- 校验和:用于校验TCP段的完整性。
- 紧急指针:指示紧急数据的结束位置,仅在URG位为1时有效。
- 选项,可变长度:用于指定其他功能,如最大报文段大小(MSS)。
- 填充,可变长度:为了使TCP头部长度是4字节的整数倍。
五、TCP在IP数据包的封装
>---------------数据报--------------------------------<
>-------TCP报文段---------<
|IP头部协议(ipv4或ipv6)|TCP头部|TCP(应用程序)数据|
------------------------------------------------------
>ipv4 20字节,不带选项
>ipv6 40字节
>tcp头部20字节