struct sk_buff是linux网络系统中的核心结构体,linux网络中的所有数据包的封装以及解封装都是在这个结构体的基础上进行。

sk_buff是Linux网络中最核心的结构体,它用来管理和控制接收或发送数据包的信息。各层协议都依赖于sk_buff而存在。

内核中sk_buff结构体在各层协议之间传输不是用拷贝sk_buff结构体,而是通过增加协议头和移动指针来操作的。

如果是从L4传输到L2,则是通过往sk_buff结构体中增加该层协议头来操作;

如果是从L4到L2,则是通过移动sk_buff结构体中的data指针来实现,不会删除各层协议头。

这样做是为了提高CPU的工作效率。

1 struct  sk_buff_head 
 2 {
 3      struct  sk_buff *next;
 4      struct  sk_buff *prev;
 5      
 6      __u32 qlen;
 7      spinlock_t lock;
 8 }
 9  
10  
11 struct  sk_buff
12 {
13      struct  sk_buff *next;
14      struct  sk_buff *prev;
15      struct  sock *sock ;       //struct sock是socket在网络层的表示,其中存放了网络层的信息
16      
17      unsigned  int  len;        //下面有介绍
18      unsigned  int  data_len;      //下面有介绍
19      __u16   mac_len ;          //数路链路层的头长度
20      __u16   hdr_len ;          //writable header length of cloned skb
21      unsigned  int  truesize ;      //socket buffer(套接字缓存区的大小)
22      atomic_t users ;            //对当前的struct sk_buff结构体的引用次数;
23      __u32   priority ;            //这个struct sk_buff结构体的优先级
24      
25      sk_buff_data_t transport_header ;  //传输层头部的偏移量
26      sk_buff_data_t network_header ;    //网络层头部的偏移量
27      sk_buff_data_t mac_header ;        //数据链路层头部的偏移量
28      
29      char  *data ;                //socket buffer中数据的起始位置;
30      sk_buff_data_t tail ;            //socket buffer中数据的结束位置;
31      char  *head ;                 //socket buffer缓存区的起始位置;
32      sk_buffer_data_t end ;            //socket buffer缓存区的终止位置;
33      
34      struct  net_device *dev;          //将要发送struct sk_buff结构体的网络设备或struct sk_buff的接收
35                                       //网络设备
36      int  iif;                   //网络设备的接口索引号;
37      
38      
39      struct  timeval tstamp ;          //用于存放接受的数据包的到达时间;
40      
41      __u8  local_df : 1 ,           //allow local fragmentaion;
42            cloned   : 1 ,          // head may be cloned
43            ;
44      
45      __u8  pkt_type : 3 ,            //数据包的类型;
46            fclone   : 2,             // struct sk_buff clone status
47      
48 }

 

