Procházet zdrojové kódy

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.
MHSanaei před 8 hodinami
rodič
revize
a3dca4b82d

+ 3 - 8
frontend/src/lib/xray/inbound-tag.ts

@@ -49,12 +49,8 @@ function transportTagSuffix(bits: TransportBits): string {
   return 'any';
 }
 
-function isAnyListen(listen: string): boolean {
-  return listen === '' || listen === '0.0.0.0' || listen === '::' || listen === '::0';
-}
-
-function baseInboundTag(listen: string, port: number): string {
-  return isAnyListen(listen) ? `in-${port}` : `in-${listen}:${port}`;
+function baseInboundTag(port: number): string {
+  return `in-${port}`;
 }
 
 function nodeTagPrefix(nodeId: number | null | undefined): string {
@@ -62,7 +58,6 @@ function nodeTagPrefix(nodeId: number | null | undefined): string {
 }
 
 export interface InboundTagInput {
-  listen: string;
   port: number;
   nodeId: number | null | undefined;
   protocol: string;
@@ -74,7 +69,7 @@ export function composeInboundTag(input: InboundTagInput): string {
   const bits = inboundTransports(input.protocol, input.streamSettings, input.settings);
   return (
     nodeTagPrefix(input.nodeId)
-    + baseInboundTag(input.listen ?? '', input.port ?? 0)
+    + baseInboundTag(input.port ?? 0)
     + '-'
     + transportTagSuffix(bits)
   );

+ 1 - 4
frontend/src/pages/inbounds/form/InboundFormModal.tsx

@@ -160,7 +160,6 @@ export default function InboundFormModal({
   const security = Form.useWatch(['streamSettings', 'security'], form) ?? 'none';
   const streamEnabled = canEnableStream({ protocol });
 
-  const wListen = Form.useWatch('listen', form) ?? '';
   const wPort = Form.useWatch('port', form);
   const wNodeId = Form.useWatch('nodeId', form) ?? null;
   const wTag = Form.useWatch('tag', form) ?? '';
@@ -169,7 +168,6 @@ export default function InboundFormModal({
   const autoTagRef = useRef(true);
   const lastWrittenTagRef = useRef('');
   const currentTagInput = (): InboundTagInput => ({
-    listen: typeof wListen === 'string' ? wListen : '',
     port: typeof wPort === 'number' ? wPort : 0,
     nodeId: typeof wNodeId === 'number' ? wNodeId : null,
     protocol,
@@ -293,7 +291,6 @@ export default function InboundFormModal({
     form.setFieldsValue(initial);
     const initialTag = (initial.tag ?? '') as string;
     autoTagRef.current = isAutoInboundTag(initialTag, {
-      listen: initial.listen ?? '',
       port: initial.port ?? 0,
       nodeId: initial.nodeId ?? null,
       protocol: initial.protocol,
@@ -329,7 +326,7 @@ export default function InboundFormModal({
       form.setFieldValue('tag', next);
     }
     // eslint-disable-next-line react-hooks/exhaustive-deps
-  }, [open, wListen, wPort, wNodeId, protocol, network, mixedUdpOn, wSsNetwork, wTunnelNetwork]);
+  }, [open, wPort, wNodeId, protocol, network, mixedUdpOn, wSsNetwork, wTunnelNetwork]);
 
   // Why: protocol picker reset cascades through the form — clearing the
   // settings DU branch and dropping a nodeId that no longer applies. The

+ 4 - 5
frontend/src/test/inbound-tag.test.ts

@@ -7,7 +7,6 @@ import { composeInboundTag, isAutoInboundTag, type InboundTagInput } from '@/lib
 // tag the backend re-derives on save.
 describe('composeInboundTag transport suffix parity', () => {
   const base = (over: Partial<InboundTagInput>): InboundTagInput => ({
-    listen: '0.0.0.0',
     port: 443,
     nodeId: null,
     protocol: 'vless',
@@ -36,9 +35,9 @@ describe('composeInboundTag transport suffix parity', () => {
     expect(composeInboundTag(input)).toBe(want);
   });
 
-  it('scopes a non-any listen and node prefix', () => {
-    expect(composeInboundTag(base({ listen: '127.0.0.1', port: 8443, streamSettings: { network: 'tcp' } })))
-      .toBe('in-127.0.0.1:8443-tcp');
+  it('ignores the listen address and adds the node prefix', () => {
+    expect(composeInboundTag(base({ port: 8443, streamSettings: { network: 'tcp' } })))
+      .toBe('in-8443-tcp');
     expect(composeInboundTag(base({ nodeId: 1, port: 443, streamSettings: { network: 'tcp' } })))
       .toBe('n1-in-443-tcp');
   });
@@ -47,7 +46,7 @@ describe('composeInboundTag transport suffix parity', () => {
 // Parity with TestIsAutoGeneratedTag.
 describe('isAutoInboundTag', () => {
   const input: InboundTagInput = {
-    listen: '0.0.0.0', port: 443, nodeId: null, protocol: 'vless', streamSettings: { network: 'tcp' },
+    port: 443, nodeId: null, protocol: 'vless', streamSettings: { network: 'tcp' },
   };
 
   it('recognises canonical, dedup-suffixed and empty as auto', () => {

+ 1 - 1
web/service/inbound.go

@@ -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
web/service/port_conflict.go

@@ -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
web/service/port_conflict_test.go

@@ -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)
 			}
 		})