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 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 response_code @ ResponseCode::NXDomain |
188 response_code @ ResponseCode::NoError
190 if !response.contains_answer() && !response.truncated() => {
191 let response = response;
195 let soa = response.soa().as_ref().map(RecordRef::to_owned);
196 let negative_ttl = response.negative_ttl();
197 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 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}