Improve proxy handling
Have a background proxy detection loop; removes need to check specific peers at startup. Consider proxy down once attempts to use it fail 3 times in a row. Regularly attempt to rediscover a proxy if it is down.
This commit is contained in:
parent
0aa9195fc5
commit
77a441ad06
119
lib/socks.py
119
lib/socks.py
@ -137,44 +137,101 @@ class Socks(util.LoggedClass):
|
||||
class SocksProxy(util.LoggedClass):
|
||||
|
||||
def __init__(self, host, port, loop=None):
|
||||
'''Host can be an IPv4 address, IPv6 address, or a host name.'''
|
||||
'''Host can be an IPv4 address, IPv6 address, or a host name.
|
||||
Port can be None, in which case one is auto-detected.'''
|
||||
super().__init__()
|
||||
# Host and port of the proxy
|
||||
self.host = host
|
||||
self.port = port
|
||||
self.try_ports = [port, 9050, 9150, 1080]
|
||||
self.errors = 0
|
||||
self.ip_addr = None
|
||||
self.lost_event = asyncio.Event()
|
||||
self.loop = loop or asyncio.get_event_loop()
|
||||
self.set_lost()
|
||||
|
||||
async def auto_detect_loop(self):
|
||||
'''Try to detect a proxy at regular intervals until one is found.
|
||||
If one is found, do nothing until one is lost.'''
|
||||
while True:
|
||||
await self.lost_event.wait()
|
||||
self.lost_event.clear()
|
||||
tries = 0
|
||||
while True:
|
||||
tries += 1
|
||||
log_failure = tries % 10 == 1
|
||||
await self.detect_proxy(log_failure=log_failure)
|
||||
if self.is_up():
|
||||
break
|
||||
await asyncio.sleep(600)
|
||||
|
||||
def is_up(self):
|
||||
'''Returns True if we have a good proxy.'''
|
||||
return self.port is not None
|
||||
|
||||
def set_lost(self):
|
||||
'''Called when the proxy appears lost/down.'''
|
||||
self.port = None
|
||||
self.lost_event.set()
|
||||
|
||||
async def connect_via_proxy(self, host, port, proxy_address=None):
|
||||
'''Connect to a (host, port) pair via the proxy. Returns the
|
||||
connected socket on success.'''
|
||||
proxy_address = proxy_address or (self.host, self.port)
|
||||
sock = socket.socket()
|
||||
sock.setblocking(False)
|
||||
try:
|
||||
await self.loop.sock_connect(sock, proxy_address)
|
||||
socks = Socks(self.loop, sock, host, port)
|
||||
await socks.handshake()
|
||||
return sock
|
||||
except Exception:
|
||||
sock.close()
|
||||
raise
|
||||
|
||||
async def detect_proxy(self, host='www.google.com', port=80,
|
||||
log_failure=True):
|
||||
'''Attempt to detect a proxy by establishing a connection through it
|
||||
to the given target host / port pair.
|
||||
'''
|
||||
if self.is_up():
|
||||
return
|
||||
|
||||
sock = None
|
||||
for proxy_port in self.try_ports:
|
||||
if proxy_port is None:
|
||||
continue
|
||||
paddress = (self.host, proxy_port)
|
||||
try:
|
||||
sock = await self.connect_via_proxy(host, port, paddress)
|
||||
break
|
||||
except Exception as e:
|
||||
if log_failure:
|
||||
self.logger.info('failed to detect proxy at {}: {}'
|
||||
.format(util.address_string(paddress), e))
|
||||
|
||||
# Failed all ports?
|
||||
if sock is None:
|
||||
return
|
||||
|
||||
peername = sock.getpeername()
|
||||
sock.close()
|
||||
self.ip_addr = peername[0]
|
||||
self.port = proxy_port
|
||||
self.errors = 0
|
||||
self.logger.info('detected proxy at {} ({})'
|
||||
.format(util.address_string(paddress), self.ip_addr))
|
||||
|
||||
async def create_connection(self, protocol_factory, host, port, ssl=None):
|
||||
'''All arguments are as to asyncio's create_connection method.'''
|
||||
if self.port is None:
|
||||
proxy_ports = [9050, 9150, 1080]
|
||||
else:
|
||||
proxy_ports = [self.port]
|
||||
|
||||
for proxy_port in proxy_ports:
|
||||
address = (self.host, proxy_port)
|
||||
sock = socket.socket()
|
||||
sock.setblocking(False)
|
||||
try:
|
||||
await self.loop.sock_connect(sock, address)
|
||||
except OSError as e:
|
||||
if proxy_port == proxy_ports[-1]:
|
||||
raise
|
||||
continue
|
||||
|
||||
socks = Socks(self.loop, sock, host, port)
|
||||
try:
|
||||
await socks.handshake()
|
||||
if self.port is None:
|
||||
self.ip_addr = sock.getpeername()[0]
|
||||
self.port = proxy_port
|
||||
self.logger.info('detected proxy at {} ({})'
|
||||
.format(util.address_string(address),
|
||||
self.ip_addr))
|
||||
break
|
||||
except Exception as e:
|
||||
sock.close()
|
||||
raise
|
||||
try:
|
||||
sock = await self.connect_via_proxy(host, port)
|
||||
self.errors = 0
|
||||
except Exception:
|
||||
self.errors += 1
|
||||
# If we have 3 consecutive errors, consider the proxy undetected
|
||||
if self.errors == 3:
|
||||
self.set_lost()
|
||||
raise
|
||||
|
||||
hostname = host if ssl else None
|
||||
return await self.loop.create_connection(
|
||||
|
||||
@ -230,7 +230,6 @@ class PeerManager(util.LoggedClass):
|
||||
self.peers = set()
|
||||
self.onion_peers = []
|
||||
self.permit_onion_peer_time = time.time()
|
||||
self.last_tor_retry_time = 0
|
||||
self.tor_proxy = SocksProxy(env.tor_proxy_host, env.tor_proxy_port,
|
||||
loop=self.loop)
|
||||
self.import_peers()
|
||||
@ -463,6 +462,7 @@ 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')
|
||||
@ -492,10 +492,6 @@ class PeerManager(util.LoggedClass):
|
||||
nearly_stale_time = (now - STALE_SECS) + WAKEUP_SECS * 2
|
||||
|
||||
def should_retry(peer):
|
||||
# Try some Tor at startup to determine the proxy so we can
|
||||
# serve the right banner file
|
||||
if self.tor_proxy.port is None and self.is_coin_onion_peer(peer):
|
||||
return True
|
||||
# Retry a peer whose ports might have updated
|
||||
if peer.other_port_pairs:
|
||||
return True
|
||||
@ -507,14 +503,6 @@ class PeerManager(util.LoggedClass):
|
||||
|
||||
peers = [peer for peer in self.peers if should_retry(peer)]
|
||||
|
||||
# If we don't have a tor proxy drop tor peers, but retry
|
||||
# occasionally
|
||||
if self.tor_proxy.port is None:
|
||||
if now < self.last_tor_retry_time + 3600:
|
||||
peers = [peer for peer in peers if not peer.is_tor]
|
||||
elif any(peer.is_tor for peer in peers):
|
||||
self.last_tor_retry_time = now
|
||||
|
||||
for peer in peers:
|
||||
peer.try_count += 1
|
||||
pairs = peer.connection_port_pairs()
|
||||
@ -529,6 +517,9 @@ class PeerManager(util.LoggedClass):
|
||||
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():
|
||||
return
|
||||
create_connection = self.tor_proxy.create_connection
|
||||
else:
|
||||
create_connection = self.loop.create_connection
|
||||
|
||||
Loading…
Reference in New Issue
Block a user