首页 > theory > network > > 正文

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

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

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

 我们知道现在的互联网中使用的TCP/IP协议是基于,OSI(开放系统互联)的七层参考模型的,(虽然不是完全符合)从上到下分别为 应用层 表示层 会话层 传输层 网络层 数据链路层和物理层。其中数据链路层又可是分为两个子层分别为逻辑链路控制层(Logic Link Control,LLC )和介质访问控制层((Media Access Control,MAC )也就是平常说的MAC层。LLC对两个节点中的链路进行初始化,防止连接中断,保持可靠的通信。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地址,

(3)然后数据会到达交换机A,交换机A看到数据包的第二层目的MAC地址,是去往路由器A的,就把数据包发送到路由器A,路由器A收到数据包,首先查看数据包的第三层ip目的地址,如果在自己的路由表中有去往PC-C的路由,说明这是一个可路由的数据包。

(4)然后路由器进行IP重组和分组的过程。首先更换此数据包的第二层包头信息,路由器PC-A到达PC—C要经过一个广域网,在这里会封装很多广域网相关的协议。其作用也是为了找下一阶段的信息。同时对第二层和第三层的数据包重校验。把数据经过Internet发送出去。最后经过很多的节点发送到目标主机PC_C中。

   现在我们想一个问题,PC-A和PC-C的MAC地址如果是相同的话,会不会影响正常的通讯呢!答案是不会影响的,因为这两个主机所处的局域网被广域网分隔开了,通过对发包过程的分析可以看出来,不会有任何的问题。而如果在同一个局域网中的话,那么就会产生通讯的混乱。当数据发送到交换机是,这是的端口信息会有两个相同的MAC地址,而这时数据会发送到两个主机上,这样信息就会混乱。因此这也是保证MAC地址唯一性的一个理由。







主机A与主机B通信,A Ping B,图:A-交换机-B,AB同一子网

(1)ping数据包用的是ICMP协议,IP协议的一个子协议,位于三层,包含A的IP,B的IP,三层进行IP封装成包,进入二层

(2)A,B处于相同子网,查看缓存中对与目的对应得B第2层mac地址,如果存在,直接进行第2层封装成帧,经物理层信号编码,以0101010010这样的bits流传输在网络介质上。

(3)如果不存在B的MAC,则发送ARP广播请求B的MAC,ARP数据包经物理层进入交换机端口,需要进行源端口号学习,目的端口查找,B响应ARP请求,交换机又可以学习一目的MAC地址与哪个端口对应,在下次传送数据时就不再需要对所有端口进行广播了。B通过ARP单播把B的MAC响应给A,此时再返回(2)的处理过程。

(4)当再进行A与B之间数据包转发,将直接用B的MAC地址封装,数据转发得以高速交换

主机A与主机C通信,A Ping C,图:A-交换机-路由器M-路由器N-交换机-C,AC不同子网

(1)两个机器不在同一子网内,A要与目的C通信,A要向“缺省网关”发出ARP包,而“缺省网关”的IP地址已经在A系统软件中设置。这个IP地址实际上对应路由器M的E0的IP地址。所以A对“缺省网关”的IP地址广播出一个ARP请求时,路由器M的E0口的MAC回复给A,此时A数据包的目的MAC替换为M的E0口MAC。

(2)MAC替换后,经物理层,传输到交换机物理层,再传到交换机数据链路层,再经数据数据链路层到物理层。

(3)经bit流传到路由器M的物理层,然后传到数据链路层,二层解封装,目的IP与路由器M三层IP路由表比较,若匹配正确,交给上层处理,否则会缓存数据包内容,然后根据目标地址查找路由表找到相关表项,得到NEXT HOP及出接口的MAC地址,用这两个地址作为新的目的及源MAC地址封装事先缓存的数据包,然后转发,这个过程称为帧的重写(REWRITE)。

(4)重复进行MAC替换后, 在路由器N中,找到与目的IP匹配表项,若N中存在C对应的MAC表项,则数据传输到C网络层,否则N发送ARP广播请求C的MAC。

(5)当C收到A的数据包后,再从A到C进行一次反向MAC替换过程。反向过程中,每一步省略掉ARP请求NEXT HOP的MAC,因为MAC表已经建立起来。

总结:

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网络技术内幕》,这本书比较厚,很多内容是“非主线内容”,翻译的也不太好,总之前面一两遍不容易看懂。如果单纯为了计算机网络这门课去做这些,代价太大。如果有兴趣,又能坚持把这些看完,对与理解计算机网络还是有很大帮助的。
那这里划一下重点吧:第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头。最后由网卡驱动将数据包发送出去。

====================================fen    ge xian===========================

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

=============================fen    ge xian===============================
最后,嗯,如果只是为了上一门课,看这些代价确实比较大! 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头。最后由网卡驱动将数据包发送出去。

====================================fen    ge xian===========================

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

=============================fen    ge xian===============================
最后,嗯,如果只是为了上一门课,看这些代价确实比较大!


参考文章:http://blog.chinaunix.net/uid-26226893-id-2482301.html