Skip to content

Commit 06b6da5

Browse files
Add TorBackend::Socks to connect to a Tor daemon over a TCP SOCKS5 proxy
1 parent d8be275 commit 06b6da5

File tree

5 files changed

+209
-7
lines changed

5 files changed

+209
-7
lines changed

monero-rpc-pool/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ tracing-subscriber = { workspace = true }
3838
# Async runtime
3939
crossbeam = "0.8.4"
4040
tokio = { workspace = true, features = ["full"] }
41+
tokio-socks = "0.5"
4142

4243
# Serialization
4344
chrono = { version = "0.4", features = ["serde"] }

monero-rpc-pool/src/tor.rs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr};
12
use swap_tor::TorBackend;
23
use tokio::io::{AsyncRead, AsyncWrite};
4+
use tokio_socks::TargetAddr;
35

46
/// Trait alias for a stream that can be used with hyper
57
pub trait HyperStream: AsyncRead + AsyncWrite + Unpin + Send {}
@@ -19,14 +21,61 @@ impl TorBackendRpc for TorBackend {
1921
fn ready_for_traffic(&self) -> bool {
2022
match self {
2123
TorBackend::Arti(arti) => arti.bootstrap_status().ready_for_traffic(),
24+
TorBackend::Socks(..) => true,
2225
TorBackend::None => false,
2326
}
2427
}
2528

2629
async fn connect(&self, address: (&str, u16)) -> anyhow::Result<Box<dyn HyperStream>> {
2730
match self {
2831
TorBackend::Arti(tor_client) => Ok(Box::new(tor_client.connect(address).await?)),
32+
TorBackend::Socks(proxy) => Ok(Box::new(proxy.proxy(pair_to_socks(address)).await?)),
2933
TorBackend::None => Ok(Box::new(tokio::net::TcpStream::connect(address).await?)),
3034
}
3135
}
3236
}
37+
38+
// Parse order matches tokio::net::ToSocketAddrs
39+
fn pair_to_socks((host, port): (&str, u16)) -> TargetAddr {
40+
if let Ok(addr) = host.parse::<Ipv4Addr>() {
41+
TargetAddr::Ip(SocketAddr::new(addr.into(), 10))
42+
} else if let Ok(addr) = host.parse::<Ipv6Addr>() {
43+
TargetAddr::Ip(SocketAddr::new(addr.into(), 10))
44+
} else {
45+
TargetAddr::Domain(host.into(), port)
46+
}
47+
}
48+
#[cfg(test)]
49+
mod tests {
50+
use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr};
51+
use tokio_socks::TargetAddr;
52+
53+
#[test]
54+
fn pair_to_socks() {
55+
assert_eq!(
56+
[
57+
("ip.tld", 10),
58+
("dns.ip4.tld", 11),
59+
("dns.ip6.tld", 12),
60+
(
61+
"cebulka7uxchnbpvmqapg5pfos4ngaxglsktzvha7a5rigndghvadeyd.onion",
62+
13
63+
),
64+
("127.0.0.1", 10),
65+
("::1", 10),
66+
]
67+
.map(super::pair_to_socks),
68+
[
69+
TargetAddr::Domain("ip.tld".into(), 10),
70+
TargetAddr::Domain("dns.ip4.tld".into(), 11),
71+
TargetAddr::Domain("dns.ip6.tld".into(), 12),
72+
TargetAddr::Domain(
73+
"cebulka7uxchnbpvmqapg5pfos4ngaxglsktzvha7a5rigndghvadeyd.onion".into(),
74+
13,
75+
),
76+
TargetAddr::Ip(SocketAddr::new(Ipv4Addr::LOCALHOST.into(), 10)),
77+
TargetAddr::Ip(SocketAddr::new(Ipv6Addr::LOCALHOST.into(), 10)),
78+
],
79+
);
80+
}
81+
}

swap-tor/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ name = "swap-tor"
33
version = "3.2.0-rc.4"
44
authors = ["наб <[email protected]>"]
55
edition = "2021"
6-
description = "Arti Tor back-end."
6+
description = "Arti/SOCKS5 Tor back-end."
77

88
[dependencies]
99
anyhow = { workspace = true }

swap-tor/src/lib.rs

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,152 @@
11
use arti_client::TorClient;
22
use libp2p::core::multiaddr::Protocol;
33
use libp2p::core::transport::{ListenerId, TransportEvent};
4+
use libp2p::{Multiaddr, Transport, TransportError};
5+
use std::future::Future;
6+
use std::net::SocketAddr;
7+
use std::pin::Pin;
48
use std::sync::Arc;
9+
use std::task::{Context, Poll};
10+
use tokio::net::TcpStream;
11+
use tokio_socks::tcp::Socks5Stream;
12+
use tokio_socks::TargetAddr;
513
use tor_rtcompat::tokio::TokioRustlsRuntime;
614

