From 1ce0c385f082ec9f7b6e2c69a5393995681040d0 Mon Sep 17 00:00:00 2001 From: JHB <16675200+jharveyb@users.noreply.github.com> Date: Thu, 29 Jan 2026 19:38:18 -0500 Subject: [PATCH] Add Tor support for outbound connections via SOCKS --- src/builder.rs | 27 ++++++++++++- src/config.rs | 7 ++++ src/connection.rs | 96 +++++++++++++++++++++++++++++++++++------------ 3 files changed, 105 insertions(+), 25 deletions(-) diff --git a/src/builder.rs b/src/builder.rs index 5d8a5a7a9..8c90d3f49 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -518,6 +518,16 @@ impl NodeBuilder { Ok(self) } + /// Set the address which [`Node`] will use as a Tor proxy to connect to peer OnionV3 addresses. + /// + /// **Note**: If unset, connecting to peer OnionV3 addresses will fail. + /// + /// [`tor_proxy_address`]: Config::tor_proxy_address + pub fn set_tor_proxy_address(&mut self, tor_proxy_address: core::net::SocketAddr) -> &mut Self { + self.config.tor_proxy_address = Some(tor_proxy_address); + self + } + /// Sets the node alias that will be used when broadcasting announcements to the gossip /// network. /// @@ -904,6 +914,15 @@ impl ArcedNodeBuilder { self.inner.write().unwrap().set_announcement_addresses(announcement_addresses).map(|_| ()) } + /// Set the address which [`Node`] will use as a Tor proxy to connect to peer OnionV3 addresses. + /// + /// **Note**: If unset, connecting to peer OnionV3 addresses will fail. + /// + /// [`tor_proxy_address`]: Config::tor_proxy_address + pub fn set_tor_proxy_address(&mut self, tor_proxy_address: core::net::SocketAddr) { + self.config.tor_proxy_address = Some(tor_proxy_address); + } + /// Sets the node alias that will be used when broadcasting announcements to the gossip /// network. /// @@ -1683,8 +1702,12 @@ fn build_with_store_internal( liquidity_source.as_ref().map(|l| l.set_peer_manager(Arc::downgrade(&peer_manager))); - let connection_manager = - Arc::new(ConnectionManager::new(Arc::clone(&peer_manager), Arc::clone(&logger))); + let connection_manager = Arc::new(ConnectionManager::new( + Arc::clone(&peer_manager), + config.tor_proxy_address.clone(), + ephemeral_bytes, + Arc::clone(&logger), + )); let output_sweeper = match sweeper_bytes_res { Ok(output_sweeper) => Arc::new(output_sweeper), diff --git a/src/config.rs b/src/config.rs index 103b74657..4f6321864 100644 --- a/src/config.rs +++ b/src/config.rs @@ -149,6 +149,12 @@ pub struct Config { /// /// [`listening_addresses`]: Config::listening_addresses pub announcement_addresses: Option>, + /// The address which the node will use as a Tor proxy to connect to peer OnionV3 addresses. + /// + /// **Note**: If unset, connecting to peer OnionV3 addresses will fail. + /// + /// [`tor_proxy_address`]: Config::tor_proxy_address + pub tor_proxy_address: Option, /// The node alias that will be used when broadcasting announcements to the gossip network. /// /// The provided alias must be a valid UTF-8 string and no longer than 32 bytes in total. @@ -201,6 +207,7 @@ impl Default for Config { network: DEFAULT_NETWORK, listening_addresses: None, announcement_addresses: None, + tor_proxy_address: None, trusted_peers_0conf: Vec::new(), probing_liquidity_limit_multiplier: DEFAULT_PROBING_LIQUIDITY_LIMIT_MULTIPLIER, anchor_channels_config: Some(AnchorChannelsConfig::default()), diff --git a/src/connection.rs b/src/connection.rs index e3a25f357..c7a7b7935 100644 --- a/src/connection.rs +++ b/src/connection.rs @@ -13,6 +13,7 @@ use std::time::Duration; use bitcoin::secp256k1::PublicKey; use lightning::ln::msgs::SocketAddress; +use lightning::sign::RandomBytes; use crate::logger::{log_error, log_info, LdkLogger}; use crate::types::PeerManager; @@ -25,6 +26,8 @@ where pending_connections: Mutex>>>>, peer_manager: Arc, + tor_proxy_addr: Option, + tor_proxy_rng: Arc, logger: L, } @@ -32,9 +35,14 @@ impl ConnectionManager where L::Target: LdkLogger, { - pub(crate) fn new(peer_manager: Arc, logger: L) -> Self { + pub(crate) fn new( + peer_manager: Arc, tor_proxy_addr: Option, + ephemeral_random_data: [u8; 32], logger: L, + ) -> Self { let pending_connections = Mutex::new(HashMap::new()); - Self { pending_connections, peer_manager, logger } + let tor_proxy_rng = Arc::new(RandomBytes::new(ephemeral_random_data)); + + Self { pending_connections, peer_manager, tor_proxy_addr, tor_proxy_rng, logger } } pub(crate) async fn connect_peer_if_necessary( @@ -64,27 +72,73 @@ where log_info!(self.logger, "Connecting to peer: {}@{}", node_id, addr); - let socket_addr = addr - .to_socket_addrs() - .map_err(|e| { - log_error!(self.logger, "Failed to resolve network address {}: {}", 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); + let res = if let SocketAddress::OnionV2(old_onion_addr) = 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); + } else if let SocketAddress::OnionV3 { .. } = addr { + let proxy_addr = self.tor_proxy_addr.ok_or_else(|| { + log_error!( + self.logger, + "Failed to resolve network address {:?}: Tor proxy address is unset.", + addr + ); self.propagate_result_to_subscribers(&node_id, Err(Error::InvalidSocketAddress)); Error::InvalidSocketAddress })?; + let connection_future = lightning_net_tokio::tor_connect_outbound( + Arc::clone(&self.peer_manager), + node_id, + addr.clone(), + proxy_addr, + self.tor_proxy_rng.clone(), + ); + self.await_connection(connection_future, node_id, addr).await + } else { + let socket_addr = addr + .to_socket_addrs() + .map_err(|e| { + log_error!(self.logger, "Failed to resolve network address {}: {}", 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( + Arc::clone(&self.peer_manager), + node_id, + socket_addr, + ); + self.await_connection(connection_future, node_id, addr).await + }; + + self.propagate_result_to_subscribers(&node_id, res); - let connection_future = lightning_net_tokio::connect_outbound( - Arc::clone(&self.peer_manager), - node_id, - socket_addr, - ); + res + } - let res = match connection_future.await { + async fn await_connection( + &self, connection_future: F, node_id: PublicKey, addr: SocketAddress, + ) -> Result<(), Error> + where + F: std::future::Future>, + CF: std::future::Future, + { + match connection_future.await { Some(connection_closed_future) => { let mut connection_closed_future = Box::pin(connection_closed_future); loop { @@ -106,11 +160,7 @@ where log_error!(self.logger, "Failed to connect to peer: {}@{}", node_id, addr); Err(Error::ConnectionFailed) }, - }; - - self.propagate_result_to_subscribers(&node_id, res); - - res + } } fn register_or_subscribe_pending_connection(