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