Compare commits

...

126 Commits

Author SHA1 Message Date
Mark Qvist 95d3346da6 Fixed I2P interface missing attribute 2022-02-26 21:37:50 +01:00
Mark Qvist d4aabc8b89 Added I2P base32 address output to rnstatus utility 2022-02-26 21:04:54 +01:00
Mark Qvist d487609dcf Updated docs and manual 2022-02-26 20:46:14 +01:00
Mark Qvist c96c82f1d1 Updated manual 2022-02-26 19:35:37 +01:00
Mark Qvist cb023cde40 Fixed potential race condition in resource assembly 2022-02-26 18:27:11 +01:00
Mark Qvist 17be289f37 Updated documentation and manual 2022-02-25 22:34:41 +01:00
Mark Qvist b8105e23ff Fixed TCP interface mode reference 2022-02-25 22:10:55 +01:00
Mark Qvist f378d09cbe Updated documentation and manual 2022-02-25 21:50:03 +01:00
Mark Qvist 4dfa62833c Restructured default config 2022-02-25 21:48:25 +01:00
Mark Qvist 2ec6d3ba6c Updated I2P interface documentation 2022-02-25 21:41:43 +01:00
Mark Qvist 15d027e11e Restructured default config and added config example to rnsd 2022-02-25 21:41:24 +01:00
Mark Qvist 87a274d177 Added I2P interface documentation 2022-02-25 21:26:34 +01:00
Mark Qvist f8272793b4 Tuned AutoInterface timeouts 2022-02-25 20:29:47 +01:00
Mark Qvist 3a215be859 Interface mode defaults 2022-02-25 18:56:09 +01:00
Mark Qvist 0e1279d012 Added Access Point interface mode 2022-02-25 18:47:55 +01:00
Mark Qvist 8ec356a28e Interface outbound option enabled by default 2022-02-25 13:31:52 +01:00
Mark Qvist 49d7808835 i2plib license 2022-02-24 01:45:42 +01:00
Mark Qvist 48184134e4 Improved I2P Interface 2022-02-24 01:30:10 +01:00
Mark Qvist 987ff0658b Version bump 2022-02-23 22:53:16 +01:00
Mark Qvist 27dea7c524 Implemented I2PInterface 2022-02-23 22:43:08 +01:00
Mark Qvist 9c6fd132d4 Work on I2P Interface 2022-02-23 22:15:06 +01:00
Mark Qvist 8d58bb62ab Work on I2P Interface 2022-02-23 21:47:30 +01:00
Mark Qvist c357f7a94e Work on I2P Interface 2022-02-23 21:39:29 +01:00
Mark Qvist 4b3ead3db2 Work on I2P Interface 2022-02-23 21:29:18 +01:00
Mark Qvist b62e9af5d4 Work on I2P Interface 2022-02-23 21:19:43 +01:00
Mark Qvist fa82989a2e Preliminary I2P Interface support 2022-02-23 17:40:31 +01:00
Mark Qvist 07a65609b4 Updated documentation 2022-02-22 21:39:16 +01:00
Mark Qvist 257bd95da8 AutoInterface carrier loss detection 2022-02-22 20:16:02 +01:00
Mark Qvist 1ccfa9079c Work on AutoInterface recovery on WiFi carrier loss 2022-02-22 14:49:43 +01:00
Mark Qvist 57226201ff Fixed I2P tunneled config keyword 2022-02-22 14:45:36 +01:00
Mark Qvist d9419cd895 Merge branch 'master' of github.com:markqvist/Reticulum 2022-02-22 14:43:19 +01:00
Mark Qvist aae10ede72 Work on AutoInterface recovery on WiFi carrier loss 2022-02-22 14:43:14 +01:00
Mark Qvist 291b3056cd Updated docs 2022-02-01 23:07:18 +01:00
Mark Qvist 3f53c89d32 Added I2P-tunneled mode to TCP interfaces 2022-01-31 23:31:29 +01:00
Mark Qvist 05288d7c97 Updated documentation for release 2022-01-27 23:49:24 +01:00
Mark Qvist b403441074 Version updated 2022-01-27 23:30:44 +01:00
Mark Qvist d3a23e3b00 RNodeInterface firmware version check 2022-01-22 22:46:47 +01:00
markqvist 329d83587e Merge pull request #10 from 4c3e/4c3e-osx-nameerror-fix
OSX NameError fix
2022-01-22 21:39:01 +01:00
Mark Qvist 0a4dd64434 Improved support for ESP32-based RNodes 2022-01-22 21:36:49 +01:00
4c3e b96cbf1014 OSX NameError fix
Had the following error when trying to run Reticulum on OSX High Sierra:
line 115, in set_timeouts_osx
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
NameError: name 'sock' is not defined
This fix resolved the problem for me.
2022-01-21 01:34:55 +00:00
Mark Qvist 485558cd6b Updated documentation and manual 2022-01-14 22:21:13 +01:00
Mark Qvist 8d93867a22 Updated manual 2022-01-14 22:16:02 +01:00
markqvist 6b20a98adc Update README.md 2022-01-12 12:53:18 +01:00
Mark Qvist f3d04ba90f Improved AutoInterface handling on Android 2022-01-12 12:12:04 +01:00
Mark Qvist 1d2564cedb Interface import on Android 2022-01-12 12:02:00 +01:00
Mark Qvist bec8473695 Better Android detection 2022-01-12 11:50:03 +01:00
Mark Qvist 25620415a0 Updated platform utils 2022-01-12 11:18:24 +01:00
Mark Qvist b6df952995 Platform version check for Windows 2022-01-12 10:16:59 +01:00
Mark Qvist a72aaf12ca Platform version check for Windows 2022-01-12 10:07:44 +01:00
Mark Qvist b978a993b2 Version update 2022-01-11 03:07:34 +01:00
Mark Qvist 5ae00264e8 Preliminaly ESP32 support for RNodeInterface 2022-01-11 03:07:03 +01:00
Mark Qvist 5396b80e80 Updated example 2022-01-11 03:06:35 +01:00
Mark Qvist fdaa58a6fa Improved malformed packet detection 2022-01-11 03:06:16 +01:00
Mark Qvist 4253175627 Cleanup 2021-12-11 20:10:31 +01:00
Mark Qvist 81158c27e4 Cleanup 2021-12-11 18:41:28 +01:00
Mark Qvist eeb424ecee Link request debug 2021-12-11 18:33:09 +01:00
Mark Qvist 0273328b23 Link proof debug 2021-12-11 18:19:51 +01:00
Mark Qvist 20dfbcf0cc Link activation time 2021-12-11 17:26:45 +01:00
Mark Qvist c96e067839 Added proper requester interface detection for path requests for destinations behind local clients. 2021-12-11 16:50:03 +01:00
Mark Qvist 9ff37543f3 Adjusted request timeout 2021-12-11 16:42:57 +01:00
Mark Qvist 974ca48cb4 Adjusted peering timing 2021-12-11 16:42:15 +01:00
Mark Qvist 167d48c8ce Updated peering timeouts 2021-12-11 15:41:34 +01:00
Mark Qvist f253b08774 Updated documentation and manual 2021-12-10 20:10:11 +01:00
Mark Qvist 1c768e9219 Removed log statement 2021-12-10 18:55:17 +01:00
Mark Qvist df39cff520 Added recovery to local shared interfaces if master RNS instance is restarted 2021-12-10 18:32:24 +01:00
Mark Qvist e1e31692d7 UDP socket contructor and doc update 2021-12-10 16:23:35 +01:00
Mark Qvist 293a834c35 Log output and cleanup 2021-12-10 14:48:30 +01:00
Mark Qvist 1bbdd9b3f5 Ignore interfaces on Darwin 2021-12-10 11:10:09 +01:00
Mark Qvist d4b6b6ee59 Ignore interfaces on Darwin 2021-12-10 11:00:48 +01:00
Mark Qvist fca03bbdce Ignore AWDL interfaces on Darwin 2021-12-10 10:58:28 +01:00
Mark Qvist 29aa4f9315 Updated AutoInterface IPv6 bind address 2021-12-10 10:35:25 +01:00
Mark Qvist d5cac30a85 Log cleanup 2021-12-10 09:48:57 +01:00
Mark Qvist 6500bc7390 Updated docs 2021-12-09 18:53:28 +01:00
Mark Qvist 81fed10855 Updated readme 2021-12-09 18:35:38 +01:00
Mark Qvist a39876106b Updated readme 2021-12-09 18:35:01 +01:00
Mark Qvist 90b39774d1 Updated readme 2021-12-09 18:34:07 +01:00
Mark Qvist 006c70cd09 Updated documentation 2021-12-09 18:12:18 +01:00
Mark Qvist 02945f960d Updated timing 2021-12-09 17:44:19 +01:00
Mark Qvist e401ec870d Updated readme 2021-12-09 17:04:20 +01:00
Mark Qvist 90174fcc28 Cleanup 2021-12-09 17:02:13 +01:00
Mark Qvist c18ebed419 Added auto interface 2021-12-09 16:07:36 +01:00
Mark Qvist 1d180a96f6 Updated dependencies 2021-12-08 20:47:14 +01:00
Mark Qvist 4241990690 Implemented AutoInterface outbound traffic and multicast discovery listeners 2021-12-08 20:46:53 +01:00
Mark Qvist 3d49076602 Compatibility with IPv6 based interfaces 2021-12-08 20:45:41 +01:00
Mark Qvist 2e0dd278b6 Updated announce example 2021-12-08 20:45:03 +01:00
Mark Qvist b432a7c7de Updated documentation 2021-12-08 20:42:48 +01:00
Mark Qvist c0383fa2b0 Updated docs 2021-12-06 19:38:03 +01:00
Mark Qvist 98d66e2ba5 Updated documentation 2021-12-06 14:10:22 +01:00
Mark Qvist 2e4fcc659c Added KISS framing option to TCP client interface 2021-12-06 13:07:12 +01:00
Mark Qvist 8fe7c19c59 Updated documentation 2021-12-05 23:31:01 +01:00
Mark Qvist 27b46c9e89 Updated documentation 2021-12-05 23:28:15 +01:00
Mark Qvist 70a3637a98 Updated documentation 2021-12-05 23:26:52 +01:00
Mark Qvist 2e0476e6b9 Updated documentation 2021-12-05 23:24:30 +01:00
Mark Qvist 39911190aa Updated documentation 2021-12-05 16:07:53 +01:00
Mark Qvist 9e9606b8cf Systemd service support and documentation update 2021-12-05 16:05:43 +01:00
Mark Qvist 8be1acee0a Added auto reconnection for disconnected serial-based devices 2021-12-05 14:35:25 +01:00
Mark Qvist ba39a69175 Timeout default structure updated 2021-12-05 11:45:13 +01:00
Mark Qvist a692d29c90 Reconnect on serial port errors for KISS interface 2021-12-05 11:44:30 +01:00
Mark Qvist 7092589388 Updated documentation 2021-12-02 18:33:00 +01:00
Mark Qvist 2d3969aa3d Added makefile 2021-12-01 19:23:19 +01:00
Mark Qvist 1443f4c104 Updated umsgpack to 2.7.1 2021-12-01 19:20:24 +01:00
Mark Qvist d2232f19ba Removed pyserial dependency 2021-12-01 14:05:33 +01:00
Mark Qvist c44c6f9086 Conditional imports for serial-based interfaces 2021-12-01 13:57:40 +01:00
Mark Qvist 259c2aa397 Conditional imports for serial-based interfaces 2021-12-01 13:39:51 +01:00
Mark Qvist 10854bfdbc Added conditional import of netifaces 2021-12-01 11:46:19 +01:00
Mark Qvist f5236878b0 Added Android platform detection 2021-12-01 11:40:44 +01:00
Mark Qvist daf72f4237 Version updated 2021-12-01 11:40:31 +01:00
Mark Qvist 652b884d72 Added conditional import of netifaces 2021-12-01 11:39:40 +01:00
Mark Qvist ea3716f48e Added Android platform detection 2021-12-01 11:39:06 +01:00
Mark Qvist 165e620043 Improved shutdown handling on interrupt. Updated gitignore. 2021-11-04 17:15:58 +01:00
Mark Qvist 58f43b163e Updated docs 2021-10-15 19:26:53 +02:00
Mark Qvist 448ea8ceb5 Added try statements for various callbacks 2021-10-15 14:36:50 +02:00
Mark Qvist f7e8fc4719 Updated docs 2021-10-14 21:06:16 +02:00
Mark Qvist 1d6c877b4c Added RSSI and SNR to echo example 2021-10-12 18:31:46 +02:00
Mark Qvist c3dcd9366d Added RSSI and SNR to echo example 2021-10-12 18:09:02 +02:00
Mark Qvist 8d01586a5a Added RSSI and SNR to echo example 2021-10-12 18:04:55 +02:00
Mark Qvist 3e5f613f66 Fixed typo 2021-10-12 16:36:29 +02:00
Mark Qvist 614a139cd4 Merge branch 'master' of github.com:markqvist/Reticulum 2021-10-12 16:34:25 +02:00
Mark Qvist 1cf6570c2d Added RSSI and SNR reporting to packets on supported interfaces 2021-10-12 16:34:17 +02:00
Mark Qvist d207cbcd9c Update README.md 2021-10-11 15:26:21 +02:00
Mark Qvist 18b20f2d8d Update README.md 2021-10-11 15:26:07 +02:00
Mark Qvist c37533d2c7 Updated docs 2021-10-10 00:27:04 +02:00
Mark Qvist fd13e20165 Updated version 2021-10-09 23:23:44 +02:00
Mark Qvist 66ce58f0f4 Implemented path updating for moving nodes 2021-10-09 22:13:27 +02:00
Mark Qvist e8ee26f78d Emission timestamp in announce. 2021-10-09 21:36:01 +02:00
Mark Qvist c0fb419fe1 Fixed Resource string representation. Added emission timestamp in announce. 2021-10-09 21:30:34 +02:00
66 changed files with 4997 additions and 1063 deletions
+1
View File
@@ -3,6 +3,7 @@
testutils
TODO
Examples/RNS
RNS/Utilities/RNS
build
dist
docs/build
+1 -1
View File
@@ -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
View File
@@ -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.
+6
View File
@@ -1,6 +1,12 @@
##########################################################
# This RNS example demonstrates a simple speedtest #
# program to measure link throughput. #
# #
# The current configuration is suited for testing fast #
# links. If you want to measure slow links like LoRa or #
# packet radio, you must significantly lower the #
# data_cap variable, which defines how much data is sent #
# for each test. #
##########################################################
import os
+25
View File
@@ -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/*
+30 -17
View File
@@ -9,6 +9,7 @@ Having no dependencies on traditional networking stacks free up overhead that ha
No kernel modules or drivers are required. Reticulum runs completely in userland, and can run on practically any system that runs Python 3.
## Read The Manual
The full documentation for Reticulum is available at [markqvist.github.io/Reticulum/manual/](https://markqvist.github.io/Reticulum/manual/).
You can also [download the Reticulum manual as a PDF](https://github.com/markqvist/Reticulum/raw/master/docs/Reticulum%20Manual.pdf)
@@ -45,7 +46,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 +54,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.
@@ -69,36 +86,32 @@ Reticulum implements a range of generalised interface types that covers most of
## Feature Roadmap
- Stream mode for links
## Planned Features
- More interface types for even broader compatibility
- ESP32 devices (ESP-Now, Bluetooth, etc.)
- More LoRa transceivers
- AT-compatible modems
- AWDL / OWL
- HF Modems
- CAN-bus
- ZeroMQ
- MQTT
- SPI
- i²c
- Globally routable multicast
## 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.
+7 -2
View File
@@ -1,5 +1,6 @@
import base64
import math
import time
import RNS
from cryptography.fernet import Fernet
@@ -146,7 +147,7 @@ class Destination:
:param path_response: Internal flag used by :ref:`RNS.Transport<api-transport>`. Ignore.
"""
destination_hash = self.hash
random_hash = RNS.Identity.get_random_hash()
random_hash = RNS.Identity.get_random_hash()[0:5]+int(time.time()).to_bytes(5, "big")
if app_data == None and self.default_app_data != None:
if isinstance(self.default_app_data, bytes):
@@ -262,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)
+64 -35
View File
@@ -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+"]"
+323
View File
@@ -0,0 +1,323 @@
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 = 7.5
DARWIN_IGNORE_IFS = ["awdl0", "llw0", "lo0", "en5"]
ANDROID_IGNORE_IFS = ["dummy0", "lo", "tun0"]
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.multicast_echoes = {}
self.timed_out_interfaces = {}
self.outbound_udp_socket = None
self.announce_interval = AutoInterface.PEERING_TIMEOUT/5.0
self.peer_job_interval = AutoInterface.PEERING_TIMEOUT*1.1
self.peering_timeout = AutoInterface.PEERING_TIMEOUT
self.multicast_echo_timeout = AutoInterface.PEERING_TIMEOUT/2
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.is_darwin() and ifname in AutoInterface.DARWIN_IGNORE_IFS and not ifname in self.allowed_interfaces:
RNS.log(str(self)+" skipping Darwin AWDL or tethering interface "+str(ifname), RNS.LOG_EXTREME)
elif RNS.vendor.platformutils.is_darwin() and ifname == "lo0":
RNS.log(str(self)+" skipping Darwin loopback interface "+str(ifname), RNS.LOG_EXTREME)
elif RNS.vendor.platformutils.is_android() and ifname in AutoInterface.ANDROID_IGNORE_IFS and not ifname in self.allowed_interfaces:
RNS.log(str(self)+" skipping Android system 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]
self.multicast_echoes[ifname] = time.time()
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 = []
# Check for 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)
# Remove any timed out peers
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)
for ifname in self.adopted_interfaces:
last_multicast_echo = 0
if ifname in self.multicast_echoes:
last_multicast_echo = self.multicast_echoes[ifname]
if now - last_multicast_echo > self.multicast_echo_timeout:
if ifname in self.timed_out_interfaces and self.timed_out_interfaces[ifname] == False:
RNS.log("Multicast echo timeout for "+str(ifname)+". Carrier lost.", RNS.LOG_WARNING)
self.timed_out_interfaces[ifname] = True
else:
if ifname in self.timed_out_interfaces and self.timed_out_interfaces[ifname] == True:
RNS.log(str(self)+" Carrier recovered on "+str(ifname), RNS.LOG_WARNING)
self.timed_out_interfaces[ifname] = False
def announce_handler(self, ifname):
while True:
self.peer_announce(ifname)
time.sleep(self.announce_interval)
def peer_announce(self, ifname):
try:
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])
except Exception as e:
if (ifname in self.timed_out_interfaces and self.timed_out_interfaces[ifname] == False) or not ifname in self.timed_out_interfaces:
RNS.log(str(self)+" Detected possible carrier loss on "+str(ifname)+": "+str(e), RNS.LOG_WARNING)
else:
pass
def add_peer(self, addr, ifname):
if addr in self.link_local_addresses:
ifname = None
for interface_name in self.adopted_interfaces:
if self.adopted_interfaces[interface_name] == addr:
ifname = interface_name
if ifname != None:
self.multicast_echoes[ifname] = time.time()
else:
RNS.log(str(self)+" received multicast echo on unexpected interface "+str(ifname), RNS.LOG_WARNING)
else:
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)
+593
View File
@@ -0,0 +1,593 @@
from .Interface import Interface
import socketserver
import threading
import platform
import socket
import time
import sys
import os
import RNS
import asyncio
class HDLC():
FLAG = 0x7E
ESC = 0x7D
ESC_MASK = 0x20
@staticmethod
def escape(data):
data = data.replace(bytes([HDLC.ESC]), bytes([HDLC.ESC, HDLC.ESC^HDLC.ESC_MASK]))
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
# TODO: Neater shutdown of the event loop and
# better error handling is needed. Sometimes
# errors occur in I2P that leave tunnel setup
# hanging indefinitely, and right now we have
# no way of catching it. Sometimes the server
# and client tasks are also not cancelled on
# shutdown, which leads to errors dumped to
# the console. This should also be remedied.
class I2PController:
def __init__(self, rns_storagepath):
import RNS.vendor.i2plib as i2plib
import RNS.vendor.i2plib.utils
self.client_tunnels = {}
self.server_tunnels = {}
self.loop = None
self.i2plib = i2plib
self.utils = i2plib.utils
self.sam_address = i2plib.get_sam_address()
self.storagepath = rns_storagepath+"/i2p"
if not os.path.isdir(self.storagepath):
os.makedirs(self.storagepath)
def start(self):
asyncio.set_event_loop(asyncio.new_event_loop())
self.loop = asyncio.get_event_loop()
try:
self.loop.run_forever()
except Exception as e:
RNS.log("Exception on event loop for "+str(self)+": "+str(e), RNS.LOG_ERROR)
finally:
self.loop.close()
def stop(self):
for task in asyncio.Task.all_tasks(loop=self.loop):
task.cancel()
self.loop.stop()
def get_free_port(self):
return self.i2plib.utils.get_free_port()
def client_tunnel(self, owner, i2p_destination):
self.client_tunnels[i2p_destination] = False
while True:
if not self.client_tunnels[i2p_destination]:
try:
async def tunnel_up():
RNS.log("Bringing up I2P tunnel to "+str(owner)+", this may take a while...", RNS.LOG_INFO)
tunnel = self.i2plib.ClientTunnel(i2p_destination, owner.local_addr, sam_address=self.sam_address, loop=self.loop)
await tunnel.run()
owner.awaiting_i2p_tunnel = False
RNS.log(str(owner)+ " tunnel setup complete", RNS.LOG_VERBOSE)
try:
self.loop.ext_owner = self
future = asyncio.run_coroutine_threadsafe(tunnel_up(), self.loop).result()
self.client_tunnels[i2p_destination] = True
except Exception as e:
RNS.log("Error while setting up I2P tunnel: "+str(e))
raise e
except Exception as e:
raise IOError("Could not connect to I2P SAM API while configuring to "+str(owner)+". Check that I2P is running and SAM is enabled.")
time.sleep(5)
def server_tunnel(self, owner):
i2p_dest_hash = RNS.Identity.full_hash(RNS.Identity.full_hash(owner.name.encode("utf-8")))
i2p_keyfile = self.storagepath+"/"+RNS.hexrep(i2p_dest_hash, delimit=False)+".i2p"
i2p_dest = None
if not os.path.isfile(i2p_keyfile):
coro = self.i2plib.new_destination(sam_address=self.sam_address, loop=self.loop)
i2p_dest = asyncio.run_coroutine_threadsafe(coro, self.loop).result()
key_file = open(i2p_keyfile, "w")
key_file.write(i2p_dest.private_key.base64)
key_file.close()
else:
key_file = open(i2p_keyfile, "r")
prvd = key_file.read()
key_file.close()
i2p_dest = self.i2plib.Destination(data=prvd, has_private_key=True)
i2p_b32 = i2p_dest.base32
owner.b32 = i2p_b32
self.server_tunnels[i2p_b32] = False
while self.server_tunnels[i2p_b32] == False:
try:
async def tunnel_up():
RNS.log(str(owner)+" Bringing up I2P endpoint, this may take a while...", RNS.LOG_INFO)
tunnel = self.i2plib.ServerTunnel((owner.bind_ip, owner.bind_port), loop=self.loop, destination=i2p_dest, sam_address=self.sam_address)
await tunnel.run()
RNS.log(str(owner)+ " endpoint setup complete. Now reachable at: "+str(i2p_dest.base32)+".b32.i2p", RNS.LOG_VERBOSE)
asyncio.run_coroutine_threadsafe(tunnel_up(), self.loop).result()
self.server_tunnels[i2p_b32] = True
except Exception as e:
raise IOError("Could not connect to I2P SAM API while configuring "+str(self)+". Check that I2P is running and SAM is enabled.")
time.sleep(5)
def get_loop(self):
return asyncio.get_event_loop()
class ThreadingI2PServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
pass
class I2PInterfacePeer(Interface):
RECONNECT_WAIT = 15
RECONNECT_MAX_TRIES = None
# TCP socket options
I2P_USER_TIMEOUT = 40
I2P_PROBE_AFTER = 10
I2P_PROBE_INTERVAL = 5
I2P_PROBES = 6
def __init__(self, parent_interface, owner, name, target_i2p_dest=None, connected_socket=None, max_reconnect_tries=None):
self.rxb = 0
self.txb = 0
self.IN = True
self.OUT = False
self.socket = None
self.parent_interface = parent_interface
self.parent_count = True
self.name = name
self.initiator = False
self.reconnecting = False
self.never_connected = True
self.owner = owner
self.writing = False
self.online = False
self.detached = False
self.kiss_framing = False
self.i2p_tunneled = True
self.i2p_dest = None
self.i2p_tunnel_ready = False
self.mode = RNS.Interfaces.Interface.Interface.MODE_FULL
if max_reconnect_tries == None:
self.max_reconnect_tries = I2PInterfacePeer.RECONNECT_MAX_TRIES
else:
self.max_reconnect_tries = max_reconnect_tries
if connected_socket != None:
self.receives = True
self.target_ip = None
self.target_port = None
self.socket = connected_socket
if platform.system() == "Linux":
self.set_timeouts_linux()
elif platform.system() == "Darwin":
self.set_timeouts_osx()
elif target_i2p_dest != None:
self.receives = True
self.initiator = True
self.bind_ip = "127.0.0.1"
self.bind_port = self.parent_interface.i2p.get_free_port()
self.local_addr = (self.bind_ip, self.bind_port)
self.target_ip = self.bind_ip
self.target_port = self.bind_port
self.awaiting_i2p_tunnel = True
def tunnel_job():
self.parent_interface.i2p.client_tunnel(self, target_i2p_dest)
thread = threading.Thread(target=tunnel_job)
thread.setDaemon(True)
thread.start()
def wait_job():
while self.awaiting_i2p_tunnel:
time.sleep(0.25)
if not self.kiss_framing:
self.wants_tunnel = True
if not self.connect(initial=True):
thread = threading.Thread(target=self.reconnect)
thread.setDaemon(True)
thread.start()
else:
thread = threading.Thread(target=self.read_loop)
thread.setDaemon(True)
thread.start()
thread = threading.Thread(target=wait_job)
thread.setDaemon(True)
thread.start()
def set_timeouts_linux(self):
if not self.i2p_tunneled:
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_USER_TIMEOUT, int(I2PInterfacePeer.TCP_USER_TIMEOUT * 1000))
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, int(I2PInterfacePeer.TCP_PROBE_AFTER))
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, int(I2PInterfacePeer.TCP_PROBE_INTERVAL))
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, int(I2PInterfacePeer.TCP_PROBES))
else:
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_USER_TIMEOUT, int(I2PInterfacePeer.I2P_USER_TIMEOUT * 1000))
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, int(I2PInterfacePeer.I2P_PROBE_AFTER))
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, int(I2PInterfacePeer.I2P_PROBE_INTERVAL))
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, int(I2PInterfacePeer.I2P_PROBES))
def set_timeouts_osx(self):
if hasattr(socket, "TCP_KEEPALIVE"):
TCP_KEEPIDLE = socket.TCP_KEEPALIVE
else:
TCP_KEEPIDLE = 0x10
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
if not self.i2p_tunneled:
self.socket.setsockopt(socket.IPPROTO_TCP, TCP_KEEPIDLE, int(I2PInterfacePeer.TCP_PROBE_AFTER))
else:
self.socket.setsockopt(socket.IPPROTO_TCP, TCP_KEEPIDLE, int(I2PInterfacePeer.I2P_PROBE_AFTER))
def detach(self):
if self.socket != None:
if hasattr(self.socket, "close"):
if callable(self.socket.close):
RNS.log("Detaching "+str(self), RNS.LOG_DEBUG)
self.detached = True
try:
self.socket.shutdown(socket.SHUT_RDWR)
except Exception as e:
RNS.log("Error while shutting down socket for "+str(self)+": "+str(e))
try:
self.socket.close()
except Exception as e:
RNS.log("Error while closing socket for "+str(self)+": "+str(e))
self.socket = None
def connect(self, initial=False):
try:
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.socket.connect((self.target_ip, self.target_port))
self.online = True
except Exception as e:
if initial:
if not self.awaiting_i2p_tunnel:
RNS.log("Initial connection for "+str(self)+" could not be established: "+str(e), RNS.LOG_ERROR)
RNS.log("Leaving unconnected and retrying connection in "+str(I2PInterfacePeer.RECONNECT_WAIT)+" seconds.", RNS.LOG_ERROR)
return False
else:
raise e
if platform.system() == "Linux":
self.set_timeouts_linux()
elif platform.system() == "Darwin":
self.set_timeouts_osx()
self.online = True
self.writing = False
self.never_connected = False
if not self.kiss_framing and self.wants_tunnel:
RNS.Transport.synthesize_tunnel(self)
return True
def reconnect(self):
if self.initiator:
if not self.reconnecting:
self.reconnecting = True
attempts = 0
while not self.online:
time.sleep(I2PInterfacePeer.RECONNECT_WAIT)
attempts += 1
if self.max_reconnect_tries != None and attempts > self.max_reconnect_tries:
RNS.log("Max reconnection attempts reached for "+str(self), RNS.LOG_ERROR)
self.teardown()
break
try:
self.connect()
except Exception as e:
if not self.awaiting_i2p_tunnel:
RNS.log("Connection attempt for "+str(self)+" failed: "+str(e), RNS.LOG_DEBUG)
else:
RNS.log(str(self)+" still waiting for I2P tunnel to appear", RNS.LOG_VERBOSE)
if not self.never_connected:
RNS.log(str(self)+" Re-established connection via I2P tunnel", RNS.LOG_INFO)
self.reconnecting = False
thread = threading.Thread(target=self.read_loop)
thread.setDaemon(True)
thread.start()
if not self.kiss_framing:
RNS.Transport.synthesize_tunnel(self)
else:
RNS.log("Attempt to reconnect on a non-initiator I2P interface. This should not happen.", RNS.LOG_ERROR)
raise IOError("Attempt to reconnect on a non-initiator I2P interface")
def processIncoming(self, data):
self.rxb += len(data)
if hasattr(self, "parent_interface") and self.parent_interface != None and self.parent_count:
self.parent_interface.rxb += len(data)
self.owner.inbound(data, self)
def processOutgoing(self, data):
if self.online:
while self.writing:
time.sleep(0.01)
try:
self.writing = True
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)
if hasattr(self, "parent_interface") and self.parent_interface != None and self.parent_count:
self.parent_interface.txb += len(data)
except Exception as e:
RNS.log("Exception occurred while transmitting via "+str(self)+", tearing down interface", RNS.LOG_ERROR)
RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR)
self.teardown()
def read_loop(self):
try:
in_frame = False
escape = False
data_buffer = b""
command = KISS.CMD_UNKNOWN
while True:
data_in = self.socket.recv(4096)
if len(data_in) > 0:
pointer = 0
while pointer < len(data_in):
byte = data_in[pointer]
pointer += 1
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:
RNS.log("Socket for "+str(self)+" was closed, attempting to reconnect...", RNS.LOG_WARNING)
self.reconnect()
else:
RNS.log("Socket for remote client "+str(self)+" was closed.", RNS.LOG_VERBOSE)
self.teardown()
break
except Exception as e:
self.online = False
RNS.log("An interface error occurred for "+str(self)+", the contained exception was: "+str(e), RNS.LOG_WARNING)
if self.initiator:
RNS.log("Attempting to reconnect...", RNS.LOG_WARNING)
self.reconnect()
else:
self.teardown()
def teardown(self):
if self.initiator and not self.detached:
RNS.log("The interface "+str(self)+" experienced an unrecoverable error and is being torn down. Restart Reticulum to attempt to open this interface again.", RNS.LOG_ERROR)
if RNS.Reticulum.panic_on_interface_error:
RNS.panic()
else:
RNS.log("The interface "+str(self)+" is being torn down.", RNS.LOG_VERBOSE)
self.online = False
self.OUT = False
self.IN = False
if hasattr(self, "parent_interface") and self.parent_interface != None:
self.parent_interface.clients -= 1
if self in RNS.Transport.interfaces:
if not self.initiator:
RNS.Transport.interfaces.remove(self)
def __str__(self):
return "I2PInterfacePeer["+str(self.name)+"]"
class I2PInterface(Interface):
def __init__(self, owner, name, rns_storagepath, peers, connectable = True):
self.rxb = 0
self.txb = 0
self.online = False
self.clients = 0
self.owner = owner
self.connectable = connectable
self.i2p_tunneled = True
self.mode = RNS.Interfaces.Interface.Interface.MODE_FULL
self.b32 = None
self.i2p = I2PController(rns_storagepath)
self.IN = True
self.OUT = False
self.name = name
self.receives = True
self.bind_ip = "127.0.0.1"
self.bind_port = self.i2p.get_free_port()
self.address = (self.bind_ip, self.bind_port)
i2p_thread = threading.Thread(target=self.i2p.start)
i2p_thread.setDaemon(True)
i2p_thread.start()
def handlerFactory(callback):
def createHandler(*args, **keys):
return I2PInterfaceHandler(callback, *args, **keys)
return createHandler
ThreadingI2PServer.allow_reuse_address = True
self.server = ThreadingI2PServer(self.address, handlerFactory(self.incoming_connection))
thread = threading.Thread(target=self.server.serve_forever)
thread.setDaemon(True)
thread.start()
if self.connectable:
def tunnel_job():
self.i2p.server_tunnel(self)
thread = threading.Thread(target=tunnel_job)
thread.setDaemon(True)
thread.start()
if peers != None:
for peer_addr in peers:
interface_name = peer_addr
peer_interface = I2PInterfacePeer(self, self.owner, interface_name, peer_addr)
peer_interface.OUT = True
peer_interface.IN = True
peer_interface.parent_interface = self
peer_interface.parent_count = False
RNS.Transport.interfaces.append(peer_interface)
self.online = True
def incoming_connection(self, handler):
RNS.log("Accepting incoming I2P connection", RNS.LOG_VERBOSE)
interface_name = "Connected peer on "+self.name
spawned_interface = I2PInterfacePeer(self, self.owner, interface_name, connected_socket=handler.request)
spawned_interface.OUT = True
spawned_interface.IN = True
spawned_interface.parent_interface = self
spawned_interface.online = True
RNS.log("Spawned new I2PInterface Peer: "+str(spawned_interface), RNS.LOG_VERBOSE)
RNS.Transport.interfaces.append(spawned_interface)
self.clients += 1
spawned_interface.read_loop()
def processOutgoing(self, data):
pass
def detach(self):
self.i2p.stop()
def __str__(self):
return "I2PInterface["+self.name+"]"
class I2PInterfaceHandler(socketserver.BaseRequestHandler):
def __init__(self, callback, *args, **keys):
self.callback = callback
socketserver.BaseRequestHandler.__init__(self, *args, **keys)
def handle(self):
self.callback(handler=self)
+4
View File
@@ -7,6 +7,10 @@ class Interface:
RPT = False
name = None
MODE_FULL = 0x01
MODE_POINT_TO_POINT = 0x02
MODE_ACCESS_POINT = 0x03
def __init__(self):
self.rxb = 0
self.txb = 0
+67 -32
View File
@@ -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+"]"
+61 -14
View File
@@ -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,7 +33,11 @@ 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
self.mode = RNS.Interfaces.Interface.Interface.MODE_FULL
if connected_socket != None:
self.receives = True
@@ -46,11 +51,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 +62,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 +110,7 @@ class LocalClientInterface(Interface):
self.owner.inbound(data, self)
def processOutgoing(self, data):
if self.online:
while self.writing:
@@ -119,8 +162,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 +217,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):
@@ -192,6 +238,7 @@ class LocalServerInterface(Interface):
self.IN = True
self.OUT = False
self.name = "Reticulum"
self.mode = RNS.Interfaces.Interface.Interface.MODE_FULL
if (bindport != None):
self.receives = True
+170 -37
View File
@@ -2,7 +2,6 @@
from .Interface import Interface
from time import sleep
import sys
import serial
import threading
import time
import math
@@ -31,8 +30,11 @@ class KISS():
CMD_STAT_SNR = 0x24
CMD_BLINK = 0x30
CMD_RANDOM = 0x40
CMD_PLATFORM = 0x48
CMD_MCU = 0x49
CMD_FW_VERSION = 0x50
CMD_ROM_READ = 0x51
CMD_RESET = 0x55
DETECT_REQ = 0x73
DETECT_RESP = 0x46
@@ -46,6 +48,9 @@ class KISS():
ERROR_TXFAILED = 0x02
ERROR_EEPROM_LOCKED = 0x03
PLATFORM_AVR = 0x90
PLATFORM_ESP32 = 0x80
@staticmethod
def escape(data):
data = data.replace(bytes([0xdb]), bytes([0xdb, 0xdd]))
@@ -71,10 +76,22 @@ class RNodeInterface(Interface):
CALLSIGN_MAX_LEN = 32
REQUIRED_FW_VER_MAJ = 1
REQUIRED_FW_VER_MIN = 26
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
@@ -93,6 +110,12 @@ class RNodeInterface(Interface):
self.cr = cr
self.state = KISS.RADIO_STATE_OFF
self.bitrate = 0
self.platform = None
self.mcu = None
self.detected = False
self.firmware_ok = False
self.maj_version = 0
self.min_version = 0
self.last_id = 0
self.first_tx = None
@@ -150,46 +173,64 @@ 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.detect()
sleep(0.1)
if not self.detected:
raise IOError("Could not detect device")
else:
if self.platform == KISS.PLATFORM_ESP32:
RNS.log("Resetting ESP32-based device before configuration...", RNS.LOG_VERBOSE)
self.hard_reset()
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 configuration validation")
def initRadio(self):
self.setFrequency()
@@ -199,6 +240,19 @@ class RNodeInterface(Interface):
self.setCodingRate()
self.setRadioState(KISS.RADIO_STATE_ON)
def detect(self):
kiss_command = bytes([KISS.FEND, KISS.CMD_DETECT, KISS.DETECT_REQ, KISS.FEND, KISS.CMD_FW_VERSION, 0x00, KISS.FEND, KISS.CMD_PLATFORM, 0x00, KISS.FEND, KISS.CMD_MCU, 0x00, KISS.FEND])
written = self.serial.write(kiss_command)
if written != len(kiss_command):
raise IOError("An IO error occurred while detecting hardware for "+self(str))
def hard_reset(self):
kiss_command = bytes([KISS.FEND, KISS.CMD_RESET, 0xf8, KISS.FEND])
written = self.serial.write(kiss_command)
if written != len(kiss_command):
raise IOError("An IO error occurred while restarting device")
sleep(2.25);
def setFrequency(self):
c1 = self.frequency >> 24
c2 = self.frequency >> 16 & 0xFF
@@ -245,13 +299,28 @@ class RNodeInterface(Interface):
raise IOError("An IO error occurred while configuring coding rate for "+self(str))
def setRadioState(self, state):
self.state = state
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_RADIO_STATE])+bytes([state])+bytes([KISS.FEND])
written = self.serial.write(kiss_command)
if written != len(kiss_command):
raise IOError("An IO error occurred while configuring radio state for "+self(str))
def validate_firmware(self):
if (self.maj_version >= RNodeInterface.REQUIRED_FW_VER_MAJ):
if (self.min_version >= RNodeInterface.REQUIRED_FW_VER_MIN):
self.firmware_ok = True
if self.firmware_ok:
return
RNS.log("The firmware version of the connected RNode is "+str(self.maj_version)+"."+str(self.min_version), RNS.LOG_ERROR)
RNS.log("This version of Reticulum requires at least version "+str(RNodeInterface.REQUIRED_FW_VER_MAJ)+"."+str(RNodeInterface.REQUIRED_FW_VER_MIN), RNS.LOG_ERROR)
RNS.log("Please update your RNode firmware with rnodeconf (https://github.com/markqvist/rnodeconfigutil/)")
RNS.panic()
def validateRadioState(self):
RNS.log("Validating radio configuration for "+str(self)+"...", RNS.LOG_VERBOSE)
RNS.log("Wating for radio configuration validation for "+str(self)+"...", RNS.LOG_VERBOSE)
sleep(0.25);
if (self.frequency != self.r_frequency):
RNS.log("Frequency mismatch", RNS.LOG_ERROR)
@@ -265,6 +334,9 @@ class RNodeInterface(Interface):
if (self.sf != self.r_sf):
RNS.log("Spreading factor mismatch", RNS.LOG_ERROR)
self.validcfg = False
if (self.state != self.r_state):
RNS.log("Radio state mismatch", RNS.LOG_ERROR)
self.validcfg = False
if (self.validcfg):
return True
@@ -283,6 +355,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):
@@ -403,8 +477,30 @@ class RNodeInterface(Interface):
self.updateBitrate()
elif (command == KISS.CMD_RADIO_STATE):
self.r_state = byte
if self.r_state:
pass
#RNS.log(str(self)+" Radio reporting state is online", RNS.LOG_DEBUG)
else:
RNS.log(str(self)+" Radio reporting state is offline", RNS.LOG_DEBUG)
elif (command == KISS.CMD_RADIO_LOCK):
self.r_lock = byte
elif (command == KISS.CMD_FW_VERSION):
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
command_buffer = command_buffer+bytes([byte])
if (len(command_buffer) == 2):
self.maj_version = int(command_buffer[0])
self.min_version = int(command_buffer[1])
self.validate_firmware()
elif (command == KISS.CMD_STAT_RX):
if (byte == KISS.FESC):
escape = True
@@ -439,15 +535,33 @@ class RNodeInterface(Interface):
self.r_stat_snr = int.from_bytes(bytes([byte]), byteorder="big", signed=True) * 0.25
elif (command == KISS.CMD_RANDOM):
self.r_random = byte
elif (command == KISS.CMD_PLATFORM):
self.platform = byte
elif (command == KISS.CMD_MCU):
self.mcu = byte
elif (command == KISS.CMD_ERROR):
if (byte == KISS.ERROR_INITRADIO):
RNS.log(str(self)+" hardware initialisation error (code "+RNS.hexrep(byte)+")", RNS.LOG_ERROR)
raise IOError("Radio initialisation failure")
elif (byte == KISS.ERROR_INITRADIO):
RNS.log(str(self)+" hardware TX error (code "+RNS.hexrep(byte)+")", RNS.LOG_ERROR)
raise IOError("Hardware transmit failure")
else:
RNS.log(str(self)+" hardware error (code "+RNS.hexrep(byte)+")", RNS.LOG_ERROR)
raise IOError("Unknown hardware failure")
elif (command == KISS.CMD_RESET):
if (byte == 0xF8):
if self.platform == KISS.PLATFORM_ESP32:
if self.online:
RNS.log("Detected reset while device was online, reinitialising device...", RNS.LOG_ERROR)
raise IOError("ESP32 reset")
elif (command == KISS.CMD_READY):
self.process_queue()
elif (command == KISS.CMD_DETECT):
if byte == KISS.DETECT_RESP:
self.detected = True
else:
self.detected = False
else:
time_since_last = int(time.time()*1000) - last_read_ms
@@ -469,11 +583,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()
def __str__(self):
return "RNodeInterface["+self.name+"]"
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(3.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["+str(self.name)+"]"
+59 -23
View File
@@ -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+"]"
+124 -37
View File
@@ -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,12 @@ 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):
I2P_USER_TIMEOUT = 40
I2P_PROBE_AFTER = 10
I2P_PROBE_INTERVAL = 5
I2P_PROBES = 6
def __init__(self, owner, name, target_ip=None, target_port=None, connected_socket=None, max_reconnect_tries=None, kiss_framing=False, i2p_tunneled = False):
self.rxb = 0
self.txb = 0
@@ -49,7 +67,10 @@ class TCPClientInterface(Interface):
self.writing = False
self.online = False
self.detached = False
self.kiss_framing = kiss_framing
self.i2p_tunneled = i2p_tunneled
self.mode = RNS.Interfaces.Interface.Interface.MODE_FULL
if max_reconnect_tries == None:
self.max_reconnect_tries = TCPClientInterface.RECONNECT_MAX_TRIES
else:
@@ -80,16 +101,23 @@ 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):
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_USER_TIMEOUT, int(TCPClientInterface.TCP_USER_TIMEOUT * 1000))
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, int(TCPClientInterface.TCP_PROBE_AFTER))
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, int(TCPClientInterface.TCP_PROBE_INTERVAL))
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, int(TCPClientInterface.TCP_PROBES))
if not self.i2p_tunneled:
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_USER_TIMEOUT, int(TCPClientInterface.TCP_USER_TIMEOUT * 1000))
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, int(TCPClientInterface.TCP_PROBE_AFTER))
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, int(TCPClientInterface.TCP_PROBE_INTERVAL))
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, int(TCPClientInterface.TCP_PROBES))
else:
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_USER_TIMEOUT, int(TCPClientInterface.I2P_USER_TIMEOUT * 1000))
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, int(TCPClientInterface.I2P_PROBE_AFTER))
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, int(TCPClientInterface.I2P_PROBE_INTERVAL))
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, int(TCPClientInterface.I2P_PROBES))
def set_timeouts_osx(self):
if hasattr(socket, "TCP_KEEPALIVE"):
@@ -97,9 +125,13 @@ class TCPClientInterface(Interface):
else:
TCP_KEEPIDLE = 0x10
sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
sock.setsockopt(socket.IPPROTO_TCP, TCP_KEEPIDLE, int(TCPClientInterface.TCP_PROBE_AFTER))
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
if not self.i2p_tunneled:
self.socket.setsockopt(socket.IPPROTO_TCP, TCP_KEEPIDLE, int(TCPClientInterface.TCP_PROBE_AFTER))
else:
self.socket.setsockopt(socket.IPPROTO_TCP, TCP_KEEPIDLE, int(TCPClientInterface.I2P_PROBE_AFTER))
def detach(self):
if self.socket != None:
if hasattr(self.socket, "close"):
@@ -173,7 +205,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 +226,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 +249,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 +258,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,12 +355,27 @@ 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):
def __init__(self, owner, name, device=None, bindip=None, bindport=None, i2p_tunneled=False):
self.rxb = 0
self.txb = 0
self.online = False
@@ -301,6 +385,9 @@ class TCPServerInterface(Interface):
self.OUT = False
self.name = name
self.i2p_tunneled = i2p_tunneled
self.mode = RNS.Interfaces.Interface.Interface.MODE_FULL
if device != None:
bindip = TCPServerInterface.get_address_for_if(device)
@@ -330,7 +417,7 @@ class TCPServerInterface(Interface):
def incoming_connection(self, handler):
RNS.log("Accepting incoming TCP connection", RNS.LOG_VERBOSE)
interface_name = "Client on "+self.name
spawned_interface = TCPClientInterface(self.owner, interface_name, target_ip=None, target_port=None, connected_socket=handler.request)
spawned_interface = TCPClientInterface(self.owner, interface_name, target_ip=None, target_port=None, connected_socket=handler.request, i2p_tunneled=self.i2p_tunneled)
spawned_interface.OUT = self.OUT
spawned_interface.IN = self.IN
spawned_interface.target_ip = handler.client_address[0]
@@ -354,4 +441,4 @@ class TCPInterfaceHandler(socketserver.BaseRequestHandler):
socketserver.BaseRequestHandler.__init__(self, *args, **keys)
def handle(self):
self.callback(handler=self)
self.callback(handler=self)
+18 -3
View File
@@ -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)
+49 -19
View File
@@ -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)
@@ -546,7 +551,8 @@ class Link:
break
if remove != None:
self.pending_requests.remove(remove)
if remove in self.pending_requests:
self.pending_requests.remove(remove)
def request_resource_concluded(self, resource):
if resource.status == RNS.Resource.COMPLETE:
@@ -598,7 +604,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 +622,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 +671,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)
@@ -751,7 +766,7 @@ class Link:
return plaintext
except Exception as e:
RNS.log("Decryption failed on link "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
RNS.log(traceback.format_exc(), RNS.LOG_ERROR)
# RNS.log(traceback.format_exc(), RNS.LOG_ERROR)
# TODO: Think long about implications here
# self.teardown()
@@ -933,7 +948,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 +969,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 +988,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 +1011,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):
"""
+48 -27
View File
@@ -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:
@@ -185,27 +185,33 @@ class Packet:
def unpack(self):
self.flags = self.raw[0]
self.hops = self.raw[1]
try:
self.flags = self.raw[0]
self.hops = self.raw[1]
self.header_type = (self.flags & 0b11000000) >> 6
self.transport_type = (self.flags & 0b00110000) >> 4
self.destination_type = (self.flags & 0b00001100) >> 2
self.packet_type = (self.flags & 0b00000011)
self.header_type = (self.flags & 0b11000000) >> 6
self.transport_type = (self.flags & 0b00110000) >> 4
self.destination_type = (self.flags & 0b00001100) >> 2
self.packet_type = (self.flags & 0b00000011)
if self.header_type == Packet.HEADER_2:
self.transport_id = self.raw[2:12]
self.destination_hash = self.raw[12:22]
self.context = ord(self.raw[22:23])
self.data = self.raw[23:]
else:
self.transport_id = None
self.destination_hash = self.raw[2:12]
self.context = ord(self.raw[12:13])
self.data = self.raw[13:]
if self.header_type == Packet.HEADER_2:
self.transport_id = self.raw[2:12]
self.destination_hash = self.raw[12:22]
self.context = ord(self.raw[22:23])
self.data = self.raw[23:]
else:
self.transport_id = None
self.destination_hash = self.raw[2:12]
self.context = ord(self.raw[12:13])
self.data = self.raw[13:]
self.packed = False
self.update_hash()
self.packed = False
self.update_hash()
return True
except Exception as e:
RNS.log("Received malformed packet, dropping it. The contained exception was: "+str(e), RNS.LOG_EXTREME)
return False
def send(self):
"""
@@ -328,6 +334,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 +351,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 +368,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 +397,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 +408,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 +432,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
+30 -29
View File
@@ -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)
@@ -140,6 +143,8 @@ class Resource:
def __init__(self, data, link, advertise=True, auto_compress=True, callback=None, progress_callback=None, timeout = None, segment_index = 1, original_hash = None, request_id = None, is_response = False):
data_size = None
resource_data = None
self.assembly_lock = False
if hasattr(data, "read"):
data_size = os.stat(data.name).st_size
self.total_size = data_size
@@ -321,10 +326,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 +406,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 +498,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 +535,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,20 +594,17 @@ 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:
if self.received_count == self.total_parts and not self.assembly_lock:
self.assembly_lock = True
self.assemble()
elif self.outstanding_parts == 0:
# TODO: Figure out if there is a mathematically
@@ -754,7 +749,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 +772,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
@@ -804,7 +805,7 @@ class Resource:
return progress
def __str__(self):
return RNS.prettyhexrep(self.hash)+str(self.link)
return "<"+RNS.hexrep(self.hash)+"/"+RNS.hexrep(self.link.link_id)+">"
class ResourceAdvertisement:
+221 -292
View File
@@ -1,8 +1,18 @@
from .Interfaces import *
from .vendor.platformutils import get_platform
if get_platform() == "android":
from .Interfaces import Interface
from .Interfaces import LocalInterface
from .Interfaces import AutoInterface
from .Interfaces import TCPInterface
from .Interfaces import UDPInterface
from .Interfaces import I2PInterface
else:
from .Interfaces import *
from .vendor.configobj import ConfigObj
import configparser
import multiprocessing.connection
import RNS
import signal
import threading
import atexit
@@ -53,6 +63,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 +105,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
@@ -96,6 +120,8 @@ class Reticulum:
:param configdir: Full path to a Reticulum configuration directory.
"""
RNS.vendor.platformutils.platform_checks()
if configdir != None:
Reticulum.configdir = configdir
@@ -146,9 +172,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 +194,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:
@@ -247,13 +274,58 @@ class Reticulum:
self.__start_local_interface()
if self.is_shared_instance or self.is_standalone_instance:
RNS.log("Bringing up system interfaces...", RNS.LOG_DEBUG)
interface_names = []
for name in self.config["interfaces"]:
if not name in interface_names:
c = self.config["interfaces"][name]
interface_mode = Interface.Interface.MODE_FULL
if "mode" in c:
if c["mode"] == "full":
interface_mode = Interface.Interface.MODE_FULL
elif c["mode"] == "accesspoint" or c["mode"] == "ap":
interface_mode = Interface.Interface.MODE_ACCESS_POINT
elif c["mode"] == "pointtopoint" or c["mode"] == "ptp":
interface_mode = Interface.Interface.MODE_POINT_TO_POINT
try:
if ("interface_enabled" in c) and c.as_bool("interface_enabled") == True:
if c["type"] == "AutoInterface":
if not RNS.vendor.platformutils.is_windows():
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") == False:
interface.OUT = False
else:
interface.OUT = True
interface.mode = interface_mode
RNS.Transport.interfaces.append(interface)
else:
RNS.log("AutoInterface is not currently supported on Windows, disabling interface.", RNS.LOG_ERROR);
RNS.log("Please remove this AutoInterface instance from your configuration file.", RNS.LOG_ERROR);
RNS.log("You will have to manually configure other interfaces for connectivity.", RNS.LOG_ERROR);
if c["type"] == "UDPInterface":
device = c["device"] if "device" in c else None
port = int(c["port"]) if "port" in c else None
@@ -278,10 +350,12 @@ class Reticulum:
forward_port
)
if "outgoing" in c and c.as_bool("outgoing") == True:
interface.OUT = True
else:
if "outgoing" in c and c.as_bool("outgoing") == False:
interface.OUT = False
else:
interface.OUT = True
interface.mode = interface_mode
RNS.Transport.interfaces.append(interface)
@@ -291,6 +365,7 @@ class Reticulum:
port = int(c["port"]) if "port" in c else None
listen_ip = c["listen_ip"] if "listen_ip" in c else None
listen_port = int(c["listen_port"]) if "listen_port" in c else None
i2p_tunneled = c.as_bool("i2p_tunneled") if "i2p_tunneled" in c else False
if port != None:
listen_port = port
@@ -300,29 +375,76 @@ class Reticulum:
name,
device,
listen_ip,
listen_port
listen_port,
i2p_tunneled
)
if "outgoing" in c and c.as_bool("outgoing") == True:
interface.OUT = True
else:
if "outgoing" in c and c.as_bool("outgoing") == False:
interface.OUT = False
else:
interface.OUT = True
if interface_mode != Interface.Interface.MODE_FULL:
RNS.log(str(interface)+" does not support Access Point mode, reverting to default mode: Full", RNS.LOG_WARNING)
interface_mode = Interface.Interface.MODE_FULL
interface.mode = interface_mode
RNS.Transport.interfaces.append(interface)
if c["type"] == "TCPClientInterface":
kiss_framing = False
if "kiss_framing" in c and c.as_bool("kiss_framing") == True:
kiss_framing = True
i2p_tunneled = c.as_bool("i2p_tunneled") if "i2p_tunneled" in c else False
interface = TCPInterface.TCPClientInterface(
RNS.Transport,
name,
c["target_host"],
int(c["target_port"])
int(c["target_port"]),
kiss_framing = kiss_framing,
i2p_tunneled = i2p_tunneled
)
if "outgoing" in c and c.as_bool("outgoing") == True:
interface.OUT = True
else:
if "outgoing" in c and c.as_bool("outgoing") == False:
interface.OUT = False
else:
interface.OUT = True
if interface_mode != Interface.Interface.MODE_FULL:
RNS.log(str(interface)+" does not support Access Point mode, reverting to default mode: Full", RNS.LOG_WARNING)
interface_mode = Interface.Interface.MODE_FULL
interface.mode = interface_mode
RNS.Transport.interfaces.append(interface)
if c["type"] == "I2PInterface":
i2p_peers = c.as_list("peers") if "peers" in c else None
connectable = c.as_bool("connectable") if "connectable" in c else False
interface = I2PInterface.I2PInterface(
RNS.Transport,
name,
Reticulum.storagepath,
i2p_peers,
connectable = connectable,
)
if "outgoing" in c and c.as_bool("outgoing") == False:
interface.OUT = False
else:
interface.OUT = True
if interface_mode != Interface.Interface.MODE_FULL:
RNS.log(str(interface)+" does not support Access Point mode, reverting to default mode: Full", RNS.LOG_WARNING)
interface_mode = Interface.Interface.MODE_FULL
interface.mode = interface_mode
RNS.Transport.interfaces.append(interface)
@@ -347,10 +469,12 @@ class Reticulum:
stopbits
)
if "outgoing" in c and c.as_bool("outgoing") == True:
interface.OUT = True
else:
if "outgoing" in c and c.as_bool("outgoing") == False:
interface.OUT = False
else:
interface.OUT = True
interface.mode = interface_mode
RNS.Transport.interfaces.append(interface)
@@ -388,10 +512,12 @@ class Reticulum:
beacon_data
)
if "outgoing" in c and c.as_bool("outgoing") == True:
interface.OUT = True
else:
if "outgoing" in c and c.as_bool("outgoing") == False:
interface.OUT = False
else:
interface.OUT = True
interface.mode = interface_mode
RNS.Transport.interfaces.append(interface)
@@ -430,10 +556,12 @@ class Reticulum:
flow_control
)
if "outgoing" in c and c.as_bool("outgoing") == True:
interface.OUT = True
else:
if "outgoing" in c and c.as_bool("outgoing") == False:
interface.OUT = False
else:
interface.OUT = True
interface.mode = interface_mode
RNS.Transport.interfaces.append(interface)
@@ -466,14 +594,16 @@ class Reticulum:
id_callsign = id_callsign
)
if "outgoing" in c and c.as_bool("outgoing") == True:
interface.OUT = True
else:
if "outgoing" in c and c.as_bool("outgoing") == False:
interface.OUT = False
else:
interface.OUT = True
interface.mode = interface_mode
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)
@@ -482,6 +612,9 @@ class Reticulum:
else:
RNS.log("The interface name \""+name+"\" was already used. Check your configuration file for errors!", RNS.LOG_ERROR)
RNS.panic()
RNS.log("System interfaces are ready", RNS.LOG_DEBUG)
def __create_default_config(self):
@@ -491,7 +624,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 +643,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)
@@ -531,6 +669,12 @@ class Reticulum:
else:
ifstats["clients"] = None
if hasattr(interface, "b32"):
if interface.b32 != None:
ifstats["i2p_b32"] = interface.b32+".b32.i2p"
else:
ifstats["i2p_b32"] = None
ifstats["name"] = str(interface)
ifstats["rxb"] = interface.rxb
ifstats["txb"] = interface.txb
@@ -545,6 +689,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 +699,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():
@@ -586,6 +760,12 @@ __default_rns_config__ = '''# This is the default Reticulum config file.
# You should probably edit it to include any additional,
# interfaces and settings you might need.
# Only the most basic options are included in this default
# configuration. To see a more verbose, and much longer,
# configuration example, you can run the command:
# rnsd --exampleconfig
[reticulum]
# If you enable Transport, your system will route traffic
@@ -619,6 +799,7 @@ share_instance = Yes
shared_instance_port = 37428
instance_control_port = 37429
# You can configure Reticulum to panic and forcibly close
# if an unrecoverable interface error occurs, such as the
# hardware device for an interface disappearing. This is
@@ -651,267 +832,15 @@ 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
listen_ip = 0.0.0.0
listen_port = 4242
forward_ip = 255.255.255.255
forward_port = 4242
# The above configuration will allow communication
# within the local broadcast domains of all local
# IP interfaces. This is enabled by default as an
# easy way to get started, but you might want to
# consider altering it to something more specific.
# Instead of specifying listen_ip, listen_port,
# forward_ip and forward_port, you can also bind
# to a specific network device like below.
# device = eth0
# port = 4242
# Assuming the eth0 device has the address
# 10.55.0.72/24, the above configuration would
# be equivalent to the following manual setup.
# Note that we are both listening and forwarding to
# the broadcast address of the network segments.
# listen_ip = 10.55.0.255
# listen_port = 4242
# forward_ip = 10.55.0.255
# forward_port = 4242
# You can of course also communicate only with
# a single IP address
# listen_ip = 10.55.0.15
# listen_port = 4242
# forward_ip = 10.55.0.16
# forward_port = 4242
# This example demonstrates a TCP server interface.
# It will listen for incoming connections on the
# specified IP address and port number.
[[TCP Server Interface]]
type = TCPServerInterface
interface_enabled = False
outgoing = True
# This configuration will listen on all IP
# interfaces on port 4242
listen_ip = 0.0.0.0
listen_port = 4242
# Alternatively you can bind to a specific IP
# listen_ip = 10.0.0.88
# listen_port = 4242
# Or a specific network device
# device = eth0
# port = 4242
# To connect to a TCP server interface, you would
# naturally use the TCP client interface. Here's
# an example. The target_host can either be an IP
# address or a hostname
[[TCP Client Interface]]
type = TCPClientInterface
interface_enabled = False
outgoing = True
target_host = 127.0.0.1
target_port = 4242
# Here's an example of how to add a LoRa interface
# using the RNode LoRa transceiver.
[[RNode LoRa Interface]]
type = RNodeInterface
# Enable interface if you want use it!
interface_enabled = False
# Allow transmit on interface. Setting
# this to false will create a listen-
# only interface.
outgoing = true
# Serial port for the device
port = /dev/ttyUSB0
# Set frequency to 867.2 MHz
frequency = 867200000
# Set LoRa bandwidth to 125 KHz
bandwidth = 125000
# Set TX power to 7 dBm (5 mW)
txpower = 7
# Select spreading factor 8. Valid
# range is 7 through 12, with 7
# being the fastest and 12 having
# the longest range.
spreadingfactor = 8
# Select coding rate 5. Valid range
# is 5 throough 8, with 5 being the
# fastest, and 8 the longest range.
codingrate = 5
# You can configure the RNode to send
# out identification on the channel with
# a set interval by configuring the
# following two parameters. The trans-
# ceiver will only ID if the set
# interval has elapsed since it's last
# actual transmission. The interval is
# configured in seconds.
# This option is commented out and not
# used by default.
# id_callsign = MYCALL-0
# id_interval = 600
# For certain homebrew RNode interfaces
# with low amounts of RAM, using packet
# flow control can be useful. By default
# it is disabled.
flow_control = False
# An example KISS modem interface. Useful for running
# Reticulum over packet radio hardware.
[[Packet Radio KISS Interface]]
type = KISSInterface
# Enable interface if you want use it!
interface_enabled = False
# Allow transmit on interface.
outgoing = true
# Serial port for the device
port = /dev/ttyUSB1
# Set the serial baud-rate and other
# configuration parameters.
speed = 115200
databits = 8
parity = none
stopbits = 1
# Set the modem preamble. A 150ms
# preamble should be a reasonable
# default, but may need to be
# increased for radios with slow-
# opening squelch and long TX/RX
# turnaround
preamble = 150
# Set the modem TX tail. In most
# cases this should be kept as low
# as possible to not waste airtime.
txtail = 10
# Configure CDMA parameters. These
# settings are reasonable defaults.
persistence = 200
slottime = 20
# You can configure the interface to send
# out identification on the channel with
# a set interval by configuring the
# following two parameters. The KISS
# interface will only ID if the set
# interval has elapsed since it's last
# actual transmission. The interval is
# configured in seconds.
# This option is commented out and not
# used by default.
# id_callsign = MYCALL-0
# id_interval = 600
# Whether to use KISS flow-control.
# This is useful for modems that have
# a small internal packet buffer, but
# support packet flow control instead.
flow_control = false
# If you're using Reticulum on amateur radio spectrum,
# you might want to use the AX.25 KISS interface. This
# way, Reticulum will automatically encapsulate it's
# traffic in AX.25 and also identify your stations
# transmissions with your callsign and SSID.
#
# Only do this if you really need to! Reticulum doesn't
# need the AX.25 layer for anything, and it incurs extra
# overhead on every packet to encapsulate in AX.25.
#
# A more efficient way is to use the plain KISS interface
# with the beaconing functionality described above.
[[Packet Radio AX.25 KISS Interface]]
type = AX25KISSInterface
# Set the station callsign and SSID
callsign = NO1CLL
ssid = 0
# Enable interface if you want use it!
interface_enabled = False
# Allow transmit on interface.
outgoing = true
# Serial port for the device
port = /dev/ttyUSB2
# Set the serial baud-rate and other
# configuration parameters.
speed = 115200
databits = 8
parity = none
stopbits = 1
# Whether to use KISS flow-control.
# This is useful for modems with a
# small internal packet buffer.
flow_control = false
# Set the modem preamble. A 150ms
# preamble should be a reasonable
# default, but may need to be
# increased for radios with slow-
# opening squelch and long TX/RX
# turnaround
preamble = 150
# Set the modem TX tail. In most
# cases this should be kept as low
# as possible to not waste airtime.
txtail = 10
# Configure CDMA parameters. These
# settings are reasonable defaults.
persistence = 200
slottime = 20
'''.splitlines()
'''.splitlines()
+138 -19
View File
@@ -35,6 +35,7 @@ class Transport:
PATHFINDER_T = 10 # Retry grace period
PATHFINDER_RW = 10 # Random window for announce rebroadcast
PATHFINDER_E = 60*60*24*7 # Path expiration in seconds
AP_PATH_TIME = 60*60*24 # Expiration for Access Point paths
# TODO: Calculate an optimal number for this in
# various situations
@@ -76,6 +77,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
@@ -505,13 +512,19 @@ class Transport:
for interface in Transport.interfaces:
if interface.OUT:
should_transmit = True
if packet.destination.type == RNS.Destination.LINK:
if packet.destination.status == RNS.Link.CLOSED:
should_transmit = False
if interface != packet.destination.attached_interface:
should_transmit = False
if packet.attached_interface != None and interface != packet.attached_interface:
should_transmit = False
if packet.packet_type == RNS.Packet.ANNOUNCE:
if packet.attached_interface == None and interface.mode == RNS.Interfaces.Interface.Interface.MODE_ACCESS_POINT:
should_transmit = False
if should_transmit:
if not stored_hash:
@@ -579,14 +592,35 @@ class Transport:
Transport.jobs_locked = True
packet = RNS.Packet(None, raw)
packet.unpack()
if not packet.unpack():
return
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
@@ -775,7 +809,8 @@ class Transport:
# First, check that the announce is not for a destination
# local to this system, and that hops are less than the max
if (not any(packet.destination_hash == d.hash for d in Transport.destinations) and packet.hops < Transport.PATHFINDER_M+1):
random_blob = packet.data[RNS.Identity.KEYSIZE//8+10:RNS.Identity.KEYSIZE//8+20]
random_blob = packet.data[RNS.Identity.KEYSIZE//8:RNS.Identity.KEYSIZE//8+RNS.Reticulum.TRUNCATED_HASHLENGTH//8]
announce_emitted = int.from_bytes(random_blob[5:10], "big")
random_blobs = []
if packet.destination_hash in Transport.destination_table:
random_blobs = Transport.destination_table[packet.destination_hash][4]
@@ -796,8 +831,18 @@ class Transport:
else:
# If an announce arrives with a larger hop
# count than we already have in the table,
# ignore it, unless the path is expired
if (time.time() > Transport.destination_table[packet.destination_hash][3]):
# ignore it, unless the path is expired, or
# the emission timestamp is more recent.
now = time.time()
path_expires = Transport.destination_table[packet.destination_hash][3]
path_announce_emitted = 0
for path_random_blob in random_blobs:
path_announce_emitted = max(path_announce_emitted, int.from_bytes(path_random_blob[5:10], "big"))
if path_announce_emitted >= announce_emitted:
break
if (now >= path_expires):
# We also check that the announce hash is
# different from ones we've already heard,
# to avoid loops in the network
@@ -809,7 +854,13 @@ class Transport:
else:
should_add = False
else:
should_add = False
if (announce_emitted > path_announce_emitted):
if not random_blob in random_blobs:
RNS.log("Replacing destination table entry for "+str(RNS.prettyhexrep(packet.destination_hash))+" with new announce, since it was more recently emitted", RNS.LOG_DEBUG)
should_add = True
else:
should_add = False
else:
# If this destination is unknown in our table
# we should add it
@@ -818,21 +869,25 @@ class Transport:
if should_add:
now = time.time()
retries = 0
expires = now + Transport.PATHFINDER_E
announce_hops = packet.hops
local_rebroadcasts = 0
block_rebroadcasts = False
attached_interface = None
retransmit_timeout = now + math.pow(Transport.PATHFINDER_C, packet.hops) + (RNS.rand() * Transport.PATHFINDER_RW)
if packet.receiving_interface.mode == RNS.Interfaces.Interface.Interface.MODE_ACCESS_POINT:
expires = now + Transport.AP_PATH_TIME
else:
expires = now + Transport.PATHFINDER_E
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
@@ -848,6 +903,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)):
@@ -958,8 +1036,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:
@@ -1350,24 +1431,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]
@@ -1444,6 +1538,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:
+21 -1
View File
@@ -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
+359 -5
View File
@@ -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,20 +26,366 @@ 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("--exampleconfig", action='store_true', default=False, help="print verbose configuration example to stdout and exit")
parser.add_argument("--version", action="version", version="rnsd {version}".format(version=__version__))
args = parser.parse_args()
if args.exampleconfig:
print(__example_rns_config__)
exit()
if args.config:
configarg = args.config
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("")
exit()
__example_rns_config__ = '''# This is an example Reticulum config file.
# You should probably edit it to include any additional,
# interfaces and settings you might need.
[reticulum]
# If you enable Transport, your system will route traffic
# for other peers, pass announces and serve path requests.
# This should be done for systems that are suited to act
# as transport nodes, ie. if they are stationary and
# always-on. This directive is optional and can be removed
# for brevity.
enable_transport = False
# By default, the first program to launch the Reticulum
# Network Stack will create a shared instance, that other
# programs can communicate with. Only the shared instance
# opens all the configured interfaces directly, and other
# local programs communicate with the shared instance over
# a local socket. This is completely transparent to the
# user, and should generally be turned on. This directive
# is optional and can be removed for brevity.
share_instance = Yes
# If you want to run multiple *different* shared instances
# on the same system, you will need to specify different
# shared instance ports for each. The defaults are given
# below, and again, these options can be left out if you
# don't need them.
shared_instance_port = 37428
instance_control_port = 37429
# You can configure Reticulum to panic and forcibly close
# if an unrecoverable interface error occurs, such as the
# hardware device for an interface disappearing. This is
# an optional directive, and can be left out for brevity.
# This behaviour is disabled by default.
panic_on_interface_error = No
[logging]
# Valid log levels are 0 through 7:
# 0: Log only critical information
# 1: Log errors and lower log levels
# 2: Log warnings and lower log levels
# 3: Log notices and lower log levels
# 4: Log info and lower (this is the default)
# 5: Verbose logging
# 6: Debug logging
# 7: Extreme logging
loglevel = 4
# The interfaces section defines the physical and virtual
# interfaces Reticulum will use to communicate on. This
# section will contain examples for a variety of interface
# types. You can modify these or use them as a basis for
# your own config, or simply remove the unused ones.
[interfaces]
# This interface enables communication with other
# 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
# The following example enables communication with other
# local Reticulum peers using UDP broadcasts.
[[UDP Interface]]
type = UDPInterface
interface_enabled = False
listen_ip = 0.0.0.0
listen_port = 4242
forward_ip = 255.255.255.255
forward_port = 4242
# The above configuration will allow communication
# within the local broadcast domains of all local
# IP interfaces.
# Instead of specifying listen_ip, listen_port,
# forward_ip and forward_port, you can also bind
# to a specific network device like below.
# device = eth0
# port = 4242
# Assuming the eth0 device has the address
# 10.55.0.72/24, the above configuration would
# be equivalent to the following manual setup.
# Note that we are both listening and forwarding to
# the broadcast address of the network segments.
# listen_ip = 10.55.0.255
# listen_port = 4242
# forward_ip = 10.55.0.255
# forward_port = 4242
# You can of course also communicate only with
# a single IP address
# listen_ip = 10.55.0.15
# listen_port = 4242
# forward_ip = 10.55.0.16
# forward_port = 4242
# This example demonstrates a TCP server interface.
# It will listen for incoming connections on the
# specified IP address and port number.
[[TCP Server Interface]]
type = TCPServerInterface
interface_enabled = False
# This configuration will listen on all IP
# interfaces on port 4242
listen_ip = 0.0.0.0
listen_port = 4242
# Alternatively you can bind to a specific IP
# listen_ip = 10.0.0.88
# listen_port = 4242
# Or a specific network device
# device = eth0
# port = 4242
# To connect to a TCP server interface, you would
# naturally use the TCP client interface. Here's
# an example. The target_host can either be an IP
# address or a hostname
[[TCP Client Interface]]
type = TCPClientInterface
interface_enabled = False
target_host = 127.0.0.1
target_port = 4242
# This example shows how to make your Reticulum
# instance available over I2P, and connect to
# another I2P peer. Please be aware that you
# must have an I2P router running on your system
# with the SAMv3 API enabled for this to work.
[[I2P]]
type = I2PInterface
interface_enabled = yes
connectable = yes
peers = 5urvjicpzi7q3ybztsef4i5ow2aq4soktfj7zedz53s47r54jnqq.b32.i2p
# Here's an example of how to add a LoRa interface
# using the RNode LoRa transceiver.
[[RNode LoRa Interface]]
type = RNodeInterface
# Enable interface if you want use it!
interface_enabled = False
# Serial port for the device
port = /dev/ttyUSB0
# Set frequency to 867.2 MHz
frequency = 867200000
# Set LoRa bandwidth to 125 KHz
bandwidth = 125000
# Set TX power to 7 dBm (5 mW)
txpower = 7
# Select spreading factor 8. Valid
# range is 7 through 12, with 7
# being the fastest and 12 having
# the longest range.
spreadingfactor = 8
# Select coding rate 5. Valid range
# is 5 throough 8, with 5 being the
# fastest, and 8 the longest range.
codingrate = 5
# You can configure the RNode to send
# out identification on the channel with
# a set interval by configuring the
# following two parameters. The trans-
# ceiver will only ID if the set
# interval has elapsed since it's last
# actual transmission. The interval is
# configured in seconds.
# This option is commented out and not
# used by default.
# id_callsign = MYCALL-0
# id_interval = 600
# For certain homebrew RNode interfaces
# with low amounts of RAM, using packet
# flow control can be useful. By default
# it is disabled.
flow_control = False
# An example KISS modem interface. Useful for running
# Reticulum over packet radio hardware.
[[Packet Radio KISS Interface]]
type = KISSInterface
# Enable interface if you want use it!
interface_enabled = False
# Serial port for the device
port = /dev/ttyUSB1
# Set the serial baud-rate and other
# configuration parameters.
speed = 115200
databits = 8
parity = none
stopbits = 1
# Set the modem preamble. A 150ms
# preamble should be a reasonable
# default, but may need to be
# increased for radios with slow-
# opening squelch and long TX/RX
# turnaround
preamble = 150
# Set the modem TX tail. In most
# cases this should be kept as low
# as possible to not waste airtime.
txtail = 10
# Configure CDMA parameters. These
# settings are reasonable defaults.
persistence = 200
slottime = 20
# You can configure the interface to send
# out identification on the channel with
# a set interval by configuring the
# following two parameters. The KISS
# interface will only ID if the set
# interval has elapsed since it's last
# actual transmission. The interval is
# configured in seconds.
# This option is commented out and not
# used by default.
# id_callsign = MYCALL-0
# id_interval = 600
# Whether to use KISS flow-control.
# This is useful for modems that have
# a small internal packet buffer, but
# support packet flow control instead.
flow_control = false
# If you're using Reticulum on amateur radio spectrum,
# you might want to use the AX.25 KISS interface. This
# way, Reticulum will automatically encapsulate it's
# traffic in AX.25 and also identify your stations
# transmissions with your callsign and SSID.
#
# Only do this if you really need to! Reticulum doesn't
# need the AX.25 layer for anything, and it incurs extra
# overhead on every packet to encapsulate in AX.25.
#
# A more efficient way is to use the plain KISS interface
# with the beaconing functionality described above.
[[Packet Radio AX.25 KISS Interface]]
type = AX25KISSInterface
# Set the station callsign and SSID
callsign = NO1CLL
ssid = 0
# Enable interface if you want use it!
interface_enabled = False
# Serial port for the device
port = /dev/ttyUSB2
# Set the serial baud-rate and other
# configuration parameters.
speed = 115200
databits = 8
parity = none
stopbits = 1
# Whether to use KISS flow-control.
# This is useful for modems with a
# small internal packet buffer.
flow_control = false
# Set the modem preamble. A 150ms
# preamble should be a reasonable
# default, but may need to be
# increased for radios with slow-
# opening squelch and long TX/RX
# turnaround
preamble = 150
# Set the modem TX tail. In most
# cases this should be kept as low
# as possible to not waste airtime.
txtail = 10
# Configure CDMA parameters. These
# settings are reasonable defaults.
persistence = 200
slottime = 20
'''
if __name__ == "__main__":
main()
main()
+4
View File
@@ -51,6 +51,10 @@ def program_setup(configdir, dispall=False, verbosity = 0):
print(" {n}".format(n=ifstat["name"]))
print("\tStatus: {ss}".format(ss=ss))
if "i2p_b32" in ifstat:
print("\tI2P B32: {ep}".format(ep=str(ifstat["i2p_b32"])))
if clients != None:
print("\t"+clients_string)
print("\tRX: {rxb}\n\tTX: {txb}".format(rxb=size_str(ifstat["rxb"]), txb=size_str(ifstat["txb"])))
+10
View File
@@ -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
@@ -105,6 +109,11 @@ def rand():
return result
def hexrep(data, delimit=True):
try:
iter(data)
except TypeError:
data = [data]
delimiter = ":"
if not delimit:
delimiter = ""
@@ -120,4 +129,5 @@ def panic():
os._exit(255)
def exit():
print("")
sys.exit(0)
+1 -1
View File
@@ -1 +1 @@
__version__ = "0.2.7"
__version__ = "0.3.3"
+20
View File
@@ -0,0 +1,20 @@
Copyright (c) 2018 Viktor Villainov
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+25
View File
@@ -0,0 +1,25 @@
"""
A modern asynchronous library for building I2P applications.
"""
from .__version__ import (
__title__, __description__, __url__, __version__,
__author__, __author_email__, __license__, __copyright__
)
from .sam import Destination, PrivateKey
from .aiosam import (
get_sam_socket, dest_lookup, new_destination,
create_session, stream_connect, stream_accept,
Session, StreamConnection, StreamAcceptor
)
from .tunnel import ClientTunnel, ServerTunnel
from .utils import get_sam_address
from .exceptions import (
CantReachPeer, DuplicatedDest, DuplicatedId, I2PError,
InvalidId, InvalidKey, KeyNotFound, PeerNotFound, Timeout,
)
+8
View File
@@ -0,0 +1,8 @@
__title__ = 'i2plib'
__description__ = 'A modern asynchronous library for building I2P applications.'
__url__ = 'https://github.com/l-n-s/i2plib'
__version__ = '0.0.14'
__author__ = 'Viktor Villainov'
__author_email__ = 'supervillain@riseup.net'
__license__ = 'MIT'
__copyright__ = 'Copyright 2018 Viktor Villainov'
+258
View File
@@ -0,0 +1,258 @@
import asyncio
from . import sam
from . import exceptions
from . import utils
from .log import logger
def parse_reply(data):
if not data:
raise ConnectionAbortedError("Empty response: SAM API went offline")
try:
msg = sam.Message(data.decode().strip())
logger.debug("SAM reply: "+str(msg))
except:
raise ConnectionAbortedError("Invalid SAM response")
return msg
async def get_sam_socket(sam_address=sam.DEFAULT_ADDRESS, loop=None):
"""A couroutine used to create a new SAM socket.
:param sam_address: (optional) SAM API address
:param loop: (optional) event loop instance
:return: A (reader, writer) pair
"""
reader, writer = await asyncio.open_connection(*sam_address, loop=loop)
writer.write(sam.hello("3.1", "3.1"))
reply = parse_reply(await reader.readline())
if reply.ok:
return (reader, writer)
else:
writer.close()
raise exceptions.SAM_EXCEPTIONS[reply["RESULT"]]()
async def dest_lookup(domain, sam_address=sam.DEFAULT_ADDRESS,
loop=None):
"""A coroutine used to lookup a full I2P destination by .i2p domain or
.b32.i2p address.
:param domain: Address to be resolved, can be a .i2p domain or a .b32.i2p
address.
:param sam_address: (optional) SAM API address
:param loop: (optional) Event loop instance
:return: An instance of :class:`Destination`
"""
reader, writer = await get_sam_socket(sam_address, loop)
writer.write(sam.naming_lookup(domain))
reply = parse_reply(await reader.readline())
writer.close()
if reply.ok:
return sam.Destination(reply["VALUE"])
else:
raise exceptions.SAM_EXCEPTIONS[reply["RESULT"]]()
async def new_destination(sam_address=sam.DEFAULT_ADDRESS, loop=None,
sig_type=sam.Destination.default_sig_type):
"""A coroutine used to generate a new destination with a private key of a
chosen signature type.
:param sam_address: (optional) SAM API address
:param loop: (optional) Event loop instance
:param sig_type: (optional) Signature type
:return: An instance of :class:`Destination`
"""
reader, writer = await get_sam_socket(sam_address, loop)
writer.write(sam.dest_generate(sig_type))
reply = parse_reply(await reader.readline())
writer.close()
return sam.Destination(reply["PRIV"], has_private_key=True)
async def create_session(session_name, sam_address=sam.DEFAULT_ADDRESS,
loop=None, style="STREAM",
signature_type=sam.Destination.default_sig_type,
destination=None, options={}):
"""A coroutine used to create a new SAM session.
:param session_name: Session nick name
:param sam_address: (optional) SAM API address
:param loop: (optional) Event loop instance
:param style: (optional) Session style, can be STREAM, DATAGRAM, RAW
:param signature_type: (optional) If the destination is TRANSIENT, this
signature type is used
:param destination: (optional) Destination to use in this session. Can be
a base64 encoded string, :class:`Destination`
instance or None. TRANSIENT destination is used when it
is None.
:param options: (optional) A dict object with i2cp options
:return: A (reader, writer) pair
"""
logger.debug("Creating session {}".format(session_name))
if destination:
if type(destination) == sam.Destination:
destination = destination
else:
destination = sam.Destination(
destination, has_private_key=True)
dest_string = destination.private_key.base64
else:
dest_string = sam.TRANSIENT_DESTINATION
options = " ".join(["{}={}".format(k, v) for k, v in options.items()])
reader, writer = await get_sam_socket(sam_address, loop)
writer.write(sam.session_create(
style, session_name, dest_string, options))
reply = parse_reply(await reader.readline())
if reply.ok:
if not destination:
destination = sam.Destination(
reply["DESTINATION"], has_private_key=True)
logger.debug(destination.base32)
logger.debug("Session created {}".format(session_name))
return (reader, writer)
else:
writer.close()
raise exceptions.SAM_EXCEPTIONS[reply["RESULT"]]()
async def stream_connect(session_name, destination,
sam_address=sam.DEFAULT_ADDRESS, loop=None):
"""A coroutine used to connect to a remote I2P destination.
:param session_name: Session nick name
:param destination: I2P destination to connect to
:param sam_address: (optional) SAM API address
:param loop: (optional) Event loop instance
:return: A (reader, writer) pair
"""
logger.debug("Connecting stream {}".format(session_name))
if isinstance(destination, str) and not destination.endswith(".i2p"):
destination = sam.Destination(destination)
elif isinstance(destination, str):
destination = await dest_lookup(destination, sam_address, loop)
reader, writer = await get_sam_socket(sam_address, loop)
writer.write(sam.stream_connect(session_name, destination.base64,
silent="false"))
reply = parse_reply(await reader.readline())
if reply.ok:
logger.debug("Stream connected {}".format(session_name))
return (reader, writer)
else:
writer.close()
raise exceptions.SAM_EXCEPTIONS[reply["RESULT"]]()
async def stream_accept(session_name, sam_address=sam.DEFAULT_ADDRESS,
loop=None):
"""A coroutine used to accept a connection from the I2P network.
:param session_name: Session nick name
:param sam_address: (optional) SAM API address
:param loop: (optional) Event loop instance
:return: A (reader, writer) pair
"""
reader, writer = await get_sam_socket(sam_address, loop)
writer.write(sam.stream_accept(session_name, silent="false"))
reply = parse_reply(await reader.readline())
if reply.ok:
return (reader, writer)
else:
writer.close()
raise exceptions.SAM_EXCEPTIONS[reply["RESULT"]]()
### Context managers
class Session:
"""Async SAM session context manager.
:param session_name: Session nick name
:param sam_address: (optional) SAM API address
:param loop: (optional) Event loop instance
:param style: (optional) Session style, can be STREAM, DATAGRAM, RAW
:param signature_type: (optional) If the destination is TRANSIENT, this
signature type is used
:param destination: (optional) Destination to use in this session. Can be
a base64 encoded string, :class:`Destination`
instance or None. TRANSIENT destination is used when it
is None.
:param options: (optional) A dict object with i2cp options
:return: :class:`Session` object
"""
def __init__(self, session_name, sam_address=sam.DEFAULT_ADDRESS,
loop=None, style="STREAM",
signature_type=sam.Destination.default_sig_type,
destination=None, options={}):
self.session_name = session_name
self.sam_address = sam_address
self.loop = loop
self.style = style
self.signature_type = signature_type
self.destination = destination
self.options = options
async def __aenter__(self):
self.reader, self.writer = await create_session(self.session_name,
sam_address=self.sam_address, loop=self.loop, style=self.style,
signature_type=self.signature_type,
destination=self.destination, options=self.options)
return self
async def __aexit__(self, exc_type, exc, tb):
### TODO handle exceptions
self.writer.close()
class StreamConnection:
"""Async stream connection context manager.
:param session_name: Session nick name
:param destination: I2P destination to connect to
:param sam_address: (optional) SAM API address
:param loop: (optional) Event loop instance
:return: :class:`StreamConnection` object
"""
def __init__(self, session_name, destination,
sam_address=sam.DEFAULT_ADDRESS, loop=None):
self.session_name = session_name
self.sam_address = sam_address
self.loop = loop
self.destination = destination
async def __aenter__(self):
self.reader, self.writer = await stream_connect(self.session_name,
self.destination, sam_address=self.sam_address, loop=self.loop)
self.read = self.reader.read
self.write = self.writer.write
return self
async def __aexit__(self, exc_type, exc, tb):
### TODO handle exceptions
self.writer.close()
class StreamAcceptor:
"""Async stream acceptor context manager.
:param session_name: Session nick name
:param sam_address: (optional) SAM API address
:param loop: (optional) Event loop instance
:return: :class:`StreamAcceptor` object
"""
def __init__(self, session_name, sam_address=sam.DEFAULT_ADDRESS,
loop=None):
self.session_name = session_name
self.sam_address = sam_address
self.loop = loop
async def __aenter__(self):
self.reader, self.writer = await stream_accept(self.session_name,
sam_address=self.sam_address, loop=self.loop)
self.read = self.reader.read
self.write = self.writer.write
return self
async def __aexit__(self, exc_type, exc, tb):
### TODO handle exceptions
self.writer.close()
+44
View File
@@ -0,0 +1,44 @@
# SAM exceptions
class SAMException(IOError):
"""Base class for SAM exceptions"""
class CantReachPeer(SAMException):
"""The peer exists, but cannot be reached"""
class DuplicatedDest(SAMException):
"""The specified Destination is already in use"""
class DuplicatedId(SAMException):
"""The nickname is already associated with a session"""
class I2PError(SAMException):
"""A generic I2P error"""
class InvalidId(SAMException):
"""STREAM SESSION ID doesn't exist"""
class InvalidKey(SAMException):
"""The specified key is not valid (bad format, etc.)"""
class KeyNotFound(SAMException):
"""The naming system can't resolve the given name"""
class PeerNotFound(SAMException):
"""The peer cannot be found on the network"""
class Timeout(SAMException):
"""The peer cannot be found on the network"""
SAM_EXCEPTIONS = {
"CANT_REACH_PEER": CantReachPeer,
"DUPLICATED_DEST": DuplicatedDest,
"DUPLICATED_ID": DuplicatedId,
"I2P_ERROR": I2PError,
"INVALID_ID": InvalidId,
"INVALID_KEY": InvalidKey,
"KEY_NOT_FOUND": KeyNotFound,
"PEER_NOT_FOUND": PeerNotFound,
"TIMEOUT": Timeout,
}
+5
View File
@@ -0,0 +1,5 @@
"""Logging configuration."""
import logging
# Name the logger after the package.
logger = logging.getLogger(__package__)
+147
View File
@@ -0,0 +1,147 @@
from base64 import b64decode, b64encode, b32encode
from hashlib import sha256
import struct
import re
I2P_B64_CHARS = "-~"
def i2p_b64encode(x):
"""Encode I2P destination"""
return b64encode(x, altchars=I2P_B64_CHARS.encode()).decode()
def i2p_b64decode(x):
"""Decode I2P destination"""
return b64decode(x, altchars=I2P_B64_CHARS, validate=True)
SAM_BUFSIZE = 4096
DEFAULT_ADDRESS = ("127.0.0.1", 7656)
DEFAULT_MIN_VER = "3.1"
DEFAULT_MAX_VER = "3.1"
TRANSIENT_DESTINATION = "TRANSIENT"
VALID_BASE32_ADDRESS = re.compile(r"^([a-zA-Z0-9]{52}).b32.i2p$")
VALID_BASE64_ADDRESS = re.compile(r"^([a-zA-Z0-9-~=]{516,528})$")
class Message(object):
"""Parse SAM message to an object"""
def __init__(self, s):
self.opts = {}
if type(s) != str:
self._reply_string = s.decode().strip()
else:
self._reply_string = s
self.cmd, self.action, opts = self._reply_string.split(" ", 2)
for v in opts.split(" "):
data = v.split("=", 1) if "=" in v else (v, True)
self.opts[data[0]] = data[1]
def __getitem__(self, key):
return self.opts[key]
@property
def ok(self):
return self["RESULT"] == "OK"
def __repr__(self):
return self._reply_string
# SAM request messages
def hello(min_version, max_version):
return "HELLO VERSION MIN={} MAX={}\n".format(min_version,
max_version).encode()
def session_create(style, session_id, destination, options=""):
return "SESSION CREATE STYLE={} ID={} DESTINATION={} {}\n".format(
style, session_id, destination, options).encode()
def stream_connect(session_id, destination, silent="false"):
return "STREAM CONNECT ID={} DESTINATION={} SILENT={}\n".format(
session_id, destination, silent).encode()
def stream_accept(session_id, silent="false"):
return "STREAM ACCEPT ID={} SILENT={}\n".format(session_id, silent).encode()
def stream_forward(session_id, port, options=""):
return "STREAM FORWARD ID={} PORT={} {}\n".format(
session_id, port, options).encode()
def naming_lookup(name):
return "NAMING LOOKUP NAME={}\n".format(name).encode()
def dest_generate(signature_type):
return "DEST GENERATE SIGNATURE_TYPE={}\n".format(signature_type).encode()
class Destination(object):
"""I2P destination
https://geti2p.net/spec/common-structures#destination
:param data: (optional) Base64 encoded data or binary data
:param path: (optional) A path to a file with binary data
:param has_private_key: (optional) Does data have a private key?
"""
ECDSA_SHA256_P256 = 1
ECDSA_SHA384_P384 = 2
ECDSA_SHA512_P521 = 3
EdDSA_SHA512_Ed25519 = 7
default_sig_type = EdDSA_SHA512_Ed25519
_pubkey_size = 256
_signkey_size = 128
_min_cert_size = 3
def __init__(self, data=None, path=None, has_private_key=False):
#: Binary destination
self.data = bytes()
#: Base64 encoded destination
self.base64 = ""
#: :class:`RNS.vendor.i2plib.PrivateKey` instance or None
self.private_key = None
if path:
with open(path, "rb") as f: data = f.read()
if data and has_private_key:
self.private_key = PrivateKey(data)
cert_len = struct.unpack("!H", self.private_key.data[385:387])[0]
data = self.private_key.data[:387+cert_len]
if not data:
raise Exception("Can't create a destination with no data")
self.data = data if type(data) == bytes else i2p_b64decode(data)
self.base64 = data if type(data) == str else i2p_b64encode(data)
def __repr__(self):
return "<Destination: {}>".format(self.base32)
@property
def base32(self):
"""Base32 destination hash of this destination"""
desthash = sha256(self.data).digest()
return b32encode(desthash).decode()[:52].lower()
class PrivateKey(object):
"""I2P private key
https://geti2p.net/spec/common-structures#keysandcert
:param data: Base64 encoded data or binary data
"""
def __init__(self, data):
#: Binary private key
self.data = data if type(data) == bytes else i2p_b64decode(data)
#: Base64 encoded private key
self.base64 = data if type(data) == str else i2p_b64encode(data)
+202
View File
@@ -0,0 +1,202 @@
import logging
import asyncio
import argparse
from . import sam
from . import aiosam
from . import utils
from .log import logger
BUFFER_SIZE = 65536
async def proxy_data(reader, writer):
"""Proxy data from reader to writer"""
try:
while True:
data = await reader.read(BUFFER_SIZE)
if not data:
break
writer.write(data)
except Exception as e:
logger.debug('proxy_data_task exception {}'.format(e))
finally:
try:
writer.close()
except RuntimeError:
pass
logger.debug('close connection')
class I2PTunnel(object):
"""Base I2P Tunnel object, not to be used directly
:param local_address: A local address to use for a tunnel.
E.g. ("127.0.0.1", 6668)
:param destination: (optional) Destination to use for this tunnel. Can be
a base64 encoded string, :class:`Destination`
instance or None. A new destination is created when it
is None.
:param session_name: (optional) Session nick name. A new session nickname is
generated if not specified.
:param options: (optional) A dict object with i2cp options
:param loop: (optional) Event loop instance
:param sam_address: (optional) SAM API address
"""
def __init__(self, local_address, destination=None, session_name=None,
options={}, loop=None, sam_address=sam.DEFAULT_ADDRESS):
self.local_address = local_address
self.destination = destination
self.session_name = session_name or utils.generate_session_id()
self.options = options
self.loop = loop
self.sam_address = sam_address
async def _pre_run(self):
if not self.destination:
self.destination = await aiosam.new_destination(
sam_address=self.sam_address, loop=self.loop)
_, self.session_writer = await aiosam.create_session(
self.session_name, style=self.style, options=self.options,
sam_address=self.sam_address,
loop=self.loop, destination=self.destination)
def stop(self):
"""Stop the tunnel"""
self.session_writer.close()
class ClientTunnel(I2PTunnel):
"""Client tunnel, a subclass of tunnel.I2PTunnel
If you run a client tunnel with a local address ("127.0.0.1", 6668) and
a remote destination "irc.echelon.i2p", all connections to 127.0.0.1:6668
will be proxied to irc.echelon.i2p.
:param remote_destination: Remote I2P destination, can be either .i2p
domain, .b32.i2p address, base64 destination or
:class:`Destination` instance
"""
def __init__(self, remote_destination, *args, **kwargs):
super().__init__(*args, **kwargs)
self.style = "STREAM"
self.remote_destination = remote_destination
async def run(self):
"""A coroutine used to run the tunnel"""
await self._pre_run()
async def handle_client(client_reader, client_writer):
"""Handle local client connection"""
remote_reader, remote_writer = await aiosam.stream_connect(
self.session_name, self.remote_destination,
sam_address=self.sam_address, loop=self.loop)
asyncio.ensure_future(proxy_data(remote_reader, client_writer),
loop=self.loop)
asyncio.ensure_future(proxy_data(client_reader, remote_writer),
loop=self.loop)
self.server = await asyncio.start_server(handle_client, *self.local_address, loop=self.loop)
def stop(self):
super().stop()
self.server.close()
class ServerTunnel(I2PTunnel):
"""Server tunnel, a subclass of tunnel.I2PTunnel
If you want to expose a local service 127.0.0.1:80 to the I2P network, run
a server tunnel with a local address ("127.0.0.1", 80). If you don't
provide a private key or a session name, it will use a TRANSIENT
destination.
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.style = "STREAM"
async def run(self):
"""A coroutine used to run the tunnel"""
await self._pre_run()
async def handle_client(incoming, client_reader, client_writer):
# data and dest may come in one chunk
dest, data = incoming.split(b"\n", 1)
remote_destination = sam.Destination(dest.decode())
logger.debug("{} client connected: {}.b32.i2p".format(
self.session_name, remote_destination.base32))
try:
remote_reader, remote_writer = await asyncio.wait_for(
asyncio.open_connection(
host=self.local_address[0],
port=self.local_address[1], loop=self.loop),
timeout=5, loop=self.loop)
if data: remote_writer.write(data)
asyncio.ensure_future(proxy_data(remote_reader, client_writer),
loop=self.loop)
asyncio.ensure_future(proxy_data(client_reader, remote_writer),
loop=self.loop)
except ConnectionRefusedError:
client_writer.close()
async def server_loop():
try:
while True:
client_reader, client_writer = await aiosam.stream_accept(
self.session_name, sam_address=self.sam_address,
loop=self.loop)
incoming = await client_reader.read(BUFFER_SIZE)
asyncio.ensure_future(handle_client(
incoming, client_reader, client_writer), loop=self.loop)
except asyncio.CancelledError:
pass
self.server_loop = asyncio.ensure_future(server_loop(), loop=self.loop)
def stop(self):
super().stop()
self.server_loop.cancel()
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('type', metavar="TYPE", choices=('server', 'client'),
help="Tunnel type (server or client)")
parser.add_argument('address', metavar="ADDRESS",
help="Local address (e.g. 127.0.0.1:8000)")
parser.add_argument('--debug', '-d', action='store_true',
help='Debugging')
parser.add_argument('--key', '-k', default='', metavar='PRIVATE_KEY',
help='Path to private key file')
parser.add_argument('--destination', '-D', default='',
metavar='DESTINATION', help='Remote destination')
args = parser.parse_args()
SAM_ADDRESS = utils.get_sam_address()
logging.basicConfig(level=logging.DEBUG if args.debug else logging.INFO)
loop = asyncio.get_event_loop()
loop.set_debug(args.debug)
if args.key:
destination = sam.Destination(path=args.key, has_private_key=True)
else:
destination = None
local_address = utils.address_from_string(args.address)
if args.type == "client":
tunnel = ClientTunnel(args.destination, local_address, loop=loop,
destination=destination, sam_address=SAM_ADDRESS)
elif args.type == "server":
tunnel = ServerTunnel(local_address, loop=loop, destination=destination,
sam_address=SAM_ADDRESS)
asyncio.ensure_future(tunnel.run(), loop=loop)
try:
loop.run_forever()
except KeyboardInterrupt:
tunnel.stop()
finally:
loop.stop()
loop.close()
+42
View File
@@ -0,0 +1,42 @@
import socket
import os
import random
import string
from . import sam
def get_free_port():
"""Get a free port on your local host"""
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(('', 0))
free_port = s.getsockname()[1]
s.close()
return free_port
def is_address_accessible(address):
"""Check if address is accessible or down"""
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
is_accessible = s.connect_ex(address) == 0
s.close()
return is_accessible
def address_from_string(address_string):
"""Address tuple from host:port string"""
address = address_string.split(":")
return (address[0], int(address[1]))
def get_sam_address():
"""
Get SAM address from environment variable I2P_SAM_ADDRESS, or use a default
value
"""
value = os.getenv("I2P_SAM_ADDRESS")
return address_from_string(value) if value else sam.DEFAULT_ADDRESS
def generate_session_id(length=6):
"""Generate random session id"""
rand = random.SystemRandom()
sid = [rand.choice(string.ascii_letters) for _ in range(length)]
return "reticulum-" + "".join(sid)
+38
View File
@@ -0,0 +1,38 @@
def get_platform():
from os import environ
if "ANDROID_ARGUMENT" in environ:
return "android"
elif "ANDROID_ROOT" in environ:
return "android"
else:
import sys
return sys.platform
def is_darwin():
if get_platform() == "darwin":
return True
else:
return False
def is_android():
if get_platform() == "android":
return True
else:
return False
def is_windows():
if str(get_platform()).startswith("win"):
return True
else:
return False
def platform_checks():
if is_windows():
import sys
if sys.version_info.major >= 3 and sys.version_info.minor >= 8:
pass
else:
import RNS
RNS.log("On Windows, Reticulum requires Python 3.8 or higher.", RNS.LOG_ERROR)
RNS.log("Please update Python to run Reticulum.", RNS.LOG_ERROR)
RNS.panic()
+252 -128
View File
@@ -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 -1
View File
@@ -1,4 +1,4 @@
# Sphinx build info version 1
# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done.
config: 55e1a358a797ca9390d1b4f0e18f7ba3
config: 8cd01657672a2b3a4d1c8ecc92b32a11
tags: 645f666f9bcd5a90fca523b33c5a78b7
@@ -6,6 +6,7 @@ The best way to get started with the Reticulum Network Stack depends on what
you want to do. This guide will outline sensible starting paths for different
scenarios.
Try Using a Reticulum-based Program
=============================================
If you simply want to try using a program built with Reticulum, you can take
@@ -16,7 +17,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:
@@ -29,6 +30,10 @@ You can install Nomad Network via pip:
# ... and run
nomadnet
**Please Note**: If this is the very first time you use pip to install a program
on your system, you might need to reboot your system for your program to become
available. If you get a "command not found" error or similar when running the
program, reboot your system and try again.
Using the Included Utilities
@@ -44,11 +49,13 @@ network status and connectivity.
To learn more about these utility programs, have a look at the
:ref:`Using Reticulum on Your System<using-main>` chapter of this manual.
Creating a Network With Reticulum
=============================================
To create a network, you will need to specify one or more *interfaces* for
Reticulum to use. This is done in the Reticulum configuration file, which by
default is located at ``~/.reticulum/config``.
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 +84,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 +134,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.
+276 -73
View File
@@ -18,6 +18,238 @@ 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
# 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
# Configure global discovery
group_id = custom_network_name
discovery_scope = global
# Other configuration options
discovery_port = 48555
data_port = 49555
.. _interfaces-i2p:
I2P Interface
=============
The I2P interface lets you connect Reticulum instances over the
`Invisible Internet Protocol <https://i2pd.website>`_. This can be
especially useful in cases where you want to host a globally reachable
Reticulum instance, but do not have access to any public IP addresses,
have a frequently changing IP address, or have firewalls blocking
inbound traffic.
Using the I2P interface, you will get a globally reachable, portable
and persistent I2P address that your Reticulum instance can be reached
at.
To use the I2P interface, you must have an I2P router running
on your system. The easiest way to acheive this is to download and
install the `latest release <https://github.com/PurpleI2P/i2pd/releases/latest>`_
of the ``ì2pd`` package. For more details about I2P, see the
`geti2p.net website <https://geti2p.net/en/about/intro>`_.`
When an I2P router is running on your system, you can simply add
an I2P interface to reticulum:
.. code::
[[I2P]]
type = I2PInterface
interface_enabled = yes
connectable = yes
On the first start, Reticulum will generate a new I2P address for the
interface and start listening for inbound traffic on it. This can take
a while the first time, especially if your I2P router was also just
started, and is not yet well-connected to the I2P network. When ready,
you should see I2P base32 address printed to your log file. You can
also inspect the status of the interface using the ``rnstatus`` utility.
To connect to other Reticulum instances over I2P, just add a comma-separated
list of I2P base32 addresses to the ``peers`` option of the interface:
.. code::
[[I2P]]
type = I2PInterface
interface_enabled = yes
connectable = yes
peers = 5urvjicpzi7q3ybztsef4i5ow2aq4soktfj7zedz53s47r54jnqq.b32.i2p
It can take anywhere from a few seconds to a few minutes to establish
I2P connections to the desired peers, so Reticulum handles the process
in the background, and will output relevant events to the log.
**Please Note!** While the I2P interface is the simplest way to use
Reticulum over I2P, it is also possible to tunnel the TCP server and
client interfaces over I2P manually. This can be useful in situations
where more control is needed, but requires manual tunnel setup through
the I2P daemon configuration.
It is important to note that the two methods are *interchangably compatible*.
You can use the I2PInterface to connect to a TCPServerInterface that
was manually tunneled over I2P, for example. This offers a high degree
of flexibility in network setup, while retaining ease of use in simpler
use-cases.
.. _interfaces-tcps:
TCP Server Interface
====================
The TCP Server interface is suitable for allowing other peers to connect over
the Internet or private IP networks. When a TCP server interface has been
configured, other Reticulum peers can connect to it with a TCP Client interface.
.. code::
# This example demonstrates a TCP server interface.
# It will listen for incoming connections on the
# specified IP address and port number.
[[TCP Server Interface]]
type = TCPServerInterface
interface_enabled = True
# This configuration will listen on all IP
# interfaces on port 4242
listen_ip = 0.0.0.0
listen_port = 4242
# Alternatively you can bind to a specific IP
# listen_ip = 10.0.0.88
# listen_port = 4242
# Or a specific network device
# device = eth0
# port = 4242
**Please Note!** The TCP interfaces support tunneling over I2P, but to do so reliably,
you must use the i2p_tunneled option:
.. code::
[[TCP Server on I2P]]
type = TCPServerInterface
interface_enabled = yes
listen_ip = 127.0.0.1
listen_port = 5001
i2p_tunneled = yes
.. _interfaces-tcpc:
TCP Client Interface
====================
To connect to a TCP server interface, you would naturally use the TCP client
interface. Many TCP Client interfaces from different peers can connect to the
same TCP Server interface at the same time.
.. code::
# Here's an example of a TCP Client interface. The
# target_host can either be an IP address or a hostname.
[[TCP Client Interface]]
type = TCPClientInterface
interface_enabled = True
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
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.
**Please Note!** The TCP interfaces support tunneling over I2P, but to do so reliably,
you must use the i2p_tunneled option:
.. code::
[[TCP Client over I2P]]
type = TCPClientInterface
interface_enabled = yes
target_host = 127.0.0.1
target_port = 5001
i2p_tunneled = yes
.. _interfaces-udp:
UDP Interface
@@ -28,6 +260,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.
@@ -40,7 +278,7 @@ pre-existing LAN.
[[Default UDP Interface]]
type = UDPInterface
interface_enabled = True
outgoing = True
listen_ip = 0.0.0.0
listen_port = 4242
forward_ip = 255.255.255.255
@@ -48,9 +286,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,64 +314,6 @@ pre-existing LAN.
# forward_ip = 10.55.0.16
# forward_port = 4242
.. _interfaces-tcps:
TCP Server Interface
====================
The TCP Server interface is suitable for allowing other peers to connect over
the Internet or private IP networks. When a TCP server interface has been
configured, other Reticulum peers can connect to it with a TCP Client interface.
.. code::
# This example demonstrates a TCP server interface.
# It will listen for incoming connections on the
# specified IP address and port number.
[[TCP Server Interface]]
type = TCPServerInterface
interface_enabled = True
outgoing = True
# This configuration will listen on all IP
# interfaces on port 4242
listen_ip = 0.0.0.0
listen_port = 4242
# Alternatively you can bind to a specific IP
# listen_ip = 10.0.0.88
# listen_port = 4242
# Or a specific network device
# device = eth0
# port = 4242
.. _interfaces-tcpc:
TCP Client Interface
====================
To connect to a TCP server interface, you would naturally use the TCP client
interface. Many TCP Client interfaces from different peers can connect to the
same TCP Server interface at the same time.
.. code::
# Here's an example of a TCP Client interface. The
# target_host can either be an IP address or a hostname.
[[TCP Client Interface]]
type = TCPClientInterface
interface_enabled = True
outgoing = True
target_host = 127.0.0.1
target_port = 4242
.. _interfaces-rnode:
@@ -156,11 +334,6 @@ can be used, and offers full control over LoRa parameters.
# Enable interface if you want use it!
interface_enabled = True
# Allow transmit on interface. Setting
# this to false will create a listen-
# only interface.
outgoing = true
# Serial port for the device
port = /dev/ttyUSB0
@@ -211,7 +384,6 @@ directly over a wire-pair, or for using devices such as data radios and lasers.
[[Serial Interface]]
type = SerialInterface
interface_enabled = True
outgoing = True
# Serial port for the device
port = /dev/ttyUSB0
@@ -238,7 +410,6 @@ for station identification purposes.
[[Packet Radio KISS Interface]]
type = KISSInterface
interface_enabled = True
outgoing = true
# Serial port for the device
port = /dev/ttyUSB1
@@ -309,9 +480,6 @@ beaconing functionality described above.
# Enable interface if you want use it!
interface_enabled = True
# Allow transmit on interface.
outgoing = True
# Serial port for the device
port = /dev/ttyUSB2
@@ -343,4 +511,39 @@ beaconing functionality described above.
# Whether to use KISS flow-control.
# This is useful for modems with a
# small internal packet buffer.
flow_control = false
flow_control = false
.. _interfaces-options:
Common Interface Options
========================
A number of general options can be used to control various
aspects of interface behaviour.
The ``interface_enabled`` option tells Reticulum whether or not
to bring up the interface. Defaults to ``False``. For any
interface to be brought up, the ``interface_enabled`` option
must be set to ``True`` or ``Yes``.
The ``outgoing`` option sets whether an interface is allowed
to transmit. Defaults to ``True``. If set to ``False`` the
interface will only receive data, and never transmit.
The ``interface_mode`` option allows selecting the high-level
behaviour of the interface from a number of options.
- The default value is ``full``. In this mode, all discovery,
meshing and transpor functionality is available.
- In the ``access_point`` (or shorthand ``ap``) mode, the
interface will operate as a network access point. In this
mode, announces will not be automatically broadcasted on
the interface, and paths to destinations on the interface
will have a much shorter expiry time. This mode is useful
for creating interfaces that are mostly quiet, unless when
someone is actually using them. An example of this could
be a radio interface serving a wide area, where users are
expected to connect momentarily, use the network, and then
disappear again.
+17 -17
View File
@@ -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 its
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 its
@@ -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 destinations 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
View File
@@ -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
+8 -8
View File
@@ -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 -1
View File
@@ -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.3 beta',
LANGUAGE: 'None',
COLLAPSE_INDEX: false,
BUILDER: 'html',
+53 -6
View File
@@ -5,7 +5,7 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Code Examples &#8212; Reticulum Network Stack 0.2.7 beta documentation</title>
<title>Code Examples &#8212; Reticulum Network Stack 0.3.3 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> &#187;</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.3 beta documentation</a> &#187;</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">&quot;example_utilities&quot;</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">&quot;Peach&quot;</span><span class="p">,</span> <span class="s2">&quot;Quince&quot;</span><span class="p">,</span> <span class="s2">&quot;Date palm&quot;</span><span class="p">,</span> <span class="s2">&quot;Tangerine&quot;</span><span class="p">,</span> <span class="s2">&quot;Pomelo&quot;</span><span class="p">,</span> <span class="s2">&quot;Carambola&quot;</span><span class="p">,</span> <span class="s2">&quot;Grape&quot;</span><span class="p">]</span>
<span class="n">fruits</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&quot;Peach&quot;</span><span class="p">,</span> <span class="s2">&quot;Quince&quot;</span><span class="p">,</span> <span class="s2">&quot;Date&quot;</span><span class="p">,</span> <span class="s2">&quot;Tangerine&quot;</span><span class="p">,</span> <span class="s2">&quot;Pomelo&quot;</span><span class="p">,</span> <span class="s2">&quot;Carambola&quot;</span><span class="p">,</span> <span class="s2">&quot;Grape&quot;</span><span class="p">]</span>
<span class="n">noble_gases</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&quot;Helium&quot;</span><span class="p">,</span> <span class="s2">&quot;Neon&quot;</span><span class="p">,</span> <span class="s2">&quot;Argon&quot;</span><span class="p">,</span> <span class="s2">&quot;Krypton&quot;</span><span class="p">,</span> <span class="s2">&quot;Xenon&quot;</span><span class="p">,</span> <span class="s2">&quot;Radon&quot;</span><span class="p">,</span> <span class="s2">&quot;Oganesson&quot;</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">&quot;Received packet from echo client, proof sent&quot;</span><span class="p">)</span>
<span class="n">reception_stats</span> <span class="o">=</span> <span class="s2">&quot;&quot;</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">&quot; [RSSI &quot;</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">&quot; dBm]&quot;</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">&quot; [SNR &quot;</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">&quot; dBm]&quot;</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">&quot; [RSSI &quot;</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">&quot; dBm]&quot;</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">&quot; [SNR &quot;</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">&quot; dB]&quot;</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;Received packet from echo client, proof sent&quot;</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">&gt;=</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">&quot; milliseconds&quot;</span>
<span class="n">reception_stats</span> <span class="o">=</span> <span class="s2">&quot;&quot;</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">&quot; [RSSI &quot;</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">&quot; dBm]&quot;</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">&quot; [SNR &quot;</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">&quot; dB]&quot;</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">&quot; [RSSI &quot;</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">&quot; dBm]&quot;</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">&quot; [SNR &quot;</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">&quot; dB]&quot;</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span>
<span class="s2">&quot;Valid reply received from &quot;</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">&quot;, round-trip time is &quot;</span><span class="o">+</span><span class="n">rttstring</span>
<span class="s2">&quot;, round-trip time is &quot;</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> &#187;</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.3 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">Code Examples</a></li>
</ul>
</div>
+3 -3
View File
@@ -5,7 +5,7 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Index &#8212; Reticulum Network Stack 0.2.7 beta documentation</title>
<title>Index &#8212; Reticulum Network Stack 0.3.3 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> &#187;</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.3 beta documentation</a> &#187;</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> &#187;</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.3 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">Index</a></li>
</ul>
</div>
+70 -5
View File
@@ -5,7 +5,7 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Getting Started Fast &#8212; Reticulum Network Stack 0.2.7 beta documentation</title>
<title>Getting Started Fast &#8212; Reticulum Network Stack 0.3.3 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> &#187;</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.3 beta documentation</a> &#187;</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>
@@ -63,6 +63,10 @@ in the development for the messaging and information-sharing protocol
<span class="n">nomadnet</span>
</pre></div>
</div>
<p><strong>Please Note</strong>: If this is the very first time you use pip to install a program
on your system, you might need to reboot your system for your program to become
available. If you get a “command not found” error or similar when running the
program, reboot your system and try again.</p>
</div>
<div class="section" id="using-the-included-utilities">
<h2>Using the Included Utilities<a class="headerlink" href="#using-the-included-utilities" title="Permalink to this headline"></a></h2>
@@ -79,7 +83,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 +106,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 +152,60 @@ dont use pip, but try this recipe:</p>
<p>When you have experimented with the basic examples, its 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">&quot;aarch64-linux-android&quot;</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 +223,8 @@ dont 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 +268,7 @@ dont 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> &#187;</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.3 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">Getting Started Fast</a></li>
</ul>
</div>
+15 -5
View File
@@ -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 &#8212; Reticulum Network Stack 0.2.7 beta documentation</title>
<title>Reticulum Network Stack Manual &#8212; Reticulum Network Stack 0.3.3 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> &#187;</li>
<li class="nav-item nav-item-0"><a href="#">Reticulum Network Stack 0.3.3 beta documentation</a> &#187;</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,13 +88,16 @@ 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#udp-interface">UDP Interface</a></li>
<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#i2p-interface">I2P Interface</a></li>
<li class="toctree-l2"><a class="reference internal" href="interfaces.html#tcp-server-interface">TCP Server Interface</a></li>
<li class="toctree-l2"><a class="reference internal" href="interfaces.html#tcp-client-interface">TCP Client Interface</a></li>
<li class="toctree-l2"><a class="reference internal" href="interfaces.html#udp-interface">UDP Interface</a></li>
<li class="toctree-l2"><a class="reference internal" href="interfaces.html#rnode-lora-interface">RNode LoRa Interface</a></li>
<li class="toctree-l2"><a class="reference internal" href="interfaces.html#serial-interface">Serial Interface</a></li>
<li class="toctree-l2"><a class="reference internal" href="interfaces.html#kiss-interface">KISS Interface</a></li>
<li class="toctree-l2"><a class="reference internal" href="interfaces.html#ax-25-kiss-interface">AX.25 KISS Interface</a></li>
<li class="toctree-l2"><a class="reference internal" href="interfaces.html#common-interface-options">Common Interface Options</a></li>
</ul>
</li>
<li class="toctree-l1"><a class="reference internal" href="understanding.html">Understanding Reticulum</a><ul>
@@ -200,7 +210,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> &#187;</li>
<li class="nav-item nav-item-0"><a href="#">Reticulum Network Stack 0.3.3 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">Reticulum Network Stack Manual</a></li>
</ul>
</div>
+238 -67
View File
@@ -5,7 +5,7 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Supported Interfaces &#8212; Reticulum Network Stack 0.2.7 beta documentation</title>
<title>Supported Interfaces &#8212; Reticulum Network Stack 0.3.3 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> &#187;</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.3 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">Supported Interfaces</a></li>
</ul>
</div>
@@ -53,12 +53,211 @@ 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="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="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>
</div>
<div class="section" id="i2p-interface">
<span id="interfaces-i2p"></span><h2>I2P Interface<a class="headerlink" href="#i2p-interface" title="Permalink to this headline"></a></h2>
<p>The I2P interface lets you connect Reticulum instances over the
<a class="reference external" href="https://i2pd.website">Invisible Internet Protocol</a>. This can be
especially useful in cases where you want to host a globally reachable
Reticulum instance, but do not have access to any public IP addresses,
have a frequently changing IP address, or have firewalls blocking
inbound traffic.</p>
<p>Using the I2P interface, you will get a globally reachable, portable
and persistent I2P address that your Reticulum instance can be reached
at.</p>
<p>To use the I2P interface, you must have an I2P router running
on your system. The easiest way to acheive this is to download and
install the <a class="reference external" href="https://github.com/PurpleI2P/i2pd/releases/latest">latest release</a>
of the <code class="docutils literal notranslate"><span class="pre">ì2pd</span></code> package. For more details about I2P, see the
<a class="reference external" href="https://geti2p.net/en/about/intro">geti2p.net website</a>.`</p>
<p>When an I2P router is running on your system, you can simply add
an I2P interface to reticulum:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="p">[[</span><span class="n">I2P</span><span class="p">]]</span>
<span class="nb">type</span> <span class="o">=</span> <span class="n">I2PInterface</span>
<span class="n">interface_enabled</span> <span class="o">=</span> <span class="n">yes</span>
<span class="n">connectable</span> <span class="o">=</span> <span class="n">yes</span>
</pre></div>
</div>
<p>On the first start, Reticulum will generate a new I2P address for the
interface and start listening for inbound traffic on it. This can take
a while the first time, especially if your I2P router was also just
started, and is not yet well-connected to the I2P network. When ready,
you should see I2P base32 address printed to your log file. You can
also inspect the status of the interface using the <code class="docutils literal notranslate"><span class="pre">rnstatus</span></code> utility.</p>
<p>To connect to other Reticulum instances over I2P, just add a comma-separated
list of I2P base32 addresses to the <code class="docutils literal notranslate"><span class="pre">peers</span></code> option of the interface:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="p">[[</span><span class="n">I2P</span><span class="p">]]</span>
<span class="nb">type</span> <span class="o">=</span> <span class="n">I2PInterface</span>
<span class="n">interface_enabled</span> <span class="o">=</span> <span class="n">yes</span>
<span class="n">connectable</span> <span class="o">=</span> <span class="n">yes</span>
<span class="n">peers</span> <span class="o">=</span> <span class="mi">5</span><span class="n">urvjicpzi7q3ybztsef4i5ow2aq4soktfj7zedz53s47r54jnqq</span><span class="o">.</span><span class="n">b32</span><span class="o">.</span><span class="n">i2p</span>
</pre></div>
</div>
<p>It can take anywhere from a few seconds to a few minutes to establish
I2P connections to the desired peers, so Reticulum handles the process
in the background, and will output relevant events to the log.</p>
<p><strong>Please Note!</strong> While the I2P interface is the simplest way to use
Reticulum over I2P, it is also possible to tunnel the TCP server and
client interfaces over I2P manually. This can be useful in situations
where more control is needed, but requires manual tunnel setup through
the I2P daemon configuration.</p>
<p>It is important to note that the two methods are <em>interchangably compatible</em>.
You can use the I2PInterface to connect to a TCPServerInterface that
was manually tunneled over I2P, for example. This offers a high degree
of flexibility in network setup, while retaining ease of use in simpler
use-cases.</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>
<p>The TCP Server interface is suitable for allowing other peers to connect over
the Internet or private IP networks. When a TCP server interface has been
configured, other Reticulum peers can connect to it with a TCP Client interface.</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># This example demonstrates a TCP server interface.</span>
<span class="c1"># It will listen for incoming connections on the</span>
<span class="c1"># specified IP address and port number.</span>
<span class="p">[[</span><span class="n">TCP</span> <span class="n">Server</span> <span class="n">Interface</span><span class="p">]]</span>
<span class="nb">type</span> <span class="o">=</span> <span class="n">TCPServerInterface</span>
<span class="n">interface_enabled</span> <span class="o">=</span> <span class="kc">True</span>
<span class="c1"># This configuration will listen on all IP</span>
<span class="c1"># interfaces on port 4242</span>
<span class="n">listen_ip</span> <span class="o">=</span> <span class="mf">0.0</span><span class="o">.</span><span class="mf">0.0</span>
<span class="n">listen_port</span> <span class="o">=</span> <span class="mi">4242</span>
<span class="c1"># Alternatively you can bind to a specific IP</span>
<span class="c1"># listen_ip = 10.0.0.88</span>
<span class="c1"># listen_port = 4242</span>
<span class="c1"># Or a specific network device</span>
<span class="c1"># device = eth0</span>
<span class="c1"># port = 4242</span>
</pre></div>
</div>
<p><strong>Please Note!</strong> The TCP interfaces support tunneling over I2P, but to do so reliably,
you must use the i2p_tunneled option:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="p">[[</span><span class="n">TCP</span> <span class="n">Server</span> <span class="n">on</span> <span class="n">I2P</span><span class="p">]]</span>
<span class="nb">type</span> <span class="o">=</span> <span class="n">TCPServerInterface</span>
<span class="n">interface_enabled</span> <span class="o">=</span> <span class="n">yes</span>
<span class="n">listen_ip</span> <span class="o">=</span> <span class="mf">127.0</span><span class="o">.</span><span class="mf">0.1</span>
<span class="n">listen_port</span> <span class="o">=</span> <span class="mi">5001</span>
<span class="n">i2p_tunneled</span> <span class="o">=</span> <span class="n">yes</span>
</pre></div>
</div>
</div>
<div class="section" id="tcp-client-interface">
<span id="interfaces-tcpc"></span><h2>TCP Client Interface<a class="headerlink" href="#tcp-client-interface" title="Permalink to this headline"></a></h2>
<p>To connect to a TCP server interface, you would naturally use the TCP client
interface. Many TCP Client interfaces from different peers can connect to the
same TCP Server interface at the same time.</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># Here&#39;s an example of a TCP Client interface. The</span>
<span class="c1"># target_host can either be an IP address or a hostname.</span>
<span class="p">[[</span><span class="n">TCP</span> <span class="n">Client</span> <span class="n">Interface</span><span class="p">]]</span>
<span class="nb">type</span> <span class="o">=</span> <span class="n">TCPClientInterface</span>
<span class="n">interface_enabled</span> <span class="o">=</span> <span class="kc">True</span>
<span class="n">target_host</span> <span class="o">=</span> <span class="mf">127.0</span><span class="o">.</span><span class="mf">0.1</span>
<span class="n">target_port</span> <span class="o">=</span> <span class="mi">4242</span>
</pre></div>
</div>
<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&#39;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">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>
<p><strong>Please Note!</strong> The TCP interfaces support tunneling over I2P, but to do so reliably,
you must use the i2p_tunneled option:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="p">[[</span><span class="n">TCP</span> <span class="n">Client</span> <span class="n">over</span> <span class="n">I2P</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="n">yes</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">5001</span>
<span class="n">i2p_tunneled</span> <span class="o">=</span> <span class="n">yes</span>
</pre></div>
</div>
</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>
@@ -68,7 +267,7 @@ pre-existing LAN.</p>
<span class="p">[[</span><span class="n">Default</span> <span class="n">UDP</span> <span class="n">Interface</span><span class="p">]]</span>
<span class="nb">type</span> <span class="o">=</span> <span class="n">UDPInterface</span>
<span class="n">interface_enabled</span> <span class="o">=</span> <span class="kc">True</span>
<span class="n">outgoing</span> <span class="o">=</span> <span class="kc">True</span>
<span class="n">listen_ip</span> <span class="o">=</span> <span class="mf">0.0</span><span class="o">.</span><span class="mf">0.0</span>
<span class="n">listen_port</span> <span class="o">=</span> <span class="mi">4242</span>
<span class="n">forward_ip</span> <span class="o">=</span> <span class="mf">255.255</span><span class="o">.</span><span class="mf">255.255</span>
@@ -76,9 +275,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>
@@ -108,55 +305,6 @@ pre-existing LAN.</p>
</pre></div>
</div>
</div>
<div class="section" id="tcp-server-interface">
<span id="interfaces-tcps"></span><h2>TCP Server Interface<a class="headerlink" href="#tcp-server-interface" title="Permalink to this headline"></a></h2>
<p>The TCP Server interface is suitable for allowing other peers to connect over
the Internet or private IP networks. When a TCP server interface has been
configured, other Reticulum peers can connect to it with a TCP Client interface.</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># This example demonstrates a TCP server interface.</span>
<span class="c1"># It will listen for incoming connections on the</span>
<span class="c1"># specified IP address and port number.</span>
<span class="p">[[</span><span class="n">TCP</span> <span class="n">Server</span> <span class="n">Interface</span><span class="p">]]</span>
<span class="nb">type</span> <span class="o">=</span> <span class="n">TCPServerInterface</span>
<span class="n">interface_enabled</span> <span class="o">=</span> <span class="kc">True</span>
<span class="n">outgoing</span> <span class="o">=</span> <span class="kc">True</span>
<span class="c1"># This configuration will listen on all IP</span>
<span class="c1"># interfaces on port 4242</span>
<span class="n">listen_ip</span> <span class="o">=</span> <span class="mf">0.0</span><span class="o">.</span><span class="mf">0.0</span>
<span class="n">listen_port</span> <span class="o">=</span> <span class="mi">4242</span>
<span class="c1"># Alternatively you can bind to a specific IP</span>
<span class="c1"># listen_ip = 10.0.0.88</span>
<span class="c1"># listen_port = 4242</span>
<span class="c1"># Or a specific network device</span>
<span class="c1"># device = eth0</span>
<span class="c1"># port = 4242</span>
</pre></div>
</div>
</div>
<div class="section" id="tcp-client-interface">
<span id="interfaces-tcpc"></span><h2>TCP Client Interface<a class="headerlink" href="#tcp-client-interface" title="Permalink to this headline"></a></h2>
<p>To connect to a TCP server interface, you would naturally use the TCP client
interface. Many TCP Client interfaces from different peers can connect to the
same TCP Server interface at the same time.</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># Here&#39;s an example of a TCP Client interface. The</span>
<span class="c1"># target_host can either be an IP address or a hostname.</span>
<span class="p">[[</span><span class="n">TCP</span> <span class="n">Client</span> <span class="n">Interface</span><span class="p">]]</span>
<span class="nb">type</span> <span class="o">=</span> <span class="n">TCPClientInterface</span>
<span class="n">interface_enabled</span> <span class="o">=</span> <span class="kc">True</span>
<span class="n">outgoing</span> <span class="o">=</span> <span class="kc">True</span>
<span class="n">target_host</span> <span class="o">=</span> <span class="mf">127.0</span><span class="o">.</span><span class="mf">0.1</span>
<span class="n">target_port</span> <span class="o">=</span> <span class="mi">4242</span>
</pre></div>
</div>
</div>
<div class="section" id="rnode-lora-interface">
<span id="interfaces-rnode"></span><h2>RNode LoRa Interface<a class="headerlink" href="#rnode-lora-interface" title="Permalink to this headline"></a></h2>
<p>To use Reticulum over LoRa, the <a class="reference external" href="https://unsigned.io/rnode/">RNode</a> interface
@@ -170,11 +318,6 @@ can be used, and offers full control over LoRa parameters.</p>
<span class="c1"># Enable interface if you want use it!</span>
<span class="n">interface_enabled</span> <span class="o">=</span> <span class="kc">True</span>
<span class="c1"># Allow transmit on interface. Setting</span>
<span class="c1"># this to false will create a listen-</span>
<span class="c1"># only interface.</span>
<span class="n">outgoing</span> <span class="o">=</span> <span class="n">true</span>
<span class="c1"># Serial port for the device</span>
<span class="n">port</span> <span class="o">=</span> <span class="o">/</span><span class="n">dev</span><span class="o">/</span><span class="n">ttyUSB0</span>
@@ -221,7 +364,6 @@ directly over a wire-pair, or for using devices such as data radios and lasers.<
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="p">[[</span><span class="n">Serial</span> <span class="n">Interface</span><span class="p">]]</span>
<span class="nb">type</span> <span class="o">=</span> <span class="n">SerialInterface</span>
<span class="n">interface_enabled</span> <span class="o">=</span> <span class="kc">True</span>
<span class="n">outgoing</span> <span class="o">=</span> <span class="kc">True</span>
<span class="c1"># Serial port for the device</span>
<span class="n">port</span> <span class="o">=</span> <span class="o">/</span><span class="n">dev</span><span class="o">/</span><span class="n">ttyUSB0</span>
@@ -244,7 +386,6 @@ for station identification purposes.</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="p">[[</span><span class="n">Packet</span> <span class="n">Radio</span> <span class="n">KISS</span> <span class="n">Interface</span><span class="p">]]</span>
<span class="nb">type</span> <span class="o">=</span> <span class="n">KISSInterface</span>
<span class="n">interface_enabled</span> <span class="o">=</span> <span class="kc">True</span>
<span class="n">outgoing</span> <span class="o">=</span> <span class="n">true</span>
<span class="c1"># Serial port for the device</span>
<span class="n">port</span> <span class="o">=</span> <span class="o">/</span><span class="n">dev</span><span class="o">/</span><span class="n">ttyUSB1</span>
@@ -309,9 +450,6 @@ beaconing functionality described above.</p>
<span class="c1"># Enable interface if you want use it!</span>
<span class="n">interface_enabled</span> <span class="o">=</span> <span class="kc">True</span>
<span class="c1"># Allow transmit on interface.</span>
<span class="n">outgoing</span> <span class="o">=</span> <span class="kc">True</span>
<span class="c1"># Serial port for the device</span>
<span class="n">port</span> <span class="o">=</span> <span class="o">/</span><span class="n">dev</span><span class="o">/</span><span class="n">ttyUSB2</span>
@@ -347,6 +485,36 @@ beaconing functionality described above.</p>
</pre></div>
</div>
</div>
<div class="section" id="common-interface-options">
<span id="interfaces-options"></span><h2>Common Interface Options<a class="headerlink" href="#common-interface-options" title="Permalink to this headline"></a></h2>
<p>A number of general options can be used to control various
aspects of interface behaviour.</p>
<p>The <code class="docutils literal notranslate"><span class="pre">interface_enabled</span></code> option tells Reticulum whether or not
to bring up the interface. Defaults to <code class="docutils literal notranslate"><span class="pre">False</span></code>. For any
interface to be brought up, the <code class="docutils literal notranslate"><span class="pre">interface_enabled</span></code> option
must be set to <code class="docutils literal notranslate"><span class="pre">True</span></code> or <code class="docutils literal notranslate"><span class="pre">Yes</span></code>.</p>
<p>The <code class="docutils literal notranslate"><span class="pre">outgoing</span></code> option sets whether an interface is allowed
to transmit. Defaults to <code class="docutils literal notranslate"><span class="pre">True</span></code>. If set to <code class="docutils literal notranslate"><span class="pre">False</span></code> the
interface will only receive data, and never transmit.</p>
<p>The <code class="docutils literal notranslate"><span class="pre">interface_mode</span></code> option allows selecting the high-level
behaviour of the interface from a number of options.</p>
<blockquote>
<div><ul class="simple">
<li><p>The default value is <code class="docutils literal notranslate"><span class="pre">full</span></code>. In this mode, all discovery,
meshing and transpor functionality is available.</p></li>
<li><p>In the <code class="docutils literal notranslate"><span class="pre">access_point</span></code> (or shorthand <code class="docutils literal notranslate"><span class="pre">ap</span></code>) mode, the
interface will operate as a network access point. In this
mode, announces will not be automatically broadcasted on
the interface, and paths to destinations on the interface
will have a much shorter expiry time. This mode is useful
for creating interfaces that are mostly quiet, unless when
someone is actually using them. An example of this could
be a radio interface serving a wide area, where users are
expected to connect momentarily, use the network, and then
disappear again.</p></li>
</ul>
</div></blockquote>
</div>
</div>
@@ -359,13 +527,16 @@ 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="#udp-interface">UDP Interface</a></li>
<li><a class="reference internal" href="#auto-interface">Auto Interface</a></li>
<li><a class="reference internal" href="#i2p-interface">I2P Interface</a></li>
<li><a class="reference internal" href="#tcp-server-interface">TCP Server Interface</a></li>
<li><a class="reference internal" href="#tcp-client-interface">TCP Client Interface</a></li>
<li><a class="reference internal" href="#udp-interface">UDP Interface</a></li>
<li><a class="reference internal" href="#rnode-lora-interface">RNode LoRa Interface</a></li>
<li><a class="reference internal" href="#serial-interface">Serial Interface</a></li>
<li><a class="reference internal" href="#kiss-interface">KISS Interface</a></li>
<li><a class="reference internal" href="#ax-25-kiss-interface">AX.25 KISS Interface</a></li>
<li><a class="reference internal" href="#common-interface-options">Common Interface Options</a></li>
</ul>
</li>
</ul>
@@ -409,7 +580,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> &#187;</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.3 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">Supported Interfaces</a></li>
</ul>
</div>
+3 -3
View File
@@ -5,7 +5,7 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Building Networks &#8212; Reticulum Network Stack 0.2.7 beta documentation</title>
<title>Building Networks &#8212; Reticulum Network Stack 0.3.3 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> &#187;</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.3 beta documentation</a> &#187;</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> &#187;</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.3 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">Building Networks</a></li>
</ul>
</div>
Binary file not shown.
+3 -3
View File
@@ -5,7 +5,7 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>API Reference &#8212; Reticulum Network Stack 0.2.7 beta documentation</title>
<title>API Reference &#8212; Reticulum Network Stack 0.3.3 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> &#187;</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.3 beta documentation</a> &#187;</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> &#187;</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.3 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">API Reference</a></li>
</ul>
</div>
+3 -3
View File
@@ -5,7 +5,7 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Search &#8212; Reticulum Network Stack 0.2.7 beta documentation</title>
<title>Search &#8212; Reticulum Network Stack 0.3.3 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> &#187;</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.3 beta documentation</a> &#187;</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> &#187;</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.3 beta documentation</a> &#187;</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
+24 -21
View File
@@ -5,7 +5,7 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Understanding Reticulum &#8212; Reticulum Network Stack 0.2.7 beta documentation</title>
<title>Understanding Reticulum &#8212; Reticulum Network Stack 0.3.3 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> &#187;</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.3 beta documentation</a> &#187;</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 its
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 its
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">&lt;80e29bf7cccaf31431b3&gt;</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 destinations 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> &#187;</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.3 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">Understanding Reticulum</a></li>
</ul>
</div>
+87 -3
View File
@@ -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 &#8212; Reticulum Network Stack 0.2.7 beta documentation</title>
<title>Using Reticulum on Your System &#8212; Reticulum Network Stack 0.3.3 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> &#187;</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.3 beta documentation</a> &#187;</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&#39;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 systemds 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> &#187;</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.3 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">Using Reticulum on Your System</a></li>
</ul>
</div>
+10 -10
View File
@@ -5,7 +5,7 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>What is Reticulum? &#8212; Reticulum Network Stack 0.2.7 beta documentation</title>
<title>What is Reticulum? &#8212; Reticulum Network Stack 0.3.3 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> &#187;</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.3 beta documentation</a> &#187;</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> &#187;</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.3 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">What is Reticulum?</a></li>
</ul>
</div>
+2 -2
View File
@@ -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.3 beta'
# -- General configuration ---------------------------------------------------
@@ -65,4 +65,4 @@ html_static_path = ['_static']
# return False
# def setup(app):
# app.connect('autodoc-skip-member', check_skip_member)
# app.connect('autodoc-skip-member', check_skip_member)
+79 -3
View File
@@ -6,6 +6,7 @@ The best way to get started with the Reticulum Network Stack depends on what
you want to do. This guide will outline sensible starting paths for different
scenarios.
Try Using a Reticulum-based Program
=============================================
If you simply want to try using a program built with Reticulum, you can take
@@ -16,7 +17,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:
@@ -29,6 +30,10 @@ You can install Nomad Network via pip:
# ... and run
nomadnet
**Please Note**: If this is the very first time you use pip to install a program
on your system, you might need to reboot your system for your program to become
available. If you get a "command not found" error or similar when running the
program, reboot your system and try again.
Using the Included Utilities
@@ -44,11 +49,13 @@ network status and connectivity.
To learn more about these utility programs, have a look at the
:ref:`Using Reticulum on Your System<using-main>` chapter of this manual.
Creating a Network With Reticulum
=============================================
To create a network, you will need to specify one or more *interfaces* for
Reticulum to use. This is done in the Reticulum configuration file, which by
default is located at ``~/.reticulum/config``.
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 +84,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 +134,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.
+276 -73
View File
@@ -18,6 +18,238 @@ 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
# 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
# Configure global discovery
group_id = custom_network_name
discovery_scope = global
# Other configuration options
discovery_port = 48555
data_port = 49555
.. _interfaces-i2p:
I2P Interface
=============
The I2P interface lets you connect Reticulum instances over the
`Invisible Internet Protocol <https://i2pd.website>`_. This can be
especially useful in cases where you want to host a globally reachable
Reticulum instance, but do not have access to any public IP addresses,
have a frequently changing IP address, or have firewalls blocking
inbound traffic.
Using the I2P interface, you will get a globally reachable, portable
and persistent I2P address that your Reticulum instance can be reached
at.
To use the I2P interface, you must have an I2P router running
on your system. The easiest way to acheive this is to download and
install the `latest release <https://github.com/PurpleI2P/i2pd/releases/latest>`_
of the ``ì2pd`` package. For more details about I2P, see the
`geti2p.net website <https://geti2p.net/en/about/intro>`_.`
When an I2P router is running on your system, you can simply add
an I2P interface to reticulum:
.. code::
[[I2P]]
type = I2PInterface
interface_enabled = yes
connectable = yes
On the first start, Reticulum will generate a new I2P address for the
interface and start listening for inbound traffic on it. This can take
a while the first time, especially if your I2P router was also just
started, and is not yet well-connected to the I2P network. When ready,
you should see I2P base32 address printed to your log file. You can
also inspect the status of the interface using the ``rnstatus`` utility.
To connect to other Reticulum instances over I2P, just add a comma-separated
list of I2P base32 addresses to the ``peers`` option of the interface:
.. code::
[[I2P]]
type = I2PInterface
interface_enabled = yes
connectable = yes
peers = 5urvjicpzi7q3ybztsef4i5ow2aq4soktfj7zedz53s47r54jnqq.b32.i2p
It can take anywhere from a few seconds to a few minutes to establish
I2P connections to the desired peers, so Reticulum handles the process
in the background, and will output relevant events to the log.
**Please Note!** While the I2P interface is the simplest way to use
Reticulum over I2P, it is also possible to tunnel the TCP server and
client interfaces over I2P manually. This can be useful in situations
where more control is needed, but requires manual tunnel setup through
the I2P daemon configuration.
It is important to note that the two methods are *interchangably compatible*.
You can use the I2PInterface to connect to a TCPServerInterface that
was manually tunneled over I2P, for example. This offers a high degree
of flexibility in network setup, while retaining ease of use in simpler
use-cases.
.. _interfaces-tcps:
TCP Server Interface
====================
The TCP Server interface is suitable for allowing other peers to connect over
the Internet or private IP networks. When a TCP server interface has been
configured, other Reticulum peers can connect to it with a TCP Client interface.
.. code::
# This example demonstrates a TCP server interface.
# It will listen for incoming connections on the
# specified IP address and port number.
[[TCP Server Interface]]
type = TCPServerInterface
interface_enabled = True
# This configuration will listen on all IP
# interfaces on port 4242
listen_ip = 0.0.0.0
listen_port = 4242
# Alternatively you can bind to a specific IP
# listen_ip = 10.0.0.88
# listen_port = 4242
# Or a specific network device
# device = eth0
# port = 4242
**Please Note!** The TCP interfaces support tunneling over I2P, but to do so reliably,
you must use the i2p_tunneled option:
.. code::
[[TCP Server on I2P]]
type = TCPServerInterface
interface_enabled = yes
listen_ip = 127.0.0.1
listen_port = 5001
i2p_tunneled = yes
.. _interfaces-tcpc:
TCP Client Interface
====================
To connect to a TCP server interface, you would naturally use the TCP client
interface. Many TCP Client interfaces from different peers can connect to the
same TCP Server interface at the same time.
.. code::
# Here's an example of a TCP Client interface. The
# target_host can either be an IP address or a hostname.
[[TCP Client Interface]]
type = TCPClientInterface
interface_enabled = True
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
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.
**Please Note!** The TCP interfaces support tunneling over I2P, but to do so reliably,
you must use the i2p_tunneled option:
.. code::
[[TCP Client over I2P]]
type = TCPClientInterface
interface_enabled = yes
target_host = 127.0.0.1
target_port = 5001
i2p_tunneled = yes
.. _interfaces-udp:
UDP Interface
@@ -28,6 +260,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.
@@ -40,7 +278,7 @@ pre-existing LAN.
[[Default UDP Interface]]
type = UDPInterface
interface_enabled = True
outgoing = True
listen_ip = 0.0.0.0
listen_port = 4242
forward_ip = 255.255.255.255
@@ -48,9 +286,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,64 +314,6 @@ pre-existing LAN.
# forward_ip = 10.55.0.16
# forward_port = 4242
.. _interfaces-tcps:
TCP Server Interface
====================
The TCP Server interface is suitable for allowing other peers to connect over
the Internet or private IP networks. When a TCP server interface has been
configured, other Reticulum peers can connect to it with a TCP Client interface.
.. code::
# This example demonstrates a TCP server interface.
# It will listen for incoming connections on the
# specified IP address and port number.
[[TCP Server Interface]]
type = TCPServerInterface
interface_enabled = True
outgoing = True
# This configuration will listen on all IP
# interfaces on port 4242
listen_ip = 0.0.0.0
listen_port = 4242
# Alternatively you can bind to a specific IP
# listen_ip = 10.0.0.88
# listen_port = 4242
# Or a specific network device
# device = eth0
# port = 4242
.. _interfaces-tcpc:
TCP Client Interface
====================
To connect to a TCP server interface, you would naturally use the TCP client
interface. Many TCP Client interfaces from different peers can connect to the
same TCP Server interface at the same time.
.. code::
# Here's an example of a TCP Client interface. The
# target_host can either be an IP address or a hostname.
[[TCP Client Interface]]
type = TCPClientInterface
interface_enabled = True
outgoing = True
target_host = 127.0.0.1
target_port = 4242
.. _interfaces-rnode:
@@ -156,11 +334,6 @@ can be used, and offers full control over LoRa parameters.
# Enable interface if you want use it!
interface_enabled = True
# Allow transmit on interface. Setting
# this to false will create a listen-
# only interface.
outgoing = true
# Serial port for the device
port = /dev/ttyUSB0
@@ -211,7 +384,6 @@ directly over a wire-pair, or for using devices such as data radios and lasers.
[[Serial Interface]]
type = SerialInterface
interface_enabled = True
outgoing = True
# Serial port for the device
port = /dev/ttyUSB0
@@ -238,7 +410,6 @@ for station identification purposes.
[[Packet Radio KISS Interface]]
type = KISSInterface
interface_enabled = True
outgoing = true
# Serial port for the device
port = /dev/ttyUSB1
@@ -309,9 +480,6 @@ beaconing functionality described above.
# Enable interface if you want use it!
interface_enabled = True
# Allow transmit on interface.
outgoing = True
# Serial port for the device
port = /dev/ttyUSB2
@@ -343,4 +511,39 @@ beaconing functionality described above.
# Whether to use KISS flow-control.
# This is useful for modems with a
# small internal packet buffer.
flow_control = false
flow_control = false
.. _interfaces-options:
Common Interface Options
========================
A number of general options can be used to control various
aspects of interface behaviour.
The ``interface_enabled`` option tells Reticulum whether or not
to bring up the interface. Defaults to ``False``. For any
interface to be brought up, the ``interface_enabled`` option
must be set to ``True`` or ``Yes``.
The ``outgoing`` option sets whether an interface is allowed
to transmit. Defaults to ``True``. If set to ``False`` the
interface will only receive data, and never transmit.
The ``interface_mode`` option allows selecting the high-level
behaviour of the interface from a number of options.
- The default value is ``full``. In this mode, all discovery,
meshing and transpor functionality is available.
- In the ``access_point`` (or shorthand ``ap``) mode, the
interface will operate as a network access point. In this
mode, announces will not be automatically broadcasted on
the interface, and paths to destinations on the interface
will have a much shorter expiry time. This mode is useful
for creating interfaces that are mostly quiet, unless when
someone is actually using them. An example of this could
be a radio interface serving a wide area, where users are
expected to connect momentarily, use the network, and then
disappear again.
+17 -17
View File
@@ -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 its
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 its
@@ -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 destinations 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
View File
@@ -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
+8 -8
View File
@@ -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 -1
View File
@@ -29,6 +29,6 @@ setuptools.setup(
]
},
install_requires=['cryptography>=3.4.7', 'pyserial', 'netifaces>=0.10.4'],
install_requires=['cryptography>=3.4.7', 'pyserial>=3.5', 'netifaces'],
python_requires='>=3.6',
)