+ -
当前位置:首页 → 问答吧 → [原]Netfilter实现机制分析

[原]Netfilter实现机制分析

时间:2008-12-26

来源:互联网

                            Netfilter实现机制分析【原】
                                             By Minit <[email protected]>

                                                                                                          2008-12  

修改日志:

2009-1-14:重新修改了大部分图片,为了正确体现结构的存储方式,以前未仔细考虑的链表在这次修正了,大部分已改成双向链表。另外,第四节中关于钩子函数存储部分的某些语句做了修改。感谢网友Bigee的提醒。



1.        前言
Netfilter作为目前进行包过滤,连接跟踪,地址转换等的主要实现框架,了解其内部机制对于我们更好的利用Netfilter进行设计至关重要,因此本文通过阅读内核源码2.6.21.2,根据自身的分析总结出Netfilter的大致实现机制,由于自身水平有限,且相关的参考资料较少,因此其中的结论不能保证完全正确,如果在阅读本文的过程中发现了问题欢迎及时与作者联系。
2.        规则的存储与遍历机制
        规则的存储机制
在Netfilter中规则是顺序存储的,一条规则主要包括三个部分:ipt_entry、ipt_entry_matches、ipt_entry_target。ipt_entry_matches由多个ipt_entry_match组成,ipt_entry结构主要保存标准匹配的内容,ipt_entry_match结构主要保存扩展匹配的内容,ipt_entry_target结构主要保存规则的动作。在ipt_entry中还保存有与遍历规则相关的变量target_offset与next_offset,通过target_offset可以找到规则中动作部分ipt_entry_target的位置,通过next_offset可以找到下一条规则的位置。规则的存储如下图2-1所示。

2008-12-26 22:15

图2-1 规则的存储

     ipt_entry结构如下图2-2所示,其成员ip指向结构ipt_ip,该结构主要保存规则中标准匹配的内容(IP、mask、interface、proto等),target_offset的值等于ipt_entry的长度与ipt_entry_matches的长度之和,next_offset的值等于规则中三个部分的长度之和。通过target_offset与next_offset可以实现规则的遍历。
2009-01-14 21:53

图2-2 ipt_entry结构

        ipt_entry_match主要保存规则中扩展匹配内容(tos、ttl、time等),其是Netfilter中内核与用户态交互的关键数据结构,在其内核部分由一个函数指针指向一个ipt_match结构,该结构体中包含了对包做匹配的函数,是真正对包做匹配的地方。ipt_entry_target结构与ipt_entry_match结构很类似。
   
2008-12-26 22:15

图2-3 ipt_entry_match结构
              
2008-12-26 22:15

图2-4 ipt_entry_target结构
        
       规则的遍历机制
在Netfilter中,函数ipt_do_table()实现了规则的遍历,该函数根据传入的参数table和hook找到相应的规则起点,即第一个ipt_entry的位置,主要通过函数get_entry()实现。
private = table->private;
table_base = (void *)private->entries[smp_processor_id()];
e = get_entry(table_base, private->hook_entry[hook]);


        标准匹配是通过函数ip_packet_match()实现的,该函数主要对包的五元组信息进行匹配,扩展匹配则通过宏IPT_MATCH_ITERATE实现,该宏的定义为:

