feitianxiang 发表于 2006-7-11 21:08:46

用IPFW实现BSD防火墙

FreeBSD操作系统本身带有二种内置的IP信息包检查机制:ipfw和ipfilter。在创建决定允许哪些信息包进入系统、哪些信息包会被拒之系统门外的规则集方面,二种机制各有自己独特的语法。在这里,我们将讨论如何使用ipfw配置系统的防火墙。

在能够使用ipfw防火墙机制之前,我们需要在FreeBSD的内核配置文件中添加一些选项,并重新编译内核。如果不太清楚如何编译FreeBSD的内核,请参阅相关的手册。

可供ipfw使用的选项有好几个,我们首先从讨论LINT开始。在这里,我通过使用“/”符号进行搜索,以便能够快速地发现恰当的小节:
cd /usr/src/sys/i386/conf
more LINT
/IPFIREWALL
# IPFIREWALL和ipfw软件,这二者就可以支持IP防火墙的构建。IPFIREWALL_VERBOSE向
# 系统的注册程序发送注册信息包,IPFIREWALL_VERBOSE_LIMIT限制一台机器注册的次
# 数。注意:如果没有在启动时添加任何允许IP访问的规则,IPFIREWALL的缺省配置是
# 禁止任何IP数据包进出系统的,这时你甚至不能访问网络中的其他机器。建议首次使
# 用这一功能时在/etc/rc.conf中设置firewall_type=open,然后在对内核进行测试后
# 再在/etc/rc.firewall仔细地调整防火墙的设置。IPFIREWALL_DEFAULT_TO_ACCEPT使
# 得缺省的规则允许所有形式的访问。在使用这一变量时应该非常小心,如果黑客能够
# 突破防火墙,就能任意访问你的系统。




要启用ipfw,必须设置IPFIREWALL选项,它将通知操作系统的内核检查每个IP数据包,将它们与规则集进行比较,通过添加IPFIREWALL_VERBOSE选项包括注册支持是一个好主意,还应该通过添加IPFIREWALL_VERBOSE_LIMIT选项来限制内核注册的数据包的数量。

除非在规则集中进行了特别的说明,缺省情况下ipfw将阻塞所有的IP数据包。由于缺省设置可以仔细地控制哪些数据包会被接受,因此我非常喜欢它。我不喜欢内核会接受自己都不清楚内容的数据包,如果需要的数据没有被系统接受,会得到系统的提示,并修改规则集使系统可以接受它们。这时,如果有没有预料到的数据包通过系统也不会知道。因此,我不会通过包括IPFIREWALL_DEFAULT_TO_ACCEPT选项来绕过缺省的设置。
# IPDIVERT启用由ipfw divert使用的转向IP套接字。这一选项需要与natd联合使用。由
# 于在本例中建立的防火墙仅用于保护一台机器,因此不需要这个选项。
# IPSTEALTH启动支持秘密转发的代码,这一选项在使防火墙不被traceroute和类似工具发现时很有用。




这是一个非常有趣的选项,因此我将在防火墙中包含这一选项,并在对防火墙进行测试时看看它的工作原理。
# 接受过滤器中的静态连接
# options ACCEPT_FILTER_DATA
# options ACCEPT_FILTER_HTTP




在这台计算机上运行的不是互联网服务器,因此无需在编译时包括这二个选项。
# 下面的选项和系统级变量控制系统如何处理适当的TCP数据包。
#
# TCP_DROP_SYNFIN可以支持包含有SYN+FIN的TCP数据包,它使nmap不能识别TCP/IP栈,# 但可以破坏对RFC1644扩展的支持,建议不要在互联网服务器中使用。
#
# TCP_RESTRICT_RST支持阻止TCP RST栈的泄出,对于需要大量SYN的系统或者不希望被简单地扫描到端口的系统非常有用。




我将在防火墙中包括这些选项,在测试防火墙时,仔细看看它们有什么作用。
# ICMP_BANDLIM根据带宽限制产生icmp错误。一般情况下我们需要这个选项,它有助于
# 你的系统免受D.O.S.攻击。
#
options ICMP_BANDLIM




FreeBSD内核缺省支持这一选项。
# DUMMYNET启动“dummynet”带宽限制软件。还需要有IPFIREWALL选项的支持
# BRIDGE启动以太网卡之间的桥接功能




在本例中我不会选这二个选项,因为在独立的计算机系统上无需对流量进行控制。

在重新编译FreeBSD内核之前,我将在内核配置文件中添加下面的内容:
#以缺省的、拒绝所有数据包方式启动IPFW
options IPFIREWALL
options IPFIREWALL_VERBOSE
options IPFIREWALL_VERBOSE_LIMIT=10

#隐藏防火墙
options IPSTEALTH

#使不被nmap发现,如果是互联网服务器则去掉该选项。
options TCP_DROP_SYNFIN

#防止端口扫描
options TCP_RESTRICT_RST




在重新编译内核时,我将再次仔细审查在/etc/rc.conf中添加的选项。下面是手册中有关各个选项的说明:
man rc.conf
/firewall

firewall_enable
(布尔型)如果不想在系统启动时加载防火墙规则集,将其值设置为NO;否则,将其设置为YES。如果它被设置为YES,而内核在编译时没有使用IPFIREWALL选项,ipfw内核模块将自动被加载。
firewall_script
(字符串型)如果要运行一段防火墙脚本程序,而不是/etc/rc.firewall,将这一变量设置为脚本程序的路径全名。
firewall_type
(字符串型)从/etc/rc.firewall或包含规则集的文件中的防火墙类型中指定防火墙类型。/etc/rc.firewall中可选的防火墙类型为:open-不限制IP访问;closed-禁止除通过lo0进行的之外的所有IP服务;client-对工作站的基本保护;simple-对LAN的基本保护。如果给的是一个指定的文件名,则必须使用全路径名。




由于我希望系统启动时加载防火墙规则,因此将把firewall_enable变量的值设置为YES。由于要使用自己的规则集,需要指定使用firewall_type创建的文件的全路径名。
firewall_quiet
(布尔型)如果设置为YES,则系统在启动时,不会在控制台上显示ipfw规则。




由于会显示加载的各条规则,将这个变量设置为YES是一个好主意。如果相关规则中出现了错误,则在这个错误之后的所有规则都不会被加载。如果在启动时看着屏幕,就会在成功加载的最后一条规则之后看到一个ipfw语法消息。这样就可以在规则集中发现出现的错误,然后重新启动机器,使所有的规则都能够被成功地加载。
firewall_logging
(布尔型)设置为YES会启动ipfw事件日志功能,与IPFIREWALL_VERBOSE内核选项的功能相同。
tcp_extensions
(布尔型)缺省状态下被设置为NO。设置为YES可以启动由RFC 1323定义的一些TCP选项。如果连接有随机的问题出现,将其重新设置为NO,看是否能够解决问题,因为一些软、硬件问题都与这个选项有关。
log_in_vain
(布尔型)缺省状态下设置为NO。设置为YES将把对端口的连接尝试记入日志中。
tcp_keepalive
(布尔型)缺省状态下设置为YES。设置为NO会禁止对空闲的TCP连接的探查。
tcp_drop_synfin
(布尔型)缺省状态下设置为NO。设置为YES会使内核忽略有SYN和FIN标志的TCP帧。 虽然这样会提供操作系统的指纹,但会使一些正常的应用软件出毛病。只有在编译内核时使用了TCP_DROP_SYNFIN选项,该选项才有效。




由于在内核中添加了TCP_DROP_SYNFIN选项,我将这一变量的值设置为YES。如果在计算机上运行互联网服务器软件,应该去掉这一选项。
tcp_restrict_rst
(布尔型)缺省状态下设置为NO。设置为YES将使内核在响应无效的TCP数据包时不能输出TCP RST帧。只有在编译内核时使用了TCP_RESTRICT_RST选项,这一选项才有效。
icmp_drop_redirect
(布尔型)缺省状态下设置为NO。设置为YES将使内核忽略ICMP REDIRECT信息包。
icmp_log_redirect
(布尔型)缺省状态下设置为NO。设置为YES将使内核在日志中记录ICMP REDIRECT信息包。由于日志是没有什么限制的,因此只有在对网络维护时才会使用这一选项。




