#![deny(missing_docs)]
use std::io;
use std::sync::Arc;
use thiserror::Error;
use tracing::warn;
use crate::{
net::{DnsError, ForwardNSData, NetError, NoRecords},
proto::{
ProtoError,
op::Query,
op::ResponseCode,
rr::{Name, Record, RecordType, rdata::SOA},
},
};
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum RecursorError {
#[error("maximum record limit for {record_type} exceeded: {count} records")]
MaxRecordLimitExceeded {
count: usize,
record_type: RecordType,
},
#[error("{0}")]
Message(&'static str),
#[error("{0}")]
Msg(String),
#[error("negative response")]
Negative(AuthorityData),
#[error("forward NS Response")]
ForwardNS(Arc<[ForwardNSData]>),
#[error("io error: {0}")]
Io(#[from] io::Error),
#[error("net error: {0}")]
Net(NetError),
#[error("request timed out")]
Timeout,
#[error("maximum recursion limit exceeded: {count} queries")]
RecursionLimitExceeded {
count: usize,
},
}
impl RecursorError {
pub fn recursion_exceeded(limit: u8, depth: u8, name: &Name) -> Result<(), Self> {
if depth < limit {
return Ok(());
}
warn!("recursion depth exceeded for {name}");
Err(Self::RecursionLimitExceeded {
count: depth as usize,
})
}
pub fn into_soa(self) -> Option<Box<Record<SOA>>> {
match self {
Self::Net(net) => net.into_soa(),
Self::Negative(fwd) => fwd.soa,
_ => None,
}
}
pub fn is_no_records_found(&self) -> bool {
match self {
Self::Net(net) => net.is_no_records_found(),
Self::Negative(fwd) => fwd.is_no_records_found(),
_ => false,
}
}
pub fn is_nx_domain(&self) -> bool {
match self {
Self::Net(net) => net.is_nx_domain(),
Self::Negative(fwd) => fwd.is_nx_domain(),
_ => false,
}
}
pub fn is_timeout(&self) -> bool {
match self {
Self::Net(net) => matches!(net, NetError::Timeout),
_ => false,
}
}
}
impl From<NetError> for RecursorError {
fn from(e: NetError) -> Self {
let NetError::Dns(DnsError::NoRecordsFound(no_records)) = e else {
return Self::Net(e);
};
if let Some(ns) = no_records.ns {
Self::ForwardNS(ns)
} else {
Self::Negative(AuthorityData::new(
no_records.query,
no_records.soa,
true,
matches!(no_records.response_code, ResponseCode::NXDomain),
no_records.authorities,
))
}
}
}
impl From<RecursorError> for NetError {
fn from(e: RecursorError) -> Self {
match e {
RecursorError::Negative(fwd) => DnsError::NoRecordsFound(fwd.into()).into(),
_ => Self::from(e.to_string()),
}
}
}
impl From<ProtoError> for RecursorError {
fn from(e: ProtoError) -> Self {
NetError::from(e).into()
}
}
impl From<String> for RecursorError {
fn from(msg: String) -> Self {
Self::Msg(msg)
}
}
impl From<&'static str> for RecursorError {
fn from(msg: &'static str) -> Self {
Self::Message(msg)
}
}
impl Clone for RecursorError {
fn clone(&self) -> Self {
use self::RecursorError::*;
match self {
MaxRecordLimitExceeded { count, record_type } => MaxRecordLimitExceeded {
count: *count,
record_type: *record_type,
},
Message(msg) => Message(msg),
Msg(msg) => Msg(msg.clone()),
Negative(ns) => Negative(ns.clone()),
ForwardNS(ns) => ForwardNS(ns.clone()),
Io(io) => Io(io::Error::from(io.kind())),
Net(net) => Net(net.clone()),
Timeout => Self::Timeout,
RecursionLimitExceeded { count } => RecursionLimitExceeded { count: *count },
}
}
}
#[derive(Clone, Debug)]
pub struct AuthorityData {
pub query: Box<Query>,
pub soa: Option<Box<Record<SOA>>>,
no_records_found: bool,
nx_domain: bool,
pub authorities: Option<Arc<[Record]>>,
}
impl AuthorityData {
pub fn new(
query: Box<Query>,
soa: Option<Box<Record<SOA>>>,
no_records_found: bool,
nx_domain: bool,
authorities: Option<Arc<[Record]>>,
) -> Self {
Self {
query,
soa,
no_records_found,
nx_domain,
authorities,
}
}
pub fn is_no_records_found(&self) -> bool {
self.no_records_found
}
pub fn is_nx_domain(&self) -> bool {
self.nx_domain
}
}
impl From<AuthorityData> for NoRecords {
fn from(data: AuthorityData) -> Self {
let response_code = match data.is_nx_domain() {
true => ResponseCode::NXDomain,
false => ResponseCode::NoError,
};
let mut new = Self::new(data.query, response_code);
new.soa = data.soa;
new.authorities = data.authorities;
new
}
}