mirror of
https://github.com/markqvist/Reticulum.git
synced 2024-11-26 15:30:18 +00:00
Implemented RTT measurements on link establishment and link teardown procedure
This commit is contained in:
parent
de8d9cf722
commit
ece4f732f4
179
RNS/Link.py
179
RNS/Link.py
@ -4,7 +4,9 @@ from cryptography.hazmat.primitives import serialization
|
|||||||
from cryptography.hazmat.primitives.asymmetric import ec
|
from cryptography.hazmat.primitives.asymmetric import ec
|
||||||
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
|
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
|
||||||
from cryptography.fernet import Fernet
|
from cryptography.fernet import Fernet
|
||||||
|
import vendor.umsgpack as umsgpack
|
||||||
import base64
|
import base64
|
||||||
|
import time
|
||||||
import RNS
|
import RNS
|
||||||
|
|
||||||
import traceback
|
import traceback
|
||||||
@ -12,6 +14,7 @@ import traceback
|
|||||||
class LinkCallbacks:
|
class LinkCallbacks:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.link_established = None
|
self.link_established = None
|
||||||
|
self.link_closed = None
|
||||||
self.packet = None
|
self.packet = None
|
||||||
self.resource_started = None
|
self.resource_started = None
|
||||||
self.resource_concluded = None
|
self.resource_concluded = None
|
||||||
@ -21,8 +24,11 @@ class Link:
|
|||||||
ECPUBSIZE = 91
|
ECPUBSIZE = 91
|
||||||
BLOCKSIZE = 16
|
BLOCKSIZE = 16
|
||||||
|
|
||||||
PENDING = 0x00
|
PENDING = 0x00
|
||||||
ACTIVE = 0x01
|
HANDSHAKE = 0x01
|
||||||
|
ACTIVE = 0x02
|
||||||
|
STALE = 0x03
|
||||||
|
CLOSED = 0x04
|
||||||
|
|
||||||
ACCEPT_NONE = 0x00
|
ACCEPT_NONE = 0x00
|
||||||
ACCEPT_APP = 0x01
|
ACCEPT_APP = 0x01
|
||||||
@ -39,6 +45,7 @@ class Link:
|
|||||||
link.handshake()
|
link.handshake()
|
||||||
link.attached_interface = packet.receiving_interface
|
link.attached_interface = packet.receiving_interface
|
||||||
link.prove()
|
link.prove()
|
||||||
|
link.request_time = time.time()
|
||||||
RNS.Transport.registerLink(link)
|
RNS.Transport.registerLink(link)
|
||||||
if link.owner.callbacks.link_established != None:
|
if link.owner.callbacks.link_established != None:
|
||||||
link.owner.callbacks.link_established(link)
|
link.owner.callbacks.link_established(link)
|
||||||
@ -92,6 +99,7 @@ class Link:
|
|||||||
self.packet.pack()
|
self.packet.pack()
|
||||||
self.setLinkID(self.packet)
|
self.setLinkID(self.packet)
|
||||||
RNS.Transport.registerLink(self)
|
RNS.Transport.registerLink(self)
|
||||||
|
self.request_time = time.time()
|
||||||
self.packet.send()
|
self.packet.send()
|
||||||
RNS.log("Link request "+RNS.prettyhexrep(self.link_id)+" sent to "+str(self.destination), RNS.LOG_VERBOSE)
|
RNS.log("Link request "+RNS.prettyhexrep(self.link_id)+" sent to "+str(self.destination), RNS.LOG_VERBOSE)
|
||||||
|
|
||||||
@ -106,6 +114,7 @@ class Link:
|
|||||||
self.hash = self.link_id
|
self.hash = self.link_id
|
||||||
|
|
||||||
def handshake(self):
|
def handshake(self):
|
||||||
|
self.status = Link.HANDSHAKE
|
||||||
self.shared_key = self.prv.exchange(ec.ECDH(), self.peer_pub)
|
self.shared_key = self.prv.exchange(ec.ECDH(), self.peer_pub)
|
||||||
self.derived_key = HKDF(
|
self.derived_key = HKDF(
|
||||||
algorithm=hashes.SHA256(),
|
algorithm=hashes.SHA256(),
|
||||||
@ -131,78 +140,139 @@ class Link:
|
|||||||
if self.destination.identity.validate(signature, signed_data):
|
if self.destination.identity.validate(signature, signed_data):
|
||||||
self.loadPeer(peer_pub_bytes)
|
self.loadPeer(peer_pub_bytes)
|
||||||
self.handshake()
|
self.handshake()
|
||||||
|
self.rtt = time.time() - self.request_time
|
||||||
self.attached_interface = packet.receiving_interface
|
self.attached_interface = packet.receiving_interface
|
||||||
RNS.Transport.activateLink(self)
|
RNS.Transport.activateLink(self)
|
||||||
RNS.log("Link "+str(self)+" established with "+str(self.destination), RNS.LOG_VERBOSE)
|
RNS.log("Link "+str(self)+" established with "+str(self.destination)+", RTT is "+str(self.rtt), RNS.LOG_VERBOSE)
|
||||||
|
rtt_data = umsgpack.packb(self.rtt)
|
||||||
|
rtt_packet = RNS.Packet(self, rtt_data, context=RNS.Packet.LRRTT)
|
||||||
|
rtt_packet.send()
|
||||||
|
|
||||||
|
self.status = Link.ACTIVE
|
||||||
if self.callbacks.link_established != None:
|
if self.callbacks.link_established != None:
|
||||||
self.callbacks.link_established(self)
|
self.callbacks.link_established(self)
|
||||||
else:
|
else:
|
||||||
RNS.log("Invalid link proof signature received by "+str(self), RNS.LOG_VERBOSE)
|
RNS.log("Invalid link proof signature received by "+str(self), RNS.LOG_VERBOSE)
|
||||||
|
# TODO: should we really do this, or just wait
|
||||||
|
# for a valid one? Needs analysis.
|
||||||
|
self.teardown()
|
||||||
|
|
||||||
|
|
||||||
|
def rtt_packet(self, packet):
|
||||||
|
try:
|
||||||
|
# TODO: This is crude, we should use the delta
|
||||||
|
# to model a more representative per-bit round
|
||||||
|
# trip time, and use that to set a sensible RTT
|
||||||
|
# expectancy for the link. This will have to do
|
||||||
|
# for now though.
|
||||||
|
measured_rtt = time.time() - self.request_time
|
||||||
|
plaintext = self.decrypt(packet.data)
|
||||||
|
rtt = umsgpack.unpackb(plaintext)
|
||||||
|
#RNS.log("Measured RTT is "+str(measured_rtt)+", received RTT is "+str(rtt))
|
||||||
|
self.rtt = max(measured_rtt, rtt)
|
||||||
|
self.status = Link.ACTIVE
|
||||||
|
except Exception as e:
|
||||||
|
self.teardown()
|
||||||
|
|
||||||
def getSalt(self):
|
def getSalt(self):
|
||||||
return self.link_id
|
return self.link_id
|
||||||
|
|
||||||
def getContext(self):
|
def getContext(self):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def teardown(self):
|
||||||
|
if self.status != Link.PENDING:
|
||||||
|
teardown_packet = RNS.Packet(self, self.link_id, context=RNS.Packet.LINKCLOSE)
|
||||||
|
teardown_packet.send()
|
||||||
|
self.status = Link.CLOSED
|
||||||
|
self.link_closed()
|
||||||
|
|
||||||
|
def teardown_packet(self, packet):
|
||||||
|
try:
|
||||||
|
plaintext = self.decrypt(packet.data)
|
||||||
|
if plaintext == self.link_id:
|
||||||
|
self.status = Link.CLOSED
|
||||||
|
self.link_closed()
|
||||||
|
except Exception as e:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def link_closed(self):
|
||||||
|
self.prv = None
|
||||||
|
self.pub = None
|
||||||
|
self.pub_bytes = None
|
||||||
|
self.shared_key = None
|
||||||
|
self.derived_key = None
|
||||||
|
|
||||||
|
if self.callbacks.link_closed != None:
|
||||||
|
self.callbacks.link_closed(self)
|
||||||
|
|
||||||
|
|
||||||
def receive(self, packet):
|
def receive(self, packet):
|
||||||
if packet.receiving_interface != self.attached_interface:
|
if not self.status == Link.CLOSED:
|
||||||
RNS.log("Link-associated packet received on unexpected interface! Someone might be trying to manipulate your communication!", RNS.LOG_ERROR)
|
if packet.receiving_interface != self.attached_interface:
|
||||||
else:
|
RNS.log("Link-associated packet received on unexpected interface! Someone might be trying to manipulate your communication!", RNS.LOG_ERROR)
|
||||||
if packet.packet_type == RNS.Packet.DATA:
|
else:
|
||||||
if packet.context == RNS.Packet.NONE:
|
if packet.packet_type == RNS.Packet.DATA:
|
||||||
plaintext = self.decrypt(packet.data)
|
if packet.context == RNS.Packet.NONE:
|
||||||
if (self.callbacks.packet != None):
|
plaintext = self.decrypt(packet.data)
|
||||||
self.callbacks.packet(plaintext, packet)
|
if (self.callbacks.packet != None):
|
||||||
|
self.callbacks.packet(plaintext, packet)
|
||||||
|
|
||||||
elif packet.context == RNS.Packet.RESOURCE_ADV:
|
elif packet.context == RNS.Packet.LRRTT:
|
||||||
packet.plaintext = self.decrypt(packet.data)
|
if not self.initiator:
|
||||||
if self.resource_strategy == Link.ACCEPT_NONE:
|
self.rtt_packet(packet)
|
||||||
pass
|
|
||||||
elif self.resource_strategy == Link.ACCEPT_APP:
|
|
||||||
if self.callbacks.resource != None:
|
|
||||||
self.callbacks.resource(packet)
|
|
||||||
elif self.resource_strategy == Link.ACCEPT_ALL:
|
|
||||||
RNS.Resource.accept(packet, self.callbacks.resource_concluded)
|
|
||||||
|
|
||||||
elif packet.context == RNS.Packet.RESOURCE_REQ:
|
elif packet.context == RNS.Packet.LINKCLOSE:
|
||||||
plaintext = self.decrypt(packet.data)
|
self.teardown_packet(packet)
|
||||||
if ord(plaintext[:1]) == RNS.Resource.HASHMAP_IS_EXHAUSTED:
|
|
||||||
resource_hash = plaintext[1+RNS.Resource.MAPHASH_LEN:RNS.Identity.HASHLENGTH/8+1+RNS.Resource.MAPHASH_LEN]
|
|
||||||
else:
|
|
||||||
resource_hash = plaintext[1:RNS.Identity.HASHLENGTH/8+1]
|
|
||||||
for resource in self.outgoing_resources:
|
|
||||||
if resource.hash == resource_hash:
|
|
||||||
resource.request(plaintext)
|
|
||||||
|
|
||||||
elif packet.context == RNS.Packet.RESOURCE_HMU:
|
elif packet.context == RNS.Packet.RESOURCE_ADV:
|
||||||
plaintext = self.decrypt(packet.data)
|
packet.plaintext = self.decrypt(packet.data)
|
||||||
resource_hash = plaintext[:RNS.Identity.HASHLENGTH/8]
|
if self.resource_strategy == Link.ACCEPT_NONE:
|
||||||
for resource in self.incoming_resources:
|
pass
|
||||||
if resource_hash == resource.hash:
|
elif self.resource_strategy == Link.ACCEPT_APP:
|
||||||
resource.hashmap_update_packet(plaintext)
|
if self.callbacks.resource != None:
|
||||||
|
self.callbacks.resource(packet)
|
||||||
|
elif self.resource_strategy == Link.ACCEPT_ALL:
|
||||||
|
RNS.Resource.accept(packet, self.callbacks.resource_concluded)
|
||||||
|
|
||||||
elif packet.context == RNS.Packet.RESOURCE_ICL:
|
elif packet.context == RNS.Packet.RESOURCE_REQ:
|
||||||
plaintext = self.decrypt(packet.data)
|
plaintext = self.decrypt(packet.data)
|
||||||
resource_hash = plaintext[:RNS.Identity.HASHLENGTH/8]
|
if ord(plaintext[:1]) == RNS.Resource.HASHMAP_IS_EXHAUSTED:
|
||||||
for resource in self.incoming_resources:
|
resource_hash = plaintext[1+RNS.Resource.MAPHASH_LEN:RNS.Identity.HASHLENGTH/8+1+RNS.Resource.MAPHASH_LEN]
|
||||||
if resource_hash == resource.hash:
|
else:
|
||||||
resource.cancel()
|
resource_hash = plaintext[1:RNS.Identity.HASHLENGTH/8+1]
|
||||||
|
for resource in self.outgoing_resources:
|
||||||
|
if resource.hash == resource_hash:
|
||||||
|
resource.request(plaintext)
|
||||||
|
|
||||||
# TODO: find the most efficient way to allow multiple
|
elif packet.context == RNS.Packet.RESOURCE_HMU:
|
||||||
# transfers at the same time, sending resource hash on
|
plaintext = self.decrypt(packet.data)
|
||||||
# each packet is a huge overhead
|
resource_hash = plaintext[:RNS.Identity.HASHLENGTH/8]
|
||||||
elif packet.context == RNS.Packet.RESOURCE:
|
for resource in self.incoming_resources:
|
||||||
for resource in self.incoming_resources:
|
if resource_hash == resource.hash:
|
||||||
resource.receive_part(packet)
|
resource.hashmap_update_packet(plaintext)
|
||||||
|
|
||||||
elif packet.packet_type == RNS.Packet.PROOF:
|
elif packet.context == RNS.Packet.RESOURCE_ICL:
|
||||||
if packet.context == RNS.Packet.RESOURCE_PRF:
|
plaintext = self.decrypt(packet.data)
|
||||||
resource_hash = packet.data[0:RNS.Identity.HASHLENGTH/8]
|
resource_hash = plaintext[:RNS.Identity.HASHLENGTH/8]
|
||||||
for resource in self.outgoing_resources:
|
for resource in self.incoming_resources:
|
||||||
if resource_hash == resource.hash:
|
if resource_hash == resource.hash:
|
||||||
resource.validateProof(packet.data)
|
resource.cancel()
|
||||||
|
|
||||||
|
# TODO: find the most efficient way to allow multiple
|
||||||
|
# transfers at the same time, sending resource hash on
|
||||||
|
# each packet is a huge overhead. Probably some kind
|
||||||
|
# of hash -> sequence map
|
||||||
|
elif packet.context == RNS.Packet.RESOURCE:
|
||||||
|
for resource in self.incoming_resources:
|
||||||
|
resource.receive_part(packet)
|
||||||
|
|
||||||
|
elif packet.packet_type == RNS.Packet.PROOF:
|
||||||
|
if packet.context == RNS.Packet.RESOURCE_PRF:
|
||||||
|
resource_hash = packet.data[0:RNS.Identity.HASHLENGTH/8]
|
||||||
|
for resource in self.outgoing_resources:
|
||||||
|
if resource_hash == resource.hash:
|
||||||
|
resource.validateProof(packet.data)
|
||||||
|
|
||||||
|
|
||||||
def encrypt(self, plaintext):
|
def encrypt(self, plaintext):
|
||||||
@ -229,6 +299,9 @@ class Link:
|
|||||||
def link_established_callback(self, callback):
|
def link_established_callback(self, callback):
|
||||||
self.callbacks.link_established = callback
|
self.callbacks.link_established = callback
|
||||||
|
|
||||||
|
def link_closed_callback(self, callback):
|
||||||
|
self.callbacks.link_closed = callback
|
||||||
|
|
||||||
def packet_callback(self, callback):
|
def packet_callback(self, callback):
|
||||||
self.callbacks.packet = callback
|
self.callbacks.packet = callback
|
||||||
|
|
||||||
|
@ -29,6 +29,8 @@ class Packet:
|
|||||||
RESPONSE = 0x09
|
RESPONSE = 0x09
|
||||||
COMMAND = 0x0A
|
COMMAND = 0x0A
|
||||||
COMMAND_STAT = 0x0B
|
COMMAND_STAT = 0x0B
|
||||||
|
LINKCLOSE = 0xFD
|
||||||
|
LRRTT = 0xFE
|
||||||
LRPROOF = 0xFF
|
LRPROOF = 0xFF
|
||||||
|
|
||||||
HEADER_MAXSIZE = 23
|
HEADER_MAXSIZE = 23
|
||||||
@ -143,6 +145,9 @@ class Packet:
|
|||||||
|
|
||||||
def send(self):
|
def send(self):
|
||||||
if not self.sent:
|
if not self.sent:
|
||||||
|
if self.destination.type == RNS.Destination.LINK:
|
||||||
|
if self.destination.status == RNS.Link.CLOSED:
|
||||||
|
raise IOError("Attempt to transmit over a closed link")
|
||||||
if not self.packed:
|
if not self.packed:
|
||||||
self.pack()
|
self.pack()
|
||||||
|
|
||||||
|
@ -74,6 +74,8 @@ class Transport:
|
|||||||
if interface.OUT:
|
if interface.OUT:
|
||||||
should_transmit = True
|
should_transmit = True
|
||||||
if packet.destination.type == RNS.Destination.LINK:
|
if packet.destination.type == RNS.Destination.LINK:
|
||||||
|
if packet.destination.status == RNS.Link.CLOSED:
|
||||||
|
should_transmit = False
|
||||||
if interface != packet.destination.attached_interface:
|
if interface != packet.destination.attached_interface:
|
||||||
should_transmit = False
|
should_transmit = False
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user