Skip to content

Commit 4cf1d18

Browse files
On whonix, always connect through the system Tor SOCKS5 proxy
On whonix, collect $TOR_SOCKS_* and use that as the default Transport. Listening on a hidden service is not supported (cf. #391 (comment)). # TOR_SOCKS_HOST=127.0.0.1 TOR_SOCKS_PORT=46105 ./target/debug/asb --testnet start 2025-10-20T00:59:02.523377742Z INFO swap::common::tracing_util: swap/src/common/tracing_util.rs:225: Initialized tracing. General logs will be written to swap-all.log, and verbose logs to tracing*.log level_filter=debug logs_dir=/home/nabijaczleweli/.local/share/xmr-btc-swap/asb/testnet/logs 2025-10-20T00:59:02.523734203Z INFO asb: swap-asb/src/main.rs:59: Setting up context binary="asb" version="3.2.0-rc.4" os="linux" arch="x86_64" 2025-10-20T00:59:02.524117896Z DEBUG swap::seed: swap/src/seed.rs:128: Reading in seed from /home/nabijaczleweli/.local/share/xmr-btc-swap/asb/testnet/seed.pem 2025-10-20T00:59:02.524380312Z DEBUG swap::database: swap/src/database.rs:99: Using existing sqlite database. 2025-10-20T00:59:02.527334775Z INFO asb: swap-asb/src/main.rs:176: Not tipping the developers (maker.developer_tip = 0 or not set in config) 2025-10-20T00:59:02.527582262Z DEBUG asb: swap-asb/src/main.rs:613: Initializing Monero wallets 2025-10-20T00:59:02.527860291Z INFO monero_rpc_pool::database: monero-rpc-pool/src/database.rs:45: Using database at /home/nabijaczleweli/.local/share/xmr-btc-swap/asb/testnet/monero-rpc-pool/nodes_v3.db 2025-10-20T00:59:02.539417001Z INFO monero_rpc_pool::database: monero-rpc-pool/src/database.rs:59: Database migration completed 2025-10-20T00:59:02.585025016Z INFO monero_rpc_pool: monero-rpc-pool/src/lib.rs:174: Started server on 127.0.0.1:45251 (random port) 2025-10-20T00:59:02.585302006Z INFO asb: swap-asb/src/main.rs:637: Monero RPC Pool started for ASB on http://127.0.0.1:45251 2025-10-20T00:59:02.590742943Z DEBUG monero_sys: monero-sys/src/lib.rs:1354: Updating wallet manager's remote node address=127.0.0.1:45251 2025-10-20T00:59:09.182046612Z DEBUG monero_sys: monero-sys/src/lib.rs:1116: Opening or creating wallet path=/home/nabijaczleweli/.local/share/xmr-btc-swap/asb/testnet/monero/wallets/asb-wallet 2025-10-20T00:59:09.182339945Z DEBUG monero_sys: monero-sys/src/lib.rs:1367: Checking if wallet exists path=/home/nabijaczleweli/.local/share/xmr-btc-swap/asb/testnet/monero/wallets/asb-wallet 2025-10-20T00:59:09.182665481Z DEBUG monero_sys: monero-sys/src/lib.rs:1120: Wallet already exists, opening it wallet=/home/nabijaczleweli/.local/share/xmr-btc-swap/asb/testnet/monero/wallets/asb-wallet 2025-10-20T00:59:09.182925233Z DEBUG monero_sys: monero-sys/src/lib.rs:1318: Opening wallet path=/home/nabijaczleweli/.local/share/xmr-btc-swap/asb/testnet/monero/wallets/asb-wallet 2025-10-20T00:59:09.844215951Z DEBUG monero_sys: monero-sys/src/lib.rs:1489: Initializing wallet address=5AiDHB58fhFHB8u4uoSE8YHKbSxCEN8onDh4S8PKKQqMN2MekXNAJB42foDBgNq2wUAGnNzfZeEVfhwHQ6837Yf25gm6jmS 2025-10-20T00:59:09.844695338Z DEBUG monero_sys: monero-sys/src/lib.rs:1577: Initializing wallet daemon_address=127.0.0.1:45251 ssl=false 2025-10-20T00:59:12.471017858Z DEBUG monero_sys: monero-sys/src/lib.rs:1502: Initialized wallet, setting daemon address 2025-10-20T00:59:12.471279924Z DEBUG monero_sys: monero-sys/src/lib.rs:1552: Setting daemon address address=127.0.0.1:45251 ssl=false 2025-10-20T00:59:14.216130174Z DEBUG monero_sys: monero-sys/src/lib.rs:1507: Background sync enabled, starting refresh thread 2025-10-20T00:59:14.243361749Z INFO asb: swap-asb/src/main.rs:186: Monero wallet address monero_address=5AiDHB58fhFHB8u4uoSE8YHKbSxCEN8onDh4S8PKKQqMN2MekXNAJB42foDBgNq2wUAGnNzfZeEVfhwHQ6837Yf25gm6jmS 2025-10-20T00:59:14.244657192Z WARN asb: swap-asb/src/main.rs:196: The Monero balance is 0, make sure to deposit funds at monero_address=5AiDHB58fhFHB8u4uoSE8YHKbSxCEN8onDh4S8PKKQqMN2MekXNAJB42foDBgNq2wUAGnNzfZeEVfhwHQ6837Yf25gm6jmS 2025-10-20T00:59:14.245413828Z DEBUG asb: swap-asb/src/main.rs:574: Opening Bitcoin wallet 2025-10-20T00:59:14.254739766Z DEBUG swap::bitcoin::wallet: swap/src/bitcoin/wallet.rs:621: Loading existing Bitcoin wallet from database 2025-10-20T00:59:14.351859914Z INFO asb: swap-asb/src/main.rs:601: Skipping Bitcoin wallet sync because we are only using it for receiving funds 2025-10-20T00:59:14.352228163Z INFO asb: swap-asb/src/main.rs:218: Bitcoin wallet balance bitcoin_balance=0 BTC 2025-10-20T00:59:14.352228163Z INFO asb: swap-asb/src/main.rs:218: Bitcoin wallet balance bitcoin_balance=0 BTC 2025-10-20T00:59:14.352573504Z INFO swap_env::env: swap-env/src/env.rs:147: On whonix, not starting Tor 2025-10-20T00:59:14.353600945Z DEBUG swap::common::tor: swap/src/common/tor.rs:192: Using SOCKS5 proxy at Ip(127.0.0.1:46105) # TOR_SOCKS_IPC_PATH=$PWD/SocksPort/sock ./target/debug/asb --testnet start 2025-10-20T01:00:53.213704267Z INFO asb: swap-asb/src/main.rs:218: Bitcoin wallet balance bitcoin_balance=0 BTC 2025-10-20T01:00:53.214061031Z INFO swap_env::env: swap-env/src/env.rs:147: On whonix, not starting Tor 2025-10-20T01:00:53.215044718Z DEBUG swap::common::tor: swap/src/common/tor.rs:192: Using SOCKS5 proxy at Unix("/home/nabijaczleweli/uwu/another-bout/SocksPort/sock") $ tor ControlPort auto SocksPort unix:$PWD/uwu/another-bout/SocksPort/sock SafeLogging 0 ... Oct 20 03:02:51.000 [notice] Bootstrapped 90% (ap_handshake_done): Handshake finished with a relay to build circuits Oct 20 03:02:51.000 [notice] Bootstrapped 95% (circuit_create): Establishing a Tor circuit Oct 20 03:02:52.000 [notice] Bootstrapped 100% (done): Done Oct 20 03:03:20.000 [notice] Have tried resolving or connecting to address '94.74.164.77' at 3 different places. Giving up. Oct 20 03:03:29.000 [notice] Have tried resolving or connecting to address '94.74.164.77' at 3 different places. Giving up.
1 parent 51d0d4a commit 4cf1d18

File tree

4 files changed

+231
-8
lines changed

4 files changed

+231
-8
lines changed

swap/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ ecdsa_fun = { workspace = true, features = ["libsecp_compat", "serde", "adaptor"
4242
ed25519-dalek = "1"
4343
electrum-pool = { path = "../electrum-pool" }
4444
fns = "0.0.7"
45-
futures = { workspace = true }
45+
futures = { workspace = true, features = ["compat", "io-compat"] }
4646
hex = { workspace = true }
4747
jsonrpsee = { workspace = true, features = ["server"] }
4848
libp2p = { workspace = true, features = ["tcp", "yamux", "dns", "noise", "request-response", "ping", "rendezvous", "identify", "macros", "cbor", "json", "tokio", "serde", "rsa"] }
@@ -89,6 +89,7 @@ void = "1"
8989

9090
# Tokio
9191
tokio = { workspace = true, features = ["process", "fs", "net", "parking_lot", "rt"] }
92+
tokio-socks = "0.5"
9293
tokio-tungstenite = { version = "0.15", features = ["rustls-tls"] }
9394
tokio-util = { workspace = true }
9495

swap/src/asb/network.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ pub mod transport {
5454
let maybe_tor_transport = if let Some(universal_config) = existing_tor_config() {
5555
OrTransport::new(
5656
OptionalTransport::none(),
57-
OptionalTransport::some(todo!() as libp2p_tor::TorTransport),
57+
OptionalTransport::some(universal_config.transport()),
5858
)
5959
} else if let Some(tor_client) = maybe_tor_client {
6060
let mut tor_transport =

swap/src/cli/transport.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ pub fn new(
3030
let maybe_tor_transport = match (existing_tor_config(), maybe_tor_client) {
3131
(Some(universal_config), _) => OrTransport::new(
3232
OptionalTransport::none(),
33-
OptionalTransport::some(todo!() as libp2p_tor::TorTransport),
33+
OptionalTransport::some(universal_config.transport()),
3434
),
3535
(None, Some(client)) => OrTransport::new(
3636
OptionalTransport::some(libp2p_tor::TorTransport::from_client(

swap/src/common/tor.rs

Lines changed: 227 additions & 5 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,
@@ -9,11 +8,234 @@ use futures::StreamExt;
98
use swap_env::env::is_whonix;
109
use tor_rtcompat::tokio::TokioRustlsRuntime;
1110

12-
pub enum RemoteTorClientConfig {}
13-
pub fn existing_tor_config() -> Option<RemoteTorClientConfig> {
11+
use libp2p::core::multiaddr::Protocol;
12+
use libp2p::core::transport::{ListenerId, TransportEvent};
13+
use libp2p::{Multiaddr, Transport, TransportError};
14+
use std::future::Future;
15+
use std::net::SocketAddr;
16+
use std::path::PathBuf;
17+
use std::pin::Pin;
18+
use std::task::{Context, Poll};
19+
use tokio::io::{AsyncRead, AsyncWrite};
20+
use tokio::net::TcpStream;
21+
#[cfg(unix)]
22+
use tokio::net::UnixStream;
23+
use tokio_socks::tcp::Socks5Stream;
24+
use tokio_socks::TargetAddr;
25+
26+
pub enum TcpOrUnixStream {
27+
Tcp(TcpStream),
28+
#[cfg(unix)]
29+
Unix(UnixStream),
30+
}
31+
impl AsyncRead for TcpOrUnixStream {
32+
fn poll_read(
33+
self: Pin<&mut Self>,
34+
cx: &mut Context<'_>,
35+
buf: &mut tokio::io::ReadBuf<'_>,
36+
) -> Poll<std::io::Result<()>> {
37+
match self.get_mut() {
38+
TcpOrUnixStream::Tcp(tsock) => AsyncRead::poll_read(Pin::new(tsock), cx, buf),
39+
TcpOrUnixStream::Unix(sock) => AsyncRead::poll_read(Pin::new(sock), cx, buf),
40+
}
41+
}
42+
}
43+
impl AsyncWrite for TcpOrUnixStream {
44+
fn poll_write(
45+
self: Pin<&mut Self>,
46+
cx: &mut Context<'_>,
47+
buf: &[u8],
48+
) -> Poll<std::io::Result<usize>> {
49+
match self.get_mut() {
50+
TcpOrUnixStream::Tcp(tsock) => AsyncWrite::poll_write(Pin::new(tsock), cx, buf),
51+
TcpOrUnixStream::Unix(sock) => AsyncWrite::poll_write(Pin::new(sock), cx, buf),
52+
}
53+
}
54+
55+
fn poll_write_vectored(
56+
self: Pin<&mut Self>,
57+
cx: &mut Context<'_>,
58+
bufs: &[std::io::IoSlice<'_>],
59+
) -> Poll<std::io::Result<usize>> {
60+
match self.get_mut() {
61+
TcpOrUnixStream::Tcp(tsock) => {
62+
AsyncWrite::poll_write_vectored(Pin::new(tsock), cx, bufs)
63+
}
64+
TcpOrUnixStream::Unix(sock) => {
65+
AsyncWrite::poll_write_vectored(Pin::new(sock), cx, bufs)
66+
}
67+
}
68+
}
69+
70+
fn is_write_vectored(&self) -> bool {
71+
true
72+
}
73+
74+
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<std::io::Result<()>> {
75+
match self.get_mut() {
76+
TcpOrUnixStream::Tcp(tsock) => AsyncWrite::poll_flush(Pin::new(tsock), cx),
77+
TcpOrUnixStream::Unix(sock) => AsyncWrite::poll_flush(Pin::new(sock), cx),
78+
}
79+
}
80+
81+
fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<std::io::Result<()>> {
82+
match self.get_mut() {
83+
TcpOrUnixStream::Tcp(tsock) => AsyncWrite::poll_shutdown(Pin::new(tsock), cx),
84+
TcpOrUnixStream::Unix(sock) => AsyncWrite::poll_shutdown(Pin::new(sock), cx),
85+
}
86+
}
87+
}
88+
89+
fn multi_to_socks(addr: &Multiaddr) -> Option<TargetAddr<'static>> {
90+
let mut addr = addr.iter();
91+
match (addr.next()?, addr.next()) {
92+
(
93+
Protocol::Dns(domain) | Protocol::Dns4(domain) | Protocol::Dns6(domain),
94+
Some(Protocol::Tcp(port)),
95+
) => Some(TargetAddr::Domain(domain.into_owned().into(), port)),
96+
(Protocol::Onion3(service), _) => {
97+
let mut domain = data_encoding::BASE32.encode(service.hash()).to_lowercase();
98+
domain.push_str(".onion");
99+
Some(TargetAddr::Domain(domain.into(), service.port()))
100+
}
101+
(Protocol::Ip4(ip), Some(Protocol::Tcp(port))) => {
102+
Some(TargetAddr::Ip(SocketAddr::from((ip, port))))
103+
}
104+
(Protocol::Ip6(ip), Some(Protocol::Tcp(port))) => {
105+
Some(TargetAddr::Ip(SocketAddr::from((ip, port))))
106+
}
107+
_ => None,
108+
}
109+
}
110+
#[cfg(test)]
111+
mod tests {
112+
use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr};
113+
use tokio_socks::TargetAddr;
114+
115+
#[test]
116+
fn multi_to_socks() {
117+
assert_eq!(
118+
[
119+
"/dns/ip.tld/tcp/10",
120+
"/dns4/dns.ip4.tld/tcp/11",
121+
"/dns6/dns.ip6.tld/tcp/12",
122+
"/onion3/cebulka7uxchnbpvmqapg5pfos4ngaxglsktzvha7a5rigndghvadeyd:13",
123+
"/ip4/127.0.0.1/tcp/10",
124+
"/ip6/::1/tcp/10",
125+
]
126+
.map(|ma| super::multi_to_socks(&ma.parse().unwrap()).unwrap()),
127+
[
128+
TargetAddr::Domain("ip.tld".into(), 10),
129+
TargetAddr::Domain("dns.ip4.tld".into(), 11),
130+
TargetAddr::Domain("dns.ip6.tld".into(), 12),
131+
TargetAddr::Domain(
132+
"cebulka7uxchnbpvmqapg5pfos4ngaxglsktzvha7a5rigndghvadeyd.onion".into(),
133+
13,
134+
),
135+
TargetAddr::Ip(SocketAddr::new(Ipv4Addr::LOCALHOST.into(), 10)),
136+
TargetAddr::Ip(SocketAddr::new(Ipv6Addr::LOCALHOST.into(), 10)),
137+
],
138+
);
139+
}
140+
}
141+
142+
pub struct Socks5Transport(Arc<SocksServerAddress>);
143+
impl Transport for Socks5Transport {
144+
type Output = tokio_util::compat::Compat<TcpOrUnixStream>;
145+
type Error = tokio_socks::Error;
146+
type ListenerUpgrade = std::future::Pending<Result<Self::Output, Self::Error>>;
147+
type Dial = Pin<Box<dyn Future<Output = Result<Self::Output, Self::Error>> + Send + 'static>>;
148+
149+
fn listen_on(
150+
&mut self,
151+
_: ListenerId,
152+
addr: Multiaddr,
153+
) -> Result<(), TransportError<Self::Error>> {
154+
Err(TransportError::MultiaddrNotSupported(addr))
155+
}
156+
157+
fn remove_listener(&mut self, _: ListenerId) -> bool {
158+
false
159+
}
160+
161+
fn dial(&mut self, addr: Multiaddr) -> Result<Self::Dial, TransportError<Self::Error>> {
162+
let target = multi_to_socks(&addr).ok_or(TransportError::MultiaddrNotSupported(addr))?;
163+
let proxy = self.0.clone();
164+
165+
Ok(Box::pin(async move {
166+
let sock = match &*proxy {
167+
SocksServerAddress::Ip(tcp) => TcpOrUnixStream::Tcp(TcpStream::connect(tcp).await?),
168+
#[cfg(unix)]
169+
SocksServerAddress::Unix(unix) => {
170+
TcpOrUnixStream::Unix(UnixStream::connect(unix).await?)
171+
}
172+
};
173+
Ok(tokio_util::compat::TokioAsyncReadCompatExt::compat(
174+
Socks5Stream::connect_with_socket(sock, target)
175+
.await?
176+
.into_inner(),
177+
))
178+
}))
179+
}
180+
181+
fn dial_as_listener(
182+
&mut self,
183+
addr: Multiaddr,
184+
) -> Result<Self::Dial, TransportError<Self::Error>> {
185+
self.dial(addr)
186+
}
187+
188+
fn poll(
189+
self: Pin<&mut Self>,
190+
_: &mut Context<'_>,
191+
) -> Poll<TransportEvent<Self::ListenerUpgrade, Self::Error>> {
192+
Poll::Pending
193+
}
194+
195+
fn address_translation(&self, _: &Multiaddr, _: &Multiaddr) -> Option<Multiaddr> {
196+
None
197+
}
198+
}
199+
200+
#[derive(Debug)]
201+
pub enum SocksServerAddress {
202+
Ip(SocketAddr),
203+
#[cfg(unix)]
204+
Unix(PathBuf),
205+
}
206+
207+
impl SocksServerAddress {
208+
pub fn transport(self) -> Socks5Transport {
209+
tracing::debug!("Using SOCKS5 proxy at {self:?}");
210+
Socks5Transport(Arc::new(self))
211+
}
212+
213+
/// Consult `$TOR_SOCKS_{IPC_PATH,HOST+PORT}`
214+
///
215+
/// `$TOR_SOCKS_IPC_PATH` is ignored if `cfg(not(unix))`, and takes precedence if `cfg(unix)`.
216+
fn from_tor_environment() -> anyhow::Result<Option<Self>> {
217+
#[cfg(unix)]
218+
if let Some(p) = std::env::var_os("TOR_SOCKS_IPC_PATH") {
219+
return Ok(Some(SocksServerAddress::Unix(p.into())));
220+
}
221+
222+
use std::env::var;
223+
match (var("TOR_SOCKS_HOST"), var("TOR_SOCKS_PORT")) {
224+
(Ok(h), Ok(p)) => Ok(Some(SocksServerAddress::Ip(SocketAddr::new(
225+
h.parse()?,
226+
p.parse()?,
227+
)))),
228+
_ => Ok(None),
229+
}
230+
}
231+
}
232+
233+
pub fn existing_tor_config() -> Option<SocksServerAddress> {
14234
if is_whonix() {
15235
Some(
16-
todo!(/*RemoteTorClientConfig::from_environment().expect("whonix always has $TOR_... set")*/),
236+
SocksServerAddress::from_tor_environment()
237+
.expect("whonix always has valid $TOR_... variables")
238+
.expect("whonix always has $TOR_... set"),
17239
)
18240
} else {
19241
None

0 commit comments

Comments
 (0)