mirror of https://github.com/ospab/ostp.git
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:
parent
032f694821
commit
77ec0e3a44
|
|
@ -10,7 +10,11 @@ use std::fs::OpenOptions;
|
||||||
use std::io::Write as _;
|
use std::io::Write as _;
|
||||||
|
|
||||||
fn log_to_core_file(msg: &str) {
|
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);
|
let _ = writeln!(file, "[{}] {}", chrono::Local::now().format("%Y-%m-%d %H:%M:%S"), msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,12 @@
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
use std::process::Command;
|
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")]
|
#[cfg(target_os = "windows")]
|
||||||
#[link(name = "wininet")]
|
#[link(name = "wininet")]
|
||||||
extern "system" {
|
extern "system" {
|
||||||
|
|
@ -18,50 +24,74 @@ const INTERNET_OPTION_REFRESH: u32 = 37;
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
pub fn enable_windows_proxy(proxy_addr: &str) {
|
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([
|
.args([
|
||||||
"add",
|
"add",
|
||||||
"HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings",
|
"HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings",
|
||||||
"/v",
|
"/v", "ProxyEnable",
|
||||||
"ProxyEnable",
|
"/t", "REG_DWORD",
|
||||||
"/t",
|
"/d", "1",
|
||||||
"REG_DWORD",
|
|
||||||
"/d",
|
|
||||||
"1",
|
|
||||||
"/f",
|
"/f",
|
||||||
])
|
])
|
||||||
.output();
|
.output();
|
||||||
|
match result {
|
||||||
let proxy_str = proxy_addr.to_string();
|
Ok(out) if !out.status.success() => {
|
||||||
let _ = Command::new("reg")
|
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([
|
.args([
|
||||||
"add",
|
"add",
|
||||||
"HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings",
|
"HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings",
|
||||||
"/v",
|
"/v", "ProxyServer",
|
||||||
"ProxyServer",
|
"/t", "REG_SZ",
|
||||||
"/t",
|
"/d", proxy_addr,
|
||||||
"REG_SZ",
|
"/f",
|
||||||
"/d",
|
])
|
||||||
&proxy_str,
|
.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",
|
"/f",
|
||||||
])
|
])
|
||||||
.output();
|
.output();
|
||||||
|
|
||||||
refresh_wininet();
|
refresh_wininet();
|
||||||
|
eprintln!("[ostp] System proxy enabled successfully");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
pub fn disable_windows_proxy() {
|
pub fn disable_windows_proxy() {
|
||||||
|
eprintln!("[ostp] Disabling Windows system proxy");
|
||||||
let _ = Command::new("reg")
|
let _ = Command::new("reg")
|
||||||
|
.creation_flags(CREATE_NO_WINDOW)
|
||||||
.args([
|
.args([
|
||||||
"add",
|
"add",
|
||||||
"HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings",
|
"HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings",
|
||||||
"/v",
|
"/v", "ProxyEnable",
|
||||||
"ProxyEnable",
|
"/t", "REG_DWORD",
|
||||||
"/t",
|
"/d", "0",
|
||||||
"REG_DWORD",
|
|
||||||
"/d",
|
|
||||||
"0",
|
|
||||||
"/f",
|
"/f",
|
||||||
])
|
])
|
||||||
.output();
|
.output();
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,10 @@ impl FrameHeader {
|
||||||
pub fn encode(&self, out: &mut BytesMut) {
|
pub fn encode(&self, out: &mut BytesMut) {
|
||||||
out.put_u8(self.version);
|
out.put_u8(self.version);
|
||||||
out.put_u8(self.kind as u8);
|
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_u16(self.stream_id);
|
||||||
out.put_u32(self.payload_len);
|
out.put_u32(self.payload_len);
|
||||||
out.put_u16(self.pad_len);
|
out.put_u16(self.pad_len);
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,7 @@ impl AdaptivePadder {
|
||||||
let jitter = if jitter_cap == 0 {
|
let jitter = if jitter_cap == 0 {
|
||||||
0
|
0
|
||||||
} else {
|
} 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)
|
(base_pad + jitter).min(self.max_pad)
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
|
use rand::Rng;
|
||||||
use sha2::{Digest, Sha256};
|
use sha2::{Digest, Sha256};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use std::collections::{BTreeMap, VecDeque};
|
use std::collections::{BTreeMap, VecDeque};
|
||||||
|
|
@ -376,9 +377,19 @@ impl ProtocolMachine {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn wrap_datagram_handshake(&self, noise_payload: &[u8]) -> Result<Bytes, ProtocolError> {
|
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(&self.session_id.to_be_bytes());
|
||||||
out.extend_from_slice(noise_payload);
|
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);
|
crate::crypto::obfuscate_packet_inplace(&mut out, &self.obfuscation_key, true);
|
||||||
Ok(Bytes::from(out))
|
Ok(Bytes::from(out))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -318,7 +318,9 @@ async fn start_proxy_in_process(
|
||||||
let metrics = Arc::new(BridgeMetrics {
|
let metrics = Arc::new(BridgeMetrics {
|
||||||
bytes_sent: portable_atomic::AtomicU64::new(0),
|
bytes_sent: portable_atomic::AtomicU64::new(0),
|
||||||
bytes_recv: 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);
|
let (shutdown_tx, shutdown_rx) = watch::channel(false);
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,11 @@ use tokio::net::TcpListener;
|
||||||
use portable_atomic::Ordering;
|
use portable_atomic::Ordering;
|
||||||
|
|
||||||
fn log_to_file(msg: &str) {
|
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);
|
let _ = writeln!(file, "[{}] {}", chrono::Local::now().format("%Y-%m-%d %H:%M:%S"), msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue