diff --git a/RNS/Identity.py b/RNS/Identity.py index a2512efe..988d3a43 100644 --- a/RNS/Identity.py +++ b/RNS/Identity.py @@ -556,7 +556,8 @@ class Identity: if signal_blackholed: return "blackholed" else: return False - if announced_identity.pub != None and announced_identity.validate(signature, signed_data): + if announced_identity.pub != None and (packet.announce_signature_validated or announced_identity.validate(signature, signed_data)): + packet.announce_signature_validated = True if only_validate_signature: del announced_identity return True diff --git a/RNS/Interfaces/BackboneInterface.py b/RNS/Interfaces/BackboneInterface.py index 27e22bb7..d503eea4 100644 --- a/RNS/Interfaces/BackboneInterface.py +++ b/RNS/Interfaces/BackboneInterface.py @@ -797,8 +797,9 @@ class BackboneClientInterface(Interface): def process_outgoing(self, data): if self.online and not self.detached: try: + buffer_was_empty = len(self.transmit_buffer) == 0 self.transmit_buffer += bytes([HDLC.FLAG])+HDLC.escape(data)+bytes([HDLC.FLAG]) - BackboneInterface.tx_ready(self) + if buffer_was_empty: BackboneInterface.tx_ready(self) except Exception as e: RNS.log("Exception occurred while transmitting via "+str(self)+", tearing down interface", RNS.LOG_ERROR) diff --git a/RNS/Packet.py b/RNS/Packet.py index 8c6676f9..9357adc0 100755 --- a/RNS/Packet.py +++ b/RNS/Packet.py @@ -117,7 +117,8 @@ class Packet: __slots__ = "hops", "header", "header_type", "packet_type", "transport_type", "context", "context_flag", "destination" __slots__ += "transport_id", "data", "flags", "raw", "packed", "sent", "create_receipt", "receipt", "fromPacked", "MTU" __slots__ += "sent_at", "packet_hash", "ratchet_id", "attached_interface", "receiving_interface", "rssi", "snr", "q" - __slots__ += "ciphertext", "plaintext", "destination_hash", "destination_type", "link", "map_hash", "is_outbound_pr", "traffic_class" + __slots__ += "ciphertext", "plaintext", "destination_hash", "destination_type", "link", "map_hash", "is_outbound_pr" + __slots__ += "traffic_class", "announce_signature_validated" def __init__(self, destination, data, packet_type = DATA, context = NONE, transport_type = RNS.Transport.BROADCAST, header_type = HEADER_1, transport_id = None, attached_interface = None, create_receipt = True, context_flag=FLAG_UNSET): @@ -160,6 +161,7 @@ class Packet: self.ratchet_id = None self.traffic_class = None + self.announce_signature_validated = None self.attached_interface = attached_interface self.receiving_interface = None self.is_outbound_pr = False @@ -350,6 +352,10 @@ class Packet: def getTruncatedHash(self): return RNS.Identity.truncated_hash(self.get_hashable_part()) + @property + def truncated_packet_hash(self): + return self.packet_hash[:RNS.Reticulum.TRUNCATED_HASHLENGTH//8] + def get_hashable_part(self): hashable_part = bytes([self.raw[0] & 0b00001111]) if self.header_type == Packet.HEADER_2: hashable_part += self.raw[(RNS.Identity.TRUNCATED_HASHLENGTH//8)+2:] @@ -380,7 +386,7 @@ class Packet: class ProofDestination: def __init__(self, packet): - self.hash = packet.get_hash()[:RNS.Reticulum.TRUNCATED_HASHLENGTH//8]; + self.hash = packet.truncated_packet_hash; self.type = RNS.Destination.SINGLE def encrypt(self, plaintext): return plaintext @@ -405,8 +411,8 @@ class PacketReceipt: # Creates a new packet receipt from a sent packet def __init__(self, packet): - self.hash = packet.get_hash() - self.truncated_hash = packet.getTruncatedHash() + self.hash = packet.packet_hash + self.truncated_hash = packet.truncated_packet_hash self.sent = True self.sent_at = time.time() self.proved = False diff --git a/RNS/Transport.py b/RNS/Transport.py index fb5e6b93..c54d61bd 100755 --- a/RNS/Transport.py +++ b/RNS/Transport.py @@ -164,6 +164,8 @@ class Transport: destinations_map = {} # Destination hash map of active destinations pending_links = [] # Links that are being established active_links = [] # Links that are active + pending_links_map = {} # Link ID hash map of pending links + active_links_map = {} # Link ID hash map of active links packet_hashlist = set() # A list of packet hashes for duplicate detection packet_hashlist_prev = set() receipts = [] # Receipts of all outgoing packets for proof processing @@ -171,6 +173,7 @@ class Transport: # Notes on memory usage: 1 megabyte of memory can store approximately # 55.100 path table entries or approximately 22.300 link table entries. + link_fp_cache = {} announce_table = {} # A table for storing announces currently waiting to be retransmitted path_table = {} # A lookup table containing the next hop to a given destination reverse_table = {} # A lookup table for storing packet hashes used to return proofs and replies @@ -714,14 +717,18 @@ class Transport: closed_pending_links.append(link) - for closed_link in closed_pending_links: Transport.pending_links.remove(closed_link) + for closed_link in closed_pending_links: + Transport.pending_links.remove(closed_link) + Transport.pending_links_map.pop(closed_link.link_id, None) with Transport.active_links_lock: closed_links = [] for link in Transport.active_links: if link.status == RNS.Link.CLOSED: closed_links.append(link) - for closed_link in closed_links: Transport.active_links.remove(closed_link) + for closed_link in closed_links: + Transport.active_links.remove(closed_link) + Transport.active_links_map.pop(closed_link.link_id, None) Transport.links_last_checked = time.time() @@ -2009,9 +2016,10 @@ class Transport: if packet.transport_id != None and packet.packet_type != RNS.Packet.ANNOUNCE: if not packet.transport_id == Transport.identity.hash: return else: - if packet.destination_hash in Transport.path_table: - next_hop = Transport.path_table[packet.destination_hash][IDX_PT_NEXT_HOP] - remaining_hops = Transport.path_table[packet.destination_hash][IDX_PT_HOPS] + path_entry = Transport.path_table.get(packet.destination_hash) + if path_entry != None: + next_hop = path_entry[IDX_PT_NEXT_HOP] + remaining_hops = path_entry[IDX_PT_HOPS] if remaining_hops > 1: # Just increase hop count and transmit @@ -2044,7 +2052,7 @@ class Transport: new_raw += struct.pack("!B", packet.hops) new_raw += packet.raw[2:] - outbound_interface = Transport.path_table[packet.destination_hash][IDX_PT_RVCD_IF] + outbound_interface = path_entry[IDX_PT_RVCD_IF] if packet.packet_type == RNS.Packet.LINKREQUEST: now = time.time() @@ -2096,11 +2104,11 @@ class Transport: outbound_interface, # 1: Outbound interface time.time() ] # 2: Timestamp - with Transport.reverse_table_lock: Transport.reverse_table[packet.getTruncatedHash()] = reverse_entry + with Transport.reverse_table_lock: Transport.reverse_table[packet.truncated_packet_hash] = reverse_entry if Transport.local_hops_delta != 0 and from_local_client and not to_local_client: new_raw = Transport.mangle_hops(new_raw, Transport.local_hops_delta) Transport.transmit(outbound_interface, new_raw) - with Transport.path_table_lock: Transport.path_table[packet.destination_hash][IDX_PT_TIMESTAMP] = time.time() + path_entry[IDX_PT_TIMESTAMP] = time.time() else: # TODO: There should probably be some kind of REJECT @@ -2151,25 +2159,18 @@ class Transport: Transport.transmit(outbound_interface, new_raw) Transport.link_table[packet.destination_hash][IDX_LT_TIMESTAMP] = time.time() - else: - RNS.log(f"No-outbound return on link packet from {packet.receiving_interface}", RNS.LOG_WARNING) # TODO: Remove - - # TODO: Can we return safely here? Test and possibly enable this at some point. - return + else: RNS.log(f"No-outbound return on link packet from {packet.receiving_interface}", RNS.LOG_EXTREME) if RNS.sl(RNS.LOG_EXTREME) else None + return # Announce handling. Handles logic related to incoming # announces, queueing rebroadcasts of these, and removal # of queued announce rebroadcasts once handed to the next node. if packet.packet_type == RNS.Packet.ANNOUNCE: - local_destination = None - with Transport.destinations_map_lock: - if packet.destination_hash in Transport.destinations_map: - local_destination = Transport.destinations_map[packet.destination_hash] - announce_valid = RNS.Identity.validate_announce(packet) if not announce_valid: return packet.receiving_interface.protocol_violation("Invalid announce") if packet.receiving_interface else None + local_destination = Transport.destinations_map.get(packet.destination_hash) if local_destination == None and announce_valid: if packet.transport_id != None: received_from = packet.transport_id @@ -2536,11 +2537,7 @@ class Transport: # Handling for link requests to local destinations elif packet.packet_type == RNS.Packet.LINKREQUEST and not link_request_handled: if packet.transport_id == None or packet.transport_id == Transport.identity.hash: - destination = None - with Transport.destinations_map_lock: - if packet.destination_hash in Transport.destinations_map: - destination = Transport.destinations_map[packet.destination_hash] - + destination = Transport.destinations_map.get(packet.destination_hash) if destination and destination.type == packet.destination_type: path_mtu = RNS.Link.mtu_from_lr_packet(packet) mode = RNS.Link.mode_from_lr_packet(packet) @@ -2570,39 +2567,31 @@ class Transport: # Handling for local data packets elif packet.packet_type == RNS.Packet.DATA: if packet.destination_type == RNS.Destination.LINK: - with Transport.active_links_lock: - for link in Transport.active_links: - if link.link_id == packet.destination_hash: - if link.attached_interface == packet.receiving_interface: - packet.link = link - if packet.context == RNS.Packet.CACHE_REQUEST: - cached_packet = Transport.get_cached_packet(packet.data) - if cached_packet != None: - if not cached_packet.unpack(): return - RNS.Packet(destination=link, data=cached_packet.data, - packet_type=cached_packet.packet_type, context=cached_packet.context).send() - - else: link.receive(packet) - break - - else: - # In the strange and rare case that an interface - # is partly malfunctioning, and a link-associated - # packet is being received on an interface that - # has failed sending, and transport has failed over - # to another path, we remove this packet hash from - # the filter hashlist so the link can receive the - # packet when it finally arrives over another path. - while packet.packet_hash in Transport.packet_hashlist: - Transport.packet_hashlist.remove(packet.packet_hash) - while packet.packet_hash in Transport.packet_hashlist_prev: - Transport.packet_hashlist_prev.remove(packet.packet_hash) - else: - destination = None - with Transport.destinations_map_lock: - if packet.destination_hash in Transport.destinations_map: - destination = Transport.destinations_map[packet.destination_hash] + link = Transport.active_links_map.get(packet.destination_hash) + if link != None: + if link.attached_interface == packet.receiving_interface: + packet.link = link + if packet.context == RNS.Packet.CACHE_REQUEST: + cached_packet = Transport.get_cached_packet(packet.data) + if cached_packet != None: + if not cached_packet.unpack(): return + RNS.Packet(destination=link, data=cached_packet.data, + packet_type=cached_packet.packet_type, context=cached_packet.context).send() + else: link.receive(packet) + + else: + # In the strange and rare case that an interface + # is partly malfunctioning, and a link-associated + # packet is being received on an interface that + # has failed sending, and transport has failed over + # to another path, we remove this packet hash from + # the filter hashlist so the link can receive the + # packet when it finally arrives over another path. + while packet.packet_hash in Transport.packet_hashlist: Transport.packet_hashlist.remove(packet.packet_hash) + while packet.packet_hash in Transport.packet_hashlist_prev: Transport.packet_hashlist_prev.remove(packet.packet_hash) + else: + destination = Transport.destinations_map.get(packet.destination_hash) if destination and destination.type == packet.destination_type: packet.destination = destination if destination.receive(packet): @@ -2639,10 +2628,8 @@ class Transport: if peer_identity.validate(signature, signed_data) and not link_entry[IDX_LT_VALIDATED]: RNS.log(f"Re-balancing path to {RNS.prettyhexrep(link_destination)} from link-request proof ({link_entry[IDX_LT_REM_HOPS]}->{packet.hops})", REBALANCE_LOGLEVEL) if RNS.sl(REBALANCE_LOGLEVEL) else None link_entry[IDX_LT_REM_HOPS] = packet.hops - with Transport.path_table_lock: - if link_destination in Transport.path_table: - path_entry = Transport.path_table[link_destination] - path_entry[IDX_PT_HOPS] = packet.hops + path_entry = Transport.path_table.get(link_destination) + if path_entry: path_entry[IDX_PT_HOPS] = packet.hops elif not link_entry[IDX_LT_VALIDATED]: RNS.log(f"Aborting link request proof path re-balancing for {RNS.prettyhexrep(link_destination)} on link {RNS.prettyhexrep(packet.destination_hash)} due to invalid signature", REBALANCE_LOGLEVEL) if RNS.sl(REBALANCE_LOGLEVEL) else None else: pass @@ -2686,65 +2673,57 @@ class Transport: # Check if we can deliver it to a local pending link pending_link = None with Transport.pending_links_lock: - for link in Transport.pending_links: - if link.link_id == packet.destination_hash: - if packet.hops != link.expected_hops and link.status == RNS.Link.PENDING and Transport.ALLOW_LINK_PATH_REBALANCE: - RNS.log(f"Unbalanced link path ({packet.hops}/{link.expected_hops}) detected on link {link}, validating signature for re-balancing...", REBALANCE_LOGLEVEL) if RNS.sl(REBALANCE_LOGLEVEL) else None - try: - if len(packet.data) == RNS.Identity.SIGLENGTH//8+RNS.Link.ECPUBSIZE//2 or len(packet.data) == RNS.Identity.SIGLENGTH//8+RNS.Link.ECPUBSIZE//2+RNS.Link.LINK_MTU_SIZE: - packet_data = packet.data - signalling_bytes = b"" - confirmed_mtu = None - mode = RNS.Link.mode_from_lp_packet(packet) - if mode != link.mode: raise TypeError(f"Invalid link mode {mode} in link request proof") - if len(packet_data) == RNS.Identity.SIGLENGTH//8+RNS.Link.ECPUBSIZE//2+RNS.Link.LINK_MTU_SIZE: - confirmed_mtu = RNS.Link.mtu_from_lp_packet(packet) - signalling_bytes = RNS.Link.signalling_bytes(confirmed_mtu, mode) - packet_data = packet_data[:RNS.Identity.SIGLENGTH//8+RNS.Link.ECPUBSIZE//2] + link = Transport.pending_links_map.get(packet.destination_hash) + if link != None: + if packet.hops != link.expected_hops and link.status == RNS.Link.PENDING and Transport.ALLOW_LINK_PATH_REBALANCE: + RNS.log(f"Unbalanced link path ({packet.hops}/{link.expected_hops}) detected on link {link}, validating signature for re-balancing...", REBALANCE_LOGLEVEL) if RNS.sl(REBALANCE_LOGLEVEL) else None + try: + if len(packet.data) == RNS.Identity.SIGLENGTH//8+RNS.Link.ECPUBSIZE//2 or len(packet.data) == RNS.Identity.SIGLENGTH//8+RNS.Link.ECPUBSIZE//2+RNS.Link.LINK_MTU_SIZE: + packet_data = packet.data + signalling_bytes = b"" + confirmed_mtu = None + mode = RNS.Link.mode_from_lp_packet(packet) + if mode != link.mode: raise TypeError(f"Invalid link mode {mode} in link request proof") + if len(packet_data) == RNS.Identity.SIGLENGTH//8+RNS.Link.ECPUBSIZE//2+RNS.Link.LINK_MTU_SIZE: + confirmed_mtu = RNS.Link.mtu_from_lp_packet(packet) + signalling_bytes = RNS.Link.signalling_bytes(confirmed_mtu, mode) + packet_data = packet_data[:RNS.Identity.SIGLENGTH//8+RNS.Link.ECPUBSIZE//2] - peer_pub_bytes = packet_data[RNS.Identity.SIGLENGTH//8:RNS.Identity.SIGLENGTH//8+RNS.Link.ECPUBSIZE//2] - peer_sig_pub_bytes = link.destination.identity.get_public_key()[RNS.Link.ECPUBSIZE//2:RNS.Link.ECPUBSIZE] + peer_pub_bytes = packet_data[RNS.Identity.SIGLENGTH//8:RNS.Identity.SIGLENGTH//8+RNS.Link.ECPUBSIZE//2] + peer_sig_pub_bytes = link.destination.identity.get_public_key()[RNS.Link.ECPUBSIZE//2:RNS.Link.ECPUBSIZE] - signed_data = link.link_id+peer_pub_bytes+peer_sig_pub_bytes+signalling_bytes - signature = packet_data[:RNS.Identity.SIGLENGTH//8] + signed_data = link.link_id+peer_pub_bytes+peer_sig_pub_bytes+signalling_bytes + signature = packet_data[:RNS.Identity.SIGLENGTH//8] - if link.destination.identity.validate(signature, signed_data): - with Transport.path_table_lock: - if not link.rebalanced: - RNS.log(f"Re-balancing path to {RNS.prettyhexrep(link.destination.hash)} at link terminus ({link.expected_hops}->{packet.hops})", REBALANCE_LOGLEVEL) if RNS.sl(REBALANCE_LOGLEVEL) else None - link.rebalanced = time.time() - link.expected_hops = packet.hops - if link.destination.hash in Transport.path_table: - path_entry = Transport.path_table[link.destination.hash] - path_entry[IDX_PT_HOPS] = packet.hops - RNS.log(f"Path table re-balanced for {RNS.prettyhexrep(link.destination.hash)}", REBALANCE_LOGLEVEL) if RNS.sl(REBALANCE_LOGLEVEL) else None + if link.destination.identity.validate(signature, signed_data): + if not link.rebalanced: + RNS.log(f"Re-balancing path to {RNS.prettyhexrep(link.destination.hash)} at link terminus ({link.expected_hops}->{packet.hops})", REBALANCE_LOGLEVEL) if RNS.sl(REBALANCE_LOGLEVEL) else None + link.rebalanced = time.time() + link.expected_hops = packet.hops + path_entry = Transport.path_table.get(link.destination.hash) + if path_entry: + path_entry[IDX_PT_HOPS] = packet.hops + RNS.log(f"Path table re-balanced for {RNS.prettyhexrep(link.destination.hash)}", REBALANCE_LOGLEVEL) if RNS.sl(REBALANCE_LOGLEVEL) else None - else: RNS.log(f"Aborting path re-balancing at link terminus for {RNS.prettyhexrep(link.destination.hash)} on link {link} due to invalid signature", REBALANCE_LOGLEVEL) if RNS.sl(REBALANCE_LOGLEVEL) else None - except Exception as e: RNS.log("Error while validating link request proof for path re-balancing at link terminus. The contained exception was: "+str(e), REBALANCE_LOGLEVEL) if RNS.sl(REBALANCE_LOGLEVEL) else None + else: RNS.log(f"Aborting path re-balancing at link terminus for {RNS.prettyhexrep(link.destination.hash)} on link {link} due to invalid signature", REBALANCE_LOGLEVEL) if RNS.sl(REBALANCE_LOGLEVEL) else None + except Exception as e: RNS.log("Error while validating link request proof for path re-balancing at link terminus. The contained exception was: "+str(e), REBALANCE_LOGLEVEL) if RNS.sl(REBALANCE_LOGLEVEL) else None - if packet.hops == link.expected_hops: - # Add this packet to the filter hashlist if we - # have determined that it's actually destined - # for this system, and then validate the proof - Transport.add_packet_hash(packet.packet_hash) - pending_link = link - break + if packet.hops == link.expected_hops: + # Add this packet to the filter hashlist if we + # have determined that it's actually destined + # for this system, and then validate the proof + Transport.add_packet_hash(packet.packet_hash) + pending_link = link if pending_link: pending_link.validate_proof(packet) elif packet.context == RNS.Packet.RESOURCE_PRF: - with Transport.active_links_lock: - for link in Transport.active_links: - if link.link_id == packet.destination_hash: - link.receive(packet) - break + link = Transport.active_links_map.get(packet.destination_hash) + if link != None: link.receive(packet) else: if packet.destination_type == RNS.Destination.LINK: - with Transport.active_links_lock: - for link in Transport.active_links: - if link.link_id == packet.destination_hash: - packet.link = link - break + link = Transport.active_links_map.get(packet.destination_hash) + if link != None: packet.link = link if len(packet.data) == RNS.PacketReceipt.EXPL_LENGTH: proof_hash = packet.data[:RNS.Identity.HASHLENGTH//8] else: proof_hash = None @@ -2947,9 +2926,13 @@ class Transport: def register_link(link): RNS.log("Registering link "+str(link), RNS.LOG_EXTREME) if RNS.sl(RNS.LOG_EXTREME) else None if link.initiator: - with Transport.pending_links_lock: Transport.pending_links.append(link) + with Transport.pending_links_lock: + Transport.pending_links.append(link) + Transport.pending_links_map[link.link_id] = link else: - with Transport.active_links_lock: Transport.active_links.append(link) + with Transport.active_links_lock: + Transport.active_links.append(link) + Transport.active_links_map[link.link_id] = link @staticmethod def activate_link(link): @@ -2958,10 +2941,14 @@ class Transport: if link in Transport.pending_links: if link.status != RNS.Link.ACTIVE: raise IOError("Invalid link state for link activation: "+str(link.status)) Transport.pending_links.remove(link) - with Transport.active_links_lock: Transport.active_links.append(link) + Transport.pending_links_map.pop(link.link_id, None) + with Transport.active_links_lock: + Transport.active_links.append(link) + Transport.active_links_map[link.link_id] = link + link.status = RNS.Link.ACTIVE - else: - RNS.log("Attempted to activate a link that was not in the pending table", RNS.LOG_ERROR) + + else: RNS.log("Attempted to activate a link that was not in the pending table", RNS.LOG_ERROR) @staticmethod def register_announce_handler(handler): diff --git a/RNS/__init__.py b/RNS/__init__.py index dff1471a..1c63b3b8 100755 --- a/RNS/__init__.py +++ b/RNS/__init__.py @@ -588,18 +588,18 @@ class Profiler: name = tag["name"] stats_all = tag["stats_all"]; stats_1m = tag["stats_1m"]; stats_5m = tag["stats_5m"]; stats_30m = tag["stats_30m"]; stats_60m = tag["stats_60m"] results_str = f" {ind}{name}\n" - results_str += f" {ind} Samples : {stats_all["count"]} from {stats_all["threads"]} thread{'s' if stats_all["threads"] > 1 else ''}\n" + results_str += f" {ind} Samples : {stats_all['count']} from {stats_all['threads']} thread{'s' if stats_all['threads'] > 1 else ''}\n" if stats_all != None: results_str += f" {ind} {'Mean':^15} | {'Median':^15} | {'Min':^15} | {'Max':^15} | {'St. dev':^15} | {'Total':^15}\n" - results_str += f" {ind} Stats : ({pst(stats_all["mean"]):^15} | {pst(stats_all["median"]):^15} | {pst(stats_all["min"]):^15} | {pst(stats_all["max"]):^15} | {pst(stats_all["stdev"]):^15} | {pst(stats_all["sum"]):^15})\n" + results_str += f" {ind} Stats : ({pst(stats_all['mean']):^15} | {pst(stats_all['median']):^15} | {pst(stats_all['min']):^15} | {pst(stats_all['max']):^15} | {pst(stats_all['stdev']):^15} | {pst(stats_all['sum']):^15})\n" if stats_1m != None: - results_str += f" {ind} 0-1m : ({pst(stats_1m["mean"]):^15} | {pst(stats_1m["median"]):^15} | {pst(stats_1m["min"]):^15} | {pst(stats_1m["max"]):^15} | {pst(stats_1m["stdev"]):^15} | {pst(stats_1m["sum"]):^15})\n" + results_str += f" {ind} 0-1m : ({pst(stats_1m['mean']):^15} | {pst(stats_1m['median']):^15} | {pst(stats_1m['min']):^15} | {pst(stats_1m['max']):^15} | {pst(stats_1m['stdev']):^15} | {pst(stats_1m['sum']):^15})\n" if stats_5m != None: - results_str += f" {ind} 1-5m : ({pst(stats_5m["mean"]):^15} | {pst(stats_5m["median"]):^15} | {pst(stats_5m["min"]):^15} | {pst(stats_5m["max"]):^15} | {pst(stats_5m["stdev"]):^15} | {pst(stats_5m["sum"]):^15})\n" + results_str += f" {ind} 1-5m : ({pst(stats_5m['mean']):^15} | {pst(stats_5m['median']):^15} | {pst(stats_5m['min']):^15} | {pst(stats_5m['max']):^15} | {pst(stats_5m['stdev']):^15} | {pst(stats_5m['sum']):^15})\n" if stats_30m != None: - results_str += f" {ind} 5-30m : ({pst(stats_30m["mean"]):^15} | {pst(stats_30m["median"]):^15} | {pst(stats_30m["min"]):^15} | {pst(stats_30m["max"]):^15} | {pst(stats_30m["stdev"]):^15} | {pst(stats_30m["sum"]):^15})\n" + results_str += f" {ind} 5-30m : ({pst(stats_30m['mean']):^15} | {pst(stats_30m['median']):^15} | {pst(stats_30m['min']):^15} | {pst(stats_30m['max']):^15} | {pst(stats_30m['stdev']):^15} | {pst(stats_30m['sum']):^15})\n" if stats_60m != None: - results_str += f" {ind} 30-60m : ({pst(stats_60m["mean"]):^15} | {pst(stats_60m["median"]):^15} | {pst(stats_60m["min"]):^15} | {pst(stats_60m["max"]):^15} | {pst(stats_60m["stdev"]):^15} | {pst(stats_60m["sum"]):^15})\n" + results_str += f" {ind} 30-60m : ({pst(stats_60m['mean']):^15} | {pst(stats_60m['median']):^15} | {pst(stats_60m['min']):^15} | {pst(stats_60m['max']):^15} | {pst(stats_60m['stdev']):^15} | {pst(stats_60m['sum']):^15})\n" return results_str results_str = "" diff --git a/tests/throughput.py b/tests/throughput.py index 7112eaf0..2180edaa 100644 --- a/tests/throughput.py +++ b/tests/throughput.py @@ -608,7 +608,7 @@ def _fmt_pps(value): def print_environment_header(): - tree_fastpath = "present" if hasattr(RNS.Transport, "_fastpath") else "absent" + tree_fastpath = "present" if hasattr(RNS.Transport, "USE_FP_CACHE") else "absent" print("=" * 40) print("Reticulum Transport Throughput Benchmark") print("=" * 40) @@ -616,7 +616,7 @@ def print_environment_header(): print(f" Mode : {'compiled' if RNS.compiled else 'interpreted'}") print(f" crypto : {RNS.Cryptography.backend()}") print(f" transport : enabled") - print(f" fast path : {tree_fastpath} (informational)") + print(f" fast path : {tree_fastpath}") print(f" python : {platform.python_version()}") print(f" platform : {platform.platform()}") print(f" machine : {platform.machine()}, cpus: {os.cpu_count()}") @@ -651,6 +651,7 @@ def print_scenario_table(scenario, results): r = results[mode] tp = r['pps']*scenario.frame_size*8 + if mode == "inline": mode = "direct" print(f"{mode:<8} : {RNS.prettyspeed(tp)}") print() @@ -661,7 +662,7 @@ def print_pps_matrix(rows): if not rows: return print("-" * 72) - print("Transport Core Throughput - PPS matrix (median of runs)") + print("Transport Throughput - PPS matrix (median of runs)") print(f" {'scenario':<22}{'direct':>14}{'drainer':>14}") print(" " + "-" * 70) for name, inline_pps, drainer_pps, fsize in rows: @@ -839,7 +840,7 @@ def _usage(): "\nUsage: python3 tests/transport_throughput.py [options]\n" "\n" "Options:\n" - " -s, --scenario NAME Run only the named scenario (repeatable)\n" + " -s, --scenario NAME Run only the named scenario\n" " --mode MODE Measurement mode: inline, drainer or both\n" " (default: both)\n" " --runs N Measurement runs per scenario/mode, median\n" @@ -881,3 +882,61 @@ if __name__ == "__main__": unittest.main(argv=[sys.argv[0]] + rest, verbosity=2) +# Pre: +# +# Transport Throughput - PPS matrix (median of runs) +# scenario direct drainer +# ---------------------------------------------------------------------- +# transit_single_135 208,550 184,075 225.23 Mbps / 198.80 Mbps +# transit_single_475 195,652 130,808 743.48 Mbps / 497.07 Mbps +# transit_single_1024 169,828 111,446 1.39 Gbps / 912.97 Mbps +# transit_single_16384 49,618 74,101 6.50 Gbps / 9.71 Gbps +# transit_single_final 204,604 112,925 220.97 Mbps / 121.96 Mbps +# transit_link_135 272,813 234,430 294.64 Mbps / 253.18 Mbps +# transit_link_475 260,003 227,895 988.01 Mbps / 866.00 Mbps +# transit_link_1024 232,763 145,872 1.91 Gbps / 1.19 Gbps +# transit_link_16384 84,028 73,313 11.01 Gbps / 9.61 Gbps +# +# terminus_single 29,813 25,663 32.20 Mbps / 27.72 Mbps +# announce_ingress 7,561 7,242 10.10 Mbps / 9.68 Mbps +# outbound_path 696,106 - 751.79 Mbps / - +# +# No Fastpath: +# +# Transport Throughput - PPS matrix (median of runs) +# scenario direct drainer +# ---------------------------------------------------------------------- +# transit_single_135 247,667 162,090 267.48 Mbps / 175.06 Mbps +# transit_single_475 233,514 159,717 887.35 Mbps / 606.92 Mbps +# transit_single_1024 211,443 175,994 1.73 Gbps / 1.44 Gbps +# transit_single_16384 78,963 72,532 10.35 Gbps / 9.51 Gbps +# transit_single_final 240,855 112,702 260.12 Mbps / 121.72 Mbps +# transit_link_135 266,614 220,571 287.94 Mbps / 238.22 Mbps +# transit_link_475 256,347 152,410 974.12 Mbps / 579.16 Mbps +# transit_link_1024 228,080 194,444 1.87 Gbps / 1.59 Gbps +# transit_link_16384 82,149 73,720 10.77 Gbps / 9.66 Gbps +# +# terminus_single 29,764 28,308 32.15 Mbps / 30.57 Mbps +# announce_ingress 7,394 7,150 9.88 Mbps / 9.55 Mbps +# outbound_path 872,747 - 942.57 Mbps / - +# ---------------------------------------------------------------------- +# +# +# Fastpath: +# +# Transport Throughput - PPS matrix (median of runs) +# scenario direct drainer +# ---------------------------------------------------------------------- +# transit_single_135 407,561 411,426 440.17 Mbps / 444.34 Mbps +# transit_single_475 378,867 377,964 1.44 Gbps / 1.44 Gbps +# transit_single_1024 320,878 325,910 2.63 Gbps / 2.67 Gbps +# transit_single_16384 94,265 94,357 12.36 Gbps / 12.37 Gbps +# transit_single_final 395,245 399,247 426.86 Mbps / 431.19 Mbps +# transit_link_135 453,216 451,863 489.47 Mbps / 488.01 Mbps +# transit_link_475 417,013 417,294 1.58 Gbps / 1.59 Gbps +# transit_link_1024 347,548 353,783 2.85 Gbps / 2.90 Gbps +# transit_link_16384 96,682 96,871 12.67 Gbps / 12.70 Gbps +# +# terminus_single 29,451 25,862 31.81 Mbps / 27.93 Mbps +# announce_ingress 7,495 6,996 10.01 Mbps / 9.35 Mbps +# outbound_path 840,738 - 908.00 Mbps / -