1use 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
28pub type ResolveResult<T> = ::std::result::Result<T, ResolveError>;
30
31#[allow(clippy::large_enum_variant)]
32#[derive(Debug, Error)]
34#[non_exhaustive]
35pub enum ResolveErrorKind {
36 #[error("{0}")]
38 Message(&'static str),
39
40 #[error("{0}")]
42 Msg(String),
43
44 #[error("No connections available")]
46 NoConnections,
47
48 #[error("no record found for {:?}", query)]
50 NoRecordsFound {
51 query: Box<Query>,
53 soa: Option<Box<Record<SOA>>>,
55 negative_ttl: Option<u32>,
58 response_code: ResponseCode,
61 trusted: bool,
63 },
64
65 #[error("io error: {0}")]
68 Io(#[from] std::io::Error),
69
70 #[error("proto error: {0}")]
72 Proto(#[from] ProtoError),
73
74 #[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 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#[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 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 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 response_code @ ResponseCode::NXDomain |
187 response_code @ ResponseCode::NoError
189 if !response.contains_answer() && !response.truncated() => {
190 let soa = response.soa().as_ref().map(RecordRef::to_owned);
194 let negative_ttl = response.negative_ttl();
195 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 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}