mirror of
https://github.com/hl-archive-node/nanoreth.git
synced 2025-12-06 10:59:55 +00:00
chore: resolve external IP with just an http request (#8516)
This commit is contained in:
@ -12,16 +12,11 @@ description = "Helpers for working around NAT"
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
# nat
|
||||
public-ip = "0.2"
|
||||
igd-next = { workspace = true, features = ["aio_tokio"] }
|
||||
|
||||
# misc
|
||||
tracing.workspace = true
|
||||
pin-project-lite = "0.2.9"
|
||||
tokio = { workspace = true, features = ["time"] }
|
||||
thiserror.workspace = true
|
||||
futures-util.workspace = true
|
||||
reqwest.workspace = true
|
||||
serde_with = { workspace = true, optional = true }
|
||||
thiserror.workspace = true
|
||||
tokio = { workspace = true, features = ["time"] }
|
||||
|
||||
[dev-dependencies]
|
||||
reth-tracing.workspace = true
|
||||
|
||||
@ -12,22 +12,25 @@
|
||||
#![cfg_attr(not(test), warn(unused_crate_dependencies))]
|
||||
#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
|
||||
|
||||
use igd_next::aio::tokio::search_gateway;
|
||||
use pin_project_lite::pin_project;
|
||||
use std::{
|
||||
fmt,
|
||||
future::{poll_fn, Future},
|
||||
net::{AddrParseError, IpAddr},
|
||||
pin::Pin,
|
||||
str::FromStr,
|
||||
task::{ready, Context, Poll},
|
||||
task::{Context, Poll},
|
||||
time::Duration,
|
||||
};
|
||||
use tracing::debug;
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
use serde_with::{DeserializeFromStr, SerializeDisplay};
|
||||
|
||||
/// URLs to `GET` the external IP address.
|
||||
///
|
||||
/// Taken from: <https://stackoverflow.com/questions/3253701/get-public-external-ip-address>
|
||||
const EXTERNAL_IP_APIS: &[&str] =
|
||||
&["http://ipinfo.io/ip", "http://icanhazip.com", "http://ifconfig.me"];
|
||||
|
||||
/// All builtin resolvers.
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, Default, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(SerializeDisplay, DeserializeFromStr))]
|
||||
@ -35,9 +38,9 @@ pub enum NatResolver {
|
||||
/// Resolve with any available resolver.
|
||||
#[default]
|
||||
Any,
|
||||
/// Resolve via Upnp
|
||||
/// Resolve external IP via UPnP.
|
||||
Upnp,
|
||||
/// Resolve external IP via [public_ip::Resolver]
|
||||
/// Resolve external IP via a network request.
|
||||
PublicIp,
|
||||
/// Use the given [IpAddr]
|
||||
ExternalIp(IpAddr),
|
||||
@ -45,8 +48,6 @@ pub enum NatResolver {
|
||||
None,
|
||||
}
|
||||
|
||||
// === impl NatResolver ===
|
||||
|
||||
impl NatResolver {
|
||||
/// Attempts to produce an IP address (best effort).
|
||||
pub async fn external_addr(self) -> Option<IpAddr> {
|
||||
@ -103,12 +104,10 @@ impl FromStr for NatResolver {
|
||||
#[must_use = "Does nothing unless polled"]
|
||||
pub struct ResolveNatInterval {
|
||||
resolver: NatResolver,
|
||||
future: Option<ResolveFut>,
|
||||
future: Option<Pin<Box<dyn Future<Output = Option<IpAddr>> + Send>>>,
|
||||
interval: tokio::time::Interval,
|
||||
}
|
||||
|
||||
// === impl ResolveNatInterval ===
|
||||
|
||||
impl fmt::Debug for ResolveNatInterval {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("ResolveNatInterval")
|
||||
@ -146,8 +145,7 @@ impl ResolveNatInterval {
|
||||
|
||||
/// Completes when the next [IpAddr] in the interval has been reached.
|
||||
pub async fn tick(&mut self) -> Option<IpAddr> {
|
||||
let ip = poll_fn(|cx| self.poll_tick(cx));
|
||||
ip.await
|
||||
poll_fn(|cx| self.poll_tick(cx)).await
|
||||
}
|
||||
|
||||
/// Polls for the next resolved [IpAddr] in the interval to be reached.
|
||||
@ -165,9 +163,7 @@ impl ResolveNatInterval {
|
||||
if let Some(mut fut) = self.future.take() {
|
||||
match fut.as_mut().poll(cx) {
|
||||
Poll::Ready(ip) => return Poll::Ready(ip),
|
||||
Poll::Pending => {
|
||||
self.future = Some(fut);
|
||||
}
|
||||
Poll::Pending => self.future = Some(fut),
|
||||
}
|
||||
}
|
||||
|
||||
@ -183,83 +179,26 @@ pub async fn external_ip() -> Option<IpAddr> {
|
||||
/// Given a [`NatResolver`] attempts to produce an IP address (best effort).
|
||||
pub async fn external_addr_with(resolver: NatResolver) -> Option<IpAddr> {
|
||||
match resolver {
|
||||
NatResolver::Any => {
|
||||
ResolveAny {
|
||||
upnp: Some(Box::pin(resolve_external_ip_upnp())),
|
||||
external: Some(Box::pin(resolve_external_ip())),
|
||||
}
|
||||
.await
|
||||
}
|
||||
NatResolver::Upnp => resolve_external_ip_upnp().await,
|
||||
NatResolver::PublicIp => resolve_external_ip().await,
|
||||
NatResolver::Any | NatResolver::Upnp | NatResolver::PublicIp => resolve_external_ip().await,
|
||||
NatResolver::ExternalIp(ip) => Some(ip),
|
||||
NatResolver::None => None,
|
||||
}
|
||||
}
|
||||
|
||||
type ResolveFut = Pin<Box<dyn Future<Output = Option<IpAddr>> + Send>>;
|
||||
|
||||
pin_project! {
|
||||
/// A future that resolves the first ip via all configured resolvers
|
||||
struct ResolveAny {
|
||||
#[pin]
|
||||
upnp: Option<ResolveFut>,
|
||||
#[pin]
|
||||
external: Option<ResolveFut>,
|
||||
}
|
||||
}
|
||||
|
||||
impl Future for ResolveAny {
|
||||
type Output = Option<IpAddr>;
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let mut this = self.as_mut().project();
|
||||
|
||||
if let Some(upnp) = this.upnp.as_mut().as_pin_mut() {
|
||||
// if upnp is configured we prefer it over http and dns resolvers
|
||||
let ip = ready!(upnp.poll(cx));
|
||||
this.upnp.set(None);
|
||||
if ip.is_some() {
|
||||
return Poll::Ready(ip)
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(upnp) = this.external.as_mut().as_pin_mut() {
|
||||
if let Poll::Ready(ip) = upnp.poll(cx) {
|
||||
this.external.set(None);
|
||||
if ip.is_some() {
|
||||
return Poll::Ready(ip)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if this.upnp.is_none() && this.external.is_none() {
|
||||
return Poll::Ready(None)
|
||||
}
|
||||
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
|
||||
async fn resolve_external_ip_upnp() -> Option<IpAddr> {
|
||||
search_gateway(Default::default())
|
||||
.await
|
||||
.map_err(|err| {
|
||||
debug!(target: "net::nat", %err, "Failed to resolve external IP via UPnP: failed to find gateway");
|
||||
err
|
||||
})
|
||||
.ok()?
|
||||
.get_external_ip()
|
||||
.await
|
||||
.map_err(|err| {
|
||||
debug!(target: "net::nat", %err, "Failed to resolve external IP via UPnP");
|
||||
err
|
||||
})
|
||||
.ok()
|
||||
}
|
||||
|
||||
async fn resolve_external_ip() -> Option<IpAddr> {
|
||||
public_ip::addr().await
|
||||
let futures = EXTERNAL_IP_APIS.iter().copied().map(resolve_external_ip_url_res).map(Box::pin);
|
||||
futures_util::future::select_ok(futures).await.ok().map(|(res, _)| res)
|
||||
}
|
||||
|
||||
async fn resolve_external_ip_url_res(url: &str) -> Result<IpAddr, ()> {
|
||||
resolve_external_ip_url(url).await.ok_or(())
|
||||
}
|
||||
|
||||
async fn resolve_external_ip_url(url: &str) -> Option<IpAddr> {
|
||||
let response = reqwest::get(url).await.ok()?;
|
||||
let response = response.error_for_status().ok()?;
|
||||
let text = response.text().await.ok()?;
|
||||
text.trim().parse().ok()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
Reference in New Issue
Block a user