【转】Netfilter机制

转自http://ycnian.org/blog/archives/628

Netfilter是Linux内核网络子系统中的一个模块,这个模块可以拦截系统中的报文进行处理。Linux系统中的防火墙iptables就是构建在netfilter模块之上的。报文在一台Linux主机中的流通过程如下图所示:
netfilter
假设一台Linux主机的eth0端口接收到了一个报文,Linux主机首先会查找路由表,判断这个报文的目的地址是否是自己。如果是自己,那么就逐层剥掉报文头,然后提交给应用程序处理。如果目的地址不是自己,那么通过查找路由表可以确定将报文从哪个网口转发出去。除了接收报文外,Linux主机还会主动向外发送报文。发送报文前也需要查找路由表,确定发送报文的网口。

Netfilter在报文流通的路径中设置了5个处理点,位置如上图所示。在这5个处理点,netfilter会截获经过的每个报文,调用一系列函数进行处理。我们可以在这些处理点增加自己的处理函数,从而实现特定功能。这5个处理点分别是:
NF_INET_PRE_ROUTING(位置1):可以截获接收的所有报文,包括目的地址是自己的报文和需要转发的报文。
NF_INET_LOCAL_IN(位置2):可以截获目的地址是自己的报文。
NF_INET_FORWARD(位置3):可以截获所有转发的报文。
NF_INET_LOCAL_OUT(位置4):可以截获自身发出的所有问题。
NF_INET_POST_ROUTING(位置5):可以截获发送的所有报文,包括自身发出的报文和转发的报文。

Netfilter模块对报文的处理结果可能如下:
#define NF_DROP 0 // 丢弃该报文,不再继续传输
#define NF_ACCEPT 1 // 继续正常传输报文
#define NF_STOLEN 2 //Netfilter 模块接管该报文,不再继续传输
#define NF_QUEUE 3 // 对该数据报进行排队,通常用于将数据报提交给用户空间进程处理
#define NF_REPEAT 4 // 再次调用该钩子函数
#define NF_STOP 5 // 继续正常传输报文
其中NF_ACCEPT和NF_STOP都表示报文通过了检查,可以正常向下流通,但是NF_STOP效果强于NF_ACCEPT。在每个处理点可以注册多个钩子函数,这些钩子函数按照优先级排列。优先级高的钩子函数先处理报文,优先级低的报文后处理报文。NF_ACCEPT表示报文通过了某个钩子函数的处理,下一个钩子函数可以接着处理了。而NF_STOP表示报文通过了某个钩子函数的处理,后面的钩子函数你们就不要处理了,谁让你的优先级低呢,我就可以替你做主。假设NF_INET_LOCAL_IN中注册了两个钩子函数hook1和hook2,hook1的优先级高于hook2,hook2设定的处理结果是NF_DROP。如果hook1设定的处理结果是NF_ACCEPT,那么报文就不会递交给应用程序,因为hook2会把报文丢弃掉。如果hook1设定的处理结果是NF_STOP,那么报文就会提交给应用程序,因为hook1放行了,根本不会给hook2处理的机会。

Netfilter中,一个钩子函数的数据结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
typedef unsigned int nf_hookfn(const struct nf_hook_ops *ops,
                struct sk_buff *skb, // 正在处理的报文
                const struct net_device *in, // 输入设备
                const struct net_device *out, // 输出设备
                int (*okfn)(struct sk_buff *)) // 如果通过了检查,就调用这个函数。
struct nf_hook_ops {
        struct list_head list;  // 同一个处理点的所有钩子函数链接在一条链表中
        nf_hookfn       *hook;  // 这是钩子函数
        struct module   *owner; // 模块
        void            *priv;  // 私有指针
        u_int8_t        pf;     // 协议族
        unsigned int    hooknum;// 钩子函数起作用的时间点
        int             priority;// 钩子函数的优先级,数值越小优先级越高.
};

在nf_hookfn()中,我们不需要关心okfn(),因为一般情况下不会用到这个参数。我们可以编写自己的hook函数,然后调用nf_register_hook()将hook函数注册到netfilter模块,这样我们就可以截获每个报文进行处理了。下面是一个例子,这个例子向NF_INET_LOCAL_IN注册了hook函数,如果接收到192.168.7.121发送的报文就打印一条信息“receive message from 192.168.7.121”,然后放行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/netfilter.h>
#include <linux/skbuff.h>
 
MODULE_AUTHOR("Yanchuan Nian");
MODULE_LICENSE("GPL");
 
static char target[4];
 
static unsigned int nftest_fn(const struct nf_hook_ops *ops,
        struct sk_buff *skb,
        const struct net_device *in,
        const struct net_device *out,
        int (*okfn)(struct sk_buff *))
{
    int res;
    char    saddr[4];
    int offset;
 
    offset = skb_network_offset(skb);
    offset += 12;
    res = skb_copy_bits(skb, offset, saddr, 4);
    if (res < 0) {
        return NF_ACCEPT;
    }
    res = memcmp(saddr, target, 4);
    if (!res)
        printk(KERN_INFO"receive message from 192.168.7.121\n");
 
    return NF_ACCEPT;
}
 
static struct nf_hook_ops nf_test_ops = {
    .hook = nftest_fn,
    .owner = THIS_MODULE,
    .pf = NFPROTO_IPV4,
    .hooknum = NF_INET_LOCAL_IN,
    .priority = 0,
 
};
 
static int __init nftest_init(void)
{
    int res;
 
    target[0] = 192; target[1] = 168;
    target[2] = 7; target[3] = 121;
 
    res = nf_register_hook(&nf_test_ops);
    if (res < 0)
        printk(KERN_INFO"failed to register hook\n");
    else
        printk(KERN_INFO"hello nftest\n");
    return res;
}
 
static void __exit nftest_exit(void)
{
    printk(KERN_INFO"bye nftest\n");
    nf_unregister_hook(&nf_test_ops);
}
 
module_init(nftest_init);
module_exit(nftest_exit);

 

posted @ 2016-05-26 09:10  fukan  阅读(2091)  评论(0编辑  收藏  举报