1#![deny(missing_docs)]
11
12use std::{fmt, io, sync::Arc};
13
14use enum_as_inner::EnumAsInner;
15use thiserror::Error;
16use tracing::warn;
17
18#[cfg(feature = "backtrace")]
19use crate::proto::{ExtBacktrace, trace};
20use crate::proto::{
21 ForwardNSData, ProtoErrorKind,
22 op::ResponseCode,
23 rr::{Name, Record, rdata::SOA},
24 {ForwardData, ProtoError},
25};
26use crate::resolver::ResolveError;
27
28#[derive(Debug, EnumAsInner, Error)]
30#[non_exhaustive]
31pub enum ErrorKind {
32 #[error("{0}")]
34 Message(&'static str),
35
36 #[error("{0}")]
38 Msg(String),
39
40 #[error("forward response")]
42 Forward(ForwardData),
43
44 #[error("forward NS Response")]
47 ForwardNS(Arc<[ForwardNSData]>),
48
49 #[error("io error: {0}")]
51 Io(#[from] std::io::Error),
52
53 #[error("proto error: {0}")]
55 Proto(#[from] ProtoError),
56
57 #[error("proto error: {0}")]
59 Resolve(ResolveError),
60
61 #[error("request timed out")]
63 Timeout,
64
65 #[error("maximum recursion limit exceeded: {count} queries")]
67 RecursionLimitExceeded {
68 count: usize,
70 },
71}
72
73#[derive(Error, Clone, Debug)]
75#[non_exhaustive]
76pub struct Error {
77 pub kind: Box<ErrorKind>,
79 #[cfg(feature = "backtrace")]
81 pub backtrack: Option<ExtBacktrace>,
82}
83
84impl Error {
85 pub fn kind(&self) -> &ErrorKind {
87 &self.kind
88 }
89
90 pub fn into_kind(self) -> ErrorKind {
92 *self.kind
93 }
94
95 pub fn is_nx_domain(&self) -> bool {
97 match &*self.kind {
98 ErrorKind::Proto(proto) => proto.is_nx_domain(),
99 ErrorKind::Resolve(err) => err.is_nx_domain(),
100 ErrorKind::Forward(fwd) => fwd.is_nx_domain(),
101 _ => false,
102 }
103 }
104
105 pub fn is_no_records_found(&self) -> bool {
107 match &*self.kind {
108 ErrorKind::Proto(proto) => proto.is_no_records_found(),
109 ErrorKind::Resolve(err) => err.is_no_records_found(),
110 ErrorKind::Forward(fwd) => fwd.is_no_records_found(),
111 _ => false,
112 }
113 }
114
115 pub fn is_timeout(&self) -> bool {
117 let proto_error = match &*self.kind {
118 ErrorKind::Proto(proto) => proto,
119 ErrorKind::Resolve(err) => match err.kind() {
120 hickory_resolver::ResolveErrorKind::Proto(proto) => proto,
121 _ => return false,
122 },
123 _ => return false,
124 };
125 matches!(proto_error.kind(), ProtoErrorKind::Timeout)
126 }
127
128 pub fn into_soa(self) -> Option<Box<Record<SOA>>> {
130 match *self.kind {
131 ErrorKind::Proto(proto) => proto.into_soa(),
132 ErrorKind::Resolve(err) => err.into_soa(),
133 ErrorKind::Forward(fwd) => Some(fwd.soa),
134 _ => None,
135 }
136 }
137
138 pub fn authorities(self) -> Option<Arc<[Record]>> {
140 match *self.kind {
141 ErrorKind::Forward(fwd) => fwd.authorities,
142 _ => None,
143 }
144 }
145
146 pub fn recursion_exceeded(limit: Option<u8>, depth: u8, name: &Name) -> Result<(), Error> {
148 match limit {
149 Some(limit) if depth > limit => {}
150 _ => return Ok(()),
151 }
152
153 warn!("recursion depth exceeded for {name}");
154 Err(ErrorKind::RecursionLimitExceeded {
155 count: depth as usize,
156 }
157 .into())
158 }
159}
160
161impl fmt::Display for Error {
162 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
163 cfg_if::cfg_if! {
164 if #[cfg(feature = "backtrace")] {
165 if let Some(backtrace) = &self.backtrack {
166 fmt::Display::fmt(&self.kind, f)?;
167 fmt::Debug::fmt(backtrace, f)
168 } else {
169 fmt::Display::fmt(&self.kind, f)
170 }
171 } else {
172 fmt::Display::fmt(&self.kind, f)
173 }
174 }
175 }
176}
177
178impl<E> From<E> for Error
179where
180 E: Into<ErrorKind>,
181{
182 fn from(error: E) -> Self {
183 let kind: ErrorKind = error.into();
184
185 Self {
186 kind: Box::new(kind),
187 #[cfg(feature = "backtrace")]
188 backtrack: trace!(),
189 }
190 }
191}
192
193impl From<&'static str> for Error {
194 fn from(msg: &'static str) -> Self {
195 ErrorKind::Message(msg).into()
196 }
197}
198
199impl From<String> for Error {
200 fn from(msg: String) -> Self {
201 ErrorKind::Msg(msg).into()
202 }
203}
204
205impl From<Error> for io::Error {
206 fn from(e: Error) -> Self {
207 match e.kind() {
208 ErrorKind::Timeout => Self::new(io::ErrorKind::TimedOut, e),
209 _ => Self::new(io::ErrorKind::Other, e),
210 }
211 }
212}
213
214impl From<Error> for String {
215 fn from(e: Error) -> Self {
216 e.to_string()
217 }
218}
219
220impl From<ResolveError> for Error {
221 fn from(e: ResolveError) -> Self {
222 let nx_domain = e.is_nx_domain();
223 let no_records_found = e.is_no_records_found();
224
225 let proto_err = match ProtoErrorKind::try_from(e) {
226 Ok(res) => res,
227 Err(e) => return ErrorKind::Resolve(e).into(),
228 };
229
230 let ProtoErrorKind::NoRecordsFound {
231 query,
232 soa,
233 ns,
234 authorities,
235 ..
236 } = proto_err
237 else {
238 return ErrorKind::Proto(proto_err.into()).into();
239 };
240
241 if let Some(ns) = ns {
242 ErrorKind::ForwardNS(ns).into()
243 } else if let Some(soa) = soa {
244 ErrorKind::Forward(ForwardData::new(
245 query,
246 soa.name().clone(),
247 soa,
248 no_records_found,
249 nx_domain,
250 authorities,
251 ))
252 .into()
253 } else {
254 ErrorKind::Message("proto error missing ns and soa").into()
255 }
256 }
257}
258
259impl Clone for ErrorKind {
260 fn clone(&self) -> Self {
261 use self::ErrorKind::*;
262 match self {
263 Message(msg) => Message(msg),
264 Msg(msg) => Msg(msg.clone()),
265 Forward(ns) => Forward(ns.clone()),
266 ForwardNS(ns) => ForwardNS(ns.clone()),
267 Io(io) => Io(std::io::Error::from(io.kind())),
268 Proto(proto) => Proto(proto.clone()),
269 Resolve(resolve) => Resolve(resolve.clone()),
270 Timeout => Self::Timeout,
271 RecursionLimitExceeded { count } => RecursionLimitExceeded { count: *count },
272 }
273 }
274}
275
276impl From<Error> for ProtoError {
277 fn from(e: Error) -> Self {
278 let is_nx_domain = e.is_nx_domain();
279 match *e.kind {
280 ErrorKind::Forward(fwd) => ProtoError::nx_error(
281 fwd.query,
282 Some(fwd.soa),
283 None,
284 None,
285 if is_nx_domain {
286 ResponseCode::NXDomain
287 } else {
288 ResponseCode::NoError
289 },
290 true,
291 fwd.authorities,
292 ),
293 _ => ProtoError::from(e.to_string()),
294 }
295 }
296}