From 1284756f8a1f7313f1132ebf97d7686ec2e55214 Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Thu, 14 May 2026 13:27:55 +0200 Subject: [PATCH] fix(outbound): restore TLS, QUIC params and TCP masks when importing share links - fromHysteriaLink: parse security= URL param and populate stream.tls (SNI, fingerprint, ALPN, ECH) when security=tls; previously always forced security to 'none' - fromHysteriaLink: parse fm JSON param and populate both stream.finalmask.quicParams (drives the QUIC Params toggle in FinalMaskForm) and the mirrored stream.hysteria fields - fromParamLink (VLESS/Trojan/SS): parse fm JSON param and restore stream.finalmask (TCP masks, UDP masks, QUIC params) - fromVmessLink (VMess): same fm handling for the base64-JSON path Closes #4376 --- frontend/src/models/outbound.js | 61 +++++++++++++++++++++++++++++++-- 1 file changed, 59 insertions(+), 2 deletions(-) diff --git a/frontend/src/models/outbound.js b/frontend/src/models/outbound.js index f2cabf3e..8277590e 100644 --- a/frontend/src/models/outbound.js +++ b/frontend/src/models/outbound.js @@ -1397,6 +1397,13 @@ export class Outbound extends CommonClass { const port = json.port * 1; + // Parse fm (finalmask) JSON string — TCP/UDP masks + QUIC params from 3x-ui share links + if (json.fm) { + try { + stream.finalmask = FinalMaskStreamSettings.fromJson(JSON.parse(json.fm)); + } catch (_) { /* ignore malformed fm */ } + } + return new Outbound(json.ps, Protocols.VMess, new Outbound.VmessSettings(json.add, port, json.id, json.scy), stream); } @@ -1496,6 +1503,14 @@ export class Outbound extends CommonClass { default: return null; } + // Parse fm (finalmask) JSON param — TCP/UDP masks + QUIC params from 3x-ui share links + const fmRaw = url.searchParams.get('fm'); + if (fmRaw) { + try { + stream.finalmask = FinalMaskStreamSettings.fromJson(JSON.parse(fmRaw)); + } catch (_) { /* ignore malformed fm */ } + } + let remark = decodeURIComponent(url.hash); // Remove '#' from url.hash remark = remark.length > 0 ? remark.substring(1) : 'out-' + protocol + '-' + port; @@ -1516,7 +1531,17 @@ export class Outbound extends CommonClass { let urlParams = new URLSearchParams(params); // Create stream settings with hysteria network - let stream = new StreamSettings('hysteria', 'none'); + let security = urlParams.get('security') ?? 'none'; + let stream = new StreamSettings('hysteria', security); + + // Parse TLS settings when security=tls + if (security === 'tls') { + let fp = urlParams.get('fp') ?? 'none'; + let alpn = urlParams.get('alpn'); + let sni = urlParams.get('sni') ?? ''; + let ech = urlParams.get('ech') ?? ''; + stream.tls = new TlsStreamSettings(sni, alpn ? alpn.split(',') : [], fp, ech); + } // Set hysteria stream settings stream.hysteria.auth = password; @@ -1534,7 +1559,7 @@ export class Outbound extends CommonClass { stream.hysteria.udphopIntervalMax = parseInt(urlParams.get('udphopIntervalMax') ?? '30'); } - // Optional QUIC parameters + // Optional QUIC parameters for FinalMask support and hysteria2 share links if (urlParams.has('initStreamReceiveWindow')) { stream.hysteria.initStreamReceiveWindow = parseInt(urlParams.get('initStreamReceiveWindow')); } @@ -1557,6 +1582,38 @@ export class Outbound extends CommonClass { stream.hysteria.disablePathMTUDiscovery = urlParams.get('disablePathMTUDiscovery') === 'true'; } + // Parse fm (finalmask) JSON param — TCP/UDP masks + QUIC params from 3x-ui share links, with special handling to mirror QUIC params into both stream.finalmask and stream.hysteria + const fmRaw = urlParams.get('fm'); + if (fmRaw) { + try { + const fm = JSON.parse(fmRaw); + const qp = fm.quicParams; + if (qp && typeof qp === 'object') { + // Populate stream.finalmask.quicParams — this enables the "QUIC Params" + // toggle in FinalMaskForm and carries all QUIC tuning settings. + stream.finalmask.quicParams = QuicParams.fromJson(qp); + + // Also mirror the overlapping fields into stream.hysteria so the + // Hysteria transport section of the form shows consistent values. + if (qp.congestion) stream.hysteria.congestion = qp.congestion; + if (Number.isInteger(qp.initStreamReceiveWindow)) stream.hysteria.initStreamReceiveWindow = qp.initStreamReceiveWindow; + if (Number.isInteger(qp.maxStreamReceiveWindow)) stream.hysteria.maxStreamReceiveWindow = qp.maxStreamReceiveWindow; + if (Number.isInteger(qp.initConnectionReceiveWindow)) stream.hysteria.initConnectionReceiveWindow = qp.initConnectionReceiveWindow; + if (Number.isInteger(qp.maxConnectionReceiveWindow)) stream.hysteria.maxConnectionReceiveWindow = qp.maxConnectionReceiveWindow; + if (Number.isInteger(qp.maxIdleTimeout)) stream.hysteria.maxIdleTimeout = qp.maxIdleTimeout; + if (Number.isInteger(qp.keepAlivePeriod)) stream.hysteria.keepAlivePeriod = qp.keepAlivePeriod; + if (qp.disablePathMTUDiscovery === true) stream.hysteria.disablePathMTUDiscovery = true; + if (qp.udpHop) { + stream.hysteria.udphopPort = qp.udpHop.ports ?? stream.hysteria.udphopPort; + if (qp.udpHop.interval !== undefined) { + stream.hysteria.udphopIntervalMin = qp.udpHop.interval; + stream.hysteria.udphopIntervalMax = qp.udpHop.interval; + } + } + } + } catch (_) { /* ignore malformed fm */ } + } + // Create settings let settings = new Outbound.HysteriaSettings(address, port, 2);