4. ICMP协议:报文类型与结构、Ping与Traceroute原理、ICMP重定向与差错报告

ICMP,全称 Internet Control Message Protocol,互联网控制报文协议。很多人觉得它只是 IP 协议的附属品,没什么好学的。但说实话,网络出问题的时候,ICMP 往往是第一个帮你定位故障的帮手。

我刚开始做网络运维那会儿,遇到一个跨网段丢包的问题。抓包看了半天,TCP 重传一大堆,就是找不到根因。后来一个老同事提醒我:“你看看 ICMP 的差错报文。” 我这才发现,中间路由器一直在发 “Destination Unreachable — Fragmentation Needed”。嗯,从那以后,我再也不敢小看 ICMP 了。

4.1 ICMP 报文类型与结构

ICMP 报文是直接封装在 IP 数据报里的。它的头部很简单,只有 8 个字节,但类型字段决定了它的“性格”。

字段 长度(字节) 说明
类型(Type) 1 标识报文类型,如 0、3、8、11
代码(Code) 1 对同一类型的细分,比如 Type 3 下 Code 0 是网络不可达,Code 1 是主机不可达
校验和(Checksum) 2 覆盖整个 ICMP 报文
剩余字段 4 根据类型不同,内容不同。比如 Echo Request/Reply 里放标识符和序列号

常见的 ICMP 类型,我整理了一张表,你抓包时对照着看,一眼就能认出报文在干什么。

Type Code 含义 典型场景
0 0 Echo Reply Ping 的响应
3 0~15 Destination Unreachable 路由不可达、端口不可达、分片需要但 DF 置位等
5 0~3 Redirect 路由器告诉主机“你走错路了,应该走那条路”
8 0 Echo Request Ping 的请求
11 0~1 Time Exceeded TTL 超时(Traceroute 的核心)
抓包小技巧: 在 Wireshark 里直接过滤 icmp 就能看到所有 ICMP 报文。如果想看特定类型,用 icmp.type == 8 或 icmp.type == 3。我个人习惯把 Type 和 Code 一起过滤,比如 icmp.type == 3 && icmp.code == 4 专门抓“分片需要但 DF 置位”的报文。

4.2 Ping 的原理与抓包分析

Ping 可能是大家最熟悉的网络工具了。它的原理其实很简单:发送 ICMP Echo Request,等待 ICMP Echo Reply。

但你真的看过 Ping 的报文细节吗?我建议你亲自抓一次包。你会发现,Ping 报文的 Identifier 和 Sequence Number 字段是用来匹配请求和响应的。操作系统发出去一个请求,收到回复时通过这两个字段确认“这是刚才那个请求的回复”。

举个例子,我在 Windows 上执行 ping 8.8.8.8,抓包结果大致是这样的流程:

  1. 主机发送 ICMP Type 8, Code 0,Identifier=1, Sequence=1
  2. 目标回复 ICMP Type 0, Code 0,Identifier=1, Sequence=1
  3. 主机发送 ICMP Type 8, Code 0,Identifier=1, Sequence=2
  4. 目标回复 ICMP Type 0, Code 0,Identifier=1, Sequence=2

你看,Identifier 在整个 Ping 会话中保持不变,Sequence 逐次递增。这样即使多个 Ping 进程同时运行,也不会搞混。

注意: 有些防火墙会直接丢弃 ICMP Echo Request,导致 Ping 不通。但这不代表目标主机真的不可达。我曾经遇到过一台服务器,Ping 不通,但 SSH 和 HTTP 都正常。后来发现是安全组策略把 ICMP 给禁了。所以,Ping 不通的时候,别急着下结论。

4.3 Traceroute 的原理与抓包分析

Traceroute 的原理比 Ping 稍微绕一点,但说白了就是利用了 ICMP 的 Time Exceeded 报文。

