网络数据包结构与内存布局

一、以太网帧结构(Ethernet II)

以太网帧在内存中是一段连续的字节序列,可以用结构体来表示其各个字段。

以太网头6 + 6 + 2 = 14字节。

1.1 标准以太网帧结构

struct ether_hdr {
    uint8_t  d_addr[6];      // 目的MAC地址(6字节)
    uint8_t  s_addr[6];      // 源MAC地址(6字节)
    uint16_t ether_type;     // 以太类型(2字节,网络字节序)
} __attribute__((__packed__));

1.2 帧在内存中的布局

字段 偏移量 (offset) 长度
目的MAC 0 6B
源MAC 6 6B
EtherType 12 2B
Payload 14 46–1500B
FCS 硬件处理 4B(不含在软件访问中)

1.3 EtherType 常见值

EtherType 协议类型
0x0800 IPv4
0x0806 ARP
0x86DD IPv6
0x8100 VLAN 标签

注意:EtherType 是网络字节序,需要使用 rte_be_to_cpu_16()ntohs() 转换。

1.4 VLAN Tagged 帧结构

当 EtherType 为 0x8100 时,帧中存在 VLAN 标签字段:

struct vlan_hdr {
    uint16_t vlan_tci;   // TCI: Priority(3bit) + DEI(1bit) + VID(12bit)
    uint16_t eth_proto;  // 实际的 EtherType
} __attribute__((__packed__));

内存布局

dest_mac(6B) + src_mac(6B) + 0x8100(2B) + vlan_tci(2B) + eth_proto(2B) + payload

1.5 帧长度限制

类型 含义 字节数
最小帧 包含 FCS 64
最大帧 标准以太网 1518
Jumbo Frame 扩展帧 ≤ 9000
Payload 范围 标准 MTU 46–1500

1.6 DPDK 访问示例

struct rte_mbuf *m = ...; // 收到的数据包
struct ether_hdr *eth_hdr = rte_pktmbuf_mtod(m, struct ether_hdr *);

// 访问字段
uint16_t ether_type = rte_be_to_cpu_16(eth_hdr->ether_type);

// 跳过以太网头部,访问上层协议
void *l3_hdr = (void *)((uint8_t *)eth_hdr + sizeof(struct ether_hdr));

二、IPv4 数据包结构

IPv4 数据包是网络层的基本单元,位于以太网帧的 Payload 内。

ip头介于20~60字节。

2.1 IPv4 头部结构

struct ipv4_hdr {
#if __BYTE_ORDER == __LITTLE_ENDIAN
    uint8_t ihl:4;      // Internet Header Length, 单位为 32-bit 字
    uint8_t version:4;  // IP 版本号(4 表示 IPv4)
#else
    uint8_t version:4;
    uint8_t ihl:4;
#endif
    uint8_t tos;         // Type of Service(服务类型)
    uint16_t tot_len;    // 总长度(头部 + 数据),网络字节序
    uint16_t id;         // 标识字段
    uint16_t frag_off;   // 分片偏移 + 标志位
    uint8_t ttl;         // 生存时间(Time To Live)
    uint8_t protocol;    // 上层协议(TCP=6, UDP=17, ICMP=1)
    uint16_t check;      // 头部校验和
    uint32_t saddr;      // 源 IP 地址
    uint32_t daddr;      // 目的 IP 地址
    // 可选字段:选项(长度可变,ihl 决定)
} __attribute__((__packed__));

2.2 IPv4 头部在内存中的布局

字段 偏移量 (offset) 长度
Version/IHL 0 1B
TOS 1 1B
Total Length 2-3 2B
Identification 4-5 2B
Flags/FragOff 6-7 2B
TTL 8 1B
Protocol 9 1B
Header Checksum 10-11 2B
Source IP 12-15 4B
Dest IP 16-19 4B
Options 20+ 可变(可选)

2.3 关键字段说明

字段 说明
Version IP 版本号(IPv4=4)
IHL 头部长度(单位 4 字节)
TOS 服务类型 / DiffServ
Total Length 数据包总长度(字节)
Identification 分片标识符
Flags/FragOff 分片标志 + 偏移量
TTL 生存时间,防止循环转发
Protocol 上层协议类型(TCP/UDP/ICMP)
Header Checksum 头部校验和,检测头部损坏
Source IP / Dest IP IPv4 地址(32-bit)

2.4 常见 Protocol 值

Protocol 上层协议
1 ICMP
6 TCP
17 UDP

2.5 IP 数据包长度限制

类型 字节数
最小头部 20
最大头部 60(含可选字段)
最大总长度 65535(2 字节 Total Length)
数据部分 Total Length - Header Length

2.6 DPDK 访问示例

struct ether_hdr *eth_hdr = rte_pktmbuf_mtod(m, struct ether_hdr *);
struct ipv4_hdr *ip_hdr = (struct ipv4_hdr *)((uint8_t *)eth_hdr + sizeof(struct ether_hdr));

// 计算 IP 头部长度
uint8_t ip_hdr_len = (ip_hdr->ihl) * 4;

// 访问上层协议
void *l4_hdr = (void *)((uint8_t *)ip_hdr + ip_hdr_len);

三、UDP 数据报结构

UDP(User Datagram Protocol)是无连接的传输层协议,位于 IP 数据包的 Payload 内。

UPD头 2 + 2 + 2 + 2 = 8字节。

3.1 UDP 头部结构

struct udp_hdr {
    uint16_t src_port;   // 源端口号
    uint16_t dst_port;   // 目的端口号
    uint16_t len;        // UDP 长度(头部 + 数据),网络字节序
    uint16_t checksum;   // 校验和,覆盖 UDP 头 + 数据 + 虚拟 IP 头
} __attribute__((__packed__));

3.2 UDP 头部在内存中的布局

字段 偏移量 (offset) 长度
Source Port 0-1 2B
Dest Port 2-3 2B
Length 4-5 2B
Checksum 6-7 2B
Payload 8+ 可变

3.3 关键字段说明

字段 说明
Source Port 发送端口号
Dest Port 接收端口号
Length UDP 数据报总长度(头 + 数据)
Checksum 校验和,用于完整性检测

3.4 UDP 数据报长度限制

类型 字节数
最小长度 8(仅头部)
最大长度 65535(Total Length 字段限制)
数据部分 Length - 8

3.5 DPDK 访问示例

struct ipv4_hdr *ip_hdr = ...;
struct udp_hdr *udp_hdr = (struct udp_hdr *)((uint8_t *)ip_hdr + (ip_hdr->ihl * 4));

// 访问 UDP payload
void *payload = (void *)((uint8_t *)udp_hdr + sizeof(struct udp_hdr));

// 转换端口号
uint16_t src_port = rte_be_to_cpu_16(udp_hdr->src_port);
uint16_t dst_port = rte_be_to_cpu_16(udp_hdr->dst_port);

四、TCP 数据报结构

TCP(Transmission Control Protocol)是面向连接的传输层协议,位于 IP 数据包的 Payload 内。

tcp头和ip头一样:20~60字节。

4.1 TCP 头部结构(固定部分)

struct tcp_hdr {
    uint16_t src_port;     // 源端口号
    uint16_t dst_port;     // 目的端口号
    uint32_t seq_num;      // 序列号
    uint32_t ack_num;      // 确认号
#if __BYTE_ORDER == __LITTLE_ENDIAN
    uint8_t  reserved:4;   // 保留
    uint8_t  data_offset:4; // 数据偏移(TCP头长度/32位字)
#else
    uint8_t  data_offset:4;
    uint8_t  reserved:4;
#endif
    uint8_t  flags;        // 控制位(URG, ACK, PSH, RST, SYN, FIN)
    uint16_t window;       // 窗口大小
    uint16_t checksum;     // 校验和(覆盖 TCP 头 + 数据 + 虚拟 IP 头)
    uint16_t urg_ptr;      // 紧急指针
    // 可选字段(Options),长度可变,data_offset 决定
} __attribute__((__packed__));

4.2 TCP 头部在内存中的布局(固定部分)

字段 偏移量 (offset) 长度
Source Port 0-1 2B
Dest Port 2-3 2B
Seq Number 4-7 4B
Ack Number 8-11 4B
DataOffset+Res 12 1B
Flags 13 1B
Window 14-15 2B
Checksum 16-17 2B
Urgent Pointer 18-19 2B
Options 20+ 可变(可选)
Payload data_offset*4+ 可变

4.3 关键字段说明

字段 说明
Source Port 源端口号
Dest Port 目的端口号
Seq Number 数据序列号
Ack Number 确认号(若 ACK 标志置位)
Data Offset TCP 头部长度(单位 4 字节)
Flags 控制位(URG, ACK, PSH, RST, SYN, FIN)
Window 窗口大小,用于流量控制
Checksum 校验和,检测头部和数据完整性
Urgent Ptr 紧急数据指针
Options 可选字段(如时间戳、最大报文长度)

4.4 TCP Flags 标志位

#define TCP_FIN  0x01
#define TCP_SYN  0x02
#define TCP_RST  0x04
#define TCP_PSH  0x08
#define TCP_ACK  0x10
#define TCP_URG  0x20

