From c442e8e8c593dfa8e3fe48d5a83d7f203f3d4ee6 Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Mon, 30 Mar 2026 10:03:04 +0200 Subject: [PATCH 1/2] Refactor `do_connect_peer` to always propagate result to subscribers Extract the connection logic into `do_connect_peer_internal` and have `do_connect_peer` act as a thin wrapper that always calls `propagate_result_to_subscribers` with the result. This removes the need to manually propagate at every error site, making the code less error-prone. Co-Authored-By: HAL 9000 --- src/connection.rs | 47 +++++++++++++++-------------------------------- 1 file changed, 15 insertions(+), 32 deletions(-) diff --git a/src/connection.rs b/src/connection.rs index 9110ed0d9..91da6459d 100644 --- a/src/connection.rs +++ b/src/connection.rs @@ -56,6 +56,14 @@ where pub(crate) async fn do_connect_peer( &self, node_id: PublicKey, addr: SocketAddress, + ) -> Result<(), Error> { + let res = self.do_connect_peer_internal(node_id, addr).await; + self.propagate_result_to_subscribers(&node_id, res); + res + } + + async fn do_connect_peer_internal( + &self, node_id: PublicKey, addr: SocketAddress, ) -> Result<(), Error> { // First, we check if there is already an outbound connection in flight, if so, we just // await on the corresponding watch channel. The task driving the connection future will @@ -71,15 +79,14 @@ where log_info!(self.logger, "Connecting to peer: {}@{}", node_id, addr); - let res = match addr { + match addr { SocketAddress::OnionV2(old_onion_addr) => { log_error!( - self.logger, - "Failed to resolve network address {:?}: Resolution of OnionV2 addresses is currently unsupported.", - old_onion_addr - ); - self.propagate_result_to_subscribers(&node_id, Err(Error::InvalidSocketAddress)); - return Err(Error::InvalidSocketAddress); + self.logger, + "Failed to resolve network address {:?}: Resolution of OnionV2 addresses is currently unsupported.", + old_onion_addr + ); + Err(Error::InvalidSocketAddress) }, SocketAddress::OnionV3 { .. } => { let proxy_config = self.tor_proxy_config.as_ref().ok_or_else(|| { @@ -88,10 +95,6 @@ where "Failed to resolve network address {:?}: Tor usage is not configured.", addr ); - self.propagate_result_to_subscribers( - &node_id, - Err(Error::InvalidSocketAddress), - ); Error::InvalidSocketAddress })?; let proxy_addr = proxy_config @@ -104,10 +107,6 @@ where proxy_config.proxy_address, e ); - self.propagate_result_to_subscribers( - &node_id, - Err(Error::InvalidSocketAddress), - ); Error::InvalidSocketAddress })? .next() @@ -117,10 +116,6 @@ where "Failed to resolve Tor proxy network address {}", proxy_config.proxy_address ); - self.propagate_result_to_subscribers( - &node_id, - Err(Error::InvalidSocketAddress), - ); Error::InvalidSocketAddress })?; let connection_future = lightning_net_tokio::tor_connect_outbound( @@ -142,19 +137,11 @@ where addr, e ); - self.propagate_result_to_subscribers( - &node_id, - Err(Error::InvalidSocketAddress), - ); Error::InvalidSocketAddress })? .next() .ok_or_else(|| { log_error!(self.logger, "Failed to resolve network address {}", addr); - self.propagate_result_to_subscribers( - &node_id, - Err(Error::InvalidSocketAddress), - ); Error::InvalidSocketAddress })?; let connection_future = lightning_net_tokio::connect_outbound( @@ -164,11 +151,7 @@ where ); self.await_connection(connection_future, node_id, addr).await }, - }; - - self.propagate_result_to_subscribers(&node_id, res); - - res + } } async fn await_connection( From 105f8354191bdef016df2ebb246446c3a3e95e4b Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Mon, 30 Mar 2026 10:07:07 +0200 Subject: [PATCH 2/2] Replace `to_socket_addrs()` with `tokio::net::lookup_host` Replace the synchronous, blocking `std::net::ToSocketAddrs::to_socket_addrs()` calls with async `tokio::net::lookup_host` to avoid blocking the tokio runtime during DNS resolution. Additionally, instead of only using the first resolved address, we now iterate over all resolved addresses and try connecting to each in sequence until one succeeds. This improves connectivity for hostnames that resolve to multiple addresses (e.g., dual-stack IPv4/IPv6). Co-Authored-By: HAL 9000 --- Cargo.toml | 2 +- src/connection.rs | 137 +++++++++++++++++++++++++++++++--------------- src/lib.rs | 36 ++++++------ 3 files changed, 112 insertions(+), 63 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 539941677..8a85c6574 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -66,7 +66,7 @@ bip21 = { version = "0.5", features = ["std"], default-features = false } base64 = { version = "0.22.1", default-features = false, features = ["std"] } getrandom = { version = "0.3", default-features = false } chrono = { version = "0.4", default-features = false, features = ["clock"] } -tokio = { version = "1.37", default-features = false, features = [ "rt-multi-thread", "time", "sync", "macros" ] } +tokio = { version = "1.37", default-features = false, features = [ "rt-multi-thread", "time", "sync", "macros", "net" ] } esplora-client = { version = "0.12", default-features = false, features = ["tokio", "async-https-rustls"] } electrum-client = { version = "0.24.0", default-features = false, features = ["proxy", "use-rustls-ring"] } libc = "0.2" diff --git a/src/connection.rs b/src/connection.rs index 91da6459d..a1d24e36d 100644 --- a/src/connection.rs +++ b/src/connection.rs @@ -6,7 +6,6 @@ // accordance with one or both of these licenses. use std::collections::hash_map::{self, HashMap}; -use std::net::ToSocketAddrs; use std::ops::Deref; use std::sync::{Arc, Mutex}; use std::time::Duration; @@ -15,7 +14,7 @@ use bitcoin::secp256k1::PublicKey; use lightning::ln::msgs::SocketAddress; use crate::config::TorConfig; -use crate::logger::{log_error, log_info, LdkLogger}; +use crate::logger::{log_debug, log_error, log_info, LdkLogger}; use crate::types::{KeysManager, PeerManager}; use crate::Error; @@ -97,39 +96,64 @@ where ); Error::InvalidSocketAddress })?; - let proxy_addr = proxy_config - .proxy_address - .to_socket_addrs() - .map_err(|e| { - log_error!( - self.logger, - "Failed to resolve Tor proxy network address {}: {}", - proxy_config.proxy_address, - e - ); - Error::InvalidSocketAddress - })? - .next() - .ok_or_else(|| { - log_error!( - self.logger, - "Failed to resolve Tor proxy network address {}", - proxy_config.proxy_address - ); - Error::InvalidSocketAddress - })?; - let connection_future = lightning_net_tokio::tor_connect_outbound( - Arc::clone(&self.peer_manager), - node_id, - addr.clone(), - proxy_addr, - Arc::clone(&self.keys_manager), - ); - self.await_connection(connection_future, node_id, addr).await + let resolved_addrs: Vec<_> = + tokio::net::lookup_host(proxy_config.proxy_address.to_string()) + .await + .map_err(|e| { + log_error!( + self.logger, + "Failed to resolve Tor proxy network address {}: {}", + proxy_config.proxy_address, + e + ); + Error::InvalidSocketAddress + })? + .collect(); + + if resolved_addrs.is_empty() { + log_error!( + self.logger, + "Failed to resolve Tor proxy network address {}", + proxy_config.proxy_address + ); + return Err(Error::InvalidSocketAddress); + } + + let mut res = Err(Error::ConnectionFailed); + let mut had_failures = false; + for proxy_addr in resolved_addrs { + let connection_future = lightning_net_tokio::tor_connect_outbound( + Arc::clone(&self.peer_manager), + node_id, + addr.clone(), + proxy_addr, + Arc::clone(&self.keys_manager), + ); + res = self.await_connection(connection_future, node_id, addr.clone()).await; + if res.is_ok() { + if had_failures { + log_info!( + self.logger, + "Successfully connected to peer {}@{} via resolved proxy address {} after previous attempts failed.", + node_id, addr, proxy_addr + ); + } + break; + } + had_failures = true; + log_debug!( + self.logger, + "Failed to connect to peer {}@{} via resolved proxy address {}.", + node_id, + addr, + proxy_addr + ); + } + res }, _ => { - let socket_addr = addr - .to_socket_addrs() + let resolved_addrs: Vec<_> = tokio::net::lookup_host(addr.to_string()) + .await .map_err(|e| { log_error!( self.logger, @@ -139,17 +163,42 @@ where ); Error::InvalidSocketAddress })? - .next() - .ok_or_else(|| { - log_error!(self.logger, "Failed to resolve network address {}", addr); - Error::InvalidSocketAddress - })?; - let connection_future = lightning_net_tokio::connect_outbound( - Arc::clone(&self.peer_manager), - node_id, - socket_addr, - ); - self.await_connection(connection_future, node_id, addr).await + .collect(); + + if resolved_addrs.is_empty() { + log_error!(self.logger, "Failed to resolve network address {}", addr); + return Err(Error::InvalidSocketAddress); + } + + let mut res = Err(Error::ConnectionFailed); + let mut had_failures = false; + for socket_addr in resolved_addrs { + let connection_future = lightning_net_tokio::connect_outbound( + Arc::clone(&self.peer_manager), + node_id, + socket_addr, + ); + res = self.await_connection(connection_future, node_id, addr.clone()).await; + if res.is_ok() { + if had_failures { + log_info!( + self.logger, + "Successfully connected to peer {}@{} via resolved address {} after previous attempts failed.", + node_id, addr, socket_addr + ); + } + break; + } + had_failures = true; + log_debug!( + self.logger, + "Failed to connect to peer {}@{} via resolved address {}.", + node_id, + addr, + socket_addr + ); + } + res }, } } diff --git a/src/lib.rs b/src/lib.rs index 2e02e996c..2ac4697e8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -108,7 +108,6 @@ mod types; mod wallet; use std::default::Default; -use std::net::ToSocketAddrs; use std::sync::{Arc, Mutex, RwLock}; use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH}; #[cfg(cycle_tests)] @@ -361,28 +360,29 @@ impl Node { let peer_manager_connection_handler = Arc::clone(&self.peer_manager); let listening_logger = Arc::clone(&self.logger); - let mut bind_addrs = Vec::with_capacity(listening_addresses.len()); - - for listening_addr in listening_addresses { - let resolved_address = listening_addr.to_socket_addrs().map_err(|e| { - log_error!( - self.logger, - "Unable to resolve listening address: {:?}. Error details: {}", - listening_addr, - e, - ); - Error::InvalidSocketAddress - })?; - - bind_addrs.extend(resolved_address); - } - let logger = Arc::clone(&listening_logger); + let listening_addrs = listening_addresses.clone(); let listeners = self.runtime.block_on(async move { + let mut bind_addrs = Vec::with_capacity(listening_addrs.len()); + + for listening_addr in &listening_addrs { + let resolved = + tokio::net::lookup_host(listening_addr.to_string()).await.map_err(|e| { + log_error!( + logger, + "Unable to resolve listening address: {:?}. Error details: {}", + listening_addr, + e, + ); + Error::InvalidSocketAddress + })?; + bind_addrs.extend(resolved); + } + let mut listeners = Vec::new(); // Try to bind to all addresses - for addr in &*bind_addrs { + for addr in &bind_addrs { match tokio::net::TcpListener::bind(addr).await { Ok(listener) => { log_trace!(logger, "Listener bound to {}", addr);