From f01074e5b812c7873499bdface4ac6cc0543a6f8 Mon Sep 17 00:00:00 2001 From: Mark Qvist Date: Fri, 27 Oct 2023 18:16:52 +0200 Subject: [PATCH] Implemented link establishment on ultra low bandwidth links --- RNS/Link.py | 7 +++++-- RNS/Packet.py | 4 ++-- RNS/Reticulum.py | 45 ++++++++++++++++++++++++++++++++++++++++++++- RNS/Transport.py | 42 +++++++++++++++++++++++++++++++++++++++++- RNS/__init__.py | 3 +++ 5 files changed, 95 insertions(+), 6 deletions(-) diff --git a/RNS/Link.py b/RNS/Link.py index 3f77e25..e34e994 100644 --- a/RNS/Link.py +++ b/RNS/Link.py @@ -112,9 +112,10 @@ class Link: link = Link(owner = owner, peer_pub_bytes=data[:Link.ECPUBSIZE//2], peer_sig_pub_bytes=data[Link.ECPUBSIZE//2:Link.ECPUBSIZE]) link.set_link_id(packet) link.destination = packet.destination - link.establishment_timeout = Link.ESTABLISHMENT_TIMEOUT_PER_HOP * max(1, packet.hops) + link.establishment_timeout = Link.ESTABLISHMENT_TIMEOUT_PER_HOP * max(1, packet.hops) + Link.KEEPALIVE link.establishment_cost += len(packet.raw) RNS.log("Validating link request "+RNS.prettyhexrep(link.link_id), RNS.LOG_VERBOSE) + RNS.log(f"Establishment timeout is {RNS.prettytime(link.establishment_timeout)} for incoming link request "+RNS.prettyhexrep(link.link_id), RNS.LOG_EXTREME) link.handshake() link.attached_interface = packet.receiving_interface link.prove() @@ -175,7 +176,8 @@ class Link: self.sig_prv = self.owner.identity.sig_prv else: self.initiator = True - self.establishment_timeout = Link.ESTABLISHMENT_TIMEOUT_PER_HOP * max(1, RNS.Transport.hops_to(destination.hash)) + self.establishment_timeout = RNS.Reticulum.get_instance().get_first_hop_timeout(destination.hash) + self.establishment_timeout += Link.ESTABLISHMENT_TIMEOUT_PER_HOP * max(1, RNS.Transport.hops_to(destination.hash)) self.prv = X25519PrivateKey.generate() self.sig_prv = Ed25519PrivateKey.generate() @@ -211,6 +213,7 @@ class Link: self.packet.send() self.had_outbound() RNS.log("Link request "+RNS.prettyhexrep(self.link_id)+" sent to "+str(self.destination), RNS.LOG_DEBUG) + RNS.log(f"Establishment timeout is {RNS.prettytime(self.establishment_timeout)} for link request "+RNS.prettyhexrep(self.link_id), RNS.LOG_EXTREME) def load_peer(self, peer_pub_bytes, peer_sig_pub_bytes): diff --git a/RNS/Packet.py b/RNS/Packet.py index 3d62451..c68e2b4 100755 --- a/RNS/Packet.py +++ b/RNS/Packet.py @@ -371,8 +371,8 @@ class PacketReceipt: if packet.destination.type == RNS.Destination.LINK: self.timeout = packet.destination.rtt * packet.destination.traffic_timeout_factor else: - self.timeout = Packet.TIMEOUT_PER_HOP * RNS.Transport.hops_to(self.destination.hash) - + self.timeout = RNS.Reticulum.get_instance().get_first_hop_timeout(destination.hash) + self.timeout += Packet.TIMEOUT_PER_HOP * RNS.Transport.hops_to(self.destination.hash) def get_status(self): """ diff --git a/RNS/Reticulum.py b/RNS/Reticulum.py index 5f17073..88d5c8d 100755 --- a/RNS/Reticulum.py +++ b/RNS/Reticulum.py @@ -117,7 +117,7 @@ class Reticulum: # from interface speed, but a better general approach would most # probably be to let Reticulum somehow continously build a map of # per-hop latencies and use this map for the timeout calculation. - DEFAULT_PER_HOP_TIMEOUT = 6 + DEFAULT_PER_HOP_TIMEOUT = 4 # Length of truncated hashes in bits. TRUNCATED_HASHLENGTH = 128 @@ -145,6 +145,8 @@ class Reticulum: configpath = "" storagepath = "" cachepath = "" + + __instance = None @staticmethod def exit_handler(): @@ -168,6 +170,13 @@ class Reticulum: RNS.exit() + @staticmethod + def get_instance(): + """ + Return the currently running Reticulum instance + """ + return Reticulum.__instance + def __init__(self,configdir=None, loglevel=None, logdest=None, verbosity=None): """ Initialises and starts a Reticulum instance. This must be @@ -177,6 +186,11 @@ class Reticulum: :param configdir: Full path to a Reticulum configuration directory. """ + if Reticulum.__instance != None: + raise OSError("Attempt to reinitialise Reticulum, when it was already running") + else: + Reticulum.__instance = self + RNS.vendor.platformutils.platform_checks() if configdir != None: @@ -303,6 +317,11 @@ class Reticulum: self.local_interface_port ) interface.OUT = True + if hasattr(Reticulum, "_force_shared_instance_bitrate"): + interface.bitrate = Reticulum._force_shared_instance_bitrate + interface._force_bitrate = True + RNS.log(f"Forcing shared instance bitrate of {RNS.prettyspeed(interface.bitrate)}ps", RNS.LOG_WARNING) + interface._force_bitrate = Reticulum._force_shared_instance_bitrate RNS.Transport.interfaces.append(interface) self.is_shared_instance = True @@ -317,6 +336,10 @@ class Reticulum: self.local_interface_port) interface.target_port = self.local_interface_port interface.OUT = True + if hasattr(Reticulum, "_force_shared_instance_bitrate"): + interface.bitrate = Reticulum._force_shared_instance_bitrate + interface._force_bitrate = True + RNS.log(f"Forcing shared instance bitrate of {RNS.prettyspeed(interface.bitrate)}ps", RNS.LOG_WARNING) RNS.Transport.interfaces.append(interface) self.is_shared_instance = False self.is_standalone_instance = False @@ -376,6 +399,9 @@ class Reticulum: v = self.config["reticulum"].as_bool(option) if v == True: Reticulum.__allow_probes = True + if option == "force_shared_instance_bitrate": + v = self.config["reticulum"].as_int(option) + Reticulum._force_shared_instance_bitrate = v if option == "panic_on_interface_error": v = self.config["reticulum"].as_bool(option) if v == True: @@ -1074,6 +1100,9 @@ class Reticulum: if path == "next_hop": rpc_connection.send(self.get_next_hop(call["destination_hash"])) + if path == "first_hop_timeout": + rpc_connection.send(self.get_first_hop_timeout(call["destination_hash"])) + if path == "packet_rssi": rpc_connection.send(self.get_packet_rssi(call["packet_hash"])) @@ -1289,6 +1318,20 @@ class Reticulum: else: return str(RNS.Transport.next_hop_interface(destination)) + def get_first_hop_timeout(self, destination): + if self.is_connected_to_shared_instance: + try: + rpc_connection = multiprocessing.connection.Client(self.rpc_addr, authkey=self.rpc_key) + rpc_connection.send({"get": "first_hop_timeout", "destination_hash": destination}) + response = rpc_connection.recv() + return response + except Exception as e: + RNS.log("An error occurred while getting first hop timeout from shared instance: "+str(e), RNS.LOG_ERROR) + return RNS.Reticulum.DEFAULT_PER_HOP_TIMEOUT + + else: + return RNS.Transport.first_hop_timeout(destination) + def get_next_hop(self, destination): if self.is_connected_to_shared_instance: rpc_connection = multiprocessing.connection.Client(self.rpc_addr, authkey=self.rpc_key) diff --git a/RNS/Transport.py b/RNS/Transport.py index d4681a7..2da8d16 100755 --- a/RNS/Transport.py +++ b/RNS/Transport.py @@ -1197,7 +1197,8 @@ class Transport: if packet.packet_type == RNS.Packet.LINKREQUEST: now = time.time() - proof_timeout = now + RNS.Link.ESTABLISHMENT_TIMEOUT_PER_HOP * max(1, remaining_hops) + proof_timeout = Transport.extra_link_proof_timeout(packet.receiving_interface) + proof_timeout += now + RNS.Link.ESTABLISHMENT_TIMEOUT_PER_HOP * max(1, remaining_hops) # Entry format is link_entry = [ now, # 0: Timestamp, @@ -2026,6 +2027,45 @@ class Transport: else: return None + @staticmethod + def next_hop_interface_bitrate(destination_hash): + next_hop_interface = Transport.next_hop_interface(destination_hash) + if next_hop_interface != None: + return next_hop_interface.bitrate + else: + return None + + @staticmethod + def next_hop_per_bit_latency(destination_hash): + next_hop_interface_bitrate = Transport.next_hop_interface_bitrate(destination_hash) + if next_hop_interface_bitrate != None: + return (1/next_hop_interface_bitrate) + else: + return None + + @staticmethod + def next_hop_per_byte_latency(destination_hash): + per_byte_latency = Transport.next_hop_per_bit_latency(destination_hash) + if per_byte_latency != None: + return per_byte_latency*8 + else: + return None + + @staticmethod + def first_hop_timeout(destination_hash): + latency = Transport.next_hop_per_byte_latency(destination_hash) + if latency != None: + return RNS.Reticulum.MTU * latency + else: + return RNS.Reticulum.DEFAULT_PER_HOP_TIMEOUT + + @staticmethod + def extra_link_proof_timeout(interface): + if interface != None: + return ((1/interface.bitrate)*8)*RNS.Reticulum.MTU + else: + return 0 + @staticmethod def expire_path(destination_hash): if destination_hash in Transport.destination_table: diff --git a/RNS/__init__.py b/RNS/__init__.py index ed740bd..0e0b7a2 100755 --- a/RNS/__init__.py +++ b/RNS/__init__.py @@ -161,6 +161,9 @@ def prettyhexrep(data): hexrep = "<"+delimiter.join("{:02x}".format(c) for c in data)+">" return hexrep +def prettyspeed(num, suffix="b"): + return prettysize(num/8, suffix=suffix)+"ps" + def prettysize(num, suffix='B'): units = ['','K','M','G','T','P','E','Z'] last_unit = 'Y'