跳转到内容

配置文件

katana 读取一个 TOML 文件,里面描述的是面板管不到的那部分:要对接哪些面板、提供哪些证书、流量可以去往哪里,以及域名如何解析。本页从整体上介绍这个文件:四个顶层段、katana 解析它们时遵循的规则、进程级的 [log] 和 [dns] 段,以及多个 [[node]] 块如何共用一个进程。

在编写第一份配置、添加第二个节点,或者 --test 拒绝了某个文件而你想知道原因时,都可以参考本页。节点内部的配置、出站和路由规则各有专门的页面,下文的表格中给出了链接。

最小的可用文件只需指定一个面板节点,其余一切都有默认值:

/etc/katana/config.toml
[[node]]
panel_type = "NewV2board"
[node.api]
host = "https://panel.example.com"
node_id = 1
key = "replace-with-the-panel-key"
node_type = "V2ray"

不启动任何服务,直接检查它:

终端窗口
katana --test -c /etc/katana/config.toml

文件有效时打印 Configuration OK,退出状态为 0;无效时打印 configuration error: 加上原因,退出状态为 1。不带 -c 时,katana 读取工作目录下的 config.toml。

这个文件足以通过 --test,但实际的节点通常还需要更多配置。如果面板把节点标记为 TLS,katana 还需要在 [node.controller.cert] 中配置证书,而 --test 不会联系面板,因此无从得知这一点。各类节点分别需要什么,见节点。

完整的文件有四个顶层段。[log] 和 [dns] 作用于整个进程;每个 [[node]] 块是一个面板节点,带有自己的子表;所有 [[outbound]] 块组成一个出站池,每个节点都路由到这个池中。

[log] # 进程级:日志过滤器
[dns] # 进程级:所有出站背后的解析器
[[node]] # 每个面板节点一个块
[node.api] # 如何访问面板,以及节点是什么
[node.controller] # 绑定地址、轮询间隔、功能开关
[node.controller.cert] # TLS 和 Hysteria2 节点使用的证书
[node.hysteria] # Hysteria2 监听器设置
[node.route] # 该节点的默认出站和 geodata 文件
[[node.route.rule]] # 该节点的路由规则,首个匹配生效
[[outbound]] # 每个具名上游一个块,所有节点共用

TOML 会把每个子表归到它上方最近的 [[node]],所以节点的子表要紧跟在它的 [[node]] 行之后、下一个 [[node]] 之前书写。

段 作用范围 文档位置
[log] 进程 本页
[dns] 进程 本页
[[node]]、[node.api]、[node.controller]、[node.controller.cert] 单个节点 节点
[node.hysteria] 单个 Hysteria2 节点 Hysteria 2 节点
[node.route]、[[node.route.rule]] 单个节点 路由
[[outbound]] 所有节点 出站
键类型必填默认值说明
logtable否{}日志设置,写作 [log],只有 level 一个键。与 etemenanki-app 不同,katana 在热重载时会应用改动后的 level。
dnstable否{}整个出站池共用的域名解析,写作 [dns]。不写时使用系统解析器,前面始终有一层缓存。访问面板的 HTTP 请求不经过它。
nodearray of tables是—要服务的面板节点,每个 [[node]] 块一个,下面有 [node.api]、[node.controller]、[node.controller.cert]、[node.hysteria]、[node.route] 和 [[node.route.rule]] 几个子表。键名是单数:[[nodes]] 会被当作未知字段,只写一个 [node] 表会报 invalid type: map, expected a sequence。一个都不写时文件能解析,但 --test 和正常启动都会报 config defines no [[node]] entries。
outboundarray of tables否[]具名的上游代理,每个 [[outbound]] 块一个,所有节点共用。键名是单数。direct、freedom、block、blackhole 这几个 tag 始终存在,不能重新定义;使用保留的或重复的 tag 会报 duplicate/reserved outbound tag <tag>。

下图展示了运行时各段之间的关系。每个节点有自己的面板和自己的路由表,但所有路由表都从同一个出站池中选择出站,而每个出站都通过同一个 [dns] 解析器解析域名。

flowchart LR
  P1["面板 A"] --> N1["节点 1"]
  P2["面板 B"] --> N2["节点 2"]
  N1 --> R1["节点 1 路由表"]
  N2 --> R2["节点 2 路由表"]
  R1 --> POOL["共享出站池"]
  R2 --> POOL
  POOL --> BUILTIN["direct, freedom, block, blackhole"]
  POOL --> OUT["outbound 条目"]
  DNS["dns 解析器"] -.-> POOL

没有顶层的 [route]。路由配置位于每个节点之下,写作 [node.route];顶层的 [route] 表会被当作未知字段,导致解析失败。

文件中的每个表都会拒绝它不认识的键,而且没有任何键有别名。拼错的键会让 katana 停下,而不是被跳过,因此笔误不会悄无声息地让某项设置停留在默认值。报错会指出行号、键名以及该表接受的键:

configuration error: config parse error: TOML parse error at line 3, column 1
|
3 | paneltype = "x"
| ^^^^^^^^^
unknown field `paneltype`, expected one of `panel_type`, `api`, `controller`, `route`, `hysteria`

值的类型同样严格检查。node_id = "1" 会报 invalid type: string "1", expected u32,node_id = -1 会报 invalid value: integer `-1`, expected u32,level = 3 会报 invalid type: integer `3`, expected a string。文件必须是 UTF-8 编码,否则报 config not utf-8。

键名采用 snake_case,与参考表中列出的写法完全一致。XrayR 的 YAML 使用 ApiHost、NodeID 这样的名称,在 katana 中对应的是 [node.api] 下的 host 和 node_id。两者的差异见从 XrayR 迁移。

一部分表示选项的值在匹配时不区分大小写,因此 panel_type = "NewV2board" 与 panel_type = "newv2board" 等价;另一部分则必须严格写成小写。下表列出了文件中所有此类值:

值 大小写 可接受写法示例
[[node]].panel_type 任意 "SSpanel"、"NewV2board"、"V2board"
[node.api].node_type 任意 "V2ray"、"Trojan"、"Shadowsocks"、"Hysteria2"
[[outbound]].protocol 任意 "socks"、"Shadowsocks"、"WireGuard"
[[outbound]].security 任意 "auto"、"AES-128-GCM"
[[outbound]].address_family 任意,且 - 视同 _ "ipv4_only"、"IPv4-Only"
[[outbound]].method,Shadowsocks 2022 名称 精确匹配 "2022-blake3-aes-256-gcm"
[[outbound]].method,其他加密方式 任意 "aes-128-gcm"、"CHACHA20-POLY1305"
[dns].backend 精确匹配 "system"、"udp"、"tls"、"https"
[node.controller.cert].mode 精确匹配:只有 "file" 会加载证书 "none"、"file"
[node.hysteria].credential、obfs 精确匹配 "uuid"、"user_pass"、"salamander"

katana 报告未知的 panel_type 时,消息中的值会显示为小写:panel_type = "XBoard" 报的是 unknown panel_type "xboard"。

文件中的路径,例如 cert_file、key_file、geoip、geosite、rule_list_path 和 ca_file,会按原样打开。相对路径以 katana 的工作目录为基准,而不是配置文件所在的目录。systemd 服务的工作目录是 /,除非 unit 中设置了 WorkingDirectory=,所以请使用 /etc/katana/geoip.dat 这样的绝对路径。

--test 在不绑定端口、不联系面板的前提下,尽可能多地构建配置。它按以下顺序检查,遇到第一个错误即停止:

  1. 读取并解析文件,发现未知键、类型错误和非 UTF-8 编码。
  2. 如果设置了 [dns].ca_file,读取该文件,然后构建 [dns] 解析器。
  3. 构建每个 [[outbound]],并检查 tag 没有重复,也没有占用内置名称。
  4. 检查至少有一个 [[node]]。
  5. 依次对每个节点检查 panel_type 和 node_type,然后基于出站池编译 [node.route],并读取规则用到的 geodata 文件。对于 Hysteria2 节点,还会检查 [node.hysteria] 并读取证书和私钥。

