Skip to content

Commit

Permalink
test(nameservers): add unit test for resolv.conf parser
Browse files Browse the repository at this point in the history
  • Loading branch information
flrgh committed Dec 20, 2023
1 parent ed534c9 commit 48681d7
Show file tree
Hide file tree
Showing 2 changed files with 144 additions and 36 deletions.
6 changes: 3 additions & 3 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -384,11 +384,11 @@ pub struct UserArgs {
pub(crate) arg_0: String,
}

fn default_nameservers() -> Vec<IpAddr> {
fn discover_system_nameservers() -> Vec<IpAddr> {
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());
}
Expand Down Expand Up @@ -591,7 +591,7 @@ pub fn init(args: Vec<String>) -> Result<Action, ArgError> {
}

if user.nameservers.is_empty() {
user.nameservers.extend(default_nameservers());
user.nameservers.extend(discover_system_nameservers());
}

Ok(Action::Main(Box::new(user)))
Expand Down
174 changes: 141 additions & 33 deletions src/util.rs
Original file line number Diff line number Diff line change
@@ -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<IpAddr> {
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<T: Read>(buf: T) -> Vec<IpAddr> {
BufReader::new(buf)
.lines()
.map_while(Result::ok)
.filter_map(|line| {
let line = line.trim();

if let Ok(addr) = ns.parse::<IpAddr>() {
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::<IpAddr>() {
return Some(addr);
}

None
})
.take(11) // resty-cli stops adding nameservers after it has > 10
.collect()
}

pub(crate) fn try_parse_resolv_conf() -> Vec<IpAddr> {
let Ok(file) = fs::File::open("/etc/resolv.conf") else {
return vec![];
};

impl_try_parse_resolv_conf(file)
}

pub(crate) fn split_shell_args<T: AsRef<str> + ?Sized>(s: &T) -> Vec<String> {
Expand Down Expand Up @@ -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::<IpAddr>().unwrap()],
impl_try_parse_resolv_conf(input.as_bytes())
);

let input = "nameserver 127.0.0.53";

assert_eq!(
vec!["127.0.0.53".parse::<IpAddr>().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::<IpAddr>().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::<IpAddr>().unwrap(),
"127.0.0.2".parse::<IpAddr>().unwrap(),
"127.0.0.3".parse::<IpAddr>().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::<IpAddr>().unwrap(),
"127.0.0.2".parse::<IpAddr>().unwrap(),
"127.0.0.3".parse::<IpAddr>().unwrap(),
"127.0.0.4".parse::<IpAddr>().unwrap(),
"127.0.0.5".parse::<IpAddr>().unwrap(),
"127.0.0.6".parse::<IpAddr>().unwrap(),
"127.0.0.7".parse::<IpAddr>().unwrap(),
"127.0.0.8".parse::<IpAddr>().unwrap(),
"127.0.0.9".parse::<IpAddr>().unwrap(),
"127.0.0.10".parse::<IpAddr>().unwrap(),
"127.0.0.11".parse::<IpAddr>().unwrap(),
],
impl_try_parse_resolv_conf(input.as_bytes())
);
}
}

0 comments on commit 48681d7

Please sign in to comment.