Netfilter源码分析(9)

2020-05-27 00:00:00 函数 规则 自定义 匹配 动作

/*kendo 2006-4-2修正原来对target部份描述不完全的情况*/

filter表的内容,还剩下三个内容:
1、target的匹配;
2、每个模块的target/match等函数的实现;
3、内核与用户空间的交互;

这里,以target的匹配做为2005的的结尾吧(因为明天飞贵州,估计2005年是没有机会再发贴了)

注:这里说匹配,其实不太正确,因为前面match是条件,匹配条件是正常的,target是动作,应该用执行更准确些。

target的匹配
要理解target的匹配,还是需要先了解相关的数据结构。
与match相似,内核中每个target模块,通过一个struct ipt_target来实现:
/* Registration hooks for targets. */
struct ipt_target
{
        struct list_head list;                                /*target链,初始为NULL*/

        const char name[IPT_FUNCTION_MAXNAMELEN];        /*target名称*/

        /*target的模块函数,如果需要继续处理则返回IPT_CONTINUE(-1),否则返回NF_ACCEPT、NF_DROP等值,
        它的调用者根据它的返回值来判断如何处理它处理过的报文*/
        unsigned int (*target)(struct sk_buff **pskb,        
                               unsigned int hooknum,
                               const struct net_device *in,
                               const struct net_device *out,
                               const void *targinfo,
                               void *userdata);

        /* Called when user tries to insert an entry of this type:
           hook_mask is a bitmask of hooks from which it can be
           called. */
        /* 在使用本Match的规则注入表中之前调用,进行有效性检查,如果返回0,规则就不会加入iptables中 */
        int (*checkentry)(const char *tablename,
                          const struct ipt_entry *e,
                          void *targinfo,
                          unsigned int targinfosize,
                          unsigned int hook_mask);

        /* 在包含本Target的规则从表中删除时调用,与checkentry配合可用于动态内存分配和释放 */
        void (*destroy)(void *targinfo, unsigned int targinfosize);

        /* 表示当前Target是否为模块(NULL为否) */
        struct module *me;
};
这个结构样子与match的内核模块的描述几乎是一模一样了……

而内核及用户态中,具体地存储描述一个target,是通过一个struct ipt_entry_target来实现的:
struct ipt_entry_target
{
        union {
                struct {
                        u_int16_t target_size;

                        /* Used by userspace */
                        char name[IPT_FUNCTION_MAXNAMELEN];
                } user;
                struct {
                        u_int16_t target_size;

                        /* Used inside the kernel */
                        struct ipt_target *target;
                } kernel;

                /* Total length */
                u_int16_t target_size;
        } u;

        unsigned char data[0];
};
这个结构也与match一模一样,那么我们是不是就可以按照分析match的思路来分析match呢?“通过成员struct ipt_target *target;来与内核中注册的target的处理模块建立关连,再来调用u.kernel.target->target()函数进行匹配”???
先不急,Netfilter的target共分为三类:内建动作、扩展target和用户自定义链。而以上两个结构是不够的,它们只能描述基于扩展target的匹配函数,没有或可以讲至少没有显著地描述一个内建动作或者是用户自定义链!事实上,Netfilter描述一个完整的target,是通过以下结构来实现的:

struct ipt_standard_target
{
        struct ipt_entry_target target;                /*模块函数*/        
        int verdict;                                /*常数*/
};

其中成员verdict(判断、判决)表明用来针对内建动作(ACCEPT、DROP)或者是用户自定义链,如果是扩展的target,则通过其target成员去定位终的模块处理函数。

那么,问题又接踵而至了,如果内核中,模块也是以类似注册/维护双向链表的形式储备,那么在内核中匹配的时候如何来区分这三类target?

