作为ipchains的后继者,iptables具有更加优越的特性,良好的可扩展功能、更高的安全性以及更加紧凑、工整、规范的代码风格。
在 2.6 的内核中默认维护了三张表 ( 其实是四张,还有一个名为 raw 的表很少被用到,这里不对其进行分析介绍了 ) : filter 过滤表, nat 地址转换表和 mangle 数据包修改表,每张表各司其职。我们对这三张表做一下简要说明:
1filter filter表会在NF_IP_LOCAL_INNF_IP_FORWARDNF_IP_LOCAL_OUThook点注册钩子函数,也就是说所有配置到filer表中的规则只可能在这三个过滤点上进行设置。
2nat表
DNATSNAT和地址伪装等操作。用于修改数据包的源、目的地址。目前版本的内核中nat表监视四个hookNF_IP_PRE_ROUTINGNF_IP_LOCAL_IN/OUTNF_IP_POST_ROUTINGnatPREROUTINGPOSTROUTING 3mangle 该表主要用于对数据包的修改,诸如修改数据包的 TOS 、 TTL 等字段。同时该表还会对数据包打上一些特殊的标签以便结合 TC 等工具,实现诸如 Qos 等功能。该表监视所有的 hook 点。
在 Netfilter rule
- ipt_entry:标准匹配结构,主要包含数据包的源、目的IP,出、入接口和掩码等;
- ipt_entry_match:扩展匹配。一条rule规则可能有零个或多个ipt_entry_match结构;
- ipt_entry_target:一条rule规则有且仅有一个target动作。就是当所有的标准匹配和扩展匹配都符合之后才来执行该target。
上面这几个结构体的成员属性基本上已经做到了“见名知意”,而且内核源码也对它们做了充分的注解。这里只对最后一个属性elem做一下说明,其定义为unsigned char elems[0]。大家可能觉得有些奇怪,怎么定了一个大小为零的数组呢?而且有些面试官曾经就这样的定义还向面试者发问过呢。这种方式定义的数组叫柔性数组,又叫可变长数组。为了不至于冲淡本文主题,这里给出一个关于柔性数组的链接,这位大牛已经将的很清楚了,大家可以去拜读拜读:http://blog.csdn.net/supermegaboy/article/details/4854939C99 我们将看到内核中大量的在运用柔性数组,包括我们即将要介绍的这两个结构体:
这两个双胞胎兄弟一眼望去还以为它们是同一个东西,但事实并非如你所想的那样。其中ipt_entry_match{}表示防火墙规则的匹配部分;ipt_entry_target{}表示防火墙规则的动作处理部分。大家先忽略掉它们左边那条烦人的提示部分union,后面我会详细介绍的,现在请跟我一样尽情地无视它们吧。
ipt_match{}ipt_target{}IPipt_entry_match{}iprangeipp2pipt_match{}matchipt_match{}()ipt_match{}ipt_entry_match{}matchOK了,就这么简单。
ipt_target{}target()targettargettargetACCEPTDROP、REJECT等等之类的处理方式;扩展targetDNATSNAT等以模块形式存在的target了。对于标准的targetipt_target{}ipt_entry_target{}targetNULLtargetipt_target{}ipt_target{}target该函数必须向Netfilter框架返回IPT_CONTINUE、或者诸如NF_ACCEPT、NF_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。
n 标准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{}结构中的,我们在后面再来详细分析。
n 扩展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{} 的关系如下:koreyoshi_fly2014-11-03 13:50:19
分析的很透彻,文章写的生动有趣,我辈之榜样。ipt_entry_target结构图中kernel那部分是不是有点问题,应该是target吧。
wjlkoorey2582013-03-31 12:30:38
qxhgd:你对netfilter分析的很深刻,佩服!请教个问题,你文章里的图用什么软件画的呢?谢谢!
viso 2003,不客气。
回复 | 举报zhuimeng8662012-07-12 16:38:23
对target,其用户态和内核态target的统一是由struct ipt_entry_target承载,
从用户态来看其u.user.name字段:
<1>IPT_STANDARD_TARGET(值为””),就代表标准target。在根据其verdict区分是内建链还是自定义链;
<2> IPT_ERROR_TARGET,就代表error
<3>为其他,就是自定义目标,如果目标有参数,则参数位于data字段。
在内核态看其u.kernel.targe.target字段。
<1>为NULL,则是标准target,直接返回,或者跳转到自定义链;
<2>不为NULL,则是扩展target,调用target函数处理。Target函数所需的参数来自struct ipt_entry_target的data字段。
两者是在规则写入内核或读到用户空间时发生转换的。