Reticulum.py
1 # Reticulum License 2 # 3 # Copyright (c) 2016-2025 Mark Qvist 4 # 5 # Permission is hereby granted, free of charge, to any person obtaining a copy 6 # of this software and associated documentation files (the "Software"), to deal 7 # in the Software without restriction, including without limitation the rights 8 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 # copies of the Software, and to permit persons to whom the Software is 10 # furnished to do so, subject to the following conditions: 11 # 12 # - The Software shall not be used in any kind of system which includes amongst 13 # its functions the ability to purposefully do harm to human beings. 14 # 15 # - The Software shall not be used, directly or indirectly, in the creation of 16 # an artificial intelligence, machine learning or language model training 17 # dataset, including but not limited to any use that contributes to the 18 # training or development of such a model or algorithm. 19 # 20 # - The above copyright notice and this permission notice shall be included in 21 # all copies or substantial portions of the Software. 22 # 23 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 29 # SOFTWARE. 30 31 from .vendor.platformutils import get_platform 32 33 if get_platform() == "android": 34 from .Interfaces import Interface 35 from .Interfaces import LocalInterface 36 from .Interfaces import AutoInterface 37 from .Interfaces import BackboneInterface 38 from .Interfaces import TCPInterface 39 from .Interfaces import UDPInterface 40 from .Interfaces import I2PInterface 41 from .Interfaces import RNodeMultiInterface 42 from .Interfaces.Android import RNodeInterface 43 from .Interfaces.Android import SerialInterface 44 from .Interfaces.Android import KISSInterface 45 else: 46 from RNS.Interfaces import * 47 48 from RNS.vendor.configobj import ConfigObj 49 import configparser 50 import multiprocessing.connection 51 import importlib.util 52 import threading 53 import signal 54 import atexit 55 import struct 56 import array 57 import time 58 import os 59 import RNS 60 61 class Reticulum: 62 """ 63 This class is used to initialise access to Reticulum within a 64 program. You must create exactly one instance of this class before 65 carrying out any other RNS operations, such as creating destinations 66 or sending traffic. Every independently executed program must create 67 their own instance of the Reticulum class, but Reticulum will 68 automatically handle inter-program communication on the same system, 69 and expose all connected programs to external interfaces as well. 70 71 As soon as an instance of this class is created, Reticulum will start 72 opening and configuring any hardware devices specified in the supplied 73 configuration. 74 75 Currently the first running instance must be kept running while other 76 local instances are connected, as the first created instance will 77 act as a master instance that directly communicates with external 78 hardware such as modems, TNCs and radios. If a master instance is 79 asked to exit, it will not exit until all client processes have 80 terminated (unless killed forcibly). 81 82 If you are running Reticulum on a system with several different 83 programs that use RNS starting and terminating at different times, 84 it will be advantageous to run a master RNS instance as a daemon for 85 other programs to use on demand. 86 """ 87 88 # Future minimum will probably be locked in at 251 bytes to support 89 # networks with segments of different MTUs. Absolute minimum is 219. 90 MTU = 500 91 """ 92 The MTU that Reticulum adheres to, and will expect other peers to 93 adhere to. By default, the MTU is 500 bytes. In custom RNS network 94 implementations, it is possible to change this value, but doing so will 95 completely break compatibility with all other RNS networks. An identical 96 MTU is a prerequisite for peers to communicate in the same network. 97 98 Unless you really know what you are doing, the MTU should be left at 99 the default value. 100 """ 101 102 LINK_MTU_DISCOVERY = True 103 """ 104 Whether automatic link MTU discovery is enabled by default in this 105 release. Link MTU discovery significantly increases throughput over 106 fast links, but requires all intermediary hops to also support it. 107 Support for this feature was added in RNS version 0.9.0. This option 108 will become enabled by default in the near future. Please update your 109 RNS instances. 110 """ 111 112 MAX_QUEUED_ANNOUNCES = 16384 113 QUEUED_ANNOUNCE_LIFE = 60*60*24 114 115 ANNOUNCE_CAP = 2 116 """ 117 The maximum percentage of interface bandwidth that, at any given time, 118 may be used to propagate announces. If an announce was scheduled for 119 broadcasting on an interface, but doing so would exceed the allowed 120 bandwidth allocation, the announce will be queued for transmission 121 when there is bandwidth available. 122 123 Reticulum will always prioritise propagating announces with fewer 124 hops, ensuring that distant, large networks with many peers on fast 125 links don't overwhelm the capacity of smaller networks on slower 126 mediums. If an announce remains queued for an extended amount of time, 127 it will eventually be dropped. 128 129 This value will be applied by default to all created interfaces, 130 but it can be configured individually on a per-interface basis. In 131 general, the global default setting should not be changed, and any 132 alterations should be made on a per-interface basis instead. 133 """ 134 135 MINIMUM_BITRATE = 5 136 """ 137 Minimum bitrate required across a medium for Reticulum to be able 138 to successfully establish links. Currently 5 bits per second. 139 """ 140 141 # TODO: Let Reticulum somehow continously build a map of per-hop 142 # latencies and use this map for global timeout calculation. 143 DEFAULT_PER_HOP_TIMEOUT = 6 144 145 # Length of truncated hashes in bits. 146 TRUNCATED_HASHLENGTH = 128 147 148 HEADER_MINSIZE = 2+1+(TRUNCATED_HASHLENGTH//8)*1 149 HEADER_MAXSIZE = 2+1+(TRUNCATED_HASHLENGTH//8)*2 150 IFAC_MIN_SIZE = 1 151 IFAC_SALT = bytes.fromhex("adf54d882c9a9b80771eb4995d702d4a3e733391b2a0f53f416d9f907e55cff8") 152 153 MDU = MTU - HEADER_MAXSIZE - IFAC_MIN_SIZE 154 155 RESOURCE_CACHE = 24*60*60 156 JOB_INTERVAL = 5*60 157 CLEAN_INTERVAL = 15*60 158 PERSIST_INTERVAL = 60*60*12 159 GRACIOUS_PERSIST_INTERVAL = 60*5 160 161 router = None 162 config = None 163 164 # The default configuration path will be expanded to a directory 165 # named ".reticulum" inside the current users home directory 166 userdir = os.path.expanduser("~") 167 configdir = None 168 configpath = "" 169 storagepath = "" 170 cachepath = "" 171 interfacepath = "" 172 173 __instance = None 174 175 __interface_detach_ran = False 176 __exit_handler_ran = False 177 @staticmethod 178 def exit_handler(): 179 # This exit handler is called whenever Reticulum is asked to 180 # shut down, and will in turn call exit handlers in other 181 # classes, saving necessary information to disk and carrying 182 # out cleanup operations. 183 if not Reticulum.__exit_handler_ran: 184 Reticulum.__exit_handler_ran = True 185 if not Reticulum.__interface_detach_ran: 186 RNS.Transport.detach_interfaces() 187 RNS.Transport.exit_handler() 188 RNS.Identity.exit_handler() 189 190 if RNS.Profiler.ran(): 191 RNS.Profiler.results() 192 193 RNS.loglevel = -1 194 195 @staticmethod 196 def sigint_handler(signal, frame): 197 RNS.Transport.detach_interfaces() 198 Reticulum.__interface_detach_ran = True 199 RNS.exit() 200 201 @staticmethod 202 def sigterm_handler(signal, frame): 203 RNS.Transport.detach_interfaces() 204 Reticulum.__interface_detach_ran = True 205 RNS.exit() 206 207 @staticmethod 208 def get_instance(): 209 """ 210 Return the currently running Reticulum instance 211 """ 212 return Reticulum.__instance 213 214 def __init__(self,configdir=None, loglevel=None, logdest=None, verbosity=None, 215 require_shared_instance=False, shared_instance_type=None): 216 """ 217 Initialises and starts a Reticulum instance. This must be 218 done before any other operations, and Reticulum will not 219 pass any traffic before being instantiated. 220 221 :param configdir: Full path to a Reticulum configuration directory. 222 """ 223 224 if Reticulum.__instance != None: 225 raise OSError("Attempt to reinitialise Reticulum, when it was already running") 226 else: 227 Reticulum.__instance = self 228 229 RNS.vendor.platformutils.platform_checks() 230 231 if configdir != None: 232 Reticulum.configdir = configdir 233 else: 234 if os.path.isdir("/etc/reticulum") and os.path.isfile("/etc/reticulum/config"): 235 Reticulum.configdir = "/etc/reticulum" 236 elif os.path.isdir(Reticulum.userdir+"/.config/reticulum") and os.path.isfile(Reticulum.userdir+"/.config/reticulum/config"): 237 Reticulum.configdir = Reticulum.userdir+"/.config/reticulum" 238 else: 239 Reticulum.configdir = Reticulum.userdir+"/.reticulum" 240 241 if logdest == RNS.LOG_FILE: 242 RNS.logdest = RNS.LOG_FILE 243 RNS.logfile = Reticulum.configdir+"/logfile" 244 elif callable(logdest): 245 RNS.logdest = RNS.LOG_CALLBACK 246 RNS.logcall = logdest 247 248 Reticulum.configpath = Reticulum.configdir+"/config" 249 Reticulum.storagepath = Reticulum.configdir+"/storage" 250 Reticulum.cachepath = Reticulum.configdir+"/storage/cache" 251 Reticulum.resourcepath = Reticulum.configdir+"/storage/resources" 252 Reticulum.identitypath = Reticulum.configdir+"/storage/identities" 253 Reticulum.interfacepath = Reticulum.configdir+"/interfaces" 254 255 Reticulum.__transport_enabled = False 256 Reticulum.__link_mtu_discovery = Reticulum.LINK_MTU_DISCOVERY 257 Reticulum.__remote_management_enabled = False 258 Reticulum.__use_implicit_proof = True 259 Reticulum.__allow_probes = False 260 261 Reticulum.panic_on_interface_error = False 262 263 self.local_interface_port = 37428 264 self.local_control_port = 37429 265 self.local_socket_path = None 266 self.share_instance = True 267 self.shared_instance_type = shared_instance_type 268 self.rpc_listener = None 269 self.rpc_key = None 270 self.rpc_type = "AF_INET" 271 self.use_af_unix = False 272 273 self.ifac_salt = Reticulum.IFAC_SALT 274 275 self.requested_loglevel = loglevel 276 self.requested_verbosity = verbosity 277 if self.requested_loglevel != None: 278 if self.requested_loglevel > RNS.LOG_EXTREME: 279 self.requested_loglevel = RNS.LOG_EXTREME 280 if self.requested_loglevel < RNS.LOG_CRITICAL: 281 self.requested_loglevel = RNS.LOG_CRITICAL 282 283 RNS.loglevel = self.requested_loglevel 284 285 self.is_shared_instance = False 286 self.shared_instance_interface = None 287 self.require_shared = require_shared_instance 288 self.is_connected_to_shared_instance = False 289 self.is_standalone_instance = False 290 self.jobs_thread = None 291 self.last_data_persist = time.time() 292 self.last_cache_clean = 0 293 294 if not os.path.isdir(Reticulum.storagepath): 295 os.makedirs(Reticulum.storagepath) 296 297 if not os.path.isdir(Reticulum.cachepath): 298 os.makedirs(Reticulum.cachepath) 299 300 if not os.path.isdir(os.path.join(Reticulum.cachepath, "announces")): 301 os.makedirs(os.path.join(Reticulum.cachepath, "announces")) 302 303 if not os.path.isdir(Reticulum.resourcepath): 304 os.makedirs(Reticulum.resourcepath) 305 306 if not os.path.isdir(Reticulum.identitypath): 307 os.makedirs(Reticulum.identitypath) 308 309 if not os.path.isdir(Reticulum.interfacepath): 310 os.makedirs(Reticulum.interfacepath) 311 312 if os.path.isfile(self.configpath): 313 try: 314 self.config = ConfigObj(self.configpath) 315 except Exception as e: 316 RNS.log("Could not parse the configuration at "+self.configpath, RNS.LOG_ERROR) 317 RNS.log("Check your configuration file for errors!", RNS.LOG_ERROR) 318 RNS.panic() 319 else: 320 RNS.log("Could not load config file, creating default configuration file...") 321 self.__create_default_config() 322 RNS.log("Default config file created. Make any necessary changes in "+Reticulum.configdir+"/config and restart Reticulum if needed.") 323 time.sleep(1.5) 324 325 self.__apply_config() 326 RNS.log(f"Utilising cryptography backend \"{RNS.Cryptography.Provider.backend()}\"", RNS.LOG_DEBUG) 327 RNS.log(f"Configuration loaded from {self.configpath}", RNS.LOG_VERBOSE) 328 329 RNS.Identity.load_known_destinations() 330 RNS.Transport.start(self) 331 332 if self.use_af_unix: 333 self.rpc_addr = f"\0rns/{self.local_socket_path}/rpc" 334 self.rpc_type = "AF_UNIX" 335 else: 336 self.rpc_addr = ("127.0.0.1", self.local_control_port) 337 self.rpc_type = "AF_INET" 338 339 if self.rpc_key == None: 340 self.rpc_key = RNS.Identity.full_hash(RNS.Transport.identity.get_private_key()) 341 342 if self.is_shared_instance: 343 self.rpc_listener = multiprocessing.connection.Listener(self.rpc_addr, family=self.rpc_type, authkey=self.rpc_key) 344 thread = threading.Thread(target=self.rpc_loop) 345 thread.daemon = True 346 thread.start() 347 348 atexit.register(Reticulum.exit_handler) 349 signal.signal(signal.SIGINT, Reticulum.sigint_handler) 350 signal.signal(signal.SIGTERM, Reticulum.sigterm_handler) 351 352 def __start_jobs(self): 353 if self.jobs_thread == None: 354 RNS.Identity._clean_ratchets() 355 self.jobs_thread = threading.Thread(target=self.__jobs) 356 self.jobs_thread.daemon = True 357 self.jobs_thread.start() 358 359 def __jobs(self): 360 while True: 361 now = time.time() 362 363 if now > self.last_cache_clean+Reticulum.CLEAN_INTERVAL: 364 self.__clean_caches() 365 self.last_cache_clean = time.time() 366 367 if now > self.last_data_persist+Reticulum.PERSIST_INTERVAL: 368 self.__persist_data() 369 370 time.sleep(Reticulum.JOB_INTERVAL) 371 372 def __start_local_interface(self): 373 if self.share_instance: 374 try: 375 interface = LocalInterface.LocalServerInterface( 376 RNS.Transport, 377 self.local_interface_port, 378 socket_path=self.local_socket_path 379 ) 380 interface.OUT = True 381 if hasattr(Reticulum, "_force_shared_instance_bitrate"): 382 interface.bitrate = Reticulum._force_shared_instance_bitrate 383 interface._force_bitrate = Reticulum._force_shared_instance_bitrate 384 RNS.log(f"Forcing shared instance bitrate of {RNS.prettyspeed(interface.bitrate)}", RNS.LOG_WARNING) 385 interface.optimise_mtu() 386 387 if self.require_shared == True: 388 interface.detach() 389 self.is_shared_instance = True 390 RNS.log("Existing shared instance required, but this instance started as shared instance. Aborting startup.", RNS.LOG_VERBOSE) 391 392 else: 393 RNS.Transport.interfaces.append(interface) 394 self.shared_instance_interface = interface 395 self.is_shared_instance = True 396 RNS.log("Started shared instance interface: "+str(interface), RNS.LOG_DEBUG) 397 self.__start_jobs() 398 399 except Exception as e: 400 try: 401 interface = LocalInterface.LocalClientInterface( 402 RNS.Transport, 403 "Local shared instance", 404 self.local_interface_port, 405 socket_path=self.local_socket_path) 406 interface.target_port = self.local_interface_port 407 interface.OUT = True 408 if hasattr(Reticulum, "_force_shared_instance_bitrate"): 409 interface.bitrate = Reticulum._force_shared_instance_bitrate 410 interface._force_bitrate = True 411 RNS.log(f"Forcing shared instance bitrate of {RNS.prettyspeed(interface.bitrate)}", RNS.LOG_WARNING) 412 interface.optimise_mtu() 413 RNS.Transport.interfaces.append(interface) 414 self.is_shared_instance = False 415 self.is_standalone_instance = False 416 self.is_connected_to_shared_instance = True 417 Reticulum.__transport_enabled = False 418 Reticulum.__remote_management_enabled = False 419 Reticulum.__allow_probes = False 420 RNS.log("Connected to locally available Reticulum instance via: "+str(interface), RNS.LOG_DEBUG) 421 except Exception as e: 422 RNS.log("Local shared instance appears to be running, but it could not be connected", RNS.LOG_ERROR) 423 RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR) 424 self.is_shared_instance = False 425 self.is_standalone_instance = True 426 self.is_connected_to_shared_instance = False 427 428 if self.is_shared_instance and self.require_shared: 429 raise SystemError("No shared instance available, but application that started Reticulum required it") 430 431 else: 432 self.is_shared_instance = False 433 self.is_standalone_instance = True 434 self.is_connected_to_shared_instance = False 435 self.__start_jobs() 436 437 def __apply_config(self): 438 if "logging" in self.config: 439 for option in self.config["logging"]: 440 value = self.config["logging"][option] 441 if option == "loglevel" and self.requested_loglevel == None: 442 RNS.loglevel = int(value) 443 if self.requested_verbosity != None: 444 RNS.loglevel += self.requested_verbosity 445 if RNS.loglevel < 0: 446 RNS.loglevel = 0 447 if RNS.loglevel > 7: 448 RNS.loglevel = 7 449 450 if "reticulum" in self.config: 451 for option in self.config["reticulum"]: 452 value = self.config["reticulum"][option] 453 if option == "share_instance": 454 value = self.config["reticulum"].as_bool(option) 455 self.share_instance = value 456 if RNS.vendor.platformutils.use_af_unix(): 457 if option == "instance_name": 458 value = self.config["reticulum"][option] 459 self.local_socket_path = value 460 if option == "shared_instance_type": 461 if self.shared_instance_type == None: 462 value = self.config["reticulum"][option].lower() 463 if value in ["tcp", "unix"]: 464 self.shared_instance_type = value 465 if option == "shared_instance_port": 466 value = int(self.config["reticulum"][option]) 467 self.local_interface_port = value 468 if option == "instance_control_port": 469 value = int(self.config["reticulum"][option]) 470 self.local_control_port = value 471 if option == "rpc_key": 472 try: 473 value = bytes.fromhex(self.config["reticulum"][option]) 474 self.rpc_key = value 475 except Exception as e: 476 RNS.log("Invalid shared instance RPC key specified, falling back to default key", RNS.LOG_ERROR) 477 self.rpc_key = None 478 if option == "enable_transport": 479 v = self.config["reticulum"].as_bool(option) 480 if v == True: 481 Reticulum.__transport_enabled = True 482 if option == "link_mtu_discovery": 483 v = self.config["reticulum"].as_bool(option) 484 if v == True: 485 Reticulum.__link_mtu_discovery = True 486 if option == "enable_remote_management": 487 v = self.config["reticulum"].as_bool(option) 488 if v == True: 489 Reticulum.__remote_management_enabled = True 490 if option == "remote_management_allowed": 491 v = self.config["reticulum"].as_list(option) 492 for hexhash in v: 493 dest_len = (RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2 494 if len(hexhash) != dest_len: 495 raise ValueError("Identity hash length for remote management ACL "+str(hexhash)+" is invalid, must be {hex} hexadecimal characters ({byte} bytes).".format(hex=dest_len, byte=dest_len//2)) 496 try: 497 allowed_hash = bytes.fromhex(hexhash) 498 except Exception as e: 499 raise ValueError("Invalid identity hash for remote management ACL: "+str(hexhash)) 500 501 if not allowed_hash in RNS.Transport.remote_management_allowed: 502 RNS.Transport.remote_management_allowed.append(allowed_hash) 503 if option == "respond_to_probes": 504 v = self.config["reticulum"].as_bool(option) 505 if v == True: 506 Reticulum.__allow_probes = True 507 if option == "force_shared_instance_bitrate": 508 v = self.config["reticulum"].as_int(option) 509 Reticulum._force_shared_instance_bitrate = v 510 if option == "panic_on_interface_error": 511 v = self.config["reticulum"].as_bool(option) 512 if v == True: 513 Reticulum.panic_on_interface_error = True 514 if option == "use_implicit_proof": 515 v = self.config["reticulum"].as_bool(option) 516 if v == True: 517 Reticulum.__use_implicit_proof = True 518 if v == False: 519 Reticulum.__use_implicit_proof = False 520 521 if RNS.compiled: RNS.log("Reticulum running in compiled mode", RNS.LOG_DEBUG) 522 else: RNS.log("Reticulum running in interpreted mode", RNS.LOG_DEBUG) 523 524 if RNS.vendor.platformutils.use_af_unix(): 525 if self.shared_instance_type == "tcp": self.use_af_unix = False 526 else: self.use_af_unix = True 527 else: 528 self.shared_instance_type = "tcp" 529 self.use_af_unix = False 530 531 if self.local_socket_path == None and self.use_af_unix: 532 self.local_socket_path = "default" 533 534 self.__start_local_interface() 535 536 if self.is_shared_instance or self.is_standalone_instance: 537 RNS.log("Bringing up system interfaces...", RNS.LOG_VERBOSE) 538 interface_names = [] 539 if "interfaces" in self.config: 540 for name in self.config["interfaces"]: 541 if not name in interface_names: 542 c = self.config["interfaces"][name] 543 interface_mode = Interface.Interface.MODE_FULL 544 545 if "interface_mode" in c: 546 c["interface_mode"] = str(c["interface_mode"]).lower() 547 if c["interface_mode"] == "full": 548 interface_mode = Interface.Interface.MODE_FULL 549 elif c["interface_mode"] == "access_point" or c["interface_mode"] == "accesspoint" or c["interface_mode"] == "ap": 550 interface_mode = Interface.Interface.MODE_ACCESS_POINT 551 elif c["interface_mode"] == "pointtopoint" or c["interface_mode"] == "ptp": 552 interface_mode = Interface.Interface.MODE_POINT_TO_POINT 553 elif c["interface_mode"] == "roaming": 554 interface_mode = Interface.Interface.MODE_ROAMING 555 elif c["interface_mode"] == "boundary": 556 interface_mode = Interface.Interface.MODE_BOUNDARY 557 elif c["mode"] == "gateway" or c["mode"] == "gw": 558 interface_mode = Interface.Interface.MODE_GATEWAY 559 560 elif "mode" in c: 561 c["mode"] = str(c["mode"]).lower() 562 if c["mode"] == "full": 563 interface_mode = Interface.Interface.MODE_FULL 564 elif c["mode"] == "access_point" or c["mode"] == "accesspoint" or c["mode"] == "ap": 565 interface_mode = Interface.Interface.MODE_ACCESS_POINT 566 elif c["mode"] == "pointtopoint" or c["mode"] == "ptp": 567 interface_mode = Interface.Interface.MODE_POINT_TO_POINT 568 elif c["mode"] == "roaming": 569 interface_mode = Interface.Interface.MODE_ROAMING 570 elif c["mode"] == "boundary": 571 interface_mode = Interface.Interface.MODE_BOUNDARY 572 elif c["mode"] == "gateway" or c["mode"] == "gw": 573 interface_mode = Interface.Interface.MODE_GATEWAY 574 575 ifac_size = None 576 if "ifac_size" in c: 577 if c.as_int("ifac_size") >= Reticulum.IFAC_MIN_SIZE*8: 578 ifac_size = c.as_int("ifac_size")//8 579 580 ifac_netname = None 581 if "networkname" in c: 582 if c["networkname"] != "": 583 ifac_netname = c["networkname"] 584 if "network_name" in c: 585 if c["network_name"] != "": 586 ifac_netname = c["network_name"] 587 588 ifac_netkey = None 589 if "passphrase" in c: 590 if c["passphrase"] != "": 591 ifac_netkey = c["passphrase"] 592 if "pass_phrase" in c: 593 if c["pass_phrase"] != "": 594 ifac_netkey = c["pass_phrase"] 595 596 ingress_control = True 597 if "ingress_control" in c: ingress_control = c.as_bool("ingress_control") 598 ic_max_held_announces = None 599 if "ic_max_held_announces" in c: ic_max_held_announces = c.as_int("ic_max_held_announces") 600 ic_burst_hold = None 601 if "ic_burst_hold" in c: ic_burst_hold = c.as_float("ic_burst_hold") 602 ic_burst_freq_new = None 603 if "ic_burst_freq_new" in c: ic_burst_freq_new = c.as_float("ic_burst_freq_new") 604 ic_burst_freq = None 605 if "ic_burst_freq" in c: ic_burst_freq = c.as_float("ic_burst_freq") 606 ic_new_time = None 607 if "ic_new_time" in c: ic_new_time = c.as_float("ic_new_time") 608 ic_burst_penalty = None 609 if "ic_burst_penalty" in c: ic_burst_penalty = c.as_float("ic_burst_penalty") 610 ic_held_release_interval = None 611 if "ic_held_release_interval" in c: ic_held_release_interval = c.as_float("ic_held_release_interval") 612 613 configured_bitrate = None 614 if "bitrate" in c: 615 if c.as_int("bitrate") >= Reticulum.MINIMUM_BITRATE: 616 configured_bitrate = c.as_int("bitrate") 617 618 announce_rate_target = None 619 if "announce_rate_target" in c: 620 if c.as_int("announce_rate_target") > 0: 621 announce_rate_target = c.as_int("announce_rate_target") 622 623 announce_rate_grace = None 624 if "announce_rate_grace" in c: 625 if c.as_int("announce_rate_grace") >= 0: 626 announce_rate_grace = c.as_int("announce_rate_grace") 627 628 announce_rate_penalty = None 629 if "announce_rate_penalty" in c: 630 if c.as_int("announce_rate_penalty") >= 0: 631 announce_rate_penalty = c.as_int("announce_rate_penalty") 632 633 if announce_rate_target != None and announce_rate_grace == None: 634 announce_rate_grace = 0 635 636 if announce_rate_target != None and announce_rate_penalty == None: 637 announce_rate_penalty = 0 638 639 announce_cap = Reticulum.ANNOUNCE_CAP/100.0 640 if "announce_cap" in c: 641 if c.as_float("announce_cap") > 0 and c.as_float("announce_cap") <= 100: 642 announce_cap = c.as_float("announce_cap")/100.0 643 644 try: 645 def interface_post_init(interface): 646 if interface != None: 647 if "outgoing" in c and c.as_bool("outgoing") == False: 648 interface.OUT = False 649 else: 650 interface.OUT = True 651 652 interface.mode = interface_mode 653 interface.announce_cap = announce_cap 654 if configured_bitrate: interface.bitrate = configured_bitrate 655 interface.optimise_mtu() 656 657 if ifac_size != None: interface.ifac_size = ifac_size 658 else: interface.ifac_size = interface.DEFAULT_IFAC_SIZE 659 660 interface.announce_rate_target = announce_rate_target 661 interface.announce_rate_grace = announce_rate_grace 662 interface.announce_rate_penalty = announce_rate_penalty 663 interface.ingress_control = ingress_control 664 if ic_max_held_announces != None: interface.ic_max_held_announces = ic_max_held_announces 665 if ic_burst_hold != None: interface.ic_burst_hold = ic_burst_hold 666 if ic_burst_freq_new != None: interface.ic_burst_freq_new = ic_burst_freq_new 667 if ic_burst_freq != None: interface.ic_burst_freq = ic_burst_freq 668 if ic_new_time != None: interface.ic_new_time = ic_new_time 669 if ic_burst_penalty != None: interface.ic_burst_penalty = ic_burst_penalty 670 if ic_held_release_interval != None: interface.ic_held_release_interval = ic_held_release_interval 671 672 interface.ifac_netname = ifac_netname 673 interface.ifac_netkey = ifac_netkey 674 675 if interface.ifac_netname != None or interface.ifac_netkey != None: 676 ifac_origin = b"" 677 678 if interface.ifac_netname != None: 679 ifac_origin += RNS.Identity.full_hash(interface.ifac_netname.encode("utf-8")) 680 681 if interface.ifac_netkey != None: 682 ifac_origin += RNS.Identity.full_hash(interface.ifac_netkey.encode("utf-8")) 683 684 ifac_origin_hash = RNS.Identity.full_hash(ifac_origin) 685 interface.ifac_key = RNS.Cryptography.hkdf( 686 length=64, 687 derive_from=ifac_origin_hash, 688 salt=self.ifac_salt, 689 context=None 690 ) 691 692 interface.ifac_identity = RNS.Identity.from_bytes(interface.ifac_key) 693 interface.ifac_signature = interface.ifac_identity.sign(RNS.Identity.full_hash(interface.ifac_key)) 694 695 RNS.Transport.interfaces.append(interface) 696 interface.final_init() 697 698 interface = None 699 if (("interface_enabled" in c) and c.as_bool("interface_enabled") == True) or (("enabled" in c) and c.as_bool("enabled") == True): 700 interface_config = c 701 interface_config["name"] = name 702 interface_config["selected_interface_mode"] = interface_mode 703 interface_config["configured_bitrate"] = configured_bitrate 704 705 if c["type"] == "AutoInterface": 706 interface = AutoInterface.AutoInterface(RNS.Transport, interface_config) 707 interface_post_init(interface) 708 709 if c["type"] == "BackboneInterface" or c["type"] == "BackboneClientInterface": 710 if "port" in c: c["listen_port"] = c["port"] 711 if "port" in c: c["target_port"] = c["port"] 712 if "remote" in c: c["target_host"] = c["remote"] 713 if "listen_on" in c: c["listen_ip"] = c["listen_on"] 714 715 if c["type"] == "BackboneInterface": 716 if "target_host" in c: interface = BackboneInterface.BackboneClientInterface(RNS.Transport, interface_config) 717 else: interface = BackboneInterface.BackboneInterface(RNS.Transport, interface_config) 718 interface_post_init(interface) 719 720 if c["type"] == "BackboneClientInterface": 721 interface = BackboneInterface.BackboneClientInterface(RNS.Transport, interface_config) 722 interface_post_init(interface) 723 724 if c["type"] == "UDPInterface": 725 interface = UDPInterface.UDPInterface(RNS.Transport, interface_config) 726 interface_post_init(interface) 727 728 if c["type"] == "TCPServerInterface": 729 interface = TCPInterface.TCPServerInterface(RNS.Transport, interface_config) 730 interface_post_init(interface) 731 732 if c["type"] == "TCPClientInterface": 733 interface = TCPInterface.TCPClientInterface(RNS.Transport, interface_config) 734 interface_post_init(interface) 735 736 if c["type"] == "I2PInterface": 737 interface_config["storagepath"] = Reticulum.storagepath 738 interface_config["ifac_netname"] = ifac_netname 739 interface_config["ifac_netkey"] = ifac_netkey 740 interface_config["ifac_size"] = ifac_size 741 742 interface = I2PInterface.I2PInterface(RNS.Transport, interface_config) 743 interface_post_init(interface) 744 745 if c["type"] == "SerialInterface": 746 interface = SerialInterface.SerialInterface(RNS.Transport, interface_config) 747 interface_post_init(interface) 748 749 if c["type"] == "PipeInterface": 750 interface = PipeInterface.PipeInterface(RNS.Transport, interface_config) 751 interface_post_init(interface) 752 753 if c["type"] == "KISSInterface": 754 interface = KISSInterface.KISSInterface(RNS.Transport, interface_config) 755 interface_post_init(interface) 756 757 if c["type"] == "AX25KISSInterface": 758 interface = AX25KISSInterface.AX25KISSInterface(RNS.Transport, interface_config) 759 interface_post_init(interface) 760 761 if c["type"] == "RNodeInterface": 762 interface = RNodeInterface.RNodeInterface(RNS.Transport, interface_config) 763 interface_post_init(interface) 764 765 if c["type"] == "RNodeMultiInterface": 766 interface = RNodeMultiInterface.RNodeMultiInterface(RNS.Transport, interface_config) 767 interface_post_init(interface) 768 interface.start() 769 770 if interface == None: 771 # Interface was not handled by any internal interface types, 772 # attempt to load and initialise it from user-supplied modules 773 interface_type = c["type"] 774 interface_file = f"{interface_type}.py" 775 interface_path = os.path.join(self.interfacepath, interface_file) 776 if not os.path.isfile(interface_path): 777 RNS.log(f"Could not locate external interface module \"{interface_file}\" in \"{self.interfacepath}\"", RNS.LOG_ERROR) 778 779 else: 780 try: 781 RNS.log(f"Loading external interface \"{interface_file}\" from \"{self.interfacepath}\"", RNS.LOG_NOTICE) 782 interface_globals = {} 783 interface_globals["Interface"] = Interface.Interface 784 interface_globals["RNS"] = RNS 785 with open(interface_path) as class_file: 786 interface_code = class_file.read() 787 exec(interface_code, interface_globals) 788 interface_class = interface_globals["interface_class"] 789 790 if interface_class != None: 791 interface = interface_class(RNS.Transport, interface_config) 792 interface_post_init(interface) 793 794 except Exception as e: 795 RNS.log(f"External interface initialisation failed for {interface_type} / {name}", RNS.LOG_ERROR) 796 RNS.trace_exception(e) 797 798 else: 799 RNS.log("Skipping disabled interface \""+name+"\"", RNS.LOG_DEBUG) 800 801 except Exception as e: 802 RNS.log("The interface \""+name+"\" could not be created. Check your configuration file for errors!", RNS.LOG_ERROR) 803 RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR) 804 RNS.panic() 805 else: 806 RNS.log("The interface name \""+name+"\" was already used. Check your configuration file for errors!", RNS.LOG_ERROR) 807 RNS.panic() 808 809 RNS.log("System interfaces are ready", RNS.LOG_VERBOSE) 810 811 def _add_interface(self, interface, mode = None, configured_bitrate=None, ifac_size=None, ifac_netname=None, ifac_netkey=None, announce_cap=None, announce_rate_target=None, announce_rate_grace=None, announce_rate_penalty=None): 812 if not self.is_connected_to_shared_instance: 813 if interface != None and issubclass(type(interface), RNS.Interfaces.Interface.Interface): 814 815 if mode == None: 816 mode = Interface.Interface.MODE_FULL 817 interface.mode = mode 818 819 if configured_bitrate: 820 interface.bitrate = configured_bitrate 821 interface.optimise_mtu() 822 823 if ifac_size != None: 824 interface.ifac_size = ifac_size 825 else: 826 interface.ifac_size = 8 827 828 interface.announce_cap = announce_cap if announce_cap != None else Reticulum.ANNOUNCE_CAP/100.0 829 interface.announce_rate_target = announce_rate_target 830 interface.announce_rate_grace = announce_rate_grace 831 interface.announce_rate_penalty = announce_rate_penalty 832 833 interface.ifac_netname = ifac_netname 834 interface.ifac_netkey = ifac_netkey 835 836 if interface.ifac_netname != None or interface.ifac_netkey != None: 837 ifac_origin = b"" 838 839 if interface.ifac_netname != None: 840 ifac_origin += RNS.Identity.full_hash(interface.ifac_netname.encode("utf-8")) 841 842 if interface.ifac_netkey != None: 843 ifac_origin += RNS.Identity.full_hash(interface.ifac_netkey.encode("utf-8")) 844 845 ifac_origin_hash = RNS.Identity.full_hash(ifac_origin) 846 interface.ifac_key = RNS.Cryptography.hkdf( 847 length=64, 848 derive_from=ifac_origin_hash, 849 salt=self.ifac_salt, 850 context=None 851 ) 852 853 interface.ifac_identity = RNS.Identity.from_bytes(interface.ifac_key) 854 interface.ifac_signature = interface.ifac_identity.sign(RNS.Identity.full_hash(interface.ifac_key)) 855 856 RNS.Transport.interfaces.append(interface) 857 interface.final_init() 858 859 def _should_persist_data(self): 860 if time.time() > self.last_data_persist+Reticulum.GRACIOUS_PERSIST_INTERVAL: 861 self.__persist_data() 862 863 def __persist_data(self): 864 RNS.Transport.persist_data() 865 RNS.Identity.persist_data() 866 self.last_data_persist = time.time() 867 868 def __clean_caches(self): 869 RNS.log("Cleaning resource and packet caches...", RNS.LOG_EXTREME) 870 now = time.time() 871 872 # Clean resource caches 873 for filename in os.listdir(self.resourcepath): 874 try: 875 if len(filename) == (RNS.Identity.HASHLENGTH//8)*2: 876 filepath = self.resourcepath + "/" + filename 877 mtime = os.path.getmtime(filepath) 878 age = now - mtime 879 if age > Reticulum.RESOURCE_CACHE: 880 os.unlink(filepath) 881 882 except Exception as e: 883 RNS.log("Error while cleaning resources cache, the contained exception was: "+str(e), RNS.LOG_ERROR) 884 885 # Clean packet caches 886 for filename in os.listdir(self.cachepath): 887 try: 888 if len(filename) == (RNS.Identity.HASHLENGTH//8)*2: 889 filepath = self.cachepath + "/" + filename 890 mtime = os.path.getmtime(filepath) 891 age = now - mtime 892 if age > RNS.Transport.DESTINATION_TIMEOUT: 893 os.unlink(filepath) 894 895 except Exception as e: 896 RNS.log("Error while cleaning resources cache, the contained exception was: "+str(e), RNS.LOG_ERROR) 897 898 def __create_default_config(self): 899 self.config = ConfigObj(__default_rns_config__) 900 self.config.filename = Reticulum.configpath 901 902 if not os.path.isdir(Reticulum.configdir): 903 os.makedirs(Reticulum.configdir) 904 self.config.write() 905 906 def rpc_loop(self): 907 while True: 908 try: 909 rpc_connection = self.rpc_listener.accept() 910 call = rpc_connection.recv() 911 912 if "get" in call: 913 path = call["get"] 914 915 if path == "interface_stats": 916 rpc_connection.send(self.get_interface_stats()) 917 918 if path == "path_table": 919 mh = call["max_hops"] 920 rpc_connection.send(self.get_path_table(max_hops=mh)) 921 922 if path == "rate_table": 923 rpc_connection.send(self.get_rate_table()) 924 925 if path == "next_hop_if_name": 926 rpc_connection.send(self.get_next_hop_if_name(call["destination_hash"])) 927 928 if path == "next_hop": 929 rpc_connection.send(self.get_next_hop(call["destination_hash"])) 930 931 if path == "first_hop_timeout": 932 rpc_connection.send(self.get_first_hop_timeout(call["destination_hash"])) 933 934 if path == "link_count": 935 rpc_connection.send(self.get_link_count()) 936 937 if path == "packet_rssi": 938 rpc_connection.send(self.get_packet_rssi(call["packet_hash"])) 939 940 if path == "packet_snr": 941 rpc_connection.send(self.get_packet_snr(call["packet_hash"])) 942 943 if path == "packet_q": 944 rpc_connection.send(self.get_packet_q(call["packet_hash"])) 945 946 if "drop" in call: 947 path = call["drop"] 948 949 if path == "path": 950 rpc_connection.send(self.drop_path(call["destination_hash"])) 951 952 if path == "all_via": 953 rpc_connection.send(self.drop_all_via(call["destination_hash"])) 954 955 if path == "announce_queues": 956 rpc_connection.send(self.drop_announce_queues()) 957 958 rpc_connection.close() 959 960 except Exception as e: 961 RNS.log("An error ocurred while handling RPC call from local client: "+str(e), RNS.LOG_ERROR) 962 963 def get_rpc_client(self): return multiprocessing.connection.Client(self.rpc_addr, family=self.rpc_type, authkey=self.rpc_key) 964 965 def get_interface_stats(self): 966 if self.is_connected_to_shared_instance: 967 rpc_connection = self.get_rpc_client() 968 rpc_connection.send({"get": "interface_stats"}) 969 response = rpc_connection.recv() 970 return response 971 else: 972 interfaces = [] 973 for interface in RNS.Transport.interfaces: 974 ifstats = {} 975 976 if hasattr(interface, "clients"): 977 ifstats["clients"] = interface.clients 978 else: 979 ifstats["clients"] = None 980 981 if hasattr(interface, "parent_interface") and interface.parent_interface != None: 982 ifstats["parent_interface_name"] = str(interface.parent_interface) 983 ifstats["parent_interface_hash"] = interface.parent_interface.get_hash() 984 985 if hasattr(interface, "i2p") and hasattr(interface, "connectable"): 986 if interface.connectable: 987 ifstats["i2p_connectable"] = True 988 else: 989 ifstats["i2p_connectable"] = False 990 991 if hasattr(interface, "b32"): 992 if interface.b32 != None: 993 ifstats["i2p_b32"] = interface.b32+".b32.i2p" 994 else: 995 ifstats["i2p_b32"] = None 996 997 if hasattr(interface, "i2p_tunnel_state"): 998 if interface.i2p_tunnel_state != None: 999 state_description = "Unknown State" 1000 if interface.i2p_tunnel_state == I2PInterface.I2PInterfacePeer.TUNNEL_STATE_ACTIVE: 1001 state_description = "Tunnel Active" 1002 elif interface.i2p_tunnel_state == I2PInterface.I2PInterfacePeer.TUNNEL_STATE_INIT: 1003 state_description = "Creating Tunnel" 1004 elif interface.i2p_tunnel_state == I2PInterface.I2PInterfacePeer.TUNNEL_STATE_STALE: 1005 state_description = "Tunnel Unresponsive" 1006 ifstats["tunnelstate"] = state_description 1007 else: 1008 ifstats["tunnelstate"] = None 1009 1010 if hasattr(interface, "r_airtime_short"): 1011 ifstats["airtime_short"] = interface.r_airtime_short 1012 1013 if hasattr(interface, "r_airtime_long"): 1014 ifstats["airtime_long"] = interface.r_airtime_long 1015 1016 if hasattr(interface, "r_channel_load_short"): 1017 ifstats["channel_load_short"] = interface.r_channel_load_short 1018 1019 if hasattr(interface, "r_channel_load_long"): 1020 ifstats["channel_load_long"] = interface.r_channel_load_long 1021 1022 if hasattr(interface, "r_noise_floor"): 1023 ifstats["noise_floor"] = interface.r_noise_floor 1024 1025 if hasattr(interface, "r_battery_state"): 1026 if interface.r_battery_state != 0x00: 1027 ifstats["battery_state"] = interface.get_battery_state_string() 1028 1029 if hasattr(interface, "r_battery_percent"): 1030 ifstats["battery_percent"] = interface.r_battery_percent 1031 1032 if hasattr(interface, "bitrate"): 1033 if interface.bitrate != None: 1034 ifstats["bitrate"] = interface.bitrate 1035 else: 1036 ifstats["bitrate"] = None 1037 1038 if hasattr(interface, "current_rx_speed"): 1039 if interface.current_rx_speed != None: 1040 ifstats["rxs"] = interface.current_rx_speed 1041 else: 1042 ifstats["rxs"] = 0 1043 else: 1044 ifstats["rxs"] = 0 1045 1046 if hasattr(interface, "current_tx_speed"): 1047 if interface.current_tx_speed != None: 1048 ifstats["txs"] = interface.current_tx_speed 1049 else: 1050 ifstats["txs"] = 0 1051 else: 1052 ifstats["txs"] = 0 1053 1054 if hasattr(interface, "peers"): 1055 if interface.peers != None: 1056 ifstats["peers"] = len(interface.peers) 1057 else: 1058 ifstats["peers"] = None 1059 1060 if hasattr(interface, "ifac_signature"): 1061 ifstats["ifac_signature"] = interface.ifac_signature 1062 ifstats["ifac_size"] = interface.ifac_size 1063 ifstats["ifac_netname"] = interface.ifac_netname 1064 else: 1065 ifstats["ifac_signature"] = None 1066 ifstats["ifac_size"] = None 1067 ifstats["ifac_netname"] = None 1068 1069 if hasattr(interface, "announce_queue"): 1070 if interface.announce_queue != None: 1071 ifstats["announce_queue"] = len(interface.announce_queue) 1072 else: 1073 ifstats["announce_queue"] = None 1074 1075 ifstats["name"] = str(interface) 1076 ifstats["short_name"] = str(interface.name) 1077 ifstats["hash"] = interface.get_hash() 1078 ifstats["type"] = str(type(interface).__name__) 1079 ifstats["rxb"] = interface.rxb 1080 ifstats["txb"] = interface.txb 1081 ifstats["incoming_announce_frequency"] = interface.incoming_announce_frequency() 1082 ifstats["outgoing_announce_frequency"] = interface.outgoing_announce_frequency() 1083 ifstats["held_announces"] = len(interface.held_announces) 1084 ifstats["status"] = interface.online 1085 ifstats["mode"] = interface.mode 1086 1087 interfaces.append(ifstats) 1088 1089 stats = {} 1090 stats["interfaces"] = interfaces 1091 stats["rxb"] = RNS.Transport.traffic_rxb 1092 stats["txb"] = RNS.Transport.traffic_txb 1093 stats["rxs"] = RNS.Transport.speed_rx 1094 stats["txs"] = RNS.Transport.speed_tx 1095 if Reticulum.transport_enabled(): 1096 stats["transport_id"] = RNS.Transport.identity.hash 1097 stats["transport_uptime"] = time.time()-RNS.Transport.start_time 1098 if Reticulum.probe_destination_enabled(): 1099 stats["probe_responder"] = RNS.Transport.probe_destination.hash 1100 else: 1101 stats["probe_responder"] = None 1102 1103 if importlib.util.find_spec('psutil') != None: 1104 import psutil 1105 process = psutil.Process() 1106 stats["rss"] = process.memory_info().rss 1107 else: 1108 stats["rss"] = None 1109 1110 return stats 1111 1112 def get_path_table(self, max_hops=None): 1113 if self.is_connected_to_shared_instance: 1114 rpc_connection = self.get_rpc_client() 1115 rpc_connection.send({"get": "path_table", "max_hops": max_hops}) 1116 response = rpc_connection.recv() 1117 return response 1118 1119 else: 1120 path_table = [] 1121 for dst_hash in RNS.Transport.path_table: 1122 path_hops = RNS.Transport.path_table[dst_hash][2] 1123 if max_hops == None or path_hops <= max_hops: 1124 entry = { 1125 "hash": dst_hash, 1126 "timestamp": RNS.Transport.path_table[dst_hash][0], 1127 "via": RNS.Transport.path_table[dst_hash][1], 1128 "hops": path_hops, 1129 "expires": RNS.Transport.path_table[dst_hash][3], 1130 "interface": str(RNS.Transport.path_table[dst_hash][5]), 1131 } 1132 path_table.append(entry) 1133 1134 return path_table 1135 1136 def get_rate_table(self): 1137 if self.is_connected_to_shared_instance: 1138 rpc_connection = self.get_rpc_client() 1139 rpc_connection.send({"get": "rate_table"}) 1140 response = rpc_connection.recv() 1141 return response 1142 1143 else: 1144 rate_table = [] 1145 for dst_hash in RNS.Transport.announce_rate_table: 1146 entry = { 1147 "hash": dst_hash, 1148 "last": RNS.Transport.announce_rate_table[dst_hash]["last"], 1149 "rate_violations": RNS.Transport.announce_rate_table[dst_hash]["rate_violations"], 1150 "blocked_until": RNS.Transport.announce_rate_table[dst_hash]["blocked_until"], 1151 "timestamps": RNS.Transport.announce_rate_table[dst_hash]["timestamps"], 1152 } 1153 rate_table.append(entry) 1154 1155 return rate_table 1156 1157 def drop_path(self, destination): 1158 if self.is_connected_to_shared_instance: 1159 rpc_connection = self.get_rpc_client() 1160 rpc_connection.send({"drop": "path", "destination_hash": destination}) 1161 response = rpc_connection.recv() 1162 return response 1163 1164 else: 1165 return RNS.Transport.expire_path(destination) 1166 1167 def drop_all_via(self, transport_hash): 1168 if self.is_connected_to_shared_instance: 1169 rpc_connection = self.get_rpc_client() 1170 rpc_connection.send({"drop": "all_via", "destination_hash": transport_hash}) 1171 response = rpc_connection.recv() 1172 return response 1173 1174 else: 1175 dropped_count = 0 1176 for destination_hash in RNS.Transport.path_table: 1177 if RNS.Transport.path_table[destination_hash][1] == transport_hash: 1178 RNS.Transport.expire_path(destination_hash) 1179 dropped_count += 1 1180 1181 return dropped_count 1182 1183 def drop_announce_queues(self): 1184 if self.is_connected_to_shared_instance: 1185 rpc_connection = self.get_rpc_client() 1186 rpc_connection.send({"drop": "announce_queues"}) 1187 response = rpc_connection.recv() 1188 return response 1189 1190 else: 1191 return RNS.Transport.drop_announce_queues() 1192 1193 def get_next_hop_if_name(self, destination): 1194 if self.is_connected_to_shared_instance: 1195 rpc_connection = self.get_rpc_client() 1196 rpc_connection.send({"get": "next_hop_if_name", "destination_hash": destination}) 1197 response = rpc_connection.recv() 1198 return response 1199 1200 else: 1201 return str(RNS.Transport.next_hop_interface(destination)) 1202 1203 def get_first_hop_timeout(self, destination): 1204 if self.is_connected_to_shared_instance: 1205 try: 1206 rpc_connection = self.get_rpc_client() 1207 rpc_connection.send({"get": "first_hop_timeout", "destination_hash": destination}) 1208 response = rpc_connection.recv() 1209 1210 if self.is_connected_to_shared_instance and hasattr(self, "_force_shared_instance_bitrate") and self._force_shared_instance_bitrate: 1211 simulated_latency = ((1/self._force_shared_instance_bitrate)*8)*RNS.Reticulum.MTU 1212 RNS.log("Adding simulated latency of "+RNS.prettytime(simulated_latency)+" to first hop timeout", RNS.LOG_DEBUG) 1213 response += simulated_latency 1214 1215 return response 1216 except Exception as e: 1217 RNS.log("An error occurred while getting first hop timeout from shared instance: "+str(e), RNS.LOG_ERROR) 1218 return RNS.Reticulum.DEFAULT_PER_HOP_TIMEOUT 1219 1220 else: 1221 return RNS.Transport.first_hop_timeout(destination) 1222 1223 def get_next_hop(self, destination): 1224 if self.is_connected_to_shared_instance: 1225 rpc_connection = self.get_rpc_client() 1226 rpc_connection.send({"get": "next_hop", "destination_hash": destination}) 1227 response = rpc_connection.recv() 1228 1229 return response 1230 1231 else: 1232 return RNS.Transport.next_hop(destination) 1233 1234 def get_link_count(self): 1235 if self.is_connected_to_shared_instance: 1236 rpc_connection = self.get_rpc_client() 1237 rpc_connection.send({"get": "link_count"}) 1238 response = rpc_connection.recv() 1239 return response 1240 1241 else: 1242 return len(RNS.Transport.link_table) 1243 1244 def get_packet_rssi(self, packet_hash): 1245 if self.is_connected_to_shared_instance: 1246 rpc_connection = self.get_rpc_client() 1247 rpc_connection.send({"get": "packet_rssi", "packet_hash": packet_hash}) 1248 response = rpc_connection.recv() 1249 return response 1250 1251 else: 1252 for entry in RNS.Transport.local_client_rssi_cache: 1253 if entry[0] == packet_hash: 1254 return entry[1] 1255 1256 return None 1257 1258 def get_packet_snr(self, packet_hash): 1259 if self.is_connected_to_shared_instance: 1260 rpc_connection = self.get_rpc_client() 1261 rpc_connection.send({"get": "packet_snr", "packet_hash": packet_hash}) 1262 response = rpc_connection.recv() 1263 return response 1264 1265 else: 1266 for entry in RNS.Transport.local_client_snr_cache: 1267 if entry[0] == packet_hash: 1268 return entry[1] 1269 1270 return None 1271 1272 def get_packet_q(self, packet_hash): 1273 if self.is_connected_to_shared_instance: 1274 rpc_connection = self.get_rpc_client() 1275 rpc_connection.send({"get": "packet_q", "packet_hash": packet_hash}) 1276 response = rpc_connection.recv() 1277 return response 1278 1279 else: 1280 for entry in RNS.Transport.local_client_q_cache: 1281 if entry[0] == packet_hash: 1282 return entry[1] 1283 1284 return None 1285 1286 def halt_interface(self, interface): 1287 pass 1288 1289 def resume_interface(self, interface): 1290 pass 1291 1292 def reload_interface(self, interface): 1293 pass 1294 1295 @staticmethod 1296 def should_use_implicit_proof(): 1297 """ 1298 Returns whether proofs sent are explicit or implicit. 1299 1300 :returns: True if the current running configuration specifies to use implicit proofs. False if not. 1301 """ 1302 return Reticulum.__use_implicit_proof 1303 1304 @staticmethod 1305 def transport_enabled(): 1306 """ 1307 Returns whether Transport is enabled for the running 1308 instance. 1309 1310 When Transport is enabled, Reticulum will 1311 route traffic for other peers, respond to path requests 1312 and pass announces over the network. 1313 1314 :returns: True if Transport is enabled, False if not. 1315 """ 1316 return Reticulum.__transport_enabled 1317 1318 @staticmethod 1319 def link_mtu_discovery(): 1320 """ 1321 Returns whether link MTU discovery is enabled for the running 1322 instance. 1323 1324 When link MTU discovery is enabled, Reticulum will 1325 automatically upgrade link MTUs to the highest supported 1326 value, increasing transfer speed and efficiency. 1327 1328 :returns: True if link MTU discovery is enabled, False if not. 1329 """ 1330 return Reticulum.__link_mtu_discovery 1331 1332 @staticmethod 1333 def remote_management_enabled(): 1334 """ 1335 Returns whether remote management is enabled for the 1336 running instance. 1337 1338 When remote management is enabled, authenticated peers 1339 can remotely query and manage this instance. 1340 1341 :returns: True if remote management is enabled, False if not. 1342 """ 1343 return Reticulum.__remote_management_enabled 1344 1345 @staticmethod 1346 def probe_destination_enabled(): 1347 return Reticulum.__allow_probes 1348 1349 # Default configuration file: 1350 __default_rns_config__ = '''# This is the default Reticulum config file. 1351 # You should probably edit it to include any additional, 1352 # interfaces and settings you might need. 1353 1354 # Only the most basic options are included in this default 1355 # configuration. To see a more verbose, and much longer, 1356 # configuration example, you can run the command: 1357 # rnsd --exampleconfig 1358 1359 1360 [reticulum] 1361 1362 # If you enable Transport, your system will route traffic 1363 # for other peers, pass announces and serve path requests. 1364 # This should only be done for systems that are suited to 1365 # act as transport nodes, ie. if they are stationary and 1366 # always-on. This directive is optional and can be removed 1367 # for brevity. 1368 1369 enable_transport = False 1370 1371 1372 # By default, the first program to launch the Reticulum 1373 # Network Stack will create a shared instance, that other 1374 # programs can communicate with. Only the shared instance 1375 # opens all the configured interfaces directly, and other 1376 # local programs communicate with the shared instance over 1377 # a local socket. This is completely transparent to the 1378 # user, and should generally be turned on. This directive 1379 # is optional and can be removed for brevity. 1380 1381 share_instance = Yes 1382 1383 1384 # If you want to run multiple *different* shared instances 1385 # on the same system, you will need to specify different 1386 # instance names for each. On platforms supporting domain 1387 # sockets, this can be done with the instance_name option: 1388 1389 instance_name = default 1390 1391 # Some platforms don't support domain sockets, and if that 1392 # is the case, you can isolate different instances by 1393 # specifying a unique set of ports for each: 1394 1395 # shared_instance_port = 37428 1396 # instance_control_port = 37429 1397 1398 1399 # If you want to explicitly use TCP for shared instance 1400 # communication, instead of domain sockets, this is also 1401 # possible, by using the following option: 1402 1403 # shared_instance_type = tcp 1404 1405 1406 # You can configure Reticulum to panic and forcibly close 1407 # if an unrecoverable interface error occurs, such as the 1408 # hardware device for an interface disappearing. This is 1409 # an optional directive, and can be left out for brevity. 1410 # This behaviour is disabled by default. 1411 1412 # panic_on_interface_error = No 1413 1414 1415 [logging] 1416 # Valid log levels are 0 through 7: 1417 # 0: Log only critical information 1418 # 1: Log errors and lower log levels 1419 # 2: Log warnings and lower log levels 1420 # 3: Log notices and lower log levels 1421 # 4: Log info and lower (this is the default) 1422 # 5: Verbose logging 1423 # 6: Debug logging 1424 # 7: Extreme logging 1425 1426 loglevel = 4 1427 1428 1429 # The interfaces section defines the physical and virtual 1430 # interfaces Reticulum will use to communicate on. This 1431 # section will contain examples for a variety of interface 1432 # types. You can modify these or use them as a basis for 1433 # your own config, or simply remove the unused ones. 1434 1435 [interfaces] 1436 1437 # This interface enables communication with other 1438 # link-local Reticulum nodes over UDP. It does not 1439 # need any functional IP infrastructure like routers 1440 # or DHCP servers, but will require that at least link- 1441 # local IPv6 is enabled in your operating system, which 1442 # should be enabled by default in almost any OS. See 1443 # the Reticulum Manual for more configuration options. 1444 1445 [[Default Interface]] 1446 type = AutoInterface 1447 enabled = Yes 1448 1449 '''.splitlines()