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, 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 =
354                SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 53);
355            self.servers.push(ServerConf::new(addr, Transport::Udp));
356            self.servers.push(ServerConf::new(addr, Transport::Tcp));
357        }
358        if self.options.search.is_empty() {
359            self.options.search.push(Dname::root())
360        }
361        for server in &mut self.servers {
362            server.request_timeout = self.options.timeout
363        }
364    }
365}
366
367/// # Parsing Configuration File
368///
369impl ResolvConf {
370    /// Parses the configuration from a file.
371    pub fn parse_file<P: AsRef<Path>>(
372        &mut self,
373        path: P,
374    ) -> Result<(), Error> {
375        let mut file = fs::File::open(path)?;
376        self.parse(&mut file)
377    }
378
379    /// Parses the configuration from a reader.
380    ///
381    /// The format is that of the /etc/resolv.conf file.
382    pub fn parse<R: Read>(&mut self, reader: &mut R) -> Result<(), Error> {
383        use std::io::BufRead;
384
385        for line in io::BufReader::new(reader).lines() {
386            let line = line?;
387            let line = line.trim_end();
388
389            if line.is_empty()
390                || line.starts_with(';')
391                || line.starts_with('#')
392            {
393                continue;
394            }
395
396            let mut words = line.split_whitespace();
397            let keyword = words.next();
398            match keyword {
399                Some("nameserver") => self.parse_nameserver(words)?,
400                Some("domain") => self.parse_domain(words)?,
401                Some("search") => self.parse_search(words)?,
402                Some("sortlist") => { /* TODO: self.parse_sortlist(words)? */
403                }
404                Some("options") => self.parse_options(words)?,
405                _ => return Err(Error::ParseError),
406            }
407        }
408        Ok(())
409    }
410
411    fn parse_nameserver(
412        &mut self,
413        mut words: SplitWhitespace,
414    ) -> Result<(), Error> {
415        use std::net::ToSocketAddrs;
416
417        for addr in (next_word(&mut words)?, 53).to_socket_addrs()? {
418            self.servers.push(ServerConf::new(addr, Transport::Udp));
419            self.servers.push(ServerConf::new(addr, Transport::Tcp));
420        }
421        no_more_words(words)
422    }
423
424    fn parse_domain(
425        &mut self,
426        mut words: SplitWhitespace,
427    ) -> Result<(), Error> {
428        let domain = SearchSuffix::from_str(next_word(&mut words)?)?;
429        self.options.search = domain.into();
430        no_more_words(words)
431    }
432
433    fn parse_search(&mut self, words: SplitWhitespace) -> Result<(), Error> {
434        let mut search = SearchList::new();
435        let mut root = false;
436        for word in words {
437            let name = SearchSuffix::from_str(word)?;
438            if name.is_root() {
439                root = true
440            }
441            search.push(name);
442        }
443        if !root {
444            search.push(SearchSuffix::root());
445        }
446        self.options.search = search;
447        Ok(())
448    }
449
450    /*
451    fn parse_sortlist(
452        &mut self,
453        _words: SplitWhitespace
454    ) -> Result<(), Error> {
455        // XXX TODO
456    }
457    */
458
459    #[allow(clippy::match_same_arms)]
460    fn parse_options(&mut self, words: SplitWhitespace) -> Result<(), Error> {
461        for word in words {
462            match split_arg(word)? {
463                ("debug", None) => {}
464                ("ndots", Some(n)) => self.options.ndots = n,
465                ("timeout", Some(n)) => {
466                    self.options.timeout = Duration::new(n as u64, 0)
467                }
468                ("attempts", Some(n)) => self.options.attempts = n,
469                ("rotate", None) => self.options.rotate = true,
470                ("no-check-names", None) => self.options.no_check_name = true,
471                ("inet6", None) => self.options.use_inet6 = true,
472                ("ip6-bytestring", None) => self.options.use_bstring = true,
473                ("ip6-dotint", None) => self.options.use_ip6dotint = true,
474                ("no-ip6-dotint", None) => self.options.use_ip6dotint = false,
475                ("edns0", None) => self.options.use_edns0 = true,
476                ("single-request", None) => {
477                    self.options.single_request = true
478                }
479                ("single-request-reopen", None) => {
480                    self.options.single_request_reopen = true
481                }
482                ("no-tld-query", None) => self.options.no_tld_query = true,
483                ("use-vc", None) => self.options.use_vc = true,
484                // Ignore unknown or misformated options.
485                _ => {}
486            }
487        }
488        Ok(())
489    }
490}
491
492//--- Default
493
494impl Default for ResolvConf {
495    /// Creates a default configuration for this system.
496    ///
497    /// XXX This currently only works for Unix-y systems.
498    fn default() -> Self {
499        let mut res = ResolvConf::new();
500        let _ = res.parse_file("/etc/resolv.conf");
501        res.finalize();
502        res
503    }
504}
505
506//--- Display
507
508impl fmt::Display for ResolvConf {
509    #[allow(clippy::cognitive_complexity)]
510    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
511        for server in &self.servers {
512            let server = server.addr;
513            f.write_str("nameserver ")?;
514            if server.port() == 53 {
515                server.ip().fmt(f)?;
516            } else {
517                server.fmt(f)?;
518            }
519            "\n".fmt(f)?;
520        }
521        match self.options.search.len().cmp(&1) {
522            Ordering::Equal => {
523                writeln!(f, "domain {}", self.options.search[0])?;
524            }
525            Ordering::Greater => {
526                "search".fmt(f)?;
527                for name in self.options.search.as_slice() {
528                    write!(f, " {}", name)?;
529                }
530                "\n".fmt(f)?;
531            }
532            Ordering::Less => {}
533        }
534
535        // Collect options so we only print them if there are any non-default
536        // ones.
537        let mut options = Vec::new();
538
539        if self.options.ndots != 1 {
540            options.push(format!("ndots:{}", self.options.ndots));
541        }
542        if self.options.timeout != Duration::new(5, 0) {
543            // XXX This ignores fractional seconds.
544            options
545                .push(format!("timeout:{}", self.options.timeout.as_secs()));
546        }
547        if self.options.attempts != 2 {
548            options.push(format!("attempts:{}", self.options.attempts));
549        }
550        if self.options.aa_only {
551            options.push("aa-only".into())
552        }
553        if self.options.use_vc {
554            options.push("use-vc".into())
555        }
556        if self.options.primary {
557            options.push("primary".into())
558        }
559        if self.options.ign_tc {
560            options.push("ign-tc".into())
561        }
562        if !self.options.recurse {
563            options.push("no-recurse".into())
564        }
565        if !self.options.default_names {
566            options.push("no-default-names".into())
567        }
568        if self.options.stay_open {
569            options.push("stay-open".into())
570        }
571        if !self.options.dn_search {
572            options.push("no-dn-search".into())
573        }
574        if self.options.use_inet6 {
575            options.push("use-inet6".into())
576        }
577        if self.options.rotate {
578            options.push("rotate".into())
579        }
580        if self.options.no_check_name {
581            options.push("no-check-name".into())
582        }
583        if self.options.keep_tsig {
584            options.push("keep-tsig".into())
585        }
586        if self.options.blast {
587            options.push("blast".into())
588        }
589        if self.options.use_bstring {
590            options.push("use-bstring".into())
591        }
592        if self.options.use_ip6dotint {
593            options.push("ip6dotint".into())
594        }
595        if self.options.use_edns0 {
596            options.push("use-edns0".into())
597        }
598        if self.options.single_request {
599            options.push("single-request".into())
600        }
601        if self.options.single_request_reopen {
602            options.push("single-request-reopen".into())
603        }
604        if self.options.no_tld_query {
605            options.push("no-tld-query".into())
606        }
607
608        if !options.is_empty() {
609            "options".fmt(f)?;
610            for option in options {
611                write!(f, " {}", option)?;
612            }
613            "\n".fmt(f)?;
614        }
615
616        Ok(())
617    }
618}
619
620//------------ SearchSuffix --------------------------------------------------
621
622pub type SearchSuffix = Dname<SmallVec<[u8; 24]>>;
623
624//------------ SearchList ----------------------------------------------------
625
626#[derive(Clone, Debug, Default)]
627pub struct SearchList {
628    search: Vec<SearchSuffix>,
629}
630
631impl SearchList {
632    pub fn new() -> Self {
633        Self::default()
634    }
635
636    pub fn push(&mut self, name: SearchSuffix) {
637        self.search.push(name)
638    }
639
640    pub fn push_root(&mut self) {
641        self.search.push(Dname::root())
642    }
643
644    pub fn len(&self) -> usize {
645        self.search.len()
646    }
647
648    pub fn is_empty(&self) -> bool {
649        self.search.is_empty()
650    }
651
652    pub fn get(&self, pos: usize) -> Option<&SearchSuffix> {
653        self.search.get(pos)
654    }
655
656    pub fn as_slice(&self) -> &[SearchSuffix] {
657        self.as_ref()
658    }
659}
660
661impl From<SearchSuffix> for SearchList {
662    fn from(name: SearchSuffix) -> Self {
663        let mut res = Self::new();
664        res.push(name);
665        res
666    }
667}
668
669impl<I: SliceIndex<[SearchSuffix]>> ops::Index<I> for SearchList {
670    type Output = <I as SliceIndex<[SearchSuffix]>>::Output;
671
672    fn index(&self, index: I) -> &<I as SliceIndex<[SearchSuffix]>>::Output {
673        self.search.index(index)
674    }
675}
676
677//--- AsRef
678
679impl AsRef<[SearchSuffix]> for SearchList {
680    fn as_ref(&self) -> &[SearchSuffix] {
681        self.search.as_ref()
682    }
683}
684
685//------------ Private Helpers -----------------------------------------------
686//
687// These are here to wrap stuff into Results.
688
689/// Returns a reference to the next word or an error.
690fn next_word<'a>(
691    words: &'a mut str::SplitWhitespace,
692) -> Result<&'a str, Error> {
693    match words.next() {
694        Some(word) => Ok(word),
695        None => Err(Error::ParseError),
696    }
697}
698
699/// Returns nothing but errors out if there are words left.
700fn no_more_words(mut words: str::SplitWhitespace) -> Result<(), Error> {
701    match words.next() {
702        Some(..) => Err(Error::ParseError),
703        None => Ok(()),
704    }
705}
706
707/// Splits the name and argument from an option with arguments.
708///
709/// These options consist of a name followed by a colon followed by a
710/// value, which so far is only `usize`, so we do that.
711fn split_arg(s: &str) -> Result<(&str, Option<usize>), Error> {
712    match s.find(':') {
713        Some(idx) => {
714            let (left, right) = s.split_at(idx);
715            Ok((left, Some(right[1..].parse()?)))
716        }
717        None => Ok((s, None)),
718    }
719}
720
721//------------ Error --------------------------------------------------------
722
723/// The error that can happen when parsing `resolv.conf`.
724#[derive(Debug)]
725pub enum Error {
726    /// The file is not a proper file.
727    ParseError,
728
729    /// Something happend while reading.
730    Io(io::Error),
731}
732
733impl error::Error for Error {}
734
735impl convert::From<io::Error> for Error {
736    fn from(error: io::Error) -> Error {
737        Error::Io(error)
738    }
739}
740
741impl convert::From<name::FromStrError> for Error {
742    fn from(_: name::FromStrError) -> Error {
743        Error::ParseError
744    }
745}
746
747impl convert::From<::std::num::ParseIntError> for Error {
748    fn from(_: ::std::num::ParseIntError) -> Error {
749        Error::ParseError
750    }
751}
752
753impl fmt::Display for Error {
754    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
755        match *self {
756            Error::ParseError => write!(f, "error parsing configuration"),
757            Error::Io(ref e) => e.fmt(f),
758        }
759    }
760}
761
762//============ Testing ======================================================
763
764#[cfg(test)]
765mod test {
766    use super::*;
767    use std::io;
768    use std::string::ToString;
769
770    #[test]
771    fn parse_resolv_conf() {
772        let mut conf = ResolvConf::new();
773        let data = "nameserver 192.0.2.0\n\
774                    nameserver 192.0.2.1\n\
775                    options use-vc ndots:122\n"
776            .to_string();
777        assert!(conf.parse(&mut io::Cursor::new(data)).is_ok());
778        assert!(conf.options.use_vc);
779        assert_eq!(conf.options.ndots, 122);
780    }
781}