热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

分片传输_IP数据报结构、IP分片的原理与处理

01引言从上一篇文章我们知道了IP数据报的格式,那么这一篇文章就讲解一下IP数据报与IP分片的实现。02IP数据报的数据结构为了描述IP数据报首部的信息,

ed8a0953a1ac8db9a4fc5ac3524fe350.png

01引言

从上一篇文章我们知道了IP数据报的格式,那么这一篇文章就讲解一下IP数据报与IP分片的实现。

02IP数据报的数据结构

为了描述IP数据报首部的信息,LwIP定义了一个ip_hdr的结构体作为描述IP数据报首部,同时还定义了很多获取IP数据报首部的宏定义与设置IP数据报首部的宏定义。

对于代码的实现,有兴趣的就看,没兴趣的就不用管,反正我写的博客也是比较深入的,并没有什么影响...除此之外,还需要注意一点,这些字段是不能使用对齐操作的,因为结构体中的很多字段都是按位进行操作的。因此在LwIP中,使用了 PACK_STRUCT_BEGINPACK_STRUCT_END禁止编译器进行对齐操作。

PACK_STRUCT_BEGIN

/* The IPv4 header */

struct ip_hdr {

 /* 版本 / 首部长度 */

 PACK_STRUCT_FLD_8(u8_t _v_hl);

 /* 服务类型 */

 PACK_STRUCT_FLD_8(u8_t _tos);

 /* 数据报总长度 */

 PACK_STRUCT_FIELD(u16_t _len);

 /* 标识字段 */

 PACK_STRUCT_FIELD(u16_t _id);

 /* 标志与偏移 */

 PACK_STRUCT_FIELD(u16_t _offset);

#define IP_RF 0x8000U        /* 保留的标志位 */

#define IP_DF 0x4000U        /* 不分片标志位 */

#define IP_MF 0x2000U        /* 更多分片标志 */

#define IP_OFFMASK 0x1fffU   /* 用于分段的掩码 */

 /* 生存时间 */

 PACK_STRUCT_FLD_8(u8_t _ttl);

 /* 上层协议*/

 PACK_STRUCT_FLD_8(u8_t _proto);

 /* 校验和 */

 PACK_STRUCT_FIELD(u16_t _chksum);

 /* 源IP地址与目标IP地址 */

 PACK_STRUCT_FLD_S(ip4_addr_p_t src);

 PACK_STRUCT_FLD_S(ip4_addr_p_t dest);

} PACK_STRUCT_STRUCT;

PACK_STRUCT_END

这些字段与我们的IP数据报格式是一样的:

18b80d338468dd7ad846a7ea73237c43.png

除此之外LwIP还定义了很多宏定义对这些数据结构进行操作:

/* 获取IP数据报首部各个字段信息的宏 */

//获取协议版本

#define IPH_V(hdr)  ((hdr)->_v_hl >> 4)

//获取首部长度(字)

#define IPH_HL(hdr) ((hdr)->_v_hl & 0x0f)

//获取获取首部长度字节

#define IPH_HL_BYTES(hdr) ((u8_t)(IPH_HL(hdr) * 4))

//获取服务类型

#define IPH_TOS(hdr) ((hdr)->_tos)

//获取数据报长度

#define IPH_LEN(hdr) ((hdr)->_len)

//获取数据报标识

#define IPH_ID(hdr) ((hdr)->_id)

//获取分片标志位+偏移量

#define IPH_OFFSET(hdr) ((hdr)->_offset)

//获取偏移量大小(字节)

#define IPH_OFFSET_BYTES(hdr) \

((u16_t)((lwip_ntohs(IPH_OFFSET(hdr)) & IP_OFFMASK) * 8U))

//获取生存时间

#define IPH_TTL(hdr) ((hdr)->_ttl)

//获取上层协议

#define IPH_PROTO(hdr) ((hdr)->_proto)

//获取校验和

#define IPH_CHKSUM(hdr) ((hdr)->_chksum)

/* 用于填写IP数据报首部的宏*/

//设置版本号跟首部长度

#define IPH_VHL_SET(hdr, v, hl) \

(hdr)->_v_hl &#61; (u8_t)((((v) <<4) | (hl)))

//设置服务类型

#define IPH_TOS_SET(hdr, tos) (hdr)->_tos &#61; (tos)

//设置数据报总长度

#define IPH_LEN_SET(hdr, len) (hdr)->_len &#61; (len)

//设置标识

#define IPH_ID_SET(hdr, id) (hdr)->_id &#61; (id)

//设置分片标志与偏移量

#define IPH_OFFSET_SET(hdr, off) (hdr)->_offset &#61; (off)

//设置生存时间

#define IPH_TTL_SET(hdr, ttl) (hdr)->_ttl &#61; (u8_t)(ttl)

//设置上层协议

#define IPH_PROTO_SET(hdr, proto) (hdr)->_proto &#61; (u8_t)(proto)

//设置校验和

#define IPH_CHKSUM_SET(hdr, chksum) (hdr)->_chksum &#61; (chksum)

03IP数据报分片

其实在讲解IP数据报的时候也讲解了IP数据报的分片&#xff0c;因为任何一个IP数据报都是依赖网卡进行发送的&#xff0c;而对于某些网卡&#xff0c;它所能承载的IP数据报大小是有限的&#xff0c;一个链路层帧能承载的最大数据量叫做最大传送单元(Maximum Transmission Unit&#xff0c;MTU)&#xff0c;比如以太网的MTU就是1500字节数据&#xff0c;而某些广域网链路的帧可承载不超过576字节的数据。每个IP数据报都必须封装在链路层帧中从一台设备传输到下一台设备&#xff0c;这些设备可以是主机也可以是路由器&#xff0c;故链路层协议的MTU严格地限制着IP数据报的长度。

说点题外话&#xff1a;IP分片功能只在IPv4中实现&#xff0c;而在IPv6是不允许IP层进行分片处理的&#xff0c;那么就需要主机确定链路的MTU大小。

IP分片是在IP层完成的&#xff0c;假设一个IP数据报在主机中没有分片&#xff0c;但不代表它不会在中间传输的过程中不进行分片&#xff0c;假设这个IP数据报从主机的以太网出来&#xff0c;携带了1460个字节的数据&#xff0c;但是它在转发的过程中&#xff0c;遇到了一个MTU只有576个字节的网卡设备&#xff0c;那么它必须进行分片才能通过这个网卡&#xff0c;因此&#xff0c;它将会在这个设备中进行分片然后再向目的地进军....

对IP数据报长度具有严格限制并不是主要问题&#xff0c;问题在于在发送方与目的地路径上的每段链路可能使用不同的链路层协议&#xff0c;并且每种协议可能具有不同的MTU&#xff0c;这就需要有一个很好的处理方式&#xff0c;随之而来的就是IP数据报分片处理。其实IP分片在很多书上也叫IP分组&#xff0c;我个人还是喜欢叫IP分片。

分片处理是将IP数据报中的数据分片成两个或更多个较小的IP数据报&#xff0c;用单独的链路层帧封装这些较小的IP数据报&#xff1b;然后向输出链路上发送这些帧&#xff0c;每个这些较小的数据报都称为分片&#xff0c;由于IP数据报的分片偏移量是用8的整数倍记录的&#xff0c;所以每个数据报中的分片数据大小也必须是8的整数倍。

所有分片数据包在其到达目的地传输层之前需要在IP层完成重新组装(也称之为重装/重组)。但是如果在每个中间转发设备的IP层中组装分片数据包&#xff0c;那么将严重影响路由器的性能。

例如一台路由器&#xff0c;在收到数据分片后又进行重装完成后再转发&#xff0c;这样子的处理简直就是浪费生命&#xff0c;所以 IPv4的设计者决定将数据报的重新组装工作放到端系统中&#xff0c;而不是放到网络路由器中&#xff0c;什么是端系统呢&#xff1f;简单来说就是数据包中的目标IP地址的主机&#xff0c;在这台机器上的IP层进行数据分片的重装&#xff0c;这样子数据分片可以任意在各个路由之间进行转发&#xff0c;而路由器就无需理会数据分片是在哪里重装&#xff0c;只要数据分片不是给路由器的&#xff0c;那么就将其转发出去即可&#xff0c;当然&#xff0c;这样子的处理就会是的每个数据分片到达目标IP地址的主机时间是不一样的。因此&#xff0c;IP分片到达目标主机的顺序也是不确定的&#xff0c;在目标主机中必须进行重装的处理&#xff0c;还要设定重装的超时时间。

