sk_buff封装和解封装网络数据包的过程详解

可以说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元数据。如下图所示:

sk_buff封装和解封装网络数据包的过程详解

2.初始定位(skb_reserve)

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

sk_buff封装和解封装网络数据包的过程详解

将应用层栈帧压入协议栈之后,就可以在写指针位置开始,往后连续写n字节的应用层数据了,一般而言,这些数据来自socket。
4.设置传输层头部

和应用层的操作类似,这次需要把传输层栈帧压入协议栈中,如下图所示:

sk_buff封装和解封装网络数据包的过程详解

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

这个就不说了,和上述的类似...如下图所示:

sk_buff封装和解封装网络数据包的过程详解

实际上,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
编程的益处。如果基于skb的实现来编程,你不得不针对所有的情况编写好几套实现,而以上错误的实现只是其中一种,而且还用错了场景!这是多么痛的领悟!
系统为何这么设计,即便这并不是你所关注的!为何呢?
#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还有更多的,相当多的细节,但是不能再一一描述了,因为那样就违背了本文一开始的初衷,即用最简单的方式揭露本质,如果一一描述了,那么本文将成为一个文档而非一篇感悟,时隔多年以后,相信自己也不会看下去的。
Linux各层协议的实现,内容就更加丰富了。不过最基本的,就是本文讲述的,你得知道数据是怎样塞到一个skb并封装成一个可以被网卡实际发送的数据包的。好了,基本就是这些。最后我来总结一下本文提到的几个接口:
alloc_skb:分配一个skb;
skb_reserver:写指针向后移动到一个位置p,确定为数据包尾部,自始,写指针开始从该位置前移封装数据包;
skb_push:写指针前移n,更新数据包长度,从它返回的位置可以写n个字节数据-即封装n字节的协议;
skb_put:写指针移动到数据包尾部,返回尾部指针,可以从此位置写n字节数据,同时更新尾指针和数据包长度;

点击复制链接 与好友分享!回本站首页
您对本文章有什么意见或着疑问吗?请到论坛讨论您的关注和建议是我们前行的参考和动力
上一篇:TCP:传输控制协议简单讲解(八)
下一篇:shell脚本学习
相关文章

付款过程请求-功能和技术信息

AIX启动过程分析

最近升级到了win7,升级过程记录一下

openssl升级到1.0.1g的过程

jboss之启动加载过程详解(三)

jboss之启动加载过程详解(二)

jboss之启动加载过程详解(-)

记一次痛苦的过程-------源码编译安装

Apachehttpserverlinux安装过程说明

DNS原理总结及其解析过程详解

图文推荐
sk_buff封装和解封装网络数据包的过程详解
SharePointFeature的
sk_buff封装和解封装网络数据包的过程详解
Jenkins的分布式构建
sk_buff封装和解封装网络数据包的过程详解
如何升级SHAREPOINT场
sk_buff封装和解封装网络数据包的过程详解
Make命令教程

分类:默认分类 时间:2012-01-06 人气:1
本文关键词:
分享到:

相关文章

  • 网卡驱动4-做一个与外界交互的虚拟网卡4(netif_receive_skb和非napi分析) 2012-07-30

    看了网上的一些对此函数的解析,有些比较旧了。我在这分析一下linux-3.0.8的代码。 netif_receive_skb()中有RPS,我们不看了,直接看__netif_receive_skb()。 static int __netif_receive_skb(struct sk_buff *skb) { struct packet_type *ptype, *pt_prev; rx_handler_func_t *rx_handler; struct net_device *orig_de

  • 快速穷举TCP连接欺骗攻击-利用SYN Cookies 2013-07-12

    摘要 TCP 利用 32比特的 Seq/Ack 序列号来确认每一个连接的可靠性. 此外, 这些32位的序列号还能保证服务器不会被会话劫持,伪造一个服务器发出的初始序列号(ISN) 是个难以实现的技术. 因为暴力破解的话需要穷举这个32比特的序列号,在一个千兆比特级别的网卡上也是很难实现的. 今天的文章将为大家带来是如何利用 TCP SYN Cookies (一项广泛使用的用来防止SYN-Flooding DOS攻击的防卫机制)来减少穷举ISN所需要花费的时间, SYN Cookies已经在Ubu

  • (一)洞悉linux下的Netfilter&iptables:什么是Netfilter? 2014-08-14

    本人研究linux的防火墙系统也有一段时间了,由于近来涉及到的工作比较纷杂,久而久之怕生熟了。趁有时间,好好把这方面的东西总结一番。一来是给自己做个沉淀,二来也欢迎这方面比较牛的前辈给小弟予以指点,共同学习,共同进步。 这里说明一下:本系列博文主要侧重于分析Netfilter的实现机制,原理和设计思想层面的东西,同时从用户态的iptables到内核态的Netfilter其交互过程和通信手段等。至于iptables的入门用法方面的东西,网上随便一搜罗就有一大堆,我这里不浪费笔墨了。 很多人在接触i

Copyright (C) quwantang.com, All Rights Reserved.

趣玩堂 版权所有 京ICP备15002868号

processed in 0.115 (s). 10 q(s)