diff --git a/src/cli.rs b/src/cli.rs index 537f661..42ad55a 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -384,11 +384,11 @@ pub struct UserArgs { pub(crate) arg_0: String, } -fn default_nameservers() -> Vec { +fn discover_system_nameservers() -> Vec { let mut ns = try_parse_resolv_conf(); + // fall back to google dns for compatibility with resty-cli if ns.is_empty() { - // fall back to google dns for compatibility with resty-cli ns.push("8.8.8.8".parse().unwrap()); ns.push("8.8.4.4".parse().unwrap()); } @@ -591,7 +591,7 @@ pub fn init(args: Vec) -> Result { } if user.nameservers.is_empty() { - user.nameservers.extend(default_nameservers()); + user.nameservers.extend(discover_system_nameservers()); } Ok(Action::Main(Box::new(user))) diff --git a/src/util.rs b/src/util.rs index d29c673..dbebc1e 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,41 +1,52 @@ use crate::types::IpAddr; use std::fs; -use std::io::{BufRead, BufReader}; +use std::io::{BufRead, BufReader, Read}; -pub(crate) fn try_parse_resolv_conf() -> Vec { - let mut nameservers = vec![]; - - if let Ok(file) = fs::File::open("/etc/resolv.conf") { - BufReader::new(file) - .lines() - .map_while(Result::ok) - .for_each(|line| { - let line = line.trim(); - let mut parts = line.split_whitespace(); - - let predicate = match parts.next() { - Some("nameserver") => parts.next(), - _ => return, - }; - - let ns = match predicate { - Some(s) => s, - // not enough parts - _ => return, - }; - - // too many parts - if parts.next().is_some() { - return; - } +fn impl_try_parse_resolv_conf(buf: T) -> Vec { + BufReader::new(buf) + .lines() + .map_while(Result::ok) + .filter_map(|line| { + let line = line.trim(); - if let Ok(addr) = ns.parse::() { - nameservers.push(addr); - } - }); - } + if line.is_empty() || line.starts_with('#') { + return None; + } + + let mut parts = line.split_whitespace(); - nameservers + let predicate = match parts.next() { + Some("nameserver") => parts.next(), + _ => return None, + }; + + let ns = match predicate { + Some(s) => s, + // not enough parts + _ => return None, + }; + + // too many parts + if parts.next().is_some() { + return None; + } + + if let Ok(addr) = ns.parse::() { + return Some(addr); + } + + None + }) + .take(11) // resty-cli stops adding nameservers after it has > 10 + .collect() +} + +pub(crate) fn try_parse_resolv_conf() -> Vec { + let Ok(file) = fs::File::open("/etc/resolv.conf") else { + return vec![]; + }; + + impl_try_parse_resolv_conf(file) } pub(crate) fn split_shell_args + ?Sized>(s: &T) -> Vec { @@ -163,4 +174,101 @@ mod tests { assert_eq!(joined, rejoined); assert_eq!(args, resplit); } + + #[test] + fn test_impl_try_parse_resolv_conf() { + let input = r##"# This is /run/systemd/resolve/stub-resolv.conf managed by man:systemd-resolved(8). +# Do not edit. +# +# This file might be symlinked as /etc/resolv.conf. If you're looking at +# /etc/resolv.conf and seeing this text, you have followed the symlink. +# +# This is a dynamic resolv.conf file for connecting local clients to the +# internal DNS stub resolver of systemd-resolved. This file lists all +# configured search domains. +# +# Run "resolvectl status" to see details about the uplink DNS servers +# currently in use. +# +# Third party programs should typically not access this file directly, but only +# through the symlink at /etc/resolv.conf. To manage man:resolv.conf(5) in a +# different way, replace this symlink by a static file or a different symlink. +# +# See man:systemd-resolved.service(8) for details about the supported modes of +# operation for /etc/resolv.conf. + +nameserver 127.0.0.53 +options edns0 trust-ad +search example.com"##; + + assert_eq!( + vec!["127.0.0.53".parse::().unwrap()], + impl_try_parse_resolv_conf(input.as_bytes()) + ); + + let input = "nameserver 127.0.0.53"; + + assert_eq!( + vec!["127.0.0.53".parse::().unwrap()], + impl_try_parse_resolv_conf(input.as_bytes()) + ); + + let input = r##" +_nameserver 127.0.0.1 +nameserver 127.0.0.2 +nameserver 127.0.0.3 oops extra stuff +"##; + + assert_eq!( + vec!["127.0.0.2".parse::().unwrap()], + impl_try_parse_resolv_conf(input.as_bytes()) + ); + + let input = r##" +nameserver 127.0.0.1 +nameserver 127.0.0.2 +nameserver 127.0.0.3 +"##; + + assert_eq!( + vec![ + "127.0.0.1".parse::().unwrap(), + "127.0.0.2".parse::().unwrap(), + "127.0.0.3".parse::().unwrap(), + ], + impl_try_parse_resolv_conf(input.as_bytes()) + ); + + let input = r##" +nameserver 127.0.0.1 +nameserver 127.0.0.2 +nameserver 127.0.0.3 +nameserver 127.0.0.4 +nameserver 127.0.0.5 +nameserver 127.0.0.6 +nameserver 127.0.0.7 +nameserver 127.0.0.8 +nameserver 127.0.0.9 +nameserver 127.0.0.10 +nameserver 127.0.0.11 +nameserver 127.0.0.12 +"##; + + assert_eq!( + vec![ + "127.0.0.1".parse::().unwrap(), + "127.0.0.2".parse::().unwrap(), + "127.0.0.3".parse::().unwrap(), + "127.0.0.4".parse::().unwrap(), + "127.0.0.5".parse::().unwrap(), + "127.0.0.6".parse::().unwrap(), + "127.0.0.7".parse::().unwrap(), + "127.0.0.8".parse::().unwrap(), + "127.0.0.9".parse::().unwrap(), + "127.0.0.10".parse::().unwrap(), + "127.0.0.11".parse::().unwrap(), + ], + impl_try_parse_resolv_conf(input.as_bytes()) + ); + } }