Pārlūkot izejas kodu

fix(outbounds): preserve SNI/TLS settings on transport change

Changing the transport in the outbound edit modal rebuilt streamSettings
from scratch, dropping tlsSettings (and its serverName) while keeping
security: 'tls'. On save xray received TLS with an empty SNI, so SNI-spoof
tunnels connected but passed no traffic. Carry over tlsSettings/
realitySettings when the new network still supports the security mode,
via a new applyNetworkChange helper. Fixes #4791.
MHSanaei 1 dienu atpakaļ
vecāks
revīzija
5fb18b8819

+ 3 - 14
frontend/src/pages/xray/outbounds/OutboundFormModal.tsx

@@ -42,6 +42,7 @@ import {
   SERVER_PROTOCOLS,
 } from './outbound-form-constants';
 import {
+  applyNetworkChange,
   buildAddModeValues,
   hysteriaStreamSlice,
   newStreamSlice,
@@ -231,20 +232,8 @@ export default function OutboundFormModal({
   // wsSettings, etc.) so the DU branch matches. Preserve security if
   // the new network supports it, otherwise force back to 'none'.
   function onNetworkChange(next: string) {
-    if (next === 'hysteria') {
-      form.setFieldValue('streamSettings', hysteriaStreamSlice());
-      return;
-    }
-    const currentSecurity = form.getFieldValue(['streamSettings', 'security']) ?? 'none';
-    const stillAllowed = canEnableTls({ protocol, streamSettings: { network: next, security: currentSecurity } });
-    const stillReality = canEnableReality({ protocol, streamSettings: { network: next, security: currentSecurity } });
-    const newSecurity =
-      currentSecurity === 'tls' && !stillAllowed
-        ? 'none'
-        : currentSecurity === 'reality' && !stillReality
-          ? 'none'
-          : currentSecurity;
-    form.setFieldValue('streamSettings', { ...newStreamSlice(next), security: newSecurity });
+    const stream = (form.getFieldValue('streamSettings') ?? {}) as Record<string, unknown>;
+    form.setFieldValue('streamSettings', applyNetworkChange(protocol, stream, next));
   }
 
   function onXmuxToggle(checked: boolean) {

+ 28 - 0
frontend/src/pages/xray/outbounds/outbound-form-helpers.ts

@@ -1,4 +1,5 @@
 import { rawOutboundToFormValues } from '@/lib/xray/outbound-form-adapter';
+import { canEnableReality, canEnableTls } from '@/lib/xray/protocol-capabilities';
 import type { OutboundFormValues } from '@/schemas/forms/outbound-form';
 
 import { MUX_PROTOCOLS } from './outbound-form-constants';
@@ -74,6 +75,33 @@ export function hysteriaStreamSlice(): Record<string, unknown> {
   };
 }
 
+// Network change cascade: swap the per-network sub-key (tcpSettings,
+// wsSettings, etc.) so the DU branch matches. Carry over the security mode
+// and its settings (tlsSettings/realitySettings, including SNI serverName)
+// when the new network still supports it; otherwise fall back to 'none'.
+// Dropping tlsSettings here silently wiped the spoofed SNI on save (#4791).
+export function applyNetworkChange(
+  protocol: string,
+  prevStream: Record<string, unknown> | undefined,
+  next: string,
+): Record<string, unknown> {
+  if (next === 'hysteria') return hysteriaStreamSlice();
+  const stream = prevStream ?? {};
+  const currentSecurity = (stream.security as string) ?? 'none';
+  const stillTls = canEnableTls({ protocol, streamSettings: { network: next, security: currentSecurity } });
+  const stillReality = canEnableReality({ protocol, streamSettings: { network: next, security: currentSecurity } });
+  const newSecurity =
+    currentSecurity === 'tls' && !stillTls
+      ? 'none'
+      : currentSecurity === 'reality' && !stillReality
+        ? 'none'
+        : currentSecurity;
+  const newStream: Record<string, unknown> = { ...newStreamSlice(next), security: newSecurity };
+  if (newSecurity === 'tls' && stream.tlsSettings) newStream.tlsSettings = stream.tlsSettings;
+  else if (newSecurity === 'reality' && stream.realitySettings) newStream.realitySettings = stream.realitySettings;
+  return newStream;
+}
+
 export function buildAddModeValues(): OutboundFormValues {
   return rawOutboundToFormValues({});
 }