1 /*  include/linux/skbuff.h */
  2 struct sk_buff {
  3     union {
  4         struct {
  5             /* These two members must be first. 
  6 这两个域是用来连接相关的skb的(如果有分片的话,可以通过它们将分片链接到一起),sk_buff是双链表结构。
  7               */
  8             struct sk_buff      *next;  /*链表中的下一个skb*/
  9             struct sk_buff      *prev; /*链表中的上一个skb*/
 10 
 11             union {
 12                 ktime_t     tstamp; /*记录接受或者传输报文的时间戳*/
 13                 struct skb_mstamp skb_mstamp;
 14             };
 15         };
 16         struct rb_node      rbnode; /* 红黑树,used in netem, ip4 defrag, and tcp stack */
 17     };
 18 
 19     union {
 20         struct sock     *sk; /*指向报文所属的套接字指针*/
 21         int         ip_defrag_offset;
 22     };
 23 
 24     struct net_device   *dev; /*记录接受或发送报文的网络设备*/
 25 
 26     /*
 27      * This is the control buffer. It is free to use for every
 28      * layer. Please put your private variables there. If you
 29      * want to keep them across layers you have to do a skb_clone()
 30      * first. This is owned by whoever has the skb queued ATM.
 31      */
 32     char            cb[48] __aligned(8); /*保存与协议相关的控制信息,每个协议可能独立使用这些信息*/
 33 
 34     unsigned long       _skb_refdst; /*主要用于路由子系统,保存路由相关的东西*/
 35     void            (*destructor)(struct sk_buff *skb);
 36 #ifdef CONFIG_XFRM
 37     struct  sec_path    *sp;
 38 #endif
 39 #if defined(CONFIG_NF_CONNTRACK) || defined(CONFIG_NF_CONNTRACK_MODULE)
 40     struct nf_conntrack *nfct;
 41 #endif
 42 #if IS_ENABLED(CONFIG_BRIDGE_NETFILTER)
 43     struct nf_bridge_info   *nf_bridge;
 44 #endif
 45     unsigned int        len,   
 46 /*整个数据区域的长度,
 47 这里的len = length(实际线性数据,不包括头空间和尾空间) + length(非线性数据)
 48 len = (tail - data) + data_len
 49 这个len中数据区长度是个有效长度,因为不删除协议头,
 50 所以只计算有效协议头和包内容。如:当在L3时,不会计算L2的协议头长度。*/
 51                 data_len; /*非线性数据,length(实际线性数据 = skb->len - skb->data_len)*/
 52     __u16           mac_len, /*mac层报头的长度*/
 53                 hdr_len; /*用于clone时,表示clone的skb的头长度*/
 54 
 55     /* Following fields are _not_ copied in __copy_skb_header()
 56      * Note that queue_mapping is here mostly to fill a hole.
 57      */
 58     kmemcheck_bitfield_begin(flags1);
 59     __u16           queue_mapping;
 60 
 61 /* if you move cloned around you also must adapt those constants */
 62 #ifdef __BIG_ENDIAN_BITFIELD
 63 #define CLONED_MASK (1 << 7)
 64 #else
 65 #define CLONED_MASK 1
 66 #endif
 67 #define CLONED_OFFSET()     offsetof(struct sk_buff, __cloned_offset)
 68 
 69     __u8            __cloned_offset[0];
 70     __u8            cloned:1,
 71                 nohdr:1,
 72                 fclone:2,
 73                 peeked:1,
 74                 head_frag:1,
 75                 xmit_more:1,
 76                 pfmemalloc:1;
 77     kmemcheck_bitfield_end(flags1);
 78 
 79     /* fields enclosed in headers_start/headers_end are copied
 80      * using a single memcpy() in __copy_skb_header()
 81      */
 82     /* private: */
 83     __u32           headers_start[0];
 84     /* public: */
 85 
 86 /* if you move pkt_type around you also must adapt those constants */
 87 #ifdef __BIG_ENDIAN_BITFIELD
 88 #define PKT_TYPE_MAX    (7 << 5)
 89 #else
 90 #define PKT_TYPE_MAX    7
 91 #endif
 92 #define PKT_TYPE_OFFSET()   offsetof(struct sk_buff, __pkt_type_offset)
 93 
 94     __u8            __pkt_type_offset[0];
 95     __u8            pkt_type:3; /*标记帧的类型*/
 96     __u8            ignore_df:1;
 97     __u8            nfctinfo:3;
 98     __u8            nf_trace:1;
 99 
100     __u8            ip_summed:2;
101     __u8            ooo_okay:1;
102     __u8            l4_hash:1;
103     __u8            sw_hash:1;
104     __u8            wifi_acked_valid:1;
105     __u8            wifi_acked:1;
106     __u8            no_fcs:1;
107 
108     /* Indicates the inner headers are valid in the skbuff. */
109     __u8            encapsulation:1;
110     __u8            encap_hdr_csum:1;
111     __u8            csum_valid:1;
112     __u8            csum_complete_sw:1;
113     __u8            csum_level:2;
114     __u8            csum_bad:1;
115 #ifdef CONFIG_IPV6_NDISC_NODETYPE
116     __u8            ndisc_nodetype:2;
117 #endif
118     __u8            ipvs_property:1;
119 
120     __u8            inner_protocol_type:1;
121     __u8            remcsum_offload:1;
122 #ifdef CONFIG_NET_SWITCHDEV
123     __u8            offload_fwd_mark:1;
124 #endif
125     /* 2, 4 or 5 bit hole */
126 
127 #ifdef CONFIG_NET_SCHED
128     __u16           tc_index;   /* traffic control index */
129 #ifdef CONFIG_NET_CLS_ACT
130     __u16           tc_verd;    /* traffic control verdict */
131 #endif
132 #endif
133 
134     union {
135         __wsum      csum;
136         struct {
137             __u16   csum_start;
138             __u16   csum_offset;
139         };
140     };
141     __u32           priority; /*优先级,主要用于QOS*/
142     int         skb_iif; /*接收设备的index*/
143     __u32           hash;
144     __be16          vlan_proto;
145     __u16           vlan_tci;
146 #if defined(CONFIG_NET_RX_BUSY_POLL) || defined(CONFIG_XPS)
147     union {
148         unsigned int    napi_id;
149         unsigned int    sender_cpu;
150     };
151 #endif
152 #ifdef CONFIG_NETWORK_SECMARK
153     __u32       secmark;
154 #endif
155 
156     union {
157         __u32       mark;
158         __u32       reserved_tailroom;
159     };
160 
161     union {
162         __be16      inner_protocol;
163         __u8        inner_ipproto;
164     };
165 
166     __u16           inner_transport_header; 
167     __u16           inner_network_header; 
168     __u16           inner_mac_header; 
169 
170     __be16          protocol;       /*协议类型*/
171     __u16           transport_header;    /*传输层头部的偏移量*/
172     __u16           network_header;    /*网络层头部的偏移量*/
173     __u16           mac_header;      /*数据链路层头部的偏移量*/
174 
175     /* private: */
176     __u32           headers_end[0];
177     /* public: */
178 
179     /* These elements must be at the end, see alloc_skb() for details.  */
180     sk_buff_data_t      tail;     /*指向数据区中实际数据结束的位置*/
181     sk_buff_data_t      end;     /*指向数据区中结束的位置(非实际数据区域结束位置)*/
182      
183     unsigned char       *head,     /* 指向数据区中开始的位置(非实际数据区域开始位置)*/
184                 *data;         /*指向数据区中实际数据开始的位置*/
185     unsigned int        truesize; 
186     atomic_t        users;         /*缓冲区总长度*/
187 };

 struct sk_buff结构体中的pkt_type字段的取值为:

sembuf结构体 skb结构体_数据区

 

 

 通过struct sk_buff中的pkt_type字段中的值,可以判断出接收到的数据包是不是发送给本机的数据包。

   如果pkt_type == PACKET_HOST,说明收到的数据包是发送给本机的单播数据包

   如果pkt_type == PACKET_BROADCAST,说明收到的数据包是发送给本机的广播数据包

   如果pkt_type == PACKET_MULTICAST,说明收到的数据包是发送给本机的组播数据包

   如果pkt_type == PACKET_OTHERHOST,说明收到的数据包不是发送给本机的数据包,需要转发出去。

 

 

1.1、重要的长度len的解析

(1)、线性数据:head - end

(2)、实际线性数据:data - tail,不包含线性数据中的头空间和尾空间。

skb->data_len: skb中的分片数据(非线性数据)的长度。
skb->len: skb中的数据块的总长度,数据块包括实际线性数据和非线性数据,非线性数据为data_len,所以skb->len= (data - tail) + data_len。
skb->truesize: skb的总长度,包括sk_buff结构和数据部分,skb=sk_buff控制信息 + 线性数据(包括头空间和尾空间) + skb_shared_info控制信息 + 非线性数据,

所以:skb->truesize = sizeof(struct sk_buff) + (head - end) + sizeof(struct skb_shared_info) + data_len。

1.2、sk_buff数据区

sk_buff结构体中的都是sk_buff的控制信息,是网络数据包的一些配置,真正储存数据的是sk_buff结构体中几个指针指向的数据区中,

线性数据区的大小 = (skb->end - skb->head),对于每个数据包来说这个大小都是固定不变的,

在传输过程中skb->end和skb->head所指向的地址都是不变的,这里要注意这个地址不是本机的地址,

如果是本机的地址那么数据包传到其他主机上这个地址就是无效的,所以这个地址是这个skb缓冲区的相对地址。

线性数据区是用来存放各层协议头部和应用层发下来的数据。各层协议头部相关信息放在线性数据区中。

实际数据指针为data和tail,data指向实际数据开始的地方,tail指向实际数据结束的地方。

用一张图来表示sk_buff和数据区的关系:

sembuf结构体 skb结构体_数据区_02

 

 1.2、线性数据区

这一节介绍先行数据区在sk_buff创建过程中的变化,图中暂时省略了非线性数据区:

(1)、sk_buff结构数据区刚被申请好,此时 head 指针、data 指针、tail 指针都是指向同一个地方。记住前面讲过的:head 指针和 end 指针指向的位置一直都不变,而对于数据的变化和协议信息的添加都是通过 data 指针和 tail 指针的改变来表现的。

sembuf结构体 skb结构体_#endif_03

 

         初始化sk_buff

(2)、开始准备存储应用层下发过来的数据,通过调用函数 skb_reserve(m) 来使 data 指针和 tail 指针同时向下移动,空出一部分空间来为后期添加协议信息。m一般为最大协议头长度,内核中定义。

sembuf结构体 skb结构体_sembuf结构体_04

 

       初始定位skb_reserve(m)

(3)、开始存储数据了,通过调用函数 skb_put() 来使 tail 指针向下移动空出空间来添加数据,此时 skb->data 和 skb->tail 之间存放的都是数据信息,无协议信息。

sembuf结构体 skb结构体_数据区_05

 

       储存应用层数据skb_put()

(4)、这时就开始调用函数 skb_push() 来使 data 指针向上移动,空出空间来添加各层协议信息,添加协议信息也是用skb_put()。直到最后到达二层,添加完帧头然后就开始发包了。

sembuf结构体 skb结构体_数据区_06

 

  添加协议头

 1.3、非线性数据区

1.2中所讲的都是线性数据区中的相关的配置,当线性数据区不够用的时候就会启用非线性数据区作为数据区域的扩展,skb中用skb_shared_info分片结构体来配置非线性数据。

skb_shared_info结构体是和skb中的线性数据区一体的,所以在skb的各种操作时都会把这两个结构看作是一个结构来操作。如:

(1)、当sk_buff结构的线性数据区申请和释放空间时,分片结构会跟着数据区一起分配和释放。

(2)、克隆skb时,sk_buff的线性数据区和分片结构都由分片结构中的dataref成员字段来标识是否被引用。

sembuf结构体 skb结构体_数据区_07

 

       sk_buff与数据区的关系

从上图中可以看出来非线性数据区接到skb->end的位置后,skb->end的下一个字节就作为非线性数据区的开始。

end指针的下个字节可以作为分片结构的开始,获取end指针的位置要强行转成分片结构,内核中有定义好的宏:

1 #define skb_shinfo(SKB) ((struct skb_shared_info *)(skb_end_pointer(SKB)))

skb_shared_info结构:

1 /*  include/linux/skbuff.h */
 2 struct skb_shared_info {
 3     unsigned char   nr_frags; /*表示有多少分片结构*/
 4     __u8        tx_flags;
 5     unsigned short  gso_size;
 6     /* Warning: this field is not always filled in (UFO)! */
 7     unsigned short  gso_segs;
 8     unsigned short  gso_type;
 9     struct sk_buff  *frag_list; /*一种类型的分配数据*/
10     struct skb_shared_hwtstamps hwtstamps;
11     u32     tskey;
12     __be32          ip6_frag_id;
13 
14     /*
15      * Warning : all fields before dataref are cleared in __alloc_skb()
16      */
17     atomic_t    dataref; /*用于引用计数,克隆一个skb结构体时会增加一个引用计数*/
18 
19     /* Intermediate layers must ensure that destructor_arg
20      * remains valid until skb destructor */
21     void *      destructor_arg;
22 
23     /* must be last field, see pskb_expand_head() */
24     skb_frag_t  frags[MAX_SKB_FRAGS];  /*保存分页数据,skb->data_len = 所有数组数据长度之和*/
25 };

非线性数据区有两种不同的构成数据的方式
(1)用数组存储的分片数据区,采用是是结构体中的frags[MAX_SKB_FRAGS]
对于frags[]一般用在当数据比较多,在线性数据区装不下的时候,skb_frag_t中是一页一页的数据,skb_frag_struct结构体如下:

1 /*  include/linux/skbuff.h */
 2 typedef struct skb_frag_struct skb_frag_t;
 3 
 4 struct skb_frag_struct {
 5     struct {
 6         struct page *p; /*指向分片数据区的指针,类似于sk_buff中的data指针*/
 7     } page;
 8 #if (BITS_PER_LONG > 32) || (PAGE_SIZE >= 65536)
 9     __u32 page_offset;
10     __u32 size;
11 #else
12     __u16 page_offset; /*偏移量,表示相对开始位置的页偏移量*/
13     __u16 size; /*page中的数据长度*/
14 #endif
15 };

下图显示了frags是怎么分配分片数据的:

sembuf结构体 skb结构体_sembuf结构体_08

 

       数组存储分片指针类型

sembuf结构体 skb结构体_sembuf结构体_09

 

     rag_list指针来指向的分片数据类型