conntrack的extend机制以及扩展

ip_conntrack为无状态无连接的IP增加了一个流记录机制,你可以把任何和一个流相关的东西放进去,但是放在哪里呢?原则上ip_conntrack应该是一个可以无限扩展的东西,但事实上,内核的设计者或者说Netfilter的ip_conntrack设计者并没有给用户程序员留下任何可以扩展它的接口和机制,你只能用现有的连接跟踪机制,虽然可以通过nf_ct_extend_register接口注册一个nf_ct_ext_type,但是这个接口也只是内核源码树自己使用的,你无法从外部定义一个心的nf_ct_ext_type。

既然有一个nf_ct_extend_register,为何不让从外部定一个nf_ct_ext_type,然后注册进去呢?除了C语言定义的数据结构不能自己解释自己实现反序列化之外,我觉得这是ip_conntrack代码树的一个缺陷,但是也许是内核开发模式和应用开发模式的不同导致的。一般的,内核API非常不稳定,对于应用来讲,内核的系统调用接口尽可能保持稳定就可以了,至于内部则完全是一个黑盒子。再者,内核开发需要一定的功底,不仅仅是你熟悉一些编程技巧和熟读API文档就能搞得定的。以上的原因,或者说是我的个人猜测,导致了不允许对ip_conntrack的结构体本身进行任何修改,你能改的仅仅是实现逻辑。Linux内核怎么做到这种限制的呢?很简单啊,把相关的头文件放到内核开发的头文件目录就可以了,事实上,只要是那个目录的头文件,你就别指望修改任何结构体和声明,因为那些是要参与符号CRC计算的。

理解了ip_conntrack的extend实际上并不能扩展之后,下面就展示一下老湿是怎么扩展它的。首先看一下ip_conntrack的extend结构是怎么组织的。实际上,我觉得这种组织非常好,非常紧凑,不容易产生内存碎片,它之所以如此紧凑是因为所有的extend相关的内容在内存中全部都是连续的。连续的内存当然没有指针寻址不连续内存方便,但是使用偏移也是不错的,更大的优势是申请/拷贝/释放比较方面,不用考虑深拷贝浅拷贝的问题。类似的数据结构还有iptables的rule在内核中的表示。

连续内存的表示法的最大好处就是可以任意扩展,因为它完全是靠偏移来定位位置的,如果你知道C++和JAVA这类OO语言,就会知道,一个普通类的内存布局中,处在开头的总是基类,所谓的扩展正是处在紧接着基类的内存位置,从而构成了一个子类,当然这只是从实现上来讲的,这里不谈思想!当然这也不是一种有性生殖行为,因为有性生殖从来都不是连续的,你不能说孩子是一个爸爸(是爸爸的一部分...),但是在内存连续的世界里,我们可以说:

struct B {
      struct A a;
      ....
      char *privatedata
};

是一个struct A,毕竟我可以把struct B从a字段往后的字段切掉。知道了这一切之后,我就可以对任意Linux预定义好的所有的nf_ct_ext_type进行任意的扩展了!事实上,我只需要扩展一个指针!虽然我很崇尚连续内存的布局,但是在我还是比较喜欢灵活,毕竟现在的内存价格已经不贵了,干嘛那么在乎内存啊。仅仅扩展一个指针,你想把它解释成一个什么机构都行!我们以一幅图来做解释:

ip_conntrack的extend机制以及扩展

理解了上面的那个图,就明白下面的定义了,不需要改变nf_conn_counter的结构体,只改变它的大小即可,即:

struct nf_conn_counter {
    u_int64_t packets;
    u_int64_t bytes;
};

的大小不是sizeof(struct nf_conn_counter),而是sizeof(struct nf_conn_counter)+sizeof(char *),定义为:

struct nf_conn_counter {
    u_int64_t packets;
    u_int64_t bytes;
    char *p;
};

但是这样便改变了nf_conn_counter结构体的定义,但是考虑以上结构体, 在内存结构上,等价于:

struct my_st {
    struct nf_conn_counter nfc;
    char *p;
};

这样没有改变结构体的定义,结构体my事实上真的就“是一个”结构体nf_conn_counter,在这之后,需要修改的地方真的就不多了,仅仅是一个nf_ct_ext_type的len字段的修改。剩下的工作就是,在需要设置或者取出自己自定义的结构体的时候,按照下面这样:

struct my_st *ms = (struct my_st *)a_instance_of_struct_of_nf_conn_counter;

取出my_st的指针即可。接下来就可以访问my_st里面的p了,至于p是什么,自有作者解释。为何我不提倡连续内存而是用一个指针,关键是我觉得并不是每个conntrack都需要这个字段,为了节省内存只为需要的结构体分配内存,只能用指针。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:http://www.heiqu.com/b9085122356b710903b2a9d91a86aa22.html