TCP/IP协议如何实现ip分片

发布时间:2021-09-19 18:22:38 作者:小新
来源:亿速云 阅读:641

小编给大家分享一下TCP/IP协议如何实现ip分片,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!

分析一下ip分片。首先我们要先了解为什么需要分片。比如在以太网中,使用CSMA/CD协议(由网卡实现),他规定了一个链路层数据包(不包括mac头,但是这一版内核实现的时候是包括了mac头的大小)的最大值(MTU)和最小值。所以如果上层的包大于这个阈值就需要被分片。而分片和组包的实现是在ip层。我们看一下具体的逻辑。ip分片的逻辑在ip_fragment函数里实现。

void ip_fragment(
    struct sock *sk, 
    struct sk_buff *skb, 
    struct device *dev, 
    int is_frag
)

定义的一些变量。

    struct iphdr *iph;
    unsigned char *raw;
    unsigned char *ptr;
    struct sk_buff *skb2;
    int left, mtu, hlen, len;
    int offset;
    unsigned long flags;
    // mac首地址
    raw = skb->data;
    // ip头首地址,hard_header_len为mac头大小
    iph = (struct iphdr *) (raw + dev->hard_header_len);
    skb->ip_hdr = iph;
    // ip头的大小,不包括数据部分
    hlen = (iph->ihl * sizeof(unsigned long));
    // ip包总大小减去ip层等于ip报文的数据长度,即需要分片的部分的大小
    left = ntohs(iph->tot_len) - hlen;  
    // ip头+mac头
    hlen += dev->hard_header_len;   
    // 每个分片的数据部分长度等于mac层的mtu减去mac头和ip头,即mac层的mtu包括了mac头、ip头、ip数据部分的总和。
    mtu = (dev->mtu - hlen);        
    // 数据部分首地址
    ptr = (raw + hlen);     

判断是否可以分片。

    // 设置了不能分片则发送icmp报文,可以对照ip报文格式看
    if (ntohs(iph->frag_off) & IP_DF)
    {
        icmp_send(skb,ICMP_DEST_UNREACH, ICMP_FRAG_NEEDED, dev->mtu, dev);
        return;
    }

判断即将被分片的ip包是否本身也是一个分片。即经过了多次ip分片。

/*
    该ip报文本身就是一个分片,现在需要进行再次分片,
    偏移的首地址是该报文的首地址乘以8,因为再次被分片的报文,他的偏移是
    基于原来未被分片的数据的偏移。而不是针对当前这个分片的偏移
*/
    if (is_frag & 2)
        offset = (ntohs(iph->frag_off) & 0x1fff) << 3;
    else
        offset = 0

开始处理分片。

    // 还有则继续处理
    while(left > 0)
    {   
        // ip包默认承载的字节数,但是如果大于mtu的话则取小的值,即mtu
        len = left;
        // 大于mtu则还要分片,即只能承载mtu大小的字节,否则就是最后一个分片
        if (len > mtu)
            len = mtu;
        /*
            剩下的字节比mtu大的时候下面的判断会成立,
            即剩下的字节还不能在这次发送完,还要继续分片
            除8乘8即取8的倍数大小,不一定等于mtu 
        */
        if (len < left)
        {
            len/=8;
            len*=8;
        }
        // len 为这一分片承载的数据大小
        // 申请新的skb,大小为mac头+ip头+数据部分长度
        if ((skb2 = alloc_skb(len + hlen,GFP_ATOMIC)) == NULL)
        {
            return;
        }

        skb2->arp = skb->arp;
        skb2->free = 1;
        // 总大小是mac头+ip头+数据部分长度
        skb2->len = len + hlen;
        // 指向刚分配的内存首地址,开始复制数据
        skb2->h.raw=(char *) skb2->data;
        save_flags(flags);
        restore_flags(flags);
        // ip地址
        skb2->raddr = skb->raddr;   

        // raw指向mac头首地址,这里把mac报头和ip报头+选项都复制到skb中,ip选项应该只复制到第一个分片,这里会复制到每一个分片中
        memcpy(skb2->h.raw, raw, hlen);
        // 复制数据部分,长度为len,ptr指向原ip报文中数据部分的首地址,
        memcpy(skb2->h.raw + hlen, ptr, len);

        // 剩下需要分片的字节数
        left -= len;
        // 指向ip头首地址
        skb2->h.raw+=dev->hard_header_len;
        iph = (struct iphdr *)(skb2->h.raw);
        // 设置该分片的偏移,除以8,见ip协议的规定
        iph->frag_off = htons((offset >> 3));
        /*
            1. 还有数据,则置MF,还要更多分片
            2. is_frag =1;说明该分片后面还有更多分片。
            表示被分片的数据本身就是一个ip分片,即再分片。
            所以该报文下的所有分片MF都是1。
        */
        if (left > 0 || (is_frag & 1))
            iph->frag_off |= htons(IP_MF);
        // 更新数据指针和偏移
        ptr += len;
        offset += len;
        // 发送分片
        ip_queue_xmit(sk, dev, skb2, 2);
    }
分片主要的逻辑是

1 申请一个新的内存,把待分片报文中的mac头、ip头,复制到新内存,然后数据部分切一块继续复制到内存后面。如此,直到分片完毕
2 修改ip报文中的一些字段的值 ,比如MF。
3 调底层接口逐个发送分片
分片的逻辑不算复杂,不讲解的太详细了。

以上是“TCP/IP协议如何实现ip分片”这篇文章的所有内容,感谢各位的阅读!相信大家都有了一定的了解,希望分享的内容对大家有所帮助,如果还想学习更多知识,欢迎关注亿速云行业资讯频道!

推荐阅读:
  1. 【bug-004】【tcp】网络断开重连后,客户端recv函数一直阻塞,不会返回
  2. 详解python中TCP协议中的粘包问题

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

tcp ip

上一篇:SpringBoot中如何基于RabbitMQ实现消息延迟队列

下一篇:如何基于LibreOffice实现文档操作

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》