feat(net): add nat external ip crate (#605)

This commit is contained in:
Matthias Seitz
2022-12-25 17:22:31 +01:00
committed by GitHub
parent 73e12341c4
commit 3a07eb930f
4 changed files with 227 additions and 0 deletions

62
Cargo.lock generated
View File

@ -173,6 +173,18 @@ dependencies = [
"critical-section",
]
[[package]]
name = "attohttpc"
version = "0.16.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fdb8867f378f33f78a811a8eb9bf108ad99430d7aad43315dd9319c827ef6247"
dependencies = [
"http",
"log",
"url",
"wildmatch",
]
[[package]]
name = "atty"
version = "0.2.14"
@ -1933,6 +1945,23 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb56e1aa765b4b4f3aadfab769793b7087bb03a4ea4920644a6d238e2df5b9ed"
[[package]]
name = "igd"
version = "0.12.0"
source = "git+https://github.com/stevefan1999-personal/rust-igd#c2d1f83eb1612a462962453cb0703bc93258b173"
dependencies = [
"attohttpc",
"bytes",
"futures",
"http",
"hyper",
"log",
"rand 0.8.5",
"tokio",
"url",
"xmltree",
]
[[package]]
name = "impl-codec"
version = "0.6.0"
@ -3579,6 +3608,18 @@ dependencies = [
"reth-primitives",
]
[[package]]
name = "reth-net-nat"
version = "0.1.0"
dependencies = [
"igd",
"pin-project-lite",
"public-ip",
"reth-tracing",
"tokio",
"tracing",
]
[[package]]
name = "reth-network"
version = "0.1.0"
@ -5251,6 +5292,12 @@ dependencies = [
"webpki",
]
[[package]]
name = "wildmatch"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f44b95f62d34113cf558c93511ac93027e03e9c29a60dd0fd70e6e025c7270a"
[[package]]
name = "winapi"
version = "0.3.9"
@ -5418,6 +5465,21 @@ dependencies = [
"tap",
]
[[package]]
name = "xml-rs"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3"
[[package]]
name = "xmltree"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7d8a75eaf6557bb84a65ace8609883db44a29951042ada9b393151532e41fcb"
dependencies = [
"xml-rs",
]
[[package]]
name = "zeroize"
version = "1.5.7"

View File

@ -11,6 +11,7 @@ members = [
"crates/net/ecies",
"crates/net/eth-wire",
"crates/net/discv4",
"crates/net/nat",
"crates/net/network",
"crates/net/ipc",
"crates/net/rpc",

27
crates/net/nat/Cargo.toml Normal file
View File

@ -0,0 +1,27 @@
[package]
name = "reth-net-nat"
version = "0.1.0"
edition = "2021"
license = "Apache-2.0"
repository = "https://github.com/paradigmxyz/reth"
description = """
Helpers for working around NAT
"""
[dependencies]
# nat
public-ip = "0.2"
## fork of rust-igd with ipv6 support: https://github.com/sbstp/rust-igd/issues/47
igd = { git = "https://github.com/stevefan1999-personal/rust-igd", features = [
"aio",
"tokio1",
] }
# misc
tracing = "0.1"
pin-project-lite = "0.2.9"
[dev-dependencies]
reth-tracing = { path = "../../tracing" }
tokio = { version = "1", features = ["macros"] }

137
crates/net/nat/src/lib.rs Normal file
View File

@ -0,0 +1,137 @@
#![warn(missing_docs, unused_crate_dependencies)]
#![deny(unused_must_use, rust_2018_idioms)]
#![doc(test(
no_crate_inject,
attr(deny(warnings, rust_2018_idioms), allow(dead_code, unused_variables))
))]
//! Helpers for resolving the external IP.
use igd::aio::search_gateway;
use pin_project_lite::pin_project;
use std::{
future::Future,
net::IpAddr,
pin::Pin,
task::{ready, Context, Poll},
};
use tracing::warn;
/// All builtin resolvers.
#[derive(Debug, Clone, Copy, Eq, PartialEq, Default, Hash)]
pub enum NatResolver {
/// Resolve with any available resolver.
#[default]
Any,
/// Resolve via Upnp
Upnp,
/// Resolve external IP via [public_ip::Resolver]
ExternalIp,
}
// === impl NatResolver ===
impl NatResolver {
/// Attempts to produce an IP address (best effort).
pub async fn external_addr(self) -> Option<IpAddr> {
external_addr_with(self).await
}
}
/// Attempts to produce an IP address with all builtin resolvers (best effort).
pub async fn external_ip() -> Option<IpAddr> {
external_addr_with(NatResolver::Any).await
}
/// 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::ExternalIp => resolve_external_ip().await,
}
}
type ResolveFut = Pin<Box<dyn Future<Output = Option<IpAddr>>>>;
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| {
warn!(target: "net::nat", ?err, "failed to find upnp gateway");
err
})
.ok()?
.get_external_ip()
.await
.map_err(|err| {
warn!(target: "net::nat", ?err, "failed to resolve external ip via upnp gateway");
err
})
.ok()
}
async fn resolve_external_ip() -> Option<IpAddr> {
public_ip::addr().await
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
#[ignore]
async fn get_external_ip() {
reth_tracing::init_tracing();
let ip = external_ip().await;
dbg!(ip);
}
}