以太网帧、ip、udp/tcp数据包结构图解
本文详细介绍了网络协议栈中各层数据包的内存结构,包括以太网帧、IPv4数据包、UDP和TCP报文的结构体定义与内存布局。重点解析了各协议头部的字段含义、字节序转换、长度限制以及DPDK中的访问方法,并提供了内存对齐和性能优化的注意事项。文章通过结构体定义和内存布局示意图,清晰展示了从二层到四层协议的数据组织形式,为网络数据包处理提供了实用参考。
·
网络数据包结构与内存布局
一、以太网帧结构(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性能优化要点
- 零拷贝:使用指针+偏移量直接访问字段,避免 memcpy
- 批量处理:使用
rte_eth_rx_burst()批量收包,减少函数调用开销 - 预取:使用
rte_prefetch0()预取下一个 mbuf 数据 - 避免分支:尽量使用位运算和查表法代替 if-else
- 对齐访问:确保结构体字段对齐,提高访问效率
// 批量处理示例
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]);
}
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐


所有评论(0)