mirror of
https://github.com/comit-network/xmr-btc-swap.git
synced 2025-08-08 14:32:24 -04:00
feat(asb, cli): Listen on onion address, dial onion addresses (#203)
This pull requests - Adds rust native support for the `asb` to listen on an onion service. Previously we were depedent on a seperately running `torc` client. Instead we now use [arti](https://tpo.pages.torproject.net/core/arti/), a rust implementation of the tor protocol. - Removes the `tor.control_port` and `tor.socks5_port` property from the config of the `asb` - Adds a new `tor.register_hidden_service` boolean property to the config of the `asb` which when enabled automatically runs a hidden service at startup - Adds a new `tor.hidden_service_num_intro_points` config property to specify how many introduction points to register the onion service at - Adds support for the `cli` to dial onion addresses This is dependent on https://github.com/umgefahren/libp2p-tor/pull/24 Closes https://github.com/UnstoppableSwap/core/issues/16
This commit is contained in:
parent
45a4cf4fb7
commit
d53c12d64e
18 changed files with 427 additions and 387 deletions
|
@ -42,7 +42,7 @@ hex = "0.4"
|
|||
jsonrpsee = { version = "0.16.2", features = [ "server" ] }
|
||||
jsonrpsee-core = "0.16.2"
|
||||
libp2p = { version = "0.53.2", features = [ "tcp", "yamux", "dns", "noise", "request-response", "ping", "rendezvous", "identify", "macros", "cbor", "json", "tokio", "serde", "rsa" ] }
|
||||
libp2p-community-tor = { git = "https://github.com/UnstoppableSwap/libp2p-tor", branch = "main" }
|
||||
libp2p-community-tor = { git = "https://github.com/UnstoppableSwap/libp2p-tor", branch = "fix/announce-address-immed", features = [ "listen-onion-service" ] }
|
||||
monero = { version = "0.12", features = [ "serde_support" ] }
|
||||
monero-rpc = { path = "../monero-rpc" }
|
||||
once_cell = "1.19"
|
||||
|
@ -96,10 +96,6 @@ tokio-tungstenite = { version = "0.15", features = [ "rustls-tls" ] }
|
|||
tokio-util = { version = "0.7", features = [ "io", "codec" ] }
|
||||
toml = "0.8"
|
||||
tor-rtcompat = "0.24.0"
|
||||
torut = { version = "0.2", default-features = false, features = [
|
||||
"v3",
|
||||
"control",
|
||||
] }
|
||||
tower = { version = "0.4.13", features = [ "full" ] }
|
||||
tower-http = { version = "0.3.4", features = [ "full" ] }
|
||||
tracing = { version = "0.1", features = [ "attributes" ] }
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
use crate::env::{Mainnet, Testnet};
|
||||
use crate::fs::{ensure_directory_exists, system_config_dir, system_data_dir};
|
||||
use crate::tor::{DEFAULT_CONTROL_PORT, DEFAULT_SOCKS5_PORT};
|
||||
use anyhow::{bail, Context, Result};
|
||||
use config::ConfigError;
|
||||
use dialoguer::theme::ColorfulTheme;
|
||||
use dialoguer::Input;
|
||||
use dialoguer::{Input, Select};
|
||||
use libp2p::core::Multiaddr;
|
||||
use rust_decimal::prelude::FromPrimitive;
|
||||
use rust_decimal::Decimal;
|
||||
|
@ -200,8 +199,8 @@ pub struct Monero {
|
|||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct TorConf {
|
||||
pub control_port: u16,
|
||||
pub socks5_port: u16,
|
||||
pub register_hidden_service: bool,
|
||||
pub hidden_service_num_intro_points: u8,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
|
||||
|
@ -219,8 +218,8 @@ pub struct Maker {
|
|||
impl Default for TorConf {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
control_port: DEFAULT_CONTROL_PORT,
|
||||
socks5_port: DEFAULT_SOCKS5_PORT,
|
||||
register_hidden_service: true,
|
||||
hidden_service_num_intro_points: 5,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -313,15 +312,12 @@ pub fn query_user_for_initial_config(testnet: bool) -> Result<Config> {
|
|||
.default(defaults.monero_wallet_rpc_url)
|
||||
.interact_text()?;
|
||||
|
||||
let tor_control_port = Input::with_theme(&ColorfulTheme::default())
|
||||
.with_prompt("Enter Tor control port or hit enter to use default. If Tor is not running on your machine, no hidden service will be created.")
|
||||
.default(DEFAULT_CONTROL_PORT.to_owned())
|
||||
.interact_text()?;
|
||||
|
||||
let tor_socks5_port = Input::with_theme(&ColorfulTheme::default())
|
||||
.with_prompt("Enter Tor socks5 port or hit enter to use default")
|
||||
.default(DEFAULT_SOCKS5_PORT.to_owned())
|
||||
.interact_text()?;
|
||||
let register_hidden_service = Select::with_theme(&ColorfulTheme::default())
|
||||
.with_prompt("Do you want a Tor hidden service to be created? This will allow you to run from behind a firewall without opening ports, and hide your IP address. You do not have to run a Tor daemon yourself. We recommend this for most users. (y/n)")
|
||||
.items(&["yes", "no"])
|
||||
.default(0)
|
||||
.interact()?
|
||||
== 0;
|
||||
|
||||
let min_buy = Input::with_theme(&ColorfulTheme::default())
|
||||
.with_prompt("Enter minimum Bitcoin amount you are willing to accept per swap or hit enter to use default.")
|
||||
|
@ -387,8 +383,8 @@ pub fn query_user_for_initial_config(testnet: bool) -> Result<Config> {
|
|||
network: monero_network,
|
||||
},
|
||||
tor: TorConf {
|
||||
control_port: tor_control_port,
|
||||
socks5_port: tor_socks5_port,
|
||||
register_hidden_service,
|
||||
..Default::default()
|
||||
},
|
||||
maker: Maker {
|
||||
min_buy_btc: min_buy,
|
||||
|
|
|
@ -490,7 +490,7 @@ where
|
|||
)
|
||||
})?;
|
||||
|
||||
tracing::debug!(%ask_price, %xmr_balance, %max_bitcoin_for_monero, "Computed quote");
|
||||
tracing::trace!(%ask_price, %xmr_balance, %max_bitcoin_for_monero, "Computed quote");
|
||||
|
||||
if min_buy > max_bitcoin_for_monero {
|
||||
tracing::trace!(
|
||||
|
|
|
@ -22,16 +22,81 @@ use std::time::Duration;
|
|||
use uuid::Uuid;
|
||||
|
||||
pub mod transport {
|
||||
use libp2p::{dns, identity, tcp, Transport};
|
||||
use std::sync::Arc;
|
||||
|
||||
use arti_client::{config::onion_service::OnionServiceConfigBuilder, TorClient};
|
||||
use libp2p::{core::transport::OptionalTransport, dns, identity, tcp, Transport};
|
||||
use libp2p_community_tor::AddressConversion;
|
||||
use tor_rtcompat::tokio::TokioRustlsRuntime;
|
||||
|
||||
use super::*;
|
||||
|
||||
static ASB_ONION_SERVICE_NICKNAME: &str = "asb";
|
||||
static ASB_ONION_SERVICE_PORT: u16 = 9939;
|
||||
|
||||
type OnionTransportWithAddresses = (Boxed<(PeerId, StreamMuxerBox)>, Vec<Multiaddr>);
|
||||
|
||||
/// Creates the libp2p transport for the ASB.
|
||||
pub fn new(identity: &identity::Keypair) -> Result<Boxed<(PeerId, StreamMuxerBox)>> {
|
||||
let tcp = tcp::tokio::Transport::new(tcp::Config::new().nodelay(true));
|
||||
///
|
||||
/// If you pass in a `None` for `maybe_tor_client`, the ASB will not use Tor at all.
|
||||
///
|
||||
/// If you pass in a `Some(tor_client)`, the ASB will listen on an onion service and return
|
||||
/// the onion address. If it fails to listen on the onion address, it will only use tor for
|
||||
/// dialing and not listening.
|
||||
pub fn new(
|
||||
identity: &identity::Keypair,
|
||||
maybe_tor_client: Option<Arc<TorClient<TokioRustlsRuntime>>>,
|
||||
num_intro_points: u8,
|
||||
register_hidden_service: bool,
|
||||
) -> Result<OnionTransportWithAddresses> {
|
||||
let (maybe_tor_transport, onion_addresses) = if let Some(tor_client) = maybe_tor_client {
|
||||
let mut tor_transport = libp2p_community_tor::TorTransport::from_client(
|
||||
tor_client,
|
||||
AddressConversion::DnsOnly,
|
||||
);
|
||||
|
||||
let addresses = if register_hidden_service {
|
||||
let onion_service_config = OnionServiceConfigBuilder::default()
|
||||
.nickname(
|
||||
ASB_ONION_SERVICE_NICKNAME
|
||||
.parse()
|
||||
.expect("Static nickname to be valid"),
|
||||
)
|
||||
.num_intro_points(num_intro_points)
|
||||
.build()
|
||||
.expect("We specified a valid nickname");
|
||||
|
||||
match tor_transport.add_onion_service(onion_service_config, ASB_ONION_SERVICE_PORT)
|
||||
{
|
||||
Ok(addr) => {
|
||||
tracing::debug!(
|
||||
%addr,
|
||||
"Setting up onion service for libp2p to listen on"
|
||||
);
|
||||
vec![addr]
|
||||
}
|
||||
Err(err) => {
|
||||
tracing::warn!(error=%err, "Failed to listen on onion address");
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
|
||||
(OptionalTransport::some(tor_transport), addresses)
|
||||
} else {
|
||||
(OptionalTransport::none(), vec![])
|
||||
};
|
||||
|
||||
let tcp = maybe_tor_transport
|
||||
.or_transport(tcp::tokio::Transport::new(tcp::Config::new().nodelay(true)));
|
||||
let tcp_with_dns = dns::tokio::Transport::system(tcp)?;
|
||||
|
||||
authenticate_and_multiplex(tcp_with_dns.boxed(), identity)
|
||||
Ok((
|
||||
authenticate_and_multiplex(tcp_with_dns.boxed(), identity)?,
|
||||
onion_addresses,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -14,12 +14,9 @@
|
|||
|
||||
use anyhow::{bail, Context, Result};
|
||||
use comfy_table::Table;
|
||||
use libp2p::core::multiaddr::Protocol;
|
||||
use libp2p::core::Multiaddr;
|
||||
use libp2p::Swarm;
|
||||
use std::convert::TryInto;
|
||||
use std::env;
|
||||
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
|
||||
use std::sync::Arc;
|
||||
use structopt::clap;
|
||||
use structopt::clap::ErrorKind;
|
||||
|
@ -28,6 +25,7 @@ use swap::asb::config::{
|
|||
initial_setup, query_user_for_initial_config, read_config, Config, ConfigNotInitialized,
|
||||
};
|
||||
use swap::asb::{cancel, punish, redeem, refund, safely_abort, EventLoop, Finality, KrakenRate};
|
||||
use swap::common::tor::init_tor_client;
|
||||
use swap::common::tracing_util::Format;
|
||||
use swap::common::{self, get_logs, warn_if_outdated};
|
||||
use swap::database::{open_db, AccessMode};
|
||||
|
@ -35,8 +33,7 @@ use swap::network::rendezvous::XmrBtcNamespace;
|
|||
use swap::network::swarm;
|
||||
use swap::protocol::alice::{run, AliceState};
|
||||
use swap::seed::Seed;
|
||||
use swap::tor::AuthenticatedClient;
|
||||
use swap::{bitcoin, kraken, monero, tor};
|
||||
use swap::{bitcoin, kraken, monero};
|
||||
use tracing_subscriber::filter::LevelFilter;
|
||||
|
||||
const DEFAULT_WALLET_NAME: &str = "asb-wallet";
|
||||
|
@ -149,29 +146,16 @@ pub async fn main() -> Result<()> {
|
|||
let bitcoin_balance = bitcoin_wallet.balance().await?;
|
||||
tracing::info!(%bitcoin_balance, "Bitcoin wallet balance");
|
||||
|
||||
// Connect to Kraken
|
||||
let kraken_price_updates = kraken::connect(config.maker.price_ticker_ws_url.clone())?;
|
||||
|
||||
// Setup Tor hidden services
|
||||
let tor_client =
|
||||
tor::Client::new(config.tor.socks5_port).with_control_port(config.tor.control_port);
|
||||
let _ac = match tor_client.assert_tor_running().await {
|
||||
Ok(_) => {
|
||||
tracing::info!("Setting up Tor hidden service");
|
||||
let ac =
|
||||
register_tor_services(config.network.clone().listen, tor_client, &seed)
|
||||
.await?;
|
||||
Some(ac)
|
||||
}
|
||||
Err(_) => {
|
||||
tracing::warn!("Tor not found. Running on clear net");
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
let kraken_rate = KrakenRate::new(config.maker.ask_spread, kraken_price_updates);
|
||||
let namespace = XmrBtcNamespace::from_is_testnet(testnet);
|
||||
|
||||
let mut swarm = swarm::asb(
|
||||
// Initialize Tor client
|
||||
let tor_client = init_tor_client(&config.data.dir).await?.into();
|
||||
|
||||
let (mut swarm, onion_addresses) = swarm::asb(
|
||||
&seed,
|
||||
config.maker.min_buy_btc,
|
||||
config.maker.max_buy_btc,
|
||||
|
@ -180,6 +164,8 @@ pub async fn main() -> Result<()> {
|
|||
env_config,
|
||||
namespace,
|
||||
&rendezvous_addrs,
|
||||
tor_client,
|
||||
config.tor,
|
||||
)?;
|
||||
|
||||
for listen in config.network.listen.clone() {
|
||||
|
@ -188,10 +174,25 @@ pub async fn main() -> Result<()> {
|
|||
}
|
||||
}
|
||||
|
||||
for onion_address in onion_addresses {
|
||||
match swarm.listen_on(onion_address.clone()) {
|
||||
Err(e) => {
|
||||
tracing::warn!(
|
||||
"Failed to listen on onion address {}: {}",
|
||||
onion_address,
|
||||
e
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
swarm.add_external_address(onion_address);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tracing::info!(peer_id = %swarm.local_peer_id(), "Network layer initialized");
|
||||
|
||||
for external_address in config.network.external_addresses {
|
||||
Swarm::add_external_address(&mut swarm, external_address);
|
||||
swarm.add_external_address(external_address);
|
||||
}
|
||||
|
||||
let (event_loop, mut swap_receiver) = EventLoop::new(
|
||||
|
@ -391,46 +392,3 @@ async fn init_monero_wallet(
|
|||
|
||||
Ok(wallet)
|
||||
}
|
||||
|
||||
/// Registers a hidden service for each network.
|
||||
/// Note: Once ac goes out of scope, the services will be de-registered.
|
||||
async fn register_tor_services(
|
||||
networks: Vec<Multiaddr>,
|
||||
tor_client: tor::Client,
|
||||
seed: &Seed,
|
||||
) -> Result<AuthenticatedClient> {
|
||||
let mut ac = tor_client.into_authenticated_client().await?;
|
||||
|
||||
let hidden_services_details = networks
|
||||
.iter()
|
||||
.flat_map(|network| {
|
||||
network.iter().map(|protocol| match protocol {
|
||||
Protocol::Tcp(port) => Some((
|
||||
port,
|
||||
SocketAddr::new(IpAddr::from(Ipv4Addr::new(127, 0, 0, 1)), port),
|
||||
)),
|
||||
_ => {
|
||||
// We only care for Tcp for now.
|
||||
None
|
||||
}
|
||||
})
|
||||
})
|
||||
.flatten()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let key = seed.derive_torv3_key();
|
||||
|
||||
ac.add_services(&hidden_services_details, &key).await?;
|
||||
|
||||
let onion_address = key
|
||||
.public()
|
||||
.get_onion_address()
|
||||
.get_address_without_dot_onion();
|
||||
|
||||
hidden_services_details.iter().for_each(|(port, _)| {
|
||||
let onion_address = format!("/onion3/{}:{}", onion_address, port);
|
||||
tracing::info!(%onion_address, "Successfully created hidden service");
|
||||
});
|
||||
|
||||
Ok(ac)
|
||||
}
|
||||
|
|
|
@ -4,7 +4,6 @@ pub mod cancel_and_refund;
|
|||
pub mod command;
|
||||
mod event_loop;
|
||||
mod list_sellers;
|
||||
mod tor;
|
||||
pub mod transport;
|
||||
pub mod watcher;
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ pub mod request;
|
|||
pub mod tauri_bindings;
|
||||
|
||||
use crate::cli::command::{Bitcoin, Monero};
|
||||
use crate::common::tor::init_tor_client;
|
||||
use crate::common::tracing_util::Format;
|
||||
use crate::database::{open_db, AccessMode};
|
||||
use crate::env::{Config as EnvConfig, GetConfig, Mainnet, Testnet};
|
||||
|
@ -29,7 +30,6 @@ use tracing::Level;
|
|||
use url::Url;
|
||||
use uuid::Uuid;
|
||||
|
||||
use super::tor::init_tor_client;
|
||||
use super::watcher::Watcher;
|
||||
|
||||
static START: Once = Once::new();
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
pub mod tor;
|
||||
pub mod tracing_util;
|
||||
|
||||
use anyhow::anyhow;
|
||||
|
|
|
@ -21,6 +21,8 @@ pub async fn init_tor_client(data_dir: &Path) -> Result<Arc<TorClient<TokioRustl
|
|||
// It uses cached information when possible.)
|
||||
let runtime = TokioRustlsRuntime::current().expect("We are always running with tokio");
|
||||
|
||||
tracing::debug!("Bootstrapping Tor client");
|
||||
|
||||
let tor_client = TorClient::with_runtime(runtime)
|
||||
.config(config)
|
||||
.create_bootstrapped()
|
|
@ -31,9 +31,19 @@ pub fn init(
|
|||
dir: impl AsRef<Path>,
|
||||
tauri_handle: Option<TauriHandle>,
|
||||
) -> Result<()> {
|
||||
// file logger will always write in JSON format and with timestamps
|
||||
let ALL_CRATES: Vec<&str> = vec![
|
||||
"swap",
|
||||
"asb",
|
||||
"libp2p_community_tor",
|
||||
"unstoppableswap-gui-rs",
|
||||
"arti",
|
||||
];
|
||||
let OUR_CRATES: Vec<&str> = vec!["swap", "asb"];
|
||||
|
||||
// General log file for non-verbose logs
|
||||
let file_appender: RollingFileAppender = tracing_appender::rolling::never(&dir, "swap-all.log");
|
||||
|
||||
// Verbose log file, rotated hourly, with a maximum of 24 files
|
||||
let tracing_file_appender: RollingFileAppender = RollingFileAppender::builder()
|
||||
.rotation(Rotation::HOURLY)
|
||||
.filename_prefix("tracing")
|
||||
|
@ -42,24 +52,31 @@ pub fn init(
|
|||
.build(&dir)
|
||||
.expect("initializing rolling file appender failed");
|
||||
|
||||
// Log to file
|
||||
// Layer for writing to the general log file
|
||||
// Crates: swap, asb
|
||||
// Level: Passed in
|
||||
let file_layer = fmt::layer()
|
||||
.with_writer(file_appender)
|
||||
.with_ansi(false)
|
||||
.with_timer(UtcTime::rfc_3339())
|
||||
.with_target(false)
|
||||
.json()
|
||||
.with_filter(env_filter(level_filter)?);
|
||||
.with_filter(env_filter(level_filter, OUR_CRATES.clone())?);
|
||||
|
||||
// Layer for writing to the verbose log file
|
||||
// Crates: swap, asb, libp2p_community_tor, unstoppableswap-gui-rs, arti (all relevant crates)
|
||||
// Level: TRACE
|
||||
let tracing_file_layer = fmt::layer()
|
||||
.with_writer(tracing_file_appender)
|
||||
.with_ansi(false)
|
||||
.with_timer(UtcTime::rfc_3339())
|
||||
.with_target(false)
|
||||
.json()
|
||||
.with_filter(env_filter(LevelFilter::TRACE)?);
|
||||
.with_filter(env_filter(LevelFilter::TRACE, ALL_CRATES.clone())?);
|
||||
|
||||
// Log to stdout
|
||||
// Layer for writing to the terminal
|
||||
// Crates: swap, asb
|
||||
// Level: Passed in
|
||||
let is_terminal = atty::is(atty::Stream::Stderr);
|
||||
let terminal_layer = fmt::layer()
|
||||
.with_writer(std::io::stdout)
|
||||
|
@ -67,17 +84,23 @@ pub fn init(
|
|||
.with_timer(UtcTime::rfc_3339())
|
||||
.with_target(false);
|
||||
|
||||
// Forwards logs to the tauri guest
|
||||
// Layer for writing to the Tauri guest. This will be displayed in the GUI.
|
||||
// Crates: swap, asb, libp2p_community_tor, unstoppableswap-gui-rs, arti
|
||||
// Level: Passed in
|
||||
let tauri_layer = fmt::layer()
|
||||
.with_writer(TauriWriter::new(tauri_handle))
|
||||
.with_ansi(false)
|
||||
.with_timer(UtcTime::rfc_3339())
|
||||
.with_target(true)
|
||||
.json()
|
||||
.with_filter(env_filter(level_filter)?);
|
||||
.with_filter(env_filter(level_filter, ALL_CRATES.clone())?);
|
||||
|
||||
let env_filtered = env_filter(level_filter)?;
|
||||
// We only log the bare minimum to the terminal
|
||||
// Crates: swap, asb
|
||||
// Level: Passed in
|
||||
let env_filtered = env_filter(level_filter, OUR_CRATES.clone())?;
|
||||
|
||||
// Apply the environment filter and box the layer for the terminal
|
||||
let final_terminal_layer = match format {
|
||||
Format::Json => terminal_layer.json().with_filter(env_filtered).boxed(),
|
||||
Format::Raw => terminal_layer.with_filter(env_filtered).boxed(),
|
||||
|
@ -97,19 +120,18 @@ pub fn init(
|
|||
}
|
||||
|
||||
/// This function controls which crate's logs actually get logged and from which level.
|
||||
fn env_filter(level_filter: LevelFilter) -> Result<EnvFilter> {
|
||||
Ok(EnvFilter::from_default_env()
|
||||
.add_directive(Directive::from_str(&format!("asb={}", &level_filter))?)
|
||||
.add_directive(Directive::from_str(&format!("swap={}", &level_filter))?)
|
||||
.add_directive(Directive::from_str(&format!("arti={}", &level_filter))?)
|
||||
.add_directive(Directive::from_str(&format!(
|
||||
"libp2p_community_tor={}",
|
||||
&level_filter
|
||||
))?)
|
||||
.add_directive(Directive::from_str(&format!(
|
||||
"unstoppableswap-gui-rs={}",
|
||||
&level_filter
|
||||
))?))
|
||||
fn env_filter(level_filter: LevelFilter, crates: Vec<&str>) -> Result<EnvFilter> {
|
||||
let mut filter = EnvFilter::from_default_env();
|
||||
|
||||
// Add directives for each crate in the provided list
|
||||
for crate_name in crates {
|
||||
filter = filter.add_directive(Directive::from_str(&format!(
|
||||
"{}={}",
|
||||
crate_name, &level_filter
|
||||
))?);
|
||||
}
|
||||
|
||||
Ok(filter)
|
||||
}
|
||||
|
||||
/// A writer that forwards tracing log messages to the tauri guest.
|
||||
|
|
|
@ -31,7 +31,6 @@ pub mod network;
|
|||
pub mod protocol;
|
||||
pub mod rpc;
|
||||
pub mod seed;
|
||||
pub mod tor;
|
||||
pub mod tracing_ext;
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use crate::asb::config::TorConf;
|
||||
use crate::asb::{LatestRate, RendezvousNode};
|
||||
use crate::libp2p_ext::MultiAddrExt;
|
||||
use crate::network::rendezvous::XmrBtcNamespace;
|
||||
|
@ -23,7 +24,9 @@ pub fn asb<LR>(
|
|||
env_config: env::Config,
|
||||
namespace: XmrBtcNamespace,
|
||||
rendezvous_addrs: &[Multiaddr],
|
||||
) -> Result<Swarm<asb::Behaviour<LR>>>
|
||||
maybe_tor_client: Option<Arc<TorClient<TokioRustlsRuntime>>>,
|
||||
tor_conf: TorConf,
|
||||
) -> Result<(Swarm<asb::Behaviour<LR>>, Vec<Multiaddr>)>
|
||||
where
|
||||
LR: LatestRate + Send + 'static + Debug + Clone,
|
||||
{
|
||||
|
@ -50,7 +53,12 @@ where
|
|||
rendezvous_nodes,
|
||||
);
|
||||
|
||||
let transport = asb::transport::new(&identity)?;
|
||||
let (transport, onion_addresses) = asb::transport::new(
|
||||
&identity,
|
||||
maybe_tor_client,
|
||||
tor_conf.hidden_service_num_intro_points,
|
||||
tor_conf.register_hidden_service,
|
||||
)?;
|
||||
|
||||
let swarm = SwarmBuilder::with_existing_identity(identity)
|
||||
.with_tokio()
|
||||
|
@ -59,7 +67,7 @@ where
|
|||
.with_swarm_config(|cfg| cfg.with_idle_connection_timeout(Duration::MAX))
|
||||
.build();
|
||||
|
||||
Ok(swarm)
|
||||
Ok((swarm, onion_addresses))
|
||||
}
|
||||
|
||||
pub async fn cli<T>(
|
||||
|
|
|
@ -12,7 +12,6 @@ use std::fmt;
|
|||
use std::fs::{self, File};
|
||||
use std::io::{self, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
use torut::onion::TorSecretKeyV3;
|
||||
|
||||
pub const SEED_LENGTH: usize = 32;
|
||||
|
||||
|
@ -47,14 +46,6 @@ impl Seed {
|
|||
identity::Keypair::ed25519_from_bytes(bytes).expect("we always pass 32 bytes")
|
||||
}
|
||||
|
||||
pub fn derive_torv3_key(&self) -> TorSecretKeyV3 {
|
||||
let bytes = self.derive(b"TOR").bytes();
|
||||
let sk = ed25519_dalek::SecretKey::from_bytes(&bytes)
|
||||
.expect("Failed to create a new extended secret key for Tor.");
|
||||
let esk = ed25519_dalek::ExpandedSecretKey::from(&sk);
|
||||
esk.to_bytes().into()
|
||||
}
|
||||
|
||||
pub fn from_file_or_generate(data_dir: &Path) -> Result<Self, Error> {
|
||||
let file_path_buf = data_dir.join("seed.pem");
|
||||
let file_path = Path::new(&file_path_buf);
|
||||
|
|
128
swap/src/tor.rs
128
swap/src/tor.rs
|
@ -1,128 +0,0 @@
|
|||
use anyhow::{bail, Context, Result};
|
||||
use std::future::Future;
|
||||
use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4};
|
||||
use tokio::net::TcpStream;
|
||||
use torut::control::{AsyncEvent, AuthenticatedConn, ConnError, UnauthenticatedConn};
|
||||
use torut::onion::TorSecretKeyV3;
|
||||
|
||||
pub const DEFAULT_SOCKS5_PORT: u16 = 9050;
|
||||
pub const DEFAULT_CONTROL_PORT: u16 = 9051;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Client {
|
||||
socks5_address: SocketAddrV4,
|
||||
control_port_address: SocketAddr,
|
||||
}
|
||||
|
||||
impl Default for Client {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
socks5_address: SocketAddrV4::new(Ipv4Addr::LOCALHOST, DEFAULT_SOCKS5_PORT),
|
||||
control_port_address: SocketAddr::V4(SocketAddrV4::new(
|
||||
Ipv4Addr::LOCALHOST,
|
||||
DEFAULT_CONTROL_PORT,
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Client {
|
||||
pub fn new(socks5_port: u16) -> Self {
|
||||
Self {
|
||||
socks5_address: SocketAddrV4::new(Ipv4Addr::LOCALHOST, socks5_port),
|
||||
control_port_address: SocketAddr::V4(SocketAddrV4::new(
|
||||
Ipv4Addr::LOCALHOST,
|
||||
DEFAULT_CONTROL_PORT,
|
||||
)),
|
||||
}
|
||||
}
|
||||
pub fn with_control_port(self, control_port: u16) -> Self {
|
||||
Self {
|
||||
control_port_address: SocketAddr::V4(SocketAddrV4::new(
|
||||
Ipv4Addr::LOCALHOST,
|
||||
control_port,
|
||||
)),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
/// checks if tor is running
|
||||
pub async fn assert_tor_running(&self) -> Result<()> {
|
||||
// Make sure you are running tor and this is your socks port
|
||||
let proxy = reqwest::Proxy::all(format!("socks5h://{}", self.socks5_address).as_str())
|
||||
.context("Failed to construct Tor proxy URL")?;
|
||||
let client = reqwest::Client::builder().proxy(proxy).build()?;
|
||||
|
||||
let res = client.get("https://check.torproject.org").send().await?;
|
||||
let text = res.text().await?;
|
||||
|
||||
if !text.contains("Congratulations. This browser is configured to use Tor.") {
|
||||
bail!("Tor is currently not running")
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn init_unauthenticated_connection(&self) -> Result<UnauthenticatedConn<TcpStream>> {
|
||||
// Connect to local tor service via control port
|
||||
let sock = TcpStream::connect(self.control_port_address).await?;
|
||||
let uc = UnauthenticatedConn::new(sock);
|
||||
Ok(uc)
|
||||
}
|
||||
|
||||
/// Create a new authenticated connection to your local Tor service
|
||||
pub async fn into_authenticated_client(self) -> Result<AuthenticatedClient> {
|
||||
self.assert_tor_running().await?;
|
||||
|
||||
let mut uc = self
|
||||
.init_unauthenticated_connection()
|
||||
.await
|
||||
.context("Failed to connect to Tor")?;
|
||||
|
||||
let tor_info = uc
|
||||
.load_protocol_info()
|
||||
.await
|
||||
.context("Failed to load protocol info from Tor")?;
|
||||
|
||||
let tor_auth_data = tor_info
|
||||
.make_auth_data()?
|
||||
.context("Failed to make Tor auth data")?;
|
||||
|
||||
// Get an authenticated connection to the Tor via the Tor Controller protocol.
|
||||
uc.authenticate(&tor_auth_data)
|
||||
.await
|
||||
.context("Failed to authenticate with Tor")?;
|
||||
|
||||
Ok(AuthenticatedClient {
|
||||
inner: uc.into_authenticated().await,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn tor_proxy_port(&self) -> u16 {
|
||||
self.socks5_address.port()
|
||||
}
|
||||
}
|
||||
|
||||
type Handler = fn(AsyncEvent<'_>) -> Box<dyn Future<Output = Result<(), ConnError>> + Unpin>;
|
||||
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct AuthenticatedClient {
|
||||
inner: AuthenticatedConn<TcpStream, Handler>,
|
||||
}
|
||||
|
||||
impl AuthenticatedClient {
|
||||
/// Add an ephemeral tor service on localhost with the provided key
|
||||
/// `service_port` and `onion_port` can be different but don't have to as
|
||||
/// they are on different networks.
|
||||
pub async fn add_services(
|
||||
&mut self,
|
||||
services: &[(u16, SocketAddr)],
|
||||
tor_key: &TorSecretKeyV3,
|
||||
) -> Result<()> {
|
||||
let mut listeners = services.iter();
|
||||
self.inner
|
||||
.add_onion_v3(tor_key, false, false, false, None, &mut listeners)
|
||||
.await
|
||||
.context("Failed to add onion service")
|
||||
}
|
||||
}
|
|
@ -243,7 +243,7 @@ async fn start_alice(
|
|||
let latest_rate = FixedRate::default();
|
||||
let resume_only = false;
|
||||
|
||||
let mut swarm = swarm::asb(
|
||||
let (mut swarm, _) = swarm::asb(
|
||||
seed,
|
||||
min_buy,
|
||||
max_buy,
|
||||
|
@ -252,6 +252,7 @@ async fn start_alice(
|
|||
env_config,
|
||||
XmrBtcNamespace::Testnet,
|
||||
&[],
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
swarm.listen_on(listen_address).unwrap();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue