refactor(inbound-tag): drop protocol segment from canonical shape

The canonical tag is now "[n<id>-]in-[<listen>:]<port>-<transport>"
instead of "[n<id>-]in-[<listen>:]<port>-<protocol>-<transport>".
Two TCP inbounds on the same port are already blocked by
checkPortConflict, so only the transport segment is needed to
disambiguate the legitimate tcp/udp coexistence case.

Existing DB rows keep their current tags via resolveInboundTag's
"reuse if free" branch — no migration needed (protocol-segment
form was never released).
This commit is contained in:
MHSanaei
2026-05-28 15:50:00 +02:00
parent aefee2c15f
commit c5dc84d314
2 changed files with 26 additions and 134 deletions
+23 -23
View File
@@ -285,13 +285,13 @@ func TestGenerateInboundTag_DisambiguatesByTransportOnSamePort(t *testing.T) {
if err != nil {
t.Fatalf("generateInboundTag: %v", err)
}
if got != "in-443-hy-udp" {
t.Fatalf("expected in-443-hy-udp, got %q", got)
if got != "in-443-udp" {
t.Fatalf("expected in-443-udp, got %q", got)
}
}
// when the port is free, the canonical tag carries protocol + transport
// so tcp/8443 and udp/8443 get distinct tags out of the box.
// when the port is free, the canonical tag carries the transport so
// tcp/8443 and udp/8443 get distinct tags out of the box.
func TestGenerateInboundTag_KeepsBaseTagWhenFree(t *testing.T) {
setupConflictDB(t)
@@ -305,8 +305,8 @@ func TestGenerateInboundTag_KeepsBaseTagWhenFree(t *testing.T) {
if err != nil {
t.Fatalf("generateInboundTag: %v", err)
}
if got != "in-8443-vl-tcp" {
t.Fatalf("expected in-8443-vl-tcp, got %q", got)
if got != "in-8443-tcp" {
t.Fatalf("expected in-8443-tcp, got %q", got)
}
}
@@ -314,10 +314,10 @@ func TestGenerateInboundTag_KeepsBaseTagWhenFree(t *testing.T) {
// that's what ignoreId is for.
func TestGenerateInboundTag_IgnoresSelfOnUpdate(t *testing.T) {
setupConflictDB(t)
seedInboundConflict(t, "in-443-vl-tcp", "0.0.0.0", 443, model.VLESS, `{"network":"tcp"}`, `{}`)
seedInboundConflict(t, "in-443-tcp", "0.0.0.0", 443, model.VLESS, `{"network":"tcp"}`, `{}`)
var existing model.Inbound
if err := database.GetDB().Where("tag = ?", "in-443-vl-tcp").First(&existing).Error; err != nil {
if err := database.GetDB().Where("tag = ?", "in-443-tcp").First(&existing).Error; err != nil {
t.Fatalf("read seeded row: %v", err)
}
@@ -326,7 +326,7 @@ func TestGenerateInboundTag_IgnoresSelfOnUpdate(t *testing.T) {
if err != nil {
t.Fatalf("generateInboundTag: %v", err)
}
if got != "in-443-vl-tcp" {
if got != "in-443-tcp" {
t.Fatalf("self-update must keep base tag, got %q", got)
}
}
@@ -346,8 +346,8 @@ func TestGenerateInboundTag_SpecificListenSameDisambiguation(t *testing.T) {
if err != nil {
t.Fatalf("generateInboundTag: %v", err)
}
if got != "in-1.2.3.4:443-hy-udp" {
t.Fatalf("expected in-1.2.3.4:443-hy-udp, got %q", got)
if got != "in-1.2.3.4:443-udp" {
t.Fatalf("expected in-1.2.3.4:443-udp, got %q", got)
}
}
@@ -399,8 +399,8 @@ func TestCheckPortConflict_NodeScope(t *testing.T) {
// panels diverged, causing a UNIQUE constraint failure on sync.
func TestResolveInboundTag_RespectsCallerTagWhenFree(t *testing.T) {
setupConflictDB(t)
seedInboundConflictNode(t, "in-5000-vl-tcp", "0.0.0.0", 5000, model.VLESS, `{"network":"tcp"}`, `{}`, nil)
seedInboundConflictNode(t, "in-5000-hy-udp", "0.0.0.0", 5000, model.Hysteria, ``, ``, nil)
seedInboundConflictNode(t, "in-5000-tcp", "0.0.0.0", 5000, model.VLESS, `{"network":"tcp"}`, `{}`, nil)
seedInboundConflictNode(t, "in-5000-udp", "0.0.0.0", 5000, model.Hysteria, ``, ``, nil)
svc := &InboundService{}
pushed := &model.Inbound{
@@ -436,8 +436,8 @@ func TestResolveInboundTag_GeneratesWhenTagEmpty(t *testing.T) {
if err != nil {
t.Fatalf("resolveInboundTag: %v", err)
}
if got != "in-8443-vl-tcp" {
t.Fatalf("expected generated in-8443-vl-tcp, got %q", got)
if got != "in-8443-tcp" {
t.Fatalf("expected generated in-8443-tcp, got %q", got)
}
}
@@ -448,11 +448,11 @@ func TestResolveInboundTag_GeneratesWhenTagEmpty(t *testing.T) {
// tag that the central will pick up via the AddInbound response.
func TestResolveInboundTag_RegeneratesOnCollision(t *testing.T) {
setupConflictDB(t)
seedInboundConflictNode(t, "in-5000-vl-tcp", "0.0.0.0", 5000, model.VLESS, `{"network":"tcp"}`, `{}`, nil)
seedInboundConflictNode(t, "in-5000-tcp", "0.0.0.0", 5000, model.VLESS, `{"network":"tcp"}`, `{}`, nil)
svc := &InboundService{}
pushed := &model.Inbound{
Tag: "in-5000-vl-tcp",
Tag: "in-5000-tcp",
Listen: "0.0.0.0",
Port: 5000,
Protocol: model.Hysteria,
@@ -463,7 +463,7 @@ func TestResolveInboundTag_RegeneratesOnCollision(t *testing.T) {
if err != nil {
t.Fatalf("resolveInboundTag: %v", err)
}
if got == "in-5000-vl-tcp" {
if got == "in-5000-tcp" {
t.Fatalf("colliding caller tag must be replaced, but resolver kept %q", got)
}
}
@@ -486,8 +486,8 @@ func TestGenerateInboundTag_NodePrefix(t *testing.T) {
if err != nil {
t.Fatalf("generateInboundTag: %v", err)
}
if got != "n1-in-443-vl-tcp" {
t.Fatalf("expected n1-in-443-vl-tcp, got %q", got)
if got != "n1-in-443-tcp" {
t.Fatalf("expected n1-in-443-tcp, got %q", got)
}
}
@@ -495,7 +495,7 @@ func TestGenerateInboundTag_NodePrefix(t *testing.T) {
// the prefix scopes the tag to that specific node.
func TestGenerateInboundTag_NodePrefixedDoesNotCollideWithLocal(t *testing.T) {
setupConflictDB(t)
seedInboundConflict(t, "in-443-vl-tcp", "0.0.0.0", 443, model.VLESS, `{"network":"tcp"}`, `{}`)
seedInboundConflict(t, "in-443-tcp", "0.0.0.0", 443, model.VLESS, `{"network":"tcp"}`, `{}`)
svc := &InboundService{}
in := &model.Inbound{
@@ -508,8 +508,8 @@ func TestGenerateInboundTag_NodePrefixedDoesNotCollideWithLocal(t *testing.T) {
if err != nil {
t.Fatalf("generateInboundTag: %v", err)
}
if got != "n1-in-443-vl-tcp" {
t.Fatalf("expected n1-in-443-vl-tcp, got %q", got)
if got != "n1-in-443-tcp" {
t.Fatalf("expected n1-in-443-tcp, got %q", got)
}
}