1#![cfg(feature = "resolver")]
9
10use 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
35pub 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 pub fn with_origin(mut self, origin: Name) -> Self {
52 self.origin = origin;
53 self
54 }
55
56 #[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 pub fn options_mut(&mut self) -> &mut ResolverOpts {
68 self.config
69 .options
70 .get_or_insert_with(ResolverOpts::default)
71 }
72
73 pub fn with_domain(mut self, domain: Name) -> Self {
75 self.domain = Some(domain);
76 self
77 }
78
79 pub fn with_search(mut self, search: Vec<Name>) -> Self {
81 self.search = search;
82 self
83 }
84
85 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 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 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
154pub struct ForwardZoneHandler<P: ConnectionProvider = TokioRuntimeProvider> {
158 origin: LowerName,
159 resolver: Resolver<P>,
160}
161
162impl<P: ConnectionProvider> ForwardZoneHandler<P> {
163 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 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 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 fn zone_type(&self) -> ZoneType {
205 ZoneType::External
206 }
207
208 fn axfr_policy(&self) -> AxfrPolicy {
210 AxfrPolicy::Deny
211 }
212
213 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 fn origin(&self) -> &LowerName {
231 &self.origin
232 }
233
234 async fn lookup(
236 &self,
237 name: &LowerName,
238 rtype: RecordType,
239 _request_info: Option<&RequestInfo<'_>>,
240 _lookup_options: LookupOptions,
241 ) -> LookupControlFlow<AuthLookup> {
242 debug_assert!(self.origin.zone_of(name));
244
245 debug!("forwarding lookup: {} {}", name, rtype);
246
247 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#[derive(Clone, Deserialize, Debug, Default)]
314#[serde(deny_unknown_fields)]
315pub struct ForwardConfig {
316 pub name_servers: Vec<NameServerConfig>,
318 pub options: Option<ResolverOpts>,
320}