Compare commits

...

74 Commits

Author SHA1 Message Date
Mark Qvist 8579a7f2a5 Updated changelog 2025-07-14 16:37:41 +02:00
Mark Qvist ffbbba7395 Updated manual 2025-07-14 16:37:06 +02:00
Mark Qvist e66745c9ef Updated documentation 2025-07-14 16:07:07 +02:00
Mark Qvist 45fc9338a7 Improved BLE MTU on Android 2025-07-13 23:45:50 +02:00
Mark Qvist f2969bd1b0 Improved BLE device discovery on Android 2025-07-13 22:07:47 +02:00
Mark Qvist e0f1f3f947 Merge branch 'master' of github.com:markqvist/Reticulum 2025-07-13 19:17:13 +02:00
Mark Qvist e3827f2e25 Updated manual 2025-07-13 19:17:00 +02:00
Mark Qvist fad1d4972c Fixed potential EPOLL hang on interface failure 2025-07-13 19:16:51 +02:00
markqvist 2c33ce6c98 Merge pull request #839 from wincentbalin/master
Fixed typo
2025-07-13 15:02:45 +02:00
Mark Qvist c0d7f42f17 Cleanup 2025-07-13 14:51:24 +02:00
Mark Qvist d5a8e4b056 Trace exception on public key load failure 2025-07-13 13:58:09 +02:00
Mark Qvist 76dd50a060 Fixed potential AutoInterface peer discovery add before final init complete 2025-07-13 13:14:18 +02:00
Mark Qvist 6f9a9a7ad9 Fixed link request handling with invalid link mode 2025-07-13 13:05:37 +02:00
Mark Qvist eaec9a493b Updated readme 2025-07-13 12:28:50 +02:00
Mark Qvist d3c8555b39 Updated documentation 2025-07-13 12:26:24 +02:00
Mark Qvist 446f5c0989 Merge branch 'master' of github.com:markqvist/Reticulum 2025-07-13 12:05:59 +02:00
markqvist f3b72a8a3c Merge pull request #844 from Aareon/patch-2
fix(rnstatus): Add validation for missing -i flag when using -R
2025-07-13 12:06:20 +02:00
markqvist d2c5a1f34b Merge pull request #856 from jacobeva/multi_transport_fix
Fix announce cap crash when transport mode enabled
2025-07-13 11:56:59 +02:00
markqvist 182b49cc04 Merge pull request #843 from Aareon/patch-1
docs: Document -R and -i flag dependency in rnstatus usage
2025-07-13 11:54:59 +02:00
Mark Qvist cc8bd34cd4 Merge branch 'master' of github.com:markqvist/Reticulum 2025-07-13 11:54:54 +02:00
Mark Qvist 957ece7394 Fixed typo 2025-07-13 11:53:25 +02:00
Mark Qvist 762343adf9 Updated version 2025-07-13 11:49:44 +02:00
Mark Qvist 8d32b378d9 Fixed log statements 2025-07-13 11:49:12 +02:00
jacob.eva 41e816d299 Fix announce cap crash when transport mode enabled 2025-07-01 18:16:24 +01:00
Aareon Sullivan 4226a62f23 fix(rnstatus): Add validation for missing -i flag when using -R
Add check to ensure `management_identity` is provided when using remote
query flag (`-R`). Prevents `TypeError` and provides clear error message
when user forgets to specify identity file with `-i` flag.

Before: `expected str, bytes or os.PathLike object, not NoneType`
After: `Remote management requires an identity file. Use -i to specify the path to a management identity.`

Fixes #792
2025-06-08 19:52:46 -05:00
Aareon Sullivan 5dda28559b docs: Document -R and -i flag dependency in rnstatus usage
Add usage example and clarify that -R requires -i flag for remote
transport instance status queries. Fixes confusion where users get
"expected str, bytes or os.PathLike object, not NoneType" error
when using -R without -i.

- Add remote status query example to Usage Examples section
- Update -R flag description to indicate -i requirement
- Add explanatory note about management identity authorization

