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