Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Trivariant LookupResult type to allow authorities to decline to respond to a query #2160

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
126 changes: 119 additions & 7 deletions crates/server/src/authority/authority.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use crate::proto::rr::{
Name,
};
use crate::{
authority::{LookupError, MessageRequest, UpdateResult, ZoneType},
authority::{LookupError, LookupObject, MessageRequest, UpdateResult, ZoneType},
proto::rr::{LowerName, RecordSet, RecordType, RrsetRecords},
server::RequestInfo,
};
Expand Down Expand Up @@ -129,7 +129,7 @@ pub trait Authority: Send + Sync {
name: &LowerName,
rtype: RecordType,
lookup_options: LookupOptions,
) -> Result<Self::Lookup, LookupError>;
) -> LookupResult<Self::Lookup>;

/// Using the specified query, perform a lookup against this zone.
///
Expand All @@ -146,10 +146,10 @@ pub trait Authority: Send + Sync {
&self,
request: RequestInfo<'_>,
lookup_options: LookupOptions,
) -> Result<Self::Lookup, LookupError>;
) -> LookupResult<Self::Lookup>;

/// Get the NS, NameServer, record for the zone
async fn ns(&self, lookup_options: LookupOptions) -> Result<Self::Lookup, LookupError> {
async fn ns(&self, lookup_options: LookupOptions) -> LookupResult<Self::Lookup> {
self.lookup(self.origin(), RecordType::NS, lookup_options)
.await
}
Expand All @@ -165,20 +165,20 @@ pub trait Authority: Send + Sync {
&self,
name: &LowerName,
lookup_options: LookupOptions,
) -> Result<Self::Lookup, LookupError>;
) -> LookupResult<Self::Lookup>;

/// Returns the SOA of the authority.
///
/// *Note*: This will only return the SOA, if this is fulfilling a request, a standard lookup
/// should be used, see `soa_secure()`, which will optionally return RRSIGs.
async fn soa(&self) -> Result<Self::Lookup, LookupError> {
async fn soa(&self) -> LookupResult<Self::Lookup> {
// SOA should be origin|SOA
self.lookup(self.origin(), RecordType::SOA, LookupOptions::default())
.await
}

/// Returns the SOA record for the zone
async fn soa_secure(&self, lookup_options: LookupOptions) -> Result<Self::Lookup, LookupError> {
async fn soa_secure(&self, lookup_options: LookupOptions) -> LookupResult<Self::Lookup> {
self.lookup(self.origin(), RecordType::SOA, lookup_options)
.await
}
Expand All @@ -198,3 +198,115 @@ pub trait DnssecAuthority: Authority {
/// Sign the zone for DNSSEC
async fn secure_zone(&self) -> DnsSecResult<()>;
}

/// Result of a Lookup in the Catalog and Authority
pub enum LookupResult<T, E = LookupError> {
djc marked this conversation as resolved.
Show resolved Hide resolved
/// A successful lookup result.
Ok(T),
/// An error was encountered during the lookup.
Err(E),
/// The authority did not answer the query and the next authority in the chain should
/// be consulted. Do not use this for any other purpose, in particular, do not use it
/// to represent an empty lookup (use Ok(EmptyLookup) for that.)
Skip,
}

/// The following are a minimal set of methods typically used with Result or Option, and that
/// were used in the server code or test suite prior to when the LookupResult type was created
/// (authority lookup functions previously returned a Result over a Lookup or LookupError type.)
impl<T: LookupObject + 'static, E: std::fmt::Display> LookupResult<T, E> {
/// Return LookupResult::Ok variant or panic with a custom error message.
pub fn expect(self, msg: &str) -> T {
match self {
Self::Ok(lookup) => lookup,
Self::Err(_e) => {
panic!("LookupResult expect called on LookupResult::Err: {msg}");
}
Self::Skip => {
panic!("LookupResult expect called on LookupResult::Skip: {msg}");
}
}
}

/// Return LookupResult::Err variant or panic with a custom error message.
pub fn expect_err(self, msg: &str) -> E {
match self {
Self::Ok(_) => {
panic!("LookupResult::expect_err called on LookupResult::Ok value: {msg}");
}
Self::Err(e) => e,
Self::Skip => {
panic!("LookupResult::expect_err called on LookupResult::Skip value: {msg}");
}
}
}

/// Return LookupResult::Ok variant or panic
pub fn unwrap(self) -> T {
match self {
Self::Ok(lookup) => lookup,
Self::Err(e) => {
panic!("LookupResult unwrap called on LookupResult::Err: {e}");
}
Self::Skip => {
panic!("LookupResult unwrap called on LookupResult::Skip");
}
}
}

/// Return LookupResult::Err variant or panic
pub fn unwrap_err(self) -> E {
match self {
Self::Ok(_) => {
panic!("LookupResult::unwrap_err called on LookupResult::Ok value");
}
Self::Err(e) => e,
Self::Skip => {
panic!("LookupResult::unwrap_err called on LookupResult::Skip");
}
}
}

/// Return Ok Variant or default value
pub fn unwrap_or_default(self) -> T
where
T: Default,
{
match self {
Self::Ok(lookup) => lookup,
_ => T::default(),
}
}

/// Maps LookupResult::Ok(T) to LookupResult::Ok(U), passing LookupResult::Err and
/// LookupResult::Skip values unchanged.
pub fn map<U, F: FnOnce(T) -> U>(self, op: F) -> LookupResult<U, E> {
match self {
Self::Ok(t) => LookupResult::<U, E>::Ok(op(t)),
Self::Err(e) => LookupResult::<U, E>::Err(e),
Self::Skip => LookupResult::<U, E>::Skip,
}
}

/// Maps LookupResult::Ok(T) to LookupResult::Ok(Box<dyn LookupObject>), passing LookupResult::Err and
/// LookupResult::Skip values unchanged.
pub fn map_dyn(self) -> LookupResult<Box<dyn LookupObject>, E> {
match self {
Self::Ok(t) => {
LookupResult::<Box<dyn LookupObject>, E>::Ok(Box::new(t) as Box<dyn LookupObject>)
}
Self::Err(e) => LookupResult::<Box<dyn LookupObject>, E>::Err(e),
Self::Skip => LookupResult::<Box<dyn LookupObject>, E>::Skip,
}
}

/// Maps LookupResult::Err(T) to LookupResult::Err(U), passing LookupResult::Ok and
/// LookupResult::Skip values unchanged.
pub fn map_err<U, F: FnOnce(E) -> U>(self, op: F) -> LookupResult<T, U> {
match self {
Self::Ok(t) => LookupResult::<T, U>::Ok(t),
Self::Err(e) => LookupResult::<T, U>::Err(op(e)),
Self::Skip => LookupResult::<T, U>::Skip,
}
}
}
29 changes: 13 additions & 16 deletions crates/server/src/authority/authority_object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use std::sync::Arc;
use tracing::debug;

use crate::{
authority::{Authority, LookupError, LookupOptions, MessageRequest, UpdateResult, ZoneType},
authority::{Authority, LookupOptions, LookupResult, MessageRequest, UpdateResult, ZoneType},
proto::rr::{LowerName, Record, RecordType},
server::RequestInfo,
};
Expand Down Expand Up @@ -54,7 +54,7 @@ pub trait AuthorityObject: Send + Sync {
name: &LowerName,
rtype: RecordType,
lookup_options: LookupOptions,
) -> Result<Box<dyn LookupObject>, LookupError>;
) -> LookupResult<Box<dyn LookupObject>>;

