sk_buff操作函数学习

news2024/9/21 20:48:55

一. 前言

        内核提供了大量实用的操作sk_buff的函数,在开发网络设备驱动程序和修改网络协议栈代码时需要用到。这些函数从功能上可以分为三类:创建,释放和复制socket buffer;操作sk_buff结构中的参数和指针;管理socket buffer的队列。本文主要介绍其中一些典型的函数。本文使用的内核版本是3.18.45。

二. 创建和释放socket buffer

1. alloc_skb

        alloc_skb是__alloc_skb函数的封装,是为socket buffer分配内存的主要函数。其中具体的实现不再分析了,主要介绍调用alloc_skb函数后,sk_buff各个参数和指针的情况。其中,skb->head,skb->data和skb->tail都指向分配内存的开始地址,skb->end指向分配内存的结束地址,分配的数据大小是4字节对齐的。如下图所示。

        注意:调用alloc_skb分配的skb,由于实际数据长度还不确定,其中skb->len = 0。

2. kree_skb

        kree_skb函数是__kfree_skb函数的封装,由于同一个socket buffer可能被多个地方使用,所以kfree_skb只有在skb->users = 1时,才会完全释放socket buffer,其他情况只会将skb->users减1。

三. 操作sk_buff参数和指针的函数

1. skb_reserve

skb_reserve的函数原型:

static inline void skb_reserve(struct sk_buff *skb, int len)
{
    skb->data += len;
    skb->tail += len;
}

        skb_reserve函数的使用是在alloc_skb函数之后,作用是为网络协议栈的二层,三层以及数据负载留出空间。如下图所示:

         上图中,留出的reserve len就是为协议栈的其他数据压入预留空间,从中也可以看出,skb->data到skb->tail之间保存的是真实负载数据的内容。

2. skb_put

skb_put的函数原型:

unsigned char *skb_put(struct sk_buff *skb, unsigned int len)
{
	unsigned char *tmp = skb_tail_pointer(skb);
	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;
}

        从函数实现可知,skb_put函数将skb->tail加了len,表示将skb的实际负载数据的长度加len,要实现这一点,alloc_skb分配的len减去skb_reserve的len应该要大于等于skb_put的len,这样才能保证skb_put函数运行没问题。示意图如下:

3. skb_push

skb_push的函数原型:

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;
}

        从函数实现可以看出,skb_push将data指针往低地址移动len长度,同时skb->len长度加len,然后程序将向该地址压入长度为len长度的数据。示意图如下:

4. skb_pull

skb_pull的函数原型:

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;
}

static inline unsigned char *skb_pull_inline(struct sk_buff *skb, unsigned int len)
{
	return unlikely(len > skb->len) ? NULL : __skb_pull(skb, len);
}

unsigned char *skb_pull(struct sk_buff *skb, unsigned int len)
{
	return skb_pull_inline(skb, len);
}

        从函数实现可以看出,skb_pull是__skb_pull函数的封装,skb_pull_inline判断len要小于skb->len。skb_pull函数就是skb_push的反向操作函数,作用是将skb->data指针向高地址移动len,同时,skb->len减len,相当于从skb中删除了长度为len的数据。

5. skb_trim

skb_trim函数的原型:

static inline void skb_set_tail_pointer(struct sk_buff *skb, const int offset)
{
	skb->tail = skb->data + offset;
}

static inline void __skb_trim(struct sk_buff *skb, unsigned int len)
{
	if (unlikely(skb_is_nonlinear(skb))) {
		WARN_ON(1);
		return;
	}
	skb->len = len;
	skb_set_tail_pointer(skb, len);
}

void skb_trim(struct sk_buff *skb, unsigned int len)
{
	if (skb->len > len)
		__skb_trim(skb, len);
}

        从函数实现可以看出,skb_trim是__skb_trim的封装,作用是将skb的负载数据长度修剪为len,并修剪长度是通过修改skb->tail的指针来实现的。示意图如下:

四. 管理socket buffer的队列

1. skb_queue_head_init

skb_queue_head_init函数的原型:

static inline void __skb_queue_head_init(struct sk_buff_head *list)
{
	list->prev = list->next = (struct sk_buff *)list;
	list->qlen = 0;
}

static inline void skb_queue_head_init(struct sk_buff_head *list)
{
	spin_lock_init(&list->lock);
	__skb_queue_head_init(list);
}

        从函数实现可以看出,skb_queue_head_init是__skb_queue_head_init函数的封装,作用是初始化链表的指针以及链表的队列长度。

2. skb_dequeue

skb_dequeue的函数原型:

static inline struct sk_buff *__skb_dequeue(struct sk_buff_head *list)
{
	struct sk_buff *skb = skb_peek(list);
	if (skb)
		__skb_unlink(skb, list);
	return skb;
}

