use std::cmp::min;
use std::fs::File;
use std::io::{self, BufRead, BufReader};
use std::net::{IpAddr, SocketAddr};
use std::time::Duration;
use crate::config::DnsConfig;
use crate::hostname::get_hostname;
const DNS_PORT: u16 = 53;
pub const MAX_NAME_SERVERS: usize = 3;
pub const DEFAULT_ATTEMPTS: u32 = 2;
pub const DEFAULT_N_DOTS: u32 = 1;
pub const DEFAULT_TIMEOUT: u64 = 5;
pub const MAX_ATTEMPTS: u32 = 5;
pub const MAX_N_DOTS: u32 = 15;
pub const MAX_TIMEOUT: u64 = 30;
pub const RESOLV_CONF_PATH: &str = "/etc/resolv.conf";
fn default_config() -> DnsConfig {
DnsConfig {
name_servers: Vec::new(),
search: Vec::new(),
n_dots: DEFAULT_N_DOTS,
attempts: DEFAULT_ATTEMPTS,
timeout: Duration::from_secs(DEFAULT_TIMEOUT),
rotate: false,
use_inet6: false,
}
}
pub fn load() -> io::Result<DnsConfig> {
parse(BufReader::new(File::open(RESOLV_CONF_PATH)?))
}
fn parse<R: BufRead>(r: R) -> io::Result<DnsConfig> {
let mut cfg = default_config();
for line in r.lines() {
let line = line?;
if line.is_empty() || line.starts_with(|c| c == '#' || c == ';') {
continue;
}
let mut words = line.split_whitespace();
let name = match words.next() {
Some(name) => name,
None => continue,
};
match name {
"nameserver" => {
if let Some(ip) = words.next() {
if cfg.name_servers.len() < MAX_NAME_SERVERS {
if let Ok(ip) = ip.parse::<IpAddr>() {
cfg.name_servers.push(SocketAddr::new(ip, DNS_PORT))
}
}
}
}
"domain" => {
if let Some(domain) = words.next() {
cfg.search = vec![domain.to_owned()];
}
}
"search" => {
cfg.search = words.map(|s| s.to_owned()).collect();
}
"options" => {
for opt in words {
let (opt, value) = match opt.find(':') {
Some(pos) => (&opt[..pos], &opt[pos + 1..]),
None => (opt, ""),
};
match opt {
"ndots" => {
if let Ok(n) = value.parse() {
cfg.n_dots = min(n, MAX_N_DOTS);
}
}
"timeout" => {
if let Ok(n) = value.parse() {
cfg.timeout = Duration::from_secs(min(n, MAX_TIMEOUT));
}
}
"attempts" => {
if let Ok(n) = value.parse() {
cfg.attempts = min(n, MAX_ATTEMPTS);
}
}
"rotate" => cfg.rotate = true,
"inet6" => cfg.use_inet6 = true,
_ => (),
}
}
}
_ => (),
}
}
if cfg.name_servers.is_empty() {
return Err(io::Error::new(
io::ErrorKind::Other,
"no nameserver directives in resolv.conf",
));
}
if cfg.search.is_empty() {
let host = get_hostname()?;
if let Some(pos) = host.find('.') {
cfg.search = vec![host[pos + 1..].to_owned()];
}
}
Ok(cfg)
}
#[cfg(test)]
mod test {
use super::{parse, MAX_TIMEOUT};
use std::io::Cursor;
const TEST_CONFIG: &'static str = "\
nameserver 127.0.0.1
search foo.com bar.com
options timeout:99 ndots:2 rotate";
#[test]
fn test_parse() {
let r = Cursor::new(TEST_CONFIG.as_bytes());
let cfg = parse(r).unwrap();
assert_eq!(cfg.name_servers, ["127.0.0.1:53".parse().unwrap()]);
assert_eq!(cfg.search, ["foo.com", "bar.com"]);
assert_eq!(cfg.timeout.as_secs(), MAX_TIMEOUT);
assert_eq!(cfg.n_dots, 2);
assert_eq!(cfg.rotate, true);
}
}