/ 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 import WeaveInterface
  43      from .Interfaces.Android import RNodeInterface
  44      from .Interfaces.Android import SerialInterface
  45      from .Interfaces.Android import KISSInterface
  46  else:
  47      from RNS.Interfaces import *
  48  
  49  from RNS.vendor.configobj import ConfigObj
  50  import configparser
  51  import multiprocessing.connection
  52  import importlib.util
  53  import threading
  54  import signal
  55  import atexit
  56  import struct
  57  import array
  58  import time
  59  import os
  60  import RNS
  61  
  62  class Reticulum:
  63      """
  64      This class is used to initialise access to Reticulum within a
  65      program. You must create exactly one instance of this class before
  66      carrying out any other RNS operations, such as creating destinations
  67      or sending traffic. Every independently executed program must create
  68      their own instance of the Reticulum class, but Reticulum will
  69      automatically handle inter-program communication on the same system,
  70      and expose all connected programs to external interfaces as well.
  71  
  72      As soon as an instance of this class is created, Reticulum will start
  73      opening and configuring any hardware devices specified in the supplied
  74      configuration.
  75  
  76      Currently the first running instance must be kept running while other
  77      local instances are connected, as the first created instance will
  78      act as a master instance that directly communicates with external
  79      hardware such as modems, TNCs and radios. If a master instance is
  80      asked to exit, it will not exit until all client processes have
  81      terminated (unless killed forcibly).
  82  
  83      If you are running Reticulum on a system with several different
  84      programs that use RNS starting and terminating at different times,
  85      it will be advantageous to run a master RNS instance as a daemon for
  86      other programs to use on demand.
  87      """
  88  
  89      # Future minimum will probably be locked in at 251 bytes to support
  90      # networks with segments of different MTUs. Absolute minimum is 219.
  91      MTU            = 500
  92      """
  93      The MTU that Reticulum adheres to, and will expect other peers to
  94      adhere to. By default, the MTU is 500 bytes. In custom RNS network
  95      implementations, it is possible to change this value, but doing so will
  96      completely break compatibility with all other RNS networks. An identical
  97      MTU is a prerequisite for peers to communicate in the same network.
  98  
  99      Unless you really know what you are doing, the MTU should be left at
 100      the default value.
 101      """
 102  
 103      LINK_MTU_DISCOVERY   = True
 104      """
 105      Whether automatic link MTU discovery is enabled by default in this
 106      release. Link MTU discovery significantly increases throughput over
 107      fast links, but requires all intermediary hops to also support it.
 108      Support for this feature was added in RNS version 0.9.0. This option
 109      will become enabled by default in the near future. Please update your
 110      RNS instances.
 111      """
 112  
 113      MAX_QUEUED_ANNOUNCES = 16384
 114      QUEUED_ANNOUNCE_LIFE = 60*60*24
 115  
 116      ANNOUNCE_CAP = 2
 117      """
 118      The maximum percentage of interface bandwidth that, at any given time,
 119      may be used to propagate announces. If an announce was scheduled for
 120      broadcasting on an interface, but doing so would exceed the allowed
 121      bandwidth allocation, the announce will be queued for transmission
 122      when there is bandwidth available.
 123  
 124      Reticulum will always prioritise propagating announces with fewer
 125      hops, ensuring that distant, large networks with many peers on fast
 126      links don't overwhelm the capacity of smaller networks on slower
 127      mediums. If an announce remains queued for an extended amount of time,
 128      it will eventually be dropped.
 129  
 130      This value will be applied by default to all created interfaces,
 131      but it can be configured individually on a per-interface basis. In
 132      general, the global default setting should not be changed, and any
 133      alterations should be made on a per-interface basis instead.
 134      """
 135  
 136      MINIMUM_BITRATE = 5
 137      """
 138      Minimum bitrate required across a medium for Reticulum to be able
 139      to successfully establish links. Currently 5 bits per second.
 140      """
 141  
 142      # TODO: Let Reticulum somehow continously build a map of per-hop
 143      # latencies and use this map for global timeout calculation.
 144      DEFAULT_PER_HOP_TIMEOUT = 6
 145  
 146      # Length of truncated hashes in bits.
 147      TRUNCATED_HASHLENGTH = 128
 148  
 149      HEADER_MINSIZE   = 2+1+(TRUNCATED_HASHLENGTH//8)*1
 150      HEADER_MAXSIZE   = 2+1+(TRUNCATED_HASHLENGTH//8)*2
 151      IFAC_MIN_SIZE    = 1
 152      IFAC_SALT        = bytes.fromhex("adf54d882c9a9b80771eb4995d702d4a3e733391b2a0f53f416d9f907e55cff8")
 153      
 154      MDU              = MTU - HEADER_MAXSIZE - IFAC_MIN_SIZE
 155  
 156      RESOURCE_CACHE   = 24*60*60
 157      JOB_INTERVAL     = 5*60
 158      CLEAN_INTERVAL   = 15*60
 159      PERSIST_INTERVAL = 60*60*12
 160      GRACIOUS_PERSIST_INTERVAL = 60*5
 161  
 162      router           = None
 163      config           = None
 164      
 165      # The default configuration path will be expanded to a directory
 166      # named ".reticulum" inside the current users home directory
 167      userdir          = os.path.expanduser("~")
 168      configdir        = None
 169      configpath       = ""
 170      storagepath      = ""
 171      cachepath        = ""
 172      interfacepath    = ""
 173  
 174      __instance       = None
 175  
 176      __interface_detach_ran = False
 177      __exit_handler_ran = False
 178      @staticmethod
 179      def exit_handler():
 180          # This exit handler is called whenever Reticulum is asked to
 181          # shut down, and will in turn call exit handlers in other
 182          # classes, saving necessary information to disk and carrying
 183          # out cleanup operations.
 184          if not Reticulum.__exit_handler_ran:
 185              Reticulum.__exit_handler_ran = True
 186              if not Reticulum.__interface_detach_ran:
 187                  RNS.Transport.detach_interfaces()
 188              RNS.Transport.exit_handler()
 189              RNS.Identity.exit_handler()
 190  
 191              if RNS.Profiler.ran():
 192                  RNS.Profiler.results()
 193  
 194              RNS.loglevel = -1
 195  
 196      @staticmethod
 197      def sigint_handler(signal, frame):
 198          RNS.Transport.detach_interfaces()
 199          Reticulum.__interface_detach_ran = True
 200          RNS.exit()
 201  
 202      @staticmethod
 203      def sigterm_handler(signal, frame):
 204          RNS.Transport.detach_interfaces()
 205          Reticulum.__interface_detach_ran = True
 206          RNS.exit()
 207  
 208      @staticmethod
 209      def get_instance():
 210          """
 211          Return the currently running Reticulum instance
 212          """
 213          return Reticulum.__instance
 214  
 215      def __init__(self,configdir=None, loglevel=None, logdest=None, verbosity=None,
 216                   require_shared_instance=False, shared_instance_type=None):
 217          """
 218          Initialises and starts a Reticulum instance. This must be
 219          done before any other operations, and Reticulum will not
 220          pass any traffic before being instantiated.
 221  
 222          :param configdir: Full path to a Reticulum configuration directory.
 223          """
 224  
 225          if Reticulum.__instance != None: raise OSError("Attempt to reinitialise Reticulum, when it was already running")
 226          else: Reticulum.__instance = self
 227  
 228          RNS.vendor.platformutils.platform_checks()
 229  
 230          if configdir != None: Reticulum.configdir = configdir
 231          else:
 232              if os.path.isdir("/etc/reticulum") and os.path.isfile("/etc/reticulum/config"):
 233                  Reticulum.configdir = "/etc/reticulum"
 234              elif os.path.isdir(Reticulum.userdir+"/.config/reticulum") and os.path.isfile(Reticulum.userdir+"/.config/reticulum/config"):
 235                  Reticulum.configdir = Reticulum.userdir+"/.config/reticulum"
 236              else:
 237                  Reticulum.configdir = Reticulum.userdir+"/.reticulum"
 238  
 239          if logdest == RNS.LOG_FILE:
 240              RNS.logdest = RNS.LOG_FILE
 241              RNS.logfile = Reticulum.configdir+"/logfile"
 242          elif callable(logdest):
 243              RNS.logdest = RNS.LOG_CALLBACK
 244              RNS.logcall = logdest
 245          
 246          Reticulum.configpath    = Reticulum.configdir+"/config"
 247          Reticulum.storagepath   = Reticulum.configdir+"/storage"
 248          Reticulum.cachepath     = Reticulum.configdir+"/storage/cache"
 249          Reticulum.resourcepath  = Reticulum.configdir+"/storage/resources"
 250          Reticulum.identitypath  = Reticulum.configdir+"/storage/identities"
 251          Reticulum.blackholepath = Reticulum.configdir+"/storage/blackhole"
 252          Reticulum.interfacepath = Reticulum.configdir+"/interfaces"
 253  
 254          Reticulum.__network_identity = None
 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          Reticulum.__discovery_enabled = False
 261          Reticulum.__discover_interfaces = False
 262          Reticulum.__autoconnect_discovered_interfaces = False
 263          Reticulum.__required_discovery_value = None
 264          Reticulum.__publish_blackhole = False
 265          Reticulum.__blackhole_sources = []
 266          Reticulum.__interface_sources = []
 267  
 268          Reticulum.panic_on_interface_error = False
 269  
 270          self.local_interface_port = 37428
 271          self.local_control_port   = 37429
 272          self.local_socket_path    = None
 273          self.share_instance       = True
 274          self.shared_instance_type = shared_instance_type
 275          self.rpc_listener         = None
 276          self.rpc_key              = None
 277          self.rpc_type             = "AF_INET"
 278          self.use_af_unix          = False
 279          self.bootstrap_configs    = []
 280  
 281          self.ifac_salt = Reticulum.IFAC_SALT
 282  
 283          self.requested_loglevel = loglevel
 284          self.requested_verbosity = verbosity
 285          if self.requested_loglevel != None:
 286              if self.requested_loglevel > RNS.LOG_EXTREME:  self.requested_loglevel = RNS.LOG_EXTREME
 287              if self.requested_loglevel < RNS.LOG_CRITICAL: self.requested_loglevel = RNS.LOG_CRITICAL
 288  
 289              RNS.loglevel = self.requested_loglevel
 290  
 291          self.is_shared_instance = False
 292          self.shared_instance_interface = None
 293          self.require_shared = require_shared_instance
 294          self.is_connected_to_shared_instance = False
 295          self.is_standalone_instance = False
 296          self.jobs_thread = None
 297          self.last_data_persist = time.time()
 298          self.last_cache_clean = 0
 299  
 300          if not os.path.isdir(Reticulum.storagepath):   os.makedirs(Reticulum.storagepath)
 301          if not os.path.isdir(Reticulum.cachepath):     os.makedirs(Reticulum.cachepath)
 302          if not os.path.isdir(Reticulum.resourcepath):  os.makedirs(Reticulum.resourcepath)
 303          if not os.path.isdir(Reticulum.identitypath):  os.makedirs(Reticulum.identitypath)
 304          if not os.path.isdir(Reticulum.blackholepath): os.makedirs(Reticulum.blackholepath)
 305          if not os.path.isdir(Reticulum.interfacepath): os.makedirs(Reticulum.interfacepath)
 306          if not os.path.isdir(os.path.join(Reticulum.cachepath, "announces")): os.makedirs(os.path.join(Reticulum.cachepath, "announces"))
 307  
 308          if os.path.isfile(self.configpath):
 309              try: self.config = ConfigObj(self.configpath)
 310              except Exception as e:
 311                  RNS.log("Could not parse the configuration at "+self.configpath, RNS.LOG_ERROR)
 312                  RNS.log("Check your configuration file for errors!", RNS.LOG_ERROR)
 313                  RNS.panic()
 314          else:
 315              RNS.log("Could not load config file, creating default configuration file...")
 316              self.__create_default_config()
 317              RNS.log("Default config file created. Make any necessary changes in "+Reticulum.configdir+"/config and restart Reticulum if needed.")
 318              time.sleep(1.5)
 319  
 320          self.__apply_config()
 321          RNS.log(f"Utilising cryptography backend \"{RNS.Cryptography.Provider.backend()}\"", RNS.LOG_DEBUG)
 322          RNS.log(f"Configuration loaded from {self.configpath}", RNS.LOG_VERBOSE)
 323  
 324          RNS.Identity.load_known_destinations()
 325          RNS.Transport.start(self)
 326  
 327          if self.use_af_unix:
 328              self.rpc_addr = f"\0rns/{self.local_socket_path}/rpc"
 329              self.rpc_type = "AF_UNIX"
 330          else:
 331              self.rpc_addr = ("127.0.0.1", self.local_control_port)
 332              self.rpc_type = "AF_INET"
 333  
 334          if self.rpc_key == None:
 335              self.rpc_key  = RNS.Identity.full_hash(RNS.Transport.identity.get_private_key())
 336          
 337          if self.is_shared_instance:
 338              self.rpc_listener = multiprocessing.connection.Listener(self.rpc_addr, family=self.rpc_type, authkey=self.rpc_key)
 339              thread = threading.Thread(target=self.rpc_loop)
 340              thread.daemon = True
 341              thread.start()
 342  
 343          if self.is_shared_instance or self.is_standalone_instance:
 344              if Reticulum.__discovery_enabled:   RNS.Transport.enable_discovery()
 345              if Reticulum.__discover_interfaces: RNS.Transport.discover_interfaces()
 346              if Reticulum.__blackhole_sources:   RNS.Transport.enable_blackhole_updater()
 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                  
 454                  if option == "share_instance":
 455                      value = self.config["reticulum"].as_bool(option)
 456                      self.share_instance = value
 457                  
 458                  if RNS.vendor.platformutils.use_af_unix():
 459                      if option == "instance_name":
 460                          value = self.config["reticulum"][option]
 461                          self.local_socket_path = value
 462                  
 463                  if option == "shared_instance_type":
 464                      if self.shared_instance_type == None:
 465                          value = self.config["reticulum"][option].lower()
 466                          if value in ["tcp", "unix"]:
 467                              self.shared_instance_type = value
 468                  
 469                  if option == "shared_instance_port":
 470                      value = int(self.config["reticulum"][option])
 471                      self.local_interface_port = value
 472                  
 473                  if option == "instance_control_port":
 474                      value = int(self.config["reticulum"][option])
 475                      self.local_control_port = value
 476                  
 477                  if option == "rpc_key":
 478                      try:
 479                          value = bytes.fromhex(self.config["reticulum"][option])
 480                          self.rpc_key = value
 481                      except Exception as e:
 482                          RNS.log("Invalid shared instance RPC key specified, falling back to default key", RNS.LOG_ERROR)
 483                          self.rpc_key = None
 484                  
 485                  if option == "enable_transport":
 486                      v = self.config["reticulum"].as_bool(option)
 487                      if v == True: Reticulum.__transport_enabled = True
 488                  
 489                  if option == "network_identity":
 490                      if Reticulum.__network_identity == None:
 491                          path = self.config["reticulum"][option]
 492                          identitypath = os.path.expanduser(path)
 493                          try:
 494                              network_identity = None
 495                              if not os.path.isfile(identitypath):
 496                                  network_identity = RNS.Identity()
 497                                  network_identity.to_file(identitypath)
 498                                  RNS.log(f"Network identity generated and persisted to {identitypath}", RNS.LOG_VERBOSE)
 499                              
 500                              else:
 501                                  network_identity = RNS.Identity.from_file(identitypath)
 502                                  RNS.log(f"Network identity loaded from {identitypath}", RNS.LOG_VERBOSE)
 503  
 504                              if network_identity:
 505                                  Reticulum.__network_identity = network_identity
 506                                  RNS.Transport.set_network_identity(Reticulum.__network_identity)
 507  
 508                              else: raise ValueError("Network identity initialisation failed")
 509  
 510                          except Exception as e: raise ValueError(f"Could not set network identity from {path}: {e}")
 511                  
 512                  if option == "link_mtu_discovery":
 513                      v = self.config["reticulum"].as_bool(option)
 514                      if v == True: Reticulum.__link_mtu_discovery = True
 515                  
 516                  if option == "enable_remote_management":
 517                      v = self.config["reticulum"].as_bool(option)
 518                      if v == True: Reticulum.__remote_management_enabled = True
 519                  
 520                  if option == "remote_management_allowed":
 521                      v = self.config["reticulum"].as_list(option)
 522                      for hexhash in v:
 523                          dest_len = (RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2
 524                          if len(hexhash) != dest_len: 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))
 525                          try: allowed_hash = bytes.fromhex(hexhash)
 526                          except Exception as e: raise ValueError("Invalid identity hash for remote management ACL: "+str(hexhash))
 527  
 528                          if not allowed_hash in RNS.Transport.remote_management_allowed:
 529                              RNS.Transport.remote_management_allowed.append(allowed_hash)
 530                  
 531                  if option == "respond_to_probes":
 532                      v = self.config["reticulum"].as_bool(option)
 533                      if v == True: Reticulum.__allow_probes = True
 534                  
 535                  if option == "force_shared_instance_bitrate":
 536                      v = self.config["reticulum"].as_int(option)
 537                      Reticulum._force_shared_instance_bitrate = v
 538                  
 539                  if option == "panic_on_interface_error":
 540                      v = self.config["reticulum"].as_bool(option)
 541                      if v == True: Reticulum.panic_on_interface_error = True
 542                  
 543                  if option == "use_implicit_proof":
 544                      v = self.config["reticulum"].as_bool(option)
 545                      if v == True:  Reticulum.__use_implicit_proof = True
 546                      if v == False: Reticulum.__use_implicit_proof = False
 547                  
 548                  if option == "discover_interfaces":
 549                      v = self.config["reticulum"].as_bool(option)
 550                      if v == True:  Reticulum.__discover_interfaces = True
 551                      if v == False: Reticulum.__discover_interfaces = False
 552                  
 553                  if option == "required_discovery_value":
 554                      v = self.config["reticulum"].as_int(option)
 555                      if v > 0: Reticulum.__required_discovery_value = v
 556                      else:     Reticulum.__required_discovery_value = None
 557                  
 558                  if option == "publish_blackhole":
 559                      v = self.config["reticulum"].as_bool(option)
 560                      if v == True:  Reticulum.__publish_blackhole = True
 561                      if v == False: Reticulum.__publish_blackhole = False
 562                  
 563                  if option == "blackhole_sources":
 564                      v = self.config["reticulum"].as_list(option)
 565                      for hexhash in v:
 566                          dest_len = (RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2
 567                          if len(hexhash) != dest_len: raise ValueError(f"Identity hash length for blackhole source {hexhash} is invalid, must be {dest_len} hexadecimal characters ({dest_len//2} bytes).")
 568                          try: source_identity_hash = bytes.fromhex(hexhash)
 569                          except Exception as e: raise ValueError(f"Invalid identity hash for remote blackhole source: {hexhash}")
 570                          if not source_identity_hash in Reticulum.__blackhole_sources: Reticulum.__blackhole_sources.append(source_identity_hash)
 571                  
 572                  if option == "interface_discovery_sources":
 573                      v = self.config["reticulum"].as_list(option)
 574                      for hexhash in v:
 575                          dest_len = (RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2
 576                          if len(hexhash) != dest_len: raise ValueError(f"Identity hash length for interface discovery source {hexhash} is invalid, must be {dest_len} hexadecimal characters ({dest_len//2} bytes).")
 577                          try: source_identity_hash = bytes.fromhex(hexhash)
 578                          except Exception as e: raise ValueError(f"Invalid identity hash for interface discovery source: {hexhash}")
 579                          if not source_identity_hash in Reticulum.__interface_sources: Reticulum.__interface_sources.append(source_identity_hash)
 580                  
 581                  if option == "autoconnect_discovered_interfaces":
 582                      v = self.config["reticulum"].as_int(option)
 583                      if v > 0: Reticulum.__autoconnect_discovered_interfaces = v
 584  
 585          if RNS.compiled: RNS.log("Reticulum running in compiled mode", RNS.LOG_DEBUG)
 586          else: RNS.log("Reticulum running in interpreted mode", RNS.LOG_DEBUG)
 587  
 588          if RNS.vendor.platformutils.use_af_unix():
 589              if self.shared_instance_type == "tcp": self.use_af_unix = False
 590              else:                                  self.use_af_unix = True
 591          else:
 592              self.shared_instance_type = "tcp"
 593              self.use_af_unix          = False
 594  
 595          if self.local_socket_path == None and self.use_af_unix:
 596              self.local_socket_path = "default"
 597  
 598          self.__start_local_interface()
 599  
 600          if self.is_shared_instance or self.is_standalone_instance:
 601              RNS.log("Bringing up system interfaces...", RNS.LOG_VERBOSE)
 602              interface_names = []
 603              if "interfaces" in self.config:
 604                  for name in self.config["interfaces"]:
 605                      if not name in interface_names:
 606                          c = self.config["interfaces"][name]
 607                          self._synthesize_interface(c, name, instance_init=True)
 608  
 609                      else:
 610                          RNS.log("The interface name \""+name+"\" was already used. Check your configuration file for errors!", RNS.LOG_ERROR)
 611                          RNS.panic()
 612  
 613              RNS.log("System interfaces are ready", RNS.LOG_VERBOSE)
 614  
 615      def _synthesize_interface(self, config, name, instance_init=False):
 616          c = config
 617          interface_mode = Interface.Interface.MODE_FULL
 618          
 619          if "interface_mode" in c:
 620              c["interface_mode"] = str(c["interface_mode"]).lower()
 621              if c["interface_mode"] == "full":
 622                  interface_mode = Interface.Interface.MODE_FULL
 623              elif c["interface_mode"] == "access_point" or c["interface_mode"] == "accesspoint" or c["interface_mode"] == "ap":
 624                  interface_mode = Interface.Interface.MODE_ACCESS_POINT
 625              elif c["interface_mode"] == "pointtopoint" or c["interface_mode"] == "ptp":
 626                  interface_mode = Interface.Interface.MODE_POINT_TO_POINT
 627              elif c["interface_mode"] == "roaming":
 628                  interface_mode = Interface.Interface.MODE_ROAMING
 629              elif c["interface_mode"] == "boundary":
 630                  interface_mode = Interface.Interface.MODE_BOUNDARY
 631              elif c["mode"] == "gateway" or c["mode"] == "gw":
 632                  interface_mode = Interface.Interface.MODE_GATEWAY
 633  
 634          elif "mode" in c:
 635              c["mode"] = str(c["mode"]).lower()
 636              if c["mode"] == "full":
 637                  interface_mode = Interface.Interface.MODE_FULL
 638              elif c["mode"] == "access_point" or c["mode"] == "accesspoint" or c["mode"] == "ap":
 639                  interface_mode = Interface.Interface.MODE_ACCESS_POINT
 640              elif c["mode"] == "pointtopoint" or c["mode"] == "ptp":
 641                  interface_mode = Interface.Interface.MODE_POINT_TO_POINT
 642              elif c["mode"] == "roaming":
 643                  interface_mode = Interface.Interface.MODE_ROAMING
 644              elif c["mode"] == "boundary":
 645                  interface_mode = Interface.Interface.MODE_BOUNDARY
 646              elif c["mode"] == "gateway" or c["mode"] == "gw":
 647                  interface_mode = Interface.Interface.MODE_GATEWAY
 648  
 649          ifac_size = None
 650          if "ifac_size" in c:
 651              if c.as_int("ifac_size") >= Reticulum.IFAC_MIN_SIZE*8:
 652                  ifac_size = c.as_int("ifac_size")//8
 653                  
 654          ifac_netname = None
 655          if "networkname" in c:
 656              if c["networkname"] != "":
 657                  ifac_netname = c["networkname"]
 658          if "network_name" in c:
 659              if c["network_name"] != "":
 660                  ifac_netname = c["network_name"]
 661  
 662          ifac_netkey = None
 663          if "passphrase" in c:
 664              if c["passphrase"] != "":
 665                  ifac_netkey = c["passphrase"]
 666          if "pass_phrase" in c:
 667              if c["pass_phrase"] != "":
 668                  ifac_netkey = c["pass_phrase"]
 669                  
 670          ingress_control = True
 671          if "ingress_control" in c: ingress_control = c.as_bool("ingress_control")
 672          ic_max_held_announces = None
 673          if "ic_max_held_announces" in c: ic_max_held_announces = c.as_int("ic_max_held_announces")
 674          ic_burst_hold = None
 675          if "ic_burst_hold" in c: ic_burst_hold = c.as_float("ic_burst_hold")
 676          ic_burst_freq_new = None
 677          if "ic_burst_freq_new" in c: ic_burst_freq_new = c.as_float("ic_burst_freq_new")
 678          ic_burst_freq = None
 679          if "ic_burst_freq" in c: ic_burst_freq = c.as_float("ic_burst_freq")
 680          ic_new_time = None
 681          if "ic_new_time" in c: ic_new_time = c.as_float("ic_new_time")
 682          ic_burst_penalty = None
 683          if "ic_burst_penalty" in c: ic_burst_penalty = c.as_float("ic_burst_penalty")
 684          ic_held_release_interval = None
 685          if "ic_held_release_interval" in c: ic_held_release_interval = c.as_float("ic_held_release_interval")
 686  
 687          configured_bitrate = None
 688          if "bitrate" in c:
 689              if c.as_int("bitrate") >= Reticulum.MINIMUM_BITRATE:
 690                  configured_bitrate = c.as_int("bitrate")
 691  
 692          announce_rate_target = None
 693          if "announce_rate_target" in c:
 694              if c.as_int("announce_rate_target") > 0:
 695                  announce_rate_target = c.as_int("announce_rate_target")
 696                  
 697          announce_rate_grace = None
 698          if "announce_rate_grace" in c:
 699              if c.as_int("announce_rate_grace") >= 0:
 700                  announce_rate_grace = c.as_int("announce_rate_grace")
 701                  
 702          announce_rate_penalty = None
 703          if "announce_rate_penalty" in c:
 704              if c.as_int("announce_rate_penalty") >= 0:
 705                  announce_rate_penalty = c.as_int("announce_rate_penalty")
 706  
 707          if announce_rate_target != None and announce_rate_grace == None:
 708              announce_rate_grace = 0
 709  
 710          if announce_rate_target != None and announce_rate_penalty == None:
 711              announce_rate_penalty = 0
 712  
 713          announce_cap = Reticulum.ANNOUNCE_CAP/100.0
 714          if "announce_cap" in c:
 715              if c.as_float("announce_cap") > 0 and c.as_float("announce_cap") <= 100:
 716                  announce_cap = c.as_float("announce_cap")/100.0
 717  
 718          bootstrap_only = False
 719          if "bootstrap_only" in c: bootstrap_only = c.as_bool("bootstrap_only")
 720  
 721          ignore_config_warnings = False
 722          if "ignore_config_warnings" in c: ignore_config_warnings = c.as_bool("ignore_config_warnings")
 723  
 724          discoverable = False
 725          discovery_announce_interval = None
 726          discovery_stamp_value = None
 727          discovery_name = None
 728          discovery_encrypt = False
 729          reachable_on = None
 730          publish_ifac = False
 731          latitude = None
 732          longitude = None
 733          height = None
 734          discovery_frequency = None
 735          discovery_bandwidth = None
 736          discovery_modulation = None
 737          if "discoverable" in c:
 738              discoverable = c.as_bool("discoverable")
 739              if discoverable:
 740                  Reticulum.__discovery_enabled = True
 741                  if "announce_interval" in c:
 742                      discovery_announce_interval = c.as_int("announce_interval")*60
 743                      if discovery_announce_interval < 5*60: discovery_announce_interval = 5*60
 744  
 745                  if discovery_announce_interval == None: discovery_announce_interval = 6*60*60
 746                  if "discovery_stamp_value" in c: discovery_stamp_value = c.as_int("discovery_stamp_value")
 747                  if "discovery_name" in c: discovery_name = c["discovery_name"]
 748                  if "discovery_encrypt" in c: discovery_encrypt = c.as_bool("discovery_encrypt")
 749                  if "reachable_on" in c: reachable_on = c["reachable_on"]
 750                  if "publish_ifac" in c: publish_ifac = c.as_bool("publish_ifac")
 751                  if "latitude" in c: latitude = c.as_float("latitude")
 752                  if "longitude" in c: longitude = c.as_float("longitude")
 753                  if "height" in c: height = c.as_float("height")
 754                  if "discovery_frequency" in c: discovery_frequency = c.as_int("discovery_frequency")
 755                  if "discovery_bandwidth" in c: discovery_bandwidth = c.as_int("discovery_bandwidth")
 756                  if "discovery_modulation" in c: discovery_modulation = c.as_int("discovery_modulation")
 757  
 758                  if not interface_mode in [Interface.Interface.MODE_GATEWAY, Interface.Interface.MODE_ACCESS_POINT]:
 759                      if not ignore_config_warnings:
 760                          if c["type"] in ["RNodeInterface", "RNodeMultiInterface"]:
 761                              interface_mode = Interface.Interface.MODE_ACCESS_POINT
 762                              RNS.log(f"Discovery enabled on interface {name} without gateway or AP mode. Auto-configured to AP mode.", RNS.LOG_NOTICE)
 763                          else:
 764                              interface_mode = Interface.Interface.MODE_GATEWAY
 765                              RNS.log(f"Discovery enabled on interface {name} without gateway or AP mode. Auto-configured to gateway mode.", RNS.LOG_NOTICE)
 766                  
 767          try:
 768              def interface_post_init(interface):
 769                  if interface != None:
 770                      if "outgoing" in c and c.as_bool("outgoing") == False: interface.OUT = False
 771                      else:                                                  interface.OUT = True
 772  
 773                      interface.mode = interface_mode
 774                      interface.announce_cap = announce_cap
 775                      interface.bootstrap_only = bootstrap_only
 776                      if configured_bitrate: interface.bitrate = configured_bitrate
 777                      interface.optimise_mtu()
 778                      
 779                      if ifac_size != None: interface.ifac_size = ifac_size
 780                      else:                 interface.ifac_size = interface.DEFAULT_IFAC_SIZE
 781  
 782                      interface.discoverable = discoverable
 783                      interface.discovery_announce_interval = discovery_announce_interval
 784                      interface.discovery_publish_ifac = publish_ifac
 785                      interface.reachable_on = reachable_on
 786                      interface.discovery_name = discovery_name
 787                      interface.discovery_encrypt = discovery_encrypt
 788                      interface.discovery_stamp_value = discovery_stamp_value
 789                      interface.discovery_latitude = latitude
 790                      interface.discovery_longitude = longitude
 791                      interface.discovery_height = height
 792                      interface.discovery_frequency = discovery_frequency
 793                      interface.discovery_bandwidth = discovery_bandwidth
 794                      interface.discovery_modulation = discovery_modulation
 795  
 796                      interface.announce_rate_target = announce_rate_target
 797                      interface.announce_rate_grace = announce_rate_grace
 798                      interface.announce_rate_penalty = announce_rate_penalty
 799                      interface.ingress_control = ingress_control
 800                      if ic_max_held_announces != None: interface.ic_max_held_announces = ic_max_held_announces
 801                      if ic_burst_hold != None: interface.ic_burst_hold = ic_burst_hold
 802                      if ic_burst_freq_new != None: interface.ic_burst_freq_new = ic_burst_freq_new
 803                      if ic_burst_freq != None: interface.ic_burst_freq = ic_burst_freq
 804                      if ic_new_time != None: interface.ic_new_time = ic_new_time
 805                      if ic_burst_penalty != None: interface.ic_burst_penalty = ic_burst_penalty
 806                      if ic_held_release_interval != None: interface.ic_held_release_interval = ic_held_release_interval
 807  
 808                      interface.ifac_netname = ifac_netname
 809                      interface.ifac_netkey = ifac_netkey
 810  
 811                      if interface.ifac_netname != None or interface.ifac_netkey != None:
 812                          ifac_origin = b""
 813  
 814                          if interface.ifac_netname != None:
 815                              ifac_origin += RNS.Identity.full_hash(interface.ifac_netname.encode("utf-8"))
 816  
 817                          if interface.ifac_netkey != None:
 818                              ifac_origin += RNS.Identity.full_hash(interface.ifac_netkey.encode("utf-8"))
 819  
 820                          ifac_origin_hash = RNS.Identity.full_hash(ifac_origin)
 821                          interface.ifac_key = RNS.Cryptography.hkdf(
 822                              length=64,
 823                              derive_from=ifac_origin_hash,
 824                              salt=self.ifac_salt,
 825                              context=None
 826                          )
 827  
 828                          interface.ifac_identity = RNS.Identity.from_bytes(interface.ifac_key)
 829                          interface.ifac_signature = interface.ifac_identity.sign(RNS.Identity.full_hash(interface.ifac_key))
 830  
 831                      RNS.Transport.interfaces.append(interface)
 832                      interface.final_init()
 833  
 834              interface = None
 835              if (("interface_enabled" in c) and c.as_bool("interface_enabled") == True) or (("enabled" in c) and c.as_bool("enabled") == True):
 836                  interface_config = c
 837                  interface_config["name"] = name
 838                  interface_config["selected_interface_mode"] = interface_mode
 839                  interface_config["configured_bitrate"] = configured_bitrate
 840  
 841                  if c["type"] == "AutoInterface":
 842                      interface = AutoInterface.AutoInterface(RNS.Transport, interface_config)
 843                      interface_post_init(interface)
 844  
 845                  if c["type"] == "BackboneInterface" or c["type"] == "BackboneClientInterface":
 846                      if "port" in c: c["listen_port"] = c["port"]
 847                      if "port" in c: c["target_port"] = c["port"]
 848                      if "remote" in c: c["target_host"] = c["remote"]
 849                      if "listen_on" in c: c["listen_ip"] = c["listen_on"]
 850  
 851                  if c["type"] == "BackboneInterface":
 852                      if "target_host" in c: interface = BackboneInterface.BackboneClientInterface(RNS.Transport, interface_config)
 853                      else: interface = BackboneInterface.BackboneInterface(RNS.Transport, interface_config)
 854                      interface_post_init(interface)
 855  
 856                  if c["type"] == "BackboneClientInterface":
 857                      interface = BackboneInterface.BackboneClientInterface(RNS.Transport, interface_config)
 858                      interface_post_init(interface)
 859  
 860                  if c["type"] == "UDPInterface":
 861                      interface = UDPInterface.UDPInterface(RNS.Transport, interface_config)
 862                      interface_post_init(interface)
 863  
 864                  if c["type"] == "TCPServerInterface":
 865                      interface = TCPInterface.TCPServerInterface(RNS.Transport, interface_config)
 866                      interface_post_init(interface)
 867  
 868                  if c["type"] == "TCPClientInterface":
 869                      interface = TCPInterface.TCPClientInterface(RNS.Transport, interface_config)
 870                      interface_post_init(interface)
 871  
 872                  if c["type"] == "I2PInterface":
 873                      interface_config["storagepath"] = Reticulum.storagepath
 874                      interface_config["ifac_netname"] = ifac_netname
 875                      interface_config["ifac_netkey"] = ifac_netkey
 876                      interface_config["ifac_size"] = ifac_size
 877  
 878                      interface = I2PInterface.I2PInterface(RNS.Transport, interface_config)
 879                      interface_post_init(interface)
 880  
 881                  if c["type"] == "SerialInterface":
 882                      interface = SerialInterface.SerialInterface(RNS.Transport, interface_config)
 883                      interface_post_init(interface)
 884  
 885                  if c["type"] == "PipeInterface":
 886                      interface = PipeInterface.PipeInterface(RNS.Transport, interface_config)
 887                      interface_post_init(interface)
 888  
 889                  if c["type"] == "KISSInterface":
 890                      interface = KISSInterface.KISSInterface(RNS.Transport, interface_config)
 891                      interface_post_init(interface)
 892  
 893                  if c["type"] == "AX25KISSInterface":
 894                      interface = AX25KISSInterface.AX25KISSInterface(RNS.Transport, interface_config)
 895                      interface_post_init(interface)
 896  
 897                  if c["type"] == "RNodeInterface":
 898                      interface = RNodeInterface.RNodeInterface(RNS.Transport, interface_config)
 899                      interface_post_init(interface)
 900  
 901                  if c["type"] == "RNodeMultiInterface":
 902                      interface = RNodeMultiInterface.RNodeMultiInterface(RNS.Transport, interface_config)
 903                      interface_post_init(interface)
 904                      interface.start()
 905  
 906                  if c["type"] == "WeaveInterface":
 907                      interface = WeaveInterface.WeaveInterface(RNS.Transport, interface_config)
 908                      interface_post_init(interface)
 909  
 910                  if bootstrap_only and instance_init: self.bootstrap_configs.append(interface_config)
 911  
 912                  if interface == None:
 913                      # Interface was not handled by any internal interface types,
 914                      # attempt to load and initialise it from user-supplied modules
 915                      interface_type = c["type"]
 916                      interface_file = f"{interface_type}.py"
 917                      interface_path = os.path.join(self.interfacepath, interface_file)
 918                      if not os.path.isfile(interface_path):
 919                          RNS.log(f"Could not locate external interface module \"{interface_file}\" in \"{self.interfacepath}\"", RNS.LOG_ERROR)
 920                      
 921                      else:
 922                          try:
 923                              RNS.log(f"Loading external interface \"{interface_file}\" from \"{self.interfacepath}\"", RNS.LOG_NOTICE)
 924                              interface_globals = {}
 925                              interface_globals["Interface"] = Interface.Interface
 926                              interface_globals["RNS"] = RNS
 927                              with open(interface_path) as class_file:
 928                                  interface_code = class_file.read()
 929                                  exec(interface_code, interface_globals)
 930                                  interface_class = interface_globals["interface_class"]
 931                                  
 932                                  if interface_class != None:
 933                                      interface = interface_class(RNS.Transport, interface_config)
 934                                      interface_post_init(interface)
 935  
 936                          except Exception as e:
 937                              RNS.log(f"External interface initialisation failed for {interface_type} / {name}", RNS.LOG_ERROR)
 938                              RNS.trace_exception(e)
 939  
 940              else:
 941                  RNS.log("Skipping disabled interface \""+name+"\"", RNS.LOG_DEBUG)
 942  
 943          except Exception as e:
 944              RNS.log("The interface \""+name+"\" could not be created. Check your configuration file for errors!", RNS.LOG_ERROR)
 945              RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR)
 946              RNS.trace_exception(e)
 947              RNS.panic()
 948  
 949      def _add_interface(self, interface, mode = None, configured_bitrate=None, ifac_size=None, ifac_netname=None, ifac_netkey=None,
 950                         announce_cap=None, announce_rate_target=None, announce_rate_grace=None, announce_rate_penalty=None, bootstrap_only=False):
 951          if not self.is_connected_to_shared_instance:
 952              if interface != None and issubclass(type(interface), RNS.Interfaces.Interface.Interface):
 953                  
 954                  if mode == None: mode = Interface.Interface.MODE_FULL
 955                  interface.mode = mode
 956                  interface.OUT  = True
 957  
 958                  if configured_bitrate: interface.bitrate = configured_bitrate
 959                  if bootstrap_only == True: interface.bootstrap_only = True
 960                  interface.optimise_mtu()
 961  
 962                  if ifac_size != None: interface.ifac_size = ifac_size
 963                  else: interface.ifac_size = 8
 964  
 965                  interface.announce_cap = announce_cap if announce_cap != None else Reticulum.ANNOUNCE_CAP/100.0
 966                  interface.announce_rate_target = announce_rate_target
 967                  interface.announce_rate_grace = announce_rate_grace
 968                  interface.announce_rate_penalty = announce_rate_penalty
 969  
 970                  interface.ifac_netname = ifac_netname
 971                  interface.ifac_netkey = ifac_netkey
 972  
 973                  if interface.ifac_netname != None or interface.ifac_netkey != None:
 974                      ifac_origin = b""
 975  
 976                      if interface.ifac_netname != None:
 977                          ifac_origin += RNS.Identity.full_hash(interface.ifac_netname.encode("utf-8"))
 978  
 979                      if interface.ifac_netkey != None:
 980                          ifac_origin += RNS.Identity.full_hash(interface.ifac_netkey.encode("utf-8"))
 981  
 982                      ifac_origin_hash = RNS.Identity.full_hash(ifac_origin)
 983                      interface.ifac_key = RNS.Cryptography.hkdf(
 984                          length=64,
 985                          derive_from=ifac_origin_hash,
 986                          salt=self.ifac_salt,
 987                          context=None
 988                      )
 989  
 990                      interface.ifac_identity = RNS.Identity.from_bytes(interface.ifac_key)
 991                      interface.ifac_signature = interface.ifac_identity.sign(RNS.Identity.full_hash(interface.ifac_key))
 992  
 993                  RNS.Transport.interfaces.append(interface)
 994                  interface.final_init()
 995  
 996      def _should_persist_data(self):
 997          if time.time() > self.last_data_persist+Reticulum.GRACIOUS_PERSIST_INTERVAL:
 998              self.__persist_data()
 999  
1000      def __persist_data(self):
1001          RNS.Transport.persist_data()
1002          RNS.Identity.persist_data()
1003          self.last_data_persist = time.time()
1004  
1005      def __clean_caches(self):
1006          RNS.log("Cleaning resource and packet caches...", RNS.LOG_EXTREME)
1007          now = time.time()
1008  
1009          # Clean resource caches
1010          for filename in os.listdir(self.resourcepath):
1011              try:
1012                  if len(filename) == (RNS.Identity.HASHLENGTH//8)*2:
1013                      filepath = self.resourcepath + "/" + filename
1014                      mtime = os.path.getmtime(filepath)
1015                      age = now - mtime
1016                      if age > Reticulum.RESOURCE_CACHE:
1017                          os.unlink(filepath)
1018  
1019              except Exception as e:
1020                  RNS.log("Error while cleaning resources cache, the contained exception was: "+str(e), RNS.LOG_ERROR)
1021  
1022          # Clean packet caches
1023          for filename in os.listdir(self.cachepath):
1024              try:
1025                  if len(filename) == (RNS.Identity.HASHLENGTH//8)*2:
1026                      filepath = self.cachepath + "/" + filename
1027                      mtime = os.path.getmtime(filepath)
1028                      age = now - mtime
1029                      if age > RNS.Transport.DESTINATION_TIMEOUT:
1030                          os.unlink(filepath)
1031              
1032              except Exception as e:
1033                  RNS.log("Error while cleaning resources cache, the contained exception was: "+str(e), RNS.LOG_ERROR)
1034  
1035      def __create_default_config(self):
1036          self.config = ConfigObj(__default_rns_config__)
1037          self.config.filename = Reticulum.configpath
1038          
1039          if not os.path.isdir(Reticulum.configdir):
1040              os.makedirs(Reticulum.configdir)
1041          self.config.write()
1042  
1043      def rpc_loop(self):
1044          while True:
1045              try:
1046                  rpc_connection = self.rpc_listener.accept()
1047                  call = rpc_connection.recv()
1048  
1049                  if "get" in call:
1050                      path = call["get"]
1051  
1052                      if path == "path_table":
1053                          mh = call["max_hops"]
1054                          rpc_connection.send(self.get_path_table(max_hops=mh))
1055  
1056                      if path == "interface_stats":       rpc_connection.send(self.get_interface_stats())
1057                      if path == "rate_table":            rpc_connection.send(self.get_rate_table())
1058                      if path == "next_hop_if_name":      rpc_connection.send(self.get_next_hop_if_name(call["destination_hash"]))
1059                      if path == "next_hop":              rpc_connection.send(self.get_next_hop(call["destination_hash"]))
1060                      if path == "first_hop_timeout":     rpc_connection.send(self.get_first_hop_timeout(call["destination_hash"]))
1061                      if path == "link_count":            rpc_connection.send(self.get_link_count())
1062                      if path == "packet_rssi":           rpc_connection.send(self.get_packet_rssi(call["packet_hash"]))
1063                      if path == "packet_snr":            rpc_connection.send(self.get_packet_snr(call["packet_hash"]))
1064                      if path == "packet_q":              rpc_connection.send(self.get_packet_q(call["packet_hash"]))
1065                      if path == "blackholed_identities": rpc_connection.send(self.get_blackholed_identities())
1066  
1067                  if "drop" in call:
1068                      path = call["drop"]
1069                      if path == "path":            rpc_connection.send(self.drop_path(call["destination_hash"]))
1070                      if path == "all_via":         rpc_connection.send(self.drop_all_via(call["destination_hash"]))
1071                      if path == "announce_queues": rpc_connection.send(self.drop_announce_queues())
1072  
1073                  if "blackhole_identity" in call:
1074                      identity_hash = call["blackhole_identity"]
1075                      until = call["until"]
1076                      reason = call["reason"]
1077                      rpc_connection.send(self.blackhole_identity(identity_hash, until=until, reason=reason))
1078  
1079                  if "unblackhole_identity" in call:
1080                      identity_hash = call["unblackhole_identity"]
1081                      rpc_connection.send(self.unblackhole_identity(identity_hash))
1082  
1083                  rpc_connection.close()
1084  
1085              except Exception as e:
1086                  RNS.log("An error ocurred while handling RPC call from local client: "+str(e), RNS.LOG_ERROR)
1087  
1088      def get_rpc_client(self): return multiprocessing.connection.Client(self.rpc_addr, family=self.rpc_type, authkey=self.rpc_key)
1089  
1090      def get_interface_stats(self):
1091          if self.is_connected_to_shared_instance:
1092              rpc_connection = self.get_rpc_client()
1093              rpc_connection.send({"get": "interface_stats"})
1094              response = rpc_connection.recv()
1095              return response
1096          else:
1097              interfaces = []
1098              for interface in RNS.Transport.interfaces:
1099                  ifstats = {}
1100                  
1101                  if hasattr(interface, "clients"):
1102                      ifstats["clients"] = interface.clients
1103                  else:
1104                      ifstats["clients"] = None
1105  
1106                  if hasattr(interface, "parent_interface") and interface.parent_interface != None:
1107                      ifstats["parent_interface_name"] = str(interface.parent_interface)
1108                      ifstats["parent_interface_hash"] = interface.parent_interface.get_hash()
1109  
1110                  if hasattr(interface, "i2p") and hasattr(interface, "connectable"):
1111                      if interface.connectable:
1112                          ifstats["i2p_connectable"] = True
1113                      else:
1114                          ifstats["i2p_connectable"] = False
1115  
1116                  if hasattr(interface, "b32"):
1117                      if interface.b32 != None:
1118                          ifstats["i2p_b32"] = interface.b32+".b32.i2p"
1119                      else:
1120                          ifstats["i2p_b32"] = None
1121  
1122                  if hasattr(interface, "i2p_tunnel_state"):
1123                      if interface.i2p_tunnel_state != None:
1124                          state_description = "Unknown State"
1125                          if interface.i2p_tunnel_state == I2PInterface.I2PInterfacePeer.TUNNEL_STATE_ACTIVE:
1126                              state_description = "Tunnel Active"
1127                          elif interface.i2p_tunnel_state == I2PInterface.I2PInterfacePeer.TUNNEL_STATE_INIT:
1128                              state_description = "Creating Tunnel"
1129                          elif interface.i2p_tunnel_state == I2PInterface.I2PInterfacePeer.TUNNEL_STATE_STALE:
1130                              state_description = "Tunnel Unresponsive"
1131                          ifstats["tunnelstate"] = state_description
1132                      else:
1133                          ifstats["tunnelstate"] = None
1134  
1135                  if hasattr(interface, "r_airtime_short"):
1136                      ifstats["airtime_short"] = interface.r_airtime_short
1137  
1138                  if hasattr(interface, "r_airtime_long"):
1139                      ifstats["airtime_long"] = interface.r_airtime_long
1140  
1141                  if hasattr(interface, "r_channel_load_short"):
1142                      ifstats["channel_load_short"] = interface.r_channel_load_short
1143  
1144                  if hasattr(interface, "r_channel_load_long"):
1145                      ifstats["channel_load_long"] = interface.r_channel_load_long
1146  
1147                  if hasattr(interface, "r_noise_floor"):
1148                      ifstats["noise_floor"] = interface.r_noise_floor
1149  
1150                  if hasattr(interface, "r_interference"):
1151                      ifstats["interference"] = interface.r_interference
1152  
1153                  if hasattr(interface, "r_interference_l") and type(interface.r_interference_l) == list:
1154                      ifstats["interference_last_ts"] = interface.r_interference_l[0]
1155                      ifstats["interference_last_dbm"] = interface.r_interference_l[1]
1156  
1157                  if hasattr(interface, "cpu_temp"):
1158                      ifstats["cpu_temp"] = interface.cpu_temp
1159  
1160                  if hasattr(interface, "cpu_load"):
1161                      ifstats["cpu_load"] = interface.cpu_load
1162  
1163                  if hasattr(interface, "mem_load"):
1164                      ifstats["mem_load"] = interface.mem_load
1165  
1166                  if hasattr(interface, "switch_id"):
1167                      if interface.switch_id != None: ifstats["switch_id"] = RNS.hexrep(interface.switch_id)
1168                      else:                           ifstats["switch_id"] = None
1169  
1170                  if hasattr(interface, "via_switch_id"):
1171                      if interface.via_switch_id != None: ifstats["via_switch_id"] = RNS.hexrep(interface.via_switch_id)
1172                      else:                               ifstats["via_switch_id"] = None
1173  
1174                  if hasattr(interface, "endpoint_id"):
1175                      if interface.endpoint_id != None: ifstats["endpoint_id"] = RNS.hexrep(interface.endpoint_id)
1176                      else:                             ifstats["endpoint_id"] = None
1177  
1178                  if hasattr(interface, "r_battery_state"):
1179                      if interface.r_battery_state != 0x00:
1180                          ifstats["battery_state"] = interface.get_battery_state_string()
1181  
1182                      if hasattr(interface, "r_battery_percent"):
1183                          ifstats["battery_percent"] = interface.r_battery_percent
1184  
1185                  if hasattr(interface, "bitrate"):
1186                      if interface.bitrate != None:
1187                          ifstats["bitrate"] = interface.bitrate
1188                      else:
1189                          ifstats["bitrate"] = None
1190  
1191                  if hasattr(interface, "current_rx_speed"):
1192                      if interface.current_rx_speed != None:
1193                          ifstats["rxs"] = interface.current_rx_speed
1194                      else:
1195                          ifstats["rxs"] = 0
1196                  else:
1197                      ifstats["rxs"] = 0
1198  
1199                  if hasattr(interface, "current_tx_speed"):
1200                      if interface.current_tx_speed != None:
1201                          ifstats["txs"] = interface.current_tx_speed
1202                      else:
1203                          ifstats["txs"] = 0
1204                  else:
1205                      ifstats["txs"] = 0
1206  
1207                  if hasattr(interface, "peers"):
1208                      if interface.peers != None:
1209                          ifstats["peers"] = len(interface.peers)
1210                      else:
1211                          ifstats["peers"] = None
1212  
1213                  if hasattr(interface, "ifac_signature"):
1214                      ifstats["ifac_signature"] = interface.ifac_signature
1215                      ifstats["ifac_size"] = interface.ifac_size
1216                      ifstats["ifac_netname"] = interface.ifac_netname
1217                  else:
1218                      ifstats["ifac_signature"] = None
1219                      ifstats["ifac_size"] = None
1220                      ifstats["ifac_netname"] = None
1221  
1222                  if hasattr(interface, "autoconnect_source"):
1223                      ifstats["autoconnect_source"] = interface.autoconnect_source
1224                  else:
1225                      ifstats["autoconnect_source"] = None
1226  
1227                  if hasattr(interface, "announce_queue"):
1228                      if interface.announce_queue != None:
1229                          ifstats["announce_queue"] = len(interface.announce_queue)
1230                      else:
1231                          ifstats["announce_queue"] = None
1232  
1233                  ifstats["name"] = str(interface)
1234                  ifstats["short_name"] = str(interface.name)
1235                  ifstats["hash"] = interface.get_hash()
1236                  ifstats["type"] = str(type(interface).__name__)
1237                  ifstats["rxb"] = interface.rxb
1238                  ifstats["txb"] = interface.txb
1239                  ifstats["incoming_announce_frequency"] = interface.incoming_announce_frequency()
1240                  ifstats["outgoing_announce_frequency"] = interface.outgoing_announce_frequency()
1241                  ifstats["held_announces"] = len(interface.held_announces)
1242                  ifstats["status"] = interface.online
1243                  ifstats["mode"] = interface.mode
1244  
1245                  interfaces.append(ifstats)
1246  
1247              stats = {}
1248              stats["interfaces"] = interfaces
1249              stats["rxb"] = RNS.Transport.traffic_rxb
1250              stats["txb"] = RNS.Transport.traffic_txb
1251              stats["rxs"] = RNS.Transport.speed_rx
1252              stats["txs"] = RNS.Transport.speed_tx
1253              if Reticulum.transport_enabled():
1254                  stats["transport_id"] = RNS.Transport.identity.hash
1255                  stats["network_id"] = RNS.Transport.network_identity.hash if RNS.Transport.network_identity else None
1256                  stats["transport_uptime"] = time.time()-RNS.Transport.start_time
1257                  if Reticulum.probe_destination_enabled():
1258                      stats["probe_responder"] = RNS.Transport.probe_destination.hash
1259                  else:
1260                      stats["probe_responder"] = None
1261  
1262              if importlib.util.find_spec('psutil') != None:
1263                  import psutil
1264                  process = psutil.Process()
1265                  stats["rss"] = process.memory_info().rss
1266              else:
1267                  stats["rss"] = None
1268  
1269              return stats
1270  
1271      def get_path_table(self, max_hops=None):
1272          if self.is_connected_to_shared_instance:
1273              rpc_connection = self.get_rpc_client()
1274              rpc_connection.send({"get": "path_table", "max_hops": max_hops})
1275              response = rpc_connection.recv()
1276              return response
1277  
1278          else:
1279              path_table = []
1280              for dst_hash in RNS.Transport.path_table:
1281                  path_hops = RNS.Transport.path_table[dst_hash][2]
1282                  if max_hops == None or path_hops <= max_hops:
1283                      entry = {
1284                          "hash": dst_hash,
1285                          "timestamp": RNS.Transport.path_table[dst_hash][0],
1286                          "via": RNS.Transport.path_table[dst_hash][1],
1287                          "hops": path_hops,
1288                          "expires": RNS.Transport.path_table[dst_hash][3],
1289                          "interface": str(RNS.Transport.path_table[dst_hash][5]),
1290                      }
1291                      path_table.append(entry)
1292  
1293              return path_table
1294  
1295      def get_rate_table(self):
1296          if self.is_connected_to_shared_instance:
1297              rpc_connection = self.get_rpc_client()
1298              rpc_connection.send({"get": "rate_table"})
1299              response = rpc_connection.recv()
1300              return response
1301  
1302          else:
1303              rate_table = []
1304              for dst_hash in RNS.Transport.announce_rate_table:
1305                  entry = {
1306                      "hash": dst_hash,
1307                      "last": RNS.Transport.announce_rate_table[dst_hash]["last"],
1308                      "rate_violations": RNS.Transport.announce_rate_table[dst_hash]["rate_violations"],
1309                      "blocked_until": RNS.Transport.announce_rate_table[dst_hash]["blocked_until"],
1310                      "timestamps": RNS.Transport.announce_rate_table[dst_hash]["timestamps"],
1311                  }
1312                  rate_table.append(entry)
1313  
1314              return rate_table
1315  
1316      def drop_path(self, destination):
1317          if self.is_connected_to_shared_instance:
1318              rpc_connection = self.get_rpc_client()
1319              rpc_connection.send({"drop": "path", "destination_hash": destination})
1320              response = rpc_connection.recv()
1321              return response
1322  
1323          else:
1324              return RNS.Transport.expire_path(destination)
1325  
1326      def drop_all_via(self, transport_hash):
1327          if self.is_connected_to_shared_instance:
1328              rpc_connection = self.get_rpc_client()
1329              rpc_connection.send({"drop": "all_via", "destination_hash": transport_hash})
1330              response = rpc_connection.recv()
1331              return response
1332  
1333          else:
1334              dropped_count = 0
1335              for destination_hash in RNS.Transport.path_table:
1336                  if RNS.Transport.path_table[destination_hash][1] == transport_hash:
1337                      RNS.Transport.expire_path(destination_hash)
1338                      dropped_count += 1
1339  
1340              return dropped_count
1341  
1342      def drop_announce_queues(self):
1343          if self.is_connected_to_shared_instance:
1344              rpc_connection = self.get_rpc_client()
1345              rpc_connection.send({"drop": "announce_queues"})
1346              response = rpc_connection.recv()
1347              return response
1348  
1349          else:
1350              return RNS.Transport.drop_announce_queues()
1351  
1352      def get_next_hop_if_name(self, destination):
1353          if self.is_connected_to_shared_instance:
1354              rpc_connection = self.get_rpc_client()
1355              rpc_connection.send({"get": "next_hop_if_name", "destination_hash": destination})
1356              response = rpc_connection.recv()
1357              return response
1358  
1359          else:
1360              return str(RNS.Transport.next_hop_interface(destination))
1361  
1362      def get_first_hop_timeout(self, destination):
1363          if self.is_connected_to_shared_instance:
1364              try:
1365                  rpc_connection = self.get_rpc_client()
1366                  rpc_connection.send({"get": "first_hop_timeout", "destination_hash": destination})
1367                  response = rpc_connection.recv()
1368  
1369                  if self.is_connected_to_shared_instance and hasattr(self, "_force_shared_instance_bitrate") and self._force_shared_instance_bitrate:
1370                      simulated_latency = ((1/self._force_shared_instance_bitrate)*8)*RNS.Reticulum.MTU
1371                      RNS.log("Adding simulated latency of "+RNS.prettytime(simulated_latency)+" to first hop timeout", RNS.LOG_DEBUG)
1372                      response += simulated_latency
1373  
1374                  return response
1375              except Exception as e:
1376                  RNS.log("An error occurred while getting first hop timeout from shared instance: "+str(e), RNS.LOG_ERROR)
1377                  return RNS.Reticulum.DEFAULT_PER_HOP_TIMEOUT
1378  
1379          else:
1380              return RNS.Transport.first_hop_timeout(destination)
1381  
1382      def get_next_hop(self, destination):
1383          if self.is_connected_to_shared_instance:
1384              rpc_connection = self.get_rpc_client()
1385              rpc_connection.send({"get": "next_hop", "destination_hash": destination})
1386              response = rpc_connection.recv()
1387  
1388              return response
1389  
1390          else:
1391              return RNS.Transport.next_hop(destination)
1392  
1393      def get_link_count(self):
1394          if self.is_connected_to_shared_instance:
1395              rpc_connection = self.get_rpc_client()
1396              rpc_connection.send({"get": "link_count"})
1397              response = rpc_connection.recv()
1398              return response
1399  
1400          else:
1401              return len(RNS.Transport.link_table)
1402  
1403      def get_packet_rssi(self, packet_hash):
1404          if self.is_connected_to_shared_instance:
1405              rpc_connection = self.get_rpc_client()
1406              rpc_connection.send({"get": "packet_rssi", "packet_hash": packet_hash})
1407              response = rpc_connection.recv()
1408              return response
1409  
1410          else:
1411              for entry in RNS.Transport.local_client_rssi_cache:
1412                  if entry[0] == packet_hash:
1413                      return entry[1]
1414  
1415              return None
1416  
1417      def get_packet_snr(self, packet_hash):
1418          if self.is_connected_to_shared_instance:
1419              rpc_connection = self.get_rpc_client()
1420              rpc_connection.send({"get": "packet_snr", "packet_hash": packet_hash})
1421              response = rpc_connection.recv()
1422              return response
1423  
1424          else:
1425              for entry in RNS.Transport.local_client_snr_cache:
1426                  if entry[0] == packet_hash:
1427                      return entry[1]
1428  
1429              return None
1430  
1431      def get_packet_q(self, packet_hash):
1432          if self.is_connected_to_shared_instance:
1433              rpc_connection = self.get_rpc_client()
1434              rpc_connection.send({"get": "packet_q", "packet_hash": packet_hash})
1435              response = rpc_connection.recv()
1436              return response
1437  
1438          else:
1439              for entry in RNS.Transport.local_client_q_cache:
1440                  if entry[0] == packet_hash:
1441                      return entry[1]
1442  
1443              return None
1444  
1445      def halt_interface(self, interface):
1446          pass
1447  
1448      def resume_interface(self, interface):
1449          pass
1450  
1451      def reload_interface(self, interface):
1452          pass
1453  
1454      def get_blackholed_identities(self):
1455          if self.is_connected_to_shared_instance:
1456                  rpc_connection = self.get_rpc_client()
1457                  rpc_connection.send({"get": "blackholed_identities"})
1458                  response = rpc_connection.recv()
1459                  return response
1460              
1461          else: return RNS.Transport.blackholed_identities
1462  
1463      def blackhole_identity(self, identity_hash, until=None, reason=None):
1464          if len(identity_hash) != RNS.Reticulum.TRUNCATED_HASHLENGTH//8: return False
1465          else:
1466              if self.is_connected_to_shared_instance:
1467                  rpc_connection = self.get_rpc_client()
1468                  rpc_connection.send({"blackhole_identity": identity_hash, "until": until, "reason": reason})
1469                  response = rpc_connection.recv()
1470                  return response
1471              
1472              else: return RNS.Transport.blackhole_identity(identity_hash, until=until, reason=reason)
1473  
1474      def unblackhole_identity(self, identity_hash):
1475          if len(identity_hash) != RNS.Reticulum.TRUNCATED_HASHLENGTH//8: return False
1476          else:
1477              if self.is_connected_to_shared_instance:
1478                  rpc_connection = self.get_rpc_client()
1479                  rpc_connection.send({"unblackhole_identity": identity_hash})
1480                  response = rpc_connection.recv()
1481                  return response
1482              
1483              else: return RNS.Transport.unblackhole_identity(identity_hash)
1484  
1485      @staticmethod
1486      def should_use_implicit_proof():
1487          """
1488          Returns whether proofs sent are explicit or implicit.
1489  
1490          :returns: True if the current running configuration specifies to use implicit proofs. False if not.
1491          """
1492          return Reticulum.__use_implicit_proof
1493  
1494      @staticmethod
1495      def transport_enabled():
1496          """
1497          Returns whether Transport is enabled for the running
1498          instance.
1499  
1500          When Transport is enabled, Reticulum will
1501          route traffic for other peers, respond to path requests
1502          and pass announces over the network.
1503  
1504          :returns: True if Transport is enabled, False if not.
1505          """
1506          return Reticulum.__transport_enabled
1507  
1508      @staticmethod
1509      def link_mtu_discovery():
1510          """
1511          Returns whether link MTU discovery is enabled for the running
1512          instance.
1513  
1514          When link MTU discovery is enabled, Reticulum will
1515          automatically upgrade link MTUs to the highest supported
1516          value, increasing transfer speed and efficiency.
1517  
1518          :returns: True if link MTU discovery is enabled, False if not.
1519          """
1520          return Reticulum.__link_mtu_discovery
1521  
1522      @staticmethod
1523      def remote_management_enabled():
1524          """
1525          Returns whether remote management is enabled for the
1526          running instance.
1527  
1528          When remote management is enabled, authenticated peers
1529          can remotely query and manage this instance.
1530  
1531          :returns: True if remote management is enabled, False if not.
1532          """
1533          return Reticulum.__remote_management_enabled
1534  
1535      @staticmethod
1536      def probe_destination_enabled():
1537          return Reticulum.__allow_probes
1538  
1539      @staticmethod
1540      def required_discovery_value():
1541          """
1542          Returns the required stamp value for a discovered interface
1543          to be considered valid and remembered.
1544  
1545          :returns: The required stamp value as an integer.
1546          """
1547          return Reticulum.__required_discovery_value
1548  
1549      @staticmethod
1550      def publish_blackhole_enabled():
1551          """
1552          Returns whether blackhole list publishing is enabled for the
1553          running instance.
1554  
1555          :returns: True if blackhole list publishing is enabled, False if not.
1556          """
1557          return Reticulum.__publish_blackhole
1558  
1559      @staticmethod
1560      def blackhole_sources():
1561          """
1562          Returns the list of transport identity hashes from which
1563          blackhole lists are sourced.
1564  
1565          :returns: A list of identity hashes.
1566          """
1567          return Reticulum.__blackhole_sources
1568  
1569      @staticmethod
1570      def discovered_interfaces():
1571          """
1572          Returns a list of interfaces discovered over the network.
1573  
1574          :returns: A list of discovered interfaces.
1575          """
1576          return RNS.Discovery.InterfaceDiscovery(discover_interfaces=False).list_discovered_interfaces()
1577  
1578      @staticmethod
1579      def interface_discovery_sources():
1580          """
1581          Returns the list of network identity hashes from which
1582          interfaces are discovered.
1583  
1584          :returns: A list of identity hashes.
1585          """
1586          return Reticulum.__interface_sources
1587  
1588      @staticmethod
1589      def should_autoconnect_discovered_interfaces():
1590          return Reticulum.__autoconnect_discovered_interfaces > 0
1591  
1592      @staticmethod
1593      def max_autoconnected_interfaces():
1594          return Reticulum.__autoconnect_discovered_interfaces
1595  
1596  # Default configuration file:
1597  __default_rns_config__ = '''# This is the default Reticulum config file.
1598  # You should probably edit it to include any additional,
1599  # interfaces and settings you might need.
1600  
1601  # Only the most basic options are included in this default
1602  # configuration. To see a more verbose, and much longer,
1603  # configuration example, you can run the command:
1604  # rnsd --exampleconfig
1605  
1606  
1607  [reticulum]
1608  
1609  # If you enable Transport, your system will route traffic
1610  # for other peers, pass announces and serve path requests.
1611  # This should only be done for systems that are suited to
1612  # act as transport nodes, ie. if they are stationary and
1613  # always-on. This directive is optional and can be removed
1614  # for brevity.
1615  
1616  enable_transport = False
1617  
1618  
1619  # By default, the first program to launch the Reticulum
1620  # Network Stack will create a shared instance, that other
1621  # programs can communicate with. Only the shared instance
1622  # opens all the configured interfaces directly, and other
1623  # local programs communicate with the shared instance over
1624  # a local socket. This is completely transparent to the
1625  # user, and should generally be turned on. This directive
1626  # is optional and can be removed for brevity.
1627  
1628  share_instance = Yes
1629  
1630  
1631  # If you want to run multiple *different* shared instances
1632  # on the same system, you will need to specify different
1633  # instance names for each. On platforms supporting domain
1634  # sockets, this can be done with the instance_name option:
1635  
1636  instance_name = default
1637  
1638  
1639  # Some platforms don't support domain sockets, and if that
1640  # is the case, you can isolate different instances by
1641  # specifying a unique set of ports for each:
1642  
1643  # shared_instance_port = 37428
1644  # instance_control_port = 37429
1645  
1646  
1647  # If you want to explicitly use TCP for shared instance
1648  # communication, instead of domain sockets, this is also
1649  # possible, by using the following option:
1650  
1651  # shared_instance_type = tcp
1652  
1653  
1654  # You can configure whether Reticulum should discover
1655  # available interfaces from other Transport Instances over
1656  # the network. If this option is enabled, Reticulum will
1657  # collect interface information discovered from the network.
1658  
1659  # discover_interfaces = No
1660  
1661  
1662  # You can configure Reticulum to panic and forcibly close
1663  # if an unrecoverable interface error occurs, such as the
1664  # hardware device for an interface disappearing. This is
1665  # an optional directive, and can be left out for brevity.
1666  # This behaviour is disabled by default.
1667  
1668  # panic_on_interface_error = No
1669  
1670  
1671  # If you're connecting to a large external network, you
1672  # can use one or more external blackhole list to block
1673  # spammy and excessive announces onto your network. This
1674  # funtionality is especially useful if you're hosting public
1675  # entrypoints or gateways. The list source below provides a
1676  # functional example, but better, more timely maintained
1677  # lists probably exist in the community.
1678  
1679  # blackhole_sources = 521c87a83afb8f29e4455e77930b973b
1680  
1681  
1682  [logging]
1683  # Valid log levels are 0 through 7:
1684  #   0: Log only critical information
1685  #   1: Log errors and lower log levels
1686  #   2: Log warnings and lower log levels
1687  #   3: Log notices and lower log levels
1688  #   4: Log info and lower (this is the default)
1689  #   5: Verbose logging
1690  #   6: Debug logging
1691  #   7: Extreme logging
1692  
1693  loglevel = 4
1694  
1695  
1696  # The interfaces section defines the physical and virtual
1697  # interfaces Reticulum will use to communicate on. This
1698  # section will contain examples for a variety of interface
1699  # types. You can modify these or use them as a basis for
1700  # your own config, or simply remove the unused ones.
1701  
1702  [interfaces]
1703  
1704    # This interface enables communication with other
1705    # link-local Reticulum nodes over UDP. It does not
1706    # need any functional IP infrastructure like routers
1707    # or DHCP servers, but will require that at least link-
1708    # local IPv6 is enabled in your operating system, which
1709    # should be enabled by default in almost any OS. See
1710    # the Reticulum Manual for more configuration options.
1711  
1712    [[Default Interface]]
1713      type = AutoInterface
1714      enabled = Yes
1715  
1716  '''.splitlines()