mirror of
https://github.com/markqvist/Reticulum.git
synced 2026-06-23 04:16:12 -07:00
Compare commits
69 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4253175627 | |||
| 81158c27e4 | |||
| eeb424ecee | |||
| 0273328b23 | |||
| 20dfbcf0cc | |||
| c96e067839 | |||
| 9ff37543f3 | |||
| 974ca48cb4 | |||
| 167d48c8ce | |||
| f253b08774 | |||
| 1c768e9219 | |||
| df39cff520 | |||
| e1e31692d7 | |||
| 293a834c35 | |||
| 1bbdd9b3f5 | |||
| d4b6b6ee59 | |||
| fca03bbdce | |||
| 29aa4f9315 | |||
| d5cac30a85 | |||
| 6500bc7390 | |||
| 81fed10855 | |||
| a39876106b | |||
| 90b39774d1 | |||
| 006c70cd09 | |||
| 02945f960d | |||
| e401ec870d | |||
| 90174fcc28 | |||
| c18ebed419 | |||
| 1d180a96f6 | |||
| 4241990690 | |||
| 3d49076602 | |||
| 2e0dd278b6 | |||
| b432a7c7de | |||
| c0383fa2b0 | |||
| 98d66e2ba5 | |||
| 2e4fcc659c | |||
| 8fe7c19c59 | |||
| 27b46c9e89 | |||
| 70a3637a98 | |||
| 2e0476e6b9 | |||
| 39911190aa | |||
| 9e9606b8cf | |||
| 8be1acee0a | |||
| ba39a69175 | |||
| a692d29c90 | |||
| 7092589388 | |||
| 2d3969aa3d | |||
| 1443f4c104 | |||
| d2232f19ba | |||
| c44c6f9086 | |||
| 259c2aa397 | |||
| 10854bfdbc | |||
| f5236878b0 | |||
| daf72f4237 | |||
| 652b884d72 | |||
| ea3716f48e | |||
| 165e620043 | |||
| 58f43b163e | |||
| 448ea8ceb5 | |||
| f7e8fc4719 | |||
| 1d6c877b4c | |||
| c3dcd9366d | |||
| 8d01586a5a | |||
| 3e5f613f66 | |||
| 614a139cd4 | |||
| 1cf6570c2d | |||
| d207cbcd9c | |||
| 18b20f2d8d | |||
| c37533d2c7 |
@@ -3,6 +3,7 @@
|
||||
testutils
|
||||
TODO
|
||||
Examples/RNS
|
||||
RNS/Utilities/RNS
|
||||
build
|
||||
dist
|
||||
docs/build
|
||||
|
||||
@@ -15,7 +15,7 @@ import RNS
|
||||
APP_NAME = "example_utilities"
|
||||
|
||||
# We initialise two lists of strings to use as app_data
|
||||
fruits = ["Peach", "Quince", "Date palm", "Tangerine", "Pomelo", "Carambola", "Grape"]
|
||||
fruits = ["Peach", "Quince", "Date", "Tangerine", "Pomelo", "Carambola", "Grape"]
|
||||
noble_gases = ["Helium", "Neon", "Argon", "Krypton", "Xenon", "Radon", "Oganesson"]
|
||||
|
||||
# This initialisation is executed when the program is started
|
||||
|
||||
+49
-2
@@ -22,6 +22,8 @@ APP_NAME = "example_utilities"
|
||||
# This initialisation is executed when the users chooses
|
||||
# to run as a server
|
||||
def server(configpath):
|
||||
global reticulum
|
||||
|
||||
# We must first initialise Reticulum
|
||||
reticulum = RNS.Reticulum(configpath)
|
||||
|
||||
@@ -78,11 +80,32 @@ def announceLoop(destination):
|
||||
|
||||
|
||||
def server_callback(message, packet):
|
||||
global reticulum
|
||||
|
||||
# Tell the user that we received an echo request, and
|
||||
# that we are going to send a reply to the requester.
|
||||
# Sending the proof is handled automatically, since we
|
||||
# set up the destination to prove all incoming packets.
|
||||
RNS.log("Received packet from echo client, proof sent")
|
||||
|
||||
reception_stats = ""
|
||||
if reticulum.is_connected_to_shared_instance:
|
||||
reception_rssi = reticulum.get_packet_rssi(packet.packet_hash)
|
||||
reception_snr = reticulum.get_packet_snr(packet.packet_hash)
|
||||
|
||||
if reception_rssi != None:
|
||||
reception_stats += " [RSSI "+str(reception_rssi)+" dBm]"
|
||||
|
||||
if reception_snr != None:
|
||||
reception_stats += " [SNR "+str(reception_snr)+" dBm]"
|
||||
|
||||
else:
|
||||
if packet.rssi != None:
|
||||
reception_stats += " [RSSI "+str(packet.rssi)+" dBm]"
|
||||
|
||||
if packet.snr != None:
|
||||
reception_stats += " [SNR "+str(packet.snr)+" dB]"
|
||||
|
||||
RNS.log("Received packet from echo client, proof sent"+reception_stats)
|
||||
|
||||
|
||||
##########################################################
|
||||
@@ -92,6 +115,8 @@ def server_callback(message, packet):
|
||||
# This initialisation is executed when the users chooses
|
||||
# to run as a client
|
||||
def client(destination_hexhash, configpath, timeout=None):
|
||||
global reticulum
|
||||
|
||||
# We need a binary representation of the destination
|
||||
# hash that was entered on the command line
|
||||
try:
|
||||
@@ -188,6 +213,8 @@ def client(destination_hexhash, configpath, timeout=None):
|
||||
# This function is called when our reply destination
|
||||
# receives a proof packet.
|
||||
def packet_delivered(receipt):
|
||||
global reticulum
|
||||
|
||||
if receipt.status == RNS.PacketReceipt.DELIVERED:
|
||||
rtt = receipt.get_rtt()
|
||||
if (rtt >= 1):
|
||||
@@ -197,10 +224,30 @@ def packet_delivered(receipt):
|
||||
rtt = round(rtt*1000, 3)
|
||||
rttstring = str(rtt)+" milliseconds"
|
||||
|
||||
reception_stats = ""
|
||||
if reticulum.is_connected_to_shared_instance:
|
||||
reception_rssi = reticulum.get_packet_rssi(receipt.proof_packet.packet_hash)
|
||||
reception_snr = reticulum.get_packet_snr(receipt.proof_packet.packet_hash)
|
||||
|
||||
if reception_rssi != None:
|
||||
reception_stats += " [RSSI "+str(reception_rssi)+" dBm]"
|
||||
|
||||
if reception_snr != None:
|
||||
reception_stats += " [SNR "+str(reception_snr)+" dB]"
|
||||
|
||||
else:
|
||||
if receipt.proof_packet != None:
|
||||
if receipt.proof_packet.rssi != None:
|
||||
reception_stats += " [RSSI "+str(receipt.proof_packet.rssi)+" dBm]"
|
||||
|
||||
if receipt.proof_packet.snr != None:
|
||||
reception_stats += " [SNR "+str(receipt.proof_packet.snr)+" dB]"
|
||||
|
||||
RNS.log(
|
||||
"Valid reply received from "+
|
||||
RNS.prettyhexrep(receipt.destination.hash)+
|
||||
", round-trip time is "+rttstring
|
||||
", round-trip time is "+rttstring+
|
||||
reception_stats
|
||||
)
|
||||
|
||||
# This function is called if a packet times out.
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
all: release
|
||||
|
||||
clean:
|
||||
@echo Cleaning...
|
||||
-rm -r ./build
|
||||
-rm -r ./dist
|
||||
|
||||
remove_symlinks:
|
||||
@echo Removing symlinks for build...
|
||||
-rm Examples/RNS
|
||||
-rm RNS/Utilities/RNS
|
||||
|
||||
create_symlinks:
|
||||
@echo Creating symlinks...
|
||||
-ln -s ../RNS ./Examples/
|
||||
-ln -s ../../RNS ./RNS/Utilities/
|
||||
|
||||
build_wheel:
|
||||
python3 setup.py sdist bdist_wheel
|
||||
|
||||
release: remove_symlinks build_wheel create_symlinks
|
||||
|
||||
upload:
|
||||
@echo Uploading to PyPi...
|
||||
twine upload dist/*
|
||||
@@ -45,7 +45,7 @@ If you want to quickly get an idea of what Reticulum can do, take a look at the
|
||||
- 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.
|
||||
Over practically any medium that can support at least a half-duplex channel with 500 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.
|
||||
|
||||
An open-source LoRa-based interface called [RNode](https://unsigned.io/projects/rnode/) has been designed specifically for use with Reticulum. It is possible to build yourself, or it can be purchased as a complete transceiver that just needs a USB connection to the host.
|
||||
|
||||
@@ -53,6 +53,22 @@ Reticulum can also be encapsulated over existing IP networks, so there's nothing
|
||||
|
||||
As an example, it's possible to set up a Raspberry Pi connected to both a LoRa radio, a packet radio TNC and a WiFi network. Once the interfaces are 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.
|
||||
|
||||
## How do I get started?
|
||||
The best way to get started with the Reticulum Network Stack depends on what
|
||||
you want to do. For full details and examples, have a look at the [Getting Started Fast](https://markqvist.github.io/Reticulum/manual/gettingstartedfast.html) section of the [Reticulum Manual](https://markqvist.github.io/Reticulum/manual/).
|
||||
|
||||
To simply install Reticulum and related utilities on your system, the easiest way is via pip:
|
||||
|
||||
```bash
|
||||
pip3 install rns
|
||||
```
|
||||
|
||||
You can then start any program that uses Reticulum, or start Reticulum as a system service with [the rnsd utility](https://markqvist.github.io/Reticulum/manual/using.html#the-rnsd-utility).
|
||||
|
||||
When first started, Reticulum will create a default configuration file, providing basic connectivity to other Reticulum peers. The default config file contains examples for using Reticulum with LoRa transceivers (specifically [RNode](https://unsigned.io/projects/rnode/)), packet radio TNCs/modems, TCP and UDP.
|
||||
|
||||
You can use the examples in the config file to expand communication over many mediums such as packet radio or LoRa (with [RNode](https://unsigned.io/projects/rnode/)), serial ports, or over fast IP links and the Internet using the UDP and TCP interfaces. For more detailed examples, take a look at the [Supported Interfaces](https://markqvist.github.io/Reticulum/manual/interfaces.html) section of the [Reticulum Manual](https://markqvist.github.io/Reticulum/manual/).
|
||||
|
||||
## Current Status
|
||||
Reticulum should currently be considered beta software. All core protocol features are implemented and functioning, but additions will probably occur as real-world use is explored. There will be bugs. The API and wire-format can be considered relatively stable at the moment, but could change if warranted.
|
||||
|
||||
@@ -71,10 +87,12 @@ Reticulum implements a range of generalised interface types that covers most of
|
||||
|
||||
## Feature Roadmap
|
||||
- Stream mode for links
|
||||
- Globally routable multicast
|
||||
- More interface types for even broader compatibility
|
||||
- ESP32 devices (ESP-Now, Bluetooth, etc.)
|
||||
- More LoRa transceivers
|
||||
- AT-compatible modems
|
||||
- AWDL / OWL
|
||||
- CAN-bus
|
||||
- ZeroMQ
|
||||
- MQTT
|
||||
@@ -82,23 +100,17 @@ Reticulum implements a range of generalised interface types that covers most of
|
||||
- i²c
|
||||
|
||||
## Dependencies:
|
||||
- Python 3
|
||||
- Python 3.6
|
||||
- cryptography.io
|
||||
- netifaces
|
||||
- pyserial
|
||||
|
||||
## How do I get started?
|
||||
The best way to get started with the Reticulum Network Stack depends on what
|
||||
you want to do. For full details and examples, have a look at the [Getting Started Fast](https://markqvist.github.io/Reticulum/manual/gettingstartedfast.html) section of the [Reticulum Manual](https://markqvist.github.io/Reticulum/manual/).
|
||||
## Support Reticulum
|
||||
You can help support the continued development of open, free and private communications systems by donating via one of the following channels:
|
||||
|
||||
If you just need Reticulum as a dependency for another application, the easiest way is via pip:
|
||||
|
||||
```bash
|
||||
pip3 install rns
|
||||
```
|
||||
|
||||
The default config file contains examples for using Reticulum with LoRa transceivers (specifically [RNode](https://unsigned.io/projects/rnode/)), packet radio TNCs/modems and UDP. By default a UDP interface is already enabled in the default config, which will enable Reticulum communication in your local ethernet broadcast domain.
|
||||
|
||||
You can use the examples in the config file to expand communication over other mediums such as packet radio or LoRa, or over fast IP links using the UDP interface. I'll add in-depth tutorials and explanations on these topics later. For now, the included examples will hopefully be enough to get started.
|
||||
- Ethereum: 0x81F7B979fEa6134bA9FD5c701b3501A2e61E897a
|
||||
- Bitcoin: 3CPmacGm34qYvR6XWLVEJmi2aNe3PZqUuq
|
||||
- Ko-Fi: https://ko-fi.com/markqvist
|
||||
|
||||
## Caveat Emptor
|
||||
Reticulum is experimental software, 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. If you want to help out, or help sponsor an audit, please do get in touch.
|
||||
Reticulum is experimental software, 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. If you want to help out, or help sponsor an audit, please do get in touch.
|
||||
|
||||
+5
-1
@@ -263,7 +263,11 @@ class Destination:
|
||||
if plaintext != None:
|
||||
if packet.packet_type == RNS.Packet.DATA:
|
||||
if self.callbacks.packet != None:
|
||||
self.callbacks.packet(plaintext, packet)
|
||||
try:
|
||||
self.callbacks.packet(plaintext, packet)
|
||||
except Exception as e:
|
||||
RNS.log("Error while executing receive callback from "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
|
||||
|
||||
def incoming_link_request(self, data, packet):
|
||||
link = RNS.Link.validate_request(self, data, packet)
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
from .Interface import Interface
|
||||
from time import sleep
|
||||
import sys
|
||||
import serial
|
||||
import threading
|
||||
import time
|
||||
import RNS
|
||||
@@ -48,9 +47,18 @@ class AX25KISSInterface(Interface):
|
||||
serial = None
|
||||
|
||||
def __init__(self, owner, name, callsign, ssid, port, speed, databits, parity, stopbits, preamble, txtail, persistence, slottime, flow_control):
|
||||
import importlib
|
||||
if importlib.util.find_spec('serial') != None:
|
||||
import serial
|
||||
else:
|
||||
RNS.log("Using the AX.25 KISS interface requires a serial communication module to be installed.", RNS.LOG_CRITICAL)
|
||||
RNS.log("You can install one with the command: python3 -m pip install pyserial", RNS.LOG_CRITICAL)
|
||||
RNS.panic()
|
||||
|
||||
self.rxb = 0
|
||||
self.txb = 0
|
||||
|
||||
self.pyserial = serial
|
||||
self.serial = None
|
||||
self.owner = owner
|
||||
self.name = name
|
||||
@@ -90,44 +98,48 @@ class AX25KISSInterface(Interface):
|
||||
self.parity = serial.PARITY_ODD
|
||||
|
||||
try:
|
||||
RNS.log("Opening serial port "+self.port+"...")
|
||||
self.serial = serial.Serial(
|
||||
port = self.port,
|
||||
baudrate = self.speed,
|
||||
bytesize = self.databits,
|
||||
parity = self.parity,
|
||||
stopbits = self.stopbits,
|
||||
xonxoff = False,
|
||||
rtscts = False,
|
||||
timeout = 0,
|
||||
inter_byte_timeout = None,
|
||||
write_timeout = None,
|
||||
dsrdtr = False,
|
||||
)
|
||||
self.open_port()
|
||||
except Exception as e:
|
||||
RNS.log("Could not open serial port for interface "+str(self), RNS.LOG_ERROR)
|
||||
raise e
|
||||
|
||||
if self.serial.is_open:
|
||||
# Allow time for interface to initialise before config
|
||||
sleep(2.0)
|
||||
thread = threading.Thread(target=self.readLoop)
|
||||
thread.setDaemon(True)
|
||||
thread.start()
|
||||
self.online = True
|
||||
RNS.log("Serial port "+self.port+" is now open")
|
||||
RNS.log("Configuring AX.25 KISS interface parameters...")
|
||||
self.setPreamble(self.preamble)
|
||||
self.setTxTail(self.txtail)
|
||||
self.setPersistence(self.persistence)
|
||||
self.setSlotTime(self.slottime)
|
||||
self.setFlowControl(self.flow_control)
|
||||
self.interface_ready = True
|
||||
RNS.log("AX.25 KISS interface configured")
|
||||
sleep(2)
|
||||
self.configure_device()
|
||||
else:
|
||||
raise IOError("Could not open serial port")
|
||||
|
||||
def open_port(self):
|
||||
RNS.log("Opening serial port "+self.port+"...", RNS.LOG_VERBOSE)
|
||||
self.serial = self.pyserial.Serial(
|
||||
port = self.port,
|
||||
baudrate = self.speed,
|
||||
bytesize = self.databits,
|
||||
parity = self.parity,
|
||||
stopbits = self.stopbits,
|
||||
xonxoff = False,
|
||||
rtscts = False,
|
||||
timeout = 0,
|
||||
inter_byte_timeout = None,
|
||||
write_timeout = None,
|
||||
dsrdtr = False,
|
||||
)
|
||||
|
||||
def configure_device(self):
|
||||
# Allow time for interface to initialise before config
|
||||
sleep(2.0)
|
||||
thread = threading.Thread(target=self.readLoop)
|
||||
thread.setDaemon(True)
|
||||
thread.start()
|
||||
self.online = True
|
||||
RNS.log("Serial port "+self.port+" is now open")
|
||||
RNS.log("Configuring AX.25 KISS interface parameters...")
|
||||
self.setPreamble(self.preamble)
|
||||
self.setTxTail(self.txtail)
|
||||
self.setPersistence(self.persistence)
|
||||
self.setSlotTime(self.slottime)
|
||||
self.setFlowControl(self.flow_control)
|
||||
self.interface_ready = True
|
||||
RNS.log("AX.25 KISS interface configured")
|
||||
|
||||
def setPreamble(self, preamble):
|
||||
preamble_ms = preamble
|
||||
@@ -287,8 +299,6 @@ class AX25KISSInterface(Interface):
|
||||
escape = False
|
||||
data_buffer = data_buffer+bytes([byte])
|
||||
elif (command == KISS.CMD_READY):
|
||||
# TODO: add timeout and reset if ready
|
||||
# command never arrives
|
||||
self.process_queue()
|
||||
else:
|
||||
time_since_last = int(time.time()*1000) - last_read_ms
|
||||
@@ -308,10 +318,29 @@ class AX25KISSInterface(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)+" experienced an unrecoverable error and is being torn down. Restart Reticulum to attempt to open this interface again.", RNS.LOG_ERROR)
|
||||
|
||||
RNS.log("The interface "+str(self)+" experienced an unrecoverable error and is now offline.", RNS.LOG_ERROR)
|
||||
|
||||
if RNS.Reticulum.panic_on_interface_error:
|
||||
RNS.panic()
|
||||
|
||||
RNS.log("Reticulum will attempt to reconnect the interface periodically.", RNS.LOG_ERROR)
|
||||
|
||||
self.online = False
|
||||
self.serial.close()
|
||||
self.reconnect_port()
|
||||
|
||||
def reconnect_port(self):
|
||||
while not self.online:
|
||||
try:
|
||||
time.sleep(5)
|
||||
RNS.log("Attempting to reconnect serial port "+str(self.port)+" for "+str(self)+"...", RNS.LOG_VERBOSE)
|
||||
self.open_port()
|
||||
if self.serial.is_open:
|
||||
self.configure_device()
|
||||
except Exception as e:
|
||||
RNS.log("Error while reconnecting port, the contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
|
||||
RNS.log("Reconnected serial port for "+str(self))
|
||||
|
||||
def __str__(self):
|
||||
return "AX25KISSInterface["+self.name+"]"
|
||||
@@ -0,0 +1,282 @@
|
||||
from .Interface import Interface
|
||||
import socketserver
|
||||
import threading
|
||||
import socket
|
||||
import struct
|
||||
import time
|
||||
import sys
|
||||
import RNS
|
||||
|
||||
|
||||
class AutoInterface(Interface):
|
||||
DEFAULT_DISCOVERY_PORT = 29716
|
||||
DEFAULT_DATA_PORT = 42671
|
||||
DEFAULT_GROUP_ID = "reticulum".encode("utf-8")
|
||||
|
||||
SCOPE_LINK = "2"
|
||||
SCOPE_ADMIN = "4"
|
||||
SCOPE_SITE = "5"
|
||||
SCOPE_ORGANISATION = "8"
|
||||
SCOPE_GLOBAL = "e"
|
||||
|
||||
PEERING_TIMEOUT = 6.0
|
||||
|
||||
DARWIN_IGNORE_IFS = ["awdl0", "llw0", "lo0", "en5"]
|
||||
|
||||
def __init__(self, owner, name, group_id=None, discovery_scope=None, discovery_port=None, data_port=None, allowed_interfaces=None, ignored_interfaces=None):
|
||||
import importlib
|
||||
if importlib.util.find_spec('netifaces') != None:
|
||||
import netifaces
|
||||
else:
|
||||
RNS.log("Using AutoInterface requires the netifaces module.", RNS.LOG_CRITICAL)
|
||||
RNS.log("You can install it with the command: python3 -m pip install netifaces", RNS.LOG_CRITICAL)
|
||||
RNS.panic()
|
||||
|
||||
self.netifaces = netifaces
|
||||
self.rxb = 0
|
||||
self.txb = 0
|
||||
self.IN = True
|
||||
self.OUT = False
|
||||
self.name = name
|
||||
self.online = False
|
||||
self.peers = {}
|
||||
self.link_local_addresses = []
|
||||
self.adopted_interfaces = {}
|
||||
|
||||
self.outbound_udp_socket = None
|
||||
|
||||
self.announce_interval = AutoInterface.PEERING_TIMEOUT/4.0
|
||||
self.peer_job_interval = AutoInterface.PEERING_TIMEOUT*1.1
|
||||
self.peering_timeout = AutoInterface.PEERING_TIMEOUT
|
||||
|
||||
if allowed_interfaces == None:
|
||||
self.allowed_interfaces = []
|
||||
else:
|
||||
self.allowed_interfaces = allowed_interfaces
|
||||
|
||||
if ignored_interfaces == None:
|
||||
self.ignored_interfaces = []
|
||||
else:
|
||||
self.ignored_interfaces = ignored_interfaces
|
||||
|
||||
if group_id == None:
|
||||
self.group_id = AutoInterface.DEFAULT_GROUP_ID
|
||||
else:
|
||||
self.group_id = group_id.encode("utf-8")
|
||||
|
||||
if discovery_port == None:
|
||||
self.discovery_port = AutoInterface.DEFAULT_DISCOVERY_PORT
|
||||
else:
|
||||
self.discovery_port = discovery_port
|
||||
|
||||
if data_port == None:
|
||||
self.data_port = AutoInterface.DEFAULT_DATA_PORT
|
||||
else:
|
||||
self.data_port = data_port
|
||||
|
||||
if discovery_scope == None:
|
||||
self.discovery_scope = AutoInterface.SCOPE_LINK
|
||||
elif str(discovery_scope).lower() == "link":
|
||||
self.discovery_scope = AutoInterface.SCOPE_LINK
|
||||
elif str(discovery_scope).lower() == "admin":
|
||||
self.discovery_scope = AutoInterface.SCOPE_ADMIN
|
||||
elif str(discovery_scope).lower() == "site":
|
||||
self.discovery_scope = AutoInterface.SCOPE_SITE
|
||||
elif str(discovery_scope).lower() == "organisation":
|
||||
self.discovery_scope = AutoInterface.SCOPE_ORGANISATION
|
||||
elif str(discovery_scope).lower() == "global":
|
||||
self.discovery_scope = AutoInterface.SCOPE_GLOBAL
|
||||
|
||||
self.group_hash = RNS.Identity.full_hash(self.group_id)
|
||||
g = self.group_hash
|
||||
#gt = "{:02x}".format(g[1]+(g[0]<<8))
|
||||
gt = "0"
|
||||
gt += ":"+"{:02x}".format(g[3]+(g[2]<<8))
|
||||
gt += ":"+"{:02x}".format(g[5]+(g[4]<<8))
|
||||
gt += ":"+"{:02x}".format(g[7]+(g[6]<<8))
|
||||
gt += ":"+"{:02x}".format(g[9]+(g[8]<<8))
|
||||
gt += ":"+"{:02x}".format(g[11]+(g[10]<<8))
|
||||
gt += ":"+"{:02x}".format(g[13]+(g[12]<<8))
|
||||
self.mcast_discovery_address = "ff1"+self.discovery_scope+":"+gt
|
||||
|
||||
suitable_interfaces = 0
|
||||
for ifname in self.netifaces.interfaces():
|
||||
if RNS.vendor.platformutils.get_platform() == "darwin" and ifname in AutoInterface.DARWIN_IGNORE_IFS:
|
||||
RNS.log(str(self)+" skipping Darwin AWDL or tethering interface "+str(ifname), RNS.LOG_EXTREME)
|
||||
elif RNS.vendor.platformutils.get_platform() == "darwin" and ifname == "lo0":
|
||||
RNS.log(str(self)+" skipping Darwin loopback interface "+str(ifname), RNS.LOG_EXTREME)
|
||||
elif ifname in self.ignored_interfaces:
|
||||
RNS.log(str(self)+" ignoring disallowed interface "+str(ifname), RNS.LOG_EXTREME)
|
||||
else:
|
||||
if len(self.allowed_interfaces) > 0 and not ifname in self.allowed_interfaces:
|
||||
RNS.log(str(self)+" ignoring interface "+str(ifname)+" since it was not allowed", RNS.LOG_EXTREME)
|
||||
else:
|
||||
addresses = self.netifaces.ifaddresses(ifname)
|
||||
if self.netifaces.AF_INET6 in addresses:
|
||||
link_local_addr = None
|
||||
for address in addresses[self.netifaces.AF_INET6]:
|
||||
if "addr" in address:
|
||||
if address["addr"].startswith("fe80:"):
|
||||
link_local_addr = address["addr"]
|
||||
self.link_local_addresses.append(link_local_addr.split("%")[0])
|
||||
self.adopted_interfaces[ifname] = link_local_addr.split("%")[0]
|
||||
RNS.log(str(self)+" Selecting link-local address "+str(link_local_addr)+" for interface "+str(ifname), RNS.LOG_EXTREME)
|
||||
|
||||
if link_local_addr == None:
|
||||
RNS.log(str(self)+" No link-local IPv6 address configured for "+str(ifname)+", skipping interface", RNS.LOG_EXTREME)
|
||||
else:
|
||||
mcast_addr = self.mcast_discovery_address
|
||||
RNS.log(str(self)+" Creating multicast discovery listener on "+str(ifname)+" with address "+str(mcast_addr), RNS.LOG_EXTREME)
|
||||
|
||||
# Struct with interface index
|
||||
if_struct = struct.pack("I", socket.if_nametoindex(ifname))
|
||||
|
||||
# Set up multicast socket
|
||||
discovery_socket = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
|
||||
discovery_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
discovery_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
|
||||
discovery_socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_MULTICAST_IF, if_struct)
|
||||
|
||||
# Join multicast group
|
||||
mcast_group = socket.inet_pton(socket.AF_INET6, mcast_addr) + if_struct
|
||||
discovery_socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_JOIN_GROUP, mcast_group)
|
||||
|
||||
# Bind socket
|
||||
addr_info = socket.getaddrinfo(mcast_addr+"%"+ifname, self.discovery_port, socket.AF_INET6, socket.SOCK_DGRAM)
|
||||
discovery_socket.bind(addr_info[0][4])
|
||||
|
||||
# Set up thread for discovery packets
|
||||
def discovery_loop():
|
||||
self.discovery_handler(discovery_socket, ifname)
|
||||
|
||||
thread = threading.Thread(target=discovery_loop)
|
||||
thread.setDaemon(True)
|
||||
thread.start()
|
||||
|
||||
suitable_interfaces += 1
|
||||
|
||||
if suitable_interfaces == 0:
|
||||
RNS.log(str(self)+" could not autoconfigure. This interface currently provides no connectivity.", RNS.LOG_WARNING)
|
||||
else:
|
||||
self.receives = True
|
||||
|
||||
peering_wait = self.announce_interval*1.2
|
||||
RNS.log(str(self)+" discovering peers for "+str(round(peering_wait, 2))+" seconds...", RNS.LOG_VERBOSE)
|
||||
|
||||
def handlerFactory(callback):
|
||||
def createHandler(*args, **keys):
|
||||
return AutoInterfaceHandler(callback, *args, **keys)
|
||||
return createHandler
|
||||
|
||||
self.owner = owner
|
||||
socketserver.UDPServer.address_family = socket.AF_INET6
|
||||
|
||||
for ifname in self.adopted_interfaces:
|
||||
local_addr = self.adopted_interfaces[ifname]+"%"+ifname
|
||||
addr_info = socket.getaddrinfo(local_addr, self.data_port, socket.AF_INET6, socket.SOCK_DGRAM)
|
||||
address = addr_info[0][4]
|
||||
|
||||
self.server = socketserver.UDPServer(address, handlerFactory(self.processIncoming))
|
||||
|
||||
thread = threading.Thread(target=self.server.serve_forever)
|
||||
thread.setDaemon(True)
|
||||
thread.start()
|
||||
|
||||
job_thread = threading.Thread(target=self.peer_jobs)
|
||||
job_thread.setDaemon(True)
|
||||
job_thread.start()
|
||||
|
||||
time.sleep(peering_wait)
|
||||
|
||||
self.online = True
|
||||
|
||||
|
||||
def discovery_handler(self, socket, ifname):
|
||||
def announce_loop():
|
||||
self.announce_handler(ifname)
|
||||
|
||||
thread = threading.Thread(target=announce_loop)
|
||||
thread.setDaemon(True)
|
||||
thread.start()
|
||||
|
||||
while True:
|
||||
data, ipv6_src = socket.recvfrom(1024)
|
||||
expected_hash = RNS.Identity.full_hash(self.group_id+ipv6_src[0].encode("utf-8"))
|
||||
if data == expected_hash:
|
||||
self.add_peer(ipv6_src[0], ifname)
|
||||
else:
|
||||
RNS.log(str(self)+" received peering packet on "+str(ifname)+" from "+str(ipv6_src[0])+", but authentication hash was incorrect.", RNS.LOG_DEBUG)
|
||||
|
||||
def peer_jobs(self):
|
||||
while True:
|
||||
time.sleep(self.peer_job_interval)
|
||||
now = time.time()
|
||||
timed_out_peers = []
|
||||
for peer_addr in self.peers:
|
||||
peer = self.peers[peer_addr]
|
||||
last_heard = peer[1]
|
||||
if now > last_heard+self.peering_timeout:
|
||||
timed_out_peers.append(peer_addr)
|
||||
|
||||
for peer_addr in timed_out_peers:
|
||||
removed_peer = self.peers.pop(peer_addr)
|
||||
RNS.log(str(self)+" removed peer "+str(peer_addr)+" on "+str(removed_peer[0]), RNS.LOG_DEBUG)
|
||||
|
||||
|
||||
def announce_handler(self, ifname):
|
||||
while True:
|
||||
self.peer_announce(ifname)
|
||||
time.sleep(self.announce_interval)
|
||||
|
||||
def peer_announce(self, ifname):
|
||||
link_local_address = self.adopted_interfaces[ifname]
|
||||
discovery_token = RNS.Identity.full_hash(self.group_id+link_local_address.encode("utf-8"))
|
||||
announce_socket = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
|
||||
addr_info = socket.getaddrinfo(self.mcast_discovery_address, self.discovery_port, socket.AF_INET6, socket.SOCK_DGRAM)
|
||||
|
||||
ifis = struct.pack("I", socket.if_nametoindex(ifname))
|
||||
announce_socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_MULTICAST_IF, ifis)
|
||||
announce_socket.sendto(discovery_token, addr_info[0][4])
|
||||
|
||||
def add_peer(self, addr, ifname):
|
||||
if not addr in self.link_local_addresses:
|
||||
if not addr in self.peers:
|
||||
self.peers[addr] = [ifname, time.time()]
|
||||
RNS.log(str(self)+" added peer "+str(addr)+" on "+str(ifname), RNS.LOG_DEBUG)
|
||||
else:
|
||||
self.refresh_peer(addr)
|
||||
|
||||
def refresh_peer(self, addr):
|
||||
self.peers[addr][1] = time.time()
|
||||
|
||||
def processIncoming(self, data):
|
||||
self.rxb += len(data)
|
||||
self.owner.inbound(data, self)
|
||||
|
||||
def processOutgoing(self,data):
|
||||
for peer in self.peers:
|
||||
try:
|
||||
if self.outbound_udp_socket == None:
|
||||
self.outbound_udp_socket = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
|
||||
|
||||
peer_addr = str(peer)+"%"+str(self.peers[peer][0])
|
||||
addr_info = socket.getaddrinfo(peer_addr, self.data_port, socket.AF_INET6, socket.SOCK_DGRAM)
|
||||
self.outbound_udp_socket.sendto(data, addr_info[0][4])
|
||||
except Exception as e:
|
||||
RNS.log("Could not transmit on "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
|
||||
|
||||
self.txb += len(data)
|
||||
|
||||
|
||||
def __str__(self):
|
||||
return "AutoInterface["+self.name+"]"
|
||||
|
||||
class AutoInterfaceHandler(socketserver.BaseRequestHandler):
|
||||
def __init__(self, callback, *args, **keys):
|
||||
self.callback = callback
|
||||
socketserver.BaseRequestHandler.__init__(self, *args, **keys)
|
||||
|
||||
def handle(self):
|
||||
data = self.request[0]
|
||||
self.callback(data)
|
||||
@@ -1,7 +1,6 @@
|
||||
from .Interface import Interface
|
||||
from time import sleep
|
||||
import sys
|
||||
import serial
|
||||
import threading
|
||||
import time
|
||||
import RNS
|
||||
@@ -40,12 +39,21 @@ 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):
|
||||
import importlib
|
||||
if importlib.util.find_spec('serial') != None:
|
||||
import serial
|
||||
else:
|
||||
RNS.log("Using the KISS interface requires a serial communication module to be installed.", RNS.LOG_CRITICAL)
|
||||
RNS.log("You can install one with the command: python3 -m pip install pyserial", RNS.LOG_CRITICAL)
|
||||
RNS.panic()
|
||||
|
||||
self.rxb = 0
|
||||
self.txb = 0
|
||||
|
||||
if beacon_data == None:
|
||||
beacon_data = ""
|
||||
|
||||
self.pyserial = serial
|
||||
self.serial = None
|
||||
self.owner = owner
|
||||
self.name = name
|
||||
@@ -78,44 +86,52 @@ class KISSInterface(Interface):
|
||||
self.parity = serial.PARITY_ODD
|
||||
|
||||
try:
|
||||
RNS.log("Opening serial port "+self.port+"...")
|
||||
self.serial = serial.Serial(
|
||||
port = self.port,
|
||||
baudrate = self.speed,
|
||||
bytesize = self.databits,
|
||||
parity = self.parity,
|
||||
stopbits = self.stopbits,
|
||||
xonxoff = False,
|
||||
rtscts = False,
|
||||
timeout = 0,
|
||||
inter_byte_timeout = None,
|
||||
write_timeout = None,
|
||||
dsrdtr = False,
|
||||
)
|
||||
self.open_port()
|
||||
except Exception as e:
|
||||
RNS.log("Could not open serial port "+self.port, RNS.LOG_ERROR)
|
||||
raise e
|
||||
|
||||
if self.serial.is_open:
|
||||
# Allow time for interface to initialise before config
|
||||
sleep(2.0)
|
||||
thread = threading.Thread(target=self.readLoop)
|
||||
thread.setDaemon(True)
|
||||
thread.start()
|
||||
self.online = True
|
||||
RNS.log("Serial port "+self.port+" is now open")
|
||||
RNS.log("Configuring KISS interface parameters...")
|
||||
self.setPreamble(self.preamble)
|
||||
self.setTxTail(self.txtail)
|
||||
self.setPersistence(self.persistence)
|
||||
self.setSlotTime(self.slottime)
|
||||
self.setFlowControl(self.flow_control)
|
||||
self.interface_ready = True
|
||||
RNS.log("KISS interface configured")
|
||||
self.configure_device()
|
||||
else:
|
||||
raise IOError("Could not open serial port")
|
||||
|
||||
|
||||
def open_port(self):
|
||||
RNS.log("Opening serial port "+self.port+"...", RNS.LOG_VERBOSE)
|
||||
self.serial = self.pyserial.Serial(
|
||||
port = self.port,
|
||||
baudrate = self.speed,
|
||||
bytesize = self.databits,
|
||||
parity = self.parity,
|
||||
stopbits = self.stopbits,
|
||||
xonxoff = False,
|
||||
rtscts = False,
|
||||
timeout = 0,
|
||||
inter_byte_timeout = None,
|
||||
write_timeout = None,
|
||||
dsrdtr = False,
|
||||
)
|
||||
|
||||
|
||||
def configure_device(self):
|
||||
# Allow time for interface to initialise before config
|
||||
sleep(2.0)
|
||||
thread = threading.Thread(target=self.readLoop)
|
||||
thread.setDaemon(True)
|
||||
thread.start()
|
||||
self.online = True
|
||||
RNS.log("Serial port "+self.port+" is now open")
|
||||
RNS.log("Configuring KISS interface parameters...")
|
||||
self.setPreamble(self.preamble)
|
||||
self.setTxTail(self.txtail)
|
||||
self.setPersistence(self.persistence)
|
||||
self.setSlotTime(self.slottime)
|
||||
self.setFlowControl(self.flow_control)
|
||||
self.interface_ready = True
|
||||
RNS.log("KISS interface configured")
|
||||
|
||||
|
||||
def setPreamble(self, preamble):
|
||||
preamble_ms = preamble
|
||||
preamble = int(preamble_ms / 10)
|
||||
@@ -283,10 +299,29 @@ 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)+" experienced an unrecoverable error and is being torn down. Restart Reticulum to attempt to open this interface again.", RNS.LOG_ERROR)
|
||||
|
||||
RNS.log("The interface "+str(self)+" experienced an unrecoverable error and is now offline.", RNS.LOG_ERROR)
|
||||
|
||||
if RNS.Reticulum.panic_on_interface_error:
|
||||
RNS.panic()
|
||||
|
||||
RNS.log("Reticulum will attempt to reconnect the interface periodically.", RNS.LOG_ERROR)
|
||||
|
||||
self.online = False
|
||||
self.serial.close()
|
||||
self.reconnect_port()
|
||||
|
||||
def reconnect_port(self):
|
||||
while not self.online:
|
||||
try:
|
||||
time.sleep(5)
|
||||
RNS.log("Attempting to reconnect serial port "+str(self.port)+" for "+str(self)+"...", RNS.LOG_VERBOSE)
|
||||
self.open_port()
|
||||
if self.serial.is_open:
|
||||
self.configure_device()
|
||||
except Exception as e:
|
||||
RNS.log("Error while reconnecting port, the contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
|
||||
RNS.log("Reconnected serial port for "+str(self))
|
||||
|
||||
def __str__(self):
|
||||
return "KISSInterface["+self.name+"]"
|
||||
@@ -22,6 +22,7 @@ class ThreadingTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
|
||||
pass
|
||||
|
||||
class LocalClientInterface(Interface):
|
||||
RECONNECT_WAIT = 3
|
||||
|
||||
def __init__(self, owner, name, target_port = None, connected_socket=None):
|
||||
self.rxb = 0
|
||||
@@ -32,6 +33,9 @@ class LocalClientInterface(Interface):
|
||||
self.OUT = False
|
||||
self.socket = None
|
||||
self.parent_interface = None
|
||||
self.reconnecting = False
|
||||
self.never_connected = True
|
||||
self.detached = False
|
||||
self.name = name
|
||||
|
||||
if connected_socket != None:
|
||||
@@ -46,11 +50,7 @@ class LocalClientInterface(Interface):
|
||||
self.receives = True
|
||||
self.target_ip = "127.0.0.1"
|
||||
self.target_port = target_port
|
||||
|
||||
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
self.socket.connect((self.target_ip, self.target_port))
|
||||
|
||||
self.is_connected_to_shared_instance = True
|
||||
self.connect()
|
||||
|
||||
self.owner = owner
|
||||
self.online = True
|
||||
@@ -61,6 +61,47 @@ class LocalClientInterface(Interface):
|
||||
thread.setDaemon(True)
|
||||
thread.start()
|
||||
|
||||
def connect(self):
|
||||
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
self.socket.connect((self.target_ip, self.target_port))
|
||||
|
||||
self.online = True
|
||||
self.is_connected_to_shared_instance = True
|
||||
self.never_connected = False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def reconnect(self):
|
||||
if self.is_connected_to_shared_instance:
|
||||
if not self.reconnecting:
|
||||
self.reconnecting = True
|
||||
attempts = 0
|
||||
|
||||
while not self.online:
|
||||
time.sleep(LocalClientInterface.RECONNECT_WAIT)
|
||||
attempts += 1
|
||||
|
||||
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.shared_connection_reappeared()
|
||||
|
||||
else:
|
||||
RNS.log("Attempt to reconnect on a non-initiator shared local interface. This should not happen.", RNS.LOG_ERROR)
|
||||
raise IOError("Attempt to reconnect on a non-initiator local interface")
|
||||
|
||||
|
||||
def processIncoming(self, data):
|
||||
self.rxb += len(data)
|
||||
if hasattr(self, "parent_interface") and self.parent_interface != None:
|
||||
@@ -68,6 +109,7 @@ class LocalClientInterface(Interface):
|
||||
|
||||
self.owner.inbound(data, self)
|
||||
|
||||
|
||||
def processOutgoing(self, data):
|
||||
if self.online:
|
||||
while self.writing:
|
||||
@@ -119,8 +161,14 @@ class LocalClientInterface(Interface):
|
||||
escape = False
|
||||
data_buffer = data_buffer+bytes([byte])
|
||||
else:
|
||||
RNS.log("Socket for "+str(self)+" was closed, tearing down interface", RNS.LOG_VERBOSE)
|
||||
self.teardown(nowarning=True)
|
||||
self.online = False
|
||||
if self.is_connected_to_shared_instance and not self.detached:
|
||||
RNS.log("Socket for "+str(self)+" was closed, attempting to reconnect...", RNS.LOG_WARNING)
|
||||
RNS.Transport.shared_connection_disappeared()
|
||||
self.reconnect()
|
||||
else:
|
||||
self.teardown(nowarning=True)
|
||||
|
||||
break
|
||||
|
||||
|
||||
@@ -168,13 +216,10 @@ class LocalClientInterface(Interface):
|
||||
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()
|
||||
if nowarning == False:
|
||||
RNS.log("Permanently lost connection to local shared RNS instance. Exiting now.", RNS.LOG_CRITICAL)
|
||||
|
||||
RNS.exit()
|
||||
|
||||
|
||||
def __str__(self):
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
from .Interface import Interface
|
||||
from time import sleep
|
||||
import sys
|
||||
import serial
|
||||
import threading
|
||||
import time
|
||||
import math
|
||||
@@ -72,9 +71,18 @@ 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):
|
||||
import importlib
|
||||
if importlib.util.find_spec('serial') != None:
|
||||
import serial
|
||||
else:
|
||||
RNS.log("Using the RNode interface requires a serial communication module to be installed.", RNS.LOG_CRITICAL)
|
||||
RNS.log("You can install one with the command: python3 -m pip install pyserial", RNS.LOG_CRITICAL)
|
||||
RNS.panic()
|
||||
|
||||
self.rxb = 0
|
||||
self.txb = 0
|
||||
|
||||
self.pyserial = serial
|
||||
self.serial = None
|
||||
self.owner = owner
|
||||
self.name = name
|
||||
@@ -150,46 +158,53 @@ class RNodeInterface(Interface):
|
||||
raise ValueError("The configuration for "+str(self)+" contains errors, interface is offline")
|
||||
|
||||
try:
|
||||
RNS.log("Opening serial port "+self.port+"...")
|
||||
self.serial = serial.Serial(
|
||||
port = self.port,
|
||||
baudrate = self.speed,
|
||||
bytesize = self.databits,
|
||||
parity = self.parity,
|
||||
stopbits = self.stopbits,
|
||||
xonxoff = False,
|
||||
rtscts = False,
|
||||
timeout = 0,
|
||||
inter_byte_timeout = None,
|
||||
write_timeout = None,
|
||||
dsrdtr = False,
|
||||
)
|
||||
self.open_port()
|
||||
except Exception as e:
|
||||
RNS.log("Could not open serial port for interface "+str(self), RNS.LOG_ERROR)
|
||||
raise e
|
||||
|
||||
if self.serial.is_open:
|
||||
sleep(2.0)
|
||||
thread = threading.Thread(target=self.readLoop)
|
||||
thread.setDaemon(True)
|
||||
thread.start()
|
||||
self.online = True
|
||||
RNS.log("Serial port "+self.port+" is now open")
|
||||
RNS.log("Configuring RNode interface...", RNS.LOG_VERBOSE)
|
||||
self.initRadio()
|
||||
if (self.validateRadioState()):
|
||||
self.interface_ready = True
|
||||
RNS.log(str(self)+" is configured and powered up")
|
||||
sleep(1.0)
|
||||
else:
|
||||
RNS.log("After configuring "+str(self)+", the reported radio parameters did not match your configuration.", RNS.LOG_ERROR)
|
||||
RNS.log("Make sure that your hardware actually supports the parameters specified in the configuration", RNS.LOG_ERROR)
|
||||
RNS.log("Aborting RNode startup", RNS.LOG_ERROR)
|
||||
self.serial.close()
|
||||
raise IOError("RNode interface did not pass validation")
|
||||
self.configure_device()
|
||||
else:
|
||||
raise IOError("Could not open serial port")
|
||||
|
||||
def open_port(self):
|
||||
RNS.log("Opening serial port "+self.port+"...")
|
||||
self.serial = self.pyserial.Serial(
|
||||
port = self.port,
|
||||
baudrate = self.speed,
|
||||
bytesize = self.databits,
|
||||
parity = self.parity,
|
||||
stopbits = self.stopbits,
|
||||
xonxoff = False,
|
||||
rtscts = False,
|
||||
timeout = 0,
|
||||
inter_byte_timeout = None,
|
||||
write_timeout = None,
|
||||
dsrdtr = False,
|
||||
)
|
||||
|
||||
|
||||
def configure_device(self):
|
||||
sleep(2.0)
|
||||
thread = threading.Thread(target=self.readLoop)
|
||||
thread.setDaemon(True)
|
||||
thread.start()
|
||||
self.online = True
|
||||
RNS.log("Serial port "+self.port+" is now open")
|
||||
RNS.log("Configuring RNode interface...", RNS.LOG_VERBOSE)
|
||||
self.initRadio()
|
||||
if (self.validateRadioState()):
|
||||
self.interface_ready = True
|
||||
RNS.log(str(self)+" is configured and powered up")
|
||||
sleep(1.0)
|
||||
else:
|
||||
RNS.log("After configuring "+str(self)+", the reported radio parameters did not match your configuration.", RNS.LOG_ERROR)
|
||||
RNS.log("Make sure that your hardware actually supports the parameters specified in the configuration", RNS.LOG_ERROR)
|
||||
RNS.log("Aborting RNode startup", RNS.LOG_ERROR)
|
||||
self.serial.close()
|
||||
raise IOError("RNode interface did not pass validation")
|
||||
|
||||
|
||||
def initRadio(self):
|
||||
self.setFrequency()
|
||||
@@ -283,6 +298,8 @@ class RNodeInterface(Interface):
|
||||
def processIncoming(self, data):
|
||||
self.rxb += len(data)
|
||||
self.owner.inbound(data, self)
|
||||
self.r_stat_rssi = None
|
||||
self.r_stat_snr = None
|
||||
|
||||
|
||||
def processOutgoing(self,data):
|
||||
@@ -469,11 +486,30 @@ 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)+" experienced an unrecoverable error and is being torn down. Restart Reticulum to attempt to open this interface again.", RNS.LOG_ERROR)
|
||||
|
||||
RNS.log("The interface "+str(self)+" experienced an unrecoverable error and is now offline.", RNS.LOG_ERROR)
|
||||
|
||||
if RNS.Reticulum.panic_on_interface_error:
|
||||
RNS.panic()
|
||||
|
||||
RNS.log("Reticulum will attempt to reconnect the interface periodically.", RNS.LOG_ERROR)
|
||||
|
||||
self.online = False
|
||||
self.serial.close()
|
||||
self.reconnect_port()
|
||||
|
||||
def reconnect_port(self):
|
||||
while not self.online:
|
||||
try:
|
||||
time.sleep(5)
|
||||
RNS.log("Attempting to reconnect serial port "+str(self.port)+" for "+str(self)+"...", RNS.LOG_VERBOSE)
|
||||
self.open_port()
|
||||
if self.serial.is_open:
|
||||
self.configure_device()
|
||||
except Exception as e:
|
||||
RNS.log("Error while reconnecting port, the contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
|
||||
RNS.log("Reconnected serial port for "+str(self))
|
||||
|
||||
def __str__(self):
|
||||
return "RNodeInterface["+self.name+"]"
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
from .Interface import Interface
|
||||
from time import sleep
|
||||
import sys
|
||||
import serial
|
||||
import threading
|
||||
import time
|
||||
import RNS
|
||||
@@ -31,9 +30,18 @@ class SerialInterface(Interface):
|
||||
serial = None
|
||||
|
||||
def __init__(self, owner, name, port, speed, databits, parity, stopbits):
|
||||
import importlib
|
||||
if importlib.util.find_spec('serial') != None:
|
||||
import serial
|
||||
else:
|
||||
RNS.log("Using the Serial interface requires a serial communication module to be installed.", RNS.LOG_CRITICAL)
|
||||
RNS.log("You can install one with the command: python3 -m pip install pyserial", RNS.LOG_CRITICAL)
|
||||
RNS.panic()
|
||||
|
||||
self.rxb = 0
|
||||
self.txb = 0
|
||||
|
||||
self.pyserial = serial
|
||||
self.serial = None
|
||||
self.owner = owner
|
||||
self.name = name
|
||||
@@ -52,35 +60,43 @@ class SerialInterface(Interface):
|
||||
self.parity = serial.PARITY_ODD
|
||||
|
||||
try:
|
||||
RNS.log("Opening serial port "+self.port+"...")
|
||||
self.serial = serial.Serial(
|
||||
port = self.port,
|
||||
baudrate = self.speed,
|
||||
bytesize = self.databits,
|
||||
parity = self.parity,
|
||||
stopbits = self.stopbits,
|
||||
xonxoff = False,
|
||||
rtscts = False,
|
||||
timeout = 0,
|
||||
inter_byte_timeout = None,
|
||||
write_timeout = None,
|
||||
dsrdtr = False,
|
||||
)
|
||||
self.open_port()
|
||||
except Exception as e:
|
||||
RNS.log("Could not open serial port for interface "+str(self), RNS.LOG_ERROR)
|
||||
raise e
|
||||
|
||||
if self.serial.is_open:
|
||||
sleep(0.5)
|
||||
thread = threading.Thread(target=self.readLoop)
|
||||
thread.setDaemon(True)
|
||||
thread.start()
|
||||
self.online = True
|
||||
RNS.log("Serial port "+self.port+" is now open")
|
||||
self.configure_device()
|
||||
else:
|
||||
raise IOError("Could not open serial port")
|
||||
|
||||
|
||||
def open_port(self):
|
||||
RNS.log("Opening serial port "+self.port+"...", RNS.LOG_VERBOSE)
|
||||
self.serial = self.pyserial.Serial(
|
||||
port = self.port,
|
||||
baudrate = self.speed,
|
||||
bytesize = self.databits,
|
||||
parity = self.parity,
|
||||
stopbits = self.stopbits,
|
||||
xonxoff = False,
|
||||
rtscts = False,
|
||||
timeout = 0,
|
||||
inter_byte_timeout = None,
|
||||
write_timeout = None,
|
||||
dsrdtr = False,
|
||||
)
|
||||
|
||||
|
||||
def configure_device(self):
|
||||
sleep(0.5)
|
||||
thread = threading.Thread(target=self.readLoop)
|
||||
thread.setDaemon(True)
|
||||
thread.start()
|
||||
self.online = True
|
||||
RNS.log("Serial port "+self.port+" is now open")
|
||||
|
||||
|
||||
def processIncoming(self, data):
|
||||
self.rxb += len(data)
|
||||
self.owner.inbound(data, self)
|
||||
@@ -132,13 +148,33 @@ class SerialInterface(Interface):
|
||||
in_frame = False
|
||||
escape = False
|
||||
sleep(0.08)
|
||||
|
||||
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)+" experienced an unrecoverable error and is being torn down. Restart Reticulum to attempt to open this interface again.", RNS.LOG_ERROR)
|
||||
|
||||
RNS.log("The interface "+str(self)+" experienced an unrecoverable error and is now offline.", RNS.LOG_ERROR)
|
||||
|
||||
if RNS.Reticulum.panic_on_interface_error:
|
||||
RNS.panic()
|
||||
|
||||
RNS.log("Reticulum will attempt to reconnect the interface periodically.", RNS.LOG_ERROR)
|
||||
|
||||
self.online = False
|
||||
self.serial.close()
|
||||
self.reconnect_port()
|
||||
|
||||
def reconnect_port(self):
|
||||
while not self.online:
|
||||
try:
|
||||
time.sleep(5)
|
||||
RNS.log("Attempting to reconnect serial port "+str(self.port)+" for "+str(self)+"...", RNS.LOG_VERBOSE)
|
||||
self.open_port()
|
||||
if self.serial.is_open:
|
||||
self.configure_device()
|
||||
except Exception as e:
|
||||
RNS.log("Error while reconnecting port, the contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
|
||||
RNS.log("Reconnected serial port for "+str(self))
|
||||
|
||||
def __str__(self):
|
||||
return "SerialInterface["+self.name+"]"
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
from .Interface import Interface
|
||||
import socketserver
|
||||
import threading
|
||||
import netifaces
|
||||
import platform
|
||||
import socket
|
||||
import time
|
||||
@@ -20,6 +19,20 @@ class HDLC():
|
||||
data = data.replace(bytes([HDLC.FLAG]), bytes([HDLC.ESC, HDLC.FLAG^HDLC.ESC_MASK]))
|
||||
return data
|
||||
|
||||
class KISS():
|
||||
FEND = 0xC0
|
||||
FESC = 0xDB
|
||||
TFEND = 0xDC
|
||||
TFESC = 0xDD
|
||||
CMD_DATA = 0x00
|
||||
CMD_UNKNOWN = 0xFE
|
||||
|
||||
@staticmethod
|
||||
def escape(data):
|
||||
data = data.replace(bytes([0xdb]), bytes([0xdb, 0xdd]))
|
||||
data = data.replace(bytes([0xc0]), bytes([0xdb, 0xdc]))
|
||||
return data
|
||||
|
||||
class ThreadingTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
|
||||
pass
|
||||
|
||||
@@ -33,7 +46,7 @@ class TCPClientInterface(Interface):
|
||||
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):
|
||||
def __init__(self, owner, name, target_ip=None, target_port=None, connected_socket=None, max_reconnect_tries=None, kiss_framing=False):
|
||||
self.rxb = 0
|
||||
self.txb = 0
|
||||
|
||||
@@ -49,6 +62,7 @@ class TCPClientInterface(Interface):
|
||||
self.writing = False
|
||||
self.online = False
|
||||
self.detached = False
|
||||
self.kiss_framing = kiss_framing
|
||||
|
||||
if max_reconnect_tries == None:
|
||||
self.max_reconnect_tries = TCPClientInterface.RECONNECT_MAX_TRIES
|
||||
@@ -80,7 +94,8 @@ class TCPClientInterface(Interface):
|
||||
thread = threading.Thread(target=self.read_loop)
|
||||
thread.setDaemon(True)
|
||||
thread.start()
|
||||
self.wants_tunnel = True
|
||||
if not self.kiss_framing:
|
||||
self.wants_tunnel = True
|
||||
|
||||
|
||||
def set_timeouts_linux(self):
|
||||
@@ -173,7 +188,8 @@ class TCPClientInterface(Interface):
|
||||
thread = threading.Thread(target=self.read_loop)
|
||||
thread.setDaemon(True)
|
||||
thread.start()
|
||||
RNS.Transport.synthesize_tunnel(self)
|
||||
if not self.kiss_framing:
|
||||
RNS.Transport.synthesize_tunnel(self)
|
||||
|
||||
else:
|
||||
RNS.log("Attempt to reconnect on a non-initiator TCP interface. This should not happen.", RNS.LOG_ERROR)
|
||||
@@ -193,7 +209,12 @@ class TCPClientInterface(Interface):
|
||||
|
||||
try:
|
||||
self.writing = True
|
||||
data = bytes([HDLC.FLAG])+HDLC.escape(data)+bytes([HDLC.FLAG])
|
||||
|
||||
if self.kiss_framing:
|
||||
data = bytes([KISS.FEND])+bytes([KISS.CMD_DATA])+KISS.escape(data)+bytes([KISS.FEND])
|
||||
else:
|
||||
data = bytes([HDLC.FLAG])+HDLC.escape(data)+bytes([HDLC.FLAG])
|
||||
|
||||
self.socket.sendall(data)
|
||||
self.writing = False
|
||||
self.txb += len(data)
|
||||
@@ -211,6 +232,7 @@ class TCPClientInterface(Interface):
|
||||
in_frame = False
|
||||
escape = False
|
||||
data_buffer = b""
|
||||
command = KISS.CMD_UNKNOWN
|
||||
|
||||
while True:
|
||||
data_in = self.socket.recv(4096)
|
||||
@@ -219,23 +241,53 @@ class TCPClientInterface(Interface):
|
||||
while pointer < len(data_in):
|
||||
byte = data_in[pointer]
|
||||
pointer += 1
|
||||
if (in_frame and byte == HDLC.FLAG):
|
||||
in_frame = False
|
||||
self.processIncoming(data_buffer)
|
||||
elif (byte == HDLC.FLAG):
|
||||
in_frame = True
|
||||
data_buffer = b""
|
||||
elif (in_frame and len(data_buffer) < RNS.Reticulum.MTU):
|
||||
if (byte == HDLC.ESC):
|
||||
escape = True
|
||||
else:
|
||||
if (escape):
|
||||
if (byte == HDLC.FLAG ^ HDLC.ESC_MASK):
|
||||
byte = HDLC.FLAG
|
||||
if (byte == HDLC.ESC ^ HDLC.ESC_MASK):
|
||||
byte = HDLC.ESC
|
||||
escape = False
|
||||
data_buffer = data_buffer+bytes([byte])
|
||||
|
||||
if self.kiss_framing:
|
||||
# Read loop for KISS framing
|
||||
if (in_frame and byte == KISS.FEND and command == KISS.CMD_DATA):
|
||||
in_frame = False
|
||||
self.processIncoming(data_buffer)
|
||||
elif (byte == KISS.FEND):
|
||||
in_frame = True
|
||||
command = KISS.CMD_UNKNOWN
|
||||
data_buffer = b""
|
||||
elif (in_frame and len(data_buffer) < RNS.Reticulum.MTU):
|
||||
if (len(data_buffer) == 0 and command == KISS.CMD_UNKNOWN):
|
||||
# We only support one HDLC port for now, so
|
||||
# strip off the port nibble
|
||||
byte = byte & 0x0F
|
||||
command = byte
|
||||
elif (command == KISS.CMD_DATA):
|
||||
if (byte == KISS.FESC):
|
||||
escape = True
|
||||
else:
|
||||
if (escape):
|
||||
if (byte == KISS.TFEND):
|
||||
byte = KISS.FEND
|
||||
if (byte == KISS.TFESC):
|
||||
byte = KISS.FESC
|
||||
escape = False
|
||||
data_buffer = data_buffer+bytes([byte])
|
||||
|
||||
else:
|
||||
# Read loop for HDLC framing
|
||||
if (in_frame and byte == HDLC.FLAG):
|
||||
in_frame = False
|
||||
self.processIncoming(data_buffer)
|
||||
elif (byte == HDLC.FLAG):
|
||||
in_frame = True
|
||||
data_buffer = b""
|
||||
elif (in_frame and len(data_buffer) < RNS.Reticulum.MTU):
|
||||
if (byte == HDLC.ESC):
|
||||
escape = True
|
||||
else:
|
||||
if (escape):
|
||||
if (byte == HDLC.FLAG ^ HDLC.ESC_MASK):
|
||||
byte = HDLC.FLAG
|
||||
if (byte == HDLC.ESC ^ HDLC.ESC_MASK):
|
||||
byte = HDLC.ESC
|
||||
escape = False
|
||||
data_buffer = data_buffer+bytes([byte])
|
||||
else:
|
||||
self.online = False
|
||||
if self.initiator and not self.detached:
|
||||
@@ -286,10 +338,25 @@ class TCPClientInterface(Interface):
|
||||
class TCPServerInterface(Interface):
|
||||
@staticmethod
|
||||
def get_address_for_if(name):
|
||||
return netifaces.ifaddresses(name)[netifaces.AF_INET][0]['addr']
|
||||
import importlib
|
||||
if importlib.util.find_spec('netifaces') != None:
|
||||
import netifaces
|
||||
return netifaces.ifaddresses(name)[netifaces.AF_INET][0]['addr']
|
||||
else:
|
||||
RNS.log("Getting interface addresses from device names requires the netifaces module.", RNS.LOG_CRITICAL)
|
||||
RNS.log("You can install it with the command: python3 -m pip install netifaces", RNS.LOG_CRITICAL)
|
||||
RNS.panic()
|
||||
|
||||
@staticmethod
|
||||
def get_broadcast_for_if(name):
|
||||
return netifaces.ifaddresses(name)[netifaces.AF_INET][0]['broadcast']
|
||||
import importlib
|
||||
if importlib.util.find_spec('netifaces') != None:
|
||||
import netifaces
|
||||
return netifaces.ifaddresses(name)[netifaces.AF_INET][0]['broadcast']
|
||||
else:
|
||||
RNS.log("Getting interface addresses from device names requires the netifaces module.", RNS.LOG_CRITICAL)
|
||||
RNS.log("You can install it with the command: python3 -m pip install netifaces", RNS.LOG_CRITICAL)
|
||||
RNS.panic()
|
||||
|
||||
def __init__(self, owner, name, device=None, bindip=None, bindport=None):
|
||||
self.rxb = 0
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
from .Interface import Interface
|
||||
import socketserver
|
||||
import threading
|
||||
import netifaces
|
||||
import socket
|
||||
import time
|
||||
import sys
|
||||
@@ -12,10 +11,25 @@ class UDPInterface(Interface):
|
||||
|
||||
@staticmethod
|
||||
def get_address_for_if(name):
|
||||
return netifaces.ifaddresses(name)[netifaces.AF_INET][0]['addr']
|
||||
import importlib
|
||||
if importlib.util.find_spec('netifaces') != None:
|
||||
import netifaces
|
||||
return netifaces.ifaddresses(name)[netifaces.AF_INET][0]['addr']
|
||||
else:
|
||||
RNS.log("Getting interface addresses from device names requires the netifaces module.", RNS.LOG_CRITICAL)
|
||||
RNS.log("You can install it with the command: python3 -m pip install netifaces", RNS.LOG_CRITICAL)
|
||||
RNS.panic()
|
||||
|
||||
@staticmethod
|
||||
def get_broadcast_for_if(name):
|
||||
return netifaces.ifaddresses(name)[netifaces.AF_INET][0]['broadcast']
|
||||
import importlib
|
||||
if importlib.util.find_spec('netifaces') != None:
|
||||
import netifaces
|
||||
return netifaces.ifaddresses(name)[netifaces.AF_INET][0]['broadcast']
|
||||
else:
|
||||
RNS.log("Getting interface addresses from device names requires the netifaces module.", RNS.LOG_CRITICAL)
|
||||
RNS.log("You can install it with the command: python3 -m pip install netifaces", RNS.LOG_CRITICAL)
|
||||
RNS.panic()
|
||||
|
||||
def __init__(self, owner, name, device=None, bindip=None, bindport=None, forwardip=None, forwardport=None):
|
||||
self.rxb = 0
|
||||
@@ -44,6 +58,7 @@ class UDPInterface(Interface):
|
||||
|
||||
self.owner = owner
|
||||
address = (self.bind_ip, self.bind_port)
|
||||
socketserver.UDPServer.address_family = socket.AF_INET
|
||||
self.server = socketserver.UDPServer(address, handlerFactory(self.processIncoming))
|
||||
|
||||
thread = threading.Thread(target=self.server.serve_forever)
|
||||
|
||||
+46
-17
@@ -45,11 +45,7 @@ class Link:
|
||||
|
||||
MDU = math.floor((RNS.Reticulum.MTU-RNS.Reticulum.HEADER_MINSIZE-RNS.Identity.FERNET_OVERHEAD)/RNS.Identity.AES128_BLOCKSIZE)*RNS.Identity.AES128_BLOCKSIZE - 1
|
||||
|
||||
# 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
|
||||
ESTABLISHMENT_TIMEOUT_PER_HOP = RNS.Reticulum.DEFAULT_PER_HOP_TIMEOUT
|
||||
"""
|
||||
Default timeout for link establishment in seconds per hop to destination.
|
||||
"""
|
||||
@@ -99,7 +95,7 @@ class Link:
|
||||
|
||||
except Exception as e:
|
||||
RNS.log("Validating link request failed", RNS.LOG_VERBOSE)
|
||||
traceback.print_exc()
|
||||
RNS.log("exc: "+str(e))
|
||||
return None
|
||||
|
||||
else:
|
||||
@@ -127,6 +123,7 @@ class Link:
|
||||
self.keepalive = Link.KEEPALIVE
|
||||
self.watchdog_lock = False
|
||||
self.status = Link.PENDING
|
||||
self.activated_at = None
|
||||
self.type = RNS.Destination.LINK
|
||||
self.owner = owner
|
||||
self.destination = destination
|
||||
@@ -218,6 +215,7 @@ class Link:
|
||||
proof.send()
|
||||
self.had_outbound()
|
||||
|
||||
|
||||
def prove_packet(self, packet):
|
||||
signature = self.sign(packet.packet_hash)
|
||||
# TODO: Hardcoded as explicit proof for now
|
||||
@@ -249,6 +247,7 @@ class Link:
|
||||
self.had_outbound()
|
||||
|
||||
self.status = Link.ACTIVE
|
||||
self.activated_at = time.time()
|
||||
if self.callbacks.link_established != None:
|
||||
thread = threading.Thread(target=self.callbacks.link_established, args=(self,))
|
||||
thread.setDaemon(True)
|
||||
@@ -292,7 +291,7 @@ class Link:
|
||||
packed_request = umsgpack.packb(unpacked_request)
|
||||
|
||||
if timeout == None:
|
||||
timeout = self.rtt * self.traffic_timeout_factor + RNS.Resource.RESPONSE_MAX_GRACE_TIME
|
||||
timeout = self.rtt * self.traffic_timeout_factor + RNS.Resource.RESPONSE_MAX_GRACE_TIME/4.0
|
||||
|
||||
if len(packed_request) <= Link.MDU:
|
||||
request_packet = RNS.Packet(self, packed_request, RNS.Packet.DATA, context = RNS.Packet.REQUEST)
|
||||
@@ -338,6 +337,8 @@ class Link:
|
||||
rtt = umsgpack.unpackb(plaintext)
|
||||
self.rtt = max(measured_rtt, rtt)
|
||||
self.status = Link.ACTIVE
|
||||
self.activated_at = time.time()
|
||||
|
||||
|
||||
if self.owner.callbacks.link_established != None:
|
||||
self.owner.callbacks.link_established(self)
|
||||
@@ -425,7 +426,11 @@ class Link:
|
||||
self.destination.links.remove(self)
|
||||
|
||||
if self.callbacks.link_closed != None:
|
||||
self.callbacks.link_closed(self)
|
||||
try:
|
||||
self.callbacks.link_closed(self)
|
||||
except Exception as e:
|
||||
RNS.log("Error while executing link closed callback from "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
|
||||
|
||||
def start_watchdog(self):
|
||||
thread = threading.Thread(target=self.__watchdog_job)
|
||||
@@ -598,7 +603,10 @@ class Link:
|
||||
|
||||
elif self.destination.proof_strategy == RNS.Destination.PROVE_APP:
|
||||
if self.destination.callbacks.proof_requested:
|
||||
self.destination.callbacks.proof_requested(packet)
|
||||
try:
|
||||
self.destination.callbacks.proof_requested(packet)
|
||||
except Exception as e:
|
||||
RNS.log("Error while executing proof request callback from "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
|
||||
elif packet.context == RNS.Packet.LINKIDENTIFY:
|
||||
plaintext = self.decrypt(packet.data)
|
||||
@@ -613,7 +621,10 @@ class Link:
|
||||
if identity.validate(signature, signed_data):
|
||||
self.__remote_identity = identity
|
||||
if self.callbacks.remote_identified != None:
|
||||
self.callbacks.remote_identified(self.__remote_identity)
|
||||
try:
|
||||
self.callbacks.remote_identified(self.__remote_identity)
|
||||
except Exception as e:
|
||||
RNS.log("Error while executing remote identified callback from "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
|
||||
elif packet.context == RNS.Packet.REQUEST:
|
||||
try:
|
||||
@@ -659,8 +670,11 @@ class Link:
|
||||
pass
|
||||
elif self.resource_strategy == Link.ACCEPT_APP:
|
||||
if self.callbacks.resource != None:
|
||||
if self.callbacks.resource(resource):
|
||||
RNS.Resource.accept(packet, self.callbacks.resource_concluded)
|
||||
try:
|
||||
if self.callbacks.resource(resource):
|
||||
RNS.Resource.accept(packet, self.callbacks.resource_concluded)
|
||||
except Exception as e:
|
||||
RNS.log("Error while executing resource accept callback from "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
elif self.resource_strategy == Link.ACCEPT_ALL:
|
||||
RNS.Resource.accept(packet, self.callbacks.resource_concluded)
|
||||
|
||||
@@ -933,7 +947,10 @@ class RequestReceipt():
|
||||
self.link.pending_requests.remove(self)
|
||||
|
||||
if self.callbacks.failed != None:
|
||||
self.callbacks.failed(self)
|
||||
try:
|
||||
self.callbacks.failed(self)
|
||||
except Exception as e:
|
||||
RNS.log("Error while executing request failed callback from "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
|
||||
|
||||
def __response_timeout_job(self):
|
||||
@@ -951,7 +968,10 @@ class RequestReceipt():
|
||||
self.link.pending_requests.remove(self)
|
||||
|
||||
if self.callbacks.failed != None:
|
||||
self.callbacks.failed(self)
|
||||
try:
|
||||
self.callbacks.failed(self)
|
||||
except Exception as e:
|
||||
RNS.log("Error while executing request timed out callback from "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
|
||||
|
||||
def response_resource_progress(self, resource):
|
||||
@@ -967,7 +987,10 @@ class RequestReceipt():
|
||||
self.progress = resource.get_progress()
|
||||
|
||||
if self.callbacks.progress != None:
|
||||
self.callbacks.progress(self)
|
||||
try:
|
||||
self.callbacks.progress(self)
|
||||
except Exception as e:
|
||||
RNS.log("Error while executing response progress callback from "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
else:
|
||||
resource.cancel()
|
||||
|
||||
@@ -987,10 +1010,16 @@ class RequestReceipt():
|
||||
self.packet_receipt.callbacks.delivery(self.packet_receipt)
|
||||
|
||||
if self.callbacks.progress != None:
|
||||
self.callbacks.progress(self)
|
||||
try:
|
||||
self.callbacks.progress(self)
|
||||
except Exception as e:
|
||||
RNS.log("Error while executing response progress callback from "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
|
||||
if self.callbacks.response != None:
|
||||
self.callbacks.response(self)
|
||||
try:
|
||||
self.callbacks.response(self)
|
||||
except Exception as e:
|
||||
RNS.log("Error while executing response received callback from "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
|
||||
def get_request_id(self):
|
||||
"""
|
||||
|
||||
+24
-9
@@ -75,9 +75,7 @@ class Packet:
|
||||
The maximum size of the payload data in a single unencrypted packet
|
||||
"""
|
||||
|
||||
# This value is set at a reasonable
|
||||
# level for a 1 Kb/s channel.
|
||||
TIMEOUT_PER_HOP = 5
|
||||
TIMEOUT_PER_HOP = RNS.Reticulum.DEFAULT_PER_HOP_TIMEOUT
|
||||
|
||||
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:
|
||||
@@ -113,6 +111,8 @@ class Packet:
|
||||
|
||||
self.attached_interface = attached_interface
|
||||
self.receiving_interface = None
|
||||
self.rssi = None
|
||||
self.snr = None
|
||||
|
||||
def get_packed_flags(self):
|
||||
if self.context == Packet.LRPROOF:
|
||||
@@ -328,6 +328,7 @@ class PacketReceipt:
|
||||
self.destination = packet.destination
|
||||
self.callbacks = PacketReceiptCallbacks()
|
||||
self.concluded_at = None
|
||||
self.proof_packet = None
|
||||
|
||||
if packet.destination.type == RNS.Destination.LINK:
|
||||
self.timeout = packet.destination.rtt * packet.destination.traffic_timeout_factor
|
||||
@@ -344,12 +345,12 @@ class PacketReceipt:
|
||||
# Validate a proof packet
|
||||
def validate_proof_packet(self, proof_packet):
|
||||
if hasattr(proof_packet, "link") and proof_packet.link:
|
||||
return self.validate_link_proof(proof_packet.data, proof_packet.link)
|
||||
return self.validate_link_proof(proof_packet.data, proof_packet.link, proof_packet)
|
||||
else:
|
||||
return self.validate_proof(proof_packet.data)
|
||||
return self.validate_proof(proof_packet.data, proof_packet)
|
||||
|
||||
# Validate a raw proof for a link
|
||||
def validate_link_proof(self, proof, link):
|
||||
def validate_link_proof(self, proof, link, proof_packet=None):
|
||||
# TODO: Hardcoded as explicit proofs for now
|
||||
if True or len(proof) == PacketReceipt.EXPL_LENGTH:
|
||||
# This is an explicit proof
|
||||
@@ -361,6 +362,8 @@ class PacketReceipt:
|
||||
self.status = PacketReceipt.DELIVERED
|
||||
self.proved = True
|
||||
self.concluded_at = time.time()
|
||||
self.proof_packet = proof_packet
|
||||
|
||||
if self.callbacks.delivery != None:
|
||||
self.callbacks.delivery(self)
|
||||
return True
|
||||
@@ -388,7 +391,7 @@ class PacketReceipt:
|
||||
return False
|
||||
|
||||
# Validate a raw proof
|
||||
def validate_proof(self, proof):
|
||||
def validate_proof(self, proof, proof_packet=None):
|
||||
if len(proof) == PacketReceipt.EXPL_LENGTH:
|
||||
# This is an explicit proof
|
||||
proof_hash = proof[:RNS.Identity.HASHLENGTH//8]
|
||||
@@ -399,8 +402,14 @@ class PacketReceipt:
|
||||
self.status = PacketReceipt.DELIVERED
|
||||
self.proved = True
|
||||
self.concluded_at = time.time()
|
||||
self.proof_packet = proof_packet
|
||||
|
||||
if self.callbacks.delivery != None:
|
||||
self.callbacks.delivery(self)
|
||||
try:
|
||||
self.callbacks.delivery(self)
|
||||
except Exception as e:
|
||||
RNS.log("Error while executing proof validated callback. The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
@@ -417,8 +426,14 @@ class PacketReceipt:
|
||||
self.status = PacketReceipt.DELIVERED
|
||||
self.proved = True
|
||||
self.concluded_at = time.time()
|
||||
self.proof_packet = proof_packet
|
||||
|
||||
if self.callbacks.delivery != None:
|
||||
self.callbacks.delivery(self)
|
||||
try:
|
||||
self.callbacks.delivery(self)
|
||||
except Exception as e:
|
||||
RNS.log("Error while executing proof validated callback. The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
+25
-27
@@ -123,7 +123,10 @@ class Resource:
|
||||
|
||||
RNS.log("Accepting resource advertisement for "+RNS.prettyhexrep(resource.hash), RNS.LOG_DEBUG)
|
||||
if resource.link.callbacks.resource_started != None:
|
||||
resource.link.callbacks.resource_started(resource)
|
||||
try:
|
||||
resource.link.callbacks.resource_started(resource)
|
||||
except Exception as e:
|
||||
RNS.log("Error while executing resource started callback from "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
|
||||
resource.hashmap_update(0, resource.hashmap_raw)
|
||||
|
||||
@@ -321,10 +324,6 @@ class Resource:
|
||||
self.request_next()
|
||||
|
||||
def get_map_hash(self, data):
|
||||
# TODO: This will break if running unencrypted,
|
||||
# uncompressed transfers on streams with long blocks
|
||||
# of identical bytes. Doing so would be very silly
|
||||
# anyways but maybe it should be handled gracefully.
|
||||
return RNS.Identity.full_hash(data+self.random_hash)[:Resource.MAPHASH_LEN]
|
||||
|
||||
def advertise(self):
|
||||
@@ -405,15 +404,6 @@ class Resource:
|
||||
|
||||
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:
|
||||
RNS.log("Timed out waiting for parts, requesting retry", RNS.LOG_DEBUG)
|
||||
@@ -506,7 +496,10 @@ class Resource:
|
||||
if self.segment_index == self.total_segments:
|
||||
if self.callback != None:
|
||||
self.data = open(self.storagepath, "rb")
|
||||
self.callback(self)
|
||||
try:
|
||||
self.callback(self)
|
||||
except Exception as e:
|
||||
RNS.log("Error while executing resource assembled callback from "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
|
||||
try:
|
||||
self.data.close()
|
||||
@@ -540,7 +533,10 @@ class Resource:
|
||||
# If all segments were processed, we'll
|
||||
# signal that the resource sending concluded
|
||||
if self.callback != None:
|
||||
self.callback(self)
|
||||
try:
|
||||
self.callback(self)
|
||||
except Exception as e:
|
||||
RNS.log("Error while executing resource concluded callback from "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
else:
|
||||
# Otherwise we'll recursively create the
|
||||
# next segment of the resource
|
||||
@@ -596,19 +592,15 @@ class Resource:
|
||||
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))
|
||||
try:
|
||||
self.__progress_callback(self)
|
||||
except Exception as e:
|
||||
RNS.log("Error while executing progress callback from "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
|
||||
i += 1
|
||||
|
||||
self.receiving_part = False
|
||||
|
||||
# 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:
|
||||
@@ -754,7 +746,10 @@ class Resource:
|
||||
self.status = Resource.AWAITING_PROOF
|
||||
|
||||
if self.__progress_callback != None:
|
||||
self.__progress_callback(self)
|
||||
try:
|
||||
self.__progress_callback(self)
|
||||
except Exception as e:
|
||||
RNS.log("Error while executing progress callback from "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
|
||||
def cancel(self):
|
||||
"""
|
||||
@@ -774,8 +769,11 @@ class Resource:
|
||||
self.link.cancel_incoming_resource(self)
|
||||
|
||||
if self.callback != None:
|
||||
self.link.resource_concluded(self)
|
||||
self.callback(self)
|
||||
try:
|
||||
self.link.resource_concluded(self)
|
||||
self.callback(self)
|
||||
except Exception as e:
|
||||
RNS.log("Error while executing callbacks on resource cancel from "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
|
||||
def set_callback(self, callback):
|
||||
self.callback = callback
|
||||
|
||||
+116
-20
@@ -1,8 +1,14 @@
|
||||
from .Interfaces import *
|
||||
from .vendor.platformutils import get_platform
|
||||
|
||||
if get_platform() == "android":
|
||||
# TODO: Selectively import Android-relevant interfaces
|
||||
pass
|
||||
else:
|
||||
from .Interfaces import *
|
||||
|
||||
from .vendor.configobj import ConfigObj
|
||||
import configparser
|
||||
import multiprocessing.connection
|
||||
import RNS
|
||||
import signal
|
||||
import threading
|
||||
import atexit
|
||||
@@ -53,6 +59,14 @@ class Reticulum:
|
||||
the default value.
|
||||
"""
|
||||
|
||||
# TODO: To reach the 300bps level without unreasonably impacting
|
||||
# performance on faster links, we need a mechanism for setting
|
||||
# this value more intelligently. One option could be inferring it
|
||||
# from interface speed, but a better general approach would most
|
||||
# probably be to let Reticulum somehow continously build a map of
|
||||
# per-hop latencies and use this map for the timeout calculation.
|
||||
DEFAULT_PER_HOP_TIMEOUT = 5
|
||||
|
||||
# Length of truncated hashes in bits.
|
||||
TRUNCATED_HASHLENGTH = 80
|
||||
|
||||
@@ -87,6 +101,12 @@ class Reticulum:
|
||||
RNS.exit()
|
||||
|
||||
|
||||
@staticmethod
|
||||
def sigterm_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
|
||||
@@ -146,9 +166,9 @@ class Reticulum:
|
||||
else:
|
||||
RNS.log("Could not load config file, creating default configuration file...")
|
||||
self.__create_default_config()
|
||||
RNS.log("Default config file created. Make any necessary changes in "+Reticulum.configdir+"/config and start Reticulum again.")
|
||||
RNS.log("Exiting now!")
|
||||
exit(1)
|
||||
RNS.log("Default config file created. Make any necessary changes in "+Reticulum.configdir+"/config and restart Reticulum if needed.")
|
||||
import time
|
||||
time.sleep(1.5)
|
||||
|
||||
self.__apply_config()
|
||||
RNS.log("Configuration loaded from "+self.configpath, RNS.LOG_VERBOSE)
|
||||
@@ -168,6 +188,7 @@ class Reticulum:
|
||||
|
||||
atexit.register(Reticulum.exit_handler)
|
||||
signal.signal(signal.SIGINT, Reticulum.sigint_handler)
|
||||
signal.signal(signal.SIGTERM, Reticulum.sigterm_handler)
|
||||
|
||||
def __start_local_interface(self):
|
||||
if self.share_instance:
|
||||
@@ -254,6 +275,33 @@ class Reticulum:
|
||||
|
||||
try:
|
||||
if ("interface_enabled" in c) and c.as_bool("interface_enabled") == True:
|
||||
if c["type"] == "AutoInterface":
|
||||
group_id = c["group_id"] if "group_id" in c else None
|
||||
discovery_scope = c["discovery_scope"] if "discovery_scope" in c else None
|
||||
discovery_port = int(c["discovery_port"]) if "discovery_port" in c else None
|
||||
data_port = int(c["data_port"]) if "data_port" in c else None
|
||||
allowed_interfaces = c.as_list("devices") if "devices" in c else None
|
||||
ignored_interfaces = c.as_list("ignored_devices") if "ignored_devices" in c else None
|
||||
|
||||
interface = AutoInterface.AutoInterface(
|
||||
RNS.Transport,
|
||||
name,
|
||||
group_id,
|
||||
discovery_scope,
|
||||
discovery_port,
|
||||
data_port,
|
||||
allowed_interfaces,
|
||||
ignored_interfaces
|
||||
)
|
||||
|
||||
if "outgoing" in c and c.as_bool("outgoing") == True:
|
||||
interface.OUT = True
|
||||
else:
|
||||
interface.OUT = False
|
||||
|
||||
RNS.Transport.interfaces.append(interface)
|
||||
|
||||
|
||||
if c["type"] == "UDPInterface":
|
||||
device = c["device"] if "device" in c else None
|
||||
port = int(c["port"]) if "port" in c else None
|
||||
@@ -312,11 +360,16 @@ class Reticulum:
|
||||
|
||||
|
||||
if c["type"] == "TCPClientInterface":
|
||||
kiss_framing = False
|
||||
if "kiss_framing" in c and c.as_bool("kiss_framing") == True:
|
||||
kiss_framing = True
|
||||
|
||||
interface = TCPInterface.TCPClientInterface(
|
||||
RNS.Transport,
|
||||
name,
|
||||
c["target_host"],
|
||||
int(c["target_port"])
|
||||
int(c["target_port"]),
|
||||
kiss_framing = kiss_framing
|
||||
)
|
||||
|
||||
if "outgoing" in c and c.as_bool("outgoing") == True:
|
||||
@@ -473,7 +526,7 @@ class Reticulum:
|
||||
|
||||
RNS.Transport.interfaces.append(interface)
|
||||
else:
|
||||
RNS.log("Skipping disabled interface \""+name+"\"", RNS.LOG_INFO)
|
||||
RNS.log("Skipping disabled interface \""+name+"\"", RNS.LOG_DEBUG)
|
||||
|
||||
except Exception as e:
|
||||
RNS.log("The interface \""+name+"\" could not be created. Check your configuration file for errors!", RNS.LOG_ERROR)
|
||||
@@ -491,7 +544,6 @@ class Reticulum:
|
||||
if not os.path.isdir(Reticulum.configdir):
|
||||
os.makedirs(Reticulum.configdir)
|
||||
self.config.write()
|
||||
self.__apply_config()
|
||||
|
||||
def rpc_loop(self):
|
||||
while True:
|
||||
@@ -511,6 +563,12 @@ class Reticulum:
|
||||
if path == "next_hop":
|
||||
rpc_connection.send(self.get_next_hop(call["destination_hash"]))
|
||||
|
||||
if path == "packet_rssi":
|
||||
rpc_connection.send(self.get_packet_rssi(call["packet_hash"]))
|
||||
|
||||
if path == "packet_snr":
|
||||
rpc_connection.send(self.get_packet_snr(call["packet_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)
|
||||
@@ -545,6 +603,7 @@ class Reticulum:
|
||||
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))
|
||||
|
||||
@@ -554,9 +613,38 @@ class Reticulum:
|
||||
rpc_connection.send({"get": "next_hop", "destination_hash": destination})
|
||||
response = rpc_connection.recv()
|
||||
return response
|
||||
|
||||
else:
|
||||
return RNS.Transport.next_hop(destination)
|
||||
|
||||
def get_packet_rssi(self, packet_hash):
|
||||
if self.is_connected_to_shared_instance:
|
||||
rpc_connection = multiprocessing.connection.Client(self.rpc_addr, authkey=self.rpc_key)
|
||||
rpc_connection.send({"get": "packet_rssi", "packet_hash": packet_hash})
|
||||
response = rpc_connection.recv()
|
||||
return response
|
||||
|
||||
else:
|
||||
for entry in RNS.Transport.local_client_rssi_cache:
|
||||
if entry[0] == packet_hash:
|
||||
return entry[1]
|
||||
|
||||
return None
|
||||
|
||||
def get_packet_snr(self, packet_hash):
|
||||
if self.is_connected_to_shared_instance:
|
||||
rpc_connection = multiprocessing.connection.Client(self.rpc_addr, authkey=self.rpc_key)
|
||||
rpc_connection.send({"get": "packet_snr", "packet_hash": packet_hash})
|
||||
response = rpc_connection.recv()
|
||||
return response
|
||||
|
||||
else:
|
||||
for entry in RNS.Transport.local_client_snr_cache:
|
||||
if entry[0] == packet_hash:
|
||||
return entry[1]
|
||||
|
||||
return None
|
||||
|
||||
|
||||
@staticmethod
|
||||
def should_use_implicit_proof():
|
||||
@@ -651,16 +739,26 @@ loglevel = 4
|
||||
[interfaces]
|
||||
|
||||
# This interface enables communication with other
|
||||
# local Reticulum nodes over UDP. You can modify it
|
||||
# to suit your needs or turn it off completely.
|
||||
# As a minimum, you should probably specify the
|
||||
# network device you want to communicate on, such
|
||||
# as eth0 or wlan0.
|
||||
|
||||
[[Default UDP Interface]]
|
||||
type = UDPInterface
|
||||
# link-local Reticulum nodes over UDP. It does not
|
||||
# need any functional IP infrastructure like routers
|
||||
# or DHCP servers, but will require that at least link-
|
||||
# local IPv6 is enabled in your operating system, which
|
||||
# should be enabled by default in almost any OS. See
|
||||
# the Reticulum Manual for more configuration options.
|
||||
|
||||
[[Default Interface]]
|
||||
type = AutoInterface
|
||||
interface_enabled = True
|
||||
outgoing = True
|
||||
|
||||
|
||||
# The following example enables communication with other
|
||||
# local Reticulum peers using UDP broadcasts.
|
||||
|
||||
[[UDP Interface]]
|
||||
type = UDPInterface
|
||||
interface_enabled = False
|
||||
outgoing = True
|
||||
listen_ip = 0.0.0.0
|
||||
listen_port = 4242
|
||||
forward_ip = 255.255.255.255
|
||||
@@ -668,9 +766,7 @@ loglevel = 4
|
||||
|
||||
# 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.
|
||||
# IP interfaces.
|
||||
|
||||
# Instead of specifying listen_ip, listen_port,
|
||||
# forward_ip and forward_port, you can also bind
|
||||
@@ -914,4 +1010,4 @@ loglevel = 4
|
||||
persistence = 200
|
||||
slottime = 20
|
||||
|
||||
'''.splitlines()
|
||||
'''.splitlines()
|
||||
|
||||
+102
-13
@@ -76,6 +76,12 @@ class Transport:
|
||||
# Reticulum instance
|
||||
local_client_interfaces = []
|
||||
|
||||
local_client_rssi_cache = []
|
||||
local_client_snr_cache = []
|
||||
LOCAL_CLIENT_CACHE_MAXSIZE = 512
|
||||
|
||||
pending_local_path_requests = {}
|
||||
|
||||
jobs_locked = False
|
||||
jobs_running = False
|
||||
job_interval = 0.250
|
||||
@@ -583,10 +589,29 @@ class Transport:
|
||||
packet.receiving_interface = interface
|
||||
packet.hops += 1
|
||||
|
||||
if len(Transport.local_client_interfaces) > 0:
|
||||
if interface != None:
|
||||
if hasattr(interface, "r_stat_rssi"):
|
||||
if interface.r_stat_rssi != None:
|
||||
packet.rssi = interface.r_stat_rssi
|
||||
if len(Transport.local_client_interfaces) > 0:
|
||||
Transport.local_client_rssi_cache.append([packet.packet_hash, packet.rssi])
|
||||
|
||||
while len(Transport.local_client_rssi_cache) > Transport.LOCAL_CLIENT_CACHE_MAXSIZE:
|
||||
Transport.local_client_rssi_cache.pop()
|
||||
|
||||
if hasattr(interface, "r_stat_snr"):
|
||||
if interface.r_stat_rssi != None:
|
||||
packet.snr = interface.r_stat_snr
|
||||
if len(Transport.local_client_interfaces) > 0:
|
||||
Transport.local_client_snr_cache.append([packet.packet_hash, packet.snr])
|
||||
|
||||
while len(Transport.local_client_snr_cache) > Transport.LOCAL_CLIENT_CACHE_MAXSIZE:
|
||||
Transport.local_client_snr_cache.pop()
|
||||
|
||||
if len(Transport.local_client_interfaces) > 0:
|
||||
if Transport.is_local_client_interface(interface):
|
||||
packet.hops -= 1
|
||||
|
||||
elif Transport.interface_to_shared_instance(interface):
|
||||
packet.hops -= 1
|
||||
|
||||
@@ -845,11 +870,11 @@ class Transport:
|
||||
random_blobs.append(random_blob)
|
||||
|
||||
if (RNS.Reticulum.transport_enabled() or Transport.from_local_client(packet)) and packet.context != RNS.Packet.PATH_RESPONSE:
|
||||
# If the announce is from a local client,
|
||||
# we announce it immediately, but only one
|
||||
# time.
|
||||
# Insert announce into announce table for retransmission
|
||||
|
||||
if Transport.from_local_client(packet):
|
||||
# If the announce is from a local client,
|
||||
# it is announced immediately, but only one time.
|
||||
retransmit_timeout = now
|
||||
retries = Transport.PATHFINDER_R
|
||||
|
||||
@@ -865,6 +890,29 @@ class Transport:
|
||||
attached_interface
|
||||
]
|
||||
|
||||
# TODO: Check from_local_client once and store result
|
||||
elif Transport.from_local_client(packet) and packet.context == RNS.Packet.PATH_RESPONSE:
|
||||
# If this is a path response from a local client,
|
||||
# check if any external interfaces have pending
|
||||
# path requests.
|
||||
if packet.destination_hash in Transport.pending_local_path_requests:
|
||||
desiring_interface = Transport.pending_local_path_requests.pop(packet.destination_hash)
|
||||
retransmit_timeout = now
|
||||
retries = Transport.PATHFINDER_R
|
||||
|
||||
Transport.announce_table[packet.destination_hash] = [
|
||||
now,
|
||||
retransmit_timeout,
|
||||
retries,
|
||||
received_from,
|
||||
announce_hops,
|
||||
packet,
|
||||
local_rebroadcasts,
|
||||
block_rebroadcasts,
|
||||
attached_interface
|
||||
]
|
||||
|
||||
|
||||
# If we have any local clients connected, we re-
|
||||
# transmit the announce to them immediately
|
||||
if (len(Transport.local_client_interfaces)):
|
||||
@@ -975,8 +1023,11 @@ class Transport:
|
||||
|
||||
elif destination.proof_strategy == RNS.Destination.PROVE_APP:
|
||||
if destination.callbacks.proof_requested:
|
||||
if destination.callbacks.proof_requested(packet):
|
||||
packet.prove()
|
||||
try:
|
||||
if destination.callbacks.proof_requested(packet):
|
||||
packet.prove()
|
||||
except Exception as e:
|
||||
RNS.log("Error while executing proof request callback. The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
|
||||
# Handling for proofs and link-request proofs
|
||||
elif packet.packet_type == RNS.Packet.PROOF:
|
||||
@@ -1367,24 +1418,37 @@ class Transport:
|
||||
|
||||
@staticmethod
|
||||
def path_request_handler(data, packet):
|
||||
if len(data) >= RNS.Identity.TRUNCATED_HASHLENGTH//8:
|
||||
Transport.path_request(
|
||||
data[:RNS.Identity.TRUNCATED_HASHLENGTH//8],
|
||||
Transport.from_local_client(packet),
|
||||
packet.receiving_interface
|
||||
)
|
||||
try:
|
||||
if len(data) >= RNS.Identity.TRUNCATED_HASHLENGTH//8:
|
||||
Transport.path_request(
|
||||
data[:RNS.Identity.TRUNCATED_HASHLENGTH//8],
|
||||
Transport.from_local_client(packet),
|
||||
packet.receiving_interface
|
||||
)
|
||||
except Exception as e:
|
||||
RNS.log("Error while handling path request. The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
|
||||
@staticmethod
|
||||
def path_request(destination_hash, is_from_local_client, attached_interface):
|
||||
RNS.log("Path request for "+RNS.prettyhexrep(destination_hash), RNS.LOG_DEBUG)
|
||||
|
||||
destination_exists_on_local_client = False
|
||||
if len(Transport.local_client_interfaces) > 0:
|
||||
if destination_hash in Transport.destination_table:
|
||||
destination_interface = Transport.destination_table[destination_hash][5]
|
||||
|
||||
if Transport.is_local_client_interface(destination_interface):
|
||||
destination_exists_on_local_client = True
|
||||
Transport.pending_local_path_requests[destination_hash] = attached_interface
|
||||
|
||||
local_destination = next((d for d in Transport.destinations if d.hash == destination_hash), None)
|
||||
if local_destination != None:
|
||||
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 or len(Transport.local_client_interfaces) > 0) and destination_hash in Transport.destination_table:
|
||||
elif (RNS.Reticulum.transport_enabled() or is_from_local_client) 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]
|
||||
|
||||
@@ -1461,6 +1525,31 @@ class Transport:
|
||||
interface.detach()
|
||||
|
||||
|
||||
@staticmethod
|
||||
def shared_connection_disappeared():
|
||||
for link in Transport.active_links:
|
||||
link.teardown()
|
||||
|
||||
for link in Transport.pending_links:
|
||||
link.teardown()
|
||||
|
||||
Transport.announce_table = {}
|
||||
Transport.destination_table = {}
|
||||
Transport.reverse_table = {}
|
||||
Transport.link_table = {}
|
||||
Transport.held_announces = {}
|
||||
Transport.announce_handlers = []
|
||||
Transport.tunnels = {}
|
||||
|
||||
|
||||
@staticmethod
|
||||
def shared_connection_reappeared():
|
||||
if Transport.owner.is_connected_to_shared_instance:
|
||||
for registered_destination in Transport.destinations:
|
||||
if registered_destination.type == RNS.Destination.SINGLE:
|
||||
registered_destination.announce(path_response=True)
|
||||
|
||||
|
||||
@staticmethod
|
||||
def exit_handler():
|
||||
try:
|
||||
|
||||
@@ -101,11 +101,31 @@ def program_setup(configdir, destination_hexhash, size=DEFAULT_PROBE_SIZE, full_
|
||||
rtt = round(rtt*1000, 3)
|
||||
rttstring = str(rtt)+" milliseconds"
|
||||
|
||||
reception_stats = ""
|
||||
if reticulum.is_connected_to_shared_instance:
|
||||
reception_rssi = reticulum.get_packet_rssi(receipt.proof_packet.packet_hash)
|
||||
reception_snr = reticulum.get_packet_snr(receipt.proof_packet.packet_hash)
|
||||
|
||||
if reception_rssi != None:
|
||||
reception_stats += " [RSSI "+str(reception_rssi)+" dBm]"
|
||||
|
||||
if reception_snr != None:
|
||||
reception_stats += " [SNR "+str(reception_snr)+" dB]"
|
||||
|
||||
else:
|
||||
if receipt.proof_packet != None:
|
||||
if receipt.proof_packet.rssi != None:
|
||||
reception_stats += " [RSSI "+str(receipt.proof_packet.rssi)+" dBm]"
|
||||
|
||||
if receipt.proof_packet.snr != None:
|
||||
reception_stats += " [SNR "+str(receipt.proof_packet.snr)+" dB]"
|
||||
|
||||
print(
|
||||
"Valid reply received from "+
|
||||
RNS.prettyhexrep(receipt.destination.hash)+
|
||||
"\nRound-trip time is "+rttstring+
|
||||
" over "+str(hops)+" hop"+ms
|
||||
" over "+str(hops)+" hop"+ms+
|
||||
reception_stats
|
||||
)
|
||||
|
||||
|
||||
|
||||
Regular → Executable
+13
-4
@@ -2,15 +2,23 @@
|
||||
|
||||
import RNS
|
||||
import argparse
|
||||
import time
|
||||
|
||||
from RNS._version import __version__
|
||||
|
||||
|
||||
def program_setup(configdir, verbosity = 0, quietness = 0):
|
||||
reticulum = RNS.Reticulum(configdir = configdir, loglevel = 3+verbosity-quietness)
|
||||
def program_setup(configdir, verbosity = 0, quietness = 0, service = False):
|
||||
targetloglevel = 3+verbosity-quietness
|
||||
|
||||
if service:
|
||||
RNS.logdest = RNS.LOG_FILE
|
||||
RNS.logfile = RNS.Reticulum.configdir+"/logfile"
|
||||
targetloglevel = None
|
||||
|
||||
reticulum = RNS.Reticulum(configdir=configdir, loglevel=targetloglevel)
|
||||
RNS.log("Started rnsd version {version}".format(version=__version__), RNS.LOG_NOTICE)
|
||||
while True:
|
||||
input()
|
||||
time.sleep(1)
|
||||
|
||||
def main():
|
||||
try:
|
||||
@@ -18,6 +26,7 @@ def main():
|
||||
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('-s', '--service', action='store_true', default=False, help="rnsd is running as a service and should log to file")
|
||||
parser.add_argument("--version", action="version", version="rnsd {version}".format(version=__version__))
|
||||
|
||||
args = parser.parse_args()
|
||||
@@ -27,7 +36,7 @@ def main():
|
||||
else:
|
||||
configarg = None
|
||||
|
||||
program_setup(configdir = configarg, verbosity=args.verbose, quietness=args.quiet)
|
||||
program_setup(configdir = configarg, verbosity=args.verbose, quietness=args.quiet, service=args.service)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("")
|
||||
|
||||
@@ -67,6 +67,10 @@ def loglevelname(level):
|
||||
def version():
|
||||
return __version__
|
||||
|
||||
def host_os():
|
||||
from .vendor.platformutils import get_platform
|
||||
return get_platform()
|
||||
|
||||
def log(msg, level=3, _override_destination = False):
|
||||
global _always_override_destination
|
||||
|
||||
@@ -120,4 +124,5 @@ def panic():
|
||||
os._exit(255)
|
||||
|
||||
def exit():
|
||||
print("")
|
||||
sys.exit(0)
|
||||
+1
-1
@@ -1 +1 @@
|
||||
__version__ = "0.2.8"
|
||||
__version__ = "0.3.0"
|
||||
Vendored
+7
@@ -0,0 +1,7 @@
|
||||
def get_platform():
|
||||
from os import environ
|
||||
if 'ANDROID_ARGUMENT' in environ:
|
||||
return 'android'
|
||||
else:
|
||||
import sys
|
||||
return sys.platform
|
||||
Vendored
+252
-128
@@ -1,4 +1,4 @@
|
||||
# u-msgpack-python v2.5.0 - v at sergeev.io
|
||||
# u-msgpack-python v2.7.1 - v at sergeev.io
|
||||
# https://github.com/vsergeev/u-msgpack-python
|
||||
#
|
||||
# u-msgpack-python is a lightweight MessagePack serializer and deserializer
|
||||
@@ -10,7 +10,7 @@
|
||||
#
|
||||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2013-2016 vsergeev / Ivan (Vanya) A. Sergeev
|
||||
# Copyright (c) 2013-2020 vsergeev / Ivan (Vanya) A. Sergeev
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -31,7 +31,7 @@
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
"""
|
||||
u-msgpack-python v2.5.0 - v at sergeev.io
|
||||
u-msgpack-python v2.7.1 - v at sergeev.io
|
||||
https://github.com/vsergeev/u-msgpack-python
|
||||
|
||||
u-msgpack-python is a lightweight MessagePack serializer and deserializer
|
||||
@@ -49,10 +49,15 @@ import datetime
|
||||
import sys
|
||||
import io
|
||||
|
||||
__version__ = "2.5.0"
|
||||
if sys.version_info[0:2] >= (3, 3):
|
||||
from collections.abc import Hashable
|
||||
else:
|
||||
from collections import Hashable
|
||||
|
||||
__version__ = "2.7.1"
|
||||
"Module version string"
|
||||
|
||||
version = (2, 5, 0)
|
||||
version = (2, 7, 1)
|
||||
"Module version tuple"
|
||||
|
||||
|
||||
@@ -61,7 +66,7 @@ version = (2, 5, 0)
|
||||
##############################################################################
|
||||
|
||||
# Extension type for application-defined types and data
|
||||
class Ext:
|
||||
class Ext(object):
|
||||
"""
|
||||
The Ext class facilitates creating a serializable extension object to store
|
||||
an application-defined type and data byte array.
|
||||
@@ -75,23 +80,33 @@ class Ext:
|
||||
type: application-defined type integer
|
||||
data: application-defined data byte array
|
||||
|
||||
TypeError:
|
||||
Type is not an integer.
|
||||
ValueError:
|
||||
Type is out of range of -128 to 127.
|
||||
TypeError::
|
||||
Data is not type 'bytes' (Python 3) or not type 'str' (Python 2).
|
||||
|
||||
Example:
|
||||
>>> foo = umsgpack.Ext(0x05, b"\x01\x02\x03")
|
||||
>>> foo = umsgpack.Ext(5, b"\x01\x02\x03")
|
||||
>>> umsgpack.packb({u"special stuff": foo, u"awesome": True})
|
||||
'\x82\xa7awesome\xc3\xadspecial stuff\xc7\x03\x05\x01\x02\x03'
|
||||
>>> bar = umsgpack.unpackb(_)
|
||||
>>> print(bar["special stuff"])
|
||||
Ext Object (Type: 0x05, Data: 01 02 03)
|
||||
Ext Object (Type: 5, Data: 01 02 03)
|
||||
>>>
|
||||
"""
|
||||
# Check type is type int
|
||||
# Check type is type int and in range
|
||||
if not isinstance(type, int):
|
||||
raise TypeError("ext type is not type integer")
|
||||
# Check data is type bytes
|
||||
elif not (-2**7 <= type <= 2**7 - 1):
|
||||
raise ValueError("ext type value {:d} is out of range (-128 to 127)".format(type))
|
||||
# Check data is type bytes or str
|
||||
elif sys.version_info[0] == 3 and not isinstance(data, bytes):
|
||||
raise TypeError("ext data is not type \'bytes\'")
|
||||
elif sys.version_info[0] == 2 and not isinstance(data, str):
|
||||
raise TypeError("ext data is not type \'str\'")
|
||||
|
||||
self.type = type
|
||||
self.data = data
|
||||
|
||||
@@ -99,9 +114,8 @@ class Ext:
|
||||
"""
|
||||
Compare this Ext object with another for equality.
|
||||
"""
|
||||
return (isinstance(other, self.__class__) and
|
||||
self.type == other.type and
|
||||
self.data == other.data)
|
||||
return isinstance(other, self.__class__) \
|
||||
and self.type == other.type and self.data == other.data
|
||||
|
||||
def __ne__(self, other):
|
||||
"""
|
||||
@@ -113,8 +127,8 @@ class Ext:
|
||||
"""
|
||||
String representation of this Ext object.
|
||||
"""
|
||||
s = "Ext Object (Type: 0x%02x, Data: " % self.type
|
||||
s += " ".join(["0x%02x" % ord(self.data[i:i + 1])
|
||||
s = "Ext Object (Type: {:d}, Data: ".format(self.type)
|
||||
s += " ".join(["0x{:02}".format(ord(self.data[i:i + 1]))
|
||||
for i in xrange(min(len(self.data), 8))])
|
||||
if len(self.data) > 8:
|
||||
s += " ..."
|
||||
@@ -130,7 +144,52 @@ class Ext:
|
||||
|
||||
class InvalidString(bytes):
|
||||
"""Subclass of bytes to hold invalid UTF-8 strings."""
|
||||
pass
|
||||
|
||||
|
||||
##############################################################################
|
||||
# Ext Serializable Decorator
|
||||
##############################################################################
|
||||
|
||||
_ext_class_to_type = {}
|
||||
_ext_type_to_class = {}
|
||||
|
||||
|
||||
def ext_serializable(ext_type):
|
||||
"""
|
||||
Return a decorator to register a class for automatic packing and unpacking
|
||||
with the specified Ext type code. The application class should implement a
|
||||
`packb()` method that returns serialized bytes, and an `unpackb()` class
|
||||
method or static method that accepts serialized bytes and returns an
|
||||
instance of the application class.
|
||||
|
||||
Args:
|
||||
ext_type: application-defined Ext type code
|
||||
|
||||
Raises:
|
||||
TypeError:
|
||||
Ext type is not an integer.
|
||||
ValueError:
|
||||
Ext type is out of range of -128 to 127.
|
||||
ValueError:
|
||||
Ext type or class already registered.
|
||||
"""
|
||||
def wrapper(cls):
|
||||
if not isinstance(ext_type, int):
|
||||
raise TypeError("Ext type is not type integer")
|
||||
elif not (-2**7 <= ext_type <= 2**7 - 1):
|
||||
raise ValueError("Ext type value {:d} is out of range of -128 to 127".format(ext_type))
|
||||
elif ext_type in _ext_type_to_class:
|
||||
raise ValueError("Ext type {:d} already registered with class {:s}".format(ext_type, repr(_ext_type_to_class[ext_type])))
|
||||
elif cls in _ext_class_to_type:
|
||||
raise ValueError("Class {:s} already registered with Ext type {:d}".format(repr(cls), ext_type))
|
||||
|
||||
_ext_type_to_class[ext_type] = cls
|
||||
_ext_class_to_type[cls] = ext_type
|
||||
|
||||
return cls
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
##############################################################################
|
||||
# Exceptions
|
||||
@@ -140,39 +199,32 @@ class InvalidString(bytes):
|
||||
# Base Exception classes
|
||||
class PackException(Exception):
|
||||
"Base class for exceptions encountered during packing."
|
||||
pass
|
||||
|
||||
|
||||
class UnpackException(Exception):
|
||||
"Base class for exceptions encountered during unpacking."
|
||||
pass
|
||||
|
||||
|
||||
# Packing error
|
||||
class UnsupportedTypeException(PackException):
|
||||
"Object type not supported for packing."
|
||||
pass
|
||||
|
||||
|
||||
# Unpacking error
|
||||
class InsufficientDataException(UnpackException):
|
||||
"Insufficient data to unpack the serialized object."
|
||||
pass
|
||||
|
||||
|
||||
class InvalidStringException(UnpackException):
|
||||
"Invalid UTF-8 string encountered during unpacking."
|
||||
pass
|
||||
|
||||
|
||||
class UnsupportedTimestampException(UnpackException):
|
||||
"Unsupported timestamp format encountered during unpacking."
|
||||
pass
|
||||
|
||||
|
||||
class ReservedCodeException(UnpackException):
|
||||
"Reserved code encountered during unpacking."
|
||||
pass
|
||||
|
||||
|
||||
class UnhashableKeyException(UnpackException):
|
||||
@@ -180,12 +232,10 @@ class UnhashableKeyException(UnpackException):
|
||||
Unhashable key encountered during map unpacking.
|
||||
The serialized map cannot be deserialized into a Python dictionary.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class DuplicateKeyException(UnpackException):
|
||||
"Duplicate key encountered during map unpacking."
|
||||
pass
|
||||
|
||||
|
||||
# Backwards compatibility
|
||||
@@ -250,15 +300,15 @@ def _pack_integer(obj, fp, options):
|
||||
else:
|
||||
raise UnsupportedTypeException("huge signed int")
|
||||
else:
|
||||
if obj <= 127:
|
||||
if obj < 128:
|
||||
fp.write(struct.pack("B", obj))
|
||||
elif obj <= 2**8 - 1:
|
||||
elif obj < 2**8:
|
||||
fp.write(b"\xcc" + struct.pack("B", obj))
|
||||
elif obj <= 2**16 - 1:
|
||||
elif obj < 2**16:
|
||||
fp.write(b"\xcd" + struct.pack(">H", obj))
|
||||
elif obj <= 2**32 - 1:
|
||||
elif obj < 2**32:
|
||||
fp.write(b"\xce" + struct.pack(">I", obj))
|
||||
elif obj <= 2**64 - 1:
|
||||
elif obj < 2**64:
|
||||
fp.write(b"\xcf" + struct.pack(">Q", obj))
|
||||
else:
|
||||
raise UnsupportedTypeException("huge unsigned int")
|
||||
@@ -285,94 +335,99 @@ def _pack_float(obj, fp, options):
|
||||
|
||||
def _pack_string(obj, fp, options):
|
||||
obj = obj.encode('utf-8')
|
||||
if len(obj) <= 31:
|
||||
fp.write(struct.pack("B", 0xa0 | len(obj)) + obj)
|
||||
elif len(obj) <= 2**8 - 1:
|
||||
fp.write(b"\xd9" + struct.pack("B", len(obj)) + obj)
|
||||
elif len(obj) <= 2**16 - 1:
|
||||
fp.write(b"\xda" + struct.pack(">H", len(obj)) + obj)
|
||||
elif len(obj) <= 2**32 - 1:
|
||||
fp.write(b"\xdb" + struct.pack(">I", len(obj)) + obj)
|
||||
obj_len = len(obj)
|
||||
if obj_len < 32:
|
||||
fp.write(struct.pack("B", 0xa0 | obj_len) + obj)
|
||||
elif obj_len < 2**8:
|
||||
fp.write(b"\xd9" + struct.pack("B", obj_len) + obj)
|
||||
elif obj_len < 2**16:
|
||||
fp.write(b"\xda" + struct.pack(">H", obj_len) + obj)
|
||||
elif obj_len < 2**32:
|
||||
fp.write(b"\xdb" + struct.pack(">I", obj_len) + obj)
|
||||
else:
|
||||
raise UnsupportedTypeException("huge string")
|
||||
|
||||
|
||||
def _pack_binary(obj, fp, options):
|
||||
if len(obj) <= 2**8 - 1:
|
||||
fp.write(b"\xc4" + struct.pack("B", len(obj)) + obj)
|
||||
elif len(obj) <= 2**16 - 1:
|
||||
fp.write(b"\xc5" + struct.pack(">H", len(obj)) + obj)
|
||||
elif len(obj) <= 2**32 - 1:
|
||||
fp.write(b"\xc6" + struct.pack(">I", len(obj)) + obj)
|
||||
obj_len = len(obj)
|
||||
if obj_len < 2**8:
|
||||
fp.write(b"\xc4" + struct.pack("B", obj_len) + obj)
|
||||
elif obj_len < 2**16:
|
||||
fp.write(b"\xc5" + struct.pack(">H", obj_len) + obj)
|
||||
elif obj_len < 2**32:
|
||||
fp.write(b"\xc6" + struct.pack(">I", obj_len) + obj)
|
||||
else:
|
||||
raise UnsupportedTypeException("huge binary string")
|
||||
|
||||
|
||||
def _pack_oldspec_raw(obj, fp, options):
|
||||
if len(obj) <= 31:
|
||||
fp.write(struct.pack("B", 0xa0 | len(obj)) + obj)
|
||||
elif len(obj) <= 2**16 - 1:
|
||||
fp.write(b"\xda" + struct.pack(">H", len(obj)) + obj)
|
||||
elif len(obj) <= 2**32 - 1:
|
||||
fp.write(b"\xdb" + struct.pack(">I", len(obj)) + obj)
|
||||
obj_len = len(obj)
|
||||
if obj_len < 32:
|
||||
fp.write(struct.pack("B", 0xa0 | obj_len) + obj)
|
||||
elif obj_len < 2**16:
|
||||
fp.write(b"\xda" + struct.pack(">H", obj_len) + obj)
|
||||
elif obj_len < 2**32:
|
||||
fp.write(b"\xdb" + struct.pack(">I", obj_len) + obj)
|
||||
else:
|
||||
raise UnsupportedTypeException("huge raw string")
|
||||
|
||||
|
||||
def _pack_ext(obj, fp, options):
|
||||
if len(obj.data) == 1:
|
||||
obj_len = len(obj.data)
|
||||
if obj_len == 1:
|
||||
fp.write(b"\xd4" + struct.pack("B", obj.type & 0xff) + obj.data)
|
||||
elif len(obj.data) == 2:
|
||||
elif obj_len == 2:
|
||||
fp.write(b"\xd5" + struct.pack("B", obj.type & 0xff) + obj.data)
|
||||
elif len(obj.data) == 4:
|
||||
elif obj_len == 4:
|
||||
fp.write(b"\xd6" + struct.pack("B", obj.type & 0xff) + obj.data)
|
||||
elif len(obj.data) == 8:
|
||||
elif obj_len == 8:
|
||||
fp.write(b"\xd7" + struct.pack("B", obj.type & 0xff) + obj.data)
|
||||
elif len(obj.data) == 16:
|
||||
elif obj_len == 16:
|
||||
fp.write(b"\xd8" + struct.pack("B", obj.type & 0xff) + obj.data)
|
||||
elif len(obj.data) <= 2**8 - 1:
|
||||
fp.write(b"\xc7" +
|
||||
struct.pack("BB", len(obj.data), obj.type & 0xff) + obj.data)
|
||||
elif len(obj.data) <= 2**16 - 1:
|
||||
fp.write(b"\xc8" +
|
||||
struct.pack(">HB", len(obj.data), obj.type & 0xff) + obj.data)
|
||||
elif len(obj.data) <= 2**32 - 1:
|
||||
fp.write(b"\xc9" +
|
||||
struct.pack(">IB", len(obj.data), obj.type & 0xff) + obj.data)
|
||||
elif obj_len < 2**8:
|
||||
fp.write(b"\xc7" + struct.pack("BB", obj_len, obj.type & 0xff) + obj.data)
|
||||
elif obj_len < 2**16:
|
||||
fp.write(b"\xc8" + struct.pack(">HB", obj_len, obj.type & 0xff) + obj.data)
|
||||
elif obj_len < 2**32:
|
||||
fp.write(b"\xc9" + struct.pack(">IB", obj_len, obj.type & 0xff) + obj.data)
|
||||
else:
|
||||
raise UnsupportedTypeException("huge ext data")
|
||||
|
||||
|
||||
def _pack_ext_timestamp(obj, fp, options):
|
||||
delta = obj - _epoch
|
||||
if not obj.tzinfo:
|
||||
# Object is naive datetime, convert to aware date time,
|
||||
# assuming UTC timezone
|
||||
delta = obj.replace(tzinfo=_utc_tzinfo) - _epoch
|
||||
else:
|
||||
# Object is aware datetime
|
||||
delta = obj - _epoch
|
||||
|
||||
seconds = delta.seconds + delta.days * 86400
|
||||
microseconds = delta.microseconds
|
||||
|
||||
if microseconds == 0 and 0 <= seconds <= 2**32 - 1:
|
||||
# 32-bit timestamp
|
||||
fp.write(b"\xd6\xff" +
|
||||
struct.pack(">I", seconds))
|
||||
fp.write(b"\xd6\xff" + struct.pack(">I", seconds))
|
||||
elif 0 <= seconds <= 2**34 - 1:
|
||||
# 64-bit timestamp
|
||||
value = ((microseconds * 1000) << 34) | seconds
|
||||
fp.write(b"\xd7\xff" +
|
||||
struct.pack(">Q", value))
|
||||
fp.write(b"\xd7\xff" + struct.pack(">Q", value))
|
||||
elif -2**63 <= abs(seconds) <= 2**63 - 1:
|
||||
# 96-bit timestamp
|
||||
fp.write(b"\xc7\x0c\xff" +
|
||||
struct.pack(">I", microseconds * 1000) +
|
||||
struct.pack(">q", seconds))
|
||||
fp.write(b"\xc7\x0c\xff" + struct.pack(">Iq", microseconds * 1000, seconds))
|
||||
else:
|
||||
raise UnsupportedTypeException("huge timestamp")
|
||||
|
||||
|
||||
def _pack_array(obj, fp, options):
|
||||
if len(obj) <= 15:
|
||||
fp.write(struct.pack("B", 0x90 | len(obj)))
|
||||
elif len(obj) <= 2**16 - 1:
|
||||
fp.write(b"\xdc" + struct.pack(">H", len(obj)))
|
||||
elif len(obj) <= 2**32 - 1:
|
||||
fp.write(b"\xdd" + struct.pack(">I", len(obj)))
|
||||
obj_len = len(obj)
|
||||
if obj_len < 16:
|
||||
fp.write(struct.pack("B", 0x90 | obj_len))
|
||||
elif obj_len < 2**16:
|
||||
fp.write(b"\xdc" + struct.pack(">H", obj_len))
|
||||
elif obj_len < 2**32:
|
||||
fp.write(b"\xdd" + struct.pack(">I", obj_len))
|
||||
else:
|
||||
raise UnsupportedTypeException("huge array")
|
||||
|
||||
@@ -381,12 +436,13 @@ def _pack_array(obj, fp, options):
|
||||
|
||||
|
||||
def _pack_map(obj, fp, options):
|
||||
if len(obj) <= 15:
|
||||
fp.write(struct.pack("B", 0x80 | len(obj)))
|
||||
elif len(obj) <= 2**16 - 1:
|
||||
fp.write(b"\xde" + struct.pack(">H", len(obj)))
|
||||
elif len(obj) <= 2**32 - 1:
|
||||
fp.write(b"\xdf" + struct.pack(">I", len(obj)))
|
||||
obj_len = len(obj)
|
||||
if obj_len < 16:
|
||||
fp.write(struct.pack("B", 0x80 | obj_len))
|
||||
elif obj_len < 2**16:
|
||||
fp.write(b"\xde" + struct.pack(">H", obj_len))
|
||||
elif obj_len < 2**32:
|
||||
fp.write(b"\xdf" + struct.pack(">I", obj_len))
|
||||
else:
|
||||
raise UnsupportedTypeException("huge array")
|
||||
|
||||
@@ -435,9 +491,14 @@ def _pack2(obj, fp, **options):
|
||||
_pack_nil(obj, fp, options)
|
||||
elif ext_handlers and obj.__class__ in ext_handlers:
|
||||
_pack_ext(ext_handlers[obj.__class__](obj), fp, options)
|
||||
elif obj.__class__ in _ext_class_to_type:
|
||||
try:
|
||||
_pack_ext(Ext(_ext_class_to_type[obj.__class__], obj.packb()), fp, options)
|
||||
except AttributeError:
|
||||
raise NotImplementedError("Ext serializable class {:s} is missing implementation of packb()".format(repr(obj.__class__)))
|
||||
elif isinstance(obj, bool):
|
||||
_pack_boolean(obj, fp, options)
|
||||
elif isinstance(obj, int) or isinstance(obj, long):
|
||||
elif isinstance(obj, (int, long)):
|
||||
_pack_integer(obj, fp, options)
|
||||
elif isinstance(obj, float):
|
||||
_pack_float(obj, fp, options)
|
||||
@@ -449,7 +510,7 @@ def _pack2(obj, fp, **options):
|
||||
_pack_string(obj, fp, options)
|
||||
elif isinstance(obj, str):
|
||||
_pack_binary(obj, fp, options)
|
||||
elif isinstance(obj, list) or isinstance(obj, tuple):
|
||||
elif isinstance(obj, (list, tuple)):
|
||||
_pack_array(obj, fp, options)
|
||||
elif isinstance(obj, dict):
|
||||
_pack_map(obj, fp, options)
|
||||
@@ -464,9 +525,19 @@ def _pack2(obj, fp, **options):
|
||||
_pack_ext(ext_handlers[t](obj), fp, options)
|
||||
else:
|
||||
raise UnsupportedTypeException(
|
||||
"unsupported type: %s" % str(type(obj)))
|
||||
"unsupported type: {:s}".format(str(type(obj))))
|
||||
elif _ext_class_to_type:
|
||||
# Linear search for superclass
|
||||
t = next((t for t in _ext_class_to_type if isinstance(obj, t)), None)
|
||||
if t:
|
||||
try:
|
||||
_pack_ext(Ext(_ext_class_to_type[t], obj.packb()), fp, options)
|
||||
except AttributeError:
|
||||
raise NotImplementedError("Ext serializable class {:s} is missing implementation of packb()".format(repr(t)))
|
||||
else:
|
||||
raise UnsupportedTypeException("unsupported type: {:s}".format(str(type(obj))))
|
||||
else:
|
||||
raise UnsupportedTypeException("unsupported type: %s" % str(type(obj)))
|
||||
raise UnsupportedTypeException("unsupported type: {:s}".format(str(type(obj))))
|
||||
|
||||
|
||||
# Pack for Python 3, with unicode 'str' type, 'bytes' type, and no 'long' type
|
||||
@@ -507,6 +578,11 @@ def _pack3(obj, fp, **options):
|
||||
_pack_nil(obj, fp, options)
|
||||
elif ext_handlers and obj.__class__ in ext_handlers:
|
||||
_pack_ext(ext_handlers[obj.__class__](obj), fp, options)
|
||||
elif obj.__class__ in _ext_class_to_type:
|
||||
try:
|
||||
_pack_ext(Ext(_ext_class_to_type[obj.__class__], obj.packb()), fp, options)
|
||||
except AttributeError:
|
||||
raise NotImplementedError("Ext serializable class {:s} is missing implementation of packb()".format(repr(obj.__class__)))
|
||||
elif isinstance(obj, bool):
|
||||
_pack_boolean(obj, fp, options)
|
||||
elif isinstance(obj, int):
|
||||
@@ -521,7 +597,7 @@ def _pack3(obj, fp, **options):
|
||||
_pack_string(obj, fp, options)
|
||||
elif isinstance(obj, bytes):
|
||||
_pack_binary(obj, fp, options)
|
||||
elif isinstance(obj, list) or isinstance(obj, tuple):
|
||||
elif isinstance(obj, (list, tuple)):
|
||||
_pack_array(obj, fp, options)
|
||||
elif isinstance(obj, dict):
|
||||
_pack_map(obj, fp, options)
|
||||
@@ -536,10 +612,20 @@ def _pack3(obj, fp, **options):
|
||||
_pack_ext(ext_handlers[t](obj), fp, options)
|
||||
else:
|
||||
raise UnsupportedTypeException(
|
||||
"unsupported type: %s" % str(type(obj)))
|
||||
"unsupported type: {:s}".format(str(type(obj))))
|
||||
elif _ext_class_to_type:
|
||||
# Linear search for superclass
|
||||
t = next((t for t in _ext_class_to_type if isinstance(obj, t)), None)
|
||||
if t:
|
||||
try:
|
||||
_pack_ext(Ext(_ext_class_to_type[t], obj.packb()), fp, options)
|
||||
except AttributeError:
|
||||
raise NotImplementedError("Ext serializable class {:s} is missing implementation of packb()".format(repr(t)))
|
||||
else:
|
||||
raise UnsupportedTypeException("unsupported type: {:s}".format(str(type(obj))))
|
||||
else:
|
||||
raise UnsupportedTypeException(
|
||||
"unsupported type: %s" % str(type(obj)))
|
||||
"unsupported type: {:s}".format(str(type(obj))))
|
||||
|
||||
|
||||
def _packb2(obj, **options):
|
||||
@@ -613,9 +699,20 @@ def _packb3(obj, **options):
|
||||
|
||||
|
||||
def _read_except(fp, n):
|
||||
if n == 0:
|
||||
return b""
|
||||
|
||||
data = fp.read(n)
|
||||
if len(data) < n:
|
||||
if len(data) == 0:
|
||||
raise InsufficientDataException()
|
||||
|
||||
while len(data) < n:
|
||||
chunk = fp.read(n - len(data))
|
||||
if len(chunk) == 0:
|
||||
raise InsufficientDataException()
|
||||
|
||||
data += chunk
|
||||
|
||||
return data
|
||||
|
||||
|
||||
@@ -640,21 +737,21 @@ def _unpack_integer(code, fp, options):
|
||||
return struct.unpack(">I", _read_except(fp, 4))[0]
|
||||
elif code == b'\xcf':
|
||||
return struct.unpack(">Q", _read_except(fp, 8))[0]
|
||||
raise Exception("logic error, not int: 0x%02x" % ord(code))
|
||||
raise Exception("logic error, not int: 0x{:02x}".format(ord(code)))
|
||||
|
||||
|
||||
def _unpack_reserved(code, fp, options):
|
||||
if code == b'\xc1':
|
||||
raise ReservedCodeException(
|
||||
"encountered reserved code: 0x%02x" % ord(code))
|
||||
"encountered reserved code: 0x{:02x}".format(ord(code)))
|
||||
raise Exception(
|
||||
"logic error, not reserved code: 0x%02x" % ord(code))
|
||||
"logic error, not reserved code: 0x{:02x}".format(ord(code)))
|
||||
|
||||
|
||||
def _unpack_nil(code, fp, options):
|
||||
if code == b'\xc0':
|
||||
return None
|
||||
raise Exception("logic error, not nil: 0x%02x" % ord(code))
|
||||
raise Exception("logic error, not nil: 0x{:02x}".format(ord(code)))
|
||||
|
||||
|
||||
def _unpack_boolean(code, fp, options):
|
||||
@@ -662,7 +759,7 @@ def _unpack_boolean(code, fp, options):
|
||||
return False
|
||||
elif code == b'\xc3':
|
||||
return True
|
||||
raise Exception("logic error, not boolean: 0x%02x" % ord(code))
|
||||
raise Exception("logic error, not boolean: 0x{:02x}".format(ord(code)))
|
||||
|
||||
|
||||
def _unpack_float(code, fp, options):
|
||||
@@ -670,7 +767,7 @@ def _unpack_float(code, fp, options):
|
||||
return struct.unpack(">f", _read_except(fp, 4))[0]
|
||||
elif code == b'\xcb':
|
||||
return struct.unpack(">d", _read_except(fp, 8))[0]
|
||||
raise Exception("logic error, not float: 0x%02x" % ord(code))
|
||||
raise Exception("logic error, not float: 0x{:02x}".format(ord(code)))
|
||||
|
||||
|
||||
def _unpack_string(code, fp, options):
|
||||
@@ -683,7 +780,7 @@ def _unpack_string(code, fp, options):
|
||||
elif code == b'\xdb':
|
||||
length = struct.unpack(">I", _read_except(fp, 4))[0]
|
||||
else:
|
||||
raise Exception("logic error, not string: 0x%02x" % ord(code))
|
||||
raise Exception("logic error, not string: 0x{:02x}".format(ord(code)))
|
||||
|
||||
# Always return raw bytes in compatibility mode
|
||||
global compatibility
|
||||
@@ -707,7 +804,7 @@ def _unpack_binary(code, fp, options):
|
||||
elif code == b'\xc6':
|
||||
length = struct.unpack(">I", _read_except(fp, 4))[0]
|
||||
else:
|
||||
raise Exception("logic error, not binary: 0x%02x" % ord(code))
|
||||
raise Exception("logic error, not binary: 0x{:02x}".format(ord(code)))
|
||||
|
||||
return _read_except(fp, length)
|
||||
|
||||
@@ -730,43 +827,48 @@ def _unpack_ext(code, fp, options):
|
||||
elif code == b'\xc9':
|
||||
length = struct.unpack(">I", _read_except(fp, 4))[0]
|
||||
else:
|
||||
raise Exception("logic error, not ext: 0x%02x" % ord(code))
|
||||
raise Exception("logic error, not ext: 0x{:02x}".format(ord(code)))
|
||||
|
||||
ext_type = struct.unpack("b", _read_except(fp, 1))[0]
|
||||
ext_data = _read_except(fp, length)
|
||||
|
||||
# Create extension object
|
||||
ext = Ext(ext_type, ext_data)
|
||||
|
||||
# Unpack with ext handler, if we have one
|
||||
ext_handlers = options.get("ext_handlers")
|
||||
if ext_handlers and ext.type in ext_handlers:
|
||||
return ext_handlers[ext.type](ext)
|
||||
if ext_handlers and ext_type in ext_handlers:
|
||||
return ext_handlers[ext_type](Ext(ext_type, ext_data))
|
||||
|
||||
# Unpack with ext classes, if type is registered
|
||||
if ext_type in _ext_type_to_class:
|
||||
try:
|
||||
return _ext_type_to_class[ext_type].unpackb(ext_data)
|
||||
except AttributeError:
|
||||
raise NotImplementedError("Ext serializable class {:s} is missing implementation of unpackb()".format(repr(_ext_type_to_class[ext_type])))
|
||||
|
||||
# Timestamp extension
|
||||
if ext.type == -1:
|
||||
return _unpack_ext_timestamp(ext, options)
|
||||
if ext_type == -1:
|
||||
return _unpack_ext_timestamp(ext_data, options)
|
||||
|
||||
return ext
|
||||
return Ext(ext_type, ext_data)
|
||||
|
||||
|
||||
def _unpack_ext_timestamp(ext, options):
|
||||
if len(ext.data) == 4:
|
||||
def _unpack_ext_timestamp(ext_data, options):
|
||||
obj_len = len(ext_data)
|
||||
if obj_len == 4:
|
||||
# 32-bit timestamp
|
||||
seconds = struct.unpack(">I", ext.data)[0]
|
||||
seconds = struct.unpack(">I", ext_data)[0]
|
||||
microseconds = 0
|
||||
elif len(ext.data) == 8:
|
||||
elif obj_len == 8:
|
||||
# 64-bit timestamp
|
||||
value = struct.unpack(">Q", ext.data)[0]
|
||||
value = struct.unpack(">Q", ext_data)[0]
|
||||
seconds = value & 0x3ffffffff
|
||||
microseconds = (value >> 34) // 1000
|
||||
elif len(ext.data) == 12:
|
||||
elif obj_len == 12:
|
||||
# 96-bit timestamp
|
||||
seconds = struct.unpack(">q", ext.data[4:12])[0]
|
||||
microseconds = struct.unpack(">I", ext.data[0:4])[0] // 1000
|
||||
seconds = struct.unpack(">q", ext_data[4:12])[0]
|
||||
microseconds = struct.unpack(">I", ext_data[0:4])[0] // 1000
|
||||
else:
|
||||
raise UnsupportedTimestampException(
|
||||
"unsupported timestamp with data length %d" % len(ext.data))
|
||||
"unsupported timestamp with data length {:d}".format(len(ext_data)))
|
||||
|
||||
return _epoch + datetime.timedelta(seconds=seconds,
|
||||
microseconds=microseconds)
|
||||
@@ -780,7 +882,10 @@ def _unpack_array(code, fp, options):
|
||||
elif code == b'\xdd':
|
||||
length = struct.unpack(">I", _read_except(fp, 4))[0]
|
||||
else:
|
||||
raise Exception("logic error, not array: 0x%02x" % ord(code))
|
||||
raise Exception("logic error, not array: 0x{:02x}".format(ord(code)))
|
||||
|
||||
if options.get('use_tuple'):
|
||||
return tuple((_unpack(fp, options) for i in xrange(length)))
|
||||
|
||||
return [_unpack(fp, options) for i in xrange(length)]
|
||||
|
||||
@@ -799,10 +904,9 @@ def _unpack_map(code, fp, options):
|
||||
elif code == b'\xdf':
|
||||
length = struct.unpack(">I", _read_except(fp, 4))[0]
|
||||
else:
|
||||
raise Exception("logic error, not map: 0x%02x" % ord(code))
|
||||
raise Exception("logic error, not map: 0x{:02x}".format(ord(code)))
|
||||
|
||||
d = {} if not options.get('use_ordered_dict') \
|
||||
else collections.OrderedDict()
|
||||
d = {} if not options.get('use_ordered_dict') else collections.OrderedDict()
|
||||
for _ in xrange(length):
|
||||
# Unpack key
|
||||
k = _unpack(fp, options)
|
||||
@@ -810,12 +914,12 @@ def _unpack_map(code, fp, options):
|
||||
if isinstance(k, list):
|
||||
# Attempt to convert list into a hashable tuple
|
||||
k = _deep_list_to_tuple(k)
|
||||
elif not isinstance(k, collections.Hashable):
|
||||
elif not isinstance(k, Hashable):
|
||||
raise UnhashableKeyException(
|
||||
"encountered unhashable key: %s, %s" % (str(k), str(type(k))))
|
||||
"encountered unhashable key: \"{:s}\" ({:s})".format(str(k), str(type(k))))
|
||||
elif k in d:
|
||||
raise DuplicateKeyException(
|
||||
"encountered duplicate key: %s, %s" % (str(k), str(type(k))))
|
||||
"encountered duplicate key: \"{:s}\" ({:s})".format(str(k), str(type(k))))
|
||||
|
||||
# Unpack value
|
||||
v = _unpack(fp, options)
|
||||
@@ -824,7 +928,7 @@ def _unpack_map(code, fp, options):
|
||||
d[k] = v
|
||||
except TypeError:
|
||||
raise UnhashableKeyException(
|
||||
"encountered unhashable key: %s" % str(k))
|
||||
"encountered unhashable key: \"{:s}\"".format(str(k)))
|
||||
return d
|
||||
|
||||
|
||||
@@ -848,6 +952,8 @@ def _unpack2(fp, **options):
|
||||
Ext into an object
|
||||
use_ordered_dict (bool): unpack maps into OrderedDict, instead of
|
||||
unordered dict (default False)
|
||||
use_tuple (bool): unpacks arrays into tuples, instead of lists (default
|
||||
False)
|
||||
allow_invalid_utf8 (bool): unpack invalid strings into instances of
|
||||
InvalidString, for access to the bytes
|
||||
(default False)
|
||||
@@ -892,6 +998,8 @@ def _unpack3(fp, **options):
|
||||
Ext into an object
|
||||
use_ordered_dict (bool): unpack maps into OrderedDict, instead of
|
||||
unordered dict (default False)
|
||||
use_tuple (bool): unpacks arrays into tuples, instead of lists (default
|
||||
False)
|
||||
allow_invalid_utf8 (bool): unpack invalid strings into instances of
|
||||
InvalidString, for access to the bytes
|
||||
(default False)
|
||||
@@ -937,6 +1045,8 @@ def _unpackb2(s, **options):
|
||||
Ext into an object
|
||||
use_ordered_dict (bool): unpack maps into OrderedDict, instead of
|
||||
unordered dict (default False)
|
||||
use_tuple (bool): unpacks arrays into tuples, instead of lists (default
|
||||
False)
|
||||
allow_invalid_utf8 (bool): unpack invalid strings into instances of
|
||||
InvalidString, for access to the bytes
|
||||
(default False)
|
||||
@@ -985,6 +1095,8 @@ def _unpackb3(s, **options):
|
||||
Ext into an object
|
||||
use_ordered_dict (bool): unpack maps into OrderedDict, instead of
|
||||
unordered dict (default False)
|
||||
use_tuple (bool): unpacks arrays into tuples, instead of lists (default
|
||||
False)
|
||||
allow_invalid_utf8 (bool): unpack invalid strings into instances of
|
||||
InvalidString, for access to the bytes
|
||||
(default False)
|
||||
@@ -1045,9 +1157,21 @@ def __init():
|
||||
if sys.version_info[0] == 3:
|
||||
_utc_tzinfo = datetime.timezone.utc
|
||||
else:
|
||||
_utc_tzinfo = None
|
||||
class UTC(datetime.tzinfo):
|
||||
ZERO = datetime.timedelta(0)
|
||||
|
||||
# Calculate epoch datetime
|
||||
def utcoffset(self, dt):
|
||||
return UTC.ZERO
|
||||
|
||||
def tzname(self, dt):
|
||||
return "UTC"
|
||||
|
||||
def dst(self, dt):
|
||||
return UTC.ZERO
|
||||
|
||||
_utc_tzinfo = UTC()
|
||||
|
||||
# Calculate an aware epoch datetime
|
||||
_epoch = datetime.datetime(1970, 1, 1, tzinfo=_utc_tzinfo)
|
||||
|
||||
# Auto-detect system float precision
|
||||
|
||||
Binary file not shown.
@@ -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: 55e1a358a797ca9390d1b4f0e18f7ba3
|
||||
config: 373c2d4526d24456ccd5dac65661415a
|
||||
tags: 645f666f9bcd5a90fca523b33c5a78b7
|
||||
|
||||
@@ -16,7 +16,7 @@ provides a complete encrypted communications suite built with Reticulum.
|
||||
: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
|
||||
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:
|
||||
@@ -48,7 +48,8 @@ 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``.
|
||||
default is located at ``~/.reticulum/config``. You can edit this file by hand,
|
||||
or use the interactive ``rnsconfig`` utility.
|
||||
|
||||
When Reticulum is started for the first time, it will create a default
|
||||
configuration file, with one active interface. This default interface uses
|
||||
@@ -77,6 +78,13 @@ The above command will install Reticulum and dependencies, and you will be
|
||||
ready to import and use RNS in your own programs. The next step will most
|
||||
likely be to look at some :ref:`Example Programs<examples-main>`.
|
||||
|
||||
For extended functionality, you can install optional dependencies:
|
||||
|
||||
.. code::
|
||||
|
||||
pip3 install pyserial netifaces
|
||||
|
||||
|
||||
Further information can be found in the :ref:`API Reference<api-main>`.
|
||||
|
||||
|
||||
@@ -120,4 +128,66 @@ don't use pip, but try this recipe:
|
||||
python3 Examples/Filetransfer.py -h
|
||||
|
||||
When you have experimented with the basic examples, it's time to go read the
|
||||
:ref:`Understanding Reticulum<understanding-main>` chapter.
|
||||
:ref:`Understanding Reticulum<understanding-main>` chapter.
|
||||
|
||||
|
||||
Reticulum on ARM64
|
||||
==============================================
|
||||
On some architectures, including ARM64, not all dependencies have precompiled
|
||||
binaries. On such systems, you will need to install ``python3-dev`` before
|
||||
installing Reticulum or programs that depend on Reticulum.
|
||||
|
||||
.. code::
|
||||
|
||||
# Install Python and development packages
|
||||
sudo apt update
|
||||
sudo apt install python3 python3-pip python3-dev
|
||||
|
||||
# Install Reticulum
|
||||
python3 -m pip install rns
|
||||
|
||||
|
||||
Reticulum on Android
|
||||
==============================================
|
||||
Reticulum can be used on Android in different ways. The easiest way to get
|
||||
started is using the `Termux app <https://termux.com/>`_, at the time of writing
|
||||
available on `F-droid <https://f-droid.org>`_.
|
||||
|
||||
Termux is a terminal emulator and Linux environment for Android based devices,
|
||||
which includes the ability to use many different programs and libraries,
|
||||
including Reticulum.
|
||||
|
||||
Since the Python cryptography.io module does not offer pre-built wheels for
|
||||
Android, the standard one-line install of Reticulum does not work on Android,
|
||||
and a few extra commands are required.
|
||||
|
||||
From within Termux, execute the following:
|
||||
|
||||
.. code::
|
||||
|
||||
# First, make sure indexes and packages are up to date.
|
||||
pkg update
|
||||
pkg upgrade
|
||||
|
||||
# Then install dependencies for the cryptography library.
|
||||
pkg install python build-essential openssl libffi rust
|
||||
|
||||
# Make sure pip is up to date, and install the wheel module.
|
||||
pip3 install wheel pip --upgrade
|
||||
|
||||
# To allow the installer to build the cryptography module,
|
||||
# we need to let it know what platform we are compiling for:
|
||||
export CARGO_BUILD_TARGET="aarch64-linux-android"
|
||||
|
||||
# Start the install process for the cryptography module.
|
||||
# Depending on your device, this can take several minutes,
|
||||
# since the module must be compiled locally on your device.
|
||||
pip3 install cryptography
|
||||
|
||||
# If the above installation succeeds, you can now install
|
||||
# Reticulum and any related software
|
||||
pip3 install rns
|
||||
|
||||
It is also possible to include Reticulum in apps compiled and distributed as
|
||||
Android APKs. A detailed tutorial and example source code will be included
|
||||
here at a later point.
|
||||
|
||||
@@ -18,6 +18,73 @@ 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-auto:
|
||||
|
||||
Auto Interface
|
||||
==============
|
||||
|
||||
The Auto Interface enables communication with other discoverable Reticulum
|
||||
nodes over autoconfigured IPv6 and UDP. It does not need any functional IP
|
||||
infrastructure like routers or DHCP servers, but will require at least some
|
||||
sort of switching medium between peers (a wired switch, a hub, a WiFi access
|
||||
point or similar), and that link-local IPv6 is enabled in your operating
|
||||
system, which should be enabled by default in almost all OSes.
|
||||
|
||||
.. code::
|
||||
|
||||
# This example demonstrates a TCP server interface.
|
||||
# It will listen for incoming connections on the
|
||||
# specified IP address and port number.
|
||||
|
||||
[[Default Interface]]
|
||||
type = AutoInterface
|
||||
interface_enabled = True
|
||||
outgoing = True
|
||||
|
||||
# You can create multiple isolated Reticulum
|
||||
# networks on the same physical LAN by
|
||||
# specifying different Group IDs.
|
||||
|
||||
group_id = reticulum
|
||||
|
||||
# You can also select specifically which
|
||||
# kernel networking devices to use.
|
||||
|
||||
devices = wlan0,eth1
|
||||
|
||||
# Or let AutoInterface use all suitable
|
||||
# devices except for a list of ignored ones.
|
||||
|
||||
ignored_devices = tun0,eth0
|
||||
|
||||
|
||||
If you are connected to the Internet with IPv6, and your provider will route
|
||||
IPv6 multicast, you can potentially configure the Auto Interface to globally
|
||||
autodiscover other Reticulum nodes within your selected Group ID. You can specify
|
||||
the discovery scope by setting it to one of ``link``, ``admin``, ``site``,
|
||||
``organisation`` or ``global``.
|
||||
|
||||
.. code::
|
||||
|
||||
[[Default Interface]]
|
||||
type = AutoInterface
|
||||
interface_enabled = True
|
||||
outgoing = True
|
||||
|
||||
# Configure global discovery
|
||||
|
||||
group_id = custom_network_name
|
||||
discovery_scope = global
|
||||
|
||||
# Other configuration options
|
||||
|
||||
discovery_port = 48555
|
||||
data_port = 49555
|
||||
|
||||
*Please Note!* If you use the Auto Interface, you will need the Python module
|
||||
``netifaces`` installed on your system. You can install it with ``pip3 install netifaces``.
|
||||
|
||||
|
||||
.. _interfaces-udp:
|
||||
|
||||
UDP Interface
|
||||
@@ -28,6 +95,12 @@ 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.
|
||||
|
||||
*Please Note!* Using broadcast UDP traffic has performance implications,
|
||||
especially on WiFi. If your goal is simply to enable easy communication
|
||||
with all peers in your local ethernet broadcast domain, the
|
||||
:ref:`Auto Interface<interfaces-auto>` performs better, and is just as
|
||||
easy to use.
|
||||
|
||||
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.
|
||||
@@ -48,9 +121,7 @@ pre-existing LAN.
|
||||
|
||||
# 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.
|
||||
# IP interfaces.
|
||||
|
||||
# Instead of specifying listen_ip, listen_port,
|
||||
# forward_ip and forward_port, you can also bind
|
||||
@@ -78,6 +149,9 @@ pre-existing LAN.
|
||||
# forward_ip = 10.55.0.16
|
||||
# forward_port = 4242
|
||||
|
||||
*Please Note!* If you use the ``device`` option, you will need the Python module
|
||||
``netifaces`` installed on your system. You can install it with ``pip3 install netifaces``.
|
||||
|
||||
.. _interfaces-tcps:
|
||||
|
||||
TCP Server Interface
|
||||
@@ -114,6 +188,8 @@ configured, other Reticulum peers can connect to it with a TCP Client interface.
|
||||
# device = eth0
|
||||
# port = 4242
|
||||
|
||||
*Please Note!* If you use the ``device`` option, you will need the Python module
|
||||
``netifaces`` installed on your system. You can install it with ``pip3 install netifaces``.
|
||||
|
||||
.. _interfaces-tcpc:
|
||||
|
||||
@@ -136,6 +212,30 @@ same TCP Server interface at the same time.
|
||||
target_host = 127.0.0.1
|
||||
target_port = 4242
|
||||
|
||||
It is also possible to use this interface type to connect via other programs
|
||||
or hardware devices that expose a KISS interface on a TCP port, for example
|
||||
software-based soundmodems. To do this, use the ``kiss_framing`` option:
|
||||
|
||||
.. code::
|
||||
|
||||
# Here's an example of a TCP Client interface that connects
|
||||
# to a software TNC soundmodem on a KISS over TCP port.
|
||||
|
||||
[[TCP KISS Interface]]
|
||||
type = TCPClientInterface
|
||||
interface_enabled = True
|
||||
outgoing = True
|
||||
kiss_framing = True
|
||||
target_host = 127.0.0.1
|
||||
target_port = 8001
|
||||
|
||||
**Caution!** Only use the KISS framing option when connecting to external devices
|
||||
and programs like soundmodems and similar over TCP. When using the
|
||||
``TCPClientInterface`` in conjunction with the ``TCPServerInterface`` you should
|
||||
never enable ``kiss_framing``, since this will disable internal reliability and
|
||||
recovery mechanisms that greatly improves performance over unreliable and
|
||||
intermittent TCP links.
|
||||
|
||||
|
||||
.. _interfaces-rnode:
|
||||
|
||||
|
||||
@@ -67,9 +67,12 @@ guide the design of Reticulum:
|
||||
it can be easily replicated.
|
||||
* **Very low bandwidth requirements**
|
||||
Reticulum should be able to function reliably over links with a transmission capacity as low
|
||||
as *1,000 bps*.
|
||||
as *500 bps*.
|
||||
* **Encryption by default**
|
||||
Reticulum must use encryption by default where possible and applicable.
|
||||
Reticulum must use strong encryption by default for all communication.
|
||||
* **Initiator Anonymity**
|
||||
It must be possible to communicate over a Reticulum network without revealing any identifying
|
||||
information about oneself.
|
||||
* **Unlicensed use**
|
||||
Reticulum shall be functional over physical communication mediums that do not require any
|
||||
form of license to use. Reticulum must be designed in a way, so it is usable over ISM radio
|
||||
@@ -99,7 +102,7 @@ Introduction & Basic Functionality
|
||||
Reticulum is a networking stack suited for high-latency, low-bandwidth links. Reticulum is at it’s
|
||||
core a *message oriented* system. It is suited for both local point-to-point or point-to-multipoint
|
||||
scenarios where alle nodes are within range of each other, as well as scenarios where packets need
|
||||
to be transported over multiple hops to reach the recipient.
|
||||
to be transported over multiple hops in a complex network to reach the recipient.
|
||||
|
||||
Reticulum does away with the idea of addresses and ports known from IP, TCP and UDP. Instead
|
||||
Reticulum uses the singular concept of *destinations*. Any application using Reticulum as it’s
|
||||
@@ -110,9 +113,9 @@ All destinations in Reticulum are represented internally as 10 bytes, derived fr
|
||||
SHA-256 hash of identifying characteristics of the destination. To users, the destination addresses
|
||||
will be displayed as 10 bytes in hexadecimal representation, as in the following example: ``<80e29bf7cccaf31431b3>``.
|
||||
|
||||
By default Reticulum encrypts all data using public-key cryptography. Any message sent to a
|
||||
destination is encrypted with that destinations public key. Reticulum can also set up an encrypted
|
||||
channel to a destination with *Perfect Forward Secrecy* and *Initiator Anonymity* using a elliptic
|
||||
By default Reticulum encrypts all data using elliptic curve cryptography. Any packet sent to a
|
||||
destination is encrypted with a derived ephemeral key. Reticulum can also set up an encrypted
|
||||
channel to a destination with *Forward Secrecy* and *Initiator Anonymity* using a elliptic
|
||||
curve cryptography and ephemeral keys derived from a Diffie Hellman exchange on Curve25519. In
|
||||
Reticulum terminology, this is called a *Link*.
|
||||
|
||||
@@ -135,17 +138,17 @@ destinations. Reticulum uses three different basic destination types, and one sp
|
||||
|
||||
|
||||
* **Single**
|
||||
The *single* destination type defines a public-key encrypted destination. Any data sent to this
|
||||
destination will be encrypted with the destination’s public key, and will only be readable by
|
||||
the creator of the destination.
|
||||
The *single* destination type is always identified by a unique public key. Any data sent to this
|
||||
destination will be encrypted using ephemeral keys derived from an ECDH key exchange, and will
|
||||
only be readable by the creator of the destination, who holds the corresponding private key.
|
||||
* **Group**
|
||||
The *group* destination type defines a symmetrically encrypted destination. Data sent to this
|
||||
destination will be encrypted with a symmetric key, and will be readable by anyone in
|
||||
possession of the key. The *group* destination can be used just as well by only two peers, as it
|
||||
can by many.
|
||||
possession of the key.
|
||||
* **Plain**
|
||||
A *plain* destination type is unencrypted, and suited for traffic that should be broadcast to a
|
||||
number of users, or should be readable by anyone. Traffic to a *plain* destination is not encrypted.
|
||||
Generally, *plain* destinations can be used for broadcast information intended to be public.
|
||||
* **Link**
|
||||
A *link* is a special destination type, that serves as an abstract channel to a *single*
|
||||
destination, directly connected or over multiple hops. The *link* also offers reliability and
|
||||
@@ -507,7 +510,7 @@ the transfer is needed.
|
||||
This is the purpose of the Reticulum :ref:`Resource<api-resource>`. A *Resource* can automatically
|
||||
handle the reliable transfer of an arbitrary amount of data over an established :ref:`Link<api-link>`.
|
||||
Resources can auto-compress data, will handle breaking the data into individual packets, sequencing
|
||||
the transfer and reassembling the data on the other end.
|
||||
the transfer, integrity verification and reassembling the data on the other end.
|
||||
|
||||
:ref:`Resources<api-resource>` are programmatically very simple to use, and only requires a few lines
|
||||
of codes to reliably transfer any amount of data. They can be used to transfer data stored in memory,
|
||||
@@ -581,6 +584,7 @@ Node Types
|
||||
|
||||
Currently Reticulum defines two node types, the *Station* and the *Peer*. A node is a *station* if it fixed
|
||||
in one place, and if it is intended to be kept online most of the time. Otherwise the node is a *peer*.
|
||||
|
||||
This distinction is made by the user configuring the node, and is used to determine what nodes on the
|
||||
network will help forward traffic, and what nodes rely on other nodes for connectivity.
|
||||
|
||||
@@ -596,10 +600,6 @@ Currently, Reticulum is completely priority-agnostic regarding general traffic.
|
||||
on a first-come, first-serve basis. Announce re-transmission are handled according to the re-transmission
|
||||
times and priorities described earlier in this chapter.
|
||||
|
||||
It is possible that a prioritisation engine could be added to Reticulum in the future, but in
|
||||
the light of Reticulums goal of equal access, doing so would need to be the subject of careful
|
||||
investigation of the consequences first.
|
||||
|
||||
|
||||
.. _understanding-packetformat:
|
||||
|
||||
@@ -702,4 +702,4 @@ Binary Packet Format
|
||||
- Link Request : 77 bytes
|
||||
- Link Proof : 77 bytes
|
||||
- Link RTT packet : 83 bytes
|
||||
- Link keepalive : 14 bytes
|
||||
- Link keepalive : 14 bytes
|
||||
|
||||
@@ -57,6 +57,7 @@ the same system.
|
||||
-q, --quiet
|
||||
--version show program's version number and exit
|
||||
|
||||
You can easily add ``rnsd`` as an always-on service by :ref:`configuring a service<using-systemd>`.
|
||||
|
||||
The rnstatus Utility
|
||||
====================
|
||||
@@ -162,4 +163,101 @@ destinations will not have this option enabled, and will not be probable.
|
||||
-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
|
||||
-v, --verbose
|
||||
|
||||
|
||||
Improving System Configuration
|
||||
------------------------------
|
||||
|
||||
If you are setting up a system for permanent use with Reticulum, there is a
|
||||
few system configuration changes that can make this easier to administrate.
|
||||
These changes will be detailed here.
|
||||
|
||||
|
||||
Fixed Serial Port Names
|
||||
=======================
|
||||
|
||||
On a Reticulum node with several serial port based interfaces, it can be
|
||||
beneficial to use the fixed name device nodes for the serial ports, instead
|
||||
of the dynamically allocated shorthands such as ``/dev/ttyUSB0``. Under most
|
||||
Debian-based distributions, including Ubuntu and Raspberry Pi OS, these nodes
|
||||
can be found under ``/dev/serial/by-id``.
|
||||
|
||||
You can use such a device path directly in place of the numbered shorthands.
|
||||
Here is an example of a packet radio TNC configured as such:
|
||||
|
||||
.. code:: text
|
||||
|
||||
[[Packet Radio KISS Interface]]
|
||||
type = KISSInterface
|
||||
interface_enabled = True
|
||||
outgoing = true
|
||||
port = /dev/serial/by-id/usb-FTDI_FT230X_Basic_UART_43891CKM-if00-port0
|
||||
speed = 115200
|
||||
databits = 8
|
||||
parity = none
|
||||
stopbits = 1
|
||||
preamble = 150
|
||||
txtail = 10
|
||||
persistence = 200
|
||||
slottime = 20
|
||||
|
||||
Using this methodology avoids potential naming mix-ups where physical devices
|
||||
might be plugged and unplugged in different orders, or when node name
|
||||
assignment varies from one boot to another.
|
||||
|
||||
.. _using-systemd:
|
||||
|
||||
Reticulum as a System Service
|
||||
=============================
|
||||
|
||||
Instead of starting Reticulum manually, you can install ``rnsd`` as a system
|
||||
service and have it start automatically at boot.
|
||||
|
||||
If you installed Reticulum with ``pip``, the ``rnsd`` program will most likely
|
||||
be located in a user-local installation path only, which means ``systemd`` will not
|
||||
be able to execute it. In this case, you can simply symlink the ``rnsd`` program
|
||||
into a directory that is in systemd's path:
|
||||
|
||||
.. code:: text
|
||||
|
||||
sudo ln -s $(which rnsd) /usr/local/bin/
|
||||
|
||||
You can then create the service file ``/etc/systemd/system/rnsd.service`` with the
|
||||
following content:
|
||||
|
||||
.. code:: text
|
||||
|
||||
[Unit]
|
||||
Description=Reticulum Network Stack Daemon
|
||||
After=multi-user.target
|
||||
|
||||
[Service]
|
||||
# If you run Reticulum on WiFi devices,
|
||||
# or other devices that need some extra
|
||||
# time to initialise, you might want to
|
||||
# add a short delay before Reticulum is
|
||||
# started by systemd:
|
||||
# ExecStartPre=/bin/sleep 10
|
||||
Type=simple
|
||||
Restart=always
|
||||
RestartSec=3
|
||||
User=USERNAMEHERE
|
||||
ExecStart=rnsd --service
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
||||
Be sure to replace ``USERNAMEHERE`` with the user you want to run ``rnsd`` as.
|
||||
|
||||
To manually start ``rnsd`` run:
|
||||
|
||||
.. code:: text
|
||||
|
||||
sudo systemctl start rnsd
|
||||
|
||||
If you want to automatically start ``rnsd`` at boot, run:
|
||||
|
||||
.. code:: text
|
||||
|
||||
sudo systemctl enable rnsd
|
||||
@@ -2,7 +2,7 @@
|
||||
What is Reticulum?
|
||||
******************
|
||||
|
||||
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 is a cryptography-based networking stack for wide-area networks built on readily available hardware, that 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.
|
||||
|
||||
@@ -16,11 +16,6 @@ Current Status
|
||||
Reticulum should currently be considered beta software. All core protocol features are implemented and functioning, but additions will probably occur as real-world use is explored. There will be bugs. The API and wire-format can be considered relatively stable at the moment, but could change if warranted.
|
||||
|
||||
|
||||
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 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?
|
||||
==========================
|
||||
* Coordination-less globally unique adressing and identification
|
||||
@@ -67,7 +62,7 @@ What does Reticulum Offer?
|
||||
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,
|
||||
with 500 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.
|
||||
@@ -105,4 +100,9 @@ Reticulum implements a range of generalised interface types that covers most of
|
||||
|
||||
* UDP over IP networks
|
||||
|
||||
For a full list and more details, see the :ref:`Supported Interfaces<interfaces-main>` chapter.
|
||||
For a full list and more details, see the :ref:`Supported Interfaces<interfaces-main>` chapter.
|
||||
|
||||
|
||||
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 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.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
var DOCUMENTATION_OPTIONS = {
|
||||
URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'),
|
||||
VERSION: '0.2.7 beta',
|
||||
VERSION: '0.3.0 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>Code Examples — Reticulum Network Stack 0.2.7 beta documentation</title>
|
||||
<title>Code Examples — Reticulum Network Stack 0.3.0 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="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.7 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.0 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">Code Examples</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -174,7 +174,7 @@ notifications about announces from relevant destinations.</p>
|
||||
<span class="n">APP_NAME</span> <span class="o">=</span> <span class="s2">"example_utilities"</span>
|
||||
|
||||
<span class="c1"># We initialise two lists of strings to use as app_data</span>
|
||||
<span class="n">fruits</span> <span class="o">=</span> <span class="p">[</span><span class="s2">"Peach"</span><span class="p">,</span> <span class="s2">"Quince"</span><span class="p">,</span> <span class="s2">"Date palm"</span><span class="p">,</span> <span class="s2">"Tangerine"</span><span class="p">,</span> <span class="s2">"Pomelo"</span><span class="p">,</span> <span class="s2">"Carambola"</span><span class="p">,</span> <span class="s2">"Grape"</span><span class="p">]</span>
|
||||
<span class="n">fruits</span> <span class="o">=</span> <span class="p">[</span><span class="s2">"Peach"</span><span class="p">,</span> <span class="s2">"Quince"</span><span class="p">,</span> <span class="s2">"Date"</span><span class="p">,</span> <span class="s2">"Tangerine"</span><span class="p">,</span> <span class="s2">"Pomelo"</span><span class="p">,</span> <span class="s2">"Carambola"</span><span class="p">,</span> <span class="s2">"Grape"</span><span class="p">]</span>
|
||||
<span class="n">noble_gases</span> <span class="o">=</span> <span class="p">[</span><span class="s2">"Helium"</span><span class="p">,</span> <span class="s2">"Neon"</span><span class="p">,</span> <span class="s2">"Argon"</span><span class="p">,</span> <span class="s2">"Krypton"</span><span class="p">,</span> <span class="s2">"Xenon"</span><span class="p">,</span> <span class="s2">"Radon"</span><span class="p">,</span> <span class="s2">"Oganesson"</span><span class="p">]</span>
|
||||
|
||||
<span class="c1"># This initialisation is executed when the program is started</span>
|
||||
@@ -488,6 +488,8 @@ the Packet interface.</p>
|
||||
<span class="c1"># This initialisation is executed when the users chooses</span>
|
||||
<span class="c1"># to run as a server</span>
|
||||
<span class="k">def</span> <span class="nf">server</span><span class="p">(</span><span class="n">configpath</span><span class="p">):</span>
|
||||
<span class="k">global</span> <span class="n">reticulum</span>
|
||||
|
||||
<span class="c1"># We must first initialise Reticulum</span>
|
||||
<span class="n">reticulum</span> <span class="o">=</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Reticulum</span><span class="p">(</span><span class="n">configpath</span><span class="p">)</span>
|
||||
|
||||
@@ -544,11 +546,32 @@ the Packet interface.</p>
|
||||
|
||||
|
||||
<span class="k">def</span> <span class="nf">server_callback</span><span class="p">(</span><span class="n">message</span><span class="p">,</span> <span class="n">packet</span><span class="p">):</span>
|
||||
<span class="k">global</span> <span class="n">reticulum</span>
|
||||
|
||||
<span class="c1"># Tell the user that we received an echo request, and</span>
|
||||
<span class="c1"># that we are going to send a reply to the requester.</span>
|
||||
<span class="c1"># Sending the proof is handled automatically, since we</span>
|
||||
<span class="c1"># set up the destination to prove all incoming packets.</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">"Received packet from echo client, proof sent"</span><span class="p">)</span>
|
||||
|
||||
<span class="n">reception_stats</span> <span class="o">=</span> <span class="s2">""</span>
|
||||
<span class="k">if</span> <span class="n">reticulum</span><span class="o">.</span><span class="n">is_connected_to_shared_instance</span><span class="p">:</span>
|
||||
<span class="n">reception_rssi</span> <span class="o">=</span> <span class="n">reticulum</span><span class="o">.</span><span class="n">get_packet_rssi</span><span class="p">(</span><span class="n">packet</span><span class="o">.</span><span class="n">packet_hash</span><span class="p">)</span>
|
||||
<span class="n">reception_snr</span> <span class="o">=</span> <span class="n">reticulum</span><span class="o">.</span><span class="n">get_packet_snr</span><span class="p">(</span><span class="n">packet</span><span class="o">.</span><span class="n">packet_hash</span><span class="p">)</span>
|
||||
|
||||
<span class="k">if</span> <span class="n">reception_rssi</span> <span class="o">!=</span> <span class="kc">None</span><span class="p">:</span>
|
||||
<span class="n">reception_stats</span> <span class="o">+=</span> <span class="s2">" [RSSI "</span><span class="o">+</span><span class="nb">str</span><span class="p">(</span><span class="n">reception_rssi</span><span class="p">)</span><span class="o">+</span><span class="s2">" dBm]"</span>
|
||||
|
||||
<span class="k">if</span> <span class="n">reception_snr</span> <span class="o">!=</span> <span class="kc">None</span><span class="p">:</span>
|
||||
<span class="n">reception_stats</span> <span class="o">+=</span> <span class="s2">" [SNR "</span><span class="o">+</span><span class="nb">str</span><span class="p">(</span><span class="n">reception_snr</span><span class="p">)</span><span class="o">+</span><span class="s2">" dBm]"</span>
|
||||
|
||||
<span class="k">else</span><span class="p">:</span>
|
||||
<span class="k">if</span> <span class="n">packet</span><span class="o">.</span><span class="n">rssi</span> <span class="o">!=</span> <span class="kc">None</span><span class="p">:</span>
|
||||
<span class="n">reception_stats</span> <span class="o">+=</span> <span class="s2">" [RSSI "</span><span class="o">+</span><span class="nb">str</span><span class="p">(</span><span class="n">packet</span><span class="o">.</span><span class="n">rssi</span><span class="p">)</span><span class="o">+</span><span class="s2">" dBm]"</span>
|
||||
|
||||
<span class="k">if</span> <span class="n">packet</span><span class="o">.</span><span class="n">snr</span> <span class="o">!=</span> <span class="kc">None</span><span class="p">:</span>
|
||||
<span class="n">reception_stats</span> <span class="o">+=</span> <span class="s2">" [SNR "</span><span class="o">+</span><span class="nb">str</span><span class="p">(</span><span class="n">packet</span><span class="o">.</span><span class="n">snr</span><span class="p">)</span><span class="o">+</span><span class="s2">" dB]"</span>
|
||||
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">"Received packet from echo client, proof sent"</span><span class="o">+</span><span class="n">reception_stats</span><span class="p">)</span>
|
||||
|
||||
|
||||
<span class="c1">##########################################################</span>
|
||||
@@ -558,6 +581,8 @@ the Packet interface.</p>
|
||||
<span class="c1"># This initialisation is executed when the users chooses</span>
|
||||
<span class="c1"># to run as a client</span>
|
||||
<span class="k">def</span> <span class="nf">client</span><span class="p">(</span><span class="n">destination_hexhash</span><span class="p">,</span> <span class="n">configpath</span><span class="p">,</span> <span class="n">timeout</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span>
|
||||
<span class="k">global</span> <span class="n">reticulum</span>
|
||||
|
||||
<span class="c1"># We need a binary representation of the destination</span>
|
||||
<span class="c1"># hash that was entered on the command line</span>
|
||||
<span class="k">try</span><span class="p">:</span>
|
||||
@@ -654,6 +679,8 @@ the Packet interface.</p>
|
||||
<span class="c1"># This function is called when our reply destination</span>
|
||||
<span class="c1"># receives a proof packet.</span>
|
||||
<span class="k">def</span> <span class="nf">packet_delivered</span><span class="p">(</span><span class="n">receipt</span><span class="p">):</span>
|
||||
<span class="k">global</span> <span class="n">reticulum</span>
|
||||
|
||||
<span class="k">if</span> <span class="n">receipt</span><span class="o">.</span><span class="n">status</span> <span class="o">==</span> <span class="n">RNS</span><span class="o">.</span><span class="n">PacketReceipt</span><span class="o">.</span><span class="n">DELIVERED</span><span class="p">:</span>
|
||||
<span class="n">rtt</span> <span class="o">=</span> <span class="n">receipt</span><span class="o">.</span><span class="n">get_rtt</span><span class="p">()</span>
|
||||
<span class="k">if</span> <span class="p">(</span><span class="n">rtt</span> <span class="o">>=</span> <span class="mi">1</span><span class="p">):</span>
|
||||
@@ -663,10 +690,30 @@ the Packet interface.</p>
|
||||
<span class="n">rtt</span> <span class="o">=</span> <span class="nb">round</span><span class="p">(</span><span class="n">rtt</span><span class="o">*</span><span class="mi">1000</span><span class="p">,</span> <span class="mi">3</span><span class="p">)</span>
|
||||
<span class="n">rttstring</span> <span class="o">=</span> <span class="nb">str</span><span class="p">(</span><span class="n">rtt</span><span class="p">)</span><span class="o">+</span><span class="s2">" milliseconds"</span>
|
||||
|
||||
<span class="n">reception_stats</span> <span class="o">=</span> <span class="s2">""</span>
|
||||
<span class="k">if</span> <span class="n">reticulum</span><span class="o">.</span><span class="n">is_connected_to_shared_instance</span><span class="p">:</span>
|
||||
<span class="n">reception_rssi</span> <span class="o">=</span> <span class="n">reticulum</span><span class="o">.</span><span class="n">get_packet_rssi</span><span class="p">(</span><span class="n">receipt</span><span class="o">.</span><span class="n">proof_packet</span><span class="o">.</span><span class="n">packet_hash</span><span class="p">)</span>
|
||||
<span class="n">reception_snr</span> <span class="o">=</span> <span class="n">reticulum</span><span class="o">.</span><span class="n">get_packet_snr</span><span class="p">(</span><span class="n">receipt</span><span class="o">.</span><span class="n">proof_packet</span><span class="o">.</span><span class="n">packet_hash</span><span class="p">)</span>
|
||||
|
||||
<span class="k">if</span> <span class="n">reception_rssi</span> <span class="o">!=</span> <span class="kc">None</span><span class="p">:</span>
|
||||
<span class="n">reception_stats</span> <span class="o">+=</span> <span class="s2">" [RSSI "</span><span class="o">+</span><span class="nb">str</span><span class="p">(</span><span class="n">reception_rssi</span><span class="p">)</span><span class="o">+</span><span class="s2">" dBm]"</span>
|
||||
|
||||
<span class="k">if</span> <span class="n">reception_snr</span> <span class="o">!=</span> <span class="kc">None</span><span class="p">:</span>
|
||||
<span class="n">reception_stats</span> <span class="o">+=</span> <span class="s2">" [SNR "</span><span class="o">+</span><span class="nb">str</span><span class="p">(</span><span class="n">reception_snr</span><span class="p">)</span><span class="o">+</span><span class="s2">" dB]"</span>
|
||||
|
||||
<span class="k">else</span><span class="p">:</span>
|
||||
<span class="k">if</span> <span class="n">receipt</span><span class="o">.</span><span class="n">proof_packet</span> <span class="o">!=</span> <span class="kc">None</span><span class="p">:</span>
|
||||
<span class="k">if</span> <span class="n">receipt</span><span class="o">.</span><span class="n">proof_packet</span><span class="o">.</span><span class="n">rssi</span> <span class="o">!=</span> <span class="kc">None</span><span class="p">:</span>
|
||||
<span class="n">reception_stats</span> <span class="o">+=</span> <span class="s2">" [RSSI "</span><span class="o">+</span><span class="nb">str</span><span class="p">(</span><span class="n">receipt</span><span class="o">.</span><span class="n">proof_packet</span><span class="o">.</span><span class="n">rssi</span><span class="p">)</span><span class="o">+</span><span class="s2">" dBm]"</span>
|
||||
|
||||
<span class="k">if</span> <span class="n">receipt</span><span class="o">.</span><span class="n">proof_packet</span><span class="o">.</span><span class="n">snr</span> <span class="o">!=</span> <span class="kc">None</span><span class="p">:</span>
|
||||
<span class="n">reception_stats</span> <span class="o">+=</span> <span class="s2">" [SNR "</span><span class="o">+</span><span class="nb">str</span><span class="p">(</span><span class="n">receipt</span><span class="o">.</span><span class="n">proof_packet</span><span class="o">.</span><span class="n">snr</span><span class="p">)</span><span class="o">+</span><span class="s2">" dB]"</span>
|
||||
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span>
|
||||
<span class="s2">"Valid reply received from "</span><span class="o">+</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">prettyhexrep</span><span class="p">(</span><span class="n">receipt</span><span class="o">.</span><span class="n">destination</span><span class="o">.</span><span class="n">hash</span><span class="p">)</span><span class="o">+</span>
|
||||
<span class="s2">", round-trip time is "</span><span class="o">+</span><span class="n">rttstring</span>
|
||||
<span class="s2">", round-trip time is "</span><span class="o">+</span><span class="n">rttstring</span><span class="o">+</span>
|
||||
<span class="n">reception_stats</span>
|
||||
<span class="p">)</span>
|
||||
|
||||
<span class="c1"># This function is called if a packet times out.</span>
|
||||
@@ -2319,7 +2366,7 @@ 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.7 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.0 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">Code Examples</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>Index — Reticulum Network Stack 0.2.7 beta documentation</title>
|
||||
<title>Index — Reticulum Network Stack 0.3.0 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.7 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.0 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">Index</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -416,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.7 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.0 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.7 beta documentation</title>
|
||||
<title>Getting Started Fast — Reticulum Network Stack 0.3.0 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="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.7 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.0 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">Getting Started Fast</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -53,7 +53,7 @@ a look at <a class="reference external" href="https://github.com/markqvist/nomad
|
||||
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
|
||||
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>
|
||||
@@ -79,7 +79,8 @@ network status and connectivity.</p>
|
||||
<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>
|
||||
default is located at <code class="docutils literal notranslate"><span class="pre">~/.reticulum/config</span></code>. You can edit this file by hand,
|
||||
or use the interactive <code class="docutils literal notranslate"><span class="pre">rnsconfig</span></code> utility.</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
|
||||
@@ -101,6 +102,10 @@ started is to install the latest release of Reticulum via pip:</p>
|
||||
<p>The above command will install Reticulum and dependencies, and you will be
|
||||
ready to import and use RNS in your own programs. The next step will most
|
||||
likely be to look at some <a class="reference internal" href="examples.html#examples-main"><span class="std std-ref">Example Programs</span></a>.</p>
|
||||
<p>For extended functionality, you can install optional dependencies:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">pip3</span> <span class="n">install</span> <span class="n">pyserial</span> <span class="n">netifaces</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>Further information can be found in the <a class="reference internal" href="reference.html#api-main"><span class="std std-ref">API Reference</span></a>.</p>
|
||||
</div>
|
||||
<div class="section" id="participate-in-reticulum-development">
|
||||
@@ -143,6 +148,60 @@ don’t use pip, but try this recipe:</p>
|
||||
<p>When you have experimented with the basic examples, it’s time to go read the
|
||||
<a class="reference internal" href="understanding.html#understanding-main"><span class="std std-ref">Understanding Reticulum</span></a> chapter.</p>
|
||||
</div>
|
||||
<div class="section" id="reticulum-on-arm64">
|
||||
<h2>Reticulum on ARM64<a class="headerlink" href="#reticulum-on-arm64" title="Permalink to this headline">¶</a></h2>
|
||||
<p>On some architectures, including ARM64, not all dependencies have precompiled
|
||||
binaries. On such systems, you will need to install <code class="docutils literal notranslate"><span class="pre">python3-dev</span></code> before
|
||||
installing Reticulum or programs that depend on Reticulum.</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># Install Python and development packages</span>
|
||||
<span class="n">sudo</span> <span class="n">apt</span> <span class="n">update</span>
|
||||
<span class="n">sudo</span> <span class="n">apt</span> <span class="n">install</span> <span class="n">python3</span> <span class="n">python3</span><span class="o">-</span><span class="n">pip</span> <span class="n">python3</span><span class="o">-</span><span class="n">dev</span>
|
||||
|
||||
<span class="c1"># Install Reticulum</span>
|
||||
<span class="n">python3</span> <span class="o">-</span><span class="n">m</span> <span class="n">pip</span> <span class="n">install</span> <span class="n">rns</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="section" id="reticulum-on-android">
|
||||
<h2>Reticulum on Android<a class="headerlink" href="#reticulum-on-android" title="Permalink to this headline">¶</a></h2>
|
||||
<p>Reticulum can be used on Android in different ways. The easiest way to get
|
||||
started is using the <a class="reference external" href="https://termux.com/">Termux app</a>, at the time of writing
|
||||
available on <a class="reference external" href="https://f-droid.org">F-droid</a>.</p>
|
||||
<p>Termux is a terminal emulator and Linux environment for Android based devices,
|
||||
which includes the ability to use many different programs and libraries,
|
||||
including Reticulum.</p>
|
||||
<p>Since the Python cryptography.io module does not offer pre-built wheels for
|
||||
Android, the standard one-line install of Reticulum does not work on Android,
|
||||
and a few extra commands are required.</p>
|
||||
<p>From within Termux, execute the following:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># First, make sure indexes and packages are up to date.</span>
|
||||
<span class="n">pkg</span> <span class="n">update</span>
|
||||
<span class="n">pkg</span> <span class="n">upgrade</span>
|
||||
|
||||
<span class="c1"># Then install dependencies for the cryptography library.</span>
|
||||
<span class="n">pkg</span> <span class="n">install</span> <span class="n">python</span> <span class="n">build</span><span class="o">-</span><span class="n">essential</span> <span class="n">openssl</span> <span class="n">libffi</span> <span class="n">rust</span>
|
||||
|
||||
<span class="c1"># Make sure pip is up to date, and install the wheel module.</span>
|
||||
<span class="n">pip3</span> <span class="n">install</span> <span class="n">wheel</span> <span class="n">pip</span> <span class="o">--</span><span class="n">upgrade</span>
|
||||
|
||||
<span class="c1"># To allow the installer to build the cryptography module,</span>
|
||||
<span class="c1"># we need to let it know what platform we are compiling for:</span>
|
||||
<span class="n">export</span> <span class="n">CARGO_BUILD_TARGET</span><span class="o">=</span><span class="s2">"aarch64-linux-android"</span>
|
||||
|
||||
<span class="c1"># Start the install process for the cryptography module.</span>
|
||||
<span class="c1"># Depending on your device, this can take several minutes,</span>
|
||||
<span class="c1"># since the module must be compiled locally on your device.</span>
|
||||
<span class="n">pip3</span> <span class="n">install</span> <span class="n">cryptography</span>
|
||||
|
||||
<span class="c1"># If the above installation succeeds, you can now install</span>
|
||||
<span class="c1"># Reticulum and any related software</span>
|
||||
<span class="n">pip3</span> <span class="n">install</span> <span class="n">rns</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>It is also possible to include Reticulum in apps compiled and distributed as
|
||||
Android APKs. A detailed tutorial and example source code will be included
|
||||
here at a later point.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -160,6 +219,8 @@ don’t use pip, but try this recipe:</p>
|
||||
<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>
|
||||
<li><a class="reference internal" href="#reticulum-on-arm64">Reticulum on ARM64</a></li>
|
||||
<li><a class="reference internal" href="#reticulum-on-android">Reticulum on Android</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -203,7 +264,7 @@ don’t use pip, but try this recipe:</p>
|
||||
<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.7 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.0 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">Getting Started Fast</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
+12
-4
@@ -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.7 beta documentation</title>
|
||||
<title>Reticulum Network Stack Manual — Reticulum Network Stack 0.3.0 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.7 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-0"><a href="#">Reticulum Network Stack 0.3.0 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">Reticulum Network Stack Manual</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -46,10 +46,10 @@ to participate in the development of Reticulum itself.</p>
|
||||
<ul>
|
||||
<li class="toctree-l1"><a class="reference internal" href="whatis.html">What is Reticulum?</a><ul>
|
||||
<li class="toctree-l2"><a class="reference internal" href="whatis.html#current-status">Current Status</a></li>
|
||||
<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#interface-types-and-devices">Interface Types and Devices</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="whatis.html#caveat-emptor">Caveat Emptor</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="gettingstartedfast.html">Getting Started Fast</a><ul>
|
||||
@@ -58,6 +58,8 @@ to participate in the development of Reticulum itself.</p>
|
||||
<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>
|
||||
<li class="toctree-l2"><a class="reference internal" href="gettingstartedfast.html#reticulum-on-arm64">Reticulum on ARM64</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="gettingstartedfast.html#reticulum-on-android">Reticulum on Android</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="using.html">Using Reticulum on Your System</a><ul>
|
||||
@@ -68,6 +70,11 @@ to participate in the development of Reticulum itself.</p>
|
||||
<li class="toctree-l3"><a class="reference internal" href="using.html#the-rnprobe-utility">The rnprobe Utility</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="using.html#improving-system-configuration">Improving System Configuration</a><ul>
|
||||
<li class="toctree-l3"><a class="reference internal" href="using.html#fixed-serial-port-names">Fixed Serial Port Names</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="using.html#reticulum-as-a-system-service">Reticulum as a System Service</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="networks.html">Building Networks</a><ul>
|
||||
@@ -81,6 +88,7 @@ to participate in the development of Reticulum itself.</p>
|
||||
</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#auto-interface">Auto Interface</a></li>
|
||||
<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>
|
||||
@@ -200,7 +208,7 @@ to participate in 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.7 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-0"><a href="#">Reticulum Network Stack 0.3.0 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">Reticulum Network Stack Manual</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>Supported Interfaces — Reticulum Network Stack 0.2.7 beta documentation</title>
|
||||
<title>Supported Interfaces — Reticulum Network Stack 0.3.0 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="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-0"><a href="index.html">Reticulum Network Stack 0.3.0 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">Supported Interfaces</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -53,12 +53,75 @@ 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="auto-interface">
|
||||
<span id="interfaces-auto"></span><h2>Auto Interface<a class="headerlink" href="#auto-interface" title="Permalink to this headline">¶</a></h2>
|
||||
<p>The Auto Interface enables communication with other discoverable Reticulum
|
||||
nodes over autoconfigured IPv6 and UDP. It does not need any functional IP
|
||||
infrastructure like routers or DHCP servers, but will require at least some
|
||||
sort of switching medium between peers (a wired switch, a hub, a WiFi access
|
||||
point or similar), and that link-local IPv6 is enabled in your operating
|
||||
system, which should be enabled by default in almost all OSes.</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">Default</span> <span class="n">Interface</span><span class="p">]]</span>
|
||||
<span class="nb">type</span> <span class="o">=</span> <span class="n">AutoInterface</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"># You can create multiple isolated Reticulum</span>
|
||||
<span class="c1"># networks on the same physical LAN by</span>
|
||||
<span class="c1"># specifying different Group IDs.</span>
|
||||
|
||||
<span class="n">group_id</span> <span class="o">=</span> <span class="n">reticulum</span>
|
||||
|
||||
<span class="c1"># You can also select specifically which</span>
|
||||
<span class="c1"># kernel networking devices to use.</span>
|
||||
|
||||
<span class="n">devices</span> <span class="o">=</span> <span class="n">wlan0</span><span class="p">,</span><span class="n">eth1</span>
|
||||
|
||||
<span class="c1"># Or let AutoInterface use all suitable</span>
|
||||
<span class="c1"># devices except for a list of ignored ones.</span>
|
||||
|
||||
<span class="n">ignored_devices</span> <span class="o">=</span> <span class="n">tun0</span><span class="p">,</span><span class="n">eth0</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>If you are connected to the Internet with IPv6, and your provider will route
|
||||
IPv6 multicast, you can potentially configure the Auto Interface to globally
|
||||
autodiscover other Reticulum nodes within your selected Group ID. You can specify
|
||||
the discovery scope by setting it to one of <code class="docutils literal notranslate"><span class="pre">link</span></code>, <code class="docutils literal notranslate"><span class="pre">admin</span></code>, <code class="docutils literal notranslate"><span class="pre">site</span></code>,
|
||||
<code class="docutils literal notranslate"><span class="pre">organisation</span></code> or <code class="docutils literal notranslate"><span class="pre">global</span></code>.</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="p">[[</span><span class="n">Default</span> <span class="n">Interface</span><span class="p">]]</span>
|
||||
<span class="nb">type</span> <span class="o">=</span> <span class="n">AutoInterface</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"># Configure global discovery</span>
|
||||
|
||||
<span class="n">group_id</span> <span class="o">=</span> <span class="n">custom_network_name</span>
|
||||
<span class="n">discovery_scope</span> <span class="o">=</span> <span class="k">global</span>
|
||||
|
||||
<span class="c1"># Other configuration options</span>
|
||||
|
||||
<span class="n">discovery_port</span> <span class="o">=</span> <span class="mi">48555</span>
|
||||
<span class="n">data_port</span> <span class="o">=</span> <span class="mi">49555</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p><em>Please Note!</em> If you use the Auto Interface, you will need the Python module
|
||||
<code class="docutils literal notranslate"><span class="pre">netifaces</span></code> installed on your system. You can install it with <code class="docutils literal notranslate"><span class="pre">pip3</span> <span class="pre">install</span> <span class="pre">netifaces</span></code>.</p>
|
||||
</div>
|
||||
<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><em>Please Note!</em> Using broadcast UDP traffic has performance implications,
|
||||
especially on WiFi. If your goal is simply to enable easy communication
|
||||
with all peers in your local ethernet broadcast domain, the
|
||||
<a class="reference internal" href="#interfaces-auto"><span class="std std-ref">Auto Interface</span></a> performs better, and is just as
|
||||
easy to use.</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>
|
||||
@@ -76,9 +139,7 @@ pre-existing LAN.</p>
|
||||
|
||||
<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"># IP interfaces.</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>
|
||||
@@ -107,6 +168,8 @@ pre-existing LAN.</p>
|
||||
<span class="c1"># forward_port = 4242</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p><em>Please Note!</em> If you use the <code class="docutils literal notranslate"><span class="pre">device</span></code> option, you will need the Python module
|
||||
<code class="docutils literal notranslate"><span class="pre">netifaces</span></code> installed on your system. You can install it with <code class="docutils literal notranslate"><span class="pre">pip3</span> <span class="pre">install</span> <span class="pre">netifaces</span></code>.</p>
|
||||
</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>
|
||||
@@ -139,6 +202,8 @@ configured, other Reticulum peers can connect to it with a TCP Client interface.
|
||||
<span class="c1"># port = 4242</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p><em>Please Note!</em> If you use the <code class="docutils literal notranslate"><span class="pre">device</span></code> option, you will need the Python module
|
||||
<code class="docutils literal notranslate"><span class="pre">netifaces</span></code> installed on your system. You can install it with <code class="docutils literal notranslate"><span class="pre">pip3</span> <span class="pre">install</span> <span class="pre">netifaces</span></code>.</p>
|
||||
</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>
|
||||
@@ -156,6 +221,27 @@ same TCP Server interface at the same time.</p>
|
||||
<span class="n">target_port</span> <span class="o">=</span> <span class="mi">4242</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>It is also possible to use this interface type to connect via other programs
|
||||
or hardware devices that expose a KISS interface on a TCP port, for example
|
||||
software-based soundmodems. To do this, use the <code class="docutils literal notranslate"><span class="pre">kiss_framing</span></code> option:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># Here's an example of a TCP Client interface that connects</span>
|
||||
<span class="c1"># to a software TNC soundmodem on a KISS over TCP port.</span>
|
||||
|
||||
<span class="p">[[</span><span class="n">TCP</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">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">kiss_framing</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">8001</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p><strong>Caution!</strong> Only use the KISS framing option when connecting to external devices
|
||||
and programs like soundmodems and similar over TCP. When using the
|
||||
<code class="docutils literal notranslate"><span class="pre">TCPClientInterface</span></code> in conjunction with the <code class="docutils literal notranslate"><span class="pre">TCPServerInterface</span></code> you should
|
||||
never enable <code class="docutils literal notranslate"><span class="pre">kiss_framing</span></code>, since this will disable internal reliability and
|
||||
recovery mechanisms that greatly improves performance over unreliable and
|
||||
intermittent TCP links.</p>
|
||||
</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>
|
||||
@@ -359,6 +445,7 @@ beaconing functionality described above.</p>
|
||||
<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="#auto-interface">Auto Interface</a></li>
|
||||
<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>
|
||||
@@ -409,7 +496,7 @@ beaconing functionality described above.</p>
|
||||
<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-0"><a href="index.html">Reticulum Network Stack 0.3.0 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">Supported Interfaces</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>Building Networks — Reticulum Network Stack 0.2.7 beta documentation</title>
|
||||
<title>Building Networks — Reticulum Network Stack 0.3.0 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="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-0"><a href="index.html">Reticulum Network Stack 0.3.0 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">Building Networks</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -247,7 +247,7 @@ connected outliers are now an integral part of the network.</p>
|
||||
<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-0"><a href="index.html">Reticulum Network Stack 0.3.0 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">Building Networks</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
Binary file not shown.
@@ -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.7 beta documentation</title>
|
||||
<title>API Reference — Reticulum Network Stack 0.3.0 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="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.7 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.0 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">API Reference</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -1238,7 +1238,7 @@ will announce it.</p>
|
||||
<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.7 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.0 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.7 beta documentation</title>
|
||||
<title>Search — Reticulum Network Stack 0.3.0 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.7 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.0 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.7 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.0 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">Search</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -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.7 beta documentation</title>
|
||||
<title>Understanding Reticulum — Reticulum Network Stack 0.3.0 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="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.7 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.0 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">Understanding Reticulum</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -100,12 +100,18 @@ it can be easily replicated.</p>
|
||||
</li>
|
||||
<li><dl class="simple">
|
||||
<dt><strong>Very low bandwidth requirements</strong></dt><dd><p>Reticulum should be able to function reliably over links with a transmission capacity as low
|
||||
as <em>1,000 bps</em>.</p>
|
||||
as <em>500 bps</em>.</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</li>
|
||||
<li><dl class="simple">
|
||||
<dt><strong>Encryption by default</strong></dt><dd><p>Reticulum must use encryption by default where possible and applicable.</p>
|
||||
<dt><strong>Encryption by default</strong></dt><dd><p>Reticulum must use strong encryption by default for all communication.</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</li>
|
||||
<li><dl class="simple">
|
||||
<dt><strong>Initiator Anonymity</strong></dt><dd><p>It must be possible to communicate over a Reticulum network without revealing any identifying
|
||||
information about oneself.</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</li>
|
||||
@@ -148,7 +154,7 @@ needs to be purchased.</p>
|
||||
<p>Reticulum is a networking stack suited for high-latency, low-bandwidth links. Reticulum is at it’s
|
||||
core a <em>message oriented</em> system. It is suited for both local point-to-point or point-to-multipoint
|
||||
scenarios where alle nodes are within range of each other, as well as scenarios where packets need
|
||||
to be transported over multiple hops to reach the recipient.</p>
|
||||
to be transported over multiple hops in a complex network to reach the recipient.</p>
|
||||
<p>Reticulum does away with the idea of addresses and ports known from IP, TCP and UDP. Instead
|
||||
Reticulum uses the singular concept of <em>destinations</em>. Any application using Reticulum as it’s
|
||||
networking stack will need to create one or more destinations to receive data, and know the
|
||||
@@ -156,9 +162,9 @@ destinations it needs to send data to.</p>
|
||||
<p>All destinations in Reticulum are represented internally as 10 bytes, derived from truncating a full
|
||||
SHA-256 hash of identifying characteristics of the destination. To users, the destination addresses
|
||||
will be displayed as 10 bytes in hexadecimal representation, as in the following example: <code class="docutils literal notranslate"><span class="pre"><80e29bf7cccaf31431b3></span></code>.</p>
|
||||
<p>By default Reticulum encrypts all data using public-key cryptography. Any message sent to a
|
||||
destination is encrypted with that destinations public key. Reticulum can also set up an encrypted
|
||||
channel to a destination with <em>Perfect Forward Secrecy</em> and <em>Initiator Anonymity</em> using a elliptic
|
||||
<p>By default Reticulum encrypts all data using elliptic curve cryptography. Any packet sent to a
|
||||
destination is encrypted with a derived ephemeral key. Reticulum can also set up an encrypted
|
||||
channel to a destination with <em>Forward Secrecy</em> and <em>Initiator Anonymity</em> using a elliptic
|
||||
curve cryptography and ephemeral keys derived from a Diffie Hellman exchange on Curve25519. In
|
||||
Reticulum terminology, this is called a <em>Link</em>.</p>
|
||||
<p>Reticulum also offers symmetric key encryption for group-oriented communications, as well as
|
||||
@@ -174,23 +180,23 @@ private IP networks.</p>
|
||||
destinations. Reticulum uses three different basic destination types, and one special:</p>
|
||||
<ul class="simple">
|
||||
<li><dl class="simple">
|
||||
<dt><strong>Single</strong></dt><dd><p>The <em>single</em> destination type defines a public-key encrypted destination. Any data sent to this
|
||||
destination will be encrypted with the destination’s public key, and will only be readable by
|
||||
the creator of the destination.</p>
|
||||
<dt><strong>Single</strong></dt><dd><p>The <em>single</em> destination type is always identified by a unique public key. Any data sent to this
|
||||
destination will be encrypted using ephemeral keys derived from an ECDH key exchange, and will
|
||||
only be readable by the creator of the destination, who holds the corresponding private key.</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</li>
|
||||
<li><dl class="simple">
|
||||
<dt><strong>Group</strong></dt><dd><p>The <em>group</em> destination type defines a symmetrically encrypted destination. Data sent to this
|
||||
destination will be encrypted with a symmetric key, and will be readable by anyone in
|
||||
possession of the key. The <em>group</em> destination can be used just as well by only two peers, as it
|
||||
can by many.</p>
|
||||
possession of the key.</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</li>
|
||||
<li><dl class="simple">
|
||||
<dt><strong>Plain</strong></dt><dd><p>A <em>plain</em> destination type is unencrypted, and suited for traffic that should be broadcast to a
|
||||
number of users, or should be readable by anyone. Traffic to a <em>plain</em> destination is not encrypted.</p>
|
||||
number of users, or should be readable by anyone. Traffic to a <em>plain</em> destination is not encrypted.
|
||||
Generally, <em>plain</em> destinations can be used for broadcast information intended to be public.</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</li>
|
||||
@@ -575,7 +581,7 @@ the transfer is needed.</p>
|
||||
<p>This is the purpose of the Reticulum <a class="reference internal" href="reference.html#api-resource"><span class="std std-ref">Resource</span></a>. A <em>Resource</em> can automatically
|
||||
handle the reliable transfer of an arbitrary amount of data over an established <a class="reference internal" href="reference.html#api-link"><span class="std std-ref">Link</span></a>.
|
||||
Resources can auto-compress data, will handle breaking the data into individual packets, sequencing
|
||||
the transfer and reassembling the data on the other end.</p>
|
||||
the transfer, integrity verification and reassembling the data on the other end.</p>
|
||||
<p><a class="reference internal" href="reference.html#api-resource"><span class="std std-ref">Resources</span></a> are programmatically very simple to use, and only requires a few lines
|
||||
of codes to reliably transfer any amount of data. They can be used to transfer data stored in memory,
|
||||
or stream data directly from files.</p>
|
||||
@@ -654,8 +660,8 @@ treated more as a reference than as essential reading.</p>
|
||||
<div class="section" id="node-types">
|
||||
<h3>Node Types<a class="headerlink" href="#node-types" title="Permalink to this headline">¶</a></h3>
|
||||
<p>Currently Reticulum defines two node types, the <em>Station</em> and the <em>Peer</em>. A node is a <em>station</em> if it fixed
|
||||
in one place, and if it is intended to be kept online most of the time. Otherwise the node is a <em>peer</em>.
|
||||
This distinction is made by the user configuring the node, and is used to determine what nodes on the
|
||||
in one place, and if it is intended to be kept online most of the time. Otherwise the node is a <em>peer</em>.</p>
|
||||
<p>This distinction is made by the user configuring the node, and is used to determine what nodes on the
|
||||
network will help forward traffic, and what nodes rely on other nodes for connectivity.</p>
|
||||
<p>If a node is a <em>Peer</em> it should be given the configuration directive <code class="docutils literal notranslate"><span class="pre">enable_transport</span> <span class="pre">=</span> <span class="pre">No</span></code>.</p>
|
||||
<p>If it is a <em>Station</em>, it should be given the configuration directive <code class="docutils literal notranslate"><span class="pre">enable_transport</span> <span class="pre">=</span> <span class="pre">Yes</span></code>.</p>
|
||||
@@ -665,9 +671,6 @@ network will help forward traffic, and what nodes rely on other nodes for connec
|
||||
<p>Currently, Reticulum is completely priority-agnostic regarding general traffic. All traffic is handled
|
||||
on a first-come, first-serve basis. Announce re-transmission are handled according to the re-transmission
|
||||
times and priorities described earlier in this chapter.</p>
|
||||
<p>It is possible that a prioritisation engine could be added to Reticulum in the future, but in
|
||||
the light of Reticulums goal of equal access, doing so would need to be the subject of careful
|
||||
investigation of the consequences first.</p>
|
||||
</div>
|
||||
<div class="section" id="binary-packet-format">
|
||||
<span id="understanding-packetformat"></span><h3>Binary Packet Format<a class="headerlink" href="#binary-packet-format" title="Permalink to this headline">¶</a></h3>
|
||||
@@ -853,7 +856,7 @@ proof 11
|
||||
<li class="right" >
|
||||
<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.7 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.0 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">Understanding Reticulum</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
+87
-3
@@ -5,7 +5,7 @@
|
||||
<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>
|
||||
<title>Using Reticulum on Your System — Reticulum Network Stack 0.3.0 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="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-0"><a href="index.html">Reticulum Network Stack 0.3.0 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">Using Reticulum on Your System</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -87,6 +87,7 @@ optional arguments:
|
||||
--version show program's version number and exit
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>You can easily add <code class="docutils literal notranslate"><span class="pre">rnsd</span></code> as an always-on service by <a class="reference internal" href="#using-systemd"><span class="std std-ref">configuring a service</span></a>.</p>
|
||||
</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>
|
||||
@@ -184,6 +185,84 @@ optional arguments:
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="section" id="improving-system-configuration">
|
||||
<h2>Improving System Configuration<a class="headerlink" href="#improving-system-configuration" title="Permalink to this headline">¶</a></h2>
|
||||
<p>If you are setting up a system for permanent use with Reticulum, there is a
|
||||
few system configuration changes that can make this easier to administrate.
|
||||
These changes will be detailed here.</p>
|
||||
<div class="section" id="fixed-serial-port-names">
|
||||
<h3>Fixed Serial Port Names<a class="headerlink" href="#fixed-serial-port-names" title="Permalink to this headline">¶</a></h3>
|
||||
<p>On a Reticulum node with several serial port based interfaces, it can be
|
||||
beneficial to use the fixed name device nodes for the serial ports, instead
|
||||
of the dynamically allocated shorthands such as <code class="docutils literal notranslate"><span class="pre">/dev/ttyUSB0</span></code>. Under most
|
||||
Debian-based distributions, including Ubuntu and Raspberry Pi OS, these nodes
|
||||
can be found under <code class="docutils literal notranslate"><span class="pre">/dev/serial/by-id</span></code>.</p>
|
||||
<p>You can use such a device path directly in place of the numbered shorthands.
|
||||
Here is an example of a packet radio TNC configured as such:</p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>[[Packet Radio KISS Interface]]
|
||||
type = KISSInterface
|
||||
interface_enabled = True
|
||||
outgoing = true
|
||||
port = /dev/serial/by-id/usb-FTDI_FT230X_Basic_UART_43891CKM-if00-port0
|
||||
speed = 115200
|
||||
databits = 8
|
||||
parity = none
|
||||
stopbits = 1
|
||||
preamble = 150
|
||||
txtail = 10
|
||||
persistence = 200
|
||||
slottime = 20
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>Using this methodology avoids potential naming mix-ups where physical devices
|
||||
might be plugged and unplugged in different orders, or when node name
|
||||
assignment varies from one boot to another.</p>
|
||||
</div>
|
||||
<div class="section" id="reticulum-as-a-system-service">
|
||||
<span id="using-systemd"></span><h3>Reticulum as a System Service<a class="headerlink" href="#reticulum-as-a-system-service" title="Permalink to this headline">¶</a></h3>
|
||||
<p>Instead of starting Reticulum manually, you can install <code class="docutils literal notranslate"><span class="pre">rnsd</span></code> as a system
|
||||
service and have it start automatically at boot.</p>
|
||||
<p>If you installed Reticulum with <code class="docutils literal notranslate"><span class="pre">pip</span></code>, the <code class="docutils literal notranslate"><span class="pre">rnsd</span></code> program will most likely
|
||||
be located in a user-local installation path only, which means <code class="docutils literal notranslate"><span class="pre">systemd</span></code> will not
|
||||
be able to execute it. In this case, you can simply symlink the <code class="docutils literal notranslate"><span class="pre">rnsd</span></code> program
|
||||
into a directory that is in systemd’s path:</p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>sudo ln -s $(which rnsd) /usr/local/bin/
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>You can then create the service file <code class="docutils literal notranslate"><span class="pre">/etc/systemd/system/rnsd.service</span></code> with the
|
||||
following content:</p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>[Unit]
|
||||
Description=Reticulum Network Stack Daemon
|
||||
After=multi-user.target
|
||||
|
||||
[Service]
|
||||
# If you run Reticulum on WiFi devices,
|
||||
# or other devices that need some extra
|
||||
# time to initialise, you might want to
|
||||
# add a short delay before Reticulum is
|
||||
# started by systemd:
|
||||
# ExecStartPre=/bin/sleep 10
|
||||
Type=simple
|
||||
Restart=always
|
||||
RestartSec=3
|
||||
User=USERNAMEHERE
|
||||
ExecStart=rnsd --service
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>Be sure to replace <code class="docutils literal notranslate"><span class="pre">USERNAMEHERE</span></code> with the user you want to run <code class="docutils literal notranslate"><span class="pre">rnsd</span></code> as.</p>
|
||||
<p>To manually start <code class="docutils literal notranslate"><span class="pre">rnsd</span></code> run:</p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>sudo systemctl start rnsd
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>If you want to automatically start <code class="docutils literal notranslate"><span class="pre">rnsd</span></code> at boot, run:</p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>sudo systemctl enable rnsd
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -203,6 +282,11 @@ optional arguments:
|
||||
<li><a class="reference internal" href="#the-rnprobe-utility">The rnprobe Utility</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a class="reference internal" href="#improving-system-configuration">Improving System Configuration</a><ul>
|
||||
<li><a class="reference internal" href="#fixed-serial-port-names">Fixed Serial Port Names</a></li>
|
||||
<li><a class="reference internal" href="#reticulum-as-a-system-service">Reticulum as a System Service</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -246,7 +330,7 @@ optional arguments:
|
||||
<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-0"><a href="index.html">Reticulum Network Stack 0.3.0 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">Using Reticulum on Your System</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
+10
-10
@@ -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.7 beta documentation</title>
|
||||
<title>What is Reticulum? — Reticulum Network Stack 0.3.0 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.7 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.0 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">What is Reticulum?</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -43,7 +43,7 @@
|
||||
|
||||
<div class="section" id="what-is-reticulum">
|
||||
<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 is a cryptography-based networking stack for wide-area networks built on readily available hardware, that 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 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>
|
||||
@@ -51,10 +51,6 @@
|
||||
<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 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">
|
||||
@@ -92,7 +88,7 @@
|
||||
<div class="section" id="where-can-reticulum-be-used">
|
||||
<h2>Where can Reticulum be Used?<a class="headerlink" href="#where-can-reticulum-be-used" title="Permalink to this headline">¶</a></h2>
|
||||
<p>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,
|
||||
with 500 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.</p>
|
||||
@@ -124,6 +120,10 @@ network, and vice versa.</p>
|
||||
</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 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 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>
|
||||
|
||||
|
||||
@@ -137,10 +137,10 @@ network, and vice versa.</p>
|
||||
<ul>
|
||||
<li><a class="reference internal" href="#">What is Reticulum?</a><ul>
|
||||
<li><a class="reference internal" href="#current-status">Current Status</a></li>
|
||||
<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="#interface-types-and-devices">Interface Types and Devices</a></li>
|
||||
<li><a class="reference internal" href="#caveat-emptor">Caveat Emptor</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -184,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.7 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.0 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">What is Reticulum?</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@ copyright = '2021, Mark Qvist'
|
||||
author = 'Mark Qvist'
|
||||
|
||||
# The full version, including alpha/beta/rc tags
|
||||
release = '0.2.7 beta'
|
||||
release = '0.3.0 beta'
|
||||
|
||||
|
||||
# -- General configuration ---------------------------------------------------
|
||||
|
||||
@@ -16,7 +16,7 @@ provides a complete encrypted communications suite built with Reticulum.
|
||||
: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
|
||||
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:
|
||||
@@ -48,7 +48,8 @@ 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``.
|
||||
default is located at ``~/.reticulum/config``. You can edit this file by hand,
|
||||
or use the interactive ``rnsconfig`` utility.
|
||||
|
||||
When Reticulum is started for the first time, it will create a default
|
||||
configuration file, with one active interface. This default interface uses
|
||||
@@ -77,6 +78,13 @@ The above command will install Reticulum and dependencies, and you will be
|
||||
ready to import and use RNS in your own programs. The next step will most
|
||||
likely be to look at some :ref:`Example Programs<examples-main>`.
|
||||
|
||||
For extended functionality, you can install optional dependencies:
|
||||
|
||||
.. code::
|
||||
|
||||
pip3 install pyserial netifaces
|
||||
|
||||
|
||||
Further information can be found in the :ref:`API Reference<api-main>`.
|
||||
|
||||
|
||||
@@ -120,4 +128,66 @@ don't use pip, but try this recipe:
|
||||
python3 Examples/Filetransfer.py -h
|
||||
|
||||
When you have experimented with the basic examples, it's time to go read the
|
||||
:ref:`Understanding Reticulum<understanding-main>` chapter.
|
||||
:ref:`Understanding Reticulum<understanding-main>` chapter.
|
||||
|
||||
|
||||
Reticulum on ARM64
|
||||
==============================================
|
||||
On some architectures, including ARM64, not all dependencies have precompiled
|
||||
binaries. On such systems, you will need to install ``python3-dev`` before
|
||||
installing Reticulum or programs that depend on Reticulum.
|
||||
|
||||
.. code::
|
||||
|
||||
# Install Python and development packages
|
||||
sudo apt update
|
||||
sudo apt install python3 python3-pip python3-dev
|
||||
|
||||
# Install Reticulum
|
||||
python3 -m pip install rns
|
||||
|
||||
|
||||
Reticulum on Android
|
||||
==============================================
|
||||
Reticulum can be used on Android in different ways. The easiest way to get
|
||||
started is using the `Termux app <https://termux.com/>`_, at the time of writing
|
||||
available on `F-droid <https://f-droid.org>`_.
|
||||
|
||||
Termux is a terminal emulator and Linux environment for Android based devices,
|
||||
which includes the ability to use many different programs and libraries,
|
||||
including Reticulum.
|
||||
|
||||
Since the Python cryptography.io module does not offer pre-built wheels for
|
||||
Android, the standard one-line install of Reticulum does not work on Android,
|
||||
and a few extra commands are required.
|
||||
|
||||
From within Termux, execute the following:
|
||||
|
||||
.. code::
|
||||
|
||||
# First, make sure indexes and packages are up to date.
|
||||
pkg update
|
||||
pkg upgrade
|
||||
|
||||
# Then install dependencies for the cryptography library.
|
||||
pkg install python build-essential openssl libffi rust
|
||||
|
||||
# Make sure pip is up to date, and install the wheel module.
|
||||
pip3 install wheel pip --upgrade
|
||||
|
||||
# To allow the installer to build the cryptography module,
|
||||
# we need to let it know what platform we are compiling for:
|
||||
export CARGO_BUILD_TARGET="aarch64-linux-android"
|
||||
|
||||
# Start the install process for the cryptography module.
|
||||
# Depending on your device, this can take several minutes,
|
||||
# since the module must be compiled locally on your device.
|
||||
pip3 install cryptography
|
||||
|
||||
# If the above installation succeeds, you can now install
|
||||
# Reticulum and any related software
|
||||
pip3 install rns
|
||||
|
||||
It is also possible to include Reticulum in apps compiled and distributed as
|
||||
Android APKs. A detailed tutorial and example source code will be included
|
||||
here at a later point.
|
||||
|
||||
+103
-3
@@ -18,6 +18,73 @@ 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-auto:
|
||||
|
||||
Auto Interface
|
||||
==============
|
||||
|
||||
The Auto Interface enables communication with other discoverable Reticulum
|
||||
nodes over autoconfigured IPv6 and UDP. It does not need any functional IP
|
||||
infrastructure like routers or DHCP servers, but will require at least some
|
||||
sort of switching medium between peers (a wired switch, a hub, a WiFi access
|
||||
point or similar), and that link-local IPv6 is enabled in your operating
|
||||
system, which should be enabled by default in almost all OSes.
|
||||
|
||||
.. code::
|
||||
|
||||
# This example demonstrates a TCP server interface.
|
||||
# It will listen for incoming connections on the
|
||||
# specified IP address and port number.
|
||||
|
||||
[[Default Interface]]
|
||||
type = AutoInterface
|
||||
interface_enabled = True
|
||||
outgoing = True
|
||||
|
||||
# You can create multiple isolated Reticulum
|
||||
# networks on the same physical LAN by
|
||||
# specifying different Group IDs.
|
||||
|
||||
group_id = reticulum
|
||||
|
||||
# You can also select specifically which
|
||||
# kernel networking devices to use.
|
||||
|
||||
devices = wlan0,eth1
|
||||
|
||||
# Or let AutoInterface use all suitable
|
||||
# devices except for a list of ignored ones.
|
||||
|
||||
ignored_devices = tun0,eth0
|
||||
|
||||
|
||||
If you are connected to the Internet with IPv6, and your provider will route
|
||||
IPv6 multicast, you can potentially configure the Auto Interface to globally
|
||||
autodiscover other Reticulum nodes within your selected Group ID. You can specify
|
||||
the discovery scope by setting it to one of ``link``, ``admin``, ``site``,
|
||||
``organisation`` or ``global``.
|
||||
|
||||
.. code::
|
||||
|
||||
[[Default Interface]]
|
||||
type = AutoInterface
|
||||
interface_enabled = True
|
||||
outgoing = True
|
||||
|
||||
# Configure global discovery
|
||||
|
||||
group_id = custom_network_name
|
||||
discovery_scope = global
|
||||
|
||||
# Other configuration options
|
||||
|
||||
discovery_port = 48555
|
||||
data_port = 49555
|
||||
|
||||
*Please Note!* If you use the Auto Interface, you will need the Python module
|
||||
``netifaces`` installed on your system. You can install it with ``pip3 install netifaces``.
|
||||
|
||||
|
||||
.. _interfaces-udp:
|
||||
|
||||
UDP Interface
|
||||
@@ -28,6 +95,12 @@ 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.
|
||||
|
||||
*Please Note!* Using broadcast UDP traffic has performance implications,
|
||||
especially on WiFi. If your goal is simply to enable easy communication
|
||||
with all peers in your local ethernet broadcast domain, the
|
||||
:ref:`Auto Interface<interfaces-auto>` performs better, and is just as
|
||||
easy to use.
|
||||
|
||||
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.
|
||||
@@ -48,9 +121,7 @@ pre-existing LAN.
|
||||
|
||||
# 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.
|
||||
# IP interfaces.
|
||||
|
||||
# Instead of specifying listen_ip, listen_port,
|
||||
# forward_ip and forward_port, you can also bind
|
||||
@@ -78,6 +149,9 @@ pre-existing LAN.
|
||||
# forward_ip = 10.55.0.16
|
||||
# forward_port = 4242
|
||||
|
||||
*Please Note!* If you use the ``device`` option, you will need the Python module
|
||||
``netifaces`` installed on your system. You can install it with ``pip3 install netifaces``.
|
||||
|
||||
.. _interfaces-tcps:
|
||||
|
||||
TCP Server Interface
|
||||
@@ -114,6 +188,8 @@ configured, other Reticulum peers can connect to it with a TCP Client interface.
|
||||
# device = eth0
|
||||
# port = 4242
|
||||
|
||||
*Please Note!* If you use the ``device`` option, you will need the Python module
|
||||
``netifaces`` installed on your system. You can install it with ``pip3 install netifaces``.
|
||||
|
||||
.. _interfaces-tcpc:
|
||||
|
||||
@@ -136,6 +212,30 @@ same TCP Server interface at the same time.
|
||||
target_host = 127.0.0.1
|
||||
target_port = 4242
|
||||
|
||||
It is also possible to use this interface type to connect via other programs
|
||||
or hardware devices that expose a KISS interface on a TCP port, for example
|
||||
software-based soundmodems. To do this, use the ``kiss_framing`` option:
|
||||
|
||||
.. code::
|
||||
|
||||
# Here's an example of a TCP Client interface that connects
|
||||
# to a software TNC soundmodem on a KISS over TCP port.
|
||||
|
||||
[[TCP KISS Interface]]
|
||||
type = TCPClientInterface
|
||||
interface_enabled = True
|
||||
outgoing = True
|
||||
kiss_framing = True
|
||||
target_host = 127.0.0.1
|
||||
target_port = 8001
|
||||
|
||||
**Caution!** Only use the KISS framing option when connecting to external devices
|
||||
and programs like soundmodems and similar over TCP. When using the
|
||||
``TCPClientInterface`` in conjunction with the ``TCPServerInterface`` you should
|
||||
never enable ``kiss_framing``, since this will disable internal reliability and
|
||||
recovery mechanisms that greatly improves performance over unreliable and
|
||||
intermittent TCP links.
|
||||
|
||||
|
||||
.. _interfaces-rnode:
|
||||
|
||||
|
||||
@@ -67,9 +67,12 @@ guide the design of Reticulum:
|
||||
it can be easily replicated.
|
||||
* **Very low bandwidth requirements**
|
||||
Reticulum should be able to function reliably over links with a transmission capacity as low
|
||||
as *1,000 bps*.
|
||||
as *500 bps*.
|
||||
* **Encryption by default**
|
||||
Reticulum must use encryption by default where possible and applicable.
|
||||
Reticulum must use strong encryption by default for all communication.
|
||||
* **Initiator Anonymity**
|
||||
It must be possible to communicate over a Reticulum network without revealing any identifying
|
||||
information about oneself.
|
||||
* **Unlicensed use**
|
||||
Reticulum shall be functional over physical communication mediums that do not require any
|
||||
form of license to use. Reticulum must be designed in a way, so it is usable over ISM radio
|
||||
@@ -99,7 +102,7 @@ Introduction & Basic Functionality
|
||||
Reticulum is a networking stack suited for high-latency, low-bandwidth links. Reticulum is at it’s
|
||||
core a *message oriented* system. It is suited for both local point-to-point or point-to-multipoint
|
||||
scenarios where alle nodes are within range of each other, as well as scenarios where packets need
|
||||
to be transported over multiple hops to reach the recipient.
|
||||
to be transported over multiple hops in a complex network to reach the recipient.
|
||||
|
||||
Reticulum does away with the idea of addresses and ports known from IP, TCP and UDP. Instead
|
||||
Reticulum uses the singular concept of *destinations*. Any application using Reticulum as it’s
|
||||
@@ -110,9 +113,9 @@ All destinations in Reticulum are represented internally as 10 bytes, derived fr
|
||||
SHA-256 hash of identifying characteristics of the destination. To users, the destination addresses
|
||||
will be displayed as 10 bytes in hexadecimal representation, as in the following example: ``<80e29bf7cccaf31431b3>``.
|
||||
|
||||
By default Reticulum encrypts all data using public-key cryptography. Any message sent to a
|
||||
destination is encrypted with that destinations public key. Reticulum can also set up an encrypted
|
||||
channel to a destination with *Perfect Forward Secrecy* and *Initiator Anonymity* using a elliptic
|
||||
By default Reticulum encrypts all data using elliptic curve cryptography. Any packet sent to a
|
||||
destination is encrypted with a derived ephemeral key. Reticulum can also set up an encrypted
|
||||
channel to a destination with *Forward Secrecy* and *Initiator Anonymity* using a elliptic
|
||||
curve cryptography and ephemeral keys derived from a Diffie Hellman exchange on Curve25519. In
|
||||
Reticulum terminology, this is called a *Link*.
|
||||
|
||||
@@ -135,17 +138,17 @@ destinations. Reticulum uses three different basic destination types, and one sp
|
||||
|
||||
|
||||
* **Single**
|
||||
The *single* destination type defines a public-key encrypted destination. Any data sent to this
|
||||
destination will be encrypted with the destination’s public key, and will only be readable by
|
||||
the creator of the destination.
|
||||
The *single* destination type is always identified by a unique public key. Any data sent to this
|
||||
destination will be encrypted using ephemeral keys derived from an ECDH key exchange, and will
|
||||
only be readable by the creator of the destination, who holds the corresponding private key.
|
||||
* **Group**
|
||||
The *group* destination type defines a symmetrically encrypted destination. Data sent to this
|
||||
destination will be encrypted with a symmetric key, and will be readable by anyone in
|
||||
possession of the key. The *group* destination can be used just as well by only two peers, as it
|
||||
can by many.
|
||||
possession of the key.
|
||||
* **Plain**
|
||||
A *plain* destination type is unencrypted, and suited for traffic that should be broadcast to a
|
||||
number of users, or should be readable by anyone. Traffic to a *plain* destination is not encrypted.
|
||||
Generally, *plain* destinations can be used for broadcast information intended to be public.
|
||||
* **Link**
|
||||
A *link* is a special destination type, that serves as an abstract channel to a *single*
|
||||
destination, directly connected or over multiple hops. The *link* also offers reliability and
|
||||
@@ -507,7 +510,7 @@ the transfer is needed.
|
||||
This is the purpose of the Reticulum :ref:`Resource<api-resource>`. A *Resource* can automatically
|
||||
handle the reliable transfer of an arbitrary amount of data over an established :ref:`Link<api-link>`.
|
||||
Resources can auto-compress data, will handle breaking the data into individual packets, sequencing
|
||||
the transfer and reassembling the data on the other end.
|
||||
the transfer, integrity verification and reassembling the data on the other end.
|
||||
|
||||
:ref:`Resources<api-resource>` are programmatically very simple to use, and only requires a few lines
|
||||
of codes to reliably transfer any amount of data. They can be used to transfer data stored in memory,
|
||||
@@ -581,6 +584,7 @@ Node Types
|
||||
|
||||
Currently Reticulum defines two node types, the *Station* and the *Peer*. A node is a *station* if it fixed
|
||||
in one place, and if it is intended to be kept online most of the time. Otherwise the node is a *peer*.
|
||||
|
||||
This distinction is made by the user configuring the node, and is used to determine what nodes on the
|
||||
network will help forward traffic, and what nodes rely on other nodes for connectivity.
|
||||
|
||||
@@ -596,10 +600,6 @@ Currently, Reticulum is completely priority-agnostic regarding general traffic.
|
||||
on a first-come, first-serve basis. Announce re-transmission are handled according to the re-transmission
|
||||
times and priorities described earlier in this chapter.
|
||||
|
||||
It is possible that a prioritisation engine could be added to Reticulum in the future, but in
|
||||
the light of Reticulums goal of equal access, doing so would need to be the subject of careful
|
||||
investigation of the consequences first.
|
||||
|
||||
|
||||
.. _understanding-packetformat:
|
||||
|
||||
@@ -702,4 +702,4 @@ Binary Packet Format
|
||||
- Link Request : 77 bytes
|
||||
- Link Proof : 77 bytes
|
||||
- Link RTT packet : 83 bytes
|
||||
- Link keepalive : 14 bytes
|
||||
- Link keepalive : 14 bytes
|
||||
|
||||
+99
-1
@@ -57,6 +57,7 @@ the same system.
|
||||
-q, --quiet
|
||||
--version show program's version number and exit
|
||||
|
||||
You can easily add ``rnsd`` as an always-on service by :ref:`configuring a service<using-systemd>`.
|
||||
|
||||
The rnstatus Utility
|
||||
====================
|
||||
@@ -162,4 +163,101 @@ destinations will not have this option enabled, and will not be probable.
|
||||
-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
|
||||
-v, --verbose
|
||||
|
||||
|
||||
Improving System Configuration
|
||||
------------------------------
|
||||
|
||||
If you are setting up a system for permanent use with Reticulum, there is a
|
||||
few system configuration changes that can make this easier to administrate.
|
||||
These changes will be detailed here.
|
||||
|
||||
|
||||
Fixed Serial Port Names
|
||||
=======================
|
||||
|
||||
On a Reticulum node with several serial port based interfaces, it can be
|
||||
beneficial to use the fixed name device nodes for the serial ports, instead
|
||||
of the dynamically allocated shorthands such as ``/dev/ttyUSB0``. Under most
|
||||
Debian-based distributions, including Ubuntu and Raspberry Pi OS, these nodes
|
||||
can be found under ``/dev/serial/by-id``.
|
||||
|
||||
You can use such a device path directly in place of the numbered shorthands.
|
||||
Here is an example of a packet radio TNC configured as such:
|
||||
|
||||
.. code:: text
|
||||
|
||||
[[Packet Radio KISS Interface]]
|
||||
type = KISSInterface
|
||||
interface_enabled = True
|
||||
outgoing = true
|
||||
port = /dev/serial/by-id/usb-FTDI_FT230X_Basic_UART_43891CKM-if00-port0
|
||||
speed = 115200
|
||||
databits = 8
|
||||
parity = none
|
||||
stopbits = 1
|
||||
preamble = 150
|
||||
txtail = 10
|
||||
persistence = 200
|
||||
slottime = 20
|
||||
|
||||
Using this methodology avoids potential naming mix-ups where physical devices
|
||||
might be plugged and unplugged in different orders, or when node name
|
||||
assignment varies from one boot to another.
|
||||
|
||||
.. _using-systemd:
|
||||
|
||||
Reticulum as a System Service
|
||||
=============================
|
||||
|
||||
Instead of starting Reticulum manually, you can install ``rnsd`` as a system
|
||||
service and have it start automatically at boot.
|
||||
|
||||
If you installed Reticulum with ``pip``, the ``rnsd`` program will most likely
|
||||
be located in a user-local installation path only, which means ``systemd`` will not
|
||||
be able to execute it. In this case, you can simply symlink the ``rnsd`` program
|
||||
into a directory that is in systemd's path:
|
||||
|
||||
.. code:: text
|
||||
|
||||
sudo ln -s $(which rnsd) /usr/local/bin/
|
||||
|
||||
You can then create the service file ``/etc/systemd/system/rnsd.service`` with the
|
||||
following content:
|
||||
|
||||
.. code:: text
|
||||
|
||||
[Unit]
|
||||
Description=Reticulum Network Stack Daemon
|
||||
After=multi-user.target
|
||||
|
||||
[Service]
|
||||
# If you run Reticulum on WiFi devices,
|
||||
# or other devices that need some extra
|
||||
# time to initialise, you might want to
|
||||
# add a short delay before Reticulum is
|
||||
# started by systemd:
|
||||
# ExecStartPre=/bin/sleep 10
|
||||
Type=simple
|
||||
Restart=always
|
||||
RestartSec=3
|
||||
User=USERNAMEHERE
|
||||
ExecStart=rnsd --service
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
||||
Be sure to replace ``USERNAMEHERE`` with the user you want to run ``rnsd`` as.
|
||||
|
||||
To manually start ``rnsd`` run:
|
||||
|
||||
.. code:: text
|
||||
|
||||
sudo systemctl start rnsd
|
||||
|
||||
If you want to automatically start ``rnsd`` at boot, run:
|
||||
|
||||
.. code:: text
|
||||
|
||||
sudo systemctl enable rnsd
|
||||
@@ -2,7 +2,7 @@
|
||||
What is Reticulum?
|
||||
******************
|
||||
|
||||
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 is a cryptography-based networking stack for wide-area networks built on readily available hardware, that 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.
|
||||
|
||||
@@ -16,11 +16,6 @@ Current Status
|
||||
Reticulum should currently be considered beta software. All core protocol features are implemented and functioning, but additions will probably occur as real-world use is explored. There will be bugs. The API and wire-format can be considered relatively stable at the moment, but could change if warranted.
|
||||
|
||||
|
||||
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 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?
|
||||
==========================
|
||||
* Coordination-less globally unique adressing and identification
|
||||
@@ -67,7 +62,7 @@ What does Reticulum Offer?
|
||||
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,
|
||||
with 500 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.
|
||||
@@ -105,4 +100,9 @@ Reticulum implements a range of generalised interface types that covers most of
|
||||
|
||||
* UDP over IP networks
|
||||
|
||||
For a full list and more details, see the :ref:`Supported Interfaces<interfaces-main>` chapter.
|
||||
For a full list and more details, see the :ref:`Supported Interfaces<interfaces-main>` chapter.
|
||||
|
||||
|
||||
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 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.
|
||||
|
||||
Reference in New Issue
Block a user