From 6789c321bc6938c7c5b0254720a5e548498e1243 Mon Sep 17 00:00:00 2001 From: Razvan Dimescu Date: Thu, 16 Apr 2026 18:35:09 +0300 Subject: [PATCH] fix(windows): defer DNS redirect until port 53 is free Probe port 53 after disabling Dnscache instead of assuming reboot is needed. Skip DNS redirect when port is blocked (service does it on first boot). Fix readiness probe: TCP connect to API port instead of broken UDP send_to that always succeeded. --- src/system_dns.rs | 89 +++++++++++++++++++++++++++--------------- src/windows_service.rs | 17 ++++++++ 2 files changed, 75 insertions(+), 31 deletions(-) diff --git a/src/system_dns.rs b/src/system_dns.rs index c4279cd..35490ae 100644 --- a/src/system_dns.rs +++ b/src/system_dns.rs @@ -572,7 +572,7 @@ fn windows_backup_path() -> std::path::PathBuf { #[cfg(windows)] fn disable_dnscache() -> Result { - // Check if Dnscache is running (it holds port 53 at kernel level) + // Check if Dnscache is running (it can hold port 53) let output = std::process::Command::new("sc") .args(["query", "Dnscache"]) .output() @@ -603,8 +603,16 @@ fn disable_dnscache() -> Result { return Err("failed to disable Dnscache via registry (run as Administrator?)".into()); } - eprintln!(" Dnscache disabled. A reboot is required to free port 53."); - Ok(true) + // Dnscache is disabled for next boot. Check whether port 53 is + // actually blocked right now — on many Windows configurations + // Dnscache doesn't bind port 53 even while running. + let port_blocked = std::net::UdpSocket::bind("127.0.0.1:53").is_err(); + if port_blocked { + eprintln!(" Dnscache disabled. A reboot is required to free port 53."); + } else { + eprintln!(" Dnscache disabled. Port 53 is free."); + } + Ok(port_blocked) } #[cfg(windows)] @@ -671,31 +679,6 @@ fn install_windows() -> Result<(), String> { std::fs::write(&path, json).map_err(|e| format!("failed to write backup: {}", e))?; } - for name in interfaces.keys() { - let status = std::process::Command::new("netsh") - .args([ - "interface", - "ipv4", - "set", - "dnsservers", - name, - "static", - "127.0.0.1", - "primary", - ]) - .status() - .map_err(|e| format!("failed to set DNS for {}: {}", name, e))?; - - if status.success() { - eprintln!(" set DNS for \"{}\" -> 127.0.0.1", name); - } else { - eprintln!( - " warning: failed to set DNS for \"{}\" (run as Administrator?)", - name - ); - } - } - let needs_reboot = disable_dnscache()?; // On re-install, stop the running service first so the binary can be @@ -710,9 +693,14 @@ fn install_windows() -> Result<(), String> { let service_exe = install_service_binary()?; register_service_scm(&service_exe)?; - // If no reboot is pending (Dnscache wasn't running, port 53 free), - // start the service immediately. Otherwise it'll launch on next boot. - if !needs_reboot { + if needs_reboot { + // Dnscache still holds port 53 until reboot. Do NOT redirect DNS + // yet — nothing is listening on 127.0.0.1:53, so redirecting now + // would kill DNS. The service will call redirect_dns_to_localhost() + // on its first startup after reboot. + } else { + redirect_dns_with_interfaces(&interfaces)?; + match start_service_scm() { Ok(_) => eprintln!(" Service started."), Err(e) => eprintln!( @@ -756,6 +744,45 @@ fn run_sc(args: &[&str]) -> Result { Ok(out) } +/// Point all active network interfaces at 127.0.0.1 so Numa handles DNS. +/// Called from the service on first boot after a reboot that freed Dnscache. +#[cfg(windows)] +pub fn redirect_dns_to_localhost() -> Result<(), String> { + let interfaces = get_windows_interfaces()?; + redirect_dns_with_interfaces(&interfaces) +} + +#[cfg(windows)] +fn redirect_dns_with_interfaces( + interfaces: &std::collections::HashMap, +) -> Result<(), String> { + for name in interfaces.keys() { + let status = std::process::Command::new("netsh") + .args([ + "interface", + "ipv4", + "set", + "dnsservers", + name, + "static", + "127.0.0.1", + "primary", + ]) + .status() + .map_err(|e| format!("failed to set DNS for {}: {}", name, e))?; + + if status.success() { + eprintln!(" set DNS for \"{}\" -> 127.0.0.1", name); + } else { + eprintln!( + " warning: failed to set DNS for \"{}\" (run as Administrator?)", + name + ); + } + } + Ok(()) +} + /// Copy the currently-running binary to the service install location. SCM /// keeps a handle to this path, so it must be stable across user sessions. #[cfg(windows)] diff --git a/src/windows_service.rs b/src/windows_service.rs index a1403d7..a363359 100644 --- a/src/windows_service.rs +++ b/src/windows_service.rs @@ -83,6 +83,23 @@ fn run_service() -> windows_service::Result<()> { let _ = server_done_tx.send(()); }); + // Wait for the API to be ready, then ensure DNS points at localhost. + // On first boot after install (Dnscache was disabled, reboot freed + // port 53), the installer deferred the DNS redirect — do it now. + let api_up = (0..20).any(|i| { + if i > 0 { + std::thread::sleep(Duration::from_millis(500)); + } + std::net::TcpStream::connect(("127.0.0.1", crate::config::DEFAULT_API_PORT)).is_ok() + }); + if api_up { + if let Err(e) = crate::system_dns::redirect_dns_to_localhost() { + log::warn!("could not redirect DNS to localhost: {}", e); + } + } else { + log::error!("numa API did not start within 10s — DNS not redirected"); + } + // Wait for either SCM stop or server termination. loop { if shutdown_rx.recv_timeout(Duration::from_millis(500)).is_ok() {