4、ICMP协议:ICMP报文类型、Ping命令的底层实现、Traceroute原理、ICMP重定向与差错处理

4.1 ICMP协议到底是什么?

ICMP,全称是Internet Control Message Protocol,互联网控制报文协议。说白了,它就是IP协议的「信使」。

IP协议本身不靠谱,只管发,不管到没到。那出了问题怎么办?谁来通知发送方?

嗯,这就是ICMP的工作了。它负责传递网络层的控制信息和差错报告。注意,ICMP不是用来传输用户数据的,它是用来「管网络」的。

我个人习惯把ICMP比作网络里的「对讲机」。数据包走丢了、走错了、走不动了,ICMP就会喊一嗓子:「喂,你发的那个包,我这边过不去啊!」

4.2 ICMP报文类型

ICMP报文分两大类:差错报告报文 和 查询报文。我整理了一张表,平时调试时对照着看很方便。

类型(Type) 代码(Code) 含义 类别
0 0 Echo Reply(回显应答) 查询
3 0~15 Destination Unreachable(目的不可达) 差错
5 0~3 Redirect(重定向) 差错
8 0 Echo Request(回显请求) 查询
11 0~1 Time Exceeded(超时) 差错

这里面,Type 8 和 Type 0 就是Ping命令的核心。Type 3 和 Type 11 则是Traceroute和日常排错的关键。

重点记忆:Type 3(目的不可达)下面还有细分代码。比如Code 0表示网络不可达,Code 1表示主机不可达,Code 3表示端口不可达。我在项目里遇到过好几次,设备连不上,抓包一看是Code 1,立马知道是路由表的问题。

4.3 Ping命令的底层实现

Ping,应该是嵌入式工程师用得最多的网络诊断工具了。但你知道它底层是怎么工作的吗?

其实很简单。Ping就是发送ICMP Echo Request(Type 8),然后等待对方回复ICMP Echo Reply(Type 0)。

我写过一个简易的Ping实现,核心代码大概长这样:

// 构造ICMP Echo Request报文
struct icmp_packet {
    uint8_t  type;      // 8 表示Echo Request
    uint8_t  code;      // 固定为0
    uint16_t checksum;  // 校验和
    uint16_t id;        // 标识符,通常用进程ID
    uint16_t seq;       // 序列号
    uint8_t  data[];    // 可选填充数据
};

// 发送流程
void ping_send(uint32_t dest_ip) {
    // 1. 构造ICMP报文
    // 2. 计算校验和
    // 3. 封装成IP包
    // 4. 通过原始套接字发送
    // 5. 启动定时器
    // 6. 等待Echo Reply
}

你想想看,Ping能测量RTT(往返时间),靠的就是记录发送时间和接收时间的时间差。如果超时没收到回复,就显示「请求超时」。

调试技巧:在嵌入式设备上,如果Ping不通,我建议先抓包看看。有时候是设备根本没发出ICMP请求,有时候是发出了但没收到回复。抓包一看,问题就清楚了。我曾经在一个项目里折腾了两天,最后发现是网卡驱动里的校验和计算有bug。

4.4 Traceroute原理

Traceroute这个工具,说白了就是利用了ICMP的TTL超时机制。

原理是这样的:

  1. 发送一个UDP包(或ICMP包),TTL设为1
  2. 第一个路由器收到后,TTL减为0,丢弃包,并回复ICMP Time Exceeded(Type 11)
  3. 发送方收到这个ICMP报文,就知道第一个路由器的IP了
  4. 然后发送TTL=2的包,第二个路由器会回复超时
  5. 以此类推,直到到达目标主机

目标主机收到TTL不为0的包后,会回复什么?如果是UDP包,目标主机会回复ICMP Port Unreachable(Type 3, Code 3)。Traceroute收到这个,就知道到达终点了。

我记得有一次排查一个网络延迟问题,用Traceroute一看,发现数据包绕了大半个中国才到服务器。中间有个节点的延迟高达300ms。嗯,找到问题就好办了,直接联系运营商调整路由。

注意:有些路由器或防火墙会屏蔽ICMP报文,导致Traceroute看不到完整路径。这时候可以试试用TCP SYN包做Traceroute,很多工具都支持这种模式。

4.5 ICMP重定向

ICMP重定向,这个知识点很多工程师容易忽略。但实际工作中,它经常出来「捣乱」。

场景是这样的:主机A的默认网关是路由器R1。主机A要访问网络B,它把包发给了R1。R1一看,发现去网络B的最佳路径其实是路由器R2。于是R1就给A发了一个ICMP Redirect报文,说:「以后去网络B,你直接找R2,别找我。」

主机A收到这个报文后,会更新自己的路由表。下次再访问网络B,就直接发给R2了。

听起来挺智能的对吧?但问题来了——

我曾经遇到过一个案例:设备突然无法访问某个服务器了。查了半天,发现是之前收到过一个恶意的ICMP重定向报文,把路由表给改了。从那以后,我养成了一个习惯:在嵌入式设备上,默认关闭ICMP重定向功能。

// Linux下关闭ICMP重定向
echo 0 > /proc/sys/net/ipv4/conf/all/accept_redirects
echo 0 > /proc/sys/net/ipv4/conf/all/secure_redirects

4.6 ICMP差错处理实战

最后聊聊实际调试中怎么用ICMP来排错。

常见的ICMP差错报文及含义:

  • Destination Unreachable (Type 3):最常见。Code 0网络不可达,Code 1主机不可达,Code 3端口不可达。
  • Time Exceeded (Type 11):TTL超时,或者分片重组超时。
  • Parameter Problem (Type 12):IP头部参数错误。
  • Source Quench (Type 4):源站抑制,已废弃。以前用来做流量控制。

我的调试流程一般是这样的:

  1. 先Ping目标IP,看通不通
  2. 不通的话,抓包看有没有ICMP差错报文返回
  3. 如果有Type 3,看Code是多少,定位问题
  4. 如果没有ICMP回复,可能是防火墙过滤了,也可能是链路断了
  5. 用Traceroute看数据包走到哪一步断了

避坑指南:我曾经在一个项目里,设备Ping外网不通,但Ping网关是通的。抓包发现设备发出了ICMP请求,但网关没有回复任何ICMP差错报文。后来查配置,发现网关设备上配置了ACL,把ICMP报文全部丢弃了。所以记住:没有ICMP回复,不代表网络没问题,可能是ICMP本身被过滤了。

好了,ICMP协议就聊到这里。下一章我们讲ARP协议,那个在局域网调试中更是家常便饭。到时候再跟大家分享一些实战经验。