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