免费注册 查看新帖 |

Chinaunix

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

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

论坛徽章:
0
11 [报告]
发表于 2005-12-16 16:14 |只看该作者
你们常说得 注册函数  或者 hook 这个是什么原理实现的呢?

普通程序怎么做?  我想到的是函数指针

给个简单的例子 ok?

论坛徽章:
0
12 [报告]
发表于 2005-12-16 16:34 |只看该作者
原帖由 benjiam 于 2005-12-16 16:14 发表
你们常说得 注册函数  或者 hook 这个是什么原理实现的呢?

普通程序怎么做?  我想到的是函数指针

给个简单的例子 ok?


方法是多种多样的,可以举几个例子:
1、Netfilter
#define NF_HOOK(pf, hook, skb, indev, outdev, okfn)                        \
(list_empty(&nf_hooks[(pf)][(hook)])                                        \
? (okfn)(skb)                                                                \
: nf_hook_slow((pf), (hook), (skb), (indev), (outdev), (okfn)))
#endif

先将钩子函数置于一个全局变量中,它是一个二维数组,当程序运行至某个节点,调用NF_HOOK,判断是否有注册的函数,有则转向执行注册的函数,再回来继续执行,否则,直接就继续运行了;

当然,这种要框架支持,即源码本身提供接口。


对于Windows来讲,系统也有一些Hook的接口,可以参考其相关API,另外两种常用的是:
1、Hook动态链接库
如dll,so(so没有试过,我想当然认为原理应是一样的),加载其,获得函数的空间,保存原来地址,然后把自己的函数挂上去,然后再……

2、Hook PE
也是利用PE的export table,找到要被Hook的地址,然后替换就OK了。

论坛徽章:
0
13 [报告]
发表于 2005-12-16 16:37 |只看该作者
原帖由 albcamus 于 2005-12-16 15:26 发表


白金兄习惯在什么系统下工作?
如果是linux,我用vim+ctags足够浏览整个kernel了。

ctags -R arch/i386 fs include/asm-i386 include/asm-generic include/asm-net ……
vim
:ts the-symbol
:tf ── ...

albcamus 兄,看来我和你们差的不是一清半点啊,我一定要赶上
如果问了一些白痴问题的话,千万不要怪罪

论坛徽章:
0
14 [报告]
发表于 2005-12-16 16:39 |只看该作者
原帖由 独孤九贱 于 2005-12-16 15:22 发表


我是先找结构中重要的成员拿下来(网上找资料,源码中本来的注释),剩下的就在读代码的时候,再明白一些,最后个别的,到现在也没有明白。

至于不好找的问题,应该不会吧,SourceInsight很好跟的。

我也下了一个 SourceInsight,还是中文的,确实感觉不错
如果一同读入了 .h 文件的话,还可以在另一个窗口里把那个 struct 的定义显示出来,和很方便
但是,我如何能一次载入程序里涉及到的所有 .h 文件呢?

论坛徽章:
0
15 [报告]
发表于 2005-12-16 16:51 |只看该作者
先将钩子函数置于一个全局变量中,它是一个二维数组,当程序运行至某个节点,调用NF_HOOK,判断是否有注册的函数,有则转向执行注册的函数,再回来继续执行,否则,直接就继续运行了;

当然,这种要框架支持,即源码本身提供接口。


你指的是源码实现了的 。  我如何去注册吧

我现在是指  我自己如何写一个c 代码  提供接口给别人注册。
这个和操作系统有关吧  unix 高级编程里面 没看到累世的概念

论坛徽章:
0
16 [报告]
发表于 2005-12-16 17:11 |只看该作者
原帖由 platinum 于 2005-12-16 16:37 发表

albcamus 兄,看来我和你们差的不是一清半点啊,我一定要赶上
如果问了一些白痴问题的话,千万不要怪罪


千万别这么说, 大家互有短长, 相互学习而已。 再说,好多时候“高水平”就是个海市蜃楼,刚入门时觉得好多高人呀,慢慢扎实的学习过程中,很快就会发现原来一色的高人也分出个三六九等,自己可以超越他们:本来就是这样啊。

Solaris12兄对我说:光环效应谁到了那个时候都会有。 其实那就是没看清楚时的错觉.

