Skip to main content

reachability_core/
dig.rs

1// Copyright 2023 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 crate::FIDL_TIMEOUT_ID;
6use anyhow::anyhow;
7use async_trait::async_trait;
8use fidl_fuchsia_net as fnet;
9use fidl_fuchsia_net_name as fnet_name;
10use futures::TryFutureExt;
11use log::warn;
12use named_timer::NamedTimeoutExt;
13
14const DNS_FIDL_TIMEOUT: zx::MonotonicDuration = zx::MonotonicDuration::from_seconds(90);
15
16async fn dig<F: Fn() -> anyhow::Result<fnet_name::LookupProxy>>(
17    name_lookup_connector: &F,
18    // TODO(https://fxbug.dev/42182624): DNS fetching should be done over the
19    // specified interface. The current implementation of
20    // fidl::fuchsia::net::name::LookupIp does not support this.
21    _interface_name: &str,
22    domain: &str,
23) -> anyhow::Result<crate::ResolvedIps> {
24    let name_lookup = name_lookup_connector()?;
25    let results = name_lookup
26        .lookup_ip(
27            domain,
28            &fnet_name::LookupIpOptions {
29                ipv4_lookup: Some(true),
30                ipv6_lookup: Some(true),
31                ..Default::default()
32            },
33        )
34        .map_err(|e: fidl::Error| anyhow!("lookup_ip call failed: {e:?}"))
35        .on_timeout_named(&FIDL_TIMEOUT_ID, DNS_FIDL_TIMEOUT, || {
36            Err(anyhow!("lookup_ip timed out after {} seconds", DNS_FIDL_TIMEOUT.into_seconds()))
37        })
38        .await
39        .map_err(|e: anyhow::Error| anyhow!("{e:?}"))?
40        .map_err(|e: fnet_name::LookupError| anyhow!("lookup failed: {e:?}"))?;
41
42    if let Some(addresses) = results.addresses {
43        let mut resolved = crate::ResolvedIps::default();
44        for address in addresses {
45            match address {
46                fnet::IpAddress::Ipv4(fnet::Ipv4Address { addr }) => resolved.v4.push(addr.into()),
47                fnet::IpAddress::Ipv6(fnet::Ipv6Address { addr }) => resolved.v6.push(addr.into()),
48            }
49        }
50        Ok(resolved)
51    } else {
52        Err(anyhow!("no ip addresses were resolved"))
53    }
54}
55
56#[async_trait]
57pub trait Dig {
58    async fn dig(&self, interface_name: &str, domain: &str) -> Option<crate::ResolvedIps>;
59}
60
61pub struct Digger<F>(F);
62
63impl Digger<()> {
64    pub fn new() -> Digger<impl Fn() -> anyhow::Result<fnet_name::LookupProxy>> {
65        Digger(|| Ok(fuchsia_component::client::connect_to_protocol::<fnet_name::LookupMarker>()?))
66    }
67}
68
69#[async_trait]
70impl<F: Fn() -> anyhow::Result<fnet_name::LookupProxy> + std::marker::Sync> Dig for Digger<F> {
71    async fn dig(&self, interface_name: &str, domain: &str) -> Option<crate::ResolvedIps> {
72        let r = dig(&self.0, interface_name, &domain).await;
73        match r {
74            Ok(ips) => Some(ips),
75            Err(e) => {
76                warn!("error while digging {domain}: {e:#}");
77                None
78            }
79        }
80    }
81}
82
83#[cfg(test)]
84mod test {
85    use super::*;
86    use assert_matches::assert_matches;
87    use fuchsia_async as fasync;
88    use fuchsia_sync::Mutex;
89    use futures::prelude::*;
90    use futures::task::Poll;
91    use net_declare::fidl_ip;
92    use std::pin::pin;
93    use std::sync::Arc;
94
95    const DNS_DOMAIN: &str = "www.gstatic.com";
96
97    #[fuchsia::test]
98    fn test_dns_lookup_valid_response() {
99        let mut exec = fasync::TestExecutor::new();
100        let server_stream = Arc::new(Mutex::new(None));
101        let stream_ref = server_stream.clone();
102
103        let digger = Digger(move || {
104            let (proxy, server) =
105                fidl::endpoints::create_proxy_and_stream::<fnet_name::LookupMarker>();
106            *stream_ref.lock() = Some(server);
107            Ok(proxy)
108        });
109        let dns_lookup_fut = digger.dig("", DNS_DOMAIN);
110        let mut dns_lookup_fut = pin!(dns_lookup_fut);
111        assert_matches!(exec.run_until_stalled(&mut dns_lookup_fut), Poll::Pending);
112
113        let mut locked = server_stream.lock();
114        let mut server_end_fut = locked.as_mut().unwrap().try_next();
115        let _ = assert_matches!(exec.run_until_stalled(&mut server_end_fut),
116            Poll::Ready(Ok(Some(fnet_name::LookupRequest::LookupIp { responder, hostname, .. }))) => {
117                if DNS_DOMAIN == hostname {
118                    responder.send(Ok(&fnet_name::LookupResult {
119                        addresses: Some(vec![fidl_ip!("1.2.3.1")]), ..Default::default()
120                    }))
121                } else {
122                    responder.send(Err(fnet_name::LookupError::NotFound))
123                }
124            }
125        );
126
127        assert_matches!(exec.run_until_stalled(&mut server_end_fut), Poll::Pending);
128        assert_matches!(exec.run_until_stalled(&mut dns_lookup_fut), Poll::Ready(Some(_)));
129    }
130
131    #[fuchsia::test]
132    fn test_dns_lookup_net_name_error() {
133        let mut exec = fasync::TestExecutor::new();
134
135        let server_stream = Arc::new(Mutex::new(None));
136        let stream_ref = server_stream.clone();
137
138        let digger = Digger(move || {
139            let (proxy, server) =
140                fidl::endpoints::create_proxy_and_stream::<fnet_name::LookupMarker>();
141            *stream_ref.lock() = Some(server);
142            Ok(proxy)
143        });
144        let dns_lookup_fut = digger.dig("", DNS_DOMAIN);
145        let mut dns_lookup_fut = pin!(dns_lookup_fut);
146        assert_matches!(exec.run_until_stalled(&mut dns_lookup_fut), Poll::Pending);
147
148        let mut locked = server_stream.lock();
149        let mut server_end_fut = locked.as_mut().unwrap().try_next();
150        let _ = assert_matches!(exec.run_until_stalled(&mut server_end_fut),
151            Poll::Ready(Ok(Some(fnet_name::LookupRequest::LookupIp { responder, .. }))) => {
152                // Send a not found error regardless of the hostname
153                responder.send(Err(fnet_name::LookupError::NotFound))
154            }
155        );
156
157        assert_matches!(exec.run_until_stalled(&mut server_end_fut), Poll::Pending);
158        assert_matches!(exec.run_until_stalled(&mut dns_lookup_fut), Poll::Ready(None));
159    }
160
161    #[fuchsia::test]
162    fn test_dns_lookup_timed_out() {
163        let mut exec = fasync::TestExecutor::new_with_fake_time();
164        exec.set_fake_time(fasync::MonotonicInstant::from_nanos(0));
165
166        let server_stream = Arc::new(Mutex::new(None));
167        let stream_ref = server_stream.clone();
168
169        let digger = Digger(move || {
170            let (proxy, server) =
171                fidl::endpoints::create_proxy_and_stream::<fnet_name::LookupMarker>();
172            *stream_ref.lock() = Some(server);
173            Ok(proxy)
174        });
175        let dns_lookup_fut = digger.dig("", DNS_DOMAIN);
176        let mut dns_lookup_fut = pin!(dns_lookup_fut);
177        assert_matches!(exec.run_until_stalled(&mut dns_lookup_fut), Poll::Pending);
178
179        let mut locked = server_stream.lock();
180        let mut server_end_fut = locked.as_mut().unwrap().try_next();
181        assert_matches!(exec.run_until_stalled(&mut server_end_fut), Poll::Ready { .. });
182
183        exec.set_fake_time(fasync::MonotonicInstant::after(DNS_FIDL_TIMEOUT));
184        assert!(exec.wake_expired_timers());
185
186        assert_matches!(exec.run_until_stalled(&mut dns_lookup_fut), Poll::Ready(None));
187    }
188
189    #[fuchsia::test]
190    fn test_dns_lookup_fidl_error() {
191        let mut exec = fasync::TestExecutor::new_with_fake_time();
192
193        let digger = Digger(move || {
194            let (proxy, _) = fidl::endpoints::create_proxy_and_stream::<fnet_name::LookupMarker>();
195            Ok(proxy)
196        });
197        let dns_lookup_fut = digger.dig("", DNS_DOMAIN);
198        let mut dns_lookup_fut = pin!(dns_lookup_fut);
199
200        assert_matches!(exec.run_until_stalled(&mut dns_lookup_fut), Poll::Ready(None));
201    }
202}