(三)洞悉linux下的Netfilter&iptables:内核中的rule,match和target

作为ipchains的后继者,iptables具有更加优越的特性,良好的可扩展功能、更高的安全性以及更加紧凑、工整、规范的代码风格。

    在2.6的内核中默认维护了三张表(其实是四张,还有一个名为raw的表很少被用到,这里不对其进行分析介绍了):filter过滤表,nat地址转换表和mangle数据包修改表,每张表各司其职。

我们对这三张表做一下简要说明:

    1)、filter表

    该表是整个过滤系统中真正起“过滤”作用的地方。所有对数据包的过滤工作都在这个表里进行,也就是说用户如果需要对某种类型的数据包进行过滤拦截,那么最好在这个表中进行操作。filter表会在NF_IP_LOCAL_IN、NF_IP_FORWARD和NF_IP_LOCAL_OUT三个hook点注册钩子函数,也就是说所有配置到filer表中的规则只可能在这三个过滤点上进行设置。

    2)、nat表

    主要用于DNAT和SNAT和地址伪装等操作。用于修改数据包的源、目的地址。目前版本的内核中nat表监视四个hook点:NF_IP_PRE_ROUTING、NF_IP_LOCAL_IN/OUT、NF_IP_POST_ROUTING。但在真正的实际应用中,我们一般仅需要在nat表的PREROUTING和POSTROUTING点上注册钩子函数。该表有个特性:只有新连接的第一个数据包会经过这个表,随后该连接的所有数据包将按照第一个数据包的处理动作做同样的操作,这种特性是由连接跟踪机制来实现的。

    3)、mangle表

    该表主要用于对数据包的修改,诸如修改数据包的TOS、TTL等字段。同时该表还会对数据包打上一些特殊的标签以便结合TC等工具,实现诸如Qos等功能。该表监视所有的hook点。
    IT界有位大牛(具体是哪个我记不太清楚了)曾给程序下的定义是:程序=数据结构+算法。可见数据结构在整个程序设计过程中的重要性了。我本人也比较赞同这种说法。我们今天主要探究一下通过用户空间的iptables所配置到内核中的每条规则到底是个啥样子。

   在 Netfilter 中规则是顺序存储的,一条rule规则主要包括三个部分:

  • ipt_entry:标准匹配结构,主要包含数据包的源、目的IP,出、入接口和掩码等;
  • ipt_entry_match:扩展匹配。一条rule规则可能有零个或多个ipt_entry_match结构;
  • ipt_entry_target:一条rule规则有且仅有一个target动作。就是当所有的标准匹配和扩展匹配都符合之后才来执行该target。

   结构体struct ipt_entry{}的定义在include/linux/netfilter_ipv4/ip_tables.h文件里。其结构图如下:

 

    上面这几个结构体的成员属性基本上已经做到了“见名知意”,而且内核源码也对它们做了充分的注解。这里只对最后一个属性elem做一下说明,其定义为unsigned char elems[0]。大家可能觉得有些奇怪,怎么定了一个大小为零的数组呢?而且有些面试官曾经就这样的定义还向面试者发问过呢。这种方式定义的数组叫柔性数组,又叫可变长数组。为了不至于冲淡本文主题,这里给出一个关于柔性数组的链接,这位大牛已经将的很清楚了,大家可以去拜读拜读:http://blog.csdn.net/supermegaboy/article/details/4854939。更多详细的内容可以去研读C99标准。

       我们将看到内核中大量的在运用柔性数组,包括我们即将要介绍的这两个结构体:

    这两个双胞胎兄弟一眼望去还以为它们是同一个东西,但事实并非如你所想的那样。其中ipt_entry_match{}表示防火墙规则的匹配部分;ipt_entry_target{}表示防火墙规则的动作处理部分。大家先忽略掉它们左边那条烦人的提示部分union,后面我会详细介绍的,现在请跟我一样尽情地无视它们吧。

   这里我们还注意到,这两个家伙分别都拖了一条小尾巴:ipt_match{}和ipt_target{}。对于前面我们提到过的标准匹配,它只会去检查数据包的IP地址,源目的接口,掩码等通用信息,不会用到ipt_entry_match{}。对于以模块形式存在的扩展匹配,如iprange模块,ipp2p模块等,它们就得实现自己的ipt_match{}结构。也就是说,如果你要开发一个新的match模块,那么就必须去实例化一个ipt_match{}结构体对象,并实现该结构体中相应的成员属性(其实主要都是一些些函数指针成员,你必须实现相应的函数体),然后将该ipt_match{}对象挂在你的ipt_entry_match{}结构的match属性里就OK了,就这么简单。

    同样的,我们来说一下ipt_target{}结构体。对于target(我这里就不翻译了,那个叫“动作”的翻译太难听了,后面我都用英文表述)也分为标准target和扩展target。标准target就是那些ACCEPT、DROP、REJECT等等之类的处理方式;扩展target就是那些诸如DNAT、SNAT等以模块形式存在的target了。对于标准的target,它是不需要ipt_target{}结构的,即ipt_entry_target{}中的target属性为NULL;而对于我们自己扩展target是需要我们自己手工去实现ipt_target{}对象,并完成相关回调函数的编写。对于ipt_target{}结构体中target回调函数的编写有一点要注意:该函数必须向Netfilter框架返回IPT_CONTINUE、或者诸如NF_ACCEPTNF_DROP之类的值。开发细节我会在后续动手实践章节一一向大家说明。

    结构体ipt_entry_match{}定义在include/linux/netfilter/x_tables.h文件中。

    结构体ipt_entry_target{}也定义在include/linux/netfilter/x_tables.h文件中。

    结构体ipt_match{}定义在include/linux/netfilter/ip_tables.h文件中。

    结构体ipt_target{}也定义在include/linux/netfilter/ip_tables.h文件中。