事实上,考虑到程序的通用性、扩展性,对于内建动作或者是用户自定义链,内核是采用了“假注册”的方式来处理(这个名字是偶私人取的,或许不正确),也就是说,把内建动作或者是用户自定义链和扩展的target采用一样的处册方式,只是这个注册,只是一个样子,不具备实质意义:
在标准模块初始化Ip_tables.c的init函数注册target 的时候,可以看到:
static int __init init(void)
{
        int ret;

        /* Noone else will be downing sem now, so we won't sleep */
        down(&ipt_mutex);
        list_append(&ipt_target, &ipt_standard_target);
        ……
其注册的成员ipt_standard_target表示“标准的target”,即前文所提到的内建动作和用户自定义链。我们来看看它的初始化值:
/* The built-in targets: standard (NULL) and error. */
static struct ipt_target ipt_standard_target
= { { NULL, NULL }, IPT_STANDARD_TARGET, NULL, NULL, NULL };

同样,它也是一个ipt_target结构,也就是说和其它扩展的target模块一样,但是,它的处理函数全为空的,如target函数指针。所以,匹配的时候,如果要匹分的话,可以判断此指针函数是否指向NULL即可。而至于在标准的target中区分内建动作还是用户自定议链,前面提到过,它们都是以struct ipt_standard_target结构的verdict成员描述。到时候来判断verdict的值就可以了。我们可以推断出这段匹配的算法应该大致如下:
do {
        ……        /*前面为匹配match部份,前几节已分析过了*/
        假设e为当前待匹配规则。
        t=get_current_target(e);        /*获取当前规则的当前target*/
        
        /*因为如果注册时,如果是标准target,则t->u.kernel.target->target==NULL*/
        if (!t->u.kernel.target->target)                /*如果是标准target*/
        {
                /*进入标准target的话,还要来区分究竟是内建的动作,还是用户自定链,前面分析
                struct ipt_standard_target时说过,它们都是以verdict成员来描述的,则*/
                if(判断verdict==内建动作)
                {
                        /*相应处理*/
                }
                else
                {
                        /*相应处理*/
                }
        }
        else
        {
                /*如果是扩展的target,调用target函数*/
                verdict = t->u.kernel.target->target();
        }
}while(……);

就是在规则的循环匹配中,先根据target函数指针的值判断target,如果是标准的target,再根据的值匹别是内建动作还是自定义链。
程序实际的代码与此已经很相似了,的区别在于程序在处理自定义链时有一些技巧。
回到struct ipt_standard_target的verdict成员上来,这是一个非常重要的东西。用户空间表示一个接受动作,使用ACCEPT,内核不用能这个字符串来匹配,!strcmp(target.name,"ACCEPT"),这样效率差了点,一个自然的想法是,用一些整形数来代替,就像我们平常用1来代替true,0来代替false一样。
链中还有一种规则的target,形如-j 自定义链名,内核中的规则,并没有直接的“链”的概念,是呈一维线性排例的,所以,需要跳转至自定义链时,就需要两个东东:
1、待跳转的链相对于这条-j 自定义链的偏移值;
2、回指针,跳完了,总要回来吧……并且,规则中-j RETURN这种规则,它同样也需要回指针;
对于一条默认链来讲:
back = get_entry(table_base, table->private->underflow[hook]);
初回指针是指向这条链的末尾处的。

OK,再回到偏移值的问题上来,内核仍然以verdict这个成员来描述这个偏移值,刚才说过用verdict来描述ACCEPT等这些内建动作,难道它们不会冲突,答案是否定的。内核约定:以负数来描述ACCEPT等内建动作,需要用到时,再取其正值就行了。
例如:
#define NF_ACCEPT 1                     /*内核中定义NF_ACCEPT这种动作,对应常数为1*/
当用户在iptables中输入是"ACCEPT"字符串时,将其转换成:
verdict=-NF_ACCEPT - 1
到了内核中,要用到NETFILTER的动作时,只需要反过来:
(unsigned)(-verdict) - 1;
就OK了。

也就是说,用:
v=target.verdict;
if(v<0)            /*内核默认动作*/
{
    if (v != IPT_RETURN)
   {
            return verdict = (unsigned)(-v) - 1;     /*是默认动作,且不为RETURN,直接返回*/
    }
    //以下代码处理RETURN的情况
   ……
}
else                   /*自定义链*/
{
}
就可以处理内建动作与自定义链或RETURN几种情况了。让我们来看内核中实际的这块的代码处理:

/*获取当前规则的target*/
t = ipt_get_target(e);
/*如果不存在target模块函数,那么target应为常数,如ACCEPT,DROP等,或是自定义链*/
if (!t->u.kernel.target->target)
{
        int v;

        v = ((struct ipt_standard_target *)t)->verdict;        /*取得target的verdict值*/
        /*
#define NF_DROP 0
#define NF_ACCEPT 1
#define NF_STOLEN 2
#define NF_QUEUE 3
#define NF_REPEAT 4
#define NF_MAX_VERDICT NF_REPEAT

#define IPT_CONTINUE 0xFFFFFFFF

#define IPT_RETURN (-NF_MAX_VERDICT - 1)
        */
        
        if (v < 0)                /*动作是默认内建的动作*/
        {
                /* Pop from stack? */
                if (v != IPT_RETURN)                /*如果不是Return,返回相应的动作*/
                {
                        verdict = (unsigned)(-v) - 1;
                        break;
                }
                /*back用来描述return 动作,或者是自定义链执行完了,若还需继续匹配,那它指向那条应继续匹配的规则,所以,这里用e=back来取得下一条待匹配的规则*/
                e = back;
                               /*重新设置back点*/
                back = get_entry(table_base, back->comefrom);
                continue;
        }
        /*v>=0的情况,v表示了一个偏移值——待跳转的自定义链相对于规则起始地址的偏移,即如果是自定义链,那么应该跳到哪条规则去继续执行匹配,这里这个判断的意思是,如果下一条跳转换规则刚好就是当前规则的下一条规则,那就不用跳了……,否则,将当前规则(形如-j 自定义链名这样的规则)的下一条规则设置为back点*/
        if (table_base + v!= (void *)e + e->next_offset)        /*当前链后还有规则*/
        {
                /* Save old back ptr in next entry */
                struct ipt_entry *next = (void *)e + e->next_offset;
                next->comefrom = (void *)back - table_base;
                /* set back pointer to next entry */
                back = next;
        }
        /*确定等匹配的下一条规则*/
        e = get_entry(table_base, v);
}
else                        /*如果存在target模块函数*/
{
        /* 如果需要继续处理则返回IPT_CONTINUE(-1),否则返回NF_ACCEPT、NF_DROP等值 */
        verdict = t->u.kernel.target->target(pskb,
                                hook,
                                in, out,
                                t->data,
                                userdata);

        /* Target might have changed stuff. */
        /*Target函数有可能已经改变了stuff,所以这里重新定位指针*/
        ip = (*pskb)->nh.iph;
        protohdr = (u_int32_t *)ip + ip->ihl;
        datalen = (*pskb)->len - ip->ihl * 4;
        
        /*如果返回的动作是继续检查下一条规则,则设置当前规则为下一条规则,继续循环,否则,
        就跳出循环,因为在ipt_do_table函数末尾有return verdict;表明,则将target函数决定的返回值返回给
        调用函数nf_iterate,由它来根据verdict决定数据包的命运*/
        if (verdict == IPT_CONTINUE)
                e = (void *)e + e->next_offset;
        else
                /* Verdict */
                break;
}  


文章来源CU社区:[原创]Netfilter源码分析-我来抛砖,望能引玉

相关文章