Linux内核中sk_buff结构详解

news2024/10/1 12:23:02

目录

1.sk_buff结构体

1.1 sk_buff在内核中的结构

1.2 重要的长度len的解析

2. sk_buff数据区

2.1 线性数据区

2.2 非线性数据区

----------------------------------------------------------------------------------------------------------------------------

1.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。

2. sk_buff数据区

        sk_buff结构体中的都是sk_buff的控制信息,是网络数据包的一些配置,真正储存数据的是sk_buff结构体中几个指针指向的数据区中,线性数据区的大小 = (skb->end - skb->head),对于每个数据包来说这个大小都是固定不变的,在传输过程中skb->end和skb->head所指向的地址都是不变的,这里要注意这个地址不是本机的地址,如果是本机的地址那么数据包传到其他主机上这个地址就是无效的,所以这个地址是这个skb缓冲区的相对地址。

        线性数据区是用来存放各层协议头部和应用层发下来的数据。各层协议头部相关信息放在线性数据区中。实际数据指针为data和tail,data指向实际数据开始的地方,tail指向实际数据结束的地方。
        用一张图来表示sk_buff和数据区的关系:

初始定位skb_reserve(m)

2.1 线性数据区

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

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

初始定位skb_reserve(m)

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

初始定位skb_reserve(m)

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

储存应用层数据skb_put()

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

添加协议头
添加协议头

2.2 非线性数据区

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

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

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

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

sk_buff与数据区的关系

        从上图中可以看出来非线性数据区接到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指针来指向的分片数据:

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

3. 参考

参考1:sk_buff数据结构详解icon-default.png?t=MBR7https://zhuanlan.zhihu.com/p/524996060

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/128247.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

内置常量--R

1. R中内置的常量 R中内置的常量有&#xff1a;1. LETTERS&#xff1a; 26个大写英文字母2. letters: 26个小写英文字母3. month.abb: 月份英文前三个字母组成的缩写4. month.name: 月份的英文名字5. pi : 圆周率π

TensorRT介绍及使用

1、简介 TensorRT是一个针对已训练好模型的SDK&#xff0c;通过该SDK能够在NVIDIA的设备上进行高性能的推理。优点如下&#xff1a; 总结下来主要有以下6点&#xff1a; Reduced Precision&#xff1a;将模型量化成INT8或者FP16的数据类型&#xff08;在保证精度不变或略微降…

Linux学习笔记——Linux实用操作(三)

4.10、环境变量 学习目标&#xff1a; 理解环境变量的作用掌握符号$的作用掌握在Linux中配置环境变量 4.10.1、环境变量 1、在讲解which命令的时候&#xff0c;我们知道使用的一系列命令其实本质上就是一个个的可执行程序。 比如&#xff0c;cd命令的本体就是&#xff1a;…

股价狂跌超70%,特斯拉到底怎么了?

​在2022年的尾声&#xff0c;曾在新能源汽车领域盛极一时的特斯拉即将取得上市以来最糟糕的年度表现&#xff0c;直接跌出全球上公司市值前10名。目前特斯拉已连续第7个交易日下跌&#xff0c;截至2022年12月28日&#xff0c;跌超11%&#xff0c;创八个月最大跌幅&#xff0c;…

Leetcode 855. 考场就座

在考场里&#xff0c;一排有 N 个座位&#xff0c;分别编号为 0, 1, 2, ..., N-1 。当学生进入考场后&#xff0c;他必须坐在能够使他与离他最近的人之间的距离达到最大化的座位上。如果有多个这样的座位&#xff0c;他会坐在编号最小的座位上。(另外&#xff0c;如果考场里没有…

「回顾2022,展望2023」- 技术和兴趣,工作和生活,我们都在旅途中

技术 Java&#xff0c;云原生&#xff0c;前端 技术面好像变窄了 远离CI/CD&#xff0c;重新回到起点 2019年毕业&#xff0c;2018年9月份在距离学校(南昌)不远不近的公司实习开始实习&#xff0c;毕业后去过深圳、上海&#xff0c;最后是杭州&#xff0c;到现在&#xff0c;又…

OV7670图像传感器介绍

OV7670图像传感器简介 OV7670是图像传感器&#xff0c;其体积小、工作电压低&#xff0c;能提供单片VGA摄像头和影像处理器的所有功能。通过SCCB 总线控制&#xff0c;可以输出整帧、子采样、取窗口等方式的各种分辨率8位影响数据。该产品VGA图像最高达到30帧/秒。用户可以完全…

西门子PLC串口协议与以太网通信协议对比

西门子plc品牌众多&#xff0c;通信协议的类型就更多了&#xff0c;具体可分为串口协议和以太网通信协议两大类。 串口协议主要有&#xff1a;MODBUS RTU 通信协议&#xff1b;PROFIBUS 通信协议&#xff1b;USS通信协议&#xff1b;PPI通信协议&#xff1b;MPI通信协议&#…

YGG:2022年年终回顾

