trust_dns_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// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
5// http://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 trust-dns-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 response = response;
174                let soa = response.soa().as_ref().map(RecordRef::to_owned);
175                let query = response.queries().iter().next().cloned().unwrap_or_default();
176                let error_kind = ResolveErrorKind::NoRecordsFound {
177                    query: Box::new(query),
178                    soa: soa.map(Box::new),
179                    negative_ttl: None,
180                    response_code,
181                    trusted: false,
182                };
183
184                Err(Self::from(error_kind))
185            }
186            // Some NXDOMAIN responses contain CNAME referrals, that will not be an error
187            response_code @ ResponseCode::NXDomain |
188            // No answers are available, CNAME referrals are not failures
189            response_code @ ResponseCode::NoError
190            if !response.contains_answer() && !response.truncated() => {
191                // TODO: if authoritative, this is cacheable, store a TTL (currently that requires time, need a "now" here)
192                // let valid_until = if response.authoritative() { now + response.negative_ttl() };
193
194                let  response = response;
195                let soa = response.soa().as_ref().map(RecordRef::to_owned);
196                let negative_ttl = response.negative_ttl();
197                // Note: improperly configured servers may do recursive lookups and return bad SOA
198                // records here via AS112 (blackhole-1.iana.org. etc)
199                // Such servers should be marked not trusted, as they may break reverse lookups
200                // for local hosts.
201                let trusted = trust_nx && soa.is_some();
202                let query = response.into_message().take_queries().drain(..).next().unwrap_or_default();
203                let error_kind = ResolveErrorKind::NoRecordsFound {
204                    query: Box::new(query),
205                    soa: soa.map(Box::new),
206                    negative_ttl,
207                    response_code,
208                    trusted,
209                };
210
211                Err(Self::from(error_kind))
212            }
213            ResponseCode::NXDomain
214            | ResponseCode::NoError
215            | ResponseCode::Unknown(_) => Ok(response),
216        }
217    }
218
219    /// Compare two errors to see if one contains a server response.
220    pub(crate) fn cmp_specificity(&self, other: &Self) -> Ordering {
221        let kind = self.kind();
222        let other = other.kind();
223
224        match (kind, other) {
225            (ResolveErrorKind::NoRecordsFound { .. }, ResolveErrorKind::NoRecordsFound { .. }) => {
226                return Ordering::Equal
227            }
228            (ResolveErrorKind::NoRecordsFound { .. }, _) => return Ordering::Greater,
229            (_, ResolveErrorKind::NoRecordsFound { .. }) => return Ordering::Less,
230            _ => (),
231        }
232
233        match (kind, other) {
234            (ResolveErrorKind::Io { .. }, ResolveErrorKind::Io { .. }) => return Ordering::Equal,
235            (ResolveErrorKind::Io { .. }, _) => return Ordering::Greater,
236            (_, ResolveErrorKind::Io { .. }) => return Ordering::Less,
237            _ => (),
238        }
239
240        match (kind, other) {
241            (ResolveErrorKind::Proto { .. }, ResolveErrorKind::Proto { .. }) => {
242                return Ordering::Equal
243            }
244            (ResolveErrorKind::Proto { .. }, _) => return Ordering::Greater,
245            (_, ResolveErrorKind::Proto { .. }) => return Ordering::Less,
246            _ => (),
247        }
248
249        match (kind, other) {
250            (ResolveErrorKind::Timeout, ResolveErrorKind::Timeout) => return Ordering::Equal,
251            (ResolveErrorKind::Timeout, _) => return Ordering::Greater,
252            (_, ResolveErrorKind::Timeout) => return Ordering::Less,
253            _ => (),
254        }
255
256        Ordering::Equal
257    }
258}
259
260impl RetryableError for ResolveError {
261    fn should_retry(&self) -> bool {
262        match self.kind() {
263            ResolveErrorKind::Message(_)
264            | ResolveErrorKind::Msg(_)
265            | ResolveErrorKind::NoConnections
266            | ResolveErrorKind::NoRecordsFound { .. } => false,
267            ResolveErrorKind::Io(_) | ResolveErrorKind::Proto(_) | ResolveErrorKind::Timeout => {
268                true
269            }
270        }
271    }
272
273    fn attempted(&self) -> bool {
274        match self.kind() {
275            ResolveErrorKind::Proto(e) => !matches!(e.kind(), ProtoErrorKind::Busy),
276            _ => true,
277        }
278    }
279}
280
281impl fmt::Display for ResolveError {
282    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
283        cfg_if::cfg_if! {
284            if #[cfg(feature = "backtrace")] {
285                if let Some(ref backtrace) = self.backtrack {
286                    fmt::Display::fmt(&self.kind, f)?;
287                    fmt::Debug::fmt(backtrace, f)
288                } else {
289                    fmt::Display::fmt(&self.kind, f)
290                }
291            } else {
292                fmt::Display::fmt(&self.kind, f)
293            }
294        }
295    }
296}
297
298impl From<ResolveErrorKind> for ResolveError {
299    fn from(kind: ResolveErrorKind) -> Self {
300        Self {
301            kind,
302            #[cfg(feature = "backtrace")]
303            backtrack: trace!(),
304        }
305    }
306}
307
308impl From<&'static str> for ResolveError {
309    fn from(msg: &'static str) -> Self {
310        ResolveErrorKind::Message(msg).into()
311    }
312}
313
314#[cfg(target_os = "windows")]
315#[cfg(feature = "system-config")]
316#[cfg_attr(docsrs, doc(cfg(all(feature = "system-config", windows))))]
317impl From<ipconfig::error::Error> for ResolveError {
318    fn from(e: ipconfig::error::Error) -> ResolveError {
319        ResolveErrorKind::Msg(format!("failed to read from registry: {}", e)).into()
320    }
321}
322
323impl From<String> for ResolveError {
324    fn from(msg: String) -> Self {
325        ResolveErrorKind::Msg(msg).into()
326    }
327}
328
329impl From<io::Error> for ResolveError {
330    fn from(e: io::Error) -> Self {
331        match e.kind() {
332            io::ErrorKind::TimedOut => ResolveErrorKind::Timeout.into(),
333            _ => ResolveErrorKind::from(e).into(),
334        }
335    }
336}
337
338impl From<ProtoError> for ResolveError {
339    fn from(e: ProtoError) -> Self {
340        match *e.kind() {
341            ProtoErrorKind::Timeout => ResolveErrorKind::Timeout.into(),
342            _ => ResolveErrorKind::from(e).into(),
343        }
344    }
345}
346
347impl From<ResolveError> for io::Error {
348    fn from(e: ResolveError) -> Self {
349        match e.kind() {
350            ResolveErrorKind::Timeout => Self::new(io::ErrorKind::TimedOut, e),
351            _ => Self::new(io::ErrorKind::Other, e),
352        }
353    }
354}
355
356impl<T> From<sync::PoisonError<T>> for ResolveError {
357    fn from(e: sync::PoisonError<T>) -> Self {
358        ResolveErrorKind::Msg(format!("lock was poisoned, this is non-recoverable: {e}")).into()
359    }
360}