hickory_server/zone_handler/mod.rs
1// Copyright 2015-2019 Benjamin Fry <benjaminfry@me.com>
2//
3// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
4// https://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
5// https://opensource.org/licenses/MIT>, at your option. This file may not be
6// copied, modified, or distributed except according to those terms.
7
8//! Module for `Catalog` of `ZoneHandler` zones which are responsible for storing `RRSet` records.
9
10use std::{fmt, io, sync::Arc};
11
12use cfg_if::cfg_if;
13use serde::{Deserialize, Serialize};
14use thiserror::Error;
15
16#[cfg(feature = "__dnssec")]
17use crate::dnssec::NxProofKind;
18use crate::net::{DnsError, NetError, NoRecords};
19use crate::proto::ProtoError;
20#[cfg(feature = "__dnssec")]
21use crate::proto::dnssec::crypto::Digest;
22#[cfg(feature = "__dnssec")]
23use crate::proto::dnssec::{DnsSecResult, DnssecSigner, Nsec3HashAlgorithm};
24use crate::proto::op::{Edns, ResponseCode};
25#[cfg(feature = "__dnssec")]
26use crate::proto::rr::Name;
27use crate::proto::rr::{
28 LowerName, Record, RecordSet, RecordType, RrsetRecords, TSigResponseContext, rdata::SOA,
29};
30#[cfg(feature = "recursor")]
31use crate::resolver::recursor::RecursorError;
32use crate::server::{Request, RequestInfo};
33
34mod auth_lookup;
35mod catalog;
36pub(crate) mod message_request;
37mod message_response;
38
39pub use self::auth_lookup::{
40 AuthLookup, AuthLookupIter, AxfrRecords, AxfrRecordsIter, LookupRecords, LookupRecordsIter,
41 ZoneTransfer,
42};
43pub use self::catalog::Catalog;
44pub use self::message_request::{MessageRequest, Queries, UpdateRequest};
45pub use self::message_response::{MessageResponse, MessageResponseBuilder};
46
47/// ZoneHandler implementations can be used with a `Catalog`
48#[async_trait::async_trait]
49pub trait ZoneHandler: Send + Sync {
50 /// What type is this zone
51 fn zone_type(&self) -> ZoneType;
52
53 /// Return the policy for determining if AXFR requests are allowed
54 fn axfr_policy(&self) -> AxfrPolicy;
55
56 /// Whether the zone handler can perform DNSSEC validation
57 fn can_validate_dnssec(&self) -> bool {
58 false
59 }
60
61 /// Perform a dynamic update of a zone
62 async fn update(
63 &self,
64 _update: &Request,
65 _now: u64,
66 ) -> (Result<bool, ResponseCode>, Option<TSigResponseContext>) {
67 (Err(ResponseCode::NotImp), None)
68 }
69
70 /// Get the origin of this zone, i.e. example.com is the origin for www.example.com
71 fn origin(&self) -> &LowerName;
72
73 /// Looks up all Resource Records matching the given `Name` and `RecordType`.
74 ///
75 /// # Arguments
76 ///
77 /// * `name` - The name to look up.
78 /// * `rtype` - The `RecordType` to look up. `RecordType::ANY` will return all records matching
79 /// `name`. `RecordType::AXFR` will return all record types except `RecordType::SOA`
80 /// due to the requirements that on zone transfers the `RecordType::SOA` must both
81 /// precede and follow all other records.
82 /// * `request_info` - The `RequestInfo` structure for the request, if it is available.
83 /// * `lookup_options` - Query-related lookup options (e.g., DNSSEC DO bit, supported hash
84 /// algorithms, etc.)
85 ///
86 /// # Return value
87 ///
88 /// A LookupControlFlow containing the lookup that should be returned to the client.
89 async fn lookup(
90 &self,
91 name: &LowerName,
92 rtype: RecordType,
93 request_info: Option<&RequestInfo<'_>>,
94 lookup_options: LookupOptions,
95 ) -> LookupControlFlow<AuthLookup>;
96
97 /// Consulting lookup for all Resource Records matching the given `Name` and `RecordType`. This
98 /// will be called in a chained zone handler configuration after a zone handler in the chain has
99 /// returned a lookup with a LookupControlFlow::Continue action. Every other zone handler in the
100 /// chain will be called via this consult method, until one either returns a
101 /// LookupControlFlow::Break action, or all zone handlers have been consulted. The zone handler
102 /// that generated the primary lookup (the one returned via 'lookup') will not be consulted.
103 ///
104 /// # Arguments
105 ///
106 /// * `name` - The name to look up.
107 /// * `rtype` - The `RecordType` to look up. `RecordType::ANY` will return all records matching
108 /// `name`. `RecordType::AXFR` will return all record types except `RecordType::SOA`
109 /// due to the requirements that on zone transfers the `RecordType::SOA` must both
110 /// precede and follow all other records.
111 /// * `request_info` - The `RequestInfo` structure for the request, if it is available.
112 /// * `lookup_options` - Query-related lookup options (e.g., DNSSEC DO bit, supported hash
113 /// algorithms, etc.)
114 /// * `last_result` - The lookup returned by a previous zone handler in a chained configuration.
115 /// If a subsequent zone handler does not modify this lookup, it will be
116 /// returned to the client after consulting all zone handlers in the chain.
117 ///
118 /// # Return value
119 ///
120 /// A LookupControlFlow containing the lookup that should be returned to the client. This can
121 /// be the same last_result that was passed in, or a new lookup, depending on the logic of the
122 /// zone handler in question.
123 ///
124 /// An optional `ResponseSigner` to use to sign the response returned to the client. If it is
125 /// `None` and an earlier zone handler provided `Some`, it will be ignored. If it is `Some` it
126 /// will be used to replace any previous `ResponseSigner`.
127 async fn consult(
128 &self,
129 _name: &LowerName,
130 _rtype: RecordType,
131 _request_info: Option<&RequestInfo<'_>>,
132 _lookup_options: LookupOptions,
133 last_result: LookupControlFlow<AuthLookup>,
134 ) -> (LookupControlFlow<AuthLookup>, Option<TSigResponseContext>) {
135 (last_result, None)
136 }
137
138 /// Using the specified query, perform a lookup against this zone.
139 ///
140 /// # Arguments
141 ///
142 /// * `request` - the query to perform the lookup with.
143 /// * `lookup_options` - Query-related lookup options (e.g., DNSSEC DO bit, supported hash
144 /// algorithms, etc.)
145 ///
146 /// # Return value
147 ///
148 /// A LookupControlFlow containing the lookup that should be returned to the client.
149 ///
150 /// An optional `ResponseSigner` to use to sign the response returned to the client.
151 async fn search(
152 &self,
153 request: &Request,
154 lookup_options: LookupOptions,
155 ) -> (LookupControlFlow<AuthLookup>, Option<TSigResponseContext>);
156
157 /// Return the NSEC records based on the given name
158 ///
159 /// # Arguments
160 ///
161 /// * `name` - given this name (i.e. the lookup name), return the NSEC record that is less than
162 /// this
163 /// * `lookup_options` - Query-related lookup options (e.g., DNSSEC DO bit, supported hash
164 /// algorithms, etc.)
165 async fn nsec_records(
166 &self,
167 name: &LowerName,
168 lookup_options: LookupOptions,
169 ) -> LookupControlFlow<AuthLookup>;
170
171 /// Return the NSEC3 records based on the information available for a query.
172 #[cfg(feature = "__dnssec")]
173 async fn nsec3_records(
174 &self,
175 info: Nsec3QueryInfo<'_>,
176 lookup_options: LookupOptions,
177 ) -> LookupControlFlow<AuthLookup>;
178
179 /// Returns all records in the zone.
180 ///
181 /// This will return `None` if the next zone handler in the zone handler chain should be used instead.
182 async fn zone_transfer(
183 &self,
184 _request: &Request,
185 _lookup_options: LookupOptions,
186 _now: u64,
187 ) -> Option<(
188 Result<ZoneTransfer, LookupError>,
189 Option<TSigResponseContext>,
190 )> {
191 Some((Err(LookupError::from(ResponseCode::NotImp)), None))
192 }
193
194 /// Returns the kind of non-existence proof used for this zone.
195 #[cfg(feature = "__dnssec")]
196 fn nx_proof_kind(&self) -> Option<&NxProofKind>;
197
198 /// Returns the zone handler metrics label.
199 #[cfg(feature = "metrics")]
200 fn metrics_label(&self) -> &'static str;
201}
202
203/// Extension to ZoneHandler to allow for DNSSEC features
204#[cfg(feature = "__dnssec")]
205#[async_trait::async_trait]
206pub trait DnssecZoneHandler: ZoneHandler {
207 /// Add Signer
208 async fn add_zone_signing_key(&self, signer: DnssecSigner) -> DnsSecResult<()>;
209
210 /// Sign the zone for DNSSEC
211 async fn secure_zone(&self) -> DnsSecResult<()>;
212}
213
214/// Result of a Lookup in the Catalog and ZoneHandler
215///
216/// * **All zone handlers should default to using LookupControlFlow::Continue to wrap their
217/// responses.** These responses may be passed to other zone handlers for analysis or requery
218/// purposes.
219/// * Zone handlers may use LookupControlFlow::Break to indicate the response must be returned
220/// immediately to the client, without consulting any other zone handlers. For example, if the
221/// user configures a blocklist zone handler, it would not be appropriate to pass the query to any
222/// additional zone handlers to try to resolve, as that might be used to leak information to a
223/// hostile party, and so a blocklist (or similar) zone handler should wrap responses for any
224/// blocklist hits in LookupControlFlow::Break.
225/// * Zone handlers may use LookupControlFlow::Skip to indicate the zone handler did not attempt to
226/// process a particular query. This might be used, for example, in a block list zone handler for
227/// any queries that **did not** match the blocklist, to allow the recursor or forwarder to
228/// resolve the query. Skip must not be used to represent an empty lookup; (use
229/// Continue(EmptyLookup) or Break(EmptyLookup) for that.)
230pub enum LookupControlFlow<T, E = LookupError> {
231 /// A lookup response that may be passed to one or more additional zone handlers before
232 /// being returned to the client.
233 Continue(Result<T, E>),
234 /// A lookup response that must be immediately returned to the client without consulting
235 /// any other zone handlers.
236 Break(Result<T, E>),
237 /// The zone handler did not answer the query and the next zone handler in the chain should
238 /// be consulted.
239 Skip,
240}
241
242impl<T, E> fmt::Display for LookupControlFlow<T, E> {
243 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
244 match self {
245 Self::Continue(cont) => match cont {
246 Ok(_) => write!(f, "LookupControlFlow::Continue(Ok)"),
247 Err(_) => write!(f, "LookupControlFlow::Continue(Err)"),
248 },
249 Self::Break(b) => match b {
250 Ok(_) => write!(f, "LookupControlFlow::Break(Ok)"),
251 Err(_) => write!(f, "LookupControlFlow::Break(Err)"),
252 },
253 Self::Skip => write!(f, "LookupControlFlow::Skip"),
254 }
255 }
256}
257
258/// The following are a minimal set of methods typically used with Result or Option, and that
259/// were used in the server code or test suite prior to when the LookupControlFlow type was created
260/// (zone handler lookup functions previously returned a Result over a Lookup or LookupError type.)
261impl<T, E> LookupControlFlow<T, E> {
262 /// Return true if self is LookupControlFlow::Continue
263 pub fn is_continue(&self) -> bool {
264 matches!(self, Self::Continue(_))
265 }
266
267 /// Return true if self is LookupControlFlow::Break
268 pub fn is_break(&self) -> bool {
269 matches!(self, Self::Break(_))
270 }
271
272 /// Maps inner Ok(T) and Err(E) to Some(Result<T,E>) and Skip to None
273 pub fn map_result(self) -> Option<Result<T, E>> {
274 match self {
275 Self::Continue(Ok(lookup)) | Self::Break(Ok(lookup)) => Some(Ok(lookup)),
276 Self::Continue(Err(e)) | Self::Break(Err(e)) => Some(Err(e)),
277 Self::Skip => None,
278 }
279 }
280}
281
282impl<E: fmt::Display> LookupControlFlow<AuthLookup, E> {
283 /// Return inner Ok variant or panic with a custom error message.
284 pub fn expect(self, msg: &str) -> AuthLookup {
285 match self {
286 Self::Continue(Ok(ok)) | Self::Break(Ok(ok)) => ok,
287 _ => {
288 panic!("lookupcontrolflow::expect() called on unexpected variant {self}: {msg}");
289 }
290 }
291 }
292
293 /// Return inner Err variant or panic with a custom error message.
294 pub fn expect_err(self, msg: &str) -> E {
295 match self {
296 Self::Continue(Err(e)) | Self::Break(Err(e)) => e,
297 _ => {
298 panic!(
299 "lookupcontrolflow::expect_err() called on unexpected variant {self}: {msg}"
300 );
301 }
302 }
303 }
304
305 /// Return inner Ok variant or panic
306 pub fn unwrap(self) -> AuthLookup {
307 match self {
308 Self::Continue(Ok(ok)) | Self::Break(Ok(ok)) => ok,
309 Self::Continue(Err(e)) | Self::Break(Err(e)) => {
310 panic!("lookupcontrolflow::unwrap() called on unexpected variant _(Err(_)): {e}");
311 }
312 _ => {
313 panic!("lookupcontrolflow::unwrap() called on unexpected variant: {self}");
314 }
315 }
316 }
317
318 /// Return inner Err variant or panic
319 pub fn unwrap_err(self) -> E {
320 match self {
321 Self::Continue(Err(e)) | Self::Break(Err(e)) => e,
322 _ => {
323 panic!("lookupcontrolflow::unwrap_err() called on unexpected variant: {self}");
324 }
325 }
326 }
327
328 /// Return inner Ok Variant or default value
329 pub fn unwrap_or_default(self) -> AuthLookup {
330 match self {
331 Self::Continue(Ok(ok)) | Self::Break(Ok(ok)) => ok,
332 _ => AuthLookup::default(),
333 }
334 }
335
336 /// Maps inner Ok(T) to Ok(U), passing inner Err and Skip values unchanged.
337 pub fn map<U, F: FnOnce(AuthLookup) -> U>(self, op: F) -> LookupControlFlow<U, E> {
338 match self {
339 Self::Continue(cont) => match cont {
340 Ok(t) => LookupControlFlow::Continue(Ok(op(t))),
341 Err(e) => LookupControlFlow::Continue(Err(e)),
342 },
343 Self::Break(b) => match b {
344 Ok(t) => LookupControlFlow::Break(Ok(op(t))),
345 Err(e) => LookupControlFlow::Break(Err(e)),
346 },
347 Self::Skip => LookupControlFlow::<U, E>::Skip,
348 }
349 }
350
351 /// Maps inner Err(T) to Err(U), passing Ok and Skip values unchanged.
352 pub fn map_err<U, F: FnOnce(E) -> U>(self, op: F) -> LookupControlFlow<AuthLookup, U> {
353 match self {
354 Self::Continue(cont) => match cont {
355 Ok(lookup) => LookupControlFlow::Continue(Ok(lookup)),
356 Err(e) => LookupControlFlow::Continue(Err(op(e))),
357 },
358 Self::Break(b) => match b {
359 Ok(lookup) => LookupControlFlow::Break(Ok(lookup)),
360 Err(e) => LookupControlFlow::Break(Err(op(e))),
361 },
362 Self::Skip => LookupControlFlow::Skip,
363 }
364 }
365}
366
367/// A query could not be fulfilled
368#[derive(Debug, Error)]
369#[non_exhaustive]
370pub enum LookupError {
371 /// The query had an invalid number of queries
372 #[error("there should only be one query per request, got {0}")]
373 BadQueryCount(usize),
374 /// A record at the same Name as the query exists, but not of the queried RecordType
375 #[error("The name exists, but not for the record requested")]
376 NameExists,
377 /// There was an error performing the lookup
378 #[error("Error performing lookup: {0}")]
379 ResponseCode(ResponseCode),
380 /// Proto error
381 #[error("net error: {0}")]
382 NetError(#[from] NetError),
383 /// Recursive Resolver Error
384 #[cfg(feature = "recursor")]
385 #[error("Recursive resolution error: {0}")]
386 RecursiveError(#[from] RecursorError),
387 /// An underlying IO error occurred
388 #[error("io error: {0}")]
389 Io(io::Error),
390}
391
392impl LookupError {
393 /// Create a lookup error, specifying that a name exists at the location, but no matching RecordType
394 pub fn for_name_exists() -> Self {
395 Self::NameExists
396 }
397
398 /// This is a non-existent domain name
399 pub fn is_nx_domain(&self) -> bool {
400 match self {
401 Self::NetError(e) => e.is_nx_domain(),
402 Self::ResponseCode(ResponseCode::NXDomain) => true,
403 #[cfg(feature = "recursor")]
404 Self::RecursiveError(e) if e.is_nx_domain() => true,
405 _ => false,
406 }
407 }
408
409 /// Returns true if no records were returned
410 pub fn is_no_records_found(&self) -> bool {
411 match self {
412 Self::NetError(e) => e.is_no_records_found(),
413 #[cfg(feature = "recursor")]
414 Self::RecursiveError(e) if e.is_no_records_found() => true,
415 _ => false,
416 }
417 }
418
419 /// Returns the SOA record, if the error contains one
420 pub fn into_soa(self) -> Option<Box<Record<SOA>>> {
421 match self {
422 Self::NetError(e) => e.into_soa(),
423 #[cfg(feature = "recursor")]
424 Self::RecursiveError(e) => e.into_soa(),
425 _ => None,
426 }
427 }
428
429 /// Return authority records
430 pub fn authorities(&self) -> Option<Arc<[Record]>> {
431 match self {
432 Self::NetError(NetError::Dns(DnsError::NoRecordsFound(NoRecords {
433 authorities,
434 ..
435 }))) => authorities.clone(),
436 Self::NetError(_) => None,
437 #[cfg(feature = "recursor")]
438 Self::RecursiveError(RecursorError::Negative(fwd)) => fwd.authorities.clone(),
439 #[cfg(feature = "recursor")]
440 Self::RecursiveError(RecursorError::Net(NetError::Dns(DnsError::NoRecordsFound(
441 NoRecords { authorities, .. },
442 )))) => authorities.clone(),
443 _ => None,
444 }
445 }
446}
447
448impl From<ResponseCode> for LookupError {
449 fn from(code: ResponseCode) -> Self {
450 // this should never be a NoError
451 debug_assert!(code != ResponseCode::NoError);
452 Self::ResponseCode(code)
453 }
454}
455
456impl From<io::Error> for LookupError {
457 fn from(e: io::Error) -> Self {
458 Self::Io(e)
459 }
460}
461
462impl From<LookupError> for io::Error {
463 fn from(e: LookupError) -> Self {
464 Self::other(Box::new(e))
465 }
466}
467
468impl From<ProtoError> for LookupError {
469 fn from(e: ProtoError) -> Self {
470 NetError::from(e).into()
471 }
472}
473
474/// Information required to compute the NSEC3 records that should be sent for a query.
475#[cfg(feature = "__dnssec")]
476pub struct Nsec3QueryInfo<'q> {
477 /// The queried name.
478 pub qname: &'q LowerName,
479 /// The queried record type.
480 pub qtype: RecordType,
481 /// Whether there was a wildcard match for `qname` regardless of `qtype`.
482 pub has_wildcard_match: bool,
483 /// The algorithm used to hash the names.
484 pub algorithm: Nsec3HashAlgorithm,
485 /// The salt used for hashing.
486 pub salt: &'q [u8],
487 /// The number of hashing iterations.
488 pub iterations: u16,
489}
490
491#[cfg(feature = "__dnssec")]
492impl Nsec3QueryInfo<'_> {
493 /// Computes the hash of a given name.
494 pub(crate) fn hash_name(&self, name: &Name) -> Result<Digest, ProtoError> {
495 self.algorithm.hash(self.salt, name, self.iterations)
496 }
497
498 /// Computes the hashed owner name from a given name. That is, the hash of the given name,
499 /// followed by the zone name.
500 pub(crate) fn hashed_owner_name(
501 &self,
502 name: &LowerName,
503 zone: &Name,
504 ) -> Result<LowerName, ProtoError> {
505 let hash = self.hash_name(name)?;
506 let label = data_encoding::BASE32_DNSSEC.encode(hash.as_ref());
507 Ok(LowerName::new(&zone.prepend_label(label)?))
508 }
509}
510
511/// Options from the client to include or exclude various records in the response.
512#[non_exhaustive]
513#[derive(Clone, Copy, Debug, Default)]
514pub struct LookupOptions {
515 /// Whether the client is interested in `RRSIG` records (DNSSEC DO bit).
516 pub dnssec_ok: bool,
517}
518
519impl LookupOptions {
520 /// Create [`LookupOptions`] from the given EDNS options.
521 #[cfg_attr(not(feature = "__dnssec"), allow(unused_variables))]
522 pub fn from_edns(edns: Option<&Edns>) -> Self {
523 #[cfg_attr(not(feature = "__dnssec"), allow(unused_mut))]
524 let mut new = Self::default();
525 #[cfg(feature = "__dnssec")]
526 if let Some(edns) = edns {
527 new.dnssec_ok = edns.flags().dnssec_ok;
528 }
529 new
530 }
531
532 /// Create [`LookupOptions`] with `dnssec_ok` enabled.
533 #[cfg(feature = "__dnssec")]
534 pub fn for_dnssec() -> Self {
535 Self { dnssec_ok: true }
536 }
537
538 /// Returns the rrset's records with or without RRSIGs, depending on the DO flag.
539 pub fn rrset_with_rrigs<'r>(&self, record_set: &'r RecordSet) -> RrsetRecords<'r> {
540 cfg_if! {
541 if #[cfg(feature = "__dnssec")] {
542 record_set.records(self.dnssec_ok)
543 } else {
544 record_set.records_without_rrsigs()
545 }
546 }
547 }
548}
549
550/// AxfrPolicy describes how to handle AXFR requests
551///
552/// By default, all AXFR requests are denied.
553#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Deserialize)]
554pub enum AxfrPolicy {
555 /// Deny all AXFR requests.
556 #[default]
557 Deny,
558 /// Allow all AXFR requests, regardless of whether they are signed.
559 AllowAll,
560 /// Allow all AXFR requests that have a valid TSIG signature.
561 #[cfg(feature = "__dnssec")]
562 AllowSigned,
563}
564
565/// The type of zone stored in a Catalog
566#[derive(Serialize, Deserialize, Hash, PartialEq, Eq, Debug, Clone, Copy)]
567pub enum ZoneType {
568 /// This authority for a zone
569 Primary,
570 /// A secondary, i.e. replicated from the Primary
571 Secondary,
572 /// A cached zone that queries other nameservers
573 External,
574}