hickory_resolver/recursor/mod.rs
1// Copyright 2015-2022 Benjamin Fry <benjaminfry@me.com>
2//
3// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
4// https://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
5// https://opensource.org/licenses/MIT>, at your option. This file may not be
6// copied, modified, or distributed except according to those terms.
7
8//! A recursive DNS resolver based on the Hickory DNS (stub) resolver
9
10#[cfg(feature = "serde")]
11use std::{
12 borrow::Cow,
13 fs,
14 path::{Path, PathBuf},
15};
16use std::{
17 collections::HashSet,
18 net::{IpAddr, Ipv4Addr, Ipv6Addr},
19 sync::{Arc, atomic::AtomicU8},
20 time::Instant,
21};
22
23use ipnet::IpNet;
24#[cfg(feature = "serde")]
25use serde::Deserialize;
26use tracing::warn;
27
28#[cfg(all(feature = "__dnssec", feature = "metrics"))]
29use crate::metrics::recursor::RecursorMetrics;
30#[cfg(feature = "tokio")]
31use crate::net::runtime::TokioRuntimeProvider;
32#[cfg(feature = "serde")]
33use crate::proto::{
34 rr::{RData, RecordSet},
35 serialize::txt::{ParseError, Parser},
36};
37use crate::{
38 ConnectionProvider, NameServerTransportState, PoolContext, TlsConfig, TtlConfig,
39 config::OpportunisticEncryption,
40 proto::{
41 op::{DEFAULT_MAX_PAYLOAD_LEN, Message, Query},
42 rr::Name,
43 },
44};
45#[cfg(feature = "__dnssec")]
46use crate::{
47 ResponseCache,
48 net::{
49 DnsError, NetError, NoRecords,
50 dnssec::DnssecDnsHandle,
51 xfer::{DnsHandle as _, FirstAnswer as _},
52 },
53 proto::{
54 dnssec::TrustAnchors,
55 op::{DnsRequestOptions, ResponseCode},
56 rr::RecordType,
57 },
58};
59
60mod error;
61pub use error::{AuthorityData, RecursorError};
62
63mod handle;
64use handle::RecursorDnsHandle;
65
66#[cfg(test)]
67mod tests;
68
69/// A top down recursive resolver which operates off a list of roots for initial recursive requests.
70///
71/// This is the well known root nodes, referred to as hints in RFCs. See the IANA [Root Servers](https://www.iana.org/domains/root/servers) list.
72pub struct Recursor<P: ConnectionProvider> {
73 pub(super) mode: RecursorMode<P>,
74}
75
76#[cfg(feature = "tokio")]
77impl Recursor<TokioRuntimeProvider> {}
78
79impl<P: ConnectionProvider> Recursor<P> {
80 /// Build a new [`Recursor`] from the specified configuration
81 #[cfg(feature = "serde")]
82 pub fn from_config(
83 config: RecursiveConfig,
84 root_dir: Option<&Path>,
85 conn_provider: P,
86 ) -> Result<Self, RecursorError> {
87 let dnssec_policy =
88 DnssecPolicy::from_config(&config.dnssec_policy).map_err(|e| e.to_string())?;
89
90 #[allow(unused_mut, unused_assignments)]
91 let mut encrypted_transport_state = None;
92 #[cfg(all(feature = "toml", any(feature = "__tls", feature = "__quic")))]
93 {
94 // Before building the recursor, potentially load some pre-existing opportunistic encrypted
95 // nameserver state to configure on the builder.
96 encrypted_transport_state =
97 config.options.opportunistic_encryption.persisted_state()?;
98 }
99
100 let path = match root_dir {
101 Some(root_dir) => Cow::Owned(root_dir.join(&config.roots)),
102 None => Cow::Borrowed(&config.roots),
103 };
104
105 let roots_str = fs::read_to_string(path.as_ref()).map_err(|e| {
106 format!(
107 "failed to read roots file '{path}': {e}",
108 path = path.display()
109 )
110 })?;
111 let (_zone, roots_zone) =
112 Parser::new(roots_str, Some(path.into_owned()), Some(Name::root()))
113 .parse()
114 .map_err(|e| format!("failed to read roots {}: {e}", config.roots.display()))?;
115
116 let root_addrs = roots_zone
117 .values()
118 .flat_map(RecordSet::records_without_rrsigs)
119 .map(|r| &r.data)
120 .filter_map(RData::ip_addr) // we only want IPs
121 .collect::<Vec<_>>();
122
123 Self::new(
124 &root_addrs,
125 dnssec_policy,
126 encrypted_transport_state,
127 config.options.clone(),
128 conn_provider,
129 )
130 }
131
132 /// Build a DNSSEC-unaware [`Recursor`] without any name server transport state
133 pub fn with_options(
134 roots: &[IpAddr],
135 options: RecursorOptions,
136 conn_provider: P,
137 ) -> Result<Self, RecursorError> {
138 Self::new(roots, DnssecPolicy::default(), None, options, conn_provider)
139 }
140
141 /// Build a new [`Recursor`]
142 pub fn new(
143 roots: &[IpAddr],
144 dnssec_policy: DnssecPolicy,
145 encrypted_transport_state: Option<NameServerTransportState>,
146 options: RecursorOptions,
147 conn_provider: P,
148 ) -> Result<Self, RecursorError> {
149 let mut tls_config = TlsConfig::new()?;
150 if options.opportunistic_encryption.is_enabled() {
151 warn!("disabling TLS peer verification for opportunistic encryption mode");
152 tls_config.insecure_skip_verify();
153 }
154
155 #[cfg(feature = "__dnssec")]
156 let response_cache_size = options.response_cache_size;
157 #[cfg(feature = "__dnssec")]
158 let ttl_config = options.cache_policy.clone();
159 let handle = RecursorDnsHandle::new(
160 roots,
161 dnssec_policy.clone(),
162 encrypted_transport_state,
163 options,
164 tls_config,
165 conn_provider,
166 )?;
167
168 Ok(Self {
169 mode: match dnssec_policy {
170 DnssecPolicy::SecurityUnaware => RecursorMode::NonValidating { handle },
171 #[cfg(feature = "__dnssec")]
172 DnssecPolicy::ValidationDisabled => RecursorMode::NonValidating { handle },
173 #[cfg(feature = "__dnssec")]
174 DnssecPolicy::ValidateWithStaticKey(config) => RecursorMode::Validating(
175 ValidatingRecursor::new(handle, config, response_cache_size, ttl_config)?,
176 ),
177 },
178 })
179 }
180
181 /// Perform a recursive resolution
182 ///
183 /// [RFC 1034](https://datatracker.ietf.org/doc/html/rfc1034#section-5.3.3), Domain Concepts and Facilities, November 1987
184 ///
185 /// ```text
186 /// 5.3.3. Algorithm
187 ///
188 /// The top level algorithm has four steps:
189 ///
190 /// 1. See if the answer is in local information, and if so return
191 /// it to the client.
192 ///
193 /// 2. Find the best servers to ask.
194 ///
195 /// 3. Send them queries until one returns a response.
196 ///
197 /// 4. Analyze the response, either:
198 ///
199 /// a. if the response answers the question or contains a name
200 /// error, cache the data as well as returning it back to
201 /// the client.
202 ///
203 /// b. if the response contains a better delegation to other
204 /// servers, cache the delegation information, and go to
205 /// step 2.
206 ///
207 /// c. if the response shows a CNAME and that is not the
208 /// answer itself, cache the CNAME, change the SNAME to the
209 /// canonical name in the CNAME RR and go to step 1.
210 ///
211 /// d. if the response shows a servers failure or other
212 /// bizarre contents, delete the server from the SLIST and
213 /// go back to step 3.
214 ///
215 /// Step 1 searches the cache for the desired data. If the data is in the
216 /// cache, it is assumed to be good enough for normal use. Some resolvers
217 /// have an option at the user interface which will force the resolver to
218 /// ignore the cached data and consult with an authoritative server. This
219 /// is not recommended as the default. If the resolver has direct access to
220 /// a name server's zones, it should check to see if the desired data is
221 /// present in authoritative form, and if so, use the authoritative data in
222 /// preference to cached data.
223 ///
224 /// Step 2 looks for a name server to ask for the required data. The
225 /// general strategy is to look for locally-available name server RRs,
226 /// starting at SNAME, then the parent domain name of SNAME, the
227 /// grandparent, and so on toward the root. Thus if SNAME were
228 /// Mockapetris.ISI.EDU, this step would look for NS RRs for
229 /// Mockapetris.ISI.EDU, then ISI.EDU, then EDU, and then . (the root).
230 /// These NS RRs list the names of hosts for a zone at or above SNAME. Copy
231 /// the names into SLIST. Set up their addresses using local data. It may
232 /// be the case that the addresses are not available. The resolver has many
233 /// choices here; the best is to start parallel resolver processes looking
234 /// for the addresses while continuing onward with the addresses which are
235 /// available. Obviously, the design choices and options are complicated
236 /// and a function of the local host's capabilities. The recommended
237 /// priorities for the resolver designer are:
238 ///
239 /// 1. Bound the amount of work (packets sent, parallel processes
240 /// started) so that a request can't get into an infinite loop or
241 /// start off a chain reaction of requests or queries with other
242 /// implementations EVEN IF SOMEONE HAS INCORRECTLY CONFIGURED
243 /// SOME DATA.
244 ///
245 /// 2. Get back an answer if at all possible.
246 ///
247 /// 3. Avoid unnecessary transmissions.
248 ///
249 /// 4. Get the answer as quickly as possible.
250 ///
251 /// If the search for NS RRs fails, then the resolver initializes SLIST from
252 /// the safety belt SBELT. The basic idea is that when the resolver has no
253 /// idea what servers to ask, it should use information from a configuration
254 /// file that lists several servers which are expected to be helpful.
255 /// Although there are special situations, the usual choice is two of the
256 /// root servers and two of the servers for the host's domain. The reason
257 /// for two of each is for redundancy. The root servers will provide
258 /// eventual access to all of the domain space. The two local servers will
259 /// allow the resolver to continue to resolve local names if the local
260 /// network becomes isolated from the internet due to gateway or link
261 /// failure.
262 ///
263 /// In addition to the names and addresses of the servers, the SLIST data
264 /// structure can be sorted to use the best servers first, and to insure
265 /// that all addresses of all servers are used in a round-robin manner. The
266 /// sorting can be a simple function of preferring addresses on the local
267 /// network over others, or may involve statistics from past events, such as
268 /// previous response times and batting averages.
269 ///
270 /// Step 3 sends out queries until a response is received. The strategy is
271 /// to cycle around all of the addresses for all of the servers with a
272 /// timeout between each transmission. In practice it is important to use
273 /// all addresses of a multihomed host, and too aggressive a retransmission
274 /// policy actually slows response when used by multiple resolvers
275 /// contending for the same name server and even occasionally for a single
276 /// resolver. SLIST typically contains data values to control the timeouts
277 /// and keep track of previous transmissions.
278 ///
279 /// Step 4 involves analyzing responses. The resolver should be highly
280 /// paranoid in its parsing of responses. It should also check that the
281 /// response matches the query it sent using the ID field in the response.
282 ///
283 /// The ideal answer is one from a server authoritative for the query which
284 /// either gives the required data or a name error. The data is passed back
285 /// to the user and entered in the cache for future use if its TTL is
286 /// greater than zero.
287 ///
288 /// If the response shows a delegation, the resolver should check to see
289 /// that the delegation is "closer" to the answer than the servers in SLIST
290 /// are. This can be done by comparing the match count in SLIST with that
291 /// computed from SNAME and the NS RRs in the delegation. If not, the reply
292 /// is bogus and should be ignored. If the delegation is valid the NS
293 /// delegation RRs and any address RRs for the servers should be cached.
294 /// The name servers are entered in the SLIST, and the search is restarted.
295 ///
296 /// If the response contains a CNAME, the search is restarted at the CNAME
297 /// unless the response has the data for the canonical name or if the CNAME
298 /// is the answer itself.
299 ///
300 /// Details and implementation hints can be found in [RFC-1035].
301 ///
302 /// 6. A SCENARIO
303 ///
304 /// In our sample domain space, suppose we wanted separate administrative
305 /// control for the root, MIL, EDU, MIT.EDU and ISI.EDU zones. We might
306 /// allocate name servers as follows:
307 ///
308 ///
309 /// |(C.ISI.EDU,SRI-NIC.ARPA
310 /// | A.ISI.EDU)
311 /// +---------------------+------------------+
312 /// | | |
313 /// MIL EDU ARPA
314 /// |(SRI-NIC.ARPA, |(SRI-NIC.ARPA, |
315 /// | A.ISI.EDU | C.ISI.EDU) |
316 /// +-----+-----+ | +------+-----+-----+
317 /// | | | | | | |
318 /// BRL NOSC DARPA | IN-ADDR SRI-NIC ACC
319 /// |
320 /// +--------+------------------+---------------+--------+
321 /// | | | | |
322 /// UCI MIT | UDEL YALE
323 /// |(XX.LCS.MIT.EDU, ISI
324 /// |ACHILLES.MIT.EDU) |(VAXA.ISI.EDU,VENERA.ISI.EDU,
325 /// +---+---+ | A.ISI.EDU)
326 /// | | |
327 /// LCS ACHILLES +--+-----+-----+--------+
328 /// | | | | | |
329 /// XX A C VAXA VENERA Mockapetris
330 ///
331 /// In this example, the authoritative name server is shown in parentheses
332 /// at the point in the domain tree at which is assumes control.
333 ///
334 /// Thus the root name servers are on C.ISI.EDU, SRI-NIC.ARPA, and
335 /// A.ISI.EDU. The MIL domain is served by SRI-NIC.ARPA and A.ISI.EDU. The
336 /// EDU domain is served by SRI-NIC.ARPA. and C.ISI.EDU. Note that servers
337 /// may have zones which are contiguous or disjoint. In this scenario,
338 /// C.ISI.EDU has contiguous zones at the root and EDU domains. A.ISI.EDU
339 /// has contiguous zones at the root and MIL domains, but also has a non-
340 /// contiguous zone at ISI.EDU.
341 /// ```
342 pub async fn resolve(
343 &self,
344 query: Query,
345 request_time: Instant,
346 query_has_dnssec_ok: bool,
347 ) -> Result<Message, RecursorError> {
348 if !query.name().is_fqdn() {
349 return Err(RecursorError::from(
350 "query's domain name must be fully qualified",
351 ));
352 }
353
354 match &self.mode {
355 RecursorMode::NonValidating { handle } => {
356 handle
357 .resolve(
358 query,
359 request_time,
360 query_has_dnssec_ok,
361 0,
362 Arc::new(AtomicU8::new(0)),
363 )
364 .await
365 }
366
367 #[cfg(feature = "__dnssec")]
368 RecursorMode::Validating(validating) => {
369 validating
370 .resolve(query, request_time, query_has_dnssec_ok)
371 .await
372 }
373 }
374 }
375
376 /// Get the recursor's [`PoolContext`].
377 pub fn pool_context(&self) -> &Arc<PoolContext> {
378 match &self.mode {
379 RecursorMode::NonValidating { handle, .. } => handle.pool_context(),
380 #[cfg(feature = "__dnssec")]
381 RecursorMode::Validating(validating) => validating.handle.inner().pool_context(),
382 }
383 }
384
385 /// Whether the recursive resolver is a validating resolver
386 pub fn is_validating(&self) -> bool {
387 // matching on `NonValidating` to avoid conditional compilation (`#[cfg]`)
388 !matches!(self.mode, RecursorMode::NonValidating { .. })
389 }
390}
391
392#[allow(clippy::large_enum_variant)]
393pub(super) enum RecursorMode<P: ConnectionProvider> {
394 NonValidating {
395 handle: RecursorDnsHandle<P>,
396 },
397
398 #[cfg(feature = "__dnssec")]
399 Validating(ValidatingRecursor<P>),
400}
401
402#[cfg(feature = "__dnssec")]
403pub(crate) struct ValidatingRecursor<P: ConnectionProvider> {
404 pub(crate) handle: DnssecDnsHandle<RecursorDnsHandle<P>>,
405 // This is a separate response cache from that inside `RecursorDnsHandle`.
406 pub(crate) validated_response_cache: ResponseCache,
407 #[cfg(feature = "metrics")]
408 metrics: RecursorMetrics,
409}
410
411#[cfg(feature = "__dnssec")]
412impl<P: ConnectionProvider> ValidatingRecursor<P> {
413 pub(crate) fn new(
414 handle: RecursorDnsHandle<P>,
415 config: DnssecConfig,
416 response_cache_size: u64,
417 ttl_config: TtlConfig,
418 ) -> Result<Self, RecursorError> {
419 let validated_response_cache = ResponseCache::new(response_cache_size, ttl_config.clone());
420 let trust_anchor = match config.trust_anchor {
421 Some(anchor) if anchor.is_empty() => {
422 return Err(RecursorError::from("trust anchor must not be empty"));
423 }
424 Some(anchor) => anchor,
425 None => Arc::new(TrustAnchors::default()),
426 };
427
428 #[cfg(feature = "metrics")]
429 let metrics = handle.metrics.clone();
430
431 let mut handle = DnssecDnsHandle::with_trust_anchor(handle, trust_anchor)
432 .nsec3_iteration_limits(
433 config.nsec3_soft_iteration_limit,
434 config.nsec3_hard_iteration_limit,
435 )
436 .negative_validation_ttl(ttl_config.negative_response_ttl_bounds(RecordType::RRSIG))
437 .positive_validation_ttl(ttl_config.positive_response_ttl_bounds(RecordType::RRSIG));
438
439 if let Some(validation_cache_size) = config.validation_cache_size {
440 handle = handle.validation_cache_size(validation_cache_size);
441 }
442
443 Ok(Self {
444 validated_response_cache,
445 #[cfg(feature = "metrics")]
446 metrics,
447 handle,
448 })
449 }
450
451 async fn resolve(
452 &self,
453 query: Query,
454 request_time: Instant,
455 query_has_dnssec_ok: bool,
456 ) -> Result<Message, RecursorError> {
457 if let Some(Ok(response)) = self.validated_response_cache.get(&query, request_time) {
458 // Increment metrics on cache hits only. We will check the cache a second time
459 // inside resolve(), thus we only track cache misses there.
460 #[cfg(feature = "metrics")]
461 {
462 self.metrics.cache_hit_counter.increment(1);
463 self.metrics
464 .dnssec_metrics
465 .increment_proof_counter(&response);
466 self.metrics
467 .validated_cache_size
468 .set(self.validated_response_cache.entry_count() as f64);
469 }
470
471 let none_indeterminate = response
472 .all_sections()
473 .all(|record| !record.proof.is_indeterminate());
474
475 // if the cached response is a referral, or if any record is indeterminate, fall
476 // through and perform DNSSEC validation
477 if response.authoritative && none_indeterminate {
478 let result = response.maybe_strip_dnssec_records(query_has_dnssec_ok);
479 #[cfg(feature = "metrics")]
480 self.metrics
481 .cache_hit_duration
482 .record(request_time.elapsed());
483 return Ok(result);
484 }
485 }
486
487 let mut options = DnsRequestOptions::default();
488 // a validating recursor must be security aware
489 options.use_edns = true;
490 options.edns_set_dnssec_ok = true;
491
492 let response = self
493 .handle
494 .lookup(query.clone(), options)
495 .first_answer()
496 .await?;
497
498 // Return NXDomain and NoData responses in error form
499 // These need to bypass the cache lookup (and casting to a Lookup object in general)
500 // to preserve SOA and DNSSEC records, and to keep those records in the authorities
501 // section of the response.
502 if response.response_code == ResponseCode::NXDomain {
503 use crate::recursor::RecursorError;
504
505 let Err(dns_error) = DnsError::from_response(response) else {
506 return Err(RecursorError::from(
507 "unable to build ProtoError from response {response:?}",
508 ));
509 };
510
511 Err(RecursorError::Net(NetError::from(dns_error)))
512 } else if response.answers.is_empty()
513 && !response.authorities.is_empty()
514 && response.response_code == ResponseCode::NoError
515 {
516 let mut no_records = NoRecords::new(query.clone(), ResponseCode::NoError);
517 no_records.soa = response
518 .soa()
519 .as_ref()
520 .map(|record| Box::new(record.to_owned()));
521 no_records.authorities = Some(
522 response
523 .authorities
524 .iter()
525 .filter_map(|x| match x.record_type() {
526 RecordType::SOA => None,
527 _ => Some(x.clone()),
528 })
529 .collect(),
530 );
531
532 Err(RecursorError::from(NetError::from(no_records)))
533 } else {
534 let message = response.into_message();
535 #[cfg(feature = "metrics")]
536 self.metrics
537 .dnssec_metrics
538 .increment_proof_counter(&message);
539 self.validated_response_cache
540 .insert(query.clone(), Ok(message.clone()), request_time);
541 #[cfg(feature = "metrics")]
542 self.metrics
543 .validated_cache_size
544 .set(self.validated_response_cache.entry_count() as f64);
545 Ok(message.maybe_strip_dnssec_records(query_has_dnssec_ok))
546 }
547 }
548}
549
550/// Configuration for recursive resolver zones
551#[cfg(feature = "serde")]
552#[derive(Clone, Deserialize, Eq, PartialEq, Debug)]
553#[serde(deny_unknown_fields)]
554pub struct RecursiveConfig {
555 /// File with roots, aka hints
556 pub roots: PathBuf,
557 /// DNSSEC policy
558 #[serde(default)]
559 pub dnssec_policy: DnssecPolicyConfig,
560 /// Options for the recursor
561 #[serde(flatten)]
562 pub options: RecursorOptions,
563}
564
565/// Options for the [`Recursor`]
566#[cfg_attr(feature = "serde", derive(Deserialize))]
567#[derive(Clone, Eq, PartialEq, Debug)]
568#[cfg_attr(feature = "serde", serde(deny_unknown_fields))]
569pub struct RecursorOptions {
570 /// Maximum nameserver cache size
571 #[cfg_attr(feature = "serde", serde(default = "default_ns_cache_size"))]
572 pub ns_cache_size: usize,
573
574 /// Maximum DNS response cache size
575 #[cfg_attr(
576 feature = "serde",
577 serde(default = "default_response_cache_size", alias = "record_cache_size")
578 )]
579 pub response_cache_size: u64,
580
581 /// Maximum recursion depth for queries
582 ///
583 /// Setting to 0 will fail all requests requiring recursion.
584 #[cfg_attr(feature = "serde", serde(default = "recursion_limit_default"))]
585 pub recursion_limit: u8,
586
587 /// Maximum recursion depth for building NS pools
588 ///
589 /// Setting to 0 will fail all requests requiring recursion.
590 #[cfg_attr(feature = "serde", serde(default = "ns_recursion_limit_default"))]
591 pub ns_recursion_limit: u8,
592
593 /// Networks that will not be filtered from responses. This overrides anything present in
594 /// deny_answers
595 #[cfg_attr(feature = "serde", serde(default))]
596 pub allow_answers: Vec<IpNet>,
597
598 /// Networks that will be filtered from responses
599 #[cfg_attr(feature = "serde", serde(default))]
600 pub deny_answers: Vec<IpNet>,
601
602 /// Networks that will be queried during resolution
603 #[cfg_attr(feature = "serde", serde(default))]
604 pub allow_server: Vec<IpNet>,
605
606 /// Networks that will not be queried during resolution
607 #[cfg_attr(feature = "serde", serde(default = "deny_server_default"))]
608 pub deny_server: Vec<IpNet>,
609
610 /// Local UDP ports to avoid when making outgoing queries
611 #[cfg_attr(feature = "serde", serde(default))]
612 pub avoid_local_udp_ports: HashSet<u16>,
613
614 /// Caching policy, setting minimum and maximum TTLs
615 #[cfg_attr(feature = "serde", serde(default))]
616 pub cache_policy: TtlConfig,
617
618 /// Enable case randomization.
619 ///
620 /// Randomize the case of letters in query names, and require that responses preserve the case
621 /// of the query name, in order to mitigate spoofing attacks. This is only applied over UDP.
622 ///
623 /// This implements the mechanism described in
624 /// [draft-vixie-dnsext-dns0x20-00](https://datatracker.ietf.org/doc/html/draft-vixie-dnsext-dns0x20-00).
625 #[cfg_attr(feature = "serde", serde(default))]
626 pub case_randomization: bool,
627
628 /// Configure RFC 9539 opportunistic encryption.
629 #[cfg_attr(feature = "serde", serde(default))]
630 pub opportunistic_encryption: OpportunisticEncryption,
631
632 /// Configure the EDNS UDP payload size used in queries.
633 ///
634 /// See [DnsRequestOptions::edns_payload_len][crate::proto::op::DnsRequestOptions::edns_payload_len].
635 #[cfg_attr(feature = "serde", serde(default = "default_edns_payload_len"))]
636 pub edns_payload_len: u16,
637}
638
639impl Default for RecursorOptions {
640 fn default() -> Self {
641 Self {
642 ns_cache_size: 1_024,
643 response_cache_size: 1_048_576,
644 recursion_limit: 24,
645 ns_recursion_limit: 24,
646 allow_answers: Vec::new(),
647 deny_answers: Vec::new(),
648 allow_server: Vec::new(),
649 deny_server: RECOMMENDED_SERVER_FILTERS.to_vec(),
650 avoid_local_udp_ports: HashSet::new(),
651 cache_policy: TtlConfig::default(),
652 case_randomization: false,
653 opportunistic_encryption: OpportunisticEncryption::default(),
654 edns_payload_len: default_edns_payload_len(),
655 }
656 }
657}
658
659#[cfg(feature = "serde")]
660fn default_ns_cache_size() -> usize {
661 1_024
662}
663
664#[cfg(feature = "serde")]
665fn default_response_cache_size() -> u64 {
666 1_048_576
667}
668
669#[cfg(feature = "serde")]
670fn recursion_limit_default() -> u8 {
671 24
672}
673
674#[cfg(feature = "serde")]
675fn ns_recursion_limit_default() -> u8 {
676 24
677}
678
679#[cfg(feature = "serde")]
680fn deny_server_default() -> Vec<IpNet> {
681 RECOMMENDED_SERVER_FILTERS.to_vec()
682}
683
684fn default_edns_payload_len() -> u16 {
685 DEFAULT_MAX_PAYLOAD_LEN
686}
687
688/// `Recursor`'s DNSSEC policy
689// `Copy` can only be implemented when `dnssec` is disabled we don't want to remove a trait
690// implementation when a feature is enabled as features are meant to be additive
691#[derive(Clone, Default)]
692pub enum DnssecPolicy {
693 /// security unaware; DNSSEC records will not be requested nor processed
694 #[default]
695 SecurityUnaware,
696
697 /// DNSSEC validation is disabled; DNSSEC records will be requested and processed
698 #[cfg(feature = "__dnssec")]
699 ValidationDisabled,
700
701 /// DNSSEC validation is enabled and will use the chosen `trust_anchor` set of keys
702 #[cfg(feature = "__dnssec")]
703 ValidateWithStaticKey(DnssecConfig),
704 // TODO RFC5011
705 // ValidateWithInitialKey { .. },}
706}
707
708impl DnssecPolicy {
709 #[cfg(feature = "serde")]
710 fn from_config(config: &DnssecPolicyConfig) -> Result<Self, ParseError> {
711 Ok(match config {
712 DnssecPolicyConfig::SecurityUnaware => Self::SecurityUnaware,
713 #[cfg(feature = "__dnssec")]
714 DnssecPolicyConfig::ValidationDisabled => Self::ValidationDisabled,
715 #[cfg(feature = "__dnssec")]
716 DnssecPolicyConfig::ValidateWithStaticKey {
717 path,
718 nsec3_soft_iteration_limit,
719 nsec3_hard_iteration_limit,
720 validation_cache_size,
721 } => Self::ValidateWithStaticKey(DnssecConfig {
722 trust_anchor: path
723 .as_ref()
724 .map(|path| TrustAnchors::from_file(path))
725 .transpose()?
726 .map(Arc::new),
727 nsec3_soft_iteration_limit: *nsec3_soft_iteration_limit,
728 nsec3_hard_iteration_limit: *nsec3_hard_iteration_limit,
729 validation_cache_size: *validation_cache_size,
730 }),
731 })
732 }
733
734 pub(crate) fn is_security_aware(&self) -> bool {
735 !matches!(self, Self::SecurityUnaware)
736 }
737}
738
739/// DNSSEC configuration options for use in [`DnssecPolicy`]
740#[cfg(feature = "__dnssec")]
741#[non_exhaustive]
742#[derive(Clone, Default)]
743pub struct DnssecConfig {
744 /// set to `None` to use built-in trust anchor
745 pub trust_anchor: Option<Arc<TrustAnchors>>,
746 /// NSEC3 soft iteration limit. Responses with NSEC3 records having an iteration count
747 /// exceeding this value, but less than the hard limit, will return Proof::Insecure
748 pub nsec3_soft_iteration_limit: Option<u16>,
749 /// NSEC3 hard iteration limit. Responses with NSEC3 responses having an iteration count
750 /// exceeding this value will return Proof::Bogus
751 pub nsec3_hard_iteration_limit: Option<u16>,
752 /// Validation cache size. Controls how many DNSSEC validations are cached for future
753 /// use.
754 pub validation_cache_size: Option<usize>,
755}
756
757/// DNSSEC policy configuration
758#[cfg(feature = "serde")]
759#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq)]
760#[serde(deny_unknown_fields)]
761pub enum DnssecPolicyConfig {
762 /// security unaware; DNSSEC records will not be requested nor processed
763 #[default]
764 SecurityUnaware,
765
766 /// DNSSEC validation is disabled; DNSSEC records will be requested and processed
767 #[cfg(feature = "__dnssec")]
768 ValidationDisabled,
769
770 /// DNSSEC validation is enabled and will use the chosen `trust_anchor` set of keys
771 #[cfg(feature = "__dnssec")]
772 ValidateWithStaticKey {
773 /// set to `None` to use built-in trust anchor
774 path: Option<PathBuf>,
775 /// set to control the 'soft' NSEC3 iteration limit. Responses where valid NSEC3 records are
776 /// returned having an iteration count above this limit, but below the hard limit, will
777 /// be considered insecure (answered without the AD bit set.)
778 nsec3_soft_iteration_limit: Option<u16>,
779 /// set to control the 'hard' NSEC3 iteration limit. Responses where valid NSEC3 records are
780 /// returned having an iteration count above this limit will be considered Bogus and will
781 /// result in a SERVFAIL response being returned to the requester.
782 nsec3_hard_iteration_limit: Option<u16>,
783 /// set to control the size of the DNSSEC validation cache. Set to none to use the default
784 validation_cache_size: Option<usize>,
785 },
786}
787
788/// Bailiwick/sub zone checking.
789///
790/// # Overview
791///
792/// This function checks that two host names have a parent/child relationship, but does so more strictly than elsewhere in the libraries
793/// (see implementation notes.)
794///
795/// A resolver should not return answers outside of its delegated authority -- if we receive a delegation from the root servers for
796/// "example.com", that server should only return answers related to example.com or a sub-domain thereof. Note that record data may point
797/// to out-of-bailwick records (e.g., example.com could return a CNAME record for www.example.com that points to example.cdnprovider.net,)
798/// but it should not return a record name that is out-of-bailiwick (e.g., we ask for www.example.com and it returns www.otherdomain.com.)
799///
800/// Out-of-bailiwick responses have been used in cache poisoning attacks.
801///
802/// ## Examples
803///
804/// | Parent | Child | Expected Result |
805/// |--------------|----------------------|------------------------------------------------------------------|
806/// | . | com. | In-bailiwick (true) |
807/// | com. | example.net. | Out-of-bailiwick (false) |
808/// | example.com. | www.example.com. | In-bailiwick (true) |
809/// | example.com. | www.otherdomain.com. | Out-of-bailiwick (false) |
810/// | example.com | www.example.com. | Out-of-bailiwick (false, note the parent is not fully qualified) |
811///
812/// # Implementation Notes
813///
814/// * This function is nominally a wrapper around Name::zone_of, with two additional checks:
815/// * If the caller doesn't provide a parent at all, we'll return false.
816/// * If the domains have mixed qualification -- that is, if one is fully-qualified and the other partially-qualified, we'll return
817/// false.
818///
819/// # References
820///
821/// * [RFC 8499](https://datatracker.ietf.org/doc/html/rfc8499) -- DNS Terminology (see page 25)
822/// * [The Hitchiker's Guide to DNS Cache Poisoning](https://www.cs.utexas.edu/%7Eshmat/shmat_securecomm10.pdf) -- for a more in-depth
823/// discussion of DNS cache poisoning attacks, see section 4, specifically, for a discussion of the Bailiwick rule.
824fn is_subzone(parent: &Name, child: &Name) -> bool {
825 if parent.is_empty() {
826 return false;
827 }
828
829 if (parent.is_fqdn() && !child.is_fqdn()) || (!parent.is_fqdn() && child.is_fqdn()) {
830 return false;
831 }
832
833 parent.zone_of(child)
834}
835
836const RECOMMENDED_SERVER_FILTERS: [IpNet; 22] = [
837 IpNet::new_assert(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 0)), 8), // Loopback range
838 IpNet::new_assert(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 8), // Unspecified range
839 IpNet::new_assert(IpAddr::V4(Ipv4Addr::BROADCAST), 32), // Directed Broadcast
840 IpNet::new_assert(IpAddr::V4(Ipv4Addr::new(10, 0, 0, 0)), 8), // RFC 1918 space
841 IpNet::new_assert(IpAddr::V4(Ipv4Addr::new(172, 16, 0, 0)), 12), // RFC 1918 space
842 IpNet::new_assert(IpAddr::V4(Ipv4Addr::new(192, 168, 0, 0)), 16), // RFC 1918 space
843 IpNet::new_assert(IpAddr::V4(Ipv4Addr::new(100, 64, 0, 0)), 10), // CG NAT
844 IpNet::new_assert(IpAddr::V4(Ipv4Addr::new(169, 254, 0, 0)), 16), // Link-local space
845 IpNet::new_assert(IpAddr::V4(Ipv4Addr::new(192, 0, 0, 0)), 24), // IETF Reserved
846 IpNet::new_assert(IpAddr::V4(Ipv4Addr::new(192, 0, 2, 0)), 24), // TEST-NET-1
847 IpNet::new_assert(IpAddr::V4(Ipv4Addr::new(198, 51, 100, 0)), 24), // TEST-NET-2
848 IpNet::new_assert(IpAddr::V4(Ipv4Addr::new(203, 0, 113, 0)), 24), // TEST-NET-3
849 IpNet::new_assert(IpAddr::V4(Ipv4Addr::new(240, 0, 0, 0)), 4), // Class E Reserved
850 IpNet::new_assert(IpAddr::V6(Ipv6Addr::LOCALHOST), 128), // v6 loopback
851 IpNet::new_assert(IpAddr::V6(Ipv6Addr::UNSPECIFIED), 128), // v6 unspecified
852 IpNet::new_assert(IpAddr::V6(Ipv6Addr::new(0x100, 0, 0, 0, 0, 0, 0, 0)), 64), // v6 discard prefix
853 IpNet::new_assert(
854 IpAddr::V6(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0)),
855 32,
856 ), // v6 documentation prefix
857 IpNet::new_assert(IpAddr::V6(Ipv6Addr::new(0x3fff, 0, 0, 0, 0, 0, 0, 0)), 20), // v6 documentation prefix
858 IpNet::new_assert(IpAddr::V6(Ipv6Addr::new(0x5f00, 0, 0, 0, 0, 0, 0, 0)), 16), // v6 segment routing prefix
859 IpNet::new_assert(IpAddr::V6(Ipv6Addr::new(0xfc00, 0, 0, 0, 0, 0, 0, 0)), 7), // v6 private address,
860 IpNet::new_assert(IpAddr::V6(Ipv6Addr::new(0xfe80, 0, 0, 0, 0, 0, 0, 0)), 64), // v6 link local
861 IpNet::new_assert(IpAddr::V6(Ipv6Addr::new(0xff00, 0, 0, 0, 0, 0, 0, 0)), 8), // v6 multicast
862];