1#![deny(missing_docs)]
11
12use core::num::ParseIntError;
13use std::io;
14use std::sync::Arc;
15
16#[cfg(any(feature = "__https", feature = "__h3"))]
17use http::header::ToStrError;
18use thiserror::Error;
19use tracing::debug;
20
21use crate::proto::ProtoError;
22#[cfg(feature = "__dnssec")]
23use crate::proto::dnssec::Proof;
24use crate::proto::op::{DnsResponse, Query, ResponseCode};
25use crate::proto::rr::RData;
26use crate::proto::rr::{Record, RecordRef, RecordType, rdata::SOA};
27use crate::proto::serialize::binary::DecodeError;
28
29#[non_exhaustive]
31#[derive(Error, Clone, Debug)]
32pub enum NetError {
33 #[error("resource too busy")]
39 Busy,
40
41 #[cfg(any(feature = "__https", feature = "__h3"))]
43 #[error("header decode error: {0}")]
44 Decode(Arc<ToStrError>),
45
46 #[error("DNS error: {0}")]
48 Dns(#[from] DnsError),
49
50 #[error("H2 error: {0}")]
52 #[cfg(feature = "__https")]
53 H2(Arc<h2::Error>),
54
55 #[error("H3 error: {0}")]
57 #[cfg(feature = "__h3")]
58 H3(Arc<h3::error::StreamError>),
59
60 #[error("{0}")]
62 Message(&'static str),
63
64 #[error("{0}")]
66 Msg(String),
67
68 #[error("unable to parse number: {0}")]
70 ParseInt(#[from] ParseIntError),
71
72 #[error("no connections available")]
74 NoConnections,
75
76 #[error("protocol error: {0}")]
78 Proto(#[from] ProtoError),
79
80 #[error("io error: {0}")]
83 Io(Arc<io::Error>),
84
85 #[error("request timed out")]
87 Timeout,
88
89 #[cfg(feature = "__quic")]
91 #[error("error creating quic connection: {0}")]
92 QuinnConnect(#[from] quinn::ConnectError),
93
94 #[cfg(feature = "__quic")]
96 #[error("error with quic connection: {0}")]
97 QuinnConnection(#[from] quinn::ConnectionError),
98
99 #[cfg(feature = "__quic")]
101 #[error("error writing to quic connection: {0}")]
102 QuinnWriteError(#[from] quinn::WriteError),
103
104 #[cfg(feature = "__quic")]
106 #[error("error writing to quic read: {0}")]
107 QuinnReadError(#[from] quinn::ReadExactError),
108
109 #[cfg(feature = "__quic")]
111 #[error("referenced a closed QUIC stream: {0}")]
112 QuinnStreamError(#[from] quinn::ClosedStream),
113
114 #[cfg(feature = "__quic")]
116 #[error("error constructing quic configuration: {0}")]
117 QuinnConfigError(#[from] quinn::ConfigError),
118
119 #[cfg(feature = "__quic")]
121 #[error("QUIC TLS config must include an AES-128-GCM cipher suite")]
122 QuinnTlsConfigError(#[from] quinn::crypto::rustls::NoInitialCipherSuite),
123
124 #[cfg(feature = "__quic")]
126 #[error("an unknown quic stream was used")]
127 QuinnUnknownStreamError,
128
129 #[cfg(feature = "__quic")]
131 #[error("quic messages should always be 0, got: {0}")]
132 QuicMessageIdNot0(u16),
133
134 #[cfg(feature = "__tls")]
136 #[error("rustls construction error: {0}")]
137 RustlsError(#[from] rustls::Error),
138
139 #[error("case of query name in response did not match")]
142 QueryCaseMismatch,
143}
144
145impl NetError {
146 #[inline]
148 pub fn is_nx_domain(&self) -> bool {
149 matches!(
150 self,
151 Self::Dns(DnsError::NoRecordsFound(NoRecords {
152 response_code: ResponseCode::NXDomain,
153 ..
154 }))
155 )
156 }
157
158 #[inline]
160 pub fn is_no_records_found(&self) -> bool {
161 matches!(self, Self::Dns(DnsError::NoRecordsFound { .. }))
162 }
163
164 #[inline]
166 pub fn into_soa(self) -> Option<Box<Record<SOA>>> {
167 match self {
168 Self::Dns(DnsError::NoRecordsFound(NoRecords { soa, .. })) => soa,
169 _ => None,
170 }
171 }
172}
173
174impl From<NoRecords> for NetError {
175 fn from(no_records: NoRecords) -> Self {
176 Self::Dns(DnsError::NoRecordsFound(no_records))
177 }
178}
179
180impl From<DecodeError> for NetError {
181 fn from(e: DecodeError) -> Self {
182 Self::Proto(e.into())
183 }
184}
185
186#[cfg(feature = "__h3")]
187impl From<h3::error::StreamError> for NetError {
188 fn from(e: h3::error::StreamError) -> Self {
189 Self::H3(Arc::new(e))
190 }
191}
192
193#[cfg(feature = "__https")]
194impl From<h2::Error> for NetError {
195 fn from(e: h2::Error) -> Self {
196 Self::H2(Arc::new(e))
197 }
198}
199
200#[cfg(any(feature = "__https", feature = "__h3"))]
201impl From<ToStrError> for NetError {
202 fn from(e: ToStrError) -> Self {
203 Self::Decode(Arc::new(e))
204 }
205}
206
207impl From<io::Error> for NetError {
208 fn from(e: io::Error) -> Self {
209 match e.kind() {
210 io::ErrorKind::TimedOut => Self::Timeout,
211 _ => Self::Io(Arc::new(e)),
212 }
213 }
214}
215
216impl From<String> for NetError {
217 fn from(msg: String) -> Self {
218 Self::Msg(msg)
219 }
220}
221
222impl From<&'static str> for NetError {
223 fn from(msg: &'static str) -> Self {
224 Self::Message(msg)
225 }
226}
227
228#[derive(Clone, Debug, Error)]
230#[non_exhaustive]
231pub enum DnsError {
232 #[error("error response: {0}")]
234 ResponseCode(ResponseCode),
235 #[error("no records found for {:?}", .0.query)]
237 NoRecordsFound(NoRecords),
238 #[cfg(feature = "__dnssec")]
240 #[non_exhaustive]
241 #[error("DNSSEC Negative Record Response for {query}, {proof}")]
242 Nsec {
243 query: Box<Query>,
245 response: Box<DnsResponse>,
247 proof: Proof,
249 },
250}
251
252impl DnsError {
253 pub fn from_response(response: DnsResponse) -> Result<DnsResponse, Self> {
255 use ResponseCode::*;
256 debug!("response: {}", *response);
257
258 match response.response_code {
259 Refused => Err(Self::ResponseCode(Refused)),
260 code @ ServFail
261 | code @ FormErr
262 | code @ NotImp
263 | code @ YXDomain
264 | code @ YXRRSet
265 | code @ NXRRSet
266 | code @ NotAuth
267 | code @ NotZone
268 | code @ BADVERS
269 | code @ BADSIG
270 | code @ BADKEY
271 | code @ BADTIME
272 | code @ BADMODE
273 | code @ BADNAME
274 | code @ BADALG
275 | code @ BADTRUNC
276 | code @ BADCOOKIE => Err(Self::ResponseCode(code)),
277 code @ NXDomain |
279 code @ NoError
281 if !response.contains_answer() && !response.truncation => {
282 let soa = response.soa().as_ref().map(RecordRef::to_owned);
285
286 let mut referral_name_servers = vec![];
288 for ns in response.authorities.iter().filter(|ns| ns.record_type() == RecordType::NS) {
289 let glue = response
290 .additionals
291 .iter()
292 .filter_map(|record| {
293 if let RData::NS(ns_data) = &ns.data {
294 if record.name == **ns_data && matches!(&record.data, RData::A(_) | RData::AAAA(_)) {
295 return Some(Record::to_owned(record));
296 }
297 }
298
299 None
300 })
301 .collect::<Vec<Record>>();
302 referral_name_servers.push(ForwardNSData { ns: Record::to_owned(ns), glue: glue.into() })
303 }
304
305 let option_ns = if !referral_name_servers.is_empty() {
306 Some(referral_name_servers.into())
307 } else {
308 None
309 };
310
311 let authorities = if !response.authorities.is_empty() {
312 Some(response.authorities.to_owned().into())
313 } else {
314 None
315 };
316
317 let negative_ttl = response.negative_ttl();
318 let query = response.into_message().queries.drain(..).next().unwrap_or_default();
319
320 Err(Self::NoRecordsFound(NoRecords {
321 query: Box::new(query),
322 soa: soa.map(Box::new),
323 ns: option_ns,
324 negative_ttl,
325 response_code: code,
326 authorities,
327 }))
328 }
329 NXDomain
330 | NoError
331 | Unknown(_) => Ok(response),
332 }
333 }
334}
335
336#[derive(Clone, Debug)]
338#[non_exhaustive]
339pub struct NoRecords {
340 pub query: Box<Query>,
342 pub soa: Option<Box<Record<SOA>>>,
344 pub ns: Option<Arc<[ForwardNSData]>>,
347 pub negative_ttl: Option<u32>,
350 pub response_code: ResponseCode,
353 pub authorities: Option<Arc<[Record]>>,
355}
356
357impl NoRecords {
358 pub fn new(query: impl Into<Box<Query>>, response_code: ResponseCode) -> Self {
360 Self {
361 query: query.into(),
362 soa: None,
363 ns: None,
364 negative_ttl: None,
365 response_code,
366 authorities: None,
367 }
368 }
369}
370
371#[derive(Clone, Debug)]
373pub struct ForwardNSData {
374 pub ns: Record,
376 pub glue: Arc<[Record]>,
378}