use core::str::FromStr;
use std::collections::VecDeque;
use bytes::{Bytes, BytesMut};
use octseq::{Octets, Parser};
use crate::base::iana::Rcode;
use crate::base::message_builder::{
AnswerBuilder, AuthorityBuilder, QuestionBuilder,
};
use crate::base::net::{Ipv4Addr, Ipv6Addr};
use crate::base::rdata::ComposeRecordData;
use crate::base::{
Message, MessageBuilder, ParsedName, Record, Rtype, Serial, Ttl,
};
use crate::base::{Name, ToName};
use crate::logging::init_logging;
use crate::rdata::{Aaaa, Soa, ZoneRecordData, A};
use crate::zonetree::types::{ZoneUpdate, ZoneUpdate as ZU};
use super::interpreter::XfrResponseInterpreter;
use super::types::{Error, IterationError, ParsedRecord};
#[test]
fn non_xfr_response_is_rejected() {
init_logging();
let req = mk_request("example.com", Rtype::AXFR).into_message();
let mut interpreter = XfrResponseInterpreter::new();
let mut answer = mk_empty_answer(&req, Rcode::NOERROR);
add_answer_record(&req, &mut answer, A::new(Ipv4Addr::LOCALHOST));
let resp = answer.into_message();
assert!(matches!(
interpreter.interpret_response(resp),
Err(Error::NotValidXfrResponse)
));
}
#[test]
fn axfr_response_with_no_answers_is_rejected() {
init_logging();
let req = mk_request("example.com", Rtype::AXFR).into_message();
let mut interpreter = XfrResponseInterpreter::new();
let resp = mk_empty_answer(&req, Rcode::NOERROR).into_message();
assert!(matches!(
interpreter.interpret_response(resp),
Err(Error::NotValidXfrResponse)
));
}
#[test]
fn error_axfr_response_is_rejected() {
init_logging();
let req = mk_request("example.com", Rtype::AXFR).into_message();
let mut interpreter = XfrResponseInterpreter::new();
let mut answer = mk_empty_answer(&req, Rcode::SERVFAIL);
add_answer_record(&req, &mut answer, mk_soa(Serial::now()));
let resp = answer.into_message();
assert!(matches!(
interpreter.interpret_response(resp),
Err(Error::NotValidXfrResponse)
));
}
#[test]
fn incomplete_axfr_response_is_accepted() {
init_logging();
let req = mk_request("example.com", Rtype::AXFR).into_message();
let mut interpreter = XfrResponseInterpreter::new();
let mut answer = mk_empty_answer(&req, Rcode::NOERROR);
add_answer_record(&req, &mut answer, mk_soa(Serial::now()));
let resp = answer.into_message();
let mut it = interpreter.interpret_response(resp).unwrap();
assert!(it.next().is_none());
}
#[test]
fn axfr_response_with_only_soas_is_accepted() {
init_logging();
let req = mk_request("example.com", Rtype::AXFR).into_message();
let mut interpreter = XfrResponseInterpreter::new();
let mut answer = mk_empty_answer(&req, Rcode::NOERROR);
let soa = mk_soa(Serial::now());
add_answer_record(&req, &mut answer, soa.clone());
add_answer_record(&req, &mut answer, soa);
let resp = answer.into_message();
let mut it = interpreter.interpret_response(resp).unwrap();
assert_eq!(it.next(), Some(Ok(ZoneUpdate::DeleteAllRecords)));
assert!(matches!(it.next(), Some(Ok(ZU::Finished(_)))));
assert!(it.next().is_none());
}
#[test]
fn axfr_multi_response_with_only_soas_is_accepted() {
init_logging();
let req = mk_request("example.com", Rtype::AXFR).into_message();
let mut interpreter = XfrResponseInterpreter::new();
let mut answer = mk_empty_answer(&req, Rcode::NOERROR);
let soa = mk_soa(Serial::now());
add_answer_record(&req, &mut answer, soa.clone());
let resp = answer.into_message();
let mut it = interpreter.interpret_response(resp).unwrap();
assert!(it.next().is_none());
let mut answer = mk_empty_answer(&req, Rcode::NOERROR);
add_answer_record(&req, &mut answer, soa);
let resp = answer.into_message();
let mut it = interpreter.interpret_response(resp).unwrap();
assert_eq!(it.next(), Some(Ok(ZoneUpdate::DeleteAllRecords)));
assert!(matches!(it.next(), Some(Ok(ZU::Finished(_)))));
assert!(it.next().is_none());
}
#[test]
fn axfr_response_generates_expected_updates() {
init_logging();
let req = mk_request("example.com", Rtype::AXFR).into_message();
let mut interpreter = XfrResponseInterpreter::new();
let mut answer = mk_empty_answer(&req, Rcode::NOERROR);
let serial = Serial::now();
let soa = mk_soa(serial);
add_answer_record(&req, &mut answer, soa.clone());
add_answer_record(&req, &mut answer, A::new(Ipv4Addr::LOCALHOST));
add_answer_record(&req, &mut answer, Aaaa::new(Ipv6Addr::LOCALHOST));
add_answer_record(&req, &mut answer, soa);
let resp = answer.into_message();
let mut it = interpreter.interpret_response(resp).unwrap();
assert_eq!(it.next(), Some(Ok(ZoneUpdate::DeleteAllRecords)));
assert!(
matches!(it.next(), Some(Ok(ZoneUpdate::AddRecord(r))) if r.rtype() == Rtype::A)
);
assert!(
matches!(it.next(), Some(Ok(ZoneUpdate::AddRecord(r))) if r.rtype() == Rtype::AAAA)
);
assert!(matches!(it.next(), Some(Ok(ZoneUpdate::Finished(_)))));
assert!(it.next().is_none());
}
#[test]
fn ixfr_response_generates_expected_updates() {
init_logging();
let req = mk_request("example.com", Rtype::IXFR);
let mut authority = req.authority();
let client_serial = Serial::now();
let soa = mk_soa(client_serial);
add_authority_record(&mut authority, soa);
let req = authority.into_message();
let mut interpreter = XfrResponseInterpreter::new();
let old_serial = client_serial;
let new_serial = client_serial.add(1);
let old_soa = mk_soa(old_serial);
let new_soa = mk_soa(new_serial);
let mut answer = mk_empty_answer(&req, Rcode::NOERROR);
add_answer_record(&req, &mut answer, new_soa.clone());
add_answer_record(&req, &mut answer, old_soa.clone());
add_answer_record(&req, &mut answer, A::new(Ipv4Addr::LOCALHOST));
add_answer_record(&req, &mut answer, A::new(Ipv4Addr::BROADCAST));
add_answer_record(&req, &mut answer, new_soa.clone());
add_answer_record(&req, &mut answer, A::new(Ipv4Addr::BROADCAST));
add_answer_record(&req, &mut answer, A::new(Ipv4Addr::LOCALHOST));
add_answer_record(&req, &mut answer, new_soa.clone());
let resp = answer.into_message();
let it = interpreter.interpret_response(resp).unwrap();
let mut buf = BytesMut::new();
new_soa.compose_rdata(&mut buf).unwrap();
let buf = buf.freeze();
let mut parser = Parser::from_ref(&buf);
let expected_new_soa = Soa::parse(&mut parser).unwrap();
let mut buf = BytesMut::new();
old_soa.compose_rdata(&mut buf).unwrap();
let buf = buf.freeze();
let mut parser = Parser::from_ref(&buf);
let expected_old_soa = Soa::parse(&mut parser).unwrap();
let owner =
ParsedName::<Bytes>::from(Name::from_str("example.com").unwrap());
let expected_updates: [Result<ZoneUpdate<ParsedRecord>, IterationError>;
7] = [
Ok(ZoneUpdate::BeginBatchDelete(Record::from((
owner.clone(),
0,
ZoneRecordData::Soa(expected_old_soa),
)))),
Ok(ZoneUpdate::DeleteRecord(Record::from((
owner.clone(),
0,
ZoneRecordData::A(A::new(Ipv4Addr::LOCALHOST)),
)))),
Ok(ZoneUpdate::DeleteRecord(Record::from((
owner.clone(),
0,
ZoneRecordData::A(A::new(Ipv4Addr::BROADCAST)),
)))),
Ok(ZoneUpdate::BeginBatchAdd(Record::from((
owner.clone(),
0,
ZoneRecordData::Soa(expected_new_soa.clone()),
)))),
Ok(ZoneUpdate::AddRecord(Record::from((
owner.clone(),
0,
ZoneRecordData::A(A::new(Ipv4Addr::BROADCAST)),
)))),
Ok(ZoneUpdate::AddRecord(Record::from((
owner.clone(),
0,
ZoneRecordData::A(A::new(Ipv4Addr::LOCALHOST)),
)))),
Ok(ZoneUpdate::Finished(Record::from((
owner.clone(),
0,
ZoneRecordData::Soa(expected_new_soa),
)))),
];
for (update, expected_update) in it.zip(expected_updates) {
assert_eq!(update, expected_update);
}
}
#[test]
fn multi_ixfr_response_generates_expected_updates() {
init_logging();
let req = mk_request("example.com", Rtype::IXFR);
let mut authority = req.authority();
let client_serial = Serial::now();
let soa = mk_soa(client_serial);
add_authority_record(&mut authority, soa);
let req = authority.into_message();
let old_serial = client_serial;
let new_serial = client_serial.add(1);
let old_soa = mk_soa(old_serial);
let new_soa = mk_soa(new_serial);
let resp = mk_first_ixfr_response(&req, &new_soa, old_soa);
let mut interpreter = XfrResponseInterpreter::new();
let mut it = interpreter.interpret_response(resp).unwrap();
assert!(matches!(it.next(), Some(Ok(ZU::BeginBatchDelete(_)))));
assert!(matches!(it.next(), Some(Ok(ZU::DeleteRecord(..)))));
assert!(it.next().is_none());
let resp = mk_second_ixfr_response(req, new_soa);
let mut it = interpreter.interpret_response(resp).unwrap();
assert!(matches!(it.next(), Some(Ok(ZU::DeleteRecord(..)))));
assert!(matches!(it.next(), Some(Ok(ZU::BeginBatchAdd(_)))));
assert!(matches!(it.next(), Some(Ok(ZU::AddRecord(..)))));
assert!(matches!(it.next(), Some(Ok(ZU::AddRecord(..)))));
assert!(matches!(it.next(), Some(Ok(ZU::Finished(_)))));
assert!(it.next().is_none());
}
#[test]
fn ixfr_response_with_fallback_to_axfr_generates_expected_updates() {
init_logging();
let req = mk_request("example.com", Rtype::IXFR);
let mut authority = req.authority();
let client_serial = Serial::now();
let soa = mk_soa(client_serial);
add_authority_record(&mut authority, soa);
let req = authority.into_message();
let mut answer = mk_empty_answer(&req, Rcode::NOERROR);
let serial = Serial::now();
let soa = mk_soa(serial);
add_answer_record(&req, &mut answer, soa.clone());
add_answer_record(&req, &mut answer, A::new(Ipv4Addr::LOCALHOST));
add_answer_record(&req, &mut answer, Aaaa::new(Ipv6Addr::LOCALHOST));
add_answer_record(&req, &mut answer, soa);
let resp = answer.into_message();
let mut interpreter = XfrResponseInterpreter::new();
let mut it = interpreter.interpret_response(resp).unwrap();
assert_eq!(it.next(), Some(Ok(ZoneUpdate::DeleteAllRecords)));
assert!(
matches!(it.next(), Some(Ok(ZoneUpdate::AddRecord(r))) if r.rtype() == Rtype::A)
);
assert!(
matches!(it.next(), Some(Ok(ZoneUpdate::AddRecord(r))) if r.rtype() == Rtype::AAAA)
);
assert!(matches!(it.next(), Some(Ok(ZoneUpdate::Finished(_)))));
assert!(it.next().is_none());
}
#[test]
fn ixfr_response_single_soa_detected_as_udp_to_tcp_upgrade_signal() {
init_logging();
let req = mk_request("example.com", Rtype::IXFR);
let mut authority = req.authority();
let client_serial = Serial::now();
let soa = mk_soa(client_serial);
add_authority_record(&mut authority, soa);
let req = authority.into_message();
let mut answer = mk_empty_answer(&req, Rcode::NOERROR);
let serial = Serial::now();
let soa = mk_soa(serial);
add_answer_record(&req, &mut answer, soa.clone());
let resp = answer.into_message();
let mut interpreter = XfrResponseInterpreter::new();
let mut it = interpreter.interpret_response(resp).unwrap();
assert_eq!(
it.next(),
Some(Err(IterationError::SingleSoaIxfrTcpRetrySignal))
);
assert!(it.next().is_none());
}
#[test]
fn is_finished() {
init_logging();
let req = mk_request("example.com", Rtype::IXFR);
let mut authority = req.authority();
let client_serial = Serial::now();
let soa = mk_soa(client_serial);
add_authority_record(&mut authority, soa);
let req = authority.into_message();
let old_serial = client_serial;
let new_serial = client_serial.add(1);
let old_soa = mk_soa(old_serial);
let new_soa = mk_soa(new_serial);
let mut responses: VecDeque<_> = vec![
mk_first_ixfr_response(&req, &new_soa, old_soa),
mk_second_ixfr_response(req, new_soa),
]
.into();
let mut interpreter = XfrResponseInterpreter::new();
let mut count = 0;
while !interpreter.is_finished() {
let resp = responses.pop_front().unwrap();
let it = interpreter.interpret_response(resp).unwrap();
count += it.count();
}
assert!(interpreter.is_finished());
assert!(responses.is_empty());
assert_eq!(count, 7);
}
#[test]
fn cannot_be_used_once_finished() {
init_logging();
let req = mk_request("example.com", Rtype::AXFR).into_message();
let mut interpreter = XfrResponseInterpreter::new();
let mut answer = mk_empty_answer(&req, Rcode::NOERROR);
let soa = mk_soa(Serial::now());
add_answer_record(&req, &mut answer, soa.clone());
add_answer_record(&req, &mut answer, soa);
add_answer_record(&req, &mut answer, A::new(Ipv4Addr::BROADCAST));
let resp = answer.into_message();
let mut it = interpreter.interpret_response(resp).unwrap();
assert_eq!(it.next(), Some(Ok(ZoneUpdate::DeleteAllRecords)));
assert!(matches!(it.next(), Some(Ok(ZU::Finished(_)))));
assert_eq!(it.next(), Some(Err(IterationError::AlreadyFinished)));
}
fn mk_first_ixfr_response(
req: &Message<Bytes>,
new_soa: &Soa<Name<Bytes>>,
old_soa: Soa<Name<Bytes>>,
) -> Message<Bytes> {
let mut answer = mk_empty_answer(req, Rcode::NOERROR);
add_answer_record(req, &mut answer, new_soa.clone());
add_answer_record(req, &mut answer, old_soa);
add_answer_record(req, &mut answer, A::new(Ipv4Addr::LOCALHOST));
answer.into_message()
}
fn mk_second_ixfr_response(
req: Message<Bytes>,
new_soa: Soa<Name<Bytes>>,
) -> Message<Bytes> {
let mut answer = mk_empty_answer(&req, Rcode::NOERROR);
add_answer_record(&req, &mut answer, A::new(Ipv4Addr::BROADCAST));
add_answer_record(&req, &mut answer, new_soa.clone());
add_answer_record(&req, &mut answer, A::new(Ipv4Addr::BROADCAST));
add_answer_record(&req, &mut answer, A::new(Ipv4Addr::LOCALHOST));
add_answer_record(&req, &mut answer, new_soa);
answer.into_message()
}
fn mk_request(qname: &str, qtype: Rtype) -> QuestionBuilder<BytesMut> {
let req = MessageBuilder::new_bytes();
let mut req = req.question();
req.push((Name::vec_from_str(qname).unwrap(), qtype))
.unwrap();
req
}
fn mk_empty_answer(
req: &Message<Bytes>,
rcode: Rcode,
) -> AnswerBuilder<BytesMut> {
let builder = MessageBuilder::new_bytes();
builder.start_answer(req, rcode).unwrap()
}
fn add_answer_record<O: Octets, T: ComposeRecordData>(
req: &Message<O>,
answer: &mut AnswerBuilder<BytesMut>,
item: T,
) {
let question = req.sole_question().unwrap();
let qname = question.qname();
let qclass = question.qclass();
answer
.push((qname, qclass, Ttl::from_secs(0), item))
.unwrap();
}
fn add_authority_record<T: ComposeRecordData>(
authority: &mut AuthorityBuilder<BytesMut>,
item: T,
) {
let (qname, qclass) = {
let question = authority.as_message().sole_question().unwrap();
let qname = question.qname().to_bytes();
let qclass = question.qclass();
(qname, qclass)
};
authority
.push((qname, qclass, Ttl::from_secs(0), item))
.unwrap();
}
fn mk_soa(serial: Serial) -> Soa<Name<Bytes>> {
let mname = Name::from_str("mname").unwrap();
let rname = Name::from_str("rname").unwrap();
let ttl = Ttl::from_secs(0);
Soa::new(mname, rname, serial, ttl, ttl, ttl, ttl)
}