recursor: make the test helpers more capable
they did not previously allow any way to mock DNS query sequences in a manner compatible with the RecursorPool, which prefers to create new NameServers itself, rather than via anything injectable by the test.
This commit is contained in:
parent
ec4e22817a
commit
80f2a17bff
|
@ -40,35 +40,12 @@ 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<P: ConnectionProvider> {
|
pub struct Recursor<P: ConnectionProvider=TokioConnectionProvider> {
|
||||||
roots: RecursorPool<P>,
|
roots: RecursorPool<P>,
|
||||||
name_server_cache: Mutex<NameServerCache<P>>,
|
name_server_cache: Mutex<NameServerCache<P>>,
|
||||||
record_cache: DnsLru,
|
record_cache: DnsLru,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Recursor<GenericConnector<TokioRuntimeProvider>> {
|
|
||||||
/// Construct a new recursor using the list of NameServerConfigs for the root node list
|
|
||||||
///
|
|
||||||
/// # Panics
|
|
||||||
///
|
|
||||||
/// This will panic if the roots are empty.
|
|
||||||
pub fn new(
|
|
||||||
roots: impl Into<NameServerConfigGroup>,
|
|
||||||
ns_cache_size: usize,
|
|
||||||
record_cache_size: usize,
|
|
||||||
) -> Result<Self, ResolveError> {
|
|
||||||
// configure the hickory-resolver
|
|
||||||
let roots: NameServerConfigGroup = roots.into();
|
|
||||||
|
|
||||||
assert!(!roots.is_empty(), "roots must not be empty");
|
|
||||||
|
|
||||||
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<P: ConnectionProvider> Recursor<P> {
|
impl<P: ConnectionProvider> Recursor<P> {
|
||||||
/// Construct a new recursor using a custom name server pool.
|
/// Construct a new recursor using a custom name server pool.
|
||||||
|
@ -92,6 +69,27 @@ impl<P: ConnectionProvider> Recursor<P> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<P: ConnectionProvider + Default> Recursor<P> {
|
impl<P: ConnectionProvider + Default> Recursor<P> {
|
||||||
|
/// Construct a new recursor using the list of NameServerConfigs for the root node list
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// This will panic if the roots are empty.
|
||||||
|
pub fn new(
|
||||||
|
roots: impl Into<NameServerConfigGroup>,
|
||||||
|
ns_cache_size: usize,
|
||||||
|
record_cache_size: usize,
|
||||||
|
) -> Result<Self, ResolveError> {
|
||||||
|
// configure the hickory-resolver
|
||||||
|
let roots: NameServerConfigGroup = roots.into();
|
||||||
|
|
||||||
|
assert!(!roots.is_empty(), "roots must not be empty");
|
||||||
|
|
||||||
|
let opts = recursor_opts();
|
||||||
|
let roots = NameServerPool::from_config(roots, opts, P::default());
|
||||||
|
|
||||||
|
Self::new_with_pool(roots, ns_cache_size, record_cache_size)
|
||||||
|
}
|
||||||
|
|
||||||
/// 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
|
||||||
|
|
|
@ -8,37 +8,175 @@
|
||||||
//! 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::future::Future;
|
||||||
use std::net::*;
|
use std::net::*;
|
||||||
|
use std::pin::Pin;
|
||||||
use std::str::FromStr as _;
|
use std::str::FromStr as _;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
|
||||||
|
use futures::stream::{self, Stream};
|
||||||
|
use futures::future;
|
||||||
|
use futures::StreamExt as _;
|
||||||
use futures::executor::block_on;
|
use futures::executor::block_on;
|
||||||
|
|
||||||
use hickory_client::op::Query;
|
use hickory_client::op::{Message, MessageType, Query};
|
||||||
use hickory_proto::error::ProtoError;
|
use hickory_client::rr::{Name, Record, RecordType};
|
||||||
use hickory_proto::xfer::DnsResponse;
|
|
||||||
use hickory_integration::mock_client::*;
|
use hickory_integration::mock_client::*;
|
||||||
|
use hickory_proto::DnsHandle;
|
||||||
|
use hickory_proto::error::ProtoError;
|
||||||
|
use hickory_proto::rr::LowerName;
|
||||||
|
use hickory_proto::serialize::txt::Parser;
|
||||||
|
use hickory_proto::xfer::{DnsRequest, DnsResponse};
|
||||||
use hickory_recursor::Recursor;
|
use hickory_recursor::Recursor;
|
||||||
use hickory_resolver::name_server::{NameServer, NameServerPool};
|
|
||||||
use hickory_resolver::config::*;
|
use hickory_resolver::config::*;
|
||||||
use hickory_client::rr::{Name, RecordType};
|
use hickory_resolver::name_server::ConnectionProvider;
|
||||||
|
use hickory_resolver::name_server::{NameServer, NameServerPool};
|
||||||
|
use hickory_server::authority::{Authority, LookupOptions, ZoneType};
|
||||||
|
use hickory_server::store::in_memory::InMemoryAuthority;
|
||||||
|
|
||||||
|
|
||||||
const DEFAULT_SERVER_ADDR: IpAddr = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1));
|
const NS_FAKEA: IpAddr = IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1));
|
||||||
|
const NS_FAKEB: IpAddr = IpAddr::V4(Ipv4Addr::new(10, 0, 0, 2));
|
||||||
type MockedNameServer<O> = NameServer<MockConnProvider<O>>;
|
|
||||||
|
|
||||||
|
|
||||||
fn mock_nameserver(
|
const ZONE_FAKEA: &str = r#"
|
||||||
messages: Vec<Result<DnsResponse, ProtoError>>,
|
. IN SOA fakea action\.domains (
|
||||||
) -> MockedNameServer<DefaultOnSend> {
|
20 ; SERIAL
|
||||||
let conn_provider = MockConnProvider {
|
7200 ; REFRESH
|
||||||
on_send: DefaultOnSend,
|
600 ; RETRY
|
||||||
|
3600000; EXPIRE
|
||||||
|
60) ; MINIMUM
|
||||||
|
|
||||||
|
. NS a.root-servers.net
|
||||||
|
a.root-servers.net A 10.0.0.2
|
||||||
|
"#;
|
||||||
|
|
||||||
|
const ZONE_FAKEB: &str = r#"
|
||||||
|
fakeb. IN SOA fakeb action\.domains (
|
||||||
|
20 ; SERIAL
|
||||||
|
7200 ; REFRESH
|
||||||
|
600 ; RETRY
|
||||||
|
3600000; EXPIRE
|
||||||
|
60) ; MINIMUM
|
||||||
|
|
||||||
|
. NS fakea.
|
||||||
|
"#;
|
||||||
|
// net. NS b.gtld-servers.net.
|
||||||
|
// gtld-servers.net NS av1.nstld.com.
|
||||||
|
// com. NS b.gtld-servers.net.
|
||||||
|
// a.root-servers.net. A 1.2.3.4
|
||||||
|
|
||||||
|
// type MockedNameServer<O> = NameServer<HardcodedConnProvider>;
|
||||||
|
type HardcodedNameServer = NameServer<HardcodedConnProvider>;
|
||||||
|
|
||||||
|
/// A ConnectionProvider which includes hard-coded DNS test data shared across tests.
|
||||||
|
#[derive(Clone, Default)]
|
||||||
|
struct HardcodedConnProvider;
|
||||||
|
impl ConnectionProvider for HardcodedConnProvider {
|
||||||
|
type Conn = HardcodedDnsHandle;
|
||||||
|
type FutureConn = Pin<Box<dyn Send + Future<Output = Result<Self::Conn, ProtoError>>>>;
|
||||||
|
type RuntimeProvider = MockRuntimeProvider;
|
||||||
|
|
||||||
|
fn new_connection(
|
||||||
|
&self,
|
||||||
|
config: &NameServerConfig,
|
||||||
|
_options: &ResolverOpts,
|
||||||
|
) -> Self::FutureConn {
|
||||||
|
println!("HardcodedConnProvider::new_connection");
|
||||||
|
Box::pin(future::ok(HardcodedDnsHandle(config.socket_addr)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
struct HardcodedDnsHandle(SocketAddr);
|
||||||
|
|
||||||
|
impl DnsHandle for HardcodedDnsHandle {
|
||||||
|
type Response = Pin<Box<dyn Stream<Item = Result<DnsResponse, ProtoError>> + Send>>;
|
||||||
|
|
||||||
|
fn send<R: Into<DnsRequest>>(&self, request: R) -> Self::Response {
|
||||||
|
let addr = self.0;
|
||||||
|
let request = request.into();
|
||||||
|
// let queries: Vec<_> = request.queries().iter().collect();
|
||||||
|
println!("HardcodedDnsHandle::send request: {:?}", request.queries());
|
||||||
|
let response_futures: Vec<_> = request.queries().iter().map(move |query| {
|
||||||
|
let query = query.clone();
|
||||||
|
stream::once(async move {
|
||||||
|
println!("HardcodedDnsHandle: fielding query");
|
||||||
|
let authority = make_authority_for(addr);
|
||||||
|
let mut lookup = authority.lookup(
|
||||||
|
&LowerName::new(query.name()),
|
||||||
|
query.query_type(),
|
||||||
|
LookupOptions::default(),
|
||||||
|
).await.unwrap();
|
||||||
|
println!("HardcodeDnsHandle: result {:?}", lookup);
|
||||||
|
let mut response = Message::new();
|
||||||
|
response.add_query(query);
|
||||||
|
response.set_message_type(MessageType::Response);
|
||||||
|
response.insert_answers(lookup.iter().cloned().collect());
|
||||||
|
if let Some(additionals) = lookup.take_additionals() {
|
||||||
|
response.insert_additionals(additionals.iter().cloned().collect());
|
||||||
|
}
|
||||||
|
let resp = DnsResponse::from_message(response).unwrap();
|
||||||
|
Ok(resp)
|
||||||
|
})
|
||||||
|
}).collect();
|
||||||
|
|
||||||
|
Box::pin(stream::iter(response_futures).flatten())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_authority_for(nameserver: SocketAddr) -> InMemoryAuthority {
|
||||||
|
println!("retrieving authority...");
|
||||||
|
let (zone_text, name) = match nameserver {
|
||||||
|
s if s == SocketAddr::new(NS_FAKEA, 53) => {
|
||||||
|
(ZONE_FAKEA, ".")
|
||||||
|
},
|
||||||
|
s if s == SocketAddr::new(NS_FAKEB, 53) => {
|
||||||
|
(ZONE_FAKEB, "fakeb.")
|
||||||
|
},
|
||||||
|
ns => panic!("unexpected nameserver {:?}", ns),
|
||||||
};
|
};
|
||||||
let client = MockClientHandle::mock_on_send(messages, DefaultOnSend);
|
let (origin, records) = Parser::new(zone_text, None, Some(Name::from_str(name).unwrap())).parse().unwrap();
|
||||||
|
InMemoryAuthority::new(origin, records, ZoneType::Primary, false /* allow_axfr */).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
NameServer::from_conn(
|
pub fn logger(level: &str) {
|
||||||
|
use tracing_subscriber::layer::SubscriberExt;
|
||||||
|
use tracing_subscriber::util::SubscriberInitExt;
|
||||||
|
// Setup tracing for logging based on input
|
||||||
|
let subscriber = tracing_subscriber::EnvFilter::builder()
|
||||||
|
.with_default_directive(tracing::metadata::LevelFilter::OFF.into())
|
||||||
|
.parse(level)
|
||||||
|
.expect("failed to configure tracing/logging");
|
||||||
|
|
||||||
|
let formatter = tracing_subscriber::fmt::layer().compact();
|
||||||
|
|
||||||
|
tracing_subscriber::registry()
|
||||||
|
.with(formatter)
|
||||||
|
.with(subscriber)
|
||||||
|
.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn response(
|
||||||
|
query: Query,
|
||||||
|
answers: Vec<Record>,
|
||||||
|
name_servers: Vec<Record>,
|
||||||
|
additionals: Vec<Record>,
|
||||||
|
) -> Message {
|
||||||
|
let mut message = Message::new();
|
||||||
|
message.add_query(query);
|
||||||
|
message.set_message_type(MessageType::Response);
|
||||||
|
message.insert_answers(answers);
|
||||||
|
message.insert_name_servers(name_servers);
|
||||||
|
message.insert_additionals(additionals);
|
||||||
|
message
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn mock_nameserver(addr: IpAddr) -> HardcodedNameServer {
|
||||||
|
NameServer::new(
|
||||||
NameServerConfig {
|
NameServerConfig {
|
||||||
socket_addr: SocketAddr::new(DEFAULT_SERVER_ADDR, 0),
|
socket_addr: SocketAddr::new(addr, 53),
|
||||||
protocol: Protocol::Udp,
|
protocol: Protocol::Udp,
|
||||||
tls_dns_name: None,
|
tls_dns_name: None,
|
||||||
trust_negative_responses: false,
|
trust_negative_responses: false,
|
||||||
|
@ -46,9 +184,8 @@ fn mock_nameserver(
|
||||||
tls_config: None,
|
tls_config: None,
|
||||||
bind_addr: None,
|
bind_addr: None,
|
||||||
},
|
},
|
||||||
Default::default(),
|
ResolverOpts::default(),
|
||||||
client,
|
HardcodedConnProvider,
|
||||||
conn_provider,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,16 +201,16 @@ fn test_basic_recursion() {
|
||||||
/// Query the root for its nameserver. This is a single DNS request, no recursion necessary.
|
/// Query the root for its nameserver. This is a single DNS request, no recursion necessary.
|
||||||
#[test]
|
#[test]
|
||||||
fn test_root_ns() {
|
fn test_root_ns() {
|
||||||
|
logger("DEBUG");
|
||||||
let ns_query = Query::query(Name::from_str(".").unwrap(), RecordType::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_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(
|
let roots = NameServerPool::from_nameservers(
|
||||||
Default::default(),
|
Default::default(),
|
||||||
vec![nameserver],
|
vec![
|
||||||
|
mock_nameserver(NS_FAKEA),
|
||||||
|
// mock_nameserver(NS_FAKEB),
|
||||||
|
],
|
||||||
vec![],
|
vec![],
|
||||||
);
|
);
|
||||||
let recursor = Recursor::new_with_pool(roots, 1024, 1048576).unwrap();
|
let recursor = Recursor::new_with_pool(roots, 1024, 1048576).unwrap();
|
||||||
|
@ -81,5 +218,44 @@ fn test_root_ns() {
|
||||||
let now = Instant::now();
|
let now = Instant::now();
|
||||||
let lookup = block_on(recursor.resolve(ns_query, now)).unwrap();
|
let lookup = block_on(recursor.resolve(ns_query, now)).unwrap();
|
||||||
|
|
||||||
assert_eq!(lookup.records()[0], ns_record);
|
assert_eq!(&*lookup.records().to_vec(), &[ns_record]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// /// Query a top-level domain for its nameserver. `.` (NS) -> `com.` (NS)
|
||||||
|
// #[test]
|
||||||
|
// fn test_tld_ns() {
|
||||||
|
// logger("DEBUG");
|
||||||
|
//
|
||||||
|
// let root_ns_query = Query::query(Name::from_str(".").unwrap(), RecordType::NS);
|
||||||
|
// let root_ns_record = ns_record(Name::from_str(".").unwrap(), Name::from_str("a.root-servers.net.").unwrap());
|
||||||
|
// let root_ns_glue = v4_record(Name::from_str("a.root-servers.net.").unwrap(), Ipv4Addr::new(127, 0, 0, 2));
|
||||||
|
// let root_ns_message = response(root_ns_query.clone(), vec![root_ns_record.clone()], vec![], vec![root_ns_glue]);
|
||||||
|
//
|
||||||
|
// let com_ns_query = Query::query(Name::from_str("com.").unwrap(), RecordType::NS);
|
||||||
|
// let com_ns_record = ns_record(Name::from_str("com.").unwrap(), Name::from_str("b.gtld-servers.net.").unwrap());
|
||||||
|
// let com_ns_message = response(com_ns_query.clone(), vec![com_ns_record.clone()], vec![], vec![]);
|
||||||
|
// let root_nameserver = mock_nameserver(
|
||||||
|
// Ipv4Addr::new(127, 0, 0, 1),
|
||||||
|
// vec![
|
||||||
|
// Ok(DnsResponse::from_message(root_ns_message).unwrap()),
|
||||||
|
// ],
|
||||||
|
// );
|
||||||
|
// let com_nameserver = mock_nameserver(
|
||||||
|
// Ipv4Addr::new(127, 0, 0, 2),
|
||||||
|
// vec![
|
||||||
|
// Ok(DnsResponse::from_message(com_ns_message).unwrap()),
|
||||||
|
// ],
|
||||||
|
// );
|
||||||
|
//
|
||||||
|
// let roots = NameServerPool::from_nameservers(
|
||||||
|
// Default::default(),
|
||||||
|
// vec![mock_nameserver()],
|
||||||
|
// vec![],
|
||||||
|
// );
|
||||||
|
// let recursor = Recursor::new(roots, 1024, 1048576).unwrap();
|
||||||
|
//
|
||||||
|
// let now = Instant::now();
|
||||||
|
// let lookup = block_on(recursor.resolve(com_ns_query, now)).unwrap();
|
||||||
|
//
|
||||||
|
// assert_eq!(&*lookup.records().to_vec(), &[com_ns_record]);
|
||||||
|
// }
|
||||||
|
|
|
@ -157,7 +157,7 @@ pub async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let name = opts.domainname;
|
let name = opts.domainname;
|
||||||
let ty = opts.ty;
|
let ty = opts.ty;
|
||||||
|
|
||||||
let recursor = Recursor::new(roots, 1024, 1048576)?;
|
let recursor: Recursor = Recursor::new(roots, 1024, 1048576)?;
|
||||||
|
|
||||||
// execute query
|
// execute query
|
||||||
println!(
|
println!(
|
||||||
|
|
Loading…
Reference in New Issue
Block a user