跳转到内容

流量上报

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 秒的应答时间:

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

controller 部分会拒绝未知配置项,因此拼错的配置项会报错而不是被忽略: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 以新的超时原地重建该节点的面板客户端:节点不会被替换,不发送最后一次上报,并保留其计数器。新客户端会立即执行一次轮询周期,参见轮询周期。

完整规则见热重载。

katana 在每条流与其出站交汇的位置统计字节。此时入站协议已经去掉了自身的封装,而出站尚未加上自己的封装。因此用户被计费的是应用层载荷:

  • 上传是用户发送的数据:katana 写向目标地址的字节;如果路由使用了上游代理,则是写向该上游代理的字节。
  • 下载是用户收到的数据:katana 从这一侧读回的字节。

这适用于 katana 运行的每一种入站(包括 Hysteria 2),也适用于每一种流:

流 统计内容
TCP 请求 双向的每一个载荷字节,贯穿流的整个生命周期。
多路复用子流 每条子流单独统计,方式相同。
UDP 每个发出的数据报及每个回复的载荷。
代理路由(出站是另一个代理) 交给出站客户端的载荷,即该协议加密或封装之前的数据。

有些字节永远不会被统计:

不统计的内容 原因
TLS、WebSocket、gRPC、QUIC 和协议头部、填充以及认证标签 它们在出站一侧之前已被去掉,或在其之后才被加上。
握手,以及从未完成认证的连接 此时还没有关联到用户。
路由器发往 block 出站的流和 UDP 包 流在传输任何字节之前就被拒绝,包被丢弃。
被审计规则禁止的流和 UDP 包 在发送之前被拒绝或丢弃。
已从面板移除的用户的新流 katana 会拒绝这些流。
katana 为解析目标地址而发起的 DNS 查询 这是 katana 自身的流量,不是用户的流量。

由于 katana 按载荷计费,面板上的数字会低于网卡上的字节计数。差值就是协议开销,取决于具体协议和传输层。

同样的字节计数也用于每用户限速,因此用户被限速的依据与其被计费的字节完全一致。参见限速。

在步骤 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 为失败请求记录的日志只写明请求名称,不含 URL,因此密钥不会出现在日志中。

面板接受上报即视为上报成功:

面板 成功条件
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

为了尽量减少这种情况:

  • 给面板留出足够的时间。把 [node.api].timeout 调高到面板正常情况下最慢的响应时间以上。默认的 5 秒对繁忙的面板来说偏短。修改它会保留节点及其连接,参见设置。
  • 留意反复出现的 report traffic 警告。偶尔一次超时风险很小。更糟的是面板每个周期都存下了上报却应答太晚:katana 每个周期都会重发不断增长的全部积压流量,面板每次都会再计费一次。
  • SSPanel 在已经存下流量之后却返回不等于 1 的 ret,也会造成同样的重复计费。这是面板的 bug,katana 无法纠正。

用户的计数器跟随面板返回的用户列表。用户列表变化时,字节永远不会被丢弃:

事件 字节的去向
用户在面板上被删除、禁用或已过期 katana 拒绝该用户的新流,并断开其已打开的连接。其未上报的字节会被保留(包括这些连接关闭过程中完成传输的字节),并在后续周期中上报,直到全部报完。
用户的实际限速发生变化(用户自身或节点的限速) katana 以新的限速创建一个新计数器,用户的连接保持打开。旧计数器保留已统计的字节,已打开的流继续计入旧计数器;此后新建的流计入新计数器。两个计数器合并为一行,因此上报中不会出现断档。
凭据现在属于另一个用户 ID 旧字节以旧用户 ID 上报。该凭据已打开的连接会被断开,新流量计入新用户 ID。
已删除的用户重新出现 该用户获得一个新计数器。之前遗留的字节按用户 ID 合并到同一行中。
面板返回空的用户列表 katana 关闭该节点的监听器,并保留所有未上报的字节。每个周期继续上报,直到遗留字节全部发出。
监听器被重建,例如面板修改了端口或传输层,或重载了路由修改 katana 断开该节点的连接,但计数器属于节点而不属于监听器。未变化的用户保留各自的计数器,不会丢失任何字节。

上报失败永远不会丢弃遗留字节:已离开用户的字节会被放回,并在下一个周期再次发送,与当前用户的字节完全一样。

用户列表变化对连接的其他影响见节点。

设置 disable_upload_traffic = true 后,katana 仍会统计字节并执行限速,但不发送流量上报。违规上报(步骤 6)不受影响,仍会发送。

计数器不会被冻结,也不会全部保留:

计数器 上报关闭期间
仍在面板上且限速不变的用户 持续累积。由于没有上报,不会减去任何字节。
离开、限速变化或改属其他用户 ID 的用户;面板返回空用户列表时的所有用户 在使用旧计数器的最后一条流结束后的第一个周期,旧计数器的字节被丢弃。
已离开用户因上报失败而保留下来的字节 在上报关闭后的第一个周期被丢弃。

这样可以在不发送任何上报期间让内存占用保持有界,代价是丢弃这些遗留字节。

如果不希望这部分积压流量被计费,请重启 katana,而不是热重载:

  1. 停止 katana。由于 disable_upload_traffic 仍为 true,关闭时会跳过最后一次上报。

  2. 在配置文件中设置 disable_upload_traffic = false。

  3. 启动 katana。计数器从零开始。

计数器只存在于 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 可以减少崩溃时可能丢失的流量,代价是更多的面板请求。

以下行为源于面板如何读取上报,而不是 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 和传输层封装不计费。 参见统计哪些字节。
面板故障恢复或配置变更后立刻出现流量尖峰 失败上报积压的流量,或重新开启了上报。 属于正常现象。参见成功、失败与重试和关闭流量上报。

更多现象和日志行见故障排查。