mirror of
https://github.com/markqvist/Reticulum.git
synced 2026-10-26 19:01:53 -04:00
Added live interface attach/detach/reload
This commit is contained in:
parent
84709ccf09
commit
d23261c8d9
4 changed files with 170 additions and 22 deletions
137
RNS/Reticulum.py
137
RNS/Reticulum.py
|
|
@ -1,6 +1,6 @@
|
|||
# Reticulum License
|
||||
#
|
||||
# Copyright (c) 2016-2025 Mark Qvist
|
||||
# Copyright (c) 2016-2026 Mark Qvist
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
|
|
@ -257,6 +257,7 @@ class Reticulum:
|
|||
Reticulum.__local_hops_delta = False
|
||||
Reticulum.__link_mtu_discovery = Reticulum.LINK_MTU_DISCOVERY
|
||||
Reticulum.__remote_management_enabled = False
|
||||
Reticulum.__interface_management_enabled = True
|
||||
Reticulum.__use_implicit_proof = True
|
||||
Reticulum.__allow_probes = False
|
||||
Reticulum.__discovery_enabled = False
|
||||
|
|
@ -548,6 +549,11 @@ class Reticulum:
|
|||
v = self.config["reticulum"].as_bool(option)
|
||||
if v == True: Reticulum.__remote_management_enabled = True
|
||||
|
||||
if option == "enable_interface_management":
|
||||
v = self.config["reticulum"].as_bool(option)
|
||||
if v == False: Reticulum.__interface_management_enabled = False
|
||||
else: Reticulum.__interface_management_enabled = True
|
||||
|
||||
if option == "remote_management_allowed":
|
||||
v = self.config["reticulum"].as_list(option)
|
||||
for hexhash in v:
|
||||
|
|
@ -757,7 +763,79 @@ class Reticulum:
|
|||
|
||||
RNS.log("System interfaces are ready", RNS.LOG_VERBOSE)
|
||||
|
||||
def _synthesize_interface(self, config, name, instance_init=False):
|
||||
def _attach_interface(self, name):
|
||||
if not Reticulum.__interface_management_enabled: return False
|
||||
try:
|
||||
interfaces = { iface.name: iface for iface in RNS.Transport.interfaces }
|
||||
if name in interfaces:
|
||||
RNS.log(f"Attempt to attach existing interface \"{name}\"", RNS.LOG_WARNING)
|
||||
return False
|
||||
|
||||
else:
|
||||
try: config = ConfigObj(self.configpath)
|
||||
except Exception as e:
|
||||
RNS.log(f"Could not parse the configuration at {self.configpath} during interface attach", RNS.LOG_ERROR)
|
||||
return None
|
||||
|
||||
if "interfaces" in config:
|
||||
if not name in config["interfaces"]:
|
||||
RNS.log(f"Cannot attach interface \"{name}\", no configuration entry exists", RNS.LOG_ERROR)
|
||||
return None
|
||||
|
||||
else:
|
||||
interface_config = config["interfaces"][name]
|
||||
self._synthesize_interface(interface_config, name, force_attach=True)
|
||||
RNS.log(f"Interface \"{name}\" was attached", RNS.LOG_NOTICE)
|
||||
return True
|
||||
|
||||
except Exception as e: RNS.log(f"Error while detaching interface \"{name}\": {e}", RNS.LOG_ERROR)
|
||||
return False
|
||||
|
||||
def _detach_interface(self, name):
|
||||
if not Reticulum.__interface_management_enabled: return False
|
||||
try:
|
||||
interfaces = { iface.name: iface for iface in RNS.Transport.interfaces }
|
||||
if not name in interfaces:
|
||||
RNS.log(f"Attempt to detach non-existing interface \"{name}\"", RNS.LOG_WARNING)
|
||||
return None
|
||||
|
||||
else:
|
||||
interface = interfaces[name]
|
||||
if isinstance(interface, I2PInterface.I2PInterface): return False
|
||||
if isinstance(interface, LocalInterface.LocalClientInterface): return False
|
||||
if isinstance(interface, LocalInterface.LocalServerInterface): return False
|
||||
if interface.spawned_interfaces:
|
||||
if type(interface.spawned_interfaces) == dict: spawned = list(interface.spawned_interfaces.values())
|
||||
elif type(interface.spawned_interfaces) == list: spawned = interface.spawned_interfaces.copy()
|
||||
for s in spawned:
|
||||
s.detach()
|
||||
s.teardown()
|
||||
RNS.Transport.remove_interface(s)
|
||||
|
||||
interface.detach()
|
||||
RNS.Transport.remove_interface(interface)
|
||||
RNS.log(f"Interface \"{name}\" was detached", RNS.LOG_NOTICE)
|
||||
return True
|
||||
|
||||
except Exception as e: RNS.log(f"Error while detaching interface \"{name}\": {e}", RNS.LOG_ERROR); RNS.trace_exception(e)
|
||||
return False
|
||||
|
||||
def _reload_interface(self, name):
|
||||
if not Reticulum.__interface_management_enabled: return False
|
||||
interfaces = { iface.name: iface for iface in RNS.Transport.interfaces }
|
||||
if not name in interfaces:
|
||||
RNS.log(f"Attempt to reload non-existing interface \"{name}\"", RNS.LOG_WARNING)
|
||||
return None
|
||||
|
||||
if self._detach_interface(name) and self._attach_interface(name):
|
||||
RNS.log(f"Interface \"{name}\" was reloaded", RNS.LOG_NOTICE)
|
||||
return True
|
||||
|
||||
else:
|
||||
RNS.log(f"Could not reload interface \"{name}\"", RNS.LOG_NOTICE)
|
||||
return False
|
||||
|
||||
def _synthesize_interface(self, config, name, instance_init=False, force_attach=False):
|
||||
c = config
|
||||
interface_mode = Interface.Interface.MODE_FULL
|
||||
|
||||
|
|
@ -1009,7 +1087,7 @@ class Reticulum:
|
|||
interface.final_init()
|
||||
|
||||
interface = None
|
||||
if (("interface_enabled" in c) and c.as_bool("interface_enabled") == True) or (("enabled" in c) and c.as_bool("enabled") == True):
|
||||
if (("interface_enabled" in c) and c.as_bool("interface_enabled") == True) or (("enabled" in c) and c.as_bool("enabled") == True) or force_attach:
|
||||
interface_config = c
|
||||
interface_config["name"] = name
|
||||
interface_config["selected_interface_mode"] = interface_mode
|
||||
|
|
@ -1113,7 +1191,6 @@ class Reticulum:
|
|||
except Exception as e:
|
||||
RNS.log(f"External interface initialisation failed for {interface_type} / {name}", RNS.LOG_ERROR)
|
||||
RNS.trace_exception(e)
|
||||
|
||||
else:
|
||||
RNS.log("Skipping disabled interface \""+name+"\"", RNS.LOG_DEBUG)
|
||||
|
||||
|
|
@ -1300,6 +1377,12 @@ class Reticulum:
|
|||
if path == "blackholed_identities": self.rpc_return(conn, self.get_blackholed_identities())
|
||||
if path == "is_blackholed": self.rpc_return(conn, self.is_blackholed(call["identity_hash"]))
|
||||
|
||||
if "manage" in call:
|
||||
path = call["manage"]
|
||||
if path == "attach_interface": self.rpc_return(conn, self.attach_interface(call["name"]))
|
||||
if path == "detach_interface": self.rpc_return(conn, self.detach_interface(call["name"]))
|
||||
if path == "reload_interface": self.rpc_return(conn, self.reload_interface(call["name"]))
|
||||
|
||||
if "drop" in call:
|
||||
path = call["drop"]
|
||||
if path == "path": self.rpc_return(conn, self.drop_path(call["destination_hash"]))
|
||||
|
|
@ -1867,21 +1950,39 @@ class Reticulum:
|
|||
if RNS.Profiler.ran(): return RNS.Profiler.results()
|
||||
else: return None
|
||||
|
||||
def halt_interface(self, interface):
|
||||
pass
|
||||
def attach_interface(self, interface_name):
|
||||
if self.is_connected_to_shared_instance:
|
||||
rpc_connection = self.get_rpc_client()
|
||||
rpc_connection.send_bytes(mp.packb({"manage": "attach_interface", "name": interface_name}))
|
||||
response = mp.unpackb(rpc_connection.recv_bytes())
|
||||
return response
|
||||
|
||||
else: return self._attach_interface(interface_name)
|
||||
|
||||
def resume_interface(self, interface):
|
||||
pass
|
||||
def detach_interface(self, interface_name):
|
||||
if self.is_connected_to_shared_instance:
|
||||
rpc_connection = self.get_rpc_client()
|
||||
rpc_connection.send_bytes(mp.packb({"manage": "detach_interface", "name": interface_name}))
|
||||
response = mp.unpackb(rpc_connection.recv_bytes())
|
||||
return response
|
||||
|
||||
else: return self._detach_interface(interface_name)
|
||||
|
||||
def reload_interface(self, interface):
|
||||
pass
|
||||
def reload_interface(self, interface_name):
|
||||
if self.is_connected_to_shared_instance:
|
||||
rpc_connection = self.get_rpc_client()
|
||||
rpc_connection.send_bytes(mp.packb({"manage": "reload_interface", "name": interface_name}))
|
||||
response = mp.unpackb(rpc_connection.recv_bytes())
|
||||
return response
|
||||
|
||||
else: return self._reload_interface(interface_name)
|
||||
|
||||
def get_blackholed_identities(self):
|
||||
if self.is_connected_to_shared_instance:
|
||||
rpc_connection = self.get_rpc_client()
|
||||
rpc_connection.send_bytes(mp.packb({"get": "blackholed_identities"}))
|
||||
response = mp.unpackb(rpc_connection.recv_bytes())
|
||||
return response
|
||||
rpc_connection = self.get_rpc_client()
|
||||
rpc_connection.send_bytes(mp.packb({"get": "blackholed_identities"}))
|
||||
response = mp.unpackb(rpc_connection.recv_bytes())
|
||||
return response
|
||||
|
||||
else: return RNS.Transport.blackholed_identities
|
||||
|
||||
|
|
@ -1892,10 +1993,10 @@ class Reticulum:
|
|||
if len(identity_hash) != RNS.Reticulum.TRUNCATED_HASHLENGTH//8: raise ValueError("Invalid identity hash length for blackhole check")
|
||||
|
||||
if self.is_connected_to_shared_instance:
|
||||
rpc_connection = self.get_rpc_client()
|
||||
rpc_connection.send_bytes(mp.packb({"get": "is_blackholed", "identity_hash": identity_hash}))
|
||||
response = mp.unpackb(rpc_connection.recv_bytes())
|
||||
return response
|
||||
rpc_connection = self.get_rpc_client()
|
||||
rpc_connection.send_bytes(mp.packb({"get": "is_blackholed", "identity_hash": identity_hash}))
|
||||
response = mp.unpackb(rpc_connection.recv_bytes())
|
||||
return response
|
||||
|
||||
else: return identity_hash in RNS.Transport.blackholed_identities
|
||||
|
||||
|
|
|
|||
|
|
@ -215,6 +215,14 @@ instance_name = default
|
|||
# required_discovery_value = 14
|
||||
|
||||
|
||||
# You can disable allowing interfaces to be attached, de-
|
||||
# tached or reloaded via rnstatus, for example if running
|
||||
# on a shared system where users with normal RPC access
|
||||
# should not be allowed to control interface states.
|
||||
|
||||
# enable_interface_management = no
|
||||
|
||||
|
||||
# You can configure Reticulum to panic and forcibly close
|
||||
# if an unrecoverable interface error occurs, such as the
|
||||
# hardware device for an interface disappearing. This is
|
||||
|
|
|
|||
|
|
@ -159,7 +159,8 @@ def get_remote_status(destination_hash, include_lstats, include_profiling, ident
|
|||
def program_setup(configdir, dispall=False, verbosity=0, name_filter=None, json=False, astats=False, pstats=False, lstats=False,
|
||||
sorting=None, sort_reverse=False, remote=None, management_identity=None, must_exit=True, rns_instance=None,
|
||||
traffic_totals=False, discovered_interfaces=False, config_entries=False, burst_filter=False, blocked_ips=False,
|
||||
queue_stats=False, pps=False, profiling=False, remote_timeout=RNS.Transport.PATH_REQUEST_TIMEOUT):
|
||||
queue_stats=False, pps=False, profiling=False, remote_timeout=RNS.Transport.PATH_REQUEST_TIMEOUT,
|
||||
attach=None, detach=None, reload=None):
|
||||
|
||||
if remote: require_shared = False
|
||||
else: require_shared = True
|
||||
|
|
@ -176,6 +177,37 @@ def program_setup(configdir, dispall=False, verbosity=0, name_filter=None, json=
|
|||
if must_exit: exit(1)
|
||||
else: return
|
||||
|
||||
if attach:
|
||||
result = reticulum.attach_interface(attach)
|
||||
if result == True: print(f"Interface {attach} was attached")
|
||||
elif result == False: print(f"Could not attach interface {attach}")
|
||||
elif result == None: print(f"The interface {attach} does not exist")
|
||||
else: print(f"Unknown error while attaching interface {attach}")
|
||||
|
||||
if result == True: exit(0)
|
||||
else: exit(1)
|
||||
|
||||
if detach:
|
||||
result = reticulum.detach_interface(detach)
|
||||
if result == True: print(f"Interface {detach} was detached")
|
||||
elif result == False: print(f"Could not detach interface {detach}")
|
||||
elif result == None: print(f"The interface {detach} does not exist")
|
||||
else: print(f"Unknown error while detaching interface {detach}")
|
||||
|
||||
if result == True: exit(0)
|
||||
else: exit(1)
|
||||
|
||||
if reload:
|
||||
result = reticulum.reload_interface(reload)
|
||||
if result == True: print(f"Interface {reload} was reloaded")
|
||||
elif result == False: print(f"Could not reload interface {reload}")
|
||||
elif result == None: print(f"The interface {reload} does not exist")
|
||||
else: print(f"Unknown error while reloading interface {reload}")
|
||||
|
||||
if result == True: exit(0)
|
||||
else: exit(1)
|
||||
|
||||
|
||||
link_count = None
|
||||
active_link_count = None
|
||||
stats = None
|
||||
|
|
@ -832,6 +864,10 @@ def main(must_exit=True, rns_instance=None):
|
|||
parser.add_argument("--config", action="store", default=None, help="path to alternative Reticulum config directory", type=str)
|
||||
parser.add_argument("--version", action="version", version="rnstatus {version}".format(version=__version__))
|
||||
|
||||
parser.add_argument("--attach", action="store", metavar="name", help="Attach interface by name", default=None, type=str)
|
||||
parser.add_argument("--detach", action="store", metavar="name", help="Detach interface by name", default=None, type=str)
|
||||
parser.add_argument("--reload", action="store", metavar="name", help="Reload interface by name", default=None, type=str)
|
||||
|
||||
parser.add_argument("-a", "--all", action="store_true", help="show all interfaces", default=False)
|
||||
parser.add_argument("-A", "--announce-stats", action="store_true", help="show announce stats", default=False)
|
||||
parser.add_argument("-P", "--pr-stats", action="store_true", help="show path request stats", default=False)
|
||||
|
|
@ -898,7 +934,8 @@ def main(must_exit=True, rns_instance=None):
|
|||
astats=args.announce_stats, pstats=args.pr_stats, lstats=args.link_stats, sorting=args.sort, sort_reverse=args.reverse,
|
||||
remote=args.R, management_identity=args.i, remote_timeout=args.w, must_exit=must_exit, rns_instance=rns_instance,
|
||||
traffic_totals=args.totals, discovered_interfaces=args.discovered, config_entries=args.D, burst_filter=args.burst,
|
||||
blocked_ips=args.blocked_ips, queue_stats=args.queues, pps=args.pps, profiling=args.profiling)
|
||||
blocked_ips=args.blocked_ips, queue_stats=args.queues, pps=args.pps, profiling=args.profiling,
|
||||
attach=args.attach, detach=args.detach, reload=args.reload)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("")
|
||||
|
|
|
|||
|
|
@ -274,8 +274,7 @@ You can easily add ``rnsd`` as an always-on service by :ref:`configuring a servi
|
|||
The rnstatus Utility
|
||||
====================
|
||||
|
||||
Using the ``rnstatus`` utility, you can view the status of configured Reticulum
|
||||
interfaces, similar to the ``ifconfig`` program.
|
||||
Using the ``rnstatus`` utility, you can view the status of and control configured Reticulum interfaces, similar to the ``ifconfig`` program.
|
||||
|
||||
**Usage Examples**
|
||||
|
||||
|
|
@ -350,6 +349,9 @@ Filter output to only show some interfaces:
|
|||
-h, --help show this help message and exit
|
||||
--config CONFIG path to alternative Reticulum config directory
|
||||
--version show program's version number and exit
|
||||
--attach name Attach interface by name
|
||||
--detach name Detach interface by name
|
||||
--reload name Reload interface by name
|
||||
-a, --all show all interfaces
|
||||
-A, --announce-stats show announce stats
|
||||
-P, --pr-stats show path request stats
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue