Skip to content

stats: bytes received from a WireGuard peer are reported as downlink (directions swapped) #126

Open

Description

@Free-Guy-IR

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-110 fills Rx from wgctrl's peer.ReceiveBytes and Tx from peer.TransmitBytes. wgctrl's userspace parser fills ReceiveBytes from the UAPI field rx_bytes (internal/wguser/parse.go:165-166 in wgctrl v0.0.0-20241231184526-a9ab2273dd10).
  • pkg/stats/interface_counters.go:51-75 (buildDeltaStats) emits tx as uplink and rx as downlink. It is used for the per-user stats (GetStats / GetUsersStats in pkg/stats/tracker.go) and, through BuildInterfaceStats, for the interface outbound stats. The interface counters come from netlink RxBytes / TxBytes of 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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions