Linux TCP/IP协议栈关于IP分片重组的实现

最近学习网络层协议的时候,注意到了IP协议中数据包分片的问题。下图是IP协议头的数据字段的示意:

如图所示,IP协议理论上允许的最大IP数据报为65535字节(16位来表示包总长)。但是因为协议栈网络层下面的数据链路层一般允许的帧长远远小于这个值,例如以太网的MTU(即Maximum Transmission Unit,最大传输单元)通常在1500字节左右。所以较大的IP数据包会被分片传递给数据链路层发送,分片的IP数据报可能会以不同的路径传输到接收主机,接收主机通过一系列的重组,将其还原为一个完整的IP数据报,再提交给上层协议处理。上图中的红色字段便是被设计用来处理IP数据包分片和重组的。

那么,这三个字段如何实现对分片进行表示呢?

  • 首先是标示符(16位),协议栈应该保证来自同一个数据报的若干分片必须有一样的值。
  • 其次是标志位3位分别是R(保留位,未使用)位、DF(Do not Fragment,不允许分段)位和MF(More Fragment)位。MF位为1表示当前数据报还有更多的分片,为0表示当前分片是该数据报最后一个分片。
  • 最后是偏移量(13位),表示当前数据报分片数据起始位置在完整数据报的偏移,注意这里一个单位代表8个字节。即这里的值如果是185,则代表该分片在完整数据报的偏移是185*8=1480字节。

RFC815文档(IP datagram reassembly algorithms)文档定义了IP分片重组的算法。

操作系统内核协议栈(以下简称协议栈)只需要申请一块和原始数据报相同大小的内存空间,然后将这些数据报分片按照其偏移拷贝到指定的位置就能恢复出原先的数据报了。目前看起来一切都很清晰,不是么?但我的问题就出在这个判别数据报分片的方法上。因为标示字段只有16位,所以理论上只有65536个不同的表示。当一台拥有着超过65536个活跃连接用户的服务器时,理论上会出现重复的数据报分片。即使连接的客户没这么多,但是从概率上如果只用这个标示符的话,依旧会出现可能造成混乱的数据报分片。

协议栈究竟如何处理这个问题呢?本文不讨论IP分片可能会造成的Dos攻击和效率损失的问题,单就研讨一旦出现了IP数据报分片,协议栈如何处理的问题。妄加猜测是没有意义的,直接查阅Linux内核协议栈源码再清楚不过了。这里基于Linux-3.12.6内核源码来解释这个问题。(为什么是这个版本?因为我的机器上正好有这个版本…)

Linux内核协议栈关于IPV4协议的代码都在net/ipv4目录下。从文件名上分析,ip_fragment.c文件显然就是IP分片处理的源代码。(好吧,这种方法很不严谨…)

我们的目的是找到IP数据报组合的过程,至于分片什么的大家有兴趣的话可以自己去研究,理论上分片要比重组容易一些。

一番周折后我们找到了IP数据报重组的函数:

int ip_defrag(struct sk_buff *skb, u32 user);
 

我怎么知道这个函数是碎片重组的?因为defrag这个单词就是碎片重组的意思……咳咳,看来良好的函数命名还是很重要的。开个玩笑,函数前面的注释说明了这个函数的任务是处理IP分片组合:Process an incoming IP datagram fragment.

这个函数参数是struct sk_buff结构的指针,而网络数据包在内核协议栈中就是以struct sk_buff结构进行传送的。这个函数的作用就是尝试组合数据报,成功组合的话返回一个struct sk_buff结构的指针。

函数代码如下(Linux-3.12.6):

/* Process an incoming IP datagram fragment. */
int ip_defrag(struct sk_buff *skb, u32 user)
{
        struct ipq *qp;
        struct net *net;

        net = skb->dev ? dev_net(skb->dev) : dev_net(skb_dst(skb)->dev);
        IP_INC_STATS_BH(net, IPSTATS_MIB_REASMREQDS);

        /* Start by cleaning up the memory. */
        ip_evictor(net);

        /* Lookup (or create) queue header */
        if ((qp = ip_find(net, ip_hdr(skb), user)) != NULL) {
                int ret;

                spin_lock(&qp->q.lock);

                ret = ip_frag_queue(qp, skb);

                spin_unlock(&qp->q.lock);
                ipq_put(qp);
                return ret;
        }

        IP_INC_STATS_BH(net, IPSTATS_MIB_REASMFAILS);
        kfree_skb(skb);
        return -ENOMEM;
}
 

