QQ登录

只需一步,快速开始

 找回密码
 注册

QQ登录

只需一步,快速开始

查看: 6313|回复: 14

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

[复制链接]
发表于 2006-12-20 21:59:58 | 显示全部楼层 |阅读模式
倪继利的书上笼统的注释成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)。

这些说发不知道从哪听来的,快把我弄晕了
高手救我~
 楼主| 发表于 2006-12-24 09:33:32 | 显示全部楼层
顶顶,貌似3天没人发帖了~
回复

使用道具 举报

发表于 2006-12-25 14:51:36 | 显示全部楼层
应该是4
回复

使用道具 举报

 楼主| 发表于 2006-12-25 16:28:55 | 显示全部楼层
[quote:b10b83da83="zyzii"]应该是4[/quote]
这是我从菰城浪子的blog上抄下来的,但还是不明白啊
这个时候数据包已经完成重组了吗?一个sk_buff数据结构就那么大,怎么后面还会跟着一些分片,这是什么意思?
还有sizeof(sk_buff)到底等于多少?是变化的吗?
[quote:b10b83da83="菰城浪子的blog"]为了使用套接字缓冲区,内核创建了两个后备高速缓存(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,通过特定参数进行。[/quote]
怎么叫可以容纳任意数目的sk_buff?
IP的重组在哪个函数那完成?重组过程怎么样?重组以后就把分片放在sk_buff后面的skb_shared_info函数中的struct sk_buff        *frag_list;了吗?
具体怎么个过程?望详解~~谢谢
zyzii,好久没看到你了~~~
回复

使用道具 举报

发表于 2006-12-25 22:05:47 | 显示全部楼层
先回答一部分。
    在ddd下用命令:
       # p  sizeof(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的特点说的,用完了还,然后再申请,好像可以申请任意个。
回复

使用道具 举报

 楼主| 发表于 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不包含分片的长度,但是分片是在本地重组的,什么时候重组的?什么函数来重组的?
是网卡驱动完成这种功能吗?
谢谢~
回复

使用道具 举报

发表于 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的大包会分片。
   比较有意思,因为最近手上有些事,所以等看明白了再回答你。
回复

使用道具 举报

发表于 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()这个很经典的函数()
回复

使用道具 举报

发表于 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;
回复

使用道具 举报

发表于 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又是一个链表,依次递归。。。
回复

使用道具 举报

发表于 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 short  gso_type;
        unsigned int    ip6_frag_id;
        struct sk_buff        *frag_list;
        skb_frag_t        frags[MAX_SKB_FRAGS];
};
回复

使用道具 举报

发表于 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)。
回复

使用道具 举报

发表于 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的原因。
回复

使用道具 举报

发表于 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;
           。。。。。
     }
回复

使用道具 举报

发表于 2006-12-27 10:26:57 | 显示全部楼层
在ip_fragment()的后面是 slow_path的方式进行IP分片的,主要是处理没有skb_shinfo(skb)->frag_list的情况。
     
          在slow_path中是进行传统意义上的分片,就是把一个整个的数据分割成一个一个的小块发送出去。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

GMT+8, 2024-4-19 10:00 , Processed in 0.054917 second(s), 15 queries .

© 2021 Powered by Discuz! X3.5.

快速回复 返回顶部 返回列表