From 5fc4b9f4633874d48cdc20f8e3ed8c07cbea0370 Mon Sep 17 00:00:00 2001 From: Sanaei Date: Fri, 4 Sep 2026 02:48:35 +0200 Subject: [PATCH] fix(node): let a node-reported tag outrank a stale adopted alias MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The alias re-application added in 0775fcaa wrote every adoptedAliases entry onto the rebuilt map unconditionally, so an alias could override the id the node itself reported for that same central tag. adoptedAliases is never pruned — cacheDel clears remoteIDByTag and pushedFP only — so the entry outlives the pairing that created it. That inverts the intended precedence: once a push renames a node inbound to the central tag, the node reports it directly, and a stale alias pointing at some other inbound reusing the old name would win. Every state-changing op on that inbound then targets the wrong one, overwriting or deleting an inbound the operator created separately. The alias now only fills a gap: a central tag the node already reports is left alone. --- internal/web/runtime/remote.go | 5 +++++ internal/web/runtime/remote_test.go | 35 ++++++++++++++++++++++++++--- 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/internal/web/runtime/remote.go b/internal/web/runtime/remote.go index b6c8cef6d..8f8cc7246 100644 --- a/internal/web/runtime/remote.go +++ b/internal/web/runtime/remote.go @@ -407,6 +407,11 @@ func (r *Remote) refreshRemoteIDs(ctx context.Context) error { // A rebuild sees only node-reported tags, so the adopted aliases must be // re-applied or a later op on an adopted inbound re-creates it as a duplicate. for centralTag, nodeTag := range r.adoptedAliases { + // A tag the node reports itself is authoritative; the alias only fills + // the gap left for a central tag the node knows under another name. + if _, reported := next[centralTag]; reported { + continue + } if id, ok := next[nodeTag]; ok { next[centralTag] = id } diff --git a/internal/web/runtime/remote_test.go b/internal/web/runtime/remote_test.go index bd0916eaa..8b152d2b7 100644 --- a/internal/web/runtime/remote_test.go +++ b/internal/web/runtime/remote_test.go @@ -441,9 +441,8 @@ func TestSanitizeStreamSettingsForRemote(t *testing.T) { } } -// An adopted alias maps a central tag onto a differently-named node inbound. -// refreshRemoteIDs rebuilds the cache from node-reported tags only, so the -// alias must be re-applied or every later op on that inbound misses. +// refreshRemoteIDs rebuilds the cache from node-reported tags only, so an +// adopted alias must be re-applied or every later op on that inbound misses. func TestRemoteAdoptedAliasSurvivesRefresh(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { w.Header().Set("Content-Type", "application/json") @@ -472,3 +471,33 @@ func TestRemoteAdoptedAliasSurvivesRefresh(t *testing.T) { t.Fatalf("adopted alias resolved to %d, want 5", id) } } + +// A stale alias must never outrank the node's own report: once the node lists +// an inbound under the central tag itself, that id is the authoritative one. +func TestRemoteAdoptedAliasYieldsToNodeReportedTag(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + w.Header().Set("Content-Type", "application/json") + if req.URL.Path == "/panel/api/inbounds/list" { + _, _ = w.Write([]byte(`{"success":true,"obj":[{"id":5,"tag":"central-in"},{"id":7,"tag":"legacy-in"},{"id":9,"tag":"in-2"}]}`)) + return + } + http.NotFound(w, req) + })) + defer srv.Close() + + r := NewRemote(nodeForPlainServer(t, srv, "verify", "tok"), nil) + central := &model.Inbound{Tag: "central-in", Settings: `{"clients":[]}`} + r.AdoptInboundAlias(central, RemoteInboundOption{Id: 7, Tag: "legacy-in"}) + + if _, err := r.resolveRemoteID(context.Background(), "in-2"); err != nil { + t.Fatalf("resolveRemoteID(in-2): %v", err) + } + + id, err := r.resolveRemoteID(context.Background(), central.Tag) + if err != nil { + t.Fatalf("resolveRemoteID(%s): %v", central.Tag, err) + } + if id != 5 { + t.Fatalf("central tag resolved to %d via a stale alias, want 5 (the id the node reports)", id) + } +}