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)
251 .or_insert_with(Vec::default)
252 .push((record, ttl));
253
254 map
255 },
256 );
257
258 let mut lookup = None;
260 for (query, records_and_ttl) in records {
261 let is_query = original_query == query;
262 let inserted = self.insert(query, records_and_ttl, now);
263
264 if is_query {
265 lookup = Some(inserted)
266 }
267 }
268
269 lookup
270 }
271
272 pub(crate) fn duplicate(&self, query: Query, lookup: Lookup, ttl: u32, now: Instant) -> Lookup {
274 let ttl = Duration::from_secs(u64::from(ttl));
275 let valid_until = now + ttl;
276
277 self.cache.lock().insert(
278 query,
279 LruValue {
280 lookup: Ok(lookup.clone()),
281 valid_until,
282 },
283 );
284
285 lookup
286 }
287
288 fn nx_error_with_ttl(error: &mut ResolveError, new_ttl: Duration) {
291 if let ResolveError {
292 kind:
293 ResolveErrorKind::NoRecordsFound {
294 ref mut negative_ttl,
295 ..
296 },
297 ..
298 } = error
299 {
300 *negative_ttl = Some(u32::try_from(new_ttl.as_secs()).unwrap_or(MAX_TTL));
301 }
302 }
303
304 pub(crate) fn negative(
305 &self,
306 query: Query,
307 mut error: ResolveError,
308 now: Instant,
309 ) -> ResolveError {
310 if let ResolveError {
313 kind:
314 ResolveErrorKind::NoRecordsFound {
315 negative_ttl: Some(ttl),
316 ..
317 },
318 ..
319 } = error
320 {
321 let ttl_duration = Duration::from_secs(u64::from(ttl))
322 .clamp(self.negative_min_ttl, self.negative_max_ttl);
325 let valid_until = now + ttl_duration;
326
327 {
328 let error = error.clone();
329
330 self.cache.lock().insert(
331 query,
332 LruValue {
333 lookup: Err(error),
334 valid_until,
335 },
336 );
337 }
338
339 Self::nx_error_with_ttl(&mut error, ttl_duration);
340 }
341
342 error
343 }
344
345 pub fn get(&self, query: &Query, now: Instant) -> Option<Result<Lookup, ResolveError>> {
347 let mut out_of_date = false;
348 let mut cache = self.cache.lock();
349 let lookup = cache.get_mut(query).and_then(|value| {
350 if value.is_current(now) {
351 out_of_date = false;
352 let mut result = value.with_updated_ttl(now).lookup;
353 if let Err(ref mut err) = result {
354 Self::nx_error_with_ttl(err, value.ttl(now));
355 }
356 Some(result)
357 } else {
358 out_of_date = true;
359 None
360 }
361 });
362
363 if out_of_date {
367 cache.remove(query);
368 }
369
370 lookup
371 }
372}
373
374#[cfg(test)]
376mod tests {
377 use std::str::FromStr;
378 use std::time::*;
379
380 use proto::op::{Query, ResponseCode};
381 use proto::rr::rdata::A;
382 use proto::rr::{Name, RData, RecordType};
383
384 use super::*;
385
386 #[test]
387 fn test_is_current() {
388 let now = Instant::now();
389 let not_the_future = now + Duration::from_secs(4);
390 let future = now + Duration::from_secs(5);
391 let past_the_future = now + Duration::from_secs(6);
392
393 let value = LruValue {
394 lookup: Err(ResolveErrorKind::Message("test error").into()),
395 valid_until: future,
396 };
397
398 assert!(value.is_current(now));
399 assert!(value.is_current(not_the_future));
400 assert!(value.is_current(future));
401 assert!(!value.is_current(past_the_future));
402 }
403
404 #[test]
405 fn test_lookup_uses_positive_min_ttl() {
406 let now = Instant::now();
407
408 let name = Name::from_str("www.example.com.").unwrap();
409 let query = Query::query(name.clone(), RecordType::A);
410 let ips_ttl = vec![(
412 Record::from_rdata(name.clone(), 1, RData::A(A::new(127, 0, 0, 1))),
413 1,
414 )];
415 let ips = vec![RData::A(A::new(127, 0, 0, 1))];
416
417 let ttls = TtlConfig {
419 positive_min_ttl: Some(Duration::from_secs(2)),
420 ..TtlConfig::default()
421 };
422 let lru = DnsLru::new(1, ttls);
423
424 let rc_ips = lru.insert(query.clone(), ips_ttl, now);
425 assert_eq!(*rc_ips.iter().next().unwrap(), ips[0]);
426 assert_eq!(rc_ips.valid_until(), now + Duration::from_secs(2));
429
430 let ips_ttl = vec![(
432 Record::from_rdata(name, 3, RData::A(A::new(127, 0, 0, 1))),
433 3,
434 )];
435
436 let rc_ips = lru.insert(query, ips_ttl, now);
437 assert_eq!(*rc_ips.iter().next().unwrap(), ips[0]);
438 assert_eq!(rc_ips.valid_until(), now + Duration::from_secs(3));
441 }
442
443 #[test]
444 fn test_error_uses_negative_min_ttl() {
445 let now = Instant::now();
446
447 let name = Query::query(Name::from_str("www.example.com.").unwrap(), RecordType::A);
448
449 let ttls = TtlConfig {
451 negative_min_ttl: Some(Duration::from_secs(2)),
452 ..TtlConfig::default()
453 };
454 let lru = DnsLru::new(1, ttls);
455
456 let err = ResolveErrorKind::NoRecordsFound {
458 query: Box::new(name.clone()),
459 soa: None,
460 negative_ttl: Some(1),
461 response_code: ResponseCode::NoError,
462 trusted: false,
463 };
464 let nx_error = lru.negative(name.clone(), err.into(), now);
465 match nx_error.kind() {
466 &ResolveErrorKind::NoRecordsFound { negative_ttl, .. } => {
467 let valid_until = negative_ttl.expect("resolve error should have a deadline");
468 assert_eq!(valid_until, 2);
470 }
471 other => panic!("expected ResolveErrorKind::NoRecordsFound, got {:?}", other),
472 }
473
474 let err = ResolveErrorKind::NoRecordsFound {
476 query: Box::new(name.clone()),
477 soa: None,
478 negative_ttl: Some(3),
479 response_code: ResponseCode::NoError,
480 trusted: false,
481 };
482 let nx_error = lru.negative(name, err.into(), now);
483 match nx_error.kind() {
484 &ResolveErrorKind::NoRecordsFound { negative_ttl, .. } => {
485 let negative_ttl = negative_ttl.expect("ResolveError should have a deadline");
486 assert_eq!(negative_ttl, 3);
489 }
490 other => panic!("expected ResolveErrorKind::NoRecordsFound, got {:?}", other),
491 }
492 }
493
494 #[test]
495 fn test_lookup_uses_positive_max_ttl() {
496 let now = Instant::now();
497
498 let name = Name::from_str("www.example.com.").unwrap();
499 let query = Query::query(name.clone(), RecordType::A);
500 let ips_ttl = vec![(
502 Record::from_rdata(name.clone(), 62, RData::A(A::new(127, 0, 0, 1))),
503 62,
504 )];
505 let ips = vec![RData::A(A::new(127, 0, 0, 1))];
506
507 let ttls = TtlConfig {
509 positive_max_ttl: Some(Duration::from_secs(60)),
510 ..TtlConfig::default()
511 };
512 let lru = DnsLru::new(1, ttls);
513
514 let rc_ips = lru.insert(query.clone(), ips_ttl, now);
515 assert_eq!(*rc_ips.iter().next().unwrap(), ips[0]);
516 assert_eq!(rc_ips.valid_until(), now + Duration::from_secs(60));
519
520 let ips_ttl = vec![(
522 Record::from_rdata(name, 59, RData::A(A::new(127, 0, 0, 1))),
523 59,
524 )];
525
526 let rc_ips = lru.insert(query, ips_ttl, now);
527 assert_eq!(*rc_ips.iter().next().unwrap(), ips[0]);
528 assert_eq!(rc_ips.valid_until(), now + Duration::from_secs(59));
531 }
532
533 #[test]
534 fn test_error_uses_negative_max_ttl() {
535 let now = Instant::now();
536
537 let name = Query::query(Name::from_str("www.example.com.").unwrap(), RecordType::A);
538
539 let ttls = TtlConfig {
541 negative_max_ttl: Some(Duration::from_secs(60)),
542 ..TtlConfig::default()
543 };
544 let lru = DnsLru::new(1, ttls);
545
546 let err = ResolveErrorKind::NoRecordsFound {
548 query: Box::new(name.clone()),
549 soa: None,
550 negative_ttl: Some(62),
551 response_code: ResponseCode::NoError,
552 trusted: false,
553 };
554 let nx_error = lru.negative(name.clone(), err.into(), now);
555 match nx_error.kind() {
556 &ResolveErrorKind::NoRecordsFound { negative_ttl, .. } => {
557 let negative_ttl = negative_ttl.expect("resolve error should have a deadline");
558 assert_eq!(negative_ttl, 60);
560 }
561 other => panic!("expected ResolveErrorKind::NoRecordsFound, got {:?}", other),
562 }
563
564 let err = ResolveErrorKind::NoRecordsFound {
566 query: Box::new(name.clone()),
567 soa: None,
568 negative_ttl: Some(59),
569 response_code: ResponseCode::NoError,
570 trusted: false,
571 };
572 let nx_error = lru.negative(name, err.into(), now);
573 match nx_error.kind() {
574 &ResolveErrorKind::NoRecordsFound { negative_ttl, .. } => {
575 let negative_ttl = negative_ttl.expect("resolve error should have a deadline");
576 assert_eq!(negative_ttl, 59);
579 }
580 other => panic!("expected ResolveErrorKind::NoRecordsFound, got {:?}", other),
581 }
582 }
583
584 #[test]
585 fn test_insert() {
586 let now = Instant::now();
587
588 let name = Name::from_str("www.example.com.").unwrap();
589 let query = Query::query(name.clone(), RecordType::A);
590 let ips_ttl = vec![(
591 Record::from_rdata(name, 1, RData::A(A::new(127, 0, 0, 1))),
592 1,
593 )];
594 let ips = vec![RData::A(A::new(127, 0, 0, 1))];
595 let lru = DnsLru::new(1, TtlConfig::default());
596
597 let rc_ips = lru.insert(query.clone(), ips_ttl, now);
598 assert_eq!(*rc_ips.iter().next().unwrap(), ips[0]);
599
600 let rc_ips = lru.get(&query, now).unwrap().expect("records should exist");
601 assert_eq!(*rc_ips.iter().next().unwrap(), ips[0]);
602 }
603
604 #[test]
605 fn test_update_ttl() {
606 let now = Instant::now();
607
608 let name = Name::from_str("www.example.com.").unwrap();
609 let query = Query::query(name.clone(), RecordType::A);
610 let ips_ttl = vec![(
611 Record::from_rdata(name, 10, RData::A(A::new(127, 0, 0, 1))),
612 10,
613 )];
614 let ips = vec![RData::A(A::new(127, 0, 0, 1))];
615 let lru = DnsLru::new(1, TtlConfig::default());
616
617 let rc_ips = lru.insert(query.clone(), ips_ttl, now);
618 assert_eq!(*rc_ips.iter().next().unwrap(), ips[0]);
619
620 let ttl = lru
621 .get(&query, now + Duration::from_secs(2))
622 .unwrap()
623 .expect("records should exist")
624 .record_iter()
625 .next()
626 .unwrap()
627 .ttl();
628 assert!(ttl <= 8);
629 }
630
631 #[test]
632 fn test_insert_ttl() {
633 let now = Instant::now();
634 let name = Name::from_str("www.example.com.").unwrap();
635 let query = Query::query(name.clone(), RecordType::A);
636 let ips_ttl = vec![
638 (
639 Record::from_rdata(name.clone(), 1, RData::A(A::new(127, 0, 0, 1))),
640 1,
641 ),
642 (
643 Record::from_rdata(name, 2, RData::A(A::new(127, 0, 0, 2))),
644 2,
645 ),
646 ];
647 let ips = vec![
648 RData::A(A::new(127, 0, 0, 1)),
649 RData::A(A::new(127, 0, 0, 2)),
650 ];
651 let lru = DnsLru::new(1, TtlConfig::default());
652
653 lru.insert(query.clone(), ips_ttl, now);
654
655 let rc_ips = lru
657 .get(&query, now + Duration::from_secs(1))
658 .unwrap()
659 .expect("records should exist");
660 assert_eq!(*rc_ips.iter().next().unwrap(), ips[0]);
661
662 let rc_ips = lru.get(&query, now + Duration::from_secs(2));
664 assert!(rc_ips.is_none());
665 }
666
667 #[test]
668 fn test_insert_positive_min_ttl() {
669 let now = Instant::now();
670 let name = Name::from_str("www.example.com.").unwrap();
671 let query = Query::query(name.clone(), RecordType::A);
672 let ips_ttl = vec![
674 (
675 Record::from_rdata(name.clone(), 1, RData::A(A::new(127, 0, 0, 1))),
676 1,
677 ),
678 (
679 Record::from_rdata(name, 2, RData::A(A::new(127, 0, 0, 2))),
680 2,
681 ),
682 ];
683 let ips = vec![
684 RData::A(A::new(127, 0, 0, 1)),
685 RData::A(A::new(127, 0, 0, 2)),
686 ];
687
688 let ttls = TtlConfig {
691 positive_min_ttl: Some(Duration::from_secs(3)),
692 ..TtlConfig::default()
693 };
694 let lru = DnsLru::new(1, ttls);
695 lru.insert(query.clone(), ips_ttl, now);
696
697 let rc_ips = lru
699 .get(&query, now + Duration::from_secs(1))
700 .unwrap()
701 .expect("records should exist");
702 for (rc_ip, ip) in rc_ips.iter().zip(ips.iter()) {
703 assert_eq!(rc_ip, ip, "after 1 second");
704 }
705
706 let rc_ips = lru
707 .get(&query, now + Duration::from_secs(2))
708 .unwrap()
709 .expect("records should exist");
710 for (rc_ip, ip) in rc_ips.iter().zip(ips.iter()) {
711 assert_eq!(rc_ip, ip, "after 2 seconds");
712 }
713
714 let rc_ips = lru
715 .get(&query, now + Duration::from_secs(3))
716 .unwrap()
717 .expect("records should exist");
718 for (rc_ip, ip) in rc_ips.iter().zip(ips.iter()) {
719 assert_eq!(rc_ip, ip, "after 3 seconds");
720 }
721
722 let rc_ips = lru.get(&query, now + Duration::from_secs(4));
724 assert!(rc_ips.is_none());
725 }
726
727 #[test]
728 fn test_insert_positive_max_ttl() {
729 let now = Instant::now();
730 let name = Name::from_str("www.example.com.").unwrap();
731 let query = Query::query(name.clone(), RecordType::A);
732 let ips_ttl = vec![
734 (
735 Record::from_rdata(name.clone(), 400, RData::A(A::new(127, 0, 0, 1))),
736 400,
737 ),
738 (
739 Record::from_rdata(name, 500, RData::A(A::new(127, 0, 0, 2))),
740 500,
741 ),
742 ];
743 let ips = vec![
744 RData::A(A::new(127, 0, 0, 1)),
745 RData::A(A::new(127, 0, 0, 2)),
746 ];
747
748 let ttls = TtlConfig {
751 positive_max_ttl: Some(Duration::from_secs(2)),
752 ..TtlConfig::default()
753 };
754 let lru = DnsLru::new(1, ttls);
755 lru.insert(query.clone(), ips_ttl, now);
756
757 let rc_ips = lru
759 .get(&query, now + Duration::from_secs(1))
760 .unwrap()
761 .expect("records should exist");
762 for (rc_ip, ip) in rc_ips.iter().zip(ips.iter()) {
763 assert_eq!(rc_ip, ip, "after 1 second");
764 }
765
766 let rc_ips = lru
767 .get(&query, now + Duration::from_secs(2))
768 .unwrap()
769 .expect("records should exist");
770 for (rc_ip, ip) in rc_ips.iter().zip(ips.iter()) {
771 assert_eq!(rc_ip, ip, "after 2 seconds");
772 }
773
774 let rc_ips = lru.get(&query, now + Duration::from_secs(3));
776 assert!(rc_ips.is_none());
777 }
778}