Skip to content

Commit 4729eb6

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 if the RR created more than 1 second ago. 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 4729eb6

3 files changed

Lines changed: 76 additions & 33 deletions

File tree

src/dns_parser.rs

Lines changed: 39 additions & 11 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_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_CACHE_FLUSH) != 0,
7070
}
7171
}
7272
}
@@ -134,6 +134,16 @@ impl DnsRecord {
134134
cmp::max(0, remaining_millis / 1000) as u32
135135
}
136136

137+
/// Return the absolute time for this record being created
138+
fn get_created(&self) -> u64 {
139+
self.created
140+
}
141+
142+
/// Set the absolute expiration time in millis
143+
fn set_expire(&mut self, expire_at: u64) {
144+
self.expires = expire_at;
145+
}
146+
137147
fn reset_ttl(&mut self, other: &DnsRecord) {
138148
self.ttl = other.ttl;
139149
self.created = other.created;
@@ -157,6 +167,10 @@ pub(crate) trait DnsRecordExt: fmt::Debug {
157167
/// Returns whether `other` record is considered the same except TTL.
158168
fn matches(&self, other: &dyn DnsRecordExt) -> bool;
159169

170+
fn get_cache_flush(&self) -> bool {
171+
self.get_record().entry.cache_flush
172+
}
173+
160174
fn get_name(&self) -> &str {
161175
self.get_record().entry.name.as_str()
162176
}
@@ -168,6 +182,14 @@ pub(crate) trait DnsRecordExt: fmt::Debug {
168182
self.get_record_mut().reset_ttl(other.get_record());
169183
}
170184

185+
fn get_created(&self) -> u64 {
186+
self.get_record().get_created()
187+
}
188+
189+
fn set_expire(&mut self, expire_at: u64) {
190+
self.get_record_mut().set_expire(expire_at);
191+
}
192+
171193
/// Returns true if another record has matched content,
172194
/// and if its TTL is at least half of this record's.
173195
fn suppressed_by_answer(&self, other: &dyn DnsRecordExt) -> bool {
@@ -546,9 +568,9 @@ impl DnsOutPacket {
546568
let record = record_ext.get_record();
547569
self.write_name(&record.entry.name);
548570
self.write_short(record.entry.ty);
549-
if record.entry.unique {
571+
if record.entry.cache_flush {
550572
// check "multicast"
551-
self.write_short(record.entry.class | CLASS_UNIQUE);
573+
self.write_short(record.entry.class | CLASS_CACHE_FLUSH);
552574
} else {
553575
self.write_short(record.entry.class);
554576
}
@@ -825,7 +847,7 @@ impl DnsOutgoing {
825847
// https://tools.ietf.org/html/rfc6763#section-12.1.
826848
self.add_additional_answer(Box::new(DnsSrv::new(
827849
service.get_fullname(),
828-
CLASS_IN | CLASS_UNIQUE,
850+
CLASS_IN | CLASS_CACHE_FLUSH,
829851
service.get_host_ttl(),
830852
service.get_priority(),
831853
service.get_weight(),
@@ -836,7 +858,7 @@ impl DnsOutgoing {
836858
self.add_additional_answer(Box::new(DnsTxt::new(
837859
service.get_fullname(),
838860
TYPE_TXT,
839-
CLASS_IN | CLASS_UNIQUE,
861+
CLASS_IN | CLASS_CACHE_FLUSH,
840862
service.get_host_ttl(),
841863
service.generate_txt(),
842864
)));
@@ -850,7 +872,7 @@ impl DnsOutgoing {
850872
self.add_additional_answer(Box::new(DnsAddress::new(
851873
service.get_hostname(),
852874
t,
853-
CLASS_IN | CLASS_UNIQUE,
875+
CLASS_IN | CLASS_CACHE_FLUSH,
854876
service.get_host_ttl(),
855877
address,
856878
)));
@@ -1331,7 +1353,7 @@ mod tests {
13311353
use crate::dns_parser::{TYPE_A, TYPE_AAAA};
13321354

13331355
use super::{
1334-
DnsIncoming, DnsNSec, DnsOutgoing, DnsSrv, CLASS_IN, CLASS_UNIQUE, FLAGS_QR_QUERY,
1356+
DnsIncoming, DnsNSec, DnsOutgoing, DnsSrv, CLASS_CACHE_FLUSH, CLASS_IN, FLAGS_QR_QUERY,
13351357
FLAGS_QR_RESPONSE, TYPE_PTR,
13361358
};
13371359

@@ -1379,7 +1401,7 @@ mod tests {
13791401
let mut response = DnsOutgoing::new(FLAGS_QR_RESPONSE);
13801402
response.add_additional_answer(Box::new(DnsSrv::new(
13811403
name,
1382-
CLASS_IN | CLASS_UNIQUE,
1404+
CLASS_IN | CLASS_CACHE_FLUSH,
13831405
1,
13841406
1,
13851407
1,
@@ -1407,7 +1429,13 @@ mod tests {
14071429
let name = "instance1._nsec_test._udp.local.";
14081430
let next_domain = name.to_string();
14091431
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);
1432+
let nsec = DnsNSec::new(
1433+
name,
1434+
CLASS_IN | CLASS_CACHE_FLUSH,
1435+
1,
1436+
next_domain,
1437+
type_bitmap,
1438+
);
14111439
let absent_types = nsec._types();
14121440
assert_eq!(absent_types.len(), 2);
14131441
assert_eq!(absent_types[0], TYPE_A);

src/service_daemon.rs

Lines changed: 26 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_CACHE_FLUSH, CLASS_IN, 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_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_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_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_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_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_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_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_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_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_CACHE_FLUSH,
21012101
service.get_host_ttl(),
21022102
address,
21032103
)));
@@ -2356,6 +2356,21 @@ 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+
let now = current_time_millis();
2362+
record_vec.iter_mut().for_each(|r| {
2363+
// When cache flush is asked, we set expire date to 1 second in the future
2364+
// if created more than 1 second ago
2365+
// Ref: RFC 6762 Section 10.2
2366+
if now - r.get_created() > 1000 {
2367+
r.set_expire(now + 1000);
2368+
}
2369+
});
2370+
record_vec.insert(0, incoming);
2371+
return Some((record_vec.first().unwrap(), true));
2372+
}
2373+
23592374
// update TTL for existing record or create a new record.
23602375
let (idx, updated) = match record_vec
23612376
.iter_mut()

tests/mdns_test.rs

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,8 @@ fn integration_success() {
5353
.expect("Failed to register our service");
5454

5555
// Browse for a service
56-
let resolve_count = Arc::new(Mutex::new(0));
57-
let resolve_count_clone = resolve_count.clone();
56+
let resolved_ips: Arc<Mutex<HashSet<IpAddr>>> = Arc::new(Mutex::new(HashSet::new()));
57+
let resolved_ips_clone = Arc::clone(&resolved_ips);
5858
let remove_count = Arc::new(Mutex::new(0));
5959
let remove_count_clone = remove_count.clone();
6060
let stopped_count = Arc::new(Mutex::new(0));
@@ -83,8 +83,8 @@ fn integration_success() {
8383
println!("{}", a);
8484
}
8585
if info.get_fullname().contains(&instance_name) {
86-
let mut num = resolve_count_clone.lock().unwrap();
87-
*num += 1;
86+
let mut ip_map = resolved_ips_clone.lock().unwrap();
87+
ip_map.extend(addrs);
8888
}
8989
let hostname = info.get_hostname();
9090
assert_eq!(hostname, host_name);
@@ -146,13 +146,13 @@ fn integration_success() {
146146
let count = addr_count.lock().unwrap();
147147
assert_eq!(*count, my_addrs_count);
148148

149-
// `resolve_count` is not guaranteed to always be 1
150-
// or `my_addrs_count`. If `my_addrs_count` > 1, these
151-
// addrs could be resolved in a single message from
152-
// the daemon, or in separate messages.
153-
let count = resolve_count.lock().unwrap();
154-
assert!(*count >= 1);
155-
assert!(*count <= my_addrs_count);
149+
// IP's can get resolved more than once if fx a cache-flush is asked from the sender of the
150+
// MDNS records, so we look at unique IP addresses to see if they match the number of the
151+
// network interfaces.
152+
let unique_ip_count = resolved_ips.lock().unwrap();
153+
assert_eq!(unique_ip_count.len(), my_addrs_count);
154+
assert!(unique_ip_count.len() >= 1);
155+
assert!(unique_ip_count.len() <= my_addrs_count);
156156

157157
let count = remove_count.lock().unwrap();
158158
assert_eq!(*count, 1);

0 commit comments

Comments
 (0)