From a94d320e5daeac936b38c553af357b5c54685225 Mon Sep 17 00:00:00 2001 From: Neil Booth Date: Sun, 2 Apr 2017 14:59:45 +0900 Subject: [PATCH] New feature: force peer discovery via proxy Set FORCE_PROXY to non-empty to force peer discovery to go through the proxy. See docs/ENVIRONMENT.rst Wait for an attempt at proxy discovery to be made before beginning peer discovery. --- docs/ENVIRONMENT.rst | 12 +++++++++++- lib/socks.py | 3 +++ server/env.py | 1 + server/peers.py | 44 +++++++++++++++++++++++++------------------- server/session.py | 4 ++-- 5 files changed, 42 insertions(+), 22 deletions(-) diff --git a/docs/ENVIRONMENT.rst b/docs/ENVIRONMENT.rst index f6d5517..5c63cd5 100644 --- a/docs/ENVIRONMENT.rst +++ b/docs/ENVIRONMENT.rst @@ -251,6 +251,15 @@ some of this. peer discovery if it notices it is not present in the peer's returned list. +* **FORCE_PROXY** + + By default peer discovery happens over the clear internet. Set this + to non-empty to force peer discovery to be done via the proxy. This + might be useful if you are running a Tor service exclusively and + wish to keep your IP address private. **NOTE**: in such a case you + should leave **IRC** unset as IRC connections are *always* over the + normal internet. + * **TOR_PROXY_HOST** The host where your Tor proxy is running. Defaults to *localhost*. @@ -316,7 +325,8 @@ connectivity on IRC: * **IRC** - Set to anything non-empty to advertise on IRC + Set to anything non-empty to advertise on IRC. ElectrumX connects + to IRC over the clear internet, always. * **IRC_NICK** diff --git a/lib/socks.py b/lib/socks.py index d31a5d1..fef9498 100644 --- a/lib/socks.py +++ b/lib/socks.py @@ -146,6 +146,7 @@ class SocksProxy(util.LoggedClass): self.errors = 0 self.ip_addr = None self.lost_event = asyncio.Event() + self.tried_event = asyncio.Event() self.loop = loop or asyncio.get_event_loop() self.set_lost() @@ -209,6 +210,8 @@ class SocksProxy(util.LoggedClass): self.logger.info('failed to detect proxy at {}: {}' .format(util.address_string(paddress), e)) + self.tried_event.set() + # Failed all ports? if sock is None: return diff --git a/server/env.py b/server/env.py index 64f9b2c..bef42c8 100644 --- a/server/env.py +++ b/server/env.py @@ -53,6 +53,7 @@ class Env(LoggedClass): # Peer discovery self.peer_discovery = bool(self.default('PEER_DISCOVERY', True)) self.peer_announce = bool(self.default('PEER_ANNOUNCE', True)) + self.force_proxy = bool(self.default('FORCE_PROXY', False)) self.tor_proxy_host = self.default('TOR_PROXY_HOST', 'localhost') self.tor_proxy_port = self.integer('TOR_PROXY_PORT', None) # The electrum client takes the empty string as unspecified diff --git a/server/peers.py b/server/peers.py index 47786c6..ac1ff33 100644 --- a/server/peers.py +++ b/server/peers.py @@ -195,15 +195,7 @@ class PeerSession(JSONSession): def shutdown_connection(self): self.peer.last_connect = time.time() is_good = not (self.failed or self.bad) - self.peer_mgr.set_connection_status(self.peer, is_good) - if self.peer.is_tor: - how = 'via {} over Tor'.format(self.kind) - else: - how = 'via {} at {}'.format(self.kind, - self.peer_addr(anon=False)) - status = 'verified' if is_good else 'failed to verify' - elapsed = time.time() - self.peer.last_try - self.log_info('{} {} in {:.1f}s'.format(status, how, elapsed)) + self.peer_mgr.set_verification_status(self.peer, self.kind, is_good) self.close_connection() @@ -230,8 +222,8 @@ class PeerManager(util.LoggedClass): self.peers = set() self.onion_peers = [] self.permit_onion_peer_time = time.time() - self.tor_proxy = SocksProxy(env.tor_proxy_host, env.tor_proxy_port, - loop=self.loop) + self.proxy = SocksProxy(env.tor_proxy_host, env.tor_proxy_port, + loop=self.loop) self.import_peers() def my_clearnet_peer(self): @@ -462,13 +454,19 @@ class PeerManager(util.LoggedClass): 2) Verifying connectivity of new peers. 3) Retrying old peers at regular intervals. ''' - self.ensure_future(self.tor_proxy.auto_detect_loop()) self.connect_to_irc() if not self.env.peer_discovery: self.logger.info('peer discovery is disabled') return + # Wait a few seconds after starting the proxy detection loop + # for proxy detection to succeed + self.ensure_future(self.proxy.auto_detect_loop()) + await self.proxy.tried_event.wait() + self.logger.info('beginning peer discovery') + self.logger.info('force use of proxy: {}'.format(self.env.force_proxy)) + try: while True: timeout = self.loop.call_later(WAKEUP_SECS, @@ -516,11 +514,11 @@ class PeerManager(util.LoggedClass): kind, port = port_pairs[0] sslc = ssl.SSLContext(ssl.PROTOCOL_TLS) if kind == 'SSL' else None - if peer.is_tor: - # Don't attempt an onion connection if we don't have a tor proxy - if not self.tor_proxy.is_up(): + if self.env.force_proxy or peer.is_tor: + # Only attempt a proxy connection if the proxy is up + if not self.proxy.is_up(): return - create_connection = self.tor_proxy.create_connection + create_connection = self.proxy.create_connection else: create_connection = self.loop.create_connection @@ -546,10 +544,18 @@ class PeerManager(util.LoggedClass): if port_pairs: self.retry_peer(peer, port_pairs) else: - self.set_connection_status(peer, False) + self.maybe_forget_peer(peer) + + def set_verification_status(self, peer, kind, good): + '''Called when a verification succeeded or failed.''' + if self.env.force_proxy or peer.is_tor: + how = 'via {} over Tor'.format(kind) + else: + how = 'via {} at {}'.format(kind, peer.ip_addr) + status = 'verified' if good else 'failed to verify' + elapsed = time.time() - peer.last_try + self.log_info('{} {} in {:.1f}s'.format(status, how, elapsed)) - def set_connection_status(self, peer, good): - '''Called when a connection succeeded or failed.''' if good: peer.try_count = 0 peer.source = 'peer' diff --git a/server/session.py b/server/session.py index 5236001..b184e41 100644 --- a/server/session.py +++ b/server/session.py @@ -277,9 +277,9 @@ class ElectrumX(SessionBase): def is_tor(self): '''Try to detect if the connection is to a tor hidden service we are running.''' - tor_proxy = self.controller.peer_mgr.tor_proxy + proxy = self.controller.peer_mgr.proxy peer_info = self.peer_info() - return peer_info and peer_info[0] == tor_proxy.ip_addr + return peer_info and peer_info[0] == proxy.ip_addr async def replaced_banner(self, banner): network_info = await self.controller.daemon_request('getnetworkinfo')