dns_resolver/conf.rs
1//! Resolver configuration
2//!
3//! There are two parts to this module: Query options that allow you to
4//! modify the behaviour of the resolver on a query by query basis and
5//! the global resolver configuration (normally read from the system’s
6//! `/etc/resolv.conf`) that contains things like the name servers to query
7//! and a set of default options.
8//!
9//! Both parts are modeled along the lines of glibc’s resolver.
10
11use domain::base::name::{self, Dname};
12use smallvec::SmallVec;
13use std::cmp::Ordering;
14use std::default::Default;
15use std::io::Read;
16use std::net::{IpAddr, Ipv4Addr, SocketAddr};
17use std::path::Path;
18use std::slice::SliceIndex;
19use std::str::{self, FromStr, SplitWhitespace};
20use std::time::Duration;
21use std::vec::Vec;
22use std::{convert, error, fmt, fs, io, ops};
23
24//------------ ResolvOptions ------------------------------------------------
25
26/// Options for the resolver configuration.
27///
28/// This type contains a lot of options that influence the resolver
29/// configuration. It collects all server-indpendent options that glibc’s
30/// resolver supports. Not all of them are currently supported by this
31/// implementation.
32#[derive(Clone, Debug)]
33pub struct ResolvOptions {
34 /// Search list for host-name lookup.
35 pub search: SearchList,
36
37 /// TODO Sortlist
38 /// sortlist: ??
39
40 /// Number of dots before an initial absolute query is made.
41 pub ndots: usize,
42
43 /// Timeout to wait for a response.
44 pub timeout: Duration,
45
46 /// Number of retries before giving up.
47 pub attempts: usize,
48
49 /// Accept authoritative answers only.
50 ///
51 /// Only responses with the AA bit set will be considered. If there
52 /// aren’t any, the query will fail.
53 ///
54 /// This option is not currently implemented. It is likely to be
55 /// eventually implemented by the query.
56 pub aa_only: bool,
57
58 /// Always use TCP.
59 ///
60 /// This option is implemented by the query.
61 pub use_vc: bool,
62
63 /// Query primary name servers only.
64 ///
65 /// This option is not currently implemented. It is unclear what exactly
66 /// it is supposed to mean.
67 pub primary: bool,
68
69 /// Ignore trunactions errors, don’t retry with TCP.
70 ///
71 /// This option is implemented by the query.
72 pub ign_tc: bool,
73
74 /// Set the recursion desired bit in queries.
75 ///
76 /// Enabled by default.
77 ///
78 /// Implemented by the query request.
79 pub recurse: bool,
80
81 /// Append the default domain name to single component names.
82 ///
83 /// Enabled by default.
84 ///
85 /// This is not currently implemented. Instead, the resolver config’s
86 /// `search` and `ndots` fields govern resolution of relative names of
87 /// all kinds.
88 pub default_names: bool,
89
90 /// Keep TCP connections open between queries.
91 ///
92 /// This is not currently implemented.
93 pub stay_open: bool,
94
95 /// Search hostnames in the current domain and parent domains.
96 ///
97 /// Enabled by default.
98 ///
99 /// This options is not currently implemented. Instead, the resolver
100 /// config’s `search` and `ndots` fields govern resolution of relative
101 /// names.
102 pub dn_search: bool,
103
104 /// Try AAAA query before A query and map IPv4 responses to tunnel form.
105 ///
106 /// This option is not currently implemented. It is only relevant for
107 /// `lookup_host`.
108 pub use_inet6: bool,
109
110 /// Use round-robin selection of name servers.
111 ///
112 /// This option is implemented by the query.
113 pub rotate: bool,
114
115 /// Disable checking of incoming hostname and mail names.
116 ///
117 /// This is not currently implemented. Or rather, this is currently
118 /// always on—there is no name checking as yet.
119 pub no_check_name: bool,
120
121 /// Do not strip TSIG records.
122 ///
123 /// This is not currently implemented. Or rather, no records are stripped
124 /// at all.
125 pub keep_tsig: bool,
126
127 /// Send each query simultaneously to all name servers.
128 ///
129 /// This is not currently implemented. It would be a query option.
130 pub blast: bool,
131
132 /// Use bit-label format for IPv6 reverse lookups.
133 ///
134 /// Bit labels have been deprecated and consequently, this option is not
135 /// implemented.
136 pub use_bstring: bool,
137
138 /// Use ip6.int instead of the recommended ip6.arpa.
139 ///
140 /// (This option is the reverse of glibc’s `RES_NOIP6DOTINT` option).
141 ///
142 /// This option is only relevant for `lookup_addr()` and is implemented
143 /// there already.
144 pub use_ip6dotint: bool,
145
146 /// Use EDNS0.
147 ///
148 /// EDNS is not yet supported.
149 pub use_edns0: bool,
150
151 /// Perform IPv4 and IPv6 lookups sequentially instead of in parallel.
152 ///
153 /// This is not yet implemented but would be an option for
154 /// `lookup_host()`.
155 pub single_request: bool,
156
157 /// Open a new socket for each request.
158 ///
159 /// This is not currently implemented.
160 pub single_request_reopen: bool,
161
162 /// Don’t look up unqualified names as top-level-domain.
163 ///
164 /// This is not currently implemented. Instead, the resolver config’s
165 /// `search` and `ndots` fields govern resolution of relative names of
166 /// all kinds.
167 pub no_tld_query: bool,
168}
169
170impl Default for ResolvOptions {
171 fn default() -> Self {
172 ResolvOptions {
173 // non-flags:
174 search: SearchList::new(),
175 //sortlist,
176 ndots: 1,
177 timeout: Duration::new(5, 0),
178 attempts: 2,
179
180 // enabled by default:
181 recurse: true,
182 default_names: true,
183 dn_search: true,
184
185 // everthing else is not:
186 aa_only: false,
187 use_vc: false,
188 primary: false,
189 ign_tc: false,
190 stay_open: false,
191 use_inet6: false,
192 rotate: false,
193 no_check_name: false,
194 keep_tsig: false,
195 blast: false,
196 use_bstring: false,
197 use_ip6dotint: false,
198 use_edns0: false,
199 single_request: false,
200 single_request_reopen: false,
201 no_tld_query: false,
202 }
203 }
204}
205
206//------------ Transport -----------------------------------------------------
207
208/// The transport protocol to be used for a server.
209#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
210pub enum Transport {
211 /// Unencrypted UDP transport.
212 Udp,
213
214 /// Unencrypted TCP transport.
215 Tcp,
216}
217
218impl Transport {
219 /// Returns whether the transport is a preferred transport.
220 ///
221 /// Only preferred transports are considered initially. Only if a
222 /// truncated answer comes back will we consider streaming protocols
223 /// instead.
224 pub fn is_preferred(self) -> bool {
225 match self {
226 Transport::Udp => true,
227 Transport::Tcp => false,
228 }
229 }
230
231 /// Returns whether the transport is a streaming protocol.
232 pub fn is_stream(self) -> bool {
233 match self {
234 Transport::Udp => false,
235 Transport::Tcp => true,
236 }
237 }
238}
239
240//------------ ServerConf ----------------------------------------------------
241
242/// Configuration for one upstream DNS server.
243///
244/// The server is identified by a socket address, ie., an address/port pair.
245/// For each server you can set how it should operate on all supported
246/// transport protocols, including not at all, and two timeouts for each
247/// request and sockets. The timeouts are used for all transports. If you
248/// need different timeouts for, say, UDP and TCP, you can always use two
249/// server entries with the same address.
250#[derive(Clone, Debug)]
251pub struct ServerConf {
252 /// Server address.
253 pub addr: SocketAddr,
254
255 /// Transport protocol.
256 pub transport: Transport,
257
258 /// How long to wait for a response before returning a timeout error.
259 ///
260 /// This field defaults to 2 seconds.
261 pub request_timeout: Duration,
262
263 /// Size of the message receive buffer in bytes.
264 ///
265 /// This is used for datagram transports only. It defaults to 1232 bytes
266 /// for both IPv6 and IPv4.
267 ///
268 /// (Note: While 1372 bytes works for IPv4 in most scenarios, there has
269 /// been [research] indicating that sometimes 1232 bytes is the limit here,
270 /// sometimes too.)
271 ///
272 /// [research]: https://rp.delaat.net/2019-2020/p78/report.pdf
273 pub recv_size: usize,
274
275 /// Advertised UDP payload size.
276 ///
277 /// This values will be announced in request if EDNS is supported by the
278 /// server. It will be included both for datagram and streaming transport
279 /// but really only matters for UDP.
280 pub udp_payload_size: u16,
281}
282
283impl ServerConf {
284 /// Returns a new default server config for a given address and transport.
285 ///
286 /// The function sets default values as described in the field
287 /// descriptions above.
288 pub fn new(addr: SocketAddr, transport: Transport) -> Self {
289 ServerConf {
290 addr,
291 transport,
292 request_timeout: Duration::from_secs(2),
293 recv_size: 1232,
294 udp_payload_size: 1232,
295 }
296 }
297}
298
299//------------ ResolvConf ---------------------------------------------------
300
301/// Resolver configuration.
302///
303/// This type collects all information necessary to configure how a stub
304/// resolver talks to its upstream resolvers.
305///
306/// The type follows the builder pattern. After creating a value with
307/// `ResolvConf::new()` you can manipulate the members. Once you are happy
308/// with them, you call `finalize()` to make sure the configuration is valid.
309/// It mostly just fixes the `servers`.
310///
311/// Additionally, the type can parse a glibc-style configuration file,
312/// commonly known as `/etc/resolv.conf` through the `parse()` and
313/// `parse_file()` methods. You still need to call `finalize()` after
314/// parsing.
315///
316/// The easiest way, however, to get the system resolver configuration is
317/// through `ResolvConf::system_default()`. This will parse the configuration
318/// file or return a default configuration if that fails.
319///
320#[derive(Clone, Debug)]
321pub struct ResolvConf {
322 /// Addresses of servers to query.
323 pub servers: Vec<ServerConf>,
324
325 /// Default options.
326 pub options: ResolvOptions,
327}
328
329/// # Management
330///
331impl ResolvConf {
332 /// Creates a new, empty configuration.
333 ///
334 /// Using an empty configuration will fail since it does not contain
335 /// any name servers. Call `self.finalize()` to make it usable.
336 pub fn new() -> Self {
337 ResolvConf {
338 servers: Vec::new(),
339 options: ResolvOptions::default(),
340 }
341 }
342
343 /// Finalizes the configuration for actual use.
344 ///
345 /// The function does two things. If `servers` is empty, it adds
346 /// `127.0.0.1:53`. This is exactly what glibc does. If `search` is
347 /// empty, it adds the root domain `"."`. This differs from what
348 /// glibc does which considers the machine’s host name.
349 pub fn finalize(&mut self) {
350 if self.servers.is_empty() {
351 // glibc just simply uses 127.0.0.1:53. Let's do that, too,
352 // and claim it is for compatibility.
353 let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 53);
354 self.servers.push(ServerConf::new(addr, Transport::Udp));
355 self.servers.push(ServerConf::new(addr, Transport::Tcp));
356 }
357 if self.options.search.is_empty() {
358 self.options.search.push(Dname::root())
359 }
360 for server in &mut self.servers {
361 server.request_timeout = self.options.timeout
362 }
363 }
364}
365
366/// # Parsing Configuration File
367///
368impl ResolvConf {
369 /// Parses the configuration from a file.
370 pub fn parse_file<P: AsRef<Path>>(&mut self, path: P) -> Result<(), Error> {
371 let mut file = fs::File::open(path)?;
372 self.parse(&mut file)
373 }
374
375 /// Parses the configuration from a reader.
376 ///
377 /// The format is that of the /etc/resolv.conf file.
378 pub fn parse<R: Read>(&mut self, reader: &mut R) -> Result<(), Error> {
379 use std::io::BufRead;
380
381 for line in io::BufReader::new(reader).lines() {
382 let line = line?;
383 let line = line.trim_end();
384
385 if line.is_empty() || line.starts_with(';') || line.starts_with('#') {
386 continue;
387 }
388
389 let mut words = line.split_whitespace();
390 let keyword = words.next();
391 match keyword {
392 Some("nameserver") => self.parse_nameserver(words)?,
393 Some("domain") => self.parse_domain(words)?,
394 Some("search") => self.parse_search(words)?,
395 Some("sortlist") => { /* TODO: self.parse_sortlist(words)? */ }
396 Some("options") => self.parse_options(words)?,
397 _ => return Err(Error::ParseError),
398 }
399 }
400 Ok(())
401 }
402
403 fn parse_nameserver(&mut self, mut words: SplitWhitespace) -> Result<(), Error> {
404 use std::net::ToSocketAddrs;
405
406 for addr in (next_word(&mut words)?, 53).to_socket_addrs()? {
407 self.servers.push(ServerConf::new(addr, Transport::Udp));
408 self.servers.push(ServerConf::new(addr, Transport::Tcp));
409 }
410 no_more_words(words)
411 }
412
413 fn parse_domain(&mut self, mut words: SplitWhitespace) -> Result<(), Error> {
414 let domain = SearchSuffix::from_str(next_word(&mut words)?)?;
415 self.options.search = domain.into();
416 no_more_words(words)
417 }
418
419 fn parse_search(&mut self, words: SplitWhitespace) -> Result<(), Error> {
420 let mut search = SearchList::new();
421 let mut root = false;
422 for word in words {
423 let name = SearchSuffix::from_str(word)?;
424 if name.is_root() {
425 root = true
426 }
427 search.push(name);
428 }
429 if !root {
430 search.push(SearchSuffix::root());
431 }
432 self.options.search = search;
433 Ok(())
434 }
435
436 /*
437 fn parse_sortlist(
438 &mut self,
439 _words: SplitWhitespace
440 ) -> Result<(), Error> {
441 // XXX TODO
442 }
443 */
444
445 #[allow(clippy::match_same_arms)]
446 fn parse_options(&mut self, words: SplitWhitespace) -> Result<(), Error> {
447 for word in words {
448 match split_arg(word)? {
449 ("debug", None) => {}
450 ("ndots", Some(n)) => self.options.ndots = n,
451 ("timeout", Some(n)) => self.options.timeout = Duration::new(n as u64, 0),
452 ("attempts", Some(n)) => self.options.attempts = n,
453 ("rotate", None) => self.options.rotate = true,
454 ("no-check-names", None) => self.options.no_check_name = true,
455 ("inet6", None) => self.options.use_inet6 = true,
456 ("ip6-bytestring", None) => self.options.use_bstring = true,
457 ("ip6-dotint", None) => self.options.use_ip6dotint = true,
458 ("no-ip6-dotint", None) => self.options.use_ip6dotint = false,
459 ("edns0", None) => self.options.use_edns0 = true,
460 ("single-request", None) => self.options.single_request = true,
461 ("single-request-reopen", None) => self.options.single_request_reopen = true,
462 ("no-tld-query", None) => self.options.no_tld_query = true,
463 ("use-vc", None) => self.options.use_vc = true,
464 // Ignore unknown or misformated options.
465 _ => {}
466 }
467 }
468 Ok(())
469 }
470}
471
472//--- Default
473
474impl Default for ResolvConf {
475 /// Creates a default configuration for this system.
476 ///
477 /// XXX This currently only works for Unix-y systems.
478 fn default() -> Self {
479 let mut res = ResolvConf::new();
480 let _ = res.parse_file("/etc/resolv.conf");
481 res.finalize();
482 res
483 }
484}
485
486//--- Display
487
488impl fmt::Display for ResolvConf {
489 #[allow(clippy::cognitive_complexity)]
490 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
491 for server in &self.servers {
492 let server = server.addr;
493 f.write_str("nameserver ")?;
494 if server.port() == 53 {
495 server.ip().fmt(f)?;
496 } else {
497 server.fmt(f)?;
498 }
499 "\n".fmt(f)?;
500 }
501 match self.options.search.len().cmp(&1) {
502 Ordering::Equal => {
503 writeln!(f, "domain {}", self.options.search[0])?;
504 }
505 Ordering::Greater => {
506 "search".fmt(f)?;
507 for name in self.options.search.as_slice() {
508 write!(f, " {}", name)?;
509 }
510 "\n".fmt(f)?;
511 }
512 Ordering::Less => {}
513 }
514
515 // Collect options so we only print them if there are any non-default
516 // ones.
517 let mut options = Vec::new();
518
519 if self.options.ndots != 1 {
520 options.push(format!("ndots:{}", self.options.ndots));
521 }
522 if self.options.timeout != Duration::new(5, 0) {
523 // XXX This ignores fractional seconds.
524 options.push(format!("timeout:{}", self.options.timeout.as_secs()));
525 }
526 if self.options.attempts != 2 {
527 options.push(format!("attempts:{}", self.options.attempts));
528 }
529 if self.options.aa_only {
530 options.push("aa-only".into())
531 }
532 if self.options.use_vc {
533 options.push("use-vc".into())
534 }
535 if self.options.primary {
536 options.push("primary".into())
537 }
538 if self.options.ign_tc {
539 options.push("ign-tc".into())
540 }
541 if !self.options.recurse {
542 options.push("no-recurse".into())
543 }
544 if !self.options.default_names {
545 options.push("no-default-names".into())
546 }
547 if self.options.stay_open {
548 options.push("stay-open".into())
549 }
550 if !self.options.dn_search {
551 options.push("no-dn-search".into())
552 }
553 if self.options.use_inet6 {
554 options.push("use-inet6".into())
555 }
556 if self.options.rotate {
557 options.push("rotate".into())
558 }
559 if self.options.no_check_name {
560 options.push("no-check-name".into())
561 }
562 if self.options.keep_tsig {
563 options.push("keep-tsig".into())
564 }
565 if self.options.blast {
566 options.push("blast".into())
567 }
568 if self.options.use_bstring {
569 options.push("use-bstring".into())
570 }
571 if self.options.use_ip6dotint {
572 options.push("ip6dotint".into())
573 }
574 if self.options.use_edns0 {
575 options.push("use-edns0".into())
576 }
577 if self.options.single_request {
578 options.push("single-request".into())
579 }
580 if self.options.single_request_reopen {
581 options.push("single-request-reopen".into())
582 }
583 if self.options.no_tld_query {
584 options.push("no-tld-query".into())
585 }
586
587 if !options.is_empty() {
588 "options".fmt(f)?;
589 for option in options {
590 write!(f, " {}", option)?;
591 }
592 "\n".fmt(f)?;
593 }
594
595 Ok(())
596 }
597}
598
599//------------ SearchSuffix --------------------------------------------------
600
601pub type SearchSuffix = Dname<SmallVec<[u8; 24]>>;
602
603//------------ SearchList ----------------------------------------------------
604
605#[derive(Clone, Debug, Default)]
606pub struct SearchList {
607 search: Vec<SearchSuffix>,
608}
609
610impl SearchList {
611 pub fn new() -> Self {
612 Self::default()
613 }
614
615 pub fn push(&mut self, name: SearchSuffix) {
616 self.search.push(name)
617 }
618
619 pub fn push_root(&mut self) {
620 self.search.push(Dname::root())
621 }
622
623 pub fn len(&self) -> usize {
624 self.search.len()
625 }
626
627 pub fn is_empty(&self) -> bool {
628 self.search.is_empty()
629 }
630
631 pub fn get(&self, pos: usize) -> Option<&SearchSuffix> {
632 self.search.get(pos)
633 }
634
635 pub fn as_slice(&self) -> &[SearchSuffix] {
636 self.as_ref()
637 }
638}
639
640impl From<SearchSuffix> for SearchList {
641 fn from(name: SearchSuffix) -> Self {
642 let mut res = Self::new();
643 res.push(name);
644 res
645 }
646}
647
648impl<I: SliceIndex<[SearchSuffix]>> ops::Index<I> for SearchList {
649 type Output = <I as SliceIndex<[SearchSuffix]>>::Output;
650
651 fn index(&self, index: I) -> &<I as SliceIndex<[SearchSuffix]>>::Output {
652 self.search.index(index)
653 }
654}
655
656//--- AsRef
657
658impl AsRef<[SearchSuffix]> for SearchList {
659 fn as_ref(&self) -> &[SearchSuffix] {
660 self.search.as_ref()
661 }
662}
663
664//------------ Private Helpers -----------------------------------------------
665//
666// These are here to wrap stuff into Results.
667
668/// Returns a reference to the next word or an error.
669fn next_word<'a>(words: &'a mut str::SplitWhitespace) -> Result<&'a str, Error> {
670 match words.next() {
671 Some(word) => Ok(word),
672 None => Err(Error::ParseError),
673 }
674}
675
676/// Returns nothing but errors out if there are words left.
677fn no_more_words(mut words: str::SplitWhitespace) -> Result<(), Error> {
678 match words.next() {
679 Some(..) => Err(Error::ParseError),
680 None => Ok(()),
681 }
682}
683
684/// Splits the name and argument from an option with arguments.
685///
686/// These options consist of a name followed by a colon followed by a
687/// value, which so far is only `usize`, so we do that.
688fn split_arg(s: &str) -> Result<(&str, Option<usize>), Error> {
689 match s.find(':') {
690 Some(idx) => {
691 let (left, right) = s.split_at(idx);
692 Ok((left, Some(right[1..].parse()?)))
693 }
694 None => Ok((s, None)),
695 }
696}
697
698//------------ Error --------------------------------------------------------
699
700/// The error that can happen when parsing `resolv.conf`.
701#[derive(Debug)]
702pub enum Error {
703 /// The file is not a proper file.
704 ParseError,
705
706 /// Something happend while reading.
707 Io(io::Error),
708}
709
710impl error::Error for Error {}
711
712impl convert::From<io::Error> for Error {
713 fn from(error: io::Error) -> Error {
714 Error::Io(error)
715 }
716}
717
718impl convert::From<name::FromStrError> for Error {
719 fn from(_: name::FromStrError) -> Error {
720 Error::ParseError
721 }
722}
723
724impl convert::From<::std::num::ParseIntError> for Error {
725 fn from(_: ::std::num::ParseIntError) -> Error {
726 Error::ParseError
727 }
728}
729
730impl fmt::Display for Error {
731 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
732 match *self {
733 Error::ParseError => write!(f, "error parsing configuration"),
734 Error::Io(ref e) => e.fmt(f),
735 }
736 }
737}
738
739//============ Testing ======================================================
740
741#[cfg(test)]
742mod test {
743 use super::*;
744 use std::io;
745 use std::string::ToString;
746
747 #[test]
748 fn parse_resolv_conf() {
749 let mut conf = ResolvConf::new();
750 let data = "nameserver 192.0.2.0\n\
751 nameserver 192.0.2.1\n\
752 options use-vc ndots:122\n"
753 .to_string();
754 assert!(conf.parse(&mut io::Cursor::new(data)).is_ok());
755 assert!(conf.options.use_vc);
756 assert_eq!(conf.options.ndots, 122);
757 }
758}