Skip to content

Commit 46b2f60

Browse files
committed
Add Tor support for outbound connections via SOCKS
1 parent 7ad0d63 commit 46b2f60

File tree

5 files changed

+226
-28
lines changed

5 files changed

+226
-28
lines changed

bindings/ldk_node.udl

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ dictionary Config {
1313
u64 probing_liquidity_limit_multiplier;
1414
AnchorChannelsConfig? anchor_channels_config;
1515
RouteParametersConfig? route_parameters;
16+
TorConfig? tor_config;
1617
};
1718

1819
dictionary AnchorChannelsConfig {
@@ -57,6 +58,11 @@ dictionary LSPS2ServiceConfig {
5758
boolean client_trusts_lsp;
5859
};
5960

61+
dictionary TorConfig {
62+
SocketAddress proxy_address;
63+
boolean proxy_all_outbound;
64+
};
65+
6066
interface NodeEntropy {
6167
[Name=from_bip39_mnemonic]
6268
constructor(Mnemonic mnemonic, string? passphrase);
@@ -126,6 +132,8 @@ interface Builder {
126132
[Throws=BuildError]
127133
void set_announcement_addresses(sequence<SocketAddress> announcement_addresses);
128134
[Throws=BuildError]
135+
void set_tor_config(TorConfig tor_config);
136+
[Throws=BuildError]
129137
void set_node_alias(string node_alias);
130138
[Throws=BuildError]
131139
void set_async_payments_role(AsyncPaymentsRole? role);
@@ -388,6 +396,7 @@ enum BuildError {
388396
"InvalidChannelMonitor",
389397
"InvalidListeningAddresses",
390398
"InvalidAnnouncementAddresses",
399+
"InvalidTorProxyAddress",
391400
"InvalidNodeAlias",
392401
"RuntimeSetupFailed",
393402
"ReadFailed",

src/builder.rs

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ use vss_client::headers::VssHeaderProvider;
4545
use crate::chain::ChainSource;
4646
use crate::config::{
4747
default_user_config, may_announce_channel, AnnounceError, AsyncPaymentsRole,
48-
BitcoindRestClientConfig, Config, ElectrumSyncConfig, EsploraSyncConfig,
48+
BitcoindRestClientConfig, Config, ElectrumSyncConfig, EsploraSyncConfig, TorConfig,
4949
DEFAULT_ESPLORA_SERVER_URL, DEFAULT_LOG_FILENAME, DEFAULT_LOG_LEVEL,
5050
};
5151
use crate::connection::ConnectionManager;
@@ -163,6 +163,8 @@ pub enum BuildError {
163163
InvalidListeningAddresses,
164164
/// The given announcement addresses are invalid, e.g. too many were passed.
165165
InvalidAnnouncementAddresses,
166+
/// The given tor proxy address is invalid, e.g. an onion address was passed.
167+
InvalidTorProxyAddress,
166168
/// The provided alias is invalid.
167169
InvalidNodeAlias,
168170
/// An attempt to setup a runtime has failed.
@@ -204,6 +206,7 @@ impl fmt::Display for BuildError {
204206
Self::InvalidAnnouncementAddresses => {
205207
write!(f, "Given announcement addresses are invalid.")
206208
},
209+
Self::InvalidTorProxyAddress => write!(f, "Given Tor proxy address is invalid."),
207210
Self::RuntimeSetupFailed => write!(f, "Failed to setup a runtime."),
208211
Self::ReadFailed => write!(f, "Failed to read from store."),
209212
Self::WriteFailed => write!(f, "Failed to write to store."),
@@ -521,6 +524,23 @@ impl NodeBuilder {
521524
Ok(self)
522525
}
523526

527+
/// Configures the [`Node`] instance to use a Tor SOCKS proxy for some (or all) outbound connections.
528+
/// The proxy address must not itself be an onion address.
529+
///
530+
///
531+
/// **Note**: If unset, connecting to peer OnionV3 addresses will fail.
532+
pub fn set_tor_config(&mut self, tor_config: TorConfig) -> Result<&mut Self, BuildError> {
533+
match tor_config.proxy_address {
534+
SocketAddress::OnionV2 { .. } | SocketAddress::OnionV3 { .. } => {
535+
return Err(BuildError::InvalidTorProxyAddress);
536+
},
537+
_ => {},
538+
}
539+
540+
self.config.tor_config = Some(tor_config);
541+
Ok(self)
542+
}
543+
524544
/// Sets the node alias that will be used when broadcasting announcements to the gossip
525545
/// network.
526546
///
@@ -918,6 +938,14 @@ impl ArcedNodeBuilder {
918938
self.inner.write().unwrap().set_announcement_addresses(announcement_addresses).map(|_| ())
919939
}
920940

941+
/// Configures the [`Node`] instance to use a Tor SOCKS proxy for some (or all) outbound connections.
942+
/// The proxy address must not itself be an onion address.
943+
///
944+
/// **Note**: If unset, connecting to peer OnionV3 addresses will fail.
945+
pub fn set_tor_config(&self, tor_config: TorConfig) -> Result<(), BuildError> {
946+
self.inner.write().unwrap().set_tor_config(tor_config).map(|_| ())
947+
}
948+
921949
/// Sets the node alias that will be used when broadcasting announcements to the gossip
922950
/// network.
923951
///
@@ -1711,8 +1739,14 @@ fn build_with_store_internal(
17111739

17121740
liquidity_source.as_ref().map(|l| l.set_peer_manager(Arc::downgrade(&peer_manager)));
17131741

1714-
let connection_manager =
1715-
Arc::new(ConnectionManager::new(Arc::clone(&peer_manager), Arc::clone(&logger)));
1742+
// Use a different RNG seed for the ConnectionManager
1743+
let ephemeral_bytes: [u8; 32] = keys_manager.get_secure_random_bytes();
1744+
let connection_manager = Arc::new(ConnectionManager::new(
1745+
Arc::clone(&peer_manager),
1746+
config.tor_config.clone(),
1747+
ephemeral_bytes,
1748+
Arc::clone(&logger),
1749+
));
17161750

17171751
let output_sweeper = match sweeper_bytes_res {
17181752
Ok(output_sweeper) => Arc::new(output_sweeper),

src/config.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,13 @@ pub struct Config {
192192
/// **Note:** If unset, default parameters will be used, and you will be able to override the
193193
/// parameters on a per-payment basis in the corresponding method calls.
194194
pub route_parameters: Option<RouteParametersConfig>,
195+
/// Configuration options for enabling peer connections via the Tor network.
196+
///
197+
/// Setting [`TorConfig`] enables connecting to Tor-only peers. Please refer to [`TorConfig`]
198+
/// for further information.
199+
///
200+
/// **Note**: If unset, connecting to peer OnionV3 addresses will fail.
201+
pub tor_config: Option<TorConfig>,
195202
}
196203

197204
impl Default for Config {
@@ -204,6 +211,7 @@ impl Default for Config {
204211
trusted_peers_0conf: Vec::new(),
205212
probing_liquidity_limit_multiplier: DEFAULT_PROBING_LIQUIDITY_LIMIT_MULTIPLIER,
206213
anchor_channels_config: Some(AnchorChannelsConfig::default()),
214+
tor_config: None,
207215
route_parameters: None,
208216
node_alias: None,
209217
}
@@ -478,6 +486,16 @@ pub struct BitcoindRestClientConfig {
478486
pub rest_port: u16,
479487
}
480488

489+
/// Configuration for connecting to peers via the Tor Network.
490+
#[derive(Debug, Clone)]
491+
pub struct TorConfig {
492+
/// Tor daemon SOCKS proxy address.
493+
pub proxy_address: SocketAddress,
494+
495+
/// If set, all outbound peer connections will be made via the Tor SOCKS proxy.
496+
pub proxy_all_outbound: bool,
497+
}
498+
481499
/// Options which apply on a per-channel basis and may change at runtime or based on negotiation
482500
/// with our counterparty.
483501
#[derive(Copy, Clone, Debug, PartialEq, Eq)]

src/connection.rs

Lines changed: 160 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@ use std::time::Duration;
1313

1414
use bitcoin::secp256k1::PublicKey;
1515
use lightning::ln::msgs::SocketAddress;
16+
use lightning::sign::RandomBytes;
1617

18+
use crate::config::TorConfig;
1719
use crate::logger::{log_error, log_info, LdkLogger};
1820
use crate::types::PeerManager;
1921
use crate::Error;
@@ -25,16 +27,23 @@ where
2527
pending_connections:
2628
Mutex<HashMap<PublicKey, Vec<tokio::sync::oneshot::Sender<Result<(), Error>>>>>,
2729
peer_manager: Arc<PeerManager>,
30+
tor_proxy_config: Option<TorConfig>,
31+
tor_proxy_rng: Arc<RandomBytes>,
2832
logger: L,
2933
}
3034

3135
impl<L: Deref + Clone + Sync + Send> ConnectionManager<L>
3236
where
3337
L::Target: LdkLogger,
3438
{
35-
pub(crate) fn new(peer_manager: Arc<PeerManager>, logger: L) -> Self {
39+
pub(crate) fn new(
40+
peer_manager: Arc<PeerManager>, tor_proxy_config: Option<TorConfig>,
41+
ephemeral_random_data: [u8; 32], logger: L,
42+
) -> Self {
3643
let pending_connections = Mutex::new(HashMap::new());
37-
Self { pending_connections, peer_manager, logger }
44+
let tor_proxy_rng = Arc::new(RandomBytes::new(ephemeral_random_data));
45+
46+
Self { pending_connections, peer_manager, tor_proxy_config, tor_proxy_rng, logger }
3847
}
3948

4049
pub(crate) async fn connect_peer_if_necessary(
@@ -64,27 +73,157 @@ where
6473

6574
log_info!(self.logger, "Connecting to peer: {}@{}", node_id, addr);
6675

67-
let socket_addr = addr
68-
.to_socket_addrs()
69-
.map_err(|e| {
70-
log_error!(self.logger, "Failed to resolve network address {}: {}", addr, e);
71-
self.propagate_result_to_subscribers(&node_id, Err(Error::InvalidSocketAddress));
72-
Error::InvalidSocketAddress
73-
})?
74-
.next()
75-
.ok_or_else(|| {
76-
log_error!(self.logger, "Failed to resolve network address {}", addr);
76+
let res = match addr {
77+
SocketAddress::OnionV2(old_onion_addr) => {
78+
log_error!(
79+
self.logger,
80+
"Failed to resolve network address {:?}: Resolution of OnionV2 addresses is currently unsupported.",
81+
old_onion_addr
82+
);
7783
self.propagate_result_to_subscribers(&node_id, Err(Error::InvalidSocketAddress));
78-
Error::InvalidSocketAddress
79-
})?;
84+
return Err(Error::InvalidSocketAddress);
85+
},
86+
SocketAddress::OnionV3 { .. } => {
87+
let proxy_config = self.tor_proxy_config.as_ref().ok_or_else(|| {
88+
log_error!(
89+
self.logger,
90+
"Failed to resolve network address {:?}: Tor usage is not configured.",
91+
addr
92+
);
93+
self.propagate_result_to_subscribers(
94+
&node_id,
95+
Err(Error::InvalidSocketAddress),
96+
);
97+
Error::InvalidSocketAddress
98+
})?;
99+
let proxy_addr = proxy_config
100+
.proxy_address
101+
.to_socket_addrs()
102+
.map_err(|e| {
103+
log_error!(
104+
self.logger,
105+
"Failed to resolve Tor proxy network address {}: {}",
106+
proxy_config.proxy_address,
107+
e
108+
);
109+
self.propagate_result_to_subscribers(
110+
&node_id,
111+
Err(Error::InvalidSocketAddress),
112+
);
113+
Error::InvalidSocketAddress
114+
})?
115+
.next()
116+
.ok_or_else(|| {
117+
log_error!(
118+
self.logger,
119+
"Failed to resolve Tor proxy network address {}",
120+
proxy_config.proxy_address
121+
);
122+
self.propagate_result_to_subscribers(
123+
&node_id,
124+
Err(Error::InvalidSocketAddress),
125+
);
126+
Error::InvalidSocketAddress
127+
})?;
128+
let connection_future = lightning_net_tokio::tor_connect_outbound(
129+
Arc::clone(&self.peer_manager),
130+
node_id,
131+
addr.clone(),
132+
proxy_addr,
133+
Arc::clone(&self.tor_proxy_rng),
134+
);
135+
self.await_connection(connection_future, node_id, addr).await
136+
},
137+
_ => {
138+
let socket_addr = addr
139+
.to_socket_addrs()
140+
.map_err(|e| {
141+
log_error!(
142+
self.logger,
143+
"Failed to resolve network address {}: {}",
144+
addr,
145+
e
146+
);
147+
self.propagate_result_to_subscribers(
148+
&node_id,
149+
Err(Error::InvalidSocketAddress),
150+
);
151+
Error::InvalidSocketAddress
152+
})?
153+
.next()
154+
.ok_or_else(|| {
155+
log_error!(self.logger, "Failed to resolve network address {}", addr);
156+
self.propagate_result_to_subscribers(
157+
&node_id,
158+
Err(Error::InvalidSocketAddress),
159+
);
160+
Error::InvalidSocketAddress
161+
})?;
162+
match &self.tor_proxy_config {
163+
None | Some(TorConfig { proxy_all_outbound: false, .. }) => {
164+
let connection_future = lightning_net_tokio::connect_outbound(
165+
Arc::clone(&self.peer_manager),
166+
node_id,
167+
socket_addr,
168+
);
169+
self.await_connection(connection_future, node_id, addr).await
170+
},
171+
Some(proxy_config) => {
172+
let proxy_addr = proxy_config
173+
.proxy_address
174+
.to_socket_addrs()
175+
.map_err(|e| {
176+
log_error!(
177+
self.logger,
178+
"Failed to resolve Tor proxy network address {}: {}",
179+
proxy_config.proxy_address,
180+
e
181+
);
182+
self.propagate_result_to_subscribers(
183+
&node_id,
184+
Err(Error::InvalidSocketAddress),
185+
);
186+
Error::InvalidSocketAddress
187+
})?
188+
.next()
189+
.ok_or_else(|| {
190+
log_error!(
191+
self.logger,
192+
"Failed to resolve Tor proxy network address {}",
193+
proxy_config.proxy_address
194+
);
195+
self.propagate_result_to_subscribers(
196+
&node_id,
197+
Err(Error::InvalidSocketAddress),
198+
);
199+
Error::InvalidSocketAddress
200+
})?;
201+
let connection_future = lightning_net_tokio::tor_connect_outbound(
202+
Arc::clone(&self.peer_manager),
203+
node_id,
204+
addr.clone(),
205+
proxy_addr,
206+
Arc::clone(&self.tor_proxy_rng),
207+
);
208+
self.await_connection(connection_future, node_id, addr).await
209+
},
210+
}
211+
},
212+
};
213+
214+
self.propagate_result_to_subscribers(&node_id, res);
80215

81-
let connection_future = lightning_net_tokio::connect_outbound(
82-
Arc::clone(&self.peer_manager),
83-
node_id,
84-
socket_addr,
85-
);
216+
res
217+
}
86218

87-
let res = match connection_future.await {
219+
async fn await_connection<F, CF>(
220+
&self, connection_future: F, node_id: PublicKey, addr: SocketAddress,
221+
) -> Result<(), Error>
222+
where
223+
F: std::future::Future<Output = Option<CF>>,
224+
CF: std::future::Future<Output = ()>,
225+
{
226+
match connection_future.await {
88227
Some(connection_closed_future) => {
89228
let mut connection_closed_future = Box::pin(connection_closed_future);
90229
loop {
@@ -106,11 +245,7 @@ where
106245
log_error!(self.logger, "Failed to connect to peer: {}@{}", node_id, addr);
107246
Err(Error::ConnectionFailed)
108247
},
109-
};
110-
111-
self.propagate_result_to_subscribers(&node_id, res);
112-
113-
res
248+
}
114249
}
115250

116251
fn register_or_subscribe_pending_connection(

src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,8 @@ pub use builder::BuildError;
122122
#[cfg(not(feature = "uniffi"))]
123123
pub use builder::NodeBuilder as Builder;
124124
use chain::ChainSource;
125+
#[cfg(feature = "uniffi")]
126+
use config::TorConfig;
125127
use config::{
126128
default_user_config, may_announce_channel, AsyncPaymentsRole, ChannelConfig, Config,
127129
NODE_ANN_BCAST_INTERVAL, PEER_RECONNECTION_INTERVAL, RGS_SYNC_INTERVAL,

0 commit comments

Comments
 (0)