Skip to main content

dns_resolver/
main.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 anyhow::{Context as _, Error};
6use dns::async_resolver::{Resolver, Spawner};
7use dns::config::{ServerList, UpdateServersResult};
8use fidl_fuchsia_net as fnet;
9use fidl_fuchsia_net_ext as net_ext;
10use fidl_fuchsia_net_name::{
11    self as fname, LookupAdminRequest, LookupAdminRequestStream, LookupRequest, LookupRequestStream,
12};
13use fidl_fuchsia_net_routes as fnet_routes;
14use fuchsia_async as fasync;
15use fuchsia_component::server::{ServiceFs, ServiceFsDir};
16use fuchsia_sync::RwLock;
17use futures::channel::mpsc;
18use futures::lock::Mutex;
19use futures::{FutureExt as _, SinkExt as _, StreamExt as _, TryFutureExt as _, TryStreamExt as _};
20use log::{debug, error, info, warn};
21use net_declare::net_subnet_v6;
22use net_types::ip::IpAddress;
23use std::collections::{BTreeMap, HashMap, VecDeque};
24use std::convert::TryFrom as _;
25use std::hash::{Hash, Hasher};
26use std::net::IpAddr;
27use std::num::NonZeroUsize;
28use std::str::FromStr as _;
29use std::sync::Arc;
30use trust_dns_proto::error::ProtoErrorKind;
31use trust_dns_proto::op::ResponseCode;
32use trust_dns_proto::rr::domain::IntoName;
33use trust_dns_proto::rr::{RData, RecordType};
34use trust_dns_resolver::config::{
35    LookupIpStrategy, NameServerConfig, NameServerConfigGroup, Protocol, ResolverConfig,
36    ResolverOpts, ServerOrderingStrategy,
37};
38use trust_dns_resolver::error::{ResolveError, ResolveErrorKind};
39use trust_dns_resolver::{NameServerStats, lookup};
40use unicode_xid::UnicodeXID as _;
41
42#[derive(Debug, Clone)]
43/// A type wrapping the underlying resolver.
44///
45/// The outer `Arc` lets the whole thing be shared between threads. Although
46/// lookup and updating the config happens in a single thread, Inspect may call
47/// from other threads to get resolver information.
48///
49/// The lock is required so the server can be updated when new nameservers are
50/// configured.
51///
52/// The inner `Arc` means users of the nameserver only hold the lock long enough
53/// to clone the `Arc`, or, in the case of updating, long enough to swap out the
54/// resolver. Otherwise, lookups would have to hold the lock the whole time a
55/// query is running, which can be on the order of seconds.
56///
57/// NOTE: The lock is unnecessary since on most architectures you can atomically
58/// swap a pointer, but the lock leads to a simpler implementation and there
59/// isn't much contention here.
60struct SharedResolver<T>(Arc<RwLock<Arc<T>>>);
61
62impl<T> SharedResolver<T> {
63    fn new(resolver: T) -> Self {
64        SharedResolver(Arc::new(RwLock::new(Arc::new(resolver))))
65    }
66
67    fn read(&self) -> Arc<T> {
68        let Self(inner) = self;
69        inner.read().clone()
70    }
71
72    fn write(&self, other: Arc<T>) {
73        let Self(inner) = self;
74        *inner.write() = other;
75    }
76}
77
78const STAT_WINDOW_DURATION: zx::MonotonicDuration = zx::MonotonicDuration::from_seconds(60);
79const STAT_WINDOW_COUNT: usize = 30;
80const RETAINED_ERRORS_PER_NAME_SERVER: usize = 32;
81
82/// Stats about queries during the last `STAT_WINDOW_COUNT` windows of
83/// `STAT_WINDOW_DURATION` time.
84///
85/// For example, if `STAT_WINDOW_DURATION` == 1 minute, and
86/// `STAT_WINDOW_COUNT` == 30, `past_queries` contains information about, at
87/// most, 30 one-minute windows of completed queries.
88///
89/// NB: there is no guarantee that these windows are directly consecutive; only
90/// that each window begins at least `STAT_WINDOW_DURATION` after the previous
91/// window's start time.
92struct QueryStats {
93    inner: Mutex<VecDeque<QueryWindow>>,
94}
95
96/// Relevant info to be recorded about a completed query. The `Ok` variant
97/// contains the number of addresses in the response, and the `Err` variant
98/// contains the kind of error that was encountered.
99type QueryResult<'a> = Result<NonZeroUsize, &'a ResolveErrorKind>;
100
101impl QueryStats {
102    fn new() -> Self {
103        Self { inner: Mutex::new(VecDeque::new()) }
104    }
105
106    async fn finish_query(&self, start_time: fasync::MonotonicInstant, result: QueryResult<'_>) {
107        let end_time = fasync::MonotonicInstant::now();
108        let finish = move |window: &mut QueryWindow| {
109            let elapsed_time = end_time - start_time;
110            match result {
111                Ok(num_addrs) => window.succeed(elapsed_time, num_addrs),
112                Err(e) => window.fail(elapsed_time, e),
113            }
114        };
115
116        let Self { inner } = self;
117        let past_queries = &mut *inner.lock().await;
118
119        let current_window = past_queries.back_mut().and_then(|window| {
120            let QueryWindow { start, .. } = window;
121            (end_time - *start < STAT_WINDOW_DURATION).then_some(window)
122        });
123
124        match current_window {
125            Some(window) => finish(window),
126            None => {
127                if past_queries.len() == STAT_WINDOW_COUNT {
128                    // Remove the oldest window of query stats.
129                    let _: QueryWindow = past_queries
130                        .pop_front()
131                        .expect("there should be at least one element in `past_queries`");
132                }
133                let mut window = QueryWindow::new(end_time);
134                finish(&mut window);
135                past_queries.push_back(window);
136            }
137        }
138    }
139}
140
141#[derive(Debug)]
142struct HashableResponseCode {
143    response_code: ResponseCode,
144}
145
146impl Hash for HashableResponseCode {
147    fn hash<H: Hasher>(&self, state: &mut H) {
148        let HashableResponseCode { response_code } = self;
149        u16::from(*response_code).hash(state)
150    }
151}
152
153// Hand-implemented because of clippy's derive_hash_xor_eq lint.
154impl PartialEq for HashableResponseCode {
155    fn eq(&self, other: &Self) -> bool {
156        let HashableResponseCode { response_code } = self;
157        let HashableResponseCode { response_code: other } = other;
158        response_code.eq(other)
159    }
160}
161
162impl Eq for HashableResponseCode {}
163
164impl From<ResponseCode> for HashableResponseCode {
165    fn from(response_code: ResponseCode) -> Self {
166        HashableResponseCode { response_code }
167    }
168}
169
170#[derive(Default, Debug, PartialEq)]
171struct NoRecordsFoundStats {
172    response_code_counts: HashMap<HashableResponseCode, u64>,
173}
174
175impl NoRecordsFoundStats {
176    fn increment(&mut self, response_code: &ResponseCode) {
177        let NoRecordsFoundStats { response_code_counts } = self;
178        let count = response_code_counts.entry((*response_code).into()).or_insert(0);
179        *count += 1
180    }
181}
182
183/// A type that handles counting the number of occurrences of the name of an
184/// enum variant without any of the contents of that enum. See
185/// [`enum_variant_string`] for more information on that process.
186///
187/// This is for privacy purposes; in some cases it's not possible to know a
188/// priori whether a particular enum variant includes user data such as
189/// hostnames.
190#[derive(Default, Debug, PartialEq)]
191struct GenericErrorKindStats(HashMap<String, u64>);
192
193impl GenericErrorKindStats {
194    /// Increments the counter for the given error kind. Returns a string
195    /// containing the name of that kind so the caller doesn't have to perform
196    /// the processing if they want to log it.
197    fn increment(&mut self, error_kind: &impl std::fmt::Debug) -> String {
198        let Self(counts) = self;
199        let truncated_debug = enum_variant_string(error_kind);
200        let count = counts.entry(truncated_debug.clone()).or_insert(0);
201        *count += 1;
202
203        truncated_debug
204    }
205}
206
207#[derive(Default, Debug, PartialEq)]
208struct IoErrorStats(HashMap<std::io::ErrorKind, u64>);
209
210impl IoErrorStats {
211    /// Increments the counter for the given IO error kind.
212    fn increment(&mut self, error_kind: std::io::ErrorKind) {
213        let Self(counts) = self;
214        *counts.entry(error_kind).or_insert(0) += 1;
215    }
216}
217
218/// Stats about queries that failed due to an internal trust-dns error.
219/// These counters map to variants of
220/// [`trust_dns_resolver::error::ResolveErrorKind`].
221#[derive(Default, Debug, PartialEq)]
222struct FailureStats {
223    message: u64,
224    no_connections: u64,
225    no_records_found: NoRecordsFoundStats,
226    io: IoErrorStats,
227    proto: GenericErrorKindStats,
228    timeout: u64,
229    unhandled_resolve_error_kind: GenericErrorKindStats,
230}
231
232impl FailureStats {
233    fn increment(&mut self, kind: &ResolveErrorKind) {
234        let FailureStats {
235            message,
236            no_connections,
237            no_records_found,
238            io,
239            proto,
240            timeout,
241            unhandled_resolve_error_kind,
242        } = self;
243
244        match kind {
245            ResolveErrorKind::Message(error) => {
246                let _: &str = error;
247                *message += 1
248            }
249            ResolveErrorKind::Msg(error) => {
250                let _: &String = error;
251                *message += 1
252            }
253            ResolveErrorKind::NoConnections => *no_connections += 1,
254            ResolveErrorKind::NoRecordsFound {
255                query: _,
256                soa: _,
257                negative_ttl: _,
258                response_code,
259                trusted: _,
260            } => no_records_found.increment(response_code),
261            ResolveErrorKind::Io(error) => io.increment(error.kind()),
262
263            ResolveErrorKind::Proto(error) => match error.kind() {
264                ProtoErrorKind::Io(error) => io.increment(error.kind()),
265                _ => {
266                    let _ = proto.increment(error.kind());
267                }
268            },
269            ResolveErrorKind::Timeout => *timeout += 1,
270            // ResolveErrorKind is marked #[non_exhaustive] in trust-dns:
271            // https://github.com/hickory-dns/hickory-dns/blob/v0.21.0-alpha.1/crates/resolver/src/error.rs#L29
272            // So we have to include a wildcard match.
273            // TODO(https://github.com/rust-lang/rust/issues/89554): remove once
274            // we're able to apply the non_exhaustive_omitted_patterns lint
275            kind => {
276                let variant = unhandled_resolve_error_kind.increment(kind);
277                error!("unhandled variant: {variant}");
278            }
279        }
280    }
281
282    fn populate_inspect_node(&self, node: &fuchsia_inspect::Node) {
283        let FailureStats {
284            message,
285            no_connections,
286            no_records_found: NoRecordsFoundStats { response_code_counts },
287            io: IoErrorStats(io),
288            proto: GenericErrorKindStats(proto),
289            timeout,
290            unhandled_resolve_error_kind: GenericErrorKindStats(unhandled_resolve_error_kind),
291        } = self;
292
293        node.record_uint("Message", *message);
294        node.record_uint("NoConnections", *no_connections);
295        node.record_uint("Timeout", *timeout);
296
297        node.record_child("IoErrorCounts", |io_error_codes| {
298            for (kind, count) in io {
299                io_error_codes.record_child(format!("{kind:?}"), |child| {
300                    child.record_uint("count", *count);
301                });
302            }
303        });
304
305        node.record_child("ProtoErrorCounts", |proto_error_codes| {
306            for (kind, count) in proto {
307                proto_error_codes.record_child(kind, |child| {
308                    child.record_uint("count", *count);
309                });
310            }
311        });
312
313        node.record_child("NoRecordsFoundResponseCodeCounts", |no_records_found_response_codes| {
314            for (HashableResponseCode { response_code }, count) in response_code_counts {
315                no_records_found_response_codes.record_child(
316                    format!("{:?}", response_code),
317                    |child| {
318                        child.record_uint("count", *count);
319                    },
320                );
321            }
322        });
323
324        node.record_child("UnhandledResolveErrorKindCounts", |unhandled_resolve_error_kinds| {
325            for (error_kind, count) in unhandled_resolve_error_kind {
326                unhandled_resolve_error_kinds.record_child(error_kind, |child| {
327                    child.record_uint("count", *count);
328                });
329            }
330        });
331    }
332}
333
334struct QueryWindow {
335    start: fasync::MonotonicInstant,
336    success_count: u64,
337    failure_count: u64,
338    success_elapsed_time: zx::MonotonicDuration,
339    failure_elapsed_time: zx::MonotonicDuration,
340    failure_stats: FailureStats,
341    address_counts_histogram: BTreeMap<NonZeroUsize, u64>,
342}
343
344impl QueryWindow {
345    fn new(start: fasync::MonotonicInstant) -> Self {
346        Self {
347            start,
348            success_count: 0,
349            failure_count: 0,
350            success_elapsed_time: zx::MonotonicDuration::from_nanos(0),
351            failure_elapsed_time: zx::MonotonicDuration::from_nanos(0),
352            failure_stats: FailureStats::default(),
353            address_counts_histogram: Default::default(),
354        }
355    }
356
357    fn succeed(&mut self, elapsed_time: zx::MonotonicDuration, num_addrs: NonZeroUsize) {
358        let QueryWindow {
359            success_count,
360            success_elapsed_time,
361            address_counts_histogram: address_counts,
362            start: _,
363            failure_count: _,
364            failure_elapsed_time: _,
365            failure_stats: _,
366        } = self;
367        *success_count += 1;
368        *success_elapsed_time += elapsed_time;
369        *address_counts.entry(num_addrs).or_default() += 1;
370    }
371
372    fn fail(&mut self, elapsed_time: zx::MonotonicDuration, error: &ResolveErrorKind) {
373        let QueryWindow {
374            failure_count,
375            failure_elapsed_time,
376            failure_stats,
377            start: _,
378            success_count: _,
379            success_elapsed_time: _,
380            address_counts_histogram: _,
381        } = self;
382        *failure_count += 1;
383        *failure_elapsed_time += elapsed_time;
384        failure_stats.increment(error)
385    }
386}
387
388/// Returns the name of the enum variant that was set.
389fn enum_variant_string(variant: &impl std::fmt::Debug) -> String {
390    let debug = format!("{:?}", variant);
391    // We just want to keep the part of the debug string that indicates
392    // which enum variant this is.
393    // See https://doc.rust-lang.org/reference/identifiers.html
394    match debug.find(|c: char| !c.is_xid_continue() && !c.is_xid_start()) {
395        Some(i) => debug[..i].to_string(),
396        None => debug,
397    }
398}
399
400fn update_resolver<T: ResolverLookup>(resolver: &SharedResolver<T>, servers: ServerList) {
401    let mut resolver_opts = ResolverOpts::default();
402    // TODO(https://fxbug.dev/42053483): Set ip_strategy once a unified lookup API
403    // exists that respects this setting.
404    resolver_opts.num_concurrent_reqs = 10;
405    // TODO(https://github.com/hickory-dns/hickory-dns/issues/1702): Use the
406    // default server ordering strategy once the algorithm is improved.
407    resolver_opts.server_ordering_strategy = ServerOrderingStrategy::UserProvidedOrder;
408
409    // We're going to add each server twice, once with protocol UDP and
410    // then with protocol TCP.
411    let mut name_servers = NameServerConfigGroup::with_capacity(servers.len() * 2);
412
413    name_servers.extend(servers.into_iter().flat_map(|server| {
414        let net_ext::SocketAddress(socket_addr) = server.into();
415        // Every server config gets UDP and TCP versions with
416        // preference for UDP.
417        std::iter::once(NameServerConfig {
418            socket_addr,
419            protocol: Protocol::Udp,
420            tls_dns_name: None,
421            trust_nx_responses: false,
422            bind_addr: None,
423            num_retained_errors: RETAINED_ERRORS_PER_NAME_SERVER,
424        })
425        .chain(std::iter::once(NameServerConfig {
426            socket_addr,
427            protocol: Protocol::Tcp,
428            tls_dns_name: None,
429            trust_nx_responses: false,
430            bind_addr: None,
431            num_retained_errors: RETAINED_ERRORS_PER_NAME_SERVER,
432        }))
433    }));
434
435    let new_resolver =
436        T::new(ResolverConfig::from_parts(None, Vec::new(), name_servers), resolver_opts);
437    resolver.write(Arc::new(new_resolver));
438}
439
440enum IncomingRequest {
441    Lookup(LookupRequestStream),
442    LookupAdmin(LookupAdminRequestStream),
443}
444
445trait ResolverLookup {
446    fn new(config: ResolverConfig, options: ResolverOpts) -> Self;
447
448    async fn lookup<N: IntoName + Send>(
449        &self,
450        name: N,
451        record_type: RecordType,
452    ) -> Result<lookup::Lookup, ResolveError>;
453
454    async fn reverse_lookup(&self, addr: IpAddr) -> Result<lookup::ReverseLookup, ResolveError>;
455}
456
457impl ResolverLookup for Resolver {
458    fn new(config: ResolverConfig, options: ResolverOpts) -> Self {
459        Resolver::new(config, options, Spawner).expect("failed to create resolver")
460    }
461
462    async fn lookup<N: IntoName + Send>(
463        &self,
464        name: N,
465        record_type: RecordType,
466    ) -> Result<lookup::Lookup, ResolveError> {
467        self.lookup(name, record_type).await
468    }
469
470    async fn reverse_lookup(&self, addr: IpAddr) -> Result<lookup::ReverseLookup, ResolveError> {
471        self.reverse_lookup(addr).await
472    }
473}
474
475trait NameServerStatsProvider {
476    fn name_server_stats(&self) -> Vec<trust_dns_resolver::NameServerStats>;
477}
478
479impl NameServerStatsProvider for Resolver {
480    fn name_server_stats(&self) -> Vec<trust_dns_resolver::NameServerStats> {
481        self.name_server_stats()
482    }
483}
484
485#[derive(Debug)]
486enum LookupIpErrorSource {
487    Ipv4,
488    Ipv6,
489    CanonicalName,
490}
491
492#[derive(Default)]
493struct LookupIpErrorsFromSource {
494    ipv4: Option<ResolveError>,
495    ipv6: Option<ResolveError>,
496    canonical_name: Option<ResolveError>,
497}
498
499impl LookupIpErrorsFromSource {
500    fn any_error(&self) -> Option<&ResolveError> {
501        let Self { ipv4, ipv6, canonical_name } = self;
502        ipv4.as_ref().or(ipv6.as_ref()).or(canonical_name.as_ref())
503    }
504
505    fn accumulate(&mut self, src: LookupIpErrorSource, error: ResolveError) {
506        let Self { ipv4, ipv6, canonical_name } = self;
507        let target = match src {
508            LookupIpErrorSource::Ipv4 => ipv4,
509            LookupIpErrorSource::Ipv6 => ipv6,
510            LookupIpErrorSource::CanonicalName => canonical_name,
511        };
512        debug_assert!(target.is_none(), "multiple errors observed for {src:?}");
513        *target = Some(error)
514    }
515
516    fn handle(self) -> fname::LookupError {
517        let Self { ipv4, ipv6, canonical_name } = self;
518        let mut ret = None;
519        for (src, err) in [
520            ("LookupIp(IPv4)", ipv4),
521            ("LookupIp(IPv6)", ipv6),
522            ("LookupIp(CanonicalName)", canonical_name),
523        ]
524        .into_iter()
525        .filter_map(|(src, err)| err.map(|e| (src, e)))
526        {
527            // We want to log all errors, but only convert one of them to the
528            // return. The fixed order IPv4, IPv6, CanonicalName is chosen to
529            // maximize the likelihood of the reported error being useful.
530            let err = handle_err(src, err);
531            if ret.is_none() {
532                ret = Some(err)
533            }
534        }
535        ret.unwrap_or(fname::LookupError::InternalError)
536    }
537}
538
539fn handle_err(source: &str, err: ResolveError) -> fname::LookupError {
540    use trust_dns_proto::error::ProtoErrorKind;
541
542    let (lookup_err, ioerr): (_, Option<(std::io::ErrorKind, _)>) = match err.kind() {
543        // The following mapping is based on the analysis of `ResolveError` enumerations.
544        // For cases that are not obvious such as `ResolveErrorKind::Msg` and
545        // `ResolveErrorKind::Message`, I (chunyingw) did code searches to have more insights.
546        // `ResolveErrorKind::Msg`: An error with arbitrary message, it could be ex. "lock was
547        // poisoned, this is non-recoverable" and ""DNS Error".
548        // `ResolveErrorKind::Message`: An error with arbitrary message, it is mostly returned when
549        // there is no name in the input vector to look up with "can not lookup for no names".
550        // This is a best-effort mapping.
551        ResolveErrorKind::NoRecordsFound {
552            query: _,
553            soa: _,
554            negative_ttl: _,
555            response_code: _,
556            trusted: _,
557        } => (fname::LookupError::NotFound, None),
558        ResolveErrorKind::Proto(err) => match err.kind() {
559            ProtoErrorKind::DomainNameTooLong(_) | ProtoErrorKind::EdnsNameNotRoot(_) => {
560                (fname::LookupError::InvalidArgs, None)
561            }
562            ProtoErrorKind::Busy | ProtoErrorKind::Canceled(_) | ProtoErrorKind::Timeout => {
563                (fname::LookupError::Transient, None)
564            }
565            ProtoErrorKind::Io(inner) => {
566                (fname::LookupError::Transient, Some((inner.kind(), inner.raw_os_error())))
567            }
568            ProtoErrorKind::BadQueryCount(_)
569            | ProtoErrorKind::CharacterDataTooLong { max: _, len: _ }
570            | ProtoErrorKind::LabelOverlapsWithOther { label: _, other: _ }
571            | ProtoErrorKind::DnsKeyProtocolNot3(_)
572            | ProtoErrorKind::FormError { header: _, error: _ }
573            | ProtoErrorKind::HmacInvalid()
574            | ProtoErrorKind::IncorrectRDataLengthRead { read: _, len: _ }
575            | ProtoErrorKind::LabelBytesTooLong(_)
576            | ProtoErrorKind::PointerNotPriorToLabel { idx: _, ptr: _ }
577            | ProtoErrorKind::MaxBufferSizeExceeded(_)
578            | ProtoErrorKind::Message(_)
579            | ProtoErrorKind::Msg(_)
580            | ProtoErrorKind::NoError
581            | ProtoErrorKind::NotAllRecordsWritten { count: _ }
582            | ProtoErrorKind::RrsigsNotPresent { name: _, record_type: _ }
583            | ProtoErrorKind::UnknownAlgorithmTypeValue(_)
584            | ProtoErrorKind::UnknownDnsClassStr(_)
585            | ProtoErrorKind::UnknownDnsClassValue(_)
586            | ProtoErrorKind::UnknownRecordTypeStr(_)
587            | ProtoErrorKind::UnknownRecordTypeValue(_)
588            | ProtoErrorKind::UnrecognizedLabelCode(_)
589            | ProtoErrorKind::UnrecognizedNsec3Flags(_)
590            | ProtoErrorKind::UnrecognizedCsyncFlags(_)
591            | ProtoErrorKind::Poisoned
592            | ProtoErrorKind::Ring(_)
593            | ProtoErrorKind::SSL(_)
594            | ProtoErrorKind::Timer
595            | ProtoErrorKind::UrlParsing(_)
596            | ProtoErrorKind::Utf8(_)
597            | ProtoErrorKind::FromUtf8(_)
598            | ProtoErrorKind::ParseInt(_) => (fname::LookupError::InternalError, None),
599            // ProtoErrorKind is marked #[non_exhaustive] in trust-dns:
600            // https://github.com/hickory-dns/hickory-dns/blob/v0.21.0-alpha.1/crates/proto/src/error.rs#L66
601            // So we have to include a wildcard match.
602            kind => {
603                error!("unhandled variant {:?}", enum_variant_string(kind));
604                (fname::LookupError::InternalError, None)
605            }
606        },
607        ResolveErrorKind::Io(inner) => {
608            (fname::LookupError::Transient, Some((inner.kind(), inner.raw_os_error())))
609        }
610        ResolveErrorKind::Timeout => (fname::LookupError::Transient, None),
611        ResolveErrorKind::Msg(_)
612        | ResolveErrorKind::Message(_)
613        | ResolveErrorKind::NoConnections => (fname::LookupError::InternalError, None),
614        // ResolveErrorKind is marked #[non_exhaustive] in trust-dns:
615        // https://github.com/hickory-dns/hickory-dns/blob/v0.21.0-alpha.1/crates/resolver/src/error.rs#L29
616        // So we have to include a wildcard match.
617        kind => {
618            error!("unhandled variant {:?}", enum_variant_string(kind));
619            (fname::LookupError::InternalError, None)
620        }
621    };
622
623    if let Some((ioerr, raw_os_error)) = ioerr {
624        match raw_os_error {
625            Some(libc::EHOSTUNREACH | libc::ENETUNREACH) => {
626                debug!("{} error: {:?}; (IO error {:?})", source, lookup_err, ioerr)
627            }
628            _ => warn!("{} error: {:?}; (IO error {:?})", source, lookup_err, ioerr),
629        }
630    } else {
631        warn!("{} error: {:?}", source, lookup_err);
632    }
633
634    lookup_err
635}
636
637async fn sort_preferred_addresses(
638    mut addrs: Vec<fnet::IpAddress>,
639    routes: &fnet_routes::StateProxy,
640) -> Result<Vec<fnet::IpAddress>, fname::LookupError> {
641    let mut addrs_info = futures::future::try_join_all(
642        addrs
643            // Drain addresses from addrs, but keep it alive so we don't need to
644            // reallocate.
645            .drain(..)
646            .map(|addr| async move {
647                let source_addr = match routes.resolve(&addr).await? {
648                    Ok(fnet_routes::Resolved::Direct(fnet_routes::Destination {
649                        source_address,
650                        ..
651                    }))
652                    | Ok(fnet_routes::Resolved::Gateway(fnet_routes::Destination {
653                        source_address,
654                        ..
655                    })) => source_address,
656                    // If resolving routes returns an error treat it as an
657                    // unreachable address.
658                    Err(e) => {
659                        debug!(
660                            "fuchsia.net.routes/State.resolve({}) failed {}",
661                            net_ext::IpAddress::from(addr),
662                            zx::Status::err_from_raw(e)
663                        );
664                        None
665                    }
666                };
667                Ok((addr, DasCmpInfo::from_addrs(&addr, source_addr.as_ref())))
668            }),
669    )
670    .await
671    .map_err(|e: fidl::Error| {
672        warn!("fuchsia.net.routes/State.resolve FIDL error {:?}", e);
673        fname::LookupError::InternalError
674    })?;
675
676    addrs_info.sort_by(|(_laddr, left), (_raddr, right)| left.cmp(right));
677    // Reinsert the addresses in order from addr_info.
678    addrs.extend(addrs_info.into_iter().map(|(addr, _)| addr));
679    Ok(addrs)
680}
681
682#[derive(Debug)]
683struct Policy {
684    prefix: net_types::ip::Subnet<net_types::ip::Ipv6Addr>,
685    precedence: usize,
686    label: usize,
687}
688
689macro_rules! decl_policy {
690    ($subnet:literal => $precedence:expr, $label:expr) => {
691        Policy { prefix: net_subnet_v6!($subnet), precedence: $precedence, label: $label }
692    };
693}
694
695/// Policy table is defined in RFC 6724, section 2.1
696///
697/// A more human-readable version:
698///
699///  Prefix        Precedence Label
700///  ::1/128               50     0
701///  ::/0                  40     1
702///  ::ffff:0:0/96         35     4
703///  2002::/16             30     2
704///  2001::/32              5     5
705///  fc00::/7               3    13
706///  ::/96                  1     3
707///  fec0::/10              1    11
708///  3ffe::/16              1    12
709///
710/// We willingly left out ::/96, fec0::/10, 3ffe::/16 since those prefix
711/// assignments are deprecated.
712///
713/// The table is sorted by prefix length so longest-prefix match can be easily
714/// achieved.
715const POLICY_TABLE: [Policy; 6] = [
716    decl_policy!("::1/128" => 50, 0),
717    decl_policy!("::ffff:0:0/96" => 35, 4),
718    decl_policy!("2001::/32" => 5, 5),
719    decl_policy!("2002::/16" => 30, 2),
720    decl_policy!("fc00::/7" => 3, 13),
721    decl_policy!("::/0" => 40, 1),
722];
723
724fn policy_lookup(addr: &net_types::ip::Ipv6Addr) -> &'static Policy {
725    POLICY_TABLE
726        .iter()
727        .find(|policy| policy.prefix.contains(addr))
728        .expect("policy table MUST contain the all addresses subnet")
729}
730
731/// Destination Address selection information.
732///
733/// `DasCmpInfo` provides an implementation of a subset of Destination Address
734/// Selection according to the sorting rules defined in [RFC 6724 Section 6].
735///
736/// TODO(https://fxbug.dev/42143905): Implement missing rules 3, 4, and 7.
737/// Rules 3, 4, and 7 are omitted for compatibility with the equivalent
738/// implementation in Fuchsia's libc.
739///
740/// `DasCmpInfo` provides an [`std::cmp::Ord`] implementation that will return
741/// preferred addresses as "lesser" values.
742///
743/// [RFC 6724 Section 6]: https://tools.ietf.org/html/rfc6724#section-6
744#[derive(Debug)]
745struct DasCmpInfo {
746    usable: bool,
747    matching_scope: bool,
748    matching_label: bool,
749    precedence: usize,
750    scope: net_types::ip::Ipv6Scope,
751    common_prefix_len: u8,
752}
753
754impl DasCmpInfo {
755    /// Helper function to convert a FIDL IP address into
756    /// [`net_types::ip::Ipv6Addr`], using a mapped IPv4 when that's the case.
757    fn convert_addr(fidl: &fnet::IpAddress) -> net_types::ip::Ipv6Addr {
758        match fidl {
759            fnet::IpAddress::Ipv4(fnet::Ipv4Address { addr }) => {
760                net_types::ip::Ipv6Addr::from(net_types::ip::Ipv4Addr::new(*addr))
761            }
762            fnet::IpAddress::Ipv6(fnet::Ipv6Address { addr }) => {
763                net_types::ip::Ipv6Addr::from_bytes(*addr)
764            }
765        }
766    }
767
768    fn from_addrs(dst_addr: &fnet::IpAddress, src_addr: Option<&fnet::IpAddress>) -> Self {
769        use net_types::ScopeableAddress;
770
771        let dst_addr = Self::convert_addr(dst_addr);
772        let Policy { prefix: _, precedence, label: dst_label } = policy_lookup(&dst_addr);
773        let (usable, matching_scope, matching_label, common_prefix_len) = match src_addr {
774            Some(src_addr) => {
775                let src_addr = Self::convert_addr(src_addr);
776                let Policy { prefix: _, precedence: _, label: src_label } =
777                    policy_lookup(&src_addr);
778                (
779                    true,
780                    dst_addr.scope() == src_addr.scope(),
781                    dst_label == src_label,
782                    dst_addr.common_prefix_len(&src_addr),
783                )
784            }
785            None => (false, false, false, 0),
786        };
787        DasCmpInfo {
788            usable,
789            matching_scope,
790            matching_label,
791            precedence: *precedence,
792            scope: dst_addr.scope(),
793            common_prefix_len,
794        }
795    }
796}
797
798impl std::cmp::Ord for DasCmpInfo {
799    // TODO(https://fxbug.dev/42143905): Implement missing rules 3, 4, and 7.
800    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
801        use std::cmp::Ordering;
802        let DasCmpInfo {
803            usable: self_usable,
804            matching_scope: self_matching_scope,
805            matching_label: self_matching_label,
806            precedence: self_precedence,
807            scope: self_scope,
808            common_prefix_len: self_common_prefix_len,
809        } = self;
810        let DasCmpInfo {
811            usable: other_usable,
812            matching_scope: other_matching_scope,
813            matching_label: other_matching_label,
814            precedence: other_precedence,
815            scope: other_scope,
816            common_prefix_len: other_common_prefix_len,
817        } = other;
818
819        fn prefer_true(left: bool, right: bool) -> Ordering {
820            match (left, right) {
821                (true, false) => Ordering::Less,
822                (false, true) => Ordering::Greater,
823                (false, false) | (true, true) => Ordering::Equal,
824            }
825        }
826
827        // Rule 1: Avoid unusable destinations.
828        prefer_true(*self_usable, *other_usable)
829            .then(
830                // Rule 2: Prefer matching scope.
831                prefer_true(*self_matching_scope, *other_matching_scope),
832            )
833            .then(
834                // Rule 5: Prefer matching label.
835                prefer_true(*self_matching_label, *other_matching_label),
836            )
837            .then(
838                // Rule 6: Prefer higher precedence.
839                self_precedence.cmp(other_precedence).reverse(),
840            )
841            .then(
842                // Rule 8: Prefer smaller scope.
843                self_scope.multicast_scope_id().cmp(&other_scope.multicast_scope_id()),
844            )
845            .then(
846                // Rule 9: Use longest matching prefix.
847                self_common_prefix_len.cmp(other_common_prefix_len).reverse(),
848            )
849        // Rule 10: Otherwise, leave the order unchanged.
850    }
851}
852
853impl std::cmp::PartialOrd for DasCmpInfo {
854    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
855        Some(self.cmp(other))
856    }
857}
858
859impl std::cmp::PartialEq for DasCmpInfo {
860    fn eq(&self, other: &Self) -> bool {
861        self.cmp(other) == std::cmp::Ordering::Equal
862    }
863}
864
865impl std::cmp::Eq for DasCmpInfo {}
866
867async fn handle_lookup_hostname<T: ResolverLookup>(
868    resolver: &SharedResolver<T>,
869    addr: fnet::IpAddress,
870) -> Result<String, fname::LookupError> {
871    let net_ext::IpAddress(addr) = addr.into();
872    let resolver = resolver.read();
873
874    match resolver.reverse_lookup(addr).await {
875        Ok(response) => {
876            response.iter().next().ok_or(fname::LookupError::NotFound).map(ToString::to_string)
877        }
878        Err(error) => Err(handle_err("LookupHostname", error)),
879    }
880}
881
882struct IpLookupRequest {
883    hostname: String,
884    options: fname::LookupIpOptions,
885    responder: fname::LookupLookupIpResponder,
886}
887
888async fn run_lookup<T: ResolverLookup>(
889    resolver: &SharedResolver<T>,
890    stream: LookupRequestStream,
891    sender: mpsc::Sender<IpLookupRequest>,
892) -> Result<(), fidl::Error> {
893    stream
894        .try_for_each_concurrent(None, |request| async {
895            match request {
896                LookupRequest::LookupIp { hostname, options, responder } => {
897                    sender
898                        .clone()
899                        .send(IpLookupRequest { hostname, options, responder })
900                        .await
901                        .expect("receiver should not be closed");
902                    Ok(())
903                }
904                LookupRequest::LookupHostname { addr, responder } => responder
905                    .send(handle_lookup_hostname(&resolver, addr).await.as_deref().map_err(|e| *e)),
906            }
907        })
908        .await
909}
910
911const MAX_PARALLEL_REQUESTS: usize = 256;
912
913fn create_ip_lookup_fut<T: ResolverLookup>(
914    resolver: &SharedResolver<T>,
915    stats: Arc<QueryStats>,
916    routes: fnet_routes::StateProxy,
917    recv: mpsc::Receiver<IpLookupRequest>,
918) -> impl futures::Future<Output = ()> + '_ {
919    recv.for_each_concurrent(
920        MAX_PARALLEL_REQUESTS,
921        move |IpLookupRequest { hostname, options, responder }| {
922            let stats = stats.clone();
923            let routes = routes.clone();
924            async move {
925                let fname::LookupIpOptions {
926                    ipv4_lookup,
927                    ipv6_lookup,
928                    sort_addresses,
929                    canonical_name_lookup,
930                    ..
931                } = options;
932                let ipv4_lookup = ipv4_lookup.unwrap_or(false);
933                let ipv6_lookup = ipv6_lookup.unwrap_or(false);
934                let sort_addresses = sort_addresses.unwrap_or(false);
935                let canonical_name_lookup = canonical_name_lookup.unwrap_or(false);
936                let lookup_result = (|| async {
937                    let hostname = hostname.as_str();
938                    // The [`IntoName`] implementation for &str does not
939                    // properly reject IPv4 addresses in accordance with RFC
940                    // 1123 section 2.1:
941                    //
942                    //   If a dotted-decimal number can be entered without such
943                    //   identifying delimiters, then a full syntactic check must be
944                    //   made, because a segment of a host domain name is now allowed
945                    //   to begin with a digit and could legally be entirely numeric
946                    //   (see Section 6.1.2.4).  However, a valid host name can never
947                    //   have the dotted-decimal form #.#.#.#, since at least the
948                    //   highest-level component label will be alphabetic.
949                    //
950                    // Thus we explicitly reject such input here.
951                    //
952                    // TODO(https://github.com/hickory-dns/hickory-dns/issues/1725):
953                    // Remove this when the implementation is sufficiently
954                    // strict.
955                    match IpAddr::from_str(hostname) {
956                        Ok(addr) => {
957                            let _: IpAddr = addr;
958                            return Err(fname::LookupError::InvalidArgs);
959                        }
960                        Err(std::net::AddrParseError { .. }) => {}
961                    };
962                    let resolver = resolver.read();
963                    let start_time = fasync::MonotonicInstant::now();
964                    let (ret1, ret2, ret3) = futures::future::join3(
965                        futures::future::OptionFuture::from(
966                            ipv4_lookup.then(|| {
967                                resolver
968                                    .lookup(hostname, RecordType::A)
969                                    .map_err(|e| (LookupIpErrorSource::Ipv4, e))
970                            }),
971                        ),
972                        futures::future::OptionFuture::from(
973                            ipv6_lookup.then(|| {
974                                resolver
975                                    .lookup(hostname, RecordType::AAAA)
976                                    .map_err(|e| (LookupIpErrorSource::Ipv6, e))
977                            }),
978                        ),
979                        futures::future::OptionFuture::from(
980                            canonical_name_lookup
981                                .then(|| {
982                                    resolver
983                                        .lookup(hostname, RecordType::CNAME)
984                                        .map_err(|e| (LookupIpErrorSource::CanonicalName, e))
985                                }),
986                        ),
987                    )
988                    .await;
989                    let result = [ret1, ret2, ret3];
990                    if result.iter().all(Option::is_none) {
991                        return Err(fname::LookupError::InvalidArgs);
992                    }
993                    let (addrs, cnames, error) =
994                        result.into_iter().filter_map(std::convert::identity).fold(
995                            (Vec::new(), Vec::new(), LookupIpErrorsFromSource::default()),
996                            |(mut addrs, mut cnames, mut error), result| {
997                                match result {
998                                    Err((src, err)) => {
999                                        error.accumulate(src, err);
1000                                    },
1001                                    Ok(lookup) => lookup.iter().for_each(|rdata| match rdata {
1002                                        RData::A(addr) if ipv4_lookup => addrs
1003                                            .push(net_ext::IpAddress(IpAddr::V4(*addr)).into()),
1004                                        RData::AAAA(addr) if ipv6_lookup => addrs
1005                                            .push(net_ext::IpAddress(IpAddr::V6(*addr)).into()),
1006                                        RData::CNAME(name) => {
1007                                            // CNAME records are known to be present with other
1008                                            // query types; avoid logging in that case.
1009                                            if canonical_name_lookup {
1010                                                cnames.push(name.to_utf8())
1011                                            }
1012                                        }
1013                                        rdata => {
1014                                            error!(
1015                                                "Lookup(_, {:?}) yielded unexpected record type: {}",
1016                                                options, rdata.to_record_type(),
1017                                            )
1018                                        }
1019                                    }),
1020                                };
1021                            (addrs, cnames, error)
1022                        });
1023                    let count = match NonZeroUsize::try_from(addrs.len() + cnames.len()) {
1024                        Ok(count) => Ok(count),
1025                        Err(std::num::TryFromIntError { .. }) => match error.any_error() {
1026                            None => {
1027                                // TODO(https://fxbug.dev/42062388): Remove this
1028                                // once Trust-DNS enforces that all responses
1029                                // with no records return a `NoRecordsFound`
1030                                // error.
1031                                //
1032                                // Note that returning here means that query
1033                                // stats for inspect will not get logged. This
1034                                // is ok since this case should be rare and is
1035                                // considered to be temporary. Moreover, the
1036                                // failed query counters are based on the
1037                                // `ResolverError::kind`, which isn't applicable
1038                                // here.
1039                                error!("resolver response unexpectedly contained no records \
1040                                        and no error. See https://fxbug.dev/42062388.");
1041                                return Err(fname::LookupError::NotFound);
1042                            },
1043                            Some(any_err) => {
1044                                Err(any_err)
1045                            }
1046                        }
1047                    };
1048                    stats
1049                        .finish_query(
1050                            start_time,
1051                            count.as_ref().copied().map_err(|e| e.kind()),
1052                        )
1053                        .await;
1054                    match count {
1055                        Ok(_) => {},
1056                        Err(_any_err) => {
1057                            // Handle all the errors instead of just the one used for stats.
1058                            return Err(error.handle());
1059                        }
1060                    }
1061                    let addrs = if sort_addresses {
1062                        sort_preferred_addresses(addrs, &routes).await?
1063                    } else {
1064                        addrs
1065                    };
1066                    let addrs = if addrs.len() > fname::MAX_ADDRESSES.into() {
1067                        warn!(
1068                            "Lookup(_, {:?}): {} addresses, truncating to {}",
1069                            options, addrs.len(), fname::MAX_ADDRESSES
1070                        );
1071                        let mut addrs = addrs;
1072                        addrs.truncate(fname::MAX_ADDRESSES.into());
1073                        addrs
1074                    } else {
1075                        addrs
1076                    };
1077                    // Per RFC 1034 section 3.6.2:
1078                    //
1079                    //   If a CNAME RR is present at a node, no other data should be present; this
1080                    //   ensures that the data for a canonical name and its aliases cannot be
1081                    //   different.  This rule also insures that a cached CNAME can be used without
1082                    //   checking with an authoritative server for other RR types.
1083                    if cnames.len() > 1 {
1084                        let cnames =
1085                            cnames.iter().fold(HashMap::<&str, usize>::new(), |mut acc, cname| {
1086                                *acc.entry(cname).or_default() += 1;
1087                                acc
1088                            });
1089                        warn!(
1090                            "Lookup(_, {:?}): multiple CNAMEs: {:?}",
1091                            options, cnames
1092                        )
1093                    }
1094                    let cname = {
1095                        let mut cnames = cnames;
1096                        cnames.pop()
1097                    };
1098                    Ok(fname::LookupResult {
1099                        addresses: Some(addrs),
1100                        canonical_name: cname,
1101                        ..Default::default()
1102                    })
1103                })()
1104                .await;
1105                responder.send(lookup_result.as_ref().map_err(|e| *e)).unwrap_or_else(|e|
1106                    warn!(
1107                        "failed to send IP lookup result due to FIDL error: {}",
1108                        e
1109                    )
1110                )
1111            }
1112        },
1113    )
1114}
1115
1116/// Serves `stream` and forwards received configurations to `sink`.
1117async fn run_lookup_admin<T: ResolverLookup>(
1118    resolver: &SharedResolver<T>,
1119    state: &dns::config::ServerConfigState,
1120    stream: LookupAdminRequestStream,
1121) -> Result<(), fidl::Error> {
1122    stream
1123        .try_for_each(|req| async {
1124            match req {
1125                LookupAdminRequest::SetDnsServers { servers, responder } => {
1126                    let response = match state.update_servers(servers) {
1127                        UpdateServersResult::Updated(servers) => {
1128                            update_resolver(resolver, servers);
1129                            Ok(())
1130                        }
1131                        UpdateServersResult::NoChange => Ok(()),
1132                        UpdateServersResult::InvalidsServers => {
1133                            Err(zx::Status::INVALID_ARGS.into_raw())
1134                        }
1135                    };
1136                    responder.send(response)?;
1137                }
1138                LookupAdminRequest::GetDnsServers { responder } => {
1139                    responder.send(&state.servers())?;
1140                }
1141            }
1142            Ok(())
1143        })
1144        .await
1145}
1146
1147/// Adds a [`dns::policy::ServerConfigState`] inspection child node to
1148/// `parent`.
1149fn add_config_state_inspect(
1150    parent: &fuchsia_inspect::Node,
1151    config_state: Arc<dns::config::ServerConfigState>,
1152) -> fuchsia_inspect::LazyNode {
1153    parent.create_lazy_child("servers", move || {
1154        let config_state = config_state.clone();
1155        async move {
1156            let srv = fuchsia_inspect::Inspector::default();
1157            let server_list = config_state.servers();
1158            for (i, server) in server_list.into_iter().enumerate() {
1159                srv.root().record_child(format!("{}", i), |child| {
1160                    let net_ext::SocketAddress(addr) = server.into();
1161                    child.record_string("address", format!("{}", addr));
1162                });
1163            }
1164            Ok(srv)
1165        }
1166        .boxed()
1167    })
1168}
1169
1170/// Adds a nameserver stats child node to `parent`.
1171fn add_name_server_stats_inspect<T>(
1172    parent: &fuchsia_inspect::Node,
1173    shared_resolver: SharedResolver<T>,
1174) -> fuchsia_inspect::LazyNode
1175where
1176    T: NameServerStatsProvider + Send + Sync + 'static,
1177{
1178    parent.create_lazy_child("name_servers", move || {
1179        let shared_resolver = shared_resolver.read();
1180        async move {
1181            let inspector = fuchsia_inspect::Inspector::default();
1182            for (
1183                i,
1184                NameServerStats { addr, proto, failures, successes, recent_errors, success_streak },
1185            ) in shared_resolver.name_server_stats().into_iter().enumerate()
1186            {
1187                inspector.root().record_child(format!("{i}"), |child| {
1188                    child.record_string("address", format!("{addr}"));
1189                    child.record_string("protocol", format!("{proto:?}"));
1190                    child.record_uint("successful_queries", successes as u64);
1191                    child.record_uint("failed_queries", failures as u64);
1192                    child.record_uint("success_streak", success_streak as u64);
1193
1194                    let failure_stats =
1195                        recent_errors.iter().fold(FailureStats::default(), |mut stats, error| {
1196                            stats.increment(error);
1197                            stats
1198                        });
1199                    child.record_child("errors", |errors| {
1200                        failure_stats.populate_inspect_node(&errors);
1201                    });
1202                });
1203            }
1204            Ok(inspector)
1205        }
1206        .boxed()
1207    })
1208}
1209
1210/// Adds a [`QueryStats`] inspection child node to `parent`.
1211fn add_query_stats_inspect(
1212    parent: &fuchsia_inspect::Node,
1213    stats: Arc<QueryStats>,
1214) -> fuchsia_inspect::LazyNode {
1215    parent.create_lazy_child("query_stats", move || {
1216        let stats = stats.clone();
1217        async move {
1218            let past_queries = &*stats.inner.lock().await;
1219            let node = fuchsia_inspect::Inspector::default();
1220            for (i, query_window) in past_queries.iter().enumerate() {
1221                node.root().record_child(format!("window {}", i + 1), |child| {
1222                    record_single_query_stats_node(child, query_window)
1223                });
1224            }
1225            Ok(node)
1226        }
1227        .boxed()
1228    })
1229}
1230
1231fn record_single_query_stats_node(
1232    node: &fuchsia_inspect::Node,
1233    QueryWindow {
1234        start,
1235        success_count,
1236        failure_count,
1237        success_elapsed_time,
1238        failure_elapsed_time,
1239        failure_stats,
1240        address_counts_histogram,
1241    }: &QueryWindow,
1242) {
1243    match u64::try_from(start.into_nanos()) {
1244        Ok(nanos) => {
1245            node.record_uint("start_time_nanos", nanos);
1246        }
1247        Err(e) => warn!(
1248            "error computing `start_time_nanos`: {:?}.into_nanos() from i64 -> u64 failed: {}",
1249            start, e
1250        ),
1251    }
1252    node.record_uint("successful_queries", *success_count);
1253    node.record_uint("failed_queries", *failure_count);
1254    let record_average = |name: &str, total: zx::MonotonicDuration, count: u64| {
1255        // Don't record an average if there are no stats.
1256        if count == 0 {
1257            return;
1258        }
1259        match u64::try_from(total.into_micros()) {
1260            Ok(micros) => node.record_uint(name, micros / count),
1261            Err(e) => warn!(
1262                "error computing `{}`: {:?}.into_micros() from i64 -> u64 failed: {}",
1263                name, success_elapsed_time, e
1264            ),
1265        }
1266    };
1267    record_average("average_success_duration_micros", *success_elapsed_time, *success_count);
1268    record_average("average_failure_duration_micros", *failure_elapsed_time, *failure_count);
1269
1270    node.record_child("errors", |errors| {
1271        failure_stats.populate_inspect_node(&errors);
1272    });
1273
1274    node.record_child("address_counts", |address_counts_node| {
1275        for (count, occurrences) in address_counts_histogram {
1276            address_counts_node.record_child(count.to_string(), |child| {
1277                child.record_uint("count", *occurrences);
1278            });
1279        }
1280    });
1281}
1282
1283// NB: We manually set tags so logs from trust-dns crates also get the same
1284// tags as opposed to only the crate path.
1285#[fuchsia::main(logging_tags = ["dns"])]
1286pub async fn main() -> Result<(), Error> {
1287    info!("starting");
1288
1289    let mut resolver_opts = ResolverOpts::default();
1290    // Resolver will query for A and AAAA in parallel for lookup_ip.
1291    resolver_opts.ip_strategy = LookupIpStrategy::Ipv4AndIpv6;
1292    let resolver = SharedResolver::new(
1293        Resolver::new(ResolverConfig::default(), resolver_opts, Spawner)
1294            .expect("failed to create resolver"),
1295    );
1296
1297    let config_state = Arc::new(dns::config::ServerConfigState::new());
1298    let stats = Arc::new(QueryStats::new());
1299
1300    let mut fs = ServiceFs::new_local();
1301
1302    let inspector = fuchsia_inspect::component::inspector();
1303    let _state_inspect_node = add_config_state_inspect(inspector.root(), config_state.clone());
1304    let _query_stats_inspect_node = add_query_stats_inspect(inspector.root(), stats.clone());
1305    let _name_server_stats_inspect_node =
1306        add_name_server_stats_inspect(inspector.root(), resolver.clone());
1307    let _inspect_server_task =
1308        inspect_runtime::publish(inspector, inspect_runtime::PublishOptions::default())
1309            .context("publish Inspect task")?;
1310
1311    let routes = fuchsia_component::client::connect_to_protocol::<fnet_routes::StateMarker>()
1312        .context("failed to connect to fuchsia.net.routes/State")?;
1313
1314    let _: &mut ServiceFsDir<'_, _> = fs
1315        .dir("svc")
1316        .add_fidl_service(IncomingRequest::Lookup)
1317        .add_fidl_service(IncomingRequest::LookupAdmin);
1318    let _: &mut ServiceFs<_> =
1319        fs.take_and_serve_directory_handle().context("failed to serve directory")?;
1320
1321    // Create a channel with buffer size `MAX_PARALLEL_REQUESTS`, which allows
1322    // request processing to always be fully saturated.
1323    let (sender, recv) = mpsc::channel(MAX_PARALLEL_REQUESTS);
1324    let serve_fut = fs.for_each_concurrent(None, |incoming_service| async {
1325        match incoming_service {
1326            IncomingRequest::Lookup(stream) => run_lookup(&resolver, stream, sender.clone())
1327                .await
1328                .unwrap_or_else(|e| warn!("run_lookup finished with error: {}", e)),
1329            IncomingRequest::LookupAdmin(stream) => {
1330                run_lookup_admin(&resolver, &config_state, stream)
1331                    .await
1332                    .unwrap_or_else(|e| error!("run_lookup_admin finished with error: {}", e))
1333            }
1334        }
1335    });
1336    let ip_lookup_fut = create_ip_lookup_fut(&resolver, stats.clone(), routes, recv);
1337
1338    // Failing to apply a scheduling role is not fatal. Issue a warning in case
1339    // DNS latency is important to a product and running at default priority is
1340    // insufficient.
1341    match fuchsia_scheduler::set_role_for_this_thread("fuchsia.networking.dns.resolver.main") {
1342        Ok(_) => info!("Applied scheduling role"),
1343        Err(err) => warn!("Failed to apply scheduling role: {}", err),
1344    };
1345
1346    let ((), ()) = futures::future::join(serve_fut, ip_lookup_fut).await;
1347    Ok(())
1348}
1349
1350#[cfg(test)]
1351mod tests {
1352    use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr};
1353    use std::pin::pin;
1354    use std::str::FromStr;
1355
1356    use assert_matches::assert_matches;
1357    use diagnostics_assertions::{
1358        NonZeroUintProperty, TreeAssertion, assert_data_tree, tree_assertion,
1359    };
1360    use dns::DEFAULT_PORT;
1361    use dns::test_util::*;
1362    use itertools::Itertools as _;
1363    use net_declare::{fidl_ip, std_ip, std_ip_v4, std_ip_v6, std_socket_addr};
1364    use net_types::ip::Ip as _;
1365    use test_case::test_case;
1366    use trust_dns_proto::op::Query;
1367    use trust_dns_proto::rr::{Name, Record};
1368    use trust_dns_resolver::config::Protocol;
1369    use trust_dns_resolver::lookup::{Lookup, ReverseLookup};
1370
1371    use super::*;
1372
1373    const IPV4_LOOPBACK: fnet::IpAddress = fidl_ip!("127.0.0.1");
1374    const IPV6_LOOPBACK: fnet::IpAddress = fidl_ip!("::1");
1375    const LOCAL_HOST: &str = "localhost.";
1376
1377    // IPv4 address returned by mock lookup.
1378    const IPV4_HOST: Ipv4Addr = std_ip_v4!("240.0.0.2");
1379    // IPv6 address returned by mock lookup.
1380    const IPV6_HOST: Ipv6Addr = std_ip_v6!("abcd::2");
1381
1382    // host which has IPv4 address only.
1383    const REMOTE_IPV4_HOST: &str = "www.foo.com";
1384    // host which has IPv6 address only.
1385    const REMOTE_IPV6_HOST: &str = "www.bar.com";
1386    const REMOTE_IPV4_HOST_ALIAS: &str = "www.alsofoo.com";
1387    const REMOTE_IPV6_HOST_ALIAS: &str = "www.alsobar.com";
1388    // host used in reverse_lookup when multiple hostnames are returned.
1389    const REMOTE_IPV6_HOST_EXTRA: &str = "www.bar2.com";
1390    // host which has IPv4 and IPv6 address if reset name servers.
1391    const REMOTE_IPV4_IPV6_HOST: &str = "www.foobar.com";
1392    // host which has no records and does not result in an error.
1393    const NO_RECORDS_AND_NO_ERROR_HOST: &str = "www.no-records-and-no-error.com";
1394
1395    async fn setup_namelookup_service() -> (fname::LookupProxy, impl futures::Future<Output = ()>) {
1396        let (name_lookup_proxy, stream) =
1397            fidl::endpoints::create_proxy_and_stream::<fname::LookupMarker>();
1398
1399        let mut resolver_opts = ResolverOpts::default();
1400        resolver_opts.ip_strategy = LookupIpStrategy::Ipv4AndIpv6;
1401
1402        let resolver = SharedResolver::new(
1403            Resolver::new(ResolverConfig::default(), resolver_opts, Spawner)
1404                .expect("failed to create resolver"),
1405        );
1406        let stats = Arc::new(QueryStats::new());
1407        let (routes_proxy, routes_stream) =
1408            fidl::endpoints::create_proxy_and_stream::<fnet_routes::StateMarker>();
1409        let routes_fut =
1410            routes_stream.try_for_each(|req| -> futures::future::Ready<Result<(), fidl::Error>> {
1411                panic!("Should not call routes/State. Received request {:?}", req)
1412            });
1413        let (sender, recv) = mpsc::channel(MAX_PARALLEL_REQUESTS);
1414
1415        (name_lookup_proxy, async move {
1416            futures::future::try_join3(
1417                run_lookup(&resolver, stream, sender),
1418                routes_fut,
1419                create_ip_lookup_fut(&resolver, stats.clone(), routes_proxy, recv).map(Ok),
1420            )
1421            .map(|r| match r {
1422                Ok(((), (), ())) => (),
1423                Err(e) => panic!("namelookup service error {:?}", e),
1424            })
1425            .await
1426        })
1427    }
1428
1429    #[fuchsia::test(logging = false)]
1430    async fn test_lookupip_localhost() {
1431        let (proxy, fut) = setup_namelookup_service().await;
1432        let ((), ()) = futures::future::join(fut, async move {
1433            // IP Lookup IPv4 and IPv6 for localhost.
1434            assert_eq!(
1435                proxy
1436                    .lookup_ip(
1437                        LOCAL_HOST,
1438                        &fname::LookupIpOptions {
1439                            ipv4_lookup: Some(true),
1440                            ipv6_lookup: Some(true),
1441                            ..Default::default()
1442                        }
1443                    )
1444                    .await
1445                    .expect("lookup_ip"),
1446                Ok(fname::LookupResult {
1447                    addresses: Some(vec![IPV4_LOOPBACK, IPV6_LOOPBACK]),
1448                    ..Default::default()
1449                }),
1450            );
1451
1452            // IP Lookup IPv4 only for localhost.
1453            assert_eq!(
1454                proxy
1455                    .lookup_ip(
1456                        LOCAL_HOST,
1457                        &fname::LookupIpOptions { ipv4_lookup: Some(true), ..Default::default() }
1458                    )
1459                    .await
1460                    .expect("lookup_ip"),
1461                Ok(fname::LookupResult {
1462                    addresses: Some(vec![IPV4_LOOPBACK]),
1463                    ..Default::default()
1464                }),
1465            );
1466
1467            // IP Lookup IPv6 only for localhost.
1468            assert_eq!(
1469                proxy
1470                    .lookup_ip(
1471                        LOCAL_HOST,
1472                        &fname::LookupIpOptions { ipv6_lookup: Some(true), ..Default::default() }
1473                    )
1474                    .await
1475                    .expect("lookup_ip"),
1476                Ok(fname::LookupResult {
1477                    addresses: Some(vec![IPV6_LOOPBACK]),
1478                    ..Default::default()
1479                }),
1480            );
1481        })
1482        .await;
1483    }
1484
1485    #[fuchsia::test(logging = false)]
1486    async fn test_lookuphostname_localhost() {
1487        let (proxy, fut) = setup_namelookup_service().await;
1488        let ((), ()) = futures::future::join(fut, async move {
1489            let hostname = IPV4_LOOPBACK;
1490            assert_eq!(
1491                proxy.lookup_hostname(&hostname).await.expect("lookup_hostname").as_deref(),
1492                Ok(LOCAL_HOST)
1493            );
1494        })
1495        .await;
1496    }
1497
1498    #[derive(Debug, Clone)]
1499    struct MockResolver {
1500        config: ResolverConfig,
1501        repeat: u16,
1502        name_server_stats: Vec<NameServerStats>,
1503    }
1504
1505    impl ResolverLookup for MockResolver {
1506        fn new(config: ResolverConfig, _options: ResolverOpts) -> Self {
1507            Self { config, repeat: 1, name_server_stats: Vec::new() }
1508        }
1509
1510        async fn lookup<N: IntoName + Send>(
1511            &self,
1512            name: N,
1513            record_type: RecordType,
1514        ) -> Result<lookup::Lookup, ResolveError> {
1515            let Self { config: _, repeat, name_server_stats: _ } = self;
1516
1517            let name = name.into_name()?;
1518            let host_name = name.to_utf8();
1519
1520            if host_name == NO_RECORDS_AND_NO_ERROR_HOST {
1521                return Ok(Lookup::new_with_max_ttl(Query::default(), Arc::new([])));
1522            }
1523            let rdatas = match record_type {
1524                RecordType::A => [REMOTE_IPV4_HOST, REMOTE_IPV4_IPV6_HOST]
1525                    .contains(&host_name.as_str())
1526                    .then_some(RData::A(IPV4_HOST)),
1527                RecordType::AAAA => [REMOTE_IPV6_HOST, REMOTE_IPV4_IPV6_HOST]
1528                    .contains(&host_name.as_str())
1529                    .then_some(RData::AAAA(IPV6_HOST)),
1530                RecordType::CNAME => match host_name.as_str() {
1531                    REMOTE_IPV4_HOST_ALIAS => Some(REMOTE_IPV4_HOST),
1532                    REMOTE_IPV6_HOST_ALIAS => Some(REMOTE_IPV6_HOST),
1533                    _ => None,
1534                }
1535                .map(Name::from_str)
1536                .transpose()
1537                .unwrap()
1538                .map(RData::CNAME),
1539                record_type => {
1540                    panic!("unexpected record type {:?}", record_type)
1541                }
1542            }
1543            .into_iter();
1544
1545            let len = rdatas.len() * usize::from(*repeat);
1546            let records: Vec<Record> = rdatas
1547                .map(|rdata| {
1548                    Record::from_rdata(
1549                        Name::new(),
1550                        // The following ttl value is taken arbitrarily and does not matter in the
1551                        // test.
1552                        60,
1553                        rdata,
1554                    )
1555                })
1556                .cycle()
1557                .take(len)
1558                .collect();
1559
1560            if records.is_empty() {
1561                let mut response = trust_dns_proto::op::Message::new();
1562                let _: &mut trust_dns_proto::op::Message =
1563                    response.set_response_code(ResponseCode::NoError);
1564                let error = ResolveError::from_response(response.into(), false)
1565                    .expect_err("response with no records should be a NoRecordsFound error");
1566                return Err(error);
1567            }
1568
1569            Ok(Lookup::new_with_max_ttl(Query::default(), records.into()))
1570        }
1571
1572        async fn reverse_lookup(
1573            &self,
1574            addr: IpAddr,
1575        ) -> Result<lookup::ReverseLookup, ResolveError> {
1576            let lookup = if addr == IPV4_HOST {
1577                Lookup::from_rdata(
1578                    Query::default(),
1579                    RData::PTR(Name::from_str(REMOTE_IPV4_HOST).unwrap()),
1580                )
1581            } else if addr == IPV6_HOST {
1582                Lookup::new_with_max_ttl(
1583                    Query::default(),
1584                    Arc::new([
1585                        Record::from_rdata(
1586                            Name::new(),
1587                            60, // The value is taken arbitrarily and does not matter
1588                            // in the test.
1589                            RData::PTR(Name::from_str(REMOTE_IPV6_HOST).unwrap()),
1590                        ),
1591                        Record::from_rdata(
1592                            Name::new(),
1593                            60, // The value is taken arbitrarily and does not matter
1594                            // in the test.
1595                            RData::PTR(Name::from_str(REMOTE_IPV6_HOST_EXTRA).unwrap()),
1596                        ),
1597                    ]),
1598                )
1599            } else {
1600                Lookup::new_with_max_ttl(Query::default(), Arc::new([]))
1601            };
1602            Ok(ReverseLookup::from(lookup))
1603        }
1604    }
1605
1606    impl NameServerStatsProvider for MockResolver {
1607        fn name_server_stats(&self) -> Vec<NameServerStats> {
1608            self.name_server_stats.clone()
1609        }
1610    }
1611
1612    struct TestEnvironment {
1613        shared_resolver: SharedResolver<MockResolver>,
1614        config_state: Arc<dns::config::ServerConfigState>,
1615        stats: Arc<QueryStats>,
1616    }
1617
1618    impl Default for TestEnvironment {
1619        fn default() -> Self {
1620            Self::new(1)
1621        }
1622    }
1623
1624    impl TestEnvironment {
1625        fn new(repeat: u16) -> Self {
1626            Self {
1627                shared_resolver: SharedResolver::new(MockResolver {
1628                    config: ResolverConfig::from_parts(
1629                        None,
1630                        vec![],
1631                        // Set name_servers as empty, so it's guaranteed to be different from IPV4_NAMESERVER
1632                        // and IPV6_NAMESERVER.
1633                        NameServerConfigGroup::with_capacity(0),
1634                    ),
1635                    repeat,
1636                    name_server_stats: Vec::new(),
1637                }),
1638                config_state: Arc::new(dns::config::ServerConfigState::new()),
1639                stats: Arc::new(QueryStats::new()),
1640            }
1641        }
1642
1643        async fn run_lookup<F, Fut>(&self, f: F)
1644        where
1645            Fut: futures::Future<Output = ()>,
1646            F: FnOnce(fname::LookupProxy) -> Fut,
1647        {
1648            self.run_lookup_with_routes_handler(f, |req| {
1649                panic!("Should not call routes/State. Received request {:?}", req)
1650            })
1651            .await
1652        }
1653
1654        async fn run_lookup_with_routes_handler<F, Fut, R>(&self, f: F, handle_routes: R)
1655        where
1656            Fut: futures::Future<Output = ()>,
1657            F: FnOnce(fname::LookupProxy) -> Fut,
1658            R: Fn(fnet_routes::StateRequest),
1659        {
1660            let (name_lookup_proxy, name_lookup_stream) =
1661                fidl::endpoints::create_proxy_and_stream::<fname::LookupMarker>();
1662
1663            let (routes_proxy, routes_stream) =
1664                fidl::endpoints::create_proxy_and_stream::<fnet_routes::StateMarker>();
1665
1666            let (sender, recv) = mpsc::channel(MAX_PARALLEL_REQUESTS);
1667            let Self { shared_resolver, config_state: _, stats } = self;
1668            let ((), (), (), ()) = futures::future::try_join4(
1669                run_lookup(shared_resolver, name_lookup_stream, sender),
1670                f(name_lookup_proxy).map(Ok),
1671                routes_stream.try_for_each(|req| futures::future::ok(handle_routes(req))),
1672                create_ip_lookup_fut(shared_resolver, stats.clone(), routes_proxy, recv).map(Ok),
1673            )
1674            .await
1675            .expect("Error running lookup future");
1676        }
1677
1678        async fn run_admin<F, Fut>(&self, f: F)
1679        where
1680            Fut: futures::Future<Output = ()>,
1681            F: FnOnce(fname::LookupAdminProxy) -> Fut,
1682        {
1683            let (lookup_admin_proxy, lookup_admin_stream) =
1684                fidl::endpoints::create_proxy_and_stream::<fname::LookupAdminMarker>();
1685            let Self { shared_resolver, config_state, stats: _ } = self;
1686            let ((), ()) = futures::future::try_join(
1687                run_lookup_admin(shared_resolver, config_state, lookup_admin_stream)
1688                    .map_err(anyhow::Error::from),
1689                f(lookup_admin_proxy).map(Ok),
1690            )
1691            .await
1692            .expect("Error running admin future");
1693        }
1694    }
1695
1696    fn map_ip<T: Into<IpAddr>>(addr: T) -> fnet::IpAddress {
1697        net_ext::IpAddress(addr.into()).into()
1698    }
1699
1700    #[fuchsia::test(logging = false)]
1701    async fn test_no_records_and_no_error() {
1702        TestEnvironment::default()
1703            .run_lookup(|proxy| async move {
1704                let proxy = &proxy;
1705                futures::stream::iter([(true, true), (true, false), (false, true)])
1706                    .for_each_concurrent(None, move |(ipv4_lookup, ipv6_lookup)| async move {
1707                        // Verify that the resolver does not panic when the
1708                        // response contains no records and no error. This
1709                        // scenario should theoretically not occur, but
1710                        // currently does. See https://fxbug.dev/42062388.
1711                        assert_eq!(
1712                            proxy
1713                                .lookup_ip(
1714                                    NO_RECORDS_AND_NO_ERROR_HOST,
1715                                    &fname::LookupIpOptions {
1716                                        ipv4_lookup: Some(ipv4_lookup),
1717                                        ipv6_lookup: Some(ipv6_lookup),
1718                                        ..Default::default()
1719                                    }
1720                                )
1721                                .await
1722                                .expect("lookup_ip"),
1723                            Err(fname::LookupError::NotFound),
1724                        );
1725                    })
1726                    .await
1727            })
1728            .await;
1729    }
1730
1731    #[fuchsia::test(logging = false)]
1732    async fn test_lookupip_remotehost_overflow() {
1733        // We're returning two addresses, so we need each one to repeat only half as many times.
1734        const REPEAT: u16 = fname::MAX_ADDRESSES / 2 + 1;
1735        let expected = std::iter::empty()
1736            .chain(std::iter::repeat(map_ip(IPV4_HOST)).take(REPEAT.into()))
1737            .chain(std::iter::repeat(map_ip(IPV6_HOST)).take(REPEAT.into()))
1738            .take(fname::MAX_ADDRESSES.into())
1739            .collect::<Vec<_>>();
1740        assert_eq!(expected.len(), usize::from(fname::MAX_ADDRESSES));
1741        TestEnvironment::new(REPEAT)
1742            .run_lookup(|proxy| async move {
1743                assert_eq!(
1744                    proxy
1745                        .lookup_ip(
1746                            REMOTE_IPV4_IPV6_HOST,
1747                            &fname::LookupIpOptions {
1748                                ipv4_lookup: Some(true),
1749                                ipv6_lookup: Some(true),
1750                                ..Default::default()
1751                            }
1752                        )
1753                        .await
1754                        .expect("lookup_ip"),
1755                    Ok(fname::LookupResult { addresses: Some(expected), ..Default::default() })
1756                );
1757            })
1758            .await;
1759    }
1760
1761    #[fuchsia::test(logging = false)]
1762    async fn test_lookupip_remotehost_ipv4() {
1763        TestEnvironment::default()
1764            .run_lookup(|proxy| async move {
1765                // IP Lookup IPv4 and IPv6 for REMOTE_IPV4_HOST.
1766                assert_eq!(
1767                    proxy
1768                        .lookup_ip(
1769                            REMOTE_IPV4_HOST,
1770                            &fname::LookupIpOptions {
1771                                ipv4_lookup: Some(true),
1772                                ipv6_lookup: Some(true),
1773                                ..Default::default()
1774                            }
1775                        )
1776                        .await
1777                        .expect("lookup_ip"),
1778                    Ok(fname::LookupResult {
1779                        addresses: Some(vec![map_ip(IPV4_HOST)]),
1780                        ..Default::default()
1781                    }),
1782                );
1783
1784                // IP Lookup IPv4 for REMOTE_IPV4_HOST.
1785                assert_eq!(
1786                    proxy
1787                        .lookup_ip(
1788                            REMOTE_IPV4_HOST,
1789                            &fname::LookupIpOptions {
1790                                ipv4_lookup: Some(true),
1791                                ..Default::default()
1792                            }
1793                        )
1794                        .await
1795                        .expect("lookup_ip"),
1796                    Ok(fname::LookupResult {
1797                        addresses: Some(vec![map_ip(IPV4_HOST)]),
1798                        ..Default::default()
1799                    }),
1800                );
1801
1802                // IP Lookup IPv6 for REMOTE_IPV4_HOST.
1803                assert_eq!(
1804                    proxy
1805                        .lookup_ip(
1806                            REMOTE_IPV4_HOST,
1807                            &fname::LookupIpOptions {
1808                                ipv6_lookup: Some(true),
1809                                ..Default::default()
1810                            }
1811                        )
1812                        .await
1813                        .expect("lookup_ip"),
1814                    Err(fname::LookupError::NotFound),
1815                );
1816            })
1817            .await;
1818    }
1819
1820    #[fuchsia::test(logging = false)]
1821    async fn test_lookupip_remotehost_ipv6() {
1822        TestEnvironment::default()
1823            .run_lookup(|proxy| async move {
1824                // IP Lookup IPv4 and IPv6 for REMOTE_IPV6_HOST.
1825                assert_eq!(
1826                    proxy
1827                        .lookup_ip(
1828                            REMOTE_IPV6_HOST,
1829                            &fname::LookupIpOptions {
1830                                ipv4_lookup: Some(true),
1831                                ipv6_lookup: Some(true),
1832                                ..Default::default()
1833                            }
1834                        )
1835                        .await
1836                        .expect("lookup_ip"),
1837                    Ok(fname::LookupResult {
1838                        addresses: Some(vec![map_ip(IPV6_HOST)]),
1839                        ..Default::default()
1840                    }),
1841                );
1842
1843                // IP Lookup IPv4 for REMOTE_IPV6_HOST.
1844                assert_eq!(
1845                    proxy
1846                        .lookup_ip(
1847                            REMOTE_IPV6_HOST,
1848                            &fname::LookupIpOptions {
1849                                ipv4_lookup: Some(true),
1850                                ..Default::default()
1851                            }
1852                        )
1853                        .await
1854                        .expect("lookup_ip"),
1855                    Err(fname::LookupError::NotFound),
1856                );
1857
1858                // IP Lookup IPv6 for REMOTE_IPV4_HOST.
1859                assert_eq!(
1860                    proxy
1861                        .lookup_ip(
1862                            REMOTE_IPV6_HOST,
1863                            &fname::LookupIpOptions {
1864                                ipv6_lookup: Some(true),
1865                                ..Default::default()
1866                            }
1867                        )
1868                        .await
1869                        .expect("lookup_ip"),
1870                    Ok(fname::LookupResult {
1871                        addresses: Some(vec![map_ip(IPV6_HOST)]),
1872                        ..Default::default()
1873                    }),
1874                );
1875            })
1876            .await;
1877    }
1878
1879    #[test_case(REMOTE_IPV4_HOST_ALIAS, REMOTE_IPV4_HOST; "ipv4")]
1880    #[test_case(REMOTE_IPV6_HOST_ALIAS, REMOTE_IPV6_HOST; "ipv6")]
1881    #[fuchsia::test(logging = false)]
1882    async fn test_lookupip_remotehost_canonical_name(hostname: &str, expected: &str) {
1883        TestEnvironment::default()
1884            .run_lookup(|proxy| async move {
1885                assert_matches!(
1886                    proxy
1887                        .lookup_ip(
1888                            hostname,
1889                            &fname::LookupIpOptions {
1890                                canonical_name_lookup: Some(true),
1891                                ..Default::default()
1892                            }
1893                        )
1894                        .await,
1895                    Ok(Ok(fname::LookupResult {
1896                        canonical_name: Some(cname),
1897                        ..
1898                    })) => assert_eq!(cname, expected)
1899                );
1900            })
1901            .await;
1902    }
1903
1904    #[fuchsia::test(logging = false)]
1905    async fn test_lookupip_ip_literal() {
1906        TestEnvironment::default()
1907            .run_lookup(|proxy| async move {
1908                let proxy = &proxy;
1909
1910                let range = || [true, false].into_iter();
1911
1912                futures::stream::iter(range().cartesian_product(range()))
1913                    .for_each_concurrent(None, move |(ipv4_lookup, ipv6_lookup)| async move {
1914                        assert_eq!(
1915                            proxy
1916                                .lookup_ip(
1917                                    "240.0.0.2",
1918                                    &fname::LookupIpOptions {
1919                                        ipv4_lookup: Some(ipv4_lookup),
1920                                        ipv6_lookup: Some(ipv6_lookup),
1921                                        ..Default::default()
1922                                    }
1923                                )
1924                                .await
1925                                .expect("lookup_ip"),
1926                            Err(fname::LookupError::InvalidArgs),
1927                            "ipv4_lookup={},ipv6_lookup={}",
1928                            ipv4_lookup,
1929                            ipv6_lookup,
1930                        );
1931
1932                        assert_eq!(
1933                            proxy
1934                                .lookup_ip(
1935                                    "abcd::2",
1936                                    &fname::LookupIpOptions {
1937                                        ipv4_lookup: Some(ipv4_lookup),
1938                                        ipv6_lookup: Some(ipv6_lookup),
1939                                        ..Default::default()
1940                                    }
1941                                )
1942                                .await
1943                                .expect("lookup_ip"),
1944                            Err(fname::LookupError::InvalidArgs),
1945                            "ipv4_lookup={},ipv6_lookup={}",
1946                            ipv4_lookup,
1947                            ipv6_lookup,
1948                        );
1949                    })
1950                    .await
1951            })
1952            .await
1953    }
1954
1955    #[fuchsia::test(logging = false)]
1956    async fn test_lookup_hostname() {
1957        TestEnvironment::default()
1958            .run_lookup(|proxy| async move {
1959                assert_eq!(
1960                    proxy
1961                        .lookup_hostname(&map_ip(IPV4_HOST))
1962                        .await
1963                        .expect("lookup_hostname")
1964                        .as_deref(),
1965                    Ok(REMOTE_IPV4_HOST)
1966                );
1967            })
1968            .await;
1969    }
1970
1971    // Multiple hostnames returned from trust-dns* APIs, and only the first one will be returned
1972    // by the FIDL.
1973    #[fuchsia::test(logging = false)]
1974    async fn test_lookup_hostname_multi() {
1975        TestEnvironment::default()
1976            .run_lookup(|proxy| async move {
1977                assert_eq!(
1978                    proxy
1979                        .lookup_hostname(&map_ip(IPV6_HOST))
1980                        .await
1981                        .expect("lookup_hostname")
1982                        .as_deref(),
1983                    Ok(REMOTE_IPV6_HOST)
1984                );
1985            })
1986            .await;
1987    }
1988
1989    #[fuchsia::test(logging = false)]
1990    async fn test_set_server_names() {
1991        let env = TestEnvironment::default();
1992
1993        let to_server_configs = |socket_addr: SocketAddr| -> [NameServerConfig; 2] {
1994            [
1995                NameServerConfig {
1996                    socket_addr,
1997                    protocol: Protocol::Udp,
1998                    tls_dns_name: None,
1999                    trust_nx_responses: false,
2000                    bind_addr: None,
2001                    num_retained_errors: RETAINED_ERRORS_PER_NAME_SERVER,
2002                },
2003                NameServerConfig {
2004                    socket_addr,
2005                    protocol: Protocol::Tcp,
2006                    tls_dns_name: None,
2007                    trust_nx_responses: false,
2008                    bind_addr: None,
2009                    num_retained_errors: RETAINED_ERRORS_PER_NAME_SERVER,
2010                },
2011            ]
2012        };
2013
2014        // Assert that mock config has no servers originally.
2015        assert_eq!(env.shared_resolver.read().config.name_servers().to_vec(), vec![]);
2016
2017        // Set servers.
2018        env.run_admin(|proxy| async move {
2019            proxy
2020                .set_dns_servers(&[DHCP_SERVER, NDP_SERVER, DHCPV6_SERVER])
2021                .await
2022                .expect("Failed to call SetDnsServers")
2023                .expect("SetDnsServers error");
2024        })
2025        .await;
2026        assert_eq!(
2027            env.shared_resolver.read().config.name_servers().to_vec(),
2028            vec![DHCP_SERVER, NDP_SERVER, DHCPV6_SERVER]
2029                .into_iter()
2030                .map(|s| {
2031                    let net_ext::SocketAddress(s) = s.into();
2032                    s
2033                })
2034                .flat_map(|x| to_server_configs(x).to_vec().into_iter())
2035                .collect::<Vec<_>>()
2036        );
2037
2038        // Clear servers.
2039        env.run_admin(|proxy| async move {
2040            proxy
2041                .set_dns_servers(&[])
2042                .await
2043                .expect("Failed to call SetDnsServers")
2044                .expect("SetDnsServers error");
2045        })
2046        .await;
2047        assert_eq!(env.shared_resolver.read().config.name_servers().to_vec(), Vec::new());
2048    }
2049
2050    #[fuchsia::test(logging = false)]
2051    async fn test_set_server_names_error() {
2052        let env = TestEnvironment::default();
2053        // Assert that mock config has no servers originally.
2054        assert_eq!(env.shared_resolver.read().config.name_servers().to_vec(), vec![]);
2055
2056        env.run_admin(|proxy| async move {
2057            // Attempt to set bad addresses.
2058
2059            // Multicast not allowed.
2060            let status = proxy
2061                .set_dns_servers(&[fnet::SocketAddress::Ipv4(fnet::Ipv4SocketAddress {
2062                    address: fnet::Ipv4Address { addr: [224, 0, 0, 1] },
2063                    port: DEFAULT_PORT,
2064                })])
2065                .await
2066                .expect("Failed to call SetDnsServers")
2067                .expect_err("SetDnsServers should fail for multicast address");
2068            assert_eq!(zx::Status::ok(status), Err(zx::Status::INVALID_ARGS));
2069
2070            // Unspecified not allowed.
2071            let status = proxy
2072                .set_dns_servers(&[fnet::SocketAddress::Ipv6(fnet::Ipv6SocketAddress {
2073                    address: fnet::Ipv6Address { addr: [0; 16] },
2074                    port: DEFAULT_PORT,
2075                    zone_index: 0,
2076                })])
2077                .await
2078                .expect("Failed to call SetDnsServers")
2079                .expect_err("SetDnsServers should fail for unspecified address");
2080            assert_eq!(zx::Status::ok(status), Err(zx::Status::INVALID_ARGS));
2081        })
2082        .await;
2083
2084        // Assert that config didn't change.
2085        assert_eq!(env.shared_resolver.read().config.name_servers().to_vec(), vec![]);
2086    }
2087
2088    #[fuchsia::test(logging = false)]
2089    async fn test_get_servers() {
2090        let env = TestEnvironment::default();
2091        env.run_admin(|proxy| async move {
2092            let expect = &[NDP_SERVER, DHCP_SERVER, DHCPV6_SERVER, STATIC_SERVER];
2093            proxy.set_dns_servers(expect).await.expect("FIDL error").expect("set_servers failed");
2094            assert_matches!(proxy.get_dns_servers().await, Ok(got) if got == expect);
2095        })
2096        .await;
2097    }
2098
2099    #[fuchsia::test(logging = false)]
2100    async fn test_config_inspect() {
2101        let env = TestEnvironment::default();
2102        let inspector = fuchsia_inspect::Inspector::default();
2103        let _config_state_node =
2104            add_config_state_inspect(inspector.root(), env.config_state.clone());
2105        assert_data_tree!(inspector, root:{
2106            servers: {}
2107        });
2108        env.run_admin(|proxy| async move {
2109            let servers = &[NDP_SERVER, DHCP_SERVER, DHCPV6_SERVER, STATIC_SERVER];
2110            proxy.set_dns_servers(servers).await.expect("FIDL error").expect("set_servers failed");
2111        })
2112        .await;
2113        assert_data_tree!(inspector, root:{
2114            servers: {
2115                "0": {
2116                    address: "[2001:4860:4860::4444%2]:53",
2117                },
2118                "1": {
2119                    address: "8.8.4.4:53",
2120                },
2121                "2": {
2122                    address: "[2002:4860:4860::4444%3]:53",
2123                },
2124                "3": {
2125                    address: "8.8.8.8:53",
2126                },
2127            }
2128        });
2129    }
2130
2131    #[fuchsia::test(logging = false)]
2132    async fn test_name_server_stats_inspect() {
2133        let env = TestEnvironment::default();
2134        let inspector = fuchsia_inspect::Inspector::default();
2135        let _name_server_stats_node =
2136            add_name_server_stats_inspect(inspector.root(), env.shared_resolver.clone());
2137        assert_data_tree!(inspector, root:{
2138            name_servers: {}
2139        });
2140
2141        let addr = std_socket_addr!("1.2.3.4:53");
2142        let stats = NameServerStats {
2143            addr,
2144            proto: Protocol::Udp,
2145            failures: 1,
2146            successes: 2,
2147            success_streak: 0,
2148            recent_errors: vec![ResolveErrorKind::Timeout.into()],
2149        };
2150        env.shared_resolver.write(Arc::new(MockResolver {
2151            config: ResolverConfig::default(),
2152            repeat: 1,
2153            name_server_stats: vec![stats],
2154        }));
2155
2156        assert_data_tree!(inspector, root:{
2157            name_servers: {
2158                "0": {
2159                    address: "1.2.3.4:53",
2160                    protocol: "Udp",
2161                    successful_queries: 2u64,
2162                    failed_queries: 1u64,
2163                    success_streak: 0u64,
2164                    errors: {
2165                        Timeout: 1u64,
2166                        Message: 0u64,
2167                        NoConnections: 0u64,
2168                        IoErrorCounts: {},
2169                        ProtoErrorCounts: {},
2170                        NoRecordsFoundResponseCodeCounts: {},
2171                        UnhandledResolveErrorKindCounts: {},
2172                    }
2173                }
2174            }
2175        });
2176    }
2177
2178    #[test]
2179    fn test_unhandled_resolve_error_kind_stats() {
2180        use ResolveErrorKind::{Msg, Timeout};
2181        let mut unhandled_resolve_error_kind_stats = GenericErrorKindStats::default();
2182        assert_eq!(
2183            unhandled_resolve_error_kind_stats.increment(&Msg(String::from("abcdefgh"))),
2184            "Msg"
2185        );
2186        assert_eq!(
2187            unhandled_resolve_error_kind_stats.increment(&Msg(String::from("ijklmn"))),
2188            "Msg"
2189        );
2190        assert_eq!(unhandled_resolve_error_kind_stats.increment(&Timeout), "Timeout");
2191        assert_eq!(
2192            unhandled_resolve_error_kind_stats,
2193            GenericErrorKindStats([(String::from("Msg"), 2), (String::from("Timeout"), 1)].into())
2194        )
2195    }
2196
2197    #[fuchsia::test(logging = false)]
2198    async fn test_query_stats_updated() {
2199        let env = TestEnvironment::default();
2200        let inspector = fuchsia_inspect::Inspector::default();
2201        let _query_stats_inspect_node =
2202            add_query_stats_inspect(inspector.root(), env.stats.clone());
2203        assert_data_tree!(inspector, root:{
2204            query_stats: {}
2205        });
2206
2207        env.run_lookup(|proxy| async move {
2208            // IP Lookup IPv4 for REMOTE_IPV4_HOST.
2209            assert_eq!(
2210                proxy
2211                    .lookup_ip(
2212                        REMOTE_IPV4_HOST,
2213                        &fname::LookupIpOptions { ipv4_lookup: Some(true), ..Default::default() }
2214                    )
2215                    .await
2216                    .expect("lookup_ip"),
2217                Ok(fname::LookupResult {
2218                    addresses: Some(vec![map_ip(IPV4_HOST)]),
2219                    ..Default::default()
2220                }),
2221            );
2222        })
2223        .await;
2224        env.run_lookup(|proxy| async move {
2225            // IP Lookup IPv6 for REMOTE_IPV4_HOST.
2226            assert_eq!(
2227                proxy
2228                    .lookup_ip(
2229                        REMOTE_IPV4_HOST,
2230                        &fname::LookupIpOptions { ipv6_lookup: Some(true), ..Default::default() }
2231                    )
2232                    .await
2233                    .expect("lookup_ip"),
2234                Err(fname::LookupError::NotFound),
2235            );
2236        })
2237        .await;
2238        assert_data_tree!(inspector, root:{
2239            query_stats: {
2240                "window 1": {
2241                    start_time_nanos: NonZeroUintProperty,
2242                    successful_queries: 1u64,
2243                    failed_queries: 1u64,
2244                    average_success_duration_micros: NonZeroUintProperty,
2245                    average_failure_duration_micros: NonZeroUintProperty,
2246                    errors: {
2247                        Message: 0u64,
2248                        NoConnections: 0u64,
2249                        NoRecordsFoundResponseCodeCounts: {
2250                            NoError: {
2251                                count: 1u64
2252                            },
2253                        },
2254                        IoErrorCounts: {},
2255                        ProtoErrorCounts: {},
2256                        Timeout: 0u64,
2257                        UnhandledResolveErrorKindCounts: {},
2258                    },
2259                    address_counts: {
2260                        "1": {
2261                            count: 1u64,
2262                        }
2263                    },
2264                },
2265            }
2266        });
2267    }
2268
2269    fn run_fake_lookup(
2270        exec: &mut fasync::TestExecutor,
2271        stats: Arc<QueryStats>,
2272        result: QueryResult<'_>,
2273        delay: zx::MonotonicDuration,
2274    ) {
2275        let start_time = fasync::MonotonicInstant::now();
2276        exec.set_fake_time(fasync::MonotonicInstant::after(delay));
2277        let update_stats = stats.finish_query(start_time, result);
2278        let mut update_stats = pin!(update_stats);
2279        assert!(exec.run_until_stalled(&mut update_stats).is_ready());
2280    }
2281
2282    // Safety: This is safe because the initial value is not zero.
2283    const NON_ZERO_USIZE_ONE: NonZeroUsize = NonZeroUsize::new(1).unwrap();
2284
2285    #[test]
2286    fn test_query_stats_inspect_average() {
2287        let mut exec = fasync::TestExecutor::new_with_fake_time();
2288        const START_NANOS: i64 = 1_234_567;
2289        exec.set_fake_time(fasync::MonotonicInstant::from_nanos(START_NANOS));
2290
2291        let stats = Arc::new(QueryStats::new());
2292        let inspector = fuchsia_inspect::Inspector::default();
2293        let _query_stats_inspect_node = add_query_stats_inspect(inspector.root(), stats.clone());
2294        const SUCCESSFUL_QUERY_COUNT: u64 = 10;
2295        const SUCCESSFUL_QUERY_DURATION: zx::MonotonicDuration =
2296            zx::MonotonicDuration::from_seconds(30);
2297        for _ in 0..SUCCESSFUL_QUERY_COUNT / 2 {
2298            run_fake_lookup(
2299                &mut exec,
2300                stats.clone(),
2301                Ok(/*addresses*/ NON_ZERO_USIZE_ONE),
2302                zx::MonotonicDuration::from_nanos(0),
2303            );
2304            run_fake_lookup(
2305                &mut exec,
2306                stats.clone(),
2307                Ok(/*addresses*/ NON_ZERO_USIZE_ONE),
2308                SUCCESSFUL_QUERY_DURATION,
2309            );
2310            exec.set_fake_time(fasync::MonotonicInstant::after(
2311                STAT_WINDOW_DURATION - SUCCESSFUL_QUERY_DURATION,
2312            ));
2313        }
2314        let mut expected = tree_assertion!(query_stats: {});
2315        for i in 0..SUCCESSFUL_QUERY_COUNT / 2 {
2316            let name = &format!("window {}", i + 1);
2317            let child = tree_assertion!(var name: {
2318                start_time_nanos: u64::try_from(
2319                    START_NANOS + STAT_WINDOW_DURATION.into_nanos() * i64::try_from(i).unwrap()
2320                ).unwrap(),
2321                successful_queries: 2u64,
2322                failed_queries: 0u64,
2323                average_success_duration_micros: u64::try_from(
2324                    SUCCESSFUL_QUERY_DURATION.into_micros()
2325                ).unwrap() / 2,
2326                errors: {
2327                    Message: 0u64,
2328                    NoConnections: 0u64,
2329                    NoRecordsFoundResponseCodeCounts: {},
2330                    IoErrorCounts: {},
2331                    ProtoErrorCounts: {},
2332                    Timeout: 0u64,
2333                    UnhandledResolveErrorKindCounts: {},
2334                },
2335                address_counts: {
2336                    "1": {
2337                        count: 2u64,
2338                    }
2339                },
2340            });
2341            expected.add_child_assertion(child);
2342        }
2343        assert_data_tree!(@executor exec, inspector, root: {
2344            expected,
2345        });
2346    }
2347
2348    #[test]
2349    fn test_query_stats_inspect_error_counters() {
2350        let mut exec = fasync::TestExecutor::new_with_fake_time();
2351        const START_NANOS: i64 = 1_234_567;
2352        exec.set_fake_time(fasync::MonotonicInstant::from_nanos(START_NANOS));
2353
2354        let stats = Arc::new(QueryStats::new());
2355        let inspector = fuchsia_inspect::Inspector::default();
2356        let _query_stats_inspect_node = add_query_stats_inspect(inspector.root(), stats.clone());
2357        const FAILED_QUERY_COUNT: u64 = 10;
2358        const FAILED_QUERY_DURATION: zx::MonotonicDuration =
2359            zx::MonotonicDuration::from_millis(500);
2360        for _ in 0..FAILED_QUERY_COUNT {
2361            run_fake_lookup(
2362                &mut exec,
2363                stats.clone(),
2364                Err(&ResolveErrorKind::Timeout),
2365                FAILED_QUERY_DURATION,
2366            );
2367        }
2368        assert_data_tree!(@executor exec, inspector, root:{
2369            query_stats: {
2370                "window 1": {
2371                    start_time_nanos: u64::try_from(
2372                        START_NANOS + FAILED_QUERY_DURATION.into_nanos()
2373                    ).unwrap(),
2374                    successful_queries: 0u64,
2375                    failed_queries: FAILED_QUERY_COUNT,
2376                    average_failure_duration_micros: u64::try_from(
2377                        FAILED_QUERY_DURATION.into_micros()
2378                    ).unwrap(),
2379                    errors: {
2380                        Message: 0u64,
2381                        NoConnections: 0u64,
2382                        NoRecordsFoundResponseCodeCounts: {},
2383                        IoErrorCounts: {},
2384                        ProtoErrorCounts: {},
2385                        Timeout: FAILED_QUERY_COUNT,
2386                        UnhandledResolveErrorKindCounts: {},
2387                    },
2388                    address_counts: {},
2389                },
2390            }
2391        });
2392    }
2393
2394    #[test]
2395    fn test_query_stats_inspect_no_records_found() {
2396        let mut exec = fasync::TestExecutor::new_with_fake_time();
2397        const START_NANOS: i64 = 1_234_567;
2398        exec.set_fake_time(fasync::MonotonicInstant::from_nanos(START_NANOS));
2399
2400        let stats = Arc::new(QueryStats::new());
2401        let inspector = fuchsia_inspect::Inspector::default();
2402        let _query_stats_inspect_node = add_query_stats_inspect(inspector.root(), stats.clone());
2403        const FAILED_QUERY_COUNT: u64 = 10;
2404        const FAILED_QUERY_DURATION: zx::MonotonicDuration =
2405            zx::MonotonicDuration::from_millis(500);
2406
2407        let mut run_fake_no_records_lookup = |response_code: ResponseCode| {
2408            run_fake_lookup(
2409                &mut exec,
2410                stats.clone(),
2411                Err(&ResolveErrorKind::NoRecordsFound {
2412                    query: Box::new(Query::default()),
2413                    soa: None,
2414                    negative_ttl: None,
2415                    response_code,
2416                    trusted: false,
2417                }),
2418                FAILED_QUERY_DURATION,
2419            )
2420        };
2421
2422        for _ in 0..FAILED_QUERY_COUNT {
2423            run_fake_no_records_lookup(ResponseCode::NXDomain);
2424            run_fake_no_records_lookup(ResponseCode::Refused);
2425            run_fake_no_records_lookup(4096.into());
2426            run_fake_no_records_lookup(4097.into());
2427        }
2428
2429        assert_data_tree!(@executor exec, inspector, root:{
2430            query_stats: {
2431                "window 1": {
2432                    start_time_nanos: u64::try_from(
2433                        START_NANOS + FAILED_QUERY_DURATION.into_nanos()
2434                    ).unwrap(),
2435                    successful_queries: 0u64,
2436                    failed_queries: FAILED_QUERY_COUNT * 4,
2437                    average_failure_duration_micros: u64::try_from(
2438                        FAILED_QUERY_DURATION.into_micros()
2439                    ).unwrap(),
2440                    errors: {
2441                        Message: 0u64,
2442                        NoConnections: 0u64,
2443                        NoRecordsFoundResponseCodeCounts: {
2444                            NXDomain: {
2445                                count: FAILED_QUERY_COUNT
2446                            },
2447                            Refused: {
2448                                count: FAILED_QUERY_COUNT
2449                            },
2450                            "Unknown(4096)": {
2451                                count: FAILED_QUERY_COUNT
2452                            },
2453                            "Unknown(4097)": {
2454                                count: FAILED_QUERY_COUNT
2455                            },
2456                        },
2457                        IoErrorCounts: {},
2458                        ProtoErrorCounts: {},
2459                        Timeout: 0u64,
2460                        UnhandledResolveErrorKindCounts: {},
2461                    },
2462                    address_counts: {},
2463                },
2464            }
2465        });
2466    }
2467
2468    #[test]
2469    fn test_query_stats_resolved_address_counts() {
2470        let mut exec = fasync::TestExecutor::new_with_fake_time();
2471        const START_NANOS: i64 = 1_234_567;
2472        exec.set_fake_time(fasync::MonotonicInstant::from_nanos(START_NANOS));
2473
2474        let stats = Arc::new(QueryStats::new());
2475        let inspector = fuchsia_inspect::Inspector::default();
2476        let _query_stats_inspect_node = add_query_stats_inspect(inspector.root(), stats.clone());
2477
2478        // Create some test data to run fake lookups. Simulate a histogram with:
2479        //  - 99 occurrences of a response with 1 address,
2480        //  - 98 occurrences of a response with 2 addresses,
2481        //  - ...
2482        //  - 1 occurrence of a response with 99 addresses.
2483        let address_counts: HashMap<usize, _> = (1..100).zip((1..100).rev()).collect();
2484        const QUERY_DURATION: zx::MonotonicDuration = zx::MonotonicDuration::from_millis(10);
2485        for (count, occurrences) in address_counts.iter() {
2486            for _ in 0..*occurrences {
2487                run_fake_lookup(
2488                    &mut exec,
2489                    stats.clone(),
2490                    Ok(NonZeroUsize::new(*count).expect("address count must be greater than zero")),
2491                    QUERY_DURATION,
2492                );
2493            }
2494        }
2495
2496        let mut expected_address_counts = tree_assertion!(address_counts: {});
2497        for (count, occurrences) in address_counts.iter() {
2498            let mut child = TreeAssertion::new(&count.to_string(), true);
2499            child.add_property_assertion("count", Arc::new(*occurrences));
2500            expected_address_counts.add_child_assertion(child);
2501        }
2502        assert_data_tree!(@executor exec, inspector, root: {
2503            query_stats: {
2504                "window 1": {
2505                    start_time_nanos: u64::try_from(
2506                        START_NANOS + QUERY_DURATION.into_nanos()
2507                    ).unwrap(),
2508                    successful_queries: address_counts.values().sum::<u64>(),
2509                    failed_queries: 0u64,
2510                    average_success_duration_micros: u64::try_from(
2511                        QUERY_DURATION.into_micros()
2512                    ).unwrap(),
2513                    errors: {
2514                        Message: 0u64,
2515                        NoConnections: 0u64,
2516                        NoRecordsFoundResponseCodeCounts: {},
2517                        IoErrorCounts: {},
2518                        ProtoErrorCounts: {},
2519                        Timeout: 0u64,
2520                        UnhandledResolveErrorKindCounts: {},
2521                    },
2522                    expected_address_counts,
2523                },
2524            },
2525        });
2526    }
2527
2528    #[test]
2529    fn test_query_stats_inspect_oldest_stats_erased() {
2530        let mut exec = fasync::TestExecutor::new_with_fake_time();
2531        const START_NANOS: i64 = 1_234_567;
2532        exec.set_fake_time(fasync::MonotonicInstant::from_nanos(START_NANOS));
2533
2534        let stats = Arc::new(QueryStats::new());
2535        let inspector = fuchsia_inspect::Inspector::default();
2536        let _query_stats_inspect_node = add_query_stats_inspect(inspector.root(), stats.clone());
2537        const DELAY: zx::MonotonicDuration = zx::MonotonicDuration::from_millis(100);
2538        for _ in 0..STAT_WINDOW_COUNT {
2539            let () =
2540                run_fake_lookup(&mut exec, stats.clone(), Err(&ResolveErrorKind::Timeout), DELAY);
2541            let () =
2542                exec.set_fake_time(fasync::MonotonicInstant::after(STAT_WINDOW_DURATION - DELAY));
2543        }
2544        for _ in 0..STAT_WINDOW_COUNT {
2545            run_fake_lookup(&mut exec, stats.clone(), Ok(/*addresses*/ NON_ZERO_USIZE_ONE), DELAY);
2546            let () =
2547                exec.set_fake_time(fasync::MonotonicInstant::after(STAT_WINDOW_DURATION - DELAY));
2548        }
2549        // All the failed queries should be erased from the stats as they are
2550        // now out of date.
2551        let mut expected = tree_assertion!(query_stats: {});
2552        let start_offset = START_NANOS
2553            + DELAY.into_nanos()
2554            + STAT_WINDOW_DURATION.into_nanos() * i64::try_from(STAT_WINDOW_COUNT).unwrap();
2555        for i in 0..STAT_WINDOW_COUNT {
2556            let name = &format!("window {}", i + 1);
2557            let child = tree_assertion!(var name: {
2558                start_time_nanos: u64::try_from(
2559                    start_offset + STAT_WINDOW_DURATION.into_nanos() * i64::try_from(i).unwrap()
2560                ).unwrap(),
2561                successful_queries: 1u64,
2562                failed_queries: 0u64,
2563                average_success_duration_micros: u64::try_from(DELAY.into_micros()).unwrap(),
2564                errors: {
2565                    Message: 0u64,
2566                    NoConnections: 0u64,
2567                    NoRecordsFoundResponseCodeCounts: {},
2568                    IoErrorCounts: {},
2569                    ProtoErrorCounts: {},
2570                    Timeout: 0u64,
2571                    UnhandledResolveErrorKindCounts: {},
2572                },
2573                address_counts: {
2574                    "1": {
2575                        count: 1u64
2576                    },
2577                },
2578            });
2579            expected.add_child_assertion(child);
2580        }
2581        assert_data_tree!(@executor exec, inspector, root: {
2582            expected,
2583        });
2584    }
2585
2586    struct BlockingResolver {}
2587
2588    impl ResolverLookup for BlockingResolver {
2589        fn new(_config: ResolverConfig, _options: ResolverOpts) -> Self {
2590            BlockingResolver {}
2591        }
2592
2593        async fn lookup<N: IntoName + Send>(
2594            &self,
2595            _name: N,
2596            _record_type: RecordType,
2597        ) -> Result<lookup::Lookup, ResolveError> {
2598            futures::future::pending().await
2599        }
2600
2601        async fn reverse_lookup(
2602            &self,
2603            _addr: IpAddr,
2604        ) -> Result<lookup::ReverseLookup, ResolveError> {
2605            panic!("BlockingResolver does not handle reverse lookup")
2606        }
2607    }
2608
2609    #[fuchsia::test(logging = false)]
2610    async fn test_parallel_query_limit() {
2611        // Collect requests by setting up a FIDL proxy and stream for the Lookup
2612        // protocol, because there isn't a good way to directly construct fake
2613        // requests to be used for testing.
2614        let requests = {
2615            let (name_lookup_proxy, name_lookup_stream) =
2616                fidl::endpoints::create_proxy_and_stream::<fname::LookupMarker>();
2617            const NUM_REQUESTS: usize = MAX_PARALLEL_REQUESTS * 2 + 2;
2618            for _ in 0..NUM_REQUESTS {
2619                // Don't await on this future because we are using these
2620                // requests to collect FIDL responders in order to send test
2621                // requests later, and will not respond to these requests.
2622                let _: fidl::client::QueryResponseFut<fname::LookupLookupIpResult> =
2623                    name_lookup_proxy.lookup_ip(
2624                        LOCAL_HOST,
2625                        &fname::LookupIpOptions {
2626                            ipv4_lookup: Some(true),
2627                            ipv6_lookup: Some(true),
2628                            ..Default::default()
2629                        },
2630                    );
2631            }
2632            // Terminate the stream so its items can be collected below.
2633            drop(name_lookup_proxy);
2634            let requests = name_lookup_stream
2635                .map(|request| match request.expect("channel error") {
2636                    LookupRequest::LookupIp { hostname, options, responder } => {
2637                        IpLookupRequest { hostname, options, responder }
2638                    }
2639                    req => panic!("Expected LookupRequest::LookupIp request, found {:?}", req),
2640                })
2641                .collect::<Vec<_>>()
2642                .await;
2643            assert_eq!(requests.len(), NUM_REQUESTS);
2644            requests
2645        };
2646
2647        let (mut sender, recv) = mpsc::channel(MAX_PARALLEL_REQUESTS);
2648
2649        // The channel's capacity is equal to buffer + num-senders. Thus the
2650        // channel has a capacity of `MAX_PARALLEL_REQUESTS` + 1, and the
2651        // `for_each_concurrent` future has a limit of `MAX_PARALLEL_REQUESTS`,
2652        // so the sender should be able to queue `MAX_PARALLEL_REQUESTS` * 2 + 1
2653        // requests before `send` fails.
2654        const BEFORE_LAST_INDEX: usize = MAX_PARALLEL_REQUESTS * 2;
2655        const LAST_INDEX: usize = MAX_PARALLEL_REQUESTS * 2 + 1;
2656        let mut send_fut = pin!(
2657            async {
2658                for (i, req) in requests.into_iter().enumerate() {
2659                    match i {
2660                        BEFORE_LAST_INDEX => assert_matches!(sender.try_send(req), Ok(())),
2661                        LAST_INDEX => assert_matches!(sender.try_send(req), Err(e) if e.is_full()),
2662                        _ => assert_matches!(sender.send(req).await, Ok(())),
2663                    }
2664                }
2665            }
2666            .fuse()
2667        );
2668        let mut recv_fut = pin!({
2669            let resolver = SharedResolver::new(BlockingResolver::new(
2670                ResolverConfig::default(),
2671                ResolverOpts::default(),
2672            ));
2673            let stats = Arc::new(QueryStats::new());
2674            let (routes_proxy, _routes_stream) =
2675                fidl::endpoints::create_proxy_and_stream::<fnet_routes::StateMarker>();
2676            async move { create_ip_lookup_fut(&resolver, stats.clone(), routes_proxy, recv).await }
2677                .fuse()
2678        });
2679        futures::select! {
2680            () = send_fut => {},
2681            () = recv_fut => panic!("recv_fut should never complete"),
2682        };
2683    }
2684
2685    #[test]
2686    fn test_failure_stats() {
2687        use anyhow::anyhow;
2688        use trust_dns_proto::error::ProtoError;
2689        use trust_dns_proto::op::Query;
2690
2691        let mut stats = FailureStats::default();
2692        for (error_kind, expected) in &[
2693            (ResolveErrorKind::Message("foo"), FailureStats { message: 1, ..Default::default() }),
2694            (
2695                ResolveErrorKind::Msg("foo".to_string()),
2696                FailureStats { message: 2, ..Default::default() },
2697            ),
2698            (
2699                ResolveErrorKind::NoRecordsFound {
2700                    query: Box::new(Query::default()),
2701                    soa: None,
2702                    negative_ttl: None,
2703                    response_code: ResponseCode::Refused,
2704                    trusted: false,
2705                },
2706                FailureStats {
2707                    message: 2,
2708                    no_records_found: NoRecordsFoundStats {
2709                        response_code_counts: [(ResponseCode::Refused.into(), 1)].into(),
2710                    },
2711                    ..Default::default()
2712                },
2713            ),
2714            (
2715                ResolveErrorKind::Io(std::io::Error::new(
2716                    std::io::ErrorKind::NotFound,
2717                    anyhow!("foo"),
2718                )),
2719                FailureStats {
2720                    message: 2,
2721                    no_records_found: NoRecordsFoundStats {
2722                        response_code_counts: [(ResponseCode::Refused.into(), 1)].into(),
2723                    },
2724                    io: IoErrorStats([(std::io::ErrorKind::NotFound, 1)].into()),
2725                    ..Default::default()
2726                },
2727            ),
2728            (
2729                ResolveErrorKind::Proto(ProtoError::from("foo")),
2730                FailureStats {
2731                    message: 2,
2732                    no_records_found: NoRecordsFoundStats {
2733                        response_code_counts: [(ResponseCode::Refused.into(), 1)].into(),
2734                    },
2735                    io: IoErrorStats([(std::io::ErrorKind::NotFound, 1)].into()),
2736                    proto: GenericErrorKindStats([(String::from("Message"), 1)].into()),
2737                    ..Default::default()
2738                },
2739            ),
2740            (
2741                ResolveErrorKind::NoConnections,
2742                FailureStats {
2743                    message: 2,
2744                    no_connections: 1,
2745                    no_records_found: NoRecordsFoundStats {
2746                        response_code_counts: [(ResponseCode::Refused.into(), 1)].into(),
2747                    },
2748                    io: IoErrorStats([(std::io::ErrorKind::NotFound, 1)].into()),
2749                    proto: GenericErrorKindStats([(String::from("Message"), 1)].into()),
2750                    ..Default::default()
2751                },
2752            ),
2753            (
2754                ResolveErrorKind::Timeout,
2755                FailureStats {
2756                    message: 2,
2757                    no_connections: 1,
2758                    no_records_found: NoRecordsFoundStats {
2759                        response_code_counts: [(ResponseCode::Refused.into(), 1)].into(),
2760                    },
2761                    io: IoErrorStats([(std::io::ErrorKind::NotFound, 1)].into()),
2762                    proto: GenericErrorKindStats([(String::from("Message"), 1)].into()),
2763                    timeout: 1,
2764                    unhandled_resolve_error_kind: Default::default(),
2765                },
2766            ),
2767            (
2768                ResolveErrorKind::NoRecordsFound {
2769                    query: Box::new(Query::default()),
2770                    soa: None,
2771                    negative_ttl: None,
2772                    response_code: ResponseCode::NXDomain,
2773                    trusted: false,
2774                },
2775                FailureStats {
2776                    message: 2,
2777                    no_connections: 1,
2778                    no_records_found: NoRecordsFoundStats {
2779                        response_code_counts: [
2780                            (ResponseCode::NXDomain.into(), 1),
2781                            (ResponseCode::Refused.into(), 1),
2782                        ]
2783                        .into(),
2784                    },
2785                    io: IoErrorStats([(std::io::ErrorKind::NotFound, 1)].into()),
2786                    proto: GenericErrorKindStats([(String::from("Message"), 1)].into()),
2787                    timeout: 1,
2788                    unhandled_resolve_error_kind: Default::default(),
2789                },
2790            ),
2791            (
2792                ResolveErrorKind::NoRecordsFound {
2793                    query: Box::new(Query::default()),
2794                    soa: None,
2795                    negative_ttl: None,
2796                    response_code: ResponseCode::NXDomain,
2797                    trusted: false,
2798                },
2799                FailureStats {
2800                    message: 2,
2801                    no_connections: 1,
2802                    no_records_found: NoRecordsFoundStats {
2803                        response_code_counts: [
2804                            (ResponseCode::NXDomain.into(), 2),
2805                            (ResponseCode::Refused.into(), 1),
2806                        ]
2807                        .into(),
2808                    },
2809                    io: IoErrorStats([(std::io::ErrorKind::NotFound, 1)].into()),
2810                    proto: GenericErrorKindStats([(String::from("Message"), 1)].into()),
2811                    timeout: 1,
2812                    unhandled_resolve_error_kind: Default::default(),
2813                },
2814            ),
2815            (
2816                ResolveErrorKind::Proto(ProtoError::from(std::io::Error::new(
2817                    std::io::ErrorKind::ConnectionAborted,
2818                    anyhow!("foo"),
2819                ))),
2820                FailureStats {
2821                    message: 2,
2822                    no_connections: 1,
2823                    no_records_found: NoRecordsFoundStats {
2824                        response_code_counts: [
2825                            (ResponseCode::NXDomain.into(), 2),
2826                            (ResponseCode::Refused.into(), 1),
2827                        ]
2828                        .into(),
2829                    },
2830                    io: IoErrorStats(
2831                        [
2832                            (std::io::ErrorKind::NotFound, 1),
2833                            (std::io::ErrorKind::ConnectionAborted, 1),
2834                        ]
2835                        .into(),
2836                    ),
2837                    proto: GenericErrorKindStats([(String::from("Message"), 1)].into()),
2838                    timeout: 1,
2839                    unhandled_resolve_error_kind: Default::default(),
2840                },
2841            ),
2842        ][..]
2843        {
2844            stats.increment(error_kind);
2845            assert_eq!(&stats, expected, "invalid stats after incrementing with {:?}", error_kind);
2846        }
2847    }
2848
2849    fn test_das_helper(
2850        l_addr: fnet::IpAddress,
2851        l_src: Option<fnet::IpAddress>,
2852        r_addr: fnet::IpAddress,
2853        r_src: Option<fnet::IpAddress>,
2854        want: std::cmp::Ordering,
2855    ) {
2856        let left = DasCmpInfo::from_addrs(&l_addr, l_src.as_ref());
2857        let right = DasCmpInfo::from_addrs(&r_addr, r_src.as_ref());
2858        assert_eq!(
2859            left.cmp(&right),
2860            want,
2861            "want = {:?}\n left = {:?}({:?}) DAS={:?}\n right = {:?}({:?}) DAS={:?}",
2862            want,
2863            l_addr,
2864            l_src,
2865            left,
2866            r_addr,
2867            r_src,
2868            right
2869        );
2870    }
2871
2872    macro_rules! add_das_test {
2873        ($name:ident, preferred: $pref_dst:expr => $pref_src:expr, other: $other_dst:expr => $other_src:expr) => {
2874            #[test]
2875            fn $name() {
2876                test_das_helper(
2877                    $pref_dst,
2878                    $pref_src,
2879                    $other_dst,
2880                    $other_src,
2881                    std::cmp::Ordering::Less,
2882                )
2883            }
2884        };
2885    }
2886
2887    add_das_test!(
2888        prefer_reachable,
2889        preferred: fidl_ip!("198.51.100.121") => Some(fidl_ip!("198.51.100.117")),
2890        other: fidl_ip!("2001:db8:1::1") => Option::<fnet::IpAddress>::None
2891    );
2892
2893    // These test cases are taken from RFC 6724, section 10.2.
2894
2895    add_das_test!(
2896        prefer_matching_scope,
2897        preferred: fidl_ip!("198.51.100.121") => Some(fidl_ip!("198.51.100.117")),
2898        other: fidl_ip!("2001:db8:1::1") => Some(fidl_ip!("fe80::1"))
2899    );
2900
2901    add_das_test!(
2902        prefer_matching_label,
2903        preferred: fidl_ip!("2002:c633:6401::1") => Some(fidl_ip!("2002:c633:6401::2")),
2904        other:  fidl_ip!("2001:db8:1::1") => Some(fidl_ip!("2002:c633:6401::2"))
2905    );
2906
2907    add_das_test!(
2908        prefer_higher_precedence_1,
2909        preferred: fidl_ip!("2001:db8:1::1") => Some(fidl_ip!("2001:db8:1::2")),
2910        other: fidl_ip!("10.1.2.3") => Some(fidl_ip!("10.1.2.4"))
2911    );
2912
2913    add_das_test!(
2914        prefer_higher_precedence_2,
2915        preferred: fidl_ip!("2001:db8:1::1") => Some(fidl_ip!("2001:db8:1::2")),
2916        other: fidl_ip!("2002:c633:6401::1") => Some(fidl_ip!("2002:c633:6401::2"))
2917    );
2918
2919    add_das_test!(
2920        prefer_smaller_scope,
2921        preferred: fidl_ip!("fe80::1") => Some(fidl_ip!("fe80::2")),
2922        other: fidl_ip!("2001:db8:1::1") => Some(fidl_ip!("2001:db8:1::2"))
2923    );
2924
2925    add_das_test!(
2926        prefer_longest_matching_prefix,
2927        preferred: fidl_ip!("2001:db8:1::1") => Some(fidl_ip!("2001:db8:1::2")),
2928        other: fidl_ip!("2001:db8:3ffe::1") => Some(fidl_ip!("2001:db8:3f44::2"))
2929    );
2930
2931    #[test]
2932    fn test_das_equals() {
2933        for (dst, src) in [
2934            (fidl_ip!("192.168.0.1"), fidl_ip!("192.168.0.2")),
2935            (fidl_ip!("2001:db8::1"), fidl_ip!("2001:db8::2")),
2936        ]
2937        .iter()
2938        {
2939            test_das_helper(*dst, None, *dst, None, std::cmp::Ordering::Equal);
2940            test_das_helper(*dst, Some(*src), *dst, Some(*src), std::cmp::Ordering::Equal);
2941        }
2942    }
2943
2944    #[test]
2945    fn test_valid_policy_table() {
2946        // Last element in policy table MUST be ::/0.
2947        assert_eq!(
2948            POLICY_TABLE.iter().last().expect("empty policy table").prefix,
2949            net_types::ip::Ipv6::ALL_ADDRS_SUBNET
2950        );
2951        // Policy table must be sorted by prefix length.
2952        POLICY_TABLE.array_windows().for_each(|[w0, w1]| {
2953            let Policy { prefix: cur, precedence: _, label: _ } = w0;
2954            let Policy { prefix: nxt, precedence: _, label: _ } = w1;
2955            assert!(
2956                cur.prefix() >= nxt.prefix(),
2957                "bad ordering of prefixes, {} must come after {}",
2958                cur,
2959                nxt
2960            )
2961        });
2962        // Assert that POLICY_TABLE declaration does not use any invalid
2963        // subnets.
2964        for policy in POLICY_TABLE.iter() {
2965            assert!(policy.prefix.prefix() <= 128, "Invalid subnet in policy {:?}", policy);
2966        }
2967    }
2968
2969    #[fuchsia::test(logging = false)]
2970    async fn test_sort_preferred_addresses() {
2971        const TEST_IPS: [(fnet::IpAddress, Option<fnet::IpAddress>); 5] = [
2972            (fidl_ip!("127.0.0.1"), Some(fidl_ip!("127.0.0.1"))),
2973            (fidl_ip!("::1"), Some(fidl_ip!("::1"))),
2974            (fidl_ip!("192.168.50.22"), None),
2975            (fidl_ip!("2001::2"), None),
2976            (fidl_ip!("2001:db8:1::1"), Some(fidl_ip!("2001:db8:1::2"))),
2977        ];
2978        // Declared using std types so we get cleaner output when we assert
2979        // expectations.
2980        const SORTED: [IpAddr; 5] = [
2981            std_ip!("::1"),
2982            std_ip!("2001:db8:1::1"),
2983            std_ip!("127.0.0.1"),
2984            std_ip!("192.168.50.22"),
2985            std_ip!("2001::2"),
2986        ];
2987        let (routes_proxy, routes_stream) =
2988            fidl::endpoints::create_proxy_and_stream::<fnet_routes::StateMarker>();
2989        let routes_fut =
2990            routes_stream.map(|r| r.context("stream FIDL error")).try_for_each(|req| {
2991                let (destination, responder) = assert_matches!(
2992                    req,
2993                    fnet_routes::StateRequest::Resolve { destination, responder }
2994                        => (destination, responder)
2995                );
2996                let result = TEST_IPS
2997                    .iter()
2998                    .enumerate()
2999                    .find_map(|(i, (dst, src))| {
3000                        if *dst == destination && src.is_some() {
3001                            let inner = fnet_routes::Destination {
3002                                address: Some(*dst),
3003                                source_address: *src,
3004                                ..Default::default()
3005                            };
3006                            // Send both Direct and Gateway resolved routes to show we
3007                            // don't care about that part.
3008                            if i % 2 == 0 {
3009                                Some(fnet_routes::Resolved::Direct(inner))
3010                            } else {
3011                                Some(fnet_routes::Resolved::Gateway(inner))
3012                            }
3013                        } else {
3014                            None
3015                        }
3016                    })
3017                    .ok_or_else(|| zx::Status::ADDRESS_UNREACHABLE.into_raw());
3018                futures::future::ready(
3019                    responder
3020                        .send(result.as_ref().map_err(|e| *e))
3021                        .context("failed to send Resolve response"),
3022                )
3023            });
3024
3025        let ((), ()) = futures::future::try_join(routes_fut, async move {
3026            let addrs = TEST_IPS.iter().map(|(dst, _src)| *dst).collect();
3027            let addrs = sort_preferred_addresses(addrs, &routes_proxy)
3028                .await
3029                .expect("failed to sort addresses");
3030            let addrs = addrs
3031                .into_iter()
3032                .map(|a| {
3033                    let net_ext::IpAddress(a) = a.into();
3034                    a
3035                })
3036                .collect::<Vec<_>>();
3037            assert_eq!(&addrs[..], &SORTED[..]);
3038            Ok(())
3039        })
3040        .await
3041        .expect("error running futures");
3042    }
3043
3044    #[fuchsia::test(logging = false)]
3045    async fn test_lookupip() {
3046        // Routes handler will say that only IPV6_HOST is reachable.
3047        let routes_handler = |req| {
3048            let (destination, responder) = assert_matches!(
3049                req,
3050                fnet_routes::StateRequest::Resolve { destination, responder }
3051                    => (destination, responder)
3052            );
3053            let resolved;
3054            let response = if destination == map_ip(IPV6_HOST) {
3055                resolved = fnet_routes::Resolved::Direct(fnet_routes::Destination {
3056                    address: Some(destination),
3057                    source_address: Some(destination),
3058                    ..Default::default()
3059                });
3060                Ok(&resolved)
3061            } else {
3062                Err(zx::Status::ADDRESS_UNREACHABLE.into_raw())
3063            };
3064            responder.send(response).expect("failed to send Resolve FIDL response");
3065        };
3066        TestEnvironment::default()
3067            .run_lookup_with_routes_handler(
3068                |proxy| async move {
3069                    // All arguments unset.
3070                    assert_eq!(
3071                        proxy
3072                            .lookup_ip(REMOTE_IPV4_HOST, &fname::LookupIpOptions::default())
3073                            .await
3074                            .expect("lookup_ip"),
3075                        Err(fname::LookupError::InvalidArgs)
3076                    );
3077                    // No IP addresses to look.
3078                    assert_eq!(
3079                        proxy
3080                            .lookup_ip(
3081                                REMOTE_IPV4_HOST,
3082                                &fname::LookupIpOptions {
3083                                    ipv4_lookup: Some(false),
3084                                    ipv6_lookup: Some(false),
3085                                    ..Default::default()
3086                                }
3087                            )
3088                            .await
3089                            .expect("lookup_ip"),
3090                        Err(fname::LookupError::InvalidArgs)
3091                    );
3092                    // No results for an IPv4 only host.
3093                    assert_eq!(
3094                        proxy
3095                            .lookup_ip(
3096                                REMOTE_IPV4_HOST,
3097                                &fname::LookupIpOptions {
3098                                    ipv4_lookup: Some(false),
3099                                    ipv6_lookup: Some(true),
3100                                    ..Default::default()
3101                                }
3102                            )
3103                            .await
3104                            .expect("lookup_ip"),
3105                        Err(fname::LookupError::NotFound)
3106                    );
3107                    // Successfully resolve IPv4.
3108                    assert_eq!(
3109                        proxy
3110                            .lookup_ip(
3111                                REMOTE_IPV4_HOST,
3112                                &fname::LookupIpOptions {
3113                                    ipv4_lookup: Some(true),
3114                                    ipv6_lookup: Some(true),
3115                                    ..Default::default()
3116                                }
3117                            )
3118                            .await
3119                            .expect("lookup_ip"),
3120                        Ok(fname::LookupResult {
3121                            addresses: Some(vec![map_ip(IPV4_HOST)]),
3122                            ..Default::default()
3123                        })
3124                    );
3125                    // Successfully resolve IPv4 + IPv6 (no sorting).
3126                    assert_eq!(
3127                        proxy
3128                            .lookup_ip(
3129                                REMOTE_IPV4_IPV6_HOST,
3130                                &fname::LookupIpOptions {
3131                                    ipv4_lookup: Some(true),
3132                                    ipv6_lookup: Some(true),
3133                                    ..Default::default()
3134                                }
3135                            )
3136                            .await
3137                            .expect("lookup_ip"),
3138                        Ok(fname::LookupResult {
3139                            addresses: Some(vec![map_ip(IPV4_HOST), map_ip(IPV6_HOST)]),
3140                            ..Default::default()
3141                        })
3142                    );
3143                    // Successfully resolve IPv4 + IPv6 (with sorting).
3144                    assert_eq!(
3145                        proxy
3146                            .lookup_ip(
3147                                REMOTE_IPV4_IPV6_HOST,
3148                                &fname::LookupIpOptions {
3149                                    ipv4_lookup: Some(true),
3150                                    ipv6_lookup: Some(true),
3151                                    sort_addresses: Some(true),
3152                                    ..Default::default()
3153                                }
3154                            )
3155                            .await
3156                            .expect("lookup_ip"),
3157                        Ok(fname::LookupResult {
3158                            addresses: Some(vec![map_ip(IPV6_HOST), map_ip(IPV4_HOST)]),
3159                            ..Default::default()
3160                        })
3161                    );
3162                },
3163                routes_handler,
3164            )
3165            .await
3166    }
3167}