最终,我在系统中的/etc/rc.conf文件中加入了下面的内容:
#用于支持ipfw的选项
firewall_enable="http://blog.5dmail.net/YES"
firewall_script="/etc/rc.firewall"
firewall_type="/etc/ipfw.rules"
firewall_quiet="NO" #对现有的规则满意后将其值改为YES
firewall_logging_enable="YES"
#附加的防火墙选项
log_in_vain="YES"
tcp_drop_synfin="YES" #如果要创建互联网服务器,将其值改为NO。
tcp_restrict_rst="YES"
icmp_drop_redirect="YES"




在重新启动机器运行新的内核之前,有一点需要注意。如果LINT文件显示出“YOU WILL LOCK YOURSELF OUT”(你将封锁自己),说明新的规则已经起作用了。在重新创建允许所需的IP数据包进入系统之前,所有的IP数据包都不能进入或传出计算机。如果想从互联网上收发电子邮件和下载资料,就需要在重新启动系统之前完成这些工作。

创建一个好的规则集是一件技术性很强固的工作。如果是第一次创建防火墙,需要有大量的时间进行练习,就会发现ipfw所使用的逻辑与你认为的逻辑不完全相同。

此外,防火墙并非是安装后就一劳永逸了,需要花些时间对它进行优化,在它不能完成你预期的任务时多想想这是为什么。一旦用有防火墙的新内核启动机器后,你可能希望完成下面的三项工作。

▲系统地在规则集中添加新的规则,测试每条规则的作用,确保只有你需要的数据包才能够出入你的系统。

▲决定你要将哪些IP数据包记入日志并查看日志文件,随着不断发现你禁止或允许的一些数据包出入系统,会不断地修改规则。

▲一旦对防火墙允许或不允许通过哪些数据包满意了,就需要测试防火墙的性能是否能够令人满意。

好了,下面我要重新启动机器加载新的内核。在启动时盯着屏幕,在NIC加载后会看到下面的信息:
Flushed all rules.
00100 allow ip from any to any via lo0
00200 deny ip from any to any to 127.0.0.0/8
Firewall rules loaded, starting divert daemons:.
Additional routing options: tcp extensions=NO ignore ICMP redirect=YES TCP keepalive=YES restrict TCP reset=YES drop SYN+FIN packets=YES.

Additional TCP options: log_in_vain=YES.




你可能会有疑惑,没有创建包含规则集的文件,怎么会显示头三行信息呢?在编辑/etc/rc.conf时,我在其中添加了下面一行的内容:
firewall_script="/etc/rc.firewall"




在启动时系统会读取/etc/rc.firewall文件,该文件包含下面的内容:
############
# Flush out the list before we begin.
#
${fwcmd} -f flush

############
# 只有在极少数的情况下才需要改变这些规则
#
${fwcmd} add 100 pass all from any to any via lo0
${fwcmd} add 200 deny all from any to 127.0.0.0/8




由于规则100和规则200在启动时会用到,因此在创建规则时应该从规则300开始。在创建规则之前,我会用下面的方法分二次检查ipfw是否缺省地禁止所有的信息包出入我的计算机系统。可以通过运行ipfw show命令来进行检查:
ipfw show
ipfw: socket: Operation not permitted




似乎只有超级用户才有查看防火墙规则的权限,因此我将再次以超级用户再身份次进行检查:
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
65535 115 14092 deny ip from any to any




确实已经禁止所有的信息包出入计算机系统,我再来试着使用一下网络连接。
ping www.freebsd.org
ping: cannot resolve www.freebsd.org: Host name lookup failure

traceroute www.freebsd.org
traceroute: unknown host www.freebsd.org

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




