代码改变世界

linux netfilter

2009-10-12 01:11  cesc711  阅读(1422)  评论(1编辑  收藏  举报
netfilter 是一种内核中用于扩展各种网络服务的结构化底层框架。netfilter的设计思想是生成一个模块结构使之能够比较容易的扩展。新的特性加入到内核中并不需要从新启动内核。这样,可以通过简单的构造一个内核模块来实现网络新特性的扩展。给底层的网络特性扩展带来了极大的便利,使更多从事网络底层研发的开发人员能够集中精力实现新的网络特性。

netfilter有4大特性:
1. 每一个协议定义"hooks"(钩子),ipv4定义了5个钩子,他们遍布协议栈中包传输的整个过程。在每一个点上,协议将使用包和钩子号来调用netfilter框架。
2. 部分内核可注册后可以为每一个协议监听不同的钩子。因此,当包通过netfilter框架时,它检查看是否有模块为协议和钩子注册;如果有,他们每一个都有机会按顺序检验(也可能是更改)包,抛弃包,允许包通过,或者请求netfilter为用户空间排队包。
3. 排队了的包可以被收集送往用户空间;这些包是被异步的处理的。
4. 有非常良好的代码和文档。这一点对于一个拥有良好扩展性的开放式框架具有极其深远的意义。离开了这一特性,netfilter结构将大为逊色甚至是难以操作的。

         netfilter整体结构图:
        

        [1] : NF_INET_PRE_ROUTING: 刚刚进入网络层的数据包通过此点(刚刚进行完版本号,校验和等检测), 目的地址转换在此点进行;
        [2] : NF_INET_LOCAL_IN: 经路由查找后,送往本机的通过此检查点,INPUT包过滤在此点进行;
        [3] : NF_INET_FORWARD: 要转发的包通过此检测点,FORWORD包过滤在此点进行;
        [4] : NF_INET_POST_ROUTING:所有马上便要通过网络设备出去的包通过此检测点,内置的源地址转换功能(包括地址伪装)在此点进行;
        [5] : NF_INET_LOCAL_OUT: 本机进程发出的包通过此检测点,OUTPUT包过滤在此点进行。

我们分析数据报经过netfilter 机制的过程。数据报从左边进入系统,进行IP校验以后,数据报经过第一个钩子NF_IP_PRE_ROUTING注册函数进行处理;然后就进入路由代码,其决定该数据包是需要转发还是发给本机的;若该数据包是发被本机的,则该数据经过钩子NF_IP_LOCAL_IN注册函数处理以后然后传递给上层协议;若该数据包应该被转发则它被NF_IP_FORWARD注册函数处理;经过转发的数据报经过最后一个钩子NF_IP_POST_ROUTING 注册函数处理以后,再传输到网络上。本地产生的数据经过钩子函数NF_IP_LOCAL_OUT注册函数处理以后,进行路由选择处理,然后经过NF_IP_POST_ROUTING注册函数处理以后发送到网络上。

为了使其它内核模块能够操作数据报,Netfilter为我们提供了接口 nf_register_hook(struct nf_hook_ops *reg),函数原型在netfilter.h,定义在netfilter.c,返回值为int类型,注册成功返回零,失败则返回一个负值,参数reg为 nf_hook_ops结构体类型指针。我们在内核模块初始化的时候调用这个接口来注册处理函数,而我们的函数指针则保存在reg指针所指向的 nf_hook_ops。

struct nf_hook_ops
{
        
struct list_head list;         //链表成员
        nf_hookfn *hook;               //钩子函数指针
        struct module *owner;
        
int pf;                        //协议簇,对于ipv4而言,是PF_INET
        int hooknum;                   //hook类型
        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:优先级,目前Netfilter定义了一下几个优先级,取值也小优先级也高,我们可以根据需要对各个优先级加减一个常量得到符合我们需要的优先级。
               NF_IP_PRI_FIRST = INT_MIN
               NF_IP_PRI_CONNTRACK = -200
               NF_IP_PRI_MANGLE = -150
               NF_IP_PRI_NAT_DST = -100
               NF_IP_PRI_FILTER = 0
               NF_IP_PRI_NAT_SRC = 100
               NF_IP_PRI_LAST = INT_MAX

其中nf_hookfn是一个函数指针,它定义为:

static unsigned int nf_hooknf(unsigned int hooknum,               /* hook点 */
       
struct sk_buff *skb,
       
const struct net_device *in,
       
const struct net_device *out,
       
int (*okfn)(struct sk_buff *))     /* 默认处理函数 */

注册函数处理完后,将返回一个整形常量,内核根据这个返回值随数据报作下一步的处理。目前内核定义了一下四个常量:
               NF_DROP 0:丢弃此数据报,而不进入此后的处理;
               NF_ACCEPT 1:接受此数据报,进入下一步的处理;
               NF_STOLEN 2:表示异常分组;
               NF_QUEUE 3:排队到用户空间,等待用户处理;
               NF_REPEAT 4:进入此函数再作处理。

一个nf_hook_ops的例子:

static struct nf_hook_ops my_in_filter = { { NULL, NULL },
                                        incoming_filter,
                                        THIS_MODULE, PF_INET,
                                        NF_INET_PRE_ROUTING,
                                        NF_IP_PRI_FIRST };


最后调用nf_register_hook函数将自己的nf_hook_ops注册到nf_hook_ops链表当中去。上面这个my_in_filter结构体变量的钩子函数为incoming_filter,这个函数被钩在PRE_ROUTING点。当有ip包经过PRE_ROUTING点的时候,就会被incoming_filter函数处理。