From 87713e96b71fc7794402b2df80e7d69cefd0f3f7 Mon Sep 17 00:00:00 2001 From: Luna Yao <40349250+ZnqbuZ@users.noreply.github.com> Date: Sat, 21 Feb 2026 00:43:44 +0100 Subject: [PATCH] chore: move all config to a dedicated mod --- easytier/src/dns/client.rs | 2 +- easytier/src/dns/config.rs | 257 --------------------------- easytier/src/dns/config/constants.rs | 16 ++ easytier/src/dns/config/dns.rs | 112 ++++++++++++ easytier/src/dns/config/mod.rs | 6 + easytier/src/dns/config/policy.rs | 47 +++++ easytier/src/dns/config/zone.rs | 97 ++++++++++ easytier/src/dns/server.rs | 31 ++-- easytier/src/proto/dns.rs | 2 +- 9 files changed, 295 insertions(+), 275 deletions(-) delete mode 100644 easytier/src/dns/config.rs create mode 100644 easytier/src/dns/config/constants.rs create mode 100644 easytier/src/dns/config/dns.rs create mode 100644 easytier/src/dns/config/mod.rs create mode 100644 easytier/src/dns/config/policy.rs create mode 100644 easytier/src/dns/config/zone.rs diff --git a/easytier/src/dns/client.rs b/easytier/src/dns/client.rs index 448ed8c8..f57a6d07 100644 --- a/easytier/src/dns/client.rs +++ b/easytier/src/dns/client.rs @@ -1,4 +1,4 @@ -use super::config::DNS_SERVER_RPC_ADDR; +use crate::dns::config::DNS_SERVER_RPC_ADDR; use crate::dns::peer_mgr::DnsPeerMgr; use crate::peers::peer_manager::PeerManager; use crate::proto::dns::{DnsPeerManagerRpcServer, DnsServerRpcClientFactory, HeartbeatRequest}; diff --git a/easytier/src/dns/config.rs b/easytier/src/dns/config.rs deleted file mode 100644 index cad2af0f..00000000 --- a/easytier/src/dns/config.rs +++ /dev/null @@ -1,257 +0,0 @@ -use crate::common::config::ConfigLoader; -use crate::common::global_ctx::GlobalCtx; -use crate::dns::utils::{parse, NameServerAddr, NameServerAddrGroup}; -use crate::dns::zone::Zone; -use crate::proto::dns::{GetExportConfigResponse, ZoneData}; -use derivative::Derivative; -use derive_more::{Deref, DerefMut, Into}; -use gethostname::gethostname; -use hickory_proto::rr::{LowerName, Name}; -use hickory_proto::xfer::Protocol; -use serde::{Deserialize, Deserializer, Serialize}; -use std::collections::HashMap; -use std::iter; -use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4}; -use std::str::FromStr; -use std::sync::LazyLock; -use itertools::Itertools; -use url::Url; -use uuid::Uuid; - -pub const DNS_DEFAULT_ADDRESS: NameServerAddr = NameServerAddr { - protocol: Protocol::Udp, - addr: SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(100, 100, 100, 101), 53)), -}; -pub static DNS_DEFAULT_TLD: LazyLock = - LazyLock::new(|| LowerName::from_str("et.net.").unwrap()); -pub static DNS_SERVER_RPC_ADDR: LazyLock = - LazyLock::new(|| Url::parse("tcp://127.0.0.1:49813").unwrap()); - -#[derive(Derivative, Debug, Clone, Deserialize, Serialize, PartialEq)] -#[derivative(Default)] -#[serde(default)] -pub struct DnsConfig { - #[serde(rename = "zone")] - pub zones: Vec, - #[serde(flatten)] - pub policies: HashMap, - name: LowerName, - #[derivative(Default(value = "DNS_DEFAULT_TLD.clone()"))] - pub domain: LowerName, - #[derivative(Default(value = "vec![DNS_DEFAULT_ADDRESS].into()"))] - #[serde(deserialize_with = "DnsConfig::validate_addresses")] - pub addresses: NameServerAddrGroup, - pub listeners: NameServerAddrGroup, -} - -impl DnsConfig { - pub fn validate_addresses<'de, D>(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let addresses = NameServerAddrGroup::deserialize(deserializer)?; - for address in &addresses { - if address.protocol != Protocol::Udp { - return Err(serde::de::Error::custom(format!( - "unsupported address protocol: {}, only udp is supported", - address.protocol - ))); - } - } - Ok(addresses) - } -} - -impl DnsConfig { - pub fn get_name(&self) -> LowerName { - if self.name.is_empty() { - parse(gethostname().to_string_lossy().as_ref()) - } else { - self.name.clone() - } - } - - pub fn set_name(&mut self, name: &str) { - self.name = parse(name); - } - - pub fn get_fqdn(&self) -> LowerName { - Name::from(self.get_name()) - .append_domain(&self.domain) - .unwrap() - .into() - } - - pub fn set_fqdn(&mut self, fqdn: &str) { - let mut fqdn = Name::from(parse(fqdn)); - fqdn.set_fqdn(true); - self.name = Name::from_labels(iter::once(fqdn.iter().next().unwrap_or_default())) - .unwrap_or_default() - .into(); - self.domain = fqdn.base_name().into(); - } -} - -pub type DnsExportConfig = GetExportConfigResponse; - -pub trait DnsGlobalCtxExt { - fn dns_self_zone(&self) -> Option; - fn dns_export_config(&self) -> DnsExportConfig; -} - -impl DnsGlobalCtxExt for GlobalCtx { - fn dns_self_zone(&self) -> Option { - let fqdn = self.config.get_dns().get_fqdn(); - let ipv4 = self.get_ipv4().map(|ip| ip.address()); - let ipv6 = self.get_ipv6().map(|ip| ip.address()); - let ipv6 = ipv6.map(|a| vec![a]).unwrap_or_default(); - - ZoneConfig::dedicated(Some(self.get_id()), fqdn.clone(), ipv4, ipv6) - } - - fn dns_export_config(&self) -> DnsExportConfig { - let config = self.config.get_dns(); - let zone = self.dns_self_zone(); - let zones = config.zones.iter().chain(zone.iter()); - - DnsExportConfig { - zones: zones - .filter(|z| z.policy.export.is_some()) // TODO: check policies of parent zones - .cloned() - .map_into() - .collect(), - fqdn: config.get_fqdn().to_string(), - } - } -} - -#[derive(Derivative, Debug, Clone, Deserialize, Serialize, Default, Deref, DerefMut, Into)] -#[derivative(PartialEq)] -#[serde(try_from = "ZoneConfigInner", into = "ZoneConfigInner")] -pub struct ZoneConfig { - #[into] - #[derivative(PartialEq = "ignore")] - data: ZoneData, - #[into] - #[deref] - #[deref_mut] - inner: ZoneConfigInner, -} - -impl TryFrom for ZoneConfig { - type Error = anyhow::Error; - - fn try_from(value: ZoneConfigInner) -> Result { - let data = ZoneData::from(value.clone()); - let _ = Zone::try_from(&data)?; - Ok(Self { data, inner: value }) - } -} - -impl ZoneConfig { - fn dedicated( - id: Option, - origin: LowerName, - ipv4: Option, - ipv6: Vec, - ) -> Option { - let mut records = Vec::new(); - - if let Some(ipv4) = ipv4 { - records.push(format!("@ IN A {}", ipv4)); - } - for ipv6 in ipv6 { - records.push(format!("@ IN AAAA {}", ipv6)); - } - - let policy = ZonePolicyConfig { - export: Some(DnsExportPolicy::default()), - }; - - if records.is_empty() { - return None; - } - - let config = ZoneConfigInner { - id: id.unwrap_or_else(Uuid::new_v4), - origin, - records, - policy, - ..Default::default() - }; - - config.try_into().ok() - } -} - -#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Default)] -pub struct ZoneConfigInner { - #[serde(default = "Uuid::new_v4")] - #[serde(skip_serializing)] - id: Uuid, - pub origin: LowerName, - #[serde(default)] - pub ttl: u32, - #[serde(default)] - pub records: Vec, - #[serde(default)] - pub forwarders: NameServerAddrGroup, - #[serde(flatten)] - pub policy: ZonePolicyConfig, -} - -impl From for ZoneData { - fn from(value: ZoneConfigInner) -> Self { - Self { - id: Some(value.id.into()), - origin: value.origin.to_string(), - records: value.records, - forwarders: value.forwarders.into(), - } - } -} - -#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Default)] -#[serde(default)] -pub struct AclPolicy { - pub whitelist: Option>, - pub blacklist: Option>, -} - -#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Default, Deref, DerefMut)] -#[serde(default)] -pub struct FunctionalityPolicy { - #[serde(flatten)] - #[deref] - #[deref_mut] - acl: AclPolicy, // TODO - pub disabled: bool, -} - -#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Default, Deref, DerefMut)] -#[serde(default)] -pub struct DnsPolicy

