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}