内核版本:3.4.39

很多项目涉及到IP分片的时候都是绕过去了,感觉分片挺难的。但是老这么做也不行啊,抽空分析了内核的分片处理函数ip_fragment,也不是特别复杂,感觉挺简单的,看来事情只有实际去做才知道。

int ip_fragment(struct sk_buff *skb, int (*output)(struct sk_buff *))
{struct iphdr *iph;int ptr;struct net_device *dev;struct sk_buff *skb2;unsigned int mtu, hlen, left, len, ll_rs;int offset;__be16 not_last_frag;struct rtable *rt = skb_rtable(skb);int err = 0;/* 获取路由里面的出口设备 */dev = rt->dst.dev;/* 获取IP头指针 */iph = ip_hdr(skb);/* 如果数据包携带不分片标志并且本地开启了pmtu发现(local_df==0),则需要* 给发送方返回一个icmp不可达报文。  */    if (unlikely((iph->frag_off & htons(IP_DF)) && !skb->local_df)) {IP_INC_STATS(dev_net(dev), IPSTATS_MIB_FRAGFAILS);icmp_send(skb, ICMP_DEST_UNREACH, ICMP_FRAG_NEEDED,htonl(ip_skb_dst_mtu(skb)));kfree_skb(skb);return -EMSGSIZE;}/** Setup starting values.*//* 获取IP首部长度 */hlen = iph->ihl * 4;/* 获取分片报文数据部分大小(mtu 最大传输单元) */mtu = dst_mtu(&rt->dst) - hlen; /* Size of data space */#ifdef CONFIG_BRIDGE_NETFILTERif (skb->nf_bridge)mtu -= nf_bridge_mtu_reduction(skb);
#endif/* 设置标志位,ip_defrag重组函数会去判断这个标志位 */    IPCB(skb)->flags |= IPSKB_FRAG_COMPLETE;/* When frag_list is given, use it. First, check its validity:* some transformers could create wrong frag_list or break existing* one, it is not prohibited. In this case fall back to copying.** LATER: this step can be merged to real generation of fragments,* we can switch to copy when see the first bad fragment.*//* 传输层如果已经进行了分片 */ if (skb_has_frag_list(skb)) {struct sk_buff *frag, *frag2;/* 同一个页面内的报文长度,不包括其它分片 */int first_len = skb_pagelen(skb);/** 以下四种情况是不能进行快速分片的* 1. 第一个报文长度大于mtu。* 2. 第一个报文长度不是8的倍数。* 3. 该报文自身就是分片报文,需要走慢速通道。* 4. 该报文是克隆的,因为快速分片是直接修改skb的,如果是克隆的*    则在其它地方存在引用,因此不能直接修改。*/if (first_len - hlen > mtu ||((first_len - hlen) & 7) ||ip_is_fragment(iph) ||skb_cloned(skb))goto slow_path;/* 判断其它分片是否满足快速分片要求 */skb_walk_frags(skb, frag) {/* 如果SKB分片超过mtu则进入慢速分片。* 如果SKB分片不是8的倍数并且不是最后一个分片也走慢速通道。* 如果SKB分片头部空间无法塞下一个IP头* 符合上述情况就进入慢速通道,和上面的慢速通道略有区别*/if (frag->len > mtu ||((frag->len & 7) && frag->next) ||skb_headroom(frag) < hlen)goto slow_path_clean;/* 如果某个地方在引用skb结构体* 就进入慢速通道。*/if (skb_shared(frag))goto slow_path_clean;/* 这里我有点疑问,为啥分片的skb不能有sk */BUG_ON(frag->sk);if (skb->sk) {/* 将所有片段都关联到同一个套接字* 设置套接字的回调函数*/frag->sk = skb->sk;frag->destructor = sock_wfree;}/* truesize 是skb的总长度,包括skb结构体和数据部分大小 * 这里将其长度从skb中移除,相当于分离开来*/skb->truesize -= frag->truesize;}/* Everything is OK. Generate! */err = 0;offset = 0;/* 保存那些独立分片 */frag = skb_shinfo(skb)->frag_list;/* 清空首个报文的分片指针,该指针已经保存在frag里 */skb_frag_list_init(skb);/* 第一个报文非线性数据区长度 */skb->data_len = first_len - skb_headlen(skb);/* 设置skb数据长度,包括线性区和非线性区 */skb->len = first_len;/* 设置ip头域里面数据长度 */iph->tot_len = htons(first_len);/* 设置MF标志位 */iph->frag_off = htons(IP_MF);/* 设置IP头域校验和 */ip_send_check(iph);/* 准备其它分片 */for (;;) {if (frag) {frag->ip_summed = CHECKSUM_NONE;/* 设置传输层首部指针 */skb_reset_transport_header(frag);/* 插入IP首部长度 */                                               __skb_push(frag, hlen);/* 设置网络层首部指针 */skb_reset_network_header(frag);/* 复制IP头部,因为是后续分片,所以要设置* 偏移值,长度和分片标志位,当然,校验和要重新计算。*/memcpy(skb_network_header(frag), iph, hlen);iph = ip_hdr(frag);iph->tot_len = htons(frag->len);/* 复制第一个分片报文的标志位,包括优先级,出口设备* netfilter子模块使用到的标志位等等。* 所有报文设置相同的属性*/ip_copy_metadata(frag, skb);/* 处理IP扩展选项,分片情况下扩展选项处理还是比较特殊的* 有些选项要求所有分片报文都要携带,有些选项只需要首个分片报文携带。* 详细情况参考RFC791*/if (offset == 0)ip_options_fragment(frag);/* 分片偏移值,等于之前报文长度累积和 */    /* 这里已经假定之前的报文长度都是一样的 */offset += skb->len - hlen;/* 一定能被8整除? *//* 没错,毕竟大小是之前分片的总和 */iph->frag_off = htons(offset>>3);/* 如果不是最后一个分片,就设置MF标识 */if (frag->next != NULL)iph->frag_off |= htons(IP_MF);/* Ready, complete checksum *//* 重新计算校验和 */ip_send_check(iph);}/* 调用发送接口发送出去 */err = output(skb);if (!err)IP_INC_STATS(dev_net(dev), IPSTATS_MIB_FRAGCREATES);/* 如果出错并且没有多余分片 */if (err || !frag)break;/* 当前skb传输完成,指向下一个分片 */skb = frag;frag = skb->next;skb->next = NULL;}/* 传输正常,增加MIB统计计数 */if (err == 0) {IP_INC_STATS(dev_net(dev), IPSTATS_MIB_FRAGOKS);return 0;}/* 当传输结束后,释放skb */while (frag) {skb = frag->next;kfree_skb(frag);frag = skb;}IP_INC_STATS(dev_net(dev), IPSTATS_MIB_FRAGFAILS);return err;slow_path_clean:/* 这一步到时没有很好理解⊙﹏⊙‖∣,了解的大牛可以说下 */skb_walk_frags(skb, frag2) {if (frag2 == frag)break;                   frag2->sk = NULL;frag2->destructor = NULL;skb->truesize += frag2->truesize;}}/* 慢速分片 */
slow_path:/* 先计算纯数据总长度,这个长度不包括IP头长 */left = skb->len - hlen;     /* Space per frame *//* 指向数据的起始位置 */ptr = hlen;        /* Where to start from *//* for bridged IP traffic encapsulated inside f.e. a vlan header,* we need to make room for the encapsulating header*//* (链路层预留空间) */ ll_rs = LL_RESERVED_SPACE_EXTRA(rt->dst.dev, nf_bridge_pad(skb));/** Fragment the datagram.*//* 获取偏移值,不从0开始是因为有可能需要分片的报文自身就是一个分片报文 */ offset = (ntohs(iph->frag_off) & IP_OFFSET) << 3;/* 可能的取值包括0和1 */not_last_frag = iph->frag_off & htons(IP_MF);/* 进行分片处理,left的初值就是数据总长 */while (left > 0) {len = left;/* 确保每个报文长度不超过MTU */if (len > mtu)len = mtu;/* 确保分片报文大小长度为8字节的整数倍,最后一个分片除外 */   if (len < left) {len &= ~7;}/* 分配一个新的skb buffer* 数据长度+IP头部长度+链路长度*/if ((skb2 = alloc_skb(len+hlen+ll_rs, GFP_ATOMIC)) == NULL) {NETDEBUG(KERN_INFO "IP: frag: no memory for new fragment!\n");err = -ENOMEM;goto fail;}/* 复制一些公共数据 */ip_copy_metadata(skb2, skb);/* 预留链路层首部空间 */skb_reserve(skb2, ll_rs);/* 插入数据部分和IP头 */skb_put(skb2, len + hlen);/* 重置网络头指针 */skb_reset_network_header(skb2);/* 设置传输层头部指针 */skb2->transport_header = skb2->network_header + hlen;/** 将每一个分片的skb包都关联到源包的socket */if (skb->sk)skb_set_owner_w(skb2, skb->sk);/**  Copy the packet header into the new buffer.*//* 复制IP报头 */skb_copy_from_linear_data(skb, skb_network_header(skb2), hlen);/** Copy a block of the IP datagram.*  从原始报文中的ptr起复制长度为len的数据到skb2中*/if (skb_copy_bits(skb, ptr, skb_transport_header(skb2), len))BUG();/* 从总长度中减去这个分片的长度,得到剩余部分的长度 */           left -= len;/**  接下来就是设置分片报文的网络层首部*/iph = ip_hdr(skb2);/* 设置片偏移 */iph->frag_off = htons((offset >> 3));/* ANK: dirty, but effective trick. Upgrade options only if* the segment to be fragmented was THE FIRST (otherwise,* options are already fixed) and make it ONCE* on the initial skb, so that all the following fragments* will inherit fixed options.*//* 处理IP选项,对于分片报文来说有些选项是需要每个报文必须携带,* 有些选项只需第一个分片报文携带就可以了。具体的操作可以参考RFC791*/if (offset == 0)ip_options_fragment(skb);/**    Added AC : If we are fragmenting a fragment that's not the*           last fragment then keep MF on each bit*//* 设置分片标识位 * 刚才提到not_last_frag可以是0或者1,取决于它在原始分片报文的值*/ if (left > 0 || not_last_frag)iph->frag_off |= htons(IP_MF);/* 更新数据指针,指向下一个分片报文的起始复制位置 */ptr += len;/* 更新片偏移字段 */offset += len;/* 更新长度字段 */iph->tot_len = htons(len + hlen);/* 更新校验和字段 */ip_send_check(iph);/* 配置完成发送 */err = output(skb2);if (err)goto fail;IP_INC_STATS(dev_net(dev), IPSTATS_MIB_FRAGCREATES);}/* 分片结束,释放原始报文* 这个和快速分片不同,快速分片是发送原始分片* 慢速分片是新建skb然后复制发送*/kfree_skb(skb);IP_INC_STATS(dev_net(dev), IPSTATS_MIB_FRAGOKS);return err;fail:kfree_skb(skb);IP_INC_STATS(dev_net(dev), IPSTATS_MIB_FRAGFAILS);return err;
}
EXPORT_SYMBOL(ip_fragment);