struct sk_buff *skb_dequeue(struct sk_buff_head *list)
{
	unsigned long flags;
	struct sk_buff *result;

	spin_lock_irqsave(&list->lock, flags);
	result = __skb_dequeue(list);
	spin_unlock_irqrestore(&list->lock, flags);
	return result;
}

        函数的作用是从链表的头部取出一个skb,并且将skb从链表中删除。

3. skb_dequeue_tail

skb_dequeue_tail的函数原型:

static inline struct sk_buff *__skb_dequeue_tail(struct sk_buff_head *list)
{
	struct sk_buff *skb = skb_peek_tail(list);
	if (skb)
		__skb_unlink(skb, list);
	return skb;
}

struct sk_buff *skb_dequeue_tail(struct sk_buff_head *list)
{
	unsigned long flags;
	struct sk_buff *result;

	spin_lock_irqsave(&list->lock, flags);
	result = __skb_dequeue_tail(list);
	spin_unlock_irqrestore(&list->lock, flags);
	return result;
}

        函数的作用是从链表的尾部取出一个skb,并且将skb从链表中删除。

4. skb_append

skb_append的函数原型:

static inline void __skb_insert(struct sk_buff *newsk,
				struct sk_buff *prev, struct sk_buff *next,
				struct sk_buff_head *list)
{
	newsk->next = next;
	newsk->prev = prev;
	next->prev  = prev->next = newsk;
	list->qlen++;
}

static inline void __skb_queue_after(struct sk_buff_head *list,
				     struct sk_buff *prev,
				     struct sk_buff *newsk)
{
	__skb_insert(newsk, prev, prev->next, list);
}

void skb_append(struct sk_buff *old, struct sk_buff *newsk, struct sk_buff_head *list)
{
	unsigned long flags;

	spin_lock_irqsave(&list->lock, flags);
	__skb_queue_after(list, old, newsk);
	spin_unlock_irqrestore(&list->lock, flags);
}

        函数的作用是在链表中的old节点后面插入newsk的skb,链表的队列长度也加1。

五. 实例

#include <linux/module.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/skbuff.h>
#include <linux/udp.h>
#include <linux/ip.h>

#define UDP_SRC_PORT	3015
#define UDP_DST_PORT	3016

char data[32] = "chaos is a ladder!";
char src_ip[] = "192.168.0.56";
char dst_ip[] = "192.168.0.105";
char src_mac[] = {0x00, 0x0C, 0x43, 0x28, 0x80, 0xF5};
char dst_mac[] = {0x3C, 0x7C, 0x3F, 0xD7, 0x94, 0xAF};

struct sk_buff *skb;
struct udphdr *uhdr;
struct iphdr *ihdr;
struct ethhdr *ehdr;
struct net_device * dev = NULL;


void assemble_udp_header(struct udphdr *udp_hdr)
{
	udp_hdr->source = htons(UDP_SRC_PORT);
	udp_hdr->dest = htons(UDP_DST_PORT);
	udp_hdr->len = htons(sizeof(data) + sizeof(struct udphdr));
	udp_hdr->check = ~csum_tcpudp_magic(ihdr->saddr, ihdr->daddr, skb->len, IPPROTO_UDP, 0);
}

void assemble_ip_header(struct iphdr *ip_hdr)
{
	ip_hdr->version = IPVERSION;
	ip_hdr->ihl = sizeof(struct iphdr)/4;
	ip_hdr->tos =  0;
	ip_hdr->tot_len = htons(sizeof(data) + sizeof(struct udphdr) + sizeof(struct iphdr));
	ip_hdr->id = htons(42351);
	ip_hdr->frag_off = 0;
	ip_hdr->ttl = 64;
	ip_hdr->protocol = IPPROTO_UDP;
	ip_hdr->check = 0;
	ip_hdr->saddr = in_aton(src_ip);
	ip_hdr->daddr = in_aton(dst_ip);
}

void assemble_eth_header(struct ethhdr *eth_hdr)
{
	memcpy(eth_hdr->h_dest, dst_mac, 6);
	memcpy(eth_hdr->h_source, src_mac, 6) ;
	eth_hdr->h_proto = htons(ETH_P_IP);
}

int __init skb_package_init(void)
{
	printk("skb init\n");
	unsigned char *p;
	
	skb = alloc_skb(MAX_SKB_SIZE, GFP_ATOMIC);
	dev = dev_get_by_name(&init_net, "br0");
	skb->dev = dev;

	skb_reserve(skb, 2 + sizeof(struct ethhdr) + sizeof(struct iphdr) + sizeof(struct udphdr) + sizeof(data));

	p = skb_push(skb, sizeof(data));
	memcpy(p, (void*)data, sizeof(data));

	p = skb_push(skb, sizeof(struct udphdr));
	uhdr = (struct udphdr *)p;
	skb_reset_transport_header(skb);

	p = skb_push(skb, sizeof(struct iphdr));
	ihdr = (struct iphdr *)p;
	skb_reset_network_header(skb);

	p = skb_push(skb, sizeof(struct ethhdr));
	ehdr = (struct ethhdr *)p;
	skb_reset_mac_header(skb);	

	assemble_ip_header(ihdr);
	assemble_udp_header(uhdr);
	assemble_eth_header(ehdr);

	dev_queue_xmit(skb);	

    return 0;
}

void __exit skb_package_exit(void)
{
	kfree_skb(skb);
    printk("skb exit\n");
}

module_init(skb_package_init);
module_exit(skb_package_exit);
MODULE_LICENSE("GPL");

        代码的功能是组一个UDP包并且通过网口br0发送出去。其中涉及到了skb的分配,预留数据包的空间,将协议头和实际负载数据放入到skb->data中,最后调用dev_queue_xmit将包发送出去的过程。

六. 总结

        本文总结了socket buffer的一些常用的操作函数,并介绍了它们的作用,其中包括skb分配,以及skb的指针的操作和skb的队列的操作,这些函数是编写网络设备驱动程序的基础,应该熟练掌握。

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

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

相关文章

XML 学习笔记 7:XSD

本文章内容参考自&#xff1a; W3school XSD 教程 Extensible Markup Language (XML) 1.0 (Second Edition) XML Schema 2001 XML Schema Part 2: Datatypes Second Edition 文章目录 1、XSD 是什么2、XSD 内置数据类型 - built-in datatypes2.1、基本数据类型 19 种2.1.1、基本…

从0到1自学网络安全(黑客)【附学习路线图+配套搭建资源】

前言 网络安全产业就像一个江湖&#xff0c;各色人等聚集。相对于欧美国家基础扎实&#xff08;懂加密、会防护、能挖洞、擅工程&#xff09;的众多名门正派&#xff0c;我国的人才更多的属于旁门左道&#xff08;很多白帽子可能会不服气&#xff09;&#xff0c;因此在未来的…

【STM32】小电流FOC驱控一体板(开源)

FOC驱控一体板 主控芯片stm32f103c8t6 驱动芯片drv8313 三相电流采样 根据B站一个UP主的改的&#xff08;【【自制】年轻人的第一块FOC驱动器】&#xff09;&#xff0c;大多数元器件是0805&#xff0c;实验室具备且便于自己动手焊接 。 晶振用的是无源晶振&#xff0c;体…

Bias Tee理论到实践

Bias Tees是将直流电压施加到必须传递RF/微波信号的任何组件的必不可少的组件&#xff0c;最常见的是需要DC电源的RF放大器。 对于窄带应用&#xff0c;bias tee设计和结构是简单的&#xff0c;只需要注意组件的自谐振频率。 然而&#xff0c;对于宽带应用&#xff0c;bias t…

基于瑞萨RA6M5的环境监测系统设计

基于瑞萨RA6M5的环境监测系统设计 1. 设计简介 本项目初步设计思路是打算以瑞萨单片机作为控制和数据处理的单元&#xff0c;使用温湿度&#xff0c;光照传感器去监测周围的环境参数&#xff0c;在屏幕上完成传感器数据和相关信息的显示。同时&#xff0c;使用WIFI无线模组与…

用于大型图像模型的 CNN 内核的最新内容

一、说明 由于OpenAI的ChatGPT的巨大成功引发了大语言模型的繁荣&#xff0c;许多人预见到大图像模型的下一个突破。在这个领域&#xff0c;可以提示视觉模型分析甚至生成图像和视频&#xff0c;其方式类似于我们目前提示 ChatGPT 的方式。 用于大型图像模型的最新深度学习方法…

Pytohn将matplotlib嵌入到tkinter中

文章目录 matplotlib窗口组成tkinter布局嵌入图像 matplotlib窗口组成 tkinter是Python标准库中自带的GUI工具&#xff0c;使用十分方便&#xff0c;如能将matplotlib嵌入到tkinter中&#xff0c;就可以做出相对专业的数据展示系统&#xff0c;很有竞争力。 在具体实现之前&a…

FTP使用教程

FTP使用教程 目录 一&#xff0e;FTP简介二&#xff0e;FTP搭建三&#xff0e;FTP使用 一&#xff0e;FTP简介 FTP中文为文件传输协议&#xff0c;简称为文传协议。它也是一个应用程序&#xff0c;不同的操作系统有不同的FTP应用程序&#xff0c;这些应用程序都遵守同一种协议以…

六寸相纸打印拼图 - opencv

