1#![deny(missing_docs)]
11
12use alloc::borrow::ToOwned;
13use alloc::boxed::Box;
14use alloc::string::{String, ToString};
15use alloc::sync::Arc;
16use alloc::vec::Vec;
17use core::cmp::Ordering;
18use core::fmt;
19#[cfg(feature = "std")]
20use std::{io, sync};
21
22#[cfg(feature = "backtrace")]
23pub use backtrace::Backtrace as ExtBacktrace;
24use enum_as_inner::EnumAsInner;
25#[cfg(feature = "backtrace")]
26use once_cell::sync::Lazy;
27use thiserror::Error;
28use tracing::debug;
29
30#[cfg(feature = "__dnssec")]
31use crate::dnssec::Proof;
32#[cfg(any(feature = "dnssec-aws-lc-rs", feature = "dnssec-ring"))]
33use crate::dnssec::ring_like::Unspecified;
34use crate::op::{Header, Query, ResponseCode};
35use crate::rr::{Record, RecordType, domain::Name, rdata::SOA, resource::RecordRef};
36use crate::serialize::binary::DecodeError;
37use crate::xfer::DnsResponse;
38
39#[cfg(feature = "backtrace")]
41pub static ENABLE_BACKTRACE: Lazy<bool> = Lazy::new(|| {
42 use std::env;
43 let bt = env::var("RUST_BACKTRACE");
44 matches!(bt.as_ref().map(|s| s as &str), Ok("full") | Ok("1"))
45});
46
47#[cfg(feature = "backtrace")]
51#[macro_export]
52macro_rules! trace {
53 () => {{
54 use $crate::ExtBacktrace as Backtrace;
55
56 if *$crate::ENABLE_BACKTRACE {
57 Some(Backtrace::new())
58 } else {
59 None
60 }
61 }};
62}
63
64pub(crate) type ProtoResult<T> = ::core::result::Result<T, ProtoError>;
66
67#[derive(Debug, EnumAsInner, Error)]
69#[non_exhaustive]
70pub enum ProtoErrorKind {
71 #[error("there should only be one query per request, got: {0}")]
73 BadQueryCount(usize),
74
75 #[error("resource too busy")]
81 Busy,
82
83 #[error("future was canceled: {0:?}")]
85 Canceled(futures_channel::oneshot::Canceled),
86
87 #[error("char data length exceeds {max}: {len}")]
89 CharacterDataTooLong {
90 max: usize,
92 len: usize,
94 },
95
96 #[error("overlapping labels name {label} other {other}")]
98 LabelOverlapsWithOther {
99 label: usize,
101 other: usize,
103 },
104
105 #[cfg(feature = "__dnssec")]
107 #[error("DNSSEC Negative Record Response for {query}, {proof}")]
108 Nsec {
109 query: Box<Query>,
111 proof: Proof,
113 },
114
115 #[error("dns key value unknown, must be 3: {0}")]
117 DnsKeyProtocolNot3(u8),
118
119 #[error("name label data exceed 255: {0}")]
121 DomainNameTooLong(usize),
122
123 #[error("edns resource record label must be the root label (.): {0}")]
125 EdnsNameNotRoot(crate::rr::Name),
126
127 #[error("message format error: {error}")]
129 FormError {
130 header: Header,
132 error: Box<ProtoError>,
134 },
135
136 #[error("incorrect rdata length read: {read} expected: {len}")]
138 IncorrectRDataLengthRead {
139 read: usize,
141 len: usize,
143 },
144
145 #[error("label bytes exceed 63: {0}")]
147 LabelBytesTooLong(usize),
148
149 #[error("label points to data not prior to idx: {idx} ptr: {ptr}")]
151 PointerNotPriorToLabel {
152 idx: usize,
154 ptr: u16,
156 },
157
158 #[error("maximum buffer size exceeded: {0}")]
160 MaxBufferSizeExceeded(usize),
161
162 #[error("maximum record limit for {record_type} exceeded: {count} records")]
164 MaxRecordLimitExceeded {
165 count: usize,
167 record_type: RecordType,
169 },
170
171 #[error("{0}")]
173 Message(&'static str),
174
175 #[error("{0}")]
177 Msg(String),
178
179 #[error("no connections available")]
181 NoConnections,
182
183 #[error("no error specified")]
185 NoError,
186
187 #[error("not all records could be written, wrote: {count}")]
189 NotAllRecordsWritten {
190 count: usize,
192 },
193
194 #[error("no records found for {:?}", query)]
196 NoRecordsFound {
197 query: Box<Query>,
199 soa: Option<Box<Record<SOA>>>,
201 ns: Option<Arc<[ForwardNSData]>>,
204 negative_ttl: Option<u32>,
207 response_code: ResponseCode,
210 trusted: bool,
212 authorities: Option<Arc<[Record]>>,
214 },
215
216 #[error("algorithm type value unknown: {0}")]
218 UnknownAlgorithmTypeValue(u8),
219
220 #[error("digest type value unknown: {0}")]
222 UnknownDigestTypeValue(u8),
223
224 #[error("dns class string unknown: {0}")]
226 UnknownDnsClassStr(String),
227
228 #[error("dns class value unknown: {0}")]
230 UnknownDnsClassValue(u16),
231
232 #[error("record type string unknown: {0}")]
234 UnknownRecordTypeStr(String),
235
236 #[error("record type value unknown: {0}")]
238 UnknownRecordTypeValue(u16),
239
240 #[error("unrecognized label code: {0:b}")]
242 UnrecognizedLabelCode(u8),
243
244 #[error("nsec3 flags should be 0b0000000*: {0:b}")]
246 UnrecognizedNsec3Flags(u8),
247
248 #[error("csync flags should be 0b000000**: {0:b}")]
250 UnrecognizedCsyncFlags(u16),
251
252 #[cfg(feature = "std")]
255 #[error("io error: {0}")]
256 Io(Arc<io::Error>),
257
258 #[error("lock poisoned error")]
260 Poisoned,
261
262 #[error("request refused")]
264 RequestRefused,
265
266 #[cfg(feature = "__dnssec")]
268 #[error("ring error: {0}")]
269 Ring(#[from] Unspecified),
270
271 #[error("timer error")]
273 Timer,
274
275 #[error("request timed out")]
277 Timeout,
278
279 #[error("url parsing error")]
281 UrlParsing(#[from] url::ParseError),
282
283 #[error("error parsing utf8 string")]
285 Utf8(#[from] core::str::Utf8Error),
286
287 #[error("error parsing utf8 string")]
289 FromUtf8(#[from] alloc::string::FromUtf8Error),
290
291 #[error("error parsing int")]
293 ParseInt(#[from] core::num::ParseIntError),
294
295 #[cfg(feature = "__quic")]
297 #[error("error creating quic connection: {0}")]
298 QuinnConnect(#[from] quinn::ConnectError),
299
300 #[cfg(feature = "__quic")]
302 #[error("error with quic connection: {0}")]
303 QuinnConnection(#[from] quinn::ConnectionError),
304
305 #[cfg(feature = "__quic")]
307 #[error("error writing to quic connection: {0}")]
308 QuinnWriteError(#[from] quinn::WriteError),
309
310 #[cfg(feature = "__quic")]
312 #[error("error writing to quic read: {0}")]
313 QuinnReadError(#[from] quinn::ReadExactError),
314
315 #[cfg(feature = "__quic")]
317 #[error("referenced a closed QUIC stream: {0}")]
318 QuinnStreamError(#[from] quinn::ClosedStream),
319
320 #[cfg(feature = "__quic")]
322 #[error("error constructing quic configuration: {0}")]
323 QuinnConfigError(#[from] quinn::ConfigError),
324
325 #[cfg(feature = "__quic")]
327 #[error("QUIC TLS config must include an AES-128-GCM cipher suite")]
328 QuinnTlsConfigError(#[from] quinn::crypto::rustls::NoInitialCipherSuite),
329
330 #[cfg(feature = "__quic")]
332 #[error("an unknown quic stream was used")]
333 QuinnUnknownStreamError,
334
335 #[cfg(feature = "__quic")]
337 #[error("quic messages should always be 0, got: {0}")]
338 QuicMessageIdNot0(u16),
339
340 #[cfg(feature = "__tls")]
342 #[error("rustls construction error: {0}")]
343 RustlsError(#[from] rustls::Error),
344
345 #[error("case of query name in response did not match")]
348 QueryCaseMismatch,
349}
350
351#[derive(Clone, Debug)]
353pub struct ForwardData {
354 pub query: Box<Query>,
356 pub name: Name,
358 pub soa: Box<Record<SOA>>,
360 no_records_found: bool,
362 nx_domain: bool,
364 pub authorities: Option<Arc<[Record]>>,
366}
367
368impl ForwardData {
369 pub fn new(
371 query: Box<Query>,
372 name: Name,
373 soa: Box<Record<SOA>>,
374 no_records_found: bool,
375 nx_domain: bool,
376 authorities: Option<Arc<[Record]>>,
377 ) -> Self {
378 Self {
379 query,
380 name,
381 soa,
382 no_records_found,
383 nx_domain,
384 authorities,
385 }
386 }
387
388 pub fn is_no_records_found(&self) -> bool {
390 self.no_records_found
391 }
392
393 pub fn is_nx_domain(&self) -> bool {
395 self.nx_domain
396 }
397}
398
399#[derive(Clone, Debug)]
401pub struct ForwardNSData {
402 pub ns: Record,
404 pub glue: Arc<[Record]>,
406}
407
408#[derive(Error, Clone, Debug)]
410#[non_exhaustive]
411pub struct ProtoError {
412 pub kind: Box<ProtoErrorKind>,
414 #[cfg(feature = "backtrace")]
416 pub backtrack: Option<ExtBacktrace>,
417}
418
419impl ProtoError {
420 #[inline]
422 pub fn nx_error(
423 query: Box<Query>,
424 soa: Option<Box<Record<SOA>>>,
425 ns: Option<Arc<[ForwardNSData]>>,
426 negative_ttl: Option<u32>,
427 response_code: ResponseCode,
428 trusted: bool,
429 authorities: Option<Arc<[Record]>>,
430 ) -> Self {
431 ProtoErrorKind::NoRecordsFound {
432 query,
433 soa,
434 ns,
435 negative_ttl,
436 response_code,
437 trusted,
438 authorities,
439 }
440 .into()
441 }
442
443 #[inline]
445 pub fn kind(&self) -> &ProtoErrorKind {
446 &self.kind
447 }
448
449 #[inline]
451 pub fn is_busy(&self) -> bool {
452 matches!(*self.kind, ProtoErrorKind::Busy)
453 }
454
455 #[inline]
457 pub fn is_no_connections(&self) -> bool {
458 matches!(*self.kind, ProtoErrorKind::NoConnections)
459 }
460
461 #[inline]
463 pub fn is_nx_domain(&self) -> bool {
464 matches!(
465 *self.kind,
466 ProtoErrorKind::NoRecordsFound {
467 response_code: ResponseCode::NXDomain,
468 ..
469 }
470 )
471 }
472
473 #[inline]
475 pub fn is_no_records_found(&self) -> bool {
476 matches!(*self.kind, ProtoErrorKind::NoRecordsFound { .. })
477 }
478
479 #[inline]
481 pub fn into_soa(self) -> Option<Box<Record<SOA>>> {
482 match *self.kind {
483 ProtoErrorKind::NoRecordsFound { soa, .. } => soa,
484 _ => None,
485 }
486 }
487
488 #[inline]
490 #[cfg(feature = "std")]
491 pub fn is_io(&self) -> bool {
492 matches!(*self.kind, ProtoErrorKind::Io(..))
493 }
494
495 #[cfg(feature = "std")]
496 pub(crate) fn as_dyn(&self) -> &(dyn std::error::Error + 'static) {
497 self
498 }
499
500 pub fn from_response(response: DnsResponse, trust_nx: bool) -> Result<DnsResponse, Self> {
502 use ResponseCode::*;
503 debug!("response: {}", *response);
504
505 match response.response_code() {
506 code @ ServFail
507 | code @ Refused
508 | code @ FormErr
509 | code @ NotImp
510 | code @ YXDomain
511 | code @ YXRRSet
512 | code @ NXRRSet
513 | code @ NotAuth
514 | code @ NotZone
515 | code @ BADVERS
516 | code @ BADSIG
517 | code @ BADKEY
518 | code @ BADTIME
519 | code @ BADMODE
520 | code @ BADNAME
521 | code @ BADALG
522 | code @ BADTRUNC
523 | code @ BADCOOKIE => {
524 let soa = response.soa().as_ref().map(RecordRef::to_owned);
525 let query = response.queries().iter().next().cloned().unwrap_or_default();
526 let error_kind = ProtoErrorKind::NoRecordsFound {
527 query: Box::new(query),
528 ns: None,
529 soa: soa.map(Box::new),
530 negative_ttl: None,
531 response_code: code,
532 trusted: false,
535 authorities: None,
536 };
537
538 Err(Self::from(error_kind))
539 }
540 code @ NXDomain |
542 code @ NoError
544 if !response.contains_answer() && !response.truncated() => {
545 let soa = response.soa().as_ref().map(RecordRef::to_owned);
548
549 let mut referral_name_servers = vec![];
551 for ns in response.name_servers().iter().filter(|ns| ns.record_type() == RecordType::NS) {
552 let glue = response
553 .additionals()
554 .iter()
555 .filter_map(|record| {
556 if let Some(ns_data) = ns.data().as_ns() {
557 if *record.name() == **ns_data &&
558 (record.data().as_a().is_some() || record.data().as_aaaa().is_some()) {
559 return Some(Record::to_owned(record));
560 }
561 }
562
563 None
564 })
565 .collect::<Vec<Record>>();
566 referral_name_servers.push(ForwardNSData { ns: Record::to_owned(ns), glue: glue.into() })
567 }
568
569 let option_ns = if !referral_name_servers.is_empty() {
570 Some(referral_name_servers.into())
571 } else {
572 None
573 };
574
575 let authorities = if ! response.name_servers().is_empty() {
576 Some(response.name_servers().to_owned().into())
577 } else {
578 None
579 };
580
581 let negative_ttl = response.negative_ttl();
582 let trusted = trust_nx && soa.is_some();
587 let query = response.into_message().take_queries().drain(..).next().unwrap_or_default();
588
589 let error_kind = ProtoErrorKind::NoRecordsFound {
590 query: Box::new(query),
591 soa: soa.map(Box::new),
592 ns: option_ns,
593 negative_ttl,
594 response_code: code,
595 trusted,
596 authorities,
597 };
598
599 Err(Self::from(error_kind))
600 }
601 NXDomain
602 | NoError
603 | Unknown(_) => Ok(response),
604 }
605 }
606
607 pub fn cmp_specificity(&self, other: &Self) -> Ordering {
609 let kind = self.kind();
610 let other = other.kind();
611
612 match (kind, other) {
613 (ProtoErrorKind::NoRecordsFound { .. }, ProtoErrorKind::NoRecordsFound { .. }) => {
614 return Ordering::Equal;
615 }
616 (ProtoErrorKind::NoRecordsFound { .. }, _) => return Ordering::Greater,
617 (_, ProtoErrorKind::NoRecordsFound { .. }) => return Ordering::Less,
618 _ => (),
619 }
620
621 match (kind, other) {
622 #[cfg(feature = "std")]
623 (ProtoErrorKind::Io { .. }, ProtoErrorKind::Io { .. }) => return Ordering::Equal,
624 #[cfg(feature = "std")]
625 (ProtoErrorKind::Io { .. }, _) => return Ordering::Greater,
626 #[cfg(feature = "std")]
627 (_, ProtoErrorKind::Io { .. }) => return Ordering::Less,
628 _ => (),
629 }
630
631 match (kind, other) {
632 (ProtoErrorKind::Timeout, ProtoErrorKind::Timeout) => return Ordering::Equal,
633 (ProtoErrorKind::Timeout, _) => return Ordering::Greater,
634 (_, ProtoErrorKind::Timeout) => return Ordering::Less,
635 _ => (),
636 }
637
638 Ordering::Equal
639 }
640}
641
642impl fmt::Display for ProtoError {
643 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
644 cfg_if::cfg_if! {
645 if #[cfg(feature = "backtrace")] {
646 if let Some(backtrace) = &self.backtrack {
647 fmt::Display::fmt(&self.kind, f)?;
648 fmt::Debug::fmt(backtrace, f)
649 } else {
650 fmt::Display::fmt(&self.kind, f)
651 }
652 } else {
653 fmt::Display::fmt(&self.kind, f)
654 }
655 }
656 }
657}
658
659impl<E> From<E> for ProtoError
660where
661 E: Into<ProtoErrorKind>,
662{
663 fn from(error: E) -> Self {
664 let kind: ProtoErrorKind = error.into();
665
666 Self {
667 kind: Box::new(kind),
668 #[cfg(feature = "backtrace")]
669 backtrack: trace!(),
670 }
671 }
672}
673
674impl From<DecodeError> for ProtoError {
675 fn from(err: DecodeError) -> Self {
676 match err {
677 DecodeError::PointerNotPriorToLabel { idx, ptr } => {
678 ProtoErrorKind::PointerNotPriorToLabel { idx, ptr }
679 }
680 DecodeError::LabelBytesTooLong(len) => ProtoErrorKind::LabelBytesTooLong(len),
681 DecodeError::UnrecognizedLabelCode(code) => ProtoErrorKind::UnrecognizedLabelCode(code),
682 DecodeError::DomainNameTooLong(len) => ProtoErrorKind::DomainNameTooLong(len),
683 DecodeError::LabelOverlapsWithOther { label, other } => {
684 ProtoErrorKind::LabelOverlapsWithOther { label, other }
685 }
686 _ => ProtoErrorKind::Msg(err.to_string()),
687 }
688 .into()
689 }
690}
691
692impl From<&'static str> for ProtoError {
693 fn from(msg: &'static str) -> Self {
694 ProtoErrorKind::Message(msg).into()
695 }
696}
697
698impl From<String> for ProtoError {
699 fn from(msg: String) -> Self {
700 ProtoErrorKind::Msg(msg).into()
701 }
702}
703
704#[cfg(feature = "std")]
705impl From<io::Error> for ProtoErrorKind {
706 fn from(e: io::Error) -> Self {
707 match e.kind() {
708 io::ErrorKind::TimedOut => Self::Timeout,
709 _ => Self::Io(e.into()),
710 }
711 }
712}
713
714#[cfg(feature = "std")]
715impl<T> From<sync::PoisonError<T>> for ProtoError {
716 fn from(_e: sync::PoisonError<T>) -> Self {
717 ProtoErrorKind::Poisoned.into()
718 }
719}
720
721#[cfg(feature = "std")]
722impl From<ProtoError> for io::Error {
723 fn from(e: ProtoError) -> Self {
724 match e.kind() {
725 ProtoErrorKind::Timeout => Self::new(io::ErrorKind::TimedOut, e),
726 _ => Self::new(io::ErrorKind::Other, e),
727 }
728 }
729}
730
731impl From<ProtoError> for String {
732 fn from(e: ProtoError) -> Self {
733 e.to_string()
734 }
735}
736
737#[cfg(feature = "wasm-bindgen")]
738impl From<ProtoError> for wasm_bindgen_crate::JsValue {
739 fn from(e: ProtoError) -> Self {
740 js_sys::Error::new(&e.to_string()).into()
741 }
742}
743
744impl Clone for ProtoErrorKind {
745 fn clone(&self) -> Self {
746 use self::ProtoErrorKind::*;
747 match *self {
748 BadQueryCount(count) => BadQueryCount(count),
749 Busy => Busy,
750 Canceled(ref c) => Canceled(*c),
751 CharacterDataTooLong { max, len } => CharacterDataTooLong { max, len },
752 LabelOverlapsWithOther { label, other } => LabelOverlapsWithOther { label, other },
753 DnsKeyProtocolNot3(protocol) => DnsKeyProtocolNot3(protocol),
754 DomainNameTooLong(len) => DomainNameTooLong(len),
755 EdnsNameNotRoot(ref found) => EdnsNameNotRoot(found.clone()),
756 FormError { header, ref error } => FormError {
757 header,
758 error: error.clone(),
759 },
760 IncorrectRDataLengthRead { read, len } => IncorrectRDataLengthRead { read, len },
761 LabelBytesTooLong(len) => LabelBytesTooLong(len),
762 PointerNotPriorToLabel { idx, ptr } => PointerNotPriorToLabel { idx, ptr },
763 MaxBufferSizeExceeded(max) => MaxBufferSizeExceeded(max),
764 MaxRecordLimitExceeded { count, record_type } => {
765 MaxRecordLimitExceeded { count, record_type }
766 }
767 Message(msg) => Message(msg),
768 Msg(ref msg) => Msg(msg.clone()),
769 NoConnections => NoConnections,
770 NoError => NoError,
771 NotAllRecordsWritten { count } => NotAllRecordsWritten { count },
772 NoRecordsFound {
773 ref query,
774 ref soa,
775 ref ns,
776 negative_ttl,
777 response_code,
778 trusted,
779 ref authorities,
780 } => NoRecordsFound {
781 query: query.clone(),
782 soa: soa.clone(),
783 ns: ns.clone(),
784 negative_ttl,
785 response_code,
786 trusted,
787 authorities: authorities.clone(),
788 },
789 RequestRefused => RequestRefused,
790 #[cfg(feature = "__dnssec")]
791 Nsec { ref query, proof } => Nsec {
792 query: query.clone(),
793 proof,
794 },
795 UnknownAlgorithmTypeValue(value) => UnknownAlgorithmTypeValue(value),
796 UnknownDigestTypeValue(value) => UnknownDigestTypeValue(value),
797 UnknownDnsClassStr(ref value) => UnknownDnsClassStr(value.clone()),
798 UnknownDnsClassValue(value) => UnknownDnsClassValue(value),
799 UnknownRecordTypeStr(ref value) => UnknownRecordTypeStr(value.clone()),
800 UnknownRecordTypeValue(value) => UnknownRecordTypeValue(value),
801 UnrecognizedLabelCode(value) => UnrecognizedLabelCode(value),
802 UnrecognizedNsec3Flags(flags) => UnrecognizedNsec3Flags(flags),
803 UnrecognizedCsyncFlags(flags) => UnrecognizedCsyncFlags(flags),
804 #[cfg(feature = "std")]
805 Io(ref e) => Io(e.clone()),
806 Poisoned => Poisoned,
807 #[cfg(feature = "__dnssec")]
808 Ring(ref _e) => Ring(Unspecified),
809 Timeout => Timeout,
810 Timer => Timer,
811 UrlParsing(ref e) => UrlParsing(*e),
812 Utf8(ref e) => Utf8(*e),
813 FromUtf8(ref e) => FromUtf8(e.clone()),
814 ParseInt(ref e) => ParseInt(e.clone()),
815 #[cfg(feature = "__quic")]
816 QuinnConnect(ref e) => QuinnConnect(e.clone()),
817 #[cfg(feature = "__quic")]
818 QuinnConnection(ref e) => QuinnConnection(e.clone()),
819 #[cfg(feature = "__quic")]
820 QuinnWriteError(ref e) => QuinnWriteError(e.clone()),
821 #[cfg(feature = "__quic")]
822 QuicMessageIdNot0(val) => QuicMessageIdNot0(val),
823 #[cfg(feature = "__quic")]
824 QuinnReadError(ref e) => QuinnReadError(e.clone()),
825 #[cfg(feature = "__quic")]
826 QuinnStreamError(ref e) => QuinnStreamError(e.clone()),
827 #[cfg(feature = "__quic")]
828 QuinnConfigError(ref e) => QuinnConfigError(e.clone()),
829 #[cfg(feature = "__quic")]
830 QuinnTlsConfigError(ref e) => QuinnTlsConfigError(e.clone()),
831 #[cfg(feature = "__quic")]
832 QuinnUnknownStreamError => QuinnUnknownStreamError,
833 #[cfg(feature = "__tls")]
834 RustlsError(ref e) => RustlsError(e.clone()),
835 QueryCaseMismatch => QueryCaseMismatch,
836 }
837 }
838}