日常开发中,我们经常会碰到查询网络是否畅通以及域名对应 IP 地址等小需求,这时候用的最多的应该就是 ping 命令了。 那你知道 ping
命令是怎么工作的吗?今天,我们就来一起认识下 ping 命令及其对应的 ICMP 协议。

ICMP 协议

    ICMP 全称 Internet Control Message Protocol,指互联网控制报文协议。

    网络本身是不可靠的,数据包在传输过程中,可能会发生很多突发事件并导致数据传输失败。而网络层的 IP
协议是一个无连接的协议,它不会处理网络层的故障,因此,我们需要其它的协议,在数据包传输出现故障时,能将故障信息传回来,这样才能对应处理相关问题。

    就像在电视剧里看到的古代战争一样,打仗的时候需要通过斥候来传递战局情况,进而更好的控制战局。而 ICMP 报文在网络世界中就充当“斥候”这样的角色。

    ICMP 报文是封装在 IP 包里面的。因为传输指令的时候,肯定需要源地址和目标地址。它本身格式非常简单,如下图:



    ICMP 报文有很多的类型,不同的类型有不同的代码,最常用的类型是主动请求,代码为 8,主动请求的应答,代码为 0。从大的方面看可以分为 查询报文类型
和差错报文类型。

查询报文类型

    我们经常在电视剧里听到这样的话:来人,前方战事如何?斥候回来没?一有情况,立刻通报。

    类似这种主帅发起,主动查看敌情的情况,就对应着 ICMP的查询报文类型。例如,常见的 ping 命令就是查询报文,是一种主动请求,并且获得主动应答的
ICMP 协议。因此,ping 命令发出的包也是符合 ICMP 协议格式的,只不过它在后面增加了自己的格式。

    对 ping 的主动请求,进行网络抓包,称为 ICMP ECHO REQUEST。同理,主动请求的回复,称为ICMP ECHO REPLY
。比起原生的 ICMP,这里面多了两个字段,一个是标识符,另一个是序号。这不难理解,大帅派出去两队斥候,一队是找谁要的,一队是侦查战况的,要有个标识才能区分。

    另一方面,派出去的斥候,都要编个号。如果派出去 10 个,回来 10 个,就说明前方战况不错。如果派出去 10 个,回来 2 个,就说明情况可能不妙。

    在选项数据中,ping 还会存放发送请求的时间值,来计算往返时间,说明路程的长短。

差错报文类型

    差错报文主要是用来将发送的出错报文相关信息返回到源设备,以供源设备确定如果更好的重发失败的数据包。

    还是拿我们的“大帅”举例。

    当主帅正在大帐中看地图,思考战事时,外面的小兵突然喊到:大帅,不好啦,张将军遭遇埋伏,全军覆没了。

    这种是异常情况发起的,来报告发生了不好的事情,对应 ICMP 的差错报文。

    差错报文有以下常用的类型:

* 3:终点不可达
* 4:源抑制
* 5:重定向
* 11:超时
    第一种情况终点不可达。小兵报告,大帅,送给张将军的粮草没有送到。

    那大帅肯定会问,为啥没有送到?这就对应 ICMP 中的以下代码了。

* 网络不可达代码:0
* 主机不可达代码:1
* 协议不可达:2
* 端口不可达:3
* 需要进行分片但设置了不分片:4
    具体的场景就像这样:

* 网络不可达:大帅,找不到地方
* 主机不可达:大帅,找到地方,没找到张将军
* 协议不可达:大帅,找到地方,也找到人了,但是口令没对上。
* 端口不可达:大帅,找到地方,找到了人,也对上了口令,但事情没对上。我去送粮草,人家说在等救兵。
* 需要进行分片但设置不分片:大帅,走到一半,山路狭窄,想换瞎扯,但是出发前你下令严禁换小车,就没办法送到了。
    第二种是源站抑制。也就是让源站放慢发送速度(小兵:大帅,粮草送的太多了吃不完,你可以慢点送)。

    第三种是时间超时。也就是超过网络包的生存时间还是没到目的地(大帅,送粮草的人都把粮食吃完了,还没到地方,已经饿死了)。

    第四种是路由重定向
。也就是下次发给另一个路由器(大帅,上次送粮草的人本来只要走大王村,一公里就到了,结果非要绕道张家界,多了五公里,下次记得走大王村)。

    差错报文的结构相对复杂一些。除了前面还是 IP,ICMP 的前 8 个字节不变,后面则跟上出错的那个 IP 包的 IP 头和 IP 正文的前 8
个字节。

    而且这类斥候特别尽责,不但字节返回来报信,还把一部分遗物带回来。

