diff --git a/.rustfmt.toml b/.rustfmt.toml index 70aa60e5..453a7d9e 100644 --- a/.rustfmt.toml +++ b/.rustfmt.toml @@ -1,3 +1,4 @@ +edition = "2018" # use_try_shorthand = true # error_on_line_overflow = false # error_on_line_overflow_comments = false diff --git a/crates/resolver/src/async_resolver.rs b/crates/resolver/src/async_resolver.rs index 3fc41984..23cb3019 100644 --- a/crates/resolver/src/async_resolver.rs +++ b/crates/resolver/src/async_resolver.rs @@ -249,7 +249,7 @@ impl> AsyncResolver { Ok(AsyncResolver { config, options, - client_cache: CachingClient::with_cache(lru, either), + client_cache: CachingClient::with_cache(lru, either, options.preserve_intermediates), hosts, }) } diff --git a/crates/resolver/src/config.rs b/crates/resolver/src/config.rs index 068a431c..f84f399f 100644 --- a/crates/resolver/src/config.rs +++ b/crates/resolver/src/config.rs @@ -688,6 +688,8 @@ pub struct ResolverOpts { /// /// 0 or 1 will configure this to execute all requests serially pub num_concurrent_reqs: usize, + /// Preserve all intermediate records in the lookup response, suchas CNAME records + pub preserve_intermediates: bool, } impl Default for ResolverOpts { @@ -712,6 +714,7 @@ impl Default for ResolverOpts { negative_max_ttl: None, distrust_nx_responses: true, num_concurrent_reqs: 2, + preserve_intermediates: false, } } } diff --git a/crates/resolver/src/lookup.rs b/crates/resolver/src/lookup.rs index 52d00390..d62f249a 100644 --- a/crates/resolver/src/lookup.rs +++ b/crates/resolver/src/lookup.rs @@ -326,6 +326,13 @@ impl SrvLookup { pub fn ip_iter(&self) -> LookupIpIter { LookupIpIter(self.0.iter()) } + + /// Return a reference to the inner lookup + /// + /// This can be useful for getting all records from the request + pub fn as_lookup(&self) -> &Lookup { + &self.0 + } } impl From for SrvLookup { @@ -399,6 +406,13 @@ macro_rules! lookup_type { pub fn valid_until(&self) -> Instant { self.0.valid_until() } + + /// Return a reference to the inner lookup + /// + /// This can be useful for getting all records from the request + pub fn as_lookup(&self) -> &Lookup { + &self.0 + } } impl From for $l { @@ -557,7 +571,7 @@ pub mod tests { vec![Name::root()], RecordType::A, DnsRequestOptions::default(), - CachingClient::new(0, mock(vec![v4_message()])), + CachingClient::new(0, mock(vec![v4_message()]), false), )) .unwrap() .iter() @@ -574,7 +588,7 @@ pub mod tests { vec![Name::root()], RecordType::A, DnsRequestOptions::default(), - CachingClient::new(0, mock(vec![v4_message()])), + CachingClient::new(0, mock(vec![v4_message()]), false), )) .unwrap() .into_iter() @@ -590,7 +604,7 @@ pub mod tests { vec![Name::root()], RecordType::A, DnsRequestOptions::default(), - CachingClient::new(0, mock(vec![error()])), + CachingClient::new(0, mock(vec![error()]), false), )) .is_err()); } @@ -602,7 +616,7 @@ pub mod tests { vec![Name::root()], RecordType::A, DnsRequestOptions::default(), - CachingClient::new(0, mock(vec![empty()])), + CachingClient::new(0, mock(vec![empty()]), false), )) .unwrap_err() .kind() diff --git a/crates/resolver/src/lookup_ip.rs b/crates/resolver/src/lookup_ip.rs index 8be4cccb..75bb1bfb 100644 --- a/crates/resolver/src/lookup_ip.rs +++ b/crates/resolver/src/lookup_ip.rs @@ -49,6 +49,13 @@ impl LookupIp { pub fn valid_until(&self) -> Instant { self.0.valid_until() } + + /// Return a reference to the inner lookup + /// + /// This can be useful for getting all records from the request + pub fn as_lookup(&self) -> &Lookup { + &self.0 + } } impl From for LookupIp { @@ -463,7 +470,7 @@ pub mod tests { assert_eq!( block_on(ipv4_only( Name::root(), - CachingClient::new(0, mock(vec![v4_message()])), + CachingClient::new(0, mock(vec![v4_message()]), false), Default::default(), None, )) @@ -480,7 +487,7 @@ pub mod tests { assert_eq!( block_on(ipv6_only( Name::root(), - CachingClient::new(0, mock(vec![v6_message()])), + CachingClient::new(0, mock(vec![v6_message()]), false), Default::default(), None, )) @@ -499,7 +506,7 @@ pub mod tests { assert_eq!( block_on(ipv4_and_ipv6( Name::root(), - CachingClient::new(0, mock(vec![v6_message(), v4_message()])), + CachingClient::new(0, mock(vec![v6_message(), v4_message()]), false), Default::default(), None, )) @@ -517,7 +524,7 @@ pub mod tests { assert_eq!( block_on(ipv4_and_ipv6( Name::root(), - CachingClient::new(0, mock(vec![empty(), v4_message()])), + CachingClient::new(0, mock(vec![empty(), v4_message()]), false), Default::default(), None, )) @@ -532,7 +539,7 @@ pub mod tests { assert_eq!( block_on(ipv4_and_ipv6( Name::root(), - CachingClient::new(0, mock(vec![error(), v4_message()])), + CachingClient::new(0, mock(vec![error(), v4_message()]), false), Default::default(), None, )) @@ -547,7 +554,7 @@ pub mod tests { assert_eq!( block_on(ipv4_and_ipv6( Name::root(), - CachingClient::new(0, mock(vec![v6_message(), empty()])), + CachingClient::new(0, mock(vec![v6_message(), empty()]), false), Default::default(), None, )) @@ -562,7 +569,7 @@ pub mod tests { assert_eq!( block_on(ipv4_and_ipv6( Name::root(), - CachingClient::new(0, mock(vec![v6_message(), error()])), + CachingClient::new(0, mock(vec![v6_message(), error()]), false), Default::default(), None, )) @@ -580,7 +587,7 @@ pub mod tests { assert_eq!( block_on(ipv6_then_ipv4( Name::root(), - CachingClient::new(0, mock(vec![v6_message()])), + CachingClient::new(0, mock(vec![v6_message()]), false), Default::default(), None, )) @@ -595,7 +602,7 @@ pub mod tests { assert_eq!( block_on(ipv6_then_ipv4( Name::root(), - CachingClient::new(0, mock(vec![v4_message(), empty()])), + CachingClient::new(0, mock(vec![v4_message(), empty()]), false), Default::default(), None, )) @@ -610,7 +617,7 @@ pub mod tests { assert_eq!( block_on(ipv6_then_ipv4( Name::root(), - CachingClient::new(0, mock(vec![v4_message(), error()])), + CachingClient::new(0, mock(vec![v4_message(), error()]), false), Default::default(), None, )) @@ -628,7 +635,7 @@ pub mod tests { assert_eq!( block_on(ipv4_then_ipv6( Name::root(), - CachingClient::new(0, mock(vec![v4_message()])), + CachingClient::new(0, mock(vec![v4_message()]), false), Default::default(), None, )) @@ -643,7 +650,7 @@ pub mod tests { assert_eq!( block_on(ipv4_then_ipv6( Name::root(), - CachingClient::new(0, mock(vec![v6_message(), empty()])), + CachingClient::new(0, mock(vec![v6_message(), empty()]), false), Default::default(), None, )) @@ -658,7 +665,7 @@ pub mod tests { assert_eq!( block_on(ipv4_then_ipv6( Name::root(), - CachingClient::new(0, mock(vec![v6_message(), error()])), + CachingClient::new(0, mock(vec![v6_message(), error()]), false), Default::default(), None, )) diff --git a/crates/resolver/src/lookup_state.rs b/crates/resolver/src/lookup_state.rs index 84eb2cce..f3ecec7f 100644 --- a/crates/resolver/src/lookup_state.rs +++ b/crates/resolver/src/lookup_state.rs @@ -15,7 +15,7 @@ use std::sync::Arc; use std::time::Instant; use futures::lock::Mutex; -use futures::{future, Future, FutureExt}; +use futures::Future; use proto::op::{Message, Query, ResponseCode}; use proto::rr::domain::usage::{ @@ -63,23 +63,30 @@ pub struct CachingClient { lru: Arc>, client: C, query_depth: Arc, + preserve_intermediates: bool, } impl CachingClient { #[doc(hidden)] - pub fn new(max_size: usize, client: C) -> Self { + pub fn new(max_size: usize, client: C, preserve_intermediates: bool) -> Self { Self::with_cache( Arc::new(Mutex::new(DnsLru::new(max_size, Default::default()))), client, + preserve_intermediates, ) } - pub(crate) fn with_cache(lru: Arc>, client: C) -> Self { + pub(crate) fn with_cache( + lru: Arc>, + client: C, + preserve_intermediates: bool, + ) -> Self { let query_depth = Arc::new(AtomicU8::new(0)); CachingClient { lru, client, query_depth, + preserve_intermediates, } } @@ -89,16 +96,24 @@ impl CachingClient { query: Query, options: DnsRequestOptions, ) -> Pin> + Send>> { - // see https://tools.ietf.org/html/rfc6761 + Box::pin(Self::inner_lookup(query, options, self.clone(), vec![])) + } + + async fn inner_lookup( + query: Query, + options: DnsRequestOptions, + mut client: Self, + preserved_records: Vec<(Record, u32)>, + ) -> Result { + // see https://tools.ietf.ofc6761 // // ```text - // Name resolution APIs and libraries SHOULD recognize localhost - // names as special and SHOULD always return the IP loopback address + // Name resolution APIs and librariesognize localhost + // names as special analways return the IP loopback address // for address queries and negative responses for all other query - // types. Name resolution APIs SHOULD NOT send queries for - // localhost names to their configured caching DNS server(s). + // types. Name resolution APIs SHOULD NOT s for + // localhost names to their cocachir(s). // ``` - // // special use rules only apply to the IN Class if query.query_class() == DNSClass::IN { let usage = match query.name() { @@ -113,16 +128,10 @@ impl CachingClient { match usage.resolver() { ResolverUsage::Loopback => match query.query_type() { // TODO: look in hosts for these ips/names first... - RecordType::A => { - return future::ok(Lookup::from_rdata(query, LOCALHOST_V4.clone())).boxed() - } - RecordType::AAAA => { - return future::ok(Lookup::from_rdata(query, LOCALHOST_V6.clone())).boxed() - } - RecordType::PTR => { - return future::ok(Lookup::from_rdata(query, LOCALHOST.clone())).boxed() - } - _ => return future::err(DnsLru::nx_error(query, None)).boxed(), // Are there any other types we can use? + RecordType::A => return Ok(Lookup::from_rdata(query, LOCALHOST_V4.clone())), + RecordType::AAAA => return Ok(Lookup::from_rdata(query, LOCALHOST_V6.clone())), + RecordType::PTR => return Ok(Lookup::from_rdata(query, LOCALHOST.clone())), + _ => return Err(DnsLru::nx_error(query, None)), // Are there any other types we can use? }, // when mdns is enabled we will follow a standard query path #[cfg(feature = "mdns")] @@ -131,21 +140,11 @@ impl CachingClient { // when mdns is not enabled we will return errors on LinkLocal ("*.local.") names #[cfg(not(feature = "mdns"))] ResolverUsage::LinkLocal => (), - ResolverUsage::NxDomain => { - return future::err(DnsLru::nx_error(query, None)).boxed() - } + ResolverUsage::NxDomain => return Err(DnsLru::nx_error(query, None)), ResolverUsage::Normal => (), } } - Box::pin(Self::inner_lookup(query, options, self.clone())) - } - - async fn inner_lookup( - query: Query, - options: DnsRequestOptions, - mut client: Self, - ) -> Result { let _tracker = DepthTracker::track(client.query_depth.clone()); let is_dnssec = client.client.is_verifying_dnssec(); @@ -164,9 +163,14 @@ impl CachingClient { response_message, false, /* false b/c DNSSec should not cache NXDomain */ )), - ResponseCode::NoError => { - Self::handle_noerror(&mut client, options, is_dnssec, &query, response_message) - } + ResponseCode::NoError => Self::handle_noerror( + &mut client, + options, + is_dnssec, + &query, + response_message, + preserved_records, + ), r => Err(ResolveErrorKind::Msg(format!("DNS Error: {}", r)).into()), }?; @@ -227,12 +231,16 @@ impl CachingClient { is_dnssec: bool, query: &Query, mut response: DnsResponse, + mut preserved_records: Vec<(Record, u32)>, ) -> Result { // initial ttl is what CNAMES for min usage const INITIAL_TTL: u32 = dns_lru::MAX_TTL; // seek out CNAMES, this is only performed if the query is not a CNAME, ANY, or SRV - let (search_name, cname_ttl, was_cname) = { + // FIXME: for SRV this evaluation is inadequate. CNAME is a single chain to a single record + // for SRV, there could be many different targets. The search_name needs to be enhanced to + // be a list of names found for SRV records. + let (search_name, cname_ttl, was_cname, preserved_records) = { // this will only search for CNAMEs if the request was not meant to be for one of the triggers for recursion let (search_name, cname_ttl, was_cname) = if query.query_type().is_any() || query.query_type().is_cname() { @@ -282,6 +290,10 @@ impl CachingClient { .flat_map(Message::take_additionals) .collect(); + // set of names that still require resolution + // TODO: this needs to be enhanced for SRV + let mut found_name = false; + // After following all the CNAMES to the last one, try and lookup the final name let records = answers .into_iter() @@ -290,20 +302,31 @@ impl CachingClient { .filter_map(|r| { // because this resolved potentially recursively, we want the min TTL from the chain let ttl = cname_ttl.min(r.ttl()); - // TODO: disable name validation with ResolverOpts? glibc feature... // restrict to the RData type requested if query.query_class() == r.dns_class() { // standard evaluation, it's an any type or it's the requested type and the search_name matches - // - or - + if (query.query_type().is_any() || query.query_type() == r.rr_type()) + && (search_name.as_ref() == r.name() || query.name() == r.name()) + { + found_name = true; + return Some((r, ttl)); + } + // CNAME evaluation, it's an A/AAAA lookup and the record is from the CNAME lookup chain. + if client.preserve_intermediates + && r.rr_type() == RecordType::CNAME + && (query.query_type() == RecordType::A + || query.query_type() == RecordType::AAAA) + { + return Some((r, ttl)); + } // srv evaluation, it's an srv lookup and the srv_search_name/target matches this name // and it's an IP - if ((query.query_type().is_any() || query.query_type() == r.rr_type()) - && (search_name.as_ref() == r.name() || query.name() == r.name())) - || (query.query_type().is_srv() - && r.rr_type().is_ip_addr() - && search_name.as_ref() == r.name()) + if query.query_type().is_srv() + && r.rr_type().is_ip_addr() + && search_name.as_ref() == r.name() { + found_name = true; Some((r, ttl)) } else { None @@ -314,21 +337,34 @@ impl CachingClient { }) .collect::>(); - if !records.is_empty() { - return Ok(Records::Exists(records)); + // adding the newly collected records to the preserved records + preserved_records.extend(records); + if !preserved_records.is_empty() && found_name { + return Ok(Records::Exists(preserved_records)); } - (search_name.into_owned(), cname_ttl, was_cname) + ( + search_name.into_owned(), + cname_ttl, + was_cname, + preserved_records, + ) }; // TODO: for SRV records we *could* do an implicit lookup, but, this requires knowing the type of IP desired // for now, we'll make the API require the user to perform a follow up to the lookups. // It was a CNAME, but not included in the request... - if was_cname { + if was_cname && client.query_depth.load(Ordering::Acquire) < MAX_QUERY_DEPTH { let next_query = Query::query(search_name, query.query_type()); - Ok(Self::next_query( - client, options, is_dnssec, next_query, cname_ttl, response, - )) + Ok(Records::CnameChain { + next: Box::pin(CachingClient::inner_lookup( + next_query, + options, + client.clone(), + preserved_records, + )), + min_ttl: cname_ttl, + }) } else { // TODO: review See https://tools.ietf.org/html/rfc2308 for NoData section // Note on DNSSec, in secure_client_handle, if verify_nsec fails then the request fails. @@ -337,27 +373,6 @@ impl CachingClient { } } - // TODO: merge this with cname - fn next_query( - client: &mut Self, - options: DnsRequestOptions, - is_dnssec: bool, - query: Query, - cname_ttl: u32, - message: DnsResponse, - ) -> Records { - // tracking the depth of our queries, to prevent infinite CNAME recursion - if client.query_depth.load(Ordering::Acquire) >= MAX_QUERY_DEPTH { - // TODO: This should return an error - Self::handle_nxdomain(is_dnssec, message, true) - } else { - Records::CnameChain { - next: client.lookup(query, options), - min_ttl: cname_ttl, - } - } - } - async fn cname( query: Query, future: Pin> + Send>>, @@ -423,10 +438,10 @@ mod tests { fn test_empty_cache() { let cache = Arc::new(Mutex::new(DnsLru::new(1, dns_lru::TtlConfig::default()))); let client = mock(vec![empty()]); - let client = CachingClient::with_cache(cache, client); + let client = CachingClient::with_cache(cache, client, false); if let ResolveErrorKind::NoRecordsFound { query, valid_until } = block_on( - CachingClient::inner_lookup(Query::new(), Default::default(), client), + CachingClient::inner_lookup(Query::new(), Default::default(), client, vec![]), ) .unwrap_err() .kind() @@ -456,12 +471,13 @@ mod tests { ); let client = mock(vec![empty()]); - let client = CachingClient::with_cache(cache, client); + let client = CachingClient::with_cache(cache, client, false); let ips = block_on(CachingClient::inner_lookup( Query::new(), Default::default(), client, + vec![], )) .unwrap(); @@ -476,12 +492,13 @@ mod tests { let cache = Arc::new(Mutex::new(DnsLru::new(1, dns_lru::TtlConfig::default()))); // first should come from client... let client = mock(vec![v4_message()]); - let client = CachingClient::with_cache(cache.clone(), client); + let client = CachingClient::with_cache(cache.clone(), client, false); let ips = block_on(CachingClient::inner_lookup( Query::new(), Default::default(), client, + vec![], )) .unwrap(); @@ -492,12 +509,13 @@ mod tests { // next should come from cache... let client = mock(vec![empty()]); - let client = CachingClient::with_cache(cache, client); + let client = CachingClient::with_cache(cache, client, false); let ips = block_on(CachingClient::inner_lookup( Query::new(), Default::default(), client, + vec![], )) .unwrap(); @@ -537,12 +555,13 @@ mod tests { // the cname should succeed, we shouldn't query again after that, which would cause an error... let client = mock(vec![error(), cname_message()]); - let client = CachingClient::with_cache(cache, client); + let client = CachingClient::with_cache(cache, client, false); let ips = block_on(CachingClient::inner_lookup( Query::query(Name::from_str("www.example.com.").unwrap(), query_type), Default::default(), client, + vec![], )) .expect("lookup failed"); @@ -568,7 +587,7 @@ mod tests { // the cname should succeed, we shouldn't query again after that, which would cause an error... let client = mock(vec![error(), srv_message()]); - let client = CachingClient::with_cache(cache, client); + let client = CachingClient::with_cache(cache, client, false); let ips = block_on(CachingClient::inner_lookup( Query::query( @@ -577,6 +596,7 @@ mod tests { ), Default::default(), client, + vec![], )) .expect("lookup failed"); @@ -615,7 +635,7 @@ mod tests { ]); let client = mock(vec![error(), Ok(message)]); - let client = CachingClient::with_cache(cache, client); + let client = CachingClient::with_cache(cache, client, false); let ips = block_on(CachingClient::inner_lookup( Query::query( @@ -624,6 +644,7 @@ mod tests { ), Default::default(), client, + vec![], )) .expect("lookup failed"); @@ -694,7 +715,7 @@ mod tests { fn cname_ttl_test(first: u32, second: u32) { let lru = Arc::new(Mutex::new(DnsLru::new(1, dns_lru::TtlConfig::default()))); // expecting no queries to be performed - let mut client = CachingClient::with_cache(Arc::clone(&lru), mock(vec![error()])); + let mut client = CachingClient::with_cache(Arc::clone(&lru), mock(vec![error()]), false); let mut message = Message::new(); message.insert_answers(vec![Record::from_rdata( @@ -714,11 +735,17 @@ mod tests { false, &Query::query(Name::from_str("ttl.example.com.").unwrap(), RecordType::A), message.into(), + vec![], ); if let Ok(records) = records { if let Records::Exists(records) = records { - assert!(records.iter().all(|&(_, ttl)| ttl == 1)); + for (record, ttl) in records.iter() { + if record.record_type() == RecordType::CNAME { + continue; + } + assert_eq!(ttl, &1); + } } else { panic!("records don't exist"); } @@ -737,7 +764,7 @@ mod tests { fn test_early_return_localhost() { let cache = Arc::new(Mutex::new(DnsLru::new(0, dns_lru::TtlConfig::default()))); let client = mock(vec![empty()]); - let mut client = CachingClient::with_cache(cache, client); + let mut client = CachingClient::with_cache(cache, client, false); { let query = Query::query(Name::from_ascii("localhost.").unwrap(), RecordType::A); @@ -812,7 +839,7 @@ mod tests { fn test_early_return_invalid() { let cache = Arc::new(Mutex::new(DnsLru::new(0, dns_lru::TtlConfig::default()))); let client = mock(vec![empty()]); - let mut client = CachingClient::with_cache(cache, client); + let mut client = CachingClient::with_cache(cache, client, false); assert!(block_on(client.lookup( Query::query( @@ -836,7 +863,7 @@ mod tests { )); let client = mock(vec![error(), Ok(message)]); - let mut client = CachingClient::with_cache(cache, client); + let mut client = CachingClient::with_cache(cache, client, false); assert!(block_on(client.lookup( Query::query( diff --git a/tests/integration-tests/tests/lookup_tests.rs b/tests/integration-tests/tests/lookup_tests.rs index 4c5c5b40..b00052bf 100644 --- a/tests/integration-tests/tests/lookup_tests.rs +++ b/tests/integration-tests/tests/lookup_tests.rs @@ -45,7 +45,7 @@ fn test_lookup() { vec![Name::from_str("www.example.com.").unwrap()], RecordType::A, Default::default(), - CachingClient::new(0, client), + CachingClient::new(0, client, false), ); let lookup = io_loop.block_on(lookup).unwrap(); @@ -87,7 +87,7 @@ fn test_lookup_hosts() { let lookup = LookupIpFuture::lookup( vec![Name::from_str("www.example.com.").unwrap()], LookupIpStrategy::default(), - CachingClient::new(0, client), + CachingClient::new(0, client, false), Default::default(), Some(Arc::new(hosts)), None, @@ -130,7 +130,7 @@ fn test_lookup_ipv4_like() { let lookup = LookupIpFuture::lookup( vec![Name::from_str("1.2.3.4.example.com.").unwrap()], LookupIpStrategy::default(), - CachingClient::new(0, client), + CachingClient::new(0, client, false), Default::default(), Some(Arc::new(Hosts::default())), Some(RData::A(Ipv4Addr::new(1, 2, 3, 4))), @@ -160,7 +160,7 @@ fn test_lookup_ipv4_like_fall_through() { let lookup = LookupIpFuture::lookup( vec![Name::from_str("198.51.100.35.example.com.").unwrap()], LookupIpStrategy::default(), - CachingClient::new(0, client), + CachingClient::new(0, client, false), Default::default(), Some(Arc::new(Hosts::default())), Some(RData::A(Ipv4Addr::new(198, 51, 100, 35))), @@ -187,7 +187,7 @@ fn test_mock_lookup() { vec![Name::from_str("www.example.com.").unwrap()], RecordType::A, Default::default(), - CachingClient::new(0, client), + CachingClient::new(0, client, false), ); let mut io_loop = Runtime::new().unwrap(); @@ -217,7 +217,7 @@ fn test_cname_lookup() { vec![Name::from_str("www.example.com.").unwrap()], RecordType::A, Default::default(), - CachingClient::new(0, client), + CachingClient::new(0, client, false), ); let mut io_loop = Runtime::new().unwrap(); @@ -229,6 +229,43 @@ fn test_cname_lookup() { ); } +#[test] +fn test_cname_lookup_preserve() { + let resp_query = Query::query(Name::from_str("www.example.com.").unwrap(), RecordType::A); + let cname_record = cname_record( + Name::from_str("www.example.com.").unwrap(), + Name::from_str("v4.example.com.").unwrap(), + ); + let v4_record = v4_record( + Name::from_str("v4.example.com.").unwrap(), + Ipv4Addr::new(93, 184, 216, 34), + ); + let message = message( + resp_query, + vec![cname_record.clone(), v4_record], + vec![], + vec![], + ); + let client = MockClientHandle::mock(vec![message.map(Into::into)]); + + let lookup = LookupFuture::lookup( + vec![Name::from_str("www.example.com.").unwrap()], + RecordType::A, + Default::default(), + CachingClient::new(0, client, true), + ); + + let mut io_loop = Runtime::new().unwrap(); + let lookup = io_loop.block_on(lookup).unwrap(); + + let mut iter = lookup.iter(); + assert_eq!(iter.next().unwrap(), cname_record.rdata()); + assert_eq!( + *iter.next().unwrap(), + RData::A(Ipv4Addr::new(93, 184, 216, 34)) + ); +} + #[test] fn test_chained_cname_lookup() { let resp_query = Query::query(Name::from_str("www.example.com.").unwrap(), RecordType::A); @@ -252,7 +289,7 @@ fn test_chained_cname_lookup() { vec![Name::from_str("www.example.com.").unwrap()], RecordType::A, Default::default(), - CachingClient::new(0, client), + CachingClient::new(0, client, false), ); let mut io_loop = Runtime::new().unwrap(); @@ -264,6 +301,48 @@ fn test_chained_cname_lookup() { ); } +#[test] +fn test_chained_cname_lookup_preserve() { + let resp_query = Query::query(Name::from_str("www.example.com.").unwrap(), RecordType::A); + let cname_record = cname_record( + Name::from_str("www.example.com.").unwrap(), + Name::from_str("v4.example.com.").unwrap(), + ); + let v4_record = v4_record( + Name::from_str("v4.example.com.").unwrap(), + Ipv4Addr::new(93, 184, 216, 34), + ); + + // The first response should be a cname, the second will be the actual record + let message1 = message( + resp_query.clone(), + vec![cname_record.clone()], + vec![], + vec![], + ); + let message2 = message(resp_query, vec![v4_record], vec![], vec![]); + + // the mock pops messages... + let client = MockClientHandle::mock(vec![message2.map(Into::into), message1.map(Into::into)]); + + let lookup = LookupFuture::lookup( + vec![Name::from_str("www.example.com.").unwrap()], + RecordType::A, + Default::default(), + CachingClient::new(0, client, true), + ); + + let mut io_loop = Runtime::new().unwrap(); + let lookup = io_loop.block_on(lookup).unwrap(); + + let mut iter = lookup.iter(); + assert_eq!(iter.next().unwrap(), cname_record.rdata()); + assert_eq!( + *iter.next().unwrap(), + RData::A(Ipv4Addr::new(93, 184, 216, 34)) + ); +} + #[test] fn test_max_chained_lookup_depth() { let resp_query = Query::query(Name::from_str("www.example.com.").unwrap(), RecordType::A); @@ -334,7 +413,7 @@ fn test_max_chained_lookup_depth() { message1.map(Into::into), ]); - let client = CachingClient::new(0, client); + let client = CachingClient::new(0, client, false); let lookup = LookupFuture::lookup( vec![Name::from_str("www.example.com.").unwrap()], RecordType::A, @@ -345,7 +424,6 @@ fn test_max_chained_lookup_depth() { let mut io_loop = Runtime::new().unwrap(); println!("performing max cname validation"); - // TODO: validate exact error assert!(io_loop.block_on(lookup).is_err()); // This query should succeed, as the queue depth should reset to 0 on a failed request