trust_dns_resolver/system_conf/
unix.rs1use std::fs::File;
15use std::io;
16use std::io::Read;
17use std::net::SocketAddr;
18use std::path::Path;
19use std::str::FromStr;
20use std::time::Duration;
21
22use resolv_conf;
23
24use crate::config::*;
25use crate::proto::rr::Name;
26
27const DEFAULT_PORT: u16 = 53;
28
29pub fn read_system_conf() -> io::Result<(ResolverConfig, ResolverOpts)> {
30 read_resolv_conf("/etc/resolv.conf")
31}
32
33fn read_resolv_conf<P: AsRef<Path>>(path: P) -> io::Result<(ResolverConfig, ResolverOpts)> {
34 let mut data = String::new();
35 let mut file = File::open(path)?;
36 file.read_to_string(&mut data)?;
37 parse_resolv_conf(&data)
38}
39
40pub fn parse_resolv_conf<T: AsRef<[u8]>>(data: T) -> io::Result<(ResolverConfig, ResolverOpts)> {
41 let parsed_conf = resolv_conf::Config::parse(&data).map_err(|e| {
42 io::Error::new(
43 io::ErrorKind::Other,
44 format!("Error parsing resolv.conf: {e}"),
45 )
46 })?;
47 into_resolver_config(parsed_conf)
48}
49
50fn into_resolver_config(
52 parsed_config: resolv_conf::Config,
53) -> io::Result<(ResolverConfig, ResolverOpts)> {
54 let domain = if let Some(domain) = parsed_config.get_system_domain() {
55 Name::from_str(domain.as_str()).ok()
60 } else {
61 None
62 };
63
64 let mut nameservers = Vec::<NameServerConfig>::with_capacity(parsed_config.nameservers.len());
66 for ip in &parsed_config.nameservers {
67 nameservers.push(NameServerConfig {
68 socket_addr: SocketAddr::new(ip.into(), DEFAULT_PORT),
69 protocol: Protocol::Udp,
70 tls_dns_name: None,
71 trust_negative_responses: false,
72 #[cfg(feature = "dns-over-rustls")]
73 tls_config: None,
74 bind_addr: None,
75 });
76 nameservers.push(NameServerConfig {
77 socket_addr: SocketAddr::new(ip.into(), DEFAULT_PORT),
78 protocol: Protocol::Tcp,
79 tls_dns_name: None,
80 trust_negative_responses: false,
81 #[cfg(feature = "dns-over-rustls")]
82 tls_config: None,
83 bind_addr: None,
84 });
85 }
86 if nameservers.is_empty() {
87 tracing::warn!("no nameservers found in config");
88 }
89
90 let mut search = vec![];
92 for search_domain in parsed_config.get_last_search_or_domain() {
93 if search_domain == "--" {
95 continue;
96 }
97
98 search.push(Name::from_str_relaxed(search_domain).map_err(|e| {
99 io::Error::new(
100 io::ErrorKind::Other,
101 format!("Error parsing resolv.conf: {e}"),
102 )
103 })?);
104 }
105
106 let config = ResolverConfig::from_parts(domain, search, nameservers);
107
108 let options = ResolverOpts {
109 ndots: parsed_config.ndots as usize,
110 timeout: Duration::from_secs(u64::from(parsed_config.timeout)),
111 attempts: parsed_config.attempts as usize,
112 ..ResolverOpts::default()
113 };
114
115 Ok((config, options))
116}
117
118#[cfg(test)]
119mod tests {
120 use super::*;
121 use proto::rr::Name;
122 use std::env;
123 use std::net::*;
124 use std::str::FromStr;
125
126 fn empty_config() -> ResolverConfig {
127 ResolverConfig::from_parts(None, vec![], vec![])
128 }
129
130 fn nameserver_config(ip: &str) -> [NameServerConfig; 2] {
131 let addr = SocketAddr::new(IpAddr::from_str(ip).unwrap(), 53);
132 [
133 NameServerConfig {
134 socket_addr: addr,
135 protocol: Protocol::Udp,
136 tls_dns_name: None,
137 trust_negative_responses: false,
138 #[cfg(feature = "dns-over-rustls")]
139 tls_config: None,
140 bind_addr: None,
141 },
142 NameServerConfig {
143 socket_addr: addr,
144 protocol: Protocol::Tcp,
145 tls_dns_name: None,
146 trust_negative_responses: false,
147 #[cfg(feature = "dns-over-rustls")]
148 tls_config: None,
149 bind_addr: None,
150 },
151 ]
152 }
153
154 fn tests_dir() -> String {
155 let server_path = env::var("TDNS_WORKSPACE_ROOT").unwrap_or_else(|_| "../..".to_owned());
156 format!("{server_path}/crates/resolver/tests")
157 }
158
159 #[test]
160 #[allow(clippy::redundant_clone)]
161 fn test_name_server() {
162 let parsed = parse_resolv_conf("nameserver 127.0.0.1").expect("failed");
163 let mut cfg = empty_config();
164 let nameservers = nameserver_config("127.0.0.1");
165 cfg.add_name_server(nameservers[0].clone());
166 cfg.add_name_server(nameservers[1].clone());
167 assert_eq!(cfg.name_servers(), parsed.0.name_servers());
168 assert_eq!(ResolverOpts::default(), parsed.1);
169 }
170
171 #[test]
172 fn test_search() {
173 let parsed = parse_resolv_conf("search localnet.").expect("failed");
174 let mut cfg = empty_config();
175 cfg.add_search(Name::from_str("localnet.").unwrap());
176 assert_eq!(cfg.search(), parsed.0.search());
177 assert_eq!(ResolverOpts::default(), parsed.1);
178 }
179
180 #[test]
181 fn test_skips_invalid_search() {
182 let parsed =
183 parse_resolv_conf("\n\nnameserver 127.0.0.53\noptions edns0 trust-ad\nsearch -- lan\n")
184 .expect("failed");
185 let mut cfg = empty_config();
186
187 {
188 let nameservers = nameserver_config("127.0.0.53");
189 cfg.add_name_server(nameservers[0].clone());
190 cfg.add_name_server(nameservers[1].clone());
191 assert_eq!(cfg.name_servers(), parsed.0.name_servers());
192 assert_eq!(ResolverOpts::default(), parsed.1);
193 }
194
195 {
197 cfg.add_search(Name::from_str("lan").unwrap());
198 assert_eq!(cfg.search(), parsed.0.search());
199 assert_eq!(ResolverOpts::default(), parsed.1);
200 }
201 }
202
203 #[test]
204 fn test_underscore_in_search() {
205 let parsed = parse_resolv_conf("search Speedport_000").expect("failed");
206 let mut cfg = empty_config();
207 cfg.add_search(Name::from_str_relaxed("Speedport_000.").unwrap());
208 assert_eq!(cfg.search(), parsed.0.search());
209 assert_eq!(ResolverOpts::default(), parsed.1);
210 }
211
212 #[test]
213 fn test_domain() {
214 let parsed = parse_resolv_conf("domain example.com").expect("failed");
215 let mut cfg = empty_config();
216 cfg.set_domain(Name::from_str("example.com").unwrap());
217 assert_eq!(cfg, parsed.0);
218 assert_eq!(ResolverOpts::default(), parsed.1);
219 }
220
221 #[test]
222 fn test_read_resolv_conf() {
223 read_resolv_conf(format!("{}/resolv.conf-simple", tests_dir())).expect("simple failed");
224 read_resolv_conf(format!("{}/resolv.conf-macos", tests_dir())).expect("macos failed");
225 read_resolv_conf(format!("{}/resolv.conf-linux", tests_dir())).expect("linux failed");
226 }
227}