好了,名字解析也无效,我们再试试ping吧:
ping 24.141.116.1
PING 24.141.116.1 (24.141.116.1): 56 data bytes
ping: sendto: Permission denied
ping: sendto: Permission denied
^C
--- 24.141.116.1 ping statistics ---
2 packets transmitted, 0 packets received, 100% packet loss




由于我是以超级用户的身份运行最后一个ping的,因此ipfw确实已经禁止所有信息包的出入,我已经完全不能使用网络连接了。现在我们来创建一个允许收发我需要的IP信息包的规则集。

有二种方式可以创建被ipfw读取的规则:

如果已经在使用ipfw,不用重启动机器就能使规则生效。但是,如果重新启动机器后,添加的规则就会丢了。

你还可以在让ipfw读取的文件中添加一条规则,这样只有机器重新启动后,新添加的规则才能生效。

由于这台机器只有我一个人使用,因此,我将把规则直接加进文件中并重启机器。我已经在/etc/rc.conf中添加了下面的一行内容:
firewall_type="http://blog.5dmail.net//etc/ipfw.rules"




因此,我将创建一个名字为/etc/ipfw.rules的文件。

用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

这次我发现自己在http://www.freebsd.org的主页上了。?..抡飧鯥P地址吧:
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地址访问http://www.freebsd.org的网站,因此?..求的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连接的条目。

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


用IPIW实现BSD防火墙(下)

上面我们已经创建了一个规则集,使ipfw能够允许对发出的互联网请求进行响应,并能进行DNS名字解析。下面我们将仔细调节已经创建的规则集的性能,并通过内置的登录工具对它进行测试。

创建的规则集已经可以起作用了,因此可以在DHCP规定的时间用完之前使用互联网连接了。时间用完后,互联网连接就不能再使用了。要搞清楚是哪条规则实现互联网连接的,对DHCP的基本工作原理有一定的了解是很有必要的。

DHCP使用UDP数据包,意味着动态规则和状态表在这里是不起作用的。因此必须允许在我的计算机和ISP的DHCP服务器之间传输UDP数据包。DHCP需要二个端口:DHCP客户端使用端口68,ISP的DHCP服务器使用端口67。

为了搞明白DHCP的工作原理,我们来看一下我的计算机的DHCP“租用”文件:
more /var/db/dhclient.leases

lease {
interface "ed0";
fixed-address 24.141.119.162;
option subnet-mask 255.255.252.0;
option time-offset -18000;
option routers 24.141.116.1;
option domain-name-servers 24.226.1.90,24.226.1.20,24.2.9.34;
option host-name "my_hostname";
option domain-name "my_domainname";
option broadcast-address 255.255.255.255;
option dhcp-lease-time 604800;
option dhcp-message-type 5;
option dhcp-server-identifier 24.226.1.41;
renew 2 2001/5/15 13:12:11;
rebind 5 2001/5/18 04:12:11;
expire 6 2001/5/19 01:12:11;
}

DHCP服务器提供了一个IP地址、子网掩码、缺省的网关地址、三个DNS服务器的IP地址、我的主机名和提供服务的DHCP服务器的IP地址。由于DHCP“租用”契约是一种真正的“契约”,这意味着我必须保存好这些信息,最后三行内容与我的DHCP客户端如何重新修改“租用”契约有关。

以renew开头的这一行向我的DHCP客户端表明它何时应该结束,并更新其“租用”契约,这一时间要早于expire行中列出的时间。在2001年5月15日13时12分11秒,我的计算机将会向IP地址为24.226.1.41的DHCP服务器上的端口67发送UDP数据包,因此需要添加一条规则允许向外发送UDP数据包。如果DHCP服务器收到了我的计算机发送的UDP数据包,它应该对要求更新“租用”契约的要求作出响应,并且以UDP数据包形式将此信息发回到我的计算机上的68端口。因此,我另外还需要在规则集中添加一规则,允许ipfw对此信息作出反应。

如果不在规则集中添加这些规则,或者由于其他原因DCHP服务器没有对我的计算机发出的更新“租用”契约的要求作出响应,rebind行将在2001年5月18日4时12分11秒启动,这时,我的DCHP客户端就会开始担心“租用”期满,将会向DHCP服务器发出更多的UDP数据包,只是这次将不再向特定的DHCP服务器发送数据包,而是会向255.255.255.255发送数据包,任何服务器都可以响应发出的请求。

如果没有DHCP服务器进行响应,我的计算机的契约会在2001年5月19日1时12分11秒结束,这意味着我的DCHP客户端不能保证还可以继续使用这些租用信息。这时,会有几种情况出现。客户端将继续试图与DHCP服务器联系,向端口67发送UDP数据包。它将继续试图用ping与缺省的网关联系,检查其IP地址是否仍然有效。在最坏的情况下,我的客户端的IP地址已经无效,DHCP服务器的应答将作为广播被IP地址为255.255.255.255的机器的68端口接收。

既然已经明白了其工作原理,我们就清楚应该在规则集中添加什么样的规则了。在添加规则前,应该对规则进行仔细的检查,因为规则的顺序已经越来越重要了。规则集中的规则越多,前面的规则覆盖后面新添加的规则的可能性也就越大。设计一个好的规则集的诀窍是让你希望的数据包使用尽可能少的规则,如果添加的规则过多,尽管防火墙仍然会起作用,但这样会加重ipfw不必要的负担,因为在找出一个数据包适应的规则前它需要读取更多的规则。此外,在你希望搞清楚到底是哪一条规则使系统不能按你的意愿运行时,规则太多了会相当的麻烦。

我将以超级用户的身份运行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
00403 0 0 allow udp from any to any out
65535 0 0 deny ip from any to any

由于我需要向外发送UDP数据包,因此需要指明DHCP端口号和DHCP服务器的IP地址,作为超级用户,我将考虑在/etc/ipfw.rules文件中添加下面的内容:
#allow DHCP
add 00500 allow udp from any 68 to 24.226.1.41 67 out via ed0
add 00501 allow udp from 24.226.1.41 67 to any 68 in via ed0

这些规则可以说是使DHCP客户端更新其“租用”契约所需要的最少的规则了,是否需要添加更多的规则会因DHCP服务器可靠性的不同而有所不同。如果DHCP服务器总是能够响应我的更新请求,我也就无需采用发送UDP广播、ping缺省的网关或者接收UDP广播这些方式了。如果DHCP服务器的可靠性不高,那就还需要添加下面的规则:
add 00502 allow udp from any 68 to 255.255.255.255 67 out via ed0
add 00503 allow udp from any 67 to 255.255.255.255 68 in via ed0

由于我使用的DHCP服务器是相当可靠的,因此不需要立即添加00502和00503这二条规则。我只是反复提醒自己,在ISP的DHCP服务器出了问题或其IP地址有变化的时候就需要考虑这二条规则了。

在保存修改之前,我将把00500和00501二条规则与其余的规则进行比较,以确保它们之间没有任何冲突和重复之处,结果00403和00500之间确实存在着部分重复:
add 00403 allow udp from any to any out
add 00500 allow udp from any 68 to 24.226.1.41 67 out via ed0

因为规则00403允许我的计算机发出任何的UDP数据包,ipfw就不会读取其他的只从端口68发送UDP数据包的规则,在这里,就需要在使用数量最少的规则还是使用把各种可能都考虑在内的数量最多的规则之间进行选择了。

规则00403是在创建允许DNS解析时添加的,如果删除了它,就需要添加三条规则才能实现向三个DNS服务器发送UDP数据包的功能。另外,如果还需要向其他的服务器发送UDP数据包,我就必须再添加规则。因此,如果不使用这样一条“通用”的规则,ipfw规则集中就会包含一些多余的规则,使系统负担不必要的负荷。

这样作也不符合使用最少数量规则的原则,但我们必须仔细审查一下“通用”规则带来的潜在后果。如果我限制系统接受UDP数据包,向外发送UDP数据包是不会有什么危险的。例如,规则00403允许我的计算机向外发送任何数据包,但规则00400、00401、0402和00501保证我的计算机只能接受我的ISP的3台DNS和一台DHCP服务器发送的UDP数据包。因此,对于我的单独运行的FreeBSD计算机而言,这个规则集还是比较合理的。