4.5 TCP 数据报长度限制

类型 字节数
最小头部 20
最大头部 60(含选项)
最大总长度 65535(受 IP Total Length 限制)
数据部分 Total Length - Header Length

4.6 DPDK 访问示例

struct ipv4_hdr *ip_hdr = ...;
struct tcp_hdr *tcp_hdr = (struct tcp_hdr *)((uint8_t *)ip_hdr + (ip_hdr->ihl * 4));

// 计算 TCP 头部长度
uint8_t tcp_hdr_len = tcp_hdr->data_offset * 4;

// 访问 TCP payload
void *payload = (void *)((uint8_t *)tcp_hdr + tcp_hdr_len);

// 检查标志位
if (tcp_hdr->flags & TCP_SYN) {
    // 处理 SYN 包
}

五、完整数据包内存布局示意图

+-------------------+  offset 0
| Ethernet Header   |  14 字节
|  - Dst MAC (6)    |
|  - Src MAC (6)    |
|  - EtherType (2)  |
+-------------------+  offset 14
| IPv4 Header       |  20-60 字节(可变)
|  - Version/IHL    |
|  - ...            |
|  - Src IP (4)     |
|  - Dst IP (4)     |
|  - Options        |
+-------------------+  offset 14 + IP_hdr_len
| TCP/UDP Header    |  TCP: 20-60, UDP: 8
|  - Src Port (2)   |
|  - Dst Port (2)   |
|  - ...            |
+-------------------+  offset 14 + IP_hdr_len + L4_hdr_len
| Application Data  |  可变长度
| (Payload)         |
+-------------------+

六、内存对齐与字节序注意事项

6.1 结构体对齐

  • 使用 __attribute__((__packed__)) 防止编译器插入 padding
  • 确保结构体大小等于实际字节数

6.2 字节序转换

  • 网络字节序:大端(Big Endian)
  • x86 主机字节序:小端(Little Endian)
  • DPDK 转换函数:
    • rte_be_to_cpu_16() / rte_cpu_to_be_16()
    • rte_be_to_cpu_32() / rte_cpu_to_be_32()
    • 或使用标准函数:ntohs(), ntohl(), htons(), htonl()

6.3 DPDK——mbuf 数据访问

// 获取数据包起始地址
struct ether_hdr *eth = rte_pktmbuf_mtod(m, struct ether_hdr *);

// mbuf 的 data_off 表示数据起始位置相对 buf_addr 的偏移
// rte_pktmbuf_mtod 返回 buf_addr + data_off

6.4 DPDK指针运算

// 层层解析数据包
uint8_t *pkt = rte_pktmbuf_mtod(m, uint8_t *);
struct ether_hdr *eth = (struct ether_hdr *)pkt;
struct ipv4_hdr *ip = (struct ipv4_hdr *)(pkt + sizeof(struct ether_hdr));
struct tcp_hdr *tcp = (struct tcp_hdr *)(pkt + sizeof(struct ether_hdr) + (ip->ihl * 4));
void *payload = pkt + sizeof(struct ether_hdr) + (ip->ihl * 4) + (tcp->data_offset * 4);

七、DPDK 常用宏定义

// DPDK 提供的以太网类型宏
#define RTE_ETHER_TYPE_IPv4  0x0800
#define RTE_ETHER_TYPE_IPv6  0x86DD
#define RTE_ETHER_TYPE_ARP   0x0806
#define RTE_ETHER_TYPE_VLAN  0x8100

// IP 协议号宏
#define IPPROTO_ICMP  1
#define IPPROTO_TCP   6
#define IPPROTO_UDP   17

八、DPDK性能优化要点

  1. 零拷贝:使用指针+偏移量直接访问字段,避免 memcpy
  2. 批量处理:使用 rte_eth_rx_burst() 批量收包,减少函数调用开销
  3. 预取:使用 rte_prefetch0() 预取下一个 mbuf 数据
  4. 避免分支:尽量使用位运算和查表法代替 if-else
  5. 对齐访问:确保结构体字段对齐,提高访问效率
// 批量处理示例
struct rte_mbuf *pkts[BURST_SIZE];
uint16_t nb_rx = rte_eth_rx_burst(port_id, queue_id, pkts, BURST_SIZE);

for (int i = 0; i < nb_rx; i++) {
    // 预取下一个包
    if (i + 1 < nb_rx)
        rte_prefetch0(rte_pktmbuf_mtod(pkts[i + 1], void *));
    
    // 处理当前包
    process_packet(pkts[i]);
}
Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