1use 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)]
43struct 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
82struct QueryStats {
93 inner: Mutex<VecDeque<QueryWindow>>,
94}
95
96type 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 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
153impl 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#[derive(Default, Debug, PartialEq)]
191struct GenericErrorKindStats(HashMap<String, u64>);
192
193impl GenericErrorKindStats {
194 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 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#[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 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
388fn enum_variant_string(variant: &impl std::fmt::Debug) -> String {
390 let debug = format!("{:?}", variant);
391 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 resolver_opts.num_concurrent_reqs = 10;
405 resolver_opts.server_ordering_strategy = ServerOrderingStrategy::UserProvidedOrder;
408
409 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 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 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 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 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 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(..)
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 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 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 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
707const 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#[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 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 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 prefer_true(*self_usable, *other_usable)
841 .then(
842 prefer_true(*self_matching_scope, *other_matching_scope),
844 )
845 .then(
846 prefer_true(*self_matching_label, *other_matching_label),
848 )
849 .then(
850 self_precedence.cmp(other_precedence).reverse(),
852 )
853 .then(
854 self_scope.multicast_scope_id().cmp(&other_scope.multicast_scope_id()),
856 )
857 .then(
858 self_common_prefix_len.cmp(other_common_prefix_len).reverse(),
860 )
861 }
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 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 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 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 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 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
1128async 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
1159fn 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
1182fn 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
1222fn 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 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#[fuchsia::main(logging_tags = ["dns"])]
1298pub async fn main() -> Result<(), Error> {
1299 info!("starting");
1300
1301 let mut resolver_opts = ResolverOpts::default();
1302 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 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 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 const IPV4_HOST: Ipv4Addr = std_ip_v4!("240.0.0.2");
1391 const IPV6_HOST: Ipv6Addr = std_ip_v6!("abcd::2");
1393
1394 const REMOTE_IPV4_HOST: &str = "www.foo.com";
1396 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 const REMOTE_IPV6_HOST_EXTRA: &str = "www.bar2.com";
1402 const REMOTE_IPV4_IPV6_HOST: &str = "www.foobar.com";
1404 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 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 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 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 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, RData::PTR(Name::from_str(REMOTE_IPV6_HOST).unwrap()),
1602 ),
1603 Record::from_rdata(
1604 Name::new(),
1605 60, 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 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 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 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 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 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 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 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 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 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 #[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_eq!(env.shared_resolver.read().config.name_servers().to_vec(), vec![]);
2028
2029 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 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_eq!(env.shared_resolver.read().config.name_servers().to_vec(), vec![]);
2067
2068 env.run_admin(|proxy| async move {
2069 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 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_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 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 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 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(NON_ZERO_USIZE_ONE),
2314 zx::MonotonicDuration::from_nanos(0),
2315 );
2316 run_fake_lookup(
2317 &mut exec,
2318 stats.clone(),
2319 Ok(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 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(NON_ZERO_USIZE_ONE), DELAY);
2558 let () =
2559 exec.set_fake_time(fasync::MonotonicInstant::after(STAT_WINDOW_DURATION - DELAY));
2560 }
2561 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 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 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 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 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 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 assert_eq!(
2960 POLICY_TABLE.iter().last().expect("empty policy table").prefix,
2961 net_types::ip::Ipv6::ALL_ADDRS_SUBNET
2962 );
2963 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 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 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 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 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 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 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 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 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 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 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}