hickory_server/authority/authority_object.rs
1// Copyright 2015-2021 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//! Object-safe authority and lookup traits
9
10use tracing::debug;
11
12#[cfg(feature = "__dnssec")]
13use crate::{authority::Nsec3QueryInfo, dnssec::NxProofKind, proto::dnssec::Proof};
14use crate::{
15 authority::{
16 Authority, LookupControlFlow, LookupOptions, MessageRequest, UpdateResult, ZoneType,
17 },
18 proto::rr::{LowerName, Record, RecordType},
19 server::RequestInfo,
20};
21
22/// An Object safe Authority
23#[async_trait::async_trait]
24pub trait AuthorityObject: Send + Sync {
25 /// What type is this zone
26 fn zone_type(&self) -> ZoneType;
27
28 /// Return true if AXFR is allowed
29 fn is_axfr_allowed(&self) -> bool;
30
31 /// Whether the authority can perform DNSSEC validation
32 fn can_validate_dnssec(&self) -> bool;
33
34 /// Perform a dynamic update of a zone
35 async fn update(&self, update: &MessageRequest) -> UpdateResult<bool>;
36
37 /// Get the origin of this zone, i.e. example.com is the origin for www.example.com
38 fn origin(&self) -> &LowerName;
39
40 /// Looks up all Resource Records matching the given `Name` and `RecordType`.
41 ///
42 /// # Arguments
43 ///
44 /// * `name` - The name to look up.
45 /// * `rtype` - The `RecordType` to look up. `RecordType::ANY` will return all records matching
46 /// `name`. `RecordType::AXFR` will return all record types except `RecordType::SOA`
47 /// due to the requirements that on zone transfers the `RecordType::SOA` must both
48 /// precede and follow all other records.
49 /// * `lookup_options` - Query-related lookup options (e.g., DNSSEC DO bit, supported hash
50 /// algorithms, etc.)
51 ///
52 /// # Return value
53 ///
54 /// A LookupControlFlow containing the lookup that should be returned to the client.
55 async fn lookup(
56 &self,
57 name: &LowerName,
58 rtype: RecordType,
59 lookup_options: LookupOptions,
60 ) -> LookupControlFlow<Box<dyn LookupObject>>;
61
62 /// Consulting lookup for all Resource Records matching the given `Name` and `RecordType`.
63 /// This will be called in a chained authority configuration after an authority in the chain
64 /// has returned a lookup with a LookupControlFlow::Continue action. Every other authority in
65 /// the chain will be called via this consult method, until one either returns a
66 /// LookupControlFlow::Break action, or all authorities have been consulted. The authority that
67 /// generated the primary lookup (the one returned via 'lookup') will not be consulted.
68 ///
69 /// # Arguments
70 ///
71 /// * `name` - The name to look up.
72 /// * `rtype` - The `RecordType` to look up. `RecordType::ANY` will return all records matching
73 /// `name`. `RecordType::AXFR` will return all record types except `RecordType::SOA`
74 /// due to the requirements that on zone transfers the `RecordType::SOA` must both
75 /// precede and follow all other records.
76 /// * `lookup_options` - Query-related lookup options (e.g., DNSSEC DO bit, supported hash
77 /// algorithms, etc.)
78 /// * `last_result` - The lookup returned by a previous authority in a chained configuration.
79 /// If a subsequent authority does not modify this lookup, it will be returned
80 /// to the client after consulting all authorities in the chain.
81 ///
82 /// # Return value
83 ///
84 /// A LookupControlFlow containing the lookup that should be returned to the client. This can
85 /// be the same last_result that was passed in, or a new lookup, depending on the logic of the
86 /// authority in question.
87 async fn consult(
88 &self,
89 name: &LowerName,
90 rtype: RecordType,
91 lookup_options: LookupOptions,
92 last_result: LookupControlFlow<Box<dyn LookupObject>>,
93 ) -> LookupControlFlow<Box<dyn LookupObject>>;
94
95 /// Using the specified query, perform a lookup against this zone.
96 ///
97 /// # Arguments
98 ///
99 /// * `request_info` - the query to perform the lookup with.
100 /// * `lookup_options` - Query-related lookup options (e.g., DNSSEC DO bit, supported hash
101 /// algorithms, etc.)
102 ///
103 /// # Return value
104 ///
105 /// A LookupControlFlow containing the lookup that should be returned to the client.
106 async fn search(
107 &self,
108 request_info: RequestInfo<'_>,
109 lookup_options: LookupOptions,
110 ) -> LookupControlFlow<Box<dyn LookupObject>>;
111
112 /// Get the NS, NameServer, record for the zone
113 async fn ns(&self, lookup_options: LookupOptions) -> LookupControlFlow<Box<dyn LookupObject>> {
114 self.lookup(self.origin(), RecordType::NS, lookup_options)
115 .await
116 }
117
118 /// Return the NSEC records based on the given name
119 ///
120 /// # Arguments
121 ///
122 /// * `name` - given this name (i.e. the lookup name), return the NSEC record that is less than
123 /// this
124 /// * `lookup_options` - Query-related lookup options (e.g., DNSSEC DO bit, supported hash
125 /// algorithms, etc.)
126 async fn get_nsec_records(
127 &self,
128 name: &LowerName,
129 lookup_options: LookupOptions,
130 ) -> LookupControlFlow<Box<dyn LookupObject>>;
131
132 /// Return the NSEC3 records based on the given query information.
133 #[cfg(feature = "__dnssec")]
134 async fn get_nsec3_records(
135 &self,
136 info: Nsec3QueryInfo<'_>,
137 lookup_options: LookupOptions,
138 ) -> LookupControlFlow<Box<dyn LookupObject>>;
139
140 /// Returns the SOA of the authority.
141 ///
142 /// *Note*: This will only return the SOA, if this is fulfilling a request, a standard lookup
143 /// should be used, see `soa_secure()`, which will optionally return RRSIGs.
144 async fn soa(&self) -> LookupControlFlow<Box<dyn LookupObject>> {
145 // SOA should be origin|SOA
146 self.lookup(self.origin(), RecordType::SOA, LookupOptions::default())
147 .await
148 }
149
150 /// Returns the SOA record for the zone
151 async fn soa_secure(
152 &self,
153 lookup_options: LookupOptions,
154 ) -> LookupControlFlow<Box<dyn LookupObject>> {
155 self.lookup(self.origin(), RecordType::SOA, lookup_options)
156 .await
157 }
158
159 /// Returns the kind of non-existence proof used for this zone.
160 #[cfg(feature = "__dnssec")]
161 fn nx_proof_kind(&self) -> Option<&NxProofKind>;
162}
163
164#[async_trait::async_trait]
165impl<A, L> AuthorityObject for A
166where
167 A: Authority<Lookup = L> + Send + Sync + 'static,
168 L: LookupObject + Send + Sync + 'static,
169{
170 /// What type is this zone
171 fn zone_type(&self) -> ZoneType {
172 Authority::zone_type(self)
173 }
174
175 /// Return true if AXFR is allowed
176 fn is_axfr_allowed(&self) -> bool {
177 Authority::is_axfr_allowed(self)
178 }
179
180 /// Whether the authority can perform DNSSEC validation
181 fn can_validate_dnssec(&self) -> bool {
182 Authority::can_validate_dnssec(self)
183 }
184
185 /// Perform a dynamic update of a zone
186 async fn update(&self, update: &MessageRequest) -> UpdateResult<bool> {
187 Authority::update(self, update).await
188 }
189
190 /// Get the origin of this zone, i.e. example.com is the origin for www.example.com
191 fn origin(&self) -> &LowerName {
192 Authority::origin(self)
193 }
194
195 /// Looks up all Resource Records matching the given `Name` and `RecordType`.
196 ///
197 /// # Arguments
198 ///
199 /// * `name` - The name to look up.
200 /// * `rtype` - The `RecordType` to look up. `RecordType::ANY` will return all records matching
201 /// `name`. `RecordType::AXFR` will return all record types except `RecordType::SOA`
202 /// due to the requirements that on zone transfers the `RecordType::SOA` must both
203 /// precede and follow all other records.
204 /// * `lookup_options` - Query-related lookup options (e.g., DNSSEC DO bit, supported hash
205 /// algorithms, etc.)
206 ///
207 /// # Return value
208 ///
209 /// A LookupControlFlow containing the lookup that should be returned to the client.
210 async fn lookup(
211 &self,
212 name: &LowerName,
213 rtype: RecordType,
214 lookup_options: LookupOptions,
215 ) -> LookupControlFlow<Box<dyn LookupObject>> {
216 Authority::lookup(self, name, rtype, lookup_options)
217 .await
218 .map_dyn()
219 }
220
221 /// Consulting lookup for all Resource Records matching the given `Name` and `RecordType`.
222 /// This will be called in a chained authority configuration after an authority in the chain
223 /// has returned a lookup with a LookupControlFlow::Continue action. Every other authority in
224 /// the chain will be called via this consult method, until one either returns a
225 /// LookupControlFlow::Break action, or all authorities have been consulted. The authority that
226 /// generated the primary lookup (the one returned via 'lookup') will not be consulted.
227 ///
228 /// # Arguments
229 ///
230 /// * `name` - The name to look up.
231 /// * `rtype` - The `RecordType` to look up. `RecordType::ANY` will return all records matching
232 /// `name`. `RecordType::AXFR` will return all record types except `RecordType::SOA`
233 /// due to the requirements that on zone transfers the `RecordType::SOA` must both
234 /// precede and follow all other records.
235 /// * `lookup_options` - Query-related lookup options (e.g., DNSSEC DO bit, supported hash
236 /// algorithms, etc.)
237 /// * `last_result` - The lookup returned by a previous authority in a chained configuration.
238 /// If a subsequent authority does not modify this lookup, it will be returned
239 /// to the client after consulting all authorities in the chain.
240 ///
241 /// # Return value
242 ///
243 /// A LookupControlFlow containing the lookup that should be returned to the client. This can
244 /// be the same last_result that was passed in, or a new lookup, depending on the logic of the
245 /// authority in question.
246 async fn consult(
247 &self,
248 name: &LowerName,
249 rtype: RecordType,
250 lookup_options: LookupOptions,
251 last_result: LookupControlFlow<Box<dyn LookupObject>>,
252 ) -> LookupControlFlow<Box<dyn LookupObject>> {
253 Authority::consult(self, name, rtype, lookup_options, last_result).await
254 }
255
256 /// Using the specified query, perform a lookup against this zone.
257 ///
258 /// # Arguments
259 ///
260 /// * `request_info` - the query to perform the lookup with.
261 /// * `lookup_options` - Query-related lookup options (e.g., DNSSEC DO bit, supported hash
262 /// algorithms, etc.)
263 ///
264 /// # Return value
265 ///
266 /// A LookupControlFlow containing the lookup that should be returned to the client.
267 async fn search(
268 &self,
269 request_info: RequestInfo<'_>,
270 lookup_options: LookupOptions,
271 ) -> LookupControlFlow<Box<dyn LookupObject>> {
272 debug!("performing {} on {}", request_info.query, self.origin());
273 Authority::search(self, request_info, lookup_options)
274 .await
275 .map_dyn()
276 }
277
278 /// Return the NSEC records based on the given name
279 ///
280 /// # Arguments
281 ///
282 /// * `name` - given this name (i.e. the lookup name), return the NSEC record that is less than
283 /// this
284 /// * `lookup_options` - Query-related lookup options (e.g., DNSSEC DO bit, supported hash
285 /// algorithms, etc.)
286 async fn get_nsec_records(
287 &self,
288 name: &LowerName,
289 lookup_options: LookupOptions,
290 ) -> LookupControlFlow<Box<dyn LookupObject>> {
291 Authority::get_nsec_records(self, name, lookup_options)
292 .await
293 .map_dyn()
294 }
295
296 /// Return the NSEC3 records based on the given query information.
297 #[cfg(feature = "__dnssec")]
298 async fn get_nsec3_records(
299 &self,
300 info: Nsec3QueryInfo<'_>,
301 lookup_options: LookupOptions,
302 ) -> LookupControlFlow<Box<dyn LookupObject>> {
303 Authority::get_nsec3_records(self, info, lookup_options)
304 .await
305 .map_dyn()
306 }
307
308 /// Returns the kind of non-existence proof used for this zone.
309 #[cfg(feature = "__dnssec")]
310 fn nx_proof_kind(&self) -> Option<&NxProofKind> {
311 Authority::nx_proof_kind(self)
312 }
313}
314
315/// DNSSEC status of an answer
316#[derive(Clone, Copy, Debug)]
317pub enum DnssecSummary {
318 /// All records have been DNSSEC validated
319 Secure,
320 /// At least one record is in the Bogus state
321 Bogus,
322 /// Insecure / Indeterminate (e.g. "Island of security")
323 Insecure,
324}
325
326/// An Object Safe Lookup for Authority
327pub trait LookupObject: Send {
328 /// Returns true if either the associated Records are empty, or this is a NameExists or NxDomain
329 fn is_empty(&self) -> bool;
330
331 /// Conversion to an iterator
332 fn iter<'a>(&'a self) -> Box<dyn Iterator<Item = &'a Record> + Send + 'a>;
333
334 /// For CNAME and similar records, this is an additional set of lookup records
335 ///
336 /// it is acceptable for this to return None after the first call.
337 fn take_additionals(&mut self) -> Option<Box<dyn LookupObject>>;
338
339 /// Whether the records have been DNSSEC validated or not
340 #[cfg(feature = "__dnssec")]
341 fn dnssec_summary(&self) -> DnssecSummary {
342 let mut all_secure = None;
343 for record in self.iter() {
344 match record.proof() {
345 Proof::Secure => {
346 all_secure.get_or_insert(true);
347 }
348 Proof::Bogus => return DnssecSummary::Bogus,
349 _ => all_secure = Some(false),
350 }
351 }
352
353 if all_secure.unwrap_or(false) {
354 DnssecSummary::Secure
355 } else {
356 DnssecSummary::Insecure
357 }
358 }
359
360 /// Whether the records have been DNSSEC validated or not
361 #[cfg(not(feature = "__dnssec"))]
362 fn dnssec_summary(&self) -> DnssecSummary {
363 DnssecSummary::Insecure
364 }
365}
366
367/// A lookup that returns no records
368#[derive(Clone, Copy, Debug)]
369pub struct EmptyLookup;
370
371impl LookupObject for EmptyLookup {
372 fn is_empty(&self) -> bool {
373 true
374 }
375
376 fn iter<'a>(&'a self) -> Box<dyn Iterator<Item = &'a Record> + Send + 'a> {
377 Box::new([].iter())
378 }
379
380 fn take_additionals(&mut self) -> Option<Box<dyn LookupObject>> {
381 None
382 }
383}