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