sk_buff封装和解封装网络数据包的过程详解
转自:http://www.2cto.com/os/201502/376226.html
可以说sk_buff结构体是Linux网络协议栈的核心中的核心,几乎所有的操作都是围绕sk_buff这个结构体进行的,它的重要性和BSD的mbuf类似(看过《TCP/IP详解 卷2》的都知道),那么sk_buff是什么呢?
网络分层模型这是一切的本质。网络被设计成分层的,所以网络的操作就可以称作一个“栈”,这就是网络协议栈的名称的由来。在具体的操作上,数据包最终形成的过程就是一层一层封装的过程,在栈上形成一段连续的数据,我们可以称作是一层一层的push操作。同样的,数据包的解封装的过程,则可以认为是一层一层的pop操作。
sk_buff的操作要想形成一个最终的数据包,即以太帧(不考虑其它的链路层)。要进行以下的操作:
1.分配一个skb结构体
可以看出基本的模式,即“定位/设置”两步骤操作,有点区别的是应用层操作,这是因为应用层的操作一般都是在socket接口之上完成的。但是既然本文讲述的是skb的通用操作,就不再区分这个了。
skb的核心操作在上面一小节,我们展示了skb的封装逻辑,但是具体到接口层面,就涉及到了skb的核心操作。
1.分配skb这个是由alloc_skb完成的,完成同一任务的接口形成一个接口族,但是alloc_skb是最基本的接口。
该alloc_skb接口完成两件事,即分配skb结构体以及skb数据包缓冲区,设置初始值。size参数表示skb的数据包缓冲区的大小,这个大小包括所有层的总和。如果该函数成功返回,那么就相当于你已经有了一个大小为size的空数据包缓冲区以及操作该数据包缓冲区的skb元数据。如下图所示:

2.初始定位(skb_reserve)
skb的逐层封装的关键在于写指针的定位,即这一层从哪个位置开始写。从协议封装的压栈形象来看,这个定位应该是顺序有规律的。初始定位十分重要,后面的定位就是例行公事了。初始定位当然是定位到应用层的末端,从这里开始,逐层将协议头push到skb的数据包缓冲区。初始定位图示如下:

3.拷贝应用层数据(skb_push/copy)
当skb分配好了之后,需要将协议“栈”的位置定位在数据包的“最低处”,这是初始定位,这样才可以把每一层的数据或者协议头push到栈上,这个操作由skb_reserve来完成。应用层数据已经在socket之上封装好了,那么就把skb的数据包缓冲区写指针定位到应用数据的开始处,此时的写指针在应用层缓冲区的末尾,因此需要使用skb_push操作将写指针定位到应用层开始处,这等于说压入了应用层栈帧。

将应用层栈帧压入协议栈之后,就可以在写指针位置开始,往后连续写n字节的应用层数据了,一般而言,这些数据来自socket。
4.设置传输层头部和应用层的操作类似,这次需要把传输层栈帧压入协议栈中,如下图所示:

接下来就可以愉快地在skb_push返回的位置设置传输层头部了,UDP,TCP,就看你对传输层的理解了。设置传输层头部其实就是在skb_push返回的位置开始写数据,写入的长度由skb_push的参数指定,即n。
5.设置IP层头部和应用层以及传输层操作类似,这次需要把IP层的栈帧压入协议栈中,如下图所示:

接下来就可以愉快地在skb_push返回的位置设置IP层头部了,如何设置,就看你对IP层的理解了。由于只是演示skb如何封装,因此没有涉及IP层相当重要的IP路由过程。
6.设置以太帧头部这个就不说了,和上述的类似...如下图所示:

到此为止,我封装了一个完整的以太帧,可以直接通过dev_queue_xmit发送的那种。一路下来,你会发现,skb数据包缓冲区以“压栈(push)”的方式逐渐被填充,每一层,都是通过skb_push接口压入一个栈帧,返回写指针,然后按照该层的协议逻辑从写指针开始写入栈帧长度的数据。
7.在应用数据后面追加PADDING目前为止,从最后的图示上可以看到,在skb数据包缓冲区中,还有两块区域没有使用,一个headroom,一个是tailroom,这些是干什么用的呢?作为一个练习的例子,由于存在某种对齐原则,在封装完成后,我需要在数据包的最后追加一些填充,或者说我需要在最前面加一个前导码,或者最常见的,我要在数据包的最后加一个纠错码,此时应该怎么办呢?
这个时候就需要headroom或者tailroom了,以在数据包最后追加数据为例,请看下图:

