第 31 章 防火墙

31.1. 入门

防火墙的存在, 使得过滤出入系统的数据流成为可能。 防火墙可以使用一组或多组 "规则 (rules)", 来检查出入您的网络连接的数据包, 并决定允许或阻止它们通过。 这些规则通常可以检查数据包的某个或某些特征, 这些特征包括, 但不必限于协议类型、 来源或目的主机地址, 以及来源或目的端口。

防火墙可以大幅度地改善主机或网络的安全。 它可以用来完成下面的任务:

  • 保护和隔离应用程序、 服务程序, 以及您内部网络上的机器, 不受那些来自公共的 Internet 网络上您所不希望的数据流量的干扰。

  • 限制或禁止从内部网访问公共的 Internet 上的服务。

  • 支持网络地址转换 (NAT), 它使得您的内部网络能够使用私有的 IP 地址, 并分享一条通往公共的 Internet 的连接 (使用一个 IP 地址, 或者一组公网地址)。

读完这章, 您将了解:

  • 如何正确地定义包过滤规则。

  • FreeBSD 中内建的几种防火墙之间的差异。

  • 如何使用和配置 OpenBSD 的 PF 防火墙。

  • 如何使用和配置 IPFILTER。

  • 如何使用和配置 IPFW。

阅读这章之前, 您需要:

  • 理解基本的 FreeBSD 和 Internet 概念。

31.2. 防火墙的概念

建立防火墙规则集的基本方法有两种: "明示允许 (inclusive)"型 或 "明示禁止 (exclusive)"型。 明示禁止的防火墙规则, 默认允许所有数据通过防火墙, 而这种规则集中定义的, 则是不允许通过防火墙的流量, 换言之, 与这些规则不匹配的数据, 全部是允许通过防火墙的。 明示允许的防火墙正好相反, 它只允许符合规则集中定义规则的流量通过, 而其他所有的流量都被阻止。

明示允许型防火墙能够提供对于传出流量更好的控制, 这使其更适合那些直接对 Internet 公网提供服务的系统的需要。 它也能够控制来自 Internet 公网到您的私有网络的访问类型。 所有和规则不匹配的流量都会被阻止并记录在案。 一般来说明示允许防火墙要比明示禁止防火墙更安全, 因为它们显著地减少了允许不希望的流量通过可能造成的风险。

除非特别说明, 这一章的配置和示范的规则集都是创建明示允许防火墙的。

使用了 "带状态功能的防火墙 (stateful firewall)", 可以进一步地收紧安全机制。 这种防火墙能够记录通过防火墙的连接, 进而只允许与现有连接匹配的连接, 或创建新的连接。 带状态功能的防火墙的缺点是, 在很短时间内有大量的连接请求时, 它们可能会受到拒绝服务 (DoS) 攻击。 绝大多数防火墙都提供了同时启用两种防火墙的能力, 以便为站点提供更好的保护。

31.3. 防火墙软件包

FreeBSD 的基本系统内建了三种不同的防火墙软件包。 它们是 IPFILTER (也被称作 IPF)、 IPFIREWALL (也被称作 IPFW), 以及 OpenBSD 的 PacketFilter (也被称为 PF)。 FreeBSD 也提供了两个内建的、 用于流量整形 (基本上是控制带宽占用) 的软件包: altq(4)dummynet(4)。 Dummynet 在过去一直和 IPFW 紧密集成, 而 ALTQ 则需要配合 PF 使用。 IPFILTER 的流量整形功能可以使用 IPFILTER 的 NAT 和过滤功能以及 IPFW 的 dummynet(4) 配合, 或者 使用 PF 跟 ALTQ 的组合。 IPFW, 以及 PF 都是用规则来控制是否允许数据包出入您的系统, 虽然它们采取了不同的实现方法和规则语法。

FreeBSD 包含多个内建的防火墙软件包的原因在于, 不同的人会有不同的需求和偏好。 任何一个防火墙软件包都很难说是最好的。

作者倾向于使用 IPFILTER, 因为它提供的状态式规则, 在 NAT 的环境中要简单许多, 而且它内建了 ftp 代理, 这简化了使用外部 FTP 服务时所需的配置。

由于所有的防火墙都基于检查所选定的包控制字段来实现功能, 撰写防火墙规则集时, 就必须了解 TCP/IP 是如何工作的, 以及包的控制字段在正常会话交互中的作用。 您可以在这个网站找到一份很好的解释文档: http://www.ipprimer.com/overview.cfm.

31.4. OpenBSD Packet Filter (PF) 和 ALTQ

2003 年 7 月, OpenBSD 的防火墙, 也就是常说的 PF 被成功地移植到了 FreeBSD 上, 并可以通过 FreeBSD Ports Collection 来安装了; 第一个将 PF 集成到基本系统中的版本是 2004 年 11 月发行的 FreeBSD 5.3。 PF 是一个完整的提供了大量功能的防火墙软件, 并提供了可选的 ALTQ (交错队列, Alternate Queuing) 功能。 ALTQ 提供了服务品质 (QoS) 带宽整形功能。

OpenBSD 项目非常杰出的维护着一份 PF FAQ。 就其本身而言,这一节注重于 FreeBSD 的 PF 和提供一些关于使用方面的一般常识。更详细的使用信息请参阅 PF FAQ

更多的详细信息, 可以在 FreeBSD 版本的 PF 网站上找到: http://pf4freebsd.love2party.net/

31.4.1. 使用 PF 可加载的内核模块

要加载 PF 内核模块, 可以在 /etc/rc.conf 中加入下面的设置:

pf_enable="YES"

然后使用启动脚本来加载模块:

# /etc/rc.d/pf start

需要说明的是, 如果系统中没有规则集配置文件, 则上述操作不会加载 PF 模块。 配置文件的默认位置是 /etc/pf.conf。 如果 PF 规则集在其他位置, 可以用下面的 /etc/rc.conf 配置来告诉 PF:

pf_rules="/path/to/pf.conf"

pf.conf 的例子可以在 /usr/shared/examples/pf/ 找到。

PF 模块也可以手工从命令行加载:

# kldload pf.ko

PF 的日志记录功能是由 pflog.ko 提供的, 通过在 /etc/rc.conf 中加入下面的设置:

pflog_enable="YES"

然后使用启动脚本来加载模块:

# /etc/rc.d/pflog start

如果您需要其他 PF 特性, 则需要将 PF 支持联编进内核。

31.4.2. PF 内核选项

虽然你不必亲自把对 PF 的支持编译进 FreeBSD 内核,但是有时你仍然需要这么做来使用到 PF 的某些没有被收录进可加载模块的高级特性,比如 pfsync(4) 伪设备用来发送某些改变到PF 状态表。 它能配合 carp(4) 使用 PF 建立支持故障转移的防火墙。 更多有关 CARP 的详细信息可以参阅本手册的 Common Address Redundancy Protocol (CARP, 共用地址冗余协议)

The PF kernel options can be found in /usr/src/sys/conf/NOTES and are reproduced below:

有关 PF 的内核选项可以在 /usr/src/sys/conf/NOTES 中找到, 以下也略有阐述:

device pf
device pflog
device pfsync

device pf 选项用于启用 "Packet Filter" 防火墙的支持 (pf(4))。

device pflog 启用可选的 pflog(4) 伪网络设备, 用以通过 bpf(4) 描述符来记录流量。 pflogd(8) 服务可以用来存储信息, 并把它们以日志形式记录到磁盘上。

device pfsync 选项启用可选的 pfsync(4) 支持,这是用于监视 "状态变更" 的伪网络设备。

31.4.3. 可用的 rc.conf 选项

The following rc.conf(5) statements configure PF and pflog(4) at boot:

以下 rc.conf(5) 中的语句用于启动时配置 PF 和 pflog(4)

pf_enable="YES"                 # 启用 PF (如果需要的话, 自动加载内核模块)
pf_rules="/etc/pf.conf"         # pf 使用的规则定义文件
pf_flags=""                     # 启动时传递给 pfctl 的其他选项
pflog_enable="YES"              # 启动 pflogd(8)
pflog_logfile="/var/log/pflog"  # pflogd 用于记录日志的文件名
pflog_flags=""                  # 启动时传递给 pflogd 的其他选项

如果您的防火墙后面有一个 LAN, 而且需要通过它来转发 LAN 上的包, 或进行 NAT, 还需要同时启用下述选项:

gateway_enable="YES"            # 启用为 LAN 网关

31.4.4. 建立过滤规则

PF 会从 pf.conf(5) (默认为 /etc/pf.conf) 文件中读取配置规则, 并根据那里的规则修改、丢弃或让数据包通过。 默认安装的 FreeBSD 已经提供了一些简单的例子放在 /usr/shared/examples/pf/ 目录下。 请参阅 PF FAQ 获取完整的 PF 规则信息。

在浏览 PF FAQ 时, 请时刻注意不同版本的 FreeBSD 可能会使用不同版本的 PF。 目前, FreeBSD 8.X 和之前的系统使用的是与 OpenBSD 4.1 相同版本的 PF。 FreeBSD 9.X 和之后的系统使用的是与 OpenBSD 4.5 相同版本的 PF。

FreeBSD packet filter 邮件列表 是一个提有关配置使用 PF 防火墙问题的好地方。请在提问之前查阅邮件列表的归档!

31.4.5. 使用 PF

使用 pfctl(8) 可以控制 PF。 以下是一些实用的命令 (请查阅 pfctl(8) 获得全部可用的选项):

命令作用

pfctl -e

启用 PF

pfctl -d

禁用 PF

pfctl -F all -f /etc/pf.conf

清除所有规则 (nat, filter, state, table, 等等。) 并读取 /etc/pf.conf

pfctl -s [ rules | nat | state ]

列出 filter 规则, nat 规则, 或状态表

pfctl -vnf /etc/pf.conf

检查 /etc/pf.conf 中的错误,但不加载相关的规则

31.4.6. 启用 ALTQ

ALTQ 只有在作为编译选项加入到 FreeBSD 内核时才能使用。ALTQ 目前还不是所有的可用网卡驱动都能够支持的。 请参见 altq(4) 联机手册了解您正使用的 FreeBSD 版本中的驱动支持情况。

下面这些选项将启用 ALTQ 以及一些附加的功能:

options         ALTQ
options         ALTQ_CBQ        # 基于分类的排列 (CBQ)
options         ALTQ_RED        # 随机先期检测 (RED)
options         ALTQ_RIO        # 对进入和发出的包进行 RED
options         ALTQ_HFSC       # 带等级的包调度器 (HFSC)
options         ALTQ_PRIQ       # 按优先级的排列 (PRIQ)
options         ALTQ_NOPCC      # 在联编 SMP 内核时必须使用,禁止读时钟

options ALTQ 将启用 ALTQ 框架的支持。

options ALTQ_CBQ 用于启用 基于分类的队列 (CBQ) 支持。 CBQ 允许您将连接分成不同的类别, 或者说, 队列, 以便在规则中为它们指定不同的优先级。

options ALTQ_RED 将启用 随机预检测 (RED)。 RED 是一种用于防止网络拥塞的技术。 RED 度量队列的长度, 并将其与队列的最大和最小长度阈值进行比较。 如果队列过长, 则新的包将被丢弃。 如名所示, RED 从不同的连接中随机地丢弃数据包。

options ALTQ_RIO 将启用 出入的随机预检测

options ALTQ_HFSC 启用 层次式公平服务平滑包调度器。 要了解关于 HFSC 进一步的信息, 请参见 http://www-2.cs.cmu.edu/~hzhang/HFSC/main.html

options ALTQ_PRIQ 启用 优先队列 (PRIQ)。 PRIQ 首先允许高优先级队列中的包通过。

options ALTQ_NOPCC 启用 ALTQ 的 SMP 支持。 如果是 SMP 系统, 则必须使用它。

31.5. IPFILTER (IPF) 防火墙

IPFILTER 的作者是 Darren Reed。 IPFILTER 是独立于操作系统的: 它是一个开放源代码的应用, 并且已经被移植到了 FreeBSD、 NetBSD、 OpenBSD、 SunOS、 HP/UX, 以及 Solaris 操作系统上。 IPFILTER 的支持和维护都相当活跃, 并且有规律地发布更新版本。

