From d34760523389f7153cc8c63118f3eded4182d155 Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Wed, 27 May 2026 18:34:18 +0200 Subject: [PATCH] fix(remote-traffic): handle tag collisions + readable warning format MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit setRemoteTrafficLocked attempted to INSERT a new central inbound for every snap whose tag was not in tagToCentral (which is scoped by node_id). When a different owner — the local panel or another node — already held the tag, the INSERT tripped the UNIQUE constraint on inbounds.tag and re-fired on every periodic snap. Pre-check for tag ownership before the INSERT. If a different owner holds it, log once per (nodeID, tag) via a sync.Map dedupe and skip silently from then on. Real DB errors still surface. Also switch the six setRemoteTraffic warnings from logger.Warning(...) to logger.Warningf("%q ... %v", ...) — fmt.Sprint only inserts spaces between adjacent non-string operands, so the all-string call sites produced runs like "taginbound-443failed:UNIQUE...". Format strings also let us quote the tag with %q so it stands out from the prose. --- web/service/inbound.go | 32 +++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/web/service/inbound.go b/web/service/inbound.go index 3ab380b8..b0184b20 100644 --- a/web/service/inbound.go +++ b/web/service/inbound.go @@ -5,10 +5,12 @@ package service import ( "context" "encoding/json" + "errors" "fmt" "sort" "strconv" "strings" + "sync" "time" "github.com/google/uuid" @@ -23,6 +25,8 @@ import ( "gorm.io/gorm/clause" ) +var reportedRemoteTagConflict sync.Map + type InboundService struct { xrayApi xray.XrayAPI clientService ClientService @@ -1313,6 +1317,24 @@ func (s *InboundService) setRemoteTrafficLocked(nodeID int, snap *runtime.Traffi c, ok := tagToCentral[snapIb.Tag] if !ok { + var owner model.Inbound + if err := tx.Where("tag = ?", snapIb.Tag).First(&owner).Error; err == nil { + key := fmt.Sprintf("%d:%s", nodeID, snapIb.Tag) + if _, seen := reportedRemoteTagConflict.LoadOrStore(key, struct{}{}); !seen { + ownerLabel := "the local panel" + if owner.NodeID != nil { + ownerLabel = fmt.Sprintf("node #%d", *owner.NodeID) + } + logger.Warningf( + "setRemoteTraffic: tag %q from node %d collides with inbound owned by %s — skipping (rename one side to remove the duplicate)", + snapIb.Tag, nodeID, ownerLabel, + ) + } + continue + } else if !errors.Is(err, gorm.ErrRecordNotFound) { + logger.Warningf("setRemoteTraffic: check tag %q failed: %v", snapIb.Tag, err) + continue + } newIb := model.Inbound{ UserId: defaultUserId, NodeID: &nodeID, @@ -1332,7 +1354,7 @@ func (s *InboundService) setRemoteTrafficLocked(nodeID int, snap *runtime.Traffi Down: snapIb.Down, } if err := tx.Create(&newIb).Error; err != nil { - logger.Warning("setRemoteTraffic: create central inbound for tag", snapIb.Tag, "failed:", err) + logger.Warningf("setRemoteTraffic: create central inbound for tag %q failed: %v", snapIb.Tag, err) continue } tagToCentral[snapIb.Tag] = &newIb @@ -1508,7 +1530,7 @@ func (s *InboundService) setRemoteTrafficLocked(nodeID int, snap *runtime.Traffi clients, gcErr := s.GetClients(snapIb) if gcErr != nil { - logger.Warning("setRemoteTraffic: parse clients for tag", snapIb.Tag, "failed:", gcErr) + logger.Warningf("setRemoteTraffic: parse clients for tag %q failed: %v", snapIb.Tag, gcErr) continue } csEnableByEmail := make(map[string]bool, len(snapIb.ClientStats)) @@ -1526,7 +1548,7 @@ func (s *InboundService) setRemoteTrafficLocked(nodeID int, snap *runtime.Traffi filtered = append(filtered, clients[i]) } if err := s.clientService.SyncInbound(tx, c.Id, filtered); err != nil { - logger.Warning("setRemoteTraffic: sync clients for tag", snapIb.Tag, "failed:", err) + logger.Warningf("setRemoteTraffic: sync clients for tag %q failed: %v", snapIb.Tag, err) } } @@ -1557,10 +1579,10 @@ func (s *InboundService) setRemoteTrafficLocked(nodeID int, snap *runtime.Traffi continue } if err := tx.Where("email = ?", email).Delete(&model.ClientRecord{}).Error; err != nil { - logger.Warning("setRemoteTraffic: delete ClientRecord", email, "failed:", err) + logger.Warningf("setRemoteTraffic: delete ClientRecord %q failed: %v", email, err) } if err := tx.Where("email = ?", email).Delete(&xray.ClientTraffic{}).Error; err != nil { - logger.Warning("setRemoteTraffic: delete ClientTraffic", email, "failed:", err) + logger.Warningf("setRemoteTraffic: delete ClientTraffic %q failed: %v", email, err) } structuralChange = true }