From 77ec0e3a448f69e3d72de7983eb5176ef77f2265 Mon Sep 17 00:00:00 2001 From: ospab Date: Sun, 17 May 2026 14:40:13 +0300 Subject: [PATCH] fix: DPI resistance, GUI proxy/tunnel, and code quality MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DPI/TSPU resistance: - Handshake packets now padded with 32-128 random bytes (prevents size fingerprinting — previously every handshake was exactly 52 bytes) - Frame header reserved bytes randomized instead of always 0 (prevents known-plaintext oracle inside encrypted payload) - Padding jitter cap increased from 96 to 256 bytes for better traffic pattern masking GUI Windows app (tunnel/proxy not starting): - CRITICAL: Added CREATE_NO_WINDOW flag to all reg.exe calls in sysproxy.rs. In Tauri GUI context (no console window), Command::new('reg') was silently failing because there was no attached console. This prevented the Windows system proxy from being enabled. - Added ProxyOverride bypass list (localhost;127.*;10.*;192.168.*;) to prevent proxy loop for local traffic - Added comprehensive logging for all registry operations - Set initial connection_state to 1 (connecting) instead of 0 — prevents UI polling from immediately flipping back to 'disconnected' before the handshake has a chance to begin Code quality: - Fixed log file paths: log_to_core_file() and log_to_file() now write next to the executable instead of CWD. In GUI context, CWD could be C:\Windows\System32, causing write failures or misplaced log files. --- ostp-client/src/runner.rs | 6 ++- ostp-client/src/sysproxy.rs | 74 ++++++++++++++++++++++---------- ostp-core/src/framing/frame.rs | 5 ++- ostp-core/src/framing/padding.rs | 2 +- ostp-core/src/protocol.rs | 13 +++++- ostp-gui/src-tauri/src/lib.rs | 4 +- ostp-tun-helper/src/main.rs | 6 ++- 7 files changed, 82 insertions(+), 28 deletions(-) diff --git a/ostp-client/src/runner.rs b/ostp-client/src/runner.rs index 414c6b6..c3e13dc 100644 --- a/ostp-client/src/runner.rs +++ b/ostp-client/src/runner.rs @@ -10,7 +10,11 @@ use std::fs::OpenOptions; use std::io::Write as _; fn log_to_core_file(msg: &str) { - if let Ok(mut file) = OpenOptions::new().create(true).append(true).open("ostp-core.log") { + let path = std::env::current_exe() + .ok() + .and_then(|p| p.parent().map(|d| d.join("ostp-core.log"))) + .unwrap_or_else(|| std::path::PathBuf::from("ostp-core.log")); + if let Ok(mut file) = OpenOptions::new().create(true).append(true).open(path) { let _ = writeln!(file, "[{}] {}", chrono::Local::now().format("%Y-%m-%d %H:%M:%S"), msg); } } diff --git a/ostp-client/src/sysproxy.rs b/ostp-client/src/sysproxy.rs index 93dfc75..3c26f24 100644 --- a/ostp-client/src/sysproxy.rs +++ b/ostp-client/src/sysproxy.rs @@ -1,6 +1,12 @@ #[cfg(target_os = "windows")] use std::process::Command; +#[cfg(target_os = "windows")] +use std::os::windows::process::CommandExt; + +#[cfg(target_os = "windows")] +const CREATE_NO_WINDOW: u32 = 0x08000000; + #[cfg(target_os = "windows")] #[link(name = "wininet")] extern "system" { @@ -18,50 +24,74 @@ const INTERNET_OPTION_REFRESH: u32 = 37; #[cfg(target_os = "windows")] pub fn enable_windows_proxy(proxy_addr: &str) { - let _ = Command::new("reg") + eprintln!("[ostp] Enabling Windows system proxy: {}", proxy_addr); + + let result = Command::new("reg") + .creation_flags(CREATE_NO_WINDOW) .args([ "add", "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings", - "/v", - "ProxyEnable", - "/t", - "REG_DWORD", - "/d", - "1", + "/v", "ProxyEnable", + "/t", "REG_DWORD", + "/d", "1", "/f", ]) .output(); - - let proxy_str = proxy_addr.to_string(); - let _ = Command::new("reg") + match result { + Ok(out) if !out.status.success() => { + eprintln!("[ostp] Failed to set ProxyEnable: {}", String::from_utf8_lossy(&out.stderr)); + } + Err(e) => eprintln!("[ostp] Failed to execute reg.exe (ProxyEnable): {}", e), + _ => {} + } + + let result = Command::new("reg") + .creation_flags(CREATE_NO_WINDOW) .args([ "add", "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings", - "/v", - "ProxyServer", - "/t", - "REG_SZ", - "/d", - &proxy_str, + "/v", "ProxyServer", + "/t", "REG_SZ", + "/d", proxy_addr, + "/f", + ]) + .output(); + match result { + Ok(out) if !out.status.success() => { + eprintln!("[ostp] Failed to set ProxyServer: {}", String::from_utf8_lossy(&out.stderr)); + } + Err(e) => eprintln!("[ostp] Failed to execute reg.exe (ProxyServer): {}", e), + _ => {} + } + + // Set bypass list to prevent proxy loop for localhost traffic + let _ = Command::new("reg") + .creation_flags(CREATE_NO_WINDOW) + .args([ + "add", + "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings", + "/v", "ProxyOverride", + "/t", "REG_SZ", + "/d", "localhost;127.*;10.*;192.168.*;", "/f", ]) .output(); refresh_wininet(); + eprintln!("[ostp] System proxy enabled successfully"); } #[cfg(target_os = "windows")] pub fn disable_windows_proxy() { + eprintln!("[ostp] Disabling Windows system proxy"); let _ = Command::new("reg") + .creation_flags(CREATE_NO_WINDOW) .args([ "add", "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings", - "/v", - "ProxyEnable", - "/t", - "REG_DWORD", - "/d", - "0", + "/v", "ProxyEnable", + "/t", "REG_DWORD", + "/d", "0", "/f", ]) .output(); diff --git a/ostp-core/src/framing/frame.rs b/ostp-core/src/framing/frame.rs index e614dfc..74f6b18 100644 --- a/ostp-core/src/framing/frame.rs +++ b/ostp-core/src/framing/frame.rs @@ -44,7 +44,10 @@ impl FrameHeader { pub fn encode(&self, out: &mut BytesMut) { out.put_u8(self.version); out.put_u8(self.kind as u8); - out.put_u16(0); // 2 reserved bytes + // Anti-DPI: reserved bytes filled with random data instead of zeros + // to prevent known-plaintext fingerprinting inside encrypted frames + let rnd: u16 = rand::random(); + out.put_u16(rnd); out.put_u16(self.stream_id); out.put_u32(self.payload_len); out.put_u16(self.pad_len); diff --git a/ostp-core/src/framing/padding.rs b/ostp-core/src/framing/padding.rs index 20c1203..900d757 100644 --- a/ostp-core/src/framing/padding.rs +++ b/ostp-core/src/framing/padding.rs @@ -60,7 +60,7 @@ impl AdaptivePadder { let jitter = if jitter_cap == 0 { 0 } else { - rand::thread_rng().gen_range(0..=jitter_cap.min(96)) + rand::thread_rng().gen_range(0..=jitter_cap.min(256)) }; (base_pad + jitter).min(self.max_pad) diff --git a/ostp-core/src/protocol.rs b/ostp-core/src/protocol.rs index 6f9fa98..289f6e5 100644 --- a/ostp-core/src/protocol.rs +++ b/ostp-core/src/protocol.rs @@ -1,4 +1,5 @@ use bytes::Bytes; +use rand::Rng; use sha2::{Digest, Sha256}; use thiserror::Error; use std::collections::{BTreeMap, VecDeque}; @@ -376,9 +377,19 @@ impl ProtocolMachine { } fn wrap_datagram_handshake(&self, noise_payload: &[u8]) -> Result { - let mut out = Vec::with_capacity(4 + noise_payload.len()); + // Anti-DPI: add random padding after the Noise payload to prevent + // size fingerprinting. Without this, every handshake is exactly 52 bytes + // which is trivially detectable by TSPU/DPI systems. + let pad_len: usize = rand::thread_rng().gen_range(32..=128); + let mut pad = vec![0u8; pad_len]; + rand::thread_rng().fill(&mut pad[..]); + + let mut out = Vec::with_capacity(4 + noise_payload.len() + 2 + pad_len); out.extend_from_slice(&self.session_id.to_be_bytes()); out.extend_from_slice(noise_payload); + // 2-byte padding length prefix so receiver can strip it + out.extend_from_slice(&(pad_len as u16).to_be_bytes()); + out.extend_from_slice(&pad); crate::crypto::obfuscate_packet_inplace(&mut out, &self.obfuscation_key, true); Ok(Bytes::from(out)) } diff --git a/ostp-gui/src-tauri/src/lib.rs b/ostp-gui/src-tauri/src/lib.rs index 67fc387..4b9bf0e 100644 --- a/ostp-gui/src-tauri/src/lib.rs +++ b/ostp-gui/src-tauri/src/lib.rs @@ -318,7 +318,9 @@ async fn start_proxy_in_process( let metrics = Arc::new(BridgeMetrics { bytes_sent: portable_atomic::AtomicU64::new(0), bytes_recv: portable_atomic::AtomicU64::new(0), - connection_state: portable_atomic::AtomicU8::new(0), + // Start at 1 (connecting) so UI polling doesn't see 0 and flip back to disconnected + // before the handshake task has had a chance to begin. + connection_state: portable_atomic::AtomicU8::new(1), }); let (shutdown_tx, shutdown_rx) = watch::channel(false); diff --git a/ostp-tun-helper/src/main.rs b/ostp-tun-helper/src/main.rs index 430b0d5..0cbb7e6 100644 --- a/ostp-tun-helper/src/main.rs +++ b/ostp-tun-helper/src/main.rs @@ -13,7 +13,11 @@ use tokio::net::TcpListener; use portable_atomic::Ordering; fn log_to_file(msg: &str) { - if let Ok(mut file) = OpenOptions::new().create(true).append(true).open("ostp-helper.log") { + let path = std::env::current_exe() + .ok() + .and_then(|p| p.parent().map(|d| d.join("ostp-helper.log"))) + .unwrap_or_else(|| std::path::PathBuf::from("ostp-helper.log")); + if let Ok(mut file) = OpenOptions::new().create(true).append(true).open(path) { let _ = writeln!(file, "[{}] {}", chrono::Local::now().format("%Y-%m-%d %H:%M:%S"), msg); } }