initial layout of recursor
This commit is contained in:
parent
47ac8b39f7
commit
28ffe0e8b6
4
Cargo.lock
generated
4
Cargo.lock
generated
@ -319,9 +319,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "3.2.15"
|
||||
version = "3.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ba52acd3b0a5c33aeada5cdaa3267cdc7c594a98731d4268cdc1532f4264cb4"
|
||||
checksum = "01d42c94ce7c2252681b5fed4d3627cc807b13dfc033246bd05d5b252399000e"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro-error",
|
||||
|
@ -7,7 +7,9 @@ edition = "2021"
|
||||
# A short blurb about the package. This is not rendered in any format when
|
||||
# uploaded to crates.io (aka this is not markdown)
|
||||
description = """
|
||||
Trust-DNS is a safe and secure DNS recursive resolver with DNSSec support.
|
||||
*WARNING* This library is experimental
|
||||
|
||||
Trust-DNS Recursor is a safe and secure DNS recursive resolver with DNSSec support.
|
||||
Trust-DNS is based on the Tokio and Futures libraries, which means
|
||||
it should be easily integrated into other software that also use those
|
||||
libraries. This library can be used as in the server and binary for performing recursive lookups.
|
||||
|
@ -1,21 +1,45 @@
|
||||
use std::{fmt, time::Instant};
|
||||
|
||||
use log::debug;
|
||||
|
||||
use trust_dns_proto::rr::{RecordSet, RecordType};
|
||||
use trust_dns_resolver::{
|
||||
config::{NameServerConfigGroup, ResolverConfig, ResolverOpts},
|
||||
error::ResolveError,
|
||||
lookup::Lookup,
|
||||
name_server::NameServerPool,
|
||||
IntoName, Name, TokioAsyncResolver, TokioConnection, TokioConnectionProvider,
|
||||
use std::{
|
||||
collections::BTreeSet,
|
||||
fmt,
|
||||
net::{IpAddr, SocketAddr},
|
||||
time::Instant,
|
||||
};
|
||||
|
||||
use crate::Error;
|
||||
use futures_util::StreamExt;
|
||||
use log::debug;
|
||||
use lru_cache::LruCache;
|
||||
|
||||
use trust_dns_proto::{
|
||||
op::Query,
|
||||
rr::{RData, RecordSet, RecordType},
|
||||
xfer::{DnsRequestOptions, DnsResponse},
|
||||
DnsHandle,
|
||||
};
|
||||
use trust_dns_resolver::{
|
||||
config::{NameServerConfigGroup, ResolverConfig, ResolverOpts},
|
||||
error::{ResolveError, ResolveErrorKind},
|
||||
lookup::Lookup,
|
||||
name_server::{GenericConnectionProvider, NameServerPool, RuntimeProvider, TokioRuntime},
|
||||
IntoName, Name, TokioAsyncResolver, TokioConnection, TokioConnectionProvider, TokioHandle,
|
||||
};
|
||||
|
||||
use crate::{Error, ErrorKind};
|
||||
|
||||
/// A socket_addr based via of known nameservers.
|
||||
///
|
||||
/// The SocketAddr is matched to the Glue records in the GlueCache
|
||||
type NameServerCache =
|
||||
LruCache<ConnectionKey, NameServerPool<TokioConnection, TokioConnectionProvider>>;
|
||||
|
||||
/// Records that have been found
|
||||
type RecordCache = LruCache<CacheKey, RecordSet>;
|
||||
|
||||
/// A top down recursive resolver which operates off a list of "hints", this is often the root nodes.
|
||||
pub struct Recursor {
|
||||
hints: TokioAsyncResolver,
|
||||
hints: NameServerPool<TokioConnection, TokioConnectionProvider>,
|
||||
opts: ResolverOpts,
|
||||
name_server_cache: NameServerCache,
|
||||
record_cache: RecordCache,
|
||||
}
|
||||
|
||||
impl Recursor {
|
||||
@ -25,14 +49,18 @@ impl Recursor {
|
||||
let mut config = ResolverConfig::new();
|
||||
let hints: NameServerConfigGroup = hints.into();
|
||||
|
||||
for hint in hints.into_inner() {
|
||||
config.add_name_server(hint);
|
||||
}
|
||||
|
||||
let opts = recursor_opts();
|
||||
let hints = TokioAsyncResolver::tokio(config, opts)?;
|
||||
let hints =
|
||||
NameServerPool::from_config(hints, &opts, TokioConnectionProvider::new(TokioHandle));
|
||||
let name_server_cache = NameServerCache::new(100); // TODO: make this configurable
|
||||
let record_cache = RecordCache::new(100);
|
||||
|
||||
Ok(Self { hints, opts })
|
||||
Ok(Self {
|
||||
hints,
|
||||
opts,
|
||||
name_server_cache,
|
||||
record_cache,
|
||||
})
|
||||
}
|
||||
|
||||
/// Permform a recursive resolution
|
||||
@ -197,11 +225,11 @@ impl Recursor {
|
||||
/// contiguous zone at ISI.EDU.
|
||||
/// ```
|
||||
pub async fn resolve<N: IntoName>(
|
||||
&self,
|
||||
&mut self,
|
||||
domain: N,
|
||||
ty: RecordType,
|
||||
request_time: Instant,
|
||||
) -> Result<Lookup, ResolveError> {
|
||||
) -> Result<&RecordSet, ResolveError> {
|
||||
let domain = domain.into_name()?;
|
||||
|
||||
// wild guess on number fo lookups needed
|
||||
@ -209,43 +237,156 @@ impl Recursor {
|
||||
lookups.push((domain.clone(), ty).into());
|
||||
|
||||
// collect all the nameservers we need.
|
||||
let mut next_ns = domain;
|
||||
loop {
|
||||
let ns = next_ns.base_name();
|
||||
if ns.is_root() {
|
||||
break;
|
||||
// let mut next_ns = domain;
|
||||
// loop {
|
||||
// let ns = next_ns.base_name();
|
||||
// if ns.is_root() {
|
||||
// break;
|
||||
// }
|
||||
|
||||
// lookups.push((ns.clone(), RecordType::NS).into());
|
||||
// next_ns = ns;
|
||||
// }
|
||||
|
||||
// peak the next thing... only pop if we've found it.
|
||||
while let Some(lookup) = lookups.last() {
|
||||
let cached = self.check_cache(lookup.clone());
|
||||
|
||||
if cached.is_some() {
|
||||
lookups.pop();
|
||||
continue;
|
||||
}
|
||||
|
||||
lookups.push((ns.clone(), RecordType::NS).into());
|
||||
next_ns = ns;
|
||||
// not in cache, let's look for an ns record for lookup
|
||||
let ns_query = if lookup.ty == RecordType::NS {
|
||||
// if the lookup was NS, then we need to bump up a level
|
||||
RecursiveQuery::from((lookup.domain.base_name(), RecordType::NS))
|
||||
} else {
|
||||
// look for the NS records "inside" the zone
|
||||
RecursiveQuery::from((lookup.domain.clone(), RecordType::NS))
|
||||
};
|
||||
|
||||
let nameserver_pool = if ns_query.domain.is_root() {
|
||||
&self.hints
|
||||
} else {
|
||||
// see if we have the nameserver records cached
|
||||
let ns_cache = if let Some(ns) = self.check_cache(ns_query.clone()) {
|
||||
// collect the NS records
|
||||
let names = ns
|
||||
.records_without_rrsigs()
|
||||
.filter_map(|r| r.data().map(RData::as_ns).flatten());
|
||||
|
||||
let names = names.collect::<Vec<_>>();
|
||||
|
||||
if names.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(names)
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let ns_cache = if let Some(ns_cache) = ns_cache {
|
||||
ns_cache
|
||||
} else {
|
||||
// add to search list and continue
|
||||
// we have more to find.
|
||||
lookups.push(ns_query);
|
||||
continue;
|
||||
};
|
||||
|
||||
// now see if we have the glue for these
|
||||
let mut glue = BTreeSet::<IpAddr>::new();
|
||||
|
||||
todo!()
|
||||
// for ns in ns_cache {
|
||||
// let glue = self.check_glue();
|
||||
};
|
||||
|
||||
log::info!("sending request: {}", lookup);
|
||||
// let response = self.lookup(lookup.clone(), nameserver_pool).await?;
|
||||
// log::info!("got response: {}", response.header());
|
||||
todo!()
|
||||
}
|
||||
|
||||
for to_resolve in lookups {
|
||||
debug!("resolving: {}", to_resolve);
|
||||
}
|
||||
// we've gone through the entire search list, so we either found the record or not
|
||||
// self.check_cache((domain.clone(), ty).into())
|
||||
// .ok_or_else(|| ResolveErrorKind::Message("No records found").into())
|
||||
todo!()
|
||||
}
|
||||
|
||||
todo!();
|
||||
fn check_cache(&mut self, query: RecursiveQuery) -> Option<&RecordSet> {
|
||||
let key = CacheKey {
|
||||
domain: query.domain,
|
||||
ty: query.ty,
|
||||
};
|
||||
|
||||
self.record_cache.get_mut(&key).map(|v| &*v)
|
||||
}
|
||||
|
||||
fn check_glue(&mut self, domain: &Name) -> Vec<IpAddr> {
|
||||
let mut ipv4s: Vec<IpAddr> = self
|
||||
.check_cache(RecursiveQuery {
|
||||
domain: domain.clone(),
|
||||
ty: RecordType::A,
|
||||
})
|
||||
.map(|r| r.records_without_rrsigs())
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.filter_map(|r| r.data().map(RData::as_a).flatten())
|
||||
.map(|i| IpAddr::from(*i))
|
||||
.collect();
|
||||
|
||||
let ipv6s = self
|
||||
.check_cache(RecursiveQuery {
|
||||
domain: domain.clone(),
|
||||
ty: RecordType::AAAA,
|
||||
})
|
||||
.map(|r| r.records_without_rrsigs())
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.filter_map(|r| r.data().map(RData::as_aaaa).flatten())
|
||||
.map(|i| IpAddr::from(*i));
|
||||
|
||||
// combine the ips for glue...
|
||||
ipv4s.extend(ipv6s);
|
||||
ipv4s
|
||||
}
|
||||
|
||||
async fn lookup(
|
||||
&self,
|
||||
domain: &Name,
|
||||
ty: RecordType,
|
||||
ns: &NameServerPool<TokioConnection, TokioConnectionProvider>,
|
||||
) -> Result<RecordSet, Error> {
|
||||
unimplemented!()
|
||||
query: RecursiveQuery,
|
||||
mut ns: NameServerPool<TokioConnection, TokioConnectionProvider>,
|
||||
) -> Result<DnsResponse, Error> {
|
||||
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);
|
||||
|
||||
// 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"
|
||||
response.map_err(|e| ErrorKind::from(e).into())
|
||||
} else {
|
||||
Err("no responses from nameserver".into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct RecursiveQuery {
|
||||
name: Name,
|
||||
domain: Name,
|
||||
ty: RecordType,
|
||||
}
|
||||
|
||||
impl From<(Name, RecordType)> for RecursiveQuery {
|
||||
fn from(name_ty: (Name, RecordType)) -> Self {
|
||||
Self {
|
||||
name: name_ty.0,
|
||||
domain: name_ty.0,
|
||||
ty: name_ty.1,
|
||||
}
|
||||
}
|
||||
@ -253,7 +394,7 @@ impl From<(Name, RecordType)> for RecursiveQuery {
|
||||
|
||||
impl fmt::Display for RecursiveQuery {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
write!(f, "({},{})", self.name, self.ty)
|
||||
write!(f, "({},{})", self.domain, self.ty)
|
||||
}
|
||||
}
|
||||
|
||||
@ -272,3 +413,24 @@ enum RecursiveLookup {
|
||||
Found(RecordSet),
|
||||
Forward(RecordSet),
|
||||
}
|
||||
|
||||
// FIXME: this needs a borrowed variant
|
||||
#[derive(Clone, Hash, Eq, PartialEq, Ord, PartialOrd)]
|
||||
struct CacheKey {
|
||||
domain: Name,
|
||||
ty: RecordType,
|
||||
}
|
||||
|
||||
// FIXME: this is inefficient, doing this to hash from list of addrs for a NameServer to connections.
|
||||
#[derive(Hash, Eq, PartialEq, Ord, PartialOrd)]
|
||||
struct ConnectionKey {
|
||||
addrs: BTreeSet<IpAddr>,
|
||||
}
|
||||
|
||||
impl ConnectionKey {
|
||||
fn from_addresses(addrs: &[IpAddr]) -> Self {
|
||||
let addrs = BTreeSet::from_iter(addrs.iter().map(|a| *a));
|
||||
|
||||
Self { addrs }
|
||||
}
|
||||
}
|
||||
|
@ -266,7 +266,7 @@ mod hosts;
|
||||
mod https;
|
||||
pub mod lookup;
|
||||
pub mod lookup_ip;
|
||||
#[doc(hidden)]
|
||||
// TODO: consider #[doc(hidden)]
|
||||
pub mod name_server;
|
||||
#[cfg(feature = "dns-over-quic")]
|
||||
mod quic;
|
||||
|
@ -19,7 +19,7 @@ use proto::xfer::{DnsHandle, DnsRequest, DnsResponse, FirstAnswer};
|
||||
use proto::Time;
|
||||
use tracing::debug;
|
||||
|
||||
use crate::config::{ResolverConfig, ResolverOpts, ServerOrderingStrategy};
|
||||
use crate::config::{NameServerConfigGroup, ResolverConfig, ResolverOpts, ServerOrderingStrategy};
|
||||
use crate::error::{ResolveError, ResolveErrorKind};
|
||||
#[cfg(feature = "mdns")]
|
||||
use crate::name_server;
|
||||
@ -111,6 +111,33 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Construct a NameServerPool from a set of name server configs
|
||||
pub fn from_config(
|
||||
mut name_servers: NameServerConfigGroup,
|
||||
options: &ResolverOpts,
|
||||
conn_provider: P,
|
||||
) -> Self {
|
||||
let map_config_to_ns = |ns_config| {
|
||||
NameServer::<C, P>::new_with_provider(ns_config, *options, conn_provider.clone())
|
||||
};
|
||||
|
||||
let (datagram, stream): (Vec<_>, Vec<_>) = name_servers
|
||||
.into_inner()
|
||||
.into_iter()
|
||||
.partition(|ns| ns.protocol.is_datagram());
|
||||
|
||||
let datagram_conns: Vec<_> = datagram.into_iter().map(map_config_to_ns).collect();
|
||||
let stream_conns: Vec<_> = stream.into_iter().map(map_config_to_ns).collect();
|
||||
|
||||
Self {
|
||||
datagram_conns: Arc::from(datagram_conns),
|
||||
stream_conns: Arc::from(stream_conns),
|
||||
#[cfg(feature = "mdns")]
|
||||
mdns_conns: name_server::mdns_nameserver(*options, conn_provider.clone(), false),
|
||||
options: *options,
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[cfg(not(feature = "mdns"))]
|
||||
pub fn from_nameservers(
|
||||
|
92
tests/test-data/named_test_configs/default/root.zone
Normal file
92
tests/test-data/named_test_configs/default/root.zone
Normal file
@ -0,0 +1,92 @@
|
||||
; This file holds the information on root name servers needed to
|
||||
; initialize cache of Internet domain name servers
|
||||
; (e.g. reference this file in the "cache . <file>"
|
||||
; configuration file of BIND domain name servers).
|
||||
;
|
||||
; This file is made available by InterNIC
|
||||
; under anonymous FTP as
|
||||
; file /domain/named.cache
|
||||
; on server FTP.INTERNIC.NET
|
||||
; -OR- RS.INTERNIC.NET
|
||||
;
|
||||
; last update: February 16, 2022
|
||||
; related version of root zone: 2022021601
|
||||
;
|
||||
; FORMERLY NS.INTERNIC.NET
|
||||
;
|
||||
. 3600000 NS A.ROOT-SERVERS.NET.
|
||||
A.ROOT-SERVERS.NET. 3600000 A 198.41.0.4
|
||||
A.ROOT-SERVERS.NET. 3600000 AAAA 2001:503:ba3e::2:30
|
||||
;
|
||||
; FORMERLY NS1.ISI.EDU
|
||||
;
|
||||
. 3600000 NS B.ROOT-SERVERS.NET.
|
||||
B.ROOT-SERVERS.NET. 3600000 A 199.9.14.201
|
||||
B.ROOT-SERVERS.NET. 3600000 AAAA 2001:500:200::b
|
||||
;
|
||||
; FORMERLY C.PSI.NET
|
||||
;
|
||||
. 3600000 NS C.ROOT-SERVERS.NET.
|
||||
C.ROOT-SERVERS.NET. 3600000 A 192.33.4.12
|
||||
C.ROOT-SERVERS.NET. 3600000 AAAA 2001:500:2::c
|
||||
;
|
||||
; FORMERLY TERP.UMD.EDU
|
||||
;
|
||||
. 3600000 NS D.ROOT-SERVERS.NET.
|
||||
D.ROOT-SERVERS.NET. 3600000 A 199.7.91.13
|
||||
D.ROOT-SERVERS.NET. 3600000 AAAA 2001:500:2d::d
|
||||
;
|
||||
; FORMERLY NS.NASA.GOV
|
||||
;
|
||||
. 3600000 NS E.ROOT-SERVERS.NET.
|
||||
E.ROOT-SERVERS.NET. 3600000 A 192.203.230.10
|
||||
E.ROOT-SERVERS.NET. 3600000 AAAA 2001:500:a8::e
|
||||
;
|
||||
; FORMERLY NS.ISC.ORG
|
||||
;
|
||||
. 3600000 NS F.ROOT-SERVERS.NET.
|
||||
F.ROOT-SERVERS.NET. 3600000 A 192.5.5.241
|
||||
F.ROOT-SERVERS.NET. 3600000 AAAA 2001:500:2f::f
|
||||
;
|
||||
; FORMERLY NS.NIC.DDN.MIL
|
||||
;
|
||||
. 3600000 NS G.ROOT-SERVERS.NET.
|
||||
G.ROOT-SERVERS.NET. 3600000 A 192.112.36.4
|
||||
G.ROOT-SERVERS.NET. 3600000 AAAA 2001:500:12::d0d
|
||||
;
|
||||
; FORMERLY AOS.ARL.ARMY.MIL
|
||||
;
|
||||
. 3600000 NS H.ROOT-SERVERS.NET.
|
||||
H.ROOT-SERVERS.NET. 3600000 A 198.97.190.53
|
||||
H.ROOT-SERVERS.NET. 3600000 AAAA 2001:500:1::53
|
||||
;
|
||||
; FORMERLY NIC.NORDU.NET
|
||||
;
|
||||
. 3600000 NS I.ROOT-SERVERS.NET.
|
||||
I.ROOT-SERVERS.NET. 3600000 A 192.36.148.17
|
||||
I.ROOT-SERVERS.NET. 3600000 AAAA 2001:7fe::53
|
||||
;
|
||||
; OPERATED BY VERISIGN, INC.
|
||||
;
|
||||
. 3600000 NS J.ROOT-SERVERS.NET.
|
||||
J.ROOT-SERVERS.NET. 3600000 A 192.58.128.30
|
||||
J.ROOT-SERVERS.NET. 3600000 AAAA 2001:503:c27::2:30
|
||||
;
|
||||
; OPERATED BY RIPE NCC
|
||||
;
|
||||
. 3600000 NS K.ROOT-SERVERS.NET.
|
||||
K.ROOT-SERVERS.NET. 3600000 A 193.0.14.129
|
||||
K.ROOT-SERVERS.NET. 3600000 AAAA 2001:7fd::1
|
||||
;
|
||||
; OPERATED BY ICANN
|
||||
;
|
||||
. 3600000 NS L.ROOT-SERVERS.NET.
|
||||
L.ROOT-SERVERS.NET. 3600000 A 199.7.83.42
|
||||
L.ROOT-SERVERS.NET. 3600000 AAAA 2001:500:9f::42
|
||||
;
|
||||
; OPERATED BY WIDE
|
||||
;
|
||||
. 3600000 NS M.ROOT-SERVERS.NET.
|
||||
M.ROOT-SERVERS.NET. 3600000 A 202.12.27.33
|
||||
M.ROOT-SERVERS.NET. 3600000 AAAA 2001:dc3::35
|
||||
; End of file
|
@ -51,7 +51,12 @@ struct Opts {
|
||||
ty: RecordType,
|
||||
|
||||
/// Specify a nameserver to use, ip and port e.g. 8.8.8.8:53 or \[2001:4860:4860::8888\]:53 (port required)
|
||||
#[clap(short = 'n', long, require_value_delimiter = true)]
|
||||
#[clap(
|
||||
short = 'n',
|
||||
long,
|
||||
use_value_delimiter = true,
|
||||
require_value_delimiter = true
|
||||
)]
|
||||
nameserver: Vec<SocketAddr>,
|
||||
|
||||
/// Specify the IP address to connect from.
|
||||
@ -175,7 +180,7 @@ pub async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let name = &opts.domainname;
|
||||
let ty = opts.ty;
|
||||
|
||||
let recursor = Recursor::new(hints)?;
|
||||
let mut recursor = Recursor::new(hints)?;
|
||||
|
||||
// execute query
|
||||
println!(
|
||||
@ -189,12 +194,12 @@ pub async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
|
||||
// report response, TODO: better display of errors
|
||||
println!(
|
||||
"{} for query {}",
|
||||
"{} for query {:?}",
|
||||
style("Success").green(),
|
||||
style(lookup.query()).blue()
|
||||
style(lookup).blue()
|
||||
);
|
||||
|
||||
for r in lookup.record_iter() {
|
||||
for r in lookup.records_without_rrsigs() {
|
||||
print!(
|
||||
"\t{name} {ttl} {class} {ty}",
|
||||
name = style(r.name()).blue(),
|
||||
|
Loading…
Reference in New Issue
Block a user