# 15.2 Packet Filter（PF）

PF（Packet Filter，包过滤器）是一款源自 OpenBSD 的防火墙，提供了丰富功能，包括 ALTQ（Alternate Queuing，交替队列）等。ALTQ 是 PF 的流量队列管理机制，用于实现服务质量（QoS）控制。作为一种状态防火墙（stateful firewall），PF 通过跟踪网络连接状态实现精细化的访问控制。

## 启用 PF

在启用 PF 之前，需要先加载内核模块、准备配置文件并启动相关服务，按以下步骤依次操作。

```sh
# kldload pf                  # 加载 pf 内核模块，使系统能够识别和使用 PF 防火墙
# cp /usr/share/examples/pf/pf.conf /etc/  # 复制示例文件作为默认配置规则集，否则 pf 无法启动
# service pf enable           # 设置 pf 在系统启动时自动启动
# service pf start            # 启动 pf 服务
```

* ① 如果不执行上述操作，则会提示 `pfctl: /dev/pf: No such file or directory`，此时可以重启系统后再执行 `service pf start`。
* ② 否则会提示如下信息：

```sh
/etc/rc.d/pf: WARNING: /etc/pf.conf is not readable.
```

PF 的管理命令为 `pfctl`。

## 相关文件结构

```sh
/
├── etc
│   └── pf.conf
└── usr
    └── share
        └── examples
            └── pf
                └── pf.conf
```

## 一次性操作命令

PF 提供了丰富的一次性操作命令，可用于管理防火墙的运行状态。

1. **启动 PF 防火墙**

```sh
pfctl -e
```

作用：启用 PF，相当于执行 `service pf start`。

2. **禁用 PF 防火墙规则**

```sh
pfctl -d
```

作用：停用 PF 防火墙，相当于执行 `service pf stop`。

3. **加载规则集文件中的规则**

```sh
pfctl -f /etc/pf.conf
```

作用：将 `/etc/pf.conf` 文件中的规则加载到 PF 中。

4. **解析规则但不加载**

```sh
pfctl -nf /etc/pf.conf
```

作用：检查规则语法是否正确，但不实际应用。

可选参数：

* `-N`：只解析 NAT 规则
* `-R`：只加载过滤规则
* `-A`：只加载队列规则
* `-O`：只加载选项规则

5. **查看 PF 的对象信息**

```sh
pfctl -s all
```

作用：显示 PF 的所有对象信息。

可用替换 `all` 的选项及其含义：

* `nat`：查看 NAT 规则
* `queue`：查看队列规则
* `rules`：查看过滤规则
* `anchors`：查看 anchor
* `states`：查看状态表
* `sources`：查看源跟踪信息
* `info`：查看 PF 运行信息
* `running`：查看运行状态
* `labels`：查看规则标签
* `timeouts`：查看超时设置
* `memory`：查看内存使用
* `tables`：查看表
* `osfp`：查看操作系统指纹
* `interfaces`：查看接口信息

6. **删除 PF 的所有规则**

```sh
pfctl -F all
```

作用：删除 PF 中所有已加载的规则和状态信息。

如果想查看特定规则，可以用 `nat`、`queue`、`rules`、`states`、`sources`、`info`、`tables`、`osfp` 替换 `all`。

## 永久性的规则集

上述操作仅针对 PF 的运行状态和规则加载，并未对规则进行管理，因此仍需修改规则集文件，常用示例如下。

### 规则集文件

以下是一些常用的 PF 规则示例，可根据实际需求进行选择和组合。注意：示例中使用的 IP 地址 192.168.1.184 为演示用途，请根据实际情况修改。

1. **整理所有输入的数据**

```ini
scrub in all
```

作用：对所有进入的数据包进行规范化处理（如重组分片、清理异常标志等），提高规则匹配的可靠性。scrub 可以防御某些利用分片或异常标志的攻击。

2. **阻止所有访问**

```ini
block all
```

作用：默认阻止所有访问。

说明：`block` 表示动作，`all` 表示从任何源到任何目标的所有流量。

3. **允许回环接口访问**

```ini
pass quick on lo0 all
```

作用：允许本地回环接口 `lo0` 的所有访问，但不影响外部访问。 说明：`quick` 表示匹配该规则后立即停止，不再继续匹配后续规则。

4. **允许 TCP 协议访问 80 端口**

```ini
pass in quick proto tcp from any to 192.168.1.184 port 80
```

作用：允许任意设备通过 TCP 协议访问本机的 80 端口（通常用于 HTTP 服务）。

5. **允许本机对外发送 80 端口的响应**

```ini
pass out quick proto tcp from 192.168.1.184 port 80 to any
```

