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