1use std::{
11 borrow::Cow,
12 future::Future,
13 time::{Duration, Instant},
14};
15
16use once_cell::sync::Lazy;
17
18use crate::{
19 cache::{MAX_TTL, ResponseCache, TtlConfig},
20 lookup::Lookup,
21 net::{
22 DnsError, NetError, NoRecords,
23 xfer::{DnsHandle, FirstAnswer},
24 },
25 proto::{
26 op::{DnsRequestOptions, DnsResponse, Message, OpCode, Query, ResponseCode},
27 rr::{
28 DNSClass, Name, RData, Record, RecordRef, RecordType,
29 domain::usage::{
30 DEFAULT, IN_ADDR_ARPA_127, INVALID, IP6_ARPA_1, LOCAL,
31 LOCALHOST as LOCALHOST_usage, ONION, ResolverUsage,
32 },
33 rdata::{A, AAAA, CNAME, PTR},
34 },
35 },
36};
37
38static LOCALHOST: Lazy<RData> =
39 Lazy::new(|| RData::PTR(PTR(Name::from_ascii("localhost.").unwrap())));
40static LOCALHOST_V4: Lazy<RData> = Lazy::new(|| RData::A(A::new(127, 0, 0, 1)));
41static LOCALHOST_V6: Lazy<RData> = Lazy::new(|| RData::AAAA(AAAA::new(0, 0, 0, 0, 0, 0, 0, 1)));
42
43#[derive(Default, Clone, Copy)]
45struct DepthTracker {
46 query_depth: u8,
47}
48
49impl DepthTracker {
50 fn nest(self) -> Self {
51 Self {
52 query_depth: self.query_depth + 1,
53 }
54 }
55
56 fn is_exhausted(self) -> bool {
57 self.query_depth + 1 >= Self::MAX_QUERY_DEPTH
58 }
59
60 const MAX_QUERY_DEPTH: u8 = 8; }
62
63#[derive(Clone, Debug)]
64#[doc(hidden)]
65pub struct CachingClient<C>
66where
67 C: DnsHandle,
68{
69 cache: ResponseCache,
70 client: C,
71 preserve_intermediates: bool,
72 #[cfg(feature = "metrics")]
73 cache_metrics: crate::metrics::CacheMetrics,
74}
75
76impl<C> CachingClient<C>
77where
78 C: DnsHandle + Send + 'static,
79{
80 #[doc(hidden)]
81 pub fn new(max_size: u64, client: C, preserve_intermediates: bool) -> Self {
82 Self::with_cache(
83 ResponseCache::new(max_size, TtlConfig::default()),
84 client,
85 preserve_intermediates,
86 )
87 }
88
89 pub(crate) fn with_cache(
90 cache: ResponseCache,
91 client: C,
92 preserve_intermediates: bool,
93 ) -> Self {
94 Self {
95 cache,
96 client,
97 preserve_intermediates,
98 #[cfg(feature = "metrics")]
99 cache_metrics: crate::metrics::CacheMetrics::default(),
100 }
101 }
102
103 pub fn lookup(
105 &self,
106 query: Query,
107 options: DnsRequestOptions,
108 ) -> impl Future<Output = Result<Lookup, NetError>> {
109 Self::inner_lookup(
110 query,
111 options,
112 self.clone(),
113 vec![],
114 DepthTracker::default(),
115 )
116 }
117
118 async fn inner_lookup(
119 query: Query,
120 options: DnsRequestOptions,
121 mut client: Self,
122 preserved_records: Vec<Record>,
123 depth: DepthTracker,
124 ) -> Result<Lookup, NetError> {
125 if query.query_class() == DNSClass::IN {
136 let usage = match query.name() {
137 n if LOCALHOST_usage.zone_of(n) => &*LOCALHOST_usage,
138 n if IN_ADDR_ARPA_127.zone_of(n) => &*LOCALHOST_usage,
139 n if IP6_ARPA_1.zone_of(n) => &*LOCALHOST_usage,
140 n if INVALID.zone_of(n) => &*INVALID,
141 n if LOCAL.zone_of(n) => &*LOCAL,
142 n if ONION.zone_of(n) => &*ONION,
143 _ => &*DEFAULT,
144 };
145
146 match usage.resolver() {
147 ResolverUsage::Loopback => match query.query_type() {
148 RecordType::A => return Ok(Lookup::from_rdata(query, LOCALHOST_V4.clone())),
150 RecordType::AAAA => return Ok(Lookup::from_rdata(query, LOCALHOST_V6.clone())),
151 RecordType::PTR => return Ok(Lookup::from_rdata(query, LOCALHOST.clone())),
152 _ => return Err(NoRecords::new(query, ResponseCode::NoError).into()),
154 },
155 ResolverUsage::LinkLocal => (),
158 ResolverUsage::NxDomain => {
159 return Err(NoRecords::new(query, ResponseCode::NXDomain).into());
160 }
161 ResolverUsage::Normal => (),
162 }
163 }
164
165 let is_dnssec = client.client.is_verifying_dnssec();
166
167 #[cfg(feature = "metrics")]
168 let request_start = Instant::now();
169
170 if let Some(cached_lookup) = client.lookup_from_cache(&query) {
171 #[cfg(feature = "metrics")]
172 {
173 client.cache_metrics.cache_hit.increment(1);
174 client
175 .cache_metrics
176 .cache_hit_duration
177 .record(request_start.elapsed());
178 client
179 .cache_metrics
180 .cache_size
181 .set(client.cache.entry_count() as f64);
182 }
183 return cached_lookup;
184 };
185
186 #[cfg(feature = "metrics")]
187 client.cache_metrics.cache_miss.increment(1);
188
189 let response_message = client
190 .client
191 .lookup(query.clone(), options)
192 .first_answer()
193 .await;
194
195 let response_message = if let Ok(response) = response_message {
198 DnsError::from_response(response).map_err(NetError::from)
199 } else {
200 response_message
201 };
202
203 let records = match response_message {
206 Ok(response_message) => {
207 let records = match Self::handle_noerror(
209 &mut client,
210 options,
211 &query,
212 response_message,
213 preserved_records,
214 depth,
215 ) {
216 Ok(records) => records,
217 Err(err) => {
218 #[cfg(feature = "metrics")]
219 client
220 .cache_metrics
221 .cache_miss_duration
222 .record(request_start.elapsed());
223 return Err(err);
224 }
225 };
226
227 Ok(records)
228 }
229 Err(NetError::Dns(DnsError::NoRecordsFound(mut no_records))) => {
231 if is_dnssec {
232 no_records.negative_ttl = None;
233 }
234 Err(no_records.into())
235 }
236 Err(err) => {
237 #[cfg(feature = "metrics")]
238 client
239 .cache_metrics
240 .cache_miss_duration
241 .record(request_start.elapsed());
242 return Err(err);
243 }
244 };
245
246 let result = match records {
248 Ok(Records::CnameChain { next: future, .. }) => match future.await {
249 Ok(lookup) => client.cname(lookup, query),
250 Err(e) => client.cache(query, Err(e)),
251 },
252 Ok(Records::Exists { message }) => client.cache(query, Ok(message)),
253 Err(e) => client.cache(query, Err(e)),
254 };
255
256 #[cfg(feature = "metrics")]
257 client
258 .cache_metrics
259 .cache_miss_duration
260 .record(request_start.elapsed());
261
262 result
263 }
264
265 fn lookup_from_cache(&self, query: &Query) -> Option<Result<Lookup, NetError>> {
267 let now = Instant::now();
268 let message_res = self.cache.get(query, now)?;
269 let message = match message_res {
270 Ok(message) => message,
271 Err(err) => return Some(Err(err)),
272 };
273
274 let valid_until = now
275 + Duration::from_secs(
276 message
277 .answers
278 .iter()
279 .map(|r| r.ttl)
280 .min()
281 .unwrap_or(MAX_TTL)
282 .into(),
283 );
284
285 Some(Ok(Lookup::new(message, valid_until)))
286 }
287
288 fn handle_noerror(
290 client: &mut Self,
291 options: DnsRequestOptions,
292 query: &Query,
293 response: DnsResponse,
294 mut preserved_records: Vec<Record>,
295 depth: DepthTracker,
296 ) -> Result<Records<impl Future<Output = Result<Lookup, NetError>>>, NetError> {
297 const INITIAL_TTL: u32 = MAX_TTL;
303
304 let soa = response.soa().as_ref().map(RecordRef::to_owned);
306 let negative_ttl = response.negative_ttl();
307 let response_code = response.response_code;
308
309 let (search_name, was_cname, preserved_records) = {
314 let (search_name, cname_ttl, was_cname) =
316 if query.query_type().is_any() || query.query_type().is_cname() {
317 (Cow::Borrowed(query.name()), INITIAL_TTL, false)
318 } else {
319 response.answers.iter().fold(
326 (Cow::Borrowed(query.name()), INITIAL_TTL, false),
327 |(search_name, cname_ttl, was_cname), r| {
328 match &r.data {
329 RData::CNAME(CNAME(cname)) => {
330 let ttl = cname_ttl.min(r.ttl);
332 debug_assert_eq!(r.record_type(), RecordType::CNAME);
333 if search_name.as_ref() == &r.name {
334 return (Cow::Owned(cname.clone()), ttl, true);
335 }
336 }
337 RData::SRV(srv) => {
338 let ttl = cname_ttl.min(r.ttl);
340 debug_assert_eq!(r.record_type(), RecordType::SRV);
341
342 return (Cow::Owned(srv.target.clone()), ttl, true);
344 }
345 _ => (),
346 }
347
348 (search_name, cname_ttl, was_cname)
349 },
350 )
351 };
352
353 let mut message = response.into_message();
355
356 let mut found_name = false;
359 let mut found_cname_target = false;
360 for r in message.all_sections() {
363 if query.query_class() != r.dns_class {
365 continue;
366 }
367
368 let type_matches =
371 query.query_type().is_any() || query.query_type() == r.record_type();
372 let name_matches = search_name.as_ref() == &r.name || query.name() == &r.name;
373 if type_matches && name_matches {
374 found_name = true;
375 if was_cname && search_name.as_ref() == &r.name {
377 found_cname_target = true;
378 }
379 }
380 }
381
382 if found_name && (!was_cname || preserved_records.is_empty()) {
384 let needs_filtering = !preserved_records.is_empty()
391 || (found_cname_target && !client.preserve_intermediates);
392
393 if needs_filtering {
394 preserved_records.extend(message.all_sections().filter_map(|r| {
397 let ttl = cname_ttl.min(r.ttl);
399 let mut r = r.clone();
400 r.ttl = ttl;
401
402 if query.query_class() != r.dns_class {
404 return None;
405 }
406
407 let query_type = query.query_type();
410 let record_type = r.record_type();
411 let type_matches = query_type.is_any() || query_type == record_type;
412 let name_matches =
413 search_name.as_ref() == &r.name || query.name() == &r.name;
414 if type_matches && name_matches {
415 return Some(r);
416 }
417
418 if client.preserve_intermediates && record_type == RecordType::CNAME {
420 return Some(r);
421 }
422
423 None
426 }));
427
428 message.answers = preserved_records;
430 }
431
432 message = message.maybe_strip_dnssec_records(options.edns_set_dnssec_ok);
434
435 return Ok(Records::Exists { message });
436 }
437
438 preserved_records.extend(message.take_all_sections().filter_map(|mut r| {
442 r.ttl = cname_ttl.min(r.ttl);
444
445 if query.query_class() != r.dns_class {
447 return None;
448 }
449
450 if client.preserve_intermediates && r.record_type() == RecordType::CNAME {
452 return Some(r);
453 }
454
455 None
458 }));
459
460 (search_name.into_owned(), was_cname, preserved_records)
461 };
462
463 if was_cname && !depth.is_exhausted() {
467 let next_query = Query::query(search_name, query.query_type());
468 Ok(Records::CnameChain {
469 next: Box::pin(Self::inner_lookup(
470 next_query,
471 options,
472 client.clone(),
473 #[cfg(test)]
474 preserved_records.clone(),
475 #[cfg(not(test))]
476 preserved_records,
477 depth.nest(),
478 )),
479 #[cfg(test)]
480 preserved_records,
481 })
482 } else {
483 let mut new = NoRecords::new(query.clone(), response_code);
487 new.soa = soa.map(Box::new);
488 new.negative_ttl = negative_ttl;
489 Err(new.into())
490 }
491 }
492
493 #[allow(clippy::unnecessary_wraps)]
494 fn cname(&self, lookup: Lookup, query: Query) -> Result<Lookup, NetError> {
495 let mut message = Message::response(0, OpCode::Query);
496 message.add_query(query.clone());
497 message.add_answers(lookup.answers().iter().cloned());
498 message.add_authorities(lookup.authorities().iter().cloned());
499 message.add_additionals(lookup.additionals().iter().cloned());
500 self.cache.insert(query, Ok(message), Instant::now());
501 Ok(lookup)
502 }
503
504 fn cache(&self, query: Query, result: Result<Message, NetError>) -> Result<Lookup, NetError> {
505 let now = Instant::now();
506 let result = match result {
507 Ok(mut message) => {
508 let ttl = self
512 .cache
513 .clamp_positive_ttls(query.query_type(), &mut message);
514 let valid_until = now + ttl;
515 let lookup = Lookup::new(message.clone(), valid_until);
516 self.cache.insert(query, Ok(message), now);
517 Ok(lookup)
518 }
519 Err(err) => {
520 self.cache.insert(query, Err(err.clone()), now);
521 Err(err)
522 }
523 };
524 #[cfg(feature = "metrics")]
525 self.cache_metrics
526 .cache_size
527 .set(self.cache.entry_count() as f64);
528 result
529 }
530
531 pub fn clear_cache(&self) {
533 self.cache.clear();
534 }
535
536 pub fn clear_cache_query(&self, query: &Query) {
538 self.cache.clear_query(query);
539 }
540}
541
542enum Records<F> {
543 Exists { message: Message },
545 CnameChain {
547 next: F,
548 #[cfg(test)]
549 preserved_records: Vec<Record>,
550 },
551}
552
553#[cfg(test)]
555mod tests {
556 use std::net::*;
557 use std::str::FromStr;
558 use std::time::*;
559
560 use futures_executor::block_on;
561 use test_support::subscribe;
562
563 use super::*;
564 use crate::cache::TtlConfig;
565 use crate::lookup_ip::tests::*;
566 use crate::proto::op::{Message, Query};
567 use crate::proto::rr::rdata::{NS, SRV};
568 use crate::proto::rr::{Name, Record};
569
570 #[test]
571 fn test_empty_cache() {
572 subscribe();
573 let cache = ResponseCache::new(1, TtlConfig::default());
574 let client = mock(vec![empty()]);
575 let client = CachingClient::with_cache(cache, client, false);
576
577 let error = block_on(CachingClient::inner_lookup(
578 Query::new(),
579 DnsRequestOptions::default(),
580 client,
581 vec![],
582 DepthTracker::default(),
583 ))
584 .unwrap_err();
585
586 let NetError::Dns(DnsError::NoRecordsFound(no_records)) = error else {
587 panic!("wrong error received")
588 };
589
590 assert_eq!(no_records.query, Box::new(Query::new()));
591 assert_eq!(no_records.negative_ttl, None);
592 }
593
594 #[test]
595 fn test_from_cache() {
596 subscribe();
597 let cache = ResponseCache::new(1, TtlConfig::default());
598 let query = Query::new();
599 let mut message = Message::response(0, OpCode::Query);
600 message.add_query(query.clone());
601 message.add_answer(Record::from_rdata(
602 query.name().clone(),
603 u32::MAX,
604 RData::A(A::new(127, 0, 0, 1)),
605 ));
606 cache.insert(query.clone(), Ok(message), Instant::now());
607
608 let client = mock(vec![empty()]);
609 let client = CachingClient::with_cache(cache, client, false);
610
611 let ips = block_on(CachingClient::inner_lookup(
612 Query::new(),
613 DnsRequestOptions::default(),
614 client,
615 vec![],
616 DepthTracker::default(),
617 ))
618 .unwrap();
619
620 assert_eq!(
621 ips.answers(),
622 &[Record::from_rdata(
623 query.name().clone(),
624 u32::MAX,
625 RData::A(A::new(127, 0, 0, 1))
626 )]
627 );
628 }
629
630 #[test]
631 fn test_no_cache_insert() {
632 subscribe();
633 let cache = ResponseCache::new(1, TtlConfig::default());
634 let client = mock(vec![v4_message()]);
636 let client = CachingClient::with_cache(cache.clone(), client, false);
637
638 let ips = block_on(CachingClient::inner_lookup(
639 Query::query(Name::root(), RecordType::A),
640 DnsRequestOptions::default(),
641 client,
642 vec![],
643 DepthTracker::default(),
644 ))
645 .unwrap();
646
647 assert_eq!(
648 ips.answers(),
649 &[Record::from_rdata(
650 Name::root(),
651 86400,
652 RData::A(A::new(127, 0, 0, 1))
653 )]
654 );
655
656 let client = mock(vec![empty()]);
658 let client = CachingClient::with_cache(cache, client, false);
659
660 let ips = block_on(CachingClient::inner_lookup(
661 Query::query(Name::root(), RecordType::A),
662 DnsRequestOptions::default(),
663 client,
664 vec![],
665 DepthTracker::default(),
666 ))
667 .unwrap();
668
669 assert_eq!(
670 ips.answers(),
671 &[Record::from_rdata(
672 Name::root(),
673 86400,
674 RData::A(A::new(127, 0, 0, 1))
675 )]
676 );
677 }
678
679 #[allow(clippy::unnecessary_wraps)]
680 pub(crate) fn cname_message() -> Result<DnsResponse, NetError> {
681 let mut message = Message::query();
682 message.add_query(Query::query(
683 Name::from_str("www.example.com.").unwrap(),
684 RecordType::A,
685 ));
686 message.insert_answers(vec![Record::from_rdata(
687 Name::from_str("www.example.com.").unwrap(),
688 86400,
689 RData::CNAME(CNAME(Name::from_str("actual.example.com.").unwrap())),
690 )]);
691 Ok(DnsResponse::from_message(message.into_response()).unwrap())
692 }
693
694 #[allow(clippy::unnecessary_wraps)]
695 pub(crate) fn srv_message() -> Result<DnsResponse, NetError> {
696 let mut message = Message::query();
697 message.add_query(Query::query(
698 Name::from_str("_443._tcp.www.example.com.").unwrap(),
699 RecordType::SRV,
700 ));
701 message.insert_answers(vec![Record::from_rdata(
702 Name::from_str("_443._tcp.www.example.com.").unwrap(),
703 86400,
704 RData::SRV(SRV::new(
705 1,
706 2,
707 443,
708 Name::from_str("www.example.com.").unwrap(),
709 )),
710 )]);
711 Ok(DnsResponse::from_message(message.into_response()).unwrap())
712 }
713
714 #[allow(clippy::unnecessary_wraps)]
715 pub(crate) fn ns_message() -> Result<DnsResponse, NetError> {
716 let mut message = Message::query();
717 message.add_query(Query::query(
718 Name::from_str("www.example.com.").unwrap(),
719 RecordType::NS,
720 ));
721 message.insert_answers(vec![Record::from_rdata(
722 Name::from_str("www.example.com.").unwrap(),
723 86400,
724 RData::NS(NS(Name::from_str("www.example.com.").unwrap())),
725 )]);
726 Ok(DnsResponse::from_message(message.into_response()).unwrap())
727 }
728
729 fn no_recursion_on_query_test(query_type: RecordType) {
730 let cache = ResponseCache::new(1, TtlConfig::default());
731
732 let client = mock(vec![error(), cname_message()]);
734 let client = CachingClient::with_cache(cache, client, false);
735
736 let ips = block_on(CachingClient::inner_lookup(
737 Query::query(Name::from_str("www.example.com.").unwrap(), query_type),
738 DnsRequestOptions::default(),
739 client,
740 vec![],
741 DepthTracker::default(),
742 ))
743 .expect("lookup failed");
744
745 assert_eq!(
746 ips.answers(),
747 &[Record::from_rdata(
748 Name::from_str("www.example.com.").unwrap(),
749 86400,
750 RData::CNAME(CNAME(Name::from_str("actual.example.com.").unwrap()))
751 )]
752 );
753 }
754
755 #[test]
756 fn test_no_recursion_on_cname_query() {
757 subscribe();
758 no_recursion_on_query_test(RecordType::CNAME);
759 }
760
761 #[test]
762 fn test_no_recursion_on_all_query() {
763 subscribe();
764 no_recursion_on_query_test(RecordType::ANY);
765 }
766
767 #[test]
768 fn test_non_recursive_srv_query() {
769 subscribe();
770
771 let cache = ResponseCache::new(1, TtlConfig::default());
772
773 let client = mock(vec![error(), srv_message()]);
775 let client = CachingClient::with_cache(cache, client, false);
776
777 let ips = block_on(CachingClient::inner_lookup(
778 Query::query(
779 Name::from_str("_443._tcp.www.example.com.").unwrap(),
780 RecordType::SRV,
781 ),
782 DnsRequestOptions::default(),
783 client,
784 vec![],
785 DepthTracker::default(),
786 ))
787 .expect("lookup failed");
788
789 assert_eq!(
790 ips.answers(),
791 &[Record::from_rdata(
792 Name::from_str("_443._tcp.www.example.com.").unwrap(),
793 86400,
794 RData::SRV(SRV::new(
795 1,
796 2,
797 443,
798 Name::from_str("www.example.com.").unwrap(),
799 ))
800 )]
801 );
802 }
803
804 #[test]
805 fn test_single_srv_query_response() {
806 subscribe();
807
808 let cache = ResponseCache::new(1, TtlConfig::default());
809
810 let mut message = srv_message().unwrap().into_message();
811 message.add_answer(Record::from_rdata(
812 Name::from_str("www.example.com.").unwrap(),
813 86400,
814 RData::CNAME(CNAME(Name::from_str("actual.example.com.").unwrap())),
815 ));
816 message.insert_additionals(vec![
817 Record::from_rdata(
818 Name::from_str("actual.example.com.").unwrap(),
819 86400,
820 RData::A(A::new(127, 0, 0, 1)),
821 ),
822 Record::from_rdata(
823 Name::from_str("actual.example.com.").unwrap(),
824 86400,
825 RData::AAAA(AAAA::new(0, 0, 0, 0, 0, 0, 0, 1)),
826 ),
827 ]);
828
829 let client = mock(vec![
830 error(),
831 Ok(DnsResponse::from_message(message).unwrap()),
832 ]);
833 let client = CachingClient::with_cache(cache, client, false);
834
835 let ips = block_on(CachingClient::inner_lookup(
836 Query::query(
837 Name::from_str("_443._tcp.www.example.com.").unwrap(),
838 RecordType::SRV,
839 ),
840 DnsRequestOptions::default(),
841 client,
842 vec![],
843 DepthTracker::default(),
844 ))
845 .expect("lookup failed");
846
847 let answers = ips
849 .answers()
850 .iter()
851 .map(|r| r.data.clone())
852 .collect::<Vec<_>>();
853 assert!(answers.contains(&RData::SRV(SRV::new(
854 1,
855 2,
856 443,
857 Name::from_str("www.example.com.").unwrap(),
858 ))));
859 assert!(answers.contains(&RData::CNAME(CNAME(
860 Name::from_str("actual.example.com.").unwrap()
861 ))));
862
863 let additionals = ips
865 .additionals()
866 .iter()
867 .map(|r| r.data.clone())
868 .collect::<Vec<_>>();
869 assert!(additionals.contains(&RData::A(A::new(127, 0, 0, 1))));
870 assert!(additionals.contains(&RData::AAAA(AAAA::new(0, 0, 0, 0, 0, 0, 0, 1))));
871 }
872
873 #[test]
923 fn test_single_ns_query_response() {
924 subscribe();
925
926 let cache = ResponseCache::new(1, TtlConfig::default());
927
928 let mut message = ns_message().unwrap().into_message();
929 message.add_answer(Record::from_rdata(
930 Name::from_str("www.example.com.").unwrap(),
931 86400,
932 RData::CNAME(CNAME(Name::from_str("actual.example.com.").unwrap())),
933 ));
934 message.insert_additionals(vec![
935 Record::from_rdata(
936 Name::from_str("actual.example.com.").unwrap(),
937 86400,
938 RData::A(A::new(127, 0, 0, 1)),
939 ),
940 Record::from_rdata(
941 Name::from_str("actual.example.com.").unwrap(),
942 86400,
943 RData::AAAA(AAAA::new(0, 0, 0, 0, 0, 0, 0, 1)),
944 ),
945 ]);
946
947 let client = mock(vec![
948 error(),
949 Ok(DnsResponse::from_message(message).unwrap()),
950 ]);
951 let client = CachingClient::with_cache(cache, client, false);
952
953 let ips = block_on(CachingClient::inner_lookup(
954 Query::query(Name::from_str("www.example.com.").unwrap(), RecordType::NS),
955 DnsRequestOptions::default(),
956 client,
957 vec![],
958 DepthTracker::default(),
959 ))
960 .expect("lookup failed");
961
962 let answers = ips
964 .answers()
965 .iter()
966 .map(|r| r.data.clone())
967 .collect::<Vec<_>>();
968 assert!(answers.contains(&RData::NS(NS(Name::from_str("www.example.com.").unwrap()))));
969 assert!(answers.contains(&RData::CNAME(CNAME(
970 Name::from_str("actual.example.com.").unwrap()
971 ))));
972
973 let additionals = ips
975 .additionals()
976 .iter()
977 .map(|r| r.data.clone())
978 .collect::<Vec<_>>();
979 assert!(additionals.contains(&RData::A(A::new(127, 0, 0, 1))));
980 assert!(additionals.contains(&RData::AAAA(AAAA::new(0, 0, 0, 0, 0, 0, 0, 1))));
981 }
982
983 #[test]
988 fn test_ns_query_glue_in_additional_section() {
989 subscribe();
990
991 let cache = ResponseCache::new(1, TtlConfig::default());
992
993 let mut message = Message::response(0, OpCode::Query);
995 message.add_query(Query::query(
996 Name::from_str("example.com.").unwrap(),
997 RecordType::NS,
998 ));
999
1000 message.insert_answers(vec![
1002 Record::from_rdata(
1003 Name::from_str("example.com.").unwrap(),
1004 3600,
1005 RData::NS(NS(Name::from_str("ns1.example.com.").unwrap())),
1006 ),
1007 Record::from_rdata(
1008 Name::from_str("example.com.").unwrap(),
1009 3600,
1010 RData::NS(NS(Name::from_str("ns2.example.com.").unwrap())),
1011 ),
1012 ]);
1013
1014 message.insert_additionals(vec![
1016 Record::from_rdata(
1017 Name::from_str("ns1.example.com.").unwrap(),
1018 3600,
1019 RData::A(A::new(192, 0, 2, 1)),
1020 ),
1021 Record::from_rdata(
1022 Name::from_str("ns2.example.com.").unwrap(),
1023 3600,
1024 RData::A(A::new(192, 0, 2, 2)),
1025 ),
1026 ]);
1027
1028 let client = mock(vec![
1029 error(),
1030 Ok(DnsResponse::from_message(message).unwrap()),
1031 ]);
1032 let client = CachingClient::with_cache(cache, client, false);
1033
1034 let lookup = block_on(CachingClient::inner_lookup(
1035 Query::query(Name::from_str("example.com.").unwrap(), RecordType::NS),
1036 DnsRequestOptions::default(),
1037 client,
1038 vec![],
1039 DepthTracker::default(),
1040 ))
1041 .expect("lookup failed");
1042
1043 let answers = lookup.answers().iter().collect::<Vec<_>>();
1045 assert_eq!(
1046 answers.len(),
1047 2,
1048 "Should have exactly 2 NS records in ANSWER"
1049 );
1050
1051 for answer in &answers {
1053 assert_eq!(
1054 answer.record_type(),
1055 RecordType::NS,
1056 "All ANSWER section records should be NS type"
1057 );
1058 }
1059
1060 let additionals = lookup.additionals().iter().collect::<Vec<_>>();
1062 assert_eq!(
1063 additionals.len(),
1064 2,
1065 "Should have exactly 2 glue A records in ADDITIONAL"
1066 );
1067
1068 for additional in &additionals {
1070 assert_eq!(
1071 additional.record_type(),
1072 RecordType::A,
1073 "All ADDITIONAL section records should be A type (glue records)"
1074 );
1075 }
1076
1077 for answer in &answers {
1079 assert_ne!(
1080 answer.record_type(),
1081 RecordType::A,
1082 "A records (glue) should NEVER appear in ANSWER for NS query - this was the original bug!"
1083 );
1084 }
1085
1086 assert_eq!(
1088 lookup.authorities().len(),
1089 0,
1090 "AUTHORITY section should be empty"
1091 );
1092 }
1093
1094 #[test]
1100 fn test_single_hop_cname_preserves_sections() {
1101 subscribe();
1102
1103 let cache = ResponseCache::new(1, TtlConfig::default());
1104
1105 let mut message = Message::response(0, OpCode::Query);
1107 message.add_query(Query::query(
1108 Name::from_str("www.example.com.").unwrap(),
1109 RecordType::A,
1110 ));
1111
1112 message.insert_answers(vec![
1114 Record::from_rdata(
1115 Name::from_str("www.example.com.").unwrap(),
1116 300,
1117 RData::CNAME(CNAME(Name::from_str("v4.example.com.").unwrap())),
1118 ),
1119 Record::from_rdata(
1120 Name::from_str("v4.example.com.").unwrap(),
1121 300,
1122 RData::A(A::new(192, 0, 2, 1)),
1123 ),
1124 ]);
1125
1126 message.insert_authorities(vec![Record::from_rdata(
1128 Name::from_str("example.com.").unwrap(),
1129 3600,
1130 RData::NS(NS(Name::from_str("ns1.example.com.").unwrap())),
1131 )]);
1132
1133 message.insert_additionals(vec![Record::from_rdata(
1135 Name::from_str("ns1.example.com.").unwrap(),
1136 3600,
1137 RData::A(A::new(192, 0, 2, 10)),
1138 )]);
1139
1140 let client = mock(vec![
1141 error(),
1142 Ok(DnsResponse::from_message(message).unwrap()),
1143 ]);
1144 let client = CachingClient::with_cache(cache, client, false); let lookup = block_on(CachingClient::inner_lookup(
1147 Query::query(Name::from_str("www.example.com.").unwrap(), RecordType::A),
1148 DnsRequestOptions::default(),
1149 client,
1150 vec![],
1151 DepthTracker::default(),
1152 ))
1153 .expect("lookup failed");
1154
1155 let answers = lookup.answers().iter().collect::<Vec<_>>();
1157 assert_eq!(
1158 answers.len(),
1159 1,
1160 "ANSWER should have 1 record (CNAME filtered)"
1161 );
1162 assert_eq!(
1163 answers[0].record_type(),
1164 RecordType::A,
1165 "ANSWER should contain only the A record"
1166 );
1167 match answers[0].data {
1168 RData::A(a) => assert_eq!(a, A::new(192, 0, 2, 1), "A record should have correct IP"),
1169 _ => panic!("wrong rdata type"),
1170 }
1171
1172 let authorities = lookup.authorities().iter().collect::<Vec<_>>();
1174 assert_eq!(
1175 authorities.len(),
1176 1,
1177 "AUTHORITY section should be preserved"
1178 );
1179 assert_eq!(
1180 authorities[0].record_type(),
1181 RecordType::NS,
1182 "AUTHORITY should contain NS record"
1183 );
1184
1185 let additionals = lookup.additionals().iter().collect::<Vec<_>>();
1187 assert_eq!(
1188 additionals.len(),
1189 1,
1190 "ADDITIONAL section should be preserved"
1191 );
1192 assert_eq!(
1193 additionals[0].record_type(),
1194 RecordType::A,
1195 "ADDITIONAL should contain glue A record"
1196 );
1197 match additionals[0].data {
1198 RData::A(a) => assert_eq!(
1199 a,
1200 A::new(192, 0, 2, 10),
1201 "Glue record should have correct IP"
1202 ),
1203 _ => panic!("wrong rdata type"),
1204 }
1205 }
1206
1207 #[test]
1214 fn test_single_hop_cname_with_preserve_intermediates() {
1215 subscribe();
1216
1217 let cache = ResponseCache::new(1, TtlConfig::default());
1218
1219 let mut message = Message::response(0, OpCode::Query);
1221 message.add_query(Query::query(
1222 Name::from_str("www.example.com.").unwrap(),
1223 RecordType::A,
1224 ));
1225
1226 message.insert_answers(vec![
1227 Record::from_rdata(
1228 Name::from_str("www.example.com.").unwrap(),
1229 300,
1230 RData::CNAME(CNAME(Name::from_str("v4.example.com.").unwrap())),
1231 ),
1232 Record::from_rdata(
1233 Name::from_str("v4.example.com.").unwrap(),
1234 300,
1235 RData::A(A::new(192, 0, 2, 1)),
1236 ),
1237 ]);
1238
1239 message.insert_authorities(vec![Record::from_rdata(
1240 Name::from_str("example.com.").unwrap(),
1241 3600,
1242 RData::NS(NS(Name::from_str("ns1.example.com.").unwrap())),
1243 )]);
1244
1245 message.insert_additionals(vec![Record::from_rdata(
1246 Name::from_str("ns1.example.com.").unwrap(),
1247 3600,
1248 RData::A(A::new(192, 0, 2, 10)),
1249 )]);
1250
1251 let client = mock(vec![
1252 error(),
1253 Ok(DnsResponse::from_message(message).unwrap()),
1254 ]);
1255 let client = CachingClient::with_cache(cache, client, true); let lookup = block_on(CachingClient::inner_lookup(
1258 Query::query(Name::from_str("www.example.com.").unwrap(), RecordType::A),
1259 DnsRequestOptions::default(),
1260 client,
1261 vec![],
1262 DepthTracker::default(),
1263 ))
1264 .expect("lookup failed");
1265
1266 let answers = lookup.answers().iter().collect::<Vec<_>>();
1268 assert_eq!(answers.len(), 2, "ANSWER should have 2 records (CNAME + A)");
1269
1270 let cname_records = answers
1272 .iter()
1273 .filter(|r| r.record_type() == RecordType::CNAME)
1274 .collect::<Vec<_>>();
1275 assert_eq!(cname_records.len(), 1, "Should have 1 CNAME record");
1276
1277 let a_records = answers
1279 .iter()
1280 .filter(|r| r.record_type() == RecordType::A)
1281 .collect::<Vec<_>>();
1282 assert_eq!(a_records.len(), 1, "Should have 1 A record");
1283
1284 assert_eq!(
1286 lookup.authorities().len(),
1287 1,
1288 "AUTHORITY section should be preserved"
1289 );
1290
1291 assert_eq!(
1293 lookup.additionals().len(),
1294 1,
1295 "ADDITIONAL section should be preserved"
1296 );
1297 }
1298
1299 #[test]
1304 fn test_multi_hop_cname_preserves_final_sections() {
1305 subscribe();
1306
1307 let cache = ResponseCache::new(1, TtlConfig::default());
1308
1309 let mut message1 = Message::response(0, OpCode::Query);
1311 message1.add_query(Query::query(
1312 Name::from_str("www.example.com.").unwrap(),
1313 RecordType::A,
1314 ));
1315
1316 message1.insert_answers(vec![Record::from_rdata(
1317 Name::from_str("www.example.com.").unwrap(),
1318 300,
1319 RData::CNAME(CNAME(Name::from_str("v4.example.com.").unwrap())),
1320 )]);
1321
1322 message1.insert_authorities(vec![Record::from_rdata(
1324 Name::from_str("www-zone.example.com.").unwrap(),
1325 3600,
1326 RData::NS(NS(Name::from_str("ns-www.example.com.").unwrap())),
1327 )]);
1328
1329 message1.insert_additionals(vec![Record::from_rdata(
1331 Name::from_str("ns-www.example.com.").unwrap(),
1332 3600,
1333 RData::A(A::new(192, 0, 2, 20)),
1334 )]);
1335
1336 let mut message2 = Message::response(0, OpCode::Query);
1338 message2.add_query(Query::query(
1339 Name::from_str("v4.example.com.").unwrap(),
1340 RecordType::A,
1341 ));
1342
1343 message2.insert_answers(vec![Record::from_rdata(
1344 Name::from_str("v4.example.com.").unwrap(),
1345 300,
1346 RData::A(A::new(192, 0, 2, 1)),
1347 )]);
1348
1349 message2.insert_authorities(vec![Record::from_rdata(
1351 Name::from_str("v4-zone.example.com.").unwrap(),
1352 3600,
1353 RData::NS(NS(Name::from_str("ns-v4.example.com.").unwrap())),
1354 )]);
1355
1356 message2.insert_additionals(vec![Record::from_rdata(
1358 Name::from_str("ns-v4.example.com.").unwrap(),
1359 3600,
1360 RData::A(A::new(192, 0, 2, 30)),
1361 )]);
1362
1363 let mut client = CachingClient::with_cache(cache, mock(vec![]), false); let result1 = CachingClient::handle_noerror(
1367 &mut client,
1368 DnsRequestOptions::default(),
1369 &Query::query(Name::from_str("www.example.com.").unwrap(), RecordType::A),
1370 DnsResponse::from_message(message1).unwrap(),
1371 vec![],
1372 DepthTracker::default(),
1373 );
1374
1375 let preserved_records = match result1 {
1377 Ok(Records::CnameChain {
1378 preserved_records, ..
1379 }) => {
1380 assert_eq!(
1382 preserved_records.len(),
1383 0,
1384 "With preserve_intermediates=false, preserved_records should be empty"
1385 );
1386 preserved_records
1387 }
1388 Ok(Records::Exists { .. }) => {
1389 panic!("Expected Records::CnameChain from first hop, got Records::Exists")
1390 }
1391 Err(e) => panic!(
1392 "Expected Records::CnameChain from first hop, got error: {}",
1393 e
1394 ),
1395 };
1396
1397 let result2 = CachingClient::handle_noerror(
1399 &mut client,
1400 DnsRequestOptions::default(),
1401 &Query::query(Name::from_str("v4.example.com.").unwrap(), RecordType::A),
1402 DnsResponse::from_message(message2).unwrap(),
1403 preserved_records,
1404 DepthTracker::default().nest(),
1405 );
1406
1407 let lookup_message = match result2 {
1409 Ok(Records::Exists { message, .. }) => message,
1410 Ok(Records::CnameChain { .. }) => {
1411 panic!("Expected Records::Exists from second hop, got Records::CnameChain")
1412 }
1413 Err(e) => panic!("Expected Records::Exists from second hop, got error: {}", e),
1414 };
1415
1416 let lookup = Lookup::new(lookup_message, Instant::now() + Duration::from_secs(300));
1418
1419 let answers = lookup.answers().iter().collect::<Vec<_>>();
1421 assert_eq!(
1422 answers.len(),
1423 1,
1424 "ANSWER should have only the final A record"
1425 );
1426 assert_eq!(answers[0].record_type(), RecordType::A);
1427 match answers[0].data {
1428 RData::A(a) => assert_eq!(a, A::new(192, 0, 2, 1), "Should have IP from Response 2"),
1429 _ => panic!("wrong rdata type"),
1430 }
1431 match answers[0].data {
1432 RData::A(a) => assert_eq!(a, A::new(192, 0, 2, 1), "Should have IP from Response 2"),
1433 _ => panic!("wrong rdata type"),
1434 }
1435
1436 let authorities = lookup.authorities().iter().collect::<Vec<_>>();
1438 assert_eq!(
1439 authorities.len(),
1440 1,
1441 "AUTHORITY should have 1 record from final response only"
1442 );
1443
1444 match &authorities[0].data {
1446 RData::NS(ns_name) => assert_eq!(
1447 ns_name.0,
1448 Name::from_str("ns-v4.example.com.").unwrap(),
1449 "AUTHORITY should be from Response 2 (ns-v4), NOT Response 1 (ns-www)"
1450 ),
1451 _ => panic!("wrong rdata type"),
1452 }
1453
1454 let additionals = lookup.additionals().iter().collect::<Vec<_>>();
1456 assert_eq!(
1457 additionals.len(),
1458 1,
1459 "ADDITIONAL should have 1 record from final response only"
1460 );
1461
1462 match additionals[0].data {
1464 RData::A(a) => assert_eq!(
1465 a,
1466 A::new(192, 0, 2, 30),
1467 "ADDITIONAL should have IP 192.0.2.30 from Response 2, NOT 192.0.2.20 from Response 1"
1468 ),
1469 _ => panic!("wrong rdata type"),
1470 }
1471 }
1472
1473 #[test]
1485 fn test_multi_hop_cname_with_preserve_accumulates_cnames() {
1486 subscribe();
1487
1488 let cache = ResponseCache::new(1, TtlConfig::default());
1489
1490 let mut message1 = Message::response(0, OpCode::Query);
1492 message1.add_query(Query::query(
1493 Name::from_str("www.example.com.").unwrap(),
1494 RecordType::A,
1495 ));
1496
1497 message1.insert_answers(vec![Record::from_rdata(
1498 Name::from_str("www.example.com.").unwrap(),
1499 300,
1500 RData::CNAME(CNAME(Name::from_str("v4.example.com.").unwrap())),
1501 )]);
1502
1503 message1.insert_authorities(vec![Record::from_rdata(
1504 Name::from_str("www-zone.example.com.").unwrap(),
1505 3600,
1506 RData::NS(NS(Name::from_str("ns-www.example.com.").unwrap())),
1507 )]);
1508
1509 message1.insert_additionals(vec![Record::from_rdata(
1510 Name::from_str("ns-www.example.com.").unwrap(),
1511 3600,
1512 RData::A(A::new(192, 0, 2, 20)),
1513 )]);
1514
1515 let mut message2 = Message::response(0, OpCode::Query);
1517 message2.add_query(Query::query(
1518 Name::from_str("v4.example.com.").unwrap(),
1519 RecordType::A,
1520 ));
1521
1522 message2.insert_answers(vec![Record::from_rdata(
1523 Name::from_str("v4.example.com.").unwrap(),
1524 300,
1525 RData::A(A::new(192, 0, 2, 1)),
1526 )]);
1527
1528 message2.insert_authorities(vec![Record::from_rdata(
1529 Name::from_str("v4-zone.example.com.").unwrap(),
1530 3600,
1531 RData::NS(NS(Name::from_str("ns-v4.example.com.").unwrap())),
1532 )]);
1533
1534 message2.insert_additionals(vec![Record::from_rdata(
1535 Name::from_str("ns-v4.example.com.").unwrap(),
1536 3600,
1537 RData::A(A::new(192, 0, 2, 30)),
1538 )]);
1539
1540 let client = mock(vec![]);
1541 let mut client = CachingClient::with_cache(cache, client, true); let result1 = CachingClient::handle_noerror(
1545 &mut client,
1546 DnsRequestOptions::default(),
1547 &Query::query(Name::from_str("www.example.com.").unwrap(), RecordType::A),
1548 DnsResponse::from_message(message1.clone()).unwrap(),
1549 vec![],
1550 DepthTracker::default(),
1551 );
1552
1553 let preserved_records = match result1 {
1555 Ok(Records::CnameChain {
1556 preserved_records, ..
1557 }) => {
1558 assert_eq!(
1560 preserved_records.len(),
1561 1,
1562 "With preserve_intermediates=true, preserved_records should contain the CNAME"
1563 );
1564 assert_eq!(
1565 preserved_records[0].record_type(),
1566 RecordType::CNAME,
1567 "Preserved record should be a CNAME"
1568 );
1569 preserved_records
1570 }
1571 _ => panic!("Expected CnameChain from first hop"),
1572 };
1573
1574 let result2 = CachingClient::handle_noerror(
1576 &mut client,
1577 DnsRequestOptions::default(),
1578 &Query::query(Name::from_str("v4.example.com.").unwrap(), RecordType::A),
1579 DnsResponse::from_message(message2).unwrap(),
1580 preserved_records,
1581 DepthTracker::default().nest(),
1582 );
1583
1584 let lookup_message = match result2 {
1585 Ok(Records::Exists { message, .. }) => message,
1586 Ok(Records::CnameChain { .. }) => {
1587 panic!("Expected Records::Exists from second hop, got Records::CnameChain")
1588 }
1589 Err(e) => panic!("Expected Records::Exists from second hop, got error: {}", e),
1590 };
1591
1592 let lookup = Lookup::new(lookup_message, Instant::now() + Duration::from_secs(300));
1594
1595 let answers = lookup.answers().iter().collect::<Vec<_>>();
1597 assert_eq!(
1598 answers.len(),
1599 2,
1600 "ANSWER should have CNAME + A (both preserved)"
1601 );
1602
1603 let cname_records = answers
1605 .iter()
1606 .filter(|r| r.record_type() == RecordType::CNAME)
1607 .collect::<Vec<_>>();
1608 assert_eq!(
1609 cname_records.len(),
1610 1,
1611 "Should have 1 CNAME from Response 1"
1612 );
1613
1614 match &cname_records[0].data {
1615 RData::CNAME(cname_target) => assert_eq!(
1616 cname_target.0,
1617 Name::from_str("v4.example.com.").unwrap(),
1618 "CNAME should point to v4.example.com"
1619 ),
1620 _ => panic!("wrong rdata type"),
1621 }
1622
1623 let a_records = answers
1625 .iter()
1626 .filter(|r| r.record_type() == RecordType::A)
1627 .collect::<Vec<_>>();
1628 assert_eq!(a_records.len(), 1, "Should have 1 A record");
1629 match a_records[0].data {
1630 RData::A(a) => assert_eq!(
1631 a,
1632 A::new(192, 0, 2, 1),
1633 "A record should have IP from Response 2"
1634 ),
1635 _ => panic!("wrong rdata type"),
1636 };
1637
1638 assert_eq!(
1640 lookup.authorities().len(),
1641 1,
1642 "AUTHORITY should be from final response only"
1643 );
1644
1645 assert_eq!(
1647 lookup.additionals().len(),
1648 1,
1649 "ADDITIONAL should be from final response only"
1650 );
1651 }
1652
1653 fn cname_ttl_test(first: u32, second: u32) {
1654 let lru = ResponseCache::new(1, TtlConfig::default());
1655 let mut client = CachingClient::with_cache(lru, mock(vec![error()]), false);
1657
1658 let mut message = Message::query();
1659 message.insert_answers(vec![Record::from_rdata(
1660 Name::from_str("ttl.example.com.").unwrap(),
1661 first,
1662 RData::CNAME(CNAME(Name::from_str("actual.example.com.").unwrap())),
1663 )]);
1664 message.insert_additionals(vec![Record::from_rdata(
1665 Name::from_str("actual.example.com.").unwrap(),
1666 second,
1667 RData::A(A::new(127, 0, 0, 1)),
1668 )]);
1669
1670 let records = CachingClient::handle_noerror(
1671 &mut client,
1672 DnsRequestOptions::default(),
1673 &Query::query(Name::from_str("ttl.example.com.").unwrap(), RecordType::A),
1674 DnsResponse::from_message(message.into_response()).unwrap(),
1675 vec![],
1676 DepthTracker::default(),
1677 );
1678
1679 if let Ok(Records::Exists { message }) = records {
1680 assert!(!message.answers.is_empty());
1681 } else {
1682 panic!("expected Records::Exists");
1683 }
1684 }
1685
1686 #[test]
1687 fn test_cname_ttl() {
1688 subscribe();
1689 cname_ttl_test(1, 2);
1690 cname_ttl_test(2, 1);
1691 }
1692
1693 #[test]
1694 fn test_early_return_localhost() {
1695 subscribe();
1696 let cache = ResponseCache::new(0, TtlConfig::default());
1697 let client = mock(vec![empty()]);
1698 let client = CachingClient::with_cache(cache, client, false);
1699
1700 {
1701 let query = Query::query(Name::from_ascii("localhost.").unwrap(), RecordType::A);
1702 let lookup = block_on(client.lookup(query.clone(), DnsRequestOptions::default()))
1703 .expect("should have returned localhost");
1704 assert_eq!(lookup.query(), &query);
1705 assert_eq!(
1706 lookup.answers(),
1707 &[Record::from_rdata(
1708 query.name().clone(),
1709 MAX_TTL,
1710 LOCALHOST_V4.clone()
1711 )]
1712 );
1713 }
1714
1715 {
1716 let query = Query::query(Name::from_ascii("localhost.").unwrap(), RecordType::AAAA);
1717 let lookup = block_on(client.lookup(query.clone(), DnsRequestOptions::default()))
1718 .expect("should have returned localhost");
1719 assert_eq!(lookup.query(), &query);
1720 assert_eq!(
1721 lookup.answers(),
1722 &[Record::from_rdata(
1723 query.name().clone(),
1724 MAX_TTL,
1725 LOCALHOST_V6.clone()
1726 )]
1727 );
1728 }
1729
1730 {
1731 let query = Query::query(Name::from(Ipv4Addr::LOCALHOST), RecordType::PTR);
1732 let lookup = block_on(client.lookup(query.clone(), DnsRequestOptions::default()))
1733 .expect("should have returned localhost");
1734 assert_eq!(lookup.query(), &query);
1735 assert_eq!(
1736 lookup.answers(),
1737 &[Record::from_rdata(
1738 query.name().clone(),
1739 MAX_TTL,
1740 LOCALHOST.clone()
1741 )]
1742 );
1743 }
1744
1745 {
1746 let query = Query::query(
1747 Name::from(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)),
1748 RecordType::PTR,
1749 );
1750 let lookup = block_on(client.lookup(query.clone(), DnsRequestOptions::default()))
1751 .expect("should have returned localhost");
1752 assert_eq!(lookup.query(), &query);
1753 assert_eq!(
1754 lookup.answers(),
1755 &[Record::from_rdata(
1756 query.name().clone(),
1757 MAX_TTL,
1758 LOCALHOST.clone()
1759 )]
1760 );
1761 }
1762
1763 assert!(
1764 block_on(client.lookup(
1765 Query::query(Name::from_ascii("localhost.").unwrap(), RecordType::MX),
1766 DnsRequestOptions::default()
1767 ))
1768 .is_err()
1769 );
1770
1771 assert!(
1772 block_on(client.lookup(
1773 Query::query(Name::from(Ipv4Addr::LOCALHOST), RecordType::MX),
1774 DnsRequestOptions::default()
1775 ))
1776 .is_err()
1777 );
1778
1779 assert!(
1780 block_on(client.lookup(
1781 Query::query(
1782 Name::from(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)),
1783 RecordType::MX
1784 ),
1785 DnsRequestOptions::default()
1786 ))
1787 .is_err()
1788 );
1789 }
1790
1791 #[test]
1792 fn test_early_return_invalid() {
1793 subscribe();
1794 let cache = ResponseCache::new(0, TtlConfig::default());
1795 let client = mock(vec![empty()]);
1796 let client = CachingClient::with_cache(cache, client, false);
1797
1798 assert!(
1799 block_on(client.lookup(
1800 Query::query(
1801 Name::from_ascii("horrible.invalid.").unwrap(),
1802 RecordType::A,
1803 ),
1804 DnsRequestOptions::default()
1805 ))
1806 .is_err()
1807 );
1808 }
1809
1810 #[test]
1811 fn test_no_error_on_dot_local_no_mdns() {
1812 subscribe();
1813
1814 let cache = ResponseCache::new(1, TtlConfig::default());
1815
1816 let mut message = srv_message().unwrap().into_message();
1817 message.add_query(Query::query(
1818 Name::from_ascii("www.example.local.").unwrap(),
1819 RecordType::A,
1820 ));
1821 message.add_answer(Record::from_rdata(
1822 Name::from_str("www.example.local.").unwrap(),
1823 86400,
1824 RData::A(A::new(127, 0, 0, 1)),
1825 ));
1826
1827 let client = mock(vec![
1828 error(),
1829 Ok(DnsResponse::from_message(message).unwrap()),
1830 ]);
1831 let client = CachingClient::with_cache(cache, client, false);
1832
1833 assert!(
1834 block_on(client.lookup(
1835 Query::query(
1836 Name::from_ascii("www.example.local.").unwrap(),
1837 RecordType::A,
1838 ),
1839 DnsRequestOptions::default()
1840 ))
1841 .is_ok()
1842 );
1843 }
1844}