sk_buff 剖析 (一)

来源:互联网 发布:见缝插针安卓源码 编辑:程序博客网 时间:2024/06/05 14:23

1 sk_buff介绍

sk_buff(socket buffer)结构是linux网络代码中重要的数据结构,它管理和控制接收或发送数据包的信息。

2 sk_buff组成

Packet data:通过网卡收发的报文,包括链路层、网络层、传输层的协议头和携带的应用数据,包括head room,data,tail room三部分。

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

Data buffer:用于存储packet data的缓冲区,分为以上两部分。

Sk_buff:缓冲区控制结构sk_buff。

整个sk_buff结构图如图。


图 sk_buff结构图

3 struct sk_buff 结构体

<span style="font-family:Times New Roman;">/* struct sk_buff - socket buffer */  struct sk_buff {/* These two members must be first. */struct sk_buff*next;struct sk_buff*prev;struct sock*sk;struct skb_timevaltstamp; // 记录接收或发送报文的时间戳struct net_device*dev; //通过该设备接收或发送,记录网络接口的信息和完成操作struct net_device*input_dev; //接收数据的网络设备struct net_device*curlayer_input_dev;struct net_device*l2tp_input_dev;union {struct tcphdr*th;struct udphdr*uh;struct icmphdr*icmph;struct igmphdr*igmph;struct iphdr*ipiph;struct ipv6hdr*ipv6h;//?unsigned char*raw;} h; //传输层报头union {struct iphdr*iph;struct ipv6hdr*ipv6h;struct arphdr*arph;unsigned char*raw;} nh; //网络层报头union {unsigned char *raw;} mac; //链路层报头...unsigned intlen, //len缓冲区中数据部分的长度。data_len, // data_len只计算分片中数据的长度mac_len, //mac头的长度csum; //校验和__u32priority;__u8local_df:1,cloned:1, //表示该结构是另一个sk_buff克隆的ip_summed:2,nohdr:1,nfctinfo:3;__u8pkt_type:3,fclone:2,ipvs_property:1;__be16protocol;__u32 flag; /*packet flags*/.../* These elements must be at the end, see alloc_skb() for details.  */unsigned inttruesize; //这是缓冲区的总长度,包括sk_buff结构和数据部分atomic_tusers;unsigned char*head, //指向缓冲区的头部*data,// 指向实际数据的头部*tail, //指向实际数据的尾部*end;//指向缓冲区的尾部};</span>


4 sk_buff成员变量

Sk_buff成员变量主要包括以下3类

1 Layout布局

2General通用

3 Feature-specific功能相关

4.1Layout布局

1 struct sk_buff *next, struct sk_buff *prev

有些sk_buff成员变量的作用是方便查找,或者是连接数据结构本身. 内核可以把sk_buff组织成一个双向链表。当然,这个链表的结构要比常见的双向链表的结构复杂一点。就像任何一个双向链表一样,sk_buff中有两个指针next和prev,其中,next指向下一个节点,而prev指向上一个节点。但是,这个链表还有另一个需求:每个sk_buff结构都必须能够很快找到链表头节点。为了满足这个需求,在第一个节点前面会插入另一个结构sk_buff_head,这是一个辅助节点,它的定义如下

struct sk_buff_head {        /* These two members must be first. */        struct sk_buff  *next;        struct sk_buff  *prev;        __u32           qlen;//sk_buff结构的数量        spinlock_t      lock;//自旋锁};

sk_buff和sk_buff_head的前两个元素是一样的:next和prev指针。这使得它们可以放到同一个链表中,尽管sk_buff_head要比sk_buff小得多。另外,相同的函数可以同样应用于sk_buff和sk_buff_head。



2 struct sock *sk

这是一个指向拥有这个sk_buff的sock结构的指针。这个指针在网络包由本机发出或者由本机进程接收时有效,因为插口相关的信息被L4(TCP或UDP)或者用户空间程序使用。如果sk_buff只在转发中使用(这意味着,源地址和目的地址都不是本机地址),这个指针是NULL。

3 unsigned int len

这是缓冲区中数据部分的长度。它包括主缓冲区中的数据长度(data指针指向它)和分片中的数据长度。它的值在缓冲区从一个层向另一个层传递时改变,因为往上层传递,旧的头部就没有用了,而往下层传递,需要添加本层的头部。len同样包含了协议头的长度。

4 unsigned int data_len

和len不同,data_len只计算分片中数据的长度。

5 unsigned int mac_len

这是mac头的长度。

6 atomic_t users

这是一个引用计数,用于计算有多少实体引用了这个sk_buff缓冲区。它的主要用途是防止释放sk_buff后,还有其他实体引用这个sk_buff。因此,每个引用这个缓冲区的实体都必须在适当的时候增加或减小这个变量。这个计数器只保护sk_buff结构本身,而缓冲区的数据部分由类似的计数器(dataref)来保护。有时可以用atomic_inc和atomic_dec函数来直接增加或减小users,但是,通常还是使用函数skb_get和kfree_skb来操作这个变量。

7 unsigned int truesize

这是缓冲区的总长度,包括sk_buff结构和数据部分。如果申请一个len字节的缓冲区,alloc_skb函数会把它初始化成len+sizeof(sk_buff)。

8 unsigned char *head ,*end, *data, *tail

它们表示缓冲区和数据部分的边界。在每一层申请缓冲区时,它会分配比协议头或协议数据大的空间。head和end指向缓冲区的头部和尾部,而data和tail指向实际数据的头部和尾部,参见下图。每一层会在head和data之间填充协议头,或者在tail和end之间添加新的协议数据。图3中右边数据部分会在尾部包含一个附加的头部。



9 void(*destructor)(...)

这个函数指针可以初始化成一个在缓冲区释放时完成某些动作的函数。如果缓冲区不属于一个socket,这个函数指针通常是不会被赋值的。如果缓冲区属于一个socket,这个函数指针会被赋值为sock_rfree或sock_wfree(分别由skb_set_owner_r或skb_set_owner_w函数初始化)。这两个sock_xxx函数用于更新socket队列中的内存容量。

4.2 General通用

本节描述sk_buff的主要成员变量,这些成员变量与特定的内核功能无关。

1 struct timeval tstamp

这个变量只对接收到的包有意义。它代表包接收时的时间戳,或者有时代表包准备发出时的时间戳。它在netif_rx里面由函数net_timestamp设置,而netif_rx是设备驱动收到一个包后调用的函数。

2  struct net_device *dev

这个变量的类型是net_device,net_device它代表一个网络设备。dev的作用与这个包是准备发出的包,还是刚接收的包有关。当收到一个包时,设备驱动会把sk_buff的dev指针指向收到这个包的设备的数据结构

当一个包被发送时,这个变量代表将要发送这个包的设备。在发送网络包时设置这个值的代码要比接收网络包时设置这个值的代码复杂。有些网络功能可以把多个网络设备组成一个虚拟的网络设备(也就是说,这些设备没有和物理设备直接关联),并由一个虚拟网络设备驱动管理。当虚拟设备被使用时,dev指针指向虚拟设备的net_device结构。而虚拟设备驱动会在一组设备中选择一个设备并把dev指针修改为这个设备的net_device结构。因此,在某些情况下,指向传输设备的指针会在包处理过程中被改变。

3 struct net_device *input_dev

这是收到包的网络设备的指针。如果包是本地生成的,这个值为NULL。对以太网设备来说,这个值由eth_type_trans初始化,它主要被流量控制代码使用。

4 struct net_device *real_dev

这个变量只对虚拟设备有意义,它代表与虚拟设备关联的真实设备。例如,Bonding和VLAN设备都使用它来指向收到包的真实设备。

5 union {...} h  union {...} nh  union {...} mac

这些是指向TCP/IP各层协议头的指针:h指向L4,nh指向L3,mac指向L2。每个指针的类型都是一个联合,包含多个数据结构,每一个数据结构都表示内核在这一层可以解析的协议。例如,h是一个包含内核所能解析的L4协议的数据结构的联合。每一个联合都有一个raw变量用于初始化,后续的访问都是通过协议相关的变量进行的。

当接收一个包时,处理n层协议头的函数从n-1层收到一个缓冲区,它的skb->data指向n层协议的头。处理n层协议的函数把本层的指针(例如,L3对应的是skb->nh指针)初始化为skb->data,因为这个指针的值会在处理下一层协议时改变(skb->data将被初始化成缓冲区里的其他地址)。在处理n层协议的函数结束时,在把包传递给n+1层的处理函数前,它会把skb->data指针指向n层协议头的末尾,这正好是n+1层协议的协议头。

发送包的过程与此相反,但是由于要为每一层添加新的协议头,这个过程要比接收包的过程复杂。


6 struct dst_entry dst 

这个变量在路由子系统中使用。

7 char cb[40]

这是一个控制缓存,或者说是一个私有信息的存储空间,由每一层自己维护并使用。它在分配sk_buff结构时分配(它目前的大小是40字节,已经足够为每一层存储必要的私有信息了)。在每一层中,访问这个变量的代码通常用宏实现,以增强代码的可读性。例如,TCP用这个变量存储tcp_skb_cb结构,这个结构在include/net/tcp.h中定义:

struct tcp_skb_cb {        union {                struct inet_skb_parm    h4;#if defined(CONFIG_IPV6) || defined (CONFIG_IPV6_MODULE)                struct inet6_skb_parm   h6;#endif        } header;       /* For incoming frames          */        __u32           seq;            /* Starting sequence number     */        __u32           end_seq;        /* SEQ + FIN + SYN + datalen    */        __u32           when;           /* used to compute rtt's        */        __u8            flags;          /* TCP header flags.            */... 

8 unsigned int csum  unsigned charip_summed

表示校验和以及相关状态标记。

9 unsigned char cloned

一个布尔标记,当被设置时,表示这个结构是另一个sk_buff的克隆。

10 unsigned char pkt_type

这个变量表示帧的类型,分类是由L2的目的地址来决定的。可能的取值都在include/linux/if_packet.h中定义。对以太网设备来说,这个变量由eth_type_trans函数初始化。

11 __u32 priority

这个变量描述发送或转发包的QoS类别。如果包是本地生成的,socket层会设置priority变量。如果包是将要被转发的,rt_tos2priority函数会根据ip头中的Tos域来计算赋给这个变量的值。这个变量的值与DSCP(DiffServ CodePoint)没有任何关系。

12 unsigned short protocol

这个变量是高层协议从二层设备的角度所看到的协议。典型的协议包括IP,IPV6和ARP。完整的列表在 include/linux/if_ether.h中。由于每个协议都有自己的协议处理函数来处理接收到的包,因此,这个域被设备驱动用于通知上层调用哪个协议处理函数。每个网络驱动都调用netif_rx来通知上层网络协议的协议处理函数,因此protocol变量必须在这些协议处理函数调用之前初始化。

13 unsigned short security

这是包的安全级别。这个变量最初由IPSec子系统使用,但现在已经作废了。

4.3Feature-specific功能相关

linux内核是模块化的,你可以选择包含或者删除某些功能。因此,sk_buff结构里面的一些成员变量只有在内核选择支持某些功能时才有效,比如防火墙(netfilter)或者qos:

1 unsignedlong nfmark  __u32 nfcache  __u32 nfctinfo  struct nf_conntrack *nfct

unsignedint nfdebug   struct nf_bridge_info*nf_bridge

这些变量被netfilter使用(防火墙代码),内核编译选项是“DeviceDrivers->Networking support-> Networking options-> Network packetfiltering”和两个子选项“Networkpacket filtering debugging”和“Bridged IP/ARP packets filtering”

2 union{...} private

这个联合结构被高性能并行接口(HIPPI)使用。相应的内核编译选项是“Device->Drivers ->Networking support ->Network devicesupport ->HIPPI driver support”

3 __u32tc_index  __u32 tc_verd  __u32 tc_classid

这些变量被流量控制代码使用,内核编译选项是“Device Drivers ->Networking->support ->Networking options->QoS and/or fair queueing”和它的子选项“Packetclassifier API”

4 structsec_path *sp

这个变量被IPSec协议用于跟踪传输的信息。






0 0