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::fidl_ip_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    ($ip:tt/$prefix:expr => $precedence:expr, $label:expr) => {
691        Policy {
692            // Unsafe allows us to declare constant subnets.
693            // We make sure no invalid subnets are created in
694            // test_valid_policy_table.
695            prefix: unsafe {
696                net_types::ip::Subnet::new_unchecked(
697                    net_types::ip::Ipv6Addr::from_bytes(fidl_ip_v6!($ip).addr),
698                    $prefix,
699                )
700            },
701            precedence: $precedence,
702            label: $label,
703        }
704    };
705}
706
707/// Policy table is defined in RFC 6724, section 2.1
708///
709/// A more human-readable version:
710///
711///  Prefix        Precedence Label
712///  ::1/128               50     0
713///  ::/0                  40     1
714///  ::ffff:0:0/96         35     4
715///  2002::/16             30     2
716///  2001::/32              5     5
717///  fc00::/7               3    13
718///  ::/96                  1     3
719///  fec0::/10              1    11
720///  3ffe::/16              1    12
721///
722/// We willingly left out ::/96, fec0::/10, 3ffe::/16 since those prefix
723/// assignments are deprecated.
724///
725/// The table is sorted by prefix length so longest-prefix match can be easily
726/// achieved.
727const POLICY_TABLE: [Policy; 6] = [
728    decl_policy!("::1"/128 => 50, 0),
729    decl_policy!("::ffff:0:0"/96 => 35, 4),
730    decl_policy!("2001::"/32 => 5, 5),
731    decl_policy!("2002::"/16 => 30, 2),
732    decl_policy!("fc00::"/7 => 3, 13),
733    decl_policy!("::"/0 => 40, 1),
734];
735
736fn policy_lookup(addr: &net_types::ip::Ipv6Addr) -> &'static Policy {
737    POLICY_TABLE
738        .iter()
739        .find(|policy| policy.prefix.contains(addr))
740        .expect("policy table MUST contain the all addresses subnet")
741}
742
743/// Destination Address selection information.
744///
745/// `DasCmpInfo` provides an implementation of a subset of Destination Address
746/// Selection according to the sorting rules defined in [RFC 6724 Section 6].
747///
748/// TODO(https://fxbug.dev/42143905): Implement missing rules 3, 4, and 7.
749/// Rules 3, 4, and 7 are omitted for compatibility with the equivalent
750/// implementation in Fuchsia's libc.
751///
752/// `DasCmpInfo` provides an [`std::cmp::Ord`] implementation that will return
753/// preferred addresses as "lesser" values.
754///
755/// [RFC 6724 Section 6]: https://tools.ietf.org/html/rfc6724#section-6
756#[derive(Debug)]
757struct DasCmpInfo {
758    usable: bool,
759    matching_scope: bool,
760    matching_label: bool,
761    precedence: usize,
762    scope: net_types::ip::Ipv6Scope,
763    common_prefix_len: u8,
764}
765
766impl DasCmpInfo {
767    /// Helper function to convert a FIDL IP address into
768    /// [`net_types::ip::Ipv6Addr`], using a mapped IPv4 when that's the case.
769    fn convert_addr(fidl: &fnet::IpAddress) -> net_types::ip::Ipv6Addr {
770        match fidl {
771            fnet::IpAddress::Ipv4(fnet::Ipv4Address { addr }) => {
772                net_types::ip::Ipv6Addr::from(net_types::ip::Ipv4Addr::new(*addr))
773            }
774            fnet::IpAddress::Ipv6(fnet::Ipv6Address { addr }) => {
775                net_types::ip::Ipv6Addr::from_bytes(*addr)
776            }
777        }
778    }
779
780    fn from_addrs(dst_addr: &fnet::IpAddress, src_addr: Option<&fnet::IpAddress>) -> Self {
781        use net_types::ScopeableAddress;
782
783        let dst_addr = Self::convert_addr(dst_addr);
784        let Policy { prefix: _, precedence, label: dst_label } = policy_lookup(&dst_addr);
785        let (usable, matching_scope, matching_label, common_prefix_len) = match src_addr {
786            Some(src_addr) => {
787                let src_addr = Self::convert_addr(src_addr);
788                let Policy { prefix: _, precedence: _, label: src_label } =
789                    policy_lookup(&src_addr);
790                (
791                    true,
792                    dst_addr.scope() == src_addr.scope(),
793                    dst_label == src_label,
794                    dst_addr.common_prefix_len(&src_addr),
795                )
796            }
797            None => (false, false, false, 0),
798        };
799        DasCmpInfo {
800            usable,
801            matching_scope,
802            matching_label,
803            precedence: *precedence,
804            scope: dst_addr.scope(),
805            common_prefix_len,
806        }
807    }
808}
809
810impl std::cmp::Ord for DasCmpInfo {
811    // TODO(https://fxbug.dev/42143905): Implement missing rules 3, 4, and 7.
812    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
813        use std::cmp::Ordering;
814        let DasCmpInfo {
815            usable: self_usable,
816            matching_scope: self_matching_scope,
817            matching_label: self_matching_label,
818            precedence: self_precedence,
819            scope: self_scope,
820            common_prefix_len: self_common_prefix_len,
821        } = self;
822        let DasCmpInfo {
823            usable: other_usable,
824            matching_scope: other_matching_scope,
825            matching_label: other_matching_label,
826            precedence: other_precedence,
827            scope: other_scope,
828            common_prefix_len: other_common_prefix_len,
829        } = other;
830
831        fn prefer_true(left: bool, right: bool) -> Ordering {
832            match (left, right) {
833                (true, false) => Ordering::Less,
834                (false, true) => Ordering::Greater,
835                (false, false) | (true, true) => Ordering::Equal,
836            }
837        }
838
839        // Rule 1: Avoid unusable destinations.
840        prefer_true(*self_usable, *other_usable)
841            .then(
842                // Rule 2: Prefer matching scope.
843                prefer_true(*self_matching_scope, *other_matching_scope),
844            )
845            .then(
846                // Rule 5: Prefer matching label.
847                prefer_true(*self_matching_label, *other_matching_label),
848            )
849            .then(
850                // Rule 6: Prefer higher precedence.
851                self_precedence.cmp(other_precedence).reverse(),
852            )
853            .then(
854                // Rule 8: Prefer smaller scope.
855                self_scope.multicast_scope_id().cmp(&other_scope.multicast_scope_id()),
856            )
857            .then(
858                // Rule 9: Use longest matching prefix.
859                self_common_prefix_len.cmp(other_common_prefix_len).reverse(),
860            )
861        // Rule 10: Otherwise, leave the order unchanged.
862    }
863}
864
865impl std::cmp::PartialOrd for DasCmpInfo {
866    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
867        Some(self.cmp(other))
868    }
869}
870
871impl std::cmp::PartialEq for DasCmpInfo {
872    fn eq(&self, other: &Self) -> bool {
873        self.cmp(other) == std::cmp::Ordering::Equal
874    }
875}
876
877impl std::cmp::Eq for DasCmpInfo {}
878
879async fn handle_lookup_hostname<T: ResolverLookup>(
880    resolver: &SharedResolver<T>,
881    addr: fnet::IpAddress,
882) -> Result<String, fname::LookupError> {
883    let net_ext::IpAddress(addr) = addr.into();
884    let resolver = resolver.read();
885
886    match resolver.reverse_lookup(addr).await {
887        Ok(response) => {
888            response.iter().next().ok_or(fname::LookupError::NotFound).map(ToString::to_string)
889        }
890        Err(error) => Err(handle_err("LookupHostname", error)),
891    }
892}
893
894struct IpLookupRequest {
895    hostname: String,
896    options: fname::LookupIpOptions,
897    responder: fname::LookupLookupIpResponder,
898}
899
900async fn run_lookup<T: ResolverLookup>(
901    resolver: &SharedResolver<T>,
902    stream: LookupRequestStream,
903    sender: mpsc::Sender<IpLookupRequest>,
904) -> Result<(), fidl::Error> {
905    stream
906        .try_for_each_concurrent(None, |request| async {
907            match request {
908                LookupRequest::LookupIp { hostname, options, responder } => {
909                    sender
910                        .clone()
911                        .send(IpLookupRequest { hostname, options, responder })
912                        .await
913                        .expect("receiver should not be closed");
914                    Ok(())
915                }
916                LookupRequest::LookupHostname { addr, responder } => responder
917                    .send(handle_lookup_hostname(&resolver, addr).await.as_deref().map_err(|e| *e)),
918            }
919        })
920        .await
921}
922
923const MAX_PARALLEL_REQUESTS: usize = 256;
924
925fn create_ip_lookup_fut<T: ResolverLookup>(
926    resolver: &SharedResolver<T>,
927    stats: Arc<QueryStats>,
928    routes: fnet_routes::StateProxy,
929    recv: mpsc::Receiver<IpLookupRequest>,
930) -> impl futures::Future<Output = ()> + '_ {
931    recv.for_each_concurrent(
932        MAX_PARALLEL_REQUESTS,
933        move |IpLookupRequest { hostname, options, responder }| {
934            let stats = stats.clone();
935            let routes = routes.clone();
936            async move {
937                let fname::LookupIpOptions {
938                    ipv4_lookup,
939                    ipv6_lookup,
940                    sort_addresses,
941                    canonical_name_lookup,
942                    ..
943                } = options;
944                let ipv4_lookup = ipv4_lookup.unwrap_or(false);
945                let ipv6_lookup = ipv6_lookup.unwrap_or(false);
946                let sort_addresses = sort_addresses.unwrap_or(false);
947                let canonical_name_lookup = canonical_name_lookup.unwrap_or(false);
948                let lookup_result = (|| async {
949                    let hostname = hostname.as_str();
950                    // The [`IntoName`] implementation for &str does not
951                    // properly reject IPv4 addresses in accordance with RFC
952                    // 1123 section 2.1:
953                    //
954                    //   If a dotted-decimal number can be entered without such
955                    //   identifying delimiters, then a full syntactic check must be
956                    //   made, because a segment of a host domain name is now allowed
957                    //   to begin with a digit and could legally be entirely numeric
958                    //   (see Section 6.1.2.4).  However, a valid host name can never
959                    //   have the dotted-decimal form #.#.#.#, since at least the
960                    //   highest-level component label will be alphabetic.
961                    //
962                    // Thus we explicitly reject such input here.
963                    //
964                    // TODO(https://github.com/hickory-dns/hickory-dns/issues/1725):
965                    // Remove this when the implementation is sufficiently
966                    // strict.
967                    match IpAddr::from_str(hostname) {
968                        Ok(addr) => {
969                            let _: IpAddr = addr;
970                            return Err(fname::LookupError::InvalidArgs);
971                        }
972                        Err(std::net::AddrParseError { .. }) => {}
973                    };
974                    let resolver = resolver.read();
975                    let start_time = fasync::MonotonicInstant::now();
976                    let (ret1, ret2, ret3) = futures::future::join3(
977                        futures::future::OptionFuture::from(
978                            ipv4_lookup.then(|| {
979                                resolver
980                                    .lookup(hostname, RecordType::A)
981                                    .map_err(|e| (LookupIpErrorSource::Ipv4, e))
982                            }),
983                        ),
984                        futures::future::OptionFuture::from(
985                            ipv6_lookup.then(|| {
986                                resolver
987                                    .lookup(hostname, RecordType::AAAA)
988                                    .map_err(|e| (LookupIpErrorSource::Ipv6, e))
989                            }),
990                        ),
991                        futures::future::OptionFuture::from(
992                            canonical_name_lookup
993                                .then(|| {
994                                    resolver
995                                        .lookup(hostname, RecordType::CNAME)
996                                        .map_err(|e| (LookupIpErrorSource::CanonicalName, e))
997                                }),
998                        ),
999                    )
1000                    .await;
1001                    let result = [ret1, ret2, ret3];
1002                    if result.iter().all(Option::is_none) {
1003                        return Err(fname::LookupError::InvalidArgs);
1004                    }
1005                    let (addrs, cnames, error) =
1006                        result.into_iter().filter_map(std::convert::identity).fold(
1007                            (Vec::new(), Vec::new(), LookupIpErrorsFromSource::default()),
1008                            |(mut addrs, mut cnames, mut error), result| {
1009                                match result {
1010                                    Err((src, err)) => {
1011                                        error.accumulate(src, err);
1012                                    },
1013                                    Ok(lookup) => lookup.iter().for_each(|rdata| match rdata {
1014                                        RData::A(addr) if ipv4_lookup => addrs
1015                                            .push(net_ext::IpAddress(IpAddr::V4(*addr)).into()),
1016                                        RData::AAAA(addr) if ipv6_lookup => addrs
1017                                            .push(net_ext::IpAddress(IpAddr::V6(*addr)).into()),
1018                                        RData::CNAME(name) => {
1019                                            // CNAME records are known to be present with other
1020                                            // query types; avoid logging in that case.
1021                                            if canonical_name_lookup {
1022                                                cnames.push(name.to_utf8())
1023                                            }
1024                                        }
1025                                        rdata => {
1026                                            error!(
1027                                                "Lookup(_, {:?}) yielded unexpected record type: {}",
1028                                                options, rdata.to_record_type(),
1029                                            )
1030                                        }
1031                                    }),
1032                                };
1033                            (addrs, cnames, error)
1034                        });
1035                    let count = match NonZeroUsize::try_from(addrs.len() + cnames.len()) {
1036                        Ok(count) => Ok(count),
1037                        Err(std::num::TryFromIntError { .. }) => match error.any_error() {
1038                            None => {
1039                                // TODO(https://fxbug.dev/42062388): Remove this
1040                                // once Trust-DNS enforces that all responses
1041                                // with no records return a `NoRecordsFound`
1042                                // error.
1043                                //
1044                                // Note that returning here means that query
1045                                // stats for inspect will not get logged. This
1046                                // is ok since this case should be rare and is
1047                                // considered to be temporary. Moreover, the
1048                                // failed query counters are based on the
1049                                // `ResolverError::kind`, which isn't applicable
1050                                // here.
1051                                error!("resolver response unexpectedly contained no records \
1052                                        and no error. See https://fxbug.dev/42062388.");
1053                                return Err(fname::LookupError::NotFound);
1054                            },
1055                            Some(any_err) => {
1056                                Err(any_err)
1057                            }
1058                        }
1059                    };
1060                    stats
1061                        .finish_query(
1062                            start_time,
1063                            count.as_ref().copied().map_err(|e| e.kind()),
1064                        )
1065                        .await;
1066                    match count {
1067                        Ok(_) => {},
1068                        Err(_any_err) => {
1069                            // Handle all the errors instead of just the one used for stats.
1070                            return Err(error.handle());
1071                        }
1072                    }
1073                    let addrs = if sort_addresses {
1074                        sort_preferred_addresses(addrs, &routes).await?
1075                    } else {
1076                        addrs
1077                    };
1078                    let addrs = if addrs.len() > fname::MAX_ADDRESSES.into() {
1079                        warn!(
1080                            "Lookup(_, {:?}): {} addresses, truncating to {}",
1081                            options, addrs.len(), fname::MAX_ADDRESSES
1082                        );
1083                        let mut addrs = addrs;
1084                        addrs.truncate(fname::MAX_ADDRESSES.into());
1085                        addrs
1086                    } else {
1087                        addrs
1088                    };
1089                    // Per RFC 1034 section 3.6.2:
1090                    //
1091                    //   If a CNAME RR is present at a node, no other data should be present; this
1092                    //   ensures that the data for a canonical name and its aliases cannot be
1093                    //   different.  This rule also insures that a cached CNAME can be used without
1094                    //   checking with an authoritative server for other RR types.
1095                    if cnames.len() > 1 {
1096                        let cnames =
1097                            cnames.iter().fold(HashMap::<&str, usize>::new(), |mut acc, cname| {
1098                                *acc.entry(cname).or_default() += 1;
1099                                acc
1100                            });
1101                        warn!(
1102                            "Lookup(_, {:?}): multiple CNAMEs: {:?}",
1103                            options, cnames
1104                        )
1105                    }
1106                    let cname = {
1107                        let mut cnames = cnames;
1108                        cnames.pop()
1109                    };
1110                    Ok(fname::LookupResult {
1111                        addresses: Some(addrs),
1112                        canonical_name: cname,
1113                        ..Default::default()
1114                    })
1115                })()
1116                .await;
1117                responder.send(lookup_result.as_ref().map_err(|e| *e)).unwrap_or_else(|e|
1118                    warn!(
1119                        "failed to send IP lookup result due to FIDL error: {}",
1120                        e
1121                    )
1122                )
1123            }
1124        },
1125    )
1126}
1127
1128/// Serves `stream` and forwards received configurations to `sink`.
1129async fn run_lookup_admin<T: ResolverLookup>(
1130    resolver: &SharedResolver<T>,
1131    state: &dns::config::ServerConfigState,
1132    stream: LookupAdminRequestStream,
1133) -> Result<(), fidl::Error> {
1134    stream
1135        .try_for_each(|req| async {
1136            match req {
1137                LookupAdminRequest::SetDnsServers { servers, responder } => {
1138                    let response = match state.update_servers(servers) {
1139                        UpdateServersResult::Updated(servers) => {
1140                            update_resolver(resolver, servers);
1141                            Ok(())
1142                        }
1143                        UpdateServersResult::NoChange => Ok(()),
1144                        UpdateServersResult::InvalidsServers => {
1145                            Err(zx::Status::INVALID_ARGS.into_raw())
1146                        }
1147                    };
1148                    responder.send(response)?;
1149                }
1150                LookupAdminRequest::GetDnsServers { responder } => {
1151                    responder.send(&state.servers())?;
1152                }
1153            }
1154            Ok(())
1155        })
1156        .await
1157}
1158
1159/// Adds a [`dns::policy::ServerConfigState`] inspection child node to
1160/// `parent`.
1161fn add_config_state_inspect(
1162    parent: &fuchsia_inspect::Node,
1163    config_state: Arc<dns::config::ServerConfigState>,
1164) -> fuchsia_inspect::LazyNode {
1165    parent.create_lazy_child("servers", move || {
1166        let config_state = config_state.clone();
1167        async move {
1168            let srv = fuchsia_inspect::Inspector::default();
1169            let server_list = config_state.servers();
1170            for (i, server) in server_list.into_iter().enumerate() {
1171                srv.root().record_child(format!("{}", i), |child| {
1172                    let net_ext::SocketAddress(addr) = server.into();
1173                    child.record_string("address", format!("{}", addr));
1174                });
1175            }
1176            Ok(srv)
1177        }
1178        .boxed()
1179    })
1180}
1181
1182/// Adds a nameserver stats child node to `parent`.
1183fn add_name_server_stats_inspect<T>(
1184    parent: &fuchsia_inspect::Node,
1185    shared_resolver: SharedResolver<T>,
1186) -> fuchsia_inspect::LazyNode
1187where
1188    T: NameServerStatsProvider + Send + Sync + 'static,
1189{
1190    parent.create_lazy_child("name_servers", move || {
1191        let shared_resolver = shared_resolver.read();
1192        async move {
1193            let inspector = fuchsia_inspect::Inspector::default();
1194            for (
1195                i,
1196                NameServerStats { addr, proto, failures, successes, recent_errors, success_streak },
1197            ) in shared_resolver.name_server_stats().into_iter().enumerate()
1198            {
1199                inspector.root().record_child(format!("{i}"), |child| {
1200                    child.record_string("address", format!("{addr}"));
1201                    child.record_string("protocol", format!("{proto:?}"));
1202                    child.record_uint("successful_queries", successes as u64);
1203                    child.record_uint("failed_queries", failures as u64);
1204                    child.record_uint("success_streak", success_streak as u64);
1205
1206                    let failure_stats =
1207                        recent_errors.iter().fold(FailureStats::default(), |mut stats, error| {
1208                            stats.increment(error);
1209                            stats
1210                        });
1211                    child.record_child("errors", |errors| {
1212                        failure_stats.populate_inspect_node(&errors);
1213                    });
1214                });
1215            }
1216            Ok(inspector)
1217        }
1218        .boxed()
1219    })
1220}
1221
1222/// Adds a [`QueryStats`] inspection child node to `parent`.
1223fn add_query_stats_inspect(
1224    parent: &fuchsia_inspect::Node,
1225    stats: Arc<QueryStats>,
1226) -> fuchsia_inspect::LazyNode {
1227    parent.create_lazy_child("query_stats", move || {
1228        let stats = stats.clone();
1229        async move {
1230            let past_queries = &*stats.inner.lock().await;
1231            let node = fuchsia_inspect::Inspector::default();
1232            for (i, query_window) in past_queries.iter().enumerate() {
1233                node.root().record_child(format!("window {}", i + 1), |child| {
1234                    record_single_query_stats_node(child, query_window)
1235                });
1236            }
1237            Ok(node)
1238        }
1239        .boxed()
1240    })
1241}
1242
1243fn record_single_query_stats_node(
1244    node: &fuchsia_inspect::Node,
1245    QueryWindow {
1246        start,
1247        success_count,
1248        failure_count,
1249        success_elapsed_time,
1250        failure_elapsed_time,
1251        failure_stats,
1252        address_counts_histogram,
1253    }: &QueryWindow,
1254) {
1255    match u64::try_from(start.into_nanos()) {
1256        Ok(nanos) => {
1257            node.record_uint("start_time_nanos", nanos);
1258        }
1259        Err(e) => warn!(
1260            "error computing `start_time_nanos`: {:?}.into_nanos() from i64 -> u64 failed: {}",
1261            start, e
1262        ),
1263    }
1264    node.record_uint("successful_queries", *success_count);
1265    node.record_uint("failed_queries", *failure_count);
1266    let record_average = |name: &str, total: zx::MonotonicDuration, count: u64| {
1267        // Don't record an average if there are no stats.
1268        if count == 0 {
1269            return;
1270        }
1271        match u64::try_from(total.into_micros()) {
1272            Ok(micros) => node.record_uint(name, micros / count),
1273            Err(e) => warn!(
1274                "error computing `{}`: {:?}.into_micros() from i64 -> u64 failed: {}",
1275                name, success_elapsed_time, e
1276            ),
1277        }
1278    };
1279    record_average("average_success_duration_micros", *success_elapsed_time, *success_count);
1280    record_average("average_failure_duration_micros", *failure_elapsed_time, *failure_count);
1281
1282    node.record_child("errors", |errors| {
1283        failure_stats.populate_inspect_node(&errors);
1284    });
1285
1286    node.record_child("address_counts", |address_counts_node| {
1287        for (count, occurrences) in address_counts_histogram {
1288            address_counts_node.record_child(count.to_string(), |child| {
1289                child.record_uint("count", *occurrences);
1290            });
1291        }
1292    });
1293}
1294
1295// NB: We manually set tags so logs from trust-dns crates also get the same
1296// tags as opposed to only the crate path.
1297#[fuchsia::main(logging_tags = ["dns"])]
1298pub async fn main() -> Result<(), Error> {
1299    info!("starting");
1300
1301    let mut resolver_opts = ResolverOpts::default();
1302    // Resolver will query for A and AAAA in parallel for lookup_ip.
1303    resolver_opts.ip_strategy = LookupIpStrategy::Ipv4AndIpv6;
1304    let resolver = SharedResolver::new(
1305        Resolver::new(ResolverConfig::default(), resolver_opts, Spawner)
1306            .expect("failed to create resolver"),
1307    );
1308
1309    let config_state = Arc::new(dns::config::ServerConfigState::new());
1310    let stats = Arc::new(QueryStats::new());
1311
1312    let mut fs = ServiceFs::new_local();
1313
1314    let inspector = fuchsia_inspect::component::inspector();
1315    let _state_inspect_node = add_config_state_inspect(inspector.root(), config_state.clone());
1316    let _query_stats_inspect_node = add_query_stats_inspect(inspector.root(), stats.clone());
1317    let _name_server_stats_inspect_node =
1318        add_name_server_stats_inspect(inspector.root(), resolver.clone());
1319    let _inspect_server_task =
1320        inspect_runtime::publish(inspector, inspect_runtime::PublishOptions::default())
1321            .context("publish Inspect task")?;
1322
1323    let routes = fuchsia_component::client::connect_to_protocol::<fnet_routes::StateMarker>()
1324        .context("failed to connect to fuchsia.net.routes/State")?;
1325
1326    let _: &mut ServiceFsDir<'_, _> = fs
1327        .dir("svc")
1328        .add_fidl_service(IncomingRequest::Lookup)
1329        .add_fidl_service(IncomingRequest::LookupAdmin);
1330    let _: &mut ServiceFs<_> =
1331        fs.take_and_serve_directory_handle().context("failed to serve directory")?;
1332
1333    // Create a channel with buffer size `MAX_PARALLEL_REQUESTS`, which allows
1334    // request processing to always be fully saturated.
1335    let (sender, recv) = mpsc::channel(MAX_PARALLEL_REQUESTS);
1336    let serve_fut = fs.for_each_concurrent(None, |incoming_service| async {
1337        match incoming_service {
1338            IncomingRequest::Lookup(stream) => run_lookup(&resolver, stream, sender.clone())
1339                .await
1340                .unwrap_or_else(|e| warn!("run_lookup finished with error: {}", e)),
1341            IncomingRequest::LookupAdmin(stream) => {
1342                run_lookup_admin(&resolver, &config_state, stream)
1343                    .await
1344                    .unwrap_or_else(|e| error!("run_lookup_admin finished with error: {}", e))
1345            }
1346        }
1347    });
1348    let ip_lookup_fut = create_ip_lookup_fut(&resolver, stats.clone(), routes, recv);
1349
1350    // Failing to apply a scheduling role is not fatal. Issue a warning in case
1351    // DNS latency is important to a product and running at default priority is
1352    // insufficient.
1353    match fuchsia_scheduler::set_role_for_this_thread("fuchsia.networking.dns.resolver.main") {
1354        Ok(_) => info!("Applied scheduling role"),
1355        Err(err) => warn!("Failed to apply scheduling role: {}", err),
1356    };
1357
1358    let ((), ()) = futures::future::join(serve_fut, ip_lookup_fut).await;
1359    Ok(())
1360}
1361
1362#[cfg(test)]
1363mod tests {
1364    use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr};
1365    use std::pin::pin;
1366    use std::str::FromStr;
1367
1368    use assert_matches::assert_matches;
1369    use diagnostics_assertions::{
1370        NonZeroUintProperty, TreeAssertion, assert_data_tree, tree_assertion,
1371    };
1372    use dns::DEFAULT_PORT;
1373    use dns::test_util::*;
1374    use itertools::Itertools as _;
1375    use net_declare::{fidl_ip, std_ip, std_ip_v4, std_ip_v6, std_socket_addr};
1376    use net_types::ip::Ip as _;
1377    use test_case::test_case;
1378    use trust_dns_proto::op::Query;
1379    use trust_dns_proto::rr::{Name, Record};
1380    use trust_dns_resolver::config::Protocol;
1381    use trust_dns_resolver::lookup::{Lookup, ReverseLookup};
1382
1383    use super::*;
1384
1385    const IPV4_LOOPBACK: fnet::IpAddress = fidl_ip!("127.0.0.1");
1386    const IPV6_LOOPBACK: fnet::IpAddress = fidl_ip!("::1");
1387    const LOCAL_HOST: &str = "localhost.";
1388
1389    // IPv4 address returned by mock lookup.
1390    const IPV4_HOST: Ipv4Addr = std_ip_v4!("240.0.0.2");
1391    // IPv6 address returned by mock lookup.
1392    const IPV6_HOST: Ipv6Addr = std_ip_v6!("abcd::2");
1393
1394    // host which has IPv4 address only.
1395    const REMOTE_IPV4_HOST: &str = "www.foo.com";
1396    // host which has IPv6 address only.
1397    const REMOTE_IPV6_HOST: &str = "www.bar.com";
1398    const REMOTE_IPV4_HOST_ALIAS: &str = "www.alsofoo.com";
1399    const REMOTE_IPV6_HOST_ALIAS: &str = "www.alsobar.com";
1400    // host used in reverse_lookup when multiple hostnames are returned.
1401    const REMOTE_IPV6_HOST_EXTRA: &str = "www.bar2.com";
1402    // host which has IPv4 and IPv6 address if reset name servers.
1403    const REMOTE_IPV4_IPV6_HOST: &str = "www.foobar.com";
1404    // host which has no records and does not result in an error.
1405    const NO_RECORDS_AND_NO_ERROR_HOST: &str = "www.no-records-and-no-error.com";
1406
1407    async fn setup_namelookup_service() -> (fname::LookupProxy, impl futures::Future<Output = ()>) {
1408        let (name_lookup_proxy, stream) =
1409            fidl::endpoints::create_proxy_and_stream::<fname::LookupMarker>();
1410
1411        let mut resolver_opts = ResolverOpts::default();
1412        resolver_opts.ip_strategy = LookupIpStrategy::Ipv4AndIpv6;
1413
1414        let resolver = SharedResolver::new(
1415            Resolver::new(ResolverConfig::default(), resolver_opts, Spawner)
1416                .expect("failed to create resolver"),
1417        );
1418        let stats = Arc::new(QueryStats::new());
1419        let (routes_proxy, routes_stream) =
1420            fidl::endpoints::create_proxy_and_stream::<fnet_routes::StateMarker>();
1421        let routes_fut =
1422            routes_stream.try_for_each(|req| -> futures::future::Ready<Result<(), fidl::Error>> {
1423                panic!("Should not call routes/State. Received request {:?}", req)
1424            });
1425        let (sender, recv) = mpsc::channel(MAX_PARALLEL_REQUESTS);
1426
1427        (name_lookup_proxy, async move {
1428            futures::future::try_join3(
1429                run_lookup(&resolver, stream, sender),
1430                routes_fut,
1431                create_ip_lookup_fut(&resolver, stats.clone(), routes_proxy, recv).map(Ok),
1432            )
1433            .map(|r| match r {
1434                Ok(((), (), ())) => (),
1435                Err(e) => panic!("namelookup service error {:?}", e),
1436            })
1437            .await
1438        })
1439    }
1440
1441    #[fuchsia::test(logging = false)]
1442    async fn test_lookupip_localhost() {
1443        let (proxy, fut) = setup_namelookup_service().await;
1444        let ((), ()) = futures::future::join(fut, async move {
1445            // IP Lookup IPv4 and IPv6 for localhost.
1446            assert_eq!(
1447                proxy
1448                    .lookup_ip(
1449                        LOCAL_HOST,
1450                        &fname::LookupIpOptions {
1451                            ipv4_lookup: Some(true),
1452                            ipv6_lookup: Some(true),
1453                            ..Default::default()
1454                        }
1455                    )
1456                    .await
1457                    .expect("lookup_ip"),
1458                Ok(fname::LookupResult {
1459                    addresses: Some(vec![IPV4_LOOPBACK, IPV6_LOOPBACK]),
1460                    ..Default::default()
1461                }),
1462            );
1463
1464            // IP Lookup IPv4 only for localhost.
1465            assert_eq!(
1466                proxy
1467                    .lookup_ip(
1468                        LOCAL_HOST,
1469                        &fname::LookupIpOptions { ipv4_lookup: Some(true), ..Default::default() }
1470                    )
1471                    .await
1472                    .expect("lookup_ip"),
1473                Ok(fname::LookupResult {
1474                    addresses: Some(vec![IPV4_LOOPBACK]),
1475                    ..Default::default()
1476                }),
1477            );
1478
1479            // IP Lookup IPv6 only for localhost.
1480            assert_eq!(
1481                proxy
1482                    .lookup_ip(
1483                        LOCAL_HOST,
1484                        &fname::LookupIpOptions { ipv6_lookup: Some(true), ..Default::default() }
1485                    )
1486                    .await
1487                    .expect("lookup_ip"),
1488                Ok(fname::LookupResult {
1489                    addresses: Some(vec![IPV6_LOOPBACK]),
1490                    ..Default::default()
1491                }),
1492            );
1493        })
1494        .await;
1495    }
1496
1497    #[fuchsia::test(logging = false)]
1498    async fn test_lookuphostname_localhost() {
1499        let (proxy, fut) = setup_namelookup_service().await;
1500        let ((), ()) = futures::future::join(fut, async move {
1501            let hostname = IPV4_LOOPBACK;
1502            assert_eq!(
1503                proxy.lookup_hostname(&hostname).await.expect("lookup_hostname").as_deref(),
1504                Ok(LOCAL_HOST)
1505            );
1506        })
1507        .await;
1508    }
1509
1510    #[derive(Debug, Clone)]
1511    struct MockResolver {
1512        config: ResolverConfig,
1513        repeat: u16,
1514        name_server_stats: Vec<NameServerStats>,
1515    }
1516
1517    impl ResolverLookup for MockResolver {
1518        fn new(config: ResolverConfig, _options: ResolverOpts) -> Self {
1519            Self { config, repeat: 1, name_server_stats: Vec::new() }
1520        }
1521
1522        async fn lookup<N: IntoName + Send>(
1523            &self,
1524            name: N,
1525            record_type: RecordType,
1526        ) -> Result<lookup::Lookup, ResolveError> {
1527            let Self { config: _, repeat, name_server_stats: _ } = self;
1528
1529            let name = name.into_name()?;
1530            let host_name = name.to_utf8();
1531
1532            if host_name == NO_RECORDS_AND_NO_ERROR_HOST {
1533                return Ok(Lookup::new_with_max_ttl(Query::default(), Arc::new([])));
1534            }
1535            let rdatas = match record_type {
1536                RecordType::A => [REMOTE_IPV4_HOST, REMOTE_IPV4_IPV6_HOST]
1537                    .contains(&host_name.as_str())
1538                    .then_some(RData::A(IPV4_HOST)),
1539                RecordType::AAAA => [REMOTE_IPV6_HOST, REMOTE_IPV4_IPV6_HOST]
1540                    .contains(&host_name.as_str())
1541                    .then_some(RData::AAAA(IPV6_HOST)),
1542                RecordType::CNAME => match host_name.as_str() {
1543                    REMOTE_IPV4_HOST_ALIAS => Some(REMOTE_IPV4_HOST),
1544                    REMOTE_IPV6_HOST_ALIAS => Some(REMOTE_IPV6_HOST),
1545                    _ => None,
1546                }
1547                .map(Name::from_str)
1548                .transpose()
1549                .unwrap()
1550                .map(RData::CNAME),
1551                record_type => {
1552                    panic!("unexpected record type {:?}", record_type)
1553                }
1554            }
1555            .into_iter();
1556
1557            let len = rdatas.len() * usize::from(*repeat);
1558            let records: Vec<Record> = rdatas
1559                .map(|rdata| {
1560                    Record::from_rdata(
1561                        Name::new(),
1562                        // The following ttl value is taken arbitrarily and does not matter in the
1563                        // test.
1564                        60,
1565                        rdata,
1566                    )
1567                })
1568                .cycle()
1569                .take(len)
1570                .collect();
1571
1572            if records.is_empty() {
1573                let mut response = trust_dns_proto::op::Message::new();
1574                let _: &mut trust_dns_proto::op::Message =
1575                    response.set_response_code(ResponseCode::NoError);
1576                let error = ResolveError::from_response(response.into(), false)
1577                    .expect_err("response with no records should be a NoRecordsFound error");
1578                return Err(error);
1579            }
1580
1581            Ok(Lookup::new_with_max_ttl(Query::default(), records.into()))
1582        }
1583
1584        async fn reverse_lookup(
1585            &self,
1586            addr: IpAddr,
1587        ) -> Result<lookup::ReverseLookup, ResolveError> {
1588            let lookup = if addr == IPV4_HOST {
1589                Lookup::from_rdata(
1590                    Query::default(),
1591                    RData::PTR(Name::from_str(REMOTE_IPV4_HOST).unwrap()),
1592                )
1593            } else if addr == IPV6_HOST {
1594                Lookup::new_with_max_ttl(
1595                    Query::default(),
1596                    Arc::new([
1597                        Record::from_rdata(
1598                            Name::new(),
1599                            60, // The value is taken arbitrarily and does not matter
1600                            // in the test.
1601                            RData::PTR(Name::from_str(REMOTE_IPV6_HOST).unwrap()),
1602                        ),
1603                        Record::from_rdata(
1604                            Name::new(),
1605                            60, // The value is taken arbitrarily and does not matter
1606                            // in the test.
1607                            RData::PTR(Name::from_str(REMOTE_IPV6_HOST_EXTRA).unwrap()),
1608                        ),
1609                    ]),
1610                )
1611            } else {
1612                Lookup::new_with_max_ttl(Query::default(), Arc::new([]))
1613            };
1614            Ok(ReverseLookup::from(lookup))
1615        }
1616    }
1617
1618    impl NameServerStatsProvider for MockResolver {
1619        fn name_server_stats(&self) -> Vec<NameServerStats> {
1620            self.name_server_stats.clone()
1621        }
1622    }
1623
1624    struct TestEnvironment {
1625        shared_resolver: SharedResolver<MockResolver>,
1626        config_state: Arc<dns::config::ServerConfigState>,
1627        stats: Arc<QueryStats>,
1628    }
1629
1630    impl Default for TestEnvironment {
1631        fn default() -> Self {
1632            Self::new(1)
1633        }
1634    }
1635
1636    impl TestEnvironment {
1637        fn new(repeat: u16) -> Self {
1638            Self {
1639                shared_resolver: SharedResolver::new(MockResolver {
1640                    config: ResolverConfig::from_parts(
1641                        None,
1642                        vec![],
1643                        // Set name_servers as empty, so it's guaranteed to be different from IPV4_NAMESERVER
1644                        // and IPV6_NAMESERVER.
1645                        NameServerConfigGroup::with_capacity(0),
1646                    ),
1647                    repeat,
1648                    name_server_stats: Vec::new(),
1649                }),
1650                config_state: Arc::new(dns::config::ServerConfigState::new()),
1651                stats: Arc::new(QueryStats::new()),
1652            }
1653        }
1654
1655        async fn run_lookup<F, Fut>(&self, f: F)
1656        where
1657            Fut: futures::Future<Output = ()>,
1658            F: FnOnce(fname::LookupProxy) -> Fut,
1659        {
1660            self.run_lookup_with_routes_handler(f, |req| {
1661                panic!("Should not call routes/State. Received request {:?}", req)
1662            })
1663            .await
1664        }
1665
1666        async fn run_lookup_with_routes_handler<F, Fut, R>(&self, f: F, handle_routes: R)
1667        where
1668            Fut: futures::Future<Output = ()>,
1669            F: FnOnce(fname::LookupProxy) -> Fut,
1670            R: Fn(fnet_routes::StateRequest),
1671        {
1672            let (name_lookup_proxy, name_lookup_stream) =
1673                fidl::endpoints::create_proxy_and_stream::<fname::LookupMarker>();
1674
1675            let (routes_proxy, routes_stream) =
1676                fidl::endpoints::create_proxy_and_stream::<fnet_routes::StateMarker>();
1677
1678            let (sender, recv) = mpsc::channel(MAX_PARALLEL_REQUESTS);
1679            let Self { shared_resolver, config_state: _, stats } = self;
1680            let ((), (), (), ()) = futures::future::try_join4(
1681                run_lookup(shared_resolver, name_lookup_stream, sender),
1682                f(name_lookup_proxy).map(Ok),
1683                routes_stream.try_for_each(|req| futures::future::ok(handle_routes(req))),
1684                create_ip_lookup_fut(shared_resolver, stats.clone(), routes_proxy, recv).map(Ok),
1685            )
1686            .await
1687            .expect("Error running lookup future");
1688        }
1689
1690        async fn run_admin<F, Fut>(&self, f: F)
1691        where
1692            Fut: futures::Future<Output = ()>,
1693            F: FnOnce(fname::LookupAdminProxy) -> Fut,
1694        {
1695            let (lookup_admin_proxy, lookup_admin_stream) =
1696                fidl::endpoints::create_proxy_and_stream::<fname::LookupAdminMarker>();
1697            let Self { shared_resolver, config_state, stats: _ } = self;
1698            let ((), ()) = futures::future::try_join(
1699                run_lookup_admin(shared_resolver, config_state, lookup_admin_stream)
1700                    .map_err(anyhow::Error::from),
1701                f(lookup_admin_proxy).map(Ok),
1702            )
1703            .await
1704            .expect("Error running admin future");
1705        }
1706    }
1707
1708    fn map_ip<T: Into<IpAddr>>(addr: T) -> fnet::IpAddress {
1709        net_ext::IpAddress(addr.into()).into()
1710    }
1711
1712    #[fuchsia::test(logging = false)]
1713    async fn test_no_records_and_no_error() {
1714        TestEnvironment::default()
1715            .run_lookup(|proxy| async move {
1716                let proxy = &proxy;
1717                futures::stream::iter([(true, true), (true, false), (false, true)])
1718                    .for_each_concurrent(None, move |(ipv4_lookup, ipv6_lookup)| async move {
1719                        // Verify that the resolver does not panic when the
1720                        // response contains no records and no error. This
1721                        // scenario should theoretically not occur, but
1722                        // currently does. See https://fxbug.dev/42062388.
1723                        assert_eq!(
1724                            proxy
1725                                .lookup_ip(
1726                                    NO_RECORDS_AND_NO_ERROR_HOST,
1727                                    &fname::LookupIpOptions {
1728                                        ipv4_lookup: Some(ipv4_lookup),
1729                                        ipv6_lookup: Some(ipv6_lookup),
1730                                        ..Default::default()
1731                                    }
1732                                )
1733                                .await
1734                                .expect("lookup_ip"),
1735                            Err(fname::LookupError::NotFound),
1736                        );
1737                    })
1738                    .await
1739            })
1740            .await;
1741    }
1742
1743    #[fuchsia::test(logging = false)]
1744    async fn test_lookupip_remotehost_overflow() {
1745        // We're returning two addresses, so we need each one to repeat only half as many times.
1746        const REPEAT: u16 = fname::MAX_ADDRESSES / 2 + 1;
1747        let expected = std::iter::empty()
1748            .chain(std::iter::repeat(map_ip(IPV4_HOST)).take(REPEAT.into()))
1749            .chain(std::iter::repeat(map_ip(IPV6_HOST)).take(REPEAT.into()))
1750            .take(fname::MAX_ADDRESSES.into())
1751            .collect::<Vec<_>>();
1752        assert_eq!(expected.len(), usize::from(fname::MAX_ADDRESSES));
1753        TestEnvironment::new(REPEAT)
1754            .run_lookup(|proxy| async move {
1755                assert_eq!(
1756                    proxy
1757                        .lookup_ip(
1758                            REMOTE_IPV4_IPV6_HOST,
1759                            &fname::LookupIpOptions {
1760                                ipv4_lookup: Some(true),
1761                                ipv6_lookup: Some(true),
1762                                ..Default::default()
1763                            }
1764                        )
1765                        .await
1766                        .expect("lookup_ip"),
1767                    Ok(fname::LookupResult { addresses: Some(expected), ..Default::default() })
1768                );
1769            })
1770            .await;
1771    }
1772
1773    #[fuchsia::test(logging = false)]
1774    async fn test_lookupip_remotehost_ipv4() {
1775        TestEnvironment::default()
1776            .run_lookup(|proxy| async move {
1777                // IP Lookup IPv4 and IPv6 for REMOTE_IPV4_HOST.
1778                assert_eq!(
1779                    proxy
1780                        .lookup_ip(
1781                            REMOTE_IPV4_HOST,
1782                            &fname::LookupIpOptions {
1783                                ipv4_lookup: Some(true),
1784                                ipv6_lookup: Some(true),
1785                                ..Default::default()
1786                            }
1787                        )
1788                        .await
1789                        .expect("lookup_ip"),
1790                    Ok(fname::LookupResult {
1791                        addresses: Some(vec![map_ip(IPV4_HOST)]),
1792                        ..Default::default()
1793                    }),
1794                );
1795
1796                // IP Lookup IPv4 for REMOTE_IPV4_HOST.
1797                assert_eq!(
1798                    proxy
1799                        .lookup_ip(
1800                            REMOTE_IPV4_HOST,
1801                            &fname::LookupIpOptions {
1802                                ipv4_lookup: Some(true),
1803                                ..Default::default()
1804                            }
1805                        )
1806                        .await
1807                        .expect("lookup_ip"),
1808                    Ok(fname::LookupResult {
1809                        addresses: Some(vec![map_ip(IPV4_HOST)]),
1810                        ..Default::default()
1811                    }),
1812                );
1813
1814                // IP Lookup IPv6 for REMOTE_IPV4_HOST.
1815                assert_eq!(
1816                    proxy
1817                        .lookup_ip(
1818                            REMOTE_IPV4_HOST,
1819                            &fname::LookupIpOptions {
1820                                ipv6_lookup: Some(true),
1821                                ..Default::default()
1822                            }
1823                        )
1824                        .await
1825                        .expect("lookup_ip"),
1826                    Err(fname::LookupError::NotFound),
1827                );
1828            })
1829            .await;
1830    }
1831
1832    #[fuchsia::test(logging = false)]
1833    async fn test_lookupip_remotehost_ipv6() {
1834        TestEnvironment::default()
1835            .run_lookup(|proxy| async move {
1836                // IP Lookup IPv4 and IPv6 for REMOTE_IPV6_HOST.
1837                assert_eq!(
1838                    proxy
1839                        .lookup_ip(
1840                            REMOTE_IPV6_HOST,
1841                            &fname::LookupIpOptions {
1842                                ipv4_lookup: Some(true),
1843                                ipv6_lookup: Some(true),
1844                                ..Default::default()
1845                            }
1846                        )
1847                        .await
1848                        .expect("lookup_ip"),
1849                    Ok(fname::LookupResult {
1850                        addresses: Some(vec![map_ip(IPV6_HOST)]),
1851                        ..Default::default()
1852                    }),
1853                );
1854
1855                // IP Lookup IPv4 for REMOTE_IPV6_HOST.
1856                assert_eq!(
1857                    proxy
1858                        .lookup_ip(
1859                            REMOTE_IPV6_HOST,
1860                            &fname::LookupIpOptions {
1861                                ipv4_lookup: Some(true),
1862                                ..Default::default()
1863                            }
1864                        )
1865                        .await
1866                        .expect("lookup_ip"),
1867                    Err(fname::LookupError::NotFound),
1868                );
1869
1870                // IP Lookup IPv6 for REMOTE_IPV4_HOST.
1871                assert_eq!(
1872                    proxy
1873                        .lookup_ip(
1874                            REMOTE_IPV6_HOST,
1875                            &fname::LookupIpOptions {
1876                                ipv6_lookup: Some(true),
1877                                ..Default::default()
1878                            }
1879                        )
1880                        .await
1881                        .expect("lookup_ip"),
1882                    Ok(fname::LookupResult {
1883                        addresses: Some(vec![map_ip(IPV6_HOST)]),
1884                        ..Default::default()
1885                    }),
1886                );
1887            })
1888            .await;
1889    }
1890
1891    #[test_case(REMOTE_IPV4_HOST_ALIAS, REMOTE_IPV4_HOST; "ipv4")]
1892    #[test_case(REMOTE_IPV6_HOST_ALIAS, REMOTE_IPV6_HOST; "ipv6")]
1893    #[fuchsia::test(logging = false)]
1894    async fn test_lookupip_remotehost_canonical_name(hostname: &str, expected: &str) {
1895        TestEnvironment::default()
1896            .run_lookup(|proxy| async move {
1897                assert_matches!(
1898                    proxy
1899                        .lookup_ip(
1900                            hostname,
1901                            &fname::LookupIpOptions {
1902                                canonical_name_lookup: Some(true),
1903                                ..Default::default()
1904                            }
1905                        )
1906                        .await,
1907                    Ok(Ok(fname::LookupResult {
1908                        canonical_name: Some(cname),
1909                        ..
1910                    })) => assert_eq!(cname, expected)
1911                );
1912            })
1913            .await;
1914    }
1915
1916    #[fuchsia::test(logging = false)]
1917    async fn test_lookupip_ip_literal() {
1918        TestEnvironment::default()
1919            .run_lookup(|proxy| async move {
1920                let proxy = &proxy;
1921
1922                let range = || [true, false].into_iter();
1923
1924                futures::stream::iter(range().cartesian_product(range()))
1925                    .for_each_concurrent(None, move |(ipv4_lookup, ipv6_lookup)| async move {
1926                        assert_eq!(
1927                            proxy
1928                                .lookup_ip(
1929                                    "240.0.0.2",
1930                                    &fname::LookupIpOptions {
1931                                        ipv4_lookup: Some(ipv4_lookup),
1932                                        ipv6_lookup: Some(ipv6_lookup),
1933                                        ..Default::default()
1934                                    }
1935                                )
1936                                .await
1937                                .expect("lookup_ip"),
1938                            Err(fname::LookupError::InvalidArgs),
1939                            "ipv4_lookup={},ipv6_lookup={}",
1940                            ipv4_lookup,
1941                            ipv6_lookup,
1942                        );
1943
1944                        assert_eq!(
1945                            proxy
1946                                .lookup_ip(
1947                                    "abcd::2",
1948                                    &fname::LookupIpOptions {
1949                                        ipv4_lookup: Some(ipv4_lookup),
1950                                        ipv6_lookup: Some(ipv6_lookup),
1951                                        ..Default::default()
1952                                    }
1953                                )
1954                                .await
1955                                .expect("lookup_ip"),
1956                            Err(fname::LookupError::InvalidArgs),
1957                            "ipv4_lookup={},ipv6_lookup={}",
1958                            ipv4_lookup,
1959                            ipv6_lookup,
1960                        );
1961                    })
1962                    .await
1963            })
1964            .await
1965    }
1966
1967    #[fuchsia::test(logging = false)]
1968    async fn test_lookup_hostname() {
1969        TestEnvironment::default()
1970            .run_lookup(|proxy| async move {
1971                assert_eq!(
1972                    proxy
1973                        .lookup_hostname(&map_ip(IPV4_HOST))
1974                        .await
1975                        .expect("lookup_hostname")
1976                        .as_deref(),
1977                    Ok(REMOTE_IPV4_HOST)
1978                );
1979            })
1980            .await;
1981    }
1982
1983    // Multiple hostnames returned from trust-dns* APIs, and only the first one will be returned
1984    // by the FIDL.
1985    #[fuchsia::test(logging = false)]
1986    async fn test_lookup_hostname_multi() {
1987        TestEnvironment::default()
1988            .run_lookup(|proxy| async move {
1989                assert_eq!(
1990                    proxy
1991                        .lookup_hostname(&map_ip(IPV6_HOST))
1992                        .await
1993                        .expect("lookup_hostname")
1994                        .as_deref(),
1995                    Ok(REMOTE_IPV6_HOST)
1996                );
1997            })
1998            .await;
1999    }
2000
2001    #[fuchsia::test(logging = false)]
2002    async fn test_set_server_names() {
2003        let env = TestEnvironment::default();
2004
2005        let to_server_configs = |socket_addr: SocketAddr| -> [NameServerConfig; 2] {
2006            [
2007                NameServerConfig {
2008                    socket_addr,
2009                    protocol: Protocol::Udp,
2010                    tls_dns_name: None,
2011                    trust_nx_responses: false,
2012                    bind_addr: None,
2013                    num_retained_errors: RETAINED_ERRORS_PER_NAME_SERVER,
2014                },
2015                NameServerConfig {
2016                    socket_addr,
2017                    protocol: Protocol::Tcp,
2018                    tls_dns_name: None,
2019                    trust_nx_responses: false,
2020                    bind_addr: None,
2021                    num_retained_errors: RETAINED_ERRORS_PER_NAME_SERVER,
2022                },
2023            ]
2024        };
2025
2026        // Assert that mock config has no servers originally.
2027        assert_eq!(env.shared_resolver.read().config.name_servers().to_vec(), vec![]);
2028
2029        // Set servers.
2030        env.run_admin(|proxy| async move {
2031            proxy
2032                .set_dns_servers(&[DHCP_SERVER, NDP_SERVER, DHCPV6_SERVER])
2033                .await
2034                .expect("Failed to call SetDnsServers")
2035                .expect("SetDnsServers error");
2036        })
2037        .await;
2038        assert_eq!(
2039            env.shared_resolver.read().config.name_servers().to_vec(),
2040            vec![DHCP_SERVER, NDP_SERVER, DHCPV6_SERVER]
2041                .into_iter()
2042                .map(|s| {
2043                    let net_ext::SocketAddress(s) = s.into();
2044                    s
2045                })
2046                .flat_map(|x| to_server_configs(x).to_vec().into_iter())
2047                .collect::<Vec<_>>()
2048        );
2049
2050        // Clear servers.
2051        env.run_admin(|proxy| async move {
2052            proxy
2053                .set_dns_servers(&[])
2054                .await
2055                .expect("Failed to call SetDnsServers")
2056                .expect("SetDnsServers error");
2057        })
2058        .await;
2059        assert_eq!(env.shared_resolver.read().config.name_servers().to_vec(), Vec::new());
2060    }
2061
2062    #[fuchsia::test(logging = false)]
2063    async fn test_set_server_names_error() {
2064        let env = TestEnvironment::default();
2065        // Assert that mock config has no servers originally.
2066        assert_eq!(env.shared_resolver.read().config.name_servers().to_vec(), vec![]);
2067
2068        env.run_admin(|proxy| async move {
2069            // Attempt to set bad addresses.
2070
2071            // Multicast not allowed.
2072            let status = proxy
2073                .set_dns_servers(&[fnet::SocketAddress::Ipv4(fnet::Ipv4SocketAddress {
2074                    address: fnet::Ipv4Address { addr: [224, 0, 0, 1] },
2075                    port: DEFAULT_PORT,
2076                })])
2077                .await
2078                .expect("Failed to call SetDnsServers")
2079                .expect_err("SetDnsServers should fail for multicast address");
2080            assert_eq!(zx::Status::ok(status), Err(zx::Status::INVALID_ARGS));
2081
2082            // Unspecified not allowed.
2083            let status = proxy
2084                .set_dns_servers(&[fnet::SocketAddress::Ipv6(fnet::Ipv6SocketAddress {
2085                    address: fnet::Ipv6Address { addr: [0; 16] },
2086                    port: DEFAULT_PORT,
2087                    zone_index: 0,
2088                })])
2089                .await
2090                .expect("Failed to call SetDnsServers")
2091                .expect_err("SetDnsServers should fail for unspecified address");
2092            assert_eq!(zx::Status::ok(status), Err(zx::Status::INVALID_ARGS));
2093        })
2094        .await;
2095
2096        // Assert that config didn't change.
2097        assert_eq!(env.shared_resolver.read().config.name_servers().to_vec(), vec![]);
2098    }
2099
2100    #[fuchsia::test(logging = false)]
2101    async fn test_get_servers() {
2102        let env = TestEnvironment::default();
2103        env.run_admin(|proxy| async move {
2104            let expect = &[NDP_SERVER, DHCP_SERVER, DHCPV6_SERVER, STATIC_SERVER];
2105            proxy.set_dns_servers(expect).await.expect("FIDL error").expect("set_servers failed");
2106            assert_matches!(proxy.get_dns_servers().await, Ok(got) if got == expect);
2107        })
2108        .await;
2109    }
2110
2111    #[fuchsia::test(logging = false)]
2112    async fn test_config_inspect() {
2113        let env = TestEnvironment::default();
2114        let inspector = fuchsia_inspect::Inspector::default();
2115        let _config_state_node =
2116            add_config_state_inspect(inspector.root(), env.config_state.clone());
2117        assert_data_tree!(inspector, root:{
2118            servers: {}
2119        });
2120        env.run_admin(|proxy| async move {
2121            let servers = &[NDP_SERVER, DHCP_SERVER, DHCPV6_SERVER, STATIC_SERVER];
2122            proxy.set_dns_servers(servers).await.expect("FIDL error").expect("set_servers failed");
2123        })
2124        .await;
2125        assert_data_tree!(inspector, root:{
2126            servers: {
2127                "0": {
2128                    address: "[2001:4860:4860::4444%2]:53",
2129                },
2130                "1": {
2131                    address: "8.8.4.4:53",
2132                },
2133                "2": {
2134                    address: "[2002:4860:4860::4444%3]:53",
2135                },
2136                "3": {
2137                    address: "8.8.8.8:53",
2138                },
2139            }
2140        });
2141    }
2142
2143    #[fuchsia::test(logging = false)]
2144    async fn test_name_server_stats_inspect() {
2145        let env = TestEnvironment::default();
2146        let inspector = fuchsia_inspect::Inspector::default();
2147        let _name_server_stats_node =
2148            add_name_server_stats_inspect(inspector.root(), env.shared_resolver.clone());
2149        assert_data_tree!(inspector, root:{
2150            name_servers: {}
2151        });
2152
2153        let addr = std_socket_addr!("1.2.3.4:53");
2154        let stats = NameServerStats {
2155            addr,
2156            proto: Protocol::Udp,
2157            failures: 1,
2158            successes: 2,
2159            success_streak: 0,
2160            recent_errors: vec![ResolveErrorKind::Timeout.into()],
2161        };
2162        env.shared_resolver.write(Arc::new(MockResolver {
2163            config: ResolverConfig::default(),
2164            repeat: 1,
2165            name_server_stats: vec![stats],
2166        }));
2167
2168        assert_data_tree!(inspector, root:{
2169            name_servers: {
2170                "0": {
2171                    address: "1.2.3.4:53",
2172                    protocol: "Udp",
2173                    successful_queries: 2u64,
2174                    failed_queries: 1u64,
2175                    success_streak: 0u64,
2176                    errors: {
2177                        Timeout: 1u64,
2178                        Message: 0u64,
2179                        NoConnections: 0u64,
2180                        IoErrorCounts: {},
2181                        ProtoErrorCounts: {},
2182                        NoRecordsFoundResponseCodeCounts: {},
2183                        UnhandledResolveErrorKindCounts: {},
2184                    }
2185                }
2186            }
2187        });
2188    }
2189
2190    #[test]
2191    fn test_unhandled_resolve_error_kind_stats() {
2192        use ResolveErrorKind::{Msg, Timeout};
2193        let mut unhandled_resolve_error_kind_stats = GenericErrorKindStats::default();
2194        assert_eq!(
2195            unhandled_resolve_error_kind_stats.increment(&Msg(String::from("abcdefgh"))),
2196            "Msg"
2197        );
2198        assert_eq!(
2199            unhandled_resolve_error_kind_stats.increment(&Msg(String::from("ijklmn"))),
2200            "Msg"
2201        );
2202        assert_eq!(unhandled_resolve_error_kind_stats.increment(&Timeout), "Timeout");
2203        assert_eq!(
2204            unhandled_resolve_error_kind_stats,
2205            GenericErrorKindStats([(String::from("Msg"), 2), (String::from("Timeout"), 1)].into())
2206        )
2207    }
2208
2209    #[fuchsia::test(logging = false)]
2210    async fn test_query_stats_updated() {
2211        let env = TestEnvironment::default();
2212        let inspector = fuchsia_inspect::Inspector::default();
2213        let _query_stats_inspect_node =
2214            add_query_stats_inspect(inspector.root(), env.stats.clone());
2215        assert_data_tree!(inspector, root:{
2216            query_stats: {}
2217        });
2218
2219        env.run_lookup(|proxy| async move {
2220            // IP Lookup IPv4 for REMOTE_IPV4_HOST.
2221            assert_eq!(
2222                proxy
2223                    .lookup_ip(
2224                        REMOTE_IPV4_HOST,
2225                        &fname::LookupIpOptions { ipv4_lookup: Some(true), ..Default::default() }
2226                    )
2227                    .await
2228                    .expect("lookup_ip"),
2229                Ok(fname::LookupResult {
2230                    addresses: Some(vec![map_ip(IPV4_HOST)]),
2231                    ..Default::default()
2232                }),
2233            );
2234        })
2235        .await;
2236        env.run_lookup(|proxy| async move {
2237            // IP Lookup IPv6 for REMOTE_IPV4_HOST.
2238            assert_eq!(
2239                proxy
2240                    .lookup_ip(
2241                        REMOTE_IPV4_HOST,
2242                        &fname::LookupIpOptions { ipv6_lookup: Some(true), ..Default::default() }
2243                    )
2244                    .await
2245                    .expect("lookup_ip"),
2246                Err(fname::LookupError::NotFound),
2247            );
2248        })
2249        .await;
2250        assert_data_tree!(inspector, root:{
2251            query_stats: {
2252                "window 1": {
2253                    start_time_nanos: NonZeroUintProperty,
2254                    successful_queries: 1u64,
2255                    failed_queries: 1u64,
2256                    average_success_duration_micros: NonZeroUintProperty,
2257                    average_failure_duration_micros: NonZeroUintProperty,
2258                    errors: {
2259                        Message: 0u64,
2260                        NoConnections: 0u64,
2261                        NoRecordsFoundResponseCodeCounts: {
2262                            NoError: {
2263                                count: 1u64
2264                            },
2265                        },
2266                        IoErrorCounts: {},
2267                        ProtoErrorCounts: {},
2268                        Timeout: 0u64,
2269                        UnhandledResolveErrorKindCounts: {},
2270                    },
2271                    address_counts: {
2272                        "1": {
2273                            count: 1u64,
2274                        }
2275                    },
2276                },
2277            }
2278        });
2279    }
2280
2281    fn run_fake_lookup(
2282        exec: &mut fasync::TestExecutor,
2283        stats: Arc<QueryStats>,
2284        result: QueryResult<'_>,
2285        delay: zx::MonotonicDuration,
2286    ) {
2287        let start_time = fasync::MonotonicInstant::now();
2288        exec.set_fake_time(fasync::MonotonicInstant::after(delay));
2289        let update_stats = stats.finish_query(start_time, result);
2290        let mut update_stats = pin!(update_stats);
2291        assert!(exec.run_until_stalled(&mut update_stats).is_ready());
2292    }
2293
2294    // Safety: This is safe because the initial value is not zero.
2295    const NON_ZERO_USIZE_ONE: NonZeroUsize = NonZeroUsize::new(1).unwrap();
2296
2297    #[test]
2298    fn test_query_stats_inspect_average() {
2299        let mut exec = fasync::TestExecutor::new_with_fake_time();
2300        const START_NANOS: i64 = 1_234_567;
2301        exec.set_fake_time(fasync::MonotonicInstant::from_nanos(START_NANOS));
2302
2303        let stats = Arc::new(QueryStats::new());
2304        let inspector = fuchsia_inspect::Inspector::default();
2305        let _query_stats_inspect_node = add_query_stats_inspect(inspector.root(), stats.clone());
2306        const SUCCESSFUL_QUERY_COUNT: u64 = 10;
2307        const SUCCESSFUL_QUERY_DURATION: zx::MonotonicDuration =
2308            zx::MonotonicDuration::from_seconds(30);
2309        for _ in 0..SUCCESSFUL_QUERY_COUNT / 2 {
2310            run_fake_lookup(
2311                &mut exec,
2312                stats.clone(),
2313                Ok(/*addresses*/ NON_ZERO_USIZE_ONE),
2314                zx::MonotonicDuration::from_nanos(0),
2315            );
2316            run_fake_lookup(
2317                &mut exec,
2318                stats.clone(),
2319                Ok(/*addresses*/ NON_ZERO_USIZE_ONE),
2320                SUCCESSFUL_QUERY_DURATION,
2321            );
2322            exec.set_fake_time(fasync::MonotonicInstant::after(
2323                STAT_WINDOW_DURATION - SUCCESSFUL_QUERY_DURATION,
2324            ));
2325        }
2326        let mut expected = tree_assertion!(query_stats: {});
2327        for i in 0..SUCCESSFUL_QUERY_COUNT / 2 {
2328            let name = &format!("window {}", i + 1);
2329            let child = tree_assertion!(var name: {
2330                start_time_nanos: u64::try_from(
2331                    START_NANOS + STAT_WINDOW_DURATION.into_nanos() * i64::try_from(i).unwrap()
2332                ).unwrap(),
2333                successful_queries: 2u64,
2334                failed_queries: 0u64,
2335                average_success_duration_micros: u64::try_from(
2336                    SUCCESSFUL_QUERY_DURATION.into_micros()
2337                ).unwrap() / 2,
2338                errors: {
2339                    Message: 0u64,
2340                    NoConnections: 0u64,
2341                    NoRecordsFoundResponseCodeCounts: {},
2342                    IoErrorCounts: {},
2343                    ProtoErrorCounts: {},
2344                    Timeout: 0u64,
2345                    UnhandledResolveErrorKindCounts: {},
2346                },
2347                address_counts: {
2348                    "1": {
2349                        count: 2u64,
2350                    }
2351                },
2352            });
2353            expected.add_child_assertion(child);
2354        }
2355        assert_data_tree!(@executor exec, inspector, root: {
2356            expected,
2357        });
2358    }
2359
2360    #[test]
2361    fn test_query_stats_inspect_error_counters() {
2362        let mut exec = fasync::TestExecutor::new_with_fake_time();
2363        const START_NANOS: i64 = 1_234_567;
2364        exec.set_fake_time(fasync::MonotonicInstant::from_nanos(START_NANOS));
2365
2366        let stats = Arc::new(QueryStats::new());
2367        let inspector = fuchsia_inspect::Inspector::default();
2368        let _query_stats_inspect_node = add_query_stats_inspect(inspector.root(), stats.clone());
2369        const FAILED_QUERY_COUNT: u64 = 10;
2370        const FAILED_QUERY_DURATION: zx::MonotonicDuration =
2371            zx::MonotonicDuration::from_millis(500);
2372        for _ in 0..FAILED_QUERY_COUNT {
2373            run_fake_lookup(
2374                &mut exec,
2375                stats.clone(),
2376                Err(&ResolveErrorKind::Timeout),
2377                FAILED_QUERY_DURATION,
2378            );
2379        }
2380        assert_data_tree!(@executor exec, inspector, root:{
2381            query_stats: {
2382                "window 1": {
2383                    start_time_nanos: u64::try_from(
2384                        START_NANOS + FAILED_QUERY_DURATION.into_nanos()
2385                    ).unwrap(),
2386                    successful_queries: 0u64,
2387                    failed_queries: FAILED_QUERY_COUNT,
2388                    average_failure_duration_micros: u64::try_from(
2389                        FAILED_QUERY_DURATION.into_micros()
2390                    ).unwrap(),
2391                    errors: {
2392                        Message: 0u64,
2393                        NoConnections: 0u64,
2394                        NoRecordsFoundResponseCodeCounts: {},
2395                        IoErrorCounts: {},
2396                        ProtoErrorCounts: {},
2397                        Timeout: FAILED_QUERY_COUNT,
2398                        UnhandledResolveErrorKindCounts: {},
2399                    },
2400                    address_counts: {},
2401                },
2402            }
2403        });
2404    }
2405
2406    #[test]
2407    fn test_query_stats_inspect_no_records_found() {
2408        let mut exec = fasync::TestExecutor::new_with_fake_time();
2409        const START_NANOS: i64 = 1_234_567;
2410        exec.set_fake_time(fasync::MonotonicInstant::from_nanos(START_NANOS));
2411
2412        let stats = Arc::new(QueryStats::new());
2413        let inspector = fuchsia_inspect::Inspector::default();
2414        let _query_stats_inspect_node = add_query_stats_inspect(inspector.root(), stats.clone());
2415        const FAILED_QUERY_COUNT: u64 = 10;
2416        const FAILED_QUERY_DURATION: zx::MonotonicDuration =
2417            zx::MonotonicDuration::from_millis(500);
2418
2419        let mut run_fake_no_records_lookup = |response_code: ResponseCode| {
2420            run_fake_lookup(
2421                &mut exec,
2422                stats.clone(),
2423                Err(&ResolveErrorKind::NoRecordsFound {
2424                    query: Box::new(Query::default()),
2425                    soa: None,
2426                    negative_ttl: None,
2427                    response_code,
2428                    trusted: false,
2429                }),
2430                FAILED_QUERY_DURATION,
2431            )
2432        };
2433
2434        for _ in 0..FAILED_QUERY_COUNT {
2435            run_fake_no_records_lookup(ResponseCode::NXDomain);
2436            run_fake_no_records_lookup(ResponseCode::Refused);
2437            run_fake_no_records_lookup(4096.into());
2438            run_fake_no_records_lookup(4097.into());
2439        }
2440
2441        assert_data_tree!(@executor exec, inspector, root:{
2442            query_stats: {
2443                "window 1": {
2444                    start_time_nanos: u64::try_from(
2445                        START_NANOS + FAILED_QUERY_DURATION.into_nanos()
2446                    ).unwrap(),
2447                    successful_queries: 0u64,
2448                    failed_queries: FAILED_QUERY_COUNT * 4,
2449                    average_failure_duration_micros: u64::try_from(
2450                        FAILED_QUERY_DURATION.into_micros()
2451                    ).unwrap(),
2452                    errors: {
2453                        Message: 0u64,
2454                        NoConnections: 0u64,
2455                        NoRecordsFoundResponseCodeCounts: {
2456                            NXDomain: {
2457                                count: FAILED_QUERY_COUNT
2458                            },
2459                            Refused: {
2460                                count: FAILED_QUERY_COUNT
2461                            },
2462                            "Unknown(4096)": {
2463                                count: FAILED_QUERY_COUNT
2464                            },
2465                            "Unknown(4097)": {
2466                                count: FAILED_QUERY_COUNT
2467                            },
2468                        },
2469                        IoErrorCounts: {},
2470                        ProtoErrorCounts: {},
2471                        Timeout: 0u64,
2472                        UnhandledResolveErrorKindCounts: {},
2473                    },
2474                    address_counts: {},
2475                },
2476            }
2477        });
2478    }
2479
2480    #[test]
2481    fn test_query_stats_resolved_address_counts() {
2482        let mut exec = fasync::TestExecutor::new_with_fake_time();
2483        const START_NANOS: i64 = 1_234_567;
2484        exec.set_fake_time(fasync::MonotonicInstant::from_nanos(START_NANOS));
2485
2486        let stats = Arc::new(QueryStats::new());
2487        let inspector = fuchsia_inspect::Inspector::default();
2488        let _query_stats_inspect_node = add_query_stats_inspect(inspector.root(), stats.clone());
2489
2490        // Create some test data to run fake lookups. Simulate a histogram with:
2491        //  - 99 occurrences of a response with 1 address,
2492        //  - 98 occurrences of a response with 2 addresses,
2493        //  - ...
2494        //  - 1 occurrence of a response with 99 addresses.
2495        let address_counts: HashMap<usize, _> = (1..100).zip((1..100).rev()).collect();
2496        const QUERY_DURATION: zx::MonotonicDuration = zx::MonotonicDuration::from_millis(10);
2497        for (count, occurrences) in address_counts.iter() {
2498            for _ in 0..*occurrences {
2499                run_fake_lookup(
2500                    &mut exec,
2501                    stats.clone(),
2502                    Ok(NonZeroUsize::new(*count).expect("address count must be greater than zero")),
2503                    QUERY_DURATION,
2504                );
2505            }
2506        }
2507
2508        let mut expected_address_counts = tree_assertion!(address_counts: {});
2509        for (count, occurrences) in address_counts.iter() {
2510            let mut child = TreeAssertion::new(&count.to_string(), true);
2511            child.add_property_assertion("count", Arc::new(*occurrences));
2512            expected_address_counts.add_child_assertion(child);
2513        }
2514        assert_data_tree!(@executor exec, inspector, root: {
2515            query_stats: {
2516                "window 1": {
2517                    start_time_nanos: u64::try_from(
2518                        START_NANOS + QUERY_DURATION.into_nanos()
2519                    ).unwrap(),
2520                    successful_queries: address_counts.values().sum::<u64>(),
2521                    failed_queries: 0u64,
2522                    average_success_duration_micros: u64::try_from(
2523                        QUERY_DURATION.into_micros()
2524                    ).unwrap(),
2525                    errors: {
2526                        Message: 0u64,
2527                        NoConnections: 0u64,
2528                        NoRecordsFoundResponseCodeCounts: {},
2529                        IoErrorCounts: {},
2530                        ProtoErrorCounts: {},
2531                        Timeout: 0u64,
2532                        UnhandledResolveErrorKindCounts: {},
2533                    },
2534                    expected_address_counts,
2535                },
2536            },
2537        });
2538    }
2539
2540    #[test]
2541    fn test_query_stats_inspect_oldest_stats_erased() {
2542        let mut exec = fasync::TestExecutor::new_with_fake_time();
2543        const START_NANOS: i64 = 1_234_567;
2544        exec.set_fake_time(fasync::MonotonicInstant::from_nanos(START_NANOS));
2545
2546        let stats = Arc::new(QueryStats::new());
2547        let inspector = fuchsia_inspect::Inspector::default();
2548        let _query_stats_inspect_node = add_query_stats_inspect(inspector.root(), stats.clone());
2549        const DELAY: zx::MonotonicDuration = zx::MonotonicDuration::from_millis(100);
2550        for _ in 0..STAT_WINDOW_COUNT {
2551            let () =
2552                run_fake_lookup(&mut exec, stats.clone(), Err(&ResolveErrorKind::Timeout), DELAY);
2553            let () =
2554                exec.set_fake_time(fasync::MonotonicInstant::after(STAT_WINDOW_DURATION - DELAY));
2555        }
2556        for _ in 0..STAT_WINDOW_COUNT {
2557            run_fake_lookup(&mut exec, stats.clone(), Ok(/*addresses*/ NON_ZERO_USIZE_ONE), DELAY);
2558            let () =
2559                exec.set_fake_time(fasync::MonotonicInstant::after(STAT_WINDOW_DURATION - DELAY));
2560        }
2561        // All the failed queries should be erased from the stats as they are
2562        // now out of date.
2563        let mut expected = tree_assertion!(query_stats: {});
2564        let start_offset = START_NANOS
2565            + DELAY.into_nanos()
2566            + STAT_WINDOW_DURATION.into_nanos() * i64::try_from(STAT_WINDOW_COUNT).unwrap();
2567        for i in 0..STAT_WINDOW_COUNT {
2568            let name = &format!("window {}", i + 1);
2569            let child = tree_assertion!(var name: {
2570                start_time_nanos: u64::try_from(
2571                    start_offset + STAT_WINDOW_DURATION.into_nanos() * i64::try_from(i).unwrap()
2572                ).unwrap(),
2573                successful_queries: 1u64,
2574                failed_queries: 0u64,
2575                average_success_duration_micros: u64::try_from(DELAY.into_micros()).unwrap(),
2576                errors: {
2577                    Message: 0u64,
2578                    NoConnections: 0u64,
2579                    NoRecordsFoundResponseCodeCounts: {},
2580                    IoErrorCounts: {},
2581                    ProtoErrorCounts: {},
2582                    Timeout: 0u64,
2583                    UnhandledResolveErrorKindCounts: {},
2584                },
2585                address_counts: {
2586                    "1": {
2587                        count: 1u64
2588                    },
2589                },
2590            });
2591            expected.add_child_assertion(child);
2592        }
2593        assert_data_tree!(@executor exec, inspector, root: {
2594            expected,
2595        });
2596    }
2597
2598    struct BlockingResolver {}
2599
2600    impl ResolverLookup for BlockingResolver {
2601        fn new(_config: ResolverConfig, _options: ResolverOpts) -> Self {
2602            BlockingResolver {}
2603        }
2604
2605        async fn lookup<N: IntoName + Send>(
2606            &self,
2607            _name: N,
2608            _record_type: RecordType,
2609        ) -> Result<lookup::Lookup, ResolveError> {
2610            futures::future::pending().await
2611        }
2612
2613        async fn reverse_lookup(
2614            &self,
2615            _addr: IpAddr,
2616        ) -> Result<lookup::ReverseLookup, ResolveError> {
2617            panic!("BlockingResolver does not handle reverse lookup")
2618        }
2619    }
2620
2621    #[fuchsia::test(logging = false)]
2622    async fn test_parallel_query_limit() {
2623        // Collect requests by setting up a FIDL proxy and stream for the Lookup
2624        // protocol, because there isn't a good way to directly construct fake
2625        // requests to be used for testing.
2626        let requests = {
2627            let (name_lookup_proxy, name_lookup_stream) =
2628                fidl::endpoints::create_proxy_and_stream::<fname::LookupMarker>();
2629            const NUM_REQUESTS: usize = MAX_PARALLEL_REQUESTS * 2 + 2;
2630            for _ in 0..NUM_REQUESTS {
2631                // Don't await on this future because we are using these
2632                // requests to collect FIDL responders in order to send test
2633                // requests later, and will not respond to these requests.
2634                let _: fidl::client::QueryResponseFut<fname::LookupLookupIpResult> =
2635                    name_lookup_proxy.lookup_ip(
2636                        LOCAL_HOST,
2637                        &fname::LookupIpOptions {
2638                            ipv4_lookup: Some(true),
2639                            ipv6_lookup: Some(true),
2640                            ..Default::default()
2641                        },
2642                    );
2643            }
2644            // Terminate the stream so its items can be collected below.
2645            drop(name_lookup_proxy);
2646            let requests = name_lookup_stream
2647                .map(|request| match request.expect("channel error") {
2648                    LookupRequest::LookupIp { hostname, options, responder } => {
2649                        IpLookupRequest { hostname, options, responder }
2650                    }
2651                    req => panic!("Expected LookupRequest::LookupIp request, found {:?}", req),
2652                })
2653                .collect::<Vec<_>>()
2654                .await;
2655            assert_eq!(requests.len(), NUM_REQUESTS);
2656            requests
2657        };
2658
2659        let (mut sender, recv) = mpsc::channel(MAX_PARALLEL_REQUESTS);
2660
2661        // The channel's capacity is equal to buffer + num-senders. Thus the
2662        // channel has a capacity of `MAX_PARALLEL_REQUESTS` + 1, and the
2663        // `for_each_concurrent` future has a limit of `MAX_PARALLEL_REQUESTS`,
2664        // so the sender should be able to queue `MAX_PARALLEL_REQUESTS` * 2 + 1
2665        // requests before `send` fails.
2666        const BEFORE_LAST_INDEX: usize = MAX_PARALLEL_REQUESTS * 2;
2667        const LAST_INDEX: usize = MAX_PARALLEL_REQUESTS * 2 + 1;
2668        let mut send_fut = pin!(
2669            async {
2670                for (i, req) in requests.into_iter().enumerate() {
2671                    match i {
2672                        BEFORE_LAST_INDEX => assert_matches!(sender.try_send(req), Ok(())),
2673                        LAST_INDEX => assert_matches!(sender.try_send(req), Err(e) if e.is_full()),
2674                        _ => assert_matches!(sender.send(req).await, Ok(())),
2675                    }
2676                }
2677            }
2678            .fuse()
2679        );
2680        let mut recv_fut = pin!({
2681            let resolver = SharedResolver::new(BlockingResolver::new(
2682                ResolverConfig::default(),
2683                ResolverOpts::default(),
2684            ));
2685            let stats = Arc::new(QueryStats::new());
2686            let (routes_proxy, _routes_stream) =
2687                fidl::endpoints::create_proxy_and_stream::<fnet_routes::StateMarker>();
2688            async move { create_ip_lookup_fut(&resolver, stats.clone(), routes_proxy, recv).await }
2689                .fuse()
2690        });
2691        futures::select! {
2692            () = send_fut => {},
2693            () = recv_fut => panic!("recv_fut should never complete"),
2694        };
2695    }
2696
2697    #[test]
2698    fn test_failure_stats() {
2699        use anyhow::anyhow;
2700        use trust_dns_proto::error::ProtoError;
2701        use trust_dns_proto::op::Query;
2702
2703        let mut stats = FailureStats::default();
2704        for (error_kind, expected) in &[
2705            (ResolveErrorKind::Message("foo"), FailureStats { message: 1, ..Default::default() }),
2706            (
2707                ResolveErrorKind::Msg("foo".to_string()),
2708                FailureStats { message: 2, ..Default::default() },
2709            ),
2710            (
2711                ResolveErrorKind::NoRecordsFound {
2712                    query: Box::new(Query::default()),
2713                    soa: None,
2714                    negative_ttl: None,
2715                    response_code: ResponseCode::Refused,
2716                    trusted: false,
2717                },
2718                FailureStats {
2719                    message: 2,
2720                    no_records_found: NoRecordsFoundStats {
2721                        response_code_counts: [(ResponseCode::Refused.into(), 1)].into(),
2722                    },
2723                    ..Default::default()
2724                },
2725            ),
2726            (
2727                ResolveErrorKind::Io(std::io::Error::new(
2728                    std::io::ErrorKind::NotFound,
2729                    anyhow!("foo"),
2730                )),
2731                FailureStats {
2732                    message: 2,
2733                    no_records_found: NoRecordsFoundStats {
2734                        response_code_counts: [(ResponseCode::Refused.into(), 1)].into(),
2735                    },
2736                    io: IoErrorStats([(std::io::ErrorKind::NotFound, 1)].into()),
2737                    ..Default::default()
2738                },
2739            ),
2740            (
2741                ResolveErrorKind::Proto(ProtoError::from("foo")),
2742                FailureStats {
2743                    message: 2,
2744                    no_records_found: NoRecordsFoundStats {
2745                        response_code_counts: [(ResponseCode::Refused.into(), 1)].into(),
2746                    },
2747                    io: IoErrorStats([(std::io::ErrorKind::NotFound, 1)].into()),
2748                    proto: GenericErrorKindStats([(String::from("Message"), 1)].into()),
2749                    ..Default::default()
2750                },
2751            ),
2752            (
2753                ResolveErrorKind::NoConnections,
2754                FailureStats {
2755                    message: 2,
2756                    no_connections: 1,
2757                    no_records_found: NoRecordsFoundStats {
2758                        response_code_counts: [(ResponseCode::Refused.into(), 1)].into(),
2759                    },
2760                    io: IoErrorStats([(std::io::ErrorKind::NotFound, 1)].into()),
2761                    proto: GenericErrorKindStats([(String::from("Message"), 1)].into()),
2762                    ..Default::default()
2763                },
2764            ),
2765            (
2766                ResolveErrorKind::Timeout,
2767                FailureStats {
2768                    message: 2,
2769                    no_connections: 1,
2770                    no_records_found: NoRecordsFoundStats {
2771                        response_code_counts: [(ResponseCode::Refused.into(), 1)].into(),
2772                    },
2773                    io: IoErrorStats([(std::io::ErrorKind::NotFound, 1)].into()),
2774                    proto: GenericErrorKindStats([(String::from("Message"), 1)].into()),
2775                    timeout: 1,
2776                    unhandled_resolve_error_kind: Default::default(),
2777                },
2778            ),
2779            (
2780                ResolveErrorKind::NoRecordsFound {
2781                    query: Box::new(Query::default()),
2782                    soa: None,
2783                    negative_ttl: None,
2784                    response_code: ResponseCode::NXDomain,
2785                    trusted: false,
2786                },
2787                FailureStats {
2788                    message: 2,
2789                    no_connections: 1,
2790                    no_records_found: NoRecordsFoundStats {
2791                        response_code_counts: [
2792                            (ResponseCode::NXDomain.into(), 1),
2793                            (ResponseCode::Refused.into(), 1),
2794                        ]
2795                        .into(),
2796                    },
2797                    io: IoErrorStats([(std::io::ErrorKind::NotFound, 1)].into()),
2798                    proto: GenericErrorKindStats([(String::from("Message"), 1)].into()),
2799                    timeout: 1,
2800                    unhandled_resolve_error_kind: Default::default(),
2801                },
2802            ),
2803            (
2804                ResolveErrorKind::NoRecordsFound {
2805                    query: Box::new(Query::default()),
2806                    soa: None,
2807                    negative_ttl: None,
2808                    response_code: ResponseCode::NXDomain,
2809                    trusted: false,
2810                },
2811                FailureStats {
2812                    message: 2,
2813                    no_connections: 1,
2814                    no_records_found: NoRecordsFoundStats {
2815                        response_code_counts: [
2816                            (ResponseCode::NXDomain.into(), 2),
2817                            (ResponseCode::Refused.into(), 1),
2818                        ]
2819                        .into(),
2820                    },
2821                    io: IoErrorStats([(std::io::ErrorKind::NotFound, 1)].into()),
2822                    proto: GenericErrorKindStats([(String::from("Message"), 1)].into()),
2823                    timeout: 1,
2824                    unhandled_resolve_error_kind: Default::default(),
2825                },
2826            ),
2827            (
2828                ResolveErrorKind::Proto(ProtoError::from(std::io::Error::new(
2829                    std::io::ErrorKind::ConnectionAborted,
2830                    anyhow!("foo"),
2831                ))),
2832                FailureStats {
2833                    message: 2,
2834                    no_connections: 1,
2835                    no_records_found: NoRecordsFoundStats {
2836                        response_code_counts: [
2837                            (ResponseCode::NXDomain.into(), 2),
2838                            (ResponseCode::Refused.into(), 1),
2839                        ]
2840                        .into(),
2841                    },
2842                    io: IoErrorStats(
2843                        [
2844                            (std::io::ErrorKind::NotFound, 1),
2845                            (std::io::ErrorKind::ConnectionAborted, 1),
2846                        ]
2847                        .into(),
2848                    ),
2849                    proto: GenericErrorKindStats([(String::from("Message"), 1)].into()),
2850                    timeout: 1,
2851                    unhandled_resolve_error_kind: Default::default(),
2852                },
2853            ),
2854        ][..]
2855        {
2856            stats.increment(error_kind);
2857            assert_eq!(&stats, expected, "invalid stats after incrementing with {:?}", error_kind);
2858        }
2859    }
2860
2861    fn test_das_helper(
2862        l_addr: fnet::IpAddress,
2863        l_src: Option<fnet::IpAddress>,
2864        r_addr: fnet::IpAddress,
2865        r_src: Option<fnet::IpAddress>,
2866        want: std::cmp::Ordering,
2867    ) {
2868        let left = DasCmpInfo::from_addrs(&l_addr, l_src.as_ref());
2869        let right = DasCmpInfo::from_addrs(&r_addr, r_src.as_ref());
2870        assert_eq!(
2871            left.cmp(&right),
2872            want,
2873            "want = {:?}\n left = {:?}({:?}) DAS={:?}\n right = {:?}({:?}) DAS={:?}",
2874            want,
2875            l_addr,
2876            l_src,
2877            left,
2878            r_addr,
2879            r_src,
2880            right
2881        );
2882    }
2883
2884    macro_rules! add_das_test {
2885        ($name:ident, preferred: $pref_dst:expr => $pref_src:expr, other: $other_dst:expr => $other_src:expr) => {
2886            #[test]
2887            fn $name() {
2888                test_das_helper(
2889                    $pref_dst,
2890                    $pref_src,
2891                    $other_dst,
2892                    $other_src,
2893                    std::cmp::Ordering::Less,
2894                )
2895            }
2896        };
2897    }
2898
2899    add_das_test!(
2900        prefer_reachable,
2901        preferred: fidl_ip!("198.51.100.121") => Some(fidl_ip!("198.51.100.117")),
2902        other: fidl_ip!("2001:db8:1::1") => Option::<fnet::IpAddress>::None
2903    );
2904
2905    // These test cases are taken from RFC 6724, section 10.2.
2906
2907    add_das_test!(
2908        prefer_matching_scope,
2909        preferred: fidl_ip!("198.51.100.121") => Some(fidl_ip!("198.51.100.117")),
2910        other: fidl_ip!("2001:db8:1::1") => Some(fidl_ip!("fe80::1"))
2911    );
2912
2913    add_das_test!(
2914        prefer_matching_label,
2915        preferred: fidl_ip!("2002:c633:6401::1") => Some(fidl_ip!("2002:c633:6401::2")),
2916        other:  fidl_ip!("2001:db8:1::1") => Some(fidl_ip!("2002:c633:6401::2"))
2917    );
2918
2919    add_das_test!(
2920        prefer_higher_precedence_1,
2921        preferred: fidl_ip!("2001:db8:1::1") => Some(fidl_ip!("2001:db8:1::2")),
2922        other: fidl_ip!("10.1.2.3") => Some(fidl_ip!("10.1.2.4"))
2923    );
2924
2925    add_das_test!(
2926        prefer_higher_precedence_2,
2927        preferred: fidl_ip!("2001:db8:1::1") => Some(fidl_ip!("2001:db8:1::2")),
2928        other: fidl_ip!("2002:c633:6401::1") => Some(fidl_ip!("2002:c633:6401::2"))
2929    );
2930
2931    add_das_test!(
2932        prefer_smaller_scope,
2933        preferred: fidl_ip!("fe80::1") => Some(fidl_ip!("fe80::2")),
2934        other: fidl_ip!("2001:db8:1::1") => Some(fidl_ip!("2001:db8:1::2"))
2935    );
2936
2937    add_das_test!(
2938        prefer_longest_matching_prefix,
2939        preferred: fidl_ip!("2001:db8:1::1") => Some(fidl_ip!("2001:db8:1::2")),
2940        other: fidl_ip!("2001:db8:3ffe::1") => Some(fidl_ip!("2001:db8:3f44::2"))
2941    );
2942
2943    #[test]
2944    fn test_das_equals() {
2945        for (dst, src) in [
2946            (fidl_ip!("192.168.0.1"), fidl_ip!("192.168.0.2")),
2947            (fidl_ip!("2001:db8::1"), fidl_ip!("2001:db8::2")),
2948        ]
2949        .iter()
2950        {
2951            test_das_helper(*dst, None, *dst, None, std::cmp::Ordering::Equal);
2952            test_das_helper(*dst, Some(*src), *dst, Some(*src), std::cmp::Ordering::Equal);
2953        }
2954    }
2955
2956    #[test]
2957    fn test_valid_policy_table() {
2958        // Last element in policy table MUST be ::/0.
2959        assert_eq!(
2960            POLICY_TABLE.iter().last().expect("empty policy table").prefix,
2961            net_types::ip::Ipv6::ALL_ADDRS_SUBNET
2962        );
2963        // Policy table must be sorted by prefix length.
2964        POLICY_TABLE.array_windows().for_each(|[w0, w1]| {
2965            let Policy { prefix: cur, precedence: _, label: _ } = w0;
2966            let Policy { prefix: nxt, precedence: _, label: _ } = w1;
2967            assert!(
2968                cur.prefix() >= nxt.prefix(),
2969                "bad ordering of prefixes, {} must come after {}",
2970                cur,
2971                nxt
2972            )
2973        });
2974        // Assert that POLICY_TABLE declaration does not use any invalid
2975        // subnets.
2976        for policy in POLICY_TABLE.iter() {
2977            assert!(policy.prefix.prefix() <= 128, "Invalid subnet in policy {:?}", policy);
2978        }
2979    }
2980
2981    #[fuchsia::test(logging = false)]
2982    async fn test_sort_preferred_addresses() {
2983        const TEST_IPS: [(fnet::IpAddress, Option<fnet::IpAddress>); 5] = [
2984            (fidl_ip!("127.0.0.1"), Some(fidl_ip!("127.0.0.1"))),
2985            (fidl_ip!("::1"), Some(fidl_ip!("::1"))),
2986            (fidl_ip!("192.168.50.22"), None),
2987            (fidl_ip!("2001::2"), None),
2988            (fidl_ip!("2001:db8:1::1"), Some(fidl_ip!("2001:db8:1::2"))),
2989        ];
2990        // Declared using std types so we get cleaner output when we assert
2991        // expectations.
2992        const SORTED: [IpAddr; 5] = [
2993            std_ip!("::1"),
2994            std_ip!("2001:db8:1::1"),
2995            std_ip!("127.0.0.1"),
2996            std_ip!("192.168.50.22"),
2997            std_ip!("2001::2"),
2998        ];
2999        let (routes_proxy, routes_stream) =
3000            fidl::endpoints::create_proxy_and_stream::<fnet_routes::StateMarker>();
3001        let routes_fut =
3002            routes_stream.map(|r| r.context("stream FIDL error")).try_for_each(|req| {
3003                let (destination, responder) = assert_matches!(
3004                    req,
3005                    fnet_routes::StateRequest::Resolve { destination, responder }
3006                        => (destination, responder)
3007                );
3008                let result = TEST_IPS
3009                    .iter()
3010                    .enumerate()
3011                    .find_map(|(i, (dst, src))| {
3012                        if *dst == destination && src.is_some() {
3013                            let inner = fnet_routes::Destination {
3014                                address: Some(*dst),
3015                                source_address: *src,
3016                                ..Default::default()
3017                            };
3018                            // Send both Direct and Gateway resolved routes to show we
3019                            // don't care about that part.
3020                            if i % 2 == 0 {
3021                                Some(fnet_routes::Resolved::Direct(inner))
3022                            } else {
3023                                Some(fnet_routes::Resolved::Gateway(inner))
3024                            }
3025                        } else {
3026                            None
3027                        }
3028                    })
3029                    .ok_or_else(|| zx::Status::ADDRESS_UNREACHABLE.into_raw());
3030                futures::future::ready(
3031                    responder
3032                        .send(result.as_ref().map_err(|e| *e))
3033                        .context("failed to send Resolve response"),
3034                )
3035            });
3036
3037        let ((), ()) = futures::future::try_join(routes_fut, async move {
3038            let addrs = TEST_IPS.iter().map(|(dst, _src)| *dst).collect();
3039            let addrs = sort_preferred_addresses(addrs, &routes_proxy)
3040                .await
3041                .expect("failed to sort addresses");
3042            let addrs = addrs
3043                .into_iter()
3044                .map(|a| {
3045                    let net_ext::IpAddress(a) = a.into();
3046                    a
3047                })
3048                .collect::<Vec<_>>();
3049            assert_eq!(&addrs[..], &SORTED[..]);
3050            Ok(())
3051        })
3052        .await
3053        .expect("error running futures");
3054    }
3055
3056    #[fuchsia::test(logging = false)]
3057    async fn test_lookupip() {
3058        // Routes handler will say that only IPV6_HOST is reachable.
3059        let routes_handler = |req| {
3060            let (destination, responder) = assert_matches!(
3061                req,
3062                fnet_routes::StateRequest::Resolve { destination, responder }
3063                    => (destination, responder)
3064            );
3065            let resolved;
3066            let response = if destination == map_ip(IPV6_HOST) {
3067                resolved = fnet_routes::Resolved::Direct(fnet_routes::Destination {
3068                    address: Some(destination),
3069                    source_address: Some(destination),
3070                    ..Default::default()
3071                });
3072                Ok(&resolved)
3073            } else {
3074                Err(zx::Status::ADDRESS_UNREACHABLE.into_raw())
3075            };
3076            responder.send(response).expect("failed to send Resolve FIDL response");
3077        };
3078        TestEnvironment::default()
3079            .run_lookup_with_routes_handler(
3080                |proxy| async move {
3081                    // All arguments unset.
3082                    assert_eq!(
3083                        proxy
3084                            .lookup_ip(REMOTE_IPV4_HOST, &fname::LookupIpOptions::default())
3085                            .await
3086                            .expect("lookup_ip"),
3087                        Err(fname::LookupError::InvalidArgs)
3088                    );
3089                    // No IP addresses to look.
3090                    assert_eq!(
3091                        proxy
3092                            .lookup_ip(
3093                                REMOTE_IPV4_HOST,
3094                                &fname::LookupIpOptions {
3095                                    ipv4_lookup: Some(false),
3096                                    ipv6_lookup: Some(false),
3097                                    ..Default::default()
3098                                }
3099                            )
3100                            .await
3101                            .expect("lookup_ip"),
3102                        Err(fname::LookupError::InvalidArgs)
3103                    );
3104                    // No results for an IPv4 only host.
3105                    assert_eq!(
3106                        proxy
3107                            .lookup_ip(
3108                                REMOTE_IPV4_HOST,
3109                                &fname::LookupIpOptions {
3110                                    ipv4_lookup: Some(false),
3111                                    ipv6_lookup: Some(true),
3112                                    ..Default::default()
3113                                }
3114                            )
3115                            .await
3116                            .expect("lookup_ip"),
3117                        Err(fname::LookupError::NotFound)
3118                    );
3119                    // Successfully resolve IPv4.
3120                    assert_eq!(
3121                        proxy
3122                            .lookup_ip(
3123                                REMOTE_IPV4_HOST,
3124                                &fname::LookupIpOptions {
3125                                    ipv4_lookup: Some(true),
3126                                    ipv6_lookup: Some(true),
3127                                    ..Default::default()
3128                                }
3129                            )
3130                            .await
3131                            .expect("lookup_ip"),
3132                        Ok(fname::LookupResult {
3133                            addresses: Some(vec![map_ip(IPV4_HOST)]),
3134                            ..Default::default()
3135                        })
3136                    );
3137                    // Successfully resolve IPv4 + IPv6 (no sorting).
3138                    assert_eq!(
3139                        proxy
3140                            .lookup_ip(
3141                                REMOTE_IPV4_IPV6_HOST,
3142                                &fname::LookupIpOptions {
3143                                    ipv4_lookup: Some(true),
3144                                    ipv6_lookup: Some(true),
3145                                    ..Default::default()
3146                                }
3147                            )
3148                            .await
3149                            .expect("lookup_ip"),
3150                        Ok(fname::LookupResult {
3151                            addresses: Some(vec![map_ip(IPV4_HOST), map_ip(IPV6_HOST)]),
3152                            ..Default::default()
3153                        })
3154                    );
3155                    // Successfully resolve IPv4 + IPv6 (with sorting).
3156                    assert_eq!(
3157                        proxy
3158                            .lookup_ip(
3159                                REMOTE_IPV4_IPV6_HOST,
3160                                &fname::LookupIpOptions {
3161                                    ipv4_lookup: Some(true),
3162                                    ipv6_lookup: Some(true),
3163                                    sort_addresses: Some(true),
3164                                    ..Default::default()
3165                                }
3166                            )
3167                            .await
3168                            .expect("lookup_ip"),
3169                        Ok(fname::LookupResult {
3170                            addresses: Some(vec![map_ip(IPV6_HOST), map_ip(IPV4_HOST)]),
3171                            ..Default::default()
3172                        })
3173                    );
3174                },
3175                routes_handler,
3176            )
3177            .await
3178    }
3179}