From b2e2120eb3f8368b8ec42c201ff6798b079e6a9b Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Tue, 2 Jun 2026 00:37:20 +0200 Subject: [PATCH] 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. --- database/db_seed_test.go | 2 +- frontend/src/lib/xray/inbound-link.ts | 11 +++++++++-- .../src/pages/inbounds/form/InboundFormModal.tsx | 6 +++++- frontend/src/test/inbound-link.test.ts | 13 +++++++++++++ web/service/client.go | 5 +---- web/translation/ar-EG.json | 3 ++- web/translation/en-US.json | 3 ++- web/translation/es-ES.json | 3 ++- web/translation/fa-IR.json | 3 ++- web/translation/id-ID.json | 3 ++- web/translation/ja-JP.json | 3 ++- web/translation/pt-BR.json | 3 ++- web/translation/ru-RU.json | 3 ++- web/translation/tr-TR.json | 3 ++- web/translation/uk-UA.json | 3 ++- web/translation/vi-VN.json | 3 ++- web/translation/zh-CN.json | 3 ++- web/translation/zh-TW.json | 3 ++- 18 files changed, 55 insertions(+), 21 deletions(-) diff --git a/database/db_seed_test.go b/database/db_seed_test.go index 27da4abf..2874f4cc 100644 --- a/database/db_seed_test.go +++ b/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) { diff --git a/frontend/src/lib/xray/inbound-link.ts b/frontend/src/lib/xray/inbound-link.ts index e35d1b39..3e2057cf 100644 --- a/frontend/src/lib/xray/inbound-link.ts +++ b/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; } diff --git a/frontend/src/pages/inbounds/form/InboundFormModal.tsx b/frontend/src/pages/inbounds/form/InboundFormModal.tsx index 03da6749..1bd8e906 100644 --- a/frontend/src/pages/inbounds/form/InboundFormModal.tsx +++ b/frontend/src/pages/inbounds/form/InboundFormModal.tsx @@ -481,7 +481,11 @@ export default function InboundFormModal({ diff --git a/frontend/src/test/inbound-link.test.ts b/frontend/src/test/inbound-link.test.ts index 4ac8f10e..a7e36d9d 100644 --- a/frontend/src/test/inbound-link.test.ts +++ b/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, diff --git a/web/service/client.go b/web/service/client.go index c8113f00..56588b44 100644 --- a/web/service/client.go +++ b/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 diff --git a/web/translation/ar-EG.json b/web/translation/ar-EG.json index d629d048..8f9e405a 100644 --- a/web/translation/ar-EG.json +++ b/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": "الوضع", diff --git a/web/translation/en-US.json b/web/translation/en-US.json index ec43b1b8..df3b695d 100644 --- a/web/translation/en-US.json +++ b/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", diff --git a/web/translation/es-ES.json b/web/translation/es-ES.json index fb934fef..23215489 100644 --- a/web/translation/es-ES.json +++ b/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", diff --git a/web/translation/fa-IR.json b/web/translation/fa-IR.json index 4a76ddc0..e753d7a1 100644 --- a/web/translation/fa-IR.json +++ b/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": "حالت", diff --git a/web/translation/id-ID.json b/web/translation/id-ID.json index 20d52688..862ef030 100644 --- a/web/translation/id-ID.json +++ b/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", diff --git a/web/translation/ja-JP.json b/web/translation/ja-JP.json index 644c24e2..9fcea30f 100644 --- a/web/translation/ja-JP.json +++ b/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": "モード", diff --git a/web/translation/pt-BR.json b/web/translation/pt-BR.json index 82d8e8be..24348dbc 100644 --- a/web/translation/pt-BR.json +++ b/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", diff --git a/web/translation/ru-RU.json b/web/translation/ru-RU.json index d8826d58..c35381da 100644 --- a/web/translation/ru-RU.json +++ b/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": "Режим", diff --git a/web/translation/tr-TR.json b/web/translation/tr-TR.json index b5d55f8a..92df8ac0 100644 --- a/web/translation/tr-TR.json +++ b/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", diff --git a/web/translation/uk-UA.json b/web/translation/uk-UA.json index f92835b5..0d9f7cde 100644 --- a/web/translation/uk-UA.json +++ b/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": "Режим", diff --git a/web/translation/vi-VN.json b/web/translation/vi-VN.json index 1e403b94..08ee081d 100644 --- a/web/translation/vi-VN.json +++ b/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ế độ", diff --git a/web/translation/zh-CN.json b/web/translation/zh-CN.json index 81333bf2..ad712717 100644 --- a/web/translation/zh-CN.json +++ b/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": "模式", diff --git a/web/translation/zh-TW.json b/web/translation/zh-TW.json index 99ba51d8..b0f6c1ba 100644 --- a/web/translation/zh-TW.json +++ b/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": "模式",