1use 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
25pub(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
53pub(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 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 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 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 error!("Option type provided did not match RDNSS option type");
129 None
130 }
131 fnet_ndp_ext::TryParseAsOptionResult::ParseErr(err) => {
132 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 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 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#[derive(Default)]
213pub(crate) struct DnsServerWatchResponders {
214 generation: UpdateGeneration,
217
218 generations: HashMap<ConnectionId, UpdateGeneration>,
220
221 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 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 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#[derive(Default)]
283pub(crate) struct DnsServerWatcherRequestStreams {
284 next_id: ConnectionId,
286
287 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 }
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 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 let (watch1, watch2, _) = futures::try_join!(
504 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 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 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 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 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 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 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 assert_eq!(watch2, expectation);
577
578 Ok(())
579 }
580}