如果您家中有多台计算机需要同时上网,但却只有一个 IP 可以使用,这时 NAT 就派上用场了。NAT 可以让很多计算机经由一台 FreeBSD 服务器上网,而且还可以让 FreeBSD 的防火墙功能保护内部网络的计算机。这里介绍如何用FreeBSD 架设 NAT 服务器及使用 FreeBSD 防火墙功能。
我们举二种最常被应用结合防火墙设定来保护整个网络的方法,一个是 NAT,另一个是具封包过滤的桥接器。下面我们分三个段落来明晰。
一 NAT介绍:
所谓的 NAT 就是 (Network Address Translation),它可以让你在只有一个 IP 的情形下让多台计算机一起连上网络。举个实例而言,一个公司有三十台计算机,而 ISP 所提供的 ADSL 却只有八个实体 IP,这种情况下,我们可以将每台计算机的 IP 设定为 private IP,再让它们经由一台有实体 IP 的 NAT 服务器连上网络即可。
Private IP 是 RFC 所定义的私人 IP,这些 IP 不能够直接在因特网中出现,所以必需经由 NAT 的转换,将它们伪装成是由 NAT 服务器连向外部网络。这些可以用的私人 IP 如下:
Class | 范围 | 子网掩码 |
Class A | 10.0.0.0 ~ 10.255.255.255 | 255.0.0.0 |
Class B | 172.16.0.0 ~ 172.31.255.255 | 255.255.0.0 |
Class C | 192.168.0.0 ~ 192.168.255.255 | 255.255.255.0 |
我们只需在 NAT 服务器中做好设定,再将其它使用 private IP 的计算机设定 gateway 为该服务器的 IP 即可。另外,我们也可以在服务器中设定一些防火墙的规则,来保全内部网络。
1)具封包过滤的桥接器
如果我们的网络中有多台不同网域的计算机,这些计算机都有它们的 IP 及网络设定,我们可以将 FreeBSD 设定成为桥接器 (bridge),让这台桥接器作封包过滤的工作。这种做法对于网域内其它计算机原本的网络设定不会有影响,如果没有设定任何防火墙规则,对它们而言几乎 不会发现桥接器的存在。我们可以使用桥接器来过滤同一个网域内的网络交通,如果您有一个很大的网域,想要降低同一网域内彼此网络封包的交互影响,却又不想 将这个大网域分割成数个小网域,您可以使用具封包过滤功能的桥接器来达成网络封包分割的功能。我们也可以使用路由器来取代桥接器,但是路由器只能�v送二个 不同网域,而且设定比较复杂,因此,我会使用桥接器来做为防火墙。
FreeBSD 内建有 ipfw 这个程序可以让我们轻易的设定一个简单的防火墙,我们只要在 kernel 中加上一些设定就可以打开它。在这里我们也将简单的介绍一些防火墙的语法,让我们可以保护我们不想、不需要被外界使用的网络服务。
在设定防火墙之前,有个观念必须先厘清。防火墙并不能够完全保护我们的网络安全,防火墙只是限制我们不想公开的服务、限制已知的 IP。就算架了防火墙,没有适当的管理也是枉然。
2)Nat
这里我们假设使用二张网络卡,一张是对外的网卡,代号是 fxp0;另一张是对内的网卡,代号 fxp1。以下的设定中请依您的网卡代号来加以修改。当然,你也可以只使用一张网络卡,将所有的计算机及对外网络都接在一台 HUB 上,再利用 alias 的功能将一张网卡设定二个 IP。在开始前,请先参考「网络设定」一章中的说明设定好第一张对外的网络卡喔。
在开始设定之前,请先检查一下内部网络的配置是否正确。我们内部网络的线路应该如图所示:
上图中,内部网络的计算机全部都接到同一个 HUB 中,而 FreeBSD 有二个网络卡,对内的网络卡 fxp1 接在内部的 HUB 上,而对外的网络卡直接接在 ADSL Modem 或是对外的 HUB 上。上图的网络配置只是一个建议,您也可以将 fxp0、fxp1、及 ADSL Modem 全部接在内部的 HUB 上,只是这样 FreeBSD 就没有真正的隔离内外部网络了。
3)设定内核(kernel)
首先,我们必须先确定核心有支持 NAT 及防火墙功能。FreeBSD 预设的 GENERIC 核心并未加入此功能,因此,请先编辑您的核心设定档,加入下列设定,并重新编译核心。如果您不知道如何修改核心设定,请参考「编译核心」一章的说明。假设 我们要修改的核心设定档为 /usr/src/sys/i386/conf/GENERIC,先 cd /usr/src/sys/i386/conf/,再 ee GENERIC 加入下列几行:
# 防火墙 |
我们在上述设定中加入了许多项目,基本上,一定要有的项目为 IPFIREWALL 及 IPDIVERT,其它项目是为了支持限制频宽或记录信息使用。编辑完核心设定后,请重新编译并安装新的核心,重开机之后核心就己经支持防火墙及 NAT 了。
4)设定 rc.conf
设定好您的第一张网卡,并确定可以上网后,才开始下列设定。我们要修改 /etc/rc.conf 以启动 NAT 功能。我们假设网络卡代号是 fxp0 及 fxp1,请自行变更成您的网络卡代号:
# 设定第二张网络卡的 IP。 |
设定结束之后,重开机应该就可以设定其它计算机使用这台 NAT 服务器来连上网络了。
5)设定 rc.firewall
我们在 /etc/rc.conf 中设定 firewall_type="OPEN",如果是使用原本 /etc/rc.firewall 的话,这样就已经就已经驱动了 NAT 的功能。
如果您想要加上更多的防火墙规则,可以编辑 /etc/rc.firewall ,并在档案最后加上您的设定。例如,我们要设定每一个内部计算机 (192.168.0.0/16) 最多只能使用的上传频宽为 64 Kb/s,下传频宽为 256 Kb/s,则可以在 rc.firewall中加入下列设定:
# 限制频宽 |
修改完后,执行 sh /etc/rc.firewall 就可以更新防火墙的设定了。其它关于防火墙规则的详细说明,请 man ipfw 或参考下一节的说明。
6)client 端的设定
在内部其它计算机方面,我们必须要再做一些设定才能让它们经由 FreeBSD 上网。首先,你的网络架构应该如图 12-1 所示。
而内部的其它的计算机设定方面,我们必须将其它计算机的 IP 设定为和 FreeBSD 服务器同一个子网络。以上列设定为例,您必须将其它计算机的 IP 设为 192.168.0.X (除了 192.168.0.1 以外的其它 IP)、子网掩码是 255.255.255.0,gateway 设定为 FreeBSD 连到局域网络的网络卡 IP,在此范例中是 192.168.0.1。然后再设定你的 DNS 为你 ISP 的 DNS 即可。
完成上述的设定后,我们就能享受以 FreeBSD 为NAT上网了。
如果您的其它计算机还是无法上网,您可以依下列步骤除错:
7)NAT Port Forwarding
NAT 还有一个功能叫作 Port Forwarding,它的用途在于从连到本机的封包导向到别的计算机或本机其它连接埠。例如,我们对外有一台防火墙,在 DNS 设定方面,我们设定了 ftp.mydomain.com 及 www.mydomain.com 都指向这台防火墙。但我们希望所有 HTTP 联机都重新导向到内部的 192.168.0.2 这台机器上,而所有 FTP 联机都交由 192.168.0.3 来处理。这时候我们就可以使用 port forwarding 的方式来达成。
首先,我们知道 HTTP 使用 TCP 协议 port 80,而 FTP 使用了 TCP 协议 port 20 及 port 21,接着我们就可以在 /etc目录下新增一个 NAT 的设定档,名为 natd.conf,并编辑内容如下:
redirect_port tcp 192.168.0.2:80 80 |
第一行的目的就是将 port 80 的 tcp 联机重新导向到 192.168.0.2 的 port 80,而二、三行是将 port 20 及 port 21 的联机交由 192.168.0.3 来处理。在 192.168.0.2 及 192.168.0.3 这二台机器上,我们只要设定它们的 gateway 为防火墙的 IP,例如 192.168.0.1 即可。
接下来我们必须在 /etc/rc.conf 中加入下列这一行,让 natd 在启动时能读取 /etc/natd.conf 的设定:
natd_flags="-f /etc/natd.conf" |
重新启动后,您就可以进行测试了。假设防火墙的对外 IP 是 11.22.33.44,当我们从外部网络使用 HTTP 联机到该 IP 时,虽然该服务器并未架设 HTTP server,但你却可以看到网页,表示网络封包有被重导至内部的另一台服务器。
[小提示 ]
Port Forwarding 的功能是将外部联机转向到内部的计算机。所以,如果您从内部计算机连到 NAT 服务器时,并不会被转向到内部 IP 喔。
二 防火墙
ipfw 是 FreeBSD 内建的防火墙指令,我们可以用它来管理进出的网络交通。如果防火墙服务器是扮演着路由器 (gateway 例如上一篇中的 NAT 服务器) 的角色,则进出的封包会被 ipfw 处理二次,而如果防火墙扮演的是桥接器 (bridge) 的角色,则封包只会被处理一次。这个观念关系着我们以下所要介绍的语法,有的语法并不适用于桥接器。
另外,我们在设定防火墙时有二种模式,一种模式是预设拒绝所有联机,再一条一条加入允许的联机;另一种是预设接受所有联机,加入几条拒绝的规则。如果是非常强调安全性,应该是使用预设拒绝所有联机,再一条一条加入我们允许的规则。
我们会将 firewall 的设定写在 /etc/rc.firewall 中,每一条设定都是以先入为主 (first match wins) 的方式来呈现,也就是先符合的规则 (rules) 为优先。所有进出的封包都会被这些规则过滤,因此我们会尽量减少规则的数量,以加速处理的速度。
在 kernel 中,关于防火墙的设定有下列几条:
# 防火墙 |
ipfw 也支持状态维持 (keep-state) 的功能,就是可以让符合设定的规则以动态的方式来分配增加规则 (地址或连接端口) 来让封包通过。也就是说防火墙可以记住一个外流的封包所使用的地址及连接端口,并在接下来的几分钟内允许外界响应。这种动态分配的规则有时间的限制,一段 时间内会检查联机状态,并清除记录。
所有的规则都有计数器记录封包的数量、位数、记录的数量及时间等。而这些记录可以用 ipfw 指令来显示或清除。
在说明 ipfw 规则的语法之前,我们先来看这个指令的用法。ipfw 可以使用参数:
指令 | 说明 |
ipfw add [rule] | 新增一条规则。规则 (rule) 的语法请参考下一节的说明。 |
ipfw delete [number] | 删除一条编号为 number 的规则。 |
ipfw -f flush | 清除所有的规则。 |
ipfw zero | 将计数统计归零。 |
ipfw list | 列出现在所有规则,可以配合下列参数使用。 |
-a | 使用 list 时,可以列出封包统计的数目。 |
-f | 不要提出确认的询问。 |
-q | 当 新增 (add)、归零(zero)、或清除 (flush) 时,不要列出任何回应。当使用远程登入,以 script (如 sh /etc/rc.firewall) 来修改防火墙规则时,内定会列出你修改的规则。但是当下了 flush 之后,会立即关掉所有联机,这时候响应的讯息无法传达终端机,而规则也将不被继续执行。此时唯一的方法就是回到该计算机前重新执行了。在修改防火墙规则 时,最好在计算机前修改,以免因为一个小错误而使网络联机中断。 |
-t | 当使用 list 时,列出最后一个符合的时间。 |
-N | 在输出时尝试解析 IP 地址及服务的名称。 |
-s [field] | 当列出规则时,依哪一个计数器 (封包的数量、位数、记录的数量及时间) 来排序。 |
1) ipfw 规则
在过滤封包时,可以依据下列的几个封包所包含的信息来处理该封包:
使用 IP 地址或 TCP/UDP 的端口号来做为规则可能蛮危险的,因为这二种都有可能被以假的信息所蒙骗 (spoof)。但是这二种却也是最常被使用的方法。
下列为 ipfw rules 的语法:
[number] action [log] proto from src to dist [interface_spec] [option]
使用 [ ] 包起来的表示可有可无,我们一一为大家说明它们的意义:
number:
number 是一个数字,用来定义规则的顺序,因为规则是以先入为主的方式处理,如果你将规则设定放在一个档案中 ( 如 /etc/rc.firewall ),规则会依每一行排列的顺序自动分配编号。你也可以在规则中加上编号,这样就不需要按顺序排列了。如果是在命令列中下 ipfw 指令来新增规则的话,也要指定编号,这样才能让规则依我们的喜好排列,否则就会以指令的先后顺序来排。这个编号不要重复,否则结果可能不是你想要的样子。
action:
action 表示我们这条规则所要做的事,可以用的 action 有下列几个:
命令 | 意义 |
allow | 允许的规则,符合则通过。也可以使用 pass,permit, accept 等别名。 |
deny | 拒绝通过的规则。 |
reject | 拒绝通过的规则,符合规则的封包将被丢弃并传回一个 host unreachable 的 ICMP。 |
count | 更新所有符合规则的计数器。 |
check-state | 检查封包是否符合动态规则,如果符合则停止比对。若没有 check-state 这条规则,动态规则将被第一个 keep-state 的规则所检查。 |
divert port | 将符合 divert sock 的封包转向到指定的 port。 |
fwd ipaddr[,port] | 将 符合规则封包的去向转向到 ipaddr,ipaddr 可以是 IP 地址或是 hostname。如果设定的 ipaddr 不是直接可以到达的地址,则会依本机即有的 routing table 来将封包送出。如果该地址是本地地址 (local address),则保留本地地址并将封包送原本指定的 IP 地址。这项设定通常用来和 transparent proxy 搭配使用。例如: #ipfw add 50000 fwd 127.0.0.1,3128 tcp from \ 如果没有设定 port ,则会依来源封包的 port 将封包送到指定的 IP。使用这项规则时,必须在 kernel 中设定选项 IPFIREWALL_FORWARD。 |
pipe pipe_nr | 传递封包给 dummynet(4) "pipe",用以限制频宽。使用本语法必须先在核心中加入 option DUMMYNET。请 man ipfw 及 man dummynet。 基本语法是先将要设定频宽的规则加入: ipfw add pipe pipe_nr .... 再设定该规则的频宽: ipfw pipe pipe_nr config bw B delay D queue Q plr P 这 里的 pipe_nr 指的是 pipe 规则编号,从 1~65535;B 是指频宽,可以表示为 bit/s、Kbit/s、Mbit/s、Bytes/s、KBytes/s、或 MBytes/s。D 是延迟多少 milliseconds (1/1000)。Q 是 queue size 的大小 (单位为 packages 或 Bytes)。P 是要随机丢弃的封包数量。 例如我们要限制内部网域的计算机对外上传的最大频宽是 20 KBytes: ipfw add pipe 1 ip from 192.168.0.1/24 to any in |
log:
如果该规则有加上 log 这个关键词,则会将符合规则的封包记录在 /var/log/security 中。前提是在核心中有设定 IPFIREWALL_VERBOSE 的选项。有时因为同样的封包太多,会使记录文件保有大量相同的记录,因此我们会在核心中再设定 IPFIREWALL_VERBOSE_LIMIT 这个选项,来限制要记录多少相同的封包。
proto:
proto 表示 protocol,即网络协议的名称,如果使用 ip 或 all 表示所有协议。可以使用的选项有 ip,all,tcp,udp,icmp 等。
src 及 dist:
src 是封包来源;dist 是封包目的地。在这二个项目可以用的关键词有 any, me, 或是以 <address/mask>[ports] 的方式明确指定地址及端口号。
若使用关键词 any 表示使这条规则符合所有 ip 地址。若使用关键词 me 则代表所有在本系统接口的 IP 地址。而使用明确指定地址的方式有下列三种:
而在 me, any 及 指定的 ip 之后还可以再加上连接埠编号 (ports),指定 port 的方法可以是直接写出 port ,如 23;或给定一个范围,如 23-80;或是指定数个 ports,如 23,21,80 以逗点隔开。或者是写出在 /etc/services 中所定义的名称,如 ftp,在 services 中定义是 21,因此写 ftp 则代表 port 21。
interface-spec:
interface-spec 表示我们所要指定的网络接口及流入或流出的网络封包。我们可以使用下列几个关键词的结合:
关键词 | 意义 |
in | 只符合流入的封包。 |
out | 只符合流出的封包。 |
via ifX | 封包一定要经过接口 ifX,if 为接口的代号,X 为编号,如 vr0。 |
via if* | 封包一定要经过接口 ifX,if 为接口的代号,而 * 则是任何编号,如 vr* 代表 vr0,vr1,...。 |
via any | 表示经过任何界面的封包。 |
via ipno | 表示经过 IP 为 ipno 界面的封包。 |
via 会使接口永远都会被检查,如果用另一个关键词 recv ,则表示只检查接收的封包,而 xmit 则是送出的封包。这二个选项有时也很有用,例如要限制进出的接口不同时:
ipfw add 100 deny ip from any to any out recv vr0 xmit ed1
recv 接口可以检查流入或流出的封包,而 xmit 接口只能检查流出的封包。所以在上面这里一定要用 out 而不能用 in,只要有使用 xmit 就一定要使用 out。另外,如果 via 和 recv 或 xmit 一起使用是没有效的。
有的封包可能没有接收或传送的接口:例如原本就由本机所送出的封包没有接收接口,而目的是本机的封包也没有传送界面。
options:
我们再列出一些常用的 option 选项 ,更多选项请 man ipfw:
选项名称 | 意义 |
keep-state | 当符合规则时,ipfw 会建立一个动态规则,内定是让符合规则的来源及目的地使用相同的协议时就让封包通过。这个规则有一定的生存期限 (lift time,由 sysctl 中的变量所控制),每当有新的封包符合规则时,便用重设生存期限。 |
bridged | 只符合 bridged 的封包。 |
established | 只适用于 TCP 封包,当封包中有 RST 或 ACK bits 时就符合。 |
uid xxx | 当使用者 uid 为 xxx 则符合该规则。例如,我们如果要限制 Anonymous FTP 的下载速度最大为 64KB/s,则可以使用: ipfw pipe 1 config bw 512Kbit/s 上 列规则第一行是先建一个编号为 1 的 pipe,限制频宽为 512 Kbit/s (也就是 64 KByte/s),接着第二条是当使用者 uid 为 21 时,从本机 (me) 下载的 tcp 封包都使用编号 1 的 pipe。因为 Anonymous FTP 的使用者是 ftp,它的预设 uid 为 21,所以这条规则会被套用在 Anonymous FTP user 上。 |
setup | 只适用于 TCP 封包,当封包中有 SYN bits 时就符合。 |
以上的说明只是 man ipfw 中的一小部份。如果你想要对 ipfw 更了解,例如如何使用 ipfw 来限制频宽等,建议你 man ipfw。
将原本的 /etc/rc.firewall 备份成 rc.firewal.old,并将它改成下列内容,请注意,这里只是范例,只供参考:
# 设定我的 IP |
存盘后就可以使用 sh rc.firewall 来执行新的规则了。如果您将规则放在 /etc/rc.firewall 中,则开机时会自动执行。
这里给一点小小的建议:
在建立一个封包过滤的防火墙时,应该尽可能阻挡一些不必要的服务。避免开放 port 1024 以下的 TCP 服务,例如只通过 SMTP 封包 (port 25) 给邮件服务器;拒绝所有 UDP 联机 (只有少部份服务如 NFS 会用到);一些只有内部才会使用的服务,如数据库等也不必对外开放。
另外,同样的防火墙限制可以使用不同的语法来展现,应该要试着让规则数量越少越好,以加快处理速度。
在更新 firewall 规则时,如果规则没有写好,而你又是以远程登入的方式修改规则,很可能会因此无法继续登入。因此建议更新规则时最好在 console 前执行,若迫不得已一定要使用远程登入,建议您执行/usr/share/examples/ipfw/change_rules.sh 这支程序来编辑规则:
#
cd /usr/share/examples/ipfw
#
sh change_rules.sh
接着会出现文书编辑软件并最动加载 /etc/rc.firewall 让你编辑,结束离开后,会询问是否要执行更新。如果执行新的规则后造成断线,它会自动加载旧的规则,让我们可以再次联机。
三 封包过滤桥接器
如果您有三台机器全部都有 public IP,而您想使用其中一台做为防火墙,在不改变另外二台机器的设定下,我们可以使具封包过滤的桥接器来架设防火墙。只要将这台桥接器放在另外二台和对外网络之间即可。
另外,当我们的内部网络有不同 class 的主机时,例如内部有 140.115.2.3 及 140.115.5.6 这二台计算机时,就无法使用传统的防火墙。如果要在这二台机器连到因特网中途中使用防火墙,我们必须使用新的方式,也可以使用这里介绍的桥接器。
我们可以使用 FreeBSD 为桥接器,利用它来做封包过滤的动作,而丝毫不影响内部的主机原本的设定。为了达到这个功能,我们必需要有二张支持 promiscuous mode 的网络卡,现在的网络卡大部份都有支持。二张网络卡当中,一张需要设定 IP,另一张不需要。至于您要将 IP 设定在哪一张卡都可以,建议是设在对外的网络卡上。
首先,我们必须在核心中加入关于桥接器的设定:
# 支援桥接器 |
如果您要让桥接器具有流量控制的功能,则可以加上之前提到的选项「options DUMMYNET」。重新编译核心后,在重开机前,我们先设定一下 /etc/rc.conf:
firewall_enable="YES" |
还有一件事要做,当在以太网络上跑 IP 协议时,事实上使用二种以太网络协议,一个是 IP,另一个是 ARP。ARP 协定是当机器要找出给定 IP 地址所对应的以太网络地址时使用的。ARP 并不是 IP 层的一部份,只是给 IP 应用在以太网络上运作。标准的防火墙规则中并未加入对于 ARP 的支持,幸运的是,高手们的在 ipfirewall 程序代码中加入了对封包过滤桥接器的支持。如果我们在 IP 地址 0.0.0.0 上建立一个特别的 UDP 规则,UDP 端口的号码将被使用来搭配被桥接封包的以太网络协议号码,如此一来,我们的桥接器就可以被设定成传递或拒绝非 IP 的协议。请在 /etc/rc.firewall 中接近文件顶端处理 lo0 的那三行之下(就是有写 Only in rare cases do you want to change these rules 的地方)加入下面一行:
${fwcmd} add allow udp from 0.0.0.0 2054 to 0.0.0.0 |
现在我们就可以重新开机了。重开机之后,先执行下列指令来启动桥接器:
如果您使用的是 FreeBSD 4.x:
#
sysctl -w net.link.ether.bridge_ipfw=1
#
sysctl -w net.link.ether.bridge=1
如果您使用的是 FreeBSD 5.x:
#
sysctl -w net.link.ether.bridge.ipfw=1
#
sysctl -w net.link.ether.bridge.enable=1
现在我们可以将机器放在内外二个网域之间了。因为我们之前在 /etc/rc.conf 中,设定防火墙完全打开,不阻挡任何封包,所以放在二个网域之间时,运作应该没有问题。我们之前只设了一张网络上的 IP,而在执行了上述的指令之后,第二张网络卡便开始运作。
下一步就是将我们启动桥接器的指令放在 /etc/rc.local 中,让系统在开机时自动执行。或者,我们可以在/etc/sysctl.conf 中加入下面二行:
# 如果您使用的是 FreeBSD 4.x |
接下来我们就可以依自己的需求在 /etc/rc.firewall 文件的最后面加上我们自己想要的防火墙规则了。以下是一个简单的设定规则,假设桥接器的 IP 是 140.115.75.137,内部有二台主机,一台提供网页服务,一台是 BBS:
us_ip=140.115.75.137 |
到这里就完成了。。。