Skip to content

Commit fc53eab

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 4cb740e commit fc53eab

File tree

4 files changed

+206
-8
lines changed

4 files changed

+206
-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"] }
@@ -91,6 +91,7 @@ void = "1"
9191
tokio = { workspace = true, features = ["process", "fs", "net", "parking_lot", "rt"] }
9292
tokio-tungstenite = { version = "0.15", features = ["rustls-tls"] }
9393
tokio-util = { workspace = true }
94+
tokio-socks = "0.5"
9495

9596
tor-rtcompat = { workspace = true, features = ["tokio"] }
9697
tower = { version = "0.4.13", features = ["full"] }

swap/src/asb/network.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ pub mod transport {
4949
) -> Result<OnionTransportWithAddresses> {
5050
let mut onion_addresses = vec![];
5151
let maybe_tor_transport = if let Some(universal_config) = existing_tor_config() {
52-
OrTransport::new(OptionalTransport::none(), OptionalTransport::some(todo!() as libp2p_tor::TorTransport))
52+
OrTransport::new(OptionalTransport::none(), OptionalTransport::some(universal_config.transport()))
5353
} else if let Some(tor_client) = maybe_tor_client {
5454
let mut tor_transport =
5555
libp2p_tor::TorTransport::from_client(tor_client, AddressConversion::DnsOnly);

swap/src/cli/transport.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ pub fn new(
2929

3030
let maybe_tor_transport = match (existing_tor_config(), maybe_tor_client) {
3131
(Some(universal_config), _) => {
32-
OrTransport::new(OptionalTransport::none(), OptionalTransport::some(todo!() as libp2p_tor::TorTransport))
32+
OrTransport::new(OptionalTransport::none(), OptionalTransport::some(universal_config.transport()))
3333
}
3434
(None, Some(client)) => OrTransport::new(OptionalTransport::some(libp2p_tor::TorTransport::from_client(
3535
client,

swap/src/common/tor.rs

Lines changed: 202 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::{net::SocketAddr, path::{PathBuf, Path}, sync::Arc, time::Duration};
32

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

0 commit comments

Comments
 (0)