fix: DPI resistance, GUI proxy/tunnel, and code quality

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.*;<local>)
  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.
This commit is contained in:
ospab 2026-05-17 14:40:13 +03:00
parent 032f694821
commit 77ec0e3a44
7 changed files with 82 additions and 28 deletions

View File

@ -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);
}
}

View File

@ -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();
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 proxy_str = proxy_addr.to_string();
let _ = Command::new("reg")
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.*;<local>",
"/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();

View File

@ -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);

View File

@ -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)

View File

@ -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<Bytes, ProtocolError> {
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))
}

View File

@ -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);

View File

@ -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);
}
}