chore: bump dependencies, add metrics support, remove DNS resolver (#699)

* upgrade version

* add metrics

* remove dns resolver

* add metrics counter for create bucket

* fix

* fix

* fix
This commit is contained in:
houseme
2025-10-24 00:16:17 +08:00
committed by GitHub
parent 1d069fd351
commit e22b24684f
15 changed files with 726 additions and 850 deletions

View File

@@ -35,7 +35,6 @@ futures = { workspace = true, optional = true }
hashbrown = { workspace = true, optional = true }
hex-simd = { workspace = true, optional = true }
highway = { workspace = true, optional = true }
hickory-resolver = { workspace = true, optional = true }
hmac = { workspace = true, optional = true }
http = { workspace = true, optional = true }
hyper = { workspace = true, optional = true }
@@ -43,7 +42,6 @@ libc = { workspace = true, optional = true }
local-ip-address = { workspace = true, optional = true }
lz4 = { workspace = true, optional = true }
md-5 = { workspace = true, optional = true }
moka = { workspace = true, optional = true, features = ["future"] }
netif = { workspace = true, optional = true }
nix = { workspace = true, optional = true }
rand = { workspace = true, optional = true }
@@ -83,7 +81,7 @@ workspace = true
default = ["ip"] # features that are enabled by default
ip = ["dep:local-ip-address"] # ip characteristics and their dependencies
tls = ["dep:rustls", "dep:rustls-pemfile", "dep:rustls-pki-types"] # tls characteristics and their dependencies
net = ["ip", "dep:url", "dep:netif", "dep:futures", "dep:transform-stream", "dep:bytes", "dep:s3s", "dep:hyper", "dep:hickory-resolver", "dep:moka", "dep:thiserror", "dep:tokio"] # network features with DNS resolver
net = ["ip", "dep:url", "dep:netif", "dep:futures", "dep:transform-stream", "dep:bytes", "dep:s3s", "dep:hyper", "dep:thiserror", "dep:tokio"] # network features with DNS resolver
io = ["dep:tokio"]
path = []
notify = ["dep:hyper", "dep:s3s", "dep:hashbrown", "dep:thiserror", "dep:serde", "dep:libc"] # file system notification features

View File

@@ -12,465 +12,465 @@
// See the License for the specific language governing permissions and
// limitations under the License.
#![allow(dead_code)]
//! Layered DNS resolution utility for Kubernetes environments
//!
//! This module provides robust DNS resolution with multiple fallback layers:
//! 1. Local cache (Moka) for previously resolved results
//! 2. System DNS resolver (container/host adaptive) using hickory-resolver
//! 3. Public DNS servers as final fallback (8.8.8.8, 1.1.1.1) using hickory-resolver with TLS
//!
//! The resolver is designed to handle 5-level or deeper domain names that may fail
//! in Kubernetes environments due to CoreDNS configuration, DNS recursion limits,
//! or network-related issues. Uses hickory-resolver for actual DNS queries with TLS support.
use hickory_resolver::Resolver;
use hickory_resolver::config::ResolverConfig;
use hickory_resolver::name_server::TokioConnectionProvider;
use moka::future::Cache;
use std::net::IpAddr;
use std::sync::OnceLock;
use std::time::Duration;
use tracing::{debug, error, info, instrument, warn};
/// Maximum FQDN length according to RFC standards
const MAX_FQDN_LENGTH: usize = 253;
/// Maximum DNS label length according to RFC standards
const MAX_LABEL_LENGTH: usize = 63;
/// Cache entry TTL in seconds
const CACHE_TTL_SECONDS: u64 = 300; // 5 minutes
/// Maximum cache size (number of entries)
const MAX_CACHE_SIZE: u64 = 10000;
/// DNS resolution error types with detailed context and tracing information
#[derive(Debug, thiserror::Error)]
pub enum DnsError {
#[error("Invalid domain format: {reason}")]
InvalidFormat { reason: String },
#[error("Local cache miss for domain: {domain}")]
CacheMiss { domain: String },
#[error("System DNS resolution failed for domain: {domain} - {source}")]
SystemDnsFailed {
domain: String,
#[source]
source: Box<dyn std::error::Error + Send + Sync>,
},
#[error("Public DNS resolution failed for domain: {domain} - {source}")]
PublicDnsFailed {
domain: String,
#[source]
source: Box<dyn std::error::Error + Send + Sync>,
},
#[error(
"All DNS resolution attempts failed for domain: {domain}. Please check your domain spelling, network connectivity, or DNS configuration"
)]
AllAttemptsFailed { domain: String },
#[error("DNS resolver initialization failed: {source}")]
InitializationFailed {
#[source]
source: Box<dyn std::error::Error + Send + Sync>,
},
#[error("DNS configuration error: {source}")]
ConfigurationError {
#[source]
source: Box<dyn std::error::Error + Send + Sync>,
},
}
/// Layered DNS resolver with caching and multiple fallback strategies
pub struct LayeredDnsResolver {
/// Local cache for resolved domains using Moka for high performance
cache: Cache<String, Vec<IpAddr>>,
/// System DNS resolver using hickory-resolver with default configuration
system_resolver: Resolver<TokioConnectionProvider>,
/// Public DNS resolver using hickory-resolver with Cloudflare DNS servers
public_resolver: Resolver<TokioConnectionProvider>,
}
impl LayeredDnsResolver {
/// Create a new layered DNS resolver with automatic DNS configuration detection
#[instrument(skip_all)]
pub async fn new() -> Result<Self, DnsError> {
info!("Initializing layered DNS resolver with hickory-resolver, Moka cache and public DNS fallback");
// Create Moka cache with TTL and size limits
let cache = Cache::builder()
.time_to_live(Duration::from_secs(CACHE_TTL_SECONDS))
.max_capacity(MAX_CACHE_SIZE)
.build();
// Create system DNS resolver with default configuration (auto-detects container/host DNS)
let system_resolver =
Resolver::builder_with_config(ResolverConfig::default(), TokioConnectionProvider::default()).build();
let mut config = ResolverConfig::cloudflare_tls();
for ns in ResolverConfig::google_tls().name_servers() {
config.add_name_server(ns.clone())
}
// Create public DNS resolver using Cloudflare DNS with TLS support
let public_resolver = Resolver::builder_with_config(config, TokioConnectionProvider::default()).build();
info!("DNS resolver initialized successfully with hickory-resolver system and Cloudflare TLS public fallback");
Ok(Self {
cache,
system_resolver,
public_resolver,
})
}
/// Validate domain format according to RFC standards
#[instrument(skip_all, fields(domain = %domain))]
fn validate_domain_format(domain: &str) -> Result<(), DnsError> {
info!("Validating domain format start");
// Check FQDN length
if domain.len() > MAX_FQDN_LENGTH {
return Err(DnsError::InvalidFormat {
reason: format!("FQDN must not exceed {} bytes, got {} bytes", MAX_FQDN_LENGTH, domain.len()),
});
}
// Check each label length
for label in domain.split('.') {
if label.len() > MAX_LABEL_LENGTH {
return Err(DnsError::InvalidFormat {
reason: format!(
"Each label must not exceed {} bytes, label '{}' has {} bytes",
MAX_LABEL_LENGTH,
label,
label.len()
),
});
}
}
// Check for empty labels (except trailing dot)
let labels: Vec<&str> = domain.trim_end_matches('.').split('.').collect();
for label in &labels {
if label.is_empty() {
return Err(DnsError::InvalidFormat {
reason: "Domain contains empty labels".to_string(),
});
}
}
info!("DNS resolver validated successfully");
Ok(())
}
/// Check local cache for resolved domain
#[instrument(skip_all, fields(domain = %domain))]
async fn check_cache(&self, domain: &str) -> Option<Vec<IpAddr>> {
match self.cache.get(domain).await {
Some(ips) => {
debug!("DNS cache hit for domain: {}, found {} IPs", domain, ips.len());
Some(ips)
}
None => {
debug!("DNS cache miss for domain: {}", domain);
None
}
}
}
/// Update local cache with resolved IPs
#[instrument(skip_all, fields(domain = %domain, ip_count = ips.len()))]
async fn update_cache(&self, domain: &str, ips: Vec<IpAddr>) {
self.cache.insert(domain.to_string(), ips.clone()).await;
debug!("DNS cache updated for domain: {} with {} IPs", domain, ips.len());
}
/// Get cache statistics for monitoring
#[instrument(skip_all)]
pub async fn cache_stats(&self) -> (u64, u64) {
let entry_count = self.cache.entry_count();
let weighted_size = self.cache.weighted_size();
debug!("DNS cache stats - entries: {}, weighted_size: {}", entry_count, weighted_size);
(entry_count, weighted_size)
}
/// Manually invalidate cache entries (useful for testing or forced refresh)
#[instrument(skip_all)]
pub async fn invalidate_cache(&self) {
self.cache.invalidate_all();
info!("DNS cache invalidated");
}
/// Resolve domain using system DNS (cluster/host DNS configuration) with hickory-resolver
#[instrument(skip_all, fields(domain = %domain))]
async fn resolve_with_system_dns(&self, domain: &str) -> Result<Vec<IpAddr>, DnsError> {
debug!("Attempting system DNS resolution for domain: {} using hickory-resolver", domain);
match self.system_resolver.lookup_ip(domain).await {
Ok(lookup) => {
let ips: Vec<IpAddr> = lookup.iter().collect();
if !ips.is_empty() {
info!("System DNS resolution successful for domain: {} -> {} IPs", domain, ips.len());
Ok(ips)
} else {
warn!("System DNS returned empty result for domain: {}", domain);
Err(DnsError::SystemDnsFailed {
domain: domain.to_string(),
source: "No IP addresses found".to_string().into(),
})
}
}
Err(e) => {
warn!("System DNS resolution failed for domain: {} - {}", domain, e);
Err(DnsError::SystemDnsFailed {
domain: domain.to_string(),
source: Box::new(e),
})
}
}
}
/// Resolve domain using public DNS servers (Cloudflare TLS DNS) with hickory-resolver
#[instrument(skip_all, fields(domain = %domain))]
async fn resolve_with_public_dns(&self, domain: &str) -> Result<Vec<IpAddr>, DnsError> {
debug!(
"Attempting public DNS resolution for domain: {} using hickory-resolver with TLS-enabled Cloudflare DNS",
domain
);
match self.public_resolver.lookup_ip(domain).await {
Ok(lookup) => {
let ips: Vec<IpAddr> = lookup.iter().collect();
if !ips.is_empty() {
info!("Public DNS resolution successful for domain: {} -> {} IPs", domain, ips.len());
Ok(ips)
} else {
warn!("Public DNS returned empty result for domain: {}", domain);
Err(DnsError::PublicDnsFailed {
domain: domain.to_string(),
source: "No IP addresses found".to_string().into(),
})
}
}
Err(e) => {
error!("Public DNS resolution failed for domain: {} - {}", domain, e);
Err(DnsError::PublicDnsFailed {
domain: domain.to_string(),
source: Box::new(e),
})
}
}
}
/// Resolve domain with layered fallback strategy using hickory-resolver
///
/// Resolution order with detailed tracing:
/// 1. Local cache (Moka with TTL)
/// 2. System DNS (hickory-resolver with host/container adaptive configuration)
/// 3. Public DNS (hickory-resolver with TLS-enabled Cloudflare DNS fallback)
#[instrument(skip_all, fields(domain = %domain))]
pub async fn resolve(&self, domain: &str) -> Result<Vec<IpAddr>, DnsError> {
info!("Starting DNS resolution process for domain: {} start", domain);
// Validate domain format first
Self::validate_domain_format(domain)?;
info!("Starting DNS resolution for domain: {}", domain);
// Step 1: Check local cache
if let Some(ips) = self.check_cache(domain).await {
info!("DNS resolution completed from cache for domain: {} -> {} IPs", domain, ips.len());
return Ok(ips);
}
debug!("Local cache miss for domain: {}, attempting system DNS", domain);
// Step 2: Try system DNS (cluster/host adaptive)
match self.resolve_with_system_dns(domain).await {
Ok(ips) => {
self.update_cache(domain, ips.clone()).await;
info!("DNS resolution completed via system DNS for domain: {} -> {} IPs", domain, ips.len());
return Ok(ips);
}
Err(system_err) => {
warn!("System DNS failed for domain: {} - {}", domain, system_err);
}
}
// Step 3: Fallback to public DNS
info!("Falling back to public DNS for domain: {}", domain);
match self.resolve_with_public_dns(domain).await {
Ok(ips) => {
self.update_cache(domain, ips.clone()).await;
info!("DNS resolution completed via public DNS for domain: {} -> {} IPs", domain, ips.len());
Ok(ips)
}
Err(public_err) => {
error!(
"All DNS resolution attempts failed for domain:` {}`. System DNS: failed, Public DNS: {}",
domain, public_err
);
Err(DnsError::AllAttemptsFailed {
domain: domain.to_string(),
})
}
}
}
}
/// Global DNS resolver instance
static GLOBAL_DNS_RESOLVER: OnceLock<LayeredDnsResolver> = OnceLock::new();
/// Initialize the global DNS resolver
#[instrument]
pub async fn init_global_dns_resolver() -> Result<(), DnsError> {
info!("Initializing global DNS resolver");
let resolver = LayeredDnsResolver::new().await?;
match GLOBAL_DNS_RESOLVER.set(resolver) {
Ok(()) => {
info!("Global DNS resolver initialized successfully");
Ok(())
}
Err(_) => {
warn!("Global DNS resolver was already initialized");
Ok(())
}
}
}
/// Get the global DNS resolver instance
pub fn get_global_dns_resolver() -> Option<&'static LayeredDnsResolver> {
GLOBAL_DNS_RESOLVER.get()
}
/// Resolve domain using the global DNS resolver with comprehensive tracing
#[instrument(skip_all, fields(domain = %domain))]
pub async fn resolve_domain(domain: &str) -> Result<Vec<IpAddr>, DnsError> {
info!("resolving domain for: {}", domain);
match get_global_dns_resolver() {
Some(resolver) => resolver.resolve(domain).await,
None => Err(DnsError::InitializationFailed {
source: "Global DNS resolver not initialized. Call init_global_dns_resolver() first."
.to_string()
.into(),
}),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_domain_validation() {
// Valid domains
assert!(LayeredDnsResolver::validate_domain_format("example.com").is_ok());
assert!(LayeredDnsResolver::validate_domain_format("sub.example.com").is_ok());
assert!(LayeredDnsResolver::validate_domain_format("very.deep.sub.domain.example.com").is_ok());
// Invalid domains - too long FQDN
let long_domain = "a".repeat(254);
assert!(LayeredDnsResolver::validate_domain_format(&long_domain).is_err());
// Invalid domains - label too long
let long_label = format!("{}.com", "a".repeat(64));
assert!(LayeredDnsResolver::validate_domain_format(&long_label).is_err());
// Invalid domains - empty label
assert!(LayeredDnsResolver::validate_domain_format("example..com").is_err());
}
#[tokio::test]
async fn test_cache_functionality() {
let resolver = LayeredDnsResolver::new().await.unwrap();
// Test cache miss
assert!(resolver.check_cache("example.com").await.is_none());
// Update cache
let test_ips = vec![IpAddr::from([192, 0, 2, 1])];
resolver.update_cache("example.com", test_ips.clone()).await;
// Test cache hit
assert_eq!(resolver.check_cache("example.com").await, Some(test_ips));
// Test cache stats (note: moka cache might not immediately reflect changes)
let (total, _weighted_size) = resolver.cache_stats().await;
// Cache should have at least the entry we just added (might be 0 due to async nature)
assert!(total <= 1, "Cache should have at most 1 entry, got {total}");
}
#[tokio::test]
async fn test_dns_resolution() {
let resolver = LayeredDnsResolver::new().await.unwrap();
// Test resolution of a known domain (localhost should always resolve)
match resolver.resolve("localhost").await {
Ok(ips) => {
assert!(!ips.is_empty());
println!("Resolved localhost to: {ips:?}");
}
Err(e) => {
// In some test environments, even localhost might fail
// This is acceptable as long as our error handling works
println!("DNS resolution failed (might be expected in test environments): {e}");
}
}
}
#[tokio::test]
async fn test_invalid_domain_resolution() {
let resolver = LayeredDnsResolver::new().await.unwrap();
// Test resolution of invalid domain
let result = resolver
.resolve("nonexistent.invalid.domain.example.thisdefinitelydoesnotexist")
.await;
assert!(result.is_err());
if let Err(e) = result {
println!("Expected error for invalid domain: {e}");
// Should be AllAttemptsFailed since both system and public DNS should fail
assert!(matches!(e, DnsError::AllAttemptsFailed { .. }));
}
}
#[tokio::test]
async fn test_cache_invalidation() {
let resolver = LayeredDnsResolver::new().await.unwrap();
// Add entry to cache
let test_ips = vec![IpAddr::from([192, 0, 2, 1])];
resolver.update_cache("test.example.com", test_ips.clone()).await;
// Verify cache hit
assert_eq!(resolver.check_cache("test.example.com").await, Some(test_ips));
// Invalidate cache
resolver.invalidate_cache().await;
// Verify cache miss after invalidation
assert!(resolver.check_cache("test.example.com").await.is_none());
}
#[tokio::test]
async fn test_global_resolver_initialization() {
// Test initialization
assert!(init_global_dns_resolver().await.is_ok());
// Test that resolver is available
assert!(get_global_dns_resolver().is_some());
// Test domain resolution through global resolver
match resolve_domain("localhost").await {
Ok(ips) => {
assert!(!ips.is_empty());
println!("Global resolver resolved localhost to: {ips:?}");
}
Err(e) => {
println!("Global resolver DNS resolution failed (might be expected in test environments): {e}");
}
}
}
}
// #![allow(dead_code)]
//
// //! Layered DNS resolution utility for Kubernetes environments
// //!
// //! This module provides robust DNS resolution with multiple fallback layers:
// //! 1. Local cache (Moka) for previously resolved results
// //! 2. System DNS resolver (container/host adaptive) using hickory-resolver
// //! 3. Public DNS servers as final fallback (8.8.8.8, 1.1.1.1) using hickory-resolver with TLS
// //!
// //! The resolver is designed to handle 5-level or deeper domain names that may fail
// //! in Kubernetes environments due to CoreDNS configuration, DNS recursion limits,
// //! or network-related issues. Uses hickory-resolver for actual DNS queries with TLS support.
//
// use hickory_resolver::Resolver;
// use hickory_resolver::config::ResolverConfig;
// use hickory_resolver::name_server::TokioConnectionProvider;
// use moka::future::Cache;
// use std::net::IpAddr;
// use std::sync::OnceLock;
// use std::time::Duration;
// use tracing::{debug, error, info, instrument, warn};
//
// /// Maximum FQDN length according to RFC standards
// const MAX_FQDN_LENGTH: usize = 253;
// /// Maximum DNS label length according to RFC standards
// const MAX_LABEL_LENGTH: usize = 63;
// /// Cache entry TTL in seconds
// const CACHE_TTL_SECONDS: u64 = 300; // 5 minutes
// /// Maximum cache size (number of entries)
// const MAX_CACHE_SIZE: u64 = 10000;
//
// /// DNS resolution error types with detailed context and tracing information
// #[derive(Debug, thiserror::Error)]
// pub enum DnsError {
// #[error("Invalid domain format: {reason}")]
// InvalidFormat { reason: String },
//
// #[error("Local cache miss for domain: {domain}")]
// CacheMiss { domain: String },
//
// #[error("System DNS resolution failed for domain: {domain} - {source}")]
// SystemDnsFailed {
// domain: String,
// #[source]
// source: Box<dyn std::error::Error + Send + Sync>,
// },
//
// #[error("Public DNS resolution failed for domain: {domain} - {source}")]
// PublicDnsFailed {
// domain: String,
// #[source]
// source: Box<dyn std::error::Error + Send + Sync>,
// },
//
// #[error(
// "All DNS resolution attempts failed for domain: {domain}. Please check your domain spelling, network connectivity, or DNS configuration"
// )]
// AllAttemptsFailed { domain: String },
//
// #[error("DNS resolver initialization failed: {source}")]
// InitializationFailed {
// #[source]
// source: Box<dyn std::error::Error + Send + Sync>,
// },
//
// #[error("DNS configuration error: {source}")]
// ConfigurationError {
// #[source]
// source: Box<dyn std::error::Error + Send + Sync>,
// },
// }
//
// /// Layered DNS resolver with caching and multiple fallback strategies
// pub struct LayeredDnsResolver {
// /// Local cache for resolved domains using Moka for high performance
// cache: Cache<String, Vec<IpAddr>>,
// /// System DNS resolver using hickory-resolver with default configuration
// system_resolver: Resolver<TokioConnectionProvider>,
// /// Public DNS resolver using hickory-resolver with Cloudflare DNS servers
// public_resolver: Resolver<TokioConnectionProvider>,
// }
//
// impl LayeredDnsResolver {
// /// Create a new layered DNS resolver with automatic DNS configuration detection
// #[instrument(skip_all)]
// pub async fn new() -> Result<Self, DnsError> {
// info!("Initializing layered DNS resolver with hickory-resolver, Moka cache and public DNS fallback");
//
// // Create Moka cache with TTL and size limits
// let cache = Cache::builder()
// .time_to_live(Duration::from_secs(CACHE_TTL_SECONDS))
// .max_capacity(MAX_CACHE_SIZE)
// .build();
//
// // Create system DNS resolver with default configuration (auto-detects container/host DNS)
// let system_resolver =
// Resolver::builder_with_config(ResolverConfig::default(), TokioConnectionProvider::default()).build();
//
// let mut config = ResolverConfig::cloudflare_tls();
// for ns in ResolverConfig::google_tls().name_servers() {
// config.add_name_server(ns.clone())
// }
// // Create public DNS resolver using Cloudflare DNS with TLS support
// let public_resolver = Resolver::builder_with_config(config, TokioConnectionProvider::default()).build();
//
// info!("DNS resolver initialized successfully with hickory-resolver system and Cloudflare TLS public fallback");
//
// Ok(Self {
// cache,
// system_resolver,
// public_resolver,
// })
// }
//
// /// Validate domain format according to RFC standards
// #[instrument(skip_all, fields(domain = %domain))]
// fn validate_domain_format(domain: &str) -> Result<(), DnsError> {
// info!("Validating domain format start");
// // Check FQDN length
// if domain.len() > MAX_FQDN_LENGTH {
// return Err(DnsError::InvalidFormat {
// reason: format!("FQDN must not exceed {} bytes, got {} bytes", MAX_FQDN_LENGTH, domain.len()),
// });
// }
//
// // Check each label length
// for label in domain.split('.') {
// if label.len() > MAX_LABEL_LENGTH {
// return Err(DnsError::InvalidFormat {
// reason: format!(
// "Each label must not exceed {} bytes, label '{}' has {} bytes",
// MAX_LABEL_LENGTH,
// label,
// label.len()
// ),
// });
// }
// }
//
// // Check for empty labels (except trailing dot)
// let labels: Vec<&str> = domain.trim_end_matches('.').split('.').collect();
// for label in &labels {
// if label.is_empty() {
// return Err(DnsError::InvalidFormat {
// reason: "Domain contains empty labels".to_string(),
// });
// }
// }
// info!("DNS resolver validated successfully");
// Ok(())
// }
//
// /// Check local cache for resolved domain
// #[instrument(skip_all, fields(domain = %domain))]
// async fn check_cache(&self, domain: &str) -> Option<Vec<IpAddr>> {
// match self.cache.get(domain).await {
// Some(ips) => {
// debug!("DNS cache hit for domain: {}, found {} IPs", domain, ips.len());
// Some(ips)
// }
// None => {
// debug!("DNS cache miss for domain: {}", domain);
// None
// }
// }
// }
//
// /// Update local cache with resolved IPs
// #[instrument(skip_all, fields(domain = %domain, ip_count = ips.len()))]
// async fn update_cache(&self, domain: &str, ips: Vec<IpAddr>) {
// self.cache.insert(domain.to_string(), ips.clone()).await;
// debug!("DNS cache updated for domain: {} with {} IPs", domain, ips.len());
// }
//
// /// Get cache statistics for monitoring
// #[instrument(skip_all)]
// pub async fn cache_stats(&self) -> (u64, u64) {
// let entry_count = self.cache.entry_count();
// let weighted_size = self.cache.weighted_size();
// debug!("DNS cache stats - entries: {}, weighted_size: {}", entry_count, weighted_size);
// (entry_count, weighted_size)
// }
//
// /// Manually invalidate cache entries (useful for testing or forced refresh)
// #[instrument(skip_all)]
// pub async fn invalidate_cache(&self) {
// self.cache.invalidate_all();
// info!("DNS cache invalidated");
// }
//
// /// Resolve domain using system DNS (cluster/host DNS configuration) with hickory-resolver
// #[instrument(skip_all, fields(domain = %domain))]
// async fn resolve_with_system_dns(&self, domain: &str) -> Result<Vec<IpAddr>, DnsError> {
// debug!("Attempting system DNS resolution for domain: {} using hickory-resolver", domain);
//
// match self.system_resolver.lookup_ip(domain).await {
// Ok(lookup) => {
// let ips: Vec<IpAddr> = lookup.iter().collect();
// if !ips.is_empty() {
// info!("System DNS resolution successful for domain: {} -> {} IPs", domain, ips.len());
// Ok(ips)
// } else {
// warn!("System DNS returned empty result for domain: {}", domain);
// Err(DnsError::SystemDnsFailed {
// domain: domain.to_string(),
// source: "No IP addresses found".to_string().into(),
// })
// }
// }
// Err(e) => {
// warn!("System DNS resolution failed for domain: {} - {}", domain, e);
// Err(DnsError::SystemDnsFailed {
// domain: domain.to_string(),
// source: Box::new(e),
// })
// }
// }
// }
//
// /// Resolve domain using public DNS servers (Cloudflare TLS DNS) with hickory-resolver
// #[instrument(skip_all, fields(domain = %domain))]
// async fn resolve_with_public_dns(&self, domain: &str) -> Result<Vec<IpAddr>, DnsError> {
// debug!(
// "Attempting public DNS resolution for domain: {} using hickory-resolver with TLS-enabled Cloudflare DNS",
// domain
// );
//
// match self.public_resolver.lookup_ip(domain).await {
// Ok(lookup) => {
// let ips: Vec<IpAddr> = lookup.iter().collect();
// if !ips.is_empty() {
// info!("Public DNS resolution successful for domain: {} -> {} IPs", domain, ips.len());
// Ok(ips)
// } else {
// warn!("Public DNS returned empty result for domain: {}", domain);
// Err(DnsError::PublicDnsFailed {
// domain: domain.to_string(),
// source: "No IP addresses found".to_string().into(),
// })
// }
// }
// Err(e) => {
// error!("Public DNS resolution failed for domain: {} - {}", domain, e);
// Err(DnsError::PublicDnsFailed {
// domain: domain.to_string(),
// source: Box::new(e),
// })
// }
// }
// }
//
// /// Resolve domain with layered fallback strategy using hickory-resolver
// ///
// /// Resolution order with detailed tracing:
// /// 1. Local cache (Moka with TTL)
// /// 2. System DNS (hickory-resolver with host/container adaptive configuration)
// /// 3. Public DNS (hickory-resolver with TLS-enabled Cloudflare DNS fallback)
// #[instrument(skip_all, fields(domain = %domain))]
// pub async fn resolve(&self, domain: &str) -> Result<Vec<IpAddr>, DnsError> {
// info!("Starting DNS resolution process for domain: {} start", domain);
// // Validate domain format first
// Self::validate_domain_format(domain)?;
//
// info!("Starting DNS resolution for domain: {}", domain);
//
// // Step 1: Check local cache
// if let Some(ips) = self.check_cache(domain).await {
// info!("DNS resolution completed from cache for domain: {} -> {} IPs", domain, ips.len());
// return Ok(ips);
// }
//
// debug!("Local cache miss for domain: {}, attempting system DNS", domain);
//
// // Step 2: Try system DNS (cluster/host adaptive)
// match self.resolve_with_system_dns(domain).await {
// Ok(ips) => {
// self.update_cache(domain, ips.clone()).await;
// info!("DNS resolution completed via system DNS for domain: {} -> {} IPs", domain, ips.len());
// return Ok(ips);
// }
// Err(system_err) => {
// warn!("System DNS failed for domain: {} - {}", domain, system_err);
// }
// }
//
// // Step 3: Fallback to public DNS
// info!("Falling back to public DNS for domain: {}", domain);
// match self.resolve_with_public_dns(domain).await {
// Ok(ips) => {
// self.update_cache(domain, ips.clone()).await;
// info!("DNS resolution completed via public DNS for domain: {} -> {} IPs", domain, ips.len());
// Ok(ips)
// }
// Err(public_err) => {
// error!(
// "All DNS resolution attempts failed for domain:` {}`. System DNS: failed, Public DNS: {}",
// domain, public_err
// );
// Err(DnsError::AllAttemptsFailed {
// domain: domain.to_string(),
// })
// }
// }
// }
// }
//
// /// Global DNS resolver instance
// static GLOBAL_DNS_RESOLVER: OnceLock<LayeredDnsResolver> = OnceLock::new();
//
// /// Initialize the global DNS resolver
// #[instrument]
// pub async fn init_global_dns_resolver() -> Result<(), DnsError> {
// info!("Initializing global DNS resolver");
// let resolver = LayeredDnsResolver::new().await?;
//
// match GLOBAL_DNS_RESOLVER.set(resolver) {
// Ok(()) => {
// info!("Global DNS resolver initialized successfully");
// Ok(())
// }
// Err(_) => {
// warn!("Global DNS resolver was already initialized");
// Ok(())
// }
// }
// }
//
// /// Get the global DNS resolver instance
// pub fn get_global_dns_resolver() -> Option<&'static LayeredDnsResolver> {
// GLOBAL_DNS_RESOLVER.get()
// }
//
// /// Resolve domain using the global DNS resolver with comprehensive tracing
// #[instrument(skip_all, fields(domain = %domain))]
// pub async fn resolve_domain(domain: &str) -> Result<Vec<IpAddr>, DnsError> {
// info!("resolving domain for: {}", domain);
// match get_global_dns_resolver() {
// Some(resolver) => resolver.resolve(domain).await,
// None => Err(DnsError::InitializationFailed {
// source: "Global DNS resolver not initialized. Call init_global_dns_resolver() first."
// .to_string()
// .into(),
// }),
// }
// }
//
// #[cfg(test)]
// mod tests {
// use super::*;
//
// #[test]
// fn test_domain_validation() {
// // Valid domains
// assert!(LayeredDnsResolver::validate_domain_format("example.com").is_ok());
// assert!(LayeredDnsResolver::validate_domain_format("sub.example.com").is_ok());
// assert!(LayeredDnsResolver::validate_domain_format("very.deep.sub.domain.example.com").is_ok());
//
// // Invalid domains - too long FQDN
// let long_domain = "a".repeat(254);
// assert!(LayeredDnsResolver::validate_domain_format(&long_domain).is_err());
//
// // Invalid domains - label too long
// let long_label = format!("{}.com", "a".repeat(64));
// assert!(LayeredDnsResolver::validate_domain_format(&long_label).is_err());
//
// // Invalid domains - empty label
// assert!(LayeredDnsResolver::validate_domain_format("example..com").is_err());
// }
//
// #[tokio::test]
// async fn test_cache_functionality() {
// let resolver = LayeredDnsResolver::new().await.unwrap();
//
// // Test cache miss
// assert!(resolver.check_cache("example.com").await.is_none());
//
// // Update cache
// let test_ips = vec![IpAddr::from([192, 0, 2, 1])];
// resolver.update_cache("example.com", test_ips.clone()).await;
//
// // Test cache hit
// assert_eq!(resolver.check_cache("example.com").await, Some(test_ips));
//
// // Test cache stats (note: moka cache might not immediately reflect changes)
// let (total, _weighted_size) = resolver.cache_stats().await;
// // Cache should have at least the entry we just added (might be 0 due to async nature)
// assert!(total <= 1, "Cache should have at most 1 entry, got {total}");
// }
//
// #[tokio::test]
// async fn test_dns_resolution() {
// let resolver = LayeredDnsResolver::new().await.unwrap();
//
// // Test resolution of a known domain (localhost should always resolve)
// match resolver.resolve("localhost").await {
// Ok(ips) => {
// assert!(!ips.is_empty());
// println!("Resolved localhost to: {ips:?}");
// }
// Err(e) => {
// // In some test environments, even localhost might fail
// // This is acceptable as long as our error handling works
// println!("DNS resolution failed (might be expected in test environments): {e}");
// }
// }
// }
//
// #[tokio::test]
// async fn test_invalid_domain_resolution() {
// let resolver = LayeredDnsResolver::new().await.unwrap();
//
// // Test resolution of invalid domain
// let result = resolver
// .resolve("nonexistent.invalid.domain.example.thisdefinitelydoesnotexist")
// .await;
// assert!(result.is_err());
//
// if let Err(e) = result {
// println!("Expected error for invalid domain: {e}");
// // Should be AllAttemptsFailed since both system and public DNS should fail
// assert!(matches!(e, DnsError::AllAttemptsFailed { .. }));
// }
// }
//
// #[tokio::test]
// async fn test_cache_invalidation() {
// let resolver = LayeredDnsResolver::new().await.unwrap();
//
// // Add entry to cache
// let test_ips = vec![IpAddr::from([192, 0, 2, 1])];
// resolver.update_cache("test.example.com", test_ips.clone()).await;
//
// // Verify cache hit
// assert_eq!(resolver.check_cache("test.example.com").await, Some(test_ips));
//
// // Invalidate cache
// resolver.invalidate_cache().await;
//
// // Verify cache miss after invalidation
// assert!(resolver.check_cache("test.example.com").await.is_none());
// }
//
// #[tokio::test]
// async fn test_global_resolver_initialization() {
// // Test initialization
// assert!(init_global_dns_resolver().await.is_ok());
//
// // Test that resolver is available
// assert!(get_global_dns_resolver().is_some());
//
// // Test domain resolution through global resolver
// match resolve_domain("localhost").await {
// Ok(ips) => {
// assert!(!ips.is_empty());
// println!("Global resolver resolved localhost to: {ips:?}");
// }
// Err(e) => {
// println!("Global resolver DNS resolution failed (might be expected in test environments): {e}");
// }
// }
// }
// }

View File

@@ -24,8 +24,6 @@ pub mod net;
#[cfg(feature = "http")]
pub mod http;
#[cfg(feature = "net")]
pub use dns_resolver::*;
#[cfg(feature = "net")]
pub use net::*;

View File

@@ -148,17 +148,6 @@ pub fn is_local_host(host: Host<&str>, port: u16, local_port: u16) -> std::io::R
pub async fn get_host_ip(host: Host<&str>) -> std::io::Result<HashSet<IpAddr>> {
match host {
Host::Domain(domain) => {
match crate::dns_resolver::resolve_domain(domain).await {
Ok(ips) => {
info!("Resolved domain {domain} using custom DNS resolver: {ips:?}");
return Ok(ips.into_iter().collect());
}
Err(err) => {
error!(
"Failed to resolve domain {domain} using custom DNS resolver, falling back to system resolver,err: {err}"
);
}
}
// Check cache first
if CUSTOM_DNS_RESOLVER.read().unwrap().is_none() {
if let Ok(mut cache) = DNS_CACHE.lock() {
@@ -321,7 +310,6 @@ where
#[cfg(test)]
mod test {
use super::*;
use crate::init_global_dns_resolver;
use std::net::{Ipv4Addr, Ipv6Addr};
use std::{collections::HashSet, io::Error as IoError};
@@ -448,12 +436,7 @@ mod test {
#[tokio::test]
async fn test_get_host_ip() {
set_mock_dns_resolver(mock_resolver);
match init_global_dns_resolver().await {
Ok(_) => {}
Err(e) => {
error!("Failed to initialize global DNS resolver: {e}");
}
}
// Test IPv4 address
let ipv4_host = Host::Ipv4(Ipv4Addr::new(192, 168, 1, 1));
let ipv4_result = get_host_ip(ipv4_host).await.unwrap();