{ - #[serde(flatten)] - #[deref] - #[deref_mut] - policy: P, - pub recursive: bool, // TODO -} - -pub type ZoneExportPolicy = FunctionalityPolicy; -pub type DnsExportPolicy = DnsPolicy; -pub type DnsImportPolicy = DnsPolicy; - -#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Default)] -#[serde(default)] -pub struct DnsPolicyConfig { - pub import: DnsImportPolicy, - pub export: Option, -} - -#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Default)] -#[serde(default)] -pub struct ZonePolicyConfig { - #[serde(default)] - pub export: Option, -} diff --git a/easytier/src/dns/config/constants.rs b/easytier/src/dns/config/constants.rs new file mode 100644 index 00000000..fc311a93 --- /dev/null +++ b/easytier/src/dns/config/constants.rs @@ -0,0 +1,16 @@ +use crate::dns::utils::NameServerAddr; +use hickory_proto::rr::LowerName; +use hickory_proto::xfer::Protocol; +use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4}; +use std::str::FromStr; +use std::sync::LazyLock; +use url::Url; + +pub const DNS_DEFAULT_ADDRESS: NameServerAddr = NameServerAddr { + protocol: Protocol::Udp, + addr: SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(100, 100, 100, 101), 53)), +}; +pub static DNS_DEFAULT_TLD: LazyLock = + LazyLock::new(|| LowerName::from_str("et.net.").unwrap()); +pub static DNS_SERVER_RPC_ADDR: LazyLock = + LazyLock::new(|| Url::parse("tcp://127.0.0.1:49813").unwrap()); diff --git a/easytier/src/dns/config/dns.rs b/easytier/src/dns/config/dns.rs new file mode 100644 index 00000000..2af4c438 --- /dev/null +++ b/easytier/src/dns/config/dns.rs @@ -0,0 +1,112 @@ +use crate::common::global_ctx::GlobalCtx; +use crate::dns::config::policy::DnsPolicyConfig; +use crate::dns::config::zone::ZoneConfig; +use crate::dns::config::{DNS_DEFAULT_ADDRESS, DNS_DEFAULT_TLD}; +use crate::dns::utils::{parse, NameServerAddrGroup}; +use crate::proto::dns::GetExportConfigResponse; +use derivative::Derivative; +use gethostname::gethostname; +use hickory_proto::rr::{LowerName, Name}; +use hickory_proto::xfer::Protocol; +use itertools::Itertools; +use serde::{Deserialize, Deserializer, Serialize}; +use std::collections::HashMap; +use std::iter; + +#[derive(Derivative, Debug, Clone, Deserialize, Serialize, PartialEq)] +#[derivative(Default)] +#[serde(default)] +pub struct DnsConfig { + #[serde(rename = "zone")] + pub zones: Vec, + #[serde(flatten)] + pub policies: HashMap, + name: LowerName, + #[derivative(Default(value = "DNS_DEFAULT_TLD.clone()"))] + pub domain: LowerName, + #[derivative(Default(value = "vec![DNS_DEFAULT_ADDRESS].into()"))] + #[serde(deserialize_with = "DnsConfig::validate_addresses")] + pub addresses: NameServerAddrGroup, + pub listeners: NameServerAddrGroup, +} + +impl DnsConfig { + pub fn validate_addresses<'de, D>(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let addresses = NameServerAddrGroup::deserialize(deserializer)?; + for address in &addresses { + if address.protocol != Protocol::Udp { + return Err(serde::de::Error::custom(format!( + "unsupported address protocol: {}, only udp is supported", + address.protocol + ))); + } + } + Ok(addresses) + } +} + +impl DnsConfig { + pub fn get_name(&self) -> LowerName { + if self.name.is_empty() { + parse(gethostname().to_string_lossy().as_ref()) + } else { + self.name.clone() + } + } + + pub fn set_name(&mut self, name: &str) { + self.name = parse(name); + } + + pub fn get_fqdn(&self) -> LowerName { + Name::from(self.get_name()) + .append_domain(&self.domain) + .unwrap() + .into() + } + + pub fn set_fqdn(&mut self, fqdn: &str) { + let mut fqdn = Name::from(parse(fqdn)); + fqdn.set_fqdn(true); + self.name = Name::from_labels(iter::once(fqdn.iter().next().unwrap_or_default())) + .unwrap_or_default() + .into(); + self.domain = fqdn.base_name().into(); + } +} + +pub type DnsExportConfig = GetExportConfigResponse; + +pub trait DnsGlobalCtxExt { + fn dns_self_zone(&self) -> Option; + fn dns_export_config(&self) -> DnsExportConfig; +} + +impl DnsGlobalCtxExt for GlobalCtx { + fn dns_self_zone(&self) -> Option { + let fqdn = self.config.get_dns().get_fqdn(); + let ipv4 = self.get_ipv4().map(|ip| ip.address()); + let ipv6 = self.get_ipv6().map(|ip| ip.address()); + let ipv6 = ipv6.map(|a| vec![a]).unwrap_or_default(); + + ZoneConfig::dedicated(Some(self.get_id()), fqdn.clone(), ipv4, ipv6) + } + + fn dns_export_config(&self) -> DnsExportConfig { + let config = self.config.get_dns(); + let zone = self.dns_self_zone(); + let zones = config.zones.iter().chain(zone.iter()); + + DnsExportConfig { + zones: zones + .filter(|z| z.policy.export.is_some()) // TODO: check policies of parent zones + .cloned() + .map_into() + .collect(), + fqdn: config.get_fqdn().to_string(), + } + } +} diff --git a/easytier/src/dns/config/mod.rs b/easytier/src/dns/config/mod.rs new file mode 100644 index 00000000..9e480b77 --- /dev/null +++ b/easytier/src/dns/config/mod.rs @@ -0,0 +1,6 @@ +mod constants; +pub use constants::*; +mod dns; +pub use dns::*; +mod policy; +mod zone; diff --git a/easytier/src/dns/config/policy.rs b/easytier/src/dns/config/policy.rs new file mode 100644 index 00000000..5ffba3eb --- /dev/null +++ b/easytier/src/dns/config/policy.rs @@ -0,0 +1,47 @@ +use derive_more::{Deref, DerefMut}; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Default)] +#[serde(default)] +pub struct AclPolicy { + pub whitelist: Option>, + pub blacklist: Option>, +} + +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Default, Deref, DerefMut)] +#[serde(default)] +pub struct FunctionalityPolicy { + #[serde(flatten)] + #[deref] + #[deref_mut] + acl: AclPolicy, // TODO + pub disabled: bool, +} + +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Default, Deref, DerefMut)] +#[serde(default)] +pub struct DnsPolicy

{ + #[serde(flatten)] + #[deref] + #[deref_mut] + policy: P, + pub recursive: bool, // TODO +} + +pub type ZoneExportPolicy = FunctionalityPolicy; +pub type DnsExportPolicy = DnsPolicy; +pub type DnsImportPolicy = DnsPolicy; + +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Default)] +#[serde(default)] +pub struct DnsPolicyConfig { + pub import: DnsImportPolicy, + pub export: Option, +} + +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Default)] +#[serde(default)] +pub struct ZonePolicyConfig { + #[serde(default)] + pub export: Option, +} diff --git a/easytier/src/dns/config/zone.rs b/easytier/src/dns/config/zone.rs new file mode 100644 index 00000000..1ea3e013 --- /dev/null +++ b/easytier/src/dns/config/zone.rs @@ -0,0 +1,97 @@ +use crate::dns::config::policy::{DnsExportPolicy, ZonePolicyConfig}; +use crate::dns::utils::NameServerAddrGroup; +use crate::dns::zone::Zone; +use crate::proto::dns::ZoneData; +use derivative::Derivative; +use derive_more::{Deref, DerefMut, Into}; +use hickory_proto::rr::LowerName; +use serde::{Deserialize, Serialize}; +use std::convert::{TryFrom, TryInto}; +use std::net::{Ipv4Addr, Ipv6Addr}; +use uuid::Uuid; + +#[derive(Derivative, Debug, Clone, Deserialize, Serialize, Default, Deref, DerefMut, Into)] +#[derivative(PartialEq)] +#[serde(try_from = "ZoneConfigInner", into = "ZoneConfigInner")] +pub struct ZoneConfig { + #[into] + #[derivative(PartialEq = "ignore")] + data: ZoneData, + #[into] + #[deref] + #[deref_mut] + inner: ZoneConfigInner, +} + +impl TryFrom for ZoneConfig { + type Error = anyhow::Error; + + fn try_from(value: ZoneConfigInner) -> Result { + let data = ZoneData::from(value.clone()); + let _ = Zone::try_from(&data)?; + Ok(Self { data, inner: value }) + } +} + +impl ZoneConfig { + pub fn dedicated( + id: Option, + origin: LowerName, + ipv4: Option, + ipv6: Vec, + ) -> Option { + let mut records = Vec::new(); + + if let Some(ipv4) = ipv4 { + records.push(format!("@ IN A {}", ipv4)); + } + for ipv6 in ipv6 { + records.push(format!("@ IN AAAA {}", ipv6)); + } + + let policy = ZonePolicyConfig { + export: Some(DnsExportPolicy::default()), + }; + + if records.is_empty() { + return None; + } + + let config = ZoneConfigInner { + id: id.unwrap_or_else(Uuid::new_v4), + origin, + records, + policy, + ..Default::default() + }; + + config.try_into().ok() + } +} + +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Default)] +pub struct ZoneConfigInner { + #[serde(default = "Uuid::new_v4")] + #[serde(skip_serializing)] + id: Uuid, + pub origin: LowerName, + #[serde(default)] + pub ttl: u32, + #[serde(default)] + pub records: Vec, + #[serde(default)] + pub forwarders: NameServerAddrGroup, + #[serde(flatten)] + pub policy: ZonePolicyConfig, +} + +impl From for ZoneData { + fn from(value: ZoneConfigInner) -> Self { + Self { + id: Some(value.id.into()), + origin: value.origin.to_string(), + records: value.records, + forwarders: value.forwarders.into(), + } + } +} diff --git a/easytier/src/dns/server.rs b/easytier/src/dns/server.rs index 08ff9437..7b61167e 100644 --- a/easytier/src/dns/server.rs +++ b/easytier/src/dns/server.rs @@ -5,11 +5,11 @@ use hickory_server::{ server::{Request, RequestHandler, ResponseHandler, ResponseInfo}, ServerFuture, }; +use itertools::Itertools; use moka::future::Cache; use std::collections::HashSet; use std::sync::atomic::{AtomicBool, Ordering}; use std::{sync::Arc, time::Duration}; -use itertools::Itertools; use tokio::net::{TcpListener, UdpSocket}; use tokio::sync::Notify; use tokio::{ @@ -19,8 +19,11 @@ use tokio::{ use uuid::Uuid; use super::{utils::NameServerAddr, zone::Zone}; +use crate::dns::utils::NameServerAddrGroup; +use crate::dns::zone::ZoneGroup; +use crate::proto::dns::DnsSnapshot; use crate::proto::rpc_types; -use crate::utils::{DeterministicDigest}; +use crate::utils::DeterministicDigest; use crate::{ common::global_ctx::GlobalCtx, proto::{ @@ -28,9 +31,6 @@ use crate::{ rpc_types::controller::BaseController, }, }; -use crate::dns::utils::NameServerAddrGroup; -use crate::dns::zone::ZoneGroup; -use crate::proto::dns::DnsSnapshot; #[derive(Debug, Clone, Default)] pub struct DnsClientInfo { @@ -53,7 +53,6 @@ impl TryFrom<&DnsSnapshot> for DnsClientInfo { } } -// A wrapper around Catalog to allow hot-swapping the inner catalog #[derive(Clone)] pub struct DynamicCatalog { inner: Arc>, @@ -66,8 +65,8 @@ impl DynamicCatalog { } } - pub async fn replace(&self, new_catalog: Catalog) { - *self.inner.write().await = new_catalog; + pub async fn replace(&self, new: Catalog) { + *self.inner.write().await = new; } } @@ -260,17 +259,17 @@ impl DnsServerRpc for DnsServer { _: BaseController, input: HeartbeatRequest, ) -> rpc_types::error::Result { - let id = input.id.ok_or( - anyhow::anyhow!("missing id in heartbeat request: {:?}", input) - )?.into(); + let id = input + .id + .ok_or(anyhow::anyhow!( + "missing id in heartbeat request: {:?}", + input + ))? + .into(); let resync = if let Some(snapshot) = input.snapshot.as_ref() { let new = DnsClientInfo::try_from(snapshot)?; - let old = self - .clients - .get(&id) - .await - .unwrap_or_default(); + let old = self.clients.get(&id).await.unwrap_or_default(); if new.digest != old.digest { if new.zones != old.zones { self.dirty.zones.store(true, Ordering::Release); diff --git a/easytier/src/proto/dns.rs b/easytier/src/proto/dns.rs index 1dee2b8d..378d7483 100644 --- a/easytier/src/proto/dns.rs +++ b/easytier/src/proto/dns.rs @@ -1,5 +1,5 @@ -use std::fmt::Display; use crate::utils::DeterministicDigest; +use std::fmt::Display; include!(concat!(env!("OUT_DIR"), "/dns.rs"));