转自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);
|