hickory_server/authority/
catalog.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// TODO, I've implemented this as a separate entity from the cache, but I wonder if the cache
9//  should be the only "front-end" for lookups, where if that misses, then we go to the catalog
10//  then, if requested, do a recursive lookup... i.e. the catalog would only point to files.
11use std::{borrow::Borrow, collections::HashMap, future::Future, io};
12
13use cfg_if::cfg_if;
14use tracing::{debug, error, info, trace, warn};
15
16#[cfg(feature = "dnssec")]
17use crate::proto::rr::{
18    dnssec::{Algorithm, SupportedAlgorithms},
19    rdata::opt::{EdnsCode, EdnsOption},
20};
21use crate::{
22    authority::{
23        AuthLookup, AuthorityObject, EmptyLookup, LookupError, LookupObject, LookupOptions,
24        MessageResponse, MessageResponseBuilder, ZoneType,
25    },
26    proto::op::{Edns, Header, LowerQuery, MessageType, OpCode, ResponseCode},
27    proto::rr::{LowerName, Record, RecordType},
28    server::{Request, RequestHandler, RequestInfo, ResponseHandler, ResponseInfo},
29};
30
31/// Set of authorities, zones, available to this server.
32#[derive(Default)]
33pub struct Catalog {
34    authorities: HashMap<LowerName, Box<dyn AuthorityObject>>,
35}
36
37#[allow(unused_mut, unused_variables)]
38async fn send_response<'a, R: ResponseHandler>(
39    response_edns: Option<Edns>,
40    mut response: MessageResponse<
41        '_,
42        'a,
43        impl Iterator<Item = &'a Record> + Send + 'a,
44        impl Iterator<Item = &'a Record> + Send + 'a,
45        impl Iterator<Item = &'a Record> + Send + 'a,
46        impl Iterator<Item = &'a Record> + Send + 'a,
47    >,
48    mut response_handle: R,
49) -> io::Result<ResponseInfo> {
50    if let Some(mut resp_edns) = response_edns {
51        #[cfg(feature = "dnssec")]
52        {
53            // set edns DAU and DHU
54            // send along the algorithms which are supported by this authority
55            let mut algorithms = SupportedAlgorithms::default();
56            algorithms.set(Algorithm::RSASHA256);
57            algorithms.set(Algorithm::ECDSAP256SHA256);
58            algorithms.set(Algorithm::ECDSAP384SHA384);
59            algorithms.set(Algorithm::ED25519);
60
61            let dau = EdnsOption::DAU(algorithms);
62            let dhu = EdnsOption::DHU(algorithms);
63
64            resp_edns.options_mut().insert(dau);
65            resp_edns.options_mut().insert(dhu);
66        }
67        response.set_edns(resp_edns);
68    }
69
70    response_handle.send_response(response).await
71}
72
73#[async_trait::async_trait]
74impl RequestHandler for Catalog {
75    /// Determines what needs to happen given the type of request, i.e. Query or Update.
76    ///
77    /// # Arguments
78    ///
79    /// * `request` - the requested action to perform.
80    /// * `response_handle` - sink for the response message to be sent
81    async fn handle_request<R: ResponseHandler>(
82        &self,
83        request: &Request,
84        mut response_handle: R,
85    ) -> ResponseInfo {
86        trace!("request: {:?}", request);
87
88        let response_edns: Option<Edns>;
89
90        // check if it's edns
91        if let Some(req_edns) = request.edns() {
92            let mut response = MessageResponseBuilder::new(Some(request.raw_query()));
93            let mut response_header = Header::response_from_request(request.header());
94
95            let mut resp_edns: Edns = Edns::new();
96
97            // check our version against the request
98            // TODO: what version are we?
99            let our_version = 0;
100            resp_edns.set_dnssec_ok(true);
101            resp_edns.set_max_payload(req_edns.max_payload().max(512));
102            resp_edns.set_version(our_version);
103
104            if req_edns.version() > our_version {
105                warn!(
106                    "request edns version greater than {}: {}",
107                    our_version,
108                    req_edns.version()
109                );
110                response_header.set_response_code(ResponseCode::BADVERS);
111                resp_edns.set_rcode_high(ResponseCode::BADVERS.high());
112                response.edns(resp_edns);
113
114                // TODO: should ResponseHandle consume self?
115                let result = response_handle
116                    .send_response(response.build_no_records(response_header))
117                    .await;
118
119                // couldn't handle the request
120                return match result {
121                    Err(e) => {
122                        error!("request error: {}", e);
123                        ResponseInfo::serve_failed()
124                    }
125                    Ok(info) => info,
126                };
127            }
128
129            response_edns = Some(resp_edns);
130        } else {
131            response_edns = None;
132        }
133
134        let result = match request.message_type() {
135            // TODO think about threading query lookups for multiple lookups, this could be a huge improvement
136            //  especially for recursive lookups
137            MessageType::Query => match request.op_code() {
138                OpCode::Query => {
139                    debug!("query received: {}", request.id());
140                    let info = self.lookup(request, response_edns, response_handle).await;
141
142                    Ok(info)
143                }
144                OpCode::Update => {
145                    debug!("update received: {}", request.id());
146                    self.update(request, response_edns, response_handle).await
147                }
148                c => {
149                    warn!("unimplemented op_code: {:?}", c);
150                    let response = MessageResponseBuilder::new(Some(request.raw_query()));
151
152                    response_handle
153                        .send_response(response.error_msg(request.header(), ResponseCode::NotImp))
154                        .await
155                }
156            },
157            MessageType::Response => {
158                warn!("got a response as a request from id: {}", request.id());
159                let response = MessageResponseBuilder::new(Some(request.raw_query()));
160
161                response_handle
162                    .send_response(response.error_msg(request.header(), ResponseCode::FormErr))
163                    .await
164            }
165        };
166
167        match result {
168            Err(e) => {
169                error!("request failed: {}", e);
170                ResponseInfo::serve_failed()
171            }
172            Ok(info) => info,
173        }
174    }
175}
176
177impl Catalog {
178    /// Constructs a new Catalog
179    pub fn new() -> Self {
180        Self {
181            authorities: HashMap::new(),
182        }
183    }
184
185    /// Insert or update a zone authority
186    ///
187    /// # Arguments
188    ///
189    /// * `name` - zone name, e.g. example.com.
190    /// * `authority` - the zone data
191    pub fn upsert(&mut self, name: LowerName, authority: Box<dyn AuthorityObject>) {
192        self.authorities.insert(name, authority);
193    }
194
195    /// Remove a zone from the catalog
196    pub fn remove(&mut self, name: &LowerName) -> Option<Box<dyn AuthorityObject>> {
197        self.authorities.remove(name)
198    }
199
200    /// Update the zone given the Update request.
201    ///
202    /// [RFC 2136](https://tools.ietf.org/html/rfc2136), DNS Update, April 1997
203    ///
204    /// ```text
205    /// 3.1 - Process Zone Section
206    ///
207    ///   3.1.1. The Zone Section is checked to see that there is exactly one
208    ///   RR therein and that the RR's ZTYPE is SOA, else signal FORMERR to the
209    ///   requestor.  Next, the ZNAME and ZCLASS are checked to see if the zone
210    ///   so named is one of this server's authority zones, else signal NOTAUTH
211    ///   to the requestor.  If the server is a zone Secondary, the request will be
212    ///   forwarded toward the Primary Zone Server.
213    ///
214    ///   3.1.2 - Pseudocode For Zone Section Processing
215    ///
216    ///      if (zcount != 1 || ztype != SOA)
217    ///           return (FORMERR)
218    ///      if (zone_type(zname, zclass) == SECONDARY)
219    ///           return forward()
220    ///      if (zone_type(zname, zclass) == PRIMARY)
221    ///           return update()
222    ///      return (NOTAUTH)
223    ///
224    ///   Sections 3.2 through 3.8 describe the primary's behaviour,
225    ///   whereas Section 6 describes a forwarder's behaviour.
226    ///
227    /// 3.8 - Response
228    ///
229    ///   At the end of UPDATE processing, a response code will be known.  A
230    ///   response message is generated by copying the ID and Opcode fields
231    ///   from the request, and either copying the ZOCOUNT, PRCOUNT, UPCOUNT,
232    ///   and ADCOUNT fields and associated sections, or placing zeros (0) in
233    ///   the these "count" fields and not including any part of the original
234    ///   update.  The QR bit is set to one (1), and the response is sent back
235    ///   to the requestor.  If the requestor used UDP, then the response will
236    ///   be sent to the requestor's source UDP port.  If the requestor used
237    ///   TCP, then the response will be sent back on the requestor's open TCP
238    ///   connection.
239    /// ```
240    ///
241    /// The "request" should be an update formatted message.
242    ///  The response will be in the alternate, all 0's format described in RFC 2136 section 3.8
243    ///  as this is more efficient.
244    ///
245    /// # Arguments
246    ///
247    /// * `request` - an update message
248    /// * `response_handle` - sink for the response message to be sent
249    pub async fn update<R: ResponseHandler>(
250        &self,
251        update: &Request,
252        response_edns: Option<Edns>,
253        response_handle: R,
254    ) -> io::Result<ResponseInfo> {
255        let request_info = update.request_info();
256
257        let verify_request = move || -> Result<RequestInfo<'_>, ResponseCode> {
258            // 2.3 - Zone Section
259            //
260            //  All records to be updated must be in the same zone, and
261            //  therefore the Zone Section is allowed to contain exactly one record.
262            //  The ZNAME is the zone name, the ZTYPE must be SOA, and the ZCLASS is
263            //  the zone's class.
264
265            let ztype = request_info.query.query_type();
266
267            if ztype != RecordType::SOA {
268                warn!(
269                    "invalid update request zone type must be SOA, ztype: {}",
270                    ztype
271                );
272                return Err(ResponseCode::FormErr);
273            }
274
275            Ok(request_info)
276        };
277
278        // verify the zone type and number of zones in request, then find the zone to update
279        let request_info = verify_request();
280        let authority = request_info.as_ref().map_err(|e| *e).and_then(|info| {
281            self.find(info.query.name())
282                .map(|a| a.box_clone())
283                .ok_or(ResponseCode::Refused)
284        });
285
286        let response_code = match authority {
287            Ok(authority) => {
288                #[allow(deprecated)]
289                match authority.zone_type() {
290                    ZoneType::Secondary | ZoneType::Slave => {
291                        error!("secondary forwarding for update not yet implemented");
292                        ResponseCode::NotImp
293                    }
294                    ZoneType::Primary | ZoneType::Master => {
295                        let update_result = authority.update(update).await;
296                        match update_result {
297                            // successful update
298                            Ok(..) => ResponseCode::NoError,
299                            Err(response_code) => response_code,
300                        }
301                    }
302                    _ => ResponseCode::NotAuth,
303                }
304            }
305            Err(response_code) => response_code,
306        };
307
308        let response = MessageResponseBuilder::new(Some(update.raw_query()));
309        let mut response_header = Header::default();
310        response_header.set_id(update.id());
311        response_header.set_op_code(OpCode::Update);
312        response_header.set_message_type(MessageType::Response);
313        response_header.set_response_code(response_code);
314
315        send_response(
316            response_edns,
317            response.build_no_records(response_header),
318            response_handle,
319        )
320        .await
321    }
322
323    /// Checks whether the `Catalog` contains DNS records for `name`
324    ///
325    /// Use this when you know the exact `LowerName` that was used when
326    /// adding an authority and you don't care about the authority it
327    /// contains. For public domain names, `LowerName` is usually the
328    /// top level domain name like `example.com.`.
329    ///
330    /// If you do not know the exact domain name to use or you actually
331    /// want to use the authority it contains, use `find` instead.
332    pub fn contains(&self, name: &LowerName) -> bool {
333        self.authorities.contains_key(name)
334    }
335
336    /// Given the requested query, lookup and return any matching results.
337    ///
338    /// # Arguments
339    ///
340    /// * `request` - the query message.
341    /// * `response_handle` - sink for the response message to be sent
342    pub async fn lookup<R: ResponseHandler>(
343        &self,
344        request: &Request,
345        response_edns: Option<Edns>,
346        response_handle: R,
347    ) -> ResponseInfo {
348        let request_info = request.request_info();
349        let authority = self.find(request_info.query.name());
350
351        if let Some(authority) = authority {
352            lookup(
353                request_info,
354                authority,
355                request,
356                response_edns
357                    .as_ref()
358                    .map(|arc| Borrow::<Edns>::borrow(arc).clone()),
359                response_handle.clone(),
360            )
361            .await
362        } else {
363            // if this is empty then the there are no authorities registered that can handle the request
364            let response = MessageResponseBuilder::new(Some(request.raw_query()));
365
366            let result = send_response(
367                response_edns,
368                response.error_msg(request.header(), ResponseCode::Refused),
369                response_handle,
370            )
371            .await;
372
373            match result {
374                Err(e) => {
375                    error!("failed to send response: {}", e);
376                    ResponseInfo::serve_failed()
377                }
378                Ok(r) => r,
379            }
380        }
381    }
382
383    /// Recursively searches the catalog for a matching authority
384    pub fn find(&self, name: &LowerName) -> Option<&(dyn AuthorityObject + 'static)> {
385        debug!("searching authorities for: {}", name);
386        self.authorities
387            .get(name)
388            .map(|authority| &**authority)
389            .or_else(|| {
390                if !name.is_root() {
391                    let name = name.base_name();
392                    self.find(&name)
393                } else {
394                    None
395                }
396            })
397    }
398}
399
400async fn lookup<R: ResponseHandler + Unpin>(
401    request_info: RequestInfo<'_>,
402    authority: &dyn AuthorityObject,
403    request: &Request,
404    response_edns: Option<Edns>,
405    response_handle: R,
406) -> ResponseInfo {
407    let query = request_info.query;
408    debug!(
409        "request: {} found authority: {}",
410        request.id(),
411        authority.origin()
412    );
413
414    let (response_header, sections) = build_response(
415        authority,
416        request_info,
417        request.id(),
418        request.header(),
419        query,
420        request.edns(),
421    )
422    .await;
423
424    let response = MessageResponseBuilder::new(Some(request.raw_query())).build(
425        response_header,
426        sections.answers.iter(),
427        sections.ns.iter(),
428        sections.soa.iter(),
429        sections.additionals.iter(),
430    );
431
432    let result = send_response(response_edns.clone(), response, response_handle.clone()).await;
433
434    match result {
435        Err(e) => {
436            error!("error sending response: {}", e);
437            ResponseInfo::serve_failed()
438        }
439        Ok(i) => i,
440    }
441}
442
443#[allow(unused_variables)]
444fn lookup_options_for_edns(edns: Option<&Edns>) -> LookupOptions {
445    let edns = match edns {
446        Some(edns) => edns,
447        None => return LookupOptions::default(),
448    };
449
450    cfg_if! {
451        if #[cfg(feature = "dnssec")] {
452            let supported_algorithms = if let Some(&EdnsOption::DAU(algs)) = edns.option(EdnsCode::DAU)
453            {
454               algs
455            } else {
456               debug!("no DAU in request, used default SupportAlgorithms");
457               SupportedAlgorithms::default()
458            };
459
460            LookupOptions::for_dnssec(edns.dnssec_ok(), supported_algorithms)
461        } else {
462            LookupOptions::default()
463        }
464    }
465}
466
467async fn build_response(
468    authority: &dyn AuthorityObject,
469    request_info: RequestInfo<'_>,
470    request_id: u16,
471    request_header: &Header,
472    query: &LowerQuery,
473    edns: Option<&Edns>,
474) -> (Header, LookupSections) {
475    let lookup_options = lookup_options_for_edns(edns);
476
477    // log algorithms being requested
478    if lookup_options.is_dnssec() {
479        info!(
480            "request: {} lookup_options: {:?}",
481            request_id, lookup_options
482        );
483    }
484
485    let mut response_header = Header::response_from_request(request_header);
486    response_header.set_authoritative(authority.zone_type().is_authoritative());
487
488    debug!("performing {} on {}", query, authority.origin());
489    let future = authority.search(request_info, lookup_options);
490
491    #[allow(deprecated)]
492    let sections = match authority.zone_type() {
493        ZoneType::Primary | ZoneType::Secondary | ZoneType::Master | ZoneType::Slave => {
494            send_authoritative_response(
495                future,
496                authority,
497                &mut response_header,
498                lookup_options,
499                request_id,
500                query,
501            )
502            .await
503        }
504        ZoneType::Forward | ZoneType::Hint => {
505            send_forwarded_response(future, request_header, &mut response_header).await
506        }
507    };
508
509    (response_header, sections)
510}
511
512async fn send_authoritative_response(
513    future: impl Future<Output = Result<Box<dyn LookupObject>, LookupError>>,
514    authority: &dyn AuthorityObject,
515    response_header: &mut Header,
516    lookup_options: LookupOptions,
517    request_id: u16,
518    query: &LowerQuery,
519) -> LookupSections {
520    // In this state we await the records, on success we transition to getting
521    // NS records, which indicate an authoritative response.
522    //
523    // On Errors, the transition depends on the type of error.
524    let answers = match future.await {
525        Ok(records) => {
526            response_header.set_response_code(ResponseCode::NoError);
527            response_header.set_authoritative(true);
528            Some(records)
529        }
530        // This request was refused
531        // TODO: there are probably other error cases that should just drop through (FormErr, ServFail)
532        Err(LookupError::ResponseCode(ResponseCode::Refused)) => {
533            response_header.set_response_code(ResponseCode::Refused);
534            return LookupSections {
535                answers: Box::<AuthLookup>::default(),
536                ns: Box::<AuthLookup>::default(),
537                soa: Box::<AuthLookup>::default(),
538                additionals: Box::<AuthLookup>::default(),
539            };
540        }
541        Err(e) => {
542            if e.is_nx_domain() {
543                response_header.set_response_code(ResponseCode::NXDomain);
544            } else if e.is_name_exists() {
545                response_header.set_response_code(ResponseCode::NoError);
546            };
547            None
548        }
549    };
550
551    let (ns, soa) = if answers.is_some() {
552        // SOA queries should return the NS records as well.
553        if query.query_type().is_soa() {
554            // This was a successful authoritative lookup for SOA:
555            //   get the NS records as well.
556            match authority.ns(lookup_options).await {
557                Ok(ns) => (Some(ns), None),
558                Err(e) => {
559                    warn!("ns_lookup errored: {}", e);
560                    (None, None)
561                }
562            }
563        } else {
564            (None, None)
565        }
566    } else {
567        let nsecs = if lookup_options.is_dnssec() {
568            // in the dnssec case, nsec records should exist, we return NoError + NoData + NSec...
569            debug!("request: {} non-existent adding nsecs", request_id);
570            // run the nsec lookup future, and then transition to get soa
571            let future = authority.get_nsec_records(query.name(), lookup_options);
572            match future.await {
573                // run the soa lookup
574                Ok(nsecs) => Some(nsecs),
575                Err(e) => {
576                    warn!("failed to lookup nsecs: {}", e);
577                    None
578                }
579            }
580        } else {
581            None
582        };
583
584        match authority.soa_secure(lookup_options).await {
585            Ok(soa) => (nsecs, Some(soa)),
586            Err(e) => {
587                warn!("failed to lookup soa: {}", e);
588                (nsecs, None)
589            }
590        }
591    };
592
593    // everything is done, return results.
594    let (answers, additionals) = match answers {
595        Some(mut answers) => match answers.take_additionals() {
596            Some(additionals) => (answers, additionals),
597            None => (
598                answers,
599                Box::<AuthLookup>::default() as Box<dyn LookupObject>,
600            ),
601        },
602        None => (
603            Box::<AuthLookup>::default() as Box<dyn LookupObject>,
604            Box::<AuthLookup>::default() as Box<dyn LookupObject>,
605        ),
606    };
607
608    LookupSections {
609        answers,
610        ns: ns.unwrap_or_else(|| Box::<AuthLookup>::default()),
611        soa: soa.unwrap_or_else(|| Box::<AuthLookup>::default()),
612        additionals,
613    }
614}
615
616async fn send_forwarded_response(
617    future: impl Future<Output = Result<Box<dyn LookupObject>, LookupError>>,
618    request_header: &Header,
619    response_header: &mut Header,
620) -> LookupSections {
621    response_header.set_recursion_available(true);
622    response_header.set_authoritative(false);
623
624    // Don't perform the recursive query if this is disabled...
625    let answers = if !request_header.recursion_desired() {
626        // cancel the future??
627        // future.cancel();
628        drop(future);
629
630        info!(
631            "request disabled recursion, returning no records: {}",
632            request_header.id()
633        );
634
635        Box::new(EmptyLookup)
636    } else {
637        match future.await {
638            Err(e) => {
639                if e.is_nx_domain() {
640                    response_header.set_response_code(ResponseCode::NXDomain);
641                }
642                debug!("error resolving: {}", e);
643                Box::new(EmptyLookup)
644            }
645            Ok(rsp) => rsp,
646        }
647    };
648
649    LookupSections {
650        answers,
651        ns: Box::<AuthLookup>::default(),
652        soa: Box::<AuthLookup>::default(),
653        additionals: Box::<AuthLookup>::default(),
654    }
655}
656
657struct LookupSections {
658    answers: Box<dyn LookupObject>,
659    ns: Box<dyn LookupObject>,
660    soa: Box<dyn LookupObject>,
661    additionals: Box<dyn LookupObject>,
662}