1#![cfg(feature = "recursor")]
9
10#[cfg(feature = "__dnssec")]
13use std::sync::Arc;
14use std::{
15 borrow::Cow,
16 collections::HashSet,
17 fs::File,
18 io::{self, Read},
19 net::SocketAddr,
20 path::{Path, PathBuf},
21 time::Instant,
22};
23
24use ipnet::IpNet;
25use serde::Deserialize;
26use tracing::{debug, info};
27
28#[cfg(feature = "__dnssec")]
29use crate::{authority::Nsec3QueryInfo, dnssec::NxProofKind, proto::dnssec::TrustAnchors};
30use crate::{
31 authority::{
32 Authority, LookupControlFlow, LookupError, LookupObject, LookupOptions, MessageRequest,
33 UpdateResult, ZoneType,
34 },
35 error::ConfigError,
36 proto::{
37 op::{Query, ResponseCode},
38 rr::{LowerName, Name, RData, Record, RecordSet, RecordType},
39 serialize::txt::{ParseError, Parser},
40 xfer::Protocol,
41 },
42 recursor::{DnssecPolicy, Recursor},
43 resolver::{
44 config::{NameServerConfig, NameServerConfigGroup},
45 dns_lru::TtlConfig,
46 lookup::Lookup,
47 },
48 server::RequestInfo,
49};
50
51pub struct RecursiveAuthority {
55 origin: LowerName,
56 recursor: Recursor,
57}
58
59impl RecursiveAuthority {
60 pub async fn try_from_config(
62 origin: Name,
63 _zone_type: ZoneType,
64 config: &RecursiveConfig,
65 root_dir: Option<&Path>,
66 ) -> Result<Self, String> {
67 info!("loading recursor config: {}", origin);
68
69 let root_addrs = config
71 .read_roots(root_dir)
72 .map_err(|e| format!("failed to read roots {}: {}", config.roots.display(), e))?;
73
74 let mut roots = NameServerConfigGroup::new();
76 for socket_addr in root_addrs {
77 roots.push(NameServerConfig {
78 socket_addr,
79 protocol: Protocol::Tcp,
80 tls_dns_name: None,
81 http_endpoint: None,
82 trust_negative_responses: false,
83 bind_addr: None, });
85
86 roots.push(NameServerConfig {
87 socket_addr,
88 protocol: Protocol::Udp,
89 tls_dns_name: None,
90 http_endpoint: None,
91 trust_negative_responses: false,
92 bind_addr: None,
93 });
94 }
95
96 let mut builder = Recursor::builder();
97 if let Some(ns_cache_size) = config.ns_cache_size {
98 builder = builder.ns_cache_size(ns_cache_size);
99 }
100 if let Some(record_cache_size) = config.record_cache_size {
101 builder = builder.record_cache_size(record_cache_size);
102 }
103
104 let recursor = builder
105 .dnssec_policy(config.dnssec_policy.load().map_err(|e| e.to_string())?)
106 .nameserver_filter(config.allow_server.iter(), config.deny_server.iter())
107 .recursion_limit(match config.recursion_limit {
108 0 => None,
109 limit => Some(limit),
110 })
111 .ns_recursion_limit(match config.ns_recursion_limit {
112 0 => None,
113 limit => Some(limit),
114 })
115 .avoid_local_udp_ports(config.avoid_local_udp_ports.clone())
116 .ttl_config(config.cache_policy.clone())
117 .case_randomization(config.case_randomization)
118 .build(roots)
119 .map_err(|e| format!("failed to initialize recursor: {e}"))?;
120
121 Ok(Self {
122 origin: origin.into(),
123 recursor,
124 })
125 }
126}
127
128#[async_trait::async_trait]
129impl Authority for RecursiveAuthority {
130 type Lookup = RecursiveLookup;
131
132 fn zone_type(&self) -> ZoneType {
134 ZoneType::External
135 }
136
137 fn is_axfr_allowed(&self) -> bool {
139 false
140 }
141
142 fn can_validate_dnssec(&self) -> bool {
143 self.recursor.is_validating()
144 }
145
146 async fn update(&self, _update: &MessageRequest) -> UpdateResult<bool> {
147 Err(ResponseCode::NotImp)
148 }
149
150 fn origin(&self) -> &LowerName {
156 &self.origin
157 }
158
159 async fn lookup(
161 &self,
162 name: &LowerName,
163 rtype: RecordType,
164 lookup_options: LookupOptions,
165 ) -> LookupControlFlow<Self::Lookup> {
166 debug!("recursive lookup: {} {}", name, rtype);
167
168 let query = Query::query(name.into(), rtype);
169 let now = Instant::now();
170
171 let result = self
172 .recursor
173 .resolve(query, now, lookup_options.dnssec_ok())
174 .await;
175
176 use LookupControlFlow::*;
177 match result {
178 Ok(lookup) => Continue(Ok(RecursiveLookup(lookup))),
179 Err(error) => Continue(Err(LookupError::from(error))),
180 }
181 }
182
183 async fn search(
184 &self,
185 request_info: RequestInfo<'_>,
186 lookup_options: LookupOptions,
187 ) -> LookupControlFlow<Self::Lookup> {
188 self.lookup(
189 request_info.query.name(),
190 request_info.query.query_type(),
191 lookup_options,
192 )
193 .await
194 }
195
196 async fn get_nsec_records(
197 &self,
198 _name: &LowerName,
199 _lookup_options: LookupOptions,
200 ) -> LookupControlFlow<Self::Lookup> {
201 LookupControlFlow::Continue(Err(LookupError::from(io::Error::new(
202 io::ErrorKind::Other,
203 "Getting NSEC records is unimplemented for the recursor",
204 ))))
205 }
206
207 #[cfg(feature = "__dnssec")]
208 async fn get_nsec3_records(
209 &self,
210 _info: Nsec3QueryInfo<'_>,
211 _lookup_options: LookupOptions,
212 ) -> LookupControlFlow<Self::Lookup> {
213 LookupControlFlow::Continue(Err(LookupError::from(io::Error::new(
214 io::ErrorKind::Other,
215 "getting NSEC3 records is unimplemented for the recursor",
216 ))))
217 }
218
219 #[cfg(feature = "__dnssec")]
220 fn nx_proof_kind(&self) -> Option<&NxProofKind> {
221 None
222 }
223}
224
225pub struct RecursiveLookup(Lookup);
227
228impl LookupObject for RecursiveLookup {
229 fn is_empty(&self) -> bool {
230 self.0.is_empty()
231 }
232
233 fn iter<'a>(&'a self) -> Box<dyn Iterator<Item = &'a Record> + Send + 'a> {
234 Box::new(self.0.record_iter())
235 }
236
237 fn take_additionals(&mut self) -> Option<Box<dyn LookupObject>> {
238 None
239 }
240}
241
242#[derive(Clone, Deserialize, Eq, PartialEq, Debug)]
244#[serde(deny_unknown_fields)]
245pub struct RecursiveConfig {
246 pub roots: PathBuf,
248
249 pub ns_cache_size: Option<usize>,
251
252 pub record_cache_size: Option<usize>,
254
255 #[serde(default = "recursion_limit_default")]
257 pub recursion_limit: u8,
258
259 #[serde(default = "ns_recursion_limit_default")]
261 pub ns_recursion_limit: u8,
262
263 #[serde(default)]
265 pub dnssec_policy: DnssecPolicyConfig,
266
267 #[serde(default)]
269 pub allow_server: Vec<IpNet>,
270
271 #[serde(default)]
273 pub deny_server: Vec<IpNet>,
274
275 #[serde(default)]
277 pub avoid_local_udp_ports: HashSet<u16>,
278
279 #[serde(default)]
281 pub cache_policy: TtlConfig,
282
283 #[serde(default)]
291 pub case_randomization: bool,
292}
293
294impl RecursiveConfig {
295 pub(crate) fn read_roots(
296 &self,
297 root_dir: Option<&Path>,
298 ) -> Result<Vec<SocketAddr>, ConfigError> {
299 let path = if let Some(root_dir) = root_dir {
300 Cow::Owned(root_dir.join(&self.roots))
301 } else {
302 Cow::Borrowed(&self.roots)
303 };
304
305 let mut roots = File::open(path.as_ref())?;
306 let mut roots_str = String::new();
307 roots.read_to_string(&mut roots_str)?;
308
309 let (_zone, roots_zone) =
310 Parser::new(roots_str, Some(path.into_owned()), Some(Name::root())).parse()?;
311
312 Ok(roots_zone
314 .values()
315 .flat_map(RecordSet::records_without_rrsigs)
316 .map(Record::data)
317 .filter_map(RData::ip_addr) .map(|ip| SocketAddr::from((ip, 53))) .collect())
320 }
321}
322
323fn recursion_limit_default() -> u8 {
324 12
325}
326
327fn ns_recursion_limit_default() -> u8 {
328 16
329}
330
331#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq)]
333#[serde(deny_unknown_fields)]
334#[allow(missing_copy_implementations)]
335pub enum DnssecPolicyConfig {
336 #[default]
338 SecurityUnaware,
339
340 #[cfg(feature = "__dnssec")]
342 ValidationDisabled,
343
344 #[cfg(feature = "__dnssec")]
346 ValidateWithStaticKey {
347 path: Option<PathBuf>,
349 },
350}
351
352impl DnssecPolicyConfig {
353 pub(crate) fn load(&self) -> Result<DnssecPolicy, ParseError> {
354 Ok(match self {
355 Self::SecurityUnaware => DnssecPolicy::SecurityUnaware,
356 #[cfg(feature = "__dnssec")]
357 Self::ValidationDisabled => DnssecPolicy::ValidationDisabled,
358 #[cfg(feature = "__dnssec")]
359 Self::ValidateWithStaticKey { path } => DnssecPolicy::ValidateWithStaticKey {
360 trust_anchor: path
361 .as_ref()
362 .map(|path| TrustAnchors::from_file(path))
363 .transpose()?
364 .map(Arc::new),
365 },
366 })
367 }
368}
369
370#[cfg(test)]
371mod tests {
372 #[cfg(all(feature = "__dnssec", feature = "toml"))]
373 use super::*;
374
375 #[cfg(all(feature = "__dnssec", feature = "toml"))]
376 #[test]
377 fn can_parse_recursive_config() {
378 let input = r#"roots = "/etc/root.hints"
379dnssec_policy.ValidateWithStaticKey.path = "/etc/trusted-key.key""#;
380
381 let config: RecursiveConfig = toml::from_str(input).unwrap();
382
383 if let DnssecPolicyConfig::ValidateWithStaticKey { path } = config.dnssec_policy {
384 assert_eq!(Some(Path::new("/etc/trusted-key.key")), path.as_deref());
385 } else {
386 unreachable!()
387 }
388 }
389
390 #[cfg(all(feature = "recursor", feature = "toml"))]
391 #[test]
392 fn can_parse_recursor_cache_policy() {
393 use std::time::Duration;
394
395 use hickory_proto::rr::RecordType;
396
397 let input = r#"roots = "/etc/root.hints"
398
399[cache_policy.default]
400positive_max_ttl = 14400
401
402[cache_policy.A]
403positive_max_ttl = 3600"#;
404
405 let config: RecursiveConfig = toml::from_str(input).unwrap();
406
407 assert_eq!(
408 *config
409 .cache_policy
410 .positive_response_ttl_bounds(RecordType::MX)
411 .end(),
412 Duration::from_secs(14400)
413 );
414
415 assert_eq!(
416 *config
417 .cache_policy
418 .positive_response_ttl_bounds(RecordType::A)
419 .end(),
420 Duration::from_secs(3600)
421 )
422 }
423}