ICMP 与 PMTUD

本页目录

网页能完成 TCP 握手,却在传输较大响应时停住,可能是路径大小反馈出了问题:握手小包通过,数据包超过某一出口 MTU,返回的 ICMP 又未到达发送端。但这个现象也可能来自过滤、拥塞或服务端停顿。ICMP 的价值是提供可关联到原始报文的反馈,让诊断从“猜原因”走向“核对证据”。

想确认什么应观察什么单独不足以证明什么
IP Echo 往返是否可用匹配的 Echo Reply 与 RTTTCP 服务、DNS 或所有包长都正常
哪些节点参与探测路径TTL/Hop Limit 到期反馈每一跳都必须响应、返回路径相同
是否有 MTU 限制对应原包的 Too Big/Frag Needed一次超时就是 MTU 黑洞
发送端是否采纳反馈后续包长、重传区间和路径估计抓到 ICMP 就等于应用已恢复

两个协议族,不能混用类型编号

ICMPv4 在 IPv4 中的 Protocol 为 1;ICMPv6 的 Next Header 为 58。它们服务于 IP 的错误报告与信息交换,不提供通用可靠字节流。Echo 可以携带数据,但这不使 ICMP 成为 TCP 那样的应用传输服务。

用途ICMPv4 Type/CodeICMPv6 Type/Code
Echo 请求 / 回复8/0、0/0128/0、129/0
端口不可达3/31/4
包过大3/4,需要分片且 DF 已置位2/0,Packet Too Big
转发寿命耗尽11/03/0
分片重组超时11/13/1
参数问题Type 12,具体看 CodeType 4,具体看 Code

这是一张常用子集表,不能据此把所有不可达归为同一个错误。ICMPv6 还承载邻居与路由器发现等消息,粗暴屏蔽整个协议会影响基本网络运行。RFC 792:ICMPv4、RFC 4443:ICMPv6

错误消息为什么能找到原连接

ICMP 通用起始部分是 Type、Code、Checksum,后面结构随类型变化。错误消息引用触发错误的原始 IP 报文,端点结合地址、协议及可用的端口/序号等,把反馈关联到对应传输或探测。

以 IPv4 Fragmentation Needed 为例,前 8 B ICMP 头中包含 16 bit Next-Hop MTU,后接原 IP 头及原始数据起始部分。经典最低引用范围包含原 IP 头和最初 8 B 数据,但收到更多引用内容也不异常。解析引用报文时仍应按其真实 IHL、长度与可用字节检查,不能信任里面宣称的原始 Total Length 就直接越界读取。RFC 1191:MTU 字段与反馈

ICMPv4 校验覆盖 ICMP 消息;ICMPv6 校验还使用 IPv6 伪首部。校验成功只说明相应错误检测通过,不认证发送者。端点需要验证引用内容与已有发送状态相符,避免无关或伪造反馈任意改变连接状态。

错误反馈也不是可靠交付:通常不会针对另一个 ICMP 错误继续产生错误,以免递归;多播、广播、非首片等还有各协议的限制及例外,IPv6 Packet Too Big 就有重要例外。再加限速、丢包与过滤,⁠没有反馈不等于没有错误⁠。RFC 4443:错误生成规则

ping:一次匹配的往返样本

Echo 的 Identifier 和 Sequence 帮助关联请求/回复,Identifier 不应固定理解为进程 PID。RTT 包含去程、对端处理和回程;不能直接除二当作单向延迟,也不能用它代表高负载下应用请求延迟。

回复 TTL=57,只有在初始 TTL 确实为 64 的前提下,才支持回程经过约七次递减的推算。初始值可配置,中间可能有隧道或地址转换,因此不能据此确定操作系统,甚至不能直接推断正向跳数。

同一 1500 B MTU:应用可用字节取决于具体头部 IPv4 Echo IP 20 B 上层头 8 B 数据 1472 B IPv6 Echo IP 40 B 上层头 8 B 数据 1452 B IPv4 TCP IP 20 B 上层头 20 B 数据 1460 B IPv6 TCP IP 40 B 上层头 20 B 数据 1440 B 不含 IPv4 选项、IPv6 扩展头和 TCP 选项;框宽用于阅读,不按字节比例。

Linux iputils 的 ping -s 指 Echo 数据字节数。无选项 IPv4 的 -s 1472 对应 20+8+1472=1500 B IP 包;IPv6 无扩展头时相应值是 1452。以太网头和 FCS 不算在这个 IP MTU 中。iputils ping 手册

# 将占位符替换为待诊断目标;仅作为命令说明,本轮未执行探测。
ping -4 -c 3 -M do -s 1472 <目标IPv4>
ping -4 -c 3 -M do -s 1464 <目标IPv4>
ping -6 -c 3 -M do -s 1452 <目标IPv6>

-M do 受内核 PMTU 检查约束,过大的发送可能在本机直接报错,而非先到达中间路由器。需要区分本地发送失败、网络返回的错误和等待超时。三个探测样本也只是快速观察,不足以估计稳定丢包率。

traceroute:逐步延长探测寿命

从 TTL=1 开始,第一台转发路由器使其耗尽,按条件返回 Time Exceeded;增大 TTL 后,探测可到更远节点。最终如何判定到达取决于探测协议:UDP 常利用目的端口不可达,ICMP Echo 利用回复,TCP 可利用 SYN-ACK 或 RST 等结果。

每一跳显示的时间都是从探测源到该反馈节点再返回的往返时间,不能简单用相邻 RTT 相减,声称得到了两台路由器之间的链路延迟。反馈可能走不同回程,设备对 ICMP 的处理优先级也可能不同。

