一、sk_buff结构体
sk_buff是Linux网络中最核心的结构体,它用来管理和控制接收或发送数据包的信息。各层协议都依赖于sk_buff而存在。内核中sk_buff结构体在各层协议之间传输不是用拷贝sk_buff结构体,而是通过增加协议头和移动指针来操作的。如果是从L4传输到L2,则是通过往sk_buff结构体中增加该层协议头来操作;如果是从L4到L2,则是通过移动sk_buff结构体中的data指针来实现,不会删除各层协议头。这样做是为了提高CPU的工作效率。
1.1 sk_buff在内核中的结构
skb_buff结构如下所示:
/* include/linux/skbuff.h */
struct sk_buff {
union {
struct {
/* These two members must be first.
这两个域是用来连接相关的skb的(如果有分片的话,可以通过它们将分片链接到一起),sk_buff是双链表结构。
*/
struct sk_buff *next; /*链表中的下一个skb*/
struct sk_buff *prev; /*链表中的上一个skb*/
union {
ktime_t tstamp; /*记录接受或者传输报文的时间戳*/
struct skb_mstamp skb_mstamp;
};
};
struct rb_node rbnode; /* 红黑树,used in netem, ip4 defrag, and tcp stack */
};
union {
struct sock *sk; /*指向报文所属的套接字指针*/
int ip_defrag_offset;
};
struct net_device *dev; /*记录接受或发送报文的网络设备*/
/*
* This is the control buffer. It is free to use for every
* layer. Please put your private variables there. If you
* want to keep them across layers you have to do a skb_clone()
* first. This is owned by whoever has the skb queued ATM.
*/
char cb[48] __aligned(8); /*保存与协议相关的控制信息,每个协议可能独立使用这些信息*/
unsigned long _skb_refdst; /*主要用于路由子系统,保存路由相关的东西*/
void (*destructor)(struct sk_buff *skb);
#ifdef CONFIG_XFRM
struct sec_path *sp;
#endif
#if defined(CONFIG_NF_CONNTRACK) || defined(CONFIG_NF_CONNTRACK_MODULE)
struct nf_conntrack *nfct;
#endif
#if IS_ENABLED(CONFIG_BRIDGE_NETFILTER)
struct nf_bridge_info *nf_bridge;
#endif
unsigned int len,
/*整个数据区域的长度,
这里的len = length(实际线性数据,不包括头空间和尾空间) + length(非线性数据)
len = (tail - data) + data_len
这个len中数据区长度是个有效长度,因为不删除协议头,
所以只计算有效协议头和包内容。如:当在L3时,不会计算L2的协议头长度。*/
data_len; /*非线性数据,length(实际线性数据 = skb->len - skb->data_len)*/
__u16 mac_len, /*mac层报头的长度*/
hdr_len; /*用于clone时,表示clone的skb的头长度*/
/* Following fields are _not_ copied in __copy_skb_header()
* Note that queue_mapping is here mostly to fill a hole.
*/
kmemcheck_bitfield_begin(flags1);
__u16 queue_mapping;
/* if you move cloned around you also must adapt those constants */
#ifdef __BIG_ENDIAN_BITFIELD
#define CLONED_MASK (1 << 7)
#else
#define CLONED_MASK 1
#endif
#define CLONED_OFFSET() offsetof(struct sk_buff, __cloned_offset)
__u8 __cloned_offset[0];
__u8 cloned:1,
nohdr:1,
fclone:2,
peeked:1,
head_frag:1,
xmit_more:1,
pfmemalloc:1;
kmemcheck_bitfield_end(flags1);
/* fields enclosed in headers_start/headers_end are copied
* using a single memcpy() in __copy_skb_header()
*/
/* private: */
__u32 headers_start[0];
/* public: */
/* if you move pkt_type around you also must adapt those constants */
#ifdef __BIG_ENDIAN_BITFIELD
#define PKT_TYPE_MAX (7 << 5)
#else
#define PKT_TYPE_MAX 7
#endif
#define PKT_TYPE_OFFSET() offsetof(struct sk_buff, __pkt_type_offset)
__u8 __pkt_type_offset[0];
__u8 pkt_type:3; /*标记帧的类型*/
__u8 ignore_df:1;
__u8 nfctinfo:3;
__u8 nf_trace:1;
__u8 ip_summed:2;
__u8 ooo_okay:1;
__u8 l4_hash:1;
__u8 sw_hash:1;
__u8 wifi_acked_valid:1;
__u8 wifi_acked:1;
__u8 no_fcs:1;
/* Indicates the inner headers are valid in the skbuff. */
__u8 encapsulation:1;
__u8 encap_hdr_csum:1;
__u8 csum_valid:1;
__u8 csum_complete_sw:1;
__u8 csum_level:2;
__u8 csum_bad:1;
#ifdef CONFIG_IPV6_NDISC_NODETYPE
__u8 ndisc_nodetype:2;
#endif
__u8 ipvs_property:1;
__u8 inner_protocol_type:1;
__u8 remcsum_offload:1;
#ifdef CONFIG_NET_SWITCHDEV
__u8 offload_fwd_mark:1;
#endif
/* 2, 4 or 5 bit hole */
#ifdef CONFIG_NET_SCHED
__u16 tc_index; /* traffic control index */
#ifdef CONFIG_NET_CLS_ACT
__u16 tc_verd; /* traffic control verdict */
#endif
#endif
union {
__wsum csum;
struct {
__u16 csum_start;
__u16 csum_offset;
};
};
__u32 priority; /*优先级,主要用于QOS*/
int skb_iif; /*接收设备的index*/
__u32 hash;
__be16 vlan_proto;
__u16 vlan_tci;
#if defined(CONFIG_NET_RX_BUSY_POLL) || defined(CONFIG_XPS)
union {
unsigned int napi_id;
unsigned int sender_cpu;
};
#endif
#ifdef CONFIG_NETWORK_SECMARK
__u32 secmark;
#endif
union {
__u32 mark;
__u32 reserved_tailroom;
};
union {
__be16 inner_protocol;
__u8 inner_ipproto;
};
__u16 inner_transport_header;
__u16 inner_network_header;
__u16 inner_mac_header;
__be16 protocol; /*协议类型*/
__u16 transport_header;
__u16 network_header;
__u16 mac_header;
/* private: */
__u32 headers_end[0];
/* public: */
/* These elements must be at the end, see alloc_skb() for details. */
sk_buff_data_t tail; /*指向数据区中实际数据结束的位置*/
sk_buff_data_t end; /*指向数据区中结束的位置(非实际数据区域结束位置)*/
unsigned char *head, /* 指向数据区中开始的位置(非实际数据区域开始位置)*/
*data; /*指向数据区中实际数据开始的位置*/
unsigned int truesize;
atomic_t users; /*缓冲区总长度*/
};
1.2 重要的长度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。
二、sk_buff数据区
sk_buff结构体中的都是sk_buff的控制信息,是网络数据包的一些配置,真正储存数据的是sk_buff结构体中几个指针指向的数据区中,线性数据区的大小 = (skb->end - skb->head),对于每个数据包来说这个大小都是固定不变的,在传输过程中skb->end和skb->head所指向的地址都是不变的,这里要注意这个地址不是本机的地址,如果是本机的地址那么数据包传到其他主机上这个地址就是无效的,所以这个地址是这个skb缓冲区的相对地址。
线性数据区是用来存放各层协议头部和应用层发下来的数据。各层协议头部相关信息放在线性数据区中。实际数据指针为data和tail,data指向实际数据开始的地方,tail指向实际数据结束的地方。
用一张图来表示sk_buff和数据区的关系:
2.1 线性数据区
这一节介绍先行数据区在sk_buff创建过程中的变化,图中暂时省略了非线性数据区:
-
sk_buff结构数据区刚被申请好,此时 head 指针、data 指针、tail 指针都是指向同一个地方。记住前面讲过的:head 指针和 end 指针指向的位置一直都不变,而对于数据的变化和协议信息的添加都是通过 data 指针和 tail 指针的改变来表现的。
-
开始准备存储应用层下发过来的数据,通过调用函数 skb_reserve(m) 来使 data 指针和 tail 指针同时向下移动,空出一部分空间来为后期添加协议信息。m一般为最大协议头长度,内核中定义。
-
开始存储数据了,通过调用函数 skb_put() 来使 tail 指针向下移动空出空间来添加数据,此时 skb->data 和 skb->tail 之间存放的都是数据信息,无协议信息。
-
这时就开始调用函数 skb_push() 来使 data 指针向上移动,空出空间来添加各层协议信息,添加协议信息也是用skb_put()。直到最后到达二层,添加完帧头然后就开始发包了。
2.2 非线性数据区
2.1中所讲的都是线性数据区中的相关的配置,当线性数据区不够用的时候就会启用非线性数据区作为数据区域的扩展,skb中用skb_shared_info分片结构体来配置非线性数据。
skb_shared_info结构体是和skb中的线性数据区一体的,所以在skb的各种操作时都会把这两个结构看作是一个结构来操作。如:
- 当sk_buff结构的线性数据区申请和释放空间时,分片结构会跟着数据区一起分配和释放。
-
克隆skb时,sk_buff的线性数据区和分片结构都由分片结构中的dataref成员字段来标识是否被引用。
从上图中可以看出来非线性数据区接到skb->end的位置后,skb->end的下一个字节就作为非线性数据区的开始。end指针的下个字节可以作为分片结构的开始,获取end指针的位置要强行转成分片结构,内核中有定义好的宏:
#define skb_shinfo(SKB) ((struct skb_shared_info *)(skb_end_pointer(SKB)))
skb_shared_info结构:
/* include/linux/skbuff.h */
struct skb_shared_info {
unsigned char nr_frags; /*表示有多少分片结构*/
__u8 tx_flags;
unsigned short gso_size;
/* Warning: this field is not always filled in (UFO)! */
unsigned short gso_segs;
unsigned short gso_type;
struct sk_buff *frag_list; /*一种类型的分配数据*/
struct skb_shared_hwtstamps hwtstamps;
u32 tskey;
__be32 ip6_frag_id;
/*
* Warning : all fields before dataref are cleared in __alloc_skb()
*/
atomic_t dataref; /*用于引用计数,克隆一个skb结构体时会增加一个引用计数*/
/* Intermediate layers must ensure that destructor_arg
* remains valid until skb destructor */
void * destructor_arg;
/* must be last field, see pskb_expand_head() */
skb_frag_t frags[MAX_SKB_FRAGS]; /*保存分页数据,skb->data_len = 所有数组数据长度之和*/
};
非线性数据区有两种不同的构成数据的方式
(1)用数组存储的分片数据区,采用是是结构体中的frags[MAX_SKB_FRAGS]
对于frags[]一般用在当数据比较多,在线性数据区装不下的时候,skb_frag_t中是一页一页的数据,skb_frag_struct结构体如下:
/* include/linux/skbuff.h */
typedef struct skb_frag_struct skb_frag_t;
struct skb_frag_struct {
struct {
struct page *p; /*指向分片数据区的指针,类似于sk_buff中的data指针*/
} page;
#if (BITS_PER_LONG > 32) || (PAGE_SIZE >= 65536)
__u32 page_offset;
__u32 size;
#else
__u16 page_offset; /*偏移量,表示相对开始位置的页偏移量*/
__u16 size; /*page中的数据长度*/
#endif
};
下图显示了frags是怎么分配分片数据的:
(2)frag_list指针来指向的分片数据:
参考:
- linux 内核网络 sk_buff 之数据结构剖析Ⅰ_老王不让用的博客-CSDN博客
- sk_buff 结构体 以及 完全解释_gsls181711的专栏-CSDN博客