/// Using the specified query, perform a lookup against this zone.
///
Expand All @@ -71,13 +71,10 @@ pub trait AuthorityObject: Send + Sync {
&self,
request_info: RequestInfo<'_>,
lookup_options: LookupOptions,
) -> Result<Box<dyn LookupObject>, LookupError>;
) -> LookupResult<Box<dyn LookupObject>>;

/// Get the NS, NameServer, record for the zone
async fn ns(
&self,
lookup_options: LookupOptions,
) -> Result<Box<dyn LookupObject>, LookupError> {
async fn ns(&self, lookup_options: LookupOptions) -> LookupResult<Box<dyn LookupObject>> {
self.lookup(self.origin(), RecordType::NS, lookup_options)
.await
}
Expand All @@ -93,13 +90,13 @@ pub trait AuthorityObject: Send + Sync {
&self,
name: &LowerName,
lookup_options: LookupOptions,
) -> Result<Box<dyn LookupObject>, LookupError>;
) -> LookupResult<Box<dyn LookupObject>>;

/// Returns the SOA of the authority.
///
/// *Note*: This will only return the SOA, if this is fulfilling a request, a standard lookup
/// should be used, see `soa_secure()`, which will optionally return RRSIGs.
async fn soa(&self) -> Result<Box<dyn LookupObject>, LookupError> {
async fn soa(&self) -> LookupResult<Box<dyn LookupObject>> {
// SOA should be origin|SOA
self.lookup(self.origin(), RecordType::SOA, LookupOptions::default())
.await
Expand All @@ -109,7 +106,7 @@ pub trait AuthorityObject: Send + Sync {
async fn soa_secure(
&self,
lookup_options: LookupOptions,
) -> Result<Box<dyn LookupObject>, LookupError> {
) -> LookupResult<Box<dyn LookupObject>> {
self.lookup(self.origin(), RecordType::SOA, lookup_options)
.await
}
Expand Down Expand Up @@ -164,10 +161,10 @@ where
name: &LowerName,
rtype: RecordType,
lookup_options: LookupOptions,
) -> Result<Box<dyn LookupObject>, LookupError> {
) -> LookupResult<Box<dyn LookupObject>> {
let this = self.as_ref();
let lookup = Authority::lookup(this, name, rtype, lookup_options).await;
lookup.map(|l| Box::new(l) as Box<dyn LookupObject>)
lookup.map_dyn() //(|l| Box::new(l) as Box<dyn LookupObject>)
}

/// Using the specified query, perform a lookup against this zone.
Expand All @@ -185,11 +182,11 @@ where
&self,
request_info: RequestInfo<'_>,
lookup_options: LookupOptions,
) -> Result<Box<dyn LookupObject>, LookupError> {
) -> LookupResult<Box<dyn LookupObject>> {
let this = self.as_ref();
debug!("performing {} on {}", request_info.query, this.origin());
let lookup = Authority::search(this, request_info, lookup_options).await;
lookup.map(|l| Box::new(l) as Box<dyn LookupObject>)
lookup.map_dyn() //(|l| Box::new(l) as Box<dyn LookupObject>)
}

/// Return the NSEC records based on the given name
Expand All @@ -203,9 +200,9 @@ where
&self,
name: &LowerName,
lookup_options: LookupOptions,
) -> Result<Box<dyn LookupObject>, LookupError> {
) -> LookupResult<Box<dyn LookupObject>> {
let lookup = Authority::get_nsec_records(self.as_ref(), name, lookup_options).await;
lookup.map(|l| Box::new(l) as Box<dyn LookupObject>)
lookup.map_dyn() //(|l| Box::new(l) as Box<dyn LookupObject>)
}
}

Expand Down