文章出处:http://book.51cto.com/art/200912/168620.htm
http://www.newsmth.net/pc/pccon.php?id=1363&nid=296040&pid=0&tag=0&tid=7570
内核层和用户层在网络方面的差别很大,在内核的网络层中sk_buff结构占有重要的地位,几乎所有的处理均与此结构有关系。网络协议栈是一个层次架构的软件结构,层与层之间通过预定的接口传递报文。网络报文中包含了在协议各层使用到的各种信息。由于网络报文之间的大小不是固定的,因此采用合适的数据结构来存储这些网络报文就显得非常重要。
1. 结构sk_buff的原型
在linux的2.6版本的内核中,采用结构sk_buff来存储这些数据。在这个结构中,既有指向网络报文的指针,同时也有描述网络报文的变量。sk_buff数据结构的代码如下所示。
struct sk_buff { struct sk_buff *next; struct sk_buff *prev; struct sock *sk; ktime_t tstamp; struct net_device *dev; union { struct dst_entry *dst; struct rtable *rtable; }; struct sec_path *sp; char cb[48]; unsigned int len, data_len; __u16 mac_len, hdr_len; union { __wsum csum; struct { __u16 csum_start; __u16 csum_offset; }; }; __u32 priority; __u8 local_df:1, cloned:1, ip_summed:2, nohdr:1, nfctinfo:3; __u8 pkt_type:3, fclone:2, ipvs_property:1, peeked:1, nf_trace:1; __be16 protocol; void (*destructor)(struct sk_buff *skb); struct nf_conntrack *nfct; struct sk_buff *nfct_reasm; struct nf_bridge_info *nf_bridge; int iif; __u16 queue_mapping; __u16 tc_index; __u16 tc_verd; __u8 ndisc_nodetype:2; dma_cookie_t dma_cookie; __u32 secmark; __u32 mark; sk_buff_data_t transport_header; sk_buff_data_t network_header; sk_buff_data_t mac_header; sk_buff_data_t tail; sk_buff_data_t end; unsigned char *head, *data; unsigned int truesize; atomic_t users; };
sk_buff主要成员的含义如下:
next: sk_buff链表中的下一个缓冲区。
prev: sk_buff链表中的前一个缓冲区。以上两个变量将sk_buff链接到一个双向链表中。
sk: 本网络报文所属的sock结构,此值仅在本机发出的报文中有效,从网络收到的报文此值为空。
tstamp: 报文收到的时间戳。
dev: 收到此报文的网络设备。
transport_header: 传输层头部。
network_header: 网络层头部。
mac_header: 链路层头部。
cb: 用于控制缓冲区。每个层都可以使用此指针,将私有的数据放置于此。
len: 有效数据长度。
data_len: 数据长度。
mac_len: 链路层头部长度,对于以太网,指mac地址所用的长度,为6.
hdr_len: skb的可写头部长度。
csum: 校验和(包含开始和偏移)。
csum_start: 当开始计算校验和时从skb->head的偏移。
csum_offset: 从csum_start开始的偏移。
local_df: 允许本地分片。
pkt_type: 包的类别。
priority: 包队列的优先级。
truesize: 报文缓冲区的大小。
head: 报文缓冲区的头。
data: 数据的头指针。
tail: 数据的尾指针。
end: 报文缓冲区的尾部。
2. sk_buff的含义
下图是sk_buff的框图,其中tail、end、head和data是对网络报文部分的描述。
图的左边表示sk_buff结构,右侧标识网络包的存储区。可以看到,sk_buff中有四个指针指向存储区。其中head一定指向存储区的开头,end一定指向存储区的结尾。 data指向实际内容的开头,tail指向实际内容的结尾。这样做有两个原因,一时在分配空间的时候我们尚不知道具体需要多大的空间,只能按照最大可能空间来分配;二是为了满足字节对齐的需要。
图中data部分的内容包括网络包的所有内容。对于输入包而言,其每向下传输一层,都会添加一层头,所以sk_buff的data指针也会不断减小。
对于输入包而言,存储区的内容是不变的。但我们在分析包时,在每层都会剥除该层的头。为了保留我们剥除的结果,结构中会有三个指针分别指向以太、网络和传输三层的协议头。这三个指针依次分别是union{...}mac, union{...}nh和union{...}h。三个成员都是联合,其中的内容是该层可能的协议类型的指针。
还有一组成员标识存储区中的数据的长度。len表示存储区的数据长度和分片长度之和。data_len标识分片长度。mac_len表示mac头的长度。truesize表示存储区总长度(即end-head)和sk_buff本身长度之和。
下面的内容是关于如何维护存储区。
alloc_skb和dev_alloc_skb用来分配sk_buff和存储区,kfree_skb和dev_kree_skb则释放sk_buff和其对应的存储区。刚初始化完的存储区,head,data,tail都指向存储区的开头,end指向结尾。下面这些函数是修改data和tail指针的指向。
skb_reserve是在存储区的开头保留一段空间。其有两个作用,对于接收到的报文而言,可以保持字节对齐;对于发送的报文而言,可以保留空间用于存放各层的协议头。毕竟我们在高层协议就会分配该存储区,所以需要预留下层协议头的空间。该函数的作用会导致data和tail两个指针都下移,在head和它们之间留出空间。
skb_put则是将tail下移,即增加了真正空间的长度。
skb_push是将data下移,也增加了真正空间的长度。
skb_pull将data下移,减少了真正空间的长度。
网络报文存储空间实在应用层发送网络数据,或者网络设备收到网络数据时动态分配的,分配成功之后,将接收或者发送的网络数据填充到这个存储空间中去。将网络数据填充到存储空间时,在存储空间的头部预留了一定数量的空隙,然后从此偏移量开始将网络报文复制到存储空间中。
结构sk_buff以sk_buff_head构成一个环状的链,如下图所示,next变量指向下一个sk_buff结构,prev变量指向前一个sk_buff结构,内核程序通过访问其中的各个单元来遍历整个协议栈中的网络数据。