diff --git a/README.md b/README.md index c58b413..fe71bfe 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ sudo numa # port 53 requires root Open the dashboard: **http://numa.numa** (or `http://localhost:5380`) -Set as system DNS: `sudo numa install && sudo numa service start` +Set as system DNS: `sudo numa install` ## Local Services diff --git a/src/config.rs b/src/config.rs index 3c287ca..34990f8 100644 --- a/src/config.rs +++ b/src/config.rs @@ -59,8 +59,10 @@ fn default_bind_addr() -> String { "0.0.0.0:53".to_string() } +pub const DEFAULT_API_PORT: u16 = 5380; + fn default_api_port() -> u16 { - 5380 + DEFAULT_API_PORT } #[derive(Deserialize, Default, PartialEq, Eq, Clone, Copy)] diff --git a/src/recursive.rs b/src/recursive.rs index aed93e7..7801bec 100644 --- a/src/recursive.rs +++ b/src/recursive.rs @@ -65,18 +65,19 @@ pub async fn probe_udp(root_hints: &[SocketAddr]) { } } -/// Probe whether recursive resolution works by querying a root server. +/// Probe whether recursive resolution works by querying root servers. +/// Tries up to 3 hints before declaring failure. pub async fn probe_recursive(root_hints: &[SocketAddr]) -> bool { - let hint = match root_hints.first() { - Some(h) => *h, - None => return false, - }; let mut probe = DnsPacket::query(next_id(), ".", QueryType::NS); probe.header.recursion_desired = false; - match forward_udp(&probe, hint, Duration::from_secs(3)).await { - Ok(resp) => !resp.answers.is_empty() || !resp.authorities.is_empty(), - Err(_) => false, + for hint in root_hints.iter().take(3) { + if let Ok(resp) = forward_udp(&probe, *hint, Duration::from_secs(3)).await { + if !resp.answers.is_empty() || !resp.authorities.is_empty() { + return true; + } + } } + false } pub async fn prime_tld_cache( diff --git a/src/system_dns.rs b/src/system_dns.rs index eb2a2c1..f1c1426 100644 --- a/src/system_dns.rs +++ b/src/system_dns.rs @@ -255,7 +255,7 @@ fn resolvectl_dns_server() -> Option { if line.contains("DNS Servers") || line.contains("Current DNS Server") { if let Some(ip) = line.split(':').next_back() { let ip = ip.trim(); - if !is_loopback_or_stub(ip) { + if ip.parse::().is_ok() && !is_loopback_or_stub(ip) { return Some(ip.to_string()); } } @@ -632,12 +632,7 @@ fn install_service_macos() -> Result<(), String> { std::fs::write(PLIST_DEST, plist) .map_err(|e| format!("failed to write {}: {}", PLIST_DEST, e))?; - // Configure system DNS before starting service - if let Err(e) = install_macos() { - eprintln!(" warning: failed to configure system DNS: {}", e); - } - - // Load the service + // Load the service first so numa is listening before DNS redirect let status = std::process::Command::new("launchctl") .args(["load", "-w", PLIST_DEST]) .status() @@ -647,6 +642,27 @@ fn install_service_macos() -> Result<(), String> { return Err("launchctl load failed".to_string()); } + // Wait for numa to be ready before redirecting DNS + let api_up = (0..10).any(|i| { + if i > 0 { + std::thread::sleep(std::time::Duration::from_millis(500)); + } + std::net::TcpStream::connect(("127.0.0.1", crate::config::DEFAULT_API_PORT)).is_ok() + }); + if !api_up { + // Service failed to start — don't redirect DNS to a dead endpoint + let _ = std::process::Command::new("launchctl") + .args(["unload", PLIST_DEST]) + .status(); + return Err( + "numa service did not start (port 53 may be in use). Service unloaded.".to_string(), + ); + } + + if let Err(e) = install_macos() { + eprintln!(" warning: failed to configure system DNS: {}", e); + } + eprintln!(" Service installed and started."); eprintln!(" Numa will auto-start on boot and restart if killed."); eprintln!(" Logs: /usr/local/var/log/numa.log");