# 18.5 Fail2Ban（基于 IPFW、PF 与 IPF）

网络安全实践中，暴力破解（brute-force attack）是针对身份认证系统的常见威胁。

Fail2Ban 是一款基于日志的入侵防御系统（Intrusion Prevention System, IPS），通过动态更新防火墙规则实现自适应访问控制，为服务器提供实时防护。

根据 [官方说明](https://github.com/fail2ban/fail2ban)，Fail2Ban 通过更新系统防火墙规则，拒绝多次身份验证失败的 IP 地址发起的新连接（详见 [Fail2Ban 工作原理](https://github.com/fail2ban/fail2ban/wiki/How-fail2ban-works)）。Fail2Ban 几乎完全由 Python 实现（Python 代码占比约 96%）。本节适配了 FreeBSD 常见的三种防火墙（IPFW、PF 与 IPF），无需同时配置所有防火墙，只需选择其中一种即可。

## 安装 Fail2Ban

Fail2Ban 的安装过程如下，用户可选择 pkg 或 Ports 安装。

* 使用 pkg 安装：

```sh
# pkg install security/py-fail2ban
```

* 或者使用 Ports 安装：

```sh
# cd /usr/ports/security/py-fail2ban/
# make install clean
```

安装完成后，需启用服务使其开机自动启动。

* 启用 fail2ban 服务：

```sh
# service fail2ban enable
```

## 查看 Fail2Ban 安装后的说明

安装完成后，可通过以下命令查看软件包提供的配置说明。

```sh
# pkg info -D security/py-fail2ban
```

## Fail2Ban 配置解读

下文将详细解读 Fail2Ban 的配置。

> **注意**
>
> 此处的 `jail` 并非 BSD 系统中的 jail（容器）。在 Fail2Ban 中，jail 是指对特定服务的监控和封禁配置单元，每个 jail 对应一个需要防护的服务（如 SSH、FTP 等）。

Fail2Ban 的工作流程分为两个核心部分：filter（过滤器）负责解析日志，识别失败的登录尝试；action（动作）负责调用防火墙规则执行封禁操作。

涉及的文件结构：

```sh
/usr/local/etc/fail2ban/
├── jail.conf
├── filter.d/
│   ├── bsd-sendmail.conf
│   ├── bsd-sshd.conf
│   ├── bsdftp.conf
│   ├── courier-auth.conf
│   ├── mssql-auth.conf
│   ├── murmur.conf
│   ├── mysqld-auth.conf
│   ├── nginx-botsearch.conf
│   ├── slapd.conf
│   ├── softethervpn.conf
│   ├── sogo-auth.conf
│   └── sshd.conf
├── action.d/
│   ├── bsd-ipfw.conf
│   ├── ipfw.conf
│   └── ipfilter.conf
└── jail.d/
    └── sshd.conf
```

配置文件示例为 **/usr/local/etc/fail2ban/jail.conf** 文件（请勿直接编辑，参见上文说明），下文仅列出本书所需内容：

```ini
# DEFAULT 是全局定义的配置，之后每个 jail 都可以单独覆盖这些设置。

[DEFAULT]

# "ignoreip" 可以是单个 IP 地址、CIDR 子网或 DNS 主机名列表。Fail2Ban
# 不会封禁列表中匹配的主机。多个地址可以使用空格或逗号分隔
# "ignoreip" 即白名单
# ignoreip = 127.0.0.1/8 ::1

# "maxretry" 默认重试次数
maxretry = 5

# 如果主机在过去的 "findtime" 秒内产生了 "maxretry" 次失败，主机将被封禁。
# 即定义封禁频率。
findtime = 10m

# "bantime" 表示主机被封禁的时间，可用秒为单位的整数或时间缩写格式（m 表示分钟，h 表示小时，d 表示天，w 表示周，mo 表示月，y 表示年）
# 即多长时间后可重试。
bantime  = 10m

[sshd]
# enabled = true
#
# 参见 jail.conf(5)

# "filter" 定义了 jail 所使用的过滤器。 ①
# 在默认情况下，jail 的名称与其过滤器名称相匹配。
#
filter = %(__name__)s[mode=%(mode)s]
# 例如：
# filter = sshd[mode=aggressive]
#
# 操作快捷方式。用于定义 action 参数。

# Fail2Ban 还需要防火墙来执行封禁动作。
# 默认封禁操作（例如 iptables、iptables-new、iptables-multiport、shorewall 等）。 ②
# 它用于定义 action_* 变量，可以在 jail.local 文件的全局或特定部分中覆盖。
# banaction = iptables-multiport
# banaction_allports = iptables-allports
```

* ① 列出 Fail2Ban 的过滤器选项：

```sh
# ls /usr/local/etc/fail2ban/filter.d/
……省略一部分输出……
bsd-sendmail.conf		mssql-auth.conf			slapd.conf
bsd-sshd.conf			murmur.conf			softethervpn.conf
bsdftp.conf			mysqld-auth.conf		sogo-auth.conf
courier-auth.conf		nginx-botsearch.conf		sshd.conf
```

需要注意，以 `bsd-` 开头的文件（如 `bsd-sshd.conf`）是 FreeBSD Port 维护者为适配特定环境提供的变体，但本节应直接使用 `sshd.conf`，它与 Fail2Ban 标准配置的兼容性更好。

* ② 查看 Fail2Ban 支持的防火墙：

```sh
# ls /usr/local/etc/fail2ban/action.d/
bsd-ipfw.conf					ipfw.conf				ipfilter.conf				pf.conf
……省略其他防火墙……
```

## Fail2Ban 封禁配置

创建并编辑文件 **/usr/local/etc/fail2ban/jail.d/sshd.conf**，写入如下内容：

```ini
[DEFAULT]
ignoreip=192.168.0.0/24
maxretry = 3
findtime = 10m
bantime = 8h

[sshd]
enabled=true
filter=sshd
action=bsd-ipfw
```

配置解释：

* 白名单，表示不会被封禁的 IP 段。**192.168.0.0/24** 表示从 **192.168.0.0** 到 **192.168.0.255**。
* `bsd-ipfw` 是示例防火墙，可自行选择，参见下文。

在 FreeBSD 中，Fail2Ban 提供了两个 IPFW 相关的动作配置：`ipfw` 和 `bsd-ipfw`。其中 `bsd-ipfw` 是专门为 FreeBSD 优化的版本，与系统的集成度更高。

> **警告**
>
> 如果使用 IPFW 防火墙，必须选择 `bsd-ipfw`，而不能使用 `ipfw`，否则无法生效。

## 配置防火墙

完成 Fail2Ban 配置后，还需配置对应防火墙，然后启动服务。

启动 Fail2Ban 服务：

```sh
# service fail2ban start
```

### IPFW

IPFW 是 FreeBSD 内置的防火墙。Fail2Ban 配置同上。

#### 配置开机启动服务

启用防火墙，实现开机自动启动：

```sh
# sysrc firewall_enable="YES"
```

> **警告**
>
> 请勿立即执行 `start` 命令，否则可能导致无法通过 SSH 连接。

#### 修改 IPFW 默认规则

IPFW 规则的默认策略是“默认拒绝”，即规则 65535 会阻断所有未匹配的流量。为避免配置过程中访问被阻断，可先将其修改为“默认允许”：

```sh
# echo 'net.inet.ip.fw.default_to_accept=1' >> /boot/loader.conf   # 设置防火墙默认策略为接受，开机生效
# reboot # 重启生效
```

* 显示当前 IPFW 防火墙规则列表：

```sh
# ipfw list
……省略一部分……
65535 allow ip from any to any
```

#### 参考文献

* FreeBSD Project. ipfw(8) - IP firewall and traffic shaper control program\[EB/OL]. \[2026-03-26]. <https://man.freebsd.org/cgi/man.cgi?query=ipfw&sektion=8>. FreeBSD 官方防火墙管理工具参考手册，提供了 IPFW 完整命令说明。
* dapeng.li. fail2ban\[EB/OL]. \[2026-03-26]. <https://dapeng.li/learning/fail2ban/d4.html>. 本节架构基于此，是 Fail2Ban 与 FreeBSD 的集成方案。
* FreeBSD Project. Chapter 33. Firewalls\[EB/OL]. \[2026-04-14]. <https://docs.freebsd.org/en/books/handbook/firewalls/>. FreeBSD Handbook 防火墙章节，介绍了 PF、IPFW 和 IPFILTER 的基本配置。
* FreeBSD Foundation. FreeBSD's Firewall Feast\[EB/OL]. \[2026-04-14]. <https://freebsdfoundation.org/wp-content/uploads/2017/06/FreeBSDs-Firewall-Feast.pdf>. FreeBSD Foundation 发布的防火墙对比技术文章。

### PF

PF（Packet Filter，包过滤器）是源自 OpenBSD 的防火墙，其配置如下。

在 PF 中，table 用于存储需要封禁的 IP 地址列表，anchor 用于组织和管理特定的规则集。Fail2Ban 向 PF 的 table 中添加 IP 地址，再通过 anchor 应用相应规则来封禁。

#### Fail2Ban 配置文件

将上方配置文件 **/usr/local/etc/fail2ban/jail.d/sshd.conf** 中 `action=bsd-ipfw` 改为 `action=pf[port={22 23}, name=ssh]`，其他配置项无需修改。

#### 修改 PF 配置文件

需要先准备 PF 的配置文件，涉及的目录结构：

```sh
/
├── etc
│   ├── pf.conf
│   ├── ipf.rules
│   └── ipnat.rules
├── usr
│   └── share
│       └── examples
│           ├── pf
│           │   └── pf.conf
│           └── ipfilter
│               ├── ipf.conf.sample
│               └── ipnat.conf.sample
├── var
│   └── log
│       ├── auth.log
│       └── fail2ban.log
└── boot
    └── loader.conf.local
```

* 要让 PF 正常启动，需将示例 PF 配置文件复制到 **/etc** 目录以便修改和使用：

```sh
# cp /usr/share/examples/pf/pf.conf /etc/
```

* 编辑 **/etc/pf.conf** 文件，写入：

```ini
table <f2b> persist   # 定义并持久化表 f2b
anchor "f2b/*"        # 创建并引用 f2b 相关的 anchor
block drop in log quick on em0 from <f2b> to any   # 在 em0 接口上快速阻止并记录来自 f2b 表的所有流量
```

* `em0`：示例中的 `em0` 为网卡名称，需根据实际网卡修改，可使用命令 `ifconfig` 查看。

#### 服务

PF 服务的启动和配置如下。

```sh
# kldload pf # 加载内核模块，后续无需再手动加载
# service pf enable # 开机自启
# service pf start # 启动 PF 防火墙
```

**参考文献**

* Xavier Humbert. PF - fail2ban does not feed pf table\[EB/OL]. \[2026-03-26]. <https://forums.freebsd.org/threads/fail2ban-does-not-feed-pf-table.67798/>. FreeBSD 社区论坛讨论 Fail2Ban 与 PF 集成问题。
* mfechner. Fail2ban on FreeBSD drops complete pf firewall rules\[EB/OL]. \[2026-03-26]. <https://github.com/fail2ban/fail2ban/issues/1915>. GitHub Issue 记录的 Fail2Ban 与 PF 规则冲突问题。

### IPFILTER (IPF)

IPFILTER（IPF）是一款开源防火墙，其配置如下。

#### Fail2Ban 配置文件

将上方配置文件 **/usr/local/etc/fail2ban/jail.d/sshd.conf** 中 `action=bsd-ipfw` 改为 `action=ipfilter`，其他配置项无需修改。`ipfilter` action 会调用 IPF 的命令来添加和删除封禁规则。

#### 服务

IPF 服务的配置和启动如下。

* 要让 IPF 正常启动并工作，需复制示例文件作为默认配置规则集文件（示例文件自带的规则不影响使用）：

```sh
# cp /usr/share/examples/ipfilter/ipf.conf.sample /etc/ipf.rules   # 将示例 ipfilter 配置文件复制为 /etc/ipf.rules 以便修改和使用
```

* 配置守护进程：

```sh
# service ipfilter enable   # 设置 ipfilter 服务开机自启动
# service ipfilter start    # 启动 ipfilter 防火墙服务
```

## 测试效果

配置完成后，可以测试 Fail2Ban 是否能正常封禁 IP。使用 Fail2Ban 客户端手动将一个 IP 加入封禁列表以验证功能。

* 为了查看效果，使用 Fail2Ban 客户端将 IP **192.168.179.1** 在 sshd 监控下加入封禁列表：

```sh
# fail2ban-client set sshd banip 192.168.179.1
```

* TTY 输出：此输出示例显示 SSH 服务检测到来自 **192.168.179.1** 的多次认证失败。

```sh
Mar 25 15:27:38 gkla sshd[970]: error : maximum authentication attempts exceeded for ykla from 192.168.179.1 port 8652 ssh2 [ preauth ]
```

已建立的 SSH 连接也会被强制断开。

### 查看状态

可以通过以下命令查看 Fail2Ban 的运行状态。

查看 Fail2Ban 对 sshd 监控的状态，包括被封禁的 IP 列表：

```sh
# fail2ban-client status sshd
Status for the jail: sshd
|- Filter
|  |- Currently failed:	1
|  |- Total failed:	4
|  `- File list:	/var/log/auth.log
`- Actions
   |- Currently banned:	1
   |- Total banned:	1
   `- Banned IP list:	192.168.179.1
```

### 解禁 IP

如果需要解除对特定 IP 的封禁，可执行以下操作。

从 sshd 监控中解除对指定 IP 的封禁：

```sh
# fail2ban-client set sshd unbanip 192.168.179.1
1
# fail2ban-client status sshd
Status for the jail: sshd
|- Filter
|  |- Currently failed:	1
|  |- Total failed:	4
|  `- File list:	/var/log/auth.log
`- Actions
   |- Currently banned:	0
   |- Total banned:	1
   `- Banned IP list:
```

## 故障排除与未竟事宜

如果遇到问题，可查看日志排查。

* Fail2Ban 的日志位于 **/var/log/fail2ban.log** 文件。


---

# 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.5-jie-fail2ban-pei-zhi-ji-yu-ipfw-pf-yu-ipf.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.
