Started bundle class

This commit is contained in:
Mark Qvist 2020-06-14 18:33:01 +02:00
parent 4ffe4482d3
commit e2122be006
6 changed files with 76 additions and 57 deletions

View File

@ -1056,8 +1056,9 @@ body .markdown-body
.codehilite .il{color:#009999} .codehilite .il{color:#009999}
.codehilite .gc{color:#999;background-color:#EAF2F5} .codehilite .gc{color:#999;background-color:#EAF2F5}
</style><title>README</title></head><body><article class="markdown-body"><h1 id="reticulum-network-stack">Reticulum Network Stack α<a class="headerlink" href="#reticulum-network-stack" title="Permanent link"></a></h1> </style><title>README</title></head><body><article class="markdown-body"><h1 id="reticulum-network-stack">Reticulum Network Stack α<a class="headerlink" href="#reticulum-network-stack" title="Permanent link"></a></h1>
<p>Reticulum is a cryptography-based networking stack for high-latency, wide-area networks built on readily available hardware. Reticulum allows you to build very wide-area networks with off-the-shelf tools, and offers end-to-end encryption, autoconfiguring cryptographically backed multi-hop transport, efficient addressing, resource caching, unforgeable packet acknowledgements and much more.</p> <p>Reticulum is a cryptography-based networking stack for high-latency, wide-area networks built on readily available hardware. Reticulum allows you to build very wide-area networks with off-the-shelf tools, and offers end-to-end encryption, autoconfiguring cryptographically backed multi-hop transport, efficient addressing, unforgeable packet acknowledgements and more.</p>
<p>Reticulum is a complete networking stack, and does not use IP or higher layers, although it can be easily tunnelled through conventional IP networks. This frees up overhead, that has been utilised to implement a networking stack built directly on cryptographic principles, allowing resilience and stable functionality in open and trustless networks.</p> <p>Reticulum is a complete networking stack, and does not use IP or higher layers, although it is easy to utilise IP (with TCP or UDP) as the underlying carrier for Reticulum.</p>
<p>Having no dependencies on traditional networking stacks free up overhead that has been utilised to implement a networking stack built directly on cryptographic principles, allowing resilience and stable functionality in open and trustless networks.</p>
<p>No kernel modules or drivers are required. Reticulum runs completely in userland, and can run on practically any system that runs Python 3.</p> <p>No kernel modules or drivers are required. Reticulum runs completely in userland, and can run on practically any system that runs Python 3.</p>
<p>For more info, see <a href="https://unsigned.io/projects/reticulum/">unsigned.io/projects/reticulum</a></p> <p>For more info, see <a href="https://unsigned.io/projects/reticulum/">unsigned.io/projects/reticulum</a></p>
<h2 id="notable-features">Notable Features<a class="headerlink" href="#notable-features" title="Permanent link"></a></h2> <h2 id="notable-features">Notable Features<a class="headerlink" href="#notable-features" title="Permanent link"></a></h2>
@ -1066,7 +1067,7 @@ body .markdown-body
<li>Fully self-configuring multi-hop routing</li> <li>Fully self-configuring multi-hop routing</li>
<li>Asymmetric RSA encryption and signatures as basis for all communication</li> <li>Asymmetric RSA encryption and signatures as basis for all communication</li>
<li>Perfect Forward Secrecy on links with ephemereal Elliptic Curve Diffie-Hellman keys (on the SECP256R1 curve)</li> <li>Perfect Forward Secrecy on links with ephemereal Elliptic Curve Diffie-Hellman keys (on the SECP256R1 curve)</li>
<li>Reticulum uses the <a href="https://github.com/fernet/spec/blob/master/Spec.md">Fernet</a> specification for encryption to group destinations and on links<ul> <li>Reticulum uses the <a href="https://github.com/fernet/spec/blob/master/Spec.md">Fernet</a> specification for encryption on links and to group destinations<ul>
<li>AES-128 in CBC mode with PKCS7 padding</li> <li>AES-128 in CBC mode with PKCS7 padding</li>
<li>HMAC using SHA256 for authentication</li> <li>HMAC using SHA256 for authentication</li>
<li>IVs are generated through os.urandom()</li> <li>IVs are generated through os.urandom()</li>
@ -1075,7 +1076,7 @@ body .markdown-body
<li>Unforgeable packet delivery confirmations</li> <li>Unforgeable packet delivery confirmations</li>
<li>A variety of supported interface types</li> <li>A variety of supported interface types</li>
<li>Efficient and easy resource transfers</li> <li>Efficient and easy resource transfers</li>
<li>A simple and easy-to-use API</li> <li>An intuitive and easy-to-use API</li>
</ul> </ul>
<h2 id="where-can-reticulum-be-used">Where can Reticulum be used?<a class="headerlink" href="#where-can-reticulum-be-used" title="Permanent link"></a></h2> <h2 id="where-can-reticulum-be-used">Where can Reticulum be used?<a class="headerlink" href="#where-can-reticulum-be-used" title="Permanent link"></a></h2>
<p>On practically any hardware that can support at least a half-duplex channel with 1.000 bits per second throughput, and an MTU of 500 bytes. Data radios, modems, LoRa radios, serial lines, AX.25 TNCs, amateur radio digital modes, free-space optical links and similar systems are all examples of the types of interfaces Reticulum was designed for.</p> <p>On practically any hardware that can support at least a half-duplex channel with 1.000 bits per second throughput, and an MTU of 500 bytes. Data radios, modems, LoRa radios, serial lines, AX.25 TNCs, amateur radio digital modes, free-space optical links and similar systems are all examples of the types of interfaces Reticulum was designed for.</p>

View File

@ -1,9 +1,11 @@
Reticulum Network Stack α Reticulum Network Stack α
========== ==========
Reticulum is a cryptography-based networking stack for high-latency, wide-area networks built on readily available hardware. Reticulum allows you to build very wide-area networks with off-the-shelf tools, and offers end-to-end encryption, autoconfiguring cryptographically backed multi-hop transport, efficient addressing, resource caching, unforgeable packet acknowledgements and much more. Reticulum is a cryptography-based networking stack for high-latency, wide-area networks built on readily available hardware. Reticulum allows you to build very wide-area networks with off-the-shelf tools, and offers end-to-end encryption, autoconfiguring cryptographically backed multi-hop transport, efficient addressing, unforgeable packet acknowledgements and more.
Reticulum is a complete networking stack, and does not use IP or higher layers, although it can be easily tunnelled through conventional IP networks. This frees up overhead, that has been utilised to implement a networking stack built directly on cryptographic principles, allowing resilience and stable functionality in open and trustless networks. Reticulum is a complete networking stack, and does not use IP or higher layers, although it is easy to utilise IP (with TCP or UDP) as the underlying carrier for Reticulum.
Having no dependencies on traditional networking stacks free up overhead that has been utilised to implement a networking stack built directly on cryptographic principles, allowing resilience and stable functionality in open and trustless networks.
No kernel modules or drivers are required. Reticulum runs completely in userland, and can run on practically any system that runs Python 3. No kernel modules or drivers are required. Reticulum runs completely in userland, and can run on practically any system that runs Python 3.
@ -14,14 +16,14 @@ For more info, see [unsigned.io/projects/reticulum](https://unsigned.io/projects
- Fully self-configuring multi-hop routing - Fully self-configuring multi-hop routing
- Asymmetric RSA encryption and signatures as basis for all communication - Asymmetric RSA encryption and signatures as basis for all communication
- Perfect Forward Secrecy on links with ephemereal Elliptic Curve Diffie-Hellman keys (on the SECP256R1 curve) - Perfect Forward Secrecy on links with ephemereal Elliptic Curve Diffie-Hellman keys (on the SECP256R1 curve)
- Reticulum uses the [Fernet](https://github.com/fernet/spec/blob/master/Spec.md) specification for encryption to group destinations and on links - Reticulum uses the [Fernet](https://github.com/fernet/spec/blob/master/Spec.md) specification for encryption on links and to group destinations
- AES-128 in CBC mode with PKCS7 padding - AES-128 in CBC mode with PKCS7 padding
- HMAC using SHA256 for authentication - HMAC using SHA256 for authentication
- IVs are generated through os.urandom() - IVs are generated through os.urandom()
- Unforgeable packet delivery confirmations - Unforgeable packet delivery confirmations
- A variety of supported interface types - A variety of supported interface types
- Efficient and easy resource transfers - Efficient and easy resource transfers
- A simple and easy-to-use API - An intuitive and easy-to-use API
## Where can Reticulum be used? ## Where can Reticulum be used?
On practically any hardware that can support at least a half-duplex channel with 1.000 bits per second throughput, and an MTU of 500 bytes. Data radios, modems, LoRa radios, serial lines, AX.25 TNCs, amateur radio digital modes, free-space optical links and similar systems are all examples of the types of interfaces Reticulum was designed for. On practically any hardware that can support at least a half-duplex channel with 1.000 bits per second throughput, and an MTU of 500 bytes. Data radios, modems, LoRa radios, serial lines, AX.25 TNCs, amateur radio digital modes, free-space optical links and similar systems are all examples of the types of interfaces Reticulum was designed for.

0
RNS/Bundle.py Normal file
View File

View File

@ -89,6 +89,7 @@ class Packet:
self.packet_hash = None self.packet_hash = None
self.attached_interface = attached_interface self.attached_interface = attached_interface
self.receiving_interface = None
def getPackedFlags(self): def getPackedFlags(self):
if self.context == Packet.LRPROOF: if self.context == Packet.LRPROOF:
@ -127,9 +128,12 @@ class Packet:
# Keepalive packets contain no actual # Keepalive packets contain no actual
# data # data
self.ciphertext = self.data self.ciphertext = self.data
elif self.context == Packet.CACHE_REQUEST:
# Cache-requests are not encrypted
self.ciphertext = self.data
else: else:
# In all other cases, we encrypt the packet # In all other cases, we encrypt the packet
# with the destination's public key # with the destination's encryption method
self.ciphertext = self.destination.encrypt(self.data) self.ciphertext = self.destination.encrypt(self.data)
if self.header_type == Packet.HEADER_2: if self.header_type == Packet.HEADER_2:

View File

@ -22,7 +22,7 @@ class Resource:
# #
# A small system in this regard is # A small system in this regard is
# defined as a Raspberry Pi, which should # defined as a Raspberry Pi, which should
# be able to compress, encrypt and hashmap # be able to compress, encrypt and hash-map
# the resource in about 10 seconds. # the resource in about 10 seconds.
MAX_EFFICIENT_SIZE = 16 * 1024 * 1024 MAX_EFFICIENT_SIZE = 16 * 1024 * 1024
@ -308,7 +308,7 @@ class Resource:
if sleep_time < 0: if sleep_time < 0:
if self.retries_left > 0: if self.retries_left > 0:
RNS.log("Timeout waiting for parts, requesting retry", RNS.LOG_DEBUG) RNS.log("Timed out waiting for parts, requesting retry", RNS.LOG_DEBUG)
if self.window > self.window_min: if self.window > self.window_min:
self.window -= 1 self.window -= 1
if self.window_max > self.window_min: if self.window_max > self.window_min:
@ -344,7 +344,7 @@ class Resource:
expected_data = self.hash + self.expected_proof expected_data = self.hash + self.expected_proof
expected_proof_packet = RNS.Packet(self.link, expected_data, packet_type=RNS.Packet.PROOF, context=RNS.Packet.RESOURCE_PRF) expected_proof_packet = RNS.Packet(self.link, expected_data, packet_type=RNS.Packet.PROOF, context=RNS.Packet.RESOURCE_PRF)
expected_proof_packet.pack() expected_proof_packet.pack()
RNS.Transport.cache_request(expected_proof_packet.packet_hash) RNS.Transport.cache_request(expected_proof_packet.packet_hash, self.link)
self.last_part_sent = time.time() self.last_part_sent = time.time()
sleep_time = 0.001 sleep_time = 0.001

View File

@ -338,7 +338,7 @@ class Transport:
new_raw += Transport.destination_table[packet.destination_hash][1] new_raw += Transport.destination_table[packet.destination_hash][1]
new_raw += packet.raw[2:] new_raw += packet.raw[2:]
# TODO: Remove at some point # TODO: Remove at some point
RNS.log("Packet was inserted into transport via "+RNS.prettyhexrep(Transport.destination_table[packet.destination_hash][1])+" on: "+str(outbound_interface), RNS.LOG_EXTREME) # RNS.log("Packet was inserted into transport via "+RNS.prettyhexrep(Transport.destination_table[packet.destination_hash][1])+" on: "+str(outbound_interface), RNS.LOG_EXTREME)
outbound_interface.processOutgoing(new_raw) outbound_interface.processOutgoing(new_raw)
Transport.destination_table[packet.destination_hash][0] = time.time() Transport.destination_table[packet.destination_hash][0] = time.time()
sent = True sent = True
@ -359,7 +359,7 @@ class Transport:
new_raw += Transport.destination_table[packet.destination_hash][1] new_raw += Transport.destination_table[packet.destination_hash][1]
new_raw += packet.raw[2:] new_raw += packet.raw[2:]
# TODO: Remove at some point # TODO: Remove at some point
RNS.log("Packet was inserted into transport via "+RNS.prettyhexrep(Transport.destination_table[packet.destination_hash][1])+" on: "+str(outbound_interface), RNS.LOG_EXTREME) # RNS.log("Packet was inserted into transport via "+RNS.prettyhexrep(Transport.destination_table[packet.destination_hash][1])+" on: "+str(outbound_interface), RNS.LOG_EXTREME)
outbound_interface.processOutgoing(new_raw) outbound_interface.processOutgoing(new_raw)
Transport.destination_table[packet.destination_hash][0] = time.time() Transport.destination_table[packet.destination_hash][0] = time.time()
sent = True sent = True
@ -416,6 +416,7 @@ class Transport:
Transport.jobs_locked = False Transport.jobs_locked = False
return sent return sent
@staticmethod @staticmethod
def packet_filter(packet): def packet_filter(packet):
# TODO: Think long and hard about this. # TODO: Think long and hard about this.
@ -429,8 +430,11 @@ class Transport:
return True return True
if packet.context == RNS.Packet.RESOURCE: if packet.context == RNS.Packet.RESOURCE:
return True return True
if packet.context == RNS.Packet.CACHE_REQUEST:
return True
if packet.destination_type == RNS.Destination.PLAIN: if packet.destination_type == RNS.Destination.PLAIN:
return True return True
if not packet.packet_hash in Transport.packet_hashlist: if not packet.packet_hash in Transport.packet_hashlist:
return True return True
else: else:
@ -455,9 +459,6 @@ class Transport:
RNS.log(str(interface)+" received packet with hash "+RNS.prettyhexrep(packet.packet_hash), RNS.LOG_EXTREME) RNS.log(str(interface)+" received packet with hash "+RNS.prettyhexrep(packet.packet_hash), RNS.LOG_EXTREME)
if len(Transport.local_client_interfaces) > 0: if len(Transport.local_client_interfaces) > 0:
new_raw = packet.raw[0:1]
new_raw += struct.pack("!B", packet.hops)
new_raw += packet.raw[2:]
if Transport.is_local_client_interface(interface): if Transport.is_local_client_interface(interface):
packet.hops -= 1 packet.hops -= 1
@ -506,10 +507,19 @@ class Transport:
# shared instance, so they look directly reach- # shared instance, so they look directly reach-
# able), and reinsert, so the normal transport # able), and reinsert, so the normal transport
# implementation can handle the packet. # implementation can handle the packet.
if packet.transport_id == None and for_local_client: if packet.transport_id == None and for_local_client:
packet.transport_id = Transport.identity.hash packet.transport_id = Transport.identity.hash
# If this is a cache request, and we can fullfill
# it, do so and stop processing. Otherwise resume
# normal processing.
if packet.context == RNS.Packet.CACHE_REQUEST:
if Transport.cache_request_packet(packet):
return
# If the packet is in transport, check whether we
# are the designated next hop, and process it
# accordingly if we are.
if packet.transport_id != None and packet.packet_type != RNS.Packet.ANNOUNCE: if packet.transport_id != None and packet.packet_type != RNS.Packet.ANNOUNCE:
if packet.transport_id == Transport.identity.hash: 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) RNS.log("Received packet in transport for "+RNS.prettyhexrep(packet.destination_hash)+" with matching transport ID, transporting it...", RNS.LOG_DEBUG)
@ -563,10 +573,8 @@ class Transport:
else: else:
# TODO: There should probably be some kind of REJECT # TODO: There should probably be some kind of REJECT
# mechanism here, to signal to the source that their # mechanism here, to signal to the source that their
# expected path failed # expected path failed.
RNS.log("Got packet in transport, but no known path to final destination. Dropping packet.", RNS.LOG_DEBUG) RNS.log("Got packet in transport, but no known path to final destination. Dropping packet.", RNS.LOG_DEBUG)
else:
pass
# Link transport handling. Directs packets according # Link transport handling. Directs packets according
# to entries in the link tables # to entries in the link tables
@ -872,10 +880,8 @@ class Transport:
@staticmethod @staticmethod
def shouldCache(packet): def shouldCache(packet):
# TODO: Implement sensible rules for which if packet.context == RNS.Packet.RESOURCE_PRF:
# packets to cache return True
#if packet.context == RNS.Packet.RESOURCE_PRF:
# return True
return False return False
@ -889,10 +895,14 @@ class Transport:
if RNS.Transport.shouldCache(packet) or force_cache: if RNS.Transport.shouldCache(packet) or force_cache:
try: try:
packet_hash = RNS.hexrep(packet.getHash(), delimit=False) packet_hash = RNS.hexrep(packet.getHash(), delimit=False)
interface_reference = None
if packet.receiving_interface != None:
interface_reference = str(packet.receiving_interface)
file = open(RNS.Reticulum.cachepath+"/"+packet_hash, "wb") file = open(RNS.Reticulum.cachepath+"/"+packet_hash, "wb")
file.write(packet.raw) file.write(umsgpack.packb([packet.raw, interface_reference]))
file.close() file.close()
RNS.log("Wrote packet "+packet_hash+" to cache", RNS.LOG_EXTREME)
except Exception as e: except Exception as e:
RNS.log("Error writing packet to cache", RNS.LOG_ERROR) RNS.log("Error writing packet to cache", RNS.LOG_ERROR)
RNS.log("The contained exception was: "+str(e)) RNS.log("The contained exception was: "+str(e))
@ -902,11 +912,19 @@ class Transport:
try: try:
packet_hash = RNS.hexrep(packet_hash, delimit=False) packet_hash = RNS.hexrep(packet_hash, delimit=False)
path = RNS.Reticulum.cachepath+"/"+packet_hash path = RNS.Reticulum.cachepath+"/"+packet_hash
if os.path.isfile(path): if os.path.isfile(path):
file = open(path, "rb") file = open(path, "rb")
raw = file.read() cached_data = umsgpack.unpackb(file.read())
file.close() file.close()
packet = RNS.Packet(None, raw)
packet = RNS.Packet(None, cached_data[0])
interface_reference = cached_data[1]
for interface in Transport.interfaces:
if str(interface) == interface_reference:
packet.receiving_interface = interface
return packet return packet
else: else:
return None return None
@ -914,33 +932,34 @@ class Transport:
RNS.log("Exception occurred while getting cached packet.", RNS.LOG_ERROR) RNS.log("Exception occurred while getting cached packet.", RNS.LOG_ERROR)
RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR) RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR)
# TODO: Implement cache requests. Needs methodology
# rethinking. This is skeleton code.
@staticmethod @staticmethod
def cache_request_packet(packet): def cache_request_packet(packet):
if len(packet.data) == RNS.Identity.HASHLENGTH/8: if len(packet.data) == RNS.Identity.HASHLENGTH/8:
packet_hash = RNS.hexrep(packet.data, delimit=False) packet = Transport.get_cached_packet(packet.data)
packet = Transport.get_cached_packet(packet_hash)
if packet != None: if packet != None:
# TODO: Implement outbound for this # If the packet was retrieved from the local
pass # cache, replay it to the Transport instance,
# so that it can be directed towards it original
# destination.
Transport.inbound(packet.raw, packet.receiving_interface)
return True
else: else:
pass return False
else:
return False
# TODO: Implement cache requests. Needs methodology
# rethinking. This is skeleton code.
@staticmethod @staticmethod
def cache_request(packet_hash): def cache_request(packet_hash, destination):
RNS.log("Cache request for "+RNS.prettyhexrep(packet_hash), RNS.LOG_EXTREME) cached_packet = Transport.get_cached_packet(packet_hash)
path = RNS.Reticulum.cachepath+"/"+RNS.hexrep(packet_hash, delimit=False) if cached_packet:
if os.path.isfile(path): # The packet was found in the local cache,
file = open(path, "rb") # replay it to the Transport instance.
raw = file.read() Transport.inbound(packet.raw, packet.receiving_interface)
Transport.inbound(raw)
file.close()
else: else:
cache_request_packet = RNS.Packet(Transport.transport_destination(), packet_hash, context = RNS.Packet.CACHE_REQUEST) # The packet is not in the local cache,
# query the network.
RNS.Packet(destination, packet_hash, context = RNS.Packet.CACHE_REQUEST).send()
@staticmethod @staticmethod
def hasPath(destination_hash): def hasPath(destination_hash):
@ -1026,13 +1045,6 @@ class Transport:
else: else:
RNS.log("No known path to requested destination, ignoring request", RNS.LOG_DEBUG) RNS.log("No known path to requested destination, ignoring request", RNS.LOG_DEBUG)
# TODO: Currently only used for cache requests.
# Needs rethink.
@staticmethod
def transport_destination():
# TODO: implement this
pass
@staticmethod @staticmethod
def from_local_client(packet): def from_local_client(packet):
if hasattr(packet.receiving_interface, "parent_interface"): if hasattr(packet.receiving_interface, "parent_interface"):