ip_evictor对分片处理的内存占用进行统计,超出了使用范围的话会进行内存的释放,避免遭受恶意的网络攻击(比如恶意的制造IP分片,使得目标机器内存大量消耗等等)。这不是我们要分析的重点,跳过它就是ip_find函数了,函数头部的注释告诉我们这个函数的职责是”在不完整的IP数据报队列中寻找当前数据报分片的位置,没有找到的话就为当前分片重新建立一个队列”。

函数代码如下(Linux-3.12.6):

 /* Find the correct entry in the "incomplete datagrams" queue for
 * this IP datagram, and create new one, if nothing is found.
 */
static inline struct ipq *ip_find(struct net *net, struct iphdr *iph, u32 user)
{
        struct inet_frag_queue *q;
        struct ip4_create_arg arg;
        unsigned int hash;

        arg.iph = iph;
        arg.user = user;

        read_lock(&ip4_frags.lock);
        hash = ipqhashfn(iph->id, iph->saddr, iph->daddr, iph->protocol);

        q = inet_frag_find(&net->ipv4.frags, &ip4_frags, &arg, hash);
        if (q == NULL)
                goto out_nomem;

        return container_of(q, struct ipq, q);

out_nomem:
        LIMIT_NETDEBUG(KERN_ERR "ip_frag_create: no memory left !\n");
        return NULL;
}
 

下面这行代码明确的指出了协议栈判断IP分片的依据:

 hash = ipqhashfn(iph->id, iph->saddr, iph->daddr, iph->protocol);
 

Ipqhashfn函数依靠(标示符、源地址、目标地址、协议)这个四元组来唯一的表示一个IP数据报分片,这就解决了单单依赖表示符无法确定一个数据报的问题。那么这个四元组怎么表示呢?查找的效率问题如何解决呢?答案就在ipqhashfn这个hash函数里,其代码如下(Linux-3.12.6):

 static unsigned int ipqhashfn(__be16 id, __be32 saddr, __be32 daddr, u8 prot)
{
        return jhash_3words((__force u32)id << 16 | prot,
                            (__force u32)saddr, (__force u32)daddr,
                            ip4_frags.rnd) & (INETFRAGS_HASHSZ - 1);
}
 

jhash_3words函数参数累加求模,实现如下(Linux-3.12.6):

/* A special ultra-optimized versions that knows they are hashing exactly
 * 3, 2 or 1 word(s).
 *
 * NOTE: In partilar the "c += length; __jhash_mix(a,b,c);" normally
 *       done at the end is not done here.
 */
static inline u32 jhash_3words(u32 a, u32 b, u32 c, u32 initval)
{
        a += JHASH_GOLDEN_RATIO;
        b += JHASH_GOLDEN_RATIO;
        c += initval;

        __jhash_mix(a, b, c);

        return c;
}
 

OK,问题解决了。有兴趣的同学可以去研究一下IP数据报分片如何实现。最近很忙,就到这里吧~

时间: 2024-10-03 17:32:08

Linux TCP/IP协议栈关于IP分片重组的实现的相关文章

使用TCP/IP协议栈指纹进行远程操作系统辨识_网络冲浪

概述 本文讨论了如何查询一台主机的TCP/IP协议栈来收集宝贵的信息.首先,我列举了栈指纹之外的几种"经典的"操作系统辨识方法.然后我描述了栈指纹工具的"工艺现状".接下来说明让远程主机泄漏其信息的一些技术.最后详述了我的实现(nmap),和用它获得的一些流行网站的操作系统信息.理由热点网络 我认为辨识一个系统所运行OS的用处是相当显而易见的,所以这一节会很短.最有力的例子之一是许多安全漏洞是OS相关的.试想你正在作突破试验并发现53端口是打开的.如果那是易遭攻击的

结合Wireshark捕获分组深入理解TCP/IP协议栈

摘要:     本文剖析了浏览器输入URL到整个页面显示的整个过程,以百度首页为例,结合Wireshark俘获分组进行详细分析整个过程,从而更好地了解TCP/IP协议栈.   一.俘获分组 1.1 准备工作 (1) 清空浏览器缓存     首先清空Web浏览器的高速缓存,确保Web网页是从网络中获取,而不是从高速缓冲取得[1].谷歌浏览器,Options --> Under the Hood --> Clear browsing data. (2)清空DNS缓存     在客户端清空DNS高速

