Kaynağa Gözat

feat(inbounds): support Unix domain socket path in Listen field (#4429)

UDS listen already worked for proxying (the listen string is passed to xray verbatim and port 0 is accepted), and the Go sub/link layer already ignores the bind listen. The only gap was the frontend resolveAddr, which would put a socket-path listen into share/sub links (e.g. vless://uuid@/run/xray/x.sock:0). resolveAddr now treats a path-style listen (starting with / or @) as having no client-reachable address and falls back to hostOverride/hostname. Adds a test and a Listen-field help hint across all locales.
MHSanaei 1 gün önce
ebeveyn
işleme
b2e2120eb3

+ 1 - 1
database/db_seed_test.go

@@ -133,7 +133,7 @@ func TestNormalizeInboundClientSubId_FillsMissingAndPreservesExisting(t *testing
 	}
 
 	subIdPattern := regexp.MustCompile(`^[0-9a-z]{16}$`)
-	for i := 0; i < 2; i++ {
+	for i := range 2 {
 		obj := clients[i].(map[string]any)
 		sub, _ := obj["subId"].(string)
 		if !subIdPattern.MatchString(sub) {

+ 9 - 2
frontend/src/lib/xray/inbound-link.ts

@@ -706,15 +706,22 @@ export function genWireguardConfig(input: GenWireguardLinkInput): string {
 
 export type { WireguardInboundPeer };
 
+function isUnixSocketListen(listen: string): boolean {
+  return listen.startsWith('/') || listen.startsWith('@');
+}
+
 // Orchestrators.
 // resolveAddr picks the host that goes into share/sub links. Order:
 //   1. hostOverride (caller supplies node address for node-managed inbounds)
-//   2. inbound's bind listen (when explicit, not 0.0.0.0)
+//   2. inbound's bind listen (when it's an explicit reachable address —
+//      not 0.0.0.0 and not a unix domain socket path)
 //   3. fallbackHostname (caller-supplied — typically window.location.hostname
 //      in the browser; tests pass a fixed value)
 export function resolveAddr(inbound: Inbound, hostOverride: string, fallbackHostname: string): string {
   if (hostOverride.length > 0) return hostOverride;
-  if (inbound.listen.length > 0 && inbound.listen !== '0.0.0.0') return inbound.listen;
+  if (inbound.listen.length > 0 && inbound.listen !== '0.0.0.0' && !isUnixSocketListen(inbound.listen)) {
+    return inbound.listen;
+  }
   return fallbackHostname;
 }
 

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

@@ -481,7 +481,11 @@ export default function InboundFormModal({
         <Select disabled={mode === 'edit'} options={PROTOCOL_OPTIONS} />
       </Form.Item>
 
-      <Form.Item name="listen" label={t('pages.inbounds.address')}>
+      <Form.Item
+        name="listen"
+        label={t('pages.inbounds.address')}
+        extra={t('pages.inbounds.form.listenHelp')}
+      >
         <Input placeholder={t('pages.inbounds.monitorDesc')} />
       </Form.Item>
 

+ 13 - 0
frontend/src/test/inbound-link.test.ts

@@ -196,6 +196,19 @@ describe('resolveAddr precedence', () => {
     )).toBe('fallback.test');
   });
 
+  it('skips a unix socket path listen and falls through to fallbackHostname', () => {
+    expect(resolveAddr(
+      { ...baseInbound, listen: '/run/xray/in.sock' } as never,
+      '',
+      'fallback.test',
+    )).toBe('fallback.test');
+    expect(resolveAddr(
+      { ...baseInbound, listen: '@xray-abstract' } as never,
+      '',
+      'fallback.test',
+    )).toBe('fallback.test');
+  });
+
   it('falls through to fallbackHostname when listen is empty', () => {
     expect(resolveAddr(
       baseInbound as never,

+ 1 - 4
web/service/client.go

@@ -245,10 +245,7 @@ func (s *ClientService) SyncInbound(tx *gorm.DB, inboundId int, clients []model.
 			if incoming.CreatedAt > 0 && (row.CreatedAt == 0 || incoming.CreatedAt < row.CreatedAt) {
 				row.CreatedAt = incoming.CreatedAt
 			}
-			preservedUpdatedAt := row.UpdatedAt
-			if incoming.UpdatedAt > preservedUpdatedAt {
-				preservedUpdatedAt = incoming.UpdatedAt
-			}
+			preservedUpdatedAt := max(incoming.UpdatedAt, row.UpdatedAt)
 			row.UpdatedAt = preservedUpdatedAt
 			if err := tx.Save(row).Error; err != nil {
 				return err

+ 2 - 1
web/translation/ar-EG.json

@@ -570,7 +570,8 @@
         "getNewCert": "احصل على شهادة جديدة",
         "mldsa65Seed": "mldsa65 Seed",
         "mldsa65Verify": "mldsa65 Verify",
-        "getNewSeed": "احصل على Seed جديد"
+        "getNewSeed": "احصل على Seed جديد",
+        "listenHelp": "يمكنك أيضًا إدخال مسار Unix socket (مثل /run/xray/in.sock) للاستماع على socket بدلاً من منفذ TCP — في هذه الحالة اضبط المنفذ على 0."
       },
       "info": {
         "mode": "الوضع",

+ 2 - 1
web/translation/en-US.json

@@ -570,7 +570,8 @@
         "getNewCert": "Get New Cert",
         "mldsa65Seed": "mldsa65 Seed",
         "mldsa65Verify": "mldsa65 Verify",
-        "getNewSeed": "Get New Seed"
+        "getNewSeed": "Get New Seed",
+        "listenHelp": "You can also enter a Unix socket path (e.g. /run/xray/in.sock) to listen on a socket instead of a TCP port — set Port to 0 in that case."
       },
       "info": {
         "mode": "Mode",

+ 2 - 1
web/translation/es-ES.json

@@ -570,7 +570,8 @@
         "getNewCert": "Obtener nuevo cert",
         "mldsa65Seed": "mldsa65 Seed",
         "mldsa65Verify": "mldsa65 Verify",
-        "getNewSeed": "Obtener nuevo Seed"
+        "getNewSeed": "Obtener nuevo Seed",
+        "listenHelp": "También puedes introducir una ruta de socket Unix (p. ej. /run/xray/in.sock) para escuchar en un socket en lugar de un puerto TCP; en ese caso, establece el Puerto en 0."
       },
       "info": {
         "mode": "Modo",

+ 2 - 1
web/translation/fa-IR.json

@@ -570,7 +570,8 @@
         "getNewCert": "دریافت گواهی جدید",
         "mldsa65Seed": "mldsa65 Seed",
         "mldsa65Verify": "mldsa65 Verify",
-        "getNewSeed": "دریافت Seed جدید"
+        "getNewSeed": "دریافت Seed جدید",
+        "listenHelp": "می‌توانید به‌جای پورت TCP یک مسیر سوکت یونیکس وارد کنید (مثلاً /run/xray/in.sock) تا روی سوکت گوش داده شود — در این حالت پورت را روی ۰ بگذارید."
       },
       "info": {
         "mode": "حالت",

+ 2 - 1
web/translation/id-ID.json

@@ -570,7 +570,8 @@
         "getNewCert": "Dapatkan sertifikat baru",
         "mldsa65Seed": "mldsa65 Seed",
         "mldsa65Verify": "mldsa65 Verify",
-        "getNewSeed": "Dapatkan Seed baru"
+        "getNewSeed": "Dapatkan Seed baru",
+        "listenHelp": "Anda juga dapat memasukkan path Unix socket (mis. /run/xray/in.sock) untuk listen pada socket alih-alih port TCP — dalam hal ini setel Port ke 0."
       },
       "info": {
         "mode": "Mode",

+ 2 - 1
web/translation/ja-JP.json

@@ -570,7 +570,8 @@
         "getNewCert": "新しい証明書を取得",
         "mldsa65Seed": "mldsa65 Seed",
         "mldsa65Verify": "mldsa65 Verify",
-        "getNewSeed": "新しい Seed を取得"
+        "getNewSeed": "新しい Seed を取得",
+        "listenHelp": "TCP ポートの代わりに Unix ソケットのパス(例: /run/xray/in.sock)を入力してソケットでリッスンすることもできます。その場合はポートを 0 に設定してください。"
       },
       "info": {
         "mode": "モード",

+ 2 - 1
web/translation/pt-BR.json

@@ -570,7 +570,8 @@
         "getNewCert": "Obter novo certificado",
         "mldsa65Seed": "mldsa65 Seed",
         "mldsa65Verify": "mldsa65 Verify",
-        "getNewSeed": "Obter novo Seed"
+        "getNewSeed": "Obter novo Seed",
+        "listenHelp": "Você também pode informar um caminho de socket Unix (ex.: /run/xray/in.sock) para escutar em um socket em vez de uma porta TCP — nesse caso, defina a Porta como 0."
       },
       "info": {
         "mode": "Modo",

+ 2 - 1
web/translation/ru-RU.json

@@ -570,7 +570,8 @@
         "getNewCert": "Получить новый сертификат",
         "mldsa65Seed": "mldsa65 Seed",
         "mldsa65Verify": "mldsa65 Verify",
-        "getNewSeed": "Получить новый Seed"
+        "getNewSeed": "Получить новый Seed",
+        "listenHelp": "Можно также указать путь Unix-сокета (например, /run/xray/in.sock), чтобы слушать сокет вместо TCP-порта — в этом случае задайте порт 0."
       },
       "info": {
         "mode": "Режим",

+ 2 - 1
web/translation/tr-TR.json

@@ -570,7 +570,8 @@
         "getNewCert": "Yeni sertifika al",
         "mldsa65Seed": "mldsa65 Seed",
         "mldsa65Verify": "mldsa65 Verify",
-        "getNewSeed": "Yeni Seed al"
+        "getNewSeed": "Yeni Seed al",
+        "listenHelp": "TCP portu yerine bir Unix soket yolu da girebilirsiniz (örn. /run/xray/in.sock) — bu durumda Portu 0 olarak ayarlayın."
       },
       "info": {
         "mode": "Mod",

+ 2 - 1
web/translation/uk-UA.json

@@ -570,7 +570,8 @@
         "getNewCert": "Отримати новий сертифікат",
         "mldsa65Seed": "mldsa65 Seed",
         "mldsa65Verify": "mldsa65 Verify",
-        "getNewSeed": "Отримати новий Seed"
+        "getNewSeed": "Отримати новий Seed",
+        "listenHelp": "Можна також указати шлях Unix-сокета (наприклад, /run/xray/in.sock), щоб слухати сокет замість TCP-порту — у цьому разі встановіть порт 0."
       },
       "info": {
         "mode": "Режим",

+ 2 - 1
web/translation/vi-VN.json

@@ -570,7 +570,8 @@
         "getNewCert": "Lấy chứng chỉ mới",
         "mldsa65Seed": "mldsa65 Seed",
         "mldsa65Verify": "mldsa65 Verify",
-        "getNewSeed": "Lấy Seed mới"
+        "getNewSeed": "Lấy Seed mới",
+        "listenHelp": "Bạn cũng có thể nhập đường dẫn Unix socket (ví dụ /run/xray/in.sock) để lắng nghe trên socket thay vì cổng TCP — khi đó hãy đặt Port là 0."
       },
       "info": {
         "mode": "Chế độ",

+ 2 - 1
web/translation/zh-CN.json

@@ -570,7 +570,8 @@
         "getNewCert": "获取新证书",
         "mldsa65Seed": "mldsa65 Seed",
         "mldsa65Verify": "mldsa65 Verify",
-        "getNewSeed": "获取新 Seed"
+        "getNewSeed": "获取新 Seed",
+        "listenHelp": "也可以填写 Unix socket 路径(例如 /run/xray/in.sock),以使用套接字而非 TCP 端口监听——此时请将端口设为 0。"
       },
       "info": {
         "mode": "模式",

+ 2 - 1
web/translation/zh-TW.json

@@ -570,7 +570,8 @@
         "getNewCert": "取得新憑證",
         "mldsa65Seed": "mldsa65 Seed",
         "mldsa65Verify": "mldsa65 Verify",
-        "getNewSeed": "取得新 Seed"
+        "getNewSeed": "取得新 Seed",
+        "listenHelp": "也可以填寫 Unix socket 路徑(例如 /run/xray/in.sock),以使用通訊端而非 TCP 連接埠監聽——此時請將連接埠設為 0。"
       },
       "info": {
         "mode": "模式",