From 2c46750687118d62430bc1473a13eda3b284a9f9 Mon Sep 17 00:00:00 2001 From: ospab Date: Thu, 28 May 2026 12:30:06 +0300 Subject: [PATCH] fix: remove DNS interception on server, fix TUN routing on Windows and Linux MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ostp-server/relay.rs: remove DNS port 53 interception — DNS queries now pass through to the actual DNS server as regular TCP connections - ostp-client/native_handler.rs (Windows): add explicit gateway/32 route via real interface BEFORE setting default route via TUN to prevent loop - ostp-client/native_handler.rs (Linux): properly detect real gateway and add default route via TUN with metric 10 after server IP exclusion - Remove redundant extra DNS host routes from Windows setup script --- ostp-client/src/tunnel/native_handler.rs | 43 ++++++++++++++++----- ostp-client/test_udp.rs | 1 + ostp-server/src/lib.rs | 1 - ostp-server/src/relay.rs | 48 +----------------------- 4 files changed, 35 insertions(+), 58 deletions(-) create mode 100644 ostp-client/test_udp.rs diff --git a/ostp-client/src/tunnel/native_handler.rs b/ostp-client/src/tunnel/native_handler.rs index b04a12f..1e378e4 100644 --- a/ostp-client/src/tunnel/native_handler.rs +++ b/ostp-client/src/tunnel/native_handler.rs @@ -54,18 +54,15 @@ pub async fn run_native_tunnel( 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\ + $route = Get-NetRoute -DestinationPrefix '0.0.0.0/0' | Where-Object {{ $_.InterfaceAlias -notmatch 'ostp' -and $_.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\ + # Route server IP and gateway directly via real interface (bypass TUN)\n\ New-NetRoute -DestinationPrefix \"$remote_ip/32\" -NextHop $gw -InterfaceIndex $ifIndex -RouteMetric 1 -ErrorAction SilentlyContinue\n\ - $dns_ips = Get-DnsClientServerAddress -InterfaceIndex $ifIndex | Select-Object -ExpandProperty ServerAddresses\n\ - foreach ($dns in $dns_ips) {{\n\ - if ($dns -match '^\\d+\\.\\d+\\.\\d+\\.\\d+$') {{\n\ - New-NetRoute -DestinationPrefix \"$dns/32\" -NextHop $gw -InterfaceIndex $ifIndex -RouteMetric 1 -ErrorAction SilentlyContinue\n\ - }}\n\ + if ($gw -ne '0.0.0.0') {{\n\ + New-NetRoute -DestinationPrefix \"$gw/32\" -NextHop '0.0.0.0' -InterfaceIndex $ifIndex -RouteMetric 1 -ErrorAction SilentlyContinue\n\ }}\n\ - New-NetRoute -DestinationPrefix \"1.1.1.1/32\" -NextHop $gw -InterfaceIndex $ifIndex -RouteMetric 1 -ErrorAction SilentlyContinue\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\ @@ -91,8 +88,28 @@ pub async fn run_native_tunnel( #[cfg(target_os = "linux")] { - // Add default route to tun, bypassing server IP - let _ = Command::new("ip").args(["route", "add", &format!("{}/32", server_ip_str), "via", "10.1.0.1"]).output(); + // Get real gateway before routing through TUN + 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| { + // "default via 192.168.1.1 dev eth0" -> "192.168.1.1" + 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()) + }); + + // Add exclusion route for server IP via real gateway (bypass TUN) + if let (Some(ref gw), Some(ref dev)) = (&real_gw, &real_dev) { + let _ = Command::new("ip").args(["route", "add", &format!("{}/32", server_ip_str), "via", gw, "dev", dev]).output(); + } + + // Add default route through TUN (lower metric to take priority) + let _ = Command::new("ip").args(["route", "add", "default", "via", "10.1.0.1", "dev", "ostp_tun", "metric", "10"]).output(); } let (stack, tcp_runner, udp_socket, tcp_listener) = StackBuilder::default() @@ -236,7 +253,6 @@ pub async fn run_native_tunnel( 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=\"ostp_tun\" source=dhcp 2>$null\n", server_ip_str @@ -247,6 +263,13 @@ pub async fn run_native_tunnel( .output(); } + #[cfg(target_os = "linux")] + { + // Remove default route via TUN and server exclusion route + 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(); + } + Ok(()) } diff --git a/ostp-client/test_udp.rs b/ostp-client/test_udp.rs new file mode 100644 index 0000000..657e493 --- /dev/null +++ b/ostp-client/test_udp.rs @@ -0,0 +1 @@ +fn main() { let x: () = netstack_smoltcp::StackBuilder::default().build().unwrap(); } diff --git a/ostp-server/src/lib.rs b/ostp-server/src/lib.rs index 2c57726..13bae98 100644 --- a/ostp-server/src/lib.rs +++ b/ostp-server/src/lib.rs @@ -554,7 +554,6 @@ async fn run_server_loop( stream_tx.clone(), connect_tx.clone(), outbound.clone(), - dns_server.clone(), debug, ).await?; } diff --git a/ostp-server/src/relay.rs b/ostp-server/src/relay.rs index 8d33841..d61c3bd 100644 --- a/ostp-server/src/relay.rs +++ b/ostp-server/src/relay.rs @@ -8,7 +8,6 @@ use tokio::net::UdpSocket; use tokio::sync::mpsc; use crate::dispatcher::Dispatcher; -use crate::dns::DnsServer; use crate::outbound::{self, OutboundConfig}; use crate::{RemoteState, UiEvent}; @@ -24,53 +23,11 @@ pub async fn handle_relay_message( stream_tx: mpsc::UnboundedSender<(u32, u16, Vec)>, connect_tx: mpsc::UnboundedSender<(u32, u16, String, Result<(tokio::net::tcp::OwnedWriteHalf, mpsc::Sender<()>), String>)>, outbound_cfg: Option, - dns_server: std::sync::Arc, debug: bool, ) -> Result<()> { match RelayMessage::decode(&payload)? { RelayMessage::Connect(target) => { let _ = ui_event_tx.send(UiEvent::Log(format!("Relay CONNECT start for [{session_id}:{stream_id}] -> {target}"))); - - // ── DNS Interception ──────────────────────────────────────────────── - // If client is connecting to port 53 (DNS), we handle it locally - // instead of opening a real UDP/TCP socket to the destination. - // - // Protocol flow: - // 1. Client sends Connect("8.8.8.8:53") → we reply ConnectOk - // 2. Client sends Data() → we resolve & reply Data() + Close - if is_dns_target(&target) { - 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); - - // Channel: relay.rs Data handler → DNS resolution task - let (dns_query_tx, mut dns_query_rx) = mpsc::unbounded_channel::(); - - // Spawn task that waits for the DNS query payload and resolves it - 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)); - } - } - // Always close the stream after responding - let _ = stream_tx_dns.send((session_id, stream_id, Vec::new())); - }); - - // Store as a RemoteState — Data messages will be forwarded to dns_query_tx - remotes.insert((session_id, stream_id), RemoteState { - data_tx: dns_query_tx, - cancel_tx, - is_dns: true, - }); - - // Tell the client we are ready to receive its DNS query - send_relay_to_stream(session_id, stream_id, RelayMessage::ConnectOk, dispatcher, socket, ui_event_tx).await?; - return Ok(()); - } - - // ── Normal TCP Connect ────────────────────────────────────────────── let target_clone = target.clone(); let connect_tx_clone = connect_tx.clone(); let stream_tx_clone = stream_tx.clone(); @@ -136,10 +93,7 @@ pub async fn handle_relay_message( Ok(()) } -/// Returns true if the target address is a DNS server (port 53) -fn is_dns_target(target: &str) -> bool { - target.ends_with(":53") -} + pub async fn send_relay_to_stream( session_id: u32,