如果我在FreeBSD防火墙后面添加新的客户端机器,就需要重新考虑这个规则集。例如,微软的客户端会发送数量不等的UDP数据包通报其共享资源,让这些数据包通过防火墙发送出去,对我而言是不负责任并具有一定安全风险的。在本例中,我将使用只能发出我需要发出的UDP数据包的规则,而删除可以发出任意UDP数据包的规则。

由于我现在保护的只是一台单独的FreeBSD计算机,因此我将保留规则00403,删除规则00500,因为系统永远都不会读取到它。我将对规则进行如下的改变:
#允许DHCP 操作
add 00501 allow udp from 24.226.1.41 67 to any 68 in via ed0

保存所作的改变,并通过使用killall init命令进行测试,以超级用户身份重新登录,看看所作改变的效果。
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 8 1322 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 8 469 allow udp from any to any out
00501 4 1592 allow udp from 24.226.1.41 67 to any 68 in recv ed0
65535 29 8591 deny ip from any to any

我好象从IP地址为24.226.1.90的DNS服务器收到了8个UDP数据包,从IP地址为24.226.1.41的DHCP服务器收到了4个UDP数据包。现在,我们再来看看DHCP的“租用”时间问题。
more /var/db/dhclient.leases
renew 3 2001/5/16 07:46:25;
rebind 5 2001/5/18 08:50:46;
expire 6 2001/5/19 01:12:14;

在执行killall init命令时,我成功地找到了DHCP服务器并更新了其“租用”时间,因此,我的DHCP规则是比较成功的。

由于我的规则集还拒绝收发所有的ICMP数据包,下面我们再来研究一下允许ICMP数据包收发的问题。阻止收发ICMP数据包并不是一件好事,因为它会破坏Path-MTU发现并阻止Source Quench信息。另外,ICMP使用types和codes来指定真正的ICMP信息。

在创建与ICMP有关的规则时,只能指定ICMP数据包的type而不能指定它的code。我将以超级用户的身份登录,在/etc/ipfw.rules文件中添加下面的内容:
#允许接受一些ICMP types (不支持codes)
###########允许双向的path-mtu
add 00600 allow icmp from any to any icmptypes 3
add 00601 allow icmp from any to any icmptypes 4

我需要考虑是否需要ping我的网络之外的主机或运行traceroute命令,由于二者都需要,并希望收到相应的应答,但我并不希望互联网上的所有用户都可以对我运行ping 或traceroute命令,因此,我需要添加下面的规则:
###########允许我对外部的主机运行ping命令,并得到相应的应答
add 00602 allow icmp from any to any icmptypes 8 out
add 00603 allow icmp from any to any icmptypes 0 in

###########允许我运行traceroute命令
add 00604 allow icmp from any to any icmptypes 11 in

ICMP type 8是一个重复的请求,ICMP type 0是反复的应答。由于我只允许反复地发出请求并接受应答,从而我可以ping别人而别人不能ping我。

在运行traceroute命令时,就会向外发出UDP数据包,这一点在规则00403中已经得到了保证。如果希望能够获得所有应答信息,我还必须允许系统接受所有的CMP type 11数据包。

好了,我们现在保存所作的修改,并使用ipfw zero命令对ipfw计数器重新复位。然后运行killall init命令,并试运行ping和traceroute命令:
ping www.freebsd.org
PING freefall.freebsd.org (216.136.204.21): 56 data bytes
64 bytes from 216.136.204.21: icmp_seq=0 ttl=239 time=85.250 ms
64 bytes from 216.136.204.21: icmp_seq=1 ttl=239 time=88.338 ms
64 bytes from 216.136.204.21: icmp_seq=2 ttl=239 time=83.757 ms
^C
--- freefall.freebsd.org ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max/stddev = 83.757/85.782/88.338/1.908 ms

