add fallthrough flag

zone

test log
This commit is contained in:
Luna Yao
2026-04-18 02:45:45 +02:00
parent 9332baf6f9
commit bc86917dea
6 changed files with 119 additions and 19 deletions
+5 -6
View File
@@ -1,14 +1,14 @@
use derive_more::{Deref, DerefMut}; use derive_more::{Deref, DerefMut};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Default)] #[derive(Debug, Clone, PartialEq, Default, Deserialize, Serialize)]
#[serde(default)] #[serde(default)]
pub struct AclPolicy { pub struct AclPolicy {
pub whitelist: Option<Vec<String>>, pub whitelist: Option<Vec<String>>,
pub blacklist: Option<Vec<String>>, pub blacklist: Option<Vec<String>>,
} }
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Default, Deref, DerefMut)] #[derive(Debug, Clone, PartialEq, Default, Deserialize, Serialize, Deref, DerefMut)]
#[serde(default)] #[serde(default)]
pub struct FunctionalityPolicy { pub struct FunctionalityPolicy {
#[serde(flatten)] #[serde(flatten)]
@@ -18,7 +18,7 @@ pub struct FunctionalityPolicy {
pub disabled: bool, pub disabled: bool,
} }
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Default, Deref, DerefMut)] #[derive(Debug, Clone, PartialEq, Default, Deserialize, Serialize, Deref, DerefMut)]
#[serde(default)] #[serde(default)]
pub struct DnsPolicy<P = FunctionalityPolicy> { pub struct DnsPolicy<P = FunctionalityPolicy> {
#[serde(flatten)] #[serde(flatten)]
@@ -32,16 +32,15 @@ pub type ZoneExportPolicy = FunctionalityPolicy;
pub type DnsExportPolicy = DnsPolicy<ZoneExportPolicy>; pub type DnsExportPolicy = DnsPolicy<ZoneExportPolicy>;
pub type DnsImportPolicy = DnsPolicy<FunctionalityPolicy>; pub type DnsImportPolicy = DnsPolicy<FunctionalityPolicy>;
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Default)] #[derive(Debug, Clone, PartialEq, Default, Deserialize, Serialize)]
#[serde(default)] #[serde(default)]
pub struct DnsPolicyConfig { pub struct DnsPolicyConfig {
pub import: DnsImportPolicy, pub import: DnsImportPolicy,
pub export: Option<DnsExportPolicy>, pub export: Option<DnsExportPolicy>,
} }
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Default)] #[derive(Debug, Clone, PartialEq, Default, Deserialize, Serialize)]
#[serde(default)] #[serde(default)]
pub struct ZonePolicyConfig { pub struct ZonePolicyConfig {
#[serde(default)]
pub export: Option<DnsExportPolicy>, pub export: Option<DnsExportPolicy>,
} }
+7 -5
View File
@@ -69,20 +69,21 @@ impl ZoneConfig {
} }
} }
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Default)] #[derive(Derivative, Debug, Clone, PartialEq, Deserialize, Serialize)]
#[derivative(Default)]
#[serde(default)]
pub struct ZoneConfigInner { pub struct ZoneConfigInner {
#[serde(default = "Uuid::new_v4")] #[derivative(Default(value = "Uuid::new_v4()"))]
#[serde(skip_serializing)] #[serde(skip_serializing)]
id: Uuid, id: Uuid,
pub origin: LowerName, pub origin: LowerName,
#[serde(default)]
pub ttl: u32, pub ttl: u32,
#[serde(default)]
pub records: Vec<String>, pub records: Vec<String>,
#[serde(default)]
pub forwarders: NameServerAddrGroup, pub forwarders: NameServerAddrGroup,
#[serde(flatten)] #[serde(flatten)]
pub policy: ZonePolicyConfig, pub policy: ZonePolicyConfig,
#[derivative(Default(value = "true"))]
pub fallthrough: bool,
} }
impl From<ZoneConfigInner> for ZoneData { impl From<ZoneConfigInner> for ZoneData {
@@ -93,6 +94,7 @@ impl From<ZoneConfigInner> for ZoneData {
ttl: value.ttl, ttl: value.ttl,
records: value.records, records: value.records,
forwarders: value.forwarders.into(), forwarders: value.forwarders.into(),
fallthrough: value.fallthrough,
} }
} }
} }
+1
View File
@@ -250,6 +250,7 @@ mod tests {
ttl: 60, ttl: 60,
records: vec!["@ IN A 10.0.0.11".to_string()], records: vec!["@ IN A 10.0.0.11".to_string()],
forwarders: vec![], forwarders: vec![],
fallthrough: false,
}], }],
fqdn: "invalid.peer.test".to_string(), fqdn: "invalid.peer.test".to_string(),
}; };
+14 -4
View File
@@ -114,6 +114,7 @@ pub fn zone_data_a_with_forwarders(origin: &str, record: &str, forwarders: Vec<&
.into_iter() .into_iter()
.map(|f| Url::from_str(f).expect("invalid forwarder")) .map(|f| Url::from_str(f).expect("invalid forwarder"))
.collect(), .collect(),
fallthrough: false,
} }
} }
@@ -344,8 +345,10 @@ async fn wait_peer_zone_visibility(
loop { loop {
dns.refresh(target_peer_id).await; dns.refresh(target_peer_id).await;
let visible = dns
.snapshot() let snapshot = dns.snapshot();
let visible = snapshot
.zones .zones
.iter() .iter()
.any(|z| z.origin.contains(zone_origin_substr)); .any(|z| z.origin.contains(zone_origin_substr));
@@ -354,12 +357,19 @@ async fn wait_peer_zone_visibility(
return; return;
} }
let origins = snapshot
.zones
.iter()
.map(|z| z.origin.clone())
.collect::<Vec<_>>();
assert!( assert!(
Instant::now() < deadline, Instant::now() < deadline,
"zone visibility mismatch for '{}': expected {}, got {}", "zone visibility mismatch for '{}': expected {}, got {}, current origins: {:?}",
zone_origin_substr, zone_origin_substr,
expected_visible, expected_visible,
visible visible,
origins
); );
tokio::time::sleep(Duration::from_millis(200)).await; tokio::time::sleep(Duration::from_millis(200)).await;
} }
+91 -4
View File
@@ -1,6 +1,6 @@
use crate::common::dns::get_default_resolver_config; use crate::common::dns::get_default_resolver_config;
use crate::dns::utils::addr::{NameServerAddr, NameServerAddrGroup}; use crate::dns::utils::addr::{NameServerAddr, NameServerAddrGroup};
use crate::dns::utils::zone_handler::ArcZoneHandler; use crate::dns::utils::zone_handler::{ArcZoneHandler, ChainedZoneHandler};
use crate::proto; use crate::proto;
use crate::proto::utils::RepeatedMessageModel; use crate::proto::utils::RepeatedMessageModel;
use hickory_net::runtime::TokioRuntimeProvider; use hickory_net::runtime::TokioRuntimeProvider;
@@ -23,6 +23,7 @@ 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,
} }
impl Zone { impl Zone {
@@ -35,6 +36,7 @@ impl Zone {
}; };
let mut zone = Self::new(".".parse().unwrap()); let mut zone = Self::new(".".parse().unwrap());
zone.forward = Some(forward); zone.forward = Some(forward);
zone.fallthrough = false;
zone zone
} }
} }
@@ -46,6 +48,7 @@ impl Zone {
origin: name, origin: name,
records: BTreeMap::new(), records: BTreeMap::new(),
forward: None, forward: None,
fallthrough: true,
} }
} }
@@ -64,7 +67,11 @@ impl Zone {
.map(|(k, v)| (k, Arc::new(v))), .map(|(k, v)| (k, Arc::new(v))),
); );
Arc::new(memory) as ArcZoneHandler if self.fallthrough {
Arc::new(ChainedZoneHandler::from(memory)) as _
} else {
Arc::new(memory) as _
}
}) })
} }
@@ -77,7 +84,13 @@ impl Zone {
.build() .build()
.inspect_err(|e| tracing::error!("failed to create forward zone_handler: {:?}", e)) .inspect_err(|e| tracing::error!("failed to create forward zone_handler: {:?}", e))
.ok() .ok()
.map(|f| Arc::new(f) as ArcZoneHandler) .map(|f| {
if self.fallthrough {
Arc::new(ChainedZoneHandler::from(f)) as _
} else {
Arc::new(f) as _
}
})
}) })
} }
} }
@@ -111,6 +124,7 @@ impl TryFrom<&proto::dns::ZoneData> for Zone {
origin: origin.into(), origin: origin.into(),
records, records,
forward, forward,
fallthrough: value.fallthrough,
}) })
} }
} }
@@ -139,6 +153,7 @@ impl From<Zone> for proto::dns::ZoneData {
ttl: 0, ttl: 0,
records, records,
forwarders, forwarders,
fallthrough: value.fallthrough,
} }
} }
} }
@@ -195,7 +210,12 @@ mod tests {
} }
} }
fn zone_data(origin: &str, records: Vec<&str>, forwarders: Vec<&str>) -> ZoneData { fn zone_data_with_fallthrough(
origin: &str,
records: Vec<&str>,
forwarders: Vec<&str>,
fallthrough: bool,
) -> ZoneData {
ZoneData { ZoneData {
id: Some(Uuid::new_v4().into()), id: Some(Uuid::new_v4().into()),
origin: origin.to_string(), origin: origin.to_string(),
@@ -205,9 +225,14 @@ mod tests {
.into_iter() .into_iter()
.map(|f| Url::from_str(f).expect("invalid forwarder")) .map(|f| Url::from_str(f).expect("invalid forwarder"))
.collect(), .collect(),
fallthrough,
} }
} }
fn zone_data(origin: &str, records: Vec<&str>, forwarders: Vec<&str>) -> ZoneData {
zone_data_with_fallthrough(origin, records, forwarders, true)
}
fn build_catalog(zones: ZoneGroup) -> Catalog { fn build_catalog(zones: ZoneGroup) -> Catalog {
zones zones
.into_groups() .into_groups()
@@ -273,6 +298,7 @@ mod tests {
ttl: 60, ttl: 60,
records: vec!["@ IN A 10.0.0.1".to_string()], records: vec!["@ IN A 10.0.0.1".to_string()],
forwarders: vec![], forwarders: vec![],
fallthrough: false,
}; };
let err = Zone::try_from(&data).expect_err("missing id should fail"); let err = Zone::try_from(&data).expect_err("missing id should fail");
@@ -416,6 +442,67 @@ mod tests {
Ok(()) Ok(())
} }
#[tokio::test]
async fn catalog_lookup_falls_back_to_later_zone_handler_with_same_origin() -> anyhow::Result<()>
{
let zones: ZoneGroup = vec![
// First matching zone exists but does not contain the queried name.
Zone::try_from(&zone_data(
"fallback.test",
vec!["first IN A 10.20.30.1"],
vec![],
))?,
// Second matching zone should be queried as fallback and answer.
Zone::try_from(&zone_data(
"fallback.test",
vec!["target IN A 10.20.30.2"],
vec![],
))?,
]
.into();
let catalog = build_catalog(zones);
let (rcode, message) =
lookup_message(&catalog, "target.fallback.test.", RecordType::A).await?;
assert_eq!(rcode, ResponseCode::NoError);
assert!(has_a_answer(
&message.expect("response should exist"),
Ipv4Addr::new(10, 20, 30, 2)
));
Ok(())
}
#[tokio::test]
async fn catalog_lookup_does_not_fall_back_when_fallthrough_disabled() -> anyhow::Result<()> {
let zones: ZoneGroup = vec![
Zone::try_from(&zone_data_with_fallthrough(
"fallback-disabled.test",
vec!["first IN A 10.20.31.1"],
vec![],
false,
))?,
Zone::try_from(&zone_data_with_fallthrough(
"fallback-disabled.test",
vec!["target IN A 10.20.31.2"],
vec![],
false,
))?,
]
.into();
let catalog = build_catalog(zones);
let (rcode, message) =
lookup_message(&catalog, "target.fallback-disabled.test.", RecordType::A).await?;
assert_ne!(rcode, ResponseCode::NoError);
if let Some(message) = message.as_ref() {
assert!(!has_a_answer(message, Ipv4Addr::new(10, 20, 31, 2)));
}
Ok(())
}
#[tokio::test] #[tokio::test]
async fn catalog_forward_only_zone_queries_upstream() -> anyhow::Result<()> { async fn catalog_forward_only_zone_queries_upstream() -> anyhow::Result<()> {
let (upstream_addr, upstream_handle) = start_upstream_server().await?; let (upstream_addr, upstream_handle) = start_upstream_server().await?;
+1
View File
@@ -10,6 +10,7 @@ message ZoneData {
uint32 ttl = 3; uint32 ttl = 3;
repeated string records = 4; repeated string records = 4;
repeated common.Url forwarders = 5; repeated common.Url forwarders = 5;
bool fallthrough = 6;
} }
message GetExportConfigRequest {} message GetExportConfigRequest {}