Skip to content

Commit

Permalink
feat: honor cache flush
Browse files Browse the repository at this point in the history
According to RFC 6762, when the cache-flush bit is set on a RR, all previously
cache entries should be set to expire 1 second in the future.

This bit was previously called 'UNIQUE` which might have the same meaning, but
cache-flush seems more in line with the RFC
  • Loading branch information
lyager committed Apr 24, 2024
1 parent 1b9647d commit 4ab5e6e
Show file tree
Hide file tree
Showing 2 changed files with 52 additions and 23 deletions.
44 changes: 32 additions & 12 deletions src/dns_parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ pub(crate) const TYPE_ANY: u16 = 255;

pub(crate) const CLASS_IN: u16 = 1;
pub(crate) const CLASS_MASK: u16 = 0x7FFF;
pub(crate) const CLASS_UNIQUE: u16 = 0x8000;
pub(crate) const CLASS_MASK_CACHE_FLUSH: u16 = 0x8000;

/// Max size of UDP datagram payload: 9000 bytes - IP header 20 bytes - UDP header 8 bytes.
/// Reference: RFC6762: https://datatracker.ietf.org/doc/html/rfc6762#section-17
Expand All @@ -57,7 +57,7 @@ pub(crate) struct DnsEntry {
pub(crate) name: String, // always lower case.
pub(crate) ty: u16,
class: u16,
unique: bool,
cache_flush: bool,
}

impl DnsEntry {
Expand All @@ -66,7 +66,7 @@ impl DnsEntry {
name,
ty,
class: class & CLASS_MASK,
unique: (class & CLASS_UNIQUE) != 0,
cache_flush: (class & CLASS_MASK_CACHE_FLUSH) != 0,
}
}
}
Expand Down Expand Up @@ -134,6 +134,12 @@ impl DnsRecord {
cmp::max(0, remaining_millis / 1000) as u32
}

fn expire(&mut self) {
// When cache flush is asked, we set expire date to 1 second in the future
// Ref: RFC 6762 Section 10.2
self.expires = current_time_millis() + 1000;
}

