chore: resolve external IP with just an http request (#8516)

This commit is contained in:
DaniPopes
2024-05-30 18:40:23 +03:00
committed by GitHub
parent 716976f0d1
commit 9a08ad7844
3 changed files with 41 additions and 343 deletions

258
Cargo.lock generated
View File

@ -1013,17 +1013,6 @@ version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
[[package]]
name = "attohttpc"
version = "0.24.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d9a9bf8b79a749ee0b911b91b671cc2b6c670bdbc7e3dfd537576ddc94bb2a2"
dependencies = [
"http 0.2.12",
"log",
"url",
]
[[package]]
name = "aurora-engine-modexp"
version = "1.1.0"
@ -1724,7 +1713,7 @@ dependencies = [
"anstream",
"anstyle",
"clap_lex",
"strsim 0.11.1",
"strsim",
]
[[package]]
@ -2266,38 +2255,14 @@ dependencies = [
"tracing",
]
[[package]]
name = "darling"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d706e75d87e35569db781a9b5e2416cff1236a47ed380831f959382ccd5f858"
dependencies = [
"darling_core 0.10.2",
"darling_macro 0.10.2",
]
[[package]]
name = "darling"
version = "0.20.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83b2eb4d90d12bdda5ed17de686c2acb4c57914f8f921b8da7e112b5a36f3fe1"
dependencies = [
"darling_core 0.20.9",
"darling_macro 0.20.9",
]
[[package]]
name = "darling_core"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0c960ae2da4de88a91b2d920c2a7233b400bc33cb28453a2987822d8392519b"
dependencies = [
"fnv",
"ident_case",
"proc-macro2",
"quote",
"strsim 0.9.3",
"syn 1.0.109",
"darling_core",
"darling_macro",
]
[[package]]
@ -2310,28 +2275,17 @@ dependencies = [
"ident_case",
"proc-macro2",
"quote",
"strsim 0.11.1",
"strsim",
"syn 2.0.66",
]
[[package]]
name = "darling_macro"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72"
dependencies = [
"darling_core 0.10.2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "darling_macro"
version = "0.20.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "733cabb43482b1a1b53eee8583c2b9e8684d592215ea83efd305dd31bc2f0178"
dependencies = [
"darling_core 0.20.9",
"darling_core",
"quote",
"syn 2.0.66",
]
@ -2453,31 +2407,6 @@ dependencies = [
"syn 2.0.66",
]
[[package]]
name = "derive_builder"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2658621297f2cf68762a6f7dc0bb7e1ff2cfd6583daef8ee0fed6f7ec468ec0"
dependencies = [
"darling 0.10.2",
"derive_builder_core",
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "derive_builder_core"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2791ea3e372c8495c0bc2033991d76b512cd799d07491fbd6890124db9458bef"
dependencies = [
"darling 0.10.2",
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "derive_more"
version = "0.99.17"
@ -2605,18 +2534,6 @@ dependencies = [
"syn 2.0.66",
]
[[package]]
name = "dns-lookup"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53ecafc952c4528d9b51a458d1a8904b81783feff9fde08ab6ed2545ff396872"
dependencies = [
"cfg-if",
"libc",
"socket2 0.4.10",
"winapi",
]
[[package]]
name = "downcast"
version = "0.11.0"
@ -2775,18 +2692,6 @@ dependencies = [
"zeroize",
]
[[package]]
name = "enum-as-inner"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "570d109b813e904becc80d8d5da38376818a143348413f7149f1340fe04754d4"
dependencies = [
"heck 0.4.1",
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "enum-as-inner"
version = "0.6.0"
@ -3768,20 +3673,6 @@ dependencies = [
"tower-service",
]
[[package]]
name = "hyper-system-resolver"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6eea26c5d0b6ab9d72219f65000af310f042a740926f7b2fa3553e774036e2e7"
dependencies = [
"derive_builder",
"dns-lookup",
"hyper 0.14.28",
"tokio",
"tower-service",
"tracing",
]
[[package]]
name = "hyper-util"
version = "0.1.4"
@ -3984,17 +3875,6 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]]
name = "idna"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8"
dependencies = [
"matches",
"unicode-bidi",
"unicode-normalization",
]
[[package]]
name = "idna"
version = "0.4.0"
@ -4015,25 +3895,6 @@ dependencies = [
"unicode-normalization",
]
[[package]]
name = "igd-next"
version = "0.14.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "064d90fec10d541084e7b39ead8875a5a80d9114a2b18791565253bae25f49e4"
dependencies = [
"async-trait",
"attohttpc",
"bytes",
"futures",
"http 0.2.12",
"hyper 0.14.28",
"log",
"rand 0.8.5",
"tokio",
"url",
"xmltree",
]
[[package]]
name = "impl-codec"
version = "0.6.0"
@ -4870,12 +4731,6 @@ dependencies = [
"regex-automata 0.1.10",
]
[[package]]
name = "matches"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5"
[[package]]
name = "memchr"
version = "2.7.2"
@ -5977,27 +5832,6 @@ dependencies = [
"syn 1.0.109",
]
[[package]]
name = "public-ip"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b4c40db5262d93298c363a299f8bc1b3a956a78eecddba3bc0e58b76e2f419a"
dependencies = [
"dns-lookup",
"futures-core",
"futures-util",
"http 0.2.12",
"hyper 0.14.28",
"hyper-system-resolver",
"pin-project-lite",
"thiserror",
"tokio",
"tracing",
"tracing-futures",
"trust-dns-client",
"trust-dns-proto 0.20.4",
]
[[package]]
name = "quanta"
version = "0.11.1"
@ -7198,14 +7032,12 @@ dependencies = [
name = "reth-net-nat"
version = "0.2.0-beta.7"
dependencies = [
"igd-next",
"pin-project-lite",
"public-ip",
"futures-util",
"reqwest 0.12.4",
"reth-tracing",
"serde_with",
"thiserror",
"tokio",
"tracing",
]
[[package]]
@ -8877,7 +8709,7 @@ version = "3.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "65569b702f41443e8bc8bbb1c5779bd0450bbe723b56198980e80ec45780bce2"
dependencies = [
"darling 0.20.9",
"darling",
"proc-macro2",
"quote",
"syn 2.0.66",
@ -9210,12 +9042,6 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9091b6114800a5f2141aee1d1b9d6ca3592ac062dc5decb3764ec5895a47b4eb"
[[package]]
name = "strsim"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c"
[[package]]
name = "strsim"
version = "0.11.1"
@ -9430,7 +9256,7 @@ version = "5.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abe1689311f7edc6bab4033a259a3c37510b41063e4b01e57970105c0c764428"
dependencies = [
"darling 0.20.9",
"darling",
"itertools 0.12.1",
"once_cell",
"prettyplease",
@ -9849,8 +9675,6 @@ version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2"
dependencies = [
"futures",
"futures-task",
"pin-project",
"tracing",
]
@ -9930,51 +9754,6 @@ dependencies = [
"rlp",
]
[[package]]
name = "trust-dns-client"
version = "0.20.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b4ef9b9bde0559b78a4abb00339143750085f05e5a453efb7b8bef1061f09dc"
dependencies = [
"cfg-if",
"data-encoding",
"futures-channel",
"futures-util",
"lazy_static",
"log",
"radix_trie",
"rand 0.8.5",
"thiserror",
"time",
"tokio",
"trust-dns-proto 0.20.4",
]
[[package]]
name = "trust-dns-proto"
version = "0.20.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca94d4e9feb6a181c690c4040d7a24ef34018d8313ac5044a61d21222ae24e31"
dependencies = [
"async-trait",
"cfg-if",
"data-encoding",
"enum-as-inner 0.3.4",
"futures-channel",
"futures-io",
"futures-util",
"idna 0.2.3",
"ipnet",
"lazy_static",
"log",
"rand 0.8.5",
"smallvec",
"thiserror",
"tinyvec",
"tokio",
"url",
]
[[package]]
name = "trust-dns-proto"
version = "0.23.2"
@ -9984,7 +9763,7 @@ dependencies = [
"async-trait",
"cfg-if",
"data-encoding",
"enum-as-inner 0.6.0",
"enum-as-inner",
"futures-channel",
"futures-io",
"futures-util",
@ -10018,7 +9797,7 @@ dependencies = [
"thiserror",
"tokio",
"tracing",
"trust-dns-proto 0.23.2",
"trust-dns-proto",
]
[[package]]
@ -10664,21 +10443,6 @@ dependencies = [
"tap",
]
[[package]]
name = "xml-rs"
version = "0.8.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "791978798f0597cfc70478424c2b4fdc2b7a8024aaff78497ef00f24ef674193"
[[package]]
name = "xmltree"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7d8a75eaf6557bb84a65ace8609883db44a29951042ada9b393151532e41fcb"
dependencies = [
"xml-rs",
]
[[package]]
name = "yoke"
version = "0.7.3"

View File

@ -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

View File

@ -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)]