use std::borrow::Cow;
use std::cell::RefCell;
use std::mem;
use std::net::{Ipv4Addr, Ipv6Addr};
use std::sync::{Arc, Mutex, TryLockError};
use std::time::Instant;
use futures::{future, task, Async, Future, Poll};
use proto::op::{Message, Query, ResponseCode};
use proto::rr::domain::usage::{
ResolverUsage, DEFAULT, INVALID, IN_ADDR_ARPA_127, IP6_ARPA_1, LOCAL,
LOCALHOST as LOCALHOST_usage,
};
use proto::rr::{DNSClass, Name, RData, Record, RecordType};
use proto::xfer::{DnsHandle, DnsRequestOptions, DnsResponse};
use dns_lru;
use dns_lru::DnsLru;
use error::*;
use lookup::Lookup;
const MAX_QUERY_DEPTH: u8 = 7;
task_local! {
static QUERY_DEPTH: RefCell<u8> = RefCell::new(0)
}
lazy_static! {
static ref LOCALHOST: RData = RData::PTR(Name::from_ascii("localhost.").unwrap());
static ref LOCALHOST_V4: RData = RData::A(Ipv4Addr::new(127, 0, 0, 1));
static ref LOCALHOST_V6: RData = RData::AAAA(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1));
}
#[derive(Clone, Debug)]
#[doc(hidden)]
pub struct CachingClient<C: DnsHandle> {
lru: Arc<Mutex<DnsLru>>,
client: C,
}
impl<C: DnsHandle + 'static> CachingClient<C> {
#[doc(hidden)]
pub fn new(max_size: usize, client: C) -> Self {
Self::with_cache(
Arc::new(Mutex::new(DnsLru::new(max_size, Default::default()))),
client,
)
}
pub(crate) fn with_cache(lru: Arc<Mutex<DnsLru>>, client: C) -> Self {
CachingClient { lru, client }
}
pub fn lookup(
&mut self,
query: Query,
options: DnsRequestOptions,
) -> Box<Future<Item = Lookup, Error = ResolveError> + Send> {
if query.query_class() == DNSClass::IN {
let usage = match query.name() {
n if LOCALHOST_usage.zone_of(n) => &*LOCALHOST_usage,
n if IN_ADDR_ARPA_127.zone_of(n) => &*LOCALHOST_usage,
n if IP6_ARPA_1.zone_of(n) => &*LOCALHOST_usage,
n if INVALID.zone_of(n) => &*INVALID,
n if LOCAL.zone_of(n) => &*LOCAL,
_ => &*DEFAULT,
};
match usage.resolver() {
ResolverUsage::Loopback => match query.query_type() {
RecordType::A => {
return Box::new(future::ok(Lookup::from_rdata(
query,
LOCALHOST_V4.clone(),
)))
}
RecordType::AAAA => {
return Box::new(future::ok(Lookup::from_rdata(
query,
LOCALHOST_V6.clone(),
)))
}
RecordType::PTR => {
return Box::new(future::ok(Lookup::from_rdata(query, LOCALHOST.clone())))
}
_ => return Box::new(future::err(DnsLru::nx_error(query, None))),
},
#[cfg(feature = "mdns")]
ResolverUsage::LinkLocal => (),
#[cfg(not(feature = "mdns"))]
ResolverUsage::LinkLocal => (),
ResolverUsage::NxDomain => {
return Box::new(future::err(DnsLru::nx_error(query, None)))
}
ResolverUsage::Normal => (),
}
}
Box::new(QueryState::lookup(
query,
options,
&mut self.client,
self.lru.clone(),
))
}
}
struct FromCache {
query: Query,
options: DnsRequestOptions,
cache: Arc<Mutex<DnsLru>>,
}
impl Future for FromCache {
type Item = Option<Lookup>;
type Error = ResolveError;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
match self.cache.try_lock() {
Err(TryLockError::WouldBlock) => {
task::current().notify();
Ok(Async::NotReady)
}
Err(TryLockError::Poisoned(poison)) => {
Err(ResolveErrorKind::Msg(format!("poisoned: {}", poison)).into())
}
Ok(mut lru) => Ok(Async::Ready(lru.get(&self.query, Instant::now()))),
}
}
}
struct QueryFuture<C: DnsHandle + 'static> {
message_future: <C as DnsHandle>::Response,
query: Query,
cache: Arc<Mutex<DnsLru>>,
dnssec: bool,
options: DnsRequestOptions,
client: CachingClient<C>,
}
enum Records {
Exists(Vec<(Record, u32)>),
NoData { ttl: Option<u32> },
CnameChain {
next: Box<Future<Item = Lookup, Error = ResolveError> + Send>,
min_ttl: u32,
},
Chained { cached: Lookup, min_ttl: u32 },
}
impl<C: DnsHandle + 'static> QueryFuture<C> {
fn next_query(&mut self, query: Query, cname_ttl: u32, message: DnsResponse) -> Records {
if QUERY_DEPTH.with(|c| *c.borrow() >= MAX_QUERY_DEPTH) {
self.handle_nxdomain(message, true)
} else {
QUERY_DEPTH.with(|c| *c.borrow_mut() += 1);
Records::CnameChain {
next: self.client.lookup(query, self.options.clone()),
min_ttl: cname_ttl,
}
}
}
fn handle_noerror(&mut self, mut response: DnsResponse) -> Poll<Records, ResolveError> {
const INITIAL_TTL: u32 = dns_lru::MAX_TTL;
let (search_name, cname_ttl, was_cname) = {
let (search_name, cname_ttl, was_cname) =
if self.query.query_type().is_any() || self.query.query_type().is_cname() {
(Cow::Borrowed(self.query.name()), INITIAL_TTL, false)
} else {
response.messages().flat_map(Message::answers).fold(
(Cow::Borrowed(self.query.name()), INITIAL_TTL, false),
|(search_name, cname_ttl, was_cname), r| {
match *r.rdata() {
RData::CNAME(ref cname) => {
let ttl = cname_ttl.min(r.ttl());
debug_assert_eq!(r.rr_type(), RecordType::CNAME);
if search_name.as_ref() == r.name() {
return (Cow::Owned(cname.clone()), ttl, true);
}
}
RData::SRV(ref srv) => {
let ttl = cname_ttl.min(r.ttl());
debug_assert_eq!(r.rr_type(), RecordType::SRV);
return (Cow::Owned(srv.target().clone()), ttl, true);
}
_ => (),
}
(search_name, cname_ttl, was_cname)
},
)
};
let answers: Vec<Record> = response
.messages_mut()
.flat_map(Message::take_answers)
.collect();
let additionals: Vec<Record> = response
.messages_mut()
.flat_map(Message::take_additionals)
.collect();
let records = answers
.into_iter()
.chain(additionals.into_iter())
.filter_map(|r| {
let ttl = cname_ttl.min(r.ttl());
if self.query.query_class() == r.dns_class() {
if ((self.query.query_type().is_any()
|| self.query.query_type() == r.rr_type())
&& (search_name.as_ref() == r.name() || self.query.name() == r.name()))
|| (self.query.query_type().is_srv()
&& r.rr_type().is_ip_addr()
&& search_name.as_ref() == r.name())
{
Some((r, ttl))
} else {
None
}
} else {
None
}
})
.collect::<Vec<_>>();
if !records.is_empty() {
return Ok(Async::Ready(Records::Exists(records)));
}
(search_name.into_owned(), cname_ttl, was_cname)
};
if was_cname {
let next_query = Query::query(search_name, self.query.query_type());
Ok(Async::Ready(
self.next_query(next_query, cname_ttl, response),
))
} else {
Ok(Async::Ready(self.handle_nxdomain(response, true)))
}
}
fn handle_nxdomain(&self, mut message: DnsResponse, valid_nsec: bool) -> Records {
if valid_nsec || !self.dnssec {
let soa = message
.take_name_servers()
.into_iter()
.find(|r| r.rr_type() == RecordType::SOA);
let ttl = if let Some(RData::SOA(soa)) = soa.map(Record::unwrap_rdata) {
Some(soa.minimum())
} else {
None
};
Records::NoData { ttl }
} else {
Records::NoData { ttl: None }
}
}
}
impl<C: DnsHandle + 'static> Future for QueryFuture<C> {
type Item = Records;
type Error = ResolveError;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
match self.message_future.poll() {
Ok(Async::Ready(message)) => {
match message.response_code() {
ResponseCode::NXDomain => Ok(Async::Ready(self.handle_nxdomain(
message, false,
))),
ResponseCode::NoError => self.handle_noerror(message),
r => Err(ResolveErrorKind::Msg(format!("DNS Error: {}", r)).into()),
}
}
Ok(Async::NotReady) => Ok(Async::NotReady),
Err(err) => Err(err.into()),
}
}
}
struct InsertCache {
rdatas: Records,
query: Query,
cache: Arc<Mutex<DnsLru>>,
}
impl Future for InsertCache {
type Item = Lookup;
type Error = ResolveError;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
match self.cache.try_lock() {
Err(TryLockError::WouldBlock) => {
task::current().notify();
Ok(Async::NotReady)
}
Err(TryLockError::Poisoned(poison)) => {
Err(ResolveErrorKind::Msg(format!("poisoned: {}", poison)).into())
}
Ok(mut lru) => {
let query = mem::replace(&mut self.query, Query::new());
let rdata = mem::replace(&mut self.rdatas, Records::NoData { ttl: None });
match rdata {
Records::Exists(rdata) => {
Ok(Async::Ready(lru.insert(query, rdata, Instant::now())))
}
Records::Chained {
cached: lookup,
min_ttl: ttl,
} => Ok(Async::Ready(lru.duplicate(
query,
lookup,
ttl,
Instant::now(),
))),
Records::NoData { ttl: Some(ttl) } => {
Err(lru.negative(query, ttl, Instant::now()))
}
Records::NoData { ttl: None } | Records::CnameChain { .. } => {
Err(DnsLru::nx_error(query, None))
}
}
}
}
}
}
enum QueryState<C: DnsHandle + 'static> {
FromCache(FromCache, C),
Query(QueryFuture<C>),
CnameChain(
Box<Future<Item = Lookup, Error = ResolveError> + Send>,
Query,
u32,
Arc<Mutex<DnsLru>>,
),
InsertCache(InsertCache),
QueryError,
}
impl<C: DnsHandle + 'static> QueryState<C> {
pub(crate) fn lookup(
query: Query,
options: DnsRequestOptions,
client: &mut C,
cache: Arc<Mutex<DnsLru>>,
) -> QueryState<C> {
QueryState::FromCache(
FromCache {
query,
options,
cache,
},
client.clone(),
)
}
fn query_after_cache(&mut self) {
let from_cache_state = mem::replace(self, QueryState::QueryError);
match from_cache_state {
QueryState::FromCache(from_cache, mut client) => {
let cache = from_cache.cache;
let query = from_cache.query;
let options = from_cache.options;
let message_future = client.lookup(query.clone(), options.clone());
mem::replace(
self,
QueryState::Query(QueryFuture {
message_future,
query,
cache: cache.clone(),
dnssec: client.is_verifying_dnssec(),
options,
client: CachingClient::with_cache(cache, client),
}),
);
}
_ => panic!("bad state, expected FromCache"),
}
}
fn cname(
&mut self,
future: Box<Future<Item = Lookup, Error = ResolveError> + Send>,
cname_ttl: u32,
) {
let query_state = mem::replace(self, QueryState::QueryError);
match query_state {
QueryState::Query(QueryFuture {
message_future: _m,
query,
cache,
dnssec: _d,
options: _o,
client: _c,
}) => {
mem::replace(
self,
QueryState::CnameChain(future, query, cname_ttl, cache),
);
}
_ => panic!("bad state, expected Query"),
}
}
fn cache(&mut self, rdatas: Records) {
let query_state = mem::replace(self, QueryState::QueryError);
match query_state {
QueryState::Query(QueryFuture {
message_future: _m,
query,
cache,
dnssec: _d,
options: _o,
client: _c,
}) => {
match rdatas {
Records::CnameChain { .. } => {
panic!("CnameChain should have been polled in poll() of QueryState");
}
rdatas => {
mem::replace(
self,
QueryState::InsertCache(InsertCache {
rdatas,
query,
cache,
}),
);
}
}
}
QueryState::CnameChain(_, query, _, cache) => {
match rdatas {
Records::CnameChain { .. } => {
panic!("CnameChain should have been polled in poll() of QueryState");
}
rdatas => {
mem::replace(
self,
QueryState::InsertCache(InsertCache {
rdatas,
query,
cache,
}),
);
}
}
}
_ => panic!("bad state, expected Query"),
}
}
}
impl<C: DnsHandle + 'static> Future for QueryState<C> {
type Item = Lookup;
type Error = ResolveError;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
let records: Option<Records>;
match *self {
QueryState::FromCache(ref mut from_cache, ..) => {
match from_cache.poll() {
Ok(Async::Ready(None)) => (),
Ok(Async::Ready(Some(ips))) => return Ok(Async::Ready(ips)),
Ok(Async::NotReady) => return Ok(Async::NotReady),
Err(error) => return Err(error),
};
records = None;
}
QueryState::Query(ref mut query, ..) => {
let poll = query.poll();
match poll {
Ok(Async::NotReady) => {
return Ok(Async::NotReady);
}
Ok(Async::Ready(rdatas)) => records = Some(rdatas),
Err(e) => {
return Err(e);
}
}
}
QueryState::CnameChain(ref mut future, _, ttl, _) => {
let poll = future.poll();
match poll {
Ok(Async::NotReady) => {
return Ok(Async::NotReady);
}
Ok(Async::Ready(lookup)) => {
records = Some(Records::Chained {
cached: lookup,
min_ttl: ttl,
});
}
Err(e) => {
return Err(e);
}
}
}
QueryState::InsertCache(ref mut insert_cache) => {
return insert_cache.poll();
}
QueryState::QueryError => panic!("invalid error state"),
}
match *self {
QueryState::FromCache(..) => self.query_after_cache(),
QueryState::Query(..) => match records {
Some(Records::CnameChain {
next: future,
min_ttl: ttl,
}) => self.cname(future, ttl),
Some(records) => {
self.cache(records);
}
None => panic!("should have returned earlier"),
},
QueryState::CnameChain(..) => match records {
Some(records) => self.cache(records),
None => panic!("should have returned earlier"),
},
QueryState::InsertCache(..) | QueryState::QueryError => {
panic!("should have returned earlier")
}
}
task::current().notify();
Ok(Async::NotReady)
}
}
#[cfg(test)]
mod tests {
use std::net::*;
use std::str::FromStr;
use std::time::*;
use futures::future;
use proto::error::{ProtoError, ProtoResult};
use proto::op::{Message, Query};
use proto::rr::rdata::SRV;
use proto::rr::{Name, Record};
use super::*;
use lookup_ip::tests::*;
#[test]
fn test_empty_cache() {
let cache = Arc::new(Mutex::new(DnsLru::new(1, dns_lru::TtlConfig::default())));
let mut client = mock(vec![empty()]);
assert_eq!(
*QueryState::lookup(Query::new(), Default::default(), &mut client, cache)
.wait()
.unwrap_err()
.kind(),
ResolveErrorKind::NoRecordsFound {
query: Query::new(),
valid_until: None,
}
);
}
#[test]
fn test_from_cache() {
let cache = Arc::new(Mutex::new(DnsLru::new(1, dns_lru::TtlConfig::default())));
let query = Query::new();
cache.lock().unwrap().insert(
query.clone(),
vec![(
Record::from_rdata(
query.name().clone(),
u32::max_value(),
RData::A(Ipv4Addr::new(127, 0, 0, 1)),
),
u32::max_value(),
)],
Instant::now(),
);
let mut client = mock(vec![empty()]);
let ips = QueryState::lookup(Query::new(), Default::default(), &mut client, cache)
.wait()
.unwrap();
assert_eq!(
ips.iter().cloned().collect::<Vec<_>>(),
vec![RData::A(Ipv4Addr::new(127, 0, 0, 1))]
);
}
#[test]
fn test_no_cache_insert() {
let cache = Arc::new(Mutex::new(DnsLru::new(1, dns_lru::TtlConfig::default())));
let mut client = mock(vec![v4_message()]);
let ips = QueryState::lookup(Query::new(), Default::default(), &mut client, cache.clone())
.wait()
.unwrap();
assert_eq!(
ips.iter().cloned().collect::<Vec<_>>(),
vec![RData::A(Ipv4Addr::new(127, 0, 0, 1))]
);
let mut client = mock(vec![empty()]);
let ips = QueryState::lookup(Query::new(), Default::default(), &mut client, cache)
.wait()
.unwrap();
assert_eq!(
ips.iter().cloned().collect::<Vec<_>>(),
vec![RData::A(Ipv4Addr::new(127, 0, 0, 1))]
);
}
pub fn cname_message() -> ProtoResult<DnsResponse> {
let mut message = Message::new();
message.insert_answers(vec![Record::from_rdata(
Name::from_str("www.example.com.").unwrap(),
86400,
RData::CNAME(Name::from_str("actual.example.com.").unwrap()),
)]);
Ok(message.into())
}
pub fn srv_message() -> ProtoResult<DnsResponse> {
let mut message = Message::new();
message.insert_answers(vec![Record::from_rdata(
Name::from_str("_443._tcp.www.example.com.").unwrap(),
86400,
RData::SRV(SRV::new(
1,
2,
443,
Name::from_str("www.example.com.").unwrap(),
)),
)]);
Ok(message.into())
}
fn no_recursion_on_query_test(query_type: RecordType) {
let cache = Arc::new(Mutex::new(DnsLru::new(1, dns_lru::TtlConfig::default())));
let mut client = mock(vec![error(), cname_message()]);
let ips = QueryState::lookup(
Query::query(Name::from_str("www.example.com.").unwrap(), query_type),
Default::default(),
&mut client,
cache.clone(),
)
.wait()
.expect("lookup failed");
assert_eq!(
ips.iter().cloned().collect::<Vec<_>>(),
vec![RData::CNAME(Name::from_str("actual.example.com.").unwrap())]
);
}
#[test]
fn test_no_recursion_on_cname_query() {
no_recursion_on_query_test(RecordType::CNAME);
}
#[test]
fn test_no_recursion_on_all_query() {
no_recursion_on_query_test(RecordType::ANY);
}
#[test]
fn test_non_recursive_srv_query() {
let cache = Arc::new(Mutex::new(DnsLru::new(1, dns_lru::TtlConfig::default())));
let mut client = mock(vec![error(), srv_message()]);
let ips = QueryState::lookup(
Query::query(
Name::from_str("_443._tcp.www.example.com.").unwrap(),
RecordType::SRV,
),
Default::default(),
&mut client,
cache.clone(),
)
.wait()
.expect("lookup failed");
assert_eq!(
ips.iter().cloned().collect::<Vec<_>>(),
vec![RData::SRV(SRV::new(
1,
2,
443,
Name::from_str("www.example.com.").unwrap(),
))]
);
}
#[test]
fn test_single_srv_query_response() {
let cache = Arc::new(Mutex::new(DnsLru::new(1, dns_lru::TtlConfig::default())));
let mut message = srv_message().unwrap();
message.add_answer(Record::from_rdata(
Name::from_str("www.example.com.").unwrap(),
86400,
RData::CNAME(Name::from_str("actual.example.com.").unwrap()),
));
message.insert_additionals(vec![
Record::from_rdata(
Name::from_str("actual.example.com.").unwrap(),
86400,
RData::A(Ipv4Addr::new(127, 0, 0, 1)),
),
Record::from_rdata(
Name::from_str("actual.example.com.").unwrap(),
86400,
RData::AAAA(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)),
),
]);
let mut client = mock(vec![error(), Ok(message)]);
let ips = QueryState::lookup(
Query::query(
Name::from_str("_443._tcp.www.example.com.").unwrap(),
RecordType::SRV,
),
Default::default(),
&mut client,
cache.clone(),
)
.wait()
.expect("lookup failed");
assert_eq!(
ips.iter().cloned().collect::<Vec<_>>(),
vec![
RData::SRV(SRV::new(
1,
2,
443,
Name::from_str("www.example.com.").unwrap(),
)),
RData::A(Ipv4Addr::new(127, 0, 0, 1)),
RData::AAAA(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)),
]
);
}
fn cname_ttl_test(first: u32, second: u32) {
let lru = Arc::new(Mutex::new(DnsLru::new(1, dns_lru::TtlConfig::default())));
let client = CachingClient::with_cache(Arc::clone(&lru), mock(vec![error()]));
let mut query_future = QueryFuture {
message_future: Box::new(future::err(ProtoError::from("no message_future in test")))
as Box<Future<Item = DnsResponse, Error = ProtoError> + Send>,
query: Query::query(Name::from_str("ttl.example.com.").unwrap(), RecordType::A),
cache: lru,
dnssec: false,
options: Default::default(),
client,
};
let mut message = Message::new();
message.insert_answers(vec![Record::from_rdata(
Name::from_str("ttl.example.com.").unwrap(),
first,
RData::CNAME(Name::from_str("actual.example.com.").unwrap()),
)]);
message.insert_additionals(vec![Record::from_rdata(
Name::from_str("actual.example.com.").unwrap(),
second,
RData::A(Ipv4Addr::new(127, 0, 0, 1)),
)]);
let poll: Async<Records> = query_future
.handle_noerror(message.into())
.expect("handle_noerror failed");
assert!(poll.is_ready());
if let Async::Ready(records) = poll {
if let Records::Exists(records) = records {
assert!(records.iter().all(|&(_, ttl)| ttl == 1));
} else {
panic!("records don't exist");
}
} else {
panic!("poll not ready");
}
}
#[test]
fn test_cname_ttl() {
cname_ttl_test(1, 2);
cname_ttl_test(2, 1);
}
#[test]
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 { lru: cache, client };
{
let query = Query::query(Name::from_ascii("localhost.").unwrap(), RecordType::A);
let lookup = client
.lookup(query.clone(), Default::default())
.wait()
.expect("should have returned localhost");
assert_eq!(lookup.query(), &query);
assert_eq!(
lookup.iter().cloned().collect::<Vec<_>>(),
vec![LOCALHOST_V4.clone()]
);
}
{
let query = Query::query(Name::from_ascii("localhost.").unwrap(), RecordType::AAAA);
let lookup = client
.lookup(query.clone(), Default::default())
.wait()
.expect("should have returned localhost");
assert_eq!(lookup.query(), &query);
assert_eq!(
lookup.iter().cloned().collect::<Vec<_>>(),
vec![LOCALHOST_V6.clone()]
);
}
{
let query = Query::query(Name::from(Ipv4Addr::new(127, 0, 0, 1)), RecordType::PTR);
let lookup = client
.lookup(query.clone(), Default::default())
.wait()
.expect("should have returned localhost");
assert_eq!(lookup.query(), &query);
assert_eq!(
lookup.iter().cloned().collect::<Vec<_>>(),
vec![LOCALHOST.clone()]
);
}
{
let query = Query::query(
Name::from(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)),
RecordType::PTR,
);
let lookup = client
.lookup(query.clone(), Default::default())
.wait()
.expect("should have returned localhost");
assert_eq!(lookup.query(), &query);
assert_eq!(
lookup.iter().cloned().collect::<Vec<_>>(),
vec![LOCALHOST.clone()]
);
}
assert!(client
.lookup(
Query::query(Name::from_ascii("localhost.").unwrap(), RecordType::MX),
Default::default()
)
.wait()
.is_err());
assert!(client
.lookup(
Query::query(Name::from(Ipv4Addr::new(127, 0, 0, 1)), RecordType::MX),
Default::default()
)
.wait()
.is_err());
assert!(client
.lookup(
Query::query(
Name::from(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)),
RecordType::MX
),
Default::default()
)
.wait()
.is_err());
}
#[test]
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 { lru: cache, client };
assert!(client
.lookup(
Query::query(
Name::from_ascii("horrible.invalid.").unwrap(),
RecordType::A,
),
Default::default()
)
.wait()
.is_err());
}
#[test]
fn test_no_error_on_dot_local_no_mdns() {
let cache = Arc::new(Mutex::new(DnsLru::new(1, dns_lru::TtlConfig::default())));
let mut message = srv_message().unwrap();
message.add_answer(Record::from_rdata(
Name::from_str("www.example.local.").unwrap(),
86400,
RData::A(Ipv4Addr::new(127, 0, 0, 1)),
));
let client = mock(vec![error(), Ok(message)]);
let mut client = CachingClient { lru: cache, client };
assert!(client
.lookup(
Query::query(
Name::from_ascii("www.example.local.").unwrap(),
RecordType::A,
),
Default::default()
)
.wait()
.is_ok());
}
}