网络基础: ICMP 是如何工作的?
2017-09-30 计算机网络
概述
因特网控制报文协议 ICMP(Internet Control Message Protocol)是一个差错报告机制,属于 TCP/IP 协议簇中的一个重要子协议。通常被 IP 层或更高层协议(TCP/UDP)使用,属于网络层协议,主要用于在 IP 主机和路由器之间传递控制消息,用于报告主机是否可达、路由是否可用等。
ICMP 控制消息虽然并不传输具体的数据,但是对于收集各种网络信息、诊断和排除各种网络故障以及指定数据的传递具有至关重要的作用。
日常开发中很多常用的网络工具,其底层实现都使用到了 ICMP
协议,例如 ping
和 traceroute
。
为什么需要 ICMP ?
在数据传输过程中,网络层的 IP 协议提供 “尽力而为” 的传输服务 (也就是不保证数据包一定会被送达),如果要使传输过程变得可靠,就要使用传输层的 TCP 协议来实现。IP 协议不会在目标主机是否收到数据进行验证、无法进行流量控制和差错控制,因此在数据包的传输过程中难免产生各种错误。
ICMP 就是为了更有效地转发 IP 数据报和提高数据传输成功的机会,虽然封装在 IP 数据报中,但是依然属于网络层协议。
当网路中的数据包传输出现问题时,主机/设备就可以使用 ICMP 向上层协议报告异常和具体的差错情况,然后上层协议就可以判断通信是否正确,以及进行必要的流量控制和差错控制。
ICMP 如何工作 ?
ICMP 是一个差错报告机制,所以只需要做一件事情: 当数据报的处理过程出现差错时,向 源主机/设备 返回具体的错误就可以了,其他一律不管。
源主机/设备 收到 ICMP 差错报告后,虽然无法判断差错是由哪个中间网络设备所引起的 (因为 IP 数据包中只有源端和目标端,没有记录数据包在网络中传输的全部路径),但是可以根据 ICMP 差错报文来确定错误的类型,以及确定接下来如何更有效地重发失败的数据包。
ICMP 报文类型
不同的 “类型 + 代码” 组合表示不同的 ICMP 报文类型,每一个 ICMP 报文作为 IP 数据包的数据部分被封装在 IP 数据包内部。
ICMP 规定了各种类型的消息,除了请求和应答外,还有数据包在转发和接收过程中可能遇到的各种错误。
例如当主机或路由器无法将数据包送达目标地址时,会发送 Destination Unreachable
错误应答给源主机,造成这个问题可能有如下的原因:
- 网络不可达: 路由器无法确定到达目标主机的路径,可能因为路由表中不存在到达目标网络的路由,或者路由器无法到达指定网络
- 主机不可达: 路由器无法直接到达目标主机,可能因为目标主机关闭了连接,或者目标主机已离线
- 端口不可达: 目标主机上没有程序监听目标端口
- 协议不可达: 目标主机上无法处理数据包指定的协议
不会产生 ICMP 差错的场景
- ICMP 差错报文不会产生 ICMP 差错报文 (类似递归),主要为了防止 ICMP 消息的无限产生和传递,但是 ICMP 查询报文可能会产生 ICMP 差错报文
- 目的地址是广播地址或多播地址的 IP 数据报文
- 作为链路层广播的数据报
- ICMP 不是 IP 数据分片的 “第一片”
- 源地址不是单个主机的数据包 (例如 0.0.0.0 默认地址、127.0.0.1 环回地址、广播地址、多播地址)
Fragment reassembly
当接收方因为分片丢失而无法按时完成数据包的重组时,它可以放弃并回复一个超时消息。
ping
ping
是 ICMP 的一个经典应用,主要用来测试两台主机之间的连通性,通过 ICMP 的 Echo 的报文来确定:
- 目标设备是否可达
- 与远程主机的通信往返延迟 RTT
- 数据报文的丢失情况 (丢包)
实现原理
ping
命令的实现原理: 通过向目标主机发送 ICMP Echo 请求报文,目的主机收到之后会发送 Echo 回答报文,ping 会根据时间和成功响应的次数估算出数据包往返时间以及丢包率。
具体来说,ping
命令程序通过在 ICMP
报文中存放发送请求的时间来计算往返时间,当收到应答时 (应答数据报中会将请求时间带回来),再使用当前时间减去存放在 ICMP
报文中请求时间,就得到了往返时间。
Wireshark 抓包
先启动 Wireshark 软件,然后执行 ping 命令。
# 发送 5 次 ping 请求
$ ping -c 5 dbwu.tech
# 本机 IP: 192.168.3.200
# 应答如下
PING dbwu.tech (104.21.71.166) 56(84) bytes of data.
64 bytes from 104.21.71.166 (104.21.71.166): icmp_seq=1 ttl=53 time=168 ms
64 bytes from 104.21.71.166 (104.21.71.166): icmp_seq=2 ttl=53 time=166 ms
64 bytes from 104.21.71.166 (104.21.71.166): icmp_seq=3 ttl=53 time=166 ms
64 bytes from 104.21.71.166 (104.21.71.166): icmp_seq=4 ttl=53 time=169 ms
64 bytes from 104.21.71.166 (104.21.71.166): icmp_seq=5 ttl=53 time=169 ms
--- dbwu.tech ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 8594ms
rtt min/avg/max/mdev = 166.858/168.176/169.432/1.153 ms
在 Wireshark 软件操作界面中通过 icmp 关键字,将对应的数据包过滤出来:
通过对比 ping 命令的输出和 Wireshark 的抓包数据,可以看到请求和应答顺序都是可以一一对应的。
在 ICMP 请求抓包数据中,数据报的 Type 字段为 8, 表示这是一条 ICMP Echo 请求报文。
在 ICMP 应答抓包数据中,数据报的 Type 字段为 0, 表示这是一条 ICMP Reply 应答报文。
TTL 字段输出说明
在 ping 命令的输出中,有一个 ttl 字段,使用初始 ttl 值减去应答 ttl 值,表明 ICMP 报文经过的路由器数量。
根据具体协议栈的实现,ping 命令执行的 ICMP 报文一般采用的 ttl 初始值分别为 32、64、128、255 中任意一个,不会使用其它值,如果应答 ttl 值和初始 ttl 值一样,说明通信的双方都在同一广播域 (局域网),没有经过任何路由器。
下面举个例子来说明,从本机 ping 家庭路由器的 IP 地址。
# 本机 IP: 192.168.3.200
$ ping 192.168.3.1
# 应答如下
PING 192.168.3.1 (192.168.3.1): 56 data bytes
64 bytes from 192.168.3.1: icmp_seq=0 ttl=64 time=26.104 ms
64 bytes from 192.168.3.1: icmp_seq=1 ttl=64 time=6.658 ms
64 bytes from 192.168.3.1: icmp_seq=2 ttl=64 time=22.854 ms
^C
--- 192.168.3.1 ping statistics ---
4 packets transmitted, 4 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 4.046/14.915/26.104/9.676 ms
从输出结果中可以看到,ttl 的值没有发生任何变化,因为是局域网内部通信。
丢包
使用 ping
命令时,如果发生丢包,输出序列中的 icmp_seq
就不再是连续的。
$ ping dbwu.tech
64 bytes from (172.67.147.110): icmp_seq=0. time=660. ms
64 bytes from (172.67.147.110): icmp_seq=5. time=1780. ms
64 bytes from (172.67.147.110): icmp_seq=7. time=380. ms
64 bytes from (172.67.147.110): icmp_seq=8. time=420. ms
64 bytes from (172.67.147.110): icmp_seq=9. time=390. ms
64 bytes from (172.67.147.110): icmp_seq=14. time=110. ms
64 bytes from (172.67.147.110): icmp_seq=15. time=170. ms
64 bytes from (172.67.147.110): icmp_seq=16. time=100. ms
^? type interrupt key to stop
----vangogh.CS.Berkeley.EDU PING Statistics----
17 packets transmitted, 8 packets received, 52% packet loss
round-trip (ms) min/avg/max = 100/501/1780
ˆ
面试题
❓ 客户端可以正常连接到服务端?但是 ping
不通服务端的 IP 地址,为什么?
这属于正常现象,一般是因为服务端的防火墙屏蔽了 ICMP
协议,数据包被防火墙直接丢掉了。
traceroute
traceroute 是 ICMP 的另一个经典应用,用来跟踪一个数据包从源设备到目标设备的传输信息,通常用于诊断网络问题和查找数据包传输路径。
实现原理
traceroute 不仅支持 ICMP 协议,还支持 TCP, UDP 协议,本文使用 ICMP 来说明其工作流程和抓包实验,其他协议的工作原理也是类似的,感兴趣的读者可以自行尝试。
- 发送 ICMP 数据包: 源设备向目标设备发送一系列 ICMP 报文,初始 TTL (Time To Live) 设置为 1 (默认情况下,单个 TTL 值会发送 3 个相同的包)
- 路由器处理: 第一个路由器收到 ICMP 报文后,检测 TTL, TTL 等于 1, 然后将其减 1, TTL 等于 0, 路由器将数据包丢掉,然后发送 ICMP 超时 (Time Exceeded) 报文给源设备
- 累计处理:源设备收到 ICMP 超时报文之后,将 TTL 设置为 2, 然后发送新的 ICMP 报文
- 路由器再次处理: 第一个路由器收到 ICMP 报文后,检测 TTL, TTL 等于 2, 然后将其减 1, TTL 等于 1, 接着转发到第二个路由器,第二个路由器收到 ICMP 报文后,检测 TTL, TTL 等于 1, 然后将其减 1, TTL 等于 0, 第二个路由器将数据包丢掉,然后发送 ICMP 超时 (Time Exceeded) 报文给源设备
- 重复第 2 - 4 步,直到数据包到达目标设备 (这时目标设备会发送 ICMP (端口不可达) 应答报文给源设备) 或者到达路由最大跳跃数量 (通常为 30)
- 这时源设备就可以知道: 到达目标设备所经过的路由器 IP 地址以及每个路由器的往返时间
我们可以将 traceroute 命令的整个执行策略概括为: 步步为营,每次向前走一步,直到到达目标或者发生错误,通过命令输出的数据包传输路径,可以很快找到网络故障的位置。
Wireshark 抓包
先启动 Wireshark 软件,然后执行 traceroute(路由追踪)的原理及实现 命令。
# 指定使用 ICMP 协议
# 因为很多 Linux 系统默认使用 UDP 协议
$ traceroute --icmp dbwu.tech
# 本机 IP: 192.168.3.200
# 应答如下
traceroute to dbwu.tech (172.67.147.110), 30 hops max, 60 byte packets
1 DESKTOP-IU99KUS (172.26.208.1) 0.217 ms 0.176 ms 0.187 ms
2 192.168.3.1 (192.168.3.1) 6.730 ms 6.728 ms 6.726 ms
3 100.64.0.1 (100.64.0.1) 10.613 ms 10.611 ms 10.608 ms
4 * * 10.224.23.13 (10.224.23.13) 10.591 ms
5 * * *
6 * * *
7 * * *
8 * 202.97.54.54 (202.97.54.54) 31.306 ms 31.293 ms
9 202.97.83.162 (202.97.83.162) 179.562 ms 179.560 ms 179.558 ms
10 * * *
11 172.70.94.2 (172.70.94.2) 172.700 ms 172.696 ms 172.693 ms
...
...
...
在 Wireshark 软件操作界面中通过 icmp 关键字,将对应的数据包过滤出来:
从抓包到数据包可以看到,每发送 3 个 ICMP 包,TTL 的值会自动加 1, 与 ICMP 请求报文对应的,就是传输过程中的中间设备返回的 ICMP 超时应答 (图中背景为黑色的部分)。
可以从中间挑几个数据包,看看对应的 TTL 值:
隐藏路由器
数据包在传输过程中,有的路由器会隐藏自己的位置,不让 ICMP Timeout 的消息通过,或者 某次探查没有获得响应,或者响应丢失,结果就是 traceroute 的结果输出中,在对应的路由器的那一跳上始终会显示 *
号。
还有一种情况,某些路由器会错误地转发 TTL 为 0 的数据报,此时该路由器的下一跳路由器,就会直接丢弃数据,并返回 ICMP 传输超时
报文。
# 指定使用 TCP 协议
$ traceroute --tcp -p 80 -n google.com
traceroute to google.com (93.46.8.90), 30 hops max, 60 byte packets
1 172.26.208.1 0.332 ms 0.315 ms 0.308 ms
2 192.168.3.1 3.925 ms 3.919 ms 3.914 ms
3 * * *
4 * * *
5 * * *
6 * * *
7 202.97.53.142 26.462 ms 202.97.94.198 25.753 ms *
8 * * *
9 202.97.40.70 184.716 ms 202.97.55.150 184.693 ms 202.97.40.70 184.685 ms
10 * * *
...
30 * * *
完整版抓包和截图
下面给一个完整的 traceroute 命令输出和 Wireshark 抓包截图。
注意: 域名 dbwu.tech
解析到了多个 IP 地址,所以输出结果中的网络链路会和上面的不一样。
# 指定使用 ICMP 协议
$ traceroute --icmp dbwu.tech
# 本机 IP: 192.168.3.200
# 应答如下
traceroute to dbwu.tech (104.21.71.166), 64 hops max, 72 byte packets
1 192.168.3.1 (192.168.3.1) 4.991 ms 4.030 ms 5.451 ms
2 100.64.0.1 (100.64.0.1) 15.547 ms 9.119 ms 18.130 ms
3 * 10.224.23.1 (10.224.23.1) 21.869 ms 12.968 ms
4 * * *
5 10.255.51.5 (10.255.51.5) 14.064 ms 7.399 ms 5.838 ms
6 * * 202.97.106.1 (202.97.106.1) 28.820 ms
7 * * *
8 * 202.97.54.38 (202.97.54.38) 27.518 ms *
9 202.97.83.162 (202.97.83.162) 171.627 ms 167.289 ms 168.189 ms
10 81.173.16.34 (81.173.16.34) 168.333 ms 200.227 ms 165.401 ms
11 172.69.143.2 (172.69.143.2) 166.677 ms 166.071 ms 166.491 ms
12 104.21.71.166 (104.21.71.166) 167.848 ms 165.138 ms 171.294 ms
在 Wireshark 软件操作界面中通过 icmp 关键字,将对应的数据包过滤出来:
从截图中可以非常清晰地看到,ICMP 报文中 TTL 值的变化情况,当 TTL 到达 12 时 (经过 12 跳),终于到达目标主机。
需要注意的是: 即使第一次已经收到目标主机的 ICMP 应答报文,源主机依然会再发送两个 ICMP 请求报文 (默认凑够 3 次),读者可以观察一下最后 3 个 ICMP 请求和应答报文中的 TTL 值,是否发现了什么有趣的东西 :-)
ICMP 洪泛攻击
ICMP 协议没有验证机制,所以很容易被用于进行网络攻击,目前最常见的是 ICMP 洪泛攻击: 攻击者在短时间内向目标设备发送大量的 ICMP 虚假报文,导致设备忙于应答虚假报文,无法应答正常报文。
ICMP 泛洪攻击具体可分为带宽 DOoS 攻击和端口扫描攻击(针对连接的 DOoS 攻击)两类。
带宽 DOoS 攻击
攻击者发送大量伪造的 ICMP Echo 请求报文,交换机、路由器等网络设备的 CPU 需要响应这种报文,会占用大量的带宽和 CPU 资源,这种 DOoS 攻击和其他 DOoS 攻击一样,消耗设备的资源而使得设备无法提供正常服务。
连接 DOoS 攻击
攻击者发送大量的端口扫描报文,交换机需要回应大量的 ICMP 目标不可达报文,这种攻击既消耗系统的资源,同时攻击者能够很轻易获得设备对外开放的端口,然后可以针对这些端口进行攻击,可以影响所有 IP 设备的网络连接。