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):
|
class SocksProxy(util.LoggedClass):
|
||||||
|
|
||||||
def __init__(self, host, port, loop=None):
|
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__()
|
super().__init__()
|
||||||
|
# Host and port of the proxy
|
||||||
self.host = host
|
self.host = host
|
||||||
self.port = port
|
self.try_ports = [port, 9050, 9150, 1080]
|
||||||
|
self.errors = 0
|
||||||
self.ip_addr = None
|
self.ip_addr = None
|
||||||
|
self.lost_event = asyncio.Event()
|
||||||
self.loop = loop or asyncio.get_event_loop()
|
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):
|
async def create_connection(self, protocol_factory, host, port, ssl=None):
|
||||||
'''All arguments are as to asyncio's create_connection method.'''
|
'''All arguments are as to asyncio's create_connection method.'''
|
||||||
if self.port is None:
|
try:
|
||||||
proxy_ports = [9050, 9150, 1080]
|
sock = await self.connect_via_proxy(host, port)
|
||||||
else:
|
self.errors = 0
|
||||||
proxy_ports = [self.port]
|
except Exception:
|
||||||
|
self.errors += 1
|
||||||
for proxy_port in proxy_ports:
|
# If we have 3 consecutive errors, consider the proxy undetected
|
||||||
address = (self.host, proxy_port)
|
if self.errors == 3:
|
||||||
sock = socket.socket()
|
self.set_lost()
|
||||||
sock.setblocking(False)
|
raise
|
||||||
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
|
|
||||||
|
|
||||||
hostname = host if ssl else None
|
hostname = host if ssl else None
|
||||||
return await self.loop.create_connection(
|
return await self.loop.create_connection(
|
||||||
|
|||||||
@ -230,7 +230,6 @@ class PeerManager(util.LoggedClass):
|
|||||||
self.peers = set()
|
self.peers = set()
|
||||||
self.onion_peers = []
|
self.onion_peers = []
|
||||||
self.permit_onion_peer_time = time.time()
|
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,
|
self.tor_proxy = SocksProxy(env.tor_proxy_host, env.tor_proxy_port,
|
||||||
loop=self.loop)
|
loop=self.loop)
|
||||||
self.import_peers()
|
self.import_peers()
|
||||||
@ -463,6 +462,7 @@ class PeerManager(util.LoggedClass):
|
|||||||
2) Verifying connectivity of new peers.
|
2) Verifying connectivity of new peers.
|
||||||
3) Retrying old peers at regular intervals.
|
3) Retrying old peers at regular intervals.
|
||||||
'''
|
'''
|
||||||
|
self.ensure_future(self.tor_proxy.auto_detect_loop())
|
||||||
self.connect_to_irc()
|
self.connect_to_irc()
|
||||||
if not self.env.peer_discovery:
|
if not self.env.peer_discovery:
|
||||||
self.logger.info('peer discovery is disabled')
|
self.logger.info('peer discovery is disabled')
|
||||||
@ -492,10 +492,6 @@ class PeerManager(util.LoggedClass):
|
|||||||
nearly_stale_time = (now - STALE_SECS) + WAKEUP_SECS * 2
|
nearly_stale_time = (now - STALE_SECS) + WAKEUP_SECS * 2
|
||||||
|
|
||||||
def should_retry(peer):
|
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
|
# Retry a peer whose ports might have updated
|
||||||
if peer.other_port_pairs:
|
if peer.other_port_pairs:
|
||||||
return True
|
return True
|
||||||
@ -507,14 +503,6 @@ class PeerManager(util.LoggedClass):
|
|||||||
|
|
||||||
peers = [peer for peer in self.peers if should_retry(peer)]
|
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:
|
for peer in peers:
|
||||||
peer.try_count += 1
|
peer.try_count += 1
|
||||||
pairs = peer.connection_port_pairs()
|
pairs = peer.connection_port_pairs()
|
||||||
@ -529,6 +517,9 @@ class PeerManager(util.LoggedClass):
|
|||||||
sslc = ssl.SSLContext(ssl.PROTOCOL_TLS) if kind == 'SSL' else None
|
sslc = ssl.SSLContext(ssl.PROTOCOL_TLS) if kind == 'SSL' else None
|
||||||
|
|
||||||
if peer.is_tor:
|
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
|
create_connection = self.tor_proxy.create_connection
|
||||||
else:
|
else:
|
||||||
create_connection = self.loop.create_connection
|
create_connection = self.loop.create_connection
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user