它的工作方式是这样的:

  1. 发送一个 UDP 报文(Linux 默认)或 ICMP Echo Request(Windows 默认),TTL 设为 1。
  2. 第一跳路由器收到后,TTL 减为 0,丢弃报文,并回复一个 ICMP Time Exceeded(Type 11, Code 0)。
  3. 源主机收到这个 ICMP 报文,就知道第一跳路由器的 IP 了。
  4. 接着发送 TTL=2 的报文,第二跳路由器回复 Time Exceeded。
  5. 以此类推,直到报文到达目标主机。目标主机收到 TTL=1 的报文后,会回复一个 ICMP Port Unreachable(Type 3, Code 3),因为 Traceroute 用的目标端口通常是一个高位随机端口,目标主机上没有对应的服务。

我画了一张流程图,帮你把 Traceroute 的完整路径理清楚。

Traceroute 工作原理 源主机 路由器 1 路由器 2 目标主机 TTL=1 Time Exceeded TTL=2 TTL=1 Time Exceeded TTL=3 TTL=2 TTL=1 Port Unreachable 关键点 1. 每跳发送一个报文,TTL 逐次递增 2. 中间路由器回复 ICMP Time Exceeded 3. 目标主机回复 ICMP Port Unreachable 4. 通过 RTT 可以估算每一跳的延迟

你想想看,Traceroute 其实就是在“试探”路径上的每一台路由器。每次 TTL 超时,路由器都会“暴露”自己的 IP。这就是为什么 Traceroute 能画出网络路径。

避坑指南: 我曾经遇到过一个问题:Traceroute 到某一跳之后,后面的几跳全是 * * *。一开始我以为网络断了,后来发现是中间某台路由器配置了“不回复 TTL 超时”的策略。这种情况下,Traceroute 就失效了。所以,看到星号别慌,先确认一下是不是路由器故意不回复。

4.4 ICMP 重定向与差错报告

ICMP 重定向,说白了就是路由器在告诉主机:“你刚才发报文的路由不是最优的,下次走这条路。” 这个机制在早期网络里很常见,但现在用得少了,因为很多主机直接配了默认网关,不需要动态学习。

重定向的流程是这样的:

  1. 主机发送一个报文到路由器 A。
  2. 路由器 A 查路由表,发现报文应该走路由器 B 才能到目标。
  3. 路由器 A 把报文转发给路由器 B,同时给主机发一个 ICMP Redirect(Type 5)。
  4. 主机收到后,更新自己的路由表,下次直接发给路由器 B。

但这里有个安全风险。你想想看,如果攻击者伪造一个 ICMP Redirect 报文,告诉主机“去 8.8.8.8 走我这里”,那主机的流量就被劫持了。这就是所谓的“ICMP 重定向攻击”。

安全提醒: 我个人建议,在生产环境的主机上,直接禁用 ICMP 重定向。Linux 下可以通过 sysctl -w net.ipv4.conf.all.accept_redirects=0 来关闭。Windows 下也可以在注册表里关掉。别给攻击者留机会。

再来说说 ICMP 的差错报告。当 IP 数据报在传输过程中遇到问题,比如目标不可达、TTL 超时、参数错误等,路由器或目标主机会生成一个 ICMP 差错报文,告诉源主机“你的报文出问题了”。

差错报文有一个特点:它会把出问题的 IP 数据报的头部(至少 8 个字节)原封不动地放在 ICMP 报文的数据部分。这样源主机收到后,就能知道是哪个报文出了问题。

举个例子,你发了一个 TCP SYN 报文到某个端口,但目标主机上没有服务监听。目标主机会回复一个 ICMP Destination Unreachable(Type 3, Code 3 — Port Unreachable)。你抓包一看,ICMP 报文的数据部分就是那个 TCP SYN 报文的 IP 头和 TCP 头。嗯,这样你就能精确地定位到是哪个连接出了问题。

核心要点:
  • ICMP 差错报文必须包含原始报文的 IP 头 + 至少 8 字节载荷
  • 差错报文本身不会再触发新的 ICMP 差错报文(防止风暴)
  • 多播和广播报文不会触发 ICMP 差错报告

好了,ICMP 的内容就讲到这里。你只要记住:Ping 是 ICMP 的“请求-应答”模型,Traceroute 是 ICMP 的“TTL 超时”模型,而重定向和差错报告是 ICMP 的“网络诊断”模型。抓包的时候,多看看 Type 和 Code 字段,你就能读懂网络在说什么。