linux中Netfilter机制的分析

来源:互联网 发布:碳晶地暖垫 知乎 编辑:程序博客网 时间:2024/04/30 08:43
 

1.概述:

2.4.x的内核相对于2.2.xIP协议栈部分有比较大的改动, Netfilter-iptables更是其一大特色,由于它功能强大,并且与内核完美结合,因此迅速成为Linux平台下进行网络应用扩展的主要利器,这些扩展不仅包括防火墙的实现--这只是Netfilter-iptables的基本功能--还包括各种报文处理工作(如报文加密、报文分类统计等),甚至还可以借助Netfilter-iptables机制来实现虚拟专用网(VPN)。本文将致力于深入剖析Netfilter-iptables的组织结构,并详细介绍如何对其进行扩展。Netfilter目前已在ARPIPv4IPv6中实现,考虑到IPv4是目前网络应用的主流,本文仅就IPv4Netfilter实现进行分析。

 

2 Netfilter Frame
Netfilter
2.4.x内核引入的,尽管它提供了对2.0.x内核中的ipfw以及2.2.x内核中的ipchains的兼容,但实际上它的工作和意义远不止于此。从上面对IP报文的流程分析中可以看出,NetfilterIP报文的处理是完全结合在一起的,同时由于其结构相对独立,又是可以完全剥离的。这种机制也是Netfilter-iptables既高效又灵活的保证之一。

在剖析Netfilter机制之前,我们还是由浅入深的从Netfilter的使用开始。

 

2.1 编译
Networking Options中选定Network packet filtering项,并将其下的IPNetfilter Configurations小节的所有选项设为Module模式。编译并安装新内核,然后重启,系统的核内Netfilter就配置好了。以下对相关的内核配置选项稍作解释,也可以参阅编译系统自带的Help

Kernel/User netlink socket】建立一类PF_NETLINK套接字族,用于核心与用户进程通信。当Netfilter需要使用用户队列来管理某些报文时就要使用这一机制;

Network packet filtering (replaces ipchains)Netfilter主选项,提供Netfilter框架;

Network packet filtering debuggingNetfilter主选项的分支,支持更详细的Netfilter报告;

IP: Netfilter Configuration】此节下是netfilter的各种选项的集合:

Connection tracking (required for masq/NAT)】连接跟踪,用于基于连接的报文处理,比如NAT

IP tables support (required for filtering/masq/NAT)】这是Netfilter的框架,NAT等应用的容器;

ipchains (2.2-style) supportipchains机制的兼容代码,在新的Netfilter结构上实现了ipchains接口;

ipfwadm (2.0-style) support2.0内核防火墙ipfwadm兼容代码,基于新的Netfilter实现。

 

2.2 总体结构
Netfilter
是嵌入内核IP协议栈的一系列调用入口,设置在报文处理的路径上。网络报文按照来源和去向,可以分为三类:流入的、流经的和流出的,其中流入和流经的报文需要经过路由才能区分,而流经和流出的报文则需要经过投递,此外,流经的报文还有一个FORWARD的过程,即从一个NIC转到另一个NICNetfilter就是根据网络报文的流向,在以下几个点插入处理过程:

NF_IP_PRE_ROUTING,在报文作路由以前执行;

NF_IP_FORWARD,在报文转向另一个NIC以前执行;

NF_IP_POST_ROUTING,在报文流出以前执行;

NF_IP_LOCAL_IN,在流入本地的报文作路由以后执行;

NF_IP_LOCAL_OUT,在本地报文做流出路由前执行。

 

如图所示:

5 Netfilter HOOK位置

Netfilter框架为多种协议提供了一套类似的钩子(HOOK),用一个struct list_head nf_hooks[NPROTO][NF_MAX_HOOKS]二维数组结构存储,一维为协议族,二维为上面提到的各个调用入口。每个希望嵌入Netfilter中的模块都可以为多个协议族的多个调用点注册多个钩子函数(HOOK),这些钩子函数将形成一条函数指针链,每次协议栈代码执行到NF_HOOK()函数时(有多个时机),都会依次启动所有这些函数,处理参数所指定的协议栈内容。

每个注册的钩子函数经过处理后都将返回下列值之一,告知Netfilter核心代码处理结果,以便对报文采取相应的动作:

NF_ACCEPT:继续正常的报文处理;

NF_DROP:将报文丢弃;

NF_STOLEN:由钩子函数处理了该报文,不要再继续传送;

NF_QUEUE:将报文入队,通常交由用户程序处理;

NF_REPEAT:再次调用该钩子函数。

 

2.3 IPTables

 

Netfilter-iptables由两部分组成,一部分是Netfilter的钩子,另一部分则是知道这些钩子函数如何工作的一套规则--这些规则存储在被称为iptables的数据结构之中。钩子函数通过访问iptables来判断应该返回什么值给Netfilter框架。

在现有(kernel 2.4.21)中已内建了三个iptablesfilternatmangle,绝大部分报文处理功能都可以通过在这些内建(built-in)的表格中填入规则完成:

 

filter,该模块的功能是过滤报文,不作任何修改,或者接受,或者拒绝。它在NF_IP_LOCAL_INNF_IP_FORWARDNF_IP_LOCAL_OUT三处注册了钩子函数,也就是说,所有报文都将经过filter模块的处理。

nat,网络地址转换(Network Address Translation),该模块以Connection Tracking模块为基础,仅对每个连接的第一个报文进行匹配和处理,然后交由Connection Tracking模块将处理结果应用到该连接之后的所有报文。natNF_IP_PRE_ROUTINGNF_IP_POST_ROUTING注册了钩子函数,如果需要,还可以在NF_IP_LOCAL_INNF_IP_LOCAL_OUT两处注册钩子,提供对本地报文(出/入)的地址转换。nat仅对报文头的地址信息进行修改,而不修改报文内容,按所修改的部分,nat可分为源NATSNAT)和目的NATDNAT)两类,前者修改第一个报文的源地址部分,而后者则修改第一个报文的目的地址部分。SNAT可用来实现IP伪装,而DNAT则是透明代理的实现基础。

mangle,属于可以进行报文内容修改的IP Tables,可供修改的报文内容包括MARKTOSTTL等,mangle表的操作函数嵌入在NetfilterNF_IP_PRE_ROUTINGNF_IP_LOCAL_OUT两处。

内核编程人员还可以通过注入模块,调用Netfilter的接口函数创建新的iptables。在下面的Netfilter-iptables应用中我们将进一步接触Netfilter的结构和使用方式。

 

2.4 Netfilter配置工具
iptables
是专门针对2.4.x内核的Netfilter制作的核外配置工具,通过socket接口对Netfilter进行操作,创建socket的方式如下:

socket(TC_AF, SOCK_RAW, IPPROTO_RAW)

其中TC_AF就是AF_INET。核外程序可以通过创建一个"原始IP套接字"获得访问Netfilter的句柄,然后通过getsockopt()setsockopt()系统调用来读取、更改Netfilter设置,详情见下。

iptables功能强大,可以对核内的表进行操作,这些操作主要指对其中规则链的添加、修改、清除,它的命令行参数主要可分为四类:指定所操作的IP Tables-t);指定对该表所进行的操作(-A-D等);规则描述和匹配;对iptables命令本身的指令(-n等)。在下面的例子中,我们通过iptables将访问10.0.0.153端口(DNS)的TCP连接引导到192.168.0.1地址上。

iptables -t nat -A PREROUTING -p TCP -i eth0 -d 10.0.0.1 --dport 53 -j DNAT --to-destination 192.168.0.1

由于iptables是操作核内Netfilter的用户界面,有时也把Netfilter-iptables简称为iptables,以便与ipchainsipfwadm等老版本的防火墙并列。

2.5 iptables核心数据结构
2.5.1

Linux内核里,iptablesstruct ipt_table表示,定义如下(include/linux/netfilter_ipv4/ip_tables.h):

 

struct ipt_table

{

              struct list_head list;

                     /* 表链 */

              char name[IPT_TABLE_MAXNAMELEN];

                     /* 表名,如"filter""nat"等,为了满足自动模块加载的设计,包含该表的模块应命名为iptable_'name'.o */

              struct ipt_replace *table;

                     /* 表模子,初始为initial_table.repl */

              unsigned int valid_hooks;

                     /* 位向量,标示本表所影响的HOOK */

              rwlock_t lock;

                     /* 读写锁,初始为打开状态 */

              struct ipt_table_info *private;

                     /* iptable的数据区,见下 */

              struct module *me;

                     /* 是否在模块中定义 */

};

struct ipt_table_info是实际描述表的数据结构(net/ipv4/netfilter/ip_tables.c):

