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}