hickory_recursor/
error.rs

1// Copyright 2015-2020 Benjamin Fry <benjaminfry@me.com>
2//
3// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
4// https://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
5// https://opensource.org/licenses/MIT>, at your option. This file may not be
6// copied, modified, or distributed except according to those terms.
7
8//! Error types for the crate
9
10#![deny(missing_docs)]
11
12use std::{fmt, io, sync::Arc};
13
14use enum_as_inner::EnumAsInner;
15use thiserror::Error;
16use tracing::warn;
17
18#[cfg(feature = "backtrace")]
19use crate::proto::{ExtBacktrace, trace};
20use crate::proto::{
21    ForwardNSData, ProtoErrorKind,
22    op::ResponseCode,
23    rr::{Name, Record, rdata::SOA},
24    {ForwardData, ProtoError},
25};
26use crate::resolver::ResolveError;
27
28/// The error kind for errors that get returned in the crate
29#[derive(Debug, EnumAsInner, Error)]
30#[non_exhaustive]
31pub enum ErrorKind {
32    /// An error with an arbitrary message, referenced as &'static str
33    #[error("{0}")]
34    Message(&'static str),
35
36    /// An error with an arbitrary message, stored as String
37    #[error("{0}")]
38    Msg(String),
39
40    /// Upstream DNS authority returned a Referral to another nameserver in the form of an SOA record
41    #[error("forward response")]
42    Forward(ForwardData),
43
44    /// Upstream DNS authority returned a referral to another set of nameservers in the form of
45    /// additional NS records.
46    #[error("forward NS Response")]
47    ForwardNS(Arc<[ForwardNSData]>),
48
49    /// An error got returned from IO
50    #[error("io error: {0}")]
51    Io(#[from] std::io::Error),
52
53    /// An error got returned by the hickory-proto crate
54    #[error("proto error: {0}")]
55    Proto(#[from] ProtoError),
56
57    /// An error got returned by the hickory-proto crate
58    #[error("proto error: {0}")]
59    Resolve(ResolveError),
60
61    /// A request timed out
62    #[error("request timed out")]
63    Timeout,
64
65    /// Could not fetch all records because a recursion limit was exceeded
66    #[error("maximum recursion limit exceeded: {count} queries")]
67    RecursionLimitExceeded {
68        /// Number of queries that were made
69        count: usize,
70    },
71}
72
73/// The error type for errors that get returned in the crate
74#[derive(Error, Clone, Debug)]
75#[non_exhaustive]
76pub struct Error {
77    /// Kind of error that occurred
78    pub kind: Box<ErrorKind>,
79    /// Backtrace to the source of the error
80    #[cfg(feature = "backtrace")]
81    pub backtrack: Option<ExtBacktrace>,
82}
83
84impl Error {
85    /// Get the kind of the error
86    pub fn kind(&self) -> &ErrorKind {
87        &self.kind
88    }
89
90    /// Take kind from the Error
91    pub fn into_kind(self) -> ErrorKind {
92        *self.kind
93    }
94
95    /// Returns true if the domain does not exist
96    pub fn is_nx_domain(&self) -> bool {
97        match &*self.kind {
98            ErrorKind::Proto(proto) => proto.is_nx_domain(),
99            ErrorKind::Resolve(err) => err.is_nx_domain(),
100            ErrorKind::Forward(fwd) => fwd.is_nx_domain(),
101            _ => false,
102        }
103    }
104
105    /// Returns true if no records were returned
106    pub fn is_no_records_found(&self) -> bool {
107        match &*self.kind {
108            ErrorKind::Proto(proto) => proto.is_no_records_found(),
109            ErrorKind::Resolve(err) => err.is_no_records_found(),
110            ErrorKind::Forward(fwd) => fwd.is_no_records_found(),
111            _ => false,
112        }
113    }
114
115    /// Returns true if a query timed out
116    pub fn is_timeout(&self) -> bool {
117        let proto_error = match &*self.kind {
118            ErrorKind::Proto(proto) => proto,
119            ErrorKind::Resolve(err) => match err.kind() {
120                hickory_resolver::ResolveErrorKind::Proto(proto) => proto,
121                _ => return false,
122            },
123            _ => return false,
124        };
125        matches!(proto_error.kind(), ProtoErrorKind::Timeout)
126    }
127
128    /// Returns the SOA record, if the error contains one
129    pub fn into_soa(self) -> Option<Box<Record<SOA>>> {
130        match *self.kind {
131            ErrorKind::Proto(proto) => proto.into_soa(),
132            ErrorKind::Resolve(err) => err.into_soa(),
133            ErrorKind::Forward(fwd) => Some(fwd.soa),
134            _ => None,
135        }
136    }
137
138    /// Return additional records
139    pub fn authorities(self) -> Option<Arc<[Record]>> {
140        match *self.kind {
141            ErrorKind::Forward(fwd) => fwd.authorities,
142            _ => None,
143        }
144    }
145
146    /// Test if the recursion depth has been exceeded, and return an error if it has.
147    pub fn recursion_exceeded(limit: Option<u8>, depth: u8, name: &Name) -> Result<(), Error> {
148        match limit {
149            Some(limit) if depth > limit => {}
150            _ => return Ok(()),
151        }
152
153        warn!("recursion depth exceeded for {name}");
154        Err(ErrorKind::RecursionLimitExceeded {
155            count: depth as usize,
156        }
157        .into())
158    }
159}
160
161impl fmt::Display for Error {
162    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
163        cfg_if::cfg_if! {
164            if #[cfg(feature = "backtrace")] {
165                if let Some(backtrace) = &self.backtrack {
166                    fmt::Display::fmt(&self.kind, f)?;
167                    fmt::Debug::fmt(backtrace, f)
168                } else {
169                    fmt::Display::fmt(&self.kind, f)
170                }
171            } else {
172                fmt::Display::fmt(&self.kind, f)
173            }
174        }
175    }
176}
177
178impl<E> From<E> for Error
179where
180    E: Into<ErrorKind>,
181{
182    fn from(error: E) -> Self {
183        let kind: ErrorKind = error.into();
184
185        Self {
186            kind: Box::new(kind),
187            #[cfg(feature = "backtrace")]
188            backtrack: trace!(),
189        }
190    }
191}
192
193impl From<&'static str> for Error {
194    fn from(msg: &'static str) -> Self {
195        ErrorKind::Message(msg).into()
196    }
197}
198
199impl From<String> for Error {
200    fn from(msg: String) -> Self {
201        ErrorKind::Msg(msg).into()
202    }
203}
204
205impl From<Error> for io::Error {
206    fn from(e: Error) -> Self {
207        match e.kind() {
208            ErrorKind::Timeout => Self::new(io::ErrorKind::TimedOut, e),
209            _ => Self::new(io::ErrorKind::Other, e),
210        }
211    }
212}
213
214impl From<Error> for String {
215    fn from(e: Error) -> Self {
216        e.to_string()
217    }
218}
219
220impl From<ResolveError> for Error {
221    fn from(e: ResolveError) -> Self {
222        let nx_domain = e.is_nx_domain();
223        let no_records_found = e.is_no_records_found();
224
225        let proto_err = match ProtoErrorKind::try_from(e) {
226            Ok(res) => res,
227            Err(e) => return ErrorKind::Resolve(e).into(),
228        };
229
230        let ProtoErrorKind::NoRecordsFound {
231            query,
232            soa,
233            ns,
234            authorities,
235            ..
236        } = proto_err
237        else {
238            return ErrorKind::Proto(proto_err.into()).into();
239        };
240
241        if let Some(ns) = ns {
242            ErrorKind::ForwardNS(ns).into()
243        } else if let Some(soa) = soa {
244            ErrorKind::Forward(ForwardData::new(
245                query,
246                soa.name().clone(),
247                soa,
248                no_records_found,
249                nx_domain,
250                authorities,
251            ))
252            .into()
253        } else {
254            ErrorKind::Message("proto error missing ns and soa").into()
255        }
256    }
257}
258
259impl Clone for ErrorKind {
260    fn clone(&self) -> Self {
261        use self::ErrorKind::*;
262        match self {
263            Message(msg) => Message(msg),
264            Msg(msg) => Msg(msg.clone()),
265            Forward(ns) => Forward(ns.clone()),
266            ForwardNS(ns) => ForwardNS(ns.clone()),
267            Io(io) => Io(std::io::Error::from(io.kind())),
268            Proto(proto) => Proto(proto.clone()),
269            Resolve(resolve) => Resolve(resolve.clone()),
270            Timeout => Self::Timeout,
271            RecursionLimitExceeded { count } => RecursionLimitExceeded { count: *count },
272        }
273    }
274}
275
276impl From<Error> for ProtoError {
277    fn from(e: Error) -> Self {
278        let is_nx_domain = e.is_nx_domain();
279        match *e.kind {
280            ErrorKind::Forward(fwd) => ProtoError::nx_error(
281                fwd.query,
282                Some(fwd.soa),
283                None,
284                None,
285                if is_nx_domain {
286                    ResponseCode::NXDomain
287                } else {
288                    ResponseCode::NoError
289                },
290                true,
291                fwd.authorities,
292            ),
293            _ => ProtoError::from(e.to_string()),
294        }
295    }
296}