From 31f3fff18736167fdca101651fec02d279dfa2c4 Mon Sep 17 00:00:00 2001 From: ospab Date: Sun, 17 May 2026 03:31:48 +0300 Subject: [PATCH] fix: GUI, JNI SDK, and TUN handler audit fixes ostp-gui: - GUI-01: Config parsing now strips JSONC comments via json_comments crate, matching CLI behavior. Previously failed on any commented config. - GUI-02: stop_tunnel now properly aborts the JoinHandle with a 2s timeout instead of silently dropping it. ostp-jni (Android SDK): - JNI-01: Replaced all .unwrap() calls in JNI functions with safe null_mut fallback. JNI functions must never panic. - JNI-02: Added missing exclusions, multiplex, debug fields to Kotlin SDK Config.toNativeJson(). Without these, serde deserialization on the native side could fail or use wrong defaults. - JNI-03: Replaced shutdown_background() with shutdown_timeout(3s) to allow proper task cleanup and port unbinding. - JNI-04: Updated Kotlin log string matchers to match professionalized messages (Connection established, TUN tunnel established, etc.) TUN handlers: - TUN-01: Windows TUN cleanup guard now resets DNS via netsh. Previously the custom DNS server remained configured after disconnect, causing complete DNS resolution failure. - Unified all remaining [ostp-client] log prefixes to [ostp] across wintun_handler.rs, linux_handler.rs, and proxy.rs. --- ostp-client/src/tunnel/linux_handler.rs | 10 +++++----- ostp-client/src/tunnel/proxy.rs | 20 ++++++++++---------- ostp-client/src/tunnel/wintun_handler.rs | 15 ++++++++------- ostp-gui/src-tauri/Cargo.toml | 1 + ostp-gui/src-tauri/src/lib.rs | 22 ++++++++++++++++------ ostp-jni/OstpClientSdk.kt | 18 ++++++++++++++---- ostp-jni/src/lib.rs | 22 +++++++++++++++++----- 7 files changed, 71 insertions(+), 37 deletions(-) diff --git a/ostp-client/src/tunnel/linux_handler.rs b/ostp-client/src/tunnel/linux_handler.rs index 5c867da..62c1923 100644 --- a/ostp-client/src/tunnel/linux_handler.rs +++ b/ostp-client/src/tunnel/linux_handler.rs @@ -102,7 +102,7 @@ pub async fn run_linux_tunnel( let server_ip_str = server_ip.to_string(); if debug { - println!("[ostp-client] Resolved server IP: {}", server_ip_str); + println!("[ostp] Resolved server IP: {}", server_ip_str); } // 3. Detect current default gateway and interface @@ -132,7 +132,7 @@ pub async fn run_linux_tunnel( } if debug { - println!("[ostp-client] Default route: gateway={} interface={}", default_gw, default_if); + println!("[ostp] Default route: gateway={} interface={}", default_gw, default_if); } // 4. Setup commands (Using standard /1 routing trick for fail-proof overriding) @@ -150,7 +150,7 @@ pub async fn run_linux_tunnel( ); if debug { - println!("[ostp-client] Executing Linux network config: {}", setup_script); + println!("[ostp] Executing Linux network config: {}", setup_script); } let out = Command::new("sh") @@ -158,7 +158,7 @@ pub async fn run_linux_tunnel( .output()?; if !out.status.success() && debug { - println!("[ostp-client] Warning: Setup routing returned: {}", String::from_utf8_lossy(&out.stderr)); + println!("[ostp] Warning: Setup routing returned: {}", String::from_utf8_lossy(&out.stderr)); } // 5. Prepare and launch tun2socks @@ -167,7 +167,7 @@ pub async fn run_linux_tunnel( let proxy_url = format!("http://{}", config.local_proxy.bind_addr); if debug { - println!("[ostp-client] Spawning {} -device ostp_tun -proxy {}", tun2socks_exe.display(), proxy_url); + println!("[ostp] Spawning {} -device ostp_tun -proxy {}", tun2socks_exe.display(), proxy_url); } let mut child = Command::new(&tun2socks_exe) diff --git a/ostp-client/src/tunnel/proxy.rs b/ostp-client/src/tunnel/proxy.rs index 87818ed..9809bfa 100644 --- a/ostp-client/src/tunnel/proxy.rs +++ b/ostp-client/src/tunnel/proxy.rs @@ -23,8 +23,8 @@ pub async fn run_local_socks5_proxy( .with_context(|| format!("failed to bind local HTTP/SOCKS5 proxy at {}", cfg.bind_addr))?; if debug { - eprintln!("[ostp-client] local HTTP/SOCKS5 proxy listening at {}", cfg.bind_addr); - eprintln!("[ostp-client] Windows system proxy: set HTTP proxy to {}. tun2socks: SOCKS5 on same address.", cfg.bind_addr); + eprintln!("[ostp] local HTTP/SOCKS5 proxy listening at {}", cfg.bind_addr); + eprintln!("[ostp] Windows system proxy: set HTTP proxy to {}. tun2socks: SOCKS5 on same address.", cfg.bind_addr); } let matcher = ExclusionMatcher::new(&exclusions); @@ -75,7 +75,7 @@ pub async fn run_local_socks5_proxy( && !msg.contains("unsupported SOCKS5 command") { if debug { - eprintln!("[ostp-client] proxy client error: {err}"); + eprintln!("[ostp] proxy client error: {err}"); } } } @@ -85,7 +85,7 @@ pub async fn run_local_socks5_proxy( if stream_id == 0 { if let ProxyToClientMsg::Close = msg { if debug { - eprintln!("[ostp-client] Resetting all active proxy streams on reconnect"); + eprintln!("[ostp] Resetting all active proxy streams on reconnect"); } for (_, tx) in active_streams.drain() { let _ = tx.send(ProxyToClientMsg::Close); @@ -200,7 +200,7 @@ async fn handle_proxy_client( }; if debug { - eprintln!("[ostp-client] proxy CONNECT stream_id={stream_id} target={target}"); + eprintln!("[ostp] proxy CONNECT stream_id={stream_id} target={target}"); } if matcher.should_bypass(&target, connect_timeout).await { return direct_connect_socks5(client, stream_id, &target, close_tx, debug).await; @@ -277,7 +277,7 @@ async fn handle_proxy_client( }; if debug { - eprintln!("[ostp-client] proxy CONNECT stream_id={stream_id} target={target}"); + eprintln!("[ostp] proxy CONNECT stream_id={stream_id} target={target}"); } if matcher.should_bypass(&target, connect_timeout).await { return direct_connect_http( @@ -333,7 +333,7 @@ async fn handle_proxy_client( Ok(0) => { let _ = event_tx.send(ProxyEvent::Close { stream_id }).await; if debug { - eprintln!("[ostp-client] proxy CLOSE stream_id={stream_id}"); + eprintln!("[ostp] proxy CLOSE stream_id={stream_id}"); } break; } @@ -346,7 +346,7 @@ async fn handle_proxy_client( Err(_) => { let _ = event_tx.send(ProxyEvent::Close { stream_id }).await; if debug { - eprintln!("[ostp-client] proxy CLOSE stream_id={stream_id}"); + eprintln!("[ostp] proxy CLOSE stream_id={stream_id}"); } break; } @@ -513,7 +513,7 @@ async fn direct_connect_socks5( debug: bool, ) -> Result<()> { if debug { - eprintln!("[ostp-client] proxy BYPASS stream_id={stream_id} target={target}"); + eprintln!("[ostp] proxy BYPASS stream_id={stream_id} target={target}"); } let mut remote = TcpStream::connect(target).await .with_context(|| format!("direct connect failed: {target}"))?; @@ -534,7 +534,7 @@ async fn direct_connect_http( debug: bool, ) -> Result<()> { if debug { - eprintln!("[ostp-client] proxy BYPASS stream_id={stream_id} target={target}"); + eprintln!("[ostp] proxy BYPASS stream_id={stream_id} target={target}"); } let mut remote = TcpStream::connect(target).await .with_context(|| format!("direct connect failed: {target}"))?; diff --git a/ostp-client/src/tunnel/wintun_handler.rs b/ostp-client/src/tunnel/wintun_handler.rs index d071817..1e566e7 100644 --- a/ostp-client/src/tunnel/wintun_handler.rs +++ b/ostp-client/src/tunnel/wintun_handler.rs @@ -24,7 +24,8 @@ pub async fn run_wintun_tunnel( "$remote_ip = '{}'\n\ Remove-NetRoute -DestinationPrefix \"$remote_ip/32\" -Confirm:$false -ErrorAction SilentlyContinue\n\ Remove-NetRoute -DestinationPrefix \"1.1.1.1/32\" -Confirm:$false -ErrorAction SilentlyContinue\n\ - Remove-NetFirewallRule -DisplayName 'OSTP Tunnel*' -ErrorAction SilentlyContinue\n", + Remove-NetFirewallRule -DisplayName 'OSTP Tunnel*' -ErrorAction SilentlyContinue\n\ + netsh interface ipv4 set dnsservers name=\"ostp_tun\" source=dhcp 2>$null\n", self.server_ip_str ); let _ = Command::new("powershell") @@ -64,12 +65,12 @@ pub async fn run_wintun_tunnel( let server_ip_str = server_ip.to_string(); if debug { - println!("[ostp-client] Resolved server IP: {}", server_ip_str); + println!("[ostp] Resolved server IP: {}", server_ip_str); } // 3. Run PowerShell script to configure system routes if debug { - println!("[ostp-client] Configuring system routes..."); + println!("[ostp] Configuring system routes..."); } let current_exe = std::env::current_exe()?.to_string_lossy().into_owned(); @@ -102,7 +103,7 @@ pub async fn run_wintun_tunnel( .output()?; if !out.status.success() && debug { - println!("[ostp-client] Warning: Setup routing returned: {}", String::from_utf8_lossy(&out.stderr)); + println!("[ostp] Warning: Setup routing returned: {}", String::from_utf8_lossy(&out.stderr)); } // 4. Prepare and launch tun2socks.exe in the background @@ -111,7 +112,7 @@ pub async fn run_wintun_tunnel( let proxy_url = format!("http://{}", config.local_proxy.bind_addr); if debug { - println!("[ostp-client] Starting tun2socks (proxy={})", proxy_url); + println!("[ostp] Starting tun2socks (proxy={})", proxy_url); } // Spawning buffer to allow local proxy listener to finish binding to local address @@ -139,7 +140,7 @@ pub async fn run_wintun_tunnel( tokio::time::sleep(tokio::time::Duration::from_secs(2)).await; if debug { - println!("[ostp-client] Applying network configuration..."); + println!("[ostp] Applying network configuration..."); } let mut net_setup = String::from("\ @@ -150,7 +151,7 @@ pub async fn run_wintun_tunnel( if let Some(ref dns) = config.dns_server { if !dns.is_empty() { if debug { - println!("[ostp-client] DNS server: {}", dns); + println!("[ostp] DNS server: {}", dns); } net_setup.push_str(&format!("netsh interface ipv4 set dnsservers name=\"ostp_tun\" static {} primary\n", dns)); } diff --git a/ostp-gui/src-tauri/Cargo.toml b/ostp-gui/src-tauri/Cargo.toml index 997eab8..bd6ca4e 100644 --- a/ostp-gui/src-tauri/Cargo.toml +++ b/ostp-gui/src-tauri/Cargo.toml @@ -26,4 +26,5 @@ tokio = { version = "1", features = ["full"] } anyhow = "1" ostp-client = { path = "../../ostp-client" } portable-atomic = "1" +json_comments = "0.2" diff --git a/ostp-gui/src-tauri/src/lib.rs b/ostp-gui/src-tauri/src/lib.rs index aaf1860..67fc387 100644 --- a/ostp-gui/src-tauri/src/lib.rs +++ b/ostp-gui/src-tauri/src/lib.rs @@ -206,15 +206,18 @@ async fn get_config() -> Result { "debug": false }"#.into()); } - std::fs::read_to_string(&path).map_err(|e| format!("Read error: {}", e)) + std::fs::read_to_string(&path) + .map_err(|e| format!("Failed to read config: {}", e)) } #[tauri::command] async fn save_config(json_content: String) -> Result { - let _parsed: UnifiedConfig = serde_json::from_str(&json_content) - .map_err(|e| format!("Invalid OSTP config JSON: {}", e))?; + // Strip JSONC comments before validation + let mut stripped = json_comments::StripComments::new(json_content.as_bytes()); + let _parsed: UnifiedConfig = serde_json::from_reader(&mut stripped) + .map_err(|e| format!("Invalid configuration: {}", e))?; let path = get_config_path(); - std::fs::write(path, json_content).map_err(|e| format!("Write error: {}", e))?; + std::fs::write(path, json_content).map_err(|e| format!("Failed to write config: {}", e))?; Ok(true) } @@ -260,7 +263,12 @@ async fn stop_tunnel(state: tauri::State<'_, AppState>) -> Result None => {} Some(TunnelHandle::InProcess(mut s)) => { if let Some(tx) = s.shutdown_tx.take() { let _ = tx.send(true); } - drop(s.handle); + s.handle.abort(); + // Brief wait for cleanup + let _ = tokio::time::timeout( + std::time::Duration::from_secs(2), + s.handle, + ).await; } Some(TunnelHandle::Helper(h)) => { let _ = h.cmd_tx.send("{\"cmd\":\"stop\"}\n".to_string()).await; @@ -284,7 +292,9 @@ async fn start_tunnel(state: tauri::State<'_, AppState>) -> Result let path = get_config_path(); let content = std::fs::read_to_string(&path).map_err(|e| e.to_string())?; - let unified: UnifiedConfig = serde_json::from_str(&content).map_err(|e| format!("Config parse error: {}", e))?; + let mut stripped = json_comments::StripComments::new(content.as_bytes()); + let unified: UnifiedConfig = serde_json::from_reader(&mut stripped) + .map_err(|e| format!("Config parse error: {}", e))?; let client_cfg = match unified.mode { AppMode::Client(c) => c, diff --git a/ostp-jni/OstpClientSdk.kt b/ostp-jni/OstpClientSdk.kt index 6f59dfb..f29d8b4 100644 --- a/ostp-jni/OstpClientSdk.kt +++ b/ostp-jni/OstpClientSdk.kt @@ -73,6 +73,7 @@ class OstpClientSdk private constructor(private val context: Context) { fun toNativeJson(): String { return JSONObject().apply { put("mode", mode) + put("debug", false) put("ostp", JSONObject().apply { put("server_addr", server) put("local_bind_addr", "0.0.0.0:0") @@ -90,6 +91,15 @@ class OstpClientSdk private constructor(private val context: Context) { put("username", turnUsername) put("access_key", turnPassword) }) + put("exclusions", JSONObject().apply { + put("domains", org.json.JSONArray()) + put("ips", org.json.JSONArray()) + put("processes", org.json.JSONArray()) + }) + put("multiplex", JSONObject().apply { + put("enabled", false) + put("sessions", 1) + }) }.toString() } } @@ -223,13 +233,13 @@ class OstpClientSdk private constructor(private val context: Context) { emitLog(line) // Detect state transitions from log content when { - line.contains("Bridge connection established") || - line.contains("TUN Tunnel established") -> { + line.contains("Connection established") || + line.contains("TUN tunnel established") -> { wasConnected = true } line.contains("Bridge stopped") || - line.contains("Tunnel stopped") || - line.contains("Handshake failed") -> { + line.contains("TUN tunnel stopped") || + line.contains("Connection failed") -> { wasConnected = false } } diff --git a/ostp-jni/src/lib.rs b/ostp-jni/src/lib.rs index b35e51f..fb1bf95 100644 --- a/ostp-jni/src/lib.rs +++ b/ostp-jni/src/lib.rs @@ -158,7 +158,7 @@ pub extern "system" fn Java_net_ostp_client_OstpClientSdk_stopClient( } if let Some(rt) = state.runtime.take() { - rt.shutdown_background(); + rt.shutdown_timeout(std::time::Duration::from_secs(3)); } state.metrics = None; @@ -173,16 +173,25 @@ pub extern "system" fn Java_net_ostp_client_OstpClientSdk_getMetrics( ) -> jstring { let state = match STATE.lock() { Ok(s) => s, - Err(_) => return env.new_string("{}").unwrap().into_raw(), + Err(_) => return match env.new_string("{}") { + Ok(s) => s.into_raw(), + Err(_) => std::ptr::null_mut(), + }, }; if let Some(m) = &state.metrics { let sent = m.bytes_sent.load(Ordering::Relaxed); let recv = m.bytes_recv.load(Ordering::Relaxed); let json = format!(r#"{{"bytes_sent": {}, "bytes_recv": {}}}"#, sent, recv); - env.new_string(json).unwrap().into_raw() + match env.new_string(json) { + Ok(s) => s.into_raw(), + Err(_) => std::ptr::null_mut(), + } } else { - env.new_string(r#"{"bytes_sent": 0, "bytes_recv": 0}"#).unwrap().into_raw() + match env.new_string(r#"{"bytes_sent": 0, "bytes_recv": 0}"#) { + Ok(s) => s.into_raw(), + Err(_) => std::ptr::null_mut(), + } } } @@ -201,5 +210,8 @@ pub extern "system" fn Java_net_ostp_client_OstpClientSdk_getLogs( Err(_) => "[]".to_string(), }; - env.new_string(json).unwrap().into_raw() + match env.new_string(json) { + Ok(s) => s.into_raw(), + Err(_) => std::ptr::null_mut(), + } }