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超时机制。
原理是这样的:
- 发送一个UDP包(或ICMP包),TTL设为1
- 第一个路由器收到后,TTL减为0,丢弃包,并回复ICMP Time Exceeded(Type 11)
- 发送方收到这个ICMP报文,就知道第一个路由器的IP了
- 然后发送TTL=2的包,第二个路由器会回复超时
- 以此类推,直到到达目标主机
目标主机收到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):源站抑制,已废弃。以前用来做流量控制。
我的调试流程一般是这样的:
- 先Ping目标IP,看通不通
- 不通的话,抓包看有没有ICMP差错报文返回
- 如果有Type 3,看Code是多少,定位问题
- 如果没有ICMP回复,可能是防火墙过滤了,也可能是链路断了
- 用Traceroute看数据包走到哪一步断了
避坑指南:我曾经在一个项目里,设备Ping外网不通,但Ping网关是通的。抓包发现设备发出了ICMP请求,但网关没有回复任何ICMP差错报文。后来查配置,发现网关设备上配置了ACL,把ICMP报文全部丢弃了。所以记住:没有ICMP回复,不代表网络没问题,可能是ICMP本身被过滤了。
好了,ICMP协议就聊到这里。下一章我们讲ARP协议,那个在局域网调试中更是家常便饭。到时候再跟大家分享一些实战经验。