SKBBUFF整理笔记.docx

上传人:b****3 文档编号:12650453 上传时间:2023-04-21 格式:DOCX 页数:13 大小:20.30KB
下载 相关 举报
SKBBUFF整理笔记.docx_第1页
第1页 / 共13页
SKBBUFF整理笔记.docx_第2页
第2页 / 共13页
SKBBUFF整理笔记.docx_第3页
第3页 / 共13页
SKBBUFF整理笔记.docx_第4页
第4页 / 共13页
SKBBUFF整理笔记.docx_第5页
第5页 / 共13页
点击查看更多>>
下载资源
资源描述

SKBBUFF整理笔记.docx

《SKBBUFF整理笔记.docx》由会员分享,可在线阅读,更多相关《SKBBUFF整理笔记.docx(13页珍藏版)》请在冰豆网上搜索。

SKBBUFF整理笔记.docx

SKBBUFF整理笔记

SKB_BUFF整理笔记

一.SKB_BUFF的基本概念

1.一个完整的skbbuff组成

(1)structsk_buff--用于维护socketbuffer状态和描述信息

(2)headerdata--独立于sk_buff结构体的数据缓冲区,用来存放报文分组,使各层协议的header存储在连续的空间中,以方便协议栈对其操作

(3)structskb_shared_info--作为headerdata的补充,用于存储ip分片,其中sk_buff*frag_list是一系列子skbuff链表,而frag[]是由一组单独的page组成的数据缓冲区

skbbuff结构图如下:

structskb_buff

表示接收或发送数据包的包头信息,其成员变量在从一层向另一层传递时会发生修改。

例如L3向L2传递前,会添加一个L3的头部,所以在添加头部前调用skb_reserve在缓冲区的头部给协议头预留一定的空间;L2向L3传递时候,L2的头部只有在网络驱动处理L2的协议时有用,L3是不会关心它的信息的。

但是,内核不会把L2的头部从缓冲区中删除,

sk_buff->h

sk_buff->nh

sk_buff->mac

指向TCP/IP各层协议头的指针:

h指向L4(传输层),nh指向L3(网络层),mac指向L2(数据链路层)。

每个指针的类型都是一个联合,包含多个数据结构,

sk_buff->head

sk_buff->data

sk_buff->tail

sk_buff->end

表示缓冲区和数据部分的边界。

在每一层申请缓冲区时,它会分配比协议头或协议数据大的空间。

head和end指向缓冲区的头部和尾部,而data和tail指向实际数据的头部和尾部。

每一层会在head和data之间填充协议头,或者在tail和end之间添加新的协议数据。

数据部分会在尾部包含一个附加的头部。

下图是TCP(L4)向下发送数据给链路层L2的过程。

注意skb_buff->data在从L4向L2穿越过程中的变化

几个len的区别?

(1)sk_buff->len:

表示当前协议数据包的长度。

它包括主缓冲区中的数据长度(data指针指向它)和分片中的数据长度。

比如,处在网络层,len指的是ip包的长度,如果包已经到了应用层,则len是应用层头部和数据载荷的长度。

(2)sk_buff->data_len:

data_len只计算分片中数据的长度,即skb_shared_info中有效数据总长度(包括frag_list,frags[]中的扩展数据),一般为0

(3)sk_buff->truesize:

这是缓冲区的总长度,包括sk_buff结构和数据部分。

如果申请一个len字节的缓冲区,alloc_skb函数会把它初始化成len+sizeof(sk_buff)。

当skb->len变化时,这个变量也会变化。

通常,DataBuffer只是一个简单的线性buffer,这时候len就是线性buffer中的数据长度;

但在有‘pageddata’情况下,DataBuffer不仅包括第一个线性buffer,还包括多个pagebuffer;这种情况下,‘data_len’指的是pagebuffer中数据的长度,’len’指的是线性buffer加上pagebuffer的长度;len–data_len就是线性buffer的长度。

二.sk_buff结构操作函数

内核通过alloc_skb()和dev_alloc_skb()为套接字缓存申请内存空间。

这两个函数的定义位于net/core/skbuff.c文件内。

通过这alloc_skb()申请的内存空间有两个,一个是存放实际报文数据的内存空间,通过kmalloc()函数申请;一个是sk_buff数据结构的内存空间,通过kmem_cache_alloc()函数申请。

