hickory_recursor/
recursor.rs

1// Copyright 2015-2023 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
8use std::{
9    collections::HashSet,
10    net::{IpAddr, Ipv4Addr, Ipv6Addr},
11    sync::{Arc, atomic::AtomicU8},
12    time::Instant,
13};
14
15use ipnet::IpNet;
16
17use crate::{
18    DnssecPolicy, Error,
19    proto::op::Query,
20    recursor_dns_handle::RecursorDnsHandle,
21    resolver::{config::NameServerConfigGroup, dns_lru::TtlConfig, lookup::Lookup},
22};
23#[cfg(feature = "__dnssec")]
24use crate::{
25    ErrorKind,
26    proto::{
27        ProtoError,
28        dnssec::{DnssecDnsHandle, TrustAnchors},
29        op::ResponseCode,
30        rr::{Record, RecordType, resource::RecordRef},
31        xfer::{DnsHandle as _, DnsRequestOptions, FirstAnswer as _},
32    },
33    resolver::dns_lru::DnsLru,
34};
35
36/// A `Recursor` builder
37#[derive(Clone)]
38pub struct RecursorBuilder {
39    ns_cache_size: usize,
40    record_cache_size: usize,
41    /// This controls how many nested lookups will be attempted to resolve a CNAME chain. Setting it
42    /// to None will disable the recursion limit check, and is not recommended.
43    recursion_limit: Option<u8>,
44    /// This controls how many nested lookups will be attempted when trying to build an NS pool.
45    /// Setting it to None will disable the recursion limit check, and is not recommended.
46    ns_recursion_limit: Option<u8>,
47    dnssec_policy: DnssecPolicy,
48    allow_servers: Vec<IpNet>,
49    deny_servers: Vec<IpNet>,
50    avoid_local_udp_ports: HashSet<u16>,
51    ttl_config: TtlConfig,
52    case_randomization: bool,
53}
54
55impl RecursorBuilder {
56    /// Sets the size of the list of cached name servers
57    pub fn ns_cache_size(mut self, size: usize) -> Self {
58        self.ns_cache_size = size;
59        self
60    }
61
62    /// Sets the size of the list of cached records
63    pub fn record_cache_size(mut self, size: usize) -> Self {
64        self.record_cache_size = size;
65        self
66    }
67
68    /// Sets the maximum recursion depth for queries; set to None for unlimited
69    /// recursion.
70    pub fn recursion_limit(mut self, limit: Option<u8>) -> Self {
71        self.recursion_limit = limit;
72        self
73    }
74
75    /// Sets the maximum recursion depth for building NS pools; set to None for unlimited
76    /// recursion.
77    pub fn ns_recursion_limit(mut self, limit: Option<u8>) -> Self {
78        self.ns_recursion_limit = limit;
79        self
80    }
81
82    /// Sets the DNSSEC policy
83    pub fn dnssec_policy(mut self, dnssec_policy: DnssecPolicy) -> Self {
84        self.dnssec_policy = dnssec_policy;
85        self
86    }
87
88    /// Add networks that should not be queried during recursive resolution
89    pub fn nameserver_filter<'a>(
90        mut self,
91        allow: impl Iterator<Item = &'a IpNet>,
92        deny: impl Iterator<Item = &'a IpNet>,
93    ) -> Self {
94        for addr in RECOMMENDED_SERVER_FILTERS {
95            self.deny_servers.push(addr);
96        }
97
98        self.allow_servers.extend(allow);
99        self.deny_servers.extend(deny);
100        self
101    }
102
103    /// Sets local UDP ports that should be avoided when making outgoing queries
104    pub fn avoid_local_udp_ports(mut self, ports: HashSet<u16>) -> Self {
105        self.avoid_local_udp_ports = ports;
106        self
107    }
108
109    /// Sets the minimum and maximum TTL values for cached responses
110    pub fn ttl_config(mut self, ttl_config: TtlConfig) -> Self {
111        self.ttl_config = ttl_config;
112        self
113    }
114
115    /// Enable case randomization.
116    ///
117    /// Sets whether to randomize the case of letters in query names, and require that responses
118    /// preserve the case.
119    pub fn case_randomization(mut self, case_randomization: bool) -> Self {
120        self.case_randomization = case_randomization;
121        self
122    }
123
124    /// Construct a new recursor using the list of NameServerConfigs for the root node list
125    ///
126    /// # Panics
127    ///
128    /// This will panic if the roots are empty.
129    pub fn build(self, roots: impl Into<NameServerConfigGroup>) -> Result<Recursor, Error> {
130        Recursor::build(roots, self)
131    }
132}
133
134/// A top down recursive resolver which operates off a list of roots for initial recursive requests.
135///
136/// 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.
137pub struct Recursor {
138    mode: RecursorMode,
139}
140
141impl Recursor {
142    /// Construct the new [`Recursor`] via the [`RecursorBuilder`]
143    pub fn builder() -> RecursorBuilder {
144        RecursorBuilder::default()
145    }
146
147    /// Whether the recursive resolver is a validating resolver
148    pub fn is_validating(&self) -> bool {
149        // matching on `NonValidating` to avoid conditional compilation (`#[cfg]`)
150        !matches!(self.mode, RecursorMode::NonValidating { .. })
151    }
152
153    #[allow(clippy::too_many_arguments)]
154    fn build(
155        roots: impl Into<NameServerConfigGroup>,
156        builder: RecursorBuilder,
157    ) -> Result<Self, Error> {
158        let RecursorBuilder {
159            ns_cache_size,
160            record_cache_size,
161            recursion_limit,
162            ns_recursion_limit,
163            dnssec_policy,
164            allow_servers,
165            deny_servers,
166            avoid_local_udp_ports,
167            ttl_config,
168            case_randomization,
169        } = builder;
170
171        let handle = RecursorDnsHandle::new(
172            roots,
173            ns_cache_size,
174            record_cache_size,
175            recursion_limit,
176            ns_recursion_limit,
177            dnssec_policy.is_security_aware(),
178            allow_servers,
179            deny_servers,
180            Arc::new(avoid_local_udp_ports),
181            ttl_config,
182            case_randomization,
183        );
184
185        let mode = match dnssec_policy {
186            DnssecPolicy::SecurityUnaware => RecursorMode::NonValidating { handle },
187
188            #[cfg(feature = "__dnssec")]
189            DnssecPolicy::ValidationDisabled => RecursorMode::NonValidating { handle },
190
191            #[cfg(feature = "__dnssec")]
192            DnssecPolicy::ValidateWithStaticKey { trust_anchor } => {
193                let record_cache = handle.record_cache().clone();
194                let trust_anchor = match trust_anchor {
195                    Some(anchor) if anchor.is_empty() => {
196                        return Err(Error::from("trust anchor must not be empty"));
197                    }
198                    Some(anchor) => anchor,
199                    None => Arc::new(TrustAnchors::default()),
200                };
201
202                RecursorMode::Validating {
203                    record_cache,
204                    handle: DnssecDnsHandle::with_trust_anchor(handle, trust_anchor),
205                }
206            }
207        };
208
209        Ok(Self { mode })
210    }
211
212    /// Perform a recursive resolution
213    ///
214    /// [RFC 1034](https://datatracker.ietf.org/doc/html/rfc1034#section-5.3.3), Domain Concepts and Facilities, November 1987
215    ///
216    /// ```text
217    /// 5.3.3. Algorithm
218    ///
219    /// The top level algorithm has four steps:
220    ///
221    ///    1. See if the answer is in local information, and if so return
222    ///       it to the client.
223    ///
224    ///    2. Find the best servers to ask.
225    ///
226    ///    3. Send them queries until one returns a response.
227    ///
228    ///    4. Analyze the response, either:
229    ///
230    ///          a. if the response answers the question or contains a name
231    ///             error, cache the data as well as returning it back to
232    ///             the client.
233    ///
234    ///          b. if the response contains a better delegation to other
235    ///             servers, cache the delegation information, and go to
236    ///             step 2.
237    ///
238    ///          c. if the response shows a CNAME and that is not the
239    ///             answer itself, cache the CNAME, change the SNAME to the
240    ///             canonical name in the CNAME RR and go to step 1.
241    ///
242    ///          d. if the response shows a servers failure or other
243    ///             bizarre contents, delete the server from the SLIST and
244    ///             go back to step 3.
245    ///
246    /// Step 1 searches the cache for the desired data. If the data is in the
247    /// cache, it is assumed to be good enough for normal use.  Some resolvers
248    /// have an option at the user interface which will force the resolver to
249    /// ignore the cached data and consult with an authoritative server.  This
250    /// is not recommended as the default.  If the resolver has direct access to
251    /// a name server's zones, it should check to see if the desired data is
252    /// present in authoritative form, and if so, use the authoritative data in
253    /// preference to cached data.
254    ///
255    /// Step 2 looks for a name server to ask for the required data.  The
256    /// general strategy is to look for locally-available name server RRs,
257    /// starting at SNAME, then the parent domain name of SNAME, the
258    /// grandparent, and so on toward the root.  Thus if SNAME were
259    /// Mockapetris.ISI.EDU, this step would look for NS RRs for
260    /// Mockapetris.ISI.EDU, then ISI.EDU, then EDU, and then . (the root).
261    /// These NS RRs list the names of hosts for a zone at or above SNAME.  Copy
262    /// the names into SLIST.  Set up their addresses using local data.  It may
263    /// be the case that the addresses are not available.  The resolver has many
264    /// choices here; the best is to start parallel resolver processes looking
265    /// for the addresses while continuing onward with the addresses which are
266    /// available.  Obviously, the design choices and options are complicated
267    /// and a function of the local host's capabilities.  The recommended
268    /// priorities for the resolver designer are:
269    ///
270    ///    1. Bound the amount of work (packets sent, parallel processes
271    ///       started) so that a request can't get into an infinite loop or
272    ///       start off a chain reaction of requests or queries with other
273    ///       implementations EVEN IF SOMEONE HAS INCORRECTLY CONFIGURED
274    ///       SOME DATA.
275    ///
276    ///    2. Get back an answer if at all possible.
277    ///
278    ///    3. Avoid unnecessary transmissions.
279    ///
280    ///    4. Get the answer as quickly as possible.
281    ///
282    /// If the search for NS RRs fails, then the resolver initializes SLIST from
283    /// the safety belt SBELT.  The basic idea is that when the resolver has no
284    /// idea what servers to ask, it should use information from a configuration
285    /// file that lists several servers which are expected to be helpful.
286    /// Although there are special situations, the usual choice is two of the
287    /// root servers and two of the servers for the host's domain.  The reason
288    /// for two of each is for redundancy.  The root servers will provide
289    /// eventual access to all of the domain space.  The two local servers will
290    /// allow the resolver to continue to resolve local names if the local
291    /// network becomes isolated from the internet due to gateway or link
292    /// failure.
293    ///
294    /// In addition to the names and addresses of the servers, the SLIST data
295    /// structure can be sorted to use the best servers first, and to insure
296    /// that all addresses of all servers are used in a round-robin manner.  The
297    /// sorting can be a simple function of preferring addresses on the local
298    /// network over others, or may involve statistics from past events, such as
299    /// previous response times and batting averages.
300    ///
301    /// Step 3 sends out queries until a response is received.  The strategy is
302    /// to cycle around all of the addresses for all of the servers with a
303    /// timeout between each transmission.  In practice it is important to use
304    /// all addresses of a multihomed host, and too aggressive a retransmission
305    /// policy actually slows response when used by multiple resolvers
306    /// contending for the same name server and even occasionally for a single
307    /// resolver.  SLIST typically contains data values to control the timeouts
308    /// and keep track of previous transmissions.
309    ///
310    /// Step 4 involves analyzing responses.  The resolver should be highly
311    /// paranoid in its parsing of responses.  It should also check that the
312    /// response matches the query it sent using the ID field in the response.
313    ///
314    /// The ideal answer is one from a server authoritative for the query which
315    /// either gives the required data or a name error.  The data is passed back
316    /// to the user and entered in the cache for future use if its TTL is
317    /// greater than zero.
318    ///
319    /// If the response shows a delegation, the resolver should check to see
320    /// that the delegation is "closer" to the answer than the servers in SLIST
321    /// are.  This can be done by comparing the match count in SLIST with that
322    /// computed from SNAME and the NS RRs in the delegation.  If not, the reply
323    /// is bogus and should be ignored.  If the delegation is valid the NS
324    /// delegation RRs and any address RRs for the servers should be cached.
325    /// The name servers are entered in the SLIST, and the search is restarted.
326    ///
327    /// If the response contains a CNAME, the search is restarted at the CNAME
328    /// unless the response has the data for the canonical name or if the CNAME
329    /// is the answer itself.
330    ///
331    /// Details and implementation hints can be found in [RFC-1035].
332    ///
333    /// 6. A SCENARIO
334    ///
335    /// In our sample domain space, suppose we wanted separate administrative
336    /// control for the root, MIL, EDU, MIT.EDU and ISI.EDU zones.  We might
337    /// allocate name servers as follows:
338    ///
339    ///
340    ///                                    |(C.ISI.EDU,SRI-NIC.ARPA
341    ///                                    | A.ISI.EDU)
342    ///              +---------------------+------------------+
343    ///              |                     |                  |
344    ///             MIL                   EDU                ARPA
345    ///              |(SRI-NIC.ARPA,       |(SRI-NIC.ARPA,    |
346    ///              | A.ISI.EDU           | C.ISI.EDU)       |
347    ///        +-----+-----+               |     +------+-----+-----+
348    ///        |     |     |               |     |      |           |
349    ///       BRL  NOSC  DARPA             |  IN-ADDR  SRI-NIC     ACC
350    ///                                    |
351    ///        +--------+------------------+---------------+--------+
352    ///        |        |                  |               |        |
353    ///       UCI      MIT                 |              UDEL     YALE
354    ///                 |(XX.LCS.MIT.EDU, ISI
355    ///                 |ACHILLES.MIT.EDU) |(VAXA.ISI.EDU,VENERA.ISI.EDU,
356    ///             +---+---+              | A.ISI.EDU)
357    ///             |       |              |
358    ///            LCS   ACHILLES +--+-----+-----+--------+
359    ///             |             |  |     |     |        |
360    ///             XX            A  C   VAXA  VENERA Mockapetris
361    ///
362    /// In this example, the authoritative name server is shown in parentheses
363    /// at the point in the domain tree at which is assumes control.
364    ///
365    /// Thus the root name servers are on C.ISI.EDU, SRI-NIC.ARPA, and
366    /// A.ISI.EDU.  The MIL domain is served by SRI-NIC.ARPA and A.ISI.EDU.  The
367    /// EDU domain is served by SRI-NIC.ARPA. and C.ISI.EDU.  Note that servers
368    /// may have zones which are contiguous or disjoint.  In this scenario,
369    /// C.ISI.EDU has contiguous zones at the root and EDU domains.  A.ISI.EDU
370    /// has contiguous zones at the root and MIL domains, but also has a non-
371    /// contiguous zone at ISI.EDU.
372    /// ```
373    pub async fn resolve(
374        &self,
375        query: Query,
376        request_time: Instant,
377        query_has_dnssec_ok: bool,
378    ) -> Result<Lookup, Error> {
379        if !query.name().is_fqdn() {
380            return Err(Error::from("query's domain name must be fully qualified"));
381        }
382
383        match &self.mode {
384            RecursorMode::NonValidating { handle } => {
385                handle
386                    .resolve(
387                        query,
388                        request_time,
389                        query_has_dnssec_ok,
390                        0,
391                        Arc::new(AtomicU8::new(0)),
392                    )
393                    .await
394            }
395
396            #[cfg(feature = "__dnssec")]
397            RecursorMode::Validating {
398                handle,
399                record_cache,
400            } => {
401                if let Some(Ok(lookup)) = record_cache.get(&query, request_time) {
402                    let none_indeterminate = lookup
403                        .records()
404                        .iter()
405                        .all(|record| !record.proof().is_indeterminate());
406
407                    // if any cached record is indeterminate, fall through and perform
408                    // DNSSEC validation
409                    if none_indeterminate {
410                        return Ok(super::maybe_strip_dnssec_records(
411                            query_has_dnssec_ok,
412                            lookup,
413                            query,
414                        ));
415                    }
416                }
417
418                let mut options = DnsRequestOptions::default();
419                // a validating recursor must be security aware
420                options.use_edns = true;
421                options.edns_set_dnssec_ok = true;
422
423                let response = handle.lookup(query.clone(), options).first_answer().await?;
424
425                // Return NXDomain and NoData responses in error form
426                // These need to bypass the cache lookup (and casting to a Lookup object in general)
427                // to preserve SOA and DNSSEC records, and to keep those records in the authorities
428                // section of the response.
429                if response.response_code() == ResponseCode::NXDomain {
430                    let Err(proto_err) = ProtoError::from_response(response, true) else {
431                        return Err(Error::from(
432                            "unable to build ProtoError from response {response:?}",
433                        ));
434                    };
435
436                    Err(Error {
437                        kind: Box::new(ErrorKind::Proto(proto_err)),
438                        #[cfg(feature = "backtrace")]
439                        backtrack: None,
440                    })
441                } else if response.answers().is_empty()
442                    && !response.name_servers().is_empty()
443                    && response.response_code() == ResponseCode::NoError
444                {
445                    let authorities = response
446                        .name_servers()
447                        .iter()
448                        .filter_map(|x| match x.record_type() {
449                            RecordType::SOA => None,
450                            _ => Some(x.clone()),
451                        })
452                        .collect::<Arc<[Record]>>();
453
454                    let soa = response.soa().as_ref().map(RecordRef::to_owned);
455
456                    Err(Error {
457                        kind: Box::new(ErrorKind::Proto(ProtoError::nx_error(
458                            Box::new(query),
459                            soa.map(Box::new),
460                            None,
461                            None,
462                            ResponseCode::NoError,
463                            true,
464                            Some(authorities),
465                        ))),
466                        #[cfg(feature = "backtrace")]
467                        backtrack: None,
468                    })
469                } else {
470                    // do not perform is_subzone filtering as it already happened in `handle.lookup`
471                    let no_subzone_filtering = None;
472                    let lookup = super::cache_response(
473                        response,
474                        no_subzone_filtering,
475                        record_cache,
476                        query.clone(),
477                        request_time,
478                    )?;
479                    Ok(super::maybe_strip_dnssec_records(
480                        query_has_dnssec_ok,
481                        lookup,
482                        query,
483                    ))
484                }
485            }
486        }
487    }
488}
489
490impl Default for RecursorBuilder {
491    fn default() -> Self {
492        Self {
493            ns_cache_size: 1_024,
494            record_cache_size: 1_048_576,
495            // This default is based on CNAME recursion failures of long (> 8 records) CNAME chains
496            // that users of Unbound encountered (see https://github.com/NLnetLabs/unbound/issues/438)
497            // with a small safety margin added.
498            recursion_limit: Some(12),
499            ns_recursion_limit: Some(16),
500            dnssec_policy: DnssecPolicy::SecurityUnaware,
501            allow_servers: vec![],
502            deny_servers: vec![],
503            avoid_local_udp_ports: HashSet::new(),
504            ttl_config: TtlConfig::default(),
505            case_randomization: false,
506        }
507    }
508}
509
510enum RecursorMode {
511    NonValidating {
512        handle: RecursorDnsHandle,
513    },
514
515    #[cfg(feature = "__dnssec")]
516    Validating {
517        handle: DnssecDnsHandle<RecursorDnsHandle>,
518        // this is a handle to the record cache in `RecursorDnsHandle`; not a whole separate cache
519        record_cache: DnsLru,
520    },
521}
522
523#[cfg(feature = "__dnssec")]
524mod for_dnssec {
525    use std::{
526        sync::{Arc, atomic::AtomicU8},
527        time::Instant,
528    };
529
530    use futures_util::{
531        StreamExt as _, future,
532        stream::{self, BoxStream},
533    };
534
535    use crate::ErrorKind;
536    use crate::proto::{
537        ProtoError,
538        op::{Message, OpCode},
539        xfer::DnsHandle,
540        xfer::DnsResponse,
541    };
542    use crate::recursor_dns_handle::RecursorDnsHandle;
543
544    impl DnsHandle for RecursorDnsHandle {
545        type Response = BoxStream<'static, Result<DnsResponse, ProtoError>>;
546
547        fn send<R: Into<hickory_proto::xfer::DnsRequest> + Unpin + Send + 'static>(
548            &self,
549            request: R,
550        ) -> Self::Response {
551            let request = request.into();
552
553            let query = if let OpCode::Query = request.op_code() {
554                if let Some(query) = request.queries().first().cloned() {
555                    query
556                } else {
557                    return Box::pin(stream::once(future::err(ProtoError::from(
558                        "no query in request",
559                    ))));
560                }
561            } else {
562                return Box::pin(stream::once(future::err(ProtoError::from(
563                    "request is not a query",
564                ))));
565            };
566
567            let this = self.clone();
568            stream::once(async move {
569                // request the DNSSEC records; we'll strip them if not needed on the caller side
570                let do_bit = true;
571
572                let future =
573                    this.resolve(query, Instant::now(), do_bit, 0, Arc::new(AtomicU8::new(0)));
574                let lookup = match future.await {
575                    Ok(lookup) => lookup,
576                    Err(e) => {
577                        return Err(match e.kind() {
578                            // Translate back into a ProtoError::NoRecordsFound
579                            ErrorKind::Forward(_fwd) => e.into(),
580                            _ => ProtoError::from(e.to_string()),
581                        });
582                    }
583                };
584
585                // `DnssecDnsHandle` will only look at the answer section of the message so
586                // we can put "stubs" in the other fields
587                let mut msg = Message::new();
588
589                // XXX this effectively merges the original nameservers and additional
590                // sections into the answers section
591                msg.add_answers(lookup.records().iter().cloned());
592
593                DnsResponse::from_message(msg)
594            })
595            .boxed()
596        }
597    }
598}
599
600const RECOMMENDED_SERVER_FILTERS: [IpNet; 22] = [
601    IpNet::new_assert(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 0)), 8), // Loopback range
602    IpNet::new_assert(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 8),       // Unspecified range
603    IpNet::new_assert(IpAddr::V4(Ipv4Addr::BROADCAST), 32),        // Directed Broadcast
604    IpNet::new_assert(IpAddr::V4(Ipv4Addr::new(10, 0, 0, 0)), 8),  // RFC 1918 space
605    IpNet::new_assert(IpAddr::V4(Ipv4Addr::new(172, 16, 0, 0)), 12), // RFC 1918 space
606    IpNet::new_assert(IpAddr::V4(Ipv4Addr::new(192, 168, 0, 0)), 16), // RFC 1918 space
607    IpNet::new_assert(IpAddr::V4(Ipv4Addr::new(100, 64, 0, 0)), 10), // CG NAT
608    IpNet::new_assert(IpAddr::V4(Ipv4Addr::new(169, 254, 0, 0)), 16), // Link-local space
609    IpNet::new_assert(IpAddr::V4(Ipv4Addr::new(192, 0, 0, 0)), 24), // IETF Reserved
610    IpNet::new_assert(IpAddr::V4(Ipv4Addr::new(192, 0, 2, 0)), 24), // TEST-NET-1
611    IpNet::new_assert(IpAddr::V4(Ipv4Addr::new(198, 51, 100, 0)), 24), // TEST-NET-2
612    IpNet::new_assert(IpAddr::V4(Ipv4Addr::new(203, 0, 113, 0)), 24), // TEST-NET-3
613    IpNet::new_assert(IpAddr::V4(Ipv4Addr::new(240, 0, 0, 0)), 4), // Class E Reserved
614    IpNet::new_assert(IpAddr::V6(Ipv6Addr::LOCALHOST), 128),       // v6 loopback
615    IpNet::new_assert(IpAddr::V6(Ipv6Addr::UNSPECIFIED), 128),     // v6 unspecified
616    IpNet::new_assert(IpAddr::V6(Ipv6Addr::new(0x100, 0, 0, 0, 0, 0, 0, 0)), 64), // v6 discard prefix
617    IpNet::new_assert(
618        IpAddr::V6(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0)),
619        32,
620    ), // v6 documentation prefix
621    IpNet::new_assert(IpAddr::V6(Ipv6Addr::new(0x3fff, 0, 0, 0, 0, 0, 0, 0)), 20), // v6 documentation prefix
622    IpNet::new_assert(IpAddr::V6(Ipv6Addr::new(0x5f00, 0, 0, 0, 0, 0, 0, 0)), 16), // v6 segment routing prefix
623    IpNet::new_assert(IpAddr::V6(Ipv6Addr::new(0xfc00, 0, 0, 0, 0, 0, 0, 0)), 7), // v6 private address,
624    IpNet::new_assert(IpAddr::V6(Ipv6Addr::new(0xfe80, 0, 0, 0, 0, 0, 0, 0)), 64), // v6 link local
625    IpNet::new_assert(IpAddr::V6(Ipv6Addr::new(0xff00, 0, 0, 0, 0, 0, 0, 0)), 8), // v6 multicast
626];
627
628#[cfg(test)]
629mod tests {
630    use std::time::Instant;
631
632    use hickory_proto::op::Query;
633    use hickory_resolver::config::NameServerConfigGroup;
634    use test_support::subscribe;
635
636    use crate::{Error, Recursor, proto::rr::RecordType, resolver::Name};
637
638    #[tokio::test]
639    async fn not_fully_qualified_domain_name_in_query() -> Result<(), Error> {
640        subscribe();
641
642        let recursor = Recursor::builder().build(NameServerConfigGroup::cloudflare())?;
643        let name = Name::from_ascii("example.com")?;
644        assert!(!name.is_fqdn());
645        let query = Query::query(name, RecordType::A);
646        let res = recursor
647            .resolve(query, Instant::now(), false)
648            .await
649            .unwrap_err();
650        assert!(res.to_string().contains("fully qualified"));
651
652        Ok(())
653    }
654}