大多数主要的Linux发行商,包括SuSE,在防火墙设置方面都有某些独特的用户接口特征。他们这样并没有错,但是这样我就无法直接得到最想要的配置,所以只好自己手动设置。Iptableman页面完全是个依照iptable命令行句法的文件,它不提供关于将不同规则的防火墙结合起来的指导说明。你可以搜索到许多零散的关于iptables的信息,但是这些都不足以教会我想要弄明白的东西。最后我用运行着SuSELinuxPro10.0的一个Vmware虚拟机终于弄清楚了到底应该怎样做。下面就是用iptables配置的简单防火墙的文档。请读者验证如此配置的防火墙是否足以保护相应的主机。

尽管很难操作,iptables/NetFilter却难以置信的棒。它们的功能广泛并且配置方法很直接。如果发行商的产品带有自己的防火墙特征,而你觉得它没有满足你的全部要求的话,你就会体会到iptables的这个优点了。

SuSE防火墙配置和相应的脚本都使用了多种用户组规则。我的目的就是展示一个简单的防火墙范例,它只有一个默认的瀑布型组规则。我发现合成得到的防火墙配置脚本更容易理解,并且更容易表达出我的意图。

iptables/NetFilter原理

NetFilter是一组核心元件,实际上是它在执行防火墙规则。Iptables是用来定义和插入这些规则的程序。从这点来看,我可以我可以用iptables来指代NetFilter。

Iptables配置要求规定一个“表”,一个“链”以及规则细节。链就是一组规则。这些在一个链中的规则在由链本身以及一个“表”来共同定义的环境中应用。表就是一组链。表定义了全局环境,而链定义表内的本地环境。最简单的例子就是运行防火墙的主机。它能接受数据包(输入)并且发送数据包(输出)。假设我们想要过滤进出主机的传输,全局环境(表)就是“过滤器”,本地环境(那些链)就是“输入”和“输出”。Linux主机也可以当作路由器来使用来转发数据包。因此,过滤器表也有“转发”链。

你可能很熟悉网址转换(NAT)。这就是一种多路ip地址方面的方法。Iptables在一个nat表中有一组链。Prerouting和postrouting链包含了对那些到达和离开主机的数据包进行的操作规则,相应地外加一个输出链来对主机自己发出的数据包的操作规则。

还有两个其它的表,mangle和raw,在这里我就不详细讨论了。

我们想要构造的东西

为了满足我自己的需要,我想在家里用Linux和iptables来替代WindowsNAT路由器/防火墙。我在内网中有些主机,因此我需要一个NAT路由器来让它们连到网络上。有些主机上运行着服务器,我想对外公开;另外防火墙上运行的一些服务我也想对网络公开。我把这些罗列如下:

开始

我们将用[bash]shell脚本来定义该防火墙。首先我要定义一些会重复使用的东西:


#Privateinterface
IF_PRV=eth0
IP_PRV=192.168.1.1
NET_PRV=192.168.1.0/24

#Publicinterface1
IF_PUB=eth1
IP_PUB=10.0.0.1
NET_PUB=10.0.0.0/24

#Others
ANYWHERE=0.0.0.0/0

这就使我们可以使用多种网络元素的名称,并且能够很方便修改这些东西。
每个链都是使用瀑布类型。我们用依次用每个规则检验数据包,根据所有匹配的规则处理该数据包。每个链都有一个最后常用的规则成为“策略”。我们就从限制的策略开始:


iptables-PINPUTDROP
iptables-POUTPUTDROP
iptables-PFORWARDDROP

Thissetsthepolicyforthethreechainsinthefiltertabletodropallpackets.Notethatthetableisn'tspecified.iptablesdefaultstothefiltertablewhennoneisspecified.


以上是设置了过滤器表中的三个链丢弃所有包的策略。需要注意的是该表没有被指定。在没有特别指定的情况下,iptables默认值为过滤器表。


Nextwewanttoremoveanyexistingrulesfromthetables:

接下来我们要除去表中所有现有的规则:


iptables-F-tnat
iptables-F-tmangle
iptables-F-tfilter
iptables-X

前三句依次去掉所有nat,mangle以及filter表中的规则。最后一句去掉所有用户定义的链。
路由

