dns_server_watcher/lib.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//! DNS Server watcher.
mod stream;
#[cfg(test)]
mod test_util;
use std::cmp::Ordering;
use std::collections::{HashMap, HashSet};
use fidl_fuchsia_net::SocketAddress;
use fidl_fuchsia_net_name::{
DhcpDnsServerSource, Dhcpv6DnsServerSource, DnsServerSource, DnsServer_, NdpDnsServerSource,
StaticDnsServerSource,
};
pub use self::stream::*;
/// The default DNS server port.
pub const DEFAULT_DNS_PORT: u16 = 53;
/// The DNS servers learned from all sources.
#[derive(Default)]
pub struct DnsServers {
/// DNS servers obtained from some default configurations.
///
/// These servers will have the lowest priority of all servers.
default: Vec<DnsServer_>,
/// DNS servers obtained from the netstack.
netstack: Vec<DnsServer_>,
/// DNS servers obtained from DHCPv4 clients.
dhcpv4: HashMap<u64, Vec<DnsServer_>>,
/// DNS servers obtained from DHCPv6 clients.
dhcpv6: HashMap<u64, Vec<DnsServer_>>,
}
impl DnsServers {
/// Sets the DNS servers discovered from `source`.
// TODO(https://fxbug.dev/42133326): Make sure `servers` only contain servers that could be obtained
// from `source`.
pub fn set_servers_from_source(
&mut self,
source: DnsServersUpdateSource,
servers: Vec<DnsServer_>,
) {
let Self { default, netstack, dhcpv4, dhcpv6 } = self;
match source {
DnsServersUpdateSource::Default => *default = servers,
DnsServersUpdateSource::Netstack => *netstack = servers,
DnsServersUpdateSource::Dhcpv4 { interface_id } => {
// We discard existing servers since they are being replaced with
// `servers` - the old servers are useless to us now.
let _: Option<Vec<DnsServer_>> = if servers.is_empty() {
dhcpv4.remove(&interface_id)
} else {
dhcpv4.insert(interface_id, servers)
};
}
DnsServersUpdateSource::Dhcpv6 { interface_id } => {
// We discard existing servers since they are being replaced with
// `servers` - the old servers are useless to us now.
let _: Option<Vec<DnsServer_>> = if servers.is_empty() {
dhcpv6.remove(&interface_id)
} else {
dhcpv6.insert(interface_id, servers)
};
}
}
}
/// Returns a consolidated list of server addresses.
///
/// The servers will be returned deduplicated by their address and sorted by the source
/// that each server was learned from. The servers will be sorted in most to least
/// preferred order, with the most preferred server first. The preference of the servers
/// is NDP, DHCPv4, DHCPv6 then Static, where NDP is the most preferred. No ordering is
/// guaranteed across servers from different sources of the same source-kind, but ordering
/// within a source is maintained.
///
/// Example, say we had servers SA1 and SA2 set for DHCPv6 interface A, and SB1 and SB2
/// for DHCPv6 interface B. The consolidated list will be either one of [SA1, SA2, SB1, SB2]
/// or [SB1, SB2, SA1, SA2]. Notice how the ordering across sources (DHCPv6 interface A vs
/// DHCPv6 interface B) is not fixed, but the ordering within the source ([SA1, SA2] for DHCPv6
/// interface A and [SB1, SB2] for DHCPv6 interface B) is maintained.
///
/// Note, if multiple `DnsServer_`s have the same address but different sources, only
/// the `DnsServer_` with the most preferred source will be present in the consolidated
/// list of servers.
// TODO(https://fxbug.dev/42133571): Consider ordering across sources of the same source-kind based on some
// metric.
pub fn consolidated(&self) -> Vec<SocketAddress> {
self.consolidate_filter_map(|x| x.address)
}
/// Returns a consolidated list of [`DnsServer_`] structs.
///
/// The servers will be returned deduplicated by their address and sorted by the source
/// that each server was learned from. The servers will be sorted in most to least
/// preferred order, with the most preferred server first. The preference of the servers
/// is NDP, DHCPv4, DHCPv6 then Static, where NDP is the most preferred. No ordering is
/// guaranteed across servers from different sources of the same source-kind, but ordering
/// within a source is maintained.
///
/// Example, say we had servers SA1 and SA2 set for DHCPv6 interface A, and SB1 and SB2
/// for DHCPv6 interface B. The consolidated list will be either one of [SA1, SA2, SB1, SB2]
/// or [SB1, SB2, SA1, SA2]. Notice how the ordering across sources (DHCPv6 interface A vs
/// DHCPv6 interface B) is not fixed, but the ordering within the source ([SA1, SA2] for DHCPv6
/// interface A and [SB1, SB2] for DHCPv6 interface B) is maintained.
///
/// Note, if multiple `DnsServer_`s have the same address but different sources, only
/// the `DnsServer_` with the most preferred source will be present in the consolidated
/// list of servers.
// TODO(https://fxbug.dev/42133571): Consider ordering across sources of the
// same source-kind based on some metric.
pub fn consolidated_dns_servers(&self) -> Vec<DnsServer_> {
self.consolidate_filter_map(|x| Some(x))
}
/// Returns a consolidated list of servers, with the mapping function `f` applied.
///
/// See `consolidated` for details on ordering.
fn consolidate_filter_map<T, F: Fn(DnsServer_) -> Option<T>>(&self, f: F) -> Vec<T> {
let Self { default, netstack, dhcpv4, dhcpv6 } = self;
let mut servers = netstack
.iter()
.chain(dhcpv4.values().flatten())
.chain(dhcpv6.values().flatten())
.cloned()
.collect::<Vec<_>>();
// Sorting happens before deduplication to ensure that when multiple sources report the same
// address, the highest priority source wins.
//
// `sort_by` maintains the order of equal elements. This is required to maintain ordering
// within a source of DNS servers. `sort_unstable_by` may not preserve this ordering.
let () = servers.sort_by(Self::ordering);
// Default servers are considered to have the lowest priority so we append them to the end
// of the list of sorted dynamically learned servers.
let () = servers.extend(default.clone());
let mut addresses = HashSet::new();
let () = servers.retain(move |s| addresses.insert(s.address));
servers.into_iter().filter_map(f).collect()
}
/// Returns the ordering of [`DnsServer_`]s.
///
/// The ordering from most to least preferred is is DHCP, NDP, DHCPv6, then Static. Preference
/// is currently informed by a goal of keeping servers discovered via the same internet
/// protocol together and preferring network-supplied configurations over product-supplied
/// ones.
///
/// TODO(https://fxbug.dev/42125772): We currently prioritize IPv4 servers over IPv6 as our DHCPv6
/// client does not yet support stateful address assignment. This can result in situations
/// where we can discover v6 nameservers that can't be reached as we've not discovered a global
/// address.
///
/// An unspecified source will be treated as a static address.
fn ordering(a: &DnsServer_, b: &DnsServer_) -> Ordering {
let ordering = |source| match source {
Some(&DnsServerSource::Dhcp(DhcpDnsServerSource { source_interface: _, .. })) => 0,
Some(&DnsServerSource::Ndp(NdpDnsServerSource { source_interface: _, .. })) => 1,
Some(&DnsServerSource::Dhcpv6(Dhcpv6DnsServerSource {
source_interface: _, ..
})) => 2,
Some(&DnsServerSource::StaticSource(StaticDnsServerSource { .. })) | None => 3,
};
let a = ordering(a.source.as_ref());
let b = ordering(b.source.as_ref());
std::cmp::Ord::cmp(&a, &b)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_util::constants::*;
#[test]
fn deduplicate_within_source() {
// Simple deduplication and sorting of repeated `DnsServer_`.
let servers = DnsServers {
default: vec![ndp_server(), ndp_server()],
netstack: vec![ndp_server(), static_server(), ndp_server(), static_server()],
// `DHCPV4/6_SERVER2` would normally only come from an interface with ID
// `DHCPV4/6_SERVER2_INTERFACE_ID`, but we are just testing deduplication
// logic here.
dhcpv4: [
(DHCPV4_SERVER1_INTERFACE_ID, vec![dhcpv4_server1(), dhcpv4_server2()]),
(DHCPV4_SERVER2_INTERFACE_ID, vec![dhcpv4_server1(), dhcpv4_server2()]),
]
.into_iter()
.collect(),
dhcpv6: [
(DHCPV6_SERVER1_INTERFACE_ID, vec![dhcpv6_server1(), dhcpv6_server2()]),
(DHCPV6_SERVER2_INTERFACE_ID, vec![dhcpv6_server1(), dhcpv6_server2()]),
]
.into_iter()
.collect(),
};
// Ordering across (the DHCPv6) sources is not guaranteed, but both DHCPv6 sources
// have the same set of servers with the same order. With deduplication, we know
// we will only see one of the sources' servers.
assert_eq!(
servers.consolidated(),
vec![
DHCPV4_SOURCE_SOCKADDR1,
DHCPV4_SOURCE_SOCKADDR2,
NDP_SOURCE_SOCKADDR,
DHCPV6_SOURCE_SOCKADDR1,
DHCPV6_SOURCE_SOCKADDR2,
STATIC_SOURCE_SOCKADDR,
],
);
}
#[test]
fn default_low_prio() {
// Default servers should always have low priority, but if the same server
// is observed by a higher priority source, then use the higher source for
// ordering.
let servers = DnsServers {
default: vec![static_server(), ndp_server(), dhcpv4_server1(), dhcpv6_server1()],
netstack: vec![static_server()],
dhcpv4: [
(DHCPV4_SERVER1_INTERFACE_ID, vec![dhcpv4_server1()]),
(DHCPV4_SERVER2_INTERFACE_ID, vec![dhcpv4_server1()]),
]
.into_iter()
.collect(),
dhcpv6: [
(DHCPV6_SERVER1_INTERFACE_ID, vec![dhcpv6_server1()]),
(DHCPV6_SERVER2_INTERFACE_ID, vec![dhcpv6_server2()]),
]
.into_iter()
.collect(),
};
// No ordering is guaranteed across servers from different sources of the same
// source-kind.
let mut got = servers.consolidated();
let mut got = got.drain(..);
let want_dhcpv4 = [DHCPV4_SOURCE_SOCKADDR1];
assert_eq!(
HashSet::from_iter(got.by_ref().take(want_dhcpv4.len())),
HashSet::from(want_dhcpv4),
);
let want_dhcpv6 = [DHCPV6_SOURCE_SOCKADDR1, DHCPV6_SOURCE_SOCKADDR2];
assert_eq!(
HashSet::from_iter(got.by_ref().take(want_dhcpv6.len())),
HashSet::from(want_dhcpv6),
);
let want_rest = [STATIC_SOURCE_SOCKADDR, NDP_SOURCE_SOCKADDR];
assert_eq!(got.as_slice(), want_rest);
}
#[test]
fn deduplicate_across_sources() {
// Deduplication and sorting of same address across different sources.
// DHCPv6 is not as preferred as NDP so this should not be in the consolidated
// servers list.
let dhcpv6_with_ndp_address = || DnsServer_ {
address: Some(NDP_SOURCE_SOCKADDR),
source: Some(DnsServerSource::Dhcpv6(Dhcpv6DnsServerSource {
source_interface: Some(DHCPV6_SERVER1_INTERFACE_ID),
..Default::default()
})),
..Default::default()
};
let mut dhcpv6 = HashMap::new();
assert_matches::assert_matches!(
dhcpv6.insert(
DHCPV6_SERVER1_INTERFACE_ID,
vec![dhcpv6_with_ndp_address(), dhcpv6_server1()]
),
None
);
let mut servers = DnsServers {
default: vec![],
netstack: vec![
dhcpv6_with_ndp_address(),
dhcpv4_server1(),
ndp_server(),
static_server(),
],
dhcpv4: [(DHCPV4_SERVER1_INTERFACE_ID, vec![dhcpv4_server1()])].into_iter().collect(),
dhcpv6: [(
DHCPV6_SERVER1_INTERFACE_ID,
vec![dhcpv6_with_ndp_address(), dhcpv6_server1()],
)]
.into_iter()
.collect(),
};
let expected_servers =
vec![dhcpv4_server1(), ndp_server(), dhcpv6_server1(), static_server()];
assert_eq!(servers.consolidate_filter_map(Some), expected_servers);
let expected_sockaddrs = vec![
DHCPV4_SOURCE_SOCKADDR1,
NDP_SOURCE_SOCKADDR,
DHCPV6_SOURCE_SOCKADDR1,
STATIC_SOURCE_SOCKADDR,
];
assert_eq!(servers.consolidated(), expected_sockaddrs);
servers.netstack =
vec![dhcpv4_server1(), ndp_server(), static_server(), dhcpv6_with_ndp_address()];
assert_eq!(servers.consolidate_filter_map(Some), expected_servers);
assert_eq!(servers.consolidated(), expected_sockaddrs);
// NDP is more preferred than DHCPv6 so `dhcpv6_server1()` should not be in the
// consolidated list of servers.
let ndp_with_dhcpv6_sockaddr1 = || DnsServer_ {
address: Some(DHCPV6_SOURCE_SOCKADDR1),
source: Some(DnsServerSource::Ndp(NdpDnsServerSource {
source_interface: Some(NDP_SERVER_INTERFACE_ID),
..Default::default()
})),
..Default::default()
};
let mut dhcpv6 = HashMap::new();
assert_matches::assert_matches!(
dhcpv6.insert(DHCPV6_SERVER1_INTERFACE_ID, vec![dhcpv6_server1()]),
None
);
assert_matches::assert_matches!(
dhcpv6.insert(DHCPV6_SERVER2_INTERFACE_ID, vec![dhcpv6_server2()]),
None
);
let mut servers = DnsServers {
default: vec![],
netstack: vec![ndp_with_dhcpv6_sockaddr1(), static_server()],
dhcpv4: Default::default(),
dhcpv6,
};
let expected_servers = vec![ndp_with_dhcpv6_sockaddr1(), dhcpv6_server2(), static_server()];
assert_eq!(servers.consolidate_filter_map(Some), expected_servers);
let expected_sockaddrs =
vec![DHCPV6_SOURCE_SOCKADDR1, DHCPV6_SOURCE_SOCKADDR2, STATIC_SOURCE_SOCKADDR];
assert_eq!(servers.consolidated(), expected_sockaddrs);
servers.netstack = vec![static_server(), ndp_with_dhcpv6_sockaddr1()];
assert_eq!(servers.consolidate_filter_map(Some), expected_servers);
assert_eq!(servers.consolidated(), expected_sockaddrs);
}
#[test]
fn test_dns_servers_ordering() {
assert_eq!(DnsServers::ordering(&ndp_server(), &ndp_server()), Ordering::Equal);
assert_eq!(DnsServers::ordering(&dhcpv4_server1(), &dhcpv4_server1()), Ordering::Equal);
assert_eq!(DnsServers::ordering(&dhcpv6_server1(), &dhcpv6_server1()), Ordering::Equal);
assert_eq!(DnsServers::ordering(&static_server(), &static_server()), Ordering::Equal);
assert_eq!(
DnsServers::ordering(&unspecified_source_server(), &unspecified_source_server()),
Ordering::Equal
);
assert_eq!(
DnsServers::ordering(&static_server(), &unspecified_source_server()),
Ordering::Equal
);
let servers = [
dhcpv4_server1(),
ndp_server(),
dhcpv6_server1(),
static_server(),
unspecified_source_server(),
];
// We don't compare the last two servers in the list because their ordering is equal
// w.r.t. eachother.
for (i, a) in servers[..servers.len() - 2].iter().enumerate() {
for b in servers[i + 1..].iter() {
assert_eq!(DnsServers::ordering(a, b), Ordering::Less);
}
}
let mut servers = vec![dhcpv6_server1(), dhcpv4_server1(), static_server(), ndp_server()];
servers.sort_by(DnsServers::ordering);
assert_eq!(
servers,
vec![dhcpv4_server1(), ndp_server(), dhcpv6_server1(), static_server()]
);
}
}