11.1.1 netfilter的结构框架(1)
1.Linux防火墙发展历程
最开始的ipfwadm是AlanCox在Linux Kernel发展的初期,从FreeBSD的内核代码中移植过来的。后来经历了ipchains,再经由Paul Russel在Linux Kernel 2.3系列的开发过程中发展了netfilter这个架构。而用户空间的防火墙管理工具,也相应的发展为iptables。在经历了Linux Kernel 2.4和2.6的发展以后,的确可以说,netfilter iptables经受住了大量用户广泛使用的考验。本文将基于Linux 2.6的内核来进行叙述。
2.什么是netfilter
netfilter是Linux 2.6内核实现的防火墙框架,它比以前任何一版Linux内核的防火墙子系统都要完善强大。netfilter提供了一个抽象、通用化的框架,该框架定义了一个子功能,实现的就是包过滤子系统。netfilter由一系列基于协议栈的钩子组成,这些钩子都对应某一具体的协议。
3.netfilter在IPv4中的结构
Linux 2.6支持对IPv4、IPv6及DECnet的钩子(本小节只提及IPv4的钩子)。IPv4协议栈为了实现对netfilter架构的支持,在IP包在IPv4协议栈上的游历路线(如图11.1所示)之中选择了5个检查点,可以在linux/netfilter_ipv4.h里面找到这些符号的定义,表11.1列出了IPv4中定义的钩子。
表11.1 IPv4中定义的钩子
钩子名称 | 调用时机 |
NF_IP_PRE_ROUTING | 完整性校验之后,路由决策之前 |
NF_IP_LOCAL_IN | 目的地为本机,路由决策之后 |
NF_IP_FORWARD | 数据包要到达另外一个接口 |
NF_IP_LOCAL_OUT | 本地进程的数据,发送出去的过程中 |
NF_IP_POST_ROUTING | 向外流出的数据上线之前 |
在这5个检查点上,各引入了一行对NF_HOOK()宏函数的相应调用。如果没配置防火墙,NF_HOOK()便从netfilter模块转回到IPv4协议栈继续往下处理。如果配置了防火墙,NF_HOOK()就转去调用nf_hook_slow()函数,该函数会按顺序调用在该检查点注册的钩子函数,不管钩子函数对数据包做了哪些处理,它都必须返回表11.2中的一个预定义的值。NF_IP_PRE_ROUTING钩子是数据包接收后第一个调用的钩子程序,这个钩子在后面编写的模块当中将会被用到。
表11.2 netfilter的返回值
返回值 | 含义 |
NF_DROP | 丢弃这个数据包 |
NF_ACCEPT | 保留这个数据包 |
NF_STOLEN | 忘掉这个数据包 |
NF_QUEUE | 让这个数据包在用户空间排队 |
NF_REPEAT | 再次调用这个钩子函数 |
NF_DROP表示要丢弃这个数据包,并且为这个数据包申请的所有资源都要得到释放。NF_ACCEPT告诉netfilter到目前为止,这个数据包仍然可以被接收,应该将它移到网络堆栈的下一层。NF_STOLEN是非常有趣的一个返回值,它告诉netfilter让其忘掉这个数据包,也就是说钩子函数会在这里对这个数据包进行完全处理,而netfilter应该放弃对它的任何处理。然而这并不意味着为该数据包申请的所有资源都要释放掉。这个数据包和它各自的sk_buff结构体依然有效,只是钩子函数从netfilter夺取了对这个数据包的掌控权。最后一个返回值NF_REPEAT,就是当用户改变了该数据包包头的某些信息时,那可以请求netfilter再次调用这个钩子函数对它进行操作。
4.注册和注销netfilter钩子函数
在上面提到了nf_hook_slow()函数会按顺序调用在该检查点注册的钩子函数,那钩子函数是怎样注册的呢?注册一个钩子函数是一个围绕nf_ hook_ ops结构体的简单过程,在linux/netfilter.h中有这个结构体的定义:
这个结构体的成员列表主要是用来维护注册的钩子函数列表的,对于用户来说,在注册时并没有多么重要。list是一个有prev和next两个域的双向链表,各检查点的钩子函数就是通过它按照priority的值由小到大链接在一起的,nf_hook_slow()函数依靠遍历这个表来调用该检查点的钩子函数。hook 是指向nf_hookfn函数的指针,也就是为这个钩子将要调用的所有函数。nf_hookfn同样定义在linux/netfilter.h文件中。pf字段指定了协议簇(Protocol Family),linux/socket.h中定义了可用的协议簇。对于IPv4而言,只使用PF_INET。hooknum 域指明了为哪个检查点安装这个函数,即表11.1中所列出的条目中的一个。priority域表示在运行时这个钩子函数执行的顺序。我们选择NF_IP_PRI_FIRST这个最高优先级来运行编写的内核模块。
- struct nf_hook_ops
- {
- struct list_head list;
- nf_hookfn *hook;
- int pf;
- int hooknum;
- int priority;
- };