From d66c31b4e9c2d5406cdb20362abeebd57bae8931 Mon Sep 17 00:00:00 2001 From: Mark Qvist Date: Sat, 14 May 2022 22:14:38 +0200 Subject: [PATCH] Added announce rate information to rnpath utility, added exit codes and improved table lookup. --- RNS/Reticulum.py | 24 +++++++ RNS/Utilities/rnpath.py | 156 +++++++++++++++++++++++++++++++++++++--- 2 files changed, 170 insertions(+), 10 deletions(-) diff --git a/RNS/Reticulum.py b/RNS/Reticulum.py index 4114349..47054b0 100755 --- a/RNS/Reticulum.py +++ b/RNS/Reticulum.py @@ -883,6 +883,9 @@ class Reticulum: if path == "path_table": rpc_connection.send(self.get_path_table()) + if path == "rate_table": + rpc_connection.send(self.get_rate_table()) + if path == "next_hop_if_name": rpc_connection.send(self.get_next_hop_if_name(call["destination_hash"])) @@ -995,6 +998,27 @@ class Reticulum: return path_table + def get_rate_table(self): + if self.is_connected_to_shared_instance: + rpc_connection = multiprocessing.connection.Client(self.rpc_addr, authkey=self.rpc_key) + rpc_connection.send({"get": "rate_table"}) + response = rpc_connection.recv() + return response + + else: + rate_table = [] + for dst_hash in RNS.Transport.announce_rate_table: + entry = { + "hash": dst_hash, + "last": RNS.Transport.announce_rate_table[dst_hash]["last"], + "rate_violations": RNS.Transport.announce_rate_table[dst_hash]["rate_violations"], + "blocked_until": RNS.Transport.announce_rate_table[dst_hash]["blocked_until"], + "timestamps": RNS.Transport.announce_rate_table[dst_hash]["timestamps"], + } + rate_table.append(entry) + + return rate_table + def drop_path(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/Utilities/rnpath.py b/RNS/Utilities/rnpath.py index 427cc3f..fde9786 100644 --- a/RNS/Utilities/rnpath.py +++ b/RNS/Utilities/rnpath.py @@ -30,18 +30,102 @@ import argparse from RNS._version import __version__ -def program_setup(configdir, table, drop, destination_hexhash, verbosity, timeout, drop_queues): +def program_setup(configdir, table, rates, drop, destination_hexhash, verbosity, timeout, drop_queues): if table: + destination_hash = None + if destination_hexhash != None: + try: + dest_len = (RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2 + if len(destination_hexhash) != dest_len: + raise ValueError("Destination length is invalid, must be {hex} hexadecimal characters ({byte} bytes).".format(hex=dest_len, byte=dest_len//2)) + try: + destination_hash = bytes.fromhex(destination_hexhash) + except Exception as e: + raise ValueError("Invalid destination entered. Check your input.") + except Exception as e: + print(str(e)) + sys.exit(1) + reticulum = RNS.Reticulum(configdir = configdir, loglevel = 3+verbosity) table = sorted(reticulum.get_path_table(), key=lambda e: (e["interface"], e["hops"]) ) + displayed = 0 for path in table: - exp_str = RNS.timestamp_str(path["expires"]) - if path["hops"] == 1: - m_str = " " - else: - m_str = "s" - print(RNS.prettyhexrep(path["hash"])+" is "+str(path["hops"])+" hop"+m_str+" away via "+RNS.prettyhexrep(path["via"])+" on "+path["interface"]+" expires "+RNS.timestamp_str(path["expires"])) + if destination_hash == None or destination_hash == path["hash"]: + displayed += 1 + exp_str = RNS.timestamp_str(path["expires"]) + if path["hops"] == 1: + m_str = " " + else: + m_str = "s" + print(RNS.prettyhexrep(path["hash"])+" is "+str(path["hops"])+" hop"+m_str+" away via "+RNS.prettyhexrep(path["via"])+" on "+path["interface"]+" expires "+RNS.timestamp_str(path["expires"])) + + if destination_hash != None and displayed == 0: + print("No path known") + sys.exit(1) + + elif rates: + destination_hash = None + if destination_hexhash != None: + try: + dest_len = (RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2 + if len(destination_hexhash) != dest_len: + raise ValueError("Destination length is invalid, must be {hex} hexadecimal characters ({byte} bytes).".format(hex=dest_len, byte=dest_len//2)) + try: + destination_hash = bytes.fromhex(destination_hexhash) + except Exception as e: + raise ValueError("Invalid destination entered. Check your input.") + except Exception as e: + print(str(e)) + sys.exit(1) + + reticulum = RNS.Reticulum(configdir = configdir, loglevel = 3+verbosity) + table = sorted(reticulum.get_rate_table(), key=lambda e: e["last"] ) + + if len(table) == 0: + print("No information available") + + else: + displayed = 0 + for entry in table: + if destination_hash == None or destination_hash == entry["hash"]: + displayed += 1 + try: + last_str = pretty_date(int(entry["last"])) + start_ts = entry["timestamps"][0] + span = max(time.time() - start_ts, 3600.0) + span_hours = span/3600.0 + span_str = pretty_date(int(entry["timestamps"][0])) + hour_rate = round(len(entry["timestamps"])/span_hours, 3) + if hour_rate-int(hour_rate) == 0: + hour_rate = int(hour_rate) + + if entry["rate_violations"] > 0: + if entry["rate_violations"] == 1: + s_str = "" + else: + s_str = "s" + + rv_str = ", "+str(entry["rate_violations"])+" active rate violation"+s_str + else: + rv_str = "" + + if entry["blocked_until"] > time.time(): + bli = time.time()-(int(entry["blocked_until"])-time.time()) + bl_str = ", new announces allowed in "+pretty_date(int(bli)) + else: + bl_str = "" + + + print(RNS.prettyhexrep(entry["hash"])+" last heard "+last_str+" ago, "+str(hour_rate)+" announces/hour in the last "+span_str+rv_str+bl_str) + + except Exception as e: + print("Error while processing entry for "+RNS.prettyhexrep(entry["hash"])) + print(str(e)) + + if destination_hash != None and displayed == 0: + print("No information available") + sys.exit(1) elif drop_queues: reticulum = RNS.Reticulum(configdir = configdir, loglevel = 3+verbosity) @@ -59,7 +143,8 @@ def program_setup(configdir, table, drop, destination_hexhash, verbosity, timeou raise ValueError("Invalid destination entered. Check your input.") except Exception as e: print(str(e)) - exit() + sys.exit(1) + reticulum = RNS.Reticulum(configdir = configdir, loglevel = 3+verbosity) @@ -67,6 +152,8 @@ def program_setup(configdir, table, drop, destination_hexhash, verbosity, timeou print("Dropped path to "+RNS.prettyhexrep(destination_hash)) else: print("Unable to drop path to "+RNS.prettyhexrep(destination_hash)+". Does it exist?") + sys.exit(1) + else: try: @@ -79,7 +166,8 @@ def program_setup(configdir, table, drop, destination_hexhash, verbosity, timeou raise ValueError("Invalid destination entered. Check your input.") except Exception as e: print(str(e)) - exit() + sys.exit(1) + reticulum = RNS.Reticulum(configdir = configdir, loglevel = 3+verbosity) @@ -110,6 +198,8 @@ def program_setup(configdir, table, drop, destination_hexhash, verbosity, timeou print("\rPath found, destination "+RNS.prettyhexrep(destination_hash)+" is "+str(hops)+" hop"+ms+" away via "+next_hop+" on "+next_hop_interface) else: print("\r \rPath not found") + sys.exit(1) + def main(): @@ -137,6 +227,14 @@ def main(): default=False ) + parser.add_argument( + "-r", + "--rates", + action="store_true", + help="show announce rate info", + default=False + ) + parser.add_argument( "-d", "--drop", @@ -179,7 +277,7 @@ def main(): else: configarg = None - if not args.drop_announces and not args.table and not args.destination: + if not args.drop_announces and not args.table and not args.rates and not args.destination: print("") parser.print_help() print("") @@ -187,16 +285,54 @@ def main(): program_setup( configdir = configarg, table = args.table, + rates = args.rates, drop = args.drop, destination_hexhash = args.destination, verbosity = args.verbose, timeout = args.w, drop_queues = args.drop_announces, ) + sys.exit(0) except KeyboardInterrupt: print("") exit() +def pretty_date(time=False): + from datetime import datetime + now = datetime.now() + if type(time) is int: + diff = now - datetime.fromtimestamp(time) + elif isinstance(time,datetime): + diff = now - time + elif not time: + diff = now - now + second_diff = diff.seconds + day_diff = diff.days + if day_diff < 0: + return '' + if day_diff == 0: + if second_diff < 10: + return str(second_diff) + " seconds" + if second_diff < 60: + return str(second_diff) + " seconds" + if second_diff < 120: + return "1 minute" + if second_diff < 3600: + return str(int(second_diff / 60)) + " minutes" + if second_diff < 7200: + return "an hour ago" + if second_diff < 86400: + return str(int(second_diff / 3600)) + " hours" + if day_diff == 1: + return "1 day" + if day_diff < 7: + return str(day_diff) + " days" + if day_diff < 31: + return str(int(day_diff / 7)) + " weeks" + if day_diff < 365: + return str(int(day_diff / 30)) + " months" + return str(int(day_diff / 365)) + " years" + if __name__ == "__main__": main() \ No newline at end of file