IPFILTER 提供了内核模式的防火墙和 NAT 机制, 这些机制可以通过用户模式运行的接口程序进行监视和控制。 防火墙规则可以使用 ipf(8) 工具来动态地设置和删除。 NAT 规则可以通过 ipnat(1) 工具来维护。 ipfstat(8) 工具则可以用来显示 IPFILTER 内核部分的统计数据。 最后, 使用 ipmon(8) 程序可以把 IPFILTER 的动作记录到系统日志文件中。

IPF 最初是使用一组 "以最后匹配的规则为准" 的策略来实现的, 这种方式只能支持无状态的规则。 随着时代的进步, IPF 被逐渐增强, 并加入了 "quick" 选项, 以及支持状态的 "keep state" 选项, 这使得规则处理逻辑变得更富有现代气息。 IPF 的官方文档只介绍了传统的规则编写方法和文件处理逻辑。 新增的功能只是作为一些附加的选项出现, 如果能完全理解这些功能, 则对于建立更安全的防火墙就很有好处。

这一节中主要是针对 "quick" 选项, 以及支持状态的 "keep state" 选项的介绍。 这是明示允许防火墙规则集最基本的编写要素。

要获得关于传统规则处理方式的详细信息, 请参考: http://www.obfuscation.org/ipf/ipf-howto.html#TOC_1 以及 http://coombs.anu.edu.au/~avalon/ip-filter.html

IPF FAQ 可以在 http://www.phildev.net/ipf/index.html 找到。

除此之外, 您还可以在 http://marc.theaimsgroup.com/?l=ipfilter 找到开放源代码的 IPFilter 的邮件列表存档, 并进行搜索。

31.5.1. 启用 IPF

IPF 作为 FreeBSD 基本安装的一部分, 以一个独立的内核模块的形式提供。 如果在 rc.conf 中配置了 ipfilter_enable="YES", 系统就会自动地动态加载 IPF 内核模块。 这个内核模块在创建时启用了日志支持, 并加入了 default pass all 选项。 如果只是需要把默认的规则设置为 block all 的话, 就不需要把 IPF 编译到内核中。 简单地通过把 block all 这条规则加入自己的规则集来达到同样的目的。

31.5.2. 内核选项

下面这些 FreeBSD 内核编译选项并不是启用 IPF 所必需的。 这里只是作为背景知识来加以阐述。 如果将 IPF 编入了内核, 则对应的内核模块将不被使用。

关于 IPF 选项语句的内核编译配置的例子, 可以在内核源代码中的 /usr/src/sys/conf/NOTES 找到。 此处列举如下:

options IPFILTER
options IPFILTER_LOG
options IPFILTER_DEFAULT_BLOCK

options IPFILTER 用于启用 "IPFILTER" 防火墙的支持。

options IPFILTER_LOG 用于启用 IPF 的日志支持, 所有匹配了包含 log 的规则的包, 都会被记录到 ipl 这个包记录伪-设备中。

options IPFILTER_DEFAULT_BLOCK 将改变防火墙的默认动作, 进而, 所有不匹配防火墙的 pass 规则的包都会被阻止。

这些选项只有在您重新编译并安装了上述配置的内核之后才会生效。

31.5.3. 可用的 rc.conf 选项

要在启动时激活 IPF, 需要在 /etc/rc.conf 中增加下面的设置:

ipfilter_enable="YES"             # 启动 ipf 防火墙
ipfilter_rules="/etc/ipf.rules"   # 将被加载的规则定义, 这是一个文本文件
ipmon_enable="YES"                # 启动 IP 监视日志
ipmon_flags="-Ds"                 # D = 作为服务程序启动
                                  # s = 使用 syslog 记录
                                  # v = 记录 tcp 窗口大小、 ack 和顺序号(seq)
                                  # n = 将 IP 和端口映射为名字

如果在防火墙后面有使用了保留的私有 IP 地址范围的 LAN, 还需要增加下面的一些选项来启用 NAT 功能:

gateway_enable="YES"              # 启用作为 LAN 网关的功能
ipnat_enable="YES"                # 启动 ipnat 功能
ipnat_rules="/etc/ipnat.rules"    # 用于 ipnat 的规则定义文件

31.5.4. IPF

ipf(8) 命令可以用来加载您自己的规则文件。 一般情况下, 您可以建立一个包括您自定义的规则的文件, 并使用这个命令来替换掉正在运行的防火墙中的内部规则:

# ipf -Fa -f /etc/ipf.rules

-Fa 表示清除所有的内部规则表。

-f 用于指定将要被读取的规则定义文件。

这个功能使得您能够修改自定义的规则文件, 通过运行上面的 IPF 命令, 可以将正在运行的防火墙刷新为使用全新的规则集, 而不需要重新启动系统。 这对于测试新的规则来说就很方便, 因为您可以任意执行上面的命令。

请参考 ipf(8) 联机手册以了解这个命令提供的其它选项。

ipf(8) 命令假定规则文件是一个标准的文本文件。 它不能处理使用符号代换的脚本。

也确实有办法利用脚本的非常强大的符号替换能力来构建 IPF 规则。 要了解进一步的细节, 请参考 构建采用符号替换的规则脚本

31.5.5. IPFSTAT

默认情况下, ipfstat(8) 会获取并显示所有的累积统计, 这些统计是防火墙启动以来用户定义的规则匹配的出入流量, 您可以通过使用 ipf -Z 命令来将这些计数器清零。

请参见 ipfstat(8) 联机手册以了解进一步的细节。

默认的 ipfstat(8) 命令输出类似于下面的样子:

input packets: blocked 99286 passed 1255609 nomatch 14686 counted 0
 output packets: blocked 4200 passed 1284345 nomatch 14687 counted 0
 input packets logged: blocked 99286 passed 0
 output packets logged: blocked 0 passed 0
 packets logged: input 0 output 0
 log failures: input 3898 output 0
 fragment state(in): kept 0 lost 0
 fragment state(out): kept 0 lost 0
 packet state(in): kept 169364 lost 0
 packet state(out): kept 431395 lost 0
 ICMP replies: 0 TCP RSTs sent: 0
 Result cache hits(in): 1215208 (out): 1098963
 IN Pullups succeeded: 2 failed: 0
 OUT Pullups succeeded: 0 failed: 0
 Fastroute successes: 0 failures: 0
 TCP cksum fails(in): 0 (out): 0
 Packet log flags set: (0)

如果使用了 -i (进入流量) 或者 -o (输出流量), 这个命令就只获取并显示内核中所安装的对应过滤器规则的统计数据。

ipfstat -in 以规则号的形式显示进入的内部规则表。

ipfstat -on 以规则号的形式显示流出的内部规则表。

输出和下面的类似:

@1 pass out on xl0 from any to any
@2 block out on dc0 from any to any
@3 pass out quick on dc0 proto tcp/udp from any to any keep state

ipfstat -ih 显示内部规则表中的进入流量, 每一个匹配规则前面会同时显示匹配的次数。

ipfstat -oh 显示内部规则表中的流出流量, 每一个匹配规则前面会同时显示匹配的次数。

输出和下面的类似:

2451423 pass out on xl0 from any to any
354727 block out on dc0 from any to any
430918 pass out quick on dc0 proto tcp/udp from any to any keep state

ipfstat 命令的一个重要的功能可以通过指定 -t 参数来使用, 它会以类似 top(1) 的显示 FreeBSD 正运行的进程表的方式来显示统计数据。 当您的防火墙正在受到攻击的时候, 这个功能让您得以识别、 试验, 并查看攻击的数据包。 这个选项提还提供了实时选择希望监视的目的或源 IP、 端口或协议的能力。 请参见 ipfstat(8) 联机手册以了解详细信息。

31.5.6. IPMON

为了使 ipmon 能够正确工作, 必须打开 IPFILTER_LOG 这个内核选项。 这个命令提供了两种不同的使用模式。 内建模式是默认的模式, 如果您不指定 -D 参数, 就会采用这种模式。

服务模式是持续地通过系统日志来记录的工作模式, 这样, 您就可以通过查看日志来了解过去曾经发生过的事情。 这种模式是 FreeBSD 和 IPFILTER 配合工作的模式。 由于在 FreeBSD 中提供了一个内建的系统日志自动轮转功能, 因此, 使用 syslogd(8) 比默认的将日志信息记录到一个普通文件要好。 在默认的 rc.conf 文件中, ipmon_flags 语句会指定 -Ds 标志:

ipmon_flags="-Ds"                 # D = 作为服务程序启动
                  # s = 使用 syslog 记录
                  # v = 记录 tcp 窗口大小、 ack 和顺序号(seq)
                  # n = 将 IP 和端口映射为名字

记录日志的好处是很明显的。 它提供了在事后重新审查相关信息, 例如哪些包被丢弃, 以及这些包的来源地址等等。 这将为查找攻击者提供非常有用的第一手资料。

即使启用了日志机制, IPF 仍然不会对其规则进行任何日志记录工作。 防火墙管理员可以决定规则集中的哪些应记录日志, 并在这些规则上加入 log 关键字。 一般来说, 只应记录拒绝性的规则。

作为惯例, 通常会有一条默认的、拒绝所有网络流量的规则, 并指定 log 关键字, 作为您的规则集的最后一条。 这样就能够看到所有没有匹配任何规则的数据包了。

31.5.7. IPMON 的日志

Syslogd 使用特殊的方法对日志数据进行分类。 它使用称为 "facility" 和 "level" 的组。 以 -Ds 模式运行的 IPMON 采用 local0 作为默认的 "facility" 名。 如果需要, 可以用下列 levels 来进一步区分数据:

LOG_INFO - 使用 "log" 关键字指定的通过或阻止动作
LOG_NOTICE - 同时记录通过的那些数据包
LOG_WARNING - 同时记录阻止的数据包
LOG_ERR - 进一步记录含不完整的包头的数据包

要设置 IPFILTER 来将所有的数据记录到 /var/log/ipfilter.log, 需要首先建立这个文件。 下面的命令可以完成这个工作:

# touch /var/log/ipfilter.log

syslogd(8) 功能可以通过在 /etc/syslog.conf 文件中的语句来定义。 syslog.conf 提供了相当多的用以控制 syslog 如何处理类似 IPF 这样的用用程序所产生的系统消息的方法。

您需要将下列语句加到 /etc/syslog.conf

local0.* /var/log/ipfilter.log

这里的 local0.* 表示把所有的相关日志信息写到指定的文件中。

要让 /etc/syslog.conf 中的修改立即生效, 可以重新启动计算机, 或者通过执行 /etc/rc.d/syslogd reload 来让它重新读取 /etc/syslog.conf

不要忘了修改 /etc/newsyslog.conf 来让刚创建的日志进行轮转。

31.5.8. 记录消息的格式

ipmon 生成的消息由空格分隔的数据字段组成。 所有的消息都包含的字段是:

  1. 接到数据包的日期。

  2. 接到数据包的时间。 其格式为 HH:MM:SS.F, 分别是小时、 分钟、 秒, 以及分秒 (这个数字可能有许多位)。

  3. 处理数据包的网络接口名字, 例如 dc0

  4. 组和规则的编号, 例如 @0:17

可以通过 ipfstat -in 来查看这些信息。

  1. 动作: p 表示通过, b 表示阻止, S 表示包头不全, n 表示没有匹配任何规则, L 表示 log 规则。 显示这些标志的顺序是: S, p, b, n, L。 大写的 P 或 B 表示记录包的原因是某个全局的日志配置, 而不是某个特定的规则。

  2. 地址。 这实际上包括三部分: 源地址和端口 (以逗号分开), 一个 → 符号, 以及目的地址和端口, 例如: 209.53.17.22,80 → 198.73.220.17,1722

  3. PR, 后跟协议名称或编号, 例如: PR tcp

  4. len, 后跟包头的长度, 以及包的总长度, 例如: len 20 40

对于 TCP 包, 则还会包括一个附加的字段, 由一个连字号开始, 之后是表示所设置的标志的一个字母。 请参见 ipf(5) 联机手册, 以了解这些字母所对应的标志。

对于 ICMP 包, 则在最后会有两个字段。 前一个总是 "ICMP", 而后一个则是 ICMP 消息和子消息的类型, 中间以斜线分靠, 例如 ICMP 3/3 表示端口不可达消息。

31.5.9. 构建采用符号替换的规则脚本

一些有经验的 IPF 会创建包含规则的文件, 并把它编写成能够与符号替换脚本兼容的方式。 这样做最大的好处是能够在修改时只修改符号名字所代表的值, 而在脚本执行时直接替换掉所有的名符。 作为脚本, 可以使用符号替换来把那些经常使用的值直接用于多个规则。 下面将给出一个例子。

这个脚本所使用的语法与 sh(1)csh(1), 以及 tcsh(1) 脚本。

符号替换的前缀字段是美元符号: $

符号字段不使用 $ 前缀。

希望替换符号字段的值, 必须使用双引号 (") 括起来。

您的规则文件的开头类似这样:

############# IPF 规则脚本的开头 ########################
oif="dc0"            # 外网接口的名字
odns="192.0.2.11"    # ISP 的 DNS 服务器 IP 地址
myip="192.0.2.7"     # 来自 ISP 的静态 IP 地址
ks="keep state"
fks="flags S keep state"

# 可以使用这个脚本来建立 /etc/ipf.rules 文件,
# 也可以 "直接地" 运行它。
#
# 请删除两个注释号之一。
#
# 1) 保留下面一行, 则创建 /etc/ipf.rules:
#cat > /etc/ipf.rules << EOF
#
# 2) 保留下面一行, 则 "直接地" 运行脚本:
/sbin/ipf -Fa -f - << EOF

# 允许发出到我的 ISP 的域名服务器的访问
pass out quick on $oif proto tcp from any to $odns port = 53 $fks
pass out quick on $oif proto udp from any to $odns port = 53 $ks

# 允许发出未加密的 www 访问请求
pass out quick on $oif proto tcp from $myip to any port = 80 $fks

# 允许发出使用 TLS SSL 加密的 https www 访问请求
pass out quick on $oif proto tcp from $myip to any port = 443 $fks
EOF
################## IPF 规则脚本的结束 ########################

这就是所需的全部内容。 这个规则本身并不重要, 它们主要是用于体现如何使用符号代换字段, 以及如何完成值的替换。 如果上面的例子的名字是 /etc/ipf.rules.script, 就可以通过输入下面的命令来重新加载规则:

# sh /etc/ipf.rules.script

在规则文件中嵌入符号有一个问题: IPF 无法识别符号替换, 因此它不能直接地读取这样的脚本。

这个脚本可以使用下面两种方法之一来使用:

  • 去掉 cat 之前的注释, 并注释掉 /sbin/ipf 开头的那一行。 像其他配置一样, 将 ipfilter_enable="YES" 放到 /etc/rc.conf 文件中, 并在此后立刻执行脚本, 以创建或更新 /etc/ipf.rules

  • 通过把 ipfilter_enable="NO" (这是默认值) 加到 /etc/rc.conf 中, 来禁止系统启动脚本开启 IPFILTER。

    /usr/local/etc/rc.d/ 启动目录中增加一个类似下面的脚本。 应该给它起一个显而易见的名字, 例如 ipf.loadrules.sh。 请注意, .sh 扩展名是必需的。

    #!/bin/sh
    sh /etc/ipf.rules.script

    脚本文件必须设置为属于 root, 并且属主可读、 可写、 可执行。

    # chmod 700 /usr/local/etc/rc.d/ipf.loadrules.sh

这样, 在系统启动时, 就会自动加载您的 IPF 规则了。

31.5.10. IPF 规则集

规则集是指一组编写好的依据包的值决策允许通过或阻止 IPF 规则。 包的双向交换组成了一个会话交互。 防火墙规则集会作用于来自于 Internet 公网的包以及由系统发出来回应这些包的数据包。 每一个 TCP/IP 服务 (例如 telnet, www, 邮件等等) 都由协议预先定义了其特权 (监听) 端口。 发到特定服务的包会从源地址使用非特权 (高编号) 端口发出, 并发到特定服务在目的地址的对应端口。 所有这些参数 (例如: 端口和地址) 都是可以为防火墙规则所利用的, 判别是否允许服务通过的标准。

IPF 最初被写成使用一组称作 "以最后匹配的规则为准" 的处理逻辑, 且只能处理无状态的规则。 随着时代的发展, IPF 进行了改进, 并提供了 "quick" 选项, 以及一个有状态的 "keep state" 选项。 后者使处理逻辑迅速地跟上了时代的步伐。

这一节中提供的一些指导, 是基于使用包含 "quick" 选项和有状态的 "keep state" 选项来进行阐述的。 这些是编写明示允许防火墙规则集的基本要素。

当对防火墙规则进行操作时, 应 谨慎行事。 某些配置可能会 将您反锁在 服务器外面。 保险起见, 您可以考虑在第一次进行防火墙配置时在本地控制台上, 而不是远程, 如通过 ssh 来进行。

31.5.11. 规则语法

这里给出的规则语法已经简化到只处理那些新式的带状态规则, 并且都是 "第一个匹配的规则获胜" 逻辑的。 要了解完整的传统规则语法描述, 请参见 ipf(8) 联机手册。

# 字符开头的内容会被认为是注释。 这些注释可以出现在一行规则的末尾, 或者独占一行。 空行会被忽略。

规则由关键字组成。 这些关键字必须以一定的顺序, 从左到右出现在一行上。 接下来的文字中关键字将使用粗体表示。 某些关键字可能提供了子选项, 这些子选项本身可能也是关键字, 而且可能会提供更多的子选项。 下面的文字中, 每种语法都使用粗体的小节标题呈现, 并介绍了其上下文。

ACTION IN-OUT OPTIONS SELECTION STATEFUL PROTO SRC_ADDR,DST_ADDR OBJECT PORT_NUM TCP_FLAG STATEFUL

ACTION = block | pass

IN-OUT = in | out

OPTIONS = log | quick | on 网络接口的名字

SELECTION = proto 协议名称 | 源/目的 IP | port = 端口号 | flags 标志值

PROTO = tcp/udp | udp | tcp | icmp

SRC_ADD,DST_ADDR = all | from 对象 to 对象

OBJECT = IP地址 | any

PORT_NUM = port 端口号

TCP_FLAG = S

STATEFUL = keep state

31.5.11.1. ACTION (动作)

动作对表示匹配规则的包应采取什么动作。 每一个规则 必须 包含一个动作。 可以使用下面两种动作之一:

block 表示如果规则与包匹配, 则丢弃包。

pass 表示如果规则与包匹配, 则允许包通过防火墙。

31.5.11.2. IN-OUT

每个过滤器规则都必须明确地指定是流入还是流出的规则。 下一个关键字必须要么是 in, 要么是 out, 否则将无法通过语法检查。

in 表示规则应被应用于刚刚从 Internet 公网上收到的数据包。

out 表示规则应被应用于即将发出到 Internet 的数据包。

31.5.11.3. OPTIONS

这些选项必须按下面指定的顺序出现。

log 表示包头应被写入到 ipl 日志 (如前面 LOGGING 小节所介绍的那样), 如果它与规则匹配的话。

quick 表示如果给出的参数与包匹配, 则以这个规则为准, 这使得能够 "短路" 掉后面的规则。 这个选项对于使用新式的处理逻辑是必需的。

on 表示将网络接口的名称作为筛选参数的一部分。 接口的名字会在 ifconfig(8) 的输出中显示。 使用这个选项, 则规则只会应用到某一个网络接口上的出入数据包上。 要配置新式的处理逻辑, 必须使用这个选项。

当记录包时, 包的头会被写入到 IPL 包日志伪设备中。 紧跟 log 关键字, 可以使用下面几个修饰符 (按照下列顺序):

body 表示应同时记录包的前 128 字节的内容。

first 如果 log 关键字和 keep state 选项同时使用, 则这个选项只在第一个包上触发, 这样就不用记录每一个 "keep state" 包信息了。

31.5.11.4. SELECTION

这一节所介绍的关键字可以用于所检察的包的属性。 有一个关键字主题, 以及一组子选项关键字, 您必须从他们中选择一个。 以下是一些通用的属性, 它们必须按下面的顺序使用:

31.5.11.5. PROTO

proto 是一个主题关键字, 它必须与某个相关的子选项关键字配合使用。 这个值的作用是匹配某个特定的协议。 要使用新式的规则处理逻辑, 就必须使用这个选项。

tcp/udp | udp | tcp | icmp 或其他在 /etc/protocols 中定义的协议。 特殊的协议关键字 tcp/udp 可以用于匹配 TCP 或 UDP 包, 引入这个关键字的作用是是避免大量的重复规则的麻烦。

31.5.11.6. SRC_ADDR/DST_ADDR

使用 all 关键词, 基本上相当于 "from any to any" 在没有配合其他关键字的情形。

from src to dstfromto 关键字主要是用来匹配 IP 地址。 所有的规则都必须 同时 给出源和目的两个参数。 any 是一个可以用于匹配任意 IP 地址的特殊关键字。 例如, 您可以使用 from any to anyfrom 0.0.0.0/0 to anyfrom any to 0.0.0.0/0from 0.0.0.0 to any 以及 from any to 0.0.0.0

如果无法使用子网掩码来表示 IP 的话, 表达地址就会很麻烦。 使用 net-mgmt/ipcalc port 可以帮助进行计算。 请参见下面的网页了解如何撰写长度掩码: http://jodies.de/ipcalc

31.5.11.7. PORT

如果为源或目的指定了匹配端口, 规则就只能应用于 TCP 和 UDP 包了。 当编写端口比较规则时, 可以指定 /etc/services 中所定义的名字, 也可以直接用端口号来指定。 如果端口号出现在源对象一侧, 则被认为是源端口号; 反之, 则被认为是目的端口号。 要使用新式的规则处理逻辑, 就必须与 to 对象配合使用这个选项。 使用的例子: from any to any port = 80

对单个端口的比较可以多种方式进行, 并可使用不同的比较算符。 此外, 还可以指定端口的范围。

port "=" | "!=" | "<" | ">" | "⇐" | ">=" | "eq" | "ne" | "lt" | "gt" | "le" | "ge".

要指定端口范围, 可以使用 "<>" | "><"。

在源和目的匹配参数之后, 需要使用下面两个参数, 才能够使用新式的规则处理逻辑。

31.5.11.8. TCP_FLAG

标志只对 TCP 过滤有用。 这些字母用来表达 TCP 包头的标志。

新式的规则处理逻辑使用 flags S 参数来识别 tcp 会话开始的请求。

31.5.11.9. STATEFUL

keep state 表示如果有一个包与规则匹配, 则其筛选参数应激活有状态的过滤机制。

如果使用新式的处理逻辑, 则这个选项是必需的。

31.5.12. 有状态过滤

有状态过滤将网络流量当作一种双向的包交换来处理。 如果激活它, keep-state 会动态地为每一个相关的包在双向会话交互过程中产生内部规则。 它能够确认发起者和包的目的地之间的会话是有效的双向包交换过程的一部分。 如果包与这些规则不符, 则将自动地拒绝。

状态保持也使得 ICMP 包能够与 TCP 或 UDP 会话相关。 因此, 如果您在浏览网站时收到允许的状态保持规则匹配的 ICMP 类型 3 代码 4 响应, 则这些响应会被自动地允许进入。 所有 IPF 能够处理的包, 都可以作为某种活跃会话的一部分, 即使它是另一种协议的, 也会被允许进入。

所发生的事情是:

将要通过连入 Internet 公网的网络接口发出的包, 首先会经过动态状态表的检查。 如果包与会话中预期的下一个包匹配, 防火墙就会允许包通过, 并更新状态表中的会话的交互流信息。 不属于活跃会话的包, 则简单地交给输出规则集去检查。

发到连入 Internet 公网接口的包, 也会先经过动态状态表的检查。 如果包与会话中预期的下一个包匹配, 防火墙就会允许包通过, 并更新状态表中的会话的交互流信息。 不属于活跃会话的包, 则简单地交给输入规则集去检查。

当会话结束时, 对应的项会在动态状态表中删除。

有状态过滤使得您能够集中于阻止/允许新的会话。 一旦新会话被允许通过, 则所有后续的包就都被自动地允许通过, 而伪造的包则被自动地拒绝。 如果新的会话被阻止, 则后续的包也都不会被允许通过。 有状态过滤从技术角度而言, 在阻止目前攻击者常用的洪水式攻击来说, 具有更好的抗御能力。

31.5.13. 明示允许规则集的例子

下面的规则集是如何编写非常安全的明示允许防火墙规则集的一个范例。 明示允许防火墙只让允许的服务 pass (通过), 而所有其他的访问都会被默认地拒绝。 期望用来保护其他机器的防火墙, 通常也叫做 "网络防火墙", 应使用至少两个网络接口, 并且通常只有一个接入到受信的一端 (LAN), 而另一块则接入不受信的一端 (Internet 公网)。 另外, 防火墙也可以配置为只保护它所运行的那个系统 - 这种类型称作 "主机防火墙", 通常在接入不受信网络的服务器上使用。

包括 FreeBSD 在内的所有类 UNIX® 系统通常都会使用 lo0 和 IP 地址 127.0.0.1 用于操作系统中内部的通讯。 防火墙规则必须允许这些包无阻碍地通过。

接入 Internet 公网的网络接口, 是放置规则并允许将访问请求发到 Internet 以及接收响应的地方。 这有可能是用户模式的 PPP tun0 接口, 如果您的网卡同 DSL 或电缆调制解调器相联的话。

如果有网卡是直接接入私有网段的, 这些网络接口就可能需要配置允许来自这些 LAN 的包在彼此之间, 以及到外界 (Internet) 上的对应的通过规则。

一般说来, 规则应被组织为三个主要的小节: 所有允许自由通过的接口规则, 发到公网接口的规则, 以及进入公网接口的规则。

每一个公网接口规则中, 经常会匹配到的规则应该放置在尽可能靠前的位置。 而最后一个规则应该是阻止包通过, 并记录它们。

下面防火墙规则集中, Outbound 部分是一些使用 pass 的规则, 这些规则指定了允许访问的公网 Internet 服务, 并且指定了 quickonprotoport, 以及 keep state 这些选项。 proto tcp 规则还指定了 flag 这个选项, 这样会话的第一个包将出发状态机制。

接收部分则首先阻止所有不希望的包, 这样做有两个不同的原因。 其一是恶意的包可能和某些允许的流量规则存在部分匹配, 而我们希望阻止, 而不是让这些包仅仅与 allow 规则部分匹配就允许它们进入。 其二是, 已经确信要阻止的包被拒绝这件事, 往往并不是我们需要关注的, 因此只要简单地予以阻止即可。 防火墙规则集中的每个部分的最后一条规则都是阻止并记录包, 这有助于为逮捕攻击者留下法律所要求的证据。

另外一个需要注意的事情是确保系统对不希望的数据包不做回应。 无效的包应被丢弃和消失。 这样, 攻击者便无法知道包是否到达了您的系统。 攻击者对系统了解的越少, 攻陷系统所需的时间也就越多。 包含 log first 选项的规则只会记录它们第一次被触发时的包, 在例子中这个选项被用于记录 nmap OS 指纹探测 规则。 security/nmap 是攻击者常用的一种用于探测目标系统所用操作系统的工具。

如果您看到了 log first 规则的日志, 就应该用 ipfstat -hio 命令来看看那个规则被匹配的次数。 如果数目较大, 则表示系统正在受到洪水式攻击。

如果记录的包的端口号并不是您所知道的, 可以在 /etc/serviceshttp://en.wikipedia.org/wiki/List_of_TCP_and_UDP_port_numbers 了解端口号通常的用途。

参考下面的网页, 了解木马使用的端口: http://www.sans.org/security-resources/idfaq/oddports.php

下面是我在自己的系统中使用的完整的, 非常安全的 明示允许 防火墙规则集。 直接使用这个规则集不会给您造成问题, 您所要做的只是注释掉那些您不需要 pass(允许通过) 的服务。

如果在日志中发现了希望 阻止 的记录, 只需在 inbound 小节中增加一条阻止规则集可。

您必须将每一个规则中的 dc0 替换为您系统上接入 Internet 的网络接口名称, 例如, 用户环境下的 PPP 应该是 tun0

/etc/ipf.rules 中加入下面的内容:

#################################################################
# No restrictions on Inside LAN Interface for private network
# Not needed unless you have LAN
#################################################################

#pass out quick on xl0 all
#pass in quick on xl0 all

#################################################################
# No restrictions on Loopback Interface
#################################################################
pass in quick on lo0 all
pass out quick on lo0 all

#################################################################
# Interface facing Public Internet (Outbound Section)
# Match session start requests originating from behind the
# firewall on the private network
# or from this gateway server destined for the public Internet.
#################################################################

# Allow out access to my ISP's Domain name server.
# xxx must be the IP address of your ISP's DNS.
# Dup these lines if your ISP has more than one DNS server
# Get the IP addresses from /etc/resolv.conf file
pass out quick on dc0 proto tcp from any to xxx port = 53 flags S keep state
pass out quick on dc0 proto udp from any to xxx port = 53 keep state

# Allow out access to my ISP's DHCP server for cable or DSL networks.
# This rule is not needed for 'user ppp' type connection to the
# public Internet, so you can delete this whole group.
# Use the following rule and check log for IP address.
# Then put IP address in commented out rule & delete first rule
pass out log quick on dc0 proto udp from any to any port = 67 keep state
#pass out quick on dc0 proto udp from any to z.z.z.z port = 67 keep state

# Allow out non-secure standard www function
pass out quick on dc0 proto tcp from any to any port = 80 flags S keep state

# Allow out secure www function https over TLS SSL
pass out quick on dc0 proto tcp from any to any port = 443 flags S keep state

# Allow out send & get email function
pass out quick on dc0 proto tcp from any to any port = 110 flags S keep state
pass out quick on dc0 proto tcp from any to any port = 25 flags S keep state

# Allow out Time
pass out quick on dc0 proto tcp from any to any port = 37 flags S keep state

# Allow out nntp news
pass out quick on dc0 proto tcp from any to any port = 119 flags S keep state

# Allow out gateway & LAN users' non-secure FTP ( both passive & active modes)
# This function uses the IPNAT built in FTP proxy function coded in
# the nat rules file to make this single rule function correctly.
# If you want to use the pkg_add command to install application packages
# on your gateway system you need this rule.
pass out quick on dc0 proto tcp from any to any port = 21 flags S keep state

# Allow out ssh/sftp/scp (telnet/rlogin/FTP replacements)
# This function is using SSH (secure shell)
pass out quick on dc0 proto tcp from any to any port = 22 flags S keep state

# Allow out insecure Telnet
pass out quick on dc0 proto tcp from any to any port = 23 flags S keep state

# Allow out FreeBSD CVSup
pass out quick on dc0 proto tcp from any to any port = 5999 flags S keep state

# Allow out ping to public Internet
pass out quick on dc0 proto icmp from any to any icmp-type 8 keep state

# Allow out whois from LAN to public Internet
pass out quick on dc0 proto tcp from any to any port = 43 flags S keep state

# Block and log only the first occurrence of everything
# else that's trying to get out.
# This rule implements the default block
block out log first quick on dc0 all

#################################################################
# Interface facing Public Internet (Inbound Section)
# Match packets originating from the public Internet
# destined for this gateway server or the private network.
#################################################################

# Block all inbound traffic from non-routable or reserved address spaces
block in quick on dc0 from 192.168.0.0/16 to any    #RFC 1918 private IP
block in quick on dc0 from 172.16.0.0/12 to any     #RFC 1918 private IP
block in quick on dc0 from 10.0.0.0/8 to any        #RFC 1918 private IP
block in quick on dc0 from 127.0.0.0/8 to any       #loopback
block in quick on dc0 from 0.0.0.0/8 to any         #loopback
block in quick on dc0 from 169.254.0.0/16 to any    #DHCP auto-config
block in quick on dc0 from 192.0.2.0/24 to any      #reserved for docs
block in quick on dc0 from 204.152.64.0/23 to any   #Sun cluster interconnect
block in quick on dc0 from 224.0.0.0/3 to any       #Class D & E multicast

##### Block a bunch of different nasty things. ############
# That I do not want to see in the log

# Block frags
block in quick on dc0 all with frags

# Block short tcp packets
block in quick on dc0 proto tcp all with short

# block source routed packets
block in quick on dc0 all with opt lsrr
block in quick on dc0 all with opt ssrr

# Block nmap OS fingerprint attempts
# Log first occurrence of these so I can get their IP address
block in log first quick on dc0 proto tcp from any to any flags FUP

# Block anything with special options
block in quick on dc0 all with ipopts

# Block public pings
block in quick on dc0 proto icmp all icmp-type 8

# Block ident
block in quick on dc0 proto tcp from any to any port = 113

# Block all Netbios service. 137=name, 138=datagram, 139=session
# Netbios is MS/Windows sharing services.
# Block MS/Windows hosts2 name server requests 81
block in log first quick on dc0 proto tcp/udp from any to any port = 137
block in log first quick on dc0 proto tcp/udp from any to any port = 138
block in log first quick on dc0 proto tcp/udp from any to any port = 139
block in log first quick on dc0 proto tcp/udp from any to any port = 81

# Allow traffic in from ISP's DHCP server. This rule must contain
# the IP address of your ISP's DHCP server as it's the only
# authorized source to send this packet type. Only necessary for
# cable or DSL configurations. This rule is not needed for
# 'user ppp' type connection to the public Internet.
# This is the same IP address you captured and
# used in the outbound section.
pass in quick on dc0 proto udp from z.z.z.z to any port = 68 keep state

# Allow in standard www function because I have apache server
pass in quick on dc0 proto tcp from any to any port = 80 flags S keep state

# Allow in non-secure Telnet session from public Internet
# labeled non-secure because ID/PW passed over public Internet as clear text.
# Delete this sample group if you do not have telnet server enabled.
#pass in quick on dc0 proto tcp from any to any port = 23 flags S keep state

# Allow in secure FTP, Telnet, and SCP from public Internet
# This function is using SSH (secure shell)
pass in quick on dc0 proto tcp from any to any port = 22 flags S keep state

# Block and log only first occurrence of all remaining traffic
# coming into the firewall. The logging of only the first
# occurrence avoids filling up disk with Denial of Service logs.
# This rule implements the default block.
block in log first quick on dc0 all
################### End of rules file #####################################

31.5.14. NAT

NAT 是 网络地址转换(Network Address Translation) 的缩写。 对于那些熟悉 Linux® 的人来说, 这个概念叫做 IP 伪装 (Masquerading); NAT 和 IP 伪装是完全一样的概念。 由 IPF 的 NAT 提供的一项功能是, 将防火墙后的本地局域网 (LAN) 共享一个 ISP 提供的 IP 地址来接入 Internet 公网。

有些人可能会问, 为什么需要这么做。 一般而言, ISP 会为非商业用户提供动态的 IP 地址。 动态地址意味着每次登录到 ISP 都有可能得到不同的 IP 地址, 无论是采用电话拨号登录, 或使用 cable 以及 DSL 调制解调器的方式。 这个 IP 是您与 Internet 公网交互时使用的身份。

现在考虑家中有五台 PC 需要访问 Internet 的情形。 您可能需要向 ISP 为每一台 PC 所使用的独立的 Internet 账号付费, 并且拥有五根电话线。

有了 NAT, 您就只需要一个 ISP 账号, 然后将另外四台 PC 的网卡通过交换机连接起来, 并通过运行 FreeBSD 系统的那台机器作为网关连接出去。 NAT 会自动地将每一台 PC 在内网的 LAN IP 地址, 在离开防火墙时转换为公网的 IP 地址。 此外, 当数据包返回时, 也将进行逆向的转换。

在 IP 地址空间中, 有一些特殊的范围是保留供经过 NAT 的内网 LAN IP 地址使用的。 根据 RFC 1918, 可以使用下面这些 IP 范围用于内网, 它们不会在 Internet 公网上路由:

起始 IP 10.0.0.0

-

结束 IP 10.255.255.255

起始 IP 172.16.0.0

-

结束 IP 172.31.255.255

起始 IP 192.168.0.0

-

结束 IP 192.168.255.255

31.5.15. IPNAT

NAT 规则是通过 ipnat 命令加载的。 默认情况下, NAT 规则会保存在 /etc/ipnat.rules 文件中。 请参见 ipnat(1) 了解更多的详情。

如果在 NAT 已经启动之后想要修改 NAT 规则, 可以修改保存 NAT 规则的那个文件, 然后在执行 ipnat 命令时加上 -CF 参数, 以删除在用的 NAT 内部规则表, 以及所有地址翻译表中已有的项。

要重新加载 NAT 规则, 可以使用类似下面的命令:

# ipnat -CF -f /etc/ipnat.rules

如果想要看看您系统上 NAT 的统计信息, 可以用下面的命令:

# ipnat -s

要列出当前的 NAT 表的映射关系, 使用下面的命令:

# ipnat -l

要显示详细的信息并显示与规则处理和当前的规则/表项:

# ipnat -v

31.5.16. IPNAT 规则

NAT 规则非常的灵活, 能够适应商业用户和家庭用户的各种不同的需求。

这里所介绍的规则语法已经被简化, 以适应非商用环境中的一般情况。 完整的规则语法描述, 请参考 ipnat(5) 联机手册中的介绍。

NAT 规则的写法与下面的例子类似:

map IF LAN_IP_RANGE -> PUBLIC_ADDRESS

关键词 map 出现在规则的最前面。

IF 替换为对外的网络接口名。

LAN_IP_RANGE 是内网中的客户机使用的地址范围。 通常情况下, 这应该是类似 192.168.1.0/24 的地址。

PUBLIC_ADDRESS 既可以是外网的 IP 地址, 也可以是 0/32 这个特殊的关键字, 它表示分配到 IF 上的所有地址。

31.5.17. NAT 的工作原理

当包从 LAN 到达防火墙, 而目的地址是公网地址时, 它首先会通过 outbound 过滤规则。 接下来, NAT 会得到包, 并按自顶向下的顺序处理规则, 而第一个匹配的规则将生效。 NAT 接下来会根据包对应的接口名字和源 IP 地址检查所有的规则。 如果包和某个 NAT 规则匹配, 则会检查包的 (源 IP 地址, 例如, 内网的 IP 地址) 是否在 NAT 规则中箭头左侧指定的 IP 地址范围匹配。 如果匹配, 则包的原地址将被根据用 0/32 关键字指定的 IP 地址重写。 NAT 将向它的内部 NAT 表发送此地址, 这样, 当包从 Internet 公网中返回时, 就能够把地址映射回原先的内网 IP 地址, 并在随后使用过滤器规则来处理。

31.5.18. 启用 IPNAT

要启用 IPNAT, 只需在 /etc/rc.conf 中加入下面一些语句。

使机器能够在不同的网络接口之间进行包的转发, 需要:

gateway_enable="YES"

每次开机时自动启动 IPNAT:

ipnat_enable="YES"

指定 IPNAT 规则集文件:

ipnat_rules="/etc/ipnat.rules"

31.5.19. 大型 LAN 中的 NAT

对于在一个 LAN 中有大量 PC, 以及包含多个 LAN 的情形, 把所有的内网 IP 地址都映射到同一个公网 IP 上会导致资源不够的问题, 因为同一个端口可能在许多做了 NAT 的 LAN PC 上被多次使用, 并导致碰撞。 有两种方法来缓解这个难题。

31.5.19.1. 指定使用哪些端口

普通的 NAT 规则类似于:

map dc0 192.168.1.0/24 -> 0/32

上面的规则中, 包的源端口在包通过 IPNAT 时时不会发生变化的。 通过使用 portmap 关键字, 您可以要求 IPNAT 只使用指定范围内的端口地址。 比如说, 下面的规则将让 IPNAT 把源端口改为指定范围内的端口:

map dc0 192.168.1.0/24 -> 0/32 portmap tcp/udp 20000:60000

使用 auto 关键字可以让配置变得更简单一些, 它会要求 IPNAT 自动地检测可用的端口并使用:

map dc0 192.168.1.0/24 -> 0/32 portmap tcp/udp auto

31.5.19.2. 使用公网地址池

对很大的 LAN 而言, 总有一天会达到这样一个临界值, 此时的 LAN 地址已经多到了无法只用一个公网地址表现的程度。 如果有可用的一块公网 IP 地址, 则可以将这些地址作为一个 "地址池" 来使用, 让 IPNAT 来从这些公网 IP 地址中挑选用于发包的地址, 并将其为这些包创建映射关系。

例如, 如果将下面这个把所有包都映射到同一公网 IP 地址的规则:

map dc0 192.168.1.0/24 -> 204.134.75.1

稍作修改, 就可以用子网掩码来表达 IP 地址范围:

map dc0 192.168.1.0/24 -> 204.134.75.0/255.255.255.0

或者用 CIDR 记法来指定的一组地址了:

map dc0 192.168.1.0/24 -> 204.134.75.0/24

31.5.20. 端口重定向

非常流行的一种做法是, 将 web 服务器、 邮件服务器、 数据库服务器以及 DNS 分别放到 LAN 上的不同的 PC 上。 这种情况下, 来自这些服务器的网络流量仍然应该被 NAT, 但必须有办法把进入的流量发到对应的局域网的 PC 上。 IPNAT 提供了 NAT 重定向机制来解决这个问题。 考虑下面的情况, 您的 web 服务器的 LAN 地址是 10.0.10.25, 而您的唯一的公网 IP 地址是 20.20.20.5, 则可以编写这样的规则:

rdr dc0 20.20.20.5/32 port 80 -> 10.0.10.25 port 80

或者:

rdr dc0 0.0.0.0/0 port 80 -> 10.0.10.25 port 80

另外, 也可以让 LAN 地址 10.0.10.33 上运行的 LAN DNS 服务器来处理公网上的 DNS 请求:

rdr dc0 20.20.20.5/32 port 53 -> 10.0.10.33 port 53 udp

31.5.21. FTP 和 NAT

FTP 是一个在 Internet 如今天这样为人所熟知之前就已经出现的恐龙, 那时, 研究机构和大学是通过租用的线路连到一起的, 而 FTP 则被用于在科研人员之间共享大文件。 那时, 数据的安全性并不是需要考虑的事情。 若干年之后, FTP 协议则被埋进了正在形成中的 Internet 骨干, 而它使用明文来交换用户名和口令的缺点, 并没有随着新出现的一些安全需求而得到改变。 FTP 提供了两种不同的风格, 即主动模式和被动模式。 两者的区别在于数据通道的建立方式。 被动模式相对而言要更加安全, 因为数据通道是由发起 ftp 会话的一方建立的。 关于 FTP 以及它所提供的不同模式, 在 http://www.slacksite.com/other/ftp.html 进行了很好的阐述。

31.5.21.1. IPNAT 规则

IPNAT 提供了一个内建的 FTP 代理选项, 它可以在 NAT map 规则中指定。 它能够监视所有外发的 FTP 主动或被动模式的会话开始请求, 并动态地创建临时性的过滤器规则, 只打开用于数据通道的端口号。 这样, 就消除了 FTP 一般会给防火墙带来的, 需要大范围地打开高端口所可能带来的安全隐患。

下面的规则可以处理来自内网的 FTP 访问:

map dc0 10.0.10.0/29 -> 0/32 proxy port 21 ftp/tcp

这个规则能够处理来自网关的 FTP 访问:

map dc0 0.0.0.0/0 -> 0/32 proxy port 21 ftp/tcp

这个则处理所有来自内网的非 FTP 网络流量:

map dc0 10.0.10.0/29 -> 0/32

FTP map 规则应该在普通的 map 规则之前出现。 所有的包会从最上面的第一个规则开始进行检查。 匹配的顺序是网卡名称, 内网源 IP 地址, 以及它是否是 FTP 包。 如果所有这些规则都匹配成功, 则 FTP 代理将建立一个临时的过滤规则, 以便让 FTP 会话的数据包能够正常出入, 同时对这些包进行 NAT。 所有的 LAN 数据包, 如果没有匹配第一条规则, 则会继续尝试匹配下面的规则, 并最终被 NAT。

31.5.21.2. IPNAT FTP 过滤规则

如果使用了 NAT FTP 代理, 则只需要为 FTP 创建一个规则。

如果不使用 FTP 代理, 就需要下面这三个规则:

# Allow out LAN PC client FTP to public Internet
# Active and passive modes
pass out quick on rl0 proto tcp from any to any port = 21 flags S keep state

# Allow out passive mode data channel high order port numbers
pass out quick on rl0 proto tcp from any to any port > 1024 flags S keep state

# Active mode let data channel in from FTP server
pass in quick on rl0 proto tcp from any to any port = 20 flags S keep state

31.6. IPFW

IPFIREWALL (IPFW) 是一个由 FreeBSD 发起的防火墙应用软件, 它由 FreeBSD 的志愿者成员编写和维护。 它使用了传统的无状态规则和规则编写方式, 以期达到简单状态逻辑所期望的目标。

标准的 FreeBSD 安装中, IPFW 所给出的规则集样例 (可以在 /etc/rc.firewall/etc/rc.firewall6 中找到) 非常简单, 建议不要不加修改地直接使用。 该样例中没有使用状态过滤, 而该功能在大部分的配置中都是非常有用的, 因此这一节并不以系统自带的样例作为基础。

IPFW 的无状态规则语法, 是由一种提供复杂的选择能力的技术支持的, 这种技术远远超出了一般的防火墙安装人员的知识水平。 IPFW 是为满足专业用户, 以及掌握先进技术的电脑爱好者们对于高级的包选择需求而设计的。 要完全释放 IPFW 的规则所拥有的强大能力, 需要对不同的协议的细节有深入的了解, 并根据它们独特的包头信息来编写规则。 这一级别的详细阐述超出了这本手册的范围。

IPFW 由七个部分组成, 其主要组件是内核的防火墙过滤规则处理器, 及其集成的数据包记帐工具、 日志工具、 用以触发 NAT 工具的 divert (转发) 规则、 高级特殊用途工具、 dummynet 流量整形机制, fwd rule 转发工具, 桥接工具, 以及 ipstealth 工具。 IPFW 支持 IPv4 和 IPv6。

31.6.1. 启用 IPFW

IPFW 是基本的 FreeBSD 安装的一部分, 以单独的可加载内核模块的形式提供。 如果在 rc.conf 中加入 firewall_enable="YES" 语句, 就会自动地加载对应的内核模块。 除非您打算使用由它提供的 NAT 功能, 一般情况下并不需要把 IPFW 编进 FreeBSD 的内核。

如果将 firewall_enable="YES" 加入到 rc.conf 中并重新启动系统, 则下列信息将在启动过程中, 以高亮的白色显示出来:

ipfw2 initialized, divert disabled, rule-based forwarding disabled, default to deny, logging disabled

可加载内核模块在编译时加入了记录日志的能力。 要启用日志功能, 并配置详细日志记录的限制, 需要在 /etc/sysctl.conf 中加入一些配置。 这些设置将在重新启动之后生效:

net.inet.ip.fw.verbose=1
net.inet.ip.fw.verbose_limit=5

31.6.2. 内核选项

把下列选项在编译 FreeBSD 内核时就加入, 并不是启用 IPFW 所必需的, 除非您需要使用 NAT 功能。 这里只是将这些选项作为背景知识来介绍。

options    IPFIREWALL

这个选项将 IPFW 作为内核的一部分来启用。

options    IPFIREWALL_VERBOSE

这个选项将启用记录通过 IPFW 的匹配了包含 log 关键字规则的每一个包的功能。

options    IPFIREWALL_VERBOSE_LIMIT=5

以每项的方式, 限制通过 syslogd(8) 记录的包的个数。 如果在比较恶劣的环境下记录防火墙的活动可能会需要这个选项。 它能够避免潜在的针对 syslog 的洪水式拒绝服务攻击。

options    IPFIREWALL_DEFAULT_TO_ACCEPT

这个选项默认地允许所有的包通过防火墙, 如果您是第一次配置防火墙, 使用这个选项将是一个不错的主意。

options    IPDIVERT

这一选项启用 NAT 功能。

如果内核选项中没有加入 IPFIREWALL_DEFAULT_TO_ACCEPT, 而配置使用的规则集中也没有明确地指定允许连接进入的规则, 默认情况下, 发到本机和从本机发出的所有包都会被阻止。

31.6.3. /etc/rc.conf Options

启用防火墙:

firewall_enable="YES"

要选择由 FreeBSD 提供的几种防火墙类型中的一种来作为默认配置, 您需要阅读 /etc/rc.firewall 文件并选出合适的类型, 然后在 /etc/rc.conf 中加入类似下面的配置:

firewall_type="open"

您还可以指定下列配置规则之一:

  • open - 允许所有流量通过。

  • client - 只保护本机。

  • simple - 保护整个网络。

  • closed - 完全禁止除回环设备之外的全部 IP 流量。

  • UNKNOWN - 禁止加载防火墙规则。

  • filename - 到防火墙规则文件的绝对路径。

有两种加载自定义 ipfw 防火墙规则的方法。 其一是将变量 firewall_type 设为包含不带 ipfw(8) 命令行选项的 防火墙规则 文件的完整路径。 下面是一个简单的规则集例子:

add deny in
add deny out

除此之外, 也可以将 firewall_script 变量设为包含 ipfw 命令的可执行脚本, 这样这个脚本会在启动时自动执行。 与前面规则集文件等价的规则脚本如下:

ipfw 命令是在防火墙运行时, 用于在其内部规则表中手工逐条添加或删除防火墙规则的标准工具。 这一方法的问题在于, 一旦您的关闭计算机或停机, 则所有增加或删除或修改的规则也就丢掉了。 把所有的规则都写到一个文件中, 并在启动时使用这个文件来加载规则, 或一次大批量地替换防火墙规则, 那么推荐使用这里介绍的方法。

ipfw 的另一个非常实用的功能是将所有正在运行的防火墙规则显示出来。 IPFW 的记账机制会为每一个规则动态地创建计数器, 用以记录与它们匹配的包的数量。 在测试规则的过程中, 列出规则及其计数器是了解它们是否工作正常的重要手段。

按顺序列出所有的规则:

# ipfw list

列出所有的规则, 同时给出最后一次匹配的时间戳:

# ipfw -t list

列出所有的记账信息、 匹配规则的包的数量, 以及规则本身。 第一列是规则的编号, 随后是发出包匹配的数量, 进入包的匹配数量, 最后是规则本身。

# ipfw -a list

列出所有的动态规则和静态规则:

# ipfw -d list

同时显示已过期的动态规则:

# ipfw -d -e list

将计数器清零:

# ipfw zero

只把规则号为 NUM 的计数器清零:

# ipfw zero NUM

31.6.4. IPFW 规则集

规则集是指一组编写好的依据包的值决策允许通过或阻止 IPFW 规则。 包的双向交换组成了一个会话交互。 防火墙规则集会作用于来自于 Internet 公网的包以及由系统发出来回应这些包的数据包。 每一个 TCP/IP 服务 (例如 telnet, www, 邮件等等) 都由协议预先定义了其特权 (监听) 端口。 发到特定服务的包会从源地址使用非特权 (高编号) 端口发出, 并发到特定服务在目的地址的对应端口。 所有这些参数 (例如: 端口和地址) 都是可以为防火墙规则所利用的, 判别是否允许服务通过的标准。

当有数据包进入防火墙时, 会从规则集里的第一个规则开始进行比较, 并自顶向下地进行匹配。 当包与某个选择规则参数相匹配时, 将会执行规则所定义的动作, 并停止规则集搜索。 这种策略, 通常也被称作 "最先匹配者获胜" 的搜索方法。 如果没有任何与包相匹配的规则, 那么它就会根据强制的 IPFW 默认规则, 也就是 65535 号规则截获。 一般情况下这个规则是阻止包, 而且不给出任何回应。

如果规则定义的动作是 countskiptotee 规则的话, 搜索会继续。

这里所介绍的规则, 都是使用了那些包含状态功能的, 也就是 keep statelimitinout 以及 via 选项的规则。 这是编写明示允许防火墙规则集所需的基本框架。

在操作防火墙规则时应谨慎行事, 如果操作不当, 很容易将自己反锁在外面。

31.6.4.1. 规则语法

这里所介绍的规则语法已经经过了简化, 只包括了建立标准的明示允许防火墙规则集所必需的那些。 要了解完整的规则语法说明, 请参见 ipfw(8) 联机手册。

规则是由关键字组成的: 这些关键字必须以特定的顺序从左到右书写。 下面的介绍中, 关键字使用粗体表示。 某些关键字还包括了子选项, 这些子选项本身可能也是关键字, 有些还可以包含更多的子选项。

# 用于表示开始一段注释。 它可以出现在一个规则的后面, 也可以独占一行。 空行会被忽略。

CMD RULE_NUMBER ACTION LOGGING SELECTION STATEFUL

31.6.4.1.1. CMD

每一个新的规则都应以 add 作为前缀, 它表示将规则加入内部表。

31.6.4.1.2. RULE_NUMBER

每一条规则都与一个范围在 1 到 65535 之间的规则编号相关联。

31.6.4.1.3. ACTION

每一个规则可以与下列的动作之一相关联, 所指定的动作将在进入的数据包与规则所指定的选择标准相匹配时执行。

allow | accept | pass | permit

这些关键字都表示允许匹配规则的包通过防火墙, 并停止继续搜索规则。

check-state

根据动态规则表检查数据包。 如果匹配, 则执行规则所指定的动作, 亦即生成动态规则; 否则, 转移到下一个规则。 check-state 规则没有选择标准。 如果规则集中没有 check-state 规则, 则会在第一个 keep-state 或 limit 规则处, 对动态规则表实施检查。

deny | drop

这两个关键字都表示丢弃匹配规则的包。 同时, 停止继续搜索规则。

31.6.4.1.4. LOGGING

log or logamount

当数据包与带 log 关键字的规则匹配时, 将通过名为 SECURITY 的 facility 来把消息记录到 syslogd(8)。 只有在记录的次数没有超过 logamount 参数所指定的次数时, 才会记录日志。 如果没有指定 logamount, 则会以 sysctl 变量 net.inet.ip.fw.verbose_limit 所指定的限制为准。 如果将这两种限制值之一指定为零, 则表示不作限制。 如果达到了限制数, 可以通过将规则的日志计数或包计数清零来重新启用日志, 请参见 ipfw reset log 命令来了解细节。

日志是在所有其他匹配条件都验证成功之后, 在针对包实施最终动作 (accept, deny) 之前进行的。 您可以自行决定哪些规则应启用日志。

31.6.4.1.5. SELECTION

这一节所介绍的关键字主要用来描述检查包的哪些属性, 用以判断包是否与规则相匹配。 下面是一些通用的用于匹配包特征的属性, 它们必须按顺序使用:

udp | tcp | icmp

也可以指定在 /etc/protocols 中所定义的协议。 这个值定义的是匹配的协议, 在规则中必须指定它。

from src to dst

fromto 关键字用于匹配 IP 地址。 规则中必须 同时 指定源和目的两个参数。 如果需要匹配任意 IP 地址, 可以使用特殊关键字 any。 还有一个特殊关键字, 即 me, 用于匹配您的 FreeBSD 系统上所有网络接口上所配置的 IP 地址, 它可以用于表达网络上的其他计算机到防火墙 (也就是本机), 例如 from me to anyfrom any to mefrom 0.0.0.0/0 to anyfrom any to 0.0.0.0/0from 0.0.0.0 to anyfrom any to 0.0.0.0 以及 from me to 0.0.0.0。 IP 地址可以通过 带点的 IP 地址/掩码长度 (CIDR 记法), 或者一个带点的 IP 地址的形式来指定。 这是编写规则时所必需的。 使用 net-mgmt/ipcalc port 可以用来简化计算。 关于这个工具的更多信息, 也可参考它的主页: http://jodies.de/ipcalc

port number

这个参数主要用于那些支持端口号的协议 (例如 TCP 和 UDP)。 如果要通过端口号匹配某个协议, 就必须指定这个参数。 此外, 也可以通过服务的名字 (根据 /etc/services) 来指定服务, 这样会比使用数字指定端口号直观一些。

in | out

相应地, 匹配进入和发出的包。 这里的 inout 都是关键字, 在编写匹配规则时, 必需作为其他条件的一部分来使用。

via IF

根据指定的网络接口的名称精确地匹配进出的包。 这里的 via 关键字将使得接口名称成为匹配过程的一部分。

setup

要匹配 TCP 会话的发起请求, 就必须使用它。

keep-state

这是一个必须使用的关键字。 在发生匹配时, 防火墙将创建一个动态规则, 其默认行为是, 匹配使用同一协议的、从源到目的 IP/端口 的双向网络流量。

limit {src-addr | src-port | dst-addr | dst-port}

防火墙只允许匹配规则时, 与指定的参数相同的 N 个连接。 可以指定至少一个源或目的地址及端口。 limitkeep-state 不能在同一规则中同时使用。 limit 提供了与 keep-state 相同的功能, 并增加了一些独有的能力。

31.6.4.2. 状态规则选项

有状态过滤将网络流量当作一种双向的包交换来处理。 它提供了一种额外的检查能力, 用以检测会话中的包是否来自最初的发送者, 并在遵循双向包交换的规则进行会话。 如果包与这些规则不符, 则将自动地拒绝它们。

check-state 用来识别在 IPFW 规则集中的包是否符合动态规则机制的规则。 如果匹配, 则允许包通过, 此时防火墙将创建一个新的动态规则来匹配双向交换中的下一个包。 如果不匹配, 则将继续尝试规则集中的下一个规则。

动态规则机制在 SYN-flood 攻击下是脆弱的, 因为这种情况会产生大量的动态规则, 从而耗尽资源。 为了抵抗这种攻击, 从 FreeBSD 中加入了一个叫做 limit 的新选项。 这个选项可以用来限制符合规则的会话允许的并发连接数。 如果动态规则表中的规则数超过 limit 的限制数量, 则包将被丢弃。

31.6.4.3. 记录防火墙消息

记录日志的好处是显而易见的: 它提供了在事后检查所发生的状况的方法, 例如哪些包被丢弃了, 这些包的来源和目的地, 从而为您提供找到攻击者所需的证据。

即使启用了日志机制, IPFW 也不会自行生成任何规则的日志。 防火墙管理员需要指定规则集中的哪些规则应该记录日志, 并在这些规则上增加 log 动作。 一般来说, 只有 deny 规则应记录日志, 例如对于进入的 ICMP ping 的 deny 规则。 另外, 复制 "默认的 ipfw 终极 deny 规则", 并加入 log 动作来作为您的规则集的最后一条规则也是很常见的用法。 这样, 您就能看到没有匹配任何一条规则的那些数据包。

日志是一把双刃剑, 如果不谨慎地加以利用, 则可能会陷入过多的日志数据中, 并导致磁盘被日志塞满。 将磁盘填满是 DoS 攻击最为老套的手法之一。 由于 syslogd 除了会将日志写入磁盘之外, 还会输出到 root 的控制台屏幕上, 因此有过多的日志信息是很让人恼火的事情。

IPFIREWALL_VERBOSE_LIMIT=5 内核选项将限制同一个规则发到系统日志程序 syslogd(8) 的连续消息的数量。 当内核启用了这个选项时, 某一特定规则所产生的连续消息的数量将封顶为这个数字。 一般来说, 没有办法从连续 200 条一模一样的日志信息中获取更多有用的信息。 举例来说, 如果同一个规则产生了 5 次消息并被记录到 syslogd, 余下的相同的消息将被计数, 并像下面这样发给 syslogd:

last message repeated 45 times

所有记录的数据包包消息, 默认情况下会最终写到 /var/log/security 文件中, 后者在 /etc/syslog.conf 文件里进行了定义。

31.6.4.4. 编写规则脚本

绝大多数有经验的 IPFW 用户会创建一个包含规则的文件, 并且, 按能够以脚本形式运行的方式来书写。 这样做最大的一个好处是, 可以大批量地刷新防火墙规则, 而无须重新启动系统就能够激活它们。 这种方法在测试新规则时会非常方便, 因为同一过程在需要时可以多次执行。 作为脚本, 您可以使用符号替换来撰写那些经常需要使用的值, 并用同一个符号在多个规则中反复地表达它。 下面将给出一个例子。

这个脚本使用的语法同 sh(1)csh(1) 以及 tcsh(1) 脚本兼容。 符号替换字段使用美元符号 $ 作为前缀。 符号字段本身并不使用 $ 前缀。 符号替换字段的值必须使用 "双引号" 括起来。

可以使用类似下面的规则文件:

############### start of example ipfw rules script #############
#
ipfw -q -f flush       # Delete all rules
# Set defaults
oif="tun0"             # out interface
odns="192.0.2.11"      # ISP's DNS server IP address
cmd="ipfw -q add "     # build rule prefix
ks="keep-state"        # just too lazy to key this each time
$cmd 00500 check-state
$cmd 00502 deny all from any to any frag
$cmd 00501 deny tcp from any to any established
$cmd 00600 allow tcp from any to any 80 out via $oif setup $ks
$cmd 00610 allow tcp from any to $odns 53 out via $oif setup $ks
$cmd 00611 allow udp from any to $odns 53 out via $oif $ks
################### End of example ipfw rules script ############

这就是所要做的全部事情了。 例子中的规则并不重要, 它们主要是用来表示如何使用符号替换。

如果把上面的例子保存到 /etc/ipfw.rules 文件中。 下面的命令来会重新加载规则。

# sh /etc/ipfw.rules

/etc/ipfw.rules 这个文件可以放到任何位置, 也可以命名为随便什么别的名字。

也可以手工执行下面的命令来达到类似的目的:

# ipfw -q -f flush
# ipfw -q add check-state
# ipfw -q add deny all from any to any frag
# ipfw -q add deny tcp from any to any established
# ipfw -q add allow tcp from any to any 80 out via tun0 setup keep-state
# ipfw -q add allow tcp from any to 192.0.2.11 53 out via tun0 setup keep-state
# ipfw -q add 00611 allow udp from any to 192.0.2.11 53 out via tun0 keep-state

31.6.4.5. 带状态规则集

以下的这组非-NAT 规则集, 是如何编写非常安全的 '明示允许' 防火墙的一个例子。 明示允许防火墙只允许匹配了 pass 规则的包通过, 而默认阻止所有的其他数据包。 用来保护整个网段的防火墙, 至少需要有两个网络接口, 并且其上必须配置规则, 以便让防火墙正常工作。

所有类 UNIX® 操作系统, 也包括 FreeBSD, 都设计为允许使用网络接口 lo0 和 IP 地址 127.0.0.1 来完成操作系统内部的通讯。 防火墙必须包含一组规则, 使这些数据包能够无障碍地收发。

接入 Internet 公网的那个网络接口上, 应该配置授权和访问控制, 来限制对外的访问, 以及来自 Internet 公网的访问。 这个接口很可能是您的用户态 PPP 接口, 例如 tun0, 或者您接在 DSL 或电缆 modem 上的网卡。

如果有至少一个网卡接入了防火墙后的内网 LAN, 则必须为这些接口配置规则, 以便让这些接口之间的包能够顺畅地通过。

所有的规则应被组织为三个部分, 所有应无阻碍地通过的规则, 公网的发出规则, 以及公网的接收规则。

公网接口相关的规则的顺序, 应该是最经常用到的放在尽可能靠前的位置, 而最后一个规则, 则应该是阻止那个接口在那一方向上的包。

发出部分的规则只包含一些 allow 规则, 允许选定的那些唯一区分协议的端口号所指定的协议通过, 以允许访问 Internet 公网上的这些服务。 所有的规则中都指定了 protoportin/outvia 以及 keep state 这些选项。 proto tcp 规则同时指定 setup 选项, 来区分开始协议会话的包, 以触发将包放入 keep state 规则表中的动作。

接收部分则首先阻止所有不希望的包, 这样做有两个不同的原因。 其一是恶意的包可能和某些允许的流量规则存在部分匹配, 而我们希望阻止, 而不是让这些包仅仅与 allow 规则部分匹配就允许它们进入。 其二是, 已经确信要阻止的包被拒绝这件事, 往往并不是我们需要关注的, 因此只要简单地予以阻止即可。 防火墙规则集中的每个部分的最后一条规则都是阻止并记录包, 这有助于为逮捕攻击者留下法律所要求的证据。

另外一个需要注意的事情是确保系统对不希望的数据包不做回应。 无效的包应被丢弃和消失。 这样, 攻击者便无法知道包是否到达了您的系统。 攻击者对系统了解的越少, 其攻击的难度也就越大。 如果不知道端口号, 可以查阅 /etc/services/ 或到 http://en.wikipedia.org/wiki/List_of_TCP_and_UDP_port_numbers 并查找一下端口号, 以了解其用途。 另外, 您也可以在这个网页上了解常见木马所使用的端口: http://www.sans.org/security-resources/idfaq/oddports.php

31.6.4.6. 明示允许规则集的例子

下面是一个非-NAT 的规则集, 它是一个完整的明示允许规则集。 使用它作为您的规则集不会有什么问题。 只需把那些不需要的服务对应的 pass 规则注释掉就可以了。 如果您在日志中看到消息, 而且不想再看到它们, 只需在接收部分增加一个一个 deny 规则。 您可能需要把 dc0 改为接入公网的接口的名字。 对于使用用户态 PPP 的用户而言, 应该是 tun0

这些规则遵循一定的模式。

  • 所有请求 Internet 公网上服务的会话开始包, 都使用了 keep-state

  • 所有来自 Internet 的授权服务请求, 都采用了 limit 选项来防止洪水式攻击。

  • 所有的规则都使用了 in 或者 out 来说明方向。

  • 所有的规则都使用了 via接口名 来指定应该匹配通过哪一个接口的包。

这些规则都应放到 /etc/ipfw.rules

################ Start of IPFW rules file ###############################
# Flush out the list before we begin.
ipfw -q -f flush

# Set rules command prefix
cmd="ipfw -q add"
pif="dc0"     # public interface name of NIC
              # facing the public Internet

#################################################################
# No restrictions on Inside LAN Interface for private network
# Not needed unless you have LAN.
# Change xl0 to your LAN NIC interface name
#################################################################
#$cmd 00005 allow all from any to any via xl0

#################################################################
# No restrictions on Loopback Interface
#################################################################
$cmd 00010 allow all from any to any via lo0

#################################################################
# Allow the packet through if it has previous been added to the
# the "dynamic" rules table by a allow keep-state statement.
#################################################################
$cmd 00015 check-state

#################################################################
# Interface facing Public Internet (Outbound Section)
# Interrogate session start requests originating from behind the
# firewall on the private network or from this gateway server
# destined for the public Internet.
#################################################################

# Allow out access to my ISP's Domain name server.
# x.x.x.x must be the IP address of your ISP.s DNS
# Dup these lines if your ISP has more than one DNS server
# Get the IP addresses from /etc/resolv.conf file
$cmd 00110 allow tcp from any to x.x.x.x 53 out via $pif setup keep-state
$cmd 00111 allow udp from any to x.x.x.x 53 out via $pif keep-state

# Allow out access to my ISP's DHCP server for cable/DSL configurations.
# This rule is not needed for .user ppp. connection to the public Internet.
# so you can delete this whole group.
# Use the following rule and check log for IP address.
# Then put IP address in commented out rule & delete first rule
$cmd 00120 allow log udp from any to any 67 out via $pif keep-state
#$cmd 00120 allow udp from any to x.x.x.x 67 out via $pif keep-state

# Allow out non-secure standard www function
$cmd 00200 allow tcp from any to any 80 out via $pif setup keep-state

# Allow out secure www function https over TLS SSL
$cmd 00220 allow tcp from any to any 443 out via $pif setup keep-state

# Allow out send & get email function
$cmd 00230 allow tcp from any to any 25 out via $pif setup keep-state
$cmd 00231 allow tcp from any to any 110 out via $pif setup keep-state

# Allow out FBSD (make install & CVSUP) functions
# Basically give user root "GOD" privileges.
$cmd 00240 allow tcp from me to any out via $pif setup keep-state uid root

# Allow out ping
$cmd 00250 allow icmp from any to any out via $pif keep-state

# Allow out Time
$cmd 00260 allow tcp from any to any 37 out via $pif setup keep-state

# Allow out nntp news (i.e., news groups)
$cmd 00270 allow tcp from any to any 119 out via $pif setup keep-state

# Allow out secure FTP, Telnet, and SCP
# This function is using SSH (secure shell)
$cmd 00280 allow tcp from any to any 22 out via $pif setup keep-state

# Allow out whois
$cmd 00290 allow tcp from any to any 43 out via $pif setup keep-state

# deny and log everything else that.s trying to get out.
# This rule enforces the block all by default logic.
$cmd 00299 deny log all from any to any out via $pif

#################################################################
# Interface facing Public Internet (Inbound Section)
# Check packets originating from the public Internet
# destined for this gateway server or the private network.
#################################################################

# Deny all inbound traffic from non-routable reserved address spaces
$cmd 00300 deny all from 192.168.0.0/16 to any in via $pif  #RFC 1918 private IP
$cmd 00301 deny all from 172.16.0.0/12 to any in via $pif     #RFC 1918 private IP
$cmd 00302 deny all from 10.0.0.0/8 to any in via $pif          #RFC 1918 private IP
$cmd 00303 deny all from 127.0.0.0/8 to any in via $pif        #loopback
$cmd 00304 deny all from 0.0.0.0/8 to any in via $pif            #loopback
$cmd 00305 deny all from 169.254.0.0/16 to any in via $pif   #DHCP auto-config
$cmd 00306 deny all from 192.0.2.0/24 to any in via $pif       #reserved for docs
$cmd 00307 deny all from 204.152.64.0/23 to any in via $pif  #Sun cluster interconnect
$cmd 00308 deny all from 224.0.0.0/3 to any in via $pif         #Class D & E multicast

# Deny public pings
$cmd 00310 deny icmp from any to any in via $pif

# Deny ident
$cmd 00315 deny tcp from any to any 113 in via $pif

# Deny all Netbios service. 137=name, 138=datagram, 139=session
# Netbios is MS/Windows sharing services.
# Block MS/Windows hosts2 name server requests 81
$cmd 00320 deny tcp from any to any 137 in via $pif
$cmd 00321 deny tcp from any to any 138 in via $pif
$cmd 00322 deny tcp from any to any 139 in via $pif
$cmd 00323 deny tcp from any to any 81 in via $pif

# Deny any late arriving packets
$cmd 00330 deny all from any to any frag in via $pif

# Deny ACK packets that did not match the dynamic rule table
$cmd 00332 deny tcp from any to any established in via $pif

# Allow traffic in from ISP's DHCP server. This rule must contain
# the IP address of your ISP.s DHCP server as it.s the only
# authorized source to send this packet type.
# Only necessary for cable or DSL configurations.
# This rule is not needed for .user ppp. type connection to
# the public Internet. This is the same IP address you captured
# and used in the outbound section.
#$cmd 00360 allow udp from any to x.x.x.x 67 in via $pif keep-state

# Allow in standard www function because I have apache server
$cmd 00400 allow tcp from any to me 80 in via $pif setup limit src-addr 2

# Allow in secure FTP, Telnet, and SCP from public Internet
$cmd 00410 allow tcp from any to me 22 in via $pif setup limit src-addr 2

# Allow in non-secure Telnet session from public Internet
# labeled non-secure because ID & PW are passed over public
# Internet as clear text.
# Delete this sample group if you do not have telnet server enabled.
$cmd 00420 allow tcp from any to me 23 in via $pif setup limit src-addr 2

# Reject & Log all incoming connections from the outside
$cmd 00499 deny log all from any to any in via $pif

# Everything else is denied by default
# deny and log all packets that fell through to see what they are
$cmd 00999 deny log all from any to any
################ End of IPFW rules file ###############################

31.6.4.7. 一个 NAT 和带状态规则集的例子

要使用 IPFW 的 NAT 功能, 还需要进行一些额外的配置。 除了其他 IPFIREWALL 语句之外, 还需要在内核编译配置中加上 option IPDIVERT 语句。

/etc/rc.conf 中, 除了普通的 IPFW 配置之外, 还需要加入:

natd_enable="YES"                   # Enable NATD function
natd_interface="rl0"                # interface name of public Internet NIC
natd_flags="-dynamic -m"            # -m = preserve port numbers if possible

将带状态规则与 divert natd 规则 (网络地址转换) 会使规则集的编写变得非常复杂。 check-state 的位置, 以及 divert natd 规则将变得非常关键。 这样一来, 就不再有简单的顺序处理逻辑流程了。 提供了一种新的动作类型, 称为 skipto。 要使用 skipto 命令, 就必须给每一个规则进行编号, 以确定 skipto 规则号是您希望跳转到的位置。

下面给出了一些未加注释的例子来说明如何编写这样的规则, 用以帮助您理解包处理规则集的处理顺序。

处理流程从规则文件最上边的第一个规则开始处理, 并自顶向下地尝试每一个规则, 直到找到匹配的规则, 且数据包从防火墙中放出为止。 请注意规则号 100 101, 450, 500, 以及 510 的位置非常重要。 这些规则控制发出和接收的包的地址转换过程, 这样它们在 keep-state 动态表中的对应项中就能够与内网的 LAN IP 地址关联。 另一个需要注意的是, 所有的 allow 和 deny 规则都指定了包的方向 (也就是 outbound 或 inbound) 以及网络接口。 最后, 请注意所有发出的会话请求都会请求 skipto rule 500 以完成网络地址转换。

下面以 LAN 用户使用 web 浏览器访问一个 web 页面为例。 Web 页面使用 80 来完成通讯。 当包进入防火墙时, 规则 100 并不匹配, 因为它是发出而不是收到的包。 它能够通过规则 101, 因为这是第一个包, 因而它还没有进入动态状态保持表。 包最终到达规则 125, 并匹配该规则。 最终, 它会通过接入 Internet 公网的网卡发出。 这之前, 包的源地址仍然是内网 IP 地址。 一旦匹配这个规则, 就会触发两个动作。 keep-state 选项会把这个规则发到 keep-state 动态规则表中, 并执行所指定的动作。 动作是发到规则表中的信息的一部分。 在这个例子中, 这个动作是 skipto rule 500。 规则 500 NAT 包的 IP 地址, 并将其发出。 请务必牢记, 这一步非常重要。 接下来, 数据包将到达目的地, 之后返回并从规则集的第一条规则开始处理。 这一次, 它将与规则 100 匹配, 其目的 IP 地址将被映射回对应的内网 LAN IP 地址。 其后, 它会被 check-state 规则处理, 进而在暨存会话表中找到对应项, 并发到 LAN。 数据包接下来发到了内网 LAN PC 上, 而后者则会发送从远程服务器请求下一段数据的新数据包。 这个包会再次由 check-state 规则检查, 并找到发出的表项, 并执行其关联的动作, 即 skipto 500。 包跳转到规则 500 并被 NAT 后发出。

在接收一侧, 已经存在的会话的数据包会被 check-state 规则自动地处理, 并转到 divert nat 规则。 我们需要解决的问题是, 阻止所有的坏数据包, 而只允许授权的服务。 例如在防火墙上运行了 Apache 服务, 而我们希望人们在访问 Internet 公网的同时, 也能够访问本地的 web 站点。 新的接入开始请求包将匹配规则 100, 而 IP 地址则为防火墙所在的服务器而映射到了 LAN IP。 此后, 包会匹配所有我们希望检查的那些令人生厌的东西, 并最终匹配规则 425。 一旦发生匹配, 会发生两件事。 数据包会被发到 keep-state 动态表, 但此时, 所有来自那个源 IP 的会话请求的数量会被限制为 2。 这一做法能够挫败针对指定端口上服务的 DoS 攻击。 动作同时指定了 allow 包应被发到 LAN 上。 包返回时, check-state 规则会识别出包属于某一已经存在的会话交互, 并直接把它发到规则 500 做 NAT, 并发到发出接口。

示范规则集 #1:

#!/bin/sh
cmd="ipfw -q add"
skip="skipto 500"
pif=rl0
ks="keep-state"
good_tcpo="22,25,37,43,53,80,443,110,119"

ipfw -q -f flush

$cmd 002 allow all from any to any via xl0  # exclude LAN traffic
$cmd 003 allow all from any to any via lo0  # exclude loopback traffic

$cmd 100 divert natd ip from any to any in via $pif
$cmd 101 check-state

# Authorized outbound packets
$cmd 120 $skip udp from any to xx.168.240.2 53 out via $pif $ks
$cmd 121 $skip udp from any to xx.168.240.5 53 out via $pif $ks
$cmd 125 $skip tcp from any to any $good_tcpo out via $pif setup $ks
$cmd 130 $skip icmp from any to any out via $pif $ks
$cmd 135 $skip udp from any to any 123 out via $pif $ks

# Deny all inbound traffic from non-routable reserved address spaces
$cmd 300 deny all from 192.168.0.0/16  to any in via $pif  #RFC 1918 private IP
$cmd 301 deny all from 172.16.0.0/12   to any in via $pif  #RFC 1918 private IP
$cmd 302 deny all from 10.0.0.0/8      to any in via $pif  #RFC 1918 private IP
$cmd 303 deny all from 127.0.0.0/8     to any in via $pif  #loopback
$cmd 304 deny all from 0.0.0.0/8       to any in via $pif  #loopback
$cmd 305 deny all from 169.254.0.0/16  to any in via $pif  #DHCP auto-config
$cmd 306 deny all from 192.0.2.0/24    to any in via $pif  #reserved for docs
$cmd 307 deny all from 204.152.64.0/23 to any in via $pif  #Sun cluster
$cmd 308 deny all from 224.0.0.0/3     to any in via $pif  #Class D & E multicast

# Authorized inbound packets
$cmd 400 allow udp from xx.70.207.54 to any 68 in $ks
$cmd 420 allow tcp from any to me 80 in via $pif setup limit src-addr 1

$cmd 450 deny log ip from any to any

# This is skipto location for outbound stateful rules
$cmd 500 divert natd ip from any to any out via $pif
$cmd 510 allow ip from any to any

######################## end of rules  ##################

下面的这个规则集基本上和上面一样, 但使用了易于读懂的编写方式, 并给出了相当多的注解, 以帮助经验较少的 IPFW 规则编写者更好地理解这些规则到底在做什么。

示范规则集 #2:

#!/bin/sh
################ Start of IPFW rules file ###############################
# Flush out the list before we begin.
ipfw -q -f flush

# Set rules command prefix
cmd="ipfw -q add"
skip="skipto 800"
pif="rl0"     # public interface name of NIC
              # facing the public Internet

#################################################################
# No restrictions on Inside LAN Interface for private network
# Change xl0 to your LAN NIC interface name
#################################################################
$cmd 005 allow all from any to any via xl0

#################################################################
# No restrictions on Loopback Interface
#################################################################
$cmd 010 allow all from any to any via lo0

#################################################################
# check if packet is inbound and nat address if it is
#################################################################
$cmd 014 divert natd ip from any to any in via $pif

#################################################################
# Allow the packet through if it has previous been added to the
# the "dynamic" rules table by a allow keep-state statement.
#################################################################
$cmd 015 check-state

#################################################################
# Interface facing Public Internet (Outbound Section)
# Check session start requests originating from behind the
# firewall on the private network or from this gateway server
# destined for the public Internet.
#################################################################

# Allow out access to my ISP's Domain name server.
# x.x.x.x must be the IP address of your ISP's DNS
# Dup these lines if your ISP has more than one DNS server
# Get the IP addresses from /etc/resolv.conf file
$cmd 020 $skip tcp from any to x.x.x.x 53 out via $pif setup keep-state

# Allow out access to my ISP's DHCP server for cable/DSL configurations.
$cmd 030 $skip udp from any to x.x.x.x 67 out via $pif keep-state

# Allow out non-secure standard www function
$cmd 040 $skip tcp from any to any 80 out via $pif setup keep-state

# Allow out secure www function https over TLS SSL
$cmd 050 $skip tcp from any to any 443 out via $pif setup keep-state

# Allow out send & get email function
$cmd 060 $skip tcp from any to any 25 out via $pif setup keep-state
$cmd 061 $skip tcp from any to any 110 out via $pif setup keep-state

# Allow out FreeBSD (make install & CVSUP) functions
# Basically give user root "GOD" privileges.
$cmd 070 $skip tcp from me to any out via $pif setup keep-state uid root

# Allow out ping
$cmd 080 $skip icmp from any to any out via $pif keep-state

# Allow out Time
$cmd 090 $skip tcp from any to any 37 out via $pif setup keep-state

# Allow out nntp news (i.e., news groups)
$cmd 100 $skip tcp from any to any 119 out via $pif setup keep-state

# Allow out secure FTP, Telnet, and SCP
# This function is using SSH (secure shell)
$cmd 110 $skip tcp from any to any 22 out via $pif setup keep-state

# Allow out whois
$cmd 120 $skip tcp from any to any 43 out via $pif setup keep-state

# Allow ntp time server
$cmd 130 $skip udp from any to any 123 out via $pif keep-state

#################################################################
# Interface facing Public Internet (Inbound Section)
# Check packets originating from the public Internet
# destined for this gateway server or the private network.
#################################################################

# Deny all inbound traffic from non-routable reserved address spaces
$cmd 300 deny all from 192.168.0.0/16  to any in via $pif  #RFC 1918 private IP
$cmd 301 deny all from 172.16.0.0/12   to any in via $pif  #RFC 1918 private IP
$cmd 302 deny all from 10.0.0.0/8      to any in via $pif  #RFC 1918 private IP
$cmd 303 deny all from 127.0.0.0/8     to any in via $pif  #loopback
$cmd 304 deny all from 0.0.0.0/8       to any in via $pif  #loopback
$cmd 305 deny all from 169.254.0.0/16  to any in via $pif  #DHCP auto-config
$cmd 306 deny all from 192.0.2.0/24    to any in via $pif  #reserved for docs
$cmd 307 deny all from 204.152.64.0/23 to any in via $pif  #Sun cluster
$cmd 308 deny all from 224.0.0.0/3     to any in via $pif  #Class D & E multicast

# Deny ident
$cmd 315 deny tcp from any to any 113 in via $pif

# Deny all Netbios service. 137=name, 138=datagram, 139=session
# Netbios is MS/Windows sharing services.
# Block MS/Windows hosts2 name server requests 81
$cmd 320 deny tcp from any to any 137 in via $pif
$cmd 321 deny tcp from any to any 138 in via $pif
$cmd 322 deny tcp from any to any 139 in via $pif
$cmd 323 deny tcp from any to any 81  in via $pif

# Deny any late arriving packets
$cmd 330 deny all from any to any frag in via $pif

# Deny ACK packets that did not match the dynamic rule table
$cmd 332 deny tcp from any to any established in via $pif

# Allow traffic in from ISP's DHCP server. This rule must contain
# the IP address of your ISP's DHCP server as it's the only
# authorized source to send this packet type.
# Only necessary for cable or DSL configurations.
# This rule is not needed for 'user ppp' type connection to
# the public Internet. This is the same IP address you captured
# and used in the outbound section.
$cmd 360 allow udp from x.x.x.x to any 68 in via $pif keep-state

# Allow in standard www function because I have Apache server
$cmd 370 allow tcp from any to me 80 in via $pif setup limit src-addr 2

# Allow in secure FTP, Telnet, and SCP from public Internet
$cmd 380 allow tcp from any to me 22 in via $pif setup limit src-addr 2

# Allow in non-secure Telnet session from public Internet
# labeled non-secure because ID & PW are passed over public
# Internet as clear text.
# Delete this sample group if you do not have telnet server enabled.
$cmd 390 allow tcp from any to me 23 in via $pif setup limit src-addr 2

# Reject & Log all unauthorized incoming connections from the public Internet
$cmd 400 deny log all from any to any in via $pif

# Reject & Log all unauthorized out going connections to the public Internet
$cmd 450 deny log all from any to any out via $pif

# This is skipto location for outbound stateful rules
$cmd 800 divert natd ip from any to any out via $pif
$cmd 801 allow ip from any to any

# Everything else is denied by default
# deny and log all packets that fell through to see what they are
$cmd 999 deny log all from any to any
################ End of IPFW rules file ###############################

本文档和其它文档可从这里下载 https://download.freebsd.org/ftp/doc/

如果对于FreeBSD有问题,请先阅读 文档 ,如不能解决再联系 <freebsd-questions@FreeBSD.org>.
关于本文档的问题请发信联系 <freebsd-doc@FreeBSD.org>.