看到的现象更稳妥的解释
中间一跳是 *,后续与目标仍回复该跳未在时限内给出可见反馈,转发可能正常
中间一跳“高丢包”,终点正常可能是反馈限速,不能直接指控其转发丢包
同一 TTL 出现多个地址可能有 ECMP、路径变化或回复源地址差异
UDP 与 TCP 路径结果不同五元组散列、策略与过滤可能不同

传统探测改变 UDP 目的端口时可能改变 ECMP 散列,因此连续打印的地址不一定属于一条真实固定路径。定位业务问题应尽量对齐目标、协议、端口、时间和方向,并把路径图视为观测结果。

PMTUD:完整地跟踪一次 1500 → 1492 调整

PMTUD 的反馈回到原发送端,不沿原方向继续 发送端 S 发送 1500 B,DF=1 路由器 R 出口 MTU=1492 接收端 D 本次大包未收到 丢弃 ICMP 3/4:MTU=1492 + 原包引用 后续按可用大小分包;反馈路径本身也可能被过滤 示意 IPv4 经典 PMTUD;IPv6 对应 Packet Too Big,不能通过路由器分片绕过。

假设无选项 IPv4/TCP,接收方曾在 SYN 中通告 MSS=1460。发送端发出含 1460 B TCP 数据的 1500 B IP 包,DF=1;路由器出口 MTU=1492,反馈携带 1492。发送端验证反馈并更新路径估计后,后续数据包的有效数据上限可降为 1492−20−20=1452 B。

这里变化的是发送端当前使用的分段大小⁠,并非连接中途重新握手把接收方通告的 MSS 改成了 1452。若带 12 B TCP 选项,则同一 PMTU 下数据还要扣除这 12 B;IPv6 或隧道也需要按实际头部预算。

TCP 会按需要重传尚未确认的数据区间,IP 本身不保证替原包重发。路径可能再次改变,也可能还有更小的后续瓶颈,所以收到一次 1492 不代表永久找到唯一正确值。RFC 1191

IPv6 对应 Packet Too Big,基本头无 DF 位,路由器不分片;扩展头也占空间,不能因为基本头固定就忽略它们。经典 PMTUD 需要接收可信的大小反馈,具体缓存对象、寿命与重新探测行为由实现和配置决定。RFC 8201

收到 Too Big,改变的是 MSS 通告吗?

保留握手通告,才能看清后续调整究竟发生在哪个变量上。先丢掉大小反馈并重发,再重置、递送反馈、重发,可以对照两个不同的因果过程。历史结果不会因收到反馈而变成“那一包当时其实成功了”。

正在呈现知识画面
收到 Too Big,改变的是 MSS 通告吗?

路径反馈更新当前发送预算,不会改写握手里的 MSS。没有反馈时,重复同样的大包仍可能失败;TCP 选项还要从包长中扣除。

黑洞:按因果链收集证据

一次可信诊断可以依次确认:发送端确实发出某个大小的包;相应出口无法容纳;错误反馈在何处生成、到达或丢失;后续发送是否仍使用过大的包。若只能看端点,则结合可控包长变化、重传和应用进度缩小范围,同时保留其他原因。

观察点支持的判断下一步
大包本机发送即失败本地接口/路径估计已限制发送核对错误、接口 MTU 与目的路由
收到匹配的 Too Big路径给出了大小约束看发送端是否接受及缩小
小包稳定通过、大包反复无确认大小相关故障候选排除协议过滤、随机丢包与探测路径差异
缩小后恢复修改与恢复有相关性结合原反馈/出口证据判断根因

抓包还需注意 TSO/GSO/GRO:主机上的“巨包”可能是分段之前或聚合之后的视图,不一定曾以该大小在线上传输。MSS clamping 只涉及 TCP,不能修复所有 UDP/QUIC 路径问题。

PLPMTUD 与 Linux 的实现开关

分包层可以利用探测和明确确认来验证某个包长是否可用,减少对 ICMP 唯一反馈通路的依赖。DPLPMTUD 需要基础大小、向上搜索、探测失败重试与黑洞恢复等状态;单次大包丢失可能只是拥塞,不能简单二分一次就宣布最大值。若某 UDP 应用没有确认机制,也不能凭本地 send 成功断言远端收到。RFC 8899:DPLPMTUD

Linux net.ipv4.tcp_mtu_probing 文档区分 0(禁用)、1(检测到相应黑洞时启用)和 2(始终启用)。它是 TCP 实现行为,不是全局修复所有传输协议的开关;是否适用要结合内核、连接和网络策略。本篇保留机制说明,不用一个无条件 sysctl 写入代替诊断。Linux IP sysctl 文档

本地校验与长度预算

import struct

def checksum(data):
    if len(data)%2:
        data += bytes(1)
    total = sum(struct.unpack('!%dH' % (len(data)//2), data))
    while total>>16:
        total = (total&65535)+(total>>16)
    return (~total)&65535

message = bytearray(struct.pack('!BBHHH',8,0,0,0x1234,7)+b'hello')
message[2:4] = struct.pack('!H',checksum(message))
assert checksum(message) == 0
message[-1] ^= 1
assert checksum(message) != 0
assert 1500-20-8 == 1472
assert 1500-40-8 == 1452
assert 1492-20-20 == 1452
assert 1492-20-32 == 1440
assert 1464+8+20 == 1492
print('ICMP 一补校验、奇数字节填充及 MTU 数据预算通过')

这里仅构造 ICMPv4 Echo 的局部消息并计算长度;没有发送网络探测,也没有改变 PMTU、MSS 或 sysctl。

继续查阅