那么怎么样处理每个分片的数据呢&#xff1f;其实在发送主机中&#xff0c;它会把需要分片的数据进行切割(分片)&#xff0c;按照数据的偏移量进行切割&#xff0c;切割后形成的每个IP数据报(即分片)具有与初始IP数据报几乎一样的IP数据报首部&#xff0c;为什么说是几乎一样而不是全部一样呢&#xff0c;因为IP数据报首部的标志、分片偏移量这两个字段与分片有关&#xff0c;不同的分片&#xff0c;这些信息可能不一样&#xff0c;不同的分片数据报长度也是不一样的&#xff0c;校验和字段也是不一样的。但是源IP地址、目标IP地址与标识号肯定是一样的&#xff0c;每个分片上的分片偏移量字段是不一样的。与IP分片有关的标志位&#xff1a;

/* 标识字段 */

PACK_STRUCT_FIELD(u16_t _id);

/* 标志与偏移 */

PACK_STRUCT_FIELD(u16_t _offset);

#define IP_RF 0x8000U        /* 保留的标志位 */

#define IP_DF 0x4000U        /* 不分片标志位 */

#define IP_MF 0x2000U        /* 更多分片标志 */

标识字段用于表示IP层发送出去的每一份IP数据报&#xff0c;在发送每一份报文&#xff0c;该值加1&#xff0c;在分片的时候&#xff0c;该字段会被复制到每个分片数据报中&#xff0c;在目标接收主机中&#xff0c;使用该字段判断这些数据是否属于同一个IP数据报。

标志位(3bit)的定义如下&#xff1a;第一位保留未用&#xff1b;第二位是不分片标志位&#xff0c;如果该位为1&#xff0c;则表示IP数据报在发送的过程中不允许进行分片&#xff0c;如果这个IP数据报的大小超过链路层能承载的大小&#xff0c;这个IP数据报将被丢弃&#xff0c;如果该位为0则表示IP层在必要的时候可以对其进行分片处理&#xff1b;第三位为更多分片位&#xff0c;如果为1则表示该分片数据报不是整个IP数据报的最后一个分片&#xff0c;如果为0则表示是整个IP数据报的最后一个分片。

分片偏移量占据13bit空间&#xff0c;表示当前分片所携带的数据在整个IP数据报中的相对偏移位置(以8字节为单位)&#xff0c;目标主机必须受到以0偏移量开始到最高偏移量的所有分片&#xff0c;才能将分片进行重装为一个完整的IP数据报&#xff0c;并且重装IP数据报的依据就是分片的偏移量。

IP协议是一种提供不可靠的传输服务协议&#xff0c;一个或多个分片可能永远到达不了目的地。为了让目标主机相信它已经收到了初始IP数据报的最后一个分片&#xff0c;在最后一个IP分片上的标志字段 IP_MF会被设置为0。而所有其他分片的标志被设为1。另外&#xff0c;为了让目的主机确定是否丢失了一个分片(且能按正确的顺序重新组装分片)&#xff0c;使用分片偏移量字段指定该分片应放在初始IP数据报的哪个位置。

比如一个主机打算发送4000字节的IP数据报(20字节IP首部加上3980字节IP数据区域&#xff0c;假设没有IP数据报首部选项字段)&#xff0c;且该数据报必须通过一条MTU为1500字节的以太网链路。这就意味着源始IP数据报中3980字节数据必须被分配为3个独立的数据报分片(其中的每个分片也是一个IP数据报)。假定初始IP数据报贴上的标识号为666&#xff0c;那么第一个分片的数据报总大小为1500字节(1480字节数据大小&#43;20字节IP数据报首部)&#xff0c;分片偏移量为0&#xff0c;第二个分片的数据报大小也为1500字节&#xff0c;分片偏移量为185(185*8&#61;1480)&#xff0c;第三个分片的数据报大小为1020(4000-1480-1480&#43;20)&#xff0c;分片偏移量为370(185&#43;185)。

那么对于的IP分片数据结构就是如下&#xff1a;

编号标识IP_RFIP_DFIP_MF偏移量携带数据大小
原报文666保留0004000
分片1666保留0101500
分片2666保留011851500
分片3666保留003701020
05LwIP源码实现IP分片

那么LwIP源码是怎么样实现的呢&#xff1f;

整个函数是比较复杂的&#xff0c;主要是循环处理数据报的分片&#xff0c;主要是处理偏移量与分片标志&#xff0c;拷贝原始数据的部分到分片空间中并发送出去&#xff0c;然后填写IP数据报首部的其他字段&#xff0c;如果是分片的最后一个数据报&#xff0c;则修改标志位并且发送出去&#xff0c;发送完成则释放分片空间。

err_t

ip4_frag(struct pbuf *p,

struct netif *netif,

const ip4_addr_t *dest)

{

 struct pbuf *rambuf;

 struct pbuf *newpbuf;

 u16_t newpbuflen &#61; 0;

 u16_t left_to_copy;

 struct ip_hdr *original_iphdr;

 struct ip_hdr *iphdr;

 const u16_t nfb &#61; (u16_t)((netif->mtu - IP_HLEN) / 8);

 u16_t left, fragsize;

 u16_t ofo;

 int last;

 u16_t poff &#61; IP_HLEN;

 u16_t tmp;

 int mf_set;

 //原来的数据区域

 original_iphdr &#61; (struct ip_hdr *)p->payload;

 iphdr &#61; original_iphdr;

 if (IPH_HL_BYTES(iphdr) !&#61; IP_HLEN) {

   /* 如果ip4_frag不支持IP选项 */

   return ERR_VAL;

 }

 /* 保存原始偏移量 */

 tmp &#61; lwip_ntohs(IPH_OFFSET(iphdr));

 ofo &#61; tmp & IP_OFFMASK;

 /* 得到更多的分配标志位 */

 mf_set &#61; tmp & IP_MF;

 /* 得到要发送数据的长度 */

 left &#61; (u16_t)(p->tot_len - IP_HLEN);

 //要发送的数据长度大于0

 while (left)

 {

   fragsize &#61; LWIP_MIN(left, (u16_t)(nfb * 8));//4000  1480

   //申请分片pbuf结构

   rambuf &#61; pbuf_alloc(PBUF_LINK, IP_HLEN, PBUF_RAM);

   if (rambuf &#61;&#61; NULL) {

     goto memerr;

   }

   LWIP_ASSERT("this needs a pbuf in one piece!",

               (rambuf->len >&#61; (IP_HLEN)));

   //拷贝原始数据的部分到分片中

   SMEMCPY(rambuf->payload, original_iphdr, IP_HLEN);

   //得到分片包存储区域

   iphdr &#61; (struct ip_hdr *)rambuf->payload;

   //更新还需要拷贝的数据

   left_to_copy &#61; fragsize;

   while (left_to_copy)

   {

     struct pbuf_custom_ref *pcr;

     //定义记录已经拷贝的数据大小变量 plen

     u16_t plen &#61; (u16_t)(p->len - poff);

     //需要创建一个新pbuf拷贝剩下的

     newpbuflen &#61; LWIP_MIN(left_to_copy, plen);

     if (!newpbuflen)

     {

       poff &#61; 0;

       p &#61; p->next;

       continue;

     }

     //申请分片新的pbuf

     pcr &#61; ip_frag_alloc_pbuf_custom_ref();

     if (pcr &#61;&#61; NULL)

     {

       pbuf_free(rambuf);

       goto memerr;

     }

     /* 初始化这个pbuf */

     newpbuf &#61; pbuf_alloced_custom(PBUF_RAW,

                                   newpbuflen,

                                   PBUF_REF,

                                   &pcr->pc,

                                   (u8_t *)p->payload &#43; poff,

                                   newpbuflen);

     if (newpbuf &#61;&#61; NULL)

     {

       ip_frag_free_pbuf_custom_ref(pcr);

       pbuf_free(rambuf);

       goto memerr;

     }

     pbuf_ref(p);

     pcr->original &#61; p;

     pcr->pc.custom_free_function &#61; ipfrag_free_pbuf_custom;

     //将它添加到rambuf链的末尾

     pbuf_cat(rambuf, newpbuf);

     left_to_copy &#61; (u16_t)(left_to_copy - newpbuflen);

     if (left_to_copy)

     {

       poff &#61; 0;

       p &#61; p->next;

     }

   }

   //更新数据报的偏移量

   poff &#61; (u16_t)(poff &#43; newpbuflen);

   /* 处理分片 */

   last &#61; (left <&#61; netif->mtu - IP_HLEN);

   /* 设置新的偏移和MF标志 */

   tmp &#61; (IP_OFFMASK & (ofo));

   if (!last || mf_set)

   {

     tmp &#61; tmp | IP_MF;

   }

   //填写分片相关字段

   IPH_OFFSET_SET(iphdr, lwip_htons(tmp));

   IPH_LEN_SET(iphdr, lwip_htons((u16_t)(fragsize &#43; IP_HLEN)));

   IPH_CHKSUM_SET(iphdr, 0);

#if CHECKSUM_GEN_IP

   //校验和

   IF__NETIF_CHECKSUM_ENABLED(netif, NETIF_CHECKSUM_GEN_IP) {

     IPH_CHKSUM_SET(iphdr, inet_chksum(iphdr, IP_HLEN));

   }

#endif

   /* 发送数据报 */

   netif->output(netif, rambuf, dest);

   IPFRAG_STATS_INC(ip_frag.xmit);

   //释放分片空间

   pbuf_free(rambuf);

   //待发送数据减少

   left &#61; (u16_t)(left - fragsize);

   //分片偏移增加

   ofo &#61; (u16_t)(ofo &#43; nfb);

 }

 MIB2_STATS_INC(mib2.ipfragoks);

 return ERR_OK;

memerr:

 MIB2_STATS_INC(mib2.ipfragfails);

 return ERR_MEM;

}

如果你耐心看到这里&#xff0c;我感觉很欣慰&#xff0c;因为写文章不容易&#xff0c;特别是写这些比较深入的文章&#xff0c;看文章更不容易&#xff0c;特别是对暂时不需要的人。。。当然啦&#xff0c;我写文章并不是为了什么东西~我只是为了写而写。

热情提示&#xff1a;在公众号上不适合看源代码&#xff0c;如需认真研究源码的请移步到博客上看&#xff1a; https://jiejietop.cn/index.php/2019/04/10/ipfp/&#xff0c;源码可以全屏&#xff0c;高亮看起来更舒服...

b9e53bfaadbd1e5724da533eee4ea3aa.png

0e5829817bc1e6bafd5420cea0d37bbb.png你点的每个赞&#xff0c;我都认真当成了喜欢



推荐阅读
  • 微软头条实习生分享深度学习自学指南
    本文介绍了一位微软头条实习生自学深度学习的经验分享,包括学习资源推荐、重要基础知识的学习要点等。作者强调了学好Python和数学基础的重要性,并提供了一些建议。 ... [详细]
  • 云原生边缘计算之KubeEdge简介及功能特点
    本文介绍了云原生边缘计算中的KubeEdge系统,该系统是一个开源系统,用于将容器化应用程序编排功能扩展到Edge的主机。它基于Kubernetes构建,并为网络应用程序提供基础架构支持。同时,KubeEdge具有离线模式、基于Kubernetes的节点、群集、应用程序和设备管理、资源优化等特点。此外,KubeEdge还支持跨平台工作,在私有、公共和混合云中都可以运行。同时,KubeEdge还提供数据管理和数据分析管道引擎的支持。最后,本文还介绍了KubeEdge系统生成证书的方法。 ... [详细]
  • vue使用
    关键词: ... [详细]
  • 在Docker中,将主机目录挂载到容器中作为volume使用时,常常会遇到文件权限问题。这是因为容器内外的UID不同所导致的。本文介绍了解决这个问题的方法,包括使用gosu和suexec工具以及在Dockerfile中配置volume的权限。通过这些方法,可以避免在使用Docker时出现无写权限的情况。 ... [详细]
  • 本文介绍了lua语言中闭包的特性及其在模式匹配、日期处理、编译和模块化等方面的应用。lua中的闭包是严格遵循词法定界的第一类值,函数可以作为变量自由传递,也可以作为参数传递给其他函数。这些特性使得lua语言具有极大的灵活性,为程序开发带来了便利。 ... [详细]
  • 本文介绍了brain的意思、读音、翻译、用法、发音、词组、同反义词等内容,以及脑新东方在线英语词典的相关信息。还包括了brain的词汇搭配、形容词和名词的用法,以及与brain相关的短语和词组。此外,还介绍了与brain相关的医学术语和智囊团等相关内容。 ... [详细]
  • 本文介绍了设计师伊振华受邀参与沈阳市智慧城市运行管理中心项目的整体设计,并以数字赋能和创新驱动高质量发展的理念,建设了集成、智慧、高效的一体化城市综合管理平台,促进了城市的数字化转型。该中心被称为当代城市的智能心脏,为沈阳市的智慧城市建设做出了重要贡献。 ... [详细]
  • 本文介绍了数据库的存储结构及其重要性,强调了关系数据库范例中将逻辑存储与物理存储分开的必要性。通过逻辑结构和物理结构的分离,可以实现对物理存储的重新组织和数据库的迁移,而应用程序不会察觉到任何更改。文章还展示了Oracle数据库的逻辑结构和物理结构,并介绍了表空间的概念和作用。 ... [详细]
  • 本文讨论了在Windows 8上安装gvim中插件时出现的错误加载问题。作者将EasyMotion插件放在了正确的位置,但加载时却出现了错误。作者提供了下载链接和之前放置插件的位置,并列出了出现的错误信息。 ... [详细]
  • 本文讨论了使用差分约束系统求解House Man跳跃问题的思路与方法。给定一组不同高度,要求从最低点跳跃到最高点,每次跳跃的距离不超过D,并且不能改变给定的顺序。通过建立差分约束系统,将问题转化为图的建立和查询距离的问题。文章详细介绍了建立约束条件的方法,并使用SPFA算法判环并输出结果。同时还讨论了建边方向和跳跃顺序的关系。 ... [详细]
  • [译]技术公司十年经验的职场生涯回顾
    本文是一位在技术公司工作十年的职场人士对自己职业生涯的总结回顾。她的职业规划与众不同,令人深思又有趣。其中涉及到的内容有机器学习、创新创业以及引用了女性主义者在TED演讲中的部分讲义。文章表达了对职业生涯的愿望和希望,认为人类有能力不断改善自己。 ... [详细]
  • 本文介绍了计算机网络的定义和通信流程,包括客户端编译文件、二进制转换、三层路由设备等。同时,还介绍了计算机网络中常用的关键词,如MAC地址和IP地址。 ... [详细]
  • switch语句的一些用法及注意事项
    本文介绍了使用switch语句时的一些用法和注意事项,包括如何实现"fall through"、default语句的作用、在case语句中定义变量时可能出现的问题以及解决方法。同时也提到了C#严格控制switch分支不允许贯穿的规定。通过本文的介绍,读者可以更好地理解和使用switch语句。 ... [详细]
  • 本文介绍了在多平台下进行条件编译的必要性,以及具体的实现方法。通过示例代码展示了如何使用条件编译来实现不同平台的功能。最后总结了只要接口相同,不同平台下的编译运行结果也会相同。 ... [详细]
  • 3.223.28周学习总结中的贪心作业收获及困惑
    本文是对3.223.28周学习总结中的贪心作业进行总结,作者在解题过程中参考了他人的代码,但前提是要先理解题目并有解题思路。作者分享了自己在贪心作业中的收获,同时提到了一道让他困惑的题目,即input details部分引发的疑惑。 ... [详细]
author-avatar
曾雅芬珍念孟璇
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有