1 ICMP协议概述
ICMP(Internet Control Message Protocol)协议是因特网控制报文协议,ICMP常被认为是网络层协议,它的报文存在于IP数据报的数据部分,如图。
因为ICMP是基于IP数据报的,所以跟TCP不同的是,它是不需要指定端口的,更没有建立连接一说。而且,通常来说ICMP协议都是内核帮你实现的,系统自身就支持了,并不像TCP/HTTP等还要自己开个服务监听对应端口啥的。 可能有人会有疑问了,既然没有端口来标识了,那我有时候开多个ping进程,这些响应消息是怎么对应到不同的ping进程的? 这个就是ICMP报文里面的标识符的作用了。标识符会在响应中带回来,这样发送方就能根据标识符将请求和应答匹配了。在ping中,这个标识符就是进程ID。
ICMP报文有多种类型,如地址掩码请求和应答、时间戳请求和应答、请求回显和回显应答等。ICMP报文通用格式如下,不同类型的报文内容有所不同。ICMP协议在 ping,traceroute等工具中有典型应用,下面都分析一下。
2 Ping 原理分析
ping使用的是 ICMP 的请求回显/回显应答类型的报文,格式如下。它的内容包括标识符、序列号以及回显数据3部分,报文大小默认为 64 字节(header的8字节+body的56字节)。
- 请求回显类型是8,回显应答类型是0,他们代码都是 0,校验和是包内容根据算法生成用于校验数据完整性。
- 标识符在 Linux/macOS 中用的是进程ID。
- 序列号在 Linux/macOS 中是从0递增的,每个进程独立的。
- 回显数据包括发送ping请求的时间戳(在macOS占8字节,在Linux占16字节),以及一串填充数据,在Linux这串数字默认是0x10...37,共40字节。在macOS中是0x08090a...37,共48字节。填充数据你也可以通过
-p pattern
指定,比如ping -pff 192.168.33.10
,则填充数据全部是 ff。 - 默认TTL是64,你可以通过
-t ttl
指定TTL值。 - ping请求时间 = 接收到回显应答的时间 - 应答回显数据中的时间
实例分析
在测试机ping我的虚拟机 ping -c2 192.168.33.10
,192.168.33.10是我测试用的虚拟机IP,wireshark抓包如下:
可以验证前面的分析。第2对请求和应答跟第一对类似,只是序列号,校验和等不同罢了。
关于校验和
ICMP报文头部中的校验和生成/校验方式也比较简单。
- 生成:先将校验和置为0,然后将ICMP报文的header+body按16bit分组求和。如果结果溢出,则将高16位和低16位求和,直到高16位为0。最后求反就是检验和的值。
- 校验:将报文的header+body按16bit分组求和(包括校验和字段),看看结果是否全是1,如果不是,则校验失败。
如何自己写一个ping?可以参考下这位朋友的ping工具的 python实现。 Lingerhk: icmp_ping_tool.py
3 Traceroute 原理分析
traceroute 用于查看IP数据报从一台主机传到另一台主机所经过的路由。其实,在IP数据报的头部的选项字段有一个 IP记录路由选项(RR),它也可以记录路由。为什么不直接用它而是另外弄出个traceroute工具,这是因为:
- 1)IP首部长度限制,导致记录的IP地址最多9个 ,远远不够。
- 2)并不是所有路由器都支持记录路由选项,因此某些路径无法使用。
traceroute 用到ICMP协议和TTL字段。TTL字段是数据报的生存周期,初始值通常默认是64,每个处理数据报的路由器都需要把TTL值减去1或者数据报在路由器停留的秒数(因为绝大多数路由器转发数据报时延都小于1秒,因此通常都是减去1,而且很多路由器的实现即便超过1秒也是减去1,因此可以把TTL看做一个跳站计数器)。路由器接收到一份IP数据报时,如果TTL为0或者1,则路由器不转发该数据报,而是丢弃并给源机器发送一份ICMP超时报文,而ICMP信息中的IP报文中源地址正是路由器的IP地址。
traceroute的原理就是:
- 先发送一份TTL为1的报文,这样第一个路由器会将TTL减1然后丢弃该报文,并发回一个ICMP超时报文,这样就得到了第一个路由器的IP地址;接着发送一个TTL为2的报文,可以得到第二个路由器的IP地址;继续该过程直到目的主机。
- 但是目的主机即便收到TTL为1的报文,它也不会丢弃该数据报并发回一份ICMP超时报文,因为此时数据报已经到了目的地。为了判断是否到达目的主机,traceroute发送的报文采用了UDP数据报,它选择一个很大的端口值如30000以上的,以保证没有其他应用程序使用该端口,然后目的主机会返回一个端口不可达的ICMP报文,这样就可以知道什么时候结束。
- traceroute针对每个TTL会发3次UDP报文,并打印每次的往返时间。如果5秒内没有收到3次报文中任何一个的响应,则打印*号继续下一个TLL的报文发送。3次报文选择的UDP目的端口分别是 33435,33436,33437,UDP报文数据长度为24字节,内容为全0(macOS环境)。
实例分析
运行 traceroute 119.75.217.109
,可以看到wireshark抓包的前几跳信息,TTL最开始是1,然后是2...,端口是33435到33437,每个TTL发3次报文,在没有达到目的主机前,返回的是ICMP超时报文。到达主机后,则会返回ICMP端口不可达报文。
由于IP路由通常都是动态的,每个路由器都要判断数据报接下来要转发到哪个路由器,应用程序对路由策略并不控制。而traceroute程序的IP源站选路选项(-g gateway
)可以实现发送者指定路由,比如指定必须经过哪些路由IP,这里就不展开了。有兴趣的可以参见 《TCP/IP详解 卷1:协议》的第8章。
参考资料
- 《TCP/IP详解 卷1:协议》第6、7、8章
- https://zh.scribd.com/doc/7074846/ICMP-and-Checksum-Calc(注:本文中引用的ICMP报文结构图都来自这里)
- https://linux.die.net/man/8/ping
- https://github.com/Lingerhk/hacking_script/blob/master/net_attacking/icmp_ping_tool.py