1#![cfg_attr(docsrs, feature(doc_cfg))]
16#![cfg_attr(doc, deny(rustdoc::all))]
17#![forbid(trivial_casts, trivial_numeric_casts, unstable_features)]
18#![deny(
19 unused,
20 missing_docs,
21 rust_2018_idioms,
22 future_incompatible,
23 clippy::all,
24 clippy::correctness,
25 clippy::style,
26 clippy::complexity,
27 clippy::perf,
28 clippy::pedantic,
29 clippy::cargo
30)]
31#![allow(clippy::needless_pass_by_value)]
32
33mod error;
34
35#[cfg(any(
36 all(feature = "dns-resolver", not(feature = "tokio-dns-resolver")),
37 all(feature = "http-resolver", not(feature = "tokio-http-resolver"))
38))]
39compile_error!("tokio is not enabled and is the only supported runtime currently - consider creating a PR or issue");
40
41#[cfg(feature = "dns-resolver")]
43#[cfg_attr(docsrs, doc(cfg(feature = "dns-resolver")))]
44pub mod dns;
45
46#[cfg(feature = "http-resolver")]
48#[cfg_attr(docsrs, doc(cfg(feature = "http-resolver")))]
49pub mod http;
50
51use std::any::Any;
52use std::net::IpAddr;
53#[cfg(any(feature = "dns-resolver", feature = "http-resolver"))]
54use std::net::{Ipv4Addr, Ipv6Addr};
55use std::pin::Pin;
56use std::slice;
57use std::task::{Context, Poll};
58
59use futures_core::Stream;
60use futures_util::stream::{self, BoxStream, StreamExt, TryStreamExt};
61use futures_util::{future, ready};
62use pin_project_lite::pin_project;
63use tracing::trace_span;
64use tracing_futures::Instrument;
65
66pub use crate::error::Error;
67
68pub type Details = Box<dyn Any + Send + Sync + 'static>;
72
73pub type Resolutions<'a> = BoxStream<'a, Result<(IpAddr, Details), Error>>;
75
76#[cfg(any(feature = "dns-resolver", feature = "http-resolver"))]
78#[cfg_attr(
79 docsrs,
80 doc(cfg(any(feature = "dns-resolver", feature = "http-resolver")))
81)]
82pub const ALL: &dyn crate::Resolver<'static> = &&[
83 #[cfg(feature = "dns-resolver")]
84 dns::ALL,
85 #[cfg(feature = "http-resolver")]
86 http::ALL,
87];
88
89#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
91#[non_exhaustive]
92pub enum Version {
93 V4,
95 V6,
97 Any,
99}
100
101impl Version {
102 #[must_use]
104 pub fn matches(self, addr: IpAddr) -> bool {
105 self == Version::Any
106 || (self == Version::V4 && addr.is_ipv4())
107 || (self == Version::V6 && addr.is_ipv6())
108 }
109}
110
111#[cfg(any(feature = "dns-resolver", feature = "http-resolver"))]
118#[cfg_attr(
119 docsrs,
120 doc(cfg(any(feature = "dns-resolver", feature = "http-resolver")))
121)]
122pub async fn addr() -> Option<IpAddr> {
123 addr_with(ALL, Version::Any).await
124}
125
126#[cfg(any(feature = "dns-resolver", feature = "http-resolver"))]
132#[cfg_attr(
133 docsrs,
134 doc(cfg(any(feature = "dns-resolver", feature = "http-resolver")))
135)]
136pub async fn addr_v4() -> Option<Ipv4Addr> {
137 addr_with(ALL, Version::V4).await.map(|addr| match addr {
138 IpAddr::V4(addr) => addr,
139 IpAddr::V6(_) => unreachable!(),
140 })
141}
142
143#[cfg(any(feature = "dns-resolver", feature = "http-resolver"))]
149#[cfg_attr(
150 docsrs,
151 doc(cfg(any(feature = "dns-resolver", feature = "http-resolver")))
152)]
153pub async fn addr_v6() -> Option<Ipv6Addr> {
154 addr_with(ALL, Version::V6).await.map(|addr| match addr {
155 IpAddr::V6(addr) => addr,
156 IpAddr::V4(_) => unreachable!(),
157 })
158}
159
160pub async fn addr_with(resolver: impl Resolver<'_>, version: Version) -> Option<IpAddr> {
166 addr_with_details(resolver, version)
167 .await
168 .map(|(addr, _)| addr)
169}
170
171pub async fn addr_with_details(
177 resolver: impl Resolver<'_>,
178 version: Version,
179) -> Option<(IpAddr, Details)> {
180 resolve(resolver, version)
181 .filter_map(|result| future::ready(result.ok()))
182 .next()
183 .await
184}
185
186pub fn resolve<'r>(resolver: impl Resolver<'r>, version: Version) -> Resolutions<'r> {
191 let stream = resolver.resolve(version).and_then(move |(addr, details)| {
192 let result = if version.matches(addr) {
195 Ok((addr, details))
196 } else {
197 Err(Error::Version)
198 };
199 future::ready(result)
200 });
201 Box::pin(stream.instrument(trace_span!("resolve public ip address")))
202}
203
204pub trait Resolver<'a>: Send + Sync {
208 fn resolve(&self, version: Version) -> Resolutions<'a>;
210}
211
212impl<'r> Resolver<'r> for &'r dyn Resolver<'r> {
213 fn resolve(&self, version: Version) -> Resolutions<'r> {
214 (**self).resolve(version)
215 }
216}
217
218impl<'r, R> Resolver<'r> for &'r [R]
219where
220 R: Resolver<'r>,
221{
222 fn resolve(&self, version: Version) -> Resolutions<'r> {
223 pin_project! {
224 struct DynSliceResolver<'r, R> {
225 version: Version,
226 resolvers: slice::Iter<'r, R>,
227 #[pin]
228 stream: Resolutions<'r>,
229 }
230 }
231
232 impl<'r, R> Stream for DynSliceResolver<'r, R>
233 where
234 R: Resolver<'r>,
235 {
236 type Item = Result<(IpAddr, Details), Error>;
237
238 fn poll_next(
239 mut self: Pin<&mut Self>,
240 cx: &mut Context<'_>,
241 ) -> Poll<Option<Self::Item>> {
242 match ready!(self.as_mut().project().stream.poll_next(cx)) {
243 Some(o) => Poll::Ready(Some(o)),
244 None => self.resolvers.next().map_or(Poll::Ready(None), |next| {
245 self.stream = next.resolve(self.version);
246 self.project().stream.poll_next(cx)
247 }),
248 }
249 }
250 }
251
252 let mut resolvers = self.iter();
253 let first_resolver = resolvers.next();
254 Box::pin(DynSliceResolver {
255 version,
256 resolvers,
257 stream: match first_resolver {
258 Some(first) => first.resolve(version),
259 None => Box::pin(stream::empty()),
260 },
261 })
262 }
263}
264
265macro_rules! resolver_array {
266 () => {
267 resolver_array!(
268 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16
269 );
270 };
271 ($($n:expr),*) => {
272 $(
273 impl<'r> Resolver<'r> for &'r [&'r dyn Resolver<'r>; $n] {
274 fn resolve(&self, version: Version) -> Resolutions<'r> {
275 Resolver::resolve(&&self[..], version)
276 }
277 }
278 )*
279 }
280}
281
282resolver_array!();