标题：IPFW
防火墙的模式
另外，我们在设定防火墙时有二种模式，一种模式是预设拒绝所有联机，再一条一条加入允许的联机；另一种是预设接受所有联机，加入几条拒绝的规则。如果是非常强调安全性，应该是使用预设拒绝所有联机，再一条一条加入我们允许的规则。
设定防火墙
我们会将 firewall 的设定写在 /etc/rc.firewall 中，每一条设定都是以先入为主 (first match wins) 的方式来呈现，也就是先符合的规则 (rules) 为优先。所有进出的封包都会被这些规则过滤，因此我们会尽量减少规则的数量，以加速处理的速度。
在 kernel 中，关于防火墙的设定有下列几条：
# 防火墙
options IPFIREWALL
# 支援 NAT
options IPDIVERT
# 下面这一行是预设允许所有封包通过，如果没有这一行，
# 就必须在 /etc/rc.firewall 中设定封包的规则。
# 这条规则内定编号是 65535，也就是所有规则的最后一条
# 如果没有加这一条规则，内定就是拒绝所有封包，
# 只允许规则中允许的封包通过。
options IPFIREWALL_DEFAULT_TO_ACCEPT
# 这一行是让你可以在 ipfw 中设定要记录哪些封包，
# 如果没有这一行，就算设定了要留下记录也不会有作用。
options IPFIREWALL_VERBOSE
# 这一行是限制每一条规则所要记录的封包数量，
# 因为同样的规则可能有许多记录，加上这一条可以使
# 同样的记录重复数减少，以避免记录文件爆增。
options IPFIREWALL_VERBOSE_LIMIT=10
# 下面这一行是用来支援封包转向，
# 当你要使用 fwd 动作时必须要有这一项设定。
options IPFIREWALL_FORWARD
# 如果要使用 pipe 来限制频宽，必须加入下列选项以支持 dummynet。
options DUMMYNET
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] 当列出规则时，依哪一个计数器 (封包的数量、位数、记录的数量及时间) 来排序。ipfw 规则我们在过滤封包时，可以依据下列的几个封包所包含的信息来处理该封包：
接收或传送的接口，可以使用接口名称或地址。 
方向，流入或流出。 
来源或目的地的 IP 地址，也可以加上子网掩码。 
通讯协议，TCP,UDP,ICMP 等。 
TCP flags。 
IP fragment flag。 
IP options。 
ICMP 的类型。 
和封包相关的 socket User/group ID。 
使用 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 \
192.168.1.0/24 to any 80
如果没有设定 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
ipfw pipe 1 config bw 20KBytes/s
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 地址。而使用明确指定地址的方式有下列三种：
IP 地址，指定一个 IP，如 168.20.33.45。 
IP/bits，如 1.2.3.4/24，表示所有从 1.2.3.0 到 1.2.3.255 的 IP 都符合规则。 
IP:mask，由 IP 加上子网掩码，如 1.2.3.4:255.255.240.0 表示从 1.2.0.0 到 1.2.15.255 都符合。 
而在 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
ipfw add pipe 1 tcp from me to any uid 21
上列规则第一行是先建一个编号为 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。
不知道您看了这么多的规则是否觉得眼花缭乱，如果不了解 TCP/IP 的原理，彻底了解 ipfw 的设定还真不容易。没关系，我们下面将举几个简单、常用的设定，这些范例应该够平常使用了。范例我将原本的 /etc/rc.firewall 备份成 rc.firewal.old，并将它改成下列内容，请注意，这里只是范例，只供参考：
# 设定我的 IP
myip="1.2.3.4"
# 设定对外的网络卡代号
outif="vr0"
# 设定对内的网络上代号
inif="vr1"
#清除所有的规则
/sbin/ipfw -f flush
# Throw away RFC 1918 networks
$ add deny ip from 10.0.0.0/8 to any in via $
$ add deny ip from 172.16.0.0/12 to any in via $
$ add deny ip from 192.168.0.0/16 to any in via $
# 只允许内部网络对 192.168.0.1 使用 telnet 服务
/sbin/ipfw add 200 allow tcp from 192.168.0.1/24 to 192.168.0.1 telnet
# 拒绝其它人连到 port 23，并记录尝试联机的机器
/sbin/ipfw add 300 deny log tcp from any to me 23
# 拒绝任何 ICMP 封包
/sbin/ipfw add 400 deny icmp from any to any
# 下面这台机器是坏人，不让它进来，并记录下来
/sbin/ipfw add 1100 deny log all from 211.21.104.102 to any
# NAT 的设定
/sbin/ipfw add divert natd all from any to any via vr0
# 限制内部网域对外下载最大频宽为 20KBytes/s，上传最大频宽为 5KBytes/s
ipfw pipe 20 config bw 20KBytes/s
ipfw add pipe 20 ip from any to 192.168.0.1/24 out
ipfw pipe 30 config bw 5KBytes/s
ipfw add pipe 30 ip from 192.168.0.1/24 to any in
# 允许本机对任何地方联机
/sbin/ipfw add check-state
/sbin/ipfw add 2000 allow udp from $ to any keep-state
/sbin/ipfw add 2100 pass ip from $  to any
# 允许外界使用邮件服务
/sbin/ipfw add 3000 pass tcp from any to $ 25 in via $
# 不允许内部的 IP 从外部连进来
/sbin/ipfw add 1200 add deny ip from $/24 to any in via $
# 其它都拒绝，如果没有在 kernel 中设定
# IPFIREWALL_DEFAULT_TO_ACCEPT 则内定就有下列这一条
/sbin/ipfw 65535 add deny all from any to any
存盘后就可以使用 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 BRIDGE
# 防火墙设定
options IPFIREWALL
options IPFIREWALL_VERBOSE
# 我们这里不将防火墙预设为接收所有封包
#options IPFIREWALL_DEFAULT_TO_ACCEPT
如果您要让桥接器具有流量控制的功能，则可以加上之前提到的选项「options DUMMYNET」。重新编译核心后，在重开机前，我们先设定一下 /etc/rc.conf：
firewall_enable="YES"
firewall_type="open"
还有一件事要做，当在以太网络上跑 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 的地方)加入下面一行：
$ 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
net.link.ether.bridge_ipfw=1
net.link.ether.bridge=1
# 如果您使用的是 FreeBSD 5.2 以后的版本
net.link.ether.bridge.enable=1
net.link.ether.bridge.ipfw=1
接下来我们就可以依自己的需求在 /etc/rc.firewall 文件的最后面加上我们自己想要的防火墙规则了。以下是一个简单的设定规则，假设桥接器的 IP 是 140.115.75.137，内部有二台主机，一台提供网页服务，一台是 BBS：
us_ip=140.115.75.137
basrv_ip=140.115.3.4
bbs_ip=140.115.5.6
oif=fxp0
iif=fxp1
ipfw="/sbin/ipfw"
# Things that we've kept state on before get to go through in a hurry.
$ 1000 add check-state
# Throw away RFC 1918 networks
$ 1100 add deny ip from 10.0.0.0/8 to any in via $
$ 1200 add deny log ip from 172.16.0.0/12 to any in via $
$ 1300 add deny log ip from 192.68.0.0/16 to any in via $
# 允许桥接器本身所有想做的联机 (keep state if UDP)
$ 1400 add pass udp from $ to any keep-state
$ 1500 add pass ip from $ to any
# 允许内部网络任何想做的联机 (keep state if UDP)
$ 1600 add pass udp from any to any in via $ keep-state
$ 1700 add pass ip from any to any in via $
# 允许任何的 ICMP 联机
$ 1800 add pass icmp from any to any
# 不允许使用 port 888 联机
$ 2000 add deny log tcp from any to $ 888
# TCP section
# 任何地方都可以建立 TCP 联机
$ 3000 add pass tcp from any to any via $
# Pass the "quarantine" range.
$ 3100 add pass tcp from any to any 49152-65535 in via $
# Pass ident probes. It's better than waiting for them to timeout
$ 3200 add pass tcp from any to any 113 in via $
# Pass SSH.
$ 3300 add pass tcp from any to any 22 in via $
# Pass DNS. 当内部网络有名称服务器时才需要
#$ add pass tcp from any to any 53 in via $
# 只传递 SMTP 给邮件服务器
$ 3400 add pass tcp from any to $ 25 in via $
$ 3500 add pass tcp from any to $ 25 in via $
# UDP section
# Pass the "quarantine" range.
$ 4000 add pass udp from any to any 49152-65535 in via $
# Pass DNS. 当内部网络有名称服务器时才需要
#$ 4100 add pass udp from any to any 53 in via $
# 其它的都拒绝
$ 60000 add deny ip from any to any