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