目标审计
目标审计让 katana 节点拒绝连接你不希望它访问的主机,例如 BitTorrent tracker,或服务条款禁止的网站。每条规则是一个正则表达式。katana 用它匹配每个流所请求的主机,在第一次匹配时拒绝该流,并记住哪个用户命中了哪条规则,以便告知保存检测日志的面板。
规则有两个来源,可以同时使用:面板,以及 rule_list_path 指定的本地文件。如果你的节点所对接的面板会管理审计规则,或者你想要一份不依赖面板的固定屏蔽列表,请阅读本页。
快速上手:本地规则文件
Section titled “快速上手:本地规则文件”以下步骤为现有节点添加一份本地屏蔽列表,两种面板都适用。
-
编写规则文件。 每行写一条正则。空行和以
#开头的行会被跳过。/etc/katana/rules.txt # katana destination audit rules: one regex per line.# Hosts named tracker.*, announce.* or open.tracker.*, a common naming# pattern for public BitTorrent trackers(?i)^(tracker|announce|open\.tracker)\.# One site and every subdomain of it, with or without a trailing dot(?i)(^|\.)blocked\.example\.com\.?$# Any destination given as an IPv4 literal in 203.0.113.0/24^203\.0\.113\.\d+$ -
让节点指向该文件。 在节点的
[node.api]表中设置rule_list_path:/etc/katana/config.toml [[node]]panel_type = "newV2board"[node.api]host = "https://panel.example.com"node_id = 1key = "replace-with-the-panel-key"node_type = "V2ray"rule_list_path = "/etc/katana/rules.txt" -
检查配置。
katana --test -c /etc/katana/config.toml会检查该配置项拼写正确且值为字符串。它不会打开规则文件,所以即使路径错误或正则有误,也仍然输出Configuration OK。 -
应用修改。 如果 katana 已在运行,保存配置即可:热重载会立即加载新路径,并保留已打开的连接。否则请启动 katana。节点在启动结束时读取规则,之后每次轮询面板时再读取一次,因此之后修改文件内容既不需要重载也不需要重启:见规则变化时。
-
查看日志。 katana 无法读取的路径和每条无法编译的正则都会产生一行
WARN日志,SSPanel 规则请求失败时也是如此。日志行表列出了这些日志。
两个配置项都属于同一个 [[node]] 条目。每个节点有自己的规则集,因此两个节点可以使用不同的文件,也可以使用同一个文件。
| 配置项 | 表 | 类型 | 默认值 | 说明 |
|---|---|---|---|---|
rule_list_path |
[node.api] |
path | "" |
本地规则文件。为空表示没有本地规则。katana 在每次刷新规则时读取该文件。相对路径以 katana 的工作目录为基准解析,所以请使用绝对路径。 |
disable_get_rule |
[node.controller] |
bool | false |
设为 true 会完全关闭规则刷新,面板规则和本地文件都不再刷新。如果在启动时就设置,节点将不做任何审计。 |
这两个表都会拒绝未知配置项,因此拼写错误会让 katana 停止运行,而不是悄悄关闭审计:
configuration error: config parse error: TOML parse error at line 9, column 1 |9 | disable_get_rules = true | ^^^^^^^^^^^^^^^^^unknown field `disable_get_rules`, expected one of `listen_ip`, `send_ip`, `update_periodic`, `disable_upload_traffic`, `disable_get_rule`, `disable_sniffing`, `cert`[node.controller] 中的 update_periodic(默认 60 秒)决定节点轮询面板的频率,因此也决定了刷新规则和上报命中的频率。katana 配置文件页面与其他 controller 配置项一起介绍了它。
规则从哪里来
Section titled “规则从哪里来”每次刷新时,katana 会构建一个有序列表:先是本地规则,按文件中的顺序排列,然后是面板的规则。面板提供哪些规则取决于面板类型。
使用 panel_type = "newV2board"(或 "v2board")时,katana 从 UniProxy API 返回的节点配置中的 routes 数组获取规则,不会为此额外发请求。
"routes": [ { "id": 3, "match": ["(?i)(^|\\.)blocked\\.example\\.com$", "(?i)^tracker\\."], "action": "block" }, { "id": 7, "match": ["example.org"], "action": "dns", "action_value": "198.51.100.53" }]- 只有
action恰好为block的条目才会成为审计规则。katana 忽略其他 action。 - 一个 route 的
match列表中的各项用|连接成一条正则。上面第一个 route 会变成(?i)(^|\.)blocked\.example\.com$|(?i)^tracker\.。 - 规则 id 是该 route 在
routes数组中的位置,从0开始计数,而不是面板的id字段。上面第一个 route 的 id 为0。id 只对上报有意义,而这种面板类型没有上报。 - 如果连接后的正则无法编译,katana 会记录一条警告并跳过整个 route,而不只是跳过出错的那一项。
- katana 把每一项原样当作正则使用。它不解释其他一些节点 agent 能识别的前缀,例如
regexp:或protocol:,所以请写普通正则。 - katana 保留它最后收到的节点配置中的 routes。面板返回
304 Not Modified或请求失败时,继续使用之前的 routes。 - 修改节点的
[node.api]表后,热重载会为节点创建新的面板客户端,新客户端会接管这些 routes。即使此时面板无法访问,由它们生成的规则也仍然有效。
使用 panel_type = "sspanel" 时,katana 在每次刷新时请求 GET /mod_mu/func/detect_rules。每一项都带有自己的 id 和正则;katana 只读取这两个字段。
{ "ret": 1, "data": [ { "id": 5, "regex": "(?i)^tracker\\." }, { "id": 6, "regex": "(?i)(^|\\.)blocked\\.example\\.com$" } ]}- 每一项成为一条规则,使用面板的
id。 regex无法编译的项会被记录并跳过,其他项照常加载。- 没有
id的项 id 为0;没有regex的项得到空正则,它匹配所有主机。 - 当面板返回
304 Not Modified、请求失败、ret不为1或响应体无法解析时,katana 保留已有的规则集。这些情况下它也不会重新读取本地文件,因此如果面板的规则请求一直不成功,本地文件就永远不会加载。
对两种面板类型,本地文件的处理方式相同:
- katana 先去掉每行首尾的空白,如果该行为空或以
#开头则跳过。不支持行内注释:foo # bar就是正则foo # bar。 - 其余每一行都编译为一条正则。无法编译的行会被记录并跳过,文件其余部分照常加载。
- 每条本地规则的 id 都是
-1。 - 如果文件无法读取,katana 会记录一条警告,本次刷新在没有本地规则的情况下继续进行。面板的规则仍会加载。
| Xboard / V2board | SSPanel | 本地文件 | |
|---|---|---|---|
| 来源 | action = "block" 的 routes 条目 |
/mod_mu/func/detect_rules |
rule_list_path |
| 每条规则对应 | 一个 route(其 match 列表用 | 连接) |
一项 | 一行 |
| 规则 id | 在 routes 中的位置,从 0 开始 |
该项的 id |
-1 |
| 命中是否上报给面板 | 否 | 是,仅 id 为 0 及以上的规则 |
否 |
规则匹配什么
Section titled “规则匹配什么”规则匹配的是客户端请求的主机,即不带端口的字符串:
- 域名,与客户端发送的完全一致,例如
www.blocked.example.com; - 或 IP 字面量,例如
203.0.113.7或2001:db8::1。IPv6 地址不带方括号,并写成压缩的小写形式,因此2001:0DB8:0:0::1会以2001:db8::1参与匹配。
匹配是不加锚点的搜索,使用 Rust regex 语法。这意味着:
blocked\.example\.com也会匹配notblocked.example.com.evil.test。请用^和$加锚点,并用(^|\.)同时覆盖一个域名及其子域名。- katana 不会去掉末尾的点,所以请求
blocked.example.com.的客户端不会匹配以com$结尾的模式。模式以\.?$结尾即可覆盖两种形式。 .匹配任意字符。在域名和地址中请转义为\.。- 匹配区分大小写。模式以
(?i)开头可忽略大小写,因为客户端不一定会把域名转成小写。 - 不支持环视(
(?=…)、(?<!…))和反向引用。这样的正则无法编译,会被跳过。 - 端口不在字符串中,因此审计规则无法针对端口。请改用路由规则。
流会经历什么
Section titled “流会经历什么”任何入站打开的每个流都经过同样的检查:TCP 请求、每个多路复用子流、每个 Hysteria 2 stream,以及每个 UDP 关联。katana 先对用户做准入,再为流选路,最后才审计。
flowchart TB
A["新的流"] --> B{"用户仍在注册列表中?"}
B -- 否 --> R1["拒绝"]
B -- 是 --> C{"UDP?"}
C -- 是 --> U["逐包检查"]
C -- 否 --> D{"路由选择 block?"}
D -- 是 --> R2["拒绝,不记录命中"]
D -- 否 --> E{"有审计规则匹配?"}
E -- 是 --> R3["拒绝,记录命中"]
E -- 否 --> F["经路由选出的出站拨号"]
- 第一条匹配的规则生效。 katana 按列表顺序测试规则,本地规则在前,并只记录第一条匹配的规则。
- 被拒绝的流看起来与路由屏蔽一样。 katana 拒绝该流的方式与拒绝被路由到
block的流完全相同,所以客户端无法区分两者。katana 不会写出任何指明规则或用户的日志行。 - 被路由屏蔽的流不做审计。 如果路由已经选择了
block,katana 会直接拒绝该流而不测试审计规则,因此不会记录命中。
UDP 关联没有单一的目标地址,因为每个数据报都带有自己的地址。因此 katana 对每个发出的数据包分别做路由和审计。目标被路由屏蔽或匹配规则的数据包会被静默丢弃。它不计入流量,关联对其他目标地址仍保持开放。匹配规则的数据包会记录一次命中,与流的情况相同。
每个 TCP 流和每个发出的 UDP 数据包都会逐条测试整个规则列表,直到有规则匹配,所以请只保留需要的规则。
一次命中是一对用户 id 和规则 id。katana 维护一个集合,保存自上次上报以来出现过的所有组合,所以同一用户在两次轮询之间命中同一规则一千次,也只出现一次。在每次轮询结束时,以及节点停止时再一次,katana 取出整个集合,将其清空,并发送给面板:
| 面板 | katana 发送的内容 |
|---|---|
| SSPanel | POST /mod_mu/users/detectlog,请求体为 {"data":[{"list_id":5,"user_id":42}]},每次命中一项。规则 id 小于 0 的命中(即本地规则)会被排除。如果没有剩余命中,则不发请求。 |
| Xboard / V2board | 不发送。katana 没有针对这种面板类型的检测日志上报,因此命中会被清空并丢弃。 |
上报失败会记录日志但不会重试:它携带的命中已经被清空。这与流量上报不同,后者会把未上报的流量留到下次轮询。
每次轮询按顺序执行以下步骤:
- 获取节点配置和用户列表,并应用它们。
- 刷新规则,除非
disable_get_rule为true:读取本地文件,并按上文所述收集面板的规则。 - 上报流量。
- 上报并清空命中。
因此,新规则会在一个 update_periodic 间隔内生效。启动时,katana 先启动节点的监听器,随后立即加载规则。当热重载修改了运行中节点的 [node.api] 表或其监听器设置(例如 listen_ip)时,katana 会立即执行一次轮询,而不是等待定时器,因此那次轮询也会刷新规则。
katana 按 id 和正则文本逐条比较新列表与当前生效的列表。如果两者完全相同,就保留当前规则集。任何差异(包括顺序变化)都会一次性替换整个规则集。已经打开的 TCP 流不会被重新检查;之后的每个 UDP 数据包按其发出时生效的规则集检查。
有三种情况不遵循这一规则:
- 使用 SSPanel 时,如果规则请求得到
304 Not Modified响应,katana 会保留当前规则集,而不重新读取本地文件。如果你的面板会这样响应,对本地文件的修改要等到面板规则下次变化、你保存对节点[node.api]表的修改,或 katana 重启时才生效。修改[node.api]会让节点获得一个不持有任何 ETag 的新面板客户端,因此面板会完整响应它的第一次规则请求,katana 也会重新读取本地文件。 - 监听器变化时。 katana 按监听器保存规则,监听器由节点类型、
listen_ip和端口确定。当面板把节点改到另一个端口时,新监听器会在同一次轮询的刷新中获得规则。当热重载修改了listen_ip时,katana 会重建监听器,并从热重载立即执行的那次轮询中获得它的规则。使用 SSPanel 时,如果这两种情况下的那次刷新得到304 Not Modified响应,新监听器将没有规则,直到面板规则变化、修改[node.api]或重启。面板改端口和修改listen_ip都不会让节点获得新的面板客户端,节点保留原有的 ETag,所以面板在这里仍可能返回304。 - 在 Xboard / V2board 上设置了
[node.hysteria].port的 Hysteria 2 节点,katana 根据配置文件描述该节点,从不获取面板的节点配置,因此节点收不到面板的routes。只有本地文件生效。
要让运行中的节点改用另一个文件,修改 rule_list_path 并保存配置即可。热重载会为节点创建新的面板客户端并立即执行一次轮询,那次轮询的刷新会读取新文件。新的流会立即按新规则检查,这次修改也不会断开任何已打开的连接。使用 SSPanel 时,新文件会在第一次成功的规则请求中加载;在此之前,当前规则集保持生效。修改节点已指向的文件的内容无需重载:下次刷新就会读取。disable_get_rule 在每次轮询时读取,同样可以通过热重载修改。
katana 以 WARN 级别记录规则问题,并继续运行。1 代表节点的 node_id。
| 日志行 | 原因与处理 |
|---|---|
cannot read rule_list_path /etc/katana/rules.txt: No such file or directory (os error 2) |
文件不存在,或 katana 用户无法读取。在后续某次刷新能读取它之前,节点只使用面板的规则运行。 |
invalid local rule "(foo": regex parse error: … |
本地文件中有一行无法编译。该行被跳过;修正后会在下次刷新时加载。 |
invalid block rule ["(foo", "bar"]: regex parse error: … |
Xboard / V2board 的某个 block route 连接后的 match 列表无法编译。整个 route 被跳过。 |
invalid panel rule "(foo": regex parse error: … |
某条 SSPanel 检测规则无法编译。该项被跳过。 |
node 1: node_rule: … |
SSPanel 规则请求失败。当前生效的规则集保持不变。 |
node 1: report illegal: … |
向 SSPanel 上报检测日志失败。它携带的命中被丢弃。 |
正则解析错误会跨越多行,并在出错位置下方标出脱字符:
invalid local rule "(foo": regex parse error: (foo ^error: unclosed group规则从不匹配。
- 客户端可能在用 IP 地址连接。添加 IP 模式,或使用能看到嗅探域名的路由规则。
- 检查大小写:加上
(?i)。 - 对照确切的主机字符串检查锚点:
^blocked\.example\.com$既不匹配www.blocked.example.com,也不匹配blocked.example.com.。 - 查找与该规则相关的
WARN日志行,并确认disable_get_rule不是true。 - 使用 SSPanel 时,查找
node_rule警告:规则请求失败期间,本地文件也不会加载。 - 如果某条路由规则已经把该目标发往
block,流会被拒绝,但不会产生审计命中。
所有连接都被拒绝。
- 查找空的或过于宽泛的模式:
blockroute 上为空的match列表或空条目、regex为空或缺失的 SSPanel 项,或者.、.*这样的本地规则。 - 逐条注释掉本地规则;每次修改在下次刷新时生效,但要注意规则变化时中的 SSPanel
304情况。
SSPanel 中没有检测日志条目。
- 本地规则(id 为
-1)的命中永远不会上报。 - 命中在每次轮询结束时发送,所以请等待一个
update_periodic间隔。 - 查找
report illegal警告。