#![warn(
clippy::default_trait_access,
clippy::dbg_macro,
clippy::print_stdout,
clippy::unimplemented,
missing_copy_implementations,
missing_docs,
non_snake_case,
non_upper_case_globals,
rust_2018_idioms,
unreachable_pub
)]
#![allow(
clippy::single_component_path_imports,
clippy::upper_case_acronyms, // can be removed on a major release boundary
)]
#![recursion_limit = "2048"]
#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
mod error;
mod recursor;
mod recursor_dns_handle;
pub(crate) mod recursor_pool;
#[cfg(feature = "__dnssec")]
use std::sync::Arc;
use std::time::Instant;
pub use error::{Error, ErrorKind};
pub use hickory_proto as proto;
pub use hickory_resolver as resolver;
pub use hickory_resolver::config::{NameServerConfig, NameServerConfigGroup};
#[cfg(feature = "__dnssec")]
use proto::dnssec::TrustAnchors;
use proto::{op::Query, xfer::DnsResponse};
pub use recursor::{Recursor, RecursorBuilder};
use resolver::{Name, dns_lru::DnsLru, lookup::Lookup};
use tracing::{info, warn};
#[allow(missing_copy_implementations)]
#[derive(Clone)]
pub enum DnssecPolicy {
SecurityUnaware,
#[cfg(feature = "__dnssec")]
ValidationDisabled,
#[cfg(feature = "__dnssec")]
ValidateWithStaticKey {
trust_anchor: Option<Arc<TrustAnchors>>,
},
}
impl DnssecPolicy {
pub(crate) fn is_security_aware(&self) -> bool {
!matches!(self, Self::SecurityUnaware)
}
}
fn cache_response(
response: DnsResponse,
zone: Option<&Name>,
record_cache: &DnsLru,
query: Query,
now: Instant,
) -> Result<Lookup, Error> {
let mut response = response.into_message();
info!("response: {}", response.header());
let records = response
.take_answers()
.into_iter()
.chain(response.take_name_servers())
.chain(response.take_additionals())
.filter(|x| {
if let Some(zone) = zone {
if !is_subzone(zone, x.name()) {
warn!("Dropping out of bailiwick record {x} for zone {}", zone);
false
} else {
true
}
} else {
true
}
});
let lookup = record_cache.insert_records(query, records, now);
lookup.ok_or_else(|| Error::from("no records found"))
}
fn maybe_strip_dnssec_records(query_has_dnssec_ok: bool, lookup: Lookup, query: Query) -> Lookup {
if query_has_dnssec_ok {
return lookup;
}
let records = lookup
.records()
.iter()
.filter(|rrset| {
let record_type = rrset.record_type();
record_type == query.query_type() || !record_type.is_dnssec()
})
.cloned()
.collect();
Lookup::new_with_deadline(query, records, lookup.valid_until())
}
fn is_subzone(parent: &Name, child: &Name) -> bool {
if parent.is_empty() {
return false;
}
if (parent.is_fqdn() && !child.is_fqdn()) || (!parent.is_fqdn() && child.is_fqdn()) {
return false;
}
parent.zone_of(child)
}
#[test]
fn is_subzone_test() {
use core::str::FromStr;
assert!(is_subzone(
&Name::from_str(".").unwrap(),
&Name::from_str("com.").unwrap(),
));
assert!(is_subzone(
&Name::from_str("com.").unwrap(),
&Name::from_str("example.com.").unwrap(),
));
assert!(is_subzone(
&Name::from_str("example.com.").unwrap(),
&Name::from_str("host.example.com.").unwrap(),
));
assert!(is_subzone(
&Name::from_str("example.com.").unwrap(),
&Name::from_str("host.multilevel.example.com.").unwrap(),
));
assert!(!is_subzone(
&Name::from_str("").unwrap(),
&Name::from_str("example.com.").unwrap(),
));
assert!(!is_subzone(
&Name::from_str("com.").unwrap(),
&Name::from_str("example.net.").unwrap(),
));
assert!(!is_subzone(
&Name::from_str("example.com.").unwrap(),
&Name::from_str("otherdomain.com.").unwrap(),
));
assert!(!is_subzone(
&Name::from_str("com").unwrap(),
&Name::from_str("example.com.").unwrap(),
));
}