recursor: define the bare minimum integration test
This commit is contained in:
parent
591a4a9fb2
commit
ec4e22817a
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -966,6 +966,7 @@ dependencies = [
|
||||||
"futures",
|
"futures",
|
||||||
"hickory-client",
|
"hickory-client",
|
||||||
"hickory-proto",
|
"hickory-proto",
|
||||||
|
"hickory-recursor",
|
||||||
"hickory-resolver",
|
"hickory-resolver",
|
||||||
"hickory-server",
|
"hickory-server",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
|
|
|
@ -28,7 +28,7 @@ use crate::{
|
||||||
dns_lru::{DnsLru, TtlConfig},
|
dns_lru::{DnsLru, TtlConfig},
|
||||||
error::ResolveError,
|
error::ResolveError,
|
||||||
lookup::Lookup,
|
lookup::Lookup,
|
||||||
name_server::{GenericNameServerPool, TokioRuntimeProvider},
|
name_server::{ConnectionProvider, GenericConnector, GenericNameServerPool, NameServerPool, TokioRuntimeProvider},
|
||||||
Name,
|
Name,
|
||||||
},
|
},
|
||||||
Error, ErrorKind,
|
Error, ErrorKind,
|
||||||
|
@ -40,13 +40,13 @@ type NameServerCache<P> = LruCache<Name, RecursorPool<P>>;
|
||||||
/// A top down recursive resolver which operates off a list of roots for initial recursive requests.
|
/// A top down recursive resolver which operates off a list of roots for initial recursive requests.
|
||||||
///
|
///
|
||||||
/// This is the well known root nodes, referred to as hints in RFCs. See the IANA [Root Servers](https://www.iana.org/domains/root/servers) list.
|
/// This is the well known root nodes, referred to as hints in RFCs. See the IANA [Root Servers](https://www.iana.org/domains/root/servers) list.
|
||||||
pub struct Recursor {
|
pub struct Recursor<P: ConnectionProvider> {
|
||||||
roots: RecursorPool<TokioRuntimeProvider>,
|
roots: RecursorPool<P>,
|
||||||
name_server_cache: Mutex<NameServerCache<TokioRuntimeProvider>>,
|
name_server_cache: Mutex<NameServerCache<P>>,
|
||||||
record_cache: DnsLru,
|
record_cache: DnsLru,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Recursor {
|
impl Recursor<GenericConnector<TokioRuntimeProvider>> {
|
||||||
/// Construct a new recursor using the list of NameServerConfigs for the root node list
|
/// Construct a new recursor using the list of NameServerConfigs for the root node list
|
||||||
///
|
///
|
||||||
/// # Panics
|
/// # Panics
|
||||||
|
@ -62,11 +62,24 @@ impl Recursor {
|
||||||
|
|
||||||
assert!(!roots.is_empty(), "roots must not be empty");
|
assert!(!roots.is_empty(), "roots must not be empty");
|
||||||
|
|
||||||
debug!("Using cache sizes {}/{}", ns_cache_size, record_cache_size);
|
|
||||||
let opts = recursor_opts();
|
let opts = recursor_opts();
|
||||||
let roots =
|
let roots =
|
||||||
GenericNameServerPool::from_config(roots, opts, TokioConnectionProvider::default());
|
GenericNameServerPool::from_config(roots, opts, TokioConnectionProvider::default());
|
||||||
|
|
||||||
|
Self::new_with_pool(roots, ns_cache_size, record_cache_size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<P: ConnectionProvider> Recursor<P> {
|
||||||
|
/// Construct a new recursor using a custom name server pool.
|
||||||
|
/// You likely want to use `new` instead.
|
||||||
|
pub fn new_with_pool(
|
||||||
|
roots: NameServerPool<P>,
|
||||||
|
ns_cache_size: usize,
|
||||||
|
record_cache_size: usize,
|
||||||
|
) -> Result<Self, ResolveError> {
|
||||||
let roots = RecursorPool::from(Name::root(), roots);
|
let roots = RecursorPool::from(Name::root(), roots);
|
||||||
|
debug!("Using cache sizes {}/{}", ns_cache_size, record_cache_size);
|
||||||
let name_server_cache = Mutex::new(NameServerCache::new(ns_cache_size));
|
let name_server_cache = Mutex::new(NameServerCache::new(ns_cache_size));
|
||||||
let record_cache = DnsLru::new(record_cache_size, TtlConfig::default());
|
let record_cache = DnsLru::new(record_cache_size, TtlConfig::default());
|
||||||
|
|
||||||
|
@ -76,7 +89,9 @@ impl Recursor {
|
||||||
record_cache,
|
record_cache,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<P: ConnectionProvider + Default> Recursor<P> {
|
||||||
/// Perform a recursive resolution
|
/// Perform a recursive resolution
|
||||||
///
|
///
|
||||||
/// [RFC 1034](https://datatracker.ietf.org/doc/html/rfc1034#section-5.3.3), Domain Concepts and Facilities, November 1987
|
/// [RFC 1034](https://datatracker.ietf.org/doc/html/rfc1034#section-5.3.3), Domain Concepts and Facilities, November 1987
|
||||||
|
@ -287,7 +302,7 @@ impl Recursor {
|
||||||
async fn lookup(
|
async fn lookup(
|
||||||
&self,
|
&self,
|
||||||
query: Query,
|
query: Query,
|
||||||
ns: RecursorPool<TokioRuntimeProvider>,
|
ns: RecursorPool<P>,
|
||||||
now: Instant,
|
now: Instant,
|
||||||
) -> Result<Lookup, Error> {
|
) -> Result<Lookup, Error> {
|
||||||
if let Some(lookup) = self.record_cache.get(&query, now) {
|
if let Some(lookup) = self.record_cache.get(&query, now) {
|
||||||
|
@ -337,7 +352,7 @@ impl Recursor {
|
||||||
&self,
|
&self,
|
||||||
zone: Name,
|
zone: Name,
|
||||||
request_time: Instant,
|
request_time: Instant,
|
||||||
) -> Result<RecursorPool<TokioRuntimeProvider>, Error> {
|
) -> Result<RecursorPool<P>, Error> {
|
||||||
// TODO: need to check TTLs here.
|
// TODO: need to check TTLs here.
|
||||||
if let Some(ns) = self.name_server_cache.lock().get_mut(&zone) {
|
if let Some(ns) = self.name_server_cache.lock().get_mut(&zone) {
|
||||||
return Ok(ns.clone());
|
return Ok(ns.clone());
|
||||||
|
@ -465,10 +480,10 @@ impl Recursor {
|
||||||
}
|
}
|
||||||
|
|
||||||
// now construct a namesever pool based off the NS and glue records
|
// now construct a namesever pool based off the NS and glue records
|
||||||
let ns = GenericNameServerPool::from_config(
|
let ns = NameServerPool::from_config(
|
||||||
config_group,
|
config_group,
|
||||||
recursor_opts(),
|
recursor_opts(),
|
||||||
TokioConnectionProvider::default(),
|
Default::default(),
|
||||||
);
|
);
|
||||||
let ns = RecursorPool::from(zone.clone(), ns);
|
let ns = RecursorPool::from(zone.clone(), ns);
|
||||||
|
|
||||||
|
|
|
@ -18,10 +18,9 @@ use hickory_proto::{
|
||||||
xfer::{DnsRequestOptions, DnsResponse},
|
xfer::{DnsRequestOptions, DnsResponse},
|
||||||
DnsHandle,
|
DnsHandle,
|
||||||
};
|
};
|
||||||
use hickory_resolver::name_server::{RuntimeProvider, TokioRuntimeProvider};
|
|
||||||
use hickory_resolver::{
|
use hickory_resolver::{
|
||||||
error::{ResolveError, ResolveErrorKind},
|
error::{ResolveError, ResolveErrorKind},
|
||||||
name_server::GenericNameServerPool,
|
name_server::{ConnectionProvider, NameServerPool},
|
||||||
Name,
|
Name,
|
||||||
};
|
};
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
|
@ -50,14 +49,14 @@ impl Future for SharedLookup {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub(crate) struct RecursorPool<P: RuntimeProvider + Send + 'static> {
|
pub(crate) struct RecursorPool<P: ConnectionProvider> {
|
||||||
zone: Name,
|
zone: Name,
|
||||||
ns: GenericNameServerPool<P>,
|
ns: NameServerPool<P>,
|
||||||
active_requests: Arc<Mutex<ActiveRequests>>,
|
active_requests: Arc<Mutex<ActiveRequests>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RecursorPool<TokioRuntimeProvider> {
|
impl<P: ConnectionProvider> RecursorPool<P> {
|
||||||
pub(crate) fn from(zone: Name, ns: GenericNameServerPool<TokioRuntimeProvider>) -> Self {
|
pub(crate) fn from(zone: Name, ns: NameServerPool<P>) -> Self {
|
||||||
let active_requests = Arc::new(Mutex::new(ActiveRequests::default()));
|
let active_requests = Arc::new(Mutex::new(ActiveRequests::default()));
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
|
@ -68,10 +67,7 @@ impl RecursorPool<TokioRuntimeProvider> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<P> RecursorPool<P>
|
impl<P: ConnectionProvider> RecursorPool<P> {
|
||||||
where
|
|
||||||
P: RuntimeProvider + Send + 'static,
|
|
||||||
{
|
|
||||||
pub(crate) fn zone(&self) -> &Name {
|
pub(crate) fn zone(&self) -> &Name {
|
||||||
&self.zone
|
&self.zone
|
||||||
}
|
}
|
||||||
|
|
|
@ -108,6 +108,7 @@ tokio = { workspace = true, features = ["time", "rt"] }
|
||||||
tracing.workspace = true
|
tracing.workspace = true
|
||||||
hickory-client.workspace = true
|
hickory-client.workspace = true
|
||||||
hickory-proto = { workspace = true, features = ["testing"] }
|
hickory-proto = { workspace = true, features = ["testing"] }
|
||||||
|
hickory-recursor = { workspace = true }
|
||||||
hickory-resolver = { workspace = true, features = ["tokio-runtime"] }
|
hickory-resolver = { workspace = true, features = ["tokio-runtime"] }
|
||||||
hickory-server = { workspace = true, features = ["testing"] }
|
hickory-server = { workspace = true, features = ["testing"] }
|
||||||
webpki-roots = { workspace = true, optional = true }
|
webpki-roots = { workspace = true, optional = true }
|
||||||
|
|
|
@ -14,7 +14,7 @@ use futures::stream::{once, Stream};
|
||||||
use futures::{future, AsyncRead, AsyncWrite, Future};
|
use futures::{future, AsyncRead, AsyncWrite, Future};
|
||||||
|
|
||||||
use hickory_client::op::{Message, Query};
|
use hickory_client::op::{Message, Query};
|
||||||
use hickory_client::rr::rdata::{CNAME, SOA};
|
use hickory_client::rr::rdata::{CNAME, NS, SOA};
|
||||||
use hickory_client::rr::{Name, RData, Record};
|
use hickory_client::rr::{Name, RData, Record};
|
||||||
use hickory_proto::error::ProtoError;
|
use hickory_proto::error::ProtoError;
|
||||||
use hickory_proto::tcp::DnsTcpStream;
|
use hickory_proto::tcp::DnsTcpStream;
|
||||||
|
@ -127,7 +127,7 @@ impl RuntimeProvider for MockRuntimeProvider {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone, Default)]
|
||||||
pub struct MockConnProvider<O: OnSend + Unpin> {
|
pub struct MockConnProvider<O: OnSend + Unpin> {
|
||||||
pub on_send: O,
|
pub on_send: O,
|
||||||
}
|
}
|
||||||
|
@ -211,6 +211,10 @@ pub fn soa_record(name: Name, mname: Name) -> Record {
|
||||||
Record::from_rdata(name, 86400, RData::SOA(soa))
|
Record::from_rdata(name, 86400, RData::SOA(soa))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn ns_record(name: Name, ns: Name) -> Record {
|
||||||
|
Record::from_rdata(name, 86400, RData::NS(NS(ns)))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn message(
|
pub fn message(
|
||||||
query: Query,
|
query: Query,
|
||||||
answers: Vec<Record>,
|
answers: Vec<Record>,
|
||||||
|
@ -245,7 +249,7 @@ pub trait OnSend: Clone + Send + Sync + 'static {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone, Default)]
|
||||||
pub struct DefaultOnSend;
|
pub struct DefaultOnSend;
|
||||||
|
|
||||||
impl OnSend for DefaultOnSend {}
|
impl OnSend for DefaultOnSend {}
|
||||||
|
|
|
@ -8,6 +8,50 @@
|
||||||
//! Integration tests for the recursor. These integration tests setup scenarios to verify that the recursor is able
|
//! Integration tests for the recursor. These integration tests setup scenarios to verify that the recursor is able
|
||||||
//! to recursively resolve various real world scenarios. As new scenarios are discovered, they should be added here.
|
//! to recursively resolve various real world scenarios. As new scenarios are discovered, they should be added here.
|
||||||
|
|
||||||
|
use std::net::*;
|
||||||
|
use std::str::FromStr as _;
|
||||||
|
use std::time::Instant;
|
||||||
|
use futures::executor::block_on;
|
||||||
|
|
||||||
|
use hickory_client::op::Query;
|
||||||
|
use hickory_proto::error::ProtoError;
|
||||||
|
use hickory_proto::xfer::DnsResponse;
|
||||||
|
use hickory_integration::mock_client::*;
|
||||||
|
use hickory_recursor::Recursor;
|
||||||
|
use hickory_resolver::name_server::{NameServer, NameServerPool};
|
||||||
|
use hickory_resolver::config::*;
|
||||||
|
use hickory_client::rr::{Name, RecordType};
|
||||||
|
|
||||||
|
|
||||||
|
const DEFAULT_SERVER_ADDR: IpAddr = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1));
|
||||||
|
|
||||||
|
type MockedNameServer<O> = NameServer<MockConnProvider<O>>;
|
||||||
|
|
||||||
|
|
||||||
|
fn mock_nameserver(
|
||||||
|
messages: Vec<Result<DnsResponse, ProtoError>>,
|
||||||
|
) -> MockedNameServer<DefaultOnSend> {
|
||||||
|
let conn_provider = MockConnProvider {
|
||||||
|
on_send: DefaultOnSend,
|
||||||
|
};
|
||||||
|
let client = MockClientHandle::mock_on_send(messages, DefaultOnSend);
|
||||||
|
|
||||||
|
NameServer::from_conn(
|
||||||
|
NameServerConfig {
|
||||||
|
socket_addr: SocketAddr::new(DEFAULT_SERVER_ADDR, 0),
|
||||||
|
protocol: Protocol::Udp,
|
||||||
|
tls_dns_name: None,
|
||||||
|
trust_negative_responses: false,
|
||||||
|
#[cfg(any(feature = "dns-over-rustls", feature = "dns-over-https-rustls"))]
|
||||||
|
tls_config: None,
|
||||||
|
bind_addr: None,
|
||||||
|
},
|
||||||
|
Default::default(),
|
||||||
|
client,
|
||||||
|
conn_provider,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/// Tests a basic recursive resolution `a.recursive.test.` , `.` -> `test.` -> `recursive.test.` -> `a.recursive.test.`
|
/// Tests a basic recursive resolution `a.recursive.test.` , `.` -> `test.` -> `recursive.test.` -> `a.recursive.test.`
|
||||||
///
|
///
|
||||||
/// There are three authorities needed for this test `.` which contains the `test` nameserver, `recursive.test` which is
|
/// There are three authorities needed for this test `.` which contains the `test` nameserver, `recursive.test` which is
|
||||||
|
@ -16,3 +60,26 @@
|
||||||
fn test_basic_recursion() {
|
fn test_basic_recursion() {
|
||||||
// TBD
|
// TBD
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Query the root for its nameserver. This is a single DNS request, no recursion necessary.
|
||||||
|
#[test]
|
||||||
|
fn test_root_ns() {
|
||||||
|
let ns_query = Query::query(Name::from_str(".").unwrap(), RecordType::NS);
|
||||||
|
let ns_record = ns_record(Name::from_str(".").unwrap(), Name::from_str("a.root-servers.net.").unwrap());
|
||||||
|
let ns_message = message(ns_query.clone(), vec![ns_record.clone()], vec![], vec![]);
|
||||||
|
let nameserver = mock_nameserver(
|
||||||
|
vec![Ok(DnsResponse::from_message(ns_message).unwrap())],
|
||||||
|
);
|
||||||
|
|
||||||
|
let roots = NameServerPool::from_nameservers(
|
||||||
|
Default::default(),
|
||||||
|
vec![nameserver],
|
||||||
|
vec![],
|
||||||
|
);
|
||||||
|
let recursor = Recursor::new_with_pool(roots, 1024, 1048576).unwrap();
|
||||||
|
|
||||||
|
let now = Instant::now();
|
||||||
|
let lookup = block_on(recursor.resolve(ns_query, now)).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(lookup.records()[0], ns_record);
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user