feat: forward-by-default, auto recursive mode, Linux install fixes #27

Merged
razvandimescu merged 7 commits from feat/auto-recursive-install-fixes into main 2026-04-01 13:49:16 +08:00
Showing only changes of commit 58ac135654 - Show all commits

View File

@@ -30,10 +30,7 @@ pub fn discover_system_dns() -> SystemDnsInfo {
} }
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
{ {
SystemDnsInfo { discover_linux()
default_upstream: detect_upstream_linux_or_backup(),
forwarding_rules: Vec::new(),
}
} }
#[cfg(windows)] #[cfg(windows)]
{ {
@@ -166,8 +163,81 @@ fn make_rule(domain: &str, nameserver: &str) -> Option<ForwardingRule> {
}) })
} }
/// Detect upstream from /etc/resolv.conf, falling back to backup file if resolv.conf #[cfg(target_os = "linux")]
/// only has loopback (meaning numa install already ran). fn discover_linux() -> SystemDnsInfo {
let upstream = detect_upstream_linux_or_backup();
// Parse search domains and create forwarding rules to the original nameserver.
// On cloud VMs (AWS/GCP), internal domains need to reach the VPC resolver.
let forwarding_rules = detect_search_domain_rules();
if !forwarding_rules.is_empty() {
info!(
"detected {} search domain forwarding rules",
forwarding_rules.len()
);
}
SystemDnsInfo {
default_upstream: upstream,
forwarding_rules,
}
}
/// Parse search domains from resolv.conf and create forwarding rules to the
/// original nameserver or the AWS VPC resolver (169.254.169.253).
#[cfg(target_os = "linux")]
fn detect_search_domain_rules() -> Vec<ForwardingRule> {
let mut rules = Vec::new();
// Find the original nameserver to forward internal domains to
let forwarder = find_original_nameserver().unwrap_or_else(|| "169.254.169.253".to_string());
// Parse search domains from resolv.conf
for path in &["/etc/resolv.conf"] {
if let Ok(text) = std::fs::read_to_string(path) {
for line in text.lines() {
let line = line.trim();
if line.starts_with("search") || line.starts_with("domain") {
for domain in line.split_whitespace().skip(1) {
if let Some(rule) = make_rule(domain, &forwarder) {
info!("forwarding .{} to {}", domain, forwarder);
rules.push(rule);
}
}
}
}
}
}
rules
}
/// Find the original (non-loopback) nameserver from resolv.conf or systemd-resolved config.
#[cfg(target_os = "linux")]
fn find_original_nameserver() -> Option<String> {
// Try resolv.conf for a real nameserver
if let Some(ns) = read_upstream_from_file("/etc/resolv.conf") {
return Some(ns);
}
// Try systemd-resolved's actual upstream
if let Ok(output) = std::process::Command::new("resolvectl")
.args(["status", "--no-pager"])
.output()
{
let text = String::from_utf8_lossy(&output.stdout);
for line in text.lines() {
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) && !ip.is_empty() {
return Some(ip.to_string());
}
}
}
}
}
None
}
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
fn detect_upstream_linux_or_backup() -> Option<String> { fn detect_upstream_linux_or_backup() -> Option<String> {
// Try /etc/resolv.conf first // Try /etc/resolv.conf first