随着linux内核版本的不同,sk_buff数据结构有细微差异。这里以kernel version:4.1.45来说。
一、sk_buff()数据结构
/**
* struct sk_buff - socket buffer
* @next: Next buffer in list
* @prev: Previous buffer in list
* @tstamp: Time we arrived/left
* @rbnode: RB tree node, alternative to next/prev for netem/tcp
* @sk: Socket we are owned by
* @dev: Device we arrived on/are leaving by
* @cb: Control buffer. Free for use by every layer. Put private vars here
* @_skb_refdst: destination entry (with norefcount bit)
* @sp: the security path, used for xfrm
* @len: Length of actual data
* @data_len: Data length
* @mac_len: Length of link layer header
* @hdr_len: writable header length of cloned skb
* @csum: Checksum (must include start/offset pair)
* @csum_start: Offset from skb->head where checksumming should start
* @csum_offset: Offset from csum_start where checksum should be stored
* @priority: Packet queueing priority
* @ignore_df: allow local fragmentation
* @cloned: Head may be cloned (check refcnt to be sure)
* @ip_summed: Driver fed us an IP checksum
* @nohdr: Payload reference only, must not modify header
* @nfctinfo: Relationship of this skb to the connection
* @pkt_type: Packet class
* @fclone: skbuff clone status
* @ipvs_property: skbuff is owned by ipvs
* @peeked: this packet has been seen already, so stats have been
* done for it, don't do them again
* @nf_trace: netfilter packet trace flag
* @protocol: Packet protocol from driver
* @destructor: Destruct function
* @nfct: Associated connection, if any
* @nf_bridge: Saved data about a bridged frame - see br_netfilter.c
* @skb_iif: ifindex of device we arrived on
* @tc_index: Traffic control index
* @tc_verd: traffic control verdict
* @hash: the packet hash
* @queue_mapping: Queue mapping for multiqueue devices
* @xmit_more: More SKBs are pending for this queue
* @ndisc_nodetype: router type (from link layer)
* @ooo_okay: allow the mapping of a socket to a queue to be changed
* @l4_hash: indicate hash is a canonical 4-tuple hash over transport
* ports.
* @sw_hash: indicates hash was computed in software stack
* @wifi_acked_valid: wifi_acked was set
* @wifi_acked: whether frame was acked on wifi or not
* @no_fcs: Request NIC to treat last 4 bytes as Ethernet FCS
* @napi_id: id of the NAPI struct this skb came from
* @secmark: security marking
* @mark: Generic packet mark
* @vlan_proto: vlan encapsulation protocol
* @vlan_tci: vlan tag control information
* @inner_protocol: Protocol (encapsulation)
* @inner_transport_header: Inner transport layer header (encapsulation)
* @inner_network_header: Network layer header (encapsulation)
* @inner_mac_header: Link layer header (encapsulation)
* @transport_header: Transport layer header
* @network_header: Network layer header
* @mac_header: Link layer header
* @tail: Tail pointer
* @end: End pointer
* @head: Head of buffer
* @data: Data head pointer
* @truesize: Buffer size
* @users: User count - see {datagram,tcp}.c
*/
struct sk_buff {
union {
struct {
/* These two members must be first. */
struct sk_buff *next;//指向下一个sk_buff
struct sk_buff *prev;//指向上一个sk_buff
union {
ktime_t tstamp;//时间戳
struct skb_mstamp skb_mstamp;
};
};
struct rb_node rbnode; /* used in netem & tcp stack */
};
/*对应本机生成的报文,或者发给本机的报文,sk为拥有该skb的套接字;
对于需要转发的报文,sk为NULL*/
struct sock *sk;
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;
//析构函数,skb被释放时调用,skb不属于一个sk时,一般不会被初始化。
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,//数据包总长度=data到tail+分片长度
data_len;//分片的长度
__u16 mac_len,//L2层头长度
hdr_len;
/* 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;
__u8 cloned:1,
nohdr:1,
fclone:2,
peeked:1,
head_frag:1,
xmit_more:1;
/* one bit hole */
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];
/*数据包类型,是自己接收还是转发,由函数eth_type_trans设置
PACKET_HOST 0 :发给自己的包
PACKET_BROADCAST 1 :广播包
PACKET_MULTICAST 2 :组播包
PACKET_OTHERHOST 3 :发给别人的包,需要转发
......*/
__u8 pkt_type:3;
__u8 pfmemalloc:1;
__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;
/* 3 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;//优先级
int skb_iif;
__u32 hash;
__be16 vlan_proto;//vlan类型
__u16 vlan_tci;//vlan tci:包括VLAN优先级,CFI,vlan id等信息。
#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;
/*IP层使用协议,如IP/ARP等,例如,为IP时,
在网卡驱动中由方法eth_type_trans()设置为ETH_P_IP*/
__be16 protocol;
__u16 transport_header;//传输层头相较于head的偏移量
__u16 network_header;//网络层头相较于head的偏移量
//MAC头相较于head的偏移量,中间的是不是多出来的头部空间?
__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;//数据头
/*为skb分配的总内存,包括skb结构本身
(sizeof(struct sk_buff))和分配的数据块长度*/
unsigned int truesize;
atomic_t users;//引用计数
};
linux内核在一个双向链表中维护所有的sk_buff结构,在一开始包括了next,prev指针。

在早期linux内核2.6.10版本中,sk_buff中还包括了list元素,用于指向skb链表头,不过现在linux版本已经没有它了。
//linux 2.6.10
struct sk_buff {
/* These two members must be first. */
struct sk_buff *next;
struct sk_buff *prev;
struct sk_buff_head *list;
struct sock *sk;
//...
}
当时的skb链表是这样的:

关于head、data、tail、end指针:
head:已分配skb线性缓冲区头
end:已分配skb线性缓冲区尾
data:skb数据区头
tail:skb数据区尾
二、相关操作函数
1. alloc_skb()
这个函数很重要,分配一个skb,包括sk_buff结构本身内存和skb缓冲区内存
在这个函数中,两次分配内存,一次分配sk_buff结构本身,一次分配skb数据占用内存(线性+非线性)
参数size:skb线性缓冲区长度
//该函数alloc_skb还没详细分析,最近有点忙,以后再详细分析
/**
* __alloc_skb - allocate a network buffer
* @size: size to allocate
* @gfp_mask: allocation mask
* @flags: If SKB_ALLOC_FCLONE is set, allocate from fclone cache
* instead of head cache and allocate a cloned (child) skb.
* If SKB_ALLOC_RX is set, __GFP_MEMALLOC will be used for
* allocations in case the data is required for writeback
* @node: numa node to allocate memory on
*
* Allocate a new &sk_buff. The returned buffer has no headroom and a
* tail room of at least size bytes. The object has a reference count
* of one. The return is the buffer. On a failure the return is %NULL.
*
* Buffers may only be allocated from interrupts using a @gfp_mask of
* %GFP_ATOMIC.
*/
struct sk_buff *__alloc_skb(unsigned int size, gfp_t gfp_mask,
int flags, int node)
{
struct kmem_cache *cache;
struct skb_shared_info *shinfo;
struct sk_buff *skb;
u8 *data;
bool pfmemalloc;
cache = (flags & SKB_ALLOC_FCLONE)
? skbuff_fclone_cache : skbuff_head_cache;
if (sk_memalloc_socks() && (flags & SKB_ALLOC_RX))
gfp_mask |= __GFP_MEMALLOC;
/* Get the HEAD */
//分配sk_buff内存
skb = kmem_cache_alloc_node(cache, gfp_mask & ~__GFP_DMA, node);
if (!skb)
goto out;
prefetchw(skb);
/* We do our best to align skb_shared_info on a separate cache
* line. It usually works because kmalloc(X > SMP_CACHE_BYTES) gives
* aligned memory blocks, unless SLUB/SLAB debug is enabled.
* Both skb->head and skb_shared_info are cache line aligned.
*/
//数据对齐
size = SKB_DATA_ALIGN(size);
//加上skb_shared_info对齐后长度
size += SKB_DATA_ALIGN(sizeof(struct skb_shared_info));
//分配skb数据区内存
data = kmalloc_reserve(size, gfp_mask, node, &pfmemalloc);
if (!data)
goto nodata;
/* kmalloc(size) might give us more room than requested.
* Put skb_shared_info exactly at the end of allocated zone,
* to allow max possible filling before reallocation.
*/
//除了skb_shared_info以外数据的长度
size = SKB_WITH_OVERHEAD(ksize(data));
prefetchw(data + size);
/*
* Only clear those fields we need to clear, not those that we will
* actually initialise below. Hence, don't put any more fields after
* the tail pointer in struct sk_buff!
*/
memset(skb, 0, offsetof(struct sk_buff, tail));
/* Account for allocated memory : skb + skb->head */
//真实长度=sizeof(struct sk_buff)+skb缓冲区长度+sizeof(struct skb_shared_info)
skb->truesize = SKB_TRUESIZE(size);
skb->pfmemalloc = pfmemalloc;
//应用计数设置为1
atomic_set(&skb->users, 1);
//head,data,tail都指向缓冲区头
skb->head = data;
skb->data = data;
skb_reset_tail_pointer(skb);
//end指向缓冲区尾
skb->end = skb->tail + size;
skb->mac_header = (typeof(skb->mac_header))~0U;
skb->transport_header = (typeof(skb->transport_header))~0U;
/* make sure we initialize shinfo sequentially */
shinfo = skb_shinfo(skb);
memset(shinfo, 0, offsetof(struct skb_shared_info, dataref));
atomic_set(&shinfo->dataref, 1);
kmemcheck_annotate_variable(shinfo->destructor_arg);
if (flags & SKB_ALLOC_FCLONE) {
struct sk_buff_fclones *fclones;
fclones = container_of(skb, struct sk_buff_fclones, skb1);
kmemcheck_annotate_bitfield(&fclones->skb2, flags1);
skb->fclone = SKB_FCLONE_ORIG;
atomic_set(&fclones->fclone_ref, 1);
fclones->skb2.fclone = SKB_FCLONE_CLONE;
fclones->skb2.pfmemalloc = pfmemalloc;
}
out:
return skb;
nodata:
kmem_cache_free(cache, skb);
skb = NULL;
goto out;
}

执行alloc_skb()函数后,head、data、tail都是指向线性缓冲区头的,end是指向线性缓冲区尾的。
关于alloc_skb()函数中涉及到的几个小函数:
(1).SKB_TRUESIZE(X)宏
/* return minimum truesize of one skb containing X bytes of data */
#define SKB_TRUESIZE(X) ((X) + \
SKB_DATA_ALIGN(sizeof(struct sk_buff)) + \
SKB_DATA_ALIGN(sizeof(struct skb_shared_info)))
可以看到真实长度包括sk_buff结构本身长度+线性区域长度+非线性区域长度。
而在老版本中,是不包括非线性区域长度的。
//2.6.10
size = SKB_DATA_ALIGN(size);
size = SKB_DATA_ALIGN(size);
data = kmalloc(size + sizeof(struct skb_shared_info), gfp_mask);
if (!data)
goto nodata;
memset(skb, 0, offsetof(struct sk_buff, truesize));
skb->truesize = size + sizeof(struct sk_buff);
(2).skb_reset_tail_pointer()
将tail指向data,发现有两种版本,其实意思是一样的,第一中tail是指针,第二种tail是个整型,它的值为相对应head的偏移量(字节为单位)。
#ifdef NET_SKBUFF_DATA_USES_OFFSET
typedef unsigned int sk_buff_data_t;
#else
typedef unsigned char *sk_buff_data_t;
#endif
static inline void skb_reset_tail_pointer(struct sk_buff *skb)
{
skb->tail = skb->data;
}
#ifdef NET_SKBUFF_DATA_USES_OFFSET
static inline void skb_reset_tail_pointer(struct sk_buff *skb)
{
skb->tail = skb->data - skb->head;
}
在4.1.45中tail、end是整型,head,data是指针:
sk_buff_data_t tail;//已分配缓冲区尾
sk_buff_data_t end;//数据尾
unsigned char *head,//已分配缓冲区头
*data;//数据头
linux4.1.45中:
__u16 transport_header;//传输层头相较于head的偏移量
__u16 network_header;//网络层头相较于head的偏移量
__u16 mac_header;//MAC头相较于head的偏移量,中间的是不是多出来的头部空间?
linux2.6.32中:
sk_buff_data_t transport_header;
sk_buff_data_t network_header;
sk_buff_data_t mac_header;
(3). skb_reset_network_header()
static inline void skb_reset_network_header(struct sk_buff *skb)
{
skb->network_header = skb->data - skb->head;
}
static inline void skb_reset_network_header(struct sk_buff *skb)
{
skb->network_header = skb->data;
}
2. skb_reserve()
在缓存区头部预留一定的空间,用于封装各层报头。刚分配skb后,data、tail都指向head,现在让data、tail增加len,给头部空间留len长度。
/**
* skb_reserve - adjust headroom
* @skb: buffer to alter
* @len: bytes to move
*
* Increase the headroom of an empty &sk_buff by reducing the tail
* room. This is only allowed for an empty buffer.
*/
static inline void skb_reserve(struct sk_buff *skb, int len)
{
skb->data += len;
skb->tail += len;
}
3. skb_push()
在数据缓冲区的头部加入一块数据,skb往下层传递的时候封装头?
/**
* skb_push - add data to the start of a buffer
* @skb: buffer to use
* @len: amount of data to add
*
* This function extends the used data area of the buffer at the buffer
* start. If this would exceed the total buffer headroom the kernel will
* panic. A pointer to the first byte of the extra data is returned.
*/
unsigned char *skb_push(struct sk_buff *skb, unsigned int len)
{
skb->data -= len;
skb->len += len;
if (unlikely(skb->data<skb->head))
skb_under_panic(skb, len, __builtin_return_address(0));
return skb->data;
}
4. skb_put()
修改tail指针,向下移动len字节,使数据区扩大len字节
/**
* skb_put - add data to a buffer
* @skb: buffer to use
* @len: amount of data to add
*
* This function extends the used data area of the buffer. If this would
* exceed the total buffer size the kernel will panic. A pointer to the
* first byte of the extra data is returned.
*/
unsigned char *skb_put(struct sk_buff *skb, unsigned int len)
{
unsigned char *tmp = skb_tail_pointer(skb);//指向skb->tail
SKB_LINEAR_ASSERT(skb);
skb->tail += len;
skb->len += len;
if (unlikely(skb->tail > skb->end))
skb_over_panic(skb, len, __builtin_return_address(0));
return tmp;
}
5.skb_pull()
调整data指向上一层的头部开始位置,用于下层向上层传递时,上层忽略下层的首部
/**
* skb_pull - remove data from the start of a buffer
* @skb: buffer to use
* @len: amount of data to remove
*
* This function removes data from the start of a buffer, returning
* the memory to the headroom. A pointer to the next data in the buffer
* is returned. Once the data has been pulled future pushes will overwrite
* the old data.
*/
unsigned char *skb_pull(struct sk_buff *skb, unsigned int len)
{
return skb_pull_inline(skb, len);
}
static inline unsigned char *skb_pull_inline(struct sk_buff *skb, unsigned int len)
{
return unlikely(len > skb->len) ? NULL : __skb_pull(skb, len);
}
static inline unsigned char *__skb_pull(struct sk_buff *skb, unsigned int len)
{
skb->len -= len;
BUG_ON(skb->len < skb->data_len);
return skb->data += len;
}
skb从L4层到L2填充过程 :


参考:
https://www.cnblogs.com/wanpengcoder/p/7529576.html
《深入理解linux网络技术内幕》
《understanding-linux-network-internals》英文的图片清晰点。
《Linux内核源码剖析 TCP/IP实现》
本文深入解析Linux内核中的sk_buff数据结构,详细介绍其成员变量及作用,并讲解与sk_buff相关的操作函数,如alloc_skb(), skb_reserve(), skb_push(), skb_put(), skb_pull()等,帮助读者理解网络数据包在Linux内核中的处理流程。

553

被折叠的 条评论
为什么被折叠?



