1. 前言
iptables所制定的都是和内核中netfilter内的“表(table)”相联系的,缺省情况中已经自带了3个表: filter、nat和mangle,iptables命令中的“-t”参数就是用来指定该是属于哪个表。和匹配和目标一样,netfilter也提 供了模块化的表功能扩展,用户可以根据需要自己编写新的表。
以下内核代码版本为2.4.26。
2. 表、规则、hook点之间的相互关系
netfilter的架构的基本过滤原理是网络栈中定义5个hook点,在每个hook点进行检查,就是按优先级执行挂接在该点上的所有struct nf_hook_ops结构的处理函数,只有这些函数都返回NF_ACCEPT才表示该数据包允许通过该hook点。
每个表中都要定义一些起效的hook点,这些hook点就对应了iptables规则中的链,如
filter表的起效hook点是NF_IP_LOCAL_IN、
NF_IP_FORWARD和NF_IP_LOCAL_OUT,对应iptables规则filter表中的INPUT、FORWARD和OUTPUT
链,在每个合法hook点都定义一个struct nf_hook_ops结构,每个struct
nf_hook_ops结构都有一个基本处理函数,如filter表NF_IP_LOCAL_IN、NF_IP_FORWARD点的是ipt_hook
()函数,这些函数最终都会调用ipt_do_table()函数来进行该链中规则的遍历匹配操作,但要注意的是ipt_hook()等基本处理函数的参
数中并没有struct ipt_table结构的参数,所以目前这些表都是静态存在而不支持动态分配。
filter表在net/ipv4/netfilter/iptable_filter.c中定义,nat表在
net/ipv4/netfilter/ip_nat_rule.c中定义,mangle表在
net/ipv4/netfilter/iptable_mangle.c中定义.nat表和其他两个
稍有不同,其他两个表结构和hook_ops都在同一个文件中定义了,而nat的hook_ops是在net/ipv4/netfilter/ip_nat_standalone.c中定义的。
3. 数据结构
每个表都需要一个struct ipt_table结构来进行描述,建立一个新表就是要填写这样一个结构:
/* include/linux/netfilter_ipv4/ip_tables.h */
struct ipt_table
{
struct list_head list;
/* A unique name... */
char name[IPT_TABLE_MAXNAMELEN];
/* Seed table: copied in register_table */
struct ipt_replace *table;
/* What hooks you will enter on */
unsigned int valid_hooks;
/* Lock for the curtain */
rwlock_t lock;
/* Man behind the curtain... */
struct ipt_table_info *private;
/* Set this to THIS_MODULE if you are a module, otherwise NULL */
struct module *me;
};
结构中包括以下参数:
struct list_head list:这是将该结构挂接到table链表中
char name[]:表名称
struct ipt_replace *table:struct ipt_replace结构定义该表基本属性
unsigned int valid_hooks:该表合法的挂接点位置
rwlock_t lock:表操作时的读写锁
struct ipt_table_info *private:这实际上是表中规则项表的索引指针,初始化为NULL
struct module *me:指向模块本身,统计模块是否被使用
其中struct ipt_table_info结构定义为:
/* include/linux/netfilter_ipv4/ip_tables.h */
struct ipt_table_info
{
/* Size per table */
unsigned int size;
/* Number of entries: FIXME. --RR */
unsigned int number;
/* Initial number of entries. Needed for module usage count */
unsigned int initial_entries;
/* Entry points and underflows */
unsigned int hook_entry[NF_IP_NUMHOOKS];
unsigned int underflow[NF_IP_NUMHOOKS];
/* ipt_entry tables: one per CPU */
char entries[0] ____cacheline_aligned;
};
结构参数说明如下:
unsigned int size:表的大小
unsigned int number:规则数量
unsigned int initial_entries:初始化时的规则数
unsigned int hook_entry[NF_IP_NUMHOOKS]:各hook点起始规则的偏移
unsigned int underflow[NF_IP_NUMHOOKS]:各hook点结束规则的偏移
char entries[0]:实际的规则数组
其中struct ipt_replace结构定义为:
/* include/linux/netfilter_ipv4/ip_tables.h */
struct ipt_replace
{
/* Which table. */
char name[IPT_TABLE_MAXNAMELEN];
/* Which hook entry points are valid: bitmask. You can't
change this. */
unsigned int valid_hooks;
/* Number of entries */
unsigned int num_entries;
/* Total size of new entries */
unsigned int size;
/* Hook entry points. */
unsigned int hook_entry[NF_IP_NUMHOOKS];
/* Underflow points. */
unsigned int underflow[NF_IP_NUMHOOKS];
/* Information about old entries: */
/* Number of counters (must be equal to current number of entries). */
unsigned int num_counters;
/* The old entries' counters. */
struct ipt_counters *counters;
/* The entries (hang off end: not really an array). */
struct ipt_entry entries[0];
};
结构参数说明如下:
char name[IPT_TABLE_MAXNAMELEN]:表名称
unsigned int valid_hooks:有效hook点
unsigned int num_entries:规则数
unsigned int size:规则大小
unsigned int hook_entry[NF_IP_NUMHOOKS]:各hook点起始规则的偏移
unsigned int underflow[NF_IP_NUMHOOKS]:各hook点结束规则的偏移
unsigned int num_counters:计数器的数量,各规则都有一个
struct ipt_counters *counters:计数器
struct ipt_entry entries[0]:规则入口
规则的存储是相当于数组方式存储的,只是数组大小会动态修改,每个表的所有规则都形成一个数组,而且是按hook点的顺序存储,如对于
filter表,数组开始是INPUT链的规则,后面是FORWARD链规则,然后是OUTPUT链的规则,最后是处理错误的规则,这就是
iptables规则使用“-L -v -v”时看到的实际的规则存储方式。各个hook点的规则起始和结束规则是靠struct
ipt_table_info结构中的hook_entry[]和underflow[]来确定的。按数组方式实现在遍历处理时会比较快,但编辑时就比较
麻烦了,会伴随大量的内存重分配和拷贝操作。
在具体实现表时,在初始化ipt_table结构的struct ipt_replace结构参数时,除了struct
ipt_replace结构本身的参数外,还自动添加描述各条链的缺省动作的规则,也就是说制定iptables规则时用“-P”定义的链的缺省动作也是
靠规则来实现的,如对于filter表的定义如下:
/* net/ipv4/netfilter/iptable_filter.c */
struct ipt_standard
{
struct ipt_entry entry;
struct ipt_standard_target target;
};
struct ipt_error_target
{
struct ipt_entry_target target;
char errorname[IPT_FUNCTION_MAXNAMELEN];
};
struct ipt_error
{
struct ipt_entry entry;
struct ipt_error_target target;
};
static struct
{
struct ipt_replace repl;
struct ipt_standard entries[3]; // 3个hook点,各自一条缺省规则
struct ipt_error term; // 错误处理规则
} initial_table __initdata
4. 相关函数
表的处理函数相对比较少,使用函数 int ipt_register_table(struct ipt_table
*)来登记表使之生效,而使用void ipt_unregister_table(struct ipt_table
*)取消表的登记。在登记时,会初始化数据中的struct ipt_replace结构的数据转化到struct
ipt_table_info结构中。登记后用iptables规则就可以用“-t table_name”来配置新表中的规则了。
每个表通过ipt_do_table()函数(net/ipv4/netfilter/ip_tables.c)来进行表中的规则匹配最终确定对数据包的动作。
5. 结论
netfilter的表处理也可以模块化实现,新表的添加可以以当前系统自带的表为蓝本来编写,只是要确定好各个hook点处理函数的优先级来确定相对其他表的执行顺序。一般情况下不需要添加新表,但在某些特殊功能,如实现虚拟系统,多个表分开过滤会更方便处理一些。
后记:本文初稿在基本写完时突然掉电,没存,这是第二版了,内容少了一些。