匹配match:

    上面我们说过,match分为两种:基本match,又叫标准match;扩展match。

  • 标准match:

    标准匹配主要用于匹配由struct ipt_ip{}所定义的数据包的特征项。标准匹配的内核数据结构就是我们上面所看到的ipt_match{}定义在include/linux/netfilter/ip_tables.h。在所有的表中我们最后真正所用到的match结构为ipt_entry_match{},它和ipt_match{}的关系我也将其画出来了,如上所示。

    既然说到这里,那我就再啰嗦一点。对于ipt_entry_match{}的结构大家可能也留意到了它内部结构有个union成员,同时它还区分了user和kernel两种情况。我们刚刚在上面所讨论的ipt_match{}结构是内核中用来表示match的数据类型,在用户空间我们用的是iptables_match{}结构(定义在iptables.tar.gz源码包中的include/iptables.h头文件中)来表示match的。

    也就是说,内核空间和用户空间在注册和维护match时使用的是各自的match结构,ipt_match{}和iptables_match{},但是当某个具体的match被应用到防火墙规则里时,它们两个必须统一成ipt_entry_match{},这才是防火墙规则中真正用到的match结构。

至于iptables_match{}和ipt_match{}是如何完美地统一到ipt_entry_match{}结构中的,我们在后面再来详细分析。

  • 扩展match:

扩展match通常以插件或模块的形式存在。当我们在用户空间通过iptables命令设置规则时如果用到了-m  ‘name’ 参数时,那么此时‘name就是一个扩展匹配模块。前面我们也说过,如果你需要开发一个新的match模块,那么就必须去实例化一个ipt_match{}结构体对象,并实现其中的重要回调函数(如match()函数),最后通过xt_register_match()接口将你的ipt_match{}对象注册到Netfilter中去就可以了。实战篇我们讲解如何开发一个新match的全过程。

动作target:

       根据上面的两幅图我们可以看到ipt_entry_match{}和ipt_entry_target{}的结构基本如出一辙,那么它们也就存在着很多非常相似的地方了。在所有的表中关于target的使用,我们既可以用ipt_standard_target{}又可以用ipt_entry_target{},这是为什么呢?后面再解释。这两个结构体的关系如下图所示:

     怎么样很简单吧,就多了一个verdict变量而已。说了半天,那么target到底是用来干什么的呢?说白了,target主要用来处理:当某条规则中的所有match都被数据包匹配后该执行什么样的动作来处理这个报文,最后将处理后结果通过verdict值返回给Netfilter框架

    同样的target也分内核空间和用户空间两种结构。在内核空间中,我们所说的target由ipt_target{}表示,定义在include/linux/netfilter/ip_tables.h文件中;在用户空间中,所使用的是iptables_target{}结构,该结构定义在iptables源码包里的iptables.h头文件中。同样地,ipt_target{}iptables_target{}最后也完美地统一到了ipt_entry_target{}里。

iptables的-j参数后面即可跟诸如ACCEPT、DROP这些动作,也可以跟一条用户自定义链表的名字。我们都知道iptables中的规则链(chain)其实就是某个hook点上所有规则的集合。除了系统内建的链外,用户还可以创建自定义的新链。在iptables中,同一个链里的规则是顺序存放的。内建链的最后一条规则的target是链的policy策略,而用户自定义链中是没有policy这么一说。用户自定义链的最后一条规则的target是NF_RETURN,遍历过程将返回原来的链中。当然,规则中的target也可以指定跳转到某个用户创建的自定义链上,这时这条规则的target就是ipt_standard_target{}类型,并且这个target的verdict值大于0。如果在用户自定义链上没有找到任何匹配的规则的话,遍历过程将返回到原来调用这条用户自定链的链里去匹配下一条规则。

这里还需要注意一点:target也分为标准target扩展target,前面简单提过一些。它和标准的match以及扩展match还是有些区别:

标准的target,即ipt_standard_target{}里可以根据verdict的值再划分为内建的动作或者跳转到自定义链中。简单了说,标准的target就是内核内建的一些处理动作或其延伸。

扩展的target,则完全是由用户定义的处理动作。如果ipt_target.target()函数是空的,那就是标准target,因为它不需要用户再去提供新的target函数了;反之,如果有target函数那就是扩展的target。

    如果我们要开发自己的target,那么也只需要实例化一个ipt_target{}对象,并填充其内部相关的回调函数,然后调用xt_register_target()将其注册到Netfilter框架即可。

 

规则rule:

         其实每张table表中最后真正用于表示其内部所有规则的结构体是ipt_standard{},定义在include/linux/netfilter_ipv4/ip_tables.h文件中。最后在我们几张表里,如filter表,nat表里真正用的规则结构为ipt_standard{}。它和我们前面介绍的ipt_entry{}的关系如下:
    未完,待续...
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值