Skip to main content

hickory_resolver/
lookup_ip.rs

1// Copyright 2015-2017 Benjamin Fry <benjaminfry@me.com>
2//
3// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
4// https://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
5// https://opensource.org/licenses/MIT>, at your option. This file may not be
6// copied, modified, or distributed except according to those terms.
7
8//! LookupIp result from a resolution of ipv4 and ipv6 records with a Resolver.
9//!
10//! At its heart LookupIp uses Lookup for performing all lookups. It is unlike other standard lookups in that there are customizations around A and AAAA resolutions.
11
12use std::future::Future;
13use std::iter::FusedIterator;
14use std::net::IpAddr;
15use std::pin::Pin;
16use std::slice;
17use std::sync::Arc;
18use std::task::{Context, Poll};
19use std::time::Instant;
20
21use futures_util::{
22    FutureExt,
23    future::{self, BoxFuture},
24};
25use tracing::debug;
26
27use crate::cache::MAX_TTL;
28use crate::caching_client::CachingClient;
29use crate::config::LookupIpStrategy;
30use crate::hosts::Hosts;
31use crate::lookup::Lookup;
32use crate::net::NetError;
33use crate::net::xfer::DnsHandle;
34use crate::proto::op::{DnsRequestOptions, Message, Query};
35use crate::proto::rr::{Name, RData, Record, RecordType};
36
37/// Result of a DNS query when querying for A or AAAA records.
38///
39/// When resolving IP records, there can be many IPs that match a given name. A consumer of this should expect that there are more than a single address potentially returned. Generally there are multiple IPs stored for a given service in DNS so that there is a form of high availability offered for a given name. The service implementation is responsible for the semantics around which IP should be used and when, but in general if a connection fails to one, the next in the list should be attempted.
40#[derive(Debug, Clone)]
41pub struct LookupIp(Lookup);
42
43impl LookupIp {
44    /// Returns an iterator over the response records.
45    ///
46    /// Only IP records will be returned, either A or AAAA record types.
47    pub fn iter(&self) -> LookupIpIter<'_> {
48        LookupIpIter(self.0.answers().iter())
49    }
50
51    /// Returns a reference to the `Query` that was used to produce this result.
52    pub fn query(&self) -> &Query {
53        self.0.query()
54    }
55
56    /// Returns the `Instant` at which this lookup is no longer valid.
57    pub fn valid_until(&self) -> Instant {
58        self.0.valid_until()
59    }
60
61    /// Return a reference to the inner lookup
62    ///
63    /// This can be useful for getting all records from the request
64    pub fn as_lookup(&self) -> &Lookup {
65        &self.0
66    }
67}
68
69impl From<Lookup> for LookupIp {
70    fn from(lookup: Lookup) -> Self {
71        Self(lookup)
72    }
73}
74
75impl From<LookupIp> for Lookup {
76    fn from(lookup: LookupIp) -> Self {
77        lookup.0
78    }
79}
80
81impl IntoIterator for LookupIp {
82    type Item = IpAddr;
83    type IntoIter = LookupIpIntoIter;
84
85    fn into_iter(self) -> Self::IntoIter {
86        let message = Message::from(self.0);
87        LookupIpIntoIter(message.answers.into_iter())
88    }
89}
90
91/// Borrowed view of set of IPs returned from a LookupIp
92pub struct LookupIpIter<'a>(slice::Iter<'a, Record>);
93
94impl Iterator for LookupIpIter<'_> {
95    type Item = IpAddr;
96
97    fn next(&mut self) -> Option<Self::Item> {
98        self.0.find_map(|record| record.data.ip_addr())
99    }
100}
101
102impl FusedIterator for LookupIpIter<'_> {}
103
104/// Owned iterator over the IP addresses returned from a LookupIp.
105pub struct LookupIpIntoIter(std::vec::IntoIter<Record>);
106
107impl Iterator for LookupIpIntoIter {
108    type Item = IpAddr;
109
110    fn next(&mut self) -> Option<Self::Item> {
111        self.0.find_map(|record| record.data.ip_addr())
112    }
113}
114
115impl FusedIterator for LookupIpIntoIter {}
116
117/// The Future returned from [crate::Resolver] when performing an A or AAAA lookup.
118///
119/// This type isn't necessarily something that should be used by users, see the default TypeParameters are generally correct
120pub struct LookupIpFuture<C: DnsHandle + 'static> {
121    client_cache: CachingClient<C>,
122    names: Vec<Name>,
123    strategy: LookupIpStrategy,
124    options: DnsRequestOptions,
125    query: BoxFuture<'static, Result<Lookup, NetError>>,
126    hosts: Arc<Hosts>,
127    finally_ip_addr: Option<RData>,
128}
129
130impl<C: DnsHandle + 'static> LookupIpFuture<C> {
131    /// Perform a lookup from a hostname to a set of IPs
132    ///
133    /// # Arguments
134    ///
135    /// * `names` - a set of DNS names to attempt to resolve, they will be attempted in queue order, i.e. the first is `names.pop()`. Upon each failure, the next will be attempted.
136    /// * `strategy` - the lookup IP strategy to use
137    /// * `client_cache` - cache with a connection to use for performing all lookups
138    pub fn lookup(
139        names: Vec<Name>,
140        strategy: LookupIpStrategy,
141        client_cache: CachingClient<C>,
142        options: DnsRequestOptions,
143        hosts: Arc<Hosts>,
144        finally_ip_addr: Option<RData>,
145    ) -> Self {
146        Self {
147            names,
148            strategy,
149            client_cache,
150            // If there are no names remaining, this will be returned immediately,
151            // otherwise, it will be retried.
152            query: future::err("can not lookup IPs for no names".into()).boxed(),
153            options,
154            hosts,
155            finally_ip_addr,
156        }
157    }
158}
159
160impl<C: DnsHandle + 'static> Future for LookupIpFuture<C> {
161    type Output = Result<LookupIp, NetError>;
162
163    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
164        loop {
165            // Try polling the underlying DNS query.
166            let query = self.query.as_mut().poll(cx);
167
168            // Determine whether or not we will attempt to retry the query.
169            let should_retry = match &query {
170                // If the query is NotReady, yield immediately.
171                Poll::Pending => return Poll::Pending,
172                // If the query returned a successful lookup, we will attempt
173                // to retry if the lookup is empty. Otherwise, we will return
174                // that lookup.
175                Poll::Ready(Ok(lookup)) => lookup.answers().is_empty(),
176                // If the query failed, we will attempt to retry.
177                Poll::Ready(Err(_)) => true,
178            };
179
180            if !should_retry {
181                // If we didn't have to retry the query, or we weren't able to
182                // retry because we've exhausted the names to search and have no
183                // fallback IP address, return the current query.
184                return query.map(|f| f.map(LookupIp::from));
185            }
186
187            if let Some(name) = self.names.pop() {
188                // If there's another name left to try, build a new query
189                // for that next name and continue looping.
190                self.query = LookupContext {
191                    client: self.client_cache.clone(),
192                    options: self.options,
193                    hosts: self.hosts.clone(),
194                }
195                .strategic_lookup(name, self.strategy)
196                .boxed();
197                // Continue looping with the new query. It will be polled
198                // on the next iteration of the loop.
199                continue;
200            } else if let Some(ip_addr) = self.finally_ip_addr.take() {
201                // Otherwise, if there's an IP address to fall back to,
202                // we'll return it.
203                let record = Record::from_rdata(Name::new(), MAX_TTL, ip_addr);
204                let lookup = Lookup::new_with_max_ttl(Query::new(), [record]);
205                return Poll::Ready(Ok(lookup.into()));
206            }
207
208            // If we skipped retrying the  query, this will return the
209            // successful lookup, otherwise, if the retry failed, this will
210            // return the last  query result --- either an empty lookup or the
211            // last error we saw.
212            return query.map(|f| f.map(LookupIp::from));
213        }
214    }
215}
216
217#[derive(Clone)]
218struct LookupContext<C: DnsHandle> {
219    client: CachingClient<C>,
220    options: DnsRequestOptions,
221    hosts: Arc<Hosts>,
222}
223
224impl<C: DnsHandle> LookupContext<C> {
225    /// returns a new future for lookup
226    async fn strategic_lookup(
227        self,
228        name: Name,
229        strategy: LookupIpStrategy,
230    ) -> Result<Lookup, NetError> {
231        match strategy {
232            LookupIpStrategy::Ipv4Only => self.ipv4_only(name).await,
233            LookupIpStrategy::Ipv6Only => self.ipv6_only(name).await,
234            LookupIpStrategy::Ipv4AndIpv6 => self.ipv4_and_ipv6(name).await,
235            LookupIpStrategy::Ipv6AndIpv4 => self.ipv6_and_ipv4(name).await,
236            LookupIpStrategy::Ipv6thenIpv4 => self.ipv6_then_ipv4(name).await,
237            LookupIpStrategy::Ipv4thenIpv6 => self.ipv4_then_ipv6(name).await,
238        }
239    }
240
241    /// queries only for A records
242    async fn ipv4_only(&self, name: Name) -> Result<Lookup, NetError> {
243        self.hosts_lookup(Query::query(name, RecordType::A)).await
244    }
245
246    /// queries only for AAAA records
247    async fn ipv6_only(&self, name: Name) -> Result<Lookup, NetError> {
248        self.hosts_lookup(Query::query(name, RecordType::AAAA))
249            .await
250    }
251
252    // TODO: this really needs to have a stream interface
253    /// queries for A and AAAA in parallel, ordering A before AAAA
254    async fn ipv4_and_ipv6(&self, name: Name) -> Result<Lookup, NetError> {
255        self.multi_lookup(name, RecordType::A, RecordType::AAAA)
256            .await
257    }
258
259    // TODO: this really needs to have a stream interface
260    /// queries for AAAA and A in parallel, ordering AAAA before A
261    async fn ipv6_and_ipv4(&self, name: Name) -> Result<Lookup, NetError> {
262        self.multi_lookup(name, RecordType::AAAA, RecordType::A)
263            .await
264    }
265
266    /// makes queries for both RecordTypes in parallel, ordering the result
267    async fn multi_lookup(
268        &self,
269        name: Name,
270        first_type: RecordType,
271        second_type: RecordType,
272    ) -> Result<Lookup, NetError> {
273        let joined_res = future::join(
274            self.hosts_lookup(Query::query(name.clone(), first_type)),
275            self.hosts_lookup(Query::query(name, second_type)),
276        )
277        .await;
278
279        match joined_res {
280            (Ok(first), Ok(second)) => {
281                // TODO: create a LookupIp enum with the ability to chain these together
282                let ips = first.append(second);
283                Ok(ips)
284            }
285            (Ok(ips), Err(e)) | (Err(e), Ok(ips)) => {
286                debug!("one of ipv4 or ipv6 lookup failed: {e}");
287                Ok(ips)
288            }
289            (Err(e1), Err(e2)) => {
290                debug!("both of ipv4 or ipv6 lookup failed e1: {e1}, e2: {e2}");
291                Err(e1)
292            }
293        }
294    }
295
296    /// queries only for AAAA and on no results queries for A
297    async fn ipv6_then_ipv4(&self, name: Name) -> Result<Lookup, NetError> {
298        self.rt_then_swap(name, RecordType::AAAA, RecordType::A)
299            .await
300    }
301
302    /// queries only for A and on no results queries for AAAA
303    async fn ipv4_then_ipv6(&self, name: Name) -> Result<Lookup, NetError> {
304        self.rt_then_swap(name, RecordType::A, RecordType::AAAA)
305            .await
306    }
307
308    /// queries only for first_type and on no results queries for second_type
309    async fn rt_then_swap(
310        &self,
311        name: Name,
312        first_type: RecordType,
313        second_type: RecordType,
314    ) -> Result<Lookup, NetError> {
315        let res = self
316            .hosts_lookup(Query::query(name.clone(), first_type))
317            .await;
318
319        match res {
320            Ok(ips) if !ips.answers().is_empty() => Ok(ips),
321            // no ips returned, NXDomain or Otherwise, doesn't matter
322            _ => self.hosts_lookup(Query::query(name, second_type)).await,
323        }
324    }
325
326    /// first lookups in hosts, then performs the query
327    async fn hosts_lookup(&self, query: Query) -> Result<Lookup, NetError> {
328        match self.hosts.lookup_static_host(&query) {
329            Some(lookup) => Ok(lookup),
330            None => self.client.lookup(query, self.options).await,
331        }
332    }
333}
334
335#[cfg(test)]
336pub(crate) mod tests {
337    use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
338    use std::str::FromStr;
339    use std::sync::{Arc, Mutex};
340    use std::thread;
341
342    use futures_executor::block_on;
343    use futures_util::future;
344    use futures_util::stream::{Stream, once};
345    use test_support::subscribe;
346
347    use super::*;
348    use crate::net::runtime::TokioRuntimeProvider;
349    use crate::net::xfer::DnsHandle;
350    use crate::proto::op::{DnsRequest, DnsResponse, Message};
351    use crate::proto::rr::rdata::NS;
352    use crate::proto::rr::{Name, RData, Record};
353
354    #[derive(Clone)]
355    pub(crate) struct MockDnsHandle {
356        messages: Arc<Mutex<Vec<Result<DnsResponse, NetError>>>>,
357    }
358
359    impl DnsHandle for MockDnsHandle {
360        type Response = Pin<Box<dyn Stream<Item = Result<DnsResponse, NetError>> + Send + Unpin>>;
361        type Runtime = TokioRuntimeProvider;
362
363        fn send(&self, _: DnsRequest) -> Self::Response {
364            Box::pin(once(future::ready(
365                self.messages.lock().unwrap().pop().unwrap_or_else(empty),
366            )))
367        }
368    }
369
370    pub(crate) fn v4_message() -> Result<DnsResponse, NetError> {
371        let mut message = Message::query();
372        message.add_query(Query::query(Name::root(), RecordType::A));
373        message.insert_answers(vec![Record::from_rdata(
374            Name::root(),
375            86400,
376            RData::A(Ipv4Addr::LOCALHOST.into()),
377        )]);
378
379        let resp = DnsResponse::from_message(message.into_response()).unwrap();
380        assert!(resp.contains_answer());
381        Ok(resp)
382    }
383
384    pub(crate) fn v6_message() -> Result<DnsResponse, NetError> {
385        let mut message = Message::query();
386        message.add_query(Query::query(Name::root(), RecordType::AAAA));
387        message.insert_answers(vec![Record::from_rdata(
388            Name::root(),
389            86400,
390            RData::AAAA(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1).into()),
391        )]);
392
393        let resp = DnsResponse::from_message(message.into_response()).unwrap();
394        assert!(resp.contains_answer());
395        Ok(resp)
396    }
397
398    pub(crate) fn empty() -> Result<DnsResponse, NetError> {
399        Ok(DnsResponse::from_message(Message::query().into_response()).unwrap())
400    }
401
402    pub(crate) fn error() -> Result<DnsResponse, NetError> {
403        Err(NetError::from("forced test failure"))
404    }
405
406    pub(crate) fn mock(messages: Vec<Result<DnsResponse, NetError>>) -> MockDnsHandle {
407        MockDnsHandle {
408            messages: Arc::new(Mutex::new(messages)),
409        }
410    }
411
412    fn assert_static<T: 'static>() {}
413
414    #[test]
415    fn test_iterator_static() {
416        assert_static::<LookupIpIntoIter>();
417    }
418
419    #[test]
420    fn test_lookup_ip_into_iter_returns_ip_addresses() {
421        let mut message = Message::query();
422        message.add_query(Query::query(Name::root(), RecordType::A));
423        message.add_answers(vec![
424            Record::from_rdata(
425                Name::root(),
426                86400,
427                RData::A(Ipv4Addr::new(192, 0, 2, 1).into()),
428            ),
429            Record::from_rdata(
430                Name::root(),
431                86400,
432                RData::NS(NS(Name::from_str("ns.example.").unwrap())),
433            ),
434            Record::from_rdata(Name::root(), 86400, RData::AAAA(Ipv6Addr::LOCALHOST.into())),
435        ]);
436
437        let lookup = LookupIp::from(Lookup::new(message, Instant::now()));
438
439        assert_eq!(
440            lookup.into_iter().collect::<Vec<_>>(),
441            vec![
442                IpAddr::V4(Ipv4Addr::new(192, 0, 2, 1)),
443                IpAddr::V6(Ipv6Addr::LOCALHOST),
444            ]
445        );
446    }
447
448    #[test]
449    fn test_lookup_ip_into_iter_can_move_across_threads() {
450        let mut message = Message::query();
451        message.add_query(Query::query(Name::root(), RecordType::A));
452        message.add_answers(vec![
453            Record::from_rdata(
454                Name::root(),
455                86400,
456                RData::A(Ipv4Addr::new(192, 0, 2, 1).into()),
457            ),
458            Record::from_rdata(Name::root(), 86400, RData::AAAA(Ipv6Addr::LOCALHOST.into())),
459        ]);
460
461        let lookup = LookupIp::from(Lookup::new(message, Instant::now()));
462        let iter = lookup.into_iter();
463
464        let ips = thread::spawn(move || iter.collect::<Vec<_>>())
465            .join()
466            .unwrap();
467
468        assert_eq!(
469            ips,
470            vec![
471                IpAddr::V4(Ipv4Addr::new(192, 0, 2, 1)),
472                IpAddr::V6(Ipv6Addr::LOCALHOST),
473            ]
474        );
475    }
476
477    #[test]
478    fn test_ipv4_only_strategy() {
479        subscribe();
480
481        let cx = LookupContext {
482            client: CachingClient::new(0, mock(vec![v4_message()]), false),
483            options: DnsRequestOptions::default(),
484            hosts: Arc::new(Hosts::default()),
485        };
486
487        assert_eq!(
488            block_on(cx.ipv4_only(Name::root()))
489                .unwrap()
490                .answers()
491                .iter()
492                .map(|r| r.data.ip_addr().unwrap())
493                .collect::<Vec<IpAddr>>(),
494            vec![Ipv4Addr::LOCALHOST]
495        );
496    }
497
498    #[test]
499    fn test_ipv6_only_strategy() {
500        subscribe();
501
502        let cx = LookupContext {
503            client: CachingClient::new(0, mock(vec![v6_message()]), false),
504            options: DnsRequestOptions::default(),
505            hosts: Arc::new(Hosts::default()),
506        };
507
508        assert_eq!(
509            block_on(cx.ipv6_only(Name::root()))
510                .unwrap()
511                .answers()
512                .iter()
513                .map(|r| r.data.ip_addr().unwrap())
514                .collect::<Vec<IpAddr>>(),
515            vec![Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)]
516        );
517    }
518
519    #[test]
520    fn test_ipv4_and_ipv6_strategy() {
521        subscribe();
522
523        let mut cx = LookupContext {
524            client: CachingClient::new(0, mock(vec![v6_message(), v4_message()]), false),
525            options: DnsRequestOptions::default(),
526            hosts: Arc::new(Hosts::default()),
527        };
528
529        // ipv6 is consistently queried first (even though the join has it second)
530        // both succeed
531        assert_eq!(
532            block_on(cx.ipv4_and_ipv6(Name::root()))
533                .unwrap()
534                .answers()
535                .iter()
536                .map(|r| r.data.ip_addr().unwrap())
537                .collect::<Vec<IpAddr>>(),
538            vec![
539                IpAddr::V4(Ipv4Addr::LOCALHOST),
540                IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)),
541            ]
542        );
543
544        // only ipv4 available
545        cx.client = CachingClient::new(0, mock(vec![empty(), v4_message()]), false);
546        assert_eq!(
547            block_on(cx.ipv4_and_ipv6(Name::root()))
548                .unwrap()
549                .answers()
550                .iter()
551                .map(|r| r.data.ip_addr().unwrap())
552                .collect::<Vec<IpAddr>>(),
553            vec![IpAddr::V4(Ipv4Addr::LOCALHOST)]
554        );
555
556        // error then ipv4
557        cx.client = CachingClient::new(0, mock(vec![error(), v4_message()]), false);
558        assert_eq!(
559            block_on(cx.ipv4_and_ipv6(Name::root()))
560                .unwrap()
561                .answers()
562                .iter()
563                .map(|r| r.data.ip_addr().unwrap())
564                .collect::<Vec<IpAddr>>(),
565            vec![IpAddr::V4(Ipv4Addr::LOCALHOST)]
566        );
567
568        // only ipv6 available
569        cx.client = CachingClient::new(0, mock(vec![v6_message(), empty()]), false);
570        assert_eq!(
571            block_on(cx.ipv4_and_ipv6(Name::root()))
572                .unwrap()
573                .answers()
574                .iter()
575                .map(|r| r.data.ip_addr().unwrap())
576                .collect::<Vec<IpAddr>>(),
577            vec![IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1))]
578        );
579
580        // error, then only ipv6 available
581        cx.client = CachingClient::new(0, mock(vec![v6_message(), error()]), false);
582        assert_eq!(
583            block_on(cx.ipv4_and_ipv6(Name::root()))
584                .unwrap()
585                .answers()
586                .iter()
587                .map(|r| r.data.ip_addr().unwrap())
588                .collect::<Vec<IpAddr>>(),
589            vec![IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1))]
590        );
591    }
592
593    #[test]
594    fn test_ipv6_and_ipv4_strategy() {
595        subscribe();
596
597        let mut cx = LookupContext {
598            client: CachingClient::new(0, mock(vec![v4_message(), v6_message()]), false),
599            options: DnsRequestOptions::default(),
600            hosts: Arc::new(Hosts::default()),
601        };
602
603        // ipv4 is consistently queried first (even though the join has it second)
604        // both succeed
605        assert_eq!(
606            block_on(cx.ipv6_and_ipv4(Name::root()))
607                .unwrap()
608                .answers()
609                .iter()
610                .map(|r| r.data.ip_addr().unwrap())
611                .collect::<Vec<IpAddr>>(),
612            vec![
613                IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)),
614                IpAddr::V4(Ipv4Addr::LOCALHOST),
615            ]
616        );
617
618        // only ipv4 available
619        cx.client = CachingClient::new(0, mock(vec![v4_message(), empty()]), false);
620        assert_eq!(
621            block_on(cx.ipv6_and_ipv4(Name::root()))
622                .unwrap()
623                .answers()
624                .iter()
625                .map(|r| r.data.ip_addr().unwrap())
626                .collect::<Vec<IpAddr>>(),
627            vec![IpAddr::V4(Ipv4Addr::LOCALHOST)]
628        );
629
630        // v6 errors, v4 succeeds
631        cx.client = CachingClient::new(0, mock(vec![v4_message(), error()]), false);
632        assert_eq!(
633            block_on(cx.ipv6_and_ipv4(Name::root()))
634                .unwrap()
635                .answers()
636                .iter()
637                .map(|r| r.data.ip_addr().unwrap())
638                .collect::<Vec<IpAddr>>(),
639            vec![IpAddr::V4(Ipv4Addr::LOCALHOST)]
640        );
641
642        // only ipv6 available
643        cx.client = CachingClient::new(0, mock(vec![empty(), v6_message()]), false);
644        assert_eq!(
645            block_on(cx.ipv6_and_ipv4(Name::root()))
646                .unwrap()
647                .answers()
648                .iter()
649                .map(|r| r.data.ip_addr().unwrap())
650                .collect::<Vec<IpAddr>>(),
651            vec![IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1))]
652        );
653
654        // v4 errors, v6 succeeds
655        cx.client = CachingClient::new(0, mock(vec![error(), v6_message()]), false);
656        assert_eq!(
657            block_on(cx.ipv6_and_ipv4(Name::root()))
658                .unwrap()
659                .answers()
660                .iter()
661                .map(|r| r.data.ip_addr().unwrap())
662                .collect::<Vec<IpAddr>>(),
663            vec![IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1))]
664        );
665    }
666
667    #[test]
668    fn test_ipv6_then_ipv4_strategy() {
669        subscribe();
670
671        let mut cx = LookupContext {
672            client: CachingClient::new(0, mock(vec![v6_message()]), false),
673            options: DnsRequestOptions::default(),
674            hosts: Arc::new(Hosts::default()),
675        };
676
677        // ipv6 first
678        assert_eq!(
679            block_on(cx.ipv6_then_ipv4(Name::root()))
680                .unwrap()
681                .answers()
682                .iter()
683                .map(|r| r.data.ip_addr().unwrap())
684                .collect::<Vec<IpAddr>>(),
685            vec![Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)]
686        );
687
688        // nothing then ipv4
689        cx.client = CachingClient::new(0, mock(vec![v4_message(), empty()]), false);
690        assert_eq!(
691            block_on(cx.ipv6_then_ipv4(Name::root()))
692                .unwrap()
693                .answers()
694                .iter()
695                .map(|r| r.data.ip_addr().unwrap())
696                .collect::<Vec<IpAddr>>(),
697            vec![Ipv4Addr::LOCALHOST]
698        );
699
700        // ipv4 and error
701        cx.client = CachingClient::new(0, mock(vec![v4_message(), error()]), false);
702        assert_eq!(
703            block_on(cx.ipv6_then_ipv4(Name::root()))
704                .unwrap()
705                .answers()
706                .iter()
707                .map(|r| r.data.ip_addr().unwrap())
708                .collect::<Vec<IpAddr>>(),
709            vec![Ipv4Addr::LOCALHOST]
710        );
711    }
712
713    #[test]
714    fn test_ipv4_then_ipv6_strategy() {
715        subscribe();
716
717        let mut cx = LookupContext {
718            client: CachingClient::new(0, mock(vec![v4_message()]), false),
719            options: DnsRequestOptions::default(),
720            hosts: Arc::new(Hosts::default()),
721        };
722
723        // ipv6 first
724        assert_eq!(
725            block_on(cx.ipv4_then_ipv6(Name::root()))
726                .unwrap()
727                .answers()
728                .iter()
729                .map(|r| r.data.ip_addr().unwrap())
730                .collect::<Vec<IpAddr>>(),
731            vec![Ipv4Addr::LOCALHOST]
732        );
733
734        // nothing then ipv6
735        cx.client = CachingClient::new(0, mock(vec![v6_message(), empty()]), false);
736        assert_eq!(
737            block_on(cx.ipv4_then_ipv6(Name::root()))
738                .unwrap()
739                .answers()
740                .iter()
741                .map(|r| r.data.ip_addr().unwrap())
742                .collect::<Vec<IpAddr>>(),
743            vec![Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)]
744        );
745
746        // error then ipv6
747        cx.client = CachingClient::new(0, mock(vec![v6_message(), error()]), false);
748        assert_eq!(
749            block_on(cx.ipv4_then_ipv6(Name::root()))
750                .unwrap()
751                .answers()
752                .iter()
753                .map(|r| r.data.ip_addr().unwrap())
754                .collect::<Vec<IpAddr>>(),
755            vec![Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)]
756        );
757    }
758}