dev_alloc_skb()的功能与alloc_skb()类似,它只被驱动程序的中断所调用,与alloc_skb()比较只是申请的内存空间长度多了16个字节。

内核通过kfree_skb()和dev_kfree_skb()释放为套接字缓存申请的内存空间。

dev_kfree_skb()被驱动程序使用,功能与kfree_skb()一样。

当skb->users为1时kfree_skb()才会执行释放内存空间的动作,否则只会减少skb->users的值。

skb->users为1表示已没有其他用户使用该缓存了。

skb_reserve()函数为skb_buff缓存结构预留足够的空间来存放各层网络协议的头信息。

该函数在在skb缓存申请成功后,加载报文数据前执行。

在执行skb_reserve()函数前,skb->head,skb->data和skb->tail指针的位置的一样的,都位于skb内存空间的开始位置。

这部份空间叫做headroom。

有效数据后的空间叫tailroom。

skb_reserve的操作只是把skb->data和skb->tail指针向后移,但缓存总长不变。

运行skb_reserve()前sk_buff的结构

sk_buff

-------------------------------->skb->head,skb->data,skb->tail

||

||

||

||

||

||

||

||

||

------------------------------->skb->end

运行skb_reserve()后sk_buff的结构

sk_buff

-------------------------------->skb->head

||

|headroom|

||

|---------------------|---------->skb->data,skb->tail

||

||

||

||

||

------------------------------->skb->end

skb_put()向后扩大数据区空间,tailroom空间减少,skb->data指针不变,skb->tail指针下移。

skb_push()向前扩大数据区空间,headroom空间减少,skb->tail指针不变,skb->data指针上移

skb_pull()缩小数据区空间,headroom空间增大,skb->data指针下移,skb->tail指针不变。

skb_shared_info结构位于skb->end后,用skb_shinfo函数申请内存空间。

该结构主要用以描述data内存空间的信息。

-------------------------------->skb->head

||

||

|sk_buff|

||

||

||

|---------------------|----------->skb->end

||

|skb_share_info|

||

---------------------

skb_clone和skb_copy可拷贝一个sk_buff结构,skb_clone方式是clone,只生成新的sk_buff内存区,不会生成新的data内存区,新sk_buff的skb->data指向旧data内存区。

skb_copy方式是完全拷贝,生成新的sk_buff内存区和data内存区。

三.skb的分配细节

1.关于SKB的分配细节.

LINUX中SKB的分配最终是由函数:

structsk_buff*__alloc_skb(unsignedintsize,gfp_tgfp_mask,intfclone)来完成.

SKB可以分为SKB描述符与SKB数据区两个部分,其中描述符必须从CACHE中来分配:

或者从skbuff_fclone_cache中分配,或者从skbuff_head_cache中来分配.

如果从分配描述符失败,则直接反悔NULL,表示SKB分配失败.

SKB描述符分配成功后,即可分配数据区.

在具体分配数据区之前首先要对数据区的长度进行ALIGN操作,通过宏SKB_DATA_ALIGN来重新确定size大小.然后戏台调用kmalloc函数分配数据区:

data=kmalloc(size+sizeof(structskb_shared_info),gfp_mask);

需要注意的是数据区的大小是SIZE的大小加上skb_shared_info结构的大小.

数据区分配成功后,便对SKB描述符进行与此数据区相关的赋值操作:

memset(skb,0,offsetof(structsk_buff,truesize));

skb->truesize=size+sizeof(structsk_buff);

atomic_set(&skb->users,1);

skb->head=data;

skb->data=data;

skb->tail=data;

skb->end=data+size;

需要主意的是,SKB的truesize的大小并不包含skb_shared_info结构的大小.另外,skb的end成员指针也就是skb_shared_info结构的起始指针,系统用

一个宏:

skb_shinfo来完成寻找skb_shared_info结构指针的操作.

最后,系统初始化skb_shared_info结构的成员变量:

atomic_set(&(skb_shinfo(skb)->dataref),1);

skb_shinfo(skb)->nr_frags=0;

skb_shinfo(skb)->tso_size=0;

skb_shinfo(skb)->tso_segs=0;

skb_shinfo(skb)->frag_list=NULL;

