Fail2Ban 开发者指南:从代码测试、编码规范到服务端架构设计
2026/9/20 11:29:46 网站建设 项目流程

Fail2Ban 开发者指南:从代码测试、编码规范到服务端架构设计

【免费下载链接】fail2banDaemon to ban hosts that cause multiple authentication errors项目地址: https://gitcode.com/gh_mirrors/fa/fail2ban

本篇以仓库根目录 DEVELOP(由 doc/develop.rst 引入)为骨架编写,面向希望为 Fail2Ban 贡献代码、或想深入理解其服务端内部设计的开发者。文章覆盖从 Git 工作流、Pull Request 提交规范、测试与覆盖率工具链,到 server/ 目录下 Jail、Filter、FailManager、Actions 等核心组件的类层次与数据流,并结合fail2ban/server/源码逐一印证文档中的架构描述,帮助读者快速上手开发与调试。

一、开发环境与协作流程

Fail2Ban 使用 Git 分布式版本控制进行开发,每个开发者都拥有仓库的完整副本,可以自由地添加分支、切换分支、提交本地修改,随后请求维护者合并(merge)自己的改动。

  • 代码托管在 GitHub 的 fail2ban/fail2ban 仓库,GitHub 提供开源项目免费托管、基于 Web 的 Git 仓库浏览与 Issue 跟踪。
  • 如果你熟悉 Python,并且想提交一个 bug 修复或新特性,推荐的方式是使用 GitHub 的 Pull Request(PR)功能。

重要前提:本文基于当前仓库源码描述开发流程。实际向官方项目提交代码时,需要以官方仓库的实时分支状态和贡献要求为准;本文给出的所有命令、路径与代码引用均以本仓库当前内容为基准。

1.1 Pull Request 提交要求

