Skip to content

Commit 21bdc9d

Browse files
committed
feat: honor cache flush
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
1 parent 1b9647d commit 21bdc9d

2 files changed

Lines changed: 52 additions & 23 deletions

File tree

src/dns_parser.rs

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ pub(crate) const TYPE_ANY: u16 = 255;
3131

3232
pub(crate) const CLASS_IN: u16 = 1;
3333
pub(crate) const CLASS_MASK: u16 = 0x7FFF;
34-
pub(crate) const CLASS_UNIQUE: u16 = 0x8000;
34+
pub(crate) const CLASS_MASK_CACHE_FLUSH: u16 = 0x8000;
3535

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

6363
impl DnsEntry {
@@ -66,7 +66,7 @@ impl DnsEntry {
6666
name,
6767
ty,
6868
class: class & CLASS_MASK,
69-
unique: (class & CLASS_UNIQUE) != 0,
69+
cache_flush: (class & CLASS_MASK_CACHE_FLUSH) != 0,
7070
}
7171
}
7272
}
@@ -134,6 +134,12 @@ impl DnsRecord {
134134
cmp::max(0, remaining_millis / 1000) as u32
135135
}
136136

137+
fn expire(&mut self) {
138+
// When cache flush is asked, we set expire date to 1 second in the future
139+
// Ref: RFC 6762 Section 10.2
140+
self.expires = current_time_millis() + 1000 as u64;
141+
}
142+
137143
fn reset_ttl(&mut self, other: &DnsRecord) {
138144
self.ttl = other.ttl;
139145
self.created = other.created;
@@ -157,6 +163,10 @@ pub(crate) trait DnsRecordExt: fmt::Debug {
157163
/// Returns whether `other` record is considered the same except TTL.
158164
fn matches(&self, other: &dyn DnsRecordExt) -> bool;
159165

166+
fn get_cache_flush(&self) -> bool {
167+
self.get_record().entry.cache_flush
168+
}
169+
160170
fn get_name(&self) -> &str {
161171
self.get_record().entry.name.as_str()
162172
}
@@ -168,6 +178,10 @@ pub(crate) trait DnsRecordExt: fmt::Debug {
168178
self.get_record_mut().reset_ttl(other.get_record());
169179
}
170180

181+
fn expire(&mut self) {
182+
self.get_record_mut().expire();
183+
}
184+
171185
/// Returns true if another record has matched content,
172186
/// and if its TTL is at least half of this record's.
173187
fn suppressed_by_answer(&self, other: &dyn DnsRecordExt) -> bool {
@@ -546,9 +560,9 @@ impl DnsOutPacket {
546560
let record = record_ext.get_record();
547561
self.write_name(&record.entry.name);
548562
self.write_short(record.entry.ty);
549-
if record.entry.unique {
563+
if record.entry.cache_flush {
550564
// check "multicast"
551-
self.write_short(record.entry.class | CLASS_UNIQUE);
565+
self.write_short(record.entry.class | CLASS_MASK_CACHE_FLUSH);
552566
} else {
553567
self.write_short(record.entry.class);
554568
}
@@ -825,7 +839,7 @@ impl DnsOutgoing {
825839
// https://tools.ietf.org/html/rfc6763#section-12.1.
826840
self.add_additional_answer(Box::new(DnsSrv::new(
827841
service.get_fullname(),
828-
CLASS_IN | CLASS_UNIQUE,
842+
CLASS_IN | CLASS_MASK_CACHE_FLUSH,
829843
service.get_host_ttl(),
830844
service.get_priority(),
831845
service.get_weight(),
@@ -836,7 +850,7 @@ impl DnsOutgoing {
836850
self.add_additional_answer(Box::new(DnsTxt::new(
837851
service.get_fullname(),
838852
TYPE_TXT,
839-
CLASS_IN | CLASS_UNIQUE,
853+
CLASS_IN | CLASS_MASK_CACHE_FLUSH,
840854
service.get_host_ttl(),
841855
service.generate_txt(),
842856
)));
@@ -850,7 +864,7 @@ impl DnsOutgoing {
850864
self.add_additional_answer(Box::new(DnsAddress::new(
851865
service.get_hostname(),
852866
t,
853-
CLASS_IN | CLASS_UNIQUE,
867+
CLASS_IN | CLASS_MASK_CACHE_FLUSH,
854868
service.get_host_ttl(),
855869
address,
856870
)));
@@ -1331,8 +1345,8 @@ mod tests {
13311345
use crate::dns_parser::{TYPE_A, TYPE_AAAA};
13321346

13331347
use super::{
1334-
DnsIncoming, DnsNSec, DnsOutgoing, DnsSrv, CLASS_IN, CLASS_UNIQUE, FLAGS_QR_QUERY,
1335-
FLAGS_QR_RESPONSE, TYPE_PTR,
1348+
DnsIncoming, DnsNSec, DnsOutgoing, DnsSrv, CLASS_IN, CLASS_MASK_CACHE_FLUSH,
1349+
FLAGS_QR_QUERY, FLAGS_QR_RESPONSE, TYPE_PTR,
13361350
};
13371351

13381352
#[test]
@@ -1379,7 +1393,7 @@ mod tests {
13791393
let mut response = DnsOutgoing::new(FLAGS_QR_RESPONSE);
13801394
response.add_additional_answer(Box::new(DnsSrv::new(
13811395
name,
1382-
CLASS_IN | CLASS_UNIQUE,
1396+
CLASS_IN | CLASS_MASK_CACHE_FLUSH,
13831397
1,
13841398
1,
13851399
1,
@@ -1407,7 +1421,13 @@ mod tests {
14071421
let name = "instance1._nsec_test._udp.local.";
14081422
let next_domain = name.to_string();
14091423
let type_bitmap = vec![64, 0, 0, 8]; // Two bits set to '1': bit 1 and bit 28.
1410-
let nsec = DnsNSec::new(name, CLASS_IN | CLASS_UNIQUE, 1, next_domain, type_bitmap);
1424+
let nsec = DnsNSec::new(
1425+
name,
1426+
CLASS_IN | CLASS_MASK_CACHE_FLUSH,
1427+
1,
1428+
next_domain,
1429+
type_bitmap,
1430+
);
14111431
let absent_types = nsec._types();
14121432
assert_eq!(absent_types.len(), 2);
14131433
assert_eq!(absent_types[0], TYPE_A);

src/service_daemon.rs

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ use crate::log::{debug, error, warn};
3333
use crate::{
3434
dns_parser::{
3535
current_time_millis, DnsAddress, DnsIncoming, DnsOutgoing, DnsPointer, DnsRecordBox,
36-
DnsRecordExt, DnsSrv, DnsTxt, CLASS_IN, CLASS_UNIQUE, FLAGS_AA, FLAGS_QR_QUERY,
36+
DnsRecordExt, DnsSrv, DnsTxt, CLASS_IN, CLASS_MASK_CACHE_FLUSH, FLAGS_AA, FLAGS_QR_QUERY,
3737
FLAGS_QR_RESPONSE, MAX_MSG_ABSOLUTE, TYPE_A, TYPE_AAAA, TYPE_ANY, TYPE_NSEC, TYPE_PTR,
3838
TYPE_SRV, TYPE_TXT,
3939
},
@@ -1418,7 +1418,7 @@ impl Zeroconf {
14181418
out.add_answer_at_time(
14191419
Box::new(DnsSrv::new(
14201420
info.get_fullname(),
1421-
CLASS_IN | CLASS_UNIQUE,
1421+
CLASS_IN | CLASS_MASK_CACHE_FLUSH,
14221422
info.get_host_ttl(),
14231423
info.get_priority(),
14241424
info.get_weight(),
@@ -1431,7 +1431,7 @@ impl Zeroconf {
14311431
Box::new(DnsTxt::new(
14321432
info.get_fullname(),
14331433
TYPE_TXT,
1434-
CLASS_IN | CLASS_UNIQUE,
1434+
CLASS_IN | CLASS_MASK_CACHE_FLUSH,
14351435
info.get_other_ttl(),
14361436
info.generate_txt(),
14371437
)),
@@ -1452,7 +1452,7 @@ impl Zeroconf {
14521452
Box::new(DnsAddress::new(
14531453
info.get_hostname(),
14541454
t,
1455-
CLASS_IN | CLASS_UNIQUE,
1455+
CLASS_IN | CLASS_MASK_CACHE_FLUSH,
14561456
info.get_host_ttl(),
14571457
addr,
14581458
)),
@@ -1494,7 +1494,7 @@ impl Zeroconf {
14941494
out.add_answer_at_time(
14951495
Box::new(DnsSrv::new(
14961496
info.get_fullname(),
1497-
CLASS_IN | CLASS_UNIQUE,
1497+
CLASS_IN | CLASS_MASK_CACHE_FLUSH,
14981498
0,
14991499
info.get_priority(),
15001500
info.get_weight(),
@@ -1507,7 +1507,7 @@ impl Zeroconf {
15071507
Box::new(DnsTxt::new(
15081508
info.get_fullname(),
15091509
TYPE_TXT,
1510-
CLASS_IN | CLASS_UNIQUE,
1510+
CLASS_IN | CLASS_MASK_CACHE_FLUSH,
15111511
0,
15121512
info.generate_txt(),
15131513
)),
@@ -1523,7 +1523,7 @@ impl Zeroconf {
15231523
Box::new(DnsAddress::new(
15241524
info.get_hostname(),
15251525
t,
1526-
CLASS_IN | CLASS_UNIQUE,
1526+
CLASS_IN | CLASS_MASK_CACHE_FLUSH,
15271527
0,
15281528
addr,
15291529
)),
@@ -2036,7 +2036,7 @@ impl Zeroconf {
20362036
Box::new(DnsAddress::new(
20372037
&question.entry.name,
20382038
t,
2039-
CLASS_IN | CLASS_UNIQUE,
2039+
CLASS_IN | CLASS_MASK_CACHE_FLUSH,
20402040
service.get_host_ttl(),
20412041
address,
20422042
)),
@@ -2057,7 +2057,7 @@ impl Zeroconf {
20572057
&msg,
20582058
Box::new(DnsSrv::new(
20592059
&question.entry.name,
2060-
CLASS_IN | CLASS_UNIQUE,
2060+
CLASS_IN | CLASS_MASK_CACHE_FLUSH,
20612061
service.get_host_ttl(),
20622062
service.get_priority(),
20632063
service.get_weight(),
@@ -2073,7 +2073,7 @@ impl Zeroconf {
20732073
Box::new(DnsTxt::new(
20742074
&question.entry.name,
20752075
TYPE_TXT,
2076-
CLASS_IN | CLASS_UNIQUE,
2076+
CLASS_IN | CLASS_MASK_CACHE_FLUSH,
20772077
service.get_host_ttl(),
20782078
service.generate_txt(),
20792079
)),
@@ -2097,7 +2097,7 @@ impl Zeroconf {
20972097
out.add_additional_answer(Box::new(DnsAddress::new(
20982098
service.get_hostname(),
20992099
t,
2100-
CLASS_IN | CLASS_UNIQUE,
2100+
CLASS_IN | CLASS_MASK_CACHE_FLUSH,
21012101
service.get_host_ttl(),
21022102
address,
21032103
)));
@@ -2356,6 +2356,15 @@ impl DnsCache {
23562356
_ => return None,
23572357
};
23582358

2359+
if incoming.get_cache_flush() {
2360+
// Mark all existing records of this type as expired, and prepend the new record.
2361+
record_vec.iter_mut().for_each(|r| {
2362+
r.expire();
2363+
});
2364+
record_vec.insert(0, incoming);
2365+
return Some((record_vec.get(0).unwrap(), true));
2366+
}
2367+
23592368
// update TTL for existing record or create a new record.
23602369
let (idx, updated) = match record_vec
23612370
.iter_mut()

0 commit comments

Comments
 (0)