Balboah fix/missing cname records (#1033)

* Allow CNAME records on A/AAAA lookups

Fixes #184

* rustfmt

* Fix cname chain min TTL test

The test assumed that the CNAME record wouldn't be returned.
Now we only check that the A record TTLs were changed

* Fix lookup integration tests & max query depth

- include cname record in test
- move max query depth check

* add preserve_intermediates option

* preserve records across queries

* add as_lookup to LookupIp

Co-authored-by: Johnny Bergström <github@joonix.se>
This commit is contained in:
Benjamin Fry
2020-02-27 17:42:19 -08:00
committed by GitHub
parent c6a2a657ff
commit e137e72f34
7 changed files with 239 additions and 109 deletions

View File

@@ -1,3 +1,4 @@
edition = "2018"
# use_try_shorthand = true
# error_on_line_overflow = false
# error_on_line_overflow_comments = false

View File

@@ -249,7 +249,7 @@ impl<C: DnsHandle, P: ConnectionProvider<Conn = C>> AsyncResolver<C, P> {
Ok(AsyncResolver {
config,
options,
client_cache: CachingClient::with_cache(lru, either),
client_cache: CachingClient::with_cache(lru, either, options.preserve_intermediates),
hosts,
})
}

View File

@@ -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,
}
}
}

View File

@@ -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<Lookup> 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<Lookup> 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()

View File

@@ -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<Lookup> 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,
))

View File

@@ -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<C: DnsHandle> {
lru: Arc<Mutex<DnsLru>>,
client: C,
query_depth: Arc<AtomicU8>,
preserve_intermediates: bool,
}
impl<C: DnsHandle + Send + 'static> CachingClient<C> {
#[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<Mutex<DnsLru>>, client: C) -> Self {
pub(crate) fn with_cache(
lru: Arc<Mutex<DnsLru>>,
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<C: DnsHandle + Send + 'static> CachingClient<C> {
query: Query,
options: DnsRequestOptions,
) -> Pin<Box<dyn Future<Output = Result<Lookup, ResolveError>> + 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<Lookup, ResolveError> {
// 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<C: DnsHandle + Send + 'static> CachingClient<C> {
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<C: DnsHandle + Send + 'static> CachingClient<C> {
// 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<Lookup, ResolveError> {
let _tracker = DepthTracker::track(client.query_depth.clone());
let is_dnssec = client.client.is_verifying_dnssec();
@@ -164,9 +163,14 @@ impl<C: DnsHandle + Send + 'static> CachingClient<C> {
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<C: DnsHandle + Send + 'static> CachingClient<C> {
is_dnssec: bool,
query: &Query,
mut response: DnsResponse,
mut preserved_records: Vec<(Record, u32)>,
) -> Result<Records, ResolveError> {
// 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<C: DnsHandle + Send + 'static> CachingClient<C> {
.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<C: DnsHandle + Send + 'static> CachingClient<C> {
.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<C: DnsHandle + Send + 'static> CachingClient<C> {
})
.collect::<Vec<_>>();
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<C: DnsHandle + Send + 'static> CachingClient<C> {
}
}
// 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<Box<dyn Future<Output = Result<Lookup, ResolveError>> + 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(

View File

@@ -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