linux协议栈 IPv4之发送过程中的分段处理ip_fragment()
目录
1 分片位置
2 ip_fragment()代码分析
1 分片位置
分段是网络层的一个重要任务,网络层需要对两方面的IP数据包进行分段:
- 本地产生的数据包;
- 转发的数据包;
这两种数据包的长度如果超过了出口设备的MTU(或者PMTU),则网络层必须先对数据包进行分段,使其适配出口设备的MTU。
IPv4使用 ip_fragment() 处理分段,在设计时,要求该函数能够处理所有的情况,但是在实现过程中,充分考虑了实际可能的情况,对某些场景的处理进行了优化,下面分情况介绍。
2 ip_fragment()代码分析
/** This IP datagram is too large to be sent in one piece. Break it up into* smaller pieces (each of size equal to IP header plus* a block of the data of the original IP data part) that will yet fit in a* single device frame, and queue such a frame for sending.*/
int ip_fragment(struct sk_buff *skb, int (*output)(struct sk_buff*))
{struct iphdr *iph;int raw = 0;int ptr;struct net_device *dev;struct sk_buff *skb2;unsigned int mtu, hlen, left, len, ll_rs, pad;int offset;__be16 not_last_frag;struct rtable *rt = (struct rtable*)skb->dst;int err = 0;dev = rt->u.dst.dev;/** Point into the IP datagram header.*/iph = ip_hdr(skb);// 一旦进入该函数,说明skb过大,需要进行IP分片,但是又设置了DF标记或者本身是抑制分片的,// 那么发送失败,向源端发送ICMP报文,这里主要是为forward数据所判断。if (unlikely((iph->frag_off & htons(IP_DF)) && !skb->local_df)) {IP_INC_STATS(IPSTATS_MIB_FRAGFAILS);icmp_send(skb, ICMP_DEST_UNREACH, ICMP_FRAG_NEEDED, htonl(ip_skb_dst_mtu(skb)));kfree_skb(skb);return -EMSGSIZE;}// hlen保存IP首部长度hlen = iph->ihl * 4;// mtu代表每个IP片段能够容纳的L4载荷,所以需要在MTU基础上去掉IP首部的开销mtu = dst_mtu(&rt->u.dst) - hlen;// 设置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.*//** 如果4层将数据包分片了,那么就会把这些数据包放到skb的frag_list链表中,* 因此这里首先先判断frag_list链表是否为空,为空的话将会进行slow 分片*/if (skb_shinfo(skb)->frag_list) {struct sk_buff *frag;/** 取得第一个数据报的len.当sk_write_queue队列被flush后,* 除了第一个切好包的另外的包都会加入到frag_list中(接口ip_push_pending_frames),* 而这里需要得到的第一个包(也就是本身这个sk_buff)的长度。* first_len是第一个skb中所有数据的总长度,包括线性缓冲区和frags[]数组*/int first_len = skb_pagelen(skb);int truesizes = 0;/** 接下来的判断都是为了确定能进行fast分片。分片不能被共享,* 这是因为在fast path 中,需要加给每个分片不同的ip头(而并* 不会复制每个分片)。因此在fast path中是不可接受的。而在* slow path中,就算有共享也无所谓,因为他会复制每一个分片,* 使用一个新的buff。 *//** 判断第一个包长度是否符合一些限制(包括mtu,mf位等一些限制).* 如果第一个数据报的len没有包含mtu的大小这里之所以要把第一个* 切好片的数据包单独拿出来检测,是因为一些域是第一个包所独有* 的(比如IP_MF要为1)。这里由于这个mtu是不包括hlen的mtu,因此* 需要减去一个hlen。 */// 1. 条件1说明高层协议切割的skb的长度还是太长了;// 2. 第一个片段长度必须是8字节对齐的(IP片段偏移量是8字节对齐决定的);// 3. 偏移量在下面的分段过程中才会进行设置,这里不应该有值;// 4. skb不能是被共享的,因为快速路径上不会进行skb拷贝,而是直接修改skb;// 上述4个条件有任何一个不满足,那么就用慢速路径完成分片if (first_len - hlen > mtu || ((first_len - hlen) & 7) ||(iph->frag_off & htons(IP_MF|IP_OFFSET)) || skb_cloned(skb)){goto slow_path;}// 遍历frag_list列表,检查是否所有的分片是否符合快速分片处理for (frag = skb_shinfo(skb)->frag_list; frag; frag = frag->next) {// 这4个条件和前面对第一个片段的检查类似if (frag->len > mtu || ((frag->len & 7) && frag->next) || skb_headroom(frag) < hlen)goto slow_path;if (skb_shared(frag))goto slow_path;BUG_ON(frag->sk);// 设置skb的属主if (skb->sk) {sock_hold(skb->sk);frag->sk = skb->sk;frag->destructor = sock_wfree;truesizes += frag->truesize;}}// 对第一个分片的特殊处理err = 0;offset = 0;frag = skb_shinfo(skb)->frag_list;skb_shinfo(skb)->frag_list = NULL;skb->data_len = first_len - skb_headlen(skb);skb->truesize -= truesizes;skb->len = first_len;iph->tot_len = htons(first_len);iph->frag_off = htons(IP_MF);ip_send_check(iph);// 循环处理后面所有的分片for (;;) {if (frag) {frag->ip_summed = CHECKSUM_NONE;skb_reset_transport_header(frag);__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);ip_copy_metadata(frag, skb);if (offset == 0)ip_options_fragment(frag);offset += skb->len - hlen;iph->frag_off = htons(offset>>3);if (frag->next != NULL)iph->frag_off |= htons(IP_MF);/* Ready, complete checksum */ip_send_check(iph);}// 继续发送过程,先发送首片ip的skb自己,后面继续发送frag。err = output(skb);if (!err)IP_INC_STATS(IPSTATS_MIB_FRAGCREATES);if (err || !frag)break;// 继续处理下一个分片skb = frag;frag = skb->next;skb->next = NULL;}// 一切正常,分片过程从这里返回if (err == 0) {IP_INC_STATS(IPSTATS_MIB_FRAGOKS);return 0;}// 分片或者发送过程失败了,释放所有的skb分片while (frag) {skb = frag->next;kfree_skb(frag);frag = skb;}// 快速路径结束IP_INC_STATS(IPSTATS_MIB_FRAGFAILS);return err;}//end of (skb_shinfo(skb)->frag_list)slow_path:// left保存整个IP报文中剩余需要分段的报文长度,在下面分段过程中会逐渐减小,// 直到为0说明分段过程结束left = skb->len - hlen; /* Space per frame */// ptr指向L4载荷的偏移,初始值指向L4报文的开头ptr = raw + 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*/// L2的特殊使用,处理桥接、VLAN、PPPOE相关MTU,这里认为pad=0即可pad = nf_bridge_pad(skb);// link layer reserved space,即链路层保留长度,是指应该在skb线性缓冲区的首部// 应该为L2保留的长度,主要包括mac层首部ll_rs = LL_RESERVED_SPACE_EXTRA(rt->u.dst.dev, pad);mtu -= pad;// offset为偏移量,不包括DF、MF标记offset = (ntohs(iph->frag_off) & IP_OFFSET) << 3;// 顾名思义,标识是否是IP报文的最后一个片段,因为最后一个片段的MF标记是0not_last_frag = iph->frag_off & htons(IP_MF);// 循环创建新的skb2,然后拷贝数据,完成分段(left==0)while (left > 0) {// 调整len为本轮循环分片中能够容纳的L4报文载荷长度len = left;/* IF: it doesn't fit, use 'mtu' - the data space left */if (len > mtu)len = mtu;// IP报文首部的片偏移字段格式决定了非最后一个IP片段的偏移量必须是8字节对齐的if (len < left) {len &= ~7;}// 分配skb2,长度包括三部分:len代表的L4报文部分;hlen代表的IP报文首部;ll_rs代表的L2报文首部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;}// 首先设置skb2中的各个字段ip_copy_metadata(skb2, skb);skb_reserve(skb2, ll_rs);skb_put(skb2, len + hlen);skb_reset_network_header(skb2);skb2->transport_header = skb2->network_header + hlen;// 设置owner,内存的消耗将会记录到owner的账上if (skb->sk)skb_set_owner_w(skb2, skb->sk);// 先从源skb的线性缓冲区将IP报文首部拷贝到skb2的线性缓冲区,因为内存上是连续的,// 所以直接使用memcpy拷贝即可skb_copy_from_linear_data(skb, skb_network_header(skb2), hlen);// 下面的数据拷贝就复杂了一些,因为慢速路径要能够处理任何可能的skb的数据组织方式。// skb_copy_bits()将从skb->data开始偏移的ptr位置开始拷贝数据,共拷贝len字节到// skb2传输层开始的位置,注意这里在处理ptr偏移会考虑页缓冲区和frag_list两种情况if (skb_copy_bits(skb, ptr, skb_transport_header(skb2), len))BUG();// 分段完成,left减去1个片段的长度left -= len;// 填充IP首部,主要是选项和偏移字段iph = ip_hdr(skb2);iph->frag_off = htons((offset >> 3));// ip_options_fragment()会在第一个IP片段的基础上将后面片段不需要的IP选项删除,// 这样后面的IP片段直接继承即可(前面已经拷贝),无需重新设定选项,所以这里只// 在第一个片段分片过程中调用ip_options_fragment()if (offset == 0)ip_options_fragment(skb);//不是最后一个包,因此设置mf位 if (left > 0 || not_last_frag)iph->frag_off |= htons(IP_MF);// 更新ptr和offset,为下一个分片做好准备ptr += len;offset += len;// IP首部的total字段表示的是IP片段的长度iph->tot_len = htons(len + hlen);// 计算IP首部校验和ip_send_check(iph);// 调用发送接口继续发送过程err = output(skb2);if (err)goto fail;IP_INC_STATS(IPSTATS_MIB_FRAGCREATES);}// 原有的IP报文都已经被分割成一个个新的skb,所以处理结束后,原来的skb需要释放kfree_skb(skb);IP_INC_STATS(IPSTATS_MIB_FRAGOKS);return err;
fail:kfree_skb(skb);IP_INC_STATS(IPSTATS_MIB_FRAGFAILS);return err;
}
linux协议栈 IPv4之发送过程中的分段处理ip_fragment()相关推荐
- linux内核协议栈 IPv4之发送接口 ip_queue_xmi()【TCP使用】
ip_queue_xmi() 接口主要由tcp使用,主要就干了两件事: 如果还没有查询过路由,那么查路由: 构造IP首部,然后调用IPv4协议内部的接口继续发送: 注意:该函数并没有处理分段,所以调用 ...
- Linux 系统 uos / deepin 系统安装过程中 最全常用命令及问题 总结
本博客已暂停更新,为了您更好的阅读,请转至新博客https://www.whbwiki.com/1296.html 此教程用来解决uos/deepin系统安装过程中的一系列问题,其他linux发行版适 ...
- WSL(windows subsystem for linux)安装错误:安装过程中遇到错误,但可以继续安装。组件: ‘WSL 内核‘ 错误代码: 0x80072f78解决方法
文章目录 问题来源 解决 参考 问题来源 使用管理员身份打开powershell,输入 wsl --install 之后等待安装: 结果,出现如下问题: PS C:\WINDOWS\system32& ...
- 高通android 7.0彩信发送过程中使用到的google pdu
对于彩信与数据库的交互操作,google并没有将这部分代码放在Mms中,而是放在framework中的pdu部分. 具体代码路径是在:opt\telephony\src\java\com\google ...
- Mosquitto 在Linux服务器上的部署过程中以及踩过的坑
1. wget http://mosquitto.org/files/source/mosquitto-1.4.9.tar.gz 2.tar zxfv mosquitto-1.4.9.tar.gz 2 ...
- 测试录制的电话拨码声音信号在发送过程中的问题
简 介: 对比了实际录制的手机音频信号的波形以及频谱,可以看到,录制双音频信号出现了较大的变化: 拨码的幅值降低了很多,需要放大20倍才能够与录制的双音频相当: 双音频中的高频区的分量很弱,几乎看不到 ...
- oracle oui25031 linux,搭建Oracle 10g RAC过程中出现 OUI-25031 的解决办法
解决办法如下: OUI-25031错误处理:手动配置VIPCA 手动修改vipca设置(两节点都要执行) [root@HHDB20 ~]# cd /u01/app/Oracle/crs/bin/ [r ...
- 拆解 Linux 网络包发送过程
半年前我以源码的方式描述了网络包的接收过程.之后不断有粉丝提醒我还没聊发送过程呢.好,安排! 在开始今天的文章之前,我先来请大家思考几个小问题. 问1:我们在查看内核发送数据消耗的 CPU 时,是应该 ...
- 图解分析 Linux 网络包发送过程
大家好,下面的文章转发一个鹅厂同学的文章,这篇文章从应用到内核,写的非常不错,希望大家分析某个技术也可以从这方面入手. ----- 大家好,我是飞哥! 半年前我以源码的方式描述了网络包的接收过程.之后 ...
最新文章
- 【转】PHP获取重定向URL的几种方法
- 将C/C++程序的变量数据导入到MATLAB中的方法
- Spring和shiro整合 logout 配置方式
- Java 网络 socket 编程
- 磁盘里竟然还有这个东西!多亏这个1.5M大小的神器工具发现了它
- 人工智能和机器学习的前世今生
- Ubuntu被曝严重漏洞:切换系统语言+输入几行命令,就能获取root权限(仅支持ubuntu桌面版、提权)
- 来吧,1分钟带你玩转Kafka
- SpringBoot 利用过滤器Filter修改请求url地址
- SM系列国密算法(转)
- RHEL/Centos下VSFTPD服务器搭建
- Qt:Exception at 0xeefde9, code:0x0000005: read access violation at: 0x0, flags = 0x0(first chance)
- php微信公众平台关注后欢迎语的设置,关注公众号的欢迎语怎么设置?公众号欢迎语怎么加链接?...
- Cadence制作flash焊盘时找不到
- 无线鼠标,滚轮不灵,迟钝多转卡怎么办
- Spring DI和AOP简介(一)
- esp8266基于arduino一键配网掉电保存WIFI账号密码
- 微信隐藏的功能和技巧
- python Linux下的安装
- 大数据分析软件都有哪些平台?
热门文章
- 8DOER: Dual Cross-Shared RNN for Aspect Term-Polarity Co-Extraction(2020.10.22)
- 倭黑猩猩机器人_科学美国人60秒:倭黑猩猩妈妈监管儿子的私生活
- 洛伦兹力的matlab求解,问:由安培力推导洛伦兹力的过程?
- 有人不理解,有人不屑,到底什么是UXD
- ubuntu可爱的玩具:小猫咪 oneko
- ghost linux引导修复工具,GhostBSD 19.10 发布,UEFI多重引导的修复
- Field error in object ‘xxx‘ on field ‘xxx‘: rejected value [xxx]
- javaMailSender 发送邮件设置昵称
- 长沙理工大学第十二届ACM大赛-重现赛
- 自定义控件---继承ViewGroup类方式(循序渐进之第3步效果----图片左右拖动+RadioGroup切换效果)