首页 > theory > network > > 正文

IP数据包的传输全过程详解—数据是如何在tcp/ip各层封装?

发布人:zhoulujun@live.cn    点击:

我们知道现在的互联网中使用的TCP IP协议是基于,OSI(开放系统互联)的七层参考模型的,(虽然不是完全符合)从上到下分别为 应用层 表

 我们知道现在的互联网中使用的TCP/IP协议是基于,OSI(开放系统互联)的七层参考模型的,(虽然不是完全符合)从上到下分别为: 应用层 表示层 会话层 传输层 网络层 数据链路层和物理层。

具体参看:https://www.zhoulujun.cn/html/theory/network/2016_0316_7709.html

其中数据链路层又可是分为两个子层分别为

  1. 逻辑链路控制层(Logic Link Control,LLC

    LLC对两个节点中的链路进行初始化,防止连接中断,保持可靠的通信。

  2. 介质访问控制层((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报文传输过程

  1. Host sends packet to default gateway(主机将数据包发送到默认网关)

  2. Packet placed in frame(数据包被封装入帧)

  3. Router receives frame(路由器接到帧)

  4. Router finds destination network in route table(路由器在路由表中发现目标网络)

  5. Router chooses next hop toward destination(路由器选择一个更接近目标的下一跳)

  6. MAC address of next hop determined(下一跳的MAC地址被确定)

  7. Packet placed in frame(数据包被封装入帧)

  8. Repeats steps 2 through 7 as necessary(如果需要的话,重复步骤2~7)

  9. Router receives frame(路由器接到帧)

  10. Router finds network directly connected(路由器发现直连网络)

  11. MAC address of end host determined(最终主机的MAC地址被确定)

  12. 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协议的实现过于复杂,很容易理不清楚。

文字节选(拼凑)来源:

IP数据包的传输全过程详解

深入理解Linux网络技术内幕——L4层协议与Raw IP的处理

ARP攻击原理简析及防御措施

还有的内容,已经找不到出处了……