#![deny(missing_docs)]
use core::num::ParseIntError;
use std::io;
use std::sync::Arc;
#[cfg(any(feature = "__https", feature = "__h3"))]
use http::header::ToStrError;
use thiserror::Error;
use tracing::debug;
use crate::proto::ProtoError;
#[cfg(feature = "__dnssec")]
use crate::proto::dnssec::Proof;
use crate::proto::op::{DnsResponse, Query, ResponseCode};
use crate::proto::rr::RData;
use crate::proto::rr::{Record, RecordRef, RecordType, rdata::SOA};
use crate::proto::serialize::binary::DecodeError;
#[non_exhaustive]
#[derive(Error, Clone, Debug)]
pub enum NetError {
#[error("resource too busy")]
Busy,
#[cfg(any(feature = "__https", feature = "__h3"))]
#[error("header decode error: {0}")]
Decode(Arc<ToStrError>),
#[error("DNS error: {0}")]
Dns(#[from] DnsError),
#[error("H2 error: {0}")]
#[cfg(feature = "__https")]
H2(Arc<h2::Error>),
#[error("H3 error: {0}")]
#[cfg(feature = "__h3")]
H3(Arc<h3::error::StreamError>),
#[error("{0}")]
Message(&'static str),
#[error("{0}")]
Msg(String),
#[error("unable to parse number: {0}")]
ParseInt(#[from] ParseIntError),
#[error("no connections available")]
NoConnections,
#[error("protocol error: {0}")]
Proto(#[from] ProtoError),
#[error("io error: {0}")]
Io(Arc<io::Error>),
#[error("request timed out")]
Timeout,
#[cfg(feature = "__quic")]
#[error("error creating quic connection: {0}")]
QuinnConnect(#[from] quinn::ConnectError),
#[cfg(feature = "__quic")]
#[error("error with quic connection: {0}")]
QuinnConnection(#[from] quinn::ConnectionError),
#[cfg(feature = "__quic")]
#[error("error writing to quic connection: {0}")]
QuinnWriteError(#[from] quinn::WriteError),
#[cfg(feature = "__quic")]
#[error("error writing to quic read: {0}")]
QuinnReadError(#[from] quinn::ReadExactError),
#[cfg(feature = "__quic")]
#[error("referenced a closed QUIC stream: {0}")]
QuinnStreamError(#[from] quinn::ClosedStream),
#[cfg(feature = "__quic")]
#[error("error constructing quic configuration: {0}")]
QuinnConfigError(#[from] quinn::ConfigError),
#[cfg(feature = "__quic")]
#[error("QUIC TLS config must include an AES-128-GCM cipher suite")]
QuinnTlsConfigError(#[from] quinn::crypto::rustls::NoInitialCipherSuite),
#[cfg(feature = "__quic")]
#[error("an unknown quic stream was used")]
QuinnUnknownStreamError,
#[cfg(feature = "__quic")]
#[error("quic messages should always be 0, got: {0}")]
QuicMessageIdNot0(u16),
#[cfg(feature = "__tls")]
#[error("rustls construction error: {0}")]
RustlsError(#[from] rustls::Error),
#[error("case of query name in response did not match")]
QueryCaseMismatch,
}
impl NetError {
#[inline]
pub fn is_nx_domain(&self) -> bool {
matches!(
self,
Self::Dns(DnsError::NoRecordsFound(NoRecords {
response_code: ResponseCode::NXDomain,
..
}))
)
}
#[inline]
pub fn is_no_records_found(&self) -> bool {
matches!(self, Self::Dns(DnsError::NoRecordsFound { .. }))
}
#[inline]
pub fn into_soa(self) -> Option<Box<Record<SOA>>> {
match self {
Self::Dns(DnsError::NoRecordsFound(NoRecords { soa, .. })) => soa,
_ => None,
}
}
}
impl From<NoRecords> for NetError {
fn from(no_records: NoRecords) -> Self {
Self::Dns(DnsError::NoRecordsFound(no_records))
}
}
impl From<DecodeError> for NetError {
fn from(e: DecodeError) -> Self {
Self::Proto(e.into())
}
}
#[cfg(feature = "__h3")]
impl From<h3::error::StreamError> for NetError {
fn from(e: h3::error::StreamError) -> Self {
Self::H3(Arc::new(e))
}
}
#[cfg(feature = "__https")]
impl From<h2::Error> for NetError {
fn from(e: h2::Error) -> Self {
Self::H2(Arc::new(e))
}
}
#[cfg(any(feature = "__https", feature = "__h3"))]
impl From<ToStrError> for NetError {
fn from(e: ToStrError) -> Self {
Self::Decode(Arc::new(e))
}
}
impl From<io::Error> for NetError {
fn from(e: io::Error) -> Self {
match e.kind() {
io::ErrorKind::TimedOut => Self::Timeout,
_ => Self::Io(Arc::new(e)),
}
}
}
impl From<String> for NetError {
fn from(msg: String) -> Self {
Self::Msg(msg)
}
}
impl From<&'static str> for NetError {
fn from(msg: &'static str) -> Self {
Self::Message(msg)
}
}
#[derive(Clone, Debug, Error)]
#[non_exhaustive]
pub enum DnsError {
#[error("error response: {0}")]
ResponseCode(ResponseCode),
#[error("no records found for {:?}", .0.query)]
NoRecordsFound(NoRecords),
#[cfg(feature = "__dnssec")]
#[non_exhaustive]
#[error("DNSSEC Negative Record Response for {query}, {proof}")]
Nsec {
query: Box<Query>,
response: Box<DnsResponse>,
proof: Proof,
},
}
impl DnsError {
pub fn from_response(response: DnsResponse) -> Result<DnsResponse, Self> {
use ResponseCode::*;
debug!("response: {}", *response);
match response.response_code {
Refused => Err(Self::ResponseCode(Refused)),
code @ ServFail
| code @ FormErr
| code @ NotImp
| code @ YXDomain
| code @ YXRRSet
| code @ NXRRSet
| code @ NotAuth
| code @ NotZone
| code @ BADVERS
| code @ BADSIG
| code @ BADKEY
| code @ BADTIME
| code @ BADMODE
| code @ BADNAME
| code @ BADALG
| code @ BADTRUNC
| code @ BADCOOKIE => Err(Self::ResponseCode(code)),
code @ NXDomain |
code @ NoError
if !response.contains_answer() && !response.truncation => {
let soa = response.soa().as_ref().map(RecordRef::to_owned);
let mut referral_name_servers = vec![];
for ns in response.authorities.iter().filter(|ns| ns.record_type() == RecordType::NS) {
let glue = response
.additionals
.iter()
.filter_map(|record| {
if let RData::NS(ns_data) = &ns.data {
if record.name == **ns_data && matches!(&record.data, RData::A(_) | RData::AAAA(_)) {
return Some(Record::to_owned(record));
}
}
None
})
.collect::<Vec<Record>>();
referral_name_servers.push(ForwardNSData { ns: Record::to_owned(ns), glue: glue.into() })
}
let option_ns = if !referral_name_servers.is_empty() {
Some(referral_name_servers.into())
} else {
None
};
let authorities = if !response.authorities.is_empty() {
Some(response.authorities.to_owned().into())
} else {
None
};
let negative_ttl = response.negative_ttl();
let query = response.into_message().queries.drain(..).next().unwrap_or_default();
Err(Self::NoRecordsFound(NoRecords {
query: Box::new(query),
soa: soa.map(Box::new),
ns: option_ns,
negative_ttl,
response_code: code,
authorities,
}))
}
NXDomain
| NoError
| Unknown(_) => Ok(response),
}
}
}
#[derive(Clone, Debug)]
#[non_exhaustive]
pub struct NoRecords {
pub query: Box<Query>,
pub soa: Option<Box<Record<SOA>>>,
pub ns: Option<Arc<[ForwardNSData]>>,
pub negative_ttl: Option<u32>,
pub response_code: ResponseCode,
pub authorities: Option<Arc<[Record]>>,
}
impl NoRecords {
pub fn new(query: impl Into<Box<Query>>, response_code: ResponseCode) -> Self {
Self {
query: query.into(),
soa: None,
ns: None,
negative_ttl: None,
response_code,
authorities: None,
}
}
}
#[derive(Clone, Debug)]
pub struct ForwardNSData {
pub ns: Record,
pub glue: Arc<[Record]>,
}