mirror of
https://github.com/NLnetLabs/domain.git
synced 2026-09-06 18:07:42 +02:00
- Automatically determine the MSRV. - Rebuild 'main' every week to catch environmental changes. - Run Clippy in a dedicated job to be more parallel. - Only run it on Ubuntu. - Introduce caching of '~/.cargo' and '/target'. - Only save it on 'main', but restore it in all other jobs. - Preserve OpenSSL-excluding behavior for Windows.
419 lines
13 KiB
Rust
419 lines
13 KiB
Rust
//! TSIG interop testing with other DNS implementations.
|
||
#![cfg(all(feature = "tsig", feature = "bytes", feature = "std"))]
|
||
|
||
mod common;
|
||
|
||
use std::io::{Read, Write};
|
||
use std::net::{IpAddr, SocketAddr, TcpListener, TcpStream, UdpSocket};
|
||
use std::process::Command;
|
||
use std::str::FromStr;
|
||
use std::string::String;
|
||
use std::time::Duration;
|
||
use std::vec::Vec;
|
||
use std::{env, fs, io, path::PathBuf, thread};
|
||
|
||
use domain::base::iana::{Rcode, Rtype};
|
||
use domain::base::message::Message;
|
||
use domain::base::message_builder::{
|
||
AdditionalBuilder, AnswerBuilder, MessageBuilder, StreamTarget,
|
||
};
|
||
use domain::base::name::Name;
|
||
use domain::base::opt::TcpKeepalive;
|
||
use domain::base::record::Ttl;
|
||
use domain::rdata::tsig::Time48;
|
||
use domain::rdata::{Soa, A};
|
||
use domain::tsig;
|
||
use domain::utils::base64;
|
||
use ring::rand::SystemRandom;
|
||
|
||
use common::nsd;
|
||
|
||
type TestMessage = Message<Vec<u8>>;
|
||
type TestBuilder = MessageBuilder<StreamTarget<Vec<u8>>>;
|
||
type TestAnswer = AnswerBuilder<StreamTarget<Vec<u8>>>;
|
||
type TestAdditional = AdditionalBuilder<StreamTarget<Vec<u8>>>;
|
||
|
||
//------------ Tests --------------------------------------------------------
|
||
|
||
/// Tests the TSIG client implementation against NSD as a server.
|
||
///
|
||
/// Spins up an NSD serving example.com. and then tries to AXFR that.
|
||
#[test]
|
||
#[ignore]
|
||
fn tsig_client_nsd() {
|
||
// Set up and start NSD with example.org and a TSIG key for AXFRing it.
|
||
let rng = SystemRandom::new();
|
||
|
||
let cur_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||
let base_dir = cur_dir.join("target/test/tsig_client_nsd");
|
||
fs::create_dir_all(&base_dir).unwrap();
|
||
let base_dir = base_dir.canonicalize().unwrap();
|
||
let nsdconfpath = base_dir.join("nsd.conf");
|
||
let zonepath = cur_dir.join("test-data/zonefiles/example.com.txt");
|
||
|
||
let (key, secret) = tsig::Key::generate(
|
||
tsig::Algorithm::Sha1,
|
||
&rng,
|
||
Name::from_str("test.key.").unwrap(),
|
||
None,
|
||
None,
|
||
)
|
||
.unwrap();
|
||
|
||
let mut conf = nsd::Config::all_in(&base_dir);
|
||
conf.ip_address
|
||
.push(SocketAddr::from_str("127.0.0.1:54321").unwrap());
|
||
conf.verbosity = Some(3);
|
||
conf.keys
|
||
.push(nsd::KeyConfig::new("test.key.", "hmac-sha1", secret));
|
||
conf.zones.push(nsd::ZoneConfig::new(
|
||
"example.com",
|
||
zonepath,
|
||
vec![nsd::Acl::new(
|
||
IpAddr::from_str("127.0.0.1").unwrap(),
|
||
None,
|
||
None,
|
||
Some("test.key.".into()),
|
||
)],
|
||
));
|
||
conf.save(&nsdconfpath).unwrap();
|
||
let mut nsd = Command::new("/usr/sbin/nsd")
|
||
.args(["-c", &format!("{}", nsdconfpath.display()), "-d"])
|
||
.spawn()
|
||
.expect("failed to start nsd");
|
||
thread::sleep(Duration::from_secs(1));
|
||
if nsd.try_wait().unwrap().is_some() {
|
||
panic!("NSD didn't start.");
|
||
}
|
||
|
||
let res = thread::spawn(move || {
|
||
// Create an AXFR request and send it to NSD.
|
||
let request = TestBuilder::new_stream_vec();
|
||
let mut request = request
|
||
.request_axfr(Name::<Vec<u8>>::from_str("example.com.").unwrap())
|
||
.unwrap()
|
||
.additional();
|
||
request
|
||
.opt(|builder| builder.push(&TcpKeepalive::new(None)))
|
||
.unwrap();
|
||
let tran = tsig::ClientTransaction::request(
|
||
&key,
|
||
&mut request,
|
||
Time48::now(),
|
||
)
|
||
.unwrap();
|
||
let sock = UdpSocket::bind("127.0.0.1:54320").unwrap();
|
||
sock.send_to(request.as_target().as_dgram_slice(), "127.0.0.1:54321")
|
||
.unwrap();
|
||
let mut answer = loop {
|
||
let mut buf = vec![0; 512];
|
||
let (len, addr) = sock.recv_from(buf.as_mut()).unwrap();
|
||
if addr != SocketAddr::from_str("127.0.0.1:54321").unwrap() {
|
||
continue;
|
||
}
|
||
buf.truncate(len);
|
||
let answer = Message::from_octets(buf).unwrap();
|
||
if answer.header().id() == request.header().id() {
|
||
break answer;
|
||
}
|
||
};
|
||
assert_eq!(answer.header().rcode(), Rcode::NOTIMP);
|
||
if let Err(err) = tran.answer(&mut answer, Time48::now()) {
|
||
panic!("{err:?}");
|
||
}
|
||
})
|
||
.join();
|
||
|
||
// Shut down NSD just to be sure.
|
||
let _ = nsd.kill();
|
||
res.unwrap(); // Panic if the thread panicked.
|
||
}
|
||
|
||
/// Tests the TSIG server implementation against dig as a client.
|
||
#[test]
|
||
#[ignore]
|
||
fn tsig_server_dig() {
|
||
let rng = SystemRandom::new();
|
||
let (key, secret) = tsig::Key::generate(
|
||
tsig::Algorithm::Sha1,
|
||
&rng,
|
||
Name::from_str("test.key.").unwrap(),
|
||
None,
|
||
None,
|
||
)
|
||
.unwrap();
|
||
let secret = base64::encode_string(&secret);
|
||
let secret = format!("hmac-sha1:test.key:{secret}");
|
||
|
||
let join = thread::spawn(move || {
|
||
let sock = UdpSocket::bind("127.0.0.1:54322").unwrap();
|
||
loop {
|
||
let mut buf = vec![0; 512];
|
||
let (len, addr) = sock.recv_from(buf.as_mut()).unwrap();
|
||
buf.truncate(len);
|
||
let mut request = match Message::from_octets(buf) {
|
||
Ok(request) => request,
|
||
Err(_) => continue,
|
||
};
|
||
let answer = TestBuilder::new_stream_vec();
|
||
let answer =
|
||
answer.start_answer(&request, Rcode::NOERROR).unwrap();
|
||
let tran = match tsig::ServerTransaction::request(
|
||
&&key,
|
||
&mut request,
|
||
Time48::now(),
|
||
) {
|
||
Ok(Some(tran)) => tran,
|
||
Ok(None) => {
|
||
sock.send_to(answer.as_slice(), addr).unwrap();
|
||
continue;
|
||
}
|
||
Err(error) => {
|
||
let answer = error
|
||
.build_message(
|
||
&request,
|
||
TestBuilder::new_stream_vec(),
|
||
)
|
||
.unwrap();
|
||
sock.send_to(answer.as_slice(), addr).unwrap();
|
||
continue;
|
||
}
|
||
};
|
||
let mut answer = answer.additional();
|
||
tran.answer(&mut answer, Time48::now()).unwrap();
|
||
sock.send_to(answer.as_slice(), addr).unwrap();
|
||
}
|
||
});
|
||
|
||
let output = Command::new("/usr/bin/dig")
|
||
.args(["-p", "54322", "-y", &secret, "example.com", "@127.0.0.1"])
|
||
.output()
|
||
.expect("failed to start dig");
|
||
drop(join);
|
||
assert!(output.status.success());
|
||
assert!(!String::from_utf8(output.stdout)
|
||
.unwrap()
|
||
.contains("tsig verify failure"));
|
||
}
|
||
|
||
/// Test the client sequence implementation against NSD.
|
||
#[test]
|
||
#[ignore]
|
||
fn tsig_client_sequence_nsd() {
|
||
let rng = SystemRandom::new();
|
||
|
||
let cur_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||
let base_dir = cur_dir.join("target/test/tsig_client_sequence_nsd");
|
||
fs::create_dir_all(&base_dir).unwrap();
|
||
let base_dir = base_dir.canonicalize().unwrap();
|
||
let nsdconfpath = base_dir.join("nsd.conf");
|
||
let zonepath = cur_dir.join("test-data/zonefiles/big.example.com.txt");
|
||
|
||
let (key, secret) = tsig::Key::generate(
|
||
tsig::Algorithm::Sha1,
|
||
&rng,
|
||
Name::from_str("test.key.").unwrap(),
|
||
None,
|
||
None,
|
||
)
|
||
.unwrap();
|
||
|
||
let mut conf = nsd::Config::all_in(&base_dir);
|
||
conf.ip_address
|
||
.push(SocketAddr::from_str("127.0.0.1:54323").unwrap());
|
||
conf.verbosity = Some(3);
|
||
conf.keys
|
||
.push(nsd::KeyConfig::new("test.key.", "hmac-sha1", secret));
|
||
conf.zones.push(nsd::ZoneConfig::new(
|
||
"example.com",
|
||
zonepath,
|
||
vec![nsd::Acl::new(
|
||
IpAddr::from_str("127.0.0.1").unwrap(),
|
||
None,
|
||
None,
|
||
Some("test.key.".into()),
|
||
)],
|
||
));
|
||
conf.save(&nsdconfpath).unwrap();
|
||
let mut nsd = Command::new("/usr/sbin/nsd")
|
||
.args(["-c", &format!("{}", nsdconfpath.display()), "-d"])
|
||
.spawn()
|
||
.expect("failed to start nsd");
|
||
thread::sleep(Duration::from_secs(1));
|
||
if nsd.try_wait().unwrap().is_some() {
|
||
panic!("NSD didn't start.");
|
||
}
|
||
|
||
let res = thread::spawn(move || {
|
||
let mut sock = TcpStream::connect("127.0.0.1:54323").unwrap();
|
||
let request = TestBuilder::new_stream_vec();
|
||
let mut request = request
|
||
.request_axfr(Name::<Vec<u8>>::from_str("example.com.").unwrap())
|
||
.unwrap()
|
||
.additional();
|
||
let mut tran =
|
||
tsig::ClientSequence::request(&key, &mut request, Time48::now())
|
||
.unwrap();
|
||
sock.write_all(request.as_target().as_stream_slice())
|
||
.unwrap();
|
||
loop {
|
||
let mut len = [0u8; 2];
|
||
sock.read_exact(&mut len).unwrap();
|
||
let len = u16::from_be_bytes(len) as usize;
|
||
assert!(len != 0);
|
||
let mut buf = vec![0; len];
|
||
sock.read_exact(&mut buf).unwrap();
|
||
let mut answer = Message::from_octets(buf).unwrap();
|
||
tran.answer(&mut answer, Time48::now()).unwrap();
|
||
// Last message has SOA as last record in answer section.
|
||
// We don’t care about details.
|
||
if answer.answer().unwrap().last().unwrap().unwrap().rtype()
|
||
== Rtype::SOA
|
||
{
|
||
break;
|
||
}
|
||
}
|
||
tran.done().unwrap()
|
||
})
|
||
.join();
|
||
|
||
// Shut down NSD just to be sure.
|
||
let _ = nsd.kill();
|
||
res.unwrap(); // Panic if the thread paniced.
|
||
}
|
||
|
||
/// Tests the TSIG server sequence implementation against dig.
|
||
#[test]
|
||
#[ignore]
|
||
fn tsig_server_sequence_dig() {
|
||
let rng = SystemRandom::new();
|
||
let (key, secret) = tsig::Key::generate(
|
||
tsig::Algorithm::Sha1,
|
||
&rng,
|
||
Name::from_str("test.key.").unwrap(),
|
||
None,
|
||
None,
|
||
)
|
||
.unwrap();
|
||
let secret = base64::encode_string(&secret);
|
||
let secret = format!("hmac-sha1:test.key:{secret}");
|
||
let listener = TcpListener::bind("127.0.0.1:54324").unwrap();
|
||
let port = listener.local_addr().unwrap().port();
|
||
|
||
let join = thread::spawn(move || {
|
||
for sock in listener.incoming() {
|
||
let mut sock = sock.unwrap();
|
||
let mut buf = [0u8, 2];
|
||
sock.read_exact(&mut buf).unwrap();
|
||
let len = u16::from_be_bytes(buf) as usize;
|
||
let mut buf = vec![0; len];
|
||
sock.read_exact(&mut buf).unwrap();
|
||
let mut request = Message::from_octets(buf).unwrap();
|
||
let mut tran = tsig::ServerSequence::request(
|
||
&&key,
|
||
&mut request,
|
||
Time48::now(),
|
||
)
|
||
.unwrap()
|
||
.unwrap();
|
||
let mut answer = make_first_axfr(&request);
|
||
tran.answer(&mut answer, Time48::now()).unwrap();
|
||
send_tcp(&mut sock, answer.as_target().as_stream_slice())
|
||
.unwrap();
|
||
for two in 0..10u8 {
|
||
for one in 0..10u8 {
|
||
let mut answer = make_middle_axfr(&request, one, two);
|
||
tran.answer(&mut answer, Time48::now()).unwrap();
|
||
send_tcp(&mut sock, answer.as_target().as_stream_slice())
|
||
.unwrap();
|
||
}
|
||
}
|
||
let mut answer = make_last_axfr(&request);
|
||
tran.answer(&mut answer, Time48::now()).unwrap();
|
||
send_tcp(&mut sock, answer.as_target().as_stream_slice())
|
||
.unwrap();
|
||
}
|
||
});
|
||
|
||
let output = Command::new("/usr/bin/dig")
|
||
.args([
|
||
"-p",
|
||
&format!("{port}"),
|
||
"-y",
|
||
&secret,
|
||
"example.com",
|
||
"AXFR",
|
||
"@127.0.0.1",
|
||
"+tcp",
|
||
])
|
||
.output()
|
||
.expect("failed to start dig");
|
||
drop(join);
|
||
assert!(output.status.success());
|
||
assert!(!String::from_utf8(output.stdout)
|
||
.unwrap()
|
||
.contains("tsig verify failure"));
|
||
}
|
||
|
||
//------------ Helpers ------------------------------------------------------
|
||
|
||
fn send_tcp(sock: &mut TcpStream, msg: &[u8]) -> Result<(), io::Error> {
|
||
sock.write_all(msg)?;
|
||
Ok(())
|
||
}
|
||
|
||
fn make_first_axfr(request: &TestMessage) -> TestAdditional {
|
||
let msg = TestBuilder::new_stream_vec();
|
||
let mut msg = msg.start_answer(request, Rcode::NOERROR).unwrap();
|
||
push_soa(&mut msg);
|
||
push_a(&mut msg, 0, 0, 0);
|
||
msg.additional()
|
||
}
|
||
|
||
fn make_middle_axfr(
|
||
request: &TestMessage,
|
||
one: u8,
|
||
two: u8,
|
||
) -> TestAdditional {
|
||
let msg = TestBuilder::new_stream_vec();
|
||
let mut msg = msg.start_answer(request, Rcode::NOERROR).unwrap();
|
||
push_a(&mut msg, 1, one, two);
|
||
msg.additional()
|
||
}
|
||
|
||
fn make_last_axfr(request: &TestMessage) -> TestAdditional {
|
||
let msg = TestBuilder::new_stream_vec();
|
||
let mut msg = msg.start_answer(request, Rcode::NOERROR).unwrap();
|
||
push_a(&mut msg, 2, 0, 0);
|
||
push_soa(&mut msg);
|
||
msg.additional()
|
||
}
|
||
|
||
fn push_soa(builder: &mut TestAnswer) {
|
||
builder
|
||
.push((
|
||
Name::<Vec<u8>>::from_str("example.com.").unwrap(),
|
||
3600,
|
||
Soa::new(
|
||
Name::<Vec<u8>>::from_str("mname.example.com.").unwrap(),
|
||
Name::<Vec<u8>>::from_str("rname.example.com.").unwrap(),
|
||
12.into(),
|
||
Ttl::from_secs(3600),
|
||
Ttl::from_secs(3600),
|
||
Ttl::from_secs(3600),
|
||
Ttl::from_secs(3600),
|
||
),
|
||
))
|
||
.unwrap()
|
||
}
|
||
|
||
fn push_a(builder: &mut TestAnswer, zero: u8, one: u8, two: u8) {
|
||
builder
|
||
.push((
|
||
Name::<Vec<u8>>::from_str("example.com.").unwrap(),
|
||
3600,
|
||
A::from_octets(10, zero, one, two),
|
||
))
|
||
.unwrap()
|
||
}
|