# 18.6 blocklistd

blocklistd 是一个守护进程：它通过套接字监听，接收其他守护进程发送的连接成功或失败通知，主要用于封锁发起过多连接尝试的来源地址。典型的例子是互联网上运行的 SSH 收到大量来自机器人或脚本的请求，这些请求试图猜测密码来获取访问权限。使用 blocklistd 后，守护进程可通知防火墙，在单个来源地址尝试多次后创建过滤规则，以封锁过多的连接尝试。

blocklistd 最初在 NetBSD 上开发，并于 NetBSD 7 发布，最初名为 blacklistd，后更名为 blocklistd。自 FreeBSD 11 起从 NetBSD 引入。

本节介绍了 blocklistd 的使用方法，并提供了使用示例。读者应熟悉基本的防火墙概念。

## 启用 blocklistd

要在系统启动时启用该守护进程，执行以下命令：

```sh
# service blocklistd enable
```

要手动启动服务，运行以下命令：

```sh
# service blocklistd start
```

## 创建 blocklistd 规则集

blocklistd 规则集位于 **/etc/blocklistd.conf** 文件，每行填写一条规则。每条规则由空格或制表符分隔的七个字段组成。规则分为 `local` 和 `remote`，分别作用于 blocklistd 所在的本机和外部来源。

### 本地规则

默认配置文件 **/etc/blocklistd.conf** 内容如下：

```ini
# Blocklist rule
# adr/mask:port	type	proto	owner		name	nfail	duration
[local]
ssh		stream	*	*		*	3	24h
ftp		stream	*	*		*	3	24h
smtp		stream	*	*		*	3	24h
submission	stream	*	*		*	3	24h
#6161		stream	tcp6	christos	*	2	10m
*		*	*	*		*	3	60

# adr/mask:port	type	proto	owner		name	nfail	duration
[remote]
#129.168.0.0/16	*	*	*		=	*	*
#[2001:db8::]/32:ssh	*	*	*		=	*	*
#6161		=	=	=		=/24	=	=
#*		stream	tcp	*		=	=	=
```

文件 **/etc/blocklistd.conf** 中的默认本地规则如下所示：

```ini
[local]
ssh		stream	*	*		*	3	24h
ftp		stream	*	*		*	3	24h
smtp		stream	*	*		*	3	24h
submission	stream	*	*		*	3	24h
#6161		stream	tcp6	christos	*	2	10m
*		*	*	*		*	3	60
```

`[local]` 段后的所有规则均视为本地规则（此为默认设置），适用于本机。遇到 `[remote]` 段后，后续规则将作为远程规则处理。通配符由星号 (`*`) 表示，匹配该字段中的任意内容。

七个字段共同定义一条规则，字段之间以制表符或空格分隔：

* `adr/mask:port`、`type`、`proto`、`owner`：前四个字段用于标识需列入封禁列表的流量。
  * `adr/mask:port` 字段：定义地址。在本地规则中，该字段是网络端口。

    `adr/mask:port` 地址字段的语法：

    ```sh
    [地址|接口][/掩码][:端口]
    ```

    `adr/mask:port` 地址字段可按数字格式指定为 IPv4 或 IPv6（以方括号表示），也可使用接口名称，如 `em0`。
  * `type` 字段：定义套接字类型。TCP 套接字为 `stream` 类型，而 UDP 则用 `dgram` 表示。因为 SSH 基于 TCP 协议，上例使用的是 `stream`。
  * `proto` 字段：定义协议。可用协议包括 `tcp`、`udp`、`tcp6`、`udp6` 或数值形式的协议号。除非有必要按特定协议区分流量，否则通常如示例所示使用通配符来匹配所有协议。
  * `owner` 字段：定义报告事件的守护进程的有效用户或属主。守护进程报告事件时，以此字段标识其身份。这里可以使用用户名或 UID，也可以使用通配符。
* `name`、`nfail`、`duration`：后三个字段定义 blocklistd 的行为。
  * `name` 字段：定义数据包过滤规则名称，标志着规则行为部分的开始。 如果需要单独的封锁列表，可以在此字段中使用锚点名称。在其他情况下，使用通配符即可。名称以连字符（`-`）开头时，表示应使用带有默认规则名称的锚点。修改后的示例如下：

    ```sh
    ssh             stream  *       *               -ssh       3       24h
    ```

    **在此规则下**，新的封禁规则将添加到锚点 `blocklistd-ssh` 中。

    若因某 IP 违反某条规则而需封锁整个子网，可以在规则名称中使用 **/**。这样名称中 **/** 后面的部分会当作掩码，应用到规则中指定的地址。例如，以下这条规则会封锁与指定地址相邻的所有 **/24** 网段的地址。

    ```sh
    22              stream  tcp       *               */24    3       24h
    ```

    > **注意**
    >
    > 这里需指定正确的协议。IPv4 和 IPv6 对 **/24** 的处理不同，因此在此规则的第三个字段中不能使用 `*`。

    此规则定义：若该网络中任何主机行为不当，则整个网络中的其他主机均将受到封禁。
  * `nfail` 字段：设置需多少次登录失败方可将远程 IP 列入封禁列表。若在此位置使用通配符，则永不触发封禁。在上例中，定义了 3 次登录失败的限制，即在 SSH 上登录失败 3 次后，该 IP 被封禁。
* `duration` 字段：指定主机列入封禁列表的时间。默认单位为秒，亦可指定 `m`、`h`、`d` 等后缀，分别表示分钟、小时和天。

完整的示例规则 `ssh stream * * * 3 24h` 表示：SSH 验证失败三次后，该主机将生成一条新的 PF 封锁规则。规则匹配时，首先按顺序检查 `local` 规则，从最具体（匹配条件最严格、范围最小的规则）到最不具体（匹配条件宽泛、范围大的规则）。匹配成功后，会应用 `remote` 规则，并由匹配的 `remote` 规则修改字段 `name`、`nfail` 和 `duration`。

### 远程规则

`remote` 规则用于指定 blocklistd 如何根据当前评估的远程主机调整其行为。`remote` 规则中的每个字段与 `local` 规则相同，唯一的区别在于 blocklistd 使用这些字段的方式不同。以下用示例规则说明：

```ini
# adr/mask:port	type	proto	owner		name	nfail	duration
[remote]
#129.168.0.0/16	*	*	*		=	*	*
#[2001:db8::]/32:ssh	*	*	*		=	*	*
#6161		=	=	=		=/24	=	=
#*		stream	tcp	*		=	=	=
```

地址字段可为 IP 地址（IPv4 或 IPv6）、端口，或二者兼有。这样可以为特定的远程地址范围设置特殊规则（如本例所示）。套接字类型、协议和属主字段的解释与 `local` 规则完全相同。

name 字段有所不同：在 `remote` 规则中，等号（`=`）表示 blocklistd 使用匹配的 `local` 规则中的值。也就是说，防火墙规则条目会沿用匹配的 `local` 规则中的值，并加上 **/16** 前缀（掩码为 **255.255.0.0**）。该地址范围内的任一连接被封锁时，整个子网均会受到影响。此处也可使用 PF 锚点名称，此时 blocklistd 会将该地址块的规则添加到该锚点下。若使用通配符，则使用默认表格。

`nfail` 列可为某个地址定义自定义失败次数。这在设置特定规则的例外时很有用，比如给予某些用户较宽松的登录尝试机会。第六个字段使用星号时，封禁功能即告禁用。

与来自本地网络（如办公室）的登录尝试相比，`remote` 规则可以施加更严格的限制。

## 配置 blocklistd 客户端

在 FreeBSD 中，有一些软件包可以利用 blocklistd 的功能。最常用的是 ftpd(8) 和 sshd(8)，可阻止过多的连接尝试。

要为 SSH 守护进程启用 blocklistd，在 **/etc/ssh/sshd\_config** 文件中添加以下行：

```ini
UseBlocklist yes
```

然后重新启动 `sshd` 使更改生效。

对于 Port ftp/freebsd-ftpd，可通过 `-B` 参数启用 blocklist 功能。既可以在 **/etc/inetd.conf** 中设置该参数，也可以像下面这样作为标志写入 **/etc/rc.conf**：

```ini
ftpd_flags="-B"
```

以上即为让这些程序与 blocklistd 通信所需的全部配置。

## 启用 PF 防火墙

blocklistd 只是一个监控的守护进程，本身并无封禁能力，需依赖防火墙才能封禁。可以参考其他章节安装并启用 PF 防火墙。示例中使用了 PF，但 FreeBSD 上其他可用防火墙也可与 blocklistd 配合使用。

blocklistd 会将所有封禁规则放入 **/etc/pf.conf** 文件中的 PF 锚点 `blocklistd` 下。 因此，需要将以下内容写入 PF 防火墙配置文件 **/etc/pf.conf** 中：

```ini
ext_if="em0"        # 需替换为实际的网卡接口
table <blocklistd> persist
anchor "blocklistd/*"
block drop in log quick on $ext_if from <blocklistd> to any
pass out all
```

随后启用 PF 防火墙。

## 管理 blocklistd

blocklistd 提供了管理工具 blocklistctl(8)，用于查看 blocklistd.conf(5) 中定义的规则所封禁的地址和网络。

要查看当前封禁的主机列表，可使用以下命令：

```sh
# blocklistctl dump -b
rulename      		address/ma:port id      nfail   last access
blocklistd	 192.168.179.32/32:22   OK      3/3     2026/04/16 12:33:43
```

此示例显示，从地址范围 **192.168.179.32/32** 发起的端口 `22` 登录尝试已达到 3 次上限。由于 SSH 允许客户端在单条 TCP 连接上进行多次登录尝试，实际尝试次数可能略超配置的 `nfail` 值。输出中的“last access”列显示了最后一次连接尝试的时间。

> **技巧**
>
> 测试时可先将配置文件 **/etc/blocklistd.conf** 保存到其他位置，再新建空的配置文件，写入 `* * * * * 3 180`（对任意端口的任意连接，超过三次即封禁 180 秒）。另外，**blocklistd 不会中断当前正在进行的连接，因此测试前需断开相关连接。** 切勿在生产环境执行此测试！

要查看该主机在封锁列表上剩余的时间，可在之前的命令中添加 `-r` 参数。

```sh
# blocklistctl dump -br
rulename			  address/ma:port id      nfail   remaining time
blocklistd		192.168.179.32/32:22  OK      3/3     2m13
```

在此示例中，距离该主机解除封锁还有 2 分 13 秒。

## 从 blocklistd 中移除主机

有时需在剩余时间重置前将主机从封锁列表中移出。但 blocklistd 本身并未提供此功能。

不过，可以使用 pfctl 命令从 PF 表中移除该地址。每个封禁端口在 **/etc/pf.conf** 的 blocklistd 锚点下都有一个子锚点。例如，用于封锁端口 22 的子锚点是 **blocklistd/22**。该子锚点内有一个包含所有封禁地址的表，表名为 port 加上端口号，在此例中为 `port22`。

有了这些信息，就可以使用 pfctl(8) 来显示列出的所有地址：

```sh
# pfctl -a blocklistd/22 -t port22 -T show
192.168.179.1
```

从列表中识别出需解除封禁的地址后，使用以下命令将其从列表中移除：

```sh
# pfctl -a blocklistd/22 -t port22 -T delete 192.168.179.1
```

blocklistd 无法感知 PF 中的更改，即使已从 PF 中移除该地址，该地址仍会出现在 blocklistctl 列表中。blocklistd 数据库中的该条目最终会过期，并从输出中移除。

若该主机再次匹配 blocklistd 中的某条封禁规则，则此主机的对应条目将再次被添加。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://book.bsdcn.org/di-18-zhang-fang-huo-qiang/di-18.6-jie-blocklistd.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
