博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
ip_append_data
阅读量:6315 次
发布时间:2019-06-22

本文共 14256 字,大约阅读时间需要 47 分钟。

hot3.png

/* *    ip_append_data() and ip_append_page() can make one large IP datagram *    from many pieces of data. Each pieces will be holded on the socket *    until ip_push_pending_frames() is called. Each piece can be a page *    or non-page data. * *    Not only UDP, other transport protocols - e.g. raw sockets - can use *    this interface potentially. * *    LATER: length must be adjusted by pad at tail, when it is required. */int ip_append_data(struct sock *sk,           int getfrag(void *from, char *to, int offset, int len,                   int odd, struct sk_buff *skb),           void *from, int length, int transhdrlen,           struct ipcm_cookie *ipc, struct rtable *rt,           unsigned int flags){    struct inet_sock *inet = inet_sk(sk);    struct sk_buff *skb;    struct ip_options *opt = NULL;    int hh_len;    int exthdrlen;    int mtu;    int copy;    int err;    int offset = 0;    unsigned int maxfraglen, fragheaderlen;    int csummode = CHECKSUM_NONE;    //.若指定了MSG_PROBE标志则返回,这个标志在以前记得在manpage send中有说明,现在怎么又找不到了。。    if (flags&MSG_PROBE)        return 0;    /*.检测本段数据是不是此ip包的第一个分片,如果是第一个分片则处理ip选项    (从sk->sk_write_queue检测,这个队列用来储存一个ip包的所有分片,如果为空则说明此段数据为第一个分片)    (1)是第一个分片:从函数参数中获取一些信息保存在cork中,cork是一个缓存一般用来存储些首部信息,以后的分片再进来就可以直接使用里面的信息,因为对于同一个ip包来说,这些信息都是相同的。从传入的ipc中获取ip选项并且将选项缓存在sock->cork.opt中,以便后来的分片以及ip_push_pending_frames使用。将参数rt的目标路由项也保存在cork.dst中,从路由项中获取mtu存入cork.fragsize。如果exthdrlen(IPSec首部的长度)不为0,则将参数length和transhdrlen都加上exthdrlen,算是为exthdr预留空间。    (2)不是第一个分片:从cork中获取到刚才保存的信息并且从cork.dst中读取到目标路由项,ip选项,mtu。同时将transhdrlen和exthdrlen都清0(因为L4首部和IPSec首部都只存在于第一个分片中)*/    if (skb_queue_empty(&sk->sk_write_queue)) {        /* setup for corking.*/        opt = ipc->opt;        if (opt) {            if (inet->cork.opt == NULL) {                inet->cork.opt = kmalloc(sizeof(struct ip_options) + 40, sk->sk_allocation);                if (unlikely(inet->cork.opt == NULL))                    return -ENOBUFS;            }            memcpy(inet->cork.opt, opt, sizeof(struct ip_options)+opt->optlen);            inet->cork.flags |= IPCORK_OPT;            inet->cork.addr = ipc->addr;        }        dst_hold(&rt->u.dst);        inet->cork.fragsize = mtu = inet->pmtudisc == IP_PMTUDISC_PROBE ?                        rt->u.dst.dev->mtu :                        dst_mtu(rt->u.dst.path);        inet->cork.dst = &rt->u.dst;        inet->cork.length = 0;        //sk_sndmsg_page指向当前正在使用的页面,sk_sndmsg_off是新数据应该存放的偏移。        //有新的数据就继续放入这个页面的这个偏移,放不下了就再分配一个并且将sk_sndmsg_page指向它        sk->sk_sndmsg_page = NULL;        sk->sk_sndmsg_off = 0;        if ((exthdrlen = rt->u.dst.header_len) != 0) {            length += exthdrlen;            transhdrlen += exthdrlen;        }    } else {        rt = (struct rtable *)inet->cork.dst;        if (inet->cork.flags & IPCORK_OPT)            opt = inet->cork.opt;        transhdrlen = 0;        exthdrlen = 0;        mtu = inet->cork.fragsize;    }    //根据目标路由项的发送设备计算出的需要为L2首部预留的最大长度    hh_len = LL_RESERVED_SPACE(rt->u.dst.dev);    //ip首部加上选项的总长度    fragheaderlen = sizeof(struct iphdr) + (opt ? opt->optlen : 0);    //一个分片的最大长度,注意分片的数据部分的长度必须八字节对齐     maxfraglen = ((mtu - fragheaderlen) & ~7) + fragheaderlen;        //一个ip封包的最大长度为64k(0xffff),如果cork.length(累积的所有ip分片总长度)+length(当前分片长度)超过0xffff则出错    if (inet->cork.length + length > 0xFFFF - fragheaderlen) {        ip_local_error(sk, EMSGSIZE, rt->rt_dst, inet->dport, mtu-exthdrlen);        return -EMSGSIZE;    }    /* transhdrlen > 0 means that this is the first fragment and we wish     * it won't be fragmented in the future.*/    //如果满足以下条件则将csummode赋值为CHECKSUM_PARTIAL(作用尚不明白):    //1.transhdrlen不为0(说明是第一个分片);    //2.length+fragheaderlen<=mtu说明当前的ip包可以整个发出去,不需分片;    //3.目标设备特性有NETIF_F_V4_CSUM(支持L4层的硬件校验和);    //4.exthdrlen等于0(没有IPSec首部)    if (transhdrlen &&        length + fragheaderlen <= mtu &&        rt->u.dst.dev->features & NETIF_F_V4_CSUM &&        !exthdrlen)        csummode = CHECKSUM_PARTIAL;    //从这里可以看出cork.length存储的是当前ip封包的总长度    inet->cork.length += length;    if (((length> mtu) || !skb_queue_empty(&sk->sk_write_queue)) &&        (sk->sk_protocol == IPPROTO_UDP) &&        (rt->u.dst.dev->features & NETIF_F_UFO)) {        err = ip_ufo_append_data(sk, getfrag, from, length, hh_len,                     fragheaderlen, transhdrlen, mtu,                     flags);        if (err)            goto error;        return 0;    }    /* So, what's going on in the loop below?     * We use calculated fragment length to generate chained skb,     * each of segments is IP fragment ready for sending to network after     * adding appropriate IP header.*/    //从sk->sk_write_queue取出末尾元素,赋给skb,若为空,则说明此为第一个分片,直接跳到alloc_new_skb    if ((skb = skb_peek_tail(&sk->sk_write_queue)) == NULL)        goto alloc_new_skb;    //主循环将数据拷贝入缓冲区,如果不够的话随时可以分配新的skb或者page,直到length==0就是拷贝完成    while (length > 0) {        /* Check if the remaining data fits into current packet. */        /*计算本次循环允许拷贝的数据量,copy = mtu - skb->len(注意这时skb是上一个使用过的skb,这里是看看给上一个skb分配的buff还有没有剩余空间)。如果剩余空间小于待拷贝数据量(copy
len(需要注意mtu和maxfraglen的区别,maxfraglen是满足数据部分8字节对齐的情况下的最大报文长度,而mtu无须满足8字节对齐,因此maxfraglen的范围在[mtu-7,mtu]。这里是判断上一个skb中有没有需要移动到本次skb中的数据,因为上一个skb可能没有8字节对齐,这时就要移动一部分数据到这个skb)。这里可能产生非8字节对齐的情况。这时copy三种情况:        (1)copy>0,说明上一个skb还有一定空间,数据可以拷贝到上一个skb中去。        (2)copy=0.说明上一个skb刚好填满,这时需要重新分配skb填入新数据。        (3)copy<0,说明上一个skb已经填满,但是末尾有些数据不是8字节对齐,需要拷贝到新分配的skb中去。        对于第一种情况,跳过分配skb的步骤,直接向上一个skb拷贝数据,后两种情况则需要重新分配skb。*/        copy = mtu - skb->len;        if (copy < length)            copy = maxfraglen - skb->len;        if (copy <= 0) {            char *data; //指向当前拷贝数据的目的地址            unsigned int datalen; //本次循环所需拷贝的数据长度,不包括ip头            unsigned int fraglen; //此ip分片的长度,包括ip头、ip选项和数据,一般等于datalen+fragheaderlen            unsigned int fraggap; //需要从上一个skb尾部移动到新skb开头的数据长度,为了保证ip分片的8字节对齐            unsigned int alloclen; //需要分配的skb数据部分长度(除去L2头部,实际分配时还要加上L2首部长度),                                    //如果支持S/G则需要多少就刚好分配多少,若不支持S/G或者有MSG_MORE则分配mtu大小            struct sk_buff *skb_prev; //总是指向前一个skb,如果这时正在处理第一个skb,则它为空指针alloc_new_skb:            skb_prev = skb;            if (skb_prev) //检查有多少数据需要从前一个skb移动到新skb来保证8字节对齐                fraggap = skb_prev->len - maxfraglen;            else                fraggap = 0;            /* If remaining data exceeds the mtu,             * we know we need more fragment(s). */            /*先令datalen = length + fraggap,为待拷贝数据总长度,            如果大于一分片所能容纳的数据 mtu - fragheaderlen,则令datalen=maxfraglen - fragheaderlen,            注意这里也可能产生8字节不对齐的情况,就是datalen虽然不大于mtu-fragheaderlen但是大于了maxfraglen-fragheaderlen,            可以看出这种情况下最后一个分片似乎没有遵守8字节对齐。            (附:经过实测,ip分片的最后一个分片允许不8字节对齐,因为offset是8字节对齐,            所以只要求非最后分片的报文长度是8字节对齐,因此这里如果能直接放下所有数据就可以不检测对齐)。*/            datalen = length + fraggap;            if (datalen > mtu - fragheaderlen)                datalen = maxfraglen - fragheaderlen;            fraglen = datalen + fragheaderlen;            //如果用户还要输入数据(MSG_MORE)并且网卡不支持S/G的话,令alloclen=mtu,            //算是为后来的数据预先准备空间。否则只令alloclen = datalen + fragheaderlen。            if ((flags & MSG_MORE) &&                !(rt->u.dst.dev->features&NETIF_F_SG))                alloclen = mtu;            else                alloclen = datalen + fragheaderlen;            /* The last fragment gets additional space at tail.             * Note, with MSG_MORE we overallocate on fragments,             * because we have no idea what fragment will be             * the last.*/            /*如果当前分片是最后一个分片(datalen == length + fraggap),那么alloclen还要加上rt->u.dst.trailer_len,            注释中写道最后一个分片需要在末尾留出一些空间,猜测可能是为某种协议的尾部预留空间。            个人觉得这里只能判断是当前发送请求的最后一个分片,不能判断是不是整个ip封包的最后分片,            完全有可能当前指定了MSG_MORE,后面还要来数据),*/            if (datalen == length + fraggap)                alloclen += rt->u.dst.trailer_len;            //如果当前是第一个分片(transhdrlen!=0),那么调用sock_alloc_send_skb;            //否则调用skb = sock_wmalloc(sk,alloclen + hh_len + 15, 1,sk->sk_allocation);             //从参数可以看出,分配的buf长度为ip首部长度加上数据长度加上尾部长度(这三个包含在alloclen)加上l2首部长度,            //另外加上15估计是为了为某种对齐预留空间。            if (transhdrlen) {                skb = sock_alloc_send_skb(sk,                        alloclen + hh_len + 15,                        (flags & MSG_DONTWAIT), &err);            } else {                skb = NULL;                if (atomic_read(&sk->sk_wmem_alloc) <=                    2 * sk->sk_sndbuf)                    skb = sock_wmalloc(sk,                               alloclen + hh_len + 15, 1,                               sk->sk_allocation);                if (unlikely(skb == NULL))                    err = -ENOBUFS;            }            if (skb == NULL)                goto error;            /*             *    Fill in the control structures             */            /*对刚分配的skb进行初始化,操作包括:            ip_summed设置为csummode(此时可能为CHECKSUM_PARTIAL或CHECKSUM_NONE)。            校验和skb->csum设为0,在头部预留hh_len空间给L2首部。            接下来在中间留出fraglen大小的数据部分用来存放ip数据,同时将局部变量data指向skb传输层(包括IPSec)的开头,            将skb->network_header指向skb->data+exthdrlen,将skb->transport_header指向skb->network_header + fragheaderlen。*/            skb->ip_summed = csummode;            skb->csum = 0;            skb_reserve(skb, hh_len);            /*Find where to start putting bytes. */            data = skb_put(skb, fraglen);            skb_set_network_header(skb, exthdrlen);            skb->transport_header = (skb->network_header +                         fragheaderlen);            data += fragheaderlen;            //如果fraggap不为0,说明有些数据需要从上一个skb拷贝到当前skb,            //这时进行拷贝并且重新计算上一个skb和本skb的ip校验和,同时将data指针后移越过刚拷贝的fraggap数据            if (fraggap) {                skb->csum = skb_copy_and_csum_bits(                    skb_prev, maxfraglen,                    data + transhdrlen, fraggap, 0);                skb_prev->csum = csum_sub(skb_prev->csum,                              skb->csum);                data += fraggap;                pskb_trim_unique(skb_prev, maxfraglen);            }            //重新计算copy,看起来应该是L4的负载部分长度,不知道为何要跳过传输层首部,            //如果copy>0说明传输层有数据需要拷贝,调用getfrag将L4数据拷贝到skb中去,            //这里getfrag可以参考ip_generic_getfrag,注意这里的getfrag的from参数有可能是用户空间的指针            copy = datalen - transhdrlen - fraggap;            if (copy > 0 && getfrag(from, data + transhdrlen, offset, copy, fraggap, skb) < 0) {                err = -EFAULT;                kfree_skb(skb);                goto error;            }            //offset为尚未拷贝的用户数据的起始地址偏移            //这里copy并不等于datalen - fraggap,参考上面几行,copy = datalen - transhdrlen - fraggap            //可以看到getfrag的第二个参数也跳过了transhdrlen。从参数from来看,明明拷贝了L4首部,但是长度却没计算L4首部,不知为何            offset += copy;            length -= datalen - fraggap;            transhdrlen = 0;            exthdrlen = 0;            csummode = CHECKSUM_NONE;            /*             * Put the packet on the pending queue.             */            __skb_queue_tail(&sk->sk_write_queue, skb);            continue;        }        //处理copy>=length也就是说上一个skb的空间末尾剩余空间大于带拷贝数据的情况,        //这种情况无须新分配skb,而可以直接使用上一个skb末尾的空闲空间        if (copy > length)            copy = length;        //下面根据发送设备是否支持S/G而分为两条路,        if (!(rt->u.dst.dev->features&NETIF_F_SG)) {            //不支持S/G说明上一个skb一定没有使用frags分片保存数据,而是全部保存在skb的主buf中,            //因此可以直接调用getfrag将数据拷贝到上一个skb的剩余空间处。            unsigned int off;            off = skb->len;            if (getfrag(from, skb_put(skb, copy),                    offset, copy, off, skb) < 0) {                __skb_trim(skb, off);                err = -EFAULT;                goto error;            }        } else {            //支持S/G说明上一个skb是分frags存放的,而上一个skb的数据小于mtu,则可以向上一个skb添加数据,            //对于支持SG的网卡来说,这时不一定有剩余空间,要看上一个分配的page还有没有空位,如果没有空位就要重新分配page存放新数据            //首先从skb_shared_info中取得nr_frags,然后在从skb_shared_info中取得第nr_frags-1(即最后一个)frag,            //然后从sk->sk_sndmsg_page取得缓存在sock中的页面,这个缓存页是用来保存最近使用过的页面的,            //再从sk->sk_sndmsg_off取得该缓存页面空闲空间的偏移指针            int i = skb_shinfo(skb)->nr_frags;            skb_frag_t *frag = &skb_shinfo(skb)->frags[i-1];            struct page *page = sk->sk_sndmsg_page;            int off = sk->sk_sndmsg_off;            unsigned int left;            //如果缓存页面存在并且页面上的剩余空间大于0:            if (page && (left = PAGE_SIZE - off) > 0) {                //当left>0                //判断待拷贝数据(copy)是否大于剩余空间(left),若不大于则令copy=left。                //然后看sock中的缓存页(page)是否等于skb_shared_info中的最后一个页面,                //若不等(这种情况为什么会发生?)则将page引用计数+1并且添加到skb_shared_info的frags末尾,frag指向新frag                if (copy >= left)                    copy = left;                if (page != frag->page) {                    if (i == MAX_SKB_FRAGS) {                        err = -EMSGSIZE;                        goto error;                    }                    get_page(page);                    skb_fill_page_desc(skb, i, page, sk->sk_sndmsg_off, 0);                    frag = &skb_shinfo(skb)->frags[i];                }            } else if (i < MAX_SKB_FRAGS) {                 //当left==0且i
 PAGE_SIZE)                    copy = PAGE_SIZE;                page = alloc_pages(sk->sk_allocation, 0);                if (page == NULL)  {                    err = -ENOMEM;                    goto error;                }                sk->sk_sndmsg_page = page;                sk->sk_sndmsg_off = 0;                skb_fill_page_desc(skb, i, page, 0, 0);                frag = &skb_shinfo(skb)->frags[i];            } else {                //left==0并且i>=MAX_SKB_FRAGS,出错返回-EMSGSIZE                err = -EMSGSIZE;                goto error;            }            //经过以上处理应该都已经有空间了,虽然不一定能容纳下所有数据,这时调用getfrag将数据拷贝入目标页面            if (getfrag(from, page_address(frag->page)+frag->page_offset+frag->size, offset, copy, skb->len, skb) < 0) {                err = -EFAULT;                goto error;            }            //拷贝完成,调整指针,全部加上copy大小            sk->sk_sndmsg_off += copy;            frag->size += copy;            skb->len += copy;            skb->data_len += copy;            skb->truesize += copy;            atomic_add(copy, &sk->sk_wmem_alloc);        }        offset += copy;        length -= copy;    }    return 0;error:    inet->cork.length -= length;    IP_INC_STATS(sock_net(sk), IPSTATS_MIB_OUTDISCARDS);    return err;} 

转载于:https://my.oschina.net/u/572632/blog/356549

你可能感兴趣的文章
String类的一些常用方法
查看>>
hdu 4122(RMQ)2011福州现场赛B题
查看>>
小组项目冲刺第四天的个人总结
查看>>
Two Sum II - Input array is sorted(leetcode167) - Solution2
查看>>
Mybatis入门
查看>>
nyoj198数数
查看>>
2019.2.15 t2
查看>>
[bzoj 4833]最小公倍佩尔数
查看>>
17、ListView & GridView
查看>>
java中的继承与oc中的继承的区别
查看>>
1065 A+B and C (64bit)
查看>>
Django之ORM
查看>>
style、currentStyle、getComputedStyle区别介绍
查看>>
布局的一点总结
查看>>
根据条件更改水晶报表的背景颜色
查看>>
c程序设计语言第一章5
查看>>
对象池的应用
查看>>
Reboot分享第一期(已结束)
查看>>
BZOJ4916 神犇和蒟蒻
查看>>
vue请求本地自己编写的json文件。
查看>>