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