From 66234315e4aaceabb580e7862d6c432e87cccdfc Mon Sep 17 00:00:00 2001 From: Leslie Alexander Date: Sun, 11 Oct 2026 13:17:05 +0800 Subject: [PATCH] fix(panel): default TLS ALPN to http/1.1 for WebSocket transport (#6787) * fix(panel): default TLS ALPN to http/1.1 for WebSocket transport WebSocket bootstraps over HTTP/1.1: leaving ALPN at the schema default ['h2','http/1.1'] makes the server negotiate h2 and the WS handshake fails. See issue #6782. * style(frontend): keep comments within the two-line limit * style(frontend): keep comments within the two-line limit --- frontend/src/lib/xray/inbound-tls-defaults.ts | 7 +++++- .../pages/inbounds/form/InboundFormModal.tsx | 6 +++++ .../pages/inbounds/form/useSecurityActions.ts | 3 ++- frontend/src/test/inbound-defaults.test.ts | 22 ++++++++++++++++++- 4 files changed, 35 insertions(+), 3 deletions(-) diff --git a/frontend/src/lib/xray/inbound-tls-defaults.ts b/frontend/src/lib/xray/inbound-tls-defaults.ts index 87e1e3115..3861e3fc9 100644 --- a/frontend/src/lib/xray/inbound-tls-defaults.ts +++ b/frontend/src/lib/xray/inbound-tls-defaults.ts @@ -14,7 +14,7 @@ function defaultCertificate(): Record { }; } -export function createTlsSettingsWithDefaultCert(): Record { +export function createTlsSettingsWithDefaultCert(network?: string): Record { const tls = TlsStreamSettingsSchema.parse({}) as Record; tls.certificates = [defaultCertificate()]; const settings = @@ -23,6 +23,11 @@ export function createTlsSettingsWithDefaultCert(): Record { : {}; settings.fingerprint = 'chrome'; tls.settings = settings; + /* WebSocket bootstraps over HTTP/1.1: the schema default ALPN + * ['h2','http/1.1'] makes the server negotiate h2 (see issue #6782). */ + if (network === 'ws') { + tls.alpn = ['http/1.1']; + } return tls; } diff --git a/frontend/src/pages/inbounds/form/InboundFormModal.tsx b/frontend/src/pages/inbounds/form/InboundFormModal.tsx index ac0954ea4..e5c8f7b55 100644 --- a/frontend/src/pages/inbounds/form/InboundFormModal.tsx +++ b/frontend/src/pages/inbounds/form/InboundFormModal.tsx @@ -894,6 +894,12 @@ export default function InboundFormModal({ cleaned.finalmask = { ...fm, udp }; } } + /* WebSocket needs HTTP/1.1 for its handshake; the TLS default + * ALPN ['h2','http/1.1'] makes the server negotiate h2 (see #6782). */ + if (next === 'ws' && cleaned.security === 'tls') { + const tls = (cleaned.tlsSettings as Record | undefined) ?? {}; + cleaned.tlsSettings = { ...tls, alpn: ['http/1.1'] }; + } setV('streamSettings', cleaned); }; diff --git a/frontend/src/pages/inbounds/form/useSecurityActions.ts b/frontend/src/pages/inbounds/form/useSecurityActions.ts index 85802e969..074ae86df 100644 --- a/frontend/src/pages/inbounds/form/useSecurityActions.ts +++ b/frontend/src/pages/inbounds/form/useSecurityActions.ts @@ -344,7 +344,8 @@ export function useSecurityActions({ delete cleaned.tlsSettings; delete cleaned.realitySettings; if (next === 'tls') { - cleaned.tlsSettings = createTlsSettingsWithDefaultCert(); + const network = (current.network as string | undefined) ?? ''; + cleaned.tlsSettings = createTlsSettingsWithDefaultCert(network); } if (next === 'reality') { const reality = RealityStreamSettingsSchema.parse({}) as Record; diff --git a/frontend/src/test/inbound-defaults.test.ts b/frontend/src/test/inbound-defaults.test.ts index 1422e6ea2..7f8f72ea4 100644 --- a/frontend/src/test/inbound-defaults.test.ts +++ b/frontend/src/test/inbound-defaults.test.ts @@ -18,12 +18,16 @@ import { createDefaultVmessInboundSettings, createDefaultWireguardInboundSettings, } from '@/lib/xray/inbound-defaults'; -import { createHysteriaTlsSettingsWithDefaultCert } from '@/lib/xray/inbound-tls-defaults'; +import { + createHysteriaTlsSettingsWithDefaultCert, + createTlsSettingsWithDefaultCert, +} from '@/lib/xray/inbound-tls-defaults'; import { HttpInboundSettingsSchema } from '@/schemas/protocols/inbound/http'; import { HysteriaClientSchema, HysteriaInboundSettingsSchema, } from '@/schemas/protocols/inbound/hysteria'; +import { TlsStreamSettingsSchema } from '@/schemas/protocols/security/tls'; import { MixedInboundSettingsSchema } from '@/schemas/protocols/inbound/mixed'; import { ShadowsocksClientSchema, @@ -234,3 +238,19 @@ describe('createHysteriaTlsSettingsWithDefaultCert', () => { ]); }); }); + +describe('createTlsSettingsWithDefaultCert', () => { + it('keeps the schema ALPN default for non-WebSocket transports', () => { + for (const network of [undefined, '', 'tcp', 'kcp', 'grpc', 'httpupgrade', 'xhttp']) { + const tls = createTlsSettingsWithDefaultCert(network); + expect(tls.alpn).toEqual(['h2', 'http/1.1']); + } + }); + + it("defaults ALPN to http/1.1 for WebSocket (issue #6782)", () => { + const tls = createTlsSettingsWithDefaultCert('ws'); + expect(tls.alpn).toEqual(['http/1.1']); + // The overridden value must still satisfy the TLS settings schema. + expect(TlsStreamSettingsSchema.parse(tls).alpn).toEqual(['http/1.1']); + }); +});