15+
fn onion3_to_dotonion(service: &[u8; 35]) -> String {
16+
let mut domain = data_encoding::BASE32.encode(service).to_lowercase();
17+
domain.push_str(".onion");
18+
domain
19+
}
20+
fn multi_to_socks(addr: &Multiaddr) -> Option<TargetAddr<'static>> {
21+
let mut addr = addr.iter();
22+
match (addr.next()?, addr.next()) {
23+
(
24+
Protocol::Dns(domain) | Protocol::Dns4(domain) | Protocol::Dns6(domain),
25+
Some(Protocol::Tcp(port)),
26+
) => Some(TargetAddr::Domain(domain.into_owned().into(), port)),
27+
(Protocol::Onion3(service), _) => Some(TargetAddr::Domain(
28+
onion3_to_dotonion(service.hash()).into(),
29+
service.port(),
30+
)),
31+
(Protocol::Ip4(ip), Some(Protocol::Tcp(port))) => {
32+
Some(TargetAddr::Ip(SocketAddr::from((ip, port))))
33+
}
34+
(Protocol::Ip6(ip), Some(Protocol::Tcp(port))) => {
35+
Some(TargetAddr::Ip(SocketAddr::from((ip, port))))
36+
}
37+
_ => None,
38+
}
39+
}
40+
#[cfg(test)]
41+
mod tests {
42+
use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr};
43+
use tokio_socks::TargetAddr;
44+
45+
const MULTIS: [&str; 6] = [
46+
"/dns/ip.tld/tcp/10",
47+
"/dns4/dns.ip4.tld/tcp/11",
48+
"/dns6/dns.ip6.tld/tcp/12",
49+
"/onion3/cebulka7uxchnbpvmqapg5pfos4ngaxglsktzvha7a5rigndghvadeyd:13",
50+
"/ip4/127.0.0.1/tcp/10",
51+
"/ip6/::1/tcp/10",
52+
];
53+
54+
#[test]
55+
fn multi_to_socks() {
56+
assert_eq!(
57+
MULTIS.map(|ma| super::multi_to_socks(&ma.parse().unwrap()).unwrap()),
58+
[
59+
TargetAddr::Domain("ip.tld".into(), 10),
60+
TargetAddr::Domain("dns.ip4.tld".into(), 11),
61+
TargetAddr::Domain("dns.ip6.tld".into(), 12),
62+
TargetAddr::Domain(
63+
"cebulka7uxchnbpvmqapg5pfos4ngaxglsktzvha7a5rigndghvadeyd.onion".into(),
64+
13,
65+
),
66+
TargetAddr::Ip(SocketAddr::new(Ipv4Addr::LOCALHOST.into(), 10)),
67+
TargetAddr::Ip(SocketAddr::new(Ipv6Addr::LOCALHOST.into(), 10)),
68+
],
69+
);
70+
}
71+
}
72+
73+
pub struct Socks5Transport(SocksServerAddress);
74+
impl Transport for Socks5Transport {
75+
type Output = tokio_util::compat::Compat<TcpStream>;
76+
type Error = tokio_socks::Error;
77+
type ListenerUpgrade = std::future::Pending<Result<Self::Output, Self::Error>>;
78+
type Dial = Pin<Box<dyn Future<Output = Result<Self::Output, Self::Error>> + Send + 'static>>;
79+
80+
fn listen_on(
81+
&mut self,
82+
_: ListenerId,
83+
addr: Multiaddr,
84+
) -> Result<(), TransportError<Self::Error>> {
85+
Err(TransportError::MultiaddrNotSupported(addr))
86+
}
87+
88+
fn remove_listener(&mut self, _: ListenerId) -> bool {
89+
false
90+
}
91+
92+
fn dial(&mut self, addr: Multiaddr) -> Result<Self::Dial, TransportError<Self::Error>> {
93+
let target = multi_to_socks(&addr).ok_or(TransportError::MultiaddrNotSupported(addr))?;
94+
let proxy = self.0;
95+
96+
Ok(Box::pin(async move {
97+
Ok(tokio_util::compat::TokioAsyncReadCompatExt::compat(
98+
proxy.proxy(target).await?,
99+
))
100+
}))
101+
}
102+
103+
fn dial_as_listener(
104+
&mut self,
105+
addr: Multiaddr,
106+
) -> Result<Self::Dial, TransportError<Self::Error>> {
107+
self.dial(addr)
108+
}
109+
110+
fn poll(
111+
self: Pin<&mut Self>,
112+
_: &mut Context<'_>,
113+
) -> Poll<TransportEvent<Self::ListenerUpgrade, Self::Error>> {
114+
Poll::Pending
115+
}
116+
117+
fn address_translation(&self, _: &Multiaddr, _: &Multiaddr) -> Option<Multiaddr> {
118+
None
119+
}
120+
}
121+
122+
#[derive(Debug, Copy, Clone)]
123+
pub struct SocksServerAddress(pub SocketAddr);
124+
125+
impl SocksServerAddress {
126+
pub fn transport(self) -> Socks5Transport {
127+
tracing::debug!("Using SOCKS5 proxy at {:?}", self.0);
128+
Socks5Transport(self)
129+
}
130+
131+
pub async fn connect(&self) -> std::io::Result<TcpStream> {
132+
TcpStream::connect(self.0).await
133+
}
134+
135+
pub async fn proxy(&self, target: TargetAddr<'_>) -> Result<TcpStream, tokio_socks::Error> {
136+
Socks5Stream::connect_with_socket(self.connect().await?, target)
137+
.await
138+
.map(Socks5Stream::into_inner)
139+
}
140+
}
141+
7142
pub type TcpTransport = libp2p::dns::tokio::Transport<libp2p::tcp::tokio::Transport>;
8143

