Repository navigation
Description
Found by code review and confirmed with real wireguard-go counters; never seen in production.
Checked against node dev 9ccd263 and main 739eab9 (v0.5.7), and panel dev f92acaf51cec. PR #131 contains the fix.
Summary
For WireGuard users and the WireGuard interface outbound stats, the node reports the bytes the server received from the client (the client's upload) as downlink, and the bytes it sent to the client as uplink. Xray reports client-to-server traffic as uplink, and the node's xray backend passes Xray's type through unchanged (backend/xray/api/stats.go:158-168). So does the MTProto backend that dev gained in #120, so WireGuard is the only backend with the directions swapped.
Root cause
At 9ccd263:
backend/wireguard/jobs.go:104-110fillsRxfrom wgctrl'speer.ReceiveBytesandTxfrompeer.TransmitBytes. wgctrl's userspace parser fillsReceiveBytesfrom the UAPI fieldrx_bytes(internal/wguser/parse.go:165-166in wgctrl v0.0.0-20241231184526-a9ab2273dd10).pkg/stats/interface_counters.go:51-75(buildDeltaStats) emitstxasuplinkandrxasdownlink. It is used for the per-user stats (GetStats/GetUsersStatsinpkg/stats/tracker.go) and, throughBuildInterfaceStats, for the interface outbound stats. The interface counters come from netlinkRxBytes/TxBytesof the WireGuard link (backend/wireguard/manager.go:357-373).
The MTProto backend does not use buildDeltaStats. It builds its own stats (backend/mtproto/tracker.go:265-274), with uplink from telemt's telemt_user_octets_from_client_total (backend/mtproto/metrics.go:19-22). When telemt suppresses per-user series (more than 4096 users), it reports both directions summed as downlink with uplink 0 (backend/mtproto/stats.go:234-244). That is a separate, documented choice and not this issue.
Measurement
Real userspace wireguard-go (golang.zx2c4.com/wireguard v0.0.0-20261024220952-b3bc704ef0ee, the version in the node's go.mod): two devices with gVisor netstack TUNs, UDP on 127.0.0.1, macOS arm64. The client uploaded 1 MiB over TCP through the tunnel, then downloaded 300000 bytes. The server device's counters for the client peer were fed into the node's pkg/stats at 9ccd263:
server received 1048576 bytes of client upload
after 1 MiB client upload: server peer(client) rx_bytes=1114692 tx_bytes=5372 -> node GetUsersStats uplink=5372 downlink=1114692; BuildInterfaceStats uplink=5372 downlink=1114692
client received 300000 bytes of download
download phase only (delta): server peer(client) rx_bytes=864 tx_bytes=318912 -> node GetUsersStats uplink=318912 downlink=864; BuildInterfaceStats uplink=318912 downlink=864
So rx_bytes is the client's upload, and the node reports it as downlink.
For comparison, real Xray 26.3.27 (macOS arm64) with a dokodemo-door inbound and a freedom outbound, same traffic: inbound>>>client-in>>>traffic>>>uplink = 1048576, outbound>>>direct>>>traffic>>>uplink = 1048576, and both downlink counters = 300000.
Not measured: Linux kernel WireGuard, and the netlink interface counters. In the run above, BuildInterfaceStats was fed the peer counters, to show its mapping only.
Impact
Display only. Totals are not affected, so billing and limits are correct. Panel dev f92acaf51cec:
- per-user stats are summed without looking at the direction (
app/jobs/record_usages.py:821-823); - a node's limit uses
downlink + uplink(app/db/models.py:773-787).
What is wrong is the direction split. The panel maps an outbound stat of type uplink to up and everything else to down (app/jobs/record_usages.py:925-933). That feeds Node.uplink/downlink and System.uplink/downlink (app/jobs/record_usages.py:1177-1197), and System.uplink is returned as incoming_bandwidth (app/operation/system.py:101-102). For a WireGuard node these values are swapped. A fix corrects new samples only; history already recorded keeps the swapped split.
Proposed fix
Emit rx (bytes received from the peer) as uplink and tx as downlink in buildDeltaStats. The panel has no WireGuard-specific handling of directions, so this does not cause a double swap.
Nine existing tests (six in pkg/stats/tracker_test.go, three in backend/wireguard/stats_outbound_test.go) assert the current, swapped mapping. With only the fix applied they fail and need their uplink / downlink labels exchanged.
Duplicate search
Searched PasarGuard/node issues and PRs on 2026-11-07 for wireguard uplink and uplink downlink. Apart from this issue and PR #131, the only hit is #120 (MTProto backend, merged), which is unrelated.
Activity