From bfd079ff4871220e94efb78ef621aeac56232e93 Mon Sep 17 00:00:00 2001 From: ospab Date: Wed, 19 Aug 2026 19:58:18 +0300 Subject: [PATCH] feat(gui): TTL-desync toggle in client settings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a TTL Desync toggle to the obfuscation section of the desktop client settings, wired the same way as the Junk/TCP-fragmentation toggles: persisted in client settings, applied into transport.ttl_desync in the built config, included in the hot-reload fingerprint so flipping it while connected re-applies, and labelled in both EN and RU. ttl_desync_auto is passed true, so the engine measures the hop distance and aims the decoys itself — the user just flips it on. --- ostp-gui/src/i18n.js | 4 ++++ ostp-gui/src/index.html | 11 +++++++++++ ostp-gui/src/main.js | 12 +++++++++--- 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/ostp-gui/src/i18n.js b/ostp-gui/src/i18n.js index 5fd1ef7..71f40f9 100644 --- a/ostp-gui/src/i18n.js +++ b/ostp-gui/src/i18n.js @@ -28,6 +28,8 @@ const translations = { label_kill_switch: 'Kill Switch', kill_switch_hint: 'Block non-VPN traffic when tunnel drops', label_transport: 'Transport Protocol', + label_ttl_desync: 'TTL Desync', + hint_ttl_desync: 'Decoy packets that die before the server (UDP, auto-tuned)', label_mtu: 'MTU Size', label_transport: 'Transport Protocol', label_sni: 'Stealth SNI (Fake Host)', @@ -93,6 +95,8 @@ const translations = { label_kill_switch: 'Kill Switch', kill_switch_hint: 'Блокировать трафик вне VPN при обрыве связи', label_transport: 'Транспортный протокол', + label_ttl_desync: 'TTL-десинхронизация', + hint_ttl_desync: 'Пакеты-приманки, умирающие до сервера (UDP, авто-настройка)', label_mtu: 'Размер MTU', label_transport: 'Транспортный протокол', label_sni: 'Маскировочный SNI', diff --git a/ostp-gui/src/index.html b/ostp-gui/src/index.html index a60e6cf..6c013f8 100644 --- a/ostp-gui/src/index.html +++ b/ostp-gui/src/index.html @@ -336,6 +336,17 @@ +
+
+ TTL Desync + Decoy packets that die before the server (UDP, auto-tuned) +
+ +
+
OSTP GUI
diff --git a/ostp-gui/src/main.js b/ostp-gui/src/main.js index 58f83ed..5b5b1b9 100644 --- a/ostp-gui/src/main.js +++ b/ostp-gui/src/main.js @@ -161,6 +161,7 @@ const btnJunkDone = $('btn-junk-done'); const inTcpFrag = $('cs-tcp-frag'); const btnFragSettings = $('btn-frag-settings'); +const inTtlDesync = $('cs-ttl-desync'); const fragModal = $('frag-modal'); const inFragChunk = $('cs-frag-chunk'); const inFragSleep = $('cs-frag-sleep'); @@ -318,7 +319,9 @@ function buildConfig() { frag_chunk: s.tcpFrag ? (s.fragChunk || 2) : (active.frag_chunk || 2), frag_sleep: s.tcpFrag ? (!isNaN(parseInt(s.fragSleep)) ? s.fragSleep : 2) : (active.frag_sleep !== undefined ? active.frag_sleep : 2), junk_pc: s.junkEnabled ? [s.junkPcMin || 2, s.junkPcMax || 5] : (active.junk_pc || [2, 5]), - junk_ps: s.junkEnabled ? [s.junkPsMin || 100, s.junkPsMax || 1000] : (active.junk_ps || [100, 1000]) + junk_ps: s.junkEnabled ? [s.junkPsMin || 100, s.junkPsMax || 1000] : (active.junk_ps || [100, 1000]), + ttl_desync: !!s.ttlDesync, + ttl_desync_auto: true }, tun: { enable: !!s.tun, @@ -657,6 +660,7 @@ function loadSettingsIntoForm() { inTcpFrag.checked = !!s.tcpFrag; inFragChunk.value = s.fragChunk || 2; inFragSleep.value = !isNaN(parseInt(s.fragSleep)) ? s.fragSleep : 2; + if (inTtlDesync) inTtlDesync.checked = !!s.ttlDesync; updateClientVisibility(); } @@ -692,6 +696,7 @@ function collectAndSaveSettings() { tcpFrag: inTcpFrag.checked, fragChunk: parseInt(inFragChunk.value) || 2, fragSleep: !isNaN(parseInt(inFragSleep.value)) ? parseInt(inFragSleep.value) : 2, + ttlDesync: inTtlDesync ? inTtlDesync.checked : false, }; // Cheap and local: safe to run on every debounced keystroke. saveClientSettings(s); @@ -715,7 +720,7 @@ function collectAndSaveSettings() { const tunnelRelevant = JSON.stringify([ s.tun, s.killSwitch, s.mux, s.muxSessions, s.mtu, s.dns, s.socks, s.exDomains, s.exIps, s.exProcs, s.junkEnabled, s.junkPcMin, s.junkPcMax, - s.junkPsMin, s.junkPsMax, s.tcpFrag, s.fragChunk, s.fragSleep, + s.junkPsMin, s.junkPsMax, s.tcpFrag, s.fragChunk, s.fragSleep, s.ttlDesync, ]); if (tunnelRelevant !== lastAppliedTunnelConfig) { clearTimeout(hotReloadTimer); @@ -901,7 +906,8 @@ window.addEventListener('DOMContentLoaded', async () => { wintunModal.addEventListener('click', e => { if (e.target === wintunModal) wintunModal.classList.add('hidden'); }); // Client settings — wire all inputs - [inTun, inKillSwitch, inMux, inAutoconnect, inLaunchStartup, inDebug, inShowRtt, inShowSpeed, inJunkEnabled, inTcpFrag] + [inTun, inKillSwitch, inMux, inAutoconnect, inLaunchStartup, inDebug, inShowRtt, inShowSpeed, inJunkEnabled, inTcpFrag, inTtlDesync] + .filter(Boolean) .forEach(el => el.addEventListener('change', collectAndSaveSettings)); [inMuxSessions, inMtu, inDns, inSocks, inExDomains, inExIps, inExProcs, inJunkPcMin, inJunkPcMax, inJunkPsMin, inJunkPsMax, inFragChunk, inFragSleep] .forEach(el => {