1use 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
24pub type ResolveResult<T> = ::std::result::Result<T, ResolveError>;
26
27#[allow(clippy::large_enum_variant)]
28#[derive(Debug, Error)]
30#[non_exhaustive]
31pub enum ResolveErrorKind {
32 #[error("{0}")]
34 Message(&'static str),
35
36 #[error("{0}")]
38 Msg(String),
39
40 #[error("No connections available")]
42 NoConnections,
43
44 #[error("no record found for {:?}", query)]
46 NoRecordsFound {
47 query: Box<Query>,
49 soa: Option<Box<Record>>,
51 negative_ttl: Option<u32>,
54 response_code: ResponseCode,
57 trusted: bool,
59 },
60
61 #[error("io error: {0}")]
64 Io(#[from] std::io::Error),
65
66 #[error("proto error: {0}")]
68 Proto(#[from] ProtoError),
69
70 #[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 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#[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 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 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 response_code @ ResponseCode::NXDomain |
184 response_code @ ResponseCode::NoError
186 if !response.contains_answer() && !response.truncated() => {
187 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 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}