实际上,skb_put的操作就是,在数据包的末尾追加数据。至于说headroom如何使用,我就不多说了,其实还是skb_push,headroom有什么用呢?前导码,X over Y封装,不一而足。
实际的例子
下面我给出一个实际的例子,封装一个以太帧,然后发送出去:
skb = alloc_skb(1500, GFP_ATOMIC);
skb->dev = dev;
// 例行填充skb元数据 /* 保留skb区域 */
skb_reserve (skb, 2 + sizeof(struct ethhdr) +
sizeof(struct iphdr) +
sizeof(struct iphdr) +
sizeof(app_data)); /* 构造数据区 */
p = skb_push(skb, sizeof(app_data));
memcpy(p, &app_data[0], sizeof(app_data)); p = skb_push(skb, sizeof(struct udphdr));
udphdr = (struct udphdr *)p;
// 填充udphdr字段,略
skb_reset_transport_header(skb); /* 构造IP头 */
p = skb_push(skb, sizeof(struct iphdr));
iphdr = (struct iphdr*)p;
// 填充iphdr字段,略
skb_reset_network_header(skb); /* 构造以太头 */
p = skb_push(skb, sizeof(struct ethhdr));
ethhdr = (struct ethhdr*)p;
// 填充ethhdr字段,略
skb_reset_mac_header(skb); /* 发射 */
dev_queue_xmit(skb);
按照接口编码而不是按照实现编码这好像是Effective C++里面的一条,同样也适合于skb的操作场景。典型的就是“如何让skb记住IP层协议头,传输层协议头,mac头的位置”,接口是:
skb_reset_mac_header skb_reset_network_header skb_reset_transport_header 调用时机为skb_push返回的当时。曾几何时,我按照下面的方式设置了协议头的位置:
/* 构造IP头 */ p = skb_push(skb, sizeof(struct iphdr)); iphdr = (struct iphdr*)p; // 填充iphdr字段,略 //skb_reset_network_header(skb); skb->network_header = p; 有错吗?咋一看是没错的,但是却报错了:
protocol 0008 is buggy, dev eth2
#if BITS_PER_LONG > 32 #define NET_SKBUFF_DATA_USES_OFFSET 1 #endif #ifdef NET_SKBUFF_DATA_USES_OFFSET typedef unsigned int sk_buff_data_t; #else typedef unsigned char *sk_buff_data_t; #endif 节约空间之外,对于和大小相关的操作,接口实现也更加统一。这就是细节,而这些细节并不是玩网络协议栈的人所要关注的,不是吗?这完全是系统实现的层面,和业务逻辑是无关的。
为何未竟全功本文讲述到此为止。事实上,sk_buff还有更多的,相当多的细节,但是不能再一一描述了,因为那样就违背了本文一开始的初衷,即用最简单的方式揭露本质,如果一一描述了,那么本文将成为一个文档而非一篇感悟,时隔多年以后,相信自己也不会看下去的。
alloc_skb:分配一个skb;
skb_reserver:写指针向后移动到一个位置p,确定为数据包尾部,自始,写指针开始从该位置前移封装数据包;
skb_push:写指针前移n,更新数据包长度,从它返回的位置可以写n个字节数据-即封装n字节的协议;
skb_put:写指针移动到数据包尾部,返回尾部指针,可以从此位置写n字节数据,同时更新尾指针和数据包长度;
sk_buff封装和解封装网络数据包的过程详解的更多相关文章
- sk_buff封装和解封装网络数据包的过程详解(转载)
http://dog250.blog.51cto.com/2466061/1612791 可以说sk_buff结构体是Linux网络协议栈的核心中的核心,几乎所有的操作都是围绕sk_buff这个结构体 ...
- [转帖]IP /TCP协议及握手过程和数据包格式中级详解
IP /TCP协议及握手过程和数据包格式中级详解 https://www.toutiao.com/a6665292902458982926/ 写的挺好的 其实 一直没闹明白 网络好 广播地址 还有 网 ...
- Linux 中的网络数据包捕获
Linux 中的网络数据包捕获 Ashish Chaurasia, 工程师 简介: 本教程介绍了捕获和操纵数据包的不同机制.安全应用程序,如 VPN.防火墙和嗅探器,以及网络应用程序,如路由程序,都依 ...
- 网络数据包分析 网卡Offload
http://blog.nsfocus.net/network-packets-analysis-nic-offload/ 对于网络安全来说,网络传输数据包的捕获和分析是个基础工作,绿盟科技研 ...
- 捕获网络数据包并进行分析的开源库-WinPcap
什么是WinPcap WinPcap是一个基于Win32平台的,用于捕获网络数据包并进行分析的开源库. 大多数网络应用程序通过被广泛使用的操作系统元件来访问网络,比如sockets. 这是一种简单的 ...
- linux2.6.24内核源代码分析(2)——扒一扒网络数据包在链路层的流向路径之一
在2.6.24内核中链路层接收网络数据包出现了两种方法,第一种是传统方法,利用中断来接收网络数据包,适用于低速设备:第二种是New Api(简称NAPI)方法,利用了中断+轮询的方法来接收网络数据包, ...
- Linux内核中网络数据包的接收-第一部分 概念和框架
与网络数据包的发送不同,网络收包是异步的的.由于你不确定谁会在什么时候突然发一个网络包给你.因此这个网络收包逻辑事实上包括两件事:1.数据包到来后的通知2.收到通知并从数据包中获取数据这两件事发生在协 ...
- Linux内核网络数据包处理流程
Linux内核网络数据包处理流程 from kernel-4.9: 0. Linux内核网络数据包处理流程 - 网络硬件 网卡工作在物理层和数据链路层,主要由PHY/MAC芯片.Tx/Rx FIFO. ...
- 用C++实现网络编程---抓取网络数据包的实现方法
一般都熟悉sniffer这个工具,它可以捕捉流经本地网卡的所有数据包.抓取网络数据包进行分析有很多用处,如分析网络是否有网络病毒等异常数据,通信协议的分析(数据链路层协议.IP.UDP.TCP.甚至各 ...
随机推荐
- vue.js开发环境搭建
1.安装node.js(http://www.runoob.com/nodejs/nodejs-install-setup.html) 2.基于node.js,利用淘宝npm镜像安装相关依赖 在cmd ...
- 使用Microsoft Web Application Stress Tool对web进行压力测试
Web压力测试是目前比较流行的话题,利用Web压力测试可以有效地测试一些Web服务器的运行状态和响应时间等等,对于Web服务器的承受力测试是个非常好的手法.Web 压力测试通常是利用一些工具,例如微软 ...
- juery学习6——焦点事件
参考资料 深入理解javascript中的焦点管理:http://www.cnblogs.com/xiaohuochai/p/5874447.html
- 【Java】增强的for流程
增强for循环语法: for(type element: array) { System.out.println(element); } 可遍历输出数组元素,但无法获取元素下标. 相关链接 ...
- Alpha阶段项目总结
1.我们的软件要解决什么问题?是否定义得很清楚?是否对典型用户和典型场景有清晰的描述? 我们的软件是一款面向高校学生的简单快速的云笔记,不仅具有笔记的添加.修改.查看和删除功能,还有笔记公开功能,用户 ...
- Yii2 assets注册的css样式文件没有加载
准备引入layui.css文件的,在LayuiAssets类中已经配置了资源属性 <?php namespace frontend\assets; use yii\web\AssetBundle ...
- java.lang.ClassNotFoundException: com.mysql.jdbc.Driver解决办法
这个问题的原因是没有导入mysql连接库,我从官网上下载后照着网上的教程各种导入无果,最后发现是我导入的文件错了.... 官网上下下来的压缩文件是这个,不过这并不是直接要导入的文件,首先解压文件,然后 ...
- C#高级编程笔记2016年10月12日 运算符重载
1.运算符重载:运算符重重载的关键是在对象上不能总是只调用方法或属性,有时还需要做一些其他工作,例如,对数值进行相加.相乘或逻辑操作等.例如,语句if(a==b).对于类,这个语句在默认状态下会比较引 ...
- word20161222
T.120 tag / 标记 TAPI, Telephony API / 电话 API target journaling / 目标日志 taskbar / 任务栏 taskbar button / ...
- VB.NET操作Excel
VB.NET操作Excel的基本方法与例子: