initial recursive resolution working
This commit is contained in:
parent
25861da44e
commit
a84deaca12
@ -69,8 +69,9 @@ impl fmt::Display for Header {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
write!(
|
||||
f,
|
||||
"{id}:{flags}:{code:?}:{op_code}:{answers}/{authorities}/{additionals}",
|
||||
"{id}:{message_type}:{flags}:{code:?}:{op_code}:{answers}/{authorities}/{additionals}",
|
||||
id = self.id,
|
||||
message_type = self.message_type,
|
||||
flags = self.flags(),
|
||||
code = self.response_code,
|
||||
op_code = self.op_code,
|
||||
|
@ -413,6 +413,14 @@ impl Message {
|
||||
self.header.response_code()
|
||||
}
|
||||
|
||||
/// Returns the query from this Message.
|
||||
///
|
||||
/// In almost all cases, a Message will only contain one query. This is a convenience function to get the single query.
|
||||
/// See the alternative `queries*` methods for the raw set of queries in the Message
|
||||
pub fn query(&self) -> Option<&Query> {
|
||||
self.queries.first()
|
||||
}
|
||||
|
||||
/// ```text
|
||||
/// Question Carries the query name and other query parameters.
|
||||
/// ```
|
||||
@ -1078,12 +1086,17 @@ impl fmt::Display for Message {
|
||||
|
||||
writeln!(f, "; query")?;
|
||||
write_query(self.queries(), f)?;
|
||||
writeln!(f, "; answers {}", self.answer_count())?;
|
||||
write_slice(self.answers(), f)?;
|
||||
writeln!(f, "; nameservers {}", self.name_server_count())?;
|
||||
write_slice(self.name_servers(), f)?;
|
||||
writeln!(f, "; additionals {}", self.additional_count())?;
|
||||
write_slice(self.additionals(), f)?;
|
||||
|
||||
if self.header().message_type() == MessageType::Response
|
||||
|| self.header().op_code() == OpCode::Update
|
||||
{
|
||||
writeln!(f, "; answers {}", self.answer_count())?;
|
||||
write_slice(self.answers(), f)?;
|
||||
writeln!(f, "; nameservers {}", self.name_server_count())?;
|
||||
write_slice(self.name_servers(), f)?;
|
||||
writeln!(f, "; additionals {}", self.additional_count())?;
|
||||
write_slice(self.additionals(), f)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -989,6 +989,11 @@ impl RData {
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if
|
||||
pub fn is_soa(&self) -> bool {
|
||||
matches!(self, RData::SOA(..))
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for RData {
|
||||
|
@ -32,8 +32,7 @@ pub struct DnsRequestOptions {
|
||||
impl Default for DnsRequestOptions {
|
||||
fn default() -> Self {
|
||||
#[allow(deprecated)]
|
||||
DnsRequestOptions {
|
||||
max_request_depth: 26,
|
||||
Self {
|
||||
expects_multiple_responses: false,
|
||||
use_edns: false,
|
||||
recursion_desired: true,
|
||||
|
@ -19,8 +19,7 @@ use futures_util::stream::Stream;
|
||||
|
||||
use crate::error::{ProtoError, ProtoErrorKind, ProtoResult};
|
||||
use crate::op::{Message, ResponseCode};
|
||||
use crate::rr::rdata::SOA;
|
||||
use crate::rr::{RData, RecordType};
|
||||
use crate::rr::{RData, Record, RecordType};
|
||||
|
||||
/// A stream returning DNS responses
|
||||
pub struct DnsResponseStream {
|
||||
@ -133,12 +132,10 @@ pub struct DnsResponse(Message);
|
||||
// TODO: when `impl Trait` lands in stable, remove this, and expose FlatMap over answers, et al.
|
||||
impl DnsResponse {
|
||||
/// Retrieves the SOA from the response. This will only exist if it was an authoritative response.
|
||||
pub fn soa(&self) -> Option<SOA> {
|
||||
pub fn soa(&self) -> Option<&Record> {
|
||||
self.name_servers()
|
||||
.iter()
|
||||
.filter_map(|record| record.data().and_then(RData::as_soa))
|
||||
.next()
|
||||
.cloned()
|
||||
.find(|record| record.data().map(|d| d.is_soa()).unwrap_or(false))
|
||||
}
|
||||
|
||||
/// Looks in the authority section for an SOA record from the response, and returns the negative_ttl, None if not available.
|
||||
|
@ -9,15 +9,16 @@
|
||||
|
||||
#![deny(missing_docs)]
|
||||
|
||||
use std::{fmt, io, sync};
|
||||
use std::{fmt, io};
|
||||
|
||||
use enum_as_inner::EnumAsInner;
|
||||
use thiserror::Error;
|
||||
use trust_dns_resolver::Name;
|
||||
|
||||
#[cfg(feature = "backtrace")]
|
||||
use crate::proto::{trace, ExtBacktrace};
|
||||
use crate::{
|
||||
proto::error::{ProtoError, ProtoErrorKind},
|
||||
proto::error::ProtoError,
|
||||
resolver::error::{ResolveError, ResolveErrorKind},
|
||||
};
|
||||
|
||||
@ -33,6 +34,10 @@ pub enum ErrorKind {
|
||||
#[error("{0}")]
|
||||
Msg(String),
|
||||
|
||||
/// An name error of some kind happened
|
||||
#[error("forward response: {0}")]
|
||||
Forward(Name),
|
||||
|
||||
/// An error got returned from IO
|
||||
#[error("io error: {0}")]
|
||||
Io(#[from] std::io::Error),
|
||||
@ -43,7 +48,7 @@ pub enum ErrorKind {
|
||||
|
||||
/// An error got returned by the trust-dns-proto crate
|
||||
#[error("proto error: {0}")]
|
||||
Resolve(#[from] ResolveError),
|
||||
Resolve(ResolveError),
|
||||
|
||||
/// A request timed out
|
||||
#[error("request timed out")]
|
||||
@ -127,11 +132,16 @@ impl From<Error> for String {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "wasm-bindgen")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "wasm-bindgen")))]
|
||||
impl From<Error> for wasm_bindgen_crate::JsValue {
|
||||
fn from(e: Error) -> Self {
|
||||
js_sys::Error::new(&e.to_string()).into()
|
||||
impl From<ResolveError> for Error {
|
||||
fn from(e: ResolveError) -> Self {
|
||||
if let ResolveErrorKind::NoRecordsFound { soa, .. } = e.kind() {
|
||||
match soa {
|
||||
Some(soa) => ErrorKind::Forward(soa.name().clone()).into(),
|
||||
_ => ErrorKind::Resolve(e).into(),
|
||||
}
|
||||
} else {
|
||||
ErrorKind::Resolve(e).into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -141,9 +151,10 @@ impl Clone for ErrorKind {
|
||||
match *self {
|
||||
Message(msg) => Message(msg),
|
||||
Msg(ref msg) => Msg(msg.clone()),
|
||||
Io(ref io) => Self::from(std::io::Error::from(io.kind())),
|
||||
Proto(ref proto) => Self::from(proto.clone()),
|
||||
Resolve(ref resolve) => Self::from(resolve.clone()),
|
||||
Forward(ref ns) => Forward(ns.clone()),
|
||||
Io(ref io) => Io(std::io::Error::from(io.kind())),
|
||||
Proto(ref proto) => Proto(proto.clone()),
|
||||
Resolve(ref resolve) => Resolve(resolve.clone()),
|
||||
Timeout => Self::Timeout,
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,10 @@
|
||||
// Copyright 2015-2022 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.
|
||||
|
||||
//! A recursive DNS resolver based on the Trust-DNS (stub) resolver
|
||||
|
||||
#![warn(
|
||||
@ -21,6 +28,7 @@
|
||||
|
||||
pub mod error;
|
||||
mod recursor;
|
||||
pub(crate) mod recursor_pool;
|
||||
|
||||
pub use error::{Error, ErrorKind};
|
||||
pub use recursor::Recursor;
|
||||
|
@ -1,53 +1,42 @@
|
||||
use std::{
|
||||
collections::{BTreeSet, HashMap},
|
||||
fmt,
|
||||
net::{IpAddr, SocketAddr},
|
||||
time::Instant,
|
||||
};
|
||||
// Copyright 2015-2022 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.
|
||||
|
||||
use std::{net::SocketAddr, time::Instant};
|
||||
|
||||
use async_recursion::async_recursion;
|
||||
use futures_util::{future::select_all, Future, FutureExt, StreamExt};
|
||||
use futures_util::{future::select_all, FutureExt};
|
||||
|
||||
use lru_cache::LruCache;
|
||||
use parking_lot::Mutex;
|
||||
use tracing::{debug, dispatcher::SetGlobalDefaultError, info, warn};
|
||||
use tracing::{debug, info, warn};
|
||||
|
||||
use trust_dns_proto::{
|
||||
op::{Message, Query},
|
||||
rr::{RData, Record, RecordSet, RecordType},
|
||||
xfer::{DnsRequestOptions, DnsResponse},
|
||||
DnsHandle,
|
||||
op::Query,
|
||||
rr::{RData, RecordType},
|
||||
};
|
||||
use trust_dns_resolver::{
|
||||
config::{NameServerConfig, NameServerConfigGroup, Protocol, ResolverConfig, ResolverOpts},
|
||||
error::{ResolveError, ResolveErrorKind},
|
||||
config::{NameServerConfig, NameServerConfigGroup, Protocol, ResolverOpts},
|
||||
dns_lru::{DnsLru, TtlConfig},
|
||||
error::ResolveError,
|
||||
lookup::Lookup,
|
||||
name_server::{GenericConnectionProvider, NameServerPool, RuntimeProvider, TokioRuntime},
|
||||
IntoName, Name, TokioAsyncResolver, TokioConnection, TokioConnectionProvider, TokioHandle,
|
||||
name_server::NameServerPool,
|
||||
Name, TokioConnectionProvider, TokioHandle,
|
||||
};
|
||||
|
||||
use crate::{Error, ErrorKind};
|
||||
use crate::{recursor_pool::RecursorPool, Error, ErrorKind};
|
||||
|
||||
/// Set of nameservers by the zone name
|
||||
type NameServerCache = LruCache<Name, NameServerPool<TokioConnection, TokioConnectionProvider>>;
|
||||
|
||||
/// Records that have been found
|
||||
///
|
||||
/// We will cache Message responses for simplicity
|
||||
type MessageCache = LruCache<RecursiveQuery, DnsResponse>;
|
||||
|
||||
/// Active request cache
|
||||
///
|
||||
/// The futures are Shared so any waiting on these results will resolve to the same result
|
||||
type ActiveRequests =
|
||||
HashMap<RecursiveQuery, Box<dyn Future<Output = Result<Message, ResolveError>>>>;
|
||||
type NameServerCache = LruCache<Name, RecursorPool>;
|
||||
|
||||
/// A top down recursive resolver which operates off a list of "hints", this is often the root nodes.
|
||||
pub struct Recursor {
|
||||
hints: NameServerPool<TokioConnection, TokioConnectionProvider>,
|
||||
opts: ResolverOpts,
|
||||
hints: RecursorPool,
|
||||
name_server_cache: Mutex<NameServerCache>,
|
||||
message_cache: Mutex<MessageCache>,
|
||||
record_cache: DnsLru,
|
||||
}
|
||||
|
||||
impl Recursor {
|
||||
@ -58,7 +47,6 @@ impl Recursor {
|
||||
/// This will panic if the hints are empty.
|
||||
pub fn new(hints: impl Into<NameServerConfigGroup>) -> Result<Self, ResolveError> {
|
||||
// configure the trust-dns-resolver
|
||||
let mut config = ResolverConfig::new();
|
||||
let hints: NameServerConfigGroup = hints.into();
|
||||
|
||||
assert!(!hints.is_empty(), "hints must not be empty");
|
||||
@ -66,14 +54,14 @@ impl Recursor {
|
||||
let opts = recursor_opts();
|
||||
let hints =
|
||||
NameServerPool::from_config(hints, &opts, TokioConnectionProvider::new(TokioHandle));
|
||||
let hints = RecursorPool::from(Name::root(), hints);
|
||||
let name_server_cache = Mutex::new(NameServerCache::new(100)); // TODO: make this configurable
|
||||
let message_cache = Mutex::new(MessageCache::new(100));
|
||||
let record_cache = DnsLru::new(100, TtlConfig::default());
|
||||
|
||||
Ok(Self {
|
||||
hints,
|
||||
opts,
|
||||
name_server_cache,
|
||||
message_cache,
|
||||
record_cache,
|
||||
})
|
||||
}
|
||||
|
||||
@ -238,64 +226,81 @@ impl Recursor {
|
||||
/// has contiguous zones at the root and MIL domains, but also has a non-
|
||||
/// contiguous zone at ISI.EDU.
|
||||
/// ```
|
||||
pub async fn resolve(
|
||||
&self,
|
||||
domain: Name,
|
||||
ty: RecordType,
|
||||
request_time: Instant,
|
||||
) -> Result<DnsResponse, Error> {
|
||||
// wild guess on number fo lookups needed
|
||||
let lookup: RecursiveQuery = (domain, ty).into();
|
||||
pub async fn resolve(&self, query: Query, request_time: Instant) -> Result<Lookup, Error> {
|
||||
if let Some(lookup) = self.record_cache.get(&query, request_time) {
|
||||
return lookup.map_err(Into::into);
|
||||
}
|
||||
|
||||
// not in cache, let's look for an ns record for lookup
|
||||
let zone = if lookup.ty == RecordType::NS {
|
||||
lookup.domain.base_name()
|
||||
let zone = if query.query_type() == RecordType::NS {
|
||||
query.name().base_name()
|
||||
} else {
|
||||
// look for the NS records "inside" the zone
|
||||
lookup.domain.clone()
|
||||
query.name().clone()
|
||||
};
|
||||
|
||||
let ns = self.get_ns_pool_for_zone(zone, request_time).await?;
|
||||
let mut zone = zone;
|
||||
let mut ns = None;
|
||||
|
||||
let response = self.lookup(lookup, ns).await?;
|
||||
// max number of forwarding processes
|
||||
'max_forward: for _ in 0..20 {
|
||||
match self.get_ns_pool_for_zone(zone.clone(), request_time).await {
|
||||
Ok(found) => {
|
||||
// found the nameserver
|
||||
ns = Some(found);
|
||||
break 'max_forward;
|
||||
}
|
||||
Err(e) => match e.kind() {
|
||||
ErrorKind::Forward(name) => {
|
||||
// if we already had this name, don't try again
|
||||
if &zone == name {
|
||||
debug!("zone previously searched for {name}");
|
||||
break 'max_forward;
|
||||
};
|
||||
|
||||
debug!("ns forwarded to {name}");
|
||||
zone = name.clone();
|
||||
}
|
||||
_ => return Err(e),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
let ns = ns.ok_or_else(|| Error::from(format!("no nameserver found for {}", zone)))?;
|
||||
debug!("found zone {} for {}", ns.zone(), query);
|
||||
|
||||
let response = self.lookup(query, ns, request_time).await?;
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
async fn lookup(
|
||||
&self,
|
||||
query: RecursiveQuery,
|
||||
mut ns: NameServerPool<TokioConnection, TokioConnectionProvider>,
|
||||
) -> Result<DnsResponse, Error> {
|
||||
if let Some(cached) = self.message_cache.lock().get_mut(&query) {
|
||||
return Ok(cached.clone());
|
||||
async fn lookup(&self, query: Query, ns: RecursorPool, now: Instant) -> Result<Lookup, Error> {
|
||||
if let Some(lookup) = self.record_cache.get(&query, now) {
|
||||
debug!("cached data {:?}", lookup);
|
||||
return lookup.map_err(Into::into);
|
||||
}
|
||||
|
||||
info!("querying: {}", query);
|
||||
|
||||
let query = Query::query(query.domain, query.ty);
|
||||
let mut options = DnsRequestOptions::default();
|
||||
options.use_edns = false; // TODO: this should be configurable
|
||||
options.recursion_desired = false;
|
||||
|
||||
let mut response = ns.lookup(query, options);
|
||||
let response = ns.lookup(query.clone());
|
||||
|
||||
// TODO: we are only expecting one response
|
||||
// TODO: should we change DnsHandle to always be a single response? And build a totally custom handler for other situations?
|
||||
if let Some(response) = response.next().await {
|
||||
// TODO: check if data is "authentic"
|
||||
match response {
|
||||
Ok(r) => {
|
||||
info!("response: {}", r.header());
|
||||
debug!("response: {}", *r);
|
||||
Ok(r)
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("lookup error: {}", e);
|
||||
Err(ErrorKind::from(e).into())
|
||||
}
|
||||
// TODO: check if data is "authentic"
|
||||
match response.await {
|
||||
Ok(mut r) => {
|
||||
info!("response: {}", r.header());
|
||||
let records = r
|
||||
.take_answers()
|
||||
.into_iter()
|
||||
.chain(r.take_name_servers())
|
||||
.chain(r.take_additionals());
|
||||
|
||||
let lookup = self.record_cache.insert_records(query, records, now);
|
||||
|
||||
lookup.ok_or_else(|| Error::from("no records found"))
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("lookup error: {}", e);
|
||||
Err(Error::from(e))
|
||||
}
|
||||
} else {
|
||||
Err("no responses from nameserver".into())
|
||||
}
|
||||
}
|
||||
|
||||
@ -304,7 +309,7 @@ impl Recursor {
|
||||
&self,
|
||||
zone: Name,
|
||||
request_time: Instant,
|
||||
) -> Result<NameServerPool<TokioConnection, TokioConnectionProvider>, Error> {
|
||||
) -> Result<RecursorPool, Error> {
|
||||
// TODO: need to check TTLs here.
|
||||
if let Some(ns) = self.name_server_cache.lock().get_mut(&zone) {
|
||||
return Ok(ns.clone());
|
||||
@ -313,7 +318,7 @@ impl Recursor {
|
||||
let parent_zone = zone.base_name();
|
||||
|
||||
let nameserver_pool = if parent_zone.is_root() {
|
||||
debug!("using ROOTS for {zone} nameservers");
|
||||
debug!("using hints for {zone} nameservers");
|
||||
self.hints.clone()
|
||||
} else {
|
||||
self.get_ns_pool_for_zone(parent_zone, request_time).await?
|
||||
@ -321,11 +326,13 @@ impl Recursor {
|
||||
|
||||
// TODO: check for cached ns pool for this zone
|
||||
|
||||
let lookup = RecursiveQuery::from((zone.clone(), RecordType::NS));
|
||||
let response = self.lookup(lookup.clone(), nameserver_pool).await?;
|
||||
let lookup = Query::query(zone.clone(), RecordType::NS);
|
||||
let response = self
|
||||
.lookup(lookup.clone(), nameserver_pool.clone(), request_time)
|
||||
.await?;
|
||||
|
||||
let zone_nameservers = response.name_servers();
|
||||
let glue = response.additionals();
|
||||
// let zone_nameservers = response.name_servers();
|
||||
// let glue = response.additionals();
|
||||
|
||||
// TODO: grab TTL and use for cache
|
||||
// get all the NS records and glue
|
||||
@ -333,22 +340,38 @@ impl Recursor {
|
||||
let mut need_ips_for_names = Vec::new();
|
||||
|
||||
// unpack all glued records
|
||||
for zns in zone_nameservers {
|
||||
for zns in response.record_iter() {
|
||||
if let Some(ns_data) = zns.data().and_then(RData::as_ns) {
|
||||
let glue_ips = glue
|
||||
.iter()
|
||||
.filter(|g| g.name() == ns_data)
|
||||
.map(Record::data)
|
||||
.filter_map(|g| match g {
|
||||
Some(RData::A(a)) => Some(IpAddr::from(*a)),
|
||||
Some(RData::AAAA(aaaa)) => Some(IpAddr::from(*aaaa)),
|
||||
_ => None,
|
||||
});
|
||||
// let glue_ips = glue
|
||||
// .iter()
|
||||
// .filter(|g| g.name() == ns_data)
|
||||
// .filter_map(Record::data)
|
||||
// .filter_map(RData::to_ip_addr);
|
||||
|
||||
let cached_a = self
|
||||
.record_cache
|
||||
.get(&Query::query(ns_data.clone(), RecordType::A), request_time);
|
||||
let cached_aaaa = self.record_cache.get(
|
||||
&Query::query(ns_data.clone(), RecordType::AAAA),
|
||||
request_time,
|
||||
);
|
||||
|
||||
let cached_a = cached_a.and_then(Result::ok).map(Lookup::into_iter);
|
||||
let cached_aaaa = cached_aaaa.and_then(Result::ok).map(Lookup::into_iter);
|
||||
|
||||
let glue_ips = cached_a
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.chain(cached_aaaa.into_iter().flatten())
|
||||
.filter_map(|r| RData::to_ip_addr(&r));
|
||||
|
||||
let mut had_glue = false;
|
||||
for ip in glue_ips {
|
||||
let udp = NameServerConfig::new(SocketAddr::from((ip, 53)), Protocol::Udp);
|
||||
let tcp = NameServerConfig::new(SocketAddr::from((ip, 53)), Protocol::Tcp);
|
||||
let mut udp = NameServerConfig::new(SocketAddr::from((ip, 53)), Protocol::Udp);
|
||||
let mut tcp = NameServerConfig::new(SocketAddr::from((ip, 53)), Protocol::Tcp);
|
||||
|
||||
udp.trust_nx_responses = true;
|
||||
tcp.trust_nx_responses = true;
|
||||
|
||||
config_group.push(udp);
|
||||
config_group.push(tcp);
|
||||
@ -363,15 +386,17 @@ impl Recursor {
|
||||
}
|
||||
|
||||
// collect missing IP addresses, select over them all, get the addresses
|
||||
if !need_ips_for_names.is_empty() {
|
||||
// make it configurable to query for all records?
|
||||
if config_group.is_empty() && !need_ips_for_names.is_empty() {
|
||||
debug!("need glue for {zone}");
|
||||
let a_resolves = need_ips_for_names.iter().take(1).map(|name| {
|
||||
self.resolve((*name).clone(), RecordType::A, request_time)
|
||||
.boxed()
|
||||
let a_query = Query::query((*name).clone(), RecordType::A);
|
||||
self.resolve(a_query, request_time).boxed()
|
||||
});
|
||||
|
||||
let aaaa_resolves = need_ips_for_names.iter().take(1).map(|name| {
|
||||
self.resolve((*name).clone(), RecordType::AAAA, request_time)
|
||||
.boxed()
|
||||
let aaaa_query = Query::query((*name).clone(), RecordType::AAAA);
|
||||
self.resolve(aaaa_query, request_time).boxed()
|
||||
});
|
||||
|
||||
let mut a_resolves: Vec<_> = a_resolves.chain(aaaa_resolves).collect();
|
||||
@ -381,17 +406,8 @@ impl Recursor {
|
||||
|
||||
match next {
|
||||
Ok(response) => {
|
||||
debug!("A or AAAA response: {}", *response);
|
||||
let ips =
|
||||
response
|
||||
.answers()
|
||||
.iter()
|
||||
.map(Record::data)
|
||||
.filter_map(|d| match d {
|
||||
Some(RData::A(a)) => Some(IpAddr::from(*a)),
|
||||
Some(RData::AAAA(aaaa)) => Some(IpAddr::from(*aaaa)),
|
||||
_ => None,
|
||||
});
|
||||
debug!("A or AAAA response: {:?}", response);
|
||||
let ips = response.iter().filter_map(RData::to_ip_addr);
|
||||
|
||||
for ip in ips {
|
||||
let udp =
|
||||
@ -416,34 +432,15 @@ impl Recursor {
|
||||
&recursor_opts(),
|
||||
TokioConnectionProvider::new(TokioHandle),
|
||||
);
|
||||
let ns = RecursorPool::from(zone.clone(), ns);
|
||||
|
||||
// store in cache for future usage
|
||||
debug!("found nameservers for {}", zone);
|
||||
self.name_server_cache.lock().insert(zone, ns.clone());
|
||||
Ok(ns)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Hash, Eq, PartialEq, Ord, PartialOrd)]
|
||||
struct RecursiveQuery {
|
||||
domain: Name,
|
||||
ty: RecordType,
|
||||
}
|
||||
|
||||
impl From<(Name, RecordType)> for RecursiveQuery {
|
||||
fn from(name_ty: (Name, RecordType)) -> Self {
|
||||
Self {
|
||||
domain: name_ty.0,
|
||||
ty: name_ty.1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for RecursiveQuery {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
write!(f, "({},{})", self.domain, self.ty)
|
||||
}
|
||||
}
|
||||
|
||||
fn recursor_opts() -> ResolverOpts {
|
||||
let mut options = ResolverOpts::default();
|
||||
options.ndots = 0;
|
||||
@ -455,8 +452,3 @@ fn recursor_opts() -> ResolverOpts {
|
||||
|
||||
options
|
||||
}
|
||||
|
||||
enum RecursiveLookup {
|
||||
Found(RecordSet),
|
||||
Forward(RecordSet),
|
||||
}
|
||||
|
112
crates/recursor/src/recursor_pool.rs
Normal file
112
crates/recursor/src/recursor_pool.rs
Normal file
@ -0,0 +1,112 @@
|
||||
// Copyright 2015-2022 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.
|
||||
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
pin::Pin,
|
||||
sync::Arc,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
use futures_util::{future::Shared, Future, FutureExt, StreamExt};
|
||||
use parking_lot::Mutex;
|
||||
use tracing::info;
|
||||
use trust_dns_proto::{
|
||||
op::Query,
|
||||
xfer::{DnsRequestOptions, DnsResponse},
|
||||
DnsHandle,
|
||||
};
|
||||
use trust_dns_resolver::{
|
||||
error::{ResolveError, ResolveErrorKind},
|
||||
name_server::NameServerPool,
|
||||
Name, TokioConnection, TokioConnectionProvider,
|
||||
};
|
||||
|
||||
/// Active request cache
|
||||
///
|
||||
/// The futures are Shared so any waiting on these results will resolve to the same result
|
||||
type ActiveRequests = HashMap<Query, SharedLookup>;
|
||||
|
||||
type DnsResponseFuture =
|
||||
Box<dyn Future<Output = Option<Result<DnsResponse, ResolveError>>> + Send + 'static>;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct SharedLookup(Shared<Pin<DnsResponseFuture>>);
|
||||
|
||||
impl Future for SharedLookup {
|
||||
type Output = Result<DnsResponse, ResolveError>;
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
self.0.poll_unpin(cx).map(|o| match o {
|
||||
Some(r) => r,
|
||||
None => Err(ResolveErrorKind::Message("no response from nameserver").into()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct RecursorPool {
|
||||
zone: Name,
|
||||
ns: NameServerPool<TokioConnection, TokioConnectionProvider>,
|
||||
active_requests: Arc<Mutex<ActiveRequests>>,
|
||||
}
|
||||
|
||||
impl RecursorPool {
|
||||
pub(crate) fn from(
|
||||
zone: Name,
|
||||
ns: NameServerPool<TokioConnection, TokioConnectionProvider>,
|
||||
) -> Self {
|
||||
let active_requests = Arc::new(Mutex::new(ActiveRequests::default()));
|
||||
|
||||
Self {
|
||||
zone,
|
||||
ns,
|
||||
active_requests,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn zone(&self) -> &Name {
|
||||
&self.zone
|
||||
}
|
||||
|
||||
pub(crate) async fn lookup(&self, query: Query) -> Result<DnsResponse, ResolveError> {
|
||||
let mut ns = self.ns.clone();
|
||||
|
||||
let query_cpy = query.clone();
|
||||
|
||||
// block concurrent requests
|
||||
let lookup = self
|
||||
.active_requests
|
||||
.lock()
|
||||
.entry(query.clone())
|
||||
.or_insert_with(move || {
|
||||
info!("querying {} for {}", self.zone, query_cpy);
|
||||
|
||||
let mut options = DnsRequestOptions::default();
|
||||
options.use_edns = false; // TODO: this should be configurable
|
||||
options.recursion_desired = false;
|
||||
|
||||
// convert the lookup into a shared future
|
||||
let lookup = ns
|
||||
.lookup(query_cpy, options)
|
||||
.into_future()
|
||||
.map(|(next, _)| next)
|
||||
.boxed()
|
||||
.shared();
|
||||
|
||||
SharedLookup(lookup)
|
||||
})
|
||||
.clone();
|
||||
|
||||
let result = lookup.await;
|
||||
|
||||
// remove the concurrent request marker
|
||||
self.active_requests.lock().remove(&query);
|
||||
|
||||
result
|
||||
}
|
||||
}
|
@ -23,7 +23,6 @@ use proto::rr::domain::usage::{
|
||||
ResolverUsage, DEFAULT, INVALID, IN_ADDR_ARPA_127, IP6_ARPA_1, LOCAL,
|
||||
LOCALHOST as LOCALHOST_usage, ONION,
|
||||
};
|
||||
use proto::rr::rdata::SOA;
|
||||
use proto::rr::{DNSClass, Name, RData, Record, RecordType};
|
||||
use proto::xfer::{DnsHandle, DnsRequestOptions, DnsResponse, FirstAnswer};
|
||||
|
||||
@ -269,7 +268,7 @@ where
|
||||
is_dnssec: bool,
|
||||
valid_nsec: bool,
|
||||
query: Query,
|
||||
soa: Option<SOA>,
|
||||
soa: Option<Record>,
|
||||
negative_ttl: Option<u32>,
|
||||
response_code: ResponseCode,
|
||||
trusted: bool,
|
||||
@ -310,7 +309,7 @@ where
|
||||
const INITIAL_TTL: u32 = dns_lru::MAX_TTL;
|
||||
|
||||
// need to capture these before the subsequent and destructive record processing
|
||||
let soa = response.soa();
|
||||
let soa = response.soa().cloned();
|
||||
let negative_ttl = response.negative_ttl();
|
||||
let response_code = response.response_code();
|
||||
|
||||
|
@ -7,6 +7,7 @@
|
||||
|
||||
//! An LRU cache designed for work with DNS lookups
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::convert::TryFrom;
|
||||
use std::sync::Arc;
|
||||
use std::time::{Duration, Instant};
|
||||
@ -44,8 +45,9 @@ impl LruValue {
|
||||
}
|
||||
}
|
||||
|
||||
/// And LRU eviction cache specifically for storing DNS records
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct DnsLru {
|
||||
pub struct DnsLru {
|
||||
cache: Arc<Mutex<LruCache<Query, LruValue>>>,
|
||||
/// A minimum TTL value for positive responses.
|
||||
///
|
||||
@ -93,7 +95,7 @@ pub(crate) struct DnsLru {
|
||||
/// shouldn't cause any issue as this will never be used in serialization,
|
||||
/// but understand that this would be outside the standard range.
|
||||
#[derive(Copy, Clone, Debug, Default)]
|
||||
pub(crate) struct TtlConfig {
|
||||
pub struct TtlConfig {
|
||||
/// An optional minimum TTL value for positive responses.
|
||||
///
|
||||
/// Positive responses with TTLs under `positive_min_ttl` will use
|
||||
@ -117,7 +119,8 @@ pub(crate) struct TtlConfig {
|
||||
}
|
||||
|
||||
impl TtlConfig {
|
||||
pub(crate) fn from_opts(opts: &config::ResolverOpts) -> Self {
|
||||
/// Construct the LRU based on the ResolverOpts configuration
|
||||
pub fn from_opts(opts: &config::ResolverOpts) -> Self {
|
||||
Self {
|
||||
positive_min_ttl: opts.positive_min_ttl,
|
||||
negative_min_ttl: opts.negative_min_ttl,
|
||||
@ -128,7 +131,13 @@ impl TtlConfig {
|
||||
}
|
||||
|
||||
impl DnsLru {
|
||||
pub(crate) fn new(capacity: usize, ttl_cfg: TtlConfig) -> Self {
|
||||
/// Construct a new cache
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `capacity` - size in number of records, this can be the max size of 2048 (record size) * `capacity`
|
||||
/// * `ttl_cfg` - force minimums and maximums for cached records
|
||||
pub fn new(capacity: usize, ttl_cfg: TtlConfig) -> Self {
|
||||
let TtlConfig {
|
||||
positive_min_ttl,
|
||||
negative_min_ttl,
|
||||
@ -187,6 +196,54 @@ impl DnsLru {
|
||||
lookup
|
||||
}
|
||||
|
||||
/// inserts a record based on the name and type.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `original_query` - is used for matching the records that should be returned
|
||||
/// * `records` - the records will be partitioned by type and name for storage in the cache
|
||||
/// * `now` - current time for use in associating TTLs
|
||||
///
|
||||
/// # Return
|
||||
///
|
||||
/// This should always return some records, but will be None if there are no records or the original_query matches none
|
||||
pub fn insert_records(
|
||||
&self,
|
||||
original_query: Query,
|
||||
records: impl Iterator<Item = Record>,
|
||||
now: Instant,
|
||||
) -> Option<Lookup> {
|
||||
// collect all records by name
|
||||
let records = records.fold(
|
||||
HashMap::<Query, Vec<(Record, u32)>>::new(),
|
||||
|mut map, record| {
|
||||
let mut query = Query::query(record.name().clone(), record.record_type());
|
||||
query.set_query_class(record.dns_class());
|
||||
|
||||
let ttl = record.ttl();
|
||||
|
||||
map.entry(query)
|
||||
.or_insert_with(Vec::default)
|
||||
.push((record, ttl));
|
||||
|
||||
map
|
||||
},
|
||||
);
|
||||
|
||||
// now insert by record type and name
|
||||
let mut lookup = None;
|
||||
for (query, records_and_ttl) in records {
|
||||
let is_query = original_query == query;
|
||||
let inserted = self.insert(query, records_and_ttl, now);
|
||||
|
||||
if is_query {
|
||||
lookup = Some(inserted)
|
||||
}
|
||||
}
|
||||
|
||||
lookup
|
||||
}
|
||||
|
||||
/// Generally for inserting a set of records that have already been cached, but with a different Query.
|
||||
pub(crate) fn duplicate(&self, query: Query, lookup: Lookup, ttl: u32, now: Instant) -> Lookup {
|
||||
let ttl = Duration::from_secs(u64::from(ttl));
|
||||
@ -260,8 +317,8 @@ impl DnsLru {
|
||||
error
|
||||
}
|
||||
|
||||
/// This needs to be mut b/c it's an LRU, meaning the ordering of elements will potentially change on retrieval...
|
||||
pub(crate) fn get(&self, query: &Query, now: Instant) -> Option<Result<Lookup, ResolveError>> {
|
||||
/// Based on the query, see if there are any records available
|
||||
pub fn get(&self, query: &Query, now: Instant) -> Option<Result<Lookup, ResolveError>> {
|
||||
let mut out_of_date = false;
|
||||
let mut cache = self.cache.lock();
|
||||
let lookup = cache.get_mut(query).and_then(|value| {
|
||||
|
@ -12,10 +12,10 @@ use std::{fmt, io, sync};
|
||||
|
||||
use thiserror::Error;
|
||||
use tracing::debug;
|
||||
use trust_dns_proto::rr::Record;
|
||||
|
||||
use crate::proto::error::{ProtoError, ProtoErrorKind};
|
||||
use crate::proto::op::{Query, ResponseCode};
|
||||
use crate::proto::rr::rdata::SOA;
|
||||
use crate::proto::xfer::retry_dns_handle::RetryableError;
|
||||
use crate::proto::xfer::DnsResponse;
|
||||
#[cfg(feature = "backtrace")]
|
||||
@ -42,12 +42,12 @@ pub enum ResolveErrorKind {
|
||||
NoConnections,
|
||||
|
||||
/// No records were found for a query
|
||||
#[error("no record found for {query}")]
|
||||
#[error("no record found for {:?}", query)]
|
||||
NoRecordsFound {
|
||||
/// The query for which no records were found.
|
||||
query: Box<Query>,
|
||||
/// If an SOA is present, then this is an authoritative response.
|
||||
soa: Option<Box<SOA>>,
|
||||
/// If an SOA is present, then this is an authoritative response or a referral to another nameserver, see the negative_type field.
|
||||
soa: Option<Box<Record>>,
|
||||
/// negative ttl, as determined from DnsResponse::negative_ttl
|
||||
/// this will only be present if the SOA was also present.
|
||||
negative_ttl: Option<u32>,
|
||||
@ -111,7 +111,7 @@ pub struct ResolveError {
|
||||
impl ResolveError {
|
||||
pub(crate) fn nx_error(
|
||||
query: Query,
|
||||
soa: Option<SOA>,
|
||||
soa: Option<Record>,
|
||||
negative_ttl: Option<u32>,
|
||||
response_code: ResponseCode,
|
||||
trusted: bool,
|
||||
@ -145,7 +145,7 @@ impl ResolveError {
|
||||
|
||||
/// A conversion to determine if the response is an error
|
||||
pub fn from_response(response: DnsResponse, trust_nx: bool) -> Result<DnsResponse, Self> {
|
||||
debug!("Response:{}", response.header());
|
||||
debug!("Response:{}", *response);
|
||||
|
||||
match response.response_code() {
|
||||
response_code @ ResponseCode::ServFail
|
||||
@ -167,7 +167,7 @@ impl ResolveError {
|
||||
| response_code @ ResponseCode::BADTRUNC
|
||||
| response_code @ ResponseCode::BADCOOKIE => {
|
||||
let mut response = response;
|
||||
let soa = response.soa();
|
||||
let soa = response.soa().cloned();
|
||||
let query = response.take_queries().drain(..).next().unwrap_or_default();
|
||||
let error_kind = ResolveErrorKind::NoRecordsFound {
|
||||
query: Box::new(query),
|
||||
@ -188,7 +188,7 @@ impl ResolveError {
|
||||
// let valid_until = if response.is_authoritative() { now + response.get_negative_ttl() };
|
||||
|
||||
let mut response = response;
|
||||
let soa = response.soa();
|
||||
let soa = response.soa().cloned();
|
||||
let negative_ttl = response.negative_ttl();
|
||||
let trusted = if response_code == ResponseCode::NoError { false } else { trust_nx };
|
||||
let query = response.take_queries().drain(..).next().unwrap_or_default();
|
||||
|
@ -258,7 +258,7 @@ pub extern crate trust_dns_proto as proto;
|
||||
mod async_resolver;
|
||||
pub mod caching_client;
|
||||
pub mod config;
|
||||
mod dns_lru;
|
||||
pub mod dns_lru;
|
||||
pub mod dns_sd;
|
||||
pub mod error;
|
||||
mod hosts;
|
||||
|
@ -88,6 +88,7 @@ pub trait RuntimeProvider: Clone + 'static {
|
||||
|
||||
/// A type defines the Handle which can spawn future.
|
||||
pub trait Spawn {
|
||||
/// Spawn a future in the background
|
||||
fn spawn_bg<F>(&mut self, future: F)
|
||||
where
|
||||
F: Future<Output = Result<(), ProtoError>> + Send + 'static;
|
||||
@ -98,6 +99,7 @@ pub trait Spawn {
|
||||
pub struct GenericConnectionProvider<R: RuntimeProvider>(R::Handle);
|
||||
|
||||
impl<R: RuntimeProvider> GenericConnectionProvider<R> {
|
||||
/// construct a new Connection provider based on the Runtime Handle
|
||||
pub fn new(handle: R::Handle) -> Self {
|
||||
Self(handle)
|
||||
}
|
||||
@ -379,6 +381,7 @@ pub mod tokio_runtime {
|
||||
use super::*;
|
||||
use tokio::net::UdpSocket as TokioUdpSocket;
|
||||
|
||||
/// A handle to the Tokio runtime
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct TokioHandle;
|
||||
impl Spawn for TokioHandle {
|
||||
@ -390,6 +393,7 @@ pub mod tokio_runtime {
|
||||
}
|
||||
}
|
||||
|
||||
/// The Tokio Runtime for async execution
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct TokioRuntime;
|
||||
impl RuntimeProvider for TokioRuntime {
|
||||
@ -398,6 +402,10 @@ pub mod tokio_runtime {
|
||||
type Timer = TokioTime;
|
||||
type Udp = TokioUdpSocket;
|
||||
}
|
||||
|
||||
/// An alias for Tokio use cases
|
||||
pub type TokioConnection = GenericConnection;
|
||||
|
||||
/// An alias for Tokio use cases
|
||||
pub type TokioConnectionProvider = GenericConnectionProvider<TokioRuntime>;
|
||||
}
|
||||
|
@ -5,6 +5,8 @@
|
||||
// http://opensource.org/licenses/MIT>, at your option. This file may not be
|
||||
// copied, modified, or distributed except according to those terms.
|
||||
|
||||
//! A module with associated items for working with nameservers
|
||||
|
||||
mod connection_provider;
|
||||
#[allow(clippy::module_inception)]
|
||||
mod name_server;
|
||||
|
@ -52,12 +52,14 @@ impl<C: DnsHandle<Error = ResolveError>, P: ConnectionProvider<Conn = C>> Debug
|
||||
#[cfg(feature = "tokio-runtime")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "tokio-runtime")))]
|
||||
impl NameServer<TokioConnection, TokioConnectionProvider> {
|
||||
/// A shortcut for constructing a nameserver usable in the Tokio runtime
|
||||
pub fn new(config: NameServerConfig, options: ResolverOpts, runtime: TokioHandle) -> Self {
|
||||
Self::new_with_provider(config, options, TokioConnectionProvider::new(runtime))
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: DnsHandle<Error = ResolveError>, P: ConnectionProvider<Conn = C>> NameServer<C, P> {
|
||||
/// Construct a new Nameserver with the configuration and options. The connection provider will create UDP and TCP sockets
|
||||
pub fn new_with_provider(
|
||||
config: NameServerConfig,
|
||||
options: ResolverOpts,
|
||||
@ -169,6 +171,7 @@ impl<C: DnsHandle<Error = ResolveError>, P: ConnectionProvider<Conn = C>> NameSe
|
||||
}
|
||||
}
|
||||
|
||||
/// Specifies that thie NameServer will treat negative responses as permanent failures and will not retry
|
||||
pub fn trust_nx_responses(&self) -> bool {
|
||||
self.config.trust_nx_responses
|
||||
}
|
||||
|
@ -47,7 +47,7 @@ pub struct NameServerPool<
|
||||
#[cfg(test)]
|
||||
#[cfg(feature = "tokio-runtime")]
|
||||
impl NameServerPool<TokioConnection, TokioConnectionProvider> {
|
||||
pub(crate) fn from_config(
|
||||
pub(crate) fn tokio_from_config(
|
||||
config: &ResolverConfig,
|
||||
options: &ResolverOpts,
|
||||
runtime: TokioHandle,
|
||||
@ -113,7 +113,7 @@ where
|
||||
|
||||
/// Construct a NameServerPool from a set of name server configs
|
||||
pub fn from_config(
|
||||
mut name_servers: NameServerConfigGroup,
|
||||
name_servers: NameServerConfigGroup,
|
||||
options: &ResolverOpts,
|
||||
conn_provider: P,
|
||||
) -> Self {
|
||||
@ -496,7 +496,7 @@ mod tests {
|
||||
resolver_config.add_name_server(config2);
|
||||
|
||||
let io_loop = Runtime::new().unwrap();
|
||||
let mut pool = NameServerPool::<_, TokioConnectionProvider>::from_config(
|
||||
let mut pool = NameServerPool::<_, TokioConnectionProvider>::tokio_from_config(
|
||||
&resolver_config,
|
||||
&ResolverOpts::default(),
|
||||
TokioHandle,
|
||||
|
@ -8,7 +8,6 @@
|
||||
use std::io;
|
||||
|
||||
use tracing::{debug, info};
|
||||
use trust_dns_proto::xfer::DnsRequestOptions;
|
||||
|
||||
use crate::{
|
||||
authority::{
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2015-2020 Benjamin Fry <benjaminfry@me.com>
|
||||
// Copyright 2015-2022 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
|
||||
@ -22,18 +22,19 @@
|
||||
|
||||
use std::{
|
||||
net::{IpAddr, SocketAddr},
|
||||
path::{Path, PathBuf},
|
||||
path::PathBuf,
|
||||
time::Instant,
|
||||
};
|
||||
|
||||
use clap::Parser;
|
||||
use console::style;
|
||||
|
||||
use trust_dns_client::op::Query;
|
||||
use trust_dns_recursor::Recursor;
|
||||
use trust_dns_resolver::{
|
||||
config::{NameServerConfig, NameServerConfigGroup, Protocol, ResolverConfig, ResolverOpts},
|
||||
config::{NameServerConfig, NameServerConfigGroup, Protocol},
|
||||
proto::rr::RecordType,
|
||||
Name, TokioAsyncResolver,
|
||||
Name,
|
||||
};
|
||||
|
||||
/// A CLI interface for the trust-dns-recursor.
|
||||
@ -156,24 +157,11 @@ pub async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
(udp && ns.protocol == Protocol::Udp) || (tcp && ns.protocol == Protocol::Tcp)
|
||||
});
|
||||
|
||||
let name_servers =
|
||||
hints
|
||||
.iter()
|
||||
.map(|n| format!("{}", n))
|
||||
.fold(String::new(), |mut names, n| {
|
||||
if !names.is_empty() {
|
||||
names.push_str(", ")
|
||||
}
|
||||
|
||||
names.push_str(&n);
|
||||
names
|
||||
});
|
||||
|
||||
// query parameters
|
||||
let name = opts.domainname;
|
||||
let ty = opts.ty;
|
||||
|
||||
let mut recursor = Recursor::new(hints)?;
|
||||
let recursor = Recursor::new(hints)?;
|
||||
|
||||
// execute query
|
||||
println!(
|
||||
@ -183,7 +171,8 @@ pub async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
);
|
||||
|
||||
let now = Instant::now();
|
||||
let lookup = recursor.resolve(name, ty, now).await?;
|
||||
let query = Query::query(name, ty);
|
||||
let lookup = recursor.resolve(query, now).await?;
|
||||
|
||||
// report response, TODO: better display of errors
|
||||
println!(
|
||||
@ -192,15 +181,7 @@ pub async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
style(&lookup).blue()
|
||||
);
|
||||
|
||||
let answers = lookup.answers();
|
||||
let nameservers = lookup.name_servers();
|
||||
let additionals = lookup.additionals();
|
||||
let records = answers
|
||||
.iter()
|
||||
.chain(nameservers.iter())
|
||||
.chain(additionals.iter());
|
||||
|
||||
for r in records.filter(|r| r.record_type() == ty) {
|
||||
for r in lookup.record_iter().filter(|r| r.record_type() == ty) {
|
||||
print!(
|
||||
"\t{name} {ttl} {class} {ty}",
|
||||
name = style(r.name()).blue(),
|
||||
|
Loading…
Reference in New Issue
Block a user