use std::collections::HashSet;
use std::fmt;
use std::str::FromStr;
use dns_types::protocol::types::*;
pub const CANNOT_PARSE_PROTOCOL_MODE: &str =
"expected one of 'only-v4', 'prefer-v4', 'prefer-v6', 'only'v6'";
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum ProtocolMode {
OnlyV4,
PreferV4,
PreferV6,
OnlyV6,
}
impl fmt::Display for ProtocolMode {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
ProtocolMode::OnlyV4 => write!(f, "only-v4"),
ProtocolMode::PreferV4 => write!(f, "prefer-v4"),
ProtocolMode::PreferV6 => write!(f, "prefer-v6"),
ProtocolMode::OnlyV6 => write!(f, "only-v6"),
}
}
}
impl FromStr for ProtocolMode {
type Err = &'static str;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"only-v4" => Ok(ProtocolMode::OnlyV4),
"prefer-v4" => Ok(ProtocolMode::PreferV4),
"prefer-v6" => Ok(ProtocolMode::PreferV6),
"only-v6" => Ok(ProtocolMode::OnlyV6),
_ => Err(CANNOT_PARSE_PROTOCOL_MODE),
}
}
}
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)]
pub enum ResolvedRecord {
Authoritative {
rrs: Vec<ResourceRecord>,
soa_rr: ResourceRecord,
},
AuthoritativeNameError {
soa_rr: ResourceRecord,
},
NonAuthoritative {
rrs: Vec<ResourceRecord>,
soa_rr: Option<ResourceRecord>,
},
}
impl ResolvedRecord {
pub fn rrs(self) -> Vec<ResourceRecord> {
match self {
ResolvedRecord::Authoritative { rrs, .. } => rrs,
ResolvedRecord::AuthoritativeNameError { .. } => Vec::new(),
ResolvedRecord::NonAuthoritative { rrs, .. } => rrs,
}
}
pub fn soa_rr(&self) -> Option<&ResourceRecord> {
match self {
ResolvedRecord::Authoritative { soa_rr, .. } => Some(soa_rr),
ResolvedRecord::AuthoritativeNameError { soa_rr } => Some(soa_rr),
ResolvedRecord::NonAuthoritative { soa_rr, .. } => soa_rr.into(),
}
}
}
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub enum ResolutionError {
Timeout,
RecursionLimit,
DuplicateQuestion { question: Question },
DeadEnd { question: Question },
LocalDelegationMissingNS {
apex: DomainName,
domain: DomainName,
},
CacheTypeMismatch {
query: QueryType,
result: RecordType,
},
}
impl std::fmt::Display for ResolutionError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
ResolutionError::Timeout => write!(f, "timed out"),
ResolutionError::RecursionLimit => write!(f, "CNAME chain too long"),
ResolutionError::DuplicateQuestion{question} => write!(f, "loop when answering '{} {} {}'", question.name, question.qclass, question.qtype),
ResolutionError::DeadEnd{question} => write!(f, "unable to answer '{} {} {}'", question.name, question.qclass, question.qtype),
ResolutionError::LocalDelegationMissingNS{apex,domain} => write!(f, "configuration error: got delegation for domain '{domain}' from zone '{apex}', but there are no NS records"),
ResolutionError::CacheTypeMismatch{query,result} => write!(f, "internal error (bug): tried to fetch '{query}' from cache but got '{result}' instead"),
}
}
}
impl std::error::Error for ResolutionError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
None
}
}
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct Nameservers {
pub hostnames: Vec<DomainName>,
pub name: DomainName,
}
impl Nameservers {
pub fn match_count(&self) -> usize {
self.name.labels.len()
}
}
pub fn prioritising_merge(priority: &mut Vec<ResourceRecord>, new: Vec<ResourceRecord>) {
let mut seen = HashSet::new();
for rr in &*priority {
seen.insert((rr.name.clone(), rr.rtype_with_data.rtype()));
}
for rr in new {
if !seen.contains(&(rr.name.clone(), rr.rtype_with_data.rtype())) {
priority.push(rr);
}
}
}
#[cfg(test)]
mod tests {
use std::net::Ipv4Addr;
use dns_types::protocol::types::test_util::*;
use super::*;
#[test]
fn prioritised_merge_prioritises_by_name_and_type() {
let mut priority = vec![
a_record("www.example.com.", Ipv4Addr::new(1, 1, 1, 1)),
a_record("www.example.com.", Ipv4Addr::new(2, 2, 2, 2)),
cname_record("www.example.com.", "target.example.com."),
];
let new = vec![
a_record("www.example.com.", Ipv4Addr::new(3, 3, 3, 3)),
a_record("www.example.net.", Ipv4Addr::new(4, 4, 4, 4)),
cname_record("www.example.com.", "other-target.example.com."),
ns_record("www.example.com.", "ns1.example.com."),
ns_record("www.example.com.", "ns2.example.com."),
];
prioritising_merge(&mut priority, new);
priority.sort();
let mut expected = vec![
a_record("www.example.com.", Ipv4Addr::new(1, 1, 1, 1)),
a_record("www.example.com.", Ipv4Addr::new(2, 2, 2, 2)),
cname_record("www.example.com.", "target.example.com."),
a_record("www.example.net.", Ipv4Addr::new(4, 4, 4, 4)),
ns_record("www.example.com.", "ns1.example.com."),
ns_record("www.example.com.", "ns2.example.com."),
];
expected.sort();
assert_eq!(expected, priority);
}
}