dns_server_watcher/
lib.rs

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