netcfg/
dns.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
5use std::collections::HashMap;
6use std::pin::Pin;
7
8use {
9    fidl_fuchsia_net as fnet, fidl_fuchsia_net_name as fnet_name, fidl_fuchsia_net_ndp as fnet_ndp,
10    fidl_fuchsia_net_ndp_ext as fnet_ndp_ext,
11};
12
13use anyhow::Context;
14use async_utils::stream::{Tagged, WithTag as _};
15use dns_server_watcher::{DnsServers, DnsServersUpdateSource};
16use fidl::endpoints::{ControlHandle as _, Responder as _};
17use futures::stream::BoxStream;
18use futures::{Stream, StreamExt};
19use log::{error, info, trace, warn};
20use net_types::{Scope, ScopeableAddress};
21use packet_formats::icmp::ndp as packet_formats_ndp;
22
23const DNS_PORT: u16 = 53;
24
25/// Updates the DNS servers used by the DNS resolver.
26pub(super) async fn update_servers(
27    lookup_admin: &fnet_name::LookupAdminProxy,
28    dns_servers: &mut DnsServers,
29    dns_server_watch_responders: &mut DnsServerWatchResponders,
30    source: DnsServersUpdateSource,
31    servers: Vec<fnet_name::DnsServer_>,
32) {
33    trace!("updating DNS servers obtained from {:?} to {:?}", source, servers);
34
35    let servers_before = dns_servers.consolidated();
36    dns_servers.set_servers_from_source(source, servers);
37    let servers = dns_servers.consolidated();
38    if servers_before == servers {
39        trace!("Update skipped because dns server list has not changed");
40        return;
41    }
42    trace!("updating LookupAdmin with DNS servers = {:?}", servers);
43
44    match lookup_admin.set_dns_servers(&servers).await {
45        Ok(Ok(())) => {}
46        Ok(Err(e)) => warn!("error setting DNS servers: {:?}", zx::Status::from_raw(e)),
47        Err(e) => warn!("error sending set DNS servers request: {:?}", e),
48    }
49
50    dns_server_watch_responders.send(dns_servers.consolidated_dns_servers());
51}
52
53/// Creates a stream of RDNSS DNS updates to conform to the output of
54/// `dns_server_watcher::new_dns_server_stream`. Returns None if the protocol
55/// is not available on the system, indicating the protocol should not be used.
56pub(super) async fn create_rdnss_stream(
57    watcher_provider: &fnet_ndp::RouterAdvertisementOptionWatcherProviderProxy,
58    source: DnsServersUpdateSource,
59    interface_id: u64,
60) -> Option<
61    Result<
62        impl Stream<Item = (DnsServersUpdateSource, Result<Vec<fnet_name::DnsServer_>, fidl::Error>)>,
63        fidl::Error,
64    >,
65> {
66    let watcher_result = fnet_ndp_ext::create_watcher_stream(
67        &watcher_provider,
68        &fnet_ndp::RouterAdvertisementOptionWatcherParams {
69            interest_types: Some(vec![
70                packet_formats_ndp::options::NdpOptionType::RecursiveDnsServer.into(),
71            ]),
72            interest_interface_id: Some(interface_id),
73            ..Default::default()
74        },
75    )
76    .await?;
77
78    // This cannot be directly returned using `?` operator since the
79    // function returns an Option.
80    let watcher = match watcher_result {
81        Ok(res) => res,
82        Err(e) => return Some(Err(e)),
83    };
84
85    Some(Ok(watcher
86        .filter_map(move |entry_res| async move {
87            let entry = match entry_res {
88                Ok(entry) => entry,
89                Err(fnet_ndp_ext::OptionWatchStreamError::Fidl(e)) => {
90                    return Some(Err(e));
91                }
92                Err(fnet_ndp_ext::OptionWatchStreamError::Conversion(e)) => {
93                    // Netstack didn't uphold the invariant to populate the
94                    // fields for `OptionWatchEntry`.
95                    error!("Failed to convert OptionWatchStream item: {e:?}");
96                    return None;
97                }
98            };
99            match entry {
100                fnet_ndp_ext::OptionWatchStreamItem::Entry(entry) => {
101                    match entry.try_parse_as_rdnss() {
102                        fnet_ndp_ext::TryParseAsOptionResult::Parsed(option) => Some(Ok(option
103                            .iter_addresses()
104                            .into_iter()
105                            .map(|addr| fnet_name::DnsServer_ {
106                                address: Some(fnet::SocketAddress::Ipv6(fnet::Ipv6SocketAddress {
107                                    address: fnet::Ipv6Address { addr: addr.ipv6_bytes() },
108                                    port: DNS_PORT,
109                                    // Determine whether the address has a zone or not in accordance
110                                    // with https://datatracker.ietf.org/doc/html/rfc8106
111                                    zone_index: addr
112                                        .scope()
113                                        .can_have_zone()
114                                        .then_some(interface_id)
115                                        .unwrap_or_default(),
116                                })),
117                                source: Some(fnet_name::DnsServerSource::Ndp(
118                                    fnet_name::NdpDnsServerSource {
119                                        source_interface: Some(interface_id),
120                                        ..Default::default()
121                                    },
122                                )),
123                                ..Default::default()
124                            })
125                            .collect::<Vec<_>>())),
126                        fnet_ndp_ext::TryParseAsOptionResult::OptionTypeMismatch => {
127                            // Netstack didn't respect our interest configuration.
128                            error!("Option type provided did not match RDNSS option type");
129                            None
130                        }
131                        fnet_ndp_ext::TryParseAsOptionResult::ParseErr(err) => {
132                            // A network peer could have included an invalid RDNSS option.
133                            warn!("Error while parsing as OptionResult: {err:?}");
134                            None
135                        }
136                    }
137                }
138                fnet_ndp_ext::OptionWatchStreamItem::Dropped(num) => {
139                    warn!(
140                        "The server dropped ({num}) NDP options \
141                    due to the HangingGet falling behind"
142                    );
143                    None
144                }
145            }
146        })
147        .tagged(source)))
148}
149
150pub(super) async fn add_rdnss_watcher(
151    watcher_provider: &fnet_ndp::RouterAdvertisementOptionWatcherProviderProxy,
152    interface_id: crate::InterfaceId,
153    watchers: &mut crate::DnsServerWatchers<'_>,
154) -> Result<(), anyhow::Error> {
155    let source = DnsServersUpdateSource::Ndp { interface_id: interface_id.get() };
156
157    // Returns None when RouterAdvertisementOptionWatcherProvider isn't available on the system.
158    let stream = create_rdnss_stream(watcher_provider, source, interface_id.get()).await;
159
160    match stream {
161        Some(result) => {
162            if let Some(o) =
163                watchers.insert(source, result.context("failed to create watcher stream")?.boxed())
164            {
165                let _: Pin<Box<BoxStream<'_, _>>> = o;
166                unreachable!("DNS server watchers must not contain key {:?}", source);
167            }
168            info!("started NDP watcher on host interface (id={interface_id})");
169        }
170        None => {
171            info!(
172                "NDP protocol unavailable: not starting watcher for interface (id={interface_id})"
173            );
174        }
175    }
176    Ok(())
177}
178
179pub(super) async fn remove_rdnss_watcher(
180    lookup_admin: &fnet_name::LookupAdminProxy,
181    dns_servers: &mut DnsServers,
182    dns_server_watch_responders: &mut DnsServerWatchResponders,
183    interface_id: crate::InterfaceId,
184    watchers: &mut crate::DnsServerWatchers<'_>,
185) {
186    let source = DnsServersUpdateSource::Ndp { interface_id: interface_id.get() };
187
188    if let None = watchers.remove(&source) {
189        // It's surprising that the DNS Watcher for the interface doesn't exist
190        // when the RDNSS stream is getting removed, but this can happen
191        // when multiple futures try to stop the NDP watcher at the same time.
192        warn!(
193            "DNS Watcher for key not present; multiple futures stopped NDP \
194            watcher for key {:?}; interface_id={}",
195            source, interface_id
196        );
197    }
198
199    update_servers(lookup_admin, dns_servers, dns_server_watch_responders, source, vec![]).await
200}
201
202#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
203pub(crate) struct ConnectionId(usize);
204
205#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
206pub(crate) struct UpdateGeneration(usize);
207
208/// Tracks the currently registered `fnet_name::DnsServerWatcherWatchServersResponder`s.
209///
210/// Keeps track of which connection ID has been notified for which generation of
211/// the DNS server list.
212#[derive(Default)]
213pub(crate) struct DnsServerWatchResponders {
214    /// The current generation. It gets incremented every time the `responders`
215    /// list gets emptied by a call to `DnsServerWatchResponders::take`
216    generation: UpdateGeneration,
217
218    /// Tracks the last generation for which a DNS server list update has been sent to each client.
219    generations: HashMap<ConnectionId, UpdateGeneration>,
220
221    /// The list of registered responders, indexed by their associated client ID.
222    responders: HashMap<ConnectionId, fnet_name::DnsServerWatcherWatchServersResponder>,
223}
224
225impl DnsServerWatchResponders {
226    fn send(&mut self, next_servers: Vec<fnet_name::DnsServer_>) {
227        let responders = std::mem::take(&mut self.responders);
228        self.generation.0 += 1;
229        for (id, responder) in responders {
230            match responder.send(&next_servers) {
231                Ok(()) => {
232                    let _: Option<UpdateGeneration> = self.generations.insert(id, self.generation);
233                }
234                Err(e) => warn!("Error responding to DnsServerWatcher request: {e:?}"),
235            }
236        }
237    }
238
239    /// Handles a call to `fuchsia.net.name/DnsServerWatcher.WatchServers`, the
240    /// responder may be called immediately, or stored for later.
241    pub(crate) fn handle_request(
242        &mut self,
243        id: ConnectionId,
244        request: Result<fnet_name::DnsServerWatcherRequest, fidl::Error>,
245        servers: &DnsServers,
246    ) -> Result<(), fidl::Error> {
247        use std::collections::hash_map::Entry;
248        match request {
249            Ok(fnet_name::DnsServerWatcherRequest::WatchServers { responder }) => {
250                match self.responders.entry(id) {
251                    Entry::Occupied(_) => {
252                        warn!(
253                            "Only one call to fuchsia.net.name/DnsServerWatcher.WatchServers \
254                            may be active at once"
255                        );
256                        responder.control_handle().shutdown()
257                    }
258                    Entry::Vacant(vacant_entry) => {
259                        // None is always less than any Some.
260                        // See: https://doc.rust-lang.org/std/option/index.html#comparison-operators
261                        if self.generations.get(&id) < Some(&self.generation) {
262                            let _: Option<_> = self.generations.insert(id, self.generation);
263                            responder.send(&servers.consolidated_dns_servers())?;
264                        } else {
265                            let _: &fnet_name::DnsServerWatcherWatchServersResponder =
266                                vacant_entry.insert(responder);
267                        }
268                    }
269                }
270            }
271            Err(e) => {
272                error!("fuchsia.net.name/DnsServerWatcher request error: {:?}", e)
273            }
274        }
275
276        Ok(())
277    }
278}
279
280/// Keep track of all of the connected clients of
281/// `fuchsia.net.name/DnsServerWatcher` and assign each of them a unique ID.
282#[derive(Default)]
283pub(crate) struct DnsServerWatcherRequestStreams {
284    /// The ID to be assigned to the next connection.
285    next_id: ConnectionId,
286
287    /// The currently connected clients.
288    request_streams:
289        futures::stream::SelectAll<Tagged<ConnectionId, fnet_name::DnsServerWatcherRequestStream>>,
290}
291
292impl DnsServerWatcherRequestStreams {
293    pub fn handle_request_stream(&mut self, req_stream: fnet_name::DnsServerWatcherRequestStream) {
294        self.request_streams.push(req_stream.tagged(self.next_id));
295        self.next_id.0 += 1;
296    }
297}
298
299impl futures::Stream for DnsServerWatcherRequestStreams {
300    type Item = (ConnectionId, Result<fnet_name::DnsServerWatcherRequest, fidl::Error>);
301
302    fn poll_next(
303        mut self: std::pin::Pin<&mut Self>,
304        cx: &mut std::task::Context<'_>,
305    ) -> std::task::Poll<Option<Self::Item>> {
306        std::pin::Pin::new(&mut self.request_streams).poll_next(cx)
307    }
308}
309
310impl futures::stream::FusedStream for DnsServerWatcherRequestStreams {
311    fn is_terminated(&self) -> bool {
312        self.request_streams.is_terminated()
313    }
314}
315
316#[cfg(test)]
317mod tests {
318    use anyhow::{anyhow, Context as _};
319    use fuchsia_component::server::{ServiceFs, ServiceFsDir};
320    use fuchsia_component_test::{
321        Capability, ChildOptions, LocalComponentHandles, RealmBuilder, RealmInstance, Ref, Route,
322    };
323    use futures::channel::mpsc;
324    use futures::{
325        FutureExt as _, SinkExt as _, StreamExt as _, TryFutureExt as _, TryStreamExt as _,
326    };
327    use net_declare::fidl_socket_addr;
328    use pretty_assertions::assert_eq;
329
330    use super::*;
331
332    enum StubbedServices {
333        LookupAdmin(fnet_name::LookupAdminRequestStream),
334    }
335
336    async fn run_lookup_admin(handles: LocalComponentHandles) -> Result<(), anyhow::Error> {
337        let mut fs = ServiceFs::new();
338        let _: &mut ServiceFsDir<'_, _> =
339            fs.dir("svc").add_fidl_service(StubbedServices::LookupAdmin);
340        let _: &mut ServiceFs<_> = fs.serve_connection(handles.outgoing_dir)?;
341
342        fs.for_each_concurrent(0, move |StubbedServices::LookupAdmin(stream)| async move {
343            stream
344                .try_for_each(|request| async move {
345                    match request {
346                        fidl_fuchsia_net_name::LookupAdminRequest::SetDnsServers { .. } => {
347                            // Silently ignore this request.
348                        }
349                        fidl_fuchsia_net_name::LookupAdminRequest::GetDnsServers { .. } => {
350                            unimplemented!("Unused in this test")
351                        }
352                    }
353                    Ok(())
354                })
355                .await
356                .context("Failed to serve request stream")
357                .unwrap_or_else(|e| warn!("Error encountered: {:?}", e))
358        })
359        .await;
360
361        Ok(())
362    }
363
364    enum IncomingService {
365        DnsServerWatcher(fnet_name::DnsServerWatcherRequestStream),
366    }
367
368    async fn run_dns_server_watcher(
369        handles: LocalComponentHandles,
370        mut receiver: mpsc::Receiver<(crate::DnsServersUpdateSource, Vec<fnet_name::DnsServer_>)>,
371    ) -> Result<(), anyhow::Error> {
372        let connection = handles.connect_to_protocol::<fnet_name::LookupAdminMarker>()?;
373
374        let mut fs = ServiceFs::new();
375        let _: &mut ServiceFsDir<'_, _> =
376            fs.dir("svc").add_fidl_service(IncomingService::DnsServerWatcher);
377        let _: &mut ServiceFs<_> = fs.serve_connection(handles.outgoing_dir)?;
378
379        let mut dns_server_watcher_incoming_requests = DnsServerWatcherRequestStreams::default();
380        let mut dns_servers = DnsServers::default();
381        let mut dns_server_watch_responders = DnsServerWatchResponders::default();
382
383        let mut fs = futures::StreamExt::fuse(fs);
384
385        loop {
386            futures::select! {
387                req_stream = fs.select_next_some() => {
388                    match req_stream {
389                        IncomingService::DnsServerWatcher(stream) => {
390                            dns_server_watcher_incoming_requests.handle_request_stream(stream)
391                        }
392                    }
393                }
394                req = dns_server_watcher_incoming_requests.select_next_some() => {
395                    let (id, req) = req;
396                    dns_server_watch_responders.handle_request(
397                        id,
398                        req,
399                        &dns_servers,
400                    )?;
401                }
402                update = receiver.select_next_some() => {
403                    let (source, servers) = update;
404                    update_servers(
405                        &connection,
406                        &mut dns_servers,
407                        &mut dns_server_watch_responders,
408                        source,
409                        servers,
410                    ).await
411                }
412            }
413        }
414    }
415
416    async fn setup_test() -> Result<
417        (RealmInstance, mpsc::Sender<(crate::DnsServersUpdateSource, Vec<fnet_name::DnsServer_>)>),
418        anyhow::Error,
419    > {
420        let (tx, rx) = mpsc::channel(1);
421        let builder = RealmBuilder::new().await?;
422        let admin_server = builder
423            .add_local_child(
424                "lookup_admin",
425                move |handles: LocalComponentHandles| Box::pin(run_lookup_admin(handles)),
426                ChildOptions::new(),
427            )
428            .await?;
429
430        let dns_server_watcher = builder
431            .add_local_child(
432                "dns_server_watcher",
433                {
434                    let rx = std::sync::Mutex::new(Some(rx));
435                    move |handles: LocalComponentHandles| {
436                        Box::pin(run_dns_server_watcher(
437                            handles,
438                            rx.lock()
439                                .expect("lock poison")
440                                .take()
441                                .expect("Only one instance of run_dns_server_watcher should exist"),
442                        ))
443                    }
444                },
445                ChildOptions::new(),
446            )
447            .await?;
448
449        builder
450            .add_route(
451                Route::new()
452                    .capability(Capability::protocol::<fnet_name::DnsServerWatcherMarker>())
453                    .from(&dns_server_watcher)
454                    .to(Ref::parent()),
455            )
456            .await?;
457        builder
458            .add_route(
459                Route::new()
460                    .capability(Capability::protocol::<fnet_name::LookupAdminMarker>())
461                    .from(&admin_server)
462                    .to(&dns_server_watcher),
463            )
464            .await?;
465
466        let realm = builder.build().await?;
467
468        Ok((realm, tx))
469    }
470
471    fn server(address: fidl_fuchsia_net::SocketAddress) -> fnet_name::DnsServer_ {
472        fnet_name::DnsServer_ { address: Some(address), ..fnet_name::DnsServer_::default() }
473    }
474
475    #[fuchsia::test]
476    async fn test_dns_server_watcher() -> Result<(), anyhow::Error> {
477        let (realm, mut tx) = setup_test().await?;
478
479        let watcher1 = realm
480            .root
481            .connect_to_protocol_at_exposed_dir::<fnet_name::DnsServerWatcherMarker>()
482            .context("While connecting to DnsServerWatcher")?;
483        let watcher2 = realm
484            .root
485            .connect_to_protocol_at_exposed_dir::<fnet_name::DnsServerWatcherMarker>()
486            .context("While connecting to DnsServerWatcher")?;
487
488        assert_eq!(watcher1.watch_servers().await?, vec![]);
489        assert_eq!(watcher2.watch_servers().await?, vec![]);
490
491        // This next call to watch_servers() should hang, so we expect the on_timeout response.
492        let mut watcher1_call = watcher1.watch_servers().fuse();
493        futures::select! {
494            _ = watcher1_call => {
495                return Err(
496                    anyhow!("WatchServers should not respond here, there have been no updates")
497                );
498            },
499            _ = fuchsia_async::Timer::new(std::time::Duration::from_millis(100)).fuse() => {}
500        }
501
502        // Insert a server from the "Default" source (statically defined).
503        let (watch1, watch2, _) = futures::try_join!(
504            // This call to watch_servers should now resolve.
505            watcher1_call.map_err(|e| anyhow::Error::from(e)),
506            watcher2.watch_servers().map_err(|e| anyhow::Error::from(e)),
507            tx.send((
508                DnsServersUpdateSource::Default,
509                vec![server(fidl_socket_addr!("203.0.113.1:1"))],
510            ))
511            .map_err(|e| anyhow::Error::from(e)),
512        )?;
513        assert_eq!(watch1, vec![server(fidl_socket_addr!("203.0.113.1:1")),]);
514        assert_eq!(watch2, vec![server(fidl_socket_addr!("203.0.113.1:1")),]);
515
516        // Insert a server derived from DHCPv4 interface 1.
517        let (watch1, watch2, _) = futures::try_join!(
518            watcher1.watch_servers().map_err(|e| anyhow::Error::from(e)),
519            watcher2.watch_servers().map_err(|e| anyhow::Error::from(e)),
520            tx.send((
521                DnsServersUpdateSource::Dhcpv4 { interface_id: 1 },
522                vec![server(fidl_socket_addr!("203.0.113.1:2")),],
523            ))
524            .map_err(|e| anyhow::Error::from(e)),
525        )?;
526        // The DHCPv4 is expected to be first since the "Default" source is
527        // given the lowest priority.
528        let expectation = vec![
529            server(fidl_socket_addr!("203.0.113.1:2")),
530            server(fidl_socket_addr!("203.0.113.1:1")),
531        ];
532        assert_eq!(watch1, expectation);
533        assert_eq!(watch2, expectation);
534
535        // Insert a server derived from DHCPv6 interface 1. Also, only have watcher 1 do the watch.
536        let (watch1, _) = futures::try_join!(
537            watcher1.watch_servers().map_err(|e| anyhow::Error::from(e)),
538            tx.send((
539                DnsServersUpdateSource::Dhcpv6 { interface_id: 1 },
540                vec![server(fidl_socket_addr!("[2001:db8::]:1")),],
541            ))
542            .map_err(|e| anyhow::Error::from(e)),
543        )?;
544        // DHCPv4 is higher priority than DHCPv6, but Default is still the lowest.
545        let expectation = vec![
546            server(fidl_socket_addr!("203.0.113.1:2")),
547            server(fidl_socket_addr!("[2001:db8::]:1")),
548            server(fidl_socket_addr!("203.0.113.1:1")),
549        ];
550        assert_eq!(watch1, expectation);
551
552        // Update the default servers while no watcher is watching. This should
553        // increment the generation, meaning that both watchers should respond
554        // immediately upon request.
555        tx.send((
556            DnsServersUpdateSource::Default,
557            vec![fnet_name::DnsServer_ {
558                address: Some(fidl_socket_addr!("203.0.113.1:5")),
559                ..fnet_name::DnsServer_::default()
560            }],
561        ))
562        .await?;
563        let (watch1, watch2) = futures::try_join!(
564            watcher1.watch_servers().map_err(|e| anyhow::Error::from(e)),
565            watcher2.watch_servers().map_err(|e| anyhow::Error::from(e)),
566        )?;
567        // DHCPv4 is higher priority than DHCPv6, but Default is still the lowest.
568        let expectation = vec![
569            server(fidl_socket_addr!("203.0.113.1:2")),
570            server(fidl_socket_addr!("[2001:db8::]:1")),
571            server(fidl_socket_addr!("203.0.113.1:5")),
572        ];
573        assert_eq!(watch1, expectation);
574
575        // watcher2 has skipped the previous update and just received the most up-to-date.
576        assert_eq!(watch2, expectation);
577
578        Ok(())
579    }
580}