use std::{
net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr},
str::FromStr,
time::Duration,
};
use anyhow::{Context, anyhow};
use clap::Args;
use hickory_resolver::config::{CLOUDFLARE_IPS, LookupIpStrategy};
use humantime::parse_duration;
use strum::EnumString;
use crate::types::http::Error;
pub const DEFAULT_RESOLVERS: &[IpAddr] = &[
IpAddr::V4(Ipv4Addr::new(1, 1, 1, 1)), IpAddr::V4(Ipv4Addr::new(8, 8, 8, 8)), IpAddr::V4(Ipv4Addr::new(9, 9, 9, 9)), IpAddr::V6(Ipv6Addr::new(0x2606, 0x4700, 0x4700, 0, 0, 0, 0, 0x1111)), IpAddr::V6(Ipv6Addr::new(0x2001, 0x4860, 0x4860, 0, 0, 0, 0, 0x8888)), IpAddr::V6(Ipv6Addr::new(0x2620, 0x00fe, 0, 0, 0, 0, 0, 0x00fe)), ];
#[derive(Clone, Copy, Debug, PartialEq, Eq, EnumString)]
#[strum(serialize_all = "snake_case")]
pub enum LookupStrategy {
Ipv4Only,
Ipv6Only,
Ipv4AndIpv6,
Ipv6ThenIpv4,
Ipv4ThenIpv6,
}
impl From<LookupStrategy> for LookupIpStrategy {
fn from(value: LookupStrategy) -> Self {
match value {
LookupStrategy::Ipv4Only => Self::Ipv4Only,
LookupStrategy::Ipv6Only => Self::Ipv6Only,
LookupStrategy::Ipv4AndIpv6 => Self::Ipv4AndIpv6,
LookupStrategy::Ipv6ThenIpv4 => Self::Ipv6thenIpv4,
LookupStrategy::Ipv4ThenIpv6 => Self::Ipv4thenIpv6,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Protocol {
Clear(u16),
Tls(u16),
Https(u16),
}
impl FromStr for Protocol {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut iter = s.split(":");
let (proto, port) = (iter.next().unwrap(), iter.next());
let port = if let Some(v) = port {
Some(v.parse::<u16>().context("unable to parse port")?)
} else {
None
};
match proto {
"clear" => Ok(Self::Clear(port.unwrap_or(53))),
"tls" => Ok(Self::Tls(port.unwrap_or(853))),
"https" => Ok(Self::Https(port.unwrap_or(443))),
_ => Err(anyhow!("unknown DNS protocol: {proto}").into()),
}
}
}
#[derive(Debug, Clone)]
pub struct Options {
pub protocol: Protocol,
pub servers: Vec<IpAddr>,
pub lookup_ip_strategy: LookupIpStrategy,
pub cache_size: usize,
pub timeout: Duration,
pub tls_name: String,
pub dnssec_disabled: bool,
}
impl Default for Options {
fn default() -> Self {
Self {
protocol: Protocol::Clear(53),
servers: CLOUDFLARE_IPS.into(),
lookup_ip_strategy: LookupIpStrategy::Ipv4AndIpv6,
cache_size: 1024,
timeout: Duration::from_secs(3),
tls_name: "cloudflare-dns.com".into(),
dnssec_disabled: false,
}
}
}
pub struct SocketAddrs {
pub iter: Box<dyn Iterator<Item = IpAddr> + Send>,
}
impl Iterator for SocketAddrs {
type Item = SocketAddr;
fn next(&mut self) -> Option<Self::Item> {
self.iter.next().map(|ip_addr| SocketAddr::new(ip_addr, 0))
}
}
#[derive(Args)]
pub struct DnsCli {
#[clap(env, long, value_delimiter = ',', default_values_t = DEFAULT_RESOLVERS)]
pub dns_servers: Vec<IpAddr>,
#[clap(env, long, default_value = "tls")]
pub dns_protocol: Protocol,
#[clap(env, long, default_value = "2048")]
pub dns_cache_size: usize,
#[clap(env, long, default_value = "5s", value_parser = parse_duration)]
pub dns_timeout: Duration,
#[clap(env, long, default_value = "cloudflare-dns.com")]
pub dns_tls_name: String,
#[clap(env, long, default_value = "ipv4_and_ipv6")]
pub dns_lookup_strategy: LookupStrategy,
#[clap(env, long)]
pub dns_dnssec_disabled: bool,
}
impl From<&DnsCli> for Options {
fn from(c: &DnsCli) -> Self {
Self {
protocol: c.dns_protocol,
servers: c.dns_servers.clone(),
lookup_ip_strategy: c.dns_lookup_strategy.into(),
cache_size: c.dns_cache_size,
timeout: c.dns_timeout,
tls_name: c.dns_tls_name.clone(),
dnssec_disabled: c.dns_dnssec_disabled,
}
}
}