mirror of
https://github.com/hl-archive-node/nanoreth.git
synced 2025-12-06 10:59:55 +00:00
feat(net): integrate external public ip auto discovery (#632)
* feat(net): integrate external public ip auto discovery * Update crates/net/discv4/src/config.rs Co-authored-by: Georgios Konstantopoulos <me@gakonst.com> * rename var Co-authored-by: Georgios Konstantopoulos <me@gakonst.com>
This commit is contained in:
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -3514,9 +3514,9 @@ dependencies = [
|
||||
"enr 0.7.0",
|
||||
"generic-array",
|
||||
"hex",
|
||||
"public-ip",
|
||||
"rand 0.8.5",
|
||||
"reth-net-common",
|
||||
"reth-net-nat",
|
||||
"reth-primitives",
|
||||
"reth-rlp",
|
||||
"reth-rlp-derive",
|
||||
|
||||
@ -6,7 +6,7 @@ license = "Apache-2.0"
|
||||
repository = "https://github.com/paradigmxyz/reth"
|
||||
readme = "README.md"
|
||||
description = """
|
||||
Ethereum network support
|
||||
Ethereum network discovery
|
||||
"""
|
||||
|
||||
[dependencies]
|
||||
@ -15,6 +15,7 @@ reth-primitives = { path = "../../primitives" }
|
||||
reth-rlp = { path = "../../common/rlp", features = ["enr"] }
|
||||
reth-rlp-derive = { path = "../../common/rlp-derive" }
|
||||
reth-net-common = { path = "../common" }
|
||||
reth-net-nat = { path = "../nat" }
|
||||
|
||||
# ethereum
|
||||
discv5 = { git = "https://github.com/sigp/discv5" }
|
||||
@ -34,7 +35,6 @@ bytes = "1.2"
|
||||
tracing = "0.1"
|
||||
thiserror = "1.0"
|
||||
hex = "0.4"
|
||||
public-ip = "0.2"
|
||||
rand = { version = "0.8", optional = true }
|
||||
generic-array = "0.14"
|
||||
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use reth_net_common::ban_list::BanList;
|
||||
use reth_net_nat::{NatResolver, ResolveNatInterval};
|
||||
use reth_primitives::NodeRecord;
|
||||
use reth_rlp::Encodable;
|
||||
use std::{
|
||||
@ -54,6 +55,11 @@ pub struct Discv4Config {
|
||||
pub enable_eip868: bool,
|
||||
/// Additional pairs to include in The [`Enr`](enr::Enr) if EIP-868 extension is enabled <https://eips.ethereum.org/EIPS/eip-868>
|
||||
pub additional_eip868_rlp_pairs: HashMap<Vec<u8>, Bytes>,
|
||||
/// If configured, try to resolve public ip
|
||||
pub external_ip_resolver: Option<NatResolver>,
|
||||
/// If configured and a `external_ip_resolver` is configured, try to resolve the external ip
|
||||
/// using this interval.
|
||||
pub resolve_external_ip_interval: Option<Duration>,
|
||||
}
|
||||
|
||||
impl Discv4Config {
|
||||
@ -85,6 +91,14 @@ impl Discv4Config {
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Returns the corresponding [`ResolveNatInterval`], if a [NatResolver] and an interval was
|
||||
/// configured
|
||||
pub fn resolve_external_ip_interval(&self) -> Option<ResolveNatInterval> {
|
||||
let resolver = self.external_ip_resolver?;
|
||||
let interval = self.resolve_external_ip_interval?;
|
||||
Some(ResolveNatInterval::interval(resolver, interval))
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Discv4Config {
|
||||
@ -113,6 +127,9 @@ impl Default for Discv4Config {
|
||||
enable_lookup: true,
|
||||
enable_eip868: true,
|
||||
additional_eip868_rlp_pairs: Default::default(),
|
||||
external_ip_resolver: Some(Default::default()),
|
||||
/// By default retry public IP using a 5min interval
|
||||
resolve_external_ip_interval: Some(Duration::from_secs(60 * 5)),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -247,6 +264,21 @@ impl Discv4ConfigBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
/// Configures if and how the external IP of the node should be resolved.
|
||||
pub fn external_ip_resolver(&mut self, external_ip_resolver: Option<NatResolver>) -> &mut Self {
|
||||
self.config.external_ip_resolver = external_ip_resolver;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the interval at which the external IP is to be resolved.
|
||||
pub fn resolve_external_ip_interval(
|
||||
&mut self,
|
||||
resolve_external_ip_interval: Option<Duration>,
|
||||
) -> &mut Self {
|
||||
self.config.resolve_external_ip_interval = resolve_external_ip_interval;
|
||||
self
|
||||
}
|
||||
|
||||
/// Returns the configured [`Discv4Config`]
|
||||
pub fn build(&self) -> Discv4Config {
|
||||
self.config.clone()
|
||||
|
||||
@ -49,7 +49,7 @@ use tokio::{
|
||||
time::Interval,
|
||||
};
|
||||
use tokio_stream::{wrappers::ReceiverStream, Stream, StreamExt};
|
||||
use tracing::{debug, trace, warn};
|
||||
use tracing::{debug, info, trace, warn};
|
||||
|
||||
pub mod bootnodes;
|
||||
pub mod error;
|
||||
@ -67,8 +67,9 @@ pub use reth_primitives::NodeRecord;
|
||||
#[cfg(any(test, feature = "mock"))]
|
||||
pub mod mock;
|
||||
|
||||
use reth_net_nat::ResolveNatInterval;
|
||||
/// reexport to get public ip.
|
||||
pub use public_ip;
|
||||
pub use reth_net_nat::{external_ip, NatResolver};
|
||||
|
||||
/// The default port for discv4 via UDP
|
||||
///
|
||||
@ -361,6 +362,8 @@ pub struct Discv4Service {
|
||||
evict_expired_requests_interval: Interval,
|
||||
/// Interval when to resend pings.
|
||||
ping_interval: Interval,
|
||||
/// The interval at which to attempt resolving external IP again.
|
||||
resolve_external_ip_interval: Option<ResolveNatInterval>,
|
||||
/// How this services is configured
|
||||
config: Discv4Config,
|
||||
}
|
||||
@ -453,8 +456,9 @@ impl Discv4Service {
|
||||
lookup_interval: self_lookup_interval,
|
||||
ping_interval,
|
||||
evict_expired_requests_interval,
|
||||
config,
|
||||
lookup_rotator,
|
||||
resolve_external_ip_interval: config.resolve_external_ip_interval(),
|
||||
config,
|
||||
}
|
||||
}
|
||||
|
||||
@ -467,6 +471,16 @@ impl Discv4Service {
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the given ip address as the node's external IP in the node record announced in
|
||||
/// discovery
|
||||
pub fn set_external_ip_addr(&mut self, external_ip: IpAddr) {
|
||||
if self.local_node_record.address != external_ip {
|
||||
info!(target : "discv4", ?external_ip, "Updating external ip");
|
||||
self.local_node_record.address = external_ip;
|
||||
let _ = self.local_eip_868_enr.set_ip(external_ip, &self.secret_key);
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the address of the UDP socket
|
||||
pub fn local_addr(&self) -> SocketAddr {
|
||||
self.local_address
|
||||
@ -1251,6 +1265,12 @@ impl Discv4Service {
|
||||
self.re_ping_oldest();
|
||||
}
|
||||
|
||||
if let Some(Poll::Ready(Some(ip))) =
|
||||
self.resolve_external_ip_interval.as_mut().map(|r| r.poll_tick(cx))
|
||||
{
|
||||
self.set_external_ip_addr(ip);
|
||||
}
|
||||
|
||||
// process all incoming commands
|
||||
if let Some(mut rx) = self.commands_rx.take() {
|
||||
let mut is_done = false;
|
||||
|
||||
@ -216,7 +216,7 @@ pub async fn create_discv4_with_config(config: Discv4Config) -> (Discv4, Discv4S
|
||||
let socket = SocketAddr::from_str("0.0.0.0:0").unwrap();
|
||||
let (secret_key, pk) = SECP256K1.generate_keypair(&mut rng);
|
||||
let id = PeerId::from_slice(&pk.serialize_uncompressed()[1..]);
|
||||
let external_addr = public_ip::addr().await.unwrap_or_else(|| socket.ip());
|
||||
let external_addr = reth_net_nat::external_ip().await.unwrap_or_else(|| socket.ip());
|
||||
let local_enr =
|
||||
NodeRecord { address: external_addr, tcp_port: socket.port(), udp_port: socket.port(), id };
|
||||
Discv4::bind(socket, local_enr, secret_key, config).await.unwrap()
|
||||
|
||||
@ -21,6 +21,7 @@ igd = { git = "https://github.com/stevefan1999-personal/rust-igd", features = [
|
||||
# misc
|
||||
tracing = "0.1"
|
||||
pin-project-lite = "0.2.9"
|
||||
tokio = { version = "1", features = ["time"] }
|
||||
|
||||
[dev-dependencies]
|
||||
reth-tracing = { path = "../../tracing" }
|
||||
|
||||
@ -10,10 +10,11 @@
|
||||
use igd::aio::search_gateway;
|
||||
use pin_project_lite::pin_project;
|
||||
use std::{
|
||||
future::Future,
|
||||
future::{poll_fn, Future},
|
||||
net::IpAddr,
|
||||
pin::Pin,
|
||||
task::{ready, Context, Poll},
|
||||
time::Duration,
|
||||
};
|
||||
use tracing::warn;
|
||||
|
||||
@ -38,6 +39,72 @@ impl NatResolver {
|
||||
}
|
||||
}
|
||||
|
||||
/// With this type you can resolve the external public IP address on an interval basis.
|
||||
#[must_use = "Does nothing unless polled"]
|
||||
pub struct ResolveNatInterval {
|
||||
resolver: NatResolver,
|
||||
future: Option<ResolveFut>,
|
||||
interval: tokio::time::Interval,
|
||||
}
|
||||
|
||||
// === impl ResolveNatInterval ===
|
||||
|
||||
impl ResolveNatInterval {
|
||||
fn with_interval(resolver: NatResolver, interval: tokio::time::Interval) -> Self {
|
||||
Self { resolver, future: None, interval }
|
||||
}
|
||||
|
||||
/// Creates a new [ResolveNatInterval] that attempts to resolve the public IP with interval of
|
||||
/// period. See also [tokio::time::interval]
|
||||
#[track_caller]
|
||||
pub fn interval(resolver: NatResolver, period: Duration) -> Self {
|
||||
let interval = tokio::time::interval(period);
|
||||
Self::with_interval(resolver, interval)
|
||||
}
|
||||
|
||||
/// Creates a new [ResolveNatInterval] that attempts to resolve the public IP with interval of
|
||||
/// period with the first attempt starting at `sart`. See also [tokio::time::interval_at]
|
||||
#[track_caller]
|
||||
pub fn interval_at(
|
||||
resolver: NatResolver,
|
||||
start: tokio::time::Instant,
|
||||
period: Duration,
|
||||
) -> Self {
|
||||
let interval = tokio::time::interval_at(start, period);
|
||||
Self::with_interval(resolver, interval)
|
||||
}
|
||||
|
||||
/// 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
|
||||
}
|
||||
|
||||
/// Polls for the next resolved [IpAddr] in the interval to be reached.
|
||||
///
|
||||
/// This method can return the following values:
|
||||
///
|
||||
/// * `Poll::Pending` if the next [IpAddr] has not yet been resolved.
|
||||
/// * `Poll::Ready(Option<IpAddr>)` if the next [IpAddr] has been resolved. This returns `None`
|
||||
/// if the attempt was unsuccessful.
|
||||
pub fn poll_tick(&mut self, cx: &mut Context<'_>) -> Poll<Option<IpAddr>> {
|
||||
if self.interval.poll_tick(cx).is_ready() {
|
||||
self.future = Some(Box::pin(self.resolver.external_addr()));
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
/// 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
|
||||
@ -58,7 +125,7 @@ pub async fn external_addr_with(resolver: NatResolver) -> Option<IpAddr> {
|
||||
}
|
||||
}
|
||||
|
||||
type ResolveFut = Pin<Box<dyn Future<Output = Option<IpAddr>>>>;
|
||||
type ResolveFut = Pin<Box<dyn Future<Output = Option<IpAddr>> + Send>>;
|
||||
|
||||
pin_project! {
|
||||
/// A future that resolves the first ip via all configured resolvers
|
||||
@ -134,4 +201,16 @@ mod tests {
|
||||
let ip = external_ip().await;
|
||||
dbg!(ip);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore]
|
||||
async fn get_external_ip_interval() {
|
||||
reth_tracing::init_tracing();
|
||||
let mut interval = ResolveNatInterval::interval(Default::default(), Duration::from_secs(5));
|
||||
|
||||
let ip = interval.tick().await;
|
||||
dbg!(ip);
|
||||
let ip = interval.tick().await;
|
||||
dbg!(ip);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user