参考文档:

1. linux内核ip分片函数ip_fragment解析  https://blog.csdn.net/force_eagle/article/details/4555314

2. SKB结构体分析 http://vger.kernel.org/~davem/skb.html

tcp/ip 协议栈Linux源码分析四 IPv4分片 ip_fragment函数分析相关推荐

  1. tcp/ip 协议栈Linux源码分析五 IPv6分片报文重组分析一

    做防火墙模块的时候遇到过IPv6分片报文处理的问题是,当时的问题是netfilter无法基于传输层的端口拦截IPv6分片报文,但是IPv4的分片报文可以.分析了内核源码得知是因为netfilter的连 ...

  2. tcp/ip 协议栈Linux源码分析一 IPv4分片报文重组分析一

    内核版本:3.4.39 之前因工作原因接触到了IPv4 报文重组这个话题,一直以来对这个重组流程不是很清楚,所以很多功能的实现都避开了分片报文的处理,一方面是因为重组比较复杂,另一方面是经验不多无从下 ...

  3. tcp/ip 协议栈Linux源码分析三 IPv4分片报文重组分析三

    继续上篇,上次讲到了分片队列的查找操作,剩下的就是分片队列插入和重组两个部分了,这个也是分片重组的关键部分. 将收到的分片插入到分片队列是由函数inet_frag_queue()函数完成,这个函数比较 ...

  4. tcp/ip 协议栈Linux源码分析二 IPv4分片报文重组分析二

    继续接着上篇讲,之前我们说过,收到分片报文后首先会检查分片报文所占内存是否过大,如果超过阈值的话就要调用ip_evictor函数去释放一些旧的分片队列,关于如何释放分片队列资源上一篇已经总结完成,接下 ...

  5. tcp/ip 协议栈Linux内核源码分析15 udp套接字接收流程二

    内核版本:3.4.39 上篇我们分析了UDP套接字如何接收数据的流程,最终它是在内核套接字的接收队列里取出报文,剩下的问题就是谁会去写入这个队列,当然,这部分工作由内核来完成,本篇剩下的文章主要分析内 ...

  6. tcp/ip 协议栈Linux内核源码分析12 udp套接字发送流程一

    内核版本:3.4.39 因为过往的开发工作中既包括内核网络层模块的开发,又包括应用层程序的开发,所以对于网络数据的通信有那么一些了解.但是对于网络通信过程中,内核和应用层之间接口是如何运作的不是很清楚 ...

  7. tcp/ip 协议栈Linux内核源码分析14 udp套接字接收流程一

    内核版本:3.4.39 前面两篇文章分析了UDP套接字从应用层发送数据到内核层的处理流程,这里继续分析相反的流程,看看数据是怎么从内核送到应用层的. 与发送类似,内核也提供了多个接收数据的系统调用接口 ...

  8. tcp/ip 协议栈Linux内核源码分析九 IPv6分片ip6_fragment 分析

    内核版本:3.4.39 IPv6的分片流程和IPv4基本一致,这一点内核源码作者也说了.流程比较简单,分片的时候判断是否满足快速分片,满足的话直接一个接一个加上分片扩展选项发送出去,不满足的话就只能走 ...

  9. tcp/ip 协议栈Linux内核源码分析七 路由子系统分析二 策略路由

    内核版本:3.4.39 策略路由就是根据配置策略查找路由表,早期的Linux版本是不支持策略路由的,默认的查找策略就是先查找local路由表,找不到再继续查找main表,当支持策略路由功能时,内核最多 ...

