//! TSIG interop testing with other DNS implementations. #![cfg(all(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>; type TestBuilder = MessageBuilder>>; type TestAnswer = AnswerBuilder>>; type TestAdditional = AdditionalBuilder>>; //------------ 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::>::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::>::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::>::from_str("example.com.").unwrap(), 3600, Soa::new( Name::>::from_str("mname.example.com.").unwrap(), Name::>::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::>::from_str("example.com.").unwrap(), 3600, A::from_octets(10, zero, one, two), )) .unwrap() }