diff --git a/CHANGELOG.md b/CHANGELOG.md
index a72178d7..3af49acd 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -16,6 +16,10 @@ All notes should be prepended with the location of the change, e.g. `(proto)` or
### Changed
+- (server) `ResponseHandler` trait is now `async_trait`, requires all impls to be annotated with `#[async_trait]` #1550
+- (server) `Authority` impls required to be internally modifiable and `Send + Sync` #1550
+- (server) Most `Authority` methods changes to `async fn` rather than returning custom `Future` impls #1550
+- (server) `Authority` trait is now `async_trait`, requires all impls to be annotated with `#[async_trait]` #1550
- (proto) Header now stores ResponseCode instead of just u8 #1537
- (client) improved async client example documentation (@ErwanDL) #1539
- (resolver) on `REFUSED` (and other negative) response(s), fall back to other nameservers (@peterthejohnston) #1513 #1526
diff --git a/Cargo.lock b/Cargo.lock
index 5bc0dbca..72d791a2 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1,7 +1,5 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
-version = 3
-
[[package]]
name = "addr2line"
version = "0.16.0"
diff --git a/bin/src/named.rs b/bin/src/named.rs
index 9a8fe4c1..7dac5b19 100644
--- a/bin/src/named.rs
+++ b/bin/src/named.rs
@@ -47,10 +47,9 @@ use std::{
};
use clap::{Arg, ArgMatches};
-use futures::lock::Mutex;
use tokio::{
net::{TcpListener, UdpSocket},
- runtime::{self, Runtime},
+ runtime,
};
use trust_dns_client::rr::Name;
@@ -74,14 +73,14 @@ use trust_dns_server::{logger, server::ServerFuture};
use {trust_dns_client::rr::rdata::key::KeyUsage, trust_dns_server::authority::DnssecAuthority};
#[cfg(feature = "dnssec")]
-fn load_keys(
+async fn load_keys(
authority: &mut A,
zone_name: Name,
zone_config: &ZoneConfig,
) -> Result<(), String>
where
A: DnssecAuthority,
- L: Send + Sized + 'static,
+ L: Send + Sync + Sized + 'static,
{
if zone_config.is_dnssec_enabled() {
for key_config in zone_config.get_keys() {
@@ -97,6 +96,7 @@ where
})?;
authority
.add_zone_signing_key(zone_signer)
+ .await
.expect("failed to add zone signing key to authority");
}
if key_config.is_zone_update_auth() {
@@ -110,19 +110,20 @@ where
.expect("failed to get sig0 key");
authority
.add_update_auth_key(zone_name.clone(), public_key)
+ .await
.expect("failed to add update auth key to authority");
}
}
info!("signing zone: {}", zone_config.get_zone().unwrap());
- authority.secure_zone().expect("failed to sign zone");
+ authority.secure_zone().await.expect("failed to sign zone");
}
Ok(())
}
#[cfg(not(feature = "dnssec"))]
#[allow(clippy::unnecessary_wraps)]
-fn load_keys(
+async fn load_keys(
_authority: &mut T,
_zone_name: Name,
_zone_config: &ZoneConfig,
@@ -132,10 +133,9 @@ fn load_keys(
#[cfg_attr(not(feature = "dnssec"), allow(unused_mut, unused))]
#[warn(clippy::wildcard_enum_match_arm)] // make sure all cases are handled despite of non_exhaustive
-fn load_zone(
+async fn load_zone(
zone_dir: &Path,
zone_config: &ZoneConfig,
- runtime: &mut Runtime,
) -> Result, String> {
debug!("loading zone with config: {:#?}", zone_config);
@@ -166,11 +166,12 @@ fn load_zone(
is_dnssec_enabled,
Some(zone_dir),
config,
- )?;
+ )
+ .await?;
// load any keys for the Zone, if it is a dynamic update zone, then keys are required
- load_keys(&mut authority, zone_name_for_signer, zone_config)?;
- Box::new(Arc::new(Mutex::new(authority)))
+ load_keys(&mut authority, zone_name_for_signer, zone_config).await?;
+ Box::new(Arc::new(authority)) as Box
}
Some(StoreConfig::File(ref config)) => {
if zone_path.is_some() {
@@ -186,15 +187,15 @@ fn load_zone(
)?;
// load any keys for the Zone, if it is a dynamic update zone, then keys are required
- load_keys(&mut authority, zone_name_for_signer, zone_config)?;
- Box::new(Arc::new(Mutex::new(authority)))
+ load_keys(&mut authority, zone_name_for_signer, zone_config).await?;
+ Box::new(Arc::new(authority)) as Box
}
#[cfg(feature = "resolver")]
Some(StoreConfig::Forward(ref config)) => {
let forwarder = ForwardAuthority::try_from_config(zone_name, zone_type, config);
- let authority = runtime.block_on(forwarder)?;
+ let authority = forwarder.await?;
- Box::new(Arc::new(Mutex::new(authority)))
+ Box::new(Arc::new(authority)) as Box
}
#[cfg(feature = "sqlite")]
None if zone_config.is_update_allowed() => {
@@ -221,11 +222,12 @@ fn load_zone(
is_dnssec_enabled,
Some(zone_dir),
&config,
- )?;
+ )
+ .await?;
// load any keys for the Zone, if it is a dynamic update zone, then keys are required
- load_keys(&mut authority, zone_name_for_signer, zone_config)?;
- Box::new(Arc::new(Mutex::new(authority)))
+ load_keys(&mut authority, zone_name_for_signer, zone_config).await?;
+ Box::new(Arc::new(authority)) as Box
}
None => {
let config = FileConfig {
@@ -241,8 +243,8 @@ fn load_zone(
)?;
// load any keys for the Zone, if it is a dynamic update zone, then keys are required
- load_keys(&mut authority, zone_name_for_signer, zone_config)?;
- Box::new(Arc::new(Mutex::new(authority)))
+ load_keys(&mut authority, zone_name_for_signer, zone_config).await?;
+ Box::new(Arc::new(authority)) as Box
}
Some(_) => {
panic!("unrecognized authority type, check enabled features");
@@ -303,6 +305,7 @@ impl<'a> From> for Args {
/// Main method for running the named server.
///
/// `Note`: Tries to avoid panics, in favor of always starting.
+#[allow(unused_mut)]
fn main() {
let args = app_from_crate!()
.arg(
@@ -397,7 +400,7 @@ fn main() {
.get_zone()
.unwrap_or_else(|_| panic!("bad zone name in {:?}", config_path));
- match load_zone(&zone_dir, zone, &mut runtime) {
+ match runtime.block_on(load_zone(&zone_dir, zone)) {
Ok(authority) => catalog.upsert(zone_name.into(), authority),
Err(error) => panic!("could not load zone {}: {}", zone_name, error),
}
@@ -527,7 +530,7 @@ fn config_tls(
tls_cert_config: &TlsCertConfig,
zone_dir: &Path,
listen_addrs: &[IpAddr],
- runtime: &mut Runtime,
+ runtime: &mut runtime::Runtime,
) {
use futures::TryFutureExt;
@@ -580,7 +583,7 @@ fn config_https(
tls_cert_config: &TlsCertConfig,
zone_dir: &Path,
listen_addrs: &[IpAddr],
- runtime: &mut Runtime,
+ runtime: &mut runtime::Runtime,
) {
use futures::TryFutureExt;
diff --git a/crates/server/Cargo.toml b/crates/server/Cargo.toml
index 032f2a6b..56a9b7f0 100644
--- a/crates/server/Cargo.toml
+++ b/crates/server/Cargo.toml
@@ -66,6 +66,8 @@ tls = ["dns-over-openssl"]
# WARNING: there is a bug in the mutual tls auth code at the moment see issue #100
# mtls = ["trust-dns-client/mtls"]
+testing = []
+
[lib]
name = "trust_dns_server"
path = "src/lib.rs"
@@ -95,6 +97,9 @@ trust-dns-client= { version = "0.21.0-alpha.2", path = "../client" }
trust-dns-proto = { version = "0.21.0-alpha.2", path = "../proto" }
trust-dns-resolver = { version = "0.21.0-alpha.2", path = "../resolver", features = ["serde-config"], optional = true }
+[dev-dependencies]
+tokio = { version="1.0", features = ["macros", "rt"] }
+
[package.metadata.docs.rs]
all-features = true
default-target = "x86_64-unknown-linux-gnu"
diff --git a/crates/server/src/authority/authority.rs b/crates/server/src/authority/authority.rs
index a5fd1a54..5e4f7354 100644
--- a/crates/server/src/authority/authority.rs
+++ b/crates/server/src/authority/authority.rs
@@ -109,7 +109,7 @@ pub trait Authority: Send + Sync {
fn is_axfr_allowed(&self) -> bool;
/// Perform a dynamic update of a zone
- async fn update(&mut self, update: &MessageRequest) -> UpdateResult;
+ async fn update(&self, update: &MessageRequest) -> UpdateResult;
/// Get the origin of this zone, i.e. example.com is the origin for www.example.com
fn origin(&self) -> &LowerName;
@@ -191,13 +191,14 @@ pub trait Authority: Send + Sync {
/// Extension to Authority to allow for DNSSEC features
#[cfg(feature = "dnssec")]
#[cfg_attr(docsrs, doc(cfg(feature = "dnssec")))]
+#[async_trait::async_trait]
pub trait DnssecAuthority: Authority {
/// Add a (Sig0) key that is authorized to perform updates against this authority
- fn add_update_auth_key(&mut self, name: Name, key: KEY) -> DnsSecResult<()>;
+ async fn add_update_auth_key(&self, name: Name, key: KEY) -> DnsSecResult<()>;
/// Add Signer
- fn add_zone_signing_key(&mut self, signer: SigSigner) -> DnsSecResult<()>;
+ async fn add_zone_signing_key(&self, signer: SigSigner) -> DnsSecResult<()>;
/// Sign the zone for DNSSEC
- fn secure_zone(&mut self) -> DnsSecResult<()>;
+ async fn secure_zone(&self) -> DnsSecResult<()>;
}
diff --git a/crates/server/src/authority/authority_object.rs b/crates/server/src/authority/authority_object.rs
index 21fdd6a9..cbb6243e 100644
--- a/crates/server/src/authority/authority_object.rs
+++ b/crates/server/src/authority/authority_object.rs
@@ -9,7 +9,6 @@
use std::sync::Arc;
-use futures_util::lock::Mutex;
use log::debug;
use crate::{
@@ -27,16 +26,16 @@ pub trait AuthorityObject: Send + Sync {
fn box_clone(&self) -> Box;
/// What type is this zone
- async fn zone_type(&self) -> ZoneType;
+ fn zone_type(&self) -> ZoneType;
/// Return true if AXFR is allowed
- async fn is_axfr_allowed(&self) -> bool;
+ fn is_axfr_allowed(&self) -> bool;
/// Perform a dynamic update of a zone
async fn update(&self, update: &MessageRequest) -> UpdateResult;
/// Get the origin of this zone, i.e. example.com is the origin for www.example.com
- async fn origin(&self) -> LowerName;
+ fn origin(&self) -> &LowerName;
/// Looks up all Resource Records matching the giving `Name` and `RecordType`.
///
@@ -81,7 +80,7 @@ pub trait AuthorityObject: Send + Sync {
&self,
lookup_options: LookupOptions,
) -> Result, LookupError> {
- self.lookup(&self.origin().await, RecordType::NS, lookup_options)
+ self.lookup(self.origin(), RecordType::NS, lookup_options)
.await
}
@@ -104,12 +103,8 @@ pub trait AuthorityObject: Send + Sync {
/// should be used, see `soa_secure()`, which will optionally return RRSIGs.
async fn soa(&self) -> Result, LookupError> {
// SOA should be origin|SOA
- self.lookup(
- &self.origin().await,
- RecordType::SOA,
- LookupOptions::default(),
- )
- .await
+ self.lookup(self.origin(), RecordType::SOA, LookupOptions::default())
+ .await
}
/// Returns the SOA record for the zone
@@ -117,13 +112,13 @@ pub trait AuthorityObject: Send + Sync {
&self,
lookup_options: LookupOptions,
) -> Result, LookupError> {
- self.lookup(&self.origin().await, RecordType::SOA, lookup_options)
+ self.lookup(self.origin(), RecordType::SOA, lookup_options)
.await
}
}
#[async_trait::async_trait]
-impl AuthorityObject for Arc>
+impl AuthorityObject for Arc
where
A: Authority + Send + Sync + 'static,
L: LookupObject + Send + Sync + 'static,
@@ -133,23 +128,23 @@ where
}
/// What type is this zone
- async fn zone_type(&self) -> ZoneType {
- Authority::zone_type(&*self.lock().await)
+ fn zone_type(&self) -> ZoneType {
+ Authority::zone_type(self.as_ref())
}
/// Return true if AXFR is allowed
- async fn is_axfr_allowed(&self) -> bool {
- Authority::is_axfr_allowed(&*self.lock().await)
+ fn is_axfr_allowed(&self) -> bool {
+ Authority::is_axfr_allowed(self.as_ref())
}
/// Perform a dynamic update of a zone
async fn update(&self, update: &MessageRequest) -> UpdateResult {
- Authority::update(&mut *self.lock().await, update).await
+ Authority::update(self.as_ref(), update).await
}
/// Get the origin of this zone, i.e. example.com is the origin for www.example.com
- async fn origin(&self) -> LowerName {
- Authority::origin(&*self.lock().await).clone()
+ fn origin(&self) -> &LowerName {
+ Authority::origin(self.as_ref())
}
/// Looks up all Resource Records matching the giving `Name` and `RecordType`.
@@ -172,7 +167,7 @@ where
rtype: RecordType,
lookup_options: LookupOptions,
) -> Result, LookupError> {
- let this = self.lock().await;
+ let this = self.as_ref();
let lookup = Authority::lookup(&*this, name, rtype, lookup_options).await;
lookup.map(|l| Box::new(l) as Box)
}
@@ -193,7 +188,7 @@ where
query: &LowerQuery,
lookup_options: LookupOptions,
) -> Result, LookupError> {
- let this = self.lock().await;
+ let this = self.as_ref();
debug!("performing {} on {}", query, this.origin());
let lookup = Authority::search(&*this, query, lookup_options).await;
lookup.map(|l| Box::new(l) as Box)
@@ -211,7 +206,7 @@ where
name: &LowerName,
lookup_options: LookupOptions,
) -> Result, LookupError> {
- let lookup = Authority::get_nsec_records(&*self.lock().await, name, lookup_options).await;
+ let lookup = Authority::get_nsec_records(self.as_ref(), name, lookup_options).await;
lookup.map(|l| Box::new(l) as Box)
}
}
diff --git a/crates/server/src/authority/catalog.rs b/crates/server/src/authority/catalog.rs
index 84fd9d76..2339211d 100644
--- a/crates/server/src/authority/catalog.rs
+++ b/crates/server/src/authority/catalog.rs
@@ -271,7 +271,7 @@ impl Catalog {
let response_code = match result {
Ok(authority) => {
#[allow(deprecated)]
- match authority.zone_type().await {
+ match authority.zone_type() {
ZoneType::Secondary | ZoneType::Slave => {
error!("secondary forwarding for update not yet implemented");
ResponseCode::NotImp
@@ -397,7 +397,7 @@ async fn lookup(
info!(
"request: {} found authority: {}",
request.id(),
- authority.origin().await
+ authority.origin()
);
let (response_header, sections) = build_response(
@@ -466,13 +466,13 @@ async fn build_response(
}
let mut response_header = Header::response_from_request(request_header);
- response_header.set_authoritative(authority.zone_type().await.is_authoritative());
+ response_header.set_authoritative(authority.zone_type().is_authoritative());
- debug!("performing {} on {}", query, authority.origin().await);
+ debug!("performing {} on {}", query, authority.origin());
let future = authority.search(query, lookup_options);
#[allow(deprecated)]
- let sections = match authority.zone_type().await {
+ let sections = match authority.zone_type() {
ZoneType::Primary | ZoneType::Secondary | ZoneType::Master | ZoneType::Slave => {
send_authoritative_response(
future,
diff --git a/crates/server/src/server/server_future.rs b/crates/server/src/server/server_future.rs
index 2af8f139..02fca13e 100644
--- a/crates/server/src/server/server_future.rs
+++ b/crates/server/src/server/server_future.rs
@@ -207,6 +207,7 @@ impl ServerFuture {
) -> io::Result<()> {
use crate::proto::openssl::{tls_server, TlsStream};
use openssl::ssl::Ssl;
+ use std::pin::Pin;
use tokio_openssl::SslStream as TokioSslStream;
let ((cert, chain), key) = certificate_and_key;
diff --git a/crates/server/src/store/file/authority.rs b/crates/server/src/store/file/authority.rs
index 48536d80..18ae316b 100644
--- a/crates/server/src/store/file/authority.rs
+++ b/crates/server/src/store/file/authority.rs
@@ -249,7 +249,7 @@ impl Authority for FileAuthority {
}
/// Perform a dynamic update of a zone
- async fn update(&mut self, _update: &MessageRequest) -> UpdateResult {
+ async fn update(&self, _update: &MessageRequest) -> UpdateResult {
use crate::proto::op::ResponseCode;
Err(ResponseCode::NotImp)
}
@@ -337,20 +337,21 @@ impl Authority for FileAuthority {
#[cfg(feature = "dnssec")]
#[cfg_attr(docsrs, doc(cfg(feature = "dnssec")))]
+#[async_trait::async_trait]
impl DnssecAuthority for FileAuthority {
/// Add a (Sig0) key that is authorized to perform updates against this authority
- fn add_update_auth_key(&mut self, name: Name, key: KEY) -> DnsSecResult<()> {
- self.0.add_update_auth_key(name, key)
+ async fn add_update_auth_key(&self, name: Name, key: KEY) -> DnsSecResult<()> {
+ self.0.add_update_auth_key(name, key).await
}
/// Add Signer
- fn add_zone_signing_key(&mut self, signer: SigSigner) -> DnsSecResult<()> {
- self.0.add_zone_signing_key(signer)
+ async fn add_zone_signing_key(&self, signer: SigSigner) -> DnsSecResult<()> {
+ self.0.add_zone_signing_key(signer).await
}
/// Sign the zone for DNSSEC
- fn secure_zone(&mut self) -> DnsSecResult<()> {
- DnssecAuthority::secure_zone(&mut self.0)
+ async fn secure_zone(&self) -> DnsSecResult<()> {
+ DnssecAuthority::secure_zone(&self.0).await
}
}
diff --git a/crates/server/src/store/forwarder/authority.rs b/crates/server/src/store/forwarder/authority.rs
index fe48f40c..2bb26b07 100644
--- a/crates/server/src/store/forwarder/authority.rs
+++ b/crates/server/src/store/forwarder/authority.rs
@@ -84,7 +84,7 @@ impl Authority for ForwardAuthority {
false
}
- async fn update(&mut self, _update: &MessageRequest) -> UpdateResult {
+ async fn update(&self, _update: &MessageRequest) -> UpdateResult {
Err(ResponseCode::NotImp)
}
diff --git a/crates/server/src/store/in_memory/authority.rs b/crates/server/src/store/in_memory/authority.rs
index e132752f..d50a6e14 100644
--- a/crates/server/src/store/in_memory/authority.rs
+++ b/crates/server/src/store/in_memory/authority.rs
@@ -7,11 +7,19 @@
//! All authority related types
-use std::{borrow::Borrow, collections::BTreeMap, sync::Arc};
+use std::{
+ borrow::Borrow,
+ collections::BTreeMap,
+ ops::{Deref, DerefMut},
+ sync::Arc,
+};
use cfg_if::cfg_if;
-use futures_util::future::{self, TryFutureExt};
-use log::{debug, error};
+use futures_util::{
+ future::{self, TryFutureExt},
+ lock::{Mutex, MutexGuard},
+};
+use log::{debug, error, warn};
#[cfg(feature = "dnssec")]
use crate::{
@@ -42,16 +50,9 @@ use crate::{
pub struct InMemoryAuthority {
origin: LowerName,
class: DNSClass,
- records: BTreeMap>,
zone_type: ZoneType,
allow_axfr: bool,
- // Private key mapped to the Record of the DNSKey
- // TODO: these private_keys should be stored securely. Ideally, we have keys only stored per
- // server instance, but that requires requesting updates from the parent zone, which may or
- // may not support dynamic updates to register the new key... Trust-DNS will provide support
- // for this, in some form, perhaps alternate root zones...
- #[cfg(feature = "dnssec")]
- secure_keys: Vec,
+ inner: Mutex,
}
impl InMemoryAuthority {
@@ -77,6 +78,7 @@ impl InMemoryAuthority {
allow_axfr: bool,
) -> Result {
let mut this = Self::empty(origin.clone(), zone_type, allow_axfr);
+ let inner = this.inner.get_mut();
// SOA must be present
let serial = records
@@ -95,7 +97,7 @@ impl InMemoryAuthority {
let rr_type = rrset.record_type();
for record in rrset.records_without_rrsigs() {
- if !this.upsert(record.clone(), serial) {
+ if !inner.upsert(record.clone(), serial, this.class) {
return Err(format!(
"Failed to insert {} {} to zone: {}",
name, rr_type, origin
@@ -116,47 +118,231 @@ impl InMemoryAuthority {
Self {
origin: LowerName::new(&origin),
class: DNSClass::IN,
- records: BTreeMap::new(),
zone_type,
allow_axfr,
- #[cfg(feature = "dnssec")]
- secure_keys: Vec::new(),
+ inner: Mutex::new(InnerInMemory::default()),
}
}
- /// Clears all records (including SOA, etc)
- pub fn clear(&mut self) {
- self.records.clear()
- }
-
- /// Get the DNSClass of the zone
+ /// The DNSClass of this zone
pub fn class(&self) -> DNSClass {
self.class
}
- /// Enables AXFRs of all the zones records
+ /// Allow AXFR's (zone transfers)
+ #[cfg(any(test, feature = "testing"))]
+ #[cfg_attr(docsrs, doc(cfg(feature = "testing")))]
pub fn set_allow_axfr(&mut self, allow_axfr: bool) {
self.allow_axfr = allow_axfr;
}
+ /// Clears all records (including SOA, etc)
+ pub fn clear(&mut self) {
+ self.inner.get_mut().records.clear()
+ }
+
/// Retrieve the Signer, which contains the private keys, for this zone
- #[cfg(feature = "dnssec")]
- pub fn secure_keys(&self) -> &[SigSigner] {
- &self.secure_keys
+ #[cfg(all(feature = "dnssec", feature = "testing"))]
+ pub async fn secure_keys(&self) -> impl Deref + '_ {
+ MutexGuard::map(self.inner.lock().await, |i| i.secure_keys.as_mut_slice())
}
/// Get all the records
- pub fn records(&self) -> &BTreeMap> {
- &self.records
+ pub async fn records(&self) -> impl Deref>> + '_ {
+ MutexGuard::map(self.inner.lock().await, |i| &mut i.records)
}
/// Get a mutable reference to the records
- pub fn records_mut(&mut self) -> &mut BTreeMap> {
- &mut self.records
+ pub async fn records_mut(
+ &self,
+ ) -> impl DerefMut>> + '_ {
+ MutexGuard::map(self.inner.lock().await, |i| &mut i.records)
}
- fn inner_soa(&self) -> Option<&SOA> {
- let rr_key = RrKey::new(self.origin.clone(), RecordType::SOA);
+ /// Get a mutable reference to the records
+ pub fn records_get_mut(&mut self) -> &mut BTreeMap> {
+ &mut self.inner.get_mut().records
+ }
+
+ /// Returns the minimum ttl (as used in the SOA record)
+ pub async fn minimum_ttl(&self) -> u32 {
+ self.inner.lock().await.minimum_ttl(self.origin())
+ }
+
+ /// get the current serial number for the zone.
+ pub async fn serial(&self) -> u32 {
+ self.inner.lock().await.serial(self.origin())
+ }
+
+ #[cfg(any(feature = "dnssec", feature = "sqlite"))]
+ #[allow(unused)]
+ pub(crate) async fn increment_soa_serial(&self) -> u32 {
+ self.inner
+ .lock()
+ .await
+ .increment_soa_serial(self.origin(), self.class)
+ }
+
+ /// Inserts or updates a `Record` depending on it's existence in the authority.
+ ///
+ /// Guarantees that SOA, CNAME only has one record, will implicitly update if they already exist.
+ ///
+ /// # Arguments
+ ///
+ /// * `record` - The `Record` to be inserted or updated.
+ /// * `serial` - Current serial number to be recorded against updates.
+ ///
+ /// # Return value
+ ///
+ /// true if the value was inserted, false otherwise
+ pub async fn upsert(&self, record: Record, serial: u32) -> bool {
+ self.inner.lock().await.upsert(record, serial, self.class)
+ }
+
+ /// Non-async version of upsert when behind a mutable reference.
+ pub fn upsert_mut(&mut self, record: Record, serial: u32) -> bool {
+ self.inner.get_mut().upsert(record, serial, self.class)
+ }
+
+ /// Add a (Sig0) key that is authorized to perform updates against this authority
+ #[cfg(feature = "dnssec")]
+ fn inner_add_update_auth_key(
+ inner: &mut InnerInMemory,
+
+ name: Name,
+ key: KEY,
+ origin: &LowerName,
+ dns_class: DNSClass,
+ ) -> DnsSecResult<()> {
+ let rdata = RData::DNSSEC(DNSSECRData::KEY(key));
+ // TODO: what TTL?
+ let record = Record::from_rdata(name, 86400, rdata);
+
+ let serial = inner.serial(origin);
+ if inner.upsert(record, serial, dns_class) {
+ Ok(())
+ } else {
+ Err("failed to add auth key".into())
+ }
+ }
+
+ /// Non-async method of add_update_auth_key when behind a mutable reference
+ #[cfg(feature = "dnssec")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "dnssec")))]
+ pub fn add_update_auth_key_mut(&mut self, name: Name, key: KEY) -> DnsSecResult<()> {
+ let Self {
+ ref origin,
+ ref mut inner,
+ class,
+ ..
+ } = self;
+
+ Self::inner_add_update_auth_key(inner.get_mut(), name, key, origin, *class)
+ }
+
+ /// By adding a secure key, this will implicitly enable dnssec for the zone.
+ ///
+ /// # Arguments
+ ///
+ /// * `signer` - Signer with associated private key
+ #[cfg(feature = "dnssec")]
+ fn inner_add_zone_signing_key(
+ inner: &mut InnerInMemory,
+ signer: SigSigner,
+ origin: &LowerName,
+ dns_class: DNSClass,
+ ) -> DnsSecResult<()> {
+ // also add the key to the zone
+ let zone_ttl = inner.minimum_ttl(origin);
+ let dnskey = signer.key().to_dnskey(signer.algorithm())?;
+ let dnskey = Record::from_rdata(
+ origin.clone().into(),
+ zone_ttl,
+ RData::DNSSEC(DNSSECRData::DNSKEY(dnskey)),
+ );
+
+ // TODO: also generate the CDS and CDNSKEY
+ let serial = inner.serial(origin);
+ inner.upsert(dnskey, serial, dns_class);
+ inner.secure_keys.push(signer);
+ Ok(())
+ }
+
+ /// Non-async method of add_zone_signing_key when behind a mutable reference
+ #[cfg(feature = "dnssec")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "dnssec")))]
+ pub fn add_zone_signing_key_mut(&mut self, signer: SigSigner) -> DnsSecResult<()> {
+ let Self {
+ ref origin,
+ ref mut inner,
+ class,
+ ..
+ } = self;
+
+ Self::inner_add_zone_signing_key(inner.get_mut(), signer, origin, *class)
+ }
+
+ /// (Re)generates the nsec records, increments the serial number and signs the zone
+ #[cfg(feature = "dnssec")]
+ #[cfg_attr(docsrs, doc(cfg(feature = "dnssec")))]
+ pub fn secure_zone_mut(&mut self) -> DnsSecResult<()> {
+ let Self {
+ ref origin,
+ ref mut inner,
+ ..
+ } = self;
+ inner.get_mut().secure_zone_mut(origin, self.class)
+ }
+
+ /// (Re)generates the nsec records, increments the serial number and signs the zone
+ #[cfg(not(feature = "dnssec"))]
+ #[cfg_attr(docsrs, doc(cfg(feature = "dnssec")))]
+ pub fn secure_zone_mut(&mut self) -> Result<(), &str> {
+ Err("DNSSEC was not enabled during compilation.")
+ }
+}
+
+struct InnerInMemory {
+ records: BTreeMap>,
+ // Private key mapped to the Record of the DNSKey
+ // TODO: these private_keys should be stored securely. Ideally, we have keys only stored per
+ // server instance, but that requires requesting updates from the parent zone, which may or
+ // may not support dynamic updates to register the new key... Trust-DNS will provide support
+ // for this, in some form, perhaps alternate root zones...
+ #[cfg(feature = "dnssec")]
+ secure_keys: Vec,
+}
+
+impl Default for InnerInMemory {
+ fn default() -> Self {
+ Self {
+ records: BTreeMap::new(),
+ #[cfg(feature = "dnssec")]
+ secure_keys: Vec::new(),
+ }
+ }
+}
+
+impl InnerInMemory {
+ /// Retrieve the Signer, which contains the private keys, for this zone
+ #[cfg(feature = "dnssec")]
+ fn secure_keys(&self) -> &[SigSigner] {
+ &self.secure_keys
+ }
+
+ // /// Get all the records
+ // fn records(&self) -> &BTreeMap> {
+ // &self.records
+ // }
+
+ // /// Get a mutable reference to the records
+ // fn records_mut(&mut self) -> &mut BTreeMap> {
+ // &mut self.records
+ // }
+
+ fn inner_soa(&self, origin: &LowerName) -> Option<&SOA> {
+ // FIXME: can't there be an RrKeyRef?
+ let rr_key = RrKey::new(origin.clone(), RecordType::SOA);
self.records
.get(&rr_key)
@@ -165,13 +351,13 @@ impl InMemoryAuthority {
}
/// Returns the minimum ttl (as used in the SOA record)
- pub fn minimum_ttl(&self) -> u32 {
- let soa = self.inner_soa();
+ fn minimum_ttl(&self, origin: &LowerName) -> u32 {
+ let soa = self.inner_soa(origin);
let soa = match soa {
Some(soa) => soa,
None => {
- error!("could not lookup SOA for authority: {}", self.origin);
+ error!("could not lookup SOA for authority: {}", origin);
return 0;
}
};
@@ -180,13 +366,13 @@ impl InMemoryAuthority {
}
/// get the current serial number for the zone.
- pub fn serial(&self) -> u32 {
- let soa = self.inner_soa();
+ fn serial(&self, origin: &LowerName) -> u32 {
+ let soa = self.inner_soa(origin);
let soa = match soa {
Some(soa) => soa,
None => {
- error!("could not lookup SOA for authority: {}", self.origin);
+ error!("could not lookup SOA for authority: {}", origin);
return 0;
}
};
@@ -330,9 +516,9 @@ impl InMemoryAuthority {
}
#[cfg(any(feature = "dnssec", feature = "sqlite"))]
- pub(crate) fn increment_soa_serial(&mut self) -> u32 {
+ fn increment_soa_serial(&mut self, origin: &LowerName, dns_class: DNSClass) -> u32 {
// we'll remove the SOA and then replace it
- let rr_key = RrKey::new(self.origin.clone(), RecordType::SOA);
+ let rr_key = RrKey::new(origin.clone(), RecordType::SOA);
let record = self
.records
.remove(&rr_key)
@@ -342,7 +528,7 @@ impl InMemoryAuthority {
let mut record = if let Some(record) = record {
record
} else {
- error!("could not lookup SOA for authority: {}", self.origin);
+ error!("could not lookup SOA for authority: {}", origin);
return 0;
};
@@ -353,7 +539,7 @@ impl InMemoryAuthority {
panic!("This was not an SOA record"); // valid panic, never should happen
};
- self.upsert(record, serial);
+ self.upsert(record, serial, dns_class);
serial
}
@@ -369,8 +555,15 @@ impl InMemoryAuthority {
/// # Return value
///
/// true if the value was inserted, false otherwise
- pub fn upsert(&mut self, record: Record, serial: u32) -> bool {
- assert_eq!(self.class, record.dns_class());
+ fn upsert(&mut self, record: Record, serial: u32, dns_class: DNSClass) -> bool {
+ if dns_class != record.dns_class() {
+ warn!(
+ "mismatched dns_class on record insert, zone: {} record: {}",
+ dns_class,
+ record.dns_class()
+ );
+ return false;
+ }
#[cfg(feature = "dnssec")]
fn is_nsec(upsert_type: RecordType, occupied_type: RecordType) -> bool {
@@ -442,35 +635,29 @@ impl InMemoryAuthority {
/// (Re)generates the nsec records, increments the serial number and signs the zone
#[cfg(feature = "dnssec")]
#[cfg_attr(docsrs, doc(cfg(feature = "dnssec")))]
- pub fn secure_zone(&mut self) -> DnsSecResult<()> {
+ fn secure_zone_mut(&mut self, origin: &LowerName, dns_class: DNSClass) -> DnsSecResult<()> {
// TODO: only call nsec_zone after adds/deletes
// needs to be called before incrementing the soa serial, to make sure IXFR works properly
- self.nsec_zone();
+ self.nsec_zone(origin, dns_class);
// need to resign any records at the current serial number and bump the number.
// first bump the serial number on the SOA, so that it is resigned with the new serial.
- self.increment_soa_serial();
+ self.increment_soa_serial(origin, dns_class);
// TODO: should we auto sign here? or maybe up a level...
- self.sign_zone()
- }
-
- /// (Re)generates the nsec records, increments the serial number and signs the zone
- #[cfg(not(feature = "dnssec"))]
- pub fn secure_zone(&mut self) -> Result<(), &str> {
- Err("DNSSEC was not enabled during compilation.")
+ self.sign_zone(origin, dns_class)
}
/// Dummy implementation for when DNSSEC is disabled.
#[cfg(feature = "dnssec")]
- fn nsec_zone(&mut self) {
+ fn nsec_zone(&mut self, origin: &LowerName, dns_class: DNSClass) {
use crate::client::rr::rdata::NSEC;
// only create nsec records for secure zones
if self.secure_keys.is_empty() {
return;
}
- debug!("generating nsec records: {}", self.origin);
+ debug!("generating nsec records: {}", origin);
// first remove all existing nsec records
let delete_keys: Vec = self
@@ -485,8 +672,8 @@ impl InMemoryAuthority {
}
// now go through and generate the nsec records
- let ttl = self.minimum_ttl();
- let serial = self.serial();
+ let ttl = self.minimum_ttl(origin);
+ let serial = self.serial(origin);
let mut records: Vec = vec![];
{
@@ -514,7 +701,7 @@ impl InMemoryAuthority {
if let Some((name, vec)) = nsec_info {
// names aren't equal, create the NSEC record
let mut record = Record::with(name.clone(), RecordType::NSEC, ttl);
- let rdata = NSEC::new_cover_self(Authority::origin(self).clone().into(), vec);
+ let rdata = NSEC::new_cover_self(origin.clone().into(), vec);
record.set_rdata(RData::DNSSEC(DNSSECRData::NSEC(rdata)));
records.push(record);
}
@@ -522,7 +709,7 @@ impl InMemoryAuthority {
// insert all the nsec records
for record in records {
- let upserted = self.upsert(record, serial);
+ let upserted = self.upsert(record, serial, dns_class);
debug_assert!(upserted);
}
}
@@ -631,25 +818,26 @@ impl InMemoryAuthority {
/// Signs any records in the zone that have serial numbers greater than or equal to `serial`
#[cfg(feature = "dnssec")]
- fn sign_zone(&mut self) -> DnsSecResult<()> {
- use log::warn;
+ fn sign_zone(&mut self, origin: &LowerName, dns_class: DNSClass) -> DnsSecResult<()> {
+ debug!("signing zone: {}", origin);
- debug!("signing zone: {}", self.origin);
-
- let minimum_ttl = self.minimum_ttl();
+ let minimum_ttl = self.minimum_ttl(origin);
let secure_keys = &self.secure_keys;
let records = &mut self.records;
// TODO: should this be an error?
if secure_keys.is_empty() {
- warn!("attempt to sign_zone for dnssec, but no keys available!")
+ warn!(
+ "attempt to sign_zone {} for dnssec, but no keys available!",
+ origin
+ )
}
// sign all record_sets, as of 0.12.1 this includes DNSKEY
for rr_set_orig in records.values_mut() {
// because the rrset is an Arc, it must be cloned before mutated
let rr_set = Arc::make_mut(rr_set_orig);
- Self::sign_rrset(rr_set, secure_keys, minimum_ttl, self.class)?;
+ Self::sign_rrset(rr_set, secure_keys, minimum_ttl, dns_class)?;
}
Ok(())
@@ -776,7 +964,7 @@ impl Authority for InMemoryAuthority {
///
/// true if any of additions, updates or deletes were made to the zone, false otherwise. Err is
/// returned in the case of bad data, etc.
- async fn update(&mut self, _update: &MessageRequest) -> UpdateResult {
+ async fn update(&self, _update: &MessageRequest) -> UpdateResult {
Err(ResponseCode::NotImp)
}
@@ -805,13 +993,15 @@ impl Authority for InMemoryAuthority {
query_type: RecordType,
lookup_options: LookupOptions,
) -> Result {
+ let inner = self.inner.lock().await;
+
// Collect the records from each rr_set
let (result, additionals): (LookupResult, Option) =
match query_type {
RecordType::AXFR | RecordType::ANY => {
let result = AnyRecords::new(
lookup_options,
- self.records.values().cloned().collect(),
+ inner.records.values().cloned().collect(),
query_type,
name.clone(),
);
@@ -819,20 +1009,21 @@ impl Authority for InMemoryAuthority {
}
_ => {
// perform the lookup
- let answer = self.inner_lookup(name, query_type, lookup_options);
+ let answer = inner.inner_lookup(name, query_type, lookup_options);
// evaluate any cnames for additional inclusion
let additionals_root_chain_type: Option<(_, _)> = answer
.as_ref()
.and_then(|a| maybe_next_name(&*a, query_type))
.and_then(|(search_name, search_type)| {
- self.additional_search(
- query_type,
- search_name,
- search_type,
- lookup_options,
- )
- .map(|adds| (adds, search_type))
+ inner
+ .additional_search(
+ query_type,
+ search_name,
+ search_type,
+ lookup_options,
+ )
+ .map(|adds| (adds, search_type))
});
// if the chain started with an ANAME, take the A or AAAA record from the list
@@ -891,10 +1082,10 @@ impl Authority for InMemoryAuthority {
// ANAME's are constructed on demand, so need to be signed before return
if lookup_options.is_dnssec() {
- Self::sign_rrset(
+ InnerInMemory::sign_rrset(
&mut new_answer,
- self.secure_keys(),
- self.minimum_ttl(),
+ inner.secure_keys(),
+ inner.minimum_ttl(self.origin()),
self.class(),
)
// rather than failing the request, we'll just warn
@@ -935,7 +1126,7 @@ impl Authority for InMemoryAuthority {
// TODO: can we get rid of this?
let result = match result {
Err(LookupError::ResponseCode(ResponseCode::NXDomain)) => {
- if self
+ if inner
.records
.keys()
.any(|key| key.name() == name || name.zone_of(key.name()))
@@ -1026,13 +1217,14 @@ impl Authority for InMemoryAuthority {
name: &LowerName,
lookup_options: LookupOptions,
) -> Result {
+ let inner = self.inner.lock().await;
fn is_nsec_rrset(rr_set: &RecordSet) -> bool {
rr_set.record_type() == RecordType::NSEC
}
// TODO: need a BorrowdRrKey
let rr_key = RrKey::new(name.clone(), RecordType::NSEC);
- let no_data = self
+ let no_data = inner
.records
.get(&rr_key)
.map(|rr_set| LookupRecords::new(lookup_options, rr_set.clone()));
@@ -1042,7 +1234,8 @@ impl Authority for InMemoryAuthority {
}
let get_closest_nsec = |name: &LowerName| -> Option> {
- self.records
+ inner
+ .records
.values()
.rev()
.filter(|rr_set| is_nsec_rrset(rr_set))
@@ -1070,10 +1263,11 @@ impl Authority for InMemoryAuthority {
// we need the wildcard proof, but make sure that it's still part of the zone.
let wildcard = name.base_name();
- let wildcard = if self.origin().zone_of(&wildcard) {
+ let origin = self.origin();
+ let wildcard = if origin.zone_of(&wildcard) {
wildcard
} else {
- self.origin().clone()
+ origin.clone()
};
// don't duplicate the record...
@@ -1111,19 +1305,13 @@ impl Authority for InMemoryAuthority {
#[cfg(feature = "dnssec")]
#[cfg_attr(docsrs, doc(cfg(feature = "dnssec")))]
+#[async_trait::async_trait]
impl DnssecAuthority for InMemoryAuthority {
/// Add a (Sig0) key that is authorized to perform updates against this authority
- fn add_update_auth_key(&mut self, name: Name, key: KEY) -> DnsSecResult<()> {
- let rdata = RData::DNSSEC(DNSSECRData::KEY(key));
- // TODO: what TTL?
- let record = Record::from_rdata(name, 86400, rdata);
+ async fn add_update_auth_key(&self, name: Name, key: KEY) -> DnsSecResult<()> {
+ let mut inner = self.inner.lock().await;
- let serial = self.serial();
- if self.upsert(record, serial) {
- Ok(())
- } else {
- Err("failed to add auth key".into())
- }
+ Self::inner_add_update_auth_key(&mut inner, name, key, self.origin(), self.class)
}
/// By adding a secure key, this will implicitly enable dnssec for the zone.
@@ -1131,34 +1319,16 @@ impl DnssecAuthority for InMemoryAuthority {
/// # Arguments
///
/// * `signer` - Signer with associated private key
- fn add_zone_signing_key(&mut self, signer: SigSigner) -> DnsSecResult<()> {
- // also add the key to the zone
- let zone_ttl = self.minimum_ttl();
- let dnskey = signer.key().to_dnskey(signer.algorithm())?;
- let dnskey = Record::from_rdata(
- self.origin.clone().into(),
- zone_ttl,
- RData::DNSSEC(DNSSECRData::DNSKEY(dnskey)),
- );
+ async fn add_zone_signing_key(&self, signer: SigSigner) -> DnsSecResult<()> {
+ let mut inner = self.inner.lock().await;
- // TODO: also generate the CDS and CDNSKEY
- let serial = self.serial();
- self.upsert(dnskey, serial);
- self.secure_keys.push(signer);
- Ok(())
+ Self::inner_add_zone_signing_key(&mut inner, signer, self.origin(), self.class)
}
/// Sign the zone for DNSSEC
- fn secure_zone(&mut self) -> DnsSecResult<()> {
- // TODO: only call nsec_zone after adds/deletes
- // needs to be called before incrementing the soa serial, to make sure IXFR works properly
- self.nsec_zone();
+ async fn secure_zone(&self) -> DnsSecResult<()> {
+ let mut inner = self.inner.lock().await;
- // need to resign any records at the current serial number and bump the number.
- // first bump the serial number on the SOA, so that it is resigned with the new serial.
- self.increment_soa_serial();
-
- // TODO: should we auto sign here? or maybe up a level...
- self.sign_zone()
+ inner.secure_zone_mut(self.origin(), self.class)
}
}
diff --git a/crates/server/src/store/sqlite/authority.rs b/crates/server/src/store/sqlite/authority.rs
index 3365d1ea..f959ac23 100644
--- a/crates/server/src/store/sqlite/authority.rs
+++ b/crates/server/src/store/sqlite/authority.rs
@@ -13,6 +13,7 @@ use std::{
sync::Arc,
};
+use futures_util::lock::Mutex;
use log::{error, info, warn};
use crate::{
@@ -45,7 +46,7 @@ use crate::{
#[allow(dead_code)]
pub struct SqliteAuthority {
in_memory: InMemoryAuthority,
- journal: Option,
+ journal: Mutex