从良心上说,把这个放在这一节是有点分类不当的,但是ICMP有个端口不可达的错误消息,放在网络层感觉也差那么点意思,不如就这样吧,乱有乱的道理。
PING的起源
对于每个计算机专业的人一定听过这句话,"xxx连不上了,那你先ping下试试”,虽然很大时候接着的一句话就是更常见的“那你重启下试试”,但是ping这个所有主流操作系统基本都自带的小程序却依然蕴含着计算机网络里面一个重要的协议ICMP。
PING这个名称最初来自于这个程序的编写者,Mike Muuss,因为这个程序的目的是探测网路是否通畅或者出问题了其根源在哪里,而且本质上是向目的主机发送一个ICMP echo的数据包。这就和声纳很像,潜水艇利用声纳来辨别物体,所以就用了这么个拟声词,ping。不过也有另一位教授David L. Mills, 是对Mike的PING程序做出comment的另一个大牛。提出过另一个取名,叫Packet Internet Grouper/Goupher。意思为一个带着因特网数据包的地鼠,脑补一下钻来钻去的画面,感觉还是很形象的。不过Mike自己说了,他不太喜欢Davie教授的这个名字,并且还吐槽了一下zheng fu。Mike Muuss的PING程序只有千多行代码,并且按照他自己的说的他一晚上就写好了,确实是一个大牛级的人物。具体更多的内容可以搜索“the story of ping program”,是Mike Muuss自己亲自写的喔,而且,只有两页。
现在能见到linux版本里的ping程序比最开始的程序复杂很多了,作者是Vasiliy Kulikov和Pavel KanKovsky,浓浓的东欧风味,都是计算机网路和安全方面的大牛。这个ping程序可以再net/ipv4/ping.c中找到,在我后面第二章写程序的部分会介绍利用raw socket来完成一个最简单的ping程序功能。
ICMP是啥
上面我也提过,ping程序实际上是用ICMP协议来实现的,那么很明显,我们首先知道啥是ICMP协议,不过为了不至于连续的文字太多,先来看看ping程序运行的样子。
首先映入眼帘一行啥具有32个字节的数据,这个在后面正好可以说明,接着是四行消息,虽然第一行是“请求超时”,但是后面三行都是来自xxx的回复,包括花了多少时间,TTL是多少,最后再来一个总结,一目了然。作为个人喜好来说,我是很讨厌那种过于在界面上做太多炫酷的效果的程序员,我觉得如果是日常开发中用的工具,界面的花哨并不重要,而对于重要信息的输出,排版才是最重要的。
伴随着这个程序的运行,来看一下抓包的效果,wireshark可以很方面的过滤掉数据包,只要在filter这一栏输入ip.addr 等于啥ip地址就可以找到你想要某个地址的数据包。从这个抓包结果上可以看到一共有7个条目。除了第一条没有回复,也及时“请求超时”,后面每一条都有来自于对端的回复,从IP地址中就可以看出来。这也符合我们上面ping程序的运行结果。
ICMP,学名Internet Control Message Protocol,号称互联网核心协议之一,依靠IP来实现其功能。但是他并不能像TCP/UDP这些工作在IP层次之上的协议被划分为传输层协议,因为它并不用于两个端点之间的传输,说人话的话可以理解为在它的包头里面并没有任何端口的信息。但是它需要IP来配合他的实现,他被直接封装在一个IP数据包里的Data中,也就是说在没有选项的情况下,他开始于IP数据包的第20个字节。ICMP报头很小,一共就8个字节,格式如下(这里画的ping使用ICMP数据包头格式,原因下面说明):
- 0-7: ICMP的报文类型,标识是哪一种ICMP报文
- 7-15:在类型的基础上,进一步标识到底是哪一种错误,使用一个1-31的数来标识
- 15-31: CRC,这个CRC包含ICMP报头和数据
上面4个字节的应该说是ICMP数据包的通用报头,虽然下面的4个字节也是报头部分,但是在不同ICMP数据包中却是不一样的,比如说在PING程序中,这两个字段分别是ID和Seq,用来标识回复的。
那么ICMP有多少种报文类型呢?换句话说,Type里面会有哪些值?我觉得可以分为8组吧,表中未黑体的都是我应该会涉及到的,如果想看所有的,请参考RFC 792。
Type值 | 含义 |
---|---|
0/8 | Echo Reply and Echo (Ping) |
3 | 端口不可达(TraceRoute) |
4 | Source Quench |
5 | 重定向消息 |
11 | 超时 (TraceRoute) |
12 | 指针错误 |
13/14 | 时间戳和时间戳回复 |
15/16 | 信息请求和回复 |
在ICMP报文之后就是数据部分了,根据不同的类型会搭载不同的数据。
PING的ICMP报文
PING程序使用的是ICMP的Echo和Echo Reply报文,从发送端发送的消息会把type填为0,然后填入一定量的数据,在ping程序中默认是32个随机的字节,那么有想法的骚年们可能发现了一个事情啊,既然能发送一串数据,那么我能不能用这个hack掉别人的机器呢?答案是可以的,不过难度很大,在目前这种计算机网络设计里面的更是几乎不可能了。但是在计算机网络发展的初期,有一种ping洪水攻击法,ping的程序的后面可以加一个参数来指定数据大小的,由于ICMP数据包是在IP包之内,而IP数据包包括头部一共可以搭载65,535个字节,除去IP头部和ICMP头部加起来的28个字节,就用很多台机器用最大的数据去ping某一个主机。如此大的负载,很容易某一个主机就会down机了,造成deny of service。但是现在已经有很多种方法来避免这一切了,比如拒绝收大于一定大小的IP包等等。
又一次回到老传统,让我们看看Wireshark抓到的包里是什么样子的,首先是发送端发送的echo request。
可以看到类型,code都是和我们介绍的是一样的,并且带有一个32个字节 数据,而在ID 和 序列号出现了两个,这是为什么?有两个序列号码?其实不是的,你看它括号里一个LE,一个BE,其实这是计算机本身硬件和网络的一个不太兼容的地方,后面在写程序的时候回作为重点之一来介绍。简单的说就是数据排布方式不同,LE就是Little Endian,BE 就是Big Endian,一个是从最低字节开始写数,一个从最高字节。你看就以ID的1来看,BE是0001,01在最后面,BE是0100,01在最前面。具体的概念和这两个词的来源后面会详细介绍,这里只要记住这不是有两个值就可以了。
然后再来看看回复:
这里的type是0,也是就是Echo reply,发送端是怎样知道是对自己的回复呢?可以看到ID序列号都是一样的,同时还计算出了回复时间,那可能要问了,这个时间怎么计算出来的?那么骚年们,你们可能被前面这么多的文字蒙蔽了,发送ping包的时候主机会记录一个时间,收到时候用当前时间一减就可以了。
回头再看一下最开始截的那个四个ICMP数据包的图,可以看到他们的ID虽然一样,但是序列号是不同的,这样也就是知道了为什么请求和回复可以互相匹配了。
最后你可能会好奇32个字节的默认数据是啥,其实你用ASCII码表查一下就知道了,在windows上是从a开始一直循环往复往下写。Linux上也是一样,有条件完全可以自己验证一下。
Ping还带有很多参数,但是大部分一般程序员都用不到,有兴趣的话完全可以自己去试试啦。