1use std::collections::HashMap;
11use std::convert::TryFrom;
12use std::sync::Arc;
13use std::time::{Duration, Instant};
14
15use lru_cache::LruCache;
16use parking_lot::Mutex;
17
18use proto::op::Query;
19use proto::rr::Record;
20
21use crate::config;
22use crate::error::*;
23use crate::lookup::Lookup;
24
25pub(crate) const MAX_TTL: u32 = 86400_u32;
28
29#[derive(Debug)]
30struct LruValue {
31 lookup: Result<Lookup, ResolveError>,
33 valid_until: Instant,
34}
35
36impl LruValue {
37 fn is_current(&self, now: Instant) -> bool {
39 now <= self.valid_until
40 }
41
42 fn ttl(&self, now: Instant) -> Duration {
44 self.valid_until.saturating_duration_since(now)
45 }
46}
47
48#[derive(Clone, Debug)]
50pub struct DnsLru {
51 cache: Arc<Mutex<LruCache<Query, LruValue>>>,
52 positive_min_ttl: Duration,
60 negative_min_ttl: Duration,
68 positive_max_ttl: Duration,
78 negative_max_ttl: Duration,
88}
89
90#[derive(Copy, Clone, Debug, Default)]
98pub struct TtlConfig {
99 pub(crate) positive_min_ttl: Option<Duration>,
104 pub(crate) negative_min_ttl: Option<Duration>,
109 pub(crate) positive_max_ttl: Option<Duration>,
114 pub(crate) negative_max_ttl: Option<Duration>,
119}
120
121impl TtlConfig {
122 pub fn from_opts(opts: &config::ResolverOpts) -> Self {
124 Self {
125 positive_min_ttl: opts.positive_min_ttl,
126 negative_min_ttl: opts.negative_min_ttl,
127 positive_max_ttl: opts.positive_max_ttl,
128 negative_max_ttl: opts.negative_max_ttl,
129 }
130 }
131}
132
133impl DnsLru {
134 pub fn new(capacity: usize, ttl_cfg: TtlConfig) -> Self {
141 let TtlConfig {
142 positive_min_ttl,
143 negative_min_ttl,
144 positive_max_ttl,
145 negative_max_ttl,
146 } = ttl_cfg;
147 let cache = Arc::new(Mutex::new(LruCache::new(capacity)));
148 Self {
149 cache,
150 positive_min_ttl: positive_min_ttl.unwrap_or_else(|| Duration::from_secs(0)),
151 negative_min_ttl: negative_min_ttl.unwrap_or_else(|| Duration::from_secs(0)),
152 positive_max_ttl: positive_max_ttl
153 .unwrap_or_else(|| Duration::from_secs(u64::from(MAX_TTL))),
154 negative_max_ttl: negative_max_ttl
155 .unwrap_or_else(|| Duration::from_secs(u64::from(MAX_TTL))),
156 }
157 }
158
159 pub(crate) fn clear(&self) {
160 self.cache.lock().clear();
161 }
162
163 pub(crate) fn insert(
164 &self,
165 query: Query,
166 records_and_ttl: Vec<(Record, u32)>,
167 now: Instant,
168 ) -> Lookup {
169 let len = records_and_ttl.len();
170 let (records, ttl): (Vec<Record>, Duration) = records_and_ttl.into_iter().fold(
172 (Vec::with_capacity(len), self.positive_max_ttl),
173 |(mut records, mut min_ttl), (record, ttl)| {
174 records.push(record);
175 let ttl = Duration::from_secs(u64::from(ttl));
176 min_ttl = min_ttl.min(ttl);
177 (records, min_ttl)
178 },
179 );
180
181 let ttl = self.positive_min_ttl.max(ttl);
184 let valid_until = now + ttl;
185
186 let lookup = Lookup::new_with_deadline(query.clone(), Arc::from(records), valid_until);
188 self.cache.lock().insert(
189 query,
190 LruValue {
191 lookup: Ok(lookup.clone()),
192 valid_until,
193 },
194 );
195
196 lookup
197 }
198
199 pub fn insert_records(
211 &self,
212 original_query: Query,
213 records: impl Iterator<Item = Record>,
214 now: Instant,
215 ) -> Option<Lookup> {
216 let records = records.fold(
218 HashMap::<Query, Vec<(Record, u32)>>::new(),
219 |mut map, record| {
220 let mut query = Query::query(record.name().clone(), record.record_type());
221 query.set_query_class(record.dns_class());
222
223 let ttl = record.ttl();
224
225 map.entry(query)
226 .or_insert_with(Vec::default)
227 .push((record, ttl));
228
229 map
230 },
231 );
232
233 let mut lookup = None;
235 for (query, records_and_ttl) in records {
236 let is_query = original_query == query;
237 let inserted = self.insert(query, records_and_ttl, now);
238
239 if is_query {
240 lookup = Some(inserted)
241 }
242 }
243
244 lookup
245 }
246
247 pub(crate) fn duplicate(&self, query: Query, lookup: Lookup, ttl: u32, now: Instant) -> Lookup {
249 let ttl = Duration::from_secs(u64::from(ttl));
250 let valid_until = now + ttl;
251
252 self.cache.lock().insert(
253 query,
254 LruValue {
255 lookup: Ok(lookup.clone()),
256 valid_until,
257 },
258 );
259
260 lookup
261 }
262
263 fn nx_error_with_ttl(error: &mut ResolveError, new_ttl: Duration) {
266 if let ResolveError {
267 kind:
268 ResolveErrorKind::NoRecordsFound {
269 ref mut negative_ttl,
270 ..
271 },
272 ..
273 } = error
274 {
275 *negative_ttl = Some(u32::try_from(new_ttl.as_secs()).unwrap_or(MAX_TTL));
276 }
277 }
278
279 pub(crate) fn negative(
280 &self,
281 query: Query,
282 mut error: ResolveError,
283 now: Instant,
284 ) -> ResolveError {
285 if let ResolveError {
288 kind:
289 ResolveErrorKind::NoRecordsFound {
290 negative_ttl: Some(ttl),
291 ..
292 },
293 ..
294 } = error
295 {
296 let ttl_duration = Duration::from_secs(u64::from(ttl))
297 .clamp(self.negative_min_ttl, self.negative_max_ttl);
300 let valid_until = now + ttl_duration;
301
302 {
303 let error = error.clone();
304
305 self.cache.lock().insert(
306 query,
307 LruValue {
308 lookup: Err(error),
309 valid_until,
310 },
311 );
312 }
313
314 Self::nx_error_with_ttl(&mut error, ttl_duration);
315 }
316
317 error
318 }
319
320 pub fn get(&self, query: &Query, now: Instant) -> Option<Result<Lookup, ResolveError>> {
322 let mut out_of_date = false;
323 let mut cache = self.cache.lock();
324 let lookup = cache.get_mut(query).and_then(|value| {
325 if value.is_current(now) {
326 out_of_date = false;
327 let mut result = value.lookup.clone();
328
329 if let Err(ref mut err) = result {
330 Self::nx_error_with_ttl(err, value.ttl(now));
331 }
332 Some(result)
333 } else {
334 out_of_date = true;
335 None
336 }
337 });
338
339 if out_of_date {
343 cache.remove(query);
344 }
345
346 lookup
347 }
348}
349
350#[cfg(test)]
352mod tests {
353 use std::net::*;
354 use std::str::FromStr;
355 use std::time::*;
356
357 use proto::op::{Query, ResponseCode};
358 use proto::rr::{Name, RData, RecordType};
359
360 use super::*;
361
362 #[test]
363 fn test_is_current() {
364 let now = Instant::now();
365 let not_the_future = now + Duration::from_secs(4);
366 let future = now + Duration::from_secs(5);
367 let past_the_future = now + Duration::from_secs(6);
368
369 let value = LruValue {
370 lookup: Err(ResolveErrorKind::Message("test error").into()),
371 valid_until: future,
372 };
373
374 assert!(value.is_current(now));
375 assert!(value.is_current(not_the_future));
376 assert!(value.is_current(future));
377 assert!(!value.is_current(past_the_future));
378 }
379
380 #[test]
381 fn test_lookup_uses_positive_min_ttl() {
382 let now = Instant::now();
383
384 let name = Name::from_str("www.example.com.").unwrap();
385 let query = Query::query(name.clone(), RecordType::A);
386 let ips_ttl = vec![(
388 Record::from_rdata(name.clone(), 1, RData::A(Ipv4Addr::new(127, 0, 0, 1))),
389 1,
390 )];
391 let ips = vec![RData::A(Ipv4Addr::new(127, 0, 0, 1))];
392
393 let ttls = TtlConfig {
395 positive_min_ttl: Some(Duration::from_secs(2)),
396 ..TtlConfig::default()
397 };
398 let lru = DnsLru::new(1, ttls);
399
400 let rc_ips = lru.insert(query.clone(), ips_ttl, now);
401 assert_eq!(*rc_ips.iter().next().unwrap(), ips[0]);
402 assert_eq!(rc_ips.valid_until(), now + Duration::from_secs(2));
405
406 let ips_ttl = vec![(
408 Record::from_rdata(name, 3, RData::A(Ipv4Addr::new(127, 0, 0, 1))),
409 3,
410 )];
411
412 let rc_ips = lru.insert(query, ips_ttl, now);
413 assert_eq!(*rc_ips.iter().next().unwrap(), ips[0]);
414 assert_eq!(rc_ips.valid_until(), now + Duration::from_secs(3));
417 }
418
419 #[test]
420 fn test_error_uses_negative_min_ttl() {
421 let now = Instant::now();
422
423 let name = Query::query(Name::from_str("www.example.com.").unwrap(), RecordType::A);
424
425 let ttls = TtlConfig {
427 negative_min_ttl: Some(Duration::from_secs(2)),
428 ..TtlConfig::default()
429 };
430 let lru = DnsLru::new(1, ttls);
431
432 let err = ResolveErrorKind::NoRecordsFound {
434 query: Box::new(name.clone()),
435 soa: None,
436 negative_ttl: Some(1),
437 response_code: ResponseCode::NoError,
438 trusted: false,
439 };
440 let nx_error = lru.negative(name.clone(), err.into(), now);
441 match nx_error.kind() {
442 &ResolveErrorKind::NoRecordsFound { negative_ttl, .. } => {
443 let valid_until = negative_ttl.expect("resolve error should have a deadline");
444 assert_eq!(valid_until, 2);
446 }
447 other => panic!("expected ResolveErrorKind::NoRecordsFound, got {:?}", other),
448 }
449
450 let err = ResolveErrorKind::NoRecordsFound {
452 query: Box::new(name.clone()),
453 soa: None,
454 negative_ttl: Some(3),
455 response_code: ResponseCode::NoError,
456 trusted: false,
457 };
458 let nx_error = lru.negative(name, err.into(), now);
459 match nx_error.kind() {
460 &ResolveErrorKind::NoRecordsFound { negative_ttl, .. } => {
461 let negative_ttl = negative_ttl.expect("ResolveError should have a deadline");
462 assert_eq!(negative_ttl, 3);
465 }
466 other => panic!("expected ResolveErrorKind::NoRecordsFound, got {:?}", other),
467 }
468 }
469
470 #[test]
471 fn test_lookup_uses_positive_max_ttl() {
472 let now = Instant::now();
473
474 let name = Name::from_str("www.example.com.").unwrap();
475 let query = Query::query(name.clone(), RecordType::A);
476 let ips_ttl = vec![(
478 Record::from_rdata(name.clone(), 62, RData::A(Ipv4Addr::new(127, 0, 0, 1))),
479 62,
480 )];
481 let ips = vec![RData::A(Ipv4Addr::new(127, 0, 0, 1))];
482
483 let ttls = TtlConfig {
485 positive_max_ttl: Some(Duration::from_secs(60)),
486 ..TtlConfig::default()
487 };
488 let lru = DnsLru::new(1, ttls);
489
490 let rc_ips = lru.insert(query.clone(), ips_ttl, now);
491 assert_eq!(*rc_ips.iter().next().unwrap(), ips[0]);
492 assert_eq!(rc_ips.valid_until(), now + Duration::from_secs(60));
495
496 let ips_ttl = vec![(
498 Record::from_rdata(name, 59, RData::A(Ipv4Addr::new(127, 0, 0, 1))),
499 59,
500 )];
501
502 let rc_ips = lru.insert(query, ips_ttl, now);
503 assert_eq!(*rc_ips.iter().next().unwrap(), ips[0]);
504 assert_eq!(rc_ips.valid_until(), now + Duration::from_secs(59));
507 }
508
509 #[test]
510 fn test_error_uses_negative_max_ttl() {
511 let now = Instant::now();
512
513 let name = Query::query(Name::from_str("www.example.com.").unwrap(), RecordType::A);
514
515 let ttls = TtlConfig {
517 negative_max_ttl: Some(Duration::from_secs(60)),
518 ..TtlConfig::default()
519 };
520 let lru = DnsLru::new(1, ttls);
521
522 let err = ResolveErrorKind::NoRecordsFound {
524 query: Box::new(name.clone()),
525 soa: None,
526 negative_ttl: Some(62),
527 response_code: ResponseCode::NoError,
528 trusted: false,
529 };
530 let nx_error = lru.negative(name.clone(), err.into(), now);
531 match nx_error.kind() {
532 &ResolveErrorKind::NoRecordsFound { negative_ttl, .. } => {
533 let negative_ttl = negative_ttl.expect("resolve error should have a deadline");
534 assert_eq!(negative_ttl, 60);
536 }
537 other => panic!("expected ResolveErrorKind::NoRecordsFound, got {:?}", other),
538 }
539
540 let err = ResolveErrorKind::NoRecordsFound {
542 query: Box::new(name.clone()),
543 soa: None,
544 negative_ttl: Some(59),
545 response_code: ResponseCode::NoError,
546 trusted: false,
547 };
548 let nx_error = lru.negative(name, err.into(), now);
549 match nx_error.kind() {
550 &ResolveErrorKind::NoRecordsFound { negative_ttl, .. } => {
551 let negative_ttl = negative_ttl.expect("resolve error should have a deadline");
552 assert_eq!(negative_ttl, 59);
555 }
556 other => panic!("expected ResolveErrorKind::NoRecordsFound, got {:?}", other),
557 }
558 }
559
560 #[test]
561 fn test_insert() {
562 let now = Instant::now();
563
564 let name = Name::from_str("www.example.com.").unwrap();
565 let query = Query::query(name.clone(), RecordType::A);
566 let ips_ttl = vec![(
567 Record::from_rdata(name, 1, RData::A(Ipv4Addr::new(127, 0, 0, 1))),
568 1,
569 )];
570 let ips = vec![RData::A(Ipv4Addr::new(127, 0, 0, 1))];
571 let lru = DnsLru::new(1, TtlConfig::default());
572
573 let rc_ips = lru.insert(query.clone(), ips_ttl, now);
574 assert_eq!(*rc_ips.iter().next().unwrap(), ips[0]);
575
576 let rc_ips = lru.get(&query, now).unwrap().expect("records should exist");
577 assert_eq!(*rc_ips.iter().next().unwrap(), ips[0]);
578 }
579
580 #[test]
581 fn test_insert_ttl() {
582 let now = Instant::now();
583 let name = Name::from_str("www.example.com.").unwrap();
584 let query = Query::query(name.clone(), RecordType::A);
585 let ips_ttl = vec![
587 (
588 Record::from_rdata(name.clone(), 1, RData::A(Ipv4Addr::new(127, 0, 0, 1))),
589 1,
590 ),
591 (
592 Record::from_rdata(name, 2, RData::A(Ipv4Addr::new(127, 0, 0, 2))),
593 2,
594 ),
595 ];
596 let ips = vec![
597 RData::A(Ipv4Addr::new(127, 0, 0, 1)),
598 RData::A(Ipv4Addr::new(127, 0, 0, 2)),
599 ];
600 let lru = DnsLru::new(1, TtlConfig::default());
601
602 lru.insert(query.clone(), ips_ttl, now);
603
604 let rc_ips = lru
606 .get(&query, now + Duration::from_secs(1))
607 .unwrap()
608 .expect("records should exist");
609 assert_eq!(*rc_ips.iter().next().unwrap(), ips[0]);
610
611 let rc_ips = lru.get(&query, now + Duration::from_secs(2));
613 assert!(rc_ips.is_none());
614 }
615
616 #[test]
617 fn test_insert_positive_min_ttl() {
618 let now = Instant::now();
619 let name = Name::from_str("www.example.com.").unwrap();
620 let query = Query::query(name.clone(), RecordType::A);
621 let ips_ttl = vec![
623 (
624 Record::from_rdata(name.clone(), 1, RData::A(Ipv4Addr::new(127, 0, 0, 1))),
625 1,
626 ),
627 (
628 Record::from_rdata(name, 2, RData::A(Ipv4Addr::new(127, 0, 0, 2))),
629 2,
630 ),
631 ];
632 let ips = vec![
633 RData::A(Ipv4Addr::new(127, 0, 0, 1)),
634 RData::A(Ipv4Addr::new(127, 0, 0, 2)),
635 ];
636
637 let ttls = TtlConfig {
640 positive_min_ttl: Some(Duration::from_secs(3)),
641 ..TtlConfig::default()
642 };
643 let lru = DnsLru::new(1, ttls);
644 lru.insert(query.clone(), ips_ttl, now);
645
646 let rc_ips = lru
648 .get(&query, now + Duration::from_secs(1))
649 .unwrap()
650 .expect("records should exist");
651 for (rc_ip, ip) in rc_ips.iter().zip(ips.iter()) {
652 assert_eq!(rc_ip, ip, "after 1 second");
653 }
654
655 let rc_ips = lru
656 .get(&query, now + Duration::from_secs(2))
657 .unwrap()
658 .expect("records should exist");
659 for (rc_ip, ip) in rc_ips.iter().zip(ips.iter()) {
660 assert_eq!(rc_ip, ip, "after 2 seconds");
661 }
662
663 let rc_ips = lru
664 .get(&query, now + Duration::from_secs(3))
665 .unwrap()
666 .expect("records should exist");
667 for (rc_ip, ip) in rc_ips.iter().zip(ips.iter()) {
668 assert_eq!(rc_ip, ip, "after 3 seconds");
669 }
670
671 let rc_ips = lru.get(&query, now + Duration::from_secs(4));
673 assert!(rc_ips.is_none());
674 }
675
676 #[test]
677 fn test_insert_positive_max_ttl() {
678 let now = Instant::now();
679 let name = Name::from_str("www.example.com.").unwrap();
680 let query = Query::query(name.clone(), RecordType::A);
681 let ips_ttl = vec![
683 (
684 Record::from_rdata(name.clone(), 400, RData::A(Ipv4Addr::new(127, 0, 0, 1))),
685 400,
686 ),
687 (
688 Record::from_rdata(name, 500, RData::A(Ipv4Addr::new(127, 0, 0, 2))),
689 500,
690 ),
691 ];
692 let ips = vec![
693 RData::A(Ipv4Addr::new(127, 0, 0, 1)),
694 RData::A(Ipv4Addr::new(127, 0, 0, 2)),
695 ];
696
697 let ttls = TtlConfig {
700 positive_max_ttl: Some(Duration::from_secs(2)),
701 ..TtlConfig::default()
702 };
703 let lru = DnsLru::new(1, ttls);
704 lru.insert(query.clone(), ips_ttl, now);
705
706 let rc_ips = lru
708 .get(&query, now + Duration::from_secs(1))
709 .unwrap()
710 .expect("records should exist");
711 for (rc_ip, ip) in rc_ips.iter().zip(ips.iter()) {
712 assert_eq!(rc_ip, ip, "after 1 second");
713 }
714
715 let rc_ips = lru
716 .get(&query, now + Duration::from_secs(2))
717 .unwrap()
718 .expect("records should exist");
719 for (rc_ip, ip) in rc_ips.iter().zip(ips.iter()) {
720 assert_eq!(rc_ip, ip, "after 2 seconds");
721 }
722
723 let rc_ips = lru.get(&query, now + Duration::from_secs(3));
725 assert!(rc_ips.is_none());
726 }
727}