Compare commits
122 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fd13e20165 | |||
| 66ce58f0f4 | |||
| e8ee26f78d | |||
| c0fb419fe1 | |||
| 4ef369cdd8 | |||
| a2f18b1daf | |||
| 2e411fa1de | |||
| 549dc40be6 | |||
| 1a99597f4d | |||
| b21e0bee20 | |||
| be8389a906 | |||
| 4ca00c6973 | |||
| 95f81cab7f | |||
| 60917f0eea | |||
| de800f0ea7 | |||
| 5dad76879c | |||
| 75c3180933 | |||
| 4c6ba97dca | |||
| cd6427cc9d | |||
| 1749393732 | |||
| dcde5035b9 | |||
| c14f6aa14a | |||
| 77fe621cba | |||
| 129b1d0713 | |||
| 161eeca509 | |||
| f25906d44e | |||
| dd5133751e | |||
| 5f8a55b702 | |||
| 7991db5c74 | |||
| f5510f9777 | |||
| 05e0b17fbf | |||
| 7e9d608530 | |||
| 3d4ac0126b | |||
| 81cdb0b7e6 | |||
| c71660a9c3 | |||
| 9c1ac46989 | |||
| c5b792f64a | |||
| 76d75e9a3e | |||
| 9edb641058 | |||
| 1bc2d4015e | |||
| ab4f3ad8ae | |||
| 16dae81844 | |||
| e9e2ffbe0d | |||
| dc36644a1e | |||
| 8436bc5ba3 | |||
| 858d54f90d | |||
| 9323fd22ee | |||
| 544e15afdf | |||
| acae9e34c2 | |||
| aaf0ace027 | |||
| d8b76b4bc5 | |||
| d29ff38a05 | |||
| 65e8487b39 | |||
| 6362e04567 | |||
| 711b754dcc | |||
| 1351316f17 | |||
| 7af14cec84 | |||
| 0687ee2231 | |||
| 872075a31e | |||
| d8f0380aa9 | |||
| 569f9bd2b1 | |||
| 450b88d0f0 | |||
| 1cb9df109a | |||
| 80455c9614 | |||
| c1e280d896 | |||
| 4a2925cdea | |||
| 7f38c32e90 | |||
| 8646be0dcf | |||
| 6b3cc07740 | |||
| 3b57b0013b | |||
| 24d8f39dd1 | |||
| 58e4bf3c80 | |||
| 1da8a0c8f1 | |||
| 8b8d4410ef | |||
| 7d804daa8f | |||
| ce00822cb0 | |||
| 6d6c91edaf | |||
| 8432cf40c2 | |||
| 5e21bdd233 | |||
| c7e5f4612a | |||
| f80e09cb5c | |||
| 91d94f2f6f | |||
| 53ca86ebfc | |||
| 534bb28900 | |||
| 0de5ec73ad | |||
| c0f627b50b | |||
| 5629a062a5 | |||
| 83232f0446 | |||
| aa794514b3 | |||
| 07cf180ea8 | |||
| 42a3d23e99 | |||
| d28c888d1c | |||
| 58d48c18f4 | |||
| ecf0c55fd6 | |||
| 32e4c262ef | |||
| f87a6a57df | |||
| 6373f159f8 | |||
| ad9f548eeb | |||
| 425f0153d0 | |||
| cd9daaefee | |||
| 0fe76d50f6 | |||
| 9562803bb3 | |||
| e9c89209c7 | |||
| cd8de64201 | |||
| 40f7a6d359 | |||
| 0c96508cca | |||
| 1fd59f1a02 | |||
| 0a0d0af821 | |||
| b694cbdc91 | |||
| 71c3333e10 | |||
| 972fcdee22 | |||
| 17dbfe6401 | |||
| 781cb4712d | |||
| cdb08325cc | |||
| 62d954d7bf | |||
| 4bbf1ae57d | |||
| 2678aeb6a1 | |||
| 6d441dac02 | |||
| 66b2be87f4 | |||
| 2e7126ef39 | |||
| c0f909850b | |||
| a199e4c929 |
@@ -358,7 +358,7 @@ def print_menu():
|
||||
print("")
|
||||
while menu_mode == "downloading":
|
||||
global current_download
|
||||
percent = round(current_download.progress() * 100.0, 1)
|
||||
percent = round(current_download.get_progress() * 100.0, 1)
|
||||
print(("\rProgress: "+str(percent)+" % "), end=' ')
|
||||
sys.stdout.flush()
|
||||
time.sleep(0.1)
|
||||
|
||||
@@ -0,0 +1,337 @@
|
||||
##########################################################
|
||||
# This RNS example demonstrates a simple speedtest #
|
||||
# program to measure link throughput. #
|
||||
##########################################################
|
||||
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import argparse
|
||||
import RNS
|
||||
|
||||
# Let's define an app name. We'll use this for all
|
||||
# destinations we create.
|
||||
APP_NAME = "example_utilities"
|
||||
|
||||
##########################################################
|
||||
#### Server Part #########################################
|
||||
##########################################################
|
||||
|
||||
latest_client_link = None
|
||||
first_packet_at = None
|
||||
last_packet_at = None
|
||||
received_data = 0
|
||||
rc = 0
|
||||
data_cap = 2*1024*1024
|
||||
printed = False
|
||||
|
||||
# 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,
|
||||
"speedtest"
|
||||
)
|
||||
|
||||
# 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 requests or user input
|
||||
server_loop(server_destination)
|
||||
|
||||
def server_loop(destination):
|
||||
# Let the user know that everything is ready
|
||||
RNS.log(
|
||||
"Speedtest "+
|
||||
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, first_packet_at, rc
|
||||
|
||||
RNS.log("Client connected")
|
||||
first_packet_at = time.time()
|
||||
rc = 0
|
||||
link.set_link_closed_callback(client_disconnected)
|
||||
link.set_packet_callback(server_packet_received)
|
||||
latest_client_link = link
|
||||
|
||||
def client_disconnected(link):
|
||||
RNS.log("Client disconnected")
|
||||
|
||||
|
||||
# A convenience function for printing a human-
|
||||
# readable file size
|
||||
def size_str(num, suffix='B'):
|
||||
units = ['','Ki','Mi','Gi','Ti','Pi','Ei','Zi']
|
||||
last_unit = 'Yi'
|
||||
|
||||
if suffix == 'b':
|
||||
num *= 8
|
||||
units = ['','K','M','G','T','P','E','Z']
|
||||
last_unit = 'Y'
|
||||
|
||||
for unit in units:
|
||||
if abs(num) < 1024.0:
|
||||
return "%3.2f %s%s" % (num, unit, suffix)
|
||||
num /= 1024.0
|
||||
return "%.2f %s%s" % (num, last_unit, suffix)
|
||||
|
||||
|
||||
def server_packet_received(message, packet):
|
||||
global latest_client_link, first_packet_at, last_packet_at, received_data, rc, data_cap
|
||||
|
||||
received_data += len(packet.data)
|
||||
|
||||
rc += 1
|
||||
if rc >= 50:
|
||||
RNS.log(size_str(received_data))
|
||||
rc = 0
|
||||
|
||||
if received_data > data_cap:
|
||||
rcv_d = received_data
|
||||
received_data = 0
|
||||
rc = 0
|
||||
|
||||
last_packet_at = time.time()
|
||||
|
||||
# Print statistics
|
||||
download_time = last_packet_at-first_packet_at
|
||||
hours, rem = divmod(download_time, 3600)
|
||||
minutes, seconds = divmod(rem, 60)
|
||||
timestring = "{:0>2}:{:0>2}:{:05.2f}".format(int(hours),int(minutes),seconds)
|
||||
|
||||
print("")
|
||||
print("")
|
||||
print("--- Statistics -----")
|
||||
print("\tTime taken : "+timestring)
|
||||
print("\tData transferred : "+size_str(rcv_d))
|
||||
print("\tTransfer rate : "+size_str(rcv_d/download_time, suffix='b')+"/s")
|
||||
print("")
|
||||
|
||||
sys.stdout.flush()
|
||||
latest_client_link.teardown()
|
||||
time.sleep(0.2)
|
||||
rc = 0
|
||||
received_data = 0
|
||||
# latest_client_link.teardown()
|
||||
# os._exit(0)
|
||||
|
||||
|
||||
##########################################################
|
||||
#### Client Part #########################################
|
||||
##########################################################
|
||||
|
||||
# A reference to the server link
|
||||
server_link = None
|
||||
|
||||
# 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:
|
||||
if len(destination_hexhash) != 20:
|
||||
raise ValueError("Destination length is invalid, must be 20 hexadecimal characters (10 bytes)")
|
||||
destination_hash = bytes.fromhex(destination_hexhash)
|
||||
except:
|
||||
RNS.log("Invalid destination entered. Check your input!\n")
|
||||
exit()
|
||||
|
||||
# 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,
|
||||
"speedtest"
|
||||
)
|
||||
|
||||
# And create a link
|
||||
link = RNS.Link(server_destination)
|
||||
|
||||
# We'll also 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:
|
||||
text = input()
|
||||
|
||||
# Check if we should quit the example
|
||||
if text == "quit" or text == "q" or text == "exit":
|
||||
should_quit = True
|
||||
server_link.teardown()
|
||||
|
||||
except Exception as e:
|
||||
raise e
|
||||
|
||||
# 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, data_cap, printed
|
||||
server_link = link
|
||||
data_sent = 0
|
||||
|
||||
# Inform the user that the server is
|
||||
# connected
|
||||
RNS.log("Link established with server,sending...")
|
||||
rd = os.urandom(RNS.Link.MDU)
|
||||
started = time.time()
|
||||
while link.status == RNS.Link.ACTIVE and data_sent < data_cap*1.25:
|
||||
RNS.Packet(server_link, rd, create_receipt=False).send()
|
||||
data_sent += len(rd)
|
||||
|
||||
if data_sent > data_cap and not printed:
|
||||
printed = True
|
||||
ended = time.time()
|
||||
# Print statistics
|
||||
download_time = ended-started
|
||||
hours, rem = divmod(download_time, 3600)
|
||||
minutes, seconds = divmod(rem, 60)
|
||||
timestring = "{:0>2}:{:0>2}:{:05.2f}".format(int(hours),int(minutes),seconds)
|
||||
print("")
|
||||
print("")
|
||||
print("--- Statistics -----")
|
||||
print("\tTime taken : "+timestring)
|
||||
print("\tData transferred : "+size_str(data_sent))
|
||||
print("\tTransfer rate : "+size_str(data_sent/download_time, suffix='b')+"/s")
|
||||
print("")
|
||||
|
||||
sys.stdout.flush()
|
||||
time.sleep(0.1)
|
||||
|
||||
|
||||
# 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")
|
||||
|
||||
RNS.Reticulum.exit_handler()
|
||||
|
||||
time.sleep(1.5)
|
||||
os._exit(0)
|
||||
|
||||
def client_packet_received(message, packet):
|
||||
pass
|
||||
|
||||
##########################################################
|
||||
#### 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="Speedtest example")
|
||||
|
||||
parser.add_argument(
|
||||
"-s",
|
||||
"--server",
|
||||
action="store_true",
|
||||
help="wait for incoming requests 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("")
|
||||
exit()
|
||||
@@ -3,7 +3,7 @@ Reticulum Network Stack β
|
||||
|
||||
Reticulum is a cryptography-based networking stack for wide-area networks built on readily available hardware, and can operate even with very high latency and extremely low bandwidth. Reticulum allows you to build very wide-area networks with off-the-shelf tools, and offers end-to-end encryption, autoconfiguring cryptographically backed multi-hop transport, efficient addressing, unforgeable packet acknowledgements and more.
|
||||
|
||||
Reticulum is a complete networking stack, and does not use IP or higher layers, although it is easy to utilise IP (with TCP or UDP) as the underlying carrier for Reticulum. It is therefore trivial to tunnel Reticulum over the Internet or private IP networks.
|
||||
Reticulum is a complete networking stack, and does not need IP or higher layers, although it is easy to use IP (with TCP or UDP) as the underlying carrier for Reticulum. It is therefore trivial to tunnel Reticulum over the Internet or private IP networks.
|
||||
|
||||
Having no dependencies on traditional networking stacks free up overhead that has been utilised to implement a networking stack built directly on cryptographic principles, allowing resilience and stable functionality in open and trustless networks.
|
||||
|
||||
@@ -18,13 +18,14 @@ For more info, see [unsigned.io/projects/reticulum](https://unsigned.io/projects
|
||||
## Notable Features
|
||||
- Coordination-less globally unique adressing and identification
|
||||
- Fully self-configuring multi-hop routing
|
||||
- Complete initiator anonymity, communicate without revealing your identity
|
||||
- Asymmetric X25519 encryption and Ed25519 signatures as a basis for all communication
|
||||
- Forward Secrecy with ephemereal Elliptic Curve Diffie-Hellman keys on Curve25519
|
||||
- Reticulum uses the [Fernet](https://github.com/fernet/spec/blob/master/Spec.md) specification for encryption
|
||||
- Reticulum uses the [Fernet](https://github.com/fernet/spec/blob/master/Spec.md) specification for on-the-wire / over-the-air encryption
|
||||
- Keys are ephemeral and derived from an ECDH key exchange on Curve25519
|
||||
- AES-128 in CBC mode with PKCS7 padding
|
||||
- HMAC using SHA256 for authentication
|
||||
- IVs are generated through os.urandom()
|
||||
- Keys are ephemeral and derived from an ECDH key exchange on Curve25519
|
||||
- Unforgeable packet delivery confirmations
|
||||
- A variety of supported interface types
|
||||
- An intuitive and easy-to-use API
|
||||
@@ -34,9 +35,15 @@ For more info, see [unsigned.io/projects/reticulum](https://unsigned.io/projects
|
||||
- The API is very easy to use, and provides transfer progress
|
||||
- Lightweight, flexible and expandable Request/Response mechanism
|
||||
- Efficient link establishment
|
||||
- Total bandwidth cost of setting up a link is 3 packets totalling 240 bytes
|
||||
- Total bandwidth cost of setting up a link is 3 packets totalling 237 bytes
|
||||
- Low cost of keeping links open at only 0.62 bits per second
|
||||
|
||||
## Examples of Reticulum Applications
|
||||
If you want to quickly get an idea of what Reticulum can do, take a look at the following resources.
|
||||
|
||||
- For an off-grid, encrypted and resilient mesh communications platform, see [Nomad Network](https://github.com/markqvist/NomadNet)
|
||||
- For a distributed, delay and disruption tolerant message transfer protocol built on Reticulum, see [LXMF](https://github.com/markqvist/lxmf)
|
||||
|
||||
## Where can Reticulum be used?
|
||||
Over practically any medium that can support at least a half-duplex channel with 1.000 bits per second throughput, and an MTU of 500 bytes. Data radios, modems, LoRa radios, serial lines, AX.25 TNCs, amateur radio digital modes, ad-hoc WiFi, free-space optical links and similar systems are all examples of the types of interfaces Reticulum was designed for.
|
||||
|
||||
@@ -60,14 +67,19 @@ Reticulum implements a range of generalised interface types that covers most of
|
||||
- TCP over IP networks
|
||||
- UDP over IP networks
|
||||
|
||||
## What is currently being worked on?
|
||||
- API documentation
|
||||
- Useful example programs and utilities
|
||||
- A delay and disruption tolerant message transfer protocol built on Reticulum, see [LXMF](https://github.com/markqvist/lxmf)
|
||||
- A few useful-in-the-real-world apps built with Reticulum
|
||||
|
||||
## Can I use Reticulum on amateur radio spectrum?
|
||||
Some countries still ban the use of encryption when operating under an amateur radio license. Reticulum offers several encryptionless modes, while still using cryptographic principles for station verification, link establishment, data integrity verification, acknowledgements and routing. It is therefore perfectly possible to include Reticulum in amateur radio use, even if your country bans encryption.
|
||||
|
||||
## Feature Roadmap
|
||||
- Stream mode for links
|
||||
- More interface types for even broader compatibility
|
||||
- ESP32 devices (ESP-Now, Bluetooth, etc.)
|
||||
- More LoRa transceivers
|
||||
- AT-compatible modems
|
||||
- CAN-bus
|
||||
- ZeroMQ
|
||||
- MQTT
|
||||
- SPI
|
||||
- i²c
|
||||
|
||||
## Dependencies:
|
||||
- Python 3
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import base64
|
||||
import math
|
||||
import time
|
||||
import RNS
|
||||
|
||||
from cryptography.fernet import Fernet
|
||||
@@ -22,7 +23,7 @@ class Destination:
|
||||
encrypted communication with it.
|
||||
|
||||
:param identity: An instance of :ref:`RNS.Identity<api-identity>`. Can hold only public keys for an outgoing destination, or holding private keys for an ingoing.
|
||||
:param direction: ``RNS.Destination.IN`` or ``RNS.Destination.OUT``
|
||||
:param direction: ``RNS.Destination.IN`` or ``RNS.Destination.OUT``.
|
||||
:param type: ``RNS.Destination.SINGLE``, ``RNS.Destination.GROUP`` or ``RNS.Destination.PLAIN``.
|
||||
:param app_name: A string specifying the app name.
|
||||
:param \*aspects: Any non-zero number of string arguments.
|
||||
@@ -146,7 +147,7 @@ class Destination:
|
||||
:param path_response: Internal flag used by :ref:`RNS.Transport<api-transport>`. Ignore.
|
||||
"""
|
||||
destination_hash = self.hash
|
||||
random_hash = RNS.Identity.get_random_hash()
|
||||
random_hash = RNS.Identity.get_random_hash()[0:5]+int(time.time()).to_bytes(5, "big")
|
||||
|
||||
if app_data == None and self.default_app_data != None:
|
||||
if isinstance(self.default_app_data, bytes):
|
||||
|
||||
@@ -34,12 +34,13 @@ class Identity:
|
||||
"""
|
||||
|
||||
# Non-configurable constants
|
||||
AES_HMAC_OVERHEAD = 58 # In bytes
|
||||
FERNET_VERSION = 0x80
|
||||
FERNET_OVERHEAD = 54 # In bytes
|
||||
AES128_BLOCKSIZE = 16 # In bytes
|
||||
HASHLENGTH = 256 # In bits
|
||||
SIGLENGTH = KEYSIZE # In bits
|
||||
|
||||
TRUNCATED_HASHLENGTH = 80 # In bits
|
||||
TRUNCATED_HASHLENGTH = RNS.Reticulum.TRUNCATED_HASHLENGTH
|
||||
"""
|
||||
Constant specifying the truncated hash length (in bits) used by Reticulum
|
||||
for addressable hashes and other purposes. Non-configurable.
|
||||
@@ -64,16 +65,13 @@ class Identity:
|
||||
:param destination_hash: Destination hash as *bytes*.
|
||||
:returns: An :ref:`RNS.Identity<api-identity>` instance that can be used to create an outgoing :ref:`RNS.Destination<api-destination>`, or *None* if the destination is unknown.
|
||||
"""
|
||||
RNS.log("Searching for "+RNS.prettyhexrep(destination_hash)+"...", RNS.LOG_EXTREME)
|
||||
if destination_hash in Identity.known_destinations:
|
||||
identity_data = Identity.known_destinations[destination_hash]
|
||||
identity = Identity(create_keys=False)
|
||||
identity.load_public_key(identity_data[2])
|
||||
identity.app_data = identity_data[3]
|
||||
RNS.log("Found "+RNS.prettyhexrep(destination_hash)+" in known destinations", RNS.LOG_EXTREME)
|
||||
return identity
|
||||
else:
|
||||
RNS.log("Could not find "+RNS.prettyhexrep(destination_hash)+" in known destinations", RNS.LOG_EXTREME)
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
@@ -84,22 +82,35 @@ class Identity:
|
||||
:param destination_hash: Destination hash as *bytes*.
|
||||
:returns: *Bytes* containing app_data, or *None* if the destination is unknown.
|
||||
"""
|
||||
RNS.log("Searching for app_data for "+RNS.prettyhexrep(destination_hash)+"...", RNS.LOG_EXTREME)
|
||||
if destination_hash in Identity.known_destinations:
|
||||
app_data = Identity.known_destinations[destination_hash][3]
|
||||
RNS.log("Found "+RNS.prettyhexrep(destination_hash)+" app_data in known destinations", RNS.LOG_EXTREME)
|
||||
return app_data
|
||||
else:
|
||||
RNS.log("Could not find "+RNS.prettyhexrep(destination_hash)+" app_data in known destinations", RNS.LOG_EXTREME)
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def save_known_destinations():
|
||||
RNS.log("Saving known destinations to storage...", RNS.LOG_VERBOSE)
|
||||
file = open(RNS.Reticulum.storagepath+"/known_destinations","wb")
|
||||
umsgpack.dump(Identity.known_destinations, file)
|
||||
file.close()
|
||||
RNS.log("Done saving known destinations to storage", RNS.LOG_VERBOSE)
|
||||
try:
|
||||
storage_known_destinations = {}
|
||||
if os.path.isfile(RNS.Reticulum.storagepath+"/known_destinations"):
|
||||
try:
|
||||
file = open(RNS.Reticulum.storagepath+"/known_destinations","rb")
|
||||
storage_known_destinations = umsgpack.load(file)
|
||||
file.close()
|
||||
except:
|
||||
pass
|
||||
|
||||
for destination_hash in storage_known_destinations:
|
||||
if not destination_hash in Identity.known_destinations:
|
||||
Identity.known_destinations[destination_hash] = storage_known_destinations[destination_hash]
|
||||
|
||||
RNS.log("Saving known destinations to storage...", RNS.LOG_VERBOSE)
|
||||
file = open(RNS.Reticulum.storagepath+"/known_destinations","wb")
|
||||
umsgpack.dump(Identity.known_destinations, file)
|
||||
file.close()
|
||||
RNS.log("Done saving known destinations to storage", RNS.LOG_VERBOSE)
|
||||
except Exception as e:
|
||||
RNS.log("Error while saving known destinations to disk, the contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
|
||||
@staticmethod
|
||||
def load_known_destinations():
|
||||
@@ -112,7 +123,7 @@ class Identity:
|
||||
except:
|
||||
RNS.log("Error loading known destinations from disk, file will be recreated on exit", RNS.LOG_ERROR)
|
||||
else:
|
||||
RNS.log("Destinations file does not exist, so no known destinations loaded", RNS.LOG_VERBOSE)
|
||||
RNS.log("Destinations file does not exist, no known destinations loaded", RNS.LOG_VERBOSE)
|
||||
|
||||
@staticmethod
|
||||
def full_hash(data):
|
||||
@@ -182,6 +193,22 @@ class Identity:
|
||||
Identity.save_known_destinations()
|
||||
|
||||
|
||||
@staticmethod
|
||||
def from_bytes(prv_bytes):
|
||||
"""
|
||||
Create a new :ref:`RNS.Identity<api-identity>` instance from *bytes* of private key.
|
||||
Can be used to load previously created and saved identities into Reticulum.
|
||||
|
||||
:param prv_bytes: The *bytes* of private a saved private key. **HAZARD!** Never use this to generate a new key by feeding random data in prv_bytes.
|
||||
:returns: A :ref:`RNS.Identity<api-identity>` instance, or *None* if the *bytes* data was invalid.
|
||||
"""
|
||||
identity = Identity(create_keys=False)
|
||||
if identity.load_private_key(prv_bytes):
|
||||
return identity
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
@staticmethod
|
||||
def from_file(path):
|
||||
"""
|
||||
@@ -197,21 +224,23 @@ class Identity:
|
||||
else:
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def from_bytes(prv_bytes):
|
||||
def to_file(self, path):
|
||||
"""
|
||||
Create a new :ref:`RNS.Identity<api-identity>` instance from *bytes* of private key.
|
||||
Can be used to load previously created and saved identities into Reticulum.
|
||||
Saves the identity to a file. This will write the private key to disk,
|
||||
and anyone with access to this file will be able to decrypt all
|
||||
communication for the identity. Be very careful with this method.
|
||||
|
||||
:param prv_bytes: The *bytes* of private a saved private key. **HAZARD!** Never not use this to generate a new key by feeding random data in prv_bytes.
|
||||
:returns: A :ref:`RNS.Identity<api-identity>` instance, or *None* if the *bytes* data was invalid.
|
||||
:param path: The full path specifying where to save the identity.
|
||||
:returns: True if the file was saved, otherwise False.
|
||||
"""
|
||||
identity = Identity(create_keys=False)
|
||||
if identity.load_private_key(prv_bytes):
|
||||
return identity
|
||||
else:
|
||||
return None
|
||||
|
||||
try:
|
||||
with open(path, "wb") as key_file:
|
||||
key_file.write(self.get_private_key())
|
||||
return True
|
||||
return False
|
||||
except Exception as e:
|
||||
RNS.log("Error while saving identity to "+str(path), RNS.LOG_ERROR)
|
||||
RNS.log("The contained exception was: "+str(e))
|
||||
|
||||
def __init__(self,create_keys=True):
|
||||
# Initialize keys to none
|
||||
@@ -331,24 +360,6 @@ class Identity:
|
||||
self.hash = Identity.truncated_hash(self.get_public_key())
|
||||
self.hexhash = self.hash.hex()
|
||||
|
||||
def to_file(self, path):
|
||||
"""
|
||||
Saves the identity to a file. This will write the private key to disk,
|
||||
and anyone with access to this file will be able to decrypt all
|
||||
communication for the identity. Be very careful with this method.
|
||||
|
||||
:param path: The full path specifying where to save the identity.
|
||||
:returns: True if the file was saved, otherwise False.
|
||||
"""
|
||||
try:
|
||||
with open(path, "wb") as key_file:
|
||||
key_file.write(self.get_public_key())
|
||||
return True
|
||||
return False
|
||||
except Exception as e:
|
||||
RNS.log("Error while saving identity to "+str(path), RNS.LOG_ERROR)
|
||||
RNS.log("The contained exception was: "+str(e))
|
||||
|
||||
def load(self, path):
|
||||
try:
|
||||
with open(path, "rb") as key_file:
|
||||
|
||||
@@ -48,6 +48,9 @@ class AX25KISSInterface(Interface):
|
||||
serial = None
|
||||
|
||||
def __init__(self, owner, name, callsign, ssid, port, speed, databits, parity, stopbits, preamble, txtail, persistence, slottime, flow_control):
|
||||
self.rxb = 0
|
||||
self.txb = 0
|
||||
|
||||
self.serial = None
|
||||
self.owner = owner
|
||||
self.name = name
|
||||
@@ -62,13 +65,12 @@ class AX25KISSInterface(Interface):
|
||||
self.stopbits = stopbits
|
||||
self.timeout = 100
|
||||
self.online = False
|
||||
# TODO: Sane default and make this configurable
|
||||
# TODO: Changed to 25ms instead of 100ms, check it
|
||||
self.txdelay = 0.025
|
||||
|
||||
self.packet_queue = []
|
||||
self.flow_control = flow_control
|
||||
self.interface_ready = False
|
||||
self.flow_control_timeout = 5
|
||||
self.flow_control_locked = time.time()
|
||||
|
||||
if (len(self.src_call) < 3 or len(self.src_call) > 6):
|
||||
raise ValueError("Invalid callsign for "+str(self))
|
||||
@@ -189,14 +191,17 @@ class AX25KISSInterface(Interface):
|
||||
|
||||
def processIncoming(self, data):
|
||||
if (len(data) > AX25.HEADER_SIZE):
|
||||
self.rxb += len(data)
|
||||
self.owner.inbound(data[AX25.HEADER_SIZE:], self)
|
||||
|
||||
|
||||
def processOutgoing(self,data):
|
||||
datalen = len(data)
|
||||
if self.online:
|
||||
if self.interface_ready:
|
||||
if self.flow_control:
|
||||
self.interface_ready = False
|
||||
self.flow_control_locked = time.time()
|
||||
|
||||
encoded_dst_ssid = bytes([0x60 | (self.dst_ssid << 1)])
|
||||
encoded_src_ssid = bytes([0x60 | (self.src_ssid << 1) | 0x01])
|
||||
@@ -223,11 +228,9 @@ class AX25KISSInterface(Interface):
|
||||
data = data.replace(bytes([0xc0]), bytes([0xdb])+bytes([0xdc]))
|
||||
kiss_frame = bytes([KISS.FEND])+bytes([0x00])+data+bytes([KISS.FEND])
|
||||
|
||||
if (self.txdelay > 0):
|
||||
RNS.log(str(self.name)+" delaying TX for "+str(self.txdelay)+" seconds", RNS.LOG_EXTREME)
|
||||
sleep(self.txdelay)
|
||||
|
||||
written = self.serial.write(kiss_frame)
|
||||
self.txb += datalen
|
||||
|
||||
if written != len(kiss_frame):
|
||||
if self.flow_control:
|
||||
self.interface_ready = True
|
||||
@@ -294,12 +297,21 @@ class AX25KISSInterface(Interface):
|
||||
in_frame = False
|
||||
command = KISS.CMD_UNKNOWN
|
||||
escape = False
|
||||
sleep(0.08)
|
||||
sleep(0.05)
|
||||
|
||||
if self.flow_control:
|
||||
if not self.interface_ready:
|
||||
if time.time() > self.flow_control_locked + self.flow_control_timeout:
|
||||
RNS.log("Interface "+str(self)+" is unlocking flow control due to time-out. This should not happen. Your hardware might have missed a flow-control READY command, or maybe it does not support flow-control.", RNS.LOG_WARNING)
|
||||
self.process_queue()
|
||||
|
||||
except Exception as e:
|
||||
self.online = False
|
||||
RNS.log("A serial port error occurred, the contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
RNS.log("The interface "+str(self.name)+" is now offline. Restart Reticulum to attempt reconnection.", RNS.LOG_ERROR)
|
||||
RNS.log("The interface "+str(self)+" experienced an unrecoverable error and is being torn down. Restart Reticulum to attempt to open this interface again.", RNS.LOG_ERROR)
|
||||
|
||||
if RNS.Reticulum.panic_on_interface_error:
|
||||
RNS.panic()
|
||||
|
||||
def __str__(self):
|
||||
return "AX25KISSInterface["+self.name+"]"
|
||||
@@ -8,8 +8,12 @@ class Interface:
|
||||
name = None
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
self.rxb = 0
|
||||
self.txb = 0
|
||||
self.online = False
|
||||
|
||||
def get_hash(self):
|
||||
# TODO: Maybe expand this to something more unique
|
||||
return RNS.Identity.full_hash(str(self).encode("utf-8"))
|
||||
return RNS.Identity.full_hash(str(self).encode("utf-8"))
|
||||
|
||||
def detach(self):
|
||||
pass
|
||||
@@ -40,6 +40,9 @@ class KISSInterface(Interface):
|
||||
serial = None
|
||||
|
||||
def __init__(self, owner, name, port, speed, databits, parity, stopbits, preamble, txtail, persistence, slottime, flow_control, beacon_interval, beacon_data):
|
||||
self.rxb = 0
|
||||
self.txb = 0
|
||||
|
||||
if beacon_data == None:
|
||||
beacon_data = ""
|
||||
|
||||
@@ -60,7 +63,7 @@ class KISSInterface(Interface):
|
||||
self.packet_queue = []
|
||||
self.flow_control = flow_control
|
||||
self.interface_ready = False
|
||||
self.flow_control_timeout = 10
|
||||
self.flow_control_timeout = 5
|
||||
self.flow_control_locked = time.time()
|
||||
|
||||
self.preamble = preamble if preamble != None else 350;
|
||||
@@ -174,10 +177,12 @@ class KISSInterface(Interface):
|
||||
|
||||
|
||||
def processIncoming(self, data):
|
||||
self.rxb += len(data)
|
||||
self.owner.inbound(data, self)
|
||||
|
||||
|
||||
def processOutgoing(self,data):
|
||||
datalen = len(data)
|
||||
if self.online:
|
||||
if self.interface_ready:
|
||||
if self.flow_control:
|
||||
@@ -189,6 +194,7 @@ class KISSInterface(Interface):
|
||||
frame = bytes([KISS.FEND])+bytes([0x00])+data+bytes([KISS.FEND])
|
||||
|
||||
written = self.serial.write(frame)
|
||||
self.txb += datalen
|
||||
|
||||
if data == self.beacon_d:
|
||||
self.first_tx = None
|
||||
@@ -259,12 +265,12 @@ class KISSInterface(Interface):
|
||||
in_frame = False
|
||||
command = KISS.CMD_UNKNOWN
|
||||
escape = False
|
||||
sleep(0.08)
|
||||
sleep(0.05)
|
||||
|
||||
if self.flow_control:
|
||||
if not self.interface_ready:
|
||||
if time.time() > self.flow_control_locked + self.flow_control_timeout:
|
||||
RNS.log("Interface "+str(self)+" is unlocking flow control due to time-out. This should not happen. Your hardware might have missed a flow-control READY command.", RNS.LOG_WARNING)
|
||||
RNS.log("Interface "+str(self)+" is unlocking flow control due to time-out. This should not happen. Your hardware might have missed a flow-control READY command, or maybe it does not support flow-control.", RNS.LOG_WARNING)
|
||||
self.process_queue()
|
||||
|
||||
if self.beacon_i != None and self.beacon_d != None:
|
||||
@@ -277,7 +283,10 @@ class KISSInterface(Interface):
|
||||
except Exception as e:
|
||||
self.online = False
|
||||
RNS.log("A serial port error occurred, the contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
RNS.log("The interface "+str(self.name)+" is now offline. Restart Reticulum to attempt reconnection.", RNS.LOG_ERROR)
|
||||
RNS.log("The interface "+str(self)+" experienced an unrecoverable error and is being torn down. Restart Reticulum to attempt to open this interface again.", RNS.LOG_ERROR)
|
||||
|
||||
if RNS.Reticulum.panic_on_interface_error:
|
||||
RNS.panic()
|
||||
|
||||
def __str__(self):
|
||||
return "KISSInterface["+self.name+"]"
|
||||
@@ -24,6 +24,10 @@ class ThreadingTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
|
||||
class LocalClientInterface(Interface):
|
||||
|
||||
def __init__(self, owner, name, target_port = None, connected_socket=None):
|
||||
self.rxb = 0
|
||||
self.txb = 0
|
||||
self.online = False
|
||||
|
||||
self.IN = True
|
||||
self.OUT = False
|
||||
self.socket = None
|
||||
@@ -36,6 +40,8 @@ class LocalClientInterface(Interface):
|
||||
self.target_port = None
|
||||
self.socket = connected_socket
|
||||
|
||||
self.is_connected_to_shared_instance = False
|
||||
|
||||
elif target_port != None:
|
||||
self.receives = True
|
||||
self.target_ip = "127.0.0.1"
|
||||
@@ -56,6 +62,10 @@ class LocalClientInterface(Interface):
|
||||
thread.start()
|
||||
|
||||
def processIncoming(self, data):
|
||||
self.rxb += len(data)
|
||||
if hasattr(self, "parent_interface") and self.parent_interface != None:
|
||||
self.parent_interface.rxb += len(data)
|
||||
|
||||
self.owner.inbound(data, self)
|
||||
|
||||
def processOutgoing(self, data):
|
||||
@@ -68,6 +78,10 @@ class LocalClientInterface(Interface):
|
||||
data = bytes([HDLC.FLAG])+HDLC.escape(data)+bytes([HDLC.FLAG])
|
||||
self.socket.sendall(data)
|
||||
self.writing = False
|
||||
self.txb += len(data)
|
||||
if hasattr(self, "parent_interface") and self.parent_interface != None:
|
||||
self.parent_interface.txb += len(data)
|
||||
|
||||
except Exception as e:
|
||||
RNS.log("Exception occurred while transmitting via "+str(self)+", tearing down interface", RNS.LOG_ERROR)
|
||||
RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
@@ -106,7 +120,7 @@ class LocalClientInterface(Interface):
|
||||
data_buffer = data_buffer+bytes([byte])
|
||||
else:
|
||||
RNS.log("Socket for "+str(self)+" was closed, tearing down interface", RNS.LOG_VERBOSE)
|
||||
self.teardown()
|
||||
self.teardown(nowarning=True)
|
||||
break
|
||||
|
||||
|
||||
@@ -116,7 +130,26 @@ class LocalClientInterface(Interface):
|
||||
RNS.log("Tearing down "+str(self), RNS.LOG_ERROR)
|
||||
self.teardown()
|
||||
|
||||
def teardown(self):
|
||||
def detach(self):
|
||||
if self.socket != None:
|
||||
if hasattr(self.socket, "close"):
|
||||
if callable(self.socket.close):
|
||||
RNS.log("Detaching "+str(self), RNS.LOG_DEBUG)
|
||||
self.detached = True
|
||||
|
||||
try:
|
||||
self.socket.shutdown(socket.SHUT_RDWR)
|
||||
except Exception as e:
|
||||
RNS.log("Error while shutting down socket for "+str(self)+": "+str(e))
|
||||
|
||||
try:
|
||||
self.socket.close()
|
||||
except Exception as e:
|
||||
RNS.log("Error while closing socket for "+str(self)+": "+str(e))
|
||||
|
||||
self.socket = None
|
||||
|
||||
def teardown(self, nowarning=False):
|
||||
self.online = False
|
||||
self.OUT = False
|
||||
self.IN = False
|
||||
@@ -126,6 +159,22 @@ class LocalClientInterface(Interface):
|
||||
|
||||
if self in RNS.Transport.local_client_interfaces:
|
||||
RNS.Transport.local_client_interfaces.remove(self)
|
||||
if hasattr(self, "parent_interface") and self.parent_interface != None:
|
||||
self.parent_interface.clients -= 1
|
||||
|
||||
if nowarning == False:
|
||||
RNS.log("The interface "+str(self)+" experienced an unrecoverable error and is being torn down. Restart Reticulum to attempt to open this interface again.", RNS.LOG_ERROR)
|
||||
if RNS.Reticulum.panic_on_interface_error:
|
||||
RNS.panic()
|
||||
|
||||
if self.is_connected_to_shared_instance:
|
||||
# TODO: Maybe add automatic recovery here.
|
||||
# Needs thinking through, since user needs
|
||||
# to now that all connectivity has been cut
|
||||
# while service is recovering. Better for
|
||||
# now to take down entire stack.
|
||||
RNS.log("Lost connection to local shared RNS instance. Exiting now.", RNS.LOG_CRITICAL)
|
||||
RNS.panic()
|
||||
|
||||
|
||||
def __str__(self):
|
||||
@@ -135,6 +184,11 @@ class LocalClientInterface(Interface):
|
||||
class LocalServerInterface(Interface):
|
||||
|
||||
def __init__(self, owner, bindport=None):
|
||||
self.rxb = 0
|
||||
self.txb = 0
|
||||
self.online = False
|
||||
self.clients = 0
|
||||
|
||||
self.IN = True
|
||||
self.OUT = False
|
||||
self.name = "Reticulum"
|
||||
@@ -153,12 +207,17 @@ class LocalServerInterface(Interface):
|
||||
self.is_local_shared_instance = True
|
||||
|
||||
address = (self.bind_ip, self.bind_port)
|
||||
|
||||
ThreadingTCPServer.allow_reuse_address = True
|
||||
self.server = ThreadingTCPServer(address, handlerFactory(self.incoming_connection))
|
||||
|
||||
thread = threading.Thread(target=self.server.serve_forever)
|
||||
thread.setDaemon(True)
|
||||
thread.start()
|
||||
|
||||
self.online = True
|
||||
|
||||
|
||||
|
||||
def incoming_connection(self, handler):
|
||||
interface_name = str(str(handler.client_address[1]))
|
||||
@@ -171,13 +230,14 @@ class LocalServerInterface(Interface):
|
||||
RNS.log("Accepting new connection to shared instance: "+str(spawned_interface), RNS.LOG_VERBOSE)
|
||||
RNS.Transport.interfaces.append(spawned_interface)
|
||||
RNS.Transport.local_client_interfaces.append(spawned_interface)
|
||||
self.clients += 1
|
||||
spawned_interface.read_loop()
|
||||
|
||||
def processOutgoing(self, data):
|
||||
pass
|
||||
|
||||
def __str__(self):
|
||||
return "Shared Instance ["+str(self.bind_port)+"]"
|
||||
return "Shared Instance["+str(self.bind_port)+"]"
|
||||
|
||||
class LocalInterfaceHandler(socketserver.BaseRequestHandler):
|
||||
def __init__(self, callback, *args, **keys):
|
||||
|
||||
@@ -72,6 +72,9 @@ class RNodeInterface(Interface):
|
||||
CALLSIGN_MAX_LEN = 32
|
||||
|
||||
def __init__(self, owner, name, port, frequency = None, bandwidth = None, txpower = None, sf = None, cr = None, flow_control = False, id_interval = None, id_callsign = None):
|
||||
self.rxb = 0
|
||||
self.txb = 0
|
||||
|
||||
self.serial = None
|
||||
self.owner = owner
|
||||
self.name = name
|
||||
@@ -273,15 +276,17 @@ class RNodeInterface(Interface):
|
||||
try:
|
||||
self.bitrate = self.r_sf * ( (4.0/self.r_cr) / (math.pow(2,self.r_sf)/(self.r_bandwidth/1000)) ) * 1000
|
||||
self.bitrate_kbps = round(self.bitrate/1000.0, 2)
|
||||
RNS.log(str(self)+" On-air bitrate is now "+str(self.bitrate_kbps)+ " kbps", RNS.LOG_INFO)
|
||||
RNS.log(str(self)+" On-air bitrate is now "+str(self.bitrate_kbps)+ " kbps", RNS.LOG_VERBOSE)
|
||||
except:
|
||||
self.bitrate = 0
|
||||
|
||||
def processIncoming(self, data):
|
||||
self.rxb += len(data)
|
||||
self.owner.inbound(data, self)
|
||||
|
||||
|
||||
def processOutgoing(self,data):
|
||||
datalen = len(data)
|
||||
if self.online:
|
||||
if self.interface_ready:
|
||||
if self.flow_control:
|
||||
@@ -297,6 +302,7 @@ class RNodeInterface(Interface):
|
||||
frame = bytes([0xc0])+bytes([0x00])+data+bytes([0xc0])
|
||||
|
||||
written = self.serial.write(frame)
|
||||
self.txb += datalen
|
||||
|
||||
if written != len(frame):
|
||||
raise IOError("Serial interface only wrote "+str(written)+" bytes of "+str(len(data)))
|
||||
@@ -463,7 +469,10 @@ class RNodeInterface(Interface):
|
||||
except Exception as e:
|
||||
self.online = False
|
||||
RNS.log("A serial port error occurred, the contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
RNS.log("The interface "+str(self.name)+" is now offline. Restart Reticulum to attempt reconnection.", RNS.LOG_ERROR)
|
||||
RNS.log("The interface "+str(self)+" experienced an unrecoverable error and is being torn down. Restart Reticulum to attempt to open this interface again.", RNS.LOG_ERROR)
|
||||
|
||||
if RNS.Reticulum.panic_on_interface_error:
|
||||
RNS.panic()
|
||||
|
||||
def __str__(self):
|
||||
return "RNodeInterface["+self.name+"]"
|
||||
|
||||
@@ -31,6 +31,9 @@ class SerialInterface(Interface):
|
||||
serial = None
|
||||
|
||||
def __init__(self, owner, name, port, speed, databits, parity, stopbits):
|
||||
self.rxb = 0
|
||||
self.txb = 0
|
||||
|
||||
self.serial = None
|
||||
self.owner = owner
|
||||
self.name = name
|
||||
@@ -79,6 +82,7 @@ class SerialInterface(Interface):
|
||||
|
||||
|
||||
def processIncoming(self, data):
|
||||
self.rxb += len(data)
|
||||
self.owner.inbound(data, self)
|
||||
|
||||
|
||||
@@ -86,6 +90,7 @@ class SerialInterface(Interface):
|
||||
if self.online:
|
||||
data = bytes([HDLC.FLAG])+HDLC.escape(data)+bytes([HDLC.FLAG])
|
||||
written = self.serial.write(data)
|
||||
self.txb += len(data)
|
||||
if written != len(data):
|
||||
raise IOError("Serial interface only wrote "+str(written)+" bytes of "+str(len(data)))
|
||||
|
||||
@@ -130,7 +135,10 @@ class SerialInterface(Interface):
|
||||
except Exception as e:
|
||||
self.online = False
|
||||
RNS.log("A serial port error occurred, the contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
RNS.log("The interface "+str(self.name)+" is now offline. Restart Reticulum to attempt reconnection.", RNS.LOG_ERROR)
|
||||
RNS.log("The interface "+str(self)+" experienced an unrecoverable error and is being torn down. Restart Reticulum to attempt to open this interface again.", RNS.LOG_ERROR)
|
||||
|
||||
if RNS.Reticulum.panic_on_interface_error:
|
||||
RNS.panic()
|
||||
|
||||
def __str__(self):
|
||||
return "SerialInterface["+self.name+"]"
|
||||
|
||||
@@ -2,6 +2,7 @@ from .Interface import Interface
|
||||
import socketserver
|
||||
import threading
|
||||
import netifaces
|
||||
import platform
|
||||
import socket
|
||||
import time
|
||||
import sys
|
||||
@@ -23,13 +24,36 @@ class ThreadingTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
|
||||
pass
|
||||
|
||||
class TCPClientInterface(Interface):
|
||||
RECONNECT_WAIT = 5
|
||||
RECONNECT_MAX_TRIES = None
|
||||
|
||||
def __init__(self, owner, name, target_ip=None, target_port=None, connected_socket=None):
|
||||
# TCP socket options
|
||||
TCP_USER_TIMEOUT = 20
|
||||
TCP_PROBE_AFTER = 5
|
||||
TCP_PROBE_INTERVAL = 3
|
||||
TCP_PROBES = 5
|
||||
|
||||
def __init__(self, owner, name, target_ip=None, target_port=None, connected_socket=None, max_reconnect_tries=None):
|
||||
self.rxb = 0
|
||||
self.txb = 0
|
||||
|
||||
self.IN = True
|
||||
self.OUT = False
|
||||
self.socket = None
|
||||
self.parent_interface = None
|
||||
self.name = name
|
||||
self.initiator = False
|
||||
self.reconnecting = False
|
||||
self.never_connected = True
|
||||
self.owner = owner
|
||||
self.writing = False
|
||||
self.online = False
|
||||
self.detached = False
|
||||
|
||||
if max_reconnect_tries == None:
|
||||
self.max_reconnect_tries = TCPClientInterface.RECONNECT_MAX_TRIES
|
||||
else:
|
||||
self.max_reconnect_tries = max_reconnect_tries
|
||||
|
||||
if connected_socket != None:
|
||||
self.receives = True
|
||||
@@ -37,24 +61,129 @@ class TCPClientInterface(Interface):
|
||||
self.target_port = None
|
||||
self.socket = connected_socket
|
||||
|
||||
if platform.system() == "Linux":
|
||||
self.set_timeouts_linux()
|
||||
elif platform.system() == "Darwin":
|
||||
self.set_timeouts_osx()
|
||||
|
||||
elif target_ip != None and target_port != None:
|
||||
self.receives = True
|
||||
self.target_ip = target_ip
|
||||
self.target_port = target_port
|
||||
self.initiator = True
|
||||
|
||||
if not self.connect(initial=True):
|
||||
thread = threading.Thread(target=self.reconnect)
|
||||
thread.setDaemon(True)
|
||||
thread.start()
|
||||
else:
|
||||
thread = threading.Thread(target=self.read_loop)
|
||||
thread.setDaemon(True)
|
||||
thread.start()
|
||||
self.wants_tunnel = True
|
||||
|
||||
|
||||
def set_timeouts_linux(self):
|
||||
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_USER_TIMEOUT, int(TCPClientInterface.TCP_USER_TIMEOUT * 1000))
|
||||
|
||||
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
|
||||
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, int(TCPClientInterface.TCP_PROBE_AFTER))
|
||||
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, int(TCPClientInterface.TCP_PROBE_INTERVAL))
|
||||
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, int(TCPClientInterface.TCP_PROBES))
|
||||
|
||||
def set_timeouts_osx(self):
|
||||
if hasattr(socket, "TCP_KEEPALIVE"):
|
||||
TCP_KEEPIDLE = socket.TCP_KEEPALIVE
|
||||
else:
|
||||
TCP_KEEPIDLE = 0x10
|
||||
|
||||
sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
|
||||
sock.setsockopt(socket.IPPROTO_TCP, TCP_KEEPIDLE, int(TCPClientInterface.TCP_PROBE_AFTER))
|
||||
|
||||
def detach(self):
|
||||
if self.socket != None:
|
||||
if hasattr(self.socket, "close"):
|
||||
if callable(self.socket.close):
|
||||
RNS.log("Detaching "+str(self), RNS.LOG_DEBUG)
|
||||
self.detached = True
|
||||
|
||||
try:
|
||||
self.socket.shutdown(socket.SHUT_RDWR)
|
||||
except Exception as e:
|
||||
RNS.log("Error while shutting down socket for "+str(self)+": "+str(e))
|
||||
|
||||
try:
|
||||
self.socket.close()
|
||||
except Exception as e:
|
||||
RNS.log("Error while closing socket for "+str(self)+": "+str(e))
|
||||
|
||||
self.socket = None
|
||||
|
||||
def connect(self, initial=False):
|
||||
try:
|
||||
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
self.socket.connect((self.target_ip, self.target_port))
|
||||
self.online = True
|
||||
|
||||
except Exception as e:
|
||||
if initial:
|
||||
RNS.log("Initial connection for "+str(self)+" could not be established: "+str(e), RNS.LOG_ERROR)
|
||||
RNS.log("Leaving unconnected and retrying connection in "+str(TCPClientInterface.RECONNECT_WAIT)+" seconds.", RNS.LOG_ERROR)
|
||||
return False
|
||||
|
||||
else:
|
||||
raise e
|
||||
|
||||
self.owner = owner
|
||||
if platform.system() == "Linux":
|
||||
self.set_timeouts_linux()
|
||||
elif platform.system() == "Darwin":
|
||||
self.set_timeouts_osx()
|
||||
|
||||
self.online = True
|
||||
self.writing = False
|
||||
self.never_connected = False
|
||||
|
||||
if connected_socket == None:
|
||||
thread = threading.Thread(target=self.read_loop)
|
||||
thread.setDaemon(True)
|
||||
thread.start()
|
||||
return True
|
||||
|
||||
|
||||
def reconnect(self):
|
||||
if self.initiator:
|
||||
if not self.reconnecting:
|
||||
self.reconnecting = True
|
||||
attempts = 0
|
||||
while not self.online:
|
||||
time.sleep(TCPClientInterface.RECONNECT_WAIT)
|
||||
attempts += 1
|
||||
|
||||
if self.max_reconnect_tries != None and attempts > self.max_reconnect_tries:
|
||||
RNS.log("Max reconnection attempts reached for "+str(self), RNS.LOG_ERROR)
|
||||
self.teardown()
|
||||
break
|
||||
|
||||
try:
|
||||
self.connect()
|
||||
|
||||
except Exception as e:
|
||||
RNS.log("Connection attempt for "+str(self)+" failed: "+str(e), RNS.LOG_DEBUG)
|
||||
|
||||
if not self.never_connected:
|
||||
RNS.log("Reconnected TCP socket for "+str(self)+".", RNS.LOG_INFO)
|
||||
|
||||
self.reconnecting = False
|
||||
thread = threading.Thread(target=self.read_loop)
|
||||
thread.setDaemon(True)
|
||||
thread.start()
|
||||
RNS.Transport.synthesize_tunnel(self)
|
||||
|
||||
else:
|
||||
RNS.log("Attempt to reconnect on a non-initiator TCP interface. This should not happen.", RNS.LOG_ERROR)
|
||||
raise IOError("Attempt to reconnect on a non-initiator TCP interface")
|
||||
|
||||
def processIncoming(self, data):
|
||||
self.rxb += len(data)
|
||||
if hasattr(self, "parent_interface") and self.parent_interface != None:
|
||||
self.parent_interface.rxb += len(data)
|
||||
|
||||
self.owner.inbound(data, self)
|
||||
|
||||
def processOutgoing(self, data):
|
||||
@@ -67,6 +196,10 @@ class TCPClientInterface(Interface):
|
||||
data = bytes([HDLC.FLAG])+HDLC.escape(data)+bytes([HDLC.FLAG])
|
||||
self.socket.sendall(data)
|
||||
self.writing = False
|
||||
self.txb += len(data)
|
||||
if hasattr(self, "parent_interface") and self.parent_interface != None:
|
||||
self.parent_interface.txb += len(data)
|
||||
|
||||
except Exception as e:
|
||||
RNS.log("Exception occurred while transmitting via "+str(self)+", tearing down interface", RNS.LOG_ERROR)
|
||||
RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
@@ -104,23 +237,46 @@ class TCPClientInterface(Interface):
|
||||
escape = False
|
||||
data_buffer = data_buffer+bytes([byte])
|
||||
else:
|
||||
RNS.log("TCP socket for "+str(self)+" was closed, tearing down interface", RNS.LOG_VERBOSE)
|
||||
self.teardown()
|
||||
self.online = False
|
||||
if self.initiator and not self.detached:
|
||||
RNS.log("TCP socket for "+str(self)+" was closed, attempting to reconnect...", RNS.LOG_WARNING)
|
||||
self.reconnect()
|
||||
else:
|
||||
RNS.log("TCP socket for remote client "+str(self)+" was closed.", RNS.LOG_VERBOSE)
|
||||
self.teardown()
|
||||
|
||||
break
|
||||
|
||||
|
||||
except Exception as e:
|
||||
self.online = False
|
||||
RNS.log("An interface error occurred, the contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
RNS.log("Tearing down "+str(self), RNS.LOG_ERROR)
|
||||
self.teardown()
|
||||
RNS.log("An interface error occurred for "+str(self)+", the contained exception was: "+str(e), RNS.LOG_WARNING)
|
||||
|
||||
if self.initiator:
|
||||
RNS.log("Attempting to reconnect...", RNS.LOG_WARNING)
|
||||
self.reconnect()
|
||||
else:
|
||||
self.teardown()
|
||||
|
||||
def teardown(self):
|
||||
if self.initiator and not self.detached:
|
||||
RNS.log("The interface "+str(self)+" experienced an unrecoverable error and is being torn down. Restart Reticulum to attempt to open this interface again.", RNS.LOG_ERROR)
|
||||
if RNS.Reticulum.panic_on_interface_error:
|
||||
RNS.panic()
|
||||
|
||||
else:
|
||||
RNS.log("The interface "+str(self)+" is being torn down.", RNS.LOG_VERBOSE)
|
||||
|
||||
self.online = False
|
||||
self.OUT = False
|
||||
self.IN = False
|
||||
|
||||
if hasattr(self, "parent_interface") and self.parent_interface != None:
|
||||
self.parent_interface.clients -= 1
|
||||
|
||||
if self in RNS.Transport.interfaces:
|
||||
RNS.Transport.interfaces.remove(self)
|
||||
if not self.initiator:
|
||||
RNS.Transport.interfaces.remove(self)
|
||||
|
||||
|
||||
def __str__(self):
|
||||
@@ -136,6 +292,11 @@ class TCPServerInterface(Interface):
|
||||
return netifaces.ifaddresses(name)[netifaces.AF_INET][0]['broadcast']
|
||||
|
||||
def __init__(self, owner, name, device=None, bindip=None, bindport=None):
|
||||
self.rxb = 0
|
||||
self.txb = 0
|
||||
self.online = False
|
||||
self.clients = 0
|
||||
|
||||
self.IN = True
|
||||
self.OUT = False
|
||||
self.name = name
|
||||
@@ -155,12 +316,16 @@ class TCPServerInterface(Interface):
|
||||
|
||||
self.owner = owner
|
||||
address = (self.bind_ip, self.bind_port)
|
||||
|
||||
ThreadingTCPServer.allow_reuse_address = True
|
||||
self.server = ThreadingTCPServer(address, handlerFactory(self.incoming_connection))
|
||||
|
||||
thread = threading.Thread(target=self.server.serve_forever)
|
||||
thread.setDaemon(True)
|
||||
thread.start()
|
||||
|
||||
self.online = True
|
||||
|
||||
|
||||
def incoming_connection(self, handler):
|
||||
RNS.log("Accepting incoming TCP connection", RNS.LOG_VERBOSE)
|
||||
@@ -171,8 +336,10 @@ class TCPServerInterface(Interface):
|
||||
spawned_interface.target_ip = handler.client_address[0]
|
||||
spawned_interface.target_port = str(handler.client_address[1])
|
||||
spawned_interface.parent_interface = self
|
||||
spawned_interface.online = True
|
||||
RNS.log("Spawned new TCPClient Interface: "+str(spawned_interface), RNS.LOG_VERBOSE)
|
||||
RNS.Transport.interfaces.append(spawned_interface)
|
||||
self.clients += 1
|
||||
spawned_interface.read_loop()
|
||||
|
||||
def processOutgoing(self, data):
|
||||
|
||||
@@ -18,9 +18,12 @@ class UDPInterface(Interface):
|
||||
return netifaces.ifaddresses(name)[netifaces.AF_INET][0]['broadcast']
|
||||
|
||||
def __init__(self, owner, name, device=None, bindip=None, bindport=None, forwardip=None, forwardport=None):
|
||||
self.rxb = 0
|
||||
self.txb = 0
|
||||
self.IN = True
|
||||
self.OUT = False
|
||||
self.name = name
|
||||
self.online = False
|
||||
|
||||
if device != None:
|
||||
if bindip == None:
|
||||
@@ -47,6 +50,8 @@ class UDPInterface(Interface):
|
||||
thread.setDaemon(True)
|
||||
thread.start()
|
||||
|
||||
self.online = True
|
||||
|
||||
if (forwardip != None and forwardport != None):
|
||||
self.forwards = True
|
||||
self.forward_ip = forwardip
|
||||
@@ -54,12 +59,18 @@ class UDPInterface(Interface):
|
||||
|
||||
|
||||
def processIncoming(self, data):
|
||||
self.rxb += len(data)
|
||||
self.owner.inbound(data, self)
|
||||
|
||||
def processOutgoing(self,data):
|
||||
udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
udp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
|
||||
udp_socket.sendto(data, (self.forward_ip, self.forward_port))
|
||||
try:
|
||||
udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
udp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
|
||||
udp_socket.sendto(data, (self.forward_ip, self.forward_port))
|
||||
self.txb += len(data)
|
||||
|
||||
except Exception as e:
|
||||
RNS.log("Could not transmit on "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
|
||||
|
||||
def __str__(self):
|
||||
|
||||
@@ -27,12 +27,13 @@ class LinkCallbacks:
|
||||
|
||||
class Link:
|
||||
"""
|
||||
This class.
|
||||
This class is used to establish and manage links to other peers. When a
|
||||
link instance is created, Reticulum will attempt to establish verified
|
||||
connectivity with the specified destination.
|
||||
|
||||
:param destination: A :ref:`RNS.Destination<api-destination>` instance which to establish a link to.
|
||||
:param owner: Internal use by :ref:`RNS.Transport<api-Transport>`, ignore this argument.
|
||||
:param peer_pub_bytes: Internal use, ignore this argument.
|
||||
:param peer_sig_pub_bytes: Internal use, ignore this argument.
|
||||
:param established_callback: An optional function or method with the signature *callback(link)* to be called when the link has been established.
|
||||
:param closed_callback: An optional function or method with the signature *callback(link)* to be called when the link is closed.
|
||||
"""
|
||||
CURVE = RNS.Identity.CURVE
|
||||
"""
|
||||
@@ -42,16 +43,19 @@ class Link:
|
||||
ECPUBSIZE = 32+32
|
||||
KEYSIZE = 32
|
||||
|
||||
MDU = math.floor((RNS.Reticulum.MDU-RNS.Identity.AES_HMAC_OVERHEAD)/RNS.Identity.AES128_BLOCKSIZE)*RNS.Identity.AES128_BLOCKSIZE - 1
|
||||
MDU = math.floor((RNS.Reticulum.MTU-RNS.Reticulum.HEADER_MINSIZE-RNS.Identity.FERNET_OVERHEAD)/RNS.Identity.AES128_BLOCKSIZE)*RNS.Identity.AES128_BLOCKSIZE - 1
|
||||
|
||||
# TODO: This should not be hardcoded,
|
||||
# but calculated from something like
|
||||
# first-hop RTT latency and distance
|
||||
DEFAULT_TIMEOUT = 15.0
|
||||
# This value is set at a reasonable level for a 1 Kb/s channel.
|
||||
#
|
||||
# TODO: Find a way to automatically raise or lower this according to
|
||||
# channel bandwidth and utilisation.
|
||||
ESTABLISHMENT_TIMEOUT_PER_HOP = 5
|
||||
"""
|
||||
Default timeout for link establishment in seconds.
|
||||
Default timeout for link establishment in seconds per hop to destination.
|
||||
"""
|
||||
TIMEOUT_FACTOR = 3
|
||||
|
||||
TRAFFIC_TIMEOUT_FACTOR = 6
|
||||
KEEPALIVE_TIMEOUT_FACTOR = 4
|
||||
STALE_GRACE = 2
|
||||
KEEPALIVE = 360
|
||||
"""
|
||||
@@ -80,6 +84,7 @@ class Link:
|
||||
link = Link(owner = owner, peer_pub_bytes=data[:Link.ECPUBSIZE//2], peer_sig_pub_bytes=data[Link.ECPUBSIZE//2:Link.ECPUBSIZE])
|
||||
link.set_link_id(packet)
|
||||
link.destination = packet.destination
|
||||
link.establishment_timeout = Link.ESTABLISHMENT_TIMEOUT_PER_HOP * max(1, packet.hops)
|
||||
RNS.log("Validating link request "+RNS.prettyhexrep(link.link_id), RNS.LOG_VERBOSE)
|
||||
link.handshake()
|
||||
link.attached_interface = packet.receiving_interface
|
||||
@@ -102,7 +107,7 @@ class Link:
|
||||
return None
|
||||
|
||||
|
||||
def __init__(self, destination=None, owner=None, peer_pub_bytes = None, peer_sig_pub_bytes = None):
|
||||
def __init__(self, destination=None, established_callback = None, closed_callback = None, owner=None, peer_pub_bytes = None, peer_sig_pub_bytes = None):
|
||||
if destination != None and destination.type != RNS.Destination.SINGLE:
|
||||
raise TypeError("Links can only be established to the \"single\" destination type")
|
||||
self.rtt = None
|
||||
@@ -117,9 +122,8 @@ class Link:
|
||||
self.rx = 0
|
||||
self.txbytes = 0
|
||||
self.rxbytes = 0
|
||||
self.default_timeout = Link.DEFAULT_TIMEOUT
|
||||
self.proof_timeout = self.default_timeout
|
||||
self.timeout_factor = Link.TIMEOUT_FACTOR
|
||||
self.traffic_timeout_factor = Link.TRAFFIC_TIMEOUT_FACTOR
|
||||
self.keepalive_timeout_factor = Link.KEEPALIVE_TIMEOUT_FACTOR
|
||||
self.keepalive = Link.KEEPALIVE
|
||||
self.watchdog_lock = False
|
||||
self.status = Link.PENDING
|
||||
@@ -128,13 +132,13 @@ class Link:
|
||||
self.destination = destination
|
||||
self.attached_interface = None
|
||||
self.__remote_identity = None
|
||||
self.__encryption_disabled = False
|
||||
if self.destination == None:
|
||||
self.initiator = False
|
||||
self.prv = self.owner.identity.prv
|
||||
self.sig_prv = self.owner.identity.sig_prv
|
||||
else:
|
||||
self.initiator = True
|
||||
self.establishment_timeout = Link.ESTABLISHMENT_TIMEOUT_PER_HOP * max(1, RNS.Transport.hops_to(destination.hash))
|
||||
self.prv = X25519PrivateKey.generate()
|
||||
self.sig_prv = Ed25519PrivateKey.generate()
|
||||
|
||||
@@ -158,6 +162,12 @@ class Link:
|
||||
else:
|
||||
self.load_peer(peer_pub_bytes, peer_sig_pub_bytes)
|
||||
|
||||
if established_callback != None:
|
||||
self.set_link_established_callback(established_callback)
|
||||
|
||||
if closed_callback != None:
|
||||
self.set_link_closed_callback(closed_callback)
|
||||
|
||||
if (self.initiator):
|
||||
peer_pub_bytes = self.destination.identity.get_public_key()[:Link.ECPUBSIZE//2]
|
||||
peer_sig_pub_bytes = self.destination.identity.get_public_key()[Link.ECPUBSIZE//2:Link.ECPUBSIZE]
|
||||
@@ -172,7 +182,7 @@ class Link:
|
||||
self.start_watchdog()
|
||||
self.packet.send()
|
||||
self.had_outbound()
|
||||
RNS.log("Link request "+RNS.prettyhexrep(self.link_id)+" sent to "+str(self.destination), RNS.LOG_VERBOSE)
|
||||
RNS.log("Link request "+RNS.prettyhexrep(self.link_id)+" sent to "+str(self.destination), RNS.LOG_DEBUG)
|
||||
|
||||
|
||||
def load_peer(self, peer_pub_bytes, peer_sig_pub_bytes):
|
||||
@@ -222,29 +232,29 @@ class Link:
|
||||
self.had_outbound()
|
||||
|
||||
def validate_proof(self, packet):
|
||||
if self.initiator and len(packet.data) == RNS.Identity.SIGLENGTH//8:
|
||||
signed_data = self.link_id+self.peer_pub_bytes+self.peer_sig_pub_bytes
|
||||
signature = packet.data[:RNS.Identity.SIGLENGTH//8]
|
||||
|
||||
if self.destination.identity.validate(signature, signed_data):
|
||||
self.rtt = time.time() - self.request_time
|
||||
self.attached_interface = packet.receiving_interface
|
||||
self.__remote_identity = self.destination.identity
|
||||
RNS.Transport.activate_link(self)
|
||||
RNS.log("Link "+str(self)+" established with "+str(self.destination)+", RTT is "+str(self.rtt), RNS.LOG_VERBOSE)
|
||||
rtt_data = umsgpack.packb(self.rtt)
|
||||
rtt_packet = RNS.Packet(self, rtt_data, context=RNS.Packet.LRRTT)
|
||||
RNS.log("Sending RTT packet", RNS.LOG_EXTREME);
|
||||
rtt_packet.send()
|
||||
self.had_outbound()
|
||||
if self.status == Link.HANDSHAKE:
|
||||
if self.initiator and len(packet.data) == RNS.Identity.SIGLENGTH//8:
|
||||
signed_data = self.link_id+self.peer_pub_bytes+self.peer_sig_pub_bytes
|
||||
signature = packet.data[:RNS.Identity.SIGLENGTH//8]
|
||||
|
||||
if self.destination.identity.validate(signature, signed_data):
|
||||
self.rtt = time.time() - self.request_time
|
||||
self.attached_interface = packet.receiving_interface
|
||||
self.__remote_identity = self.destination.identity
|
||||
RNS.Transport.activate_link(self)
|
||||
RNS.log("Link "+str(self)+" established with "+str(self.destination)+", RTT is "+str(self.rtt), RNS.LOG_VERBOSE)
|
||||
rtt_data = umsgpack.packb(self.rtt)
|
||||
rtt_packet = RNS.Packet(self, rtt_data, context=RNS.Packet.LRRTT)
|
||||
rtt_packet.send()
|
||||
self.had_outbound()
|
||||
|
||||
self.status = Link.ACTIVE
|
||||
if self.callbacks.link_established != None:
|
||||
thread = threading.Thread(target=self.callbacks.link_established, args=(self,))
|
||||
thread.setDaemon(True)
|
||||
thread.start()
|
||||
else:
|
||||
RNS.log("Invalid link proof signature received by "+str(self)+". Ignoring.", RNS.LOG_DEBUG)
|
||||
self.status = Link.ACTIVE
|
||||
if self.callbacks.link_established != None:
|
||||
thread = threading.Thread(target=self.callbacks.link_established, args=(self,))
|
||||
thread.setDaemon(True)
|
||||
thread.start()
|
||||
else:
|
||||
RNS.log("Invalid link proof signature received by "+str(self)+". Ignoring.", RNS.LOG_DEBUG)
|
||||
|
||||
|
||||
def identify(self, identity):
|
||||
@@ -266,38 +276,53 @@ class Link:
|
||||
self.had_outbound()
|
||||
|
||||
|
||||
def request(self, path, data = None, response_callback = None, failed_callback = None):
|
||||
def request(self, path, data = None, response_callback = None, failed_callback = None, progress_callback = None, timeout = None):
|
||||
"""
|
||||
Sends a request to the remote peer.
|
||||
|
||||
:param path: The request path.
|
||||
:param response_callback: A function or method with the signature *response_callback(request_receipt)* to be called when a response is received. See the :ref:`Request Example<example-request>` for more info.
|
||||
:param failed_callback: A function or method with the signature *failed_callback(request_receipt)* to be called when a request fails. See the :ref:`Request Example<example-request>` for more info.
|
||||
:param response_callback: An optional function or method with the signature *response_callback(request_receipt)* to be called when a response is received. See the :ref:`Request Example<example-request>` for more info.
|
||||
:param failed_callback: An optional function or method with the signature *failed_callback(request_receipt)* to be called when a request fails. See the :ref:`Request Example<example-request>` for more info.
|
||||
:param progress_callback: An optional function or method with the signature *progress_callback(request_receipt)* to be called when progress is made receiving the response. Progress can be accessed as a float between 0.0 and 1.0 by the *request_receipt.progress* property.
|
||||
:param timeout: An optional timeout in seconds for the request. If *None* is supplied it will be calculated based on link RTT.
|
||||
:returns: A :ref:`RNS.RequestReceipt<api-requestreceipt>` instance if the request was sent, or *False* if it was not.
|
||||
"""
|
||||
request_path_hash = RNS.Identity.truncated_hash(path.encode("utf-8"))
|
||||
unpacked_request = [time.time(), request_path_hash, data]
|
||||
packed_request = umsgpack.packb(unpacked_request)
|
||||
|
||||
if timeout == None:
|
||||
timeout = self.rtt * self.traffic_timeout_factor + RNS.Resource.RESPONSE_MAX_GRACE_TIME
|
||||
|
||||
if len(packed_request) <= Link.MDU:
|
||||
request_packet = RNS.Packet(self, packed_request, RNS.Packet.DATA, context = RNS.Packet.REQUEST)
|
||||
packet_receipt = request_packet.send()
|
||||
|
||||
return RequestReceipt(
|
||||
self,
|
||||
packet_receipt = request_packet.send(),
|
||||
response_callback = response_callback,
|
||||
failed_callback = failed_callback
|
||||
)
|
||||
|
||||
if packet_receipt == False:
|
||||
return False
|
||||
else:
|
||||
packet_receipt.set_timeout(timeout)
|
||||
return RequestReceipt(
|
||||
self,
|
||||
packet_receipt = packet_receipt,
|
||||
response_callback = response_callback,
|
||||
failed_callback = failed_callback,
|
||||
progress_callback = progress_callback,
|
||||
timeout = timeout
|
||||
)
|
||||
|
||||
else:
|
||||
request_id = RNS.Identity.truncated_hash(packed_request)
|
||||
RNS.log("Sending request "+RNS.prettyhexrep(request_id)+" as resource.", RNS.LOG_DEBUG)
|
||||
request_resource = RNS.Resource(packed_request, self, request_id = request_id, is_response = False)
|
||||
request_resource = RNS.Resource(packed_request, self, request_id = request_id, is_response = False, timeout = timeout)
|
||||
|
||||
return RequestReceipt(
|
||||
self,
|
||||
resource = request_resource,
|
||||
response_callback = response_callback,
|
||||
failed_callback = failed_callback
|
||||
failed_callback = failed_callback,
|
||||
progress_callback = progress_callback,
|
||||
timeout = timeout
|
||||
)
|
||||
|
||||
|
||||
@@ -394,6 +419,11 @@ class Link:
|
||||
self.shared_key = None
|
||||
self.derived_key = None
|
||||
|
||||
if self.destination != None:
|
||||
if self.destination.direction == RNS.Destination.IN:
|
||||
if self in self.destination.links:
|
||||
self.destination.links.remove(self)
|
||||
|
||||
if self.callbacks.link_closed != None:
|
||||
self.callbacks.link_closed(self)
|
||||
|
||||
@@ -411,9 +441,9 @@ class Link:
|
||||
# Link was initiated, but no response
|
||||
# from destination yet
|
||||
if self.status == Link.PENDING:
|
||||
next_check = self.request_time + self.proof_timeout
|
||||
next_check = self.request_time + self.establishment_timeout
|
||||
sleep_time = next_check - time.time()
|
||||
if time.time() >= self.request_time + self.proof_timeout:
|
||||
if time.time() >= self.request_time + self.establishment_timeout:
|
||||
RNS.log("Link establishment timed out", RNS.LOG_VERBOSE)
|
||||
self.status = Link.CLOSED
|
||||
self.teardown_reason = Link.TIMEOUT
|
||||
@@ -421,10 +451,14 @@ class Link:
|
||||
sleep_time = 0.001
|
||||
|
||||
elif self.status == Link.HANDSHAKE:
|
||||
next_check = self.request_time + self.proof_timeout
|
||||
next_check = self.request_time + self.establishment_timeout
|
||||
sleep_time = next_check - time.time()
|
||||
if time.time() >= self.request_time + self.proof_timeout:
|
||||
RNS.log("Timeout waiting for RTT packet from link initiator", RNS.LOG_DEBUG)
|
||||
if time.time() >= self.request_time + self.establishment_timeout:
|
||||
if self.initiator:
|
||||
RNS.log("Timeout waiting for link request proof", RNS.LOG_DEBUG)
|
||||
else:
|
||||
RNS.log("Timeout waiting for RTT packet from link initiator", RNS.LOG_DEBUG)
|
||||
|
||||
self.status = Link.CLOSED
|
||||
self.teardown_reason = Link.TIMEOUT
|
||||
self.link_closed()
|
||||
@@ -432,7 +466,7 @@ class Link:
|
||||
|
||||
elif self.status == Link.ACTIVE:
|
||||
if time.time() >= self.last_inbound + self.keepalive:
|
||||
sleep_time = self.rtt * self.timeout_factor + Link.STALE_GRACE
|
||||
sleep_time = self.rtt * self.keepalive_timeout_factor + Link.STALE_GRACE
|
||||
self.status = Link.STALE
|
||||
if self.initiator:
|
||||
self.send_keepalive()
|
||||
@@ -462,53 +496,57 @@ class Link:
|
||||
self.had_outbound()
|
||||
|
||||
def handle_request(self, request_id, unpacked_request):
|
||||
requested_at = unpacked_request[0]
|
||||
path_hash = unpacked_request[1]
|
||||
request_data = unpacked_request[2]
|
||||
if self.status == Link.ACTIVE:
|
||||
requested_at = unpacked_request[0]
|
||||
path_hash = unpacked_request[1]
|
||||
request_data = unpacked_request[2]
|
||||
|
||||
if path_hash in self.destination.request_handlers:
|
||||
request_handler = self.destination.request_handlers[path_hash]
|
||||
path = request_handler[0]
|
||||
response_generator = request_handler[1]
|
||||
allow = request_handler[2]
|
||||
allowed_list = request_handler[3]
|
||||
if path_hash in self.destination.request_handlers:
|
||||
request_handler = self.destination.request_handlers[path_hash]
|
||||
path = request_handler[0]
|
||||
response_generator = request_handler[1]
|
||||
allow = request_handler[2]
|
||||
allowed_list = request_handler[3]
|
||||
|
||||
allowed = False
|
||||
if not allow == RNS.Destination.ALLOW_NONE:
|
||||
if allow == RNS.Destination.ALLOW_LIST:
|
||||
if self.__remote_identity.hash in allowed_list:
|
||||
allowed = False
|
||||
if not allow == RNS.Destination.ALLOW_NONE:
|
||||
if allow == RNS.Destination.ALLOW_LIST:
|
||||
if self.__remote_identity.hash in allowed_list:
|
||||
allowed = True
|
||||
elif allow == RNS.Destination.ALLOW_ALL:
|
||||
allowed = True
|
||||
elif allow == RNS.Destination.ALLOW_ALL:
|
||||
allowed = True
|
||||
|
||||
if allowed:
|
||||
RNS.log("Handling request "+RNS.prettyhexrep(request_id)+" for: "+str(path), RNS.LOG_DEBUG)
|
||||
response = response_generator(path, request_data, request_id, self.__remote_identity, requested_at)
|
||||
if response != None:
|
||||
packed_response = umsgpack.packb([request_id, response])
|
||||
if allowed:
|
||||
RNS.log("Handling request "+RNS.prettyhexrep(request_id)+" for: "+str(path), RNS.LOG_DEBUG)
|
||||
response = response_generator(path, request_data, request_id, self.__remote_identity, requested_at)
|
||||
if response != None:
|
||||
packed_response = umsgpack.packb([request_id, response])
|
||||
|
||||
if len(packed_response) <= Link.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)
|
||||
else:
|
||||
identity_string = RNS.prettyhexrep(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)
|
||||
if len(packed_response) <= Link.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)
|
||||
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):
|
||||
remove = None
|
||||
for pending_request in self.pending_requests:
|
||||
if pending_request.request_id == request_id:
|
||||
remove = pending_request
|
||||
try:
|
||||
pending_request.response_received(response_data)
|
||||
except Exception as e:
|
||||
RNS.log("Error occurred while handling response. The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
def handle_response(self, request_id, response_data, response_size, response_transfer_size):
|
||||
if self.status == Link.ACTIVE:
|
||||
remove = None
|
||||
for pending_request in self.pending_requests:
|
||||
if pending_request.request_id == request_id:
|
||||
remove = pending_request
|
||||
try:
|
||||
pending_request.response_size = response_size
|
||||
pending_request.response_transfer_size = response_transfer_size
|
||||
pending_request.response_received(response_data)
|
||||
except Exception as e:
|
||||
RNS.log("Error occurred while handling response. The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
|
||||
break
|
||||
break
|
||||
|
||||
if remove != None:
|
||||
self.pending_requests.remove(remove)
|
||||
if remove != None:
|
||||
self.pending_requests.remove(remove)
|
||||
|
||||
def request_resource_concluded(self, resource):
|
||||
if resource.status == RNS.Resource.COMPLETE:
|
||||
@@ -528,9 +566,12 @@ class Link:
|
||||
request_id = unpacked_response[0]
|
||||
response_data = unpacked_response[1]
|
||||
|
||||
self.handle_response(request_id, response_data)
|
||||
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:
|
||||
if pending_request.request_id == resource.request_id:
|
||||
pending_request.request_timed_out(None)
|
||||
|
||||
def receive(self, packet):
|
||||
self.watchdog_lock = True
|
||||
@@ -589,7 +630,8 @@ class Link:
|
||||
unpacked_response = umsgpack.unpackb(packed_response)
|
||||
request_id = unpacked_response[0]
|
||||
response_data = unpacked_response[1]
|
||||
self.handle_response(request_id, response_data)
|
||||
transfer_size = len(umsgpack.packb(response_data))-2
|
||||
self.handle_response(request_id, response_data, transfer_size, transfer_size)
|
||||
except Exception as e:
|
||||
RNS.log("Error occurred while handling response. The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
|
||||
@@ -606,7 +648,13 @@ class Link:
|
||||
if RNS.ResourceAdvertisement.is_request(packet):
|
||||
RNS.Resource.accept(packet, callback=self.request_resource_concluded)
|
||||
elif RNS.ResourceAdvertisement.is_response(packet):
|
||||
RNS.Resource.accept(packet, callback=self.response_resource_concluded)
|
||||
request_id = RNS.ResourceAdvertisement.get_request_id(packet)
|
||||
for pending_request in self.pending_requests:
|
||||
if pending_request.request_id == request_id:
|
||||
RNS.Resource.accept(packet, callback=self.response_resource_concluded, progress_callback=pending_request.response_resource_progress, request_id = request_id)
|
||||
pending_request.response_size = RNS.ResourceAdvertisement.get_size(packet)
|
||||
pending_request.response_transfer_size = RNS.ResourceAdvertisement.get_transfer_size(packet)
|
||||
pending_request.started_at = time.time()
|
||||
elif self.resource_strategy == Link.ACCEPT_NONE:
|
||||
pass
|
||||
elif self.resource_strategy == Link.ACCEPT_APP:
|
||||
@@ -624,7 +672,11 @@ class Link:
|
||||
resource_hash = plaintext[1:RNS.Identity.HASHLENGTH//8+1]
|
||||
for resource in self.outgoing_resources:
|
||||
if resource.hash == resource_hash:
|
||||
resource.request(plaintext)
|
||||
# We need to check that this request has not been
|
||||
# received before in order to avoid sequencing errors.
|
||||
if not packet.packet_hash in resource.req_hashlist:
|
||||
resource.req_hashlist.append(packet.packet_hash)
|
||||
resource.request(plaintext)
|
||||
|
||||
elif packet.context == RNS.Packet.RESOURCE_HMU:
|
||||
plaintext = self.decrypt(packet.data)
|
||||
@@ -666,32 +718,42 @@ class Link:
|
||||
|
||||
|
||||
def encrypt(self, plaintext):
|
||||
if self.__encryption_disabled:
|
||||
return plaintext
|
||||
try:
|
||||
if not self.fernet:
|
||||
self.fernet = Fernet(base64.urlsafe_b64encode(self.derived_key))
|
||||
try:
|
||||
self.fernet = Fernet(base64.urlsafe_b64encode(self.derived_key))
|
||||
except Exception as e:
|
||||
RNS.log("Could not "+str(self)+" instantiate Fernet while performin encryption on link. The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
raise e
|
||||
|
||||
ciphertext = base64.urlsafe_b64decode(self.fernet.encrypt(plaintext))
|
||||
# The fernet token VERSION field is stripped here and
|
||||
# reinserted on the receiving end, since it is always
|
||||
# set to 0x80.
|
||||
#
|
||||
# Since we're also quite content with supporting time-
|
||||
# stamps until the year 8921556 AD, we'll also strip 2
|
||||
# bytes from the timestamp field and reinsert those as
|
||||
# 0x00 when received.
|
||||
ciphertext = base64.urlsafe_b64decode(self.fernet.encrypt(plaintext))[3:]
|
||||
return ciphertext
|
||||
|
||||
except Exception as e:
|
||||
RNS.log("Encryption on link "+str(self)+" failed. The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
raise e
|
||||
|
||||
|
||||
def decrypt(self, ciphertext):
|
||||
if self.__encryption_disabled:
|
||||
return ciphertext
|
||||
try:
|
||||
if not self.fernet:
|
||||
self.fernet = Fernet(base64.urlsafe_b64encode(self.derived_key))
|
||||
|
||||
plaintext = self.fernet.decrypt(base64.urlsafe_b64encode(ciphertext))
|
||||
plaintext = self.fernet.decrypt(base64.urlsafe_b64encode(bytes([RNS.Identity.FERNET_VERSION, 0x00, 0x00])+ciphertext))
|
||||
return plaintext
|
||||
except Exception as e:
|
||||
RNS.log("Decryption failed on link "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
RNS.log(traceback.format_exc(), RNS.LOG_ERROR)
|
||||
# TODO: Do we really need to do this? Or can we recover somehow?
|
||||
self.teardown()
|
||||
# TODO: Think long about implications here
|
||||
# self.teardown()
|
||||
|
||||
|
||||
def sign(self, message):
|
||||
@@ -799,68 +861,71 @@ class Link:
|
||||
else:
|
||||
return True
|
||||
|
||||
def disable_encryption(self):
|
||||
"""
|
||||
HAZARDOUS. This will downgrade the link to encryptionless. All
|
||||
information over the link will be sent in plaintext. Never use
|
||||
this in production applications. Should only be used for debugging
|
||||
purposes, and will disappear in a future version.
|
||||
|
||||
If encryptionless links are not explicitly allowed in the users
|
||||
configuration file, Reticulum will terminate itself along with the
|
||||
client application and throw an error message to the user.
|
||||
"""
|
||||
if (RNS.Reticulum.should_allow_unencrypted()):
|
||||
RNS.log("The link "+str(self)+" was downgraded to an encryptionless link", RNS.LOG_NOTICE)
|
||||
self.__encryption_disabled = True
|
||||
else:
|
||||
RNS.log("Attempt to disable encryption on link, but encryptionless links are not allowed by config.", RNS.LOG_CRITICAL)
|
||||
RNS.log("Shutting down Reticulum now!", RNS.LOG_CRITICAL)
|
||||
RNS.panic()
|
||||
|
||||
def encryption_disabled(self):
|
||||
return self.__encryption_disabled
|
||||
|
||||
def __str__(self):
|
||||
return RNS.prettyhexrep(self.link_id)
|
||||
|
||||
|
||||
class RequestReceipt():
|
||||
"""
|
||||
An instance of this class is returned by the ``request`` method of ``RNS.Link``
|
||||
instances. It should never be instantiated manually. It provides methods to
|
||||
check status, response time and response data when the request concludes.
|
||||
"""
|
||||
|
||||
FAILED = 0x00
|
||||
SENT = 0x01
|
||||
DELIVERED = 0x02
|
||||
READY = 0x03
|
||||
RECEIVING = 0x03
|
||||
READY = 0x04
|
||||
|
||||
def __init__(self, link, packet_receipt = None, resource = None, response_callback = None, failed_callback = None):
|
||||
def __init__(self, link, packet_receipt = None, resource = None, response_callback = None, failed_callback = None, progress_callback = None, timeout = None):
|
||||
self.packet_receipt = packet_receipt
|
||||
self.resource = resource
|
||||
self.started_at = None
|
||||
|
||||
if self.packet_receipt != None:
|
||||
self.hash = packet_receipt.truncated_hash
|
||||
self.packet_receipt.set_timeout_callback(self.request_timed_out)
|
||||
self.started_at = time.time()
|
||||
|
||||
elif self.resource != None:
|
||||
self.hash = resource.request_id
|
||||
resource.set_callback(self.request_resource_concluded)
|
||||
|
||||
self.link = link
|
||||
self.request_id = self.hash
|
||||
self.link = link
|
||||
self.request_id = self.hash
|
||||
|
||||
self.response = None
|
||||
self.status = RequestReceipt.SENT
|
||||
self.sent_at = time.time()
|
||||
self.timeout = RNS.Packet.TIMEOUT
|
||||
self.concluded_at = None
|
||||
self.response = None
|
||||
self.response_transfer_size = None
|
||||
self.response_size = None
|
||||
self.status = RequestReceipt.SENT
|
||||
self.sent_at = time.time()
|
||||
self.progress = 0
|
||||
self.concluded_at = None
|
||||
self.response_concluded_at = None
|
||||
|
||||
if timeout != None:
|
||||
self.timeout = timeout
|
||||
else:
|
||||
raise ValueError("No timeout specified for request receipt")
|
||||
|
||||
self.callbacks = RequestReceiptCallbacks()
|
||||
self.callbacks.response = response_callback
|
||||
self.callbacks.failed = failed_callback
|
||||
self.callbacks.progress = progress_callback
|
||||
|
||||
self.link.pending_requests.append(self)
|
||||
|
||||
|
||||
def request_resource_concluded(self, resource):
|
||||
if resource.status == RNS.Resource.COMPLETE:
|
||||
RNS.log("Request "+RNS.prettyhexrep(self.request_id)+" successfully sent as resource.", RNS.LOG_DEBUG)
|
||||
self.started_at = time.time()
|
||||
self.status = RequestReceipt.DELIVERED
|
||||
self.__resource_response_timeout = time.time()+self.timeout
|
||||
response_timeout_thread = threading.Thread(target=self.__response_timeout_job)
|
||||
response_timeout_thread.setDaemon(True)
|
||||
response_timeout_thread.start()
|
||||
else:
|
||||
RNS.log("Sending request "+RNS.prettyhexrep(self.request_id)+" as resource failed with status: "+RNS.hexrep([resource.status]), RNS.LOG_DEBUG)
|
||||
self.status = RequestReceipt.FAILED
|
||||
@@ -870,6 +935,16 @@ class RequestReceipt():
|
||||
if self.callbacks.failed != None:
|
||||
self.callbacks.failed(self)
|
||||
|
||||
|
||||
def __response_timeout_job(self):
|
||||
while self.status == RequestReceipt.DELIVERED:
|
||||
now = time.time()
|
||||
if now > self.__resource_response_timeout:
|
||||
self.request_timed_out(None)
|
||||
|
||||
time.sleep(0.1)
|
||||
|
||||
|
||||
def request_timed_out(self, packet_receipt):
|
||||
self.status = RequestReceipt.FAILED
|
||||
self.concluded_at = time.time()
|
||||
@@ -878,21 +953,85 @@ class RequestReceipt():
|
||||
if self.callbacks.failed != None:
|
||||
self.callbacks.failed(self)
|
||||
|
||||
|
||||
def response_resource_progress(self, resource):
|
||||
if not self.status == RequestReceipt.FAILED:
|
||||
self.status = RequestReceipt.RECEIVING
|
||||
if self.packet_receipt != None:
|
||||
self.packet_receipt.status = RNS.PacketReceipt.DELIVERED
|
||||
self.packet_receipt.proved = True
|
||||
self.packet_receipt.concluded_at = time.time()
|
||||
if self.packet_receipt.callbacks.delivery != None:
|
||||
self.packet_receipt.callbacks.delivery(self.packet_receipt)
|
||||
|
||||
self.progress = resource.get_progress()
|
||||
|
||||
if self.callbacks.progress != None:
|
||||
self.callbacks.progress(self)
|
||||
else:
|
||||
resource.cancel()
|
||||
|
||||
|
||||
def response_received(self, response):
|
||||
self.response = response
|
||||
if not self.status == RequestReceipt.FAILED:
|
||||
self.progress = 1.0
|
||||
self.response = response
|
||||
self.status = RequestReceipt.READY
|
||||
self.response_concluded_at = time.time()
|
||||
|
||||
if self.packet_receipt != None:
|
||||
self.packet_receipt.status = RNS.PacketReceipt.DELIVERED
|
||||
self.packet_receipt.proved = True
|
||||
self.packet_receipt.concluded_at = time.time()
|
||||
if self.packet_receipt.callbacks.delivery != None:
|
||||
self.packet_receipt.callbacks.delivery(self)
|
||||
if self.packet_receipt != None:
|
||||
self.packet_receipt.status = RNS.PacketReceipt.DELIVERED
|
||||
self.packet_receipt.proved = True
|
||||
self.packet_receipt.concluded_at = time.time()
|
||||
if self.packet_receipt.callbacks.delivery != None:
|
||||
self.packet_receipt.callbacks.delivery(self.packet_receipt)
|
||||
|
||||
if self.callbacks.progress != None:
|
||||
self.callbacks.progress(self)
|
||||
|
||||
if self.callbacks.response != None:
|
||||
self.callbacks.response(self)
|
||||
|
||||
def get_request_id(self):
|
||||
"""
|
||||
:returns: The request ID as *bytes*.
|
||||
"""
|
||||
return self.request_id
|
||||
|
||||
def get_status(self):
|
||||
"""
|
||||
:returns: The current status of the request, one of ``RNS.RequestReceipt.FAILED``, ``RNS.RequestReceipt.SENT``, ``RNS.RequestReceipt.DELIVERED``, ``RNS.RequestReceipt.READY``.
|
||||
"""
|
||||
return self.status
|
||||
|
||||
def get_progress(self):
|
||||
"""
|
||||
:returns: The progress of a response being received as a *float* between 0.0 and 1.0.
|
||||
"""
|
||||
return self.progress
|
||||
|
||||
def get_response(self):
|
||||
"""
|
||||
:returns: The response as *bytes* if it is ready, otherwise *None*.
|
||||
"""
|
||||
if self.status == RequestReceipt.READY:
|
||||
return self.response
|
||||
else:
|
||||
return None
|
||||
|
||||
def get_response_time(self):
|
||||
"""
|
||||
:returns: The response time of the request in seconds.
|
||||
"""
|
||||
if self.status == RequestReceipt.READY:
|
||||
return self.response_concluded_at - self.started_at
|
||||
else:
|
||||
return None
|
||||
|
||||
if self.callbacks.response != None:
|
||||
self.callbacks.response(self)
|
||||
|
||||
|
||||
class RequestReceiptCallbacks:
|
||||
def __init__(self):
|
||||
self.response = None
|
||||
self.failed = None
|
||||
self.failed = None
|
||||
self.progress = None
|
||||
@@ -20,11 +20,6 @@ class Packet:
|
||||
:param destination: A :ref:`RNS.Destination<api-destination>` instance to which the packet will be sent.
|
||||
:param data: The data payload to be included in the packet as *bytes*.
|
||||
:param create_receipt: Specifies whether a :ref:`RNS.PacketReceipt<api-packetreceipt>` should be created when instantiating the packet.
|
||||
:param type: Internal use by :ref:`RNS.Transport<api-transport>`. Defaults to ``RNS.Packet.DATA``, and should not be specified.
|
||||
:param context: Internal use by :ref:`RNS.Transport<api-transport>`. Ignore.
|
||||
:param transport_type: Internal use by :ref:`RNS.Transport<api-transport>`. Ignore.
|
||||
:param transport_id: Internal use by :ref:`RNS.Transport<api-transport>`. Ignore.
|
||||
:param attached_interface: Internal use by :ref:`RNS.Transport<api-transport>`. Ignore.
|
||||
"""
|
||||
|
||||
# Packet types
|
||||
@@ -71,19 +66,18 @@ class Packet:
|
||||
# With an MTU of 500, the maximum of data we can
|
||||
# send in a single encrypted packet is given by
|
||||
# the below calculation; 383 bytes.
|
||||
ENCRYPTED_MDU = math.floor((RNS.Reticulum.MDU-RNS.Identity.AES_HMAC_OVERHEAD-RNS.Identity.KEYSIZE//16)/RNS.Identity.AES128_BLOCKSIZE)*RNS.Identity.AES128_BLOCKSIZE - 1
|
||||
ENCRYPTED_MDU = math.floor((RNS.Reticulum.MDU-RNS.Identity.FERNET_OVERHEAD-RNS.Identity.KEYSIZE//16)/RNS.Identity.AES128_BLOCKSIZE)*RNS.Identity.AES128_BLOCKSIZE - 1
|
||||
"""
|
||||
The maximum size of the payload data in a single encrypted packet
|
||||
"""
|
||||
PLAIN_MDU = MDU
|
||||
PLAIN_MDU = MDU
|
||||
"""
|
||||
The maximum size of the payload data in a single unencrypted packet
|
||||
"""
|
||||
|
||||
# TODO: This should be calculated
|
||||
# more intelligently
|
||||
# Default packet timeout
|
||||
TIMEOUT = 60
|
||||
# This value is set at a reasonable
|
||||
# level for a 1 Kb/s channel.
|
||||
TIMEOUT_PER_HOP = 5
|
||||
|
||||
def __init__(self, destination, data, packet_type = DATA, context = NONE, transport_type = RNS.Transport.BROADCAST, header_type = HEADER_1, transport_id = None, attached_interface = None, create_receipt = True):
|
||||
if destination != None:
|
||||
@@ -310,8 +304,8 @@ class PacketReceipt:
|
||||
"""
|
||||
The PacketReceipt class is used to receive notifications about
|
||||
:ref:`RNS.Packet<api-packet>` instances sent over the network. Instances
|
||||
of this class should never be created manually, but always returned
|
||||
from a the *send()* method of a :ref:`RNS.Packet<api-packet>` instance.
|
||||
of this class are never created manually, but always returned from
|
||||
the *send()* method of a :ref:`RNS.Packet<api-packet>` instance.
|
||||
"""
|
||||
# Receipt status constants
|
||||
FAILED = 0x00
|
||||
@@ -329,13 +323,18 @@ class PacketReceipt:
|
||||
self.truncated_hash = packet.getTruncatedHash()
|
||||
self.sent = True
|
||||
self.sent_at = time.time()
|
||||
self.timeout = Packet.TIMEOUT
|
||||
self.proved = False
|
||||
self.status = PacketReceipt.SENT
|
||||
self.destination = packet.destination
|
||||
self.callbacks = PacketReceiptCallbacks()
|
||||
self.concluded_at = None
|
||||
|
||||
if packet.destination.type == RNS.Destination.LINK:
|
||||
self.timeout = packet.destination.rtt * packet.destination.traffic_timeout_factor
|
||||
else:
|
||||
self.timeout = Packet.TIMEOUT_PER_HOP * RNS.Transport.hops_to(self.destination.hash)
|
||||
|
||||
|
||||
def get_status(self):
|
||||
"""
|
||||
:returns: The status of the associated :ref:`RNS.Packet<api-packet>` instance. Can be one of ``RNS.PacketReceipt.SENT``, ``RNS.PacketReceipt.DELIVERED``, ``RNS.PacketReceipt.FAILED`` or ``RNS.PacketReceipt.CULLED``.
|
||||
@@ -436,7 +435,7 @@ class PacketReceipt:
|
||||
return (self.sent_at+self.timeout < time.time())
|
||||
|
||||
def check_timeout(self):
|
||||
if self.is_timed_out():
|
||||
if self.status == PacketReceipt.SENT and self.is_timed_out():
|
||||
if self.timeout == -1:
|
||||
self.status = PacketReceipt.CULLED
|
||||
else:
|
||||
@@ -448,7 +447,6 @@ class PacketReceipt:
|
||||
thread = threading.Thread(target=self.callbacks.timeout, args=(self,))
|
||||
thread.setDaemon(True)
|
||||
thread.start()
|
||||
#self.callbacks.timeout(self)
|
||||
|
||||
|
||||
def set_timeout(self, timeout):
|
||||
|
||||
@@ -15,14 +15,10 @@ class Resource:
|
||||
|
||||
:param data: The data to be transferred. Can be *bytes* or an open *file handle*. See the :ref:`Filetransfer Example<example-filetransfer>` for details.
|
||||
:param link: The :ref:`RNS.Link<api-link>` instance on which to transfer the data.
|
||||
:param advertise: Whether to automatically advertise the resource. Can be *True* or *False*.
|
||||
:param auto_compress: Whether to auto-compress the resource. Can be *True* or *False*.
|
||||
:param callback: A *callable* with the signature *callback(resource)*. Will be called when the resource transfer concludes.
|
||||
:param progress_callback: A *callable* with the signature *callback(resource)*. Will be called whenever the resource transfer progress is updated.
|
||||
:param segment_index: Internal use, ignore.
|
||||
:param original_hash: Internal use, ignore.
|
||||
:param is_request: Internal use, ignore.
|
||||
:param is_response: Internal use, ignore.
|
||||
:param advertise: Optional. Whether to automatically advertise the resource. Can be *True* or *False*.
|
||||
:param auto_compress: Optional. Whether to auto-compress the resource. Can be *True* or *False*.
|
||||
:param callback: An optional *callable* with the signature *callback(resource)*. Will be called when the resource transfer concludes.
|
||||
:param progress_callback: An optional *callable* with the signature *callback(resource)*. Will be called whenever the resource transfer progress is updated.
|
||||
"""
|
||||
WINDOW_FLEXIBILITY = 4
|
||||
WINDOW_MIN = 1
|
||||
@@ -36,26 +32,31 @@ class Resource:
|
||||
# maximum size a resource should be, if
|
||||
# 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.
|
||||
MAX_EFFICIENT_SIZE = 16 * 1024 * 1024
|
||||
#
|
||||
# Capped at 16777215 (0xFFFFFF) per segment to
|
||||
# fit in 3 bytes in resource advertisements.
|
||||
MAX_EFFICIENT_SIZE = 16 * 1024 * 1024 - 1
|
||||
RESPONSE_MAX_GRACE_TIME = 10
|
||||
|
||||
# The maximum size to auto-compress with
|
||||
# bz2 before sending.
|
||||
AUTO_COMPRESS_MAX_SIZE = MAX_EFFICIENT_SIZE
|
||||
|
||||
# TODO: Should be allocated more
|
||||
# intelligently
|
||||
# TODO: Set higher
|
||||
MAX_RETRIES = 5
|
||||
SENDER_GRACE_TIME = 10
|
||||
RETRY_GRACE_TIME = 0.25
|
||||
PART_TIMEOUT_FACTOR = 4
|
||||
PART_TIMEOUT_FACTOR_AFTER_RTT = 2
|
||||
MAX_RETRIES = 5
|
||||
SENDER_GRACE_TIME = 10
|
||||
RETRY_GRACE_TIME = 0.25
|
||||
|
||||
WATCHDOG_MAX_SLEEP = 1
|
||||
|
||||
HASHMAP_IS_NOT_EXHAUSTED = 0x00
|
||||
HASHMAP_IS_EXHAUSTED = 0xFF
|
||||
@@ -72,11 +73,11 @@ class Resource:
|
||||
CORRUPT = 0x08
|
||||
|
||||
@staticmethod
|
||||
def accept(advertisement_packet, callback=None, progress_callback = None):
|
||||
def accept(advertisement_packet, callback=None, progress_callback = None, request_id = None):
|
||||
try:
|
||||
adv = ResourceAdvertisement.unpack(advertisement_packet.plaintext)
|
||||
|
||||
resource = Resource(None, advertisement_packet.link)
|
||||
resource = Resource(None, advertisement_packet.link, request_id = request_id)
|
||||
resource.status = Resource.TRANSFERRING
|
||||
|
||||
resource.flags = adv.f
|
||||
@@ -136,7 +137,7 @@ 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, segment_index = 1, original_hash = None, request_id = None, is_response = False):
|
||||
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):
|
||||
data_size = None
|
||||
resource_data = None
|
||||
if hasattr(data, "read"):
|
||||
@@ -183,8 +184,8 @@ class Resource:
|
||||
self.link = link
|
||||
self.max_retries = Resource.MAX_RETRIES
|
||||
self.retries_left = self.max_retries
|
||||
self.default_timeout = self.link.default_timeout
|
||||
self.timeout_factor = self.link.timeout_factor
|
||||
self.timeout_factor = self.link.traffic_timeout_factor
|
||||
self.part_timeout_factor = Resource.PART_TIMEOUT_FACTOR
|
||||
self.sender_grace_time = Resource.SENDER_GRACE_TIME
|
||||
self.hmu_retry_ok = False
|
||||
self.watchdog_lock = False
|
||||
@@ -194,8 +195,14 @@ class Resource:
|
||||
self.request_id = request_id
|
||||
self.is_response = is_response
|
||||
|
||||
self.req_hashlist = []
|
||||
self.receiver_min_consecutive_height = 0
|
||||
|
||||
if timeout != None:
|
||||
self.timeout = timeout
|
||||
else:
|
||||
self.timeout = self.link.rtt * self.link.traffic_timeout_factor
|
||||
|
||||
if data != None:
|
||||
self.initiator = True
|
||||
self.callback = callback
|
||||
@@ -238,11 +245,8 @@ class Resource:
|
||||
# make optimal use of packet MTU on an entire
|
||||
# encrypted stream. The Resource instance will
|
||||
# use it's underlying link directly to encrypt.
|
||||
if not self.link.encryption_disabled():
|
||||
self.data = self.link.encrypt(self.data)
|
||||
self.encrypted = True
|
||||
else:
|
||||
self.encrypted = False
|
||||
self.data = self.link.encrypt(self.data)
|
||||
self.encrypted = True
|
||||
|
||||
self.size = len(self.data)
|
||||
self.sent_parts = 0
|
||||
@@ -370,7 +374,7 @@ class Resource:
|
||||
sleep_time = None
|
||||
|
||||
if self.status == Resource.ADVERTISED:
|
||||
sleep_time = (self.adv_sent+self.default_timeout)-time.time()
|
||||
sleep_time = (self.adv_sent+self.timeout)-time.time()
|
||||
if sleep_time < 0:
|
||||
if self.retries_left <= 0:
|
||||
RNS.log("Resource transfer timeout after sending advertisement", RNS.LOG_DEBUG)
|
||||
@@ -391,8 +395,24 @@ class Resource:
|
||||
|
||||
elif self.status == Resource.TRANSFERRING:
|
||||
if not self.initiator:
|
||||
rtt = self.link.rtt if self.rtt == None else self.rtt
|
||||
sleep_time = self.last_activity + (rtt*self.timeout_factor) + Resource.RETRY_GRACE_TIME - time.time()
|
||||
|
||||
if self.rtt == None:
|
||||
rtt = self.link.rtt
|
||||
else:
|
||||
rtt = self.rtt
|
||||
|
||||
window_remaining = self.outstanding_parts
|
||||
|
||||
sleep_time = self.last_activity + (rtt*(self.part_timeout_factor+window_remaining)) + Resource.RETRY_GRACE_TIME - time.time()
|
||||
|
||||
# TODO: Remove debug info
|
||||
# RNS.log("rtt "+str(rtt))
|
||||
# RNS.log("ptof "+str(self.part_timeout_factor))
|
||||
# RNS.log("wait "+str((rtt*self.part_timeout_factor) + Resource.RETRY_GRACE_TIME))
|
||||
# RNS.log("sleep "+str(sleep_time))
|
||||
# RNS.log("wndw "+str(self.window))
|
||||
# RNS.log("wndwr "+str(window_remaining))
|
||||
# RNS.log("")
|
||||
|
||||
if sleep_time < 0:
|
||||
if self.retries_left > 0:
|
||||
@@ -443,7 +463,7 @@ class Resource:
|
||||
self.cancel()
|
||||
|
||||
if sleep_time != None:
|
||||
sleep(sleep_time)
|
||||
sleep(min(sleep_time, Resource.WATCHDOG_MAX_SLEEP))
|
||||
|
||||
def assemble(self):
|
||||
if not self.status == Resource.FAILED:
|
||||
@@ -542,11 +562,15 @@ class Resource:
|
||||
if self.req_resp == None:
|
||||
self.req_resp = self.last_activity
|
||||
rtt = self.req_resp-self.req_sent
|
||||
|
||||
self.part_timeout_factor = Resource.PART_TIMEOUT_FACTOR_AFTER_RTT
|
||||
if self.rtt == None:
|
||||
self.rtt = rtt
|
||||
self.rtt = self.link.rtt
|
||||
self.watchdog_job()
|
||||
elif self.rtt < rtt:
|
||||
self.rtt = rtt
|
||||
elif rtt < self.rtt:
|
||||
self.rtt = max(self.rtt - self.rtt*0.05, rtt)
|
||||
elif rtt > self.rtt:
|
||||
self.rtt = min(self.rtt + self.rtt*0.05, rtt)
|
||||
|
||||
if not self.status == Resource.FAILED:
|
||||
self.status = Resource.TRANSFERRING
|
||||
@@ -571,14 +595,21 @@ class Resource:
|
||||
self.consecutive_completed_height = cp
|
||||
cp += 1
|
||||
|
||||
if self.__progress_callback != None:
|
||||
self.__progress_callback(self)
|
||||
|
||||
# TODO: Remove debug info
|
||||
# RNS.log("outstanding_parts "+str(self.outstanding_parts))
|
||||
# RNS.log("total_parts "+str(self.total_parts))
|
||||
# RNS.log("received_count "+str(self.received_count))
|
||||
|
||||
i += 1
|
||||
|
||||
self.receiving_part = False
|
||||
|
||||
if self.__progress_callback != None:
|
||||
self.__progress_callback(self)
|
||||
|
||||
if self.outstanding_parts == 0 and self.received_count == self.total_parts:
|
||||
# TODO: Remove
|
||||
#if self.outstanding_parts == 0 and self.received_count == self.total_parts:
|
||||
if self.received_count == self.total_parts:
|
||||
self.assemble()
|
||||
elif self.outstanding_parts == 0:
|
||||
# TODO: Figure out if there is a mathematically
|
||||
@@ -696,6 +727,7 @@ class Resource:
|
||||
if part_index % ResourceAdvertisement.HASHMAP_MAX_LEN != 0:
|
||||
RNS.log("Resource sequencing error, cancelling transfer!", RNS.LOG_ERROR)
|
||||
self.cancel()
|
||||
return
|
||||
else:
|
||||
segment = part_index // ResourceAdvertisement.HASHMAP_MAX_LEN
|
||||
|
||||
@@ -751,7 +783,7 @@ class Resource:
|
||||
def progress_callback(self, callback):
|
||||
self.__progress_callback = callback
|
||||
|
||||
def progress(self):
|
||||
def get_progress(self):
|
||||
"""
|
||||
:returns: The current progress of the resource transfer as a *float* between 0.0 and 1.0.
|
||||
"""
|
||||
@@ -772,13 +804,16 @@ class Resource:
|
||||
return progress
|
||||
|
||||
def __str__(self):
|
||||
return RNS.prettyhexrep(self.hash)+str(self.link)
|
||||
return "<"+RNS.hexrep(self.hash)+"/"+RNS.hexrep(self.link.link_id)+">"
|
||||
|
||||
|
||||
class ResourceAdvertisement:
|
||||
HASHMAP_MAX_LEN = 70
|
||||
OVERHEAD = 128
|
||||
HASHMAP_MAX_LEN = math.floor((RNS.Link.MDU-OVERHEAD)/Resource.MAPHASH_LEN)
|
||||
COLLISION_GUARD_SIZE = 2*Resource.WINDOW_MAX+HASHMAP_MAX_LEN
|
||||
|
||||
assert HASHMAP_MAX_LEN > 0, "The configured MTU is too small to include any map hashes in resource advertisments"
|
||||
|
||||
@staticmethod
|
||||
def is_request(advertisement_packet):
|
||||
adv = ResourceAdvertisement.unpack(advertisement_packet.plaintext)
|
||||
@@ -804,6 +839,18 @@ class ResourceAdvertisement:
|
||||
return adv.q
|
||||
|
||||
|
||||
@staticmethod
|
||||
def get_transfer_size(advertisement_packet):
|
||||
adv = ResourceAdvertisement.unpack(advertisement_packet.plaintext)
|
||||
return adv.t
|
||||
|
||||
|
||||
@staticmethod
|
||||
def get_size(advertisement_packet):
|
||||
adv = ResourceAdvertisement.unpack(advertisement_packet.plaintext)
|
||||
return adv.d
|
||||
|
||||
|
||||
def __init__(self, resource=None, request_id=None, is_response=False):
|
||||
if resource != None:
|
||||
self.t = resource.size # Transfer size
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
from .Interfaces import *
|
||||
import configparser
|
||||
from .vendor.configobj import ConfigObj
|
||||
import configparser
|
||||
import multiprocessing.connection
|
||||
import RNS
|
||||
import signal
|
||||
import threading
|
||||
import atexit
|
||||
import struct
|
||||
import array
|
||||
@@ -36,15 +39,26 @@ class Reticulum:
|
||||
other programs to use on demand.
|
||||
"""
|
||||
|
||||
# The default RNS MTU is 500 bytes. This number has been chosen as
|
||||
# a balance between compatibility with existing hardware devices
|
||||
# on one hand, and the ability to use sufficiently high cryptographic
|
||||
# key sizes on the other. In custom RNS network implementations, it
|
||||
# is possible to raise this value, but doing so will completely break
|
||||
# compatibility with all other RNS networks. An identical MTU is a
|
||||
# prerequisite for peers to communicate in the same network.
|
||||
# Future minimum will probably be locked in at 244 bytes to support
|
||||
# networks with segments of different MTUs. Absolute minimum is 211.
|
||||
MTU = 500
|
||||
HEADER_MAXSIZE = 23
|
||||
"""
|
||||
The MTU that Reticulum adheres to, and will expect other peers to
|
||||
adhere to. By default, the MTU is 500 bytes. In custom RNS network
|
||||
implementations, it is possible to change this value, but doing so will
|
||||
completely break compatibility with all other RNS networks. An identical
|
||||
MTU is a prerequisite for peers to communicate in the same network.
|
||||
|
||||
Unless you really know what you are doing, the MTU should be left at
|
||||
the default value.
|
||||
"""
|
||||
|
||||
# Length of truncated hashes in bits.
|
||||
TRUNCATED_HASHLENGTH = 80
|
||||
|
||||
HEADER_MINSIZE = 2+1+(TRUNCATED_HASHLENGTH//8)*1
|
||||
HEADER_MAXSIZE = 2+1+(TRUNCATED_HASHLENGTH//8)*2
|
||||
|
||||
MDU = MTU - HEADER_MAXSIZE
|
||||
|
||||
router = None
|
||||
@@ -67,7 +81,13 @@ class Reticulum:
|
||||
RNS.Transport.exit_handler()
|
||||
RNS.Identity.exit_handler()
|
||||
|
||||
def __init__(self,configdir=None):
|
||||
@staticmethod
|
||||
def sigint_handler(signal, frame):
|
||||
RNS.Transport.detach_interfaces()
|
||||
RNS.exit()
|
||||
|
||||
|
||||
def __init__(self,configdir=None, loglevel=None):
|
||||
"""
|
||||
Initialises and starts a Reticulum instance. This must be
|
||||
done before any other operations, and Reticulum will not
|
||||
@@ -84,12 +104,24 @@ class Reticulum:
|
||||
Reticulum.cachepath = Reticulum.configdir+"/storage/cache"
|
||||
Reticulum.resourcepath = Reticulum.configdir+"/storage/resources"
|
||||
|
||||
Reticulum.__allow_unencrypted = False
|
||||
Reticulum.__transport_enabled = False
|
||||
Reticulum.__use_implicit_proof = True
|
||||
|
||||
Reticulum.panic_on_interface_error = False
|
||||
|
||||
self.local_interface_port = 37428
|
||||
self.share_instance = True
|
||||
self.local_control_port = 37429
|
||||
self.share_instance = True
|
||||
self.rpc_listener = None
|
||||
|
||||
self.requested_loglevel = loglevel
|
||||
if self.requested_loglevel != None:
|
||||
if self.requested_loglevel > RNS.LOG_EXTREME:
|
||||
self.requested_loglevel = RNS.LOG_EXTREME
|
||||
if self.requested_loglevel < RNS.LOG_CRITICAL:
|
||||
self.requested_loglevel = RNS.LOG_CRITICAL
|
||||
|
||||
RNS.loglevel = self.requested_loglevel
|
||||
|
||||
self.is_shared_instance = False
|
||||
self.is_connected_to_shared_instance = False
|
||||
@@ -107,7 +139,6 @@ class Reticulum:
|
||||
if os.path.isfile(self.configpath):
|
||||
try:
|
||||
self.config = ConfigObj(self.configpath)
|
||||
RNS.log("Configuration loaded from "+self.configpath)
|
||||
except Exception as e:
|
||||
RNS.log("Could not parse the configuration at "+self.configpath, RNS.LOG_ERROR)
|
||||
RNS.log("Check your configuration file for errors!", RNS.LOG_ERROR)
|
||||
@@ -120,11 +151,23 @@ class Reticulum:
|
||||
exit(1)
|
||||
|
||||
self.__apply_config()
|
||||
RNS.log("Configuration loaded from "+self.configpath, RNS.LOG_VERBOSE)
|
||||
|
||||
RNS.Identity.load_known_destinations()
|
||||
|
||||
RNS.Transport.start(self)
|
||||
|
||||
self.rpc_addr = ("127.0.0.1", self.local_control_port)
|
||||
self.rpc_key = RNS.Identity.full_hash(RNS.Transport.identity.get_private_key())
|
||||
|
||||
if self.is_shared_instance:
|
||||
self.rpc_listener = multiprocessing.connection.Listener(self.rpc_addr, authkey=self.rpc_key)
|
||||
thread = threading.Thread(target=self.rpc_loop)
|
||||
thread.setDaemon(True)
|
||||
thread.start()
|
||||
|
||||
atexit.register(Reticulum.exit_handler)
|
||||
signal.signal(signal.SIGINT, Reticulum.sigint_handler)
|
||||
|
||||
def __start_local_interface(self):
|
||||
if self.share_instance:
|
||||
@@ -135,6 +178,7 @@ class Reticulum:
|
||||
)
|
||||
interface.OUT = True
|
||||
RNS.Transport.interfaces.append(interface)
|
||||
|
||||
self.is_shared_instance = True
|
||||
RNS.log("Started shared instance interface: "+str(interface), RNS.LOG_DEBUG)
|
||||
except Exception as e:
|
||||
@@ -149,6 +193,7 @@ class Reticulum:
|
||||
self.is_shared_instance = False
|
||||
self.is_standalone_instance = False
|
||||
self.is_connected_to_shared_instance = True
|
||||
Reticulum.__transport_enabled = False
|
||||
RNS.log("Connected to local shared instance via: "+str(interface), RNS.LOG_DEBUG)
|
||||
except Exception as e:
|
||||
RNS.log("Local shared instance appears to be running, but it could not be connected", RNS.LOG_ERROR)
|
||||
@@ -165,7 +210,7 @@ class Reticulum:
|
||||
if "logging" in self.config:
|
||||
for option in self.config["logging"]:
|
||||
value = self.config["logging"][option]
|
||||
if option == "loglevel":
|
||||
if option == "loglevel" and self.requested_loglevel == None:
|
||||
RNS.loglevel = int(value)
|
||||
if RNS.loglevel < 0:
|
||||
RNS.loglevel = 0
|
||||
@@ -181,30 +226,23 @@ class Reticulum:
|
||||
if option == "shared_instance_port":
|
||||
value = int(self.config["reticulum"][option])
|
||||
self.local_interface_port = value
|
||||
if option == "instance_control_port":
|
||||
value = int(self.config["reticulum"][option])
|
||||
self.local_control_port = value
|
||||
if option == "enable_transport":
|
||||
v = self.config["reticulum"].as_bool(option)
|
||||
if v == True:
|
||||
Reticulum.__transport_enabled = True
|
||||
if option == "panic_on_interface_error":
|
||||
v = self.config["reticulum"].as_bool(option)
|
||||
if v == True:
|
||||
Reticulum.panic_on_interface_error = True
|
||||
if option == "use_implicit_proof":
|
||||
v = self.config["reticulum"].as_bool(option)
|
||||
if v == True:
|
||||
Reticulum.__use_implicit_proof = True
|
||||
if v == False:
|
||||
Reticulum.__use_implicit_proof = False
|
||||
if option == "allow_unencrypted":
|
||||
v = self.config["reticulum"].as_bool(option)
|
||||
if v == True:
|
||||
RNS.log("", RNS.LOG_CRITICAL)
|
||||
RNS.log("! ! ! ! ! ! ! ! !", RNS.LOG_CRITICAL)
|
||||
RNS.log("", RNS.LOG_CRITICAL)
|
||||
RNS.log("Danger! Encryptionless links have been allowed in the config file!", RNS.LOG_CRITICAL)
|
||||
RNS.log("Beware of the consequences! Any data sent over a link can potentially be intercepted,", RNS.LOG_CRITICAL)
|
||||
RNS.log("read and modified! If you are not absolutely sure that you want this,", RNS.LOG_CRITICAL)
|
||||
RNS.log("you should exit Reticulum NOW and change your config file!", RNS.LOG_CRITICAL)
|
||||
RNS.log("", RNS.LOG_CRITICAL)
|
||||
RNS.log("! ! ! ! ! ! ! ! !", RNS.LOG_CRITICAL)
|
||||
RNS.log("", RNS.LOG_CRITICAL)
|
||||
Reticulum.__allow_unencrypted = True
|
||||
|
||||
self.__start_local_interface()
|
||||
|
||||
@@ -309,7 +347,7 @@ class Reticulum:
|
||||
stopbits
|
||||
)
|
||||
|
||||
if "outgoing" in c and c["outgoing"].lower() == "true":
|
||||
if "outgoing" in c and c.as_bool("outgoing") == True:
|
||||
interface.OUT = True
|
||||
else:
|
||||
interface.OUT = False
|
||||
@@ -350,7 +388,7 @@ class Reticulum:
|
||||
beacon_data
|
||||
)
|
||||
|
||||
if "outgoing" in c and c["outgoing"].lower() == "true":
|
||||
if "outgoing" in c and c.as_bool("outgoing") == True:
|
||||
interface.OUT = True
|
||||
else:
|
||||
interface.OUT = False
|
||||
@@ -392,7 +430,7 @@ class Reticulum:
|
||||
flow_control
|
||||
)
|
||||
|
||||
if "outgoing" in c and c["outgoing"].lower() == "true":
|
||||
if "outgoing" in c and c.as_bool("outgoing") == True:
|
||||
interface.OUT = True
|
||||
else:
|
||||
interface.OUT = False
|
||||
@@ -428,14 +466,14 @@ class Reticulum:
|
||||
id_callsign = id_callsign
|
||||
)
|
||||
|
||||
if "outgoing" in c and c["outgoing"].lower() == "true":
|
||||
if "outgoing" in c and c.as_bool("outgoing") == True:
|
||||
interface.OUT = True
|
||||
else:
|
||||
interface.OUT = False
|
||||
|
||||
RNS.Transport.interfaces.append(interface)
|
||||
else:
|
||||
RNS.log("Skipping disabled interface \""+name+"\"", RNS.LOG_NOTICE)
|
||||
RNS.log("Skipping disabled interface \""+name+"\"", RNS.LOG_INFO)
|
||||
|
||||
except Exception as e:
|
||||
RNS.log("The interface \""+name+"\" could not be created. Check your configuration file for errors!", RNS.LOG_ERROR)
|
||||
@@ -455,15 +493,70 @@ class Reticulum:
|
||||
self.config.write()
|
||||
self.__apply_config()
|
||||
|
||||
@staticmethod
|
||||
def should_allow_unencrypted():
|
||||
"""
|
||||
Returns whether unencrypted links are allowed by the
|
||||
current configuration.
|
||||
def rpc_loop(self):
|
||||
while True:
|
||||
try:
|
||||
rpc_connection = self.rpc_listener.accept()
|
||||
call = rpc_connection.recv()
|
||||
|
||||
if "get" in call:
|
||||
path = call["get"]
|
||||
|
||||
if path == "interface_stats":
|
||||
rpc_connection.send(self.get_interface_stats())
|
||||
|
||||
if path == "next_hop_if_name":
|
||||
rpc_connection.send(self.get_next_hop_if_name(call["destination_hash"]))
|
||||
|
||||
if path == "next_hop":
|
||||
rpc_connection.send(self.get_next_hop(call["destination_hash"]))
|
||||
|
||||
rpc_connection.close()
|
||||
except Exception as e:
|
||||
RNS.log("An error ocurred while handling RPC call from local client: "+str(e), RNS.LOG_ERROR)
|
||||
|
||||
def get_interface_stats(self):
|
||||
if self.is_connected_to_shared_instance:
|
||||
rpc_connection = multiprocessing.connection.Client(self.rpc_addr, authkey=self.rpc_key)
|
||||
rpc_connection.send({"get": "interface_stats"})
|
||||
response = rpc_connection.recv()
|
||||
return response
|
||||
else:
|
||||
stats = []
|
||||
for interface in RNS.Transport.interfaces:
|
||||
ifstats = {}
|
||||
|
||||
if hasattr(interface, "clients"):
|
||||
ifstats["clients"] = interface.clients
|
||||
else:
|
||||
ifstats["clients"] = None
|
||||
|
||||
ifstats["name"] = str(interface)
|
||||
ifstats["rxb"] = interface.rxb
|
||||
ifstats["txb"] = interface.txb
|
||||
ifstats["status"] = interface.online
|
||||
stats.append(ifstats)
|
||||
|
||||
return stats
|
||||
|
||||
def get_next_hop_if_name(self, destination):
|
||||
if self.is_connected_to_shared_instance:
|
||||
rpc_connection = multiprocessing.connection.Client(self.rpc_addr, authkey=self.rpc_key)
|
||||
rpc_connection.send({"get": "next_hop_if_name", "destination_hash": destination})
|
||||
response = rpc_connection.recv()
|
||||
return response
|
||||
else:
|
||||
return str(RNS.Transport.next_hop_interface(destination))
|
||||
|
||||
def get_next_hop(self, destination):
|
||||
if self.is_connected_to_shared_instance:
|
||||
rpc_connection = multiprocessing.connection.Client(self.rpc_addr, authkey=self.rpc_key)
|
||||
rpc_connection.send({"get": "next_hop", "destination_hash": destination})
|
||||
response = rpc_connection.recv()
|
||||
return response
|
||||
else:
|
||||
return RNS.Transport.next_hop(destination)
|
||||
|
||||
:returns: True if the current running configuration allows downgrading links to plaintext. False if not.
|
||||
"""
|
||||
return Reticulum.__allow_unencrypted
|
||||
|
||||
@staticmethod
|
||||
def should_use_implicit_proof():
|
||||
@@ -495,14 +588,6 @@ __default_rns_config__ = '''# This is the default Reticulum config file.
|
||||
|
||||
[reticulum]
|
||||
|
||||
# Don't allow unencrypted links by default.
|
||||
# If you REALLY need to allow unencrypted links, for example
|
||||
# for debug or regulatory purposes, this can be set to true.
|
||||
# This directive is optional and can be removed for brevity.
|
||||
|
||||
allow_unencrypted = False
|
||||
|
||||
|
||||
# If you enable Transport, your system will route traffic
|
||||
# for other peers, pass announces and serve path requests.
|
||||
# This should be done for systems that are suited to act
|
||||
@@ -526,11 +611,21 @@ share_instance = Yes
|
||||
|
||||
|
||||
# If you want to run multiple *different* shared instances
|
||||
# on the same system, you will need to specify a different
|
||||
# shared instance port for each. The default is given below,
|
||||
# and again, this option is optional and can be left out.
|
||||
# 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.
|
||||
|
||||
shared_instance_port = 37428
|
||||
instance_control_port = 37429
|
||||
|
||||
# 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
|
||||
|
||||
|
||||
[logging]
|
||||
@@ -566,15 +661,30 @@ loglevel = 4
|
||||
type = UDPInterface
|
||||
interface_enabled = True
|
||||
outgoing = True
|
||||
device = eth0
|
||||
port = 4242
|
||||
listen_ip = 0.0.0.0
|
||||
listen_port = 4242
|
||||
forward_ip = 255.255.255.255
|
||||
forward_port = 4242
|
||||
|
||||
# The above configuration will allow communication
|
||||
# within the local broadcast domains of all local
|
||||
# IP interfaces. This is enabled by default as an
|
||||
# easy way to get started, but you might want to
|
||||
# consider altering it to something more specific.
|
||||
|
||||
# Instead of specifying listen_ip, listen_port,
|
||||
# forward_ip and forward_port, you can also bind
|
||||
# to a specific network device like below.
|
||||
|
||||
# device = eth0
|
||||
# port = 4242
|
||||
|
||||
# Assuming the eth0 device has the address
|
||||
# 10.55.0.72/24, the above configuration would
|
||||
# be equivalent to the following manual setup.
|
||||
# Note that we are both listening and forwarding
|
||||
# to the network segments broadcast address.
|
||||
|
||||
# Note that we are both listening and forwarding to
|
||||
# the broadcast address of the network segments.
|
||||
|
||||
# listen_ip = 10.55.0.255
|
||||
# listen_port = 4242
|
||||
# forward_ip = 10.55.0.255
|
||||
|
||||
@@ -9,6 +9,10 @@ from time import sleep
|
||||
from .vendor import umsgpack as umsgpack
|
||||
|
||||
class Transport:
|
||||
"""
|
||||
Through static methods of this class you can interact with the
|
||||
Transport system of Reticulum.
|
||||
"""
|
||||
# Constants
|
||||
BROADCAST = 0x00;
|
||||
TRANSPORT = 0x01;
|
||||
@@ -22,41 +26,45 @@ class Transport:
|
||||
|
||||
APP_NAME = "rnstransport"
|
||||
|
||||
PATHFINDER_M = 18 # Max hops
|
||||
PATHFINDER_C = 2.0 # Decay constant
|
||||
PATHFINDER_R = 1 # Retransmit retries
|
||||
PATHFINDER_T = 10 # Retry grace period
|
||||
PATHFINDER_RW = 10 # Random window for announce rebroadcast
|
||||
PATHFINDER_E = 60*15 # Path expiration in seconds
|
||||
PATHFINDER_M = 128 # Max hops
|
||||
"""
|
||||
Maximum amount of hops that Reticulum will transport a packet.
|
||||
"""
|
||||
PATHFINDER_C = 2.0 # Decay constant
|
||||
PATHFINDER_R = 1 # Retransmit retries
|
||||
PATHFINDER_T = 10 # Retry grace period
|
||||
PATHFINDER_RW = 10 # Random window for announce rebroadcast
|
||||
PATHFINDER_E = 60*60*24*7 # Path expiration in seconds
|
||||
|
||||
# TODO: Calculate an optimal number for this in
|
||||
# various situations
|
||||
LOCAL_REBROADCASTS_MAX = 2 # How many local rebroadcasts of an announce is allowed
|
||||
LOCAL_REBROADCASTS_MAX = 2 # How many local rebroadcasts of an announce is allowed
|
||||
|
||||
PATH_REQUEST_GRACE = 0.35 # Grace time before a path announcement is made, allows directly reachable peers to respond first
|
||||
PATH_REQUEST_RW = 2 # Path request random window
|
||||
PATH_REQUEST_GRACE = 0.35 # Grace time before a path announcement is made, allows directly reachable peers to respond first
|
||||
PATH_REQUEST_RW = 2 # Path request random window
|
||||
|
||||
LINK_TIMEOUT = RNS.Link.KEEPALIVE * 2
|
||||
REVERSE_TIMEOUT = 30*60 # Reverse table entries are removed after max 30 minutes
|
||||
DESTINATION_TIMEOUT = 60*60*24*7 # Destination table entries are removed if unused for one week
|
||||
MAX_RECEIPTS = 1024 # Maximum number of receipts to keep track of
|
||||
REVERSE_TIMEOUT = 30*60 # Reverse table entries are removed after max 30 minutes
|
||||
DESTINATION_TIMEOUT = PATHFINDER_E # Destination table entries are removed if unused for one week
|
||||
MAX_RECEIPTS = 1024 # Maximum number of receipts to keep track of
|
||||
|
||||
interfaces = [] # All active interfaces
|
||||
destinations = [] # All active destinations
|
||||
pending_links = [] # Links that are being established
|
||||
active_links = [] # Links that are active
|
||||
packet_hashlist = [] # A list of packet hashes for duplicate detection
|
||||
receipts = [] # Receipts of all outgoing packets for proof processing
|
||||
interfaces = [] # All active interfaces
|
||||
destinations = [] # All active destinations
|
||||
pending_links = [] # Links that are being established
|
||||
active_links = [] # Links that are active
|
||||
packet_hashlist = [] # A list of packet hashes for duplicate detection
|
||||
receipts = [] # Receipts of all outgoing packets for proof processing
|
||||
|
||||
# TODO: "destination_table" should really be renamed to "path_table"
|
||||
# Notes on memory usage: 1 megabyte of memory can store approximately
|
||||
# 55.100 path table entries or approximately 22.300 link table entries.
|
||||
announce_table = {} # A table for storing announces currently waiting to be retransmitted
|
||||
destination_table = {} # A lookup table containing the next hop to a given destination
|
||||
reverse_table = {} # A lookup table for storing packet hashes used to return proofs and replies
|
||||
link_table = {} # A lookup table containing hops for links
|
||||
held_announces = {} # A table containing temporarily held announce-table entries
|
||||
announce_handlers = [] # A table storing externally registered announce handlers
|
||||
announce_table = {} # A table for storing announces currently waiting to be retransmitted
|
||||
destination_table = {} # A lookup table containing the next hop to a given destination
|
||||
reverse_table = {} # A lookup table for storing packet hashes used to return proofs and replies
|
||||
link_table = {} # A lookup table containing hops for links
|
||||
held_announces = {} # A table containing temporarily held announce-table entries
|
||||
announce_handlers = [] # A table storing externally registered announce handlers
|
||||
tunnels = {} # A table storing tunnels to other transport instances
|
||||
|
||||
# Transport control destinations are used
|
||||
# for control purposes like path requests
|
||||
@@ -112,12 +120,19 @@ class Transport:
|
||||
Transport.control_destinations.append(Transport.path_request_destination)
|
||||
Transport.control_hashes.append(Transport.path_request_destination.hash)
|
||||
|
||||
Transport.tunnel_synthesize_destination = RNS.Destination(None, RNS.Destination.IN, RNS.Destination.PLAIN, Transport.APP_NAME, "tunnel", "synthesize")
|
||||
Transport.tunnel_synthesize_destination.set_packet_callback(Transport.tunnel_synthesize_handler)
|
||||
Transport.control_destinations.append(Transport.tunnel_synthesize_handler)
|
||||
Transport.control_hashes.append(Transport.tunnel_synthesize_destination.hash)
|
||||
|
||||
thread = threading.Thread(target=Transport.jobloop)
|
||||
thread.setDaemon(True)
|
||||
thread.start()
|
||||
|
||||
if RNS.Reticulum.transport_enabled():
|
||||
destination_table_path = RNS.Reticulum.storagepath+"/destination_table"
|
||||
tunnel_table_path = RNS.Reticulum.storagepath+"/tunnels"
|
||||
|
||||
if os.path.isfile(destination_table_path) and not Transport.owner.is_connected_to_shared_instance:
|
||||
serialised_destinations = []
|
||||
try:
|
||||
@@ -161,7 +176,63 @@ class Transport:
|
||||
except Exception as e:
|
||||
RNS.log("Could not load destination table from storage, the contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
|
||||
RNS.log("Transport instance "+str(Transport.identity)+" started")
|
||||
if os.path.isfile(tunnel_table_path) and not Transport.owner.is_connected_to_shared_instance:
|
||||
serialised_tunnels = []
|
||||
try:
|
||||
file = open(tunnel_table_path, "rb")
|
||||
serialised_tunnels = umsgpack.unpackb(file.read())
|
||||
file.close()
|
||||
|
||||
for serialised_tunnel in serialised_tunnels:
|
||||
tunnel_id = serialised_tunnel[0]
|
||||
interface_hash = serialised_tunnel[1]
|
||||
serialised_paths = serialised_tunnel[2]
|
||||
expires = serialised_tunnel[3]
|
||||
|
||||
tunnel_paths = {}
|
||||
for serialised_entry in serialised_paths:
|
||||
destination_hash = serialised_entry[0]
|
||||
timestamp = serialised_entry[1]
|
||||
received_from = serialised_entry[2]
|
||||
hops = serialised_entry[3]
|
||||
expires = serialised_entry[4]
|
||||
random_blobs = serialised_entry[5]
|
||||
receiving_interface = Transport.find_interface_from_hash(serialised_entry[6])
|
||||
announce_packet = Transport.get_cached_packet(serialised_entry[7])
|
||||
|
||||
if announce_packet != None:
|
||||
announce_packet.unpack()
|
||||
# We increase the hops, since reading a packet
|
||||
# from cache is equivalent to receiving it again
|
||||
# over an interface. It is cached with it's non-
|
||||
# increased hop-count.
|
||||
announce_packet.hops += 1
|
||||
|
||||
tunnel_path = [timestamp, received_from, hops, expires, random_blobs, receiving_interface, announce_packet]
|
||||
tunnel_paths[destination_hash] = tunnel_path
|
||||
|
||||
tunnel = [tunnel_id, None, tunnel_paths, expires]
|
||||
Transport.tunnels[tunnel_id] = tunnel
|
||||
|
||||
if len(Transport.destination_table) == 1:
|
||||
specifier = "entry"
|
||||
else:
|
||||
specifier = "entries"
|
||||
|
||||
RNS.log("Loaded "+str(len(Transport.tunnels))+" tunnel table "+specifier+" from storage", RNS.LOG_VERBOSE)
|
||||
|
||||
except Exception as e:
|
||||
RNS.log("Could not load tunnel table from storage, the contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
|
||||
|
||||
|
||||
RNS.log("Transport instance "+str(Transport.identity)+" started", RNS.LOG_VERBOSE)
|
||||
|
||||
# Synthesize tunnels for any interfaces wanting it
|
||||
for interface in Transport.interfaces:
|
||||
interface.tunnel_id = None
|
||||
if hasattr(interface, "wants_tunnel") and interface.wants_tunnel:
|
||||
Transport.synthesize_tunnel(interface)
|
||||
|
||||
@staticmethod
|
||||
def jobloop():
|
||||
@@ -180,7 +251,7 @@ class Transport:
|
||||
while len(Transport.receipts) > Transport.MAX_RECEIPTS:
|
||||
culled_receipt = Transport.receipts.pop(0)
|
||||
culled_receipt.timeout = -1
|
||||
receipt.check_timeout()
|
||||
culled_receipt.check_timeout()
|
||||
|
||||
for receipt in Transport.receipts:
|
||||
receipt.check_timeout()
|
||||
@@ -246,15 +317,16 @@ class Transport:
|
||||
|
||||
|
||||
# Cull the packet hashlist if it has reached max size
|
||||
while (len(Transport.packet_hashlist) > Transport.hashlist_maxsize):
|
||||
Transport.packet_hashlist.pop(0)
|
||||
if len(Transport.packet_hashlist) > Transport.hashlist_maxsize:
|
||||
Transport.packet_hashlist = Transport.packet_hashlist[len(Transport.packet_hashlist)-Transport.hashlist_maxsize:len(Transport.packet_hashlist)-1]
|
||||
|
||||
if time.time() > Transport.tables_last_culled + Transport.tables_cull_interval:
|
||||
# Cull the reverse table according to timeout
|
||||
stale_reverse_entries = []
|
||||
for truncated_packet_hash in Transport.reverse_table:
|
||||
reverse_entry = Transport.reverse_table[truncated_packet_hash]
|
||||
if time.time() > reverse_entry[2] + Transport.REVERSE_TIMEOUT:
|
||||
Transport.reverse_table.pop(truncated_packet_hash)
|
||||
stale_reverse_entries.append(truncated_packet_hash)
|
||||
|
||||
# Cull the link table according to timeout
|
||||
stale_links = []
|
||||
@@ -272,11 +344,56 @@ class Transport:
|
||||
if time.time() > destination_entry[0] + Transport.DESTINATION_TIMEOUT:
|
||||
stale_paths.append(destination_hash)
|
||||
RNS.log("Path to "+RNS.prettyhexrep(destination_hash)+" timed out and was removed", RNS.LOG_DEBUG)
|
||||
|
||||
if not attached_interface in Transport.interfaces:
|
||||
elif not attached_interface in Transport.interfaces:
|
||||
stale_paths.append(destination_hash)
|
||||
RNS.log("Path to "+RNS.prettyhexrep(destination_hash)+" was removed since the attached interface no longer exists", RNS.LOG_DEBUG)
|
||||
|
||||
# Cull the tunnel table
|
||||
stale_tunnels = []
|
||||
ti = 0
|
||||
for tunnel_id in Transport.tunnels:
|
||||
tunnel_entry = Transport.tunnels[tunnel_id]
|
||||
|
||||
expires = tunnel_entry[3]
|
||||
if time.time() > expires:
|
||||
stale_tunnels.append(tunnel_id)
|
||||
RNS.log("Tunnel "+RNS.prettyhexrep(tunnel_id)+" timed out and was removed", RNS.LOG_DEBUG)
|
||||
else:
|
||||
stale_tunnel_paths = []
|
||||
tunnel_paths = tunnel_entry[2]
|
||||
for tunnel_path in tunnel_paths:
|
||||
tunnel_path_entry = tunnel_paths[tunnel_path]
|
||||
|
||||
if time.time() > tunnel_path_entry[0] + Transport.DESTINATION_TIMEOUT:
|
||||
stale_tunnel_paths.append(tunnel_path)
|
||||
RNS.log("Tunnel path to "+RNS.prettyhexrep(tunnel_path)+" timed out and was removed", RNS.LOG_DEBUG)
|
||||
|
||||
for tunnel_path in stale_tunnel_paths:
|
||||
tunnel_paths.pop(tunnel_path)
|
||||
ti += 1
|
||||
|
||||
|
||||
if ti > 0:
|
||||
if ti == 1:
|
||||
RNS.log("Removed "+str(ti)+" tunnel path", RNS.LOG_DEBUG)
|
||||
else:
|
||||
RNS.log("Removed "+str(ti)+" tunnel paths", RNS.LOG_DEBUG)
|
||||
|
||||
|
||||
|
||||
i = 0
|
||||
for truncated_packet_hash in stale_reverse_entries:
|
||||
Transport.reverse_table.pop(truncated_packet_hash)
|
||||
i += 1
|
||||
|
||||
if i > 0:
|
||||
if i == 1:
|
||||
RNS.log("Dropped "+str(i)+" reverse table entry", RNS.LOG_DEBUG)
|
||||
else:
|
||||
RNS.log("Dropped "+str(i)+" reverse table entries", RNS.LOG_DEBUG)
|
||||
|
||||
|
||||
|
||||
i = 0
|
||||
for link_id in stale_links:
|
||||
Transport.link_table.pop(link_id)
|
||||
@@ -299,6 +416,17 @@ class Transport:
|
||||
else:
|
||||
RNS.log("Removed "+str(i)+" paths", RNS.LOG_DEBUG)
|
||||
|
||||
i = 0
|
||||
for tunnel_id in stale_tunnels:
|
||||
Transport.tunnels.pop(tunnel_id)
|
||||
i += 1
|
||||
|
||||
if i > 0:
|
||||
if i == 1:
|
||||
RNS.log("Removed "+str(i)+" tunnel", RNS.LOG_DEBUG)
|
||||
else:
|
||||
RNS.log("Removed "+str(i)+" tunnels", RNS.LOG_DEBUG)
|
||||
|
||||
Transport.tables_last_culled = time.time()
|
||||
|
||||
except Exception as e:
|
||||
@@ -338,8 +466,6 @@ class Transport:
|
||||
new_raw += packet.raw[1:2]
|
||||
new_raw += Transport.destination_table[packet.destination_hash][1]
|
||||
new_raw += packet.raw[2:]
|
||||
# TODO: Remove at some point
|
||||
# RNS.log("Packet was inserted into transport via "+RNS.prettyhexrep(Transport.destination_table[packet.destination_hash][1])+" on: "+str(outbound_interface), RNS.LOG_EXTREME)
|
||||
outbound_interface.processOutgoing(new_raw)
|
||||
Transport.destination_table[packet.destination_hash][0] = time.time()
|
||||
sent = True
|
||||
@@ -359,8 +485,6 @@ class Transport:
|
||||
new_raw += packet.raw[1:2]
|
||||
new_raw += Transport.destination_table[packet.destination_hash][1]
|
||||
new_raw += packet.raw[2:]
|
||||
# TODO: Remove at some point
|
||||
# RNS.log("Packet was inserted into transport via "+RNS.prettyhexrep(Transport.destination_table[packet.destination_hash][1])+" on: "+str(outbound_interface), RNS.LOG_EXTREME)
|
||||
outbound_interface.processOutgoing(new_raw)
|
||||
Transport.destination_table[packet.destination_hash][0] = time.time()
|
||||
sent = True
|
||||
@@ -390,11 +514,9 @@ class Transport:
|
||||
should_transmit = False
|
||||
|
||||
if should_transmit:
|
||||
RNS.log("Transmitting "+str(len(packet.raw))+" bytes on: "+str(interface), RNS.LOG_EXTREME)
|
||||
RNS.log("Hash is "+RNS.prettyhexrep(packet.packet_hash), RNS.LOG_EXTREME)
|
||||
|
||||
if not stored_hash:
|
||||
Transport.packet_hashlist.append(packet.packet_hash)
|
||||
stored_hash = True
|
||||
|
||||
interface.processOutgoing(packet.raw)
|
||||
sent = True
|
||||
@@ -452,8 +574,7 @@ class Transport:
|
||||
@staticmethod
|
||||
def inbound(raw, interface=None):
|
||||
while (Transport.jobs_running):
|
||||
# TODO: Decrease this for performance
|
||||
sleep(0.1)
|
||||
sleep(0.01)
|
||||
|
||||
Transport.jobs_locked = True
|
||||
|
||||
@@ -462,8 +583,6 @@ class Transport:
|
||||
packet.receiving_interface = interface
|
||||
packet.hops += 1
|
||||
|
||||
RNS.log(str(interface)+" received packet with hash "+RNS.prettyhexrep(packet.packet_hash), RNS.LOG_EXTREME)
|
||||
|
||||
if len(Transport.local_client_interfaces) > 0:
|
||||
|
||||
if Transport.is_local_client_interface(interface):
|
||||
@@ -528,11 +647,10 @@ class Transport:
|
||||
# accordingly if we are.
|
||||
if packet.transport_id != None and packet.packet_type != RNS.Packet.ANNOUNCE:
|
||||
if packet.transport_id == Transport.identity.hash:
|
||||
RNS.log("Received packet in transport for "+RNS.prettyhexrep(packet.destination_hash)+" with matching transport ID, transporting it...", RNS.LOG_DEBUG)
|
||||
if packet.destination_hash in Transport.destination_table:
|
||||
next_hop = Transport.destination_table[packet.destination_hash][1]
|
||||
remaining_hops = Transport.destination_table[packet.destination_hash][2]
|
||||
RNS.log("Next hop to destination is "+RNS.prettyhexrep(next_hop)+" with "+str(remaining_hops)+" hops remaining, transporting it.", RNS.LOG_DEBUG)
|
||||
|
||||
if remaining_hops > 1:
|
||||
# Just increase hop count and transmit
|
||||
new_raw = packet.raw[0:1]
|
||||
@@ -580,7 +698,7 @@ class Transport:
|
||||
# TODO: There should probably be some kind of REJECT
|
||||
# mechanism here, to signal to the source that their
|
||||
# expected path failed.
|
||||
RNS.log("Got packet in transport, but no known path to final destination. Dropping packet.", RNS.LOG_DEBUG)
|
||||
RNS.log("Got packet in transport, but no known path to final destination "+RNS.prettyhexrep(packet.destination_hash)+". Dropping packet.", RNS.LOG_DEBUG)
|
||||
|
||||
# Link transport handling. Directs packets according
|
||||
# to entries in the link tables
|
||||
@@ -624,7 +742,7 @@ class Transport:
|
||||
# of queued announce rebroadcasts once handed to the next node.
|
||||
if packet.packet_type == RNS.Packet.ANNOUNCE:
|
||||
local_destination = next((d for d in Transport.destinations if d.hash == packet.destination_hash), None)
|
||||
if local_destination == None and RNS.Identity.validate_announce(packet):
|
||||
if local_destination == None and RNS.Identity.validate_announce(packet):
|
||||
if packet.transport_id != None:
|
||||
received_from = packet.transport_id
|
||||
|
||||
@@ -657,7 +775,8 @@ class Transport:
|
||||
# First, check that the announce is not for a destination
|
||||
# local to this system, and that hops are less than the max
|
||||
if (not any(packet.destination_hash == d.hash for d in Transport.destinations) and packet.hops < Transport.PATHFINDER_M+1):
|
||||
random_blob = packet.data[RNS.Identity.KEYSIZE//8+10:RNS.Identity.KEYSIZE//8+20]
|
||||
random_blob = packet.data[RNS.Identity.KEYSIZE//8:RNS.Identity.KEYSIZE//8+RNS.Reticulum.TRUNCATED_HASHLENGTH//8]
|
||||
announce_emitted = int.from_bytes(random_blob[5:10], "big")
|
||||
random_blobs = []
|
||||
if packet.destination_hash in Transport.destination_table:
|
||||
random_blobs = Transport.destination_table[packet.destination_hash][4]
|
||||
@@ -678,8 +797,18 @@ class Transport:
|
||||
else:
|
||||
# If an announce arrives with a larger hop
|
||||
# count than we already have in the table,
|
||||
# ignore it, unless the path is expired
|
||||
if (time.time() > Transport.destination_table[packet.destination_hash][3]):
|
||||
# ignore it, unless the path is expired, or
|
||||
# the emission timestamp is more recent.
|
||||
now = time.time()
|
||||
path_expires = Transport.destination_table[packet.destination_hash][3]
|
||||
|
||||
path_announce_emitted = 0
|
||||
for path_random_blob in random_blobs:
|
||||
path_announce_emitted = max(path_announce_emitted, int.from_bytes(path_random_blob[5:10], "big"))
|
||||
if path_announce_emitted >= announce_emitted:
|
||||
break
|
||||
|
||||
if (now >= path_expires):
|
||||
# We also check that the announce hash is
|
||||
# different from ones we've already heard,
|
||||
# to avoid loops in the network
|
||||
@@ -691,7 +820,13 @@ class Transport:
|
||||
else:
|
||||
should_add = False
|
||||
else:
|
||||
should_add = False
|
||||
if (announce_emitted > path_announce_emitted):
|
||||
if not random_blob in random_blobs:
|
||||
RNS.log("Replacing destination table entry for "+str(RNS.prettyhexrep(packet.destination_hash))+" with new announce, since it was more recently emitted", RNS.LOG_DEBUG)
|
||||
should_add = True
|
||||
else:
|
||||
should_add = False
|
||||
|
||||
else:
|
||||
# If this destination is unknown in our table
|
||||
# we should add it
|
||||
@@ -713,6 +848,7 @@ class Transport:
|
||||
# If the announce is from a local client,
|
||||
# we announce it immediately, but only one
|
||||
# time.
|
||||
|
||||
if Transport.from_local_client(packet):
|
||||
retransmit_timeout = now
|
||||
retries = Transport.PATHFINDER_R
|
||||
@@ -739,48 +875,80 @@ class Transport:
|
||||
announce_context = RNS.Packet.NONE
|
||||
announce_data = packet.data
|
||||
|
||||
new_announce = RNS.Packet(
|
||||
announce_destination,
|
||||
announce_data,
|
||||
RNS.Packet.ANNOUNCE,
|
||||
context = announce_context,
|
||||
header_type = RNS.Packet.HEADER_2,
|
||||
transport_type = Transport.TRANSPORT,
|
||||
transport_id = Transport.identity.hash,
|
||||
attached_interface = attached_interface
|
||||
)
|
||||
if Transport.from_local_client(packet) and packet.context == RNS.Packet.PATH_RESPONSE:
|
||||
for local_interface in Transport.local_client_interfaces:
|
||||
if packet.receiving_interface != local_interface:
|
||||
new_announce = RNS.Packet(
|
||||
announce_destination,
|
||||
announce_data,
|
||||
RNS.Packet.ANNOUNCE,
|
||||
context = announce_context,
|
||||
header_type = RNS.Packet.HEADER_2,
|
||||
transport_type = Transport.TRANSPORT,
|
||||
transport_id = Transport.identity.hash,
|
||||
attached_interface = local_interface
|
||||
)
|
||||
|
||||
new_announce.hops = packet.hops
|
||||
new_announce.send()
|
||||
|
||||
new_announce.hops = packet.hops
|
||||
new_announce.send()
|
||||
else:
|
||||
for local_interface in Transport.local_client_interfaces:
|
||||
if packet.receiving_interface != local_interface:
|
||||
new_announce = RNS.Packet(
|
||||
announce_destination,
|
||||
announce_data,
|
||||
RNS.Packet.ANNOUNCE,
|
||||
context = announce_context,
|
||||
header_type = RNS.Packet.HEADER_2,
|
||||
transport_type = Transport.TRANSPORT,
|
||||
transport_id = Transport.identity.hash,
|
||||
attached_interface = local_interface
|
||||
)
|
||||
|
||||
Transport.destination_table[packet.destination_hash] = [now, received_from, announce_hops, expires, random_blobs, packet.receiving_interface, packet]
|
||||
new_announce.hops = packet.hops
|
||||
new_announce.send()
|
||||
|
||||
destination_table_entry = [now, received_from, announce_hops, expires, random_blobs, packet.receiving_interface, packet]
|
||||
Transport.destination_table[packet.destination_hash] = destination_table_entry
|
||||
RNS.log("Path to "+RNS.prettyhexrep(packet.destination_hash)+" is now "+str(announce_hops)+" hops away via "+RNS.prettyhexrep(received_from)+" on "+str(packet.receiving_interface), RNS.LOG_VERBOSE)
|
||||
|
||||
# If the receiving interface is a tunnel, we add the
|
||||
# announce to the tunnels table
|
||||
if hasattr(packet.receiving_interface, "tunnel_id") and packet.receiving_interface.tunnel_id != None:
|
||||
tunnel_entry = Transport.tunnels[packet.receiving_interface.tunnel_id]
|
||||
paths = tunnel_entry[2]
|
||||
paths[packet.destination_hash] = destination_table_entry
|
||||
expires = time.time() + Transport.DESTINATION_TIMEOUT
|
||||
tunnel_entry[3] = expires
|
||||
RNS.log("Path to "+RNS.prettyhexrep(packet.destination_hash)+" associated with tunnel "+RNS.prettyhexrep(packet.receiving_interface.tunnel_id), RNS.LOG_VERBOSE)
|
||||
|
||||
# Call externally registered callbacks from apps
|
||||
# wanting to know when an announce arrives
|
||||
for handler in Transport.announce_handlers:
|
||||
try:
|
||||
# Check that the announced destination matches
|
||||
# the handlers aspect filter
|
||||
execute_callback = False
|
||||
if handler.aspect_filter == None:
|
||||
# If the handlers aspect filter is set to
|
||||
# None, we execute the callback in all cases
|
||||
execute_callback = True
|
||||
else:
|
||||
announce_identity = RNS.Identity.recall(packet.destination_hash)
|
||||
handler_expected_hash = RNS.Destination.hash_from_name_and_identity(handler.aspect_filter, announce_identity)
|
||||
if packet.destination_hash == handler_expected_hash:
|
||||
if packet.context != RNS.Packet.PATH_RESPONSE:
|
||||
for handler in Transport.announce_handlers:
|
||||
try:
|
||||
# Check that the announced destination matches
|
||||
# the handlers aspect filter
|
||||
execute_callback = False
|
||||
if handler.aspect_filter == None:
|
||||
# If the handlers aspect filter is set to
|
||||
# None, we execute the callback in all cases
|
||||
execute_callback = True
|
||||
if execute_callback:
|
||||
handler.received_announce(
|
||||
destination_hash=packet.destination_hash,
|
||||
announced_identity=announce_identity,
|
||||
app_data=RNS.Identity.recall_app_data(packet.destination_hash)
|
||||
)
|
||||
except Exception as e:
|
||||
RNS.log("Error while processing external announce callback.", RNS.LOG_ERROR)
|
||||
RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
else:
|
||||
announce_identity = RNS.Identity.recall(packet.destination_hash)
|
||||
handler_expected_hash = RNS.Destination.hash_from_name_and_identity(handler.aspect_filter, announce_identity)
|
||||
if packet.destination_hash == handler_expected_hash:
|
||||
execute_callback = True
|
||||
if execute_callback:
|
||||
handler.received_announce(
|
||||
destination_hash=packet.destination_hash,
|
||||
announced_identity=announce_identity,
|
||||
app_data=RNS.Identity.recall_app_data(packet.destination_hash)
|
||||
)
|
||||
except Exception as e:
|
||||
RNS.log("Error while processing external announce callback.", RNS.LOG_ERROR)
|
||||
RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
|
||||
# Handling for linkrequests to local destinations
|
||||
elif packet.packet_type == RNS.Packet.LINKREQUEST:
|
||||
@@ -869,15 +1037,120 @@ class Transport:
|
||||
if receipt.hash == proof_hash:
|
||||
receipt_validated = receipt.validate_proof_packet(packet)
|
||||
else:
|
||||
# TODO: This looks like it should actually
|
||||
# be rewritten when implicit proofs are added.
|
||||
|
||||
# In case of an implicit proof, we have
|
||||
# to check every single outstanding receipt
|
||||
receipt_validated = receipt.validate_proof_packet(packet)
|
||||
|
||||
if receipt_validated:
|
||||
Transport.receipts.remove(receipt)
|
||||
if receipt in Transport.receipts:
|
||||
Transport.receipts.remove(receipt)
|
||||
|
||||
Transport.jobs_locked = False
|
||||
|
||||
@staticmethod
|
||||
def synthesize_tunnel(interface):
|
||||
interface_hash = interface.get_hash()
|
||||
public_key = RNS.Transport.identity.get_public_key()
|
||||
random_hash = RNS.Identity.get_random_hash()
|
||||
|
||||
tunnel_id_data = public_key+interface_hash
|
||||
tunnel_id = RNS.Identity.full_hash(tunnel_id_data)
|
||||
|
||||
signed_data = tunnel_id_data+random_hash
|
||||
signature = Transport.identity.sign(signed_data)
|
||||
|
||||
data = signed_data+signature
|
||||
|
||||
tnl_snth_dst = RNS.Destination(None, RNS.Destination.OUT, RNS.Destination.PLAIN, Transport.APP_NAME, "tunnel", "synthesize")
|
||||
|
||||
packet = RNS.Packet(tnl_snth_dst, data, packet_type = RNS.Packet.DATA, transport_type = RNS.Transport.BROADCAST, header_type = RNS.Packet.HEADER_1, attached_interface = interface)
|
||||
packet.send()
|
||||
|
||||
interface.wants_tunnel = False
|
||||
|
||||
@staticmethod
|
||||
def tunnel_synthesize_handler(data, packet):
|
||||
try:
|
||||
expected_length = RNS.Identity.KEYSIZE//8+RNS.Identity.HASHLENGTH//8+RNS.Reticulum.TRUNCATED_HASHLENGTH//8+RNS.Identity.SIGLENGTH//8
|
||||
if len(data) == expected_length:
|
||||
public_key = data[:RNS.Identity.KEYSIZE//8]
|
||||
interface_hash = data[RNS.Identity.KEYSIZE//8:RNS.Identity.KEYSIZE//8+RNS.Identity.HASHLENGTH//8]
|
||||
tunnel_id_data = public_key+interface_hash
|
||||
tunnel_id = RNS.Identity.full_hash(tunnel_id_data)
|
||||
random_hash = data[RNS.Identity.KEYSIZE//8+RNS.Identity.HASHLENGTH//8:RNS.Identity.KEYSIZE//8+RNS.Identity.HASHLENGTH//8+RNS.Reticulum.TRUNCATED_HASHLENGTH//8]
|
||||
|
||||
signature = data[RNS.Identity.KEYSIZE//8+RNS.Identity.HASHLENGTH//8+RNS.Reticulum.TRUNCATED_HASHLENGTH//8:expected_length]
|
||||
signed_data = tunnel_id_data+random_hash
|
||||
|
||||
remote_transport_identity = RNS.Identity(create_keys=False)
|
||||
remote_transport_identity.load_public_key(public_key)
|
||||
|
||||
if remote_transport_identity.validate(signature, signed_data):
|
||||
Transport.handle_tunnel(tunnel_id, packet.receiving_interface)
|
||||
|
||||
except Exception as e:
|
||||
RNS.log("An error occurred while validating tunnel establishment packet.", RNS.LOG_DEBUG)
|
||||
RNS.log("The contained exception was: "+str(e), RNS.LOG_DEBUG)
|
||||
|
||||
@staticmethod
|
||||
def handle_tunnel(tunnel_id, interface):
|
||||
expires = time.time() + Transport.DESTINATION_TIMEOUT
|
||||
if not tunnel_id in Transport.tunnels:
|
||||
RNS.log("Tunnel endpoint "+RNS.prettyhexrep(tunnel_id)+" established.", RNS.LOG_DEBUG)
|
||||
paths = {}
|
||||
tunnel_entry = [tunnel_id, interface, paths, expires]
|
||||
interface.tunnel_id = tunnel_id
|
||||
Transport.tunnels[tunnel_id] = tunnel_entry
|
||||
else:
|
||||
RNS.log("Tunnel endpoint "+RNS.prettyhexrep(tunnel_id)+" reappeared. Restoring paths...", RNS.LOG_DEBUG)
|
||||
tunnel_entry = Transport.tunnels[tunnel_id]
|
||||
tunnel_entry[1] = interface
|
||||
tunnel_entry[3] = expires
|
||||
interface.tunnel_id = tunnel_id
|
||||
paths = tunnel_entry[2]
|
||||
|
||||
deprecated_paths = []
|
||||
for destination_hash, path_entry in paths.items():
|
||||
received_from = path_entry[1]
|
||||
announce_hops = path_entry[2]
|
||||
expires = path_entry[3]
|
||||
random_blobs = path_entry[4]
|
||||
receiving_interface = interface
|
||||
packet = path_entry[6]
|
||||
new_entry = [time.time(), received_from, announce_hops, expires, random_blobs, receiving_interface, packet]
|
||||
|
||||
should_add = False
|
||||
if destination_hash in Transport.destination_table:
|
||||
old_entry = Transport.destination_table[destination_hash]
|
||||
old_hops = old_entry[2]
|
||||
old_expires = old_entry[3]
|
||||
if announce_hops <= old_hops or time.time() > old_expires:
|
||||
should_add = True
|
||||
else:
|
||||
RNS.log("Did not restore path to "+RNS.prettyhexrep(packet.destination_hash)+" because a newer path with fewer hops exist", RNS.LOG_DEBUG)
|
||||
else:
|
||||
if time.time() < expires:
|
||||
should_add = True
|
||||
else:
|
||||
RNS.log("Did not restore path to "+RNS.prettyhexrep(packet.destination_hash)+" because it has expired", RNS.LOG_DEBUG)
|
||||
|
||||
if should_add:
|
||||
Transport.destination_table[destination_hash] = new_entry
|
||||
RNS.log("Restored path to "+RNS.prettyhexrep(packet.destination_hash)+" is now "+str(announce_hops)+" hops away via "+RNS.prettyhexrep(received_from)+" on "+str(receiving_interface), RNS.LOG_DEBUG)
|
||||
else:
|
||||
deprecated_paths.append(destination_hash)
|
||||
|
||||
for deprecated_path in deprecated_paths:
|
||||
RNS.log("Removing path to "+RNS.prettyhexrep(deprecated_path)+" from tunnel "+RNS.prettyhexrep(tunnel_id), RNS.LOG_DEBUG)
|
||||
paths.pop(deprecated_path)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@staticmethod
|
||||
def register_destination(destination):
|
||||
destination.MTU = RNS.Reticulum.MTU
|
||||
@@ -885,8 +1158,13 @@ class Transport:
|
||||
for registered_destination in Transport.destinations:
|
||||
if destination.hash == registered_destination.hash:
|
||||
raise KeyError("Attempt to register an already registered destination.")
|
||||
|
||||
Transport.destinations.append(destination)
|
||||
|
||||
if Transport.owner.is_connected_to_shared_instance:
|
||||
if destination.type == RNS.Destination.SINGLE:
|
||||
destination.announce(path_response=True)
|
||||
|
||||
@staticmethod
|
||||
def deregister_destination(destination):
|
||||
if destination in Transport.destinations:
|
||||
@@ -1033,6 +1311,39 @@ class Transport:
|
||||
else:
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def hops_to(destination_hash):
|
||||
"""
|
||||
:param destination_hash: A destination hash as *bytes*.
|
||||
:returns: The number of hops to the specified destination, or ``RNS.Transport.PATHFINDER_M`` if the number of hops is unknown.
|
||||
"""
|
||||
if destination_hash in Transport.destination_table:
|
||||
return Transport.destination_table[destination_hash][2]
|
||||
else:
|
||||
return Transport.PATHFINDER_M
|
||||
|
||||
@staticmethod
|
||||
def next_hop(destination_hash):
|
||||
"""
|
||||
:param destination_hash: A destination hash as *bytes*.
|
||||
:returns: The destination hash as *bytes* for the next hop to the specified destination, or *None* if the next hop is unknown.
|
||||
"""
|
||||
if destination_hash in Transport.destination_table:
|
||||
return Transport.destination_table[destination_hash][1]
|
||||
else:
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def next_hop_interface(destination_hash):
|
||||
"""
|
||||
:param destination_hash: A destination hash as *bytes*.
|
||||
:returns: The interface for the next hop to the specified destination, or *None* if the interface is unknown.
|
||||
"""
|
||||
if destination_hash in Transport.destination_table:
|
||||
return Transport.destination_table[destination_hash][5]
|
||||
else:
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def request_path(destination_hash):
|
||||
"""
|
||||
@@ -1072,7 +1383,7 @@ class Transport:
|
||||
RNS.log("Destination is local to this system, announcing", RNS.LOG_DEBUG)
|
||||
local_destination.announce(path_response=True)
|
||||
|
||||
elif (RNS.Reticulum.transport_enabled() or is_from_local_client) and destination_hash in Transport.destination_table:
|
||||
elif (RNS.Reticulum.transport_enabled() or is_from_local_client or len(Transport.local_client_interfaces) > 0) and destination_hash in Transport.destination_table:
|
||||
RNS.log("Path found, inserting announce for transmission", RNS.LOG_DEBUG)
|
||||
packet = Transport.destination_table[destination_hash][6]
|
||||
received_from = Transport.destination_table[destination_hash][5]
|
||||
@@ -1141,6 +1452,15 @@ class Transport:
|
||||
else:
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def detach_interfaces():
|
||||
for interface in Transport.interfaces:
|
||||
interface.detach()
|
||||
|
||||
for interface in Transport.local_client_interfaces:
|
||||
interface.detach()
|
||||
|
||||
|
||||
@staticmethod
|
||||
def exit_handler():
|
||||
try:
|
||||
@@ -1201,3 +1521,55 @@ class Transport:
|
||||
RNS.log("Done saving "+str(len(serialised_destinations))+" path table entries to storage", RNS.LOG_VERBOSE)
|
||||
except Exception as e:
|
||||
RNS.log("Could not save path table to storage, the contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
|
||||
RNS.log("Saving tunnel table to storage...", RNS.LOG_VERBOSE)
|
||||
try:
|
||||
serialised_tunnels = []
|
||||
for tunnel_id in Transport.tunnels:
|
||||
te = Transport.tunnels[tunnel_id]
|
||||
interface = te[1]
|
||||
tunnel_paths = te[2]
|
||||
expires = te[3]
|
||||
|
||||
if interface != None:
|
||||
interface_hash = interface.get_hash()
|
||||
else:
|
||||
interface_hash = None
|
||||
|
||||
serialised_paths = []
|
||||
for destination_hash in tunnel_paths:
|
||||
de = tunnel_paths[destination_hash]
|
||||
|
||||
timestamp = de[0]
|
||||
received_from = de[1]
|
||||
hops = de[2]
|
||||
expires = de[3]
|
||||
random_blobs = de[4]
|
||||
packet_hash = de[6].get_hash()
|
||||
|
||||
serialised_entry = [
|
||||
destination_hash,
|
||||
timestamp,
|
||||
received_from,
|
||||
hops,
|
||||
expires,
|
||||
random_blobs,
|
||||
interface_hash,
|
||||
packet_hash
|
||||
]
|
||||
|
||||
serialised_paths.append(serialised_entry)
|
||||
|
||||
Transport.cache(de[6], force_cache=True)
|
||||
|
||||
|
||||
serialised_tunnel = [tunnel_id, interface_hash, serialised_paths, expires]
|
||||
serialised_tunnels.append(serialised_tunnel)
|
||||
|
||||
tunnels_path = RNS.Reticulum.storagepath+"/tunnels"
|
||||
file = open(tunnels_path, "wb")
|
||||
file.write(umsgpack.packb(serialised_tunnels))
|
||||
file.close()
|
||||
RNS.log("Done saving "+str(len(serialised_tunnels))+" tunnel table entries to storage", RNS.LOG_VERBOSE)
|
||||
except Exception as e:
|
||||
RNS.log("Could not save tunnel table to storage, the contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
@@ -0,0 +1,5 @@
|
||||
import os
|
||||
import glob
|
||||
|
||||
modules = glob.glob(os.path.dirname(__file__)+"/*.py")
|
||||
__all__ = [ os.path.basename(f)[:-3] for f in modules if not f.endswith('__init__.py')]
|
||||
@@ -0,0 +1,97 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import RNS
|
||||
import sys
|
||||
import time
|
||||
import argparse
|
||||
|
||||
from RNS._version import __version__
|
||||
|
||||
|
||||
def program_setup(configdir, destination_hexhash, verbosity):
|
||||
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))
|
||||
try:
|
||||
destination_hash = bytes.fromhex(destination_hexhash)
|
||||
except Exception as e:
|
||||
raise ValueError("Invalid destination entered. Check your input.")
|
||||
except Exception as e:
|
||||
print(str(e))
|
||||
exit()
|
||||
|
||||
reticulum = RNS.Reticulum(configdir = configdir, loglevel = 3+verbosity)
|
||||
|
||||
if not RNS.Transport.has_path(destination_hash):
|
||||
RNS.Transport.request_path(destination_hash)
|
||||
print("Path to "+RNS.prettyhexrep(destination_hash)+" requested ", end=" ")
|
||||
sys.stdout.flush()
|
||||
|
||||
i = 0
|
||||
syms = "⢄⢂⢁⡁⡈⡐⡠"
|
||||
while not RNS.Transport.has_path(destination_hash):
|
||||
time.sleep(0.1)
|
||||
print(("\b\b"+syms[i]+" "), end="")
|
||||
sys.stdout.flush()
|
||||
i = (i+1)%len(syms)
|
||||
|
||||
hops = RNS.Transport.hops_to(destination_hash)
|
||||
next_hop = RNS.prettyhexrep(reticulum.get_next_hop(destination_hash))
|
||||
next_hop_interface = reticulum.get_next_hop_if_name(destination_hash)
|
||||
|
||||
if hops != 1:
|
||||
ms = "s"
|
||||
else:
|
||||
ms = ""
|
||||
|
||||
print("\rPath found, destination "+RNS.prettyhexrep(destination_hash)+" is "+str(hops)+" hop"+ms+" away via "+next_hop+" on "+next_hop_interface)
|
||||
|
||||
|
||||
def main():
|
||||
try:
|
||||
parser = argparse.ArgumentParser(description="Reticulum Path Discovery Utility")
|
||||
|
||||
parser.add_argument("--config",
|
||||
action="store",
|
||||
default=None,
|
||||
help="path to alternative Reticulum config directory",
|
||||
type=str
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--version",
|
||||
action="version",
|
||||
version="rnpath {version}".format(version=__version__)
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"destination",
|
||||
nargs="?",
|
||||
default=None,
|
||||
help="hexadecimal hash of the destination",
|
||||
type=str
|
||||
)
|
||||
|
||||
parser.add_argument('-v', '--verbose', action='count', default=0)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.config:
|
||||
configarg = args.config
|
||||
else:
|
||||
configarg = None
|
||||
|
||||
if not args.destination:
|
||||
print("")
|
||||
parser.print_help()
|
||||
print("")
|
||||
else:
|
||||
program_setup(configdir = configarg, destination_hexhash = args.destination, verbosity = args.verbose)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("")
|
||||
exit()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,172 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import RNS
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import argparse
|
||||
|
||||
from RNS._version import __version__
|
||||
|
||||
DEFAULT_PROBE_SIZE = 16
|
||||
|
||||
def program_setup(configdir, destination_hexhash, size=DEFAULT_PROBE_SIZE, full_name = None, verbosity = 0):
|
||||
if full_name == None:
|
||||
print("The full destination name including application name aspects must be specified for the destination")
|
||||
exit()
|
||||
|
||||
try:
|
||||
app_name, aspects = RNS.Destination.app_and_aspects_from_name(full_name)
|
||||
|
||||
except Exception as e:
|
||||
print(str(e))
|
||||
exit()
|
||||
|
||||
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))
|
||||
try:
|
||||
destination_hash = bytes.fromhex(destination_hexhash)
|
||||
except Exception as e:
|
||||
raise ValueError("Invalid destination entered. Check your input.")
|
||||
except Exception as e:
|
||||
print(str(e))
|
||||
exit()
|
||||
|
||||
if verbosity > 0:
|
||||
more_output = True
|
||||
verbosity -= 1
|
||||
else:
|
||||
more_output = False
|
||||
verbosity -= 1
|
||||
|
||||
|
||||
reticulum = RNS.Reticulum(configdir = configdir, loglevel = 3+verbosity)
|
||||
|
||||
if not RNS.Transport.has_path(destination_hash):
|
||||
RNS.Transport.request_path(destination_hash)
|
||||
print("Path to "+RNS.prettyhexrep(destination_hash)+" requested ", end=" ")
|
||||
sys.stdout.flush()
|
||||
|
||||
i = 0
|
||||
syms = "⢄⢂⢁⡁⡈⡐⡠"
|
||||
while not RNS.Transport.has_path(destination_hash):
|
||||
time.sleep(0.1)
|
||||
print(("\b\b"+syms[i]+" "), end="")
|
||||
sys.stdout.flush()
|
||||
i = (i+1)%len(syms)
|
||||
|
||||
server_identity = RNS.Identity.recall(destination_hash)
|
||||
|
||||
request_destination = RNS.Destination(
|
||||
server_identity,
|
||||
RNS.Destination.OUT,
|
||||
RNS.Destination.SINGLE,
|
||||
app_name,
|
||||
*aspects
|
||||
)
|
||||
|
||||
probe = RNS.Packet(request_destination, os.urandom(size))
|
||||
receipt = probe.send()
|
||||
|
||||
if more_output:
|
||||
more = " via "+RNS.prettyhexrep(reticulum.get_next_hop(destination_hash))+" on "+str(reticulum.get_next_hop_if_name(destination_hash))
|
||||
else:
|
||||
more = ""
|
||||
|
||||
print("\rSent "+str(size)+" byte probe to "+RNS.prettyhexrep(destination_hash)+more+" ", end=" ")
|
||||
|
||||
i = 0
|
||||
while not receipt.status == RNS.PacketReceipt.DELIVERED:
|
||||
time.sleep(0.1)
|
||||
print(("\b\b"+syms[i]+" "), end="")
|
||||
sys.stdout.flush()
|
||||
i = (i+1)%len(syms)
|
||||
|
||||
print("\b\b ")
|
||||
sys.stdout.flush()
|
||||
|
||||
hops = RNS.Transport.hops_to(destination_hash)
|
||||
if hops != 1:
|
||||
ms = "s"
|
||||
else:
|
||||
ms = ""
|
||||
|
||||
rtt = receipt.get_rtt()
|
||||
if (rtt >= 1):
|
||||
rtt = round(rtt, 3)
|
||||
rttstring = str(rtt)+" seconds"
|
||||
else:
|
||||
rtt = round(rtt*1000, 3)
|
||||
rttstring = str(rtt)+" milliseconds"
|
||||
|
||||
print(
|
||||
"Valid reply received from "+
|
||||
RNS.prettyhexrep(receipt.destination.hash)+
|
||||
"\nRound-trip time is "+rttstring+
|
||||
" over "+str(hops)+" hop"+ms
|
||||
)
|
||||
|
||||
|
||||
|
||||
def main():
|
||||
try:
|
||||
parser = argparse.ArgumentParser(description="Reticulum Probe Utility")
|
||||
|
||||
parser.add_argument("--config",
|
||||
action="store",
|
||||
default=None,
|
||||
help="path to alternative Reticulum config directory",
|
||||
type=str
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--version",
|
||||
action="version",
|
||||
version="rnprobe {version}".format(version=__version__)
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"full_name",
|
||||
nargs="?",
|
||||
default=None,
|
||||
help="full destination name in dotted notation",
|
||||
type=str
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"destination_hash",
|
||||
nargs="?",
|
||||
default=None,
|
||||
help="hexadecimal hash of the destination",
|
||||
type=str
|
||||
)
|
||||
|
||||
parser.add_argument('-v', '--verbose', action='count', default=0)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.config:
|
||||
configarg = args.config
|
||||
else:
|
||||
configarg = None
|
||||
|
||||
if not args.destination_hash:
|
||||
print("")
|
||||
parser.print_help()
|
||||
print("")
|
||||
else:
|
||||
program_setup(
|
||||
configdir = configarg,
|
||||
destination_hexhash = args.destination_hash,
|
||||
full_name = args.full_name,
|
||||
verbosity = args.verbose
|
||||
)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("")
|
||||
exit()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,37 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import RNS
|
||||
import argparse
|
||||
|
||||
from RNS._version import __version__
|
||||
|
||||
|
||||
def program_setup(configdir, verbosity = 0, quietness = 0):
|
||||
reticulum = RNS.Reticulum(configdir = configdir, loglevel = 3+verbosity-quietness)
|
||||
RNS.log("Started rnsd version {version}".format(version=__version__), RNS.LOG_NOTICE)
|
||||
while True:
|
||||
input()
|
||||
|
||||
def main():
|
||||
try:
|
||||
parser = argparse.ArgumentParser(description="Reticulum Network Stack Daemon")
|
||||
parser.add_argument("--config", action="store", default=None, help="path to alternative Reticulum config directory", type=str)
|
||||
parser.add_argument('-v', '--verbose', action='count', default=0)
|
||||
parser.add_argument('-q', '--quiet', action='count', default=0)
|
||||
parser.add_argument("--version", action="version", version="rnsd {version}".format(version=__version__))
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.config:
|
||||
configarg = args.config
|
||||
else:
|
||||
configarg = None
|
||||
|
||||
program_setup(configdir = configarg, verbosity=args.verbose, quietness=args.quiet)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("")
|
||||
exit()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,93 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import RNS
|
||||
import argparse
|
||||
|
||||
from RNS._version import __version__
|
||||
|
||||
def size_str(num, suffix='B'):
|
||||
units = ['','K','M','G','T','P','E','Z']
|
||||
last_unit = 'Y'
|
||||
|
||||
if suffix == 'b':
|
||||
num *= 8
|
||||
units = ['','K','M','G','T','P','E','Z']
|
||||
last_unit = 'Y'
|
||||
|
||||
for unit in units:
|
||||
if abs(num) < 1000.0:
|
||||
if unit == "":
|
||||
return "%.0f %s%s" % (num, unit, suffix)
|
||||
else:
|
||||
return "%.2f %s%s" % (num, unit, suffix)
|
||||
num /= 1000.0
|
||||
|
||||
return "%.2f%s%s" % (num, last_unit, suffix)
|
||||
|
||||
def program_setup(configdir, dispall=False, verbosity = 0):
|
||||
reticulum = RNS.Reticulum(configdir = configdir, loglevel = 3+verbosity)
|
||||
|
||||
ifstats = reticulum.get_interface_stats()
|
||||
if ifstats != None:
|
||||
for ifstat in ifstats:
|
||||
name = ifstat["name"]
|
||||
|
||||
if dispall or not (name.startswith("LocalInterface[") or name.startswith("TCPInterface[Client")):
|
||||
print("")
|
||||
if ifstat["status"]:
|
||||
ss = "Up"
|
||||
else:
|
||||
ss = "Down"
|
||||
|
||||
if ifstat["clients"] != None:
|
||||
clients = ifstat["clients"]
|
||||
if name.startswith("Shared Instance["):
|
||||
clients_string = "Connected applications: "+str(max(clients-1,0))
|
||||
else:
|
||||
clients_string = "Connected clients: "+str(clients)
|
||||
|
||||
else:
|
||||
clients = None
|
||||
|
||||
print(" {n}".format(n=ifstat["name"]))
|
||||
print("\tStatus: {ss}".format(ss=ss))
|
||||
if clients != None:
|
||||
print("\t"+clients_string)
|
||||
print("\tRX: {rxb}\n\tTX: {txb}".format(rxb=size_str(ifstat["rxb"]), txb=size_str(ifstat["txb"])))
|
||||
|
||||
print("")
|
||||
|
||||
else:
|
||||
print("Could not get RNS status")
|
||||
|
||||
def main():
|
||||
try:
|
||||
parser = argparse.ArgumentParser(description="Reticulum Network Stack Status")
|
||||
parser.add_argument("--config", action="store", default=None, help="path to alternative Reticulum config directory", type=str)
|
||||
parser.add_argument("--version", action="version", version="rnstatus {version}".format(version=__version__))
|
||||
|
||||
parser.add_argument(
|
||||
"-a",
|
||||
"--all",
|
||||
action="store_true",
|
||||
help="show all interfaces",
|
||||
default=False
|
||||
)
|
||||
|
||||
parser.add_argument('-v', '--verbose', action='count', default=0)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.config:
|
||||
configarg = args.config
|
||||
else:
|
||||
configarg = None
|
||||
|
||||
program_setup(configdir = configarg, dispall = args.all, verbosity=args.verbose)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("")
|
||||
exit()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -9,7 +9,7 @@ from ._version import __version__
|
||||
|
||||
from .Reticulum import Reticulum
|
||||
from .Identity import Identity
|
||||
from .Link import Link
|
||||
from .Link import Link, RequestReceipt
|
||||
from .Transport import Transport
|
||||
from .Destination import Destination
|
||||
from .Packet import Packet
|
||||
@@ -31,6 +31,8 @@ LOG_EXTREME = 7
|
||||
LOG_STDOUT = 0x91
|
||||
LOG_FILE = 0x92
|
||||
|
||||
LOG_MAXSIZE = 5*1024*1024
|
||||
|
||||
loglevel = LOG_NOTICE
|
||||
logfile = None
|
||||
logdest = LOG_STDOUT
|
||||
@@ -82,6 +84,13 @@ def log(msg, level=3, _override_destination = False):
|
||||
file = open(logfile, "a")
|
||||
file.write(logstring+"\n")
|
||||
file.close()
|
||||
|
||||
if os.path.getsize(logfile) > LOG_MAXSIZE:
|
||||
prevfile = logfile+".1"
|
||||
if os.path.isfile(prevfile):
|
||||
os.unlink(prevfile)
|
||||
os.rename(logfile, prevfile)
|
||||
|
||||
logging_lock.release()
|
||||
except Exception as e:
|
||||
logging_lock.release()
|
||||
@@ -108,4 +117,7 @@ def prettyhexrep(data):
|
||||
return hexrep
|
||||
|
||||
def panic():
|
||||
os._exit(255)
|
||||
os._exit(255)
|
||||
|
||||
def exit():
|
||||
sys.exit(0)
|
||||
@@ -1 +1 @@
|
||||
__version__ = "0.2.2"
|
||||
__version__ = "0.2.8"
|
||||
@@ -1,7 +1,5 @@
|
||||
import os
|
||||
import glob
|
||||
|
||||
__version__ = "0.1.9"
|
||||
|
||||
modules = glob.glob(os.path.dirname(__file__)+"/*.py")
|
||||
__all__ = [ os.path.basename(f)[:-3] for f in modules if not f.endswith('__init__.py')]
|
||||
|
||||
@@ -19,7 +19,7 @@ import sys
|
||||
|
||||
from codecs import BOM_UTF8, BOM_UTF16, BOM_UTF16_BE, BOM_UTF16_LE
|
||||
|
||||
import six
|
||||
import RNS.vendor.six as six
|
||||
__version__ = '5.0.6'
|
||||
|
||||
# imported lazily to avoid startup performance hit if it isn't used
|
||||
|
||||
@@ -0,0 +1,998 @@
|
||||
# Copyright (c) 2010-2020 Benjamin Peterson
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in all
|
||||
# copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
|
||||
"""Utilities for writing code that runs on Python 2 and 3"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import functools
|
||||
import itertools
|
||||
import operator
|
||||
import sys
|
||||
import types
|
||||
|
||||
__author__ = "Benjamin Peterson <benjamin@python.org>"
|
||||
__version__ = "1.16.0"
|
||||
|
||||
|
||||
# Useful for very coarse version differentiation.
|
||||
PY2 = sys.version_info[0] == 2
|
||||
PY3 = sys.version_info[0] == 3
|
||||
PY34 = sys.version_info[0:2] >= (3, 4)
|
||||
|
||||
if PY3:
|
||||
string_types = str,
|
||||
integer_types = int,
|
||||
class_types = type,
|
||||
text_type = str
|
||||
binary_type = bytes
|
||||
|
||||
MAXSIZE = sys.maxsize
|
||||
else:
|
||||
string_types = basestring,
|
||||
integer_types = (int, long)
|
||||
class_types = (type, types.ClassType)
|
||||
text_type = unicode
|
||||
binary_type = str
|
||||
|
||||
if sys.platform.startswith("java"):
|
||||
# Jython always uses 32 bits.
|
||||
MAXSIZE = int((1 << 31) - 1)
|
||||
else:
|
||||
# It's possible to have sizeof(long) != sizeof(Py_ssize_t).
|
||||
class X(object):
|
||||
|
||||
def __len__(self):
|
||||
return 1 << 31
|
||||
try:
|
||||
len(X())
|
||||
except OverflowError:
|
||||
# 32-bit
|
||||
MAXSIZE = int((1 << 31) - 1)
|
||||
else:
|
||||
# 64-bit
|
||||
MAXSIZE = int((1 << 63) - 1)
|
||||
del X
|
||||
|
||||
if PY34:
|
||||
from importlib.util import spec_from_loader
|
||||
else:
|
||||
spec_from_loader = None
|
||||
|
||||
|
||||
def _add_doc(func, doc):
|
||||
"""Add documentation to a function."""
|
||||
func.__doc__ = doc
|
||||
|
||||
|
||||
def _import_module(name):
|
||||
"""Import module, returning the module after the last dot."""
|
||||
__import__(name)
|
||||
return sys.modules[name]
|
||||
|
||||
|
||||
class _LazyDescr(object):
|
||||
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
|
||||
def __get__(self, obj, tp):
|
||||
result = self._resolve()
|
||||
setattr(obj, self.name, result) # Invokes __set__.
|
||||
try:
|
||||
# This is a bit ugly, but it avoids running this again by
|
||||
# removing this descriptor.
|
||||
delattr(obj.__class__, self.name)
|
||||
except AttributeError:
|
||||
pass
|
||||
return result
|
||||
|
||||
|
||||
class MovedModule(_LazyDescr):
|
||||
|
||||
def __init__(self, name, old, new=None):
|
||||
super(MovedModule, self).__init__(name)
|
||||
if PY3:
|
||||
if new is None:
|
||||
new = name
|
||||
self.mod = new
|
||||
else:
|
||||
self.mod = old
|
||||
|
||||
def _resolve(self):
|
||||
return _import_module(self.mod)
|
||||
|
||||
def __getattr__(self, attr):
|
||||
_module = self._resolve()
|
||||
value = getattr(_module, attr)
|
||||
setattr(self, attr, value)
|
||||
return value
|
||||
|
||||
|
||||
class _LazyModule(types.ModuleType):
|
||||
|
||||
def __init__(self, name):
|
||||
super(_LazyModule, self).__init__(name)
|
||||
self.__doc__ = self.__class__.__doc__
|
||||
|
||||
def __dir__(self):
|
||||
attrs = ["__doc__", "__name__"]
|
||||
attrs += [attr.name for attr in self._moved_attributes]
|
||||
return attrs
|
||||
|
||||
# Subclasses should override this
|
||||
_moved_attributes = []
|
||||
|
||||
|
||||
class MovedAttribute(_LazyDescr):
|
||||
|
||||
def __init__(self, name, old_mod, new_mod, old_attr=None, new_attr=None):
|
||||
super(MovedAttribute, self).__init__(name)
|
||||
if PY3:
|
||||
if new_mod is None:
|
||||
new_mod = name
|
||||
self.mod = new_mod
|
||||
if new_attr is None:
|
||||
if old_attr is None:
|
||||
new_attr = name
|
||||
else:
|
||||
new_attr = old_attr
|
||||
self.attr = new_attr
|
||||
else:
|
||||
self.mod = old_mod
|
||||
if old_attr is None:
|
||||
old_attr = name
|
||||
self.attr = old_attr
|
||||
|
||||
def _resolve(self):
|
||||
module = _import_module(self.mod)
|
||||
return getattr(module, self.attr)
|
||||
|
||||
|
||||
class _SixMetaPathImporter(object):
|
||||
|
||||
"""
|
||||
A meta path importer to import six.moves and its submodules.
|
||||
|
||||
This class implements a PEP302 finder and loader. It should be compatible
|
||||
with Python 2.5 and all existing versions of Python3
|
||||
"""
|
||||
|
||||
def __init__(self, six_module_name):
|
||||
self.name = six_module_name
|
||||
self.known_modules = {}
|
||||
|
||||
def _add_module(self, mod, *fullnames):
|
||||
for fullname in fullnames:
|
||||
self.known_modules[self.name + "." + fullname] = mod
|
||||
|
||||
def _get_module(self, fullname):
|
||||
return self.known_modules[self.name + "." + fullname]
|
||||
|
||||
def find_module(self, fullname, path=None):
|
||||
if fullname in self.known_modules:
|
||||
return self
|
||||
return None
|
||||
|
||||
def find_spec(self, fullname, path, target=None):
|
||||
if fullname in self.known_modules:
|
||||
return spec_from_loader(fullname, self)
|
||||
return None
|
||||
|
||||
def __get_module(self, fullname):
|
||||
try:
|
||||
return self.known_modules[fullname]
|
||||
except KeyError:
|
||||
raise ImportError("This loader does not know module " + fullname)
|
||||
|
||||
def load_module(self, fullname):
|
||||
try:
|
||||
# in case of a reload
|
||||
return sys.modules[fullname]
|
||||
except KeyError:
|
||||
pass
|
||||
mod = self.__get_module(fullname)
|
||||
if isinstance(mod, MovedModule):
|
||||
mod = mod._resolve()
|
||||
else:
|
||||
mod.__loader__ = self
|
||||
sys.modules[fullname] = mod
|
||||
return mod
|
||||
|
||||
def is_package(self, fullname):
|
||||
"""
|
||||
Return true, if the named module is a package.
|
||||
|
||||
We need this method to get correct spec objects with
|
||||
Python 3.4 (see PEP451)
|
||||
"""
|
||||
return hasattr(self.__get_module(fullname), "__path__")
|
||||
|
||||
def get_code(self, fullname):
|
||||
"""Return None
|
||||
|
||||
Required, if is_package is implemented"""
|
||||
self.__get_module(fullname) # eventually raises ImportError
|
||||
return None
|
||||
get_source = get_code # same as get_code
|
||||
|
||||
def create_module(self, spec):
|
||||
return self.load_module(spec.name)
|
||||
|
||||
def exec_module(self, module):
|
||||
pass
|
||||
|
||||
_importer = _SixMetaPathImporter(__name__)
|
||||
|
||||
|
||||
class _MovedItems(_LazyModule):
|
||||
|
||||
"""Lazy loading of moved objects"""
|
||||
__path__ = [] # mark as package
|
||||
|
||||
|
||||
_moved_attributes = [
|
||||
MovedAttribute("cStringIO", "cStringIO", "io", "StringIO"),
|
||||
MovedAttribute("filter", "itertools", "builtins", "ifilter", "filter"),
|
||||
MovedAttribute("filterfalse", "itertools", "itertools", "ifilterfalse", "filterfalse"),
|
||||
MovedAttribute("input", "__builtin__", "builtins", "raw_input", "input"),
|
||||
MovedAttribute("intern", "__builtin__", "sys"),
|
||||
MovedAttribute("map", "itertools", "builtins", "imap", "map"),
|
||||
MovedAttribute("getcwd", "os", "os", "getcwdu", "getcwd"),
|
||||
MovedAttribute("getcwdb", "os", "os", "getcwd", "getcwdb"),
|
||||
MovedAttribute("getoutput", "commands", "subprocess"),
|
||||
MovedAttribute("range", "__builtin__", "builtins", "xrange", "range"),
|
||||
MovedAttribute("reload_module", "__builtin__", "importlib" if PY34 else "imp", "reload"),
|
||||
MovedAttribute("reduce", "__builtin__", "functools"),
|
||||
MovedAttribute("shlex_quote", "pipes", "shlex", "quote"),
|
||||
MovedAttribute("StringIO", "StringIO", "io"),
|
||||
MovedAttribute("UserDict", "UserDict", "collections"),
|
||||
MovedAttribute("UserList", "UserList", "collections"),
|
||||
MovedAttribute("UserString", "UserString", "collections"),
|
||||
MovedAttribute("xrange", "__builtin__", "builtins", "xrange", "range"),
|
||||
MovedAttribute("zip", "itertools", "builtins", "izip", "zip"),
|
||||
MovedAttribute("zip_longest", "itertools", "itertools", "izip_longest", "zip_longest"),
|
||||
MovedModule("builtins", "__builtin__"),
|
||||
MovedModule("configparser", "ConfigParser"),
|
||||
MovedModule("collections_abc", "collections", "collections.abc" if sys.version_info >= (3, 3) else "collections"),
|
||||
MovedModule("copyreg", "copy_reg"),
|
||||
MovedModule("dbm_gnu", "gdbm", "dbm.gnu"),
|
||||
MovedModule("dbm_ndbm", "dbm", "dbm.ndbm"),
|
||||
MovedModule("_dummy_thread", "dummy_thread", "_dummy_thread" if sys.version_info < (3, 9) else "_thread"),
|
||||
MovedModule("http_cookiejar", "cookielib", "http.cookiejar"),
|
||||
MovedModule("http_cookies", "Cookie", "http.cookies"),
|
||||
MovedModule("html_entities", "htmlentitydefs", "html.entities"),
|
||||
MovedModule("html_parser", "HTMLParser", "html.parser"),
|
||||
MovedModule("http_client", "httplib", "http.client"),
|
||||
MovedModule("email_mime_base", "email.MIMEBase", "email.mime.base"),
|
||||
MovedModule("email_mime_image", "email.MIMEImage", "email.mime.image"),
|
||||
MovedModule("email_mime_multipart", "email.MIMEMultipart", "email.mime.multipart"),
|
||||
MovedModule("email_mime_nonmultipart", "email.MIMENonMultipart", "email.mime.nonmultipart"),
|
||||
MovedModule("email_mime_text", "email.MIMEText", "email.mime.text"),
|
||||
MovedModule("BaseHTTPServer", "BaseHTTPServer", "http.server"),
|
||||
MovedModule("CGIHTTPServer", "CGIHTTPServer", "http.server"),
|
||||
MovedModule("SimpleHTTPServer", "SimpleHTTPServer", "http.server"),
|
||||
MovedModule("cPickle", "cPickle", "pickle"),
|
||||
MovedModule("queue", "Queue"),
|
||||
MovedModule("reprlib", "repr"),
|
||||
MovedModule("socketserver", "SocketServer"),
|
||||
MovedModule("_thread", "thread", "_thread"),
|
||||
MovedModule("tkinter", "Tkinter"),
|
||||
MovedModule("tkinter_dialog", "Dialog", "tkinter.dialog"),
|
||||
MovedModule("tkinter_filedialog", "FileDialog", "tkinter.filedialog"),
|
||||
MovedModule("tkinter_scrolledtext", "ScrolledText", "tkinter.scrolledtext"),
|
||||
MovedModule("tkinter_simpledialog", "SimpleDialog", "tkinter.simpledialog"),
|
||||
MovedModule("tkinter_tix", "Tix", "tkinter.tix"),
|
||||
MovedModule("tkinter_ttk", "ttk", "tkinter.ttk"),
|
||||
MovedModule("tkinter_constants", "Tkconstants", "tkinter.constants"),
|
||||
MovedModule("tkinter_dnd", "Tkdnd", "tkinter.dnd"),
|
||||
MovedModule("tkinter_colorchooser", "tkColorChooser",
|
||||
"tkinter.colorchooser"),
|
||||
MovedModule("tkinter_commondialog", "tkCommonDialog",
|
||||
"tkinter.commondialog"),
|
||||
MovedModule("tkinter_tkfiledialog", "tkFileDialog", "tkinter.filedialog"),
|
||||
MovedModule("tkinter_font", "tkFont", "tkinter.font"),
|
||||
MovedModule("tkinter_messagebox", "tkMessageBox", "tkinter.messagebox"),
|
||||
MovedModule("tkinter_tksimpledialog", "tkSimpleDialog",
|
||||
"tkinter.simpledialog"),
|
||||
MovedModule("urllib_parse", __name__ + ".moves.urllib_parse", "urllib.parse"),
|
||||
MovedModule("urllib_error", __name__ + ".moves.urllib_error", "urllib.error"),
|
||||
MovedModule("urllib", __name__ + ".moves.urllib", __name__ + ".moves.urllib"),
|
||||
MovedModule("urllib_robotparser", "robotparser", "urllib.robotparser"),
|
||||
MovedModule("xmlrpc_client", "xmlrpclib", "xmlrpc.client"),
|
||||
MovedModule("xmlrpc_server", "SimpleXMLRPCServer", "xmlrpc.server"),
|
||||
]
|
||||
# Add windows specific modules.
|
||||
if sys.platform == "win32":
|
||||
_moved_attributes += [
|
||||
MovedModule("winreg", "_winreg"),
|
||||
]
|
||||
|
||||
for attr in _moved_attributes:
|
||||
setattr(_MovedItems, attr.name, attr)
|
||||
if isinstance(attr, MovedModule):
|
||||
_importer._add_module(attr, "moves." + attr.name)
|
||||
del attr
|
||||
|
||||
_MovedItems._moved_attributes = _moved_attributes
|
||||
|
||||
moves = _MovedItems(__name__ + ".moves")
|
||||
_importer._add_module(moves, "moves")
|
||||
|
||||
|
||||
class Module_six_moves_urllib_parse(_LazyModule):
|
||||
|
||||
"""Lazy loading of moved objects in six.moves.urllib_parse"""
|
||||
|
||||
|
||||
_urllib_parse_moved_attributes = [
|
||||
MovedAttribute("ParseResult", "urlparse", "urllib.parse"),
|
||||
MovedAttribute("SplitResult", "urlparse", "urllib.parse"),
|
||||
MovedAttribute("parse_qs", "urlparse", "urllib.parse"),
|
||||
MovedAttribute("parse_qsl", "urlparse", "urllib.parse"),
|
||||
MovedAttribute("urldefrag", "urlparse", "urllib.parse"),
|
||||
MovedAttribute("urljoin", "urlparse", "urllib.parse"),
|
||||
MovedAttribute("urlparse", "urlparse", "urllib.parse"),
|
||||
MovedAttribute("urlsplit", "urlparse", "urllib.parse"),
|
||||
MovedAttribute("urlunparse", "urlparse", "urllib.parse"),
|
||||
MovedAttribute("urlunsplit", "urlparse", "urllib.parse"),
|
||||
MovedAttribute("quote", "urllib", "urllib.parse"),
|
||||
MovedAttribute("quote_plus", "urllib", "urllib.parse"),
|
||||
MovedAttribute("unquote", "urllib", "urllib.parse"),
|
||||
MovedAttribute("unquote_plus", "urllib", "urllib.parse"),
|
||||
MovedAttribute("unquote_to_bytes", "urllib", "urllib.parse", "unquote", "unquote_to_bytes"),
|
||||
MovedAttribute("urlencode", "urllib", "urllib.parse"),
|
||||
MovedAttribute("splitquery", "urllib", "urllib.parse"),
|
||||
MovedAttribute("splittag", "urllib", "urllib.parse"),
|
||||
MovedAttribute("splituser", "urllib", "urllib.parse"),
|
||||
MovedAttribute("splitvalue", "urllib", "urllib.parse"),
|
||||
MovedAttribute("uses_fragment", "urlparse", "urllib.parse"),
|
||||
MovedAttribute("uses_netloc", "urlparse", "urllib.parse"),
|
||||
MovedAttribute("uses_params", "urlparse", "urllib.parse"),
|
||||
MovedAttribute("uses_query", "urlparse", "urllib.parse"),
|
||||
MovedAttribute("uses_relative", "urlparse", "urllib.parse"),
|
||||
]
|
||||
for attr in _urllib_parse_moved_attributes:
|
||||
setattr(Module_six_moves_urllib_parse, attr.name, attr)
|
||||
del attr
|
||||
|
||||
Module_six_moves_urllib_parse._moved_attributes = _urllib_parse_moved_attributes
|
||||
|
||||
_importer._add_module(Module_six_moves_urllib_parse(__name__ + ".moves.urllib_parse"),
|
||||
"moves.urllib_parse", "moves.urllib.parse")
|
||||
|
||||
|
||||
class Module_six_moves_urllib_error(_LazyModule):
|
||||
|
||||
"""Lazy loading of moved objects in six.moves.urllib_error"""
|
||||
|
||||
|
||||
_urllib_error_moved_attributes = [
|
||||
MovedAttribute("URLError", "urllib2", "urllib.error"),
|
||||
MovedAttribute("HTTPError", "urllib2", "urllib.error"),
|
||||
MovedAttribute("ContentTooShortError", "urllib", "urllib.error"),
|
||||
]
|
||||
for attr in _urllib_error_moved_attributes:
|
||||
setattr(Module_six_moves_urllib_error, attr.name, attr)
|
||||
del attr
|
||||
|
||||
Module_six_moves_urllib_error._moved_attributes = _urllib_error_moved_attributes
|
||||
|
||||
_importer._add_module(Module_six_moves_urllib_error(__name__ + ".moves.urllib.error"),
|
||||
"moves.urllib_error", "moves.urllib.error")
|
||||
|
||||
|
||||
class Module_six_moves_urllib_request(_LazyModule):
|
||||
|
||||
"""Lazy loading of moved objects in six.moves.urllib_request"""
|
||||
|
||||
|
||||
_urllib_request_moved_attributes = [
|
||||
MovedAttribute("urlopen", "urllib2", "urllib.request"),
|
||||
MovedAttribute("install_opener", "urllib2", "urllib.request"),
|
||||
MovedAttribute("build_opener", "urllib2", "urllib.request"),
|
||||
MovedAttribute("pathname2url", "urllib", "urllib.request"),
|
||||
MovedAttribute("url2pathname", "urllib", "urllib.request"),
|
||||
MovedAttribute("getproxies", "urllib", "urllib.request"),
|
||||
MovedAttribute("Request", "urllib2", "urllib.request"),
|
||||
MovedAttribute("OpenerDirector", "urllib2", "urllib.request"),
|
||||
MovedAttribute("HTTPDefaultErrorHandler", "urllib2", "urllib.request"),
|
||||
MovedAttribute("HTTPRedirectHandler", "urllib2", "urllib.request"),
|
||||
MovedAttribute("HTTPCookieProcessor", "urllib2", "urllib.request"),
|
||||
MovedAttribute("ProxyHandler", "urllib2", "urllib.request"),
|
||||
MovedAttribute("BaseHandler", "urllib2", "urllib.request"),
|
||||
MovedAttribute("HTTPPasswordMgr", "urllib2", "urllib.request"),
|
||||
MovedAttribute("HTTPPasswordMgrWithDefaultRealm", "urllib2", "urllib.request"),
|
||||
MovedAttribute("AbstractBasicAuthHandler", "urllib2", "urllib.request"),
|
||||
MovedAttribute("HTTPBasicAuthHandler", "urllib2", "urllib.request"),
|
||||
MovedAttribute("ProxyBasicAuthHandler", "urllib2", "urllib.request"),
|
||||
MovedAttribute("AbstractDigestAuthHandler", "urllib2", "urllib.request"),
|
||||
MovedAttribute("HTTPDigestAuthHandler", "urllib2", "urllib.request"),
|
||||
MovedAttribute("ProxyDigestAuthHandler", "urllib2", "urllib.request"),
|
||||
MovedAttribute("HTTPHandler", "urllib2", "urllib.request"),
|
||||
MovedAttribute("HTTPSHandler", "urllib2", "urllib.request"),
|
||||
MovedAttribute("FileHandler", "urllib2", "urllib.request"),
|
||||
MovedAttribute("FTPHandler", "urllib2", "urllib.request"),
|
||||
MovedAttribute("CacheFTPHandler", "urllib2", "urllib.request"),
|
||||
MovedAttribute("UnknownHandler", "urllib2", "urllib.request"),
|
||||
MovedAttribute("HTTPErrorProcessor", "urllib2", "urllib.request"),
|
||||
MovedAttribute("urlretrieve", "urllib", "urllib.request"),
|
||||
MovedAttribute("urlcleanup", "urllib", "urllib.request"),
|
||||
MovedAttribute("URLopener", "urllib", "urllib.request"),
|
||||
MovedAttribute("FancyURLopener", "urllib", "urllib.request"),
|
||||
MovedAttribute("proxy_bypass", "urllib", "urllib.request"),
|
||||
MovedAttribute("parse_http_list", "urllib2", "urllib.request"),
|
||||
MovedAttribute("parse_keqv_list", "urllib2", "urllib.request"),
|
||||
]
|
||||
for attr in _urllib_request_moved_attributes:
|
||||
setattr(Module_six_moves_urllib_request, attr.name, attr)
|
||||
del attr
|
||||
|
||||
Module_six_moves_urllib_request._moved_attributes = _urllib_request_moved_attributes
|
||||
|
||||
_importer._add_module(Module_six_moves_urllib_request(__name__ + ".moves.urllib.request"),
|
||||
"moves.urllib_request", "moves.urllib.request")
|
||||
|
||||
|
||||
class Module_six_moves_urllib_response(_LazyModule):
|
||||
|
||||
"""Lazy loading of moved objects in six.moves.urllib_response"""
|
||||
|
||||
|
||||
_urllib_response_moved_attributes = [
|
||||
MovedAttribute("addbase", "urllib", "urllib.response"),
|
||||
MovedAttribute("addclosehook", "urllib", "urllib.response"),
|
||||
MovedAttribute("addinfo", "urllib", "urllib.response"),
|
||||
MovedAttribute("addinfourl", "urllib", "urllib.response"),
|
||||
]
|
||||
for attr in _urllib_response_moved_attributes:
|
||||
setattr(Module_six_moves_urllib_response, attr.name, attr)
|
||||
del attr
|
||||
|
||||
Module_six_moves_urllib_response._moved_attributes = _urllib_response_moved_attributes
|
||||
|
||||
_importer._add_module(Module_six_moves_urllib_response(__name__ + ".moves.urllib.response"),
|
||||
"moves.urllib_response", "moves.urllib.response")
|
||||
|
||||
|
||||
class Module_six_moves_urllib_robotparser(_LazyModule):
|
||||
|
||||
"""Lazy loading of moved objects in six.moves.urllib_robotparser"""
|
||||
|
||||
|
||||
_urllib_robotparser_moved_attributes = [
|
||||
MovedAttribute("RobotFileParser", "robotparser", "urllib.robotparser"),
|
||||
]
|
||||
for attr in _urllib_robotparser_moved_attributes:
|
||||
setattr(Module_six_moves_urllib_robotparser, attr.name, attr)
|
||||
del attr
|
||||
|
||||
Module_six_moves_urllib_robotparser._moved_attributes = _urllib_robotparser_moved_attributes
|
||||
|
||||
_importer._add_module(Module_six_moves_urllib_robotparser(__name__ + ".moves.urllib.robotparser"),
|
||||
"moves.urllib_robotparser", "moves.urllib.robotparser")
|
||||
|
||||
|
||||
class Module_six_moves_urllib(types.ModuleType):
|
||||
|
||||
"""Create a six.moves.urllib namespace that resembles the Python 3 namespace"""
|
||||
__path__ = [] # mark as package
|
||||
parse = _importer._get_module("moves.urllib_parse")
|
||||
error = _importer._get_module("moves.urllib_error")
|
||||
request = _importer._get_module("moves.urllib_request")
|
||||
response = _importer._get_module("moves.urllib_response")
|
||||
robotparser = _importer._get_module("moves.urllib_robotparser")
|
||||
|
||||
def __dir__(self):
|
||||
return ['parse', 'error', 'request', 'response', 'robotparser']
|
||||
|
||||
_importer._add_module(Module_six_moves_urllib(__name__ + ".moves.urllib"),
|
||||
"moves.urllib")
|
||||
|
||||
|
||||
def add_move(move):
|
||||
"""Add an item to six.moves."""
|
||||
setattr(_MovedItems, move.name, move)
|
||||
|
||||
|
||||
def remove_move(name):
|
||||
"""Remove item from six.moves."""
|
||||
try:
|
||||
delattr(_MovedItems, name)
|
||||
except AttributeError:
|
||||
try:
|
||||
del moves.__dict__[name]
|
||||
except KeyError:
|
||||
raise AttributeError("no such move, %r" % (name,))
|
||||
|
||||
|
||||
if PY3:
|
||||
_meth_func = "__func__"
|
||||
_meth_self = "__self__"
|
||||
|
||||
_func_closure = "__closure__"
|
||||
_func_code = "__code__"
|
||||
_func_defaults = "__defaults__"
|
||||
_func_globals = "__globals__"
|
||||
else:
|
||||
_meth_func = "im_func"
|
||||
_meth_self = "im_self"
|
||||
|
||||
_func_closure = "func_closure"
|
||||
_func_code = "func_code"
|
||||
_func_defaults = "func_defaults"
|
||||
_func_globals = "func_globals"
|
||||
|
||||
|
||||
try:
|
||||
advance_iterator = next
|
||||
except NameError:
|
||||
def advance_iterator(it):
|
||||
return it.next()
|
||||
next = advance_iterator
|
||||
|
||||
|
||||
try:
|
||||
callable = callable
|
||||
except NameError:
|
||||
def callable(obj):
|
||||
return any("__call__" in klass.__dict__ for klass in type(obj).__mro__)
|
||||
|
||||
|
||||
if PY3:
|
||||
def get_unbound_function(unbound):
|
||||
return unbound
|
||||
|
||||
create_bound_method = types.MethodType
|
||||
|
||||
def create_unbound_method(func, cls):
|
||||
return func
|
||||
|
||||
Iterator = object
|
||||
else:
|
||||
def get_unbound_function(unbound):
|
||||
return unbound.im_func
|
||||
|
||||
def create_bound_method(func, obj):
|
||||
return types.MethodType(func, obj, obj.__class__)
|
||||
|
||||
def create_unbound_method(func, cls):
|
||||
return types.MethodType(func, None, cls)
|
||||
|
||||
class Iterator(object):
|
||||
|
||||
def next(self):
|
||||
return type(self).__next__(self)
|
||||
|
||||
callable = callable
|
||||
_add_doc(get_unbound_function,
|
||||
"""Get the function out of a possibly unbound function""")
|
||||
|
||||
|
||||
get_method_function = operator.attrgetter(_meth_func)
|
||||
get_method_self = operator.attrgetter(_meth_self)
|
||||
get_function_closure = operator.attrgetter(_func_closure)
|
||||
get_function_code = operator.attrgetter(_func_code)
|
||||
get_function_defaults = operator.attrgetter(_func_defaults)
|
||||
get_function_globals = operator.attrgetter(_func_globals)
|
||||
|
||||
|
||||
if PY3:
|
||||
def iterkeys(d, **kw):
|
||||
return iter(d.keys(**kw))
|
||||
|
||||
def itervalues(d, **kw):
|
||||
return iter(d.values(**kw))
|
||||
|
||||
def iteritems(d, **kw):
|
||||
return iter(d.items(**kw))
|
||||
|
||||
def iterlists(d, **kw):
|
||||
return iter(d.lists(**kw))
|
||||
|
||||
viewkeys = operator.methodcaller("keys")
|
||||
|
||||
viewvalues = operator.methodcaller("values")
|
||||
|
||||
viewitems = operator.methodcaller("items")
|
||||
else:
|
||||
def iterkeys(d, **kw):
|
||||
return d.iterkeys(**kw)
|
||||
|
||||
def itervalues(d, **kw):
|
||||
return d.itervalues(**kw)
|
||||
|
||||
def iteritems(d, **kw):
|
||||
return d.iteritems(**kw)
|
||||
|
||||
def iterlists(d, **kw):
|
||||
return d.iterlists(**kw)
|
||||
|
||||
viewkeys = operator.methodcaller("viewkeys")
|
||||
|
||||
viewvalues = operator.methodcaller("viewvalues")
|
||||
|
||||
viewitems = operator.methodcaller("viewitems")
|
||||
|
||||
_add_doc(iterkeys, "Return an iterator over the keys of a dictionary.")
|
||||
_add_doc(itervalues, "Return an iterator over the values of a dictionary.")
|
||||
_add_doc(iteritems,
|
||||
"Return an iterator over the (key, value) pairs of a dictionary.")
|
||||
_add_doc(iterlists,
|
||||
"Return an iterator over the (key, [values]) pairs of a dictionary.")
|
||||
|
||||
|
||||
if PY3:
|
||||
def b(s):
|
||||
return s.encode("latin-1")
|
||||
|
||||
def u(s):
|
||||
return s
|
||||
unichr = chr
|
||||
import struct
|
||||
int2byte = struct.Struct(">B").pack
|
||||
del struct
|
||||
byte2int = operator.itemgetter(0)
|
||||
indexbytes = operator.getitem
|
||||
iterbytes = iter
|
||||
import io
|
||||
StringIO = io.StringIO
|
||||
BytesIO = io.BytesIO
|
||||
del io
|
||||
_assertCountEqual = "assertCountEqual"
|
||||
if sys.version_info[1] <= 1:
|
||||
_assertRaisesRegex = "assertRaisesRegexp"
|
||||
_assertRegex = "assertRegexpMatches"
|
||||
_assertNotRegex = "assertNotRegexpMatches"
|
||||
else:
|
||||
_assertRaisesRegex = "assertRaisesRegex"
|
||||
_assertRegex = "assertRegex"
|
||||
_assertNotRegex = "assertNotRegex"
|
||||
else:
|
||||
def b(s):
|
||||
return s
|
||||
# Workaround for standalone backslash
|
||||
|
||||
def u(s):
|
||||
return unicode(s.replace(r'\\', r'\\\\'), "unicode_escape")
|
||||
unichr = unichr
|
||||
int2byte = chr
|
||||
|
||||
def byte2int(bs):
|
||||
return ord(bs[0])
|
||||
|
||||
def indexbytes(buf, i):
|
||||
return ord(buf[i])
|
||||
iterbytes = functools.partial(itertools.imap, ord)
|
||||
import StringIO
|
||||
StringIO = BytesIO = StringIO.StringIO
|
||||
_assertCountEqual = "assertItemsEqual"
|
||||
_assertRaisesRegex = "assertRaisesRegexp"
|
||||
_assertRegex = "assertRegexpMatches"
|
||||
_assertNotRegex = "assertNotRegexpMatches"
|
||||
_add_doc(b, """Byte literal""")
|
||||
_add_doc(u, """Text literal""")
|
||||
|
||||
|
||||
def assertCountEqual(self, *args, **kwargs):
|
||||
return getattr(self, _assertCountEqual)(*args, **kwargs)
|
||||
|
||||
|
||||
def assertRaisesRegex(self, *args, **kwargs):
|
||||
return getattr(self, _assertRaisesRegex)(*args, **kwargs)
|
||||
|
||||
|
||||
def assertRegex(self, *args, **kwargs):
|
||||
return getattr(self, _assertRegex)(*args, **kwargs)
|
||||
|
||||
|
||||
def assertNotRegex(self, *args, **kwargs):
|
||||
return getattr(self, _assertNotRegex)(*args, **kwargs)
|
||||
|
||||
|
||||
if PY3:
|
||||
exec_ = getattr(moves.builtins, "exec")
|
||||
|
||||
def reraise(tp, value, tb=None):
|
||||
try:
|
||||
if value is None:
|
||||
value = tp()
|
||||
if value.__traceback__ is not tb:
|
||||
raise value.with_traceback(tb)
|
||||
raise value
|
||||
finally:
|
||||
value = None
|
||||
tb = None
|
||||
|
||||
else:
|
||||
def exec_(_code_, _globs_=None, _locs_=None):
|
||||
"""Execute code in a namespace."""
|
||||
if _globs_ is None:
|
||||
frame = sys._getframe(1)
|
||||
_globs_ = frame.f_globals
|
||||
if _locs_ is None:
|
||||
_locs_ = frame.f_locals
|
||||
del frame
|
||||
elif _locs_ is None:
|
||||
_locs_ = _globs_
|
||||
exec("""exec _code_ in _globs_, _locs_""")
|
||||
|
||||
exec_("""def reraise(tp, value, tb=None):
|
||||
try:
|
||||
raise tp, value, tb
|
||||
finally:
|
||||
tb = None
|
||||
""")
|
||||
|
||||
|
||||
if sys.version_info[:2] > (3,):
|
||||
exec_("""def raise_from(value, from_value):
|
||||
try:
|
||||
raise value from from_value
|
||||
finally:
|
||||
value = None
|
||||
""")
|
||||
else:
|
||||
def raise_from(value, from_value):
|
||||
raise value
|
||||
|
||||
|
||||
print_ = getattr(moves.builtins, "print", None)
|
||||
if print_ is None:
|
||||
def print_(*args, **kwargs):
|
||||
"""The new-style print function for Python 2.4 and 2.5."""
|
||||
fp = kwargs.pop("file", sys.stdout)
|
||||
if fp is None:
|
||||
return
|
||||
|
||||
def write(data):
|
||||
if not isinstance(data, basestring):
|
||||
data = str(data)
|
||||
# If the file has an encoding, encode unicode with it.
|
||||
if (isinstance(fp, file) and
|
||||
isinstance(data, unicode) and
|
||||
fp.encoding is not None):
|
||||
errors = getattr(fp, "errors", None)
|
||||
if errors is None:
|
||||
errors = "strict"
|
||||
data = data.encode(fp.encoding, errors)
|
||||
fp.write(data)
|
||||
want_unicode = False
|
||||
sep = kwargs.pop("sep", None)
|
||||
if sep is not None:
|
||||
if isinstance(sep, unicode):
|
||||
want_unicode = True
|
||||
elif not isinstance(sep, str):
|
||||
raise TypeError("sep must be None or a string")
|
||||
end = kwargs.pop("end", None)
|
||||
if end is not None:
|
||||
if isinstance(end, unicode):
|
||||
want_unicode = True
|
||||
elif not isinstance(end, str):
|
||||
raise TypeError("end must be None or a string")
|
||||
if kwargs:
|
||||
raise TypeError("invalid keyword arguments to print()")
|
||||
if not want_unicode:
|
||||
for arg in args:
|
||||
if isinstance(arg, unicode):
|
||||
want_unicode = True
|
||||
break
|
||||
if want_unicode:
|
||||
newline = unicode("\n")
|
||||
space = unicode(" ")
|
||||
else:
|
||||
newline = "\n"
|
||||
space = " "
|
||||
if sep is None:
|
||||
sep = space
|
||||
if end is None:
|
||||
end = newline
|
||||
for i, arg in enumerate(args):
|
||||
if i:
|
||||
write(sep)
|
||||
write(arg)
|
||||
write(end)
|
||||
if sys.version_info[:2] < (3, 3):
|
||||
_print = print_
|
||||
|
||||
def print_(*args, **kwargs):
|
||||
fp = kwargs.get("file", sys.stdout)
|
||||
flush = kwargs.pop("flush", False)
|
||||
_print(*args, **kwargs)
|
||||
if flush and fp is not None:
|
||||
fp.flush()
|
||||
|
||||
_add_doc(reraise, """Reraise an exception.""")
|
||||
|
||||
if sys.version_info[0:2] < (3, 4):
|
||||
# This does exactly the same what the :func:`py3:functools.update_wrapper`
|
||||
# function does on Python versions after 3.2. It sets the ``__wrapped__``
|
||||
# attribute on ``wrapper`` object and it doesn't raise an error if any of
|
||||
# the attributes mentioned in ``assigned`` and ``updated`` are missing on
|
||||
# ``wrapped`` object.
|
||||
def _update_wrapper(wrapper, wrapped,
|
||||
assigned=functools.WRAPPER_ASSIGNMENTS,
|
||||
updated=functools.WRAPPER_UPDATES):
|
||||
for attr in assigned:
|
||||
try:
|
||||
value = getattr(wrapped, attr)
|
||||
except AttributeError:
|
||||
continue
|
||||
else:
|
||||
setattr(wrapper, attr, value)
|
||||
for attr in updated:
|
||||
getattr(wrapper, attr).update(getattr(wrapped, attr, {}))
|
||||
wrapper.__wrapped__ = wrapped
|
||||
return wrapper
|
||||
_update_wrapper.__doc__ = functools.update_wrapper.__doc__
|
||||
|
||||
def wraps(wrapped, assigned=functools.WRAPPER_ASSIGNMENTS,
|
||||
updated=functools.WRAPPER_UPDATES):
|
||||
return functools.partial(_update_wrapper, wrapped=wrapped,
|
||||
assigned=assigned, updated=updated)
|
||||
wraps.__doc__ = functools.wraps.__doc__
|
||||
|
||||
else:
|
||||
wraps = functools.wraps
|
||||
|
||||
|
||||
def with_metaclass(meta, *bases):
|
||||
"""Create a base class with a metaclass."""
|
||||
# This requires a bit of explanation: the basic idea is to make a dummy
|
||||
# metaclass for one level of class instantiation that replaces itself with
|
||||
# the actual metaclass.
|
||||
class metaclass(type):
|
||||
|
||||
def __new__(cls, name, this_bases, d):
|
||||
if sys.version_info[:2] >= (3, 7):
|
||||
# This version introduced PEP 560 that requires a bit
|
||||
# of extra care (we mimic what is done by __build_class__).
|
||||
resolved_bases = types.resolve_bases(bases)
|
||||
if resolved_bases is not bases:
|
||||
d['__orig_bases__'] = bases
|
||||
else:
|
||||
resolved_bases = bases
|
||||
return meta(name, resolved_bases, d)
|
||||
|
||||
@classmethod
|
||||
def __prepare__(cls, name, this_bases):
|
||||
return meta.__prepare__(name, bases)
|
||||
return type.__new__(metaclass, 'temporary_class', (), {})
|
||||
|
||||
|
||||
def add_metaclass(metaclass):
|
||||
"""Class decorator for creating a class with a metaclass."""
|
||||
def wrapper(cls):
|
||||
orig_vars = cls.__dict__.copy()
|
||||
slots = orig_vars.get('__slots__')
|
||||
if slots is not None:
|
||||
if isinstance(slots, str):
|
||||
slots = [slots]
|
||||
for slots_var in slots:
|
||||
orig_vars.pop(slots_var)
|
||||
orig_vars.pop('__dict__', None)
|
||||
orig_vars.pop('__weakref__', None)
|
||||
if hasattr(cls, '__qualname__'):
|
||||
orig_vars['__qualname__'] = cls.__qualname__
|
||||
return metaclass(cls.__name__, cls.__bases__, orig_vars)
|
||||
return wrapper
|
||||
|
||||
|
||||
def ensure_binary(s, encoding='utf-8', errors='strict'):
|
||||
"""Coerce **s** to six.binary_type.
|
||||
|
||||
For Python 2:
|
||||
- `unicode` -> encoded to `str`
|
||||
- `str` -> `str`
|
||||
|
||||
For Python 3:
|
||||
- `str` -> encoded to `bytes`
|
||||
- `bytes` -> `bytes`
|
||||
"""
|
||||
if isinstance(s, binary_type):
|
||||
return s
|
||||
if isinstance(s, text_type):
|
||||
return s.encode(encoding, errors)
|
||||
raise TypeError("not expecting type '%s'" % type(s))
|
||||
|
||||
|
||||
def ensure_str(s, encoding='utf-8', errors='strict'):
|
||||
"""Coerce *s* to `str`.
|
||||
|
||||
For Python 2:
|
||||
- `unicode` -> encoded to `str`
|
||||
- `str` -> `str`
|
||||
|
||||
For Python 3:
|
||||
- `str` -> `str`
|
||||
- `bytes` -> decoded to `str`
|
||||
"""
|
||||
# Optimization: Fast return for the common case.
|
||||
if type(s) is str:
|
||||
return s
|
||||
if PY2 and isinstance(s, text_type):
|
||||
return s.encode(encoding, errors)
|
||||
elif PY3 and isinstance(s, binary_type):
|
||||
return s.decode(encoding, errors)
|
||||
elif not isinstance(s, (text_type, binary_type)):
|
||||
raise TypeError("not expecting type '%s'" % type(s))
|
||||
return s
|
||||
|
||||
|
||||
def ensure_text(s, encoding='utf-8', errors='strict'):
|
||||
"""Coerce *s* to six.text_type.
|
||||
|
||||
For Python 2:
|
||||
- `unicode` -> `unicode`
|
||||
- `str` -> `unicode`
|
||||
|
||||
For Python 3:
|
||||
- `str` -> `str`
|
||||
- `bytes` -> decoded to `str`
|
||||
"""
|
||||
if isinstance(s, binary_type):
|
||||
return s.decode(encoding, errors)
|
||||
elif isinstance(s, text_type):
|
||||
return s
|
||||
else:
|
||||
raise TypeError("not expecting type '%s'" % type(s))
|
||||
|
||||
|
||||
def python_2_unicode_compatible(klass):
|
||||
"""
|
||||
A class decorator that defines __unicode__ and __str__ methods under Python 2.
|
||||
Under Python 3 it does nothing.
|
||||
|
||||
To support Python 2 and 3 with a single code base, define a __str__ method
|
||||
returning text and apply this decorator to the class.
|
||||
"""
|
||||
if PY2:
|
||||
if '__str__' not in klass.__dict__:
|
||||
raise ValueError("@python_2_unicode_compatible cannot be applied "
|
||||
"to %s because it doesn't define __str__()." %
|
||||
klass.__name__)
|
||||
klass.__unicode__ = klass.__str__
|
||||
klass.__str__ = lambda self: self.__unicode__().encode('utf-8')
|
||||
return klass
|
||||
|
||||
|
||||
# Complete the moves implementation.
|
||||
# This code is at the end of this module to speed up module loading.
|
||||
# Turn this module into a package.
|
||||
__path__ = [] # required for PEP 302 and PEP 451
|
||||
__package__ = __name__ # see PEP 366 @ReservedAssignment
|
||||
if globals().get("__spec__") is not None:
|
||||
__spec__.submodule_search_locations = [] # PEP 451 @UndefinedVariable
|
||||
# Remove other six meta path importers, since they cause problems. This can
|
||||
# happen if six is removed from sys.modules and then reloaded. (Setuptools does
|
||||
# this for some reason.)
|
||||
if sys.meta_path:
|
||||
for i, importer in enumerate(sys.meta_path):
|
||||
# Here's some real nastiness: Another "instance" of the six module might
|
||||
# be floating around. Therefore, we can't use isinstance() to check for
|
||||
# the six meta path importer, since the other six instance will have
|
||||
# inserted an importer with different class.
|
||||
if (type(importer).__name__ == "_SixMetaPathImporter" and
|
||||
importer.name == __name__):
|
||||
del sys.meta_path[i]
|
||||
break
|
||||
del i, importer
|
||||
# Finally, add the importer to the meta path import hook.
|
||||
sys.meta_path.append(_importer)
|
||||
@@ -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: 966ae7177c1d48c9ee15971994c623b5
|
||||
config: 55e1a358a797ca9390d1b4f0e18f7ba3
|
||||
tags: 645f666f9bcd5a90fca523b33c5a78b7
|
||||
|
||||
|
After Width: | Height: | Size: 86 KiB |
@@ -1,8 +1,9 @@
|
||||
.. _examples-main:
|
||||
|
||||
********
|
||||
Examples
|
||||
********
|
||||
*************
|
||||
Code Examples
|
||||
*************
|
||||
|
||||
A number of examples are included in the source distribution of Reticulum.
|
||||
You can use these examples to learn how to write your own programs.
|
||||
|
||||
|
||||
@@ -10,15 +10,60 @@ Try Using a Reticulum-based Program
|
||||
=============================================
|
||||
If you simply want to try using a program built with Reticulum, you can take
|
||||
a look at `Nomad Network <https://github.com/markqvist/nomadnet>`_, which
|
||||
provides a basic encrypted communications suite built completely on Reticulum.
|
||||
provides a complete encrypted communications suite built with Reticulum.
|
||||
|
||||
.. image:: screenshots/nomadnet3.png
|
||||
:target: _images/nomadnet3.png
|
||||
.. image:: screenshots/nomadnet_3.png
|
||||
:target: _images/nomadnet_3.png
|
||||
|
||||
`Nomad Network <https://github.com/markqvist/nomadnet>`_ is a user-facing client
|
||||
in the development for the messaging and information-sharing protocol
|
||||
`LXMF <https://github.com/markqvist/lxmf>`_, another project built with Reticulum.
|
||||
|
||||
You can install Nomad Network via pip:
|
||||
|
||||
.. code::
|
||||
|
||||
# Install ...
|
||||
pip3 install nomadnet
|
||||
|
||||
# ... and run
|
||||
nomadnet
|
||||
|
||||
|
||||
|
||||
Using the Included Utilities
|
||||
=============================================
|
||||
Reticulum comes with a range of included utilities that make it easier to
|
||||
manage your network, check connectivity and make Reticulum available to other
|
||||
programs on your system.
|
||||
|
||||
You can use ``rnsd`` to run Reticulum as a background or foreground service,
|
||||
and the ``rnstatus``, ``rnpath`` and ``rnprobe`` utilities to view and query
|
||||
network status and connectivity.
|
||||
|
||||
To learn more about these utility programs, have a look at the
|
||||
:ref:`Using Reticulum on Your System<using-main>` chapter of this manual.
|
||||
|
||||
Creating a Network With Reticulum
|
||||
=============================================
|
||||
To create a network, you will need to specify one or more *interfaces* for
|
||||
Reticulum to use. This is done in the Reticulum configuration file, which by
|
||||
default is located at ``~/.reticulum/config``.
|
||||
|
||||
When Reticulum is started for the first time, it will create a default
|
||||
configuration file, with one active interface. This default interface uses
|
||||
your existing ethernet network (if there is one), and only allows you to
|
||||
communicate with other Reticulum peers within your local broadcast domain.
|
||||
|
||||
To communicate further, you will have to add one or more interfaces. The default
|
||||
configuration includes a number of examples, ranging from using TCP over the
|
||||
internet, to LoRa and Packet Radio interfaces.
|
||||
|
||||
Possibly, the examples in the config file are enough to get you started. If
|
||||
you want more information, you can read the :ref:`Building Networks<networks-main>`
|
||||
and :ref:`Interfaces<interfaces-main>` chapters of this manual.
|
||||
|
||||
|
||||
Develop a Program with Reticulum
|
||||
===========================================
|
||||
If you want to develop programs that use Reticulum, the easiest way to get
|
||||
@@ -44,7 +89,7 @@ don't use pip, but try this recipe:
|
||||
.. code::
|
||||
|
||||
# Install dependencies
|
||||
pip3 install cryptography pyserial
|
||||
pip3 install cryptography pyserial netifaces
|
||||
|
||||
# Clone repository
|
||||
git clone https://github.com/markqvist/Reticulum.git
|
||||
|
||||
@@ -2,14 +2,17 @@
|
||||
Reticulum Network Stack Manual
|
||||
******************************
|
||||
This manual aims to provide you with all the information you need to
|
||||
understand Reticulum, develop programs using it, or to participate in
|
||||
the development of Reticulum itself.
|
||||
understand Reticulum, build networks or develop programs using it, or
|
||||
to participate in the development of Reticulum itself.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 3
|
||||
|
||||
whatis
|
||||
gettingstartedfast
|
||||
using
|
||||
networks
|
||||
interfaces
|
||||
understanding
|
||||
reference
|
||||
examples
|
||||
|
||||
@@ -0,0 +1,346 @@
|
||||
|
||||
.. _interfaces-main:
|
||||
|
||||
********************
|
||||
Supported Interfaces
|
||||
********************
|
||||
|
||||
Reticulum supports using many kinds of devices as networking interfaces, and
|
||||
allows you to mix and match them in any way you choose. The number of distinct
|
||||
network topologies you can create with Reticulum is more or less endless, but
|
||||
common to them all is that you will need to define one or more *interfaces*
|
||||
for Reticulum to use.
|
||||
|
||||
The following sections describe the interfaces currently available in Reticulum,
|
||||
and gives example configurations for the respective interface types.
|
||||
|
||||
For a high-level overview of how networks can be formed over different interface
|
||||
types, have a look at the :ref:`Building Networks<networks-main>` chapter of this
|
||||
manual.
|
||||
|
||||
.. _interfaces-udp:
|
||||
|
||||
UDP Interface
|
||||
=============
|
||||
|
||||
A UDP interface can be useful for communicating over IP networks, both
|
||||
private and the internet. It can also allow broadcast communication
|
||||
over IP networks, so it can provide an easy way to enable connectivity
|
||||
with all other peers on a local area network.
|
||||
|
||||
The below example is enabled by default on new Reticulum installations,
|
||||
as it provides an easy way to get started and to test Reticulum on a
|
||||
pre-existing LAN.
|
||||
|
||||
.. code::
|
||||
|
||||
# This example enables communication with other
|
||||
# local Reticulum peers over UDP.
|
||||
|
||||
[[Default UDP Interface]]
|
||||
type = UDPInterface
|
||||
interface_enabled = True
|
||||
outgoing = True
|
||||
listen_ip = 0.0.0.0
|
||||
listen_port = 4242
|
||||
forward_ip = 255.255.255.255
|
||||
forward_port = 4242
|
||||
|
||||
# The above configuration will allow communication
|
||||
# within the local broadcast domains of all local
|
||||
# IP interfaces. This is enabled by default as an
|
||||
# easy way to get started, but you might want to
|
||||
# consider altering it to something more specific.
|
||||
|
||||
# Instead of specifying listen_ip, listen_port,
|
||||
# forward_ip and forward_port, you can also bind
|
||||
# to a specific network device like below.
|
||||
|
||||
# device = eth0
|
||||
# port = 4242
|
||||
|
||||
# Assuming the eth0 device has the address
|
||||
# 10.55.0.72/24, the above configuration would
|
||||
# be equivalent to the following manual setup.
|
||||
# Note that we are both listening and forwarding to
|
||||
# the broadcast address of the network segments.
|
||||
|
||||
# listen_ip = 10.55.0.255
|
||||
# listen_port = 4242
|
||||
# forward_ip = 10.55.0.255
|
||||
# forward_port = 4242
|
||||
|
||||
# You can of course also communicate only with
|
||||
# a single IP address
|
||||
|
||||
# listen_ip = 10.55.0.15
|
||||
# listen_port = 4242
|
||||
# forward_ip = 10.55.0.16
|
||||
# forward_port = 4242
|
||||
|
||||
.. _interfaces-tcps:
|
||||
|
||||
TCP Server Interface
|
||||
====================
|
||||
|
||||
The TCP Server interface is suitable for allowing other peers to connect over
|
||||
the Internet or private IP networks. When a TCP server interface has been
|
||||
configured, other Reticulum peers can connect to it with a TCP Client interface.
|
||||
|
||||
.. code::
|
||||
|
||||
# This example demonstrates a TCP server interface.
|
||||
# It will listen for incoming connections on the
|
||||
# specified IP address and port number.
|
||||
|
||||
[[TCP Server Interface]]
|
||||
type = TCPServerInterface
|
||||
interface_enabled = True
|
||||
outgoing = True
|
||||
|
||||
# This configuration will listen on all IP
|
||||
# interfaces on port 4242
|
||||
|
||||
listen_ip = 0.0.0.0
|
||||
listen_port = 4242
|
||||
|
||||
# Alternatively you can bind to a specific IP
|
||||
|
||||
# listen_ip = 10.0.0.88
|
||||
# listen_port = 4242
|
||||
|
||||
# Or a specific network device
|
||||
|
||||
# device = eth0
|
||||
# port = 4242
|
||||
|
||||
|
||||
.. _interfaces-tcpc:
|
||||
|
||||
TCP Client Interface
|
||||
====================
|
||||
|
||||
To connect to a TCP server interface, you would naturally use the TCP client
|
||||
interface. Many TCP Client interfaces from different peers can connect to the
|
||||
same TCP Server interface at the same time.
|
||||
|
||||
.. code::
|
||||
|
||||
# Here's an example of a TCP Client interface. The
|
||||
# target_host can either be an IP address or a hostname.
|
||||
|
||||
[[TCP Client Interface]]
|
||||
type = TCPClientInterface
|
||||
interface_enabled = True
|
||||
outgoing = True
|
||||
target_host = 127.0.0.1
|
||||
target_port = 4242
|
||||
|
||||
|
||||
.. _interfaces-rnode:
|
||||
|
||||
RNode LoRa Interface
|
||||
====================
|
||||
|
||||
To use Reticulum over LoRa, the `RNode <https://unsigned.io/rnode/>`_ interface
|
||||
can be used, and offers full control over LoRa parameters.
|
||||
|
||||
.. code::
|
||||
|
||||
# Here's an example of how to add a LoRa interface
|
||||
# using the RNode LoRa transceiver.
|
||||
|
||||
[[RNode LoRa Interface]]
|
||||
type = RNodeInterface
|
||||
|
||||
# Enable interface if you want use it!
|
||||
interface_enabled = True
|
||||
|
||||
# Allow transmit on interface. Setting
|
||||
# this to false will create a listen-
|
||||
# only interface.
|
||||
outgoing = true
|
||||
|
||||
# Serial port for the device
|
||||
port = /dev/ttyUSB0
|
||||
|
||||
# Set frequency to 867.2 MHz
|
||||
frequency = 867200000
|
||||
|
||||
# Set LoRa bandwidth to 125 KHz
|
||||
bandwidth = 125000
|
||||
|
||||
# Set TX power to 7 dBm (5 mW)
|
||||
txpower = 7
|
||||
|
||||
# Select spreading factor 8. Valid
|
||||
# range is 7 through 12, with 7
|
||||
# being the fastest and 12 having
|
||||
# the longest range.
|
||||
spreadingfactor = 8
|
||||
|
||||
# Select coding rate 5. Valid range
|
||||
# is 5 throough 8, with 5 being the
|
||||
# fastest, and 8 the longest range.
|
||||
codingrate = 5
|
||||
|
||||
# You can configure the RNode to send
|
||||
# out identification on the channel with
|
||||
# a set interval by configuring the
|
||||
# following two parameters.
|
||||
# id_callsign = MYCALL-0
|
||||
# id_interval = 600
|
||||
|
||||
# For certain homebrew RNode interfaces
|
||||
# with low amounts of RAM, using packet
|
||||
# flow control can be useful. By default
|
||||
# it is disabled.
|
||||
flow_control = False
|
||||
|
||||
.. _interfaces-serial:
|
||||
|
||||
Serial Interface
|
||||
================
|
||||
|
||||
Reticulum can be used over serial ports directly, or over any device with a
|
||||
serial port, that will transparently pass data. Useful for communicating
|
||||
directly over a wire-pair, or for using devices such as data radios and lasers.
|
||||
|
||||
.. code::
|
||||
|
||||
[[Serial Interface]]
|
||||
type = SerialInterface
|
||||
interface_enabled = True
|
||||
outgoing = True
|
||||
|
||||
# Serial port for the device
|
||||
port = /dev/ttyUSB0
|
||||
|
||||
# Set the serial baud-rate and other
|
||||
# configuration parameters.
|
||||
speed = 115200
|
||||
databits = 8
|
||||
parity = none
|
||||
stopbits = 1
|
||||
|
||||
.. _interfaces-kiss:
|
||||
|
||||
KISS Interface
|
||||
==============
|
||||
|
||||
With the KISS interface, you can use Reticulum over a variety of packet
|
||||
radio modems and TNCs, including `OpenModem <https://unsigned.io/openmodem/>`_.
|
||||
KISS interfaces can also be configured to periodically send out beacons
|
||||
for station identification purposes.
|
||||
|
||||
.. code::
|
||||
|
||||
[[Packet Radio KISS Interface]]
|
||||
type = KISSInterface
|
||||
interface_enabled = True
|
||||
outgoing = true
|
||||
|
||||
# Serial port for the device
|
||||
port = /dev/ttyUSB1
|
||||
|
||||
# Set the serial baud-rate and other
|
||||
# configuration parameters.
|
||||
speed = 115200
|
||||
databits = 8
|
||||
parity = none
|
||||
stopbits = 1
|
||||
|
||||
# Set the modem preamble.
|
||||
preamble = 150
|
||||
|
||||
# Set the modem TX tail.
|
||||
txtail = 10
|
||||
|
||||
# Configure CDMA parameters. These
|
||||
# settings are reasonable defaults.
|
||||
persistence = 200
|
||||
slottime = 20
|
||||
|
||||
# You can configure the interface to send
|
||||
# out identification on the channel with
|
||||
# a set interval by configuring the
|
||||
# following two parameters. The KISS
|
||||
# interface will only ID if the set
|
||||
# interval has elapsed since it's last
|
||||
# actual transmission. The interval is
|
||||
# configured in seconds.
|
||||
# This option is commented out and not
|
||||
# used by default.
|
||||
# id_callsign = MYCALL-0
|
||||
# id_interval = 600
|
||||
|
||||
# Whether to use KISS flow-control.
|
||||
# This is useful for modems that have
|
||||
# a small internal packet buffer, but
|
||||
# support packet flow control instead.
|
||||
flow_control = false
|
||||
|
||||
.. _interfaces-ax25:
|
||||
|
||||
AX.25 KISS Interface
|
||||
====================
|
||||
|
||||
If you're using Reticulum on amateur radio spectrum, you might want to
|
||||
use the AX.25 KISS interface. This way, Reticulum will automatically
|
||||
encapsulate it's traffic in AX.25 and also identify your stations
|
||||
transmissions with your callsign and SSID.
|
||||
|
||||
Only do this if you really need to! Reticulum doesn't need the AX.25
|
||||
layer for anything, and it incurs extra overhead on every packet to
|
||||
encapsulate in AX.25.
|
||||
|
||||
A more efficient way is to use the plain KISS interface with the
|
||||
beaconing functionality described above.
|
||||
|
||||
.. code::
|
||||
|
||||
[[Packet Radio AX.25 KISS Interface]]
|
||||
type = AX25KISSInterface
|
||||
|
||||
# Set the station callsign and SSID
|
||||
callsign = NO1CLL
|
||||
ssid = 0
|
||||
|
||||
# Enable interface if you want use it!
|
||||
interface_enabled = True
|
||||
|
||||
# Allow transmit on interface.
|
||||
outgoing = True
|
||||
|
||||
# Serial port for the device
|
||||
port = /dev/ttyUSB2
|
||||
|
||||
# Set the serial baud-rate and other
|
||||
# configuration parameters.
|
||||
speed = 115200
|
||||
databits = 8
|
||||
parity = none
|
||||
stopbits = 1
|
||||
|
||||
# Set the modem preamble. A 150ms
|
||||
# preamble should be a reasonable
|
||||
# default, but may need to be
|
||||
# increased for radios with slow-
|
||||
# opening squelch and long TX/RX
|
||||
# turnaround
|
||||
preamble = 150
|
||||
|
||||
# Set the modem TX tail. In most
|
||||
# cases this should be kept as low
|
||||
# as possible to not waste airtime.
|
||||
txtail = 10
|
||||
|
||||
# Configure CDMA parameters. These
|
||||
# settings are reasonable defaults.
|
||||
persistence = 200
|
||||
slottime = 20
|
||||
|
||||
# Whether to use KISS flow-control.
|
||||
# This is useful for modems with a
|
||||
# small internal packet buffer.
|
||||
flow_control = false
|
||||
@@ -0,0 +1,150 @@
|
||||
.. _networks-main:
|
||||
|
||||
*****************
|
||||
Building Networks
|
||||
*****************
|
||||
|
||||
This chapter will provide you with the knowledge needed to build networks with
|
||||
Reticulum, which can often be easier than using traditional stacks, since you
|
||||
don't have to worry about coordinating addresses, subnets and routing for an
|
||||
entire network that you might not know how will evolve in the future. With
|
||||
Reticulum, you can simply add more segments to your network when it becomes
|
||||
necesarry, and Reticulum will handle the convergence of the entire network
|
||||
automatically.
|
||||
|
||||
Concepts & Overview
|
||||
--------------------
|
||||
|
||||
There are important points that need to be kept in mind when building networks
|
||||
with Reticulum:
|
||||
|
||||
* | In a Reticulum network, any node can autonomously generate as many adresses
|
||||
(called *destinations* in Reticulum terminology) as it needs, which become
|
||||
globally reachable to the rest of the network. There is no central point of
|
||||
control over the adress space.
|
||||
|
||||
* | Reticulum was designed to handle both very small, and very large networks.
|
||||
While the adress space can support billions of endpoints, Reticulum is
|
||||
also very useful when just a few devices needs to communicate.
|
||||
|
||||
* | Reticulum provides sender/initiator anonymity by default. There is no way
|
||||
to filter traffic or discriminate it based on the source of the traffic.
|
||||
|
||||
* | All traffic is encrypted using ephemeral keys generated by an Elliptic Curve
|
||||
Diffie-Hellman key exchange on Curve25519. There is no way to inspect traffic
|
||||
contents, and no way to prioritise or throttle certain kinds of traffic.
|
||||
All transport and routing layers are thus completely agnostic to traffic type,
|
||||
and will pass all traffic equally.
|
||||
|
||||
* | Reticulum can function both with and without infrastructure. When *transport
|
||||
nodes* are available, they can route traffic over multiple hops for other
|
||||
nodes, and will function as a distributed cryptographic keystore. When there
|
||||
is no transport nodes available, all nodes that are within communication range
|
||||
can still communicate.
|
||||
|
||||
* | Every node can become a transport node, simply by enabling it in it's
|
||||
configuration, but there is no need for every node on the network to be a
|
||||
transport node. Letting every node be a transport node will in most cases
|
||||
degrade the performance and reliability of the network.
|
||||
|
||||
In general terms, if a node is stationary, well-connected and kept running
|
||||
most of the time, it is a good candidate to be a transport node. For optimal
|
||||
performance, a network should contain the amount of transport nodes that
|
||||
provides connectivity to the intended area / topography, and not many more
|
||||
than that.
|
||||
|
||||
|
||||
Reticulum allows you to mix very different kinds of networking mediums into a
|
||||
unified mesh, or to keep everything within one medium. You could build a "virtual
|
||||
network" running entirely over the Internet, where all nodes communicate over TCP
|
||||
and UDP "channels". You could also build such a network using MQTT or ZeroMQ as
|
||||
the underlying carrier for Reticulum.
|
||||
|
||||
However, most real-world networks will probably involve either some form of
|
||||
wireless or direct hardline communications. To allow Reticulum to communicate
|
||||
over any type of medium, you must specify it in the configuration file, by default
|
||||
located at ``~/.reticulum/config``. See the :ref:`Supported Interfaces<interfaces-main>`
|
||||
chapter of this manual for interface configuration examples.
|
||||
|
||||
Any number of interfaces can be configured, and Reticulum will automatically
|
||||
decide which are suitable to use in any given situation, depending on where
|
||||
traffic needs to flow.
|
||||
|
||||
Example Scenarios
|
||||
-----------------
|
||||
|
||||
This section illustrates a few example scenarios, and how they would, in general
|
||||
terms, be planned, implemented and configured.
|
||||
|
||||
Interconnected LoRa Sites
|
||||
=========================
|
||||
|
||||
An organisation wants to provide communication and information services to it's
|
||||
members, which are located mainly in three separate areas. Three suitable hill-top
|
||||
locations are found, where the organisation can install equipment: Site A, B and C.
|
||||
|
||||
Since the amount of data that needs to be exchanged between users is mainly text-
|
||||
based, the bandwidth requirements are low, and LoRa radios are chosen to connect
|
||||
users to the network.
|
||||
|
||||
Due to the hill-top locations found, there is radio line-of-sight between site A
|
||||
and B, and also between site B and C. Because of this, the organisation does not
|
||||
need to use the Internet to interconnect the sites, but purchases four Point-to-Point
|
||||
WiFi based radios for interconnecting the sites.
|
||||
|
||||
At each site, a Raspberry Pi is installed to function as a gateway. A LoRa radio
|
||||
is connected to the Pi with a USB cable, and the WiFi radio is connected to the
|
||||
ethernet port of the Pi. At site B, two WiFi radios are needed to be able to reach
|
||||
both site A and site C, so an extra ethernet adapter is connected to the Pi in
|
||||
this location.
|
||||
|
||||
Once the hardware has been installed, Reticulum is installed on all the Pis, and at
|
||||
site A and C, one interface is added for the LoRa radio, as well as one for the WiFi
|
||||
radio. At site B, an interface for the LoRa radio, and one interface for each WiFi
|
||||
radio is added to the Reticulum configuration file. The transport node option is
|
||||
enabled in the configuration of all three gateways.
|
||||
|
||||
The network is now operational, and ready to serve users across all three areas.
|
||||
The organisation prepares a LoRa radio that is supplied to the end users, along
|
||||
with a Reticulum configuration file, that contains the right parameters for
|
||||
communicating with the LoRa radios installed at the gateway sites.
|
||||
|
||||
Once users connect to the network, anyone will be able to communicate with anyone
|
||||
else across all three sites.
|
||||
|
||||
Bridging Over the Internet
|
||||
==========================
|
||||
|
||||
As the organisation grows, several new communities form in places too far away
|
||||
from the core network to be reachable over WiFi links. New gateways similar to those
|
||||
previously installed are set up for the new communities at the new sites D and E, but
|
||||
they are islanded from the core network, and only serve the local users.
|
||||
|
||||
After investigating the options, it is found that it is possible to install an
|
||||
Internet connection at site A, and an interface on the Internet connection is
|
||||
configured for Reticulum on the Raspberry Pi at site A.
|
||||
|
||||
A member of the organisation at site D, named Dori, is willing to help by sharing
|
||||
the Internet connection she already has in her home, and is able to leave a Raspberry
|
||||
Pi running. A new Reticulum interface is configured on her Pi, connecting to the newly
|
||||
enabled Internet interface on the gateway at site A. Dori is now connected to both
|
||||
all the nodes at her own local site (through the hill-top LoRa gateway), and all the
|
||||
combined users of sites A, B and C. She then enables transport on her node, and
|
||||
traffic from site D can now reach everyone at site A, B and C, and vice versa.
|
||||
|
||||
Growth and Convergence
|
||||
======================
|
||||
|
||||
As the organisation grows, more gateways are added to keep up with the growing user
|
||||
base. Some local gateways even add VHF radios and packet modems to reach outlying users
|
||||
and communities that are out of reach for the LoRa radios and WiFi backhauls.
|
||||
|
||||
As more sites, gateways and users are connected, the amount of coordination required
|
||||
is kept to a minimum. If one community wants to add connectivity to the next one
|
||||
over, it can simply be done without having to involve everyone or coordinate address
|
||||
space or routing tables.
|
||||
|
||||
With the added geographical coverage, the operators at site A one day find that
|
||||
the original internet bridged interfaces are no longer utilised. The network has
|
||||
converged to be completely self-connected, and the sites that were once poorly
|
||||
connected outliers are now an integral part of the network.
|
||||
@@ -39,7 +39,7 @@ Destination
|
||||
Packet
|
||||
------
|
||||
|
||||
.. autoclass:: RNS.Packet
|
||||
.. autoclass:: RNS.Packet(destination, data, create_receipt = True)
|
||||
:members:
|
||||
|
||||
.. _api-packetreceipt:
|
||||
@@ -47,7 +47,7 @@ Packet
|
||||
Packet Receipt
|
||||
--------------
|
||||
|
||||
.. autoclass:: RNS.PacketReceipt
|
||||
.. autoclass:: RNS.PacketReceipt()
|
||||
:members:
|
||||
|
||||
.. _api-link:
|
||||
@@ -55,7 +55,15 @@ Packet Receipt
|
||||
Link
|
||||
----
|
||||
|
||||
.. autoclass:: RNS.Link
|
||||
.. autoclass:: RNS.Link(destination, established_callback=None, closed_callback = None)
|
||||
:members:
|
||||
|
||||
.. _api-requestreceipt:
|
||||
|
||||
Request Receipt
|
||||
---------------
|
||||
|
||||
.. autoclass:: RNS.RequestReceipt()
|
||||
:members:
|
||||
|
||||
.. _api-resource:
|
||||
@@ -63,7 +71,7 @@ Link
|
||||
Resource
|
||||
--------
|
||||
|
||||
.. autoclass:: RNS.Resource
|
||||
.. autoclass:: RNS.Resource(data, link, advertise=True, auto_compress=True, callback=None, progress_callback=None, timeout=None)
|
||||
:members:
|
||||
|
||||
.. _api-transport:
|
||||
|
||||
@@ -52,7 +52,7 @@ by using multiple hops).
|
||||
Goals
|
||||
=====
|
||||
|
||||
To be as widely usable and easy to implement as possible, the following goals have been used to
|
||||
To be as widely usable and easy to use as possible, the following goals have been used to
|
||||
guide the design of Reticulum:
|
||||
|
||||
|
||||
@@ -429,7 +429,7 @@ terms of bandwidth, so it can be used just for a short exchange, and then recrea
|
||||
also rotate encryption keys. The link can also be kept alive for longer periods of time, if this is
|
||||
more suitable to the application. The procedure also inserts the *link id* , a hash calculated from the link request packet, into the memory of forwarding nodes, which means that the communicating nodes can thereafter reach each other simply by referring to this *link id*.
|
||||
|
||||
The combined bandwidth cost of setting up a link is 3 packets totalling 240 bytes (more info in the
|
||||
The combined bandwidth cost of setting up a link is 3 packets totalling 237 bytes (more info in the
|
||||
:ref:`Binary Packet Format<understanding-packetformat>` section). The amount of bandwidth used on keeping
|
||||
a link open is practically negligible, at 0.62 bits per second. Even on a slow 1200 bits per second packet
|
||||
radio channel, 100 concurrent links will still leave 95% channel capacity for actual data.
|
||||
@@ -701,5 +701,5 @@ Binary Packet Format
|
||||
- Announce : 151 bytes
|
||||
- Link Request : 77 bytes
|
||||
- Link Proof : 77 bytes
|
||||
- Link RTT packet : 86 bytes
|
||||
- Link RTT packet : 83 bytes
|
||||
- Link keepalive : 14 bytes
|
||||
@@ -0,0 +1,165 @@
|
||||
.. _using-main:
|
||||
|
||||
******************************
|
||||
Using Reticulum on Your System
|
||||
******************************
|
||||
|
||||
Reticulum is not installed as a driver or kernel module, as one might expect
|
||||
of a networking stack. Instead, Reticulum is distributed as a Python module.
|
||||
This means that no special privileges are required to install or use it.
|
||||
Any program or application that uses Reticulum will automatically load and
|
||||
initialise Reticulum when it starts.
|
||||
|
||||
In many cases, this approach is sufficient. When any program needs to use
|
||||
Reticulum, it is loaded, initialised, interfaces are brought up, and the
|
||||
program can now communicate over Reticulum. If another program starts up
|
||||
and also wants access to the same Reticulum network, the instance is simply
|
||||
shared. This works for any number of programs running concurrently, and is
|
||||
very easy to use, but depending on your use case, there are other options.
|
||||
|
||||
Included Utility Programs
|
||||
-------------------------
|
||||
|
||||
If you often use Reticulum from several different programs, or simply want
|
||||
Reticulum to stay available all the time, for example if you are hosting
|
||||
a transport node, you might want to run Reticulum as a separate service that
|
||||
other programs, applications and services can utilise.
|
||||
|
||||
The rnsd Utility
|
||||
================
|
||||
|
||||
To do so is very easy. Simply run the included ``rnsd`` command. When ``rnsd``
|
||||
is running, it will keep all configured interfaces open, handle transport if
|
||||
it is enabled, and allow any other programs to immediately utilise the
|
||||
Reticulum network it is configured for.
|
||||
|
||||
You can even run multiple instances of rnsd with different configurations on
|
||||
the same system.
|
||||
|
||||
.. code:: text
|
||||
|
||||
# Install Reticulum
|
||||
pip3 install rns
|
||||
|
||||
# Run rnsd
|
||||
rnsd
|
||||
|
||||
.. code:: text
|
||||
|
||||
usage: rnsd [-h] [--config CONFIG] [-v] [-q] [--version]
|
||||
|
||||
Reticulum Network Stack Daemon
|
||||
|
||||
optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
--config CONFIG path to alternative Reticulum config directory
|
||||
-v, --verbose
|
||||
-q, --quiet
|
||||
--version show program's version number and exit
|
||||
|
||||
|
||||
The rnstatus Utility
|
||||
====================
|
||||
|
||||
Using the ``rnstatus`` utility, you can view the status of configured Reticulum
|
||||
interfaces, similar to the ``ifconfig`` program.
|
||||
|
||||
.. code:: text
|
||||
|
||||
# Run rnstatus
|
||||
rnstatus
|
||||
|
||||
# Example output
|
||||
Shared Instance[37428]
|
||||
Status: Up
|
||||
Connected applications: 1
|
||||
RX: 1.13 KB
|
||||
TX: 1.07 KB
|
||||
|
||||
UDPInterface[Default UDP Interface/0.0.0.0:4242]
|
||||
Status: Up
|
||||
RX: 1.01 KB
|
||||
TX: 1.01 KB
|
||||
|
||||
TCPInterface[RNS Testnet Frankfurt/frankfurt.rns.unsigned.io:4965]
|
||||
Status: Up
|
||||
RX: 1.37 KB
|
||||
TX: 9.02 KB
|
||||
|
||||
.. code:: text
|
||||
|
||||
usage: rnsd [-h] [--config CONFIG] [-v] [-q] [--version]
|
||||
|
||||
Reticulum Network Stack Daemon
|
||||
|
||||
optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
--config CONFIG path to alternative Reticulum config directory
|
||||
-v, --verbose
|
||||
-q, --quiet
|
||||
--version show program's version number and exit
|
||||
|
||||
|
||||
The rnpath Utility
|
||||
====================
|
||||
|
||||
With the ``rnpath`` utility, you can look up and view paths for
|
||||
destinations on the Reticulum network.
|
||||
|
||||
.. code:: text
|
||||
|
||||
# Run rnpath
|
||||
rnpath eca6f4e4dc26ae329e61
|
||||
|
||||
# Example output
|
||||
Path found, destination <eca6f4e4dc26ae329e61> is 4 hops away via <56b115c30cd386cad69c> on TCPInterface[Testnet/frankfurt.rns.unsigned.io:4965]
|
||||
|
||||
.. code:: text
|
||||
|
||||
usage: rnpath.py [-h] [--config CONFIG] [--version] [-v] [destination]
|
||||
|
||||
Reticulum Path Discovery Utility
|
||||
|
||||
positional arguments:
|
||||
destination hexadecimal hash of the destination
|
||||
|
||||
optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
--config CONFIG path to alternative Reticulum config directory
|
||||
--version show program's version number and exit
|
||||
-v, --verbose
|
||||
|
||||
|
||||
The rnprobe Utility
|
||||
====================
|
||||
|
||||
The ``rnprobe`` utility lets you probe a destination for connectivity, similar
|
||||
to the ``ping`` program. Please note that probes will only be answered if the
|
||||
specified destination is configured to send proofs for received packets. Many
|
||||
destinations will not have this option enabled, and will not be probable.
|
||||
|
||||
.. code:: text
|
||||
|
||||
# Run rnprobe
|
||||
python3 -m RNS.Utilities.rnprobe example_utilities.echo.request 9382f334de63217a4278
|
||||
|
||||
# Example output
|
||||
Sent 16 byte probe to <9382f334de63217a4278>
|
||||
Valid reply received from <9382f334de63217a4278>
|
||||
Round-trip time is 38.469 milliseconds over 2 hops
|
||||
|
||||
.. code:: text
|
||||
|
||||
usage: rnprobe.py [-h] [--config CONFIG] [--version] [-v] [full_name] [destination_hash]
|
||||
|
||||
Reticulum Probe Utility
|
||||
|
||||
positional arguments:
|
||||
full_name full destination name in dotted notation
|
||||
destination_hash hexadecimal hash of the destination
|
||||
|
||||
optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
--config CONFIG path to alternative Reticulum config directory
|
||||
--version show program's version number and exit
|
||||
-v, --verbose
|
||||
@@ -6,9 +6,9 @@ Reticulum is a cryptography-based networking stack for wide-area networks built
|
||||
|
||||
Reticulum allows you to build very wide-area networks with off-the-shelf tools, and offers end-to-end encryption, autoconfiguring cryptographically backed multi-hop transport, efficient addressing, unforgeable packet acknowledgements and more.
|
||||
|
||||
Reticulum is a complete networking stack, and does not use IP or higher layers, although it is easy to utilise IP (with TCP or UDP) as the underlying carrier for Reticulum. It is therefore trivial to tunnel Reticulum over the Internet or private IP networks. Reticulum is built directly on cryptographic principles, allowing resilience and stable functionality in open and trustless networks.
|
||||
Reticulum is a complete networking stack, and does not need IP or higher layers, although it is easy to utilise IP (with TCP or UDP) as the underlying carrier for Reticulum. It is therefore trivial to tunnel Reticulum over the Internet or private IP networks. Reticulum is built directly on cryptographic principles, allowing resilience and stable functionality in open and trustless networks.
|
||||
|
||||
No kernel modules or drivers are required. Reticulum runs completely in userland, and can run on practically any system that runs Python 3.
|
||||
No kernel modules or drivers are required. Reticulum runs completely in userland, and can run on practically any system that runs Python 3. Reticulum runs well even on small single-board computers like the Pi Zero.
|
||||
|
||||
|
||||
Current Status
|
||||
@@ -18,7 +18,7 @@ Reticulum should currently be considered beta software. All core protocol featur
|
||||
|
||||
Caveat Emptor
|
||||
==============
|
||||
Reticulum is an experimental networking stack, and should be considered as such. While it has been built with cryptography best-practices very foremost in mind, it has not been externally security audited, and there could very well be privacy-breaking bugs. To be considered even remotely secure, Reticulum needs a very thourough security review by independt cryptographers and security researchers. If you want to help out, or help sponsor an audit, please do get in touch.
|
||||
Reticulum is an experimental networking stack, and should be considered as such. While it has been built with cryptography best-practices very foremost in mind, it has not been externally security audited, and there could very well be privacy-breaking bugs. To be considered secure, Reticulum needs a thourough security review by independt cryptographers and security researchers. If you want to help out, or help sponsor an audit, please do get in touch.
|
||||
|
||||
|
||||
What does Reticulum Offer?
|
||||
@@ -27,11 +27,15 @@ What does Reticulum Offer?
|
||||
|
||||
* Fully self-configuring multi-hop routing
|
||||
|
||||
* Complete initiator anonymity, communicate without revealing your identity
|
||||
|
||||
* Asymmetric X25519 encryption and Ed25519 signatures as a basis for all communication
|
||||
|
||||
* Forward Secrecy with ephemereal Elliptic Curve Diffie-Hellman keys on Curve25519
|
||||
|
||||
* Reticulum uses the `Fernet <https://github.com/fernet/spec/blob/master/Spec.md>`_ specification for encryption
|
||||
* Reticulum uses the `Fernet <https://github.com/fernet/spec/blob/master/Spec.md>`_ specification for on-the-wire / over-the-air encryption
|
||||
|
||||
* All keys are ephemeral and derived from an ECDH key exchange on Curve25519
|
||||
|
||||
* AES-128 in CBC mode with PKCS7 padding
|
||||
|
||||
@@ -39,8 +43,6 @@ What does Reticulum Offer?
|
||||
|
||||
* IVs are generated through os.urandom()
|
||||
|
||||
* Keys are ephemeral and derived from an ECDH key exchange on Curve25519
|
||||
|
||||
* Unforgeable packet delivery confirmations
|
||||
|
||||
* A variety of supported interface types
|
||||
@@ -57,7 +59,7 @@ What does Reticulum Offer?
|
||||
|
||||
* Efficient link establishment
|
||||
|
||||
* Total bandwidth cost of setting up a link is only 3 packets, totalling 240 bytes
|
||||
* Total bandwidth cost of setting up a link is only 3 packets, totalling 237 bytes
|
||||
|
||||
* Low cost of keeping links open at only 0.62 bits per second
|
||||
|
||||
@@ -87,8 +89,8 @@ configured, Reticulum will take care of the rest, and any device on the WiFi
|
||||
network can communicate with nodes on the LoRa and packet radio sides of the
|
||||
network, and vice versa.
|
||||
|
||||
Supported Interface Types and Devices
|
||||
=====================================
|
||||
Interface Types and Devices
|
||||
===========================
|
||||
Reticulum implements a range of generalised interface types that covers most of the communications hardware that Reticulum can run over. If your hardware is not supported, it's relatively simple to implement an interface class. Currently, the following interfaces are supported:
|
||||
|
||||
* Any ethernet device
|
||||
@@ -101,4 +103,6 @@ Reticulum implements a range of generalised interface types that covers most of
|
||||
|
||||
* TCP over IP networks
|
||||
|
||||
* UDP over IP networks
|
||||
* UDP over IP networks
|
||||
|
||||
For a full list and more details, see the :ref:`Supported Interfaces<interfaces-main>` chapter.
|
||||
@@ -1,6 +1,6 @@
|
||||
var DOCUMENTATION_OPTIONS = {
|
||||
URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'),
|
||||
VERSION: '0.2.2 beta',
|
||||
VERSION: '0.2.7 beta',
|
||||
LANGUAGE: 'None',
|
||||
COLLAPSE_INDEX: false,
|
||||
BUILDER: 'html',
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Examples — Reticulum Network Stack 0.2.2 beta documentation</title>
|
||||
<title>Code Examples — Reticulum Network Stack 0.2.7 beta documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/classic.css" />
|
||||
|
||||
@@ -27,8 +27,8 @@
|
||||
<li class="right" >
|
||||
<a href="reference.html" title="API Reference"
|
||||
accesskey="P">previous</a> |</li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.2.2 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">Examples</a></li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.2.7 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">Code Examples</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -37,8 +37,8 @@
|
||||
<div class="bodywrapper">
|
||||
<div class="body" role="main">
|
||||
|
||||
<div class="section" id="examples">
|
||||
<span id="examples-main"></span><h1>Examples<a class="headerlink" href="#examples" title="Permalink to this headline">¶</a></h1>
|
||||
<div class="section" id="code-examples">
|
||||
<span id="examples-main"></span><h1>Code Examples<a class="headerlink" href="#code-examples" title="Permalink to this headline">¶</a></h1>
|
||||
<p>A number of examples are included in the source distribution of Reticulum.
|
||||
You can use these examples to learn how to write your own programs.</p>
|
||||
<div class="section" id="minimal">
|
||||
@@ -2017,7 +2017,7 @@ interface to efficiently pass files of any size over a Reticulum <a class="refer
|
||||
<span class="nb">print</span><span class="p">(</span><span class="s2">""</span><span class="p">)</span>
|
||||
<span class="k">while</span> <span class="n">menu_mode</span> <span class="o">==</span> <span class="s2">"downloading"</span><span class="p">:</span>
|
||||
<span class="k">global</span> <span class="n">current_download</span>
|
||||
<span class="n">percent</span> <span class="o">=</span> <span class="nb">round</span><span class="p">(</span><span class="n">current_download</span><span class="o">.</span><span class="n">progress</span><span class="p">()</span> <span class="o">*</span> <span class="mf">100.0</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
|
||||
<span class="n">percent</span> <span class="o">=</span> <span class="nb">round</span><span class="p">(</span><span class="n">current_download</span><span class="o">.</span><span class="n">get_progress</span><span class="p">()</span> <span class="o">*</span> <span class="mf">100.0</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
|
||||
<span class="nb">print</span><span class="p">((</span><span class="s2">"</span><span class="se">\r</span><span class="s2">Progress: "</span><span class="o">+</span><span class="nb">str</span><span class="p">(</span><span class="n">percent</span><span class="p">)</span><span class="o">+</span><span class="s2">" % "</span><span class="p">),</span> <span class="n">end</span><span class="o">=</span><span class="s1">' '</span><span class="p">)</span>
|
||||
<span class="n">sys</span><span class="o">.</span><span class="n">stdout</span><span class="o">.</span><span class="n">flush</span><span class="p">()</span>
|
||||
<span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mf">0.1</span><span class="p">)</span>
|
||||
@@ -2273,7 +2273,7 @@ interface to efficiently pass files of any size over a Reticulum <a class="refer
|
||||
<div class="sphinxsidebarwrapper">
|
||||
<h3><a href="index.html">Table of Contents</a></h3>
|
||||
<ul>
|
||||
<li><a class="reference internal" href="#">Examples</a><ul>
|
||||
<li><a class="reference internal" href="#">Code Examples</a><ul>
|
||||
<li><a class="reference internal" href="#minimal">Minimal</a></li>
|
||||
<li><a class="reference internal" href="#announce">Announce</a></li>
|
||||
<li><a class="reference internal" href="#broadcast">Broadcast</a></li>
|
||||
@@ -2319,8 +2319,8 @@ interface to efficiently pass files of any size over a Reticulum <a class="refer
|
||||
<li class="right" >
|
||||
<a href="reference.html" title="API Reference"
|
||||
>previous</a> |</li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.2.2 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">Examples</a></li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.2.7 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">Code Examples</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="footer" role="contentinfo">
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Index — Reticulum Network Stack 0.2.2 beta documentation</title>
|
||||
<title>Index — Reticulum Network Stack 0.2.7 beta documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/classic.css" />
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<li class="right" style="margin-right: 10px">
|
||||
<a href="#" title="General Index"
|
||||
accesskey="I">index</a></li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.2.2 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.2.7 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">Index</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -47,6 +47,7 @@
|
||||
| <a href="#I"><strong>I</strong></a>
|
||||
| <a href="#K"><strong>K</strong></a>
|
||||
| <a href="#L"><strong>L</strong></a>
|
||||
| <a href="#M"><strong>M</strong></a>
|
||||
| <a href="#N"><strong>N</strong></a>
|
||||
| <a href="#P"><strong>P</strong></a>
|
||||
| <a href="#R"><strong>R</strong></a>
|
||||
@@ -98,8 +99,6 @@
|
||||
<li><a href="reference.html#RNS.Identity.decrypt">(RNS.Identity method)</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
<li><a href="reference.html#RNS.Link.DEFAULT_TIMEOUT">DEFAULT_TIMEOUT (RNS.Link attribute)</a>
|
||||
</li>
|
||||
</ul></td>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#RNS.Transport.deregister_announce_handler">deregister_announce_handler() (RNS.Transport static method)</a>
|
||||
@@ -107,8 +106,6 @@
|
||||
<li><a href="reference.html#RNS.Destination.deregister_request_handler">deregister_request_handler() (RNS.Destination method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Destination">Destination (class in RNS)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Link.disable_encryption">disable_encryption() (RNS.Link method)</a>
|
||||
</li>
|
||||
</ul></td>
|
||||
</tr></table>
|
||||
@@ -125,6 +122,8 @@
|
||||
</ul></td>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#RNS.Packet.ENCRYPTED_MDU">ENCRYPTED_MDU (RNS.Packet attribute)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Link.ESTABLISHMENT_TIMEOUT_PER_HOP">ESTABLISHMENT_TIMEOUT_PER_HOP (RNS.Link attribute)</a>
|
||||
</li>
|
||||
</ul></td>
|
||||
</tr></table>
|
||||
@@ -152,20 +151,36 @@
|
||||
|
||||
<ul>
|
||||
<li><a href="reference.html#RNS.Identity.get_private_key">(RNS.Identity method)</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
<li><a href="reference.html#RNS.RequestReceipt.get_progress">get_progress() (RNS.RequestReceipt method)</a>
|
||||
|
||||
<ul>
|
||||
<li><a href="reference.html#RNS.Resource.get_progress">(RNS.Resource method)</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
<li><a href="reference.html#RNS.Identity.get_public_key">get_public_key() (RNS.Identity method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Identity.get_random_hash">get_random_hash() (RNS.Identity static method)</a>
|
||||
</li>
|
||||
</ul></td>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#RNS.Identity.get_random_hash">get_random_hash() (RNS.Identity static method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Link.get_remote_identity">get_remote_identity() (RNS.Link method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.RequestReceipt.get_request_id">get_request_id() (RNS.RequestReceipt method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.RequestReceipt.get_response">get_response() (RNS.RequestReceipt method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.RequestReceipt.get_response_time">get_response_time() (RNS.RequestReceipt method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.PacketReceipt.get_rtt">get_rtt() (RNS.PacketReceipt method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.PacketReceipt.get_status">get_status() (RNS.PacketReceipt method)</a>
|
||||
|
||||
<ul>
|
||||
<li><a href="reference.html#RNS.RequestReceipt.get_status">(RNS.RequestReceipt method)</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
</ul></td>
|
||||
</tr></table>
|
||||
|
||||
@@ -174,11 +189,13 @@
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#RNS.Transport.has_path">has_path() (RNS.Transport static method)</a>
|
||||
</li>
|
||||
</ul></td>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#RNS.Destination.hash">hash() (RNS.Destination static method)</a>
|
||||
</li>
|
||||
</ul></td>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#RNS.Destination.hash_from_name_and_identity">hash_from_name_and_identity() (RNS.Destination static method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Transport.hops_to">hops_to() (RNS.Transport static method)</a>
|
||||
</li>
|
||||
</ul></td>
|
||||
</tr></table>
|
||||
@@ -227,13 +244,25 @@
|
||||
</ul></td>
|
||||
</tr></table>
|
||||
|
||||
<h2 id="M">M</h2>
|
||||
<table style="width: 100%" class="indextable genindextable"><tr>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#RNS.Reticulum.MTU">MTU (RNS.Reticulum attribute)</a>
|
||||
</li>
|
||||
</ul></td>
|
||||
</tr></table>
|
||||
|
||||
<h2 id="N">N</h2>
|
||||
<table style="width: 100%" class="indextable genindextable"><tr>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#RNS.Link.no_inbound_for">no_inbound_for() (RNS.Link method)</a>
|
||||
<li><a href="reference.html#RNS.Transport.next_hop">next_hop() (RNS.Transport static method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Transport.next_hop_interface">next_hop_interface() (RNS.Transport static method)</a>
|
||||
</li>
|
||||
</ul></td>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#RNS.Link.no_inbound_for">no_inbound_for() (RNS.Link method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Link.no_outbound_for">no_outbound_for() (RNS.Link method)</a>
|
||||
</li>
|
||||
</ul></td>
|
||||
@@ -248,9 +277,9 @@
|
||||
</li>
|
||||
</ul></td>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#RNS.Packet.PLAIN_MDU">PLAIN_MDU (RNS.Packet attribute)</a>
|
||||
<li><a href="reference.html#RNS.Transport.PATHFINDER_M">PATHFINDER_M (RNS.Transport attribute)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Resource.progress">progress() (RNS.Resource method)</a>
|
||||
<li><a href="reference.html#RNS.Packet.PLAIN_MDU">PLAIN_MDU (RNS.Packet attribute)</a>
|
||||
</li>
|
||||
</ul></td>
|
||||
</tr></table>
|
||||
@@ -266,11 +295,13 @@
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Destination.register_request_handler">register_request_handler() (RNS.Destination method)</a>
|
||||
</li>
|
||||
</ul></td>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#RNS.Link.request">request() (RNS.Link method)</a>
|
||||
</li>
|
||||
</ul></td>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#RNS.Transport.request_path">request_path() (RNS.Transport static method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.RequestReceipt">RequestReceipt (class in RNS)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Packet.resend">resend() (RNS.Packet method)</a>
|
||||
</li>
|
||||
@@ -317,8 +348,6 @@
|
||||
<li><a href="reference.html#RNS.PacketReceipt.set_timeout">set_timeout() (RNS.PacketReceipt method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.PacketReceipt.set_timeout_callback">set_timeout_callback() (RNS.PacketReceipt method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Reticulum.should_allow_unencrypted">should_allow_unencrypted() (RNS.Reticulum static method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Reticulum.should_use_implicit_proof">should_use_implicit_proof() (RNS.Reticulum static method)</a>
|
||||
</li>
|
||||
@@ -387,7 +416,7 @@
|
||||
<li class="right" style="margin-right: 10px">
|
||||
<a href="#" title="General Index"
|
||||
>index</a></li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.2.2 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.2.7 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">Index</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Getting Started Fast — Reticulum Network Stack 0.2.2 beta documentation</title>
|
||||
<title>Getting Started Fast — Reticulum Network Stack 0.2.7 beta documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/classic.css" />
|
||||
|
||||
@@ -16,7 +16,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="next" title="Using Reticulum on Your System" href="using.html" />
|
||||
<link rel="prev" title="What is Reticulum?" href="whatis.html" />
|
||||
</head><body>
|
||||
<div class="related" role="navigation" aria-label="related navigation">
|
||||
@@ -26,12 +26,12 @@
|
||||
<a href="genindex.html" title="General Index"
|
||||
accesskey="I">index</a></li>
|
||||
<li class="right" >
|
||||
<a href="understanding.html" title="Understanding Reticulum"
|
||||
<a href="using.html" title="Using Reticulum on Your System"
|
||||
accesskey="N">next</a> |</li>
|
||||
<li class="right" >
|
||||
<a href="whatis.html" title="What is Reticulum?"
|
||||
accesskey="P">previous</a> |</li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.2.2 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.2.7 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">Getting Started Fast</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -50,11 +50,46 @@ scenarios.</p>
|
||||
<h2>Try Using a Reticulum-based Program<a class="headerlink" href="#try-using-a-reticulum-based-program" title="Permalink to this headline">¶</a></h2>
|
||||
<p>If you simply want to try using a program built with Reticulum, you can take
|
||||
a look at <a class="reference external" href="https://github.com/markqvist/nomadnet">Nomad Network</a>, which
|
||||
provides a basic encrypted communications suite built completely on Reticulum.</p>
|
||||
<a class="reference external image-reference" href="_images/nomadnet3.png"><img alt="_images/nomadnet3.png" src="_images/nomadnet3.png" /></a>
|
||||
provides a complete encrypted communications suite built with Reticulum.</p>
|
||||
<a class="reference external image-reference" href="_images/nomadnet_3.png"><img alt="_images/nomadnet_3.png" src="_images/nomadnet_3.png" /></a>
|
||||
<p><a class="reference external" href="https://github.com/markqvist/nomadnet">Nomad Network</a> is a user-facing client
|
||||
in the development for the messaging and information-sharing protocol
|
||||
<a class="reference external" href="https://github.com/markqvist/lxmf">LXMF</a>, another project built with Reticulum.</p>
|
||||
<p>You can install Nomad Network via pip:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># Install ...</span>
|
||||
<span class="n">pip3</span> <span class="n">install</span> <span class="n">nomadnet</span>
|
||||
|
||||
<span class="c1"># ... and run</span>
|
||||
<span class="n">nomadnet</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="section" id="using-the-included-utilities">
|
||||
<h2>Using the Included Utilities<a class="headerlink" href="#using-the-included-utilities" title="Permalink to this headline">¶</a></h2>
|
||||
<p>Reticulum comes with a range of included utilities that make it easier to
|
||||
manage your network, check connectivity and make Reticulum available to other
|
||||
programs on your system.</p>
|
||||
<p>You can use <code class="docutils literal notranslate"><span class="pre">rnsd</span></code> to run Reticulum as a background or foreground service,
|
||||
and the <code class="docutils literal notranslate"><span class="pre">rnstatus</span></code>, <code class="docutils literal notranslate"><span class="pre">rnpath</span></code> and <code class="docutils literal notranslate"><span class="pre">rnprobe</span></code> utilities to view and query
|
||||
network status and connectivity.</p>
|
||||
<p>To learn more about these utility programs, have a look at the
|
||||
<a class="reference internal" href="using.html#using-main"><span class="std std-ref">Using Reticulum on Your System</span></a> chapter of this manual.</p>
|
||||
</div>
|
||||
<div class="section" id="creating-a-network-with-reticulum">
|
||||
<h2>Creating a Network With Reticulum<a class="headerlink" href="#creating-a-network-with-reticulum" title="Permalink to this headline">¶</a></h2>
|
||||
<p>To create a network, you will need to specify one or more <em>interfaces</em> for
|
||||
Reticulum to use. This is done in the Reticulum configuration file, which by
|
||||
default is located at <code class="docutils literal notranslate"><span class="pre">~/.reticulum/config</span></code>.</p>
|
||||
<p>When Reticulum is started for the first time, it will create a default
|
||||
configuration file, with one active interface. This default interface uses
|
||||
your existing ethernet network (if there is one), and only allows you to
|
||||
communicate with other Reticulum peers within your local broadcast domain.</p>
|
||||
<p>To communicate further, you will have to add one or more interfaces. The default
|
||||
configuration includes a number of examples, ranging from using TCP over the
|
||||
internet, to LoRa and Packet Radio interfaces.</p>
|
||||
<p>Possibly, the examples in the config file are enough to get you started. If
|
||||
you want more information, you can read the <a class="reference internal" href="networks.html#networks-main"><span class="std std-ref">Building Networks</span></a>
|
||||
and <a class="reference internal" href="interfaces.html#interfaces-main"><span class="std std-ref">Interfaces</span></a> chapters of this manual.</p>
|
||||
</div>
|
||||
<div class="section" id="develop-a-program-with-reticulum">
|
||||
<h2>Develop a Program with Reticulum<a class="headerlink" href="#develop-a-program-with-reticulum" title="Permalink to this headline">¶</a></h2>
|
||||
@@ -74,7 +109,7 @@ likely be to look at some <a class="reference internal" href="examples.html#exam
|
||||
utilities, you’ll want to get the latest source from GitHub. In that case,
|
||||
don’t use pip, but try this recipe:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># Install dependencies</span>
|
||||
<span class="n">pip3</span> <span class="n">install</span> <span class="n">cryptography</span> <span class="n">pyserial</span>
|
||||
<span class="n">pip3</span> <span class="n">install</span> <span class="n">cryptography</span> <span class="n">pyserial</span> <span class="n">netifaces</span>
|
||||
|
||||
<span class="c1"># Clone repository</span>
|
||||
<span class="n">git</span> <span class="n">clone</span> <span class="n">https</span><span class="p">:</span><span class="o">//</span><span class="n">github</span><span class="o">.</span><span class="n">com</span><span class="o">/</span><span class="n">markqvist</span><span class="o">/</span><span class="n">Reticulum</span><span class="o">.</span><span class="n">git</span>
|
||||
@@ -121,6 +156,8 @@ don’t use pip, but try this recipe:</p>
|
||||
<ul>
|
||||
<li><a class="reference internal" href="#">Getting Started Fast</a><ul>
|
||||
<li><a class="reference internal" href="#try-using-a-reticulum-based-program">Try Using a Reticulum-based Program</a></li>
|
||||
<li><a class="reference internal" href="#using-the-included-utilities">Using the Included Utilities</a></li>
|
||||
<li><a class="reference internal" href="#creating-a-network-with-reticulum">Creating a Network With Reticulum</a></li>
|
||||
<li><a class="reference internal" href="#develop-a-program-with-reticulum">Develop a Program with Reticulum</a></li>
|
||||
<li><a class="reference internal" href="#participate-in-reticulum-development">Participate in Reticulum Development</a></li>
|
||||
</ul>
|
||||
@@ -131,8 +168,8 @@ don’t use pip, but try this recipe:</p>
|
||||
<p class="topless"><a href="whatis.html"
|
||||
title="previous chapter">What is Reticulum?</a></p>
|
||||
<h4>Next topic</h4>
|
||||
<p class="topless"><a href="understanding.html"
|
||||
title="next chapter">Understanding Reticulum</a></p>
|
||||
<p class="topless"><a href="using.html"
|
||||
title="next chapter">Using Reticulum on Your System</a></p>
|
||||
<div role="note" aria-label="source link">
|
||||
<h3>This Page</h3>
|
||||
<ul class="this-page-menu">
|
||||
@@ -161,12 +198,12 @@ don’t use pip, but try this recipe:</p>
|
||||
<a href="genindex.html" title="General Index"
|
||||
>index</a></li>
|
||||
<li class="right" >
|
||||
<a href="understanding.html" title="Understanding Reticulum"
|
||||
<a href="using.html" title="Using Reticulum on Your System"
|
||||
>next</a> |</li>
|
||||
<li class="right" >
|
||||
<a href="whatis.html" title="What is Reticulum?"
|
||||
>previous</a> |</li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.2.2 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.2.7 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">Getting Started Fast</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Reticulum Network Stack Manual — Reticulum Network Stack 0.2.2 beta documentation</title>
|
||||
<title>Reticulum Network Stack Manual — Reticulum Network Stack 0.2.7 beta documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/classic.css" />
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
<li class="right" >
|
||||
<a href="whatis.html" title="What is Reticulum?"
|
||||
accesskey="N">next</a> |</li>
|
||||
<li class="nav-item nav-item-0"><a href="#">Reticulum Network Stack 0.2.2 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-0"><a href="#">Reticulum Network Stack 0.2.7 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">Reticulum Network Stack Manual</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -40,8 +40,8 @@
|
||||
<div class="section" id="reticulum-network-stack-manual">
|
||||
<h1>Reticulum Network Stack Manual<a class="headerlink" href="#reticulum-network-stack-manual" title="Permalink to this headline">¶</a></h1>
|
||||
<p>This manual aims to provide you with all the information you need to
|
||||
understand Reticulum, develop programs using it, or to participate in
|
||||
the development of Reticulum itself.</p>
|
||||
understand Reticulum, build networks or develop programs using it, or
|
||||
to participate in the development of Reticulum itself.</p>
|
||||
<div class="toctree-wrapper compound">
|
||||
<ul>
|
||||
<li class="toctree-l1"><a class="reference internal" href="whatis.html">What is Reticulum?</a><ul>
|
||||
@@ -49,15 +49,47 @@ the development of Reticulum itself.</p>
|
||||
<li class="toctree-l2"><a class="reference internal" href="whatis.html#caveat-emptor">Caveat Emptor</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="whatis.html#what-does-reticulum-offer">What does Reticulum Offer?</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="whatis.html#where-can-reticulum-be-used">Where can Reticulum be Used?</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="whatis.html#supported-interface-types-and-devices">Supported Interface Types and Devices</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="whatis.html#interface-types-and-devices">Interface Types and Devices</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="gettingstartedfast.html">Getting Started Fast</a><ul>
|
||||
<li class="toctree-l2"><a class="reference internal" href="gettingstartedfast.html#try-using-a-reticulum-based-program">Try Using a Reticulum-based Program</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="gettingstartedfast.html#using-the-included-utilities">Using the Included Utilities</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="gettingstartedfast.html#creating-a-network-with-reticulum">Creating a Network With Reticulum</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="gettingstartedfast.html#develop-a-program-with-reticulum">Develop a Program with Reticulum</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="gettingstartedfast.html#participate-in-reticulum-development">Participate in Reticulum Development</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="using.html">Using Reticulum on Your System</a><ul>
|
||||
<li class="toctree-l2"><a class="reference internal" href="using.html#included-utility-programs">Included Utility Programs</a><ul>
|
||||
<li class="toctree-l3"><a class="reference internal" href="using.html#the-rnsd-utility">The rnsd Utility</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="using.html#the-rnstatus-utility">The rnstatus Utility</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="using.html#the-rnpath-utility">The rnpath Utility</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="using.html#the-rnprobe-utility">The rnprobe Utility</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="networks.html">Building Networks</a><ul>
|
||||
<li class="toctree-l2"><a class="reference internal" href="networks.html#concepts-overview">Concepts & Overview</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="networks.html#example-scenarios">Example Scenarios</a><ul>
|
||||
<li class="toctree-l3"><a class="reference internal" href="networks.html#interconnected-lora-sites">Interconnected LoRa Sites</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="networks.html#bridging-over-the-internet">Bridging Over the Internet</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="networks.html#growth-and-convergence">Growth and Convergence</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="interfaces.html">Supported Interfaces</a><ul>
|
||||
<li class="toctree-l2"><a class="reference internal" href="interfaces.html#udp-interface">UDP Interface</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="interfaces.html#tcp-server-interface">TCP Server Interface</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="interfaces.html#tcp-client-interface">TCP Client Interface</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="interfaces.html#rnode-lora-interface">RNode LoRa Interface</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="interfaces.html#serial-interface">Serial Interface</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="interfaces.html#kiss-interface">KISS Interface</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="interfaces.html#ax-25-kiss-interface">AX.25 KISS Interface</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="understanding.html">Understanding Reticulum</a><ul>
|
||||
<li class="toctree-l2"><a class="reference internal" href="understanding.html#motivation">Motivation</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="understanding.html#goals">Goals</a></li>
|
||||
@@ -91,13 +123,14 @@ the development of Reticulum itself.</p>
|
||||
<li class="toctree-l3"><a class="reference internal" href="reference.html#packet">Packet</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="reference.html#packet-receipt">Packet Receipt</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="reference.html#link">Link</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="reference.html#request-receipt">Request Receipt</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="reference.html#resource">Resource</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="reference.html#transport">Transport</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="examples.html">Examples</a><ul>
|
||||
<li class="toctree-l1"><a class="reference internal" href="examples.html">Code Examples</a><ul>
|
||||
<li class="toctree-l2"><a class="reference internal" href="examples.html#minimal">Minimal</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="examples.html#announce">Announce</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="examples.html#broadcast">Broadcast</a></li>
|
||||
@@ -167,7 +200,7 @@ the development of Reticulum itself.</p>
|
||||
<li class="right" >
|
||||
<a href="whatis.html" title="What is Reticulum?"
|
||||
>next</a> |</li>
|
||||
<li class="nav-item nav-item-0"><a href="#">Reticulum Network Stack 0.2.2 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-0"><a href="#">Reticulum Network Stack 0.2.7 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">Reticulum Network Stack Manual</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,421 @@
|
||||
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Supported Interfaces — Reticulum Network Stack 0.2.7 beta documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/classic.css" />
|
||||
|
||||
<script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
|
||||
<script src="_static/jquery.js"></script>
|
||||
<script src="_static/underscore.js"></script>
|
||||
<script src="_static/doctools.js"></script>
|
||||
|
||||
<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="Building Networks" href="networks.html" />
|
||||
</head><body>
|
||||
<div class="related" role="navigation" aria-label="related navigation">
|
||||
<h3>Navigation</h3>
|
||||
<ul>
|
||||
<li class="right" style="margin-right: 10px">
|
||||
<a href="genindex.html" title="General Index"
|
||||
accesskey="I">index</a></li>
|
||||
<li class="right" >
|
||||
<a href="understanding.html" title="Understanding Reticulum"
|
||||
accesskey="N">next</a> |</li>
|
||||
<li class="right" >
|
||||
<a href="networks.html" title="Building Networks"
|
||||
accesskey="P">previous</a> |</li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.2.7 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">Supported Interfaces</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="document">
|
||||
<div class="documentwrapper">
|
||||
<div class="bodywrapper">
|
||||
<div class="body" role="main">
|
||||
|
||||
<div class="section" id="supported-interfaces">
|
||||
<span id="interfaces-main"></span><h1>Supported Interfaces<a class="headerlink" href="#supported-interfaces" title="Permalink to this headline">¶</a></h1>
|
||||
<p>Reticulum supports using many kinds of devices as networking interfaces, and
|
||||
allows you to mix and match them in any way you choose. The number of distinct
|
||||
network topologies you can create with Reticulum is more or less endless, but
|
||||
common to them all is that you will need to define one or more <em>interfaces</em>
|
||||
for Reticulum to use.</p>
|
||||
<p>The following sections describe the interfaces currently available in Reticulum,
|
||||
and gives example configurations for the respective interface types.</p>
|
||||
<p>For a high-level overview of how networks can be formed over different interface
|
||||
types, have a look at the <a class="reference internal" href="networks.html#networks-main"><span class="std std-ref">Building Networks</span></a> chapter of this
|
||||
manual.</p>
|
||||
<div class="section" id="udp-interface">
|
||||
<span id="interfaces-udp"></span><h2>UDP Interface<a class="headerlink" href="#udp-interface" title="Permalink to this headline">¶</a></h2>
|
||||
<p>A UDP interface can be useful for communicating over IP networks, both
|
||||
private and the internet. It can also allow broadcast communication
|
||||
over IP networks, so it can provide an easy way to enable connectivity
|
||||
with all other peers on a local area network.</p>
|
||||
<p>The below example is enabled by default on new Reticulum installations,
|
||||
as it provides an easy way to get started and to test Reticulum on a
|
||||
pre-existing LAN.</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># This example enables communication with other</span>
|
||||
<span class="c1"># local Reticulum peers over UDP.</span>
|
||||
|
||||
<span class="p">[[</span><span class="n">Default</span> <span class="n">UDP</span> <span class="n">Interface</span><span class="p">]]</span>
|
||||
<span class="nb">type</span> <span class="o">=</span> <span class="n">UDPInterface</span>
|
||||
<span class="n">interface_enabled</span> <span class="o">=</span> <span class="kc">True</span>
|
||||
<span class="n">outgoing</span> <span class="o">=</span> <span class="kc">True</span>
|
||||
<span class="n">listen_ip</span> <span class="o">=</span> <span class="mf">0.0</span><span class="o">.</span><span class="mf">0.0</span>
|
||||
<span class="n">listen_port</span> <span class="o">=</span> <span class="mi">4242</span>
|
||||
<span class="n">forward_ip</span> <span class="o">=</span> <span class="mf">255.255</span><span class="o">.</span><span class="mf">255.255</span>
|
||||
<span class="n">forward_port</span> <span class="o">=</span> <span class="mi">4242</span>
|
||||
|
||||
<span class="c1"># The above configuration will allow communication</span>
|
||||
<span class="c1"># within the local broadcast domains of all local</span>
|
||||
<span class="c1"># IP interfaces. This is enabled by default as an</span>
|
||||
<span class="c1"># easy way to get started, but you might want to</span>
|
||||
<span class="c1"># consider altering it to something more specific.</span>
|
||||
|
||||
<span class="c1"># Instead of specifying listen_ip, listen_port,</span>
|
||||
<span class="c1"># forward_ip and forward_port, you can also bind</span>
|
||||
<span class="c1"># to a specific network device like below.</span>
|
||||
|
||||
<span class="c1"># device = eth0</span>
|
||||
<span class="c1"># port = 4242</span>
|
||||
|
||||
<span class="c1"># Assuming the eth0 device has the address</span>
|
||||
<span class="c1"># 10.55.0.72/24, the above configuration would</span>
|
||||
<span class="c1"># be equivalent to the following manual setup.</span>
|
||||
<span class="c1"># Note that we are both listening and forwarding to</span>
|
||||
<span class="c1"># the broadcast address of the network segments.</span>
|
||||
|
||||
<span class="c1"># listen_ip = 10.55.0.255</span>
|
||||
<span class="c1"># listen_port = 4242</span>
|
||||
<span class="c1"># forward_ip = 10.55.0.255</span>
|
||||
<span class="c1"># forward_port = 4242</span>
|
||||
|
||||
<span class="c1"># You can of course also communicate only with</span>
|
||||
<span class="c1"># a single IP address</span>
|
||||
|
||||
<span class="c1"># listen_ip = 10.55.0.15</span>
|
||||
<span class="c1"># listen_port = 4242</span>
|
||||
<span class="c1"># forward_ip = 10.55.0.16</span>
|
||||
<span class="c1"># forward_port = 4242</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="section" id="tcp-server-interface">
|
||||
<span id="interfaces-tcps"></span><h2>TCP Server Interface<a class="headerlink" href="#tcp-server-interface" title="Permalink to this headline">¶</a></h2>
|
||||
<p>The TCP Server interface is suitable for allowing other peers to connect over
|
||||
the Internet or private IP networks. When a TCP server interface has been
|
||||
configured, other Reticulum peers can connect to it with a TCP Client interface.</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># This example demonstrates a TCP server interface.</span>
|
||||
<span class="c1"># It will listen for incoming connections on the</span>
|
||||
<span class="c1"># specified IP address and port number.</span>
|
||||
|
||||
<span class="p">[[</span><span class="n">TCP</span> <span class="n">Server</span> <span class="n">Interface</span><span class="p">]]</span>
|
||||
<span class="nb">type</span> <span class="o">=</span> <span class="n">TCPServerInterface</span>
|
||||
<span class="n">interface_enabled</span> <span class="o">=</span> <span class="kc">True</span>
|
||||
<span class="n">outgoing</span> <span class="o">=</span> <span class="kc">True</span>
|
||||
|
||||
<span class="c1"># This configuration will listen on all IP</span>
|
||||
<span class="c1"># interfaces on port 4242</span>
|
||||
|
||||
<span class="n">listen_ip</span> <span class="o">=</span> <span class="mf">0.0</span><span class="o">.</span><span class="mf">0.0</span>
|
||||
<span class="n">listen_port</span> <span class="o">=</span> <span class="mi">4242</span>
|
||||
|
||||
<span class="c1"># Alternatively you can bind to a specific IP</span>
|
||||
|
||||
<span class="c1"># listen_ip = 10.0.0.88</span>
|
||||
<span class="c1"># listen_port = 4242</span>
|
||||
|
||||
<span class="c1"># Or a specific network device</span>
|
||||
|
||||
<span class="c1"># device = eth0</span>
|
||||
<span class="c1"># port = 4242</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="section" id="tcp-client-interface">
|
||||
<span id="interfaces-tcpc"></span><h2>TCP Client Interface<a class="headerlink" href="#tcp-client-interface" title="Permalink to this headline">¶</a></h2>
|
||||
<p>To connect to a TCP server interface, you would naturally use the TCP client
|
||||
interface. Many TCP Client interfaces from different peers can connect to the
|
||||
same TCP Server interface at the same time.</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># Here's an example of a TCP Client interface. The</span>
|
||||
<span class="c1"># target_host can either be an IP address or a hostname.</span>
|
||||
|
||||
<span class="p">[[</span><span class="n">TCP</span> <span class="n">Client</span> <span class="n">Interface</span><span class="p">]]</span>
|
||||
<span class="nb">type</span> <span class="o">=</span> <span class="n">TCPClientInterface</span>
|
||||
<span class="n">interface_enabled</span> <span class="o">=</span> <span class="kc">True</span>
|
||||
<span class="n">outgoing</span> <span class="o">=</span> <span class="kc">True</span>
|
||||
<span class="n">target_host</span> <span class="o">=</span> <span class="mf">127.0</span><span class="o">.</span><span class="mf">0.1</span>
|
||||
<span class="n">target_port</span> <span class="o">=</span> <span class="mi">4242</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="section" id="rnode-lora-interface">
|
||||
<span id="interfaces-rnode"></span><h2>RNode LoRa Interface<a class="headerlink" href="#rnode-lora-interface" title="Permalink to this headline">¶</a></h2>
|
||||
<p>To use Reticulum over LoRa, the <a class="reference external" href="https://unsigned.io/rnode/">RNode</a> interface
|
||||
can be used, and offers full control over LoRa parameters.</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># Here's an example of how to add a LoRa interface</span>
|
||||
<span class="c1"># using the RNode LoRa transceiver.</span>
|
||||
|
||||
<span class="p">[[</span><span class="n">RNode</span> <span class="n">LoRa</span> <span class="n">Interface</span><span class="p">]]</span>
|
||||
<span class="nb">type</span> <span class="o">=</span> <span class="n">RNodeInterface</span>
|
||||
|
||||
<span class="c1"># Enable interface if you want use it!</span>
|
||||
<span class="n">interface_enabled</span> <span class="o">=</span> <span class="kc">True</span>
|
||||
|
||||
<span class="c1"># Allow transmit on interface. Setting</span>
|
||||
<span class="c1"># this to false will create a listen-</span>
|
||||
<span class="c1"># only interface.</span>
|
||||
<span class="n">outgoing</span> <span class="o">=</span> <span class="n">true</span>
|
||||
|
||||
<span class="c1"># Serial port for the device</span>
|
||||
<span class="n">port</span> <span class="o">=</span> <span class="o">/</span><span class="n">dev</span><span class="o">/</span><span class="n">ttyUSB0</span>
|
||||
|
||||
<span class="c1"># Set frequency to 867.2 MHz</span>
|
||||
<span class="n">frequency</span> <span class="o">=</span> <span class="mi">867200000</span>
|
||||
|
||||
<span class="c1"># Set LoRa bandwidth to 125 KHz</span>
|
||||
<span class="n">bandwidth</span> <span class="o">=</span> <span class="mi">125000</span>
|
||||
|
||||
<span class="c1"># Set TX power to 7 dBm (5 mW)</span>
|
||||
<span class="n">txpower</span> <span class="o">=</span> <span class="mi">7</span>
|
||||
|
||||
<span class="c1"># Select spreading factor 8. Valid</span>
|
||||
<span class="c1"># range is 7 through 12, with 7</span>
|
||||
<span class="c1"># being the fastest and 12 having</span>
|
||||
<span class="c1"># the longest range.</span>
|
||||
<span class="n">spreadingfactor</span> <span class="o">=</span> <span class="mi">8</span>
|
||||
|
||||
<span class="c1"># Select coding rate 5. Valid range</span>
|
||||
<span class="c1"># is 5 throough 8, with 5 being the</span>
|
||||
<span class="c1"># fastest, and 8 the longest range.</span>
|
||||
<span class="n">codingrate</span> <span class="o">=</span> <span class="mi">5</span>
|
||||
|
||||
<span class="c1"># You can configure the RNode to send</span>
|
||||
<span class="c1"># out identification on the channel with</span>
|
||||
<span class="c1"># a set interval by configuring the</span>
|
||||
<span class="c1"># following two parameters.</span>
|
||||
<span class="c1"># id_callsign = MYCALL-0</span>
|
||||
<span class="c1"># id_interval = 600</span>
|
||||
|
||||
<span class="c1"># For certain homebrew RNode interfaces</span>
|
||||
<span class="c1"># with low amounts of RAM, using packet</span>
|
||||
<span class="c1"># flow control can be useful. By default</span>
|
||||
<span class="c1"># it is disabled.</span>
|
||||
<span class="n">flow_control</span> <span class="o">=</span> <span class="kc">False</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="section" id="serial-interface">
|
||||
<span id="interfaces-serial"></span><h2>Serial Interface<a class="headerlink" href="#serial-interface" title="Permalink to this headline">¶</a></h2>
|
||||
<p>Reticulum can be used over serial ports directly, or over any device with a
|
||||
serial port, that will transparently pass data. Useful for communicating
|
||||
directly over a wire-pair, or for using devices such as data radios and lasers.</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="p">[[</span><span class="n">Serial</span> <span class="n">Interface</span><span class="p">]]</span>
|
||||
<span class="nb">type</span> <span class="o">=</span> <span class="n">SerialInterface</span>
|
||||
<span class="n">interface_enabled</span> <span class="o">=</span> <span class="kc">True</span>
|
||||
<span class="n">outgoing</span> <span class="o">=</span> <span class="kc">True</span>
|
||||
|
||||
<span class="c1"># Serial port for the device</span>
|
||||
<span class="n">port</span> <span class="o">=</span> <span class="o">/</span><span class="n">dev</span><span class="o">/</span><span class="n">ttyUSB0</span>
|
||||
|
||||
<span class="c1"># Set the serial baud-rate and other</span>
|
||||
<span class="c1"># configuration parameters.</span>
|
||||
<span class="n">speed</span> <span class="o">=</span> <span class="mi">115200</span>
|
||||
<span class="n">databits</span> <span class="o">=</span> <span class="mi">8</span>
|
||||
<span class="n">parity</span> <span class="o">=</span> <span class="n">none</span>
|
||||
<span class="n">stopbits</span> <span class="o">=</span> <span class="mi">1</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="section" id="kiss-interface">
|
||||
<span id="interfaces-kiss"></span><h2>KISS Interface<a class="headerlink" href="#kiss-interface" title="Permalink to this headline">¶</a></h2>
|
||||
<p>With the KISS interface, you can use Reticulum over a variety of packet
|
||||
radio modems and TNCs, including <a class="reference external" href="https://unsigned.io/openmodem/">OpenModem</a>.
|
||||
KISS interfaces can also be configured to periodically send out beacons
|
||||
for station identification purposes.</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="p">[[</span><span class="n">Packet</span> <span class="n">Radio</span> <span class="n">KISS</span> <span class="n">Interface</span><span class="p">]]</span>
|
||||
<span class="nb">type</span> <span class="o">=</span> <span class="n">KISSInterface</span>
|
||||
<span class="n">interface_enabled</span> <span class="o">=</span> <span class="kc">True</span>
|
||||
<span class="n">outgoing</span> <span class="o">=</span> <span class="n">true</span>
|
||||
|
||||
<span class="c1"># Serial port for the device</span>
|
||||
<span class="n">port</span> <span class="o">=</span> <span class="o">/</span><span class="n">dev</span><span class="o">/</span><span class="n">ttyUSB1</span>
|
||||
|
||||
<span class="c1"># Set the serial baud-rate and other</span>
|
||||
<span class="c1"># configuration parameters.</span>
|
||||
<span class="n">speed</span> <span class="o">=</span> <span class="mi">115200</span>
|
||||
<span class="n">databits</span> <span class="o">=</span> <span class="mi">8</span>
|
||||
<span class="n">parity</span> <span class="o">=</span> <span class="n">none</span>
|
||||
<span class="n">stopbits</span> <span class="o">=</span> <span class="mi">1</span>
|
||||
|
||||
<span class="c1"># Set the modem preamble.</span>
|
||||
<span class="n">preamble</span> <span class="o">=</span> <span class="mi">150</span>
|
||||
|
||||
<span class="c1"># Set the modem TX tail.</span>
|
||||
<span class="n">txtail</span> <span class="o">=</span> <span class="mi">10</span>
|
||||
|
||||
<span class="c1"># Configure CDMA parameters. These</span>
|
||||
<span class="c1"># settings are reasonable defaults.</span>
|
||||
<span class="n">persistence</span> <span class="o">=</span> <span class="mi">200</span>
|
||||
<span class="n">slottime</span> <span class="o">=</span> <span class="mi">20</span>
|
||||
|
||||
<span class="c1"># You can configure the interface to send</span>
|
||||
<span class="c1"># out identification on the channel with</span>
|
||||
<span class="c1"># a set interval by configuring the</span>
|
||||
<span class="c1"># following two parameters. The KISS</span>
|
||||
<span class="c1"># interface will only ID if the set</span>
|
||||
<span class="c1"># interval has elapsed since it's last</span>
|
||||
<span class="c1"># actual transmission. The interval is</span>
|
||||
<span class="c1"># configured in seconds.</span>
|
||||
<span class="c1"># This option is commented out and not</span>
|
||||
<span class="c1"># used by default.</span>
|
||||
<span class="c1"># id_callsign = MYCALL-0</span>
|
||||
<span class="c1"># id_interval = 600</span>
|
||||
|
||||
<span class="c1"># Whether to use KISS flow-control.</span>
|
||||
<span class="c1"># This is useful for modems that have</span>
|
||||
<span class="c1"># a small internal packet buffer, but</span>
|
||||
<span class="c1"># support packet flow control instead.</span>
|
||||
<span class="n">flow_control</span> <span class="o">=</span> <span class="n">false</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="section" id="ax-25-kiss-interface">
|
||||
<span id="interfaces-ax25"></span><h2>AX.25 KISS Interface<a class="headerlink" href="#ax-25-kiss-interface" title="Permalink to this headline">¶</a></h2>
|
||||
<p>If you’re using Reticulum on amateur radio spectrum, you might want to
|
||||
use the AX.25 KISS interface. This way, Reticulum will automatically
|
||||
encapsulate it’s traffic in AX.25 and also identify your stations
|
||||
transmissions with your callsign and SSID.</p>
|
||||
<p>Only do this if you really need to! Reticulum doesn’t need the AX.25
|
||||
layer for anything, and it incurs extra overhead on every packet to
|
||||
encapsulate in AX.25.</p>
|
||||
<p>A more efficient way is to use the plain KISS interface with the
|
||||
beaconing functionality described above.</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="p">[[</span><span class="n">Packet</span> <span class="n">Radio</span> <span class="n">AX</span><span class="o">.</span><span class="mi">25</span> <span class="n">KISS</span> <span class="n">Interface</span><span class="p">]]</span>
|
||||
<span class="nb">type</span> <span class="o">=</span> <span class="n">AX25KISSInterface</span>
|
||||
|
||||
<span class="c1"># Set the station callsign and SSID</span>
|
||||
<span class="n">callsign</span> <span class="o">=</span> <span class="n">NO1CLL</span>
|
||||
<span class="n">ssid</span> <span class="o">=</span> <span class="mi">0</span>
|
||||
|
||||
<span class="c1"># Enable interface if you want use it!</span>
|
||||
<span class="n">interface_enabled</span> <span class="o">=</span> <span class="kc">True</span>
|
||||
|
||||
<span class="c1"># Allow transmit on interface.</span>
|
||||
<span class="n">outgoing</span> <span class="o">=</span> <span class="kc">True</span>
|
||||
|
||||
<span class="c1"># Serial port for the device</span>
|
||||
<span class="n">port</span> <span class="o">=</span> <span class="o">/</span><span class="n">dev</span><span class="o">/</span><span class="n">ttyUSB2</span>
|
||||
|
||||
<span class="c1"># Set the serial baud-rate and other</span>
|
||||
<span class="c1"># configuration parameters.</span>
|
||||
<span class="n">speed</span> <span class="o">=</span> <span class="mi">115200</span>
|
||||
<span class="n">databits</span> <span class="o">=</span> <span class="mi">8</span>
|
||||
<span class="n">parity</span> <span class="o">=</span> <span class="n">none</span>
|
||||
<span class="n">stopbits</span> <span class="o">=</span> <span class="mi">1</span>
|
||||
|
||||
<span class="c1"># Set the modem preamble. A 150ms</span>
|
||||
<span class="c1"># preamble should be a reasonable</span>
|
||||
<span class="c1"># default, but may need to be</span>
|
||||
<span class="c1"># increased for radios with slow-</span>
|
||||
<span class="c1"># opening squelch and long TX/RX</span>
|
||||
<span class="c1"># turnaround</span>
|
||||
<span class="n">preamble</span> <span class="o">=</span> <span class="mi">150</span>
|
||||
|
||||
<span class="c1"># Set the modem TX tail. In most</span>
|
||||
<span class="c1"># cases this should be kept as low</span>
|
||||
<span class="c1"># as possible to not waste airtime.</span>
|
||||
<span class="n">txtail</span> <span class="o">=</span> <span class="mi">10</span>
|
||||
|
||||
<span class="c1"># Configure CDMA parameters. These</span>
|
||||
<span class="c1"># settings are reasonable defaults.</span>
|
||||
<span class="n">persistence</span> <span class="o">=</span> <span class="mi">200</span>
|
||||
<span class="n">slottime</span> <span class="o">=</span> <span class="mi">20</span>
|
||||
|
||||
<span class="c1"># Whether to use KISS flow-control.</span>
|
||||
<span class="c1"># This is useful for modems with a</span>
|
||||
<span class="c1"># small internal packet buffer.</span>
|
||||
<span class="n">flow_control</span> <span class="o">=</span> <span class="n">false</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="clearer"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sphinxsidebar" role="navigation" aria-label="main navigation">
|
||||
<div class="sphinxsidebarwrapper">
|
||||
<h3><a href="index.html">Table of Contents</a></h3>
|
||||
<ul>
|
||||
<li><a class="reference internal" href="#">Supported Interfaces</a><ul>
|
||||
<li><a class="reference internal" href="#udp-interface">UDP Interface</a></li>
|
||||
<li><a class="reference internal" href="#tcp-server-interface">TCP Server Interface</a></li>
|
||||
<li><a class="reference internal" href="#tcp-client-interface">TCP Client Interface</a></li>
|
||||
<li><a class="reference internal" href="#rnode-lora-interface">RNode LoRa Interface</a></li>
|
||||
<li><a class="reference internal" href="#serial-interface">Serial Interface</a></li>
|
||||
<li><a class="reference internal" href="#kiss-interface">KISS Interface</a></li>
|
||||
<li><a class="reference internal" href="#ax-25-kiss-interface">AX.25 KISS Interface</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h4>Previous topic</h4>
|
||||
<p class="topless"><a href="networks.html"
|
||||
title="previous chapter">Building Networks</a></p>
|
||||
<h4>Next topic</h4>
|
||||
<p class="topless"><a href="understanding.html"
|
||||
title="next chapter">Understanding Reticulum</a></p>
|
||||
<div role="note" aria-label="source link">
|
||||
<h3>This Page</h3>
|
||||
<ul class="this-page-menu">
|
||||
<li><a href="_sources/interfaces.rst.txt"
|
||||
rel="nofollow">Show Source</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div id="searchbox" style="display: none" role="search">
|
||||
<h3 id="searchlabel">Quick search</h3>
|
||||
<div class="searchformwrapper">
|
||||
<form class="search" action="search.html" method="get">
|
||||
<input type="text" name="q" aria-labelledby="searchlabel" />
|
||||
<input type="submit" value="Go" />
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<script>$('#searchbox').show(0);</script>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clearer"></div>
|
||||
</div>
|
||||
<div class="related" role="navigation" aria-label="related navigation">
|
||||
<h3>Navigation</h3>
|
||||
<ul>
|
||||
<li class="right" style="margin-right: 10px">
|
||||
<a href="genindex.html" title="General Index"
|
||||
>index</a></li>
|
||||
<li class="right" >
|
||||
<a href="understanding.html" title="Understanding Reticulum"
|
||||
>next</a> |</li>
|
||||
<li class="right" >
|
||||
<a href="networks.html" title="Building Networks"
|
||||
>previous</a> |</li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.2.7 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">Supported Interfaces</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="footer" role="contentinfo">
|
||||
© Copyright 2021, Mark Qvist.
|
||||
Created using <a href="https://www.sphinx-doc.org/">Sphinx</a> 4.0.1.
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,259 @@
|
||||
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Building Networks — Reticulum Network Stack 0.2.7 beta documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/classic.css" />
|
||||
|
||||
<script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
|
||||
<script src="_static/jquery.js"></script>
|
||||
<script src="_static/underscore.js"></script>
|
||||
<script src="_static/doctools.js"></script>
|
||||
|
||||
<link rel="index" title="Index" href="genindex.html" />
|
||||
<link rel="search" title="Search" href="search.html" />
|
||||
<link rel="next" title="Supported Interfaces" href="interfaces.html" />
|
||||
<link rel="prev" title="Using Reticulum on Your System" href="using.html" />
|
||||
</head><body>
|
||||
<div class="related" role="navigation" aria-label="related navigation">
|
||||
<h3>Navigation</h3>
|
||||
<ul>
|
||||
<li class="right" style="margin-right: 10px">
|
||||
<a href="genindex.html" title="General Index"
|
||||
accesskey="I">index</a></li>
|
||||
<li class="right" >
|
||||
<a href="interfaces.html" title="Supported Interfaces"
|
||||
accesskey="N">next</a> |</li>
|
||||
<li class="right" >
|
||||
<a href="using.html" title="Using Reticulum on Your System"
|
||||
accesskey="P">previous</a> |</li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.2.7 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">Building Networks</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="document">
|
||||
<div class="documentwrapper">
|
||||
<div class="bodywrapper">
|
||||
<div class="body" role="main">
|
||||
|
||||
<div class="section" id="building-networks">
|
||||
<span id="networks-main"></span><h1>Building Networks<a class="headerlink" href="#building-networks" title="Permalink to this headline">¶</a></h1>
|
||||
<p>This chapter will provide you with the knowledge needed to build networks with
|
||||
Reticulum, which can often be easier than using traditional stacks, since you
|
||||
don’t have to worry about coordinating addresses, subnets and routing for an
|
||||
entire network that you might not know how will evolve in the future. With
|
||||
Reticulum, you can simply add more segments to your network when it becomes
|
||||
necesarry, and Reticulum will handle the convergence of the entire network
|
||||
automatically.</p>
|
||||
<div class="section" id="concepts-overview">
|
||||
<h2>Concepts & Overview<a class="headerlink" href="#concepts-overview" title="Permalink to this headline">¶</a></h2>
|
||||
<p>There are important points that need to be kept in mind when building networks
|
||||
with Reticulum:</p>
|
||||
<blockquote>
|
||||
<div><ul>
|
||||
<li><div class="line-block">
|
||||
<div class="line">In a Reticulum network, any node can autonomously generate as many adresses
|
||||
(called <em>destinations</em> in Reticulum terminology) as it needs, which become
|
||||
globally reachable to the rest of the network. There is no central point of
|
||||
control over the adress space.</div>
|
||||
</div>
|
||||
</li>
|
||||
<li><div class="line-block">
|
||||
<div class="line">Reticulum was designed to handle both very small, and very large networks.
|
||||
While the adress space can support billions of endpoints, Reticulum is
|
||||
also very useful when just a few devices needs to communicate.</div>
|
||||
</div>
|
||||
</li>
|
||||
<li><div class="line-block">
|
||||
<div class="line">Reticulum provides sender/initiator anonymity by default. There is no way
|
||||
to filter traffic or discriminate it based on the source of the traffic.</div>
|
||||
</div>
|
||||
</li>
|
||||
<li><div class="line-block">
|
||||
<div class="line">All traffic is encrypted using ephemeral keys generated by an Elliptic Curve
|
||||
Diffie-Hellman key exchange on Curve25519. There is no way to inspect traffic
|
||||
contents, and no way to prioritise or throttle certain kinds of traffic.
|
||||
All transport and routing layers are thus completely agnostic to traffic type,
|
||||
and will pass all traffic equally.</div>
|
||||
</div>
|
||||
</li>
|
||||
<li><div class="line-block">
|
||||
<div class="line">Reticulum can function both with and without infrastructure. When <em>transport
|
||||
nodes</em> are available, they can route traffic over multiple hops for other
|
||||
nodes, and will function as a distributed cryptographic keystore. When there
|
||||
is no transport nodes available, all nodes that are within communication range
|
||||
can still communicate.</div>
|
||||
</div>
|
||||
</li>
|
||||
<li><div class="line-block">
|
||||
<div class="line">Every node can become a transport node, simply by enabling it in it’s
|
||||
configuration, but there is no need for every node on the network to be a
|
||||
transport node. Letting every node be a transport node will in most cases
|
||||
degrade the performance and reliability of the network.</div>
|
||||
</div>
|
||||
<blockquote>
|
||||
<div><p>In general terms, if a node is stationary, well-connected and kept running
|
||||
most of the time, it is a good candidate to be a transport node. For optimal
|
||||
performance, a network should contain the amount of transport nodes that
|
||||
provides connectivity to the intended area / topography, and not many more
|
||||
than that.</p>
|
||||
</div></blockquote>
|
||||
</li>
|
||||
</ul>
|
||||
</div></blockquote>
|
||||
<p>Reticulum allows you to mix very different kinds of networking mediums into a
|
||||
unified mesh, or to keep everything within one medium. You could build a “virtual
|
||||
network” running entirely over the Internet, where all nodes communicate over TCP
|
||||
and UDP “channels”. You could also build such a network using MQTT or ZeroMQ as
|
||||
the underlying carrier for Reticulum.</p>
|
||||
<p>However, most real-world networks will probably involve either some form of
|
||||
wireless or direct hardline communications. To allow Reticulum to communicate
|
||||
over any type of medium, you must specify it in the configuration file, by default
|
||||
located at <code class="docutils literal notranslate"><span class="pre">~/.reticulum/config</span></code>. See the <a class="reference internal" href="interfaces.html#interfaces-main"><span class="std std-ref">Supported Interfaces</span></a>
|
||||
chapter of this manual for interface configuration examples.</p>
|
||||
<p>Any number of interfaces can be configured, and Reticulum will automatically
|
||||
decide which are suitable to use in any given situation, depending on where
|
||||
traffic needs to flow.</p>
|
||||
</div>
|
||||
<div class="section" id="example-scenarios">
|
||||
<h2>Example Scenarios<a class="headerlink" href="#example-scenarios" title="Permalink to this headline">¶</a></h2>
|
||||
<p>This section illustrates a few example scenarios, and how they would, in general
|
||||
terms, be planned, implemented and configured.</p>
|
||||
<div class="section" id="interconnected-lora-sites">
|
||||
<h3>Interconnected LoRa Sites<a class="headerlink" href="#interconnected-lora-sites" title="Permalink to this headline">¶</a></h3>
|
||||
<p>An organisation wants to provide communication and information services to it’s
|
||||
members, which are located mainly in three separate areas. Three suitable hill-top
|
||||
locations are found, where the organisation can install equipment: Site A, B and C.</p>
|
||||
<p>Since the amount of data that needs to be exchanged between users is mainly text-
|
||||
based, the bandwidth requirements are low, and LoRa radios are chosen to connect
|
||||
users to the network.</p>
|
||||
<p>Due to the hill-top locations found, there is radio line-of-sight between site A
|
||||
and B, and also between site B and C. Because of this, the organisation does not
|
||||
need to use the Internet to interconnect the sites, but purchases four Point-to-Point
|
||||
WiFi based radios for interconnecting the sites.</p>
|
||||
<p>At each site, a Raspberry Pi is installed to function as a gateway. A LoRa radio
|
||||
is connected to the Pi with a USB cable, and the WiFi radio is connected to the
|
||||
ethernet port of the Pi. At site B, two WiFi radios are needed to be able to reach
|
||||
both site A and site C, so an extra ethernet adapter is connected to the Pi in
|
||||
this location.</p>
|
||||
<p>Once the hardware has been installed, Reticulum is installed on all the Pis, and at
|
||||
site A and C, one interface is added for the LoRa radio, as well as one for the WiFi
|
||||
radio. At site B, an interface for the LoRa radio, and one interface for each WiFi
|
||||
radio is added to the Reticulum configuration file. The transport node option is
|
||||
enabled in the configuration of all three gateways.</p>
|
||||
<p>The network is now operational, and ready to serve users across all three areas.
|
||||
The organisation prepares a LoRa radio that is supplied to the end users, along
|
||||
with a Reticulum configuration file, that contains the right parameters for
|
||||
communicating with the LoRa radios installed at the gateway sites.</p>
|
||||
<p>Once users connect to the network, anyone will be able to communicate with anyone
|
||||
else across all three sites.</p>
|
||||
</div>
|
||||
<div class="section" id="bridging-over-the-internet">
|
||||
<h3>Bridging Over the Internet<a class="headerlink" href="#bridging-over-the-internet" title="Permalink to this headline">¶</a></h3>
|
||||
<p>As the organisation grows, several new communities form in places too far away
|
||||
from the core network to be reachable over WiFi links. New gateways similar to those
|
||||
previously installed are set up for the new communities at the new sites D and E, but
|
||||
they are islanded from the core network, and only serve the local users.</p>
|
||||
<p>After investigating the options, it is found that it is possible to install an
|
||||
Internet connection at site A, and an interface on the Internet connection is
|
||||
configured for Reticulum on the Raspberry Pi at site A.</p>
|
||||
<p>A member of the organisation at site D, named Dori, is willing to help by sharing
|
||||
the Internet connection she already has in her home, and is able to leave a Raspberry
|
||||
Pi running. A new Reticulum interface is configured on her Pi, connecting to the newly
|
||||
enabled Internet interface on the gateway at site A. Dori is now connected to both
|
||||
all the nodes at her own local site (through the hill-top LoRa gateway), and all the
|
||||
combined users of sites A, B and C. She then enables transport on her node, and
|
||||
traffic from site D can now reach everyone at site A, B and C, and vice versa.</p>
|
||||
</div>
|
||||
<div class="section" id="growth-and-convergence">
|
||||
<h3>Growth and Convergence<a class="headerlink" href="#growth-and-convergence" title="Permalink to this headline">¶</a></h3>
|
||||
<p>As the organisation grows, more gateways are added to keep up with the growing user
|
||||
base. Some local gateways even add VHF radios and packet modems to reach outlying users
|
||||
and communities that are out of reach for the LoRa radios and WiFi backhauls.</p>
|
||||
<p>As more sites, gateways and users are connected, the amount of coordination required
|
||||
is kept to a minimum. If one community wants to add connectivity to the next one
|
||||
over, it can simply be done without having to involve everyone or coordinate address
|
||||
space or routing tables.</p>
|
||||
<p>With the added geographical coverage, the operators at site A one day find that
|
||||
the original internet bridged interfaces are no longer utilised. The network has
|
||||
converged to be completely self-connected, and the sites that were once poorly
|
||||
connected outliers are now an integral part of the network.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="clearer"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sphinxsidebar" role="navigation" aria-label="main navigation">
|
||||
<div class="sphinxsidebarwrapper">
|
||||
<h3><a href="index.html">Table of Contents</a></h3>
|
||||
<ul>
|
||||
<li><a class="reference internal" href="#">Building Networks</a><ul>
|
||||
<li><a class="reference internal" href="#concepts-overview">Concepts & Overview</a></li>
|
||||
<li><a class="reference internal" href="#example-scenarios">Example Scenarios</a><ul>
|
||||
<li><a class="reference internal" href="#interconnected-lora-sites">Interconnected LoRa Sites</a></li>
|
||||
<li><a class="reference internal" href="#bridging-over-the-internet">Bridging Over the Internet</a></li>
|
||||
<li><a class="reference internal" href="#growth-and-convergence">Growth and Convergence</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h4>Previous topic</h4>
|
||||
<p class="topless"><a href="using.html"
|
||||
title="previous chapter">Using Reticulum on Your System</a></p>
|
||||
<h4>Next topic</h4>
|
||||
<p class="topless"><a href="interfaces.html"
|
||||
title="next chapter">Supported Interfaces</a></p>
|
||||
<div role="note" aria-label="source link">
|
||||
<h3>This Page</h3>
|
||||
<ul class="this-page-menu">
|
||||
<li><a href="_sources/networks.rst.txt"
|
||||
rel="nofollow">Show Source</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div id="searchbox" style="display: none" role="search">
|
||||
<h3 id="searchlabel">Quick search</h3>
|
||||
<div class="searchformwrapper">
|
||||
<form class="search" action="search.html" method="get">
|
||||
<input type="text" name="q" aria-labelledby="searchlabel" />
|
||||
<input type="submit" value="Go" />
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<script>$('#searchbox').show(0);</script>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clearer"></div>
|
||||
</div>
|
||||
<div class="related" role="navigation" aria-label="related navigation">
|
||||
<h3>Navigation</h3>
|
||||
<ul>
|
||||
<li class="right" style="margin-right: 10px">
|
||||
<a href="genindex.html" title="General Index"
|
||||
>index</a></li>
|
||||
<li class="right" >
|
||||
<a href="interfaces.html" title="Supported Interfaces"
|
||||
>next</a> |</li>
|
||||
<li class="right" >
|
||||
<a href="using.html" title="Using Reticulum on Your System"
|
||||
>previous</a> |</li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.2.7 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">Building Networks</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="footer" role="contentinfo">
|
||||
© Copyright 2021, Mark Qvist.
|
||||
Created using <a href="https://www.sphinx-doc.org/">Sphinx</a> 4.0.1.
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -5,7 +5,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>API Reference — Reticulum Network Stack 0.2.2 beta documentation</title>
|
||||
<title>API Reference — Reticulum Network Stack 0.2.7 beta documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/classic.css" />
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
<link rel="index" title="Index" href="genindex.html" />
|
||||
<link rel="search" title="Search" href="search.html" />
|
||||
<link rel="next" title="Examples" href="examples.html" />
|
||||
<link rel="next" title="Code Examples" href="examples.html" />
|
||||
<link rel="prev" title="Understanding Reticulum" href="understanding.html" />
|
||||
</head><body>
|
||||
<div class="related" role="navigation" aria-label="related navigation">
|
||||
@@ -26,12 +26,12 @@
|
||||
<a href="genindex.html" title="General Index"
|
||||
accesskey="I">index</a></li>
|
||||
<li class="right" >
|
||||
<a href="examples.html" title="Examples"
|
||||
<a href="examples.html" title="Code Examples"
|
||||
accesskey="N">next</a> |</li>
|
||||
<li class="right" >
|
||||
<a href="understanding.html" title="Understanding Reticulum"
|
||||
accesskey="P">previous</a> |</li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.2.2 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.2.7 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">API Reference</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -51,7 +51,7 @@
|
||||
<span id="api-reticulum"></span><h3>Reticulum<a class="headerlink" href="#reticulum" title="Permalink to this headline">¶</a></h3>
|
||||
<dl class="py class">
|
||||
<dt class="sig sig-object py" id="RNS.Reticulum">
|
||||
<em class="property"><span class="pre">class</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><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> </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><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
|
||||
@@ -72,16 +72,16 @@ terminated (unless killed forcibly).</p>
|
||||
programs that use RNS starting and terminating at different times,
|
||||
it will be advantageous to run a master RNS instance as a daemon for
|
||||
other programs to use on demand.</p>
|
||||
<dl class="py method">
|
||||
<dt class="sig sig-object py" id="RNS.Reticulum.should_allow_unencrypted">
|
||||
<em class="property"><span class="pre">static</span> </em><span class="sig-name descname"><span class="pre">should_allow_unencrypted</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Reticulum.should_allow_unencrypted" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>Returns whether unencrypted links are allowed by the
|
||||
current configuration.</p>
|
||||
<dl class="field-list simple">
|
||||
<dt class="field-odd">Returns</dt>
|
||||
<dd class="field-odd"><p>True if the current running configuration allows downgrading links to plaintext. False if not.</p>
|
||||
</dd>
|
||||
</dl>
|
||||
<dl class="py attribute">
|
||||
<dt class="sig sig-object py" id="RNS.Reticulum.MTU">
|
||||
<span class="sig-name descname"><span class="pre">MTU</span></span><em class="property"> <span class="pre">=</span> <span class="pre">500</span></em><a class="headerlink" href="#RNS.Reticulum.MTU" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>The MTU that Reticulum adheres to, and will expect other peers to
|
||||
adhere to. By default, the MTU is 500 bytes. In custom RNS network
|
||||
implementations, it is possible to change this value, but doing so will
|
||||
completely break compatibility with all other RNS networks. An identical
|
||||
MTU is a prerequisite for peers to communicate in the same network.</p>
|
||||
<p>Unless you really know what you are doing, the MTU should be left at
|
||||
the default value.</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py method">
|
||||
@@ -215,6 +215,21 @@ for addressable hashes and other purposes. Non-configurable.</p>
|
||||
</dl>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py method">
|
||||
<dt class="sig sig-object py" id="RNS.Identity.from_bytes">
|
||||
<em class="property"><span class="pre">static</span> </em><span class="sig-name descname"><span class="pre">from_bytes</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">prv_bytes</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Identity.from_bytes" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>Create a new <a class="reference internal" href="#api-identity"><span class="std std-ref">RNS.Identity</span></a> instance from <em>bytes</em> of private key.
|
||||
Can be used to load previously created and saved identities into Reticulum.</p>
|
||||
<dl class="field-list simple">
|
||||
<dt class="field-odd">Parameters</dt>
|
||||
<dd class="field-odd"><p><strong>prv_bytes</strong> – The <em>bytes</em> of private a saved private key. <strong>HAZARD!</strong> Never use this to generate a new key by feeding random data in prv_bytes.</p>
|
||||
</dd>
|
||||
<dt class="field-even">Returns</dt>
|
||||
<dd class="field-even"><p>A <a class="reference internal" href="#api-identity"><span class="std std-ref">RNS.Identity</span></a> instance, or <em>None</em> if the <em>bytes</em> data was invalid.</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py method">
|
||||
<dt class="sig sig-object py" id="RNS.Identity.from_file">
|
||||
<em class="property"><span class="pre">static</span> </em><span class="sig-name descname"><span class="pre">from_file</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">path</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Identity.from_file" title="Permalink to this definition">¶</a></dt>
|
||||
@@ -231,16 +246,17 @@ Can be used to load previously created and saved identities into Reticulum.</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py method">
|
||||
<dt class="sig sig-object py" id="RNS.Identity.from_bytes">
|
||||
<em class="property"><span class="pre">static</span> </em><span class="sig-name descname"><span class="pre">from_bytes</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">prv_bytes</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Identity.from_bytes" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>Create a new <a class="reference internal" href="#api-identity"><span class="std std-ref">RNS.Identity</span></a> instance from <em>bytes</em> of private key.
|
||||
Can be used to load previously created and saved identities into Reticulum.</p>
|
||||
<dt class="sig sig-object py" id="RNS.Identity.to_file">
|
||||
<span class="sig-name descname"><span class="pre">to_file</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">path</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Identity.to_file" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>Saves the identity to a file. This will write the private key to disk,
|
||||
and anyone with access to this file will be able to decrypt all
|
||||
communication for the identity. Be very careful with this method.</p>
|
||||
<dl class="field-list simple">
|
||||
<dt class="field-odd">Parameters</dt>
|
||||
<dd class="field-odd"><p><strong>prv_bytes</strong> – The <em>bytes</em> of private a saved private key. <strong>HAZARD!</strong> Never not use this to generate a new key by feeding random data in prv_bytes.</p>
|
||||
<dd class="field-odd"><p><strong>path</strong> – The full path specifying where to save the identity.</p>
|
||||
</dd>
|
||||
<dt class="field-even">Returns</dt>
|
||||
<dd class="field-even"><p>A <a class="reference internal" href="#api-identity"><span class="std std-ref">RNS.Identity</span></a> instance, or <em>None</em> if the <em>bytes</em> data was invalid.</p>
|
||||
<dd class="field-even"><p>True if the file was saved, otherwise False.</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd></dl>
|
||||
@@ -293,22 +309,6 @@ Can be used to load previously created and saved identities into Reticulum.</p>
|
||||
</dl>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py method">
|
||||
<dt class="sig sig-object py" id="RNS.Identity.to_file">
|
||||
<span class="sig-name descname"><span class="pre">to_file</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">path</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Identity.to_file" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>Saves the identity to a file. This will write the private key to disk,
|
||||
and anyone with access to this file will be able to decrypt all
|
||||
communication for the identity. Be very careful with this method.</p>
|
||||
<dl class="field-list simple">
|
||||
<dt class="field-odd">Parameters</dt>
|
||||
<dd class="field-odd"><p><strong>path</strong> – The full path specifying where to save the identity.</p>
|
||||
</dd>
|
||||
<dt class="field-even">Returns</dt>
|
||||
<dd class="field-even"><p>True if the file was saved, otherwise False.</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py method">
|
||||
<dt class="sig sig-object py" id="RNS.Identity.encrypt">
|
||||
<span class="sig-name descname"><span class="pre">encrypt</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">plaintext</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Identity.encrypt" title="Permalink to this definition">¶</a></dt>
|
||||
@@ -398,7 +398,7 @@ encrypted communication with it.</p>
|
||||
<dt class="field-odd">Parameters</dt>
|
||||
<dd class="field-odd"><ul class="simple">
|
||||
<li><p><strong>identity</strong> – An instance of <a class="reference internal" href="#api-identity"><span class="std std-ref">RNS.Identity</span></a>. Can hold only public keys for an outgoing destination, or holding private keys for an ingoing.</p></li>
|
||||
<li><p><strong>direction</strong> – <code class="docutils literal notranslate"><span class="pre">RNS.Destination.IN</span></code> or <code class="docutils literal notranslate"><span class="pre">RNS.Destination.OUT</span></code></p></li>
|
||||
<li><p><strong>direction</strong> – <code class="docutils literal notranslate"><span class="pre">RNS.Destination.IN</span></code> or <code class="docutils literal notranslate"><span class="pre">RNS.Destination.OUT</span></code>.</p></li>
|
||||
<li><p><strong>type</strong> – <code class="docutils literal notranslate"><span class="pre">RNS.Destination.SINGLE</span></code>, <code class="docutils literal notranslate"><span class="pre">RNS.Destination.GROUP</span></code> or <code class="docutils literal notranslate"><span class="pre">RNS.Destination.PLAIN</span></code>.</p></li>
|
||||
<li><p><strong>app_name</strong> – A string specifying the app name.</p></li>
|
||||
<li><p><strong>*aspects</strong> – Any non-zero number of string arguments.</p></li>
|
||||
@@ -645,7 +645,7 @@ unless other app_data is specified in the <em>announce</em> method.</p>
|
||||
<span id="api-packet"></span><h3>Packet<a class="headerlink" href="#packet" title="Permalink to this headline">¶</a></h3>
|
||||
<dl class="py class">
|
||||
<dt class="sig sig-object py" id="RNS.Packet">
|
||||
<em class="property"><span class="pre">class</span> </em><span class="sig-prename descclassname"><span class="pre">RNS.</span></span><span class="sig-name descname"><span class="pre">Packet</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">destination</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">data</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">packet_type</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">0</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">context</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">0</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">transport_type</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">0</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">header_type</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">0</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">transport_id</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">attached_interface</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">create_receipt</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.Packet" title="Permalink to this definition">¶</a></dt>
|
||||
<em class="property"><span class="pre">class</span> </em><span class="sig-prename descclassname"><span class="pre">RNS.</span></span><span class="sig-name descname"><span class="pre">Packet</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">destination</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">data</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">create_receipt</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.Packet" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>The Packet class is used to create packet instances that can be sent
|
||||
over a Reticulum network. Packets to will automatically be encrypted if
|
||||
they are adressed to a <code class="docutils literal notranslate"><span class="pre">RNS.Destination.SINGLE</span></code> destination,
|
||||
@@ -660,11 +660,6 @@ destinations, reticulum will use ephemeral keys, and offers <strong>Forward Secr
|
||||
<li><p><strong>destination</strong> – A <a class="reference internal" href="#api-destination"><span class="std std-ref">RNS.Destination</span></a> instance to which the packet will be sent.</p></li>
|
||||
<li><p><strong>data</strong> – The data payload to be included in the packet as <em>bytes</em>.</p></li>
|
||||
<li><p><strong>create_receipt</strong> – Specifies whether a <a class="reference internal" href="#api-packetreceipt"><span class="std std-ref">RNS.PacketReceipt</span></a> should be created when instantiating the packet.</p></li>
|
||||
<li><p><strong>type</strong> – Internal use by <a class="reference internal" href="#api-transport"><span class="std std-ref">RNS.Transport</span></a>. Defaults to <code class="docutils literal notranslate"><span class="pre">RNS.Packet.DATA</span></code>, and should not be specified.</p></li>
|
||||
<li><p><strong>context</strong> – Internal use by <a class="reference internal" href="#api-transport"><span class="std std-ref">RNS.Transport</span></a>. Ignore.</p></li>
|
||||
<li><p><strong>transport_type</strong> – Internal use by <a class="reference internal" href="#api-transport"><span class="std std-ref">RNS.Transport</span></a>. Ignore.</p></li>
|
||||
<li><p><strong>transport_id</strong> – Internal use by <a class="reference internal" href="#api-transport"><span class="std std-ref">RNS.Transport</span></a>. Ignore.</p></li>
|
||||
<li><p><strong>attached_interface</strong> – Internal use by <a class="reference internal" href="#api-transport"><span class="std std-ref">RNS.Transport</span></a>. Ignore.</p></li>
|
||||
</ul>
|
||||
</dd>
|
||||
</dl>
|
||||
@@ -709,11 +704,11 @@ destinations, reticulum will use ephemeral keys, and offers <strong>Forward Secr
|
||||
<span id="api-packetreceipt"></span><h3>Packet Receipt<a class="headerlink" href="#packet-receipt" title="Permalink to this headline">¶</a></h3>
|
||||
<dl class="py class">
|
||||
<dt class="sig sig-object py" id="RNS.PacketReceipt">
|
||||
<em class="property"><span class="pre">class</span> </em><span class="sig-prename descclassname"><span class="pre">RNS.</span></span><span class="sig-name descname"><span class="pre">PacketReceipt</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">packet</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.PacketReceipt" title="Permalink to this definition">¶</a></dt>
|
||||
<em class="property"><span class="pre">class</span> </em><span class="sig-prename descclassname"><span class="pre">RNS.</span></span><span class="sig-name descname"><span class="pre">PacketReceipt</span></span><a class="headerlink" href="#RNS.PacketReceipt" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>The PacketReceipt class is used to receive notifications about
|
||||
<a class="reference internal" href="#api-packet"><span class="std std-ref">RNS.Packet</span></a> instances sent over the network. Instances
|
||||
of this class should never be created manually, but always returned
|
||||
from a the <em>send()</em> method of a <a class="reference internal" href="#api-packet"><span class="std std-ref">RNS.Packet</span></a> instance.</p>
|
||||
of this class are never created manually, but always returned from
|
||||
the <em>send()</em> method of a <a class="reference internal" href="#api-packet"><span class="std std-ref">RNS.Packet</span></a> instance.</p>
|
||||
<dl class="py method">
|
||||
<dt class="sig sig-object py" id="RNS.PacketReceipt.get_status">
|
||||
<span class="sig-name descname"><span class="pre">get_status</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#RNS.PacketReceipt.get_status" title="Permalink to this definition">¶</a></dt>
|
||||
@@ -774,15 +769,16 @@ from a the <em>send()</em> method of a <a class="reference internal" href="#api-
|
||||
<span id="api-link"></span><h3>Link<a class="headerlink" href="#link" title="Permalink to this headline">¶</a></h3>
|
||||
<dl class="py class">
|
||||
<dt class="sig sig-object py" id="RNS.Link">
|
||||
<em class="property"><span class="pre">class</span> </em><span class="sig-prename descclassname"><span class="pre">RNS.</span></span><span class="sig-name descname"><span class="pre">Link</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">destination</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">owner</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">peer_pub_bytes</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">peer_sig_pub_bytes</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.Link" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>This class.</p>
|
||||
<em class="property"><span class="pre">class</span> </em><span class="sig-prename descclassname"><span class="pre">RNS.</span></span><span class="sig-name descname"><span class="pre">Link</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">destination</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">established_callback</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">closed_callback</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.Link" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>This class is used to establish and manage links to other peers. When a
|
||||
link instance is created, Reticulum will attempt to establish verified
|
||||
connectivity with the specified destination.</p>
|
||||
<dl class="field-list simple">
|
||||
<dt class="field-odd">Parameters</dt>
|
||||
<dd class="field-odd"><ul class="simple">
|
||||
<li><p><strong>destination</strong> – A <a class="reference internal" href="#api-destination"><span class="std std-ref">RNS.Destination</span></a> instance which to establish a link to.</p></li>
|
||||
<li><p><strong>owner</strong> – Internal use by <a class="reference internal" href="#api-transport"><span class="std std-ref">RNS.Transport</span></a>, ignore this argument.</p></li>
|
||||
<li><p><strong>peer_pub_bytes</strong> – Internal use, ignore this argument.</p></li>
|
||||
<li><p><strong>peer_sig_pub_bytes</strong> – Internal use, ignore this argument.</p></li>
|
||||
<li><p><strong>established_callback</strong> – An optional function or method with the signature <em>callback(link)</em> to be called when the link has been established.</p></li>
|
||||
<li><p><strong>closed_callback</strong> – An optional function or method with the signature <em>callback(link)</em> to be called when the link is closed.</p></li>
|
||||
</ul>
|
||||
</dd>
|
||||
</dl>
|
||||
@@ -793,9 +789,9 @@ from a the <em>send()</em> method of a <a class="reference internal" href="#api-
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py attribute">
|
||||
<dt class="sig sig-object py" id="RNS.Link.DEFAULT_TIMEOUT">
|
||||
<span class="sig-name descname"><span class="pre">DEFAULT_TIMEOUT</span></span><em class="property"> <span class="pre">=</span> <span class="pre">15.0</span></em><a class="headerlink" href="#RNS.Link.DEFAULT_TIMEOUT" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>Default timeout for link establishment in seconds.</p>
|
||||
<dt class="sig sig-object py" id="RNS.Link.ESTABLISHMENT_TIMEOUT_PER_HOP">
|
||||
<span class="sig-name descname"><span class="pre">ESTABLISHMENT_TIMEOUT_PER_HOP</span></span><em class="property"> <span class="pre">=</span> <span class="pre">5</span></em><a class="headerlink" href="#RNS.Link.ESTABLISHMENT_TIMEOUT_PER_HOP" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>Default timeout for link establishment in seconds per hop to destination.</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py attribute">
|
||||
@@ -820,16 +816,21 @@ thus preserved. This method can be used for authentication.</p>
|
||||
|
||||
<dl class="py method">
|
||||
<dt class="sig sig-object py" id="RNS.Link.request">
|
||||
<span class="sig-name descname"><span class="pre">request</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">data</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">response_callback</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">failed_callback</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.Link.request" title="Permalink to this definition">¶</a></dt>
|
||||
<span class="sig-name descname"><span class="pre">request</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">data</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">response_callback</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">failed_callback</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">progress_callback</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">timeout</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.Link.request" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>Sends a request to the remote peer.</p>
|
||||
<dl class="field-list simple">
|
||||
<dt class="field-odd">Parameters</dt>
|
||||
<dd class="field-odd"><ul class="simple">
|
||||
<li><p><strong>path</strong> – The request path.</p></li>
|
||||
<li><p><strong>response_callback</strong> – A function or method with the signature <em>response_callback(request_receipt)</em> to be called when a response is received. See the <a class="reference internal" href="examples.html#example-request"><span class="std std-ref">Request Example</span></a> for more info.</p></li>
|
||||
<li><p><strong>failed_callback</strong> – A function or method with the signature <em>failed_callback(request_receipt)</em> to be called when a request fails. See the <a class="reference internal" href="examples.html#example-request"><span class="std std-ref">Request Example</span></a> for more info.</p></li>
|
||||
<li><p><strong>response_callback</strong> – An optional function or method with the signature <em>response_callback(request_receipt)</em> to be called when a response is received. See the <a class="reference internal" href="examples.html#example-request"><span class="std std-ref">Request Example</span></a> for more info.</p></li>
|
||||
<li><p><strong>failed_callback</strong> – An optional function or method with the signature <em>failed_callback(request_receipt)</em> to be called when a request fails. See the <a class="reference internal" href="examples.html#example-request"><span class="std std-ref">Request Example</span></a> for more info.</p></li>
|
||||
<li><p><strong>progress_callback</strong> – An optional function or method with the signature <em>progress_callback(request_receipt)</em> to be called when progress is made receiving the response. Progress can be accessed as a float between 0.0 and 1.0 by the <em>request_receipt.progress</em> property.</p></li>
|
||||
<li><p><strong>timeout</strong> – An optional timeout in seconds for the request. If <em>None</em> is supplied it will be calculated based on link RTT.</p></li>
|
||||
</ul>
|
||||
</dd>
|
||||
<dt class="field-even">Returns</dt>
|
||||
<dd class="field-even"><p>A <a class="reference internal" href="#api-requestreceipt"><span class="std std-ref">RNS.RequestReceipt</span></a> instance if the request was sent, or <em>False</em> if it was not.</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd></dl>
|
||||
|
||||
@@ -956,16 +957,65 @@ identified over this link.</p>
|
||||
</dl>
|
||||
</dd></dl>
|
||||
|
||||
</dd></dl>
|
||||
|
||||
</div>
|
||||
<div class="section" id="request-receipt">
|
||||
<span id="api-requestreceipt"></span><h3>Request Receipt<a class="headerlink" href="#request-receipt" title="Permalink to this headline">¶</a></h3>
|
||||
<dl class="py class">
|
||||
<dt class="sig sig-object py" id="RNS.RequestReceipt">
|
||||
<em class="property"><span class="pre">class</span> </em><span class="sig-prename descclassname"><span class="pre">RNS.</span></span><span class="sig-name descname"><span class="pre">RequestReceipt</span></span><a class="headerlink" href="#RNS.RequestReceipt" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>An instance of this class is returned by the <code class="docutils literal notranslate"><span class="pre">request</span></code> method of <code class="docutils literal notranslate"><span class="pre">RNS.Link</span></code>
|
||||
instances. It should never be instantiated manually. It provides methods to
|
||||
check status, response time and response data when the request concludes.</p>
|
||||
<dl class="py method">
|
||||
<dt class="sig sig-object py" id="RNS.Link.disable_encryption">
|
||||
<span class="sig-name descname"><span class="pre">disable_encryption</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Link.disable_encryption" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>HAZARDOUS. This will downgrade the link to encryptionless. All
|
||||
information over the link will be sent in plaintext. Never use
|
||||
this in production applications. Should only be used for debugging
|
||||
purposes, and will disappear in a future version.</p>
|
||||
<p>If encryptionless links are not explicitly allowed in the users
|
||||
configuration file, Reticulum will terminate itself along with the
|
||||
client application and throw an error message to the user.</p>
|
||||
<dt class="sig sig-object py" id="RNS.RequestReceipt.get_request_id">
|
||||
<span class="sig-name descname"><span class="pre">get_request_id</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#RNS.RequestReceipt.get_request_id" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><dl class="field-list simple">
|
||||
<dt class="field-odd">Returns</dt>
|
||||
<dd class="field-odd"><p>The request ID as <em>bytes</em>.</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py method">
|
||||
<dt class="sig sig-object py" id="RNS.RequestReceipt.get_status">
|
||||
<span class="sig-name descname"><span class="pre">get_status</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#RNS.RequestReceipt.get_status" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><dl class="field-list simple">
|
||||
<dt class="field-odd">Returns</dt>
|
||||
<dd class="field-odd"><p>The current status of the request, one of <code class="docutils literal notranslate"><span class="pre">RNS.RequestReceipt.FAILED</span></code>, <code class="docutils literal notranslate"><span class="pre">RNS.RequestReceipt.SENT</span></code>, <code class="docutils literal notranslate"><span class="pre">RNS.RequestReceipt.DELIVERED</span></code>, <code class="docutils literal notranslate"><span class="pre">RNS.RequestReceipt.READY</span></code>.</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py method">
|
||||
<dt class="sig sig-object py" id="RNS.RequestReceipt.get_progress">
|
||||
<span class="sig-name descname"><span class="pre">get_progress</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#RNS.RequestReceipt.get_progress" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><dl class="field-list simple">
|
||||
<dt class="field-odd">Returns</dt>
|
||||
<dd class="field-odd"><p>The progress of a response being received as a <em>float</em> between 0.0 and 1.0.</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py method">
|
||||
<dt class="sig sig-object py" id="RNS.RequestReceipt.get_response">
|
||||
<span class="sig-name descname"><span class="pre">get_response</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#RNS.RequestReceipt.get_response" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><dl class="field-list simple">
|
||||
<dt class="field-odd">Returns</dt>
|
||||
<dd class="field-odd"><p>The response as <em>bytes</em> if it is ready, otherwise <em>None</em>.</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py method">
|
||||
<dt class="sig sig-object py" id="RNS.RequestReceipt.get_response_time">
|
||||
<span class="sig-name descname"><span class="pre">get_response_time</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#RNS.RequestReceipt.get_response_time" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><dl class="field-list simple">
|
||||
<dt class="field-odd">Returns</dt>
|
||||
<dd class="field-odd"><p>The response time of the request in seconds.</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd></dl>
|
||||
|
||||
</dd></dl>
|
||||
@@ -975,7 +1025,7 @@ client application and throw an error message to the user.</p>
|
||||
<span id="api-resource"></span><h3>Resource<a class="headerlink" href="#resource" title="Permalink to this headline">¶</a></h3>
|
||||
<dl class="py class">
|
||||
<dt class="sig sig-object py" id="RNS.Resource">
|
||||
<em class="property"><span class="pre">class</span> </em><span class="sig-prename descclassname"><span class="pre">RNS.</span></span><span class="sig-name descname"><span class="pre">Resource</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">data</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">link</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">advertise</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">True</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>, <em class="sig-param"><span class="n"><span class="pre">callback</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">progress_callback</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">segment_index</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">1</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">original_hash</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">request_id</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">is_response</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.Resource" title="Permalink to this definition">¶</a></dt>
|
||||
<em class="property"><span class="pre">class</span> </em><span class="sig-prename descclassname"><span class="pre">RNS.</span></span><span class="sig-name descname"><span class="pre">Resource</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">data</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">link</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">advertise</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">True</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>, <em class="sig-param"><span class="n"><span class="pre">callback</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">progress_callback</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">timeout</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.Resource" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>The Resource class allows transferring arbitrary amounts
|
||||
of data over a link. It will automatically handle sequencing,
|
||||
compression, coordination and checksumming.</p>
|
||||
@@ -984,14 +1034,10 @@ compression, coordination and checksumming.</p>
|
||||
<dd class="field-odd"><ul class="simple">
|
||||
<li><p><strong>data</strong> – The data to be transferred. Can be <em>bytes</em> or an open <em>file handle</em>. See the <a class="reference internal" href="examples.html#example-filetransfer"><span class="std std-ref">Filetransfer Example</span></a> for details.</p></li>
|
||||
<li><p><strong>link</strong> – The <a class="reference internal" href="#api-link"><span class="std std-ref">RNS.Link</span></a> instance on which to transfer the data.</p></li>
|
||||
<li><p><strong>advertise</strong> – Whether to automatically advertise the resource. Can be <em>True</em> or <em>False</em>.</p></li>
|
||||
<li><p><strong>auto_compress</strong> – Whether to auto-compress the resource. Can be <em>True</em> or <em>False</em>.</p></li>
|
||||
<li><p><strong>callback</strong> – A <em>callable</em> with the signature <em>callback(resource)</em>. Will be called when the resource transfer concludes.</p></li>
|
||||
<li><p><strong>progress_callback</strong> – A <em>callable</em> with the signature <em>callback(resource)</em>. Will be called whenever the resource transfer progress is updated.</p></li>
|
||||
<li><p><strong>segment_index</strong> – Internal use, ignore.</p></li>
|
||||
<li><p><strong>original_hash</strong> – Internal use, ignore.</p></li>
|
||||
<li><p><strong>is_request</strong> – Internal use, ignore.</p></li>
|
||||
<li><p><strong>is_response</strong> – Internal use, ignore.</p></li>
|
||||
<li><p><strong>advertise</strong> – Optional. Whether to automatically advertise the resource. Can be <em>True</em> or <em>False</em>.</p></li>
|
||||
<li><p><strong>auto_compress</strong> – Optional. Whether to auto-compress the resource. Can be <em>True</em> or <em>False</em>.</p></li>
|
||||
<li><p><strong>callback</strong> – An optional <em>callable</em> with the signature <em>callback(resource)</em>. Will be called when the resource transfer concludes.</p></li>
|
||||
<li><p><strong>progress_callback</strong> – An optional <em>callable</em> with the signature <em>callback(resource)</em>. Will be called whenever the resource transfer progress is updated.</p></li>
|
||||
</ul>
|
||||
</dd>
|
||||
</dl>
|
||||
@@ -1009,8 +1055,8 @@ the resource advertisement it will begin transferring.</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py method">
|
||||
<dt class="sig sig-object py" id="RNS.Resource.progress">
|
||||
<span class="sig-name descname"><span class="pre">progress</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Resource.progress" title="Permalink to this definition">¶</a></dt>
|
||||
<dt class="sig sig-object py" id="RNS.Resource.get_progress">
|
||||
<span class="sig-name descname"><span class="pre">get_progress</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Resource.get_progress" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><dl class="field-list simple">
|
||||
<dt class="field-odd">Returns</dt>
|
||||
<dd class="field-odd"><p>The current progress of the resource transfer as a <em>float</em> between 0.0 and 1.0.</p>
|
||||
@@ -1026,7 +1072,15 @@ the resource advertisement it will begin transferring.</p>
|
||||
<dl class="py class">
|
||||
<dt class="sig sig-object py" id="RNS.Transport">
|
||||
<em class="property"><span class="pre">class</span> </em><span class="sig-prename descclassname"><span class="pre">RNS.</span></span><span class="sig-name descname"><span class="pre">Transport</span></span><a class="headerlink" href="#RNS.Transport" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><dl class="py method">
|
||||
<dd><p>Through static methods of this class you can interact with the
|
||||
Transport system of Reticulum.</p>
|
||||
<dl class="py attribute">
|
||||
<dt class="sig sig-object py" id="RNS.Transport.PATHFINDER_M">
|
||||
<span class="sig-name descname"><span class="pre">PATHFINDER_M</span></span><em class="property"> <span class="pre">=</span> <span class="pre">128</span></em><a class="headerlink" href="#RNS.Transport.PATHFINDER_M" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>Maximum amount of hops that Reticulum will transport a packet.</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py method">
|
||||
<dt class="sig sig-object py" id="RNS.Transport.register_announce_handler">
|
||||
<em class="property"><span class="pre">static</span> </em><span class="sig-name descname"><span class="pre">register_announce_handler</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">handler</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Transport.register_announce_handler" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><p>Registers an announce handler.</p>
|
||||
@@ -1061,6 +1115,45 @@ the resource advertisement it will begin transferring.</p>
|
||||
</dl>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py method">
|
||||
<dt class="sig sig-object py" id="RNS.Transport.hops_to">
|
||||
<em class="property"><span class="pre">static</span> </em><span class="sig-name descname"><span class="pre">hops_to</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">destination_hash</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Transport.hops_to" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><dl class="field-list simple">
|
||||
<dt class="field-odd">Parameters</dt>
|
||||
<dd class="field-odd"><p><strong>destination_hash</strong> – A destination hash as <em>bytes</em>.</p>
|
||||
</dd>
|
||||
<dt class="field-even">Returns</dt>
|
||||
<dd class="field-even"><p>The number of hops to the specified destination, or <code class="docutils literal notranslate"><span class="pre">RNS.Transport.PATHFINDER_M</span></code> if the number of hops is unknown.</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py method">
|
||||
<dt class="sig sig-object py" id="RNS.Transport.next_hop">
|
||||
<em class="property"><span class="pre">static</span> </em><span class="sig-name descname"><span class="pre">next_hop</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">destination_hash</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Transport.next_hop" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><dl class="field-list simple">
|
||||
<dt class="field-odd">Parameters</dt>
|
||||
<dd class="field-odd"><p><strong>destination_hash</strong> – A destination hash as <em>bytes</em>.</p>
|
||||
</dd>
|
||||
<dt class="field-even">Returns</dt>
|
||||
<dd class="field-even"><p>The destination hash as <em>bytes</em> for the next hop to the specified destination, or <em>None</em> if the next hop is unknown.</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py method">
|
||||
<dt class="sig sig-object py" id="RNS.Transport.next_hop_interface">
|
||||
<em class="property"><span class="pre">static</span> </em><span class="sig-name descname"><span class="pre">next_hop_interface</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">destination_hash</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Transport.next_hop_interface" title="Permalink to this definition">¶</a></dt>
|
||||
<dd><dl class="field-list simple">
|
||||
<dt class="field-odd">Parameters</dt>
|
||||
<dd class="field-odd"><p><strong>destination_hash</strong> – A destination hash as <em>bytes</em>.</p>
|
||||
</dd>
|
||||
<dt class="field-even">Returns</dt>
|
||||
<dd class="field-even"><p>The interface for the next hop to the specified destination, or <em>None</em> if the interface is unknown.</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py method">
|
||||
<dt class="sig sig-object py" id="RNS.Transport.request_path">
|
||||
<em class="property"><span class="pre">static</span> </em><span class="sig-name descname"><span class="pre">request_path</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">destination_hash</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Transport.request_path" title="Permalink to this definition">¶</a></dt>
|
||||
@@ -1097,6 +1190,7 @@ will announce it.</p>
|
||||
<li><a class="reference internal" href="#packet">Packet</a></li>
|
||||
<li><a class="reference internal" href="#packet-receipt">Packet Receipt</a></li>
|
||||
<li><a class="reference internal" href="#link">Link</a></li>
|
||||
<li><a class="reference internal" href="#request-receipt">Request Receipt</a></li>
|
||||
<li><a class="reference internal" href="#resource">Resource</a></li>
|
||||
<li><a class="reference internal" href="#transport">Transport</a></li>
|
||||
</ul>
|
||||
@@ -1110,7 +1204,7 @@ will announce it.</p>
|
||||
title="previous chapter">Understanding Reticulum</a></p>
|
||||
<h4>Next topic</h4>
|
||||
<p class="topless"><a href="examples.html"
|
||||
title="next chapter">Examples</a></p>
|
||||
title="next chapter">Code Examples</a></p>
|
||||
<div role="note" aria-label="source link">
|
||||
<h3>This Page</h3>
|
||||
<ul class="this-page-menu">
|
||||
@@ -1139,12 +1233,12 @@ will announce it.</p>
|
||||
<a href="genindex.html" title="General Index"
|
||||
>index</a></li>
|
||||
<li class="right" >
|
||||
<a href="examples.html" title="Examples"
|
||||
<a href="examples.html" title="Code Examples"
|
||||
>next</a> |</li>
|
||||
<li class="right" >
|
||||
<a href="understanding.html" title="Understanding Reticulum"
|
||||
>previous</a> |</li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.2.2 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.2.7 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">API Reference</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Search — Reticulum Network Stack 0.2.2 beta documentation</title>
|
||||
<title>Search — Reticulum Network Stack 0.2.7 beta documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/classic.css" />
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
<li class="right" style="margin-right: 10px">
|
||||
<a href="genindex.html" title="General Index"
|
||||
accesskey="I">index</a></li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.2.2 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.2.7 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">Search</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -85,7 +85,7 @@
|
||||
<li class="right" style="margin-right: 10px">
|
||||
<a href="genindex.html" title="General Index"
|
||||
>index</a></li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.2.2 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.2.7 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">Search</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Understanding Reticulum — Reticulum Network Stack 0.2.2 beta documentation</title>
|
||||
<title>Understanding Reticulum — Reticulum Network Stack 0.2.7 beta documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/classic.css" />
|
||||
|
||||
@@ -17,7 +17,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="Getting Started Fast" href="gettingstartedfast.html" />
|
||||
<link rel="prev" title="Supported Interfaces" href="interfaces.html" />
|
||||
</head><body>
|
||||
<div class="related" role="navigation" aria-label="related navigation">
|
||||
<h3>Navigation</h3>
|
||||
@@ -29,9 +29,9 @@
|
||||
<a href="reference.html" title="API Reference"
|
||||
accesskey="N">next</a> |</li>
|
||||
<li class="right" >
|
||||
<a href="gettingstartedfast.html" title="Getting Started Fast"
|
||||
<a href="interfaces.html" title="Supported Interfaces"
|
||||
accesskey="P">previous</a> |</li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.2.2 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.2.7 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">Understanding Reticulum</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -80,7 +80,7 @@ by using multiple hops).</p>
|
||||
</div>
|
||||
<div class="section" id="goals">
|
||||
<span id="understanding-goals"></span><h2>Goals<a class="headerlink" href="#goals" title="Permalink to this headline">¶</a></h2>
|
||||
<p>To be as widely usable and easy to implement as possible, the following goals have been used to
|
||||
<p>To be as widely usable and easy to use as possible, the following goals have been used to
|
||||
guide the design of Reticulum:</p>
|
||||
<ul class="simple">
|
||||
<li><dl class="simple">
|
||||
@@ -490,7 +490,7 @@ At the same time we establish an efficient encrypted channel. The setup of this
|
||||
terms of bandwidth, so it can be used just for a short exchange, and then recreated as needed, which will
|
||||
also rotate encryption keys. The link can also be kept alive for longer periods of time, if this is
|
||||
more suitable to the application. The procedure also inserts the <em>link id</em> , a hash calculated from the link request packet, into the memory of forwarding nodes, which means that the communicating nodes can thereafter reach each other simply by referring to this <em>link id</em>.</p>
|
||||
<p>The combined bandwidth cost of setting up a link is 3 packets totalling 240 bytes (more info in the
|
||||
<p>The combined bandwidth cost of setting up a link is 3 packets totalling 237 bytes (more info in the
|
||||
<a class="reference internal" href="#understanding-packetformat"><span class="std std-ref">Binary Packet Format</span></a> section). The amount of bandwidth used on keeping
|
||||
a link open is practically negligible, at 0.62 bits per second. Even on a slow 1200 bits per second packet
|
||||
radio channel, 100 concurrent links will still leave 95% channel capacity for actual data.</p>
|
||||
@@ -764,7 +764,7 @@ proof 11
|
||||
- Announce : 151 bytes
|
||||
- Link Request : 77 bytes
|
||||
- Link Proof : 77 bytes
|
||||
- Link RTT packet : 86 bytes
|
||||
- Link RTT packet : 83 bytes
|
||||
- Link keepalive : 14 bytes
|
||||
</pre></div>
|
||||
</div>
|
||||
@@ -815,8 +815,8 @@ proof 11
|
||||
</ul>
|
||||
|
||||
<h4>Previous topic</h4>
|
||||
<p class="topless"><a href="gettingstartedfast.html"
|
||||
title="previous chapter">Getting Started Fast</a></p>
|
||||
<p class="topless"><a href="interfaces.html"
|
||||
title="previous chapter">Supported Interfaces</a></p>
|
||||
<h4>Next topic</h4>
|
||||
<p class="topless"><a href="reference.html"
|
||||
title="next chapter">API Reference</a></p>
|
||||
@@ -851,9 +851,9 @@ proof 11
|
||||
<a href="reference.html" title="API Reference"
|
||||
>next</a> |</li>
|
||||
<li class="right" >
|
||||
<a href="gettingstartedfast.html" title="Getting Started Fast"
|
||||
<a href="interfaces.html" title="Supported Interfaces"
|
||||
>previous</a> |</li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.2.2 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.2.7 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">Understanding Reticulum</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,258 @@
|
||||
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Using Reticulum on Your System — Reticulum Network Stack 0.2.7 beta documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/classic.css" />
|
||||
|
||||
<script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
|
||||
<script src="_static/jquery.js"></script>
|
||||
<script src="_static/underscore.js"></script>
|
||||
<script src="_static/doctools.js"></script>
|
||||
|
||||
<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="Getting Started Fast" href="gettingstartedfast.html" />
|
||||
</head><body>
|
||||
<div class="related" role="navigation" aria-label="related navigation">
|
||||
<h3>Navigation</h3>
|
||||
<ul>
|
||||
<li class="right" style="margin-right: 10px">
|
||||
<a href="genindex.html" title="General Index"
|
||||
accesskey="I">index</a></li>
|
||||
<li class="right" >
|
||||
<a href="networks.html" title="Building Networks"
|
||||
accesskey="N">next</a> |</li>
|
||||
<li class="right" >
|
||||
<a href="gettingstartedfast.html" title="Getting Started Fast"
|
||||
accesskey="P">previous</a> |</li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.2.7 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">Using Reticulum on Your System</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="document">
|
||||
<div class="documentwrapper">
|
||||
<div class="bodywrapper">
|
||||
<div class="body" role="main">
|
||||
|
||||
<div class="section" id="using-reticulum-on-your-system">
|
||||
<span id="using-main"></span><h1>Using Reticulum on Your System<a class="headerlink" href="#using-reticulum-on-your-system" title="Permalink to this headline">¶</a></h1>
|
||||
<p>Reticulum is not installed as a driver or kernel module, as one might expect
|
||||
of a networking stack. Instead, Reticulum is distributed as a Python module.
|
||||
This means that no special privileges are required to install or use it.
|
||||
Any program or application that uses Reticulum will automatically load and
|
||||
initialise Reticulum when it starts.</p>
|
||||
<p>In many cases, this approach is sufficient. When any program needs to use
|
||||
Reticulum, it is loaded, initialised, interfaces are brought up, and the
|
||||
program can now communicate over Reticulum. If another program starts up
|
||||
and also wants access to the same Reticulum network, the instance is simply
|
||||
shared. This works for any number of programs running concurrently, and is
|
||||
very easy to use, but depending on your use case, there are other options.</p>
|
||||
<div class="section" id="included-utility-programs">
|
||||
<h2>Included Utility Programs<a class="headerlink" href="#included-utility-programs" title="Permalink to this headline">¶</a></h2>
|
||||
<p>If you often use Reticulum from several different programs, or simply want
|
||||
Reticulum to stay available all the time, for example if you are hosting
|
||||
a transport node, you might want to run Reticulum as a separate service that
|
||||
other programs, applications and services can utilise.</p>
|
||||
<div class="section" id="the-rnsd-utility">
|
||||
<h3>The rnsd Utility<a class="headerlink" href="#the-rnsd-utility" title="Permalink to this headline">¶</a></h3>
|
||||
<p>To do so is very easy. Simply run the included <code class="docutils literal notranslate"><span class="pre">rnsd</span></code> command. When <code class="docutils literal notranslate"><span class="pre">rnsd</span></code>
|
||||
is running, it will keep all configured interfaces open, handle transport if
|
||||
it is enabled, and allow any other programs to immediately utilise the
|
||||
Reticulum network it is configured for.</p>
|
||||
<p>You can even run multiple instances of rnsd with different configurations on
|
||||
the same system.</p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span># Install Reticulum
|
||||
pip3 install rns
|
||||
|
||||
# Run rnsd
|
||||
rnsd
|
||||
</pre></div>
|
||||
</div>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>usage: rnsd [-h] [--config CONFIG] [-v] [-q] [--version]
|
||||
|
||||
Reticulum Network Stack Daemon
|
||||
|
||||
optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
--config CONFIG path to alternative Reticulum config directory
|
||||
-v, --verbose
|
||||
-q, --quiet
|
||||
--version show program's version number and exit
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="section" id="the-rnstatus-utility">
|
||||
<h3>The rnstatus Utility<a class="headerlink" href="#the-rnstatus-utility" title="Permalink to this headline">¶</a></h3>
|
||||
<p>Using the <code class="docutils literal notranslate"><span class="pre">rnstatus</span></code> utility, you can view the status of configured Reticulum
|
||||
interfaces, similar to the <code class="docutils literal notranslate"><span class="pre">ifconfig</span></code> program.</p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span># Run rnstatus
|
||||
rnstatus
|
||||
|
||||
# Example output
|
||||
Shared Instance[37428]
|
||||
Status: Up
|
||||
Connected applications: 1
|
||||
RX: 1.13 KB
|
||||
TX: 1.07 KB
|
||||
|
||||
UDPInterface[Default UDP Interface/0.0.0.0:4242]
|
||||
Status: Up
|
||||
RX: 1.01 KB
|
||||
TX: 1.01 KB
|
||||
|
||||
TCPInterface[RNS Testnet Frankfurt/frankfurt.rns.unsigned.io:4965]
|
||||
Status: Up
|
||||
RX: 1.37 KB
|
||||
TX: 9.02 KB
|
||||
</pre></div>
|
||||
</div>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>usage: rnsd [-h] [--config CONFIG] [-v] [-q] [--version]
|
||||
|
||||
Reticulum Network Stack Daemon
|
||||
|
||||
optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
--config CONFIG path to alternative Reticulum config directory
|
||||
-v, --verbose
|
||||
-q, --quiet
|
||||
--version show program's version number and exit
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="section" id="the-rnpath-utility">
|
||||
<h3>The rnpath Utility<a class="headerlink" href="#the-rnpath-utility" title="Permalink to this headline">¶</a></h3>
|
||||
<p>With the <code class="docutils literal notranslate"><span class="pre">rnpath</span></code> utility, you can look up and view paths for
|
||||
destinations on the Reticulum network.</p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span># Run rnpath
|
||||
rnpath eca6f4e4dc26ae329e61
|
||||
|
||||
# Example output
|
||||
Path found, destination <eca6f4e4dc26ae329e61> is 4 hops away via <56b115c30cd386cad69c> on TCPInterface[Testnet/frankfurt.rns.unsigned.io:4965]
|
||||
</pre></div>
|
||||
</div>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>usage: rnpath.py [-h] [--config CONFIG] [--version] [-v] [destination]
|
||||
|
||||
Reticulum Path Discovery Utility
|
||||
|
||||
positional arguments:
|
||||
destination hexadecimal hash of the destination
|
||||
|
||||
optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
--config CONFIG path to alternative Reticulum config directory
|
||||
--version show program's version number and exit
|
||||
-v, --verbose
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="section" id="the-rnprobe-utility">
|
||||
<h3>The rnprobe Utility<a class="headerlink" href="#the-rnprobe-utility" title="Permalink to this headline">¶</a></h3>
|
||||
<p>The <code class="docutils literal notranslate"><span class="pre">rnprobe</span></code> utility lets you probe a destination for connectivity, similar
|
||||
to the <code class="docutils literal notranslate"><span class="pre">ping</span></code> program. Please note that probes will only be answered if the
|
||||
specified destination is configured to send proofs for received packets. Many
|
||||
destinations will not have this option enabled, and will not be probable.</p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span># Run rnprobe
|
||||
python3 -m RNS.Utilities.rnprobe example_utilities.echo.request 9382f334de63217a4278
|
||||
|
||||
# Example output
|
||||
Sent 16 byte probe to <9382f334de63217a4278>
|
||||
Valid reply received from <9382f334de63217a4278>
|
||||
Round-trip time is 38.469 milliseconds over 2 hops
|
||||
</pre></div>
|
||||
</div>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>usage: rnprobe.py [-h] [--config CONFIG] [--version] [-v] [full_name] [destination_hash]
|
||||
|
||||
Reticulum Probe Utility
|
||||
|
||||
positional arguments:
|
||||
full_name full destination name in dotted notation
|
||||
destination_hash hexadecimal hash of the destination
|
||||
|
||||
optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
--config CONFIG path to alternative Reticulum config directory
|
||||
--version show program's version number and exit
|
||||
-v, --verbose
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="clearer"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sphinxsidebar" role="navigation" aria-label="main navigation">
|
||||
<div class="sphinxsidebarwrapper">
|
||||
<h3><a href="index.html">Table of Contents</a></h3>
|
||||
<ul>
|
||||
<li><a class="reference internal" href="#">Using Reticulum on Your System</a><ul>
|
||||
<li><a class="reference internal" href="#included-utility-programs">Included Utility Programs</a><ul>
|
||||
<li><a class="reference internal" href="#the-rnsd-utility">The rnsd Utility</a></li>
|
||||
<li><a class="reference internal" href="#the-rnstatus-utility">The rnstatus Utility</a></li>
|
||||
<li><a class="reference internal" href="#the-rnpath-utility">The rnpath Utility</a></li>
|
||||
<li><a class="reference internal" href="#the-rnprobe-utility">The rnprobe Utility</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h4>Previous topic</h4>
|
||||
<p class="topless"><a href="gettingstartedfast.html"
|
||||
title="previous chapter">Getting Started Fast</a></p>
|
||||
<h4>Next topic</h4>
|
||||
<p class="topless"><a href="networks.html"
|
||||
title="next chapter">Building Networks</a></p>
|
||||
<div role="note" aria-label="source link">
|
||||
<h3>This Page</h3>
|
||||
<ul class="this-page-menu">
|
||||
<li><a href="_sources/using.rst.txt"
|
||||
rel="nofollow">Show Source</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div id="searchbox" style="display: none" role="search">
|
||||
<h3 id="searchlabel">Quick search</h3>
|
||||
<div class="searchformwrapper">
|
||||
<form class="search" action="search.html" method="get">
|
||||
<input type="text" name="q" aria-labelledby="searchlabel" />
|
||||
<input type="submit" value="Go" />
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<script>$('#searchbox').show(0);</script>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clearer"></div>
|
||||
</div>
|
||||
<div class="related" role="navigation" aria-label="related navigation">
|
||||
<h3>Navigation</h3>
|
||||
<ul>
|
||||
<li class="right" style="margin-right: 10px">
|
||||
<a href="genindex.html" title="General Index"
|
||||
>index</a></li>
|
||||
<li class="right" >
|
||||
<a href="networks.html" title="Building Networks"
|
||||
>next</a> |</li>
|
||||
<li class="right" >
|
||||
<a href="gettingstartedfast.html" title="Getting Started Fast"
|
||||
>previous</a> |</li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.2.7 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">Using Reticulum on Your System</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="footer" role="contentinfo">
|
||||
© Copyright 2021, Mark Qvist.
|
||||
Created using <a href="https://www.sphinx-doc.org/">Sphinx</a> 4.0.1.
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -5,7 +5,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>What is Reticulum? — Reticulum Network Stack 0.2.2 beta documentation</title>
|
||||
<title>What is Reticulum? — Reticulum Network Stack 0.2.7 beta documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/classic.css" />
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
<li class="right" >
|
||||
<a href="index.html" title="Reticulum Network Stack Manual"
|
||||
accesskey="P">previous</a> |</li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.2.2 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.2.7 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">What is Reticulum?</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -45,29 +45,30 @@
|
||||
<h1>What is Reticulum?<a class="headerlink" href="#what-is-reticulum" title="Permalink to this headline">¶</a></h1>
|
||||
<p>Reticulum is a cryptography-based networking stack for wide-area networks built on readily available hardware, and can operate even with very high latency and extremely low bandwidth.</p>
|
||||
<p>Reticulum allows you to build very wide-area networks with off-the-shelf tools, and offers end-to-end encryption, autoconfiguring cryptographically backed multi-hop transport, efficient addressing, unforgeable packet acknowledgements and more.</p>
|
||||
<p>Reticulum is a complete networking stack, and does not use IP or higher layers, although it is easy to utilise IP (with TCP or UDP) as the underlying carrier for Reticulum. It is therefore trivial to tunnel Reticulum over the Internet or private IP networks. Reticulum is built directly on cryptographic principles, allowing resilience and stable functionality in open and trustless networks.</p>
|
||||
<p>No kernel modules or drivers are required. Reticulum runs completely in userland, and can run on practically any system that runs Python 3.</p>
|
||||
<p>Reticulum is a complete networking stack, and does not need IP or higher layers, although it is easy to utilise IP (with TCP or UDP) as the underlying carrier for Reticulum. It is therefore trivial to tunnel Reticulum over the Internet or private IP networks. Reticulum is built directly on cryptographic principles, allowing resilience and stable functionality in open and trustless networks.</p>
|
||||
<p>No kernel modules or drivers are required. Reticulum runs completely in userland, and can run on practically any system that runs Python 3. Reticulum runs well even on small single-board computers like the Pi Zero.</p>
|
||||
<div class="section" id="current-status">
|
||||
<h2>Current Status<a class="headerlink" href="#current-status" title="Permalink to this headline">¶</a></h2>
|
||||
<p>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.</p>
|
||||
</div>
|
||||
<div class="section" id="caveat-emptor">
|
||||
<h2>Caveat Emptor<a class="headerlink" href="#caveat-emptor" title="Permalink to this headline">¶</a></h2>
|
||||
<p>Reticulum is an experimental networking stack, and should be considered as such. While it has been built with cryptography best-practices very foremost in mind, it has not been externally security audited, and there could very well be privacy-breaking bugs. To be considered even remotely secure, Reticulum needs a very thourough security review by independt cryptographers and security researchers. If you want to help out, or help sponsor an audit, please do get in touch.</p>
|
||||
<p>Reticulum is an experimental networking stack, and should be considered as such. While it has been built with cryptography best-practices very foremost in mind, it has not been externally security audited, and there could very well be privacy-breaking bugs. To be considered secure, Reticulum needs a thourough security review by independt cryptographers and security researchers. If you want to help out, or help sponsor an audit, please do get in touch.</p>
|
||||
</div>
|
||||
<div class="section" id="what-does-reticulum-offer">
|
||||
<h2>What does Reticulum Offer?<a class="headerlink" href="#what-does-reticulum-offer" title="Permalink to this headline">¶</a></h2>
|
||||
<ul class="simple">
|
||||
<li><p>Coordination-less globally unique adressing and identification</p></li>
|
||||
<li><p>Fully self-configuring multi-hop routing</p></li>
|
||||
<li><p>Complete initiator anonymity, communicate without revealing your identity</p></li>
|
||||
<li><p>Asymmetric X25519 encryption and Ed25519 signatures as a basis for all communication</p></li>
|
||||
<li><p>Forward Secrecy with ephemereal Elliptic Curve Diffie-Hellman keys on Curve25519</p></li>
|
||||
<li><p>Reticulum uses the <a class="reference external" href="https://github.com/fernet/spec/blob/master/Spec.md">Fernet</a> specification for encryption</p>
|
||||
<li><p>Reticulum uses the <a class="reference external" href="https://github.com/fernet/spec/blob/master/Spec.md">Fernet</a> specification for on-the-wire / over-the-air encryption</p>
|
||||
<ul>
|
||||
<li><p>All keys are ephemeral and derived from an ECDH key exchange on Curve25519</p></li>
|
||||
<li><p>AES-128 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>
|
||||
<li><p>Keys are ephemeral and derived from an ECDH key exchange on Curve25519</p></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><p>Unforgeable packet delivery confirmations</p></li>
|
||||
@@ -82,7 +83,7 @@
|
||||
</li>
|
||||
<li><p>Efficient link establishment</p>
|
||||
<ul>
|
||||
<li><p>Total bandwidth cost of setting up a link is only 3 packets, totalling 240 bytes</p></li>
|
||||
<li><p>Total bandwidth cost of setting up a link is only 3 packets, totalling 237 bytes</p></li>
|
||||
<li><p>Low cost of keeping links open at only 0.62 bits per second</p></li>
|
||||
</ul>
|
||||
</li>
|
||||
@@ -110,8 +111,8 @@ configured, Reticulum will take care of the rest, and any device on the WiFi
|
||||
network can communicate with nodes on the LoRa and packet radio sides of the
|
||||
network, and vice versa.</p>
|
||||
</div>
|
||||
<div class="section" id="supported-interface-types-and-devices">
|
||||
<h2>Supported Interface Types and Devices<a class="headerlink" href="#supported-interface-types-and-devices" title="Permalink to this headline">¶</a></h2>
|
||||
<div class="section" id="interface-types-and-devices">
|
||||
<h2>Interface Types and Devices<a class="headerlink" href="#interface-types-and-devices" title="Permalink to this headline">¶</a></h2>
|
||||
<p>Reticulum implements a range of generalised interface types that covers most of the communications hardware that Reticulum can run over. If your hardware is not supported, it’s relatively simple to implement an interface class. Currently, the following interfaces are supported:</p>
|
||||
<ul class="simple">
|
||||
<li><p>Any ethernet device</p></li>
|
||||
@@ -121,6 +122,7 @@ network, and vice versa.</p>
|
||||
<li><p>TCP over IP networks</p></li>
|
||||
<li><p>UDP over IP networks</p></li>
|
||||
</ul>
|
||||
<p>For a full list and more details, see the <a class="reference internal" href="interfaces.html#interfaces-main"><span class="std std-ref">Supported Interfaces</span></a> chapter.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -138,7 +140,7 @@ network, and vice versa.</p>
|
||||
<li><a class="reference internal" href="#caveat-emptor">Caveat Emptor</a></li>
|
||||
<li><a class="reference internal" href="#what-does-reticulum-offer">What does Reticulum Offer?</a></li>
|
||||
<li><a class="reference internal" href="#where-can-reticulum-be-used">Where can Reticulum be Used?</a></li>
|
||||
<li><a class="reference internal" href="#supported-interface-types-and-devices">Supported Interface Types and Devices</a></li>
|
||||
<li><a class="reference internal" href="#interface-types-and-devices">Interface Types and Devices</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -182,7 +184,7 @@ network, and vice versa.</p>
|
||||
<li class="right" >
|
||||
<a href="index.html" title="Reticulum Network Stack Manual"
|
||||
>previous</a> |</li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.2.2 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.2.7 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">What is Reticulum?</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -22,7 +22,7 @@ copyright = '2021, Mark Qvist'
|
||||
author = 'Mark Qvist'
|
||||
|
||||
# The full version, including alpha/beta/rc tags
|
||||
release = '0.2.2 beta'
|
||||
release = '0.2.7 beta'
|
||||
|
||||
|
||||
# -- General configuration ---------------------------------------------------
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
.. _examples-main:
|
||||
|
||||
********
|
||||
Examples
|
||||
********
|
||||
*************
|
||||
Code Examples
|
||||
*************
|
||||
|
||||
A number of examples are included in the source distribution of Reticulum.
|
||||
You can use these examples to learn how to write your own programs.
|
||||
|
||||
|
||||
@@ -10,15 +10,60 @@ Try Using a Reticulum-based Program
|
||||
=============================================
|
||||
If you simply want to try using a program built with Reticulum, you can take
|
||||
a look at `Nomad Network <https://github.com/markqvist/nomadnet>`_, which
|
||||
provides a basic encrypted communications suite built completely on Reticulum.
|
||||
provides a complete encrypted communications suite built with Reticulum.
|
||||
|
||||
.. image:: screenshots/nomadnet3.png
|
||||
:target: _images/nomadnet3.png
|
||||
.. image:: screenshots/nomadnet_3.png
|
||||
:target: _images/nomadnet_3.png
|
||||
|
||||
`Nomad Network <https://github.com/markqvist/nomadnet>`_ is a user-facing client
|
||||
in the development for the messaging and information-sharing protocol
|
||||
`LXMF <https://github.com/markqvist/lxmf>`_, another project built with Reticulum.
|
||||
|
||||
You can install Nomad Network via pip:
|
||||
|
||||
.. code::
|
||||
|
||||
# Install ...
|
||||
pip3 install nomadnet
|
||||
|
||||
# ... and run
|
||||
nomadnet
|
||||
|
||||
|
||||
|
||||
Using the Included Utilities
|
||||
=============================================
|
||||
Reticulum comes with a range of included utilities that make it easier to
|
||||
manage your network, check connectivity and make Reticulum available to other
|
||||
programs on your system.
|
||||
|
||||
You can use ``rnsd`` to run Reticulum as a background or foreground service,
|
||||
and the ``rnstatus``, ``rnpath`` and ``rnprobe`` utilities to view and query
|
||||
network status and connectivity.
|
||||
|
||||
To learn more about these utility programs, have a look at the
|
||||
:ref:`Using Reticulum on Your System<using-main>` chapter of this manual.
|
||||
|
||||
Creating a Network With Reticulum
|
||||
=============================================
|
||||
To create a network, you will need to specify one or more *interfaces* for
|
||||
Reticulum to use. This is done in the Reticulum configuration file, which by
|
||||
default is located at ``~/.reticulum/config``.
|
||||
|
||||
When Reticulum is started for the first time, it will create a default
|
||||
configuration file, with one active interface. This default interface uses
|
||||
your existing ethernet network (if there is one), and only allows you to
|
||||
communicate with other Reticulum peers within your local broadcast domain.
|
||||
|
||||
To communicate further, you will have to add one or more interfaces. The default
|
||||
configuration includes a number of examples, ranging from using TCP over the
|
||||
internet, to LoRa and Packet Radio interfaces.
|
||||
|
||||
Possibly, the examples in the config file are enough to get you started. If
|
||||
you want more information, you can read the :ref:`Building Networks<networks-main>`
|
||||
and :ref:`Interfaces<interfaces-main>` chapters of this manual.
|
||||
|
||||
|
||||
Develop a Program with Reticulum
|
||||
===========================================
|
||||
If you want to develop programs that use Reticulum, the easiest way to get
|
||||
@@ -44,7 +89,7 @@ don't use pip, but try this recipe:
|
||||
.. code::
|
||||
|
||||
# Install dependencies
|
||||
pip3 install cryptography pyserial
|
||||
pip3 install cryptography pyserial netifaces
|
||||
|
||||
# Clone repository
|
||||
git clone https://github.com/markqvist/Reticulum.git
|
||||
|
||||
@@ -2,14 +2,17 @@
|
||||
Reticulum Network Stack Manual
|
||||
******************************
|
||||
This manual aims to provide you with all the information you need to
|
||||
understand Reticulum, develop programs using it, or to participate in
|
||||
the development of Reticulum itself.
|
||||
understand Reticulum, build networks or develop programs using it, or
|
||||
to participate in the development of Reticulum itself.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 3
|
||||
|
||||
whatis
|
||||
gettingstartedfast
|
||||
using
|
||||
networks
|
||||
interfaces
|
||||
understanding
|
||||
reference
|
||||
examples
|
||||
|
||||
@@ -0,0 +1,346 @@
|
||||
|
||||
.. _interfaces-main:
|
||||
|
||||
********************
|
||||
Supported Interfaces
|
||||
********************
|
||||
|
||||
Reticulum supports using many kinds of devices as networking interfaces, and
|
||||
allows you to mix and match them in any way you choose. The number of distinct
|
||||
network topologies you can create with Reticulum is more or less endless, but
|
||||
common to them all is that you will need to define one or more *interfaces*
|
||||
for Reticulum to use.
|
||||
|
||||
The following sections describe the interfaces currently available in Reticulum,
|
||||
and gives example configurations for the respective interface types.
|
||||
|
||||
For a high-level overview of how networks can be formed over different interface
|
||||
types, have a look at the :ref:`Building Networks<networks-main>` chapter of this
|
||||
manual.
|
||||
|
||||
.. _interfaces-udp:
|
||||
|
||||
UDP Interface
|
||||
=============
|
||||
|
||||
A UDP interface can be useful for communicating over IP networks, both
|
||||
private and the internet. It can also allow broadcast communication
|
||||
over IP networks, so it can provide an easy way to enable connectivity
|
||||
with all other peers on a local area network.
|
||||
|
||||
The below example is enabled by default on new Reticulum installations,
|
||||
as it provides an easy way to get started and to test Reticulum on a
|
||||
pre-existing LAN.
|
||||
|
||||
.. code::
|
||||
|
||||
# This example enables communication with other
|
||||
# local Reticulum peers over UDP.
|
||||
|
||||
[[Default UDP Interface]]
|
||||
type = UDPInterface
|
||||
interface_enabled = True
|
||||
outgoing = True
|
||||
listen_ip = 0.0.0.0
|
||||
listen_port = 4242
|
||||
forward_ip = 255.255.255.255
|
||||
forward_port = 4242
|
||||
|
||||
# The above configuration will allow communication
|
||||
# within the local broadcast domains of all local
|
||||
# IP interfaces. This is enabled by default as an
|
||||
# easy way to get started, but you might want to
|
||||
# consider altering it to something more specific.
|
||||
|
||||
# Instead of specifying listen_ip, listen_port,
|
||||
# forward_ip and forward_port, you can also bind
|
||||
# to a specific network device like below.
|
||||
|
||||
# device = eth0
|
||||
# port = 4242
|
||||
|
||||
# Assuming the eth0 device has the address
|
||||
# 10.55.0.72/24, the above configuration would
|
||||
# be equivalent to the following manual setup.
|
||||
# Note that we are both listening and forwarding to
|
||||
# the broadcast address of the network segments.
|
||||
|
||||
# listen_ip = 10.55.0.255
|
||||
# listen_port = 4242
|
||||
# forward_ip = 10.55.0.255
|
||||
# forward_port = 4242
|
||||
|
||||
# You can of course also communicate only with
|
||||
# a single IP address
|
||||
|
||||
# listen_ip = 10.55.0.15
|
||||
# listen_port = 4242
|
||||
# forward_ip = 10.55.0.16
|
||||
# forward_port = 4242
|
||||
|
||||
.. _interfaces-tcps:
|
||||
|
||||
TCP Server Interface
|
||||
====================
|
||||
|
||||
The TCP Server interface is suitable for allowing other peers to connect over
|
||||
the Internet or private IP networks. When a TCP server interface has been
|
||||
configured, other Reticulum peers can connect to it with a TCP Client interface.
|
||||
|
||||
.. code::
|
||||
|
||||
# This example demonstrates a TCP server interface.
|
||||
# It will listen for incoming connections on the
|
||||
# specified IP address and port number.
|
||||
|
||||
[[TCP Server Interface]]
|
||||
type = TCPServerInterface
|
||||
interface_enabled = True
|
||||
outgoing = True
|
||||
|
||||
# This configuration will listen on all IP
|
||||
# interfaces on port 4242
|
||||
|
||||
listen_ip = 0.0.0.0
|
||||
listen_port = 4242
|
||||
|
||||
# Alternatively you can bind to a specific IP
|
||||
|
||||
# listen_ip = 10.0.0.88
|
||||
# listen_port = 4242
|
||||
|
||||
# Or a specific network device
|
||||
|
||||
# device = eth0
|
||||
# port = 4242
|
||||
|
||||
|
||||
.. _interfaces-tcpc:
|
||||
|
||||
TCP Client Interface
|
||||
====================
|
||||
|
||||
To connect to a TCP server interface, you would naturally use the TCP client
|
||||
interface. Many TCP Client interfaces from different peers can connect to the
|
||||
same TCP Server interface at the same time.
|
||||
|
||||
.. code::
|
||||
|
||||
# Here's an example of a TCP Client interface. The
|
||||
# target_host can either be an IP address or a hostname.
|
||||
|
||||
[[TCP Client Interface]]
|
||||
type = TCPClientInterface
|
||||
interface_enabled = True
|
||||
outgoing = True
|
||||
target_host = 127.0.0.1
|
||||
target_port = 4242
|
||||
|
||||
|
||||
.. _interfaces-rnode:
|
||||
|
||||
RNode LoRa Interface
|
||||
====================
|
||||
|
||||
To use Reticulum over LoRa, the `RNode <https://unsigned.io/rnode/>`_ interface
|
||||
can be used, and offers full control over LoRa parameters.
|
||||
|
||||
.. code::
|
||||
|
||||
# Here's an example of how to add a LoRa interface
|
||||
# using the RNode LoRa transceiver.
|
||||
|
||||
[[RNode LoRa Interface]]
|
||||
type = RNodeInterface
|
||||
|
||||
# Enable interface if you want use it!
|
||||
interface_enabled = True
|
||||
|
||||
# Allow transmit on interface. Setting
|
||||
# this to false will create a listen-
|
||||
# only interface.
|
||||
outgoing = true
|
||||
|
||||
# Serial port for the device
|
||||
port = /dev/ttyUSB0
|
||||
|
||||
# Set frequency to 867.2 MHz
|
||||
frequency = 867200000
|
||||
|
||||
# Set LoRa bandwidth to 125 KHz
|
||||
bandwidth = 125000
|
||||
|
||||
# Set TX power to 7 dBm (5 mW)
|
||||
txpower = 7
|
||||
|
||||
# Select spreading factor 8. Valid
|
||||
# range is 7 through 12, with 7
|
||||
# being the fastest and 12 having
|
||||
# the longest range.
|
||||
spreadingfactor = 8
|
||||
|
||||
# Select coding rate 5. Valid range
|
||||
# is 5 throough 8, with 5 being the
|
||||
# fastest, and 8 the longest range.
|
||||
codingrate = 5
|
||||
|
||||
# You can configure the RNode to send
|
||||
# out identification on the channel with
|
||||
# a set interval by configuring the
|
||||
# following two parameters.
|
||||
# id_callsign = MYCALL-0
|
||||
# id_interval = 600
|
||||
|
||||
# For certain homebrew RNode interfaces
|
||||
# with low amounts of RAM, using packet
|
||||
# flow control can be useful. By default
|
||||
# it is disabled.
|
||||
flow_control = False
|
||||
|
||||
.. _interfaces-serial:
|
||||
|
||||
Serial Interface
|
||||
================
|
||||
|
||||
Reticulum can be used over serial ports directly, or over any device with a
|
||||
serial port, that will transparently pass data. Useful for communicating
|
||||
directly over a wire-pair, or for using devices such as data radios and lasers.
|
||||
|
||||
.. code::
|
||||
|
||||
[[Serial Interface]]
|
||||
type = SerialInterface
|
||||
interface_enabled = True
|
||||
outgoing = True
|
||||
|
||||
# Serial port for the device
|
||||
port = /dev/ttyUSB0
|
||||
|
||||
# Set the serial baud-rate and other
|
||||
# configuration parameters.
|
||||
speed = 115200
|
||||
databits = 8
|
||||
parity = none
|
||||
stopbits = 1
|
||||
|
||||
.. _interfaces-kiss:
|
||||
|
||||
KISS Interface
|
||||
==============
|
||||
|
||||
With the KISS interface, you can use Reticulum over a variety of packet
|
||||
radio modems and TNCs, including `OpenModem <https://unsigned.io/openmodem/>`_.
|
||||
KISS interfaces can also be configured to periodically send out beacons
|
||||
for station identification purposes.
|
||||
|
||||
.. code::
|
||||
|
||||
[[Packet Radio KISS Interface]]
|
||||
type = KISSInterface
|
||||
interface_enabled = True
|
||||
outgoing = true
|
||||
|
||||
# Serial port for the device
|
||||
port = /dev/ttyUSB1
|
||||
|
||||
# Set the serial baud-rate and other
|
||||
# configuration parameters.
|
||||
speed = 115200
|
||||
databits = 8
|
||||
parity = none
|
||||
stopbits = 1
|
||||
|
||||
# Set the modem preamble.
|
||||
preamble = 150
|
||||
|
||||
# Set the modem TX tail.
|
||||
txtail = 10
|
||||
|
||||
# Configure CDMA parameters. These
|
||||
# settings are reasonable defaults.
|
||||
persistence = 200
|
||||
slottime = 20
|
||||
|
||||
# You can configure the interface to send
|
||||
# out identification on the channel with
|
||||
# a set interval by configuring the
|
||||
# following two parameters. The KISS
|
||||
# interface will only ID if the set
|
||||
# interval has elapsed since it's last
|
||||
# actual transmission. The interval is
|
||||
# configured in seconds.
|
||||
# This option is commented out and not
|
||||
# used by default.
|
||||
# id_callsign = MYCALL-0
|
||||
# id_interval = 600
|
||||
|
||||
# Whether to use KISS flow-control.
|
||||
# This is useful for modems that have
|
||||
# a small internal packet buffer, but
|
||||
# support packet flow control instead.
|
||||
flow_control = false
|
||||
|
||||
.. _interfaces-ax25:
|
||||
|
||||
AX.25 KISS Interface
|
||||
====================
|
||||
|
||||
If you're using Reticulum on amateur radio spectrum, you might want to
|
||||
use the AX.25 KISS interface. This way, Reticulum will automatically
|
||||
encapsulate it's traffic in AX.25 and also identify your stations
|
||||
transmissions with your callsign and SSID.
|
||||
|
||||
Only do this if you really need to! Reticulum doesn't need the AX.25
|
||||
layer for anything, and it incurs extra overhead on every packet to
|
||||
encapsulate in AX.25.
|
||||
|
||||
A more efficient way is to use the plain KISS interface with the
|
||||
beaconing functionality described above.
|
||||
|
||||
.. code::
|
||||
|
||||
[[Packet Radio AX.25 KISS Interface]]
|
||||
type = AX25KISSInterface
|
||||
|
||||
# Set the station callsign and SSID
|
||||
callsign = NO1CLL
|
||||
ssid = 0
|
||||
|
||||
# Enable interface if you want use it!
|
||||
interface_enabled = True
|
||||
|
||||
# Allow transmit on interface.
|
||||
outgoing = True
|
||||
|
||||
# Serial port for the device
|
||||
port = /dev/ttyUSB2
|
||||
|
||||
# Set the serial baud-rate and other
|
||||
# configuration parameters.
|
||||
speed = 115200
|
||||
databits = 8
|
||||
parity = none
|
||||
stopbits = 1
|
||||
|
||||
# Set the modem preamble. A 150ms
|
||||
# preamble should be a reasonable
|
||||
# default, but may need to be
|
||||
# increased for radios with slow-
|
||||
# opening squelch and long TX/RX
|
||||
# turnaround
|
||||
preamble = 150
|
||||
|
||||
# Set the modem TX tail. In most
|
||||
# cases this should be kept as low
|
||||
# as possible to not waste airtime.
|
||||
txtail = 10
|
||||
|
||||
# Configure CDMA parameters. These
|
||||
# settings are reasonable defaults.
|
||||
persistence = 200
|
||||
slottime = 20
|
||||
|
||||
# Whether to use KISS flow-control.
|
||||
# This is useful for modems with a
|
||||
# small internal packet buffer.
|
||||
flow_control = false
|
||||
@@ -0,0 +1,150 @@
|
||||
.. _networks-main:
|
||||
|
||||
*****************
|
||||
Building Networks
|
||||
*****************
|
||||
|
||||
This chapter will provide you with the knowledge needed to build networks with
|
||||
Reticulum, which can often be easier than using traditional stacks, since you
|
||||
don't have to worry about coordinating addresses, subnets and routing for an
|
||||
entire network that you might not know how will evolve in the future. With
|
||||
Reticulum, you can simply add more segments to your network when it becomes
|
||||
necesarry, and Reticulum will handle the convergence of the entire network
|
||||
automatically.
|
||||
|
||||
Concepts & Overview
|
||||
--------------------
|
||||
|
||||
There are important points that need to be kept in mind when building networks
|
||||
with Reticulum:
|
||||
|
||||
* | In a Reticulum network, any node can autonomously generate as many adresses
|
||||
(called *destinations* in Reticulum terminology) as it needs, which become
|
||||
globally reachable to the rest of the network. There is no central point of
|
||||
control over the adress space.
|
||||
|
||||
* | Reticulum was designed to handle both very small, and very large networks.
|
||||
While the adress space can support billions of endpoints, Reticulum is
|
||||
also very useful when just a few devices needs to communicate.
|
||||
|
||||
* | Reticulum provides sender/initiator anonymity by default. There is no way
|
||||
to filter traffic or discriminate it based on the source of the traffic.
|
||||
|
||||
* | All traffic is encrypted using ephemeral keys generated by an Elliptic Curve
|
||||
Diffie-Hellman key exchange on Curve25519. There is no way to inspect traffic
|
||||
contents, and no way to prioritise or throttle certain kinds of traffic.
|
||||
All transport and routing layers are thus completely agnostic to traffic type,
|
||||
and will pass all traffic equally.
|
||||
|
||||
* | Reticulum can function both with and without infrastructure. When *transport
|
||||
nodes* are available, they can route traffic over multiple hops for other
|
||||
nodes, and will function as a distributed cryptographic keystore. When there
|
||||
is no transport nodes available, all nodes that are within communication range
|
||||
can still communicate.
|
||||
|
||||
* | Every node can become a transport node, simply by enabling it in it's
|
||||
configuration, but there is no need for every node on the network to be a
|
||||
transport node. Letting every node be a transport node will in most cases
|
||||
degrade the performance and reliability of the network.
|
||||
|
||||
In general terms, if a node is stationary, well-connected and kept running
|
||||
most of the time, it is a good candidate to be a transport node. For optimal
|
||||
performance, a network should contain the amount of transport nodes that
|
||||
provides connectivity to the intended area / topography, and not many more
|
||||
than that.
|
||||
|
||||
|
||||
Reticulum allows you to mix very different kinds of networking mediums into a
|
||||
unified mesh, or to keep everything within one medium. You could build a "virtual
|
||||
network" running entirely over the Internet, where all nodes communicate over TCP
|
||||
and UDP "channels". You could also build such a network using MQTT or ZeroMQ as
|
||||
the underlying carrier for Reticulum.
|
||||
|
||||
However, most real-world networks will probably involve either some form of
|
||||
wireless or direct hardline communications. To allow Reticulum to communicate
|
||||
over any type of medium, you must specify it in the configuration file, by default
|
||||
located at ``~/.reticulum/config``. See the :ref:`Supported Interfaces<interfaces-main>`
|
||||
chapter of this manual for interface configuration examples.
|
||||
|
||||
Any number of interfaces can be configured, and Reticulum will automatically
|
||||
decide which are suitable to use in any given situation, depending on where
|
||||
traffic needs to flow.
|
||||
|
||||
Example Scenarios
|
||||
-----------------
|
||||
|
||||
This section illustrates a few example scenarios, and how they would, in general
|
||||
terms, be planned, implemented and configured.
|
||||
|
||||
Interconnected LoRa Sites
|
||||
=========================
|
||||
|
||||
An organisation wants to provide communication and information services to it's
|
||||
members, which are located mainly in three separate areas. Three suitable hill-top
|
||||
locations are found, where the organisation can install equipment: Site A, B and C.
|
||||
|
||||
Since the amount of data that needs to be exchanged between users is mainly text-
|
||||
based, the bandwidth requirements are low, and LoRa radios are chosen to connect
|
||||
users to the network.
|
||||
|
||||
Due to the hill-top locations found, there is radio line-of-sight between site A
|
||||
and B, and also between site B and C. Because of this, the organisation does not
|
||||
need to use the Internet to interconnect the sites, but purchases four Point-to-Point
|
||||
WiFi based radios for interconnecting the sites.
|
||||
|
||||
At each site, a Raspberry Pi is installed to function as a gateway. A LoRa radio
|
||||
is connected to the Pi with a USB cable, and the WiFi radio is connected to the
|
||||
ethernet port of the Pi. At site B, two WiFi radios are needed to be able to reach
|
||||
both site A and site C, so an extra ethernet adapter is connected to the Pi in
|
||||
this location.
|
||||
|
||||
Once the hardware has been installed, Reticulum is installed on all the Pis, and at
|
||||
site A and C, one interface is added for the LoRa radio, as well as one for the WiFi
|
||||
radio. At site B, an interface for the LoRa radio, and one interface for each WiFi
|
||||
radio is added to the Reticulum configuration file. The transport node option is
|
||||
enabled in the configuration of all three gateways.
|
||||
|
||||
The network is now operational, and ready to serve users across all three areas.
|
||||
The organisation prepares a LoRa radio that is supplied to the end users, along
|
||||
with a Reticulum configuration file, that contains the right parameters for
|
||||
communicating with the LoRa radios installed at the gateway sites.
|
||||
|
||||
Once users connect to the network, anyone will be able to communicate with anyone
|
||||
else across all three sites.
|
||||
|
||||
Bridging Over the Internet
|
||||
==========================
|
||||
|
||||
As the organisation grows, several new communities form in places too far away
|
||||
from the core network to be reachable over WiFi links. New gateways similar to those
|
||||
previously installed are set up for the new communities at the new sites D and E, but
|
||||
they are islanded from the core network, and only serve the local users.
|
||||
|
||||
After investigating the options, it is found that it is possible to install an
|
||||
Internet connection at site A, and an interface on the Internet connection is
|
||||
configured for Reticulum on the Raspberry Pi at site A.
|
||||
|
||||
A member of the organisation at site D, named Dori, is willing to help by sharing
|
||||
the Internet connection she already has in her home, and is able to leave a Raspberry
|
||||
Pi running. A new Reticulum interface is configured on her Pi, connecting to the newly
|
||||
enabled Internet interface on the gateway at site A. Dori is now connected to both
|
||||
all the nodes at her own local site (through the hill-top LoRa gateway), and all the
|
||||
combined users of sites A, B and C. She then enables transport on her node, and
|
||||
traffic from site D can now reach everyone at site A, B and C, and vice versa.
|
||||
|
||||
Growth and Convergence
|
||||
======================
|
||||
|
||||
As the organisation grows, more gateways are added to keep up with the growing user
|
||||
base. Some local gateways even add VHF radios and packet modems to reach outlying users
|
||||
and communities that are out of reach for the LoRa radios and WiFi backhauls.
|
||||
|
||||
As more sites, gateways and users are connected, the amount of coordination required
|
||||
is kept to a minimum. If one community wants to add connectivity to the next one
|
||||
over, it can simply be done without having to involve everyone or coordinate address
|
||||
space or routing tables.
|
||||
|
||||
With the added geographical coverage, the operators at site A one day find that
|
||||
the original internet bridged interfaces are no longer utilised. The network has
|
||||
converged to be completely self-connected, and the sites that were once poorly
|
||||
connected outliers are now an integral part of the network.
|
||||
@@ -39,7 +39,7 @@ Destination
|
||||
Packet
|
||||
------
|
||||
|
||||
.. autoclass:: RNS.Packet
|
||||
.. autoclass:: RNS.Packet(destination, data, create_receipt = True)
|
||||
:members:
|
||||
|
||||
.. _api-packetreceipt:
|
||||
@@ -47,7 +47,7 @@ Packet
|
||||
Packet Receipt
|
||||
--------------
|
||||
|
||||
.. autoclass:: RNS.PacketReceipt
|
||||
.. autoclass:: RNS.PacketReceipt()
|
||||
:members:
|
||||
|
||||
.. _api-link:
|
||||
@@ -55,7 +55,15 @@ Packet Receipt
|
||||
Link
|
||||
----
|
||||
|
||||
.. autoclass:: RNS.Link
|
||||
.. autoclass:: RNS.Link(destination, established_callback=None, closed_callback = None)
|
||||
:members:
|
||||
|
||||
.. _api-requestreceipt:
|
||||
|
||||
Request Receipt
|
||||
---------------
|
||||
|
||||
.. autoclass:: RNS.RequestReceipt()
|
||||
:members:
|
||||
|
||||
.. _api-resource:
|
||||
@@ -63,7 +71,7 @@ Link
|
||||
Resource
|
||||
--------
|
||||
|
||||
.. autoclass:: RNS.Resource
|
||||
.. autoclass:: RNS.Resource(data, link, advertise=True, auto_compress=True, callback=None, progress_callback=None, timeout=None)
|
||||
:members:
|
||||
|
||||
.. _api-transport:
|
||||
|
||||
|
Before Width: | Height: | Size: 108 KiB |
|
Before Width: | Height: | Size: 118 KiB |
|
Before Width: | Height: | Size: 124 KiB |
|
After Width: | Height: | Size: 79 KiB |
|
After Width: | Height: | Size: 81 KiB |
|
After Width: | Height: | Size: 86 KiB |
|
After Width: | Height: | Size: 84 KiB |
|
After Width: | Height: | Size: 68 KiB |
@@ -52,7 +52,7 @@ by using multiple hops).
|
||||
Goals
|
||||
=====
|
||||
|
||||
To be as widely usable and easy to implement as possible, the following goals have been used to
|
||||
To be as widely usable and easy to use as possible, the following goals have been used to
|
||||
guide the design of Reticulum:
|
||||
|
||||
|
||||
@@ -429,7 +429,7 @@ terms of bandwidth, so it can be used just for a short exchange, and then recrea
|
||||
also rotate encryption keys. The link can also be kept alive for longer periods of time, if this is
|
||||
more suitable to the application. The procedure also inserts the *link id* , a hash calculated from the link request packet, into the memory of forwarding nodes, which means that the communicating nodes can thereafter reach each other simply by referring to this *link id*.
|
||||
|
||||
The combined bandwidth cost of setting up a link is 3 packets totalling 240 bytes (more info in the
|
||||
The combined bandwidth cost of setting up a link is 3 packets totalling 237 bytes (more info in the
|
||||
:ref:`Binary Packet Format<understanding-packetformat>` section). The amount of bandwidth used on keeping
|
||||
a link open is practically negligible, at 0.62 bits per second. Even on a slow 1200 bits per second packet
|
||||
radio channel, 100 concurrent links will still leave 95% channel capacity for actual data.
|
||||
@@ -701,5 +701,5 @@ Binary Packet Format
|
||||
- Announce : 151 bytes
|
||||
- Link Request : 77 bytes
|
||||
- Link Proof : 77 bytes
|
||||
- Link RTT packet : 86 bytes
|
||||
- Link RTT packet : 83 bytes
|
||||
- Link keepalive : 14 bytes
|
||||
@@ -0,0 +1,165 @@
|
||||
.. _using-main:
|
||||
|
||||
******************************
|
||||
Using Reticulum on Your System
|
||||
******************************
|
||||
|
||||
Reticulum is not installed as a driver or kernel module, as one might expect
|
||||
of a networking stack. Instead, Reticulum is distributed as a Python module.
|
||||
This means that no special privileges are required to install or use it.
|
||||
Any program or application that uses Reticulum will automatically load and
|
||||
initialise Reticulum when it starts.
|
||||
|
||||
In many cases, this approach is sufficient. When any program needs to use
|
||||
Reticulum, it is loaded, initialised, interfaces are brought up, and the
|
||||
program can now communicate over Reticulum. If another program starts up
|
||||
and also wants access to the same Reticulum network, the instance is simply
|
||||
shared. This works for any number of programs running concurrently, and is
|
||||
very easy to use, but depending on your use case, there are other options.
|
||||
|
||||
Included Utility Programs
|
||||
-------------------------
|
||||
|
||||
If you often use Reticulum from several different programs, or simply want
|
||||
Reticulum to stay available all the time, for example if you are hosting
|
||||
a transport node, you might want to run Reticulum as a separate service that
|
||||
other programs, applications and services can utilise.
|
||||
|
||||
The rnsd Utility
|
||||
================
|
||||
|
||||
To do so is very easy. Simply run the included ``rnsd`` command. When ``rnsd``
|
||||
is running, it will keep all configured interfaces open, handle transport if
|
||||
it is enabled, and allow any other programs to immediately utilise the
|
||||
Reticulum network it is configured for.
|
||||
|
||||
You can even run multiple instances of rnsd with different configurations on
|
||||
the same system.
|
||||
|
||||
.. code:: text
|
||||
|
||||
# Install Reticulum
|
||||
pip3 install rns
|
||||
|
||||
# Run rnsd
|
||||
rnsd
|
||||
|
||||
.. code:: text
|
||||
|
||||
usage: rnsd [-h] [--config CONFIG] [-v] [-q] [--version]
|
||||
|
||||
Reticulum Network Stack Daemon
|
||||
|
||||
optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
--config CONFIG path to alternative Reticulum config directory
|
||||
-v, --verbose
|
||||
-q, --quiet
|
||||
--version show program's version number and exit
|
||||
|
||||
|
||||
The rnstatus Utility
|
||||
====================
|
||||
|
||||
Using the ``rnstatus`` utility, you can view the status of configured Reticulum
|
||||
interfaces, similar to the ``ifconfig`` program.
|
||||
|
||||
.. code:: text
|
||||
|
||||
# Run rnstatus
|
||||
rnstatus
|
||||
|
||||
# Example output
|
||||
Shared Instance[37428]
|
||||
Status: Up
|
||||
Connected applications: 1
|
||||
RX: 1.13 KB
|
||||
TX: 1.07 KB
|
||||
|
||||
UDPInterface[Default UDP Interface/0.0.0.0:4242]
|
||||
Status: Up
|
||||
RX: 1.01 KB
|
||||
TX: 1.01 KB
|
||||
|
||||
TCPInterface[RNS Testnet Frankfurt/frankfurt.rns.unsigned.io:4965]
|
||||
Status: Up
|
||||
RX: 1.37 KB
|
||||
TX: 9.02 KB
|
||||
|
||||
.. code:: text
|
||||
|
||||
usage: rnsd [-h] [--config CONFIG] [-v] [-q] [--version]
|
||||
|
||||
Reticulum Network Stack Daemon
|
||||
|
||||
optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
--config CONFIG path to alternative Reticulum config directory
|
||||
-v, --verbose
|
||||
-q, --quiet
|
||||
--version show program's version number and exit
|
||||
|
||||
|
||||
The rnpath Utility
|
||||
====================
|
||||
|
||||
With the ``rnpath`` utility, you can look up and view paths for
|
||||
destinations on the Reticulum network.
|
||||
|
||||
.. code:: text
|
||||
|
||||
# Run rnpath
|
||||
rnpath eca6f4e4dc26ae329e61
|
||||
|
||||
# Example output
|
||||
Path found, destination <eca6f4e4dc26ae329e61> is 4 hops away via <56b115c30cd386cad69c> on TCPInterface[Testnet/frankfurt.rns.unsigned.io:4965]
|
||||
|
||||
.. code:: text
|
||||
|
||||
usage: rnpath.py [-h] [--config CONFIG] [--version] [-v] [destination]
|
||||
|
||||
Reticulum Path Discovery Utility
|
||||
|
||||
positional arguments:
|
||||
destination hexadecimal hash of the destination
|
||||
|
||||
optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
--config CONFIG path to alternative Reticulum config directory
|
||||
--version show program's version number and exit
|
||||
-v, --verbose
|
||||
|
||||
|
||||
The rnprobe Utility
|
||||
====================
|
||||
|
||||
The ``rnprobe`` utility lets you probe a destination for connectivity, similar
|
||||
to the ``ping`` program. Please note that probes will only be answered if the
|
||||
specified destination is configured to send proofs for received packets. Many
|
||||
destinations will not have this option enabled, and will not be probable.
|
||||
|
||||
.. code:: text
|
||||
|
||||
# Run rnprobe
|
||||
python3 -m RNS.Utilities.rnprobe example_utilities.echo.request 9382f334de63217a4278
|
||||
|
||||
# Example output
|
||||
Sent 16 byte probe to <9382f334de63217a4278>
|
||||
Valid reply received from <9382f334de63217a4278>
|
||||
Round-trip time is 38.469 milliseconds over 2 hops
|
||||
|
||||
.. code:: text
|
||||
|
||||
usage: rnprobe.py [-h] [--config CONFIG] [--version] [-v] [full_name] [destination_hash]
|
||||
|
||||
Reticulum Probe Utility
|
||||
|
||||
positional arguments:
|
||||
full_name full destination name in dotted notation
|
||||
destination_hash hexadecimal hash of the destination
|
||||
|
||||
optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
--config CONFIG path to alternative Reticulum config directory
|
||||
--version show program's version number and exit
|
||||
-v, --verbose
|
||||
@@ -6,9 +6,9 @@ Reticulum is a cryptography-based networking stack for wide-area networks built
|
||||
|
||||
Reticulum allows you to build very wide-area networks with off-the-shelf tools, and offers end-to-end encryption, autoconfiguring cryptographically backed multi-hop transport, efficient addressing, unforgeable packet acknowledgements and more.
|
||||
|
||||
Reticulum is a complete networking stack, and does not use IP or higher layers, although it is easy to utilise IP (with TCP or UDP) as the underlying carrier for Reticulum. It is therefore trivial to tunnel Reticulum over the Internet or private IP networks. Reticulum is built directly on cryptographic principles, allowing resilience and stable functionality in open and trustless networks.
|
||||
Reticulum is a complete networking stack, and does not need IP or higher layers, although it is easy to utilise IP (with TCP or UDP) as the underlying carrier for Reticulum. It is therefore trivial to tunnel Reticulum over the Internet or private IP networks. Reticulum is built directly on cryptographic principles, allowing resilience and stable functionality in open and trustless networks.
|
||||
|
||||
No kernel modules or drivers are required. Reticulum runs completely in userland, and can run on practically any system that runs Python 3.
|
||||
No kernel modules or drivers are required. Reticulum runs completely in userland, and can run on practically any system that runs Python 3. Reticulum runs well even on small single-board computers like the Pi Zero.
|
||||
|
||||
|
||||
Current Status
|
||||
@@ -18,7 +18,7 @@ Reticulum should currently be considered beta software. All core protocol featur
|
||||
|
||||
Caveat Emptor
|
||||
==============
|
||||
Reticulum is an experimental networking stack, and should be considered as such. While it has been built with cryptography best-practices very foremost in mind, it has not been externally security audited, and there could very well be privacy-breaking bugs. To be considered even remotely secure, Reticulum needs a very thourough security review by independt cryptographers and security researchers. If you want to help out, or help sponsor an audit, please do get in touch.
|
||||
Reticulum is an experimental networking stack, and should be considered as such. While it has been built with cryptography best-practices very foremost in mind, it has not been externally security audited, and there could very well be privacy-breaking bugs. To be considered secure, Reticulum needs a thourough security review by independt cryptographers and security researchers. If you want to help out, or help sponsor an audit, please do get in touch.
|
||||
|
||||
|
||||
What does Reticulum Offer?
|
||||
@@ -27,11 +27,15 @@ What does Reticulum Offer?
|
||||
|
||||
* Fully self-configuring multi-hop routing
|
||||
|
||||
* Complete initiator anonymity, communicate without revealing your identity
|
||||
|
||||
* Asymmetric X25519 encryption and Ed25519 signatures as a basis for all communication
|
||||
|
||||
* Forward Secrecy with ephemereal Elliptic Curve Diffie-Hellman keys on Curve25519
|
||||
|
||||
* Reticulum uses the `Fernet <https://github.com/fernet/spec/blob/master/Spec.md>`_ specification for encryption
|
||||
* Reticulum uses the `Fernet <https://github.com/fernet/spec/blob/master/Spec.md>`_ specification for on-the-wire / over-the-air encryption
|
||||
|
||||
* All keys are ephemeral and derived from an ECDH key exchange on Curve25519
|
||||
|
||||
* AES-128 in CBC mode with PKCS7 padding
|
||||
|
||||
@@ -39,8 +43,6 @@ What does Reticulum Offer?
|
||||
|
||||
* IVs are generated through os.urandom()
|
||||
|
||||
* Keys are ephemeral and derived from an ECDH key exchange on Curve25519
|
||||
|
||||
* Unforgeable packet delivery confirmations
|
||||
|
||||
* A variety of supported interface types
|
||||
@@ -57,7 +59,7 @@ What does Reticulum Offer?
|
||||
|
||||
* Efficient link establishment
|
||||
|
||||
* Total bandwidth cost of setting up a link is only 3 packets, totalling 240 bytes
|
||||
* Total bandwidth cost of setting up a link is only 3 packets, totalling 237 bytes
|
||||
|
||||
* Low cost of keeping links open at only 0.62 bits per second
|
||||
|
||||
@@ -87,8 +89,8 @@ configured, Reticulum will take care of the rest, and any device on the WiFi
|
||||
network can communicate with nodes on the LoRa and packet radio sides of the
|
||||
network, and vice versa.
|
||||
|
||||
Supported Interface Types and Devices
|
||||
=====================================
|
||||
Interface Types and Devices
|
||||
===========================
|
||||
Reticulum implements a range of generalised interface types that covers most of the communications hardware that Reticulum can run over. If your hardware is not supported, it's relatively simple to implement an interface class. Currently, the following interfaces are supported:
|
||||
|
||||
* Any ethernet device
|
||||
@@ -101,4 +103,6 @@ Reticulum implements a range of generalised interface types that covers most of
|
||||
|
||||
* TCP over IP networks
|
||||
|
||||
* UDP over IP networks
|
||||
* UDP over IP networks
|
||||
|
||||
For a full list and more details, see the :ref:`Supported Interfaces<interfaces-main>` chapter.
|
||||
@@ -20,6 +20,15 @@ setuptools.setup(
|
||||
"License :: OSI Approved :: MIT License",
|
||||
"Operating System :: OS Independent",
|
||||
],
|
||||
entry_points= {
|
||||
'console_scripts': [
|
||||
'rnsd=RNS.Utilities.rnsd:main',
|
||||
'rnstatus=RNS.Utilities.rnstatus:main',
|
||||
'rnprobe=RNS.Utilities.rnprobe:main',
|
||||
'rnpath=RNS.Utilities.rnpath:main',
|
||||
|
||||
]
|
||||
},
|
||||
install_requires=['cryptography>=3.4.7', 'pyserial', 'netifaces>=0.10.4'],
|
||||
python_requires='>=3.5',
|
||||
)
|
||||
python_requires='>=3.6',
|
||||
)
|
||||
|
||||