免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 1140 | 回复: 0
打印 上一主题 下一主题

Netfilter源码分析[转载] [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2007-01-18 12:02 |只看该作者 |倒序浏览

一、主函数
init为初始化函数,主要完成表的注册,然后再注册与表相对应的HOOK
//初始化函数为init:
module_init(init);
//init 函数负责注册filter表和默认的三个chain
static int __init init(void)
{
        int ret;
        if (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;
        /* 注册filter表 */
        ret = ipt_register_table(&packet_filter);
        if (ret
独孤九贱
回复于:2005-12-16 12:52:40

二、表的注册
表的注册由函数ipt_register_table来完成,
ipt_register_table(&packet_filter);
其参数packet_filter包含了待注册表的各个参数:
static struct ipt_table packet_filter
= { { NULL, NULL }, "filter", &initial_table.repl,
    FILTER_VALID_HOOKS, RW_LOCK_UNLOCKED, NULL, THIS_MODULE };
   
可见,内核中,表是以结构struct ipt_table来表示的:
struct ipt_table
{
                struct list_head list;
                        /* 用于构造,维护链表的结构 */
                char name[IPT_TABLE_MAXNAMELEN];
                        /* 表名,如"filter"、"nat"等,为了满足自动模块加载的设计,包含该表的模块应命名为iptable_'name'.o */
                struct ipt_replace *table;
                        /* 表的初始化模板,初始为initial_table.repl */
                unsigned int valid_hooks;
                        /* 位向量,表示当前表所影响的HOOK */
                rwlock_t lock;
                        /* 读写锁,初始为打开状态 */
                struct ipt_table_info *private;
                        /* iptable的数据区*/
                struct module *me;
                        /* 是否在模块中定义,若否,则为NULL */
};
对照这一结构分析,filter表的初始化为:
链表:{ NULL, NULL }
表名:"filter"
初始化模板:&initial_table.repl
当前表所影响的Hook:FILTER_VALID_HOOKS
/*#define FILTER_VALID_HOOKS ((1 table->size) * smp_num_cpus);
        /*分配失败*/
        if (!newinfo) {
                ret = -ENOMEM;
                MOD_DEC_USE_COUNT;
                return ret;
        }
       
        /*将规则项拷贝到新表项的第一个cpu空间里面*/
        memcpy(newinfo->entries, table->table->entries, table->table->size);
/*translate_table函数将newinfo表示的table的各个规则进行边界检查,然后对于newinfo所指的
ipt_talbe_info结构中的hook_entries和underflows赋予正确的值,最后将表项向其他cpu拷贝*/
        ret = translate_table(table->name, table->valid_hooks,
                              newinfo, table->table->size,
                              table->table->num_entries,
                              table->table->hook_entry,
                              table->table->underflow);
        if (ret != 0) {
                vfree(newinfo);
                MOD_DEC_USE_COUNT;
                return ret;
        }
        ret = down_interruptible(&ipt_mutex);
        if (ret != 0) {
                vfree(newinfo);
                MOD_DEC_USE_COUNT;
                return ret;
        }
        /* 如果注册的table已经存在,释放空间 并且递减模块计数 */
        if (list_named_find(&ipt_tables, table->name)) {
                ret = -EEXIST;
                goto free_unlock;
        }
        /* 替换table项. */
        table->private = &bootstrap;
        if (!replace_table(table, 0, newinfo, &ret))
                goto free_unlock;
        duprintf("table->private->number = %u\n",
                 table->private->number);
       
        /* 保存初始规则计数器 */
        table->private->initial_entries = table->private->number;
        table->lock = RW_LOCK_UNLOCKED;
        /*将表添加进链表*/
        list_prepend(&ipt_tables, table);
unlock:
        up(&ipt_mutex);
        return ret;
free_unlock:
        vfree(newinfo);
        MOD_DEC_USE_COUNT;
        goto unlock;
}
呵呵,初次看table的注册,有点头大,因为它不光是netfilter,还涉及到linux内核中的内存管理、
信号量设置等等,不过其实注册也就完成两件事:初始化表,将表添加进表的链表。

独孤九贱
回复于:2005-12-16 12:53:14

表的注册中涉及到的重要函数
表注册函数中,主要涉及到的重要函数有:
translate_table
list_named_find
list_prepend
1、translate_table
/*
* 函数:translate_table()
* 参数:
*         name:表名称;
*        valid_hooks:当前表所影响的hook
*        newinfo:包含当前表的所有信息的结构
*        size:表的大小
*        number:表中的规则数
*        hook_entries:记录所影响的HOOK的规则入口相对于下面的entries变量的偏移量
*        underflows:与hook_entry相对应的规则表上限偏移量
* 作用:
*        translate_table函数将newinfo表示的table的各个规则进行边界检查,然后对于newinfo所指的
*        ipt_talbe_info结构中的hook_entries和underflows赋予正确的值,最后将表项向其他cpu拷贝
* 返回值:
*        int ret==0表示成功返回
*/
static int
translate_table(const char *name,
                unsigned int valid_hooks,
                struct ipt_table_info *newinfo,
                unsigned int size,
                unsigned int number,
                const unsigned int *hook_entries,
                const unsigned int *underflows)
{
        unsigned int i;
        int ret;
        newinfo->size = size;
        newinfo->number = number;
        /* 初始化所有Hooks为不可能的值. */
        for (i = 0; i hook_entry = 0xFFFFFFFF;
                newinfo->underflow = 0xFFFFFFFF;
        }
        duprintf("translate_table: size %u\n", newinfo->size);
        i = 0;
        /* 遍历所有规则,检查所有偏量,检查的工作都是由IPT_ENTRY_ITERATE这个宏来完成,并且它
        的最后一个参数i,返回表的所有规则数. */
        ret = IPT_ENTRY_ITERATE(newinfo->entries, newinfo->size,
                                check_entry_size_and_hooks,
                                newinfo,
                                newinfo->entries,
                                newinfo->entries + size,
                                hook_entries, underflows, &i);
        if (ret != 0)
                return ret;
        /*实际计算得到的规则数与指定的不符*/
        if (i != number) {
                duprintf("translate_table: %u not %u entries\n",
                         i, number);
                return -EINVAL;
        }
        /* 因为函数一开始将HOOK的偏移地址全部初始成了不可能的值,而在上一个宏的遍历中设置了
        hook_entries和underflows的值,这里对它们进行检查 */
        for (i = 0; i hook_entry == 0xFFFFFFFF) {
                        duprintf("Invalid hook entry %u %u\n",
                                 i, hook_entries);
                        return -EINVAL;
                }
                if (newinfo->underflow == 0xFFFFFFFF) {
                        duprintf("Invalid underflow %u %u\n",
                                 i, underflows);
                        return -EINVAL;
                }
        }
        /*确保新的table中不存在规则环*/
        if (!mark_source_chains(newinfo, valid_hooks))
                return -ELOOP;
        /* 对tables中的规则项进行完整性检查,保证每一个规则项在形式上是合法的*/
        i = 0;
        ret = IPT_ENTRY_ITERATE(newinfo->entries, newinfo->size,
                                check_entry, name, size, &i);
       
        /*检查失败,释放空间,返回*/
        if (ret != 0) {
                IPT_ENTRY_ITERATE(newinfo->entries, newinfo->size,
                                  cleanup_entry, &i);
                return ret;
        }
        /* 为每个CPU复制一个完整的table项*/
        for (i = 1; i entries + SMP_ALIGN(newinfo->size)*i,
                       newinfo->entries,
                       SMP_ALIGN(newinfo->size));
        }
        return ret;
}
函数的核心处理,是调用了IPT_ENTRY_ITERATE,我在《iptables源码分析》中已提过,这个宏用来遍历每一个规则,然后
调用其第三个参数(函数指针)进行处理,前两个参数分别表示规则的起始位置和规则总大小,后面的参数则视情况而定。
再来看一次:
/* fn returns 0 to continue iteration */
#define IPT_ENTRY_ITERATE(entries, size, fn, args...)                \
({                                                                \
        unsigned int __i;                                        \
        int __ret = 0;                                                \
        struct ipt_entry *__entry;                                \
                                                                \
        for (__i = 0; __i next_offset) { \
                __entry = (void *)(entries) + __i;                \
                                                                \
                __ret = fn(__entry , ## args);                        \
                if (__ret != 0)                                        \
                        break;                                        \
        }                                                        \
        __ret;                                                        \
})
对应地,函数的第一次宏的调用,
        ret = IPT_ENTRY_ITERATE(newinfo->entries, newinfo->size,
                                check_entry_size_and_hooks,
                                newinfo,
                                newinfo->entries,
                                newinfo->entries + size,
                                hook_entries, underflows, &i);
遍历到每一项规则后,就调用check_entry_size_and_hooks继续处理。
static inline int
check_entry_size_and_hooks(struct ipt_entry *e,
                           struct ipt_table_info *newinfo,
                           unsigned char *base,
                           unsigned char *limit,
                           const unsigned int *hook_entries,
                           const unsigned int *underflows,
                           unsigned int *i)
{
        unsigned int h;
       
        /*(unsigned long)e % __alignof__(struct ipt_entry) != 0--不能整除,规则不完整
        (unsigned char *)e + sizeof(struct ipt_entry) >= limit--超过上限了*/
       
        if ((unsigned long)e % __alignof__(struct ipt_entry) != 0
            || (unsigned char *)e + sizeof(struct ipt_entry) >= limit) {
                duprintf("Bad offset %p\n", e);
                return -EINVAL;
        }
       
        /*e->next_offset
            next_offset
            next_offset);
                return -EINVAL;
        }
        /* 检查并设置正确的 hooks & underflows */
        for (h = 0; h hook_entry[h] = hook_entries[h];
                if ((unsigned char *)e - base == underflows[h])
                        newinfo->underflow[h] = underflows[h];
        }
        /* FIXME: underflows must be unconditional, standard verdicts
           counters = ((struct ipt_counters) { 0, 0 });                /*包和字节的计数器清零*/
        e->comefrom = 0;                                        /*环路计数器清零*/
        (*i)++;                                                        /*规则计数器累加*/
        return 0;
}
2、replace_table
前面说过,表中以struct ipt_table_info *private;表示实际数据区。但是在初始化赋值的时候,被设为
NULL,而表的初始变量都以模版的形式,放在struct ipt_replace *table;中。
注册函数一开始,就声明了:
struct ipt_table_info *newinfo;
然后对其分配了空间,将模块中的初值拷贝了进来。所以replace_table要做的工作,主要就是把newinfo中的
值传递给table结构中的private成员。
其函数原型如下:
static struct ipt_table_info *
replace_table(struct ipt_table *table,
              unsigned int num_counters,
              struct ipt_table_info *newinfo,
              int *error)
{
        struct ipt_table_info *oldinfo;
#ifdef CONFIG_NETFILTER_DEBUG
        {
                struct ipt_entry *table_base;
                unsigned int i;
                for (i = 0; i entries
                                + TABLE_OFFSET(newinfo, i);
                        table_base->comefrom = 0xdead57ac;
                }
        }
#endif
        /* Do the substitution. */
        write_lock_bh(&table->lock);
        /* Check inside lock: is the old number correct? */
        if (num_counters != table->private->number) {
                duprintf("num_counters != table->private->number (%u/%u)\n",
                         num_counters, table->private->number);
                write_unlock_bh(&table->lock);
                *error = -EAGAIN;
                return NULL;
        }
        oldinfo = table->private;
        table->private = newinfo;
        newinfo->initial_entries = oldinfo->initial_entries;
        write_unlock_bh(&table->lock);
        return oldinfo;
}
3、list_named_find
在注册函数中,调用
        /* Don't autoload: we'd eat our tail... */
        if (list_named_find(&ipt_tables, table->name)) {
                ret = -EEXIST;
                goto free_unlock;
        }
来检查当前表是否已被注册过了。可见,第一个参数为链表首部,第二个参数为当前表名。
其原型如下:
/* Find this named element in the list. */
#define list_named_find(head, name)                        \
LIST_FIND(head, __list_cmp_name, void *, name)
/* Return pointer to first true entry, if any, or NULL.  A macro
   required to allow inlining of cmpfn. */
#define LIST_FIND(head, cmpfn, type, args...)                \
({                                                        \
        const struct list_head *__i = (head);                \
                                                        \
        ASSERT_READ_LOCK(head);                                \
        do {                                                \
                __i = __i->next;                        \
                if (__i == (head)) {                        \
                        __i = NULL;                        \
                        break;                                \
                }                                        \
        } while (!cmpfn((const type)__i , ## args));        \
        (type)__i;                                        \
})
前面提过,表是一个双向链表,在宏当中,以while进行循环,以__i = __i->next;
进行遍历,然后调用比较函数进行比较,传递过来的比较函数是__list_cmp_name。
比较函数很简单:
static inline int __list_cmp_name(const void *i, const char *name)
{
        return strcmp(name, i+sizeof(struct list_head)) == 0;
}
4、list_prepend
当所有的初始化工作结束,就调用list_prepend来构建链表了。
/* Prepend. */
static inline void
list_prepend(struct list_head *head, void *new)
{
        ASSERT_WRITE_LOCK(head);                /*设置写互斥*/
        list_add(new, head);                        /*将当前表节点添加进链表*/
}
list_add就是一个构建双向链表的过程:
static __inline__ void list_add(struct list_head *new, struct list_head *head)
{
        __list_add(new, head, head->next);
}
static __inline__ void __list_add(struct list_head * new,
        struct list_head * prev,
        struct list_head * next)
{
        next->prev = new;
        new->next = next;
        new->prev = prev;
        prev->next = new;
}

独孤九贱
回复于:2005-12-16 12:54:08

三、Hook的注册
如果你对Netfilter的hook的注册还不了解的话,推荐先到网上搜搜《深入Linux网络核心堆栈》bioforge
看看先。(本节中有部份文字引自该文)
注册一个hook函数是围绕nf_hook_ops数据结构的一个非常简单的操作,nf_hook_ops数据结构在linux/netfilter.h中定义,
该数据结构的定义如下:
struct nf_hook_ops
{
        struct list_head list;
        /* User fills in from here down. */
        nf_hookfn *hook;
        int pf;
        int hooknum;
        /* Hooks are ordered in ascending priority. */
        int priority;
};
该数据结构中的list成员用于维护Netfilter hook的列表。
hook成员是一个指向nf_hookfn类型的函数的指针,该函数是这个hook被调用时执行的函数。
nf_hookfn同样在linux/netfilter.h中定义。
pf这个成员用于指定协议族。有效的协议族在linux/socket.h中列出,但对于IPv4我们希望使用协议族PF_INET。
hooknum这个成员用于指定安装的这个函数对应的具体的hook类型:
        NF_IP_PRE_ROUTING    在完整性校验之后,选路确定之前
        NF_IP_LOCAL_IN        在选路确定之后,且数据包的目的是本地主机
        NF_IP_FORWARD        目的地是其它主机地数据包
        NF_IP_LOCAL_OUT        来自本机进程的数据包在其离开本地主机的过程中
        NF_IP_POST_ROUTING    在数据包离开本地主机“上线”之前
最后,priority这个成员用于指定在执行的顺序中,这个hook函数应当在被放在什么地方。
对于IPv4,可用的值在linux/netfilter_ipv4.h的nf_ip_hook_priorities枚举中定义。
针对HOOK的注册,在初始化函数中有:
        /* Register table */
        ret = ipt_register_table(&packet_filter);
        if (ret pf][reg->hooknum].next;
             i != &nf_hooks[reg->pf][reg->hooknum];
             i = i->next) {
                if (reg->priority priority)
                        break;
        }
        /*添加节点*/
        list_add(&reg->list, i->prev);
        br_write_unlock_bh(BR_NETPROTO_LOCK);
        return 0;
}
能过表的注册,HOOK的注册,准备工作基本上就完成了,其它表的注册和Hook的注册,都是一样的,可以对照分析,没有必要再详述了。
不过注册也只是准备工作。重要的事情是对数据包的处理,对于filter来说,就是包过滤,对于nat来讲,就是地址转换。
四、数据包过滤
1、钩子函数
以中转包过滤为例(FORWARD),注册的时候,向内核注册了一个ipt_hook的钩子函数。
static unsigned int
ipt_hook(unsigned int hook,                                //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);
}
转向到了ipt_do_table。也就是说,如果向内核挂了钩,中转的数据,将进入ipt_do_table函数。
2、钩子函数被调用
钩子函数被注册了,但是内核是如何调用它的呢?
在/src/net/ipv4下边,对应于input/output/forward,分别有Ip_forward.c,Ip_output.c,Ip_input.c。同样继续以forward为例,
(关于linux堆栈处理数据包流程的各个函数的作用等,这里就不进一步详述,请参考其它相关资料)。
对于转发的数据,将进入Ip_forward.c中的ip_forward函数,当处理完成后,在最后一句,可以看到:
        return NF_HOOK(PF_INET, NF_IP_FORWARD, skb, skb->dev, dev2,
                       ip_forward_finish);
事实上,你在linux的每一个数据转发的"关节"的函数处,都可以发现这个宏的调用,它就是调用我们注册的钩子,其最后一个参数为
下一步处理的函数,即,如果有钩子函数,则处理完所有的钩子函数后,调用这个函数继续处理,如果没有注册任何钩子,则直接调用
此函数。
/* This is gross, but inline doesn't cut it for avoiding the function
   call in fast path: gcc doesn't inline (needs value tracking?). --RR */
#ifdef CONFIG_NETFILTER_DEBUG
#define NF_HOOK nf_hook_slow
#else
#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
先初略看看这个宏,okfn,我们已讲过,它是下一步要处理的函数,这里先调用
list_empty函数检查nf_hooks是否为空,为空则表示没有Hook注册,则直接调用
okfn继续处理。如果不为空,则转入nf_hook_slow函数:
int nf_hook_slow(int pf, unsigned int hook, struct sk_buff *skb,
                 struct net_device *indev,
                 struct net_device *outdev,
                 int (*okfn)(struct sk_buff *))
{
        struct list_head *elem;
        unsigned int verdict;
        int ret = 0;
        /* This stopgap cannot be removed until all the hooks are audited. */
        if (skb_is_nonlinear(skb) && skb_linearize(skb, GFP_ATOMIC) != 0) {
                kfree_skb(skb);
                return -ENOMEM;
        }
        if (skb->ip_summed == CHECKSUM_HW) {
                if (outdev == NULL) {
                        skb->ip_summed = CHECKSUM_NONE;
                } else {
                        skb_checksum_help(skb);
                }
        }
        /* We may already have this, but read-locks nest anyway */
        br_read_lock_bh(BR_NETPROTO_LOCK);
#ifdef CONFIG_NETFILTER_DEBUG
        if (skb->nf_debug & (1 nf_debug |= (1 hook就是我们关心的终极目标*/
        elem = &nf_hooks[pf][hook];
        /*找到后,遍历双向链表,进一步处理,以调用Hook函数,并返回相应的动作*/
        verdict = nf_iterate(&nf_hooks[pf][hook], &skb, hook, indev,
                             outdev, &elem, okfn);
        if (verdict == NF_QUEUE) {
                NFDEBUG("nf_hook: Verdict = QUEUE.\n");
                nf_queue(skb, elem, pf, hook, indev, outdev, okfn);
        }
        /*如果是接受,则调用okfn继续处理,否则丢度之*/
        switch (verdict) {
        case NF_ACCEPT:
                ret = okfn(skb);       
                break;
        case NF_DROP:
                kfree_skb(skb);
                ret = -EPERM;
                break;
        }
        br_read_unlock_bh(BR_NETPROTO_LOCK);
        return ret;
}
再来看nf_iterate:
static unsigned int nf_iterate(struct list_head *head,
                               struct sk_buff **skb,
                               int hook,
                               const struct net_device *indev,
                               const struct net_device *outdev,
                               struct list_head **i,
                               int (*okfn)(struct sk_buff *))
{
        /*循环遍历所有注册的钩子函数,包括系统默认的三个,用户自定义的……*/
        for (*i = (*i)->next; *i != head; *i = (*i)->next) {
                struct nf_hook_ops *elem = (struct nf_hook_ops *)*i;
                /*就在这里调用了*/
                switch (elem->hook(hook, skb, indev, outdev, okfn)) {
                case NF_QUEUE:
                        return NF_QUEUE;
                case NF_STOLEN:
                        return NF_STOLEN;
                case NF_DROP:
                        return NF_DROP;
                case NF_REPEAT:
                        *i = (*i)->prev;
                        break;
#ifdef CONFIG_NETFILTER_DEBUG
                case NF_ACCEPT:
                        break;
                default:
                        NFDEBUG("Evil return from %p(%u).\n",
                                elem->hook, hook);
#endif
                }
        }
        return NF_ACCEPT;
}   
解释一下各个返回值:
NF_DROP                丢弃该数据包
NF_ACCEPT            保留该数据包
NF_STOLEN            忘掉该数据包
NF_QUEUE            将该数据包插入到用户空间
NF_REPEAT            再次调用该hook函数
这样,最终关心的还是每一个注册的函数,这样又回到本节开头所说的ipt_do_table……
文章出处
http://www.chinaunix.net/jh/4/670248.html

               
               
               

本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u/2108/showart_234000.html
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

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

清除 Cookies - ChinaUnix - Archiver - WAP - TOP