mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-08-14 23:31:00 +00:00
refactor(inbound-tag): node-prefixed + transport-suffixed canonical shape
Tag scheme moves to "[n<nodeID>-]inbound-[<listen>:]<port>-<transport>"
so two long-standing collision classes go away on the create path:
- tcp/443 and udp/443 on the same listener (independent sockets)
- same listen+port living on the central panel and on a remote node
Examples:
local TCP 443 → inbound-443-tcp
local UDP 443 → inbound-443-udp
node 1 TCP 443 → n1-inbound-443-tcp
Refactor:
- composeInboundTag is the single source of truth, called from
generateInboundTag. Transport segment is now always present
(used to appear only on collision); n<id>- prefix is added when
Inbound.NodeID != nil.
- addInbound / importInbound drop their inline "inbound-<port>"
fallback; an empty Tag now flows through resolveInboundTag, which
keeps caller-supplied tags verbatim when free and otherwise
delegates to generateInboundTag.
- setRemoteTrafficLocked indexes tagToCentral under both the stored
tag and the prefix-stripped form, so a node sending its bare tag
still resolves to a row we may have rewritten at materialization.
The create branch now picks between snap.Tag and the n<id>-
prefixed form before falling back to the warn-once skip.
- Tests updated for the always-on transport suffix, and two new
cases cover the node-prefix behaviour.
Existing inbounds keep their tags — only newly generated tags adopt
the new shape, so user routing rules pointing at "inbound-443" still
match the row they always did until the row is recreated.
This commit is contained in:
+38
-13
@@ -1258,9 +1258,15 @@ func (s *InboundService) setRemoteTrafficLocked(nodeID int, snap *runtime.Traffi
|
||||
Find(¢ral).Error; err != nil {
|
||||
return false, err
|
||||
}
|
||||
tagToCentral := make(map[string]*model.Inbound, len(central))
|
||||
// Index under both stored tag and the prefix-stripped form so a snap's
|
||||
// bare tag resolves whether or not we rewrote it with n<id>- at create.
|
||||
tagToCentral := make(map[string]*model.Inbound, len(central)*2)
|
||||
prefix := nodeTagPrefix(&nodeID)
|
||||
for i := range central {
|
||||
tagToCentral[central[i].Tag] = ¢ral[i]
|
||||
if prefix != "" && strings.HasPrefix(central[i].Tag, prefix) {
|
||||
tagToCentral[strings.TrimPrefix(central[i].Tag, prefix)] = ¢ral[i]
|
||||
}
|
||||
}
|
||||
|
||||
var centralClientStats []xray.ClientTraffic
|
||||
@@ -1317,28 +1323,44 @@ 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 {
|
||||
// Try snap.Tag first; on collision fall back to the n<id>-
|
||||
// prefixed form so local+node can both own the same port.
|
||||
pickFreeTag := func() (string, error) {
|
||||
candidates := []string{snapIb.Tag}
|
||||
if prefix != "" && !strings.HasPrefix(snapIb.Tag, prefix) {
|
||||
candidates = append(candidates, prefix+snapIb.Tag)
|
||||
}
|
||||
for _, t := range candidates {
|
||||
var owner model.Inbound
|
||||
err := tx.Where("tag = ?", t).First(&owner).Error
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return t, nil
|
||||
}
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
chosenTag, err := pickFreeTag()
|
||||
if err != nil {
|
||||
logger.Warningf("setRemoteTraffic: check tag %q failed: %v", snapIb.Tag, err)
|
||||
continue
|
||||
}
|
||||
if chosenTag == "" {
|
||||
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,
|
||||
"setRemoteTraffic: tag %q from node %d collides with an existing inbound even after the n%d- prefix — skipping (rename one side to remove the duplicate)",
|
||||
snapIb.Tag, nodeID, nodeID,
|
||||
)
|
||||
}
|
||||
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,
|
||||
Tag: snapIb.Tag,
|
||||
Tag: chosenTag,
|
||||
Listen: snapIb.Listen,
|
||||
Port: snapIb.Port,
|
||||
Protocol: snapIb.Protocol,
|
||||
@@ -1358,6 +1380,9 @@ func (s *InboundService) setRemoteTrafficLocked(nodeID int, snap *runtime.Traffi
|
||||
continue
|
||||
}
|
||||
tagToCentral[snapIb.Tag] = &newIb
|
||||
if newIb.Tag != snapIb.Tag {
|
||||
tagToCentral[newIb.Tag] = &newIb
|
||||
}
|
||||
structuralChange = true
|
||||
continue
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user