struct ipt_table_info

{

              unsigned int size;

                     /* 表大小 */

              unsigned int number;

                     /* 表中的规则数 */

              unsigned int initial_entries;

                     /* 初始的规则数,用于模块计数 */

              unsigned int hook_entry[NF_IP_NUMHOOKS];

                     /* 记录所影响的HOOK的规则入口相对于下面的entries变量的偏移量 */

              unsigned int underflow[NF_IP_NUMHOOKS];

                     /* hook_entry相对应的规则表上限偏移量,当无规则录入时,相应的hook_entryunderflow均为0 */

              char entries[0] ____cacheline_aligned;

                     /* 规则表入口 */

};

例如内建的filter表初始定义如下(net/ipv4/netfilter/iptable_filter.c):

 

static struct ipt_table packet_filter

= { { NULL, NULL },           // 链表

"filter",                         // 表名

      &initial_table.repl,          // 初始的表模板

    FILTER_VALID_HOOKS,// 定义为((1 << NF_IP6_LOCAL_IN) | (1 << NF_IP6_FORWARD) | (1 << NF_IP6_LOCAL_OUT))

           即关心INPUTFORWARDOUTPUT三点

      RW_LOCK_UNLOCKED,//

NULL,                         // 初始的表数据为空

             THIS_MODULE           // 模块标示

};

经过调用ipt_register_table(&packet_filter)后,filter表的private数据区即参照模板填好了。

2.5.2 规则

规则用struct ipt_entry结构表示,包含匹配用的IP头部分、一个Target0个或多个Match。由于Match数不定,所以一条规则实际的占用空间是可变的。结构定义如下(include/linux/netfilter_ipv4):

 

struct ipt_entry

{

              struct ipt_ip ip;

                     /* 所要匹配的报文的IP头信息 */

              unsigned int nfcache;

                     /* 位向量,标示本规则关心报文的什么部分,暂未使用 */

              u_int16_t target_offset;

                     /* target区的偏移,通常target区位于match区之后,而match区则在ipt_entry的末尾;

                     初始化为sizeof(struct ipt_entry),即假定没有match */

              u_int16_t next_offset;

                     /* 下一条规则相对于本规则的偏移,也即本规则所用空间的总和,

                     初始化为sizeof(struct ipt_entry)+sizeof(struct ipt_target),即没有match */

       unsigned int comefrom;

                     /* 位向量,标记调用本规则的HOOK号,可用于检查规则的有效性 */

       struct ipt_counters counters;

                     /* 记录该规则处理过的报文数和报文总字节数 */

       unsigned char elems[0];

                     /*target或者是match的起始位置 */

}

规则按照所关注的HOOK点,被放置在struct ipt_table::private->entries之后的区域,比邻排列。

2.5.3 规则填写过程

在了解了iptables在核心中的数据结构之后,我们再通过遍历一次用户通过iptables配置程序填写规则的过程,来了解这些数据结构是如何工作的了。

一个最简单的规则可以描述为拒绝所有转发报文,用iptables命令表示就是:

 

iptables -A FORWARD -j DROP;

iptables应用程序将命令行输入转换为程序可读的格式(iptables-standalone.c::main()::do_command(),然后再调用libiptc库提供的iptc_commit()函数向核心提交该操作请求。在libiptc/libiptc.c中定义了iptc_commit()(即TC_COMMIT()),它根据请求设置了一个struct ipt_replace结构,用来描述规则所涉及的表(filter)和HOOK点(FORWARD)等信息,并在其后附接当前这条规则--一个struct ipt_entry结构(实际上也可以是多个规则entry)。组织好这些数据后,iptc_commit()调用setsockopt()系统调用来启动核心处理这一请求:

 

setsockopt(

sockfd,                        //通过socket(TC_AF, SOCK_RAW, IPPROTO_RAW)创建的套接字,其中TC_AFAF_INET

      TC_IPPROTO,             //IPPROTO_IP

      SO_SET_REPLACE,    //IPT_SO_SET_REPLACE

repl,                             //struct ipt_replace结构

sizeof(*repl) + (*handle)->entries.size)        //ipt_replace加上后面的ipt_entry

核心对于setsockopt()的处理是从协议栈中一层层传递上来的,调用过程如下图所示:

6 规则填写过程

nf_sockopts是在iptables进行初始化时通过nf_register_sockopt()函数生成的一个struct nf_sockopt_ops结构,对于ipv4来说,在net/ipv4/netfilter/ip_tables.c中定义了一个ipt_sockopts变量(struct nf_sockopt_ops),其中的set操作指定为do_ipt_set_ctl(),因此,当nf_sockopt()调用对应的set操作时,控制将转入net/ipv4/netfilter/ip_tables.c::do_ipt_set_ctl()中。

对于IPT_SO_SET_REPLACE命令,do_ipt_set_ctl()调用do_replace()来处理,该函数将用户层传入的struct ipt_replacestruct ipt_entry组织到filter(根据struct ipt_replace::name项)表的hook_entry[NF_IP_FORWARD]所指向的区域,如果是添加规则,结果将是filter表的privatestruct ipt_table_info)项的hook_entry[NF_IP_FORWARD]underflow[NF_IP_FORWARD]的差值扩大(用于容纳该规则),private->number1

2.5.4 规则应用过程

以上描述了规则注入核内iptables的过程,这些规则都挂接在各自的表的相应HOOK入口处,当报文流经该HOOK时进行匹配,对于与规则匹配成功的报文,调用规则对应的Target来处理。仍以转发的报文为例,假定filter表中添加了如上所述的规则:拒绝所有转发报文。

1.2节所示,经由本地转发的报文经过路由以后将调用ip_forward()来处理,在ip_forward()返回前,将调用如下代码:

 

NF_HOOK(PF_INET, NF_IP_FORWARD, skb, skb->dev, dev2, ip_forward_finish)

NF_HOOK是这样一个宏(include/linux/netfilter.h):

#define NF_HOOK(pf, hook, skb, indev, outdev, okfn)                     /

(list_empty(&nf_hooks[(pf)][(hook)])                              /

 ? (okfn)(skb)                                                        /

 : nf_hook_slow((pf), (hook), (skb), (indev), (outdev), (okfn)))

也就是说,如果nf_hooks[PF_INET][NF_IP_FORWARD]所指向的链表为空(即该钩子上没有挂处理函数),则直接调用ip_forward_finish(skb)完成ip_forward()的操作;否则,则调用net/core/netfilter.c::nf_hook_slow()转入Netfilter的处理。

这里引入了一个nf_hooks链表二维数组:

 

struct list_head nf_hooks[NPROTO][NF_MAX_HOOKS];

每一个希望使用Netfilter挂钩的表都需要将表处理函数在nf_hooks数组的相应链表上进行注册。对于filter表来说,在其初始化(net/ipv4/netfilter/iptable_filter.c::init())时,调用了net/core/netfilter.c::nf_register_hook(),将预定义的三个struct nf_hook_ops结构(分别对应INPUTFORWARDOUTPUT链)连入链表中:

 

struct nf_hook_ops

{

              struct list_head list;

                     //链表

              nf_hookfn *hook;

                     //处理函数指针

              int pf;

                     //协议号

              int hooknum;

                     //HOOK

              int priority;

                     //优先级,在nf_hooks链表中各处理函数按优先级排序

};

对于filter表来说,FORWARD点的hook设置成ipt_hook(),它将直接调用ipt_do_table()。几乎所有处理函数最终都将调用ipt_do_table()来查询表中的规则,以调用对应的target。下图所示即为在FORWARD点上调用nf_hook_slow()的过程:

7 规则应用流程

2.5.5 Netfilter的结构特点

由上可见,nf_hooks链表数组是联系报文处理流程和iptables的纽带,在iptables初始化(各自的init()函数)时,一方面调用nf_register_table()建立规则容器,另一方面还要调用nf_register_hook()将自己的挂钩愿望表达给Netfilter框架。初始化完成之后,用户只需要通过用户级的iptables命令操作规则容器(添加规则、删除规则、修改规则等),而对规则的使用则完全不用操心。如果一个容器内没有规则,或者nf_hooks上没有需要表达的愿望,则报文处理照常进行,丝毫不受Netfilter-iptables的影响;即使报文经过了过滤规则的处理,它也会如同平时一样重新回到报文处理流程上来,因此从宏观上看,就像在行车过程中去了一趟加油站。

Netfilter不仅仅有此高效的设计,同时还具备很大的灵活性,这主要表现在Netfilter-iptables中的很多部分都是可扩充的,包括TableMatchTarget以及Connection Track Protocol Helper,下面一节将介绍这方面的内容。