Files
hickory-dns/bin/tests/server_harness/mod.rs
Benjamin Fry 408d0baca0 Prepare 0.24 release with branding change to Hickory DNS (#2054)
* update all READMEs with notices about the name change

* update changelog for 0.24

* bump crate versions to 0.24

* update version notice information

* update readmes to back reference trust-dns

* rename all crates to hickory counterparts

* replace all Trust-DNS references in code and comments with Hickory DNS

* rename all Trust-DNS references to Hickory DNS in non-code

* rename all trust-dns-resolver references to hickory-resolver

* rename all trust-dns-client references to hickory-client

* rename all trust-dns-proto references to hickory-proto

* rename all trust-dns-server references to hickory-server

* rename all trust-dns-compatibility references to hickory-compatability

* rename all trust-dns-integration references to hickory-integration

* rename all trust-dns-util references to hickory-util

* Update MIT licenses to reference Hickory DNS

* update all trust-dns references to hickory-dns

* update all bluejekyll github references to hickorydns org

* Update name in Changelog

* make sure hickory-dns logs during tests

* add changelogs for recent main additions

* fix references to trust-dns and hickory in architecture

* update a few trust-dns references in READMEs

* fixup some dangling trust_dns references

* replace fka with formerly in change log

* replace all hickoydns org references to hickory-dns

* replace all http links with https

* update logos

* update hickorydns to hickory-dns for all other org references

* fix Notices of Trust-DNS to Hickory in each Readme
2023-10-13 18:39:28 -07:00

322 lines
10 KiB
Rust

pub mod mut_message_client;
use std::{
env,
io::{stdout, BufRead, BufReader, Write},
panic::{catch_unwind, UnwindSafe},
process::{Command, Stdio},
str::FromStr,
sync::*,
thread,
time::*,
};
use hickory_client::{client::*, proto::xfer::DnsResponse};
#[cfg(feature = "dnssec")]
use hickory_proto::rr::dnssec::*;
use hickory_proto::rr::{rdata::A, *};
use regex::Regex;
use tokio::runtime::Runtime;
use tracing::{info, warn};
#[cfg(feature = "dnssec")]
use self::mut_message_client::MutMessageHandle;
fn collect_and_print<R: BufRead>(read: &mut R, output: &mut String) {
output.clear();
read.read_line(output).expect("could not read stdio");
if !output.is_empty() {
// uncomment for debugging
// println!("SRV: {}", output.trim_end());
}
}
/// Spins up a Server and handles shutting it down after running the test
#[allow(dead_code)]
pub fn named_test_harness<F, R>(toml: &str, test: F)
where
F: FnOnce(Option<u16>, Option<u16>, Option<u16>, Option<u16>, Option<u16>) -> R + UnwindSafe,
{
let server_path = env::var("TDNS_WORKSPACE_ROOT").unwrap_or_else(|_| "..".to_owned());
println!("using server src path: {server_path}");
let mut command = Command::new(format!("{server_path}/target/debug/hickory-dns"));
command
.stdout(Stdio::piped())
.env(
"RUST_LOG",
"hickory_dns=debug,hickory_client=debug,hickory_proto=debug,hickory_resolver=debug,hickory_server=debug",
)
.arg("-d")
.arg(&format!(
"--config={server_path}/tests/test-data/test_configs/{toml}"
))
.arg(&format!(
"--zonedir={server_path}/tests/test-data/test_configs"
))
.arg(&format!("--port={}", 0))
.arg(&format!("--tls-port={}", 0))
.arg(&format!("--https-port={}", 0))
.arg(&format!("--quic-port={}", 0));
println!("named cli options: {command:#?}");
let mut named = command.spawn().expect("failed to start named");
println!("server starting");
let mut named_out = BufReader::new(named.stdout.take().expect("no stdout"));
// forced thread killer
let named = Arc::new(Mutex::new(named));
let named_killer = Arc::clone(&named);
let succeeded = Arc::new(atomic::AtomicBool::new(false));
let succeeded_clone = succeeded.clone();
let killer_join = thread::Builder::new()
.name("thread_killer".to_string())
.spawn(move || {
let succeeded = succeeded_clone;
let kill_named = || {
info!("killing named");
let mut named = named_killer.lock().unwrap();
if let Err(e) = named.kill() {
warn!("warning: failed to kill named: {:?}", e);
}
};
for _ in 0..30 {
thread::sleep(Duration::from_secs(1));
if succeeded.load(atomic::Ordering::Relaxed) {
kill_named();
return;
}
}
kill_named();
println!("Thread Killer has been awoken, killing process");
std::process::exit(-1);
})
.expect("could not start thread killer");
// These will be collected from the server startup'
// FIXME: create a wrapper type for all of these params
let mut test_udp_port = Option::<u16>::None;
let mut test_tcp_port = Option::<u16>::None;
let mut test_tls_port = Option::<u16>::None;
let mut test_https_port = Option::<u16>::None;
let mut test_quic_port = Option::<u16>::None;
// we should get the correct output before 1000 lines...
let mut output = String::new();
let mut found = false;
let wait_for_start_until = Instant::now() + Duration::from_secs(60);
// Search strings for the ports used during testing
let udp_regex = Regex::new(r"listening for UDP on (?:V4\()?0\.0\.0\.0:(\d+)\)?").unwrap();
let tcp_regex = Regex::new(r"listening for TCP on (?:V4\()?0\.0\.0\.0:(\d+)\)?").unwrap();
let tls_regex = Regex::new(r"listening for TLS on (?:V4\()?0\.0\.0\.0:(\d+)\)?").unwrap();
let https_regex = Regex::new(r"listening for HTTPS on (?:V4\()?0\.0\.0\.0:(\d+)\)?").unwrap();
let quic_regex = Regex::new(r"listening for QUIC on (?:V4\()?0\.0\.0\.0:(\d+)\)?").unwrap();
while Instant::now() < wait_for_start_until {
{
if let Some(ret_code) = named
.lock()
.unwrap()
.try_wait()
.expect("failed to check status of named")
{
panic!("named has already exited with code: {}", ret_code);
}
}
collect_and_print(&mut named_out, &mut output);
if let Some(udp) = udp_regex.captures(&output) {
test_udp_port = Some(
udp.get(1)
.expect("udp missing port")
.as_str()
.parse()
.expect("could not parse udp port"),
);
} else if let Some(tcp) = tcp_regex.captures(&output) {
test_tcp_port = Some(
tcp.get(1)
.expect("tcp missing port")
.as_str()
.parse()
.expect("could not parse tcp port"),
);
} else if let Some(tls) = tls_regex.captures(&output) {
test_tls_port = Some(
tls.get(1)
.expect("tls missing port")
.as_str()
.parse()
.expect("could not parse tls port"),
);
} else if let Some(https) = https_regex.captures(&output) {
test_https_port = Some(
https
.get(1)
.expect("https missing port")
.as_str()
.parse()
.expect("could not parse https port"),
);
} else if let Some(quic) = quic_regex.captures(&output) {
test_quic_port = Some(
quic.get(1)
.expect("quic missing port")
.as_str()
.parse()
.expect("could not parse quic port"),
);
} else if output.contains("awaiting connections...") {
found = true;
break;
}
}
stdout().flush().unwrap();
assert!(found);
println!(
"Test server started. ports: udp {test_udp_port:?}, tcp {test_tcp_port:?}, tls {test_tls_port:?}, https {test_https_port:?}, quic {test_quic_port:?}",
);
// spawn a thread to capture stdout
let succeeded_clone = succeeded.clone();
thread::Builder::new()
.name("named stdout".into())
.spawn(move || {
let succeeded = succeeded_clone;
while !succeeded.load(atomic::Ordering::Relaxed) {
collect_and_print(&mut named_out, &mut output);
if let Some(_ret_code) = named
.lock()
.unwrap()
.try_wait()
.expect("failed to check status of named")
{
// uncomment for debugging:
// println!("named exited with code: {}", _ret_code);
}
}
})
.expect("no thread available");
println!("running test...");
let result = catch_unwind(move || {
test(
test_udp_port,
test_tcp_port,
test_tls_port,
test_https_port,
test_quic_port,
)
});
println!("test completed");
succeeded.store(true, atomic::Ordering::Relaxed);
killer_join.join().expect("join failed");
assert!(result.is_ok(), "test failed");
}
pub fn query_message<C: ClientHandle>(
io_loop: &mut Runtime,
client: &mut C,
name: Name,
record_type: RecordType,
) -> DnsResponse {
println!("sending request: {name} for: {record_type}");
let response = io_loop.block_on(client.query(name, DNSClass::IN, record_type));
//println!("got response: {}");
response.expect("request failed")
}
// This only validates that a query to the server works, it shouldn't be used for more than this.
// i.e. more complex checks live with the clients and authorities to validate deeper functionality
#[allow(dead_code)]
pub fn query_a<C: ClientHandle>(io_loop: &mut Runtime, client: &mut C) {
let name = Name::from_str("www.example.com").unwrap();
let response = query_message(io_loop, client, name, RecordType::A);
let record = &response.answers()[0];
if let Some(RData::A(ref address)) = record.data() {
assert_eq!(address, &A::new(127, 0, 0, 1))
} else {
panic!("wrong RDATA")
}
}
// This only validates that a query to the server works, it shouldn't be used for more than this.
// i.e. more complex checks live with the clients and authorities to validate deeper functionality
#[allow(dead_code)]
#[cfg(feature = "dnssec")]
pub fn query_all_dnssec(
io_loop: &mut Runtime,
client: AsyncClient,
algorithm: Algorithm,
with_rfc6975: bool,
) {
use hickory_client::rr::rdata::{DNSKEY, RRSIG};
let name = Name::from_str("example.com.").unwrap();
let mut client = MutMessageHandle::new(client);
client.lookup_options.set_is_dnssec(true);
if with_rfc6975 {
client
.lookup_options
.set_supported_algorithms(SupportedAlgorithms::from_vec(&[algorithm]));
}
let response = query_message(io_loop, &mut client, name.clone(), RecordType::DNSKEY);
let dnskey = response
.answers()
.iter()
.filter_map(Record::data)
.filter_map(DNSKEY::try_borrow)
.find(|d| d.algorithm() == algorithm);
assert!(dnskey.is_some(), "DNSKEY not found");
let response = query_message(io_loop, &mut client, name, RecordType::DNSKEY);
let rrsig = response
.answers()
.iter()
.filter_map(Record::data)
.filter_map(RRSIG::try_borrow)
.filter(|rrsig| rrsig.algorithm() == algorithm)
.find(|rrsig| rrsig.type_covered() == RecordType::DNSKEY);
assert!(rrsig.is_some(), "Associated RRSIG not found");
}
#[allow(dead_code)]
#[cfg(feature = "dnssec")]
pub fn query_all_dnssec_with_rfc6975(
io_loop: &mut Runtime,
client: AsyncClient,
algorithm: Algorithm,
) {
query_all_dnssec(io_loop, client, algorithm, true)
}
#[allow(dead_code)]
#[cfg(feature = "dnssec")]
pub fn query_all_dnssec_wo_rfc6975(
io_loop: &mut Runtime,
client: AsyncClient,
algorithm: Algorithm,
) {
query_all_dnssec(io_loop, client, algorithm, false)
}