第二十二章:因特网协议第四版(IPv4):处理分段

IP分段:

无论是本地产生的封包还是转发封包都会调用dst_output函数,所以,由dst_output调用的ip_fragment函数也可以在这两种情况下执行。

较新版的L4协议会事先协助分段,L4协议不再把一个会分段的缓冲区传给IP层,而是传递一组和pmtu相配的缓冲区。这样,IP层的分段工作就只是为每个已形成的数据片段建立IP头而已。

当前,分段可以用两种方式执行:快速法和慢速法。两种方式都由ip_fragment处理。ip_fragment会把等待传送的缓冲区转变为实际的封包。下面是两个分段用到的辅助函数:

    ip_dont_fragment:决定IP封包是否可以分段(根据路径MTU发现功能的配置)。

    ip_options_fragment:修改第一个片段的IP头,使其可以被后续片段循环利用。参见第十九章“IP选项”一节。

ip_fragment函数:

ip_fragment可以处理两种分段方式,我们先看公共部分做些什么。

原型:int ip_fragment(struct sk_buff *skb , int (*output)(struct sk_buff *))

参数:

    skb:包含要被分段的IP封包的缓冲区,此封包含由一个初始化的IP头,而这个IP头将被调整,用于拷贝到有片段中。

    output:用于传输片段的函数。

ip_fragment一开始会对一些变量做初始化。如果输入的IP封包设置了DF标志而无法分段,则ip_fragment会回传一个ICMP报文并丢弃该封包。

当ip_fragment接收了一个其数据已经被分段的sk_buff时,就会使用快速分段。例如,对于使用ip_append_data,ip_push_pending_frames的L4协议或者使用ip_queue_xmit的L4协议所产生的封包,就会发生这样的情况。

慢速法则用在所有其他情况,如被转发的封包,抵达dst_output之前没有分段的本地流量,快速分段法被关闭等。无论是快速法还是慢速法,当任何片段传输失败,ip_fragment会立刻返回错误,后续片段将无法传输,所以此时,目的主机只会收到IP片段,无法重组整个IP封包。

慢速分段:

慢速分段的流程仅包括把IP封包分割成一些片段,片段尺寸根据外出接口的MTU或PMTU决定。

    left = skb->len - hlen;//left的初值设为IP封包的长度,hlen是L2报头长度。

    ptr = raw + hlen;//要被分段的封包里的偏移量

    offset = (ntohs(iph->frag_off) & IP_OFFSET) << 3;//从ip头中取出片段偏移量字段

    not_last_frag = iph->frag_off & htons(IP_MF);//当前片段是否是最后一个片段

注意ptr和offset的区别。持有offset是因为正被分段的封包可能是另一个较大封包的一个片段,如果是,offset表示当前片段在原有封包内的偏移量,否则offset就是0。ptr是我们正在分段的封包内的偏移量,而且会随着循环进程改变。

