我们知道现在的互联网中使用的TCP IP协议是基于,OSI(开放系统互联)的七层参考模型的,(虽然不是完全符合)从上到下分别为 应用层 表
我们知道现在的互联网中使用的TCP/IP协议是基于,OSI(开放系统互联)的七层参考模型的,(虽然不是完全符合)从上到下分别为: 应用层 表示层 会话层 传输层 网络层 数据链路层和物理层。
具体参看:https://www.zhoulujun.cn/html/theory/network/2016_0316_7709.html
其中数据链路层又可是分为两个子层分别为
-
逻辑链路控制层(Logic Link Control,LLC )
LLC对两个节点中的链路进行初始化,防止连接中断,保持可靠的通信。
-
介质访问控制层((Media Access Control,MAC )也就是平常说的MAC层。
MAC层用来检验包含在每个桢中的地址信息。
在下面会分析到。还要明白一点路由器是在网路层的,而网卡在数据链路层。
我们知道,ARP(Address Resolution Protocol,地址转换协议)被当作底层协议,用于IP地址到物理地址的转换。在以太网中,所有对IP的访问最终都转化为对网卡MAC地址的访问。如果主机A的ARP列表中,到主机B的IP地址与MAC地址对应不正确,由A发往B数据包就会发向错误的MAC地址,当然无法顺利到达B,结果是A与B根本不能进行通信。
首先我们分析一下在同一个网段的情况。假设有两台电脑分别命名为A和B,A需要相B发送数据的话,A主机首先把目标设备B的IP地址与自己的子网掩码进行“与”操作,以判断目标设备与自己是否位于同一网段内。如果目标设备在同一网段内,并且A没有获得与目标设备B的IP地址相对应的MAC地址信息,则源设备(A)以第二层广播的形式(目标MAC地址为全1)发送ARP请求报文,在ARP请求报文中包含了源设备(A)与目标设备(B)的IP地址。同一网段中的所有其他设备都可以收到并分析这个ARP请求报文,如果某设备发现报文中的目标IP地址与自己的IP地址相同,则它向源设备发回ARP响应报文,通过该报文使源设备获得目标设备的MAC地址信息。为了减少广播量,网络设备通过ARP表在缓存中保存IP与MAC地址的映射信息。在一次 ARP的请求与响应过程中,通信双方都把对方的MAC地址与IP地址的对应关系保存在各自的ARP表中,以在后续的通信中使用。ARP表使用老化机制,删除在一段时间内没有使用过的IP与MAC地址的映射关系。
如果中间要经过交换机的话,根据交换机的原理,它是直接将数据发送到相应端口,那么就必须保有一个数据库,包含所有端口所连网卡的MAC地址。它通过分析Ethernet包的包头信息(其中包含不原MAC地址,目标MAC地址,信息的长度等信息),取得目标B的MAC地址后,查找交换机中存储的地址对照表,(MAC地址对应的端口),确认具有此MAC地址的网卡连接在哪个端口上,然后将数据包发送到这个对应的端口,也就相应的发送到目标主机B上。这样一来,即使某台主机盗用了这个IP地址,但由于他没有这个MAC地址,因此也不会收到数据包。
现在我们讨论两台不在同一个网段中的主机,假设网络中要从主机PC-A发送数据包PAC到PC-C主机中,如下图所示:
路由器A ===================路由器B
| INTERNET |
| |
交换机A 交换机B
| | | |
| | | |
PC-A PC-B PC-C PC-D
PC-A并不需要获取远程主机(PC-C)的MAC地址,而是把IP分组发向缺省网关,由网关IP分组的完成转发过程。如果源主机(PC-A)没有缺省网关MAC地址的缓存记录,则它会通过ARP协议获取网关的MAC地址,因此在A的ARP表中只观察到网关的MAC地址记录,而观察不到远程主机的 MAC地址。在以太网(Ethernet)中,一个网络设备要和另一个网络设备进行直接通信,除了知道目标设备的网络层逻辑地址(如IP地址)外,还要知道目标设备的第二层物理地址(MAC地址)。ARP协议的基本功能就是通过目标设备的IP地址,查询目标设备的MAC地址,以保证通信的顺利进行。
数据包在网络中的发送是一个及其复杂的过程,上图只是一种很简单的情况,中间没有过多的中间节点,其实现实中只会比这个更复杂,但是大致的原理是一致的。
(1)PC-A要发送数据包到PC-C的话,如果PC-A没有PC-C的IP地址,则PC-A首先要发出一个dns的请求,路由器A或者dns解析服务器会给PC-A回应PC-C的ip地址,这样PC-A关于数据包第三层的IP地址信息就全了:源IP地址:PC-A --> 目的ip地址:PC-C。
(2)接下来PC-A要知道如何到达PC-C,然后,PC-A会发送一个arp的地址解析请求,发送这个地址解析请求,不是为了获得目标主机PC-C的MAC地址,而是把请求发送到了路由器A中,然后路由器A中的MAC地址会发送给源主机PC-A,这样PC-A的数据包的第二层信息也全了:源MAC地址:PC-A的MAC地址 --> 目的MAC地址:路由器A的MAC地址
矫正下:如果PC1想发送数据给PC2,PC1首先会检查自己的ARP缓存表,查看是否有PC2的IP地址和MAC地址的对应关系,如果有,则会将PC2的MAC地址作为目的MAC地址封装到数据帧中。如果没有,PC1则会发送一个ARP请求信息,请求的目标IP地址是PC2的IP地址,目标MAC地址是MAC地址的广播帧(即FF-FF-FF-FF-FF-FF),源IP地址和MAC地址是PC1的IP地址和MAC地址。
(3)然后数据会到达交换机A,交换机A看到数据包的第二层目的MAC地址,是去往路由器A的,就把数据包发送到路由器A,路由器A收到数据包,首先查看数据包的第三层ip目的地址,如果在自己的路由表中有去往PC-C的路由,说明这是一个可路由的数据包。
(4)然后路由器进行IP重组和分组的过程。首先更换此数据包的第二层包头信息,路由器PC-A到达PC—C要经过一个广域网,在这里会封装很多广域网相关的协议。其作用也是为了找下一阶段的信息。同时对第二层和第三层的数据包重校验。把数据经过Internet发送出去。最后经过很多的节点发送到目标主机PC_C中。
举个例子,你要从上海,写一封信到广州,你会写上你朋友在广州的地址,这个地址,就是类似ip地址。但是这封信不是直接送到广州的,而是要先送到邮局,这个邮局就是类似我们说的路由器,第一个路由器,我们可以称之为网关。邮局看到ip地址,哦,是发往广州的,他就要查,这封邮件的下一站要去哪里,比如下一站是杭州的中转站,而邮局根据内部的代号,就知道下个目的地地址了,这个代号就是mac地址,就这样,邮件打上去杭州的包装,去了杭州。
到了杭州,中转站拆掉外层的包装,再检查目的ip,再决定去哪里,比如福州,于是包上福州目的信息的包装,送到了福州,福州中转站拆掉外层包装,根据目的ip,下一站是广州目的地,于是再包装上目的地是广州的包装,送到了广州。在这里,杭州和福州的中转站,就类似路由器,路由器根据ip路由表决定下一跳,但是最终需要通过mac地址(外层包装)来决定传送给谁。
现在我们想一个问题,PC-A和PC-C的MAC地址如果是相同的话,会不会影响正常的通讯呢!答案是不会影响的,因为这两个主机所处的局域网被广域网分隔开了,通过对发包过程的分析可以看出来,不会有任何的问题。而如果在同一个局域网中的话,那么就会产生通讯的混乱。当数据发送到交换机是,这是的端口信息会有两个相同的MAC地址,而这时数据会发送到两个主机上,这样信息就会混乱。因此这也是保证MAC地址唯一性的一个理由。
所以如果只有链路层而没有网络层,那么整个通讯就是一张没有路由的大网,一个人找一台主机就要广播给全网主机,这显然是不可能的。网络层就能将链路层的广播域划分开,通过路由表决定数据走向。而链路层除了寻址,还有很多其他功能,比如lacp聚合,stp防止网络风暴等,这些都是以太网链路层的功能,网络层不参与也不需要ip地址。
ARP协议的缺陷
ARP协议是建立在信任局域网内所有节点的基础上的,他的效率很高。但是不安全。它是无状态的协议。他不会检查自己是否发过请求包,
也不知道自己是否发过请求包。他也不管是否合法的应答,只要收到目标mac地址是自己的ARP reply或者ARP广播包(包括ARP reply和ARP request),都会接受并缓存。
ARP攻击原理
ARP欺骗攻击建立在局域网主机间相互信任的基础上的
当A发广播询问:我想知道IP是192.168.0.3的硬件地址是多少?
此时B当然会回话:我是IP192.168.0.3我的硬件地址是mac-b,
可是此时IP地址是192.168.0.4的C也非法回了:我是IP192.168.0.3,我的硬件地址是mac-c。而且是大量的。
所以A就会误信192.168.0.3的硬件地址是mac-c,而且动态更新缓存表
这样主机C就劫持了主机A发送给主机B的数据,这就是ARP欺骗的过程。
假如C直接冒充网关,此时主机C会不停的发送ARP欺骗广播,大声说:我的IP是192.168.0.1,我的硬件地址是mac-c,
此时局域网内所有主机都被欺骗,更改自己的缓存表,此时C将会监听到整个局域网发送给互联网的数据报。
总结IP报文传输过程
-
Host sends packet to default gateway(主机将数据包发送到默认网关)
-
Packet placed in frame(数据包被封装入帧)
-
Router receives frame(路由器接到帧)
-
Router finds destination network in route table(路由器在路由表中发现目标网络)
-
Router chooses next hop toward destination(路由器选择一个更接近目标的下一跳)
-
MAC address of next hop determined(下一跳的MAC地址被确定)
-
Packet placed in frame(数据包被封装入帧)
-
Repeats steps 2 through 7 as necessary(如果需要的话,重复步骤2~7)
-
Router receives frame(路由器接到帧)
-
Router finds network directly connected(路由器发现直连网络)
-
MAC address of end host determined(最终主机的MAC地址被确定)
-
Packet placed in frame to final destination(帧中的数据包被发送到最终主机)
要理解下面的内容,需要有一些socket的基础知识:可以参考《UNIX网络编程》的4、5章。至少需要知道怎么创建一个socket,怎么将用户数据通过socket发出去,怎么从socket接收数据。接下来需要了解tcp/ip协议。这个想必大家都有所了解。
然后,可以根据一个网络协议栈具体的实现,反过来理解协议。在代码中,比较容易理解网络数据的处理流程,以及各个字段是怎么用的。这里推荐两本参考书:《Linux内核协议栈源码分析》和《深入理解Linux网络技术内幕》。
在《深入理解Linux网络技术内幕》第二章中,介绍了一个最重要的数据结构, struct sk_buff{},linux内核所有数据包的收发都是基于这个数据结构的。
网卡收到数据,会构造一个sk_buff的实例,依次从skb中拿出MAC头,IP头,TCP头用于处理,然后将TCP负载返回给用户层。
而用户层发送一个数据包,也是构造一个sk_buff的实例,填写需要发送的用户数据,然后经过TCP层,IP层,MAC层时,依次向sk_buff中填写tcp头,ip头和mac头。最后由网卡驱动将数据包发送出去。
以linux-2.6.32为例,梳理一下收发数据包的主要流程,如果有兴趣,可以下载一份源码对照着看。
一、链路层接收数据包:以太网封装,只有三个字段,目的MAC,源MAC和3层协议类型。
-
根据数据包的目的MAC与本地网卡的MAC是否匹配,可以判断这个数据包是否是给本地网卡的。
-
根据协议类型,查找3层协议处理函数。比如0800对应到IP协议处理函数。见代码dev.c的netif_receive_skb函数。其中有根据协议号查询ptype_base的操作,ptype_base中的一个元素可以理解为一个3层协议。ip协议对应的处理函数就是ip_rcv。
二、ip层接收数据,ip头部如图所示,主要经过下面几个函数
-
ip_rcv函数:取出ip头部,判断ip头中的首部长度,版本是否正确,判断数据包总长度和网卡实际接收的数据长度是否一致,判断校验和是否正确。
-
ip_rcv_finish:根据目的ip查询路由表,如果给本机的数据包(ip头的目的地址是本机IP地址),则进入函数ip_local_deliver。处理ip选项(如果有)。
-
ip_local_deliver:根据IP头的MF标志,16位标识,13位偏移进行defrag操作。
-
ip_local_deliver_finish:根据IP层的8bit协议字段,查找4层协议。如果是一个TCP数据包,就会找到TCP协议处理函数,即tcp_v4_rcv。
这个过程中,用到了IP头的这些字段:版本,首部长度,校验和,ip选项(如果有),16bit标识,MF标识,13bit偏移,8bit协议,32bit目的ip。
三、tcp层接收数据:头部结构如图,主要经过以下几个函数:
-
tcp_v4_rcv:取出tcp头部,取出本数据包的32bit序号,终止序号,32bit确认序号 根据源端口和目的端口,查找tcp的socket。
-
tcp_v4_do_rcv:检查校验和。调用tcp_rcv_state_process处理TCP状态机。
-
根据tcp socket的当前状态,和tcp头部的flags处理TCP的状态机。
-
如果tcp socket处于TCP_ESTABLISHED状态(可以收发TCP数据的状态),进入函数tcp_rcv_established。
-
tcp_rcv_established:判断这个TCP数据包携带的32bit序号是否在本方通告的窗口之内,如果是,将这个数据包放到tcp_v4_rcv找到的socket的sk_receive_queue中。
这个过程用到了TCP头部的源端口,目的端口,32bit序号,处理状态机需要SYN/FIN/ACK等flags,校验和。
四、socket层接收数据。
到这一步的时候,数据已经在一个socket的接收队列中了,用户层可以调用socket的recv/recvfrom/read等接口去读取数据。
调用关系依次为:recv系统调用, sys_recv, sys_recvfrom , sock_recvmsg, _sock_recvmsg, sock->ops->recvmsg, tcp_recvmsg。
tcp_recvmsg会从socket的sk_receive_queue队列取出数据返回给用户层。
五、socket层发送数据:
调用关系依次为:send系统调用,sys_send,sys_sendto , sock_sendmsg, __sock_sendmsg , sock->ops->sendmsg,其实就是tcp_sendmsg。
六、tcp层发送数据:
调用关系为:__tcp_push_pending_frames, tcp_write_xmit,tcp_transmit_skb,icsk->icsk_af_ops->queue_xmit(钩子函数,其实就是ip_queue_xmit)
tcp_transmit_skb:这个函数就有将源端口、目的端口,seq,ack_seq填写到数据包中的操作。源端口和目的端口哪儿来的?源端口可能在bind系统调用时指定的,目的端口是在connect系统调用时指定的。这些参数都被记录在本地的socket结构中。
七、ip层发送数据,依次经过以下函数:
-
ip_queue_xmit:查路由表,将ttl,源ip,目的ip填入ip头部。
-
ip_local_out,__ip_local_out:
-
dst_output:在查路由表时被设置,通常就是ip_output函数。
-
ip_output,ip_finish_output:将大数据报分片,并且设置MF标志和偏移。
八、链路层发送数据包:
-
根据数据包的目的IP,通过ARP协议获得目的MAC地址。
-
将设备的源MAC和目的MAC填入到数据包中,可能需要重新计算校验和。
-
将数据包发送出去。
照着发送和接收的主线,看看在这些主要的函数里都做了些什么,对网络协议的理解应该会加深一些。
还有,刚开始看的时候,可以先看udp协议的实现,因为tcp协议的实现过于复杂,很容易理不清楚。
最后,嗯,如果只是为了上一门课,看这些代价确实比较大! Talk is cheap,是时候放码过来了。
要理解下面的内容,需要有一些socket的基础知识:可以参考《UNIX网络编程》的4、5章。至少需要知道怎么创建一个socket,怎么将用户数据通过socket发出去,怎么从socket接收数据。
接下来需要了解tcp/ip协议。这个想必大家都有所了解。
然后,可以根据一个网络协议栈具体的实现,反过来理解协议。在代码中,比较容易理解网络数据的处理流程,以及各个字段是怎么用的。这里推荐两本参考书:《Linux内核协议栈源码分析》和《深入理解Linux网络技术内幕》。
特别说一下《深入理解Linux网络技术内幕》,这本书比较厚,很多内容是“非主线内容”,翻译的也不太好,总之前面一两遍不容易看懂。如果单纯为了计算机网络这门课去做这些,代价太大。如果有兴趣,又能坚持把这些看完,对与理解计算机网络还是有很大帮助的。
那这里划一下重点吧:第2章-基本数据接口,第9/10/11章-讲链路层的发送和接收,18/19/20/21章--讲IP层的处理。
在《深入理解Linux网络技术内幕》第二章中,介绍了一个最重要的数据结构, struct sk_buff{},linux内核所有数据包的收发都是基于这个数据结构的。
网卡收到数据,会构造一个sk_buff的实例,依次从skb中拿出MAC头,IP头,TCP头用于处理,然后将TCP负载返回给用户层。
而用户层发送一个数据包,也是构造一个sk_buff的实例,填写需要发送的用户数据,然后经过TCP层,IP层,MAC层时,依次向sk_buff中填写tcp头,ip头和mac头。最后由网卡驱动将数据包发送出去。
以linux-2.6.32为例,梳理一下收发数据包的主要流程,如果有兴趣,可以下载一份源码对照着看。
一、链路层接收数据包:以太网封装,只有三个字段,目的MAC,源MAC和3层协议类型。
-
根据数据包的目的MAC与本地网卡的MAC是否匹配,可以判断这个数据包是否是给本地网卡的。
-
根据协议类型,查找3层协议处理函数。比如0800对应到IP协议处理函数。见代码dev.c的netif_receive_skb函数。其中有根据协议号查询ptype_base的操作,ptype_base中的一个元素可以理解为一个3层协议。ip协议对应的处理函数就是ip_rcv。
二、ip层接收数据,ip头部如图所示,主要经过下面几个函数
-
ip_rcv函数:取出ip头部,判断ip头中的首部长度,版本是否正确,判断数据包总长度和网卡实际接收的数据长度是否一致,判断校验和是否正确。
-
ip_rcv_finish:根据目的ip查询路由表,如果给本机的数据包(ip头的目的地址是本机IP地址),则进入函数ip_local_deliver。处理ip选项(如果有)。
-
ip_local_deliver:根据IP头的MF标志,16位标识,13位偏移进行defrag操作。
-
ip_local_deliver_finish:根据IP层的8bit协议字段,查找4层协议。如果是一个TCP数据包,就会找到TCP协议处理函数,即tcp_v4_rcv。
这个过程中,用到了IP头的这些字段:版本,首部长度,校验和,ip选项(如果有),16bit标识,MF标识,13bit偏移,8bit协议,32bit目的ip。
三、tcp层接收数据:头部结构如图,主要经过以下几个函数:
-
tcp_v4_rcv:取出tcp头部,取出本数据包的32bit序号,终止序号,32bit确认序号 根据源端口和目的端口,查找tcp的socket。
-
tcp_v4_do_rcv:检查校验和。调用tcp_rcv_state_process处理TCP状态机。
-
根据tcp socket的当前状态,和tcp头部的flags处理TCP的状态机。
-
如果tcp socket处于TCP_ESTABLISHED状态(可以收发TCP数据的状态),进入函数tcp_rcv_established。
-
tcp_rcv_established:判断这个TCP数据包携带的32bit序号是否在本方通告的窗口之内,如果是,将这个数据包放到tcp_v4_rcv找到的socket的sk_receive_queue中。
这个过程用到了TCP头部的源端口,目的端口,32bit序号,处理状态机需要SYN/FIN/ACK等flags,校验和。
四、socket层接收数据。
到这一步的时候,数据已经在一个socket的接收队列中了,用户层可以调用socket的recv/recvfrom/read等接口去读取数据。
调用关系依次为:recv系统调用, sys_recv, sys_recvfrom , sock_recvmsg, _sock_recvmsg, sock->ops->recvmsg, tcp_recvmsg。
tcp_recvmsg会从socket的sk_receive_queue队列取出数据返回给用户层。
五、socket层发送数据:
调用关系依次为:send系统调用,sys_send,sys_sendto , sock_sendmsg, __sock_sendmsg , sock->ops->sendmsg,其实就是tcp_sendmsg。
六、tcp层发送数据:
调用关系为:__tcp_push_pending_frames, tcp_write_xmit,tcp_transmit_skb,icsk->icsk_af_ops->queue_xmit(钩子函数,其实就是ip_queue_xmit)
tcp_transmit_skb:这个函数就有将源端口、目的端口,seq,ack_seq填写到数据包中的操作。源端口和目的端口哪儿来的?源端口可能在bind系统调用时指定的,目的端口是在connect系统调用时指定的。这些参数都被记录在本地的socket结构中。
七、ip层发送数据,依次经过以下函数:
-
ip_queue_xmit:查路由表,将ttl,源ip,目的ip填入ip头部。
-
ip_local_out,__ip_local_out:
-
dst_output:在查路由表时被设置,通常就是ip_output函数。
-
ip_output,ip_finish_output:将大数据报分片,并且设置MF标志和偏移。
八、链路层发送数据包:
-
根据数据包的目的IP,通过ARP协议获得目的MAC地址。
-
将设备的源MAC和目的MAC填入到数据包中,可能需要重新计算校验和。
-
将数据包发送出去。
照着发送和接收的主线,看看在这些主要的函数里都做了些什么,对网络协议的理解应该会加深一些。
还有,刚开始看的时候,可以先看udp协议的实现,因为tcp协议的实现过于复杂,很容易理不清楚。
文字节选(拼凑)来源:
深入理解Linux网络技术内幕——L4层协议与Raw IP的处理
还有的内容,已经找不到出处了……
转载本站文章《 IP数据包的传输全过程详解—数据是如何在tcp/ip各层封装?》,
请注明出处:https://www.zhoulujun.cn/html/theory/ComputerScienceTechnology/network/2016_0316_7708.html