fix(inbounds): drop listen address from auto-generated inbound tag

A non-empty, non-any Address (listen) leaked into the tag as
in-<listen>:<port>-<transport> (e.g. in-127.0.0.1:443-tcp). The tag is
now always in-<port>-<transport>, with the node prefix and numeric dedup
suffix still handling uniqueness across nodes and same-port/different-listen
inbounds. Mirrored in the Go authority and the TS form preview, kept in
parity by tests.

Existing colon-form tags are now treated as custom, so editing such an
inbound preserves its tag rather than rewriting it; new inbounds (or a
cleared tag field) get the clean form.
This commit is contained in:
MHSanaei
2026-06-01 09:33:49 +02:00
parent 48f470c465
commit a3dca4b82d
6 changed files with 33 additions and 45 deletions
+1 -1
View File
@@ -762,7 +762,7 @@ func (s *InboundService) UpdateInbound(inbound *model.Inbound) (*model.Inbound,
tag := oldInbound.Tag
oldBits := inboundTransports(oldInbound.Protocol, oldInbound.StreamSettings, oldInbound.Settings)
oldTagWasAuto := isAutoGeneratedTag(tag, oldInbound.Listen, oldInbound.Port, oldInbound.NodeID, oldBits)
oldTagWasAuto := isAutoGeneratedTag(tag, oldInbound.Port, oldInbound.NodeID, oldBits)
db := database.GetDB()
tx := db.Begin()
+7 -10
View File
@@ -171,11 +171,8 @@ func sameNode(a, b *int) bool {
return *a == *b
}
func baseInboundTag(listen string, port int) string {
if isAnyListen(listen) {
return fmt.Sprintf("in-%v", port)
}
return fmt.Sprintf("in-%v:%v", listen, port)
func baseInboundTag(port int) string {
return fmt.Sprintf("in-%v", port)
}
func transportTagSuffix(b transportBits) string {
@@ -200,12 +197,12 @@ func nodeTagPrefix(nodeID *int) string {
return fmt.Sprintf("n%d-", *nodeID)
}
func composeInboundTag(listen string, port int, nodeID *int, bits transportBits) string {
return nodeTagPrefix(nodeID) + baseInboundTag(listen, port) + "-" + transportTagSuffix(bits)
func composeInboundTag(port int, nodeID *int, bits transportBits) string {
return nodeTagPrefix(nodeID) + baseInboundTag(port) + "-" + transportTagSuffix(bits)
}
func isAutoGeneratedTag(tag, listen string, port int, nodeID *int, bits transportBits) bool {
base := composeInboundTag(listen, port, nodeID, bits)
func isAutoGeneratedTag(tag string, port int, nodeID *int, bits transportBits) bool {
base := composeInboundTag(port, nodeID, bits)
if tag == base {
return true
}
@@ -223,7 +220,7 @@ func isAutoGeneratedTag(tag, listen string, port int, nodeID *int, bits transpor
func (s *InboundService) generateInboundTag(inbound *model.Inbound, ignoreId int) (string, error) {
bits := inboundTransports(inbound.Protocol, inbound.StreamSettings, inbound.Settings)
candidate := composeInboundTag(inbound.Listen, inbound.Port, inbound.NodeID, bits)
candidate := composeInboundTag(inbound.Port, inbound.NodeID, bits)
exists, err := s.tagExists(candidate, ignoreId)
if err != nil {
return "", err
+17 -17
View File
@@ -331,10 +331,11 @@ func TestGenerateInboundTag_IgnoresSelfOnUpdate(t *testing.T) {
}
}
// specific listen address gets the listen-prefixed shape and same suffix.
func TestGenerateInboundTag_SpecificListenSameDisambiguation(t *testing.T) {
// the listen address never appears in the tag; the transport suffix still
// keeps a udp inbound distinct from a tcp one on the same port.
func TestGenerateInboundTag_ListenIgnoredTransportDisambiguates(t *testing.T) {
setupConflictDB(t)
seedInboundConflict(t, "in-1.2.3.4:443", "1.2.3.4", 443, model.VLESS, `{"network":"tcp"}`, `{}`)
seedInboundConflict(t, "in-443-tcp", "1.2.3.4", 443, model.VLESS, `{"network":"tcp"}`, `{}`)
svc := &InboundService{}
udp := &model.Inbound{
@@ -346,8 +347,8 @@ func TestGenerateInboundTag_SpecificListenSameDisambiguation(t *testing.T) {
if err != nil {
t.Fatalf("generateInboundTag: %v", err)
}
if got != "in-1.2.3.4:443-udp" {
t.Fatalf("expected in-1.2.3.4:443-udp, got %q", got)
if got != "in-443-udp" {
t.Fatalf("expected in-443-udp, got %q", got)
}
}
@@ -644,26 +645,25 @@ func TestIsAutoGeneratedTag(t *testing.T) {
cases := []struct {
name string
tag string
listen string
port int
nodeID *int
bits transportBits
want bool
}{
{"canonical", "in-443-tcp", "0.0.0.0", 443, nil, tcp, true},
{"canonical udp", "in-443-udp", "0.0.0.0", 443, nil, transportUDP, true},
{"dedup suffix", "in-443-tcp-2", "0.0.0.0", 443, nil, tcp, true},
{"listen scoped", "in-127.0.0.1:443-tcp", "127.0.0.1", 443, nil, tcp, true},
{"node prefixed", "n1-in-443-tcp", "0.0.0.0", 443, intPtr(1), tcp, true},
{"custom tag", "my-cool-tag", "0.0.0.0", 443, nil, tcp, false},
{"stale port", "in-443-tcp", "0.0.0.0", 8443, nil, tcp, false},
{"stale transport", "in-443-tcp", "0.0.0.0", 443, nil, transportUDP, false},
{"non-numeric suffix", "in-443-tcp-x", "0.0.0.0", 443, nil, tcp, false},
{"empty suffix", "in-443-tcp-", "0.0.0.0", 443, nil, tcp, false},
{"canonical", "in-443-tcp", 443, nil, tcp, true},
{"canonical udp", "in-443-udp", 443, nil, transportUDP, true},
{"dedup suffix", "in-443-tcp-2", 443, nil, tcp, true},
{"node prefixed", "n1-in-443-tcp", 443, intPtr(1), tcp, true},
{"legacy listen-scoped is now custom", "in-127.0.0.1:443-tcp", 443, nil, tcp, false},
{"custom tag", "my-cool-tag", 443, nil, tcp, false},
{"stale port", "in-443-tcp", 8443, nil, tcp, false},
{"stale transport", "in-443-tcp", 443, nil, transportUDP, false},
{"non-numeric suffix", "in-443-tcp-x", 443, nil, tcp, false},
{"empty suffix", "in-443-tcp-", 443, nil, tcp, false},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
if got := isAutoGeneratedTag(c.tag, c.listen, c.port, c.nodeID, c.bits); got != c.want {
if got := isAutoGeneratedTag(c.tag, c.port, c.nodeID, c.bits); got != c.want {
t.Fatalf("isAutoGeneratedTag(%q) = %v, want %v", c.tag, got, c.want)
}
})