Linux netfilter 学习笔记 之六 ip层netfilter的filter表的创建及其hook函数分析

时间:2022-01-02 18:24:44

基于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 3hook点,即该表只包含这个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];/*创建了3rule和一个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) 3rule和一个error占用的内存大小

repl->hook_entry[] 即设置3rule与一个error 对应于第一个rule的偏移量

repl->underflow[] 即设置3rule与一个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 即标准targetACCEPT

 

结合ipt_repleacipt_standardipt_error结构体的定义,我们知道在filter表初始化时需要创建NF_IP_LOCAL_INNF_IP_FORWARDNF_IP_LOCAL_OUT3条链,且为每条链创建一条targetNF_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_INNF_LOCAL_OUTNF_FORWAD,那我们也需要在这3hook点注册相应的hook函数。

b)而我们通过iptables添加的过滤规则又是保存在xt_table->private.entries中,显然要想数据包能够匹配filter表的某条规则,就需要遍历xt_table并对表中的每一条规则都执行matchtarget操作,即我们编写的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模块初始化了3nf_hook_ops结构,且这3nf_hook_ops挂载的hook点分别为 NF_IP_LOCAL_INNF_IP_LOCAL_OUTNF_IP_FORWARD,和我们的猜想的一样。

3hook回调函数分别为ipt_hookNF_IP_LOCAL_INNF_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]targetNF_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_entryipt_standardipt_standard_targetipt_entry_matchipt_entry_targetxt_tablext_matchxt_target)、表的注册、表中规则的遍历与规则的匹配等机制后,就很容易理解iptables filter表的注册与hook函数注册及hook函数的流程了。

 

由于nat表牵扯到连接跟踪,比filter表要复杂的多,下一节就开始分析连接跟踪,把连接跟踪分析完以后,再好好分析nat的功能。