ip_append_data

/*
 *    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<length),則令copy = maxfraglen - skb->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<MAX_SKB_FRAGS,這時會從新分配一個頁面而且添加到sock的緩存頁以及skb_shared_info的frags末尾,frag指向新frag
                if (copy > 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;
}&nbsp;
本站公眾號
   歡迎關注本站公眾號,獲取更多信息