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}