2022-04-01 11:18:18 -04:00
# MIT License
#
2024-09-04 11:37:18 -04:00
# Copyright (c) 2016-2024 Mark Qvist / unsigned.io and contributors.
2022-04-01 11:18:18 -04:00
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
2021-12-01 05:40:44 -05:00
from . vendor . platformutils import get_platform
if get_platform ( ) == " android " :
2022-01-12 06:02:00 -05:00
from . Interfaces import Interface
from . Interfaces import LocalInterface
from . Interfaces import AutoInterface
from . Interfaces import TCPInterface
from . Interfaces import UDPInterface
2022-02-23 11:40:31 -05:00
from . Interfaces import I2PInterface
2022-10-12 10:08:29 -04:00
from . Interfaces . Android import RNodeInterface
2022-10-15 05:39:23 -04:00
from . Interfaces . Android import SerialInterface
2022-10-15 08:56:23 -04:00
from . Interfaces . Android import KISSInterface
2021-12-01 05:40:44 -05:00
else :
from . Interfaces import *
2020-04-22 06:07:13 -04:00
from . vendor . configobj import ConfigObj
2021-09-24 14:10:04 -04:00
import configparser
import multiprocessing . connection
2021-09-24 10:09:07 -04:00
import signal
2021-09-24 14:10:04 -04:00
import threading
2018-04-04 08:14:22 -04:00
import atexit
import struct
import array
2022-07-02 07:12:54 -04:00
import time
2018-04-04 08:14:22 -04:00
import os
import RNS
class Reticulum :
2021-05-16 06:55:50 -04:00
"""
This class is used to initialise access to Reticulum within a
program . You must create exactly one instance of this class before
carrying out any other RNS operations , such as creating destinations
or sending traffic . Every independently executed program must create
their own instance of the Reticulum class , but Reticulum will
automatically handle inter - program communication on the same system ,
and expose all connected programs to external interfaces as well .
As soon as an instance of this class is created , Reticulum will start
opening and configuring any hardware devices specified in the supplied
configuration .
Currently the first running instance must be kept running while other
local instances are connected , as the first created instance will
act as a master instance that directly communicates with external
hardware such as modems , TNCs and radios . If a master instance is
asked to exit , it will not exit until all client processes have
terminated ( unless killed forcibly ) .
If you are running Reticulum on a system with several different
programs that use RNS starting and terminating at different times ,
it will be advantageous to run a master RNS instance as a daemon for
other programs to use on demand .
"""
2022-04-27 07:21:53 -04:00
# Future minimum will probably be locked in at 251 bytes to support
# networks with segments of different MTUs. Absolute minimum is 219.
2020-08-13 06:15:56 -04:00
MTU = 500
2021-09-02 12:00:03 -04:00
"""
The MTU that Reticulum adheres to , and will expect other peers to
2023-10-31 06:09:54 -04:00
adhere to . By default , the MTU is 500 bytes . In custom RNS network
2021-09-02 12:00:03 -04:00
implementations , it is possible to change this value , but doing so will
completely break compatibility with all other RNS networks . An identical
MTU is a prerequisite for peers to communicate in the same network .
2021-09-02 14:35:42 -04:00
Unless you really know what you are doing , the MTU should be left at
the default value .
2021-09-02 12:00:03 -04:00
"""
2022-04-18 10:23:24 -04:00
MAX_QUEUED_ANNOUNCES = 16384
QUEUED_ANNOUNCE_LIFE = 60 * 60 * 24
2022-04-17 14:14:20 -04:00
ANNOUNCE_CAP = 2
"""
The maximum percentage of interface bandwidth that , at any given time ,
may be used to propagate announces . If an announce was scheduled for
broadcasting on an interface , but doing so would exceed the allowed
bandwidth allocation , the announce will be queued for transmission
when there is bandwidth available .
Reticulum will always prioritise propagating announces with fewer
hops , ensuring that distant , large networks with many peers on fast
links don ' t overwhelm the capacity of smaller networks on slower
mediums . If an announce remains queued for an extended amount of time ,
it will eventually be dropped .
This value will be applied by default to all created interfaces ,
2023-10-31 06:09:54 -04:00
but it can be configured individually on a per - interface basis . In
general , the global default setting should not be changed , and any
alterations should be made on a per - interface basis instead .
2022-04-17 14:14:20 -04:00
"""
2023-10-31 06:09:54 -04:00
MINIMUM_BITRATE = 5
"""
Minimum bitrate required across a medium for Reticulum to be able
to successfully establish links . Currently 5 bits per second .
"""
2022-04-17 13:35:31 -04:00
2023-10-31 06:09:54 -04:00
# TODO: Let Reticulum somehow continously build a map of per-hop
# latencies and use this map for global timeout calculation.
2023-11-01 23:35:57 -04:00
DEFAULT_PER_HOP_TIMEOUT = 6
2021-12-05 05:45:13 -05:00
2021-09-02 12:00:03 -04:00
# Length of truncated hashes in bits.
2022-06-30 08:02:57 -04:00
TRUNCATED_HASHLENGTH = 128
2021-09-02 12:00:03 -04:00
2022-09-13 14:17:25 -04:00
HEADER_MINSIZE = 2 + 1 + ( TRUNCATED_HASHLENGTH / / 8 ) * 1
HEADER_MAXSIZE = 2 + 1 + ( TRUNCATED_HASHLENGTH / / 8 ) * 2
IFAC_MIN_SIZE = 1
IFAC_SALT = bytes . fromhex ( " adf54d882c9a9b80771eb4995d702d4a3e733391b2a0f53f416d9f907e55cff8 " )
2021-09-02 12:00:03 -04:00
2022-09-13 14:17:25 -04:00
MDU = MTU - HEADER_MAXSIZE - IFAC_MIN_SIZE
2020-08-13 06:15:56 -04:00
2022-09-13 14:17:25 -04:00
RESOURCE_CACHE = 24 * 60 * 60
JOB_INTERVAL = 5 * 60
CLEAN_INTERVAL = 15 * 60
PERSIST_INTERVAL = 60 * 60 * 12
2023-09-13 14:07:07 -04:00
GRACIOUS_PERSIST_INTERVAL = 60 * 5
2022-07-02 07:12:54 -04:00
2022-09-13 14:17:25 -04:00
router = None
config = None
2020-08-13 06:15:56 -04:00
2021-05-16 07:02:46 -04:00
# The default configuration path will be expanded to a directory
# named ".reticulum" inside the current users home directory
2022-09-14 10:21:34 -04:00
userdir = os . path . expanduser ( " ~ " )
configdir = None
2022-09-13 14:17:25 -04:00
configpath = " "
storagepath = " "
cachepath = " "
2023-10-27 12:16:52 -04:00
__instance = None
2020-08-13 06:15:56 -04:00
@staticmethod
def exit_handler ( ) :
2021-05-16 09:52:45 -04:00
# This exit handler is called whenever Reticulum is asked to
# shut down, and will in turn call exit handlers in other
# classes, saving necessary information to disk and carrying
# out cleanup operations.
2021-05-16 07:02:46 -04:00
RNS . Transport . exit_handler ( )
RNS . Identity . exit_handler ( )
2020-08-13 06:15:56 -04:00
2021-09-24 10:09:07 -04:00
@staticmethod
def sigint_handler ( signal , frame ) :
RNS . Transport . detach_interfaces ( )
RNS . exit ( )
2021-12-05 10:05:43 -05:00
@staticmethod
def sigterm_handler ( signal , frame ) :
RNS . Transport . detach_interfaces ( )
RNS . exit ( )
2023-10-27 12:16:52 -04:00
@staticmethod
def get_instance ( ) :
"""
Return the currently running Reticulum instance
"""
return Reticulum . __instance
2023-05-02 10:42:04 -04:00
def __init__ ( self , configdir = None , loglevel = None , logdest = None , verbosity = None ) :
2021-05-16 06:55:50 -04:00
"""
Initialises and starts a Reticulum instance . This must be
done before any other operations , and Reticulum will not
pass any traffic before being instantiated .
: param configdir : Full path to a Reticulum configuration directory .
"""
2023-10-27 12:16:52 -04:00
if Reticulum . __instance != None :
raise OSError ( " Attempt to reinitialise Reticulum, when it was already running " )
else :
Reticulum . __instance = self
2022-01-12 04:07:44 -05:00
RNS . vendor . platformutils . platform_checks ( )
2020-08-13 06:15:56 -04:00
if configdir != None :
Reticulum . configdir = configdir
2022-09-14 10:21:34 -04:00
else :
2022-09-30 14:41:11 -04:00
if os . path . isdir ( " /etc/reticulum " ) and os . path . isfile ( " /etc/reticulum/config " ) :
Reticulum . configdir = " /etc/reticulum "
2022-09-30 14:37:46 -04:00
elif os . path . isdir ( Reticulum . userdir + " /.config/reticulum " ) and os . path . isfile ( Reticulum . userdir + " /.config/reticulum/config " ) :
2022-09-14 10:21:34 -04:00
Reticulum . configdir = Reticulum . userdir + " /.config/reticulum "
2022-09-30 14:37:46 -04:00
else :
Reticulum . configdir = Reticulum . userdir + " /.reticulum "
2022-09-14 10:21:34 -04:00
if logdest == RNS . LOG_FILE :
RNS . logdest = RNS . LOG_FILE
RNS . logfile = Reticulum . configdir + " /logfile "
2020-08-13 06:15:56 -04:00
Reticulum . configpath = Reticulum . configdir + " /config "
Reticulum . storagepath = Reticulum . configdir + " /storage "
Reticulum . cachepath = Reticulum . configdir + " /storage/cache "
Reticulum . resourcepath = Reticulum . configdir + " /storage/resources "
2022-05-22 13:09:16 -04:00
Reticulum . identitypath = Reticulum . configdir + " /storage/identities "
2020-08-13 06:15:56 -04:00
Reticulum . __transport_enabled = False
2024-08-28 19:54:34 -04:00
Reticulum . __remote_management_enabled = False
2020-08-13 06:15:56 -04:00
Reticulum . __use_implicit_proof = True
2023-09-21 12:48:08 -04:00
Reticulum . __allow_probes = False
2020-08-13 06:15:56 -04:00
2021-09-18 16:49:04 -04:00
Reticulum . panic_on_interface_error = False
2020-08-13 06:15:56 -04:00
self . local_interface_port = 37428
2021-09-24 14:10:04 -04:00
self . local_control_port = 37429
self . share_instance = True
self . rpc_listener = None
2023-10-07 06:34:10 -04:00
self . rpc_key = None
2020-08-13 06:15:56 -04:00
2022-04-27 13:00:09 -04:00
self . ifac_salt = Reticulum . IFAC_SALT
2021-09-24 09:18:06 -04:00
self . requested_loglevel = loglevel
2023-05-02 10:42:04 -04:00
self . requested_verbosity = verbosity
2021-09-24 09:34:03 -04:00
if self . requested_loglevel != None :
if self . requested_loglevel > RNS . LOG_EXTREME :
self . requested_loglevel = RNS . LOG_EXTREME
if self . requested_loglevel < RNS . LOG_CRITICAL :
self . requested_loglevel = RNS . LOG_CRITICAL
2021-09-24 10:09:07 -04:00
2021-09-24 09:34:03 -04:00
RNS . loglevel = self . requested_loglevel
2021-09-24 09:18:06 -04:00
2020-08-13 06:15:56 -04:00
self . is_shared_instance = False
self . is_connected_to_shared_instance = False
self . is_standalone_instance = False
2022-07-02 07:24:07 -04:00
self . jobs_thread = None
2022-09-13 14:17:25 -04:00
self . last_data_persist = time . time ( )
self . last_cache_clean = 0
2020-08-13 06:15:56 -04:00
if not os . path . isdir ( Reticulum . storagepath ) :
os . makedirs ( Reticulum . storagepath )
if not os . path . isdir ( Reticulum . cachepath ) :
os . makedirs ( Reticulum . cachepath )
if not os . path . isdir ( Reticulum . resourcepath ) :
os . makedirs ( Reticulum . resourcepath )
2022-05-22 13:09:16 -04:00
if not os . path . isdir ( Reticulum . identitypath ) :
os . makedirs ( Reticulum . identitypath )
2020-08-13 06:15:56 -04:00
if os . path . isfile ( self . configpath ) :
try :
self . config = ConfigObj ( self . configpath )
except Exception as e :
RNS . log ( " Could not parse the configuration at " + self . configpath , RNS . LOG_ERROR )
RNS . log ( " Check your configuration file for errors! " , RNS . LOG_ERROR )
RNS . panic ( )
else :
RNS . log ( " Could not load config file, creating default configuration file... " )
2021-05-16 06:55:50 -04:00
self . __create_default_config ( )
2021-12-09 10:07:36 -05:00
RNS . log ( " Default config file created. Make any necessary changes in " + Reticulum . configdir + " /config and restart Reticulum if needed. " )
time . sleep ( 1.5 )
2020-08-13 06:15:56 -04:00
2021-05-16 06:55:50 -04:00
self . __apply_config ( )
2021-09-24 08:13:31 -04:00
RNS . log ( " Configuration loaded from " + self . configpath , RNS . LOG_VERBOSE )
2021-05-16 10:15:57 -04:00
RNS . Identity . load_known_destinations ( )
2020-08-13 06:15:56 -04:00
RNS . Transport . start ( self )
2021-09-24 14:10:04 -04:00
self . rpc_addr = ( " 127.0.0.1 " , self . local_control_port )
2023-10-07 06:34:10 -04:00
if self . rpc_key == None :
self . rpc_key = RNS . Identity . full_hash ( RNS . Transport . identity . get_private_key ( ) )
2021-09-24 14:10:04 -04:00
if self . is_shared_instance :
self . rpc_listener = multiprocessing . connection . Listener ( self . rpc_addr , authkey = self . rpc_key )
thread = threading . Thread ( target = self . rpc_loop )
2022-09-30 13:02:25 -04:00
thread . daemon = True
2021-09-24 14:10:04 -04:00
thread . start ( )
2020-08-13 06:15:56 -04:00
atexit . register ( Reticulum . exit_handler )
2021-09-24 10:09:07 -04:00
signal . signal ( signal . SIGINT , Reticulum . sigint_handler )
2021-12-05 10:05:43 -05:00
signal . signal ( signal . SIGTERM , Reticulum . sigterm_handler )
2020-08-13 06:15:56 -04:00
2022-07-02 07:24:07 -04:00
def __start_jobs ( self ) :
if self . jobs_thread == None :
2024-09-05 09:02:22 -04:00
RNS . Identity . _clean_ratchets ( )
2022-07-02 07:24:07 -04:00
self . jobs_thread = threading . Thread ( target = self . __jobs )
2022-09-30 13:02:25 -04:00
self . jobs_thread . daemon = True
2022-07-02 07:24:07 -04:00
self . jobs_thread . start ( )
def __jobs ( self ) :
while True :
2022-09-13 14:17:25 -04:00
now = time . time ( )
if now > self . last_cache_clean + Reticulum . CLEAN_INTERVAL :
self . __clean_caches ( )
self . last_cache_clean = time . time ( )
if now > self . last_data_persist + Reticulum . PERSIST_INTERVAL :
self . __persist_data ( )
2022-07-02 07:24:07 -04:00
time . sleep ( Reticulum . JOB_INTERVAL )
2021-05-16 06:55:50 -04:00
def __start_local_interface ( self ) :
2020-08-13 06:15:56 -04:00
if self . share_instance :
try :
interface = LocalInterface . LocalServerInterface (
RNS . Transport ,
self . local_interface_port
)
interface . OUT = True
2023-10-27 12:16:52 -04:00
if hasattr ( Reticulum , " _force_shared_instance_bitrate " ) :
interface . bitrate = Reticulum . _force_shared_instance_bitrate
interface . _force_bitrate = Reticulum . _force_shared_instance_bitrate
2023-11-02 07:24:42 -04:00
RNS . log ( f " Forcing shared instance bitrate of { RNS . prettyspeed ( interface . bitrate ) } " , RNS . LOG_WARNING )
2020-08-13 06:15:56 -04:00
RNS . Transport . interfaces . append ( interface )
2021-09-24 14:10:04 -04:00
2020-08-13 06:15:56 -04:00
self . is_shared_instance = True
RNS . log ( " Started shared instance interface: " + str ( interface ) , RNS . LOG_DEBUG )
2022-07-02 07:24:07 -04:00
self . __start_jobs ( )
2022-07-02 07:12:54 -04:00
2020-08-13 06:15:56 -04:00
except Exception as e :
try :
interface = LocalInterface . LocalClientInterface (
RNS . Transport ,
" Local shared instance " ,
self . local_interface_port )
interface . target_port = self . local_interface_port
interface . OUT = True
2023-10-27 12:16:52 -04:00
if hasattr ( Reticulum , " _force_shared_instance_bitrate " ) :
interface . bitrate = Reticulum . _force_shared_instance_bitrate
interface . _force_bitrate = True
2023-11-02 07:24:42 -04:00
RNS . log ( f " Forcing shared instance bitrate of { RNS . prettyspeed ( interface . bitrate ) } " , RNS . LOG_WARNING )
2020-08-13 06:15:56 -04:00
RNS . Transport . interfaces . append ( interface )
self . is_shared_instance = False
self . is_standalone_instance = False
self . is_connected_to_shared_instance = True
2021-10-03 09:23:12 -04:00
Reticulum . __transport_enabled = False
2024-08-28 19:54:34 -04:00
Reticulum . __remote_management_enabled = False
2023-09-21 12:48:08 -04:00
Reticulum . __allow_probes = False
2022-05-17 07:25:42 -04:00
RNS . log ( " Connected to locally available Reticulum instance via: " + str ( interface ) , RNS . LOG_DEBUG )
2020-08-13 06:15:56 -04:00
except Exception as e :
RNS . log ( " Local shared instance appears to be running, but it could not be connected " , RNS . LOG_ERROR )
RNS . log ( " The contained exception was: " + str ( e ) , RNS . LOG_ERROR )
self . is_shared_instance = False
self . is_standalone_instance = True
self . is_connected_to_shared_instance = False
else :
self . is_shared_instance = False
self . is_standalone_instance = True
self . is_connected_to_shared_instance = False
2022-07-02 07:24:07 -04:00
self . __start_jobs ( )
2020-08-13 06:15:56 -04:00
2021-05-16 06:55:50 -04:00
def __apply_config ( self ) :
2020-08-13 06:15:56 -04:00
if " logging " in self . config :
for option in self . config [ " logging " ] :
value = self . config [ " logging " ] [ option ]
2021-09-24 09:18:06 -04:00
if option == " loglevel " and self . requested_loglevel == None :
2020-08-13 06:15:56 -04:00
RNS . loglevel = int ( value )
2023-05-02 10:42:04 -04:00
if self . requested_verbosity != None :
RNS . loglevel + = self . requested_verbosity
2020-08-13 06:15:56 -04:00
if RNS . loglevel < 0 :
RNS . loglevel = 0
if RNS . loglevel > 7 :
RNS . loglevel = 7
if " reticulum " in self . config :
for option in self . config [ " reticulum " ] :
value = self . config [ " reticulum " ] [ option ]
if option == " share_instance " :
value = self . config [ " reticulum " ] . as_bool ( option )
self . share_instance = value
if option == " shared_instance_port " :
value = int ( self . config [ " reticulum " ] [ option ] )
self . local_interface_port = value
2021-09-24 14:10:04 -04:00
if option == " instance_control_port " :
value = int ( self . config [ " reticulum " ] [ option ] )
self . local_control_port = value
2023-10-07 06:34:10 -04:00
if option == " rpc_key " :
try :
value = bytes . fromhex ( self . config [ " reticulum " ] [ option ] )
self . rpc_key = value
except Exception as e :
RNS . log ( " Invalid shared instance RPC key specified, falling back to default key " , RNS . LOG_ERROR )
self . rpc_key = None
2020-08-13 06:15:56 -04:00
if option == " enable_transport " :
v = self . config [ " reticulum " ] . as_bool ( option )
if v == True :
Reticulum . __transport_enabled = True
2024-08-28 19:54:34 -04:00
if option == " enable_remote_management " :
v = self . config [ " reticulum " ] . as_bool ( option )
if v == True :
Reticulum . __remote_management_enabled = True
if option == " remote_management_allowed " :
v = self . config [ " reticulum " ] . as_list ( option )
for hexhash in v :
dest_len = ( RNS . Reticulum . TRUNCATED_HASHLENGTH / / 8 ) * 2
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 ) )
try :
allowed_hash = bytes . fromhex ( hexhash )
except Exception as e :
raise ValueError ( " Invalid identity hash for remote management ACL: " + str ( hexhash ) )
if not allowed_hash in RNS . Transport . remote_management_allowed :
RNS . Transport . remote_management_allowed . append ( allowed_hash )
2023-09-21 12:48:08 -04:00
if option == " respond_to_probes " :
v = self . config [ " reticulum " ] . as_bool ( option )
if v == True :
Reticulum . __allow_probes = True
2023-10-27 12:16:52 -04:00
if option == " force_shared_instance_bitrate " :
v = self . config [ " reticulum " ] . as_int ( option )
Reticulum . _force_shared_instance_bitrate = v
2021-09-18 16:49:04 -04:00
if option == " panic_on_interface_error " :
v = self . config [ " reticulum " ] . as_bool ( option )
if v == True :
Reticulum . panic_on_interface_error = True
2020-08-13 06:15:56 -04:00
if option == " use_implicit_proof " :
v = self . config [ " reticulum " ] . as_bool ( option )
if v == True :
Reticulum . __use_implicit_proof = True
if v == False :
Reticulum . __use_implicit_proof = False
2021-05-16 06:55:50 -04:00
self . __start_local_interface ( )
2020-08-13 06:15:56 -04:00
if self . is_shared_instance or self . is_standalone_instance :
2022-05-14 14:19:46 -04:00
RNS . log ( " Bringing up system interfaces... " , RNS . LOG_VERBOSE )
2020-08-13 06:15:56 -04:00
interface_names = [ ]
2022-07-07 18:21:48 -04:00
if " interfaces " in self . config :
for name in self . config [ " interfaces " ] :
if not name in interface_names :
# TODO: We really need to generalise this way of instantiating
# and configuring interfaces. Ideally, interfaces should just
# have a conrfig dict passed to their init method, and return
# a ready interface, onto which this routine can configure any
# generic or extra parameters.
c = self . config [ " interfaces " ] [ name ]
interface_mode = Interface . Interface . MODE_FULL
if " interface_mode " in c :
c [ " interface_mode " ] = str ( c [ " interface_mode " ] ) . lower ( )
if c [ " interface_mode " ] == " full " :
interface_mode = Interface . Interface . MODE_FULL
elif c [ " interface_mode " ] == " access_point " or c [ " interface_mode " ] == " accesspoint " or c [ " interface_mode " ] == " ap " :
interface_mode = Interface . Interface . MODE_ACCESS_POINT
elif c [ " interface_mode " ] == " pointtopoint " or c [ " interface_mode " ] == " ptp " :
interface_mode = Interface . Interface . MODE_POINT_TO_POINT
elif c [ " interface_mode " ] == " roaming " :
interface_mode = Interface . Interface . MODE_ROAMING
elif c [ " interface_mode " ] == " boundary " :
interface_mode = Interface . Interface . MODE_BOUNDARY
elif c [ " mode " ] == " gateway " or c [ " mode " ] == " gw " :
interface_mode = Interface . Interface . MODE_GATEWAY
elif " mode " in c :
c [ " mode " ] = str ( c [ " mode " ] ) . lower ( )
if c [ " mode " ] == " full " :
interface_mode = Interface . Interface . MODE_FULL
elif c [ " mode " ] == " access_point " or c [ " mode " ] == " accesspoint " or c [ " mode " ] == " ap " :
interface_mode = Interface . Interface . MODE_ACCESS_POINT
elif c [ " mode " ] == " pointtopoint " or c [ " mode " ] == " ptp " :
interface_mode = Interface . Interface . MODE_POINT_TO_POINT
elif c [ " mode " ] == " roaming " :
interface_mode = Interface . Interface . MODE_ROAMING
elif c [ " mode " ] == " boundary " :
interface_mode = Interface . Interface . MODE_BOUNDARY
elif c [ " mode " ] == " gateway " or c [ " mode " ] == " gw " :
interface_mode = Interface . Interface . MODE_GATEWAY
ifac_size = None
if " ifac_size " in c :
if c . as_int ( " ifac_size " ) > = Reticulum . IFAC_MIN_SIZE * 8 :
ifac_size = c . as_int ( " ifac_size " ) / / 8
ifac_netname = None
if " networkname " in c :
if c [ " networkname " ] != " " :
ifac_netname = c [ " networkname " ]
if " network_name " in c :
if c [ " network_name " ] != " " :
ifac_netname = c [ " network_name " ]
ifac_netkey = None
if " passphrase " in c :
if c [ " passphrase " ] != " " :
ifac_netkey = c [ " passphrase " ]
if " pass_phrase " in c :
if c [ " pass_phrase " ] != " " :
ifac_netkey = c [ " pass_phrase " ]
2023-09-30 15:07:22 -04:00
ingress_control = True
2023-09-30 18:43:26 -04:00
if " ingress_control " in c : ingress_control = c . as_bool ( " ingress_control " )
ic_max_held_announces = None
if " ic_max_held_announces " in c : ic_max_held_announces = c . as_int ( " ic_max_held_announces " )
ic_burst_hold = None
if " ic_burst_hold " in c : ic_burst_hold = c . as_float ( " ic_burst_hold " )
ic_burst_freq_new = None
if " ic_burst_freq_new " in c : ic_burst_freq_new = c . as_float ( " ic_burst_freq_new " )
ic_burst_freq = None
if " ic_burst_freq " in c : ic_burst_freq = c . as_float ( " ic_burst_freq " )
ic_new_time = None
if " ic_new_time " in c : ic_new_time = c . as_float ( " ic_new_time " )
ic_burst_penalty = None
if " ic_burst_penalty " in c : ic_burst_penalty = c . as_float ( " ic_burst_penalty " )
ic_held_release_interval = None
if " ic_held_release_interval " in c : ic_held_release_interval = c . as_float ( " ic_held_release_interval " )
2023-09-30 15:07:22 -04:00
2022-07-07 18:21:48 -04:00
configured_bitrate = None
if " bitrate " in c :
if c . as_int ( " bitrate " ) > = Reticulum . MINIMUM_BITRATE :
configured_bitrate = c . as_int ( " bitrate " )
announce_rate_target = None
if " announce_rate_target " in c :
if c . as_int ( " announce_rate_target " ) > 0 :
announce_rate_target = c . as_int ( " announce_rate_target " )
announce_rate_grace = None
if " announce_rate_grace " in c :
if c . as_int ( " announce_rate_grace " ) > = 0 :
announce_rate_grace = c . as_int ( " announce_rate_grace " )
announce_rate_penalty = None
if " announce_rate_penalty " in c :
if c . as_int ( " announce_rate_penalty " ) > = 0 :
announce_rate_penalty = c . as_int ( " announce_rate_penalty " )
if announce_rate_target != None and announce_rate_grace == None :
announce_rate_grace = 0
if announce_rate_target != None and announce_rate_penalty == None :
announce_rate_penalty = 0
announce_cap = Reticulum . ANNOUNCE_CAP / 100.0
if " announce_cap " in c :
if c . as_float ( " announce_cap " ) > 0 and c . as_float ( " announce_cap " ) < = 100 :
announce_cap = c . as_float ( " announce_cap " ) / 100.0
try :
interface = None
if ( ( " interface_enabled " in c ) and c . as_bool ( " interface_enabled " ) == True ) or ( ( " enabled " in c ) and c . as_bool ( " enabled " ) == True ) :
if c [ " type " ] == " AutoInterface " :
2024-05-16 12:09:11 -04:00
group_id = c [ " group_id " ] if " group_id " in c else None
discovery_scope = c [ " discovery_scope " ] if " discovery_scope " in c else None
discovery_port = int ( c [ " discovery_port " ] ) if " discovery_port " in c else None
multicast_address_type = c [ " multicast_address_type " ] if " multicast_address_type " in c else None
data_port = int ( c [ " data_port " ] ) if " data_port " in c else None
allowed_interfaces = c . as_list ( " devices " ) if " devices " in c else None
ignored_interfaces = c . as_list ( " ignored_devices " ) if " ignored_devices " in c else None
interface = AutoInterface . AutoInterface (
RNS . Transport ,
name ,
group_id ,
discovery_scope ,
discovery_port ,
multicast_address_type ,
data_port ,
allowed_interfaces ,
ignored_interfaces
)
if " outgoing " in c and c . as_bool ( " outgoing " ) == False :
interface . OUT = False
else :
interface . OUT = True
interface . mode = interface_mode
2022-07-07 18:21:48 -04:00
2024-05-16 12:09:11 -04:00
interface . announce_cap = announce_cap
if configured_bitrate :
interface . bitrate = configured_bitrate
if ifac_size != None :
interface . ifac_size = ifac_size
2022-07-07 18:21:48 -04:00
else :
2024-05-16 12:09:11 -04:00
interface . ifac_size = 16
2022-07-07 18:21:48 -04:00
if c [ " type " ] == " UDPInterface " :
device = c [ " device " ] if " device " in c else None
port = int ( c [ " port " ] ) if " port " in c else None
listen_ip = c [ " listen_ip " ] if " listen_ip " in c else None
listen_port = int ( c [ " listen_port " ] ) if " listen_port " in c else None
forward_ip = c [ " forward_ip " ] if " forward_ip " in c else None
forward_port = int ( c [ " forward_port " ] ) if " forward_port " in c else None
if port != None :
if listen_port == None :
listen_port = port
if forward_port == None :
forward_port = port
interface = UDPInterface . UDPInterface (
2022-01-12 06:12:04 -05:00
RNS . Transport ,
name ,
2022-07-07 18:21:48 -04:00
device ,
listen_ip ,
listen_port ,
forward_ip ,
forward_port
2022-01-12 06:12:04 -05:00
)
2022-02-25 07:31:52 -05:00
if " outgoing " in c and c . as_bool ( " outgoing " ) == False :
2022-01-12 06:12:04 -05:00
interface . OUT = False
2022-02-25 07:31:52 -05:00
else :
interface . OUT = True
2022-01-12 06:12:04 -05:00
2022-02-25 12:47:55 -05:00
interface . mode = interface_mode
2022-04-17 14:14:20 -04:00
interface . announce_cap = announce_cap
2022-04-17 13:35:31 -04:00
if configured_bitrate :
interface . bitrate = configured_bitrate
2022-04-27 07:21:53 -04:00
if ifac_size != None :
interface . ifac_size = ifac_size
else :
interface . ifac_size = 16
2022-04-17 13:35:31 -04:00
2022-07-07 18:21:48 -04:00
if c [ " type " ] == " TCPServerInterface " :
device = c [ " device " ] if " device " in c else None
port = int ( c [ " port " ] ) if " port " in c else None
listen_ip = c [ " listen_ip " ] if " listen_ip " in c else None
listen_port = int ( c [ " listen_port " ] ) if " listen_port " in c else None
i2p_tunneled = c . as_bool ( " i2p_tunneled " ) if " i2p_tunneled " in c else False
if port != None :
2021-08-19 13:56:35 -04:00
listen_port = port
2022-07-07 18:21:48 -04:00
interface = TCPInterface . TCPServerInterface (
RNS . Transport ,
name ,
device ,
listen_ip ,
listen_port ,
i2p_tunneled
)
if " outgoing " in c and c . as_bool ( " outgoing " ) == False :
interface . OUT = False
else :
interface . OUT = True
if interface_mode == Interface . Interface . MODE_ACCESS_POINT :
RNS . log ( str ( interface ) + " does not support Access Point mode, reverting to default mode: Full " , RNS . LOG_WARNING )
interface_mode = Interface . Interface . MODE_FULL
interface . mode = interface_mode
interface . announce_cap = announce_cap
if configured_bitrate :
interface . bitrate = configured_bitrate
if ifac_size != None :
interface . ifac_size = ifac_size
else :
interface . ifac_size = 16
if c [ " type " ] == " TCPClientInterface " :
kiss_framing = False
if " kiss_framing " in c and c . as_bool ( " kiss_framing " ) == True :
kiss_framing = True
i2p_tunneled = c . as_bool ( " i2p_tunneled " ) if " i2p_tunneled " in c else False
2022-09-15 09:35:28 -04:00
tcp_connect_timeout = c . as_int ( " connect_timeout " ) if " connect_timeout " in c else None
2022-07-07 18:21:48 -04:00
interface = TCPInterface . TCPClientInterface (
RNS . Transport ,
name ,
c [ " target_host " ] ,
int ( c [ " target_port " ] ) ,
kiss_framing = kiss_framing ,
2022-09-15 09:35:28 -04:00
i2p_tunneled = i2p_tunneled ,
connect_timeout = tcp_connect_timeout ,
2022-07-07 18:21:48 -04:00
)
if " outgoing " in c and c . as_bool ( " outgoing " ) == False :
interface . OUT = False
else :
interface . OUT = True
if interface_mode == Interface . Interface . MODE_ACCESS_POINT :
RNS . log ( str ( interface ) + " does not support Access Point mode, reverting to default mode: Full " , RNS . LOG_WARNING )
interface_mode = Interface . Interface . MODE_FULL
interface . mode = interface_mode
interface . announce_cap = announce_cap
if configured_bitrate :
interface . bitrate = configured_bitrate
if ifac_size != None :
interface . ifac_size = ifac_size
else :
interface . ifac_size = 16
if c [ " type " ] == " I2PInterface " :
i2p_peers = c . as_list ( " peers " ) if " peers " in c else None
connectable = c . as_bool ( " connectable " ) if " connectable " in c else False
2022-11-03 10:22:34 -04:00
if ifac_size == None :
ifac_size = 16
2022-07-07 18:21:48 -04:00
interface = I2PInterface . I2PInterface (
RNS . Transport ,
name ,
Reticulum . storagepath ,
i2p_peers ,
connectable = connectable ,
2022-11-03 10:22:34 -04:00
ifac_size = ifac_size ,
ifac_netname = ifac_netname ,
ifac_netkey = ifac_netkey ,
2022-06-07 09:48:23 -04:00
)
2022-04-27 13:00:09 -04:00
2022-07-07 18:21:48 -04:00
if " outgoing " in c and c . as_bool ( " outgoing " ) == False :
interface . OUT = False
else :
interface . OUT = True
2022-04-27 13:00:09 -04:00
2022-07-07 18:21:48 -04:00
if interface_mode == Interface . Interface . MODE_ACCESS_POINT :
RNS . log ( str ( interface ) + " does not support Access Point mode, reverting to default mode: Full " , RNS . LOG_WARNING )
interface_mode = Interface . Interface . MODE_FULL
interface . mode = interface_mode
2022-04-17 14:14:20 -04:00
2022-07-07 18:21:48 -04:00
interface . announce_cap = announce_cap
if configured_bitrate :
interface . bitrate = configured_bitrate
2020-08-13 06:15:56 -04:00
2022-07-07 18:21:48 -04:00
if c [ " type " ] == " SerialInterface " :
port = c [ " port " ] if " port " in c else None
speed = int ( c [ " speed " ] ) if " speed " in c else 9600
databits = int ( c [ " databits " ] ) if " databits " in c else 8
parity = c [ " parity " ] if " parity " in c else " N "
stopbits = int ( c [ " stopbits " ] ) if " stopbits " in c else 1
if port == None :
raise ValueError ( " No port specified for serial interface " )
interface = SerialInterface . SerialInterface (
RNS . Transport ,
name ,
port ,
speed ,
databits ,
parity ,
stopbits
)
if " outgoing " in c and c . as_bool ( " outgoing " ) == False :
interface . OUT = False
else :
interface . OUT = True
interface . mode = interface_mode
interface . announce_cap = announce_cap
if configured_bitrate :
interface . bitrate = configured_bitrate
if ifac_size != None :
interface . ifac_size = ifac_size
else :
interface . ifac_size = 8
if c [ " type " ] == " PipeInterface " :
command = c [ " command " ] if " command " in c else None
respawn_delay = c . as_float ( " respawn_delay " ) if " respawn_delay " in c else None
if command == None :
raise ValueError ( " No command specified for PipeInterface " )
interface = PipeInterface . PipeInterface (
RNS . Transport ,
name ,
command ,
respawn_delay ,
)
if " outgoing " in c and c . as_bool ( " outgoing " ) == False :
interface . OUT = False
else :
interface . OUT = True
interface . mode = interface_mode
interface . announce_cap = announce_cap
if configured_bitrate :
interface . bitrate = configured_bitrate
if ifac_size != None :
interface . ifac_size = ifac_size
else :
interface . ifac_size = 8
if c [ " type " ] == " KISSInterface " :
preamble = int ( c [ " preamble " ] ) if " preamble " in c else None
txtail = int ( c [ " txtail " ] ) if " txtail " in c else None
persistence = int ( c [ " persistence " ] ) if " persistence " in c else None
slottime = int ( c [ " slottime " ] ) if " slottime " in c else None
flow_control = c . as_bool ( " flow_control " ) if " flow_control " in c else False
port = c [ " port " ] if " port " in c else None
speed = int ( c [ " speed " ] ) if " speed " in c else 9600
databits = int ( c [ " databits " ] ) if " databits " in c else 8
parity = c [ " parity " ] if " parity " in c else " N "
stopbits = int ( c [ " stopbits " ] ) if " stopbits " in c else 1
beacon_interval = int ( c [ " id_interval " ] ) if " id_interval " in c else None
beacon_data = c [ " id_callsign " ] if " id_callsign " in c else None
if port == None :
raise ValueError ( " No port specified for serial interface " )
interface = KISSInterface . KISSInterface (
RNS . Transport ,
name ,
port ,
speed ,
databits ,
parity ,
stopbits ,
preamble ,
txtail ,
persistence ,
slottime ,
flow_control ,
beacon_interval ,
beacon_data
)
if " outgoing " in c and c . as_bool ( " outgoing " ) == False :
interface . OUT = False
else :
interface . OUT = True
interface . mode = interface_mode
interface . announce_cap = announce_cap
if configured_bitrate :
interface . bitrate = configured_bitrate
if ifac_size != None :
interface . ifac_size = ifac_size
else :
interface . ifac_size = 8
if c [ " type " ] == " AX25KISSInterface " :
preamble = int ( c [ " preamble " ] ) if " preamble " in c else None
txtail = int ( c [ " txtail " ] ) if " txtail " in c else None
persistence = int ( c [ " persistence " ] ) if " persistence " in c else None
slottime = int ( c [ " slottime " ] ) if " slottime " in c else None
flow_control = c . as_bool ( " flow_control " ) if " flow_control " in c else False
port = c [ " port " ] if " port " in c else None
speed = int ( c [ " speed " ] ) if " speed " in c else 9600
databits = int ( c [ " databits " ] ) if " databits " in c else 8
parity = c [ " parity " ] if " parity " in c else " N "
stopbits = int ( c [ " stopbits " ] ) if " stopbits " in c else 1
callsign = c [ " callsign " ] if " callsign " in c else " "
ssid = int ( c [ " ssid " ] ) if " ssid " in c else - 1
if port == None :
raise ValueError ( " No port specified for serial interface " )
interface = AX25KISSInterface . AX25KISSInterface (
RNS . Transport ,
name ,
callsign ,
ssid ,
port ,
speed ,
databits ,
parity ,
stopbits ,
preamble ,
txtail ,
persistence ,
slottime ,
flow_control
)
if " outgoing " in c and c . as_bool ( " outgoing " ) == False :
interface . OUT = False
else :
interface . OUT = True
interface . mode = interface_mode
interface . announce_cap = announce_cap
if configured_bitrate :
interface . bitrate = configured_bitrate
if ifac_size != None :
interface . ifac_size = ifac_size
else :
interface . ifac_size = 8
if c [ " type " ] == " RNodeInterface " :
frequency = int ( c [ " frequency " ] ) if " frequency " in c else None
bandwidth = int ( c [ " bandwidth " ] ) if " bandwidth " in c else None
txpower = int ( c [ " txpower " ] ) if " txpower " in c else None
spreadingfactor = int ( c [ " spreadingfactor " ] ) if " spreadingfactor " in c else None
codingrate = int ( c [ " codingrate " ] ) if " codingrate " in c else None
flow_control = c . as_bool ( " flow_control " ) if " flow_control " in c else False
id_interval = int ( c [ " id_interval " ] ) if " id_interval " in c else None
id_callsign = c [ " id_callsign " ] if " id_callsign " in c else None
2023-09-13 14:07:07 -04:00
st_alock = float ( c [ " airtime_limit_short " ] ) if " airtime_limit_short " in c else None
lt_alock = float ( c [ " airtime_limit_long " ] ) if " airtime_limit_long " in c else None
2022-07-07 18:21:48 -04:00
port = c [ " port " ] if " port " in c else None
if port == None :
raise ValueError ( " No port specified for RNode interface " )
interface = RNodeInterface . RNodeInterface (
RNS . Transport ,
name ,
port ,
frequency = frequency ,
bandwidth = bandwidth ,
txpower = txpower ,
sf = spreadingfactor ,
cr = codingrate ,
flow_control = flow_control ,
id_interval = id_interval ,
2023-09-13 14:07:07 -04:00
id_callsign = id_callsign ,
st_alock = st_alock ,
lt_alock = lt_alock
2022-07-07 18:21:48 -04:00
)
if " outgoing " in c and c . as_bool ( " outgoing " ) == False :
interface . OUT = False
else :
interface . OUT = True
interface . mode = interface_mode
interface . announce_cap = announce_cap
if configured_bitrate :
interface . bitrate = configured_bitrate
if ifac_size != None :
interface . ifac_size = ifac_size
else :
interface . ifac_size = 8
2024-08-19 03:19:42 -04:00
if c [ " type " ] == " RNodeMultiInterface " :
count = 0
enabled_count = 0
# Count how many interfaces are in the file
for subinterface in c :
# if the retrieved entry is not a string, it must be a dictionary, which is what we want
if not isinstance ( c [ subinterface ] , str ) :
count + = 1
# Count how many interfaces are enabled to allow for appropriate matrix sizing
for subinterface in c :
# if the retrieved entry is not a string, it must be a dictionary, which is what we want
if not isinstance ( c [ subinterface ] , str ) :
subinterface_config = self . config [ " interfaces " ] [ name ] [ subinterface ]
if ( ( " interface_enabled " in subinterface_config ) and subinterface_config . as_bool ( " interface_enabled " ) == True ) or ( ( " enabled " in c ) and c . as_bool ( " enabled " ) == True ) :
enabled_count + = 1
# Create an array with a row for each subinterface
subint_config = [ [ 0 for x in range ( 10 ) ] for y in range ( enabled_count ) ]
subint_index = 0
for subinterface in c :
# If the retrieved entry is not a string, it must be a dictionary, which is what we want
if not isinstance ( c [ subinterface ] , str ) :
subinterface_config = self . config [ " interfaces " ] [ name ] [ subinterface ]
if ( ( " interface_enabled " in subinterface_config ) and subinterface_config . as_bool ( " interface_enabled " ) == True ) or ( ( " enabled " in c ) and c . as_bool ( " enabled " ) == True ) :
subint_config [ subint_index ] [ 0 ] = subinterface
subint_vport = subinterface_config [ " vport " ] if " vport " in subinterface_config else None
subint_config [ subint_index ] [ 1 ] = subint_vport
frequency = int ( subinterface_config [ " frequency " ] ) if " frequency " in subinterface_config else None
subint_config [ subint_index ] [ 2 ] = frequency
bandwidth = int ( subinterface_config [ " bandwidth " ] ) if " bandwidth " in subinterface_config else None
subint_config [ subint_index ] [ 3 ] = bandwidth
txpower = int ( subinterface_config [ " txpower " ] ) if " txpower " in subinterface_config else None
subint_config [ subint_index ] [ 4 ] = txpower
spreadingfactor = int ( subinterface_config [ " spreadingfactor " ] ) if " spreadingfactor " in subinterface_config else None
subint_config [ subint_index ] [ 5 ] = spreadingfactor
codingrate = int ( subinterface_config [ " codingrate " ] ) if " codingrate " in subinterface_config else None
subint_config [ subint_index ] [ 6 ] = codingrate
flow_control = subinterface_config . as_bool ( " flow_control " ) if " flow_control " in subinterface_config else False
subint_config [ subint_index ] [ 7 ] = flow_control
st_alock = float ( subinterface_config [ " airtime_limit_short " ] ) if " airtime_limit_short " in subinterface_config else None
subint_config [ subint_index ] [ 8 ] = st_alock
lt_alock = float ( subinterface_config [ " airtime_limit_long " ] ) if " airtime_limit_long " in subinterface_config else None
subint_config [ subint_index ] [ 9 ] = lt_alock
subint_index + = 1
# if no subinterfaces are defined
if count == 0 :
raise ValueError ( " No subinterfaces configured for " + name )
# if no subinterfaces are enabled
elif enabled_count == 0 :
raise ValueError ( " No subinterfaces enabled for " + name )
id_interval = int ( c [ " id_interval " ] ) if " id_interval " in c else None
id_callsign = c [ " id_callsign " ] if " id_callsign " in c else None
port = c [ " port " ] if " port " in c else None
if port == None :
raise ValueError ( " No port specified for " + name )
interface = RNodeMultiInterface . RNodeMultiInterface (
RNS . Transport ,
name ,
port ,
subint_config ,
id_interval = id_interval ,
id_callsign = id_callsign
)
if " outgoing " in c and c . as_bool ( " outgoing " ) == False :
interface . OUT = False
else :
interface . OUT = True
interface . mode = interface_mode
interface . announce_cap = announce_cap
if configured_bitrate :
interface . bitrate = configured_bitrate
if ifac_size != None :
interface . ifac_size = ifac_size
else :
interface . ifac_size = 8
2022-07-07 18:21:48 -04:00
if interface != None :
interface . announce_rate_target = announce_rate_target
interface . announce_rate_grace = announce_rate_grace
interface . announce_rate_penalty = announce_rate_penalty
2023-09-30 15:07:22 -04:00
interface . ingress_control = ingress_control
2023-09-30 18:43:26 -04:00
if ic_max_held_announces != None : interface . ic_max_held_announces = ic_max_held_announces
if ic_burst_hold != None : interface . ic_burst_hold = ic_burst_hold
if ic_burst_freq_new != None : interface . ic_burst_freq_new = ic_burst_freq_new
if ic_burst_freq != None : interface . ic_burst_freq = ic_burst_freq
if ic_new_time != None : interface . ic_new_time = ic_new_time
if ic_burst_penalty != None : interface . ic_burst_penalty = ic_burst_penalty
if ic_held_release_interval != None : interface . ic_held_release_interval = ic_held_release_interval
2022-07-07 18:21:48 -04:00
interface . ifac_netname = ifac_netname
interface . ifac_netkey = ifac_netkey
if interface . ifac_netname != None or interface . ifac_netkey != None :
ifac_origin = b " "
if interface . ifac_netname != None :
ifac_origin + = RNS . Identity . full_hash ( interface . ifac_netname . encode ( " utf-8 " ) )
if interface . ifac_netkey != None :
ifac_origin + = RNS . Identity . full_hash ( interface . ifac_netkey . encode ( " utf-8 " ) )
ifac_origin_hash = RNS . Identity . full_hash ( ifac_origin )
interface . ifac_key = RNS . Cryptography . hkdf (
length = 64 ,
derive_from = ifac_origin_hash ,
salt = self . ifac_salt ,
context = None
)
interface . ifac_identity = RNS . Identity . from_bytes ( interface . ifac_key )
interface . ifac_signature = interface . ifac_identity . sign ( RNS . Identity . full_hash ( interface . ifac_key ) )
RNS . Transport . interfaces . append ( interface )
else :
RNS . log ( " Skipping disabled interface \" " + name + " \" " , RNS . LOG_DEBUG )
except Exception as e :
RNS . log ( " The interface \" " + name + " \" could not be created. Check your configuration file for errors! " , RNS . LOG_ERROR )
RNS . log ( " The contained exception was: " + str ( e ) , RNS . LOG_ERROR )
RNS . panic ( )
else :
RNS . log ( " The interface name \" " + name + " \" was already used. Check your configuration file for errors! " , RNS . LOG_ERROR )
2020-08-13 06:15:56 -04:00
RNS . panic ( )
2022-02-22 08:43:14 -05:00
2022-05-14 14:19:46 -04:00
RNS . log ( " System interfaces are ready " , RNS . LOG_VERBOSE )
2022-02-22 08:43:14 -05:00
2022-07-07 18:21:48 -04:00
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 ) :
if not self . is_connected_to_shared_instance :
if interface != None and issubclass ( type ( interface ) , RNS . Interfaces . Interface . Interface ) :
if mode == None :
mode = Interface . Interface . MODE_FULL
interface . mode = mode
if configured_bitrate :
interface . bitrate = configured_bitrate
if ifac_size != None :
interface . ifac_size = ifac_size
else :
interface . ifac_size = 8
2022-10-15 17:14:47 -04:00
interface . announce_cap = announce_cap if announce_cap != None else Reticulum . ANNOUNCE_CAP / 100.0
2022-07-07 18:21:48 -04:00
interface . announce_rate_target = announce_rate_target
interface . announce_rate_grace = announce_rate_grace
interface . announce_rate_penalty = announce_rate_penalty
interface . ifac_netname = ifac_netname
interface . ifac_netkey = ifac_netkey
if interface . ifac_netname != None or interface . ifac_netkey != None :
ifac_origin = b " "
if interface . ifac_netname != None :
ifac_origin + = RNS . Identity . full_hash ( interface . ifac_netname . encode ( " utf-8 " ) )
if interface . ifac_netkey != None :
ifac_origin + = RNS . Identity . full_hash ( interface . ifac_netkey . encode ( " utf-8 " ) )
ifac_origin_hash = RNS . Identity . full_hash ( ifac_origin )
interface . ifac_key = RNS . Cryptography . hkdf (
length = 64 ,
derive_from = ifac_origin_hash ,
salt = self . ifac_salt ,
context = None
)
interface . ifac_identity = RNS . Identity . from_bytes ( interface . ifac_key )
interface . ifac_signature = interface . ifac_identity . sign ( RNS . Identity . full_hash ( interface . ifac_key ) )
RNS . Transport . interfaces . append ( interface )
2022-09-13 16:32:00 -04:00
def _should_persist_data ( self ) :
2023-09-13 14:07:07 -04:00
if time . time ( ) > self . last_data_persist + Reticulum . GRACIOUS_PERSIST_INTERVAL :
self . __persist_data ( )
2022-09-13 16:32:00 -04:00
2022-09-13 14:17:25 -04:00
def __persist_data ( self ) :
RNS . Transport . persist_data ( )
RNS . Identity . persist_data ( )
2023-09-13 14:07:07 -04:00
self . last_data_persist = time . time ( )
2020-08-13 06:15:56 -04:00
2022-07-02 07:12:54 -04:00
def __clean_caches ( self ) :
2022-07-02 07:34:17 -04:00
RNS . log ( " Cleaning resource and packet caches... " , RNS . LOG_EXTREME )
2022-07-02 07:12:54 -04:00
now = time . time ( )
# Clean resource caches
for filename in os . listdir ( self . resourcepath ) :
try :
if len ( filename ) == ( RNS . Identity . HASHLENGTH / / 8 ) * 2 :
filepath = self . resourcepath + " / " + filename
mtime = os . path . getmtime ( filepath )
age = now - mtime
2022-07-02 09:15:47 -04:00
if age > Reticulum . RESOURCE_CACHE :
2022-07-02 07:12:54 -04:00
os . unlink ( filepath )
except Exception as e :
RNS . log ( " Error while cleaning resources cache, the contained exception was: " + str ( e ) , RNS . LOG_ERROR )
# Clean packet caches
for filename in os . listdir ( self . cachepath ) :
try :
if len ( filename ) == ( RNS . Identity . HASHLENGTH / / 8 ) * 2 :
filepath = self . cachepath + " / " + filename
mtime = os . path . getmtime ( filepath )
age = now - mtime
2022-07-02 09:15:47 -04:00
if age > RNS . Transport . DESTINATION_TIMEOUT :
2022-07-02 07:12:54 -04:00
os . unlink ( filepath )
except Exception as e :
RNS . log ( " Error while cleaning resources cache, the contained exception was: " + str ( e ) , RNS . LOG_ERROR )
2021-05-16 06:55:50 -04:00
def __create_default_config ( self ) :
2020-08-13 06:15:56 -04:00
self . config = ConfigObj ( __default_rns_config__ )
self . config . filename = Reticulum . configpath
if not os . path . isdir ( Reticulum . configdir ) :
os . makedirs ( Reticulum . configdir )
self . config . write ( )
2021-09-24 14:10:04 -04:00
def rpc_loop ( self ) :
while True :
try :
rpc_connection = self . rpc_listener . accept ( )
call = rpc_connection . recv ( )
if " get " in call :
path = call [ " get " ]
if path == " interface_stats " :
rpc_connection . send ( self . get_interface_stats ( ) )
2022-04-20 04:40:51 -04:00
if path == " path_table " :
2024-08-29 05:17:07 -04:00
mh = call [ " max_hops " ]
rpc_connection . send ( self . get_path_table ( max_hops = mh ) )
2022-04-20 04:40:51 -04:00
2022-05-14 16:14:38 -04:00
if path == " rate_table " :
rpc_connection . send ( self . get_rate_table ( ) )
2021-09-25 05:27:43 -04:00
if path == " next_hop_if_name " :
rpc_connection . send ( self . get_next_hop_if_name ( call [ " destination_hash " ] ) )
if path == " next_hop " :
rpc_connection . send ( self . get_next_hop ( call [ " destination_hash " ] ) )
2023-10-27 12:16:52 -04:00
if path == " first_hop_timeout " :
rpc_connection . send ( self . get_first_hop_timeout ( call [ " destination_hash " ] ) )
2024-05-25 19:28:40 -04:00
if path == " link_count " :
rpc_connection . send ( self . get_link_count ( ) )
2021-10-12 10:34:17 -04:00
if path == " packet_rssi " :
rpc_connection . send ( self . get_packet_rssi ( call [ " packet_hash " ] ) )
if path == " packet_snr " :
rpc_connection . send ( self . get_packet_snr ( call [ " packet_hash " ] ) )
2023-10-27 18:05:35 -04:00
if path == " packet_q " :
rpc_connection . send ( self . get_packet_q ( call [ " packet_hash " ] ) )
2022-04-20 05:12:21 -04:00
if " drop " in call :
path = call [ " drop " ]
if path == " path " :
rpc_connection . send ( self . drop_path ( call [ " destination_hash " ] ) )
2023-10-01 05:39:07 -04:00
if path == " all_via " :
rpc_connection . send ( self . drop_all_via ( call [ " destination_hash " ] ) )
2022-05-13 10:18:13 -04:00
if path == " announce_queues " :
rpc_connection . send ( self . drop_announce_queues ( ) )
2021-09-24 14:10:04 -04:00
rpc_connection . close ( )
2022-04-20 05:12:21 -04:00
2021-09-24 14:10:04 -04:00
except Exception as e :
RNS . log ( " An error ocurred while handling RPC call from local client: " + str ( e ) , RNS . LOG_ERROR )
def get_interface_stats ( self ) :
if self . is_connected_to_shared_instance :
rpc_connection = multiprocessing . connection . Client ( self . rpc_addr , authkey = self . rpc_key )
rpc_connection . send ( { " get " : " interface_stats " } )
response = rpc_connection . recv ( )
return response
else :
2022-04-20 03:59:58 -04:00
interfaces = [ ]
2021-09-24 14:10:04 -04:00
for interface in RNS . Transport . interfaces :
ifstats = { }
if hasattr ( interface , " clients " ) :
ifstats [ " clients " ] = interface . clients
else :
ifstats [ " clients " ] = None
2022-05-25 09:50:54 -04:00
if hasattr ( interface , " i2p " ) and hasattr ( interface , " connectable " ) :
if interface . connectable :
ifstats [ " i2p_connectable " ] = True
else :
ifstats [ " i2p_connectable " ] = False
2021-09-24 14:10:04 -04:00
2022-02-26 15:04:54 -05:00
if hasattr ( interface , " b32 " ) :
if interface . b32 != None :
ifstats [ " i2p_b32 " ] = interface . b32 + " .b32.i2p "
else :
ifstats [ " i2p_b32 " ] = None
2022-11-03 12:49:25 -04:00
if hasattr ( interface , " i2p_tunnel_state " ) :
if interface . i2p_tunnel_state != None :
state_description = " Unknown State "
if interface . i2p_tunnel_state == I2PInterface . I2PInterfacePeer . TUNNEL_STATE_ACTIVE :
state_description = " Tunnel Active "
elif interface . i2p_tunnel_state == I2PInterface . I2PInterfacePeer . TUNNEL_STATE_INIT :
state_description = " Creating Tunnel "
elif interface . i2p_tunnel_state == I2PInterface . I2PInterfacePeer . TUNNEL_STATE_STALE :
state_description = " Tunnel Unresponsive "
ifstats [ " tunnelstate " ] = state_description
else :
ifstats [ " tunnelstate " ] = None
2023-09-13 14:07:07 -04:00
if hasattr ( interface , " r_airtime_short " ) :
ifstats [ " airtime_short " ] = interface . r_airtime_short
if hasattr ( interface , " r_airtime_long " ) :
ifstats [ " airtime_long " ] = interface . r_airtime_long
if hasattr ( interface , " r_channel_load_short " ) :
ifstats [ " channel_load_short " ] = interface . r_channel_load_short
if hasattr ( interface , " r_channel_load_long " ) :
ifstats [ " channel_load_long " ] = interface . r_channel_load_long
2022-04-17 13:07:32 -04:00
if hasattr ( interface , " bitrate " ) :
if interface . bitrate != None :
ifstats [ " bitrate " ] = interface . bitrate
else :
ifstats [ " bitrate " ] = None
if hasattr ( interface , " peers " ) :
if interface . peers != None :
ifstats [ " peers " ] = len ( interface . peers )
else :
ifstats [ " peers " ] = None
2022-04-27 13:00:09 -04:00
if hasattr ( interface , " ifac_signature " ) :
ifstats [ " ifac_signature " ] = interface . ifac_signature
ifstats [ " ifac_size " ] = interface . ifac_size
ifstats [ " ifac_netname " ] = interface . ifac_netname
else :
ifstats [ " ifac_signature " ] = None
ifstats [ " ifac_size " ] = None
ifstats [ " ifac_netname " ] = None
2022-04-18 10:41:38 -04:00
if hasattr ( interface , " announce_queue " ) :
if interface . announce_queue != None :
ifstats [ " announce_queue " ] = len ( interface . announce_queue )
else :
ifstats [ " announce_queue " ] = None
2021-09-24 14:10:04 -04:00
ifstats [ " name " ] = str ( interface )
ifstats [ " rxb " ] = interface . rxb
ifstats [ " txb " ] = interface . txb
2023-09-30 13:13:58 -04:00
ifstats [ " incoming_announce_frequency " ] = interface . incoming_announce_frequency ( )
ifstats [ " outgoing_announce_frequency " ] = interface . outgoing_announce_frequency ( )
2023-09-30 18:16:32 -04:00
ifstats [ " held_announces " ] = len ( interface . held_announces )
2021-09-24 14:10:04 -04:00
ifstats [ " status " ] = interface . online
2022-04-16 17:26:57 -04:00
ifstats [ " mode " ] = interface . mode
2022-04-17 13:07:32 -04:00
2022-04-20 03:59:58 -04:00
interfaces . append ( ifstats )
stats = { }
stats [ " interfaces " ] = interfaces
if Reticulum . transport_enabled ( ) :
stats [ " transport_id " ] = RNS . Transport . identity . hash
2023-09-18 09:45:55 -04:00
stats [ " transport_uptime " ] = time . time ( ) - RNS . Transport . start_time
2023-09-21 12:48:08 -04:00
if Reticulum . probe_destination_enabled ( ) :
stats [ " probe_responder " ] = RNS . Transport . probe_destination . hash
else :
stats [ " probe_responder " ] = None
2021-09-24 14:10:04 -04:00
return stats
2024-08-29 05:17:07 -04:00
def get_path_table ( self , max_hops = None ) :
2022-04-20 04:40:51 -04:00
if self . is_connected_to_shared_instance :
rpc_connection = multiprocessing . connection . Client ( self . rpc_addr , authkey = self . rpc_key )
2024-08-29 05:17:07 -04:00
rpc_connection . send ( { " get " : " path_table " , " max_hops " : max_hops } )
2022-04-20 04:40:51 -04:00
response = rpc_connection . recv ( )
return response
else :
path_table = [ ]
for dst_hash in RNS . Transport . destination_table :
2024-08-29 05:17:07 -04:00
path_hops = RNS . Transport . destination_table [ dst_hash ] [ 2 ]
if max_hops == None or path_hops < = max_hops :
entry = {
" hash " : dst_hash ,
" timestamp " : RNS . Transport . destination_table [ dst_hash ] [ 0 ] ,
" via " : RNS . Transport . destination_table [ dst_hash ] [ 1 ] ,
" hops " : path_hops ,
" expires " : RNS . Transport . destination_table [ dst_hash ] [ 3 ] ,
" interface " : str ( RNS . Transport . destination_table [ dst_hash ] [ 5 ] ) ,
}
path_table . append ( entry )
2022-04-20 04:40:51 -04:00
return path_table
2022-05-14 16:14:38 -04:00
def get_rate_table ( self ) :
if self . is_connected_to_shared_instance :
rpc_connection = multiprocessing . connection . Client ( self . rpc_addr , authkey = self . rpc_key )
rpc_connection . send ( { " get " : " rate_table " } )
response = rpc_connection . recv ( )
return response
else :
rate_table = [ ]
for dst_hash in RNS . Transport . announce_rate_table :
entry = {
" hash " : dst_hash ,
" last " : RNS . Transport . announce_rate_table [ dst_hash ] [ " last " ] ,
" rate_violations " : RNS . Transport . announce_rate_table [ dst_hash ] [ " rate_violations " ] ,
" blocked_until " : RNS . Transport . announce_rate_table [ dst_hash ] [ " blocked_until " ] ,
" timestamps " : RNS . Transport . announce_rate_table [ dst_hash ] [ " timestamps " ] ,
}
rate_table . append ( entry )
return rate_table
2022-04-20 05:12:21 -04:00
def drop_path ( self , destination ) :
if self . is_connected_to_shared_instance :
rpc_connection = multiprocessing . connection . Client ( self . rpc_addr , authkey = self . rpc_key )
rpc_connection . send ( { " drop " : " path " , " destination_hash " : destination } )
response = rpc_connection . recv ( )
return response
else :
return RNS . Transport . expire_path ( destination )
2023-10-01 05:39:07 -04:00
def drop_all_via ( self , transport_hash ) :
if self . is_connected_to_shared_instance :
rpc_connection = multiprocessing . connection . Client ( self . rpc_addr , authkey = self . rpc_key )
rpc_connection . send ( { " drop " : " all_via " , " destination_hash " : transport_hash } )
response = rpc_connection . recv ( )
return response
else :
dropped_count = 0
for destination_hash in RNS . Transport . destination_table :
if RNS . Transport . destination_table [ destination_hash ] [ 1 ] == transport_hash :
RNS . Transport . expire_path ( destination_hash )
dropped_count + = 1
return dropped_count
2022-05-13 10:18:13 -04:00
def drop_announce_queues ( self ) :
if self . is_connected_to_shared_instance :
rpc_connection = multiprocessing . connection . Client ( self . rpc_addr , authkey = self . rpc_key )
rpc_connection . send ( { " drop " : " announce_queues " } )
response = rpc_connection . recv ( )
return response
else :
return RNS . Transport . drop_announce_queues ( )
2021-09-25 05:27:43 -04:00
def get_next_hop_if_name ( self , destination ) :
if self . is_connected_to_shared_instance :
rpc_connection = multiprocessing . connection . Client ( self . rpc_addr , authkey = self . rpc_key )
rpc_connection . send ( { " get " : " next_hop_if_name " , " destination_hash " : destination } )
response = rpc_connection . recv ( )
return response
2021-10-12 10:34:17 -04:00
2021-09-25 05:27:43 -04:00
else :
return str ( RNS . Transport . next_hop_interface ( destination ) )
2023-10-27 12:16:52 -04:00
def get_first_hop_timeout ( self , destination ) :
if self . is_connected_to_shared_instance :
try :
rpc_connection = multiprocessing . connection . Client ( self . rpc_addr , authkey = self . rpc_key )
rpc_connection . send ( { " get " : " first_hop_timeout " , " destination_hash " : destination } )
response = rpc_connection . recv ( )
2023-11-02 07:24:42 -04:00
2023-11-02 08:04:09 -04:00
if self . is_connected_to_shared_instance and hasattr ( self , " _force_shared_instance_bitrate " ) and self . _force_shared_instance_bitrate :
2023-11-02 07:24:42 -04:00
simulated_latency = ( ( 1 / self . _force_shared_instance_bitrate ) * 8 ) * RNS . Reticulum . MTU
RNS . log ( " Adding simulated latency of " + RNS . prettytime ( simulated_latency ) + " to first hop timeout " , RNS . LOG_DEBUG )
response + = simulated_latency
2023-10-27 12:16:52 -04:00
return response
except Exception as e :
RNS . log ( " An error occurred while getting first hop timeout from shared instance: " + str ( e ) , RNS . LOG_ERROR )
return RNS . Reticulum . DEFAULT_PER_HOP_TIMEOUT
else :
return RNS . Transport . first_hop_timeout ( destination )
2021-09-25 05:27:43 -04:00
def get_next_hop ( self , destination ) :
if self . is_connected_to_shared_instance :
rpc_connection = multiprocessing . connection . Client ( self . rpc_addr , authkey = self . rpc_key )
rpc_connection . send ( { " get " : " next_hop " , " destination_hash " : destination } )
response = rpc_connection . recv ( )
2024-08-29 08:54:40 -04:00
# TODO: Remove this debugging function
# if not response:
# response = RNS.Transport.next_hop(destination)
2021-09-25 05:27:43 -04:00
return response
2021-10-12 10:34:17 -04:00
2021-09-25 05:27:43 -04:00
else :
return RNS . Transport . next_hop ( destination )
2024-05-25 19:28:40 -04:00
def get_link_count ( self ) :
if self . is_connected_to_shared_instance :
rpc_connection = multiprocessing . connection . Client ( self . rpc_addr , authkey = self . rpc_key )
rpc_connection . send ( { " get " : " link_count " } )
response = rpc_connection . recv ( )
return response
else :
return len ( RNS . Transport . link_table )
2021-10-12 10:34:17 -04:00
def get_packet_rssi ( self , packet_hash ) :
if self . is_connected_to_shared_instance :
rpc_connection = multiprocessing . connection . Client ( self . rpc_addr , authkey = self . rpc_key )
rpc_connection . send ( { " get " : " packet_rssi " , " packet_hash " : packet_hash } )
response = rpc_connection . recv ( )
return response
else :
for entry in RNS . Transport . local_client_rssi_cache :
if entry [ 0 ] == packet_hash :
return entry [ 1 ]
return None
def get_packet_snr ( self , packet_hash ) :
if self . is_connected_to_shared_instance :
rpc_connection = multiprocessing . connection . Client ( self . rpc_addr , authkey = self . rpc_key )
rpc_connection . send ( { " get " : " packet_snr " , " packet_hash " : packet_hash } )
response = rpc_connection . recv ( )
return response
else :
for entry in RNS . Transport . local_client_snr_cache :
if entry [ 0 ] == packet_hash :
return entry [ 1 ]
return None
2023-10-27 18:05:35 -04:00
def get_packet_q ( self , packet_hash ) :
if self . is_connected_to_shared_instance :
rpc_connection = multiprocessing . connection . Client ( self . rpc_addr , authkey = self . rpc_key )
rpc_connection . send ( { " get " : " packet_q " , " packet_hash " : packet_hash } )
response = rpc_connection . recv ( )
return response
else :
for entry in RNS . Transport . local_client_q_cache :
if entry [ 0 ] == packet_hash :
return entry [ 1 ]
return None
2020-08-13 06:15:56 -04:00
@staticmethod
def should_use_implicit_proof ( ) :
2021-05-16 06:55:50 -04:00
"""
2021-05-16 09:52:45 -04:00
Returns whether proofs sent are explicit or implicit .
2021-05-16 06:55:50 -04:00
2021-05-16 09:52:45 -04:00
: returns : True if the current running configuration specifies to use implicit proofs . False if not .
2021-05-16 06:55:50 -04:00
"""
2020-08-13 06:15:56 -04:00
return Reticulum . __use_implicit_proof
@staticmethod
def transport_enabled ( ) :
2021-05-16 06:55:50 -04:00
"""
2021-05-16 09:52:45 -04:00
Returns whether Transport is enabled for the running
2021-05-16 06:55:50 -04:00
instance .
When Transport is enabled , Reticulum will
route traffic for other peers , respond to path requests
and pass announces over the network .
: returns : True if Transport is enabled , False if not .
"""
2020-08-13 06:15:56 -04:00
return Reticulum . __transport_enabled
2020-04-27 06:04:14 -04:00
2024-08-28 19:54:34 -04:00
@staticmethod
def remote_management_enabled ( ) :
"""
Returns whether remote management is enabled for the
running instance .
When remote management is enabled , authenticated peers
can remotely query and manage this instance .
: returns : True if remote management is enabled , False if not .
"""
return Reticulum . __remote_management_enabled
2023-09-21 12:48:08 -04:00
@staticmethod
def probe_destination_enabled ( ) :
return Reticulum . __allow_probes
2020-04-27 04:19:49 -04:00
# Default configuration file:
__default_rns_config__ = ''' # This is the default Reticulum config file.
# You should probably edit it to include any additional,
# interfaces and settings you might need.
2022-02-25 15:41:24 -05:00
# Only the most basic options are included in this default
# configuration. To see a more verbose, and much longer,
# configuration example, you can run the command:
# rnsd --exampleconfig
2022-02-25 15:48:25 -05:00
2020-04-27 04:19:49 -04:00
[ reticulum ]
# If you enable Transport, your system will route traffic
# for other peers, pass announces and serve path requests.
2022-04-06 09:51:27 -04:00
# This should only be done for systems that are suited to
# act as transport nodes, ie. if they are stationary and
2021-08-20 05:23:35 -04:00
# always-on. This directive is optional and can be removed
# for brevity.
2020-05-13 10:03:50 -04:00
2020-04-27 04:19:49 -04:00
enable_transport = False
2020-05-13 10:03:50 -04:00
# By default, the first program to launch the Reticulum
# Network Stack will create a shared instance, that other
# programs can communicate with. Only the shared instance
# opens all the configured interfaces directly, and other
# local programs communicate with the shared instance over
# a local socket. This is completely transparent to the
# user, and should generally be turned on. This directive
# is optional and can be removed for brevity.
share_instance = Yes
# If you want to run multiple *different* shared instances
2021-09-24 08:13:31 -04:00
# on the same system, you will need to specify different
# shared instance ports for each. The defaults are given
# below, and again, these options can be left out if you
# don't need them.
2020-05-13 10:03:50 -04:00
shared_instance_port = 37428
2021-09-24 08:13:31 -04:00
instance_control_port = 37429
2020-05-13 10:03:50 -04:00
2022-02-25 15:48:25 -05:00
2021-09-18 16:49:04 -04:00
# You can configure Reticulum to panic and forcibly close
# if an unrecoverable interface error occurs, such as the
# hardware device for an interface disappearing. This is
# an optional directive, and can be left out for brevity.
# This behaviour is disabled by default.
panic_on_interface_error = No
2020-05-13 10:03:50 -04:00
2020-04-27 04:19:49 -04:00
[ logging ]
# Valid log levels are 0 through 7:
# 0: Log only critical information
# 1: Log errors and lower log levels
# 2: Log warnings and lower log levels
# 3: Log notices and lower log levels
# 4: Log info and lower (this is the default)
# 5: Verbose logging
# 6: Debug logging
# 7: Extreme logging
loglevel = 4
# The interfaces section defines the physical and virtual
# interfaces Reticulum will use to communicate on. This
# section will contain examples for a variety of interface
# types. You can modify these or use them as a basis for
# your own config, or simply remove the unused ones.
[ interfaces ]
# This interface enables communication with other
2021-12-09 10:07:36 -05:00
# link-local Reticulum nodes over UDP. It does not
# need any functional IP infrastructure like routers
# or DHCP servers, but will require that at least link-
# local IPv6 is enabled in your operating system, which
# should be enabled by default in almost any OS. See
# the Reticulum Manual for more configuration options.
[ [ Default Interface ] ]
type = AutoInterface
2022-05-25 17:10:05 -04:00
enabled = Yes
2021-12-09 10:07:36 -05:00
''' .splitlines()