作用：允许本机向任意设备发送 TCP 80 端口的响应数据。

6. **端口转发 80 → 8080**

```ini
rdr pass on em0 inet proto tcp from any to 192.168.1.184 port 80 -> 192.168.1.166 port 8080
```

作用：将访问本机 80 端口的 TCP 流量转发到内网地址 `192.168.1.166` 的 8080 端口。 说明：`rdr` 表示重定向（端口转发），`on em0` 指定接口，`inet` 指定 IPv4 协议。

7. **允许本机与外部设备进行 ping**

```ini
pass quick inet proto icmp all icmp-type 8 code 0
```

作用：允许 ICMP 回显请求（ping）通过，`icmp-type 8` 表示请求，`code 0` 表示标准返回码。

8. **允许 traceroute 的 ICMP 流量**

```ini
pass out quick inet proto icmp from 192.168.1.184 to any icmp-type 11 code 0
```

作用：允许本机发送 ICMP“时间超时”报文，用于执行 `traceroute`。

9. **允许 traceroute 的 UDP 流量**

```ini
pass out quick proto udp from 192.168.1.184 to any port 33434 >< 34500
```

作用：允许本机通过 UDP 协议发送端口范围 `33434-34500` 的数据，用于 `traceroute`。

说明：`><` 表示端口范围。

### 完整规则集示例

以下是一个完整的规则集文件示例，可直接复制到 `/etc/pf.conf` 中使用。转发规则应置于过滤规则之前以确保生效。

1. **对数据包进行标准化**

```ini
scrub in all
```

2. **端口转发规则**

```ini
rdr pass on em0 inet proto tcp from any to 192.168.1.184 port 8080 -> 192.168.1.184 port 80
```

作用：将访问本机 8080 端口的 TCP 流量转发到 80 端口。

3. **阻止所有流量并允许回环接口**

```ini
block all
pass quick on lo0 all
```

作用：默认阻止所有流量，但允许本机回环接口 `lo0` 的所有访问。

说明：`quick` 表示匹配该规则后立即停止，不再继续匹配后续规则。

4. **允许外部访问服务器特定端口**

```ini
pass in quick proto tcp from any to 192.168.1.184 port { 22, 80, 443, 4200, 10000 }
```

作用：允许任意设备访问服务器的 SSH（22）、HTTP（80）、HTTPS（443）、4200、10000 端口。

5. **允许服务器访问外部设备的特定端口**

```ini
pass out quick proto tcp from 192.168.1.184 port { 22, 80, 443, 4200, 10000 } to any
```

作用：允许服务器向外部发送数据到指定端口。

6. **服务器访问任意设备的 HTTP/HTTPS 并保持状态**

```ini
pass out quick proto tcp from 192.168.1.184 to any port { 80, 443 } keep state
```

作用：允许服务器访问外部 HTTP/HTTPS 服务，同时跟踪连接状态。

7. **服务器访问 DNS 服务器**

```ini
pass out quick proto udp from any to any port 53 keep state
```

作用：允许服务器通过 UDP 端口 53 查询 DNS，并保持状态。

8. **服务器访问 DHCP 服务器**

```ini
pass out quick proto udp from any to any port 67 keep state
```

作用：允许服务器通过 UDP 端口 67 与 DHCP 服务器通信，并保持状态。

9. **允许服务器发送 ICMP 请求**

```ini
pass quick inet proto icmp all icmp-type 8 code 0
```

作用：允许发送 ICMP 回显请求（ping）。

10. **允许 ICMP 超时消息（traceroute）**

```ini
pass out quick inet proto icmp from 192.168.1.184 to any icmp-type 11 code 0
```

作用：允许发送 ICMP“时间超时”报文，用于 traceroute。

11. **允许服务器访问 UDP 端口范围 33434 至 34500**

```ini
pass out quick proto udp from 192.168.1.184 to any port 33434 >< 34500
```

作用：允许服务器通过 UDP 协议发送端口范围 `33434-34500` 的数据，用于 traceroute。

12. **加载规则文件使其生效**

规则编辑完成后，需要执行以下命令使其生效。

```sh
pfctl -Fa -f /etc/pf.conf
```

作用：

* `-F a`：清除所有已加载的规则和状态
* `-f /etc/pf.conf`：加载指定规则文件

## 课后习题

1. 查找 FreeBSD 源码中的 PF 实现，选取其中一个核心数据结构（如状态表或规则匹配逻辑），重构最小化实现。
2. 选取 PF 的 anchor 机制，为其设计并实现一个分层的规则管理方案，验证其功能。
3. 修改 PF 的默认行为（如禁用 `scrub` 或改变默认块策略），验证其行为变化。
