use anyhow::{self as ah, format_err as err};
use std::net::IpAddr;
#[cfg(feature = "hickory-resolver")]
mod hickory;
#[cfg(feature = "hickory-resolver")]
use hickory as backend;
#[cfg(all(feature = "libc-resolver", not(feature = "hickory-resolver")))]
mod dns_lookup;
#[cfg(all(feature = "libc-resolver", not(feature = "hickory-resolver")))]
use dns_lookup as backend;
#[cfg(not(any(feature = "hickory-resolver", feature = "libc-resolver")))]
compile_error!(
"At least one resolver backend must be enabled. Enable the 'hickory-resolver' or 'libc-resolver' feature."
);
#[cfg(all(feature = "hickory-resolver", feature = "libc-resolver"))]
#[deprecated(
note = "Feature 'hickory-resolver' and 'libc-resolver' enabled. Using 'hickory-resolver'."
)]
const WARNING_HICKORY_AND_LIBC_RESOLVER_ENABLED: () = ();
#[cfg(all(feature = "hickory-resolver", feature = "libc-resolver"))]
const _: () = WARNING_HICKORY_AND_LIBC_RESOLVER_ENABLED;
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
#[non_exhaustive]
pub enum ResMode {
#[default]
Ipv6,
Ipv4,
}
#[derive(Clone, Debug)]
#[non_exhaustive]
pub struct ResSrv {
#[cfg_attr(not(feature = "hickory-resolver"), allow(dead_code))]
system: bool,
#[cfg_attr(not(feature = "hickory-resolver"), allow(dead_code))]
quad9: bool,
#[cfg_attr(not(feature = "hickory-resolver"), allow(dead_code))]
google: bool,
#[cfg_attr(not(feature = "hickory-resolver"), allow(dead_code))]
cloudflare: bool,
}
impl Default for ResSrv {
fn default() -> Self {
Self {
system: true,
quad9: false,
google: false,
cloudflare: false,
}
}
}
impl ResSrv {
#[must_use]
pub fn system(self, enable: bool) -> Self {
Self {
system: enable,
..self
}
}
#[must_use]
pub fn quad9(self, enable: bool) -> Self {
Self {
quad9: enable,
..self
}
}
#[must_use]
pub fn google(self, enable: bool) -> Self {
Self {
google: enable,
..self
}
}
#[must_use]
pub fn cloudflare(self, enable: bool) -> Self {
Self {
cloudflare: enable,
..self
}
}
}
#[derive(Clone, Debug)]
#[non_exhaustive]
pub struct ResCrypt {
#[cfg_attr(not(feature = "hickory-resolver"), allow(dead_code))]
tls: bool,
#[cfg_attr(not(feature = "hickory-resolver"), allow(dead_code))]
https: bool,
#[cfg_attr(not(feature = "hickory-resolver"), allow(dead_code))]
unencrypted: bool,
}
impl Default for ResCrypt {
fn default() -> Self {
Self {
tls: true,
https: true,
unencrypted: true,
}
}
}
impl ResCrypt {
#[must_use]
pub fn tls(self, enable: bool) -> Self {
Self {
tls: enable,
..self
}
}
#[must_use]
pub fn https(self, enable: bool) -> Self {
Self {
https: enable,
..self
}
}
#[must_use]
pub fn unencrypted(self, enable: bool) -> Self {
Self {
unencrypted: enable,
..self
}
}
}
#[derive(Clone, Debug, Default)]
#[non_exhaustive]
pub struct ResConf {
mode: ResMode,
#[cfg_attr(not(feature = "hickory-resolver"), allow(dead_code))]
srv: ResSrv,
#[cfg_attr(not(feature = "hickory-resolver"), allow(dead_code))]
crypt: ResCrypt,
#[cfg_attr(not(feature = "hickory-resolver"), allow(dead_code))]
suppress_warnings: bool,
}
impl ResConf {
#[must_use]
pub fn mode(self, mode: ResMode) -> Self {
Self { mode, ..self }
}
#[must_use]
pub fn srv(self, srv: ResSrv) -> Self {
Self { srv, ..self }
}
#[must_use]
pub fn crypt(self, crypt: ResCrypt) -> Self {
Self { crypt, ..self }
}
#[must_use]
pub fn suppress_warnings(self, suppress_warnings: bool) -> Self {
Self {
suppress_warnings,
..self
}
}
}
#[must_use]
pub fn is_ipv4_addr(host: &str) -> bool {
host.parse::<IpAddr>().is_ok_and(|a| a.is_ipv4())
}
#[must_use]
pub fn is_ipv6_addr(host: &str) -> bool {
host.parse::<IpAddr>().is_ok_and(|a| a.is_ipv6())
}
pub async fn resolve(host: &str, cfg: &ResConf) -> ah::Result<IpAddr> {
if let Ok(addr) = host.parse::<IpAddr>() {
match cfg.mode {
ResMode::Ipv4 if !addr.is_ipv4() => {
return Err(err!(
"Supplied a raw IPv6 address, but resolution mode is set to IPv4"
));
}
ResMode::Ipv6 if !addr.is_ipv6() => {
return Err(err!(
"Supplied a raw IPv4 address, but resolution mode is set to IPv6"
));
}
_ => (),
}
return Ok(addr);
}
backend::resolve(host, cfg).await
}