`
hulianwang2014
  • 浏览: 692496 次
文章分类
社区版块
存档分类
最新评论
  • bcworld: 排版成这样,一点看的欲望都没有了
    jfinal

如何任意扩展sk_buff而不重新编译内核

 
阅读更多
玩Linux网络的同好一定希望可以任意定义网络处理逻辑的行为,可谓协议处理的高端定制,最显而易见的办法就是在结构体里面加一个字段,事实上Linux的一个入口流控补丁IMQ就是这么做的,它简单的修改了Linux内核的sk_buff结构体的定义,增加了一个字段,增加了一个IMQ使用的字段,然后重新编译了内核...
通过重新编译内核,总是能满足任何的需求,但是噩梦本身就是重新编译内核!我特别讨厌重新编译内核,因为它极其浪费宝贵且有限的时间,另外这种做法太不即插即用,场面太宏大。Linux内核代码什么时候能像面向对象语言那样可以任意扩展呢?是的,LKM可以,但是网络协议栈作为一个核心功能并不是通过LKM实现的,所以除了重新编译还是重新编译。
近期买了一本《JAVA编程思想》,一直纠结于这本书到底体现了什么思想,看得多了,便有了一点想法,对象,对象的思想是这本书的核心,注意,不是多态,而是对象本身。而对象的好处在于其可扩展,完全的可扩展。因此循着这个思想,我想做一个一劳永逸的工作,让sk_buff可扩展。
Linux内核时时刻刻考虑了结构体的扩展,因此许多的结构体都包含一个叫做private_data的字段,也可能叫做private或者priv等。事实上,几乎所有的C语言写就的框架都采用了类似的思想,比如file结构体:
struct file {
....
    /* needed for tty driver, and maybe others */
    void            *private_data;
....
};
更一般的,该字段作为结构体的最后一个字段存在,这已经很接近OO的思想了,但是却永远无法达到,因为,一个private字段是作为结构体内部的扩展,即对象的属性扩展,而不是类型本身的扩展。怎么说呢?如果我建立了一个对象,即:
struct file *f = ....;
f->private_data = ...;

看看我是对谁赋的值,是对f赋值,f是一个对象,而不是类型,在这个意义上,类型是什么?是struct file,我需要的是对file结构体的扩展。按照OO的思想,我应该这么做:
struct txtfile_extends_file {
       struct file f;
       void *private;
};
这么做和直接在file加入private_data相比,更能体现OO的思想。如果直接在file结构体加入private字段,那么任何人都可以对任何file实例的private字段进行替换和解释,只要它拿到file实例即可,但是如果扩展的是struct file类型,那么只有类型的定义者和理解类型定义的人才能对其进行操作。举例如下。如果直接在file结构体加入private,那么下面的赋值就是合法的:
f1->private = a;
...
f1->private = b;
这样一来,除非频繁加锁,否则一个file的扩展就是不稳定的,它可以在任何地点被重定义。反之,如果是对struct file的定义进行扩展的话,那么
struct txtfile1_extends_file {
       struct file f;
       void *private1;
};

struct txtfile2_extends_file {
       struct file f;
       void *private2;
};
就是两个类型,任何时候,通过txtfile1的对象引用private2字段都是非法的,当然上述情况下你完全可以通过强制类型转换达到目的,但是请注意,C语言本身对类型检查就不严格。你也可以通过内存操作来达到任何不可告人的目的,但是你要知道,计算机世界的任何事情都可以通过内存操作来进行,如果你为了炫技巧非要采用那么原始的操作,那也无妨。
理解了思想以后,我们就可以着手修改代码了,很简单,为了更加通用,我们只是希望扩展一个指针,指针真乃C语言的一项艺术,它可以将一维的内存扩展到多维,它可以指向任何东西。然而在分配skb的时候,并不知道该指针指向哪里,也不知道它的具体类型,所以只需要预先分配好内存即可,在内存不值钱的今天,多个几个字节又何妨(很多弊病都是从资源匮乏阶段继承下来的,比如勤俭节约),即:
void __init skb_init(void)
{
    skbuff_head_cache = kmem_cache_create("skbuff_head_cache",
                          sizeof(struct sk_buff) + sizeof(char *),
                          0,
                          SLAB_HWCACHE_ALIGN|SLAB_PANIC,
                          NULL);
    skbuff_fclone_cache = kmem_cache_create("skbuff_fclone_cache",
                        (2*(sizeof(struct sk_buff) + sizeof(char *))) +
                        sizeof(atomic_t),
                        0,
                        SLAB_HWCACHE_ALIGN|SLAB_PANIC,
                        NULL);
}
这样你就可以在其它代码不知情的情况下扩展skb了,比如定义以下结构体:
struct data_extends_skb {
       struct sk_buff skb;
       char *info;
};

在PREROUTING的地方调用:

struct data_extends_skb *des = (struct data_extends_skb *)skb;
strcpy(des->info, "abcdefg", 6);
然后可以在任何后续的地方将info取出来。但是这有一个问题,那就是如果出现第二个执行绪,它完全可以将info的赋值修改掉,即便它不明白结构体data_extends_skb的定义,也不知道info字段的具体名字,它如果这么做:
memcpy(skb + 1, 0, sizeof(char *));
也会取消掉代码作者的本意。当然,这在C语言中是没有办法的。
Linux协议栈传递skb的不合理性
Linux并不是完全都是合理的,它也有不合理的地方,如果想用OO的思想重构协议栈实现,那么它现有的框架将是很不合适的:
1.skb在分配以后就不能再次被分配重新定义
skb在数据包进入协议栈后只分配一次,从此以后直到它离开协议栈,仅仅靠移动它的数据指针来指示现在到了哪一层,在任何层的处理函数中,skb的结构体本身无法改变。这种想法实际上最初是为了效率而引入的,如果你看过《TCP/IP详解(第二卷 实现)》,你就会知道之前的UNIX mbuf完全不是用的这种方式,事实上,mbuf机制在每一层都要经过一次重定义,这显得效率很低,但是今天返璞归真的话,它正是体现了OO的思想。
Linux的skb只分配一次意味着你只能基于skb数据的指示路由skb,或者修改skb内部的字段的值,但是却不能改变skb本身,即你不能将这个现在的skb释放掉,然后再分配一个新的skb代替它,或者将老的skb的内容复制到新的skb中,然后在新的skb中加入新的东西。
2.Linux的Netfilter框架完全继承了Linux协议栈的处理方式
我们知道,NF_HOOK返回值就是协议栈的返回值,即一个int型的值,表示成功或者失败。但是何必呢?
#define NF_HOOK_THRESH(pf, hook, skb, indev, outdev, okfn, thresh)           \
({int __ret;                                       \
if ((__ret=nf_hook_thresh(pf, hook, (skb), indev, outdev, okfn, thresh, 1)) == 1)\
    __ret = (okfn)(skb);                               \
__ret;})
完全可以定义成:
#define NF_HOOK_THRESH(pf, hook, skb, indev, outdev, okfn, thresh)           \
({struct sk_buff  *__skb2, __ret;                                       \
if ((__skb2=nf_hook_thresh(pf, hook, (skb), indev, outdev, okfn, thresh, 1)) != NULL)\
    __ret = (okfn)(__skb2);                               \
__ret;})
....// nf_hook_thresh的修改
如此一来,就可以在Netfilter中实现skb的重定义(我还没有勇气对整个协议栈开刀,要改的地方太多太多了,还是只对Netfilter开刀吧),这是意义非凡的,迎合了OO思想的向上转型,即任何一个特殊的类的对象都可以转型为更加一般的其基类对象。看似操作的是sk_buff对象,实际上它只是一个基类对象。在Netfilter的hook中,你完全可以这样:

struct sub_extends_skb {
       struct sb_buff skb;
       type1 v1;
       type2 v2;
       type3 v3;
       ...
};

struct sub_extends_skb *sub_construct (struct sb_buff *skb)
{
       struct sub_extends_skb = _k_malloc_and_skb_copy(sizeof(sub_extends_skb), skb);
       ses->v1 = ...;
       ...
       return ses;
}
struct sub_extends_skb *ss;
if (...) {
       ss = construct(skb);
} else {
       ss = skb;
}
... // process
return ss;
只要可以修改skb的指针了,那就意味着可以重新定义skb的类型了,这也意味着扩展skb成了可能。 需要注意的是,等到需要确定一个skb到底是什么类型的时候,怎么办呢?OO语言内置的有类型检查,反射等机制,比如JAVA的instance of宏等,可在Linux内核这种底层C写就的代码中,如何来做呢?C语言没有高级OO语言的种种内置特性,这完全需要程序员自己来解决这个问题,比如你不能对一个原生的skb取v2的值,而这个是不能保证的,正如你可以写下以下的代码一样:
char *p = (char *)0;
*p = 'a';
...
越是高级的语言总是让人不关注语言以及计算机机制本身,而专注于自己的业务逻辑,但是以计算机为本业的系统程序员,或者网络安全系统程序员,却最好不要去用什么高级的语言,因为你的业务逻辑就是要搞定计算机或者网络所呈现的缤纷复杂,而不是和一群穿西服的人论战...这也是一个思想!

分享到:
评论

相关推荐

    [linux]sk_buff结构体详解

    linux内核sk_buff结构体详解,从sk_buff结构到流程详细讲解sk_buff结构体

    sk_buff结构小谈

    struct sk_buff 可能是linux网络代码中最重要的数据结构,它表示接受或发送数据包的包头信息,并包含很多成员变量供网络代码中的各子系统使用。

    sk_buff详解.pdf

    详细介绍linux内核网络模块中重要的结构体sk_buff

    linux下的sk_buff

    linux内核skb_buf相关操作介绍

    linux sk_buff结构详解

    linux著名的sk_buff结构详解 struct sk_buff可能是linux网络代码中最重要的数据结构,它表示接收或发送数据包的包头信息,并包含很多成员变量供网络代码中的各子系统使用。

    sk_buff 结构分析

    sk_buff 结构 是linux网络协议中重要的结构。

    Linux内核??与sk_buff有关重要数据结构

     1、先说一下sk_buff结构体  这个结构体是套接字的缓冲区,详细记录了一个数据包的组成,时间、网络设备、各层的首部及首部长度和数据的首尾指针。  下面是他的定义,挺长 struct sk_buff {  /* These ...

    Micrium_STM32F103ZE-SK_uCOS-II_官方版

    Micrium_STM32F103ZE-SK_uCOS-II_官方版

    sk_g.dat

    sk_g.dat

    sk_v.dat

    sk_v.dat

    动漫人物雕刻笔刷.zip

    内含动漫人物雕刻的十余款笔刷 MoveB.ZBP,MoveF.ZBP,SK_AirBrush.ZBP,SK_Carve.ZBP,SK_ClayFill.ZBP,SK_Cloth.ZBP,SK_FormSoft.ZBP,SK_Hair.ZBP,。。。

Global site tag (gtag.js) - Google Analytics