hickory_resolver/recursor/
error.rs1#![deny(missing_docs)]
11
12use std::io;
13use std::sync::Arc;
14
15use thiserror::Error;
16use tracing::warn;
17
18use crate::{
19 net::{DnsError, ForwardNSData, NetError, NoRecords},
20 proto::{
21 ProtoError,
22 op::Query,
23 op::ResponseCode,
24 rr::{Name, Record, RecordType, rdata::SOA},
25 },
26};
27
28#[derive(Debug, Error)]
30#[non_exhaustive]
31pub enum RecursorError {
32 #[error("maximum record limit for {record_type} exceeded: {count} records")]
34 MaxRecordLimitExceeded {
35 count: usize,
37 record_type: RecordType,
39 },
40
41 #[error("{0}")]
43 Message(&'static str),
44
45 #[error("{0}")]
47 Msg(String),
48
49 #[error("negative response")]
51 Negative(AuthorityData),
52
53 #[error("forward NS Response")]
56 ForwardNS(Arc<[ForwardNSData]>),
57
58 #[error("io error: {0}")]
60 Io(#[from] io::Error),
61
62 #[error("net error: {0}")]
64 Net(NetError),
65
66 #[error("request timed out")]
68 Timeout,
69
70 #[error("maximum recursion limit exceeded: {count} queries")]
72 RecursionLimitExceeded {
73 count: usize,
75 },
76}
77
78impl RecursorError {
79 pub fn recursion_exceeded(limit: u8, depth: u8, name: &Name) -> Result<(), Self> {
81 if depth < limit {
82 return Ok(());
83 }
84
85 warn!("recursion depth exceeded for {name}");
86 Err(Self::RecursionLimitExceeded {
87 count: depth as usize,
88 })
89 }
90
91 pub fn into_soa(self) -> Option<Box<Record<SOA>>> {
93 match self {
94 Self::Net(net) => net.into_soa(),
95 Self::Negative(fwd) => fwd.soa,
96 _ => None,
97 }
98 }
99
100 pub fn is_no_records_found(&self) -> bool {
102 match self {
103 Self::Net(net) => net.is_no_records_found(),
104 Self::Negative(fwd) => fwd.is_no_records_found(),
105 _ => false,
106 }
107 }
108
109 pub fn is_nx_domain(&self) -> bool {
111 match self {
112 Self::Net(net) => net.is_nx_domain(),
113 Self::Negative(fwd) => fwd.is_nx_domain(),
114 _ => false,
115 }
116 }
117
118 pub fn is_timeout(&self) -> bool {
120 match self {
121 Self::Net(net) => matches!(net, NetError::Timeout),
122 _ => false,
123 }
124 }
125}
126
127impl From<NetError> for RecursorError {
128 fn from(e: NetError) -> Self {
129 let NetError::Dns(DnsError::NoRecordsFound(no_records)) = e else {
130 return Self::Net(e);
131 };
132
133 if let Some(ns) = no_records.ns {
134 Self::ForwardNS(ns)
135 } else {
136 Self::Negative(AuthorityData::new(
137 no_records.query,
138 no_records.soa,
139 true,
140 matches!(no_records.response_code, ResponseCode::NXDomain),
141 no_records.authorities,
142 ))
143 }
144 }
145}
146
147impl From<RecursorError> for NetError {
148 fn from(e: RecursorError) -> Self {
149 match e {
150 RecursorError::Negative(fwd) => DnsError::NoRecordsFound(fwd.into()).into(),
151 _ => Self::from(e.to_string()),
152 }
153 }
154}
155
156impl From<ProtoError> for RecursorError {
157 fn from(e: ProtoError) -> Self {
158 NetError::from(e).into()
159 }
160}
161
162impl From<String> for RecursorError {
163 fn from(msg: String) -> Self {
164 Self::Msg(msg)
165 }
166}
167
168impl From<&'static str> for RecursorError {
169 fn from(msg: &'static str) -> Self {
170 Self::Message(msg)
171 }
172}
173
174impl Clone for RecursorError {
175 fn clone(&self) -> Self {
176 use self::RecursorError::*;
177 match self {
178 MaxRecordLimitExceeded { count, record_type } => MaxRecordLimitExceeded {
179 count: *count,
180 record_type: *record_type,
181 },
182 Message(msg) => Message(msg),
183 Msg(msg) => Msg(msg.clone()),
184 Negative(ns) => Negative(ns.clone()),
185 ForwardNS(ns) => ForwardNS(ns.clone()),
186 Io(io) => Io(io::Error::from(io.kind())),
187 Net(net) => Net(net.clone()),
188 Timeout => Self::Timeout,
189 RecursionLimitExceeded { count } => RecursionLimitExceeded { count: *count },
190 }
191 }
192}
193
194#[derive(Clone, Debug)]
196pub struct AuthorityData {
197 pub query: Box<Query>,
199 pub soa: Option<Box<Record<SOA>>>,
201 no_records_found: bool,
203 nx_domain: bool,
205 pub authorities: Option<Arc<[Record]>>,
207}
208
209impl AuthorityData {
210 pub fn new(
212 query: Box<Query>,
213 soa: Option<Box<Record<SOA>>>,
214 no_records_found: bool,
215 nx_domain: bool,
216 authorities: Option<Arc<[Record]>>,
217 ) -> Self {
218 Self {
219 query,
220 soa,
221 no_records_found,
222 nx_domain,
223 authorities,
224 }
225 }
226
227 pub fn is_no_records_found(&self) -> bool {
229 self.no_records_found
230 }
231
232 pub fn is_nx_domain(&self) -> bool {
234 self.nx_domain
235 }
236}
237
238impl From<AuthorityData> for NoRecords {
239 fn from(data: AuthorityData) -> Self {
240 let response_code = match data.is_nx_domain() {
241 true => ResponseCode::NXDomain,
242 false => ResponseCode::NoError,
243 };
244
245 let mut new = Self::new(data.query, response_code);
246 new.soa = data.soa;
247 new.authorities = data.authorities;
248 new
249 }
250}