Skip to main content

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];