sync_resolve/
resolv_conf.rs1use std::cmp::min;
4use std::fs::File;
5use std::io::{self, BufRead, BufReader};
6use std::net::{IpAddr, SocketAddr};
7use std::time::Duration;
8
9use crate::config::DnsConfig;
10use crate::hostname::get_hostname;
11
12const DNS_PORT: u16 = 53;
14
15pub const MAX_NAME_SERVERS: usize = 3;
17
18pub const DEFAULT_ATTEMPTS: u32 = 2;
20
21pub const DEFAULT_N_DOTS: u32 = 1;
23
24pub const DEFAULT_TIMEOUT: u64 = 5;
26
27pub const MAX_ATTEMPTS: u32 = 5;
29
30pub const MAX_N_DOTS: u32 = 15;
32
33pub const MAX_TIMEOUT: u64 = 30;
35
36pub const RESOLV_CONF_PATH: &str = "/etc/resolv.conf";
38
39fn default_config() -> DnsConfig {
40 DnsConfig {
41 name_servers: Vec::new(),
42 search: Vec::new(),
43
44 n_dots: DEFAULT_N_DOTS,
45 attempts: DEFAULT_ATTEMPTS,
46 timeout: Duration::from_secs(DEFAULT_TIMEOUT),
47
48 rotate: false,
49 use_inet6: false,
50 }
51}
52
53pub fn load() -> io::Result<DnsConfig> {
57 parse(BufReader::new(File::open(RESOLV_CONF_PATH)?))
58}
59
60fn parse<R: BufRead>(r: R) -> io::Result<DnsConfig> {
61 let mut cfg = default_config();
62
63 for line in r.lines() {
64 let line = line?;
65
66 if line.is_empty() || line.starts_with(|c| c == '#' || c == ';') {
67 continue;
68 }
69
70 let mut words = line.split_whitespace();
71
72 let name = match words.next() {
73 Some(name) => name,
74 None => continue,
75 };
76
77 match name {
78 "nameserver" => {
79 if let Some(ip) = words.next() {
80 if cfg.name_servers.len() < MAX_NAME_SERVERS {
81 if let Ok(ip) = ip.parse::<IpAddr>() {
82 cfg.name_servers.push(SocketAddr::new(ip, DNS_PORT))
83 }
84 }
85 }
86 }
87 "domain" => {
88 if let Some(domain) = words.next() {
89 cfg.search = vec![domain.to_owned()];
90 }
91 }
92 "search" => {
93 cfg.search = words.map(|s| s.to_owned()).collect();
94 }
95 "options" => {
96 for opt in words {
97 let (opt, value) = match opt.find(':') {
98 Some(pos) => (&opt[..pos], &opt[pos + 1..]),
99 None => (opt, ""),
100 };
101
102 match opt {
103 "ndots" => {
104 if let Ok(n) = value.parse() {
105 cfg.n_dots = min(n, MAX_N_DOTS);
106 }
107 }
108 "timeout" => {
109 if let Ok(n) = value.parse() {
110 cfg.timeout = Duration::from_secs(min(n, MAX_TIMEOUT));
111 }
112 }
113 "attempts" => {
114 if let Ok(n) = value.parse() {
115 cfg.attempts = min(n, MAX_ATTEMPTS);
116 }
117 }
118 "rotate" => cfg.rotate = true,
119 "inet6" => cfg.use_inet6 = true,
120 _ => (),
121 }
122 }
123 }
124 _ => (),
125 }
126 }
127
128 if cfg.name_servers.is_empty() {
129 return Err(io::Error::new(
130 io::ErrorKind::Other,
131 "no nameserver directives in resolv.conf",
132 ));
133 }
134
135 if cfg.search.is_empty() {
136 let host = get_hostname()?;
137
138 if let Some(pos) = host.find('.') {
139 cfg.search = vec![host[pos + 1..].to_owned()];
140 }
141 }
142
143 Ok(cfg)
144}
145
146#[cfg(test)]
147mod test {
148 use super::{parse, MAX_TIMEOUT};
149 use std::io::Cursor;
150
151 const TEST_CONFIG: &'static str = "\
152 nameserver 127.0.0.1
153 search foo.com bar.com
154 options timeout:99 ndots:2 rotate";
155
156 #[test]
157 fn test_parse() {
158 let r = Cursor::new(TEST_CONFIG.as_bytes());
159 let cfg = parse(r).unwrap();
160
161 assert_eq!(cfg.name_servers, ["127.0.0.1:53".parse().unwrap()]);
162 assert_eq!(cfg.search, ["foo.com", "bar.com"]);
163 assert_eq!(cfg.timeout.as_secs(), MAX_TIMEOUT);
164 assert_eq!(cfg.n_dots, 2);
165 assert_eq!(cfg.rotate, true);
166 }
167}