基于linux2.6.21
今天主要在前两节的基础上,分析filter表的创建,以及filter表的hook回调函数的分析。
1. Filter模块初始化
在前面分析表的注册时,我们知道要注册一个新的xt_table,需要实例化xt_table与ipt_replace这两个结构体。创建filter表时同样需要这样做。下面是filter表实例化的这两个结构体
1.1 Filter表的初始化
1.1.1 xt_table filter表初始化
主要是设置创建的xt_table的表名,在哪些hook点起作用等
static struct ipt_table packet_filter = {
.name = "filter",
.valid_hooks = FILTER_VALID_HOOKS,/*仅支持NF_LOCAL_IN NF_LOCAL_OUT NF_FORWARD 3个hook点,即该表只包含这个3个内建链*/
.lock = RW_LOCK_UNLOCKED,
.me = THIS_MODULE,
.af = AF_INET,
};
1.1.2 ipt_replace repl
repl的初始化定义如下:
static struct
{
struct ipt_replace repl;
struct ipt_standard entries[3];/*创建了3个rule和一个error*/
struct ipt_error term;
} initial_table __initdata
= {
{ "filter", FILTER_VALID_HOOKS, 4,
sizeof(struct ipt_standard) * 3 + sizeof(struct ipt_error),
{ [NF_IP_LOCAL_IN] = 0,
[NF_IP_FORWARD] = sizeof(struct ipt_standard),
[NF_IP_LOCAL_OUT] = sizeof(struct ipt_standard) * 2 },
{ [NF_IP_LOCAL_IN] = 0,
[NF_IP_FORWARD] = sizeof(struct ipt_standard),
[NF_IP_LOCAL_OUT] = sizeof(struct ipt_standard) * 2 },
0, NULL, { } },
{
/* LOCAL_IN */
{ { { { 0 }, { 0 }, { 0 }, { 0 }, "", "", { 0 }, { 0 }, 0, 0, 0 },
0,
sizeof(struct ipt_entry),
sizeof(struct ipt_standard),
0, { 0, 0 }, { } },
{ { { { IPT_ALIGN(sizeof(struct ipt_standard_target)), "" } }, { } },
-NF_ACCEPT - 1 } },
/* FORWARD */
{ { { { 0 }, { 0 }, { 0 }, { 0 }, "", "", { 0 }, { 0 }, 0, 0, 0 },
0,
sizeof(struct ipt_entry),
sizeof(struct ipt_standard),
0, { 0, 0 }, { } },
{ { { { IPT_ALIGN(sizeof(struct ipt_standard_target)), "" } }, { } },
-NF_ACCEPT - 1 } },
/* LOCAL_OUT */
{ { { { 0 }, { 0 }, { 0 }, { 0 }, "", "", { 0 }, { 0 }, 0, 0, 0 },
0,
sizeof(struct ipt_entry),
sizeof(struct ipt_standard),
0, { 0, 0 }, { } },
{ { { { IPT_ALIGN(sizeof(struct ipt_standard_target)), "" } }, { } },
-NF_ACCEPT - 1 } }
},
/* ERROR */
{ { { { 0 }, { 0 }, { 0 }, { 0 }, "", "", { 0 }, { 0 }, 0, 0, 0 },
0,
sizeof(struct ipt_entry),
sizeof(struct ipt_error),
0, { 0, 0 }, { } },
{ { { { IPT_ALIGN(sizeof(struct ipt_error_target)), IPT_ERROR_TARGET } },
{ } },
"ERROR"
}
}
};
在分析这部分代码之前,需要说明一下,struct ipt_standard即为
ipt_entry+ipt_standard_target
下面我们就结合结构体的定义,分析下ipt_replace的初始化与第一条ipt_standard的初始化。
repl的初始化
repl->name "filter"
repl->vaild_hooks = FILTER_VALID_HOOKS
repl->num_entries= 4
repl->size = sizeof(struct ipt_standard) * 3 + sizeof(struct ipt_error) 即3个rule和一个error占用的内存大小
repl->hook_entry[] 即设置3个rule与一个error 对应于第一个rule的偏移量
repl->underflow[] 即设置3个rule与一个error 对应于第一个rule的偏移量
repl->num_countes = 0
repl->counters = NULL
repl->entries = {}
entries[3]的初始化中,我们只选取entries[0]进行分析,一个ipt_standard即为
ipt_entry+ipt_standard_target = ipt_entry+ipt_entry_target+verdict。
所以entries[0]的ipt_entry=
{ { { 0 }, { 0 }, { 0 }, { 0 }, "", "", { 0 }, { 0 }, 0, 0, 0 },
0,sizeof(struct ipt_entry),sizeof(struct ipt_standard),0, { 0, 0 }, { } }
即
ipt_entry.ip = { { 0 }, { 0 }, { 0 }, { 0 }, "", "", { 0 }, { 0 }, 0, 0, 0 }
ipt_entry.nfcache = 0
ipt_entry.target_offset = sizeof(struct ipt_entry)
ipt_entry.next_offset = sizeof(struct ipt_standard),
而entries[0]的ipt_standard_target等于
{ { { { IPT_ALIGN(sizeof(struct ipt_standard_target)), "" } }, { } },-NF_ACCEPT - 1 }
即ipt_entry_target = { { { IPT_ALIGN(sizeof(struct ipt_standard_target)), "" } }, { } }。从而可以得出pt_entry_target.u.user.size = IPT_ALIGN(sizeof(struct ipt_standard_target))
ipt_entry_target.u.user.name = ""
veridct = -NF_ACCEPT - 1 即标准target为ACCEPT。
结合ipt_repleac、ipt_standard、ipt_error结构体的定义,我们知道在filter表初始化时需要创建NF_IP_LOCAL_IN、NF_IP_FORWARD、NF_IP_LOCAL_OUT3条链,且为每条链创建一条target为NF_ACCEPT的默认规则和一条ipt_error规则。
在初始化了以上两个数据结构后,则调用ipt_register_table即可创建一个xt_table,并插入到链表xt_af[AF_INET].tables中
1.2 hook函数的注册
注册了一个xt_table后,只是为iptables提供了一个添加过滤规则的表而已,而我们的最终目的是在执行NF_HOOK时,能够对iptables添加的过滤规则进行过滤检查操作,从而决定数据包的去向,那很明显我们需要在相应的hook点中注册hook函数,这样才能对数据包进行过滤检查。
通过以上的信息,我们可以大致进行如下猜想:
a)由上面的表注册,我们知道filter表主要对NF_LOCAL_IN、NF_LOCAL_OUT、NF_FORWAD,那我们也需要在这3个hook点注册相应的hook函数。
b)而我们通过iptables添加的过滤规则又是保存在xt_table->private.entries中,显然要想数据包能够匹配filter表的某条规则,就需要遍历xt_table并对表中的每一条规则都执行match与target操作,即我们编写的hook函数需要满足上述要求,而在第四节的分析中,我们提到函数ipt_do_table也是实现这种功能的,那是不是说我们编写的filter表的hook函数可以调用ipt_do_table呢?
下面就分析filter表的hook函数注册相关的代码,来验证我们的猜想是否正确。
1.2.1 nf_hook_ops初始化
static struct nf_hook_ops ipt_ops[] = {
{
.hook = ipt_hook,
.owner = THIS_MODULE,
.pf = PF_INET,
.hooknum = NF_IP_LOCAL_IN,
.priority = NF_IP_PRI_FILTER,
},
{
.hook = ipt_hook,
.owner = THIS_MODULE,
.pf = PF_INET,
.hooknum = NF_IP_FORWARD,
.priority = NF_IP_PRI_FILTER,
},
{
.hook = ipt_local_out_hook,
.owner = THIS_MODULE,
.pf = PF_INET,
.hooknum = NF_IP_LOCAL_OUT,
.priority = NF_IP_PRI_FILTER,
},
};
在iptables_filter.c中,我们可以看到,filter模块初始化了3个nf_hook_ops结构,且这3个nf_hook_ops挂载的hook点分别为 NF_IP_LOCAL_IN、NF_IP_LOCAL_OUT、NF_IP_FORWARD,和我们的猜想的一样。
而3个hook回调函数分别为ipt_hook(NF_IP_LOCAL_IN与NF_IP_FORWARD点上的hook回调函数是相同的)、ipt_local_out_hook。那我们接下来分析下这两个函数,看它们的实现是怎样的额。
1.2.1.1ipt_hook
我们发现这个函数就是直接调用ipt_do_table,实现对数据包的过滤操作,看来我们的分析是正确的(关于ipt_do_table的详细分析,参看上一节的分析)。
主要是调用函数ipt_do_table,遍历该filter表的NF_LOCAL_IN链或者NF_LOCAL_FORWARD链的所有规则,对数据包进行规则检查,根据返回值确定对数据包的后续操作:
i)若是NF_ACCEPT,说明在filter表的hook检查通过了,接着对数据包进行其他的hook 检查。
ii)若是NF_DROP,则说明在filter表的hook检查时,检查的结果是丢掉数据包,则会 释放数据包的缓存,程序返回。
iii)若是NF_STOP,则后续不再进行hook检查,直接放行 。
iiii)若是NF_QUEUE,则发送到应用层进行相应的操作。
static unsigned int
ipt_hook(unsigned int hook,
struct sk_buff **pskb,
const struct net_device *in,
const struct net_device *out,
int (*okfn)(struct sk_buff *))
{
return ipt_do_table(pskb, hook, in, out, &packet_filter, NULL);
}
1.2.1.2 ipt_local_out_hook
这个函数相比于ipt_hook,增加了对数据包长度的判断,即若本机发送出去的数据包ip头部长度不全时,则不进行过滤操作,直接返回ACCEPT。否则则调用ipt_do_table对数据包进行过滤操作。
static unsigned int
ipt_local_out_hook(unsigned int hook,
struct sk_buff **pskb,
const struct net_device *in,
const struct net_device *out,
int (*okfn)(struct sk_buff *))
{
/* root is playing with raw sockets. */
if ((*pskb)->len < sizeof(struct iphdr)
|| (*pskb)->nh.iph->ihl * 4 < sizeof(struct iphdr)) {
if (net_ratelimit())
printk("ipt_hook: happy cracking.\n");
return NF_ACCEPT;
}
return ipt_do_table(pskb, hook, in, out, &packet_filter, NULL);
}
2 filter模块的初始化函数
该函数就是调用ipt_register_table注册filter表,以及调用 nf_register_hook注册上面定义的ipt_ops[]。也就是把我们上面介绍的表的注册于hook函数的注册放在了同一个函数里执行而已。
这个函数里多了一句initial_table.entries[1].target.verdict = -forward - 1;,而forward即为NF_ACCEPT,这句话的意思为设置entries[1]的target为NF_ACCEPT,而entries[1]的target初始化值已经是NF_ACCEPT了,所以我感觉这句代码目前是不需要的,可能以后增加NF_FORWARD时,就有必要了。。。
static int __init init(void)
{
int ret;
if (forward < 0 || forward > NF_MAX_VERDICT) {
printk("iptables forward must be 0 or 1\n");
return -EINVAL;
}
/* Entry 1 is the FORWARD hook */
initial_table.entries[1].target.verdict = -forward - 1;
/* Register table */
ret = ipt_register_table(&packet_filter, &initial_table.repl);
if (ret < 0)
return ret;
/* Register hooks */
ret = nf_register_hook(&ipt_ops[0]);
if (ret < 0)
goto cleanup_table;
ret = nf_register_hook(&ipt_ops[1]);
if (ret < 0)
goto cleanup_hook0;
ret = nf_register_hook(&ipt_ops[2]);
if (ret < 0)
goto cleanup_hook1;
return ret;
cleanup_hook1:
nf_unregister_hook(&ipt_ops[1]);
cleanup_hook0:
nf_unregister_hook(&ipt_ops[0]);
cleanup_table:
ipt_unregister_table(&packet_filter);
return ret;
}
在熟悉了hook机制、数据结构之间的关系(ipt_entry、ipt_standard、ipt_standard_target、ipt_entry_match、ipt_entry_target、xt_table、xt_match、xt_target)、表的注册、表中规则的遍历与规则的匹配等机制后,就很容易理解iptables 的filter表的注册与hook函数注册及hook函数的流程了。
由于nat表牵扯到连接跟踪,比filter表要复杂的多,下一节就开始分析连接跟踪,把连接跟踪分析完以后,再好好分析nat的功能。