(p.s. 聚会你去吗?去的话我也去认识认识

论坛徽章:
0
17 [报告]
发表于 2005-12-16 17:13 |只看该作者
原帖由 benjiam 于 2005-12-16 16:51 发表
先将钩子函数置于一个全局变量中,它是一个二维数组,当程序运行至某个节点,调用NF_HOOK,判断是否有注册的函数,有则转向执行注册的函数,再回来继续执行,否则,直接就继续运行了;

当然,这种要框架支持, ...


如果可能,你可以直接借鉴这种框架结构,上面贴子中的源码分析事实上已经揭示了其全过程。

如果你在Win下边,直接让他来Hook你的dll/sys/exe……

论坛徽章:
0
18 [报告]
发表于 2005-12-17 00:50 |只看该作者
大家可以根据自己的体会随便谈谈
netfilter这个东西,有什么不足之处?
批判的学习才有更大的收获呀!

论坛徽章:
0
19 [报告]
发表于 2005-12-19 17:53 |只看该作者
原帖由 caibird3rd 于 2005-12-17 00:50 发表
大家可以根据自己的体会随便谈谈
netfilter这个东西,有什么不足之处?
批判的学习才有更大的收获呀!


我写出来等着大家来批判,来继续写下去,来指正我的许多错误,不过好像没有人响应,牛人们都对这个没有兴趣……?
哎,那我就接着写吧。

前面说到,对于filter表来说,所有的一切,要靠ipt_do_table函数来进行包配备,前面提过,过滤规则分为三部份:标准mathc,扩展match,target。可以预想一想,ipt_do_table就是要针对这三部份来过滤,来看一下该函数:



/* Returns one of the generic firewall policies, like NF_ACCEPT. */
unsigned int
ipt_do_table(struct sk_buff **pskb,
             unsigned int hook,
             const struct net_device *in,
             const struct net_device *out,
             struct ipt_table *table,
             void *userdata)
{
        static const char nulldevname[IFNAMSIZ] = { 0 };
        u_int16_t offset;
        struct iphdr *ip;
        void *protohdr;
        u_int16_t datalen;
        int hotdrop = 0;
        /* Initializing verdict to NF_DROP keeps gcc happy. */
        unsigned int verdict = NF_DROP;
        const char *indev, *outdev;
        void *table_base;
        struct ipt_entry *e, *back;

        /* Initialization */
        ip = (*pskb)->nh.iph;
        protohdr = (u_int32_t *)ip + ip->ihl;
        datalen = (*pskb)->len - ip->ihl * 4;
        indev = in ? in->name : nulldevname;
        outdev = out ? out->name : nulldevname;
        /* We handle fragments by dealing with the first fragment as
         * if it was a normal packet.  All other fragments are treated
         * normally, except that they will NEVER match rules that ask
         * things we don't know, ie. tcp syn flag or ports).  If the
         * rule is also a fragment-specific rule, non-fragments won't
         * match it. */
        offset = ntohs(ip->frag_off) & IP_OFFSET;

        read_lock_bh(&table->lock);
        IP_NF_ASSERT(table->valid_hooks & (1 << hook));
        table_base = (void *)table->private->entries
                + TABLE_OFFSET(table->private,
                               cpu_number_map(smp_processor_id()));
        e = get_entry(table_base, table->private->hook_entry[hook]);

#ifdef CONFIG_NETFILTER_DEBUG
        /* Check noone else using our table */
        if (((struct ipt_entry *)table_base)->comefrom != 0xdead57ac
            && ((struct ipt_entry *)table_base)->comefrom != 0xeeeeeeec) {
                printk("ASSERT: CPU #%u, %s comefrom(%p) = %X\n",
                       smp_processor_id(),
                       table->name,
                       &((struct ipt_entry *)table_base)->comefrom,
                       ((struct ipt_entry *)table_base)->comefrom);
        }
        ((struct ipt_entry *)table_base)->comefrom = 0x57acc001;
#endif

        /* For return from builtin chain */
        back = get_entry(table_base, table->private->underflow[hook]);

        do {
                IP_NF_ASSERT(e);
                IP_NF_ASSERT(back);
                (*pskb)->nfcache |= e->nfcache;
                if (ip_packet_match(ip, indev, outdev, &e->ip, offset)) {
                        struct ipt_entry_target *t;

                        if (IPT_MATCH_ITERATE(e, do_match,
                                              *pskb, in, out,
                                              offset, protohdr,
                                              datalen, &hotdrop) != 0)
                                goto no_match;

                        ADD_COUNTER(e->counters, ntohs(ip->tot_len), 1);

                        t = ipt_get_target(e);
                        IP_NF_ASSERT(t->u.kernel.target);
                        /* Standard target? */
                        if (!t->u.kernel.target->target) {
                                int v;

                                v = ((struct ipt_standard_target *)t)->verdict;
                                if (v < 0) {
                                        /* Pop from stack? */
                                        if (v != IPT_RETURN) {
                                                verdict = (unsigned)(-v) - 1;
                                                break;
                                        }
                                        e = back;
                                        back = get_entry(table_base,
                                                         back->comefrom);
                                        continue;
                                }
                                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 {
                                /* Targets which reenter must return
                                   abs. verdicts */
#ifdef CONFIG_NETFILTER_DEBUG
                                ((struct ipt_entry *)table_base)->comefrom
                                        = 0xeeeeeeec;
#endif
                                verdict = t->u.kernel.target->target(pskb,
                                                                     hook,
                                                                     in, out,
                                                                     t->data,
                                                                     userdata);

#ifdef CONFIG_NETFILTER_DEBUG
                                if (((struct ipt_entry *)table_base)->comefrom
                                    != 0xeeeeeeec
                                    && verdict == IPT_CONTINUE) {
                                        printk("Target %s reentered!\n",
                                               t->u.kernel.target->name);
                                        verdict = NF_DROP;
                                }
                                ((struct ipt_entry *)table_base)->comefrom
                                        = 0x57acc001;
#endif
                                /* Target might have changed stuff. */
                                ip = (*pskb)->nh.iph;
                                protohdr = (u_int32_t *)ip + ip->ihl;
                                datalen = (*pskb)->len - ip->ihl * 4;

                                if (verdict == IPT_CONTINUE)
                                        e = (void *)e + e->next_offset;
                                else
                                        /* Verdict */
                                        break;
                        }
                } else {

                no_match:
                        e = (void *)e + e->next_offset;
                }
        } while (!hotdrop);

#ifdef CONFIG_NETFILTER_DEBUG
        ((struct ipt_entry *)table_base)->comefrom = 0xdead57ac;
#endif
        read_unlock_bh(&table->lock);

#ifdef DEBUG_ALLOW_ALL
        return NF_ACCEPT;
#else
        if (hotdrop)
                return NF_DROP;
        else return verdict;
#endif
}

再来一句句看这个函数吧:
先是把该取的值取出来:


ip = (*pskb)->nh.iph;                /*获取IP头*/
protohdr = (u_int32_t *)ip + ip->ihl;                /*跳过IP头,搞不明白,为什么不用( u_int8_t * )ip + ip->ihl << 2^o^*/
datalen = (*pskb)->len - ip->ihl * 4;                /*指向数据区*/
indev = in ? in->name : nulldevname;                /*取得输入设备名*/
outdev = out ? out->name : nulldevname;                /*取得输出设备名*/

offset = ntohs(ip->frag_off) & IP_OFFSET;        /*设置分片包的偏移*/

read_lock_bh(&table->lock);                        /*设置互斥锁*/
IP_NF_ASSERT(table->valid_hooks & (1 << hook));        /*检验HOOK,debug用的*/

/*获取当前表的当前CPU的规则入口*/
table_base = (void *)table->private->entries
                + TABLE_OFFSET(table->private,
                               cpu_number_map(smp_processor_id()));

/*获得当前表的当前Hook的规则的起始偏移量*/                               
e = get_entry(table_base, table->private->hook_entry[hook]);

/*获得当前表的当前Hook的规则的上限偏移量*/
back = get_entry(table_base, table->private->underflow[hook]);               


然后进行规则的匹配,是在一个do...while中实现的:do {
        (*pskb)->nfcache |= e->nfcache;
       
        /*如果IP包匹配,就断续匹配下去,否则就跳到下一条规则*/
        if (ip_packet_match(ip, indev, outdev, &e->ip, offset))
        {
        }
        else
        {
        no_match:
                e = (void *)e + e->next_offset;
        }
}while (!hotdrop);

标准的match匹配,即struct ipt_ip这部份,是通过调用函数ip_packet_match来实现的;
当 ip_packet_match匹配,则继续匹配下去,否则就跳到下一条规则(e = (void *)e + e->next_offset;)


ip_packet_match放到一边,把来看看后面的情况:如果标准的match匹配了,则:
接着匹配扩展match
if (IPT_MATCH_ITERATE(e, do_match,
                                              *pskb, in, out,
                                              offset, protohdr,
                                              datalen, &hotdrop) != 0)
                                goto no_match;
IPT_MATCH_ITERATE这个宏已经遇到很多次了,它的作用是遍历扩展的match,而实际执行的功能函数是
do_match


OK,如果不匹配,则goto no_match;执行下条规则,否则:
/*这个宏用来分别处理字节计数器和分组计数器这两个计数器*/
ADD_COUNTER(e->counters, ntohs(ip->tot_len), 1);

/*获取规则的target的偏移地址*/
t = ipt_get_target(e);

然后接着匹备target:
if (!t->u.kernel.target->target) {
                                int v;

                                v = ((struct ipt_standard_target *)t)->verdict;
                                if (v < 0) {
                                        /* Pop from stack? */
                                        if (v != IPT_RETURN) {
                                                verdict = (unsigned)(-v) - 1;
                                                break;
                                        }
                                        e = back;
                                        back = get_entry(table_base,
                                                         back->comefrom);
                                        continue;
                                }
                                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 {
                                /* Targets which reenter must return
                                   abs. verdicts */
#ifdef CONFIG_NETFILTER_DEBUG
                                ((struct ipt_entry *)table_base)->comefrom
                                        = 0xeeeeeeec;
#endif
                                verdict = t->u.kernel.target->target(pskb,
                                                                     hook,
                                                                     in, out,
                                                                     t->data,
                                                                     userdata);

#ifdef CONFIG_NETFILTER_DEBUG
                                if (((struct ipt_entry *)table_base)->comefrom
                                    != 0xeeeeeeec
                                    && verdict == IPT_CONTINUE) {
                                        printk("Target %s reentered!\n",
                                               t->u.kernel.target->name);
                                        verdict = NF_DROP;
                                }
                                ((struct ipt_entry *)table_base)->comefrom
                                        = 0x57acc001;
#endif
                                /* Target might have changed stuff. */
                                ip = (*pskb)->nh.iph;
                                protohdr = (u_int32_t *)ip + ip->ihl;
                                datalen = (*pskb)->len - ip->ihl * 4;

                                if (verdict == IPT_CONTINUE)
                                        e = (void *)e + e->next_offset;
                                else
                                        /* Verdict */
                                        break;
                        }
因为到目前为止,我们谈match/target的数据结构,只接触到用户态,对于内核态的,几乎没有怎么接触,所以要把它说清楚,可不是一件容易的事情。(或者先分析用户态添加规则在内核中是如何实现的,了解了match和target的组织再来分析代码,更容易些)。不过,反正无论如何,还是一步步地来,
先来看看标准match的匹配部份,即ip_packet_match函数

[ 本帖最后由 独孤九贱 于 2005-12-20 10:21 编辑 ]

论坛徽章:
0
20 [报告]
发表于 2005-12-19 17:56 |只看该作者
ip_packet_match函数,标准match部份的匹配

先来看看相关的数据结构,在内核中,标准的match是以struct ipt_ip 结构来表示的,它包含了一条规则最基本的部份:
/* Yes, Virginia, you have to zero the padding. */
struct ipt_ip {
        /* 来源/目的地址 */
        struct in_addr src, dst;
        /* 来源/目的地址的掩码 */
        struct in_addr smsk, dmsk;
        /*输入输出网络接口*/
        char iniface[IFNAMSIZ], outiface[IFNAMSIZ];
        unsigned char iniface_mask[IFNAMSIZ], outiface_mask[IFNAMSIZ];

        /* 协议, 0 = ANY */
        u_int16_t proto;

        /* 标志字段 */
        u_int8_t flags;
        /* 取反标志 */
        u_int8_t invflags;
};

这样,再来看这部份的判断是一个很简单的事情了:

/* Returns whether matches rule or not. */
static inline int
ip_packet_match(const struct iphdr *ip,
                const char *indev,
                const char *outdev,
                const struct ipt_ip *ipinfo,
                int isfrag)
{
        size_t i;
        unsigned long ret;
/*定义一个宏,当bool和invflg的是一真一假的情况时,返回真。注意这里使用两个“!”的目的是使得这样计算后的值域只取0和1两个值*/
#define FWINV(bool,invflg) ((bool) ^ !!(ipinfo->invflags & invflg))

/*处理源和目标ip地址,这个if语句的意义是:到达分组的源ip地址经过掩码处理后与规则中的ip不匹配并且规则中
没有包含对ip地址的取反,或者规则中包含了对匹配地址的取反,但到达分组的源ip与规则中的ip地址匹配,if的第
一部分返回真,同样道理处理到达分组的目的ip地址。这两部分任意部分为真时,源或者目标地址不匹配。*/
        if (FWINV((ip->saddr&ipinfo->smsk.s_addr) != ipinfo->src.s_addr,
                  IPT_INV_SRCIP)
            || FWINV((ip->daddr&ipinfo->dmsk.s_addr) != ipinfo->dst.s_addr,
                     IPT_INV_DSTIP)) {
                dprintf("Source or dest mismatch.\n");

                dprintf("SRC: %u.%u.%u.%u. Mask: %u.%u.%u.%u. Target: %u.%u.%u.%u.%s\n",
                        NIPQUAD(ip->saddr),
                        NIPQUAD(ipinfo->smsk.s_addr),
                        NIPQUAD(ipinfo->src.s_addr),
                        ipinfo->invflags & IPT_INV_SRCIP ? " (INV)" : "");
                dprintf("DST: %u.%u.%u.%u Mask: %u.%u.%u.%u Target: %u.%u.%u.%u.%s\n",
                        NIPQUAD(ip->daddr),
                        NIPQUAD(ipinfo->dmsk.s_addr),
                        NIPQUAD(ipinfo->dst.s_addr),
                        ipinfo->invflags & IPT_INV_DSTIP ? " (INV)" : "");
                return 0;
        }

/*接着处理输入和输出的接口,for语句处理接口是否与规则中的接口匹配,不匹配时,ret返回非零,离开for语句后,
处理接口的取反问题:当接口不匹配并且接口不取反,或者接口匹配,但是接口取反,说明接口不匹配。*/

/*输入接口*/
        for (i = 0, ret = 0; i < IFNAMSIZ/sizeof(unsigned long); i++) {
                ret |= (((const unsigned long *)indev)
                        ^ ((const unsigned long *)ipinfo->iniface))
                        & ((const unsigned long *)ipinfo->iniface_mask);
        }

        if (FWINV(ret != 0, IPT_INV_VIA_IN)) {
                dprintf("VIA in mismatch (%s vs %s).%s\n",
                        indev, ipinfo->iniface,
                        ipinfo->invflags&IPT_INV_VIA_IN ?" (INV)":"");
                return 0;
        }

/*输出接口*/
        for (i = 0, ret = 0; i < IFNAMSIZ/sizeof(unsigned long); i++) {
                ret |= (((const unsigned long *)outdev)
                        ^ ((const unsigned long *)ipinfo->outiface))
                        & ((const unsigned long *)ipinfo->outiface_mask);
        }

        if (FWINV(ret != 0, IPT_INV_VIA_OUT)) {
                dprintf("VIA out mismatch (%s vs %s).%s\n",
                        outdev, ipinfo->outiface,
                        ipinfo->invflags&IPT_INV_VIA_OUT ?" (INV)":"");
                return 0;
        }

        /* 检查协议是否匹配的情况 */
        if (ipinfo->proto
            && FWINV(ip->protocol != ipinfo->proto, IPT_INV_PROTO)) {
                dprintf("Packet protocol %hi does not match %hi.%s\n",
                        ip->protocol, ipinfo->proto,
                        ipinfo->invflags&IPT_INV_PROTO ? " (INV)":"");
                return 0;
        }

        /* If we have a fragment rule but the packet is not a fragment
         * then we return zero */
        /*处理分片包的匹配情况*/
        if (FWINV((ipinfo->flags&IPT_F_FRAG) && !isfrag, IPT_INV_FRAG)) {
                dprintf("Fragment rule but not fragment.%s\n",
                        ipinfo->invflags & IPT_INV_FRAG ? " (INV)" : "");
                return 0;
        }

        return 1;
}
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

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

清除 Cookies - ChinaUnix - Archiver - WAP - TOP