我将要构造的防火墙,还将是一个能够转发数据的路由器。Linux上的IP堆栈可以如路由器一般动作,并且开能够很简单地激活:


echo1>/proc/sys/net/ipv4/ip_forward

转发规则

既然我们要把主机当服务器用,我们就得从转发规则开始。我们信任内网,因此允许所有从内网发送的数据流:


iptables-AFORWARD-i$IF_PRV-o$IF_PUB-jACCEPT

以上语句同意转发内网接口的入站数据流以及公网接口出站数据流。但是这还不够。过滤是根据每个包来执行的。我们需要允许从公网接口回到内网的数据传输。但是不能允许所有的传输。Netfilter能够感知会话,因此我们可以规定让它允许已有会话的传输:

iptables-AFORWARD-i$IF_PUB-o$IF_PRV-mstate--state
ESTABLISHED,RELATED-jACCEPT

本句规定了过滤表转发链的规则。该规则应用于内网接口的入站数据流以及公网接口出站数据流。该规则加载NetFilter“state”模块并且限制对已经建立的会话以及相关会话状态的中的数据包的操作规则。匹配的数据包被路由器接受。而“相关”会话状态则是在那种有与允许的出站会话相关联的次级通道情况下使用的,比如ftp会话的数据连接。
可信赖的防火墙

如果我们不相信防火墙,那么我们就不应当把它当防火墙用。防火墙应当能够访问其他网络。

首先我们将处理虚拟网段(lo)接口。只要有允许所有输入输出的数据就行了:


iptables-AINPUT-ilo-jACCEPT
iptables-AOUTPUT-olo-jACCEPT

这些规则很简单,我们可以看到第一句接受过滤表中输入链的数据,到达lo接口的数据由该表处理。第二句接受过滤表中输出链的数据,从lo接口输出的数据由该表处理。

同样的,我们希望允许防火墙与内网的主机相通:


iptables-AINPUT-i$IF_PRV-s$NET_PRV-jACCEPT
iptables-AOUTPUT-o$IF_PRV-d$NET_PRV-jACCEPT

第一句使防火墙接受内网接口进入的源自内网的数据流。第二句使防火墙允许内网接口输出的目的地在内网的数据流。

我们不能对公网接口用同样的规则,因为那样会允许所有数据流连接到防火墙。另一方面,我们希望防火墙能够不受限制地访问公网:


iptables-AOUTPUT-o$IF_PUB-jACCEPT
iptables-AINPUT-i$IF_PUB-mstate--stateESTABLISHED,RELATED-j
ACCEPT


以上两句允许防火墙通过公网接口发送数据流到任何地方,但它只限接受已经存在的连接或者与之相关的输入数据流。因此该句允许防火墙与一些公网主机连接,还有允许那些连接中的输入数据流,但是不允许从公网连接到防火墙。

注意区分公网和内网的接口规则。我们假设内网上所有的机器都可信赖,并且公网上的所有主机都不可信。

内网路由

内网是非公开路由的IP网络。这就要求内网得有个带公网IP地址的路由器来做地址转换,否则内网就收不到公网返回的数据包。用iptables很容易就可以进行地址转换。需要转换的地址是会话的“源”,因此该模块叫做源NAT(SNAT):


iptables-tnat-APOSTROUTING-s$NET_PRV-o$IF_PUB-jSNAT--to
$IP_PUB

需要注意,我们用的-tnat这个规则规定了这个表。该规则不是应用在默认的filter表中的。它是应用在NAT表的post-routing链中的。换句话说,确定路由之后才会应用该规则。它应用在内网上带源地址的数据包上,该数据包即将输出到公网接口上。该规则的动作就是执行SNAT并且把数据包的源IP地址变换成公网接口的IP地址。

也许你会奇怪为什么没有规则来执行这个相反的转换。为了实现那种规则,NetFilter必须知道对哪个数据包应用这样的规则。使用它的唯一有效的地方就是会话本身。由于NetFilter要维持这些规则的表,因此相反的转换规则是没必要的,并且在会话中接受到的数据包系统会自动进行相反的转换。

哲学问题