Addresses user reported issue where documentation was unclear about
mandatory flag combination for remote management functionality.
2025-06-08 19:33:42 -05:00
Wincent Balin d055ca50d6 Fixed typo 2025-05-31 01:12:25 +02:00
Mark Qvist 799bcfc7aa Updated version 2025-05-26 19:08:03 +02:00
Mark Qvist 045cb662ef Removed legacy AES-128 handlers 2025-05-26 19:04:30 +02:00
Mark Qvist 51e3983bf8 Updated readme 2025-05-17 10:39:52 +02:00
Mark Qvist 95fdc41845 Updated readme 2025-05-17 10:39:30 +02:00
Mark Qvist d795fbeaf3 Updated documentation 2025-05-17 10:37:11 +02:00
Mark Qvist 13037d68ed Updated documentation 2025-05-17 10:35:49 +02:00
Mark Qvist 6da5df9f21 Merge branch 'master' of github.com:markqvist/Reticulum 2025-05-17 10:25:24 +02:00
Mark Qvist 8128f573ef Updated manual 2025-05-17 10:25:09 +02:00
markqvist accf104553 Update FUNDING.yml 2025-05-17 10:24:38 +02:00
Mark Qvist 5387264dcb Updated changelog 2025-05-15 22:24:33 +02:00
Mark Qvist 308a6906db Updated manual 2025-05-15 15:32:31 +02:00
Mark Qvist 96ce7e3f47 Updated changelog 2025-05-15 15:32:20 +02:00
Mark Qvist f186b6266b Implemented dynamic keepalive and link timeout calculation 2025-05-15 12:50:16 +02:00
Mark Qvist 756029e5af Added option to specify shared instance type 2025-05-15 01:14:55 +02:00
Mark Qvist c1673f39b6 Updated changelog 2025-05-13 19:46:18 +02:00
Mark Qvist 30a08c4192 Updated manual 2025-05-13 18:01:13 +02:00
Mark Qvist d680f4d411 Cleanup 2025-05-13 17:59:26 +02:00
Mark Qvist 29a52e19cf Cleanup 2025-05-13 17:25:00 +02:00
Mark Qvist 11511168dc Cleanup 2025-05-13 13:32:35 +02:00
Mark Qvist d4ea698236 Cleanup 2025-05-13 13:29:20 +02:00
Mark Qvist 11e06b477e Cleanup 2025-05-13 13:26:26 +02:00
Mark Qvist 4e4c68071f Removed legacy encryption modes. Default to AES-256 for links and packets. 2025-05-13 13:18:44 +02:00
Mark Qvist 5f502746a4 Updated tests 2025-05-13 13:16:37 +02:00
Mark Qvist 17bbb9c0b4 Updated docs 2025-05-12 20:20:29 +02:00
Mark Qvist 8b13d6e08b Fixed announce handlers not triggering after shared instance disappearance/reappearance 2025-05-12 11:41:06 +02:00
Mark Qvist efa512be32 Cleanup 2025-05-11 16:40:14 +02:00
Mark Qvist 594f5fba1e Added ability to return file resources for request responses. Added option to specify request response auto-compression limits. 2025-05-11 16:37:57 +02:00
Mark Qvist 2912fb2184 Added option to specify resource auto-compression limits 2025-05-11 16:37:19 +02:00
Mark Qvist 02496f39f7 Removed completed tasks from roadmap 2025-05-11 11:30:06 +02:00
Mark Qvist 4e31f113c6 Optimised hardware MTU autoconfig 2025-05-10 23:15:43 +02:00
Mark Qvist 9aded3e1da Updated readme 2025-05-10 23:09:21 +02:00
Mark Qvist 3337d18e9a Added allow overwrite option to rncp 2025-05-10 21:44:42 +02:00
Mark Qvist 2cb6d019f9 Improved rncp memory utilisation and performance 2025-05-10 21:19:57 +02:00
Mark Qvist 3dc260a300 Updated link tests 2025-05-10 20:59:23 +02:00
Mark Qvist 4d7f5b8ca6 Let shared instance handle packet hashlist 2025-05-10 20:58:54 +02:00
Mark Qvist 48be5f65d8 Faster link cleanup on close 2025-05-10 20:58:01 +02:00
Mark Qvist b5d854a55c Resource performance and memory optimisations 2025-05-10 20:57:32 +02:00
Mark Qvist 552663c625 Fixed offset 2025-05-10 17:00:27 +02:00
Mark Qvist e6f0b92464 Updated example 2025-05-10 17:00:02 +02:00
Mark Qvist 08a6820aa0 Updated descriptions 2025-05-10 15:42:22 +02:00
Mark Qvist cc1faa55be Updated readme 2025-05-10 15:39:51 +02:00
Mark Qvist 840966f3e6 Updated version 2025-05-10 15:38:28 +02:00
Mark Qvist 763078a1ae Added ability to include metadata on resource transfers 2025-05-10 15:38:06 +02:00
Mark Qvist 5fb6abd019 Added resource example 2025-05-10 15:32:06 +02:00
Mark Qvist 7065856229 Added resource with metadata tests 2025-05-10 15:31:35 +02:00
Mark Qvist 668ef9253a Cleanup 2025-05-09 12:07:00 +02:00
Mark Qvist 6f333b8234 Cleanup 2025-05-09 12:04:55 +02:00
57 changed files with 1157 additions and 528 deletions
+54 -3
View File
@@ -1,10 +1,61 @@
### 2025-07-14: RNS 1.0.0
We're out of beta. Thanks to **everyone** who helped make it this far.
This release brings a number of bugfixes, as well as stability and reliability improvements.
**Changes**
- Improved BLE device discovery on Android
- Improved BLE MTU configuration on Android
- Fixed a bug in handling of link requests with invalid link mode bytes
- Fixed potential AutoInterface peer discovery add before final init complete
- Fixed a potential EPOLL backend hang on interface failure
- Fixed various log statements
- Fixed announce cap crash for `RNodeMultiInterface` with transport mode enabled
- Updated documentation
- Removed legacy AES-128 handlers
**Release Hashes**
```
5a9f18840510b69f89c6706d130177e2843c9e19c774707ae2661030d693dfc1 rns-1.0.0-py3-none-any.whl
acfd52af9bf41f78be017579ca06c0abe748d0b98dbdc9baacf140a0606599ec rnspure-1.0.0-py3-none-any.whl
```
### 2025-05-15: RNS β 0.9.6
This release activates AES-256 as the default encryption mode for all communication. It is the last release that will support the old AES-128 based modes, which will be entirely phased out in the next release.
This release also includes a number of API and resource consumption improvements, and fixes a bug.
**Changes**
- Enabled AES-256 as default encryption mode for all traffic
- Added dynamic link keepalive and timeout calculation
- Added ability to efficiently transfer files as responses in the `Request` API
- Added ability to include metadata on `Resource` transfers
- Added option to specify `Resource` auto-compression limits
- Added option to specify `Request` response auto-compression limits
- Added `Resource` transfer example
- Added allow overwrite option to `rncp`
- Improved hardware MTU auto-configuration
- Improved handling of file transfers using the `Resource` API
- Improved `Resource` transfer memory consumption
- Improved memory consumption of applications connected to a shared instance
- Improved `rncp` memory consumption for large files
- Fixed announce handlers not triggering after shared instance disappearance
**Release Hashes**
```
a23c64a04c1e83fd0ab449f564ac904da7fd4f61c0faf68a063f486cc48b44bd rns-0.9.6-py3-none-any.whl
4544882dea902b18b00d8a04c9ab93201974573b7b63c3db06cb310b0acec240 rnspure-0.9.6-py3-none-any.whl
```
### 2025-05-09: RNS β 0.9.5
This release initiates migration of Reticulum from AES-128 to AES-256 as the default link and packet cipher mode. It is a compatibility/migration release, that while supporting AES-256 doesn't use it by default. It will work with both the old AES-128 based modes, and the new AES-256 based modes. There's a very slight penalty in performance to support both the old and new modes at the same time, but only for single packet APIs (not links), and it really shouldn't be noticeable in any everyday use.
In the next release, version `0.9.6`, Reticulum will transition fully to AES-256 and use it by default for all communications. That means that both single packets and links will use AES-256 by default. The old AES-128 link mode may or may not be available for a few releases, but will ultimately be phased out entirely.
The update requires no intervention, configuration changes or anything similar from a users or developers perspective. Everything should simply work. This goes both for the 0.9.5 update, and the next 0.9.6 update that transitions fully to AES-256.
The update requires no intervention, configuration changes or anything similar from a users or developers perspective. Everything should simply work. This goes both for the `0.9.5` update, and the next `0.9.6` update that transitions fully to AES-256.
**Changes**
- Added support for AES-256 mode to links and packets
@@ -20,8 +71,8 @@ The update requires no intervention, configuration changes or anything similar f
**Release Hashes**
```
0b880827369dd4bda7e4691e0e157e75fab4c22554743c1c7f07a350106114e8 rns-0.9.5-py3-none-any.whl
6c00d12176de40d7dc45d9edd7952a161413f4cc4cc276b82bf3811f99440556 rnspure-0.9.5-py3-none-any.whl
ae6587c86c98cae0df73567af093cc92fe204e71bb01f2506da9aec626a27e97 rns-0.9.5-py3-none-any.whl
96208c1d1234e3e4b1c18ca986bad5d4693aeb431453efd7ade33b87f35600e1 rnspure-0.9.5-py3-none-any.whl
```
### 2025-04-15: RNS β 0.9.4
+2 -2
View File
@@ -1,6 +1,6 @@
##########################################################
# This RNS example demonstrates how to set perform #
# requests and receive responses over a link. #
# This RNS example demonstrates how to perform requests #
# and receive responses over a link. #
##########################################################
import os
+294
View File
@@ -0,0 +1,294 @@
##########################################################
# This RNS example demonstrates how to transfer a #
# resource over an established link #
##########################################################
import os
import sys
import time
import random
import argparse
import RNS
# Let's define an app name. We'll use this for all
# destinations we create. Since this echo example
# is part of a range of example utilities, we'll put
# them all within the app namespace "example_utilities"
APP_NAME = "example_utilities"
##########################################################
#### Server Part #########################################
##########################################################
# A reference to the latest client link that connected
latest_client_link = None
# This initialisation is executed when the users chooses
# to run as a server
def server(configpath):
# We must first initialise Reticulum
reticulum = RNS.Reticulum(configpath)
# Randomly create a new identity for our link example
server_identity = RNS.Identity()
# We create a destination that clients can connect to. We
# want clients to create links to this destination, so we
# need to create a "single" destination type.
server_destination = RNS.Destination(
server_identity,
RNS.Destination.IN,
RNS.Destination.SINGLE,
APP_NAME,
"resourceexample"
)
# We configure a function that will get called every time
# a new client creates a link to this destination.
server_destination.set_link_established_callback(client_connected)
# Everything's ready!
# Let's Wait for client resources or user input
server_loop(server_destination)
def server_loop(destination):
# Let the user know that everything is ready
RNS.log(
"Resource example "+
RNS.prettyhexrep(destination.hash)+
" running, waiting for a connection."
)
RNS.log("Hit enter to manually send an announce (Ctrl-C to quit)")
# We enter a loop that runs until the users exits.
# If the user hits enter, we will announce our server
# destination on the network, which will let clients
# know how to create messages directed towards it.
while True:
entered = input()
destination.announce()
RNS.log("Sent announce from "+RNS.prettyhexrep(destination.hash))
# When a client establishes a link to our server
# destination, this function will be called with
# a reference to the link.
def client_connected(link):
global latest_client_link
RNS.log("Client connected")
# We configure the link to accept all resources
# and set a callback for completed resources
link.set_resource_strategy(RNS.Link.ACCEPT_ALL)
link.set_resource_concluded_callback(resource_concluded)
link.set_link_closed_callback(client_disconnected)
latest_client_link = link
def client_disconnected(link):
RNS.log("Client disconnected")
def resource_concluded(resource):
if resource.status == RNS.Resource.COMPLETE:
RNS.log(f"Resource {resource} received")
RNS.log(f"Metadata: {resource.metadata}")
RNS.log(f"Data length: {os.stat(resource.data.name).st_size}")
RNS.log(f"Data can be read directly from: {resource.data}")
RNS.log(f"Data can be moved or copied from: {resource.data.name}")
RNS.log(f"First 32 bytes of data: {RNS.hexrep(resource.data.read(32))}")
else:
RNS.log(f"Receiving resource {resource} failed")
##########################################################
#### Client Part #########################################
##########################################################
# A reference to the server link
server_link = None
def random_text_generator():
texts = ["They looked up", "On each full moon", "Becky was upset", "Ill stay away from it", "The pet shop stocks everything"]
return texts[random.randint(0, len(texts)-1)]
# This initialisation is executed when the users chooses
# to run as a client
def client(destination_hexhash, configpath):
# We need a binary representation of the destination
# hash that was entered on the command line
try:
dest_len = (RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2
if len(destination_hexhash) != dest_len:
raise ValueError(
"Destination length is invalid, must be {hex} hexadecimal characters ({byte} bytes).".format(hex=dest_len, byte=dest_len//2)
)
destination_hash = bytes.fromhex(destination_hexhash)
except:
RNS.log("Invalid destination entered. Check your input!\n")
sys.exit(0)
# We must first initialise Reticulum
reticulum = RNS.Reticulum(configpath)
# Check if we know a path to the destination
if not RNS.Transport.has_path(destination_hash):
RNS.log("Destination is not yet known. Requesting path and waiting for announce to arrive...")
RNS.Transport.request_path(destination_hash)
while not RNS.Transport.has_path(destination_hash):
time.sleep(0.1)
# 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...")
# When the server identity is known, we set
# up a destination
server_destination = RNS.Destination(
server_identity,
RNS.Destination.OUT,
RNS.Destination.SINGLE,
APP_NAME,
"resourceexample"
)
# And create a link
link = RNS.Link(server_destination)
# We'll set up functions to inform the
# user when the link is established or closed
link.set_link_established_callback(link_established)
link.set_link_closed_callback(link_closed)
# Everything is set up, so let's enter a loop
# for the user to interact with the example
client_loop()
def client_loop():
global server_link
# Wait for the link to become active
while not server_link:
time.sleep(0.1)
should_quit = False
while not should_quit:
try:
print("> ", end=" ")
text = input()
# Check if we should quit the example
if text == "quit" or text == "q" or text == "exit":
should_quit = True
server_link.teardown()
else:
# Generate 32 megabytes of random data
data = os.urandom(32*1024*1024)
RNS.log(f"Data length: {len(data)}")
RNS.log(f"First 32 bytes of data: {RNS.hexrep(data[:32])}")
# Generate some metadata
metadata = {"text": random_text_generator(), "numbers": [1,2,3,4], "blob": os.urandom(16)}
# Send the resource
resource = RNS.Resource(data, server_link, metadata=metadata, callback=resource_concluded_sending, auto_compress=False)
# Alternatively, you can stream data
# directly from an open file descriptor
# with open("/path/to/file", "rb") as data_file:
# resource = RNS.Resource(data_file, server_link, metadata=metadata, callback=resource_concluded_sending, auto_compress=False)
except Exception as e:
RNS.log("Error while sending resource over the link: "+str(e))
should_quit = True
server_link.teardown()
def resource_concluded_sending(resource):
if resource.status == RNS.Resource.COMPLETE: RNS.log(f"The resource {resource} was sent successfully")
else: RNS.log(f"Sending the resource {resource} failed")
# This function is called when a link
# has been established with the server
def link_established(link):
# We store a reference to the link
# instance for later use
global server_link
server_link = link
# Inform the user that the server is
# connected
RNS.log("Link established with server, hit enter to sand a resource, or type in \"quit\" to quit")
# When a link is closed, we'll inform the
# user, and exit the program
def link_closed(link):
if link.teardown_reason == RNS.Link.TIMEOUT:
RNS.log("The link timed out, exiting now")
elif link.teardown_reason == RNS.Link.DESTINATION_CLOSED:
RNS.log("The link was closed by the server, exiting now")
else:
RNS.log("Link closed, exiting now")
time.sleep(1.5)
sys.exit(0)
##########################################################
#### Program Startup #####################################
##########################################################
# This part of the program runs at startup,
# and parses input of from the user, and then
# starts up the desired program mode.
if __name__ == "__main__":
try:
parser = argparse.ArgumentParser(description="Simple resource example")
parser.add_argument(
"-s",
"--server",
action="store_true",
help="wait for incoming resources from clients"
)
parser.add_argument(
"--config",
action="store",
default=None,
help="path to alternative Reticulum config directory",
type=str
)
parser.add_argument(
"destination",
nargs="?",
default=None,
help="hexadecimal hash of the server destination",
type=str
)
args = parser.parse_args()
if args.config:
configarg = args.config
else:
configarg = None
if args.server:
server(configarg)
else:
if (args.destination == None):
print("")
parser.print_help()
print("")
else:
client(args.destination, configarg)
except KeyboardInterrupt:
print("")
sys.exit(0)
+2 -1
View File
@@ -1,2 +1,3 @@
liberapay: Reticulum
ko_fi: markqvist
custom: "https://unsigned.io/donate"
custom: "https://unsigned.io/donate"
+10 -9
View File
@@ -1,4 +1,4 @@
Reticulum Network Stack β <img align="right" src="https://static.pepy.tech/personalized-badge/rns?period=month&units=international_system&left_color=grey&right_color=blue&left_text=Installs/month" style="padding-left:10px"/><a href="https://github.com/markqvist/Reticulum/actions/workflows/build.yml"><img align="right" src="https://github.com/markqvist/Reticulum/actions/workflows/build.yml/badge.svg"/></a>
Reticulum Network Stack <img align="right" src="https://static.pepy.tech/personalized-badge/rns?period=month&units=international_system&left_color=grey&right_color=blue&left_text=Installs/month" style="padding-left:10px"/><a href="https://github.com/markqvist/Reticulum/actions/workflows/build.yml"><img align="right" src="https://github.com/markqvist/Reticulum/actions/workflows/build.yml/badge.svg"/></a>
==========
<p align="center"><img width="200" src="https://raw.githubusercontent.com/markqvist/Reticulum/master/docs/source/graphics/rns_logo_512.png"></p>
@@ -52,7 +52,7 @@ For more info, see [reticulum.network](https://reticulum.network/) and [the FAQ
- Forward Secrecy is available for all communication types, both for single packets and over links
- Reticulum uses the following format for encrypted tokens:
- Ephemeral per-packet and link keys and derived from an ECDH key exchange on Curve25519
- AES-128 or AES-256 in CBC mode with PKCS7 padding
- AES-256 in CBC mode with PKCS7 padding
- HMAC using SHA256 for authentication
- IVs are generated through os.urandom()
- Unforgeable packet delivery confirmations
@@ -62,7 +62,7 @@ For more info, see [reticulum.network](https://reticulum.network/) and [the FAQ
- Easily create your own custom interfaces for communicating over anything
- Authentication and virtual network segmentation on all supported interface types
- An intuitive and easy-to-use API
- Simpler and easier to use than sockets APIs and simpler, but more powerful
- Simpler and easier to use than sockets APIs, but more powerful
- Makes building distributed and decentralised applications much simpler
- Reliable and efficient transfer of arbitrary amounts of data
- Reticulum can handle a few bytes of data or files of many gigabytes
@@ -211,10 +211,9 @@ saturated. Performance beyond the current level is intended for future
upgrades, but not highly prioritised at this point in time.
## Current Status
Reticulum should currently be considered beta software. All core protocol
features are implemented and functioning, but additions will probably occur as
real-world use is explored. There will be bugs. The API and wire-format can be
considered relatively stable at the moment, but could change if warranted.
All core protocol features are implemented and functioning, but additions will
probably occur as real-world use is explored and understood. The API and wire-format
can be considered stable.
## Dependencies
The installation of the default `rns` package requires the dependencies listed
@@ -305,6 +304,8 @@ You can help support the continued development of open, free and private communi
```
0xae89F3B94fC4AD6563F0864a55F9a697a90261ff
```
- Liberapay: https://liberapay.com/Reticulum/
- Ko-Fi: https://ko-fi.com/markqvist
## Cryptographic Primitives
@@ -328,12 +329,12 @@ intentionally compromised or weakened clone. The utilised primitives are:
- Ephemeral keys derived from an ECDH key exchange on Curve25519
- HMAC using SHA256 for message authentication
- IVs must be generated through `os.urandom()` or better
- AES-128 or AES-256 in CBC mode with PKCS7 padding
- AES-256 in CBC mode with PKCS7 padding
- No Fernet version and timestamp metadata fields
- SHA-256
- SHA-512
In the default installation configuration, the `X25519`, `Ed25519`, `AES-128-CBC`
In the default installation configuration, the `X25519`, `Ed25519`,
and `AES-256-CBC` primitives are provided by [OpenSSL](https://www.openssl.org/)
(via the [PyCA/cryptography](https://github.com/pyca/cryptography) package).
The hashing functions `SHA-256` and `SHA-512` are provided by the standard
+3 -14
View File
@@ -37,9 +37,6 @@ from RNS.Cryptography import AES
from RNS.Cryptography.AES import AES_128_CBC
from RNS.Cryptography.AES import AES_256_CBC
# TODO: Remove after migration
import RNS
class Token():
"""
This class provides a slightly modified implementation of the Fernet spec
@@ -53,7 +50,7 @@ class Token():
TOKEN_OVERHEAD = 48 # Bytes
@staticmethod
def generate_key(mode=AES_128_CBC):
def generate_key(mode=AES_256_CBC):
if mode == AES_128_CBC: return os.urandom(32)
elif mode == AES_256_CBC: return os.urandom(64)
else: raise TypeError(f"Invalid token mode: {mode}")
@@ -91,19 +88,16 @@ class Token():
if not isinstance(data, bytes): raise TypeError("Token plaintext input must be bytes")
iv = os.urandom(16)
# RNS.log(f"Encrypting with {self.mode}") # TODO: Remove
ciphertext = self.mode.encrypt(
plaintext = PKCS7.pad(data),
key = self._encryption_key,
iv = iv)
signed_parts = iv+ciphertext
return signed_parts + HMAC.new(self._signing_key, signed_parts).digest()
def decrypt(self, token = None):
# RNS.log(f"Trying decryption with {self.mode}") # TODO: Remove
if not isinstance(token, bytes): raise TypeError("Token must be bytes")
if not self.verify_hmac(token): raise ValueError("Token HMAC was invalid")
@@ -111,15 +105,10 @@ class Token():
ciphertext = token[16:-32]
try:
plaintext = PKCS7.unpad(
return PKCS7.unpad(
self.mode.decrypt(
ciphertext = ciphertext,
key = self._encryption_key,
iv = iv))
# RNS.log(f"Decrypted packet with {self.mode}") # TODO: Remove
return plaintext
except Exception as e:
RNS.trace_exception(e) # TODO: Remove after migration
raise ValueError("Could not decrypt token")
except Exception as e: raise ValueError(f"Could not decrypt token: {e}")
+7 -10
View File
@@ -377,7 +377,7 @@ class Destination:
else:
self.proof_strategy = proof_strategy
def register_request_handler(self, path, response_generator = None, allow = ALLOW_NONE, allowed_list = None):
def register_request_handler(self, path, response_generator = None, allow = ALLOW_NONE, allowed_list = None, auto_compress = True):
"""
Registers a request handler.
@@ -385,17 +385,15 @@ class Destination:
:param response_generator: A function or method with the signature *response_generator(path, data, request_id, link_id, remote_identity, requested_at)* to be called. Whatever this funcion returns will be sent as a response to the requester. If the function returns ``None``, no response will be sent.
:param allow: One of ``RNS.Destination.ALLOW_NONE``, ``RNS.Destination.ALLOW_ALL`` or ``RNS.Destination.ALLOW_LIST``. If ``RNS.Destination.ALLOW_LIST`` is set, the request handler will only respond to requests for identified peers in the supplied list.
:param allowed_list: A list of *bytes-like* :ref:`RNS.Identity<api-identity>` hashes.
:param auto_compress: If ``True`` or ``False``, determines whether automatic compression of responses should be carried out. If set to an integer value, responses will only be auto-compressed if under this size in bytes. If omitted, the default compression settings will be followed.
:raises: ``ValueError`` if any of the supplied arguments are invalid.
"""
if path == None or path == "":
raise ValueError("Invalid path specified")
elif not callable(response_generator):
raise ValueError("Invalid response generator specified")
elif not allow in Destination.request_policies:
raise ValueError("Invalid request policy")
if path == None or path == "": raise ValueError("Invalid path specified")
elif not callable(response_generator): raise ValueError("Invalid response generator specified")
elif not allow in Destination.request_policies: raise ValueError("Invalid request policy")
else:
path_hash = RNS.Identity.truncated_hash(path.encode("utf-8"))
request_handler = [path, response_generator, allow, allowed_list]
request_handler = [path, response_generator, allow, allowed_list, auto_compress]
self.request_handlers[path_hash] = request_handler
def deregister_request_handler(self, path):
@@ -491,7 +489,6 @@ class Destination:
self.latest_ratchet_time = 0
self._reload_ratchets(ratchets_path)
# TODO: Remove at some point
RNS.log("Ratchets enabled on "+str(self), RNS.LOG_DEBUG)
return True
@@ -645,7 +642,7 @@ class Destination:
RNS.log(f"Decryption still failing after ratchet reload. The contained exception was: {e}", RNS.LOG_ERROR)
raise e
RNS.log("Decryption succeeded after ratchet reload", RNS.LOG_NOTICE)
if decrypted: RNS.log("Decryption succeeded after ratchet reload", RNS.LOG_NOTICE)
return decrypted
+15 -47
View File
@@ -679,20 +679,8 @@ class Identity:
shared_key = ephemeral_key.exchange(target_public_key)
# TODO: Reset after migration
# derived_key = RNS.Cryptography.hkdf(
# length=Identity.DERIVED_KEY_LENGTH,
# derive_from=shared_key,
# salt=self.get_salt(),
# context=self.get_context(),
# )
# Use legacy derived key length (AES-128) during migration by
# default. This allows AES-256 capable instances on RNS 0.9.5
# to still communicate with older versions. This migration
# handling will be removed in RNS 0.9.6.
derived_key = RNS.Cryptography.hkdf(
length=Identity.DERIVED_KEY_LENGTH_LEGACY,
length=Identity.DERIVED_KEY_LENGTH,
derive_from=shared_key,
salt=self.get_salt(),
context=self.get_context(),
@@ -706,6 +694,16 @@ class Identity:
else:
raise KeyError("Encryption failed because identity does not hold a public key")
def __decrypt(self, shared_key, ciphertext):
derived_key = RNS.Cryptography.hkdf(
length=Identity.DERIVED_KEY_LENGTH,
derive_from=shared_key,
salt=self.get_salt(),
context=self.get_context())
token = Token(derived_key)
plaintext = token.decrypt(ciphertext)
return plaintext
def decrypt(self, ciphertext_token, ratchets=None, enforce_ratchets=False, ratchet_id_receiver=None):
"""
@@ -716,36 +714,6 @@ class Identity:
:raises: *KeyError* if the instance does not hold a private key.
"""
# This handles decryption during migration to AES-256 where
# older instances may still use AES-128. If decryption fails
# initially, AES-128 will be attempted as a fallback mode.
# This handler will be removed in RNS 0.9.6.
def migration_decrypt(shared_key, ciphertext):
try:
derived_key = RNS.Cryptography.hkdf(
length=Identity.DERIVED_KEY_LENGTH,
derive_from=shared_key,
salt=self.get_salt(),
context=self.get_context())
token = Token(derived_key)
plaintext = token.decrypt(ciphertext)
# TODO: Remove after migration
# If decryption fails, try legacy decryption mode
except Exception as e:
RNS.log("Decryption failed, attempting legacy mode fallback", RNS.LOG_DEBUG)
derived_key = RNS.Cryptography.hkdf(
length=Identity.DERIVED_KEY_LENGTH_LEGACY,
derive_from=shared_key,
salt=self.get_salt(),
context=self.get_context())
token = Token(derived_key)
plaintext = token.decrypt(ciphertext)
return plaintext
if self.prv != None:
if len(ciphertext_token) > Identity.KEYSIZE//8//2:
plaintext = None
@@ -760,8 +728,7 @@ class Identity:
ratchet_prv = X25519PrivateKey.from_private_bytes(ratchet)
ratchet_id = Identity._get_ratchet_id(ratchet_prv.public_key().public_bytes())
shared_key = ratchet_prv.exchange(peer_pub)
plaintext = migration_decrypt(shared_key, ciphertext)
plaintext = self.__decrypt(shared_key, ciphertext)
if ratchet_id_receiver:
ratchet_id_receiver.latest_ratchet_id = ratchet_id
@@ -778,7 +745,7 @@ class Identity:
if plaintext == None:
shared_key = self.prv.exchange(peer_pub)
plaintext = migration_decrypt(shared_key, ciphertext)
plaintext = self.__decrypt(shared_key, ciphertext)
if ratchet_id_receiver:
ratchet_id_receiver.latest_ratchet_id = None
@@ -788,7 +755,8 @@ class Identity:
if ratchet_id_receiver:
ratchet_id_receiver.latest_ratchet_id = None
return plaintext;
return plaintext
else:
RNS.log("Decryption failed because the token size was invalid.", RNS.LOG_DEBUG)
return None
+17 -10
View File
@@ -1554,11 +1554,11 @@ class BLEConnection(BluetoothDispatcher):
UART_TX_CHAR_UUID = "6e400003-b5a3-f393-e0a9-e50e24dcca9e"
MAX_GATT_ATTR_LEN = 512
BASE_MTU = 20
TARGET_MTU = 512
TARGET_MTU = 242
MTU_TIMEOUT = 4.0
CONNECT_TIMEOUT = 7.0
RECONNECT_WAIT = 1.0
RECONNECT_WAIT = 2.5
@property
def is_open(self):
@@ -1659,13 +1659,17 @@ class BLEConnection(BluetoothDispatcher):
self.write_thread = None
def connection_job(self):
ble_devices = []
while self.should_run:
if self.bt_manager.bt_enabled():
if self.ble_device == None:
self.ble_device = self.find_target_device()
if not self.connected:
if len(ble_devices) == 0:
ble_devices = self.find_target_devices()
if len(ble_devices) > 0: self.ble_device = ble_devices.pop()
else: self.ble_device == None
if self.ble_device != None:
if not self.connected:
if self.ble_device != None:
if self.was_connected:
RNS.log(f"Throttling BLE reconnect for {BLEConnection.RECONNECT_WAIT} seconds", RNS.LOG_DEBUG)
time.sleep(BLEConnection.RECONNECT_WAIT)
@@ -1677,7 +1681,7 @@ class BLEConnection(BluetoothDispatcher):
RNS.log("Bluetooth was disabled, closing active BLE device connection", RNS.LOG_ERROR)
self.close()
time.sleep(2)
time.sleep(1)
def connect_device(self):
if self.ble_device != None and self.bt_manager.bt_enabled():
@@ -1710,15 +1714,17 @@ class BLEConnection(BluetoothDispatcher):
self.ble_device = None
self.close_gatt()
def find_target_device(self):
def find_target_devices(self):
found_device = None
potential_devices = self.bt_manager.get_paired_devices()
suitable_devices = []
if self.target_bt_addr != None:
for device in potential_devices:
if (device.getType() == AndroidBluetoothManager.DEVICE_TYPE_LE) or (device.getType() == AndroidBluetoothManager.DEVICE_TYPE_DUAL):
if str(device.getAddress()).replace(":", "").lower() == str(self.target_bt_addr).replace(":", "").lower():
found_device = device
suitable_devices.append(device)
break
if not found_device and self.target_name != None:
@@ -1726,6 +1732,7 @@ class BLEConnection(BluetoothDispatcher):
if (device.getType() == AndroidBluetoothManager.DEVICE_TYPE_LE) or (device.getType() == AndroidBluetoothManager.DEVICE_TYPE_DUAL):
if device.getName().lower() == self.target_name.lower():
found_device = device
suitable_devices.append(device)
break
if not found_device:
@@ -1733,9 +1740,9 @@ class BLEConnection(BluetoothDispatcher):
if (device.getType() == AndroidBluetoothManager.DEVICE_TYPE_LE) or (device.getType() == AndroidBluetoothManager.DEVICE_TYPE_DUAL):
if device.getName().startswith("RNode "):
found_device = device
break
suitable_devices.append(device)
return found_device
return suitable_devices
def on_connection_state_change(self, status, state):
if status == GATT_SUCCESS and state:
+10 -8
View File
@@ -119,6 +119,7 @@ class AutoInterface(Interface):
self.name = name
self.owner = owner
self.online = False
self.final_init_done = False
self.peers = {}
self.link_local_addresses = []
self.adopted_interfaces = {}
@@ -274,8 +275,7 @@ class AutoInterface(Interface):
discovery_socket.bind(addr_info[0][4])
# Set up thread for discovery packets
def discovery_loop():
self.discovery_handler(discovery_socket, ifname)
def discovery_loop(): self.discovery_handler(discovery_socket, ifname)
thread = threading.Thread(target=discovery_loop)
thread.daemon = True
@@ -325,6 +325,7 @@ class AutoInterface(Interface):
time.sleep(peering_wait)
self.online = True
self.final_init_done = True
def discovery_handler(self, socket, ifname):
def announce_loop():
@@ -336,12 +337,13 @@ class AutoInterface(Interface):
while True:
data, ipv6_src = socket.recvfrom(1024)
peering_hash = data[:RNS.Identity.HASHLENGTH//8]
expected_hash = RNS.Identity.full_hash(self.group_id+ipv6_src[0].encode("utf-8"))
if peering_hash == expected_hash:
self.add_peer(ipv6_src[0], ifname)
else:
RNS.log(str(self)+" received peering packet on "+str(ifname)+" from "+str(ipv6_src[0])+", but authentication hash was incorrect.", RNS.LOG_DEBUG)
if self.final_init_done:
peering_hash = data[:RNS.Identity.HASHLENGTH//8]
expected_hash = RNS.Identity.full_hash(self.group_id+ipv6_src[0].encode("utf-8"))
if peering_hash == expected_hash:
self.add_peer(ipv6_src[0], ifname)
else:
RNS.log(str(self)+" received peering packet on "+str(ifname)+" from "+str(ipv6_src[0])+", but authentication hash was incorrect.", RNS.LOG_DEBUG)
def peer_jobs(self):
while True:
+5 -5
View File
@@ -588,9 +588,7 @@ class BackboneClientInterface(Interface):
self.teardown()
break
try:
self.connect()
try: self.connect()
except Exception as e:
RNS.log("Connection attempt for "+str(self)+" failed: "+str(e), RNS.LOG_DEBUG)
@@ -648,7 +646,8 @@ class BackboneClientInterface(Interface):
self.online = False
if self.initiator and not self.detached:
RNS.log("The socket for "+str(self)+" was closed, attempting to reconnect...", RNS.LOG_WARNING)
self.reconnect()
def job(): self.reconnect()
threading.Thread(target=job, daemon=True).start()
else:
RNS.log("The socket for remote client "+str(self)+" was closed.", RNS.LOG_VERBOSE)
self.teardown()
@@ -659,7 +658,8 @@ class BackboneClientInterface(Interface):
if self.initiator:
RNS.log("Attempting to reconnect...", RNS.LOG_WARNING)
self.reconnect()
def job(): self.reconnect()
threading.Thread(target=job, daemon=True).start()
else:
self.teardown()
+9 -9
View File
@@ -135,23 +135,23 @@ class Interface:
def optimise_mtu(self):
if self.AUTOCONFIGURE_MTU:
if self.bitrate > 500_000_000:
if self.bitrate >= 1_000_000_000:
self.HW_MTU = 524288
elif self.bitrate > 16_000_000:
elif self.bitrate > 750_000_000:
self.HW_MTU = 262144
elif self.bitrate > 8_000_000:
elif self.bitrate > 400_000_000:
self.HW_MTU = 131072
elif self.bitrate > 4_000_000:
elif self.bitrate > 200_000_000:
self.HW_MTU = 65536
elif self.bitrate > 2_000_000:
elif self.bitrate > 100_000_000:
self.HW_MTU = 32768
elif self.bitrate > 1_000_000:
elif self.bitrate > 10_000_000:
self.HW_MTU = 16384
elif self.bitrate > 500_000:
elif self.bitrate > 5_000_000:
self.HW_MTU = 8192
elif self.bitrate > 250_000:
elif self.bitrate > 2_000_000:
self.HW_MTU = 4096
elif self.bitrate > 125_000:
elif self.bitrate > 1_000_000:
self.HW_MTU = 2048
elif self.bitrate > 62_500:
self.HW_MTU = 1024
+8 -2
View File
@@ -70,7 +70,7 @@ class LocalClientInterface(Interface):
self.HW_MTU = 262144
self.online = False
if socket_path != None and RNS.vendor.platformutils.use_af_unix(): self.socket_path = f"\0rns/{socket_path}"
if socket_path != None and RNS.Reticulum.get_instance().use_af_unix: self.socket_path = f"\0rns/{socket_path}"
else: self.socket_path = None
self.IN = True
@@ -254,6 +254,9 @@ class LocalClientInterface(Interface):
if self.is_connected_to_shared_instance and not self.detached:
RNS.log("Socket for "+str(self)+" was closed, attempting to reconnect...", RNS.LOG_WARNING)
RNS.Transport.shared_connection_disappeared()
# TODO: Potentially run this in a thread, but since if we get here,
# there's no other connectivity left to block anyway, it might be
# unnecessary.
self.reconnect()
else:
self.teardown(nowarning=True)
@@ -276,6 +279,9 @@ class LocalClientInterface(Interface):
if self.is_connected_to_shared_instance and not self.detached:
RNS.log("Socket for "+str(self)+" was closed, attempting to reconnect...", RNS.LOG_WARNING)
RNS.Transport.shared_connection_disappeared()
# TODO: Potentially run this in a thread, but since if we get here,
# there's no other connectivity left to block anyway, it might be
# unnecessary.
self.reconnect()
else:
self.teardown(nowarning=True)
@@ -350,7 +356,7 @@ class LocalServerInterface(Interface):
self.online = False
self.clients = 0
if socket_path != None and RNS.vendor.platformutils.use_af_unix(): self.socket_path = f"\0rns/{socket_path}"
if socket_path != None and RNS.Reticulum.get_instance().use_af_unix: self.socket_path = f"\0rns/{socket_path}"
else: self.socket_path = None
self.IN = True
-1
View File
@@ -993,7 +993,6 @@ class RNodeSubInterface(Interface):
self.announce_rate_target = None
self.mode = None
self.announce_cap = None
self.bitrate = None
self.ifac_size = None
+75 -34
View File
@@ -40,6 +40,7 @@ import struct
import math
import time
import RNS
import io
class LinkCallbacks:
def __init__(self):
@@ -79,19 +80,23 @@ class Link:
LINK_MTU_SIZE = 3
TRAFFIC_TIMEOUT_MIN_MS = 5
TRAFFIC_TIMEOUT_FACTOR = 6
KEEPALIVE_MAX_RTT = 1.75
KEEPALIVE_TIMEOUT_FACTOR = 4
"""
RTT timeout factor used in link timeout calculation.
"""
STALE_GRACE = 2
STALE_GRACE = 5
"""
Grace period in seconds used in link timeout calculation.
"""
KEEPALIVE = 360
KEEPALIVE_MAX = 360
KEEPALIVE_MIN = 5
KEEPALIVE = KEEPALIVE_MAX
"""
Interval for sending keep-alive packets on established links in seconds.
Default interval for sending keep-alive packets on established links in seconds.
"""
STALE_TIME = 2*KEEPALIVE
STALE_FACTOR = 2
STALE_TIME = STALE_FACTOR*KEEPALIVE
"""
If no traffic or keep-alive packets are received within this period, the
link will be marked as stale, and a final keep-alive packet will be sent.
@@ -100,6 +105,8 @@ class Link:
and will be torn down.
"""
WATCHDOG_MAX_SLEEP = 5
PENDING = 0x00
HANDSHAKE = 0x01
ACTIVE = 0x02
@@ -123,8 +130,8 @@ class Link:
MODE_PQ_RESERVED_2 = 0x05
MODE_PQ_RESERVED_3 = 0x06
MODE_PQ_RESERVED_4 = 0x07
ENABLED_MODES = [MODE_AES128_CBC, MODE_AES256_CBC]
MODE_DEFAULT = MODE_AES128_CBC
ENABLED_MODES = [MODE_AES256_CBC]
MODE_DEFAULT = MODE_AES256_CBC
MODE_DESCRIPTIONS = {MODE_AES128_CBC: "AES_128_CBC",
MODE_AES256_CBC: "AES_256_CBC",
MODE_AES256_GCM: "MODE_AES256_GCM",
@@ -192,7 +199,7 @@ class Link:
link.mode = Link.mode_from_lr_packet(packet)
# TODO: Remove
# TODO: Remove debug
RNS.log(f"Incoming link request with mode {Link.MODE_DESCRIPTIONS[link.mode]}", RNS.LOG_DEBUG)
link.update_mdu()
@@ -223,7 +230,6 @@ class Link:
return None
# TODO: Set default link mode to AES_256_CBC after migration
def __init__(self, destination=None, established_callback=None, closed_callback=None, owner=None, peer_pub_bytes=None, peer_sig_pub_bytes=None, mode=MODE_DEFAULT):
if destination != None and destination.type != RNS.Destination.SINGLE: raise TypeError("Links can only be established to the \"single\" destination type")
self.mode = mode
@@ -241,6 +247,7 @@ class Link:
self.pending_requests = []
self.last_inbound = 0
self.last_outbound = 0
self.last_keepalive = 0
self.last_proof = 0
self.last_data = 0
self.tx = 0
@@ -423,11 +430,13 @@ class Link:
self.activated_at = time.time()
self.last_proof = self.activated_at
RNS.Transport.activate_link(self)
RNS.log("Link "+str(self)+" established with "+str(self.destination)+", RTT is "+str(round(self.rtt, 3))+"s", RNS.LOG_DEBUG)
RNS.log("Link "+str(self)+" established with "+str(self.destination)+", RTT is "+RNS.prettyshorttime(self.rtt), RNS.LOG_DEBUG)
if self.rtt != None and self.establishment_cost != None and self.rtt > 0 and self.establishment_cost > 0:
self.establishment_rate = self.establishment_cost/self.rtt
self.__update_keepalive()
rtt_data = umsgpack.packb(self.rtt)
rtt_packet = RNS.Packet(self, rtt_data, context=RNS.Packet.LRRTT)
rtt_packet.send()
@@ -535,6 +544,8 @@ class Link:
if self.rtt != None and self.establishment_cost != None and self.rtt > 0 and self.establishment_cost > 0:
self.establishment_rate = self.establishment_cost/self.rtt
self.__update_keepalive()
try:
if self.owner.callbacks.link_established != None:
self.owner.callbacks.link_established(self)
@@ -677,23 +688,23 @@ class Link:
def had_outbound(self, is_keepalive=False):
self.last_outbound = time.time()
if not is_keepalive:
self.last_data = self.last_outbound
if not is_keepalive: self.last_data = self.last_outbound
else: self.last_keepalive = self.last_outbound
def __teardown_packet(self):
teardown_packet = RNS.Packet(self, self.link_id, context=RNS.Packet.LINKCLOSE)
teardown_packet.send()
self.had_outbound()
def teardown(self):
"""
Closes the link and purges encryption keys. New keys will
be used if a new link to the same destination is established.
"""
if self.status != Link.PENDING and self.status != Link.CLOSED:
teardown_packet = RNS.Packet(self, self.link_id, context=RNS.Packet.LINKCLOSE)
teardown_packet.send()
self.had_outbound()
if self.status != Link.PENDING and self.status != Link.CLOSED: self.__teardown_packet()
self.status = Link.CLOSED
if self.initiator:
self.teardown_reason = Link.INITIATOR_CLOSED
else:
self.teardown_reason = Link.DESTINATION_CLOSED
if self.initiator: self.teardown_reason = Link.INITIATOR_CLOSED
else: self.teardown_reason = Link.DESTINATION_CLOSED
self.link_closed()
def teardown_packet(self, packet):
@@ -780,9 +791,10 @@ class Link:
elif self.status == Link.ACTIVE:
activated_at = self.activated_at if self.activated_at != None else 0
last_inbound = max(max(self.last_inbound, self.last_proof), activated_at)
now = time.time()
if time.time() >= last_inbound + self.keepalive:
if self.initiator:
if now >= last_inbound + self.keepalive:
if self.initiator and now >= self.last_keepalive + self.keepalive:
self.send_keepalive()
if time.time() >= last_inbound + self.stale_time:
@@ -796,6 +808,7 @@ class Link:
elif self.status == Link.STALE:
sleep_time = 0.001
self.__teardown_packet()
self.status = Link.CLOSED
self.teardown_reason = Link.TIMEOUT
self.link_closed()
@@ -808,6 +821,7 @@ class Link:
self.teardown()
sleep_time = 0.1
sleep_time = min(sleep_time, Link.WATCHDOG_MAX_SLEEP)
sleep(sleep_time)
if not self.__track_phy_stats:
@@ -830,6 +844,10 @@ class Link:
self.snr = packet.snr
if packet.q != None:
self.q = packet.q
def __update_keepalive(self):
self.keepalive = max(min(self.rtt*(Link.KEEPALIVE_MAX/Link.KEEPALIVE_MAX_RTT), Link.KEEPALIVE_MAX), Link.KEEPALIVE_MIN)
self.stale_time = self.keepalive * Link.STALE_FACTOR
def send_keepalive(self):
keepalive_packet = RNS.Packet(self, bytes([0xFF]), context=RNS.Packet.KEEPALIVE)
@@ -848,6 +866,7 @@ class Link:
response_generator = request_handler[1]
allow = request_handler[2]
allowed_list = request_handler[3]
auto_compress = request_handler[4]
allowed = False
if not allow == RNS.Destination.ALLOW_NONE:
@@ -866,18 +885,29 @@ class Link:
else:
raise TypeError("Invalid signature for response generator callback")
if response != None:
packed_response = umsgpack.packb([request_id, response])
file_response = False
file_handle = None
if type(response) == list or type(response) == tuple:
metadata = None
if len(response) > 0 and type(response[0]) == io.BufferedReader:
if len(response) > 1: metadata = response[1]
file_handle = response[0]
file_response = True
if len(packed_response) <= self.mdu:
RNS.Packet(self, packed_response, RNS.Packet.DATA, context = RNS.Packet.RESPONSE).send()
if response != None:
if file_response:
response_resource = RNS.Resource(file_handle, self, metadata=metadata, request_id = request_id, is_response = True, auto_compress=auto_compress)
else:
response_resource = RNS.Resource(packed_response, self, request_id = request_id, is_response = True)
packed_response = umsgpack.packb([request_id, response])
if len(packed_response) <= self.mdu:
RNS.Packet(self, packed_response, RNS.Packet.DATA, context = RNS.Packet.RESPONSE).send()
else:
response_resource = RNS.Resource(packed_response, self, request_id = request_id, is_response = True, auto_compress=auto_compress)
else:
identity_string = str(self.get_remote_identity()) if self.get_remote_identity() != None else "<Unknown>"
RNS.log("Request "+RNS.prettyhexrep(request_id)+" from "+identity_string+" not allowed for: "+str(path), RNS.LOG_DEBUG)
def handle_response(self, request_id, response_data, response_size, response_transfer_size):
def handle_response(self, request_id, response_data, response_size, response_transfer_size, metadata=None):
if self.status == Link.ACTIVE:
remove = None
for pending_request in self.pending_requests:
@@ -888,7 +918,7 @@ class Link:
if pending_request.response_transfer_size == None:
pending_request.response_transfer_size = 0
pending_request.response_transfer_size += response_transfer_size
pending_request.response_received(response_data)
pending_request.response_received(response_data, metadata)
except Exception as e:
RNS.log("Error occurred while handling response. The contained exception was: "+str(e), RNS.LOG_ERROR)
@@ -911,12 +941,21 @@ class Link:
def response_resource_concluded(self, resource):
if resource.status == RNS.Resource.COMPLETE:
packed_response = resource.data.read()
unpacked_response = umsgpack.unpackb(packed_response)
request_id = unpacked_response[0]
response_data = unpacked_response[1]
# If the response resource has metadata, this
# is a file response, and we'll pass the open
# file handle directly.
if resource.has_metadata:
self.handle_response(resource.request_id, resource.data, resource.total_size, resource.size, metadata=resource.metadata)
# If not, we'll unpack the response data and
# pass the unpacked structure to the handler
else:
packed_response = resource.data.read()
unpacked_response = umsgpack.unpackb(packed_response)
request_id = unpacked_response[0]
response_data = unpacked_response[1]
self.handle_response(request_id, response_data, resource.total_size, resource.size)
self.handle_response(request_id, response_data, resource.total_size, resource.size)
else:
RNS.log("Incoming response resource failed with status: "+RNS.hexrep([resource.status]), RNS.LOG_DEBUG)
for pending_request in self.pending_requests:
@@ -1340,6 +1379,7 @@ class RequestReceipt():
self.response = None
self.response_transfer_size = None
self.response_size = None
self.metadata = None
self.status = RequestReceipt.SENT
self.sent_at = time.time()
self.progress = 0
@@ -1426,10 +1466,11 @@ class RequestReceipt():
resource.cancel()
def response_received(self, response):
def response_received(self, response, metadata=None):
if not self.status == RequestReceipt.FAILED:
self.progress = 1.0
self.response = response
self.metadata = metadata
self.status = RequestReceipt.READY
self.response_concluded_at = time.time()
+2 -2
View File
@@ -43,10 +43,10 @@ class Packet:
For ``RNS.Destination.GROUP`` destinations, Reticulum will use the
pre-shared key configured for the destination. All packets to group
destinations are encrypted with the same AES-128 key.
destinations are encrypted with the same AES-256 key.
For ``RNS.Destination.SINGLE`` destinations, Reticulum will use a newly
derived ephemeral AES-128 key for every packet.
derived ephemeral AES-256 key for every packet.
For :ref:`RNS.Link<api-link>` destinations, Reticulum will use per-link
ephemeral keys, and offers **Forward Secrecy**.
+137 -66
View File
@@ -33,6 +33,7 @@ import os
import bz2
import math
import time
import struct
import tempfile
import threading
from threading import Lock
@@ -107,22 +108,20 @@ class Resource:
# it is to be handled within reasonable
# time constraint, even on small systems.
#
# A small system in this regard is
# defined as a Raspberry Pi, which should
# be able to compress, encrypt and hash-map
# the resource in about 10 seconds.
#
# This constant will be used when determining
# how to sequence the sending of large resources.
#
# Capped at 16777215 (0xFFFFFF) per segment to
# fit in 3 bytes in resource advertisements.
MAX_EFFICIENT_SIZE = 16 * 1024 * 1024 - 1
MAX_EFFICIENT_SIZE = 1 * 1024 * 1024 - 1
RESPONSE_MAX_GRACE_TIME = 10
# Max metadata size is 16777215 (0xFFFFFF) bytes
METADATA_MAX_SIZE = 16 * 1024 * 1024 - 1
# The maximum size to auto-compress with
# bz2 before sending.
AUTO_COMPRESS_MAX_SIZE = MAX_EFFICIENT_SIZE
AUTO_COMPRESS_MAX_SIZE = 64 * 1024 * 1024
PART_TIMEOUT_FACTOR = 4
PART_TIMEOUT_FACTOR_AFTER_RTT = 2
@@ -196,12 +195,15 @@ class Resource:
resource.started_transferring = resource.last_activity
resource.storagepath = RNS.Reticulum.resourcepath+"/"+resource.original_hash.hex()
resource.meta_storagepath = resource.storagepath+".meta"
resource.segment_index = adv.i
resource.total_segments = adv.l
if adv.l > 1:
resource.split = True
else:
resource.split = False
if adv.l > 1: resource.split = True
else: resource.split = False
if adv.x: resource.has_metadata = True
else: resource.has_metadata = False
resource.hashmap = [None] * resource.total_parts
resource.hashmap_height = 0
@@ -227,9 +229,7 @@ class Resource:
RNS.log("Error while executing resource started callback from "+str(resource)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
resource.hashmap_update(0, resource.hashmap_raw)
resource.watchdog_job()
return resource
else:
@@ -243,15 +243,33 @@ class Resource:
# Create a resource for transmission to a remote destination
# The data passed can be either a bytes-array or a file opened
# in binary read mode.
def __init__(self, data, link, advertise=True, auto_compress=True, callback=None, progress_callback=None, timeout = None, segment_index = 1, original_hash = None, request_id = None, is_response = False):
def __init__(self, data, link, metadata=None, advertise=True, auto_compress=True, callback=None, progress_callback=None,
timeout = None, segment_index = 1, original_hash = None, request_id = None, is_response = False, sent_metadata_size=0):
data_size = None
resource_data = None
self.assembly_lock = False
self.preparing_next_segment = False
self.next_segment = None
self.metadata = None
self.has_metadata = False
self.metadata_size = sent_metadata_size
if metadata != None:
packed_metadata = umsgpack.packb(metadata)
metadata_size = len(packed_metadata)
if metadata_size > Resource.METADATA_MAX_SIZE:
raise SystemError("Resource metadata size exceeded")
else:
self.metadata = struct.pack(">I", metadata_size)[1:] + packed_metadata
self.metadata_size = len(self.metadata)
self.has_metadata = True
else:
self.metadata = b""
if sent_metadata_size > 0: self.has_metadata = True
if data != None:
if not hasattr(data, "read") and len(data) > Resource.MAX_EFFICIENT_SIZE:
if not hasattr(data, "read") and self.metadata_size + len(data) > Resource.MAX_EFFICIENT_SIZE:
original_data = data
data_size = len(original_data)
data = tempfile.TemporaryFile()
@@ -259,31 +277,43 @@ class Resource:
del original_data
if hasattr(data, "read"):
if data_size == None:
data_size = os.stat(data.name).st_size
if data_size == None: data_size = os.stat(data.name).st_size
self.total_size = data_size + self.metadata_size
self.total_size = data_size
if data_size <= Resource.MAX_EFFICIENT_SIZE:
if self.total_size <= Resource.MAX_EFFICIENT_SIZE:
self.total_segments = 1
self.segment_index = 1
self.split = False
resource_data = data.read()
data.close()
else:
self.total_segments = ((data_size-1)//Resource.MAX_EFFICIENT_SIZE)+1
# self.total_segments = ((data_size-1)//Resource.MAX_EFFICIENT_SIZE)+1
# self.segment_index = segment_index
# self.split = True
# seek_index = segment_index-1
# seek_position = seek_index*Resource.MAX_EFFICIENT_SIZE
self.total_segments = ((self.total_size-1)//Resource.MAX_EFFICIENT_SIZE)+1
self.segment_index = segment_index
self.split = True
seek_index = segment_index-1
seek_position = seek_index*Resource.MAX_EFFICIENT_SIZE
first_read_size = Resource.MAX_EFFICIENT_SIZE - self.metadata_size
if segment_index == 1:
seek_position = 0
segment_read_size = first_read_size
else:
seek_position = first_read_size + ((seek_index-1)*Resource.MAX_EFFICIENT_SIZE)
segment_read_size = Resource.MAX_EFFICIENT_SIZE
data.seek(seek_position)
resource_data = data.read(Resource.MAX_EFFICIENT_SIZE)
resource_data = data.read(segment_read_size)
self.input_file = data
elif isinstance(data, bytes):
data_size = len(data)
self.total_size = data_size
self.total_size = data_size + self.metadata_size
resource_data = data
self.total_segments = 1
@@ -296,7 +326,9 @@ class Resource:
else:
raise TypeError("Invalid data instance type passed to resource initialisation")
data = resource_data
if resource_data:
if self.has_metadata: data = self.metadata + resource_data
else: data = resource_data
self.status = Resource.NONE
self.link = link
@@ -327,7 +359,16 @@ class Resource:
self.request_id = request_id
self.started_transferring = None
self.is_response = is_response
self.auto_compress = auto_compress
self.auto_compress_limit = Resource.AUTO_COMPRESS_MAX_SIZE
self.auto_compress_option = auto_compress
if type(auto_compress) == bool:
self.auto_compress = auto_compress
elif type(auto_compress) == int:
self.auto_compress = True
self.auto_compress_limit = auto_compress
else:
raise TypeError(f"Invalid type {type(auto_compress)} for auto_compress option")
self.req_hashlist = []
self.receiver_min_consecutive_height = 0
@@ -343,7 +384,7 @@ class Resource:
self.uncompressed_data = data
compression_began = time.time()
if (auto_compress and len(self.uncompressed_data) <= Resource.AUTO_COMPRESS_MAX_SIZE):
if self.auto_compress and data_size <= self.auto_compress_limit:
RNS.log("Compressing resource data...", RNS.LOG_EXTREME)
self.compressed_data = bz2.compress(self.uncompressed_data)
RNS.log("Compression completed in "+str(round(time.time()-compression_began, 3))+" seconds", RNS.LOG_EXTREME)
@@ -362,19 +403,20 @@ class Resource:
self.data += self.compressed_data
self.compressed = True
self.uncompressed_data = None
else:
self.data = b""
self.data += RNS.Identity.get_random_hash()[:Resource.RANDOM_HASH_SIZE]
self.data += self.uncompressed_data
self.uncompressed_data = self.data
self.compressed = False
self.compressed_data = None
if auto_compress:
if self.auto_compress and data_size <= self.auto_compress_limit:
RNS.log("Compression did not decrease size, sending uncompressed", RNS.LOG_EXTREME)
self.compressed_data = None
self.uncompressed_data = None
# Resources handle encryption directly to
# make optimal use of packet MTU on an entire
# encrypted stream. The Resource instance will
@@ -427,7 +469,8 @@ class Resource:
self.parts.append(part)
RNS.log("Hashmap computation concluded in "+str(round(time.time()-hashmap_computation_began, 3))+" seconds", RNS.LOG_EXTREME)
self.data = None
if advertise:
self.advertise()
else:
@@ -512,8 +555,7 @@ class Resource:
if self.link: self.link.expected_rate = self.eifr
def watchdog_job(self):
thread = threading.Thread(target=self.__watchdog_job)
thread.daemon = True
thread = threading.Thread(target=self.__watchdog_job, daemon=True)
thread.start()
def __watchdog_job(self):
@@ -559,6 +601,7 @@ class Resource:
else:
sleep_time = self.last_activity + self.part_timeout_factor*((3*self.sdu)/self.eifr) + Resource.RETRY_GRACE_TIME + extra_wait - time.time()
# TODO: Remove debug at some point
# RNS.log(f"EIFR {RNS.prettyspeed(self.eifr)}, ETOF {RNS.prettyshorttime(expected_tof_remaining)} ", RNS.LOG_DEBUG, pt=True)
# RNS.log(f"Resource ST {RNS.prettyshorttime(sleep_time)}, RTT {RNS.prettyshorttime(self.rtt or self.link.rtt)}, {self.outstanding_parts} left", RNS.LOG_DEBUG, pt=True)
@@ -628,29 +671,37 @@ class Resource:
self.status = Resource.ASSEMBLING
stream = b"".join(self.parts)
if self.encrypted:
data = self.link.decrypt(stream)
else:
data = stream
if self.encrypted: data = self.link.decrypt(stream)
else: data = stream
# Strip off random hash
data = data[Resource.RANDOM_HASH_SIZE:]
if self.compressed:
self.data = bz2.decompress(data)
else:
self.data = data
if self.compressed: self.data = bz2.decompress(data)
else: self.data = data
calculated_hash = RNS.Identity.full_hash(self.data+self.random_hash)
if calculated_hash == self.hash:
if self.has_metadata and self.segment_index == 1:
# TODO: Add early metadata_ready callback
metadata_size = self.data[0] << 16 | self.data[1] << 8 | self.data[2]
packed_metadata = self.data[3:3+metadata_size]
metadata_file = open(self.meta_storagepath, "wb")
metadata_file.write(packed_metadata)
metadata_file.close()
del packed_metadata
data = self.data[3+metadata_size:]
else:
data = self.data
self.file = open(self.storagepath, "ab")
self.file.write(self.data)
self.file.write(data)
self.file.close()
self.status = Resource.COMPLETE
del data
self.prove()
else:
self.status = Resource.CORRUPT
else: self.status = Resource.CORRUPT
except Exception as e:
@@ -662,21 +713,27 @@ class Resource:
if self.segment_index == self.total_segments:
if self.callback != None:
if not os.path.isfile(self.meta_storagepath):
self.metadata = None
else:
metadata_file = open(self.meta_storagepath, "rb")
self.metadata = umsgpack.unpackb(metadata_file.read())
metadata_file.close()
try: os.unlink(self.meta_storagepath)
except Exception as e:
RNS.log(f"Error while cleaning up resource metadata file, the contained exception was: {e}", RNS.LOG_ERROR)
self.data = open(self.storagepath, "rb")
try:
self.callback(self)
try: self.callback(self)
except Exception as e:
RNS.log("Error while executing resource assembled callback from "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
try:
if hasattr(self.data, "close") and callable(self.data.close):
self.data.close()
os.unlink(self.storagepath)
if hasattr(self.data, "close") and callable(self.data.close): self.data.close()
if os.path.isfile(self.storagepath): os.unlink(self.storagepath)
except Exception as e:
RNS.log("Error while cleaning up resource files, the contained exception was:", RNS.LOG_ERROR)
RNS.log(str(e))
RNS.log(f"Error while cleaning up resource files, the contained exception was: {e}", RNS.LOG_ERROR)
else:
RNS.log("Resource segment "+str(self.segment_index)+" of "+str(self.total_segments)+" received, waiting for next segment to be announced", RNS.LOG_DEBUG)
@@ -707,7 +764,8 @@ class Resource:
request_id = self.request_id,
is_response = self.is_response,
advertise = False,
auto_compress = self.auto_compress,
auto_compress = self.auto_compress_option,
sent_metadata_size = self.metadata_size,
)
def validate_proof(self, proof_data):
@@ -720,18 +778,18 @@ class Resource:
# If all segments were processed, we'll
# signal that the resource sending concluded
if self.callback != None:
try:
self.callback(self)
except Exception as e:
RNS.log("Error while executing resource concluded callback from "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
try: self.callback(self)
except Exception as e: RNS.log("Error while executing resource concluded callback from "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
finally:
try:
if hasattr(self, "input_file"):
if hasattr(self.input_file, "close") and callable(self.input_file.close):
self.input_file.close()
except Exception as e:
RNS.log("Error while closing resource input file: "+str(e), RNS.LOG_ERROR)
if hasattr(self.input_file, "close") and callable(self.input_file.close): self.input_file.close()
except Exception as e: RNS.log("Error while closing resource input file: "+str(e), RNS.LOG_ERROR)
else:
try:
if hasattr(self, "input_file"):
if hasattr(self.input_file, "close") and callable(self.input_file.close): self.input_file.close()
except Exception as e: RNS.log("Error while closing resource input file: "+str(e), RNS.LOG_ERROR)
else:
# Otherwise we'll recursively create the
# next segment of the resource
@@ -739,8 +797,16 @@ class Resource:
RNS.log(f"Next segment preparation for resource {self} was not started yet, manually preparing now. This will cause transfer slowdown.", RNS.LOG_WARNING)
self.__prepare_next_segment()
while self.next_segment == None:
time.sleep(0.05)
while self.next_segment == None: time.sleep(0.05)
self.data = None
self.metadata = None
self.parts = None
self.input_file = None
self.link = None
self.req_hashlist = None
self.hashmap = None
self.next_segment.advertise()
else:
pass
@@ -1202,6 +1268,7 @@ class ResourceAdvertisement:
self.c = resource.compressed # Compression flag
self.e = resource.encrypted # Encryption flag
self.s = resource.split # Split flag
self.x = resource.has_metadata # Metadata flag
self.i = resource.segment_index # Segment index
self.l = resource.total_segments # Total segments
self.q = resource.request_id # ID of associated request
@@ -1217,7 +1284,7 @@ class ResourceAdvertisement:
self.p = True
# Flags
self.f = 0x00 | self.p << 4 | self.u << 3 | self.s << 2 | self.c << 1 | self.e
self.f = 0x00 | self.x << 5 | self.p << 4 | self.u << 3 | self.s << 2 | self.c << 1 | self.e
def get_transfer_size(self):
return self.t
@@ -1237,6 +1304,9 @@ class ResourceAdvertisement:
def is_compressed(self):
return self.c
def has_metadata(self):
return self.x
def get_link(self):
return self.link
@@ -1286,5 +1356,6 @@ class ResourceAdvertisement:
adv.s = True if ((adv.f >> 2) & 0x01) == 0x01 else False
adv.u = True if ((adv.f >> 3) & 0x01) == 0x01 else False
adv.p = True if ((adv.f >> 4) & 0x01) == 0x01 else False
adv.x = True if ((adv.f >> 5) & 0x01) == 0x01 else False
return adv
+37 -16
View File
@@ -211,7 +211,8 @@ class Reticulum:
"""
return Reticulum.__instance
def __init__(self,configdir=None, loglevel=None, logdest=None, verbosity=None, require_shared_instance=False):
def __init__(self,configdir=None, loglevel=None, logdest=None, verbosity=None,
require_shared_instance=False, shared_instance_type=None):
"""
Initialises and starts a Reticulum instance. This must be
done before any other operations, and Reticulum will not
@@ -263,12 +264,11 @@ class Reticulum:
self.local_control_port = 37429
self.local_socket_path = None
self.share_instance = True
self.shared_instance_type = shared_instance_type
self.rpc_listener = None
self.rpc_key = None
self.rpc_type = "AF_INET"
if RNS.vendor.platformutils.use_af_unix():
self.local_socket_path = "default"
self.use_af_unix = False
self.ifac_salt = Reticulum.IFAC_SALT
@@ -325,12 +325,11 @@ class Reticulum:
self.__apply_config()
RNS.log(f"Utilising cryptography backend \"{RNS.Cryptography.Provider.backend()}\"", RNS.LOG_DEBUG)
RNS.log(f"Configuration loaded from {self.configpath}", RNS.LOG_VERBOSE)
RNS.Identity.load_known_destinations()
RNS.Identity.load_known_destinations()
RNS.Transport.start(self)
if RNS.vendor.platformutils.use_af_unix():
if self.use_af_unix:
self.rpc_addr = f"\0rns/{self.local_socket_path}/rpc"
self.rpc_type = "AF_UNIX"
else:
@@ -458,6 +457,11 @@ class Reticulum:
if option == "instance_name":
value = self.config["reticulum"][option]
self.local_socket_path = value
if option == "shared_instance_type":
if self.shared_instance_type == None:
value = self.config["reticulum"][option].lower()
if value in ["tcp", "unix"]:
self.shared_instance_type = value
if option == "shared_instance_port":
value = int(self.config["reticulum"][option])
self.local_interface_port = value
@@ -516,6 +520,17 @@ class Reticulum:
if RNS.compiled: RNS.log("Reticulum running in compiled mode", RNS.LOG_DEBUG)
else: RNS.log("Reticulum running in interpreted mode", RNS.LOG_DEBUG)
if RNS.vendor.platformutils.use_af_unix():
if self.shared_instance_type == "tcp": self.use_af_unix = False
else: self.use_af_unix = True
else:
self.shared_instance_type = "tcp"
self.use_af_unix = False
if self.local_socket_path == None and self.use_af_unix:
self.local_socket_path = "default"
self.__start_local_interface()
if self.is_shared_instance or self.is_standalone_instance:
@@ -636,13 +651,11 @@ class Reticulum:
interface.mode = interface_mode
interface.announce_cap = announce_cap
if configured_bitrate:
interface.bitrate = configured_bitrate
if configured_bitrate: interface.bitrate = configured_bitrate
interface.optimise_mtu()
if ifac_size != None:
interface.ifac_size = ifac_size
else:
interface.ifac_size = interface.DEFAULT_IFAC_SIZE
if ifac_size != None: interface.ifac_size = ifac_size
else: interface.ifac_size = interface.DEFAULT_IFAC_SIZE
interface.announce_rate_target = announce_rate_target
interface.announce_rate_grace = announce_rate_grace
@@ -1379,8 +1392,16 @@ instance_name = default
# is the case, you can isolate different instances by
# specifying a unique set of ports for each:
shared_instance_port = 37428
instance_control_port = 37429
# shared_instance_port = 37428
# instance_control_port = 37429
# If you want to explicitly use TCP for shared instance
# communication, instead of domain sockets, this is also
# possible, by using the following option:
# shared_instance_type = tcp
# You can configure Reticulum to panic and forcibly close
# if an unrecoverable interface error occurs, such as the
@@ -1388,7 +1409,7 @@ instance_control_port = 37429
# an optional directive, and can be left out for brevity.
# This behaviour is disabled by default.
panic_on_interface_error = No
# panic_on_interface_error = No
[logging]
+30 -18
View File
@@ -1070,7 +1070,7 @@ class Transport:
if should_transmit:
if not stored_hash:
Transport.packet_hashlist.add(packet.packet_hash)
Transport.add_packet_hash(packet.packet_hash)
stored_hash = True
Transport.transmit(interface, packet.raw)
@@ -1082,11 +1082,16 @@ class Transport:
Transport.jobs_locked = False
return sent
@staticmethod
def add_packet_hash(packet_hash):
if not Transport.owner.is_connected_to_shared_instance:
Transport.packet_hashlist.add(packet_hash)
@staticmethod
def packet_filter(packet):
# TODO: Think long and hard about this.
# Is it even strictly necessary with the current
# transport rules?
# If connected to a shared instance, it will handle
# packet filtering
if Transport.owner.is_connected_to_shared_instance: return True
# Filter packets intended for other transport instances
if packet.transport_id != None and packet.packet_type != RNS.Packet.ANNOUNCE:
@@ -1110,7 +1115,7 @@ class Transport:
if packet.destination_type == RNS.Destination.PLAIN:
if packet.packet_type != RNS.Packet.ANNOUNCE:
if packet.hops > 1:
RNS.log("Dropped PLAIN packet "+RNS.prettyhexrep(packet.hash)+" with "+str(packet.hops)+" hops", RNS.LOG_DEBUG)
RNS.log("Dropped PLAIN packet "+RNS.prettyhexrep(packet.packet_hash)+" with "+str(packet.hops)+" hops", RNS.LOG_DEBUG)
return False
else:
return True
@@ -1121,7 +1126,7 @@ class Transport:
if packet.destination_type == RNS.Destination.GROUP:
if packet.packet_type != RNS.Packet.ANNOUNCE:
if packet.hops > 1:
RNS.log("Dropped GROUP packet "+RNS.prettyhexrep(packet.hash)+" with "+str(packet.hops)+" hops", RNS.LOG_DEBUG)
RNS.log("Dropped GROUP packet "+RNS.prettyhexrep(packet.packet_hash)+" with "+str(packet.hops)+" hops", RNS.LOG_DEBUG)
return False
else:
return True
@@ -1274,7 +1279,7 @@ class Transport:
remember_packet_hash = False
if remember_packet_hash:
Transport.packet_hashlist.add(packet.packet_hash)
Transport.add_packet_hash(packet.packet_hash)
# TODO: Enable when caching has been redesigned
# Transport.cache(packet)
@@ -1374,10 +1379,14 @@ class Transport:
new_raw = new_raw[:-RNS.Link.LINK_MTU_SIZE]
else:
if nh_mtu < path_mtu:
path_mtu = nh_mtu
clamped_mtu = RNS.Link.signalling_bytes(path_mtu, mode)
RNS.log(f"Clamping link MTU to {RNS.prettysize(nh_mtu)}", RNS.LOG_DEBUG) # TODO: Remove debug
new_raw = new_raw[:-RNS.Link.LINK_MTU_SIZE]+clamped_mtu
try:
path_mtu = nh_mtu
clamped_mtu = RNS.Link.signalling_bytes(path_mtu, mode)
RNS.log(f"Clamping link MTU to {RNS.prettysize(nh_mtu)}", RNS.LOG_DEBUG) # TODO: Remove debug
new_raw = new_raw[:-RNS.Link.LINK_MTU_SIZE]+clamped_mtu
except Exception as e:
RNS.log(f"Dropping link request packet. The contained exception was: {e}", RNS.LOG_WARNING)
return
# Entry format is
link_entry = [ now, # 0: Timestamp,
@@ -1440,7 +1449,7 @@ class Transport:
# Add this packet to the filter hashlist if we
# have determined that it's actually our turn
# to process it.
Transport.packet_hashlist.add(packet.packet_hash)
Transport.add_packet_hash(packet.packet_hash)
new_raw = packet.raw[0:1]
new_raw += struct.pack("!B", packet.hops)
@@ -1842,10 +1851,14 @@ class Transport:
packet.data = packet.data[:-RNS.Link.LINK_MTU_SIZE]
else:
if nh_mtu < path_mtu:
path_mtu = nh_mtu
clamped_mtu = RNS.Link.signalling_bytes(path_mtu, mode)
RNS.log(f"Clamping link MTU to {RNS.prettysize(nh_mtu)}", RNS.LOG_DEBUG) # TODO: Remove debug
packet.data = packet.data[:-RNS.Link.LINK_MTU_SIZE]+clamped_mtu
try:
path_mtu = nh_mtu
clamped_mtu = RNS.Link.signalling_bytes(path_mtu, mode)
RNS.log(f"Clamping link MTU to {RNS.prettysize(nh_mtu)}", RNS.LOG_DEBUG) # TODO: Remove debug
packet.data = packet.data[:-RNS.Link.LINK_MTU_SIZE]+clamped_mtu
except Exception as e:
RNS.log(f"Dropping link request packet to local destination. The contained exception was: {e}", RNS.LOG_WARNING)
return
packet.destination = destination
destination.receive(packet)
@@ -1954,7 +1967,7 @@ class Transport:
# Add this packet to the filter hashlist if we
# have determined that it's actually destined
# for this system, and then validate the proof
Transport.packet_hashlist.add(packet.packet_hash)
Transport.add_packet_hash(packet.packet_hash)
link.validate_proof(packet)
elif packet.context == RNS.Packet.RESOURCE_PRF:
@@ -2772,7 +2785,6 @@ class Transport:
Transport.reverse_table = {}
Transport.link_table = {}
Transport.held_announces = {}
Transport.announce_handlers = []
Transport.tunnels = {}
@staticmethod
+89 -88
View File
@@ -33,6 +33,7 @@
import RNS
import argparse
import threading
import shutil
import time
import sys
import os
@@ -42,6 +43,7 @@ from RNS._version import __version__
APP_NAME = "rncp"
allow_all = False
allow_fetch = False
allow_overwrite_on_receive = False
fetch_auto_compress = True
fetch_jail = None
save_path = None
@@ -55,13 +57,14 @@ erase_str = "\33[2K\r"
def listen(configdir, verbosity = 0, quietness = 0, allowed = [], display_identity = False,
limit = None, disable_auth = None, fetch_allowed = False, no_compress=False,
jail = None, save = None, announce = False):
jail = None, save = None, announce = False, allow_overwrite=False):
global allow_all, allow_fetch, allowed_identity_hashes, fetch_jail, save_path, fetch_auto_compress
from tempfile import TemporaryFile
global allow_all, allow_fetch, allowed_identity_hashes, fetch_jail, save_path
global fetch_auto_compress, allow_overwrite_on_receive
allow_fetch = fetch_allowed
fetch_auto_compress = not no_compress
allow_overwrite_on_receive = allow_overwrite
identity = None
if announce < 0:
announce = False
@@ -183,22 +186,15 @@ def listen(configdir, verbosity = 0, quietness = 0, allowed = [], display_identi
if target_link != None:
RNS.log("Sending file "+str(file_path)+" to client", RNS.LOG_VERBOSE)
temp_file = TemporaryFile()
real_file = open(file_path, "rb")
filename_bytes = os.path.basename(file_path).encode("utf-8")
filename_len = len(filename_bytes)
try:
metadata = {"name": os.path.basename(file_path).encode("utf-8") }
fetch_resource = RNS.Resource(open(file_path, "rb"), target_link, metadata=metadata, auto_compress=fetch_auto_compress)
return True
if filename_len > 0xFFFF:
print("Filename exceeds max size, cannot send")
RNS.exit(1)
except Exception as e:
RNS.log(f"Could not send file to client. The contained exception was: {e}", RNS.LOG_ERROR)
return False
temp_file.write(filename_len.to_bytes(2, "big"))
temp_file.write(filename_bytes)
temp_file.write(real_file.read())
temp_file.seek(0)
fetch_resource = RNS.Resource(temp_file, target_link, auto_compress=fetch_auto_compress)
return True
else:
return None
@@ -223,8 +219,7 @@ def listen(configdir, verbosity = 0, quietness = 0, allowed = [], display_identi
threading.Thread(target=job, daemon=True).start()
while True:
time.sleep(1)
while True: time.sleep(1)
def client_link_established(link):
RNS.log("Incoming link established", RNS.LOG_VERBOSE)
@@ -269,34 +264,42 @@ def receive_resource_started(resource):
print("Starting resource transfer "+RNS.prettyhexrep(resource.hash)+id_str)
def receive_resource_concluded(resource):
global save_path
global save_path, allow_overwrite_on_receive
if resource.status == RNS.Resource.COMPLETE:
print(str(resource)+" completed")
if resource.total_size > 4:
filename_len = int.from_bytes(resource.data.read(2), "big")
filename = resource.data.read(filename_len).decode("utf-8")
counter = 0
if save_path:
saved_filename = os.path.abspath(os.path.expanduser(save_path+"/"+filename))
if not saved_filename.startswith(save_path+"/"):
RNS.log(f"Invalid save path {saved_filename}, ignoring", RNS.LOG_ERROR)
return
else:
saved_filename = filename
full_save_path = saved_filename
while os.path.isfile(full_save_path):
counter += 1
full_save_path = saved_filename+"."+str(counter)
file = open(full_save_path, "wb")
file.write(resource.data.read())
file.close()
if resource.metadata == None:
print("Invalid data received, ignoring resource")
return
else:
print("Invalid data received, ignoring resource")
try:
filename = os.path.basename(resource.metadata["name"].decode("utf-8"))
counter = 0
if save_path:
saved_filename = os.path.abspath(os.path.expanduser(save_path+"/"+filename))
if not saved_filename.startswith(save_path+"/"):
RNS.log(f"Invalid save path {saved_filename}, ignoring", RNS.LOG_ERROR)
return
else:
saved_filename = filename
full_save_path = saved_filename
if allow_overwrite_on_receive:
if os.path.isfile(full_save_path):
try: os.unlink(full_save_path)
except Exception as e:
RNS.log(f"Could not overwrite existing file {full_save_path}, renaming instead", RNS.LOG_ERROR)
while os.path.isfile(full_save_path):
counter += 1
full_save_path = saved_filename+"."+str(counter)
shutil.move(resource.data.name, full_save_path)
except Exception as e:
RNS.log(f"An error occurred while saving received resource: {e}", RNS.LOG_ERROR)
return
else:
print("Resource failed")
@@ -342,10 +345,11 @@ def sender_progress(resource):
resource_done = True
link = None
def fetch(configdir, verbosity = 0, quietness = 0, destination = None, file = None, timeout = RNS.Transport.PATH_REQUEST_TIMEOUT, silent=False, phy_rates=False, save=None):
global current_resource, resource_done, link, speed, show_phy_rates, save_path
def fetch(configdir, verbosity = 0, quietness = 0, destination = None, file = None, timeout = RNS.Transport.PATH_REQUEST_TIMEOUT, silent=False, phy_rates=False, save=None, allow_overwrite=False):
global current_resource, resource_done, link, speed, show_phy_rates, save_path, allow_overwrite_on_receive
targetloglevel = 3+verbosity-quietness
show_phy_rates = phy_rates
allow_overwrite_on_receive = allow_overwrite
if save:
sp = os.path.abspath(os.path.expanduser(save))
@@ -481,32 +485,40 @@ def fetch(configdir, verbosity = 0, quietness = 0, destination = None, file = No
def fetch_resource_concluded(resource):
nonlocal resource_resolved, resource_status
global save_path
global save_path, allow_overwrite_on_receive
if resource.status == RNS.Resource.COMPLETE:
if resource.total_size > 4:
filename_len = int.from_bytes(resource.data.read(2), "big")
filename = resource.data.read(filename_len).decode("utf-8")
counter = 0
if save_path:
saved_filename = os.path.abspath(os.path.expanduser(save_path+"/"+filename))
else:
saved_filename = filename
full_save_path = saved_filename
while os.path.isfile(full_save_path):
counter += 1
full_save_path = saved_filename+"."+str(counter)
file = open(full_save_path, "wb")
file.write(resource.data.read())
file.close()
resource_status = "completed"
if resource.metadata == None:
print("Invalid data received, ignoring resource")
return
else:
print("Invalid data received, ignoring resource")
resource_status = "invalid_data"
try:
filename = os.path.basename(resource.metadata["name"].decode("utf-8"))
counter = 0
if save_path:
saved_filename = os.path.abspath(os.path.expanduser(save_path+"/"+filename))
if not saved_filename.startswith(save_path+"/"):
print(f"Invalid save path {saved_filename}, ignoring")
return
else:
saved_filename = filename
full_save_path = saved_filename
if allow_overwrite_on_receive:
if os.path.isfile(full_save_path):
try: os.unlink(full_save_path)
except Exception as e:
print(f"Could not overwrite existing file {full_save_path}, renaming instead")
while os.path.isfile(full_save_path):
counter += 1
full_save_path = saved_filename+"."+str(counter)
shutil.move(resource.data.name, full_save_path)
except Exception as e:
print(f"An error occurred while saving received resource: {e}")
return
else:
print("Resource failed")
@@ -604,7 +616,6 @@ def fetch(configdir, verbosity = 0, quietness = 0, destination = None, file = No
def send(configdir, verbosity = 0, quietness = 0, destination = None, file = None, timeout = RNS.Transport.PATH_REQUEST_TIMEOUT, silent=False, phy_rates=False, no_compress=False):
global current_resource, resource_done, link, speed, show_phy_rates, phy_got_total, phy_speed
from tempfile import TemporaryFile
targetloglevel = 3+verbosity-quietness
show_phy_rates = phy_rates
@@ -626,21 +637,7 @@ def send(configdir, verbosity = 0, quietness = 0, destination = None, file = Non
print("File not found")
sys.exit(1)
temp_file = TemporaryFile()
real_file = open(file_path, "rb")
filename_bytes = os.path.basename(file_path).encode("utf-8")
filename_len = len(filename_bytes)
if filename_len > 0xFFFF:
print("Filename exceeds max size, cannot send")
RNS.exit(1)
else:
print("Preparing file...", end=es)
temp_file.write(filename_len.to_bytes(2, "big"))
temp_file.write(filename_bytes)
temp_file.write(real_file.read())
temp_file.seek(0)
metadata = {"name": os.path.basename(file_path).encode("utf-8") }
print(f"{erase_str}", end="")
@@ -727,9 +724,12 @@ def send(configdir, verbosity = 0, quietness = 0, destination = None, file = Non
link.identify(identity)
auto_compress = True
if no_compress:
auto_compress = False
resource = RNS.Resource(temp_file, link, callback = sender_progress, progress_callback = sender_progress, auto_compress = auto_compress)
if no_compress: auto_compress = False
try: resource = RNS.Resource(open(file_path, "rb"), link, metadata=metadata, callback = sender_progress, progress_callback = sender_progress, auto_compress = auto_compress)
except Exception as e:
print(f"Could not start transfer: {e}")
RNS.exit(1)
current_resource = resource
while resource.status < RNS.Resource.TRANSFERRING:
@@ -800,8 +800,6 @@ def send(configdir, verbosity = 0, quietness = 0, destination = None, file = Non
print("\n"+str(file_path)+" copied to "+RNS.prettyhexrep(destination_hash))
link.teardown()
time.sleep(0.25)
real_file.close()
temp_file.close()
RNS.exit(0)
def main():
@@ -819,6 +817,7 @@ def main():
parser.add_argument("-f", '--fetch', action='store_true', default=False, help="fetch file from remote listener instead of sending")
parser.add_argument("-j", "--jail", metavar="path", action="store", default=None, help="restrict fetch requests to specified path", type=str)
parser.add_argument("-s", "--save", metavar="path", action="store", default=None, help="save received files in specified path", type=str)
parser.add_argument('-O', '--overwrite', action='store_true', default=False, help="Allow overwriting received files, instead of adding postfix")
parser.add_argument("-b", action='store', metavar="seconds", default=-1, help="announce interval, 0 to only announce at startup", type=int)
parser.add_argument('-a', metavar="allowed_hash", dest="allowed", action='append', help="allow this identity (or add in ~/.rncp/allowed_identities)", type=str)
parser.add_argument('-n', '--no-auth', action='store_true', default=False, help="accept requests from anyone")
@@ -844,6 +843,7 @@ def main():
# limit=args.limit,
disable_auth=args.no_auth,
announce=args.b,
allow_overwrite=args.overwrite,
)
elif args.fetch:
@@ -858,6 +858,7 @@ def main():
silent = args.silent,
phy_rates = args.phy_rates,
save = args.save,
allow_overwrite=args.overwrite,
)
else:
print("")
+15 -15
View File
@@ -1716,7 +1716,7 @@ def main():
print(" '")
print("[1] A specific kind of RNode")
print("")
print(" | Select this option if you have put toghether an RNode")
print(" | Select this option if you have put together an RNode")
print(" \\ / of your own design, or if you are prototyping one.")
print(" '")
print("[2] Homebrew RNode")
@@ -1762,7 +1762,7 @@ def main():
print("")
print("Important! Using RNode firmware on homebrew devices should currently be")
print("considered experimental. It is not intended for production or critical use.")
print("The currently supplied firmware is provided AS-IS as a courtesey to those")
print("The currently supplied firmware is provided AS-IS as a courtesy to those")
print("who would like to experiment with it. Hit enter to continue.")
print("---------------------------------------------------------------------------")
input()
@@ -1778,7 +1778,7 @@ def main():
print("")
print("Important! Using RNode firmware on T-Beam devices should currently be")
print("considered experimental. It is not intended for production or critical use.")
print("The currently supplied firmware is provided AS-IS as a courtesey to those")
print("The currently supplied firmware is provided AS-IS as a courtesy to those")
print("who would like to experiment with it. Hit enter to continue.")
print("---------------------------------------------------------------------------")
input()
@@ -1794,7 +1794,7 @@ def main():
print("")
print("Important! Using RNode firmware on T-Beam devices should currently be")
print("considered experimental. It is not intended for production or critical use.")
print("The currently supplied firmware is provided AS-IS as a courtesey to those")
print("The currently supplied firmware is provided AS-IS as a courtesy to those")
print("who would like to experiment with it. Hit enter to continue.")
print("---------------------------------------------------------------------------")
input()
@@ -1810,7 +1810,7 @@ def main():
print("")
print("Important! Using RNode firmware on T-Beam devices should currently be")
print("considered experimental. It is not intended for production or critical use.")
print("The currently supplied firmware is provided AS-IS as a courtesey to those")
print("The currently supplied firmware is provided AS-IS as a courtesy to those")
print("who would like to experiment with it. Hit enter to continue.")
print("---------------------------------------------------------------------------")
input()
@@ -1823,7 +1823,7 @@ def main():
print("")
print("Important! Using RNode firmware on LoRa32 devices should currently be")
print("considered experimental. It is not intended for production or critical use.")
print("The currently supplied firmware is provided AS-IS as a courtesey to those")
print("The currently supplied firmware is provided AS-IS as a courtesy to those")
print("who would like to experiment with it. Hit enter to continue.")
print("---------------------------------------------------------------------------")
input()
@@ -1836,7 +1836,7 @@ def main():
print("")
print("Important! Using RNode firmware on LoRa32 devices should currently be")
print("considered experimental. It is not intended for production or critical use.")
print("The currently supplied firmware is provided AS-IS as a courtesey to those")
print("The currently supplied firmware is provided AS-IS as a courtesy to those")
print("who would like to experiment with it.")
print("")
print("Please Note! This device is known to have a faulty battery charging circuit,")
@@ -1855,7 +1855,7 @@ def main():
print("")
print("Important! Using RNode firmware on LoRa32 devices should currently be")
print("considered experimental. It is not intended for production or critical use.")
print("The currently supplied firmware is provided AS-IS as a courtesey to those")
print("The currently supplied firmware is provided AS-IS as a courtesy to those")
print("who would like to experiment with it. Hit enter to continue.")
print("---------------------------------------------------------------------------")
input()
@@ -1869,7 +1869,7 @@ def main():
print("Important! Using RNode firmware on Heltec devices should currently be")
print("considered experimental. It is not intended for production or critical use.")
print("")
print("The currently supplied firmware is provided AS-IS as a courtesey to those")
print("The currently supplied firmware is provided AS-IS as a courtesy to those")
print("who would like to experiment with it. Hit enter to continue.")
print("---------------------------------------------------------------------------")
input()
@@ -1884,7 +1884,7 @@ def main():
print("Important! Using RNode firmware on T3S3 devices should currently be")
print("considered experimental. It is not intended for production or critical use.")
print("")
print("The currently supplied firmware is provided AS-IS as a courtesey to those")
print("The currently supplied firmware is provided AS-IS as a courtesy to those")
print("who would like to experiment with it. Hit enter to continue.")
print("---------------------------------------------------------------------------")
input()
@@ -1898,7 +1898,7 @@ def main():
print("Important! Using RNode firmware on Heltec devices should currently be")
print("considered experimental. It is not intended for production or critical use.")
print("")
print("The currently supplied firmware is provided AS-IS as a courtesey to those")
print("The currently supplied firmware is provided AS-IS as a courtesy to those")
print("who would like to experiment with it. Hit enter to continue.")
print("---------------------------------------------------------------------------")
input()
@@ -1911,7 +1911,7 @@ def main():
print("")
print("Important! Using RNode firmware on RAKwireless devices should currently be")
print("considered experimental. It is not intended for production or critical use.")
print("The currently supplied firmware is provided AS-IS as a courtesey to those")
print("The currently supplied firmware is provided AS-IS as a courtesy to those")
print("who would like to experiment with it. Hit enter to continue.")
print("---------------------------------------------------------------------------")
input()
@@ -1924,7 +1924,7 @@ def main():
print("")
print("Important! Using RNode firmware on LilyGo T-Echo devices should currently be")
print("considered experimental. It is not intended for production or critical use.")
print("The currently supplied firmware is provided AS-IS as a courtesey to those")
print("The currently supplied firmware is provided AS-IS as a courtesy to those")
print("who would like to experiment with it. Hit enter to continue.")
print("---------------------------------------------------------------------------")
input()
@@ -1937,7 +1937,7 @@ def main():
print("")
print("Important! Using RNode firmware on Heltec T114 devices should currently be")
print("considered experimental. It is not intended for production or critical use.")
print("The currently supplied firmware is provided AS-IS as a courtesey to those")
print("The currently supplied firmware is provided AS-IS as a courtesy to those")
print("who would like to experiment with it. Hit enter to continue.")
print("---------------------------------------------------------------------------")
input()
@@ -1950,7 +1950,7 @@ def main():
print("")
print("Important! Using RNode firmware on SeeedStudio XIAO/wio devices should currently be")
print("considered experimental. It is not intended for production or critical use.")
print("The currently supplied firmware is provided AS-IS as a courtesey to those")
print("The currently supplied firmware is provided AS-IS as a courtesy to those")
print("who would like to experiment with it. Hit enter to continue.")
print("---------------------------------------------------------------------------")
input()
+19 -7
View File
@@ -117,12 +117,24 @@ share_instance = Yes
# If you want to run multiple *different* shared instances
# on the same system, you will need to specify different
# shared instance ports for each. The defaults are given
# below, and again, these options can be left out if you
# don't need them.
# instance names for each. On platforms supporting domain
# sockets, this can be done with the instance_name option:
shared_instance_port = 37428
instance_control_port = 37429
instance_name = default
# Some platforms don't support domain sockets, and if that
# is the case, you can isolate different instances by
# specifying a unique set of ports for each:
# shared_instance_port = 37428
# instance_control_port = 37429
# If you want to explicitly use TCP for shared instance
# communication, instead of domain sockets, this is also
# possible, by using the following option:
# shared_instance_type = tcp
# On systems where running instances may not have access
@@ -154,7 +166,7 @@ instance_control_port = 37429
# an optional directive, and can be left out for brevity.
# This behaviour is disabled by default.
panic_on_interface_error = No
# panic_on_interface_error = No
# When Transport is enabled, it is possible to allow the
@@ -165,7 +177,7 @@ panic_on_interface_error = No
# Transport Instance, and printed to the log at startup.
# Optional, and disabled by default.
respond_to_probes = No
# respond_to_probes = No
[logging]
+3
View File
@@ -168,6 +168,9 @@ def program_setup(configdir, dispall=False, verbosity=0, name_filter=None, json=
stats = None
if remote:
try:
if management_identity is None:
raise ValueError("Remote management requires an identity file. Use -i to specify the path to a management identity.")
dest_len = (RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2
if len(remote) != dest_len:
raise ValueError("Destination length is invalid, must be {hex} hexadecimal characters ({byte} bytes).".format(hex=dest_len, byte=dest_len//2))
+1 -1
View File
@@ -1 +1 @@
__version__ = "0.9.5"
__version__ = "1.0.0"
+3 -18
View File
@@ -14,18 +14,6 @@ This document outlines the currently established development roadmap for Reticul
## Currently Active Work Areas
For each release cycle of Reticulum, improvements and additions from the five [Primary Efforts](#primary-efforts) are selected as active work areas, and can be expected to be included in the upcoming releases within that cycle. While not entirely set in stone for each release cycle, they serve as a pointer of what to expect in the near future.
- The current `0.8.x` release cycle aims at completing
- [ ] Hot-pluggable interface system
- [ ] External interface plugins
- [ ] Network-wide path balancing and multi-pathing
- [ ] Expanded hardware support
- [ ] Overhauling and updating the documentation
- [ ] Distributed Destination Naming System
- [ ] A standalone RNS Daemon app for Android
- [ ] Addding automatic retries to all use cases of the `Request` API
- [ ] Performance and memory optimisations of the Python reference implementation
- [ ] Fixing bugs discovered while operating Reticulum systems and applications
## Primary Efforts
The development path for Reticulum is currently laid out in five distinct areas: *Comprehensibility*, *Universality*, *Functionality*, *Usability & Utility* and *Interfaceability*. Conceptualising the development of Reticulum into these areas serves to advance the implementation and work towards the Foundational Goals & Values of Reticulum.
@@ -50,17 +38,14 @@ These efforts are aimed at improving the ease of which Reticulum is understood,
### Universality
These efforts seek to broaden the universality of the Reticulum software and hardware ecosystem by continously diversifying platform support, and by improving the overall availability and ease of deployment of the Reticulum stack.
- OpenWRT support
- Create a standalone RNS Daemon app for Android
- A lightweight and portable C implementation for microcontrollers, µRNS
- A portable, high-performance Reticulum implementation in C/C++, see [#21](https://github.com/markqvist/Reticulum/discussions/21)
- Performance and memory optimisations of the Python implementation
- Bindings for other programming languages
### Functionality
These efforts aim to expand and improve the core functionality and reliability of Reticulum.
- Add support for user-supplied external interface drivers
- Add interface hot-plug and live up/down control to running instances
- Add automatic retries to all use cases of the `Request` API
- Network-wide path balancing
@@ -70,11 +55,11 @@ These efforts aim to expand and improve the core functionality and reliability o
- [Metric-based path selection and multiple paths](https://github.com/markqvist/Reticulum/discussions/86)
### Usability & Utility
These effors seek to make Reticulum easier to use and operate, and to expand the utility of the stack on deployed systems.
These efforts seek to make Reticulum easier to use and operate, and to expand the utility of the stack on deployed systems.
- Easy way to share interface configurations, see [#19](https://github.com/markqvist/Reticulum/discussions/19)
- Transit traffic display in rnstatus
- rnsconfig utility
- Transit traffic display in `rnstatus`
- `rnsconfig` utility
### Interfaceability
These efforts aim to expand the types of physical and virtual interfaces that Reticulum can natively use to transport data.
Binary file not shown.
Binary file not shown.
+1 -1
View File
@@ -1,4 +1,4 @@
# Sphinx build info version 1
# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done.
config: 4e1b5f77b3c7f5d0453477a09dcc765c
config: 7db11e7bddcb7f5be2875b70e9500abe
tags: 645f666f9bcd5a90fca523b33c5a78b7
+3
View File
@@ -22,6 +22,9 @@ Donations are gratefully accepted via the following channels:
Bitcoin:
3CPmacGm34qYvR6XWLVEJmi2aNe3PZqUuq
Liberapay:
https://liberapay.com/Reticulum/
Ko-Fi:
https://ko-fi.com/markqvist
+3 -3
View File
@@ -453,7 +453,7 @@ For exchanges of small amounts of information, Reticulum offers the *Packet* API
public signing key.
* | In case the packet is addressed to a *group* destination type, the packet will be encrypted with the
pre-shared AES-128 key associated with the destination. In case the packet is addressed to a *plain*
pre-shared AES-256 key associated with the destination. In case the packet is addressed to a *plain*
destination type, the payload data will not be encrypted. Neither of these two destination types can offer
forward secrecy. In general, it is recommended to always use the *single* destination type, unless it is
strictly necessary to use one of the others.
@@ -880,7 +880,7 @@ intentionally compromised or weakened clone. The utilised primitives are:
* Ephemeral keys derived from an ECDH key exchange on Curve25519
* AES-128 or AES-256 in CBC mode with PKCS7 padding
* AES-256 in CBC mode with PKCS7 padding
* HMAC using SHA256 for message authentication
@@ -892,7 +892,7 @@ intentionally compromised or weakened clone. The utilised primitives are:
* SHA-512
In the default installation configuration, the ``X25519``, ``Ed25519``, ``AES-128-CBC`` and ``AES-256-CBC``
In the default installation configuration, the ``X25519``, ``Ed25519`` and ``AES-256-CBC``
primitives are provided by `OpenSSL <https://www.openssl.org/>`_ (via the `PyCA/cryptography <https://github.com/pyca/cryptography>`_
package). The hashing functions ``SHA-256`` and ``SHA-512`` are provided by the standard
Python `hashlib <https://docs.python.org/3/library/hashlib.html>`_. The ``HKDF``, ``HMAC``,
+38 -11
View File
@@ -69,12 +69,12 @@ configuration file is created. The default configuration looks like this:
# If you enable Transport, your system will route traffic
# for other peers, pass announces and serve path requests.
# This should only be done for systems that are suited to
# act as transport nodes, ie. if they are stationary and
# This should be done for systems that are suited to act
# as transport nodes, ie. if they are stationary and
# always-on. This directive is optional and can be removed
# for brevity.
enable_transport = False
enable_transport = No
# By default, the first program to launch the Reticulum
@@ -91,12 +91,24 @@ configuration file is created. The default configuration looks like this:
# If you want to run multiple *different* shared instances
# on the same system, you will need to specify different
# shared instance ports for each. The defaults are given
# below, and again, these options can be left out if you
# don't need them.
# instance names for each. On platforms supporting domain
# sockets, this can be done with the instance_name option:
shared_instance_port = 37428
instance_control_port = 37429
instance_name = default
# Some platforms don't support domain sockets, and if that
# is the case, you can isolate different instances by
# specifying a unique set of ports for each:
# shared_instance_port = 37428
# instance_control_port = 37429
# If you want to explicitly use TCP for shared instance
# communication, instead of domain sockets, this is also
# possible, by using the following option:
# shared_instance_type = tcp
# On systems where running instances may not have access
@@ -110,13 +122,25 @@ configuration file is created. The default configuration looks like this:
# rpc_key = e5c032d3ec4e64a6aca9927ba8ab73336780f6d71790
# It is possible to allow remote management of Reticulum
# systems using the various built-in utilities, such as
# rnstatus and rnpath. You will need to specify one or
# more Reticulum Identity hashes for authenticating the
# queries from client programs. For this purpose, you can
# use existing identity files, or generate new ones with
# the rnid utility.
# enable_remote_management = yes
# remote_management_allowed = 9fb6d773498fb3feda407ed8ef2c3229, 2d882c5586e548d79b5af27bca1776dc
# You can configure Reticulum to panic and forcibly close
# if an unrecoverable interface error occurs, such as the
# hardware device for an interface disappearing. This is
# an optional directive, and can be left out for brevity.
# This behaviour is disabled by default.
panic_on_interface_error = No
# panic_on_interface_error = No
# When Transport is enabled, it is possible to allow the
@@ -127,7 +151,7 @@ configuration file is created. The default configuration looks like this:
# Transport Instance, and printed to the log at startup.
# Optional, and disabled by default.
respond_to_probes = No
# respond_to_probes = No
[logging]
@@ -331,12 +355,15 @@ Filter output to only show some interfaces:
-s SORT, --sort SORT sort interfaces by [rate, traffic, rx, tx, announces, arx, atx, held]
-r, --reverse reverse sorting
-j, --json output in JSON format
-R hash transport identity hash of remote instance to get status from
-R hash transport identity hash of remote instance to get status from (requires -i)
-i path path to identity used for remote management
-w seconds timeout before giving up on remote queries
-v, --verbose
.. note::
When using ``-R`` to query a remote transport instance, you must also specify ``-i`` with the path to a management identity file that is authorized for remote management on the target system.
The rnid Utility
====================
+4 -5
View File
@@ -35,10 +35,9 @@ runs well even on small single-board computers like the Pi Zero.
Current Status
==============
**Please know!** Reticulum should currently be considered beta software. All core protocol
features are implemented and functioning, but additions will probably occur as
real-world use is explored. *There will be bugs*. The API and wire-format can be
considered complete and stable at the moment, but could change if absolutely warranted.
All core protocol features are implemented and functioning, but additions will probably occur as
real-world use is explored. The API and wire-format can be considered complete and stable, but
could change if absolutely warranted.
What does Reticulum Offer?
@@ -68,7 +67,7 @@ What does Reticulum Offer?
* Ephemeral per-packet and link keys and derived from an ECDH key exchange on Curve25519
* AES-128 or AES-256 in CBC mode with PKCS7 padding
* AES-256 in CBC mode with PKCS7 padding
* HMAC using SHA256 for authentication
+1 -1
View File
@@ -1,6 +1,6 @@
var DOCUMENTATION_OPTIONS = {
URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'),
VERSION: '0.9.5 beta',
VERSION: '1.0.0',
LANGUAGE: 'en',
COLLAPSE_INDEX: false,
BUILDER: 'html',
+5 -5
View File
@@ -6,7 +6,7 @@
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="Support Reticulum" href="support.html" /><link rel="prev" title="Building Networks" href="networks.html" />
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29.dev1"/>
<title>Code Examples - Reticulum Network Stack 0.9.5 beta documentation</title>
<title>Code Examples - Reticulum Network Stack 1.0.0 documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=189ec851f9bb375a2509b67be1f64f0cf212b702" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
@@ -141,7 +141,7 @@
</label>
</div>
<div class="header-center">
<a href="index.html"><div class="brand">Reticulum Network Stack 0.9.5 beta documentation</div></a>
<a href="index.html"><div class="brand">Reticulum Network Stack 1.0.0 documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -167,7 +167,7 @@
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
</div>
<span class="sidebar-brand-text">Reticulum Network Stack 0.9.5 beta documentation</span>
<span class="sidebar-brand-text">Reticulum Network Stack 1.0.0 documentation</span>
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
@@ -1610,8 +1610,8 @@ the link has been established.</p>
<span id="example-request"></span><h2>Requests &amp; Responses<a class="headerlink" href="#requests-responses" title="Permalink to this heading">#</a></h2>
<p>The <em>Request</em> example explores sending requests and receiving responses.</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1">##########################################################</span>
<span class="c1"># This RNS example demonstrates how to set perform #</span>
<span class="c1"># requests and receive responses over a link. #</span>
<span class="c1"># This RNS example demonstrates how to perform requests #</span>
<span class="c1"># and receive responses over a link. #</span>
<span class="c1">##########################################################</span>
<span class="kn">import</span><span class="w"> </span><span class="nn">os</span>
+3 -3
View File
@@ -6,7 +6,7 @@
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" />
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29.dev1"/>
<title>An Explanation of Reticulum for Human Beings - Reticulum Network Stack 0.9.5 beta documentation</title>
<title>An Explanation of Reticulum for Human Beings - Reticulum Network Stack 1.0.0 documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=189ec851f9bb375a2509b67be1f64f0cf212b702" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
@@ -141,7 +141,7 @@
</label>
</div>
<div class="header-center">
<a href="index.html"><div class="brand">Reticulum Network Stack 0.9.5 beta documentation</div></a>
<a href="index.html"><div class="brand">Reticulum Network Stack 1.0.0 documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -167,7 +167,7 @@
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
</div>
<span class="sidebar-brand-text">Reticulum Network Stack 0.9.5 beta documentation</span>
<span class="sidebar-brand-text">Reticulum Network Stack 1.0.0 documentation</span>
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
+3 -3
View File
@@ -4,7 +4,7 @@
<meta name="viewport" content="width=device-width,initial-scale=1"/>
<meta name="color-scheme" content="light dark"><link rel="index" title="Index" href="#" /><link rel="search" title="Search" href="search.html" />
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29.dev1"/><title>Index - Reticulum Network Stack 0.9.5 beta documentation</title>
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29.dev1"/><title>Index - Reticulum Network Stack 1.0.0 documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=189ec851f9bb375a2509b67be1f64f0cf212b702" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
@@ -139,7 +139,7 @@
</label>
</div>
<div class="header-center">
<a href="index.html"><div class="brand">Reticulum Network Stack 0.9.5 beta documentation</div></a>
<a href="index.html"><div class="brand">Reticulum Network Stack 1.0.0 documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -165,7 +165,7 @@
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
</div>
<span class="sidebar-brand-text">Reticulum Network Stack 0.9.5 beta documentation</span>
<span class="sidebar-brand-text">Reticulum Network Stack 1.0.0 documentation</span>
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
+3 -3
View File
@@ -6,7 +6,7 @@
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="Using Reticulum on Your System" href="using.html" /><link rel="prev" title="What is Reticulum?" href="whatis.html" />
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29.dev1"/>
<title>Getting Started Fast - Reticulum Network Stack 0.9.5 beta documentation</title>
<title>Getting Started Fast - Reticulum Network Stack 1.0.0 documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=189ec851f9bb375a2509b67be1f64f0cf212b702" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
@@ -141,7 +141,7 @@
</label>
</div>
<div class="header-center">
<a href="index.html"><div class="brand">Reticulum Network Stack 0.9.5 beta documentation</div></a>
<a href="index.html"><div class="brand">Reticulum Network Stack 1.0.0 documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -167,7 +167,7 @@
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
</div>
<span class="sidebar-brand-text">Reticulum Network Stack 0.9.5 beta documentation</span>
<span class="sidebar-brand-text">Reticulum Network Stack 1.0.0 documentation</span>
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
+3 -3
View File
@@ -6,7 +6,7 @@
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="Configuring Interfaces" href="interfaces.html" /><link rel="prev" title="Understanding Reticulum" href="understanding.html" />
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29.dev1"/>
<title>Communications Hardware - Reticulum Network Stack 0.9.5 beta documentation</title>
<title>Communications Hardware - Reticulum Network Stack 1.0.0 documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=189ec851f9bb375a2509b67be1f64f0cf212b702" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
@@ -141,7 +141,7 @@
</label>
</div>
<div class="header-center">
<a href="index.html"><div class="brand">Reticulum Network Stack 0.9.5 beta documentation</div></a>
<a href="index.html"><div class="brand">Reticulum Network Stack 1.0.0 documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -167,7 +167,7 @@
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
</div>
<span class="sidebar-brand-text">Reticulum Network Stack 0.9.5 beta documentation</span>
<span class="sidebar-brand-text">Reticulum Network Stack 1.0.0 documentation</span>
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
+3 -3
View File
@@ -6,7 +6,7 @@
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="What is Reticulum?" href="whatis.html" />
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29.dev1"/>
<title>Reticulum Network Stack 0.9.5 beta documentation</title>
<title>Reticulum Network Stack 1.0.0 documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=189ec851f9bb375a2509b67be1f64f0cf212b702" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
@@ -141,7 +141,7 @@
</label>
</div>
<div class="header-center">
<a href="#"><div class="brand">Reticulum Network Stack 0.9.5 beta documentation</div></a>
<a href="#"><div class="brand">Reticulum Network Stack 1.0.0 documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -167,7 +167,7 @@
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
</div>
<span class="sidebar-brand-text">Reticulum Network Stack 0.9.5 beta documentation</span>
<span class="sidebar-brand-text">Reticulum Network Stack 1.0.0 documentation</span>
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
+3 -3
View File
@@ -6,7 +6,7 @@
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="Building Networks" href="networks.html" /><link rel="prev" title="Communications Hardware" href="hardware.html" />
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29.dev1"/>
<title>Configuring Interfaces - Reticulum Network Stack 0.9.5 beta documentation</title>
<title>Configuring Interfaces - Reticulum Network Stack 1.0.0 documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=189ec851f9bb375a2509b67be1f64f0cf212b702" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
@@ -141,7 +141,7 @@
</label>
</div>
<div class="header-center">
<a href="index.html"><div class="brand">Reticulum Network Stack 0.9.5 beta documentation</div></a>
<a href="index.html"><div class="brand">Reticulum Network Stack 1.0.0 documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -167,7 +167,7 @@
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
</div>
<span class="sidebar-brand-text">Reticulum Network Stack 0.9.5 beta documentation</span>
<span class="sidebar-brand-text">Reticulum Network Stack 1.0.0 documentation</span>
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
+3 -3
View File
@@ -6,7 +6,7 @@
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="Code Examples" href="examples.html" /><link rel="prev" title="Configuring Interfaces" href="interfaces.html" />
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29.dev1"/>
<title>Building Networks - Reticulum Network Stack 0.9.5 beta documentation</title>
<title>Building Networks - Reticulum Network Stack 1.0.0 documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=189ec851f9bb375a2509b67be1f64f0cf212b702" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
@@ -141,7 +141,7 @@
</label>
</div>
<div class="header-center">
<a href="index.html"><div class="brand">Reticulum Network Stack 0.9.5 beta documentation</div></a>
<a href="index.html"><div class="brand">Reticulum Network Stack 1.0.0 documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -167,7 +167,7 @@
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
</div>
<span class="sidebar-brand-text">Reticulum Network Stack 0.9.5 beta documentation</span>
<span class="sidebar-brand-text">Reticulum Network Stack 1.0.0 documentation</span>
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
Binary file not shown.
+10 -9
View File
@@ -6,7 +6,7 @@
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="prev" title="Support Reticulum" href="support.html" />
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29.dev1"/>
<title>API Reference - Reticulum Network Stack 0.9.5 beta documentation</title>
<title>API Reference - Reticulum Network Stack 1.0.0 documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=189ec851f9bb375a2509b67be1f64f0cf212b702" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
@@ -141,7 +141,7 @@
</label>
</div>
<div class="header-center">
<a href="index.html"><div class="brand">Reticulum Network Stack 0.9.5 beta documentation</div></a>
<a href="index.html"><div class="brand">Reticulum Network Stack 1.0.0 documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -167,7 +167,7 @@
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
</div>
<span class="sidebar-brand-text">Reticulum Network Stack 0.9.5 beta documentation</span>
<span class="sidebar-brand-text">Reticulum Network Stack 1.0.0 documentation</span>
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
@@ -228,7 +228,7 @@ This chapter lists and explains all classes exposed by the Reticulum Network Sta
<p id="api-reticulum"><h3> Reticulum </h3></p>
<dl class="py class">
<dt class="sig sig-object py" id="RNS.Reticulum">
<em class="property"><span class="pre">class</span><span class="w"> </span></em><span class="sig-prename descclassname"><span class="pre">RNS.</span></span><span class="sig-name descname"><span class="pre">Reticulum</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">configdir</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">loglevel</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">logdest</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">verbosity</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">require_shared_instance</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">False</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Reticulum" title="Permalink to this definition">#</a></dt>
<em class="property"><span class="pre">class</span><span class="w"> </span></em><span class="sig-prename descclassname"><span class="pre">RNS.</span></span><span class="sig-name descname"><span class="pre">Reticulum</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">configdir</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">loglevel</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">logdest</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">verbosity</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">require_shared_instance</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">False</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">shared_instance_type</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Reticulum" title="Permalink to this definition">#</a></dt>
<dd><p>This class is used to initialise access to Reticulum within a
program. You must create exactly one instance of this class before
carrying out any other RNS operations, such as creating destinations
@@ -817,7 +817,7 @@ proofs should be returned for received packets.</p>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Destination.register_request_handler">
<span class="sig-name descname"><span class="pre">register_request_handler</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">path</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">response_generator</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">allow</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">ALLOW_NONE</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">allowed_list</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Destination.register_request_handler" title="Permalink to this definition">#</a></dt>
<span class="sig-name descname"><span class="pre">register_request_handler</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">path</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">response_generator</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">allow</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">ALLOW_NONE</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">allowed_list</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">auto_compress</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">True</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Destination.register_request_handler" title="Permalink to this definition">#</a></dt>
<dd><p>Registers a request handler.</p>
<dl class="field-list simple">
<dt class="field-odd">Parameters<span class="colon">:</span></dt>
@@ -826,6 +826,7 @@ proofs should be returned for received packets.</p>
<li><p><strong>response_generator</strong> A function or method with the signature <em>response_generator(path, data, request_id, link_id, remote_identity, requested_at)</em> to be called. Whatever this funcion returns will be sent as a response to the requester. If the function returns <code class="docutils literal notranslate"><span class="pre">None</span></code>, no response will be sent.</p></li>
<li><p><strong>allow</strong> One of <code class="docutils literal notranslate"><span class="pre">RNS.Destination.ALLOW_NONE</span></code>, <code class="docutils literal notranslate"><span class="pre">RNS.Destination.ALLOW_ALL</span></code> or <code class="docutils literal notranslate"><span class="pre">RNS.Destination.ALLOW_LIST</span></code>. If <code class="docutils literal notranslate"><span class="pre">RNS.Destination.ALLOW_LIST</span></code> is set, the request handler will only respond to requests for identified peers in the supplied list.</p></li>
<li><p><strong>allowed_list</strong> A list of <em>bytes-like</em> <a class="reference internal" href="#api-identity"><span class="std std-ref">RNS.Identity</span></a> hashes.</p></li>
<li><p><strong>auto_compress</strong> If <code class="docutils literal notranslate"><span class="pre">True</span></code> or <code class="docutils literal notranslate"><span class="pre">False</span></code>, determines whether automatic compression of responses should be carried out. If set to an integer value, responses will only be auto-compressed if under this size in bytes. If omitted, the default compression settings will be followed.</p></li>
</ul>
</dd>
<dt class="field-even">Raises<span class="colon">:</span></dt>
@@ -1015,9 +1016,9 @@ they are addressed to a <code class="docutils literal notranslate"><span class="
<code class="docutils literal notranslate"><span class="pre">RNS.Destination.GROUP</span></code> destination or a <a class="reference internal" href="#api-link"><span class="std std-ref">RNS.Link</span></a>.</p>
<p>For <code class="docutils literal notranslate"><span class="pre">RNS.Destination.GROUP</span></code> destinations, Reticulum will use the
pre-shared key configured for the destination. All packets to group
destinations are encrypted with the same AES-128 key.</p>
destinations are encrypted with the same AES-256 key.</p>
<p>For <code class="docutils literal notranslate"><span class="pre">RNS.Destination.SINGLE</span></code> destinations, Reticulum will use a newly
derived ephemeral AES-128 key for every packet.</p>
derived ephemeral AES-256 key for every packet.</p>
<p>For <a class="reference internal" href="#api-link"><span class="std std-ref">RNS.Link</span></a> destinations, Reticulum will use per-link
ephemeral keys, and offers <strong>Forward Secrecy</strong>.</p>
<dl class="field-list simple">
@@ -1194,14 +1195,14 @@ and encrypted connectivity with the specified destination.</p>
<dl class="py attribute">
<dt class="sig sig-object py" id="RNS.Link.STALE_GRACE">
<span class="sig-name descname"><span class="pre">STALE_GRACE</span></span><em class="property"><span class="w"> </span><span class="p"><span class="pre">=</span></span><span class="w"> </span><span class="pre">2</span></em><a class="headerlink" href="#RNS.Link.STALE_GRACE" title="Permalink to this definition">#</a></dt>
<span class="sig-name descname"><span class="pre">STALE_GRACE</span></span><em class="property"><span class="w"> </span><span class="p"><span class="pre">=</span></span><span class="w"> </span><span class="pre">5</span></em><a class="headerlink" href="#RNS.Link.STALE_GRACE" title="Permalink to this definition">#</a></dt>
<dd><p>Grace period in seconds used in link timeout calculation.</p>
</dd></dl>
<dl class="py attribute">
<dt class="sig sig-object py" id="RNS.Link.KEEPALIVE">
<span class="sig-name descname"><span class="pre">KEEPALIVE</span></span><em class="property"><span class="w"> </span><span class="p"><span class="pre">=</span></span><span class="w"> </span><span class="pre">360</span></em><a class="headerlink" href="#RNS.Link.KEEPALIVE" title="Permalink to this definition">#</a></dt>
<dd><p>Interval for sending keep-alive packets on established links in seconds.</p>
<dd><p>Default interval for sending keep-alive packets on established links in seconds.</p>
</dd></dl>
<dl class="py attribute">
+3 -3
View File
@@ -4,7 +4,7 @@
<meta name="viewport" content="width=device-width,initial-scale=1"/>
<meta name="color-scheme" content="light dark"><link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="#" />
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29.dev1"/><title>Search - Reticulum Network Stack 0.9.5 beta documentation</title><link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29.dev1"/><title>Search - Reticulum Network Stack 1.0.0 documentation</title><link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=189ec851f9bb375a2509b67be1f64f0cf212b702" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo-extensions.css?digest=30d1aed668e5c3a91c3e3bf6a60b675221979f0e" />
@@ -138,7 +138,7 @@
</label>
</div>
<div class="header-center">
<a href="index.html"><div class="brand">Reticulum Network Stack 0.9.5 beta documentation</div></a>
<a href="index.html"><div class="brand">Reticulum Network Stack 1.0.0 documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -164,7 +164,7 @@
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
</div>
<span class="sidebar-brand-text">Reticulum Network Stack 0.9.5 beta documentation</span>
<span class="sidebar-brand-text">Reticulum Network Stack 1.0.0 documentation</span>
</a><form class="sidebar-search-container" method="get" action="#" role="search">
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
File diff suppressed because one or more lines are too long
+6 -3
View File
@@ -6,7 +6,7 @@
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="API Reference" href="reference.html" /><link rel="prev" title="Code Examples" href="examples.html" />
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29.dev1"/>
<title>Support Reticulum - Reticulum Network Stack 0.9.5 beta documentation</title>
<title>Support Reticulum - Reticulum Network Stack 1.0.0 documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=189ec851f9bb375a2509b67be1f64f0cf212b702" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
@@ -141,7 +141,7 @@
</label>
</div>
<div class="header-center">
<a href="index.html"><div class="brand">Reticulum Network Stack 0.9.5 beta documentation</div></a>
<a href="index.html"><div class="brand">Reticulum Network Stack 1.0.0 documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -167,7 +167,7 @@
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
</div>
<span class="sidebar-brand-text">Reticulum Network Stack 0.9.5 beta documentation</span>
<span class="sidebar-brand-text">Reticulum Network Stack 1.0.0 documentation</span>
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
@@ -237,6 +237,9 @@ Ethereum:
Bitcoin:
3CPmacGm34qYvR6XWLVEJmi2aNe3PZqUuq
Liberapay:
https://liberapay.com/Reticulum/
Ko-Fi:
https://ko-fi.com/markqvist
</pre></div>
+6 -6
View File
@@ -6,7 +6,7 @@
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="Communications Hardware" href="hardware.html" /><link rel="prev" title="Using Reticulum on Your System" href="using.html" />
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29.dev1"/>
<title>Understanding Reticulum - Reticulum Network Stack 0.9.5 beta documentation</title>
<title>Understanding Reticulum - Reticulum Network Stack 1.0.0 documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=189ec851f9bb375a2509b67be1f64f0cf212b702" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
@@ -141,7 +141,7 @@
</label>
</div>
<div class="header-center">
<a href="index.html"><div class="brand">Reticulum Network Stack 0.9.5 beta documentation</div></a>
<a href="index.html"><div class="brand">Reticulum Network Stack 1.0.0 documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -167,7 +167,7 @@
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
</div>
<span class="sidebar-brand-text">Reticulum Network Stack 0.9.5 beta documentation</span>
<span class="sidebar-brand-text">Reticulum Network Stack 1.0.0 documentation</span>
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
@@ -673,7 +673,7 @@ public signing key.</div>
</li>
<li><div class="line-block">
<div class="line">In case the packet is addressed to a <em>group</em> destination type, the packet will be encrypted with the
pre-shared AES-128 key associated with the destination. In case the packet is addressed to a <em>plain</em>
pre-shared AES-256 key associated with the destination. In case the packet is addressed to a <em>plain</em>
destination type, the payload data will not be encrypted. Neither of these two destination types can offer
forward secrecy. In general, it is recommended to always use the <em>single</em> destination type, unless it is
strictly necessary to use one of the others.</div>
@@ -1078,7 +1078,7 @@ intentionally compromised or weakened clone. The utilised primitives are:</p>
<li><p>Encrypted tokens are based on the Fernet spec</p>
<ul>
<li><p>Ephemeral keys derived from an ECDH key exchange on Curve25519</p></li>
<li><p>AES-128 or AES-256 in CBC mode with PKCS7 padding</p></li>
<li><p>AES-256 in CBC mode with PKCS7 padding</p></li>
<li><p>HMAC using SHA256 for message authentication</p></li>
<li><p>IVs must be generated through <code class="docutils literal notranslate"><span class="pre">os.urandom()</span></code> or better</p></li>
<li><p>No Fernet version and timestamp metadata fields</p></li>
@@ -1087,7 +1087,7 @@ intentionally compromised or weakened clone. The utilised primitives are:</p>
<li><p>SHA-256</p></li>
<li><p>SHA-512</p></li>
</ul>
<p>In the default installation configuration, the <code class="docutils literal notranslate"><span class="pre">X25519</span></code>, <code class="docutils literal notranslate"><span class="pre">Ed25519</span></code>, <code class="docutils literal notranslate"><span class="pre">AES-128-CBC</span></code> and <code class="docutils literal notranslate"><span class="pre">AES-256-CBC</span></code>
<p>In the default installation configuration, the <code class="docutils literal notranslate"><span class="pre">X25519</span></code>, <code class="docutils literal notranslate"><span class="pre">Ed25519</span></code> and <code class="docutils literal notranslate"><span class="pre">AES-256-CBC</span></code>
primitives are provided by <a class="reference external" href="https://www.openssl.org/">OpenSSL</a> (via the <a class="reference external" href="https://github.com/pyca/cryptography">PyCA/cryptography</a>
package). The hashing functions <code class="docutils literal notranslate"><span class="pre">SHA-256</span></code> and <code class="docutils literal notranslate"><span class="pre">SHA-512</span></code> are provided by the standard
Python <a class="reference external" href="https://docs.python.org/3/library/hashlib.html">hashlib</a>. The <code class="docutils literal notranslate"><span class="pre">HKDF</span></code>, <code class="docutils literal notranslate"><span class="pre">HMAC</span></code>,
+42 -14
View File
@@ -6,7 +6,7 @@
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="Understanding Reticulum" href="understanding.html" /><link rel="prev" title="Getting Started Fast" href="gettingstartedfast.html" />
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29.dev1"/>
<title>Using Reticulum on Your System - Reticulum Network Stack 0.9.5 beta documentation</title>
<title>Using Reticulum on Your System - Reticulum Network Stack 1.0.0 documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=189ec851f9bb375a2509b67be1f64f0cf212b702" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
@@ -141,7 +141,7 @@
</label>
</div>
<div class="header-center">
<a href="index.html"><div class="brand">Reticulum Network Stack 0.9.5 beta documentation</div></a>
<a href="index.html"><div class="brand">Reticulum Network Stack 1.0.0 documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -167,7 +167,7 @@
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
</div>
<span class="sidebar-brand-text">Reticulum Network Stack 0.9.5 beta documentation</span>
<span class="sidebar-brand-text">Reticulum Network Stack 1.0.0 documentation</span>
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
@@ -277,12 +277,12 @@ configuration file is created. The default configuration looks like this:</p>
<span class="c1"># If you enable Transport, your system will route traffic</span>
<span class="c1"># for other peers, pass announces and serve path requests.</span>
<span class="c1"># This should only be done for systems that are suited to</span>
<span class="c1"># act as transport nodes, ie. if they are stationary and</span>
<span class="c1"># This should be done for systems that are suited to act</span>
<span class="c1"># as transport nodes, ie. if they are stationary and</span>
<span class="c1"># always-on. This directive is optional and can be removed</span>
<span class="c1"># for brevity.</span>
<span class="n">enable_transport</span> <span class="o">=</span> <span class="kc">False</span>
<span class="n">enable_transport</span> <span class="o">=</span> <span class="n">No</span>
<span class="c1"># By default, the first program to launch the Reticulum</span>
@@ -299,12 +299,24 @@ configuration file is created. The default configuration looks like this:</p>
<span class="c1"># If you want to run multiple *different* shared instances</span>
<span class="c1"># on the same system, you will need to specify different</span>
<span class="c1"># shared instance ports for each. The defaults are given</span>
<span class="c1"># below, and again, these options can be left out if you</span>
<span class="c1"># don&#39;t need them.</span>
<span class="c1"># instance names for each. On platforms supporting domain</span>
<span class="c1"># sockets, this can be done with the instance_name option:</span>
<span class="n">shared_instance_port</span> <span class="o">=</span> <span class="mi">37428</span>
<span class="n">instance_control_port</span> <span class="o">=</span> <span class="mi">37429</span>
<span class="n">instance_name</span> <span class="o">=</span> <span class="n">default</span>
<span class="c1"># Some platforms don&#39;t support domain sockets, and if that</span>
<span class="c1"># is the case, you can isolate different instances by</span>
<span class="c1"># specifying a unique set of ports for each:</span>
<span class="c1"># shared_instance_port = 37428</span>
<span class="c1"># instance_control_port = 37429</span>
<span class="c1"># If you want to explicitly use TCP for shared instance</span>
<span class="c1"># communication, instead of domain sockets, this is also</span>
<span class="c1"># possible, by using the following option:</span>
<span class="c1"># shared_instance_type = tcp</span>
<span class="c1"># On systems where running instances may not have access</span>
@@ -318,13 +330,25 @@ configuration file is created. The default configuration looks like this:</p>
<span class="c1"># rpc_key = e5c032d3ec4e64a6aca9927ba8ab73336780f6d71790</span>
<span class="c1"># It is possible to allow remote management of Reticulum</span>
<span class="c1"># systems using the various built-in utilities, such as</span>
<span class="c1"># rnstatus and rnpath. You will need to specify one or</span>
<span class="c1"># more Reticulum Identity hashes for authenticating the</span>
<span class="c1"># queries from client programs. For this purpose, you can</span>
<span class="c1"># use existing identity files, or generate new ones with</span>
<span class="c1"># the rnid utility.</span>
<span class="c1"># enable_remote_management = yes</span>
<span class="c1"># remote_management_allowed = 9fb6d773498fb3feda407ed8ef2c3229, 2d882c5586e548d79b5af27bca1776dc</span>
<span class="c1"># You can configure Reticulum to panic and forcibly close</span>
<span class="c1"># if an unrecoverable interface error occurs, such as the</span>
<span class="c1"># hardware device for an interface disappearing. This is</span>
<span class="c1"># an optional directive, and can be left out for brevity.</span>
<span class="c1"># This behaviour is disabled by default.</span>
<span class="n">panic_on_interface_error</span> <span class="o">=</span> <span class="n">No</span>
<span class="c1"># panic_on_interface_error = No</span>
<span class="c1"># When Transport is enabled, it is possible to allow the</span>
@@ -335,7 +359,7 @@ configuration file is created. The default configuration looks like this:</p>
<span class="c1"># Transport Instance, and printed to the log at startup.</span>
<span class="c1"># Optional, and disabled by default.</span>
<span class="n">respond_to_probes</span> <span class="o">=</span> <span class="n">No</span>
<span class="c1"># respond_to_probes = No</span>
<span class="p">[</span><span class="n">logging</span><span class="p">]</span>
@@ -511,12 +535,16 @@ options:
-s SORT, --sort SORT sort interfaces by [rate, traffic, rx, tx, announces, arx, atx, held]
-r, --reverse reverse sorting
-j, --json output in JSON format
-R hash transport identity hash of remote instance to get status from
-R hash transport identity hash of remote instance to get status from (requires -i)
-i path path to identity used for remote management
-w seconds timeout before giving up on remote queries
-v, --verbose
</pre></div>
</div>
<div class="admonition note">
<p class="admonition-title">Note</p>
<p>When using <code class="docutils literal notranslate"><span class="pre">-R</span></code> to query a remote transport instance, you must also specify <code class="docutils literal notranslate"><span class="pre">-i</span></code> with the path to a management identity file that is authorized for remote management on the target system.</p>
</div>
</section>
<section id="the-rnid-utility">
<h3>The rnid Utility<a class="headerlink" href="#the-rnid-utility" title="Permalink to this heading">#</a></h3>
+7 -8
View File
@@ -6,7 +6,7 @@
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="Getting Started Fast" href="gettingstartedfast.html" /><link rel="prev" title="Reticulum Network Stack Manual" href="index.html" />
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29.dev1"/>
<title>What is Reticulum? - Reticulum Network Stack 0.9.5 beta documentation</title>
<title>What is Reticulum? - Reticulum Network Stack 1.0.0 documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=189ec851f9bb375a2509b67be1f64f0cf212b702" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
@@ -141,7 +141,7 @@
</label>
</div>
<div class="header-center">
<a href="index.html"><div class="brand">Reticulum Network Stack 0.9.5 beta documentation</div></a>
<a href="index.html"><div class="brand">Reticulum Network Stack 1.0.0 documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -167,7 +167,7 @@
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
</div>
<span class="sidebar-brand-text">Reticulum Network Stack 0.9.5 beta documentation</span>
<span class="sidebar-brand-text">Reticulum Network Stack 1.0.0 documentation</span>
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
@@ -249,10 +249,9 @@ userland, and will run on practically any system that runs Python 3. Reticulum
runs well even on small single-board computers like the Pi Zero.</p>
<section id="current-status">
<h2>Current Status<a class="headerlink" href="#current-status" title="Permalink to this heading">#</a></h2>
<p><strong>Please know!</strong> Reticulum should currently be considered beta software. All core protocol
features are implemented and functioning, but additions will probably occur as
real-world use is explored. <em>There will be bugs</em>. The API and wire-format can be
considered complete and stable at the moment, but could change if absolutely warranted.</p>
<p>All core protocol features are implemented and functioning, but additions will probably occur as
real-world use is explored. The API and wire-format can be considered complete and stable, but
could change if absolutely warranted.</p>
</section>
<section id="what-does-reticulum-offer">
<h2>What does Reticulum Offer?<a class="headerlink" href="#what-does-reticulum-offer" title="Permalink to this heading">#</a></h2>
@@ -279,7 +278,7 @@ considered complete and stable at the moment, but could change if absolutely war
<li><p>Reticulum uses the following format for encrypted tokens:</p>
<ul>
<li><p>Ephemeral per-packet and link keys and derived from an ECDH key exchange on Curve25519</p></li>
<li><p>AES-128 or AES-256 in CBC mode with PKCS7 padding</p></li>
<li><p>AES-256 in CBC mode with PKCS7 padding</p></li>
<li><p>HMAC using SHA256 for authentication</p></li>
<li><p>IVs are generated through os.urandom()</p></li>
</ul>
+1 -1
View File
@@ -18,7 +18,7 @@ version = __version__
# The full version, including alpha/beta/rc tags
import RNS
release = RNS._version.__version__+" beta"
release = RNS._version.__version__
# -- General configuration ---------------------------------------------------
extensions = [
+3
View File
@@ -22,6 +22,9 @@ Donations are gratefully accepted via the following channels:
Bitcoin:
3CPmacGm34qYvR6XWLVEJmi2aNe3PZqUuq
Liberapay:
https://liberapay.com/Reticulum/
Ko-Fi:
https://ko-fi.com/markqvist
+3 -3
View File
@@ -453,7 +453,7 @@ For exchanges of small amounts of information, Reticulum offers the *Packet* API
public signing key.
* | In case the packet is addressed to a *group* destination type, the packet will be encrypted with the
pre-shared AES-128 key associated with the destination. In case the packet is addressed to a *plain*
pre-shared AES-256 key associated with the destination. In case the packet is addressed to a *plain*
destination type, the payload data will not be encrypted. Neither of these two destination types can offer
forward secrecy. In general, it is recommended to always use the *single* destination type, unless it is
strictly necessary to use one of the others.
@@ -880,7 +880,7 @@ intentionally compromised or weakened clone. The utilised primitives are:
* Ephemeral keys derived from an ECDH key exchange on Curve25519
* AES-128 or AES-256 in CBC mode with PKCS7 padding
* AES-256 in CBC mode with PKCS7 padding
* HMAC using SHA256 for message authentication
@@ -892,7 +892,7 @@ intentionally compromised or weakened clone. The utilised primitives are:
* SHA-512
In the default installation configuration, the ``X25519``, ``Ed25519``, ``AES-128-CBC`` and ``AES-256-CBC``
In the default installation configuration, the ``X25519``, ``Ed25519`` and ``AES-256-CBC``
primitives are provided by `OpenSSL <https://www.openssl.org/>`_ (via the `PyCA/cryptography <https://github.com/pyca/cryptography>`_
package). The hashing functions ``SHA-256`` and ``SHA-512`` are provided by the standard
Python `hashlib <https://docs.python.org/3/library/hashlib.html>`_. The ``HKDF``, ``HMAC``,
+38 -11
View File
@@ -69,12 +69,12 @@ configuration file is created. The default configuration looks like this:
# If you enable Transport, your system will route traffic
# for other peers, pass announces and serve path requests.
# This should only be done for systems that are suited to
# act as transport nodes, ie. if they are stationary and
# This should be done for systems that are suited to act
# as transport nodes, ie. if they are stationary and
# always-on. This directive is optional and can be removed
# for brevity.
enable_transport = False
enable_transport = No
# By default, the first program to launch the Reticulum
@@ -91,12 +91,24 @@ configuration file is created. The default configuration looks like this:
# If you want to run multiple *different* shared instances
# on the same system, you will need to specify different
# shared instance ports for each. The defaults are given
# below, and again, these options can be left out if you
# don't need them.
# instance names for each. On platforms supporting domain
# sockets, this can be done with the instance_name option:
shared_instance_port = 37428
instance_control_port = 37429
instance_name = default
# Some platforms don't support domain sockets, and if that
# is the case, you can isolate different instances by
# specifying a unique set of ports for each:
# shared_instance_port = 37428
# instance_control_port = 37429
# If you want to explicitly use TCP for shared instance
# communication, instead of domain sockets, this is also
# possible, by using the following option:
# shared_instance_type = tcp
# On systems where running instances may not have access
@@ -110,13 +122,25 @@ configuration file is created. The default configuration looks like this:
# rpc_key = e5c032d3ec4e64a6aca9927ba8ab73336780f6d71790
# It is possible to allow remote management of Reticulum
# systems using the various built-in utilities, such as
# rnstatus and rnpath. You will need to specify one or
# more Reticulum Identity hashes for authenticating the
# queries from client programs. For this purpose, you can
# use existing identity files, or generate new ones with
# the rnid utility.
# enable_remote_management = yes
# remote_management_allowed = 9fb6d773498fb3feda407ed8ef2c3229, 2d882c5586e548d79b5af27bca1776dc
# You can configure Reticulum to panic and forcibly close
# if an unrecoverable interface error occurs, such as the
# hardware device for an interface disappearing. This is
# an optional directive, and can be left out for brevity.
# This behaviour is disabled by default.
panic_on_interface_error = No
# panic_on_interface_error = No
# When Transport is enabled, it is possible to allow the
@@ -127,7 +151,7 @@ configuration file is created. The default configuration looks like this:
# Transport Instance, and printed to the log at startup.
# Optional, and disabled by default.
respond_to_probes = No
# respond_to_probes = No
[logging]
@@ -331,12 +355,15 @@ Filter output to only show some interfaces:
-s SORT, --sort SORT sort interfaces by [rate, traffic, rx, tx, announces, arx, atx, held]
-r, --reverse reverse sorting
-j, --json output in JSON format
-R hash transport identity hash of remote instance to get status from
-R hash transport identity hash of remote instance to get status from (requires -i)
-i path path to identity used for remote management
-w seconds timeout before giving up on remote queries
-v, --verbose
.. note::
When using ``-R`` to query a remote transport instance, you must also specify ``-i`` with the path to a management identity file that is authorized for remote management on the target system.
The rnid Utility
====================
+4 -5
View File
@@ -35,10 +35,9 @@ runs well even on small single-board computers like the Pi Zero.
Current Status
==============
**Please know!** Reticulum should currently be considered beta software. All core protocol
features are implemented and functioning, but additions will probably occur as
real-world use is explored. *There will be bugs*. The API and wire-format can be
considered complete and stable at the moment, but could change if absolutely warranted.
All core protocol features are implemented and functioning, but additions will probably occur as
real-world use is explored. The API and wire-format can be considered complete and stable, but
could change if absolutely warranted.
What does Reticulum Offer?
@@ -68,7 +67,7 @@ What does Reticulum Offer?
* Ephemeral per-packet and link keys and derived from an ECDH key exchange on Curve25519
* AES-128 or AES-256 in CBC mode with PKCS7 padding
* AES-256 in CBC mode with PKCS7 padding
* HMAC using SHA256 for authentication
+3 -1
View File
@@ -34,10 +34,12 @@ setuptools.setup(
long_description_content_type="text/markdown",
url="https://reticulum.network/",
packages=packages,
license="Reticulum License",
license_files = ("LICENSE"),
classifiers=[
"Programming Language :: Python :: 3",
"License :: Reticulum License",
"Operating System :: OS Independent",
"Development Status :: 5 - Production/Stable",
],
entry_points= {
'console_scripts': [
+2 -8
View File
@@ -7,11 +7,8 @@ import os
signed_message = "e51a008b8b8ba855993d8892a40daad84a6fb69a7138e1b5f69b427fe03449826ab6ccb81f0d72b4725e8d55c814d3e8e151b495cf5b59702f197ec366d935ad04a98ca519d6964f96ea09910b020351d1cdff3befbad323a2a28a6ec7ced4d0d67f02c525f93b321d9b076d704408475bd2d123cd51916f7e49039246ac56add37ef87e32d7f9853ac44a7f77d26fedc83e4e67a45742b751c2599309f5eda6efa0dafd957f61af1f0e86c4d6c5052e0e5fa577db99846f2b7a0204c31cef4013ca51cb307506c9209fd18d0195a7c9ae628af1a1d9ee7a4cf30037ed190a9fdcaa4ce5bb7bea19803cb5b5cea8c21fdb98d8f73ff5aaad87f5f6c3b7bcfe8974e5b063cc1113d77b9e96bec1c9d10ed37b780c3f7349a34092bb3968daeced40eb0b5130c0d11595e30b9671896385d04289d067f671599386536eed8430a72e186fb95023d5ac5dd442443bfabfe13a84a38d060af73bf20f921f38a768672fdbcb1dfece7458166e2e15948d6b4fa81f42db48747d283c670f576a0b410b31a70d2594823d0e29135a488cb0408c9e5bc1e197ff99aef471924231ccc8e3eddc82dbcea4801f14c5fc7a389a26a52cc93cfe0770953ef595ff410b7033a6ed5c975dd922b3f48f9dffcfb412eeed5758f3aa51de7eb47cd2cb"
sig_from_key_0 = "3020ef58f861591826a61c3d2d4a25b949cdb3094085ba6b1177a6f2a05f3cdd24d1095d6fdd078f0b2826e80b261c93c1ff97fbfd4857f25706d57dd073590c"
encrypted_message_legacy = "71884a271ead43558fcf1e331c5aebcd43498f16da16f8056b0893ce6b15d521eaa4f31639cd34da1b57995944076c4f14f300f2d2612111d21a3429a9966ac1da68545c00c7887d8b26f6c1ab9defa020b9519849ca41b7904199882802b6542771df85144a79890289d3c02daef6c26652c5ce9de231a2"
fixed_token_legacy = "54d6ba347f3f2fe74fa52d6844a9090c049a6f437d7d151b9bd7db3e6785dd40286c451babda82660cbb4827517365b740675adf60d4b82778d7f7815a0e9818f2f2d3f15c0365e9d4f08df4f8261e5549c8c398e92bc66750fcd4ce7ea150f8a8761936341129e89afd22eaa57c303ccbe045d0b2fc7b8637946e16627419ef1fea0a0fef974c418a98af046d61e8e064f42c4948b0c81701106583c8f224329c0b475cb2168dc2e3fbf649edb79c58b7c839a509e146ec8d26589cb990c76c756fdefd0110410a6ab84fa3a722db74"
encrypted_message = "71884a271ead43558fcf1e331c5aebcd43498f16da16f8056b0893ce6b15d521eaa4f31639cd34da1b57995944076c4f14f300f2d2612111d21a3429a9966ac1da68545c00c7887d8b26f6c1ab9defa020b9519849ca41b7904199882802b6542771df85144a79890289d3c02daef6c26652c5ce9de231a2"
fixed_token = "e028a7d7b757241e53c65f20214365a74b83b4529e83291b391c614703d8a218708d1b47666c277c41cd9bb64b36ba5688801945df0b2470ea05e215eb9ffac8fad658f4d7fbc28688712daf9066e662b8f941cc766ee394e57b12eb629e3e807b3af9ec867170b5cd40fc17f29ff478fa28e419772020bd7c141f732851fb2397b150f30c2f9e347dabe01e80e5d857aaee7087d557f6027922fa814a67f417fba5e600975cf7502686d4112ba7814b9661067a7c900d5ed62f3125b126dc3c7ffea21d4702e6d205614b5da09e4b4d"
fixed_token = "e37705f9b432d3711acf028678b0b9d37fdf7e00a3b47c95251aad61447df2620b5b9978783c3d9f2fb762e68c8b57c554928fb70dd79c1033ce5865f91761aad3e992790f63456092cb69b7b045f539147f7ba10d480e300f193576ae2d75a7884809b76bd17e05a735383305c0aa5621395bbf51e8cc66c1c536f339f2bea600f08f8f9a76564b2522cd904b6c2b6e553ec3d4df718ae70434c734297b313539338d184d2c64a9c4ddbc9b9a4947d0b45f5a274f65ae9f6bb203562fd5cede6abd3c615b699156e08fa33b841647a0"
fixed_keys = [
("f8953ffaf607627e615603ff1530c82c434cf87c07179dd7689ea776f30b964cfb7ba6164af00c5111a45e69e57d885e1285f8dbfe3a21e95ae17cf676b0f8b7", "650b5d76b6bec0390d1f8cfca5bd33f9"),
@@ -155,11 +152,8 @@ class TestIdentity(unittest.TestCase):
fid = RNS.Identity.from_bytes(bytes.fromhex(fixed_keys[0][0]))
self.assertEqual(fid.hash, bytes.fromhex(fixed_keys[0][1]))
# Test decryption of known AES-128 token
plaintext = fid.decrypt(bytes.fromhex(fixed_token_legacy))
self.assertEqual(plaintext, bytes.fromhex(encrypted_message_legacy))
# Test decryption of known AES-256 token
print("Testing decryption of known token")
plaintext = fid.decrypt(bytes.fromhex(fixed_token))
self.assertEqual(plaintext, bytes.fromhex(encrypted_message))
+105 -22
View File
@@ -1,3 +1,6 @@
# import tracemalloc
# tracemalloc.start()
import unittest
import subprocess
@@ -118,16 +121,11 @@ class TestLink(unittest.TestCase):
time.sleep(LINK_UP_WAIT)
self.assertEqual(l1.status, RNS.Link.CLOSED)
exc_triggered = False
print("Testing AES_128_CBC mode link establishment...")
l2 = RNS.Link(dest, mode=RNS.Link.MODE_AES128_CBC)
time.sleep(LINK_UP_WAIT)
self.assertEqual(l2.status, RNS.Link.ACTIVE)
self.assertEqual(l2.mode, RNS.Link.MODE_AES128_CBC)
self.assertEqual(len(l2.derived_key), 32)
l2.teardown()
time.sleep(LINK_UP_WAIT)
self.assertEqual(l2.status, RNS.Link.CLOSED)
try: l2 = RNS.Link(dest, mode=RNS.Link.MODE_AES128_CBC)
except TypeError as e: exc_triggered = True
self.assertEqual(exc_triggered, True)
print("Testing AES_256_CBC mode link establishment...")
l3 = RNS.Link(dest, mode=RNS.Link.MODE_AES256_CBC)
@@ -320,7 +318,31 @@ class TestLink(unittest.TestCase):
t = time.time() - start
self.assertEqual(resource.status, RNS.Resource.COMPLETE)
print("Resource completed at "+self.size_str(resource_size/t, "b")+"ps")
print("Resource completed at "+self.size_str(resource.total_size/t, "b")+f"ps ({RNS.prettysize(resource.total_size)}, {RNS.prettyshorttime(t)})")
# Test sending resource with metadata
data = os.urandom(resource_size)
metadata = {"text": "Some text", "numbers": [1,2,3,4], "blob": os.urandom(32)}
print("Sending "+self.size_str(resource_size)+" resource with metadata...")
resource = RNS.Resource(data, l1, metadata=metadata, timeout=resource_timeout)
start = time.time()
# This is a hack, don't do it. Use the callbacks instead.
while resource.status < RNS.Resource.COMPLETE:
time.sleep(0.01)
t = time.time() - start
self.assertEqual(resource.status, RNS.Resource.COMPLETE)
print("Resource completed at "+self.size_str(resource.total_size/t, "b")+f"ps ({RNS.prettysize(resource.total_size)}, {RNS.prettyshorttime(t)})")
# Test sending resource with invalid size metadata
data = os.urandom(resource_size)
metadata = os.urandom(RNS.Resource.METADATA_MAX_SIZE+1)
print("Sending "+self.size_str(resource_size)+" resource with invalid size metadata...")
exception_raised = False
try: resource = RNS.Resource(data, l1, metadata=metadata, timeout=resource_timeout)
except SystemError: exception_raised = True
self.assertEqual(exception_raised, True)
l1.teardown()
time.sleep(0.5)
@@ -349,6 +371,8 @@ class TestLink(unittest.TestCase):
resource_timeout = 120
resource_size = 256*1000
# Test sending resource without metadata
data = os.urandom(resource_size)
print("Sending "+self.size_str(resource_size)+" resource...")
resource = RNS.Resource(data, l1, timeout=resource_timeout)
@@ -360,7 +384,22 @@ class TestLink(unittest.TestCase):
t = time.time() - start
self.assertEqual(resource.status, RNS.Resource.COMPLETE)
print("Resource completed at "+self.size_str(resource_size/t, "b")+"ps")
print("Resource completed at "+self.size_str(resource.total_size/t, "b")+f"ps ({RNS.prettysize(resource.total_size)}, {RNS.prettyshorttime(t)})")
# Test sending resource with metadata
data = os.urandom(resource_size)
metadata = {"text": "Some text", "numbers": [1,2,3,4], "blob": os.urandom(8192)}
print("Sending "+self.size_str(resource_size)+" resource with metadata...")
resource = RNS.Resource(data, l1, metadata=metadata, timeout=resource_timeout)
start = time.time()
# This is a hack, don't do it. Use the callbacks instead.
while resource.status < RNS.Resource.COMPLETE:
time.sleep(0.01)
t = time.time() - start
self.assertEqual(resource.status, RNS.Resource.COMPLETE)
print("Resource completed at "+self.size_str(resource.total_size/t, "b")+f"ps ({RNS.prettysize(resource.total_size)}, {RNS.prettyshorttime(t)})")
l1.teardown()
time.sleep(0.5)
@@ -399,7 +438,22 @@ class TestLink(unittest.TestCase):
t = time.time() - start
self.assertEqual(resource.status, RNS.Resource.COMPLETE)
print("Resource completed at "+self.size_str(resource_size/t, "b")+"ps")
print("Resource completed at "+self.size_str(resource.total_size/t, "b")+f"ps ({RNS.prettysize(resource.total_size)}, {RNS.prettyshorttime(t)})")
# Test sending resource with metadata
data = os.urandom(resource_size)
metadata = {"text": "Some text", "numbers": [1,2,3,4], "blob": os.urandom(8192)}
print("Sending "+self.size_str(resource_size)+" resource with metadata...")
resource = RNS.Resource(data, l1, metadata=metadata, timeout=resource_timeout)
start = time.time()
# This is a hack, don't do it. Use the callbacks instead.
while resource.status < RNS.Resource.COMPLETE:
time.sleep(0.01)
t = time.time() - start
self.assertEqual(resource.status, RNS.Resource.COMPLETE)
print("Resource completed at "+self.size_str(resource.total_size/t, "b")+f"ps ({RNS.prettysize(resource.total_size)}, {RNS.prettyshorttime(t)})")
l1.teardown()
time.sleep(LINK_UP_WAIT)
@@ -434,16 +488,31 @@ class TestLink(unittest.TestCase):
resource_size = 5*1000*1000
data = os.urandom(resource_size)
print("Sending "+self.size_str(resource_size)+" resource...")
resource = RNS.Resource(data, l1, timeout=resource_timeout, auto_compress=False)
resource = RNS.Resource(data, l1, timeout=resource_timeout, callback=self.lr_callback, auto_compress=False)
start = time.time()
# This is a hack, don't do it. Use the callbacks instead.
while resource.status < RNS.Resource.COMPLETE:
time.sleep(0.01)
TestLink.large_resource_status = resource.status
while TestLink.large_resource_status < RNS.Resource.COMPLETE:
time.sleep(0.001)
t = time.time() - start
self.assertEqual(resource.status, RNS.Resource.COMPLETE)
print("Resource completed at "+self.size_str(resource_size/t, "b")+"ps")
print("Resource completed at "+self.size_str(resource.total_size/t, "b")+f"ps ({RNS.prettysize(resource.total_size)}, {RNS.prettyshorttime(t)})")
# Test sending resource with metadata
data = os.urandom(resource_size)
metadata = {"text": "Some text", "numbers": [1,2,3,4], "blob": os.urandom(8192)}
print("Sending "+self.size_str(resource_size)+" resource with metadata...")
resource = RNS.Resource(data, l1, timeout=resource_timeout, callback=self.lr_callback, auto_compress=False)
start = time.time()
TestLink.large_resource_status = resource.status
while TestLink.large_resource_status < RNS.Resource.COMPLETE:
time.sleep(0.001)
t = time.time() - start
self.assertEqual(resource.status, RNS.Resource.COMPLETE)
print("Resource completed at "+self.size_str(resource.total_size/t, "b")+f"ps ({RNS.prettysize(resource.total_size)}, {RNS.prettyshorttime(t)})")
l1.teardown()
time.sleep(LINK_UP_WAIT)
@@ -486,17 +555,31 @@ class TestLink(unittest.TestCase):
TestLink.large_resource_status = resource.status
while TestLink.large_resource_status < RNS.Resource.COMPLETE:
time.sleep(0.01)
time.sleep(0.001)
t = time.time() - start
self.assertEqual(TestLink.large_resource_status, RNS.Resource.COMPLETE)
print("Resource completed at "+self.size_str(resource_size/t, "b")+"ps")
print("Resource completed at "+self.size_str(resource.total_size/t, "b")+f"ps ({RNS.prettysize(resource.total_size)}, {RNS.prettyshorttime(t)})")
# Test sending resource with metadata
metadata = {"text": "Some text", "numbers": [1,2,3,4], "blob": os.urandom(8192)}
print("Sending "+self.size_str(resource_size)+" resource with metadata...")
resource = RNS.Resource(data, l1, timeout=resource_timeout, callback=self.lr_callback, auto_compress=False)
start = time.time()
TestLink.large_resource_status = resource.status
while TestLink.large_resource_status < RNS.Resource.COMPLETE:
time.sleep(0.01)
t = time.time() - start
self.assertEqual(resource.status, RNS.Resource.COMPLETE)
print("Resource with metadata completed at "+self.size_str(resource.total_size/t, "b")+f"ps ({RNS.prettysize(resource.total_size)}, {RNS.prettyshorttime(t)})")
l1.teardown()
time.sleep(LINK_UP_WAIT)
self.assertEqual(l1.status, RNS.Link.CLOSED)
#@skipIf(os.getenv('SKIP_NORMAL_TESTS') != None, "Skipping")
@skipIf(os.getenv('SKIP_NORMAL_TESTS') != None, "Skipping")
def test_10_channel_round_trip(self):
global c_rns
init_rns(self)
@@ -548,7 +631,7 @@ class TestLink(unittest.TestCase):
self.assertEqual(l1.status, RNS.Link.CLOSED)
self.assertEqual(0, len(l1._channel._rx_ring))
# @skipIf(os.getenv('SKIP_NORMAL_TESTS') != None, "Skipping")
@skipIf(os.getenv('SKIP_NORMAL_TESTS') != None, "Skipping")
def test_11_buffer_round_trip(self):
global c_rns
init_rns(self)
@@ -594,7 +677,7 @@ class TestLink(unittest.TestCase):
time.sleep(LINK_UP_WAIT)
self.assertEqual(l1.status, RNS.Link.CLOSED)
# @skipIf(os.getenv('SKIP_NORMAL_TESTS') != None and os.getenv('RUN_SLOW_TESTS') == None, "Skipping")
@skipIf(os.getenv('SKIP_NORMAL_TESTS') != None and os.getenv('RUN_SLOW_TESTS') == None, "Skipping")
def test_12_buffer_round_trip_big(self, local_bitrate = None):
global c_rns, buffer_read_target
init_rns(self)