diff --git a/frontend/src/lib/hosts/host-link.ts b/frontend/src/lib/hosts/host-link.ts index 7dc75d202..5c44c76e5 100644 --- a/frontend/src/lib/hosts/host-link.ts +++ b/frontend/src/lib/hosts/host-link.ts @@ -18,6 +18,7 @@ export type HostLinkInput = Pick< | 'pinnedPeerCertSha256' | 'verifyPeerCertByName' | 'echConfigList' + | 'cipherSuites' | 'overrideSniFromAddress' | 'keepSniBlank' | 'vlessRoute' @@ -55,6 +56,7 @@ export function hostToExternalProxyEntry(host: HostLinkInput): ExternalProxyEntr : undefined, verifyPeerCertByName: host.verifyPeerCertByName || undefined, echConfigList: host.echConfigList || undefined, + cipherSuites: host.cipherSuites || undefined, vlessRoute: host.vlessRoute || undefined, allowInsecure: host.allowInsecure || undefined, }; diff --git a/frontend/src/lib/xray/inbound-link.ts b/frontend/src/lib/xray/inbound-link.ts index 58215b1f2..0ed703cd9 100644 --- a/frontend/src/lib/xray/inbound-link.ts +++ b/frontend/src/lib/xray/inbound-link.ts @@ -235,6 +235,8 @@ function applyExternalProxyTLSObj( } if (externalProxy.echConfigList && externalProxy.echConfigList.length > 0) obj.ech = externalProxy.echConfigList; + if (externalProxy.cipherSuites && externalProxy.cipherSuites.length > 0) + obj.cs = externalProxy.cipherSuites; } export interface GenVmessLinkInput { @@ -337,6 +339,9 @@ export function genVmessLink(input: GenVmessLinkInput): string { if (tlsSettings.settings.pinnedPeerCertSha256.length > 0) { obj.pcs = tlsSettings.settings.pinnedPeerCertSha256.join(','); } + if (tlsSettings.cipherSuites && tlsSettings.cipherSuites.length > 0) { + obj.cs = tlsSettings.cipherSuites; + } } applyExternalProxyTLSObj(externalProxy, obj, tls); @@ -394,6 +399,8 @@ function applyExternalProxyTLSParams( } if (externalProxy.echConfigList && externalProxy.echConfigList.length > 0) params.set('ech', externalProxy.echConfigList); + if (externalProxy.cipherSuites && externalProxy.cipherSuites.length > 0) + params.set('cs', externalProxy.cipherSuites); } export interface GenVlessLinkInput { @@ -497,6 +504,9 @@ export function genVlessLink(input: GenVlessLinkInput): string { if (tls.settings.pinnedPeerCertSha256.length > 0) { params.set('pcs', tls.settings.pinnedPeerCertSha256.join(',')); } + if (tls.cipherSuites && tls.cipherSuites.length > 0) { + params.set('cs', tls.cipherSuites); + } } applyExternalProxyTLSParams(externalProxy, params, security); } else if (security === 'reality') { @@ -602,6 +612,9 @@ function writeTlsParams( if (tls.settings.pinnedPeerCertSha256.length > 0) { params.set('pcs', tls.settings.pinnedPeerCertSha256.join(',')); } + if (tls.cipherSuites && tls.cipherSuites.length > 0) { + params.set('cs', tls.cipherSuites); + } } // Reality query-string writer shared by VLESS and Trojan. Preserves the diff --git a/frontend/src/lib/xray/outbound-link-parser.ts b/frontend/src/lib/xray/outbound-link-parser.ts index 30659462e..6e83d988f 100644 --- a/frontend/src/lib/xray/outbound-link-parser.ts +++ b/frontend/src/lib/xray/outbound-link-parser.ts @@ -489,6 +489,8 @@ function applySecurityParams(stream: Raw, params: URLSearchParams): void { tls.echConfigList = params.get('ech') ?? ''; tls.verifyPeerCertByName = params.get('vcn') ?? ''; tls.pinnedPeerCertSha256 = params.get('pcs') ?? ''; + const cs = params.get('cs'); + if (cs) tls.cipherSuites = cs; } else if (stream.security === 'reality') { const reality = stream.realitySettings as Raw; reality.serverName = params.get('sni') ?? ''; @@ -567,6 +569,7 @@ export function parseVmessLink(link: string): Raw | null { if (typeof json.ech === 'string') tls.echConfigList = json.ech; if (typeof json.vcn === 'string') tls.verifyPeerCertByName = json.vcn; if (typeof json.pcs === 'string') tls.pinnedPeerCertSha256 = json.pcs; + if (typeof json.cs === 'string') tls.cipherSuites = json.cs; } const port = Number(json.port) || 443; diff --git a/frontend/src/pages/hosts/HostFormModal.tsx b/frontend/src/pages/hosts/HostFormModal.tsx index 2cae68ed1..b126cff65 100644 --- a/frontend/src/pages/hosts/HostFormModal.tsx +++ b/frontend/src/pages/hosts/HostFormModal.tsx @@ -106,10 +106,12 @@ export default function HostFormModal({ const [loading, setLoading] = useState(false); const security = (useWatch({ control: methods.control, name: 'security' }) ?? 'same') as string; + const fingerprint = (useWatch({ control: methods.control, name: 'fingerprint' }) ?? '') as string; const hostEchConfig = useWatch({ control: methods.control, name: 'echConfigList' }) ?? ''; const hostSni = useWatch({ control: methods.control, name: 'sni' }) ?? ''; const showTls = security === 'tls' || security === 'reality' || security === 'same'; const showTlsExtras = security === 'tls' || security === 'same'; + const isCipherSuitesEnabled = showTlsExtras && (!fingerprint || fingerprint === 'unsafe'); const watchedHosts = useWatch({ control: methods.control, name: 'hosts' }); const hasCidr = Boolean(watchedHosts?.some((h) => typeof h === 'string' && h.includes('/'))); @@ -364,7 +366,10 @@ export default function HostFormModal({ name="cipherSuites" label={t('pages.inbounds.form.cipherSuites')} > - +