当前位置:Linux教程 - Linux - 用IPIW实现BSD防火墙(中)

用IPIW实现BSD防火墙(中)

我们已经通过安装带缺省的禁止所有数据包出入的策略的ipfw,使所有的IP信息包都不能出入我的计算机系统,下面,我们再创建一个能被ipfw读取的规则集,使所需要的信息包能够出入计算机系统。

由于在创建规则集方面没有所谓“最合适”的方法,因此我不能说明如何在规则集中添加“万能”的规则,而只能说明一下在创建规则集时需要遵循的原则。在这里,我假设你已经掌握了ipfw的语法,能够理解我创建的规则。如果对这些知识不大理解,请参阅相关的资料。

在创建规则集时需要注意的是,规则是按给定数字行号的顺序被系统读取的,直到信息包符合一条规则,ipfw才会停止读取规则,也就是说,如果规则400和规则800都适用于一个信息包,系统总是会用到规则400而不会读取规则800。因此,在添加新的规则之前,需要仔细地审查原来的规则,确保新的规则不会被原来的规则所覆盖。

此外,规则对所有的连接-也就是在ifconfig -a的输出中所列出的所有连接都是适用的。如果在象我这样只有一个连接的计算机上自然不会有什么问题,但如果在有多个连接的计算机上,就会有所不同。例如,你的机器上可能有二个连接,一个是互联网连接,一个是内部局域网连接,每个不同的连接需要不同的安全规则,这一点可以通过在ipfw的规则中指定连接的名字来实现。

我的机器是一台运行FreeBSD 4.2、配置有互联网连接的单台计算机。由于这是我在家里使用的计算机,因此可以对向互联网上发送的信息包的类型不作任何限制,而只需要它能够接收是对我发出的信息包有效响应的信息包。

要完成这一任务最好的方法之一是利用ipfw的动态功能。如果你对这一概念还不太熟悉,下面我将对它作一番详尽的解释。

如果使用“动态”的规则,当我向互联网上发送一个信息包时,ipfw将在其状态表中添加一个记录,其中包括有发送的信息包的目标计算机的IP地址和使用的目标计算机的端口。当有信息包从互联网上返回时,如果其IP地址、端口号与在状态表中记录得不一致,计算机就不会接收这一信息包。动态规则只适用于TCP信息包,而不适用于UDP信息包,原因是UDP不创建一个虚拟的连接,它被称作是“无状态”协议,也就不能使用“状态表”。

ipfw手册中的例子部分给出了三条用来创建这个动态信息包过滤装置的规则。由于我决定在/etc/ipfw.rules中创建自己的规则,因此,需要以超级用户的身份创建包含下面内容的文件:

# 只允许向外发送信息包
add 00300 check-state
add 00301 deny tcp from any to any in established
add 00302 allow tcp from any to any out setup keep-state

由于规则100和规则200是预先包含在/etc/rc.firewall中的,因此,我自己添加的规则将从行号300开始。我将给相关的规则以300、301、302等行号,等规则越来越多或创建不相关的规则时,我就会把行号跳到400。不过,从理论上说你可以任意给规则指定行号,只要该行号没有在该规则集文件中出现过就行。

你可能已经注意到规则集出现了几个在ipfw手册中定义的关健字:

check-state:检查信息包是否与动态规则集匹配。如果匹配则搜索中止,否则继续搜索下一条规则。
keep-state:根据匹配情况,防火墙将创建一条动态规则,其功能是在源、目的IP地址/端口之间使用同一协议的流量,这一规则是具有一定的生命周期的(由一系列sysctl(8)变量控制),每当发现匹配协议时其生命周期都会刷新。
established:只适用于TCP信息包,与有RST或ACK位的信息包进行匹配。
setup:只适用于TCP信息包,与有SYN位但不具有ACK位的信息包进行匹配。

换句话说,当有信息包到达网络连接后,ipfw将首先检查它是否在状态表中,如果在状态表中,则允许它进入系统(行号为300的规则执行这一检查工作)。如果它不在状态表中,而且设置了RST或ACK位,ipfw将不允许它进入系统,因为它不是我创建的连接的有效响应(规则301完成这一工作)。只有当ACK标志没有设置,(这意味着它要初始化连接)并且这一信息包是由系统向外发送的时,才允许它向外发送;如果有信息包符合这一规则,则它被加入状态表中。

我们来看看添加上这些规则后对系统有什么影响。对规则集进行检查没有错误后,保存文件,然后输入下面的命令:

killall init

敲Enter键,然后输入:

exit

然后仔细观察启动信息,确保规则在加载时没有出现出错信息。如果在规则加载过程中出现了错误信息,那么可能是系统的安全级别被设置为3或更高了,必须首先在/etc/rc.conf文件中找到下面的这行内容,将kern_securelevel改为较小的值:

kern_securelevel=""3""

然后重新执行killall init命令。

重新登录后,我将尝试能否向外发送IP数据包并收到相应的应答数据包:

ping www.freebsd.org
ping: cannot resolve www.freebsd.org: Host name lookup failure

lynx www.freebsd.org
Alert!. Unable to access document.

也许是我的系统上没有安装DNA域名解析功能的缘故,我再使用IP地址试一下:

lynx 216.136.204.21

这次我发现自己在www.freebsd.org的主页上了。我们再来试着ping一下这个IP地址吧:

ping 216.136.204.21
PING 216.136.204.21 (216.136.204.21): 56 data bytes
ping: sendto: Permission denied
ping: sendto: Permission denied
ping: sendto: Permission denied
^C
--- 216.136.204.21 ping statistics ---
3 packets transmitted, 0 packets received, 100% packet loss

这里我来解释一下这一奇怪的现象吧。很明显的是,一些数据包进入或发出了计算机系统,但一些则没有。我们来仔细发分析一下我在上面的每个例子中使用的协议。

由于我只能使用其IP地址访问www.freebsd.org的网站,因此域名解析没有成功。当我使用DNS服务时,我会向我的ISP的DNS服务器发送域名查找请求,该DNS服务器应该向我的机器发送响应数据包。这些操作都是符合我们的规则的,由于我是在53端口发送请求的,也应该在端口53上接收到响应数据包。我将复查我发送请求的DNS服务器:

more /etc/resolv.conf
search kico1.on.home.com
nameserver 24.226.1.90
nameserver 24.226.1.20
nameserver 24.2.9.34

似乎问题不是出在这儿,因此应该仔细地搞清楚域名解析的工作原理。我们来看一下能否从在线手册上得到一点帮助:

apropos resolve
dnsquery(1) - 使用解析器查询域名服务器
res_query(3), res_search(3), res_mkquery(3), res_send(3), res_init(3), dn_comp(3), dn_expand(3) - 解析器例程
resolver(5) - 解析器配置文件
man resolver

通过多次查看,下面的内容引起了我的兴趣:RES_USEVC 在查询中使用TCP而不是UDP连接;RES_STAYOPEN RES_USEVC用它来在多次查询期间保持TCP连接。它只在需要进行多个查询的的软件中有用,UDP是最常用的模式。

我可能已经发现问题出在哪了。如果DNS使用的是UDP而不是TCP,而我的规则只允许TCP协议的数据包响应我的TCP连接,域名解析就会失败。

man dnsquery
<只显示我们感兴趣的部分>
-s 使用流格式而非信息包。它使用一个带名字服务器的TCP流式连接而不是UDP,这一选项会设置解析软件选项字段的RES_USEVC位。(缺省状态下使用UDP)

现在,我们来试试这个选项:

dnsquery -s www.freebsd.org
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 39772
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 5, ADDITIONAL: 5
;; www.freebsd.org, type = ANY, class = IN
www.freebsd.org. 49m21s IN CNAME freefall.freebsd.org.
freebsd.org. 22m43s IN NS ns1.iafrica.com.
freebsd.org. 22m43s IN NS ns2.iafrica.com.
freebsd.org. 22m43s IN NS ns.gnome.co.uk.
freebsd.org. 22m43s IN NS ns0.freebsd.org.
freebsd.org. 22m43s IN NS ns1.root.com.
ns1.iafrica.com. 1h1m3s IN A 196.7.0.139
ns2.iafrica.com. 1h1m3s IN A 196.7.142.133
ns.gnome.co.uk. 12m37s IN A 193.243.228.142
ns0.freebsd.org. 11h9m9s IN A 216.136.204.126
ns1.root.com. 1h8m12s IN A 209.102.106.178

在我们使用TCP连接发出一个DNS请求时,名字解析过程运行得很好。我们再在没有带s选项的情况下看使用UDP时的情况如何:

dnsquery www.freebsd.org
Query failed (h_errno=2) : Host name lookup failure

现在我们明白了,DNS使用的是UDP数据包。由于我没有在规则集中允许使用UDP数据包,因此DNS名字解析过程不能完成。

现在既然已经解决了这个问题,我们再来看看即使在使用IP地址时也ping不通的原因何在。我们知道,ping在其数据包中使用的是ICMP而非TCP协议。如果用ping发送ICMP数据包,不在防火墙的规则面前碰一鼻子灰才怪呢。

在向规则集中添加任何新的规则前,必须以超级用户身份重新登录。我们来看看ipfw的输出:

su
Password:
ipfw show
00100 0 0 allow ip from any to any via lo0
00200 0 0 deny ip from any to 127.0.0.0/8
00300 0 0 check-state
00301 0 0 deny tcp from any to any in established
00302 21 15144 allow tcp from any to any out keep-state setup
65535 142 10531 deny ip from any to any
## 动态规则:
00302 19 15040 (T 0, # 147) ty 0 tcp, 24.141.119.162 2932 <-> 216.136.204.21 80

注意一下动态规则部分,这是一个状态表。当运行lynx 216.136.204.21命令与www.freebsd.org站点上的http端口(端口 80)进行连接时,Rule 00302允许发出setup数据包,并在状态表中添加一个条目。所有从216.136.204.21上的端口80发出或以它为目标地址的数据包都可以进入或者发出我的计算机。

你也许还注意到了标号为00302和65535的规则后面都跟有数字,其中第一个数字为数据包的数量,第二个数字为符合每条规则的字节数。被规则65535拒之门外的数据包都是失败的UDP和ICMP数据包。

向规则集中添加新规则时,需要使用ipfw中的zero命令将这些计数器清零,这样,当对新添加的规则进行测试时,就能知道哪些规则后面又出现了新的统计数字。

下面,我将添加一些允许进行DNS名字解析的规则。由于DNS使用UDP,UDP不进行连接,我不能指定只允许对我的连接的有效的响应数据包进入系统。但是,我可以限制DNS使用的端口(端口 53)进出的数据包,选择只接受来自我的ISP的DNS服务器的IP地址发出的数据包。运行more /etc.resolv.conf命令就能发现这些IP地址。我将在/etc/ipfw.rules文件中添加下面的内容:

#允许 DNS
add 00400 allow udp from 24.226.1.90 53 to any in recv ed0
add 00401 allow udp from 24.226.1.20 53 to any in recv ed0
add 00402 allow udp from 24.2.9.34 53 to any in recv ed0

然后,通过运行killall init命令重新加载规则集,看名字解析是否已经可以成功地运行了:

lynx www.freebsd.org
Alert!. Unable to access document.

怎么回事?我已经在规则集中添加了允许使用UDP数据包的规则,怎么名字解析服务仍然不行呢?我们运行ipfw show命令来看看哪条规则的后面跟有数据包计数字:

su
Password:
ipfw show
00100 0 0 allow ip from any to any via lo0
00200 0 0 deny ip from any to 127.0.0.0/8
00300 0 0 check-state
00301 0 0 deny tcp from any to any in established
00302 0 0 allow tcp from any to any keep-state setup
00400 0 0 allow udp from 24.226.1.90 53 to any in recv ed0
00401 0 0 allow udp from 24.226.1.20 53 to any in recv ed0
00402 0 0 allow udp from 24.2.9.34 53 to any in recv ed0
65535 30 2196 deny ip from any to any
## Dynamic rules:

后面跟有统计数字的唯一的规则是最后一条拒绝服务的规则,说明添加的允许UDP数据包的规则没有作用。现在我才明白,我还没有允许向外发送UDP数据包,没有UDP数据包返回来也就没有什么好奇怪的了。下面我们再往规则集中添加一行内容:

00403 allow udp from any to any out

这样,我的计算机就可以向外发送UDP数据包了。然后用ipfw zero清除规则后面的统计数字,运行killall init命令重新再试一次:

lynx www.freebsd.org

FreeBSD的主页终于出现了。如果我以超级用户的身份运行ipfw show命令,就会得到更令人满意的输出:

ipfw show
00100 0 0 allow ip from any to any via lo0
00200 0 0 deny ip from any to 127.0.0.0/8
00300 0 0 check-state
00301 0 0 deny tcp from any to any in established
00302 20 15061 allow tcp from any to any keep-state setup
00400 10 1882 allow udp from 24.226.1.90 53 to any in recv ed0
00401 0 0 allow udp from 24.226.1.20 53 to any in recv ed0
00402 0 0 allow udp from 24.2.9.34 53 to any in recv ed0
00403 10 591 allow udp from any to any out
65535 31 2577 deny ip from any to any
## Dynamic rules:
00302 19 15017 (T 0, # 236) ty 0 tcp, 24.141.119.162 4363 <-> 216.136.204.21 80

规则00403允许我的计算机发出DNS请求,规则00400允许接受DNS应答,规则00302建立HTTP连接,而且,我在状态表中有了一个与216.136.204.21之间HTTP连接的条目。

我们已经建立了一个可以运行的网络连接,但这个规则集仍然有很大的改进余地,下面我们将就这方面的问题进行更详细的讨论。