ruger 发表于 2006-12-20 21:59:58

请教:sk_buff结构中len和data_len的区别

倪继利的书上笼统的注释成len:实际的数据长度,data_len:数据长度。跟没说一样
有几种说法:
1,len是数据长度,以tcp包为例,len指的是tcp部分的数据长度,也就是除去以太头,ip头,tcp头以后的部分,data_len不祥。
2,len是一个可改变的长度,到哪一层就算哪一层的数据长度。data_len是整个数据报的长度(从以太网到tcp的数据部分都算)。
3,len,data_len和tcp的重组有关,len指重组后的数据长度,而data_len是指一个数据包的长度
4,len是指数据包全部数据的长度,包括data指向的数据和end后面的分片的数据的总长,而data_len只包括分片的数据的长度。而truesize的最终值是len+sizeof(struct sk_buff)。

这些说发不知道从哪听来的,快把我弄晕了
高手救我~

ruger 发表于 2006-12-24 09:33:32

顶顶,貌似3天没人发帖了~

zyzii 发表于 2006-12-25 14:51:36

应该是4

ruger 发表于 2006-12-25 16:28:55

应该是4
这是我从菰城浪子的blog上抄下来的,但还是不明白啊
这个时候数据包已经完成重组了吗?一个sk_buff数据结构就那么大,怎么后面还会跟着一些分片,这是什么意思?
还有sizeof(sk_buff)到底等于多少?是变化的吗?
为了使用套接字缓冲区,内核创建了两个后备高速缓存(looaside cache),它们分别是skbuff_head_cache和skbuff_fclone_cache,协议栈中所使用到的所有的sk_buff结构都是从这两个后备高速缓存中分配出来的。两者的区别在于skbuff_head_cache在创建时指定的单位内存区域的大小是sizeof(struct sk_buff),可以容纳任意数目的struct sk_buff,而skbuff_fclone_cache在创建时指定的单位内存区域大小是2*sizeof(struct sk_buff)+sizeof(atomic_t),它的最小区域单位是一对strcut sk_buff和一个引用计数,这一对sk_buff是克隆的,即它们指向同一个数据缓冲区,引用计数值是0,1或2,表示这一对中有几个sk_buff 已被使用。创建一个套接字缓冲区,最常用的操作是alloc_skb,它在skbuff_head_cache中创建一个struct sk_buff,如果要在skbuff_fclone_cache中创建,可以调用__alloc_skb,通过特定参数进行。
怎么叫可以容纳任意数目的sk_buff?
IP的重组在哪个函数那完成?重组过程怎么样?重组以后就把分片放在sk_buff后面的skb_shared_info函数中的struct sk_buff        *frag_list;了吗?
具体怎么个过程?望详解~~谢谢
zyzii,好久没看到你了~~~

zyzii 发表于 2006-12-25 22:05:47

先回答一部分。
    在ddd下用命令:
       # psizeof(struct sk_buff)
    显示结果为146个字节。

skbuff_head_cache和skbuff_fclone_cache都是slab的缓存,见/net/core/skbuff.c:

void __init skb_init(void)
{
        skbuff_head_cache = kmem_cache_create("skbuff_head_cache",
                                              sizeof(struct sk_buff),
                                              0,
                                              SLAB_HWCACHE_ALIGN|SLAB_PANIC,
                                              NULL, NULL);
        skbuff_fclone_cache = kmem_cache_create("skbuff_fclone_cache",
                                                (2*sizeof(struct sk_buff)) +
                                                sizeof(atomic_t),
                                                0,
                                                SLAB_HWCACHE_ALIGN|SLAB_PANIC,
                                                NULL, NULL);
}
      这2个SLAB的缓存是为申请sk_buff用的。说可以申请任意个,是根据SLAB的特点说的,用完了还,然后再申请,好像可以申请任意个。

ruger 发表于 2006-12-26 08:58:29

ddd怎么用我怎么没
# p sizeof(struct sk_buff)
怎么没起作用?
print p?也不行
感觉一个sk_buff应该没有那么大啊?
sk_buff->end后面还有一个结构skb_shared_info存储分片,len的长度包含分片的长度,data_len不包含分片的长度,但是分片是在本地重组的,什么时候重组的?什么函数来重组的?
是网卡驱动完成这种功能吗?
谢谢~

zyzii 发表于 2006-12-26 09:57:40

ddd的view下的GDB console就这个窗口里阿。
# p sizeof(struct sk_buff)
显示为164字节,上次看错了。

   至于分片,我还要看看,也是不太明白。因为在驱动的feature设了:
    dev->features |=NETIF_F_SG| NETIF_F_HW_CSUM |NETIF_F_NO_CSUM |NETIF_F_IP_CSUM ;

   在TCP下发大包,LINUX好像不会分片,但UDP的大包会分片。
   比较有意思,因为最近手上有些事,所以等看明白了再回答你。

zyzii 发表于 2006-12-26 15:01:27

