diff --git a/CHANGELOG.md b/CHANGELOG.md index 03c055b9..39f1d5fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Printing the deposit address to the terminal as a QR code. To not break automated scripts or integrations with other software, this behaviour is disabled if `--json` is passed to the application. +### Removed + +- The websocket transport from the CLI. + Websockets were only ever intended to be used for the ASB side to allow websites to retrieve quotes. + The CLI can use regular TCP connections and having both - TCP and websockets - causes problems and unnecessary overhead. + ## [0.7.0] - 2021-05-28 ### Fixed diff --git a/swap/src/asb.rs b/swap/src/asb.rs index 222046c1..47ceb072 100644 --- a/swap/src/asb.rs +++ b/swap/src/asb.rs @@ -2,5 +2,6 @@ pub mod command; pub mod config; mod rate; pub mod tracing; +pub mod transport; pub use rate::Rate; diff --git a/swap/src/asb/transport.rs b/swap/src/asb/transport.rs new file mode 100644 index 00000000..a552a8f0 --- /dev/null +++ b/swap/src/asb/transport.rs @@ -0,0 +1,19 @@ +use crate::network::transport::authenticate_and_multiplex; +use anyhow::Result; +use libp2p::core::muxing::StreamMuxerBox; +use libp2p::core::transport::Boxed; +use libp2p::dns::TokioDnsConfig; +use libp2p::tcp::TokioTcpConfig; +use libp2p::websocket::WsConfig; +use libp2p::{identity, PeerId, Transport}; + +/// Creates the libp2p transport for the ASB. +pub fn new(identity: &identity::Keypair) -> Result> { + let tcp = TokioTcpConfig::new().nodelay(true); + let tcp_with_dns = TokioDnsConfig::system(tcp)?; + let websocket_with_dns = WsConfig::new(tcp_with_dns.clone()); + + let transport = tcp_with_dns.or_transport(websocket_with_dns).boxed(); + + authenticate_and_multiplex(transport, identity) +} diff --git a/swap/src/cli.rs b/swap/src/cli.rs index e28b6580..7962efd8 100644 --- a/swap/src/cli.rs +++ b/swap/src/cli.rs @@ -1,2 +1,3 @@ pub mod command; pub mod tracing; +pub mod transport; diff --git a/swap/src/cli/transport.rs b/swap/src/cli/transport.rs new file mode 100644 index 00000000..68a5cd1a --- /dev/null +++ b/swap/src/cli/transport.rs @@ -0,0 +1,32 @@ +use crate::network::tor_transport::TorDialOnlyTransport; +use crate::network::transport::authenticate_and_multiplex; +use anyhow::Result; +use libp2p::core::muxing::StreamMuxerBox; +use libp2p::core::transport::{Boxed, OptionalTransport}; +use libp2p::dns::TokioDnsConfig; +use libp2p::tcp::TokioTcpConfig; +use libp2p::{identity, PeerId, Transport}; + +/// Creates the libp2p transport for the swap CLI. +/// +/// The CLI's transport needs the following capabilities: +/// - Establish TCP connections +/// - Resolve DNS entries +/// - Dial onion-addresses through a running Tor daemon by connecting to the +/// socks5 port. If the port is not given, we will fall back to the regular +/// TCP transport. +pub fn new( + identity: &identity::Keypair, + maybe_tor_socks5_port: Option, +) -> Result> { + let tcp = TokioTcpConfig::new().nodelay(true); + let tcp_with_dns = TokioDnsConfig::system(tcp)?; + let maybe_tor_transport = match maybe_tor_socks5_port { + Some(port) => OptionalTransport::some(TorDialOnlyTransport::new(port)), + None => OptionalTransport::none(), + }; + + let transport = maybe_tor_transport.or_transport(tcp_with_dns).boxed(); + + authenticate_and_multiplex(transport, identity) +} diff --git a/swap/src/network/swarm.rs b/swap/src/network/swarm.rs index 2e2a8e1d..37e9f219 100644 --- a/swap/src/network/swarm.rs +++ b/swap/src/network/swarm.rs @@ -1,10 +1,9 @@ -use crate::network::transport; use crate::protocol::alice::event_loop::LatestRate; use crate::protocol::{alice, bob}; use crate::seed::Seed; -use crate::{env, monero, tor}; +use crate::{asb, cli, env, monero, tor}; use anyhow::Result; -use libp2p::swarm::{NetworkBehaviour, SwarmBuilder}; +use libp2p::swarm::SwarmBuilder; use libp2p::{PeerId, Swarm}; use std::fmt::Debug; @@ -22,41 +21,19 @@ pub fn asb( where LR: LatestRate + Send + 'static + Debug, { - with_clear_net( - seed, - alice::Behaviour::new( - balance, - lock_fee, - min_buy, - max_buy, - latest_rate, - resume_only, - env_config, - ), - ) -} - -pub async fn cli( - seed: &Seed, - alice: PeerId, - tor_socks5_port: u16, -) -> Result> { - let client = tor::Client::new(tor_socks5_port); - if client.assert_tor_running().await.is_ok() { - return with_tor(seed, bob::Behaviour::new(alice), tor_socks5_port).await; - } - with_clear_net(seed, bob::Behaviour::new(alice)) -} + let behaviour = alice::Behaviour::new( + balance, + lock_fee, + min_buy, + max_buy, + latest_rate, + resume_only, + env_config, + ); -fn with_clear_net(seed: &Seed, behaviour: B) -> Result> -where - B: NetworkBehaviour, -{ - tracing::info!("All connections will go through clear net"); let identity = seed.derive_libp2p_identity(); - let transport = transport::build_clear_net(&identity)?; + let transport = asb::transport::new(&identity)?; let peer_id = identity.public().into_peer_id(); - tracing::debug!(%peer_id, "Our peer-id"); let swarm = SwarmBuilder::new(transport, behaviour, peer_id) .executor(Box::new(|f| { @@ -67,15 +44,21 @@ where Ok(swarm) } -async fn with_tor(seed: &Seed, behaviour: B, tor_socks5_port: u16) -> Result> -where - B: NetworkBehaviour, -{ - tracing::info!("All connections will go through Tor socks5 proxy"); +pub async fn cli( + seed: &Seed, + alice: PeerId, + tor_socks5_port: u16, +) -> Result> { + let maybe_tor_socks5_port = match tor::Client::new(tor_socks5_port).assert_tor_running().await { + Ok(()) => Some(tor_socks5_port), + Err(_) => None, + }; + + let behaviour = bob::Behaviour::new(alice); + let identity = seed.derive_libp2p_identity(); - let transport = transport::build_tor(&identity, tor_socks5_port)?; + let transport = cli::transport::new(&identity, maybe_tor_socks5_port)?; let peer_id = identity.public().into_peer_id(); - tracing::debug!(%peer_id, "Our peer-id"); let swarm = SwarmBuilder::new(transport, behaviour, peer_id) .executor(Box::new(|f| { diff --git a/swap/src/network/transport.rs b/swap/src/network/transport.rs index c4fa929d..35ac3e8c 100644 --- a/swap/src/network/transport.rs +++ b/swap/src/network/transport.rs @@ -1,74 +1,39 @@ -use crate::network::tor_transport::TorDialOnlyTransport; use anyhow::Result; +use futures::{AsyncRead, AsyncWrite}; use libp2p::core::muxing::StreamMuxerBox; use libp2p::core::transport::Boxed; use libp2p::core::upgrade::{SelectUpgrade, Version}; -use libp2p::dns::TokioDnsConfig; use libp2p::mplex::MplexConfig; use libp2p::noise::{self, NoiseConfig, X25519Spec}; -use libp2p::tcp::TokioTcpConfig; -use libp2p::websocket::WsConfig; use libp2p::{identity, yamux, PeerId, Transport}; use std::time::Duration; -/// Builds a libp2p transport with the following features: -/// - TcpConnection -/// - WebSocketConnection -/// - DNS name resolution -/// - authentication via noise -/// - multiplexing via yamux or mplex -pub fn build_clear_net(id_keys: &identity::Keypair) -> Result { - let dh_keys = noise::Keypair::::new().into_authentic(id_keys)?; - let noise = NoiseConfig::xx(dh_keys).into_authenticated(); - - let tcp = TokioTcpConfig::new().nodelay(true); - let dns = TokioDnsConfig::system(tcp)?; - let websocket = WsConfig::new(dns.clone()); - - let transport = websocket - .or_transport(dns) +/// "Completes" a transport by applying the authentication and multiplexing +/// upgrades. +/// +/// Even though the actual transport technology in use might be different, for +/// two libp2p applications to be compatible, the authentication and +/// multiplexing upgrades need to be compatible. +pub fn authenticate_and_multiplex( + transport: Boxed, + identity: &identity::Keypair, +) -> Result> +where + T: AsyncRead + AsyncWrite + Unpin + Send + 'static, +{ + let auth_upgrade = { + let noise_identity = noise::Keypair::::new().into_authentic(identity)?; + NoiseConfig::xx(noise_identity).into_authenticated() + }; + let multiplex_upgrade = SelectUpgrade::new(yamux::YamuxConfig::default(), MplexConfig::new()); + + let transport = transport .upgrade(Version::V1) - .authenticate(noise) - .multiplex(SelectUpgrade::new( - yamux::YamuxConfig::default(), - MplexConfig::new(), - )) + .authenticate(auth_upgrade) + .multiplex(multiplex_upgrade) .timeout(Duration::from_secs(20)) .map(|(peer, muxer), _| (peer, StreamMuxerBox::new(muxer))) .boxed(); Ok(transport) } - -/// Builds a libp2p transport with the following features: -/// - TorTcpConnection -/// - WebSocketConnection -/// - DNS name resolution -/// - authentication via noise -/// - multiplexing via yamux or mplex -pub fn build_tor(id_keys: &identity::Keypair, tor_socks5_port: u16) -> Result { - let dh_keys = noise::Keypair::::new().into_authentic(id_keys)?; - let noise = NoiseConfig::xx(dh_keys).into_authenticated(); - - let tcp = TokioTcpConfig::new().nodelay(true); - let tcp_with_dns = TokioDnsConfig::system(tcp)?; - let websocket_with_dns = WsConfig::new(tcp_with_dns.clone()); - let tor_dial_only = TorDialOnlyTransport::new(tor_socks5_port); - - let transport = tor_dial_only - .or_transport(tcp_with_dns) - .or_transport(websocket_with_dns) - .upgrade(Version::V1) - .authenticate(noise) - .multiplex(SelectUpgrade::new( - yamux::YamuxConfig::default(), - MplexConfig::new(), - )) - .timeout(Duration::from_secs(20)) - .map(|(peer, muxer), _| (peer, StreamMuxerBox::new(muxer))) - .boxed(); - - Ok(transport) -} - -pub type SwapTransport = Boxed<(PeerId, StreamMuxerBox)>;