* 斥候:大帅,张将军已经战死沙场,这是他的印信和佩剑。
* 大帅:张将军是怎么死的(可以查看 ICMP 的前 8 字节)?没错,这是张将军的剑(IP 数据包的头及正文前 8 字节)。
ping:查询报文类型的使用

    接下来,我们重点来看 ping 命令的发送和接收过程。



    假定主机 A 的 IP 地址是 192.168.1.1,主机 B 的 IP 地址是 192.168.1.2,它们都在同一个子网。那么,当在主机 A
上运行“ping 192.168.1.2” 后,会发生什么呢?

* 源主机构建 ICMP 请求数据包。这个数据包内包含多个字段。最重要的有两个,一个是类型字段,对应请求数据包而言,该字段为 8。另一个是顺序号
,主要用于区分连续 ping 的时候发出的多个数据包。每发出一个请求数据包,顺序号会自动加 1.为了能够计算往返时间 RTT,它会在报文的数据部分插入发送时间。
* IP 层构建 IP 数据包。ICMP 协议将数据包连同目标 IP 一起交给 IP 层,IP 层将以 192.168.1.2 作为目的地址,本机 IP
地址作为源地址,加上其他控制信息,构建一个 IP 数据包。
* 加入 MAC 头。找到 192.168.1.2 对应的 MAC 地址,附加上一些控制信息,依据以太网的介质访问规则,将它们传送出去。
    主机 B 收到数据帧后,会进行如下步骤:

* 检查 MAC 地址,丢弃或接收数据帧,提取 IP 数据包。检查数据包目的 MAC 地址,并与本机 MAC
地址对比。如符合,就接收数据帧,否则就丢弃。接收后检查数据帧,将 IP 数据包从帧中提取处理,交给本机的 IP 层。
* IP 层检查IP。检查完成后,提取有用的信息交给 ICMP 协议。
* 构建 ICMP 应答包。应答数据包的类型字段为 0,顺序号为接收到的请求数据包中的顺序号。
* 将应答数据包发给主机 A。
    在规定的时间内,源主机如果没有接到 ICMP 的应答包,则说明目标主机不可达。

    如果接收到了应打包,则说明目标主机可达。此时,源主机会检测时间延迟。就是用当前时刻减该数据包从源主机发出去的时刻。

    当然,这只是最简单的,同个局域网的情况。如果跨网段的话,还会涉及网关的转发、路由器的转发等。

    可以看出,ping 命令是使用了 ICMP 里面的 ECHO REQUEST 和 ECHO REPLY 类型。

    那其它类型呢?是不是只有真正遇到错误的时候,才能收到?答案是否定的。有一个 Traceroute 命令,它会使用 ICMP
的规则,故意制造一些能够产生错误的场景。

Traceroute:差错报文类型的使用

    Traceroute 命令有两个比较常用的功能。

    第一个功能:

    通过设置特殊的 TTL,追踪去往目的地时经过的路由器

    Traceroute 的参数执行某个目的 IP 地址,会发送一个 UDP 的数据包。

    将 TTL 设置成 1 时,表示这个数据包的 MP 为 1,碰到第一个“拦路虎”(通常是路由器或一个其它类型的关卡)就会阵亡了,然后就会返回一个
ICMP 包,这个包就是网络差错包,类型是时间超时。

    通过差错包,我们就能得到数据包到第一个关卡时花费的时间及其每个关卡的 IP 地址(有的主机不会响应 ICMP,所以会出现请求时全是 * 的情况)。

    那怎么知道 UDP 有没有到达目的主机呢?Traceroute 程序会发送一份 UDP 数据包给目的主机,但它会选择一个不可能的值作为 UDP
端口号(大于30000)。当该数据报到达目的主机时,由于找不到对应端口号,所以会返回一个“端口不可达”的错误报文。这样,我们就知道 UDP 是否到达主机了。

    第二个功能:

    设置数据包不分片,确定路径的 MTU

    发送分组,并设置“不分片”标志。发送的第一个分组的长度正好与出口的 MTU 相等。如果中间遇到窄的关卡就会被卡主,返回 ICMP
网络差错包,类型是“需要进行分片但设置了不分片”。就这样,每次收到ICMP“不能分片”差错时就减小分组的长度,从而确定整个路径中的 MTU。

总结

* ICMP 相当于网络世界的侦察兵。常用的有两种类型,主动探查的查询报文和异常报告的差错报文。
* ping 命令使用查询报文,Traceroute 命令使用差错报文。
参考:

* 刘超-趣谈网络协议系列课;