Linux协议栈-netfilter(1)-框架

文章转载----------------------点击打开链接

1. netfilter框架

Netfilter 是Linux内核中进行数据包过滤、连接跟踪、地址转换等的主要实现框架。当我们希望过滤特定的数据包或者需要修改数据包的内容再发送出去,这些动作主要都在netfilter中完成。

iptables工具就是用户空间和内核的Netfilter模块通信的手段,iptables命令提供很多选项来实现过滤数据包的各种操作,所以,我们在定义数据包过滤规则时,并不需要去直接修改内核中的netfilter模块,后面会讲到iptables命令如何作用于内核中的netfilter。

Netfilter的实质就是定义一系列的hook点(挂钩),每个hook点上可以挂载多个hook函数,hook函数中就实现了我们要对数据包的内容做怎样的修改、以及要将数据包放行还是过滤掉。数据包进入netfilter框架后,实际上就是依次经过所有hook函数的处理,数据包的命运就掌握在这些hook函数的手里。

本文基于内核版本2.6.31。

所有的hook点都放在一个全局的二维数组,每个hook点上的hook函数按照优先级顺序注册到一个链表中,注册的接口为nf_register_hook()。这个二维数组的定义如下:

struct list_head nf_hooks[NFPROTO_NUMPROTO][NF_MAX_HOOKS]__read_mostly;

其中NFPROTO_NUMPROTO 为netfilter支持的协议类型:

enum {

   NFPROTO_UNSPEC=  0,

   NFPROTO_IPV4   =  2,

   NFPROTO_ARP    =  3,

   NFPROTO_BRIDGE=  7,

   NFPROTO_IPV6   = 10,

   NFPROTO_DECNET= 12,

   NFPROTO_NUMPROTO,

};

我们看到枚举值并不连续,这是为了和协议族定义的值保持一致,因为注册hook函数的时候是根据协议族来注册的,如PF_INET=2,则对应的NFPROTO_IPV4=2。

NF_MAX_HOOKS是每种协议最多可挂的hook点的个数,它的值是8。如IPv4挂了5个,分别为:

enum nf_inet_hooks {

   NF_INET_PRE_ROUTING,

   NF_INET_LOCAL_IN,

   NF_INET_FORWARD,

   NF_INET_LOCAL_OUT,

   NF_INET_POST_ROUTING,

   NF_INET_NUMHOOKS

};

也就是说,对于IPv4协议来讲,一共有5个hook点,这5个hook点上注册的hook函数放在下列链表中:

Hook点

该hook点上注册的hook函数链表的表头

NF_INET_PRE_ROUTING

nf_hooks[NFPROTO_IPV4][ NF_INET_PRE_ROUTING]

NF_INET_LOCAL_IN

nf_hooks[NFPROTO_IPV4][ NF_INET_LOCAL_IN]

NF_INET_FORWARD

nf_hooks[NFPROTO_IPV4][ NF_INET_FORWARD]

NF_INET_LOCAL_OUT

nf_hooks[NFPROTO_IPV4][ NF_INET_LOCAL_OUT]

NF_INET_POST_ROUTING

nf_hooks[NFPROTO_IPV4][ NF_INET_POST_ROUTING]

对于发往本地的数据包,会依次经过NF_INET_PRE_ROUTING和NF_INET_LOCAL_IN两个hook点的处理。

对于本地向外发出去的数据包,会依次经过NF_INET_LOCAL_OUT和NF_INET_POST_ROUTING两个hook点的处理。

对于经过本机转发的数据包,会依次经过F_INET_PRE_ROUTING、NF_INET_FORWARD和NF_INET_POST_ROUTING三个hook点的处理。


2. netfilter中的表

为了便于和用户态的iptables命令交互,引入了表的概念,iptables命令定义了各种规则,这些规则的目的就是对数据包进行过滤和处理。为了将规则分类,在内核中,用户设置的规则被放到了不同的表中。同时也方便管理和查找规则。

netfilter中的表存放在&init_net->xt.tables[NFPROTO_NUMPROTO]链表数组中,tables是一个list_head类型的链表,NFPROTO_NUMPROTO是一个枚举类型,包括各协议族,上面已经提到过。

可以看出每个协议有单独的链表来存放自己所有的netfilter的表,例如IPv4一共有5张表:

filter表:对数据包进行过滤。

NAT表:对数据包进行地址转换。

mangle表:主要用来修改数据包。

security表:用于实现强制访问控制安全模型。

raw表:其他各种用途。

常用的两个表就是filter表(用来进行数据包过滤)和NAT表(NAT转换规则以及作用于连接跟踪)。

而hook函数就是去遍历这些表中的规则,并根据这些规则去处理数据包。

这些表都是struct xt_table类型的。例如ipv4的netfilter表称为iptables,可以在&init_net ->ipv4中找到,如下:

       struct xt_table             *iptable_filter;

       struct xt_table             *iptable_mangle;

       struct xt_table             *iptable_raw;

       struct xt_table             *arptable_filter;

       struct xt_table             *iptable_security;

       struct xt_table             *nat_table;

表结构的注册通过xt_register_table()函数完成,注册的表都放在&init_net ->xt.tables[]链表数组中。

而查找和执行表的规则是通过xxt_do_table()完成的,如ipt_do_table()、ip6t_do_table()、arpt_do_table()。

我们暂时只关心ipv4,通过ipt_do_table()函数的调用者我们可以看到哪些hook点会去查找哪些表:

caller

module

hook点

priority

table

ipt_local_in_hook

iptable filter

NF_INET_LOCAL_IN

NF_IP_PRI_FILTER

filter

ipt_hook

iptable filter

NF_INET_FORWARD

NF_IP_PRI_FILTER

filter

ipt_local_out_hook

iptable filter

NF_INET_LOCAL_OUT

NF_IP_PRI_FILTER

filter

ipt_pre_routing_hook

iptables mangle

NF_INET_PRE_ROUTING

NF_IP_PRI_MANGLE

mangle

ipt_local_in_hook

iptables mangle

NF_INET_LOCAL_IN

NF_IP_PRI_MANGLE

mangle

ipt_forward_hook

iptables mangle

NF_INET_FORWARD

NF_IP_PRI_MANGLE

mangle

ipt_local_hook

iptables mangle

NF_INET_LOCAL_OUT

NF_IP_PRI_MANGLE

mangle

ipt_post_routing_hook

iptables mangle

NF_INET_POST_ROUTING

NF_IP_PRI_MANGLE

mangle

ipt_local_in_hook

iptables security

NF_INET_LOCAL_IN

NF_IP_PRI_SECURITY

security

ipt_forward_hook

iptables security

NF_INET_FORWARD

NF_IP_PRI_SECURITY

security

ipt_local_out_hook

iptables security

NF_INET_LOCAL_OUT

NF_IP_PRI_SECURITY

security

ipt_hook

iptables raw

NF_INET_PRE_ROUTING

NF_IP_PRI_RAW

raw

ipt_local_hook

iptables raw

NF_INET_LOCAL_OUT

NF_IP_PRI_RAW

raw

nf_nat_rule_find

nat

NAT的4个hook点

NF_IP_PRI_NAT_DST/

NF_IP_PRI_NAT_SRC

nat

可以看到iptable有5张表:filter、mangle、security、raw和nat表。这些表中的规则可以通过用户态的iptables程序来配置,netfilter中的hook函数会去根据用户配置的iptables规则来处理数据包。

上图是IPv4协议中每个hook点上注册的hook函数(虚线框中的函数,按照箭头方向的优先级顺序被调用),注意,图中假设security/mangle/raw这三个表中没有规则,所以没有相应的hook函数。netfilter的入口点为ip_rcv()函数。图中有四种hook函数:

1. 紫色的两个函数用来对分片包进行重组。

2. 蓝色的四个函数用来实现数据包的连接跟踪(conntrack)模块。

3. 绿色的三个函数用来用来进行数据包的过滤(查找filter表中的规则)。

4. 粉色的四个函数用来实现NAT地址转换(查找NAT表中的规则)。

3. 遍历hook函数

上一节我们看到了每个hook点上都注册了哪些hook函数,每当数据包经过某个hook点时,netfilter就遍历这个hook上的所有hook函数来处理数据包。遍历的宏为:

NF_HOOK(pf, hook, skb, indev, outdev, okfn)

参数解释:

pf:协议族,如PF_INET对应IPv4。

hook:指明要遍历那个hook点,如NF_INET_PRE_ROUTING。

skb:待处理的数据包的sk_buff结构指针。

indev:数据包的来源设备。

outdev:数据包的目的去向设备。

okfn:函数指针。如果数据包成功走完hook点上的所有hook函数,接下来执行okfn函数,函数名一般名为xxx_finish。

NF_HOOK()调用nf_hook_slow()来执行遍历动作,其实现就是去遍历注册到nf_hooks[pf][hook]链表中的hook函数并执行,根据返回结果来决定数据包的去向。

hook函数返回值的说明:

  • NF_ACCEPT: 接受分组,使之穿过网络实现中剩余的协议层(或该hook点上后续的hook函数)。
  • NF_STOLEN: 挂钩函数窃取了一个分组,并处理了该分组,此时,该分组已与内核无关,不必再调用其他挂钩,还必须取消其他协议层的处理。
  • NF_DROP: 丢弃分组,其中的数据可以释放了。
  • NF_QUEUE: 将分组置于一个等待队列上,以便其数据可以由用户空间代码处理。不会执行其他hook函数。
  • NF_REPEAT: 再次调用该hook函数。

4. netfilter的一些补充

※.  每个hook代码都分为两部分,即hook函数和xx_finish()函数,这样做,是因为编译内核时可以选择不编译netfilter模块,xx_finish函数的内容是即使没有netfilter模块,内核也要必须对数据包做的事情。

※.  netfilter可以指定一个优先级,低于这个优先级的hook函数不被执行,nf_hook_thresh(..., int thresh, int cond)函数的thresh参数就是优先级。同时还可以通过cond参数(取0或1)更干脆的取消整个链的遍历。

※.  我们发现遍历hook的代码都放在函数的最后,如下面代码中的ip_forward_finish函数,

NF_HOOK(PF_INET, NF_INET_FORWARD, skb, skb->dev,rt->u.dst.dev, ip_forward_finish);

这样的话,ip_forward_finish函数即使不被声明为inline,其执行效率也挺高,因为GNU C有一个尾部过程调用的优化,省去了函数返回的开销。

※.  hook点NF_INET_PRE_ROUTING在协议收包时进入。而NF_INET_LOCAL_OUT在上层发包的时候进入,在ip_push_pending_frames(),ip_queue_xmit()和raw_send_hdrine()等函数会去走该hook点。

后面几篇文章会依次介绍netfilter中的连接跟踪和NAT模块,并分析用户态的iptables工具是如何作用于netfilter的,为了方便和netfilter交互,iptables工具中也有表(table)的概念,并将hook点称为链(chain)