hickory_server/authority/
auth_lookup.rs

1// Copyright 2015-2017 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
8use std::iter::Chain;
9use std::slice::Iter;
10use std::sync::Arc;
11
12use cfg_if::cfg_if;
13
14use crate::authority::{LookupObject, LookupOptions};
15use crate::proto::rr::{LowerName, Record, RecordSet, RecordType, RrsetRecords};
16
17/// The result of a lookup on an Authority
18///
19/// # Lifetimes
20///
21/// * `'c` - the catalogue lifetime
22/// * `'r` - the recordset lifetime, subset of 'c
23/// * `'q` - the queries lifetime
24#[derive(Debug)]
25#[allow(clippy::large_enum_variant)]
26pub enum AuthLookup {
27    /// No records
28    Empty,
29    // TODO: change the result of a lookup to a set of chained iterators...
30    /// Records
31    Records {
32        /// Authoritative answers
33        answers: LookupRecords,
34        /// Optional set of LookupRecords
35        additionals: Option<LookupRecords>,
36    },
37    /// Soa only differs from Records in that the lifetime on the name is from the authority, and not the query
38    SOA(LookupRecords),
39    /// An axfr starts with soa, chained to all the records, then another soa...
40    AXFR {
41        /// The first SOA record in an AXFR response
42        start_soa: LookupRecords,
43        /// The records to return
44        records: LookupRecords,
45        /// The last SOA record of an AXFR (matches the first)
46        end_soa: LookupRecords,
47    },
48}
49
50impl AuthLookup {
51    /// Construct an answer with additional section
52    pub fn answers(answers: LookupRecords, additionals: Option<LookupRecords>) -> Self {
53        Self::Records {
54            answers,
55            additionals,
56        }
57    }
58
59    /// Returns true if either the associated Records are empty, or this is a NameExists or NxDomain
60    pub fn is_empty(&self) -> bool {
61        // TODO: this needs to be cheap
62        self.was_empty()
63    }
64
65    /// This is an NxDomain or NameExists, and has no associated records
66    ///
67    /// this consumes the iterator, and verifies it is empty
68    pub fn was_empty(&self) -> bool {
69        self.iter().count() == 0
70    }
71
72    /// Conversion to an iterator
73    pub fn iter(&self) -> AuthLookupIter<'_> {
74        self.into_iter()
75    }
76
77    /// Does not panic, but will return no records if it is not of that type
78    pub fn unwrap_records(self) -> LookupRecords {
79        match self {
80            // TODO: this is ugly, what about the additionals?
81            Self::Records { answers, .. } => answers,
82            _ => LookupRecords::default(),
83        }
84    }
85
86    /// Takes the additional records, leaving behind None
87    pub fn take_additionals(&mut self) -> Option<LookupRecords> {
88        match self {
89            Self::Records { additionals, .. } => additionals.take(),
90            _ => None,
91        }
92    }
93}
94
95impl LookupObject for AuthLookup {
96    fn is_empty(&self) -> bool {
97        Self::is_empty(self)
98    }
99
100    fn iter<'a>(&'a self) -> Box<dyn Iterator<Item = &'a Record> + Send + 'a> {
101        let boxed_iter = Self::iter(self);
102        Box::new(boxed_iter)
103    }
104
105    fn take_additionals(&mut self) -> Option<Box<dyn LookupObject>> {
106        let additionals = Self::take_additionals(self);
107        additionals.map(|a| Box::new(a) as Box<dyn LookupObject>)
108    }
109}
110
111impl Default for AuthLookup {
112    fn default() -> Self {
113        Self::Empty
114    }
115}
116
117impl<'a> IntoIterator for &'a AuthLookup {
118    type Item = &'a Record;
119    type IntoIter = AuthLookupIter<'a>;
120
121    fn into_iter(self) -> Self::IntoIter {
122        match self {
123            AuthLookup::Empty => AuthLookupIter::Empty,
124            // TODO: what about the additionals? is IntoIterator a bad idea?
125            AuthLookup::Records { answers: r, .. } | AuthLookup::SOA(r) => {
126                AuthLookupIter::Records(r.into_iter())
127            }
128            AuthLookup::AXFR {
129                start_soa,
130                records,
131                end_soa,
132            } => AuthLookupIter::AXFR(start_soa.into_iter().chain(records).chain(end_soa)),
133        }
134    }
135}
136
137/// An iterator over an Authority Lookup
138#[allow(clippy::large_enum_variant)]
139#[derive(Default)]
140pub enum AuthLookupIter<'r> {
141    /// The empty set
142    #[default]
143    Empty,
144    /// An iteration over a set of Records
145    Records(LookupRecordsIter<'r>),
146    /// An iteration over an AXFR
147    AXFR(Chain<Chain<LookupRecordsIter<'r>, LookupRecordsIter<'r>>, LookupRecordsIter<'r>>),
148}
149
150impl<'r> Iterator for AuthLookupIter<'r> {
151    type Item = &'r Record;
152
153    fn next(&mut self) -> Option<Self::Item> {
154        match self {
155            AuthLookupIter::Empty => None,
156            AuthLookupIter::Records(i) => i.next(),
157            AuthLookupIter::AXFR(i) => i.next(),
158        }
159    }
160}
161
162impl From<LookupRecords> for AuthLookup {
163    fn from(lookup: LookupRecords) -> Self {
164        Self::Records {
165            answers: lookup,
166            additionals: None,
167        }
168    }
169}
170
171/// An iterator over an ANY query for Records.
172///
173/// The length of this result cannot be known without consuming the iterator.
174///
175/// # Lifetimes
176///
177/// * `'r` - the record_set's lifetime, from the catalog
178/// * `'q` - the lifetime of the query/request
179#[derive(Debug)]
180pub struct AnyRecords {
181    lookup_options: LookupOptions,
182    rrsets: Vec<Arc<RecordSet>>,
183    query_type: RecordType,
184    query_name: LowerName,
185}
186
187impl AnyRecords {
188    /// construct a new lookup of any set of records
189    pub fn new(
190        lookup_options: LookupOptions,
191        // TODO: potentially very expensive
192        rrsets: Vec<Arc<RecordSet>>,
193        query_type: RecordType,
194        query_name: LowerName,
195    ) -> Self {
196        Self {
197            lookup_options,
198            rrsets,
199            query_type,
200            query_name,
201        }
202    }
203
204    fn iter(&self) -> AnyRecordsIter<'_> {
205        self.into_iter()
206    }
207}
208
209impl<'r> IntoIterator for &'r AnyRecords {
210    type Item = &'r Record;
211    type IntoIter = AnyRecordsIter<'r>;
212
213    fn into_iter(self) -> Self::IntoIter {
214        AnyRecordsIter {
215            lookup_options: self.lookup_options,
216            // TODO: potentially very expensive
217            rrsets: self.rrsets.iter(),
218            rrset: None,
219            records: None,
220            query_type: self.query_type,
221            query_name: &self.query_name,
222        }
223    }
224}
225
226/// An iteration over a lookup for any Records
227#[allow(unused)]
228pub struct AnyRecordsIter<'r> {
229    lookup_options: LookupOptions,
230    rrsets: Iter<'r, Arc<RecordSet>>,
231    rrset: Option<&'r RecordSet>,
232    records: Option<RrsetRecords<'r>>,
233    query_type: RecordType,
234    query_name: &'r LowerName,
235}
236
237impl<'r> Iterator for AnyRecordsIter<'r> {
238    type Item = &'r Record;
239
240    fn next(&mut self) -> Option<Self::Item> {
241        use std::borrow::Borrow;
242
243        let query_type = self.query_type;
244        let query_name = self.query_name;
245
246        loop {
247            if let Some(records) = &mut self.records {
248                let record = records
249                    .by_ref()
250                    .filter(|rr_set| {
251                        query_type == RecordType::ANY || rr_set.record_type() != RecordType::SOA
252                    })
253                    .find(|rr_set| {
254                        query_type == RecordType::AXFR
255                            || &LowerName::from(rr_set.name()) == query_name
256                    });
257
258                if record.is_some() {
259                    return record;
260                }
261            }
262
263            self.rrset = self.rrsets.next().map(Borrow::borrow);
264
265            // if there are no more RecordSets, then return
266            self.rrset?;
267
268            // getting here, we must have exhausted our records from the rrset
269            cfg_if! {
270                if #[cfg(feature = "__dnssec")] {
271                    self.records = Some(
272                        self.rrset
273                            .expect("rrset should not be None at this point")
274                            .records(self.lookup_options.dnssec_ok()),
275                    );
276                } else {
277                    self.records = Some(self.rrset.expect("rrset should not be None at this point").records_without_rrsigs());
278                }
279            }
280        }
281    }
282}
283
284/// The result of a lookup
285#[derive(Debug)]
286pub enum LookupRecords {
287    /// The empty set of records
288    Empty,
289    /// The associate records
290    Records {
291        /// LookupOptions for the request, e.g. dnssec
292        lookup_options: LookupOptions,
293        /// the records found based on the query
294        records: Arc<RecordSet>,
295    },
296    /// Vec of disjoint record sets
297    ManyRecords(LookupOptions, Vec<Arc<RecordSet>>),
298    // TODO: need a better option for very large zone xfrs...
299    /// A generic lookup response where anything is desired
300    AnyRecords(AnyRecords),
301}
302
303impl LookupRecords {
304    /// Construct a new LookupRecords
305    pub fn new(lookup_options: LookupOptions, records: Arc<RecordSet>) -> Self {
306        Self::Records {
307            lookup_options,
308            records,
309        }
310    }
311
312    /// Construct a new LookupRecords over a set of RecordSets
313    pub fn many(lookup_options: LookupOptions, mut records: Vec<Arc<RecordSet>>) -> Self {
314        // we're reversing the records because they are output in reverse order, via pop()
315        records.reverse();
316        Self::ManyRecords(lookup_options, records)
317    }
318
319    /// This is an NxDomain or NameExists, and has no associated records
320    ///
321    /// this consumes the iterator, and verifies it is empty
322    pub fn was_empty(&self) -> bool {
323        self.iter().count() == 0
324    }
325
326    /// Conversion to an iterator
327    pub fn iter(&self) -> LookupRecordsIter<'_> {
328        self.into_iter()
329    }
330}
331
332impl Default for LookupRecords {
333    fn default() -> Self {
334        Self::Empty
335    }
336}
337
338impl<'a> IntoIterator for &'a LookupRecords {
339    type Item = &'a Record;
340    type IntoIter = LookupRecordsIter<'a>;
341
342    #[allow(unused_variables)]
343    fn into_iter(self) -> Self::IntoIter {
344        match self {
345            LookupRecords::Empty => LookupRecordsIter::Empty,
346            LookupRecords::Records {
347                lookup_options,
348                records,
349            } => LookupRecordsIter::RecordsIter(lookup_options.rrset_with_rrigs(records)),
350            LookupRecords::ManyRecords(lookup_options, r) => LookupRecordsIter::ManyRecordsIter(
351                r.iter()
352                    .map(|r| lookup_options.rrset_with_rrigs(r))
353                    .collect(),
354                None,
355            ),
356            LookupRecords::AnyRecords(r) => LookupRecordsIter::AnyRecordsIter(r.iter()),
357        }
358    }
359}
360
361/// Iterator over lookup records
362#[derive(Default)]
363pub enum LookupRecordsIter<'r> {
364    /// An iteration over batch record type results
365    AnyRecordsIter(AnyRecordsIter<'r>),
366    /// An iteration over a single RecordSet
367    RecordsIter(RrsetRecords<'r>),
368    /// An iteration over many rrsets
369    ManyRecordsIter(Vec<RrsetRecords<'r>>, Option<RrsetRecords<'r>>),
370    /// An empty set
371    #[default]
372    Empty,
373}
374
375impl<'r> Iterator for LookupRecordsIter<'r> {
376    type Item = &'r Record;
377
378    fn next(&mut self) -> Option<Self::Item> {
379        match self {
380            LookupRecordsIter::Empty => None,
381            LookupRecordsIter::AnyRecordsIter(current) => current.next(),
382            LookupRecordsIter::RecordsIter(current) => current.next(),
383            LookupRecordsIter::ManyRecordsIter(set, current) => loop {
384                if let Some(o) = current.as_mut().and_then(Iterator::next) {
385                    return Some(o);
386                }
387
388                *current = set.pop();
389                if current.is_none() {
390                    return None;
391                }
392            },
393        }
394    }
395}
396
397impl From<AnyRecords> for LookupRecords {
398    fn from(rrset_records: AnyRecords) -> Self {
399        Self::AnyRecords(rrset_records)
400    }
401}
402
403impl LookupObject for LookupRecords {
404    fn is_empty(&self) -> bool {
405        Self::was_empty(self)
406    }
407
408    fn iter<'a>(&'a self) -> Box<dyn Iterator<Item = &'a Record> + Send + 'a> {
409        Box::new(self.iter())
410    }
411
412    fn take_additionals(&mut self) -> Option<Box<dyn LookupObject>> {
413        None
414    }
415}