数据在协议栈中的传递-用原始套接字截取数据链路层数据,那这包数还会在TCP/IP协议栈中传递吗

问题描述 用原始套接字截取数据链路层数据,那这包数还会在TCP/IP协议栈中传递吗 用原始套接字写了一个小程序,装在本机后,能够截取本机的发送/接收数据,想问一下,比如原始套接字程序在数据链路层截取到一个别人发给本机的数据,原始套接字程序截取到后,这包数还会向网络层上传,一步步再送到截取这包数的真正端口进程吗?会不会我一截取,该收到这包数的进程就收不到了呢? 解决方案 看你怎么截取的,一般是拦截,不影响数据包的发送. 解决方案二: 如果你不是通过驱动等,很可能你拿到的只是一份拷贝,所以数据还是继

关于TCP/IP协议栈(转)

1. TCP/IP协议栈 与OSI参考模型不同,TCP/IP协议栈共有4层,其中网络接口层对应OSI中的物理层和数据链路层,应用层对应OSI中的应用层.表示层和会话层. 在网络接口层的主要协议有:ARP.RARP等.ARP协议主要功能是根据IP地址获取物理地址,RARP协议则反之. 网络层的主要协议有:IP.路由协议(RIP.OSPF.BGP等).IP协议为网络上的每台主机编号,在此基础上才有路由协议,因此路由协议是基于IP协议的. 传输层的主要协议有:TCP.UDP.传输层有端口号的概念,端口

《IP组播(第1卷)》一1.4 三层组播是建立在TCP/IP协议栈中的

1.4 三层组播是建立在TCP/IP协议栈中的 IP组播是建立在TCP/IP协议栈中的.也就是说,传输组播数据帧和数据包所需的协议是由Internet工程任务组(IETF)进行控制的.IETF成员通过RFC发布和管理相关协议,也就是说IP组播协议是开放标准. 注释 组播协议IETF标准适用于IPv4和IPv6组播技术:但和其他IP协议一样,这并不意味着所有厂商处理组播的方式都是相同的,同时也不意味着所有组播协议的实施都能够与标准完美兼容. 使用TCP/IP协议栈也意味着IP组播隶属于互联网数字分

TCP/IP协议栈遭遇攻击?!

我们的网络游戏采用tcp进行通信,服务端程序绑定8300端口,为游戏客户端提供服务,游戏已经上线稳定运行两年多,从今年9月份开始至今碰到了3次攻击,3次攻击所导致的情况一样,描述如下: (1)从应用层上来看,攻击者每次攻击时,与8300端口都有建立最多两.三百个tcp连接. (2)从防火墙监控来看,攻击的流量非常微小,以至于防火墙的流量报警都未触发. (3)每次攻击产生时,原先的玩家的tcp连接并未断开(应用程序未抛出TCP连接断开的异常),但服务端的再也接收不到来自客户端的任何消息.服务端进程

TCP/IP协议栈实现

博学,切问,近思--詹子知(http://blog.csdn.net/zhiqiangzhan)  发送数据:     入栈用户数据     入栈TCP头     入栈IP头     入栈MAC头     通过网卡发送.   接受数据:     网卡获得数据     弹出MAC头     弹出IP头     弹出TCP头     弹出用户数据.     用栈的结构实现的好处很明显, 用户在使用的时候, 无需关心网络数据的物理形式, 只需按照栈的操作就可以得到不同的协议层的信息(发送的时候也一样)

Linux 防火墙iptables 禁止某些 IP访问

方法一,过滤一些IP访问本服务器 要封停一个IP,使用下面这条命令:  代码如下 复制代码 iptables -I INPUT -s ***.***.***.*** -j DROP 要解封一个IP,使用下面这条命令:    代码如下 复制代码 iptables -D INPUT -s ***.***.***.*** -j DROP 参数-I是表示Insert(添加),-D表示Delete(删除).后面跟的是规则,INPUT表示入站,***.***.***.***表示要封停的IP,DROP表示放弃

在Linux终端中查看公有IP的方法详解

  首先回顾一下一般的查看IP的命令: ifconfig Linux查看IP地址的命令--ifconfig ifconfig命令用于查看和更改网络接口的地址和参数 $ifconfig -a lo0: flags=849 mtu 8232 inet 127.0.0.1 netmask ff000000 hme0: flags=863 mtu 1500 inet 211.101.149.11 netmask ffffff00 broadcast 211.101.149.255 ether 8:0:2