feat(gui): add junk and tcp fragmentation global settings

This commit is contained in:
ospab 2026-07-09 02:35:21 +03:00
parent b89c6b0950
commit 2819e2b3c2
3 changed files with 158 additions and 7 deletions

View File

@ -302,6 +302,40 @@
</label> </label>
</div> </div>
<div class="section-divider-mini"><span>Obfuscation</span></div>
<div class="toggle-row" style="border-top:none;">
<div class="toggle-text">
<span class="toggle-name">Junk Packets</span>
<span class="toggle-hint">Random noise before connection (UoT only)</span>
</div>
<div class="toggle-with-gear">
<label class="toggle">
<input type="checkbox" id="cs-junk-enabled" />
<span class="toggle-track"><span class="toggle-thumb"></span></span>
</label>
<button id="btn-junk-settings" class="gear-btn" title="Configure junk packets">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg>
</button>
</div>
</div>
<div class="toggle-row" style="border-top:none;">
<div class="toggle-text">
<span class="toggle-name">TCP Fragmentation</span>
<span class="toggle-hint">Split handshake into segments (UoT only)</span>
</div>
<div class="toggle-with-gear">
<label class="toggle">
<input type="checkbox" id="cs-tcp-frag" />
<span class="toggle-track"><span class="toggle-thumb"></span></span>
</label>
<button id="btn-frag-settings" class="gear-btn" title="Configure fragmentation">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg>
</button>
</div>
</div>
</div> <!-- client-settings-card --> </div> <!-- client-settings-card -->
<div class="app-version" id="app-version">OSTP GUI</div> <div class="app-version" id="app-version">OSTP GUI</div>
@ -457,6 +491,58 @@
</div> </div>
</div> </div>
<!-- ── JUNK PACKETS MODAL ──────────────────────────────────── -->
<div id="junk-modal" class="modal-overlay hidden">
<div class="modal-content compact">
<h3 class="modal-title">Junk Packets</h3>
<p class="modal-text">Sends random garbage frames before the Noise handshake to confuse DPI pattern matching. Active in UoT/TCP mode only.</p>
<div class="modal-row-2">
<div class="field-group">
<label class="field-label" for="cs-junk-pc-min">Count min</label>
<input id="cs-junk-pc-min" class="field-input" type="number" min="0" max="100" placeholder="2" />
</div>
<div class="field-group">
<label class="field-label" for="cs-junk-pc-max">Count max</label>
<input id="cs-junk-pc-max" class="field-input" type="number" min="0" max="100" placeholder="5" />
</div>
</div>
<div class="modal-row-2">
<div class="field-group">
<label class="field-label" for="cs-junk-ps-min">Size min (bytes)</label>
<input id="cs-junk-ps-min" class="field-input" type="number" min="1" placeholder="100" />
</div>
<div class="field-group">
<label class="field-label" for="cs-junk-ps-max">Size max (bytes)</label>
<input id="cs-junk-ps-max" class="field-input" type="number" min="1" placeholder="1000" />
</div>
</div>
<div class="modal-actions">
<button id="btn-junk-done" class="btn primary">Done</button>
</div>
</div>
</div>
<!-- ── TCP FRAGMENTATION MODAL ──────────────────────────────── -->
<div id="frag-modal" class="modal-overlay hidden">
<div class="modal-content compact">
<h3 class="modal-title">TCP Fragmentation</h3>
<p class="modal-text">Splits the initial handshake into tiny TCP segments with delays between them. Prevents DPI from reading the full handshake signature. Active in UoT/TCP mode only.</p>
<div class="modal-row-2">
<div class="field-group">
<label class="field-label" for="cs-frag-chunk">Chunk size (bytes)</label>
<input id="cs-frag-chunk" class="field-input" type="number" min="1" placeholder="2" />
</div>
<div class="field-group">
<label class="field-label" for="cs-frag-sleep">Delay (ms)</label>
<input id="cs-frag-sleep" class="field-input" type="number" min="0" placeholder="2" />
</div>
</div>
<div class="modal-actions">
<button id="btn-frag-done" class="btn primary">Done</button>
</div>
</div>
</div>
<!-- Toast notification --> <!-- Toast notification -->
<div id="toast" class="toast" role="status" aria-live="polite"></div> <div id="toast" class="toast" role="status" aria-live="polite"></div>

View File

@ -159,6 +159,22 @@ const inShowSpeed = $('in-show-speed');
const groupKillSwitch = $('group-kill-switch'); const groupKillSwitch = $('group-kill-switch');
const groupMuxSessions = $('group-mux-sessions'); const groupMuxSessions = $('group-mux-sessions');
const inJunkEnabled = $('cs-junk-enabled');
const btnJunkSettings = $('btn-junk-settings');
const junkModal = $('junk-modal');
const inJunkPcMin = $('cs-junk-pc-min');
const inJunkPcMax = $('cs-junk-pc-max');
const inJunkPsMin = $('cs-junk-ps-min');
const inJunkPsMax = $('cs-junk-ps-max');
const btnJunkDone = $('btn-junk-done');
const inTcpFrag = $('cs-tcp-frag');
const btnFragSettings = $('btn-frag-settings');
const fragModal = $('frag-modal');
const inFragChunk = $('cs-frag-chunk');
const inFragSleep = $('cs-frag-sleep');
const btnFragDone = $('btn-frag-done');
// ── UTILITIES ───────────────────────────────────────────────────────── // ── UTILITIES ─────────────────────────────────────────────────────────
function fmtBytes(b) { function fmtBytes(b) {
if (!b || b === 0) return '0 B'; if (!b || b === 0) return '0 B';
@ -307,11 +323,11 @@ function buildConfig() {
debug: !!s.debug, debug: !!s.debug,
transport: { transport: {
mode: active.transport || 'udp', mode: active.transport || 'udp',
tcp_fragmentation: !!active.tcp_fragmentation, tcp_fragmentation: s.tcpFrag || !!active.tcp_fragmentation,
frag_chunk: active.frag_chunk || 2, frag_chunk: s.tcpFrag ? (s.fragChunk || 2) : (active.frag_chunk || 2),
frag_sleep: active.frag_sleep || 2, frag_sleep: s.tcpFrag ? (!isNaN(parseInt(s.fragSleep)) ? s.fragSleep : 2) : (active.frag_sleep !== undefined ? active.frag_sleep : 2),
junk_pc: active.junk_pc || [2, 5], junk_pc: s.junkEnabled ? [s.junkPcMin || 2, s.junkPcMax || 5] : (active.junk_pc || [2, 5]),
junk_ps: active.junk_ps || [100, 1000] junk_ps: s.junkEnabled ? [s.junkPsMin || 100, s.junkPsMax || 1000] : (active.junk_ps || [100, 1000])
}, },
tun: { tun: {
enable: !!s.tun, enable: !!s.tun,
@ -668,6 +684,14 @@ function loadSettingsIntoForm() {
inDebug.checked = !!s.debug; inDebug.checked = !!s.debug;
inShowRtt.checked = s.showRtt !== false; inShowRtt.checked = s.showRtt !== false;
inShowSpeed.checked = s.showSpeed !== false; inShowSpeed.checked = s.showSpeed !== false;
inJunkEnabled.checked = !!s.junkEnabled;
inJunkPcMin.value = s.junkPcMin || 2;
inJunkPcMax.value = s.junkPcMax || 5;
inJunkPsMin.value = s.junkPsMin || 100;
inJunkPsMax.value = s.junkPsMax || 1000;
inTcpFrag.checked = !!s.tcpFrag;
inFragChunk.value = s.fragChunk || 2;
inFragSleep.value = !isNaN(parseInt(s.fragSleep)) ? s.fragSleep : 2;
updateClientVisibility(); updateClientVisibility();
} }
@ -688,6 +712,14 @@ function collectAndSaveSettings() {
debug: inDebug.checked, debug: inDebug.checked,
showRtt: inShowRtt.checked, showRtt: inShowRtt.checked,
showSpeed: inShowSpeed.checked, showSpeed: inShowSpeed.checked,
junkEnabled: inJunkEnabled.checked,
junkPcMin: parseInt(inJunkPcMin.value) || 2,
junkPcMax: parseInt(inJunkPcMax.value) || 5,
junkPsMin: parseInt(inJunkPsMin.value) || 100,
junkPsMax: parseInt(inJunkPsMax.value) || 1000,
tcpFrag: inTcpFrag.checked,
fragChunk: parseInt(inFragChunk.value) || 2,
fragSleep: !isNaN(parseInt(inFragSleep.value)) ? parseInt(inFragSleep.value) : 2,
}; };
saveClientSettings(s); saveClientSettings(s);
updateClientVisibility(); updateClientVisibility();
@ -881,9 +913,9 @@ window.addEventListener('DOMContentLoaded', async () => {
wintunModal.addEventListener('click', e => { if (e.target === wintunModal) wintunModal.classList.add('hidden'); }); wintunModal.addEventListener('click', e => { if (e.target === wintunModal) wintunModal.classList.add('hidden'); });
// Client settings — wire all inputs // Client settings — wire all inputs
[inTun, inKillSwitch, inMux, inAutoconnect, inLaunchStartup, inDebug, inShowRtt, inShowSpeed] [inTun, inKillSwitch, inMux, inAutoconnect, inLaunchStartup, inDebug, inShowRtt, inShowSpeed, inJunkEnabled, inTcpFrag]
.forEach(el => el.addEventListener('change', collectAndSaveSettings)); .forEach(el => el.addEventListener('change', collectAndSaveSettings));
[inMuxSessions, inMtu, inDns, inSocks, inExDomains, inExIps, inExProcs] [inMuxSessions, inMtu, inDns, inSocks, inExDomains, inExIps, inExProcs, inJunkPcMin, inJunkPcMax, inJunkPsMin, inJunkPsMax, inFragChunk, inFragSleep]
.forEach(el => { .forEach(el => {
el.addEventListener('input', () => { el.addEventListener('input', () => {
clearTimeout(el._saveTimer); clearTimeout(el._saveTimer);
@ -891,6 +923,21 @@ window.addEventListener('DOMContentLoaded', async () => {
}); });
}); });
// Junk and Frag modals
btnJunkSettings.addEventListener('click', () => junkModal.classList.remove('hidden'));
btnJunkDone.addEventListener('click', () => {
collectAndSaveSettings();
junkModal.classList.add('hidden');
});
junkModal.addEventListener('click', e => { if (e.target === junkModal) junkModal.classList.add('hidden'); });
btnFragSettings.addEventListener('click', () => fragModal.classList.remove('hidden'));
btnFragDone.addEventListener('click', () => {
collectAndSaveSettings();
fragModal.classList.add('hidden');
});
fragModal.addEventListener('click', e => { if (e.target === fragModal) fragModal.classList.add('hidden'); });
// ── Global Keyboard Shortcuts (TUI emulation) ───────────────────── // ── Global Keyboard Shortcuts (TUI emulation) ─────────────────────
window.addEventListener('keydown', async e => { window.addEventListener('keydown', async e => {
// Ignore if typing in an input or textarea // Ignore if typing in an input or textarea
@ -922,6 +969,8 @@ window.addEventListener('DOMContentLoaded', async () => {
!profileModal.classList.contains('hidden') || !profileModal.classList.contains('hidden') ||
!shareModal.classList.contains('hidden') || !shareModal.classList.contains('hidden') ||
!wintunModal.classList.contains('hidden') || !wintunModal.classList.contains('hidden') ||
!junkModal.classList.contains('hidden') ||
!fragModal.classList.contains('hidden') ||
!addMenu.classList.contains('hidden')) { !addMenu.classList.contains('hidden')) {
return; return;
} }

View File

@ -604,6 +604,22 @@ a { text-decoration: none; }
background: var(--c-accent); background: var(--c-accent);
} }
.toggle-with-gear {
display: flex;
align-items: center;
gap: 10px;
}
.gear-btn {
width: 26px; height: 26px;
display: flex; align-items: center; justify-content: center;
border-radius: var(--r-xs);
color: var(--c-txt-2);
transition: all var(--t-fast);
background: transparent;
}
.gear-btn:hover { color: var(--c-txt-1); background: rgba(255,255,255,0.07); }
:root.light .gear-btn:hover { background: rgba(0,0,0,0.05); }
/* ── Inline field (label + input on one line) ────────────────────────── */ /* ── Inline field (label + input on one line) ────────────────────────── */
.inline-field { .inline-field {
display: flex; display: flex;