GrakovNe/tacet
GitHub: GrakovNe/tacet
Keenetic 路由器上的内核级轻量自动封禁工具,利用 ipset/iptables 在包过滤层拦截端口扫描和泛洪攻击。
Stars: 1 | Forks: 0
# Tacet
**针对运行 Entware 的 Keenetic 路由器,提供内核级别的端口扫描器和泛洪攻击自动防护。** 它监控 WAN,将探测您关闭的端口或猛烈攻击您转发端口的源加入黑名单,并丢弃其流量——这一切都在 Linux 包过滤器内完成,并提供了一个轻量级的 Web UI 供您查看和操作。

## 目录 - [为什么开发此项目](#why-this-exists) - [一段话安装指南](#install-in-one-paragraph) - [工作原理——整体机制](#how-it-works--the-whole-mechanism) - [术语:数据包、SYN 以及“丢弃”的含义](#vocabulary-packets-syn-and-what-dropping-means) - [三个列表 (ipsets)](#the-three-lists-ipsets) - [被拉黑:陷阱](#getting-banned-the-traps) - [已拉黑:丢弃](#being-banned-the-drop) - [白名单始终具有最高优先级](#the-whitelist-always-wins) - [单个 IP 与整个子网 (CIDR)](#single-ips-and-whole-subnets-cidr) - [封禁持续时间及如何在重启后保留](#how-long-a-ban-lasts-and-surviving-reboots) - [主开关](#the-master-switch) - [“最后访问”和“连接数”的来源](#where-last-seen-and-connections-come-from) - [Web UI](#the-web-ui) - [设置参考](#settings-reference) - [安装说明](#installation) - [更新与版本控制](#updates-and-versioning) - [架构](#architecture) - [文件及其存放位置](#files-and-where-they-live) - [通过 Shell 操作](#operating-it-from-the-shell) - [局限性(请阅读此部分)](#limitations-read-this) - [安全说明](#security-notes) - [商标](#trademark) - [许可证](#license) ## 为什么开发此项目 拥有公网 IP 的路由器会不断被扫描。机器人会扫荡整个 IPv4 地址空间,寻找开放的 SSH、暴露的管理面板、可以暴力破解的转发服务,以及用于指纹识别的关闭端口。大多数情况下这只是噪音,但这是*无休止的*噪音:它会填满连接表,浪费小型路由器的 CPU,使您的日志臃肿不堪,偶尔还会发现一些目标。 通常的应对方案都很笨重:fail2ban 风格的日志跟踪(需要守护进程和大量日志)、商业威胁情报源,或者干脆不管。在 Keenetic 上——一台只有几十 MB RAM 和 BusyBox 用户空间的路由器——这些方案并不合适。 **Tacet 的目标是在尽可能底层的廉价层——内核包过滤器——消除恶意来源,实现零常驻进程,并为您提供清晰、真实的运行状态视图。** 它在保持轻量级方面非常固执:执行层纯粹使用 `ipset`/`iptables`,UI 是一个静态页面加上一个 shell CGI,在数据包和网卡之间除了内核本身之外没有任何东西运行。 它防护的内容: - 针对路由器本身的**关闭端口扫描/探测**(有人敲击没有程序监听的端口)。 - 针对您转发到内部服务器的正常端口的**泛洪和暴力破解攻击**(例如将 `443` 端口转发到家用服务器)。 它**不**打算成为:IDS、DPI 引擎、内容过滤器,或替代您对暴露的服务进行实际的安全加固。它只是减小了攻击面和噪音;它不检查 payload。 ## 一段话安装指南 在路由器(运行 Entware 的 Keenetic)上,以 root 身份执行: ``` curl -fsSL https://raw.githubusercontent.com/GrakovNe/tacet/main/install.sh -o /tmp/tacet-install.sh sh /tmp/tacet-install.sh ``` 回答三个问题(UI 端口、需要保护的转发端口、封禁持续时间),然后打开它打印出的地址—— `http://:5050`。
更多详情,包括在 GitHub 抽风时的备用镜像,请参阅[安装说明](#installation)。
## 工作原理——整体机制
本节是项目的核心。如果您只读一部分,请读这一部分。
### 术语:数据包、SYN 以及“丢弃”的含义
每个 TCP 连接都以一个 **SYN** 数据包开始:客户端说“我想对话”。通常服务器回复 `SYN-ACK`,握手完成。Linux 防火墙(`iptables`/netfilter)会看到每一个穿过路由器的数据包,并可以通过一个** target** 决定其命运:
- `ACCEPT` — 放行。
- `REJECT` — 返回错误(发送方知道该端口已关闭)。
- **`DROP`** — 静默丢弃。完全没有回复。发送方什么都看不到,最终超时。
Tacet 使用 **`DROP`**。这里的“丢弃连接”意味着:*来自被封禁源的每一个新连接的初始数据包,在内核应答之前就被扔掉了。* 握手不会开始,因此连接不会形成。在攻击者看来,您的路由器就像一个黑洞——这正是您想要的效果:它浪费了他们的时间,并且什么也没暴露。
您还会看到的另外两个术语:
- **conntrack** — 内核的连接跟踪表。它记录每个流的状态(NEW、ESTABLISHED、RELATED、closing 等)。我们利用它在封禁*新*会话的同时保持您*现有*的会话存活,并在 UI 中显示“最后访问”和当前连接。
- **WAN 接口** — 在 Keenetic PPPoE 上,它是 `ppp0`。这里的每一条规则都限定在 `-i ppp0`,因此您的 LAN 绝不会受到影响。
### 三个列表 (ipsets)
**ipset** 是一种内核数据结构,可以容纳许多地址,并且可以通过 `iptables` 规则以 O(1) 的查找成本进行测试——这比为每个地址编写一条规则要便宜得多。Tacet 保留了三个集合,类型均为 `hash:net`(意味着每个条目可以是单个 IP *或* CIDR 子网):
| ipset | 包含内容 | 超时时间 |
|------------------|------------------------------------------------------|---------|
| `tacet-scan` | 因 **关闭端口** 扫描 (INPUT) 而被封禁的来源 | 24 小时\* |
| `tacet-flood` | 因 **开放端口** 泛洪 (FORWARD) 而被封禁的来源 | 24 小时\* |
| `tacet-allow` | **白名单** — 永不封禁,永不丢弃 | 无(永久) |
\* 封禁持续时间可配置(`BAN_TTL`,默认 24 小时);请参阅[封禁持续时间](#how-long-a-ban-lasts-and-surviving-reboots)。
这两个封禁类别是独立的集合,纯粹是为了让 UI 可以独立显示和管理它们——丢弃逻辑对它们的处理方式是相同的。
### 被拉黑:陷阱
当源 IP 越过速率阈值的那一刻,它会被自动添加到封禁集合中。计量由 netfilter 的 **`hashlimit`** 模块完成,它是一个基于源 IP 的**令牌桶**:
- 每个源 IP 都有一个令牌桶,最多可容纳 **`burst`** 个令牌,并以**每分钟 1 个令牌**的速度恢复。
- 每个匹配的 SYN 会消耗一个令牌。当令牌桶中有令牌时,该数据包被视为*低于*限制,并原封不动地通过。
- 当源发送速率超过令牌桶恢复速率并将其耗尽时,后续数据包将**超过**限制(`--hashlimit-above 1/min`)——这*就是*触发器。
当触发器触发时,规则的目标是 `-j SET --add-set src`:源 IP 被写入封禁集合。从下一个数据包开始,由丢弃规则(见下文)接管。
有两个陷阱:
**1. 关闭端口陷阱(链 INPUT,路由器本身)。**
它位于 `INPUT` 链的*末尾*,在所有接受合法服务的规则之后。因此,只有当**路由器上没有任何程序监听该端口**时(即它是在敲击一个关闭/被过滤的端口),SYN 才会到达这里。真实的规则如下所示:
```
-A INPUT -i ppp0 -p tcp --syn -m set ! --match-set tacet-allow src \
-m hashlimit --hashlimit-above 1/min --hashlimit-burst \
--hashlimit-mode srcip --hashlimit-name tct-scan \
-j SET --add-set tacet-scan src --exist
```
阈值:**`BURST`** SYN/分钟(默认 8;更严格的 5 也是个不错的选择)。
**2. 开放端口陷阱(链 FORWARD,您转发的服务)。**
在这里,端口是*真实的*——您故意将其转发到内部服务器——因此封禁不能基于“没人想要这个端口”。它基于**速率**:一个源在每分钟内对受保护端口发起过多新连接,这属于泛洪 / SYN 泛洪 / 暴力破解尝试。每个受保护的端口都会安装这样一条规则,就在转发规则*之前*:
```
-A FORWARD -i ppp0 -p tcp --dport 443 --syn -m set ! --match-set tacet-allow src \
-m hashlimit --hashlimit-above 1/min --hashlimit-burst \
--hashlimit-mode srcip --hashlimit-name tct-flood443_ \
-j SET --add-set tacet-flood src --exist
```
阈值:**`SVC_BURST`** SYN/分钟(默认 60;高于关闭端口的阈值,因为真实用户在这里会建立许多合法的连接)。
有两个细节值得了解:
- **白名单不受陷阱限制**(`! --match-set tacet-allow src`),因此白名单中的对端永远不会触发陷阱并导致自己被封禁。
- **阈值被固定在令牌桶的名称中**(`tct-scan8`、`tct-flood443_60`)。`hashlimit` 按名称缓存其令牌桶,因此如果更改了阈值,引擎会在新名称下启动一个*全新的*令牌桶,而不是静默保留旧速率——更改确实会生效。
### 已拉黑:丢弃
仅仅是成为封禁集合的成员本身并没有任何作用。执行依赖于位于 `INPUT` 和 `FORWARD` *最前面*的一对**丢弃规则**:
```
# INPUT(发往路由器的流量)
-A INPUT -i ppp0 -m set --match-set tacet-scan src -m set ! --match-set tacet-allow src -m state ! --state RELATED,ESTABLISHED -j DROP
-A INPUT -i ppp0 -m set --match-set tacet-flood src -m set ! --match-set tacet-allow src -m state ! --state RELATED,ESTABLISHED -j DROP
# FORWARD(传递到内部服务器的流量)
-A FORWARD -i ppp0 -m set --match-set tacet-scan src -m set ! --match-set tacet-allow src -j DROP
-A FORWARD -i ppp0 -m set --match-set tacet-flood src -m set ! --match-set tacet-allow src -j DROP
```
用通俗的语言来解读其中一条:*“对于到达 WAN 的流量,如果源在封禁集合中,并且不在白名单中,并且(在 INPUT 链上)不是已建立会话的一部分——则丢弃该数据包。”*
为什么要使用这两个链:
- **INPUT** 涵盖了*发往路由器*的连接(其自身的服务、关闭的端口)。
- **FORWARD** 涵盖了*穿过路由器*发往其后方机器的连接。
如果在 FORWARD 中没有丢弃规则,封禁将无法切断发往您转发服务的流量——而这正是开放端口类别的全部意义。
为什么 INPUT 上有 `! --state RELATED,ESTABLISHED`:它只丢弃**新**的连接尝试,而保持已经建立的会话不受影响。这很重要,因为您自己的隧道和长寿命连接是在路由器上终止的;您肯定不希望一个新的封禁会在传输过程中将它们切断。FORWARD 则是无条件丢弃(转发的泛洪应该被立即切断)。
### 白名单始终具有最高优先级
每一项可能对地址产生负面影响的规则——包括两个陷阱和**两个丢弃规则**——都带有 `! --match-set tacet-allow src`。这意味着白名单是在**内核中**被遵守的,而不仅仅是在 UI 中。后果如下:
- 被列入白名单的源永远不会触发陷阱。
- 被列入白名单的源永远不会被丢弃——**即使您手动封禁它**,并且**即使它属于被封禁的子网**。白名单是绝对的“绝对不要碰”。
- 此外,UI 也会拒绝显式封禁白名单中的地址,并给出提示,而不是静默失效。
使用您绝对不能丢失的任何内容来填充白名单:您的 VPS、邮件中继、监控系统,特别是**您的客户端正在与其通信的任何服务器**。(您向外连接的对端会向内发送返回的 SYN 包;如果它发送频率很高,可能会触发陷阱并导致自己被封禁。加入白名单可以防止这种自身造成的故障。)私有地址段(`10/8`、`172.16/12`、`192.168/16`)默认会被加入白名单。
### 单个 IP 与整个子网 (CIDR)
因为这些集合是 `hash:net` 类型,**每一个接受地址的地方也都可以接受 CIDR 子网**——手动封禁表单、白名单表单等所有地方。封禁 `45.10.0.0/16` 后,它将成为一个单一的集合条目,匹配该块中的所有 65,536 个地址;丢弃规则的 O(1) 查找在处理子网时与处理单个 IP 完全一样。这就是您如何一击击退嘈杂的托管地址段的方法。
与白名单的优先级是根据*效果*来判定的,而不是根据前缀长度:被封禁子网内的白名单 IP 仍然被豁免,因为丢弃规则会单独测试白名单并停止处理。因此,“封禁这个 /16 但保留这台主机”是可行的。
(旧版安装使用 `hash:ip` 集合;引擎会在首次运行时自动将它们迁移到 `hash:net`,并保留成员及其剩余的超时时间。)
### 封禁持续时间及如何在重启后保留
封禁集合是**带有超时时间**创建的(`BAN_TTL`,默认 `800` 秒 = 24 小时)。ipset 会为每个条目倒计时,并在达到零时剔除该地址——封禁会自动过期;列表不会无限增长。
**该超时时间是*空闲*超时,而不是固定的挂钟时间封禁。** 在每次丢弃之前都有一个*刷新*规则——`-j SET --add-set src --exist`——它会在丢弃规则即将丢弃每个数据包时,将封禁的计时器重置回 `BAN_TTL`。因此,一个不断敲门的源会被无限期封禁;只有在静默 `BAN_TTL`(没有尝试)之后,封禁才会失效。这是对持续性攻击的惩罚:一个在封禁即将过期时立刻卷土重来的机器人,将永远无法解脱。刷新操作是每个被丢弃的数据包执行一次额外的 O(1) `hash:net` 操作;被丢弃的流量很少,因此成本微乎其微。更改 `BAN_TTL` 会重建集合,并将每个现有条目缩减到新的限制。
封禁**在重启后依然保留**。一个 cron 任务每 30 分钟为两个封禁集合运行一次 `ipset save`,存入 `/opt/etc/tacet-scan.save` 和 `tacet-flood.save`。启动时,Keenetic 会在重建防火墙时重新运行引擎钩子(`50-tacet.sh`);该钩子会重新创建集合,如果集合为空,则恢复已保存的成员。白名单位于一个纯文本文件(`/opt/etc/tacet-allow.list`)中,并以相同的方式重新加载,因此您的修改也会被保留。
### 主开关
`MASTER` 是一个显眼的红色总开关。关闭后,引擎会**移除其拥有的每一条规则**——包括两个陷阱和所有四个丢弃规则——从而使防护完全处于无效状态;路由器的表现就像没有安装 Tacet 一样。封禁集合会被*保留*(不会丢失任何内容),只是不会被查询。将其重新打开,规则就会从保存的状态中重建。切换它(与任何设置更改一样)会重新应用防火墙,这也会重置累计的“丢弃数据包”计数器。
### “最后访问”和“连接数”的来源
此固件**没有 `xt_LOG`** target,因此没有可挖掘的按数据包记录的日志。相反,UI 直接读取内核的 conntrack 表(`/proc/net/nf_conntrack`):
- 对于被拉黑或白名单中的地址,**“最后访问”**是从 conntrack 倒计时推导出来的:每种状态都有一个基础超时时间(例如 `SYN_SENT` ≈ 120 秒),*基础时间 - 剩余时间*估算出了最近一个数据包是多久以前到达的。这是在没有时间戳的情况下可用的最佳新近度信号。
- 仪表板上的**“当前热门连接”**列出了与外部对端的已建立 TCP 流(跳过私有网段和路由器自身的 WAN IP),按繁忙程度降序排列,因此您可以直观地看到实际正在通信的对象,并一键拉黑不良 IP。
封禁/白名单**事件**(添加、过期/移除、配置保存、主开关切换)通过每分钟对比一次集合成员的差异,被记录到一个微型事件日志中——请参阅[日志记录](#settings-reference)。再次强调:这里记录的是*事件*,而不是数据包,因为固件无法记录数据包。
## Web UI
`http://:5050`,仅限局域网访问,包含四个标签页。它是一个静态页面,与 JSON API 通信并在您的浏览器中渲染;数据通过计时器(默认 60 秒)原地刷新,无需重新加载。支持浅色/深色/自动主题。
- **仪表板** — 主开关(启用/禁用);四个磁贴(当前拉黑数 / 关闭端口拉黑数 / 开放端口拉黑数 / 丢弃数据包数);一张显示 24 小时封禁随时间变化的堆叠条形图(将鼠标悬停在条形上可查看具体数据);以及**当前热门连接**,支持按行执行拉黑操作。
- **防护** — 一个用于手动拉黑任何 IP 或子网的表单;**拉黑候选**(当前正在触发陷阱但尚未超过阈值的来源,显示它们正在攻击的端口,并支持一键拉黑或加入白名单);以及两个封禁类别,作为可折叠的表格展示,每行显示国家/地区国旗、最后访问时间、剩余到期时间,以及解除拉黑或移至白名单的操作。每个类别标题都可以一次性解除所有拉黑。
- **白名单** — 一个用于添加 IP/子网的表单,以及完整的列表(每个条目均可编辑,包括默认植入的条目),带有移除操作。
- **设置** — 左侧菜单包含:常规、自动拉黑规则、数据包丢弃、地理位置数据库、日志记录、更新。大多数设置通过点击 **Apply** 保存;日志记录和主开关的切换会立即生效。
每个表格中的每个 IP 都带有**国家/地区国旗**(将鼠标悬停在上方可显示名称)。查询使用的是*本地*的 IP 到国家/地区的数据库,因此 UI 绝不会对外联网通信——请参阅[地理位置数据库](#settings-reference)。
## 设置参考
以下所有内容都存储在 `/opt/etc/tacet.conf` 中,并可在“设置”中进行编辑。括号内为默认值。
**常规**
- **封禁持续时间** [`24` 小时] — `BAN_TTL`。在*来源最后一次被封禁的尝试之后*封禁持续的时间(这是一个空闲超时——不断尝试的源将保持被封禁状态;请参阅[封禁持续时间](#how-long-a-ban-lasts-and-surviving-reboots))。
当前活跃的封禁将被立即修剪为新的较小值。
- **自动刷新** [`60` 秒] — `REFRESH_SEC`。UI 原地重新获取数据的频率。设为 `0` 可禁用。
- **配色方案** — 自动 / 浅色 / 深色,在您的浏览器中记忆(仅在客户端有效,不是服务器设置)。
**自动拉黑规则**
- **自动拉黑扫描器** [开启] — `TRAP_CLOSED`。启用关闭端口陷阱。
- **阈值 (关闭)** [`8` SYN/分钟] — `BURST`。越低越严格;1–3 可能偶尔会捕捉到来自合法但不稳定对端的零星重传。
- **自动拉黑泛洪攻击** [开启] — `TRAP_OPEN`。启用开放端口陷阱。
- **受保护端口** [`443`] — `SVC_PORTS`。逗号分隔的需要进行速率保护的转发 TCP 端口;留空表示无。
- **阈值 (开放)** [`60` SYN/分钟] — `SVC_BURST`。由于此处存在真实流量,因此阈值更高;60 可以在不影响浏览器的情况下阻止 SYN 泛洪。
**数据包丢弃**
- **半开超时** [`120` 秒] — `SYN_TIMEOUT`。内核的 `nf_conntrack_tcp_timeout_syn_sent` 值:一个被丢弃/半开的尝试在连接表中停留的时间。越小的值在扫描爆发后清除表的速度越快。范围 10–120。
**地理位置数据库** — 下载/刷新用于国旗的本地 IP 到国家/地区的数据库(dbip-country lite,免费)。它在安装时植入并存储在 `/opt/etc/tacet-geo.dat`。UI *绝对不会*加载这个大文件:后台任务仅将您实际看到的地址解析到一个微型缓存(`/opt/var/tacet-geocache`)中,页面读取的是缓存。
**日志记录** — 一个即时的开/关切换(`LOG_ENABLED`)以及事件日志。事件涵盖拉黑、释放(到期或手动解除)、白名单更改、配置保存以及主开关。因为固件没有包日志记录 target,所以收集器每分钟通过**对比集合成员差异**来记录事件(来自 UI 的手动操作会被正确标记,并且不会被重新记录为扫描)。
**更新** — 显示已安装版本和最新的 GitHub 发布版,支持一键就地更新。请参阅下文。
有两个配置项没有对应的表单,因为它们拥有自己的即时切换开关:`MASTER`(仪表板)和 `LOG_ENABLED`(日志记录)。旧版的 `TRAP_ENABLED=no` 依然可以同时禁用这两个类别。
## 安装说明
**要求:** 一台带有 **Entware** 的 Keenetic 路由器,并且安装了 `ipset`、`iptables` 和 `lighttpd` + `lighttpd-mod-cgi` 软件包。安装程序会检查这些条件,并会主动为您提供 `opkg install` lighttpd 的选项。假定 WAN 接口为 `ppp0`(PPPoE);如果您的不一样,请在 `50-tacet.sh` 顶部修改 `WAN=`。
在路由器上,以 root 身份执行:
```
curl -fsSL https://raw.githubusercontent.com/GrakovNe/tacet/main/install.sh -o /tmp/tacet-install.sh
sh /tmp/tacet-install.sh
```
GitHub 偶尔会抽风;如果下载失败,请改用通过 codeload 获取整个代码树(安装程序本身只与 `api`/`codeload` 通信):
```
curl -sfL https://codeload.github.com/GrakovNe/tacet/tar.gz/refs/heads/main | tar -xzf - -C /tmp
sh /tmp/tacet-main/install.sh
```
安装程序会询问 **UI 端口**、**受保护的转发端口**以及**封禁持续时间**,然后:(原子地)放置文件,将您的 LAN IP 和端口写入 Web 服务器配置,在不影响现有 crontab 的情况下追加其两行 cron 记录,应用防火墙规则,启动服务,在后台植入地理位置数据库,并打印出 UI 地址。它是**可安全重复运行**的——已存在的 `tacet.conf` 和白名单绝不会被覆盖。
如果您在没有附带仓库文件的情况下直接运行裸 `install.sh`(例如,您只复制了那一个脚本),它会首先通过下载最新的发布版本来引导自身。
## 更新与版本控制
该服务知道自己的版本(显示在页脚和 **Settings → Updates** 下)。版本信息保存在单个文件 `VERSION` 中,它是事实的唯一来源。
- **检查更新** 会请求 GitHub 的 **Releases API**(`releases/latest`)获取最新发布的版本(如果 API 暂时不可达,则回退到 `main` 分支上的原始 `VERSION`)。
- 如果存在更新的版本,**更新** 会从 codeload 下载该版本的 tarball,并**就地**安装:文件会被原子替换,您的配置/白名单/当前封禁都会保留,防火墙规则和 Web 服务器配置会被重新应用,页面会在大约一分钟内恢复。更新程序甚至能在运行期间安全地替换自身。
您也可以手动执行相同的操作:`sh /opt/etc/tacet-update.sh --apply`。
**发布新版本(维护者):** 提升 `VERSION`,提交,打上 `v` 标签,推送这两者,并为该标签发布一个 GitHub Release。路由器将在下次检查时看到它。
## 架构
三层架构,每一层都可以在不影响其他层的情况下进行替换:
1. **引擎 — `50-tacet.sh`。** 一个 `ndm` netfilter 钩子,Keenetic 会在每次防火墙重建(启动、PPPoE 重连或按需)时运行它。它以幂等方式创建 ipsets,安装陷阱和丢弃规则,应用 conntrack 超时,并恢复已保存的封禁。这是*唯一*执行操作的组件,并且它是纯 shell + `ipset`/`iptables`。即使 UI 被停止或卸载,它依然会继续工作。
2. **API — `api.cgi`。** 一个响应 JSON 的 shell CGI:读取快照(`fn=overview|protection|whitelist|settings`)和执行操作(`fn=ban|unban|white|unwhite|master|logtoggle|config|geoupdate|…`)。这是 UI 的整个后端;它读取引擎使用的相同 ipsets/config/conntrack,并且在执行操作时对它们进行编辑,并在需要时重新应用规则。
3. **UI — `index.html` + `tacet.js` + `tacet.css`。** 一个**静态**页面外壳;所有数据来自 `api.cgi` 并由 `tacet.js` 在客户端渲染,该脚本还处理操作并原地刷新当前活动标签页。没有整页重新加载,没有服务端 HTML 模板。图表是内联 SVG;没有任何外部资源或 CDN。
它由运行在专属端口上的独立 `lighttpd` 实例提供服务,与 Keenetic 运行的任何内容完全分离。一个每分钟执行一次的 cron 收集器(`tacet-collect.sh`)向 CSV 追加一行统计数据用于图表,并维护事件日志和地理缓存。
## 文件及其存放位置
| 仓库文件 | 安装路径 | 角色 |
|------------------------|----------------------------------------------|------|
| `50-tacet.sh` | `/opt/etc/ndm/netfilter.d/50-tacet.sh` | 引擎(陷阱、丢弃、恢复) |
| `tacet-collect.sh` | `/opt/etc/tacet-collect.sh` | cron:统计 CSV、事件日志、地理缓存 |
| `tacet-geo-update.sh` | `/opt/etc/tacet-geo.sh` | 下载 IP 到国家/地区的数据库 |
| `tacet-update.sh` | `/opt/etc/tacet-update.sh` | 就地更新程序 |
| `tacet-countries` | `/opt/etc/tacet-countries` | 国家/地区代码 → 名称映射表 |
| `VERSION` | `/opt/etc/tacet-version` | 已安装版本 |
| `index.html` | `/opt/share/tacet/index.html` | 静态页面外壳 |
| `api.cgi` | `/opt/share/tacet/api.cgi` | JSON API(后端) |
| `assets/tacet.css` | `/opt/share/tacet/tacet.css` | 样式表 |
| `assets/tacet.js` | `/opt/share/tacet/tacet.js` | UI(获取、渲染、操作) |
| `lighttpd-tacet.conf`| `/opt/etc/lighttpd-tacet.conf` | UI 的 lighttpd 实例配置 |
| `S85tacet-ui` | `/opt/etc/init.d/S85tacet-ui` | 启动/停止该 lighttpd 实例 |
| `S10crond` | `/opt/etc/init.d/S10crond` | 启动 cron |
| `crontab-root` | 追加到 `/opt/var/spool/cron/crontabs/root` | 持久化封禁 + 收集统计信息 |
运行时状态(不在仓库中):`/opt/etc/tacet.conf`(设置),`/opt/etc/tacet-allow.list`(白名单),`/opt/etc/tacet-scan.save` + `tacet-flood.save`(封禁快照),`/opt/var/tacet-stats.csv`(图表历史),`/opt/var/tacet-events.log`(事件日志),`/opt/var/tacet-geocache`(已解析的国旗)。
`dev-deploy.sh` 是为了方便开发者通过 SSH 从工作站推送工作副本而设计的;最终用户永远不需要它。
## 通过 Shell 操作
您很少需要这样做,但一切都是可检查且可编写脚本的:
```
ipset list tacet-scan # who is banned for closed-port scanning
ipset list tacet-flood # who is banned for open-port flooding
ipset list tacet-allow # the whitelist
ipset del tacet-scan 45.10.0.0/16 # unban a subnet by hand
ipset add tacet-allow 1.2.3.4 # whitelist (prefer the UI — it also updates the file so it survives reboot)
iptables -S INPUT | grep tacet-scan # the closed-port trap + drops
iptables -S FORWARD | grep tacet-scan # the open-port trap + drops
iptables -L INPUT -v -n # drop counters (packets dropped)
sh /opt/etc/ndm/netfilter.d/50-tacet.sh # re-apply the rules by hand (table=filter is set by ndm)
sh /opt/etc/tacet-update.sh --apply # update to the latest release
```
`丢弃数据包` 计数器是**自上次(重新)构建丢弃规则以来**的累积值——它会在重启、任何设置更改或主开关切换时重置。它不是历史总总量。
## 局限性(请阅读此部分)
诚实面对它的无能之处也是设计的一部分。
- **慢速扫描器会漏网。** 陷阱是一个速率计;如果源敲门的速度慢于阈值恢复速度(例如每分钟一次),它就永远不会触发。这些扫描是无害的噪音——要捕捉它们,需要固件不具备的按数据包记录日志的功能。
- **仅支持 TCP。** 陷阱仅匹配 TCP SYN。UDP 扫描/泛洪不会被计量。
- **拉黑不会切断到路由器的实时会话。** INPUT 丢弃豁免了 `ESTABLISHED`,因此您自己的隧道得以幸存;只有来自被封禁源的*新*连接会被切断。转发的流量*确实会*被立即切断。
- **没有数据包日志。** 固件缺少 `xt_LOG`,因此事件日志记录的是成员*变化*,而不是单个数据包。
- **强制执行基于 IP。** 如果攻击者执意通过轮换多个地址进行探测,他们可以继续尝试;您可以通过一条条目拉黑其整个子网来应对,但这不能替代您所暴露服务的身份验证和补丁修补。
- **假定 WAN 接口为 `ppp0`。** 非 PPPoE 设置需要进行一行修改。
## 安全说明
- UI 仅绑定到 LAN IP,并且**没有身份验证**——它是为受信任的家庭局域网设计的,就像路由器自带的管理页面一样。请勿将其端口转发到互联网。
- 在接触到 `ipset` 之前,所有成为拉黑/白名单条目的用户输入都会被验证为 IP 或 CIDR;API endpoint 名称和参数被限制在已知集合内。通过地址字段进行的注入会被拒绝。
## 商标
**Tacet 是一个独立的、非官方的项目。它不附属于 Keenetic,也不受其赞助或认可。** “Keenetic” 是其各自所有者的商标,此处仅作描述性使用,以说明该软件运行的硬件平台。
## 许可证
MIT © Max Grakov 2026。随心所用;不提供任何担保。

## 目录 - [为什么开发此项目](#why-this-exists) - [一段话安装指南](#install-in-one-paragraph) - [工作原理——整体机制](#how-it-works--the-whole-mechanism) - [术语:数据包、SYN 以及“丢弃”的含义](#vocabulary-packets-syn-and-what-dropping-means) - [三个列表 (ipsets)](#the-three-lists-ipsets) - [被拉黑:陷阱](#getting-banned-the-traps) - [已拉黑:丢弃](#being-banned-the-drop) - [白名单始终具有最高优先级](#the-whitelist-always-wins) - [单个 IP 与整个子网 (CIDR)](#single-ips-and-whole-subnets-cidr) - [封禁持续时间及如何在重启后保留](#how-long-a-ban-lasts-and-surviving-reboots) - [主开关](#the-master-switch) - [“最后访问”和“连接数”的来源](#where-last-seen-and-connections-come-from) - [Web UI](#the-web-ui) - [设置参考](#settings-reference) - [安装说明](#installation) - [更新与版本控制](#updates-and-versioning) - [架构](#architecture) - [文件及其存放位置](#files-and-where-they-live) - [通过 Shell 操作](#operating-it-from-the-shell) - [局限性(请阅读此部分)](#limitations-read-this) - [安全说明](#security-notes) - [商标](#trademark) - [许可证](#license) ## 为什么开发此项目 拥有公网 IP 的路由器会不断被扫描。机器人会扫荡整个 IPv4 地址空间,寻找开放的 SSH、暴露的管理面板、可以暴力破解的转发服务,以及用于指纹识别的关闭端口。大多数情况下这只是噪音,但这是*无休止的*噪音:它会填满连接表,浪费小型路由器的 CPU,使您的日志臃肿不堪,偶尔还会发现一些目标。 通常的应对方案都很笨重:fail2ban 风格的日志跟踪(需要守护进程和大量日志)、商业威胁情报源,或者干脆不管。在 Keenetic 上——一台只有几十 MB RAM 和 BusyBox 用户空间的路由器——这些方案并不合适。 **Tacet 的目标是在尽可能底层的廉价层——内核包过滤器——消除恶意来源,实现零常驻进程,并为您提供清晰、真实的运行状态视图。** 它在保持轻量级方面非常固执:执行层纯粹使用 `ipset`/`iptables`,UI 是一个静态页面加上一个 shell CGI,在数据包和网卡之间除了内核本身之外没有任何东西运行。 它防护的内容: - 针对路由器本身的**关闭端口扫描/探测**(有人敲击没有程序监听的端口)。 - 针对您转发到内部服务器的正常端口的**泛洪和暴力破解攻击**(例如将 `443` 端口转发到家用服务器)。 它**不**打算成为:IDS、DPI 引擎、内容过滤器,或替代您对暴露的服务进行实际的安全加固。它只是减小了攻击面和噪音;它不检查 payload。 ## 一段话安装指南 在路由器(运行 Entware 的 Keenetic)上,以 root 身份执行: ``` curl -fsSL https://raw.githubusercontent.com/GrakovNe/tacet/main/install.sh -o /tmp/tacet-install.sh sh /tmp/tacet-install.sh ``` 回答三个问题(UI 端口、需要保护的转发端口、封禁持续时间),然后打开它打印出的地址—— `http://
标签:Cutter, Docker 部署, ipset, iptables, TCP SYN 扫描, Web UI, 入侵防御, 网络运维, 路由器, 防火墙