use serde::{Deserialize, Serialize};
use std::net::IpAddr;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DnsQueryRequest {
pub query_id: Option<String>,
pub domain: String,
pub record_type: DnsRecordType,
pub enable_edns: bool,
pub client_address: Option<String>,
pub timeout_ms: Option<u64>,
pub disable_cache: bool,
pub enable_dnssec: bool,
}
impl DnsQueryRequest {
pub fn new(domain: impl Into<String>, record_type: DnsRecordType) -> Self {
Self {
query_id: None,
domain: domain.into(),
record_type,
enable_edns: true,
client_address: None,
timeout_ms: None,
disable_cache: false,
enable_dnssec: false,
}
}
pub fn with_query_id(mut self, id: impl Into<String>) -> Self {
self.query_id = Some(id.into());
self
}
pub fn with_client_address(mut self, address: impl Into<String>) -> Self {
self.client_address = Some(address.into());
self
}
pub fn with_timeout(mut self, timeout_ms: u64) -> Self {
self.timeout_ms = Some(timeout_ms);
self
}
pub fn disable_cache(mut self) -> Self {
self.disable_cache = true;
self
}
pub fn with_dnssec(mut self, enable: bool) -> Self {
self.enable_dnssec = enable;
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DnsQueryResponse {
pub query_id: String,
pub domain: String,
pub record_type: DnsRecordType,
pub success: bool,
pub error: Option<String>,
pub records: Vec<DnsRecord>,
pub duration_ms: u64,
pub server_used: Option<String>,
pub dnssec_status: Option<DnssecStatus>,
pub dnssec_records: Vec<DnsRecord>,
}
impl DnsQueryResponse {
pub fn ip_addresses(&self) -> Vec<IpAddr> {
self.records
.iter()
.filter_map(|record| {
if let DnsRecordValue::IpAddr(ip) = &record.value {
Some(*ip)
} else {
None
}
})
.collect()
}
pub fn domains(&self) -> Vec<String> {
self.records
.iter()
.filter_map(|record| {
if let DnsRecordValue::Domain(domain) = &record.value {
Some(domain.clone())
} else {
None
}
})
.collect()
}
pub fn texts(&self) -> Vec<String> {
self.records
.iter()
.filter_map(|record| {
if let DnsRecordValue::Text(text) = &record.value {
Some(text.clone())
} else {
None
}
})
.collect()
}
pub fn mx_records(&self) -> Vec<(u16, String)> {
self.records
.iter()
.filter_map(|record| {
if let DnsRecordValue::Mx { priority, exchange } = &record.value {
Some((*priority, exchange.clone()))
} else {
None
}
})
.collect()
}
pub fn has_dnssec_records(&self) -> bool {
!self.dnssec_records.is_empty() ||
self.records.iter().any(|r| r.record_type.is_dnssec_record())
}
pub fn dnssec_status_description(&self) -> String {
match &self.dnssec_status {
Some(DnssecStatus::Secure) => "🔒 DNSSEC验证通过".to_string(),
Some(DnssecStatus::Insecure) => "🔓 未启用DNSSEC".to_string(),
Some(DnssecStatus::Bogus) => "⚠️ DNSSEC验证失败".to_string(),
Some(DnssecStatus::Indeterminate) => "❓ DNSSEC状态不确定".to_string(),
None => "➖ 无DNSSEC信息".to_string(),
}
}
pub fn dnssec_record_summary(&self) -> String {
let dnssec_records: Vec<_> = self.records.iter()
.filter(|r| r.record_type.is_dnssec_record())
.collect();
if dnssec_records.is_empty() {
"无DNSSEC记录".to_string()
} else {
let mut summary = Vec::new();
let mut counts = std::collections::HashMap::new();
for record in &dnssec_records {
*counts.entry(record.record_type).or_insert(0) += 1;
}
for (record_type, count) in counts {
summary.push(format!("{}: {}", record_type.as_str(), count));
}
summary.join(", ")
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum DnssecStatus {
Secure,
Insecure,
Bogus,
Indeterminate,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum DnsRecordType {
A,
AAAA,
CNAME,
MX,
TXT,
NS,
PTR,
SRV,
SOA,
RRSIG,
DNSKEY,
DS,
NSEC,
NSEC3,
}
impl DnsRecordType {
pub fn as_str(&self) -> &'static str {
match self {
Self::A => "A",
Self::AAAA => "AAAA",
Self::CNAME => "CNAME",
Self::MX => "MX",
Self::TXT => "TXT",
Self::NS => "NS",
Self::PTR => "PTR",
Self::SRV => "SRV",
Self::SOA => "SOA",
Self::RRSIG => "RRSIG",
Self::DNSKEY => "DNSKEY",
Self::DS => "DS",
Self::NSEC => "NSEC",
Self::NSEC3 => "NSEC3",
}
}
pub fn from_str(s: &str) -> Option<Self> {
match s.to_uppercase().as_str() {
"A" => Some(Self::A),
"AAAA" => Some(Self::AAAA),
"CNAME" => Some(Self::CNAME),
"MX" => Some(Self::MX),
"TXT" => Some(Self::TXT),
"NS" => Some(Self::NS),
"PTR" => Some(Self::PTR),
"SRV" => Some(Self::SRV),
"SOA" => Some(Self::SOA),
"RRSIG" => Some(Self::RRSIG),
"DNSKEY" => Some(Self::DNSKEY),
"DS" => Some(Self::DS),
"NSEC" => Some(Self::NSEC),
"NSEC3" => Some(Self::NSEC3),
_ => None,
}
}
pub fn is_dnssec_record(&self) -> bool {
matches!(self, Self::RRSIG | Self::DNSKEY | Self::DS | Self::NSEC | Self::NSEC3)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DnsRecord {
pub name: String,
pub record_type: DnsRecordType,
pub value: DnsRecordValue,
pub ttl: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum DnsRecordValue {
IpAddr(IpAddr),
Domain(String),
Text(String),
Mx {
priority: u16,
exchange: String,
},
Srv {
priority: u16,
weight: u16,
port: u16,
target: String,
},
Soa {
mname: String,
rname: String,
serial: u32,
refresh: u32,
retry: u32,
expire: u32,
minimum: u32,
},
}
impl DnsRecord {
pub fn a(name: impl Into<String>, ip: std::net::Ipv4Addr, ttl: u32) -> Self {
Self {
name: name.into(),
record_type: DnsRecordType::A,
value: DnsRecordValue::IpAddr(IpAddr::V4(ip)),
ttl,
}
}
pub fn aaaa(name: impl Into<String>, ip: std::net::Ipv6Addr, ttl: u32) -> Self {
Self {
name: name.into(),
record_type: DnsRecordType::AAAA,
value: DnsRecordValue::IpAddr(IpAddr::V6(ip)),
ttl,
}
}
pub fn cname(name: impl Into<String>, target: impl Into<String>, ttl: u32) -> Self {
Self {
name: name.into(),
record_type: DnsRecordType::CNAME,
value: DnsRecordValue::Domain(target.into()),
ttl,
}
}
pub fn txt(name: impl Into<String>, text: impl Into<String>, ttl: u32) -> Self {
Self {
name: name.into(),
record_type: DnsRecordType::TXT,
value: DnsRecordValue::Text(text.into()),
ttl,
}
}
}