目录
以太网协议
以太网协议的简介
以太网协议所处的位置
以太网帧(或者说MAC帧)的格式
局域网通信原理
碰撞避免算法(包含MTU的知识点)
局域网攻击原理
ARP协议
ARP协议所在的位置
为什么要存在ARP协议(或者说ARP协议的作用)
ARP报文的格式
ARP协议的工作过程
ARP伪装以让自己成为中间人、ARP攻击
以太网协议
以太网协议的简介
在<<网络层协议——IP协议>>一文中说过,实际上IP协议也不真正负责传输数据,IP协议和TCP协议一样也只负责提供传输数据的策略,只不过策略的着重点不一样,IP协议提供的策略主要是负责给数据指路,真正负责传输数据的只有数据链路层,举个例子,当我们想去某个地方旅游时是需要一本指南攻略和一俩车的,IP协议就像这本指南攻略,数据链路层就像车,如果没有IP协议,每到达一个中间站就不知道下一步该往哪走,而如果没有车,则一步都无法前进,一切都是空谈了。其中以太网协议就是在数据链路层中负责传输数据的协议。
以太网协议所处的位置
如上图就很好地说明了以太网协议所处的位置。
以太网帧(或者说MAC帧)的格式
以太网帧中各个字段的含义如下:
- 数据字段:表示以太网帧的有效载荷,其可能是一个完整的IP报文、也可能是ARP报文、RARP报文。如果是IP报文,则IP报文的大小只能在46字节到1500字节浮动;如果是另外两个报文,这两个报文的大小通常只有28字节,因为有规定(为什么有这个规定会在下文中说明)以太网帧中的数据字段(即有效载荷)的长度最小为46字节,最大为1500字节,所以如果因为以太网帧的有效载荷的类型是ARP报文和RARP报文而导致该以太网帧的有效载荷的长度不够46字节,则需要在该帧的有效载荷的后面补填充位PAD。
- 目的地址字段、源地址字段:在<<网络层协议——IP协议>>一文中说过,网络层的IP协议只负责给数据指南,数据链路层才真正负责传输数据,即数据链路层要负责将IP报文封装成以太网帧,然后将以太网帧发给下一个设备,那么问题来了,网络层负责指南时定位的是下一个设备的IP地址,IP地址是网络层的概念,为了将数据链路层和网络层解耦(如果不解耦会有什么后果呢?第一,如果未来IP地址的格式发生了变动,那么不光整个网络层需要进行修改、整个数据链路层也需要跟着修改;第二、网络层是软件层,其是在操作系统内部的,修改网络层只需要修改各类OS的源码、然后重装系统即可,虽然工程量很大,但并非不能做到,而数据链路层是在驱动、硬件层上的,如果想要修改数据链路层,那全球所有的硬件设备都得跟着变动,否则设备就无法使用,这个成本就太大了),数据链路层是不能使用IP地址来定位下一个设备的,于是才诞生了目的地址字段和源地址字段。填充在这两个字段上的地址也被称为MAC地址、物理地址、网卡地址,它是一个用6个字节、即48个比特位表示的正整数,一般用16进制数字加上冒号的形式来表示,例如:08:00:27:03:fb:19。MAC地址通常在网卡出厂时就被内嵌到网卡中,是一个固定的序列,用于标识网卡的唯一性,每台设备只要配备了一张网卡,那么就具有一个MAC地址(即每个网卡都有一个全球唯一的MAC地址),只要网卡具有唯一性,使用网卡的主机也就具有唯一性了。注意虽然每个MAC地址在全球也是具有唯一性的,但MAC地址并不被用于在公网中标识唯一一台主机,而被用在同一个局域网中标识唯一一台主机,源地址字段上的MAC地址就表示当前以太网帧是哪台主机发送的,目的地址字段上的MAC地址就表示当前以太网帧是要发给哪台主机。
- 类型字段:所占两个字节,如果值是16进制的0800、即0x0800,则表示数据字段里值的类型是一个IP报文;如果值是0x0806或者0x8035,则说明数据字段里值的类型分别又是ARP报文或者RARP报文。
对MAC地址的补充说明:我们可以在Linux机器中通过ifconfig
命令查看当前主机的MAC地址,如下图所示。
如何理解帧头和帧尾呢?
(结合下图思考)操作系统内核是C语言写的,而数据链路层和网络层一样又是属于内核协议栈的,因此以太网协议和IP协议也一定是用C语言编写的,但注意和IP、TCP、UDP报头不一样的是,以太网帧的帧头和帧尾都不再是位段类型,而是正常的结构体类型了,如下图所示就是帧头和帧尾的定义:
数据链路层如何将以太网帧解包并向上交付给网络层呢?
是比较简单的,因为以太网帧的帧头或者说报头是固定大小的6+6+2=14字节,帧尾是固定大小的4字节,剩下的所有内容都是有效载荷,所以解包的过程就是把收到的以太网帧的前14个字节去掉,然后把后4个字节去掉,最后把剩下的内容即有效载荷交给网络层的对应协议(通过2个字节的类型字段上的值即可得知有效载荷的类型,然后数据链路层就知道该把有效载荷交给网络层的哪个协议了,比如说如果根据类型字段得知有效载荷是IP报文、则就会把IP报文上交给网络层的IP协议;如果得知有效载荷是ARP报文、则就会把ARP报文上交给网络层的ARP协议)。本段内容体现在代码层面上就是:
- 数据链路层接收一个以太网帧时肯定是要new出【MTU+帧头14+帧尾4】字节、即一般是1518字节的空间以防止接收不下的,顺便在new的时候把这块空间全初始化成\0或者说0(字符\0的阿斯克码就是整形0,所以数字0就表示字符\0),假如用指针char* p管理这块空间,接收到以太网帧后,首先通过(*(struct _MAC_FRAME_HEADER*)p).m_cType得到类型字段上的值以确定有效载荷的类型进而确定未来需要将有效载荷向上交付给上层中的哪个协议,然后通过某种手段比如类似于(说“类似”是因为笔者举例时所用的计算以太网帧的方法不是很准确,因为当有效载荷中存在字符\0时,笔者所说的这种计算方法就无法成功计算,至于OS中是如何计算出以太网帧的大小的,我们不必关心,只要只能OS能够计算即可)通过while循环计算出该以太网帧的大小(即在while循环中让指针p不断++,只要指针p指向的不是\0或者说0,就让计数器变量int cnt加1,循环终止条件是遇到\0,循环终止后,cnt就是以太网帧的大小了),计算出以太网帧的大小cnt后,想要去掉占用14字节的报头和占用4字节的帧尾,则只需要通过while循环从以太网帧的第15个字节开始,循环cnt-14-4次,每次将一个字节拷贝到另一块空间上即可,这另一块空间存储就是完整的有效载荷,最后直接将这块存储着完整有效载荷的空间的地址向上交付给有效载荷对应的、之前通过(*(struct _MAC_FRAME_HEADER*)p).m_cType算出来的上层协议,这样一来该上层协议就能通过解引用这块地址获取有效载荷的内容了。
数据链路层如何将上层交给自己的IP报文或者其他类型的报文比如ARP报文封装成以太网帧呢?
非常简单,以网络层交给数据链路层的报文是IP报文为例,数据链路层首先创建帧头对象和帧尾对象,然后给它们赋值,然后把收到的IP报文拷贝到帧头对象的后面,最后再把帧尾对象拷贝到IP报文的后面即可。本段内容体现在代码层面上就是:
- 首先数据链路层在接收IP报文时肯定是要开空间的,因为根据IP协议,每个IP报文是不会超过MTU字节、即1500字节的,所以可以new一个1501字节的空间以防止接收不下,并把空间全部初始化成\0,然后再接收该IP报文,然后在数据链路层中计算出该IP报文的大小,这里计算IP报文的大小可以完全仿照上文计算以太网帧的方式(比如设置计数器变量int cnt,通过while循环让管理这块空间的指针p不断++,只要指针p指向的不是\0或者说0,就让计数器变量int cnt加1,循环终止条件是遇到\0,循环终止后,cnt就是IP报文的大小了),当然数据链路层也可以选择读取IP报头中的16位总长度字段,该字段上的值直接就是整个IP报文的大小,但如果以太网帧的有效载荷不是IP报文而是ARP报文,那就只能按照本段蓝色圆括号中说的方法老老实实手动计算ARP报文的大小了。
- 计算出有效载荷的大小cnt后,直接char *p1 = new char【cnt+帧头14+帧尾4】,然后(*(struct _MAC_FRAME_HEADER*)p1) = {在其中填充帧头对象的各个成员的值},这样就完成了填充帧头对象;然后char* p2 = p1+sizeof(struct _MAC_FRAME_HEADER),以p2指向的位置开始填充有效载荷的内容,因为我们知道有效载荷的大小cnt,所以设置一个while循环循环cnt次,每次让p2[i] = p[i]即可完成填充有效载荷的内容(注意p[i]的这个p是上一段中用于指向IP报文的起始地址的指针);最后再char *p3 = p2+cnt,以p3指向的位置开始填充CRC校验字段的内容,因为该字段是4字节,所以直接(*(int*)p3)={在其中填充CRC校验字段的内容}即可完成填充该字段了。可以看到,通过这样的方式就完成了将以太网帧的帧头、有效载荷、帧尾给“拼凑”在一起,完成了将IP报文封装成以太网帧的任务。
局域网通信原理
在<<网络层协议——IP协议>>一文中只说了【位于同一个局域网中的主机可以直接互相进行通信,数据只需要经过交换机,不需要经过任何一个家用路由器或者是运营商路由器】,但并没有说明其中原理,现在我们就来更深入地学习一下其中的过程。
如上图,现在我们约定H表示主机host,M表示MAC地址,M1表示H1的MAC地址、M2表示H2的MAC地址...以此类推。当H1给H6发送以太网帧(当然这个以太网帧是通过应用层中的数据自顶向下不断封装报头得到的)时,帧头中目的地址字段上的值就会是H6,源地址字段上的值就会是H1,这个以太网帧在局域网中被交换机进行转发时,交换机实际上会给局域网中的所有主机(包括发送方主机H1自己和管理当前局域网的路由器)都发送一份,所以位于该局域网中的所有主机都是能收到该以太网帧的,只不过不同主机收到后会有不同的反应,比如说H2,H2收到该数据帧后,在自己的数据链路层进行解包把有效载荷和帧头帧尾进行分离后,读取其中的帧头时,发现目的地址字段上的MAC地址并不是自己的MAC地址,H2就不会将以太网帧的有效载荷继续向上交付给网络层,而是直接将该有效载荷丢弃(不要误认为丢弃是要将数据再转发给其他设备,是不会转发的,而是把这些数据放到一块表示可以进行数据覆盖的空间上);再比如说H6,H6收到该数据帧后,在自己的数据链路层进行解包把有效载荷和帧头帧尾进行分离后,读取其中的帧头时,发现目的地址字段上的MAC地址就是自己的MAC地址,H6就不会将该以太网帧的有效载荷丢弃,而是继续将有效载荷向上交付给网络层。以上就是局域网通信的原理。
有了上一段的理解后,咱们再回看只具有私有IP的位于局域网中的主机和具有公网IP的位于公网上的主机进行通信的过程,就会有新的认识。举个例子,假如下图中主机B是公网中的主机,主机A是局域网中的主机,当主机A把以太网帧发送给和自己位于同一个局域网的家用路由器A时(和路由器A位于同一个局域网中的主机A只要知道路由器A的LAN口IP地址,通过这个IP地址和ARP协议就能知道路由器A的MAC地址,所以主机A能把以太网帧发给路由器A。至于为什么是LAN口IP而不是WAN口IP,是因为一个路由器对子网内部使用LAN口IP,对子网外部使用WAN口IP。至于主机A如何得知管理主机A所在子网的路由器A的LAN口IP,方法有很多,比如如果在该子网上使用了DHCP,即动态主机配置协议,路由器通常会作为DHCP服务器分配IP地址给子网内的设备,在DHCP分配过程中,路由器的LAN口IP地址就会作为默认网关信息被分发给子网中的主机,同时因为主机A的IP地址是路由器A动态分配的,所以路由器A也是知道主机A的IP地址的;再比如一些路由器支持使用ICMP通告消息的功能来向子网内的主机发送路由器的信息,信息就包括LAN口IP地址等,这样的通告可以帮助主机自动获取到路由器的信息。关于ARP协议,会在下文中进行讲解。),路由器A在自己的数据链路层中对以太网帧进行解包把有效载荷和帧头帧尾进行分离后、读取这个以太网帧的帧头时就会发现目的地址字段上的MAC地址值就是自己的MAC地址,于是家用路由器A就会将该数据帧的有效载荷、即一个IP报文向上交付给网络层,然后路由器A会在网络层中对IP报文进行解包把有效载荷和IP报头进行分离后、读取IP报头中的目的IP地址,并把该IP地址和自己路由表中的所有子网掩码进行按位与得到一个个网络号,将得到的网络号和存储在路由表中的网络号进行比对后会发现没有一个是相等的,于是路由器A在网络层中重新封装IP报文时就会把IP报头中的源IP地址替换成路由器A的WAN口IP地址(家用路由器的WAN口IP和LAN口IP都是私有IP),然后把重新封装好的IP报文向下交付给数据链路层,然后路由器A在数据链路层中重新封装以太网帧时就会把目的地址字段替换成IP地址为default(该default值可能是和路由器A直接相连的某个路由器的WAN口IP地址,也可能是和路由器A直接相连的某个路由器的LAN口IP地址)的运营商路由器的MAC地址、即运营商路由器B的MAC地址(这个MAC地址也是路由器A拿着IP地址default通过ARP协议得到的。说一下,如果最初路由器A将得到的网络号和存储在路由表中的网络号进行比对时发现有相等的,那么路由器A在数据链路层中重新封装以太网帧时就会把目的地址字段替换成【管理该匹配成功的网络号对应的网络的路由器R】的MAC地址X,就没有default什么事儿了,其中MAC地址X也是路由器A拿着路由器R的LAN口IP地址通过ARP协议得到的,路由器A知道路由器R的LAN口IP地址是因为路由器A和路由器R建立连接时、路由器R在给路由器A动态分配IP地址时,路由器R会把自己的LAN口IP地址告诉路由器A,以让路由器A知道自己的默认网关是路由器R),并把源地址字段替换成替换成路由器A的MAC地址,当家用路由器A再把重新封装好的以太网帧发送给和自己位于同一个子网(这里就不能被称之为局域网了,因为路由器B既具有公网WAN口IP,也具有私有LAN口IP,所以只能说路由器B是位于局域网和公网中间的路由器)的运营商路由器B时,这个过程和主机A把以太网帧发给路由器A的过程完全一致,请自行脑补...通过这样一跳一跳的方式,最终就能将以太网帧送到主机B。
碰撞避免算法(包含MTU的知识点)
如上图左半部分是一个局域网,在上文讲解局域网通信原理的部分中我们对H1给H6发信息的过程原理进行了详细的说明,现在我们还要知道的是,当H1在给H6发信息时,如果H2也在给H4发、H3也在给H5发,那么就会导致各自发送的信息(本质是光电信号)产生交叉、叠加,各方发送的信息就都会发生碰撞,最终导致所有主机发送的信息都被破坏,信息被破坏后、目标主机H6接收到这个信息时通过查看CRC校验字段或者其他手段就能识别出信息有错误,于是H6就会直接将该信息丢弃(不要误认为丢弃是要将数据再转发给其他设备,是不会转发的,而是把这些数据放到一块表示可以进行数据覆盖的空间上),所以可以看出发信息时是不能发生碰撞的,所以如果发送方主机H1发现自己发的信息发生了碰撞、被破坏后(如何发现呢?在上文中说过,发送方发送的以太网帧在局域网中被交换机进行转发时,交换机实际上会给局域网中的所有主机包括发送方主机自己都发送一份,所以发送方主机是能收到自己发出去的信息的,收到该信息后对该信息进行检查即可得知是否被破坏,至于如何检查,OS是有自己的策略的,比如可以通过CRC校验字段或者其他手段,我们不必关心),就会执行碰撞避免算法。注意是H1和其他主机发生了碰撞,即说明不只是H1发生了碰撞,其他主机也发生了碰撞,那既然H1能知道自己的信息被破坏了从而执行碰撞避免算法,那其他主机当然也能知道自己的信息被破坏了从而执行碰撞避免算法。
问题:什么是碰撞避免算法呢?答案:就是某台发送方主机的数据链路层先发信息,如果该主机在数据链路层收到自己发的信息时发现信息被破坏,则说明发生了碰撞,于是这台主机的数据链路层就会先随机等待一段时间,期望在这段时间内其他主机能快点把信息发完,然后再重传刚才的信息(需要注意的是进行重传时,这个数据并不是重新从传输层获取的,而是之前在数据链路层进行过缓存,直接从数据链路层获取的)。当然一个局域网内不可能是单台主机在进行碰撞避免,一定是多台主机发生了碰撞,所以只要发生碰撞,那么一定是多台主机都会执行碰撞避免算法,它们的等待时间肯定是要错开的。
从上一段可以看出,数据链路层也是具有略微的“可靠性”的,说“略微”是因为数据链路层能保证的只是,【只要数据发生了碰撞,就进行重传,如果没发生碰撞,数据链路层就不管了,认为数据已经到达对方主机】,但实际上我们知道即使数据没有发生碰撞,也不一定能到达对方主机,当然数据链路层也不用关心后序的这些问题,因为如果数据真丢了,会有非常具有可靠性的传输层TCP协议来兜底。
说一下,H1和H6两台主机进行通信时,本质是两个进程在进行通信,笔者在讲解进程间通信的文章中说过【进程间想要通信,首先得看到同一份资源】,那H1和H6在通信时能看到什么同一份的资源呢?答案就是局域网,多台主机执行碰撞避免算法,本质就是想在自己发信息时其他主机不能发送,想独享局域网这个资源,所以本质上这里的局域网就是一个临界资源。以前对于临界资源我们会进行加锁,但没有对局域网进行加锁的说法,所以在局域网中互相通信时只能靠各台主机先尝试发信息,如果碰撞了就等待一段时间再发,如果恰好其他主机在等待,那当前主机就能成功地发送信息了。
问题:局域网中的主机越多越好还是越少越好?为什么?
如果局域网中的主机很多,那么相比于局域网中的主机很少的情况,局域网中的主机互相通信的概率和频率一定是更高的,这就意味着数据之间会有更大的可能性、会有更大的频率发生碰撞导致所有的主机都暂时无法把数据发出去,于是局域网中的所有主机的网速就都变慢了,所以局域网中的主机是不能很多的,以免一发数据就碰撞、再发数据又碰撞....。
根据上一段我们就能领悟一个现象发生的原因,为什么我们在人多的地方网速会卡呢,就是因为可能频繁地发生了数据碰撞,导致每个人都无法顺畅地发信息,网速就慢了。
问题:如果我非要在一个局域网中组建大量的主机,那有没有什么办法能尽可能地避免发生数据碰撞呢?
答案:有,可以对交换机进行设置,把局域网中的主机划分成几个部分(但注意本质还是在同一个局域网内,该局域网中没有第二个局域网)。什么意思呢,举个例子,如下图所示,我们可以把局域网中的6台主机分成左右两个部分,如果交换机发现主机1是要给主机3发数据,因为主机1和3都位于左半部分,于是交换机就选择只把数据转发给位于左半部分的所有主机,避免数据扩散到右边,这样就能减轻右半部分发生数据碰撞的可能性。
问题:局域网中的主机在发送以太网帧时,帧的大小是越大越好还是越小越好?为什么?
虽然数据本质是光电信号,即使传输更大的数据,传输速度也是很快的,但问题在于数据之间都是光电信号,你快我也快,所以如果以太网帧很大,那么对比以太网帧很小的情况,传输很大的以太网帧所花费的时间一定是更长的,而数据在网络中传输时间越长,停留的时间越长,其他数据和当前数据发生碰撞的概率就越大,所以局域网中的主机在发送以太网帧时,帧的大小是不能太大的,当然也不能太小,否则影响通信效率。
正是因为以太网帧的大小不能太大,所以数据链路层中才诞生了MTU的概念,所以网络层和传输层才需要对数据进行分片操作(如果传输层协议是TCP,则数据在传输层就直接被分片了,比如滑动窗口中的所有数据明明能被打包成一个TCP报文并向下交付,但在实际中却还是被分成了多条更小的TCP报文再向下交付,这时网络层就无需再对数据进行分片;如果传输层协议是UDP,因为UDP协议不支持分片功能,所以这时传输层就不会对UDP报文进行分片而是直接将其向下交付给网络层,这时在网络层就需要对其进行分片了)。关于MTU的概念如下:
- 规定以太网帧中的数据字段(即有效载荷)的长度最小为46字节,最大为1500字节,所以如果因为以太网帧的有效载荷的类型是ARP报文和RARP报文而导致该以太网帧的有效载荷的长度不够46字节,则需要在该帧的有效载荷的后面补填充位PAD。
- 最大值1500被称为以太网的最大传输单元(MTU),不同的网络类型有不同的MTU,如果一个数据包从以太网路由到拨号链路上,数据包长度大于拨号链路的MTU了,则需要对数据包进行分片(fragmentation)。
- 不同的数据链路层的MTU的大小是不同的。
我们可以在Linux机器中通过ifconfig
命令查看当前主机的数据链路层中的MTU大小,如下图所示。
MTU对IP、TCP、UDP协议的影响
在<<网络层协议——IP协议>>一文中其实已经说明过答案了,这里就简单概述一下,如下。
因为数据链路层规定了最大传输单元MTU,所以如果IP层要发送的某个IP报文的大小超过了MTU,此时IP层就需要在发送前先对该IP报文进行分片,然后才能将分片后的数据向下交付给数据链路层并发给接受端,这就是MTU对IP协议的影响,然后,如果接收端的IP层识别到没有将所有的分片收集齐,那就不会给发送端给予应答,发送端根据其传输层协议就会有对应的应对策略:
- 比如如果是UDP协议,因为UDP协议没有确认应答策略和超时重传策略,所以此时这条UDP报文就直接丢包了,注意不要认为只有这条UDP报文会丢,而要知道因为接收端的网络层将所有分片收集齐的可能性不高,所以发送端发送的每条UDP报文丢包的概率都是较大的,所以就有可能导致双方压根无法正常稳定地通信,这就是MTU对UDP协议的影响;
- 再比如如果是TCP协议,则会重传,但即使重传了,因为接收端的网络层将所有分片收集齐的可能性不高,所以发送端大概率还是要重传,频繁重传就会导致浪费大量内存资源、网络资源、CPU资源,导致双方的通信效率变得很低,为了避免这种情况发生,于是TCP协议中也增加了进行分片的逻辑,这就是MTU对TCP协议的影响。
说一下,在上文中我们知道了数据的分片会在网络层中发生,现在我们还要知道,不仅发送端主机在网络层中可能会对数据进行分片,管理各个网络的路由器在网络层中也有可能对数据进行分片,又因为不同网络的MTU大小是不一样的,如果传输路径上的某个网络的MTU比发送端主机所在的网络的MTU小,那么路由器就可能在自己的网络层中对IP报文再次进行分片。
关于MTU对TCP、UDP协议的影响的补充说明如下:
- IP报头当中如果不携带选项字段,那么IP报头的长度就是20字节,而UDP采用的是定长的8字节报头,因此如果UDP报文的有效载荷的大小(即应用层数据的大小)超过了 1500 − 20 − 8 = 1472 字节,此时该UDP报文就需要在IP层被分片,如果携带了选项字段,这个值还会从1472变得更小,所以为了保证UDP报文不在网络层被分片以受到上文所说的影响,将一个应用层数据封装成UDP报文时,这个应用层数据最好不要超过1472字节。
- IP报头和TCP报头当中如果不携带选项字段,那么IP报头和TCP报头的长度就都是20字节,因此如果TCP报文的有效载荷的大小(即应用层数据的大小)超过了 1500 − 20 − 20 = 1460 字节,此时该TCP报文就需要在传输层被分片,如果TCP报头携带了选项字段,这个值还会从1460变得更小,这就是关于MTU对TCP协议的影响的补充说明的全部内容了。(额外说一下,对于TCP报文的有效载荷是没有任何要求的,因为即使大于1460字节,TCP报文也会在传输层被分片,而不是在网络层中被分片,所以也就不会出现上文所说的接收端的网络层没有将所有的分片收集齐导致发送端频繁重传进而导致浪费资源的情况了)
补充一个知识点:对于在上一段中说的值1460,该值是单个TCP报文的有效载荷的最大值,我们将该值称为MSS(Max Segment Size),TCP通信的双方在3次握手建立连接的过程中就会进行MSS协商(即互相发送SYN标志位为1的TCP报文时,双方都把自己的MSS值放到TCP报头的选项字段中),最终选取双方支持的MSS值当中的较小值作为最终MSS。
局域网攻击原理
问题:假如我现在想要让一个局域网瘫痪,让位于该局域网中的所有主机都无法上网,那该如何做到呢?
答案:在上文中学习完碰撞避免算法的内容后其实很容易想到答案,那就是通过某种手段(至于是什么手段会在下一段中进行说明)让自己的主机能不断地、高密度地给【和自己位于同一个局域网中的随便一台主机】发信息,这样一来,局域网中就一直充满了我发送的数据,其他主机想要发送数据时就会发生数据碰撞从而在自己的数据链路层执行碰撞避免算法,即等待一段时间以期望这一段时间内别人能把数据发完,然后重传,但问题在于我是恶意攻击的一方,是一直会发数据的,所以其他主机就永远无法重传成功。
说一下,不要认为上面的事情是能通过【自己写个UDP客户端,打个死循环调用sendto函数】做到的,虽然在UDP通信中,UDP是无连接的协议,它不会像TCP那样在发送数据之前建立连接,当你主机上的UDP客户端向别人主机发送数据时,不会检查别人的主机是否有运行UDP服务端进程,你主机直接就能把数据发给别人主机(当然,因为别人主机上没有运行UDP服务端,没有创建socket套接字、也没有调用bind将套接字和端口号和某个进程绑定,所以别人主机的传输层收到UDP报文后是不知道该把报文上交给哪个进程的,于是这些UDP报文都会被丢弃),但因为你发的数据也是要到达数据链路层的,所以如果你发的大量数据和其他主机发的数据发生了碰撞,你主机的数据链路层也是会执行碰撞避免算法的,你也是需要进行等待的,所以你就无法不断地发送数据到局域网中。上一段中说的【通过某种手段】是指借助某些网上的工具,能够让自己主机发送的数据在进行自顶向下交付时能绕开数据链路层,直接把数据放到网卡并让网卡发送到局域网中,这样才能做到上一段所说的进行攻击。
ARP协议
ARP协议所在的位置
说一下,ARP协议虽然位于以太网协议的上层,但ARP协议并不是网络层的协议,它和以太网协议都是属于数据链路层的协议。正是因为ARP协议位于以太网协议和MAC帧(或者说以太网帧)的上面,所以在上文讲解以太网帧的格式时,以太网帧的有效载荷有时能是IP报文,有时又能是ARP报文。
为什么要存在ARP协议(或者说ARP协议的作用)
在上文标题为局域网通信原理的部分中讲解数据路由的过程时其实已经说过ARP协议的作用了,ARP地址解析协议(Address Resolution Protocol,ARP)协议的作用是:如果主机A和主机B位于同一个子网,则只要主机A能知道主机B的IP地址(无所谓是私有IP还是公网IP),主机A都能拿着这个IP地址通过ARP协议得到主机B的MAC地址。
问题:那为什么要存在ARP协议呢?
答案:如上图所示,当主机A发数据给主机B时,数据是需要在网络中被各个中间设备转发的,比如主机A首先需要把数据发到路由器A上,由于路由器A和主机A是属于同一个子网的,因此主机A能够直接将数据交给路由器A,但想要给同子网中的一台设备(一般来说交换机不具有MAC地址和IP地址,只会在内部构建一张MAC地址表,用于区分子网中的各个设备。路由器是有MAC地址和IP地址的)发送数据,根据上文所讲的局域网通信原理可知前提是得先知道对方的MAC地址,但主机A此时只知道路由器A的IP地址(该IP地址是路由器的LAN口IP而不是WAN口IP,因为一个路由器对子网内部使用LAN口IP,对子网外部使用WAN口IP;至于主机A如何得知管理主机A所在子网的路由器A的LAN口IP,方法有很多,比如如果在该子网上使用了DHCP,即动态主机配置协议,路由器通常会作为DHCP服务器分配IP地址给子网内的设备,在DHCP分配过程中,路由器的LAN口IP地址就会作为默认网关信息被分发给子网中的主机,再比如一些路由器支持使用ICMP通告消息的功能来向子网内的主机发送路由器的信息,信息就包括LAN口IP地址等,这样的通告可以帮助主机自动获取到路由器的信息),因此主机A必须通过某种方式得到路由器A的MAC地址,这时就需要ARP协议来起作用了,这就是为什么要存在ARP协议。
ARP报文的格式
如上图所示,ARP报文中各个字段的含义如下:
- (了解即可)硬件类型字段:指当前设备的数据链路层所使用的网络协议类型,比如数据链路层使用的协议是以太网协议时,该字段就是1;如果使用的是令牌环网或者无线LAN/WAN网,则该字段就是其他值。
- (了解即可)协议类型字段:ARP协议主要用于将IP地址转换成MAC地址(当然也可能用于将其他类型A的地址转换成其他类型B的地址,但主要用于将IP转MAC),该字段就表示需要被转换的地址的类型,比如说当ARP协议用于将IP地址转换成MAC地址时,则需要被转化的地址的类型就是IP地址,则该字段的值就是0x0800;当ARP协议用于将其他类型A的地址转换成其他类型B的地址时,则需要被转化的地址的类型就是A,这时该字段的值就又会有所不同。
- (了解即可)硬件地址长度字段:如果上面所说的硬件类型字段为1,则表示数据链路层使用的协议是以太网协议,因为在以太网协议中使用的物理硬件地址是MAC地址,而MAC地址又是48位的,所以硬件地址长度字段的值就会是6(单位是字节);如果上面所说的硬件类型字段不为1,则硬件地址长度字段的值又会有所不同。
- (了解即可)协议地址长度字段:ARP协议主要用于将IP地址转换成MAC地址,如果上面所说的协议类型字段为0x0800,则表示需要被转化的地址的类型是IP地址,而协议地址长度字段又表示需要被转化的地址的类型的长度,IP地址是4字节,所以该字段的值就会是4。
- (重点)op字段:为1则表示当前ARP报文是ARP请求,为2则表示当前ARP报文是ARP应答。
- 发送端以太网地址字段和目的以太网地址字段:就是发送端和接收端的MAC地址。
- 发送端IP地址和目的IP地址字段:字面意思,不解释。
ARP协议的工作过程
如上图所示,当主机A发数据给主机B时,数据是需要在网络中被各个中间设备转发的,比如主机A首先需要把数据发到路由器A上,由于路由器A和主机A是属于同一个子网的,因此主机A能够直接将数据交给路由器A,但想要给同子网中的一台设备发送数据,根据上文所讲的局域网通信原理可知前提是得先知道对方的MAC地址,但主机A此时只知道路由器A的IP地址(该IP地址是路由器的LAN口IP而不是WAN口IP,因为一个路由器对子网内部使用LAN口IP,对子网外部使用WAN口IP;至于主机A如何得知管理主机A所在子网的路由器A的LAN口IP,方法有很多,比如如果在该子网上使用了DHCP,即动态主机配置协议,路由器通常会作为DHCP服务器分配IP地址给子网内的设备,在DHCP分配过程中,路由器的LAN口IP地址就会作为默认网关信息被分发给子网中的主机,再比如一些路由器支持使用ICMP通告消息的功能来向子网内的主机发送路由器的信息,信息就包括LAN口IP地址等,这样的通告可以帮助主机自动获取到路由器的信息),因此主机A必须通过某种方式得到路由器A的MAC地址,这时就需要ARP协议来起作用了,那么ARP协议是如何起作用的呢?如下:
如下图所示,此时主机A就会先在自己的ARP协议层构建一个如下图红框处的ARP报文(前4个字段的值为什么是1、0800、6、4,请参考上文讲解ARP报文格式的部分,op字段为1是因为当前ARP报文是主机A发给路由器A的一个ARP请求,MAC_HA就是主机A自己的MAC地址,IP_HA就是主机A自己的IP地址,因为目前我们不知道路由器A的MAC地址,所以在该字段上填充全F表示广播,最后在目的IP地址字段上填充路由器A的IP地址),构建完毕后把ARP报文向下交付给以太网协议层,在以太网协议层中就会给该ARP报文添加上蓝框处以太网帧的帧头和帧尾把该ARP报文变成以太网帧(在帧头部分中,因为并不知道目的地路由器A的MAC地址,于是也在该字段上填充全F表示广播,然后在以太网源地址字段上填充自己的也就是主机A的MAC地址,同时因为当前以太网帧的有效载荷的类型是ARP报文,所以帧类型的字段上填充0806,这样当接收方在以太网协议层接收到这个以太网帧并解包后,才知道该把有效载荷上交给ARP协议层还是上交给网络层),然后根据局域网通信原理,主机A就会把该以太网帧发给交换机,让交换机把该以太网帧发给和主机A位于同一个子网中的所有主机(包括路由器A),然后每台主机接收到该以太网帧对以太网帧解包后,发现帧头中的以太网目的地址字段是全F,是表示广播的以太网帧,于是每台主机都需要将以太网帧的有效载荷、即ARP报文向上交付给自己的ARP协议层,然后每台主机都会在自己的ARP协议层先查看op字段,发现该ARP报文是一个ARP请求,然后就会查看ARP报文中的目的IP字段是否和自己的IP地址匹配,此时除路由器A以外的所有主机都会发现不匹配并将该ARP报文丢弃,只有路由器A会发现是匹配的并构建一个ARP响应报文(收到ARP请求的路由器A必须构建一个ARP响应并发送给主机A,在构建ARP响应报文时,会把前4个字段的值依然填充成1、0800、6、4,至于原因依然请参考上文讲解ARP报文格式的部分,然后把op字段填充成2表示ARP报文是响应报文,把发送端以太网地址字段填充成自己也就是路由器A的MAC地址以告诉主机A自己的MAC地址,把发送端IP地址字段填充成自己的IP地址,把目的以太网地址字段填充成从收到的ARP请求中得知的主机A的MAC地址,把目的IP地址字段填充成从收到的ARP请求中得知的主机A的IP地址),构建完毕后把ARP报文向下交付给以太网协议层,在以太网协议层中就会给该ARP报文添加上帧头和帧尾把该ARP报文变成以太网帧(在帧头部分中,把以太网目的地址字段填充成从收到的ARP请求中得知的主机A的MAC地址,然后在以太网源地址字段上填充自己的也就是路由器A的MAC地址,同时因为当前以太网帧的有效载荷的类型是ARP报文,所以帧类型的字段上填充0806,这样当接收方在以太网协议层接收到这个以太网帧并解包后,才知道该把有效载荷上交给ARP协议层还是上交给网络层),然后根据局域网通信原理,路由器A就会把该以太网帧发给交换机,让交换机把该以太网帧发给和路由器A位于同一个子网中的所有主机(包括主机A),然后每台主机接收到该以太网帧并对该以太网帧解包以将帧头和帧尾和有效载荷分离开后,发现帧头中的以太网目的地址字段是主机A的MAC地址,于是除了主机A外的其他主机就都会直接丢弃该以太网帧的有效载荷,只有主机A才需要将以太网帧的有效载荷、即ARP报文向上交付给自己的ARP协议层,然后在自己的ARP协议层先查看op字段,发现该ARP报文是一个ARP响应,然后就会查看ARP报文中的发送端以太网地址字段,然后主机A就得知了路由器A的MAC地址(主机A如何得知这个MAC地址是路由器A的呢?通过查看ARP响应报文中的发送端IP地址字段,发现该IP是路由器A的IP地址,然后就知道了发送端以太网地址字段上的MAC地址是路由器A的),这就是ARP协议根据目标IP地址得知目标MAC地址的全部工作流程了。
注意: ARP属于局域网通信的协议标准,所以只有两台主机位于同一个子网,一台主机才可以向另一台主机发起ARP请求,所以一台主机不能跨网络向另一台主机发起ARP请求。
说一下,有人可能会疑惑,在上一段的情景中,路由器A发送的以太网帧的【帧头部分中和ARP报文部分中】都有一个表示主机A的MAC地址的字段,同时也都有一个表示路由器A自己的MAC地址的字段,这是否有些数据冗余呢?答案是不冗余,这些字段虽然表示的含义是一样的,但这些字段是在不同的层次中起作用的,比如一个是在以太网协议层,另一个是在ARP协议层,同时也正是因为有多个表示相同含义的字段,才能将以太网协议层和ARP协议层解耦以不至于让两个协议层都使用同一个字段。
问题:在上面我们知道了主机A给路由器A发数据时首先需要通过ARP协议得到路由器A的MAC地址,即主机A给路由器A发一个ARP请求,在得到路由器A发来的ARP响应时得知路由器A的MAC地址,那么现在问题来了,主机A给路由器A发数据成功后,主机A下一次发数据时是否需要再通过ARP协议得到路由器A的MAC地址呢?
答案:需要看情况,主机A通过ARP协议得知路由器A的MAC地址后,会暂时将路由器A的IP地址和MAC地址的映射关系保存起来,路由器A也会将包含在主机A发来的ARP请求中的主机A的IP地址和MAC地址的映射关系保存起来,这个“暂时”通常是15分钟,所以在这个时间段内主机A再给路由器A发数据时就不需要通过ARP协议得知路由器A的MAC地址了,同理在这个时间段内有其他主机给主机A发送数据、数据需要通过路由器A转发给主机A时,路由器A也不需要通过ARP协议得知主机A的MAC地址了(至于为什么主机A只是暂时将映射关系保存起来而不是永远,是因为路由器A并不一定永远在线,当路由器A重启时,管理路由器A所在的子网的路由器给路由器A动态分配IP地址时,路由器A的IP地址可能对比上一次会发生改变,如果主机A是永远将IP地址和MAC地址的映射关系保存,那么这个映射关系就是错误的,后序主机A按照这个错误的映射关系发数据时就会出现某种错误);所以在这个时间段外主机A再给路由器A发数据时就需要通过ARP协议得知路由器A的MAC地址了,同理在这个时间段外有其他主机给主机A发送数据、数据需要通过路由器A转发给主机A时,路由器A就需要通过ARP协议得知主机A的MAC地址了。
如下图所示,我们可以在Linux机器中通过arp -a命令查看当前主机的arp缓存(即当前主机保存的其他主机的IP地址和MAC地址的映射表),geteway中的就是IP地址,at中的就是MAC地址。
问题:如何得知我的主机所在局域网中的所有主机的MAC地址呢?
答案:非常简单,如下图红框处所示,先拿着当前主机的IP地址(inet后的红框就是IP地址)和当前主机的子网掩码(netmask后的红框就是子网掩码)进行按位与得到网络号a.b.c.0,然后在命令行中输入一个while脚本,让主机依次ping a.b.c.1、ping a.b.c.2...(不包括特殊IP地址a.b.c.0和a.b.c.255,这两个IP一个表示网络号,一个表示广播,都不能被任意一台设备使用),因为ping在测试网络是否连通时会给对应主机发数据包,而想要给目标主机发数据包就得先知道该主机的MAC地址,所以只要ping通了,则ping底层是会自动触发ARP协议的,所以在当前主机把所有主机都ping了一遍后,当前主机的arp缓存中就建立了子网中所有主机的IP地址和MAC地址的映射关系,此后在当前主机中调用arp -a命令就能观察到所有主机的MAC地址了。
ARP伪装以让自己成为中间人、ARP攻击
ARP伪装以让自己成为中间人
通过ARP伪装,可以让自己的主机成为中间人,从而获取其他主机发的任何网络信息。举个例子,如下图1所示,主机A和主机B位于同一个子网,路由器A是这个子网的管理者,当主机A想给子网外部的设备发数据时,主机A首先得把数据发给路由器A,但主机A发数据前,首先要知道路由器A的MAC地址,于是主机A先发一个ARP请求给路由器A,路由器A回复一个ARP响应后,主机A和路由器A就都知道了对方的MAC地址,并且双方还将对方的IP地址和MAC地址的映射关系保存在了映射表中。
此时如果主机B想成为主机A和路由器A的中间人,主机B必须知道主机A和路由器A的IP地址(路由器A的IP是路由器A给主机B动态分配IP地址时路由器A告诉主机B的,但主机A的IP需要攻击者通过某种手段得知),然后主机B也得先给路由器A发ARP请求以和路由器A交换MAC地址信息,做完这些后,主机B先不断地给路由器A发送ARP响应(说“不断地”是因为映射表的有效时间只有15分钟,超时后映射关系就失效了,所以需要不断地发送伪造的ARP响应以让伪造的映射关系一直生效),该ARP响应中的发送端以太网地址字段填充的是主机B的MAC地址,发送端IP地址填充的是主机A的IP地址,当路由器A的ARP协议层收到该ARP报文后,先查看op字段,发现值为2,于是路由器A就知道了该ARP报文是响应报文,于是路由器A就会接着查看发送端以太网地址字段和发送端IP地址字段上的值,因为每个设备的映射表上对于其他设备的IP地址和MAC地址的映射关系都是取新不取旧,所以当路由器A发现发送端IP地址字段上的值是主机A的IP时,就会认为发送端以太网地址字段上的MAC地址也是主机A的MAC地址(但我们知道实际这是主机B的MAC地址),所以路由器A就会如下图2所示把自己的映射表中的IP_HA:MAC_HA修改成IP_HA:MAC_HB,同理主机B通过同样的不断给主机A发送ARP响应的方式,也能如下图2所示让主机A把自己的映射表中的IP_RA:MAC_RA修改成IP_RA:MAC_HB。
这样一来,往后主机A给路由器A发数据时,主机A在以太网协议层中封装的以太网帧的帧头中的以太网目的字段的值就永远是主机B的MAC地址而不是路由器A的MAC地址,根据局域网通信原理,未来会收到主机A发的以太网帧的就永远是主机B而不是路由器A,当主机B收到该以太网帧并解包把有效载荷即IP报文一路向上交付到应用层后,主机B就可以对其中的数据进行修改,然后又一路向下交付将数据重新封装成以太网帧,通过在封装帧头时把以太网目的字段填充成路由器A的MAC地址(在上文中说明过主机B得到路由器A的MAC地址的过程)以此把该以太网帧代替主机A发给路由器A,当路由器A收到该以太网帧后,发现帧头中的以太网源地址字段是主机A的MAC地址,IP报头中的源IP地址字段也是主机A的IP地址,所以路由器A就发现不了该以太网帧是有问题的了;同理往后当路由器A把主机A的数据转发给子网外部的某台设备后,该台设备把响应数据发给路由器A以让路由器A转发给主机A时,路由器A在以太网协议层中封装的以太网帧的帧头中的以太网目的字段的值就永远是主机B的MAC地址而不是主机A的MAC地址,根据局域网通信原理,未来会收到路由器A发的以太网帧的就永远是主机B而不是主机A,当主机B收到该以太网帧并解包把有效载荷即IP报文一路向上交付到应用层后,主机B就可以对其中的数据进行修改,然后又一路向下交付将数据重新封装成以太网帧,通过在封装帧头时把以太网目的字段填充成主机A的MAC地址(在上文中说明过主机B得到主机A的MAC地址的过程)以此把该以太网帧代替路由器A发给主机A,当主机A收到该以太网帧后,发现帧头中的以太网源地址字段是路由器A的MAC地址,IP报头中的源IP地址字段也是路由器A的IP地址,所以主机A就发现不了该以太网帧是有问题的了。
至此,主机B就真正地成为了主机A和路由器A的中间人了。
- 图1如下。
- 图2如下。
ARP攻击
通过ARP攻击,可以让子网中的具体的某些主机无法上网。举个例子,如下图所示,主机A和主机B位于同一个子网,路由器A是这个子网的管理者,当主机A想给子网外部的设备发数据时,主机A首先得把数据发给路由器A,但主机A发数据前,首先要知道路由器A的MAC地址,于是主机A先发一个ARP请求给路由器A,路由器A回复一个ARP响应后,主机A和路由器A就都知道了对方的MAC地址,并且双方还将对方的IP地址和MAC地址的映射关系保存在了映射表中。
此时如果主机B想通过ARP攻击让主机A无法上网,那么主机B就可以不断地给主机A发送ARP响应报文(说“不断地”是因为映射表的有效时间只有15分钟,超时后映射关系就失效了,所以需要不断地发送伪造的ARP响应以让伪造的映射关系一直生效),该ARP响应报文中的发送端IP地址填充的是路由器A的IP地址,发送端以太网字段上的MAC地址是胡乱填写的,假如称它为MAC_XXX,当主机A收到该ARP报文后,先查看op字段,发现值为2,于是主机A就知道了该ARP报文是响应报文,于是主机A就会接着查看发送端以太网地址字段和发送端IP地址字段上的值,因为每个设备的映射表上对于其他设备的IP地址和MAC地址的映射关系都是取新不取旧,所以当主机A发现发送端IP地址字段上的值是路由器A的IP时,就会认为发送端以太网地址字段上的MAC地址也是路由器A的MAC地址(但我们知道实际这是主机B胡乱填写的一个MAC地址),所以主机A就会如下图2所示把自己的映射表中的IP_RA:MAC_RA修改成IP_RA:MAC_XXX,这样一来,往后主机A给路由器A发数据时,主机A在以太网协议层中封装的以太网帧的帧头中的以太网目的字段的值就永远是这个胡乱填写的MAC_XXX而不是路由器A的MAC地址,根据局域网通信原理,路由器A就永远收不到主机A发的以太网帧,于是主机A也就上不了网了。这就是ARP攻击的原理。