skb_shinfo(skb)->ufo_size=0;

skb_shinfo(skb)->ip6_frag_id=0;

最后,返回SKB的指针.

2.SKB的分配时机

SKB的分配时机主要有两种,最常见的一种是在网卡的中断中,有数据包到达的时,系统分配SKB包进行包处理;第二种情况是主动分配SKB包用于各种调试或者其他处理环境.

3.SKB的reserve操作

SKB在分配的过程中使用了一个小技巧:

即在数据区中预留了128个字节大小的空间作为协议头使用,通过移动SKB的data与tail指针的位置来实现这个功能.

4.SKB的put操作

put操作是SKB中一个非常频繁也是非常重要的操作,但是,skb_put()函数其实什么也没做!

它只是根据数据的长度移动了tail指针并改写了skb->len的值,其他的什么都没做,然后就返回了skb->data指针(就是tail指针在移动之前的位置).看上去此函数仿佛要拷贝数据到skb的数据区中,其实这事儿是insl这个函数干的,跟skb_put()函数毫不相关,不过它仍然很重要.

5.中断环境下SKB的分配流程

当数据到达网卡后,会触发网卡的中断,从而进入ISR中,系统会在ISR中计算出此次接收到的数据的字节数:

pkt_len,然后调用SKB分配函数来分配SKB:

skb=dev_alloc_skb(pkt_len+5);

我们可以看到,实际上传入的数据区的长度还要比实际接收到的字节数多,这实际上是一种保护机制.实际上,在dev_alloc_skb函数调用__dev_alloc_skb函数,而__dev_alloc_skb函数又调用alloc_skb函数时,其数据区的大小又增加了128字节,这128字节就事前面我们所说的reserve机制预留的header空间.

四.不同情况下构造skb数据包的实现

在我这个网络接口的程序中(can0),其实难点就是怎样组包。

怎样在原来数据包的基础加上自己的数据,怎样构造ip头,怎样构造udp头。

调试了两个星期,终于是调通了,在这个过程中,通过看内核源代码和自己组包的尝试,大概对组包的方法有了些了解,记录在此,留做备忘,也希望能给需要这方面信息的朋友一点帮助吧。

1,正常网卡收到数据包后的情况:

她的工作就是剥离mac头,然后给一些字段赋值,最后调用netif_rx将剥离mac头后的数据报(比如ip数据包)发送到上层协议。

由协议栈处理。

在此以ldd3中的snull为例,虽然snull跟硬件不相关,但这个过程都是类似的。

structsk_buff*skb;

structsnull_priv*priv=netdev_priv(dev);

skb=dev_alloc_skb(pkt->datalen+2);

