1use std::{
4 collections::HashMap,
5 ops::RangeInclusive,
6 sync::Arc,
7 time::{Duration, Instant},
8};
9
10use moka::{Expiry, sync::Cache};
11#[cfg(feature = "serde")]
12use serde::Deserialize;
13
14use crate::{
15 config,
16 net::{DnsError, NetError, NoRecords},
17 proto::{
18 op::{Message, Query},
19 rr::RecordType,
20 },
21};
22#[cfg(feature = "__dnssec")]
23use crate::{net::dnssec::BOGUS_CACHE_TTL, proto::dnssec::DnssecSummary};
24
25#[derive(Clone, Debug)]
27pub struct ResponseCache {
28 cache: Cache<Query, Entry>,
29 ttl_config: Arc<TtlConfig>,
30}
31
32impl ResponseCache {
33 pub fn new(capacity: u64, ttl_config: TtlConfig) -> Self {
40 Self {
41 cache: Cache::builder()
42 .max_capacity(capacity)
43 .expire_after(EntryExpiry)
44 .build(),
45 ttl_config: Arc::new(ttl_config),
46 }
47 }
48
49 pub fn insert(&self, query: Query, result: Result<Message, NetError>, now: Instant) {
51 self.upsert_clamped_ttl(query, result, now, false)
52 }
53
54 pub(super) fn upsert_clamped_ttl(
63 &self,
64 query: Query,
65 result: Result<Message, NetError>,
66 now: Instant,
67 clamp_valid_until: bool,
68 ) {
69 let (ttl, result) = match result {
70 Ok(mut message) => {
71 let ttl = self.clamp_positive_ttls(query.query_type(), &mut message);
72 (ttl, Ok(message))
73 }
74 Err(NetError::Dns(DnsError::NoRecordsFound(no_records))) => {
75 let (negative_min_ttl, negative_max_ttl) = self
76 .ttl_config
77 .negative_response_ttl_bounds(query.query_type())
78 .into_inner();
79 let ttl = if let Some(ttl) = no_records.negative_ttl {
80 Duration::from_secs(u64::from(ttl)).clamp(negative_min_ttl, negative_max_ttl)
81 } else {
82 negative_min_ttl
83 };
84 (
85 ttl,
86 Err(NetError::Dns(DnsError::NoRecordsFound(no_records))),
87 )
88 }
89 Err(_) => return,
90 };
91 let valid_until = now + ttl;
92 if clamp_valid_until {
93 self.cache.entry(query).and_upsert_with(|entry_opt| Entry {
94 result: Arc::new(result),
95 original_time: now,
96 valid_until: entry_opt.map_or(valid_until, |entry| {
97 entry.value().valid_until.min(valid_until)
98 }),
99 });
100 } else {
101 self.cache.insert(
102 query,
103 Entry {
104 result: Arc::new(result),
105 original_time: now,
106 valid_until,
107 },
108 );
109 }
110 }
111
112 pub fn get(&self, query: &Query, now: Instant) -> Option<Result<Message, NetError>> {
114 let entry = self.cache.get(query)?;
115 if !entry.is_current(now) {
116 return None;
117 }
118 Some(entry.updated_ttl(now))
119 }
120
121 pub(crate) fn clamp_positive_ttls(
129 &self,
130 query_type: RecordType,
131 message: &mut Message,
132 ) -> Duration {
133 #[cfg(feature = "__dnssec")]
134 let bogus = matches!(
135 DnssecSummary::from_records(message.all_sections()),
136 DnssecSummary::Bogus,
137 );
138
139 for record in message
140 .answers
141 .iter_mut()
142 .chain(message.authorities.iter_mut())
143 .chain(message.additionals.iter_mut())
144 {
145 let (min_secs, max_secs) = self
146 .ttl_config
147 .positive_ttl_bounds_secs(record.record_type());
148 record.ttl = record.ttl.clamp(min_secs, max_secs);
149 }
150
151 let (positive_min_ttl, positive_max_ttl) = self
152 .ttl_config
153 .positive_response_ttl_bounds(query_type)
154 .into_inner();
155
156 let min_ttl = if query_type == RecordType::NS {
157 message
161 .answers
162 .iter()
163 .chain(message.authorities.iter())
164 .filter(|r| r.record_type() == RecordType::NS)
165 .map(|r| Duration::from_secs(r.ttl.into()))
166 .min()
167 } else {
168 message
173 .answers
174 .iter()
175 .filter(|r| r.record_type() == query_type || r.record_type() == RecordType::CNAME)
176 .map(|r| Duration::from_secs(r.ttl.into()))
177 .min()
178 };
179
180 let ttl = min_ttl
181 .unwrap_or(positive_min_ttl)
182 .clamp(positive_min_ttl, positive_max_ttl);
183
184 #[cfg(feature = "__dnssec")]
189 let ttl = if bogus { ttl.min(BOGUS_CACHE_TTL) } else { ttl };
190
191 ttl
192 }
193
194 pub(crate) fn clear(&self) {
195 self.cache.invalidate_all();
196 }
197
198 pub(crate) fn clear_query(&self, query: &Query) {
199 self.cache.invalidate(query);
200 }
201
202 #[cfg(feature = "metrics")]
204 pub(crate) fn entry_count(&self) -> u64 {
205 #[cfg(test)]
206 {
207 self.cache.run_pending_tasks();
212 }
213
214 self.cache.entry_count()
215 }
216}
217
218#[derive(Debug, Clone)]
223struct Entry {
224 result: Arc<Result<Message, NetError>>,
225 original_time: Instant,
226 valid_until: Instant,
227}
228
229impl Entry {
230 fn updated_ttl(&self, now: Instant) -> Result<Message, NetError> {
233 let elapsed = u32::try_from(now.saturating_duration_since(self.original_time).as_secs())
234 .unwrap_or(u32::MAX);
235 match &*self.result {
236 Ok(response) => {
237 let mut response = response.clone();
238 for records in [
239 &mut response.answers,
240 &mut response.authorities,
241 &mut response.additionals,
242 ] {
243 for record in records {
244 record.decrement_ttl(elapsed);
245 }
246 }
247 Ok(response)
248 }
249 Err(e) => {
250 let mut e = e.clone();
251
252 if let NetError::Dns(DnsError::NoRecordsFound(NoRecords {
255 negative_ttl,
256 soa,
257 authorities,
258 ns,
259 ..
260 })) = &mut e
261 {
262 if let Some(ttl) = negative_ttl {
263 *ttl = ttl.saturating_sub(elapsed);
264 }
265
266 if let Some(soa) = soa {
267 soa.decrement_ttl(elapsed);
268 }
269
270 if let Some(recs) = authorities.take() {
271 authorities.replace(Arc::from(
272 recs.iter()
273 .cloned()
274 .map(|mut rec| {
275 rec.decrement_ttl(elapsed);
276 rec
277 })
278 .collect::<Vec<_>>(),
279 ));
280 }
281
282 if let Some(ns_recs) = ns.take() {
283 ns.replace(Arc::from(
284 ns_recs
285 .iter()
286 .cloned()
287 .map(|mut ns| {
288 ns.ns.decrement_ttl(elapsed);
289 ns.glue = Arc::from(
290 ns.glue
291 .iter()
292 .cloned()
293 .map(|mut glue| {
294 glue.decrement_ttl(elapsed);
295 glue
296 })
297 .collect::<Vec<_>>(),
298 );
299
300 ns
301 })
302 .collect::<Vec<_>>(),
303 ));
304 }
305 }
306 Err(e)
307 }
308 }
309 }
310
311 fn is_current(&self, now: Instant) -> bool {
313 now <= self.valid_until
314 }
315
316 fn ttl(&self, now: Instant) -> Duration {
318 self.valid_until.saturating_duration_since(now)
319 }
320}
321
322struct EntryExpiry;
323
324impl Expiry<Query, Entry> for EntryExpiry {
325 fn expire_after_create(
326 &self,
327 _key: &Query,
328 value: &Entry,
329 created_at: Instant,
330 ) -> Option<Duration> {
331 Some(value.ttl(created_at))
332 }
333
334 fn expire_after_update(
335 &self,
336 _key: &Query,
337 value: &Entry,
338 updated_at: Instant,
339 _duration_until_expiry: Option<Duration>,
340 ) -> Option<Duration> {
341 Some(value.ttl(updated_at))
342 }
343}
344
345#[derive(Clone, Debug, Default, PartialEq, Eq)]
357#[cfg_attr(feature = "serde", derive(Deserialize))]
358#[cfg_attr(
359 feature = "serde",
360 serde(from = "ttl_config_deserialize::TtlConfigMap")
361)]
362pub struct TtlConfig {
363 default: TtlBounds,
365
366 by_query_type: HashMap<RecordType, TtlBounds>,
368}
369
370impl TtlConfig {
371 pub fn from_opts(opts: &config::ResolverOpts) -> Self {
373 Self::from(TtlBounds {
374 positive_min_ttl: opts.positive_min_ttl,
375 negative_min_ttl: opts.negative_min_ttl,
376 positive_max_ttl: opts.positive_max_ttl,
377 negative_max_ttl: opts.negative_max_ttl,
378 })
379 }
380
381 pub fn with_query_type_ttl_bounds(
386 &mut self,
387 query_type: RecordType,
388 bounds: TtlBounds,
389 ) -> &mut Self {
390 self.by_query_type.insert(query_type, bounds);
391 self
392 }
393
394 fn positive_ttl_bounds_secs(&self, record_type: RecordType) -> (u32, u32) {
399 let (min, max) = self.positive_response_ttl_bounds(record_type).into_inner();
400 (
401 u32::try_from(min.as_secs()).unwrap_or(MAX_TTL),
402 u32::try_from(max.as_secs()).unwrap_or(MAX_TTL),
403 )
404 }
405
406 pub fn positive_response_ttl_bounds(&self, query_type: RecordType) -> RangeInclusive<Duration> {
408 let bounds = self.by_query_type.get(&query_type).unwrap_or(&self.default);
409 let min = bounds
410 .positive_min_ttl
411 .unwrap_or_else(|| Duration::from_secs(0));
412 let max = bounds
413 .positive_max_ttl
414 .unwrap_or_else(|| Duration::from_secs(u64::from(MAX_TTL)));
415 min..=max
416 }
417
418 pub fn negative_response_ttl_bounds(&self, query_type: RecordType) -> RangeInclusive<Duration> {
420 let bounds = self.by_query_type.get(&query_type).unwrap_or(&self.default);
421 let min = bounds
422 .negative_min_ttl
423 .unwrap_or_else(|| Duration::from_secs(0));
424 let max = bounds
425 .negative_max_ttl
426 .unwrap_or_else(|| Duration::from_secs(u64::from(MAX_TTL)));
427 min..=max
428 }
429}
430
431impl From<TtlBounds> for TtlConfig {
432 fn from(default: TtlBounds) -> Self {
433 Self {
434 default,
435 by_query_type: HashMap::default(),
436 }
437 }
438}
439
440#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
442#[cfg_attr(feature = "serde", derive(Deserialize))]
443#[cfg_attr(feature = "serde", serde(deny_unknown_fields))]
444pub struct TtlBounds {
445 #[cfg_attr(
450 feature = "serde",
451 serde(default, deserialize_with = "config::duration_opt::deserialize")
452 )]
453 positive_min_ttl: Option<Duration>,
454
455 #[cfg_attr(
460 feature = "serde",
461 serde(default, deserialize_with = "config::duration_opt::deserialize")
462 )]
463 negative_min_ttl: Option<Duration>,
464
465 #[cfg_attr(
470 feature = "serde",
471 serde(default, deserialize_with = "config::duration_opt::deserialize")
472 )]
473 positive_max_ttl: Option<Duration>,
474
475 #[cfg_attr(
480 feature = "serde",
481 serde(default, deserialize_with = "config::duration_opt::deserialize")
482 )]
483 negative_max_ttl: Option<Duration>,
484}
485
486#[cfg(feature = "serde")]
487mod ttl_config_deserialize {
488 use std::collections::HashMap;
489
490 use serde::Deserialize;
491
492 use super::{TtlBounds, TtlConfig};
493 use crate::proto::rr::RecordType;
494
495 #[derive(Deserialize)]
496 pub(super) struct TtlConfigMap(HashMap<TtlConfigField, TtlBounds>);
497
498 impl From<TtlConfigMap> for TtlConfig {
499 fn from(value: TtlConfigMap) -> Self {
500 let mut default = TtlBounds::default();
501 let mut by_query_type = HashMap::new();
502 for (field, bounds) in value.0.into_iter() {
503 match field {
504 TtlConfigField::RecordType(record_type) => {
505 by_query_type.insert(record_type, bounds);
506 }
507 TtlConfigField::Default => default = bounds,
508 }
509 }
510 Self {
511 default,
512 by_query_type,
513 }
514 }
515 }
516
517 #[derive(PartialEq, Eq, Hash, Deserialize)]
518 enum TtlConfigField {
519 #[serde(rename = "default")]
520 Default,
521 #[serde(untagged)]
522 RecordType(RecordType),
523 }
524}
525
526pub const MAX_TTL: u32 = 86400_u32;
532
533#[cfg(test)]
534mod tests {
535 use std::{
536 str::FromStr,
537 time::{Duration, Instant},
538 };
539
540 use hickory_proto::rr::rdata::CNAME;
541 #[cfg(feature = "serde")]
542 use serde::Deserialize;
543
544 use super::*;
545 use crate::{
546 net::{ForwardNSData, NetError},
547 proto::{
548 op::{DnsResponse, Message, OpCode, Query, ResponseCode},
549 rr::{
550 Name, RData, Record, RecordType,
551 rdata::{A, AAAA, NS, SOA, TXT},
552 },
553 },
554 };
555 #[cfg(feature = "__dnssec")]
556 use hickory_proto::dnssec::Proof;
557 use test_support::subscribe;
558
559 #[test]
560 fn test_is_current() {
561 let now = Instant::now();
562 let not_the_future = now + Duration::from_secs(4);
563 let future = now + Duration::from_secs(5);
564 let past_the_future = now + Duration::from_secs(6);
565
566 let entry = Entry {
567 result: Err(NetError::Message("test error")).into(),
568 original_time: now,
569 valid_until: future,
570 };
571
572 assert!(entry.is_current(now));
573 assert!(entry.is_current(not_the_future));
574 assert!(entry.is_current(future));
575 assert!(!entry.is_current(past_the_future));
576 }
577
578 #[test]
579 fn test_positive_min_ttl() {
580 let now = Instant::now();
581
582 let name = Name::from_str("www.example.com.").unwrap();
583 let query = Query::query(name.clone(), RecordType::A);
584 let mut message = Message::response(0, OpCode::Query);
586 message.add_answer(Record::from_rdata(
587 name.clone(),
588 1,
589 RData::A(A::new(127, 0, 0, 1)),
590 ));
591
592 let ttls = TtlConfig::from(TtlBounds {
594 positive_min_ttl: Some(Duration::from_secs(2)),
595 ..TtlBounds::default()
596 });
597 let cache = ResponseCache::new(1, ttls);
598
599 cache.insert(query.clone(), Ok(message), now);
600 let valid_until = cache.cache.get(&query).unwrap().valid_until;
601 assert_eq!(valid_until, now + Duration::from_secs(2));
604
605 let mut message = Message::response(0, OpCode::Query);
607 message.add_answer(Record::from_rdata(
608 name.clone(),
609 3,
610 RData::A(A::new(127, 0, 0, 1)),
611 ));
612
613 cache.insert(query.clone(), Ok(message), now);
614 let valid_until = cache.cache.get(&query).unwrap().valid_until;
615 assert_eq!(valid_until, now + Duration::from_secs(3));
618 }
619
620 #[test]
621 fn test_positive_min_ttl_clamps_record_ttls() {
622 let now = Instant::now();
627
628 let name = Name::from_str("www.example.com.").unwrap();
629 let query = Query::query(name.clone(), RecordType::A);
630
631 let mut message = Message::response(0, OpCode::Query);
633 message.add_answer(Record::from_rdata(
634 name.clone(),
635 60,
636 RData::A(A::new(93, 184, 216, 34)),
637 ));
638
639 let ttls = TtlConfig::from(TtlBounds {
640 positive_min_ttl: Some(Duration::from_secs(3600)),
641 ..TtlBounds::default()
642 });
643 let cache = ResponseCache::new(1, ttls);
644
645 cache.insert(query.clone(), Ok(message), now);
646
647 let result = cache.get(&query, now).unwrap().unwrap();
650 assert_eq!(result.answers.first().unwrap().ttl, 3600);
651
652 let result = cache
655 .get(&query, now + Duration::from_secs(61))
656 .unwrap()
657 .unwrap();
658 assert_eq!(result.answers.first().unwrap().ttl, 3539);
659
660 let result = cache
662 .get(&query, now + Duration::from_secs(3599))
663 .unwrap()
664 .unwrap();
665 assert_eq!(result.answers.first().unwrap().ttl, 1);
666
667 assert!(cache.get(&query, now + Duration::from_secs(3601)).is_none());
669 }
670
671 #[test]
672 fn test_positive_max_ttl_clamps_record_ttls() {
673 let now = Instant::now();
678
679 let name = Name::from_str("www.example.com.").unwrap();
680 let query = Query::query(name.clone(), RecordType::A);
681
682 let mut message = Message::response(0, OpCode::Query);
684 message.add_answer(Record::from_rdata(
685 name.clone(),
686 3600,
687 RData::A(A::new(93, 184, 216, 34)),
688 ));
689
690 let ttls = TtlConfig::from(TtlBounds {
691 positive_max_ttl: Some(Duration::from_secs(120)),
692 ..TtlBounds::default()
693 });
694 let cache = ResponseCache::new(1, ttls);
695
696 cache.insert(query.clone(), Ok(message), now);
697
698 let result = cache.get(&query, now).unwrap().unwrap();
701 assert_eq!(result.answers.first().unwrap().ttl, 120);
702
703 let result = cache
705 .get(&query, now + Duration::from_secs(60))
706 .unwrap()
707 .unwrap();
708 assert_eq!(result.answers.first().unwrap().ttl, 60);
709
710 assert!(cache.get(&query, now + Duration::from_secs(121)).is_none());
712 }
713
714 #[cfg(feature = "__dnssec")]
715 #[test]
716 fn test_bogus_record_clamps_cache_ttl() {
717 let configs = [
728 TtlConfig::default(),
729 TtlConfig::from(TtlBounds {
730 positive_min_ttl: Some(Duration::from_secs(3600)),
731 ..TtlBounds::default()
732 }),
733 ];
734
735 for ttls in configs {
736 let now = Instant::now();
737 let name = Name::from_str("bogus.example.com.").unwrap();
738 let query = Query::query(name.clone(), RecordType::A);
739
740 let mut message = Message::response(0, OpCode::Query);
741 let mut record = Record::from_rdata(name.clone(), 3600, RData::A(A::new(10, 0, 0, 1)));
742 record.proof = Proof::Bogus;
743 message.add_answer(record);
744
745 let cache = ResponseCache::new(1, ttls);
746 cache.insert(query.clone(), Ok(message), now);
747
748 let valid_until = cache.cache.get(&query).unwrap().valid_until;
749 assert!(
750 valid_until <= now + Duration::from_secs(60 * 5),
753 "Bogus answer cached for {:?}, must be <= 5m",
754 valid_until.duration_since(now),
755 );
756 }
757 }
758
759 #[test]
760 fn test_authority_ttl_does_not_shorten_answer_cache() {
761 let now = Instant::now();
765
766 let name = Name::from_str("api.example.com.").unwrap();
767 let query = Query::query(name.clone(), RecordType::AAAA);
768
769 let mut message = Message::response(0, OpCode::Query);
770 message.add_answer(Record::from_rdata(
772 name.clone(),
773 120,
774 RData::AAAA(AAAA::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 1)),
775 ));
776 message.add_authority(Record::from_rdata(
778 Name::from_str("example.com.").unwrap(),
779 30,
780 RData::NS(NS(Name::from_str("ns1.example.com.").unwrap())),
781 ));
782
783 let ttls = TtlConfig::from(TtlBounds {
784 positive_min_ttl: Some(Duration::from_secs(3600)),
785 positive_max_ttl: Some(Duration::from_secs(28800)),
786 ..TtlBounds::default()
787 });
788 let cache = ResponseCache::new(1, ttls);
789
790 cache.insert(query.clone(), Ok(message), now);
791
792 let valid_until = cache.cache.get(&query).unwrap().valid_until;
795 assert_eq!(valid_until, now + Duration::from_secs(3600));
796
797 let result = cache
799 .get(&query, now + Duration::from_secs(130))
800 .unwrap()
801 .unwrap();
802 assert_eq!(result.answers.first().unwrap().ttl, 3470);
804
805 assert!(cache.get(&query, now + Duration::from_secs(3601)).is_none());
807 }
808
809 #[test]
810 fn test_negative_min_ttl() {
811 let now = Instant::now();
812
813 let name = Name::from_str("www.example.com.").unwrap();
814 let query = Query::query(name.clone(), RecordType::A);
815
816 let ttls = TtlConfig::from(TtlBounds {
818 negative_min_ttl: Some(Duration::from_secs(2)),
819 ..TtlBounds::default()
820 });
821 let cache = ResponseCache::new(1, ttls);
822
823 let mut no_records = NoRecords::new(query.clone(), ResponseCode::NoError);
825 no_records.negative_ttl = Some(1);
826 cache.insert(query.clone(), Err(no_records.into()), now);
827 let valid_until = cache.cache.get(&query).unwrap().valid_until;
828 assert_eq!(valid_until, now + Duration::from_secs(2));
830
831 let mut no_records = NoRecords::new(query.clone(), ResponseCode::NoError);
833 no_records.negative_ttl = Some(3);
834 cache.insert(query.clone(), Err(no_records.into()), now);
835 let valid_until = cache.cache.get(&query).unwrap().valid_until;
836 assert_eq!(valid_until, now + Duration::from_secs(3));
839 }
840
841 #[test]
842 fn test_positive_max_ttl() {
843 let now = Instant::now();
844
845 let name = Name::from_str("www.example.com.").unwrap();
846 let query = Query::query(name.clone(), RecordType::A);
847 let mut message = Message::response(0, OpCode::Query);
849 message.add_answer(Record::from_rdata(
850 name.clone(),
851 62,
852 RData::A(A::new(127, 0, 0, 1)),
853 ));
854
855 let ttls = TtlConfig::from(TtlBounds {
857 positive_max_ttl: Some(Duration::from_secs(60)),
858 ..Default::default()
859 });
860 let cache = ResponseCache::new(1, ttls);
861
862 cache.insert(query.clone(), Ok(message), now);
863 let valid_until = cache.cache.get(&query).unwrap().valid_until;
864 assert_eq!(valid_until, now + Duration::from_secs(60));
867
868 let mut message = Message::response(0, OpCode::Query);
870 message.add_answer(Record::from_rdata(
871 name.clone(),
872 59,
873 RData::A(A::new(127, 0, 0, 1)),
874 ));
875
876 cache.insert(query.clone(), Ok(message), now);
877 let valid_until = cache.cache.get(&query).unwrap().valid_until;
878 assert_eq!(valid_until, now + Duration::from_secs(59));
881 }
882
883 #[test]
884 fn test_negative_max_ttl() {
885 let now = Instant::now();
886
887 let name = Name::from_str("www.example.com.").unwrap();
888 let query = Query::query(name.clone(), RecordType::A);
889
890 let ttls = TtlConfig::from(TtlBounds {
892 negative_max_ttl: Some(Duration::from_secs(60)),
893 ..TtlBounds::default()
894 });
895 let cache = ResponseCache::new(1, ttls);
896
897 let mut no_records = NoRecords::new(query.clone(), ResponseCode::NoError);
899 no_records.negative_ttl = Some(62);
900 cache.insert(query.clone(), Err(no_records.into()), now);
901 let valid_until = cache.cache.get(&query).unwrap().valid_until;
902 assert_eq!(valid_until, now + Duration::from_secs(60));
904
905 let mut no_records = NoRecords::new(query.clone(), ResponseCode::NoError);
907 no_records.negative_ttl = Some(59);
908 cache.insert(query.clone(), Err(no_records.into()), now);
909 let valid_until = cache.cache.get(&query).unwrap().valid_until;
910 assert_eq!(valid_until, now + Duration::from_secs(59));
913 }
914
915 #[test]
916 fn test_insert() {
917 let now = Instant::now();
918
919 let name = Name::from_str("www.example.com.").unwrap();
920 let query = Query::query(name.clone(), RecordType::A);
921 let mut message = Message::response(0, OpCode::Query);
922 message.add_answer(Record::from_rdata(
923 name.clone(),
924 1,
925 RData::A(A::new(127, 0, 0, 1)),
926 ));
927 let cache = ResponseCache::new(1, TtlConfig::default());
928 cache.insert(query.clone(), Ok(message.clone()), now);
929
930 let result = cache.get(&query, now).unwrap();
931 let cache_message = result.unwrap();
932 assert_eq!(cache_message.answers, message.answers);
933 }
934
935 #[test]
936 fn test_insert_negative() {
937 subscribe();
938 let now = Instant::now();
939
940 let query = Query::query(
941 Name::from_str("www.example.com.").unwrap(),
942 RecordType::AAAA,
943 );
944
945 let mut norecs = NoRecords::new(query.clone(), ResponseCode::NXDomain);
946 norecs.negative_ttl = Some(10);
947 let error = NetError::from(norecs);
948 let cache = ResponseCache::new(1, TtlConfig::default());
949
950 cache.insert(query.clone(), Err(error), now);
951
952 let cache_err = cache.get(&query, now).unwrap().unwrap_err();
953 let NetError::Dns(DnsError::NoRecordsFound(_no_records)) = &cache_err else {
954 panic!("expected NoRecordsFound");
955 };
956
957 assert!(cache.get(&query, now + Duration::from_secs(11)).is_none());
959 }
960
961 #[test]
962 fn test_update_ttl() {
963 let now = Instant::now();
964
965 let name = Name::from_str("www.example.com.").unwrap();
966 let query = Query::query(name.clone(), RecordType::A);
967 let mut message = Message::response(0, OpCode::Query);
968 message.add_answer(Record::from_rdata(
969 name.clone(),
970 10,
971 RData::A(A::new(127, 0, 0, 1)),
972 ));
973 let cache = ResponseCache::new(1, TtlConfig::default());
974 cache.insert(query.clone(), Ok(message), now);
975
976 let result = cache.get(&query, now + Duration::from_secs(2)).unwrap();
977 let cache_message = result.unwrap();
978 let record = cache_message.answers.first().unwrap();
979 assert_eq!(record.ttl, 8);
980 }
981
982 #[test]
983 fn test_update_ttl_negative() -> Result<(), NetError> {
984 subscribe();
985 let now = Instant::now();
986 let name = Name::from_str("www.example.com.")?;
987 let ns_name = Name::from_str("ns1.example.com")?;
988 let zone_name = name.base_name();
989 let query = Query::query(name.clone(), RecordType::AAAA);
990
991 let mut norecs = NoRecords::new(query.clone(), ResponseCode::NXDomain);
992 norecs.negative_ttl = Some(10);
993 norecs.soa = Some(Box::new(Record::from_rdata(
994 zone_name.clone(),
995 10,
996 SOA::new(name.base_name(), name.clone(), 1, 1, 1, 1, 1),
997 )));
998 norecs.authorities = Some(Arc::new([Record::from_rdata(
999 zone_name.clone(),
1000 10,
1001 RData::NS(NS(ns_name.clone())),
1002 )]));
1003 norecs.ns = Some(Arc::new([ForwardNSData {
1004 ns: Record::from_rdata(zone_name.clone(), 10, RData::NS(NS(ns_name.clone()))),
1005 glue: Arc::new([Record::from_rdata(
1006 ns_name.clone(),
1007 10,
1008 RData::A(A([192, 0, 2, 1].into())),
1009 )]),
1010 }]));
1011
1012 let error = NetError::from(norecs);
1013
1014 let cache = ResponseCache::new(1, TtlConfig::default());
1015 cache.insert(query.clone(), Err(error), now);
1016
1017 let cache_err = cache.get(&query, now).unwrap().unwrap_err();
1018 let NetError::Dns(DnsError::NoRecordsFound(no_records)) = &cache_err else {
1019 panic!("expected NoRecordsFound");
1020 };
1021
1022 let Some(soa) = no_records.soa.clone() else {
1023 panic!("no SOA in NoRecordsFound");
1024 };
1025 assert_eq!(soa.ttl, 10);
1026
1027 let cache_err = cache
1028 .get(&query, now + Duration::from_secs(2))
1029 .unwrap()
1030 .unwrap_err();
1031 let NetError::Dns(DnsError::NoRecordsFound(NoRecords {
1032 negative_ttl: Some(negative_ttl),
1033 soa: Some(soa),
1034 authorities: Some(authorities),
1035 ns: Some(ns),
1036 ..
1037 })) = &cache_err
1038 else {
1039 panic!("expected NoRecordsFound with negative_ttl, soa, authorities, and ns");
1040 };
1041
1042 assert_eq!(*negative_ttl, 8);
1043 assert_eq!(soa.ttl, 8);
1044 assert_eq!(authorities[0].ttl, 8);
1045 assert_eq!(ns[0].ns.ttl, 8);
1046
1047 assert!(cache.get(&query, now + Duration::from_secs(11)).is_none());
1049 Ok(())
1050 }
1051
1052 #[test]
1053 fn test_insert_ttl() {
1054 let now = Instant::now();
1055
1056 let name = Name::from_str("www.example.com.").unwrap();
1057 let query = Query::query(name.clone(), RecordType::A);
1058
1059 let mut message = Message::response(0, OpCode::Query);
1061 message.add_answer(Record::from_rdata(
1062 name.clone(),
1063 1,
1064 RData::A(A::new(127, 0, 0, 1)),
1065 ));
1066 message.add_answer(Record::from_rdata(name, 2, RData::A(A::new(127, 0, 0, 2))));
1067
1068 let cache = ResponseCache::new(1, TtlConfig::default());
1069 cache.insert(query.clone(), Ok(message), now);
1070
1071 cache
1073 .get(&query, now + Duration::from_secs(1))
1074 .unwrap()
1075 .unwrap();
1076
1077 let option = cache.get(&query, now + Duration::from_secs(2));
1079 assert!(option.is_none());
1080 }
1081
1082 #[test]
1083 fn test_ttl_different_query_types() {
1084 let now = Instant::now();
1085 let name = Name::from_str("www.example.com.").unwrap();
1086
1087 let query_a = Query::query(name.clone(), RecordType::A);
1089 let rdata_a = RData::A(A::new(127, 0, 0, 1));
1090 let mut message_a = Message::response(0, OpCode::Query);
1091 message_a.add_answer(Record::from_rdata(name.clone(), 1, rdata_a.clone()));
1092
1093 let query_txt = Query::query(name.clone(), RecordType::TXT);
1094 let rdata_txt = RData::TXT(TXT::new(vec!["data".to_string()]));
1095 let mut message_txt = Message::response(0, OpCode::Query);
1096 message_txt.add_answer(Record::from_rdata(name.clone(), 1, rdata_txt.clone()));
1097
1098 let mut ttl_config = TtlConfig::from(TtlBounds {
1100 positive_min_ttl: Some(Duration::from_secs(2)),
1101 ..TtlBounds::default()
1102 });
1103 ttl_config.with_query_type_ttl_bounds(
1104 RecordType::TXT,
1105 TtlBounds {
1106 positive_min_ttl: Some(Duration::from_secs(5)),
1107 ..TtlBounds::default()
1108 },
1109 );
1110 let cache = ResponseCache::new(2, ttl_config);
1111
1112 cache.insert(query_a.clone(), Ok(message_a), now);
1113 assert_eq!(
1116 cache.cache.get(&query_a).unwrap().valid_until,
1117 now + Duration::from_secs(2)
1118 );
1119
1120 cache.insert(query_txt.clone(), Ok(message_txt), now);
1121 assert_eq!(
1124 cache.cache.get(&query_txt).unwrap().valid_until,
1125 now + Duration::from_secs(5)
1126 );
1127
1128 let mut message_a = Message::response(0, OpCode::Query);
1130 message_a.add_answer(Record::from_rdata(name.clone(), 7, rdata_a));
1131
1132 let mut message_txt = Message::response(0, OpCode::Query);
1133 message_txt.add_answer(Record::from_rdata(name.clone(), 7, rdata_txt));
1134
1135 cache.insert(query_a.clone(), Ok(message_a), now);
1136 assert_eq!(
1138 cache.cache.get(&query_a).unwrap().valid_until,
1139 now + Duration::from_secs(7)
1140 );
1141
1142 cache.insert(query_txt.clone(), Ok(message_txt), now);
1143 assert_eq!(
1145 cache.cache.get(&query_txt).unwrap().valid_until,
1146 now + Duration::from_secs(7)
1147 );
1148 }
1149
1150 #[test]
1151 fn cname_alias_bounds_cache_lifetime() {
1152 let now = Instant::now();
1153 let cname = Name::from_ascii("cname-record.com.").unwrap();
1154 let a = Name::from_ascii("a-record.com.").unwrap();
1155
1156 let query = Query::query(cname.clone(), RecordType::A);
1157
1158 let mut message = Message::response(0, OpCode::Query);
1159 message.add_query(query.clone());
1160
1161 message.add_answer(Record::from_rdata(cname, 5, RData::CNAME(CNAME(a.clone()))));
1163
1164 message.add_answer(Record::from_rdata(
1166 a,
1167 86_400,
1168 RData::A(A::new(51, 34, 100, 105)),
1169 ));
1170
1171 let cache = ResponseCache::new(1, TtlConfig::default());
1172 cache.insert(query.clone(), Ok::<Message, NetError>(message), now);
1173
1174 assert!(
1176 cache.get(&query, now).is_some(),
1177 "freshly inserted response should be a cache hit"
1178 );
1179
1180 assert!(
1183 cache.get(&query, now + Duration::from_secs(6)).is_none(),
1184 "response served past the 5s CNAME TTL"
1185 );
1186 }
1187
1188 #[test]
1191 fn authority_record_cannot_reduce_cache_ttl() {
1192 let now = Instant::now();
1193
1194 let name = Name::from_str("www.example.com.").unwrap();
1195 let query = Query::query(name.clone(), RecordType::A);
1196
1197 let mut message = Message::response(0, OpCode::Query);
1198 message.add_answer(Record::from_rdata(
1200 name.clone(),
1201 300,
1202 RData::A(A::new(93, 184, 216, 34)),
1203 ));
1204 message.add_authority(Record::from_rdata(
1206 Name::from_str("example.com.").unwrap(),
1207 10,
1208 RData::NS(NS(Name::from_str("ns1.example.com.").unwrap())),
1209 ));
1210
1211 let cache = ResponseCache::new(1, TtlConfig::default());
1212 cache.insert(query.clone(), Ok(message), now);
1213
1214 let valid_until = cache.cache.get(&query).unwrap().valid_until;
1215 assert_eq!(valid_until, now + Duration::from_secs(300));
1216
1217 assert!(cache.get(&query, now + Duration::from_secs(60)).is_some());
1219 }
1220
1221 #[test]
1224 fn additional_record_cannot_reduce_cache_ttl() {
1225 let now = Instant::now();
1226
1227 let name = Name::from_str("www.example.com.").unwrap();
1228 let query = Query::query(name.clone(), RecordType::A);
1229
1230 let mut message = Message::response(0, OpCode::Query);
1231 message.add_answer(Record::from_rdata(
1233 name.clone(),
1234 300,
1235 RData::A(A::new(93, 184, 216, 34)),
1236 ));
1237 message.add_additional(Record::from_rdata(
1239 Name::from_str("ns1.example.com.").unwrap(),
1240 5,
1241 RData::A(A::new(198, 51, 100, 1)),
1242 ));
1243
1244 let cache = ResponseCache::new(1, TtlConfig::default());
1245 cache.insert(query.clone(), Ok(message), now);
1246
1247 let valid_until = cache.cache.get(&query).unwrap().valid_until;
1248 assert_eq!(valid_until, now + Duration::from_secs(300));
1249
1250 assert!(cache.get(&query, now + Duration::from_secs(60)).is_some());
1252 }
1253
1254 #[test]
1257 fn authority_and_additional_combined_cannot_reduce_cache_ttl() {
1258 let now = Instant::now();
1259
1260 let name = Name::from_str("www.example.com.").unwrap();
1261 let query = Query::query(name.clone(), RecordType::A);
1262
1263 let mut message = Message::response(0, OpCode::Query);
1264 message.add_answer(Record::from_rdata(
1266 name.clone(),
1267 600,
1268 RData::A(A::new(93, 184, 216, 34)),
1269 ));
1270 message.add_authority(Record::from_rdata(
1272 Name::from_str("example.com.").unwrap(),
1273 1,
1274 RData::SOA(SOA::new(
1275 Name::from_str("example.com.").unwrap(),
1276 Name::from_str("admin.example.com.").unwrap(),
1277 2024010100,
1278 3600,
1279 900,
1280 604800,
1281 60,
1282 )),
1283 ));
1284 message.add_authority(Record::from_rdata(
1286 Name::from_str("example.com.").unwrap(),
1287 2,
1288 RData::NS(NS(Name::from_str("ns1.example.com.").unwrap())),
1289 ));
1290 message.add_additional(Record::from_rdata(
1292 Name::from_str("ns1.example.com.").unwrap(),
1293 3,
1294 RData::A(A::new(198, 51, 100, 1)),
1295 ));
1296 message.add_additional(Record::from_rdata(
1298 Name::from_str("ns1.example.com.").unwrap(),
1299 4,
1300 RData::AAAA(AAAA::new(0x2001, 0x0db8, 0, 0, 0, 0, 0, 53)),
1301 ));
1302
1303 let cache = ResponseCache::new(1, TtlConfig::default());
1304 cache.insert(query.clone(), Ok(message), now);
1305
1306 let valid_until = cache.cache.get(&query).unwrap().valid_until;
1308 assert_eq!(valid_until, now + Duration::from_secs(600));
1309 }
1310
1311 #[test]
1312 fn referral_response_cache_ttl() {
1313 let now = Instant::now();
1314
1315 let name = Name::from_str("example.com.").unwrap();
1316 let query = Query::query(name.clone(), RecordType::NS);
1317
1318 let mut message = Message::response(0, OpCode::Query);
1319 message.add_query(query.clone());
1320 message.add_authority(Record::from_rdata(
1322 name,
1323 300,
1324 RData::NS(NS(Name::from_str("ns1.example.com.").unwrap())),
1325 ));
1326 message.metadata.authoritative = false;
1327
1328 let response = DnsResponse::from_message(message.clone()).unwrap();
1331 let result = DnsError::from_response(response);
1332 assert!(
1333 result.is_ok(),
1334 "expected referral response to be returned in Ok: {result:?}"
1335 );
1336
1337 let cache = ResponseCache::new(1, TtlConfig::default());
1338 cache.insert(query.clone(), Ok(message), now);
1339
1340 let valid_until = cache.cache.get(&query).unwrap().valid_until;
1341 assert_eq!(valid_until, now + Duration::from_secs(300));
1342 }
1343
1344 #[cfg(feature = "serde")]
1345 #[test]
1346 fn ttl_config_deserialize_errors() {
1347 let input = r#"[default]
1349positive_max_ttl = 3600
1350[default]
1351positive_max_ttl = 3599"#;
1352 let error = toml::from_str::<TtlConfig>(input).unwrap_err();
1353 assert!(
1354 error.message().contains("duplicate key"),
1355 "wrong error message: {error}"
1356 );
1357
1358 let input = r#"[default]
1360positive_max_ttl = 86400
1361[OPENPGPKEY]
1362positive_max_ttl = 3600
1363[OPENPGPKEY]
1364negative_min_ttl = 60"#;
1365 let error = toml::from_str::<TtlConfig>(input).unwrap_err();
1366 assert!(
1367 error.message().contains("duplicate key"),
1368 "wrong error message: {error}"
1369 );
1370
1371 let input = r#"[not_a_record_type]
1373positive_max_ttl = 3600"#;
1374 let error = toml::from_str::<TtlConfig>(input).unwrap_err();
1375 assert!(
1376 error.message().contains("data did not match any variant"),
1377 "wrong error message: {error}"
1378 );
1379
1380 #[derive(Debug, Deserialize)]
1382 struct Wrapper {
1383 #[allow(unused)]
1384 cache_policy: TtlConfig,
1385 }
1386 let input = r#"cache_policy = []"#;
1387 let error = toml::from_str::<Wrapper>(input).unwrap_err();
1388 assert!(
1389 error.message().contains("invalid type: sequence"),
1390 "wrong error message: {error}"
1391 );
1392
1393 let input = r#"cache_policy = "yes""#;
1395 let error = toml::from_str::<Wrapper>(input).unwrap_err();
1396 assert!(
1397 error.message().contains("invalid type: string"),
1398 "wrong error message: {error}"
1399 );
1400 }
1401}