fix(inbound): re-derive auto tags on edit and keep node tags consistent

Auto-generated inbound tags (in-<port>-<l4>, n<id>- prefixed for node inbounds) now re-derive when port/listen/transport change on update instead of keeping the stale round-tripped value. The resolved tag is mirrored onto the API response, and NodeID is pinned to the stored row so a node inbound never loses its n<id>- prefix on edit. The edit form recomputes the tag live via a Go-parity helper so the JSON preview matches what gets saved.

Make node/central tag matching prefix-agnostic in all three places (traffic attribution, remote-id resolution, and the orphan sweep) so an n<id>- prefix present on only one side can no longer spawn duplicate inbounds or drop traffic on sync.

Force LF on shell scripts via .gitattributes (CRLF broke the Docker build shebang when the repo is checked out on Windows) and add a .dockerignore to keep node_modules/.git out of the build context.

Adds Go and frontend tests covering tag re-derivation, prefix-agnostic matching, and node-snapshot prefix mismatch.
This commit is contained in:
MHSanaei
2026-06-01 05:08:29 +02:00
parent 4a11375f36
commit eb78b8666f
12 changed files with 504 additions and 7 deletions
+25 -4
View File
@@ -758,8 +758,11 @@ func (s *InboundService) UpdateInbound(inbound *model.Inbound) (*model.Inbound,
if err != nil {
return inbound, false, err
}
inbound.NodeID = oldInbound.NodeID
tag := oldInbound.Tag
oldBits := inboundTransports(oldInbound.Protocol, oldInbound.StreamSettings, oldInbound.Settings)
oldTagWasAuto := isAutoGeneratedTag(tag, oldInbound.Listen, oldInbound.Port, oldInbound.NodeID, oldBits)
db := database.GetDB()
tx := db.Begin()
@@ -847,10 +850,14 @@ func (s *InboundService) UpdateInbound(inbound *model.Inbound) (*model.Inbound,
oldInbound.Settings = inbound.Settings
oldInbound.StreamSettings = inbound.StreamSettings
oldInbound.Sniffing = inbound.Sniffing
if oldTagWasAuto && inbound.Tag == tag {
inbound.Tag = ""
}
oldInbound.Tag, err = s.resolveInboundTag(inbound, inbound.Id)
if err != nil {
return inbound, false, err
}
inbound.Tag = oldInbound.Tag
needRestart := false
rt, rterr := s.runtimeFor(oldInbound)
@@ -1267,14 +1274,19 @@ func (s *InboundService) setRemoteTrafficLocked(nodeID int, snap *runtime.Traffi
Find(&central).Error; err != nil {
return false, err
}
// 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.
// Index under the stored tag and its prefix-flipped form so a snap matches
// whether the n<id>- prefix lives on the node side, the central side, or
// neither — a mismatch must never spawn a duplicate central inbound.
tagToCentral := make(map[string]*model.Inbound, len(central)*2)
prefix := nodeTagPrefix(&nodeID)
for i := range central {
tagToCentral[central[i].Tag] = &central[i]
if prefix != "" && strings.HasPrefix(central[i].Tag, prefix) {
tagToCentral[strings.TrimPrefix(central[i].Tag, prefix)] = &central[i]
if prefix != "" {
if stripped, found := strings.CutPrefix(central[i].Tag, prefix); found {
tagToCentral[stripped] = &central[i]
} else {
tagToCentral[prefix+central[i].Tag] = &central[i]
}
}
}
@@ -1329,6 +1341,15 @@ func (s *InboundService) setRemoteTrafficLocked(nodeID int, snap *runtime.Traffi
continue
}
snapTags[snapIb.Tag] = struct{}{}
// Record the prefix-flipped form too so the orphan sweep below keeps a
// central inbound whether its tag carries the n<id>- prefix or not.
if prefix != "" {
if stripped, found := strings.CutPrefix(snapIb.Tag, prefix); found {
snapTags[stripped] = struct{}{}
} else {
snapTags[prefix+snapIb.Tag] = struct{}{}
}
}
c, ok := tagToCentral[snapIb.Tag]
if !ok {