From 0196c946729070a24a50b289e4c4d18dff2af51a Mon Sep 17 00:00:00 2001 From: Mark Qvist Date: Wed, 4 Apr 2018 14:14:22 +0200 Subject: [PATCH] Refactored to new naming scheme. Implemented KISS interface. --- FPE/FlexPE.py | 111 ----------- {FPE => RNS}/Destination.py | 10 +- {FPE => RNS}/Identity.py | 44 ++--- {FPE => RNS}/Interfaces/Interface.py | 0 {FPE => RNS}/Interfaces/SerialInterface.py | 16 +- RNS/Interfaces/SerialKISSInterface.py | 214 +++++++++++++++++++++ {FPE => RNS}/Interfaces/UdpInterface.py | 6 +- {FPE => RNS}/Interfaces/__init__.py | 0 {FPE => RNS}/Packet.py | 12 +- RNS/Reticulum.py | 165 ++++++++++++++++ {FPE => RNS}/Transport.py | 33 ++-- {FPE => RNS}/Utilities/Echo.py | 54 +++--- {FPE => RNS}/__init__.py | 2 +- {FPE => RNS}/vendor/__init__.py | 0 {FPE => RNS}/vendor/configobj.py | 0 t.py | 14 +- 16 files changed, 476 insertions(+), 205 deletions(-) delete mode 100755 FPE/FlexPE.py rename {FPE => RNS}/Destination.py (96%) rename {FPE => RNS}/Identity.py (80%) rename {FPE => RNS}/Interfaces/Interface.py (100%) rename {FPE => RNS}/Interfaces/SerialInterface.py (81%) create mode 100644 RNS/Interfaces/SerialKISSInterface.py rename {FPE => RNS}/Interfaces/UdpInterface.py (88%) rename {FPE => RNS}/Interfaces/__init__.py (100%) rename {FPE => RNS}/Packet.py (90%) create mode 100755 RNS/Reticulum.py rename {FPE => RNS}/Transport.py (59%) rename {FPE => RNS}/Utilities/Echo.py (83%) rename {FPE => RNS}/__init__.py (97%) rename {FPE => RNS}/vendor/__init__.py (100%) rename {FPE => RNS}/vendor/configobj.py (100%) diff --git a/FPE/FlexPE.py b/FPE/FlexPE.py deleted file mode 100755 index 93c9828..0000000 --- a/FPE/FlexPE.py +++ /dev/null @@ -1,111 +0,0 @@ -from Interfaces import * -import ConfigParser -from vendor.configobj import ConfigObj -import atexit -import struct -import array -import os.path -import os - -import FPE - -class FlexPE: - MTU = 500 - router = None - config = None - - configdir = os.path.expanduser("~")+"/.flexpe" - configpath = "" - storagepath = "" - cachepath = "" - - def __init__(self,configdir=None): - if configdir != None: - FlexPE.configdir = configdir - - FlexPE.configpath = FlexPE.configdir+"/config" - FlexPE.storagepath = FlexPE.configdir+"/storage" - FlexPE.cachepath = FlexPE.configdir+"/storage/cache" - - if not os.path.isdir(FlexPE.storagepath): - os.makedirs(FlexPE.storagepath) - - if not os.path.isdir(FlexPE.cachepath): - os.makedirs(FlexPE.cachepath) - - if os.path.isfile(self.configpath): - self.config = ConfigObj(self.configpath) - FPE.log("Configuration loaded from "+self.configpath) - else: - FPE.log("Could not load config file, creating default configuration...") - self.createDefaultConfig() - - self.applyConfig() - FPE.Identity.loadKnownDestinations() - FlexPE.router = self - - atexit.register(FPE.Identity.exitHandler) - - def applyConfig(self): - if "logging" in self.config: - for option in self.config["logging"]: - value = self.config["logging"][option] - if option == "loglevel": - FPE.loglevel = int(value) - - for name in self.config["interfaces"]: - c = self.config["interfaces"][name] - try: - if c["type"] == "UdpInterface": - interface = UdpInterface.UdpInterface( - FPE.Transport, - c["listen_ip"], - int(c["listen_port"]), - c["forward_ip"], - int(c["forward_port"]) - ) - - if c["use_as_outgoing"].lower() == "true": - interface.OUT = True - - interface.name = name - FPE.Transport.interfaces.append(interface) - - if c["type"] == "SerialInterface": - interface = SerialInterface.SerialInterface( - FPE.Transport, - c["port"], - int(c["speed"]), - int(c["databits"]), - c["parity"], - int(c["stopbits"]) - ) - - if c["use_as_outgoing"].lower() == "true": - interface.OUT = True - - interface.name = name - FPE.Transport.interfaces.append(interface) - - except Exception as e: - FPE.log("The interface \""+name+"\" could not be created. Check your configuration file for errors!", FPE.LOG_ERROR) - FPE.log("The contained exception was: "+str(e), FPE.LOG_ERROR) - - - - - def createDefaultConfig(self): - self.config = ConfigObj() - self.config.filename = FlexPE.configpath - self.config["interfaces"] = {} - self.config["interfaces"]["Default UDP Interface"] = {} - self.config["interfaces"]["Default UDP Interface"]["type"] = "UdpInterface" - self.config["interfaces"]["Default UDP Interface"]["listen_ip"] = "0.0.0.0" - self.config["interfaces"]["Default UDP Interface"]["listen_port"] = 7777 - self.config["interfaces"]["Default UDP Interface"]["forward_ip"] = "255.255.255.255" - self.config["interfaces"]["Default UDP Interface"]["forward_port"] = 7777 - self.config["interfaces"]["Default UDP Interface"]["use_as_outgoing"] = "true" - if not os.path.isdir(FlexPE.configdir): - os.makedirs(FlexPE.configdir) - self.config.write() - self.applyConfig() \ No newline at end of file diff --git a/FPE/Destination.py b/RNS/Destination.py similarity index 96% rename from FPE/Destination.py rename to RNS/Destination.py index 160a448..3279954 100755 --- a/FPE/Destination.py +++ b/RNS/Destination.py @@ -1,6 +1,6 @@ import base64 import math -import FPE +import RNS from cryptography.fernet import Fernet from cryptography.hazmat.primitives import hashes @@ -11,8 +11,8 @@ from cryptography.hazmat.primitives.asymmetric import padding class Destination: - KEYSIZE = FPE.Identity.KEYSIZE; - PADDINGSIZE= FPE.Identity.PADDINGSIZE; + KEYSIZE = RNS.Identity.KEYSIZE; + PADDINGSIZE= RNS.Identity.PADDINGSIZE; # Constants SINGLE = 0x00 @@ -80,7 +80,7 @@ class Destination: self.callback = None self.proofcallback = None - FPE.Transport.registerDestination(self) + RNS.Transport.registerDestination(self) def __str__(self): @@ -192,5 +192,5 @@ class Destination: if app_data != None: announce_data += app_data - FPE.Packet(self, announce_data, FPE.Packet.ANNOUNCE).send() + RNS.Packet(self, announce_data, RNS.Packet.ANNOUNCE).send() diff --git a/FPE/Identity.py b/RNS/Identity.py similarity index 80% rename from FPE/Identity.py rename to RNS/Identity.py index dfad666..d585bcd 100644 --- a/FPE/Identity.py +++ b/RNS/Identity.py @@ -1,7 +1,7 @@ import base64 import math import os -import FPE +import RNS import time import atexit import cPickle @@ -38,40 +38,40 @@ class Identity: @staticmethod def remember(packet_hash, destination_hash, public_key, app_data = None): - FPE.log("Remembering "+FPE.prettyhexrep(destination_hash), FPE.LOG_VERBOSE) + RNS.log("Remembering "+RNS.prettyhexrep(destination_hash), RNS.LOG_VERBOSE) Identity.known_destinations[destination_hash] = [time.time(), packet_hash, public_key, app_data] @staticmethod def recall(destination_hash): - FPE.log("Searching for "+FPE.prettyhexrep(destination_hash)+"...", FPE.LOG_DEBUG) + 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]) - FPE.log("Found "+FPE.prettyhexrep(destination_hash)+" in known destinations", FPE.LOG_DEBUG) + RNS.log("Found "+RNS.prettyhexrep(destination_hash)+" in known destinations", RNS.LOG_DEBUG) return identity else: - FPE.log("Could not find "+FPE.prettyhexrep(destination_hash)+" in known destinations", FPE.LOG_DEBUG) + RNS.log("Could not find "+RNS.prettyhexrep(destination_hash)+" in known destinations", RNS.LOG_DEBUG) return None @staticmethod def saveKnownDestinations(): - FPE.log("Saving known destinations to storage...", FPE.LOG_VERBOSE) - file = open(FPE.FlexPE.storagepath+"/known_destinations","w") + RNS.log("Saving known destinations to storage...", RNS.LOG_VERBOSE) + file = open(RNS.Reticulum.storagepath+"/known_destinations","w") cPickle.dump(Identity.known_destinations, file) file.close() - FPE.log("Done saving known destinations to storage", FPE.LOG_VERBOSE) + RNS.log("Done saving known destinations to storage", RNS.LOG_VERBOSE) @staticmethod def loadKnownDestinations(): - if os.path.isfile(FPE.FlexPE.storagepath+"/known_destinations"): - file = open(FPE.FlexPE.storagepath+"/known_destinations","r") + if os.path.isfile(RNS.Reticulum.storagepath+"/known_destinations"): + file = open(RNS.Reticulum.storagepath+"/known_destinations","r") Identity.known_destinations = cPickle.load(file) file.close() - FPE.log("Loaded "+str(len(Identity.known_destinations))+" known destinations from storage", FPE.LOG_VERBOSE) + RNS.log("Loaded "+str(len(Identity.known_destinations))+" known destinations from storage", RNS.LOG_VERBOSE) else: - FPE.log("Destinations file does not exist, so no known destinations loaded", FPE.LOG_VERBOSE) + RNS.log("Destinations file does not exist, so no known destinations loaded", RNS.LOG_VERBOSE) @staticmethod def fullHash(data): @@ -89,8 +89,8 @@ class Identity: @staticmethod def validateAnnounce(packet): - if packet.packet_type == FPE.Packet.ANNOUNCE: - FPE.log("Validating announce from "+FPE.prettyhexrep(packet.destination_hash), FPE.LOG_VERBOSE) + 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] @@ -105,13 +105,13 @@ class Identity: announced_identity.loadPublicKey(public_key) if announced_identity.pub != None and announced_identity.validate(signature, signed_data): - FPE.log("Announce is valid", FPE.LOG_VERBOSE) - FPE.Identity.remember(FPE.Identity.fullHash(packet.raw), destination_hash, public_key) - FPE.log("Stored valid announce from "+FPE.prettyhexrep(destination_hash), FPE.LOG_INFO) + RNS.log("Announce is valid", RNS.LOG_VERBOSE) + 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: - FPE.log("Announce is invalid", FPE.LOG_VERBOSE) + RNS.log("Announce is invalid", RNS.LOG_VERBOSE) del announced_identity return False @@ -139,7 +139,7 @@ class Identity: self.updateHashes() - FPE.log("Identity keys created for "+FPE.prettyhexrep(self.hash), FPE.LOG_INFO) + RNS.log("Identity keys created for "+RNS.prettyhexrep(self.hash), RNS.LOG_INFO) def getPrivateKey(self): return self.prv_bytes @@ -163,7 +163,7 @@ class Identity: self.pub = load_der_public_key(self.pub_bytes, backend=default_backend()) self.updateHashes() except Exception as e: - FPE.log("Error while loading public key, the contained exception was: "+str(e), FPE.LOG_ERROR) + RNS.log("Error while loading public key, the contained exception was: "+str(e), RNS.LOG_ERROR) def updateHashes(self): self.hash = Identity.truncatedHash(self.pub_bytes) @@ -223,7 +223,7 @@ class Identity: ) ) except: - FPE.log("Decryption by "+FPE.prettyhexrep(self.hash)+" failed") + RNS.log("Decryption by "+RNS.prettyhexrep(self.hash)+" failed") return plaintext; else: @@ -264,7 +264,7 @@ class Identity: def prove(self, packet, destination): proof_data = packet.packet_hash + self.sign(packet.packet_hash) - proof = FPE.Packet(destination, proof_data, FPE.Packet.PROOF) + proof = RNS.Packet(destination, proof_data, RNS.Packet.PROOF) proof.send() diff --git a/FPE/Interfaces/Interface.py b/RNS/Interfaces/Interface.py similarity index 100% rename from FPE/Interfaces/Interface.py rename to RNS/Interfaces/Interface.py diff --git a/FPE/Interfaces/SerialInterface.py b/RNS/Interfaces/SerialInterface.py similarity index 81% rename from FPE/Interfaces/SerialInterface.py rename to RNS/Interfaces/SerialInterface.py index cc488ae..d801792 100755 --- a/FPE/Interfaces/SerialInterface.py +++ b/RNS/Interfaces/SerialInterface.py @@ -5,7 +5,7 @@ import sys import serial import threading import time -import FPE +import RNS class SerialInterface(Interface): MAX_CHUNK = 32768 @@ -36,7 +36,7 @@ class SerialInterface(Interface): self.parity = serial.PARITY_ODD try: - FPE.log("Opening serial port "+self.port+"...") + RNS.log("Opening serial port "+self.port+"...") self.serial = serial.Serial( port = self.port, baudrate = self.speed, @@ -51,7 +51,7 @@ class SerialInterface(Interface): dsrdtr = False, ) except Exception as e: - FPE.log("Could not create serial port", FPE.LOG_ERROR) + RNS.log("Could not create serial port", RNS.LOG_ERROR) raise e if self.serial.is_open: @@ -60,13 +60,13 @@ class SerialInterface(Interface): thread.setDaemon(True) thread.start() self.online = True - FPE.log("Serial port "+self.port+" is now open") + RNS.log("Serial port "+self.port+" is now open") else: raise IOError("Could not open serial port") def processIncoming(self, data): - self.owner.inbound(data) + self.owner.inbound(data, self) def processOutgoing(self,data): @@ -93,6 +93,8 @@ class SerialInterface(Interface): sleep(0.08) except Exception as e: self.online = False - FPE.log("A serial port error occurred, the contained exception was: "+str(e), FPE.LOG_ERROR) - FPE.log("The interface "+str(self.name)+" is now offline. Restart FlexPE to attempt reconnection.", FPE.LOG_ERROR) + RNS.log("A serial port error occurred, the contained exception was: "+str(e), RNS.LOG_ERROR) + RNS.log("The interface "+str(self.name)+" is now offline. Restart Reticulum to attempt reconnection.", RNS.LOG_ERROR) + def __str__(self): + return "SerialInterface["+self.name+"]" diff --git a/RNS/Interfaces/SerialKISSInterface.py b/RNS/Interfaces/SerialKISSInterface.py new file mode 100644 index 0000000..4939dfd --- /dev/null +++ b/RNS/Interfaces/SerialKISSInterface.py @@ -0,0 +1,214 @@ +from __future__ import print_function +from Interface import Interface +from time import sleep +import sys +import serial +import threading +import time +import RNS + +class KISS(): + FEND = chr(0xC0) + FESC = chr(0xDB) + TFEND = chr(0xDC) + TFESC = chr(0xDD) + CMD_UNKNOWN = chr(0xFE) + CMD_DATA = chr(0x00) + CMD_TXDELAY = chr(0x01) + CMD_P = chr(0x02) + CMD_SLOTTIME = chr(0x03) + CMD_TXTAIL = chr(0x04) + CMD_FULLDUPLEX = chr(0x05) + CMD_SETHARDWARE = chr(0x06) + CMD_RETURN = chr(0xFF) + +class SerialKISSInterface(Interface): + MAX_CHUNK = 32768 + + owner = None + port = None + speed = None + databits = None + parity = None + stopbits = None + serial = None + + def __init__(self, owner, port, speed, databits, parity, stopbits, preamble, txtail, persistence, slottime): + self.serial = None + self.owner = owner + self.port = port + self.speed = speed + self.databits = databits + self.parity = serial.PARITY_NONE + self.stopbits = stopbits + self.timeout = 100 + self.online = False + + self.preamble = preamble if preamble != None else 350; + self.txtail = txtail if txtail != None else 20; + self.persistence = persistence if persistence != None else 64; + self.slottime = slottime if slottime != None else 20; + + if parity.lower() == "e" or parity.lower() == "even": + self.parity = serial.PARITY_EVEN + + if parity.lower() == "o" or parity.lower() == "odd": + self.parity = serial.PARITY_ODD + + try: + RNS.log("Opening serial port "+self.port+"...") + self.serial = serial.Serial( + port = self.port, + baudrate = self.speed, + bytesize = self.databits, + parity = self.parity, + stopbits = self.stopbits, + xonxoff = False, + rtscts = False, + timeout = 0, + inter_byte_timeout = None, + write_timeout = None, + dsrdtr = False, + ) + except Exception as e: + RNS.log("Could not create serial port", RNS.LOG_ERROR) + raise e + + if self.serial.is_open: + # Allow time for interface to initialise before config + sleep(2.0) + thread = threading.Thread(target=self.readLoop) + thread.setDaemon(True) + thread.start() + self.online = True + RNS.log("Serial port "+self.port+" is now open") + RNS.log("Configuring KISS interface parameters...") + self.setPreamble(self.preamble) + self.setTxTail(self.txtail) + self.setPersistence(self.persistence) + self.setSlotTime(self.slottime) + RNS.log("KISS interface configured") + sleep(2) + else: + raise IOError("Could not open serial port") + + + 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 setTxTail(self, txtail): + txtail_ms = txtail + txtail = int(txtail_ms / 10) + if txtail < 0: + txtail = 0 + if txtail > 255: + txtail = 255 + + kiss_command = KISS.FEND+KISS.CMD_TXTAIL+chr(txtail)+KISS.FEND + written = self.serial.write(kiss_command) + if written != len(kiss_command): + raise IOError("Could not configure KISS interface TX tail to "+str(txtail_ms)+" (command value "+str(txtail)+")") + + def setPersistence(self, persistence): + if persistence < 0: + persistence = 0 + if persistence > 255: + persistence = 255 + + kiss_command = KISS.FEND+KISS.CMD_P+chr(persistence)+KISS.FEND + written = self.serial.write(kiss_command) + if written != len(kiss_command): + raise IOError("Could not configure KISS interface persistence to "+str(persistence)) + + def setSlotTime(self, slottime): + slottime_ms = slottime + slottime = int(slottime_ms / 10) + if slottime < 0: + slottime = 0 + if slottime > 255: + slottime = 255 + + kiss_command = KISS.FEND+KISS.CMD_SLOTTIME+chr(slottime)+KISS.FEND + written = self.serial.write(kiss_command) + if written != len(kiss_command): + raise IOError("Could not configure KISS interface slot time to "+str(slottime_ms)+" (command value "+str(slottime)+")") + + + def processIncoming(self, data): + self.owner.inbound(data, self) + + + def processOutgoing(self,data): + if self.online: + data = data.replace(chr(0xdb), chr(0xdb)+chr(0xdd)) + data = data.replace(chr(0xc0), chr(0xdb)+chr(0xdc)) + frame = chr(0xc0)+chr(0x00)+data+chr(0xc0) + written = self.serial.write(frame) + if written != len(frame): + raise IOError("Serial interface only wrote "+str(written)+" bytes of "+str(len(data))) + + + def readLoop(self): + try: + in_frame = False + escape = False + command = KISS.CMD_UNKNOWN + data_buffer = "" + last_read_ms = int(time.time()*1000) + + while self.serial.is_open: + if self.serial.in_waiting: + byte = self.serial.read(1) + last_read_ms = int(time.time()*1000) + + if (in_frame and byte == KISS.FEND and command == KISS.CMD_DATA): + in_frame = False + self.processIncoming(data_buffer) + elif (byte == KISS.FEND): + in_frame = True + command = KISS.CMD_UNKNOWN + data_buffer = "" + elif (in_frame and len(data_buffer) < RNS.Reticulum.MTU): + if (len(data_buffer) == 0 and command == KISS.CMD_UNKNOWN): + # We only support one HDLC port for now, so + # strip off port nibble + byte = chr(ord(byte) & 0x0F) + command = byte + elif (command == KISS.CMD_DATA): + if (byte == KISS.FESC): + escape = True + else: + if (escape): + if (byte == KISS.TFEND): + byte = KISS.FEND + if (byte == KISS.TFESC): + byte = KISS.FESC + data_buffer = data_buffer+byte + else: + time_since_last = int(time.time()*1000) - last_read_ms + if len(data_buffer) > 0 and time_since_last > self.timeout: + data_buffer = "" + in_frame = False + command = KISS.CMD_UNKNOWN + escape = False + sleep(0.08) + + except Exception as e: + self.online = False + RNS.log("A serial port error occurred, the contained exception was: "+str(e), RNS.LOG_ERROR) + RNS.log("The interface "+str(self.name)+" is now offline. Restart Reticulum to attempt reconnection.", RNS.LOG_ERROR) + + def __str__(self): + return "SerialKISSInterface["+self.name+"]" + diff --git a/FPE/Interfaces/UdpInterface.py b/RNS/Interfaces/UdpInterface.py similarity index 88% rename from FPE/Interfaces/UdpInterface.py rename to RNS/Interfaces/UdpInterface.py index d0359bb..4127b23 100755 --- a/FPE/Interfaces/UdpInterface.py +++ b/RNS/Interfaces/UdpInterface.py @@ -3,7 +3,7 @@ import SocketServer import threading import socket import sys -import FPE +import RNS class UdpInterface(Interface): bind_ip = None @@ -37,7 +37,7 @@ class UdpInterface(Interface): def processIncoming(self, data): - self.owner.inbound(data) + self.owner.inbound(data, self) def processOutgoing(self,data): udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) @@ -46,7 +46,7 @@ class UdpInterface(Interface): def __str__(self): - return "UdpInterface["+self.bind_ip+":"+str(self.bind_port)+"]" + return "UdpInterface["+self.name+"/"+self.bind_ip+":"+str(self.bind_port)+"]" class UdpInterfaceHandler(SocketServer.BaseRequestHandler): interface = None diff --git a/FPE/Interfaces/__init__.py b/RNS/Interfaces/__init__.py similarity index 100% rename from FPE/Interfaces/__init__.py rename to RNS/Interfaces/__init__.py diff --git a/FPE/Packet.py b/RNS/Packet.py similarity index 90% rename from FPE/Packet.py rename to RNS/Packet.py index 5c95591..24aac5a 100755 --- a/FPE/Packet.py +++ b/RNS/Packet.py @@ -1,6 +1,6 @@ import struct import time -import FPE +import RNS class Packet: # Constants @@ -16,10 +16,10 @@ class Packet: HEADER_4 = 0x03; # Reserved header_types = [HEADER_1, HEADER_2, HEADER_3, HEADER_4] - def __init__(self, destination, data, packet_type = RESOURCE, transport_type = FPE.Transport.BROADCAST, header_type = HEADER_1, transport_id = None): + def __init__(self, destination, data, packet_type = RESOURCE, transport_type = RNS.Transport.BROADCAST, header_type = HEADER_1, transport_id = None): if destination != None: if transport_type == None: - transport_type = FPE.Transport.BROADCAST + transport_type = RNS.Transport.BROADCAST self.header_type = header_type self.packet_type = packet_type @@ -94,9 +94,9 @@ class Packet: def send(self): if not self.sent: self.pack() - FPE.log("Size: "+str(len(self.raw))+" header is "+str(len(self.header))+" payload is "+str(len(self.ciphertext)), FPE.LOG_DEBUG) - FPE.Transport.outbound(self.raw) - self.packet_hash = FPE.Identity.fullHash(self.raw) + RNS.log("Size: "+str(len(self.raw))+" header is "+str(len(self.header))+" payload is "+str(len(self.ciphertext)), RNS.LOG_DEBUG) + RNS.Transport.outbound(self.raw) + self.packet_hash = RNS.Identity.fullHash(self.raw) self.sent_at = time.time() self.sent = True else: diff --git a/RNS/Reticulum.py b/RNS/Reticulum.py new file mode 100755 index 0000000..8b2ed80 --- /dev/null +++ b/RNS/Reticulum.py @@ -0,0 +1,165 @@ +from Interfaces import * +import ConfigParser +from vendor.configobj import ConfigObj +import atexit +import struct +import array +import os.path +import os +import RNS + +import traceback + +class Reticulum: + MTU = 500 + router = None + config = None + + configdir = os.path.expanduser("~")+"/.reticulum" + configpath = "" + storagepath = "" + cachepath = "" + + 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" + + 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...") + 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 + + atexit.register(RNS.Identity.exitHandler) + + 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) + + for name in self.config["interfaces"]: + c = self.config["interfaces"][name] + try: + if c["type"] == "UdpInterface": + interface = UdpInterface.UdpInterface( + RNS.Transport, + c["listen_ip"], + int(c["listen_port"]), + c["forward_ip"], + int(c["forward_port"]) + ) + + if "outgoing" in c and c["outgoing"].lower() == "true": + interface.OUT = True + else: + interface.OUT = False + + interface.name = name + 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 = SerialKISSInterface.SerialKISSInterface( + RNS.Transport, + port, + speed, + databits, + parity, + stopbits + ) + + if "outgoing" in c and c["outgoing"].lower() == "true": + interface.OUT = True + else: + interface.OUT = False + + interface.name = name + RNS.Transport.interfaces.append(interface) + + if c["type"] == "SerialKISSInterface": + 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 + + 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 = SerialKISSInterface.SerialKISSInterface( + RNS.Transport, + port, + speed, + databits, + parity, + stopbits, + preamble, + txtail, + persistence, + slottime + ) + + if "outgoing" in c and c["outgoing"].lower() == "true": + interface.OUT = True + else: + interface.OUT = False + + interface.name = name + RNS.Transport.interfaces.append(interface) + + 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) + traceback.print_exc() + + + + + def createDefaultConfig(self): + self.config = ConfigObj() + self.config.filename = Reticulum.configpath + self.config["interfaces"] = {} + self.config["interfaces"]["Default UDP Interface"] = {} + self.config["interfaces"]["Default UDP Interface"]["type"] = "UdpInterface" + self.config["interfaces"]["Default UDP Interface"]["listen_ip"] = "0.0.0.0" + self.config["interfaces"]["Default UDP Interface"]["listen_port"] = 7777 + self.config["interfaces"]["Default UDP Interface"]["forward_ip"] = "255.255.255.255" + self.config["interfaces"]["Default UDP Interface"]["forward_port"] = 7777 + self.config["interfaces"]["Default UDP Interface"]["use_as_outgoing"] = "true" + if not os.path.isdir(Reticulum.configdir): + os.makedirs(Reticulum.configdir) + self.config.write() + self.applyConfig() \ No newline at end of file diff --git a/FPE/Transport.py b/RNS/Transport.py similarity index 59% rename from FPE/Transport.py rename to RNS/Transport.py index e28cfe4..dc1e8ed 100755 --- a/FPE/Transport.py +++ b/RNS/Transport.py @@ -1,4 +1,4 @@ -import FPE +import RNS class Transport: # Constants @@ -17,31 +17,32 @@ class Transport: Transport.cacheRaw(raw) for interface in Transport.interfaces: if interface.OUT: - FPE.log("Transmitting via: "+str(interface), FPE.LOG_DEBUG) + RNS.log("Transmitting via: "+str(interface), RNS.LOG_DEBUG) interface.processOutgoing(raw) @staticmethod - def inbound(raw): - packet_hash = FPE.Identity.fullHash(raw) + def inbound(raw, interface=None): + packet_hash = RNS.Identity.fullHash(raw) + RNS.log(str(interface)+" received packet with hash "+RNS.prettyhexrep(packet_hash), RNS.LOG_DEBUG) if not packet_hash in Transport.packet_hashlist: Transport.packet_hashlist.append(packet_hash) - packet = FPE.Packet(None, raw) + packet = RNS.Packet(None, raw) packet.unpack() packet.packet_hash = packet_hash - if packet.packet_type == FPE.Packet.ANNOUNCE: - if FPE.Identity.validateAnnounce(packet): + if packet.packet_type == RNS.Packet.ANNOUNCE: + if RNS.Identity.validateAnnounce(packet): Transport.cache(packet) - if packet.packet_type == FPE.Packet.RESOURCE: + if packet.packet_type == RNS.Packet.RESOURCE: for destination in Transport.destinations: if destination.hash == packet.destination_hash and destination.type == packet.destination_type: packet.destination = destination destination.receive(packet) Transport.cache(packet) - if packet.packet_type == FPE.Packet.PROOF: + if packet.packet_type == RNS.Packet.PROOF: for destination in Transport.destinations: if destination.hash == packet.destination_hash: if destination.proofcallback != None: @@ -50,21 +51,21 @@ class Transport: @staticmethod def registerDestination(destination): - destination.MTU = FPE.FlexPE.MTU - if destination.direction == FPE.Destination.IN: + destination.MTU = RNS.Reticulum.MTU + if destination.direction == RNS.Destination.IN: Transport.destinations.append(destination) @staticmethod def cache(packet): - FPE.Transport.cacheRaw(packet.raw) + RNS.Transport.cacheRaw(packet.raw) @staticmethod def cacheRaw(raw): try: - file = open(FPE.FlexPE.cachepath+"/"+FPE.hexrep(FPE.Identity.fullHash(raw), delimit=False), "w") + file = open(RNS.Reticulum.cachepath+"/"+RNS.hexrep(RNS.Identity.fullHash(raw), delimit=False), "w") file.write(raw) file.close() - FPE.log("Wrote packet "+FPE.prettyhexrep(FPE.Identity.fullHash(raw))+" to cache", FPE.LOG_DEBUG) + RNS.log("Wrote packet "+RNS.prettyhexrep(RNS.Identity.fullHash(raw))+" to cache", RNS.LOG_DEBUG) except Exception as e: - FPE.log("Error writing packet to cache", FPE.LOG_ERROR) - FPE.log("The contained exception was: "+str(e)) \ No newline at end of file + RNS.log("Error writing packet to cache", RNS.LOG_ERROR) + RNS.log("The contained exception was: "+str(e)) \ No newline at end of file diff --git a/FPE/Utilities/Echo.py b/RNS/Utilities/Echo.py similarity index 83% rename from FPE/Utilities/Echo.py rename to RNS/Utilities/Echo.py index 2f6a192..bfbbbd3 100644 --- a/FPE/Utilities/Echo.py +++ b/RNS/Utilities/Echo.py @@ -1,6 +1,6 @@ import argparse import time -import FPE +import RNS # Let's define an app name. We'll use this for all # destinations we create. Since this echo example @@ -11,11 +11,11 @@ APP_NAME = "example_utilitites" # This initialisation is executed when the users chooses # to run as a server def server(configpath): - # We must first initialise FlexPE - fpe = FPE.FlexPE(configpath) + # We must first initialise Reticulum + RNS = RNS.Reticulum(configpath) # Randomly create a new identity for our echo server - server_identity = FPE.Identity() + server_identity = RNS.Identity() # We create a destination that clients can query. We want # to be able to verify echo replies to our clients, so we @@ -23,7 +23,7 @@ def server(configpath): # messages. This way the client can send a request and be # certain that no-one else than this destination was able # to read it. - echo_destination = FPE.Destination(server_identity, FPE.Destination.IN, FPE.Destination.SINGLE, APP_NAME, "echo", "request") + echo_destination = RNS.Destination(server_identity, RNS.Destination.IN, RNS.Destination.SINGLE, APP_NAME, "echo", "request") # Tell the destination which function in our program to # run when a packet is received. @@ -36,7 +36,7 @@ def server(configpath): def announceLoop(destination): # Let the user know that everything is ready - FPE.log("Echo server "+FPE.prettyhexrep(destination.hash)+" running, hit enter to send announce (Ctrl-C to quit)") + RNS.log("Echo server "+RNS.prettyhexrep(destination.hash)+" running, hit enter to send announce (Ctrl-C to quit)") # We enter a loop that runs until the users exits. # If the user just hits enter, we will announce our server @@ -45,7 +45,7 @@ def announceLoop(destination): while True: entered = raw_input() destination.announce() - FPE.log("Sent announce from "+FPE.prettyhexrep(destination.hash)) + RNS.log("Sent announce from "+RNS.prettyhexrep(destination.hash)) def serverCallback(message, packet): @@ -61,7 +61,7 @@ def serverCallback(message, packet): # We can now create a destination that will let us reach # the client which send the echo request. - reply_destination = FPE.Destination(None, FPE.Destination.OUT, FPE.Destination.PLAIN, APP_NAME, "echo", "reply", client_identity_hexhash) + reply_destination = RNS.Destination(None, RNS.Destination.OUT, RNS.Destination.PLAIN, APP_NAME, "echo", "reply", client_identity_hexhash) # Let's encode the reply destination hash in a readable # way, so we can output some info to the user. @@ -69,10 +69,10 @@ def serverCallback(message, packet): # Tell the user that we received an echo request, and # that we are going to send a reply to the requester. - FPE.log("Received packet from <"+reply_destination_hexhash+">, sending reply") + RNS.log("Received packet from <"+reply_destination_hexhash+">, sending reply") # To let the client know that we got the echo request, - # we will use the "proof" functions of FlexPE. In most + # we will use the "proof" functions of Reticulum. In most # applications, the proving of packets will occur fully # automatically, but in some cases like this, it can be # beneficial to use the functions manually, since it @@ -98,14 +98,14 @@ def client(destination_hexhash, configpath): raise ValueError("Destination length is invalid, must be 20 hexadecimal characters (10 bytes)") destination_hash = destination_hexhash.decode("hex") except: - FPE.log("Invalid destination entered. Check your input!") + RNS.log("Invalid destination entered. Check your input!") exit() - # We must first initialise FlexPE - fpe = FPE.FlexPE(configpath) + # We must first initialise Reticulum + RNS = RNS.Reticulum(configpath) # Randomly create a new identity for our echo server - client_identity = FPE.Identity() + client_identity = RNS.Identity() # Let's set up a destination for replies to our echo # requests. This destination will be used by the server @@ -124,7 +124,7 @@ def client(destination_hexhash, configpath): # create a unique destination for the server to respond to. # If we had used a "single" destination, something equivalent # to this process would have happened automatically. - reply_destination = FPE.Destination(client_identity, FPE.Destination.IN, FPE.Destination.PLAIN, APP_NAME, "echo", "reply", client_identity.hexhash) + reply_destination = RNS.Destination(client_identity, RNS.Destination.IN, RNS.Destination.PLAIN, APP_NAME, "echo", "reply", client_identity.hexhash) # Since we are only expecting packets of the "proof" # type to reach our reply destination, we just set the @@ -133,7 +133,7 @@ def client(destination_hexhash, configpath): reply_destination.setProofCallback(clientProofCallback) # Tell the user that the client is ready! - FPE.log("Echo client "+FPE.prettyhexrep(reply_destination.hash)+" ready, hit enter to send echo request (Ctrl-C to quit)") + RNS.log("Echo client "+RNS.prettyhexrep(reply_destination.hash)+" ready, hit enter to send echo request (Ctrl-C to quit)") # We enter a loop that runs until the user exits. # If the user hits enter, we will try to send an @@ -142,12 +142,12 @@ def client(destination_hexhash, configpath): while True: raw_input() # To address the server, we need to know it's public - # key, so we check if FlexPE knows this destination. + # key, so we check if Reticulum knows this destination. # This is done by calling the "recall" method of the # Identity module. If the destination is known, it will # return an Identity instance that can be used in # outgoing destinations. - server_identity = FPE.Identity.recall(destination_hash) + server_identity = RNS.Identity.recall(destination_hash) if server_identity != None: # We got the correct identity instance from the # recall method, so let's create an outgoing @@ -156,7 +156,7 @@ def client(destination_hexhash, configpath): # Since this is a "single" destination, the identity # hash will be automatically added to the end of # the name. - request_destination = FPE.Destination(server_identity, FPE.Destination.OUT, FPE.Destination.SINGLE, APP_NAME, "echo", "request") + request_destination = RNS.Destination(server_identity, RNS.Destination.OUT, RNS.Destination.SINGLE, APP_NAME, "echo", "request") # The destination is ready, so let's create a packet. # We set the destination to the request_destination @@ -164,7 +164,7 @@ def client(destination_hexhash, configpath): # is the identity hash of our client identity. # Including that information will let the server # create a destination to send replies to. - echo_request = FPE.Packet(request_destination, client_identity.hash) + echo_request = RNS.Packet(request_destination, client_identity.hash) # Send the packet! echo_request.send() @@ -173,11 +173,11 @@ def client(destination_hexhash, configpath): sent_requests.append(echo_request) # Tell the user that the echo request was sent - FPE.log("Sent echo request to "+FPE.prettyhexrep(request_destination.hash)) + RNS.log("Sent echo request to "+RNS.prettyhexrep(request_destination.hash)) else: # If we do not know this destination, tell the # user to wait for an announce to arrive. - FPE.log("Destination is not yet known. Wait for an announce to arrive.") + RNS.log("Destination is not yet known. Wait for an announce to arrive.") # This method is called when our reply destination # receives a proof packet. @@ -209,8 +209,8 @@ def clientProofCallback(proof_packet): rtt = round(rtt*1000, 3) rttstring = str(rtt)+" milliseconds" - FPE.log( - "Valid echo reply, proved by "+FPE.prettyhexrep(unproven_packet.destination.hash)+ + RNS.log( + "Valid echo reply, proved by "+RNS.prettyhexrep(unproven_packet.destination.hash)+ ", round-trip time was "+rttstring ) # Perform some cleanup @@ -219,9 +219,9 @@ def clientProofCallback(proof_packet): else: # If the proof was invalid, we inform # the user of this. - FPE.log("Echo reply received, but proof was invalid") + RNS.log("Echo reply received, but proof was invalid") except: - FPE.log("Proof packet received, but packet contained invalid or unparsable data") + RNS.log("Proof packet received, but packet contained invalid or unparsable data") @@ -231,7 +231,7 @@ if __name__ == "__main__": try: parser = argparse.ArgumentParser(description="Simple echo server and client utility") parser.add_argument("-s", "--server", action="store_true", help="wait for incoming packets from clients") - parser.add_argument("--config", action="store", default=None, help="path to alternative FlexPE config directory", type=str) + parser.add_argument("--config", action="store", default=None, help="path to alternative Reticulum config directory", type=str) parser.add_argument("destination", nargs="?", default=None, help="hexadecimal hash of the server destination", type=str) args = parser.parse_args() diff --git a/FPE/__init__.py b/RNS/__init__.py similarity index 97% rename from FPE/__init__.py rename to RNS/__init__.py index 7a36cc3..d44b171 100755 --- a/FPE/__init__.py +++ b/RNS/__init__.py @@ -2,7 +2,7 @@ import os import glob import time -from .FlexPE import FlexPE +from .Reticulum import Reticulum from .Identity import Identity from .Transport import Transport from .Destination import Destination diff --git a/FPE/vendor/__init__.py b/RNS/vendor/__init__.py similarity index 100% rename from FPE/vendor/__init__.py rename to RNS/vendor/__init__.py diff --git a/FPE/vendor/configobj.py b/RNS/vendor/configobj.py similarity index 100% rename from FPE/vendor/configobj.py rename to RNS/vendor/configobj.py diff --git a/t.py b/t.py index 34812ab..86d1d1e 100755 --- a/t.py +++ b/t.py @@ -1,8 +1,8 @@ -# from FPE.Destination import * -# from FPE.Packet import * -# from FPE import FlexPE -from FPE import * -# from FPE import Destination +# from RNS.Destination import * +# from RNS.Packet import * +# from RNS import Reticulum +from RNS import * +# from RNS import Destination import os import time @@ -12,8 +12,8 @@ def testCallback(message, receiver): print("----------") -#fpe = FlexPE(configdir=os.path.expanduser("~")+"/.flexpe2") -fpe = FlexPE() +#RNS = Reticulum(configdir=os.path.expanduser("~")+"/.Reticulum2") +RNS = Reticulum() identity = Identity() d1=Destination(identity, Destination.IN, Destination.SINGLE, "messenger", "user")