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 #[error("per-request query budget exceeded")]
79 QueryBudgetExceeded,
80}
81
82impl RecursorError {
83 pub fn recursion_exceeded(limit: u8, depth: u8, name: &Name) -> Result<(), Self> {
85 if depth < limit {
86 return Ok(());
87 }
88
89 warn!("recursion depth exceeded for {name}");
90 Err(Self::RecursionLimitExceeded {
91 count: depth as usize,
92 })
93 }
94
95 pub fn into_soa(self) -> Option<Box<Record<SOA>>> {
97 match self {
98 Self::Net(net) => net.into_soa(),
99 Self::Negative(fwd) => fwd.soa,
100 _ => None,
101 }
102 }
103
104 pub fn is_no_records_found(&self) -> bool {
106 match self {
107 Self::Net(net) => net.is_no_records_found(),
108 Self::Negative(fwd) => fwd.is_no_records_found(),
109 _ => false,
110 }
111 }
112
113 pub fn is_nx_domain(&self) -> bool {
115 match self {
116 Self::Net(net) => net.is_nx_domain(),
117 Self::Negative(fwd) => fwd.is_nx_domain(),
118 _ => false,
119 }
120 }
121
122 pub fn is_timeout(&self) -> bool {
124 match self {
125 Self::Net(net) => matches!(net, NetError::Timeout),
126 _ => false,
127 }
128 }
129}
130
131impl From<NetError> for RecursorError {
132 fn from(e: NetError) -> Self {
133 let NetError::Dns(DnsError::NoRecordsFound(no_records)) = e else {
134 return Self::Net(e);
135 };
136
137 if let Some(ns) = no_records.ns {
138 Self::ForwardNS(ns)
139 } else {
140 Self::Negative(AuthorityData::new(
141 no_records.query,
142 no_records.soa,
143 true,
144 matches!(no_records.response_code, ResponseCode::NXDomain),
145 no_records.authorities,
146 ))
147 }
148 }
149}
150
151impl From<RecursorError> for NetError {
152 fn from(e: RecursorError) -> Self {
153 match e {
154 RecursorError::Negative(fwd) => DnsError::NoRecordsFound(fwd.into()).into(),
155 _ => Self::from(e.to_string()),
156 }
157 }
158}
159
160impl From<ProtoError> for RecursorError {
161 fn from(e: ProtoError) -> Self {
162 NetError::from(e).into()
163 }
164}
165
166impl From<String> for RecursorError {
167 fn from(msg: String) -> Self {
168 Self::Msg(msg)
169 }
170}
171
172impl From<&'static str> for RecursorError {
173 fn from(msg: &'static str) -> Self {
174 Self::Message(msg)
175 }
176}
177
178impl Clone for RecursorError {
179 fn clone(&self) -> Self {
180 use self::RecursorError::*;
181 match self {
182 MaxRecordLimitExceeded { count, record_type } => MaxRecordLimitExceeded {
183 count: *count,
184 record_type: *record_type,
185 },
186 Message(msg) => Message(msg),
187 Msg(msg) => Msg(msg.clone()),
188 Negative(ns) => Negative(ns.clone()),
189 ForwardNS(ns) => ForwardNS(ns.clone()),
190 Io(io) => Io(io::Error::from(io.kind())),
191 Net(net) => Net(net.clone()),
192 Timeout => Self::Timeout,
193 RecursionLimitExceeded { count } => RecursionLimitExceeded { count: *count },
194 QueryBudgetExceeded => QueryBudgetExceeded,
195 }
196 }
197}
198
199#[derive(Clone, Debug)]
201pub struct AuthorityData {
202 pub query: Box<Query>,
204 pub soa: Option<Box<Record<SOA>>>,
206 no_records_found: bool,
208 nx_domain: bool,
210 pub authorities: Option<Arc<[Record]>>,
212}
213
214impl AuthorityData {
215 pub fn new(
217 query: Box<Query>,
218 soa: Option<Box<Record<SOA>>>,
219 no_records_found: bool,
220 nx_domain: bool,
221 authorities: Option<Arc<[Record]>>,
222 ) -> Self {
223 Self {
224 query,
225 soa,
226 no_records_found,
227 nx_domain,
228 authorities,
229 }
230 }
231
232 pub fn is_no_records_found(&self) -> bool {
234 self.no_records_found
235 }
236
237 pub fn is_nx_domain(&self) -> bool {
239 self.nx_domain
240 }
241}
242
243impl From<AuthorityData> for NoRecords {
244 fn from(data: AuthorityData) -> Self {
245 let response_code = match data.is_nx_domain() {
246 true => ResponseCode::NXDomain,
247 false => ResponseCode::NoError,
248 };
249
250 let mut new = Self::new(data.query, response_code);
251 new.soa = data.soa;
252 new.authorities = data.authorities;
253 new
254 }
255}