blob: 52d00390232ed8ba7bbd4cd2dcbee96e2b0d66bd [file] [log] [blame]
// Copyright 2015-2017 Benjamin Fry <benjaminfry@me.com>
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.
//! Lookup result from a resolution of ipv4 and ipv6 records with a Resolver.
use std::cmp::min;
use std::net::{Ipv4Addr, Ipv6Addr};
use std::pin::Pin;
use std::slice::Iter;
use std::sync::Arc;
use std::task::{Context, Poll};
use std::time::{Duration, Instant};
use std::vec::IntoIter;
use futures::{future, Future, FutureExt};
use proto::error::ProtoError;
use proto::op::Query;
use proto::rr::rdata;
use proto::rr::{Name, RData, Record, RecordType};
use proto::xfer::{DnsRequest, DnsRequestOptions, DnsResponse};
#[cfg(feature = "dnssec")]
use proto::DnssecDnsHandle;
use proto::{DnsHandle, RetryDnsHandle};
use crate::dns_lru::MAX_TTL;
use crate::error::*;
use crate::lookup_ip::LookupIpIter;
use crate::lookup_state::CachingClient;
use crate::name_server::{ConnectionProvider, NameServerPool};
/// Result of a DNS query when querying for any record type supported by the Trust-DNS Proto library.
///
/// For IP resolution see LookupIp, as it has more features for A and AAAA lookups.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Lookup {
query: Query,
records: Arc<Vec<Record>>,
valid_until: Instant,
}
impl Lookup {
/// Return new instance with given rdata and the maximum TTL.
pub fn from_rdata(query: Query, rdata: RData) -> Self {
let record = Record::from_rdata(query.name().clone(), MAX_TTL, rdata);
Self::new_with_max_ttl(query, Arc::new(vec![record]))
}
/// Return new instance with given records and the maximum TTL.
pub fn new_with_max_ttl(query: Query, records: Arc<Vec<Record>>) -> Self {
let valid_until = Instant::now() + Duration::from_secs(u64::from(MAX_TTL));
Lookup {
query,
records,
valid_until,
}
}
/// Return a new instance with the given records and deadline.
pub fn new_with_deadline(
query: Query,
records: Arc<Vec<Record>>,
valid_until: Instant,
) -> Self {
Lookup {
query,
records,
valid_until,
}
}
/// Returns a reference to the `Query` that was used to produce this result.
pub fn query(&self) -> &Query {
&self.query
}
/// Returns a borrowed iterator of the returned IPs
pub fn iter(&self) -> LookupIter {
LookupIter(self.records.iter())
}
/// Returns a borrowed iterator of the returned IPs
pub fn record_iter(&self) -> LookupRecordIter {
LookupRecordIter(self.records.iter())
}
/// Returns the `Instant` at which this `Lookup` is no longer valid.
pub fn valid_until(&self) -> Instant {
self.valid_until
}
#[doc(hidden)]
pub fn is_empty(&self) -> bool {
self.records.is_empty()
}
pub(crate) fn len(&self) -> usize {
self.records.len()
}
#[cfg(test)]
pub fn records(&self) -> &[Record] {
self.records.as_ref()
}
/// Clones the inner vec, appends the other vec
pub(crate) fn append(&self, other: Lookup) -> Self {
let mut records = Vec::with_capacity(self.len() + other.len());
records.extend_from_slice(&*self.records);
records.extend_from_slice(&*other.records);
// Choose the sooner deadline of the two lookups.
let valid_until = min(self.valid_until(), other.valid_until());
Self::new_with_deadline(self.query.clone(), Arc::new(records), valid_until)
}
}
/// Borrowed view of set of [`RData`]s returned from a Lookup
pub struct LookupIter<'a>(Iter<'a, Record>);
impl<'a> Iterator for LookupIter<'a> {
type Item = &'a RData;
fn next(&mut self) -> Option<Self::Item> {
self.0.next().map(Record::rdata)
}
}
/// Borrowed view of set of [`RData`]s returned from a Lookup
pub struct LookupRecordIter<'a>(Iter<'a, Record>);
impl<'a> Iterator for LookupRecordIter<'a> {
type Item = &'a Record;
fn next(&mut self) -> Option<Self::Item> {
self.0.next()
}
}
impl IntoIterator for Lookup {
type Item = RData;
type IntoIter = LookupIntoIter;
/// This is most likely not a free conversion, the `RData`s will be cloned if data is
/// held behind an Arc with more than one reference (which is most likely the case coming from cache)
fn into_iter(self) -> Self::IntoIter {
LookupIntoIter {
records: Arc::try_unwrap(self.records).map(IntoIterator::into_iter),
index: 0,
}
}
}
/// Borrowed view of set of [`RData`]s returned from a [`Lookup`].
///
/// This is not usually a zero overhead `Iterator`, it may result in clones of the [`RData`].
pub struct LookupIntoIter {
// the result of the try_unwrap on Arc
records: Result<IntoIter<Record>, Arc<Vec<Record>>>,
index: usize,
}
impl Iterator for LookupIntoIter {
type Item = RData;
fn next(&mut self) -> Option<Self::Item> {
match self.records {
// a zero overhead unwrap
Ok(ref mut iter) => iter.next().map(Record::unwrap_rdata),
Err(ref records) => {
let rdata = records.get(self.index).map(Record::rdata);
self.index += 1;
rdata.cloned()
}
}
}
}
/// Different lookup options for the lookup attempts and validation
#[derive(Clone)]
#[doc(hidden)]
pub enum LookupEither<C: DnsHandle + 'static, P: ConnectionProvider<Conn = C> + 'static> {
Retry(RetryDnsHandle<NameServerPool<C, P>>),
#[cfg(feature = "dnssec")]
Secure(DnssecDnsHandle<RetryDnsHandle<NameServerPool<C, P>>>),
}
impl<C: DnsHandle + Sync, P: ConnectionProvider<Conn = C>> DnsHandle for LookupEither<C, P> {
type Response = Pin<Box<dyn Future<Output = Result<DnsResponse, ProtoError>> + Send>>;
fn is_verifying_dnssec(&self) -> bool {
match *self {
LookupEither::Retry(ref c) => c.is_verifying_dnssec(),
#[cfg(feature = "dnssec")]
LookupEither::Secure(ref c) => c.is_verifying_dnssec(),
}
}
fn send<R: Into<DnsRequest> + Unpin + Send + 'static>(&mut self, request: R) -> Self::Response {
match *self {
LookupEither::Retry(ref mut c) => c.send(request),
#[cfg(feature = "dnssec")]
LookupEither::Secure(ref mut c) => c.send(request),
}
}
}
/// The Future returned from [`AsyncResolver`] when performing a lookup.
#[doc(hidden)]
pub struct LookupFuture<C>
where
C: DnsHandle + 'static,
{
client_cache: CachingClient<C>,
names: Vec<Name>,
record_type: RecordType,
options: DnsRequestOptions,
query: Pin<Box<dyn Future<Output = Result<Lookup, ResolveError>> + Send>>,
}
impl<C: DnsHandle + 'static> LookupFuture<C> {
/// Perform a lookup from a name and type to a set of RDatas
///
/// # Arguments
///
/// * `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.
/// * `record_type` - type of record being sought
/// * `client_cache` - cache with a connection to use for performing all lookups
#[doc(hidden)]
pub fn lookup(
mut names: Vec<Name>,
record_type: RecordType,
options: DnsRequestOptions,
mut client_cache: CachingClient<C>,
) -> Self {
let name = names.pop().ok_or_else(|| {
ResolveError::from(ResolveErrorKind::Message("can not lookup for no names"))
});
let query: Pin<Box<dyn Future<Output = Result<Lookup, ResolveError>> + Send>> = match name {
Ok(name) => client_cache
.lookup(Query::query(name, record_type), options.clone())
.boxed(),
Err(err) => future::err(err).boxed(),
};
LookupFuture {
client_cache,
names,
record_type,
options,
query,
}
}
}
impl<C: DnsHandle + 'static> Future for LookupFuture<C> {
type Output = Result<Lookup, ResolveError>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
loop {
// Try polling the underlying DNS query.
let query = self.query.as_mut().poll_unpin(cx);
// Determine whether or not we will attempt to retry the query.
let should_retry = match query {
// If the query is NotReady, yield immediately.
Poll::Pending => return Poll::Pending,
// If the query returned a successful lookup, we will attempt
// to retry if the lookup is empty. Otherwise, we will return
// that lookup.
Poll::Ready(Ok(ref lookup)) => lookup.records.len() == 0,
// If the query failed, we will attempt to retry.
Poll::Ready(Err(_)) => true,
};
if should_retry {
if let Some(name) = self.names.pop() {
let record_type = self.record_type;
let options = self.options.clone();
// If there's another name left to try, build a new query
// for that next name and continue looping.
self.query = self
.client_cache
.lookup(Query::query(name, record_type), options);
// Continue looping with the new query. It will be polled
// on the next iteration of the loop.
continue;
}
}
// If we didn't have to retry the query, or we weren't able to
// retry because we've exhausted the names to search, return the
// current query.
return query;
// If we skipped retrying the query, this will return the
// successful lookup, otherwise, if the retry failed, this will
// return the last query result --- either an empty lookup or the
// last error we saw.
}
}
}
/// The result of an SRV lookup
#[derive(Debug, Clone)]
pub struct SrvLookup(Lookup);
impl SrvLookup {
/// Returns an iterator over the SRV RData
pub fn iter(&self) -> SrvLookupIter {
SrvLookupIter(self.0.iter())
}
/// Returns a reference to the Query that was used to produce this result.
pub fn query(&self) -> &Query {
self.0.query()
}
/// Returns the list of IPs associated with the SRV record.
///
/// *Note*: That Trust-DNS performs a recursive lookup on SRV records for IPs if they were not included in the original request. If there are no IPs associated to the result, a subsequent query for the IPs via the `srv.target()` should not resolve to the IPs.
pub fn ip_iter(&self) -> LookupIpIter {
LookupIpIter(self.0.iter())
}
}
impl From<Lookup> for SrvLookup {
fn from(lookup: Lookup) -> Self {
SrvLookup(lookup)
}
}
/// An iterator over the Lookup type
pub struct SrvLookupIter<'i>(LookupIter<'i>);
impl<'i> Iterator for SrvLookupIter<'i> {
type Item = &'i rdata::SRV;
fn next(&mut self) -> Option<Self::Item> {
let iter: &mut _ = &mut self.0;
iter.filter_map(|rdata| match *rdata {
RData::SRV(ref data) => Some(data),
_ => None,
})
.next()
}
}
impl IntoIterator for SrvLookup {
type Item = rdata::SRV;
type IntoIter = SrvLookupIntoIter;
/// This is most likely not a free conversion, the RDatas will be cloned if data is
/// held behind an Arc with more than one reference (which is most likely the case coming from cache)
fn into_iter(self) -> Self::IntoIter {
SrvLookupIntoIter(self.0.into_iter())
}
}
/// Borrowed view of set of RDatas returned from a Lookup
pub struct SrvLookupIntoIter(LookupIntoIter);
impl Iterator for SrvLookupIntoIter {
type Item = rdata::SRV;
fn next(&mut self) -> Option<Self::Item> {
let iter: &mut _ = &mut self.0;
iter.filter_map(|rdata| match rdata {
RData::SRV(data) => Some(data),
_ => None,
})
.next()
}
}
/// Creates a Lookup result type from the specified components
macro_rules! lookup_type {
($l:ident, $i:ident, $ii:ident, $r:path, $t:path) => {
/// Contains the results of a lookup for the associated RecordType
#[derive(Debug, Clone)]
pub struct $l(Lookup);
impl $l {
/// Returns an iterator over the RData
pub fn iter(&self) -> $i {
$i(self.0.iter())
}
/// Returns a reference to the Query that was used to produce this result.
pub fn query(&self) -> &Query {
self.0.query()
}
/// Returns the `Instant` at which this result is no longer valid.
pub fn valid_until(&self) -> Instant {
self.0.valid_until()
}
}
impl From<Lookup> for $l {
fn from(lookup: Lookup) -> Self {
$l(lookup)
}
}
/// An iterator over the Lookup type
pub struct $i<'i>(LookupIter<'i>);
impl<'i> Iterator for $i<'i> {
type Item = &'i $t;
fn next(&mut self) -> Option<Self::Item> {
let iter: &mut _ = &mut self.0;
iter.filter_map(|rdata| match *rdata {
$r(ref data) => Some(data),
_ => None,
})
.next()
}
}
impl IntoIterator for $l {
type Item = $t;
type IntoIter = $ii;
/// This is most likely not a free conversion, the RDatas will be cloned if data is
/// held behind an Arc with more than one reference (which is most likely the case coming from cache)
fn into_iter(self) -> Self::IntoIter {
$ii(self.0.into_iter())
}
}
/// Borrowed view of set of RDatas returned from a Lookup
pub struct $ii(LookupIntoIter);
impl Iterator for $ii {
type Item = $t;
fn next(&mut self) -> Option<Self::Item> {
let iter: &mut _ = &mut self.0;
iter.filter_map(|rdata| match rdata {
$r(data) => Some(data),
_ => None,
})
.next()
}
}
};
}
// Generate all Lookup record types
lookup_type!(
ReverseLookup,
ReverseLookupIter,
ReverseLookupIntoIter,
RData::PTR,
Name
);
lookup_type!(
Ipv4Lookup,
Ipv4LookupIter,
Ipv4LookupIntoIter,
RData::A,
Ipv4Addr
);
lookup_type!(
Ipv6Lookup,
Ipv6LookupIter,
Ipv6LookupIntoIter,
RData::AAAA,
Ipv6Addr
);
lookup_type!(
MxLookup,
MxLookupIter,
MxLookupIntoIter,
RData::MX,
rdata::MX
);
lookup_type!(
TxtLookup,
TxtLookupIter,
TxtLookupIntoIter,
RData::TXT,
rdata::TXT
);
lookup_type!(
SoaLookup,
SoaLookupIter,
SoaLookupIntoIter,
RData::SOA,
rdata::SOA
);
lookup_type!(NsLookup, NsLookupIter, NsLookupIntoIter, RData::NS, Name);
#[cfg(test)]
pub mod tests {
use std::net::{IpAddr, Ipv4Addr};
use std::str::FromStr;
use std::sync::{Arc, Mutex};
use futures::executor::block_on;
use futures::{future, Future};
use proto::error::{ProtoErrorKind, ProtoResult};
use proto::op::Message;
use proto::rr::{Name, RData, Record, RecordType};
use proto::xfer::{DnsRequest, DnsRequestOptions};
use super::*;
#[derive(Clone)]
pub struct MockDnsHandle {
messages: Arc<Mutex<Vec<ProtoResult<DnsResponse>>>>,
}
impl DnsHandle for MockDnsHandle {
type Response = Pin<Box<dyn Future<Output = Result<DnsResponse, ProtoError>> + Send>>;
fn send<R: Into<DnsRequest>>(&mut self, _: R) -> Self::Response {
future::ready(self.messages.lock().unwrap().pop().unwrap_or_else(empty)).boxed()
}
}
pub fn v4_message() -> ProtoResult<DnsResponse> {
let mut message = Message::new();
message.insert_answers(vec![Record::from_rdata(
Name::root(),
86400,
RData::A(Ipv4Addr::new(127, 0, 0, 1)),
)]);
Ok(message.into())
}
pub fn empty() -> ProtoResult<DnsResponse> {
Ok(Message::new().into())
}
pub fn error() -> ProtoResult<DnsResponse> {
Err(ProtoErrorKind::from(std::io::Error::from(std::io::ErrorKind::Other)).into())
}
pub fn mock(messages: Vec<ProtoResult<DnsResponse>>) -> MockDnsHandle {
MockDnsHandle {
messages: Arc::new(Mutex::new(messages)),
}
}
#[test]
fn test_lookup() {
assert_eq!(
block_on(LookupFuture::lookup(
vec![Name::root()],
RecordType::A,
DnsRequestOptions::default(),
CachingClient::new(0, mock(vec![v4_message()])),
))
.unwrap()
.iter()
.map(|r| r.to_ip_addr().unwrap())
.collect::<Vec<IpAddr>>(),
vec![Ipv4Addr::new(127, 0, 0, 1)]
);
}
#[test]
fn test_lookup_into_iter() {
assert_eq!(
block_on(LookupFuture::lookup(
vec![Name::root()],
RecordType::A,
DnsRequestOptions::default(),
CachingClient::new(0, mock(vec![v4_message()])),
))
.unwrap()
.into_iter()
.map(|r| r.to_ip_addr().unwrap())
.collect::<Vec<IpAddr>>(),
vec![Ipv4Addr::new(127, 0, 0, 1)]
);
}
#[test]
fn test_error() {
assert!(block_on(LookupFuture::lookup(
vec![Name::root()],
RecordType::A,
DnsRequestOptions::default(),
CachingClient::new(0, mock(vec![error()])),
))
.is_err());
}
#[test]
fn test_empty_no_response() {
if let ResolveErrorKind::NoRecordsFound { query, valid_until } =
block_on(LookupFuture::lookup(
vec![Name::root()],
RecordType::A,
DnsRequestOptions::default(),
CachingClient::new(0, mock(vec![empty()])),
))
.unwrap_err()
.kind()
{
assert_eq!(*query, Query::query(Name::root(), RecordType::A));
assert_eq!(*valid_until, None);
} else {
panic!("wrong error recieved");
}
}
#[test]
fn test_lookup_into_iter_arc() {
let mut lookup = LookupIntoIter {
records: Err(Arc::new(vec![
Record::from_rdata(
Name::from_str("www.example.com.").unwrap(),
80,
RData::A(Ipv4Addr::new(127, 0, 0, 1)),
),
Record::from_rdata(
Name::from_str("www.example.com.").unwrap(),
80,
RData::A(Ipv4Addr::new(127, 0, 0, 2)),
),
])),
index: 0,
};
assert_eq!(
lookup.next().unwrap(),
RData::A(Ipv4Addr::new(127, 0, 0, 1))
);
assert_eq!(
lookup.next().unwrap(),
RData::A(Ipv4Addr::new(127, 0, 0, 2))
);
assert_eq!(lookup.next(), None);
}
#[test]
fn test_lookup_into_iter_vec() {
let mut lookup = LookupIntoIter {
records: Ok(vec![
Record::from_rdata(
Name::from_str("www.example.com.").unwrap(),
80,
RData::A(Ipv4Addr::new(127, 0, 0, 1)),
),
Record::from_rdata(
Name::from_str("www.example.com.").unwrap(),
80,
RData::A(Ipv4Addr::new(127, 0, 0, 2)),
),
]
.into_iter()),
index: 0,
};
assert_eq!(
lookup.next().unwrap(),
RData::A(Ipv4Addr::new(127, 0, 0, 1))
);
assert_eq!(
lookup.next().unwrap(),
RData::A(Ipv4Addr::new(127, 0, 0, 2))
);
assert_eq!(lookup.next(), None);
}
}