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}