From d754ed989c6719ab7546c6e6fa10a083c7ece603 Mon Sep 17 00:00:00 2001 From: Mark Qvist Date: Fri, 6 Mar 2020 22:20:50 +0100 Subject: [PATCH] Implemented transport for links --- Examples/Echo.py | 2 +- Examples/Filetransfer.py | 15 +++--- Notes/Header format | 2 +- RNS/Identity.py | 8 +-- RNS/Link.py | 4 +- RNS/Packet.py | 5 +- RNS/Transport.py | 106 +++++++++++++++++++++++++++++++++------ RNS/__init__.py | 3 -- 8 files changed, 110 insertions(+), 35 deletions(-) diff --git a/Examples/Echo.py b/Examples/Echo.py index 4aee567..90bad01 100644 --- a/Examples/Echo.py +++ b/Examples/Echo.py @@ -96,7 +96,7 @@ def client(destination_hexhash, configpath, timeout=None): # We override the loglevel to provide feedback when # an announce is received - RNS.loglevel = RNS.LOG_DEBUG + RNS.loglevel = RNS.LOG_INFO # Tell the user that the client is ready! RNS.log("Echo client ready, hit enter to send echo request to "+destination_hexhash+" (Ctrl-C to quit)") diff --git a/Examples/Filetransfer.py b/Examples/Filetransfer.py index 7391170..67f893c 100644 --- a/Examples/Filetransfer.py +++ b/Examples/Filetransfer.py @@ -180,15 +180,16 @@ def client(destination_hexhash, configpath): # We must first initialise Reticulum reticulum = RNS.Reticulum(configpath) - # Check if we already know the destination - server_identity = RNS.Identity.recall(destination_hash) - # If not, we'll have to wait until an announce arrives - if server_identity == None: - RNS.log("Destination is not yet known, waiting for an announce to arrive... (Ctrl-C to cancel)") - while (server_identity == None): + # 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) - server_identity = RNS.Identity.recall(destination_hash) + + # 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...") diff --git a/Notes/Header format b/Notes/Header format index 66b1c8e..a3b642c 100644 --- a/Notes/Header format +++ b/Notes/Header format @@ -32,7 +32,7 @@ proof 11 +- Header example -+ -01010000 00000100 +01010000 00000100 [ADDR 1, 10 bytes] [ADDR 2, 10 bytes] [CONTEXT] | | | | | | | | | +-- Context = RESOURCE_HMU | | | +------- DATA packet diff --git a/RNS/Identity.py b/RNS/Identity.py index 618e3e2..09de315 100644 --- a/RNS/Identity.py +++ b/RNS/Identity.py @@ -36,15 +36,15 @@ class Identity: @staticmethod def recall(destination_hash): - RNS.log("Searching for "+RNS.prettyhexrep(destination_hash)+"...", RNS.LOG_DEBUG) + RNS.log("Searching for "+RNS.prettyhexrep(destination_hash)+"...", RNS.LOG_EXTREME) 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) + RNS.log("Found "+RNS.prettyhexrep(destination_hash)+" in known destinations", RNS.LOG_EXTREME) return identity else: - RNS.log("Could not find "+RNS.prettyhexrep(destination_hash)+" in known destinations", RNS.LOG_DEBUG) + RNS.log("Could not find "+RNS.prettyhexrep(destination_hash)+" in known destinations", RNS.LOG_EXTREME) return None @staticmethod @@ -104,7 +104,7 @@ class Identity: 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.Identity.remember(packet.getHash(), destination_hash, public_key) RNS.log("Stored valid announce from "+RNS.prettyhexrep(destination_hash), RNS.LOG_INFO) del announced_identity return True diff --git a/RNS/Link.py b/RNS/Link.py index d51e4e2..df610e2 100644 --- a/RNS/Link.py +++ b/RNS/Link.py @@ -144,7 +144,7 @@ class Link: self.peer_pub.curve = Link.CURVE def setLinkID(self, packet): - self.link_id = RNS.Identity.truncatedHash(packet.raw) + self.link_id = packet.getTruncatedHash() self.hash = self.link_id def handshake(self): @@ -218,7 +218,7 @@ class Link: rtt = umsgpack.unpackb(plaintext) self.rtt = max(measured_rtt, rtt) self.status = Link.ACTIVE - # TODO: Link established callback moved here, ok? + if self.owner.callbacks.link_established != None: self.owner.callbacks.link_established(self) except Exception as e: diff --git a/RNS/Packet.py b/RNS/Packet.py index 5f3f1a2..b0b9a27 100755 --- a/RNS/Packet.py +++ b/RNS/Packet.py @@ -133,8 +133,6 @@ class Packet: raise IOError("Packet with header type 2 must have a transport ID") - - self.header += chr(self.context) self.raw = self.header + self.ciphertext @@ -225,6 +223,9 @@ class Packet: def getHash(self): return RNS.Identity.fullHash(self.getHashablePart()) + def getTruncatedHash(self): + return RNS.Identity.truncatedHash(self.getHashablePart()) + def getHashablePart(self): hashable_part = struct.pack("!B", struct.unpack("!B", self.raw[0])[0] & 0b00001111) if self.header_type == Packet.HEADER_2: diff --git a/RNS/Transport.py b/RNS/Transport.py index 21772ea..32b07b5 100755 --- a/RNS/Transport.py +++ b/RNS/Transport.py @@ -48,6 +48,7 @@ class Transport: announce_table = {} # A table for storing announces currently waiting to be retransmitted destination_table = {} # A lookup table containing the next hop to a given destination reverse_table = {} # A lookup table for storing packet hashes used to return proofs and replies + link_table = {} # A lookup table containing hops for links jobs_locked = False jobs_running = False @@ -132,8 +133,6 @@ class Transport: block_rebroadcasts = announce_entry[7] announce_context = RNS.Packet.NONE if block_rebroadcasts: - # TODO: Remove - RNS.log("Rebroadcasts blocked, setting context", RNS.LOG_DEBUG) announce_context = RNS.Packet.PATH_RESPONSE announce_data = packet.data announce_identity = RNS.Identity.recall(packet.destination_hash) @@ -203,9 +202,10 @@ class Transport: RNS.log("Hash is "+RNS.prettyhexrep(packet.packet_hash), RNS.LOG_EXTREME) outbound_interface.processOutgoing(packet.raw) sent = True - + else: - # Broadcast packet on all outgoing interfaces + # Broadcast packet on all outgoing interfaces, or relevant + # interface, if packet is for a link for interface in Transport.interfaces: if interface.OUT: should_transmit = True @@ -214,7 +214,9 @@ class Transport: should_transmit = False if interface != packet.destination.attached_interface: should_transmit = False - + else: + pass + if should_transmit: RNS.log("Transmitting "+str(len(packet.raw))+" bytes on: "+str(interface), RNS.LOG_EXTREME) RNS.log("Hash is "+RNS.prettyhexrep(packet.packet_hash), RNS.LOG_EXTREME) @@ -249,7 +251,6 @@ class Transport: if packet.packet_type == RNS.Packet.ANNOUNCE: return True - # TODO: Probably changee to LOG_EXTREME RNS.log("Filtered packet with hash "+RNS.prettyhexrep(packet.packet_hash), RNS.LOG_DEBUG) return False @@ -271,6 +272,9 @@ class Transport: if Transport.packet_filter(packet): Transport.packet_hashlist.append(packet.packet_hash) + # General transport handling. Takes care of directing + # packets according to transport tables and recording + # entries in reverse and link tables. if packet.transport_id != None and packet.packet_type != RNS.Packet.ANNOUNCE: if packet.transport_id == Transport.identity.hash: RNS.log("Received packet in transport for "+RNS.prettyhexrep(packet.destination_hash)+" with matching transport ID, transporting it...", RNS.LOG_DEBUG) @@ -294,7 +298,27 @@ class Transport: outbound_interface = Transport.destination_table[packet.destination_hash][5] outbound_interface.processOutgoing(new_raw) - Transport.reverse_table[packet.packet_hash[:10]] = [packet.receiving_interface, outbound_interface, time.time()] + if packet.packet_type == RNS.Packet.LINKREQUEST: + # Entry format is + link_entry = [ time.time(), # 0: Timestamp, + next_hop, # 1: Next-hop transport ID + outbound_interface, # 2: Next-hop interface + remaining_hops, # 3: Remaining hops + packet.receiving_interface, # 4: Received on interface + packet.hops, # 5: Taken hops + packet.destination_hash, # 6: Original destination hash + False] # 7: Validated + + Transport.link_table[packet.getTruncatedHash()] = link_entry + + else: + # Entry format is + reverse_entry = [ packet.receiving_interface, # 0: Received on interface + outbound_interface, # 1: Outbound interface + time.time()] # 2: Timestamp + + Transport.reverse_table[packet.getTruncatedHash()] = reverse_entry + else: # TODO: There should probably be some kind of REJECT # mechanism here, to signal to the source that their @@ -303,9 +327,45 @@ class Transport: else: pass + # Link transport handling. Directs packetes according + # to entries in the link tables + if packet.packet_type != RNS.Packet.ANNOUNCE and packet.packet_type != RNS.Packet.LINKREQUEST: + if packet.destination_hash in Transport.link_table: + link_entry = Transport.link_table[packet.destination_hash] + # If receiving and outbound interface is + # the same for this link, direction doesn't + # matter, and we simply send the packet on. + outbound_interface = None + if link_entry[2] == link_entry[4]: + # But check that taken hops matches one + # of the expectede values. + if packet.hops == link_entry[3] or packet.hops == link_entry[5]: + outbound_interface = link_entry[2] + else: + # If interfaces differ, we transmit on + # the opposite interface of what the + # packet was received on. + if packet.receiving_interface == link_entry[2]: + # Also check that expected hop count matches + if packet.hops == link_entry[3]: + outbound_interface = link_entry[4] + elif packet.receiving_interface == link_entry[4]: + # Also check that expected hop count matches + if packet.hops == link_entry[5]: + outbound_interface = link_entry[2] + + if outbound_interface != None: + new_raw = packet.raw[0:1] + new_raw += struct.pack("!B", packet.hops) + new_raw += packet.raw[2:] + outbound_interface.processOutgoing(new_raw) + else: + pass + + # Announce handling. Handles logic related to incoming # announces, queueing rebroadcasts of these, and removal - # of queued announce rebroadcasts once handed to the next node + # of queued announce rebroadcasts once handed to the next node. if packet.packet_type == RNS.Packet.ANNOUNCE: local_destination = next((d for d in Transport.destinations if d.hash == packet.destination_hash), None) if local_destination == None and RNS.Identity.validateAnnounce(packet): @@ -427,11 +487,29 @@ class Transport: elif packet.packet_type == RNS.Packet.PROOF: if packet.context == RNS.Packet.LRPROOF: - # This is a link request proof, forward - # to a waiting link request - for link in Transport.pending_links: - if link.link_id == packet.destination_hash: - link.validateProof(packet) + # This is a link request proof, check if it + # needs to be transported + + if packet.destination_hash in Transport.link_table: + link_entry = Transport.link_table[packet.destination_hash] + if packet.receiving_interface == link_entry[2]: + # TODO: Should we validate the LR proof at each transport + # step before transporting it? + RNS.log("Link request proof received on correct interface, transporting it via "+str(link_entry[4]), RNS.LOG_DEBUG) + new_raw = packet.raw[0:1] + new_raw += struct.pack("!B", packet.hops) + new_raw += packet.raw[2:] + Transport.link_table[packet.destination_hash][7] = True + link_entry[4].processOutgoing(new_raw) + else: + RNS.log("Link request proof received on wrong interface, not transporting it.", RNS.LOG_DEBUG) + else: + # Check if we can deliver it to a local + # pending link + for link in Transport.pending_links: + if link.link_id == packet.destination_hash: + link.validateProof(packet) + elif packet.context == RNS.Packet.RESOURCE_PRF: for link in Transport.active_links: if link.link_id == packet.destination_hash: @@ -443,8 +521,6 @@ class Transport: packet.link = link # plaintext = link.decrypt(packet.data) - - # TODO: Make sure everything uses new proof handling if len(packet.data) == RNS.PacketReceipt.EXPL_LENGTH: proof_hash = packet.data[:RNS.Identity.HASHLENGTH/8] else: diff --git a/RNS/__init__.py b/RNS/__init__.py index de31d78..8a3ad57 100755 --- a/RNS/__init__.py +++ b/RNS/__init__.py @@ -73,9 +73,6 @@ def rand(): result = random.random() return result -def hexprint(data): - print(hexrep(hexrep)) - def hexrep(data, delimit=True): delimiter = ":" if not delimit: