暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

sk_buffer-基本结构

CodeTrip 2021-06-10
2718

sk_buff-结构简介

内核用于管理网络数据包的缓冲区,描述已接收或待发送的数据报文信息。

缓冲区被分配为两部分:

  1. sk_buff类型的固定大小的标头,SKB描述符
  2. 足以容纳单个数据包的全部或部分数据的可变长度区域

源码:

2.6版本

/include/linux/skbuff.h

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;              //指向拥有此缓冲区的套接字sock结构
 struct timeval  stamp;         
 struct net_device *dev;          
 struct net_device *real_dev;     
 
 union {
  struct tcphdr *th;
  struct udphdr *uh;
  struct icmphdr *icmph;
  struct igmphdr *igmph;
  struct iphdr *ipiph;
  unsigned char *raw;
 } h;

 union {
  struct iphdr *iph;
  struct ipv6hdr *ipv6h;
  struct arphdr *arph;
  unsigned char *raw;
 } nh;

 union {
    struct ethhdr *ethernet;
    unsigned char  *raw;
 } mac;

 struct  dst_entry *dst;
 struct sec_path *sp;

 /*
  * 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];//可用于协议之间传递信息

 unsigned int  len,        
    data_len,           
    csum;               //csum字段保留数据的最终校验和
    
 unsigned char  local_df,
                     /*local_df字段用于在是否请求真实路径mtu时发出信号。
                        local_df==1表示未请求IP_PMTUDISC_DO,
                        local_df==0表示IP_PMTUDISC_DO被请求,因此如果我们收到片段,应该生成icmp错误。
                        */

 
    cloned,//skbuff是否已被克隆
 
    pkt_type, 
                /*描述了包的目的地
                /* Packet types */

                #define PACKET_HOST  0  /*发给接收该数据包的主机(数据包MAC==接收设备MAC)*/
                #define PACKET_BROADCAST 1  /*数据包MAC目的地址是一个广播地址 */
                #define PACKET_MULTICAST 2  /*目的地址是组播地址*/
                #define PACKET_OTHERHOST 3  /* 目的地址与接收设备MAC地址完全不同,若本机没有转发功能,包将被丢弃*/
                #define PACKET_OUTGOING  4  /*这个数据包将被发出*/
                /* These ones are invisible by user level */
                #define PACKET_LOOPBACK  5  /*数据包被发给lookup设备*/
                #define PACKET_FASTROUTE 6  /*这个数据包由快速路由代码查找路由*/
    ip_summed;
                /*告诉驱动程序是否提供校验和
                /* Don't change this without changing skb_csum_unnecessary! */

                #define CHECKSUM_NONE 0         //输入时,表示设备无法校验数据包,由软件校验
                #define CHECKSUM_UNNECESSARY 1  //没有必要校验
                #define CHECKSUM_COMPLETE 2     //已经完成执行校验和
                #define CHECKSUM_PARTIAL 3      //由硬件执行校验和
                */
 __u32   priority;
 unsigned short  protocol,
    security;//安全级别

 void   (*destructor)(struct sk_buff *skb);     
 
 
#ifdef CONFIG_NETFILTER
        unsigned long  nfmark;
 __u32   nfcache;
 struct nf_ct_info *nfct;
#ifdef CONFIG_NETFILTER_DEBUG
        unsigned int  nf_debug;
#endif
#ifdef CONFIG_BRIDGE_NETFILTER
 struct nf_bridge_info *nf_bridge;
#endif
#endif /* CONFIG_NETFILTER */
#if defined(CONFIG_HIPPI)
 union {
  __u32  ifield;
 } private;
#endif
#ifdef CONFIG_NET_SCHED
       __u32   tc_index;               /* traffic control index */
#endif

 /* These elements must be at the end, see alloc_skb() for details.  */
 unsigned int  truesize;   
 atomic_t  users;     
 unsigned char  *head,
    *data,
    *tail,
    *end;
};

内核维护sk_buff总布局

内核在一个双向链表中维护所有sk_buff结构

/include/linux/skbuff.h

每个SKB必须能被整个链表头快速找到,所以有了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;       //防止并发访问
};

struct sk_buff {
    ......
 struct sk_buff  *next;
 struct sk_buff  *prev;

 struct sk_buff_head *list;
    ......
};

image

协议的头指针

针对传输层(L4)


 union {
  struct tcphdr *th;
  struct udphdr *uh;
  struct icmphdr *icmph;
  struct igmphdr *igmph;
  struct iphdr *ipiph;
  unsigned char *raw;   //用于初始化
 } h;

针对网络层(L3)

 union {
  struct iphdr *iph;
  struct ipv6hdr *ipv6h;
  struct arphdr *arph;
  unsigned char *raw;   //用于初始化
 } nh;

IP头部:

struct iphdr {
#if defined(__LITTLE_ENDIAN_BITFIELD)
 __u8 ihl:4,
  version:4;
#elif defined (__BIG_ENDIAN_BITFIELD)
 __u8 version:4,
    ihl:4;
#else
#error "Please fix <asm/byteorder.h>"
#endif
 __u8 tos;
 __u16 tot_len;
 __u16 id;
 __u16 frag_off;
 __u8 ttl;
 __u8 protocol;
 __u16 check;
 __u32 saddr;
 __u32 daddr;
 /*The options start here. */
};

针对数据链路层(L2)

 union {
    struct ethhdr *ethernet;
    unsigned char  *raw;   //用于初始化
 } mac;

MAC头定义:

/include/linux/if_ether.h

struct ethhdr 
{

 unsigned char h_dest[ETH_ALEN]; /* destination eth addr */
 unsigned char h_source[ETH_ALEN]; /* source ether addr */
 unsigned short h_proto;  /* packet type ID field */
};

