def client(destination_hexhash, configpath): # We need a binary representation of the destination # hash that was entered on the command line try: if len(destination_hexhash) != 20: raise ValueError( "Destination length is invalid, must be 20 hexadecimal characters (10 bytes)" ) destination_hash = bytes.fromhex(destination_hexhash) except: RNS.log("Invalid destination entered. Check your input!\n") exit() # We must first initialise Reticulum reticulum = RNS.Reticulum(configpath) # Check if we know a path to the destination if not RNS.Transport.hasPath(destination_hash): RNS.log( "Destination is not yet known. Requesting path and waiting for announce to arrive..." ) RNS.Transport.requestPath(destination_hash) while not RNS.Transport.hasPath(destination_hash): time.sleep(0.1) # Recall the server identity server_identity = RNS.Identity.recall(destination_hash) # Inform the user that we'll begin connecting RNS.log("Establishing link with server...") # When the server identity is known, we set # up a destination server_destination = RNS.Destination(server_identity, RNS.Destination.OUT, RNS.Destination.SINGLE, APP_NAME, "filetransfer", "server") # We also want to automatically prove incoming packets server_destination.set_proof_strategy(RNS.Destination.PROVE_ALL) # And create a link link = RNS.Link(server_destination) # We expect any normal data packets on the link # to contain a list of served files, so we set # a callback accordingly link.packet_callback(filelist_received) # We'll also set up functions to inform the # user when the link is established or closed link.link_established_callback(link_established) link.link_closed_callback(link_closed) # And set the link to automatically begin # downloading advertised resources link.set_resource_strategy(RNS.Link.ACCEPT_ALL) link.resource_started_callback(download_began) link.resource_concluded_callback(download_concluded) menu()
def validateRequest(owner, data, packet): if len(data) == (Link.ECPUBSIZE): try: link = Link(owner=owner, peer_pub_bytes=data[:Link.ECPUBSIZE]) link.setLinkID(packet) link.destination = packet.destination RNS.log( "Validating link request " + RNS.prettyhexrep(link.link_id), RNS.LOG_VERBOSE) link.handshake() link.attached_interface = packet.receiving_interface link.prove() link.request_time = time.time() RNS.Transport.registerLink(link) link.last_inbound = time.time() link.start_watchdog() # TODO: Why was link_established callback here? Seems weird # to call this before RTT packet has been received #if self.owner.callbacks.link_established != None: # self.owner.callbacks.link_established(link) RNS.log("Incoming link request " + str(link) + " accepted", RNS.LOG_VERBOSE) return link except Exception as e: RNS.log("Validating link request failed", RNS.LOG_VERBOSE) traceback.print_exc() return None else: RNS.log("Invalid link request payload size, dropping request", RNS.LOG_VERBOSE) return None
def packet_delivered(receipt): if receipt.status == RNS.PacketReceipt.DELIVERED: rtt = receipt.rtt() if (rtt >= 1): rtt = round(rtt, 3) rttstring = str(rtt)+" seconds" else: rtt = round(rtt*1000, 3) rttstring = str(rtt)+" milliseconds" RNS.log("Valid reply received from "+RNS.prettyhexrep(receipt.destination.hash)+", round-trip time is "+rttstring)
def prove(self, destination=None): if self.fromPacked and hasattr(self, "destination") and self.destination: if self.destination.identity and self.destination.identity.prv: self.destination.identity.prove(self, destination) elif self.fromPacked and hasattr(self, "link") and self.link: self.link.prove_packet(self) else: RNS.log( "Could not prove packet associated with neither a destination nor a link", RNS.LOG_ERROR)
def link_established(link): # We store a reference to the link # instance for later use global server_link server_link = link # Inform the user that the server is # connected RNS.log( "Link established with server, enter some text to send, or \"quit\" to quit" )
def request_next(self): while self.receiving_part: sleep(0.001) if not self.status == Resource.FAILED: if not self.waiting_for_hmu: self.outstanding_parts = 0 hashmap_exhausted = Resource.HASHMAP_IS_NOT_EXHAUSTED requested_hashes = b"" offset = (1 if self.consecutive_completed_height > 0 else 0) i = 0 pn = self.consecutive_completed_height + offset search_start = pn for part in self.parts[search_start:search_start + self.window]: if part == None: part_hash = self.hashmap[pn] if part_hash != None: requested_hashes += part_hash self.outstanding_parts += 1 i += 1 else: hashmap_exhausted = Resource.HASHMAP_IS_EXHAUSTED pn += 1 if i >= self.window or hashmap_exhausted == Resource.HASHMAP_IS_EXHAUSTED: break hmu_part = bytes([hashmap_exhausted]) if hashmap_exhausted == Resource.HASHMAP_IS_EXHAUSTED: last_map_hash = self.hashmap[self.hashmap_height - 1] hmu_part += last_map_hash self.waiting_for_hmu = True requested_data = b"" request_data = hmu_part + self.hash + requested_hashes request_packet = RNS.Packet(self.link, request_data, context=RNS.Packet.RESOURCE_REQ) try: request_packet.send() self.last_activity = time.time() self.req_sent = self.last_activity self.req_resp = None except Exception as e: RNS.log( "Could not send resource request packet, cancelling resource", RNS.LOG_DEBUG) RNS.log("The contained exception was: " + str(e), RNS.LOG_DEBUG) self.cancel()
def cache_request_packet(packet): if len(packet.data) == RNS.Identity.HASHLENGTH / 8: packet_hash = RNS.hexrep(packet.data, delimit=False) # TODO: There's some pretty obvious file access # issues here. Make sure this can't happen path = RNS.Reticulum.cachepath + "/" + packet_hash if os.path.isfile(path): file = open(path, "r") raw = file.read() file.close() packet = RNS.Packet(None, raw)
def updateBitrate(self): try: self.bitrate = self.r_sf * ((4.0 / self.cr) / (math.pow(2, self.r_sf) / (self.r_bandwidth / 1000))) * 1000 self.bitrate_kbps = round(self.bitrate / 1000.0, 2) RNS.log( str(self) + " On-air bitrate is now " + str(self.bitrate_kbps) + " kbps", RNS.LOG_DEBUG) except: self.bitrate = 0
def requestPath(destination_hash): path_request_data = destination_hash + RNS.Identity.getRandomHash() path_request_dst = RNS.Destination(None, RNS.Destination.OUT, RNS.Destination.PLAIN, Transport.APP_NAME, "path", "request") packet = RNS.Packet(path_request_dst, path_request_data, packet_type=RNS.Packet.DATA, transport_type=RNS.Transport.BROADCAST, header_type=RNS.Packet.HEADER_1) packet.send()
def resend(self): if self.sent: if RNS.Transport.outbound(self): return self.receipt else: RNS.log("No interfaces could process the outbound packet", RNS.LOG_ERROR) self.sent = False self.receipt = None return False else: raise IOError("Packet was not sent yet")
def decrypt(self, ciphertext): if self.__encryption_disabled: return ciphertext try: fernet = Fernet(base64.urlsafe_b64encode(self.derived_key)) plaintext = fernet.decrypt(base64.urlsafe_b64encode(ciphertext)) return plaintext except Exception as e: RNS.log( "Decryption failed on link " + str(self) + ". The contained exception was: " + str(e), RNS.LOG_ERROR) traceback.print_exc()
def encrypt(self, plaintext): if self.__encryption_disabled: return plaintext try: fernet = Fernet(base64.urlsafe_b64encode(self.derived_key)) ciphertext = base64.urlsafe_b64decode(fernet.encrypt(plaintext)) return ciphertext except Exception as e: RNS.log( "Encryption on link " + str(self) + " failed. The contained exception was: " + str(e), RNS.LOG_ERROR)
def server_packet_received(message, packet): global latest_client_link # When data is received over any active link, # it will all be directed to the last client # that connected. text = message.decode("utf-8") RNS.log("Received data on the link: "+text) reply_text = "I received \""+text+"\" over the link" reply_data = reply_text.encode("utf-8") RNS.Packet(latest_client_link, reply_data).send()
def clean_links(self): closed_links = [] for link_hash in self.direct_links: link = self.direct_links[link_hash] inactive_time = link.inactive_for() if inactive_time > LXMRouter.LINK_MAX_INACTIVITY: link.teardown() closed_links.append(link_hash) for link_hash in closed_links: cleaned_link = self.direct_links.pop(link_hash) RNS.log("Cleaned link " + str(cleaned_link), RNS.LOG_DEBUG)
def validateAnnounce(packet): if packet.packet_type == RNS.Packet.ANNOUNCE: RNS.log("Validating announce from "+RNS.prettyhexrep(packet.destination_hash), RNS.LOG_VERBOSE) destination_hash = packet.destination_hash public_key = packet.data[10:Identity.DERKEYSIZE/8+10] random_hash = packet.data[Identity.DERKEYSIZE/8+10:Identity.DERKEYSIZE/8+20] signature = packet.data[Identity.DERKEYSIZE/8+20:Identity.DERKEYSIZE/8+20+Identity.KEYSIZE/8] app_data = "" if len(packet.data) > Identity.DERKEYSIZE/8+20+Identity.KEYSIZE/8: app_data = packet.data[Identity.DERKEYSIZE/8+20+Identity.KEYSIZE/8:] signed_data = destination_hash+public_key+random_hash+app_data announced_identity = Identity(public_only=True) announced_identity.loadPublicKey(public_key) if announced_identity.pub != None and announced_identity.validate(signature, signed_data): RNS.Identity.remember(RNS.Identity.fullHash(packet.raw), destination_hash, public_key) RNS.log("Stored valid announce from "+RNS.prettyhexrep(destination_hash), RNS.LOG_INFO) del announced_identity return True else: RNS.log("Received invalid announce", RNS.LOG_DEBUG) del announced_identity return False
def __as_packet(self): if not self.packed: self.pack() if not self.__delivery_destination: raise ValueError( "Can't synthesize packet for LXMF message before delivery destination is known" ) if self.method == LXMessage.OPPORTUNISTIC: return RNS.Packet(self.__delivery_destination, self.packed[LXMessage.DESTINATION_LENGTH:]) elif self.method == LXMessage.DIRECT or self.method == LXMessage.PROPAGATED: return RNS.Packet(self.__delivery_destination, self.packed)
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.__allow_unencrypted = False Reticulum.__use_implicit_proof = True if not os.path.isdir(Reticulum.storagepath): os.makedirs(Reticulum.storagepath) if not os.path.isdir(Reticulum.cachepath): os.makedirs(Reticulum.cachepath) if os.path.isfile(self.configpath): self.config = ConfigObj(self.configpath) RNS.log("Configuration loaded from "+self.configpath) 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() Reticulum.router = self RNS.Transport.start() atexit.register(Reticulum.exit_handler)
def start_local_interface(self): if self.share_instance: try: interface = LocalInterface.LocalServerInterface( RNS.Transport, self.local_interface_port ) interface.OUT = True RNS.Transport.interfaces.append(interface) self.is_shared_instance = True RNS.log("Started shared instance interface: "+str(interface), RNS.LOG_DEBUG) 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 RNS.Transport.interfaces.append(interface) self.is_shared_instance = False self.is_standalone_instance = False self.is_connected_to_shared_instance = True RNS.log("Connected to local shared instance via: "+str(interface), RNS.LOG_DEBUG) 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
def processOutgoing(self, data): if self.online: if self.interface_ready: if self.flow_control: self.interface_ready = False encoded_dst_ssid = bytes([0x60 | (self.dst_ssid << 1)]) encoded_src_ssid = bytes([0x60 | (self.src_ssid << 1) | 0x01]) addr = b"" for i in range(0, 6): if (i < len(self.dst_call)): addr += bytes([self.dst_call[i] << 1]) else: addr += bytes([0x20]) addr += encoded_dst_ssid for i in range(0, 6): if (i < len(self.src_call)): addr += bytes([self.src_call[i] << 1]) else: addr += bytes([0x20]) addr += encoded_src_ssid data = addr + bytes([AX25.CTRL_UI]) + bytes( [AX25.PID_NOLAYER3]) + data data = data.replace(bytes([0xdb]), bytes([0xdb]) + bytes([0xdd])) data = data.replace(bytes([0xc0]), bytes([0xdb]) + bytes([0xdc])) kiss_frame = bytes([KISS.FEND]) + bytes([0x00]) + data + bytes( [KISS.FEND]) if (self.txdelay > 0): RNS.log( str(self.name) + " delaying TX for " + str(self.txdelay) + " seconds", RNS.LOG_EXTREME) sleep(self.txdelay) written = self.serial.write(kiss_frame) if written != len(kiss_frame): if self.flow_control: self.interface_ready = True raise IOError("AX.25 interface only wrote " + str(written) + " bytes of " + str(len(kiss_frame))) else: self.queue(data)
def announceLoop(destination_1, destination_2): # Let the user know that everything is ready RNS.log( "Announce example running, hit enter to manually send an announce (Ctrl-C to quit)" ) # We enter a loop that runs until the users exits. # If the user hits enter, we will announce our server # destination on the network, which will let clients # know how to create messages directed towards it. while True: entered = input() # Randomly select a fruit fruit = fruits[random.randint(0, len(fruits) - 1)] # Send the announce including the app data destination_1.announce(app_data=fruit.encode("utf-8")) RNS.log("Sent announce from " + RNS.prettyhexrep(destination_1.hash) + " (" + destination_1.name + ")") # Randomly select a noble gas noble_gas = noble_gases[random.randint(0, len(noble_gases) - 1)] # Send the announce including the app data destination_2.announce(app_data=noble_gas.encode("utf-8")) RNS.log("Sent announce from " + RNS.prettyhexrep(destination_2.hash) + " (" + destination_2.name + ")")
def recall(destination_hash): RNS.log("Searching for "+RNS.prettyhexrep(destination_hash)+"...", RNS.LOG_DEBUG) if destination_hash in Identity.known_destinations: identity_data = Identity.known_destinations[destination_hash] identity = Identity(public_only=True) identity.loadPublicKey(identity_data[2]) RNS.log("Found "+RNS.prettyhexrep(destination_hash)+" in known destinations", RNS.LOG_DEBUG) return identity else: RNS.log("Could not find "+RNS.prettyhexrep(destination_hash)+" in known destinations", RNS.LOG_DEBUG) return None
def incoming_connection(self, handler): interface_name = str(str(handler.client_address[1])) spawned_interface = LocalClientInterface( self.owner, name=interface_name, connected_socket=handler.request) spawned_interface.OUT = self.OUT spawned_interface.IN = self.IN spawned_interface.target_ip = handler.client_address[0] spawned_interface.target_port = str(handler.client_address[1]) spawned_interface.parent_interface = self RNS.log( "Accepting new connection to shared instance: " + str(spawned_interface), RNS.LOG_VERBOSE) RNS.Transport.interfaces.append(spawned_interface) RNS.Transport.local_client_interfaces.append(spawned_interface) spawned_interface.read_loop()
def handle_link(self, link_target): if self.status >= Browser.DISCONECTED: RNS.log("Browser handling link to: " + str(link_target), RNS.LOG_DEBUG) try: self.retrieve_url(link_target) except Exception as e: self.browser_footer = urwid.Text("Could not open link: " + str(e)) self.frame.contents["footer"] = (self.browser_footer, self.frame.options()) else: RNS.log( "Browser aleady hadling link, cannot handle link to: " + str(link_target), RNS.LOG_DEBUG)
def cache_request(packet_hash): RNS.log("Cache request for " + RNS.prettyhexrep(packet_hash), RNS.LOG_EXTREME) path = RNS.Reticulum.cachepath + "/" + RNS.hexrep(packet_hash, delimit=False) if os.path.isfile(path): file = open(path, "r") raw = file.read() Transport.inbound(raw) file.close() else: cache_request_packet = RNS.Packet( Transport.transport_destination(), packet_hash, context=RNS.Packet.CACHE_REQUEST)
def setPreamble(self, preamble): preamble_ms = preamble preamble = int(preamble_ms / 10) if preamble < 0: preamble = 0 if preamble > 255: preamble = 255 RNS.log("Setting preamble to " + str(preamble) + " " + chr(preamble)) kiss_command = KISS.FEND + KISS.CMD_TXDELAY + chr(preamble) + KISS.FEND written = self.serial.write(kiss_command) if written != len(kiss_command): raise IOError("Could not configure KISS interface preamble to " + str(preamble_ms) + " (command value " + str(preamble) + ")")
def __init__(self): self.app = NomadNetworkApp.get_shared_instance() self.app.ui = self if not self.app.force_console_log: RNS.log( "Nomad Network started in daemon mode, all further messages are logged to " + str(self.app.logfilepath), RNS.LOG_INFO, _override_destination=True) else: RNS.log("Nomad Network daemon started", RNS.LOG_INFO) while True: time.sleep(1)
def client(destination_hexhash, configpath): # We need a binary representation of the destination # hash that was entered on the command line try: if len(destination_hexhash) != 20: raise ValueError("Destination length is invalid, must be 20 hexadecimal characters (10 bytes)") destination_hash = bytes.fromhex(destination_hexhash) except: RNS.log("Invalid destination entered. Check your input!\n") exit() # We must first initialise Reticulum reticulum = RNS.Reticulum(configpath) # Check if we know a path to the destination if not RNS.Transport.has_path(destination_hash): RNS.log("Destination is not yet known. Requesting path and waiting for announce to arrive...") RNS.Transport.request_path(destination_hash) while not RNS.Transport.has_path(destination_hash): time.sleep(0.1) # Recall the server identity server_identity = RNS.Identity.recall(destination_hash) # Inform the user that we'll begin connecting RNS.log("Establishing link with server...") # When the server identity is known, we set # up a destination server_destination = RNS.Destination( server_identity, RNS.Destination.OUT, RNS.Destination.SINGLE, APP_NAME, "linkexample" ) # And create a link link = RNS.Link(server_destination) # We set a callback that will get executed # every time a packet is received over the # link link.set_packet_callback(client_packet_received) # We'll also set up functions to inform the # user when the link is established or closed link.set_link_established_callback(link_established) link.set_link_closed_callback(link_closed) # Everything is set up, so let's enter a loop # for the user to interact with the example client_loop()
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 + Link.STALE_GRACE 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! Tearing down link " + str(self) + " now.", RNS.LOG_ERROR) self.teardown() sleep_time = 0.1 sleep(sleep_time)
def __init__(self, identity, direction, type, app_name, *aspects): # Check input values and build name string if "." in app_name: raise ValueError("Dots can't be used in app names") if not type in Destination.types: raise ValueError("Unknown destination type") if not direction in Destination.directions: raise ValueError("Unknown destination direction") self.callbacks = Callbacks() self.type = type self.direction = direction self.proof_strategy = Destination.PROVE_NONE self.mtu = 0 self.links = [] if identity != None and type == Destination.SINGLE: aspects = aspects+(identity.hexhash,) if identity == None and direction == Destination.IN and self.type != Destination.PLAIN: identity = RNS.Identity() aspects = aspects+(identity.hexhash,) self.identity = identity self.name = Destination.getDestinationName(app_name, *aspects) self.hash = Destination.getDestinationHash(app_name, *aspects) self.hexhash = self.hash.encode("hex_codec") self.callback = None self.proofcallback = None RNS.Transport.registerDestination(self)
def broadcastLoop(destination): # Let the user know that everything is ready RNS.log("Broadcast example " + RNS.prettyhexrep(destination.hash) + " running, enter text and hit enter to broadcast (Ctrl-C to quit)") # We enter a loop that runs until the users exits. # If the user hits enter, we will send the information # that the user entered into the prompt. while True: print("> ", end="") entered = input() if entered != "": data = entered.encode("utf-8") packet = RNS.Packet(destination, data) packet.send()