准备自己打印一些照片&#xff0c;三寸相纸性价比低&#xff0c;只好买六寸来拼四张然后裁剪&#xff0c;不过并没有搜到提供了这个功能的工具&#xff0c;想想代码应该很简单&#xff0c;所以就造轮子了。可能其实有但是我没搜到。 轮子在这里&#xff1a; https://github.co…

ArraySetter

简介​ 用来展示属性类型为数组的 setter 展示​ 配置示例​ "setter": {"componentName": "ArraySetter","props": {"itemSetter": {"componentName": "ObjectSetter","props": {"c…

React 论文《ReAct: Synergizing Reasoning and Acting in Language Models》阅读笔记

文章目录 1. 简介论文摘要翻译动机和主要贡献 2. REACT : SYNERGIZING *RE*ASONING *ACT*ING3. KNOWLEDGE-INTENSIVE REASONING TASKS3.1 设置3.2 方法3.3 结果和观察 4. 决策任务5. 参考资料 1. 简介 论文摘要翻译 虽然大型语言模型&#xff08;LLM&#xff09;在自然语言理…

医疗实施-集成平台下门诊就诊流程详解

目录 集成平台下门诊就诊流程详解1.患者建档2. 门诊挂号3. 医生就诊4.处方开立5.费用收取、6、科室执行医嘱集成平台下门诊就诊流程详解 这篇文章是考虑医院使用了集成平台之后,门诊就诊流程详解。与我的文章《医疗实施-门诊就诊流程详解》的大致一样,供学有余力的人阅读。 …

图解java.util.concurrent并发包源码系列——深入理解ReentrantLock,看完可以吊打面试官

图解java.util.concurrent并发包源码系列——深入理解ReentrantLock&#xff0c;看完可以吊打面试官 ReentrantLock是什么&#xff0c;有什么作用ReentrantLock的使用ReentrantLock源码解析ReentrantLock#lock方法FairSync#tryAcquire方法NonfairSync#tryAcquire方法 Reentrant…

SpringBoot笔记:SpringBoot 集成 Dataway

文章目录 1、什么是 Dataway?2、主打场景3、技术架构4、整合SpringBoot4.1、maven 依赖4.2、初始化脚本4.3、整合 SpringBoot 5、Dataway 接口管理6、Mybatis 语法支持7、小结 1、什么是 Dataway? 官网地址&#xff1a;https://www.hasor.net/docs/guides/quickstart Da…

连通块是什么

刷题的时候遇到一个名词概念&#xff0c;连通块是什么&#xff1f; 在图论中&#xff0c;无向图中的连通块&#xff08;也叫作连通分量&#xff09;是指原图的一个子图&#xff08;即该子图只包含原图中的部分或全部顶点及边&#xff09;&#xff0c;该子图任意两个顶点都能通…

Swift 环境搭建

Swift是一门开源的编程语言&#xff0c;该语言用于开发OS X和iOS应用程序。 在正式开发应用程序前&#xff0c;我们需要搭建Swift开发环境&#xff0c;以便更好友好的使用各种开发工具和语言进行快速应用开发。由于Swift开发环境需要在OS X系统中运行&#xff0c;因此其环境的…

智能指针shared_ptr:自定义删除器

重点&#xff1a; 1.普通指针转化成智能指针。 2.智能指针创建的时候&#xff0c;第二个参数是自定义删除器&#xff0c;默认情况下&#xff0c;shared_ptr调用delete()函数。 class A { public:void Get() { cout << b << endl; }; private:int b{ 10 }; };clas…

Java 与其他编程语言:比较分析

Java 擅长可移植性和可靠性&#xff0c;Python 擅长通用性和简单性&#xff0c;JavaScript 擅长 Web 开发&#xff0c;C 擅长性能&#xff0c;Go 擅长效率。 在广阔的软件开发世界中&#xff0c;选择正确的编程语言对于任何项目的成功都至关重要。Java 是一种以其多功能性和可移…

交换机Vlan实验

介绍 Vlan表示虚拟局域网。 常见的网络安全技术 VlanACL Vlan的作用 Vlan隔离了广播域&#xff0c;增加了网络的安全性。 知识点 默认vlan vlan1 是默认vlan&#xff0c;主要机器开机了&#xff0c;默认所有的接口都属于Vlan1 交换机的接口模式 Access : 这个模式用来…

RK3588平台开发系列讲解(文件系统篇)什么是 VFS

文章目录 一、什么是 VFS二、VFS 数据结构2.1、超级块结构2.2、目录结构2.3、文件索引结点2.4、打开的文件2.5、四大对象结构的关系沉淀、分享、成长,让自己和他人都能有所收获!😄 📢 今天我们一起来瞧一瞧 Linux 是如何管理文件,也验证一下 Linux 那句口号:一切皆为文…