Skip to content

experimental/clashapi: emit "user" field in TrackerMetadata.MarshalJSON#4159

Open
PavelLizunov wants to merge 97 commits into
SagerNet:testingfrom
PavelLizunov:feat/clashapi-emit-user-field
Open

experimental/clashapi: emit "user" field in TrackerMetadata.MarshalJSON#4159
PavelLizunov wants to merge 97 commits into
SagerNet:testingfrom
PavelLizunov:feat/clashapi-emit-user-field

Conversation

@PavelLizunov

Copy link
Copy Markdown

Summary

TrackerMetadata.MarshalJSON (in experimental/clashapi/trafficontrol/tracker.go)
emits a fixed set of metadata keys but silently drops the authenticated
user identifier. This PR adds a single key "user": t.Metadata.User to
the marshalled JSON.

Why

adapter.InboundContext.User is populated server-side by every auth-bearing
inbound (VLESS, TUIC, Trojan, etc) — the data exists, it just never
reaches the Clash-API wire. Downstream consumers that need per-user
attribution (abuse detection, per-account quota visualisation,
multi-tenant dashboards) have no way to associate a connection with the
account that opened it.

What changes

"metadata": map[string]any{
    "network":         t.Metadata.Network,
    "type":            inbound,
+   "user":            t.Metadata.User,    // ← added
    "sourceIP":        t.Metadata.Source.Addr,
    ...
}

For inbounds WITHOUT an authenticated user (direct/socks/tun) User is
the empty string — preserves existing observable behaviour for those
inbounds.

Compatibility

  • No schema migration — JSON object gets one new optional key.
  • No protocol break — Clash-API clients ignore unknown fields per
    the de-facto convention.
  • No server-side behaviour change — the field is already populated
    upstream of MarshalJSON; this only changes what gets marshalled.

Real-world driver

A Rust-based VPN-fleet control plane (github.com/PavelLizunov/vpnctl)
polls clash-api on each node to attribute per-connection traffic to
inv.db user_ids. The attribution column in its vpn_connection_stats
table is NULL on every row today because User is never on the wire —
even though authenticated VLESS connections populate it server-side.

Test plan

  • Builds clean (1-line addition to an existing literal).
  • Manual: enable clash-api on a dev sing-box, authenticate via VLESS,
    curl localhost:9090/connections → metadata now contains "user": "<uuid-or-name>".
  • Unauthenticated inbound (direct/tun) still emits "user": "" (no regression).

@nekohasekai nekohasekai force-pushed the testing branch 2 times, most recently from de161cf to 83b7304 Compare June 3, 2026 05:05
@nekohasekai nekohasekai force-pushed the testing branch 7 times, most recently from 1ed57eb to 8247670 Compare June 13, 2026 15:01
@nekohasekai nekohasekai force-pushed the testing branch 10 times, most recently from f7ca395 to f27d0e3 Compare June 21, 2026 04:16
@nekohasekai nekohasekai force-pushed the testing branch 2 times, most recently from 22fe3b6 to b2d51f6 Compare June 29, 2026 03:43
@nekohasekai nekohasekai force-pushed the testing branch 9 times, most recently from b37feb5 to aaa1289 Compare July 7, 2026 16:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants