Skip to main content

hickory_server/store/
forwarder.rs

1// Copyright 2015-2019 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 = "resolver")]
9
10//! Forwarding resolver related types
11
12use std::io;
13#[cfg(feature = "__dnssec")]
14use std::sync::Arc;
15
16use serde::Deserialize;
17use tracing::{debug, info};
18
19#[cfg(feature = "__dnssec")]
20use crate::{dnssec::NxProofKind, proto::dnssec::TrustAnchors, zone_handler::Nsec3QueryInfo};
21use crate::{
22    net::runtime::TokioRuntimeProvider,
23    proto::rr::{LowerName, Name, RecordType, TSigResponseContext},
24    resolver::{
25        ConnectionProvider, Resolver,
26        config::{NameServerConfig, ResolveHosts, ResolverConfig, ResolverOpts},
27    },
28    server::{Request, RequestInfo},
29    zone_handler::{
30        AuthLookup, AxfrPolicy, LookupControlFlow, LookupError, LookupOptions, ZoneHandler,
31        ZoneType,
32    },
33};
34
35/// A builder to construct a [`ForwardZoneHandler`].
36///
37/// Created by [`ForwardZoneHandler::builder`].
38pub struct ForwardZoneHandlerBuilder<P: ConnectionProvider> {
39    origin: Name,
40    config: ForwardConfig,
41    domain: Option<Name>,
42    search: Vec<Name>,
43    runtime: P,
44
45    #[cfg(feature = "__dnssec")]
46    trust_anchor: Option<Arc<TrustAnchors>>,
47}
48
49impl<P: ConnectionProvider> ForwardZoneHandlerBuilder<P> {
50    /// Set the origin of the zone handler.
51    pub fn with_origin(mut self, origin: Name) -> Self {
52        self.origin = origin;
53        self
54    }
55
56    /// Enables DNSSEC validation, and sets the DNSSEC trust anchors to be used by the forward
57    /// zone handler.
58    ///
59    /// This overrides the trust anchor path in the `ResolverOpts`.
60    #[cfg(feature = "__dnssec")]
61    pub fn with_trust_anchor(mut self, trust_anchor: Arc<TrustAnchors>) -> Self {
62        self.trust_anchor = Some(trust_anchor);
63        self
64    }
65
66    /// Returns a mutable reference to the [`ResolverOpts`].
67    pub fn options_mut(&mut self) -> &mut ResolverOpts {
68        self.config
69            .options
70            .get_or_insert_with(ResolverOpts::default)
71    }
72
73    /// Set the system domain name.
74    pub fn with_domain(mut self, domain: Name) -> Self {
75        self.domain = Some(domain);
76        self
77    }
78
79    /// Set the search domains.
80    pub fn with_search(mut self, search: Vec<Name>) -> Self {
81        self.search = search;
82        self
83    }
84
85    /// Construct the zone handler.
86    pub fn build(self) -> Result<ForwardZoneHandler<P>, String> {
87        let Self {
88            origin,
89            config,
90            domain,
91            search,
92            runtime,
93            #[cfg(feature = "__dnssec")]
94            trust_anchor,
95        } = self;
96        info!(%origin, "loading forwarder config");
97
98        let name_servers = config.name_servers;
99        let mut options = config.options.unwrap_or_default();
100
101        // See RFC 1034, Section 4.3.2:
102        // "If the data at the node is a CNAME, and QTYPE doesn't match
103        // CNAME, copy the CNAME RR into the answer section of the response,
104        // change QNAME to the canonical name in the CNAME RR, and go
105        // back to step 1."
106        //
107        // Essentially, it's saying that servers (including forwarders)
108        // should emit any found CNAMEs in a response ("copy the CNAME
109        // RR into the answer section"). This is the behavior that
110        // preserve_intermediates enables when set to true, and disables
111        // when set to false. So we set it to true.
112        if !options.preserve_intermediates {
113            tracing::warn!(
114                "preserve_intermediates set to false, which is invalid \
115                for a forwarder; switching to true"
116            );
117            options.preserve_intermediates = true;
118        }
119
120        // Require people to explicitly request for /etc/hosts usage in forwarder
121        // configs
122        if options.use_hosts_file == ResolveHosts::Auto {
123            options.use_hosts_file = ResolveHosts::Never;
124        }
125
126        let config = ResolverConfig::from_parts(domain, search, name_servers);
127
128        let mut resolver_builder = Resolver::builder_with_config(config, runtime);
129
130        #[cfg(feature = "__dnssec")]
131        match (trust_anchor, &options.trust_anchor) {
132            (Some(trust_anchor), _) => {
133                resolver_builder = resolver_builder.with_trust_anchor(trust_anchor);
134            }
135            (None, Some(path)) => {
136                let trust_anchor = TrustAnchors::from_file(path).map_err(|err| err.to_string())?;
137                resolver_builder = resolver_builder.with_trust_anchor(Arc::new(trust_anchor));
138            }
139            (None, None) => {}
140        }
141
142        *resolver_builder.options_mut() = options;
143        let resolver = resolver_builder.build().map_err(|err| err.to_string())?;
144
145        info!(%origin, "forward resolver configured");
146
147        Ok(ForwardZoneHandler {
148            origin: origin.into(),
149            resolver,
150        })
151    }
152}
153
154/// A zone handler that will forward resolutions to upstream resolvers.
155///
156/// This uses the hickory-resolver crate for resolving requests.
157pub struct ForwardZoneHandler<P: ConnectionProvider = TokioRuntimeProvider> {
158    origin: LowerName,
159    resolver: Resolver<P>,
160}
161
162impl<P: ConnectionProvider> ForwardZoneHandler<P> {
163    /// Construct a new [`ForwardZoneHandler`] via [`ForwardZoneHandlerBuilder`], using the operating
164    /// system's resolver configuration.
165    pub fn builder(runtime: P) -> Result<ForwardZoneHandlerBuilder<P>, String> {
166        let (resolver_config, options) = hickory_resolver::system_conf::read_system_conf()
167            .map_err(|e| format!("error reading system configuration: {e}"))?;
168        let forward_config = ForwardConfig {
169            name_servers: resolver_config.name_servers().to_owned(),
170            options: Some(options),
171        };
172        let mut builder = Self::builder_with_config(forward_config, runtime);
173        if let Some(domain) = resolver_config.domain() {
174            builder = builder.with_domain(domain.clone());
175        }
176        builder = builder.with_search(resolver_config.search().to_vec());
177        Ok(builder)
178    }
179
180    /// Construct a new [`ForwardZoneHandler`] via [`ForwardZoneHandlerBuilder`] with the provided configuration.
181    pub fn builder_with_config(config: ForwardConfig, runtime: P) -> ForwardZoneHandlerBuilder<P> {
182        ForwardZoneHandlerBuilder {
183            origin: Name::root(),
184            config,
185            domain: None,
186            search: vec![],
187            runtime,
188            #[cfg(feature = "__dnssec")]
189            trust_anchor: None,
190        }
191    }
192}
193
194impl ForwardZoneHandler<TokioRuntimeProvider> {
195    /// Construct a new [`ForwardZoneHandler`] via [`ForwardZoneHandlerBuilder`] with the provided configuration.
196    pub fn builder_tokio(config: ForwardConfig) -> ForwardZoneHandlerBuilder<TokioRuntimeProvider> {
197        Self::builder_with_config(config, TokioRuntimeProvider::default())
198    }
199}
200
201#[async_trait::async_trait]
202impl<P: ConnectionProvider> ZoneHandler for ForwardZoneHandler<P> {
203    /// Always External
204    fn zone_type(&self) -> ZoneType {
205        ZoneType::External
206    }
207
208    /// AXFR requests are always denied for Forward zones
209    fn axfr_policy(&self) -> AxfrPolicy {
210        AxfrPolicy::Deny
211    }
212
213    /// Whether the zone handler can perform DNSSEC validation
214    fn can_validate_dnssec(&self) -> bool {
215        #[cfg(feature = "__dnssec")]
216        {
217            self.resolver.options().validate
218        }
219        #[cfg(not(feature = "__dnssec"))]
220        {
221            false
222        }
223    }
224
225    /// Get the origin of this zone, i.e. example.com is the origin for www.example.com
226    ///
227    /// In the context of a forwarder, this is either a zone which this forwarder is associated,
228    ///   or `.`, the root zone for all zones. If this is not the root zone, then it will only forward
229    ///   for lookups which match the given zone name.
230    fn origin(&self) -> &LowerName {
231        &self.origin
232    }
233
234    /// Forwards a lookup given the resolver configuration for this Forwarded zone
235    async fn lookup(
236        &self,
237        name: &LowerName,
238        rtype: RecordType,
239        _request_info: Option<&RequestInfo<'_>>,
240        _lookup_options: LookupOptions,
241    ) -> LookupControlFlow<AuthLookup> {
242        // TODO: make this an error?
243        debug_assert!(self.origin.zone_of(name));
244
245        debug!("forwarding lookup: {} {}", name, rtype);
246
247        // Ignore FQDN when we forward DNS queries. Without this we can't look
248        // up addresses from system hosts file.
249        let mut name: Name = name.clone().into();
250        name.set_fqdn(false);
251
252        use LookupControlFlow::*;
253        match self.resolver.lookup(name, rtype).await {
254            Ok(lookup) => Continue(Ok(AuthLookup::from(lookup))),
255            Err(e) => Continue(Err(LookupError::from(e))),
256        }
257    }
258
259    async fn search(
260        &self,
261        request: &Request,
262        lookup_options: LookupOptions,
263    ) -> (LookupControlFlow<AuthLookup>, Option<TSigResponseContext>) {
264        let request_info = match request.request_info() {
265            Ok(info) => info,
266            Err(e) => return (LookupControlFlow::Break(Err(e)), None),
267        };
268        (
269            self.lookup(
270                request_info.query.name(),
271                request_info.query.query_type(),
272                Some(&request_info),
273                lookup_options,
274            )
275            .await,
276            None,
277        )
278    }
279
280    async fn nsec_records(
281        &self,
282        _name: &LowerName,
283        _lookup_options: LookupOptions,
284    ) -> LookupControlFlow<AuthLookup> {
285        LookupControlFlow::Continue(Err(LookupError::from(io::Error::other(
286            "Getting NSEC records is unimplemented for the forwarder",
287        ))))
288    }
289
290    #[cfg(feature = "__dnssec")]
291    async fn nsec3_records(
292        &self,
293        _info: Nsec3QueryInfo<'_>,
294        _lookup_options: LookupOptions,
295    ) -> LookupControlFlow<AuthLookup> {
296        LookupControlFlow::Continue(Err(LookupError::from(io::Error::other(
297            "getting NSEC3 records is unimplemented for the forwarder",
298        ))))
299    }
300
301    #[cfg(feature = "__dnssec")]
302    fn nx_proof_kind(&self) -> Option<&NxProofKind> {
303        None
304    }
305
306    #[cfg(feature = "metrics")]
307    fn metrics_label(&self) -> &'static str {
308        "forwarder"
309    }
310}
311
312/// Configuration for forwarder zones
313#[derive(Clone, Deserialize, Debug, Default)]
314#[serde(deny_unknown_fields)]
315pub struct ForwardConfig {
316    /// upstream name_server configurations
317    pub name_servers: Vec<NameServerConfig>,
318    /// Resolver options
319    pub options: Option<ResolverOpts>,
320}