Skip to content

Traffic reporting

katana counts every byte a user moves through a node and reports the totals to the panel, which bills them. This page is for operators who need to trust those numbers. It explains what katana counts, when it reports, what a failed report does, and the few situations in which traffic is lost or billed twice.

The short version:

  • katana counts payload bytes on the outbound side of each flow, upload and download separately, per user.
  • Every update_periodic seconds (60 by default) it sends one report with a row for every user whose counters are not zero.
  • After a successful report it subtracts exactly the reported bytes. After a failed report it subtracts nothing, and the next cycle sends the same bytes again, together with whatever came in since.
  • Counters live in memory. A clean shutdown sends one final report. A crash loses whatever was not reported yet.

Each node runs one loop. After the node has come up, katana waits update_periodic seconds and then runs one poll cycle, again and again, in this order:

Step Request On failure
1. Node info Fetch the node’s parameters from the panel. An HTTP 304 (not modified) keeps the last node info. Log a warning and keep the last node info.
2. Users Fetch the user list. An HTTP 304 keeps the last user list. Log a warning and keep the last user list.
3. Reconcile Apply any change: rebuild the listener, add or retire users. No request. If the SSPanel node info carries port 0, katana logs node 1: refreshed port is 0, keeping the last one, keeps the last node info and still applies the user list. Xboard / V2board treats port 0 as a failed step 1 instead.
4. Rules Refresh the audit rules, unless disable_get_rule = true. SSPanel fetches them from the panel. For Xboard / V2board katana builds them from the block routes of the node config it fetched in step 1 and from the local rule list, without a request. Log a warning and keep the current rules.
5. Traffic report Send the per-user byte counts. Log a warning and keep the bytes for the next cycle.
6. Illegal report Send the audit hits recorded since the last cycle. SSPanel only. Log a warning.

A Hysteria 2 node with [node.hysteria].port set takes its parameters from the configuration file, so step 1 sends no request for it.

A failure in one step does not stop the later ones. If the panel is down, every step fails on its own, and the traffic keeps accumulating until the panel answers again.

The whole cycle for one poll looks like this:

sequenceDiagram
    participant T as Timer
    participant K as katana node
    participant C as Counters
    participant P as Panel
    T->>K: tick every update_periodic seconds
    K->>P: GET node info
    K->>P: GET users
    K->>K: reconcile listener and users
    opt disable_get_rule = false
        K->>P: GET audit rules, SSPanel only
    end
    alt disable_upload_traffic = true
        K->>C: drop leftovers, send nothing
    else reporting on
        K->>C: snapshot all counters
        opt at least one row is not zero
            K->>P: POST traffic report
            alt panel accepts it
                K->>C: subtract the reported bytes
            else error or timeout
                K->>C: keep everything for the next cycle
            end
        end
    end
    K->>P: POST audit hits, SSPanel only and only if any

Some timing details:

  • The first report comes one full update_periodic after the node is up. Bringing the node up fetches node info, users and rules, but does not report.
  • If the node cannot come up, for example because the panel is unreachable or the port is still taken, katana logs node 1: <reason>; retrying in <N>s and tries again. The wait starts at 1 second and doubles after each failure, up to 60 seconds or update_periodic, whichever is shorter. Until the node is up it runs no poll cycles. A node that is stopped before it came up has carried no traffic, so it has nothing to report. See When a node fails to start.
  • An accepted configuration edit that gives the node a new panel client (a change to [node.api] that keeps the node’s identity) or forces a listener rebuild (listen_ip, the certificate, disable_sniffing, enable_vless, [node.hysteria] or the route) runs one poll cycle at once, traffic report included. The regular timer is not reset: unless the same edit changes update_periodic, the next scheduled cycle comes at its usual time.
  • The cycles of one node never overlap. Each request can take up to [node.api].timeout seconds, so a cycle against a slow panel can take several times that. When a cycle overruns the period, katana starts the missed cycles immediately, one after another, until it has caught up.
  • katana does not read the push_interval or pull_interval that Xboard sends in its node config. update_periodic alone sets the pace.

Three keys shape traffic reporting. All three belong to a [[node]] entry, so each node can set its own. The full list of node keys is on Configuration file.

Key Type Default Effect
[node.controller].update_periodic u64 60 Seconds between poll cycles, which is also the report interval. 0 is accepted and treated as 1.
[node.controller].disable_upload_traffic bool false When true, katana keeps counting but sends no traffic reports. See Turning reporting off.
[node.api].timeout u64 0 HTTP timeout in seconds for every panel request, the traffic report included. 0 means 5 seconds.

A node that reports every 60 seconds and gives a slow panel 15 seconds to answer:

/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

