Skip to main content

hickory_server/store/
recursor.rs

1// Copyright 2015-2022 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#![cfg(feature = "recursor")]
9
10//! Recursive resolver related types
11
12use std::{io, path::Path, time::Instant};
13
14#[cfg(all(feature = "toml", any(feature = "__tls", feature = "__quic")))]
15use crate::resolver::{OpportunisticEncryptionStatePersistTask, config::OpportunisticEncryption};
16#[cfg(feature = "__dnssec")]
17use crate::{dnssec::NxProofKind, zone_handler::Nsec3QueryInfo};
18use crate::{
19    net::runtime::RuntimeProvider,
20    proto::{
21        op::Query,
22        rr::{LowerName, Name, RecordType},
23    },
24    resolver::recursor::{RecursiveConfig, Recursor},
25    server::{Request, RequestInfo},
26    zone_handler::{
27        AuthLookup, AxfrPolicy, LookupControlFlow, LookupError, LookupOptions, ZoneHandler,
28        ZoneType,
29    },
30};
31use hickory_proto::rr::TSigResponseContext;
32use tracing::{debug, info};
33
34/// A zone handler that performs recursive resolutions.
35///
36/// This uses the hickory-recursor crate for resolving requests.
37pub struct RecursiveZoneHandler<P: RuntimeProvider> {
38    origin: LowerName,
39    recursor: Recursor<P>,
40    #[allow(dead_code)] // Handle is retained to Drop along with RecursiveZoneHandler.
41    opportunistic_encryption_persistence_task: Option<P::Handle>,
42}
43
44impl<P: RuntimeProvider> RecursiveZoneHandler<P> {
45    /// Read the ZoneHandler for the origin from the specified configuration
46    pub async fn try_from_config(
47        origin: Name,
48        _zone_type: ZoneType,
49        config: RecursiveConfig,
50        root_dir: Option<&Path>,
51        conn_provider: P,
52    ) -> Result<Self, String> {
53        info!("loading recursor config: {}", origin);
54
55        #[cfg(all(feature = "toml", any(feature = "__tls", feature = "__quic")))]
56        let persistence_config = match &config.options.opportunistic_encryption {
57            OpportunisticEncryption::Enabled { config } => config.persistence.clone(),
58            _ => None,
59        };
60
61        let recursor = Recursor::from_config(config, root_dir, conn_provider.clone())
62            .map_err(|e| format!("failed to build recursor: {e}"))?;
63
64        Ok(Self {
65            origin: origin.into(),
66            // Once the recursor is built, potentially use the recursor's pool context to spawn a
67            // background save task, holding the task handle (if created) so it drops with the zone handler.
68            #[cfg(all(feature = "toml", any(feature = "__tls", feature = "__quic")))]
69            opportunistic_encryption_persistence_task: match persistence_config {
70                Some(config) => {
71                    OpportunisticEncryptionStatePersistTask::<P::Timer>::start(
72                        config,
73                        recursor.pool_context(),
74                        conn_provider.clone(),
75                    )
76                    .await?
77                }
78                _ => None,
79            },
80            #[cfg(not(all(feature = "toml", any(feature = "__tls", feature = "__quic"))))]
81            opportunistic_encryption_persistence_task: None,
82            recursor,
83        })
84    }
85}
86
87#[async_trait::async_trait]
88impl<P: RuntimeProvider> ZoneHandler for RecursiveZoneHandler<P> {
89    /// Always External
90    fn zone_type(&self) -> ZoneType {
91        ZoneType::External
92    }
93
94    /// Always deny for Forward zones
95    fn axfr_policy(&self) -> AxfrPolicy {
96        AxfrPolicy::Deny
97    }
98
99    fn can_validate_dnssec(&self) -> bool {
100        self.recursor.is_validating()
101    }
102
103    /// Get the origin of this zone, i.e. example.com is the origin for www.example.com
104    ///
105    /// In the context of a forwarder, this is either a zone which this forwarder is associated,
106    ///   or `.`, the root zone for all zones. If this is not the root zone, then it will only forward
107    ///   for lookups which match the given zone name.
108    fn origin(&self) -> &LowerName {
109        &self.origin
110    }
111
112    /// Forwards a lookup given the resolver configuration for this Forwarded zone
113    async fn lookup(
114        &self,
115        name: &LowerName,
116        rtype: RecordType,
117        _request_info: Option<&RequestInfo<'_>>,
118        lookup_options: LookupOptions,
119    ) -> LookupControlFlow<AuthLookup> {
120        debug!("recursive lookup: {} {}", name, rtype);
121
122        let query = Query::query(name.into(), rtype);
123        let now = Instant::now();
124
125        let result = self
126            .recursor
127            .resolve(query.clone(), now, lookup_options.dnssec_ok)
128            .await;
129
130        let response = match result {
131            Ok(response) => response,
132            Err(error) => return LookupControlFlow::Continue(Err(LookupError::from(error))),
133        };
134        LookupControlFlow::Continue(Ok(AuthLookup::Response(response)))
135    }
136
137    async fn search(
138        &self,
139        request: &Request,
140        lookup_options: LookupOptions,
141    ) -> (LookupControlFlow<AuthLookup>, Option<TSigResponseContext>) {
142        let request_info = match request.request_info() {
143            Ok(info) => info,
144            Err(e) => return (LookupControlFlow::Break(Err(e)), None),
145        };
146        (
147            self.lookup(
148                request_info.query.name(),
149                request_info.query.query_type(),
150                Some(&request_info),
151                lookup_options,
152            )
153            .await,
154            None,
155        )
156    }
157
158    async fn nsec_records(
159        &self,
160        _name: &LowerName,
161        _lookup_options: LookupOptions,
162    ) -> LookupControlFlow<AuthLookup> {
163        LookupControlFlow::Continue(Err(LookupError::from(io::Error::other(
164            "Getting NSEC records is unimplemented for the recursor",
165        ))))
166    }
167
168    #[cfg(feature = "__dnssec")]
169    async fn nsec3_records(
170        &self,
171        _info: Nsec3QueryInfo<'_>,
172        _lookup_options: LookupOptions,
173    ) -> LookupControlFlow<AuthLookup> {
174        LookupControlFlow::Continue(Err(LookupError::from(io::Error::other(
175            "getting NSEC3 records is unimplemented for the recursor",
176        ))))
177    }
178
179    #[cfg(feature = "__dnssec")]
180    fn nx_proof_kind(&self) -> Option<&NxProofKind> {
181        None
182    }
183
184    #[cfg(feature = "metrics")]
185    fn metrics_label(&self) -> &'static str {
186        "recursive"
187    }
188}