if(!

skb){

if(printk_ratelimit())

printk(KERN_NOTICE"snullrx:

lowonmem-packetdropped/n");

priv->stats.rx_dropped++;

gotoout;

}

skb_reserve(skb,2);/*alignIPon16Bboundary*/

memcpy(skb_put(skb,pkt->datalen),pkt->data,pkt->datalen);

/*Writemetadata,andthenpasstothereceivelevel*/

skb->dev=dev;

skb->protocol=eth_type_trans(skb,dev);

skb->ip_summed=CHECKSUM_UNNECESSARY;/*don'tcheckit*/

priv->stats.rx_packets++;

priv->stats.rx_bytes+=pkt->datalen;

netif_rx(skb);

注意:

上面代码中红色放大的地方是重要的。

因为此刻收到的数据包的格式如下:

mac+ip+udp/udp+data

这时候的处理就是剥离mac头,然后需要更新的一些域值。

这些都是在函数eth_type_trans函数里做的。

需要注意的是,skb->dev=dev;这条语句是很重要的,如果没有此语句,将会导致系统错误而死机(至少在我的板子上是这样的)。

注意:

eth_type_trans()函数主要赋值的是:

skb->mac.raw,skb->protocol和skb->pkt_type。

见下面的代码有无mac头的情况。

2,完全从一个字符串开始构造一个新的skb数据包。

以前只是看过如何修改数据包,自己构造数据包,这还是头一次,刚开始确实给我难住了,来来经过看内核代码和自己摸索,我自己写的代码如下:

/*假设:

data是一个指向字符串的指针,data_len是data的长度*/

structipv6hdr*ipv6h;

structudphdr*udph;

structsk__buff*new_skb;

intlength=data_len+sizeof(structipv6hdr)+sizeof(udphdr);

new_skb=dev_alloc_skb(length);

if(!

new_skb)

{

printk("lowmemory.../n"):

return-1;

}

skb_reserve(new_skb,length);

memcpy(skb_push(new_skb,data_len),data,data_len);

new_skb->h.uh=udph=(structudphdr*)skb_push(new_skb,sizeof(structudphdr));

memcpy(udph,&udph_tmp,sizeof(structudphdr));//注意,此刻我的udph_tmp是在另一个过程中截获的数据包的udp头,如果完全是自己构造数据包,则需要自己填充udp数据头中的字段。

udph->len=..............;//此处需要给udph->len赋值。

注意udph->len是__u16的。

存储时是高低位互换的,所以你应该先将你要更新的数字编成16进制的数,然后高低位互换,在赋值给udh->len。

udplen=new_skb->len;

new_skb->nh.ipv6h=ipv6h=(structipv6hdr*)skb_push(new_skb,sizeof(structipv6hdr));

memcpy(ipv6h,&ipv6h_tmp,sizeof(structipv6hdr));//同udp头注释。

ipb6h->payload_len=..........;//此处同udph->len.需要注意的是,此处所指的长度并不包括ipv6头的长度,而是去掉ipv6头后的长度。

udph->check=0;

udph->check=csum_ipv6_magic(&ipv6h->saddr,&ipv6h->daddr,udplen,IPPROTO_UDP,csum_partial((char*)udph,udplen,0));

///////////////注意,如果是ipv4,则还需要计算ip校验和,但此处是ipv6,不用计算ip检验和,所以此处没有ipv6头的校验。

//////////////////////////

new_skb->mac.raw=new_skb->data;//因为无mac头

new_skb->protocol=htons(ETH_P_IPV6);//表明包是ipv6数据包

new_skb->pkt_type=PACKET_HOST;//表明是发往本机的包

new_skb->dev=&can_control;//此处很重要,如果没有这条语句,则内核跑死。

至少在我板子上是这样的。

can_control是我的net_device结构体变量。

netif_rx(new_skb);

3,当需要改变原有skb的数据域的情况。

此时,有两种办法:

可以先判断skb的tailroom,如果空间够大,则我们可以把需要添加的数据放在skb的tailroom里。

如果tailroom不够大,则需要调用skb_copy_expand函数来扩充tailroom或者headroom。

例如我们需要在skb的后面加上一个16个字节的字符串,则代码类似如下:

if(skb_tailroom(skb)<16)

{

nskb=skb_copy_expand(skb,skb_headroom(skb),skb_tailroom(skb)+16,GFP_ATOMIC);

if(!

nskb)

{

printk("lowmemory..../n");

dev_kfree_skb(skb);

return-1;

}

else

{

kfree_skb(skb);//注意,如果此时是钩子函数钩出来的,则skb不能在这里释放,否则会造成死机。

skb=nskb;

}

memcpy(skb_put(skb,16),ipbuf,16);//ipbuf为要加到skb后面的字符串

udplen=skb->len-sizeof(structipv6hdr);

udph->len+=0x1000;//换成十进制为+16

ipv6h->payload_len+=0x1000;

udph->check=0;

udph->check=csum_ipv6_magic(&ipv6h->saddr,&ipv6h->daddr,udplen,IPPROTO_UDP,csum_partial((char*)udph,udplen,0));

skb->mac.raw=new_skb->data;//因为无mac头

skb->protocol=htons(ETH_P_IPV6);//表明包是ipv6数据包

skb->pkt_type=PACKET_HOST;//表明是发往本机的包

skb->dev=&can_control;//此处很重要,如果没有这条语句,则内核跑死。

至少在我板子上是这样的。

can_control是我的net_device结构体变量。

netif_rx(skb);

}

注意:

当调用skb_copy_expand或者修改了skb的数据域后,一定要更新udph->len和ipv6h->payload_len。

否则上层应用(比如udp套接字)收到的数据包还是原来的数据包而不是修改后的数据包,因为udph->len的原因。

本文来自CSDN博客,转载请标明出处:

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 法律文书 > 调解书

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1