FreeBSD 使用手册
上一页 | 第30章 　防火墙 | 下一页

# 30.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 的邮件列表存档， 并进行搜索。

## 30.5.1 启用 IPF

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

## 30.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` 规则的包都会被阻止。

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

## 30.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 的规则定义文件

## 30.5.4 IPF

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

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

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

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

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

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

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

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

## 30.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) 联机手册以了解详细信息。

## 30.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 关键字， 作为您的规则集的最后一条。 这样就能够看到所有没有匹配任何规则的数据包了。

## 30.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` 来让刚创建的日志进行轮转。

## 30.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 表示端口不可达消息。

## 30.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 规则了。

## 30.5.10 IPF 规则集

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

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

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

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

## 30.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

### 30.5.11.1 ACTION (动作)

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

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

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

### 30.5.11.2 IN-OUT

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

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

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

### 30.5.11.3 OPTIONS

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

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

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

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

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

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

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

### 30.5.11.4 SELECTION

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

### 30.5.11.5 PROTO

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

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

### 30.5.11.6 SRC_ADDR/DST_ADDR

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

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

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

### 30.5.11.7 PORT

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

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

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

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

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

### 30.5.11.8 TCP_FLAG

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

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

### 30.5.11.9 STATEFUL

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

> 注意: 如果使用新式的处理逻辑， 则这个选项是必需的。

## 30.5.12 有状态过滤

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

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

所发生的事情是：

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

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

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

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

## 30.5.13 明示允许规则集的例子

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

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

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

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

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

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

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

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

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

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

如果记录的包的端口号并不是您所知道的， 可以在 `/etc/services` 或 http://www.securitystats.com/tools/portsearch.php 了解端口号通常的用途。

参考下面的网页， 了解木马使用的端口：http://www.simovits.com/trojans/trojans.html。

下面是我在自己的系统中使用的完整的， 非常安全的 `明示允许` 防火墙规则集。 直接使用这个规则集不会给您造成问题， 您所要做的只是注释掉那些您不需要 `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 #####################################

## 30.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`

## 30.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

## 30.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` 上的所有地址。

## 30.5.17 NAT 的工作原理

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

## 30.5.18 启用 IPNAT

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

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

    gateway_enable="YES"

每次开机时自动启动 IPNAT：

    ipnat_enable="YES"

指定 IPNAT 规则集文件：

    ipnat_rules="/etc/ipnat.rules"

## 30.5.19 大型 LAN 中的 NAT

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

### 30.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

### 30.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

## 30.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

## 30.5.21 FTP 和 NAT

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

### 30.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。

### 30.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

上一页 | 起始页 | 下一页
OpenBSD Packet Filter (PF) 和 ALTQ | 上一级 | IPFW

本文档和其它文档可从这里下载：ftp://ftp.FreeBSD.org/pub/FreeBSD/doc/.

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