分享

发送ip_append_data函数分析

 enchen008 2013-09-11
 

因为这个函数实在比较长,也有些复杂,专门做些注释,防止以后自己忘了
其实结合ULNI的图很快就能看懂,但是那几张图实在懒得贴上来了,自己到该书的21章去找吧

看完了肯定会佩服作者用图说明问题的能力的,呵呵

首先简单介绍几个输入参数
sk:             要发送数据的sock结构
getfrag:        在把数据从from拷贝到新的buffer中时,根据L4协议的不同还有些不同的情况需要处理,比如tcp计算校验和等,为了所有L4都能使用ip_append_data函数,在这里加入getfrag作为参数来让上层协议决定自己如何拷贝。
from:           L4及以上层数据,包括L4头部,可能是来自用户空间的。注意这是一个struct iovec指针,而不是直接的数据指针
length:         from所指向的数据的长度(包括L4头部)
transhdrlen:    L4头部长度
ipc:            发送ip包所需要的信息,包括首部字段的值,选项等等
rt:             目标路由项,在ip_queue_xmit中,这一项是由自己确定的,此函数要求上层完成路由选择
flags:          send的最后一个参数,

  1. /* 
  2.  *  ip_append_data() and ip_append_page() can make one large IP datagram 
  3.  *  from many pieces of data. Each pieces will be holded on the socket 
  4.  *  until ip_push_pending_frames() is called. Each piece can be a page 
  5.  *  or non-page data. 
  6.  * 
  7.  *  Not only UDP, other transport protocols - e.g. raw sockets - can use 
  8.  *  this interface potentially. 
  9.  * 
  10.  *  LATER: length must be adjusted by pad at tail, when it is required. 
  11.  */  
  12. int ip_append_data(struct sock *sk,  
  13.            int getfrag(void *from, char *to, int offset, int len,  
  14.                    int odd, struct sk_buff *skb),  
  15.            void *from, int length, int transhdrlen,  
  16.            struct ipcm_cookie *ipc, struct rtable *rt,  
  17.            unsigned int flags)  
  18. {  
  19.     struct inet_sock *inet = inet_sk(sk);  
  20.     struct sk_buff *skb;  
  21.     struct ip_options *opt = NULL;  
  22.     int hh_len;  
  23.     int exthdrlen;  
  24.     int mtu;  
  25.     int copy;  
  26.     int err;  
  27.     int offset = 0;  
  28.     unsigned int maxfraglen, fragheaderlen;  
  29.     int csummode = CHECKSUM_NONE;  
  30.     //.若指定了MSG_PROBE标志则返回,这个标志在以前记得在manpage send中有说明,现在怎么又找不到了。。   
  31.     if (flags&MSG_PROBE)  
  32.         return 0;  
  33.     /*.检测本段数据是不是此ip包的第一个分片,如果是第一个分片则处理ip选项 
  34.     (从sk->sk_write_queue检测,这个队列用来储存一个ip包的所有分片,如果为空则说明此段数据为第一个分片) 
  35.     (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预留空间。 
  36.     (2)不是第一个分片:从cork中获取到刚才保存的信息并且从cork.dst中读取到目标路由项,ip选项,mtu。同时将transhdrlen和exthdrlen都清0(因为L4首部和IPSec首部都只存在于第一个分片中)*/  
  37.     if (skb_queue_empty(&sk->sk_write_queue)) {  
  38.         /* setup for corking.*/  
  39.         opt = ipc->opt;  
  40.         if (opt) {  
  41.             if (inet->cork.opt == NULL) {  
  42.                 inet->cork.opt = kmalloc(sizeof(struct ip_options) + 40, sk->sk_allocation);  
  43.                 if (unlikely(inet->cork.opt == NULL))  
  44.                     return -ENOBUFS;  
  45.             }  
  46.             memcpy(inet->cork.opt, opt, sizeof(struct ip_options)+opt->optlen);  
  47.             inet->cork.flags |= IPCORK_OPT;  
  48.             inet->cork.addr = ipc->addr;  
  49.         }  
  50.         dst_hold(&rt->u.dst);  
  51.         inet->cork.fragsize = mtu = inet->pmtudisc == IP_PMTUDISC_PROBE ?  
  52.                         rt->u.dst.dev->mtu :  
  53.                         dst_mtu(rt->u.dst.path);  
  54.         inet->cork.dst = &rt->u.dst;  
  55.         inet->cork.length = 0;  
  56.         //sk_sndmsg_page指向当前正在使用的页面,sk_sndmsg_off是新数据应该存放的偏移。   
  57.         //有新的数据就继续放入这个页面的这个偏移,放不下了就再分配一个并且将sk_sndmsg_page指向它   
  58.         sk->sk_sndmsg_page = NULL;  
  59.         sk->sk_sndmsg_off = 0;  
  60.         if ((exthdrlen = rt->u.dst.header_len) != 0) {  
  61.             length += exthdrlen;  
  62.             transhdrlen += exthdrlen;  
  63.         }  
  64.     } else {  
  65.         rt = (struct rtable *)inet->cork.dst;  
  66.         if (inet->cork.flags & IPCORK_OPT)  
  67.             opt = inet->cork.opt;  
  68.         transhdrlen = 0;  
  69.         exthdrlen = 0;  
  70.         mtu = inet->cork.fragsize;  
  71.     }  
  72.     //根据目标路由项的发送设备计算出的需要为L2首部预留的最大长度   
  73.     hh_len = LL_RESERVED_SPACE(rt->u.dst.dev);  
  74.     //ip首部加上选项的总长度   
  75.     fragheaderlen = sizeof(struct iphdr) + (opt ? opt->optlen : 0);  
  76.     //一个分片的最大长度,注意分片的数据部分的长度必须八字节对齐    
  77.     maxfraglen = ((mtu - fragheaderlen) & ~7) + fragheaderlen;  
  78.       
  79.     //一个ip封包的最大长度为64k(0xffff),如果cork.length(累积的所有ip分片总长度)+length(当前分片长度)超过0xffff则出错   
  80.     if (inet->cork.length + length > 0xFFFF - fragheaderlen) {  
  81.         ip_local_error(sk, EMSGSIZE, rt->rt_dst, inet->dport, mtu-exthdrlen);  
  82.         return -EMSGSIZE;  
  83.     }  
  84.     /* transhdrlen > 0 means that this is the first fragment and we wish 
  85.      * it won't be fragmented in the future.*/  
  86.     //如果满足以下条件则将csummode赋值为CHECKSUM_PARTIAL(作用尚不明白):   
  87.     //1.transhdrlen不为0(说明是第一个分片);   
  88.     //2.length+fragheaderlen<=mtu说明当前的ip包可以整个发出去,不需分片;   
  89.     //3.目标设备特性有NETIF_F_V4_CSUM(支持L4层的硬件校验和);   
  90.     //4.exthdrlen等于0(没有IPSec首部)   
  91.     if (transhdrlen &&  
  92.         length + fragheaderlen <= mtu &&  
  93.         rt->u.dst.dev->features & NETIF_F_V4_CSUM &&  
  94.         !exthdrlen)  
  95.         csummode = CHECKSUM_PARTIAL;  
  96.     //从这里可以看出cork.length存储的是当前ip封包的总长度   
  97.     inet->cork.length += length;  
  98.     if (((length> mtu) || !skb_queue_empty(&sk->sk_write_queue)) &&  
  99.         (sk->sk_protocol == IPPROTO_UDP) &&  
  100.         (rt->u.dst.dev->features & NETIF_F_UFO)) {  
  101.         err = ip_ufo_append_data(sk, getfrag, from, length, hh_len,  
  102.                      fragheaderlen, transhdrlen, mtu,  
  103.                      flags);  
  104.         if (err)  
  105.             goto error;  
  106.         return 0;  
  107.     }  
  108.     /* So, what's going on in the loop below? 
  109.      * We use calculated fragment length to generate chained skb, 
  110.      * each of segments is IP fragment ready for sending to network after 
  111.      * adding appropriate IP header.*/  
  112.     //从sk->sk_write_queue取出末尾元素,赋给skb,若为空,则说明此为第一个分片,直接跳到alloc_new_skb   
  113.     if ((skb = skb_peek_tail(&sk->sk_write_queue)) == NULL)  
  114.         goto alloc_new_skb;  
  115.     //主循环将数据拷贝入缓冲区,如果不够的话随时可以分配新的skb或者page,直到length==0就是拷贝完成   
  116.     while (length > 0) {  
  117.         /* Check if the remaining data fits into current packet. */  
  118.         /*计算本次循环允许拷贝的数据量,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三种情况: 
  119.         (1)copy>0,说明上一个skb还有一定空间,数据可以拷贝到上一个skb中去。 
  120.         (2)copy=0.说明上一个skb刚好填满,这时需要重新分配skb填入新数据。 
  121.         (3)copy<0,说明上一个skb已经填满,但是末尾有些数据不是8字节对齐,需要拷贝到新分配的skb中去。 
  122.         对于第一种情况,跳过分配skb的步骤,直接向上一个skb拷贝数据,后两种情况则需要重新分配skb。*/  
  123.         copy = mtu - skb->len;  
  124.         if (copy < length)  
  125.             copy = maxfraglen - skb->len;  
  126.         if (copy <= 0) {  
  127.             char *data; //指向当前拷贝数据的目的地址   
  128.             unsigned int datalen; //本次循环所需拷贝的数据长度,不包括ip头   
  129.             unsigned int fraglen; //此ip分片的长度,包括ip头、ip选项和数据,一般等于datalen+fragheaderlen   
  130.             unsigned int fraggap; //需要从上一个skb尾部移动到新skb开头的数据长度,为了保证ip分片的8字节对齐   
  131.             unsigned int alloclen; //需要分配的skb数据部分长度(除去L2头部,实际分配时还要加上L2首部长度),   
  132.                                     //如果支持S/G则需要多少就刚好分配多少,若不支持S/G或者有MSG_MORE则分配mtu大小   
  133.             struct sk_buff *skb_prev; //总是指向前一个skb,如果这时正在处理第一个skb,则它为空指针   
  134. alloc_new_skb:  
  135.             skb_prev = skb;  
  136.             if (skb_prev) //检查有多少数据需要从前一个skb移动到新skb来保证8字节对齐   
  137.                 fraggap = skb_prev->len - maxfraglen;  
  138.             else  
  139.                 fraggap = 0;  
  140.             /* If remaining data exceeds the mtu, 
  141.              * we know we need more fragment(s). */  
  142.             /*先令datalen = length + fraggap,为待拷贝数据总长度, 
  143.             如果大于一分片所能容纳的数据 mtu - fragheaderlen,则令datalen=maxfraglen - fragheaderlen, 
  144.             注意这里也可能产生8字节不对齐的情况,就是datalen虽然不大于mtu-fragheaderlen但是大于了maxfraglen-fragheaderlen, 
  145.             可以看出这种情况下最后一个分片似乎没有遵守8字节对齐。 
  146.             (附:经过实测,ip分片的最后一个分片允许不8字节对齐,因为offset是8字节对齐, 
  147.             所以只要求非最后分片的报文长度是8字节对齐,因此这里如果能直接放下所有数据就可以不检测对齐)。*/  
  148.             datalen = length + fraggap;  
  149.             if (datalen > mtu - fragheaderlen)  
  150.                 datalen = maxfraglen - fragheaderlen;  
  151.             fraglen = datalen + fragheaderlen;  
  152.             //如果用户还要输入数据(MSG_MORE)并且网卡不支持S/G的话,令alloclen=mtu,   
  153.             //算是为后来的数据预先准备空间。否则只令alloclen = datalen + fragheaderlen。   
  154.             if ((flags & MSG_MORE) &&  
  155.                 !(rt->u.dst.dev->features&NETIF_F_SG))  
  156.                 alloclen = mtu;  
  157.             else  
  158.                 alloclen = datalen + fragheaderlen;  
  159.             /* The last fragment gets additional space at tail. 
  160.              * Note, with MSG_MORE we overallocate on fragments, 
  161.              * because we have no idea what fragment will be 
  162.              * the last.*/  
  163.             /*如果当前分片是最后一个分片(datalen == length + fraggap),那么alloclen还要加上rt->u.dst.trailer_len, 
  164.             注释中写道最后一个分片需要在末尾留出一些空间,猜测可能是为某种协议的尾部预留空间。 
  165.             个人觉得这里只能判断是当前发送请求的最后一个分片,不能判断是不是整个ip封包的最后分片, 
  166.             完全有可能当前指定了MSG_MORE,后面还要来数据),*/  
  167.             if (datalen == length + fraggap)  
  168.                 alloclen += rt->u.dst.trailer_len;  
  169.             //如果当前是第一个分片(transhdrlen!=0),那么调用sock_alloc_send_skb;   
  170.             //否则调用skb = sock_wmalloc(sk,alloclen + hh_len + 15, 1,sk->sk_allocation);    
  171.             //从参数可以看出,分配的buf长度为ip首部长度加上数据长度加上尾部长度(这三个包含在alloclen)加上l2首部长度,   
  172.             //另外加上15估计是为了为某种对齐预留空间。   
  173.             if (transhdrlen) {  
  174.                 skb = sock_alloc_send_skb(sk,  
  175.                         alloclen + hh_len + 15,  
  176.                         (flags & MSG_DONTWAIT), &err);  
  177.             } else {  
  178.                 skb = NULL;  
  179.                 if (atomic_read(&sk->sk_wmem_alloc) <=  
  180.                     2 * sk->sk_sndbuf)  
  181.                     skb = sock_wmalloc(sk,  
  182.                                alloclen + hh_len + 15, 1,  
  183.                                sk->sk_allocation);  
  184.                 if (unlikely(skb == NULL))  
  185.                     err = -ENOBUFS;  
  186.             }  
  187.             if (skb == NULL)  
  188.                 goto error;  
  189.             /* 
  190.              *  Fill in the control structures 
  191.              */  
  192.             /*对刚分配的skb进行初始化,操作包括: 
  193.             ip_summed设置为csummode(此时可能为CHECKSUM_PARTIAL或CHECKSUM_NONE)。 
  194.             校验和skb->csum设为0,在头部预留hh_len空间给L2首部。 
  195.             接下来在中间留出fraglen大小的数据部分用来存放ip数据,同时将局部变量data指向skb传输层(包括IPSec)的开头, 
  196.             将skb->network_header指向skb->data+exthdrlen,将skb->transport_header指向skb->network_header + fragheaderlen。*/  
  197.             skb->ip_summed = csummode;  
  198.             skb->csum = 0;  
  199.             skb_reserve(skb, hh_len);  
  200.             /*Find where to start putting bytes. */  
  201.             data = skb_put(skb, fraglen);  
  202.             skb_set_network_header(skb, exthdrlen);  
  203.             skb->transport_header = (skb->network_header +  
  204.                          fragheaderlen);  
  205.             data += fragheaderlen;  
  206.             //如果fraggap不为0,说明有些数据需要从上一个skb拷贝到当前skb,   
  207.             //这时进行拷贝并且重新计算上一个skb和本skb的ip校验和,同时将data指针后移越过刚拷贝的fraggap数据   
  208.             if (fraggap) {  
  209.                 skb->csum = skb_copy_and_csum_bits(  
  210.                     skb_prev, maxfraglen,  
  211.                     data + transhdrlen, fraggap, 0);  
  212.                 skb_prev->csum = csum_sub(skb_prev->csum,  
  213.                               skb->csum);  
  214.                 data += fraggap;  
  215.                 pskb_trim_unique(skb_prev, maxfraglen);  
  216.             }  
  217.             //重新计算copy,看起来应该是L4的负载部分长度,不知道为何要跳过传输层首部,   
  218.             //如果copy>0说明传输层有数据需要拷贝,调用getfrag将L4数据拷贝到skb中去,   
  219.             //这里getfrag可以参考ip_generic_getfrag,注意这里的getfrag的from参数有可能是用户空间的指针   
  220.             copy = datalen - transhdrlen - fraggap;  
  221.             if (copy > 0 && getfrag(from, data + transhdrlen, offset, copy, fraggap, skb) < 0) {  
  222.                 err = -EFAULT;  
  223.                 kfree_skb(skb);  
  224.                 goto error;  
  225.             }  
  226.             //offset为尚未拷贝的用户数据的起始地址偏移   
  227.             //这里copy并不等于datalen - fraggap,参考上面几行,copy = datalen - transhdrlen - fraggap   
  228.             //可以看到getfrag的第二个参数也跳过了transhdrlen。从参数from来看,明明拷贝了L4首部,但是长度却没计算L4首部,不知为何   
  229.             offset += copy;  
  230.             length -= datalen - fraggap;  
  231.             transhdrlen = 0;  
  232.             exthdrlen = 0;  
  233.             csummode = CHECKSUM_NONE;  
  234.             /* 
  235.              * Put the packet on the pending queue. 
  236.              */  
  237.             __skb_queue_tail(&sk->sk_write_queue, skb);  
  238.             continue;  
  239.         }  
  240.         //处理copy>=length也就是说上一个skb的空间末尾剩余空间大于带拷贝数据的情况,   
  241.         //这种情况无须新分配skb,而可以直接使用上一个skb末尾的空闲空间   
  242.         if (copy > length)  
  243.             copy = length;  
  244.         //下面根据发送设备是否支持S/G而分为两条路,   
  245.         if (!(rt->u.dst.dev->features&NETIF_F_SG)) {  
  246.             //不支持S/G说明上一个skb一定没有使用frags分片保存数据,而是全部保存在skb的主buf中,   
  247.             //因此可以直接调用getfrag将数据拷贝到上一个skb的剩余空间处。   
  248.             unsigned int off;  
  249.             off = skb->len;  
  250.             if (getfrag(from, skb_put(skb, copy),  
  251.                     offset, copy, off, skb) < 0) {  
  252.                 __skb_trim(skb, off);  
  253.                 err = -EFAULT;  
  254.                 goto error;  
  255.             }  
  256.         } else {  
  257.             //支持S/G说明上一个skb是分frags存放的,而上一个skb的数据小于mtu,则可以向上一个skb添加数据,   
  258.             //对于支持SG的网卡来说,这时不一定有剩余空间,要看上一个分配的page还有没有空位,如果没有空位就要重新分配page存放新数据   
  259.             //首先从skb_shared_info中取得nr_frags,然后在从skb_shared_info中取得第nr_frags-1(即最后一个)frag,   
  260.             //然后从sk->sk_sndmsg_page取得缓存在sock中的页面,这个缓存页是用来保存最近使用过的页面的,   
  261.             //再从sk->sk_sndmsg_off取得该缓存页面空闲空间的偏移指针   
  262.             int i = skb_shinfo(skb)->nr_frags;  
  263.             skb_frag_t *frag = &skb_shinfo(skb)->frags[i-1];  
  264.             struct page *page = sk->sk_sndmsg_page;  
  265.             int off = sk->sk_sndmsg_off;  
  266.             unsigned int left;  
  267.             //如果缓存页面存在并且页面上的剩余空间大于0:   
  268.             if (page && (left = PAGE_SIZE - off) > 0) {  
  269.                 //当left>0   
  270.                 //判断待拷贝数据(copy)是否大于剩余空间(left),若不大于则令copy=left。   
  271.                 //然后看sock中的缓存页(page)是否等于skb_shared_info中的最后一个页面,   
  272.                 //若不等(这种情况为什么会发生?)则将page引用计数+1并且添加到skb_shared_info的frags末尾,frag指向新frag   
  273.                 if (copy >= left)  
  274.                     copy = left;  
  275.                 if (page != frag->page) {  
  276.                     if (i == MAX_SKB_FRAGS) {  
  277.                         err = -EMSGSIZE;  
  278.                         goto error;  
  279.                     }  
  280.                     get_page(page);  
  281.                     skb_fill_page_desc(skb, i, page, sk->sk_sndmsg_off, 0);  
  282.                     frag = &skb_shinfo(skb)->frags[i];  
  283.                 }  
  284.             } else if (i < MAX_SKB_FRAGS) {   
  285.                 //当left==0且i<MAX_SKB_FRAGS,这时会重新分配一个页面并且添加到sock的缓存页以及skb_shared_info的frags末尾,frag指向新frag   
  286.                 if (copy > PAGE_SIZE)  
  287.                     copy = PAGE_SIZE;  
  288.                 page = alloc_pages(sk->sk_allocation, 0);  
  289.                 if (page == NULL)  {  
  290.                     err = -ENOMEM;  
  291.                     goto error;  
  292.                 }  
  293.                 sk->sk_sndmsg_page = page;  
  294.                 sk->sk_sndmsg_off = 0;  
  295.                 skb_fill_page_desc(skb, i, page, 0, 0);  
  296.                 frag = &skb_shinfo(skb)->frags[i];  
  297.             } else {  
  298.                 //left==0并且i>=MAX_SKB_FRAGS,出错返回-EMSGSIZE   
  299.                 err = -EMSGSIZE;  
  300.                 goto error;  
  301.             }  
  302.             //经过以上处理应该都已经有空间了,虽然不一定能容纳下所有数据,这时调用getfrag将数据拷贝入目标页面   
  303.             if (getfrag(from, page_address(frag->page)+frag->page_offset+frag->size, offset, copy, skb->len, skb) < 0) {  
  304.                 err = -EFAULT;  
  305.                 goto error;  
  306.             }  
  307.             //拷贝完成,调整指针,全部加上copy大小   
  308.             sk->sk_sndmsg_off += copy;  
  309.             frag->size += copy;  
  310.             skb->len += copy;  
  311.             skb->data_len += copy;  
  312.             skb->truesize += copy;  
  313.             atomic_add(copy, &sk->sk_wmem_alloc);  
  314.         }  
  315.         offset += copy;  
  316.         length -= copy;  
  317.     }  
  318.     return 0;  
  319. error:  
  320.     inet->cork.length -= length;  
  321.     IP_INC_STATS(sock_net(sk), IPSTATS_MIB_OUTDISCARDS);  
  322.     return err;  
  323. }   

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多