9144
#[derive(Clone)]
10145
pub enum TorBackend {
11146
/// Private Tor client
12147
Arti(Arc<TorClient<TokioRustlsRuntime>>),
148+
/// Talking through a Tor SOCKS5 proxy
149+
Socks(SocksServerAddress),
13150
/// No Tor at all
14151
None,
15152
}
@@ -18,6 +155,7 @@ impl std::fmt::Debug for TorBackend {
18155
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
19156
f.write_str(match self {
20157
TorBackend::Arti(..) => "Arti",
158+
TorBackend::Socks(..) => "Socks",
21159
TorBackend::None => "None",
22160
})
23161
}

swap/src/common/tor.rs

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
use std::sync::Arc;
2-
use std::{path::Path, time::Duration};
1+
use std::{path::Path, sync::Arc, time::Duration};
32

43
use crate::cli::api::tauri_bindings::{
54
TauriBackgroundProgress, TauriEmitter, TauriHandle, TorBootstrapStatus,
@@ -34,11 +33,17 @@ pub trait TorBackendSwap {
3433
arti_transport_hook: impl FnOnce(&mut TorTransport),
3534
) -> std::io::Result<IntoTransportT>;
3635
}
37-
type IntoTransportT = OrTransport<OptionalTransport<TorTransport>, TcpTransport>;
36+
type IntoTransportT = OrTransport<
37+
OrTransport<OptionalTransport<TorTransport>, OptionalTransport<Socks5Transport>>,
38+
TcpTransport,
39+
>;
3840
impl TorBackendSwap for TorBackend {
3941
async fn bootstrap(&self, tauri_handle: Option<TauriHandle>) -> anyhow::Result<()> {
4042
match self {
4143
TorBackend::Arti(arti) => bootstrap_arti_tor_client(arti, tauri_handle).await?,
44+
TorBackend::Socks(addr) => {
45+
addr.connect().await?; // validate the remote is actually listening
46+
}
4247
TorBackend::None => {}
4348
}
4449
Ok(())
@@ -49,7 +54,7 @@ impl TorBackendSwap for TorBackend {
4954
match self {
5055
TorBackend::Arti(..) if enable_monero_tor => self.clone(),
5156
TorBackend::Arti(..) => TorBackend::None,
52-
TorBackend::None => self.clone(),
57+
TorBackend::Socks(..) | TorBackend::None => self.clone(),
5358
}
5459
}
5560

@@ -69,9 +74,18 @@ impl TorBackendSwap for TorBackend {
6974
let mut tor_transport =
7075
TorTransport::from_client(tor_client, arti_address_conversion);
7176
arti_transport_hook(&mut tor_transport);
72-
OptionalTransport::some(tor_transport)
77+
OrTransport::new(
78+
OptionalTransport::some(tor_transport),
79+
OptionalTransport::none(),
80+
)
81+
}
82+
TorBackend::Socks(universal_config) => OrTransport::new(
83+
OptionalTransport::none(),
84+
OptionalTransport::some(universal_config.transport()),
85+
),
86+
TorBackend::None => {
87+
OrTransport::new(OptionalTransport::none(), OptionalTransport::none())
7388
}
74-
TorBackend::None => OptionalTransport::none(),
7589
};
7690
Ok(tor.or_transport(tcp_with_dns))
7791
}

0 commit comments

Comments
 (0)