From 65f0a024974f4d4f5a224f9202df2e105b1c8acf Mon Sep 17 00:00:00 2001 From: Edgar Luque Date: Fri, 22 May 2026 12:59:02 +0200 Subject: [PATCH] fix(l1): announce local IP when --p2p.addr is unspecified When `--p2p.addr=0.0.0.0` (or `::`) was passed without `--nat.extip`, ethrex used the bind address as the externally-announced address, ending up with `enode://...@0.0.0.0:30303` in the ENR. Peers can't dial back to that, so the node had 0 inbound peers. Now the announce address falls back to the auto-detected local IP when the bind is unspecified; `--nat.extip` still overrides. If no local IP is detectable, log a loud warning instead of silently advertising the unspecified address. --- cmd/ethrex/initializers.rs | 154 +++++++++++++++++++++++++++++++------ 1 file changed, 129 insertions(+), 25 deletions(-) diff --git a/cmd/ethrex/initializers.rs b/cmd/ethrex/initializers.rs index 4f76b6eb5c..248ef47c40 100644 --- a/cmd/ethrex/initializers.rs +++ b/cmd/ethrex/initializers.rs @@ -360,27 +360,27 @@ pub fn get_signer(datadir: &Path) -> SecretKey { } } -pub fn get_local_p2p_node(opts: &Options, signer: &SecretKey) -> (Node, NetworkConfig) { - let tcp_port = opts.p2p_port.parse().expect("Failed to parse p2p port"); - let udp_port = opts - .discovery_port - .parse() - .expect("Failed to parse discovery port"); - - let local_public_key = public_key_from_signing_key(signer); - - // Determine bind and external addresses. - // - // --nat.extip sets the address announced to peers (for nodes behind NAT). - // --p2p.addr sets the bind address (defaults to the auto-detected local IP - // when --nat.extip is not given, or to the unspecified address when it is: - // 0.0.0.0 for IPv4, :: for IPv6). - let (bind_addr, external_addr): (IpAddr, IpAddr) = match (&opts.p2p_addr, &opts.nat_extip) { +/// Decide the bind and externally-announced addresses for the P2P endpoint. +/// +/// Precedence: +/// - `--nat.extip` wins for the announced address; bind comes from `--p2p.addr` if given, +/// else the unspecified address of the matching family. +/// - `--p2p.addr` alone is used for both bind and announce, except when it's an unspecified +/// address (`0.0.0.0` / `::`). In that case the announced address falls back to the +/// auto-detected local IP of the matching family; this avoids advertising `0.0.0.0` in +/// the ENR, which would make the node unreachable for inbound connections. Operators +/// behind NAT still need `--nat.extip` for that case to resolve correctly. +/// - With neither flag set, the auto-detected local IP is used for both bind and announce. +fn resolve_p2p_endpoints( + p2p_addr: Option<&str>, + nat_extip: Option<&str>, + local_v4: Option, + local_v6: Option, +) -> (IpAddr, IpAddr) { + match (p2p_addr, nat_extip) { (_, Some(extip)) => { let external: IpAddr = extip.parse().expect("Failed to parse --nat.extip address"); - let bind: IpAddr = opts - .p2p_addr - .as_deref() + let bind: IpAddr = p2p_addr .map(|a| { let addr: IpAddr = a.parse().expect("Failed to parse p2p address"); assert!( @@ -399,16 +399,60 @@ pub fn get_local_p2p_node(opts: &Options, signer: &SecretKey) -> (Node, NetworkC (bind, external) } (Some(addr), None) => { - let ip: IpAddr = addr.parse().expect("Failed to parse p2p address"); - (ip, ip) + let bind: IpAddr = addr.parse().expect("Failed to parse p2p address"); + if bind.is_unspecified() { + let external = if bind.is_ipv6() { + local_v6.or(local_v4) + } else { + local_v4.or(local_v6) + }; + match external { + Some(ext) => { + info!( + announced = %ext, + bind = %bind, + "--p2p.addr is unspecified; announcing auto-detected local IP. Set --nat.extip to override." + ); + (bind, ext) + } + None => { + warn!( + bind = %bind, + "--p2p.addr is unspecified and no local IP could be detected; \ + announcing the unspecified address. Inbound peer connections will fail. \ + Set --nat.extip to fix this." + ); + (bind, bind) + } + } + } else { + (bind, bind) + } } (None, None) => { - let ip = local_ip().unwrap_or_else(|_| { - local_ipv6().expect("Neither ipv4 nor ipv6 local address found") - }); + let ip = local_v4 + .or(local_v6) + .expect("Neither ipv4 nor ipv6 local address found"); (ip, ip) } - }; + } +} + +pub fn get_local_p2p_node(opts: &Options, signer: &SecretKey) -> (Node, NetworkConfig) { + let tcp_port = opts.p2p_port.parse().expect("Failed to parse p2p port"); + let udp_port = opts + .discovery_port + .parse() + .expect("Failed to parse discovery port"); + + let local_public_key = public_key_from_signing_key(signer); + + let (bind_addr, external_addr) = resolve_p2p_endpoints( + opts.p2p_addr.as_deref(), + opts.nat_extip.as_deref(), + local_ip().ok(), + local_ipv6().ok(), + ); let node = Node::new(external_addr, udp_port, tcp_port, local_public_key); let network_config = NetworkConfig { @@ -812,3 +856,63 @@ pub async fn regenerate_head_state( Ok(()) } + +#[cfg(test)] +mod tests { + use super::resolve_p2p_endpoints; + use std::net::IpAddr; + + fn ip(s: &str) -> IpAddr { + s.parse().unwrap() + } + + #[test] + fn p2p_addr_unspecified_v4_announces_local_ip() { + let local = ip("10.0.0.5"); + let (bind, ext) = resolve_p2p_endpoints(Some("0.0.0.0"), None, Some(local), None); + assert_eq!(bind, ip("0.0.0.0")); + assert_eq!(ext, local); + } + + #[test] + fn p2p_addr_unspecified_without_local_ip_keeps_unspecified() { + let (bind, ext) = resolve_p2p_endpoints(Some("0.0.0.0"), None, None, None); + assert_eq!(bind, ip("0.0.0.0")); + assert_eq!(ext, ip("0.0.0.0")); + } + + #[test] + fn extip_overrides_unspecified_bind() { + let (bind, ext) = resolve_p2p_endpoints( + Some("0.0.0.0"), + Some("203.0.113.5"), + Some(ip("10.0.0.5")), + None, + ); + assert_eq!(bind, ip("0.0.0.0")); + assert_eq!(ext, ip("203.0.113.5")); + } + + #[test] + fn specific_p2p_addr_used_for_both() { + let (bind, ext) = + resolve_p2p_endpoints(Some("10.0.0.5"), None, Some(ip("192.168.1.1")), None); + assert_eq!(bind, ip("10.0.0.5")); + assert_eq!(ext, ip("10.0.0.5")); + } + + #[test] + fn no_flags_uses_local_v4_when_available() { + let local = ip("10.0.0.5"); + let (bind, ext) = resolve_p2p_endpoints(None, None, Some(local), Some(ip("fe80::1"))); + assert_eq!(bind, local); + assert_eq!(ext, local); + } + + #[test] + fn extip_only_uses_unspecified_bind() { + let (bind, ext) = resolve_p2p_endpoints(None, Some("203.0.113.5"), None, None); + assert_eq!(bind, ip("0.0.0.0")); + assert_eq!(ext, ip("203.0.113.5")); + } +}