hickory_resolver/
error.rs

1// Copyright 2015-2023 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
10use std::{cmp::Ordering, fmt, io, sync};
11
12use thiserror::Error;
13use tracing::debug;
14
15use crate::proto::{
16    error::{ProtoError, ProtoErrorKind},
17    op::{Query, ResponseCode},
18    rr::{
19        rdata::SOA,
20        resource::{Record, RecordRef},
21    },
22    xfer::{retry_dns_handle::RetryableError, DnsResponse},
23};
24
25#[cfg(feature = "backtrace")]
26use crate::proto::{trace, ExtBacktrace};
27
28/// An alias for results returned by functions of this crate
29pub type ResolveResult<T> = ::std::result::Result<T, ResolveError>;
30
31#[allow(clippy::large_enum_variant)]
32/// The error kind for errors that get returned in the crate
33#[derive(Debug, Error)]
34#[non_exhaustive]
35pub enum ResolveErrorKind {
36    /// An error with an arbitrary message, referenced as &'static str
37    #[error("{0}")]
38    Message(&'static str),
39
40    /// An error with an arbitrary message, stored as String
41    #[error("{0}")]
42    Msg(String),
43
44    /// No resolvers available
45    #[error("No connections available")]
46    NoConnections,
47
48    /// No records were found for a query
49    #[error("no record found for {:?}", query)]
50    NoRecordsFound {
51        /// The query for which no records were found.
52        query: Box<Query>,
53        /// If an SOA is present, then this is an authoritative response or a referral to another nameserver, see the negative_type field.
54        soa: Option<Box<Record<SOA>>>,
55        /// negative ttl, as determined from DnsResponse::negative_ttl
56        ///  this will only be present if the SOA was also present.
57        negative_ttl: Option<u32>,
58        /// ResponseCode, if `NXDOMAIN`, the domain does not exist (and no other types).
59        ///   If `NoError`, then the domain exists but there exist either other types at the same label, or subzones of that label.
60        response_code: ResponseCode,
61        /// If we trust `NXDOMAIN` errors from this server
62        trusted: bool,
63    },
64
65    // foreign
66    /// An error got returned from IO
67    #[error("io error: {0}")]
68    Io(#[from] std::io::Error),
69
70    /// An error got returned by the hickory-proto crate
71    #[error("proto error: {0}")]
72    Proto(#[from] ProtoError),
73
74    /// A request timed out
75    #[error("request timed out")]
76    Timeout,
77}
78
79impl Clone for ResolveErrorKind {
80    fn clone(&self) -> Self {
81        use self::ResolveErrorKind::*;
82        match self {
83            NoConnections => NoConnections,
84            Message(msg) => Message(msg),
85            Msg(ref msg) => Msg(msg.clone()),
86            NoRecordsFound {
87                ref query,
88                ref soa,
89                negative_ttl,
90                response_code,
91                trusted,
92            } => NoRecordsFound {
93                query: query.clone(),
94                soa: soa.clone(),
95                negative_ttl: *negative_ttl,
96                response_code: *response_code,
97                trusted: *trusted,
98            },
99            // foreign
100            Io(io) => Self::from(std::io::Error::from(io.kind())),
101            Proto(proto) => Self::from(proto.clone()),
102            Timeout => Timeout,
103        }
104    }
105}
106
107/// The error type for errors that get returned in the crate
108#[derive(Debug, Clone, Error)]
109pub struct ResolveError {
110    pub(crate) kind: ResolveErrorKind,
111    #[cfg(feature = "backtrace")]
112    backtrack: Option<ExtBacktrace>,
113}
114
115impl ResolveError {
116    pub(crate) fn nx_error(
117        query: Query,
118        soa: Option<Record<SOA>>,
119        negative_ttl: Option<u32>,
120        response_code: ResponseCode,
121        trusted: bool,
122    ) -> Self {
123        ResolveErrorKind::NoRecordsFound {
124            query: Box::new(query),
125            soa: soa.map(Box::new),
126            negative_ttl,
127            response_code,
128            trusted,
129        }
130        .into()
131    }
132
133    /// Get the kind of the error
134    pub fn kind(&self) -> &ResolveErrorKind {
135        &self.kind
136    }
137
138    pub(crate) fn no_connections() -> Self {
139        Self {
140            kind: ResolveErrorKind::NoConnections,
141            #[cfg(feature = "backtrace")]
142            backtrack: trace!(),
143        }
144    }
145
146    pub(crate) fn is_no_connections(&self) -> bool {
147        matches!(self.kind, ResolveErrorKind::NoConnections)
148    }
149
150    /// A conversion to determine if the response is an error
151    pub fn from_response(response: DnsResponse, trust_nx: bool) -> Result<DnsResponse, Self> {
152        debug!("Response:{}", *response);
153
154        match response.response_code() {
155            response_code @ ResponseCode::ServFail
156            | response_code @ ResponseCode::Refused
157            | response_code @ ResponseCode::FormErr
158            | response_code @ ResponseCode::NotImp
159            | response_code @ ResponseCode::YXDomain
160            | response_code @ ResponseCode::YXRRSet
161            | response_code @ ResponseCode::NXRRSet
162            | response_code @ ResponseCode::NotAuth
163            | response_code @ ResponseCode::NotZone
164            | response_code @ ResponseCode::BADVERS
165            | response_code @ ResponseCode::BADSIG
166            | response_code @ ResponseCode::BADKEY
167            | response_code @ ResponseCode::BADTIME
168            | response_code @ ResponseCode::BADMODE
169            | response_code @ ResponseCode::BADNAME
170            | response_code @ ResponseCode::BADALG
171            | response_code @ ResponseCode::BADTRUNC
172            | response_code @ ResponseCode::BADCOOKIE => {
173                let soa = response.soa().as_ref().map(RecordRef::to_owned);
174                let query = response.queries().iter().next().cloned().unwrap_or_default();
175                let error_kind = ResolveErrorKind::NoRecordsFound {
176                    query: Box::new(query),
177                    soa: soa.map(Box::new),
178                    negative_ttl: None,
179                    response_code,
180                    trusted: false,
181                };
182
183                Err(Self::from(error_kind))
184            }
185            // Some NXDOMAIN responses contain CNAME referrals, that will not be an error
186            response_code @ ResponseCode::NXDomain |
187            // No answers are available, CNAME referrals are not failures
188            response_code @ ResponseCode::NoError
189            if !response.contains_answer() && !response.truncated() => {
190                // TODO: if authoritative, this is cacheable, store a TTL (currently that requires time, need a "now" here)
191                // let valid_until = if response.authoritative() { now + response.negative_ttl() };
192
193                let soa = response.soa().as_ref().map(RecordRef::to_owned);
194                let negative_ttl = response.negative_ttl();
195                // Note: improperly configured servers may do recursive lookups and return bad SOA
196                // records here via AS112 (blackhole-1.iana.org. etc)
197                // Such servers should be marked not trusted, as they may break reverse lookups
198                // for local hosts.
199                let trusted = trust_nx && soa.is_some();
200                let query = response.into_message().take_queries().drain(..).next().unwrap_or_default();
201                let error_kind = ResolveErrorKind::NoRecordsFound {
202                    query: Box::new(query),
203                    soa: soa.map(Box::new),
204                    negative_ttl,
205                    response_code,
206                    trusted,
207                };
208
209                Err(Self::from(error_kind))
210            }
211            ResponseCode::NXDomain
212            | ResponseCode::NoError
213            | ResponseCode::Unknown(_) => Ok(response),
214        }
215    }
216
217    /// Compare two errors to see if one contains a server response.
218    pub(crate) fn cmp_specificity(&self, other: &Self) -> Ordering {
219        let kind = self.kind();
220        let other = other.kind();
221
222        match (kind, other) {
223            (ResolveErrorKind::NoRecordsFound { .. }, ResolveErrorKind::NoRecordsFound { .. }) => {
224                return Ordering::Equal
225            }
226            (ResolveErrorKind::NoRecordsFound { .. }, _) => return Ordering::Greater,
227            (_, ResolveErrorKind::NoRecordsFound { .. }) => return Ordering::Less,
228            _ => (),
229        }
230
231        match (kind, other) {
232            (ResolveErrorKind::Io { .. }, ResolveErrorKind::Io { .. }) => return Ordering::Equal,
233            (ResolveErrorKind::Io { .. }, _) => return Ordering::Greater,
234            (_, ResolveErrorKind::Io { .. }) => return Ordering::Less,
235            _ => (),
236        }
237
238        match (kind, other) {
239            (ResolveErrorKind::Proto { .. }, ResolveErrorKind::Proto { .. }) => {
240                return Ordering::Equal
241            }
242            (ResolveErrorKind::Proto { .. }, _) => return Ordering::Greater,
243            (_, ResolveErrorKind::Proto { .. }) => return Ordering::Less,
244            _ => (),
245        }
246
247        match (kind, other) {
248            (ResolveErrorKind::Timeout, ResolveErrorKind::Timeout) => return Ordering::Equal,
249            (ResolveErrorKind::Timeout, _) => return Ordering::Greater,
250            (_, ResolveErrorKind::Timeout) => return Ordering::Less,
251            _ => (),
252        }
253
254        Ordering::Equal
255    }
256}
257
258impl RetryableError for ResolveError {
259    fn should_retry(&self) -> bool {
260        match self.kind() {
261            ResolveErrorKind::Message(_)
262            | ResolveErrorKind::Msg(_)
263            | ResolveErrorKind::NoConnections
264            | ResolveErrorKind::NoRecordsFound { .. } => false,
265            ResolveErrorKind::Io(_) | ResolveErrorKind::Proto(_) | ResolveErrorKind::Timeout => {
266                true
267            }
268        }
269    }
270
271    fn attempted(&self) -> bool {
272        match self.kind() {
273            ResolveErrorKind::Proto(e) => !matches!(e.kind(), ProtoErrorKind::Busy),
274            _ => true,
275        }
276    }
277}
278
279impl fmt::Display for ResolveError {
280    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
281        cfg_if::cfg_if! {
282            if #[cfg(feature = "backtrace")] {
283                if let Some(ref backtrace) = self.backtrack {
284                    fmt::Display::fmt(&self.kind, f)?;
285                    fmt::Debug::fmt(backtrace, f)
286                } else {
287                    fmt::Display::fmt(&self.kind, f)
288                }
289            } else {
290                fmt::Display::fmt(&self.kind, f)
291            }
292        }
293    }
294}
295
296impl From<ResolveErrorKind> for ResolveError {
297    fn from(kind: ResolveErrorKind) -> Self {
298        Self {
299            kind,
300            #[cfg(feature = "backtrace")]
301            backtrack: trace!(),
302        }
303    }
304}
305
306impl From<&'static str> for ResolveError {
307    fn from(msg: &'static str) -> Self {
308        ResolveErrorKind::Message(msg).into()
309    }
310}
311
312#[cfg(target_os = "windows")]
313#[cfg(feature = "system-config")]
314#[cfg_attr(docsrs, doc(cfg(all(feature = "system-config", windows))))]
315impl From<ipconfig::error::Error> for ResolveError {
316    fn from(e: ipconfig::error::Error) -> ResolveError {
317        ResolveErrorKind::Msg(format!("failed to read from registry: {}", e)).into()
318    }
319}
320
321impl From<String> for ResolveError {
322    fn from(msg: String) -> Self {
323        ResolveErrorKind::Msg(msg).into()
324    }
325}
326
327impl From<io::Error> for ResolveError {
328    fn from(e: io::Error) -> Self {
329        match e.kind() {
330            io::ErrorKind::TimedOut => ResolveErrorKind::Timeout.into(),
331            _ => ResolveErrorKind::from(e).into(),
332        }
333    }
334}
335
336impl From<ProtoError> for ResolveError {
337    fn from(e: ProtoError) -> Self {
338        match *e.kind() {
339            ProtoErrorKind::Timeout => ResolveErrorKind::Timeout.into(),
340            _ => ResolveErrorKind::from(e).into(),
341        }
342    }
343}
344
345impl From<ResolveError> for io::Error {
346    fn from(e: ResolveError) -> Self {
347        match e.kind() {
348            ResolveErrorKind::Timeout => Self::new(io::ErrorKind::TimedOut, e),
349            _ => Self::new(io::ErrorKind::Other, e),
350        }
351    }
352}
353
354impl<T> From<sync::PoisonError<T>> for ResolveError {
355    fn from(e: sync::PoisonError<T>) -> Self {
356        ResolveErrorKind::Msg(format!("lock was poisoned, this is non-recoverable: {e}")).into()
357    }
358}