def __watchdog_job(self): while not self.status == Link.CLOSED: while (self.watchdog_lock): sleep(max(self.rtt, 0.025)) if not self.status == Link.CLOSED: # Link was initiated, but no response # from destination yet if self.status == Link.PENDING: next_check = self.request_time + self.proof_timeout sleep_time = next_check - time.time() if time.time() >= self.request_time + self.proof_timeout: RNS.log("Link establishment timed out", RNS.LOG_VERBOSE) self.status = Link.CLOSED self.teardown_reason = Link.TIMEOUT self.link_closed() sleep_time = 0.001 elif self.status == Link.HANDSHAKE: next_check = self.request_time + self.proof_timeout sleep_time = next_check - time.time() if time.time() >= self.request_time + self.proof_timeout: RNS.log( "Timeout waiting for RTT packet from link initiator", RNS.LOG_DEBUG) self.status = Link.CLOSED self.teardown_reason = Link.TIMEOUT self.link_closed() sleep_time = 0.001 elif self.status == Link.ACTIVE: if time.time() >= self.last_inbound + self.keepalive: sleep_time = self.rtt * self.timeout_factor self.status = Link.STALE if self.initiator: self.send_keepalive() else: sleep_time = (self.last_inbound + self.keepalive) - time.time() elif self.status == Link.STALE: sleep_time = 0.001 self.status = Link.CLOSED self.teardown_reason = Link.TIMEOUT self.link_closed() if sleep_time == 0: RNS.log("Warning! Link watchdog sleep time of 0!", RNS.LOG_ERROR) if sleep_time == None or sleep_time < 0: RNS.log("Timing error! Closing Reticulum now.", RNS.LOG_CRITICAL) RNS.panic() sleep(sleep_time)
def disableEncryption(self): if (RNS.Reticulum.should_allow_unencrypted()): RNS.log( "The link " + str(self) + " was downgraded to an encryptionless link", RNS.LOG_NOTICE) self.__encryption_disabled = True else: RNS.log( "Attempt to disable encryption on link, but encryptionless links are not allowed by config.", RNS.LOG_CRITICAL) RNS.log("Shutting down Reticulum now!", RNS.LOG_CRITICAL) RNS.panic()
def __init__(self,configdir=None): if configdir != None: Reticulum.configdir = configdir Reticulum.configpath = Reticulum.configdir+"/config" Reticulum.storagepath = Reticulum.configdir+"/storage" Reticulum.cachepath = Reticulum.configdir+"/storage/cache" Reticulum.resourcepath = Reticulum.configdir+"/storage/resources" Reticulum.__allow_unencrypted = False Reticulum.__transport_enabled = False Reticulum.__use_implicit_proof = True self.local_interface_port = 37428 self.share_instance = True self.is_shared_instance = False self.is_connected_to_shared_instance = False self.is_standalone_instance = False 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) if os.path.isfile(self.configpath): try: self.config = ConfigObj(self.configpath) RNS.log("Configuration loaded from "+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...") self.createDefaultConfig() RNS.log("Default config file created. Make any necessary changes in "+Reticulum.configdir+"/config and start Reticulum again.") RNS.log("Exiting now!") exit(1) self.applyConfig() RNS.Identity.loadKnownDestinations() RNS.Transport.start(self) atexit.register(Reticulum.exit_handler)
def disable_encryption(self): """ HAZARDOUS. This will downgrade the link to encryptionless. All information over the link will be sent in plaintext. Never use this in production applications. Should only be used for debugging purposes, and will disappear in a future version. If encryptionless links are not explicitly allowed in the users configuration file, Reticulum will terminate itself along with the client application and throw an error message to the user. """ if (RNS.Reticulum.should_allow_unencrypted()): RNS.log( "The link " + str(self) + " was downgraded to an encryptionless link", RNS.LOG_NOTICE) self.__encryption_disabled = True else: RNS.log( "Attempt to disable encryption on link, but encryptionless links are not allowed by config.", RNS.LOG_CRITICAL) RNS.log("Shutting down Reticulum now!", RNS.LOG_CRITICAL) RNS.panic()
def __watchdog_job(self): self.__watchdog_job_id += 1 this_job_id = self.__watchdog_job_id while self.status < Resource.ASSEMBLING and this_job_id == self.__watchdog_job_id: while self.watchdog_lock: sleep(0.025) sleep_time = None if self.status == Resource.ADVERTISED: sleep_time = (self.adv_sent+self.default_timeout)-time.time() if sleep_time < 0: if self.retries_left <= 0: RNS.log("Resource transfer timeout after sending advertisement", RNS.LOG_DEBUG) self.cancel() sleep_time = 0.001 else: RNS.log("No part requests received, retrying resource advertisement...", RNS.LOG_DEBUG) self.retries_left -= 1 self.advertisement_packet.resend() self.last_activity = time.time() self.adv_sent = self.last_activity sleep_time = 0.001 elif self.status == Resource.TRANSFERRING: if not self.initiator: rtt = self.link.rtt if self.rtt == None else self.rtt sleep_time = self.last_activity + (rtt*self.timeout_factor) - time.time() if sleep_time < 0: if self.retries_left > 0: RNS.log("Timeout waiting for parts, requesting retry", RNS.LOG_DEBUG) sleep_time = 0.001 self.retries_left -= 1 self.waiting_for_hmu = False self.request_next() else: self.cancel() sleep_time = 0.001 else: max_wait = self.rtt * self.timeout_factor * self.max_retries + self.sender_grace_time sleep_time = self.last_activity + max_wait - time.time() if sleep_time < 0: RNS.log("Resource timed out waiting for part requests", RNS.LOG_DEBUG) self.cancel() sleep_time = 0.001 elif self.status == Resource.AWAITING_PROOF: sleep_time = self.last_part_sent + (self.rtt*self.timeout_factor+self.sender_grace_time) - time.time() if sleep_time < 0: if self.retries_left <= 0: RNS.log("Resource timed out waiting for proof", RNS.LOG_DEBUG) self.cancel() sleep_time = 0.001 else: RNS.log("All parts sent, but no resource proof received, querying network cache...", RNS.LOG_DEBUG) self.retries_left -= 1 expected_data = self.hash + self.expected_proof expected_proof_packet = RNS.Packet(self.link, expected_data, packet_type=RNS.Packet.PROOF, context=RNS.Packet.RESOURCE_PRF) expected_proof_packet.pack() expected_proof_packet.updateHash() RNS.Transport.cache_request(expected_proof_packet.packet_hash) self.last_part_sent = time.time() sleep_time = 0.001 if sleep_time == 0: RNS.log("Warning! Link watchdog sleep time of 0!", RNS.LOG_WARNING) if sleep_time == None or sleep_time < 0: # TODO: This should probably not be here forever RNS.log("Timing error! Closing Reticulum now.", RNS.LOG_CRITICAL) RNS.panic() sleep(sleep_time)
def applyConfig(self): if "logging" in self.config: for option in self.config["logging"]: value = self.config["logging"][option] if option == "loglevel": RNS.loglevel = int(value) 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 if option == "enable_transport": v = self.config["reticulum"].as_bool(option) if v == True: Reticulum.__transport_enabled = True 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 if option == "allow_unencrypted": v = self.config["reticulum"].as_bool(option) if v == True: RNS.log("", RNS.LOG_CRITICAL) RNS.log("! ! ! ! ! ! ! ! !", RNS.LOG_CRITICAL) RNS.log("", RNS.LOG_CRITICAL) RNS.log("Danger! Encryptionless links have been allowed in the config file!", RNS.LOG_CRITICAL) RNS.log("Beware of the consequences! Any data sent over a link can potentially be intercepted,", RNS.LOG_CRITICAL) RNS.log("read and modified! If you are not absolutely sure that you want this,", RNS.LOG_CRITICAL) RNS.log("you should exit Reticulum NOW and change your config file!", RNS.LOG_CRITICAL) RNS.log("", RNS.LOG_CRITICAL) RNS.log("! ! ! ! ! ! ! ! !", RNS.LOG_CRITICAL) RNS.log("", RNS.LOG_CRITICAL) Reticulum.__allow_unencrypted = True self.start_local_interface() if self.is_shared_instance or self.is_standalone_instance: interface_names = [] for name in self.config["interfaces"]: if not name in interface_names: c = self.config["interfaces"][name] try: if ("interface_enabled" in c) and c.as_bool("interface_enabled") == True: if c["type"] == "UDPInterface": interface = UDPInterface.UDPInterface( RNS.Transport, name, c["listen_ip"], int(c["listen_port"]), c["forward_ip"], int(c["forward_port"]) ) if "outgoing" in c and c.as_bool("outgoing") == True: interface.OUT = True else: interface.OUT = False RNS.Transport.interfaces.append(interface) if c["type"] == "TCPServerInterface": interface = TCPInterface.TCPServerInterface( RNS.Transport, name, c["listen_ip"], int(c["listen_port"]) ) if "outgoing" in c and c.as_bool("outgoing") == True: interface.OUT = True else: interface.OUT = False RNS.Transport.interfaces.append(interface) if c["type"] == "TCPClientInterface": interface = TCPInterface.TCPClientInterface( RNS.Transport, name, c["target_host"], int(c["target_port"]) ) if "outgoing" in c and c.as_bool("outgoing") == True: interface.OUT = True else: interface.OUT = False RNS.Transport.interfaces.append(interface) 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["outgoing"].lower() == "true": interface.OUT = True else: interface.OUT = False RNS.Transport.interfaces.append(interface) 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["outgoing"].lower() == "true": interface.OUT = True else: interface.OUT = False RNS.Transport.interfaces.append(interface) 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["outgoing"].lower() == "true": interface.OUT = True else: interface.OUT = False RNS.Transport.interfaces.append(interface) 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 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, id_callsign = id_callsign ) if "outgoing" in c and c["outgoing"].lower() == "true": interface.OUT = True else: interface.OUT = False RNS.Transport.interfaces.append(interface) else: RNS.log("Skipping disabled interface \""+name+"\"", RNS.LOG_NOTICE) 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) RNS.panic()
def __init__(self, configdir=None): """ 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. """ if configdir != None: Reticulum.configdir = configdir Reticulum.configpath = Reticulum.configdir + "/config" Reticulum.storagepath = Reticulum.configdir + "/storage" Reticulum.cachepath = Reticulum.configdir + "/storage/cache" Reticulum.resourcepath = Reticulum.configdir + "/storage/resources" Reticulum.__allow_unencrypted = False Reticulum.__transport_enabled = False Reticulum.__use_implicit_proof = True self.local_interface_port = 37428 self.share_instance = True self.is_shared_instance = False self.is_connected_to_shared_instance = False self.is_standalone_instance = False 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) if os.path.isfile(self.configpath): try: self.config = ConfigObj(self.configpath) RNS.log("Configuration loaded from " + 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..." ) self.__create_default_config() RNS.log( "Default config file created. Make any necessary changes in " + Reticulum.configdir + "/config and start Reticulum again.") RNS.log("Exiting now!") exit(1) self.__apply_config() RNS.Identity.load_known_destinations() RNS.Transport.start(self) atexit.register(Reticulum.exit_handler)