路由相关字段

目的路由缓存项。输入输出数据包都要经过路由子系统查询得到目的路由缓存项,确定数据包流向,否则只能丢弃。

struct  dst_entry *dst; //路由子系统使用

指向用于路由sk_buff的路由缓存条目的指针。它所指向的这个路由缓存元素包含指向该函数的指针将被调用以转发该数据包。此指针dst必须指向有效的路由缓存元素,然后才能将缓冲区传递到IP层进行传输

struct sec_path *sp;

sec_path指针是一个相对较新的可选字段,它支持网络安全的附加“钩子”。

长度字段

缓冲区中数据区块的大小:主缓冲区数据+片段缓冲区数据

unsigned int  len,

片段中的数据大小,表示留在片段列表和未映射页面缓冲区中的字节总和。用来判断是否开启聚合分散IO数据

    data_len;

每当len增加该字段都会更新,由alloc_skb函数设置整个缓冲区的总长度(SKB+数据缓存区)

unsigned int  truesize;  

引用计数

标识有多少用户引用了该SKB,确定释放SKB的时机

atomic_t  users;

  • 每当共享缓冲区时,它就会递增。

  • 当逻辑上释放缓冲区时,它会递减。

  • 仅当引用计数达到0时,缓冲区才会被物理上释放。

网络设备字段

时间戳只对一个已接收的封包有意义,表示封包何时被接接收或封包的预定传输时间。

配套函数:netif_rx(),net_timestamp()

struct timeval  stamp;         

网络设备指针。与SKB是发送包还是接收包有关。

初始化网络设备驱动,分配接收缓存队列时,该指针指向收到数据包的网络设备。

struct net_device *dev;

该字段只对虚拟设备有意义,代表与虚拟设备关联的真实设备。

struct net_device *real_dev; 

接收报文的原始网络设备。若包是本地生成,设置为NULL

struct net_device *input_dev;

缓冲区内容管理指针

unsigned int len,    

......

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

head: 指向缓冲区第一个字节,缓冲区创建是指向,之后永远不会改变

data: 指向缓冲区中“数据”的开始位置。当头数据被移除或添加到数据包时,可以向前或向后调整此指针。

tail: 指向缓冲区中“数据”最后的字节。该指针也可以被调整。

end: 指向skb_shared_info结构的开始(即。数据包数据存储区域之外的第一个字节。)它也被设置在缓冲区分配时间,此后从不调整。

len: tail-data。

buffer_mangment

skb_shared_info结构

skb_shared_info用于管理碎片化的缓冲区和未映射的页面缓冲区。

这个结构位于数据区域的末端,由sk_buff的end指针指向。

/* This data is invariant across clones and lives at
 * the end of the header data, ie. at skb->end.
 */

struct skb_shared_info {
 atomic_t dataref;
 unsigned short nr_frags;
 unsigned short gso_size;
 /* Warning: this field is not always filled in (UFO)! */
 unsigned short gso_segs;
 unsigned short  gso_type;
 __be32          ip6_frag_id;
 struct sk_buff *frag_list;
 skb_frag_t frags[MAX_SKB_FRAGS];
};

当单个sk_buff的数据由多个片段组成时,将使用skb_shared_info结构。

dataref: 当一个数据缓冲区被多个SKB描述符引用时,设置该字段。

nr_frags、frags、frag_list与IP分片存储有关。用于支持聚合分散I/O数据

聚合分散 I/O数据: 如果发送报文的网络接口能够支持聚合分散I/O,报文就无需组装成一个单块,可避免大量拷贝。聚合分散 I/O 从用户空间启动"零拷贝"网络发送。内核传递发送报文给 hard_start_xmit()之前需要判断网络设置是否支持NETIF_ F_SG,不然只能对分散的报文进行线性化处理,再聚合成一个单独的报文。如果网络设备已经设置了该标志,接下来检查nr_frags值,确定片段数,这些分散的片段以关联的方式存储在frags数组中。

frags和nr_frags用于支持SG类型的聚合分散I/O分片。frags是用于存放聚合分散 I/O分片的数组。

skb_is_nonlinear()函数通过判断data_len可以知道SKB是否存在聚合分散I/O 缓存区。

*frag_list:

  1. 用于在接收分片组后链接多个分片,组成一个完整的 IP 数据报。
  2. 在 UDP 数据报的输出中,将待分片的 SKB 链接到第一个 SKB中,然后在输出过程中能够快速地分片。
  3. 用于存放 FRAGLIST类型的聚合分散 I/O 的数据包,如果输出网络设备支持 FRAGLIST类型的聚合分散 I/O,则可以直接输出。

nr_frags: 当前使用聚合分散 I/O 分片的数量,即为 frags 数组中使用的数量,不超过 MAX_SKB_FRAGS

frags[]: frags数组保留指向碎片化subuff的页面结构的指针。最后使用的页面指针是nr_frags-1,有最多为MAX_SKB_FRAGS的空间。

skb_frag_t结构表示一个未映射的页面缓冲区。

/* To allow 64K frame to be packed as single skb without frag_list */
#define MAX_SKB_FRAGS (65536/PAGE_SIZE + 2)

typedef struct skb_frag_struct skb_frag_t;

struct skb_frag_struct {
 struct page *page;
 __u32 page_offset;
 __u32 size;
};

page: 指向文件系统缓存页面的指针。

page_offset: 存储数据的页面偏移。

size: 数据的长度

skb_shared_info

data_len = 0,说明没有报文聚合分散IO数据。nr_frags = 0,frag_list = NULL说明未开启分片

存储在线性存储区的报文

skb_shared_info_list
文章转载自CodeTrip,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论