2022 年&#xff0c;Yield Guild Games&#xff08;YGG&#xff09;扩大并发展了区块链游戏生态系统和开放的元宇宙。在这篇文章中&#xff0c;我们庆祝我们的公会成员、合作伙伴和社区所取得的里程碑式的成就&#xff0c;并期待接下来的发展。 游戏和基础设施合作伙伴的数量不…

推荐 6 个 GitHub 开源项目

本期推荐开源项目目录&#xff1a;1. B 站自动任务工具2. 学习 Solidity3. 高性能异步抖音爬取工具4. Java学习指南5. 中后台管理系统模版6. ChatGPT 中文调教指南01B 站自动任务工具BiliBiliTool 是一个自动化工具&#xff0c;它可以帮助你每天获取经验、每日签到、批量取关等…

分享几个嵌入式 C 中的实用技巧

1、动态绑定、回调函数 回调函数可以达到动态绑定的作用&#xff0c;在一定程度上可以降低层与层之间的耦合。关于回调函数&#xff0c;之前已经有写过一篇&#xff1a;C语言、嵌入式重点知识&#xff1a;回调函数。可能很多初学的小伙伴可能还不理解回调函数&#xff0c;可以…

【计算机图形学入门】笔记9:Shading3着色(插值、高级纹理映射)

09Shading3着色&#xff08;插值、高级纹理映射&#xff09;1.Barycentric Coordinates 重心坐标1.A点自己的重心坐标2.如何求出任意点的重心坐标&#xff1f;2.如何把纹理应用在实际的渲染中&#xff1f;Applying Textures3.Texture Magnification纹理放大。&#xff08;解决纹…

Transform+ASM插桩系列(1)——熟悉Java字节码

前言 为什么要学习Java字节码呢&#xff0c;因为我们学的是插桩字节码技术&#xff0c;这块技术的根底就是字节码&#xff0c;要学会字节码的阅读和字节码的编写&#xff0c;虽然现在很多工具可以帮我们阅读和编写&#xff0c;但最根本的知识还是要理解的。万层楼高从地起&…

CTF之MISC题目-西游记

CTF系列文章 第一篇 CTF之密码学题目-classical && coding 第二篇 CTF之MISC题目-西游记 文章目录CTF系列文章前言一、题目是什么&#xff1f;二、解题步骤1.下载文件&#xff0c;解压2.暴力破解3.解压文件4.处理文本文件5.手动删除多余字符总结前言 CTF中关于MISC&a…

TensorFlow之回归模型-3

1 基本概念 回归模型 线性 线性模型 非线性模型 线性回归 逻辑回归 Log Loss&#xff08;损失函数&#xff09; 分类临界值 2 效率预测 如上所示&#xff0c;使用测试数据集进行评估、用图形显示逻辑回归的预测结果&#xff0c;其中&#xff0c;test_features是测试特…

hnu计网实验一-应用协议与数据包分析实验(使用Wireshark)

前言&#xff1a;这是个比较简单的实验&#xff0c;个人认为最难的不是分析部分&#xff0c;而是能否抓到一个好的包。为了抓到一个好的包我试了很多个网站&#xff0c;终于抓京东时抓到了令我个人最满意的包&#xff0c;因为没有其他各种杂乱的报文&#xff0c;就是那几条必要…

直流信号线性放大器非隔离转换模块0-10V转0-12V大功率负载180mA导轨安装

概述&#xff1a; 导轨安装DIN11 NIPO 系列模拟信号放大器是一种将输入信号放大、转换成按比例输出的直流信号放大器。产品广泛应用在电力、远程监控、仪器仪表、医疗设备、工业自控等需要直流信号测控的行业。此系列产品内部采用稳压电路&#xff0c;通过等比例控制线性放大输…

视频转换IC大全和桥接芯片大全

视频转换大全&#xff08;桥接芯片大全&#xff09; 本人从事多年视频转换ic多年累计大量宝贵资源&#xff0c;可以和大家交流交流&#xff0c;下面就视频转换或桥接芯片做下相关交流&#xff0c;期望可以帮助大家。 视频转换或视频桥接&#xff1a;就是把视频源的信号格式转成…

《MySQL高级篇》数据库建模工具---PowderDesigner的使用教程

文章目录PowerDesigner的使用11.1 开始界面11.2 概念数据模型11.3 物理数据模型11.4 概念模型转为物理模型11.5 物理模型转为概念模型11.6 物理模型导出SQL语句PowerDesigner的使用 PowerDesigner是一款开发人员常用的数据库建模工具&#xff0c;用户利用该软件可以方便地制作…

Python--字典及基本操作

字典也是 Python 提供的一种常用的数据结构&#xff0c;它用于存放具有映射关系的数据。 比如有份成绩表数据&#xff0c;语文&#xff1a;79&#xff0c;数学&#xff1a;80&#xff0c;英语&#xff1a;92&#xff0c;这组数据看上去像两个列表&#xff0c;但这两个列表的元素…