From d3b8c1c829230e324f5643c391f7c1507680bff8 Mon Sep 17 00:00:00 2001 From: Mark Qvist Date: Thu, 29 Aug 2024 13:19:39 +0200 Subject: [PATCH] Added path and rate tables to remote management --- RNS/Transport.py | 39 +++++++++- RNS/Utilities/rnpath.py | 166 ++++++++++++++++++++++++++++++++++++---- 2 files changed, 189 insertions(+), 16 deletions(-) diff --git a/RNS/Transport.py b/RNS/Transport.py index ff0d56a..777619a 100755 --- a/RNS/Transport.py +++ b/RNS/Transport.py @@ -183,6 +183,7 @@ class Transport: if RNS.Reticulum.remote_management_enabled() and not Transport.owner.is_connected_to_shared_instance: Transport.remote_management_destination = RNS.Destination(Transport.identity, RNS.Destination.IN, RNS.Destination.SINGLE, Transport.APP_NAME, "remote", "management") Transport.remote_management_destination.register_request_handler("/status", response_generator = Transport.remote_status_handler, allow = RNS.Destination.ALLOW_LIST, allowed_list=Transport.remote_management_allowed) + Transport.remote_management_destination.register_request_handler("/path", response_generator = Transport.remote_path_handler, allow = RNS.Destination.ALLOW_LIST, allowed_list=Transport.remote_management_allowed) Transport.control_destinations.append(Transport.remote_management_destination) Transport.control_hashes.append(Transport.remote_management_destination.hash) RNS.log("Enabled remote management on "+str(Transport.remote_management_destination), RNS.LOG_NOTICE) @@ -2269,7 +2270,43 @@ class Transport: return response except Exception as e: - RNS.log("An error occurred while processing remote status request from "+RNS.prettyhexrep(remote_identity), RNS.LOG_ERROR) + RNS.log("An error occurred while processing remote status request from "+str(remote_identity), RNS.LOG_ERROR) + RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR) + + return None + + @staticmethod + def remote_path_handler(path, data, request_id, link_id, remote_identity, requested_at): + if remote_identity != None: + response = None + try: + if isinstance(data, list) and len(data) > 0: + command = data[0] + destination_hash = None + max_hops = None + if len(data) > 1: + destination_hash = data[1] + if len(data) > 2: + max_hops = data[2] + + if command == "table": + table = Transport.owner.get_path_table(max_hops=max_hops) + response = [] + for path in table: + if destination_hash == None or destination_hash == path["hash"]: + response.append(path) + + elif command == "rates": + table = Transport.owner.get_rate_table() + response = [] + for path in table: + if destination_hash == None or destination_hash == path["hash"]: + response.append(path) + + return response + + except Exception as e: + RNS.log("An error occurred while processing remote status request from "+str(remote_identity), RNS.LOG_ERROR) RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR) return None diff --git a/RNS/Utilities/rnpath.py b/RNS/Utilities/rnpath.py index 2441bb0..e6e0ff9 100644 --- a/RNS/Utilities/rnpath.py +++ b/RNS/Utilities/rnpath.py @@ -23,14 +23,95 @@ # SOFTWARE. import RNS +import os import sys import time import argparse from RNS._version import __version__ +remote_link = None +def connect_remote(destination_hash, auth_identity, timeout, no_output = False): + global remote_link, reticulum + if not RNS.Transport.has_path(destination_hash): + if not no_output: + print("Path to "+RNS.prettyhexrep(destination_hash)+" requested", end=" ") + sys.stdout.flush() + RNS.Transport.request_path(destination_hash) + pr_time = time.time() + while not RNS.Transport.has_path(destination_hash): + time.sleep(0.1) + if time.time() - pr_time > timeout: + if not no_output: + print("\r \r", end="") + print("Path request timed out") + exit(12) + + remote_identity = RNS.Identity.recall(destination_hash) + + def remote_link_closed(link): + if link.teardown_reason == RNS.Link.TIMEOUT: + if not no_output: + print("\r \r", end="") + print("The link timed out, exiting now") + elif link.teardown_reason == RNS.Link.DESTINATION_CLOSED: + if not no_output: + print("\r \r", end="") + print("The link was closed by the server, exiting now") + else: + if not no_output: + print("\r \r", end="") + print("Link closed unexpectedly, exiting now") + exit(10) + + def remote_link_established(link): + global remote_link + link.identify(auth_identity) + remote_link = link + + if not no_output: + print("\r \r", end="") + print("Establishing link with remote transport instance...", end=" ") + sys.stdout.flush() + + remote_destination = RNS.Destination(remote_identity, RNS.Destination.OUT, RNS.Destination.SINGLE, "rnstransport", "remote", "management") + link = RNS.Link(remote_destination) + link.set_link_established_callback(remote_link_established) + link.set_link_closed_callback(remote_link_closed) + +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, no_output=False): + global remote_link, reticulum + reticulum = RNS.Reticulum(configdir = configdir, loglevel = 3+verbosity) + if remote: + try: + dest_len = (RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2 + if len(remote) != dest_len: + raise ValueError("Destination length is invalid, must be {hex} hexadecimal characters ({byte} bytes).".format(hex=dest_len, byte=dest_len//2)) + try: + identity_hash = bytes.fromhex(remote) + remote_hash = RNS.Destination.hash_from_name_and_identity("rnstransport.remote.management", identity_hash) + except Exception as e: + raise ValueError("Invalid destination entered. Check your input.") + + identity = RNS.Identity.from_file(os.path.expanduser(management_identity)) + if identity == None: + raise ValueError("Could not load management identity from "+str(management_identity)) + + try: + connect_remote(remote_hash, identity, remote_timeout, no_output) + except Exception as e: + raise e + + except Exception as e: + print(str(e)) + exit(20) + + while remote_link == None: + time.sleep(0.1) + -def program_setup(configdir, table, rates, drop, destination_hexhash, verbosity, timeout, drop_queues, drop_via, max_hops): if table: destination_hash = None if destination_hexhash != None: @@ -46,8 +127,25 @@ def program_setup(configdir, table, rates, drop, destination_hexhash, verbosity, print(str(e)) sys.exit(1) - reticulum = RNS.Reticulum(configdir = configdir, loglevel = 3+verbosity) - table = sorted(reticulum.get_path_table(max_hops=max_hops), key=lambda e: (e["interface"], e["hops"]) ) + if not remote_link: + table = sorted(reticulum.get_path_table(max_hops=max_hops), key=lambda e: (e["interface"], e["hops"]) ) + else: + if not no_output: + print("\r \r", end="") + print("Sending request...", end=" ") + sys.stdout.flush() + receipt = remote_link.request("/path", data = ["table", destination_hash, max_hops]) + while not receipt.concluded(): + time.sleep(0.1) + response = receipt.get_response() + if response: + table = response + print("\r \r", end="") + else: + if not no_output: + print("\r \r", end="") + print("The remote request failed. Likely authentication failure.") + exit(10) displayed = 0 for path in table: @@ -79,9 +177,27 @@ def program_setup(configdir, table, rates, drop, destination_hexhash, verbosity, 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 not remote_link: + table = reticulum.get_rate_table() + else: + if not no_output: + print("\r \r", end="") + print("Sending request...", end=" ") + sys.stdout.flush() + receipt = remote_link.request("/path", data = ["rates", destination_hash]) + while not receipt.concluded(): + time.sleep(0.1) + response = receipt.get_response() + if response: + table = response + print("\r \r", end="") + else: + if not no_output: + print("\r \r", end="") + print("The remote request failed. Likely authentication failure.") + exit(10) + table = sorted(table, key=lambda e: e["last"]) if len(table) == 0: print("No information available") @@ -128,7 +244,6 @@ def program_setup(configdir, table, rates, drop, destination_hexhash, verbosity, sys.exit(1) elif drop_queues: - reticulum = RNS.Reticulum(configdir = configdir, loglevel = 3+verbosity) RNS.log("Dropping announce queues on all interfaces...") reticulum.drop_announce_queues() @@ -145,9 +260,6 @@ def program_setup(configdir, table, rates, drop, destination_hexhash, verbosity, print(str(e)) sys.exit(1) - - reticulum = RNS.Reticulum(configdir = configdir, loglevel = 3+verbosity) - if reticulum.drop_path(destination_hash): print("Dropped path to "+RNS.prettyhexrep(destination_hash)) else: @@ -168,9 +280,6 @@ def program_setup(configdir, table, rates, drop, destination_hexhash, verbosity, print(str(e)) sys.exit(1) - - reticulum = RNS.Reticulum(configdir = configdir, loglevel = 3+verbosity) - if reticulum.drop_all_via(destination_hash): print("Dropped all paths via "+RNS.prettyhexrep(destination_hash)) else: @@ -191,9 +300,6 @@ def program_setup(configdir, table, rates, drop, destination_hexhash, verbosity, print(str(e)) sys.exit(1) - - reticulum = RNS.Reticulum(configdir = configdir, loglevel = 3+verbosity) - if not RNS.Transport.has_path(destination_hash): RNS.Transport.request_path(destination_hash) print("Path to "+RNS.prettyhexrep(destination_hash)+" requested ", end=" ") @@ -305,6 +411,33 @@ def main(): default=RNS.Transport.PATH_REQUEST_TIMEOUT ) + parser.add_argument( + "-R", + action="store", + metavar="hash", + help="transport identity hash of remote instance to manage", + default=None, + type=str + ) + + parser.add_argument( + "-i", + action="store", + metavar="path", + help="path to identity used for remote management", + default=None, + type=str + ) + + parser.add_argument( + "-W", + action="store", + metavar="seconds", + type=float, + help="timeout before giving up on remote queries", + default=RNS.Transport.PATH_REQUEST_TIMEOUT + ) + parser.add_argument( "destination", nargs="?", @@ -338,6 +471,9 @@ def main(): 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, ) sys.exit(0)