一、简介

sk_buff的意思是socket buffer,这是Linux网络子系统中的核心数据结构。

定义在 <include/linux/skbuff.h> 中,它由许多变量组成,目标就是满足所有网络协议的需要。

sk_buff 在不同的网络层被使用(MAC 或其他在 L2 的协议,在 L3 的 IP 协议,在 L4 的 TCP 或 UDP 等),当它从一层传递到另一层时,各个字段也会发生变化。在被传递到 L3 之前,L4 会追加头信息,然后在被传递到 L2 之前,L3 会追加头信息。从一层传递到另一层时,通过追加头信息的方式比将数据在层之间拷贝会更有效率。由于要在 buff 的开头增加空间(与平时常见的在尾部追加空间相比)是一项复杂的操作,内核便提供了 skb_reserve 函数执行这个操作。因此,随着 buffer 从上层到下层的传递,每层协议做的第一件事就是调用 skb_reserve 去为它们的协议头在 buffer 的头部分配空间。在后面,我们将通过一个例子去了解内核如何在当 buffer 在各个层间传递时,确保为每一层保留了足够的空间让它们添加它们自己的协议头。

在接收数据时,buffer 会被从下层到上层传递,在从下到上的过程中,前一层的协议头对于当前层来说已经没有用了。比如:L2 的协议头只会被处理 L2 协议的设备驱动程序使用,L3 并不关心 L2 的头。那么内核怎么做的呢? 内核的实现是:** sk_buff 中有一个指针会指向当前位于的层次的协议的协议头的内存开始地址,于是从 L2 到 L3 时,只需将指向 L2 头部的指针移动到 L3 的头部即可**(又是一步追求效率的操作)。

二、sk_buff的组织结构

Linux 内核把系统中所有的 sk_buff 实例维护在一个双向链表中,sk_buff 链表的每个节点也通过 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;spinlock_t lock;
};
  • len: 表示链表中的节点数
  • lock: 用作多线程同步

sk_buff 和 sk_buff_head 开始的两个节点(next prev)是相同的。即使 sk_buff_head 比 sk_buff 更轻量化,也允许这两种结构在链表中共存。另外,可以使用相同函数来操作 sk_buff 和 sk_buff_head。

为了实现通过每个节点都能快速找到链表头,每个节点都会包含一个指向链表中唯一的 sk_buff_head 的指针(list)。

二、sk_buff的关键成员

2.1、数据区

2.1.1、 线性数据区

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

这一节介绍先行数据区在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.1.2、 非线性数据区(skb_shared_info)

/*  include/linux/skbuff.h */#if (65536/PAGE_SIZE + 1) < 16
#define MAX_SKB_FRAGS 16UL
#else
#define MAX_SKB_FRAGS (65536/PAGE_SIZE + 1)
#endifstruct 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 = 所有数组数据长度之和*/
};

一个skb用于存储一个报文,如果一个报文特别大的话,一个skb存储不下,就会启用非线性数据区作为数据区域的扩展。

从上图中可以看出,skb->end的下一个字节就作为非线性数据区的开始。end指针的下个字节可以作为分片结构的开始,获取end指针的位置要强行转成分片结构,内核中有定义好的宏:

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

非线性区域有两种组织方式:

线性存储区放不下就需要多个skb来存储,这就是下面frag_list的作用,保存连续的skb,但是如果内核支持分散聚集技术的话,并且报文长度刚好又不大于mtu,就不必重新分配一个skb来存储,可以使用一些内存碎片来存储,就是下面的frags数组表示的内存页面片段。

(1)用数组存储的分片数据区。

采用是是结构体中的frags[MAX_SKB_FRAGS]。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指针来指向的分片数据。

2.2、数据区长度

unsigned int len, data_len, mac_len;
unsigned int truesize;

这里要声明两个概念的区别,后续直接用这两个概念,注意区分:
(1)线性区总长度:skb_data_len = skb->end - skb->head;(不包括 skb_shared_info)
(2)线性区有效数据长度:linear_buffer_len = skb->tail - skb->data;不包含线性数据中的头空间和尾空间。

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

2.3、协议头部指针

依次是传输层、网络层、mac层的头部指针,用union表示是互斥的,只能表示其中的一种

 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

当接收到数据包时,从底层向顶层传递数据的时候,只需要把对应的skb->data字段指向相应的头部即可。

2.4、路由相关

struct dst_entry dst

 struct  dst_entry   *dst;struct sec_path    *sp
  • dst_entry指向需要转发包的函数,这个指针在sk_buff在IP层传输前必须是合法的
  • sec_path是一个可选的有关网络安全的成员

2.5、信息控制块

 char    cb[48]

每层协议私有的信息存储空间,由每一层自己维护和使用,并只在本层有效

TCP, for example, uses that space to store a tcp_skb_cb data structure, which is defined in include/net/tcp.h:

struct tcp_skb_cb {... ... ..._ _u32        seq;        /* Starting sequence number */_ _u32        end_seq;    /* SEQ + FIN + SYN + datalen*/_ _u32        when;       /* used to compute rtt's    */_ _u8         flags;      /* TCP header flags.        */... ... ...
};

这是 TCP 代码访问结构的宏。宏仅由一个指针转换组成:

#define TCP_SKB_CB(_ _skb)    ((struct tcp_skb_cb *)&((_ _skb)->cb[0]))
int tcp_v4_rcv(struct sk_buff *skb)
{... ... ...th = skb->h.th;TCP_SKB_CB(skb)->seq = ntohl(th->seq);TCP_SKB_CB(skb)->end_seq = (TCP_SKB_CB(skb)->seq + th->syn + th->fin +skb->len - th->doff * 4);TCP_SKB_CB(skb)->ack_seq = ntohl(th->ack_seq);TCP_SKB_CB(skb)->when = 0;TCP_SKB_CB(skb)->flags = skb->nh.iph->tos;TCP_SKB_CB(skb)->sacked = 0;... ... ...
}

2.6、其他

struct timeval stamp

报文到达或者离开的时间戳,一般表示何时收到报文,一般是在包从驱动中往二层发送的接口函数中设置。它在接收函数 netif_rx (kernel/linux/net/core/dev.c)中设置。

/include/linux/skbuff.hstatic inline void __net_timestamp(struct sk_buff *skb)
{skb->tstamp = ktime_get_real();
}

struct net_device *dev

指向网络设备,作用与该sk_buff是发送包还是接收包有关。在初始化网络设备驱动,分配接收缓存队列时,将该指针指向收到数据包的网络设备

struct net_device *input_dev

This is the device the packet has been received from. It is a NULL pointer when the packet has been generated locally. 指向接收报文的原始网络设备,如果包是本地生成的,则该值为NLL,主要用于流量控制

struct net_device *real_dev

虚拟设备才有效

引用计数

atomic_t     users;
  • 记录有多少引用指向这个sk_buff,该计数器只保护sk_buff描述符,要区别于skb_shared_info结构的dataref成员
  • 通常使用skb_get()和kfree_skb()操作引用计数

附录:v4.9.130版本linux源码

/** *    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*  @pfmemalloc: skbuff was allocated from PFMEMALLOC reserves*    @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;struct sk_buff        *prev;union {ktime_t        tstamp;struct skb_mstamp skb_mstamp;};};struct rb_node  rbnode; /* used in netem & tcp stack */};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;void            (*destructor)(struct sk_buff *skb);
#ifdef CONFIG_XFRMstruct    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;
#endifunsigned int      len,data_len;__u16          mac_len,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;/* 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
#endifunion {__wsum     csum;struct {__u16  csum_start;__u16    csum_offset;};};__u32           priority;int            skb_iif;__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;
#endifunion {__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;
};

ref:

Linux 内核网络协议栈 ------sk_buff 结构体 以及 完全解释 (2.6.16) - 明明是悟空 - 博客园

Linux SKB结构体中各个长度字段的含义(len, data_len, headlen, pagelen)_mrsonko的博客-CSDN博客_skb_headlen

https://abcdxyzk.github.io/download/kernel/sk_buff%E8%AF%A6%E8%A7%A3.pdf

Linux内核中sk_buff结构详解 - 简书

理解Linux内部网络实现之关键数据结构 sk_buff – Yang Blog

Section 2.1. The Socket Buffer: sk_buff Structure--深入理解linux网络技术内幕--嵌入式linux中文站

Linux内核网络源码解析1——sk_buff结构

http://wiki.dreamrunner.org/public_html/Linux/Networks/sk_buff-structure-analysis.html

linux 网络 sk_buff结构相关推荐

  1. linux网络代码结构

    linux网络代码结构:四层 1.设备驱动,Linux/drivers/net/3c501.c 2.网络核心,linux/net/core/dev.c,struct net_device, 3.网络协 ...

  2. linux usbnet网络驱动,[技术资料]基于usb设备的linux网络驱动程序开发

    1 引言 做为开放源代码 (open source) 运动重要组成部分,linux操作系统吸引了数以万计的程序员共同开发.由于linux比较完整的继承了各种unix版本的稳定和高效,并且克服和改进了传 ...

  3. 深入浅出Linux内核网络协议栈|结构sk_buff|Iptables|Netfilter丨内核源码丨驱动开发丨内核开发丨C/C++Linux服务器开发

    深入浅出Linux内核网络协议栈 视频讲解如下,点击观看: 深入浅出Linux内核网络协议栈|结构sk C/C++Linux服务器开发高级架构师知识点精彩内容包括:C/C++,Linux,Nginx, ...

  4. Linux 网络设备驱动开发(一) —— linux内核网络分层结构

    Linux内核对网络驱动程序使用统一的接口,并且对于网络设备采用面向对象的思想设计. Linux内核采用分层结构处理网络数据包.分层结构与网络协议的结构匹配,既能简化数据包处理流程,又便于扩展和维护. ...

  5. Linux网络技术学习(一)—— sk_buff数据结构解析

    文章目录 Linux网络代码中结构体 套接字缓冲区:sk_buff数据结构 sk_buff数据结构定义文件位置 sk_buff数据结构分布 sk_buff数据结构在传输中的流程 网络选项以及内核结构 ...

  6. Linux内核协议栈-sk_buff结构详解

    文章目录 为什么需要sk_buff: sk_buff结构源码及注解: skb_shared_info结构和skb_shinfo函数 skb_shared_info结构 sk_buff结构框图 sk_b ...

  7. Linux内核中sk_buff结构详解

    目录 1.sk_buff结构体 1.1 sk_buff在内核中的结构 1.2 重要的长度len的解析 2. sk_buff数据区 2.1 线性数据区 2.2 非线性数据区 -------------- ...

  8. [Linux网络编程学习笔记]套接字地址结构

    好久没有看那Linux网络编程这本书了,今天看到了重点部分-TCP套接字.下面先来看看套接字的地址结构 Linux系统的套接字可以支持多种协议,每种不同的协议都是用不同的地址结构.在头文件<li ...

  9. Linux下网络相关结构体 struct servent

    Linux下网络相关结构体 struct servent 参考书籍:<UNIX环境高级编程> 参考链接: http://www.cnblogs.com/benxintuzi/p/45898 ...

最新文章

  1. 「重磅猜题之第二篇」2019年大学生电子设计竞赛
  2. Cities(2020昆明C)
  3. Conditional project or library reference in Visual Studio
  4. 广州python平均薪资_爬取广州的python和Java薪资,为什么Python 高于Java(有代码)...
  5. django后台管理--添加自定义action
  6. 孩子们各显神通对付 iOS 12「屏幕使用时间」的限制
  7. mysql subquery_mysql-8-subquery
  8. 大数据发展的7个趋势 -- 阿里技术专家权威解读
  9. 数据挖掘肿瘤预测_肿瘤分析数据挖掘及信息解读
  10. 《通信原理》用matlab实现加性高斯白噪声信道实验
  11. windows 2003 server 企业英文版 序列号
  12. localhost和127.0.0.1的区别
  13. infer的用法_infer使用的简单介绍
  14. openGL中的抗锯齿实现
  15. 计算机学院特色迎新标语,有创意的迎新,计算机学院用代码写迎新条幅,学弟学妹表示一脸懵...
  16. 【web压测】压测常用工具、压测指标到底是什么?
  17. lab值意义_色差仪lab值如何分析
  18. 微信的账号连接服务器失败怎么回事,微信无法连接到服务器的原因和6个解决方法...
  19. BPM 应用系统开发案例实战
  20. Lecture4 反向传播(Back Propagation)

热门文章

  1. 人类大脑到人工智能合并技术已经在开发
  2. 什么是javascript内存泄漏?以及解决方法
  3. 6个超酷的 Python 技巧
  4. SEDA性能优化的分析和模拟
  5. 2021全球什么牌子自行车质量好中国十大老牌自行车品牌排行榜
  6. OpenGL3.3鼠标拾取物体
  7. STM32定时器总结
  8. 操作系统中的堆栈区别
  9. mysql下解决动态表名
  10. 出身清华姚班,斯坦福博士!她的毕业论文为何成为「爆款」?