fn reset_ttl(&mut self, other: &DnsRecord) {
self.ttl = other.ttl;
self.created = other.created;
Expand All @@ -157,6 +163,10 @@ pub(crate) trait DnsRecordExt: fmt::Debug {
/// Returns whether `other` record is considered the same except TTL.
fn matches(&self, other: &dyn DnsRecordExt) -> bool;

fn get_cache_flush(&self) -> bool {
self.get_record().entry.cache_flush
}

fn get_name(&self) -> &str {
self.get_record().entry.name.as_str()
}
Expand All @@ -168,6 +178,10 @@ pub(crate) trait DnsRecordExt: fmt::Debug {
self.get_record_mut().reset_ttl(other.get_record());
}

fn expire(&mut self) {
self.get_record_mut().expire();
}

/// Returns true if another record has matched content,
/// and if its TTL is at least half of this record's.
fn suppressed_by_answer(&self, other: &dyn DnsRecordExt) -> bool {
Expand Down Expand Up @@ -546,9 +560,9 @@ impl DnsOutPacket {
let record = record_ext.get_record();
self.write_name(&record.entry.name);
self.write_short(record.entry.ty);
if record.entry.unique {
if record.entry.cache_flush {
// check "multicast"
self.write_short(record.entry.class | CLASS_UNIQUE);
self.write_short(record.entry.class | CLASS_MASK_CACHE_FLUSH);
} else {
self.write_short(record.entry.class);
}
Expand Down Expand Up @@ -825,7 +839,7 @@ impl DnsOutgoing {
// https://tools.ietf.org/html/rfc6763#section-12.1.
self.add_additional_answer(Box::new(DnsSrv::new(
service.get_fullname(),
CLASS_IN | CLASS_UNIQUE,
CLASS_IN | CLASS_MASK_CACHE_FLUSH,
service.get_host_ttl(),
service.get_priority(),
service.get_weight(),
Expand All @@ -836,7 +850,7 @@ impl DnsOutgoing {
self.add_additional_answer(Box::new(DnsTxt::new(
service.get_fullname(),
TYPE_TXT,
CLASS_IN | CLASS_UNIQUE,
CLASS_IN | CLASS_MASK_CACHE_FLUSH,
service.get_host_ttl(),
service.generate_txt(),
)));
Expand All @@ -850,7 +864,7 @@ impl DnsOutgoing {
self.add_additional_answer(Box::new(DnsAddress::new(
service.get_hostname(),
t,
CLASS_IN | CLASS_UNIQUE,
CLASS_IN | CLASS_MASK_CACHE_FLUSH,
service.get_host_ttl(),
address,
)));
Expand Down Expand Up @@ -1331,8 +1345,8 @@ mod tests {
use crate::dns_parser::{TYPE_A, TYPE_AAAA};

use super::{
DnsIncoming, DnsNSec, DnsOutgoing, DnsSrv, CLASS_IN, CLASS_UNIQUE, FLAGS_QR_QUERY,
FLAGS_QR_RESPONSE, TYPE_PTR,
DnsIncoming, DnsNSec, DnsOutgoing, DnsSrv, CLASS_IN, CLASS_MASK_CACHE_FLUSH,
FLAGS_QR_QUERY, FLAGS_QR_RESPONSE, TYPE_PTR,
};

#[test]
Expand Down Expand Up @@ -1379,7 +1393,7 @@ mod tests {
let mut response = DnsOutgoing::new(FLAGS_QR_RESPONSE);
response.add_additional_answer(Box::new(DnsSrv::new(
name,
CLASS_IN | CLASS_UNIQUE,
CLASS_IN | CLASS_MASK_CACHE_FLUSH,
1,
1,
1,
Expand Down Expand Up @@ -1407,7 +1421,13 @@ mod tests {
let name = "instance1._nsec_test._udp.local.";
let next_domain = name.to_string();
let type_bitmap = vec![64, 0, 0, 8]; // Two bits set to '1': bit 1 and bit 28.
let nsec = DnsNSec::new(name, CLASS_IN | CLASS_UNIQUE, 1, next_domain, type_bitmap);
let nsec = DnsNSec::new(
name,
CLASS_IN | CLASS_MASK_CACHE_FLUSH,
1,
next_domain,
type_bitmap,
);
let absent_types = nsec._types();
assert_eq!(absent_types.len(), 2);
assert_eq!(absent_types[0], TYPE_A);
Expand Down
31 changes: 20 additions & 11 deletions src/service_daemon.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ use crate::log::{debug, error, warn};
use crate::{
dns_parser::{
current_time_millis, DnsAddress, DnsIncoming, DnsOutgoing, DnsPointer, DnsRecordBox,
DnsRecordExt, DnsSrv, DnsTxt, CLASS_IN, CLASS_UNIQUE, FLAGS_AA, FLAGS_QR_QUERY,
DnsRecordExt, DnsSrv, DnsTxt, CLASS_IN, CLASS_MASK_CACHE_FLUSH, FLAGS_AA, FLAGS_QR_QUERY,
FLAGS_QR_RESPONSE, MAX_MSG_ABSOLUTE, TYPE_A, TYPE_AAAA, TYPE_ANY, TYPE_NSEC, TYPE_PTR,
TYPE_SRV, TYPE_TXT,
},
Expand Down Expand Up @@ -1418,7 +1418,7 @@ impl Zeroconf {
out.add_answer_at_time(
Box::new(DnsSrv::new(
info.get_fullname(),
CLASS_IN | CLASS_UNIQUE,
CLASS_IN | CLASS_MASK_CACHE_FLUSH,
info.get_host_ttl(),
info.get_priority(),
info.get_weight(),
Expand All @@ -1431,7 +1431,7 @@ impl Zeroconf {
Box::new(DnsTxt::new(
info.get_fullname(),
TYPE_TXT,
CLASS_IN | CLASS_UNIQUE,
CLASS_IN | CLASS_MASK_CACHE_FLUSH,
info.get_other_ttl(),
info.generate_txt(),
)),
Expand All @@ -1452,7 +1452,7 @@ impl Zeroconf {
Box::new(DnsAddress::new(
info.get_hostname(),
t,
CLASS_IN | CLASS_UNIQUE,
CLASS_IN | CLASS_MASK_CACHE_FLUSH,
info.get_host_ttl(),
addr,
)),
Expand Down Expand Up @@ -1494,7 +1494,7 @@ impl Zeroconf {
out.add_answer_at_time(
Box::new(DnsSrv::new(
info.get_fullname(),
CLASS_IN | CLASS_UNIQUE,
CLASS_IN | CLASS_MASK_CACHE_FLUSH,
0,
info.get_priority(),
info.get_weight(),
Expand All @@ -1507,7 +1507,7 @@ impl Zeroconf {
Box::new(DnsTxt::new(
info.get_fullname(),
TYPE_TXT,
CLASS_IN | CLASS_UNIQUE,
CLASS_IN | CLASS_MASK_CACHE_FLUSH,
0,
info.generate_txt(),
)),
Expand All @@ -1523,7 +1523,7 @@ impl Zeroconf {
Box::new(DnsAddress::new(
info.get_hostname(),
t,
CLASS_IN | CLASS_UNIQUE,
CLASS_IN | CLASS_MASK_CACHE_FLUSH,
0,
addr,
)),
Expand Down Expand Up @@ -2036,7 +2036,7 @@ impl Zeroconf {
Box::new(DnsAddress::new(
&question.entry.name,
t,
CLASS_IN | CLASS_UNIQUE,
CLASS_IN | CLASS_MASK_CACHE_FLUSH,
service.get_host_ttl(),
address,
)),
Expand All @@ -2057,7 +2057,7 @@ impl Zeroconf {
&msg,
Box::new(DnsSrv::new(
&question.entry.name,
CLASS_IN | CLASS_UNIQUE,
CLASS_IN | CLASS_MASK_CACHE_FLUSH,
service.get_host_ttl(),
service.get_priority(),
service.get_weight(),
Expand All @@ -2073,7 +2073,7 @@ impl Zeroconf {
Box::new(DnsTxt::new(
&question.entry.name,
TYPE_TXT,
CLASS_IN | CLASS_UNIQUE,
CLASS_IN | CLASS_MASK_CACHE_FLUSH,
service.get_host_ttl(),
service.generate_txt(),
)),
Expand All @@ -2097,7 +2097,7 @@ impl Zeroconf {
out.add_additional_answer(Box::new(DnsAddress::new(
service.get_hostname(),
t,
CLASS_IN | CLASS_UNIQUE,
CLASS_IN | CLASS_MASK_CACHE_FLUSH,
service.get_host_ttl(),
address,
)));
Expand Down Expand Up @@ -2356,6 +2356,15 @@ impl DnsCache {
_ => return None,
};

if incoming.get_cache_flush() {
// Mark all existing records of this type as expired, and prepend the new record.
record_vec.iter_mut().for_each(|r| {
r.expire();
});
record_vec.insert(0, incoming);
return Some((record_vec.first().unwrap(), true));
}

// update TTL for existing record or create a new record.
let (idx, updated) = match record_vec
.iter_mut()
Expand Down

0 comments on commit 4ab5e6e

Please sign in to comment.