From a82c664e5b8ab99150ee3e0d6649145dafc4bdad Mon Sep 17 00:00:00 2001 From: ospab Date: Sat, 30 May 2026 21:14:29 +0300 Subject: [PATCH] Fix UDP IPv4-mapped IPv6 address matching bug and completely remove tun2socks --- .github/workflows/release.yml | 53 +--- ostp-client/src/bridge.rs | 12 +- ostp-client/src/tunnel/linux_handler.rs | 233 ---------------- ostp-client/src/tunnel/mod.rs | 23 +- ostp-client/src/tunnel/native_handler.rs | 10 +- ostp-client/src/tunnel/proxy.rs | 4 + ostp-client/src/tunnel/udp_nat.rs | 8 +- ostp-client/src/tunnel/wintun_handler.rs | 259 ------------------ .../com/ospab/ostp_client/OstpVpnService.kt | 27 +- .../kotlin/net/ostp/client/OstpClientSdk.kt | 16 +- ostp-flutter/lib/main.dart | 65 +---- ostp-gui/src/main.js | 93 ++++--- ostp-server/src/dispatcher.rs | 4 +- ostp-server/src/relay.rs | 61 ++--- scripts/install.sh | 5 +- 15 files changed, 143 insertions(+), 730 deletions(-) delete mode 100644 ostp-client/src/tunnel/linux_handler.rs delete mode 100644 ostp-client/src/tunnel/wintun_handler.rs diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 71fea2d..dcaff8d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -28,21 +28,18 @@ jobs: target: x86_64-pc-windows-msvc artifact_name: ostp.exe release_name: ostp-windows-amd64.zip - tun2socks_arch: windows-amd64 wintun_arch: amd64 - os: windows-latest target: i686-pc-windows-msvc artifact_name: ostp.exe release_name: ostp-windows-386.zip - tun2socks_arch: windows-386 wintun_arch: x86 - os: windows-latest target: aarch64-pc-windows-msvc artifact_name: ostp.exe release_name: ostp-windows-arm64.zip - tun2socks_arch: windows-arm64 wintun_arch: arm64 # ── macOS ───────────────────────────────────────────────────────── @@ -50,26 +47,22 @@ jobs: target: x86_64-apple-darwin artifact_name: ostp release_name: ostp-darwin-amd64.tar.gz - tun2socks_arch: darwin-amd64 - os: macos-latest target: aarch64-apple-darwin artifact_name: ostp release_name: ostp-darwin-arm64.tar.gz - tun2socks_arch: darwin-arm64 # ── Linux native ────────────────────────────────────────────────── - os: ubuntu-latest target: x86_64-unknown-linux-musl artifact_name: ostp release_name: ostp-linux-amd64.tar.gz - tun2socks_arch: linux-amd64 - os: ubuntu-latest target: i686-unknown-linux-musl artifact_name: ostp release_name: ostp-linux-386.tar.gz - tun2socks_arch: linux-386 use_cross: true # ── Linux cross ─────────────────────────────────────────────────── @@ -77,28 +70,24 @@ jobs: target: aarch64-unknown-linux-musl artifact_name: ostp release_name: ostp-linux-arm64.tar.gz - tun2socks_arch: linux-arm64 use_cross: true - os: ubuntu-latest target: armv7-unknown-linux-musleabihf artifact_name: ostp release_name: ostp-linux-armv7.tar.gz - tun2socks_arch: linux-armv7 use_cross: true - os: ubuntu-latest target: x86_64-unknown-freebsd artifact_name: ostp release_name: ostp-freebsd-amd64.tar.gz - tun2socks_arch: freebsd-amd64 use_cross: true - os: ubuntu-latest target: mipsel-unknown-linux-musl artifact_name: ostp release_name: ostp-linux-mipsle.tar.gz - tun2socks_arch: linux-mipsle-softfloat use_cross: true toolchain: nightly @@ -106,7 +95,6 @@ jobs: target: riscv64gc-unknown-linux-gnu artifact_name: ostp release_name: ostp-linux-riscv64.tar.gz - tun2socks_arch: linux-riscv64 use_cross: true @@ -175,31 +163,16 @@ jobs: run: cross build --release --target ${{ matrix.target }} --bin ostp # ── Driver dependencies ──────────────────────────────────────────────── - - name: Download tun2socks + wintun (Windows) - if: ${{ matrix.os == 'windows-latest' && matrix.tun2socks_arch }} + - name: Download wintun (Windows) + if: ${{ matrix.os == 'windows-latest' }} shell: pwsh run: | $ProgressPreference = 'SilentlyContinue' $dir = "target/${{ matrix.target }}/release" - Invoke-WebRequest -Uri "https://github.com/xjasonlyu/tun2socks/releases/download/v2.6.0/tun2socks-${{ matrix.tun2socks_arch }}.zip" -OutFile "$dir/t2s.zip" - Expand-Archive "$dir/t2s.zip" -DestinationPath "$dir/t2s_tmp" -Force - Get-ChildItem "$dir/t2s_tmp" -Filter "*.exe" -Recurse | Select-Object -First 1 | Copy-Item -Destination "$dir/tun2socks.exe" Invoke-WebRequest -Uri "https://www.wintun.net/builds/wintun-0.14.1.zip" -OutFile "$dir/wt.zip" Expand-Archive "$dir/wt.zip" -DestinationPath "$dir/wt_tmp" -Force Get-ChildItem "$dir/wt_tmp" -Filter "wintun.dll" -Recurse | Where-Object { $_.FullName -match 'bin[\\/]${{ matrix.wintun_arch }}[\\/]' } | Copy-Item -Destination "$dir/" - Remove-Item "$dir/t2s.zip","$dir/t2s_tmp","$dir/wt.zip","$dir/wt_tmp" -Recurse -Force - - - name: Download tun2socks (Unix) - if: ${{ matrix.os != 'windows-latest' && matrix.tun2socks_arch }} - shell: bash - run: | - dir="target/${{ matrix.target }}/release" - URL="https://github.com/xjasonlyu/tun2socks/releases/download/v2.6.0/tun2socks-${{ matrix.tun2socks_arch }}.zip" - curl -fsSL "$URL" -o "$dir/t2s.zip" || exit 0 - unzip -o "$dir/t2s.zip" -d "$dir/t2s_tmp" - find "$dir/t2s_tmp" -type f -name "tun2socks*" ! -name "*.zip" | head -1 | xargs -I{} cp {} "$dir/tun2socks" - chmod +x "$dir/tun2socks" 2>/dev/null || true - rm -rf "$dir/t2s.zip" "$dir/t2s_tmp" + Remove-Item "$dir/wt.zip","$dir/wt_tmp" -Recurse -Force # ── Package ──────────────────────────────────────────────────────────── - name: Package (Windows) @@ -208,7 +181,6 @@ jobs: run: | $dir = "target/${{ matrix.target }}/release" $files = @("ostp.exe") - if (Test-Path "$dir/tun2socks.exe") { $files += "tun2socks.exe" } if (Test-Path "$dir/wintun.dll") { $files += "wintun.dll" } Push-Location $dir Compress-Archive -Path $files -DestinationPath "../../../${{ matrix.release_name }}" -Force @@ -219,7 +191,6 @@ jobs: run: | dir="target/${{ matrix.target }}/release" FILES="${{ matrix.artifact_name }}" - [ -f "$dir/tun2socks" ] && FILES="$FILES tun2socks" tar -czf "${{ matrix.release_name }}" -C "$dir" $FILES # ── Upload ───────────────────────────────────────────────────────────── @@ -267,17 +238,11 @@ jobs: target/ key: cargo-windows-gui-${{ matrix.target }}-${{ hashFiles('**/Cargo.lock') }} - - name: Download wintun and tun2socks + - name: Download wintun shell: pwsh run: | $ProgressPreference = 'SilentlyContinue' - # Download tun2socks - New-Item -ItemType Directory -Force -Path "t2s_tmp" - Invoke-WebRequest -Uri "https://github.com/xjasonlyu/tun2socks/releases/download/v2.6.0/tun2socks-windows-${{ matrix.arch }}.zip" -OutFile "t2s_tmp/t2s.zip" - Expand-Archive "t2s_tmp/t2s.zip" -DestinationPath "t2s_tmp/ext" -Force - Get-ChildItem "t2s_tmp/ext" -Filter "*.exe" -Recurse | Select-Object -First 1 | Copy-Item -Destination "t2s_tmp/tun2socks.exe" -Force - # Download wintun New-Item -ItemType Directory -Force -Path "target/${{ matrix.target }}/release" Invoke-WebRequest -Uri "https://www.wintun.net/builds/wintun-0.14.1.zip" -OutFile "target/wt.zip" @@ -298,7 +263,6 @@ jobs: New-Item -ItemType Directory -Force -Path $dir Copy-Item "ostp-gui/src-tauri/target/${{ matrix.target }}/release/ostp-gui.exe" $dir Copy-Item "target/${{ matrix.target }}/release/ostp-tun-helper.exe" $dir - Copy-Item "t2s_tmp/tun2socks.exe" $dir Copy-Item "target/${{ matrix.target }}/release/wintun.dll" $dir Compress-Archive -Path "$dir/*" -DestinationPath "ostp-windows-gui-${{ matrix.arch }}.zip" -Force @@ -320,7 +284,6 @@ jobs: - arch: arm64-v8a rust_target: aarch64-linux-android flutter_target: android-arm64 - tun2socks_arch: linux-arm64 - arch: armeabi-v7a rust_target: armv7-linux-androideabi flutter_target: android-arm @@ -364,13 +327,7 @@ jobs: cargo ndk -t ${{ matrix.arch }} -o "../ostp-flutter/android/app/src/main/jniLibs" build --release cd ../ostp-flutter - # 2. Download tun2socks - if [ ! -f "android/app/src/main/jniLibs/${{ matrix.arch }}/libtun2socks.so" ]; then - curl -fsSL "https://github.com/xjasonlyu/tun2socks/releases/download/v2.6.0/tun2socks-${{ matrix.tun2socks_arch }}.zip" -o "t2s.zip" - unzip -o t2s.zip -d t2s_tmp - cp t2s_tmp/tun2socks-${{ matrix.tun2socks_arch }} android/app/src/main/jniLibs/${{ matrix.arch }}/libtun2socks.so - rm -rf t2s.zip t2s_tmp - fi + # 3. Build Flutter APK flutter build apk --release --target-platform ${{ matrix.flutter_target }} diff --git a/ostp-client/src/bridge.rs b/ostp-client/src/bridge.rs index 2b0e7ff..5d83ca0 100644 --- a/ostp-client/src/bridge.rs +++ b/ostp-client/src/bridge.rs @@ -504,6 +504,13 @@ impl Bridge { self.last_valid_recv = Instant::now(); self.metrics.connection_state.store(2, Ordering::Relaxed); // State: Connected let _ = tx.send(UiEvent::Log("Background reconnect successful! Connection restored.".into())).await; + + // FIX: Clear existing proxy streams. Since we are on a NEW session_id, + // the server does not know about our existing streams. Closing them + // forces local apps/TUN to immediately recreate them and send proper + // Connect/UdpAssociate over the new session, avoiding a 5-minute blackhole. + stream_map.clear(); + self.reset_proxy_streams(&tx, &proxy_tx, "background reconnect"); } else { let _ = tx.send(UiEvent::Log("Background reconnect failed. Will retry on next tick...".into())).await; } @@ -753,7 +760,7 @@ impl Bridge { psk: secrets.psk, session_id, handshake_payload, - max_padding: 1280, // Safe MTU size to avoid UDP fragmentation on Windows/PPPoE + // max_padding computed dynamically below from mtu padding_strategy: PaddingStrategy::Profile(self.profile), obfuscation_key: secrets.obfuscation_key, max_reorder: 16384, // Max gap between expected and received nonce @@ -765,6 +772,7 @@ impl Bridge { handshake_pad_min: secrets.handshake_pad_min, handshake_pad_max: secrets.handshake_pad_max, mtu: self.mtu, + max_padding: self.mtu.saturating_sub(48).max(256), // leave room for UDP/IP/ostp headers })?; let resolved_addrs: Vec = match tokio::net::lookup_host(&self.server_addr).await { @@ -895,6 +903,8 @@ impl Bridge { self.reality_enabled = cfg.reality.enabled; self.reality_pbk = cfg.reality.pbk.clone(); self.reality_sid = cfg.reality.sid.clone(); + self.mtu = cfg.ostp.mtu; // Fix: mtu was never updated on hot-reload + self.keepalive_interval_sec = cfg.ostp.keepalive_interval_sec; // Fix: keepalive was never updated on hot-reload } async fn try_connect_transport( diff --git a/ostp-client/src/tunnel/linux_handler.rs b/ostp-client/src/tunnel/linux_handler.rs deleted file mode 100644 index db9714c..0000000 --- a/ostp-client/src/tunnel/linux_handler.rs +++ /dev/null @@ -1,233 +0,0 @@ -use anyhow::{anyhow, Result}; -use tokio::sync::watch; - -#[cfg(target_os = "linux")] -use std::net::ToSocketAddrs; -#[cfg(target_os = "linux")] -use std::process::{Command, Stdio, Child}; -#[cfg(target_os = "linux")] -use std::io::{BufRead, BufReader}; - -#[cfg(target_os = "linux")] -struct LinuxRouteGuard { - server_ip_str: String, - default_gw: String, - default_if: String, - child: Option, -} - -#[cfg(target_os = "linux")] -impl Drop for LinuxRouteGuard { - fn drop(&mut self) { - if let Some(mut child) = self.child.take() { - let _ = child.kill(); - } - let cleanup_script = format!( - "ip route del 0.0.0.0/1 dev ostp_tun || true; \ - ip route del 128.0.0.0/1 dev ostp_tun || true; \ - ip route del {} via {} dev {} || true; \ - ip route del 1.1.1.1 via {} dev {} || true; \ - ip link set dev ostp_tun down || true; \ - ip tuntap del name ostp_tun mode tun || true", - self.server_ip_str, self.default_gw, self.default_if, - self.default_gw, self.default_if - ); - let _ = Command::new("sh").args(["-c", &cleanup_script]).output(); - } -} - -#[cfg(target_os = "linux")] -pub async fn run_linux_tunnel( - config: crate::config::ClientConfig, - mut shutdown: watch::Receiver, -) -> Result<()> { - let debug = config.debug; - if debug { - println!("[ostp] Initializing TUN tunnel..."); - } - - let exe = std::env::current_exe()?; - let dir = exe.parent().ok_or_else(|| anyhow!("failed to get binary directory"))?; - - let mut tun2socks_exe = dir.join("tun2socks"); - if !tun2socks_exe.exists() { - // Try system PATH via standard command check - let in_path = Command::new("which") - .arg("tun2socks") - .output() - .map(|o| o.status.success()) - .unwrap_or(false); - - if in_path { - tun2socks_exe = std::path::PathBuf::from("tun2socks"); - } else { - return Err(anyhow!( - "CRITICAL: 'tun2socks' binary is missing!\n\ - OSTP requires tun2socks for TUN mode on Linux. Please download the appropriate binary for your architecture from: \n\ - https://github.com/xjasonlyu/tun2socks/releases \n\ - and place it in the same directory as the ostp executable ({}), or install it globally in your PATH.", - dir.display() - )); - } - } - - // 1.5. Pre-flight system checks - let is_root = Command::new("id") - .arg("-u") - .output() - .map(|o| String::from_utf8_lossy(&o.stdout).trim() == "0") - .unwrap_or(false); - - if !is_root { - return Err(anyhow!("FATAL: OSTP TUN mode requires root privileges on Linux. Please run via sudo.")); - } - - let has_ip_cmd = Command::new("which") - .arg("ip") - .output() - .map(|o| o.status.success()) - .unwrap_or(false); - - if !has_ip_cmd { - return Err(anyhow!("FATAL: 'ip' command not found. OSTP TUN mode requires 'iproute2' package to be installed.")); - } - - // 2. Resolve Server IP for routing table exclusion - let server_ip = config.ostp.server_addr.to_socket_addrs() - .map_err(|e| anyhow!("Failed to resolve remote server IP: {}", e))? - .next() - .map(|addr| addr.ip()) - .ok_or_else(|| anyhow!("Could not resolve host IP for routing exclusion"))?; - - let server_ip_str = server_ip.to_string(); - - if debug { - println!("[ostp] Resolved server IP: {}", server_ip_str); - } - - // 3. Detect current default gateway and interface - let route_output = Command::new("sh") - .arg("-c") - .arg("ip route show default | head -n1") - .output()?; - - let route_str = String::from_utf8_lossy(&route_output.stdout); - let parts: Vec<&str> = route_str.split_whitespace().collect(); - - // Expected: "default via 192.168.1.1 dev eth0 ..." - let mut default_gw = String::new(); - let mut default_if = String::new(); - - for i in 0..parts.len() { - if parts[i] == "via" && i + 1 < parts.len() { - default_gw = parts[i+1].to_string(); - } - if parts[i] == "dev" && i + 1 < parts.len() { - default_if = parts[i+1].to_string(); - } - } - - if default_gw.is_empty() || default_if.is_empty() { - return Err(anyhow!("Failed to discover active default gateway or network interface on Linux system.")); - } - - if debug { - println!("[ostp] Default route: gateway={} interface={}", default_gw, default_if); - } - - // 4. Setup commands (Using standard /1 routing trick for fail-proof overriding) - let setup_script = format!( - "ip tuntap add name ostp_tun mode tun || true; \ - ip link set dev ostp_tun mtu {}; \ - ip addr add 10.1.0.2/24 dev ostp_tun || true; \ - ip link set dev ostp_tun up; \ - ip route add {} via {} dev {}; \ - ip route add 1.1.1.1 via {} dev {}; \ - ip route add 0.0.0.0/1 dev ostp_tun; \ - ip route add 128.0.0.0/1 dev ostp_tun", - config.ostp.mtu, server_ip_str, default_gw, default_if, - default_gw, default_if - ); - - if debug { - println!("[ostp] Executing Linux network config: {}", setup_script); - } - - let out = Command::new("sh") - .args(["-c", &setup_script]) - .output()?; - - if !out.status.success() && debug { - println!("[ostp] Warning: Setup routing returned: {}", String::from_utf8_lossy(&out.stderr)); - } - - // 5. Prepare and launch tun2socks - // Using HTTP Proxy natively avoids any UDP Associate requests, - // providing clean TCP proxying with maximum reliability. - let proxy_url = format!("http://{}", config.local_proxy.bind_addr); - - if debug { - println!("[ostp] Spawning {} -device ostp_tun -proxy {}", tun2socks_exe.display(), proxy_url); - } - - let mut child = Command::new(&tun2socks_exe) - .args([ - "-device", "ostp_tun", - "-proxy", &proxy_url, - ]) - .stdout(if debug { Stdio::piped() } else { Stdio::null() }) - .stderr(if debug { Stdio::piped() } else { Stdio::null() }) - .spawn() - .map_err(|e| anyhow!("Failed to spawn tun2socks process: {}", e))?; - - let mut _guard = LinuxRouteGuard { - server_ip_str: server_ip_str.clone(), - default_gw: default_gw.clone(), - default_if: default_if.clone(), - child: None, - }; - - println!("[ostp] TUN tunnel active. All traffic is routed through OSTP."); - - if debug { - let stdout = child.stdout.take().unwrap(); - let stderr = child.stderr.take().unwrap(); - - tokio::spawn(async move { - let reader = BufReader::new(stdout); - for line in reader.lines().map_while(Result::ok) { - println!("[tun2socks] {}", line); - } - }); - - tokio::spawn(async move { - let reader = BufReader::new(stderr); - for line in reader.lines().map_while(Result::ok) { - tracing::warn!("tun2socks: {}", line); - } - }); - } - - _guard.child = Some(child); - - // 6. Wait for shutdown signal - let _ = shutdown.changed().await; - - println!("[ostp] Deactivating TUN tunnel..."); - - // Drop guard runs cleanup automatically - drop(_guard); - - println!("[ostp] TUN tunnel stopped."); - - Ok(()) -} - -#[cfg(not(target_os = "linux"))] -#[allow(dead_code)] -pub async fn run_linux_tunnel( - _config: crate::config::ClientConfig, - _shutdown: watch::Receiver, -) -> Result<()> { - Err(anyhow!("Linux tunnel driver executed on a non-Linux host!")) -} diff --git a/ostp-client/src/tunnel/mod.rs b/ostp-client/src/tunnel/mod.rs index cd87812..1d85004 100644 --- a/ostp-client/src/tunnel/mod.rs +++ b/ostp-client/src/tunnel/mod.rs @@ -1,6 +1,4 @@ mod proxy; -mod wintun_handler; -mod linux_handler; pub mod native_handler; mod udp_nat; @@ -8,26 +6,7 @@ pub async fn run_tun_tunnel( config: crate::config::ClientConfig, shutdown: watch::Receiver, ) -> anyhow::Result<()> { - if config.tun_stack == "ostp" { - return native_handler::run_native_tunnel(config, shutdown).await; - } - - #[cfg(target_os = "windows")] - { - wintun_handler::run_wintun_tunnel(config, shutdown).await - } - - #[cfg(target_os = "linux")] - { - linux_handler::run_linux_tunnel(config, shutdown).await - } - - #[cfg(not(any(target_os = "windows", target_os = "linux")))] - { - let _ = shutdown; - let _ = config; - anyhow::bail!("Operating system unsupported, text an issue at github."); - } + native_handler::run_native_tunnel(config, shutdown).await } use tokio::sync::{mpsc, watch}; diff --git a/ostp-client/src/tunnel/native_handler.rs b/ostp-client/src/tunnel/native_handler.rs index da652f7..2350f0e 100644 --- a/ostp-client/src/tunnel/native_handler.rs +++ b/ostp-client/src/tunnel/native_handler.rs @@ -392,7 +392,12 @@ pub async fn run_native_tunnel_from_fd( - let udp_proxy_addr = config.local_proxy.bind_addr.clone(); + let mut proxy_addr = config.local_proxy.bind_addr.clone(); + if proxy_addr.starts_with("0.0.0.0:") { + proxy_addr = proxy_addr.replace("0.0.0.0:", "127.0.0.1:"); + } + + let udp_proxy_addr = proxy_addr.clone(); let debug_udp = config.debug; let mut udp_proxy_task = tokio::spawn(async move { if let Some(udp_sock) = udp_socket { @@ -400,7 +405,6 @@ pub async fn run_native_tunnel_from_fd( } }); - let proxy_addr = config.local_proxy.bind_addr.clone(); let mut tcp_accept_task = tokio::spawn(async move { if let Some(mut listener) = tcp_listener { while let Some((mut stream, _local, remote)) = listener.next().await { @@ -443,6 +447,8 @@ pub async fn run_native_tunnel_from_fd( tokio::select! { _ = shutdown.changed() => {} _ = &mut runner_task => {} + _ = _tun_to_stack => {} + _ = _stack_to_tun => {} _ = &mut udp_proxy_task => {} _ = &mut tcp_accept_task => {} } diff --git a/ostp-client/src/tunnel/proxy.rs b/ostp-client/src/tunnel/proxy.rs index 3041986..dae7cee 100644 --- a/ostp-client/src/tunnel/proxy.rs +++ b/ostp-client/src/tunnel/proxy.rs @@ -470,6 +470,7 @@ async fn handle_udp_associate( } } } else { + tracing::debug!("proxy.rs forwarding UDP DATA to server for target={} payload len={}", target, payload.len()); let _ = event_tx.send(ProxyEvent::UdpData { stream_id, target, payload }).await; } } @@ -501,7 +502,10 @@ async fn handle_udp_associate( } packet.extend_from_slice(&port.to_be_bytes()); packet.extend_from_slice(&data); + tracing::debug!("proxy.rs forwarding UDP REPLY to client_addr={} from server for target={} payload len={}", client_addr, target, data.len()); let _ = sock_tx.send_to(&packet, client_addr).await; + } else { + tracing::error!("proxy.rs failed to parse target string as SocketAddr: {}", target); } } Some(ProxyToClientMsg::Close) | Some(ProxyToClientMsg::Error(_)) | None => break, diff --git a/ostp-client/src/tunnel/udp_nat.rs b/ostp-client/src/tunnel/udp_nat.rs index a615d8c..52a6d9d 100644 --- a/ostp-client/src/tunnel/udp_nat.rs +++ b/ostp-client/src/tunnel/udp_nat.rs @@ -116,6 +116,7 @@ async fn start_udp_session( } packet.extend_from_slice(&dst.port().to_be_bytes()); packet.extend_from_slice(&payload); + tracing::debug!("udp_nat SENDING UDP ASSOCIATE payload len={} to relay_addr={} (original dst: {})", payload.len(), relay_addr, dst); let _ = udp.send_to(&packet, relay_addr).await; } Ok(None) => break, @@ -151,8 +152,13 @@ async fn start_udp_session( _ => continue, }; let payload = buf[header_len..len].to_vec(); + tracing::debug!("udp_nat RECEIVED UDP ASSOCIATE REPLY from {} for {} len={}", remote_dst, client_src, payload.len()); use futures::SinkExt; - let _ = smoltcp_tx.lock().await.send((payload, remote_dst, client_src)).await; + if let Err(e) = smoltcp_tx.lock().await.send((payload, remote_dst, client_src)).await { + tracing::error!("udp_nat failed to inject packet into smoltcp: {}", e); + } else { + tracing::debug!("udp_nat successfully injected packet into smoltcp from {} to {}", remote_dst, client_src); + } } } } diff --git a/ostp-client/src/tunnel/wintun_handler.rs b/ostp-client/src/tunnel/wintun_handler.rs deleted file mode 100644 index 2f0b400..0000000 --- a/ostp-client/src/tunnel/wintun_handler.rs +++ /dev/null @@ -1,259 +0,0 @@ -use anyhow::{anyhow, Result}; -use tokio::sync::watch; - - -#[cfg(target_os = "windows")] -pub async fn run_wintun_tunnel( - config: crate::config::ClientConfig, - mut shutdown: watch::Receiver, -) -> Result<()> { - use std::net::ToSocketAddrs; - use std::process::{Command, Stdio, Child}; - use std::os::windows::process::CommandExt; - - const CREATE_NO_WINDOW: u32 = 0x08000000; - const TUN_NAME: &str = "ostp_tun"; - - struct WintunGuard { - server_ip_str: String, - child: Option, - } - - impl Drop for WintunGuard { - fn drop(&mut self) { - if let Some(mut child) = self.child.take() { - let _ = child.kill(); - let _ = child.wait(); - } - let cleanup_script = format!( - "$remote_ip = '{}'\n\ - Remove-NetRoute -DestinationPrefix \"$remote_ip/32\" -Confirm:$false -ErrorAction SilentlyContinue\n\ - Remove-NetRoute -DestinationPrefix \"1.1.1.1/32\" -Confirm:$false -ErrorAction SilentlyContinue\n\ - Remove-NetFirewallRule -DisplayName 'OSTP Tunnel*' -ErrorAction SilentlyContinue\n\ - netsh interface ipv4 set dnsservers name=\"{TUN_NAME}\" source=dhcp 2>$null\n", - self.server_ip_str - ); - let _ = Command::new("powershell") - .creation_flags(CREATE_NO_WINDOW) - .args(["-NoProfile", "-Command", &cleanup_script]) - .output(); - } - } - - let debug = config.debug; - - tracing::info!("Initializing TUN tunnel..."); - - let exe = std::env::current_exe()?; - let dir = exe.parent().ok_or_else(|| anyhow!("failed to get binary directory"))?; - - let mut tun2socks_exe = dir.join("tun2socks.exe"); - if !tun2socks_exe.exists() { - if let Ok(cwd) = std::env::current_dir() { - let cwd_candidate = cwd.join("tun2socks.exe"); - if cwd_candidate.exists() { - tun2socks_exe = cwd_candidate; - } - } - } - - if !tun2socks_exe.exists() { - return Err(anyhow!( - "CRITICAL: 'tun2socks.exe' binary is missing!\n\ - OSTP requires tun2socks for TUN mode on Windows. Please download the appropriate binary from: \n\ - https://github.com/xjasonlyu/tun2socks/releases \n\ - and place it in the same directory as the ostp executable ({}).", - dir.display() - )); - } - - // 1. Delete stale TUN adapter if it exists from a previous run. - // This prevents wintun from creating "ostp_tun 2", "ostp_tun 3", etc. - // Actually, tun2socks can reuse the existing adapter if we just leave it alone. - // We only clear old IP addresses and routes on it. - tracing::info!("Cleaning up stale TUN adapter..."); - let _ = tokio::task::spawn_blocking(move || { - Command::new("powershell") - .creation_flags(CREATE_NO_WINDOW) - .args(["-NoProfile", "-Command", &format!( - "Remove-NetIPAddress -InterfaceAlias '{TUN_NAME}' -Confirm:$false -ErrorAction SilentlyContinue; \ - Remove-NetRoute -InterfaceAlias '{TUN_NAME}' -Confirm:$false -ErrorAction SilentlyContinue" - )]) - .output() - }).await; - // Brief pause to let the driver release the adapter - tokio::time::sleep(std::time::Duration::from_millis(200)).await; - - // 2. Resolve Server IP for routing table exclusion - let server_ip = config.ostp.server_addr.to_socket_addrs() - .map_err(|e| anyhow!("Failed to resolve remote server IP: {}", e))? - .next() - .map(|addr| addr.ip()) - .ok_or_else(|| anyhow!("Could not resolve host IP for routing exclusion"))?; - - let server_ip_str = server_ip.to_string(); - tracing::info!("Resolved server IP: {}", server_ip_str); - - // 3. Prepare routing and firewall setup script - let current_exe = std::env::current_exe()?.to_string_lossy().into_owned(); - - let setup_script = format!( - "$remote_ip = '{}'\n\ - $exe_path = '{}'\n\ - $route = Get-NetRoute -DestinationPrefix '0.0.0.0/0' | Where-Object {{ $_.InterfaceAlias -notmatch 'tun' -and $_.InterfaceAlias -notmatch 'wintun' }} | Sort-Object RouteMetric | Select-Object -First 1\n\ - if ($route) {{\n\ - $gw = $route.NextHop\n\ - $ifIndex = $route.InterfaceIndex\n\ - if ($gw -eq '0.0.0.0' -or $gw -eq '::') {{\n\ - New-NetRoute -DestinationPrefix \"$remote_ip/32\" -InterfaceIndex $ifIndex -RouteMetric 1 -ErrorAction SilentlyContinue\n\ - }} else {{\n\ - New-NetRoute -DestinationPrefix \"$remote_ip/32\" -NextHop $gw -InterfaceIndex $ifIndex -RouteMetric 1 -ErrorAction SilentlyContinue\n\ - }}\n\ - }}\n\ - New-NetFirewallRule -DisplayName 'OSTP Tunnel In' -Direction Inbound -Program $exe_path -Action Allow -Enabled True -ErrorAction SilentlyContinue\n\ - New-NetFirewallRule -DisplayName 'OSTP Tunnel Out' -Direction Outbound -Program $exe_path -Action Allow -Enabled True -ErrorAction SilentlyContinue\n", - server_ip_str, current_exe - ); - - // 4. Launch tun2socks + route setup IN PARALLEL to save ~3 seconds - let proxy_url = format!("socks5://{}", config.local_proxy.bind_addr); - tracing::info!("Starting tun2socks (proxy={})", proxy_url); - - // Spawn tun2socks immediately — it creates the adapter on its own - let mut child = Command::new(&tun2socks_exe) - .creation_flags(CREATE_NO_WINDOW) - .args([ - "-device", TUN_NAME, - "-proxy", &proxy_url, - "-loglevel", if debug { "debug" } else { "error" } - ]) - .current_dir(dir) - .stdout(if debug { Stdio::piped() } else { Stdio::null() }) - .stderr(if debug { Stdio::piped() } else { Stdio::null() }) - .spawn() - .map_err(|e| anyhow!("Failed to launch tun2socks.exe: {}", e))?; - - let mut _guard = WintunGuard { - server_ip_str: server_ip_str.clone(), - child: None, - }; - - // Run route setup in parallel while tun2socks creates the adapter. - // Also poll for the adapter to appear (typically <1s). - let route_handle = { - let script = setup_script.clone(); - tokio::task::spawn_blocking(move || { - Command::new("powershell") - .creation_flags(CREATE_NO_WINDOW) - .args(["-NoProfile", "-Command", &script]) - .output() - }) - }; - - // 5. Wait for TUN adapter to appear (poll with timeout instead of fixed 2s sleep) - let adapter_deadline = tokio::time::Instant::now() + tokio::time::Duration::from_secs(8); - let mut adapter_ready = false; - while tokio::time::Instant::now() < adapter_deadline { - tokio::time::sleep(std::time::Duration::from_millis(250)).await; - let check = tokio::task::spawn_blocking(move || { - Command::new("powershell") - .creation_flags(CREATE_NO_WINDOW) - .args(["-NoProfile", "-Command", - &format!("(Get-NetAdapter -Name '{TUN_NAME}' -ErrorAction SilentlyContinue).Status")]) - .output() - }).await.unwrap(); - if let Ok(out) = check { - let status = String::from_utf8_lossy(&out.stdout).trim().to_string(); - if debug { - tracing::info!("Adapter status: '{}'", status); - } - if status == "Up" || status == "Disconnected" || !status.is_empty() { - adapter_ready = true; - break; - } - } - } - - if !adapter_ready { - tracing::warn!("WARNING: TUN adapter did not appear within timeout. Proceeding anyway."); - } - - // Wait for route setup to finish (should already be done by now) - let _ = route_handle.await; - - // 6. Configure the adapter (IP, metric, MTU, DNS) - tracing::info!("Applying network configuration..."); - let mut net_setup = format!( - "netsh interface ipv4 set address name=\"{TUN_NAME}\" static 10.1.0.2 255.255.255.0\n\ - netsh interface ipv4 set subinterface \"{TUN_NAME}\" mtu={} store=persistent\n\ - netsh interface ipv4 set interface name=\"{TUN_NAME}\" metric=1\n\ - New-NetRoute -DestinationPrefix '0.0.0.0/1' -InterfaceAlias '{TUN_NAME}' -RouteMetric 1 -ErrorAction SilentlyContinue\n\ - New-NetRoute -DestinationPrefix '128.0.0.0/1' -InterfaceAlias '{TUN_NAME}' -RouteMetric 1 -ErrorAction SilentlyContinue\n", - config.ostp.mtu - ); - - - if let Some(ref dns) = config.dns_server { - if !dns.is_empty() { - tracing::info!("DNS server: {}", dns); - net_setup.push_str(&format!( - "netsh interface ipv4 set dnsservers name=\"{TUN_NAME}\" static {} primary\n", dns - )); - } - } - - let _ = tokio::task::spawn_blocking(move || { - Command::new("powershell") - .creation_flags(CREATE_NO_WINDOW) - .args(["-NoProfile", "-Command", &net_setup]) - .output() - }).await.unwrap()?; - - tracing::info!("TUN tunnel active. All traffic is routed through OSTP."); - - // 7. Spawn debug log readers for tun2socks output - let mut stdout = child.stdout.take(); - let mut stderr = child.stderr.take(); - _guard.child = Some(child); - - if debug { - std::thread::spawn(move || { - use std::io::{BufRead, BufReader}; - if let Some(out) = stdout.take() { - let reader = BufReader::new(out); - for line in reader.lines().map_while(Result::ok) { - tracing::debug!("tun2socks: {}", line); - } - } - }); - std::thread::spawn(move || { - use std::io::{BufRead, BufReader}; - if let Some(err) = stderr.take() { - let reader = BufReader::new(err); - for line in reader.lines().map_while(Result::ok) { - tracing::warn!("tun2socks: {}", line); - } - } - }); - } - - // 8. Wait for shutdown signal - let _ = shutdown.changed().await; - - - - tracing::info!("Deactivating TUN tunnel..."); - drop(_guard); - tracing::info!("TUN tunnel stopped."); - - Ok(()) -} - -#[cfg(not(target_os = "windows"))] -#[allow(dead_code)] -pub async fn run_wintun_tunnel( - _config: crate::config::ClientConfig, - _shutdown: watch::Receiver, -) -> Result<()> { - Err(anyhow!("Wintun driver executed on a non-Windows host!")) -} diff --git a/ostp-flutter/android/app/src/main/kotlin/com/ospab/ostp_client/OstpVpnService.kt b/ostp-flutter/android/app/src/main/kotlin/com/ospab/ostp_client/OstpVpnService.kt index ae0c36e..5e3c2e6 100644 --- a/ostp-flutter/android/app/src/main/kotlin/com/ospab/ostp_client/OstpVpnService.kt +++ b/ostp-flutter/android/app/src/main/kotlin/com/ospab/ostp_client/OstpVpnService.kt @@ -31,16 +31,6 @@ class OstpVpnService : VpnService() { private const val CHANNEL_ID = "ostp_vpn_channel" private const val WAKE_LOCK_TAG = "ostp:vpn_wakelock" - /** Called from Kotlin OstpClientSdk to protect VPN sockets from the VPN itself. */ - @Keep - @JvmStatic - fun protectSocket(fd: Int): Boolean { - // App is excluded from VPN via addDisallowedApplication/addAllowedApplication. - // VpnService.protect() bypasses clatd (464XLAT) on IPv6-only mobile networks, - // breaking IPv4 connectivity. Since we're excluded, protect() is unnecessary. - return true - } - /** * Called by OstpClientSdk.notifyNetworkChanged() JNI thunk. */ @@ -171,7 +161,15 @@ class OstpVpnService : VpnService() { .addRoute("0.0.0.0", 0) .addRoute("::", 0) .addDnsServer(dnsServer) - .setMtu(1300) + .setMtu(json.optJSONObject("ostp")?.optInt("mtu", 1280) ?: 1280) + + try { builder.addDnsServer("8.8.8.8") } catch (e: Throwable) {} + try { builder.addDnsServer("2001:4860:4860::8888") } catch (e: Throwable) {} + try { builder.addDnsServer("2606:4700:4700::1111") } catch (e: Throwable) {} + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + builder.allowBypass() + } try { builder.allowFamily(android.system.OsConstants.AF_INET) @@ -222,14 +220,13 @@ class OstpVpnService : VpnService() { Log.e("OstpVpnService", "Failed to clear O_CLOEXEC", e) } - val t2sBin = applicationInfo.nativeLibraryDir + "/libtun2socks.so" - val success = OstpClientSdk.startClient(configJson, fd, t2sBin, localProxy) + val success = OstpClientSdk.startClient(configJson, fd, "", localProxy) if (success) { - Log.i("OstpVpnService", "OSTP Rust Core & tun2socks started successfully") + Log.i("OstpVpnService", "OSTP Rust Core started successfully") isRunning = true updateNotification(connected = true) } else { - Log.e("OstpVpnService", "Failed to start OSTP Rust Core & tun2socks") + Log.e("OstpVpnService", "Failed to start OSTP Rust Core") stopVpn() } diff --git a/ostp-flutter/android/app/src/main/kotlin/net/ostp/client/OstpClientSdk.kt b/ostp-flutter/android/app/src/main/kotlin/net/ostp/client/OstpClientSdk.kt index 0495e35..173370b 100644 --- a/ostp-flutter/android/app/src/main/kotlin/net/ostp/client/OstpClientSdk.kt +++ b/ostp-flutter/android/app/src/main/kotlin/net/ostp/client/OstpClientSdk.kt @@ -11,11 +11,17 @@ object OstpClientSdk { @Keep @JvmStatic fun protectSocket(fd: Int): Boolean { - val service = com.ospab.ostp_client.OstpVpnService.instance - if (service != null) { - val res = service.protect(fd) - android.util.Log.i("OstpClientSdk", "VpnService.protect(socketFd=$fd) -> success=$res") - return res + var retries = 5 + while (retries > 0) { + val service = com.ospab.ostp_client.OstpVpnService.instance + if (service != null) { + val res = service.protect(fd) + android.util.Log.i("OstpClientSdk", "VpnService.protect(socketFd=$fd) -> success=$res") + return res + } + android.util.Log.w("OstpClientSdk", "VpnService instance is null! Retrying... ($retries left)") + Thread.sleep(200) + retries-- } android.util.Log.e("OstpClientSdk", "VpnService instance is null! Cannot protect socketFd=$fd") return false diff --git a/ostp-flutter/lib/main.dart b/ostp-flutter/lib/main.dart index c6512ac..892fca3 100644 --- a/ostp-flutter/lib/main.dart +++ b/ostp-flutter/lib/main.dart @@ -104,8 +104,7 @@ class _HomeScreenState extends State with TickerProviderStateMixin { } void _updateLatestConfigJson() { - final bool owndns = widget.prefs.getBool('owndns') ?? false; - final dnsServer = owndns ? '10.1.0.1' : (widget.prefs.getString('dns_server') ?? '1.1.1.1'); + final exDomains = widget.prefs.getString('ex_domains') ?? ''; final exIps = widget.prefs.getString('ex_ips') ?? ''; final exProcesses = widget.prefs.getString('ex_processes') ?? ''; @@ -114,11 +113,12 @@ class _HomeScreenState extends State with TickerProviderStateMixin { final stealthSni = widget.prefs.getString('stealth_sni') ?? 'vk.com'; final stealthPort = widget.prefs.getString('stealth_port') ?? '443'; final wss = widget.prefs.getBool('wss') ?? false; - final mtu = widget.prefs.getString('mtu') ?? '1350'; + final mtu = widget.prefs.getString('mtu') ?? '1280'; final muxEnabled = widget.prefs.getBool('mux_enabled') ?? false; final muxSessions = widget.prefs.getString('mux_sessions') ?? '2'; - final tunStack = widget.prefs.getString('tun_stack') ?? 'ostp'; - + final dnsServer = widget.prefs.getString('dns_server'); + final effectiveDnsServer = (dnsServer == null || dnsServer.isEmpty) ? '1.1.1.1' : dnsServer; + final tunStack = 'ostp'; final appRoutingMode = widget.prefs.getString('app_routing_mode') ?? 'bypass'; final appRoutingPackages = widget.prefs.getStringList('app_routing_packages') ?? []; @@ -132,7 +132,7 @@ class _HomeScreenState extends State with TickerProviderStateMixin { "access_key": _accessKey, "handshake_timeout_ms": 10000, "io_timeout_ms": 5000, - "mtu": int.tryParse(mtu) ?? 1350, + "mtu": int.tryParse(mtu) ?? 1280, }, "local_proxy": { "bind_addr": localBind, @@ -169,7 +169,7 @@ class _HomeScreenState extends State with TickerProviderStateMixin { "mode": appRoutingMode, "packages": appRoutingPackages, }, - "dns_server": dnsServer, + "dns_server": effectiveDnsServer, "tun_stack": tunStack }; widget.prefs.setString('latest_config_json', jsonEncode(configMap)); @@ -202,8 +202,8 @@ class _HomeScreenState extends State with TickerProviderStateMixin { _pulseController.repeat(reverse: true); _spinController.repeat(); - final bool owndns = widget.prefs.getBool('owndns') ?? false; - final dnsServer = owndns ? '10.1.0.1' : (widget.prefs.getString('dns_server') ?? '1.1.1.1'); + final dnsServer = widget.prefs.getString('dns_server'); + final effectiveDnsServer = (dnsServer == null || dnsServer.isEmpty) ? '1.1.1.1' : dnsServer; final exDomains = widget.prefs.getString('ex_domains') ?? ''; final exIps = widget.prefs.getString('ex_ips') ?? ''; final exProcesses = widget.prefs.getString('ex_processes') ?? ''; @@ -212,10 +212,10 @@ class _HomeScreenState extends State with TickerProviderStateMixin { final stealthSni = widget.prefs.getString('stealth_sni') ?? 'vk.com'; final stealthPort = widget.prefs.getString('stealth_port') ?? '443'; final wss = widget.prefs.getBool('wss') ?? false; - final mtu = widget.prefs.getString('mtu') ?? '1350'; + final mtu = widget.prefs.getString('mtu') ?? '1280'; final muxEnabled = widget.prefs.getBool('mux_enabled') ?? false; final muxSessions = widget.prefs.getString('mux_sessions') ?? '2'; - final tunStack = widget.prefs.getString('tun_stack') ?? 'ostp'; + final tunStack = 'ostp'; final appRoutingMode = widget.prefs.getString('app_routing_mode') ?? 'bypass'; final appRoutingPackages = widget.prefs.getStringList('app_routing_packages') ?? []; @@ -230,7 +230,7 @@ class _HomeScreenState extends State with TickerProviderStateMixin { "access_key": _accessKey, "handshake_timeout_ms": 10000, "io_timeout_ms": 5000, - "mtu": int.tryParse(mtu) ?? 1350, + "mtu": int.tryParse(mtu) ?? 1280, }, "local_proxy": { "bind_addr": localBind, @@ -989,7 +989,7 @@ class _SettingsScreenState extends State { _localBindCtrl = TextEditingController(text: widget.prefs.getString('local_bind') ?? '127.0.0.1:1088'); _keyCtrl = TextEditingController(text: widget.prefs.getString('access_key') ?? ''); _dnsCtrl = TextEditingController(text: widget.prefs.getString('dns_server') ?? '1.1.1.1'); - _mtuCtrl = TextEditingController(text: widget.prefs.getString('mtu') ?? '1350'); + _mtuCtrl = TextEditingController(text: widget.prefs.getString('mtu') ?? '1280'); _domainsCtrl = TextEditingController(text: widget.prefs.getString('ex_domains') ?? ''); _ipsCtrl = TextEditingController(text: widget.prefs.getString('ex_ips') ?? ''); _processesCtrl = TextEditingController(text: widget.prefs.getString('ex_processes') ?? ''); @@ -1216,15 +1216,8 @@ class _SettingsScreenState extends State { _buildTextField('Server Address', _serverCtrl, hint: 'host:port'), _buildTextField('Local Proxy Bind', _localBindCtrl, hint: '127.0.0.1:1088'), _buildTextField('Access Key', _keyCtrl, hint: 'Secure access key', isPassword: true), - _buildToggle('Built-in Server DNS', 'Route DNS queries to the VPN server', _owndns, (val) { - setState(() { - _owndns = val; - }); - }), - if (!_owndns) ...[ - _buildTextField('Custom DNS Server', _dnsCtrl, hint: '1.1.1.1 (e.g. 8.8.8.8)'), - ], - _buildTextField('MTU (Packet Size)', _mtuCtrl, hint: '1350 (decrease if connection drops)'), + _buildTextField('Custom DNS Server', _dnsCtrl, hint: '1.1.1.1 (e.g. 8.8.8.8)'), + _buildTextField('MTU (Packet Size)', _mtuCtrl, hint: '1280 (decrease if connection drops)'), // ── Transport Mode ─────────────────────────────────────── const Text('Transport Mode', style: TextStyle(color: Colors.white54, fontSize: 13, fontWeight: FontWeight.bold, letterSpacing: 1.0)), @@ -1361,34 +1354,6 @@ class _SettingsScreenState extends State { secondChild: const SizedBox.shrink(), ), - const SizedBox(height: 16), - const Text('TUN Stack (Desktop only)', style: TextStyle(color: Colors.white54, fontSize: 13, fontWeight: FontWeight.bold, letterSpacing: 1.0)), - const SizedBox(height: 10), - Container( - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surface, - borderRadius: BorderRadius.circular(12), - ), - child: Column( - children: [ - RadioListTile( - value: 'system', - groupValue: _tunStack, - title: const Text('System (tun2socks)', style: TextStyle(fontWeight: FontWeight.w600)), - activeColor: Theme.of(context).colorScheme.secondary, - onChanged: (v) => setState(() { _tunStack = v!; _saveSettings(); }), - ), - Divider(color: Colors.white.withOpacity(0.05), height: 1), - RadioListTile( - value: 'ostp', - groupValue: _tunStack, - title: const Text('OSTP (Native)', style: TextStyle(fontWeight: FontWeight.w600)), - activeColor: Theme.of(context).colorScheme.primary, - onChanged: (v) => setState(() { _tunStack = v!; _saveSettings(); }), - ), - ], - ), - ), const SizedBox(height: 16), _buildToggle('Multiplexing (Mux)', 'Combine multiple TCP streams to bypass throttling', _muxEnabled, (v) => setState(() => _muxEnabled = v)), diff --git a/ostp-gui/src/main.js b/ostp-gui/src/main.js index c236ee7..0bdcfcc 100644 --- a/ostp-gui/src/main.js +++ b/ostp-gui/src/main.js @@ -312,8 +312,8 @@ async function handleSave(silent = false) { } const mtuStr = inMtu.value.trim(); - if (mtuStr) rawConfig.mtu = parseInt(mtuStr, 10); - else delete rawConfig.mtu; + if (mtuStr) rawConfig.ostp.mtu = parseInt(mtuStr, 10); + else delete rawConfig.ostp.mtu; if (inMux.checked) { const s = parseInt(inMuxSessions.value.trim(), 10); @@ -416,53 +416,58 @@ window.addEventListener('DOMContentLoaded', async () => { } showToast('Starting Auto search...', 'ok'); - const mtus = [1500, 1350, 1280]; - const modes = [ - { t: 'udp', w: false, r: false }, - { t: 'uot', w: false, r: false }, - { t: 'uot', w: true, r: false }, - { t: 'uot', w: false, r: true } - ]; + try { + const mtus = [1500, 1350, 1280]; + const modes = [ + { t: 'udp', w: false, r: false }, + { t: 'uot', w: false, r: false }, + { t: 'uot', w: true, r: false }, + { t: 'uot', w: false, r: true } + ]; - for (let mode of modes) { - for (let mtu of mtus) { - showToast(`Testing: ${mode.t} | WSS: ${mode.w} | XTLS: ${mode.r} | MTU: ${mtu}`); - - rawConfig.mtu = mtu; - rawConfig.transport = rawConfig.transport || {}; - rawConfig.transport.mode = mode.t; - rawConfig.transport.wss = mode.w; - - if (mode.r) { - rawConfig.reality = rawConfig.reality || {}; - rawConfig.reality.enabled = true; - } else if (rawConfig.reality) { - rawConfig.reality.enabled = false; - } - - await invoke('save_config', { jsonContent: JSON.stringify(rawConfig, null, 2) }); - - setState('connecting'); - const ok = await invoke('start_tunnel'); - if (ok) { - startPolling(); - // Wait a bit to see if it stays connected and ping works - await new Promise(r => setTimeout(r, 3000)); - try { - const metrics = await invoke('get_metrics'); - if (metrics && metrics.rtt_ms > 0) { - showToast(`Success! Found working config: ${mode.t} (MTU ${mtu})`, 'ok'); - return; // Stop on first working - } - } catch {} + for (let mode of modes) { + for (let mtu of mtus) { + showToast(`Testing: ${mode.t} | WSS: ${mode.w} | XTLS: ${mode.r} | MTU: ${mtu}`); - // If we are here, ping failed, so stop and try next - await invoke('stop_tunnel'); - setState('disconnected'); + rawConfig.ostp = rawConfig.ostp || {}; + rawConfig.ostp.mtu = mtu; + rawConfig.transport = rawConfig.transport || {}; + rawConfig.transport.mode = mode.t; + rawConfig.transport.wss = mode.w; + + if (mode.r) { + rawConfig.reality = rawConfig.reality || {}; + rawConfig.reality.enabled = true; + } else if (rawConfig.reality) { + rawConfig.reality.enabled = false; + } + + await invoke('save_config', { jsonContent: JSON.stringify(rawConfig, null, 2) }); + + setState('connecting'); + const ok = await invoke('start_tunnel'); + if (ok) { + startPolling(); + // Wait a bit to see if it stays connected and ping works + await new Promise(r => setTimeout(r, 3000)); + try { + const metrics = await invoke('get_metrics'); + if (metrics && metrics.rtt_ms > 0) { + showToast(`Success! Found working config: ${mode.t} (MTU ${mtu})`, 'ok'); + return; // Stop on first working + } + } catch {} + + // If we are here, ping failed, so stop and try next + await invoke('stop_tunnel'); + setState('disconnected'); + } } } + showToast('Auto search finished. No working config found.', 'error'); + } catch (err) { + showToast('Error during auto-connect: ' + String(err), 'error'); } - showToast('Auto search finished. No working config found.', 'error'); }); } diff --git a/ostp-server/src/dispatcher.rs b/ostp-server/src/dispatcher.rs index ec6bb17..125f607 100644 --- a/ostp-server/src/dispatcher.rs +++ b/ostp-server/src/dispatcher.rs @@ -346,7 +346,7 @@ impl Dispatcher { } if !self.replay_cache.contains_key(&payload.to_vec()) { - if self.replay_cache.len() >= 100_000 { + if self.replay_cache.len() >= 50_000 { tracing::warn!("Replay cache full (100000 entries), rejecting handshake from {}", peer); return Ok(DispatchOutcome::Unauthorized); } @@ -426,7 +426,7 @@ impl Dispatcher { let mut frames = Vec::new(); let mut expired = Vec::new(); let now = std::time::Instant::now(); - let timeout_dur = std::time::Duration::from_secs(300); // 5 minutes session timeout + let timeout_dur = std::time::Duration::from_secs(600); // 10 minute session timeout (mobile NAT can be up to 5-10min) // Gather expired or invalid sessions for (&sid, peer_state) in &self.peer_machines { diff --git a/ostp-server/src/relay.rs b/ostp-server/src/relay.rs index 12bcbea..c7066db 100644 --- a/ostp-server/src/relay.rs +++ b/ostp-server/src/relay.rs @@ -30,38 +30,8 @@ pub async fn handle_relay_message( ) -> Result<()> { match RelayMessage::decode(&payload)? { RelayMessage::Connect(target) => { - // Intercept DNS queries directed at the TUN gateway if our internal DNS is enabled - let is_internal_dns = { - target == "10.1.0.1:53" && dns_server.config.read().await.enabled - }; - - if is_internal_dns { - let client_ip = peer_addr.ip(); - let dns_srv = dns_server.clone(); - let stream_tx_dns = stream_tx.clone(); - let (cancel_tx, _) = mpsc::channel::<()>(1); - - let (dns_query_tx, mut dns_query_rx) = mpsc::unbounded_channel::(); - - tokio::spawn(async move { - if let Some(query_bytes) = dns_query_rx.recv().await { - if let Some(resp_bytes) = dns_srv.resolve(&query_bytes, client_ip).await { - let _ = stream_tx_dns.send((session_id, stream_id, resp_bytes)); - } - } - let _ = stream_tx_dns.send((session_id, stream_id, Vec::new())); - }); - - remotes.insert((session_id, stream_id), RemoteState { - data_tx: dns_query_tx, - udp_tx: None, - cancel_tx, - is_dns: true, - }); - - send_relay_to_stream(session_id, stream_id, RelayMessage::ConnectOk, dispatcher, socket, ui_event_tx, tcp_map).await?; - return Ok(()); - } + // DNS interception disabled for stability + let is_internal_dns = false; let mut connect_target = target.clone(); if connect_target.starts_with("10.1.0.1:") { @@ -156,18 +126,11 @@ pub async fn handle_relay_message( let client_ip = peer_addr.ip(); tokio::spawn(async move { while let Some((target, data)) = udp_rx.recv().await { - let is_internal_dns = target == "10.1.0.1:53" && dns_srv.config.read().await.enabled; - if is_internal_dns { - if let Some(resp_bytes) = dns_srv.resolve(&data, client_ip).await { - let _ = udp_reply_clone_dns.send((session_id, stream_id, target, resp_bytes)); - } - } else { - let mut forward_target = target.clone(); - if forward_target.starts_with("10.1.0.1:") { - forward_target = forward_target.replace("10.1.0.1:", "127.0.0.1:"); - } - let _ = tx_sock.send_to(&data, &forward_target).await; + let mut forward_target = target.clone(); + if forward_target.starts_with("10.1.0.1:") { + forward_target = forward_target.replace("10.1.0.1:", "127.0.0.1:"); } + let _ = tx_sock.send_to(&data, &forward_target).await; } }); @@ -182,7 +145,17 @@ pub async fn handle_relay_message( res = rx_sock.recv_from(&mut buf) => { match res { Ok((len, addr)) => { - let _ = udp_reply_clone.send((session_id, stream_id, addr.to_string(), buf[..len].to_vec())); + let clean_addr = match addr { + std::net::SocketAddr::V6(v6) => { + if let Some(v4) = v6.ip().to_ipv4() { + std::net::SocketAddr::new(std::net::IpAddr::V4(v4), v6.port()) + } else { + addr + } + } + _ => addr, + }; + let _ = udp_reply_clone.send((session_id, stream_id, clean_addr.to_string(), buf[..len].to_vec())); } Err(_) => break, } diff --git a/scripts/install.sh b/scripts/install.sh index 47b548a..c6c7595 100644 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -46,10 +46,7 @@ migrate_legacy() { cp "$old_dir/ostp" "$INSTALL_DIR/ostp" fi - # Migrate tun2socks if present - if [ -f "$old_dir/tun2socks" ] && [ ! -f "$INSTALL_DIR/tun2socks" ]; then - cp "$old_dir/tun2socks" "$INSTALL_DIR/tun2socks" - fi + echo "[migrate] Legacy files preserved at $old_dir (remove manually if no longer needed)" }