文档 DEVELOP 明确要求,提交 Pull Request 时应当:

  1. 清晰描述你要解决的问题(Clearly describe the problem you're solving);
  2. 避免引入回归,不给系统管理员升级带来困难(Don't introduce regressions);
  3. 如果添加的是主要特性(major feature),请在 master 上 rebase 你的改动并压缩为单个 commit
  4. 包含测试用例(详见下文"代码测试");
  5. 包含样例日志(如果相关,尤其对于 filter 开发);
  6. 更新 ChangeLog的相应章节;
  7. 如果 THANKS 中还没有你,请加入自己的名字

如果正在开发新的 filter(日志过滤规则),请查阅 FILTERS 文件,其中有专门的文档说明。

二、代码测试:测试用例、覆盖率与手动执行

2.1 运行现有测试

现有测试通过bin/fail2ban-testcases脚本运行,该脚本位于仓库 bin/fail2ban-testcases(另有聚合脚本 fail2ban-testcases-all 与 fail2ban-testcases-all-python3)。它带有--log-level等常用选项:

bin/fail2ban-testcases --help

--help会列出全部可用选项。文档特别提醒:测试用例应当覆盖所有常规情况、所有异常情况,以及所有边界内/边界外条件,并且应当覆盖所有分支。

测试源码位于 fail2ban/tests/,按模块拆分,例如:

  • fail2ban/tests/failmanagertestcase.py ——FailManager的单元测试(含AddFailureFailmanagerComplex等测试类);
  • fail2ban/tests/filtertestcase.py —— Filter 相关测试;
  • fail2ban/tests/actiontestcase.py 与 fail2ban/tests/actionstestcase.py —— Action 相关测试;
  • fail2ban/tests/servertestcase.py、fail2ban/tests/databasetestcase.py、fail2ban/tests/datedetectortestcase.py 等。

2.2 覆盖率(coverage)工具链

安装python-coverage包后可可视化测试覆盖率(注意:在 Debian 系系统中脚本名为python-coverage)。运行:

coverage run bin/fail2ban-testcases coverage report

可选地生成 HTML 报告:

coverage html

然后浏览器打开htmlcov/index.html即可查看测试用例对代码库的覆盖程度。覆盖率百分之百是好事,但文档同时提醒:"全覆盖"并不意味着完备,应尽量让测试覆盖尽可能多的独立代码路径。覆盖率工具还能帮助识别缺失的分支——关于分支覆盖率可参考 coverage.py 官方的分支文档。

2.3 手动执行:在开发环境中运行 fail2ban

在开发环境(不安装到系统)中手动运行,文档给出了标准命令:

./fail2ban-client -c config/ -s /tmp/f2b.sock -i start

参数含义:-c config/指定配置文件目录为仓库内的 config/,-s /tmp/f2b.sock指定控制 socket 路径,-i以交互模式运行,start启动服务端。

启动后可以依次输入下列命令做快速验证(这也是文档推荐的 smoke test 流程):

status add test pyinotify status test set test addaction iptables set test actionban iptables echo <ip> <cidr> >> /tmp/ban set test actionunban iptables echo <ip> <cidr> >> /tmp/unban get test actionban iptables get test actionunban iptables set test banip 192.168.2.2 status test

这段流程演示了 Fail2Ban 交互式控制台的核心操作模式:

  • status:查看全局/监狱状态;
  • add <jail> <backend>:动态添加一个名为test、后端为pyinotify的监狱;
  • set test addaction iptables:为test监狱挂载iptables动作;
  • set test actionban iptables .../set test actionunban iptables ...运行时改写动作的actionban/actionunban命令模板,这里将封禁/解封事件回显到/tmp/ban/tmp/unban,便于观察;
  • get test actionban iptables:读取当前生效的actionban命令;
  • set test banip 192.168.2.2:手动封禁一个 IP(等价于执行一次动作);
  • 再次status test:确认封禁已生效。

这些命令对应服务端 transmitter.py 中set/get/add/status等命令处理器,并由 server.py 的setBanIPaddAction等方法落地。命令行工具的完整说明见 man/fail2ban-client.1(例如set <JAIL> addaction <ACT>...set <JAIL> banip <IP>...)。

2.4 使用 Vagrant 进行隔离测试

仓库根目录提供了 Vagrantfile,可以在虚拟机中做攻防测试,共建立两台 VM:

  • "secure":用于测试 fail2ban 代码;
  • "attacker":用于对 secure VM 发起攻击。

两台 VM 共享192.168.200/24网段。如果你所在的网络恰好使用该网段,请检查 Vagrantfile 并修改 IP 以避免冲突。

三、编码规范(Coding Standards)

3.1 风格与测试要求

项目要求
Style目前请使用**制表符(tab)**缩进;可读文本尽量保持80 列以内
Tests为新增代码补充有意义的测试
Coverage随代码增加,测试覆盖率只许上升
pyflakes在包括基于 Python 的动作在内的所有 Python 代码上运行
Documentation改动后保持本文档、man 手册页同步更新,新特性要有足够的使用文档
Bugs移除 bug,且不要引入新的 bug

3.2 分支覆盖的例外说明

对于为兼容旧版 Python 而保留的分支,允许在代码中使用# pragma: no cover注释(在 fail2ban/server/action.py 等抽象方法上也能看到这类用法)。但对其他任何pragma: no coverpragma: no branch的使用,必须写明理由——"我还没写测试"不是充分的理由。

3.3 pyflakes 静态检查

pyflakes 用于发现未使用的 import、未使用/未定义/被重定义的变量。文档建议对以下路径运行:

pyflakes bin/ config/ fail2ban/

其中config/包含基于 Python 的 action(如 config/action.d/smtp.py),同样需要检查。

3.4 Git 提交信息标签规范

提交信息中请使用以下标签前缀:

  • BF:—— bug 修复(Bug Fix)
  • DOC:—— 文档修复
  • ENH:—— 功能增强(Enhancement)
  • TST:—— 仅涉及测试的提交(不触碰主代码库)

多个标签可用+连接,例如BF+TST:。仓库的 ChangeLog 中即大量使用这类前缀。

另外,可用closes #333resolves #333fixes #333等文本让提交自动关闭对应 Issue(333 为示例 Issue 号)。如果合并产生了冲突,需要在合并提交信息的Conflicts:段落中说明对相应文件做了哪些修改。

3.5 添加新 Action 的约定

如果新增了action.d/*.conf文件,还必须在 config/jail.conf 中添加一个示例:enabled = false、针对 ssh 且maxretry=5的配置块(config/jail.conf内已有大量此类enabled = false的示例模板,可直接参照编写)。这样系统管理员既能开箱参考,又不会因示例默认启用而误封。

四、服务端设计:核心组件与类层次

DEVELOP 文档指出,Fail2Ban 最初基于 Python 2.3 开发(作者回忆),至今仍力求兼容 Python 2.4,这种兼容性承诺使得部分代码显得"老派"(文档中标记为 RF-Note,即重构时值得关注的点)。0.7 版本经历了重大重构,形成了client/server 分离、每监狱一线程(a-thread-per-jail)的架构。下面用文档给出的类层次图作为导航(符号约定:->继承、+委托/聚合、*存储多个实例):

JailThread -> Filter -> FileFilter -> {FilterPoll, FilterPyinotify, ...} | * FileContainer + FailManager + DateDetector + Jail(构造时传入,用于把 ticket 从 FailManager 送入 Jail 的队列) Server + Jails * Jail + Filter (in __filter) * tickets (in __queue) + Actions (in __action) * Action + BanManager

从当前源码看,这一结构依然成立:fail2ban/server/jailthread.py 定义了抽象线程基类JailThread(维护active/idle状态与run/stop/onStop生命周期);fail2ban/server/filter.py 中依次定义了Filter(JailThread)FileFilter(Filter)FileContainer;具体后端 filterpoll.py(FilterPoll(FileFilter))、filterpyinotify.py(FilterPyinotify(FileFilter))、filtersystemd.py(FilterSystemd(JournalFilter))都沿此继承体系实现。

4.1 FailManager:失败票证的集中管理

源码 fail2ban/server/failmanager.py 与文档描述完全对应:

  • FailManager:以ticket(票证)为单位记录失败。所有操作都通过self.__lock = Lock()加锁完成(文档强调"All operations are done via acquiring a lock"),内部用__failList字典按失败标识(fid,通常为 IP)存储FailTicket。关键属性与方法:
    • setMaxRetry/getMaxRetry:最大重试次数(默认3);
    • setMaxTime/getMaxTime:失败统计窗口(默认600秒);
    • addFailure(ticket, count=1, observed=False):累加失败次数、扩展匹配日志(受maxMatches截断),并累加__failTotal
    • cleanup(time):删除超出maxTime的过期 ticket;
    • toBan(fid=None):遍历失败列表,把达到maxRetry的 ticket 移出列表并返回,若没有达到阈值的 ticket 则抛出FailManagerEmpty
  • FailManagerEmpty(Exception):文档说明它由FailManager.toBan在遍历完 ticket 列表后抛出(并附 RF-Note:这个设计未来可以考虑改成生成器)。

4.2 Filter 与 FileFilter:行处理与文件监控

文档对 fail2ban/server/filter.py 的描述:

  • Filter(JailThread):包装(非线程化的)FailManager并大量代理其方法,提供处理新日志行的全部主要逻辑——哪些 IP 需要忽略等。内部关键成员:
    • .failManagerFailManager实例;
    • .dateDetectorDateDetector实例;
    • .__failRegex/.__ignoreRegex:失败与忽略规则的正则表达式列表;
    • .__findTime:数值型时间窗口,在processLineAndAdd中用于跳过过期行。
  • FileFilter(Filter):文件感知的 Filter:
    • .__logPath:被跟踪的文件列表,通过addLogPath逐个添加,存储为FileContainer对象;
    • .getFailures:返回True表示成功打开并读取了行(直到读空),返回False表示打开失败或没有匹配该文件名的容器。
  • FileContainer:文件的适配器,专门处理日志轮转(log rotation)。提供.open.close.readline(RF-Note 指出:文件句柄缺失时readline返回"",也许应返回None更合理)以及位置指针.__pos

在 filter.py 中可以看到实际的票证流转实现(performBan):

while True: try: ticket = self.failManager.toBan(ip) except FailManagerEmpty: break self.jail.putFailTicket(ticket) if ip: break

这正是 DEVELOP 文档所描述的"把 ban tickets 从 failManager 送入对应 jail 队列"的通道:toBan()取出达到阈值的票证,jail.putFailTicket(ticket)将其投递到 Jail 的队列中等待 Actions 线程消费。

4.3 具体后端过滤器(filter*.py)

文档说明:filter*.py是针对特定后端的FileFilter实现。派生类应提供run()的实现,通常还需覆写addLogPathdelLogPath方法。所有后端的run()最终都以一种或另一种方式提供如下循环:

try: while True: ticket = self.failManager.toBan() self.jail.putFailTicket(ticket) except FailManagerEmpty: self.failManager.cleanup(MyTime.time())

即:持续从 failManager 取"达到封禁阈值"的票证送入 jail;当列表为空(抛出FailManagerEmpty)时,清理过期的失败记录。

  • filterpoll.py 的FilterPoll通过os.stat()比较mtime/ino/size判断日志是否被修改(isModified),主循环run()每轮调用getModified收集被修改的文件并逐个getFailures(filename);对文件缺失(可能由轮转引起)会记录__file404Cnt计数,超过 50 次错误则将该文件移出监控(delLogPath)。
  • filterpyinotify.py 的FilterPyinotify基于 inotify 事件回调(callback_process_file_addFileWatcher等)实时感知文件变化。
  • filtersystemd.py 的FilterSystemd则对接 systemd journal(addJournalMatchseekToTimeformatJournalEntry等)。

4.4 ipdns.py:DNS 与 IP 处理工具

ipdns.py 提供两类工具(文档所述):

  • DNSUtils:DNS 处理的工具类,包含dnsToIpipToNametextToIp(text, useDns)getSelfIPsgetIPsFromFile等;
  • IPAddr:IP 地址处理的对象类,支持 IPv4/IPv6、CIDR(plen/isInNet/contains)、PTR 反查(getPTR)、地址族判断(isIPv4/isIPv6)等。

4.5 Action 与 Actions:封禁命令的执行

文档对 action.py 的描述:"Takes care about executing start/check/ban/unban/stop commands"(负责执行 start/check/ban/unban/stop 命令)。源码层面:

  • 抽象基类定义在 fail2ban/server/action.py,start/stop/ban/reban/unban均为抽象方法;
  • fail2ban/server/actions.py 中的Actions是监狱级动作容器,其run()是动作线程主循环:启动所有 action → 等待 jail 队列中的封禁票证(hasFailTickets)→__checkBan()处理封禁、__checkUnBan()处理到期解封 → 线程停止时__flushBan(stop=True)stopActions()
  • 同一文件中的ActionInfo定义了动作命令可用的插值标签字典AI_DICT,如<ip><family><bantime><failures><matches><ipmatches><jail.name><jail.banned>等——这些就是 config/action.d/ 下各动作配置(如iptables.confnftables.confmail.conf)中<...>占位符的取值来源;
  • BanManager(fail2ban/server/banmanager.py)负责维护当前封禁列表(addBanTicketunBanListgetBanList等)。

4.6 其他辅助组件

  • server.py:核心服务器,管理 Jails 集合(addJail/delJail/startJail/stopJail),并暴露大量set/get接口(addFailRegexsetMaxRetrysetBanTimesetBanIP等),由transmitter转发控制命令;
  • jails.py:Jails容器,add(name, backend, db=None)创建 Jail;
  • jail.py:Jail聚合 Filter(__filter)、失败票证队列(__queue)、Actions(__action),并负责_setBackend选择polling/pyinotify/systemd后端;
  • failmanager.py 的FailTicket/BanTicket定义在 ticket.py(getIPgetAttemptgetMatchesincrBanCountisTimedOut等);
  • datedetector.py 与 datetemplate.py:日志行日期识别;
  • database.py:SQLite 持久化(addBan/getBans/getCurrentBans/purge),用于重启后恢复封禁状态。

五、开发时的自检清单

综合 DEVELOP 文档与仓库实践,提交代码前建议按以下清单自检:

  1. 问题描述:PR 是否清晰说明了解决的问题?
  2. 回归风险:改动是否会给系统管理员升级造成困难?配置默认值是否保持向后兼容?
  3. 测试:是否补充了覆盖常规、异常与边界条件的测试用例?运行coverage run bin/fail2ban-testcases && coverage report确认覆盖率没有下降?# pragma: no cover是否都有合理理由?
  4. 静态检查pyflakes bin/ config/ fail2ban/是否无新增告警(未使用 import、未定义变量等)?
  5. 文档:DEVELOP、FILTERS 与 man/ 手册是否同步更新?新特性是否有使用文档?
  6. 提交规范:提交信息是否使用BF:/DOC:/ENH:/TST:标签?是否需要fixes #N关联 Issue?是否需要更新 ChangeLog 与 THANKS?
  7. 新增动作:若新增action.d/*.conf,是否在 config/jail.conf 提供了enabled = falsemaxretry=5的 ssh 示例?
  8. 手动验证:是否用./fail2ban-client -c config/ -s /tmp/f2b.sock -i start走通"添加监狱 → 挂载动作 → 手动 banip → 查看状态"的完整链路?

按照上述流程,即可在保证质量的前提下为 Fail2Ban 提交修复与新特性;而理解第四节的类层次与票证流转机制,则是深入调试 Filter 与 Action 行为的关键基础。

【免费下载链接】fail2banDaemon to ban hosts that cause multiple authentication errors项目地址: https://gitcode.com/gh_mirrors/fa/fail2ban

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询