IP协议本身不支持发现发往目的地地址失败的IP数据包,也没有提供直接的方式获取诊断信息,比如在发送途中,经过了哪些路由器,以及往返时间。
为此,就有了ICMP协议
(Internet Control Message Protocol
,ICMP
)专门来负责这些事情。
ICMP并不为IP网络提供可靠性,它只是用于反馈各种故障和配置信息。丢包不会触发ICMP。
ICMP是RFC 792中定义的Internet协议套件的一部分。ICMP消息通常用于诊断网络或探测网络目的,或者是为了响应IP操作中的错误而生成(如RFC 1122中所指定),ICMP错误响应给原始数据包的源IP地址。
但是黑客经常用ICMP来做坏事,于是网络管理员可能会用防火墙阻止掉ICMP报文,这样的话,很多ping、traceroute之类的诊断程序就无法正常工作了。
1、格式
ICMP报文是在IP数据报内部传输的,格式如下:
而ICMP报文的格式如下:
其中:
- 类型有15个不同的值,描述特定类型的ICMP报文;
- 某些ICMP报文还是用代码字段的值来进一步描述不同的条件;
- 校验和字段用于ICMP报文的差错检查。
以下是常见的差错报文类型:
类型 | 代码 | 描述 | 查询 | 差错 |
---|---|---|---|---|
0 | 0 | 回显应答(ping应答) | √ | |
3 | 目标不可达 | |||
0 | 网络不可达 | √ | ||
1 | 主机不可达 | √ | ||
2 | 协议不可达 | √ | ||
3 | 端口不可达 | √ | ||
4 | 需要进行分片但设置了不分片比特 | √ | ||
5 | 源站选路失败 | √ | ||
6 | 目的网络不认识 | √ | ||
7 | 目的主机不认识 | √ | ||
8 | 源主机被隔离(作废不用) | √ | ||
9 | 目的网络被强制禁止 | √ | ||
10 | 目的主机被强制禁止 | √ | ||
11 | 由于服务类型 T O S , 网 络 不 可 达 | √ | ||
12 | 由于服务类型 T O S , 主 机 不 可 达 | √ | ||
13 | 由于过滤,通信被强制禁止 | √ | ||
14 | 主机越权 | √ | ||
15 | 优先权中止生效 | √ | ||
4 | 0 | 源端抑制 | √ | |
5 | 重定向 | √ | ||
0 | 对网络重定向 | √ | ||
1 | 对网络重定向 | √ | ||
2 | 对服务类型和网络重定向 | √ | ||
3 | 对服务类型和主机重定向 | √ | ||
8 | 0 | 回显请求(ping) | √ | |
9 | 0 | 路由器通告 | √ | |
10 | 0 | 路由器请求 | √ | |
11 | 超时 | |||
0 | 传输期间生存时间为0(Traceroute) | √ | ||
1 | 在数据报组装期间生存时间为0 | √ | ||
12 | 参数问题 | |||
0 | 坏的 I P 首部(包括各种差错) | √ | ||
1 | 缺少必需的选项 | √ | ||
13 | 0 | 时间戳请求 | √ | |
14 | 0 | 时间戳应答 | √ | |
15 | 0 | 信息请求 | √ | |
16 | 0 | 信息应答 | √ | |
17 | 0 | 地址掩码请求 | √ | |
18 | 0 | 地址掩码应答 | √ |
其中,最常用的类型是8:回显请求(ping),以及0:回显应答(ping应答)。
2、查询报文
查询报文是有关信息采集和配置的ICMP报文。
我们经常用到的ping程序就用到了ICMP查询报文。
ping程序
ping程序会发送一份ICMP回显请求给主机,并等待返回ICMP回显应答。
ping程序ping不通了,就不能访问对应的主机了吗?
我们知道,网络管理员可能会用防火墙阻止掉ICMP报文的,这样我们可能就ping不通了,但是主机的可达性不能只取决于IP层是否可达,还与端口号和协议有关,而ping是运行在网络层的,用于测试网络连接状态和信息包发送接收状况,即使ping不通,我们也可能用telnet远程登录到主机的其他端口,如25号端口。
ping程序用到了回显请求和回显应答报文,报文格式如下:
Unix系统实现ping程序时,把ICMP报文的标识符设置为进程ID,在进程内,序号从0开始,每发送一次新的 回显请求就加1,这样就可以同时运行多个ping进程了。
ping程序的端口号是什么?
端口号是传输层的东西,ping程序是使用ICMP协议,直接跳过了传输层,所以呢,ping程序是没有所谓的端口号的。
我们发送一个ping请求,数据在协议栈中的处理流程如下:
- A主机的ping应用程序向服务器发起回显请求,说了一句:hi
- 直接传输到网络层的ICMP协议,进行ICMP数据封装:
- 8表示回显请求,112是发起请求的进程号,1表示请求序号
- IP协议拿到数据后进一步加上IP头,加上自己的IP和目标IP,传输给数据链路层;
- 数据链路层拿到IP数据包,准备封装成帧,这个时候会去寻找目标IP的MAC地址,如果在A主机的ARP映射表找到了IP的MAC地址,那就直接拿来用了,否则会发起一个ARP广播请求,获取到MAC地址。至于跨网关这种ping,会多了转发的功能更,后面会进行介绍。最终数据链路层封装成数据帧,从网络接口发出去;
- 服务器拿到数据帧之后,拿到MAC头,判断MAC地址是自己的,就基于拿到Frame data,按首部协议传给对应的模块,即IP模块;
- IP模块拿到数据,判断到IP跟自己对上了,于是继续拿到IP data,传输给ICMP协议,ICMP协议收到消息,准备应答:
- 0表示回显应答
- 然后按照同样的流程,把数据包发送回A主机。
可以发现,ping程序是直接用到了网络层的ICMP协议,不经过传输层。
是什么原因导致ping失败了?
ping失败的原因有很多:
- 可能是输错了IP;
- 可能是网络配置不正确,如错误的子网掩码;
- 可能有防火墙软件组织了ping;
- 可能是硬件故障,如损坏了的以太网适配器,电缆,路由器,集线器等。
如果叫你自己实现一个ping程序,你会怎么做呢?
提示:为了能处理ICMP网络报文,我们需要用到原始套接字(SOCK_RAW),而不是SOCK_STREAM或者SOCK_DGRAM套接字。
更多提示:Homework 6: A raw socket ping tool[1],思路都在这里了,大家动手做一做,然后就可以有直接操作网络层的工作经验了。😏
3、差错报文
差错报文是有关IP数据报传递的ICMP报文。要是发送IP数据报中途产生了异常,那么就会响应ICMP差错报文。
但是不是所有情况都会响应ICMP差错报文,如以下场景:
- ICMP差错报文不会产生另一个ICMP差错报文;
- 目的地址是广播地址或者多播地址的IP数据报不会产生差错报文;
- 作为链路层广播的数据报不会产生差错报文;
- 源地址不是单个主机(源地址为零地址、环回地址、广播地址或者多波地址)的数据报不会产生差错报文;
为什么要这些规则呢?假如允许ICMP差错报文对广播分组响应,那么就会导致广播风暴了。
下面我们举一个ICMP差错报文的例子来说明下。
目标不可达
上面的表格我们了解到,如果类型为三则表示目标不可达,而根据具体的代码可以进一步划分:
类型 | 代码 | 描述 | 查询 | 差错 |
---|---|---|---|---|
3 | 目标不可达 | |||
0 | 网络不可达 | √ | ||
1 | 主机不可达 | √ | ||
2 | 协议不可达 | √ | ||
3 | 端口不可达 | √ | ||
4 | 需要进行分片但设置了不分片比特 | √ |
下面我们看一个端口不可达的例子来演示下ICMP差错报文附加的信息。
ICMP端口不可达案例
这里我们演示通过tftp访问一个不存在的端口号,查看其返回的ICMP响应差错报文。tftp应用在传输层是通过UDP来进行传输数据的
下面我们tftp请求之前先开启tcpdump抓包:
1 | sudo tcpdump -i en0 -nn host 我的IP and 目标IP |
然后执行tftp命令:
1 | tftp |
可以发现,在等待了大约25秒之后,最终输出:Transfer timed out.
观察tcpdump抓包日志:
1 | 16:51:16.739632 IP 我的IP.64517 > 目标IP.8090: UDP, length 20 |
可以发现这里执行了五次UDP请求,每次请求都响应了一个ICMP包,为udp port 8090 unreachable
端口不可达,产生了ICMP不可达报文,该报文一般格式如下:
为什么需要返回IP首部
:因为IP首部包含了协议字段,使得ICMP知道如何解释后面的8个字节;
为什么需要原始IP数据报中数据的前8个字节
:因为这里面包含了源端口和目的端口。
不过看起来我的电脑好像忽略了ICMP报文,还是继续重试了4次。
注意:ICMP报文是在主机之间交换的,网络层的协议,不需要端口号,而以上20个字节的UDP数据报是包含了源端口号和目标端口号信息的。
为什么TFTP客户程序会继续重发呢?
因为网络编程中,BSD系统不把从socket接收到的ICMP报文中的UDP数据通知用户进程,除非该进程以及发送了一个connect命令给该接口。标准的BSDTFTP客户程序并不发送connect命令,所以它永远也不会受到ICMP差错报文的通知。
traceroute程序
traceroute工具用于确定从发送者到目的地路径上的路由器。
traceroute主要是通过故意设置特殊的TTL,来达到追踪目的地路径上的路由器的功能。
TTL运行原理
TTL:是 Time To Live的缩写,该字段指定IP包被路由器丢弃之前允许通过的最大网段数量。每经过一个路由器,TTL就会减一,然后再把IP包转发出去,如果TTL减到0了,路由器就会丢弃收到的TTL=0的IP包,并向IP包的发送者发送一个ICMP差错报文,类型为11,代码为0:传输期间生存时间为0。
第一轮,traceroute设置TTL值为1,那么遇到第一个路由就返回ICMP容错报文了,下一轮,TTL设置为2…这样依次增加。最终就把整个链路的路由器都试出来了。
当然,有点路由器不会回整个ICMP,这也是为什么你去traceroute一个公网地址,看不到中间路由的原因。
除此之外,traceroute也可以通过不设置分片,来确定传输链路的MTU(Maximum Transmission Unit, 最大传输单元):首先发送一个分组的长度正好与出口MTU相等,如果中间遇到窄点的关口,就被卡主了,这个时候会接收到一个ICMP差错报文,然后调小分组长度重试…