traceroute www.freebsd.org
traceroute to freefall.freebsd.org (216.136.204.21), 30 hops max, 40 byte packets
1 10.69.4.1 (10.69.4.1) 8.678 ms 8.739 ms 10.055 ms
2 d226-12-1.home.cgocable.net (24.226.12.1) 9.800 ms 10.642 ms 7.876 ms
3 cgowave-0-158.cgocable.net (24.226.0.158) 25.910 ms 15.288 ms 13.693 ms
4 cgowave-busy-core.cgocable.net (24.226.1.1) 26.982 ms 16.521 ms 12.376 ms
5 cgowave-0-202.cgocable.net (24.226.0.202) 14.372 ms 14.224 ms 13.728 ms
6 216.197.153.65 (216.197.153.65) 14.112 ms 13.544 ms 42.612 ms
7 c1-pos8-0.bflony1.home.net (24.7.74.29) 15.093 ms 22.387 ms 18.530 ms
8 c1-pos1-0.hrfrct1.home.net (24.7.65.253) 25.953 ms 26.703 ms 26.514 ms
9 c1-pos3-0.nycmny1.home.net (24.7.69.2) 26.279 ms 29.810 ms 38.940 ms
10 * ibr02-p1-0.jrcy01.exodus.net (24.7.70.122) 32.121 ms 38.211 ms
11 bbr02-g5-0.jrcy01.exodus.net (216.32.223.130) 34.239 ms 34.815 ms 37.106 ms
12 bbr01-p2-0.okbr01.exodus.net (216.32.132.109) 37.643 ms 36.883 ms 36.201 ms
13 216.34.183.66 (216.34.183.66) 37.624 ms 39.455 ms 40.243 ms
14 bbr01-p0-0.snva03.exodus.net (206.79.9.85) 81.494 ms 82.421 ms 83.230 ms
15 64.15.192.34 (64.15.192.34) 79.431 ms 80.981 ms 115.289 ms
16 bbr02-p4-0.sntc05.exodus.net (209.185.9.70) 81.993 ms 99.964 ms 82.169 ms
17 dcr01-g6-0.sntc05.exodus.net (64.56.192.19) 81.324 ms 81.603 ms 80.146 ms
18 g2-1.bas1-m.sc5.yahoo.com (64.56.207.146) 81.867 ms 100.628 ms 94.995 ms
19 freefall.freebsd.org (216.136.204.21) 104.100 ms 95.821 ms 85.909 ms

一切正常。现在我将以超级用户的身份来看看系统使用了哪些规则。
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 29 5847 allow udp from 24.226.1.90 53 to any in recv ed0
00401 2 163 allow udp from 24.226.1.20 53 to any in recv ed0
00402 3 397 allow udp from 24.2.9.34 53 to any in recv ed0
00403 93 4712 allow udp from any to any out
00501 0 0 allow udp from 24.226.1.41 67 to any 68 in recv ed0
00600 3 168 allow icmp from any to any icmptype 3
00601 0 0 allow icmp from any to any icmptype 4
00602 3 252 allow icmp from any to any out icmptype 8
00603 3 252 allow icmp from any to any in icmptype 0
00604 53 2968 allow icmp from any to any in icmptype 11
65535 29 8591 deny ip from any to any
我们可以发现,运行ping命令时使用了三个反复的请求数据包(规则00602)和三个反复的应答数据包(规则00603)。此外,有53个ICMP type 11数据包响应traceroute命令(规则00604),然而,由于我不能够在规则中指定具体的code,因此我不能说出为什么会接收到三个“找不到目标”的信息。

[ 本帖最后由 jaffas1101 于 2006-7-25 16:55 编辑 ]

feitianxiang 发表于 2006-7-11 21:40:32

版主顶一下你,申请加精,哈哈

jaffas1101 发表于 2006-7-13 10:10:42

赫赫 感谢分享!!!

khbd2k6 发表于 2006-7-25 16:26:25

标题写错了,ipfw

jaffas1101 发表于 2006-7-25 16:55:27

修改好了!!!
页: [1]
查看完整版本: 用IPFW实现BSD防火墙