Add additional DNS servers and support DoH protocol additionally to regular DNS.
This commit is contained in:
@@ -19,6 +19,9 @@ clap = { version = "4.4", features = ["derive"] }
|
||||
tokio = { version = "1.0", features = ["full"] }
|
||||
hickory-resolver = "0.24"
|
||||
hickory-client = "0.24"
|
||||
reqwest = { version = "0.12", features = ["json"] }
|
||||
base64 = "0.22"
|
||||
urlencoding = "2.1"
|
||||
socket2 = "0.5"
|
||||
pnet = "0.34"
|
||||
anyhow = "1.0"
|
||||
|
||||
@@ -113,6 +113,20 @@ pub enum DnsCommands {
|
||||
Comprehensive { domain: String },
|
||||
#[command(about = "Test large DNS queries")]
|
||||
Large { domain: String },
|
||||
#[command(about = "Test DNS-over-HTTPS (DoH) providers")]
|
||||
Doh {
|
||||
domain: String,
|
||||
#[arg(
|
||||
short,
|
||||
long,
|
||||
help = "DoH provider name (google, google-json, cloudflare, cloudflare-family, cloudflare-security, cloudflare-json, cloudflare-family-json, cloudflare-security-json, quad9, quad9-unsecured, quad9-ecs, opendns, opendns-family, adguard, adguard-family, adguard-unfiltered)"
|
||||
)]
|
||||
provider: Option<String>,
|
||||
#[arg(short = 'r', long, value_enum, default_value = "a")]
|
||||
record_type: RecordTypeArg,
|
||||
},
|
||||
#[command(about = "List available DoH providers")]
|
||||
DohProviders,
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
|
||||
+411
@@ -0,0 +1,411 @@
|
||||
use crate::utils::{measure_time, NetworkError, Result, TestResult};
|
||||
use hickory_client::op::{Message, MessageType, OpCode, Query};
|
||||
use hickory_client::rr::{Name, RecordType};
|
||||
use reqwest::Client;
|
||||
use serde_json::Value;
|
||||
use std::str::FromStr;
|
||||
use std::time::Duration;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DohTest {
|
||||
pub domain: String,
|
||||
pub record_type: RecordType,
|
||||
pub provider: DohProvider,
|
||||
pub timeout: Duration,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DohProvider {
|
||||
pub name: &'static str,
|
||||
pub url: &'static str,
|
||||
pub description: &'static str,
|
||||
pub format: DohFormat,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum DohFormat {
|
||||
Json, // Google, Cloudflare style
|
||||
WireFormat, // Quad9, AdGuard, OpenDNS style
|
||||
}
|
||||
|
||||
// Comprehensive DoH providers list
|
||||
pub const DOH_PROVIDERS: &[DohProvider] = &[
|
||||
// Google DNS - Wire format (default/standard)
|
||||
DohProvider {
|
||||
name: "Google",
|
||||
url: "https://dns.google/dns-query",
|
||||
description: "Google Public DNS (8.8.8.8)",
|
||||
format: DohFormat::WireFormat,
|
||||
},
|
||||
// Google DNS - JSON format (special variant)
|
||||
DohProvider {
|
||||
name: "Google-JSON",
|
||||
url: "https://dns.google/resolve",
|
||||
description: "Google Public DNS (8.8.8.8) - JSON variant",
|
||||
format: DohFormat::Json,
|
||||
},
|
||||
// Cloudflare DNS - Wire format variants (default/standard)
|
||||
DohProvider {
|
||||
name: "Cloudflare",
|
||||
url: "https://1.1.1.1/dns-query",
|
||||
description: "Cloudflare DNS Primary (1.1.1.1)",
|
||||
format: DohFormat::WireFormat,
|
||||
},
|
||||
DohProvider {
|
||||
name: "Cloudflare-Family",
|
||||
url: "https://1.1.1.2/dns-query",
|
||||
description: "Cloudflare for Families (1.1.1.2) - Blocks malware/adult",
|
||||
format: DohFormat::WireFormat,
|
||||
},
|
||||
DohProvider {
|
||||
name: "Cloudflare-Security",
|
||||
url: "https://1.1.1.3/dns-query",
|
||||
description: "Cloudflare for Families (1.1.1.3) - Blocks malware only",
|
||||
format: DohFormat::WireFormat,
|
||||
},
|
||||
// Cloudflare DNS - JSON format variants (special variants)
|
||||
DohProvider {
|
||||
name: "Cloudflare-JSON",
|
||||
url: "https://1.1.1.1/dns-query",
|
||||
description: "Cloudflare DNS Primary (1.1.1.1) - JSON variant",
|
||||
format: DohFormat::Json,
|
||||
},
|
||||
DohProvider {
|
||||
name: "Cloudflare-Family-JSON",
|
||||
url: "https://1.1.1.2/dns-query",
|
||||
description: "Cloudflare for Families (1.1.1.2) - Blocks malware/adult - JSON variant",
|
||||
format: DohFormat::Json,
|
||||
},
|
||||
DohProvider {
|
||||
name: "Cloudflare-Security-JSON",
|
||||
url: "https://1.1.1.3/dns-query",
|
||||
description: "Cloudflare for Families (1.1.1.3) - Blocks malware only - JSON variant",
|
||||
format: DohFormat::Json,
|
||||
},
|
||||
// Quad9 DNS - All variants
|
||||
DohProvider {
|
||||
name: "Quad9",
|
||||
url: "https://dns.quad9.net/dns-query",
|
||||
description: "Quad9 Secure (9.9.9.9) - Blocks malicious domains",
|
||||
format: DohFormat::WireFormat,
|
||||
},
|
||||
DohProvider {
|
||||
name: "Quad9-Unsecured",
|
||||
url: "https://dns10.quad9.net/dns-query",
|
||||
description: "Quad9 Unsecured (9.9.9.10) - No domain blocking",
|
||||
format: DohFormat::WireFormat,
|
||||
},
|
||||
DohProvider {
|
||||
name: "Quad9-ECS",
|
||||
url: "https://dns11.quad9.net/dns-query",
|
||||
description: "Quad9 ECS (9.9.9.11) - Blocks malicious + EDNS Client Subnet",
|
||||
format: DohFormat::WireFormat,
|
||||
},
|
||||
// OpenDNS
|
||||
DohProvider {
|
||||
name: "OpenDNS",
|
||||
url: "https://doh.opendns.com/dns-query",
|
||||
description: "OpenDNS Standard (208.67.222.222)",
|
||||
format: DohFormat::WireFormat,
|
||||
},
|
||||
DohProvider {
|
||||
name: "OpenDNS-Family",
|
||||
url: "https://doh.familyshield.opendns.com/dns-query",
|
||||
description: "OpenDNS FamilyShield (208.67.222.123) - Blocks adult content",
|
||||
format: DohFormat::WireFormat,
|
||||
},
|
||||
// AdGuard DNS - All variants
|
||||
DohProvider {
|
||||
name: "AdGuard",
|
||||
url: "https://dns.adguard.com/dns-query",
|
||||
description: "AdGuard DNS (94.140.14.14) - Blocks ads and trackers",
|
||||
format: DohFormat::WireFormat,
|
||||
},
|
||||
DohProvider {
|
||||
name: "AdGuard-Family",
|
||||
url: "https://dns-family.adguard.com/dns-query",
|
||||
description: "AdGuard DNS Family (94.140.14.15) - Blocks ads, trackers, and adult content",
|
||||
format: DohFormat::WireFormat,
|
||||
},
|
||||
DohProvider {
|
||||
name: "AdGuard-Unfiltered",
|
||||
url: "https://dns-unfiltered.adguard.com/dns-query",
|
||||
description: "AdGuard DNS Unfiltered (94.140.14.140) - No filtering",
|
||||
format: DohFormat::WireFormat,
|
||||
},
|
||||
];
|
||||
|
||||
impl DohTest {
|
||||
pub fn new(domain: String, record_type: RecordType, provider: DohProvider) -> Self {
|
||||
Self {
|
||||
domain,
|
||||
record_type,
|
||||
provider,
|
||||
timeout: Duration::from_secs(10),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_timeout(mut self, timeout: Duration) -> Self {
|
||||
self.timeout = timeout;
|
||||
self
|
||||
}
|
||||
|
||||
pub async fn run(&self) -> TestResult {
|
||||
let test_name = format!(
|
||||
"DoH {:?} query for {} via {}",
|
||||
self.record_type, self.domain, self.provider.name
|
||||
);
|
||||
|
||||
let (duration, result) = measure_time(|| async { self.query_doh().await }).await;
|
||||
|
||||
match result {
|
||||
Ok(response) => TestResult::new(test_name).success(duration, response),
|
||||
Err(error) => TestResult::new(test_name).failure(duration, error),
|
||||
}
|
||||
}
|
||||
|
||||
async fn query_doh(&self) -> Result<String> {
|
||||
let client = Client::builder()
|
||||
.timeout(self.timeout)
|
||||
.user_agent("NetTest/1.0")
|
||||
.build()
|
||||
.map_err(|e| NetworkError::Other(format!("Failed to create HTTP client: {}", e)))?;
|
||||
|
||||
match self.provider.format {
|
||||
DohFormat::Json => self.query_doh_json(&client).await,
|
||||
DohFormat::WireFormat => self.query_doh_wire_format(&client).await,
|
||||
}
|
||||
}
|
||||
|
||||
async fn query_doh_json(&self, client: &Client) -> Result<String> {
|
||||
// Build URL with standard DoH parameters (Google/Cloudflare style)
|
||||
let url = format!(
|
||||
"{}?name={}&type={}",
|
||||
self.provider.url,
|
||||
self.domain,
|
||||
self.record_type_to_number()
|
||||
);
|
||||
|
||||
let response = client
|
||||
.get(&url)
|
||||
.header("Accept", "application/dns-json")
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| NetworkError::Other(format!("DoH request failed: {}", e)))?;
|
||||
|
||||
if !response.status().is_success() {
|
||||
return Err(NetworkError::Other(format!(
|
||||
"DoH server returned status: {}",
|
||||
response.status()
|
||||
)));
|
||||
}
|
||||
|
||||
let json_response: Value = response
|
||||
.json()
|
||||
.await
|
||||
.map_err(|e| NetworkError::Other(format!("Failed to parse DoH response: {}", e)))?;
|
||||
|
||||
self.parse_doh_response(&json_response)
|
||||
}
|
||||
|
||||
async fn query_doh_wire_format(&self, client: &Client) -> Result<String> {
|
||||
// Create DNS query packet
|
||||
let dns_packet = self.create_dns_query_packet()?;
|
||||
|
||||
let response = client
|
||||
.post(self.provider.url)
|
||||
.header("Content-Type", "application/dns-message")
|
||||
.header("Accept", "application/dns-message")
|
||||
.body(dns_packet)
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| NetworkError::Other(format!("DoH request failed: {}", e)))?;
|
||||
|
||||
if !response.status().is_success() {
|
||||
return Err(NetworkError::Other(format!(
|
||||
"DoH server returned status: {}",
|
||||
response.status()
|
||||
)));
|
||||
}
|
||||
|
||||
let response_bytes = response
|
||||
.bytes()
|
||||
.await
|
||||
.map_err(|e| NetworkError::Other(format!("Failed to read DoH response: {}", e)))?;
|
||||
|
||||
self.parse_dns_response_packet(&response_bytes)
|
||||
}
|
||||
|
||||
fn create_dns_query_packet(&self) -> Result<Vec<u8>> {
|
||||
// Create a DNS query message
|
||||
let name = Name::from_str(&self.domain)
|
||||
.map_err(|e| NetworkError::Other(format!("Invalid domain name: {}", e)))?;
|
||||
|
||||
let query = Query::query(name, self.record_type);
|
||||
let mut message = Message::new();
|
||||
message
|
||||
.set_message_type(MessageType::Query)
|
||||
.set_op_code(OpCode::Query)
|
||||
.set_recursion_desired(true)
|
||||
.add_query(query);
|
||||
|
||||
// Serialize to bytes
|
||||
message
|
||||
.to_vec()
|
||||
.map_err(|e| NetworkError::Other(format!("Failed to serialize DNS query: {}", e)))
|
||||
}
|
||||
|
||||
fn parse_dns_response_packet(&self, response_bytes: &[u8]) -> Result<String> {
|
||||
// Parse the DNS response packet
|
||||
let message = Message::from_vec(response_bytes).map_err(|e| {
|
||||
NetworkError::Other(format!("Failed to parse DNS response packet: {}", e))
|
||||
})?;
|
||||
|
||||
// Check response code
|
||||
if message.response_code() != hickory_client::op::ResponseCode::NoError {
|
||||
return Err(NetworkError::DnsResolution(format!(
|
||||
"DNS query failed with response code: {:?}",
|
||||
message.response_code()
|
||||
)));
|
||||
}
|
||||
|
||||
let answers = message.answers();
|
||||
if answers.is_empty() {
|
||||
return Ok(format!("{:?} records: (none found)", self.record_type));
|
||||
}
|
||||
|
||||
let mut records = Vec::new();
|
||||
for answer in answers {
|
||||
if answer.record_type() == self.record_type {
|
||||
match answer.data() {
|
||||
Some(rdata) => records.push(rdata.to_string()),
|
||||
None => continue,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if records.is_empty() {
|
||||
Ok(format!("{:?} records: (none found)", self.record_type))
|
||||
} else {
|
||||
Ok(format!(
|
||||
"{:?} records: {}",
|
||||
self.record_type,
|
||||
records.join(", ")
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn record_type_to_number(&self) -> u16 {
|
||||
match self.record_type {
|
||||
RecordType::A => 1,
|
||||
RecordType::NS => 2,
|
||||
RecordType::CNAME => 5,
|
||||
RecordType::SOA => 6,
|
||||
RecordType::PTR => 12,
|
||||
RecordType::MX => 15,
|
||||
RecordType::TXT => 16,
|
||||
RecordType::AAAA => 28,
|
||||
_ => 1, // Default to A record
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_doh_response(&self, response: &Value) -> Result<String> {
|
||||
let status = response["Status"]
|
||||
.as_u64()
|
||||
.ok_or_else(|| NetworkError::Other("Invalid DoH response format".to_string()))?;
|
||||
|
||||
if status != 0 {
|
||||
let status_text = match status {
|
||||
1 => "Format Error",
|
||||
2 => "Server Failure",
|
||||
3 => "Name Error (NXDOMAIN)",
|
||||
4 => "Not Implemented",
|
||||
5 => "Refused",
|
||||
_ => "Unknown Error",
|
||||
};
|
||||
return Err(NetworkError::DnsResolution(format!(
|
||||
"DoH query failed with status {}: {}",
|
||||
status, status_text
|
||||
)));
|
||||
}
|
||||
|
||||
let empty_vec = Vec::new();
|
||||
let answers = response["Answer"].as_array().unwrap_or(&empty_vec);
|
||||
|
||||
if answers.is_empty() {
|
||||
return Ok(format!("{:?} records: (none found)", self.record_type));
|
||||
}
|
||||
|
||||
let mut records = Vec::new();
|
||||
for answer in answers {
|
||||
if let Some(data) = answer["data"].as_str() {
|
||||
records.push(data.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
if records.is_empty() {
|
||||
Ok(format!("{:?} records: (none found)", self.record_type))
|
||||
} else {
|
||||
Ok(format!(
|
||||
"{:?} records: {}",
|
||||
self.record_type,
|
||||
records.join(", ")
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn test_doh_providers(domain: &str, record_type: RecordType) -> Vec<TestResult> {
|
||||
let mut results = Vec::new();
|
||||
|
||||
for provider in DOH_PROVIDERS {
|
||||
let test = DohTest::new(domain.to_string(), record_type, provider.clone());
|
||||
results.push(test.run().await);
|
||||
}
|
||||
|
||||
results
|
||||
}
|
||||
|
||||
pub async fn test_doh_comprehensive(domain: &str) -> Vec<TestResult> {
|
||||
let mut results = Vec::new();
|
||||
|
||||
// Test A records with JSON-compatible providers (Google and Cloudflare variants)
|
||||
let json_compatible_providers = [
|
||||
&DOH_PROVIDERS[0], // Google
|
||||
&DOH_PROVIDERS[1], // Cloudflare Primary
|
||||
&DOH_PROVIDERS[2], // Cloudflare-Family
|
||||
&DOH_PROVIDERS[3], // Cloudflare-Security
|
||||
];
|
||||
|
||||
for provider in json_compatible_providers {
|
||||
let test = DohTest::new(domain.to_string(), RecordType::A, provider.clone());
|
||||
results.push(test.run().await);
|
||||
}
|
||||
|
||||
// Test AAAA records with the same JSON-compatible providers
|
||||
for provider in json_compatible_providers {
|
||||
let test = DohTest::new(domain.to_string(), RecordType::AAAA, provider.clone());
|
||||
results.push(test.run().await);
|
||||
}
|
||||
|
||||
results
|
||||
}
|
||||
|
||||
pub fn get_provider_by_name(name: &str) -> Option<&'static DohProvider> {
|
||||
DOH_PROVIDERS
|
||||
.iter()
|
||||
.find(|provider| provider.name.to_lowercase() == name.to_lowercase())
|
||||
}
|
||||
|
||||
pub fn list_doh_providers() -> Vec<TestResult> {
|
||||
let mut results = Vec::new();
|
||||
|
||||
for provider in DOH_PROVIDERS {
|
||||
let details = format!("{} - {}", provider.url, provider.description);
|
||||
let result = TestResult::new(format!("DoH Provider: {}", provider.name))
|
||||
.success(Duration::from_millis(0), details);
|
||||
results.push(result);
|
||||
}
|
||||
|
||||
results
|
||||
}
|
||||
+34
-11
@@ -10,9 +10,11 @@ use tokio::net::TcpStream;
|
||||
use tokio::time::timeout;
|
||||
|
||||
pub mod categories;
|
||||
pub mod doh;
|
||||
pub mod queries;
|
||||
|
||||
pub use categories::*;
|
||||
pub use doh::*;
|
||||
pub use queries::*;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -614,18 +616,35 @@ pub async fn test_common_dns_servers(domain: &str, record_type: RecordType) -> V
|
||||
);
|
||||
results.push(system_result);
|
||||
|
||||
// Then test external DNS servers
|
||||
// Test all traditional DNS servers (UDP/TCP)
|
||||
let servers = [
|
||||
"8.8.8.8:53", // Google Primary
|
||||
"8.8.4.4:53", // Google Secondary
|
||||
"1.1.1.1:53", // Cloudflare Primary
|
||||
"1.0.0.1:53", // Cloudflare Secondary
|
||||
"9.9.9.9:53", // Quad9 Primary
|
||||
"149.112.112.112:53", // Quad9 Secondary
|
||||
"208.67.222.222:53", // OpenDNS Primary
|
||||
"208.67.220.220:53", // OpenDNS Secondary
|
||||
"1.1.1.2:53", // Cloudflare Family (blocks malware/adult)
|
||||
"1.1.1.3:53", // Cloudflare Family (blocks malware)
|
||||
// Google DNS
|
||||
"8.8.8.8:53", // Google Primary
|
||||
"8.8.4.4:53", // Google Secondary
|
||||
// Cloudflare DNS - All variants
|
||||
"1.1.1.1:53", // Cloudflare Primary (standard)
|
||||
"1.0.0.1:53", // Cloudflare Secondary (standard)
|
||||
"1.1.1.2:53", // Cloudflare Family (blocks malware/adult)
|
||||
"1.1.1.3:53", // Cloudflare Family (blocks malware only)
|
||||
// Quad9 DNS - All variants
|
||||
"9.9.9.9:53", // Quad9 Primary (blocks malicious domains)
|
||||
"149.112.112.112:53", // Quad9 Secondary (blocks malicious domains)
|
||||
"9.9.9.10:53", // Quad9 Unsecured (no blocking)
|
||||
"149.112.112.10:53", // Quad9 Unsecured Secondary (no blocking)
|
||||
"9.9.9.11:53", // Quad9 Secured + ECS (blocks malicious + EDNS)
|
||||
"149.112.112.11:53", // Quad9 Secured + ECS Secondary
|
||||
// OpenDNS
|
||||
"208.67.222.222:53", // OpenDNS Primary
|
||||
"208.67.220.220:53", // OpenDNS Secondary
|
||||
"208.67.222.123:53", // OpenDNS FamilyShield Primary
|
||||
"208.67.220.123:53", // OpenDNS FamilyShield Secondary
|
||||
// AdGuard DNS - All variants
|
||||
"94.140.14.14:53", // AdGuard DNS Primary (blocks ads/trackers)
|
||||
"94.140.15.15:53", // AdGuard DNS Secondary (blocks ads/trackers)
|
||||
"94.140.14.15:53", // AdGuard DNS Family Primary (blocks ads/trackers/adult)
|
||||
"94.140.15.16:53", // AdGuard DNS Family Secondary (blocks ads/trackers/adult)
|
||||
"94.140.14.140:53", // AdGuard DNS Unfiltered Primary
|
||||
"94.140.14.141:53", // AdGuard DNS Unfiltered Secondary
|
||||
];
|
||||
|
||||
for server_str in &servers {
|
||||
@@ -635,6 +654,10 @@ pub async fn test_common_dns_servers(domain: &str, record_type: RecordType) -> V
|
||||
}
|
||||
}
|
||||
|
||||
// Add DNS-over-HTTPS tests (all available DoH providers)
|
||||
let doh_results = crate::dns::doh::test_doh_providers(domain, record_type).await;
|
||||
results.extend(doh_results);
|
||||
|
||||
results
|
||||
}
|
||||
|
||||
|
||||
@@ -15,11 +15,25 @@ pub async fn comprehensive_dns_test(domain: &str) -> Vec<TestResult> {
|
||||
RecordType::SOA,
|
||||
];
|
||||
|
||||
// Test all record types using system DNS resolver
|
||||
// This focuses on comprehensive record type analysis rather than server testing
|
||||
for record_type in &record_types {
|
||||
let test = DnsTest::new(domain.to_string(), *record_type);
|
||||
results.push(test.run().await);
|
||||
}
|
||||
|
||||
// Add a few key DoH tests for comparison (JSON-compatible providers only)
|
||||
let key_doh_providers = [
|
||||
&crate::dns::doh::DOH_PROVIDERS[0], // Google
|
||||
&crate::dns::doh::DOH_PROVIDERS[1], // Cloudflare Primary
|
||||
];
|
||||
|
||||
for provider in key_doh_providers {
|
||||
let test =
|
||||
crate::dns::doh::DohTest::new(domain.to_string(), RecordType::A, provider.clone());
|
||||
results.push(test.run().await);
|
||||
}
|
||||
|
||||
results
|
||||
}
|
||||
|
||||
|
||||
+32
@@ -184,6 +184,38 @@ async fn handle_dns_command(command: cli::DnsCommands, timeout: Duration) -> Vec
|
||||
dns::queries::comprehensive_dns_test(&domain).await
|
||||
}
|
||||
cli::DnsCommands::Large { domain } => dns::queries::test_large_dns_queries(&domain).await,
|
||||
cli::DnsCommands::Doh {
|
||||
domain,
|
||||
provider,
|
||||
record_type,
|
||||
} => {
|
||||
let mut results = Vec::new();
|
||||
for rt in record_type.to_record_type() {
|
||||
match &provider {
|
||||
Some(provider_name) => {
|
||||
if let Some(provider) = dns::doh::get_provider_by_name(provider_name) {
|
||||
let test = dns::doh::DohTest::new(domain.clone(), rt, provider.clone());
|
||||
results.push(test.run().await);
|
||||
} else {
|
||||
results.push(TestResult::new("DoH Provider Error".to_string()).failure(
|
||||
Duration::from_millis(0),
|
||||
NetworkError::Other(format!(
|
||||
"Unknown DoH provider: {}. Available providers: google, google-json, cloudflare, cloudflare-family, cloudflare-security, cloudflare-json, cloudflare-family-json, cloudflare-security-json, quad9, quad9-unsecured, quad9-ecs, opendns, opendns-family, adguard, adguard-family, adguard-unfiltered",
|
||||
provider_name
|
||||
)),
|
||||
));
|
||||
break; // Don't repeat error for each record type
|
||||
}
|
||||
}
|
||||
None => {
|
||||
let provider_results = dns::doh::test_doh_providers(&domain, rt).await;
|
||||
results.extend(provider_results);
|
||||
}
|
||||
}
|
||||
}
|
||||
results
|
||||
}
|
||||
cli::DnsCommands::DohProviders => dns::doh::list_doh_providers(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user