接着,ip_fragment会进入循环,为每个片段建立一个新缓冲区skb2,一个片段缓冲区的尺寸包括IP有效载荷的尺寸,IP头的尺寸,L2头的尺寸。

    while(left > 0)

    {

        len = left;

        if(len > mtu)

            len = mtu;

        if(len < left)

            len &= ~7;//除了最后一个分片,其他分片要对齐8字节边界,即8的整数倍

        if((skb2 = alloc_skb(len+hlen+ll_rs,GFP_ATOMIC)  == NULL)

        {

            goto fail;

        }

    }

接着,把一些原有skb_buff结构的字段的值拷贝到新分配的skb2。

然后新分配的缓冲区与做传输的套接字关联(如果有套接字的话)。

现在,把一些真实数据填入skb2,这项任务分为两部分,拷贝IP头和拷贝原有封包的一段有效载荷到片段内。

并非所有ip选项都必须拷贝到所有片段,只有第一个片段包含所有选项。ip_options_fragment会清理原有IP封包相关的ip_opt结构的内容,使得第一个片段之后的片段不会包含不需要的选项。

当不是最后一个片段时,设置MF标志。

最后,慢速法更新报头长度(包括选项),用ip_send_check计算校验和。然后使用output虚拟函数传输该片段。IPv4所用的output虚拟函数就是ip_finish_output。

快速分段:

当ip_fragment看见输入的skb缓冲区的frag_list指针不为NULL时,就会尝试快速法。在开始快速法之前还需要做一些健康检查:

    每个片段的尺寸不应该超过pmtu

    只有最后一个片段的L3有效载荷可以不是8字节的倍数。

    每个片段的头部必须有足够的空间容纳稍后的L2报头。

......

IP重组:

通常ip重组发生在目的主机的ip层,而路由器只负责转发。但是,路由器在下面两种情况也会需要重组:

    IP头包含Route Alert选项,强迫路由器处理该封包。

    Netfilter必须查看封包,才能决定对该封包做什么。Netfilter会强迫进行重组的钩子函数是NF_IP_PRE_ROUTING和NF_IP_LOCAL_OUT。

IP片段hash表的组织:

    接收IP片段之后,会将其组织成一张hash表

每个正被重组的IP封包都会有一个ipq实例,其内有一个片段链表组成。每个片段的偏移量存储在sk_buff里名为cb的字段内,该字段是一个缓冲区,可以由各种网络分层存储私有信息。

涉及重组的函数:

重组的主要函数是ip_defrag。还有下面这些被直接或间接调用的辅助函数:

    ip_evictor:逐一删除不完整的ipq封包结构。

    ip_find:找出和正在被处理的片段相关的封包。

    ip_frag_queue:把指定的片段插入和同一IP封包相关的片段链表(ipq链表)。

    ip_frag_reasm:一旦所有片段都被接收到之后,重组原有IP包。

    ip_frag_destroy:删除传进来的ipq结构以及相关的所有IP片段,然后更新全局计数器ip_frag_mem。

    ipq_put:递减传进来的ipq结构的引用计数。

    ipq_kill:把一个ipq结构标记为可删除,因为有些片段没有准时到达。

ip_defrag函数:

ip_defrag的首要任务是搜寻片段所属的封包,为此,调用ip_find。若该片段刚好是封包的第一个片段(已抵达时间算,不一定是其在封包中的位置),则ip_find会调用ip_frag_create新建一个ipq实例,最终ip_find会返回一个ipq实例,其要么已经存在,要么是新创建的。

原型:struct sk_buff *ip_defrag(struct sk_buff *skb,u32 user);

该函数会先检查IP片段所用的内存,如果已经抵达可配置的阈值,就以ip_evictor触发垃圾收集。

如果skbs是新封包的第一个片段(已抵达时间算,不一定是其在封包中的位置),ip_find会新建一个ipq结构,否则,仅返回找到的ipq结构实例,若是新建的ipq,则会通过ip_frag_queue加入ipq_hash。

最后,该片段会排入队列。(实际上就是加入了一个ipq结构,注意和ipq结构加入ipq_hash区分,参考上图22-1)。

接收到所用片段后,将其重组并传给较高层。    

ip_frag_queue函数:

原型:static void ip_frag_queue(struct ipq *qp,struct sk_buff *skb ),其中qp是该片段所属的封包。

主要任务:

    弄清输入片段处于原有封包何处。

    是否为封包最后的片段,如果是,从中取出IP封包的长度。

    把该片段插入到链表中,该链表中的片段关联同一IP封包,还要处理可能的重叠问题。

    更新ipq结构中由垃圾收集任务所用的字段(时间戳和所用的内存)。

    必要时,让硬件中计算的L4校验和失效。

L4校验和:

如果入口设备支持L4硬件校验和计算,入口IP片段的L4校验和可能已经算好了,当片段重组时,会用csum_add把个别片段的校验和结合起来,然后把结构存在重新组合后的缓冲区。然而,当下列条件满足时,硬件校验和会失效:

    除最后一个片段外,它的尺寸不是8的倍数时,会被ip_defrag截断为8的倍数,此时,硬件校验和会失效。

    一个片段和至少一个先前已经接收的片段重叠,此时会删除重叠部分,所以,校验和必须失效。

垃圾收集:

内核为IP片段实现两种垃圾收集:

    系统内存使用限度

    重组定时器

IP片段重组子系统的内存使用上限存储在sysctl_ ipfrag_high__thresh变量中,该值可以通过/proc来修改。全局变量ip_frag_mem代表当前由片段使用的内存。

当一个先IP封包的第一个片段加入ipq_hash时,内核启动一个重组定时器,此定时器用于丢弃不完整封包的所有片段。

hash表重新组织:

图22-1是IP片段等着被重组时在内存中的组织方式。为了避免hash冲突,内核会定期使用不同的hash函数来重新组织表中的所有IP片段。片段的重新组织是有一个定时器执行的,该定时器每10分钟执行一次(到期时间可以通过/proc接口配置)。

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值