1. 挂接点(hooknum)
netfilter是Linux2.4/2.6内核中自带的防火墙架构,定义了5个挂接点:
NF_IP_PRE_ROUTING-------->NF_IP_FORWARD--------->NF_IP_POST_ROUTING
| ^
| |
V |
NF_IP_LOCAL_IN NF_IP_LOCAL_OUT
netfilter定义了一个二维的链表头数组struct list_head nf_hooks[NPROTO][NF_MAX_HOOKS]来表示所有协议族的各个挂接点,NPROTO值为32,可表示linux所支持所有32个协议族(include/linux/socket.h文件中定义),也就是使用socket(2)函数的第一个参数的值,如互联网的TCP/IP协议族PF_INET(2)。每个协议族有NF_MAX_HOOKS(8)个挂接点,但实际只用了如上所述的5个,数组中每个元素表示一个协议族在一个挂接点的处理链表头,。
以下分析使用2.4.26内核中的netfilter代码。
在IPv4(PF_INET协议族)下,各挂接点定义在:
NF_IP_PRE_ROUTING,在IP栈成功接收sk_buff包后处理,挂接点在在net/ipv4/ip_input.c的函数
int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt)中定义:
return NF_HOOK(PF_INET, NF_IP_PRE_ROUTING, skb, dev, NULL,
ip_rcv_finish);
NF_IP_LOCAL_IN,在对接收的sk_buff包完成路由分类判断是到达自身的包后进行处理,挂接点在在net/ipv4/ip_input.c的函数int ip_local_deliver(struct sk_buff *skb)中定义:
return NF_HOOK(PF_INET, NF_IP_LOCAL_IN, skb, skb->dev, NULL,
ip_local_deliver_finish);
NF_IP_FORWARD,在对接收的sk_buff包完成路由分类判断是需要进行转发的包进行处理,挂接点在在net/ipv4/ip_input.c的函数int ip_forward(struct sk_buff *skb)中定义:
return NF_HOOK(PF_INET, NF_IP_FORWARD, skb, skb->dev, dev2,
ip_forward_finish);
NF_IP_LOCAL_OUT,在对自身发出的包进行处理,挂接点在在net/ipv4/ip_output.c的函数
int ip_build_and_send_pkt(struct sk_buff *skb, struct sock *sk,
u32 saddr, u32 daddr, struct ip_options *opt)
中定义:
return NF_HOOK(PF_INET, NF_IP_LOCAL_OUT, skb, NULL, rt->u.dst.dev,
output_maybe_reroute);
NF_IP_POST_ROUTING,在IP栈成功接收sk_buff包后处理,挂接点在在net/ipv4/ip_output.c的函数
__inline__ int ip_finish_output(struct sk_buff *skb)中定义:
return NF_HOOK(PF_INET, NF_IP_POST_ROUTING, skb, NULL, dev,
ip_finish_output2);
2. 挂接点操作
挂接点的操作由结构struct nf_hook_ops定义:
include/linux/netfilter.h
struct nf_hook_ops
{
struct list_head list;
/* User fills in from here down. */
nf_hookfn *hook;
int pf;
int hooknum;
/* Hooks are ordered in ascending priority. */
int priority;
};
数组中的元素说明如下:
struct list_head list: 链表头,用于将此结构接入操作链表
nf_hookfn *hook:用户定义的挂接处理函数
int pf:协议族
hooknum:挂接点
priority:优先级
每个struct nf_hook_ops结构需要挂接到nf_hooks数组中的某个链表中才起作用,每个协议族的各种处理形成一个处理链表,链表上可以挂接多个节点,每个节点是一个数据处理结构(struct nf_hook_ops),用来描述对数据包进行如何处理,这些节点根据优先级顺序进行排序处理,优先级是有符号的32位数,值越小优先级越高,如果优先级相同,则按挂接的顺序依次处理,netfilter预定义了以下优先级:
NF_IP_PRI_FIRST = INT_MIN,
NF_IP_PRI_CONNTRACK = -200,
NF_IP_PRI_MANGLE = -150,
NF_IP_PRI_NAT_DST = -100,
NF_IP_PRI_FILTER = 0,
NF_IP_PRI_NAT_SRC = 100,
NF_IP_PRI_LAST = INT_MAX,
由此可见,netfilter的处理顺序是先连接跟踪、然后是mangle处理,再目的NAT(PREROUTING),然后是过滤(FILTER),然后是源NAT(POSTROUTING),这就是为什么mangle链的规则会先执行。
在各个处理点处理数据包时,如果发现需要丢弃数据包,那么数据包就被立即释放而不再进入后面的处理点;如果该处理点的最终结论是接受,那该数据包还会继续进入下一处理点进行匹配检查,所以对于最终通过防火墙的数据包,是经过了所有处理点的匹配的。
3. 连接跟踪
连接跟踪的struct nf_hook_ops结构在net/ipv4/netfilter/ip_conntrack_standalone.c中定义,
这是对用户隐藏的,也就是用户不能通过iptables规则对此操作点进行配置,是有系统自动完成的。
定义如下:
static struct nf_hook_ops ip_conntrack_in_ops
= { { NULL, NULL }, ip_conntrack_in, PF_INET, NF_IP_PRE_ROUTING,
NF_IP_PRI_CONNTRACK };
static struct nf_hook_ops ip_conntrack_local_out_ops
= { { NULL, NULL }, ip_conntrack_local, PF_INET, NF_IP_LOCAL_OUT,
NF_IP_PRI_CONNTRACK };
分别挂接在外部数据包进入(NF_IP_PRE_ROUTING)和自身数据发出(NF_IP_LOCAL_OUT)时进行处理,其功能就是判断该数据包是什么状态,填充该数据包struct sk_buff结构中struct nf_ct_info *nfct项的值,维护连接状态表,从而实现状态检测,具体处理过程分析可见另一篇文章:Linux下如何实现状态检测。
4. 规则表(table)
为了定义每个处理点上要执行哪些规则,netfilter定义了表(table)的概念,每个表由一个struct ipt_table来描述,如缺省的filter/nat/mangle表,每个表可单独分成几个规则链,分别在几个挂接点起作用,如filter表是在只在NF_IP_LOCAL_IN/NF_IP_LOCAL_OUT/NF_IP_FORWARD上起作用,然后通过函数ipt_do_table()来实现对某个表中某个hooknum的规则集进行匹配处理。而由结构struct nf_hook_ops所定义的各个处理点的处理函数都是直接或间接的调用了ipt_do_table()函数来实现对规则集的调用。
下面是系统缺省的三个表的定义情况:
-------+---------------------------------+-------------------------------------
table | table definition | file name
-------+---------------------------------+-------------------------------------
filter | struct ipt_table packet_filter | net/ipv4/netfilter/iptable_filter.c
|
hook | NF_IP_LOCAL_IN、NF_IP_LOCAL_OUT、NF_IP_FORWARD
|
ops | struct nf_hook_ops ipt_ops[]
| net/ipv4/netfilter/iptable_filter.c
|
|NF_IP_LOCAL_IN: ipt_hook()
| 调用ipt_do_table()函数与filter表的INPUT链挂钩
|
|NF_IP_LOCAL_FORWARD: ipt_hook()
| 调用ipt_do_table()函数与filter表的FORWARD链挂钩
|
|NF_IP_LOCAL_OUT: ipt_local_out_hook()
| 调用ipt_do_table()函数与filter表的OUTPUT链挂钩
|
-------+---------------------------------+-------------------------------------
nat | struct ipt_table nat_table | net/ipv4/netfilter/ip_nat_rule.c
|
hook | NF_IP_PRE_ROUTING_IN、NF_IP_LOCAL_OUT、NF_IP_POST_ROUTING
|
ops | net/ipv4/netfilter/ip_nat_standalone.c
|
|NF_IP_PRE_ROUTING:
| struct nf_hook_ops ip_nat_in_ops, ip_nat_fn()
| 调用ip_nat_rule_find()函数
| 调用ipt_do_table()函数与nat表的PREROUTING链挂钩
|
|NF_IP_POST_ROUTING:
| struct nf_hook_ops ip_nat_out_ops,ip_nat_out()
| 调用ip_nat_fn()
| 调用ip_nat_rule_find()函数
| 调用ipt_do_table()函数与nat表的POSTROUTING链挂钩
|
-------+---------------------------------+-------------------------------------
mangle | struct ipt_table packet_mangler | net/ipv4/netfilter/iptable_mangle.c
| |
hook | 全部五个都有
|
ops | struct nf_hook_ops ipt_ops[]
| net/ipv4/netfilter/iptable_mangle.c
|
|NF_IP_PRE_ROUTING: ip_route_hook()
| 调用ipt_do_table()函数与mangle表的PREROUTING链挂钩
|
|NF_IP_LOCAL_IN: ip_route_hook()
| 调用ipt_do_table()函数与mangle表的INPUT链挂钩
|
|NF_IP_FORWARD: ipt_hook()
| 调用ipt_do_table()函数与mangle表的FORWARD链挂钩
|
|NF_IP_LOCAL_OUT: ipt_local_hook()
| 调用ipt_do_table()函数与mangle表的OUTPUT链挂钩
|
|NF_IP_POST_ROUTING: ip_route_hook()
| 调用ipt_do_table()函数与mangle表的POSTROUTING链挂钩
|
-------+---------------------------------+-------------------------------------
每个数据处理表(table)中就是定义各自的规则集,是用动态长度的数组的形式保存,每个数组节点是一个规则,规则用struct ipt_entry结构进行描述,每条规则除了基本项外,其他附加匹配条件项还形成一个动态长度的匹配数组,struct ipt_entry中保存数组头的地址,每个匹配用结构struct ipt_match描述。规则的动作如果是扩展动作的话,用struct ipt_target描述。
用户可以自己定义自己的新的表,可以以缺省表为蓝本,然后定义新的struct nf_hook_ops操作节点,在该操作节点中调用ipt_do_table()函数将该ops和新表联系起来,这样就可以用iptables定义新的规则集。如果不定义新表,用户也可以在ops处理函数中对包直接进行判断处理,适合需要对包进行固定方式处理的场合。
5. 总结
netfilter架构以nf_hooks数组为基点,挂接在内核的协议处理的几个基本点上,通过链表方式链接struct nf_hook_ops处理结构,这些处理结构可以是在内核内部自动固定处理,如状态检测;也可以通过和struct ipt_table联系,通过iptables来动态配置处理规则,实现了一个扩展性很高的防火墙处理架构,不过实现细节部分仍然很复杂,各种细节功能将在后续文章里分析。