如何任意扩展sk_buff而不重新编译内核_sk_buff 用map扩展字段

玩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';
...

越是高级的语言总是让人不关注语言以及计算机机制本身,而专注于自己的业务逻辑,但是以计算机为本业的系统程序员,或者网络安全系统程序员,却最好不要去用什么高级的语言,因为你的业务逻辑就是要搞定计算机或者网络所呈现的缤纷复杂,而不是和一群穿西服的人论战...这也是一个思想!

原文链接: https://blog.csdn.net/dog250/article/details/23604717

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍;

也有高质量的技术群,里面有嵌入式、搜广推等BAT大佬

    如何任意扩展sk_buff而不重新编译内核_sk_buff 用map扩展字段

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/408656

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年4月26日 上午10:59
下一篇 2023年4月26日 上午10:59

相关推荐