use std::collections::HashMap;
use std::net::IpAddr;
use std::sync::Arc;
use trust_dns_resolver::config::*;
use trust_dns_resolver::proto::rr::{RData, RecordType};
use trust_dns_resolver::TokioAsyncResolver;
#[derive(Debug, Clone)]
pub enum DnsRecord {
A(String), AAAA(String), CNAME(String), NS(String), MX(u16, String), TXT(String), SOA(String), PTR(String), }
#[derive(Debug, Clone)]
pub struct DnsResolveResult {
pub domain: String,
pub records: HashMap<String, Vec<DnsRecord>>,
pub has_records: bool,
}
pub struct DnsResolver {
resolver: TokioAsyncResolver,
}
impl DnsResolver {
pub async fn new() -> Result<Self, Box<dyn std::error::Error>> {
Self::new_with_resolvers(&[]).await
}
pub async fn new_with_resolvers(
resolvers: &[String],
) -> Result<Self, Box<dyn std::error::Error>> {
let resolver =
TokioAsyncResolver::tokio(build_resolver_config(resolvers)?, ResolverOpts::default());
Ok(DnsResolver { resolver })
}
pub async fn resolve_all_records(&self, domain: &str) -> DnsResolveResult {
resolve_all_records_with(&self.resolver, domain).await
}
pub async fn resolve_domains(&self, domains: Vec<String>) -> Vec<DnsResolveResult> {
let mut results = Vec::new();
let semaphore = Arc::new(tokio::sync::Semaphore::new(20));
let mut tasks = Vec::new();
for domain in domains {
let permit = Arc::clone(&semaphore);
let resolver = self.resolver.clone();
let task = tokio::spawn(async move {
match permit.acquire().await {
Ok(_permit) => resolve_all_records_with(&resolver, &domain).await,
Err(e) => {
eprintln!("错误: 获取信号量失败: {}", e);
DnsResolveResult {
domain,
records: HashMap::new(),
has_records: false,
}
}
}
});
tasks.push(task);
}
for task in tasks {
if let Ok(result) = task.await {
results.push(result);
}
}
results
}
pub fn display_results(&self, results: &[DnsResolveResult]) {
println!("=== DNS解析结果 ===");
for result in results {
if result.has_records {
println!("域名: {}", result.domain);
for (record_type, records) in &result.records {
println!(" {}:", record_type);
for record in records {
match record {
DnsRecord::A(ip) => println!(" {}", ip),
DnsRecord::AAAA(ip) => println!(" {}", ip),
DnsRecord::CNAME(cname) => println!(" {}", cname),
DnsRecord::NS(ns) => println!(" {}", ns),
DnsRecord::MX(priority, host) => println!(" {} {}", priority, host),
DnsRecord::TXT(txt) => println!(" \"{}\"", txt),
DnsRecord::SOA(soa) => println!(" {}", soa),
DnsRecord::PTR(ptr) => println!(" {}", ptr),
}
}
}
println!();
}
}
}
pub async fn resolve_a_record(&self, domain: &str) -> Option<String> {
if let Ok(response) = self.resolver.lookup_ip(domain).await {
for ip in response.iter() {
if let IpAddr::V4(ipv4) = ip {
return Some(ipv4.to_string());
}
}
}
None
}
}
fn build_resolver_config(
resolvers: &[String],
) -> Result<ResolverConfig, Box<dyn std::error::Error>> {
let ips: Vec<IpAddr> = resolvers
.iter()
.map(|resolver| resolver.parse())
.collect::<Result<Vec<IpAddr>, _>>()?;
if ips.is_empty() {
return Ok(ResolverConfig::default());
}
Ok(ResolverConfig::from_parts(
None,
vec![],
NameServerConfigGroup::from_ips_clear(&ips, 53, true),
))
}
async fn resolve_all_records_with(resolver: &TokioAsyncResolver, domain: &str) -> DnsResolveResult {
let mut records = HashMap::new();
let mut has_records = false;
if let Ok(response) = resolver.lookup_ip(domain).await {
let mut a_records = Vec::new();
for ip in response.iter() {
match ip {
IpAddr::V4(ipv4) => {
a_records.push(DnsRecord::A(ipv4.to_string()));
has_records = true;
}
IpAddr::V6(ipv6) => {
a_records.push(DnsRecord::AAAA(ipv6.to_string()));
has_records = true;
}
}
}
if !a_records.is_empty() {
records.insert("A/AAAA".to_string(), a_records);
}
}
if let Ok(response) = resolver.lookup(domain, RecordType::CNAME).await {
let mut cname_records = Vec::new();
for record in response.iter() {
if let RData::CNAME(cname) = record {
cname_records.push(DnsRecord::CNAME(cname.to_string()));
has_records = true;
}
}
if !cname_records.is_empty() {
records.insert("CNAME".to_string(), cname_records);
}
}
if let Ok(response) = resolver.lookup(domain, RecordType::NS).await {
let mut ns_records = Vec::new();
for record in response.iter() {
if let RData::NS(ns) = record {
ns_records.push(DnsRecord::NS(ns.to_string()));
has_records = true;
}
}
if !ns_records.is_empty() {
records.insert("NS".to_string(), ns_records);
}
}
if let Ok(response) = resolver.lookup(domain, RecordType::MX).await {
let mut mx_records = Vec::new();
for record in response.iter() {
if let RData::MX(mx) = record {
mx_records.push(DnsRecord::MX(mx.preference(), mx.exchange().to_string()));
has_records = true;
}
}
if !mx_records.is_empty() {
records.insert("MX".to_string(), mx_records);
}
}
if let Ok(response) = resolver.lookup(domain, RecordType::TXT).await {
let mut txt_records = Vec::new();
for record in response.iter() {
if let RData::TXT(txt) = record {
let txt_data = txt
.iter()
.map(|bytes| String::from_utf8_lossy(bytes).to_string())
.collect::<Vec<_>>()
.join("");
txt_records.push(DnsRecord::TXT(txt_data));
has_records = true;
}
}
if !txt_records.is_empty() {
records.insert("TXT".to_string(), txt_records);
}
}
if let Ok(response) = resolver.lookup(domain, RecordType::SOA).await {
let mut soa_records = Vec::new();
for record in response.iter() {
if let RData::SOA(soa) = record {
soa_records.push(DnsRecord::SOA(format!("{} {}", soa.mname(), soa.rname())));
has_records = true;
}
}
if !soa_records.is_empty() {
records.insert("SOA".to_string(), soa_records);
}
}
DnsResolveResult {
domain: domain.to_string(),
records,
has_records,
}
}