mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-11-09 22:55:00 +03:00
5fb18b8819
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.
108 lines
3.9 KiB
TypeScript
108 lines
3.9 KiB
TypeScript
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';
|
|
|
|
export function isMuxAllowed(protocol: string, flow: string, network: string): boolean {
|
|
if (!MUX_PROTOCOLS.has(protocol)) return false;
|
|
if (protocol === 'vless' && flow) return false;
|
|
if (network === 'xhttp') return false;
|
|
return true;
|
|
}
|
|
|
|
// Per-network bootstrap. Mirrors the legacy class constructors so the
|
|
// initial state for each transport matches what xray-core expects.
|
|
export function newStreamSlice(network: string): Record<string, unknown> {
|
|
switch (network) {
|
|
case 'tcp':
|
|
return { network: 'tcp', tcpSettings: { header: { type: 'none' } } };
|
|
case 'kcp':
|
|
return {
|
|
network: 'kcp',
|
|
kcpSettings: {
|
|
mtu: 1350, tti: 20, uplinkCapacity: 5, downlinkCapacity: 20,
|
|
cwndMultiplier: 1, maxSendingWindow: 2097152,
|
|
},
|
|
};
|
|
case 'ws':
|
|
return {
|
|
network: 'ws',
|
|
wsSettings: { path: '/', host: '', headers: {}, heartbeatPeriod: 0 },
|
|
};
|
|
case 'grpc':
|
|
return {
|
|
network: 'grpc',
|
|
grpcSettings: { serviceName: '', authority: '', multiMode: false },
|
|
};
|
|
case 'httpupgrade':
|
|
return {
|
|
network: 'httpupgrade',
|
|
httpupgradeSettings: { path: '/', host: '', headers: {} },
|
|
};
|
|
case 'xhttp':
|
|
return {
|
|
network: 'xhttp',
|
|
xhttpSettings: {
|
|
path: '/', host: '', mode: '', headers: [],
|
|
xPaddingBytes: '100-1000', scMaxEachPostBytes: '1000000',
|
|
},
|
|
};
|
|
case 'hysteria':
|
|
return {
|
|
network: 'hysteria',
|
|
hysteriaSettings: {
|
|
version: 2,
|
|
auth: '',
|
|
udpIdleTimeout: 60,
|
|
},
|
|
};
|
|
default:
|
|
return { network: 'tcp', tcpSettings: { header: { type: 'none' } } };
|
|
}
|
|
}
|
|
|
|
// Hysteria2 always rides its own QUIC transport with TLS — the panel never
|
|
// offers another transport or 'none' security for it.
|
|
export function hysteriaStreamSlice(): Record<string, unknown> {
|
|
return {
|
|
...newStreamSlice('hysteria'),
|
|
security: 'tls',
|
|
tlsSettings: {
|
|
serverName: '', alpn: ['h3'], fingerprint: '',
|
|
echConfigList: '', verifyPeerCertByName: '', pinnedPeerCertSha256: '',
|
|
},
|
|
};
|
|
}
|
|
|
|
// 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({});
|
|
}
|