RGB-Tools / RGB-Tools/rgb-lightning-node
Integrate Tor Connection Options
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 50
- Forks
- 48
- Avg merge
- 8h 52m
- Merged PRs (30d)
- 2
Description
Enhancement: proposal to introduce Tor connection options and hybrid mode
I would like to suggest an enhancement to support connectivity via Tor. This feature will provide users the ability to connect to peers via clearnet, Tor, or a hybrid system (both) as in ^lnd 0.14.0.
Proposed Changes
- Add new arguments for Tor configuration
Introduce new arguments in theArgsstructure to specify the Tor options updating theLdkUserInfostructure to include the Tor configuration options.
#[derive(Parser)]
#[command(author, version, about, long_about = None)]
struct Args {
// Other existing arguments
// Flag to activate/deactivate Tor, default is false
#[arg(long, default_value_t = false)]
tor_active: bool,
// Address and port of the Tor SOCKS5 proxy, default is localhost:9050
#[arg(long, default_value = "localhost:9050")]
tor_socks: String,
// Flag to enable Tor stream isolation, default is false
#[arg(long, default_value_t = false)]
tor_stream_isolation: bool,
// Address and port for Tor control connections, default is localhost:9051
#[arg(long, default_value = "localhost:9051")]
tor_control: String,
// Flag to skip the Tor proxy for clearnet targets, default is false
#[arg(long, default_value_t = false)]
tor_skip_proxy_for_clearnet_targets: bool,
// Option for the password used for authentication on the Tor control port
#[arg(long)]
tor_password: Option<String>,
// Default path for the Tor onion service private key
#[arg(long, default_value_t = default_tor_private_key_path())]
tor_private_key_path: String,
}
// Function to get the default path for the Tor private key
fn default_tor_private_key_path() -> String {
env::current_dir().unwrap().to_str().unwrap().to_string()
}
pub(crate) struct LdkUserInfo {
// Other existing fields
pub(crate) tor_active: bool,
pub(crate) tor_socks: String,
pub(crate) tor_stream_isolation: bool,
pub(crate) tor_control: String,
pub(crate) tor_skip_proxy_for_clearnet_targets: bool,
pub(crate) tor_password: Option<String>,
pub(crate) tor_private_key_path: String,
}
pub(crate) fn parse_startup_args() -> Result<LdkUserInfo, AppError> {
let args = Args::parse(); // Parse the CLI arguments using clap
Ok(LdkUserInfo {
// ...
tor_active: args.tor_active,
tor_socks: args.tor_socks,
tor_stream_isolation: args.tor_stream_isolation,
tor_control: args.tor_control,
tor_skip_proxy_for_clearnet_targets: args.tor_skip_proxy_for_clearnet_targets,
tor_password: args.tor_password,
tor_private_key_path: args.tor_private_key_path,
})
}
- Enhance
ldk.rs
Update the PeerManager initialization to handle connections via both clearnet and Tor, based on the provided Tor arguments. We will integrate thetokio-sockslibrary to support Tor connections via an external Tor proxy and thetor-controllibrary to use the Tor Control Protocol (TorCP) to create an Onion Service for inbound connections again via the external Tor proxy. I do not consider it feasible at this time to use theartilibrary for integrating a Tor proxy directly into the application because it is still an unstable library and not comparable in security to the C implementation of the Tor daemon. Also,artiis not yet able to give full support to Onion Service, so we would be limited in handling inbound connections.
use tokio_socks::tcp::Socks5Stream;
use tor_control::{TorClient, TorAuthMethod};
use std::fs;
use std::path::Path;
use std::net::SocketAddr;
pub(crate) async fn start_ldk(
app_state: Arc<AppState>,
mnemonic: Mnemonic,
user_info: LdkUserInfo, // Add user_info as a parameter
) -> Result<(LdkBackgroundServices, Arc<UnlockedAppState>), APIError> {
let static_state = &app_state.static_state;
// Initialize the FeeEstimator, BroadcasterInterface, and KeysManager here...
let tor_proxy: Option<SocketAddr> = if user_info.tor_active {
Some(user_info.tor_socks.parse().expect("Invalid SOCKS proxy address"))
} else {
None
};
let tor_client = if user_info.tor_active {
// Configure Tor client
let mut tor_client_builder = TorClient::builder().control_port(user_info.tor_control);
if let Some(password) = &user_info.tor_password {
tor_client_builder = tor_client_builder.auth_password(password);
} else {
tor_client_builder = tor_client_builder.auth_none();
}
let tor_client = tor_client_builder
.build()
.await
.expect("Failed to connect to Tor control port");
// Verify the authentication method
if let Some(password) = &user_info.tor_password {
match tor_client.is_authenticated() {
Ok(true) => (),
_ => panic!("Tor password authentication failed"),
}
}
// Create or use an existing Onion Service
let onion_service = if Path::new(&user_info.tor_private_key_path).exists() {
let key_data = fs::read_to_string(&user_info.tor_private_key_path).expect("Unable to read private key file");
tor_client.add_onion_v3(key_data, 80, 80, None).await.expect("Failed to create Onion Service")
} else {
let onion_service = tor_client.create_onion_v3(80, None).await.expect("Failed to create Onion Service");
fs::write(&user_info.tor_private_key_path, &onion_service.private_key).expect("Unable to write private key file");
onion_service
};
println!("Onion Service Address: {}", onion_service.address);
Some(tor_client)
} else {
None
};
if user_info.tor_active {
// Listener for Tor connections
let peer_manager_connection_handler = peer_manager.clone();
let stop_processing = Arc::new(AtomicBool::new(false));
let stop_listen = Arc::clone(&stop_processing);
tokio::spawn(async move {
loop {
let peer_mgr = peer_manager_connection_handler.clone();
if user_info.tor_stream_isolation {
tor_client.as_ref().unwrap().signal_newnym().await.expect("Failed to create a new Tor circuit");
}
match Socks5Stream::connect(tor_proxy.unwrap(), onion_service.address).await {
Ok(stream) => {
if stop_listen.load(Ordering::Acquire) {
return;
}
tokio::spawn(async move {
lightning_net_tokio::setup_inbound(
peer_mgr.clone(),
stream.into_std().unwrap(),
)
.await;
});
}
Err(e) => {
eprintln!("Failed to connect via Tor: {:?}", e);
tokio::time::sleep(Duration::from_secs(5)).await; // Retry delay
}
}
}
});
}
if !user_info.tor_active || user_info.tor_skip_proxy_for_clearnet_targets {
// Listener for clearnet connections
let peer_manager_connection_handler = peer_manager.clone();
let listening_port = static_state.ldk_peer_listening_port;
let stop_processing = Arc::new(AtomicBool::new(false));
let stop_listen = Arc::clone(&stop_processing);
tokio::spawn(async move {
let listener = tokio::net::TcpListener::bind(format!("[::]:{}", listening_port))
.await
.expect("Failed to bind to listen port - is something else already listening on it?");
loop {
let peer_mgr = peer_manager_connection_handler.clone();
let tcp_stream = listener.accept().await.unwrap().0;
if stop_listen.load(Ordering::Acquire) {
return;
}
tokio::spawn(async move {
lightning_net_tokio::setup_inbound(
peer_mgr.clone(),
tcp_stream.into_std().unwrap(),
)
.await;
});
}
});
}
// Function to handle LDK events
async fn handle_ldk_events(
event: Event,
unlocked_state: Arc<UnlockedAppState>,
static_state: Arc<StaticState>,
tor_proxy: Option<SocketAddr>, // Added optional tor_proxy parameter
) {
match event {
Event::ConnectionNeeded { node_id, addresses } => {
tokio::spawn(async move {
for address in addresses {
if let Ok(sockaddrs) = address.to_socket_addrs() {
for addr in sockaddrs {
let pm = Arc::clone(&unlocked_state.peer_manager);
if let Some(tor_proxy) = tor_proxy {
// Handle connections via Tor
match Socks5Stream::connect(tor_proxy, addr).await {
Ok(stream) => {
if connect_peer_if_necessary(node_id, stream.into_std().unwrap(), pm).await.is_ok() {
return;
}
}
Err(e) => {
eprintln!("Failed to connect via Tor: {:?}", e);
}
}
} else {
// Handle clearnet connections
if connect_peer_if_necessary(node_id, addr, pm).await.is_ok() {
return;
}
}
}
}
}
});
}
_ => {}
}
}
// Start handling LDK events
let tor_proxy_option = if user_info.tor_active && !user_info.tor_skip_proxy_for_clearnet_targets {
Some(user_info.tor_socks.parse().expect("Invalid SOCKS proxy address"))
} else {
None
};
handle_ldk_events(event, unlocked_state, static_state, tor_proxy_option).await;
// Remaining LDK setup and startup
// (Code omitted for brevity)
Ok((
LdkBackgroundServices {
stop_processing,
peer_manager: peer_manager.clone(),
bp_exit,
background_processor: Some(background_processor),
},
unlocked_state,
))
}
Explanation of changes in start_ldk
-
Tor Configuration:
- Tor Client Setup: Configure the Tor client using tor_control::TorClient with appropriate authentication (password or none).
- Onion Service Management: Check if an onion service key exists. If not, create a new onion service and save the private key. If it exists, use the existing key to set up the service.
- Error Handling: Validate Tor authentication and handle errors if the password is incorrect or authentication fails.
- Tor Stream Isolation: If tor_stream_isolation is set to true, signal the Tor client to create a new circuit for each new connection using tor_client.signal_newnym().
-
Proxy Handling:
- SOCKS Proxy: Set up the SOCKS proxy using tokio_socks::tcp::Socks5Stream.
- Conditional Proxy Use: Only use the SOCKS proxy if tor_active is true and tor_skip_proxy_for_clearnet_targets is false. Otherwise, handle clearnet connections.
-
Listeners:
- Tor Listener: Set up a listener for connections via Tor if tor_active is true. Include logic to create a new Tor circuit if tor_stream_isolation is enabled.
- Clearnet Listener: Set up a listener for clearnet connections if tor_active is false or if tor_skip_proxy_for_clearnet_targets is true.
-
Event Handling:
- handle_ldk_events: Adjusted to optionally use the SOCKS proxy based on configuration.
Testing
To ensure the proper functioning of a minimal implementation:
- Run the node with
--tor-active trueand verify it connects via Tor. - Run the node with
--tor-active true--tor-skip-proxy-for-clearnet-targets trueand verify it can connect via both methods. - Run the node with
--tor-activefalse and verify it connects via clearnet.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the Args and LdkUserInfo definitions, then inspect ldk.rs and the start_ldk entry point to understand current peer connection setup. Trace how inbound listeners and ConnectionNeeded events are handled. Done should provide the proposed clearnet, Tor, and hybrid behavior with configurable Tor settings and preserved onion-service state, including appropriate error handling.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- backend, networking
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100