【转】Netfilter机制
转自http://ycnian.org/blog/archives/628
Netfilter是Linux内核网络子系统中的一个模块,这个模块可以拦截系统中的报文进行处理。Linux系统中的防火墙iptables就是构建在netfilter模块之上的。报文在一台Linux主机中的流通过程如下图所示:
假设一台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); |