#![cfg_attr(docsrs, feature(doc_cfg))]
#![cfg_attr(doc, deny(rustdoc::all))]
#![forbid(trivial_casts, trivial_numeric_casts, unstable_features)]
#![deny(
unused,
missing_docs,
rust_2018_idioms,
future_incompatible,
clippy::all,
clippy::correctness,
clippy::style,
clippy::complexity,
clippy::perf,
clippy::pedantic,
clippy::cargo
)]
#![allow(clippy::needless_pass_by_value)]
mod error;
#[cfg(any(
all(feature = "dns-resolver", not(feature = "tokio-dns-resolver")),
all(feature = "http-resolver", not(feature = "tokio-http-resolver"))
))]
compile_error!("tokio is not enabled and is the only supported runtime currently - consider creating a PR or issue");
#[cfg(feature = "dns-resolver")]
#[cfg_attr(docsrs, doc(cfg(feature = "dns-resolver")))]
pub mod dns;
#[cfg(feature = "http-resolver")]
#[cfg_attr(docsrs, doc(cfg(feature = "http-resolver")))]
pub mod http;
use std::any::Any;
use std::net::IpAddr;
#[cfg(any(feature = "dns-resolver", feature = "http-resolver"))]
use std::net::{Ipv4Addr, Ipv6Addr};
use std::pin::Pin;
use std::slice;
use std::task::{Context, Poll};
use futures_core::Stream;
use futures_util::stream::{self, BoxStream, StreamExt, TryStreamExt};
use futures_util::{future, ready};
use pin_project_lite::pin_project;
use tracing::trace_span;
use tracing_futures::Instrument;
pub use crate::error::Error;
pub type Details = Box<dyn Any + Send + Sync + 'static>;
pub type Resolutions<'a> = BoxStream<'a, Result<(IpAddr, Details), Error>>;
#[cfg(any(feature = "dns-resolver", feature = "http-resolver"))]
#[cfg_attr(
docsrs,
doc(cfg(any(feature = "dns-resolver", feature = "http-resolver")))
)]
pub const ALL: &dyn crate::Resolver<'static> = &&[
#[cfg(feature = "dns-resolver")]
dns::ALL,
#[cfg(feature = "http-resolver")]
http::ALL,
];
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Version {
V4,
V6,
Any,
}
impl Version {
#[must_use]
pub fn matches(self, addr: IpAddr) -> bool {
self == Version::Any
|| (self == Version::V4 && addr.is_ipv4())
|| (self == Version::V6 && addr.is_ipv6())
}
}
#[cfg(any(feature = "dns-resolver", feature = "http-resolver"))]
#[cfg_attr(
docsrs,
doc(cfg(any(feature = "dns-resolver", feature = "http-resolver")))
)]
pub async fn addr() -> Option<IpAddr> {
addr_with(ALL, Version::Any).await
}
#[cfg(any(feature = "dns-resolver", feature = "http-resolver"))]
#[cfg_attr(
docsrs,
doc(cfg(any(feature = "dns-resolver", feature = "http-resolver")))
)]
pub async fn addr_v4() -> Option<Ipv4Addr> {
addr_with(ALL, Version::V4).await.map(|addr| match addr {
IpAddr::V4(addr) => addr,
IpAddr::V6(_) => unreachable!(),
})
}
#[cfg(any(feature = "dns-resolver", feature = "http-resolver"))]
#[cfg_attr(
docsrs,
doc(cfg(any(feature = "dns-resolver", feature = "http-resolver")))
)]
pub async fn addr_v6() -> Option<Ipv6Addr> {
addr_with(ALL, Version::V6).await.map(|addr| match addr {
IpAddr::V6(addr) => addr,
IpAddr::V4(_) => unreachable!(),
})
}
pub async fn addr_with(resolver: impl Resolver<'_>, version: Version) -> Option<IpAddr> {
addr_with_details(resolver, version)
.await
.map(|(addr, _)| addr)
}
pub async fn addr_with_details(
resolver: impl Resolver<'_>,
version: Version,
) -> Option<(IpAddr, Details)> {
resolve(resolver, version)
.filter_map(|result| future::ready(result.ok()))
.next()
.await
}
pub fn resolve<'r>(resolver: impl Resolver<'r>, version: Version) -> Resolutions<'r> {
let stream = resolver.resolve(version).and_then(move |(addr, details)| {
let result = if version.matches(addr) {
Ok((addr, details))
} else {
Err(Error::Version)
};
future::ready(result)
});
Box::pin(stream.instrument(trace_span!("resolve public ip address")))
}
pub trait Resolver<'a>: Send + Sync {
fn resolve(&self, version: Version) -> Resolutions<'a>;
}
impl<'r> Resolver<'r> for &'r dyn Resolver<'r> {
fn resolve(&self, version: Version) -> Resolutions<'r> {
(**self).resolve(version)
}
}
impl<'r, R> Resolver<'r> for &'r [R]
where
R: Resolver<'r>,
{
fn resolve(&self, version: Version) -> Resolutions<'r> {
pin_project! {
struct DynSliceResolver<'r, R> {
version: Version,
resolvers: slice::Iter<'r, R>,
#[pin]
stream: Resolutions<'r>,
}
}
impl<'r, R> Stream for DynSliceResolver<'r, R>
where
R: Resolver<'r>,
{
type Item = Result<(IpAddr, Details), Error>;
fn poll_next(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<Self::Item>> {
match ready!(self.as_mut().project().stream.poll_next(cx)) {
Some(o) => Poll::Ready(Some(o)),
None => self.resolvers.next().map_or(Poll::Ready(None), |next| {
self.stream = next.resolve(self.version);
self.project().stream.poll_next(cx)
}),
}
}
}
let mut resolvers = self.iter();
let first_resolver = resolvers.next();
Box::pin(DynSliceResolver {
version,
resolvers,
stream: match first_resolver {
Some(first) => first.resolve(version),
None => Box::pin(stream::empty()),
},
})
}
}
macro_rules! resolver_array {
() => {
resolver_array!(
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16
);
};
($($n:expr),*) => {
$(
impl<'r> Resolver<'r> for &'r [&'r dyn Resolver<'r>; $n] {
fn resolve(&self, version: Version) -> Resolutions<'r> {
Resolver::resolve(&&self[..], version)
}
}
)*
}
}
resolver_array!();