请教: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)。
这些说发不知道从哪听来的,快把我弄晕了
高手救我~ 顶顶,貌似3天没人发帖了~ 应该是4 应该是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,好久没看到你了~~~ 先回答一部分。
在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的特点说的,用完了还,然后再申请,好像可以申请任意个。 ddd怎么用我怎么没
# p sizeof(struct sk_buff)
怎么没起作用?
print p?也不行
感觉一个sk_buff应该没有那么大啊?
sk_buff->end后面还有一个结构skb_shared_info存储分片,len的长度包含分片的长度,data_len不包含分片的长度,但是分片是在本地重组的,什么时候重组的?什么函数来重组的?
是网卡驱动完成这种功能吗?
谢谢~ 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的大包会分片。
比较有意思,因为最近手上有些事,所以等看明白了再回答你。 数据的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()这个很经典的函数() 关于 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; 这三行是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又是一个链表,依次递归。。。 如果一个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;
}; 在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)。 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的原因。 在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;
。。。。。
} 在ip_fragment()的后面是 slow_path的方式进行IP分片的,主要是处理没有skb_shinfo(skb)->frag_list的情况。
在slow_path中是进行传统意义上的分片,就是把一个整个的数据分割成一个一个的小块发送出去。
页:
[1]