From 3e14a784f3ac9e04aa20757421913305f01e3d8d Mon Sep 17 00:00:00 2001 From: tecnovert Date: Sun, 4 Feb 2024 10:24:36 +0200 Subject: [PATCH] Connect to remote XMR daemons over tor when enabled. --- basicswap/basicswap.py | 14 +++++++--- basicswap/interface/xmr.py | 30 ++++++++++++++++----- basicswap/rpc_xmr.py | 54 +++++++++++++++++++++++++++++++------- 3 files changed, 78 insertions(+), 20 deletions(-) diff --git a/basicswap/basicswap.py b/basicswap/basicswap.py index 4f2b388..1d1cd59 100644 --- a/basicswap/basicswap.py +++ b/basicswap/basicswap.py @@ -511,8 +511,14 @@ class BasicSwap(BaseApp): remote_daemon_urls = chain_client_settings.get('remote_daemon_urls', []) coin_settings = self.coin_clients[coin] - rpchost = coin_settings['rpchost'] - rpcport = coin_settings['rpcport'] + rpchost: str = coin_settings['rpchost'] + rpcport: int = coin_settings['rpcport'] + + proxy_host: str = self.tor_proxy_host if self.use_tor_proxy else None + proxy_port: int = self.tor_proxy_port if self.use_tor_proxy else None + if proxy_host: + self.log.info(f'Connecting through proxy at {proxy_host}.') + daemon_login = None if coin_settings.get('rpcuser', '') != '': daemon_login = (coin_settings.get('rpcuser', ''), coin_settings.get('rpcpassword', '')) @@ -520,7 +526,7 @@ class BasicSwap(BaseApp): if current_daemon_url in remote_daemon_urls: self.log.info(f'Trying last used url {rpchost}:{rpcport}.') try: - rpc2 = make_xmr_rpc2_func(rpcport, daemon_login, rpchost) + rpc2 = make_xmr_rpc2_func(rpcport, daemon_login, rpchost, proxy_host=proxy_host, proxy_port=proxy_port) test = rpc2('get_height', timeout=20)['height'] return True except Exception as e: @@ -530,7 +536,7 @@ class BasicSwap(BaseApp): self.log.info(f'Trying url {url}.') try: rpchost, rpcport = url.rsplit(':', 1) - rpc2 = make_xmr_rpc2_func(rpcport, daemon_login, rpchost) + rpc2 = make_xmr_rpc2_func(rpcport, daemon_login, rpchost, proxy_host=proxy_host, proxy_port=proxy_port) test = rpc2('get_height', timeout=20)['height'] coin_settings['rpchost'] = rpchost coin_settings['rpcport'] = rpcport diff --git a/basicswap/interface/xmr.py b/basicswap/interface/xmr.py index 7856c88..17c060d 100644 --- a/basicswap/interface/xmr.py +++ b/basicswap/interface/xmr.py @@ -80,12 +80,6 @@ class XMRInterface(CoinInterface): def __init__(self, coin_settings, network, swap_client=None): super().__init__(network) - daemon_login = None - if coin_settings.get('rpcuser', '') != '': - daemon_login = (coin_settings.get('rpcuser', ''), coin_settings.get('rpcpassword', '')) - self.rpc = make_xmr_rpc_func(coin_settings['rpcport'], daemon_login, host=coin_settings.get('rpchost', '127.0.0.1')) - self.rpc2 = make_xmr_rpc2_func(coin_settings['rpcport'], daemon_login, host=coin_settings.get('rpchost', '127.0.0.1')) # non-json endpoint - self.rpc_wallet = make_xmr_rpc_func(coin_settings['walletrpcport'], coin_settings['walletrpcauth'], host=coin_settings.get('walletrpchost', '127.0.0.1')) self.blocks_confirmed = coin_settings['blocks_confirmed'] self._restore_height = coin_settings.get('restore_height', 0) @@ -95,6 +89,30 @@ class XMRInterface(CoinInterface): self._wallet_password = None self._have_checked_seed = False + daemon_login = None + if coin_settings.get('rpcuser', '') != '': + daemon_login = (coin_settings.get('rpcuser', ''), coin_settings.get('rpcpassword', '')) + + rpchost = coin_settings.get('rpchost', '127.0.0.1') + proxy_host = None + proxy_port = None + # Connect to the daemon over a proxy if not running locally + if swap_client: + if swap_client.use_tor_proxy: + chain_client_settings = swap_client.getChainClientSettings(self.coin_type()) + if chain_client_settings['manage_daemon'] is False: + proxy_host = swap_client.tor_proxy_host + proxy_port = swap_client.tor_proxy_port + self._log.info(f'Connecting to remote {self.coin_name()} daemon at {rpchost} through proxy at {proxy_host}.') + else: + self._log.info(f'Not connecting to local {self.coin_name()} daemon through proxy.') + elif chain_client_settings['manage_daemon'] is False: + self._log.info(f'Connecting to remote {self.coin_name()} daemon at {proxy_host}.') + + self.rpc = make_xmr_rpc_func(coin_settings['rpcport'], daemon_login, host=rpchost, proxy_host=proxy_host, proxy_port=proxy_port) + self.rpc2 = make_xmr_rpc2_func(coin_settings['rpcport'], daemon_login, host=rpchost, proxy_host=proxy_host, proxy_port=proxy_port) # non-json endpoint + self.rpc_wallet = make_xmr_rpc_func(coin_settings['walletrpcport'], coin_settings['walletrpcauth'], host=coin_settings.get('walletrpchost', '127.0.0.1')) + def checkWallets(self) -> int: return 1 diff --git a/basicswap/rpc_xmr.py b/basicswap/rpc_xmr.py index 69a9d61..eb5ab26 100644 --- a/basicswap/rpc_xmr.py +++ b/basicswap/rpc_xmr.py @@ -2,6 +2,7 @@ import os import json +import socks import time import urllib import hashlib @@ -10,9 +11,32 @@ from xmlrpc.client import ( Transport, SafeTransport, ) +from sockshandler import SocksiPyConnection from .util import jsonDecimal +class SocksTransport(Transport): + + def set_proxy(self, proxy_host, proxy_port): + self.proxy_host = proxy_host + self.proxy_port = proxy_port + + self.proxy_type = socks.PROXY_TYPE_SOCKS5 + self.proxy_rdns = True + self.proxy_username = None + self.proxy_password = None + + def make_connection(self, host): + # return an existing connection if possible. This allows + # HTTP/1.1 keep-alive. + if self._connection and host == self._connection[0]: + return self._connection[1] + # create a HTTP connection object from a host descriptor + chost, self._extra_headers, x509 = self.get_host_info(host) + self._connection = host, SocksiPyConnection(self.proxy_type, self.proxy_host, self.proxy_port, self.proxy_rdns, self.proxy_username, self.proxy_password, chost) + return self._connection[1] + + class JsonrpcDigest(): # __getattr__ complicates extending ServerProxy def __init__(self, uri, transport=None, encoding=None, verbose=False, @@ -148,7 +172,7 @@ class JsonrpcDigest(): raise -def callrpc_xmr(rpc_port, method, params=[], rpc_host='127.0.0.1', path='json_rpc', auth=None, timeout=120): +def callrpc_xmr(rpc_port, method, params=[], rpc_host='127.0.0.1', path='json_rpc', auth=None, timeout=120, transport=None): # auth is a tuple: (username, password) try: if rpc_host.count('://') > 0: @@ -156,7 +180,7 @@ def callrpc_xmr(rpc_port, method, params=[], rpc_host='127.0.0.1', path='json_rp else: url = 'http://{}:{}/{}'.format(rpc_host, rpc_port, path) - x = JsonrpcDigest(url) + x = JsonrpcDigest(url, transport=transport) request_body = { 'method': method, 'params': params, @@ -178,14 +202,14 @@ def callrpc_xmr(rpc_port, method, params=[], rpc_host='127.0.0.1', path='json_rp return r['result'] -def callrpc_xmr2(rpc_port: int, method: str, params=None, auth=None, rpc_host='127.0.0.1', timeout=120): +def callrpc_xmr2(rpc_port: int, method: str, params=None, auth=None, rpc_host='127.0.0.1', timeout=120, transport=None): try: if rpc_host.count('://') > 0: url = '{}:{}/{}'.format(rpc_host, rpc_port, method) else: url = 'http://{}:{}/{}'.format(rpc_host, rpc_port, method) - x = JsonrpcDigest(url) + x = JsonrpcDigest(url, transport=transport) if auth: v = x.json_request(params, username=auth[0], password=auth[1], timeout=timeout) else: @@ -198,23 +222,33 @@ def callrpc_xmr2(rpc_port: int, method: str, params=None, auth=None, rpc_host='1 return r -def make_xmr_rpc2_func(port, auth, host='127.0.0.1'): +def make_xmr_rpc2_func(port, auth, host='127.0.0.1', proxy_host=None, proxy_port=None): port = port auth = auth host = host + transport = None + + if proxy_host: + transport = SocksTransport() + transport.set_proxy(proxy_host, proxy_port) def rpc_func(method, params=None, wallet=None, timeout=120): - nonlocal port, auth, host - return callrpc_xmr2(port, method, params, auth=auth, rpc_host=host, timeout=timeout) + nonlocal port, auth, host, transport + return callrpc_xmr2(port, method, params, auth=auth, rpc_host=host, timeout=timeout, transport=transport) return rpc_func -def make_xmr_rpc_func(port, auth, host='127.0.0.1'): +def make_xmr_rpc_func(port, auth, host='127.0.0.1', proxy_host=None, proxy_port=None): port = port auth = auth host = host + transport = None + + if proxy_host: + transport = SocksTransport() + transport.set_proxy(proxy_host, proxy_port) def rpc_func(method, params=None, wallet=None, timeout=120): - nonlocal port, auth, host - return callrpc_xmr(port, method, params, rpc_host=host, auth=auth, timeout=timeout) + nonlocal port, auth, host, transport + return callrpc_xmr(port, method, params, rpc_host=host, auth=auth, timeout=timeout, transport=transport) return rpc_func