From e1327842b18ae1b863fa3729a48852bbd88c290e Mon Sep 17 00:00:00 2001 From: Mark Qvist Date: Thu, 1 Jan 2026 18:07:19 +0100 Subject: [PATCH] Added ability to specify duration and reason to blackhole entries --- RNS/Reticulum.py | 10 ++++++---- RNS/Transport.py | 14 ++++++++------ RNS/Utilities/rnpath.py | 28 ++++++++++++++++++++-------- 3 files changed, 34 insertions(+), 18 deletions(-) diff --git a/RNS/Reticulum.py b/RNS/Reticulum.py index 0cc41c7..de73b49 100755 --- a/RNS/Reticulum.py +++ b/RNS/Reticulum.py @@ -1008,7 +1008,9 @@ class Reticulum: if "blackhole_identity" in call: identity_hash = call["blackhole_identity"] - rpc_connection.send(self.blackhole_identity(identity_hash)) + until = call["until"] + reason = call["reason"] + rpc_connection.send(self.blackhole_identity(identity_hash, until=until, reason=reason)) if "unblackhole_identity" in call: identity_hash = call["unblackhole_identity"] @@ -1388,16 +1390,16 @@ class Reticulum: else: return RNS.Transport.blackholed_identities - def blackhole_identity(self, identity_hash): + def blackhole_identity(self, identity_hash, until=None, reason=None): if len(identity_hash) != RNS.Reticulum.TRUNCATED_HASHLENGTH//8: return False else: if self.is_connected_to_shared_instance: rpc_connection = self.get_rpc_client() - rpc_connection.send({"blackhole_identity": identity_hash}) + rpc_connection.send({"blackhole_identity": identity_hash, "until": until, "reason": reason}) response = rpc_connection.recv() return response - else: return RNS.Transport.blackhole_identity(identity_hash) + else: return RNS.Transport.blackhole_identity(identity_hash, until=until, reason=reason) def unblackhole_identity(self, identity_hash): if len(identity_hash) != RNS.Reticulum.TRUNCATED_HASHLENGTH//8: return False diff --git a/RNS/Transport.py b/RNS/Transport.py index 9646e33..050c69a 100755 --- a/RNS/Transport.py +++ b/RNS/Transport.py @@ -3052,10 +3052,10 @@ class Transport: Transport.persist_data() @staticmethod - def blackhole_identity(identity_hash): + def blackhole_identity(identity_hash, until=None, reason=None): try: if not identity_hash in Transport.blackholed_identities: - entry = {"source": Transport.identity.hash, "until": None} + entry = {"source": Transport.identity.hash, "until": until, "reason": reason} Transport.blackholed_identities[identity_hash] = entry Transport.persist_blackhole() Transport.remove_blackholed_paths() @@ -3105,10 +3105,12 @@ class Transport: if Transport.blackholed_identities[identity_hash]["source"] == Transport.identity.hash: continue - until = source_list[identity_hash]["until"] - entry = {"source": source_identity_hash, "until": until} - if until == None or now < until: - Transport.blackholed_identities[identity_hash] = entry + se = source_list[identity_hash] + until = se["until"] if "until" in se else None + reason = se["reason"] if "reason" in se else None + entry = {"source": source_identity_hash, "until": until, "reason": reason} + + if until == None or now < until: Transport.blackholed_identities[identity_hash] = entry source_count += 1 diff --git a/RNS/Utilities/rnpath.py b/RNS/Utilities/rnpath.py index 24b8d4c..b4d42e1 100644 --- a/RNS/Utilities/rnpath.py +++ b/RNS/Utilities/rnpath.py @@ -97,7 +97,8 @@ def parse_hash(input_str): def program_setup(configdir, table, rates, drop, destination_hexhash, verbosity, timeout, drop_queues, drop_via, max_hops, remote=None, management_identity=None, remote_timeout=RNS.Transport.PATH_REQUEST_TIMEOUT, - blackholed=False, blackhole=False, unblackhole=False, no_output=False, json=False): + blackholed=False, blackhole=False, unblackhole=False, blackhole_duration=None, blackhole_reason=None, + no_output=False, json=False): global remote_link, reticulum reticulum = RNS.Reticulum(configdir = configdir, loglevel = 3+verbosity) @@ -130,14 +131,22 @@ def program_setup(configdir, table, rates, drop, destination_hexhash, verbosity, exit(255) try: + rmlen = 64 + def trunc(input_str): + if len(input_str) <= rmlen: return input_str + else: return f"{input_str[:rmlen-1]}…" + blackholed = reticulum.get_blackholed_identities() now = time.time() for identity_hash in blackholed: - until = blackholed[identity_hash]["until"] - if until == None: until_str = "indefinitely" - else: until_str = f" for {RNS.prettytime(until-now)}" - print(f"{RNS.prettyhexrep(identity_hash)} blackholed {until_str}") - + if destination_hexhash and not destination_hexhash in RNS.prettyhexrep(identity_hash): continue + until = blackholed[identity_hash]["until"] + reason = blackholed[identity_hash]["reason"] + source = blackholed[identity_hash]["source"] + until_str = f"for {RNS.prettytime(until-now)}" if until else "indefinitely" + reason_str = f" ({trunc(reason)})" if reason else "" + by_str = f" by {RNS.prettyhexrep(source)}" if source != RNS.Transport.identity.hash else "" + print(f"{RNS.prettyhexrep(identity_hash)} blackholed {until_str}{reason_str}{by_str}") except Exception as e: print(f"Could not get blackholed identities from RNS instance: {e}") @@ -152,7 +161,8 @@ def program_setup(configdir, table, rates, drop, destination_hexhash, verbosity, try: identity_hash = parse_hash(destination_hexhash) - result = reticulum.blackhole_identity(identity_hash) + until = time.time()+blackhole_duration*60*60 if blackhole_duration else None + result = reticulum.blackhole_identity(identity_hash, until=until, reason=blackhole_reason) if result == True: print(f"Blackholed identity {destination_hexhash}") elif result == None: print(f"Identity {destination_hexhash} already blackholed") else: print(f"Could not blackhole identity {destination_hexhash}") @@ -435,6 +445,8 @@ def main(): parser.add_argument("-b", "--blackholed", action="store_true", help="list blackholed identities", default=False) parser.add_argument("-B", "--blackhole", action="store_true", help="blackhole identity", default=False) parser.add_argument("-U", "--unblackhole", action="store_true", help="unblackhole identity", default=False) + parser.add_argument( "--duration", action="store", type=int, help="duration of blackhole enforcement in hours", default=None) + parser.add_argument( "--reason", action="store", type=str, help="reason for blackholing identity", default=None) parser.add_argument("-j", "--json", action="store_true", help="output in JSON format", default=False) parser.add_argument("destination", nargs="?", default=None, help="hexadecimal hash of the destination", type=str) parser.add_argument('-v', '--verbose', action='count', default=0) @@ -452,7 +464,7 @@ 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, drop_via = args.drop_via, max_hops = args.max, remote=args.R, management_identity=args.i, remote_timeout=args.W, blackholed=args.blackholed, blackhole=args.blackhole, - unblackhole=args.unblackhole, json=args.json) + unblackhole=args.unblackhole, blackhole_duration=args.duration, blackhole_reason=args.reason, json=args.json) sys.exit(0)