The controller section rejects unknown keys, so a misspelt key is an error instead of being ignored: katana refuses to start, and a live reload with the typo is rejected while the running configuration stays in place. katana --test -c /etc/katana/config.toml shows the error:

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`

A value of the wrong type fails the same way, for example update_periodic = "60" gives invalid type: string "60", expected u64.

All three keys can be changed while katana runs:

  • update_periodic and disable_upload_traffic take effect without dropping connections. katana restarts its timer with the new period, and the next cycle reads the new flag.
  • [node.api].timeout takes effect without dropping connections. katana rebuilds the node’s panel client in place with the new timeout: the node is not replaced, sends no final report and keeps its counters. The new client runs one poll cycle at once, as described in The poll cycle.

See Hot reload for the full rules.

katana counts bytes where each flow meets its outbound. By then the inbound protocol has removed its framing, and the outbound has not yet added its own. What the user is billed for is therefore the application payload:

  • Upload is what the user sent: the bytes katana wrote towards the destination, or towards the upstream proxy when the route uses one.
  • Download is what the user received: the bytes katana read back from that side.

This applies to every inbound katana runs, Hysteria 2 included, and to each kind of flow:

Flow What is counted
TCP request Every payload byte in both directions, for as long as the flow lasts.
Multiplexed sub-flow Each sub-flow on its own, in the same way.
UDP The payload of each datagram that leaves, and of each reply.
Proxied route (the outbound is another proxy) The payload handed to the outbound client, before that protocol encrypts or frames it.

Some bytes are never counted:

Not counted Why
TLS, WebSocket, gRPC, QUIC and protocol headers, padding and authentication tags They are removed before the outbound side, or added after it.
Handshakes, and connections that never authenticate No user is attached yet.
Flows and UDP packets the router sends to a block outbound Flows are refused before any byte moves. Packets are dropped.
Flows and UDP packets that an audit rule forbids Refused or dropped before they are sent.
New flows of a user who has left the panel katana refuses them.
DNS lookups katana makes to resolve a destination They are katana’s own traffic, not the user’s.

Because katana bills payload, a panel’s figures are lower than the byte counts at the network interface. The difference is the protocol overhead, which depends on the protocol and the transport.

The same byte counts drive the per-user speed limit, so a user is limited on exactly the bytes they are billed for. See Speed limits.

At step 5 katana takes a snapshot of every counter and builds one request:

  • One row per user. Rows are merged by the panel’s user ID (uid). A user can have more than one counter at a time, for example a live one and the leftover of a departed or reconfigured one. Their bytes are added together. The Xboard / V2board payload is a map keyed by user ID, where a second row for the same user would overwrite the first. katana merges the rows for SSPanel too.
  • Zero rows are skipped. A user who moved no bytes since the last successful report is not listed.
  • No request when there is nothing to report. If every row is zero, katana sends no traffic request at all in that cycle.
  • Raw bytes. katana sends plain byte counts. Any traffic multiplier, such as Xboard’s node rate, is applied by the panel.

The request body depends on the panel:

katana sends POST <host>/api/v1/server/UniProxy/push, with node_id, node_type and token in the query string. The body maps each user ID to [upload, download]:

{ "1001": [52428800, 1073741824], "1002": [0, 4096] }

The panel key travels in the query string. katana’s log line for a failed request names the request, not the URL, so the key does not appear in its logs.

A report succeeds when the panel accepts it:

Panel Success
Xboard / V2board Any HTTP status below 400. The body is not read.
SSPanel An HTTP status below 400, and, if the body is a JSON object, "ret": 1 in it. A JSON body without ret counts as a failure.

Everything else is a failure: a connection error, a timeout, an HTTP status of 400 or above, or an SSPanel ret other than 1.

On success, katana subtracts from each counter exactly the bytes it put in the report. Traffic keeps flowing while the request is in flight, and the bytes that arrive during that time stay in the counter for the next report. Nothing is reset to zero, so nothing that happens during the request is lost.

On failure, katana subtracts nothing. The bytes stay where they were, and the next cycle’s report contains them plus whatever came in since. There is no separate retry timer and no backoff: the next attempt is the next cycle. A panel that is down for an hour receives one larger report when it comes back.

A failed report is logged at WARN level. The message names the request, not the cause, so a timeout and an HTTP 500 look the same:

Panel Log line
Xboard / V2board node 1: report traffic: POST UniProxy push
SSPanel, transport error or HTTP error node 1: report traffic: POST /mod_mu/users/traffic
SSPanel, status below 400 with ret not 1 node 1: report traffic: /mod_mu/users/traffic: panel returned ret=0

To make this rare:

  • Give the panel enough time. Raise [node.api].timeout above the panel’s slowest normal response time. The default of 5 seconds is short for a busy panel. Changing it keeps the node and its connections, as described in Settings.
  • Watch for repeated report traffic warnings. One occasional timeout is a small risk. A panel that stores each report but answers too late every cycle is worse: katana resends the whole growing backlog each cycle, and the panel bills it again each time.
  • An SSPanel that answers ret other than 1 after it has already stored the traffic causes the same duplicate. That is a panel bug, and katana cannot correct it.

A user’s counter follows the user list the panel returns. Bytes are never dropped when that list changes:

Event What happens to the bytes
The user is removed, disabled or expired on the panel katana refuses the user’s new flows and ends their open connections. Their unreported bytes are kept, including bytes that finish moving while those connections close, and are reported in later cycles until nothing is left.
The user’s effective speed limit changes (the user’s or the node’s limit) katana starts a new counter with the new limit. The user’s connections stay open. The old counter keeps the bytes already counted, and flows that were already open keep counting into it; flows opened from then on count into the new one. Both counters are merged into one row, so the report shows no gap.
The credential now belongs to a different user ID The old bytes are reported under the old user ID. The credential’s open connections are ended, and new traffic counts towards the new user ID.
A removed user comes back The user gets a new counter. Any leftover bytes from before are merged into the same row by user ID.
The panel returns an empty user list katana closes the node’s listener and keeps every unreported byte. Reports continue each cycle until the leftovers are sent.
The listener is rebuilt, for example because the panel changed the port or transport, or a route edit was reloaded katana drops the node’s connections, but the counters belong to the node, not to the listener. Unchanged users keep their counters, and no byte is lost.

A failed report never discards leftovers: bytes of departed users are put back and sent again in the next cycle, exactly like the bytes of current users.

See Nodes for what else a user-list change does to connections.

With disable_upload_traffic = true, katana still counts bytes and still enforces speed limits, but it sends no traffic report. The illegal report (step 6) is not affected and is still sent.

The counters are not frozen, and they are not all kept:

Counter While reporting is off
Users who stay on the panel with the same speed limit Keep accumulating. Nothing is subtracted, because nothing is reported.
Users who leave, change speed limit or move to another user ID, and every user when the panel returns an empty user list Their old counter’s bytes are discarded at the first cycle after the last flow using that counter has ended.
Bytes held back by failed reports for users who have already left Discarded at the first cycle with reporting off.

This keeps memory bounded while nothing is sent, at the cost of the leftovers.

If you do not want that backlog billed, restart katana instead of reloading it:

  1. Stop katana. Because disable_upload_traffic is still true, the final report at shutdown is skipped.

  2. Set disable_upload_traffic = false in the configuration file.

  3. Start katana. The counters start from zero.

The counters exist only in katana’s memory. Nothing is written to disk. What happens to unreported bytes depends on how the node stops:

How the node stops Unreported bytes
SIGTERM (what systemctl stop sends) or SIGINT Flushed. katana finishes any poll cycle in progress, closes the listener and every connection, then sends one final traffic report and, if there are audit hits, one final illegal report.
The node is removed from the configuration file while katana runs Flushed the same way, before the node is gone.
A node identity key changes: panel_type, [node.api].host, node_id, key, and on Xboard / V2board the node type katana asks the panel for (node_type in lower case, or vless for a V2ray, Vmess or Vless node with enable_vless) The old node is flushed to its old panel node, then the new node starts with empty counters. The requested type is part of the identity because Xboard / V2board finds a node by its ID and that type, so the same ID asked as vmess and as vless is two panel nodes. Switching between VMess and VLESS therefore reports the traffic carried so far to the old panel node, and the new panel node starts from zero, so no traffic is billed to the wrong node.
Crash, SIGKILL, out-of-memory kill, host power loss Lost: everything since the last successful report. That is about one update_periodic of traffic, more if earlier reports had failed.

The final report is a single attempt. If the panel is unreachable at that moment, the bytes are lost when the process exits. Give katana time to stop: the final report can take up to [node.api].timeout seconds, longer if a poll cycle was still running. A service manager that kills katana sooner loses that traffic. See Deployment for the service settings.

A shorter update_periodic narrows what a crash can lose, at the cost of more panel requests.

These follow from how the panels read the report, not from katana:

  • Xboard node rate. Xboard multiplies the reported bytes by the node’s rate before it bills them. katana always sends raw bytes.
  • Xboard online users. Xboard sets a node’s online-user figure to the number of rows in the latest push. katana lists only users with traffic in that cycle, so the figure counts the users who moved bytes in the cycle of the last push. In a cycle where katana sends no push, Xboard keeps the previous figure, for up to an hour. katana does not report online users or devices separately.
  • Xboard node status. Xboard shows a node as online but without a push when it has fetched its user list recently but received no push for 5 minutes. katana sends no push when all rows are zero, so an idle node shows that status. It does not mean reporting is broken.

The panel pages cover the rest of each panel’s behaviour: Xboard and V2board, SSPanel.

Symptom Likely cause What to do
The panel shows no traffic for a node disable_upload_traffic = true, every report fails, or the node has not come up yet. Check the flag. Look for report traffic warnings, and for …; retrying in … errors such as node_info failed or initial start failed, in katana’s log.
report traffic: POST UniProxy push every cycle The panel is unreachable, rejects the token, or answers after the timeout. Check [node.api].host and key, then the panel’s own logs. Raise [node.api].timeout if the panel is slow.
panel returned ret=0 on SSPanel SSPanel refused the report. Check key and node_id, and the panel’s log for the reason.
Users are billed more than katana carried for them A timed-out report was sent again, or the Xboard node rate is above 1. Protocol overhead cannot cause this, because katana bills less than the wire carries. Check the node rate. See Double billing after a timeout.
Users are billed less than the interface counters show Expected: headers, TLS and transport framing are not billed. See What is counted.
A large spike right after a panel outage or a config change The backlog of failed reports, or re-enabled reporting. Expected. See Success, failure and retry and Turning reporting off.

More symptoms and log lines are on Troubleshooting.