Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions bindings/ldk_node.udl
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ dictionary Config {
u64 probing_liquidity_limit_multiplier;
AnchorChannelsConfig? anchor_channels_config;
RouteParametersConfig? route_parameters;
TorConfig? tor_config;
};

dictionary AnchorChannelsConfig {
Expand Down Expand Up @@ -57,6 +58,11 @@ dictionary LSPS2ServiceConfig {
boolean client_trusts_lsp;
};

dictionary TorConfig {
SocketAddress proxy_address;
boolean proxy_all_outbound;
};

interface NodeEntropy {
[Name=from_bip39_mnemonic]
constructor(Mnemonic mnemonic, string? passphrase);
Expand Down Expand Up @@ -126,6 +132,8 @@ interface Builder {
[Throws=BuildError]
void set_announcement_addresses(sequence<SocketAddress> announcement_addresses);
[Throws=BuildError]
void set_tor_config(TorConfig tor_config);
[Throws=BuildError]
void set_node_alias(string node_alias);
[Throws=BuildError]
void set_async_payments_role(AsyncPaymentsRole? role);
Expand Down Expand Up @@ -388,6 +396,7 @@ enum BuildError {
"InvalidChannelMonitor",
"InvalidListeningAddresses",
"InvalidAnnouncementAddresses",
"InvalidTorProxyAddress",
"InvalidNodeAlias",
"RuntimeSetupFailed",
"ReadFailed",
Expand Down
40 changes: 37 additions & 3 deletions src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ use vss_client::headers::VssHeaderProvider;
use crate::chain::ChainSource;
use crate::config::{
default_user_config, may_announce_channel, AnnounceError, AsyncPaymentsRole,
BitcoindRestClientConfig, Config, ElectrumSyncConfig, EsploraSyncConfig,
BitcoindRestClientConfig, Config, ElectrumSyncConfig, EsploraSyncConfig, TorConfig,
DEFAULT_ESPLORA_SERVER_URL, DEFAULT_LOG_FILENAME, DEFAULT_LOG_LEVEL,
};
use crate::connection::ConnectionManager;
Expand Down Expand Up @@ -163,6 +163,8 @@ pub enum BuildError {
InvalidListeningAddresses,
/// The given announcement addresses are invalid, e.g. too many were passed.
InvalidAnnouncementAddresses,
/// The given tor proxy address is invalid, e.g. an onion address was passed.
InvalidTorProxyAddress,
/// The provided alias is invalid.
InvalidNodeAlias,
/// An attempt to setup a runtime has failed.
Expand Down Expand Up @@ -204,6 +206,7 @@ impl fmt::Display for BuildError {
Self::InvalidAnnouncementAddresses => {
write!(f, "Given announcement addresses are invalid.")
},
Self::InvalidTorProxyAddress => write!(f, "Given Tor proxy address is invalid."),
Self::RuntimeSetupFailed => write!(f, "Failed to setup a runtime."),
Self::ReadFailed => write!(f, "Failed to read from store."),
Self::WriteFailed => write!(f, "Failed to write to store."),
Expand Down Expand Up @@ -521,6 +524,23 @@ impl NodeBuilder {
Ok(self)
}

/// Configures the [`Node`] instance to use a Tor SOCKS proxy for some (or all) outbound connections.
/// The proxy address must not itself be an onion address.
///
///
/// **Note**: If unset, connecting to peer OnionV3 addresses will fail.
pub fn set_tor_config(&mut self, tor_config: TorConfig) -> Result<&mut Self, BuildError> {
match tor_config.proxy_address {
SocketAddress::OnionV2 { .. } | SocketAddress::OnionV3 { .. } => {
return Err(BuildError::InvalidTorProxyAddress);
},
_ => {},
}

self.config.tor_config = Some(tor_config);
Ok(self)
}

/// Sets the node alias that will be used when broadcasting announcements to the gossip
/// network.
///
Expand Down Expand Up @@ -918,6 +938,14 @@ impl ArcedNodeBuilder {
self.inner.write().unwrap().set_announcement_addresses(announcement_addresses).map(|_| ())
}

/// Configures the [`Node`] instance to use a Tor SOCKS proxy for some (or all) outbound connections.
/// The proxy address must not itself be an onion address.
///
/// **Note**: If unset, connecting to peer OnionV3 addresses will fail.
pub fn set_tor_config(&self, tor_config: TorConfig) -> Result<(), BuildError> {
self.inner.write().unwrap().set_tor_config(tor_config).map(|_| ())
}

/// Sets the node alias that will be used when broadcasting announcements to the gossip
/// network.
///
Expand Down Expand Up @@ -1711,8 +1739,14 @@ 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)));
// Use a different RNG seed for the ConnectionManager
let ephemeral_bytes: [u8; 32] = keys_manager.get_secure_random_bytes();
let connection_manager = Arc::new(ConnectionManager::new(
Arc::clone(&peer_manager),
config.tor_config.clone(),
ephemeral_bytes,
Arc::clone(&logger),
));

let output_sweeper = match sweeper_bytes_res {
Ok(output_sweeper) => Arc::new(output_sweeper),
Expand Down
18 changes: 18 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,13 @@ pub struct Config {
/// **Note:** If unset, default parameters will be used, and you will be able to override the
/// parameters on a per-payment basis in the corresponding method calls.
pub route_parameters: Option<RouteParametersConfig>,
/// Configuration options for enabling peer connections via the Tor network.
///
/// Setting [`TorConfig`] enables connecting to Tor-only peers. Please refer to [`TorConfig`]
/// for further information.
///
/// **Note**: If unset, connecting to peer OnionV3 addresses will fail.
pub tor_config: Option<TorConfig>,
}

impl Default for Config {
Expand All @@ -204,6 +211,7 @@ impl Default for Config {
trusted_peers_0conf: Vec::new(),
probing_liquidity_limit_multiplier: DEFAULT_PROBING_LIQUIDITY_LIMIT_MULTIPLIER,
anchor_channels_config: Some(AnchorChannelsConfig::default()),
tor_config: None,
route_parameters: None,
node_alias: None,
}
Expand Down Expand Up @@ -478,6 +486,16 @@ pub struct BitcoindRestClientConfig {
pub rest_port: u16,
}

/// Configuration for connecting to peers via the Tor Network.
#[derive(Debug, Clone)]
pub struct TorConfig {
/// Tor daemon SOCKS proxy address.
pub proxy_address: SocketAddress,

/// If set, all outbound peer connections will be made via the Tor SOCKS proxy.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I do wonder if we want to expose this right now, as it could be misleading given that all other connections (like Esplora/Eleectrum syncing, RGS updates, etc) would still go over clearnet? Maybe we should make this clear in the TorConfig docs more generally, and only expose this flag once we're positive we can switch to tunnel everything over Tor?

pub proxy_all_outbound: bool,
}

/// Options which apply on a per-channel basis and may change at runtime or based on negotiation
/// with our counterparty.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
Expand Down
185 changes: 160 additions & 25 deletions src/connection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ use std::time::Duration;

use bitcoin::secp256k1::PublicKey;
use lightning::ln::msgs::SocketAddress;
use lightning::sign::RandomBytes;

use crate::config::TorConfig;
use crate::logger::{log_error, log_info, LdkLogger};
use crate::types::PeerManager;
use crate::Error;
Expand All @@ -25,16 +27,23 @@ where
pending_connections:
Mutex<HashMap<PublicKey, Vec<tokio::sync::oneshot::Sender<Result<(), Error>>>>>,
peer_manager: Arc<PeerManager>,
tor_proxy_config: Option<TorConfig>,
tor_proxy_rng: Arc<RandomBytes>,
logger: L,
}

impl<L: Deref + Clone + Sync + Send> ConnectionManager<L>
where
L::Target: LdkLogger,
{
pub(crate) fn new(peer_manager: Arc<PeerManager>, logger: L) -> Self {
pub(crate) fn new(
peer_manager: Arc<PeerManager>, tor_proxy_config: Option<TorConfig>,
ephemeral_random_data: [u8; 32], logger: L,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's just have this take a KeysManager reference and rederive RandomBytes when needed?

) -> 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_config, tor_proxy_rng, logger }
}

pub(crate) async fn connect_peer_if_necessary(
Expand Down Expand Up @@ -64,27 +73,157 @@ 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 = 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));
Error::InvalidSocketAddress
})?;
return Err(Error::InvalidSocketAddress);
},
SocketAddress::OnionV3 { .. } => {
let proxy_config = self.tor_proxy_config.as_ref().ok_or_else(|| {
log_error!(
self.logger,
"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
.proxy_address
.to_socket_addrs()
.map_err(|e| {
log_error!(
self.logger,
"Failed to resolve Tor proxy network address {}: {}",
proxy_config.proxy_address,
e
);
self.propagate_result_to_subscribers(
&node_id,
Err(Error::InvalidSocketAddress),
);
Error::InvalidSocketAddress
})?
.next()
.ok_or_else(|| {
log_error!(
self.logger,
"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(
Arc::clone(&self.peer_manager),
node_id,
addr.clone(),
proxy_addr,
Arc::clone(&self.tor_proxy_rng),
);
self.await_connection(connection_future, node_id, addr).await
},
_ => {
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
})?;
match &self.tor_proxy_config {
None | Some(TorConfig { proxy_all_outbound: false, .. }) => {
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
},
Some(proxy_config) => {
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
);
self.propagate_result_to_subscribers(
&node_id,
Err(Error::InvalidSocketAddress),
);
Error::InvalidSocketAddress
})?
.next()
.ok_or_else(|| {
log_error!(
self.logger,
"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(
Arc::clone(&self.peer_manager),
node_id,
addr.clone(),
proxy_addr,
Arc::clone(&self.tor_proxy_rng),
);
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<F, CF>(
&self, connection_future: F, node_id: PublicKey, addr: SocketAddress,
) -> Result<(), Error>
where
F: std::future::Future<Output = Option<CF>>,
CF: std::future::Future<Output = ()>,
{
match connection_future.await {
Some(connection_closed_future) => {
let mut connection_closed_future = Box::pin(connection_closed_future);
loop {
Expand All @@ -106,11 +245,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(
Expand Down
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,8 @@ pub use builder::BuildError;
#[cfg(not(feature = "uniffi"))]
pub use builder::NodeBuilder as Builder;
use chain::ChainSource;
#[cfg(feature = "uniffi")]
use config::TorConfig;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please just move this to the exports in the beginning of src/ffi/types.rs IIUC that should suffice to make uniffi happy.

use config::{
default_user_config, may_announce_channel, AsyncPaymentsRole, ChannelConfig, Config,
NODE_ANN_BCAST_INTERVAL, PEER_RECONNECTION_INTERVAL, RGS_SYNC_INTERVAL,
Expand Down
Loading