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.
This commit is contained in:
ospab 2026-05-17 03:31:48 +03:00
parent 8eb3fc72cb
commit 31f3fff187
7 changed files with 71 additions and 37 deletions

View File

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

View File

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

View File

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

View File

@ -26,4 +26,5 @@ tokio = { version = "1", features = ["full"] }
anyhow = "1"
ostp-client = { path = "../../ostp-client" }
portable-atomic = "1"
json_comments = "0.2"

View File

@ -206,15 +206,18 @@ async fn get_config() -> Result<String, String> {
"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<bool, String> {
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<bool, String>
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<bool, String>
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,

View File

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

View File

@ -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(),
}
}