| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138 |
- 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 { XDriveStreamSettingsSchema } from '@/schemas/protocols/stream/xdrive';
- 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',
- },
- };
- case 'hysteria':
- return {
- network: 'hysteria',
- hysteriaSettings: {
- version: 2,
- auth: '',
- udpIdleTimeout: 60,
- },
- };
- case 'xdrive':
- return { network: 'xdrive', xdriveSettings: XDriveStreamSettingsSchema.parse({}) };
- case 'masque':
- return { network: 'masque', masqueSettings: {} };
- 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: '',
- },
- };
- }
- // MASQUE needs TLS (xray-core refuses it otherwise) and dials HTTP/3 unless the ALPN
- // offers h2 alone, so it starts on h3 like Hysteria.
- export function masqueStreamSlice(): Record<string, unknown> {
- const { tlsSettings } = hysteriaStreamSlice();
- return { ...newStreamSlice('masque'), security: 'tls', tlsSettings };
- }
- // 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();
- if (next === 'masque') return masqueStreamSlice();
- 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({});
- }
|