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
10 changes: 9 additions & 1 deletion binaries/cuprated/src/config/p2p.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use cuprate_p2p_core::{
transports::{Tcp, TcpServerConfig},
ClearNet, NetworkZone, Tor, Transport,
};
use cuprate_p2p_transport::{Arti, ArtiClientConfig, ArtiServerConfig};
use cuprate_p2p_transport::{Arti, ArtiClientConfig, ArtiServerConfig, Socks, SocksClientConfig};
use cuprate_wire::OnionAddr;

use crate::{p2p::ProxySettings, tor::TorMode};
Expand Down Expand Up @@ -289,6 +289,14 @@ impl ClearNetConfig {
server_config,
}
}

/// Gets the transport config for [`ClearNet`] over [`Socks`].
pub fn socks_transport_config(&self) -> TransportConfig<ClearNet, Socks> {
TransportConfig {
client_config: self.proxy.clone().try_into().unwrap(),
server_config: None,
}
}
}

impl Default for ClearNetConfig {
Expand Down
87 changes: 65 additions & 22 deletions binaries/cuprated/src/p2p.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
//!
//! Will handle initiating the P2P and contains a protocol request handler.

use std::convert::From;
use std::{convert::From, str::FromStr};

use anyhow::anyhow;
use arti_client::TorClient;
use futures::{FutureExt, TryFutureExt};
use serde::{Deserialize, Serialize};
Expand All @@ -17,7 +18,7 @@ use cuprate_p2p::{config::TransportConfig, NetworkInterface, P2PConfig};
use cuprate_p2p_core::{
client::InternalPeerID, transports::Tcp, ClearNet, NetworkZone, Tor, Transport,
};
use cuprate_p2p_transport::{Arti, ArtiClientConfig, Daemon};
use cuprate_p2p_transport::{Arti, ArtiClientConfig, Daemon, Socks, SocksClientConfig};
use cuprate_txpool::service::{TxpoolReadHandle, TxpoolWriteHandle};
use cuprate_types::blockchain::BlockchainWriteRequest;

Expand All @@ -26,8 +27,8 @@ use crate::{
config::Config,
constants::PANIC_CRITICAL_SERVICE_ERROR,
tor::{
transport_arti_config, transport_clearnet_arti_config, transport_daemon_config, TorContext,
TorMode,
transport_arti_config, transport_clearnet_arti_config, transport_clearnet_daemon_config,
transport_daemon_config, TorContext, TorMode,
},
txpool::{self, IncomingTxHandler},
};
Expand All @@ -39,13 +40,43 @@ pub mod request_handler;
pub use network_address::CrossNetworkInternalPeerId;

/// A simple parsing enum for the `p2p.clear_net.proxy` field
#[derive(Debug, Deserialize, Serialize, PartialEq, Eq)]
#[derive(Debug, Deserialize, Serialize, PartialEq, Eq, Clone)]
pub enum ProxySettings {
Tor,
#[serde(untagged)]
Socks(String),
}

impl TryFrom<ProxySettings> for SocksClientConfig {
type Error = anyhow::Error;

fn try_from(value: ProxySettings) -> Result<Self, Self::Error> {
let ProxySettings::Socks(url) = value else {
panic!("Tor proxy setting should not be parsed!")
};

let Some((_, url)) = url.split_once("socks5://") else {
return Err(anyhow!("Invalid proxy url header."));
};

let (authentication, addr) = url
.split_once('@')
.map(|(up, ad)| {
(
up.split_once(':')
.map(|(a, b)| (a.to_string(), b.to_string())),
ad,
)
})
.unwrap_or((None, url));

Ok(Self {
proxy: addr.parse()?,
authentication,
})
}
}

/// This struct collect all supported and optional network zone interfaces.
pub struct NetworkInterfaces {
/// Mandatory clearnet network interface
Expand Down Expand Up @@ -90,30 +121,42 @@ pub async fn initialize_zones_p2p(
.await
.unwrap()
}
TorMode::Daemon => {
tracing::error!("Anonymizing clearnet connections through the Tor daemon is not yet supported.");
std::process::exit(0);
}
TorMode::Daemon => start_zone_p2p::<ClearNet, Socks>(
blockchain_read_handle.clone(),
context_svc.clone(),
txpool_read_handle.clone(),
config.clearnet_p2p_config(),
transport_clearnet_daemon_config(config),
)
.await
.unwrap(),
TorMode::Off => {
tracing::error!("Clearnet proxy set to \"tor\" but Tor is actually off. Please be sure to set a mode in the configuration or command line");
std::process::exit(0);
}
},
ProxySettings::Socks(ref s) => {
if !s.is_empty() {
tracing::error!("Socks proxy is not yet supported.");
std::process::exit(0);
if s.is_empty() {
start_zone_p2p::<ClearNet, Tcp>(
blockchain_read_handle.clone(),
context_svc.clone(),
txpool_read_handle.clone(),
config.clearnet_p2p_config(),
config.p2p.clear_net.tcp_transport_config(config.network),
)
.await
.unwrap()
} else {
start_zone_p2p::<ClearNet, Socks>(
blockchain_read_handle.clone(),
context_svc.clone(),
txpool_read_handle.clone(),
config.clearnet_p2p_config(),
config.p2p.clear_net.socks_transport_config(),
)
.await
.unwrap()
}

start_zone_p2p::<ClearNet, Tcp>(
blockchain_read_handle.clone(),
context_svc.clone(),
txpool_read_handle.clone(),
config.clearnet_p2p_config(),
config.p2p.clear_net.tcp_transport_config(config.network),
)
.await
.unwrap()
}
}
};
Expand Down
12 changes: 12 additions & 0 deletions binaries/cuprated/src/tor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ use cuprate_p2p::TransportConfig;
use cuprate_p2p_core::{ClearNet, Tor};
use cuprate_p2p_transport::{
Arti, ArtiClientConfig, ArtiServerConfig, Daemon, DaemonClientConfig, DaemonServerConfig,
Socks, SocksClientConfig,
};
use cuprate_wire::OnionAddr;

Expand Down Expand Up @@ -197,3 +198,14 @@ pub fn transport_daemon_config(config: &Config) -> TransportConfig<Tor, Daemon>
),
}
}

/// Gets the transport config for [`ClearNet`] over [`Socks`].
pub const fn transport_clearnet_daemon_config(config: &Config) -> TransportConfig<ClearNet, Socks> {
TransportConfig {
client_config: SocksClientConfig {
proxy: config.tor.daemon.address,
authentication: None,
},
server_config: None,
}
}
4 changes: 4 additions & 0 deletions p2p/p2p-transport/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ pub use arti::{Arti, ArtiClientConfig, ArtiServerConfig};
mod tor;
pub use tor::{Daemon, DaemonClientConfig, DaemonServerConfig};

/// SOCKS5 implementation
mod socks;
pub use socks::{Socks, SocksClientConfig};

/// Disabled listener
mod disabled;
pub(crate) use disabled::DisabledListener;
75 changes: 75 additions & 0 deletions p2p/p2p-transport/src/socks.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
//! Socks Transport
//!
//! This module defines a transport method for the `ClearNet` network zone using a generic SOCKS5 proxy.
//!

//---------------------------------------------------------------------------------------------------- Imports

use std::{
io::{self, ErrorKind},
net::SocketAddr,
};

use tokio::net::tcp::{OwnedReadHalf, OwnedWriteHalf};
use tokio_socks::tcp::Socks5Stream;
use tokio_util::codec::{FramedRead, FramedWrite};

use cuprate_p2p_core::{ClearNet, NetworkZone, Transport};
use cuprate_wire::MoneroWireCodec;

use crate::DisabledListener;

//---------------------------------------------------------------------------------------------------- Configuration

/// Socks5 proxied TCP transport.
#[derive(Debug, Clone, Copy, Default)]
pub struct Socks;

#[derive(Clone)]
pub struct SocksClientConfig {
/// Proxy address
pub proxy: SocketAddr,

/// According to RFC 1929, if authentication is enabled, both username and password fields MUST NOT be empty.
pub authentication: Option<(String, String)>,
}

//---------------------------------------------------------------------------------------------------- Transport

#[async_trait::async_trait]
impl Transport<ClearNet> for Socks {
type ClientConfig = SocksClientConfig;
type ServerConfig = ();

type Stream = FramedRead<OwnedReadHalf, MoneroWireCodec>;
type Sink = FramedWrite<OwnedWriteHalf, MoneroWireCodec>;
type Listener = DisabledListener<ClearNet, OwnedReadHalf, OwnedWriteHalf>;

async fn connect_to_peer(
addr: <ClearNet as NetworkZone>::Addr,
config: &Self::ClientConfig,
) -> Result<(Self::Stream, Self::Sink), io::Error> {
// Optional authentication
let proxy = if let Some((username, password)) = config.authentication.as_ref() {
Socks5Stream::connect_with_password(config.proxy, addr, username, password).await
} else {
Socks5Stream::connect(config.proxy, addr.to_string()).await
};

proxy
.map_err(|e| io::Error::new(ErrorKind::ConnectionAborted, e.to_string()))
.map(|stream| {
let (stream, sink) = stream.into_inner().into_split();
(
FramedRead::new(stream, MoneroWireCodec::default()),
FramedWrite::new(sink, MoneroWireCodec::default()),
)
})
}

async fn incoming_connection_listener(
_config: Self::ServerConfig,
) -> Result<Self::Listener, io::Error> {
panic!("In proxy mode, inbound is disabled!");
}
}