到现在为止,我们已经处理了所有我们信任和不信任的信息了。在决定允许对哪些信息的受限制访问之前,我需要讲一下对被拒绝的数据流的合理响应。到目前为止,我们的策略是将不接受的数据包扔到黑洞里。这也许是防火墙最常见的操作。它不会给该传输的发送者透漏任何信息。这种方法经常被描述成对付***者的有效方法,因为它使***者等响应等到超时。我曾经也用这种策略,但现在再也不用了。不久前,我读了一篇USENET上的文章,那个时候我正在构造自己的第一个iptables防火墙。该文章正是针对的这个问题(揭示了没有什么可以耽搁***者),它提出***者在执行的是大量的并行扫描,因此延时是毫无意义的。该文章还提出保护得当的主机不会给***者提供任何有用的信息,因此做一个顺从的IP良民更有效。我赞成这两个观点,因此,我的防火墙对大多数常见的扫描手段的回应都像是个毫无阻碍的IP堆栈。

ICMP

我不允许所有的ICMP数据传输,但允许ping。使用ICMP的echo请求是最简单的主机扫描模式。如果主机存在并且有自由的IP堆栈,那么它将给发送者一个ICMPecho回复。因此,可以用ping来确定上行的主机是否被分配了IP地址。这就会暴露我的主机,但是由于我要公开web和mail服务器,主机始终也是要被人知道的,因此没必要限制ping的使用。


iptables-AINPUT-picmp--icmp-type0-jACCEPT
iptables-AINPUT-picmp--icmp-type3-jACCEPT
iptables-AINPUT-picmp--icmp-type11-jACCEPT
iptables-AINPUT-picmp--icmp-type8-mlimit--limit1/second-j
ACCEPT

ICMPecho请求的代码是8。如你所见,应用于那些数据包的规则加载了limit模块并且限制为每秒1个包。其它类型代码还有echo响应(0),无法到达目的地(3)以及超时(11)。它们没有限制速率,因为它们不会让主机对请求作出响应(像echo请求那样)。Echo响应和无法到达目的地很容易理解,而允许超时这个类型似乎不太好理解。超时ICMP消息被用来返回一个生存周期(TTL)超时错误报告。IP包中有一个包含一位数字的部分叫做time-to-live。每次数据包经过路由器时,TTL就被消耗掉一些。当TTL变为0时,数据包就不再被继续转发了,并且发送者会收到一条ICMP超时消息。


这些规则是应用于所有接口的,不单单是公网接口。我并没有什么特殊的理由来解释这点,但是我觉得这么做对防火墙或者内网主机也没有什么显著的影响。

防火墙主机上的服务

到目前为止,防火墙可以自己连接也可以转发内网的连接了。它允许已建立的会话的返回数据传输发送给防火墙本身和内网上的主机。它还允许内网任何的数据传输发送到防火墙。现在我要把防火墙上的一个服务公开给公网。如果我从公网能够跟防火墙建立一个SSH会话的话,那将很方便。谨慎起见,我不会用标准的SSH端口(22)而用2202:


iptables-AINPUT-i$IF_PUB-ptcp-d$IP_PUB--dport2202-j
ACCEPT

这句允许把防火墙的公网IP地址作为目的地的那些传输到达公网接口,并且允许它与目的端口2202建立tcp连接。注意SSH服务器得单独配置以便监听2202端口。

要更保险点的话,我们可以限制连接频率:


iptables-AINPUT-i$IF_PUB-ptcp-d$IP_PUB--dport2202-mlimit-
-limit1/minute--limit-burst1-jACCEPT

这句话限制SSH连接频率为平均一分钟一次并且一个(脉冲)中一次。当你想要提高平均频率而不介意时不时的短脉冲的时候,脉冲限制的好处就比较明显了。例如,它可以更容易地处理在会话开始或者前期发生的连接失败。在这种情况中,我将限制SSH连接为平均一分钟一次并且一个脉冲内不允许超过一次。

内网中的服务

我的mail服务器和web服务器都在内网上。我只想把服务器上的部分服务对公网公开。我只有一个公网IP地址(内网没有公网路由的地址)并且我不希望把这些服务放到防火墙上去。针对这个问题的传统方案是“端口转发”。NAT路由器监听它的公网接口上的相关端口并且将任何进来的连接或者传输转发到内网的目的地上,将入站包上的IP目的地址转换成内网目标,而将出站包上的IP源地址转换成防火墙的公网地址。Iptables把这项功能成为目的地NAT(DNAT)。我们这样公开mail服务器:


iptables-tnat-APREROUTING-i$IF_PUB-d$IP_PUB-ptcp--dport
smtp-jDNAT--to192.168.1.254
iptables-AFORWARD-mstate--stateNEW,ESTABLISHED,RELATED-i$IF_PUB-ptcp--dport
smtp-jACCEPT

以及web服务器:


iptables-tnat-APREROUTING-i$IF_PUB-d$IP_PUB-ptcp--dport
http-jDNAT--to192.168.1.253
iptables-AFORWARD-mstate--stateNEW,ESTABLISHED,RELATED-i$IF_PUB-ptcp--dport
http-jACCEPT

每个服务有2个规则,一个执行地址转换而另一个是允许数据包的路由。DNAT规则被添加到NAT表的PREROUTING链中。它应用于那些到达公网接口的、以防火墙公网IP地址为目的地的数据传输上。对于目的端口是smtp(25)的tcp协议传输,系统执行DNAT转换将传输导向内网上地址为192.168.1.254的目的地。第二条规则规定,已经建立的会话的新连接和传输到达公网接口并且目的端口是smtp的tcp协议数据包时允许它们经过路由器。http情况与之类似只是目的端口变为http(80)。

注意,我们可以使用/etc/services命名端口(smtp,http,等等)。尽管转发规则规定了NEW,ESTABLISHED,RELATED只有NEW和ESTABLISHED两个状态对smtp和http有意义。

你可以重复使用这些范例来处理其他协议,比如mail服务器的pop3协议:


iptables-tnat-APREROUTING-i$IF_PUB-d$IP_PUB-ptcp--dport
pop3-jDNAT--to192.168.1.254
iptables-AFORWARD-mstate--stateNEW,ESTABLISHED,RELATED-i$IF_PUB-ptcp--dport
pop3-jACCEPT

记录其余的信息

由于以上就是所有我想要允许通行的,这样的话,了解防火墙拦截了什么是很有必要的。我们可以添加一个记录规则如下:


iptables-AINPUT-i$IF_PUB-jLOG--log-prefix="INPUT"
iptables-AOUTPUT-o$IF_PUB-jLOG--log-prefix="OUTPUT"
iptables-AFORWARD-jLOG--log-prefix="FORWARD"

之前那些规则没有处理的任何数据包,都将被根据它们是在filter表中的哪个链被发现的来分别记录。Filter表中没有单独的记录规则。由于所有不匹配的数据包都会到达filter表中的一个链,因此没有必要在其他表中放置记录规则。这些规则会给记录正文加上以它登录进来的链的名称所做的前缀。记录文件是/var/log/firewall,当它变得很大的时候会自动循环。前一个记录会被重命名为firewall-date,并且进行压缩。记录正文每项一行,下面的就是我的记录中的2行:

Oct2516:46:46firewallhostnamekernel:INPUTIN=eth1OUT=
MAC=00:03:47:af:35:9f:00:d0:88:01:00:95:08:00SRC=10.0.0.155DST=10.0.0.1LEN=48TOS=0x00
PREC=0x00TTL=127ID=31207DFPROTO=TCPSPT=1645DPT=135WINDOW=64240RES=0x00SYNURGP=0

Oct2516:46:47firewallhostnamekernel:INPUTIN=eth1OUT=
MAC=00:03:47:af:35:9f:00:d0:88:01:00:95:08:00SRC=10.0.0.155DST=10.0.0.1LEN=48
TOS=0x00PREC=0x00TTL=127ID=31307DFPROTO=TCPSPT=1645DPT=135WINDOW=64240RES=0x00
SYNURGP=0

这句显示10.0.0.155的主机两次试图连接到我的防火墙的tcp/135(Windows网络)端口。这两段都将被记录在filter表的INPUT链中。
而filter表中的-jACCEPT规则阻止包含那条规则的链继续处理该数据包,-jLOG规则则不然。数据包将被记录并且它会在当前链中继续被处理。