From de48cd91a06a50b087d59545f53664c30845091d Mon Sep 17 00:00:00 2001 From: ospab Date: Tue, 9 Jun 2026 01:01:36 +0300 Subject: [PATCH] feat: implement wintun dynamic downloading, add missing driver frontend modal, fix background logging and UAC helper issues --- Cargo.lock | 95 ++ Cargo.toml | 2 +- ostp-client/Cargo.toml | 3 + ostp-client/src/bridge.rs | 2 - ostp-client/src/lib.rs | 1 + ostp-client/src/logging.rs | 84 ++ ostp-client/src/tunnel/mod.rs | 2 +- ostp-client/src/tunnel/native_handler.rs | 294 +----- ostp-client/src/tunnel/proxy.rs | 2 +- ostp-client/src/tunnel/sni_sniff.rs | 2 +- ostp-gui/src-tauri/Cargo.lock | 942 +++++++++++++++++- ostp-gui/src-tauri/Cargo.toml | 2 + ostp-gui/src-tauri/src/lib.rs | 90 +- ostp-gui/src-tauri/src/main.rs | 2 + ostp-gui/src/index.html | 23 + ostp-gui/src/main.js | 61 +- ostp-gui/src/styles.css | 205 +++- ostp-tun-helper/src/main.rs | 3 + ostp-tun/Cargo.toml | 17 + ostp-tun/src/lib.rs | 40 + ostp-tun/src/linux.rs | 106 ++ ostp-tun/src/macos.rs | 89 ++ ostp-tun/src/windows.rs | 156 +++ .../tunnel => ostp-tun/src}/windows_route.rs | 0 ostp/src/main.rs | 12 +- scripts/build.ps1 | 33 + 26 files changed, 1936 insertions(+), 332 deletions(-) create mode 100644 ostp-client/src/logging.rs create mode 100644 ostp-tun/Cargo.toml create mode 100644 ostp-tun/src/lib.rs create mode 100644 ostp-tun/src/linux.rs create mode 100644 ostp-tun/src/macos.rs create mode 100644 ostp-tun/src/windows.rs rename {ostp-client/src/tunnel => ostp-tun/src}/windows_route.rs (100%) diff --git a/Cargo.lock b/Cargo.lock index 7456004..36e7f91 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -432,6 +432,15 @@ dependencies = [ "libc", ] +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-utils" version = "0.8.21" @@ -525,6 +534,15 @@ dependencies = [ "thiserror 2.0.18", ] +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "powerfmt", +] + [[package]] name = "digest" version = "0.10.7" @@ -1331,6 +1349,12 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "num-conv" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" + [[package]] name = "num-traits" version = "0.2.19" @@ -1396,6 +1420,7 @@ dependencies = [ "libc", "netstack-smoltcp", "ostp-core", + "ostp-tun", "portable-atomic", "rand 0.8.5", "serde", @@ -1404,6 +1429,8 @@ dependencies = [ "socket2", "tokio", "tracing", + "tracing-appender", + "tracing-subscriber", "tun", "webpki-roots 0.26.11", "winapi", @@ -1476,6 +1503,18 @@ dependencies = [ "x25519-dalek", ] +[[package]] +name = "ostp-tun" +version = "0.2.86" +dependencies = [ + "anyhow", + "libc", + "tokio", + "tracing", + "tun", + "winapi", +] + [[package]] name = "ostp-tun-helper" version = "0.2.86" @@ -1557,6 +1596,12 @@ dependencies = [ "zerovec", ] +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" version = "0.2.21" @@ -2119,6 +2164,12 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +[[package]] +name = "symlink" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7973cce6668464ea31f176d85b13c7ab3bba2cb3b77a2ed26abd7801688010a" + [[package]] name = "syn" version = "2.0.117" @@ -2199,6 +2250,37 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "time" +version = "0.3.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" + +[[package]] +name = "time-macros" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +dependencies = [ + "num-conv", + "time-core", +] + [[package]] name = "tinystr" version = "0.8.3" @@ -2341,6 +2423,19 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "tracing-appender" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "050686193eb999b4bb3bc2acfa891a13da00f79734704c4b8b4ef1a10b368a3c" +dependencies = [ + "crossbeam-channel", + "symlink", + "thiserror 2.0.18", + "time", + "tracing-subscriber", +] + [[package]] name = "tracing-attributes" version = "0.1.31" diff --git a/Cargo.toml b/Cargo.toml index c1e3c69..5ca7850 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ members = [ "ostp-server", "ostp-jni", "ostp", "ostp-tun-helper" -] +, "ostp-tun"] exclude = ["ostp-gui/src-tauri", "ostp-brain", "ostp-prober"] resolver = "2" diff --git a/ostp-client/Cargo.toml b/ostp-client/Cargo.toml index 64bbfba..632c30b 100644 --- a/ostp-client/Cargo.toml +++ b/ostp-client/Cargo.toml @@ -9,7 +9,10 @@ anyhow.workspace = true bytes.workspace = true tokio.workspace = true tracing.workspace = true +tracing-subscriber = { version = "0.3", features = ["env-filter"] } +tracing-appender = "0.2" ostp-core = { path = "../ostp-core" } +ostp-tun = { path = "../ostp-tun" } rand.workspace = true serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" diff --git a/ostp-client/src/bridge.rs b/ostp-client/src/bridge.rs index 608e8f3..3528ff6 100644 --- a/ostp-client/src/bridge.rs +++ b/ostp-client/src/bridge.rs @@ -153,8 +153,6 @@ impl Bridge { self.running = false; self.metrics.connection_state.store(0, Ordering::Relaxed); proxy_guard = None; - sessions_opt = None; - udp_rx_opt = None; stream_map.clear(); self.reset_proxy_streams(&tx, &proxy_tx, "manual stop"); break; diff --git a/ostp-client/src/lib.rs b/ostp-client/src/lib.rs index efd5eb5..474672a 100644 --- a/ostp-client/src/lib.rs +++ b/ostp-client/src/lib.rs @@ -8,3 +8,4 @@ pub mod tunnel; pub mod runner; +pub mod logging; diff --git a/ostp-client/src/logging.rs b/ostp-client/src/logging.rs new file mode 100644 index 0000000..4380c5d --- /dev/null +++ b/ostp-client/src/logging.rs @@ -0,0 +1,84 @@ +use std::fs::OpenOptions; +use std::io::Write; +use std::path::PathBuf; +use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter}; + +pub fn setup_panic_hook() { + std::panic::set_hook(Box::new(|info| { + let payload = info.payload(); + let msg = if let Some(s) = payload.downcast_ref::<&str>() { + *s + } else if let Some(s) = payload.downcast_ref::() { + s.as_str() + } else { + "Box" + }; + + let location = info.location().unwrap_or_else(|| std::panic::Location::caller()); + let backtrace = std::backtrace::Backtrace::force_capture(); + + let crash_msg = format!( + "[{}] PANIC at {}:{}\nMessage: {}\nBacktrace:\n{:?}", + chrono::Local::now().format("%Y-%m-%d %H:%M:%S"), + location.file(), + location.line(), + msg, + backtrace + ); + + eprintln!("{}", crash_msg); + + let path = std::env::current_exe() + .ok() + .and_then(|p| p.parent().map(|d| d.join("ostp-crash.log"))) + .unwrap_or_else(|| PathBuf::from("ostp-crash.log")); + + if let Ok(mut file) = OpenOptions::new().create(true).append(true).open(path) { + let _ = file.write_all(crash_msg.as_bytes()); + let _ = file.write_all(b"\n===================================================\n"); + } + })); +} + +pub fn init_tracing(level: &str, app_name: &str, version: &str) -> Option { + let env_filter = EnvFilter::try_from_default_env() + .unwrap_or_else(|_| EnvFilter::new(level)); + + let path = std::env::current_exe() + .ok() + .and_then(|p| p.parent().map(|d| d.join(format!("{}.log", app_name)))) + .unwrap_or_else(|| PathBuf::from(format!("{}.log", app_name))); + + if let Ok(file) = OpenOptions::new().create(true).append(true).open(path) { + let (file_writer, guard) = tracing_appender::non_blocking(file); + + let fmt_layer = tracing_subscriber::fmt::layer() + .with_target(true) + .with_thread_ids(false) + .with_thread_names(false) + .with_ansi(false) + .with_writer(file_writer); + + let stderr_layer = tracing_subscriber::fmt::layer() + .with_target(false) + .with_writer(std::io::stderr); + + let _ = tracing_subscriber::registry() + .with(env_filter) + .with(fmt_layer) + .with(stderr_layer) + .try_init(); + + tracing::info!( + "{} v{} | OS: {} | Arch: {}", + app_name, + version, + std::env::consts::OS, + std::env::consts::ARCH + ); + + Some(guard) + } else { + None + } +} diff --git a/ostp-client/src/tunnel/mod.rs b/ostp-client/src/tunnel/mod.rs index 6d98001..a6e3209 100644 --- a/ostp-client/src/tunnel/mod.rs +++ b/ostp-client/src/tunnel/mod.rs @@ -1,6 +1,6 @@ mod proxy; pub mod native_handler; -pub mod windows_route; + mod udp_nat; pub async fn run_tun_tunnel( diff --git a/ostp-client/src/tunnel/native_handler.rs b/ostp-client/src/tunnel/native_handler.rs index 82dd592..19381e4 100644 --- a/ostp-client/src/tunnel/native_handler.rs +++ b/ostp-client/src/tunnel/native_handler.rs @@ -12,14 +12,10 @@ pub async fn run_native_tunnel( mut exclusions_rx: watch::Receiver, ) -> Result<()> { use std::net::ToSocketAddrs; - use std::process::Command; use netstack_smoltcp::StackBuilder; use tokio::io::{AsyncReadExt, AsyncWriteExt}; use futures::{StreamExt, SinkExt}; - #[cfg(target_os = "windows")] - use std::os::windows::process::CommandExt; - #[cfg(target_os = "linux")] { use std::io::{self, IsTerminal, Write}; @@ -56,217 +52,56 @@ pub async fn run_native_tunnel( #[allow(unused_variables)] let server_ip_str = server_ip.to_string(); - // ── 2. Windows: grab physical gateway BEFORE we touch any routes ────────── - #[cfg(target_os = "windows")] - let (phys_gw, phys_if) = super::windows_route::sys::get_default_ipv4_route() - .ok_or_else(|| anyhow!("Cannot find physical default IPv4 route"))?; + // ── 2. Resolve excluded domains → IP addresses for bypass routing ───────── + let mut bypass_ips: Vec = Vec::new(); - // ── 3. Resolve excluded domains → IPv4 addresses for bypass routing ─────── - // - // Strategy identical to sing-box / v2rayN: - // • IP exclusions → add /32 host routes via physical gateway right now - // • Domain exclusions → resolve them NOW, add /32 routes for the IPs - // • Process exclusions → NOT possible via pure routing on Windows without - // WFP; we log a warning and skip them at the routing level - #[cfg(target_os = "windows")] - // Will be populated after TUN is up; tracks /32 routes added for cleanup. - let bypass_routes: Vec<(std::net::Ipv4Addr, std::net::Ipv4Addr, u32)>; + // Server IP always bypasses TUN + bypass_ips.push(server_ip); - #[cfg(target_os = "windows")] - { - // Collect all IPs to bypass: server IP + configured IPs + resolved domains - let mut bypass_v4: Vec = Vec::new(); - - // Server IP always bypasses TUN - if let std::net::IpAddr::V4(v4) = server_ip { - bypass_v4.push(v4); + for ip_str in &config.exclusions.ips { + let host = ip_str.split('/').next().unwrap_or(ip_str); + if let Ok(ip) = host.parse() { + bypass_ips.push(ip); } + } - // Explicitly configured IPs / CIDRs - for ip_str in &config.exclusions.ips { - // Accept single IPs ("1.2.3.4") or CIDR ("1.2.3.0/24") - let host = ip_str.split('/').next().unwrap_or(ip_str); - if let Ok(std::net::IpAddr::V4(v4)) = host.parse() { - bypass_v4.push(v4); - } - } - - // Resolve configured excluded domains (best-effort, DNS at startup). - // Use (host, port) tuple so lookup_host does NOT borrow a temporary string. - for domain in &config.exclusions.domains { - match tokio::net::lookup_host((domain.as_str(), 443u16)).await { - Ok(addrs) => { - for addr in addrs { - if let std::net::IpAddr::V4(v4) = addr.ip() { - bypass_v4.push(v4); - } - } - } - Err(e) => { - tracing::warn!("Failed to pre-resolve excluded domain {domain}: {e}"); + for domain in &config.exclusions.domains { + match tokio::net::lookup_host((domain.as_str(), 443u16)).await { + Ok(addrs) => { + for addr in addrs { + bypass_ips.push(addr.ip()); } } + Err(e) => { + tracing::warn!("Failed to pre-resolve excluded domain {domain}: {e}"); + } } + } - if !config.exclusions.processes.is_empty() { - tracing::warn!( - "Process-based split tunneling is not supported in TUN mode on Windows \ - without WFP. Processes in the exclusion list will still be tunneled. \ - Use IP or domain exclusions instead." - ); - } - - // Add /32 bypass routes via physical gateway BEFORE setting up TUN default route - bypass_routes = super::windows_route::sys::add_bypass_routes(&bypass_v4, phys_gw, phys_if, 1); - tracing::info!( - "Added {} bypass routes via {} (if_index={})", - bypass_routes.len(), - phys_gw, - phys_if + if !config.exclusions.processes.is_empty() { + tracing::warn!( + "Process-based split tunneling is not fully supported in TUN mode on all platforms \ + without WFP/eBPF. Processes in the exclusion list will still be tunneled. \ + Use IP or domain exclusions instead." ); } - // ── 4. Create TUN device ────────────────────────────────────────────────── - let mut tun_cfg = tun::Configuration::default(); - tun_cfg - .tun_name("ostp_tun") - .address((10, 1, 0, 2)) - .netmask((255, 255, 255, 0)) - .destination((10, 1, 0, 1)) - .mtu(config.ostp.mtu as u16) - .up(); + // ── 3. Create TUN device via ostp-tun crate ─────────────────────────────── + let opts = ostp_tun::OstpTunOptions { + server_ip, + bypass_ips, + dns_server: config.dns_server.clone(), + kill_switch: config.kill_switch, + mtu: config.ostp.mtu as u16, + wintun_path: None, + }; - #[cfg(target_os = "linux")] - tun_cfg.platform_config(|cfg| { - cfg.packet_information(false); - }); - - let dev = tun::create(&tun_cfg).map_err(|e| anyhow!("Failed to create TUN device: {}", e))?; - let dev = tun::AsyncDevice::new(dev).map_err(|e| anyhow!("TUN device async failed: {}", e))?; - tracing::info!("TUN device 'ostp_tun' created."); - - // ── 5. Windows: set default route through TUN + miscellaneous setup ─────── - #[cfg(target_os = "windows")] - { - const CREATE_NO_WINDOW: u32 = 0x08000000; - let current_exe = std::env::current_exe()?.to_string_lossy().into_owned(); - - // Wait for ostp_tun to be visible in the routing table - let mut tun_index = None; - for _ in 0..20 { - if let Some(idx) = super::windows_route::sys::get_interface_index("ostp_tun") { - tun_index = Some(idx); - break; - } - tokio::time::sleep(std::time::Duration::from_millis(100)).await; - } - - if let Some(idx) = tun_index { - // Default route through TUN with metric=5 — higher than bypass routes (metric=1) - // so that non-excluded traffic is captured but excluded IPs go via real NIC. - let _ = super::windows_route::sys::add_ipv4_route( - std::net::Ipv4Addr::new(0, 0, 0, 0), - std::net::Ipv4Addr::new(0, 0, 0, 0), - std::net::Ipv4Addr::new(10, 1, 0, 1), - idx, - 5, - ); - tracing::info!("Default route via TUN (if_index={idx}, metric=5) added."); - } else { - tracing::warn!("Could not find ostp_tun index in routing table — traffic may not be captured."); - } - - let exe1 = current_exe.clone(); - let exe2 = current_exe.clone(); - let _ = tokio::task::spawn_blocking(move || { - // Firewall allow-rules for OSTP binary - let _ = Command::new("netsh") - .creation_flags(CREATE_NO_WINDOW) - .args(["advfirewall", "firewall", "add", "rule", - "name=OSTP Tunnel In", "dir=in", "action=allow", - &format!("program={}", exe1)]) - .output(); - let _ = Command::new("netsh") - .creation_flags(CREATE_NO_WINDOW) - .args(["advfirewall", "firewall", "add", "rule", - "name=OSTP Tunnel Out", "dir=out", "action=allow", - &format!("program={}", exe2)]) - .output(); - // Disable DAD / Router Discovery to avoid 15s delay - let _ = Command::new("netsh") - .creation_flags(CREATE_NO_WINDOW) - .args(["interface", "ipv4", "set", "interface", "name=ostp_tun", - "routerdiscovery=disabled", "dadtransmits=0", - "managedaddress=disabled", "otherstateful=disabled"]) - .output(); - }); - - if let Some(ref dns) = config.dns_server { - if !dns.is_empty() { - let dns_clone = dns.clone(); - let _ = tokio::task::spawn_blocking(move || { - let _ = Command::new("netsh") - .creation_flags(CREATE_NO_WINDOW) - .args(["interface", "ipv4", "set", "dnsservers", - "name=ostp_tun", "static", &dns_clone, "primary"]) - .output(); - }); - } - } - - if config.kill_switch { - tracing::info!("Kill Switch enabled: Adding metric 10 blackhole route to prevent leakage"); - let _ = tokio::task::spawn_blocking(move || { - let _ = Command::new("route") - .creation_flags(CREATE_NO_WINDOW) - .args(["add", "0.0.0.0", "mask", "0.0.0.0", "127.0.0.1", "metric", "10", "if", "1"]) - .output(); - }); - } - } - - // ── 6. Linux: exclusion routes via real gateway ─────────────────────────── - #[cfg(target_os = "linux")] - { - let gw_out = Command::new("ip") - .args(["route", "show", "default"]) - .output() - .ok() - .and_then(|o| String::from_utf8(o.stdout).ok()); - - let real_gw = gw_out.as_deref().and_then(|s| { - s.split_whitespace() - .skip_while(|w| *w != "via") - .nth(1) - .map(|s| s.to_string()) - }); - let real_dev = gw_out.as_deref().and_then(|s| { - s.split_whitespace() - .skip_while(|w| *w != "dev") - .nth(1) - .map(|s| s.to_string()) - }); - - if let (Some(ref gw), Some(ref dev)) = (&real_gw, &real_dev) { - // Server IP bypass - let _ = Command::new("ip") - .args(["route", "add", &format!("{}/32", server_ip_str), "via", gw, "dev", dev]) - .output(); - // Configured IP exclusions - for ip_str in &config.exclusions.ips { - let host = ip_str.split('/').next().unwrap_or(ip_str); - let route = if ip_str.contains('/') { ip_str.as_str() } else { &format!("{}/32", host) }; - let _ = Command::new("ip") - .args(["route", "add", route, "via", gw, "dev", dev]) - .output(); - } - } - - // Default route through TUN - let _ = Command::new("ip") - .args(["route", "add", "default", "via", "10.1.0.1", "dev", "ostp_tun", "metric", "10"]) - .output(); - } + let tun_interface = ostp_tun::OstpTunInterface::create(opts) + .await + .map_err(|e| anyhow!("Failed to create OstpTunInterface: {}", e))?; + + let dev = tun_interface.device; + let _route_guard = tun_interface.guard; // ── 7. Build smoltcp network stack ──────────────────────────────────────── let (stack, tcp_runner, udp_socket, tcp_listener) = StackBuilder::default() @@ -371,7 +206,7 @@ pub async fn run_native_tunnel( // Physical interface index — Some on Windows, None everywhere else #[cfg(target_os = "windows")] - let phys_if_for_bypass: Option = Some(phys_if); + let phys_if_for_bypass: Option = ostp_tun::windows::windows_route::sys::get_default_ipv4_route().map(|(_, idx)| idx); #[cfg(not(target_os = "windows"))] let phys_if_for_bypass: Option = None; @@ -447,6 +282,7 @@ pub async fn run_native_tunnel( let Ok(socket) = socket else { return; }; // Bind to physical interface so packets don't loop back into TUN + #[cfg(target_os = "windows")] if let Some(idx) = phys_if_for_bypass { if let Err(e) = crate::tunnel::proxy::bind_socket_to_interface( @@ -537,54 +373,7 @@ pub async fn run_native_tunnel( tracing::info!("Deactivating NATIVE TUN tunnel..."); // ── Cleanup ─────────────────────────────────────────────────────────────── - #[cfg(target_os = "windows")] - { - const CREATE_NO_WINDOW: u32 = 0x08000000; - - // Remove all bypass /32 host routes we added - super::windows_route::sys::remove_bypass_routes(&bypass_routes); - tracing::info!("Removed {} bypass routes.", bypass_routes.len()); - - let is_kill_switch = config.kill_switch; - let _ = tokio::task::spawn_blocking(move || { - let _ = Command::new("netsh") - .creation_flags(CREATE_NO_WINDOW) - .args(["advfirewall", "firewall", "delete", "rule", "name=OSTP Tunnel In"]) - .output(); - let _ = Command::new("netsh") - .creation_flags(CREATE_NO_WINDOW) - .args(["advfirewall", "firewall", "delete", "rule", "name=OSTP Tunnel Out"]) - .output(); - let _ = Command::new("netsh") - .creation_flags(CREATE_NO_WINDOW) - .args(["interface", "ipv4", "set", "dnsservers", - "name=ostp_tun", "source=dhcp"]) - .output(); - if is_kill_switch { - let _ = Command::new("route") - .creation_flags(CREATE_NO_WINDOW) - .args(["delete", "0.0.0.0", "mask", "0.0.0.0", "127.0.0.1"]) - .output(); - } - }); - } - - #[cfg(target_os = "linux")] - { - let _ = Command::new("ip").args(["route", "del", "default", "dev", "ostp_tun"]).output(); - let _ = Command::new("ip") - .args(["route", "del", &format!("{}/32", server_ip_str)]) - .output(); - for ip_str in &config.exclusions.ips { - let host = ip_str.split('/').next().unwrap_or(ip_str); - let route = if ip_str.contains('/') { - ip_str.as_str().to_string() - } else { - format!("{}/32", host) - }; - let _ = Command::new("ip").args(["route", "del", &route]).output(); - } - } + // Cleanup is handled automatically by the _route_guard Drop trait in ostp-tun Ok(()) } @@ -597,6 +386,7 @@ pub async fn run_native_tunnel( pub async fn run_native_tunnel( _config: crate::config::ClientConfig, _shutdown: watch::Receiver, + _exclusions_rx: watch::Receiver, ) -> Result<()> { Err(anyhow!("Native TUN tunnel is only supported on Windows/Linux")) } diff --git a/ostp-client/src/tunnel/proxy.rs b/ostp-client/src/tunnel/proxy.rs index 09eaa73..7d3e4ab 100644 --- a/ostp-client/src/tunnel/proxy.rs +++ b/ostp-client/src/tunnel/proxy.rs @@ -86,7 +86,7 @@ pub fn bind_socket_to_interface(socket: &impl AsRawFd, if_name: &str) -> std::io pub fn get_windows_physical_if_index() -> Option { #[cfg(target_os = "windows")] { - return super::windows_route::sys::get_default_ipv4_route().map(|(_, idx)| idx); + return ostp_tun::windows::windows_route::sys::get_default_ipv4_route().map(|(_, idx)| idx); } #[cfg(not(target_os = "windows"))] { diff --git a/ostp-client/src/tunnel/sni_sniff.rs b/ostp-client/src/tunnel/sni_sniff.rs index 49eac15..4e1d49b 100644 --- a/ostp-client/src/tunnel/sni_sniff.rs +++ b/ostp-client/src/tunnel/sni_sniff.rs @@ -52,7 +52,7 @@ pub fn extract_sni(data: &[u8]) -> Option { if ext_type == 0x0000 { // Server Name Indication (SNI) if pos + 5 <= extensions_end { - let list_len = ((data[pos] as usize) << 8) | (data[pos + 1] as usize); + let _list_len = ((data[pos] as usize) << 8) | (data[pos + 1] as usize); let name_type = data[pos + 2]; if name_type == 0 { // Hostname let name_len = ((data[pos + 3] as usize) << 8) | (data[pos + 4] as usize); diff --git a/ostp-gui/src-tauri/Cargo.lock b/ostp-gui/src-tauri/Cargo.lock index 914abec..8d10cf9 100644 --- a/ostp-gui/src-tauri/Cargo.lock +++ b/ostp-gui/src-tauri/Cargo.lock @@ -14,7 +14,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" dependencies = [ - "crypto-common", + "crypto-common 0.1.7", "generic-array", ] @@ -25,8 +25,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" dependencies = [ "cfg-if", - "cipher", - "cpufeatures", + "cipher 0.4.4", + "cpufeatures 0.2.17", +] + +[[package]] +name = "aes" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1fc76eaeac4c9164506c466d4ffdd8ec9d0c5bf57ee97177c4d8eceb3a0e138" +dependencies = [ + "cipher 0.5.2", + "cpubits", + "cpufeatures 0.3.0", ] [[package]] @@ -36,8 +47,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" dependencies = [ "aead", - "aes", - "cipher", + "aes 0.8.4", + "cipher 0.4.4", "ctr", "ghash", "subtle", @@ -254,6 +265,28 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "aws-lc-rs" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ec2f1fc3ec205783a5da9a7e6c1509cc69dedf09a1949e412c1e18469326d00" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a2f9779ce85b93ab6170dd940ad0169b5766ff848247aff13bb788b832fe3f4" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", +] + [[package]] name = "base64" version = "0.21.7" @@ -302,7 +335,7 @@ version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" dependencies = [ - "digest", + "digest 0.10.7", ] [[package]] @@ -314,6 +347,16 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-buffer" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdd35008169921d80bc60d3d0ab416eecb028c4cd653352907921d95084790be" +dependencies = [ + "hybrid-array", + "zeroize", +] + [[package]] name = "block2" version = "0.6.2" @@ -393,6 +436,15 @@ dependencies = [ "serde", ] +[[package]] +name = "bzip2" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3a53fac24f34a81bc9954b5d6cfce0c21e18ec6959f44f56e8e90e4bb7c346c" +dependencies = [ + "libbz2-rs-sys", +] + [[package]] name = "cairo-rs" version = "0.18.5" @@ -467,6 +519,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1dce859f0832a7d088c4f1119888ab94ef4b5d6795d1ce05afb7fe159d79f98" dependencies = [ "find-msvc-tools", + "jobserver", + "libc", "shlex", ] @@ -516,8 +570,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" dependencies = [ "cfg-if", - "cipher", - "cpufeatures", + "cipher 0.4.4", + "cpufeatures 0.2.17", ] [[package]] @@ -528,7 +582,7 @@ checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" dependencies = [ "aead", "chacha20", - "cipher", + "cipher 0.4.4", "poly1305", "zeroize", ] @@ -553,11 +607,36 @@ version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ - "crypto-common", - "inout", + "crypto-common 0.1.7", + "inout 0.1.4", "zeroize", ] +[[package]] +name = "cipher" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8cf2a2c93cd704877c0858356ed03480ff301ee950b43f1cbe4573b088bfa6c" +dependencies = [ + "crypto-common 0.2.2", + "inout 0.2.2", +] + +[[package]] +name = "cmake" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" +dependencies = [ + "cc", +] + +[[package]] +name = "cmov" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a" + [[package]] name = "combine" version = "4.6.7" @@ -577,6 +656,18 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "const-oid" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" + +[[package]] +name = "constant_time_eq" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" + [[package]] name = "cookie" version = "0.18.1" @@ -587,6 +678,16 @@ dependencies = [ "version_check", ] +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation" version = "0.10.1" @@ -610,7 +711,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "064badf302c3194842cf2c5d61f56cc88e54a759313879cdf03abdd27d0c3b97" dependencies = [ "bitflags 2.11.1", - "core-foundation", + "core-foundation 0.10.1", "core-graphics-types", "foreign-types", "libc", @@ -623,10 +724,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" dependencies = [ "bitflags 2.11.1", - "core-foundation", + "core-foundation 0.10.1", "libc", ] +[[package]] +name = "cpubits" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15b85f9c39137c3a891689859392b1bd49812121d0d61c9caf00d46ed5ce06ae" + [[package]] name = "cpufeatures" version = "0.2.17" @@ -636,6 +743,15 @@ dependencies = [ "libc", ] +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + [[package]] name = "crc32fast" version = "1.5.0" @@ -667,10 +783,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ "generic-array", - "rand_core", + "rand_core 0.6.4", "typenum", ] +[[package]] +name = "crypto-common" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" +dependencies = [ + "hybrid-array", +] + [[package]] name = "cssparser" version = "0.36.0" @@ -716,7 +841,16 @@ version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" dependencies = [ - "cipher", + "cipher 0.4.4", +] + +[[package]] +name = "ctutils" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e" +dependencies = [ + "cmov", ] [[package]] @@ -726,7 +860,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "curve25519-dalek-derive", "fiat-crypto", "rustc_version", @@ -790,6 +924,12 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "deflate64" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac6b926516df9c60bfa16e107b21086399f8285a44ca9711344b9e553c5146e2" + [[package]] name = "defmt" version = "0.3.100" @@ -868,11 +1008,24 @@ version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer", - "crypto-common", + "block-buffer 0.10.4", + "crypto-common 0.1.7", "subtle", ] +[[package]] +name = "digest" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" +dependencies = [ + "block-buffer 0.12.0", + "const-oid", + "crypto-common 0.2.2", + "ctutils", + "zeroize", +] + [[package]] name = "dirs" version = "6.0.0" @@ -1026,6 +1179,15 @@ version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7" +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + [[package]] name = "endi" version = "1.1.1" @@ -1155,6 +1317,7 @@ checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" dependencies = [ "crc32fast", "miniz_oxide", + "zlib-rs", ] [[package]] @@ -1211,6 +1374,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + [[package]] name = "futures" version = "0.3.32" @@ -1428,8 +1597,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi", + "wasm-bindgen", ] [[package]] @@ -1439,9 +1610,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", + "js-sys", "libc", "r-efi 5.3.0", "wasip2", + "wasm-bindgen", ] [[package]] @@ -1451,10 +1624,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" dependencies = [ "cfg-if", + "js-sys", "libc", "r-efi 6.0.0", "wasip2", "wasip3", + "wasm-bindgen", ] [[package]] @@ -1615,6 +1790,25 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "h2" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "171fefbc92fe4a4de27e0698d6a5b392d6a0e333506bc49133760b3bcf948733" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap 2.14.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "hash32" version = "0.3.1" @@ -1685,7 +1879,7 @@ version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" dependencies = [ - "hmac", + "hmac 0.12.1", ] [[package]] @@ -1694,7 +1888,16 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest", + "digest 0.10.7", +] + +[[package]] +name = "hmac" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6303bc9732ae41b04cb554b844a762b4115a61bfaa81e3e83050991eeb56863f" +dependencies = [ + "digest 0.11.3", ] [[package]] @@ -1746,6 +1949,15 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" +[[package]] +name = "hybrid-array" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9155a582abd142abc056962c29e3ce5ff2ad5469f4246b537ed42c5deba857da" +dependencies = [ + "typenum", +] + [[package]] name = "hyper" version = "1.9.0" @@ -1756,6 +1968,7 @@ dependencies = [ "bytes", "futures-channel", "futures-core", + "h2", "http", "http-body", "httparse", @@ -1766,6 +1979,21 @@ dependencies = [ "want", ] +[[package]] +name = "hyper-rustls" +version = "0.27.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "tokio", + "tokio-rustls", + "tower-service", +] + [[package]] name = "hyper-util" version = "0.1.20" @@ -1784,9 +2012,11 @@ dependencies = [ "percent-encoding", "pin-project-lite", "socket2", + "system-configuration", "tokio", "tower-service", "tracing", + "windows-registry", ] [[package]] @@ -1979,6 +2209,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "inout" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4250ce6452e92010fdf7268ccc5d14faa80bb12fc741938534c58f16804e03c7" +dependencies = [ + "hybrid-array", +] + [[package]] name = "ipnet" version = "2.12.0" @@ -2049,6 +2288,36 @@ dependencies = [ "windows-sys 0.45.0", ] +[[package]] +name = "jni" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" +dependencies = [ + "cfg-if", + "combine", + "jni-macros", + "jni-sys 0.4.1", + "log", + "simd_cesu8", + "thiserror 2.0.18", + "walkdir", + "windows-link 0.2.1", +] + +[[package]] +name = "jni-macros" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "simd_cesu8", + "syn 2.0.117", +] + [[package]] name = "jni-sys" version = "0.3.1" @@ -2077,6 +2346,16 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] + [[package]] name = "js-sys" version = "0.3.98" @@ -2128,6 +2407,12 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + [[package]] name = "leb128fmt" version = "0.1.0" @@ -2158,6 +2443,12 @@ dependencies = [ "once_cell", ] +[[package]] +name = "libbz2-rs-sys" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34b357333733e8260735ba5894eb928c02ecc69c78715f01a8019e7fa7f2db4c" + [[package]] name = "libc" version = "0.2.186" @@ -2229,6 +2520,21 @@ version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + +[[package]] +name = "lzma-rust2" +version = "0.16.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce716bf1a316f47a280fc76295f6495b5bea4752bca01c3b3885e101b1c23c02" +dependencies = [ + "sha2 0.11.0", +] + [[package]] name = "managed" version = "0.8.0" @@ -2246,6 +2552,15 @@ dependencies = [ "web_atoms", ] +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + [[package]] name = "memchr" version = "2.8.0" @@ -2341,7 +2656,7 @@ checksum = "398691cef792b89eb5d29e6ea6b3999def706b908d355e29815ba8101cf5c4c8" dependencies = [ "etherparse", "futures", - "rand", + "rand 0.8.6", "smoltcp", "spin", "tokio", @@ -2367,6 +2682,15 @@ dependencies = [ "libc", ] +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "num-conv" version = "0.2.1" @@ -2623,6 +2947,12 @@ dependencies = [ "pathdiff", ] +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + [[package]] name = "option-ext" version = "0.2.0" @@ -2641,7 +2971,7 @@ dependencies = [ [[package]] name = "ostp-client" -version = "0.2.83" +version = "0.2.86" dependencies = [ "anyhow", "base64 0.22.1", @@ -2651,19 +2981,22 @@ dependencies = [ "futures", "futures-util", "hex", - "hmac", + "hmac 0.12.1", "json_comments", "libc", "netstack-smoltcp", "ostp-core", + "ostp-tun", "portable-atomic", - "rand", + "rand 0.8.6", "serde", "serde_json", - "sha2", + "sha2 0.10.9", "socket2", "tokio", "tracing", + "tracing-appender", + "tracing-subscriber", "tun", "webpki-roots 0.26.11", "winapi", @@ -2672,15 +3005,15 @@ dependencies = [ [[package]] name = "ostp-core" -version = "0.2.83" +version = "0.2.86" dependencies = [ "anyhow", "bytes", "chacha20poly1305", "hkdf", - "hmac", - "rand", - "sha2", + "hmac 0.12.1", + "rand 0.8.6", + "sha2 0.10.9", "snow", "thiserror 1.0.69", "tracing", @@ -2695,13 +3028,27 @@ dependencies = [ "json_comments", "ostp-client", "portable-atomic", - "rand", + "rand 0.8.6", + "reqwest", "serde", "serde_json", "tauri", "tauri-build", "tauri-plugin-opener", "tokio", + "zip", +] + +[[package]] +name = "ostp-tun" +version = "0.2.86" +dependencies = [ + "anyhow", + "libc", + "tokio", + "tracing", + "tun", + "winapi", ] [[package]] @@ -2764,6 +3111,16 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" +[[package]] +name = "pbkdf2" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112d82ceb8c5bf524d9af484d4e4970c9fd5a0cc15ba14ad93dccd28873b0629" +dependencies = [ + "digest 0.11.3", + "hmac 0.13.0", +] + [[package]] name = "percent-encoding" version = "2.3.2" @@ -2905,7 +3262,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" dependencies = [ - "cpufeatures", + "cpufeatures 0.2.17", "opaque-debug", "universal-hash", ] @@ -2917,7 +3274,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "opaque-debug", "universal-hash", ] @@ -2943,6 +3300,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" +[[package]] +name = "ppmd-rust" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efca4c95a19a79d1c98f791f10aebd5c1363b473244630bb7dbde1dc98455a24" + [[package]] name = "ppv-lite86" version = "0.2.21" @@ -3061,6 +3424,62 @@ dependencies = [ "memchr", ] +[[package]] +name = "quinn" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2", + "thiserror 2.0.18", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" +dependencies = [ + "aws-lc-rs", + "bytes", + "getrandom 0.3.4", + "lru-slab", + "rand 0.9.4", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.18", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.59.0", +] + [[package]] name = "quote" version = "1.0.45" @@ -3089,8 +3508,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" dependencies = [ "libc", - "rand_chacha", - "rand_core", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.5", ] [[package]] @@ -3100,7 +3529,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", ] [[package]] @@ -3112,6 +3551,15 @@ dependencies = [ "getrandom 0.2.17", ] +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + [[package]] name = "raw-window-handle" version = "0.6.2" @@ -3189,27 +3637,37 @@ checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] name = "reqwest" -version = "0.13.3" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62e0021ea2c22aed41653bc7e1419abb2c97e038ff2c33d0e1309e49a97deec0" +checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3" dependencies = [ "base64 0.22.1", "bytes", + "encoding_rs", + "futures-channel", "futures-core", "futures-util", + "h2", "http", "http-body", "http-body-util", "hyper", + "hyper-rustls", "hyper-util", "js-sys", "log", + "mime", "percent-encoding", "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", + "rustls-platform-verifier", "serde", "serde_json", "sync_wrapper", "tokio", + "tokio-rustls", "tokio-util", "tower", "tower-http", @@ -3221,6 +3679,20 @@ dependencies = [ "web-sys", ] +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + [[package]] name = "rustc-hash" version = "2.1.2" @@ -3249,11 +3721,80 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "rustls" +version = "0.23.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b" +dependencies = [ + "aws-lc-rs", + "once_cell", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + [[package]] name = "rustls-pki-types" version = "1.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-platform-verifier" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0" +dependencies = [ + "core-foundation 0.10.1", + "core-foundation-sys", + "jni 0.22.4", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" + +[[package]] +name = "rustls-webpki" +version = "0.103.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" +dependencies = [ + "aws-lc-rs", + "ring", + "rustls-pki-types", + "untrusted", +] [[package]] name = "rustversion" @@ -3270,6 +3811,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schannel" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "schemars" version = "0.8.22" @@ -3327,6 +3877,29 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags 2.11.1", + "core-foundation 0.10.1", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "selectors" version = "0.36.1" @@ -3514,6 +4087,17 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "sha1" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "digest 0.11.3", +] + [[package]] name = "sha2" version = "0.10.9" @@ -3521,8 +4105,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", - "cpufeatures", - "digest", + "cpufeatures 0.2.17", + "digest 0.10.7", +] + +[[package]] +name = "sha2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "digest 0.11.3", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", ] [[package]] @@ -3547,6 +4151,22 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" +[[package]] +name = "simd_cesu8" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94f90157bb87cddf702797c5dadfa0be7d266cdf49e22da2fcaa32eff75b2c33" +dependencies = [ + "rustc_version", + "simdutf8", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + [[package]] name = "siphasher" version = "1.0.3" @@ -3590,9 +4210,9 @@ dependencies = [ "blake2", "chacha20poly1305", "curve25519-dalek", - "rand_core", + "rand_core 0.6.4", "rustc_version", - "sha2", + "sha2 0.10.9", "subtle", ] @@ -3716,6 +4336,12 @@ dependencies = [ "serde_json", ] +[[package]] +name = "symlink" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7973cce6668464ea31f176d85b13c7ab3bba2cb3b77a2ed26abd7801688010a" + [[package]] name = "syn" version = "1.0.109" @@ -3757,6 +4383,27 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "system-configuration" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" +dependencies = [ + "bitflags 2.11.1", + "core-foundation 0.9.4", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "system-deps" version = "6.2.2" @@ -3778,7 +4425,7 @@ checksum = "a33f7f9e486ade65fcf1e45c440f9236c904f5c1002cdc7fc6ae582777345ce4" dependencies = [ "bitflags 2.11.1", "block2", - "core-foundation", + "core-foundation 0.10.1", "core-graphics", "crossbeam-channel", "dbus", @@ -3788,7 +4435,7 @@ dependencies = [ "gdkwayland-sys", "gdkx11-sys", "gtk", - "jni", + "jni 0.21.1", "libc", "log", "ndk", @@ -3844,7 +4491,7 @@ dependencies = [ "gtk", "heck 0.5.0", "http", - "jni", + "jni 0.21.1", "libc", "log", "mime", @@ -3916,7 +4563,7 @@ dependencies = [ "semver", "serde", "serde_json", - "sha2", + "sha2 0.10.9", "syn 2.0.117", "tauri-utils", "thiserror 2.0.18", @@ -3988,7 +4635,7 @@ dependencies = [ "dpi", "gtk", "http", - "jni", + "jni 0.21.1", "objc2", "objc2-ui-kit", "objc2-web-kit", @@ -4011,7 +4658,7 @@ checksum = "b83849ee63ecb27a8e8d0fe51915ca215076914aca43f96db1179f0f415f6cd9" dependencies = [ "gtk", "http", - "jni", + "jni 0.21.1", "log", "objc2", "objc2-app-kit", @@ -4141,6 +4788,15 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + [[package]] name = "time" version = "0.3.47" @@ -4149,6 +4805,7 @@ checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" dependencies = [ "deranged", "itoa", + "js-sys", "num-conv", "powerfmt", "serde_core", @@ -4225,6 +4882,16 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + [[package]] name = "tokio-util" version = "0.7.18" @@ -4414,6 +5081,19 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "tracing-appender" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "050686193eb999b4bb3bc2acfa891a13da00f79734704c4b8b4ef1a10b368a3c" +dependencies = [ + "crossbeam-channel", + "symlink", + "thiserror 2.0.18", + "time", + "tracing-subscriber", +] + [[package]] name = "tracing-attributes" version = "0.1.31" @@ -4432,6 +5112,36 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", ] [[package]] @@ -4483,6 +5193,12 @@ dependencies = [ "wintun-bindings", ] +[[package]] +name = "typed-path" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e28f89b80c87b8fb0cf04ab448d5dd0dd0ade2f8891bae878de66a75a28600e" + [[package]] name = "typeid" version = "1.0.3" @@ -4571,10 +5287,16 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" dependencies = [ - "crypto-common", + "crypto-common 0.1.7", "subtle", ] +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "url" version = "2.5.8" @@ -4624,6 +5346,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + [[package]] name = "version-compare" version = "0.2.1" @@ -4811,6 +5539,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "web_atoms" version = "0.2.4" @@ -4867,6 +5605,15 @@ dependencies = [ "system-deps", ] +[[package]] +name = "webpki-root-certs" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31141ce3fc3e300ae89b78c0dd67f9708061d1d2eda54b8209346fd6be9a92c" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "webpki-roots" version = "0.26.11" @@ -5070,6 +5817,17 @@ dependencies = [ "windows-link 0.1.3", ] +[[package]] +name = "windows-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", +] + [[package]] name = "windows-result" version = "0.3.4" @@ -5115,6 +5873,15 @@ dependencies = [ "windows-targets 0.42.2", ] +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-sys" version = "0.59.0" @@ -5438,7 +6205,7 @@ dependencies = [ "gtk", "http", "javascriptcore-rs", - "jni", + "jni 0.21.1", "libc", "ndk", "objc2", @@ -5450,7 +6217,7 @@ dependencies = [ "once_cell", "percent-encoding", "raw-window-handle", - "sha2", + "sha2 0.10.9", "soup3", "tao-macros", "thiserror 2.0.18", @@ -5492,7 +6259,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" dependencies = [ "curve25519-dalek", - "rand_core", + "rand_core 0.6.4", "serde", "zeroize", ] @@ -5675,12 +6442,85 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "zip" +version = "8.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d04a6b5381502aa6087c94c669499eb1602eb9c5e8198e534de571f7154809b" +dependencies = [ + "aes 0.9.1", + "bzip2", + "constant_time_eq", + "crc32fast", + "deflate64", + "flate2", + "getrandom 0.4.2", + "hmac 0.13.0", + "indexmap 2.14.0", + "lzma-rust2", + "memchr", + "pbkdf2", + "ppmd-rust", + "sha1", + "time", + "typed-path", + "zeroize", + "zopfli", + "zstd", +] + +[[package]] +name = "zlib-rs" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3be3d40e40a133f9c916ee3f9f4fa2d9d63435b5fbe1bfc6d9dae0aa0ada1513" + [[package]] name = "zmij" version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" +[[package]] +name = "zopfli" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249" +dependencies = [ + "bumpalo", + "crc32fast", + "log", + "simd-adler32", +] + +[[package]] +name = "zstd" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.16+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" +dependencies = [ + "cc", + "pkg-config", +] + [[package]] name = "zvariant" version = "5.11.0" diff --git a/ostp-gui/src-tauri/Cargo.toml b/ostp-gui/src-tauri/Cargo.toml index 1cacbe6..a9e7458 100644 --- a/ostp-gui/src-tauri/Cargo.toml +++ b/ostp-gui/src-tauri/Cargo.toml @@ -28,4 +28,6 @@ ostp-client = { path = "../../ostp-client" } portable-atomic = "1" json_comments = "0.2" rand = "0.8" +reqwest = { version = "0.13.4", features = ["blocking"] } +zip = "8.6.0" diff --git a/ostp-gui/src-tauri/src/lib.rs b/ostp-gui/src-tauri/src/lib.rs index badaa76..b2ea628 100644 --- a/ostp-gui/src-tauri/src/lib.rs +++ b/ostp-gui/src-tauri/src/lib.rs @@ -6,6 +6,7 @@ use serde::{Deserialize, Serialize}; use anyhow::Result; use ostp_client::bridge::BridgeMetrics; use portable_atomic::Ordering; +use tauri::Emitter; // ── Config types ───────────────────────────────────────────────────────────── @@ -193,6 +194,50 @@ fn map_to_client_config(raw: &ClientConfigRaw, mode: &str) -> ostp_client::confi // ── Tauri commands ──────────────────────────────────────────────────────────── +#[tauri::command] +async fn download_wintun() -> Result { + tokio::task::spawn_blocking(move || { + let response = reqwest::blocking::get("https://www.wintun.net/builds/wintun-0.14.1.zip") + .map_err(|e| format!("Failed to download wintun.zip: {}", e))?; + let bytes = response.bytes().map_err(|e| format!("Failed to read bytes: {}", e))?; + let cursor = std::io::Cursor::new(bytes); + let mut zip = zip::ZipArchive::new(cursor).map_err(|e| format!("Invalid zip archive: {}", e))?; + + let arch = if cfg!(target_arch = "x86") { + "x86" + } else if cfg!(target_arch = "aarch64") { + "arm64" + } else { + "amd64" + }; + let arch_path = format!("wintun/bin/{}/wintun.dll", arch); + + let mut file = zip.by_name(&arch_path).map_err(|e| format!("wintun.dll not found in zip: {}", e))?; + + let mut paths_to_write = vec![]; + if let Ok(cwd) = std::env::current_dir() { + paths_to_write.push(cwd.join("wintun.dll")); + } + if let Some(helper) = find_helper_exe() { + if let Some(dir) = helper.parent() { + paths_to_write.push(dir.join("wintun.dll")); + } + } + + if paths_to_write.is_empty() { + return Err("Could not determine where to place wintun.dll".to_string()); + } + + let mut buf = Vec::new(); + std::io::copy(&mut file, &mut buf).map_err(|e| format!("Failed to read from zip: {}", e))?; + + for p in paths_to_write { + let _ = std::fs::write(&p, &buf); + } + Ok(true) + }).await.map_err(|e| e.to_string())? +} + #[tauri::command] async fn get_config() -> Result { let path = get_config_path(); @@ -288,7 +333,7 @@ async fn get_metrics(state: tauri::State<'_, AppState>) -> Result) -> Result { - let mut guard = state.0.lock().await; + let guard = state.0.lock().await; if guard.tunnel.is_none() { return Ok(false); } @@ -316,7 +361,7 @@ async fn reload_tunnel(state: tauri::State<'_, AppState>) -> Result { + Some(TunnelHandle::InProcess(_s)) => { // Restarting in-process tunnel is not supported without re-calling start_tunnel, // but we can just abort and we should really call start_tunnel again. // For now, return false. @@ -353,7 +398,7 @@ async fn stop_tunnel(state: tauri::State<'_, AppState>) -> Result } #[tauri::command] -async fn start_tunnel(state: tauri::State<'_, AppState>) -> Result { +async fn start_tunnel(state: tauri::State<'_, AppState>, app: tauri::AppHandle) -> Result { let mut guard = state.0.lock().await; if let Some(ref t) = guard.tunnel { @@ -378,16 +423,35 @@ async fn start_tunnel(state: tauri::State<'_, AppState>) -> Result let is_tun_enabled = client_cfg.tun.as_ref().map(|t| t.enable).unwrap_or(false); + #[cfg(target_os = "windows")] if is_tun_enabled { - start_tun_via_helper(&mut guard, &client_cfg).await + let mut found = false; + if let Ok(cwd) = std::env::current_dir() { + if cwd.join("wintun.dll").exists() { found = true; } + } + if !found { + if let Some(helper) = find_helper_exe() { + if let Some(dir) = helper.parent() { + if dir.join("wintun.dll").exists() { found = true; } + } + } + } + if !found { + return Err("WINTUN_MISSING".to_string()); + } + } + + if is_tun_enabled { + start_tun_via_helper(&mut guard, &client_cfg, app).await } else { - start_proxy_in_process(&mut guard, &client_cfg).await + start_proxy_in_process(&mut guard, &client_cfg, app).await } } async fn start_proxy_in_process( guard: &mut AppStateInner, raw: &ClientConfigRaw, + app: tauri::AppHandle, ) -> Result { let mapped = map_to_client_config(raw, "proxy"); let metrics = Arc::new(BridgeMetrics { @@ -404,7 +468,10 @@ async fn start_proxy_in_process( let handle = tokio::spawn(async move { match ostp_client::runner::run_client_core(mapped, metrics_clone, shutdown_rx, None).await { Ok(_) => Ok(()), - Err(e) => Err(e.to_string()), + Err(e) => { + let _ = app.emit("tunnel-error", e.to_string()); + Err(e.to_string()) + } } }); @@ -419,6 +486,7 @@ async fn start_proxy_in_process( async fn start_tun_via_helper( guard: &mut AppStateInner, raw: &ClientConfigRaw, + app: tauri::AppHandle, ) -> Result { let port = { let listener = std::net::TcpListener::bind("127.0.0.1:0").map_err(|e| format!("Bind error: {}", e))?; @@ -470,7 +538,11 @@ async fn start_tun_via_helper( match msg { HelperMsg::Status { value } => s.connection_state = value, HelperMsg::Metrics { bytes_sent, bytes_recv, rtt_ms } => { s.bytes_sent = bytes_sent; s.bytes_recv = bytes_recv; s.rtt_ms = rtt_ms; } - HelperMsg::Error { message } => { s.connection_state = 0; eprintln!("Helper error: {}", message); } + HelperMsg::Error { message } => { + s.connection_state = 0; + eprintln!("Helper error: {}", message); + let _ = app.emit("tunnel-error", message); + } _ => {} } } @@ -617,11 +689,13 @@ pub fn run() { let connect_i = MenuItem::with_id(app, "connect", "Подключиться", true, None::<&str>)?; let disconnect_i = MenuItem::with_id(app, "disconnect", "Отключиться", true, None::<&str>)?; let server_i = MenuItem::with_id(app, "server", format!("Сервер: {}", masked_ip), false, None::<&str>)?; + let version_i = MenuItem::with_id(app, "version", format!("OSTP v{}", env!("CARGO_PKG_VERSION")), false, None::<&str>)?; let show_i = MenuItem::with_id(app, "show", "Показать окно", true, None::<&str>)?; let exit_i = MenuItem::with_id(app, "exit", "Выход", true, None::<&str>)?; let menu = Menu::with_items(app, &[ &server_i, + &version_i, &connect_i, &disconnect_i, &show_i, @@ -671,7 +745,7 @@ pub fn run() { } _ => {} }) - .invoke_handler(tauri::generate_handler![start_tunnel, stop_tunnel, reload_tunnel, get_tunnel_status, get_metrics, get_config, save_config]) + .invoke_handler(tauri::generate_handler![start_tunnel, stop_tunnel, reload_tunnel, get_tunnel_status, get_metrics, get_config, save_config, download_wintun]) .run(tauri::generate_context!()) .expect("error while running tauri application"); } diff --git a/ostp-gui/src-tauri/src/main.rs b/ostp-gui/src-tauri/src/main.rs index 1c275ee..56c2b46 100644 --- a/ostp-gui/src-tauri/src/main.rs +++ b/ostp-gui/src-tauri/src/main.rs @@ -2,5 +2,7 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] fn main() { + ostp_client::logging::setup_panic_hook(); + let _log_guard = ostp_client::logging::init_tracing("info", "ostp-gui", env!("CARGO_PKG_VERSION")); ostp_gui_lib::run() } diff --git a/ostp-gui/src/index.html b/ostp-gui/src/index.html index d33ddf3..027634e 100644 --- a/ostp-gui/src/index.html +++ b/ostp-gui/src/index.html @@ -33,6 +33,17 @@ + + + + + + diff --git a/ostp-gui/src/main.js b/ostp-gui/src/main.js index 05dce35..dc9793a 100644 --- a/ostp-gui/src/main.js +++ b/ostp-gui/src/main.js @@ -59,6 +59,10 @@ const inDomains = $('in-ex-domains'); const inIps = $('in-ex-ips'); const inProcesses = $('in-ex-processes'); +const wintunModal = $('wintun-modal'); +const btnWintunCancel = $('btn-wintun-cancel'); +const btnWintunDownload = $('btn-wintun-download'); + // ── Utilities ──────────────────────────────────────────────────────────────── function fmtBytes(b) { if (!b || b === 0) return '0 B'; @@ -81,6 +85,22 @@ function splitLines(val) { return val.split('\n').map(l => l.trim()).filter(Boolean); } +// ── Theme ──────────────────────────────────────────────────────────────────── +function applyTheme(theme) { + document.documentElement.setAttribute('data-theme', theme); + localStorage.setItem('ostp-theme', theme); +} + +function toggleTheme() { + const current = document.documentElement.getAttribute('data-theme'); + applyTheme(current === 'light' ? 'dark' : 'light'); +} + +// Apply saved theme immediately (before any paint) +(function() { + const saved = localStorage.getItem('ostp-theme') || 'dark'; + document.documentElement.setAttribute('data-theme', saved); +})(); // ── Toast ──────────────────────────────────────────────────────────────────── let toastTimer = null; function showToast(msg, variant = '') { @@ -216,8 +236,12 @@ async function handleToggle() { } } catch (err) { setState('disconnected'); - showToast(String(err), 'error'); - alert(String(err)); + if (err === "WINTUN_MISSING") { + wintunModal.classList.remove('hidden'); + } else { + showToast(String(err), 'error'); + alert(String(err)); + } } } else { try { await invoke('stop_tunnel'); } catch { /* ignore */ } @@ -487,6 +511,12 @@ window.addEventListener('DOMContentLoaded', async () => { btnBack.addEventListener('click', () => showScreen('home')); btnImport.addEventListener('click', handleImport); btnPeekKey.addEventListener('click', togglePeek); + + // Theme toggle + const btnThemeToggle = $('btn-theme-toggle'); + if (btnThemeToggle) { + btnThemeToggle.addEventListener('click', toggleTheme); + } inOwndns.addEventListener('change', () => { updateDnsVisibility(); scheduleAutoSave(); @@ -511,7 +541,30 @@ window.addEventListener('DOMContentLoaded', async () => { el.addEventListener('change', scheduleAutoSave); }); - btnTestPing.addEventListener('click', async () => { + btnTestPing.addEventListener('click', runPingTest); + + btnWintunCancel.addEventListener('click', () => { + wintunModal.classList.add('hidden'); + }); + + btnWintunDownload.addEventListener('click', async () => { + try { + btnWintunDownload.disabled = true; + btnWintunDownload.textContent = "Downloading..."; + await invoke('download_wintun'); + wintunModal.classList.add('hidden'); + showToast("Wintun driver downloaded successfully!", "ok"); + handleToggle(); + } catch (err) { + showToast("Failed to download: " + err, "error"); + alert("Download failed: " + err); + } finally { + btnWintunDownload.disabled = false; + btnWintunDownload.textContent = "Download"; + } + }); + + async function runPingTest() { pingValueTxt.textContent = 'Testing...'; pingValueTxt.className = 'ping-test-value'; try { @@ -528,7 +581,7 @@ window.addEventListener('DOMContentLoaded', async () => { } catch { pingValueTxt.textContent = 'Target Ping: Error'; } - }); + } // Restore status on app open try { diff --git a/ostp-gui/src/styles.css b/ostp-gui/src/styles.css index 109830e..34d2417 100644 --- a/ostp-gui/src/styles.css +++ b/ostp-gui/src/styles.css @@ -3,7 +3,7 @@ Minimal dark with vivid accents. Glassmorphism. No frameworks. ═══════════════════════════════════════════════════════════════════════════ */ -/* ── Tokens ──────────────────────────────────────────────────────────────── */ +/* ── Tokens — Dark (default) ─────────────────────────────────────────────── */ :root { /* Colors */ --c-bg: #08080f; @@ -44,6 +44,132 @@ color: var(--c-txt-1); } +/* ── Tokens — Light theme override ────────────────────────────────────────── */ +[data-theme="light"] { + --c-bg: #f0f2fa; + --c-surface: #ffffff; + --c-card: rgba(255,255,255,0.75); + --c-card-border: rgba(0,0,0,0.08); + + --c-accent: #5b61f0; + --c-accent-2: #8b5cf6; + --c-accent-glow: rgba(91,97,240,0.25); + --c-accent-dim: rgba(91,97,240,0.10); + + --c-green: #10b981; + --c-green-glow: rgba(16,185,129,0.25); + --c-green-dim: rgba(16,185,129,0.10); + + --c-amber: #d97706; + --c-red: #ef4444; + + --c-txt-1: #0f1020; + --c-txt-2: #5a5f7a; + --c-txt-3: #b0b4cc; +} + +/* ── Light theme element overrides ───────────────────────────────────────── */ +html[data-theme="light"], +html[data-theme="light"] body { + background: var(--c-bg); +} + +html[data-theme="light"] .blob-1 { opacity: 0.08; } +html[data-theme="light"] .blob-2 { opacity: 0.06; } + +html[data-theme="light"] .power-btn { + background: var(--c-surface); + box-shadow: + 0 0 0 8px rgba(0,0,0,0.04), + 0 8px 32px rgba(0,0,0,0.12); +} + +html[data-theme="light"] .server-badge { + background: rgba(0,0,0,0.05); + border-color: rgba(0,0,0,0.10); + color: rgba(0,0,0,0.65); +} + +html[data-theme="light"] .ping-test-box { + background: rgba(0,0,0,0.04); + border-color: rgba(0,0,0,0.07); +} + +html[data-theme="light"] .ping-test-title { color: rgba(0,0,0,0.35); } +html[data-theme="light"] .ping-test-value { color: rgba(0,0,0,0.65); } + +html[data-theme="light"] .metrics-bar { + background: rgba(0,0,0,0.04); + border-top-color: rgba(0,0,0,0.08); +} + +html[data-theme="light"] .metric-sep { background: rgba(0,0,0,0.12); } +html[data-theme="light"] .metric-label { color: rgba(0,0,0,0.45); } + +html[data-theme="light"] .field-input { + background: rgba(0,0,0,0.04); + border-color: rgba(0,0,0,0.10); +} + +html[data-theme="light"] .field-input::placeholder { color: var(--c-txt-3); } + +html[data-theme="light"] .toggle-track { + background: rgba(0,0,0,0.08); + border-color: rgba(0,0,0,0.12); +} + +html[data-theme="light"] .toggle-row { + border-top-color: rgba(0,0,0,0.06); +} + +html[data-theme="light"] .section-head { + border-top-color: rgba(0,0,0,0.06); +} + +html[data-theme="light"] .card.scrollable { + scrollbar-color: rgba(0,0,0,0.12) transparent; +} +html[data-theme="light"] .card.scrollable::-webkit-scrollbar-thumb { + background: rgba(0,0,0,0.12); +} + +html[data-theme="light"] .toast { + background: rgba(255,255,255,0.95); + box-shadow: 0 8px 32px rgba(0,0,0,0.15); +} + +html[data-theme="light"] .icon-btn:hover { + border-color: rgba(0,0,0,0.14); + color: var(--c-txt-1); + background: rgba(0,0,0,0.06); +} + +/* ── Theme toggle button ─────────────────────────────────────────────────── */ +.theme-toggle-btn { + width: 34px; height: 34px; + display: flex; align-items: center; justify-content: center; + border-radius: var(--r-sm); + border: 1px solid var(--c-card-border); + background: var(--c-card); + color: var(--c-txt-2); + transition: all var(--t-fast); + backdrop-filter: blur(8px); + cursor: pointer; +} + +.theme-toggle-btn:hover { + border-color: var(--c-accent); + color: var(--c-accent); +} + +.theme-toggle-btn:active { transform: scale(0.93); } + +/* sun icon — shown in dark mode */ +.theme-toggle-btn .icon-sun { display: block; } +.theme-toggle-btn .icon-moon { display: none; } +html[data-theme="light"] .theme-toggle-btn .icon-sun { display: none; } +html[data-theme="light"] .theme-toggle-btn .icon-moon { display: block; } + /* ── Reset ────────────────────────────────────────────────────────────────── */ *, *::before, *::after { box-sizing: border-box; @@ -778,3 +904,80 @@ input, textarea { font-family: inherit; } .toast.is-error { border-color: var(--c-red); color: var(--c-red); } .toast.is-ok { border-color: var(--c-green); color: var(--c-green); } + +/* ── Modal ────────────────────────────────────────────────────────────────── */ +.modal-overlay { + position: absolute; + inset: 0; + z-index: 50; + background: rgba(0,0,0,0.5); + backdrop-filter: blur(4px); + display: flex; + align-items: center; + justify-content: center; + opacity: 1; + transition: opacity var(--t-fast); +} + +.modal-overlay.hidden { + opacity: 0; + pointer-events: none; +} + +.modal-content { + background: var(--c-surface); + border: 1px solid var(--c-card-border); + padding: 20px; + border-radius: var(--r-md); + width: 280px; + box-shadow: 0 10px 40px rgba(0,0,0,0.6); + display: flex; + flex-direction: column; + gap: 12px; +} + +.modal-title { + font-size: 0.9rem; + font-weight: 600; + color: var(--c-txt-1); +} + +.modal-text { + font-size: 0.78rem; + color: var(--c-txt-2); + line-height: 1.4; +} + +.modal-actions { + display: flex; + justify-content: flex-end; + gap: 10px; + margin-top: 8px; +} + +.modal-actions .btn { + padding: 6px 14px; + border-radius: var(--r-sm); + font-size: 0.75rem; + font-weight: 600; + border: none; + transition: all var(--t-fast); +} + +.modal-actions .btn.secondary { + background: rgba(255,255,255,0.08); + color: var(--c-txt-1); +} +html[data-theme="light"] .modal-actions .btn.secondary { + background: rgba(0,0,0,0.08); + color: var(--c-txt-1); +} + +.modal-actions .btn.secondary:hover { background: rgba(255,255,255,0.12); } +html[data-theme="light"] .modal-actions .btn.secondary:hover { background: rgba(0,0,0,0.12); } + +.modal-actions .btn.primary { + background: var(--c-accent); + color: #fff; +} +.modal-actions .btn.primary:hover { background: var(--c-accent-2); } diff --git a/ostp-tun-helper/src/main.rs b/ostp-tun-helper/src/main.rs index 1c1e60a..a13e1ee 100644 --- a/ostp-tun-helper/src/main.rs +++ b/ostp-tun-helper/src/main.rs @@ -52,6 +52,9 @@ struct TunnelState { #[tokio::main] async fn main() -> Result<()> { + ostp_client::logging::setup_panic_hook(); + let _log_guard = ostp_client::logging::init_tracing("info", "ostp-helper", env!("CARGO_PKG_VERSION")); + if let Ok(exe) = std::env::current_exe() { if let Some(dir) = exe.parent() { let _ = std::env::set_current_dir(dir); diff --git a/ostp-tun/Cargo.toml b/ostp-tun/Cargo.toml new file mode 100644 index 0000000..edd2ce0 --- /dev/null +++ b/ostp-tun/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "ostp-tun" +version.workspace = true +edition.workspace = true +license.workspace = true + +[dependencies] +anyhow.workspace = true +tokio.workspace = true +tracing.workspace = true +tun = { version = "0.8.9", features = ["async"] } + +[target.'cfg(windows)'.dependencies] +winapi = { version = "0.3.9", features = ["iphlpapi", "tcpmib", "processthreadsapi", "psapi", "handleapi", "winerror", "minwindef", "winnt", "iptypes", "ws2def"] } + +[target.'cfg(target_os = "linux")'.dependencies] +libc = "0.2" diff --git a/ostp-tun/src/lib.rs b/ostp-tun/src/lib.rs new file mode 100644 index 0000000..4f94c2c --- /dev/null +++ b/ostp-tun/src/lib.rs @@ -0,0 +1,40 @@ +use anyhow::Result; + +pub struct OstpTunOptions { + pub server_ip: std::net::IpAddr, + pub bypass_ips: Vec, + pub dns_server: Option, + pub kill_switch: bool, + pub mtu: u16, + pub wintun_path: Option, +} + +pub struct OstpTunInterface { + pub device: tun::AsyncDevice, + pub guard: Box, +} + +#[cfg(target_os = "windows")] +pub mod windows; + +#[cfg(target_os = "linux")] +pub mod linux; + +#[cfg(target_os = "macos")] +pub mod macos; + +impl OstpTunInterface { + pub async fn create(opts: OstpTunOptions) -> Result { + #[cfg(target_os = "windows")] + return windows::create(opts).await; + + #[cfg(target_os = "linux")] + return linux::create(opts).await; + + #[cfg(target_os = "macos")] + return macos::create(opts).await; + + #[cfg(not(any(target_os = "windows", target_os = "linux", target_os = "macos")))] + anyhow::bail!("Unsupported OS for ostp-tun"); + } +} diff --git a/ostp-tun/src/linux.rs b/ostp-tun/src/linux.rs new file mode 100644 index 0000000..fb97bb5 --- /dev/null +++ b/ostp-tun/src/linux.rs @@ -0,0 +1,106 @@ +use crate::{OstpTunInterface, OstpTunOptions}; +use anyhow::{anyhow, Result}; +use std::process::Command; + +struct LinuxRouteGuard { + server_ip: String, + bypass_routes: Vec, + real_gw: Option, + real_dev: Option, + kill_switch: bool, +} + +impl Drop for LinuxRouteGuard { + fn drop(&mut self) { + let _ = Command::new("ip").args(["route", "del", "default", "dev", "ostp_tun"]).output(); + let _ = Command::new("ip").args(["route", "del", &format!("{}/32", self.server_ip)]).output(); + for route in &self.bypass_routes { + let _ = Command::new("ip").args(["route", "del", route]).output(); + } + tracing::info!("Removed Linux bypass routes."); + + if self.kill_switch { + if let (Some(ref gw), Some(ref dev)) = (&self.real_gw, &self.real_dev) { + let _ = Command::new("ip").args(["route", "add", "default", "via", gw, "dev", dev]).output(); + tracing::info!("Restored original default route via {} dev {}", gw, dev); + } + } + } +} + +pub async fn create(opts: OstpTunOptions) -> Result { + let mut tun_cfg = tun::Configuration::default(); + tun_cfg + .tun_name("ostp_tun") + .address((10, 1, 0, 2)) + .netmask((255, 255, 255, 0)) + .destination((10, 1, 0, 1)) + .mtu(opts.mtu) + .up(); + + tun_cfg.platform_config(|cfg| { + cfg.packet_information(false); + }); + + let dev = tun::create(&tun_cfg).map_err(|e| anyhow!("Failed to create TUN device: {}", e))?; + let dev = tun::AsyncDevice::new(dev).map_err(|e| anyhow!("TUN device async failed: {}", e))?; + tracing::info!("TUN device 'ostp_tun' created."); + + let gw_out = Command::new("ip") + .args(["route", "show", "default"]) + .output() + .ok() + .and_then(|o| String::from_utf8(o.stdout).ok()); + + let real_gw = gw_out.as_deref().and_then(|s| { + s.split_whitespace().skip_while(|w| *w != "via").nth(1).map(|s| s.to_string()) + }); + let real_dev = gw_out.as_deref().and_then(|s| { + s.split_whitespace().skip_while(|w| *w != "dev").nth(1).map(|s| s.to_string()) + }); + + let mut bypass_routes = Vec::new(); + + if let (Some(ref gw), Some(ref dev_name)) = (&real_gw, &real_dev) { + let server_ip_str = opts.server_ip.to_string(); + let _ = Command::new("ip") + .args(["route", "add", &format!("{}/32", server_ip_str), "via", gw, "dev", dev_name]) + .output(); + tracing::info!("Added bypass route for server {} via {}", server_ip_str, gw); + + for ip in &opts.bypass_ips { + let route = format!("{}/32", ip); + let _ = Command::new("ip").args(["route", "add", &route, "via", gw, "dev", dev_name]).output(); + bypass_routes.push(route); + } + + let _ = Command::new("ip").args(["route", "add", "default", "dev", "ostp_tun"]).output(); + + if opts.kill_switch { + tracing::info!("Kill Switch: deleting original default route to prevent leakage."); + let _ = Command::new("ip").args(["route", "del", "default", "via", gw, "dev", dev_name]).output(); + } + } else { + tracing::warn!("Could not detect physical default gateway. Tunnel routing might not work correctly."); + } + + if let Some(ref dns) = opts.dns_server { + if !dns.is_empty() { + let _ = Command::new("resolvectl").args(["dns", "ostp_tun", dns]).output(); + let _ = Command::new("resolvectl").args(["domain", "ostp_tun", "~."]).output(); + let _ = Command::new("resolvectl").args(["default-route", "ostp_tun", "true"]).output(); + tracing::info!("Configured DNS via resolvectl for ostp_tun: {}", dns); + } + } + + Ok(OstpTunInterface { + device: dev, + guard: Box::new(LinuxRouteGuard { + server_ip: opts.server_ip.to_string(), + bypass_routes, + real_gw, + real_dev, + kill_switch: opts.kill_switch, + }), + }) +} diff --git a/ostp-tun/src/macos.rs b/ostp-tun/src/macos.rs new file mode 100644 index 0000000..358ad99 --- /dev/null +++ b/ostp-tun/src/macos.rs @@ -0,0 +1,89 @@ +use crate::{OstpTunInterface, OstpTunOptions}; +use anyhow::{anyhow, Result}; +use std::process::Command; + +struct MacosRouteGuard { + server_ip: String, + bypass_routes: Vec, + real_gw: Option, + kill_switch: bool, +} + +impl Drop for MacosRouteGuard { + fn drop(&mut self) { + let _ = Command::new("route").args(["delete", "-net", "default", "-interface", "utun5"]).output(); + let _ = Command::new("route").args(["delete", "-host", &self.server_ip]).output(); + for route in &self.bypass_routes { + let _ = Command::new("route").args(["delete", "-host", route]).output(); + } + tracing::info!("Removed macOS bypass routes."); + + if self.kill_switch { + if let Some(ref gw) = self.real_gw { + let _ = Command::new("route").args(["add", "default", gw]).output(); + tracing::info!("Restored original default route via {}", gw); + } + } + } +} + +pub async fn create(opts: OstpTunOptions) -> Result { + let mut tun_cfg = tun::Configuration::default(); + tun_cfg + .tun_name("utun5") + .address((10, 1, 0, 2)) + .netmask((255, 255, 255, 0)) + .destination((10, 1, 0, 1)) + .mtu(opts.mtu) + .up(); + + let dev = tun::create(&tun_cfg).map_err(|e| anyhow!("Failed to create TUN device: {}", e))?; + let dev = tun::AsyncDevice::new(dev).map_err(|e| anyhow!("TUN device async failed: {}", e))?; + tracing::info!("TUN device 'utun5' created."); + + let gw_out = Command::new("route") + .args(["-n", "get", "default"]) + .output() + .ok() + .and_then(|o| String::from_utf8(o.stdout).ok()); + + let real_gw = gw_out.as_deref().and_then(|s| { + s.lines() + .find(|l| l.contains("gateway:")) + .and_then(|l| l.split_whitespace().nth(1)) + .map(|s| s.to_string()) + }); + + let mut bypass_routes = Vec::new(); + + if let Some(ref gw) = real_gw { + let server_ip_str = opts.server_ip.to_string(); + let _ = Command::new("route").args(["add", "-host", &server_ip_str, gw]).output(); + tracing::info!("Added bypass route for server {} via {}", server_ip_str, gw); + + for ip in &opts.bypass_ips { + let route = format!("{}", ip); + let _ = Command::new("route").args(["add", "-host", &route, gw]).output(); + bypass_routes.push(route); + } + + let _ = Command::new("route").args(["add", "-net", "default", "-interface", "utun5"]).output(); + + if opts.kill_switch { + tracing::info!("Kill Switch: deleting original default route to prevent leakage."); + let _ = Command::new("route").args(["delete", "default", gw]).output(); + } + } else { + tracing::warn!("Could not detect physical default gateway on macOS."); + } + + Ok(OstpTunInterface { + device: dev, + guard: Box::new(MacosRouteGuard { + server_ip: opts.server_ip.to_string(), + bypass_routes, + real_gw, + kill_switch: opts.kill_switch, + }), + }) +} diff --git a/ostp-tun/src/windows.rs b/ostp-tun/src/windows.rs new file mode 100644 index 0000000..78a0917 --- /dev/null +++ b/ostp-tun/src/windows.rs @@ -0,0 +1,156 @@ +use crate::{OstpTunInterface, OstpTunOptions}; +use anyhow::{anyhow, Result}; +use std::process::Command; +use std::os::windows::process::CommandExt; + +pub mod windows_route { + include!("windows_route.rs"); +} + +struct WindowsRouteGuard { + bypass_routes: Vec<(std::net::Ipv4Addr, std::net::Ipv4Addr, u32)>, + kill_switch: bool, +} + +impl Drop for WindowsRouteGuard { + fn drop(&mut self) { + const CREATE_NO_WINDOW: u32 = 0x08000000; + + windows_route::sys::remove_bypass_routes(&self.bypass_routes); + tracing::info!("Removed {} bypass routes.", self.bypass_routes.len()); + + let is_kill_switch = self.kill_switch; + let _ = std::thread::spawn(move || { + let _ = Command::new("netsh") + .creation_flags(CREATE_NO_WINDOW) + .args(["advfirewall", "firewall", "delete", "rule", "name=OSTP Tunnel In"]) + .output(); + let _ = Command::new("netsh") + .creation_flags(CREATE_NO_WINDOW) + .args(["advfirewall", "firewall", "delete", "rule", "name=OSTP Tunnel Out"]) + .output(); + let _ = Command::new("netsh") + .creation_flags(CREATE_NO_WINDOW) + .args(["interface", "ipv4", "set", "dnsservers", + "name=ostp_tun", "source=dhcp"]) + .output(); + if is_kill_switch { + let _ = Command::new("route") + .creation_flags(CREATE_NO_WINDOW) + .args(["delete", "0.0.0.0", "mask", "0.0.0.0", "127.0.0.1"]) + .output(); + } + }); + } +} + +pub async fn create(opts: OstpTunOptions) -> Result { + const CREATE_NO_WINDOW: u32 = 0x08000000; + + let (phys_gw, phys_if) = windows_route::sys::get_default_ipv4_route() + .ok_or_else(|| anyhow!("Cannot find physical default IPv4 route"))?; + + let mut bypass_v4: Vec = Vec::new(); + if let std::net::IpAddr::V4(v4) = opts.server_ip { + bypass_v4.push(v4); + } + for ip in opts.bypass_ips { + if let std::net::IpAddr::V4(v4) = ip { + bypass_v4.push(v4); + } + } + + let bypass_routes = windows_route::sys::add_bypass_routes(&bypass_v4, phys_gw, phys_if, 1); + tracing::info!("Added {} bypass routes via {} (if_index={})", bypass_routes.len(), phys_gw, phys_if); + + let mut tun_cfg = tun::Configuration::default(); + tun_cfg + .tun_name("ostp_tun") + .address((10, 1, 0, 2)) + .netmask((255, 255, 255, 0)) + .destination((10, 1, 0, 1)) + .mtu(opts.mtu) + .up(); + + let dev = tun::create(&tun_cfg).map_err(|e| anyhow!("Failed to create TUN device: {}", e))?; + let dev = tun::AsyncDevice::new(dev).map_err(|e| anyhow!("TUN device async failed: {}", e))?; + tracing::info!("TUN device 'ostp_tun' created."); + + let current_exe = std::env::current_exe()?.to_string_lossy().into_owned(); + + let mut tun_index = None; + for _ in 0..20 { + if let Some(idx) = windows_route::sys::get_interface_index("ostp_tun") { + tun_index = Some(idx); + break; + } + tokio::time::sleep(std::time::Duration::from_millis(100)).await; + } + + if let Some(idx) = tun_index { + let _ = windows_route::sys::add_ipv4_route( + std::net::Ipv4Addr::new(0, 0, 0, 0), + std::net::Ipv4Addr::new(0, 0, 0, 0), + std::net::Ipv4Addr::new(10, 1, 0, 1), + idx, + 5, + ); + tracing::info!("Default route via TUN (if_index={idx}, metric=5) added."); + } else { + tracing::warn!("Could not find ostp_tun index in routing table — traffic may not be captured."); + } + + let exe1 = current_exe.clone(); + let exe2 = current_exe.clone(); + let _ = tokio::task::spawn_blocking(move || { + let _ = Command::new("netsh") + .creation_flags(CREATE_NO_WINDOW) + .args(["advfirewall", "firewall", "add", "rule", + "name=OSTP Tunnel In", "dir=in", "action=allow", + &format!("program={}", exe1)]) + .output(); + let _ = Command::new("netsh") + .creation_flags(CREATE_NO_WINDOW) + .args(["advfirewall", "firewall", "add", "rule", + "name=OSTP Tunnel Out", "dir=out", "action=allow", + &format!("program={}", exe2)]) + .output(); + let _ = Command::new("netsh") + .creation_flags(CREATE_NO_WINDOW) + .args(["interface", "ipv4", "set", "interface", "name=ostp_tun", + "routerdiscovery=disabled", "dadtransmits=0", + "managedaddress=disabled", "otherstateful=disabled"]) + .output(); + }); + + if let Some(ref dns) = opts.dns_server { + if !dns.is_empty() { + let dns_clone = dns.clone(); + let _ = tokio::task::spawn_blocking(move || { + let _ = Command::new("netsh") + .creation_flags(CREATE_NO_WINDOW) + .args(["interface", "ipv4", "set", "dnsservers", + "name=ostp_tun", "static", &dns_clone, "primary"]) + .output(); + }); + } + } + + if opts.kill_switch { + tracing::info!("Kill Switch enabled: Adding metric 10 blackhole route to prevent leakage"); + let _ = tokio::task::spawn_blocking(move || { + let _ = Command::new("route") + .creation_flags(CREATE_NO_WINDOW) + .args(["add", "0.0.0.0", "mask", "0.0.0.0", "127.0.0.1", "metric", "10", "if", "1"]) + .output(); + }); + } + + Ok(OstpTunInterface { + device: dev, + guard: Box::new(WindowsRouteGuard { + bypass_routes, + kill_switch: opts.kill_switch, + }), + }) +} diff --git a/ostp-client/src/tunnel/windows_route.rs b/ostp-tun/src/windows_route.rs similarity index 100% rename from ostp-client/src/tunnel/windows_route.rs rename to ostp-tun/src/windows_route.rs diff --git a/ostp/src/main.rs b/ostp/src/main.rs index a316b93..512ae6e 100644 --- a/ostp/src/main.rs +++ b/ostp/src/main.rs @@ -415,16 +415,8 @@ struct MuxConfig { #[tokio::main] async fn main() -> Result<()> { - // Initialize structured logging via tracing - // Default: info level; override with RUST_LOG env var (e.g. RUST_LOG=ostp_server=debug) - tracing_subscriber::fmt() - .with_env_filter( - tracing_subscriber::EnvFilter::try_from_default_env() - .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info")) - ) - .with_target(false) - .compact() - .init(); + ostp_client::logging::setup_panic_hook(); + let _log_guard = ostp_client::logging::init_tracing("info", "ostp-cli", env!("CARGO_PKG_VERSION")); let res = run_app().await; if let Err(e) = res { diff --git a/scripts/build.ps1 b/scripts/build.ps1 index b2c3088..fab2b9c 100644 --- a/scripts/build.ps1 +++ b/scripts/build.ps1 @@ -39,6 +39,39 @@ if (Test-Path $CargoToml) { [System.IO.File]::WriteAllText($CargoToml, $NewContent) } Write-Output "[ok] Version: v$Version" + + # Bump Tauri GUI + $TauriConf = Join-Path $ProjectRoot "ostp-gui\src-tauri\tauri.conf.json" + if (Test-Path $TauriConf) { + $TauriContent = [System.IO.File]::ReadAllText($TauriConf) + $TauriRegex = [regex] '"version":\s*"[^"]+"' + $TauriContent = $TauriRegex.Replace($TauriContent, ('"version": "' + $Version + '"'), 1) + [System.IO.File]::WriteAllText($TauriConf, $TauriContent) + Write-Output " [ok] Updated tauri.conf.json" + } + + # Bump React Control Panel + $PackageJson = Join-Path $ProjectRoot "ostp-control\package.json" + if (Test-Path $PackageJson) { + $PkgContent = [System.IO.File]::ReadAllText($PackageJson) + $PkgRegex = [regex] '"version":\s*"[^"]+"' + $PkgContent = $PkgRegex.Replace($PkgContent, ('"version": "' + $Version + '"'), 1) + [System.IO.File]::WriteAllText($PackageJson, $PkgContent) + Write-Output " [ok] Updated package.json" + } + + # Bump Flutter App + $Pubspec = Join-Path $ProjectRoot "ostp-flutter\pubspec.yaml" + if (Test-Path $Pubspec) { + $PubContent = [System.IO.File]::ReadAllText($Pubspec) + if ($PubContent -match 'version:\s*(\d+\.\d+\.\d+)\+(\d+)') { + $BuildNumber = [int]$Matches[2] + 1 + $PubRegex = [regex] 'version:\s*\d+\.\d+\.\d+\+\d+' + $PubContent = $PubRegex.Replace($PubContent, ("version: $Version+$BuildNumber"), 1) + [System.IO.File]::WriteAllText($Pubspec, $PubContent) + Write-Output " [ok] Updated pubspec.yaml" + } + } } }