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()]
        );
    }
}