Browse Source

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.
master
Neil Booth 8 years ago
parent
commit
a94d320e5d
  1. 12
      docs/ENVIRONMENT.rst
  2. 3
      lib/socks.py
  3. 1
      server/env.py
  4. 44
      server/peers.py
  5. 4
      server/session.py

12
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**

3
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

1
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

44
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'

4
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')

Loading…
Cancel
Save