diff --git a/Cargo.toml b/Cargo.toml index 297b484..a95f3c9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,8 +17,8 @@ path = "src/main.rs" [dependencies] clap = { version = "4.4", features = ["derive"] } tokio = { version = "1.0", features = ["full"] } -trust-dns-resolver = "0.23" -trust-dns-client = "0.23" +hickory-resolver = "0.24" +hickory-client = "0.24" socket2 = "0.5" pnet = "0.34" anyhow = "1.0" diff --git a/src/cli/mod.rs b/src/cli/mod.rs index d0ec1fd..526ad20 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -181,24 +181,24 @@ pub enum RecordTypeArg { } impl RecordTypeArg { - pub fn to_record_type(&self) -> Vec { + pub fn to_record_type(&self) -> Vec { match self { - RecordTypeArg::A => vec![trust_dns_client::rr::RecordType::A], - RecordTypeArg::AAAA => vec![trust_dns_client::rr::RecordType::AAAA], - RecordTypeArg::MX => vec![trust_dns_client::rr::RecordType::MX], - RecordTypeArg::NS => vec![trust_dns_client::rr::RecordType::NS], - RecordTypeArg::TXT => vec![trust_dns_client::rr::RecordType::TXT], - RecordTypeArg::CNAME => vec![trust_dns_client::rr::RecordType::CNAME], - RecordTypeArg::SOA => vec![trust_dns_client::rr::RecordType::SOA], - RecordTypeArg::PTR => vec![trust_dns_client::rr::RecordType::PTR], + RecordTypeArg::A => vec![hickory_client::rr::RecordType::A], + RecordTypeArg::AAAA => vec![hickory_client::rr::RecordType::AAAA], + RecordTypeArg::MX => vec![hickory_client::rr::RecordType::MX], + RecordTypeArg::NS => vec![hickory_client::rr::RecordType::NS], + RecordTypeArg::TXT => vec![hickory_client::rr::RecordType::TXT], + RecordTypeArg::CNAME => vec![hickory_client::rr::RecordType::CNAME], + RecordTypeArg::SOA => vec![hickory_client::rr::RecordType::SOA], + RecordTypeArg::PTR => vec![hickory_client::rr::RecordType::PTR], RecordTypeArg::All => vec![ - trust_dns_client::rr::RecordType::A, - trust_dns_client::rr::RecordType::AAAA, - trust_dns_client::rr::RecordType::MX, - trust_dns_client::rr::RecordType::NS, - trust_dns_client::rr::RecordType::TXT, - trust_dns_client::rr::RecordType::CNAME, - trust_dns_client::rr::RecordType::SOA, + hickory_client::rr::RecordType::A, + hickory_client::rr::RecordType::AAAA, + hickory_client::rr::RecordType::MX, + hickory_client::rr::RecordType::NS, + hickory_client::rr::RecordType::TXT, + hickory_client::rr::RecordType::CNAME, + hickory_client::rr::RecordType::SOA, ], } } diff --git a/src/dns/categories.rs b/src/dns/categories.rs index fafd5ae..38f5b2e 100644 --- a/src/dns/categories.rs +++ b/src/dns/categories.rs @@ -1,6 +1,6 @@ use super::DnsTest; use crate::utils::TestResult; -use trust_dns_client::rr::RecordType; +use hickory_client::rr::RecordType; #[derive(Clone)] pub struct DomainCategory { diff --git a/src/dns/mod.rs b/src/dns/mod.rs index 5ddc4ec..73bd88c 100644 --- a/src/dns/mod.rs +++ b/src/dns/mod.rs @@ -1,13 +1,13 @@ use crate::utils::{measure_time, NetworkError, Result, TestResult}; +use hickory_client::rr::{Name, RData, RecordData, RecordType}; +use hickory_resolver::config::*; +use hickory_resolver::system_conf; +use hickory_resolver::TokioAsyncResolver; use std::net::{IpAddr, SocketAddr}; use std::str::FromStr; use std::time::Duration; use tokio::net::TcpStream; use tokio::time::timeout; -use trust_dns_client::rr::{Name, RData, RecordData, RecordType}; -use trust_dns_resolver::config::*; -use trust_dns_resolver::system_conf; -use trust_dns_resolver::TokioAsyncResolver; pub mod categories; pub mod queries; @@ -290,8 +290,8 @@ impl DnsTest { } async fn query_system_resolver(&self) -> Result { - // Try to use system DNS configuration first, fall back to default if that fails - let (config, opts) = match system_conf::read_system_conf() { + // Try to use system DNS configuration but disable search domains for accurate testing + let (mut config, mut opts) = match system_conf::read_system_conf() { Ok((config, opts)) => (config, opts), Err(_) => { // Fallback to default config if system config cannot be read @@ -300,6 +300,18 @@ impl DnsTest { } }; + // Clear search domains to prevent automatic domain expansion during DNS testing + // This ensures we query the exact domain name provided + // Create a new config with the same name servers but no search domains + let mut clean_config = ResolverConfig::new(); + for name_server in config.name_servers() { + clean_config.add_name_server(name_server.clone()); + } + config = clean_config; + + // Ensure we don't use search domains + opts.ndots = 0; + let resolver = TokioAsyncResolver::tokio(config, opts); let name = Name::from_str(&self.domain) @@ -324,35 +336,52 @@ impl DnsTest { Ok(format!("AAAA records: {}", ips.join(", "))) } RecordType::MX => { - let lookup = resolver - .mx_lookup(name.clone()) - .await - .map_err(|e| format!("MX lookup failed: {}", e))?; - let records: Vec = lookup - .iter() - .map(|mx| format!("{} {}", mx.preference(), mx.exchange())) - .collect(); - Ok(format!("MX records: {}", records.join(", "))) + let lookup_result = resolver.mx_lookup(name.clone()).await; + handle_dns_lookup_result( + lookup_result, + "MX", + |lookup| { + let records: Vec = lookup + .iter() + .map(|mx| format!("{} {}", mx.preference(), mx.exchange())) + .collect(); + format!("MX records: {}", records.join(", ")) + }, + "(none - no mail servers configured)", + ) } RecordType::TXT => { - let lookup = resolver - .txt_lookup(name.clone()) - .await - .map_err(|e| format!("TXT lookup failed: {}", e))?; - let records: Vec = lookup.iter().map(|txt| txt.to_string()).collect(); - Ok(format!("TXT records: {}", records.join(", "))) + let lookup_result = resolver.txt_lookup(name.clone()).await; + handle_dns_lookup_result( + lookup_result, + "TXT", + |lookup| { + let records: Vec = + lookup.iter().map(|txt| txt.to_string()).collect(); + format!("TXT records: {}", records.join(", ")) + }, + "(none - no text records found)", + ) } RecordType::NS => { - let lookup = resolver - .ns_lookup(name.clone()) - .await - .map_err(|e| format!("NS lookup failed: {}", e))?; - let records: Vec = lookup.iter().map(|ns| ns.to_string()).collect(); - Ok(format!("NS records: {}", records.join(", "))) + let lookup_result = resolver.ns_lookup(name.clone()).await; + handle_dns_lookup_result( + lookup_result, + "NS", + |lookup| { + let records: Vec = + lookup.iter().map(|ns| ns.to_string()).collect(); + format!("NS records: {}", records.join(", ")) + }, + "(none - no name servers found)", + ) } RecordType::CNAME => { - match resolver.lookup(name.clone(), self.record_type).await { - Ok(lookup) => { + let lookup_result = resolver.lookup(name.clone(), self.record_type).await; + handle_dns_lookup_result( + lookup_result, + "CNAME", + |lookup| { let records: Vec = lookup .iter() .filter_map(|record| { @@ -363,40 +392,36 @@ impl DnsTest { } }) .collect(); - Ok(format!("CNAME records: {}", records.join(", "))) - } - Err(e) => { - // Check if this is just "no records found" which is normal for domains without CNAME - let error_str = e.to_string(); - if error_str.contains("no record found") { - Ok("CNAME records: (none - domain is not an alias)".to_string()) - } else { - Err(format!("CNAME lookup failed: {}", e)) - } - } - } + format!("CNAME records: {}", records.join(", ")) + }, + "(none - domain is not an alias)", + ) } RecordType::SOA => { - let lookup = resolver - .soa_lookup(name.clone()) - .await - .map_err(|e| format!("SOA lookup failed: {}", e))?; - let records: Vec = lookup - .iter() - .map(|soa| { - format!( - "{} {} {} {} {} {} {}", - soa.mname(), - soa.rname(), - soa.serial(), - soa.refresh(), - soa.retry(), - soa.expire(), - soa.minimum() - ) - }) - .collect(); - Ok(format!("SOA records: {}", records.join(", "))) + let lookup_result = resolver.soa_lookup(name.clone()).await; + handle_dns_lookup_result( + lookup_result, + "SOA", + |lookup| { + let records: Vec = lookup + .iter() + .map(|soa| { + format!( + "{} {} {} {} {} {} {}", + soa.mname(), + soa.rname(), + soa.serial(), + soa.refresh(), + soa.retry(), + soa.expire(), + soa.minimum() + ) + }) + .collect(); + format!("SOA records: {}", records.join(", ")) + }, + "(none - no authority records found)", + ) } RecordType::PTR => { let lookup = resolver @@ -435,9 +460,9 @@ impl DnsTest { async fn query_specific_server(&self, server: SocketAddr) -> Result { // Create a resolver configuration that uses the specific server let mut config = ResolverConfig::new(); - config.add_name_server(trust_dns_resolver::config::NameServerConfig::new( + config.add_name_server(hickory_resolver::config::NameServerConfig::new( server, - trust_dns_resolver::config::Protocol::Udp, + hickory_resolver::config::Protocol::Udp, )); let opts = ResolverOpts::default(); @@ -696,3 +721,23 @@ pub fn debug_dns_config() -> String { Err(e) => format!("❌ Could not read system DNS config: {}", e), } } + +/// Helper function to handle DNS lookup results consistently across all record types +fn handle_dns_lookup_result( + result: std::result::Result, + record_type: &str, + format_success: impl FnOnce(T) -> String, + empty_message: &str, +) -> std::result::Result { + match result { + Ok(lookup_result) => Ok(format_success(lookup_result)), + Err(e) => { + let error_str = e.to_string(); + if error_str.contains("no record found") || error_str.contains("Name not found") { + Ok(format!("{} records: {}", record_type, empty_message)) + } else { + Err(format!("{} lookup failed: {}", record_type, e)) + } + } + } +} diff --git a/src/dns/queries.rs b/src/dns/queries.rs index 69412a6..313dc0c 100644 --- a/src/dns/queries.rs +++ b/src/dns/queries.rs @@ -1,6 +1,6 @@ use super::DnsTest; use crate::utils::TestResult; -use trust_dns_client::rr::RecordType; +use hickory_client::rr::RecordType; pub async fn comprehensive_dns_test(domain: &str) -> Vec { let mut results = Vec::new(); diff --git a/src/main.rs b/src/main.rs index d0af1db..3958873 100644 --- a/src/main.rs +++ b/src/main.rs @@ -285,7 +285,7 @@ async fn handle_full_test( // DNS servers test let dns_servers = - dns::test_common_dns_servers(&target, trust_dns_client::rr::RecordType::A).await; + dns::test_common_dns_servers(&target, hickory_client::rr::RecordType::A).await; all_results.extend(dns_servers); pb.inc(1); diff --git a/src/network/mod.rs b/src/network/mod.rs index f11f338..144f429 100644 --- a/src/network/mod.rs +++ b/src/network/mod.rs @@ -78,8 +78,8 @@ impl NetworkTest { } async fn resolve_target(&self) -> Result { - use trust_dns_resolver::config::*; - use trust_dns_resolver::TokioAsyncResolver; + use hickory_resolver::config::*; + use hickory_resolver::TokioAsyncResolver; let resolver = TokioAsyncResolver::tokio(ResolverConfig::default(), ResolverOpts::default()); diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index de12dd4..aaeb9e8 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -33,11 +33,8 @@ async fn test_network_udp_connectivity() { #[tokio::test] async fn test_dns_query() { - let test = dns::DnsTest::new( - "google.com".to_string(), - trust_dns_client::rr::RecordType::A, - ) - .with_timeout(Duration::from_secs(10)); + let test = dns::DnsTest::new("google.com".to_string(), hickory_client::rr::RecordType::A) + .with_timeout(Duration::from_secs(10)); let result = test.run().await; assert!(result.success, "DNS A query for google.com should succeed"); @@ -48,7 +45,7 @@ async fn test_dns_query() { #[tokio::test] async fn test_dns_servers() { let results = - dns::test_common_dns_servers("google.com", trust_dns_client::rr::RecordType::A).await; + dns::test_common_dns_servers("google.com", hickory_client::rr::RecordType::A).await; assert!(!results.is_empty()); let successful_results = results.iter().filter(|r| r.success).count(); @@ -83,7 +80,7 @@ async fn test_comprehensive_dns() { async fn test_domain_categories() { let results = dns::categories::test_domain_category( &dns::categories::NORMAL_SITES, - trust_dns_client::rr::RecordType::A, + hickory_client::rr::RecordType::A, ) .await;