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