Implemented RTT measurements on link establishment and link teardown procedure

This commit is contained in:
Mark Qvist 2018-04-21 13:54:22 +02:00
parent de8d9cf722
commit ece4f732f4
3 changed files with 133 additions and 53 deletions

View File

@ -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

View File

@ -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()

View File

@ -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