diff --git a/Cargo.lock b/Cargo.lock
index 1ff1b9c7..1eaa6be0 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -966,6 +966,7 @@ dependencies = [
"futures",
"hickory-client",
"hickory-proto",
+ "hickory-recursor",
"hickory-resolver",
"hickory-server",
"once_cell",
diff --git a/crates/recursor/src/recursor.rs b/crates/recursor/src/recursor.rs
index ff91e166..4fcb5628 100644
--- a/crates/recursor/src/recursor.rs
+++ b/crates/recursor/src/recursor.rs
@@ -28,7 +28,7 @@ use crate::{
dns_lru::{DnsLru, TtlConfig},
error::ResolveError,
lookup::Lookup,
- name_server::{GenericNameServerPool, TokioRuntimeProvider},
+ name_server::{ConnectionProvider, GenericConnector, GenericNameServerPool, NameServerPool, TokioRuntimeProvider},
Name,
},
Error, ErrorKind,
@@ -40,13 +40,13 @@ type NameServerCache
= LruCache>;
/// 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.
-pub struct Recursor {
- roots: RecursorPool,
- name_server_cache: Mutex>,
+pub struct Recursor {
+ roots: RecursorPool,
+ name_server_cache: Mutex>,
record_cache: DnsLru,
}
-impl Recursor {
+impl Recursor> {
/// Construct a new recursor using the list of NameServerConfigs for the root node list
///
/// # Panics
@@ -62,11 +62,24 @@ impl Recursor {
assert!(!roots.is_empty(), "roots must not be empty");
- debug!("Using cache sizes {}/{}", ns_cache_size, record_cache_size);
let opts = recursor_opts();
let roots =
GenericNameServerPool::from_config(roots, opts, TokioConnectionProvider::default());
+
+ Self::new_with_pool(roots, ns_cache_size, record_cache_size)
+ }
+}
+
+impl Recursor {
+ /// Construct a new recursor using a custom name server pool.
+ /// You likely want to use `new` instead.
+ pub fn new_with_pool(
+ roots: NameServerPool
,
+ ns_cache_size: usize,
+ record_cache_size: usize,
+ ) -> Result {
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 record_cache = DnsLru::new(record_cache_size, TtlConfig::default());
@@ -76,7 +89,9 @@ impl Recursor {
record_cache,
})
}
+}
+impl Recursor {
/// Perform a recursive resolution
///
/// [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(
&self,
query: Query,
- ns: RecursorPool,
+ ns: RecursorPool,
now: Instant,
) -> Result {
if let Some(lookup) = self.record_cache.get(&query, now) {
@@ -337,7 +352,7 @@ impl Recursor {
&self,
zone: Name,
request_time: Instant,
- ) -> Result, Error> {
+ ) -> Result, Error> {
// TODO: need to check TTLs here.
if let Some(ns) = self.name_server_cache.lock().get_mut(&zone) {
return Ok(ns.clone());
@@ -465,10 +480,10 @@ impl Recursor {
}
// now construct a namesever pool based off the NS and glue records
- let ns = GenericNameServerPool::from_config(
+ let ns = NameServerPool::from_config(
config_group,
recursor_opts(),
- TokioConnectionProvider::default(),
+ Default::default(),
);
let ns = RecursorPool::from(zone.clone(), ns);
diff --git a/crates/recursor/src/recursor_pool.rs b/crates/recursor/src/recursor_pool.rs
index 2611324c..9b133525 100644
--- a/crates/recursor/src/recursor_pool.rs
+++ b/crates/recursor/src/recursor_pool.rs
@@ -18,10 +18,9 @@ use hickory_proto::{
xfer::{DnsRequestOptions, DnsResponse},
DnsHandle,
};
-use hickory_resolver::name_server::{RuntimeProvider, TokioRuntimeProvider};
use hickory_resolver::{
error::{ResolveError, ResolveErrorKind},
- name_server::GenericNameServerPool,
+ name_server::{ConnectionProvider, NameServerPool},
Name,
};
use parking_lot::Mutex;
@@ -50,14 +49,14 @@ impl Future for SharedLookup {
}
#[derive(Clone)]
-pub(crate) struct RecursorPool {
+pub(crate) struct RecursorPool {
zone: Name,
- ns: GenericNameServerPool,
+ ns: NameServerPool
,
active_requests: Arc>,
}
-impl RecursorPool {
- pub(crate) fn from(zone: Name, ns: GenericNameServerPool) -> Self {
+impl RecursorPool {
+ pub(crate) fn from(zone: Name, ns: NameServerPool
) -> Self {
let active_requests = Arc::new(Mutex::new(ActiveRequests::default()));
Self {
@@ -68,10 +67,7 @@ impl RecursorPool {
}
}
-impl RecursorPool
-where
- P: RuntimeProvider + Send + 'static,
-{
+impl RecursorPool {
pub(crate) fn zone(&self) -> &Name {
&self.zone
}
diff --git a/tests/integration-tests/Cargo.toml b/tests/integration-tests/Cargo.toml
index 4945932b..dea8fe61 100644
--- a/tests/integration-tests/Cargo.toml
+++ b/tests/integration-tests/Cargo.toml
@@ -108,6 +108,7 @@ tokio = { workspace = true, features = ["time", "rt"] }
tracing.workspace = true
hickory-client.workspace = true
hickory-proto = { workspace = true, features = ["testing"] }
+hickory-recursor = { workspace = true }
hickory-resolver = { workspace = true, features = ["tokio-runtime"] }
hickory-server = { workspace = true, features = ["testing"] }
webpki-roots = { workspace = true, optional = true }
diff --git a/tests/integration-tests/src/mock_client.rs b/tests/integration-tests/src/mock_client.rs
index 8f288d6f..0400318b 100644
--- a/tests/integration-tests/src/mock_client.rs
+++ b/tests/integration-tests/src/mock_client.rs
@@ -14,7 +14,7 @@ use futures::stream::{once, Stream};
use futures::{future, AsyncRead, AsyncWrite, Future};
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_proto::error::ProtoError;
use hickory_proto::tcp::DnsTcpStream;
@@ -127,7 +127,7 @@ impl RuntimeProvider for MockRuntimeProvider {
}
}
-#[derive(Clone)]
+#[derive(Clone, Default)]
pub struct MockConnProvider {
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))
}
+pub fn ns_record(name: Name, ns: Name) -> Record {
+ Record::from_rdata(name, 86400, RData::NS(NS(ns)))
+}
+
pub fn message(
query: Query,
answers: Vec,
@@ -245,7 +249,7 @@ pub trait OnSend: Clone + Send + Sync + 'static {
}
}
-#[derive(Clone)]
+#[derive(Clone, Default)]
pub struct DefaultOnSend;
impl OnSend for DefaultOnSend {}
diff --git a/tests/integration-tests/tests/recursor_tests.rs b/tests/integration-tests/tests/recursor_tests.rs
index 4f135237..71c8ba0d 100644
--- a/tests/integration-tests/tests/recursor_tests.rs
+++ b/tests/integration-tests/tests/recursor_tests.rs
@@ -8,6 +8,50 @@
//! 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.
+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 = NameServer>;
+
+
+fn mock_nameserver(
+ messages: Vec>,
+) -> MockedNameServer {
+ 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.`
///
/// 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() {
// 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);
+}