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 83b7304 to 8a42af3 Compare June 8, 2026 00:52
@nekohasekai nekohasekai force-pushed the testing branch 12 times, most recently from 8ea55ff to a9fe3f3 Compare June 17, 2026 06:56
@nekohasekai nekohasekai force-pushed the testing branch 5 times, most recently from 2ee20d0 to ecc4776 Compare June 25, 2026 09:40
@nekohasekai nekohasekai force-pushed the testing branch 3 times, most recently from 6b41423 to 585d3e6 Compare July 5, 2026 04:47
@nekohasekai nekohasekai force-pushed the testing branch 8 times, most recently from 8bdafbb to 9ec7cc8 Compare July 8, 2026 08:30
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