network: do not connect to multiple servers on same /16
maintain a healthy spread of (IP addresses of) connected servers
This commit is contained in:
@@ -52,7 +52,7 @@ from . import blockchain
|
||||
from . import bitcoin
|
||||
from .blockchain import Blockchain, HEADER_SIZE
|
||||
from .interface import (Interface, serialize_server, deserialize_server,
|
||||
RequestTimedOut, NetworkTimeout)
|
||||
RequestTimedOut, NetworkTimeout, BUCKET_NAME_OF_ONION_SERVERS)
|
||||
from .version import PROTOCOL_VERSION
|
||||
from .simple_config import SimpleConfig
|
||||
from .i18n import _
|
||||
@@ -756,6 +756,30 @@ class Network(PrintError):
|
||||
self._add_recent_server(server)
|
||||
self.trigger_callback('network_updated')
|
||||
|
||||
def check_interface_against_healthy_spread_of_connected_servers(self, iface_to_check) -> bool:
|
||||
# main interface is exempt. this makes switching servers easier
|
||||
if iface_to_check.is_main_server():
|
||||
return True
|
||||
# bucket connected interfaces
|
||||
with self.interfaces_lock:
|
||||
interfaces = list(self.interfaces.values())
|
||||
if iface_to_check in interfaces:
|
||||
interfaces.remove(iface_to_check)
|
||||
buckets = defaultdict(list)
|
||||
for iface in interfaces:
|
||||
buckets[iface.bucket_based_on_ipaddress()].append(iface)
|
||||
# check proposed server against buckets
|
||||
onion_servers = buckets[BUCKET_NAME_OF_ONION_SERVERS]
|
||||
if iface_to_check.is_tor():
|
||||
# keep number of onion servers below half of all connected servers
|
||||
if len(onion_servers) > NUM_TARGET_CONNECTED_SERVERS // 2:
|
||||
return False
|
||||
else:
|
||||
bucket = iface_to_check.bucket_based_on_ipaddress()
|
||||
if len(buckets[bucket]) > 0:
|
||||
return False
|
||||
return True
|
||||
|
||||
async def _init_headers_file(self):
|
||||
b = blockchain.get_best_chain()
|
||||
filename = b.path()
|
||||
@@ -1149,11 +1173,20 @@ class Network(PrintError):
|
||||
async def maybe_queue_new_interfaces_to_be_launched_later():
|
||||
now = time.time()
|
||||
for i in range(self.num_server - len(self.interfaces) - len(self.connecting)):
|
||||
# FIXME this should try to honour "healthy spread of connected servers"
|
||||
self._start_random_interface()
|
||||
if now - self.nodes_retry_time > NODES_RETRY_INTERVAL:
|
||||
self.print_error('network: retrying connections')
|
||||
self.disconnected_servers = set([])
|
||||
self.nodes_retry_time = now
|
||||
async def maintain_healthy_spread_of_connected_servers():
|
||||
with self.interfaces_lock: interfaces = list(self.interfaces.values())
|
||||
random.shuffle(interfaces)
|
||||
for iface in interfaces:
|
||||
if not self.check_interface_against_healthy_spread_of_connected_servers(iface):
|
||||
self.print_error(f"disconnecting from {iface.server}. too many connected "
|
||||
f"servers already in bucket {iface.bucket_based_on_ipaddress()}")
|
||||
await self._close_interface(iface)
|
||||
async def maintain_main_interface():
|
||||
await self._ensure_there_is_a_main_interface()
|
||||
if self.is_connected():
|
||||
@@ -1164,6 +1197,7 @@ class Network(PrintError):
|
||||
try:
|
||||
await launch_already_queued_up_new_interfaces()
|
||||
await maybe_queue_new_interfaces_to_be_launched_later()
|
||||
await maintain_healthy_spread_of_connected_servers()
|
||||
await maintain_main_interface()
|
||||
except asyncio.CancelledError:
|
||||
# suppress spurious cancellations
|
||||
|
||||
Reference in New Issue
Block a user