#define IPT_MATCH_ITERATE(e, fn, args...)    \
({                        \
    unsigned int __i;            \
    int __ret = 0;                \
    struct ipt_entry_match *__match;    \
                        \
    for (__i = sizeof(struct ipt_entry);    \
     __i < (e)->target_offset;        \
     __i += __match->u.match_size) {    \
        __match = (void *)(e) + __i;    \
                        \
        __ret = fn(__match , ## args);    \
        if (__ret != 0)            \
            break;            \
    }                    \
    __ret;                    \
})


        宏IPT_MATCH_ITERATE依次调用各个ipt_entry_match所指向的ipt_match中match()处理数据包,在for循环中使用了terget_offset位置变量查找match的位置。
        在对数据包进行了匹配后,接着需要进行相应的动作处理,通过函数ipt_get_target()获取规则动作ipt_entry_target的位置:
         

static __inline__ struct ipt_entry_target *
ipt_get_target(struct ipt_entry *e)
{
    return (void *)e + e->target_offset;
}


        如果还需要继续遍历下一条规则,则继续执行以下语句以找到下一条规则的开始位置:
         

e = (void *)e + e->next_offset;





3.        表、匹配、动作存储及管理机制
       表、匹配、动作的存储机制
规则中所使用到的match、target、table使用全局变量xt_af所指向的相应链表保存,这些链表是在对Netfilter进行初始化或匹配模块扩展时进行更新的,在初始化时,默认的表及动作则添加到相应的链表中。Netfilter实现了很好的扩展性,如需要对数据包的时间进行匹配,则在match的链表中需要首先增加time扩展匹配模块,在相应的规则中则通过指向该time模块所对应的函数match()以进行时间的匹配。xt_af是个一维数组,其按照协议族的不同分别存储,目前我们常用的协议族主要是AF_INET。

2009-01-14 21:53

图3-1 match,target,table的全局存储
        
match、target、table的全局存储如上图3-1所示,以下为各部分的详细的结构表示。当扩展一个匹配模块时,其会注册一个ipt_match结构到match链表中,该结构的主要变量值如下图所示,name表示扩展模块的名字,match()是该模块最主要的函数,其主要对数据包进行相应的比较,checkentry()主要对包进行相应的完整性检验,destroy()在对模块进行撤销时调用。如果需要自己新加一个扩展模块,则需要构造一个ipt_match结构并注册到相应的链表中。ipt_target的结构与ipt_match相似,其最主要的函数是target()。

2009-01-14 21:53

图3-2 ipt_match结构的存储
   
2009-01-14 21:53

图3-3 ipt_target结构的存储

table主要是用来对规则进行管理,通过table中的相应参数可以找到相应的规则所处的入口位置。
2009-01-14 21:53

图3-4 ipt_table结构的存储
   
       表、匹配、动作的管理机制
match、target、table的注册分别调用xt_register_match()、xt_register_target()、xt_register_table()实现,前两个注册函数很相似,xt_register_table()则稍微复杂些。撤销时则分别调用相应的unregister函数实现。xt_register_match()函数的定义如下(xt_match与ipt_match是一样的):
         
int
xt_register_match(struct xt_match *match)
{
    int ret, af = match->family;

    ret = mutex_lock_interruptible(&xt[af].mutex);
    if (ret != 0)
        return ret;

    list_add(&match->list, &xt[af].match);
    mutex_unlock(&xt[af].mutex);

    return ret;
}


xt_register_table()函数的定义如下(xt_table与ipt_table是一样的),因为一个xt_table结构中还指向另一结构xt_table_info,该结构主要描述表的相关信息,所以对表注册时需要对这两类结构体进行定义。

int xt_register_table(struct xt_table *table,
         struct xt_table_info *bootstrap,
         struct xt_table_info *newinfo)
{
    int ret;
    struct xt_table_info *private;
    struct xt_table *t;

    ret = mutex_lock_interruptible(&xt[table->af].mutex);
    if (ret != 0)
        return ret;

    /* Don't autoload: we'd eat our tail... */
    list_for_each_entry(t, &xt[table->af].tables, list) {
        if (strcmp(t->name, table->name) == 0) {
            ret = -EEXIST;
            goto unlock;
        }
    }

    /* Simplifies replace_table code. */
    table->private = bootstrap;
    rwlock_init(&table->lock);
    if (!xt_replace_table(table, 0, newinfo, &ret))
        goto unlock;

    private = table->private;
    duprintf("table->private->number = %u\n", private->number);

    /* save number of initial entries */
    private->initial_entries = private->number;

    list_add(&table->list, &xt[table->af].tables);

    ret = 0;
unlock:
    mutex_unlock(&xt[table->af].mutex);
    return ret;
}



4.        钩子函数的存储及管理机制
        钩子函数的存储机制
钩子函数由一个全局二维链表nf_hooks保存,其按照协议族归类存储,在每个协议族中,根据钩子点顺序排列,在钩子点内则根据钩子函数的优先级依次排列。钩子函数的存储图如下图4-1所示,链表中的每个元素都是指向结构体nf_hook_ops中的hook()函数的指针,nf_hook_ops实际存储了钩子函数的内容,其结构如图4-2所示。在相应的钩子点调用钩子函数时,则根据协议族和钩子点找到相应的链表入口,然后依次调用该链中的每一个钩子函数对数据包进行操作。

2009-01-14 21:53

图4-1 钩子函数的全局存储

2009-01-14 21:53


图4-2 钩子函数的链表
        钩子函数的管理机制
如果需要在相应的钩子点挂载钩子函数,则需要首先定义一个nf_hook_ops结构,在其中实现实际的钩子函数,再调用函数nf_register_hook()将该钩子函数注册到图4-1所示的二维链表中,nf_register_hook()函数的定义如下:
int nf_register_hook(struct nf_hook_ops *reg)
{
    struct list_head *i;
    int err;

    err = mutex_lock_interruptible(&nf_hook_mutex);
    if (err < 0)
        return err;
    list_for_each(i, &nf_hooks[reg->pf][reg->hooknum]) {
        if (reg->priority < ((struct nf_hook_ops *)i)->priority)
            break;
    }
    list_add_rcu(&reg->list, i->prev);
    mutex_unlock(&nf_hook_mutex);
    return 0;
}



5.        Netfilter的流程框架
      在Netfilter中的不同钩子点调用了不同的钩子函数,这些钩子函数的调用如图4-1所示,其调用的流程框架如下图5-1所示。


2009-01-11 00:02

图5-1 Netfilter中hook函数的调用流程

        Netfilter中默认表filter在建立时则在NF_IP_LOCAL_IN,NF_IP_FORWARD钩子点注册了钩子函数ipt_hook(),在NF_IP_LOCAL_OUT这个点注册了钩子函数ipt_local_out_hook(),两个钩子函数都会调用ipt_do_table()对相对应的表和钩子点的规则进行遍历。调用的流程如下图5-2所示。
2009-01-11 00:02

图5-2 Netfilter中规则的调用流程
6.        总结
以上只是简单分析了Netfilter的整体框架,主要描述了其中的实现机制。在这个机制上已经实现了很多功能,除了对基本的功能进行完善和改进外,还出现了很多新的扩展功能。如在此架构上实现的连接跟踪机制和NAT机制,以及结合连接跟踪机制与Netfilter框架实现的Layer7扩展匹配模块等。对此框架的了解,有助于我们更好的利用Netfilter框架实现我们的设计,鉴于自身水平有限,因此以上的分析不能保证全部正确。希望各位批评指正。

由于主要是为了描绘出整个Netfilter的框架,故对其中较细节的的内容有所忽略而未深入分析,如规则的另外一个动作ipt_standard_target,table表注册时的初始化等,但这并不影响对整个框架的了解。至于Netfilter在链路层的实现机制此处也并未分析,因为其实现较简单,且我们大部分是在网络层利用Netfilter架构。分析中也未涉及到用户态规则与内核态规则之间的关系,对于iptables如何操作内核中的规则并未介绍,以后有机会再做详细分析。



PS:因为之前画图自己的不仔细,导致最后两张流程图出现原则性的错误,现在我将主贴与附件都已经更正回来,对于此造成的问题深表歉意。

[ 本帖最后由 Minit 于 2009-1-14 21:55 编辑 ]

Linux-netfilter机制分析(20090114修改版).pdf (308.56 KB)

下载次数:1150

2009-01-14 21:53

作者: Minit   发布时间: 2008-12-26

顶,写得很不错。

作者: dreamice:   发布时间: 2008-12-26

这是我在CU发的第一帖,希望各位轻拍哈,

作者: scutan:   发布时间: 2008-12-26



QUOTE:
原帖由 Minit 于 2008-12-26 22:20 发表
这是我在CU发的第一帖,希望各位轻拍哈,



哈哈,欢迎常来。

作者: scutan   发布时间: 2008-12-26

顶起,收藏先,谢谢分享

作者: Minit   发布时间: 2008-12-26

写得好,真是好,而且内核版本比较新的,赞!

作者: scutan   发布时间: 2008-12-26



QUOTE:
原帖由 Minit 于 2008-12-26 22:20 发表
这是我在CU发的第一帖,希望各位轻拍哈,



第一帖就这么强,以后多多分享经验阿

作者: chenyx   发布时间: 2008-12-27

分析得太详细了!赞一个!

作者: dreamice   发布时间: 2008-12-27

嗯,谢谢支持。在CU其实也逛了两三年了,从中也获取了很多知识,以后一定会把自己从中学到的东西拿出来大家一起分享。

[ 本帖最后由 Minit 于 2008-12-27 10:23 编辑 ]

作者: dreamice   发布时间: 2008-12-27



QUOTE:
原帖由 platinum 于 2008-12-27 08:40 发表
分析得太详细了!赞一个!



分析得不恰当的地方,还请白金兄多多指点。

作者: platinum   发布时间: 2008-12-27

作为分析Netfilter框架的文章,本文分析的比较清楚。尤其是使用很多丰富的图表,让理解Netfilter的框架更加容易。

一个小的问题:在第5部分介绍框架中,提到:


QUOTE:
Netfilter中默认表filter在建立时则在NF_IP_PRE_ROUTING,NF_IP_FORWARD钩子点注册了钩子函数 ipt_hook(),在NF_IP_POST_ROUTING这个点注册了钩子函数ipt_local_out_hook(),两个钩子函数都会调用 ipt_do_table()对相对应的表和钩子点的规则进行遍历。调用的流程如下图5-2所示。



你说filter表注册的hook点是PRE_ROUTING和POST_ROUTING处。但据我分析2.4的代码和2.6.18.3的代码中,fitler表注册的hook点应该是LOCAL_IN,FORWARD和LOCAL_OUT这三个hook点。 请LZ核实一下。

作者: Minit   发布时间: 2008-12-27

难得一见的好贴,支持原创!

作者: Minit   发布时间: 2008-12-27



QUOTE:
原帖由 Godbach 于 2008-12-27 13:06 发表
作为分析Netfilter框架的文章,本文分析的比较清楚。尤其是使用很多丰富的图表,让理解Netfilter的框架更加容易。

一个小的问题:在第5部分介绍框架中,提到:


你说filter表注册的hook点是PRE_ROUTING和 ...




嗯,多谢提醒,我发现了这是我的失误,您说的是对的,可能是我昨晚在写的时候迷糊了,犯了这个低级错误,呵呵。
马上在文中修改回来。

作者: Godbach   发布时间: 2008-12-27

呵呵,这个问题我也发现了。但LZ这篇文章确实分析得图文并茂,CU难得的好文章

作者: springtty   发布时间: 2008-12-27



QUOTE:
呵呵,这个问题我也发现了。但LZ这篇文章确实分析得图文并茂,CU难得的好文章



确实是一篇好文章

作者: Minit   发布时间: 2008-12-27

好文章,加个精华。希望LZ以后有更多的好文章分享啊。

作者: dreamice   发布时间: 2008-12-27

好!
对于做防火墙的工程师来说很有价值!

作者: Godbach   发布时间: 2008-12-28

兄弟,把你的文档整个pdf下来,可能图看起来会更清晰一些
也方便收藏

作者: Godbach   发布时间: 2008-12-28

呵呵, 现在还看不懂,
大家都说好那一定很好,收藏了先

作者: linuxsrc   发布时间: 2008-12-28



QUOTE:
原帖由 dreamice 于 2008-12-28 20:53 发表
兄弟,把你的文档整个pdf下来,可能图看起来会更清晰一些
也方便收藏



恩,对一个图有点模糊。不过用颜色区分,还可以认出来。

作者: dreamice   发布时间: 2008-12-28

为了以后方便看 请允许我引用哈 谢谢

作者: _LoveLinux   发布时间: 2008-12-28

已在主贴的最后新增了PDF下载,希望能对大家有所帮助,也希望大家如果发现有问题的地方及时交流。

作者: Godbach   发布时间: 2008-12-28

周末又仔细研读了一下 Minit 兄的大作,深受启发
同时,也发现了文档中的一个小的低级错误
“target-offset” 应该是 “target_offset”

另外,我 Minit 兄是使用 PDFFactor 生成的 PDF 文档,由于未注册所以每页下面有一行提示,我来上传一个有 keymaker 的 PDFF

PDF_FactoryPro210chs.rar (1.9 MB)

下载次数:140

2008-12-29 09:22

作者: skyajlm   发布时间: 2008-12-29

一帮无私的同志啊,赞一个!

作者: Minit   发布时间: 2008-12-29

前几天用了一下TinyPDF,也不错,WIN下绿色的。

作者: platinum   发布时间: 2008-12-29



QUOTE:
原帖由 platinum 于 2008-12-29 09:22 发表
周末又仔细研读了一下 Minit 兄的大作,深受启发
同时,也发现了文档中的一个小的低级错误
“target-offset” 应该是 “target_offset”

另外,我 Minit 兄是使用 PDFFactor 生成的 PDF 文档,由于未注册所 ...




谢谢白金兄的提醒,我马上更改下。也谢谢您提供的软件,O(∩_∩)O

作者: dreamice   发布时间: 2008-12-29

好文章

作者: Godbach   发布时间: 2008-12-29

虽然看得我稀里糊涂!!但是还是帮楼主顶一个!辛苦了!

作者: Minit   发布时间: 2008-12-29

lz, 借你的宝地, 想问下一个非流程问题:

在iptable_filter中:

  1. static struct
  2. {
  3.         struct ipt_replace repl;
  4.         struct ipt_standard entries[3];
  5.         struct ipt_error term;
  6. } initial_table __initdata = {
  7.         .repl = {
  8.                 .name = "filter",
  9.                 .valid_hooks = FILTER_VALID_HOOKS,
  10.                 .num_entries = 4,
  11.                 .size = sizeof(struct ipt_standard) * 3 + sizeof(struct ipt_error),
  12.                 .hook_entry = {
  13.                         [NF_IP_LOCAL_IN] = 0,
  14.                         [NF_IP_FORWARD] = sizeof(struct ipt_standard),
  15.                         [NF_IP_LOCAL_OUT] = sizeof(struct ipt_standard) * 2,
  16.                 },
  17.                 .underflow = {
  18.                         [NF_IP_LOCAL_IN] = 0,
  19.                         [NF_IP_FORWARD] = sizeof(struct ipt_standard),
  20.                         [NF_IP_LOCAL_OUT] = sizeof(struct ipt_standard) * 2,
  21.                 },
  22.         },
  23.         .entries = {
  24.                 IPT_STANDARD_INIT(NF_ACCEPT),        /* LOCAL_IN */
  25.                 IPT_STANDARD_INIT(NF_ACCEPT),        /* FORWARD */
  26.                 IPT_STANDARD_INIT(NF_ACCEPT),        /* LOCAL_OUT */
  27.         },
  28.         .term = IPT_ERROR_INIT,                        /* ERROR */
  29. };
复制代码

而在使用这个结构的时候, 只用了结构中的initial_table.repl:

  1. static int __init iptable_filter_init(void)
  2. {
  3.         ...
  4.         ret = ipt_register_table(&packet_filter, &initial_table.repl);
  5.                 ...
  6. }
复制代码




那么另外两个结构
ipt_standard entries[3];
struct ipt_error term;
哪个地方使用了呢?

作者: duanjigang   发布时间: 2008-12-29



QUOTE:
ret = ipt_register_table(&packet_filter, &initial_table.repl);


看一下这个函数的具体实现,&initial_table.repl和&initial_table其实都是指向该结构体的开始

作者: songpure520   发布时间: 2008-12-29