数据的TSO,UFO分片主要是由ip_push_pending_frames()函数完成的(见/net/ipv4/ip_output.c)。

      该函数只对UDP的UFO分片有效,对TCP的TSO竟然是没有效的,真奇怪的东西。
      在驱动中设置:
         dev->features |=NETIF_F_SG| NETIF_F_HW_CSUM |NETIF_F_NO_CSUM |NETIF_F_IP_CSUM ;
      dev->features |= NETIF_F_TSO;
      dev->features |= NETIF_F_UFO;

   结果也是UDP会产生分片,TCP不会,呵呵。 下面分析一下ip_push_pending_frames()这个很经典的函数()

zyzii 发表于 2006-12-26 15:28:07

关于 ip_push_pendiing_frames()这个函数,竟然有个漏洞:
见:
      http://netsecurity.51cto.com/art/200603/24448.htm

    下面是这个函数的代码:
    int ip_push_pending_frames(struct sock *sk)
{
        struct sk_buff *skb, *tmp_skb;
        struct sk_buff **tail_skb;
        struct inet_sock *inet = inet_sk(sk);
        struct ip_options *opt = NULL;
        struct rtable *rt = inet->cork.rt;
        struct iphdr *iph;
        __be16 df = 0;
        __u8 ttl;
        int err = 0;

        if ((skb = __skb_dequeue(&sk->sk_write_queue)) == NULL)
                goto out;
        tail_skb = &(skb_shinfo(skb)->frag_list);

        /* move skb->data to ip header from ext header */
        if (skb->data < skb->nh.raw)
                __skb_pull(skb, skb->nh.raw - skb->data);
        while ((tmp_skb = __skb_dequeue(&sk->sk_write_queue)) != NULL) {
                __skb_pull(tmp_skb, skb->h.raw - skb->nh.raw);
                *tail_skb = tmp_skb;
                tail_skb = &(tmp_skb->next);
                skb->len += tmp_skb->len;
                skb->data_len += tmp_skb->len;
                skb->truesize += tmp_skb->truesize;
                __sock_put(tmp_skb->sk);
                tmp_skb->destructor = NULL;
                tmp_skb->sk = NULL;
        }
。。。。。。



仅以UDP为分析对象。tail_skb被赋值 &(skb_shinfo(skb)->frag_list),即UDP分片的链表首部指针。其中 skb是从SOCK结构中的写队列中取的第一个sk_buff结构体。
      tmp_skb是从SOCK结构中的写队列中取的剩余的一个sk_buff结构体。
                   *tail_skb = tmp_skb;
                tail_skb = &(tmp_skb->next);
   这2行是把tmp_skb挂到skb的frag_list的链上。
   
   下面3行比较关键:
                skb->len += tmp_skb->len;
                skb->data_len += tmp_skb->len;
                skb->truesize += tmp_skb->truesize;

zyzii 发表于 2006-12-26 15:32:11

这三行是len,data_len, truesize的关系的一个很好的示范。

注意此时,并没有skb_shinfo(skb)->frags[]的数据计算!!!!
   
    skb_shinfo(skb)->frags[]代表的是一个skb的数据放到了不同的页面中。
    skb_shinfo(skb)-frag_list代表的是多个skb被组合成一个skb.
    真是个麻烦的家伙。
   说明len是指数据包全部数据的长度,包括data指向的数据和end后面的分片的数据的总长,而data_len只包括分片的数据的长度。 truesize的值较复杂。
   
    用公式表示:
   skb->len =(skb->tail - skb->data) +(skb_shinfo(skb)->frags的长度) +(skb_shinfo(skb)->frag_list中的所有skb->len长度和)

   skb->data_len = (skb_shinfo(skb)->frags的长度) +(skb_shinfo(skb)->frag_list中的所有skb->len长度和)
   
skb->truesize = (skb->end - skb->head) + (skb_shinfo(skb)->frags的长度) +(skb_shinfo(skb)->frag_list的truesize长度总和)+ sizeof(sk_buff)

   
skb_shinfo(skb)->frag_list本身是个sk_buff结构的链表,这个链上的每个sk_buff的又可能是个递归的树形结构的根节点。也就是说假设A是该链上的一个结点,则skb_shinfo(A)->frag_list又是一个链表,依次递归。。。

zyzii 发表于 2006-12-26 16:21:24

如果一个skb的skb_shinfo(skb)结构中既有frag_list不为空,又有frags不为空,这就有些麻烦了。
   先看一下结构:
struct skb_shared_info {
        atomic_t        dataref;
        unsigned short        nr_frags;
        unsigned short        gso_size;
        /* Warning: this field is not always filled in (UFO)! */
        unsigned short        gso_segs;
        unsigned shortgso_type;
        unsigned int    ip6_frag_id;
        struct sk_buff        *frag_list;
        skb_frag_t        frags;
};

zyzii 发表于 2006-12-26 18:49:01

在IP分片时会调到ip_fragment(),该函数主要是把SKB分成适合MTU大小的包发出去。见/net/ipv4/ip_output.c,该函数和ip_push_pendiing_frames()在同一个文件中。

   下面是部分代码:
int ip_fragment(struct sk_buff *skb, int (*output)(struct sk_buff*))
{
。。。。。。。。。。。。。。。。。。。
if (skb_shinfo(skb)->frag_list) {
                struct sk_buff *frag;
                int first_len = skb_pagelen(skb);
。。。。。。。。。。。。。。。。。。。。。。

                err = 0;
                offset = 0;
                frag = skb_shinfo(skb)->frag_list;
                skb_shinfo(skb)->frag_list = NULL;
                skb->data_len = first_len - skb_headlen(skb);
                skb->len = first_len;
                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. */
                        if (frag) {
                                frag->ip_summed = CHECKSUM_NONE;
                                frag->h.raw = frag->data;
                                frag->nh.raw = __skb_push(frag, hlen);
                                memcpy(frag->nh.raw, iph, hlen);
                                iph = frag->nh.iph;
                                iph->tot_len = htons(frag->len);
                                ip_copy_metadata(frag, skb);
                                if (offset == 0)
                                        ip_options_fragment(frag);
                                offset += skb->len - hlen;
                                iph->frag_off = htons(offset>>3);
                                if (frag->next != NULL)
                                        iph->frag_off |= htons(IP_MF);
                                /* Ready, complete checksum */
                                ip_send_check(iph);
                        }

                        err = output(skb);

                        if (!err)
                                IP_INC_STATS(IPSTATS_MIB_FRAGCREATES);
                        if (err || !frag)
                                break;

                        skb = frag;
                        frag = skb->next;
                        skb->next = NULL;
                }

                if (err == 0) {
                        IP_INC_STATS(IPSTATS_MIB_FRAGOKS);
                        return 0;
                }

               。。。。。。。。。。。。。。。。。。。。。。。
        }

      其中有一行代码
          int first_len = skb_pagelen(skb);
      为什么叫first_len呢? 因为skb_pagelen(skb)计算的就是skb中的自身数据的(不包括skb_shinfo(skb)结构中既有frag_list)。

zyzii 发表于 2006-12-27 10:12:47

static inline int skb_pagelen(const struct sk_buff *skb)
{
        int i, len = 0;

        for (i = (int)skb_shinfo(skb)->nr_frags - 1; i >= 0; i--)
                len += skb_shinfo(skb)->frags.size;
        return len + skb_headlen(skb);
}

static inline unsigned int skb_headlen(const struct sk_buff *skb)
{
        return skb->len - skb->data_len;
}

   首先skb_headlen得出的是大小是(skb->tail-skb->data),
   在skb_pagelen里首先计算skb_shinfo(skb)->frags的长度,然后再加上skb_headlen()的值,得出的是什么呢? 是隶属于本skb的,不包含skb_shinfo(skb)->frag_list的skb数据的值,也就是第一个skb的值了。这也就是为什么被命名为first_len的原因。

zyzii 发表于 2006-12-27 10:23:09

在ip_fragment()接着有2行:
                      skb->data_len = first_len - skb_headlen(skb);
                      skb->len = first_len;
第一行计算的是本SKB的data_len的值,不包括skb_shinfo(skb)->frag_list的skb数据的值。
第二行是将skb->len的值设为first_len;看到没?完全不要skb_shinfo(skb)->frag_list中的数据了。为什么要这样做呢?
      因为要把skb_shinfo(skb)->frag_list中的skb做当个的IP分片发出去啊!!!看下面的for循环就明白了:
      for (;;) {
                   if (frag) {
                                frag->ip_summed = CHECKSUM_NONE;
                                frag->h.raw = frag->data;
                                frag->nh.raw = __skb_push(frag, hlen);
                                memcpy(frag->nh.raw, iph, hlen);
                                iph = frag->nh.iph;
                                iph->tot_len = htons(frag->len);
                                ip_copy_metadata(frag, skb);
                                if (offset == 0)
                                        ip_options_fragment(frag);
                                offset += skb->len - hlen;
                                iph->frag_off = htons(offset>>3);
                                if (frag->next != NULL)
                                        iph->frag_off |= htons(IP_MF);
                                /* Ready, complete checksum */
                                ip_send_check(iph);
                          }

                        err = output(skb);

                        if (!err)
                                IP_INC_STATS(IPSTATS_MIB_FRAGCREATES);
                        if (err || !frag)
                                break;

                        skb = frag;
                        frag = skb->next;
                        skb->next = NULL;
         。。。。。
   }

zyzii 发表于 2006-12-27 10:26:57

在ip_fragment()的后面是 slow_path的方式进行IP分片的,主要是处理没有skb_shinfo(skb)->frag_list的情况。
   
          在slow_path中是进行传统意义上的分片,就是把一个整个的数据分割成一个一个的小块发送出去。
页: [1]
查看完整版本: 请教:sk_buff结构中len和data_len的区别