凡是由面板决定的事情,它都无法检查。--test 不检查面板 URL 和密钥是否可用、面板分配的端口是否空闲,也不检查 TLS 节点的证书文件是否存在。它也不检查 [log].level,不读取 rule_list_path。这些问题要等节点启动时才会出现在日志中。

[log] 设置 katana 向标准输出写入哪些日志行。

键类型必填默认值说明
levelstring否"info"一条 tracing 的 EnvFilter 指令,语法与 RUST_LOG 相同:一个级别(off、error、warn、info、debug、trace,或 0 到 5 的数字,不区分大小写),后面可以跟按 target 覆盖的级别,例如 "info,katana=debug"。启动时,环境变量 RUST_LOG 只要是合法的过滤器就优先生效。--test 不校验这个值:启动时无法解析的指令会被跳过,并在 stderr 打印一行 ignoring ...;不是级别的词(例如 "warning")会被当成 target 名,结果是所有日志(包括错误)都不再输出。热重载时如果这个值变了,katana 会应用新值,并取代 RUST_LOG 设置的过滤器;新值无法解析时记录 invalid log level,保留当前的过滤器。
[log]
level = "info,katana=debug"

启动时,如果环境变量 RUST_LOG 是合法的过滤器,katana 就使用它,否则使用 [log].level。想在不修改文件的情况下让某一次运行输出更多细节,可以用 RUST_LOG=debug katana -c /etc/katana/config.toml。

与 etemenanki-app 不同,katana 会在热重载时应用改动后的 level。重载发现 level 变了时,katana 会安装新的过滤器,以 info 级别记录 reload: log level → <level>,此后以文件为准,不再使用 RUST_LOG。如果新值无法解析,katana 记录 invalid log level 并保留当前过滤器。删除这个键会把级别恢复为 info。没有改动 level 的重载不会触碰当前过滤器,即使它来自 RUST_LOG。

[dns] 决定出站池如何把主机名解析为地址。它与 etemenanki-app 使用的是同一个解析器,键和规则也完全相同;关于解析器本身的更多细节,见 DNS。

键类型必填默认值说明
backendstring (enum)否"system"解析结果的来源:"system"(系统解析器,遵循 /etc/hosts 和 nsswitch.conf)、"udp"(普通 DNS)、"tls"(DNS over TLS,RFC 7858)或 "https"(DNS over HTTPS,RFC 8484)。与 katana 大多数枚举值不同,这里区分大小写:"UDP" 会报 dns: unknown backend "UDP" (expected "system", "udp", "tls" or "https")。
serverstring视情况—解析服务器地址,写成带端口的 IP 字面量,例如 "192.0.2.53:53" 或 "[2001:db8::53]:853"。udp、tls、https 必填;缺少时构建失败,报 dns: the <backend> backend needs a server address。写成主机名或漏掉端口会报 dns: invalid server address: invalid socket address syntax。对 https 来说,katana 连接的就是这个地址,不会去解析 url 里的主机名。system 忽略这个键。
server_namestring视情况—校验解析服务器证书时使用的名称。tls 必填;缺少时构建失败,报 dns: the tls backend needs a server name to verify against。其他后端忽略这个键。
urlstring视情况—DNS over HTTPS 的地址,例如 "https://dns.example.com/dns-query"。https 必填;缺少时构建失败,报 dns: the https backend needs the resolver's url。必须以 https:// 开头,否则报 dns: "<url>" is not an https:// url。其中的主机名用来校验证书,也作为 Host 头发送;URL 里的端口会被忽略,没有路径时使用 /dns-query。带用户信息的 URL 会报 has no usable host。其他后端忽略这个键。
ca_filepath否—一个 PEM 证书包,里面是校验 tls 或 https 解析服务器时额外信任的 CA 证书。katana 把它们加到系统根证书之上,不会取代系统根证书。只要设置了这个键,katana 就会读取文件,即使后端是用不到它的 system 或 udp。相对路径以工作目录为基准。文件不存在时只报 No such file or directory (os error 2),不带路径。后端是 tls 或 https 时,文件里没有证书会报 no certificate in CA PEM bundle。
# 默认值;与不写 [dns] 相同。
[dns]
backend = "system"

