hickory_resolver/system_conf/
unix.rs1use std::fs;
15use std::io;
16use std::path::Path;
17use std::str::FromStr;
18use std::time::Duration;
19
20use crate::config::{NameServerConfig, ResolverConfig, ResolverOpts};
21use crate::net::NetError;
22use crate::proto::rr::Name;
23
24pub fn read_system_conf() -> Result<(ResolverConfig, ResolverOpts), NetError> {
25 read_resolv_conf("/etc/resolv.conf")
26}
27
28fn read_resolv_conf<P: AsRef<Path>>(path: P) -> Result<(ResolverConfig, ResolverOpts), NetError> {
29 parse_resolv_conf(fs::read(path)?)
30}
31
32pub fn parse_resolv_conf<T: AsRef<[u8]>>(
33 data: T,
34) -> Result<(ResolverConfig, ResolverOpts), NetError> {
35 let parsed_conf = resolv_conf::Config::parse(&data)
36 .map_err(|e| io::Error::other(format!("Error parsing resolv.conf: {e}")))?;
37 into_resolver_config(parsed_conf)
38}
39
40fn into_resolver_config(
42 parsed_config: resolv_conf::Config,
43) -> Result<(ResolverConfig, ResolverOpts), NetError> {
44 let domain = if let Some(domain) = parsed_config.get_system_domain() {
45 Name::from_str(domain.as_str()).ok()
50 } else {
51 None
52 };
53
54 let nameservers = parsed_config
56 .nameservers
57 .iter()
58 .map(|ip| NameServerConfig::udp_and_tcp(ip.into()))
59 .collect::<Vec<_>>();
60 if nameservers.is_empty() {
61 Err(io::Error::other("no nameservers found in config"))?;
62 }
63
64 let mut search = vec![];
66 for search_domain in parsed_config.get_last_search_or_domain() {
67 if search_domain == "--" {
69 continue;
70 }
71
72 search.push(
73 Name::from_str_relaxed(search_domain)
74 .map_err(|e| io::Error::other(format!("Error parsing resolv.conf: {e}")))?,
75 );
76 }
77
78 let config = ResolverConfig::from_parts(domain, search, nameservers);
79
80 let options = ResolverOpts {
81 ndots: parsed_config.ndots as usize,
82 timeout: Duration::from_secs(u64::from(parsed_config.timeout)),
83 attempts: parsed_config.attempts as usize,
84 edns0: parsed_config.edns0,
85 ..ResolverOpts::default()
86 };
87
88 Ok((config, options))
89}
90
91#[cfg(test)]
92mod tests {
93 use super::*;
94 use crate::proto::rr::Name;
95 use std::env;
96 use std::net::*;
97 use std::str::FromStr;
98
99 fn empty_config(name_servers: NameServerConfig) -> ResolverConfig {
100 ResolverConfig::from_parts(None, vec![], vec![name_servers])
101 }
102
103 fn nameserver_config(ip: &str) -> NameServerConfig {
104 let mut server = NameServerConfig::udp_and_tcp(IpAddr::from_str(ip).unwrap());
105 server.trust_negative_responses = false;
106 server
107 }
108
109 fn tests_dir() -> String {
110 let server_path = env::var("TDNS_WORKSPACE_ROOT").unwrap_or_else(|_| "../..".to_owned());
111 format!("{server_path}/crates/resolver/tests")
112 }
113
114 #[test]
115 fn test_name_server() {
116 let parsed = parse_resolv_conf("nameserver 127.0.0.1").expect("failed");
117 let cfg = empty_config(nameserver_config("127.0.0.1"));
118 assert_eq!(cfg.name_servers()[0].ip, parsed.0.name_servers()[0].ip);
119 is_default_opts(parsed.1);
120 }
121
122 #[test]
123 fn test_search() {
124 let parsed = parse_resolv_conf("search localnet.\nnameserver 127.0.0.1").expect("failed");
125 let mut cfg = empty_config(nameserver_config("127.0.0.1"));
126 cfg.add_search(Name::from_str("localnet.").unwrap());
127 assert_eq!(cfg.search(), parsed.0.search());
128 is_default_opts(parsed.1);
129 }
130
131 #[test]
132 fn test_skips_invalid_search() {
133 let parsed =
134 parse_resolv_conf("\n\nnameserver 127.0.0.53\noptions edns0 trust-ad\nsearch -- lan\n")
135 .expect("failed");
136 let mut cfg = empty_config(nameserver_config("127.0.0.53"));
137
138 {
139 assert_eq!(cfg.name_servers()[0].ip, parsed.0.name_servers()[0].ip);
140 is_default_opts(parsed.1);
141 }
142
143 {
145 cfg.add_search(Name::from_str("lan").unwrap());
146 assert_eq!(cfg.search(), parsed.0.search());
147 }
148 }
149
150 #[test]
151 fn test_underscore_in_search() {
152 let parsed =
153 parse_resolv_conf("search Speedport_000\nnameserver 127.0.0.1").expect("failed");
154 let mut cfg = empty_config(nameserver_config("127.0.0.1"));
155 cfg.add_search(Name::from_str_relaxed("Speedport_000").unwrap());
156 assert_eq!(cfg.search(), parsed.0.search());
157 is_default_opts(parsed.1);
158 }
159
160 #[test]
161 fn test_domain() {
162 let parsed = parse_resolv_conf("domain example.com\nnameserver 127.0.0.1").expect("failed");
163 let mut cfg = empty_config(nameserver_config("127.0.0.1"));
164 cfg.set_domain(Name::from_str("example.com").unwrap());
165 assert_eq!(cfg.name_servers()[0].ip, parsed.0.name_servers()[0].ip);
166 assert_eq!(cfg.domain(), parsed.0.domain());
167 is_default_opts(parsed.1);
168 }
169
170 #[test]
171 fn test_read_resolv_conf() {
172 read_resolv_conf(format!("{}/resolv.conf-simple", tests_dir())).expect("simple failed");
173 read_resolv_conf(format!("{}/resolv.conf-macos", tests_dir())).expect("macos failed");
174 read_resolv_conf(format!("{}/resolv.conf-linux", tests_dir())).expect("linux failed");
175 }
176
177 fn is_default_opts(opts: ResolverOpts) {
179 assert_eq!(opts.ndots, 1);
180 assert_eq!(opts.timeout, Duration::from_secs(5));
181 assert_eq!(opts.attempts, 2);
182 }
183}