use std::{
net::{SocketAddr, UdpSocket},
sync::mpsc::{self, Receiver},
thread,
time::Duration,
};
use hickory_proto::{
op::{Message, MessageType},
rr::{
Name, RData, Record, RecordType,
rdata::{HTTPS, svcb::SVCB},
},
};
use hickory_resolver::config::{ConnectionConfig, NameServerConfig, ResolverConfig, ResolverOpts};
use crate::transport::Spawn;
pub const TTL: u32 = 300;
pub const QUERY_TIMEOUT: Duration = Duration::from_secs(10);
pub fn name(domain: &str) -> Name {
domain.parse().expect("test domain must parse")
}
pub fn serve(
respond: impl Fn(&Message) -> Message + Send + 'static,
) -> (SocketAddr, Receiver<(Name, RecordType)>) {
let socket = UdpSocket::bind((std::net::Ipv4Addr::LOCALHOST, 0)).unwrap();
let addr = socket.local_addr().unwrap();
let (names, queried) = mpsc::channel();
thread::spawn(move || {
let mut buffer = [0; 4096];
while let Ok((len, peer)) = socket.recv_from(&mut buffer) {
let Ok(query) = Message::from_vec(&buffer[..len]) else {
continue;
};
if let Some(asked) = query.queries.first() {
let _ = names.send((asked.name().clone(), asked.query_type()));
}
if let Ok(response) = respond(&query).to_vec() {
let _ = socket.send_to(&response, peer);
}
}
});
(addr, queried)
}
pub fn resolver_config(server: SocketAddr) -> (ResolverConfig, ResolverOpts) {
let mut udp = ConnectionConfig::udp();
udp.port = server.port();
let config = ResolverConfig::from_name_servers(vec![NameServerConfig::new(
server.ip(),
true,
vec![udp],
)]);
let mut options = ResolverOpts::default();
options.attempts = 1;
(config, options)
}
pub fn answer(
query: &Message,
rcode: hickory_proto::op::ResponseCode,
answers: Vec<Record>,
) -> Message {
let mut response = Message::new(
query.metadata.id,
MessageType::Response,
query.metadata.op_code,
);
response.metadata.recursion_desired = query.metadata.recursion_desired;
response.metadata.recursion_available = true;
response.metadata.response_code = rcode;
response.queries.clone_from(&query.queries);
response.answers = answers;
response
}
pub fn https(
owner: &str,
priority: u16,
target: &str,
params: Vec<(
hickory_proto::rr::rdata::svcb::SvcParamKey,
hickory_proto::rr::rdata::svcb::SvcParamValue,
)>,
) -> Record {
Record::from_rdata(
name(owner),
TTL,
RData::HTTPS(HTTPS(SVCB::new(priority, name(target), params))),
)
}
pub fn spawn() -> Spawn {
crate::backend::test_support::thread_spawn()
}
pub fn queried_name(queried: &Receiver<(Name, RecordType)>) -> (Name, RecordType) {
queried.recv_timeout(QUERY_TIMEOUT).unwrap()
}