通过 getaddrinfo 使用系统解析器。只有这个后端会遵循 /etc/hosts、nsswitch.conf 和搜索域。

如果解析服务器的证书由私有 CA 签发,可以在 tls 或 https 后端中加上 ca_file 来信任它。katana 会把文件中的证书加到系统根证书之上,而不是取代系统根证书,因此持有公开受信任证书的解析服务器仍能通过校验:

[dns]
backend = "tls"
server = "192.0.2.53:853"
server_name = "dns.example.com"
ca_file = "/etc/katana/dns-ca.pem"

所选后端用不到的键不会被检查。在 backend = "system" 下,残留的 server = "garbage" 也能通过 --test。例外是 ca_file:只要设置了,katana 就会读取它。

katana 只构建一个解析器,带一个缓存,并把它交给池中的每个出站,包括内置的 direct 和 freedom。它负责解析:

  • 经由 direct、freedom 或 protocol = "direct" 出站离开的流量的目标地址;
  • 上游代理出站的 server 名称;
  • 经由 WireGuard 出站发送的流量的目标地址。

WireGuard 出站自己的 server,即对端 endpoint,是个例外:katana 使用系统解析器来解析这个名称。

由于所有节点都路由到同一个池,所有节点也共享这个缓存。面板请求不经过它:无论 [dns] 怎么写,katana 访问面板 API 的 HTTP 客户端都使用系统解析器。

缓存最多保存 8192 个名称的解析结果。系统解析器的结果不带 TTL,katana 将其保留 60 秒;其他后端的结果按 TTL 保留,并限制在 5 秒到 1 小时之间。使用 udp、tls 和 https 时,每次查询 5 秒超时。

一个 katana 进程可以服务任意数量的面板节点,这些节点可以来自同一个面板,也可以来自不同面板。每个节点写一个 [[node]] 块,后面跟上它自己的子表。各节点独立运行:每个节点都有自己的面板客户端、轮询定时器、监听器、用户表、流量计数器和路由表,一个节点的面板不可达不会影响其他节点。

首次无法启动的节点会重试。原因可能是针对该节点或其用户的面板请求失败,也可能是监听器无法绑定。此时它记录 node <id>: <reason>; retrying in <N>s,然后再次尝试。第一次等待 1 秒,此后每失败一次等待时间翻倍,上限为 60 秒与该节点 update_periodic 中的较小者。在此期间其他节点照常运行。如果某次重载改动了正在等待的节点的 [[node]] 块,它会立即重试,因为这次编辑可能正好修复了问题。

所有节点共享 每个节点独立
[log] panel_type 以及 [node.api] 中的全部内容
[dns] 及其缓存 [node.controller] 及其证书
[[outbound]] 池,包括 direct 和 block [node.hysteria]
进程本身、它的文件监视器和信号 [node.route] 及其规则

下面的示例在一个进程中同时服务一个 Xboard VMess 节点和一个 SSPanel Trojan 节点。VMess 节点把私有地址发往 block,把 example.org 及其子域名发往 relay 出站,其余流量发往 direct。Trojan 节点除 RFC 1918 私有地址段和 25 端口(SMTP)发往 block 外,其余流量全部发往 relay。两个节点使用同一个只定义了一次的 relay 出站。

/etc/katana/config.toml
# Two panel nodes in one katana process: an Xboard (newV2board) VMess node and
# an SSPanel Trojan node. Both share the [dns] resolver and the [[outbound]]
# pool; each has its own [node.route] table.
#
# Validate with: katana --test -c /etc/katana/config.toml
[log]
level = "info"
# One resolver and cache for every outbound in the pool.
[dns]
backend = "tls"
server = "192.0.2.53:853"
server_name = "dns.example.com"
# ---------------------------------------------------------------------------
# Node 1: VMess from an Xboard panel. The panel supplies the port, transport
# and TLS flag; the certificate is always local.
# ---------------------------------------------------------------------------
[[node]]
panel_type = "NewV2board"
[node.api]
host = "https://panel.example.com"
node_id = 1
key = "replace-with-the-panel-key"
node_type = "V2ray"
timeout = 10
[node.controller]
listen_ip = "0.0.0.0"
update_periodic = 60
[node.controller.cert]
mode = "file"
cert_file = "/etc/katana/proxy.example.com.crt"
key_file = "/etc/katana/proxy.example.com.key"
[node.route]
default = "direct"
geoip = "/etc/katana/geoip.dat"
[[node.route.rule]]
outbound = "block"
geoip = ["private"]
[[node.route.rule]]
outbound = "relay"
domain_suffix = ["example.org"]
# ---------------------------------------------------------------------------
# Node 2: Trojan from an SSPanel panel. Trojan always runs over TLS.
# ---------------------------------------------------------------------------
[[node]]
panel_type = "SSpanel"
[node.api]
host = "https://sspanel.example.com"
node_id = 2
key = "replace-with-the-panel-key"
node_type = "Trojan"
[node.controller]
update_periodic = 60
[node.controller.cert]
mode = "file"
cert_file = "/etc/katana/proxy.example.com.crt"
key_file = "/etc/katana/proxy.example.com.key"
[node.route]
default = "relay"
[[node.route.rule]]
outbound = "block"
cidr = ["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"]
[[node.route.rule]]
outbound = "block"
port = ["25"]
# ---------------------------------------------------------------------------
# The shared outbound pool. `direct`, `freedom`, `block` and `blackhole` are
# built in; these entries add to them.
# ---------------------------------------------------------------------------
[[outbound]]
tag = "relay"
protocol = "shadowsocks"
server = "198.51.100.20"
port = 8388
method = "2022-blake3-aes-256-gcm"
# Generate a real key with: openssl rand -base64 32
password = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="

relay 出站中的 Shadowsocks 2022 密钥是占位值。请用 openssl rand -base64 32 生成一个真实的密钥。

每个节点在自己的 listen_ip 上绑定一个端口。这个端口由面板分配;唯一的例外是设置了 [node.hysteria].port 的 Hysteria2 节点,它使用该端口,不向面板索取端口。两个节点在同一地址上绑定同一个 TCP 端口时无法同时启动:后绑定的那个会记录 node <id>: initial start failed: …; retrying in <N>s 并持续重试,因此端口空出来后它就会启动。Hysteria2 节点监听的是 UDP,因此可以与 TCP 节点使用相同的端口号。--test 不检查端口。

热重载时,katana 按节点的身份把新文件中的每个 [[node]] 与正在运行的节点对应起来。身份由以下各项共同组成:

  • panel_type,比较时不区分大小写;
  • [node.api].host,按原样比较;
  • [node.api].node_id;
  • [node.api].key;
  • 在 NewV2board 和 V2board 上,还包括 katana 向面板请求的节点类型:enable_vless = true 的 V2ray、Vmess 或 Vless 节点为 vless,其他情况为小写的 node_type。SSPanel 仅凭 ID 查找节点,因此 SSPanel 节点的身份不含节点类型。

节点类型之所以属于身份,是因为 Xboard 和 V2board 按节点 ID 和所请求的类型来查找节点。同一个 node_id 分别以 vless 和 vmess 请求,就是两个面板节点,各有自己的用户和流量,katana 也同样这样对待。

条目的身份不变时,katana 就地重新配置正在运行的节点。这也包括 [node.api] 中的其他键,例如 timeout 和 speed_limit:katana 会用改动后的块构建新的面板客户端,并立即向面板请求节点及其用户。身份中任何一项发生变化时,katana 会把这次编辑视为删除一个节点再添加另一个:停止旧节点(旧节点会最后一次上报它统计到的流量),并启动一个从头联系面板的新节点。[[node]] 块在文件中的顺序无关紧要。各类改动对运行中节点的影响,见热重载。

host 按字符串比较,所以 https://panel.example.com 和 https://panel.example.com/ 算作不同的身份,尽管两者访问的是同一个面板。

katana 监视配置文件所在的目录。该目录中的任何变化都会在约半秒后触发重载,随后 katana 逐段比较新文件与正在运行的配置。无法解析的文件会被整体忽略,日志中记录 config reload failed, keeping current: …。对于能解析的文件,各个顶层段的处理方式不同:

段 重载时
[log] 改动后的 level 立即生效,除非这次重载因出站或节点问题被拒绝。
[dns] 只有同一次重载也改动了某个 [[outbound]] 时才会应用。
[[outbound]] 重建出站池。构建失败时,整个重载被拒绝,记录 reload: bad outbounds, keeping current config: …,其他改动也都不会应用。构建成功时,每个节点重新编译路由表并重启监听器,这会断开所有节点上的所有连接。如果某个节点的规则无法针对新出站池编译,它会记录 node <id>: route rebuild failed, keeping current: … 并保留旧路由表,但仍会重启监听器。
[[node]] 在应用任何改动之前,katana 先构建文件新增的每个节点(包括其面板客户端和路由表),以及每个块有改动的节点的面板客户端。其中任何一个构建失败,整个重载都会被拒绝,记录 reload: node <name>: <error>; keeping current config,其他改动也都不会应用。否则启动新出现的身份,停止消失的身份,并通过新的面板客户端在线重新配置有改动的身份,[node.api] 中的键也包括在内。改动后的 [node.route] 如果无法编译,只由该节点自己拒绝:它记录 node <id>: config edit refused, keeping the running one: … 并保留全部旧设置,重载的其余部分照常应用。

如果重载后文件中一个 [[node]] 条目都没有,所有节点都会被停止,katana 会在没有任何节点的状态下继续运行。空文件可以解析,所以清空文件的效果相同。“至少一个节点”的规则只在启动时和 --test 中生效。

消息 原因 解决方法
unknown field `route`, expected one of `log`, `dns`, `node`, `outbound` 写了顶层的 [route],或其他多余的段。 把路由移到每个节点之下,写作 [node.route]。
unknown field `nodes` 写成了复数的 [[nodes]]。 [[node]] 和 [[outbound]] 都用单数。
invalid type: map, expected a sequence 节点写成了 [node] 而不是 [[node]]。 即使只有一个节点,也要用双方括号。
config defines no [[node]] entries 没有任何 [[node]] 块。 至少添加一个节点。
unknown panel_type "" [[node]] 中缺少 panel_type。 把 panel_type 设为 SSpanel、NewV2board 或 V2board。
unknown panel_type "xboard" 写的是面板的产品名,而不是它的 API 类型。 Xboard 和 V2board 使用 newV2board API:请用 NewV2board。
unknown node_type "vmess2" katana 不认识的 node_type。 使用 V2ray、Trojan、Shadowsocks 或 Hysteria2。
duplicate/reserved outbound tag direct 某个出站 tag 用了两次,或者用了 direct、freedom、block、blackhole 之一。 给出站改名。如果想为直连流量单独设置参数,在一个新 tag 下添加 protocol = "direct"。
route references unknown outbound tag: x 路由的 default 或规则的 outbound 指向了不存在的出站。这条消息不会指明是哪个节点。 修正 tag,或补上缺失的 [[outbound]]。
dns: unknown backend "UDP" (expected …) 后端名称大小写不对,或者写了 "doh" 之类的名称。 使用小写的 system、udp、tls 或 https。
dns: invalid server address: invalid socket address syntax [dns].server 写成了主机名,或者地址没有端口。 写成 IP 地址加端口,例如 "192.0.2.53:53"。
No such file or directory (os error 2) 配置文件本身,或其中引用的某个文件不存在:[dns].ca_file、geodata 文件或 Hysteria2 证书。消息不会指明是哪一个。 逐一检查路径,并使用绝对路径。

只有在节点运行后才会出现的错误,见故障排查。