ICMP 协议简介
ICMP(Internet Control Message Protocol)因特网控制报文协议。它是IPv4协议族中的一个子协议,用于IP主机、路由器之间传递控制消息。控制消息是在网络通不通、主机是否可达、路由是否可用等网络本身的消息。这些控制消息虽然不传输用户数据,但是对于用户数据的传递起着重要的作用。
ICMP协议与ARP协议不同,ICMP靠IP协议来完成任务,所以ICMP报文中要封装IP头部。它与传输层协议(如TCP和UDP)的目的不同,一般不用来在端系统之间传送数据,不被用户网络程序直接使用,除了想Ping和 Tracert 这样的诊断程序。
下面是ICMP协议被使用的一个实例:
1、ICMP 可以指示网络出错的原因
2、ICMP 协议自身不能解决网络出错,只是指示错误
ICMP 报头格式
ICMP报文包含在IP数据报中,IP报头在ICMP报文的最前面。一个ICMP报文包括IP报头(至少20字节)、ICMP报头(至少八字节)和ICMP报文(属于ICMP报文的数据部分)。当IP报头中的协议字段值为1时,就说明这是一个ICMP报文。ICMP报头如下图所示。
ICMP 报文类型
不同报文类型由报文中的类型字段和代码字段来共同决定。
图中的最后两列表明 I C M P报文是查询报文还是差错报文
当发送一份ICMP差错报文时,报文始终包含IP的首部和产生ICMP差错报文的IP数据报的前8个字节。这样,接收ICMP差错报文的模块就会把它与某个特定协议(根据IP数据报首部中的协议字段来判断)和用户进程(根据包含在IP报前8个字节中的TCP或UDP报文首部中的TCP或UDP端口号来判断)联系起来。
ICMP报告无法传送的数据报的错误,并帮助对这些错误进行疑难解答。例如,如果IPv4不能讲数据报传送到目标主机,则路由器上的或目标主机上的ICMP会向主机发送一条“无法到达目标”消息。下表为最常见的ICMP消息。
其中无法到达目标消息中可以细分为一下几项
ICMP协议只是试图报告错误,并对特定的情况提供反馈,但最终并没有使IPv4成为一个可靠的协议。ICMP消息是以未确认的IPv4数据报传送的,它们自己也不可靠。
ICMP差错报文实例1—访问一个网络中不存在的路由
在路由器R1上Ping 172.16.32.1,由于路由器R1上存在着去往172.16.32.0 子网的静态路由,所以Ping数据包会被发送到路由器R2上,而在R2上没有去往172.16.32.0的子网路由条目,所以R2路由器就会发送ICMP的差错报文告诉路由器R1。R1的路由解析从R2路由器返回的响应报文,即可知道不可达的原因。在R1路由器上的Ping测试的结构如下图。
通过Wireshark抓取到的报文如下:
可以看到172.16.12.2给172.16.12.1发送了ICMP的差错报文。报文的类型值为3,代码值为1,表示为主机不可达,也就是,路由器R2告诉路由器R1主机不可达。
ICMP差错报文实例2—访问一个路由存在但IP规则被禁止
同样在路由器R1上Ping 172.16.32.1,请求数据包到达路由器R2后,R2路由器存在去往172.16.32.0子网的路由,但IP的访问规则限制了对172.16.32.0子网的访问。在路由器R1上Ping测试结果及Wireshark抓包如下图。
可以看到172.16.12.2给172.16.12.1发送了ICMP的差错报文。报文的类型值为3,代码值为13,表示通信被过滤掉了。
ICMP差错报文实例3—传输超过接口的MTU值的数据
路由器R1和路由器R2上都存在着去往172.16.23.0子网的路由条目。在路由器R1上Ping路由器R3的Fa 0/0 接口的IP地址172.16.23.1,Ping带的数据1200个字节且不允许分包处理。如下图所示。
由于路由器R2的接口Fa 0/0 被设置最大允许的MTU值为1000,所以ICMP的Ping的1200字节无法被直接转发,需要被分包。但Ping请求的报文中却不允许分包的情况。此时,就会由路由器R2发送ICMP的差错报文给路由器R1。Wireshark抓取的报文帧如下图所示。
详细查看ICMP的差错报文帧如下图
此时,ICMP的差错报文的报文类型值为3,代码值为4,表示为需要进行分片,但设置了不分片的比特位DF,也就是,路由器R2告诉路由器R1需要分片但设置了不分片的位而出错。
ICMP差错报文实例4—UDP协议端口不可达
在路由器R1上通过TFTP协议传输文件到路由器R3上。
TFTP协议是基于UDP的传输协议进行传输,而在路由器R3上没有开启TFTP协议,所以路由器R3向路由器R2发送ICMP的差错报文,差错报文的内容是指示R3的TFTP协议的端口不可达。R2接收到这个差错报文后,又将此报文转发给了路由器R1。通过Wireshark抓包获取的报文帧如下图所示。
双击ICMP的差错报文,可以看到此报文帧的详细信息如下:
从上图可以看到ICMP的差错报文的类型值为3,代码值为3,指示为端口不可达。对于采用UDP传输协议传输的应用层协议,到对端端口没有开启时,都会以ICMP的差错报文来告知请求端。在ICMP的报文消息的数据部分封装有请求是的IP报文头和UDP的报文头。对于请求方通过IP的报文头和UDP报文头的组合就可以确认是到目的的那个协议请求的端口不可达。
ICMP差错报文实例5—ICMP TTL超时
在路由器R1上Ping 1.1.1.1 如下图所示:
ICMP的请求报文被发送到路由器R2上,R2再把ICMP请求报文转发给路由器R3,路由器R3再把ICMP请求转发给路由器R2,此时形成了路由环路,ICMP的请求报文再路由器R2和路由器R3之间来回转发。但每次经过一次路由器,ICMP请求报文帧的TTL字段值就会减1,直到TTL字段的值减为0时,路由器就会发送ICMP的差错报文用来指示TTL超时。如下图所示。
双击上图中的ICMP差错报文,报文的详细内容如下图。
从上图可以看到ICMP的差错报文的类型值为11,代码值为0,指示报文的生命周期为0。
Ping 命令介绍
Ping命令用于进行通信的主机或路由器之间,判断所发送的数据包是否已经成功到达对端的一种消息。可以向对端主机发送请求的消息(ICMP Echo Request Message,类型8),也可以接收对端主机发回来的回显应答消息(ICMP Echo Reply Message,类型0)。网络上最常用的Ping命令,就是利用这个消息实现的。Echo Request 与 Echo Reply为经典的查询报文!
Python 实现ICMP协议
Python先安装Scapy模块,然后利用Scapy提供函数功能来实现ICMP请求报文帧的铸造。
Scapy 铸造ICMP请求数据包的方法:
pkt = Ether()/IP()/ICMP()/”Hello”
pkt.show()可以查看铸造的包的信息:
铸造好的ICMP请求包可以通过sendp()函数或send()函数发送。sendp()函数在第二层次上发送数据包,需要给定正确的网卡接口;send()函数在第三层次上发送数据包,根据本地的路由表来进行路由发送。
下面我们通过sendp()函数来发送一个构造好的ICMP回显请求报文,其中ICMP()括号中不添加任何参数,默认情况下为 type字段的值为8,code字段的值为0, 也就是回显请求报文。在第二层次上发送要指定网卡接口, 这里使用ens37(因为测试使用的是CentOS7的操作系统)。成功发送一个数据包。如果想要循环发送,可以使用loop选项。
sendp(Ether()/IP(dst="192.168.10.101")/ICMP()/"hello", iface="ens37“, loop=1)
代码执行并同时进行Wireshark抓包,可以抓取到报文如下图。
如果在网络上连接有192.168.10.101的设备的话,192.168.10.101的设备会对此ICMP回显请求进行应答。回显应答的报文帧结构如下图所示。
如果希望不仅能够发送ICMP 回显请求报文,还需要对ICMP回显应答的报文帧进行接收的话,则可以使用Scapy模块中的sr()函数来执行(此时sr()为三层发送接收函数,而srp()函数为二层发送接收函数)。这里我们使用三层的发送接收函,代码执行发送接收(三层发送就无需添加Ether()参数),如下图。
成功发送了一个数据包,接收到了4个数据包,其中1个为答复包。这是我要集中注意的地方。将收到的数据赋值给自定义的变量,并查看。
ans为应答包的接收变量;unans为未应答的接收变量。
ans[0]中包括了请求包和应答包。其中ans[0][1]为应答包
Python 使用 Scapy 实现ICMP扫描指定主机的存活状态
Python 代码如下:
Python 使用 Scapy 实现ICMP扫描指定网段存活的主机
Python 代码如下:
通过Python对ICMP协议的编程实现,更加深了对ICMP协议的理解和认识。