Skip to main content

hickory_server/zone_handler/
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::{collections::HashMap, iter, sync::Arc};
12
13use tracing::{debug, error, info, trace, warn};
14
15#[cfg(feature = "metrics")]
16use crate::metrics::CatalogMetrics;
17#[cfg(feature = "__dnssec")]
18use crate::{
19    dnssec::NxProofKind,
20    proto::{
21        dnssec::{DnssecSummary, rdata::DNSSECRData},
22        rr::RData,
23        serialize::binary::BinEncoder,
24    },
25    zone_handler::Nsec3QueryInfo,
26};
27use crate::{
28    net::runtime::Time,
29    proto::{
30        op::{Edns, LowerQuery, Message, MessageType, Metadata, OpCode, ResponseCode},
31        rr::{
32            LowerName, RecordSet, RecordType,
33            rdata::opt::{EdnsCode, EdnsOption, NSIDPayload},
34        },
35    },
36    server::{Request, RequestHandler, RequestInfo, ResponseHandler, ResponseInfo},
37    zone_handler::{
38        AuthLookup, LookupControlFlow, LookupError, LookupOptions, LookupRecords,
39        MessageResponseBuilder, ZoneHandler, ZoneType,
40    },
41};
42#[cfg(all(feature = "__dnssec", feature = "recursor"))]
43use crate::{
44    net::{DnsError, NetError},
45    resolver::recursor,
46};
47
48/// Set of zones and zone handlers available to this server.
49#[derive(Default)]
50pub struct Catalog {
51    nsid_payload: Option<NSIDPayload>,
52    handlers: HashMap<LowerName, Vec<Arc<dyn ZoneHandler>>>,
53    #[cfg(feature = "metrics")]
54    metrics: CatalogMetrics,
55}
56
57#[async_trait::async_trait]
58impl RequestHandler for Catalog {
59    /// Determines what needs to happen given the type of request, i.e. Query or Update.
60    ///
61    /// # Arguments
62    ///
63    /// * `request` - the requested action to perform.
64    /// * `response_handle` - sink for the response message to be sent
65    async fn handle_request<R: ResponseHandler, T: Time>(
66        &self,
67        request: &Request,
68        response_handle: R,
69    ) -> ResponseInfo {
70        trace!("request: {:?}", request);
71
72        let mut resp_edns: Edns;
73
74        // check if it's edns
75        let response_edns = if let Some(req_edns) = request.edns.as_ref() {
76            resp_edns = Edns::new();
77
78            // check our version against the request
79            // TODO: what version are we?
80            let our_version = 0;
81            resp_edns.set_dnssec_ok(true);
82            resp_edns.set_max_payload(req_edns.max_payload().max(512));
83            resp_edns.set_version(our_version);
84
85            if req_edns.version() > our_version {
86                warn!(
87                    "request edns version greater than {}: {}",
88                    our_version,
89                    req_edns.version()
90                );
91                return send_error_response(
92                    request,
93                    ResponseCode::BADVERS,
94                    Some(&resp_edns),
95                    response_handle,
96                )
97                .await;
98            }
99
100            // RFC 5001 "DNS Name Server Identifier (NSID) Option" handling.
101            match (req_edns.option(EdnsCode::NSID), &self.nsid_payload) {
102                // NSID was requested, and we have a payload to reply with. Add it to the
103                // response EDNS.
104                (Some(request_option), Some(payload)) => {
105                    // "The name server MUST ignore any NSID payload data that might be
106                    //  present in the query message."
107                    if !request_option.is_empty() {
108                        warn!("ignoring non-empty EDNS NSID request payload")
109                    }
110                    resp_edns
111                        .options_mut()
112                        .insert(EdnsOption::NSID(payload.clone()));
113                }
114                // NSID was requested, but we don't have a payload configured.
115                (Some(_), None) => {
116                    trace!("ignoring EDNS NSID request - no response payload configured")
117                }
118                // "A name server MUST NOT send an NSID option back to a resolver which
119                // did not request it."
120                (None, _) => {}
121            };
122
123            Some(&resp_edns)
124        } else {
125            None
126        };
127
128        let now = T::current_time();
129        match request.metadata.message_type {
130            // TODO think about threading query lookups for multiple lookups, this could be a huge improvement
131            //  especially for recursive lookups
132            MessageType::Query => match request.metadata.op_code {
133                OpCode::Query => {
134                    debug!("query received: {}", request.metadata.id);
135                    self.lookup(request, response_edns, now, response_handle)
136                        .await
137                }
138                OpCode::Update => {
139                    debug!("update received: {}", request.metadata.id);
140                    self.update(request, response_edns, now, response_handle)
141                        .await
142                }
143                c => {
144                    warn!("unimplemented op_code: {:?}", c);
145                    send_error_response(
146                        request,
147                        ResponseCode::NotImp,
148                        response_edns,
149                        response_handle,
150                    )
151                    .await
152                }
153            },
154            MessageType::Response => {
155                warn!(
156                    "got a response as a request from id: {}",
157                    request.metadata.id
158                );
159                send_error_response(
160                    request,
161                    ResponseCode::FormErr,
162                    response_edns,
163                    response_handle,
164                )
165                .await
166            }
167        }
168    }
169}
170
171impl Catalog {
172    /// Constructs a new Catalog
173    pub fn new() -> Self {
174        Self {
175            handlers: HashMap::new(),
176            nsid_payload: None,
177            #[cfg(feature = "metrics")]
178            metrics: CatalogMetrics::default(),
179        }
180    }
181
182    /// Insert or update the provided zone handlers
183    ///
184    /// # Arguments
185    ///
186    /// * `name` - zone name, e.g. example.com.
187    /// * `handlers` - a vec of zone handler objects
188    pub fn upsert(&mut self, name: LowerName, handlers: Vec<Arc<dyn ZoneHandler>>) {
189        #[cfg(feature = "metrics")]
190        for handler in handlers.iter() {
191            self.metrics.add_handler(handler.as_ref())
192        }
193
194        self.handlers.insert(name, handlers);
195    }
196
197    /// Remove a zone from the catalog
198    pub fn remove(&mut self, name: &LowerName) -> Option<Vec<Arc<dyn ZoneHandler>>> {
199        // NOTE: metrics are not removed to avoid dropping counters that are potentially still
200        // being used by other zone handlers having the same labels
201        self.handlers.remove(name)
202    }
203
204    /// Set a specified name server identifier (NSID) in responses
205    ///
206    /// The provided `NSIDPayload` will be included in responses to requests that
207    /// specify the NSID option in EDNS. Set to `None` to disable NSID.
208    ///
209    /// By default, no NSID is sent.
210    pub fn set_nsid(&mut self, payload: Option<NSIDPayload>) {
211        self.nsid_payload = payload
212    }
213
214    /// Return the name server identifier (NSID) that is used for responses (if enabled)
215    ///
216    /// See `set_nsid()` for more information.
217    pub fn nsid(&self) -> Option<&NSIDPayload> {
218        self.nsid_payload.as_ref()
219    }
220
221    /// Update the zone given the Update request.
222    ///
223    /// [RFC 2136](https://tools.ietf.org/html/rfc2136), DNS Update, April 1997
224    ///
225    /// ```text
226    /// 3.1 - Process Zone Section
227    ///
228    ///   3.1.1. The Zone Section is checked to see that there is exactly one
229    ///   RR therein and that the RR's ZTYPE is SOA, else signal FORMERR to the
230    ///   requestor.  Next, the ZNAME and ZCLASS are checked to see if the zone
231    ///   so named is one of this server's authority zones, else signal NOTAUTH
232    ///   to the requestor.  If the server is a zone Secondary, the request will be
233    ///   forwarded toward the Primary Zone Server.
234    ///
235    ///   3.1.2 - Pseudocode For Zone Section Processing
236    ///
237    ///      if (zcount != 1 || ztype != SOA)
238    ///           return (FORMERR)
239    ///      if (zone_type(zname, zclass) == SECONDARY)
240    ///           return forward()
241    ///      if (zone_type(zname, zclass) == PRIMARY)
242    ///           return update()
243    ///      return (NOTAUTH)
244    ///
245    ///   Sections 3.2 through 3.8 describe the primary's behaviour,
246    ///   whereas Section 6 describes a forwarder's behaviour.
247    ///
248    /// 3.8 - Response
249    ///
250    ///   At the end of UPDATE processing, a response code will be known.  A
251    ///   response message is generated by copying the ID and Opcode fields
252    ///   from the request, and either copying the ZOCOUNT, PRCOUNT, UPCOUNT,
253    ///   and ADCOUNT fields and associated sections, or placing zeros (0) in
254    ///   the these "count" fields and not including any part of the original
255    ///   update.  The QR bit is set to one (1), and the response is sent back
256    ///   to the requestor.  If the requestor used UDP, then the response will
257    ///   be sent to the requestor's source UDP port.  If the requestor used
258    ///   TCP, then the response will be sent back on the requestor's open TCP
259    ///   connection.
260    /// ```
261    ///
262    /// The "request" should be an update formatted message.
263    ///  The response will be in the alternate, all 0's format described in RFC 2136 section 3.8
264    ///  as this is more efficient.
265    ///
266    /// # Arguments
267    ///
268    /// * `request` - an update message
269    /// * `response_edns` an optional `Edns` value for the response message
270    /// * `response_handle` - sink for the response message to be sent
271    pub async fn update<R: ResponseHandler>(
272        &self,
273        update: &Request,
274        response_edns: Option<&Edns>,
275        now: u64,
276        mut response_handle: R,
277    ) -> ResponseInfo {
278        // 2.3 - Zone Section
279        //
280        //  All records to be updated must be in the same zone, and
281        //  therefore the Zone Section is allowed to contain exactly one record.
282        //  The ZNAME is the zone name, the ZTYPE must be SOA, and the ZCLASS is
283        //  the zone's class.
284
285        let Ok(request_info) = update.request_info() else {
286            warn!("invalid update request, zone count must be one");
287            return send_error_response(
288                update,
289                ResponseCode::FormErr,
290                response_edns,
291                response_handle,
292            )
293            .await;
294        };
295        let ztype = request_info.query.query_type();
296
297        if ztype != RecordType::SOA {
298            warn!("invalid update request zone type must be SOA, ztype: {ztype}");
299            return send_error_response(
300                update,
301                ResponseCode::FormErr,
302                response_edns,
303                response_handle,
304            )
305            .await;
306        }
307
308        // verify the zone type and number of zones in request, then find the zone to update
309        if let Some(handlers) = self.find(request_info.query.name()) {
310            #[allow(clippy::never_loop)]
311            for handler in handlers {
312                #[cfg_attr(not(feature = "__dnssec"), expect(unused))]
313                let (response_code, signer) = match handler.zone_type() {
314                    ZoneType::Secondary => {
315                        error!("secondary forwarding for update not yet implemented");
316                        (ResponseCode::NotImp, None)
317                    }
318                    ZoneType::Primary => {
319                        let (update_result, signer) = handler.update(update, now).await;
320                        match update_result {
321                            // successful update
322                            Ok(_) => (ResponseCode::NoError, signer),
323                            Err(response_code) => (response_code, signer),
324                        }
325                    }
326                    _ => (ResponseCode::NotAuth, None),
327                };
328
329                let response = MessageResponseBuilder::new(&update.queries, response_edns);
330                let mut response_meta =
331                    Metadata::new(update.metadata.id, MessageType::Response, OpCode::Update);
332                response_meta.response_code = response_code;
333                #[cfg_attr(not(feature = "__dnssec"), expect(unused_mut))]
334                let mut response = response.build_no_records(response_meta);
335
336                #[cfg(feature = "__dnssec")]
337                if let Some(signer) = signer {
338                    let mut tbs_response_buf = Vec::with_capacity(512);
339                    let mut encoder = BinEncoder::new(&mut tbs_response_buf);
340                    let mut response_meta =
341                        Metadata::new(update.metadata.id, MessageType::Response, OpCode::Update);
342                    response_meta.response_code = response_code;
343                    let tbs_response = MessageResponseBuilder::new(&update.queries, response_edns)
344                        .build_no_records(response_meta);
345                    if let Err(error) = tbs_response.destructive_emit(&mut encoder) {
346                        error!(%error, "error encoding response");
347                        return send_error_response(
348                            update,
349                            ResponseCode::ServFail,
350                            response_edns,
351                            response_handle,
352                        )
353                        .await;
354                    }
355                    match signer.sign(&tbs_response_buf) {
356                        Ok(signature) => response.set_signature(signature),
357                        Err(error) => {
358                            error!(%error, "error signing response");
359                            return send_error_response(
360                                update,
361                                ResponseCode::ServFail,
362                                response_edns,
363                                response_handle,
364                            )
365                            .await;
366                        }
367                    }
368                }
369
370                match response_handle.send_response(response).await {
371                    Err(error) => {
372                        error!(%error, "error sending message");
373                        return ResponseInfo::serve_failed(update);
374                    }
375                    Ok(response_info) => return response_info,
376                }
377            }
378        };
379
380        send_error_response(
381            update,
382            ResponseCode::ServFail,
383            response_edns,
384            response_handle,
385        )
386        .await
387    }
388
389    /// Checks whether the `Catalog` contains DNS records for `name`
390    ///
391    /// Use this when you know the exact `LowerName` that was used when
392    /// adding a zone handler and you don't care about the zone handler it
393    /// contains. For public domain names, `LowerName` is usually the
394    /// top level domain name like `example.com.`.
395    ///
396    /// If you do not know the exact domain name to use or you actually
397    /// want to use the zone handler it contains, use `find` instead.
398    pub fn contains(&self, name: &LowerName) -> bool {
399        self.handlers.contains_key(name)
400    }
401
402    /// Given the requested query, lookup and return any matching results.
403    ///
404    /// # Arguments
405    ///
406    /// * `request` - the query message.
407    /// * `response_edns` an optional `Edns` value for the response message
408    /// * `response_handle` - sink for the response message to be sent
409    pub async fn lookup<R: ResponseHandler>(
410        &self,
411        request: &Request,
412        response_edns: Option<&Edns>,
413        now: u64,
414        response_handle: R,
415    ) -> ResponseInfo {
416        let Ok(request_info) = request.request_info() else {
417            // Wrong number of queries
418            return send_error_response(
419                request,
420                ResponseCode::FormErr,
421                response_edns,
422                response_handle,
423            )
424            .await;
425        };
426        let handlers = self.find(request_info.query.name());
427
428        let Some(handlers) = handlers else {
429            // There are no zone handlers registered that can handle the request
430            return send_error_response(
431                request,
432                ResponseCode::Refused,
433                response_edns,
434                response_handle,
435            )
436            .await;
437        };
438
439        if request_info.query.query_type() == RecordType::AXFR {
440            zone_transfer(
441                request_info,
442                handlers,
443                request,
444                response_edns,
445                now,
446                response_handle.clone(),
447            )
448            .await
449        } else {
450            lookup(
451                request_info,
452                handlers,
453                request,
454                response_edns,
455                response_handle.clone(),
456                #[cfg(feature = "metrics")]
457                &self.metrics,
458            )
459            .await
460        }
461    }
462
463    /// Recursively searches the catalog for a matching zone handler
464    pub fn find(&self, name: &LowerName) -> Option<&Vec<Arc<dyn ZoneHandler + 'static>>> {
465        debug!("searching zone handlers for: {name}");
466        self.handlers.get(name).or_else(|| {
467            if !name.is_root() {
468                let name = name.base_name();
469                self.find(&name)
470            } else {
471                None
472            }
473        })
474    }
475}
476
477async fn lookup<R: ResponseHandler + Unpin>(
478    request_info: RequestInfo<'_>,
479    handlers: &[Arc<dyn ZoneHandler>],
480    request: &Request,
481    response_edns: Option<&Edns>,
482    mut response_handle: R,
483    #[cfg(feature = "metrics")] metrics: &CatalogMetrics,
484) -> ResponseInfo {
485    let edns = request.edns.as_ref();
486    let lookup_options = LookupOptions::from_edns(edns);
487    let request_id = request.metadata.id;
488
489    if lookup_options.dnssec_ok {
490        info!("request: {request_id} lookup_options: {lookup_options:?}");
491    }
492
493    let query = request_info.query;
494
495    for (index, handler) in handlers.iter().enumerate() {
496        debug!(
497            "performing {query} on zone handler {origin} with request id {request_id}",
498            origin = handler.origin(),
499        );
500
501        // Wait so we can determine if we need to fire a request to the next zone handler in a
502        // chained configuration if the current zone handler declines to answer.
503        #[cfg_attr(not(feature = "__dnssec"), expect(unused))]
504        let (mut result, mut signer) = handler.search(request, lookup_options).await;
505        #[cfg(feature = "metrics")]
506        metrics.update_zone_lookup(handler.as_ref(), &result);
507
508        if let LookupControlFlow::Skip = result {
509            trace!("catalog::lookup: zone handler did not handle request");
510            continue;
511        } else if result.is_continue() {
512            trace!("catalog::lookup: zone handler did handle request with continue");
513
514            // For LookupControlFlow::Continue results, we'll call consult on every
515            // zone handler, except the zone handler that returned the Continue result.
516            for (continue_index, consult_handler) in handlers.iter().enumerate() {
517                if continue_index == index {
518                    trace!("skipping current zone handler consult (index {continue_index})");
519                    continue;
520                } else {
521                    trace!("calling zone handler consult (index {continue_index})");
522                }
523
524                let (new_result, new_signer) = consult_handler
525                    .consult(
526                        request_info.query.name(),
527                        request_info.query.query_type(),
528                        Some(&request_info),
529                        LookupOptions::from_edns(response_edns),
530                        result,
531                    )
532                    .await;
533                #[cfg_attr(not(feature = "__dnssec"), expect(unused))]
534                if let Some(new_signer) = new_signer {
535                    signer = Some(new_signer);
536                }
537                result = new_result;
538            }
539        } else {
540            trace!("catalog::lookup: zone handler did handle request with break");
541        }
542
543        // We no longer need the context from LookupControlFlow, so decompose into a standard Result
544        // to clean up the rest of the match conditions
545        let Some(result) = result.map_result() else {
546            error!("impossible skip detected after final lookup result");
547            return send_error_response(
548                request,
549                ResponseCode::ServFail,
550                response_edns,
551                response_handle,
552            )
553            .await;
554        };
555
556        let response_message = build_response(
557            result,
558            &**handler,
559            request_id,
560            &request.metadata,
561            query,
562            edns,
563        )
564        .await;
565
566        #[cfg_attr(not(feature = "__dnssec"), expect(unused_mut))]
567        let mut message_response = MessageResponseBuilder::new(&request.queries, response_edns)
568            .build(
569                response_message.metadata,
570                response_message.answers.iter(),
571                response_message.authorities.iter(),
572                iter::empty(),
573                response_message.additionals.iter(),
574            );
575
576        #[cfg(feature = "__dnssec")]
577        if let Some(signer) = signer {
578            let mut tbs_response_buf = Vec::with_capacity(512);
579            let mut encoder = BinEncoder::new(&mut tbs_response_buf);
580            let tbs_response = MessageResponseBuilder::new(&request.queries, response_edns).build(
581                response_message.metadata,
582                response_message.answers.iter(),
583                response_message.authorities.iter(),
584                iter::empty(),
585                response_message.additionals.iter(),
586            );
587            if let Err(error) = tbs_response.destructive_emit(&mut encoder) {
588                error!(%error, "error encoding response");
589                return send_error_response(
590                    request,
591                    ResponseCode::ServFail,
592                    response_edns,
593                    response_handle,
594                )
595                .await;
596            }
597            match signer.sign(&tbs_response_buf) {
598                Ok(signature) => message_response.set_signature(signature),
599                Err(error) => {
600                    error!(%error, "error signing response");
601                    return send_error_response(
602                        request,
603                        ResponseCode::ServFail,
604                        response_edns,
605                        response_handle,
606                    )
607                    .await;
608                }
609            }
610        }
611
612        #[cfg(feature = "metrics")]
613        metrics.update_request_response(query, response_message.answers.iter());
614
615        match response_handle.send_response(message_response).await {
616            Err(error) => {
617                error!(%error, "error sending response");
618                return ResponseInfo::serve_failed(request);
619            }
620            Ok(response_info) => return response_info,
621        }
622    }
623
624    error!("end of chained zone handler loop reached with all zone handlers not answering");
625    send_error_response(
626        request,
627        ResponseCode::ServFail,
628        response_edns,
629        response_handle,
630    )
631    .await
632}
633
634async fn zone_transfer(
635    request_info: RequestInfo<'_>,
636    handlers: &[Arc<dyn ZoneHandler>],
637    request: &Request,
638    response_edns: Option<&Edns>,
639    now: u64,
640    mut response_handle: impl ResponseHandler,
641) -> ResponseInfo {
642    let request_edns = request.edns.as_ref();
643    let lookup_options = LookupOptions::from_edns(request_edns);
644    for handler in handlers.iter() {
645        debug!(
646            query = %request_info.query,
647            origin = %handler.origin(),
648            request_id = request.metadata.id,
649            "performing zone transfer"
650        );
651        #[cfg_attr(not(feature = "__dnssec"), expect(unused))]
652        let Some((result, signer)) = handler.zone_transfer(request, lookup_options, now).await
653        else {
654            continue;
655        };
656
657        let mut response_meta = Metadata::response_from_request(&request.metadata);
658        let zone_transfer = match result {
659            Ok(zone_transfer) => {
660                response_meta.response_code = ResponseCode::NoError;
661                response_meta.authoritative = true;
662                Some(zone_transfer)
663            }
664            Err(e) => {
665                match e {
666                    LookupError::ResponseCode(
667                        rcode @ ResponseCode::Refused | rcode @ ResponseCode::NotAuth,
668                    ) => {
669                        response_meta.response_code = rcode;
670                    }
671                    _ => {
672                        if e.is_nx_domain() {
673                            response_meta.response_code = ResponseCode::NXDomain;
674                        }
675                    }
676                }
677                None
678            }
679        };
680
681        // TODO(issue #351): Send more than one message in response as needed.
682        #[cfg_attr(not(feature = "__dnssec"), expect(unused_mut))]
683        let mut message_response = MessageResponseBuilder::new(&request.queries, response_edns)
684            .build(
685                response_meta,
686                zone_transfer
687                    .iter()
688                    .flat_map(|zone_transfer| zone_transfer.iter()),
689                iter::empty(),
690                iter::empty(),
691                iter::empty(),
692            );
693
694        #[cfg(feature = "__dnssec")]
695        if let Some(signer) = signer {
696            let mut tbs_response_buf = Vec::with_capacity(512);
697            let mut encoder = BinEncoder::new(&mut tbs_response_buf);
698            let tbs_response = MessageResponseBuilder::new(&request.queries, response_edns).build(
699                response_meta,
700                zone_transfer
701                    .iter()
702                    .flat_map(|zone_transfer| zone_transfer.iter()),
703                iter::empty(),
704                iter::empty(),
705                iter::empty(),
706            );
707            if let Err(error) = tbs_response.destructive_emit(&mut encoder) {
708                error!(%error, "error encoding response");
709                return send_error_response(
710                    request,
711                    ResponseCode::ServFail,
712                    response_edns,
713                    response_handle,
714                )
715                .await;
716            }
717            match signer.sign(&tbs_response_buf) {
718                Ok(signature) => message_response.set_signature(signature),
719                Err(error) => {
720                    error!(%error, "error signing response");
721                    return send_error_response(
722                        request,
723                        ResponseCode::ServFail,
724                        response_edns,
725                        response_handle,
726                    )
727                    .await;
728                }
729            }
730        }
731
732        match response_handle.send_response(message_response).await {
733            Err(error) => {
734                error!(%error, "error sending response");
735                return ResponseInfo::serve_failed(request);
736            }
737            Ok(response_info) => return response_info,
738        }
739    }
740
741    error!("end of chained zone handler loop with all zone handlers not answering");
742    send_error_response(
743        request,
744        ResponseCode::ServFail,
745        response_edns,
746        response_handle,
747    )
748    .await
749}
750
751/// Helper function to construct a response message with an error response code, and send it via a
752/// response handler.
753async fn send_error_response(
754    request: &Request,
755    response_code: ResponseCode,
756    mut response_edns: Option<&Edns>,
757    mut response_handle: impl ResponseHandler,
758) -> ResponseInfo {
759    let mut new_edns: Edns;
760    if response_code.high() != 0 {
761        if let Some(edns) = response_edns {
762            new_edns = edns.clone();
763            new_edns.set_rcode_high(response_code.high());
764            response_edns = Some(&new_edns);
765        }
766    }
767    let response = MessageResponseBuilder::new(&request.queries, response_edns)
768        .error_msg(&request.metadata, response_code);
769    match response_handle.send_response(response).await {
770        Ok(r) => r,
771        Err(error) => {
772            error!(%error, "failed to send response");
773            ResponseInfo::serve_failed(request)
774        }
775    }
776}
777
778/// Build metadata and LookupSections (answers) given a query response from a zone handler
779async fn build_response(
780    result: Result<AuthLookup, LookupError>,
781    handler: &dyn ZoneHandler,
782    request_id: u16,
783    request_meta: &Metadata,
784    query: &LowerQuery,
785    edns: Option<&Edns>,
786) -> Message {
787    let lookup_options = LookupOptions::from_edns(edns);
788
789    match handler.zone_type() {
790        ZoneType::Primary | ZoneType::Secondary => {
791            build_authoritative_response(
792                result,
793                handler,
794                request_meta,
795                lookup_options,
796                request_id,
797                query,
798            )
799            .await
800        }
801        ZoneType::External => {
802            build_forwarded_response(
803                result,
804                request_meta,
805                #[cfg(feature = "__dnssec")]
806                handler.can_validate_dnssec(),
807                query,
808                lookup_options,
809            )
810            .await
811        }
812    }
813}
814
815/// Prepare a response for an authoritative zone
816async fn build_authoritative_response(
817    response: Result<AuthLookup, LookupError>,
818    handler: &dyn ZoneHandler,
819    request_meta: &Metadata,
820    lookup_options: LookupOptions,
821    _request_id: u16,
822    query: &LowerQuery,
823) -> Message {
824    let mut response_meta = Metadata::response_from_request(request_meta);
825    response_meta.authoritative = true;
826
827    let mut message = Message::new(
828        response_meta.id,
829        response_meta.message_type,
830        response_meta.op_code,
831    );
832    message.add_query(query.original().clone());
833
834    // In this state we await the records, on success we transition to getting
835    // NS records, which indicate an authoritative response.
836    //
837    // On Errors, the transition depends on the type of error.
838    let answers = match response {
839        Ok(records) => {
840            response_meta.response_code = ResponseCode::NoError;
841            Some(records)
842        }
843        // TODO: there are probably other error cases that should just drop through (FormErr, ServFail)
844        Err(LookupError::ResponseCode(
845            rcode @ ResponseCode::Refused | rcode @ ResponseCode::NotAuth,
846        )) => {
847            response_meta.response_code = rcode;
848            message.metadata = response_meta;
849            return message;
850        }
851        Err(e) => {
852            response_meta.response_code = if e.is_nx_domain() {
853                ResponseCode::NXDomain
854            } else {
855                ResponseCode::NoError
856            };
857            None
858        }
859    };
860
861    #[cfg_attr(not(feature = "__dnssec"), allow(unused_variables))]
862    let (ns, soa) = if let Some(answers) = &answers {
863        // SOA queries should return the NS records as well.
864        if query.query_type().is_soa() {
865            // This was a successful authoritative lookup for SOA:
866            //   get the NS records as well.
867
868            let future = handler.lookup(handler.origin(), RecordType::NS, None, lookup_options);
869            match future.await.map_result() {
870                Some(Ok(ns)) => (Some(ns), None),
871                Some(Err(error)) => {
872                    warn!(%error, "ns_lookup errored");
873                    (None, None)
874                }
875                None => {
876                    warn!("ns_lookup unexpected skip");
877                    (None, None)
878                }
879            }
880        } else {
881            #[cfg(feature = "__dnssec")]
882            {
883                let has_wildcard_match = answers.iter().any(|rr| match &rr.data {
884                    RData::DNSSEC(DNSSECRData::RRSIG(rrsig)) => {
885                        rrsig.input().num_labels < rr.name.num_labels()
886                    }
887                    _ => false,
888                });
889
890                let res = match handler.nx_proof_kind() {
891                    Some(NxProofKind::Nsec3 {
892                        algorithm,
893                        salt,
894                        iterations,
895                        opt_out: _,
896                    }) => handler
897                        .nsec3_records(
898                            Nsec3QueryInfo {
899                                qname: query.name(),
900                                qtype: query.query_type(),
901                                has_wildcard_match,
902                                algorithm: *algorithm,
903                                salt,
904                                iterations: *iterations,
905                            },
906                            lookup_options,
907                        )
908                        .await
909                        .map_result(),
910                    Some(NxProofKind::Nsec) if has_wildcard_match => handler
911                        .nsec_records(query.name(), lookup_options)
912                        .await
913                        .map_result(),
914                    _ => None,
915                };
916
917                match res {
918                    // run the soa lookup
919                    Some(Ok(nsecs)) => (Some(nsecs), None),
920                    Some(Err(error)) => {
921                        warn!(%error, request_id = _request_id, "failed to lookup nsecs for request");
922                        (None, None)
923                    }
924                    None => {
925                        warn!(
926                            request_id = _request_id,
927                            "unexpected lookup skip for request"
928                        );
929                        (None, None)
930                    }
931                }
932            }
933            #[cfg(not(feature = "__dnssec"))]
934            (None, None)
935        }
936    } else {
937        let nsecs = if lookup_options.dnssec_ok {
938            #[cfg(feature = "__dnssec")]
939            {
940                // in the dnssec case, nsec records should exist, we return NoError + NoData + NSec...
941                debug!("request: {_request_id} non-existent adding nsecs");
942                match handler.nx_proof_kind() {
943                    Some(nx_proof_kind) => {
944                        // run the nsec lookup future, and then transition to get soa
945                        let future = match nx_proof_kind {
946                            NxProofKind::Nsec => handler.nsec_records(query.name(), lookup_options),
947                            NxProofKind::Nsec3 {
948                                algorithm,
949                                salt,
950                                iterations,
951                                opt_out: _,
952                            } => handler.nsec3_records(
953                                Nsec3QueryInfo {
954                                    qname: query.name(),
955                                    qtype: query.query_type(),
956                                    has_wildcard_match: false,
957                                    algorithm: *algorithm,
958                                    salt,
959                                    iterations: *iterations,
960                                },
961                                lookup_options,
962                            ),
963                        };
964
965                        match future.await.map_result() {
966                            // run the soa lookup
967                            Some(Ok(nsecs)) => Some(nsecs),
968                            Some(Err(error)) => {
969                                warn!(%error, request_id = _request_id, "failed to lookup nsecs for request");
970                                None
971                            }
972                            None => {
973                                warn!(
974                                    request_id = _request_id,
975                                    "unexpected lookup skip for request"
976                                );
977                                None
978                            }
979                        }
980                    }
981                    None => None,
982                }
983            }
984            #[cfg(not(feature = "__dnssec"))]
985            None
986        } else {
987            None
988        };
989
990        let future = handler.lookup(handler.origin(), RecordType::SOA, None, lookup_options);
991        match future.await.map_result() {
992            Some(Ok(soa)) => (nsecs, Some(soa)),
993            Some(Err(error)) => {
994                warn!(%error, "failed to lookup soa");
995                (nsecs, None)
996            }
997            None => {
998                warn!("unexpected lookup skip");
999                (None, None)
1000            }
1001        }
1002    };
1003
1004    // everything is done, construct a Message with all sections.
1005    message.metadata = response_meta;
1006
1007    if let Some(mut lookup_records) = answers {
1008        if let Some(adds) = lookup_records.take_additionals() {
1009            message.additionals.extend(adds.iter().cloned());
1010        }
1011
1012        let is_referral = lookup_records.iter().next().is_some_and(|r| {
1013            r.record_type() == RecordType::NS
1014                && query.query_type() != RecordType::NS
1015                && query.query_type() != RecordType::ANY
1016        });
1017
1018        if is_referral {
1019            message.authorities.extend(lookup_records.iter().cloned());
1020        } else {
1021            message.answers.extend(lookup_records.iter().cloned());
1022        }
1023    }
1024
1025    if let Some(ns_records) = ns {
1026        message.authorities.extend(ns_records.iter().cloned());
1027    }
1028
1029    if let Some(soa_records) = soa {
1030        message.authorities.extend(soa_records.iter().cloned());
1031    }
1032
1033    message
1034}
1035
1036/// Prepare a response for a forwarded zone.
1037async fn build_forwarded_response(
1038    response: Result<AuthLookup, LookupError>,
1039    request_meta: &Metadata,
1040    #[cfg(feature = "__dnssec")] can_validate_dnssec: bool,
1041    query: &LowerQuery,
1042    lookup_options: LookupOptions,
1043) -> Message {
1044    let mut response_meta = Metadata::response_from_request(request_meta);
1045    response_meta.recursion_available = true;
1046    response_meta.authoritative = false;
1047    let mut message = Message::new(
1048        response_meta.id,
1049        response_meta.message_type,
1050        response_meta.op_code,
1051    );
1052    message.add_query(query.original().clone());
1053
1054    if !request_meta.recursion_desired {
1055        info!(
1056            id = request_meta.id,
1057            "request disabled recursion, returning REFUSED"
1058        );
1059
1060        response_meta.response_code = ResponseCode::Refused;
1061        message.metadata = response_meta;
1062        return message;
1063    }
1064
1065    enum Answer {
1066        Normal(AuthLookup),
1067        NoRecords(AuthLookup),
1068    }
1069
1070    #[cfg_attr(not(feature = "__dnssec"), allow(unused_mut))]
1071    let (mut answers, authorities, additionals) = match response {
1072        #[cfg(feature = "resolver")]
1073        Ok(AuthLookup::Resolved(lookup)) => {
1074            // Extract each section from the Lookup to preserve section structure
1075            let answers =
1076                AuthLookup::answers(LookupRecords::Section(lookup.answers().to_vec()), None);
1077            let authorities =
1078                AuthLookup::answers(LookupRecords::Section(lookup.authorities().to_vec()), None);
1079            let additionals =
1080                AuthLookup::answers(LookupRecords::Section(lookup.additionals().to_vec()), None);
1081
1082            (Answer::Normal(answers), authorities, additionals)
1083        }
1084        Ok(l) => (
1085            Answer::Normal(l),
1086            AuthLookup::default(),
1087            AuthLookup::default(),
1088        ),
1089        Err(e) if e.is_no_records_found() || e.is_nx_domain() => {
1090            debug!(error = ?e, "error resolving");
1091
1092            if e.is_nx_domain() {
1093                response_meta.response_code = ResponseCode::NXDomain;
1094            }
1095
1096            // Collect all of the authority records, except the SOA
1097            let authorities = if let Some(authorities) = e.authorities() {
1098                let authorities = authorities
1099                    .iter()
1100                    .filter_map(|record| {
1101                        // if we have another record (probably a dnssec record) that
1102                        // matches the query name, but wasn't included in the answers
1103                        // section, change the NXDomain response to NoError
1104                        if record.name == **query.name() {
1105                            debug!(
1106                                query_name = %query.name(),
1107                                ?record,
1108                                "changing response code from NXDomain to NoError due to other record",
1109                            );
1110                            response_meta.response_code = ResponseCode::NoError;
1111                        }
1112
1113                        match record.record_type() {
1114                            RecordType::SOA => None,
1115                            _ => Some(record.clone()),
1116                        }
1117                    })
1118                    .collect();
1119
1120                AuthLookup::answers(LookupRecords::Section(authorities), None)
1121            } else {
1122                AuthLookup::default()
1123            };
1124
1125            if let Some(soa) = e.into_soa() {
1126                let soa = soa.into_record_of_rdata();
1127                let record_set = Arc::new(RecordSet::from(soa));
1128                let records = LookupRecords::new(LookupOptions::default(), record_set);
1129
1130                (
1131                    Answer::NoRecords(AuthLookup::answers(records, None)),
1132                    authorities,
1133                    AuthLookup::default(),
1134                )
1135            } else {
1136                (
1137                    Answer::Normal(AuthLookup::default()),
1138                    authorities,
1139                    AuthLookup::default(),
1140                )
1141            }
1142        }
1143        #[cfg(all(feature = "__dnssec", feature = "recursor"))]
1144        Err(LookupError::RecursiveError(recursor::RecursorError::Net(NetError::Dns(
1145            DnsError::Nsec {
1146                response, proof, ..
1147            },
1148        )))) if proof.is_insecure() => {
1149            response_meta.response_code = response.response_code;
1150
1151            if let Some(soa) = response.soa() {
1152                let soa = soa.to_owned().into_record_of_rdata();
1153                let record_set = Arc::new(RecordSet::from(soa));
1154                let records = LookupRecords::new(LookupOptions::default(), record_set);
1155
1156                (
1157                    Answer::NoRecords(AuthLookup::answers(records, None)),
1158                    AuthLookup::default(),
1159                    AuthLookup::default(),
1160                )
1161            } else {
1162                (
1163                    Answer::Normal(AuthLookup::default()),
1164                    AuthLookup::default(),
1165                    AuthLookup::default(),
1166                )
1167            }
1168        }
1169        Err(e) => {
1170            response_meta.response_code = ResponseCode::ServFail;
1171            debug!(error = ?e, "error resolving");
1172            (
1173                Answer::Normal(AuthLookup::default()),
1174                AuthLookup::default(),
1175                AuthLookup::default(),
1176            )
1177        }
1178    };
1179
1180    // If DNSSEC is disabled, we ignore the CD bit and do not set the AD bit.
1181    #[cfg(feature = "__dnssec")]
1182    if can_validate_dnssec {
1183        // section 3.2.2 ("the CD bit") of RFC4035 is a bit underspecified because it does not use
1184        // RFC2119 vocabulary ("MUST", "MAY", etc.) in some sentences that describe the resolver's
1185        // behavior.
1186        //
1187        // A. it is clear that if CD=1 in the query then data that fails DNSSEC validation SHOULD
1188        //   be returned
1189        //
1190        // B. it also clear that if CD=0 and DNSSEC validation fails then the status MUST be
1191        //   SERVFAIL
1192        //
1193        // C. it's less clear if DNSSEC validation can be skipped altogether when CD=1
1194        //
1195        // the logic here follows `unbound`'s interpretation of that section
1196        //
1197        // 0. the requirements A and B are implemented
1198        // 1. DNSSEC validation happens regardless of the state of the CD bit
1199        // 2. the AD bit gets set if DNSSEC validation succeeded regardless of the state of the
1200        //   CD bit
1201        //
1202        // this last point can result in responses that have both AD=1 and CD=1. RFC4035 is unclear
1203        // whether that's a valid state but that's what `unbound` does
1204        //
1205        // we may want to interpret (B) as allowed ("MAY be skipped") as a form of optimization in
1206        // the future to reduce the number of network transactions that a CD=1 query needs.
1207        match &mut answers {
1208            Answer::Normal(answers) => match DnssecSummary::from_records(answers.iter()) {
1209                DnssecSummary::Secure => {
1210                    if request_meta.authentic_data || lookup_options.dnssec_ok {
1211                        trace!("setting ad header");
1212                        response_meta.authentic_data = true;
1213                    }
1214                }
1215                DnssecSummary::Bogus if !request_meta.checking_disabled => {
1216                    response_meta.response_code = ResponseCode::ServFail;
1217                    // do not return Bogus records when CD=0
1218                    *answers = AuthLookup::default();
1219                }
1220                _ => {}
1221            },
1222            Answer::NoRecords(soa) => match DnssecSummary::from_records(authorities.iter()) {
1223                DnssecSummary::Secure => {
1224                    if request_meta.authentic_data || lookup_options.dnssec_ok {
1225                        trace!("setting ad header");
1226                        response_meta.authentic_data = true;
1227                    }
1228                }
1229                DnssecSummary::Bogus if !request_meta.checking_disabled => {
1230                    response_meta.response_code = ResponseCode::ServFail;
1231                    // do not return Bogus records when CD=0
1232                    *soa = AuthLookup::default();
1233                    trace!("clearing SOA record from response");
1234                }
1235                _ => {}
1236            },
1237        }
1238    }
1239
1240    message.metadata = response_meta;
1241
1242    match answers {
1243        Answer::Normal(answers) => {
1244            message.answers.extend(answers.iter().cloned());
1245        }
1246        Answer::NoRecords(soa) => {
1247            message.authorities.extend(soa.iter().cloned());
1248        }
1249    }
1250    message.authorities.extend(authorities.iter().cloned());
1251    message.additionals.extend(additionals.iter().cloned());
1252
1253    // Strip DNSSEC records from all applicable sections based on the DNSSEC OK setting.
1254    message.maybe_strip_dnssec_records(lookup_options.dnssec_ok)
1255}
1256
1257#[cfg(all(test, feature = "resolver"))]
1258mod tests {
1259    use std::{net::Ipv4Addr, str::FromStr};
1260
1261    use super::*;
1262    use crate::net::runtime::TokioRuntimeProvider;
1263    use crate::proto::rr::rdata::NS;
1264    use crate::proto::{
1265        op::{MessageType, OpCode, Query},
1266        rr::{
1267            Name, RData, Record, RecordType,
1268            rdata::{A, SOA},
1269        },
1270    };
1271    use crate::resolver::lookup::Lookup;
1272    use crate::store::in_memory::InMemoryZoneHandler;
1273    use crate::zone_handler::AxfrPolicy;
1274
1275    #[tokio::test]
1276    async fn test_build_forwarded_response_preserves_sections() {
1277        // Create a DNS message with records in all three sections
1278        let query = Query::query(Name::from_str("example.com.").unwrap(), RecordType::A);
1279
1280        // Create a Lookup from the query
1281        let mut lookup =
1282            Lookup::from_rdata(query.clone(), RData::A(A(Ipv4Addr::new(192, 0, 2, 1))));
1283
1284        // Add authority record (SOA)
1285        lookup.extend_authorities([Record::from_rdata(
1286            Name::from_str("example.com.").unwrap(),
1287            3600,
1288            RData::SOA(SOA::new(
1289                Name::from_str("ns.example.com.").unwrap(),
1290                Name::from_str("admin.example.com.").unwrap(),
1291                1,
1292                3600,
1293                1800,
1294                604800,
1295                86400,
1296            )),
1297        )]);
1298
1299        // Add additional record (glue)
1300        lookup.extend_additionals([Record::from_rdata(
1301            Name::from_str("ns.example.com.").unwrap(),
1302            3600,
1303            RData::A(A(Ipv4Addr::new(192, 0, 2, 2))),
1304        )]);
1305
1306        // Wrap it in AuthLookup::Resolved
1307        let auth_lookup = AuthLookup::Resolved(lookup);
1308
1309        // Build the forwarded response
1310        let mut request_meta = Metadata::new(1234, MessageType::Query, OpCode::Query);
1311        request_meta.recursion_desired = true;
1312        let query_lower = LowerQuery::query(query);
1313
1314        let message = build_forwarded_response(
1315            Ok(auth_lookup),
1316            &request_meta,
1317            #[cfg(feature = "__dnssec")]
1318            false,
1319            &query_lower,
1320            LookupOptions::default(),
1321        )
1322        .await;
1323
1324        // Verify that all sections were preserved
1325        assert!(
1326            !message.answers.is_empty(),
1327            "Answers section should not be empty"
1328        );
1329
1330        // Check that we have authorities
1331        let authorities_count = message.authorities.len();
1332        assert!(
1333            authorities_count > 0,
1334            "Authorities section should not be empty, got {} records",
1335            authorities_count
1336        );
1337
1338        // Check that we have additionals
1339        let additionals_count = message.additionals.len();
1340        assert!(
1341            additionals_count > 0,
1342            "Additionals section should not be empty, got {} records",
1343            additionals_count
1344        );
1345    }
1346
1347    #[tokio::test]
1348    async fn test_build_authoritative_response_referral() {
1349        let origin = Name::from_str("example.com.").unwrap();
1350        let sub = Name::from_str("sub.example.com.").unwrap();
1351        let ns_name = Name::from_str("ns.example.com.").unwrap();
1352
1353        let ns_record = Record::from_rdata(sub.clone(), 3600, RData::NS(NS(ns_name)));
1354        let record_set = RecordSet::from(ns_record);
1355
1356        let auth_lookup = AuthLookup::Records {
1357            answers: LookupRecords::new(LookupOptions::default(), Arc::new(record_set)),
1358            additionals: None,
1359        };
1360
1361        let handler = InMemoryZoneHandler::<TokioRuntimeProvider>::empty(
1362            origin.clone(),
1363            ZoneType::Primary,
1364            AxfrPolicy::Deny,
1365            #[cfg(feature = "__dnssec")]
1366            None,
1367        );
1368
1369        let metadata = Metadata::new(0, MessageType::Query, OpCode::Query);
1370        let query = LowerQuery::from(Query::query(
1371            Name::from_str("www.sub.example.com.").unwrap(),
1372            RecordType::A,
1373        ));
1374
1375        let message = build_authoritative_response(
1376            Ok(auth_lookup),
1377            &handler,
1378            &metadata,
1379            LookupOptions::default(),
1380            0,
1381            &query,
1382        )
1383        .await;
1384
1385        assert!(message.answers.is_empty());
1386        assert!(!message.authorities.is_empty());
1387        assert_eq!(message.authorities[0].record_type(), RecordType::NS);
1388    }
1389}