# 33.5.IPFILTER（IPF）

IPFILTER，即 IPF，是一款跨平台的开源防火墙，已移植到多个操作系统，包括 FreeBSD、NetBSD、OpenBSD 和 Solaris™。

IPFILTER 是一个内核级防火墙和 NAT 机制，可以通过用户空间程序进行控制和监控。防火墙规则可以通过 `ipf` 设置或删除，NAT 规则可以通过 `ipnat` 设置或删除，IPFILTER 内核部分的运行时统计信息可以通过 `ipfstat` 打印，而 `ipmon` 可用于将 IPFILTER 操作记录到系统日志文件中。

IPF 最初使用了“最后匹配规则优先”的规则处理逻辑，并且仅使用无状态规则。从那时起，IPF 已增强为包括 `quick` 和 `keep state` 选项。

IPF 的常见问题解答可参考 <http://www.phildev.net/ipf/index.html>。IPFilter 邮件列表的可搜索存档可在 <http://marc.info/?l=ipfilter> 获取。

本章节的重点是与 FreeBSD 相关的 IPF。它提供了包含 `quick` 和 `keep state` 选项的规则示例。

## 33.5.1. 启用 IPF

IPF 已包含在 FreeBSD 基本安装中，作为一个内核加载模块，这意味着不需要自定义内核即可启用 IPF。

对于那些希望将 IPF 支持静态编译到自定义内核中的用户，请参阅 [Configuring the FreeBSD Kernel](https://docs.freebsd.org/en/books/handbook/kernelconfig/#kernelconfig) 中的说明。以下是可用的内核选项：

```ini
options IPFILTER
options IPFILTER_LOG
options IPFILTER_LOOKUP
options IPFILTER_DEFAULT_BLOCK
```

其中，`options IPFILTER` 启用 IPFILTER 支持，`options IPFILTER_LOG` 启用 IPF 日志记录，通过 **ipl** 包日志伪设备记录每个包含 `log` 关键字的规则，`IPFILTER_LOOKUP` 启用 IP 池以加速 IP 查找，`options IPFILTER_DEFAULT_BLOCK` 更改默认行为，使得任何不匹配防火墙 `pass` 规则的包都会被阻止。

要配置系统在启动时启用 IPF，请在 **/etc/rc.conf** 中添加以下条目。这些条目还将启用日志记录并设置 `default pass all`。要将默认策略更改为 `block all`，而不编译自定义内核，请记得在规则集的末尾添加 `block all` 规则。

```ini
ipfilter_enable="YES"             # 启动 ipf 防火墙
ipfilter_rules="/etc/ipf.rules"   # 加载规则定义文件
ipv6_ipfilter_rules="/etc/ipf6.rules" # 加载 IPv6 的规则定义文件
ipmon_enable="YES"                # 启动 IP 监控日志
ipmon_flags="-Ds"                 # D = 以守护进程模式启动
                                  # s = 记录到 syslog
                                  # v = 记录 tcp 窗口、ack、seq
                                  # n = 将 IP 和端口映射为名称
```

如果需要 NAT 功能，还应添加以下行：

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

然后，要立即启动 IPF：

```sh
# service ipfilter start
```

要加载防火墙规则，使用 `ipf` 指定规则集文件的名称。可以使用以下命令替换当前正在运行的防火墙规则：

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

其中，`-Fa` 会刷新所有内部规则表，`-f` 指定要加载的规则文件。

这使得能够对自定义规则集进行更改，并通过更新规则的最新副本来更新正在运行的防火墙，而无需重启系统。这种方法对于测试新规则非常方便，可以执行任意多次。

有关此命令其他标志的详细信息，请参阅 [ipf(8)](https://man.freebsd.org/cgi/man.cgi?query=ipf\&sektion=8\&format=html)。

## 33.5.2. IPF 规则语法

本节描述了用于创建有状态规则的 IPF 规则语法。在创建规则时，请记住，除非规则中包含 `quick` 关键字，否则每条规则都是按顺序读取的，*最后匹配的规则* 会被应用。这意味着，即使第一个匹配数据包的规则是 `pass`，如果之后有匹配的规则是 `block`，该数据包也会被丢弃。可以在 **/usr/share/examples/ipfilter** 中找到示例规则集。

在创建规则时，`#` 字符用于标记注释的开始，并可以出现在规则的末尾，以解释该规则的功能，或者单独位于一行。任何空白行都会被忽略。

规则中使用的关键字必须按特定顺序书写，从左到右。有些关键字是必需的，而其他则是可选的。某些关键字有子选项，这些子选项本身可能也是关键字，并且还包括更多子选项。关键字的顺序如下，其中大写字母表示变量，小写字母表示必须出现在后面的变量之前：

`ACTION DIRECTION OPTIONS proto PROTO_TYPE from SRC_ADDR SRC_PORT to DST_ADDR DST_PORT TCP_FLAG|ICMP_TYPE keep state STATE`

本节将描述这些关键字及其选项。它不是每个可能选项的详尽列表。有关创建 IPF 规则时可用规则语法的完整描述，请参考 [ipf(5)](https://man.freebsd.org/cgi/man.cgi?query=ipf\&sektion=5\&format=html)，并查阅每个关键字使用示例。

### ACTION

`ACTION` 关键字指示如果数据包匹配该规则，应该对数据包执行什么操作。每条规则*必须*有一个操作。以下是已识别的操作：

* `block`：丢弃数据包。
* `pass`：允许数据包。
* `log`：生成一条日志记录。
* `count`：统计数据包和字节数，这可以指示规则使用的频率。
* `auth`：将数据包排队，交由另一个程序进一步处理。
* `call`：访问 IPF 内置的功能，以执行更复杂的操作。
* `decapsulate`：去除任何头部，以处理数据包的内容。

### DIRECTION

接下来，每条规则必须明确指定流量的方向，使用以下关键字之一：

* `in`：规则应用于传入的数据包。
* `out`：规则应用于传出的数据包。
* `all`：规则适用于任一方向。

如果系统有多个接口，可以在方向后指定接口名。例如 `in on fxp0`。

### OPTIONS

`OPTIONS` 是可选的。但是，如果指定了多个选项，它们必须按这里显示的顺序使用。

* `log`：执行指定的操作时，数据包头部的内容将写入 [ipl(4)](https://man.freebsd.org/cgi/man.cgi?query=ipl\&sektion=4\&format=html) 包日志伪设备。
* `quick`：如果数据包匹配此规则，则执行规则指定的操作，并且不再处理任何后续规则。
* `on`：后面必须跟着接口名，该接口名是通过 [ifconfig(8)](https://man.freebsd.org/cgi/man.cgi?query=ifconfig\&sektion=8\&format=html) 显示的接口名。只有数据包通过指定的接口且符合指定方向时，规则才会匹配。

使用 `log` 关键字时，可以按顺序使用以下修饰符：

* `body`：表示数据包内容的前 128 字节将在头部之后记录。
* `first`：如果 `log` 关键字与 `keep state` 选项一起使用，则建议使用此选项，以便仅记录触发数据包，而不是每个匹配的有状态连接数据包。

还可以使用额外的选项来指定错误返回消息。有关详细信息，请参考 [ipf(5)](https://man.freebsd.org/cgi/man.cgi?query=ipf\&sektion=5\&format=html)。

### PROTO\_TYPE

`PROTO_TYPE` 是可选的。然而，如果规则需要指定 `SRC_PORT` 或 `DST_PORT`，则它是必需的，因为它定义了协议类型。在指定协议类型时，使用 `proto` 关键字后跟协议号或 **/etc/protocols** 中的协议名称。示例协议名称包括 `tcp`、`udp` 或 `icmp`。如果指定了 `PROTO_TYPE`，但没有指定 `SRC_PORT` 或 `DST_PORT`，则该规则将匹配该协议的所有端口号。

### SRC\_ADDR

`from` 关键字是必需的，后面跟着表示数据包源的关键字。源地址可以是主机名、带有 CIDR 掩码的 IP 地址、地址池或 `all` 关键字。有关示例，请参考 [ipf(5)](https://man.freebsd.org/cgi/man.cgi?query=ipf\&sektion=5\&format=html)。

没有办法匹配无法通过点分十进制形式/掩码长度表示的 IP 地址范围。可以使用 [net-mgmt/ipcalc](https://cgit.freebsd.org/ports/tree/net-mgmt/ipcalc/) 软件包和 Ports 来简化 CIDR 掩码的计算。更多信息可在该工具的网页上找到：<http://jodies.de/ipcalc>。

### SRC\_PORT

`SRC_PORT` 是可选的。然而，如果使用了它，则需要先定义 `PROTO_TYPE`。端口号必须以 `proto` 关键字为前缀。

支持多种比较运算符：`=`（等于）、`!=`（不等于）、`<`（小于）、`>`（大于）、`⇐`（小于或等于）和 `>=`（大于或等于）。

要指定端口范围，请将两个端口号放在 `< >`（小于和大于）、`><`（大于和小于）或 `:`（大于或等于，且小于或等于）之间。

### DST\_ADDR

`to` 关键字是必需的，后面跟着表示数据包目的地的关键字。与 `SRC_ADDR` 类似，它可以是主机名、带有 CIDR 掩码的 IP 地址、地址池或 `all` 关键字。

### DST\_PORT

与 `SRC_PORT` 类似，`DST_PORT` 是可选的。然而，如果使用了它，则需要先定义 `PROTO_TYPE`。端口号也必须以 `proto` 关键字为前缀。

### TCP\_FLAG|ICMP\_TYPE

如果 `tcp` 被指定为 `PROTO_TYPE`，则可以指定标志，标志是表示连接状态的 TCP 标志的字母。可能的值有：`S`（SYN）、`A`（ACK）、`P`（PSH）、`F`（FIN）、`U`（URG）、`R`（RST）、`C`（CWN）和 `E`（ECN）。

如果 `icmp` 被指定为 `PROTO_TYPE`，则可以指定要匹配的 ICMP 类型。有关可接受类型的详细信息，请参考 [ipf(5)](https://man.freebsd.org/cgi/man.cgi?query=ipf\&sektion=5\&format=html)。

### STATE

如果 `pass` 规则包含 `keep state`，IPF 将在其动态状态表中添加一条记录，并允许后续匹配该连接的数据包。IPF 可以跟踪 TCP、UDP 和 ICMP 会话的状态。任何 IPF 确定属于已激活会话的数据包，即使它属于不同的协议，也会被允许。

在 IPF 中，数据包要通过连接到公共互联网的接口时，首先会与动态状态表进行检查。如果数据包匹配组成活动会话的下一个预期数据包，它将通过防火墙并更新该会话的状态。那些不属于已有活动会话的数据包将与出站规则集进行匹配。从连接到公共互联网的接口传入的数据包首先会与动态状态表进行检查。如果数据包匹配组成活动会话的下一个预期数据包，它将通过防火墙并更新该会话的状态。那些不属于已有活动会话的数据包将与入站规则集进行匹配。

在 `keep state` 后可以添加几个关键字。如果使用，它们将设置控制有状态过滤的各种选项，例如设置连接限制或连接年龄。有关可用选项及其描述的详细信息，请参阅 [ipf(5)](https://man.freebsd.org/cgi/man.cgi?query=ipf\&sektion=5\&format=html)。

## 33.5.3. 示例规则集

本节演示如何创建一个示例规则集，该规则集仅允许与 `pass` 规则匹配的服务，并阻止所有其他服务。

FreeBSD 使用回环接口（**lo0**）和 IP 地址 `127.0.0.1` 进行内部通信。防火墙规则集必须包含允许这些内部使用的包自由流动的规则：

```sh
# 对回环接口没有限制
pass in quick on lo0 all
pass out quick on lo0 all
```

连接到互联网的公共接口用于授权和控制所有出站和入站连接的访问。如果一个或多个接口连接到私人网络，则这些内部接口可能需要规则来允许从局域网起始的包在内部网络之间流动或流向连接到互联网的接口。规则集应分为三个主要部分：任何受信任的内部接口、通过公共接口的出站连接和通过公共接口的入站连接。

这两条规则允许所有流量通过一个名为 **xl0** 的受信任局域网接口：

```ini
# 对内部局域网接口没有限制，用于私有网络
pass out quick on xl0 all
pass in quick on xl0 all
```

公共接口的出站和入站部分的规则应将最常匹配的规则放在前面，将不常匹配的规则放在后面，最后一条规则用于阻止和记录该接口和方向的所有包。

以下规则集定义了公共接口 **dc0** 的出站部分。这些规则保持状态并标识内部系统被授权访问公共互联网的特定服务。所有规则都使用 `quick` 并指定适当的端口号，并在适用时指定目标地址。

```ini
# 面向互联网的接口（出站）
# 匹配来自防火墙内部或后面的会话开始请求，目标为互联网。

# 允许访问公共 DNS 服务器。
# 将 x.x.x.x 替换为 /etc/resolv.conf 中列出的地址。
# 对每个 DNS 服务器重复此规则。
pass out quick on dc0 proto tcp from any to x.x.x.x port = 53 flags S keep state
pass out quick on dc0 proto udp from any to x.x.x.x port = 53 keep state

# 允许访问 ISP 指定的 DHCP 服务器，用于电缆或 DSL 网络。
# 使用第一条规则，然后在日志中检查 DHCP 服务器的 IP 地址。
# 然后取消注释第二条规则，替换 z.z.z.z 为 IP 地址，
# 并注释掉第一条规则。
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

# 允许 HTTP 和 HTTPS
pass out quick on dc0 proto tcp from any to any port = 80 flags S keep state
pass out quick on dc0 proto tcp from any to any port = 443 flags S keep state

# 允许邮件
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

# 允许 NTP
pass out quick on dc0 proto tcp from any to any port = 37 flags S keep state

# 允许 FTP
pass out quick on dc0 proto tcp from any to any port = 21 flags S keep state

# 允许 SSH
pass out quick on dc0 proto tcp from any to any port = 22 flags S keep state

# 允许 ping
pass out quick on dc0 proto icmp from any to any icmp-type 8 keep state

# 阻止并记录其他所有流量
block out log first quick on dc0 all
```

此示例展示了公共接口入站部分的规则集，它首先阻止所有不需要的包。这样可以减少最后一条规则记录的包的数量。

```ini
# 面向互联网的接口（入站）
# 阻止所有来自非可路由或保留地址空间的入站流量
block in quick on dc0 from 192.168.0.0/16 to any    #RFC 1918 私有 IP
block in quick on dc0 from 172.16.0.0/12 to any     #RFC 1918 私有 IP
block in quick on dc0 from 10.0.0.0/8 to any        #RFC 1918 私有 IP
block in quick on dc0 from 127.0.0.0/8 to any       #回环
block in quick on dc0 from 0.0.0.0/8 to any         #回环
block in quick on dc0 from 169.254.0.0/16 to any    #DHCP 自动配置
block in quick on dc0 from 192.0.2.0/24 to any      #为文档保留
block in quick on dc0 from 204.152.64.0/23 to any   #Sun 集群互连
block in quick on dc0 from 224.0.0.0/3 to any       #Class D 和 E 多播

# 阻止碎片和过短的 TCP 包
block in quick on dc0 all with frags
block in quick on dc0 proto tcp all with short

# 阻止源路由包
block in quick on dc0 all with opt lsrr
block in quick on dc0 all with opt ssrr

# 阻止操作系统指纹尝试并记录首次出现
block in log first quick on dc0 proto tcp from any to any flags FUP

# 阻止任何具有特殊选项的包
block in quick on dc0 all with ipopts

# 阻止公共 ping 和 ident
block in quick on dc0 proto icmp all icmp-type 8
block in quick on dc0 proto tcp from any to any port = 113

# 阻止传入的 Netbios 服务
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
```

每当使用 `log first` 选项的规则生成日志消息时，运行 `ipfstat -hio` 来评估该规则被匹配的次数。大量的匹配可能表明系统正遭受攻击。

入站部分的其余规则定义了哪些连接可以从互联网发起。最后一条规则拒绝所有未被前述规则明确允许的连接。

```ini
# 允许来自 ISP DHCP 服务器的流量。将 z.z.z.z 替换为
# 出站部分中使用的相同 IP 地址。
pass in quick on dc0 proto udp from z.z.z.z to any port = 68 keep state

# 允许公共连接到指定的内部 web 服务器
pass in quick on dc0 proto tcp from any to x.x.x.x port = 80 flags S keep state

# 阻止并记录所有剩余流量的首次出现
block in log first quick on dc0 all
```

## 33.5.4. 配置 NAT

要启用 NAT，向 **/etc/rc.conf** 文件中添加以下语句，并指定包含 NAT 规则的文件名：

```ini
gateway_enable="YES"
ipnat_enable="YES"
ipnat_rules="/etc/ipnat.rules"
```

NAT 规则非常灵活，可以根据商业和家庭用户的需要实现多种功能。这里展示的规则语法已简化，以演示常见的使用方式。有关完整的规则语法描述，请参阅 [ipnat(5)](https://man.freebsd.org/cgi/man.cgi?query=ipnat\&sektion=5\&format=html)。

NAT 规则的基本语法如下，其中 `map` 开始规则，*IF* 应替换为外部接口的名称：

```sh
map IF LAN_IP_RANGE -> PUBLIC_ADDRESS
```

*LAN\_IP\_RANGE* 是内部客户端使用的 IP 地址范围，通常是类似 `192.168.1.0/24` 的私有地址范围。*PUBLIC\_ADDRESS* 可以是静态外部 IP 地址，也可以是关键字 `0/32`，表示分配给 *IF* 的 IP 地址。

在 IPF 中，当一个包从 LAN 到达防火墙并且目标是公共地址时，包首先会经过防火墙规则集的出站规则。然后，包会传递到 NAT 规则集，该规则集从上到下读取，匹配的第一个规则会生效。IPF 会根据包的接口名称和源 IP 地址测试每个 NAT 规则。当包的接口名称与 NAT 规则匹配时，包的源 IP 地址（位于私有 LAN 中）会与 *LAN\_IP\_RANGE* 中指定的 IP 地址范围进行匹配。匹配后，包的源 IP 地址会被重写为 *PUBLIC\_ADDRESS* 中指定的公共 IP 地址。IPF 会在其内部 NAT 表中记录一条条目，以便当包从互联网返回时，可以将其映射回原始的私有 IP 地址，然后将包传递到防火墙规则进行进一步处理。

对于拥有大量内部系统或多个子网的网络，将每个私有 IP 地址转换为单个公共 IP 地址会成为资源问题。为了解决这个问题，可以使用两种方法。

第一种方法是为源端口分配一个端口范围。通过添加 `portmap` 关键字，NAT 可以被引导只使用指定范围内的源端口：

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

另外，可以使用 `auto` 关键字，这会告诉 NAT 自行决定可以使用的端口：

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

第二种方法是使用一个公共地址池。当 LAN 地址太多而无法适配单个公共地址时，这种方法非常有用。如果有一块公共 IP 地址池可用，可以将这些公共 IP 地址用作 NAT 选择的 IP 地址池，在数据包外发时进行地址映射。

公共 IP 地址范围可以使用子网掩码或 CIDR 表示法来指定。以下两条规则是等效的：

```sh
map dc0 192.168.1.0/24 -> 204.134.75.0/255.255.255.0
map dc0 192.168.1.0/24 -> 204.134.75.0/24
```

一种常见的做法是将一个公开可访问的 Web 服务器或邮件服务器隔离到内部网络段。这些服务器的流量仍然需要经过 NAT，但需要端口重定向，将入站流量引导到正确的服务器。例如，将内部地址为 `10.0.10.25` 的 Web 服务器映射到其公共 IP 地址 `20.20.20.5`，可以使用以下规则：

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

如果这是唯一的 Web 服务器，这条规则也能工作，因为它将所有外部的 HTTP 请求重定向到 `10.0.10.25`：

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

IPF 内置了一个 FTP 代理，可以与 NAT 一起使用。它监视所有出站流量的主动或被动 FTP 连接请求，并动态创建临时过滤规则，包含 FTP 数据通道使用的端口号。这消除了为 FTP 连接打开大量高端端口的需要。

在此示例中，第一条规则为来自内部 LAN 的出站 FTP 流量调用代理。第二条规则将 FTP 流量从防火墙传递到互联网，第三条规则处理所有非 FTP 流量：

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

FTP `map` 规则应位于 NAT 规则之前，这样当包与 FTP 规则匹配时，FTP 代理会创建临时的过滤规则，让 FTP 会话数据包通过并经过 NAT。所有非 FTP 的 LAN 包不会匹配 FTP 规则，但如果它们匹配第三条规则，将会经过 NAT。

如果没有 FTP 代理，则需要使用以下防火墙规则。请注意，如果没有代理，则需要允许所有高于 `1024` 的端口：

```ini
# 允许出站 LAN PC 客户端 FTP 到公共互联网
# 主动模式和被动模式
pass out quick on rl0 proto tcp from any to any port = 21 flags S keep state

# 允许出站被动模式数据通道的高端端口
pass out quick on rl0 proto tcp from any to any port > 1024 flags S keep state

# 主动模式允许 FTP 服务器的 FTP 数据通道进入
pass in quick on rl0 proto tcp from any to any port = 20 flags S keep state
```

每次编辑包含 NAT 规则的文件时，运行 `ipnat` 使用 `-CF` 选项来删除当前的 NAT 规则并刷新动态转换表的内容。包括 `-f` 并指定要加载的 NAT 规则文件名：

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

要显示 NAT 统计信息：

```sh
# ipnat -s
```

要列出 NAT 表的当前映射：

```sh
# ipnat -l
```

要开启详细模式并显示与规则处理、活动规则和表条目相关的信息：

```sh
# ipnat -v
```

## 33.5.5. 查看 IPF 统计信息

IPF 包含 [ipfstat(8)](https://man.freebsd.org/cgi/man.cgi?query=ipfstat\&sektion=8\&format=html) 命令，可用于检索和显示统计信息，这些统计信息是在数据包经过防火墙时与规则匹配时收集的。统计信息自上次启动防火墙或自上次通过 `ipf -Z` 将其重置为零以来积累。

默认的 `ipfstat` 输出如下所示：

```sh
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` 表示出站时，命令将检索并显示当前由内核安装并使用的适当过滤规则列表。如果还希望看到规则编号，请添加 `-n`。例如，`ipfstat -on` 会显示带有规则编号的出站规则表：

```sh
@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
```

添加 `-h` 可以在每条规则前添加规则匹配的次数。例如，`ipfstat -oh` 会显示出站内部规则表，并在每条规则前加上其使用次数：

```sh
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
```

要以类似 [top(1)](https://man.freebsd.org/cgi/man.cgi?query=top\&sektion=1\&format=html) 的格式显示状态表，使用 `ipfstat -t`。当防火墙遭受攻击时，此选项提供了识别和查看攻击数据包的能力。可选的子标志提供了实时监控目标或源 IP、Port 或协议的能力。有关详细信息，请参阅 [ipfstat(8)](https://man.freebsd.org/cgi/man.cgi?query=ipfstat\&sektion=8\&format=html)。

## 33.5.6. IPF 日志记录

IPF 提供了 `ipmon`，可用于以人类可读的格式记录防火墙的日志信息。它要求在自定义内核中首先添加 `options IPFILTER_LOG`，按照 [配置 FreeBSD 内核](https://docs.freebsd.org/en/books/handbook/kernelconfig/#kernelconfig) 中的说明进行操作。

此命令通常以守护进程模式运行，以便提供一个持续的系统日志文件，以便查看过去的事件。由于 FreeBSD 内置了 [syslogd(8)](https://man.freebsd.org/cgi/man.cgi?query=syslogd\&sektion=8\&format=html) 系统，自动轮换系统日志，因此默认的 **rc.conf** `ipmon_flags` 语句使用 `-Ds`：

```sh
ipmon_flags="-Ds" # D = 以守护进程模式启动
                  # s = 记录到 syslog
                  # v = 记录 TCP 窗口、ack、seq
                  # n = 将 IP 和端口映射为名称
```

日志记录提供了事后查看信息的能力，例如哪些数据包被丢弃，它们来自哪里，以及它们的目的地。此信息在追踪攻击者时非常有用。

待在 **rc.conf** 中启用日志记录功能，并通过 `service ipmon start` 启动，IPF 只会记录包含 `log` 关键字的规则。防火墙管理员决定应该记录规则集中哪些规则，通常只有拒绝规则会被记录。习惯上，将 `log` 关键字包含在规则集中的最后一条规则中，这样就可以看到所有没有匹配任何规则的数据包。

默认情况下，`ipmon -Ds` 模式使用 `local0` 作为日志记录设施。以下日志级别可用于进一步区分记录的数据：

```sh
LOG_INFO - 使用 "log" 关键字记录的包，作为动作而不是 pass 或 block。
LOG_NOTICE - 记录的同时也被通过的数据包
LOG_WARNING - 记录的同时也被阻止的数据包
LOG_ERR - 已记录且因标题不完整而被认为是短包的数据包
```

要将 IPF 设置为将所有数据记录到 **/var/log/ipfilter.log**，首先创建一个空文件：

```sh
# touch /var/log/ipfilter.log
```

然后，为了将所有记录的消息写入指定的文件，向 **/etc/syslog.conf** 中添加以下语句：

```sh
local0.* /var/log/ipfilter.log
```

要激活更改并指示 [syslogd(8)](https://man.freebsd.org/cgi/man.cgi?query=syslogd\&sektion=8\&format=html) 读取修改后的 **/etc/syslog.conf**，运行 `service syslogd reload`。

不要忘记编辑 **/etc/newsyslog.conf** 来轮换新日志文件。

由 `ipmon` 生成的消息由空格分隔的数据字段组成。所有消息的公共字段如下：

1. 数据包接收的日期。
2. 数据包接收的时间，格式为 HH:MM:SS.F，表示小时、分钟、秒和秒的小数部分。
3. 处理数据包的接口名称。
4. 规则组和规则号，格式为 `@0:17`。
5. 动作：`p` 代表通过，`b` 代表阻止，`S` 代表短包，`n` 代表未匹配任何规则，`L` 代表日志规则。
6. 地址以三个字段表示：源地址和端口由逗号分隔，后跟 → 符号，再加上目标地址和端口。例如：`209.53.17.22,80 → 198.73.220.17,1722`。
7. `PR` 后跟协议名称或编号。例如：`PR tcp`。
8. `len` 后跟数据包的头部长度和总长度。例如：`len 20 40`。

如果数据包是 TCP 包，还会有一个额外的字段，前面带有连字符，后面是与设置的任何标志对应的字母。有关字母和标志的列表，请参阅 [ipf(5)](https://man.freebsd.org/cgi/man.cgi?query=ipf\&sektion=5\&format=html)。

如果数据包是 ICMP 包，消息的末尾会有两个字段，第一个始终是 "icmp"，下一个是 ICMP 消息和子消息类型，用斜杠分隔。例如：`icmp 3/3` 表示端口不可达消息。
