1use std::collections::HashMap;
11use std::sync::Arc;
12use std::time::{Duration, Instant};
13
14use lru_cache::LruCache;
15use parking_lot::Mutex;
16
17use proto::op::Query;
18use proto::rr::Record;
19
20use crate::config;
21use crate::error::*;
22use crate::lookup::Lookup;
23
24pub(crate) const MAX_TTL: u32 = 86400_u32;
27
28#[derive(Debug)]
29struct LruValue {
30 lookup: Result<Lookup, ResolveError>,
32 valid_until: Instant,
33}
34
35impl LruValue {
36 fn is_current(&self, now: Instant) -> bool {
38 now <= self.valid_until
39 }
40
41 fn ttl(&self, now: Instant) -> Duration {
43 self.valid_until.saturating_duration_since(now)
44 }
45
46 fn with_updated_ttl(&self, now: Instant) -> Self {
47 let lookup = match self.lookup {
48 Ok(ref lookup) => {
49 let records = lookup
50 .records()
51 .iter()
52 .map(|record| {
53 let mut record = record.clone();
54 record.set_ttl(self.ttl(now).as_secs() as u32);
55 record
56 })
57 .collect::<Vec<Record>>();
58 Ok(Lookup::new_with_deadline(
59 lookup.query().clone(),
60 Arc::from(records),
61 self.valid_until,
62 ))
63 }
64 Err(ref e) => Err(e.clone()),
65 };
66 Self {
67 lookup,
68 valid_until: self.valid_until,
69 }
70 }
71}
72
73#[derive(Clone, Debug)]
75pub struct DnsLru {
76 cache: Arc<Mutex<LruCache<Query, LruValue>>>,
77 positive_min_ttl: Duration,
85 negative_min_ttl: Duration,
93 positive_max_ttl: Duration,
103 negative_max_ttl: Duration,
113}
114
115#[derive(Copy, Clone, Debug, Default)]
123pub struct TtlConfig {
124 pub(crate) positive_min_ttl: Option<Duration>,
129 pub(crate) negative_min_ttl: Option<Duration>,
134 pub(crate) positive_max_ttl: Option<Duration>,
139 pub(crate) negative_max_ttl: Option<Duration>,
144}
145
146impl TtlConfig {
147 pub fn from_opts(opts: &config::ResolverOpts) -> Self {
149 Self {
150 positive_min_ttl: opts.positive_min_ttl,
151 negative_min_ttl: opts.negative_min_ttl,
152 positive_max_ttl: opts.positive_max_ttl,
153 negative_max_ttl: opts.negative_max_ttl,
154 }
155 }
156}
157
158impl DnsLru {
159 pub fn new(capacity: usize, ttl_cfg: TtlConfig) -> Self {
166 let TtlConfig {
167 positive_min_ttl,
168 negative_min_ttl,
169 positive_max_ttl,
170 negative_max_ttl,
171 } = ttl_cfg;
172 let cache = Arc::new(Mutex::new(LruCache::new(capacity)));
173 Self {
174 cache,
175 positive_min_ttl: positive_min_ttl.unwrap_or_else(|| Duration::from_secs(0)),
176 negative_min_ttl: negative_min_ttl.unwrap_or_else(|| Duration::from_secs(0)),
177 positive_max_ttl: positive_max_ttl
178 .unwrap_or_else(|| Duration::from_secs(u64::from(MAX_TTL))),
179 negative_max_ttl: negative_max_ttl
180 .unwrap_or_else(|| Duration::from_secs(u64::from(MAX_TTL))),
181 }
182 }
183
184 pub(crate) fn clear(&self) {
185 self.cache.lock().clear();
186 }
187
188 pub(crate) fn insert(
189 &self,
190 query: Query,
191 records_and_ttl: Vec<(Record, u32)>,
192 now: Instant,
193 ) -> Lookup {
194 let len = records_and_ttl.len();
195 let (records, ttl): (Vec<Record>, Duration) = records_and_ttl.into_iter().fold(
197 (Vec::with_capacity(len), self.positive_max_ttl),
198 |(mut records, mut min_ttl), (record, ttl)| {
199 records.push(record);
200 let ttl = Duration::from_secs(u64::from(ttl));
201 min_ttl = min_ttl.min(ttl);
202 (records, min_ttl)
203 },
204 );
205
206 let ttl = self.positive_min_ttl.max(ttl);
209 let valid_until = now + ttl;
210
211 let lookup = Lookup::new_with_deadline(query.clone(), Arc::from(records), valid_until);
213 self.cache.lock().insert(
214 query,
215 LruValue {
216 lookup: Ok(lookup.clone()),
217 valid_until,
218 },
219 );
220
221 lookup
222 }
223
224 pub fn insert_records(
236 &self,
237 original_query: Query,
238 records: impl Iterator<Item = Record>,
239 now: Instant,
240 ) -> Option<Lookup> {
241 let records = records.fold(
243 HashMap::<Query, Vec<(Record, u32)>>::new(),
244 |mut map, record| {
245 let mut query = Query::query(record.name().clone(), record.record_type());
246 query.set_query_class(record.dns_class());
247
248 let ttl = record.ttl();
249
250 map.entry(query).or_default().push((record, ttl));
251
252 map
253 },
254 );
255
256 let mut lookup = None;
258 for (query, records_and_ttl) in records {
259 let is_query = original_query == query;
260 let inserted = self.insert(query, records_and_ttl, now);
261
262 if is_query {
263 lookup = Some(inserted)
264 }
265 }
266
267 lookup
268 }
269
270 pub(crate) fn duplicate(&self, query: Query, lookup: Lookup, ttl: u32, now: Instant) -> Lookup {
272 let ttl = Duration::from_secs(u64::from(ttl));
273 let valid_until = now + ttl;
274
275 self.cache.lock().insert(
276 query,
277 LruValue {
278 lookup: Ok(lookup.clone()),
279 valid_until,
280 },
281 );
282
283 lookup
284 }
285
286 fn nx_error_with_ttl(error: &mut ResolveError, new_ttl: Duration) {
289 if let ResolveError {
290 kind:
291 ResolveErrorKind::NoRecordsFound {
292 ref mut negative_ttl,
293 ..
294 },
295 ..
296 } = error
297 {
298 *negative_ttl = Some(u32::try_from(new_ttl.as_secs()).unwrap_or(MAX_TTL));
299 }
300 }
301
302 pub(crate) fn negative(
303 &self,
304 query: Query,
305 mut error: ResolveError,
306 now: Instant,
307 ) -> ResolveError {
308 if let ResolveError {
311 kind:
312 ResolveErrorKind::NoRecordsFound {
313 negative_ttl: Some(ttl),
314 ..
315 },
316 ..
317 } = error
318 {
319 let ttl_duration = Duration::from_secs(u64::from(ttl))
320 .clamp(self.negative_min_ttl, self.negative_max_ttl);
323 let valid_until = now + ttl_duration;
324
325 {
326 let error = error.clone();
327
328 self.cache.lock().insert(
329 query,
330 LruValue {
331 lookup: Err(error),
332 valid_until,
333 },
334 );
335 }
336
337 Self::nx_error_with_ttl(&mut error, ttl_duration);
338 }
339
340 error
341 }
342
343 pub fn get(&self, query: &Query, now: Instant) -> Option<Result<Lookup, ResolveError>> {
345 let mut out_of_date = false;
346 let mut cache = self.cache.lock();
347 let lookup = cache.get_mut(query).and_then(|value| {
348 if value.is_current(now) {
349 out_of_date = false;
350 let mut result = value.with_updated_ttl(now).lookup;
351 if let Err(ref mut err) = result {
352 Self::nx_error_with_ttl(err, value.ttl(now));
353 }
354 Some(result)
355 } else {
356 out_of_date = true;
357 None
358 }
359 });
360
361 if out_of_date {
365 cache.remove(query);
366 }
367
368 lookup
369 }
370}
371
372#[cfg(test)]
374mod tests {
375 use std::str::FromStr;
376 use std::time::*;
377
378 use proto::op::{Query, ResponseCode};
379 use proto::rr::rdata::A;
380 use proto::rr::{Name, RData, RecordType};
381
382 use super::*;
383
384 #[test]
385 fn test_is_current() {
386 let now = Instant::now();
387 let not_the_future = now + Duration::from_secs(4);
388 let future = now + Duration::from_secs(5);
389 let past_the_future = now + Duration::from_secs(6);
390
391 let value = LruValue {
392 lookup: Err(ResolveErrorKind::Message("test error").into()),
393 valid_until: future,
394 };
395
396 assert!(value.is_current(now));
397 assert!(value.is_current(not_the_future));
398 assert!(value.is_current(future));
399 assert!(!value.is_current(past_the_future));
400 }
401
402 #[test]
403 fn test_lookup_uses_positive_min_ttl() {
404 let now = Instant::now();
405
406 let name = Name::from_str("www.example.com.").unwrap();
407 let query = Query::query(name.clone(), RecordType::A);
408 let ips_ttl = vec![(
410 Record::from_rdata(name.clone(), 1, RData::A(A::new(127, 0, 0, 1))),
411 1,
412 )];
413 let ips = [RData::A(A::new(127, 0, 0, 1))];
414
415 let ttls = TtlConfig {
417 positive_min_ttl: Some(Duration::from_secs(2)),
418 ..TtlConfig::default()
419 };
420 let lru = DnsLru::new(1, ttls);
421
422 let rc_ips = lru.insert(query.clone(), ips_ttl, now);
423 assert_eq!(*rc_ips.iter().next().unwrap(), ips[0]);
424 assert_eq!(rc_ips.valid_until(), now + Duration::from_secs(2));
427
428 let ips_ttl = vec![(
430 Record::from_rdata(name, 3, RData::A(A::new(127, 0, 0, 1))),
431 3,
432 )];
433
434 let rc_ips = lru.insert(query, ips_ttl, now);
435 assert_eq!(*rc_ips.iter().next().unwrap(), ips[0]);
436 assert_eq!(rc_ips.valid_until(), now + Duration::from_secs(3));
439 }
440
441 #[test]
442 fn test_error_uses_negative_min_ttl() {
443 let now = Instant::now();
444
445 let name = Query::query(Name::from_str("www.example.com.").unwrap(), RecordType::A);
446
447 let ttls = TtlConfig {
449 negative_min_ttl: Some(Duration::from_secs(2)),
450 ..TtlConfig::default()
451 };
452 let lru = DnsLru::new(1, ttls);
453
454 let err = ResolveErrorKind::NoRecordsFound {
456 query: Box::new(name.clone()),
457 soa: None,
458 negative_ttl: Some(1),
459 response_code: ResponseCode::NoError,
460 trusted: false,
461 };
462 let nx_error = lru.negative(name.clone(), err.into(), now);
463 match nx_error.kind() {
464 &ResolveErrorKind::NoRecordsFound { negative_ttl, .. } => {
465 let valid_until = negative_ttl.expect("resolve error should have a deadline");
466 assert_eq!(valid_until, 2);
468 }
469 other => panic!("expected ResolveErrorKind::NoRecordsFound, got {:?}", other),
470 }
471
472 let err = ResolveErrorKind::NoRecordsFound {
474 query: Box::new(name.clone()),
475 soa: None,
476 negative_ttl: Some(3),
477 response_code: ResponseCode::NoError,
478 trusted: false,
479 };
480 let nx_error = lru.negative(name, err.into(), now);
481 match nx_error.kind() {
482 &ResolveErrorKind::NoRecordsFound { negative_ttl, .. } => {
483 let negative_ttl = negative_ttl.expect("ResolveError should have a deadline");
484 assert_eq!(negative_ttl, 3);
487 }
488 other => panic!("expected ResolveErrorKind::NoRecordsFound, got {:?}", other),
489 }
490 }
491
492 #[test]
493 fn test_lookup_uses_positive_max_ttl() {
494 let now = Instant::now();
495
496 let name = Name::from_str("www.example.com.").unwrap();
497 let query = Query::query(name.clone(), RecordType::A);
498 let ips_ttl = vec![(
500 Record::from_rdata(name.clone(), 62, RData::A(A::new(127, 0, 0, 1))),
501 62,
502 )];
503 let ips = [RData::A(A::new(127, 0, 0, 1))];
504
505 let ttls = TtlConfig {
507 positive_max_ttl: Some(Duration::from_secs(60)),
508 ..TtlConfig::default()
509 };
510 let lru = DnsLru::new(1, ttls);
511
512 let rc_ips = lru.insert(query.clone(), ips_ttl, now);
513 assert_eq!(*rc_ips.iter().next().unwrap(), ips[0]);
514 assert_eq!(rc_ips.valid_until(), now + Duration::from_secs(60));
517
518 let ips_ttl = vec![(
520 Record::from_rdata(name, 59, RData::A(A::new(127, 0, 0, 1))),
521 59,
522 )];
523
524 let rc_ips = lru.insert(query, ips_ttl, now);
525 assert_eq!(*rc_ips.iter().next().unwrap(), ips[0]);
526 assert_eq!(rc_ips.valid_until(), now + Duration::from_secs(59));
529 }
530
531 #[test]
532 fn test_error_uses_negative_max_ttl() {
533 let now = Instant::now();
534
535 let name = Query::query(Name::from_str("www.example.com.").unwrap(), RecordType::A);
536
537 let ttls = TtlConfig {
539 negative_max_ttl: Some(Duration::from_secs(60)),
540 ..TtlConfig::default()
541 };
542 let lru = DnsLru::new(1, ttls);
543
544 let err = ResolveErrorKind::NoRecordsFound {
546 query: Box::new(name.clone()),
547 soa: None,
548 negative_ttl: Some(62),
549 response_code: ResponseCode::NoError,
550 trusted: false,
551 };
552 let nx_error = lru.negative(name.clone(), err.into(), now);
553 match nx_error.kind() {
554 &ResolveErrorKind::NoRecordsFound { negative_ttl, .. } => {
555 let negative_ttl = negative_ttl.expect("resolve error should have a deadline");
556 assert_eq!(negative_ttl, 60);
558 }
559 other => panic!("expected ResolveErrorKind::NoRecordsFound, got {:?}", other),
560 }
561
562 let err = ResolveErrorKind::NoRecordsFound {
564 query: Box::new(name.clone()),
565 soa: None,
566 negative_ttl: Some(59),
567 response_code: ResponseCode::NoError,
568 trusted: false,
569 };
570 let nx_error = lru.negative(name, err.into(), now);
571 match nx_error.kind() {
572 &ResolveErrorKind::NoRecordsFound { negative_ttl, .. } => {
573 let negative_ttl = negative_ttl.expect("resolve error should have a deadline");
574 assert_eq!(negative_ttl, 59);
577 }
578 other => panic!("expected ResolveErrorKind::NoRecordsFound, got {:?}", other),
579 }
580 }
581
582 #[test]
583 fn test_insert() {
584 let now = Instant::now();
585
586 let name = Name::from_str("www.example.com.").unwrap();
587 let query = Query::query(name.clone(), RecordType::A);
588 let ips_ttl = vec![(
589 Record::from_rdata(name, 1, RData::A(A::new(127, 0, 0, 1))),
590 1,
591 )];
592 let ips = [RData::A(A::new(127, 0, 0, 1))];
593 let lru = DnsLru::new(1, TtlConfig::default());
594
595 let rc_ips = lru.insert(query.clone(), ips_ttl, now);
596 assert_eq!(*rc_ips.iter().next().unwrap(), ips[0]);
597
598 let rc_ips = lru.get(&query, now).unwrap().expect("records should exist");
599 assert_eq!(*rc_ips.iter().next().unwrap(), ips[0]);
600 }
601
602 #[test]
603 fn test_update_ttl() {
604 let now = Instant::now();
605
606 let name = Name::from_str("www.example.com.").unwrap();
607 let query = Query::query(name.clone(), RecordType::A);
608 let ips_ttl = vec![(
609 Record::from_rdata(name, 10, RData::A(A::new(127, 0, 0, 1))),
610 10,
611 )];
612 let ips = [RData::A(A::new(127, 0, 0, 1))];
613 let lru = DnsLru::new(1, TtlConfig::default());
614
615 let rc_ips = lru.insert(query.clone(), ips_ttl, now);
616 assert_eq!(*rc_ips.iter().next().unwrap(), ips[0]);
617
618 let ttl = lru
619 .get(&query, now + Duration::from_secs(2))
620 .unwrap()
621 .expect("records should exist")
622 .record_iter()
623 .next()
624 .unwrap()
625 .ttl();
626 assert!(ttl <= 8);
627 }
628
629 #[test]
630 fn test_insert_ttl() {
631 let now = Instant::now();
632 let name = Name::from_str("www.example.com.").unwrap();
633 let query = Query::query(name.clone(), RecordType::A);
634 let ips_ttl = vec![
636 (
637 Record::from_rdata(name.clone(), 1, RData::A(A::new(127, 0, 0, 1))),
638 1,
639 ),
640 (
641 Record::from_rdata(name, 2, RData::A(A::new(127, 0, 0, 2))),
642 2,
643 ),
644 ];
645 let ips = vec![
646 RData::A(A::new(127, 0, 0, 1)),
647 RData::A(A::new(127, 0, 0, 2)),
648 ];
649 let lru = DnsLru::new(1, TtlConfig::default());
650
651 lru.insert(query.clone(), ips_ttl, now);
652
653 let rc_ips = lru
655 .get(&query, now + Duration::from_secs(1))
656 .unwrap()
657 .expect("records should exist");
658 assert_eq!(*rc_ips.iter().next().unwrap(), ips[0]);
659
660 let rc_ips = lru.get(&query, now + Duration::from_secs(2));
662 assert!(rc_ips.is_none());
663 }
664
665 #[test]
666 fn test_insert_positive_min_ttl() {
667 let now = Instant::now();
668 let name = Name::from_str("www.example.com.").unwrap();
669 let query = Query::query(name.clone(), RecordType::A);
670 let ips_ttl = vec![
672 (
673 Record::from_rdata(name.clone(), 1, RData::A(A::new(127, 0, 0, 1))),
674 1,
675 ),
676 (
677 Record::from_rdata(name, 2, RData::A(A::new(127, 0, 0, 2))),
678 2,
679 ),
680 ];
681 let ips = vec![
682 RData::A(A::new(127, 0, 0, 1)),
683 RData::A(A::new(127, 0, 0, 2)),
684 ];
685
686 let ttls = TtlConfig {
689 positive_min_ttl: Some(Duration::from_secs(3)),
690 ..TtlConfig::default()
691 };
692 let lru = DnsLru::new(1, ttls);
693 lru.insert(query.clone(), ips_ttl, now);
694
695 let rc_ips = lru
697 .get(&query, now + Duration::from_secs(1))
698 .unwrap()
699 .expect("records should exist");
700 for (rc_ip, ip) in rc_ips.iter().zip(ips.iter()) {
701 assert_eq!(rc_ip, ip, "after 1 second");
702 }
703
704 let rc_ips = lru
705 .get(&query, now + Duration::from_secs(2))
706 .unwrap()
707 .expect("records should exist");
708 for (rc_ip, ip) in rc_ips.iter().zip(ips.iter()) {
709 assert_eq!(rc_ip, ip, "after 2 seconds");
710 }
711
712 let rc_ips = lru
713 .get(&query, now + Duration::from_secs(3))
714 .unwrap()
715 .expect("records should exist");
716 for (rc_ip, ip) in rc_ips.iter().zip(ips.iter()) {
717 assert_eq!(rc_ip, ip, "after 3 seconds");
718 }
719
720 let rc_ips = lru.get(&query, now + Duration::from_secs(4));
722 assert!(rc_ips.is_none());
723 }
724
725 #[test]
726 fn test_insert_positive_max_ttl() {
727 let now = Instant::now();
728 let name = Name::from_str("www.example.com.").unwrap();
729 let query = Query::query(name.clone(), RecordType::A);
730 let ips_ttl = vec![
732 (
733 Record::from_rdata(name.clone(), 400, RData::A(A::new(127, 0, 0, 1))),
734 400,
735 ),
736 (
737 Record::from_rdata(name, 500, RData::A(A::new(127, 0, 0, 2))),
738 500,
739 ),
740 ];
741 let ips = vec![
742 RData::A(A::new(127, 0, 0, 1)),
743 RData::A(A::new(127, 0, 0, 2)),
744 ];
745
746 let ttls = TtlConfig {
749 positive_max_ttl: Some(Duration::from_secs(2)),
750 ..TtlConfig::default()
751 };
752 let lru = DnsLru::new(1, ttls);
753 lru.insert(query.clone(), ips_ttl, now);
754
755 let rc_ips = lru
757 .get(&query, now + Duration::from_secs(1))
758 .unwrap()
759 .expect("records should exist");
760 for (rc_ip, ip) in rc_ips.iter().zip(ips.iter()) {
761 assert_eq!(rc_ip, ip, "after 1 second");
762 }
763
764 let rc_ips = lru
765 .get(&query, now + Duration::from_secs(2))
766 .unwrap()
767 .expect("records should exist");
768 for (rc_ip, ip) in rc_ips.iter().zip(ips.iter()) {
769 assert_eq!(rc_ip, ip, "after 2 seconds");
770 }
771
772 let rc_ips = lru.get(&query, now + Duration::from_secs(3));
774 assert!(rc_ips.is_none());
775 }
776}