diff --git a/easytier/src/dns/utils/zone_handler.rs b/easytier/src/dns/utils/zone_handler.rs index 28cca2b3..15b1d7de 100644 --- a/easytier/src/dns/utils/zone_handler.rs +++ b/easytier/src/dns/utils/zone_handler.rs @@ -4,12 +4,28 @@ use hickory_proto::op::ResponseCode; use hickory_proto::rr::{LowerName, RecordType, TSigResponseContext}; use hickory_server::server::{Request, RequestInfo}; use hickory_server::zone_handler::{ - AuthLookup, AxfrPolicy, LookupControlFlow, LookupOptions, ZoneHandler, ZoneType, + AuthLookup, AxfrPolicy, LookupControlFlow, LookupError, LookupOptions, ZoneHandler, ZoneType, }; use std::sync::Arc; pub type ArcZoneHandler = Arc; +pub trait LookupControlFlowExt { + fn skip_negative(self) -> Self; +} + +impl LookupControlFlowExt for LookupControlFlow { + fn skip_negative(self) -> Self { + match self { + Self::Continue(e) | Self::Break(e) if matches!(e, Err(LookupError::NameExists)) => { + Self::Continue(Ok(Default::default())) + } + Self::Continue(Err(_)) | Self::Break(Err(_)) => Self::Skip, + other => other, + } + } +} + #[derive(From, Deref, DerefMut)] pub struct ChainedZoneHandler(H) where @@ -47,6 +63,7 @@ where self.0 .lookup(name, rtype, request_info, lookup_options) .await + .skip_negative() } #[inline] async fn consult( @@ -63,6 +80,7 @@ where self.0 .lookup(name, rtype, request_info, lookup_options) .await + .skip_negative() }; (result, None) } @@ -72,7 +90,8 @@ where request: &Request, lookup_options: LookupOptions, ) -> (LookupControlFlow, Option) { - self.0.search(request, lookup_options).await + let (result, tsig) = self.0.search(request, lookup_options).await; + (result.skip_negative(), tsig) } #[inline] async fn nsec_records( diff --git a/easytier/src/dns/zone.rs b/easytier/src/dns/zone.rs index c5b492d6..c40ca507 100644 --- a/easytier/src/dns/zone.rs +++ b/easytier/src/dns/zone.rs @@ -411,6 +411,97 @@ mod tests { Ok(()) } + #[tokio::test] + async fn catalog_lookup_returns_nodata_on_nameexists() -> anyhow::Result<()> { + let upstream = Zone::try_from(&zone_data( + "forward-aaaa.test", + vec!["host 60 IN AAAA 2001:db8::1"], + vec![], + ))?; + + let mut upstream_catalog = Catalog::new(); + upstream_catalog.upsert( + upstream.origin.clone(), + vec![upstream.create_memory_zone_handler().unwrap()], + ); + + let socket = UdpSocket::bind("127.0.0.1:0").await?; + let upstream_addr = socket.local_addr()?; + + let mut server = Server::new(upstream_catalog); + server.register_socket(socket); + let upstream_handle = tokio::spawn(async move { + let _ = server.block_until_done().await; + }); + + let zones: ZoneGroup = vec![Zone::try_from(&zone_data( + "forward-aaaa.test", + vec!["host IN A 10.20.30.40"], + vec![&format!("udp://{}", upstream_addr)], + ))?] + .into(); + let catalog = build_catalog(zones); + + let (rcode, message) = + lookup_message(&catalog, "host.forward-aaaa.test.", RecordType::AAAA).await?; + assert_eq!(rcode, ResponseCode::NoError); + + let message = message.expect("response should exist"); + assert!( + message.answers.is_empty(), + "NameExists should return NODATA" + ); + + upstream_handle.abort(); + let _ = upstream_handle.await; + + Ok(()) + } + + #[tokio::test] + async fn catalog_lookup_forwards_on_nxdomain() -> anyhow::Result<()> { + let upstream = Zone::try_from(&zone_data( + "forward-nxdomain.test", + vec!["missing 60 IN A 203.0.113.55"], + vec![], + ))?; + + let mut upstream_catalog = Catalog::new(); + upstream_catalog.upsert( + upstream.origin.clone(), + vec![upstream.create_memory_zone_handler().unwrap()], + ); + + let socket = UdpSocket::bind("127.0.0.1:0").await?; + let upstream_addr = socket.local_addr()?; + + let mut server = Server::new(upstream_catalog); + server.register_socket(socket); + let upstream_handle = tokio::spawn(async move { + let _ = server.block_until_done().await; + }); + + let zones: ZoneGroup = vec![Zone::try_from(&zone_data( + "forward-nxdomain.test", + vec!["present IN A 10.20.30.41"], + vec![&format!("udp://{}", upstream_addr)], + ))?] + .into(); + let catalog = build_catalog(zones); + + let (rcode, message) = + lookup_message(&catalog, "missing.forward-nxdomain.test.", RecordType::A).await?; + assert_eq!(rcode, ResponseCode::NoError); + + let message = message.expect("response should exist"); + assert!(has_a_answer(&message, Ipv4Addr::new(203, 0, 113, 55))); + + upstream_handle.abort(); + let _ = upstream_handle.await; + + Ok(()) + } + #[tokio::test] async fn catalog_lookup_falls_back_to_later_zone_handler_with_same_origin() -> anyhow::Result<()> {