IPv4 首部与分片:用一个 3000 字节的 ping 观察分片
拆解 IPv4 首部的 20 个字节,动手制造分片,计算每个分片的偏移量,并顺带理解 TTL 与 traceroute 的原理。
- DATE
- LEN
- 583 字
- TTL
- 2 min
- LAYER
- L3 · 网络层
IP 层的工作看似简单——把数据报从一台主机送到另一台——但首部里藏着不少值得推敲的设计。这篇用一个“超大号” ping 来观察 IPv4 分片。
IPv4 首部
012345678910111213141516171819202122232425262728293031
Version4
IHL4
DSCP6
ECN
Total Length16
Identification16
R1
DF1
MF1
Fragment Offset13
TTL8
Protocol8
Header Checksum16
Source Address32
Destination Address32
Options(IHL > 5 时存在)
几个关键字段:
- IHL:首部长度,单位是 4 字节。最常见的值是 5,即 20 字节;
- Total Length:首部 + 数据的总长度,单位是字节;
- Identification / Flags / Fragment Offset:分片三件套;
- TTL:每经过一个路由器减 1,减到 0 就丢弃并回送 ICMP 超时;
- Protocol:上层协议,
1=ICMP、6=TCP、17=UDP。
制造分片
以太网 MTU 一般是 1500 字节。让 ping 发一个远超 MTU 的报文,并且不设置 DF 位:
# -s 3000:ICMP 数据部分 3000 字节;-M dont:不设置 DF,允许分片
ping -c 1 -s 3000 -M dont 192.0.2.1
# 对比:设置 DF(禁止分片),本地就会直接报错
ping -c 1 -s 3000 -M do 192.0.2.1
# ping: local error: message too long, mtu=1500
用 Wireshark 抓包,过滤器:
ip.flags.mf == 1 || ip.frag_offset > 0
算一算分片
IP 层要发送的数据 = ICMP 首部 8 字节 + 数据 3000 字节 = 3008 字节。
每个分片能携带的数据 = MTU 1500 − IP 首部 20 = 1480 字节,且必须是 8 的倍数(因为 Fragment Offset 的单位是 8 字节)。1480 ÷ 8 = 185,刚好整除。
| 分片 | 携带数据 | Total Length | Offset 字段 | 实际字节偏移 | MF |
|---|---|---|---|---|---|
| 1 | 1480 | 1500 | 0 | 0 | 1 |
| 2 | 1480 | 1500 | 185 | 1480 | 1 |
| 3 | 48 | 68 | 370 | 2960 | 0 |
三个分片的 Identification 相同,接收方据此把它们归为一组;靠 Offset 排序;看到 MF=0 就知道是最后一片。
笔记
只有第一个分片带有 ICMP 首部。后续分片里没有端口、没有 ICMP 类型——这也是防火墙处理分片如此麻烦的原因。
技巧
IPv6 中路由器不再分片,只能由源主机分片(通过扩展首部),路径上 MTU 不够时路由器直接回送 ICMPv6 Packet Too Big。
TTL 与 traceroute
TTL 防止报文在路由环路中无限转发。traceroute 巧妙地利用了它:
- Linux 的
traceroute默认发送 UDP 探测报文到高位端口,到达目标后目标回送“端口不可达”; - Windows 的
tracert使用 ICMP Echo,到达目标后收到 Echo Reply。
过滤 traceroute 相关的 ICMP:
icmp.type == 11 || icmp.type == 3
小结
- IHL 单位 4 字节,Fragment Offset 单位 8 字节,Total Length 单位 1 字节——三种单位别搞混;
- 分片由 Identification 归组、Offset 排序、MF 标记结尾;
- 尽量避免分片:现代 TCP 通过 MSS 协商和路径 MTU 发现(PMTUD)来规避它;
- TTL 不只是“防环路”,也是 traceroute 的基础,还能粗略推测操作系统(Linux 初始 64、Windows 128)。