免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
楼主: 独孤九贱
打印 上一主题 下一主题

[原创]Netfilter源码分析-我来抛砖,望能引玉 [复制链接]

论坛徽章:
0
41 [报告]
发表于 2005-12-23 16:01 |只看该作者
千万不能修改代码去掉 ip_conntrack 功能
我虽然看不懂,但是我知道那个功能是必不可少的,因为 netfilter 是基于状态检测的,如果那个功能去掉了基本就没什么可用的了
新的内核如果打了 patch 之后会,载入 ip_conntrack 后会有 sysctl -a|grep estab 显示中那样的提示
你可以修改这个数(默认是保持一天),也可以增大 ip_conntrack_max 值,最好两者一起改

论坛徽章:
0
42 [报告]
发表于 2005-12-23 16:15 |只看该作者
原帖由 platinum 于 2005-12-23 16:01 发表
千万不能修改代码去掉 ip_conntrack 功能
我虽然看不懂,但是我知道那个功能是必不可少的,因为 netfilter 是基于状态检测的,如果那个功能去掉了基本就没什么可用的了
新的内核如果打了 patch 之后会,载入 ip ...


谢谢,被这个问题困扰好久了,我试试!

论坛徽章:
0
43 [报告]
发表于 2005-12-23 16:26 |只看该作者
代码方面还要多请九贱兄指点呢 ^_^

论坛徽章:
0
44 [报告]
发表于 2005-12-26 09:26 |只看该作者
/*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;
}

[ 本帖最后由 独孤九贱 于 2006-4-4 13:51 编辑 ]
rstevens 该用户已被删除
45 [报告]
发表于 2005-12-31 12:28 |只看该作者
提示: 作者被禁止或删除 内容自动屏蔽

论坛徽章:
0
46 [报告]
发表于 2006-01-08 18:16 |只看该作者
呵呵,内核的代码才精彩呢。

我只能看懂大概,一到加锁的细节就晕了!

论坛徽章:
0
47 [报告]
发表于 2006-01-08 18:39 |只看该作者
我认为,一个数据报在netfilter处理的过程中,一直是在softirq中进行的,因此,不可能被其他softirq打断,

如果在一个netfilter的动作,也即target中,直接调用dev_queue_xmit()来发送自己构造的数据报,会出现内存泄漏吗?

论坛徽章:
0
48 [报告]
发表于 2006-01-09 11:25 |只看该作者
原帖由 guotie 于 2006-1-8 18:39 发表
我认为,一个数据报在netfilter处理的过程中,一直是在softirq中进行的,因此,不可能被其他softirq打断,

如果在一个netfilter的动作,也即target中,直接调用dev_queue_xmit()来发送自己构造的数据报,会出现 ...


Why?不太同意你的说法
netfilter和softirq有关系吗???最多是受影响而已吧?
softirq更多的是和网卡收发数据有关系,而这个处理,从2.4.20有了较大的改变……使用了一个类似poll的动作……

论坛徽章:
0
49 [报告]
发表于 2006-02-22 10:13 |只看该作者
我有一处不明白。还望各位多指点:
规则的组织方式是struct ipt_ip + struct ipt_entry_match + struct ipt_entry_match + ... + struct ipt_entry_target;

而规则在初始化的时候(建立的时候),需要分配它的内存,而match和target都要储存在这个分配给规则的内存空间中,也就是储存在unsigned char elems[0];的elems地址开始之处,那么给规则分配内存的时候岂不是要分配(sizeof(struct ipt_ip + struct ipt_entry_match + struct ipt_entry_match + ... + struct ipt_entry_target))这么大么??
这样的话每一条规则不是只能储存一定数目的match吗?太大会超过规则的空间,内存溢出??
如果match很少,不是浪费资源吗??

如果我有理解不对的地方,各位多多包涵,耐心指正,本人十分感激。

论坛徽章:
0
50 [报告]
发表于 2006-02-22 12:24 |只看该作者
原帖由 wwwspirit 于 2006-2-22 10:13 发表
我有一处不明白。还望各位多指点:
规则的组织方式是struct ipt_ip + struct ipt_entry_match + struct ipt_entry_match + ... + struct ipt_entry_target;

而规则在初始化的时候(建立的时候),需要分 ...


想法是对了的……只是想得太过简单了……

我们以规则的添加为例来描述规则的构造,
看先iptables的源码的do_command()函数:
其中,分析一些标准的规则参数,如地址等等,存储于struct ipt_entry fw这个结构中,这个略去不表,重点在match和target.

每添加一个mathc,都对应一个-m,我们来看选项分析的case 'm':
                case 'm': {
……
                        m->m = fw_calloc(1, size);
……
                }
这样,就为每个match动态分析了一段地址!!target类似,略去不表。然后是调用每个match的parse函数分析,填充相应的值。

好,最后来看规则的整合了:
e = generate_entry(&fw, iptables_matches, target->t);

有是你所说的规则,fw是标准规则部份,每二个是match的全局结构变量,过会会遍历它,后一个是target:
只需看这个函数的前面开头部份就可以回答你的问题了:

static struct ipt_entry *
generate_entry(const struct ipt_entry *fw,
               struct iptables_match *matches,
               struct ipt_entry_target *target)
{
        unsigned int size;
        struct iptables_match *m;
        struct ipt_entry *e;

        size = sizeof(struct ipt_entry);
        for (m = matches; m; m = m->next) {
                if (!m->used)
                        continue;

                size += m->m->u.match_size;
        }

        e = fw_malloc(size + target->u.target_size);
……
其中size用来计算规是空间,其实就是分别计算三个部份的长度,然后一起分配,所以即不会浪费,也不会有溢出等等……

而到了内核中,只需把用户空间的内容直接拷贝到对应地址空间即可!!!
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP