Linux netfilter机制应用浅释
Author:banliao From: Linuxforu.net
Netfilter是自2.2版本内核后Linux网络内核重要的组成部分,是Linux网络防火墙以及IPtable实现的基础。Netfilter是
一个提供了不同于BSD
Socket接口的操作网络数据包的机制。在Netfilter中,协议栈每种协议都定义了若干个钩子(HOOK),如IPv6和IP
v4均定义了五个钩子(将在后面的章节中详解),而对应协议的数据包将按照一定的规则通过若干个钩子,每一个钩子都是处理函数挂载点。内核模块则可以在各
个钩子上注册处理函数,以操作经过对应钩子的数据包。函数处理后,根据一定的策略返回给内核进行下一步的处理。下面我们一IPv6为例子作进一步的解释。
IPv6协议定义了一下五个钩子,它们的名字(名字后面的是它们对应的值)告诉我们它们所处的位置,如图所示:
1.
NF_IP6_PRE_ROUTING 0:数据包在抵达路由之前经过这个钩子。目前,在这个钩子上只对数据包作包头检测处理,一般应用于防止拒绝服务攻击和NAT;
2. NF_IP6_LOCAL_IN 1:目的地为本地主机的数据包经过这个钩子。防火墙一般建立在这个钩子上;
3. NF_IP6_FORWARD
2:目的地非本地主机的数据包经过这个钩子;
4. NF_IP6_LOCAL_OUT 3:本地主机发出的数据包经过这个钩子;
5.
NF_IP6_POST_ROUTING 4:数据包在离开本地主机之前经过这个钩子,包括源地址为本地主机和非本地主机的。
对照图片,我们分析数据报经过Netfilter机制的过程。数据报从左边进入系统,进行IP校验以后,数据报经过第一个钩子
NF_IP6_PRE_ROUTING注册函数进行处理;然后就进入路由代码,其决定该数据包是需要转发还是发给本机的;若该数据包是发被本机的,则该数
据经过钩子NF_IP6_LOCAL_IN注册函数处理以后然后传递给上层协议;若该数据包应该被转发则它被NF_IP6_FORWARD注册函数处理;
经过转发的数据报经过最后一个钩子NF_IP6_POST_ROUTING注册函数处理以后,再传输到网络上。
本地产生的数据经过钩子函数NF_IP6_LOCAL_OUT注册函数处理以后,进行路由选择处理,然后经过NF_IP6_POST_ROUTING注册函数处理以后发送到网络上。
注册函数处理完后,将返回一个整形常量,内核根据这个返回值随数据报作下一步的处理。目前内核定义了一下四个常量:
1. NF_DROP
0:丢弃此数据报,而不进入此后的处理;
2. NF_ACCEPT 1:接受此数据报,进入下一步的处理;
3. NF_STOLEN
2:表示异常分组;
4. NF_QUEUE 3:排队到用户空间,等待用户处理;
5. NF_REPEAT 4:进入此函数再作处理。
为了是其它内核模块能够操作数据报,Netfilter为我们提供了接口nf_register_hook(struct nf_hook_ops
*reg),函数原型在netfilter.h,定义在netfilter.c,返回值为int类型,注册成功返回零,失败则返回一个负值,参数reg为
nf_hook_ops结构体类型指针。我们在内核模块初始化的时候调用这个接口来注册处理函数,而我们的函数指针则保存在reg指针所指向的
nf_hook_ops。nf_hook_ops定义在netfilter.h:
struct nf_hook_ops
{
struct list_head list;
nf_hookfn *hook;
int pf;
int hooknum;
int priority;
};
list:链表头,用来把各个处理函数组织成一个表,初始化为{NULL,NULL};
hook:我们定义的处理函数的指针,返回值必须为前面所说的几个常量之一;
pf:协议族,表示这个HOOK属于哪个协议族;
hooknum:我们想要注册的钩子,取值为五个钩子之一;
priority:优先级,目前Netfilter定义了一下几个优先级,取值也小优先级也高,我们可以根据需要对各个优先级加减一个常量得到符合我们需要的优先级。
NF_IP6_PRI_FIRST = INT_MIN
NF_IP6_PRI_CONNTRACK = -200
NF_IP6_PRI_MANGLE = -150
NF_IP6_PRI_NAT_DST = -100
NF_IP6_PRI_FILTER
= 0
NF_IP6_PRI_NAT_SRC = 100
NF_IP6_PRI_LAST = INT_MAX
接下来我们谈谈处理函数的定义,函数指针的类型为nf_hookfn,它的定义为:
typedef unsigned int
nf_hookfn(unsigned int hooknum,
struct sk_buff
**skb,
const struct
net_device *in,
const struct net_device
*out,
int
(*okfn)(struct sk_buff *));
所有的这些参数都是由Netfilter传递给我们的处理函数的。除了okfn其它用途都比较易懂,okfn是当对应的钩子的注册函数为空时,
Netfilter调用的处理函数,它就是如果我们的处理函数返回Accept时Netfilter调用的处理函数。
有了以上这些我们就可以在Netfilter框架开发初步应用了,为了让大家更好的理解和掌握Netfilter机制应用,下面给出一个简单的例子,这是
从我们的项目中截取出来的,做了些修改,读者系统如果装载IPv6模块,稍微做些修改即可用户IPv4,这个相信难不到大家,但读者必须要有Linux模
块编程的基础。
#include
#include
#include
#include
#include
#include
#include
#include
#include
static unsigned int
aaa_account_infos_intercept(unsigned int hooknum,
struct sk_buff ** pskb,
const struct net_device *in,
const struct net_device *out,
int
(*okfn) (struct sk_buff *))
{
struct in6_addr *srcaddr, *dstaddr;
struct sk_buff *skb = *pskb;
if(skb){
struct ipv6hdr *hdr =
skb->nh.ipv6h;
srcaddr = &hdr->saddr;
dstaddr =
&hdr->daddr;
printk("Packet for source address:
%x:%x:%x:%x:%x:%x:%x:%x\n
destination address: %x:%x:%x:%x:%x:%x:%x:%x ",
NIPV6ADDR(srcaddr), NIPV6ADDR(dstaddr)) ;
return NF_ACCEPT;
}
struct nf_hook_ops aaa_account_infos_ops = {
{NULL,NULL},
aaa_account_infos_intercept,
PF_INET6,
NF_IP6_FORWARD,
NF_IP6_PRI_FILTER+2
};
int __init mipv6_aaa_init(void) {
nf_register_hook(&aaa_account_infos_ops);
return 0;
}
module_init(mipv6_aaa_init);
void __init mipv6_aaa_exit(void) {
nf_unregister_hook(&aaa_account_infos_ops);
}
module_exit(mipv6_aaa_exit);
这是我这几个月来做项目的初步总结,许多地方的认识还比较肤浅。希望大家提出批评和改进,谢谢!
主要参考文献:
1. Linux内核源代码;
2.
http://www.netfilter.org/documentation/HOWTO//netfilter-hacking-HOWTO.html;
3.
IPv6 Packet Handling in Linux 2.4;