Implemented path requests

This commit is contained in:
Mark Qvist 2020-03-06 12:55:05 +01:00
parent 09a19aed72
commit 33ce3ef48f
6 changed files with 107 additions and 23 deletions

View File

@ -96,8 +96,7 @@ def client(destination_hexhash, configpath, timeout=None):
# We override the loglevel to provide feedback when # We override the loglevel to provide feedback when
# an announce is received # an announce is received
# TODO: Reset this RNS.loglevel = RNS.LOG_INFO
RNS.loglevel = RNS.LOG_DEBUG
# Tell the user that the client is ready! # 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)") RNS.log("Echo client ready, hit enter to send echo request to "+destination_hexhash+" (Ctrl-C to quit)")
@ -108,14 +107,19 @@ def client(destination_hexhash, configpath, timeout=None):
# command line. # command line.
while True: while True:
raw_input() raw_input()
# To address the server, we need to know it's public
# key, so we check if Reticulum knows this destination. # Let's first check if RNS knows a path to the destination.
# This is done by calling the "recall" method of the # If it does, we'll load the server identity and create a packet
# Identity module. If the destination is known, it will if RNS.Transport.hasPath(destination_hash):
# return an Identity instance that can be used in
# outgoing destinations. # To address the server, we need to know it's public
server_identity = RNS.Identity.recall(destination_hash) # key, so we check if Reticulum knows this destination.
if server_identity != None: # 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 = RNS.Identity.recall(destination_hash)
# We got the correct identity instance from the # We got the correct identity instance from the
# recall method, so let's create an outgoing # recall method, so let's create an outgoing
# destination. We use the naming convention: # destination. We use the naming convention:
@ -152,7 +156,8 @@ def client(destination_hexhash, configpath, timeout=None):
else: else:
# If we do not know this destination, tell the # If we do not know this destination, tell the
# user to wait for an announce to arrive. # user to wait for an announce to arrive.
RNS.log("Destination is not yet known. Wait for an announce to arrive and try again.") RNS.log("Destination is not yet known. Requesting path...")
RNS.Transport.requestPath(destination_hash)
# This function is called when our reply destination # This function is called when our reply destination
# receives a proof packet. # receives a proof packet.

View File

@ -28,3 +28,14 @@ data 00
announce 01 announce 01
link request 10 link request 10
proof 11 proof 11
+- Header example -+
01010000 00000100
| | | | |
| | | | +-- Context = RESOURCE_HMU
| | | +------- DATA packet
| | +--------- SINGLE destination
| +----------- TRANSPORT propagation type
+------------- HEADER_2, two byte header, two address fields

View File

@ -75,8 +75,8 @@ class Destination:
if identity != None and type == Destination.SINGLE: if identity != None and type == Destination.SINGLE:
aspects = aspects+(identity.hexhash,) aspects = aspects+(identity.hexhash,)
if identity == None and direction == Destination.IN: if identity == None and direction == Destination.IN and self.type != Destination.PLAIN:
identity = Identity() identity = RNS.Identity()
aspects = aspects+(identity.hexhash,) aspects = aspects+(identity.hexhash,)
self.identity = identity self.identity = identity

View File

@ -57,7 +57,8 @@ class AX25KISSInterface(Interface):
self.timeout = 100 self.timeout = 100
self.online = False self.online = False
# TODO: Sane default and make this configurable # TODO: Sane default and make this configurable
self.txdelay = 0.1 # TODO: Changed to 1ms instead of 100ms, check it
self.txdelay = 0.001
self.packet_queue = [] self.packet_queue = []
self.flow_control = flow_control self.flow_control = flow_control

View File

@ -29,8 +29,9 @@ class Packet:
CACHE_REQUEST = 0x08 # Packet is a cache request CACHE_REQUEST = 0x08 # Packet is a cache request
REQUEST = 0x09 # Packet is a request REQUEST = 0x09 # Packet is a request
RESPONSE = 0x0A # Packet is a response to a request RESPONSE = 0x0A # Packet is a response to a request
COMMAND = 0x0B # Packet is a command PATH_RESPONSE = 0x0B # Packet is a response to a path request
COMMAND_STATUS = 0x0C # Packet is a status of an executed command COMMAND = 0x0C # Packet is a command
COMMAND_STATUS = 0x0D # Packet is a status of an executed command
KEEPALIVE = 0xFB # Packet is a keepalive packet KEEPALIVE = 0xFB # Packet is a keepalive packet
LINKCLOSE = 0xFC # Packet is a link close message LINKCLOSE = 0xFC # Packet is a link close message
LINKPROOF = 0xFD # Packet is a link packet proof LINKPROOF = 0xFD # Packet is a link packet proof
@ -330,6 +331,9 @@ class PacketReceipt:
return False return False
elif len(proof) == PacketReceipt.IMPL_LENGTH: elif len(proof) == PacketReceipt.IMPL_LENGTH:
# This is an implicit proof # This is an implicit proof
if self.destination.identity == None:
return False
signature = proof[:RNS.Identity.SIGLENGTH/8] signature = proof[:RNS.Identity.SIGLENGTH/8]
proof_valid = self.destination.identity.validate(signature, self.hash) proof_valid = self.destination.identity.validate(signature, self.hash)
if proof_valid: if proof_valid:

View File

@ -20,11 +20,13 @@ class Transport:
REACHABILITY_DIRECT = 0x01 REACHABILITY_DIRECT = 0x01
REACHABILITY_TRANSPORT = 0x02 REACHABILITY_TRANSPORT = 0x02
APP_NAME = "rnstransport"
# TODO: Document the addition of random windows # TODO: Document the addition of random windows
# and max local rebroadcasts. # and max local rebroadcasts.
PATHFINDER_M = 18 # Max hops PATHFINDER_M = 18 # Max hops
PATHFINDER_C = 2.0 # Decay constant PATHFINDER_C = 2.0 # Decay constant
PATHFINDER_R = 2 # Retransmit retries PATHFINDER_R = 1 # Retransmit retries
PATHFINDER_T = 10 # Retry grace period PATHFINDER_T = 10 # Retry grace period
PATHFINDER_RW = 10 # Random window for announce rebroadcast PATHFINDER_RW = 10 # Random window for announce rebroadcast
PATHFINDER_E = 60*15 # Path expiration in seconds PATHFINDER_E = 60*15 # Path expiration in seconds
@ -33,6 +35,9 @@ class Transport:
# various situations # various situations
LOCAL_REBROADCASTS_MAX = 2 # How many local rebroadcasts of an announce is allowed LOCAL_REBROADCASTS_MAX = 2 # How many local rebroadcasts of an announce is allowed
PATH_REQUEST_GRACE = 0.25 # Grace time before a path announcement is made, allows directly reachable peers to respond first
PATH_REQUEST_RW = 2 # Path request random window
interfaces = [] # All active interfaces interfaces = [] # All active interfaces
destinations = [] # All active destinations destinations = [] # All active destinations
pending_links = [] # Links that are being established pending_links = [] # Links that are being established
@ -78,7 +83,10 @@ class Transport:
except Exception as e: except Exception as e:
RNS.log("Could not load packet hashlist from disk, the contained exception was: "+str(e), RNS.LOG_ERROR) RNS.log("Could not load packet hashlist from disk, the contained exception was: "+str(e), RNS.LOG_ERROR)
# Create transport-specific destinations
path_request_destination = RNS.Destination(None, RNS.Destination.IN, RNS.Destination.PLAIN, Transport.APP_NAME, "path", "request")
path_request_destination.packet_callback(Transport.pathRequestHandler)
thread = threading.Thread(target=Transport.jobloop) thread = threading.Thread(target=Transport.jobloop)
thread.setDaemon(True) thread.setDaemon(True)
thread.start() thread.start()
@ -121,12 +129,16 @@ class Transport:
announce_entry[1] = time.time() + math.pow(Transport.PATHFINDER_C, announce_entry[4]) + Transport.PATHFINDER_T + Transport.PATHFINDER_RW announce_entry[1] = time.time() + math.pow(Transport.PATHFINDER_C, announce_entry[4]) + Transport.PATHFINDER_T + Transport.PATHFINDER_RW
announce_entry[2] += 1 announce_entry[2] += 1
packet = announce_entry[5] packet = announce_entry[5]
block_rebroadcasts = announce_entry[7]
announce_context = RNS.Packet.NONE
if block_rebroadcasts:
announce_context = RNS.Packet.PATH_RESPONSE
announce_data = packet.data announce_data = packet.data
announce_identity = RNS.Identity.recall(packet.destination_hash) announce_identity = RNS.Identity.recall(packet.destination_hash)
announce_destination = RNS.Destination(announce_identity, RNS.Destination.OUT, RNS.Destination.SINGLE, "unknown", "unknown"); announce_destination = RNS.Destination(announce_identity, RNS.Destination.OUT, RNS.Destination.SINGLE, "unknown", "unknown");
announce_destination.hash = packet.destination_hash announce_destination.hash = packet.destination_hash
announce_destination.hexhash = announce_destination.hash.encode("hex_codec") announce_destination.hexhash = announce_destination.hash.encode("hex_codec")
new_packet = RNS.Packet(announce_destination, announce_data, RNS.Packet.ANNOUNCE, header_type = RNS.Packet.HEADER_2, transport_type = Transport.TRANSPORT, transport_id = Transport.identity.hash) new_packet = RNS.Packet(announce_destination, announce_data, RNS.Packet.ANNOUNCE, context = announce_context, header_type = RNS.Packet.HEADER_2, transport_type = Transport.TRANSPORT, transport_id = Transport.identity.hash)
new_packet.hops = announce_entry[4] new_packet.hops = announce_entry[4]
RNS.log("Rebroadcasting announce for "+RNS.prettyhexrep(announce_destination.hash)+" with hop count "+str(new_packet.hops), RNS.LOG_DEBUG) RNS.log("Rebroadcasting announce for "+RNS.prettyhexrep(announce_destination.hash)+" with hop count "+str(new_packet.hops), RNS.LOG_DEBUG)
outgoing.append(new_packet) outgoing.append(new_packet)
@ -185,7 +197,6 @@ class Transport:
# Destination is directly reachable, and we know on # Destination is directly reachable, and we know on
# what interface, so transmit only on that one # what interface, so transmit only on that one
# TODO: Strip transport headers here
RNS.log("Transmitting "+str(len(packet.raw))+" bytes on: "+str(outbound_interface), RNS.LOG_EXTREME) RNS.log("Transmitting "+str(len(packet.raw))+" bytes on: "+str(outbound_interface), RNS.LOG_EXTREME)
RNS.log("Hash is "+RNS.prettyhexrep(packet.packet_hash), RNS.LOG_EXTREME) RNS.log("Hash is "+RNS.prettyhexrep(packet.packet_hash), RNS.LOG_EXTREME)
outbound_interface.processOutgoing(packet.raw) outbound_interface.processOutgoing(packet.raw)
@ -212,7 +223,7 @@ class Transport:
packet.sent = True packet.sent = True
packet.sent_at = time.time() packet.sent_at = time.time()
if (packet.packet_type == RNS.Packet.DATA): if (packet.packet_type == RNS.Packet.DATA and packet.destination.type != RNS.Destination.PLAIN):
packet.receipt = RNS.PacketReceipt(packet) packet.receipt = RNS.PacketReceipt(packet)
Transport.receipts.append(packet.receipt) Transport.receipts.append(packet.receipt)
@ -373,10 +384,13 @@ class Transport:
retries = 0 retries = 0
expires = now + Transport.PATHFINDER_E expires = now + Transport.PATHFINDER_E
local_rebroadcasts = 0 local_rebroadcasts = 0
block_rebroadcasts = False
random_blobs.append(random_blob) random_blobs.append(random_blob)
retransmit_timeout = now + math.pow(Transport.PATHFINDER_C, packet.hops) + (RNS.rand() * Transport.PATHFINDER_RW) retransmit_timeout = now + math.pow(Transport.PATHFINDER_C, packet.hops) + (RNS.rand() * Transport.PATHFINDER_RW)
Transport.announce_table[packet.destination_hash] = [now, retransmit_timeout, retries, received_from, packet.hops, packet, local_rebroadcasts] if packet.context != RNS.Packet.PATH_RESPONSE:
Transport.destination_table[packet.destination_hash] = [now, received_from, packet.hops, expires, random_blobs, packet.receiving_interface] Transport.announce_table[packet.destination_hash] = [now, retransmit_timeout, retries, received_from, packet.hops, packet, local_rebroadcasts, block_rebroadcasts]
Transport.destination_table[packet.destination_hash] = [now, received_from, packet.hops, expires, random_blobs, packet.receiving_interface, packet]
RNS.log("Path to "+RNS.prettyhexrep(packet.destination_hash)+" is now via "+RNS.prettyhexrep(received_from)+" on "+str(packet.receiving_interface), RNS.LOG_DEBUG) RNS.log("Path to "+RNS.prettyhexrep(packet.destination_hash)+" is now via "+RNS.prettyhexrep(received_from)+" on "+str(packet.receiving_interface), RNS.LOG_DEBUG)
elif packet.packet_type == RNS.Packet.LINKREQUEST: elif packet.packet_type == RNS.Packet.LINKREQUEST:
@ -533,6 +547,55 @@ class Transport:
else: else:
cache_request_packet = RNS.Packet(Transport.transport_destination(), packet_hash, context = RNS.Packet.CACHE_REQUEST) cache_request_packet = RNS.Packet(Transport.transport_destination(), packet_hash, context = RNS.Packet.CACHE_REQUEST)
@staticmethod
def hasPath(destination_hash):
if destination_hash in Transport.destination_table:
return True
else:
return False
@staticmethod
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()
@staticmethod
def pathRequestHandler(data, packet):
if len(data) >= RNS.Identity.TRUNCATED_HASHLENGTH/8:
Transport.pathRequest(data[:RNS.Identity.TRUNCATED_HASHLENGTH/8])
@staticmethod
def pathRequest(destination_hash):
RNS.log("Path request for "+RNS.prettyhexrep(destination_hash), RNS.LOG_DEBUG)
local_destination = next((d for d in Transport.destinations if d.hash == destination_hash), None)
if local_destination != None:
RNS.log("Destination is local to this system, announcing", RNS.LOG_DEBUG)
local_destination.announce()
elif destination_hash in Transport.destination_table:
RNS.log("Path found, inserting announce for transmission", RNS.LOG_DEBUG)
packet = Transport.destination_table[destination_hash][6]
received_from = Transport.destination_table[destination_hash][5]
# Setting hops to 0xFF ensures announce is not rebroadcast by any local
# nodes, but requester will still see it and get a valid path
# TODO: Consider if there is a more elegant way to do this, or whether
# rebroadcasts should actually be allowed here
faux_hops = packet.hops
now = time.time()
retries = Transport.PATHFINDER_R
local_rebroadcasts = 0
block_rebroadcasts = True
retransmit_timeout = now + Transport.PATH_REQUEST_GRACE # + (RNS.rand() * Transport.PATHFINDER_RW)
Transport.announce_table[packet.destination_hash] = [now, retransmit_timeout, retries, received_from, faux_hops, packet, local_rebroadcasts, block_rebroadcasts]
else:
RNS.log("No known path to requested destination, ignoring request", RNS.LOG_DEBUG)
@staticmethod @staticmethod
def transport_destination(): def transport_destination():
# TODO: implement this # TODO: implement this