流量上报
katana 统计每个用户经过节点的每一个字节,并把总量上报给面板,由面板计费。本页面向需要信任这些数字的运维人员,说明 katana 统计什么、何时上报、上报失败会怎样,以及流量丢失或被重复计费的少数几种情况。
简要概括:
- katana 在每条流的出站一侧统计载荷字节,按用户分别统计上传和下载。
- 每隔
update_periodic秒(默认 60 秒),katana 发送一次上报,其中每个计数不为零的用户占一行。 - 上报成功后,katana 只减去已上报的字节数。上报失败时不减去任何字节,下一个周期会把这些字节连同此后新增的流量一起再次发送。
- 计数器只保存在内存中。正常关闭时会发送最后一次上报;崩溃则会丢失所有尚未上报的流量。
每个节点运行一个循环。节点启动完成后,katana 等待 update_periodic 秒,然后反复执行轮询周期,每个周期按以下顺序进行:
| 步骤 | 请求 | 失败时 |
|---|---|---|
| 1. 节点信息 | 从面板获取节点参数。HTTP 304(未修改)表示沿用上一次的节点信息。 | 记录一条警告,沿用上一次的节点信息。 |
| 2. 用户 | 获取用户列表。HTTP 304 表示沿用上一次的用户列表。 | 记录一条警告,沿用上一次的用户列表。 |
| 3. 同步 | 应用所有变更:重建监听器,添加或下线用户。不发送请求。 | 如果 SSPanel 的节点信息中端口为 0,katana 记录 node 1: refreshed port is 0, keeping the last one,沿用上一次的节点信息,但仍会应用用户列表。Xboard / V2board 则把端口 0 视为步骤 1 失败。 |
| 4. 规则 | 刷新审计规则,除非设置了 disable_get_rule = true。SSPanel 从面板获取规则。对于 Xboard / V2board,katana 根据步骤 1 获取的节点配置中的 block 路由和本地规则列表构建规则,不发送请求。 |
记录一条警告,保留当前规则。 |
| 5. 流量上报 | 发送每个用户的字节计数。 | 记录一条警告,把这些字节留到下一个周期。 |
| 6. 违规上报 | 发送自上个周期以来记录的审计命中。仅限 SSPanel。 | 记录一条警告。 |
设置了 [node.hysteria].port 的 Hysteria 2 节点从配置文件获取参数,因此步骤 1 不会为它发送请求。
某一步失败不会阻止后续步骤。如果面板宕机,每一步都会各自失败,流量会一直累积,直到面板重新响应。
一次轮询的完整流程如下:
sequenceDiagram
participant T as 定时器
participant K as katana 节点
participant C as 计数器
participant P as 面板
T->>K: 每 update_periodic 秒触发一次
K->>P: GET 节点信息
K->>P: GET 用户
K->>K: 同步监听器和用户
opt disable_get_rule = false
K->>P: GET 审计规则,仅限 SSPanel
end
alt disable_upload_traffic = true
K->>C: 丢弃遗留字节,不发送
else 上报已开启
K->>C: 对所有计数器做快照
opt 至少一行不为零
K->>P: POST 流量上报
alt 面板接受
K->>C: 减去已上报的字节
else 出错或超时
K->>C: 全部保留到下一个周期
end
end
end
K->>P: POST 审计命中,仅限 SSPanel 且仅在有命中时
一些时序细节:
- 第一次上报发生在节点启动完成后整整一个
update_periodic之后。启动节点时会获取节点信息、用户和规则,但不会上报。 - 如果节点无法启动,例如面板不可达或端口仍被占用,katana 会记录
node 1: <reason>; retrying in <N>s并再次尝试。等待时间从 1 秒开始,每次失败后翻倍,上限为 60 秒和update_periodic中较小的一个。节点启动完成之前不执行任何轮询周期。在启动完成之前就被停止的节点没有传输过流量,因此没有需要上报的内容。参见节点启动失败时。 - 如果一次被接受的配置修改给节点换了新的面板客户端(修改了
[node.api]但保持节点身份不变),或者强制重建监听器(listen_ip、证书、disable_sniffing、enable_vless、[node.hysteria]或路由),katana 会立即执行一次轮询周期,其中包括流量上报。常规定时器不会被重置:除非同一次修改也改变了update_periodic,下一个预定周期仍按原来的时间到来。 - 同一节点的周期永远不会重叠。每个请求最长可耗时
[node.api].timeout秒,因此面对响应缓慢的面板,一个周期可能耗时数倍于此。当某个周期超出间隔时,katana 会立即逐个补跑错过的周期,直到追上进度。 - katana 不读取 Xboard 在节点配置中下发的
push_interval或pull_interval,节奏只由update_periodic决定。
有三个配置项影响流量上报。三者都属于 [[node]] 条目,因此每个节点可以分别设置。节点配置项的完整列表见配置文件。
| 配置项 | 类型 | 默认值 | 作用 |
|---|---|---|---|
[node.controller].update_periodic |
u64 |
60 |
轮询周期的间隔秒数,也就是上报间隔。可以设为 0,按 1 处理。 |
[node.controller].disable_upload_traffic |
bool |
false |
为 true 时,katana 继续统计,但不发送流量上报。参见关闭流量上报。 |
[node.api].timeout |
u64 |
0 |
每个面板请求(包括流量上报)的 HTTP 超时秒数。0 表示 5 秒。 |
下面的节点每 60 秒上报一次,并给响应缓慢的面板 15 秒的应答时间:
[[node]]panel_type = "NewV2board"
[node.api]host = "https://panel.example.com"key = "replace-with-the-panel-key"node_id = 1node_type = "V2ray"timeout = 15
[node.controller]update_periodic = 60disable_upload_traffic = falsecontroller 部分会拒绝未知配置项,因此拼错的配置项会报错而不是被忽略:katana 拒绝启动;运行中热重载带有拼写错误的配置会被拒绝,当前运行的配置保持不变。katana --test -c /etc/katana/config.toml 会显示该错误:
configuration error: config parse error: TOML parse error at line 13, column 1 |13 | disable_upload_trafic = false | ^^^^^^^^^^^^^^^^^^^^^unknown field `disable_upload_trafic`, expected one of `listen_ip`, `send_ip`, `update_periodic`, `disable_upload_traffic`, `disable_get_rule`, `disable_sniffing`, `cert`类型错误的值也会以同样方式失败,例如 update_periodic = "60" 会报 invalid type: string "60", expected u64。
这三个配置项都可以在 katana 运行时修改:
update_periodic和disable_upload_traffic的修改生效时不会断开连接。katana 以新的间隔重启定时器,下一个周期会读取新的开关值。[node.api].timeout的修改生效时不会断开连接。katana 以新的超时原地重建该节点的面板客户端:节点不会被替换,不发送最后一次上报,并保留其计数器。新客户端会立即执行一次轮询周期,参见轮询周期。
完整规则见热重载。
统计哪些字节
Section titled “统计哪些字节”katana 在每条流与其出站交汇的位置统计字节。此时入站协议已经去掉了自身的封装,而出站尚未加上自己的封装。因此用户被计费的是应用层载荷:
- 上传是用户发送的数据:katana 写向目标地址的字节;如果路由使用了上游代理,则是写向该上游代理的字节。
- 下载是用户收到的数据:katana 从这一侧读回的字节。
这适用于 katana 运行的每一种入站(包括 Hysteria 2),也适用于每一种流:
| 流 | 统计内容 |
|---|---|
| TCP 请求 | 双向的每一个载荷字节,贯穿流的整个生命周期。 |
| 多路复用子流 | 每条子流单独统计,方式相同。 |
| UDP | 每个发出的数据报及每个回复的载荷。 |
| 代理路由(出站是另一个代理) | 交给出站客户端的载荷,即该协议加密或封装之前的数据。 |
有些字节永远不会被统计:
| 不统计的内容 | 原因 |
|---|---|
| TLS、WebSocket、gRPC、QUIC 和协议头部、填充以及认证标签 | 它们在出站一侧之前已被去掉,或在其之后才被加上。 |
| 握手,以及从未完成认证的连接 | 此时还没有关联到用户。 |
路由器发往 block 出站的流和 UDP 包 |
流在传输任何字节之前就被拒绝,包被丢弃。 |
| 被审计规则禁止的流和 UDP 包 | 在发送之前被拒绝或丢弃。 |
| 已从面板移除的用户的新流 | katana 会拒绝这些流。 |
| katana 为解析目标地址而发起的 DNS 查询 | 这是 katana 自身的流量,不是用户的流量。 |
由于 katana 按载荷计费,面板上的数字会低于网卡上的字节计数。差值就是协议开销,取决于具体协议和传输层。
同样的字节计数也用于每用户限速,因此用户被限速的依据与其被计费的字节完全一致。参见限速。
上报如何构建
Section titled “上报如何构建”在步骤 5,katana 对每个计数器做快照,并构建一个请求:
- 每个用户一行。 各行按面板的用户 ID(
uid)合并。同一用户在同一时刻可能有多个计数器,例如一个正在使用的计数器,加上一个已离开或配置已变更的旧计数器的遗留字节。它们的字节会相加。Xboard / V2board 的载荷是以用户 ID 为键的映射,同一用户的第二行会覆盖第一行。katana 对 SSPanel 也同样合并各行。 - 跳过为零的行。 自上次成功上报以来没有传输任何字节的用户不会出现在上报中。
- 没有可上报内容时不发送请求。 如果所有行都为零,katana 在该周期不发送任何流量请求。
- 原始字节数。 katana 发送的是未经处理的字节计数。任何流量倍率(例如 Xboard 的节点倍率)都由面板计算。
请求体因面板而异:
katana 发送 POST <host>/api/v1/server/UniProxy/push,查询字符串中带有 node_id、node_type 和 token。请求体把每个用户 ID 映射到 [upload, download]:
{ "1001": [52428800, 1073741824], "1002": [0, 4096] }katana 发送 POST <host>/mod_mu/users/traffic,查询字符串中带有 key、muKey 和 node_id。请求体为每个用户列出一个对象,u 表示上传,d 表示下载:
{ "data": [{ "user_id": 1001, "u": 52428800, "d": 1073741824 }] }面板密钥放在查询字符串中传递。katana 为失败请求记录的日志只写明请求名称,不含 URL,因此密钥不会出现在日志中。
成功、失败与重试
Section titled “成功、失败与重试”面板接受上报即视为上报成功:
| 面板 | 成功条件 |
|---|---|
| Xboard / V2board | 任何低于 400 的 HTTP 状态码。不读取响应体。 |
| SSPanel | HTTP 状态码低于 400,并且如果响应体是 JSON 对象,其中须有 "ret": 1。没有 ret 的 JSON 响应体视为失败。 |
其他情况都是失败:连接错误、超时、HTTP 状态码为 400 或以上,或 SSPanel 的 ret 不等于 1。
成功时,katana 从每个计数器中精确减去已写入上报的字节数。请求进行期间流量仍在继续,这段时间新到达的字节会留在计数器中,等待下一次上报。计数器不会被清零,因此请求期间发生的流量不会丢失。
失败时,katana 不减去任何字节。这些字节原样保留,下一个周期的上报会包含它们以及此后新增的流量。没有单独的重试定时器,也没有退避:下一次尝试就是下一个周期。面板宕机一小时后恢复,会收到一次数额更大的上报。
上报失败会以 WARN 级别记录日志。日志只写明请求名称,不写原因,因此超时和 HTTP 500 看起来一样:
| 面板 | 日志行 |
|---|---|
| Xboard / V2board | node 1: report traffic: POST UniProxy push |
| SSPanel,传输错误或 HTTP 错误 | node 1: report traffic: POST /mod_mu/users/traffic |
SSPanel,状态码低于 400 但 ret 不为 1 |
node 1: report traffic: /mod_mu/users/traffic: panel returned ret=0 |
超时后的重复计费
Section titled “超时后的重复计费”为了尽量减少这种情况:
- 给面板留出足够的时间。把
[node.api].timeout调高到面板正常情况下最慢的响应时间以上。默认的 5 秒对繁忙的面板来说偏短。修改它会保留节点及其连接,参见设置。 - 留意反复出现的
report traffic警告。偶尔一次超时风险很小。更糟的是面板每个周期都存下了上报却应答太晚:katana 每个周期都会重发不断增长的全部积压流量,面板每次都会再计费一次。 - SSPanel 在已经存下流量之后却返回不等于 1 的
ret,也会造成同样的重复计费。这是面板的 bug,katana 无法纠正。
用户离开、变更或回归
Section titled “用户离开、变更或回归”用户的计数器跟随面板返回的用户列表。用户列表变化时,字节永远不会被丢弃:
| 事件 | 字节的去向 |
|---|---|
| 用户在面板上被删除、禁用或已过期 | katana 拒绝该用户的新流,并断开其已打开的连接。其未上报的字节会被保留(包括这些连接关闭过程中完成传输的字节),并在后续周期中上报,直到全部报完。 |
| 用户的实际限速发生变化(用户自身或节点的限速) | katana 以新的限速创建一个新计数器,用户的连接保持打开。旧计数器保留已统计的字节,已打开的流继续计入旧计数器;此后新建的流计入新计数器。两个计数器合并为一行,因此上报中不会出现断档。 |
| 凭据现在属于另一个用户 ID | 旧字节以旧用户 ID 上报。该凭据已打开的连接会被断开,新流量计入新用户 ID。 |
| 已删除的用户重新出现 | 该用户获得一个新计数器。之前遗留的字节按用户 ID 合并到同一行中。 |
| 面板返回空的用户列表 | katana 关闭该节点的监听器,并保留所有未上报的字节。每个周期继续上报,直到遗留字节全部发出。 |
| 监听器被重建,例如面板修改了端口或传输层,或重载了路由修改 | katana 断开该节点的连接,但计数器属于节点而不属于监听器。未变化的用户保留各自的计数器,不会丢失任何字节。 |
上报失败永远不会丢弃遗留字节:已离开用户的字节会被放回,并在下一个周期再次发送,与当前用户的字节完全一样。
用户列表变化对连接的其他影响见节点。
关闭流量上报
Section titled “关闭流量上报”设置 disable_upload_traffic = true 后,katana 仍会统计字节并执行限速,但不发送流量上报。违规上报(步骤 6)不受影响,仍会发送。
计数器不会被冻结,也不会全部保留:
| 计数器 | 上报关闭期间 |
|---|---|
| 仍在面板上且限速不变的用户 | 持续累积。由于没有上报,不会减去任何字节。 |
| 离开、限速变化或改属其他用户 ID 的用户;面板返回空用户列表时的所有用户 | 在使用旧计数器的最后一条流结束后的第一个周期,旧计数器的字节被丢弃。 |
| 已离开用户因上报失败而保留下来的字节 | 在上报关闭后的第一个周期被丢弃。 |
这样可以在不发送任何上报期间让内存占用保持有界,代价是丢弃这些遗留字节。
如果不希望这部分积压流量被计费,请重启 katana,而不是热重载:
-
停止 katana。由于
disable_upload_traffic仍为true,关闭时会跳过最后一次上报。 -
在配置文件中设置
disable_upload_traffic = false。 -
启动 katana。计数器从零开始。
停止、移除与崩溃
Section titled “停止、移除与崩溃”计数器只存在于 katana 的内存中,不会写入磁盘。未上报字节的去向取决于节点如何停止:
| 节点停止方式 | 未上报的字节 |
|---|---|
SIGTERM(systemctl stop 发送的信号)或 SIGINT |
全部发出。katana 先完成正在进行的轮询周期,关闭监听器和所有连接,然后发送最后一次流量上报;如果有审计命中,还会发送最后一次违规上报。 |
| katana 运行期间从配置文件中删除该节点 | 以同样方式全部发出,然后节点才被移除。 |
节点身份配置项发生变化:panel_type、[node.api].host、node_id、key,以及在 Xboard / V2board 上 katana 向面板请求的节点类型(小写的 node_type,或者启用了 enable_vless 的 V2ray、Vmess 或 Vless 节点为 vless) |
旧节点把字节上报给旧的面板节点,然后新节点以空计数器启动。请求的类型之所以属于节点身份,是因为 Xboard / V2board 按节点 ID 和该类型查找节点,同一个 ID 分别以 vmess 和 vless 请求时是两个面板节点。因此在 VMess 和 VLESS 之间切换时,已传输的流量上报给旧的面板节点,新的面板节点从零开始,不会有流量被计费到错误的节点上。 |
崩溃、SIGKILL、因内存不足被杀、主机断电 |
丢失:自上次成功上报以来的全部流量。大约是一个 update_periodic 的流量;如果之前的上报失败过,则更多。 |
最后一次上报只尝试一次。如果此时面板不可达,进程退出后这些字节就会丢失。请给 katana 留出停止的时间:最后一次上报最长可能耗时 [node.api].timeout 秒,如果还有轮询周期正在运行则更久。如果服务管理器更早杀掉 katana,这部分流量就会丢失。服务设置见部署。
缩短 update_periodic 可以减少崩溃时可能丢失的流量,代价是更多的面板请求。
面板侧的影响
Section titled “面板侧的影响”以下行为源于面板如何读取上报,而不是 katana:
- Xboard 节点倍率。 Xboard 在计费前会把上报的字节数乘以节点倍率。katana 始终发送原始字节数。
- Xboard 在线用户。 Xboard 把节点的在线用户数设为最近一次推送中的行数。katana 只列出该周期内有流量的用户,因此这个数字统计的是最近一次推送所在周期内有流量传输的用户。在 katana 不发送推送的周期里,Xboard 会保留之前的数字,最长一小时。katana 不单独上报在线用户或设备。
- Xboard 节点状态。 如果节点最近获取过用户列表,但 5 分钟内没有收到推送,Xboard 会把节点显示为在线但未推送。katana 在所有行都为零时不发送推送,因此空闲节点会显示这种状态。这并不表示上报出了问题。
各面板的其他行为见面板页面:Xboard 和 V2board、SSPanel。
| 现象 | 可能原因 | 处理方法 |
|---|---|---|
| 面板上某个节点没有流量 | disable_upload_traffic = true、每次上报都失败,或节点尚未启动完成。 |
检查该开关。在 katana 日志中查找 report traffic 警告,以及 …; retrying in … 错误,例如 node_info failed 或 initial start failed。 |
每个周期都出现 report traffic: POST UniProxy push |
面板不可达、拒绝了 token,或在超时之后才应答。 | 检查 [node.api].host 和 key,再查看面板自身的日志。如果面板响应慢,调高 [node.api].timeout。 |
SSPanel 上出现 panel returned ret=0 |
SSPanel 拒绝了该上报。 | 检查 key 和 node_id,并在面板日志中查找原因。 |
| 用户被计费的流量多于 katana 实际为其传输的流量 | 超时的上报被再次发送,或 Xboard 节点倍率大于 1。协议开销不会导致这种情况,因为 katana 计费的字节少于线路上实际传输的字节。 | 检查节点倍率。参见超时后的重复计费。 |
| 用户被计费的流量少于网卡计数器显示的值 | 属于正常现象:头部、TLS 和传输层封装不计费。 | 参见统计哪些字节。 |
| 面板故障恢复或配置变更后立刻出现流量尖峰 | 失败上报积压的流量,或重新开启了上报。 | 属于正常现象。参见成功、失败与重试和关闭流量上报。 |
更多现象和日志行见故障排查。