最新文章

  1. java中引用类型_您真的了解Java中的4种引用类型吗?
  2. OCFS2+ASM 的RAC安装文档
  3. java base64 编码 类_java base64编码和解码的三种方式 | 学步园
  4. FFmpeg Filter基本使用
  5. 如何快速学习freemarker以及使用经验
  6. javascript 内置对象学习 笔记:
  7. 2016-2017-2(点集拓扑56, 点集拓扑56)
  8. GC Roots 是什么?哪些对象可以作为 GC Root
  9. PADS2007pads9.2使用技巧
  10. 天气预报:强势力的“.fans+体育”旋风正席卷全球!
  11. 车载吸尘器方案-无刷马达运用2
  12. 微信小程序账号注册初始化环境搭建
  13. 小学语文知识点总结(一)
  14. win10未能解析服务器名,win10系统提示“无法解析服务器的dns地址”的修复方法...
  15. 杨辉三角(Python-动态规划)
  16. sql2java-excel(二):基于apache poi实现数据库表的导出的spring web支持
  17. 编程 - 变量的命名方法
  18. python 人工智能编程_最适合人工智能开发的5种编程语言
  19. org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): com.qiang.dao.UserM
  20. 双馈风力发电系统,双pwm变换器控制系统,采用直接转矩输入代替风力发电机

热门文章

  1. String[]转化暴露“思维误区”
  2. learnpython有中文版吗_简介 | Learn Python the Hard Way 中文版
  3. everythingtoolbar.dll”或它的一个依赖项。_ASP.NET Core依赖注入最佳实践、提示和技巧...
  4. 4.4 机器学习系统设计--垃圾邮件分类-机器学习笔记-斯坦福吴恩达教授
  5. KEIL MDK 仿真时程序”乱跑“问题
  6. TCL系列 - incr命令
  7. Android4.0添加java层服务
  8. 【树莓派】更新系统镜像下载地址,可能是最简单粗暴的树莓派搭建个人网站教程...
  9. 【AI2】喜讯!app inventor最近进展,实现流媒体视频播放
  10. 【个人成长学习讨论小组】练习2:角色