• IP层收发报文ip输出报文分片


    输出pkt时分片出现位置  

      在发送数据时,如果数据块大于MTU 那么就会分片;ip_finish_output 中就会调用ip_fragment 进行分片

    static int ip_finish_output(struct net *net, struct sock *sk, struct sk_buff *skb)
    {
        unsigned int mtu;
    
    #if defined(CONFIG_NETFILTER) && defined(CONFIG_XFRM)
        /* Policy lookup after SNAT yielded a new policy */
        if (skb_dst(skb)->xfrm) {
            IPCB(skb)->flags |= IPSKB_REROUTED;
            return dst_output(net, sk, skb);
        }
    #endif
    /*
    //如果不支持TSO或者GSO,tcp发送的时候是按照mss来组织skb的,
    所以skb->len会等于mtu  所以TCP叫分段,和IP分片不一样,只有UDP才有IP分片
    //SKB不是gso类型,并且skb->len大于mtu则需要分片  
    对方接受后的分片重组在netfilter中的ipv4_conntrack_defrag
    */
        mtu = ip_skb_dst_mtu(sk, skb);
        if (skb_is_gso(skb))
            return ip_finish_output_gso(net, sk, skb, mtu);
     /* 如果数据包长度大于MTU,则调用ip_fragment()
         * 对IP数据包进行分片。
         */
        if (skb->len > mtu || (IPCB(skb)->flags & IPSKB_FRAG_PMTU))
            return ip_fragment(net, sk, skb, mtu, ip_finish_output2);
    
        return ip_finish_output2(net, sk, skb);
    }

       实际上在真正分片前需要校验是否允许本地分片或者报文是否拒绝分片

    static int ip_fragment(struct net *net, struct sock *sk, struct sk_buff *skb,
                   unsigned int mtu,
                   int (*output)(struct net *, struct sock *, struct sk_buff *))
    {
        struct iphdr *iph = ip_hdr(skb);
            //ip头允许分片,则执行分片逻辑
        if ((iph->frag_off & htons(IP_DF)) == 0)
            return ip_do_fragment(net, sk, skb, output);
        
        // 不允许本地分段&&此时ip 头部禁止分段 || 分片最大长度>MTU  则发送无法到达的icmp_dest_unreach的消息   
        if (unlikely(!skb->ignore_df ||
                 (IPCB(skb)->frag_max_size &&
                  IPCB(skb)->frag_max_size > mtu))) {
            IP_INC_STATS(net, IPSTATS_MIB_FRAGFAILS);
            icmp_send(skb, ICMP_DEST_UNREACH, ICMP_FRAG_NEEDED,
                  htonl(mtu));
            kfree_skb(skb);
            return -EMSGSIZE;
        }
    
        return ip_do_fragment(net, sk, skb, output);
    }

    目前分片分为快速路径和慢速路径:

      主数据包会被分片多个数据包,慢过程没有发送主数据块,而是把它分解成多个分片数据块;显然时间更长;注意skb_copy_bits这个函数,此函数主要是复制数据到新的skb中;主数据块可能是分散数据块ip_append_page 可能是ip_append_data也可能是

    原有就有分段数据包(ip_push_pending_frames)此时数据块分三段:

    • skb线性区域,由skb成员data开始到tail所指向的空间;
    • 页面片段区域,由保存在skb_shared_info中的成员(skb_frag_t结构体)frags所指向的页面空间;
    • skb分片区域,由保存在skb_shared_info中的成员(struct sk_buff)*frag_list所指向。

    复制过程先从主数据基本线性区开始,然后就是分散聚合SG数据块,最后就是分段片frag_list 队列中的数据块

    /*
     *    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_do_fragment(struct net *net, struct sock *sk, struct sk_buff *skb,
               int (*output)(struct net *, struct sock *, 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;
    
        /* for offloaded checksums cleanup checksum before fragmentation  ---->PARTIAL类型需要清除校验和*/
        if (skb->ip_summed == CHECKSUM_PARTIAL &&
            (err = skb_checksum_help(skb)))
            goto fail;
    
        /*
         *    Point into the IP datagram header.
         */
    
        iph = ip_hdr(skb);
    
        mtu = ip_skb_dst_mtu(sk, skb);/* 获取mtu */
        if (IPCB(skb)->frag_max_size && IPCB(skb)->frag_max_size < mtu)
            mtu = IPCB(skb)->frag_max_size; /* 接收到的最大分片长度 < mtu,则将mtu设置为该值 */
    
        /*
         *    Setup starting values.
         */
    
        hlen = iph->ihl * 4;// ip 头部长度
        mtu = mtu - hlen;    /* Size of data space */
        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.
         */
         //1)skb线性区域,由skb成员data开始到tail所指向的空间;
        //2)页面片段区域,由保存在skb_shared_info中的成员(skb_frag_t结构体)frags所指向的页面空间;
        //3)skb分片区域,由保存在skb_shared_info中的成员(struct sk_buff)*frag_list所指向。
    // 主数据使用data_len == sg数据+ frag_List 数据   ; skb_pagelen == sg数据 + liner线性数据
        if (skb_has_frag_list(skb)) {//如果主数据包已经建立了分段数据包队列
            struct sk_buff *frag, *frag2;
            int first_len = skb_pagelen(skb);// 第一个数据块的长度==基本数据(线性区)+全部分散的数据块之和也就是 ==SG数据+ling_er
            if (first_len - hlen > mtu || //第一个数据块总长度大于MTU
                ((first_len - hlen) & 7) ||//检查长度是否按8BYTE 对齐
                ip_is_fragment(iph) ||// 检查IP头分片标志,是不是分片报文
                skb_cloned(skb))// 如果报文是clone的
                goto slow_path;
            // 检查frag_List  计算 skb中除去fraglist数据后剩余的大小(skbufff结构体大小和SG数据块大小)
            skb_walk_frags(skb, frag) {
                /* Correct geometry. */
                if (frag->len > mtu ||/* 分片长度>mtu */
                    ((frag->len & 7) && frag->next) ||/* 除最后一个分片外,其余有非8字节对齐的 */
                    skb_headroom(frag) < hlen)/* 头部长度过小 */
                    goto slow_path_clean;
    
                /* Partially cloned skb? */
                if (skb_shared(frag))/* 克隆的,恢复状态,进入慢速路径 */
                    goto slow_path_clean;
    
                BUG_ON(frag->sk);
                if (skb->sk) {/* 分片关联控制块 */
                    frag->sk = skb->sk;
                    frag->destructor = sock_wfree;
                }
                skb->truesize -= frag->truesize;/* 第一个skb的长度去掉当前分片的长度 */
            }
            // 执行到此 可以算出第一个数据包的实际长度 也就是truesize
    
            /* Everything is OK. Generate! */
    
            err = 0;
            offset = 0;
            frag = skb_shinfo(skb)->frag_list;
            skb_frag_list_init(skb);// 清空主数据包的分段队列指针
            skb->data_len = first_len - skb_headlen(skb);//sg数据 --
            skb->len = first_len;//第一个数据包的总长度    headlen+sg数据
            iph->tot_len = htons(first_len);
            iph->frag_off = htons(IP_MF);//设置分片标志位
            ip_send_check(iph);//计算校验和
    
            for (;;) {
                /* Prepare header of the next frame,
                 * before previous one went down. */
                 // 注意 frag_list 一般都是由 icmp udp 调用 ip_push_pending_frams 建立的;
                 // sg 数据是所有协议都支持,他是在基本数据无法满足存放数据情况下建立的
                if (frag) {// 核心就是设置  frag_list 的skb中的分片 偏移地址 然后计算校验和
                    frag->ip_summed = CHECKSUM_NONE;//设置为NONE 表示csum域中的校验值是无意义的
                    skb_reset_transport_header(frag);// 设置为TCP 或者udp 等四层指针
                    __skb_push(frag, hlen);// 指向IP 头部  frag_list 中的data一开始都是指向数据区 四层数据区
                    skb_reset_network_header(frag);// f复位当前ip头部指针
                    memcpy(skb_network_header(frag), iph, hlen);// 复制IP头部
                    iph = ip_hdr(frag);
                    iph->tot_len = htons(frag->len);// 记录分段的数据块长度
                    ip_copy_metadata(frag, skb);
                    if (offset == 0)// 第一个分段数据包的偏移位置为0
                        ip_options_fragment(frag);
                    offset += skb->len - hlen;//下一个数据包块的偏移位置
                    iph->frag_off = htons(offset>>3);//ip头部记录分段数据块的偏移位置, 长度是8倍关系
                    if (frag->next)// 最后一个分片 不会设置MF 标志
                        iph->frag_off |= htons(IP_MF);
                    /* Ready, complete checksum */
                    ip_send_check(iph);
                }
    
                err = output(net, sk, skb);// 先发送一个数据包
    
                if (!err)
                    IP_INC_STATS(net, IPSTATS_MIB_FRAGCREATES);
                if (err || !frag)
                    break;
    
                skb = frag;// 指向要发送的数据包
                frag = skb->next;// 下一个分段数据包
                skb->next = NULL;//清空当前skb的下一个包 
            }
    
            if (err == 0) {
                IP_INC_STATS(net, IPSTATS_MIB_FRAGOKS);
                return 0;
            }
    
            while (frag) {// 私房frag_list数据
                skb = frag->next;
                kfree_skb(frag);
                frag = skb;
            }
            IP_INC_STATS(net, 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:
        iph = ip_hdr(skb);
                // skb->len == 从data指向开始,也就是协议向上变化时,data会变化(len ==tail-data + sg +fraglist)
        left = skb->len - hlen;        /* 对于tcp 协议来说:就是tcp 头部 + tcp 数据长度  Space per frame */
        ptr = hlen;        /* Where to start from */
    
        ll_rs = LL_RESERVED_SPACE(rt->dst.dev);//ll_rs== mac长度+reverse 长度
    
        /*
         *    Fragment the datagram.
         */
    
        offset = (ntohs(iph->frag_off) & IP_OFFSET) << 3;// 主数据块的偏移位置* 8 ;协议要求
        not_last_frag = iph->frag_off & htons(IP_MF);
    
        /*
         *    Keep copying data until we run out.
         */
    
        while (left > 0) {// 一开始left == 主数据剩余长度
            len = left;
            /* IF: it doesn't fit, use 'mtu' - the data space left */
            if (len > mtu)
                len = mtu;
            /* IF: we are not sending up to and including the packet end
               then align the next start on an eight byte boundary */
            if (len < left)    {
                len &= ~7;
            }
    
            /* Allocate buffer  数据包的缓冲块大小包括==缓冲块实际长度  MTU         +IP头部+链路层+ reverse  */  
            skb2 = alloc_skb(len + hlen + ll_rs, GFP_ATOMIC);
            if (!skb2) {
                err = -ENOMEM;
                goto fail;
            }
    
            /*
             *    Set up data on packet
             */
    
            ip_copy_metadata(skb2, skb);
            skb_reserve(skb2, ll_rs);// t跳过链路层头部 
            skb_put(skb2, len + hlen);// tail 指针向后偏移 len +hlen
            skb_reset_network_header(skb2);// 记录IP 头部地址指针
            skb2->transport_header = skb2->network_header + hlen;// tcp 头部指针
    
            /*
             *    Charge the memory for the fragment to any owner
             *    it might possess
             */
    
            if (skb->sk)
                skb_set_owner_w(skb2, skb->sk);
    
            /*
             *    Copy the packet header into the new buffer.
             */ 
            // 复制 主数据块的数据到分片数据中
            skb_copy_from_linear_data(skb, skb_network_header(skb2), hlen);
    
            /*
             *    Copy a block of the IP datagram.
             */// 复制主数据块的数据到分片数据块中  这里里要考虑 sg 和 frag_list 情况  这个函数很复杂
            if (skb_copy_bits(skb, ptr, skb_transport_header(skb2), len))
                BUG();
            left -= len;
    
            /*
             *    Fill in the new header fields.
             */
            iph = ip_hdr(skb2);
            iph->frag_off = htons((offset >> 3));//设置分片偏移
    
            if (IPCB(skb)->flags & IPSKB_FRAG_PMTU)
                iph->frag_off |= htons(IP_DF);
    
            /* 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.
             */
            if (offset == 0)// 分片的第一个包:重新设置ip层选项
                ip_options_fragment(skb);
    
            /*
             *    Added AC : If we are fragmenting a fragment that's not the
             *           last fragment then keep MF on each bit
             */
            if (left > 0 || not_last_frag)/* 不是最后分片需要设定MF标记 */
                iph->frag_off |= htons(IP_MF);
    
            ptr += len;// 调整下一次复制位置/* 指针和偏移更新 */
            offset += len;// 下一个分片的偏移位置
    
            /*
             *    Put this fragment into the sending queue.
             */
            iph->tot_len = htons(len + hlen);
    
            ip_send_check(iph);/* 校验和 */
    
            err = output(net, sk, skb2);// j是当前skb 数据copy 到skb2 中发送
            if (err)
                goto fail;
    
            IP_INC_STATS(net, IPSTATS_MIB_FRAGCREATES);
        }
        consume_skb(skb);/* 分片完成并发送,释放skb */
        IP_INC_STATS(net, IPSTATS_MIB_FRAGOKS);
        return err;
    
    fail:
        kfree_skb(skb);
        IP_INC_STATS(net, IPSTATS_MIB_FRAGFAILS);
        return err;
    }
    EXPORT_SYMBOL(ip_do_fragment);

     

    skb_copy_bits

    /**
     *    skb_copy_bits - copy bits from skb to kernel buffer
     *    @skb: source skb
     *    @offset: offset in source
     *    @to: destination buffer
     *    @len: number of bytes to copy
     *
     *    Copy the specified number of bytes from the source skb to the
     *    destination buffer.
     *
     *    CAUTION ! :
     *        If its prototype is ever changed,
     *        check arch/{*}/net/{*}.S files,
     *        since it is called from BPF assembly code.
     */
    int skb_copy_bits(const struct sk_buff *skb, int offset, void *to, int len)
    {
        int start = skb_headlen(skb);//主数据包的线性数据块长度
        struct sk_buff *frag_iter;
        int i, copy;
    
        if (offset > (int)skb->len - len)//skb->len : 全部数据块的总长度 offset + len > skb->len
            goto fault;// 复制超出范围出错 返回
    
        /* Copy header. *///如果线性区还有空间  先处理线性空间 
        if ((copy = start - offset) > 0) {//基本数据块长度 - 偏移处: 判断是否要拷贝头部
            if (copy > len)
                copy = len; //需拷贝的数据长度完全在liner数据块内, 所以完全是线性拷贝
            skb_copy_from_linear_data_offset(skb, offset, to, copy);
            if ((len -= copy) == 0)
                return 0;
            offset += copy;//如果拷贝的数据存在于散列在内存页面的分散数据块或者分段数据块中
            to     += copy;// 调整存放位置
        }
    
        for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
            int end;
            skb_frag_t *f = &skb_shinfo(skb)->frags[i];
    
            WARN_ON(start > offset + len);
    
            end = start + skb_frag_size(f); // 线性区+frag == 总的数据长度
            if ((copy = end - offset) > 0) {// 计算复制长度
                u8 *vaddr;
    
                if (copy > len)// 如果超过指定长度
                    copy = len;// 以指定长度为准
    
                vaddr = kmap_atomic(skb_frag_page(f));//frag 中page 的virtual虚拟地址  
                memcpy(to,
                       vaddr + f->page_offset + offset - start,// offset-start 已经copy的数据
                       copy);
                kunmap_atomic(vaddr);
    
                if ((len -= copy) == 0)
                    return 0;
                offset += copy;
                to     += copy;
            }
            start = end;//重行计算start  也就是 已经copy的数据尾部
        }
    /* 如果还没有 copy 完足够长度的数据,还要遍历 frag_list 继续  copy 数据,而且 copy 数据的方式是递归调用该函数 */
        skb_walk_frags(skb, frag_iter) {
            int end;
    
            WARN_ON(start > offset + len);
    
            end = start + frag_iter->len;
            if ((copy = end - offset) > 0) {// j计算复制的长度
                if (copy > len)
                    copy = len;
                if (skb_copy_bits(frag_iter, offset - start, to, copy))// 递归调用,按长度来复制
                    goto fault;
                if ((len -= copy) == 0)
                    return 0;
                offset += copy;// 调整复制位置
                to     += copy;//调整存放位置
            }
            start = end;
        }
    
        if (!len)
            return 0;
    
    fault:
        return -EFAULT;
    }
    EXPORT_SYMBOL(skb_copy_bits);
  • 相关阅读:
    10.12在主函数中输入10个等长的字符串。用另一个函数对它们排序,然后在主函数输出这10个已排好序的字符串。
    10.10 将一个5×5的矩阵中最大的元素放在中心,4个角分别放在4个最小的元素(按从左到右,从上到下的顺序,依次从小到大存放),写一个函数实现之,并用main函数调用。
    10.9 写一个函数,将一个3*3的整型矩阵转置。
    10.8输入一行文字,找出其中大写字母、小写字母、空格、数字及其他字符各有多少?
    10.4 有n个整数,使前面各数顺序向后移m个位置,最后m个数变成前面m个数,见图。写一函数:实现以上功能,在主函数中输入n个数和输出调整后的n个数。
    10.5 有n人围成一圈,顺序排号。从第1个人开始报数(从1到3报数),凡报到3的人退出圈子,问最后留下的是原来的第几号的那位。
    10.3 输入10个整数,将其中最小的数与第一个数对换,把最大的数与最后一个数对换。写三个函数;①输入10个数;②进行处理;③输出10个数。
    PHP中文无乱码写法
    教你怎样搜索下载百度网盘、华为网盘、迅雷快传的资源
    Yii框架的安装
  • 原文地址:https://www.cnblogs.com/codestack/p/16183400.html
Copyright © 2020-2023  润新知