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.
|
||||
///
|
||||
/// 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>,
|
||||
name_server_cache: Mutex<NameServerCache<P>>,
|
||||
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> {
|
||||
/// 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> {
|
||||
/// 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
|
||||
///
|
||||
/// [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
|
||||
//! 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::pin::Pin;
|
||||
use std::str::FromStr as _;
|
||||
use std::time::Instant;
|
||||
|
||||
use futures::stream::{self, Stream};
|
||||
use futures::future;
|
||||
use futures::StreamExt as _;
|
||||
use futures::executor::block_on;
|
||||
|
||||
use hickory_client::op::Query;
|
||||
use hickory_proto::error::ProtoError;
|
||||
use hickory_proto::xfer::DnsResponse;
|
||||
use hickory_client::op::{Message, MessageType, Query};
|
||||
use hickory_client::rr::{Name, Record, RecordType};
|
||||
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_resolver::name_server::{NameServer, NameServerPool};
|
||||
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));
|
||||
|
||||
type MockedNameServer<O> = NameServer<MockConnProvider<O>>;
|
||||
const NS_FAKEA: IpAddr = IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1));
|
||||
const NS_FAKEB: IpAddr = IpAddr::V4(Ipv4Addr::new(10, 0, 0, 2));
|
||||
|
||||
|
||||
fn mock_nameserver(
|
||||
messages: Vec<Result<DnsResponse, ProtoError>>,
|
||||
) -> MockedNameServer<DefaultOnSend> {
|
||||
let conn_provider = MockConnProvider {
|
||||
on_send: DefaultOnSend,
|
||||
const ZONE_FAKEA: &str = r#"
|
||||
. IN SOA fakea action\.domains (
|
||||
20 ; SERIAL
|
||||
7200 ; REFRESH
|
||||
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 {
|
||||
socket_addr: SocketAddr::new(DEFAULT_SERVER_ADDR, 0),
|
||||
socket_addr: SocketAddr::new(addr, 53),
|
||||
protocol: Protocol::Udp,
|
||||
tls_dns_name: None,
|
||||
trust_negative_responses: false,
|
||||
|
@ -46,9 +184,8 @@ fn mock_nameserver(
|
|||
tls_config: None,
|
||||
bind_addr: None,
|
||||
},
|
||||
Default::default(),
|
||||
client,
|
||||
conn_provider,
|
||||
ResolverOpts::default(),
|
||||
HardcodedConnProvider,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -64,16 +201,16 @@ fn test_basic_recursion() {
|
|||
/// Query the root for its nameserver. This is a single DNS request, no recursion necessary.
|
||||
#[test]
|
||||
fn test_root_ns() {
|
||||
logger("DEBUG");
|
||||
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![
|
||||
mock_nameserver(NS_FAKEA),
|
||||
// mock_nameserver(NS_FAKEB),
|
||||
],
|
||||
vec![],
|
||||
);
|
||||
let recursor = Recursor::new_with_pool(roots, 1024, 1048576).unwrap();
|
||||
|
@ -81,5 +218,44 @@ fn test_root_ns() {
|
|||
let now = Instant::now();
|
||||
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 ty = opts.ty;
|
||||
|
||||
let recursor = Recursor::new(roots, 1024, 1048576)?;
|
||||
let recursor: Recursor = Recursor::new(roots, 1024, 1048576)?;
|
||||
|
||||
// execute query
|
||||
println!(
|
||||
|
|
Loading…
Reference in New Issue
Block a user