zone: fallthrough policy

heartbeat test

try_from
This commit is contained in:
Luna Yao
2026-04-30 18:56:00 +02:00
parent ee1f656d6b
commit b7677031cb
10 changed files with 146 additions and 147 deletions
+1
View File
@@ -71,6 +71,7 @@ tokio-util = { version = "0.7.9", features = ["codec", "net", "io", "rt"] }
async-stream = "0.3.5" async-stream = "0.3.5"
async-trait = "0.1.74" async-trait = "0.1.74"
maplit = "1.0.2"
dashmap = "6.0" dashmap = "6.0"
timedmap = "=1.0.1" timedmap = "=1.0.1"
+1 -1
View File
@@ -735,7 +735,7 @@ impl DnsGlobalCtxExt for GlobalCtx {
.clone() .clone()
.unwrap_or_else(|| dns::parse(self.get_hostname())) .unwrap_or_else(|| dns::parse(self.get_hostname()))
.into(); .into();
let fqdn = name.append_domain(&*dns.domain).unwrap_or_default().into(); let fqdn = name.append_domain(&dns.domain).unwrap_or_default().into();
let ipv4 = self.get_ipv4().map(|ip| ip.address()); let ipv4 = self.get_ipv4().map(|ip| ip.address());
let ipv6 = self.get_ipv6().map(|ip| ip.address()); let ipv6 = self.get_ipv6().map(|ip| ip.address());
let ipv6 = ipv6.map(|a| vec![a]).unwrap_or_default(); let ipv6 = ipv6.map(|a| vec![a]).unwrap_or_default();
+33 -5
View File
@@ -3,12 +3,40 @@ use crate::dns::config::policy::{DnsExportPolicy, ZonePolicyConfig};
use crate::dns::utils::addr::NameServerAddrGroup; use crate::dns::utils::addr::NameServerAddrGroup;
use crate::dns::zone::Zone; use crate::dns::zone::Zone;
use crate::proto::dns::ZoneData; use crate::proto::dns::ZoneData;
use derive_more::From;
use hickory_proto::op::ResponseCode;
use hickory_proto::rr::LowerName; use hickory_proto::rr::LowerName;
use maplit::hashset;
use optional_struct::{Applicable, optional_struct}; use optional_struct::{Applicable, optional_struct};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::collections::HashSet;
use std::convert::TryFrom; use std::convert::TryFrom;
use std::net::{Ipv4Addr, Ipv6Addr}; use std::net::{Ipv4Addr, Ipv6Addr};
use url::Url;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Hash, From, Deserialize, Serialize)]
#[serde(untagged)]
pub enum Fallthrough {
Any,
ResponseCode(ResponseCode),
}
impl From<Fallthrough> for i32 {
fn from(value: Fallthrough) -> Self {
match value {
Fallthrough::ResponseCode(code) => u16::from(code).into(),
Fallthrough::Any => -1,
}
}
}
impl From<i32> for Fallthrough {
fn from(value: i32) -> Self {
match u16::try_from(value) {
Ok(value) => Self::ResponseCode(value.into()),
Err(_) => Self::Any,
}
}
}
#[optional_struct(ZoneConfigRaw)] #[optional_struct(ZoneConfigRaw)]
#[derive(Debug, Clone, Default, PartialEq, Deserialize, Serialize)] #[derive(Debug, Clone, Default, PartialEq, Deserialize, Serialize)]
@@ -21,7 +49,7 @@ pub struct ZoneConfigParsed {
#[optional_skip_wrap] #[optional_skip_wrap]
#[serde(flatten)] #[serde(flatten)]
pub policy: ZonePolicyConfig, pub policy: ZonePolicyConfig,
pub fallthrough: bool, pub fallthrough: HashSet<Fallthrough>,
} }
impl From<&ZoneConfigParsed> for ZoneData { impl From<&ZoneConfigParsed> for ZoneData {
@@ -30,8 +58,8 @@ impl From<&ZoneConfigParsed> for ZoneData {
&value.origin, &value.origin,
value.ttl, value.ttl,
&value.records, &value.records,
value.forwarders.iter().map(Url::from), value.forwarders.iter().map(Into::into),
value.fallthrough, value.fallthrough.iter().copied(),
) )
} }
} }
@@ -43,7 +71,7 @@ impl TryFrom<ZoneConfigRaw> for ZoneConfig {
fn try_from(raw: ZoneConfigRaw) -> Result<Self, Self::Error> { fn try_from(raw: ZoneConfigRaw) -> Result<Self, Self::Error> {
let default = ZoneConfigParsed { let default = ZoneConfigParsed {
fallthrough: true, fallthrough: hashset! {Fallthrough::Any},
..Default::default() ..Default::default()
}; };
+2 -8
View File
@@ -254,10 +254,10 @@ mod tests {
use crate::peers::peer_manager::RouteAlgoType; use crate::peers::peer_manager::RouteAlgoType;
use crate::peers::tests::{connect_peer_manager, wait_route_appear}; use crate::peers::tests::{connect_peer_manager, wait_route_appear};
use crate::proto::dns::GetExportConfigRequest; use crate::proto::dns::GetExportConfigRequest;
use std::collections::HashSet; use std::collections::HashSet;
use std::net::Ipv4Addr; use std::net::Ipv4Addr;
use tokio::time::{Duration, sleep}; use tokio::time::{Duration, sleep};
use url::Url;
async fn create_peer_manager_with_zone( async fn create_peer_manager_with_zone(
host: &str, host: &str,
@@ -296,13 +296,7 @@ mod tests {
#[test] #[test]
fn dns_peer_info_try_from_invalid_zone_rejected() { fn dns_peer_info_try_from_invalid_zone_rejected() {
let cfg = DnsExportConfig { let cfg = DnsExportConfig {
zones: vec![ZoneData::new( zones: vec![ZoneData::new(&".".parse().unwrap(), 60, ["?"], [], [])],
&".".parse().unwrap(),
60,
["?"],
Vec::<Url>::new(),
false,
)],
}; };
assert!(DnsPeerInfo::try_from(cfg).is_err()); assert!(DnsPeerInfo::try_from(cfg).is_err());
+3 -4
View File
@@ -28,6 +28,7 @@ use hickory_proto::rr;
use hickory_proto::rr::{DNSClass, Name, RData, RecordType}; use hickory_proto::rr::{DNSClass, Name, RData, RecordType};
use hickory_proto::serialize::binary::{BinEncodable, BinEncoder}; use hickory_proto::serialize::binary::{BinEncodable, BinEncoder};
use hickory_server::server::Request; use hickory_server::server::Request;
use maplit::hashset;
use tokio::sync::Notify; use tokio::sync::Notify;
use uuid::Uuid; use uuid::Uuid;
@@ -108,10 +109,8 @@ pub fn zone_data_a_with_forwarders(origin: &str, record: &str, forwarders: Vec<&
&origin.parse().unwrap(), &origin.parse().unwrap(),
60, 60,
[format!("@ IN A {record}")], [format!("@ IN A {record}")],
forwarders forwarders.into_iter().map(|f| Url::from_str(f).unwrap()),
.into_iter() hashset! {},
.map(|f| Url::from_str(f).expect("invalid forwarder")),
false,
) )
} }
+31 -25
View File
@@ -39,31 +39,19 @@ impl From<(IpAddr, &ConnectionConfig)> for NameServerAddr {
} }
} }
impl From<&NameServerAddr> for Url {
fn from(value: &NameServerAddr) -> Self {
Url::parse(&format!("{}://{}", value.protocol, value.addr)).unwrap()
}
}
impl From<NameServerAddr> for Url {
fn from(value: NameServerAddr) -> Self {
(&value).into()
}
}
impl TryFrom<&Url> for NameServerAddr { impl TryFrom<&Url> for NameServerAddr {
type Error = Error; type Error = Error;
fn try_from(value: &Url) -> Result<Self, Self::Error> { fn try_from(url: &Url) -> Result<Self, Self::Error> {
let protocol = match Protocol::deserialize(value.scheme().into_deserializer()).map_err( let protocol = match Protocol::deserialize(url.scheme().into_deserializer())
|e: de::value::Error| anyhow!("invalid protocol '{}': {}", value.scheme(), e), .map_err(|e: de::value::Error| anyhow!("invalid protocol '{}': {}", url.scheme(), e))?
)? { {
Protocol::Udp => ProtocolConfig::Udp, Protocol::Udp => ProtocolConfig::Udp,
Protocol::Tcp => ProtocolConfig::Tcp, Protocol::Tcp => ProtocolConfig::Tcp,
p => return Err(anyhow!("unsupported protocol: {}", p)), p => return Err(anyhow!("unsupported protocol: {}", p)),
}; };
let host = value.host_str().ok_or(anyhow!("host not found"))?; let host = url.host_str().ok_or(anyhow!("host not found"))?;
let port = value.port().unwrap_or(protocol.default_port()); let port = url.port().unwrap_or(protocol.default_port());
let addr = if let Ok(addr) = IpAddr::from_str(host) { let addr = if let Ok(addr) = IpAddr::from_str(host) {
SocketAddr::new(addr, port) SocketAddr::new(addr, port)
} else { } else {
@@ -76,17 +64,35 @@ impl TryFrom<&Url> for NameServerAddr {
} }
} }
impl From<NameServerAddr> for proto::common::Url {
fn from(value: NameServerAddr) -> Self {
Url::from(value).into()
}
}
impl TryFrom<&proto::common::Url> for NameServerAddr { impl TryFrom<&proto::common::Url> for NameServerAddr {
type Error = Error; type Error = Error;
fn try_from(value: &proto::common::Url) -> Result<Self, Self::Error> { fn try_from(value: &proto::common::Url) -> Result<Self, Self::Error> {
Self::try_from(&Url::try_from(value)?) (&Url::try_from(value)?).try_into()
}
}
impl From<&NameServerAddr> for Url {
fn from(value: &NameServerAddr) -> Self {
Url::parse(&format!("{}://{}", value.protocol, value.addr)).unwrap()
}
}
impl From<&NameServerAddr> for proto::common::Url {
fn from(value: &NameServerAddr) -> Self {
Url::from(value).into()
}
}
impl From<NameServerAddr> for Url {
fn from(value: NameServerAddr) -> Self {
(&value).into()
}
}
impl From<NameServerAddr> for proto::common::Url {
fn from(value: NameServerAddr) -> Self {
(&value).into()
} }
} }
+27 -46
View File
@@ -1,35 +1,27 @@
use crate::dns::config::zone::Fallthrough;
use delegate::delegate; use delegate::delegate;
use derive_more::{Deref, DerefMut, From}; use derive_more::{Constructor, Deref, DerefMut};
use hickory_proto::op::ResponseCode; use hickory_proto::op::ResponseCode;
use hickory_proto::rr::{LowerName, RecordType, TSigResponseContext}; use hickory_proto::rr::{LowerName, RecordType, TSigResponseContext};
use hickory_server::server::{Request, RequestInfo}; use hickory_server::server::{Request, RequestInfo};
use hickory_server::zone_handler::{ use hickory_server::zone_handler::{
AuthLookup, AxfrPolicy, LookupControlFlow, LookupError, LookupOptions, ZoneHandler, ZoneType, AuthLookup, AxfrPolicy, LookupControlFlow, LookupError, LookupOptions, ZoneHandler, ZoneType,
}; };
use std::collections::HashSet;
use std::sync::Arc; use std::sync::Arc;
pub type ArcZoneHandler = Arc<dyn ZoneHandler>; pub type ArcZoneHandler = Arc<dyn ZoneHandler>;
pub trait LookupControlFlowExt { #[derive(Constructor, Deref, DerefMut)]
fn skip_negative(self) -> Self; pub struct ChainedZoneHandler<H>
}
impl LookupControlFlowExt for LookupControlFlow<AuthLookup> {
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>(H)
where where
H: ZoneHandler; H: ZoneHandler,
{
#[deref]
#[deref_mut]
handler: H,
fallthrough: HashSet<Fallthrough>,
}
#[async_trait::async_trait] #[async_trait::async_trait]
impl<H> ZoneHandler for ChainedZoneHandler<H> impl<H> ZoneHandler for ChainedZoneHandler<H>
@@ -37,7 +29,7 @@ where
H: ZoneHandler, H: ZoneHandler,
{ {
delegate! { delegate! {
to self.0 { to self.handler {
fn zone_type(&self) -> ZoneType; fn zone_type(&self) -> ZoneType;
fn axfr_policy(&self) -> AxfrPolicy; fn axfr_policy(&self) -> AxfrPolicy;
fn origin(&self) -> &LowerName; fn origin(&self) -> &LowerName;
@@ -50,7 +42,7 @@ where
update: &Request, update: &Request,
now: u64, now: u64,
) -> (Result<bool, ResponseCode>, Option<TSigResponseContext>) { ) -> (Result<bool, ResponseCode>, Option<TSigResponseContext>) {
self.0.update(update, now).await self.handler.update(update, now).await
} }
#[inline] #[inline]
async fn lookup( async fn lookup(
@@ -60,29 +52,9 @@ where
request_info: Option<&RequestInfo<'_>>, request_info: Option<&RequestInfo<'_>>,
lookup_options: LookupOptions, lookup_options: LookupOptions,
) -> LookupControlFlow<AuthLookup> { ) -> LookupControlFlow<AuthLookup> {
self.0 self.handler
.lookup(name, rtype, request_info, lookup_options) .lookup(name, rtype, request_info, lookup_options)
.await .await
.skip_negative()
}
#[inline]
async fn consult(
&self,
name: &LowerName,
rtype: RecordType,
request_info: Option<&RequestInfo<'_>>,
lookup_options: LookupOptions,
last_result: LookupControlFlow<AuthLookup>,
) -> (LookupControlFlow<AuthLookup>, Option<TSigResponseContext>) {
let result = if let Some(Ok(l)) = last_result.map_result() {
LookupControlFlow::Break(Ok(l))
} else {
self.0
.lookup(name, rtype, request_info, lookup_options)
.await
.skip_negative()
};
(result, None)
} }
#[inline] #[inline]
async fn search( async fn search(
@@ -90,8 +62,17 @@ where
request: &Request, request: &Request,
lookup_options: LookupOptions, lookup_options: LookupOptions,
) -> (LookupControlFlow<AuthLookup>, Option<TSigResponseContext>) { ) -> (LookupControlFlow<AuthLookup>, Option<TSigResponseContext>) {
let (result, tsig) = self.0.search(request, lookup_options).await; let (result, tsig) = self.handler.search(request, lookup_options).await;
(result.skip_negative(), tsig)
match &result {
LookupControlFlow::Continue(Err(e)) | LookupControlFlow::Break(Err(e))
if self.fallthrough.contains(&Fallthrough::Any)
|| matches!(e, LookupError::ResponseCode(c) if self.fallthrough.contains(&(*c).into())) =>
{
(LookupControlFlow::Skip, None)
}
_ => (result, tsig),
}
} }
#[inline] #[inline]
async fn nsec_records( async fn nsec_records(
@@ -99,6 +80,6 @@ where
name: &LowerName, name: &LowerName,
lookup_options: LookupOptions, lookup_options: LookupOptions,
) -> LookupControlFlow<AuthLookup> { ) -> LookupControlFlow<AuthLookup> {
self.0.nsec_records(name, lookup_options).await self.handler.nsec_records(name, lookup_options).await
} }
} }
+39 -45
View File
@@ -1,3 +1,4 @@
use crate::dns::config::zone::Fallthrough;
use crate::dns::utils::addr::{NameServerAddr, NameServerAddrGroup}; use crate::dns::utils::addr::{NameServerAddr, NameServerAddrGroup};
use crate::dns::utils::zone_handler::{ArcZoneHandler, ChainedZoneHandler}; use crate::dns::utils::zone_handler::{ArcZoneHandler, ChainedZoneHandler};
use crate::proto::dns::ZoneData; use crate::proto::dns::ZoneData;
@@ -11,16 +12,16 @@ use hickory_server::store::in_memory::InMemoryZoneHandler;
use hickory_server::zone_handler::{AxfrPolicy, ZoneType}; use hickory_server::zone_handler::{AxfrPolicy, ZoneType};
use indexmap::IndexMap; use indexmap::IndexMap;
use itertools::chain; use itertools::chain;
use std::collections::BTreeMap; use maplit::hashset;
use std::collections::{BTreeMap, HashSet};
use std::sync::Arc; use std::sync::Arc;
use url::Url;
#[derive(Debug, Clone)] #[derive(Debug, Clone, Default)]
pub struct Zone { pub struct Zone {
origin: LowerName, origin: LowerName,
records: BTreeMap<RrKey, RecordSet>, records: BTreeMap<RrKey, RecordSet>,
pub forward: Option<ForwardConfig>, pub forward: Option<ForwardConfig>,
fallthrough: bool, fallthrough: HashSet<Fallthrough>,
} }
impl Zone { impl Zone {
@@ -30,23 +31,16 @@ impl Zone {
name_servers: config.name_servers().to_vec(), name_servers: config.name_servers().to_vec(),
options: Some(opts), options: Some(opts),
}; };
let mut zone = Self::new(".".parse().unwrap()); Self {
zone.forward = Some(forward); origin: ".".parse().unwrap(),
zone.fallthrough = false; forward: Some(forward),
zone fallthrough: hashset! {},
..Default::default()
}
} }
} }
impl Zone { impl Zone {
pub fn new(name: LowerName) -> Self {
Self {
origin: name,
records: BTreeMap::new(),
forward: None,
fallthrough: true,
}
}
pub fn create_memory_zone_handler(&self) -> Option<ArcZoneHandler> { pub fn create_memory_zone_handler(&self) -> Option<ArcZoneHandler> {
(!self.records.is_empty()).then(|| { (!self.records.is_empty()).then(|| {
let mut memory = InMemoryZoneHandler::<TokioRuntimeProvider>::empty( let mut memory = InMemoryZoneHandler::<TokioRuntimeProvider>::empty(
@@ -62,11 +56,7 @@ impl Zone {
.map(|(k, v)| (k, Arc::new(v))), .map(|(k, v)| (k, Arc::new(v))),
); );
if self.fallthrough { Arc::new(ChainedZoneHandler::new(memory, self.fallthrough.clone())) as _
Arc::new(ChainedZoneHandler::from(memory)) as _
} else {
Arc::new(memory) as _
}
}) })
} }
@@ -77,14 +67,10 @@ impl Zone {
TokioRuntimeProvider::default(), TokioRuntimeProvider::default(),
) )
.build() .build()
.inspect_err(|e| tracing::error!("failed to create forward zone_handler: {:?}", e)) .inspect_err(|error| tracing::error!(?error, "failed to create forward zone_handler"))
.ok() .ok()
.map(|f| { .map(|handler| {
if self.fallthrough { Arc::new(ChainedZoneHandler::new(handler, self.fallthrough.clone())) as _
Arc::new(ChainedZoneHandler::from(f)) as _
} else {
Arc::new(f) as _
}
}) })
}) })
} }
@@ -101,7 +87,7 @@ impl TryFrom<&ZoneData> for Zone {
let name_servers = value let name_servers = value
.forwarders .forwarders
.iter() .iter()
.map(TryInto::<NameServerAddr>::try_into) .map(NameServerAddr::try_from)
.map(|a| a.map(Into::into)) .map(|a| a.map(Into::into))
.collect::<Result<Vec<_>, _>>()?; .collect::<Result<Vec<_>, _>>()?;
let forward = (!name_servers.is_empty()).then_some(ForwardConfig { let forward = (!name_servers.is_empty()).then_some(ForwardConfig {
@@ -109,11 +95,13 @@ impl TryFrom<&ZoneData> for Zone {
options: None, options: None,
}); });
let fallthrough = value.fallthrough.iter().copied().map(Into::into).collect();
Ok(Self { Ok(Self {
origin: origin.into(), origin: origin.into(),
records, records,
forward, forward,
fallthrough: value.fallthrough, fallthrough,
}) })
} }
} }
@@ -132,7 +120,7 @@ impl From<Zone> for ZoneData {
.flat_map(|f| f.name_servers.into_iter()) .flat_map(|f| f.name_servers.into_iter())
.map(|ns| (&ns).into()) .map(|ns| (&ns).into())
.flat_map(NameServerAddrGroup::into_iter) .flat_map(NameServerAddrGroup::into_iter)
.map(Url::from); .map(Into::into);
Self::new(&value.origin, 0, records, forwarders, value.fallthrough) Self::new(&value.origin, 0, records, forwarders, value.fallthrough)
} }
@@ -169,7 +157,9 @@ mod tests {
use hickory_proto::rr::{RData, Record, RecordType, RrsetRecords}; use hickory_proto::rr::{RData, Record, RecordType, RrsetRecords};
use hickory_server::Server; use hickory_server::Server;
use hickory_server::zone_handler::Catalog; use hickory_server::zone_handler::Catalog;
use std::net::{Ipv4Addr, SocketAddr}; use maplit::hashset;
use std::collections::HashSet;
use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr};
use std::str::FromStr; use std::str::FromStr;
use tokio::net::UdpSocket; use tokio::net::UdpSocket;
use tokio::task::JoinHandle; use tokio::task::JoinHandle;
@@ -193,21 +183,21 @@ mod tests {
origin: &str, origin: &str,
records: Vec<&str>, records: Vec<&str>,
forwarders: Vec<&str>, forwarders: Vec<&str>,
fallthrough: bool, fallthrough: HashSet<Fallthrough>,
) -> ZoneData { ) -> ZoneData {
ZoneData::new( ZoneData::new(
&origin.parse().unwrap(), &origin.parse().unwrap(),
60, 60,
records, records,
forwarders.into_iter().map(|url| Url { forwarders
url: url.to_string(), .into_iter()
}), .map(|url| Url::from_str(url).unwrap()),
fallthrough, fallthrough,
) )
} }
fn zone_data(origin: &str, records: Vec<&str>, forwarders: Vec<&str>) -> ZoneData { fn zone_data(origin: &str, records: Vec<&str>, forwarders: Vec<&str>) -> ZoneData {
zone_data_with_fallthrough(origin, records, forwarders, true) zone_data_with_fallthrough(origin, records, forwarders, hashset! {Fallthrough::Any})
} }
fn build_catalog(zones: ZoneGroup) -> Catalog { fn build_catalog(zones: ZoneGroup) -> Catalog {
@@ -242,6 +232,13 @@ mod tests {
.any(|record| matches!(record.data, RData::A(addr) if *addr == expected)) .any(|record| matches!(record.data, RData::A(addr) if *addr == expected))
} }
fn has_aaaa_answer(message: &Message, expected: Ipv6Addr) -> bool {
message
.answers
.iter()
.any(|record| matches!(record.data, RData::AAAA(addr) if *addr == expected))
}
async fn start_upstream_server() -> anyhow::Result<(SocketAddr, JoinHandle<()>)> { async fn start_upstream_server() -> anyhow::Result<(SocketAddr, JoinHandle<()>)> {
let upstream = Zone::try_from(&zone_data( let upstream = Zone::try_from(&zone_data(
"upstream.test", "upstream.test",
@@ -400,7 +397,7 @@ mod tests {
} }
#[tokio::test] #[tokio::test]
async fn catalog_lookup_returns_nodata_on_nameexists() -> anyhow::Result<()> { async fn catalog_lookup_forwards_on_nameexists() -> anyhow::Result<()> {
let upstream = Zone::try_from(&zone_data( let upstream = Zone::try_from(&zone_data(
"forward-aaaa.test", "forward-aaaa.test",
vec!["host 60 IN AAAA 2001:db8::1"], vec!["host 60 IN AAAA 2001:db8::1"],
@@ -435,10 +432,7 @@ mod tests {
assert_eq!(rcode, ResponseCode::NoError); assert_eq!(rcode, ResponseCode::NoError);
let message = message.expect("response should exist"); let message = message.expect("response should exist");
assert!( assert!(has_aaaa_answer(&message, "2001:db8::1".parse()?));
message.answers.is_empty(),
"NameExists should return NODATA"
);
upstream_handle.abort(); upstream_handle.abort();
let _ = upstream_handle.await; let _ = upstream_handle.await;
@@ -528,13 +522,13 @@ mod tests {
"fallback-disabled.test", "fallback-disabled.test",
vec!["first IN A 10.20.31.1"], vec!["first IN A 10.20.31.1"],
vec![], vec![],
false, hashset! {},
))?, ))?,
Zone::try_from(&zone_data_with_fallthrough( Zone::try_from(&zone_data_with_fallthrough(
"fallback-disabled.test", "fallback-disabled.test",
vec!["target IN A 10.20.31.2"], vec!["target IN A 10.20.31.2"],
vec![], vec![],
false, hashset! {},
))?, ))?,
] ]
.into(); .into();
+1 -1
View File
@@ -7,7 +7,7 @@ package dns;
message ZoneData { message ZoneData {
string content = 1; string content = 1;
repeated common.Url forwarders = 2; repeated common.Url forwarders = 2;
bool fallthrough = 3; repeated int32 fallthrough = 3;
} }
message GetExportConfigRequest {} message GetExportConfigRequest {}
+8 -12
View File
@@ -1,3 +1,4 @@
use crate::dns::config::zone::Fallthrough;
use crate::proto::common::Url; use crate::proto::common::Url;
use crate::proto::utils::TransientDigest; use crate::proto::utils::TransientDigest;
use hickory_proto::rr::LowerName; use hickory_proto::rr::LowerName;
@@ -13,19 +14,13 @@ impl HeartbeatRequest {
} }
impl ZoneData { impl ZoneData {
pub fn new<Records, R, Urls, U>( pub fn new<Record: AsRef<str>>(
origin: &LowerName, origin: &LowerName,
ttl: u32, ttl: u32,
records: Records, records: impl IntoIterator<Item = Record>,
forwarders: Urls, forwarders: impl IntoIterator<Item = Url>,
fallthrough: bool, fallthrough: impl IntoIterator<Item = Fallthrough>,
) -> Self ) -> Self {
where
Records: IntoIterator<Item = R>,
R: AsRef<str>,
Urls: IntoIterator<Item = U>,
U: Into<Url>,
{
let mut content = String::new(); let mut content = String::new();
content.push_str("; EasyTier Magic DNS zone data\n"); content.push_str("; EasyTier Magic DNS zone data\n");
@@ -44,7 +39,8 @@ impl ZoneData {
content.push('\n'); content.push('\n');
} }
let forwarders = forwarders.into_iter().map(Into::into).collect(); let forwarders = forwarders.into_iter().collect();
let fallthrough = fallthrough.into_iter().map(Into::into).collect();
Self { Self {
content, content,