Linux NetFilter/iptables 简介

来源:互联网 发布:红楼梦好看吗 知乎 编辑:程序博客网 时间:2024/04/29 20:41

netfilter/iptables 系统是如何工作的?

netfilter/iptables IP 信息包过滤系统是一种功能强大的工具,可用于添加、编辑和除去规则,这些规则是在做信息包过滤决定时,防火墙所遵循和组成的规则。这些规则存储在专用的信息包过滤表中,而这些表集成在 Linux 内核中。在信息包过滤表中,规则被分组放在我们所谓的 链(chain)中。我马上会详细讨论这些规则以及如何建立这些规则并将它们分组在链中。

 

虽然 netfilter/iptables IP 信息包过滤系统被称为单个实体,但它实际上由两个组件 netfilteriptables 组成。

netfilter 组件也称为 内核空间(kernelspace),是内核的一部分,由一些信息包过滤表组成,这些表包含内核用来控制信息包过滤处理的规则集。

 

iptables 组件是一种工具,也称为 用户空间(userspace),它使插入、修改和除去信息包过滤表中的规则变得容易。除非您正在使用 Red Hat Linux 7.1 或更高版本,否则需要从 netfilter.org 下载该工具并安装使用它。

 

通过使用用户空间,可以构建自己的定制规则,这些规则存储在内核空间的信息包过滤表中。这些规则具有 目标,它们告诉内核对来自某些源、前往某些目的地或具有某些协议类型的信息包做些什么。如果某个信息包与规则匹配,那么使用目标 ACCEPT 允许该信息包通过。还可以使用目标 DROPREJECT 来阻塞并杀死信息包。对于可对信息包执行的其它操作,还有许多其它目标。

根据规则所处理的信息包的类型,可以将规则分组在链中。处理入站信息包的规则被添加到 INPUT 链中。处理出站信息包的规则被添加到 OUTPUT 链中。处理正在转发的信息包的规则被添加到 FORWARD 链中。这三个链是基本信息包过滤表中内置的缺省主链。另外,还有其它许多可用的链的类型(如 PREROUTINGPOSTROUTING ),以及提供用户定义的链。每个链都可以有一个 策略,它定义“缺省目标”,也就是要执行的缺省操作,当信息包与链中的任何规则都不匹配时,执行此操作。

建立规则并将链放在适当的位置之后,就可以开始进行真正的信息包过滤工作了。这时内核空间从用户空间接管工作。当信息包到达防火墙时,内核先检查信息包的头信息,尤其是信息包的目的地。我们将这个过程称为 路由

如果信息包源自外界并前往系统,而且防火墙是打开的,那么内核将它传递到内核空间信息包过滤表的 INPUT 链。如果信息包源自系统内部或系统所连接的内部网上的其它源,并且此信息包要前往另一个外部系统,那么信息包被传递到 OUTPUT 链。类似的,源自外部系统并前往外部系统的信息包被传递到 FORWARD 链。

接下来,将信息包的头信息与它所传递到的链中的每条规则进行比较,看它是否与某条规则完全匹配。如果信息包与某条规则匹配,那么内核就对该信息包执行由该规则的目标指定的操作。但是,如果信息包与这条规则不匹配,那么它将与链中的下一条规则进行比较。最后,如果信息包与链中的任何规则都不匹配,那么内核将参考该链的策略来决定如何处理该信息包。理想的策略应该告诉内核 DROP 该信息包。

 

建立规则和链

通过向防火墙提供有关对来自某个源、到某个目的地或具有特定协议类型的信息包要做些什么的指令,规则控制信息包的过滤。通过使用 netfilter/iptables 系统提供的特殊命令 iptables ,建立这些规则,并将其添加到内核空间的特定信息包过滤表内的链中。关于添加/除去/编辑规则的命令的一般语法如下:

$ iptables [-t table] command [match] [target]

表(table)

[-t table] 选项允许使用标准表之外的任何表。表是包含仅处理特定类型信息包的规则和链的信息包过滤表。有三种可用的表选项: filternatmangle 。该选项不是必需的,如果未指定,则 filter 用作缺省表。

filter 表用于一般的信息包过滤,它包含 INPUTOUTPUTFORWARD 链。nat 表用于要转发的信息包,它包含 PREROUTINGOUTPUTPOSTROUTING 链。如果信息包及其头内进行了任何更改,则使用 mangle 表。该表包含一些规则来标记用于高级路由的信息包,该表包含 PREROUTINGOUTPUT 链。

注: PREROUTING 链由指定信息包一到达防火墙就改变它们的规则所组成,而 POSTROUTING 链由指定正当信息包打算离开防火墙时改变它们的规则所组成。

命令(command)

上面这条命令中具有强制性的 command 部分是 iptables 命令的最重要部分。它告诉 iptables 命令要做什么,例如,插入规则、将规则添加到链的末尾或删除规则。以下是最常用的一些命令:

  • -A--append该命令将一条规则附加到链的末尾。
    示例:

    $ iptables -A INPUT -s 205.168.0.1 -j ACCEPT

    该示例命令将一条规则附加到 INPUT 链的末尾,确定来自源地址 205.168.0.1 的信息包可以 ACCEPT
  • -D--delete通过用 -D 指定要匹配的规则或者指定规则在链中的位置编号,该命令从链中删除该规则。下面的示例显示了这两种方法。
    示例:

    $ iptables -D INPUT --dport 80 -j DROP
    $ iptables -D OUTPUT 3

    第一条命令从 INPUT 链删除规则,它指定 DROP 前往端口 80 的信息包。第二条命令只是从 OUTPUT 链删除编号为 3 的规则。
  • -P--policy该命令设置链的缺省目标,即策略。所有与链中任何规则都不匹配的信息包都将被强制使用此链的策略。
    示例:

    $ iptables -P INPUT DROP

    该命令将 INPUT 链的缺省目标指定为 DROP 。这意味着,将丢弃所有与 INPUT 链中任何规则都不匹配的信息包。
  • -N--new-chain用命令中所指定的名称创建一个新链。
    示例:

    $ iptables -N allowed-chain

  • -F--flush如果指定链名,该命令删除链中的所有规则,如果未指定链名,该命令删除所有链中的所有规则。此参数用于快速清除。
    示例:

    $ iptables -F FORWARD
    $ iptables -F

  • -L--list列出指定链中的所有规则。
    示例:

    $ iptables -L allowed-chain

匹配(match)

iptables 命令的可选 match 部分指定信息包与规则匹配所应具有的特征(如源和目的地地址、协议等)。匹配分为两大类: 通用匹配特定于协议的匹配。这里,我将研究可用于采用任何协议的信息包的通用匹配。下面是一些重要的且常用的通用匹配及其示例和说明:

  • -p--protocol该通用协议匹配用于检查某些特定协议。协议示例有 TCPUDPICMP 、用逗号分隔的任何这三种协议的组合列表以及 ALL (用于所有协议)。 ALL 是缺省匹配。可以使用 ! 符号,它表示不与该项匹配。
    示例:

    $ iptables -A INPUT -p TCP, UDP
    $ iptables -A INPUT -p ! ICMP

    在上述示例中,这两条命令都执行同一任务 — 它们指定所有 TCPUDP 信息包都将与该规则匹配。通过指定 ! ICMP ,我们打算允许所有其它协议(在这种情况下是 TCPUDP ),而将 ICMP 排除在外。
  • -s--source该源匹配用于根据信息包的源 IP 地址来与它们匹配。该匹配还允许对某一范围内的 IP 地址进行匹配,可以使用 ! 符号,表示不与该项匹配。缺省源匹配与所有 IP 地址匹配。
    示例:

    $ iptables -A OUTPUT -s 192.168.1.1
    $ iptables -A OUTPUT -s 192.168.0.0/24
    $ iptables -A OUTPUT -s ! 203.16.1.89

    第二条命令指定该规则与所有来自 192.168.0.0 到 192.168.0.24 的 IP 地址范围的信息包匹配。第三条命令指定该规则将与除来自源地址 203.16.1.89 外的任何信息包匹配。
  • -d--destination该目的地匹配用于根据信息包的目的地 IP 地址来与它们匹配。该匹配还允许对某一范围内 IP 地址进行匹配,可以使用 ! 符号,表示不与该项匹配。
    示例:

    $ iptables -A INPUT -d 192.168.1.1
    $ iptables -A INPUT -d 192.168.0.0/24
    $ iptables -A OUTPUT -d ! 203.16.1.89

目标(target)

我们已经知道,目标是由规则指定的操作,对与那些规则匹配的信息包执行这些操作。除了允许用户定义的目标之外,还有许多可用的目标选项。下面是常用的一些目标及其示例和说明:

  • ACCEPT当信息包与具有 ACCEPT 目标的规则完全匹配时,会被接受(允许它前往目的地),并且它将停止遍历链(虽然该信息包可能遍历另一个表中的其它链,并且有可能在那里被丢弃)。该目标被指定为 -j ACCEPT
  • DROP当信息包与具有 DROP 目标的规则完全匹配时,会阻塞该信息包,并且不对它做进一步处理。该目标被指定为 -j DROP
  • REJECT该目标的工作方式与 DROP 目标相同,但它比 DROP 好。和 DROP 不同, REJECT 不会在服务器和客户机上留下死套接字。另外, REJECT 将错误消息发回给信息包的发送方。该目标被指定为 -j REJECT
    示例:

    $ iptables -A FORWARD -p TCP --dport 22 -j REJECT

  • RETURN在规则中设置的 RETURN 目标让与该规则匹配的信息包停止遍历包含该规则的链。如果链是如 INPUT 之类的主链,则使用该链的缺省策略处理信息包。它被指定为 -jump RETURN 。示例:

    $ iptables -A FORWARD -d 203.16.1.89 -jump RETURN

还有许多用于建立高级规则的其它目标,如 LOGREDIRECTMARKMIRRORMASQUERADE 等。

保存规则

现在,您已经学习了如何建立基本的规则和链以及如何从信息包过滤表中添加或删除它们。但是,您应该记住:用上述方法所建立的规则会被保存到内核中,当重新引导系统时,会丢失这些规则。所以,如果您将没有错误的且有效的规则集添加到信息包过滤表,同时希望在重新引导之后再次使用这些规则,那么必须将该规则集保存在文件中。可以使用 iptables-save命令来做到这一点:

$ iptables-save > iptables-script

现在,信息包过滤表中的所有规则都被保存在文件 iptables-script 中。无论何时再次引导系统,都可以使用 iptables-restore命令将规则集从该脚本文件恢复到信息包过滤表,如下所示:

$ iptables-restore iptables-script

如果您愿意在每次引导系统时自动恢复该规则集,则可以将上面指定的这条命令放到任何一个初始化 shell 脚本中。

 

IPv4 代码中 netfilter 的接口

netfilter 在 Linux kernel 中的 IPv4、IPv6 和 DECnet 等网络协议栈中都有相应的实现。本文限于篇幅,将只介绍其中最让大多数读者朋友们感兴趣的 IPv4 协议栈上的 netfilter 的实现。

我们在编译 Linux kernel 的过程中一定会注意到,netfilter 是一个在编译过程中可选的部件。也就是说,用户在编译内核的过程中,可以按照自己的需要,决定是否要在自己的内核中编译进去 netfilter 的 kernel 支持。这就带给我们一个提示,实现 netfilter 的代码对于实现 IPv4 协议栈的代码的影响应该会是尽量的小,不那么引人注目才对。否则的话,IPv4 协议栈的代码维护工作就不得不和实现 netfilter 的代码的维护工作搅在一起,让人头疼了。

事实也的确如此,IPv4 协议栈为了实现对 netfilter 架构的支持,在 IP packet 在 IPv4 协议栈上的游历路线之中,仔细选择了五个参考点。在这五个参考点上,各引入了一行对 NF_HOOK() 宏函数的一个相应的调用。这五个参考点被分别命名为 PREROUTING,LOCAL-IN,FORWARD,LOCAL-OUT 和 POSTROUTING。关于这五个参考点的含义,在 iptables 的使用说明中有准确的叙述,相信读者朋友们都应该了解了。从如下的 grep 输出,我们可以看到 IPv4 协议栈实现代码对 NF_HOOK() 宏函数的调用:

zhaoway@qhq ~/linux-2.4.19/net/ipv4 $ grep -n NF_HOOK *.carp.c:591:NF_HOOK(NF_ARP, NF_ARP_OUT, skb, NULL, dev, dev_queue_xmit);arp.c:871:return NF_HOOK(NF_ARP, NF_ARP_IN, skb, dev, NULL, arp_process);igmp.c:187:/* Don't just hand NF_HOOK skb->dst->output, in case netfilter hookigmp.c:252:return NF_HOOK(PF_INET, NF_IP_LOCAL_OUT, skb, NULL, rt->u.dst.dev,ip_forward.c:145:return NF_HOOK(PF_INET, NF_IP_FORWARD, skb, skb->dev, dev2,ip_gre.c:668:/* Need this wrapper because NF_HOOK takes the function address */ip_input.c:302:return NF_HOOK(PF_INET, NF_IP_LOCAL_IN, skb, skb->dev, NULL,ip_input.c:437:return NF_HOOK(PF_INET, NF_IP_PRE_ROUTING, skb, dev, NULL,ip_output.c:111:/* Don't just hand NF_HOOK skb->dst->output, in case netfilter hookip_output.c:156:return NF_HOOK(PF_INET, NF_IP_LOCAL_OUT, skb, NULL, rt->u.dst.dev,ip_output.c:191:return NF_HOOK(PF_INET, NF_IP_POST_ROUTING, skb, NULL, dev,ip_output.c:233:NF_HOOK(PF_INET, NF_IP_POST_ROUTING, newskb, NULL,ip_output.c:249:NF_HOOK(PF_INET, NF_IP_POST_ROUTING, newskb, NULL,ip_output.c:400:return NF_HOOK(PF_INET, NF_IP_LOCAL_OUT, skb, NULL, rt->u.dst.dev,ip_output.c:603:err = NF_HOOK(PF_INET, NF_IP_LOCAL_OUT, skb, NULL, ip_output.c:714:err = NF_HOOK(PF_INET, NF_IP_LOCAL_OUT, skb, NULL, rt->u.dst.dev,ipip.c:516:/* Need this wrapper because NF_HOOK takes the function address */ipmr.c:1211:NF_HOOK(PF_INET, NF_IP_FORWARD, skb2, skb->dev, dev, zhaoway@qhq ~/linux-2.4.19/net/ipv4 $ 
NF_HOOK() 这个宏函数,定义在 linux-2.4.19/include/linux/netfilter.h 里面。
当 #ifdef CONFIG_NETFILTER 被定义的时候,就转去调用 nf_hook_slow() 函数;
如果 CONFIG_NETFILTER 没有被定义,则从 netfilter 模块转回到 IPv4 协议栈,继续往下处理。
这样就给了用户在编译 kernel 的时候一个选项,可以通过定义 CONFIG_NETFILTER 与否来决定
是否把 netfilter 支持代码编译进内核。从这个函数的名称,我们也可以猜到,可以把 IPv4 协议栈上的这五个参考点,
形象的看成是五个钩子。IP packet 在 IPv4 协议栈上游历的时候,途经这五个钩子,
就会被 netfilter 模块钓上来,审查一番,并据审查的结果,决定 packet 的下一步命运:
是被原封不动的放回 IPv4 协议栈,继续游历;还是经过一些修改,再放回去;还是干脆丢弃掉算了?

netfilter 的核心模块

“鱼钩”和“垂钓点”

IP packet 被 NF_HOOK() 从 IPv4 协议栈上钓出来以后,就进入 linux-2.4.19/net/core/netfilter.c 中的 nf_hook_slow() 函数进行处理。这个函数干的主要事情,就是根据 nf_hooks[] 数组,开始处理 packet。准确地说来,上一段讲到的 IPv4 协议栈上的五个参考点,并不是“钓鱼的钩子”,而是“允许垂钓的地点”。换句话说,IPv4 协议栈上定义了五个“允许垂钓点”。在每一个“垂钓点”,都可以让 netfilter 放置一个“鱼钩”,把经过的 packet 钓上来。那么 netfiler 的“鱼钩”都放在什么地方?就放在 nf_hooks[][] 数组里面。这个“鱼钩”用 linux-2.4.19/include/linux/netfilter.h 中定义的如下 struct 予以描述:

struct nf_hook_ops{        struct list_head list;        nf_hookfn *hook;        int pf;        int hooknum;        int priority;};

我们看到,“鱼钩”的本质,是一个 nf_hookfn 函数。这个函数将对被钓上来的 IP packet 进行初步的处理。那么,这些“鱼钩”是由谁来放置到 nf_hooks[][] 数组里面的呢?答案是,各个 table。熟悉 iptables 管理工具的读者朋友们应该了解,一个 table 就是一组类似的防火墙 rules 的集合。iptables 里面默认定义了三个 table:filter,mangle,和 nat。举 filter table 为例,它是在 linux-2.4.19/net/ipv4/netfilter/iptable_filter.c 中实现的一个 kernel module。在这个 module 的初始化过程中,它会调用 nf_register_hook() 向 netfilter 的核心代码注册一组“鱼钩”。这个注册过程,实际上,也就是把“鱼钩”放到“垂钓点”的过程。“垂钓点”的具体位置,由 nf_hooks[][] 数组的下标具体说明。

ipt_do_table()

我们具体看到 linux-2.4.19/net/ipv4/netfilter/iptable_filter.c 也就是 filter table 的实现代码,就发现 filter table 中的“鱼钩”上的 nf_hookfn 函数,主要是在调用 ipt_do_table() 函数。这是一个定义在 linux-2.4.19/net/ipv4/netfilter/ip_tables.c 中的函数。前面提到过,一个 table 就是一组防火墙 rules 的集合。显然,ipt_do_table() 函数将要做的事情,就是按照 table 中存储的一条又一条的 rules 来处理被“钓”上来的 IP packet。

table 里面存放了这个 table 中所有的防火墙 rules。但是并不是所有的 rules 都要拿过来,按照它审查一下这个 packet。事实上,这个 packet 是从哪个“鱼钩”上被钓上来的,就只有和那个“鱼钩”相关的 rules 才被拿过来,用来审查这个 packet。这个机制,就为每个 table 实现了多个 chain,而每个 chain 上又有多个 rules。而且,我们立刻看到,一个 chain 是和 IPv4 协议栈上的一个“垂钓点”相对应的。熟悉 iptables 用户空间管理工具的使用的读者朋友们应该立刻就会注意到这一点了。

在 linux-2.4.19/include/linux/netfilter_ipv4/ip_tables.h 中定义了 table 中的 rule 的存放格式,如下:

一个 entry 就是一个 rule。一个 entry 主要由两部分组成。

一部分,是一系列的 matches;另一部分,是一个 target。这若干个 match 所要回答的问题,

是相关的 packet 和本条 rule 是否匹配。而这个 target 所要回答的问题,是一旦 packet 匹配上以后,

该拿这个 packet 怎么办?也就是要由 target 来决定这个匹配的 packet 今后的命运了。开头的 struct ipt_ip 的定义如下:

我们立刻可以看出来,在 struct ipt_ip 里面记录了关于这个 rule 所要匹配(match)的 packet 的一些特征。

match 和 target

netfilter 核心部分提供了一个分析、处置 packet 的架构,但是核心部分代码并不具体的去分析、处置 packet。

这个具体的分析、处置的任务被交给其它的 module 来完成。核心部分代码可以根据 table 中记录的 rules 信息,

来把 packet 交给能够处理相应的 rules 的 module 代码。那么,核心代码如何了解哪一个 module 可以处理哪

一类的 rules 的呢?这要由各个相应的 modules 起动的时候,主动去向核心代码注册,ipt_register_target()

或者是 ipt_register_match()。这个注册过程,主要就是通知核心代码,本 module 有一个 target() 函数,可以

决定 packet 的命运;或者是,本 module 有一个 match() 函数,可以判定一个 packet 是否符合 rules 的匹配要求。

这就提示我们,如果要写自己的防火墙模块,镶嵌在 netfilter 的架构中的话,我们主要要做的任务,就是向 netfilter 核心

注册 ipt_register_target() 或者 ipt_register_match()。

iptables 管理工具

最后,要说明的是 iptables,这个位于用户空间的管理工具。前面我们看到了,netfilter 在内核空间的代码根据

table 中的 rules,完成对 packet 的分析和处置。但是这些 table 中的具体的防火墙 rules,还是必须由系统管理员

亲自编写。kernel 中的 netfilter 只是提供了一个机制,它并不知道该怎样利用这个机制,写出合适的 rules,

来实现一个网络防火墙。那么,系统管理员编写的 rules,怎样进入位于 kernel 空间中的 netfilter 维护的 table 中去呢?

这个任务是由 iptables 这个工具来完成的。它经过 getsockopt() 以及 setsockopt() 两个系统调用,进入 kernel 空间。

这两个调用是 BSD Socket 接口的一部分。这里面的问题是 IPv4 在接到关于某个 sock 的不认识的 opt 的时候,

应该怎么处理?netfilter 要求它在 linux-2.4.19/net/ipv4/ip_sockglue.c 文件中处理 getsockopt() 和

setsockopt() 系统调用的 ip_sockopt() 函数中适当的地方调用 nf_sockopt()。这样,用户空间就可以和

netfilter 核心部分进行交流,可以维护 table 中的防火墙 rules 了。