/ RNS / Reticulum.py
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()