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