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::{DNSClass, Name, 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 #[cfg(any(feature = "__https", feature = "__h3"))]
87 #[error("request too large")]
88 RequestTooLarge,
89
90 #[error("request timed out")]
92 Timeout,
93
94 #[cfg(feature = "__quic")]
96 #[error("error creating quic connection: {0}")]
97 QuinnConnect(#[from] quinn::ConnectError),
98
99 #[cfg(feature = "__quic")]
101 #[error("error with quic connection: {0}")]
102 QuinnConnection(#[from] quinn::ConnectionError),
103
104 #[cfg(feature = "__quic")]
106 #[error("error writing to quic connection: {0}")]
107 QuinnWriteError(#[from] quinn::WriteError),
108
109 #[cfg(feature = "__quic")]
111 #[error("error writing to quic read: {0}")]
112 QuinnReadError(#[from] quinn::ReadExactError),
113
114 #[cfg(feature = "__quic")]
116 #[error("referenced a closed QUIC stream: {0}")]
117 QuinnStreamError(#[from] quinn::ClosedStream),
118
119 #[cfg(feature = "__quic")]
121 #[error("error constructing quic configuration: {0}")]
122 QuinnConfigError(#[from] quinn::ConfigError),
123
124 #[cfg(feature = "__quic")]
126 #[error("QUIC TLS config must include an AES-128-GCM cipher suite")]
127 QuinnTlsConfigError(#[from] quinn::crypto::rustls::NoInitialCipherSuite),
128
129 #[cfg(feature = "__quic")]
131 #[error("an unknown quic stream was used")]
132 QuinnUnknownStreamError,
133
134 #[cfg(feature = "__quic")]
136 #[error("quic messages should always be 0, got: {0}")]
137 QuicMessageIdNot0(u16),
138
139 #[cfg(feature = "__tls")]
141 #[error("rustls construction error: {0}")]
142 RustlsError(#[from] rustls::Error),
143
144 #[error("case of query name in response did not match")]
147 QueryCaseMismatch,
148
149 #[error("foreign class record in response: {record_name} {record_class} {record_type}")]
151 ForeignClassRecord {
152 record_name: Name,
154 record_class: DNSClass,
156 record_type: RecordType,
158 },
159
160 #[error("response was truncated; TCP already tried or no other transport available")]
162 Truncated,
163}
164
165impl NetError {
166 #[inline]
168 pub fn is_nx_domain(&self) -> bool {
169 matches!(
170 self,
171 Self::Dns(DnsError::NoRecordsFound(NoRecords {
172 response_code: ResponseCode::NXDomain,
173 ..
174 }))
175 )
176 }
177
178 #[inline]
180 pub fn is_no_records_found(&self) -> bool {
181 matches!(self, Self::Dns(DnsError::NoRecordsFound { .. }))
182 }
183
184 pub fn is_connection_closed(&self) -> bool {
188 match self {
189 #[cfg(feature = "__https")]
190 Self::H2(err) => {
191 err.is_io() || err.is_go_away() || err.reason() == Some(h2::Reason::REFUSED_STREAM)
192 }
193 #[cfg(feature = "__h3")]
194 Self::H3(err) => err.is_h3_no_error(),
195 Self::Io(err) => matches!(
196 err.kind(),
197 io::ErrorKind::ConnectionReset
198 | io::ErrorKind::ConnectionAborted
199 | io::ErrorKind::BrokenPipe
200 | io::ErrorKind::NotConnected
201 | io::ErrorKind::UnexpectedEof
202 ),
203 #[cfg(feature = "__quic")]
204 Self::QuinnConnection(err) => matches!(
205 err,
206 quinn::ConnectionError::ConnectionClosed(_)
207 | quinn::ConnectionError::ApplicationClosed(_)
208 | quinn::ConnectionError::Reset
209 ),
210 _ => false,
211 }
212 }
213
214 #[inline]
216 pub fn into_soa(self) -> Option<Box<Record<SOA>>> {
217 match self {
218 Self::Dns(DnsError::NoRecordsFound(NoRecords { soa, .. })) => soa,
219 _ => None,
220 }
221 }
222
223 pub fn as_metrics_label(&self) -> &'static str {
225 match self {
226 Self::Busy => "busy",
227 #[cfg(any(feature = "__https", feature = "__h3"))]
228 Self::Decode(_) => "http_header_decode",
229 Self::Dns(dns_err) => dns_err.as_metrics_label(),
230 #[cfg(feature = "__https")]
231 Self::H2(_) => "http2",
232 #[cfg(feature = "__h3")]
233 Self::H3(_) => "http3",
234 Self::ParseInt(_) => "parse_header_value",
235 Self::NoConnections => "no_connections",
236 Self::Proto(_) => "proto",
237 Self::Io(e) => {
238 use std::io::ErrorKind::*;
239 match e.kind() {
240 NotFound => "io_not_found",
241 PermissionDenied => "io_permission_denied",
242 ConnectionRefused => "io_connection_refused",
243 ConnectionReset => "io_connection_reset",
244 HostUnreachable => "io_host_unreachable",
245 NetworkUnreachable => "io_network_unreachable",
246 ConnectionAborted => "io_connection_aborted",
247 NotConnected => "io_not_connected",
248 AddrInUse => "io_addr_in_use",
249 AddrNotAvailable => "io_addr_not_available",
250 NetworkDown => "io_network_down",
251 BrokenPipe => "io_broken_pipe",
252 AlreadyExists => "io_already_exists",
253 WouldBlock => "io_would_block",
254 InvalidInput => "io_invalid_input",
255 InvalidData => "io_invalid_data",
256 TimedOut => "io_timed_out",
257 WriteZero => "io_write_zero",
258 QuotaExceeded => "io_quota_exceeded",
259 ResourceBusy => "io_resource_busy",
260 Deadlock => "io_deadlock",
261 Interrupted => "io_interrupted",
262 Unsupported => "io_unsupported",
263 OutOfMemory => "io_out_of_memory",
264 Other => "io_other",
265
266 NotADirectory => "io_not_a_directory",
268 IsADirectory => "io_is_a_directory",
269 DirectoryNotEmpty => "io_directory_not_empty",
270 ReadOnlyFilesystem => "io_read_only_filesystem",
271 StaleNetworkFileHandle => "io_stale_network_file_handle",
272 StorageFull => "io_storage_full",
273 NotSeekable => "io_not_seekable",
274 FileTooLarge => "io_file_too_large",
275 ExecutableFileBusy => "io_executable_file_busy",
276 CrossesDevices => "io_crosses_devices",
277 TooManyLinks => "io_too_many_links",
278 InvalidFilename => "io_invalid_filename",
279 ArgumentListTooLong => "io_argument_list_too_long",
280 UnexpectedEof => "io_unexpected_eof",
281
282 _ => "io_unknown",
283 }
284 }
285 #[cfg(any(feature = "__https", feature = "__h3"))]
286 Self::RequestTooLarge => "request_too_large",
287 Self::Timeout => "timeout",
288 #[cfg(feature = "__quic")]
289 Self::QuinnConnect(_) => "quic_connect",
290 #[cfg(feature = "__quic")]
291 Self::QuinnConnection(_) => "quic_connection",
292 #[cfg(feature = "__quic")]
293 Self::QuinnWriteError(_) => "quic_write",
294 #[cfg(feature = "__quic")]
295 Self::QuinnReadError(_) => "quic_read",
296 #[cfg(feature = "__quic")]
297 Self::QuinnStreamError(_) => "quic_stream",
298 #[cfg(feature = "__quic")]
299 Self::QuinnConfigError(_) => "quic_config",
300 #[cfg(feature = "__quic")]
301 Self::QuinnTlsConfigError(_) => "quic_tls_config_error",
302 #[cfg(feature = "__quic")]
303 Self::QuinnUnknownStreamError => "quic_unknown_stream",
304 #[cfg(feature = "__quic")]
305 Self::QuicMessageIdNot0(_) => "quic_message_id_not_0",
306 #[cfg(feature = "__tls")]
307 Self::RustlsError(_) => "tls",
308 Self::QueryCaseMismatch => "query_case_mismatch",
309 Self::ForeignClassRecord { .. } => "foreign_class_record",
310 Self::Truncated => "truncated",
311
312 Self::Message(_) | Self::Msg(_) => "message",
314 }
315 }
316}
317
318impl From<NoRecords> for NetError {
319 fn from(no_records: NoRecords) -> Self {
320 Self::Dns(DnsError::NoRecordsFound(no_records))
321 }
322}
323
324impl From<DecodeError> for NetError {
325 fn from(e: DecodeError) -> Self {
326 Self::Proto(e.into())
327 }
328}
329
330#[cfg(feature = "__h3")]
331impl From<h3::error::StreamError> for NetError {
332 fn from(e: h3::error::StreamError) -> Self {
333 Self::H3(Arc::new(e))
334 }
335}
336
337#[cfg(feature = "__https")]
338impl From<h2::Error> for NetError {
339 fn from(e: h2::Error) -> Self {
340 Self::H2(Arc::new(e))
341 }
342}
343
344#[cfg(any(feature = "__https", feature = "__h3"))]
345impl From<ToStrError> for NetError {
346 fn from(e: ToStrError) -> Self {
347 Self::Decode(Arc::new(e))
348 }
349}
350
351impl From<io::Error> for NetError {
352 fn from(e: io::Error) -> Self {
353 match e.kind() {
354 io::ErrorKind::TimedOut => Self::Timeout,
355 _ => Self::Io(Arc::new(e)),
356 }
357 }
358}
359
360impl From<String> for NetError {
361 fn from(msg: String) -> Self {
362 Self::Msg(msg)
363 }
364}
365
366impl From<&'static str> for NetError {
367 fn from(msg: &'static str) -> Self {
368 Self::Message(msg)
369 }
370}
371
372#[derive(Clone, Debug, Error)]
374#[non_exhaustive]
375pub enum DnsError {
376 #[error("error response: {0}")]
378 ResponseCode(ResponseCode),
379 #[error("no records found for {:?}", .0.query)]
381 NoRecordsFound(NoRecords),
382 #[cfg(feature = "__dnssec")]
384 #[non_exhaustive]
385 #[error("DNSSEC Negative Record Response for {query}, {proof}")]
386 Nsec {
387 query: Box<Query>,
389 response: Box<DnsResponse>,
391 proof: Proof,
393 },
394 #[error("DNSSEC validation failed")]
396 DnssecBogus,
397}
398
399impl DnsError {
400 pub fn from_response(response: DnsResponse) -> Result<DnsResponse, Self> {
402 use ResponseCode::*;
403 debug!("response: {}", *response);
404
405 match response.response_code {
406 Refused => Err(Self::ResponseCode(Refused)),
407 code @ ServFail
408 | code @ FormErr
409 | code @ NotImp
410 | code @ YXDomain
411 | code @ YXRRSet
412 | code @ NXRRSet
413 | code @ NotAuth
414 | code @ NotZone
415 | code @ BADVERS
416 | code @ BADSIG
417 | code @ BADKEY
418 | code @ BADTIME
419 | code @ BADMODE
420 | code @ BADNAME
421 | code @ BADALG
422 | code @ BADTRUNC
423 | code @ BADCOOKIE => Err(Self::ResponseCode(code)),
424 code @ NXDomain |
426 code @ NoError
428 if !response.contains_answer() && !response.truncation => {
429 let soa = response.soa().as_ref().map(RecordRef::to_owned);
432
433 let mut referral_name_servers = vec![];
435 for ns in response.authorities.iter().filter(|ns| ns.record_type() == RecordType::NS) {
436 let glue = response
437 .additionals
438 .iter()
439 .filter_map(|record| {
440 if let RData::NS(ns_data) = &ns.data {
441 if record.name == **ns_data && matches!(&record.data, RData::A(_) | RData::AAAA(_)) {
442 return Some(Record::to_owned(record));
443 }
444 }
445
446 None
447 })
448 .collect::<Vec<Record>>();
449 referral_name_servers.push(ForwardNSData { ns: Record::to_owned(ns), glue: glue.into() })
450 }
451
452 let option_ns = if !referral_name_servers.is_empty() {
453 Some(referral_name_servers.into())
454 } else {
455 None
456 };
457
458 let authorities = if !response.authorities.is_empty() {
459 Some(response.authorities.to_owned().into())
460 } else {
461 None
462 };
463
464 let negative_ttl = response.negative_ttl();
465 let query = response.into_message().queries.drain(..).next().unwrap_or_default();
466
467 Err(Self::NoRecordsFound(NoRecords {
468 query: Box::new(query),
469 soa: soa.map(Box::new),
470 ns: option_ns,
471 negative_ttl,
472 response_code: code,
473 authorities,
474 }))
475 }
476 NXDomain
477 | NoError
478 | Unknown(_) => Ok(response),
479 }
480 }
481
482 pub fn as_metrics_label(&self) -> &'static str {
484 use hickory_proto::op::ResponseCode::*;
485 match self {
486 Self::ResponseCode(NoError) => "dns_response_code_noerror",
487 Self::ResponseCode(FormErr) => "dns_response_code_formerror",
488 Self::ResponseCode(ServFail) => "dns_response_code_servfail",
489 Self::ResponseCode(NXDomain) => "dns_response_code_nxdomain",
490 Self::ResponseCode(NotImp) => "dns_response_code_notimp",
491 Self::ResponseCode(Refused) => "dns_response_code_refused",
492 Self::ResponseCode(YXDomain) => "dns_response_code_yxdomain",
493 Self::ResponseCode(YXRRSet) => "dns_response_code_yxrrset",
494 Self::ResponseCode(NXRRSet) => "dns_response_code_nxrrset",
495 Self::ResponseCode(NotAuth) => "dns_response_code_notauth",
496 Self::ResponseCode(NotZone) => "dns_response_code_notzone",
497 Self::ResponseCode(BADVERS) => "dns_response_code_badvers",
498 Self::ResponseCode(BADSIG) => "dns_response_code_badsig",
499 Self::ResponseCode(BADKEY) => "dns_response_code_badkey",
500 Self::ResponseCode(BADTIME) => "dns_response_code_badtime",
501 Self::ResponseCode(BADMODE) => "dns_response_code_badmode",
502 Self::ResponseCode(BADNAME) => "dns_response_code_badname",
503 Self::ResponseCode(BADALG) => "dns_response_code_badalg",
504 Self::ResponseCode(BADTRUNC) => "dns_response_code_badtrunc",
505 Self::ResponseCode(BADCOOKIE) => "dns_response_code_badcookie",
506 Self::ResponseCode(Unknown(_)) => "dns_response_code_unknown",
507 Self::NoRecordsFound(_) => "dns_no_records",
508 #[cfg(feature = "__dnssec")]
509 Self::Nsec { .. } => "dns_nsec",
510 Self::DnssecBogus => "dns_dnssec_bogus",
511 }
512 }
513}
514
515#[derive(Clone, Debug)]
517#[non_exhaustive]
518pub struct NoRecords {
519 pub query: Box<Query>,
521 pub soa: Option<Box<Record<SOA>>>,
523 pub ns: Option<Arc<[ForwardNSData]>>,
526 pub negative_ttl: Option<u32>,
529 pub response_code: ResponseCode,
532 pub authorities: Option<Arc<[Record]>>,
534}
535
536impl NoRecords {
537 pub fn new(query: impl Into<Box<Query>>, response_code: ResponseCode) -> Self {
539 Self {
540 query: query.into(),
541 soa: None,
542 ns: None,
543 negative_ttl: None,
544 response_code,
545 authorities: None,
546 }
547 }
548}
549
550#[derive(Clone, Debug)]
552pub struct ForwardNSData {
553 pub ns: Record,
555 pub glue: Arc<[Record]>,
557}
558
559#[cfg(test)]
560mod tests {
561 use super::*;
562
563 #[test]
564 fn is_connection_closed_io_kinds() {
565 for kind in [
566 io::ErrorKind::ConnectionReset,
567 io::ErrorKind::ConnectionAborted,
568 io::ErrorKind::BrokenPipe,
569 io::ErrorKind::NotConnected,
570 io::ErrorKind::UnexpectedEof,
571 ] {
572 assert!(
573 NetError::from(io::Error::from(kind)).is_connection_closed(),
574 "{kind:?}"
575 );
576 }
577
578 assert!(
579 !NetError::from(io::Error::from(io::ErrorKind::PermissionDenied))
580 .is_connection_closed()
581 );
582 assert!(!NetError::Timeout.is_connection_closed());
583 assert!(!NetError::Message("server error").is_connection_closed());
584 }
585
586 #[cfg(feature = "__https")]
587 #[test]
588 fn is_connection_closed_h2() {
589 assert!(NetError::from(h2::Error::from(h2::Reason::REFUSED_STREAM)).is_connection_closed());
591 assert!(
593 !NetError::from(h2::Error::from(h2::Reason::INTERNAL_ERROR)).is_connection_closed()
594 );
595 }
596
597 #[cfg(feature = "__quic")]
598 #[test]
599 fn is_connection_closed_quic_excludes_timeout() {
600 assert!(NetError::QuinnConnection(quinn::ConnectionError::Reset).is_connection_closed());
601 assert!(
602 !NetError::QuinnConnection(quinn::ConnectionError::TimedOut).is_connection_closed()
603 );
604 }
605}