Compare commits

...

82 Commits

Author SHA1 Message Date
Mark Qvist 97f97eb063 Updated changelog 2023-06-03 16:04:18 +02:00
Mark Qvist f3db762e9f Updated documentation 2023-06-03 16:03:13 +02:00
Mark Qvist f9f623dfa5 Updated version and changelog 2023-06-03 15:52:44 +02:00
Mark Qvist ffa6bec3b4 Updated parser 2023-06-02 21:24:57 +02:00
Mark Qvist 4f78973751 Fixed race condition when timed-out link receives a late establishment proof a few milliseconds after it has timed out 2023-06-02 21:24:49 +02:00
Mark Qvist a8a7af4b74 Handle missing identity file in rncp. Fixes #317. 2023-05-31 15:39:55 +02:00
Mark Qvist 45295c779c Updated changelog 2023-05-19 11:38:46 +02:00
Mark Qvist a82376d1f5 Updated manuals 2023-05-19 11:35:45 +02:00
Mark Qvist 75c6248264 Updated documentation 2023-05-19 11:31:43 +02:00
Mark Qvist 9294ab4f97 Updated version 2023-05-19 11:31:36 +02:00
Mark Qvist f01193e854 Updated documentation 2023-05-19 03:06:24 +02:00
Mark Qvist d7375bc4c3 Fixed callback invocation on channel receive 2023-05-19 01:58:28 +02:00
Mark Qvist 1a860c6ffd Add EOF signal on buffer close 2023-05-19 01:57:20 +02:00
Mark Qvist 800ed3af7a Fixed ready callback invocation 2023-05-18 23:35:28 +02:00
Mark Qvist 9c8e79546c Fixed missing check in receipt culling 2023-05-18 23:33:26 +02:00
Mark Qvist 4c272aa536 Updated buffer tests for windowed channel 2023-05-18 23:32:29 +02:00
Mark Qvist e184861822 Enabled channel tests 2023-05-18 23:31:29 +02:00
Mark Qvist d40e19f08d Updated gitignore 2023-05-18 23:29:31 +02:00
Mark Qvist 817ee0721a Updated manual 2023-05-12 12:38:12 +02:00
Mark Qvist 22ec4afdab Updated changelog 2023-05-12 12:38:02 +02:00
Mark Qvist 61626897e7 Add channel window mode for slow links 2023-05-11 21:28:13 +02:00
Mark Qvist 6fd3edbb8f Updated docs 2023-05-11 20:55:28 +02:00
Mark Qvist fc5b02ed5d Added medium window to channel 2023-05-11 20:23:36 +02:00
Mark Qvist a06e752b76 Added multi-interface duplicate deque to AutoInterface 2023-05-11 19:54:26 +02:00
Mark Qvist 3a947bf81b Updated documentation 2023-05-11 19:53:40 +02:00
Mark Qvist 31121ca885 Updated documentation 2023-05-11 18:49:01 +02:00
Mark Qvist 387b8c46ff Cleanup 2023-05-11 18:35:01 +02:00
Mark Qvist 66fda34b20 Cleanup 2023-05-11 17:48:07 +02:00
Mark Qvist 1542c5f4fe Fixed received link packet proofs not resetting watchdog stale timer 2023-05-11 16:22:44 +02:00
Mark Qvist 523fc7b8f9 Adjusted loglevel 2023-05-11 16:09:25 +02:00
Mark Qvist 73faf04ea1 Tuned channel windowing 2023-05-10 20:01:33 +02:00
Mark Qvist e10ddf9d2d Cleanup 2023-05-10 19:28:28 +02:00
Mark Qvist 641a7ea75d Implemented basic channel windowing 2023-05-10 19:15:45 +02:00
Mark Qvist e543d5c27f Implemented basic channel windowing 2023-05-10 19:15:20 +02:00
Mark Qvist 01c59ab0c6 Cleanup 2023-05-10 18:44:05 +02:00
Mark Qvist a4c64abed4 Initial framework for channel windowing 2023-05-10 18:43:17 +02:00
Mark Qvist 7df11a6f67 Fixed missing isolation of packet delivery callback 2023-05-10 18:40:46 +02:00
Mark Qvist 1bd6020163 Cleanup 2023-05-10 18:40:18 +02:00
Mark Qvist b3ac3131b5 Updated version 2023-05-09 23:07:47 +02:00
Mark Qvist f522cb1db1 Added per-packet compression to buffer 2023-05-09 22:13:57 +02:00
Mark Qvist d96a4853fe Fixed version display 2023-05-09 22:13:23 +02:00
Mark Qvist 52a0447fea Fixed resent packets not getting repacked 2023-05-09 22:12:49 +02:00
Mark Qvist e82e6d56f1 Added ability to trust external signing keys to rnodeconf 2023-05-09 15:31:02 +02:00
Mark Qvist 3967ef453d Updated documentation 2023-05-05 13:47:29 +02:00
Mark Qvist 76f7751d5f Updated documentation 2023-05-05 13:46:23 +02:00
Mark Qvist 8716ffc873 Updated documentation 2023-05-05 13:38:06 +02:00
Mark Qvist b476e4cfb0 Updated changelog 2023-05-05 11:48:00 +02:00
Mark Qvist 7ec77a10d3 Updated changelog 2023-05-05 11:46:06 +02:00
Mark Qvist 55a9c5ef71 Updated documentation 2023-05-05 11:27:52 +02:00
Mark Qvist 6d3ba31993 Updated readme 2023-05-05 11:14:50 +02:00
Mark Qvist d3f4a674aa Updated readme 2023-05-05 11:13:18 +02:00
Mark Qvist 599ab20ed0 Updated readme 2023-05-05 11:09:12 +02:00
Mark Qvist dcf33e125b Cleanup 2023-05-05 10:43:27 +02:00
Mark Qvist 01600b96a4 Fix import paths 2023-05-05 10:37:22 +02:00
Mark Qvist 64bdc4c18c Fix import paths 2023-05-05 10:25:15 +02:00
Mark Qvist 0889b8a7c5 Updated manual 2023-05-05 10:09:17 +02:00
Mark Qvist 1b2fee3ab8 Fixed EPUB output 2023-05-05 09:43:21 +02:00
Mark Qvist da7a4433c0 Updated documentation 2023-05-04 23:30:27 +02:00
Mark Qvist 5e5d89cc92 Removed dependency on netifaces. 2023-05-04 23:19:43 +02:00
Mark Qvist a3bee4baa9 Removed netifaces dependency from AutoInterface 2023-05-04 17:55:58 +02:00
Mark Qvist fab83ec399 Restructured library 2023-05-04 17:55:38 +02:00
Mark Qvist b740e36985 Added ifaddr module 2023-05-04 17:46:56 +02:00
Mark Qvist 29693c6fe2 Updated documentation 2023-05-04 12:42:12 +02:00
Mark Qvist 72638f40a6 Updated documentation 2023-05-04 12:23:25 +02:00
Mark Qvist 8d29e83d90 Updated dependencies 2023-05-04 12:23:16 +02:00
Mark Qvist 53b325d34d Added support for T3 v1.0 to rnodeconf 2023-05-03 15:56:19 +02:00
Mark Qvist d31cf6e297 Added ability to configure RNode display intensity 2023-05-03 14:26:47 +02:00
Mark Qvist e386a5d08b Use native Python unzip for updates 2023-05-03 12:57:38 +02:00
Mark Qvist d467ed9ece Merge branch 'master' of github.com:markqvist/Reticulum 2023-05-03 12:27:10 +02:00
Mark Qvist 892a467d74 Update version 2023-05-03 12:26:48 +02:00
markqvist 4366e71f34 Merge pull request #272 from VioletEternity/windows
Improve Windows compatibility for rnodeconf
2023-05-03 12:26:36 +02:00
Mark Qvist 7e9998b4fd Use included platform detection method 2023-05-03 12:21:57 +02:00
markqvist 79abe93139 Merge pull request #278 from VioletEternity/windows-so_reuseaddr
Use SO_EXCLUSIVEADDRUSE instead of SO_REUSEADDR on Windows
2023-05-03 12:18:49 +02:00
Mark Qvist d69d4b3920 Fixed firmware extraction for unverifiable devices. Fixes #266. 2023-05-02 18:10:04 +02:00
Mark Qvist 3300541181 Fixed invalid error code in conditional. Fixes #284. 2023-05-02 17:45:30 +02:00
Mark Qvist 3848059f19 Only use ifname for link-local discovery scopes. Fixes #283. 2023-05-02 17:39:06 +02:00
Mark Qvist 30021d89cb Fixed header bits in get_packed_flags(). Fixes #275. 2023-05-02 17:33:38 +02:00
Mark Qvist 29019724bd Added verbosity argument to Reticulum instantiation. Fixes #238. 2023-05-02 16:42:04 +02:00
Maya ba7838c04e Use SO_EXCLUSIVEADDRUSE instead of SO_REUSEADDR on Windows.
On Linux, SO_REUSEADDR is used so that a socket in TIME-WAIT state can
be rebound after a listening process is restarted. It does not allow two
processes to listen on the exact same (addr, port) combination. However,
on Windows, it does, and SO_EXCLUSIVEADDRUSE is required to reproduce
the Linux behavior.

Reticulum relies on an error being returned by bind() that reuses
the same (addr, port) combination as another process to detect whether
there is a shared instance already running. Setting SO_EXCLUSIVEADDRUSE
makes this detection process work on Windows as well.
2023-04-19 03:03:15 +01:00
Maya af16c68e47 Make esptool.py invocation compatible with Windows. 2023-04-13 18:17:14 +01:00
Maya bda5717051 Use standard Python zipfile module to decompress firmware 2023-04-13 18:10:21 +01:00
Mark Qvist fac4973329 Fixed potential race condition in announce queue handling for AutoInterface 2023-03-09 18:32:14 +01:00
59 changed files with 1594 additions and 393 deletions
+1
View File
@@ -10,5 +10,6 @@ docs/build
rns*.egg-info
profile.data
tests/rnsconfig/storage
tests/rnsconfig/logfile*
*.data
*.result
+70
View File
@@ -1,3 +1,73 @@
### 2023-05-19: RNS β 0.5.4
This maintenance release brings a single bugfix
**Changes**
- Fixed a potential race condition when timed-out link receives a late establishment proof a few milliseconds after it has timed out.
**Release Hashes**
```
71b42fe737da97a4b63bb227c29bb67854a7f003c9585f085b0ff68c8f460815 rns-0.5.4-py3-none-any.whl
af6949d581445444f57cfca75756200e7c509a6fc66483d859716ce6a06064db rnspure-0.5.4-py3-none-any.whl
```
### 2023-05-19: RNS β 0.5.3
This maintenance release brings a single, but important bugfix.
**Changes**
- Fixed a bug that could cause data corruption to occur over when using `Buffer` instances.
**Release Hashes**
```
f23c8d655c9e80a12a6728495aec56f19f27184d3d8e6b6ed6184b0e89d4be35 rns-0.5.3-py3-none-any.whl
2c692a2153bb766a9dc2391340a06f429c13a75b86b746b69c6fcd5a4fe5ee33 rnspure-0.5.3-py3-none-any.whl
```
### 2023-05-12: RNS β 0.5.2
This maintenance release brings a number of bugfixes and improvements.
**Important!** This release breaks backwards compatibility with `Channel` and `Buffer` for all previous releases, due to the addition of compression and windowing.
**Changes**
- Added ability to trust external signing keys to `rnodeconf`
- Added basic windowing to `Channel` and `Buffer`, improving performance over faster links
- Added per-packet compression to `Channel`
- Added automatic multi-interface duplicate deque to AutoInterface
- Fixed received link packet proofs not resetting link watchdog stale timer
- Fixed a missing exception isolation of packet delivery callbacks
- Fixed resent packets not getting repacked
**Release Hashes**
```
f3b1e9cf39420ad74c2b5c81ad339fd2a548320c9f6925bad9b614feb4c9b9d7 rns-0.5.2-py3-none-any.whl
8463f7365f179d02e7e4d4fe4afc69da4218ce40107305dfd06b9e6b29513e0f rnspure-0.5.2-py3-none-any.whl
```
### 2023-05-05: RNS β 0.5.1
This maintenance release brings a number of bugfixes and improvements. Thanks to @VioletEternity, who contributed to this release!
**Changes**
- Removed dependency on netifaces module
- Added ability to configure RNode display intensity to rnodeconf
- Added preliminary rnodeconf flasher/autoinstaller support for T3 v1.0 boards
- Fixed a bug that caused AutoInterface discovery scopes to fail
- Fixed rnodeconf firmware extraction for unverifiable devices
- Improved setting rnsd verbosity from command line
- Improved support for shared instances on Windows
- Improved rnodeconf support on Windows
- Improved rnodeconf zip-file handling
- Fixed a potentail race condition in announce queue handling for AutoInterface
- Various minor bugfixes
**Release Hashes**
```
01d76e03f93e427d9c0b95ab5d07e84ed39047e912b8afa6d619a65ac6b5e05b rns-0.5.1-py3-none-any.whl
2cfe431bec1160410b80bbcbf87eb2ab0d5abe5c6546f41eaf3f0f5faf9b2140 rnspure-0.5.1-py3-none-any.whl
```
### 2023-03-08: RNS β 0.5.0
This release brings two major new additions to the Reticulum API: The Channel and Buffer classes, that provides reliable delivery, and streams over Reticulum. Thanks to @acehoss, @erethon, @gdt and @faragher, who contributed to this release!
+8 -15
View File
@@ -190,7 +190,6 @@ these dependencies, and when the `rns` package is installed with `pip`, they
will be downloaded and installed as well.
- [PyCA/cryptography](https://github.com/pyca/cryptography)
- [netifaces](https://github.com/al45tair/netifaces)
- [pyserial](https://github.com/pyserial/pyserial)
On more unusual systems, and in some rare cases, it might not be possible to
@@ -228,31 +227,25 @@ I2P. Just add one of the following interfaces to your Reticulum configuration
file:
```
# TCP/IP interface to the Dublin Hub
# TCP/IP interface to the RNS Dublin Hub
[[RNS Testnet Dublin]]
type = TCPClientInterface
enabled = yes
target_host = dublin.connect.reticulum.network
target_port = 4965
# TCP/IP interface to the Frankfurt Hub
[[RNS Testnet Frankfurt]]
# TCP/IP interface to the BetweenTheBorders Hub (community-provided)
[[RNS Testnet BetweenTheBorders]]
type = TCPClientInterface
enabled = yes
target_host = frankfurt.connect.reticulum.network
target_port = 5377
target_host = betweentheborders.com
target_port = 4242
# Interface to I2P Hub A
[[RNS Testnet I2P Hub A]]
# Interface to Testnet I2P Hub
[[RNS Testnet I2P Hub]]
type = I2PInterface
enabled = yes
peers = pmlm3l5rpympihoy2o5ago43kluei2jjjzsalcuiuylbve3mwi2a.b32.i2p
# Interface to I2P Hub B
[[RNS Testnet I2P Hub B]]
type = I2PInterface
enabled = yes
peers = iwoqtz22dsr73aemwpw7guocplsjjoamyl7sogj33qtcd6ds4mza.b32.i2p
```
The testnet also contains a number of [Nomad Network](https://github.com/markqvist/nomadnet) nodes, and LXMF propagation nodes.
@@ -341,8 +334,8 @@ projects:
- [Curve25519.py](https://gist.github.com/nickovs/cc3c22d15f239a2640c185035c06f8a3#file-curve25519-py) by [Nicko van Someren](https://gist.github.com/nickovs), *Public Domain*
- [I2Plib](https://github.com/l-n-s/i2plib) by [Viktor Villainov](https://github.com/l-n-s)
- [PySerial](https://github.com/pyserial/pyserial) by Chris Liechti, *BSD License*
- [Netifaces](https://github.com/al45tair/netifaces) by [Alastair Houghton](https://github.com/al45tair), *MIT License*
- [Configobj](https://github.com/DiffSK/configobj) by Michael Foord, Nicola Larosa, Rob Dennis & Eli Courtwright, *BSD License*
- [Six](https://github.com/benjaminp/six) by [Benjamin Peterson](https://github.com/benjaminp), *MIT License*
- [ifaddr](https://github.com/pydron/ifaddr) by [Pydron](https://github.com/pydron), *MIT License*
- [Umsgpack.py](https://github.com/vsergeev/u-msgpack-python) by [Ivan A. Sergeev](https://github.com/vsergeev)
- [Python](https://www.python.org)
+30 -7
View File
@@ -1,5 +1,7 @@
from __future__ import annotations
import bz2
import sys
import time
import threading
from threading import RLock
import struct
@@ -9,7 +11,6 @@ from io import RawIOBase, BufferedRWPair, BufferedReader, BufferedWriter
from typing import Callable
from contextlib import AbstractContextManager
class StreamDataMessage(MessageBase):
MSGTYPE = SystemMessageTypes.SMT_STREAM_DATA
"""
@@ -17,9 +18,9 @@ class StreamDataMessage(MessageBase):
uses a system-reserved message type.
"""
STREAM_ID_MAX = 0x7fff # 32767
STREAM_ID_MAX = 0x3fff # 16383
"""
The stream id is limited to 2 bytes - 1 bit
The stream id is limited to 2 bytes - 2 bit
"""
MAX_DATA_LEN = RNS.Link.MDU - 2 - 6 # 2 for stream data message header, 6 for channel envelope
@@ -39,23 +40,36 @@ class StreamDataMessage(MessageBase):
"""
super().__init__()
if stream_id is not None and stream_id > self.STREAM_ID_MAX:
raise ValueError("stream_id must be 0-32767")
raise ValueError("stream_id must be 0-16383")
self.stream_id = stream_id
self.compressed = False
self.data = data or bytes()
self.eof = eof
def pack(self) -> bytes:
if self.stream_id is None:
raise ValueError("stream_id")
header_val = (0x7fff & self.stream_id) | (0x8000 if self.eof else 0x0000)
compressed_data = bz2.compress(self.data)
saved = len(self.data)-len(compressed_data)
if saved > 0:
self.data = compressed_data
self.compressed = True
header_val = (0x3fff & self.stream_id) | (0x8000 if self.eof else 0x0000) | (0x4000 if self.compressed > 0 else 0x0000)
return bytes(struct.pack(">H", header_val) + (self.data if self.data else bytes()))
def unpack(self, raw):
self.stream_id = struct.unpack(">H", raw[:2])[0]
self.eof = (0x8000 & self.stream_id) > 0
self.stream_id = self.stream_id & 0x7fff
self.compressed = (0x4000 & self.stream_id) > 0
self.stream_id = self.stream_id & 0x3fff
self.data = raw[2:]
if self.compressed:
self.data = bz2.decompress(self.data)
class RawChannelReader(RawIOBase, AbstractContextManager):
"""
@@ -117,7 +131,7 @@ class RawChannelReader(RawIOBase, AbstractContextManager):
self._eof = True
for listener in self._listeners:
try:
listener(len(self._buffer))
threading.Thread(target=listener, name="Message Callback", args=[len(self._buffer)], daemon=True).start()
except Exception as ex:
RNS.log("Error calling RawChannelReader(" + str(self._stream_id) + ") callback: " + str(ex))
return True
@@ -195,6 +209,15 @@ class RawChannelWriter(RawIOBase, AbstractContextManager):
return 0
def close(self):
try:
link_rtt = self._channel._outlet.link.rtt
timeout = time.time() + (link_rtt * len(self._channel._tx_ring) * 1)
except Exception as e:
timeout = time.time() + 15
while time.time() < timeout and not self._channel.is_ready_to_send():
time.sleep(0.05)
self._eof = True
self.write(bytes())
+171 -43
View File
@@ -176,6 +176,9 @@ class Envelope:
raise ChannelException(CEType.ME_NOT_REGISTERED, f"Unable to find constructor for Channel MSGTYPE {hex(msgtype)}")
message = ctor()
message.unpack(raw)
self.unpacked = True
self.message = message
return message
def pack(self) -> bytes:
@@ -183,6 +186,7 @@ class Envelope:
raise ChannelException(CEType.ME_NO_MSG_TYPE, f"{self.message.__class__} lacks MSGTYPE")
data = self.message.pack()
self.raw = struct.pack(">HHH", self.message.MSGTYPE, self.sequence, len(data)) + data
self.packed = True
return self.raw
def __init__(self, outlet: ChannelOutletBase, message: MessageBase = None, raw: bytes = None, sequence: int = None):
@@ -194,6 +198,8 @@ class Envelope:
self.sequence = sequence
self.outlet = outlet
self.tries = 0
self.unpacked = False
self.packed = False
self.tracked = False
@@ -223,6 +229,44 @@ class Channel(contextlib.AbstractContextManager):
``Channel`` is not instantiated directly, but rather
obtained from a ``Link`` with ``get_channel()``.
"""
# The initial window size at channel setup
WINDOW = 2
# Absolute minimum window size
WINDOW_MIN = 1
# The maximum window size for transfers on slow links
WINDOW_MAX_SLOW = 5
# The maximum window size for transfers on mid-speed links
WINDOW_MAX_MEDIUM = 16
# The maximum window size for transfers on fast links
WINDOW_MAX_FAST = 48
# For calculating maps and guard segments, this
# must be set to the global maximum window.
WINDOW_MAX = WINDOW_MAX_FAST
# If the fast rate is sustained for this many request
# rounds, the fast link window size will be allowed.
FAST_RATE_THRESHOLD = 10
# If the RTT rate is higher than this value,
# the max window size for fast links will be used.
RTT_FAST = 0.25
RTT_MEDIUM = 0.75
RTT_SLOW = 1.45
# The minimum allowed flexibility of the window size.
# The difference between window_max and window_min
# will never be smaller than this value.
WINDOW_FLEXIBILITY = 4
SEQ_MAX = 0xFFFF
SEQ_MODULUS = SEQ_MAX+1
def __init__(self, outlet: ChannelOutletBase):
"""
@@ -234,8 +278,22 @@ class Channel(contextlib.AbstractContextManager):
self._rx_ring: collections.deque[Envelope] = collections.deque()
self._message_callbacks: [MessageCallbackType] = []
self._next_sequence = 0
self._next_rx_sequence = 0
self._message_factories: dict[int, Type[MessageBase]] = {}
self._max_tries = 5
self.fast_rate_rounds = 0
self.medium_rate_rounds = 0
if self._outlet.rtt > Channel.RTT_SLOW:
self.window = 1
self.window_max = 1
self.window_min = 1
self.window_flexibility = 1
else:
self.window = Channel.WINDOW
self.window_max = Channel.WINDOW_MAX_SLOW
self.window_min = Channel.WINDOW_MIN
self.window_flexibility = Channel.WINDOW_FLEXIBILITY
def __enter__(self) -> Channel:
return self
@@ -319,56 +377,77 @@ class Channel(contextlib.AbstractContextManager):
def _emplace_envelope(self, envelope: Envelope, ring: collections.deque[Envelope]) -> bool:
with self._lock:
i = 0
window_overflow = (self._next_rx_sequence+Channel.WINDOW_MAX) % Channel.SEQ_MODULUS
for existing in ring:
if existing.sequence > envelope.sequence \
and not existing.sequence // 2 > envelope.sequence: # account for overflow
ring.insert(i, envelope)
return True
if existing.sequence == envelope.sequence:
RNS.log(f"Envelope: Emplacement of duplicate envelope sequence.", RNS.LOG_EXTREME)
if envelope.sequence == existing.sequence:
RNS.log(f"Envelope: Emplacement of duplicate envelope with sequence "+str(envelope.sequence), RNS.LOG_EXTREME)
return False
if envelope.sequence < existing.sequence and not envelope.sequence < window_overflow:
ring.insert(i, envelope)
RNS.log("Inserted seq "+str(envelope.sequence)+" at "+str(i), RNS.LOG_DEBUG)
envelope.tracked = True
return True
i += 1
envelope.tracked = True
ring.append(envelope)
return True
def _prune_rx_ring(self):
with self._lock:
# Implementation for fixed window = 1
stale = list(sorted(self._rx_ring, key=lambda env: env.sequence, reverse=True))[1:]
for env in stale:
env.tracked = False
self._rx_ring.remove(env)
def _run_callbacks(self, message: MessageBase):
with self._lock:
cbs = self._message_callbacks.copy()
cbs = self._message_callbacks.copy()
for cb in cbs:
try:
if cb(message):
return
except Exception as ex:
RNS.log(f"Channel: Error running message callback: {ex}", RNS.LOG_ERROR)
except Exception as e:
RNS.log("Channel "+str(self)+" experienced an error while running a message callback. The contained exception was: "+str(e), RNS.LOG_ERROR)
def _receive(self, raw: bytes):
try:
envelope = Envelope(outlet=self._outlet, raw=raw)
with self._lock:
message = envelope.unpack(self._message_factories)
prev_env = self._rx_ring[0] if len(self._rx_ring) > 0 else None
if prev_env and envelope.sequence != (prev_env.sequence + 1) % 0x10000:
RNS.log("Channel: Out of order packet received", RNS.LOG_DEBUG)
return
if envelope.sequence < self._next_rx_sequence:
window_overflow = (self._next_rx_sequence+Channel.WINDOW_MAX) % Channel.SEQ_MODULUS
if window_overflow < self._next_rx_sequence:
if envelope.sequence > window_overflow:
RNS.log("Invalid packet sequence ("+str(envelope.sequence)+") received on channel "+str(self), RNS.LOG_EXTREME)
return
else:
RNS.log("Invalid packet sequence ("+str(envelope.sequence)+") received on channel "+str(self), RNS.LOG_EXTREME)
return
is_new = self._emplace_envelope(envelope, self._rx_ring)
self._prune_rx_ring()
if not is_new:
RNS.log("Channel: Duplicate message received", RNS.LOG_DEBUG)
RNS.log("Duplicate message received on channel "+str(self), RNS.LOG_EXTREME)
return
RNS.log(f"Message received: {message}", RNS.LOG_DEBUG)
threading.Thread(target=self._run_callbacks, name="Message Callback", args=[message], daemon=True).start()
except Exception as ex:
RNS.log(f"Channel: Error receiving data: {ex}")
else:
with self._lock:
contigous = []
for e in self._rx_ring:
if e.sequence == self._next_rx_sequence:
contigous.append(e)
self._next_rx_sequence = (self._next_rx_sequence + 1) % Channel.SEQ_MODULUS
for e in contigous:
if not e.unpacked:
m = e.unpack(self._message_factories)
else:
m = e.message
self._rx_ring.remove(e)
self._run_callbacks(m)
except Exception as e:
RNS.log("An error ocurred while receiving data on "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
def is_ready_to_send(self) -> bool:
"""
@@ -377,18 +456,18 @@ class Channel(contextlib.AbstractContextManager):
:return: True if ready
"""
if not self._outlet.is_usable:
RNS.log("Channel: Link is not usable.", RNS.LOG_EXTREME)
return False
with self._lock:
outstanding = 0
for envelope in self._tx_ring:
if envelope.outlet == self._outlet and (not envelope.packet
or self._outlet.get_packet_state(envelope.packet) == MessageState.MSGSTATE_SENT):
# TODO: Check if this should be enabled with some kind of
# rate limiting, since it currently floods log output when
# messages are waiting.
# RNS.log("Channel: Link has a pending message.", RNS.LOG_EXTREME)
return False
if envelope.outlet == self._outlet:
if not envelope.packet or not self._outlet.get_packet_state(envelope.packet) == MessageState.MSGSTATE_DELIVERED:
outstanding += 1
if outstanding >= self.window:
return False
return True
def _packet_tx_op(self, packet: TPacket, op: Callable[[TPacket], bool]):
@@ -399,10 +478,40 @@ class Channel(contextlib.AbstractContextManager):
envelope.tracked = False
if envelope in self._tx_ring:
self._tx_ring.remove(envelope)
if self.window < self.window_max:
self.window += 1
if (self.window - self.window_min) > (self.window_flexibility-1):
self.window_min += 1
# TODO: Remove at some point
# RNS.log("Increased "+str(self)+" window to "+str(self.window), RNS.LOG_EXTREME)
if self._outlet.rtt != 0:
if self._outlet.rtt > Channel.RTT_FAST:
self.fast_rate_rounds = 0
if self._outlet.rtt > Channel.RTT_MEDIUM:
self.medium_rate_rounds = 0
else:
self.medium_rate_rounds += 1
if self.window_max < Channel.WINDOW_MAX_MEDIUM and self.medium_rate_rounds == Channel.FAST_RATE_THRESHOLD:
self.window_max = Channel.WINDOW_MAX_MEDIUM
# TODO: Remove at some point
# RNS.log("Increased "+str(self)+" max window to "+str(self.window_max), RNS.LOG_EXTREME)
else:
self.fast_rate_rounds += 1
if self.window_max < Channel.WINDOW_MAX_FAST and self.fast_rate_rounds == Channel.FAST_RATE_THRESHOLD:
self.window_max = Channel.WINDOW_MAX_FAST
# TODO: Remove at some point
# RNS.log("Increased "+str(self)+" max window to "+str(self.window_max), RNS.LOG_EXTREME)
else:
RNS.log("Channel: Envelope not found in TX ring", RNS.LOG_DEBUG)
RNS.log("Envelope not found in TX ring for "+str(self), RNS.LOG_EXTREME)
if not envelope:
RNS.log("Channel: Spurious message received.", RNS.LOG_EXTREME)
RNS.log("Spurious message received on "+str(self), RNS.LOG_EXTREME)
def _packet_delivered(self, packet: TPacket):
self._packet_tx_op(packet, lambda env: True)
@@ -413,13 +522,25 @@ class Channel(contextlib.AbstractContextManager):
def _packet_timeout(self, packet: TPacket):
def retry_envelope(envelope: Envelope) -> bool:
if envelope.tries >= self._max_tries:
RNS.log("Channel: Retry count exceeded, tearing down Link.", RNS.LOG_ERROR)
RNS.log("Retry count exceeded on "+str(self)+", tearing down Link.", RNS.LOG_ERROR)
self._shutdown() # start on separate thread?
self._outlet.timed_out()
return True
envelope.tries += 1
self._outlet.resend(envelope.packet)
self._outlet.set_packet_delivered_callback(envelope.packet, self._packet_delivered)
self._outlet.set_packet_timeout_callback(envelope.packet, self._packet_timeout, self._get_packet_timeout_time(envelope.tries))
if self.window > self.window_min:
self.window -= 1
if self.window_max > self.window_min:
self.window_max -= 1
if (self.window_max - self.window) > (self.window_flexibility-1):
self.window_max -= 1
# TODO: Remove at some point
# RNS.log("Decreased "+str(self)+" window to "+str(self.window), RNS.LOG_EXTREME)
return False
if self._outlet.get_packet_state(packet) != MessageState.MSGSTATE_DELIVERED:
@@ -436,19 +557,23 @@ class Channel(contextlib.AbstractContextManager):
with self._lock:
if not self.is_ready_to_send():
raise ChannelException(CEType.ME_LINK_NOT_READY, f"Link is not ready")
envelope = Envelope(self._outlet, message=message, sequence=self._next_sequence)
self._next_sequence = (self._next_sequence + 1) % 0x10000
self._next_sequence = (self._next_sequence + 1) % Channel.SEQ_MODULUS
self._emplace_envelope(envelope, self._tx_ring)
if envelope is None:
raise BlockingIOError()
envelope.pack()
if len(envelope.raw) > self._outlet.mdu:
raise ChannelException(CEType.ME_TOO_BIG, f"Packed message too big for packet: {len(envelope.raw)} > {self._outlet.mdu}")
envelope.packet = self._outlet.send(envelope.raw)
envelope.tries += 1
self._outlet.set_packet_delivered_callback(envelope.packet, self._packet_delivered)
self._outlet.set_packet_timeout_callback(envelope.packet, self._packet_timeout, self._get_packet_timeout_time(envelope.tries))
return envelope
@property
@@ -482,8 +607,8 @@ class LinkChannelOutlet(ChannelOutletBase):
return packet
def resend(self, packet: RNS.Packet) -> RNS.Packet:
RNS.log("Resending packet " + RNS.prettyhexrep(packet.packet_hash), RNS.LOG_DEBUG)
if not packet.resend():
receipt = packet.resend()
if not receipt:
RNS.log("Failed to resend packet", RNS.LOG_ERROR)
return packet
@@ -538,4 +663,7 @@ class LinkChannelOutlet(ChannelOutletBase):
packet.receipt.set_delivery_callback(inner if callback else None)
def get_packet_id(self, packet: RNS.Packet) -> any:
return packet.get_hash()
if packet and hasattr(packet, "get_hash") and callable(packet.get_hash):
return packet.get_hash()
else:
return None
+1 -1
View File
@@ -452,7 +452,7 @@ class Identity:
return False
except Exception as e:
RNS.log("Error while loading identity from "+str(path), RNS.LOG_ERROR)
RNS.log("The contained exception was: "+str(e))
RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR)
def get_salt(self):
return self.hash
+1 -1
View File
@@ -1003,7 +1003,7 @@ class RNodeInterface(Interface):
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):
elif (byte == KISS.ERROR_TXFAILED):
RNS.log(str(self)+" hardware TX error (code "+RNS.hexrep(byte)+")", RNS.LOG_ERROR)
raise IOError("Hardware transmit failure")
else:
+37 -24
View File
@@ -21,6 +21,7 @@
# SOFTWARE.
from .Interface import Interface
from collections import deque
import socketserver
import threading
import re
@@ -50,6 +51,8 @@ class AutoInterface(Interface):
BITRATE_GUESS = 10*1000*1000
MULTI_IF_DEQUE_LEN = 64
def handler_factory(self, callback):
def create_handler(*args, **keys):
return AutoInterfaceHandler(callback, *args, **keys)
@@ -62,16 +65,18 @@ class AutoInterface(Interface):
link_local_addr = re.sub(r"fe80:[0-9a-f]*::","fe80::", link_local_addr)
return link_local_addr
def __init__(self, owner, name, group_id=None, discovery_scope=None, discovery_port=None, data_port=None, allowed_interfaces=None, ignored_interfaces=None, configured_bitrate=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()
def list_interfaces(self):
ifs = self.netinfo.interfaces()
return ifs
def list_addresses(self, ifname):
ifas = self.netinfo.ifaddresses(ifname)
return ifas
def __init__(self, owner, name, group_id=None, discovery_scope=None, discovery_port=None, data_port=None, allowed_interfaces=None, ignored_interfaces=None, configured_bitrate=None):
from RNS.vendor.ifaddr import niwrapper
self.netinfo = niwrapper
self.netifaces = netifaces
self.rxb = 0
self.txb = 0
@@ -87,6 +92,7 @@ class AutoInterface(Interface):
self.interface_servers = {}
self.multicast_echoes = {}
self.timed_out_interfaces = {}
self.mif_deque = deque(maxlen=AutoInterface.MULTI_IF_DEQUE_LEN)
self.carrier_changed = False
self.outbound_udp_socket = None
@@ -148,7 +154,7 @@ class AutoInterface(Interface):
self.mcast_discovery_address = "ff1"+self.discovery_scope+":"+gt
suitable_interfaces = 0
for ifname in self.netifaces.interfaces():
for ifname in self.list_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":
@@ -163,10 +169,10 @@ class AutoInterface(Interface):
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:
addresses = self.list_addresses(ifname)
if self.netinfo.AF_INET6 in addresses:
link_local_addr = None
for address in addresses[self.netifaces.AF_INET6]:
for address in addresses[self.netinfo.AF_INET6]:
if "addr" in address:
if address["addr"].startswith("fe80:"):
link_local_addr = self.descope_linklocal(address["addr"])
@@ -196,7 +202,11 @@ class AutoInterface(Interface):
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)
if self.discovery_scope == AutoInterface.SCOPE_LINK:
addr_info = socket.getaddrinfo(mcast_addr+"%"+ifname, self.discovery_port, socket.AF_INET6, socket.SOCK_DGRAM)
else:
addr_info = socket.getaddrinfo(mcast_addr, self.discovery_port, socket.AF_INET6, socket.SOCK_DGRAM)
discovery_socket.bind(addr_info[0][4])
# Set up thread for discovery packets
@@ -214,6 +224,11 @@ class AutoInterface(Interface):
else:
self.receives = True
if configured_bitrate != None:
self.bitrate = configured_bitrate
else:
self.bitrate = AutoInterface.BITRATE_GUESS
peering_wait = self.announce_interval*1.2
RNS.log(str(self)+" discovering peers for "+str(round(peering_wait, 2))+" seconds...", RNS.LOG_VERBOSE)
@@ -238,11 +253,6 @@ class AutoInterface(Interface):
time.sleep(peering_wait)
if configured_bitrate != None:
self.bitrate = configured_bitrate
else:
self.bitrate = AutoInterface.BITRATE_GUESS
self.online = True
@@ -283,10 +293,10 @@ class AutoInterface(Interface):
for ifname in self.adopted_interfaces:
# Check that the link-local address has not changed
try:
addresses = self.netifaces.ifaddresses(ifname)
if self.netifaces.AF_INET6 in addresses:
addresses = self.list_addresses(ifname)
if self.netinfo.AF_INET6 in addresses:
link_local_addr = None
for address in addresses[self.netifaces.AF_INET6]:
for address in addresses[self.netinfo.AF_INET6]:
if "addr" in address:
if address["addr"].startswith("fe80:"):
link_local_addr = self.descope_linklocal(address["addr"])
@@ -385,8 +395,11 @@ class AutoInterface(Interface):
self.peers[addr][1] = time.time()
def processIncoming(self, data):
self.rxb += len(data)
self.owner.inbound(data, self)
data_hash = RNS.Identity.full_hash(data)
if not data_hash in self.mif_deque:
self.mif_deque.append(data_hash)
self.rxb += len(data)
self.owner.inbound(data, self)
def processOutgoing(self,data):
for peer in self.peers:
+7 -2
View File
@@ -41,7 +41,13 @@ class HDLC():
return data
class ThreadingTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
pass
def server_bind(self):
if RNS.vendor.platformutils.is_windows():
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_EXCLUSIVEADDRUSE, 1)
else:
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.socket.bind(self.server_address)
self.server_address = self.socket.getsockname()
class LocalClientInterface(Interface):
RECONNECT_WAIT = 3
@@ -299,7 +305,6 @@ class LocalServerInterface(Interface):
address = (self.bind_ip, self.bind_port)
ThreadingTCPServer.allow_reuse_address = True
self.server = ThreadingTCPServer(address, handlerFactory(self.incoming_connection))
thread = threading.Thread(target=self.server.serve_forever)
+1 -1
View File
@@ -631,7 +631,7 @@ class RNodeInterface(Interface):
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):
elif (byte == KISS.ERROR_TXFAILED):
RNS.log(str(self)+" hardware TX error (code "+RNS.hexrep(byte)+")", RNS.LOG_ERROR)
raise IOError("Hardware transmit failure")
else:
+6 -16
View File
@@ -408,25 +408,15 @@ class TCPServerInterface(Interface):
@staticmethod
def get_address_for_if(name):
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()
import RNS.vendor.ifaddr.niwrapper as netinfo
ifaddr = netinfo.ifaddresses(name)
return ifaddr[netinfo.AF_INET][0]["addr"]
@staticmethod
def get_broadcast_for_if(name):
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()
import RNS.vendor.ifaddr.niwrapper as netinfo
ifaddr = netinfo.ifaddresses(name)
return ifaddr[netinfo.AF_INET][0]["broadcast"]
def __init__(self, owner, name, device=None, bindip=None, bindport=None, i2p_tunneled=False):
self.rxb = 0
+6 -16
View File
@@ -34,25 +34,15 @@ class UDPInterface(Interface):
@staticmethod
def get_address_for_if(name):
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()
import RNS.vendor.ifaddr.niwrapper as netinfo
ifaddr = netinfo.ifaddresses(name)
return ifaddr[netinfo.AF_INET][0]["addr"]
@staticmethod
def get_broadcast_for_if(name):
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()
import RNS.vendor.ifaddr.niwrapper as netinfo
ifaddr = netinfo.ifaddresses(name)
return ifaddr[netinfo.AF_INET][0]["broadcast"]
def __init__(self, owner, name, device=None, bindip=None, bindport=None, forwardip=None, forwardport=None):
self.rxb = 0
+27 -9
View File
@@ -148,6 +148,7 @@ class Link:
self.pending_requests = []
self.last_inbound = 0
self.last_outbound = 0
self.last_proof = 0
self.tx = 0
self.rx = 0
self.txbytes = 0
@@ -224,15 +225,18 @@ class Link:
self.hash = self.link_id
def handshake(self):
self.status = Link.HANDSHAKE
self.shared_key = self.prv.exchange(self.peer_pub)
if self.status == Link.PENDING and self.prv != None:
self.status = Link.HANDSHAKE
self.shared_key = self.prv.exchange(self.peer_pub)
self.derived_key = RNS.Cryptography.hkdf(
length=32,
derive_from=self.shared_key,
salt=self.get_salt(),
context=self.get_context(),
)
self.derived_key = RNS.Cryptography.hkdf(
length=32,
derive_from=self.shared_key,
salt=self.get_salt(),
context=self.get_context(),
)
else:
RNS.log("Handshake attempt on "+str(self)+" with invalid state "+str(self.status), RNS.LOG_ERROR)
def prove(self):
@@ -277,6 +281,7 @@ class Link:
self.__remote_identity = self.destination.identity
self.status = Link.ACTIVE
self.activated_at = time.time()
self.last_proof = self.activated_at
RNS.Transport.activate_link(self)
RNS.log("Link "+str(self)+" established with "+str(self.destination)+", RTT is "+str(round(self.rtt, 3))+"s", RNS.LOG_VERBOSE)
@@ -527,7 +532,7 @@ class Link:
elif self.status == Link.ACTIVE:
activated_at = self.activated_at if self.activated_at != None else 0
last_inbound = max(self.last_inbound, activated_at)
last_inbound = max(max(self.last_inbound, self.last_proof), activated_at)
if time.time() >= last_inbound + self.keepalive:
if self.initiator:
@@ -809,6 +814,19 @@ class Link:
if not self._channel:
RNS.log(f"Channel data received without open channel", RNS.LOG_DEBUG)
else:
# TODO: Remove packet loss simulator ######
# if not hasattr(self, "drop_counter"):
# self.drop_counter = 0
# self.drop_counter += 1
# if self.drop_counter%6 == 0:
# RNS.log("Dropping channel packet for testing", RNS.LOG_DEBUG)
# else:
# packet.prove()
# plaintext = self.decrypt(packet.data)
# self._channel._receive(plaintext)
############################################
packet.prove()
plaintext = self.decrypt(packet.data)
self._channel._receive(plaintext)
+14 -4
View File
@@ -141,7 +141,7 @@ class Packet:
def get_packed_flags(self):
if self.context == Packet.LRPROOF:
packed_flags = (self.header_type << 6) | (self.transport_type << 4) | RNS.Destination.LINK | self.packet_type
packed_flags = (self.header_type << 6) | (self.transport_type << 4) | (RNS.Destination.LINK << 2) | self.packet_type
else:
packed_flags = (self.header_type << 6) | (self.transport_type << 4) | (self.destination.type << 2) | self.packet_type
return packed_flags
@@ -172,8 +172,8 @@ class Packet:
# Packet proofs over links are not encrypted
self.ciphertext = self.data
elif self.context == Packet.RESOURCE:
# A resource takes care of symmetric
# encryption by itself
# A resource takes care of encryption
# by itself
self.ciphertext = self.data
elif self.context == Packet.KEEPALIVE:
# Keepalive packets contain no actual
@@ -276,6 +276,10 @@ class Packet:
:returns: A :ref:`RNS.PacketReceipt<api-packetreceipt>` instance if *create_receipt* was set to *True* when the packet was instantiated, if not returns *None*. If the packet could not be sent *False* is returned.
"""
if self.sent:
# Re-pack the packet to obtain new ciphertext for
# encrypted destinations
self.pack()
if RNS.Transport.outbound(self):
return self.receipt
else:
@@ -396,9 +400,15 @@ class PacketReceipt:
self.proved = True
self.concluded_at = time.time()
self.proof_packet = proof_packet
link.last_proof = self.concluded_at
if self.callbacks.delivery != None:
self.callbacks.delivery(self)
try:
self.callbacks.delivery(self)
except Exception as e:
RNS.log("An error occurred while evaluating external delivery callback for "+str(link), RNS.LOG_ERROR)
RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR)
return True
else:
return False
+1 -1
View File
@@ -149,7 +149,7 @@ class Resource:
resource.total_parts = int(math.ceil(resource.size/float(Resource.SDU)))
resource.received_count = 0
resource.outstanding_parts = 0
resource.parts = [None] * resource.total_parts
resource.parts = [None] * resource.total_parts
resource.window = Resource.WINDOW
resource.window_max = Resource.WINDOW_MAX_SLOW
resource.window_min = Resource.WINDOW_MIN
+4 -1
View File
@@ -167,7 +167,7 @@ class Reticulum:
RNS.exit()
def __init__(self,configdir=None, loglevel=None, logdest=None):
def __init__(self,configdir=None, loglevel=None, logdest=None, verbosity=None):
"""
Initialises and starts a Reticulum instance. This must be
done before any other operations, and Reticulum will not
@@ -211,6 +211,7 @@ class Reticulum:
self.ifac_salt = Reticulum.IFAC_SALT
self.requested_loglevel = loglevel
self.requested_verbosity = verbosity
if self.requested_loglevel != None:
if self.requested_loglevel > RNS.LOG_EXTREME:
self.requested_loglevel = RNS.LOG_EXTREME
@@ -337,6 +338,8 @@ class Reticulum:
value = self.config["logging"][option]
if option == "loglevel" and self.requested_loglevel == None:
RNS.loglevel = int(value)
if self.requested_verbosity != None:
RNS.loglevel += self.requested_verbosity
if RNS.loglevel < 0:
RNS.loglevel = 0
if RNS.loglevel > 7:
+3 -2
View File
@@ -334,7 +334,8 @@ class Transport:
for receipt in Transport.receipts:
receipt.check_timeout()
if receipt.status != RNS.PacketReceipt.SENT:
Transport.receipts.remove(receipt)
if receipt in Transport.receipts:
Transport.receipts.remove(receipt)
Transport.receipts_last_checked = time.time()
@@ -1584,7 +1585,7 @@ class Transport:
if (RNS.Reticulum.transport_enabled() or from_local_client or proof_for_local_client) and packet.destination_hash in Transport.reverse_table:
reverse_entry = Transport.reverse_table.pop(packet.destination_hash)
if packet.receiving_interface == reverse_entry[1]:
RNS.log("Proof received on correct interface, transporting it via "+str(reverse_entry[0]), RNS.LOG_DEBUG)
RNS.log("Proof received on correct interface, transporting it via "+str(reverse_entry[0]), RNS.LOG_EXTREME)
new_raw = packet.raw[0:1]
new_raw += struct.pack("!B", packet.hops)
new_raw += packet.raw[2:]
+6 -1
View File
@@ -226,7 +226,12 @@ def send(configdir, verbosity = 0, quietness = 0, destination = None, file = Non
identity_path = RNS.Reticulum.identitypath+"/"+APP_NAME
if os.path.isfile(identity_path):
identity = RNS.Identity.from_file(identity_path)
identity = RNS.Identity.from_file(identity_path)
if identity == None:
RNS.log("Could not load identity for rncp. The identity file at \""+str(identity_path)+"\" may be corrupt or unreadable.", RNS.LOG_ERROR)
exit(2)
else:
identity = None
if identity == None:
RNS.log("No valid saved identity found, creating new...", RNS.LOG_INFO)
+1 -1
View File
@@ -88,7 +88,7 @@ def main():
parser.add_argument("-b", "--base64", action="store_true", default=False, help=argparse.SUPPRESS) # help="Use base64-encoded input and output")
parser.add_argument("--version", action="version", version="rncp {version}".format(version=__version__))
parser.add_argument("--version", action="version", version="rnid {version}".format(version=__version__))
args = parser.parse_args()
+213 -42
View File
@@ -1,4 +1,4 @@
#!python3
#!/usr/bin/env python3
# MIT License
#
@@ -25,6 +25,7 @@
from time import sleep
import argparse
import threading
import sys
import os
import os.path
import struct
@@ -32,6 +33,7 @@ import datetime
import time
import math
import hashlib
import zipfile
from urllib.request import urlretrieve
from importlib import util
import RNS
@@ -39,7 +41,7 @@ import RNS
RNS.logtimefmt = "%H:%M:%S"
RNS.compact_log_fmt = True
program_version = "2.1.1"
program_version = "2.1.2"
eth_addr = "0x81F7B979fEa6134bA9FD5c701b3501A2e61E897a"
btc_addr = "3CPmacGm34qYvR6XWLVEJmi2aNe3PZqUuq"
xmr_addr = "87HcDx6jRSkMQ9nPRd5K9hGGpZLn2s7vWETjMaVM5KfV4TD36NcYa8J8WSxhTSvBzzFpqDwp2fg5GX2moZ7VAP9QMZCZGET"
@@ -77,6 +79,7 @@ class KISS():
CMD_STAT_SNR = 0x24
CMD_BLINK = 0x30
CMD_RANDOM = 0x40
CMD_DISP_INT = 0x45
CMD_BT_CTRL = 0x46
CMD_BT_PIN = 0x62
CMD_BOARD = 0x47
@@ -129,6 +132,10 @@ class ROM():
MODEL_A2 = 0xA2
MODEL_A7 = 0xA7
PRODUCT_T32_10 = 0xB2
MODEL_BA = 0xBA
MODEL_BB = 0xBB
PRODUCT_T32_20 = 0xB0
MODEL_B3 = 0xB3
MODEL_B8 = 0xB8
@@ -180,6 +187,7 @@ products = {
ROM.PRODUCT_RNODE: "RNode",
ROM.PRODUCT_HMBRW: "Hombrew RNode",
ROM.PRODUCT_TBEAM: "LilyGO T-Beam",
ROM.PRODUCT_T32_10: "LilyGO LoRa32 v1.0",
ROM.PRODUCT_T32_20: "LilyGO LoRa32 v2.0",
ROM.PRODUCT_T32_21: "LilyGO LoRa32 v2.1",
ROM.PRODUCT_H32_V2: "Heltec LoRa32 v2",
@@ -207,6 +215,8 @@ models = {
0xB8: [850000000, 950000000, 17, "850 - 950 MHz", "rnode_firmware_lora32v20.zip"],
0xB4: [420000000, 520000000, 17, "420 - 520 MHz", "rnode_firmware_lora32v21.zip"],
0xB9: [850000000, 950000000, 17, "850 - 950 MHz", "rnode_firmware_lora32v21.zip"],
0xBA: [420000000, 520000000, 17, "420 - 520 MHz", "rnode_firmware_lora32v10.zip"],
0xBB: [850000000, 950000000, 17, "850 - 950 MHz", "rnode_firmware_lora32v10.zip"],
0xC4: [420000000, 520000000, 17, "420 - 520 MHz", "rnode_firmware_heltec32v2.zip"],
0xC9: [850000000, 950000000, 17, "850 - 950 MHz", "rnode_firmware_heltec32v2.zip"],
0xE4: [420000000, 520000000, 17, "420 - 520 MHz", "rnode_firmware_tbeam.zip"],
@@ -226,6 +236,7 @@ try:
FWD_DIR = CNF_DIR+"/firmware"
EXT_DIR = CNF_DIR+"/extracted"
RT_PATH = CNF_DIR+"/recovery_esptool.py"
TK_DIR = CNF_DIR+"/trusted_keys"
if not os.path.isdir(CNF_DIR):
os.makedirs(CNF_DIR)
@@ -235,6 +246,8 @@ try:
os.makedirs(FWD_DIR)
if not os.path.isdir(EXT_DIR):
os.makedirs(EXT_DIR)
if not os.path.isdir(TK_DIR):
os.makedirs(TK_DIR)
except Exception as e:
print("No access to directory "+str(CNF_DIR)+". This utility needs file system access to store firmware and data files. Cannot continue.")
@@ -556,6 +569,13 @@ class RNode():
if written != len(kiss_command):
raise IOError("An IO error occurred while sending host left command to device")
def set_display_intensity(self, intensity):
data = bytes([intensity & 0xFF])
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_DISP_INT])+data+bytes([KISS.FEND])
written = self.serial.write(kiss_command)
if written != len(kiss_command):
raise IOError("An IO error occurred while sending bluetooth enable command to device")
def enable_bluetooth(self):
kiss_command = bytes([KISS.FEND, KISS.CMD_BT_CTRL, 0x01, KISS.FEND])
written = self.serial.write(kiss_command)
@@ -800,6 +820,35 @@ class RNode():
RNS.log("Could not deserialize local signing key")
RNS.log(str(e))
# Try loading trusted signing key for
# validation of devices
if os.path.isdir(TK_DIR):
for f in os.listdir(TK_DIR):
if os.path.isfile(TK_DIR+"/"+f) and f.endswith(".pubkey"):
try:
file = open(TK_DIR+"/"+f, "rb")
public_bytes = file.read()
file.close()
try:
public_bytes_hex = RNS.hexrep(public_bytes, delimit=False)
vendor_keys = []
for known in known_keys:
vendor_keys.append(known[1])
if not public_bytes_hex in vendor_keys:
local_key_entry = ["LOCAL", public_bytes_hex]
known_keys.append(local_key_entry)
except Exception as e:
RNS.log("Could not deserialize trusted signing key "+str(f))
RNS.log(str(e))
except Exception as e:
RNS.log("Could not load trusted signing key"+str(f))
for known in known_keys:
vendor = known[0]
public_hexrep = known[1]
@@ -1090,6 +1139,8 @@ def main():
parser.add_argument("-B", "--bluetooth-off", action="store_true", help="Turn device bluetooth off")
parser.add_argument("-p", "--bluetooth-pair", action="store_true", help="Put device into bluetooth pairing mode")
parser.add_argument("-D", "--display", action="store", metavar="i", type=int, default=None, help="Set display intensity (0-255)")
parser.add_argument("--freq", action="store", metavar="Hz", type=int, default=None, help="Frequency in Hz for TNC mode")
parser.add_argument("--bw", action="store", metavar="Hz", type=int, default=None, help="Bandwidth in Hz for TNC mode")
parser.add_argument("--txp", action="store", metavar="dBm", type=int, default=None, help="TX power in dBm for TNC mode")
@@ -1100,12 +1151,14 @@ def main():
parser.add_argument("--eeprom-dump", action="store_true", help="Dump EEPROM to console")
parser.add_argument("--eeprom-wipe", action="store_true", help="Unlock and wipe EEPROM")
parser.add_argument("-P", "--public", action="store_true", help="Display public part of signing key")
parser.add_argument("--trust-key", action="store", metavar="hexbytes", type=str, default=None, help="Public key to trust for device verification")
parser.add_argument("--version", action="store_true", help="Print program version and exit")
parser.add_argument("-f", "--flash", action="store_true", help=argparse.SUPPRESS) # Flash firmware and bootstrap EEPROM
parser.add_argument("-r", "--rom", action="store_true", help=argparse.SUPPRESS) # Bootstrap EEPROM without flashing firmware
parser.add_argument("-k", "--key", action="store_true", help=argparse.SUPPRESS) # Generate a new signing key and exit
parser.add_argument("-P", "--public", action="store_true", help=argparse.SUPPRESS) # Display public part of signing key
parser.add_argument("-S", "--sign", action="store_true", help=argparse.SUPPRESS) # Display public part of signing key
parser.add_argument("-H", "--firmware-hash", action="store", help=argparse.SUPPRESS) # Display public part of signing key
parser.add_argument("--platform", action="store", metavar="platform", type=str, default=None, help=argparse.SUPPRESS) # Platform specification for device bootstrap
@@ -1150,7 +1203,7 @@ def main():
if args.nocheck:
upd_nocheck = True
if args.public or args.key or args.flash or args.rom or args.autoinstall:
if args.public or args.key or args.flash or args.rom or args.autoinstall or args.trust_key:
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
@@ -1161,6 +1214,25 @@ def main():
clear = lambda: os.system('clear')
if args.trust_key:
try:
public_bytes = bytes.fromhex(args.trust_key)
try:
public_key = load_der_public_key(public_bytes, backend=default_backend())
key_hash = hashlib.sha256(public_bytes).hexdigest()
RNS.log("Trusting key: "+str(key_hash))
f = open(TK_DIR+"/"+str(key_hash)+".pubkey", "wb")
f.write(public_bytes)
f.close()
except Exception as e:
RNS.log("Could not create public key from supplied data. Check that the key format is valid.")
RNS.log(str(e))
except Exception as e:
RNS.log("Invalid key data supplied")
exit(0)
if args.use_extracted and ((args.update and args.port != None) or args.autoinstall):
print("")
print("You have specified that rnodeconf should use a firmware extracted")
@@ -1249,7 +1321,16 @@ def main():
else:
RNS.log("Could not detect a connected RNode")
if rnode.provisioned and rnode.signature_valid:
if rnode.provisioned:
if not rnode.signature_valid:
print("\nThe device signature in this RNode is unknown and cannot be verified. It is still")
print("possible to extract the firmware from it, but you should make absolutely sure that")
print("it comes from a trusted source. It is possible that someone could have modified the")
print("firmware. If that is the case, these modifications will propagate to any new RNodes")
print("descendent from this one!")
print("\nHit enter if you are sure you want to continue.")
input()
if rnode.firmware_hash != None:
extracted_hash = rnode.firmware_hash
extracted_version = rnode.version
@@ -1267,11 +1348,11 @@ def main():
hash_f.close()
extraction_parts = [
("bootloader", "python \""+CNF_DIR+"/recovery_esptool.py\" --chip esp32 --port /dev/ttyACM1 --baud 921600 --before default_reset --after hard_reset read_flash 0x1000 0x4650 \""+EXT_DIR+"/extracted_rnode_firmware.bootloader\""),
("partition table", "python \""+CNF_DIR+"/recovery_esptool.py\" --chip esp32 --port /dev/ttyACM1 --baud 921600 --before default_reset --after hard_reset read_flash 0x8000 0xC00 \""+EXT_DIR+"/extracted_rnode_firmware.partitions\""),
("app boot", "python \""+CNF_DIR+"/recovery_esptool.py\" --chip esp32 --port /dev/ttyACM1 --baud 921600 --before default_reset --after hard_reset read_flash 0xe000 0x2000 \""+EXT_DIR+"/extracted_rnode_firmware.boot_app0\""),
("application image", "python \""+CNF_DIR+"/recovery_esptool.py\" --chip esp32 --port /dev/ttyACM1 --baud 921600 --before default_reset --after hard_reset read_flash 0x10000 0x200000 \""+EXT_DIR+"/extracted_rnode_firmware.bin\""),
("console image", "python \""+CNF_DIR+"/recovery_esptool.py\" --chip esp32 --port /dev/ttyACM1 --baud 921600 --before default_reset --after hard_reset read_flash 0x210000 0x1F0000 \""+EXT_DIR+"/extracted_console_image.bin\""),
("bootloader", "python \""+CNF_DIR+"/recovery_esptool.py\" --chip esp32 --port "+port_path+" --baud 921600 --before default_reset --after hard_reset read_flash 0x1000 0x4650 \""+EXT_DIR+"/extracted_rnode_firmware.bootloader\""),
("partition table", "python \""+CNF_DIR+"/recovery_esptool.py\" --chip esp32 --port "+port_path+" --baud 921600 --before default_reset --after hard_reset read_flash 0x8000 0xC00 \""+EXT_DIR+"/extracted_rnode_firmware.partitions\""),
("app boot", "python \""+CNF_DIR+"/recovery_esptool.py\" --chip esp32 --port "+port_path+" --baud 921600 --before default_reset --after hard_reset read_flash 0xe000 0x2000 \""+EXT_DIR+"/extracted_rnode_firmware.boot_app0\""),
("application image", "python \""+CNF_DIR+"/recovery_esptool.py\" --chip esp32 --port "+port_path+" --baud 921600 --before default_reset --after hard_reset read_flash 0x10000 0x200000 \""+EXT_DIR+"/extracted_rnode_firmware.bin\""),
("console image", "python \""+CNF_DIR+"/recovery_esptool.py\" --chip esp32 --port "+port_path+" --baud 921600 --before default_reset --after hard_reset read_flash 0x210000 0x1F0000 \""+EXT_DIR+"/extracted_console_image.bin\""),
]
import subprocess, shlex
for part, command in extraction_parts:
@@ -1400,8 +1481,9 @@ def main():
print("")
print("[3] LilyGO LoRa32 v2.1 (aka T3 v1.6 / T3 v1.6.1)")
print("[4] LilyGO LoRa32 v2.0")
print("[5] LilyGO T-Beam")
print("[6] Heltec LoRa32 v2")
print("[5] LilyGO LoRa32 v1.0")
print("[6] LilyGO T-Beam")
print("[7] Heltec LoRa32 v2")
print(" .")
print(" / \\ Select one of these options if you want to easily turn")
print(" | a supported development board into an RNode.")
@@ -1433,7 +1515,7 @@ def main():
print("who would like to experiment with it. Hit enter to continue.")
print("---------------------------------------------------------------------------")
input()
elif c_dev == 5:
elif c_dev == 6:
selected_product = ROM.PRODUCT_TBEAM
clear()
print("")
@@ -1463,6 +1545,25 @@ def main():
print("who would like to experiment with it. Hit enter to continue.")
print("---------------------------------------------------------------------------")
input()
elif c_dev == 5:
selected_product = ROM.PRODUCT_T32_10
clear()
print("")
print("---------------------------------------------------------------------------")
print(" LilyGO LoRa32 v1.0 RNode Installer")
print("")
print("Important! Using RNode firmware on LoRa32 devices should currently be")
print("considered experimental. It is not intended for production or critical use.")
print("The currently supplied firmware is provided AS-IS as a courtesey to those")
print("who would like to experiment with it.")
print("")
print("Please Note! This device is known to have a faulty battery charging circuit,")
print("which can result in overcharging and damaging batteries. If at all possible,")
print("it is recommended to avoid this device.")
print("")
print("Hit enter if you're sure you wish to continue.")
print("---------------------------------------------------------------------------")
input()
elif c_dev == 3:
selected_product = ROM.PRODUCT_T32_21
clear()
@@ -1476,7 +1577,7 @@ def main():
print("who would like to experiment with it. Hit enter to continue.")
print("---------------------------------------------------------------------------")
input()
elif c_dev == 6:
elif c_dev == 7:
selected_product = ROM.PRODUCT_H32_V2
clear()
print("")
@@ -1608,6 +1709,28 @@ def main():
print("That band does not exist, exiting now.")
exit()
elif selected_product == ROM.PRODUCT_T32_10:
selected_mcu = ROM.MCU_ESP32
print("\nWhat band is this LoRa32 for?\n")
print("[1] 433 MHz")
print("[2] 868 MHz")
print("[3] 915 MHz")
print("[4] 923 MHz")
print("\n? ", end="")
try:
c_model = int(input())
if c_model < 1 or c_model > 4:
raise ValueError()
elif c_model == 1:
selected_model = ROM.MODEL_BA
selected_platform = ROM.PLATFORM_ESP32
elif c_model > 1:
selected_model = ROM.MODEL_BB
selected_platform = ROM.PLATFORM_ESP32
except Exception as e:
print("That band does not exist, exiting now.")
exit()
elif selected_product == ROM.PRODUCT_T32_20:
selected_mcu = ROM.MCU_ESP32
print("\nWhat band is this LoRa32 for?\n")
@@ -1929,7 +2052,7 @@ def main():
if fw_filename == "rnode_firmware_tbeam.zip":
if numeric_version >= 1.55:
return [
flasher,
sys.executable, flasher,
"--chip", "esp32",
"--port", args.port,
"--baud", "921600",
@@ -1947,7 +2070,7 @@ def main():
]
else:
return [
flasher,
sys.executable, flasher,
"--chip", "esp32",
"--port", args.port,
"--baud", "921600",
@@ -1962,10 +2085,46 @@ def main():
"0x10000", UPD_DIR+"/"+selected_version+"/rnode_firmware_tbeam.bin",
"0x8000", UPD_DIR+"/"+selected_version+"/rnode_firmware_tbeam.partitions",
]
elif fw_filename == "rnode_firmware_lora32v10.zip":
if numeric_version >= 1.59:
return [
sys.executable, flasher,
"--chip", "esp32",
"--port", args.port,
"--baud", "921600",
"--before", "default_reset",
"--after", "hard_reset",
"write_flash", "-z",
"--flash_mode", "dio",
"--flash_freq", "80m",
"--flash_size", "4MB",
"0xe000", UPD_DIR+"/"+selected_version+"/rnode_firmware_lora32v10.boot_app0",
"0x1000", UPD_DIR+"/"+selected_version+"/rnode_firmware_lora32v10.bootloader",
"0x10000", UPD_DIR+"/"+selected_version+"/rnode_firmware_lora32v10.bin",
"0x210000",UPD_DIR+"/"+selected_version+"/console_image.bin",
"0x8000", UPD_DIR+"/"+selected_version+"/rnode_firmware_lora32v10.partitions",
]
else:
return [
sys.executable, flasher,
"--chip", "esp32",
"--port", args.port,
"--baud", "921600",
"--before", "default_reset",
"--after", "hard_reset",
"write_flash", "-z",
"--flash_mode", "dio",
"--flash_freq", "80m",
"--flash_size", "4MB",
"0xe000", UPD_DIR+"/"+selected_version+"/rnode_firmware_lora32v20.boot_app0",
"0x1000", UPD_DIR+"/"+selected_version+"/rnode_firmware_lora32v20.bootloader",
"0x10000", UPD_DIR+"/"+selected_version+"/rnode_firmware_lora32v20.bin",
"0x8000", UPD_DIR+"/"+selected_version+"/rnode_firmware_lora32v20.partitions",
]
elif fw_filename == "rnode_firmware_lora32v20.zip":
if numeric_version >= 1.55:
return [
flasher,
sys.executable, flasher,
"--chip", "esp32",
"--port", args.port,
"--baud", "921600",
@@ -1983,7 +2142,7 @@ def main():
]
else:
return [
flasher,
sys.executable, flasher,
"--chip", "esp32",
"--port", args.port,
"--baud", "921600",
@@ -2001,7 +2160,7 @@ def main():
elif fw_filename == "rnode_firmware_lora32v21.zip":
if numeric_version >= 1.55:
return [
flasher,
sys.executable, flasher,
"--chip", "esp32",
"--port", args.port,
"--baud", "921600",
@@ -2019,7 +2178,7 @@ def main():
]
else:
return [
flasher,
sys.executable, flasher,
"--chip", "esp32",
"--port", args.port,
"--baud", "921600",
@@ -2037,7 +2196,7 @@ def main():
elif fw_filename == "rnode_firmware_heltec32v2.zip":
if numeric_version >= 1.55:
return [
flasher,
sys.executable, flasher,
"--chip", "esp32",
"--port", args.port,
"--baud", "921600",
@@ -2055,7 +2214,7 @@ def main():
]
else:
return [
flasher,
sys.executable, flasher,
"--chip", "esp32",
"--port", args.port,
"--baud", "921600",
@@ -2073,7 +2232,7 @@ def main():
elif fw_filename == "rnode_firmware_featheresp32.zip":
if numeric_version >= 1.55:
return [
flasher,
sys.executable, flasher,
"--chip", "esp32",
"--port", args.port,
"--baud", "921600",
@@ -2091,7 +2250,7 @@ def main():
]
else:
return [
flasher,
sys.executable, flasher,
"--chip", "esp32",
"--port", args.port,
"--baud", "921600",
@@ -2109,7 +2268,7 @@ def main():
elif fw_filename == "rnode_firmware_esp32_generic.zip":
if numeric_version >= 1.55:
return [
flasher,
sys.executable, flasher,
"--chip", "esp32",
"--port", args.port,
"--baud", "921600",
@@ -2127,7 +2286,7 @@ def main():
]
else:
return [
flasher,
sys.executable, flasher,
"--chip", "esp32",
"--port", args.port,
"--baud", "921600",
@@ -2145,7 +2304,7 @@ def main():
elif fw_filename == "rnode_firmware_ng20.zip":
if numeric_version >= 1.55:
return [
flasher,
sys.executable, flasher,
"--chip", "esp32",
"--port", args.port,
"--baud", "921600",
@@ -2163,7 +2322,7 @@ def main():
]
else:
return [
flasher,
sys.executable, flasher,
"--chip", "esp32",
"--port", args.port,
"--baud", "921600",
@@ -2181,7 +2340,7 @@ def main():
elif fw_filename == "rnode_firmware_ng21.zip":
if numeric_version >= 1.55:
return [
flasher,
sys.executable, flasher,
"--chip", "esp32",
"--port", args.port,
"--baud", "921600",
@@ -2199,7 +2358,7 @@ def main():
]
else:
return [
flasher,
sys.executable, flasher,
"--chip", "esp32",
"--port", args.port,
"--baud", "921600",
@@ -2216,7 +2375,7 @@ def main():
]
elif fw_filename == "extracted_rnode_firmware.zip":
return [
flasher,
sys.executable, flasher,
"--chip", "esp32",
"--port", args.port,
"--baud", "921600",
@@ -2287,12 +2446,13 @@ def main():
try:
if fw_filename.endswith(".zip"):
RNS.log("Decompressing firmware...")
unzip_status = call(get_flasher_call("unzip", fw_filename))
if unzip_status == 0:
RNS.log("Firmware decompressed")
else:
RNS.log("Could not extract firmware from downloaded zip file")
try:
with zipfile.ZipFile(fw_src+fw_filename) as zip:
zip.extractall(fw_src)
except Exception as e:
RNS.log("Could not decompress firmware from downloaded zip file")
exit()
RNS.log("Firmware decompressed")
RNS.log("Flashing RNode firmware to device on "+args.port)
from subprocess import call
@@ -2414,13 +2574,15 @@ def main():
ensure_firmware_file(fw_filename)
if fw_filename.endswith(".zip") and not fw_filename == "extracted_rnode_firmware.zip":
RNS.log("Extracting firmware...")
unzip_status = call(get_flasher_call("unzip", fw_filename))
if unzip_status == 0:
RNS.log("Firmware extracted")
else:
RNS.log("Could not extract firmware from downloaded zip file")
RNS.log("Decompressing firmware...")
fw_src = UPD_DIR+"/"+selected_version+"/"
try:
with zipfile.ZipFile(fw_src+fw_filename) as zip:
zip.extractall(fw_src)
except Exception as e:
RNS.log("Could not decompress firmware from downloaded zip file")
exit()
RNS.log("Firmware decompressed")
except Exception as e:
RNS.log("Could not obtain firmware package for your board")
@@ -2521,6 +2683,15 @@ def main():
RNS.log("but file could not be written to disk.")
exit()
if isinstance(args.display, int):
di = args.display
if di < 0:
di = 0
if di > 255:
di = 255
RNS.log("Setting display intensity to "+str(di))
rnode.set_display_intensity(di)
if args.bluetooth_on:
RNS.log("Enabling Bluetooth...")
rnode.enable_bluetooth()
+3 -3
View File
@@ -30,15 +30,15 @@ from RNS._version import __version__
def program_setup(configdir, verbosity = 0, quietness = 0, service = False):
targetloglevel = 3+verbosity-quietness
targetverbosity = verbosity-quietness
if service:
targetlogdest = RNS.LOG_FILE
targetloglevel = None
targetverbosity = None
else:
targetlogdest = RNS.LOG_STDOUT
reticulum = RNS.Reticulum(configdir=configdir, loglevel=targetloglevel, logdest=targetlogdest)
reticulum = RNS.Reticulum(configdir=configdir, verbosity=targetverbosity, logdest=targetlogdest)
if reticulum.is_connected_to_shared_instance:
RNS.log("Started rnsd version {version} connected to another shared local instance, this is probably NOT what you want!".format(version=__version__), RNS.LOG_WARNING)
else:
+1 -1
View File
@@ -1 +1 @@
__version__ = "0.5.0"
__version__ = "0.5.4"
+33
View File
@@ -0,0 +1,33 @@
# Copyright (c) 2014 Stefan C. Mueller
# 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.
import os
from RNS.vendor.ifaddr._shared import Adapter, IP
if os.name == "nt":
from RNS.vendor.ifaddr._win32 import get_adapters
elif os.name == "posix":
from RNS.vendor.ifaddr._posix import get_adapters
else:
raise RuntimeError("Unsupported Operating System: %s" % os.name)
__all__ = ['Adapter', 'IP', 'get_adapters']
+93
View File
@@ -0,0 +1,93 @@
# Copyright (c) 2014 Stefan C. Mueller
# 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.
import os
import ctypes.util
import ipaddress
import collections
import socket
from typing import Iterable, Optional
import RNS.vendor.ifaddr._shared as shared
class ifaddrs(ctypes.Structure):
pass
ifaddrs._fields_ = [
('ifa_next', ctypes.POINTER(ifaddrs)),
('ifa_name', ctypes.c_char_p),
('ifa_flags', ctypes.c_uint),
('ifa_addr', ctypes.POINTER(shared.sockaddr)),
('ifa_netmask', ctypes.POINTER(shared.sockaddr)),
]
libc = ctypes.CDLL(ctypes.util.find_library("socket" if os.uname()[0] == "SunOS" else "c"), use_errno=True) # type: ignore
def get_adapters(include_unconfigured: bool = False) -> Iterable[shared.Adapter]:
addr0 = addr = ctypes.POINTER(ifaddrs)()
retval = libc.getifaddrs(ctypes.byref(addr))
if retval != 0:
eno = ctypes.get_errno()
raise OSError(eno, os.strerror(eno))
ips = collections.OrderedDict()
def add_ip(adapter_name: str, ip: Optional[shared.IP]) -> None:
if adapter_name not in ips:
index = None # type: Optional[int]
try:
# Mypy errors on this when the Windows CI runs:
# error: Module has no attribute "if_nametoindex"
index = socket.if_nametoindex(adapter_name) # type: ignore
except (OSError, AttributeError):
pass
ips[adapter_name] = shared.Adapter(adapter_name, adapter_name, [], index=index)
if ip is not None:
ips[adapter_name].ips.append(ip)
while addr:
name = addr[0].ifa_name.decode(encoding='UTF-8')
ip_addr = shared.sockaddr_to_ip(addr[0].ifa_addr)
if ip_addr:
if addr[0].ifa_netmask and not addr[0].ifa_netmask[0].sa_familiy:
addr[0].ifa_netmask[0].sa_familiy = addr[0].ifa_addr[0].sa_familiy
netmask = shared.sockaddr_to_ip(addr[0].ifa_netmask)
if isinstance(netmask, tuple):
netmaskStr = str(netmask[0])
prefixlen = shared.ipv6_prefixlength(ipaddress.IPv6Address(netmaskStr))
else:
assert netmask is not None, f'sockaddr_to_ip({addr[0].ifa_netmask}) returned None'
netmaskStr = str('0.0.0.0/' + netmask)
prefixlen = ipaddress.IPv4Network(netmaskStr).prefixlen
ip = shared.IP(ip_addr, prefixlen, name)
add_ip(name, ip)
else:
if include_unconfigured:
add_ip(name, None)
addr = addr[0].ifa_next
libc.freeifaddrs(addr0)
return ips.values()
+198
View File
@@ -0,0 +1,198 @@
# Copyright (c) 2014 Stefan C. Mueller
# 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.
import ctypes
import socket
import ipaddress
import platform
from typing import List, Optional, Tuple, Union
class Adapter(object):
"""
Represents a network interface device controller (NIC), such as a
network card. An adapter can have multiple IPs.
On Linux aliasing (multiple IPs per physical NIC) is implemented
by creating 'virtual' adapters, each represented by an instance
of this class. Each of those 'virtual' adapters can have both
a IPv4 and an IPv6 IP address.
"""
def __init__(self, name: str, nice_name: str, ips: List['IP'], index: Optional[int] = None) -> None:
#: Unique name that identifies the adapter in the system.
#: On Linux this is of the form of `eth0` or `eth0:1`, on
#: Windows it is a UUID in string representation, such as
#: `{846EE342-7039-11DE-9D20-806E6F6E6963}`.
self.name = name
#: Human readable name of the adpater. On Linux this
#: is currently the same as :attr:`name`. On Windows
#: this is the name of the device.
self.nice_name = nice_name
#: List of :class:`ifaddr.IP` instances in the order they were
#: reported by the system.
self.ips = ips
#: Adapter index as used by some API (e.g. IPv6 multicast group join).
self.index = index
def __repr__(self) -> str:
return "Adapter(name={name}, nice_name={nice_name}, ips={ips}, index={index})".format(
name=repr(self.name), nice_name=repr(self.nice_name), ips=repr(self.ips), index=repr(self.index)
)
# Type of an IPv4 address (a string in "xxx.xxx.xxx.xxx" format)
_IPv4Address = str
# Type of an IPv6 address (a three-tuple `(ip, flowinfo, scope_id)`)
_IPv6Address = Tuple[str, int, int]
class IP(object):
"""
Represents an IP address of an adapter.
"""
def __init__(self, ip: Union[_IPv4Address, _IPv6Address], network_prefix: int, nice_name: str) -> None:
#: IP address. For IPv4 addresses this is a string in
#: "xxx.xxx.xxx.xxx" format. For IPv6 addresses this
#: is a three-tuple `(ip, flowinfo, scope_id)`, where
#: `ip` is a string in the usual collon separated
#: hex format.
self.ip = ip
#: Number of bits of the IP that represent the
#: network. For a `255.255.255.0` netmask, this
#: number would be `24`.
self.network_prefix = network_prefix
#: Human readable name for this IP.
#: On Linux is this currently the same as the adapter name.
#: On Windows this is the name of the network connection
#: as configured in the system control panel.
self.nice_name = nice_name
@property
def is_IPv4(self) -> bool:
"""
Returns `True` if this IP is an IPv4 address and `False`
if it is an IPv6 address.
"""
return not isinstance(self.ip, tuple)
@property
def is_IPv6(self) -> bool:
"""
Returns `True` if this IP is an IPv6 address and `False`
if it is an IPv4 address.
"""
return isinstance(self.ip, tuple)
def __repr__(self) -> str:
return "IP(ip={ip}, network_prefix={network_prefix}, nice_name={nice_name})".format(
ip=repr(self.ip), network_prefix=repr(self.network_prefix), nice_name=repr(self.nice_name)
)
if platform.system() == "Darwin" or "BSD" in platform.system():
# BSD derived systems use marginally different structures
# than either Linux or Windows.
# I still keep it in `shared` since we can use
# both structures equally.
class sockaddr(ctypes.Structure):
_fields_ = [
('sa_len', ctypes.c_uint8),
('sa_familiy', ctypes.c_uint8),
('sa_data', ctypes.c_uint8 * 14),
]
class sockaddr_in(ctypes.Structure):
_fields_ = [
('sa_len', ctypes.c_uint8),
('sa_familiy', ctypes.c_uint8),
('sin_port', ctypes.c_uint16),
('sin_addr', ctypes.c_uint8 * 4),
('sin_zero', ctypes.c_uint8 * 8),
]
class sockaddr_in6(ctypes.Structure):
_fields_ = [
('sa_len', ctypes.c_uint8),
('sa_familiy', ctypes.c_uint8),
('sin6_port', ctypes.c_uint16),
('sin6_flowinfo', ctypes.c_uint32),
('sin6_addr', ctypes.c_uint8 * 16),
('sin6_scope_id', ctypes.c_uint32),
]
else:
class sockaddr(ctypes.Structure): # type: ignore
_fields_ = [('sa_familiy', ctypes.c_uint16), ('sa_data', ctypes.c_uint8 * 14)]
class sockaddr_in(ctypes.Structure): # type: ignore
_fields_ = [
('sin_familiy', ctypes.c_uint16),
('sin_port', ctypes.c_uint16),
('sin_addr', ctypes.c_uint8 * 4),
('sin_zero', ctypes.c_uint8 * 8),
]
class sockaddr_in6(ctypes.Structure): # type: ignore
_fields_ = [
('sin6_familiy', ctypes.c_uint16),
('sin6_port', ctypes.c_uint16),
('sin6_flowinfo', ctypes.c_uint32),
('sin6_addr', ctypes.c_uint8 * 16),
('sin6_scope_id', ctypes.c_uint32),
]
def sockaddr_to_ip(sockaddr_ptr: 'ctypes.pointer[sockaddr]') -> Optional[Union[_IPv4Address, _IPv6Address]]:
if sockaddr_ptr:
if sockaddr_ptr[0].sa_familiy == socket.AF_INET:
ipv4 = ctypes.cast(sockaddr_ptr, ctypes.POINTER(sockaddr_in))
ippacked = bytes(bytearray(ipv4[0].sin_addr))
ip = str(ipaddress.ip_address(ippacked))
return ip
elif sockaddr_ptr[0].sa_familiy == socket.AF_INET6:
ipv6 = ctypes.cast(sockaddr_ptr, ctypes.POINTER(sockaddr_in6))
flowinfo = ipv6[0].sin6_flowinfo
ippacked = bytes(bytearray(ipv6[0].sin6_addr))
ip = str(ipaddress.ip_address(ippacked))
scope_id = ipv6[0].sin6_scope_id
return (ip, flowinfo, scope_id)
return None
def ipv6_prefixlength(address: ipaddress.IPv6Address) -> int:
prefix_length = 0
for i in range(address.max_prefixlen):
if int(address) >> i & 1:
prefix_length = prefix_length + 1
return prefix_length
+145
View File
@@ -0,0 +1,145 @@
# Copyright (c) 2014 Stefan C. Mueller
# 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.
import ctypes
from ctypes import wintypes
from typing import Iterable, List
import RNS.vendor.ifaddr._shared as shared
NO_ERROR = 0
ERROR_BUFFER_OVERFLOW = 111
MAX_ADAPTER_NAME_LENGTH = 256
MAX_ADAPTER_DESCRIPTION_LENGTH = 128
MAX_ADAPTER_ADDRESS_LENGTH = 8
AF_UNSPEC = 0
class SOCKET_ADDRESS(ctypes.Structure):
_fields_ = [('lpSockaddr', ctypes.POINTER(shared.sockaddr)), ('iSockaddrLength', wintypes.INT)]
class IP_ADAPTER_UNICAST_ADDRESS(ctypes.Structure):
pass
IP_ADAPTER_UNICAST_ADDRESS._fields_ = [
('Length', wintypes.ULONG),
('Flags', wintypes.DWORD),
('Next', ctypes.POINTER(IP_ADAPTER_UNICAST_ADDRESS)),
('Address', SOCKET_ADDRESS),
('PrefixOrigin', ctypes.c_uint),
('SuffixOrigin', ctypes.c_uint),
('DadState', ctypes.c_uint),
('ValidLifetime', wintypes.ULONG),
('PreferredLifetime', wintypes.ULONG),
('LeaseLifetime', wintypes.ULONG),
('OnLinkPrefixLength', ctypes.c_uint8),
]
class IP_ADAPTER_ADDRESSES(ctypes.Structure):
pass
IP_ADAPTER_ADDRESSES._fields_ = [
('Length', wintypes.ULONG),
('IfIndex', wintypes.DWORD),
('Next', ctypes.POINTER(IP_ADAPTER_ADDRESSES)),
('AdapterName', ctypes.c_char_p),
('FirstUnicastAddress', ctypes.POINTER(IP_ADAPTER_UNICAST_ADDRESS)),
('FirstAnycastAddress', ctypes.c_void_p),
('FirstMulticastAddress', ctypes.c_void_p),
('FirstDnsServerAddress', ctypes.c_void_p),
('DnsSuffix', ctypes.c_wchar_p),
('Description', ctypes.c_wchar_p),
('FriendlyName', ctypes.c_wchar_p),
]
iphlpapi = ctypes.windll.LoadLibrary("Iphlpapi") # type: ignore
def enumerate_interfaces_of_adapter(
nice_name: str, address: IP_ADAPTER_UNICAST_ADDRESS
) -> Iterable[shared.IP]:
# Iterate through linked list and fill list
addresses = [] # type: List[IP_ADAPTER_UNICAST_ADDRESS]
while True:
addresses.append(address)
if not address.Next:
break
address = address.Next[0]
for address in addresses:
ip = shared.sockaddr_to_ip(address.Address.lpSockaddr)
assert ip is not None, f'sockaddr_to_ip({address.Address.lpSockaddr}) returned None'
network_prefix = address.OnLinkPrefixLength
yield shared.IP(ip, network_prefix, nice_name)
def get_adapters(include_unconfigured: bool = False) -> Iterable[shared.Adapter]:
# Call GetAdaptersAddresses() with error and buffer size handling
addressbuffersize = wintypes.ULONG(15 * 1024)
retval = ERROR_BUFFER_OVERFLOW
while retval == ERROR_BUFFER_OVERFLOW:
addressbuffer = ctypes.create_string_buffer(addressbuffersize.value)
retval = iphlpapi.GetAdaptersAddresses(
wintypes.ULONG(AF_UNSPEC),
wintypes.ULONG(0),
None,
ctypes.byref(addressbuffer),
ctypes.byref(addressbuffersize),
)
if retval != NO_ERROR:
raise ctypes.WinError() # type: ignore
# Iterate through adapters fill array
address_infos = [] # type: List[IP_ADAPTER_ADDRESSES]
address_info = IP_ADAPTER_ADDRESSES.from_buffer(addressbuffer)
while True:
address_infos.append(address_info)
if not address_info.Next:
break
address_info = address_info.Next[0]
# Iterate through unicast addresses
result = [] # type: List[shared.Adapter]
for adapter_info in address_infos:
# We don't expect non-ascii characters here, so encoding shouldn't matter
name = adapter_info.AdapterName.decode()
nice_name = adapter_info.Description
index = adapter_info.IfIndex
if adapter_info.FirstUnicastAddress:
ips = enumerate_interfaces_of_adapter(
adapter_info.FriendlyName, adapter_info.FirstUnicastAddress[0]
)
ips = list(ips)
result.append(shared.Adapter(name, nice_name, ips, index=index))
elif include_unconfigured:
result.append(shared.Adapter(name, nice_name, [], index=index))
return result
+38
View File
@@ -0,0 +1,38 @@
import ipaddress
import RNS.vendor.ifaddr
import socket
from typing import List
AF_INET6 = socket.AF_INET6.value
AF_INET = socket.AF_INET.value
def interfaces() -> List[str]:
adapters = RNS.vendor.ifaddr.get_adapters(include_unconfigured=True)
return [a.name for a in adapters]
def ifaddresses(ifname) -> dict:
adapters = RNS.vendor.ifaddr.get_adapters(include_unconfigured=True)
ifa = {}
for a in adapters:
if a.name == ifname:
ipv4s = []
ipv6s = []
for ip in a.ips:
t = {}
if ip.is_IPv4:
net = ipaddress.ip_network(str(ip.ip)+"/"+str(ip.network_prefix), strict=False)
t["addr"] = ip.ip
t["prefix"] = ip.network_prefix
t["broadcast"] = str(net.broadcast_address)
ipv4s.append(t)
if ip.is_IPv6:
t["addr"] = ip.ip[0]
ipv6s.append(t)
if len(ipv4s) > 0:
ifa[AF_INET] = ipv4s
if len(ipv6s) > 0:
ifa[AF_INET6] = ipv6s
return ifa
View File
+2 -2
View File
@@ -15,11 +15,12 @@ This document outlines the currently established development roadmap for Reticul
For each release cycle of Reticulum, improvements and additions from the five [Primary Efforts](#primary-efforts) are selected as active work areas, and can be expected to be included in the upcoming releases within that cycle. While not entirely set in stone for each release cycle, they serve as a pointer of what to expect in the near future.
- The current `0.5.x` release cycle aims at completing
- [x] Reach feature-completion of the Reticulum API
- [x] Improve performance and efficiency of the `Buffer` and `Channel` API
- [ ] Overhauling and updating the documentation
- [ ] Performance and memory optimisations of the Python reference implementation
- [ ] Fixing potential bugs
- [ ] Add automatic retries to all use cases of the `Request` API
- [ ] Improve performance and efficiency of the `Buffer` and `Channel` API
## Primary Efforts
The development path for Reticulum is currently laid out in five distinct areas: *Comprehensibility*, *Universality*, *Functionality*, *Usability & Utility* and *Interfaceability*. Conceptualising the development of Reticulum into these areas serves to advance the implementation and work towards the Foundational Goals & Values of Reticulum.
@@ -38,7 +39,6 @@ These efforts are aimed at improving the ease of which Reticulum is understood,
- Update NomadNet screenshots
- Update Sideband screenshots
- Installation
- Install docs for fedora, needs `python3-netifaces`
- Add a *Reticulum On Raspberry Pi* section
- Update *Reticulum On Android* section if necessary
- Update Android install documentation.
Binary file not shown.
Binary file not shown.
+1 -1
View File
@@ -1,4 +1,4 @@
# Sphinx build info version 1
# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done.
config: d8447d44585d036754ef13c1e3b2bcb2
config: 0047216ec03de95cb42793af804179e2
tags: 645f666f9bcd5a90fca523b33c5a78b7
+107 -32
View File
@@ -10,22 +10,45 @@ scenarios.
Standalone Reticulum Installation
=============================================
If you simply want to install Reticulum and related utilities on a system,
the easiest way is via ``pip``:
the easiest way is via the ``pip`` package manager:
.. code::
pip install rns
If you no not already have pip installed, you can install it using the package manager
If you do not already have pip installed, you can install it using the package manager
of your system with a command like ``sudo apt install python3-pip``,
``sudo pamac install python-pip`` or similar. You can also dowload the Reticulum release
wheels from GitHub, or other release channels, and install them offline using ``pip``:
``sudo pamac install python-pip`` or similar.
You can also dowload the Reticulum release wheels from GitHub, or other release channels,
and install them offline using ``pip``:
.. code::
pip install ./rns-0.4.6-py3-none-any.whl
pip install ./rns-0.5.1-py3-none-any.whl
Resolving Dependency & Installation Issues
=============================================
On some platforms, there may not be binary packages available for all dependencies, and
``pip`` installation may fail with an error message. In these cases, the issue can usually
be resolved by installing the development essentials packages for your platform:
.. code::
# Debian / Ubuntu / Derivatives
sudo apt install build-essential
# Arch / Manjaro / Derivatives
sudo pamac install base-devel
# Fedora
sudo dnf groupinstall "Development Tools" "Development Libraries"
With the base development packages installed, ``pip`` should be able to compile any missing
dependencies from source, and complete installation even on platforms that don't have pre-
compiled packages available.
Try Using a Reticulum-based Program
=============================================
@@ -203,12 +226,12 @@ by adding one of the following interfaces to your ``.reticulum/config`` file:
target_host = dublin.connect.reticulum.network
target_port = 4965
# TCP/IP interface to the Frankfurt hub
[[RNS Testnet Frankfurt]]
# TCP/IP interface to the BetweenTheBorders Hub (community-provided)
[[RNS Testnet BetweenTheBorders]]
type = TCPClientInterface
enabled = yes
target_host = frankfurt.connect.reticulum.network
target_port = 5377
target_host = betweentheborders.com
target_port = 4242
# Interface to I2P hub A
[[RNS Testnet I2P Hub A]]
@@ -268,7 +291,7 @@ For extended functionality, you can install optional dependencies:
.. code::
pip3 install pyserial netifaces
pip3 install pyserial
Further information can be found in the :ref:`API Reference<api-main>`.
@@ -283,7 +306,7 @@ don't use pip, but try this recipe:
.. code::
# Install dependencies
pip3 install cryptography pyserial netifaces
pip3 install cryptography pyserial
# Clone repository
git clone https://github.com/markqvist/Reticulum.git
@@ -320,30 +343,14 @@ the `disucssion forum on GitHub <https://github.com/markqvist/Reticulum/discussi
or ask one of the developers or maintainers for a good place to start.
Reticulum on ARM64
Platform-Specific Install Notes
==============================================
On some architectures, including ARM64, not all dependencies have precompiled
binaries. On such systems, you may need to install ``python3-dev`` before
installing Reticulum or programs that depend on Reticulum.
.. code::
Some platforms require a slightly different installation procedure, or have
various quirks that are worth being aware of. These are listed here.
# 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 Raspberry Pi
==============================================
It is currently recommended to use a 64-bit version of the Raspberry Pi OS
if you want to run Reticulum on Raspberry Pi computers, since 32-bit versions
don't always have packages available for some dependencies.
Reticulum on Android
==============================================
Android
^^^^^^^^^^^^^^^^^^^^^^^^
Reticulum can be used on Android in different ways. The easiest way to get
started is using an app like `Sideband <https://unsigned.io/sideband>`_.
@@ -409,6 +416,74 @@ 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. Until then you can use the `Sideband source code <https://github.com/markqvist/sideband>`_ as an example and startig point.
ARM64
^^^^^^^^^^^^^^^^^^^^^^^^
On some architectures, including ARM64, not all dependencies have precompiled
binaries. On such systems, you may 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
Raspberry Pi
^^^^^^^^^^^^^^^^^^^^^^^^^
It is currently recommended to use a 64-bit version of the Raspberry Pi OS
if you want to run Reticulum on Raspberry Pi computers, since 32-bit versions
don't always have packages available for some dependencies.
While it is possible to install and run Reticulum on 32-bit Rasperry Pi OSes,
it will require manually configuring and installing some packages, and is not
detailed in this manual.
Debian Bookworm
^^^^^^^^^^^^^^^^^^^^^^^^
On versions of Debian released after April 2023, it is no longer possible
to use ``pip`` to install packages onto your system. Unfortunately, you will need to
use the replacement ``pipx`` command instead, which places installed packages in an
isolated environment. This should not negatively affect Reticulum, but installation
via this method is not fully tested yet.
.. code::
# Install pipx
sudo apt install pipx
# Make installed programs available on the command line
pipx ensurepath
# Install Reticulum
pipx install rns
Ubuntu Lunar
^^^^^^^^^^^^^^^^^^^^^^^^
On versions of Ubuntu released after April 2023, it is no longer possible
to use ``pip`` to install packages onto your system. Unfortunately, you will need to
use the replacement ``pipx`` command instead, which places installed packages in an
isolated environment. This should not negatively affect Reticulum, but installation
via this method is not fully tested yet.
.. code::
# Install pipx
sudo apt install pipx
# Make installed programs available on the command line
pipx ensurepath
# Install Reticulum
pipx install rns
Pure-Python Reticulum
==============================================
In some rare cases, and on more obscure system types, it is not possible to
+4 -3
View File
@@ -1,15 +1,16 @@
******************************
Reticulum Network Stack Manual
******************************
This manual aims to provide you with all the information you need to
understand Reticulum, build networks or develop programs using it, or
to participate in the development of Reticulum itself.
.. only:: html
.. only:: builder_html
This manual is also available in `PDF <https://github.com/markqvist/Reticulum/releases/latest/download/Reticulum.Manual.pdf>`_ and `EPUB <https://github.com/markqvist/Reticulum/releases/latest/download/Reticulum.Manual.epub>`_ formats.
This manual is also available in `PDF <https://github.com/markqvist/Reticulum/releases/latest/download/Reticulum.Manual.pdf>`_ and `EPUB <https://github.com/markqvist/Reticulum/releases/latest/download/Reticulum.Manual.epub>`_ formats.
.. only:: html
.. only:: builder_html
Table Of Contents
=================
+2 -2
View File
@@ -223,7 +223,7 @@ interfaces, similar to the ``ifconfig`` program.
Traffic : 63.23 KB↑
80.17 KB↓
TCPInterface[RNS Testnet Frankfurt/frankfurt.rns.unsigned.io:4965]
TCPInterface[RNS Testnet Dublin/dublin.connect.reticulum.network:4965]
Status : Up
Mode : Full
Rate : 10.00 Mbps
@@ -266,7 +266,7 @@ destinations on the Reticulum network.
rnpath c89b4da064bf66d280f0e4d8abfd9806
# Example output
Path found, destination <c89b4da064bf66d280f0e4d8abfd9806> is 4 hops away via <f53a1c4278e0726bb73fcc623d6ce763> on TCPInterface[Testnet/frankfurt.connect.reticulu.network:4965]
Path found, destination <c89b4da064bf66d280f0e4d8abfd9806> is 4 hops away via <f53a1c4278e0726bb73fcc623d6ce763> on TCPInterface[Testnet/dublin.connect.reticulum.network:4965]
.. code:: text
+1 -1
View File
@@ -1,6 +1,6 @@
var DOCUMENTATION_OPTIONS = {
URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'),
VERSION: '0.5.0 beta',
VERSION: '0.5.4 beta',
LANGUAGE: 'en',
COLLAPSE_INDEX: false,
BUILDER: 'html',
+3 -3
View File
@@ -6,7 +6,7 @@
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="Support Reticulum" href="support.html" /><link rel="prev" title="Building Networks" href="networks.html" />
<meta name="generator" content="sphinx-5.2.2, furo 2022.09.29"/>
<title>Code Examples - Reticulum Network Stack 0.5.0 beta documentation</title>
<title>Code Examples - Reticulum Network Stack 0.5.4 beta documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=d81277517bee4d6b0349d71bb2661d4890b5617c" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
@@ -141,7 +141,7 @@
</label>
</div>
<div class="header-center">
<a href="index.html"><div class="brand">Reticulum Network Stack 0.5.0 beta documentation</div></a>
<a href="index.html"><div class="brand">Reticulum Network Stack 0.5.4 beta documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -167,7 +167,7 @@
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
</div>
<span class="sidebar-brand-text">Reticulum Network Stack 0.5.0 beta documentation</span>
<span class="sidebar-brand-text">Reticulum Network Stack 0.5.4 beta documentation</span>
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
+3 -3
View File
@@ -6,7 +6,7 @@
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" />
<meta name="generator" content="sphinx-5.2.2, furo 2022.09.29"/>
<title>An Explanation of Reticulum for Human Beings - Reticulum Network Stack 0.5.0 beta documentation</title>
<title>An Explanation of Reticulum for Human Beings - Reticulum Network Stack 0.5.4 beta documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=d81277517bee4d6b0349d71bb2661d4890b5617c" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
@@ -141,7 +141,7 @@
</label>
</div>
<div class="header-center">
<a href="index.html"><div class="brand">Reticulum Network Stack 0.5.0 beta documentation</div></a>
<a href="index.html"><div class="brand">Reticulum Network Stack 0.5.4 beta documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -167,7 +167,7 @@
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
</div>
<span class="sidebar-brand-text">Reticulum Network Stack 0.5.0 beta documentation</span>
<span class="sidebar-brand-text">Reticulum Network Stack 0.5.4 beta documentation</span>
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
+3 -3
View File
@@ -4,7 +4,7 @@
<meta name="viewport" content="width=device-width,initial-scale=1"/>
<meta name="color-scheme" content="light dark"><link rel="index" title="Index" href="#" /><link rel="search" title="Search" href="search.html" />
<meta name="generator" content="sphinx-5.2.2, furo 2022.09.29"/><title>Index - Reticulum Network Stack 0.5.0 beta documentation</title>
<meta name="generator" content="sphinx-5.2.2, furo 2022.09.29"/><title>Index - Reticulum Network Stack 0.5.4 beta documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=d81277517bee4d6b0349d71bb2661d4890b5617c" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
@@ -139,7 +139,7 @@
</label>
</div>
<div class="header-center">
<a href="index.html"><div class="brand">Reticulum Network Stack 0.5.0 beta documentation</div></a>
<a href="index.html"><div class="brand">Reticulum Network Stack 0.5.4 beta documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -165,7 +165,7 @@
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
</div>
<span class="sidebar-brand-text">Reticulum Network Stack 0.5.0 beta documentation</span>
<span class="sidebar-brand-text">Reticulum Network Stack 0.5.4 beta documentation</span>
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
+109 -39
View File
@@ -6,7 +6,7 @@
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="Using Reticulum on Your System" href="using.html" /><link rel="prev" title="What is Reticulum?" href="whatis.html" />
<meta name="generator" content="sphinx-5.2.2, furo 2022.09.29"/>
<title>Getting Started Fast - Reticulum Network Stack 0.5.0 beta documentation</title>
<title>Getting Started Fast - Reticulum Network Stack 0.5.4 beta documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=d81277517bee4d6b0349d71bb2661d4890b5617c" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
@@ -141,7 +141,7 @@
</label>
</div>
<div class="header-center">
<a href="index.html"><div class="brand">Reticulum Network Stack 0.5.0 beta documentation</div></a>
<a href="index.html"><div class="brand">Reticulum Network Stack 0.5.4 beta documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -167,7 +167,7 @@
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
</div>
<span class="sidebar-brand-text">Reticulum Network Stack 0.5.0 beta documentation</span>
<span class="sidebar-brand-text">Reticulum Network Stack 0.5.4 beta documentation</span>
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
@@ -229,18 +229,38 @@ scenarios.</p>
<section id="standalone-reticulum-installation">
<h2>Standalone Reticulum Installation<a class="headerlink" href="#standalone-reticulum-installation" title="Permalink to this heading">#</a></h2>
<p>If you simply want to install Reticulum and related utilities on a system,
the easiest way is via <code class="docutils literal notranslate"><span class="pre">pip</span></code>:</p>
the easiest way is via the <code class="docutils literal notranslate"><span class="pre">pip</span></code> package manager:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">pip</span> <span class="n">install</span> <span class="n">rns</span>
</pre></div>
</div>
<p>If you no not already have pip installed, you can install it using the package manager
<p>If you do not already have pip installed, you can install it using the package manager
of your system with a command like <code class="docutils literal notranslate"><span class="pre">sudo</span> <span class="pre">apt</span> <span class="pre">install</span> <span class="pre">python3-pip</span></code>,
<code class="docutils literal notranslate"><span class="pre">sudo</span> <span class="pre">pamac</span> <span class="pre">install</span> <span class="pre">python-pip</span></code> or similar. You can also dowload the Reticulum release
wheels from GitHub, or other release channels, and install them offline using <code class="docutils literal notranslate"><span class="pre">pip</span></code>:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">pip</span> <span class="n">install</span> <span class="o">./</span><span class="n">rns</span><span class="o">-</span><span class="mf">0.4.6</span><span class="o">-</span><span class="n">py3</span><span class="o">-</span><span class="n">none</span><span class="o">-</span><span class="nb">any</span><span class="o">.</span><span class="n">whl</span>
<code class="docutils literal notranslate"><span class="pre">sudo</span> <span class="pre">pamac</span> <span class="pre">install</span> <span class="pre">python-pip</span></code> or similar.</p>
<p>You can also dowload the Reticulum release wheels from GitHub, or other release channels,
and install them offline using <code class="docutils literal notranslate"><span class="pre">pip</span></code>:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">pip</span> <span class="n">install</span> <span class="o">./</span><span class="n">rns</span><span class="o">-</span><span class="mf">0.5.1</span><span class="o">-</span><span class="n">py3</span><span class="o">-</span><span class="n">none</span><span class="o">-</span><span class="nb">any</span><span class="o">.</span><span class="n">whl</span>
</pre></div>
</div>
</section>
<section id="resolving-dependency-installation-issues">
<h2>Resolving Dependency &amp; Installation Issues<a class="headerlink" href="#resolving-dependency-installation-issues" title="Permalink to this heading">#</a></h2>
<p>On some platforms, there may not be binary packages available for all dependencies, and
<code class="docutils literal notranslate"><span class="pre">pip</span></code> installation may fail with an error message. In these cases, the issue can usually
be resolved by installing the development essentials packages for your platform:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># Debian / Ubuntu / Derivatives</span>
<span class="n">sudo</span> <span class="n">apt</span> <span class="n">install</span> <span class="n">build</span><span class="o">-</span><span class="n">essential</span>
<span class="c1"># Arch / Manjaro / Derivatives</span>
<span class="n">sudo</span> <span class="n">pamac</span> <span class="n">install</span> <span class="n">base</span><span class="o">-</span><span class="n">devel</span>
<span class="c1"># Fedora</span>
<span class="n">sudo</span> <span class="n">dnf</span> <span class="n">groupinstall</span> <span class="s2">&quot;Development Tools&quot;</span> <span class="s2">&quot;Development Libraries&quot;</span>
</pre></div>
</div>
<p>With the base development packages installed, <code class="docutils literal notranslate"><span class="pre">pip</span></code> should be able to compile any missing
dependencies from source, and complete installation even on platforms that dont have pre-
compiled packages available.</p>
</section>
<section id="try-using-a-reticulum-based-program">
<h2>Try Using a Reticulum-based Program<a class="headerlink" href="#try-using-a-reticulum-based-program" title="Permalink to this heading">#</a></h2>
<p>If you simply want to try using a program built with Reticulum, a few different
@@ -374,12 +394,12 @@ by adding one of the following interfaces to your <code class="docutils literal
<span class="n">target_host</span> <span class="o">=</span> <span class="n">dublin</span><span class="o">.</span><span class="n">connect</span><span class="o">.</span><span class="n">reticulum</span><span class="o">.</span><span class="n">network</span>
<span class="n">target_port</span> <span class="o">=</span> <span class="mi">4965</span>
<span class="c1"># TCP/IP interface to the Frankfurt hub</span>
<span class="p">[[</span><span class="n">RNS</span> <span class="n">Testnet</span> <span class="n">Frankfurt</span><span class="p">]]</span>
<span class="c1"># TCP/IP interface to the BetweenTheBorders Hub (community-provided)</span>
<span class="p">[[</span><span class="n">RNS</span> <span class="n">Testnet</span> <span class="n">BetweenTheBorders</span><span class="p">]]</span>
<span class="nb">type</span> <span class="o">=</span> <span class="n">TCPClientInterface</span>
<span class="n">enabled</span> <span class="o">=</span> <span class="n">yes</span>
<span class="n">target_host</span> <span class="o">=</span> <span class="n">frankfurt</span><span class="o">.</span><span class="n">connect</span><span class="o">.</span><span class="n">reticulum</span><span class="o">.</span><span class="n">network</span>
<span class="n">target_port</span> <span class="o">=</span> <span class="mi">5377</span>
<span class="n">target_host</span> <span class="o">=</span> <span class="n">betweentheborders</span><span class="o">.</span><span class="n">com</span>
<span class="n">target_port</span> <span class="o">=</span> <span class="mi">4242</span>
<span class="c1"># Interface to I2P hub A</span>
<span class="p">[[</span><span class="n">RNS</span> <span class="n">Testnet</span> <span class="n">I2P</span> <span class="n">Hub</span> <span class="n">A</span><span class="p">]]</span>
@@ -430,7 +450,7 @@ started is to install the latest release of Reticulum via pip:</p>
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>
<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>
</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>
@@ -441,7 +461,7 @@ likely be to look at some <a class="reference internal" href="examples.html#exam
utilities, youll want to get the latest source from GitHub. In that case,
dont use pip, but try this recipe:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># Install dependencies</span>
<span class="n">pip3</span> <span class="n">install</span> <span class="n">cryptography</span> <span class="n">pyserial</span> <span class="n">netifaces</span>
<span class="n">pip3</span> <span class="n">install</span> <span class="n">cryptography</span> <span class="n">pyserial</span>
<span class="c1"># Clone repository</span>
<span class="n">git</span> <span class="n">clone</span> <span class="n">https</span><span class="p">:</span><span class="o">//</span><span class="n">github</span><span class="o">.</span><span class="n">com</span><span class="o">/</span><span class="n">markqvist</span><span class="o">/</span><span class="n">Reticulum</span><span class="o">.</span><span class="n">git</span>
@@ -478,28 +498,12 @@ your first pull request, it is probably a good idea to introduce yourself on
the <a class="reference external" href="https://github.com/markqvist/Reticulum/discussions">disucssion forum on GitHub</a>,
or ask one of the developers or maintainers for a good place to start.</p>
</section>
<section id="reticulum-on-arm64">
<h2>Reticulum on ARM64<a class="headerlink" href="#reticulum-on-arm64" title="Permalink to this heading">#</a></h2>
<p>On some architectures, including ARM64, not all dependencies have precompiled
binaries. On such systems, you may 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>
</section>
<section id="reticulum-on-raspberry-pi">
<h2>Reticulum on Raspberry Pi<a class="headerlink" href="#reticulum-on-raspberry-pi" title="Permalink to this heading">#</a></h2>
<p>It is currently recommended to use a 64-bit version of the Raspberry Pi OS
if you want to run Reticulum on Raspberry Pi computers, since 32-bit versions
dont always have packages available for some dependencies.</p>
</section>
<section id="reticulum-on-android">
<h2>Reticulum on Android<a class="headerlink" href="#reticulum-on-android" title="Permalink to this heading">#</a></h2>
<section id="platform-specific-install-notes">
<h2>Platform-Specific Install Notes<a class="headerlink" href="#platform-specific-install-notes" title="Permalink to this heading">#</a></h2>
<p>Some platforms require a slightly different installation procedure, or have
various quirks that are worth being aware of. These are listed here.</p>
<section id="android">
<h3>Android<a class="headerlink" href="#android" title="Permalink to this heading">#</a></h3>
<p>Reticulum can be used on Android in different ways. The easiest way to get
started is using an app like <a class="reference external" href="https://unsigned.io/sideband">Sideband</a>.</p>
<p>For more control and features, you can use Reticulum and related programs via
@@ -557,6 +561,66 @@ locally on your device using the following command:</p>
Android APKs. A detailed tutorial and example source code will be included
here at a later point. Until then you can use the <a class="reference external" href="https://github.com/markqvist/sideband">Sideband source code</a> as an example and startig point.</p>
</section>
<section id="arm64">
<h3>ARM64<a class="headerlink" href="#arm64" title="Permalink to this heading">#</a></h3>
<p>On some architectures, including ARM64, not all dependencies have precompiled
binaries. On such systems, you may 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>
</section>
<section id="raspberry-pi">
<h3>Raspberry Pi<a class="headerlink" href="#raspberry-pi" title="Permalink to this heading">#</a></h3>
<p>It is currently recommended to use a 64-bit version of the Raspberry Pi OS
if you want to run Reticulum on Raspberry Pi computers, since 32-bit versions
dont always have packages available for some dependencies.</p>
<p>While it is possible to install and run Reticulum on 32-bit Rasperry Pi OSes,
it will require manually configuring and installing some packages, and is not
detailed in this manual.</p>
</section>
<section id="debian-bookworm">
<h3>Debian Bookworm<a class="headerlink" href="#debian-bookworm" title="Permalink to this heading">#</a></h3>
<p>On versions of Debian released after April 2023, it is no longer possible
to use <code class="docutils literal notranslate"><span class="pre">pip</span></code> to install packages onto your system. Unfortunately, you will need to
use the replacement <code class="docutils literal notranslate"><span class="pre">pipx</span></code> command instead, which places installed packages in an
isolated environment. This should not negatively affect Reticulum, but installation
via this method is not fully tested yet.</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># Install pipx</span>
<span class="n">sudo</span> <span class="n">apt</span> <span class="n">install</span> <span class="n">pipx</span>
<span class="c1"># Make installed programs available on the command line</span>
<span class="n">pipx</span> <span class="n">ensurepath</span>
<span class="c1"># Install Reticulum</span>
<span class="n">pipx</span> <span class="n">install</span> <span class="n">rns</span>
</pre></div>
</div>
</section>
<section id="ubuntu-lunar">
<h3>Ubuntu Lunar<a class="headerlink" href="#ubuntu-lunar" title="Permalink to this heading">#</a></h3>
<p>On versions of Ubuntu released after April 2023, it is no longer possible
to use <code class="docutils literal notranslate"><span class="pre">pip</span></code> to install packages onto your system. Unfortunately, you will need to
use the replacement <code class="docutils literal notranslate"><span class="pre">pipx</span></code> command instead, which places installed packages in an
isolated environment. This should not negatively affect Reticulum, but installation
via this method is not fully tested yet.</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># Install pipx</span>
<span class="n">sudo</span> <span class="n">apt</span> <span class="n">install</span> <span class="n">pipx</span>
<span class="c1"># Make installed programs available on the command line</span>
<span class="n">pipx</span> <span class="n">ensurepath</span>
<span class="c1"># Install Reticulum</span>
<span class="n">pipx</span> <span class="n">install</span> <span class="n">rns</span>
</pre></div>
</div>
</section>
</section>
<section id="pure-python-reticulum">
<h2>Pure-Python Reticulum<a class="headerlink" href="#pure-python-reticulum" title="Permalink to this heading">#</a></h2>
<p>In some rare cases, and on more obscure system types, it is not possible to
@@ -639,6 +703,7 @@ section of this manual.</p>
<ul>
<li><a class="reference internal" href="#">Getting Started Fast</a><ul>
<li><a class="reference internal" href="#standalone-reticulum-installation">Standalone Reticulum Installation</a></li>
<li><a class="reference internal" href="#resolving-dependency-installation-issues">Resolving Dependency &amp; Installation Issues</a></li>
<li><a class="reference internal" href="#try-using-a-reticulum-based-program">Try Using a Reticulum-based Program</a><ul>
<li><a class="reference internal" href="#remote-shell">Remote Shell</a></li>
<li><a class="reference internal" href="#nomad-network">Nomad Network</a></li>
@@ -652,9 +717,14 @@ section of this manual.</p>
<li><a class="reference internal" href="#adding-radio-interfaces">Adding Radio Interfaces</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-raspberry-pi">Reticulum on Raspberry Pi</a></li>
<li><a class="reference internal" href="#reticulum-on-android">Reticulum on Android</a></li>
<li><a class="reference internal" href="#platform-specific-install-notes">Platform-Specific Install Notes</a><ul>
<li><a class="reference internal" href="#android">Android</a></li>
<li><a class="reference internal" href="#arm64">ARM64</a></li>
<li><a class="reference internal" href="#raspberry-pi">Raspberry Pi</a></li>
<li><a class="reference internal" href="#debian-bookworm">Debian Bookworm</a></li>
<li><a class="reference internal" href="#ubuntu-lunar">Ubuntu Lunar</a></li>
</ul>
</li>
<li><a class="reference internal" href="#pure-python-reticulum">Pure-Python Reticulum</a></li>
</ul>
</li>
+3 -3
View File
@@ -6,7 +6,7 @@
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="Supported Interfaces" href="interfaces.html" /><link rel="prev" title="Understanding Reticulum" href="understanding.html" />
<meta name="generator" content="sphinx-5.2.2, furo 2022.09.29"/>
<title>Communications Hardware - Reticulum Network Stack 0.5.0 beta documentation</title>
<title>Communications Hardware - Reticulum Network Stack 0.5.4 beta documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=d81277517bee4d6b0349d71bb2661d4890b5617c" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
@@ -141,7 +141,7 @@
</label>
</div>
<div class="header-center">
<a href="index.html"><div class="brand">Reticulum Network Stack 0.5.0 beta documentation</div></a>
<a href="index.html"><div class="brand">Reticulum Network Stack 0.5.4 beta documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -167,7 +167,7 @@
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
</div>
<span class="sidebar-brand-text">Reticulum Network Stack 0.5.0 beta documentation</span>
<span class="sidebar-brand-text">Reticulum Network Stack 0.5.4 beta documentation</span>
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
+12 -6
View File
@@ -6,7 +6,7 @@
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="What is Reticulum?" href="whatis.html" />
<meta name="generator" content="sphinx-5.2.2, furo 2022.09.29"/>
<title>Reticulum Network Stack 0.5.0 beta documentation</title>
<title>Reticulum Network Stack 0.5.4 beta documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=d81277517bee4d6b0349d71bb2661d4890b5617c" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
@@ -141,7 +141,7 @@
</label>
</div>
<div class="header-center">
<a href="#"><div class="brand">Reticulum Network Stack 0.5.0 beta documentation</div></a>
<a href="#"><div class="brand">Reticulum Network Stack 0.5.4 beta documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -167,7 +167,7 @@
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
</div>
<span class="sidebar-brand-text">Reticulum Network Stack 0.5.0 beta documentation</span>
<span class="sidebar-brand-text">Reticulum Network Stack 0.5.4 beta documentation</span>
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
@@ -242,6 +242,7 @@ to participate in the development of Reticulum itself.</p>
</li>
<li class="toctree-l1"><a class="reference internal" href="gettingstartedfast.html">Getting Started Fast</a><ul>
<li class="toctree-l2"><a class="reference internal" href="gettingstartedfast.html#standalone-reticulum-installation">Standalone Reticulum Installation</a></li>
<li class="toctree-l2"><a class="reference internal" href="gettingstartedfast.html#resolving-dependency-installation-issues">Resolving Dependency &amp; Installation Issues</a></li>
<li class="toctree-l2"><a class="reference internal" href="gettingstartedfast.html#try-using-a-reticulum-based-program">Try Using a Reticulum-based Program</a><ul>
<li class="toctree-l3"><a class="reference internal" href="gettingstartedfast.html#remote-shell">Remote Shell</a></li>
<li class="toctree-l3"><a class="reference internal" href="gettingstartedfast.html#nomad-network">Nomad Network</a></li>
@@ -255,9 +256,14 @@ to participate in the development of Reticulum itself.</p>
<li class="toctree-l2"><a class="reference internal" href="gettingstartedfast.html#adding-radio-interfaces">Adding Radio Interfaces</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-raspberry-pi">Reticulum on Raspberry Pi</a></li>
<li class="toctree-l2"><a class="reference internal" href="gettingstartedfast.html#reticulum-on-android">Reticulum on Android</a></li>
<li class="toctree-l2"><a class="reference internal" href="gettingstartedfast.html#platform-specific-install-notes">Platform-Specific Install Notes</a><ul>
<li class="toctree-l3"><a class="reference internal" href="gettingstartedfast.html#android">Android</a></li>
<li class="toctree-l3"><a class="reference internal" href="gettingstartedfast.html#arm64">ARM64</a></li>
<li class="toctree-l3"><a class="reference internal" href="gettingstartedfast.html#raspberry-pi">Raspberry Pi</a></li>
<li class="toctree-l3"><a class="reference internal" href="gettingstartedfast.html#debian-bookworm">Debian Bookworm</a></li>
<li class="toctree-l3"><a class="reference internal" href="gettingstartedfast.html#ubuntu-lunar">Ubuntu Lunar</a></li>
</ul>
</li>
<li class="toctree-l2"><a class="reference internal" href="gettingstartedfast.html#pure-python-reticulum">Pure-Python Reticulum</a></li>
</ul>
</li>
+3 -3
View File
@@ -6,7 +6,7 @@
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="Building Networks" href="networks.html" /><link rel="prev" title="Communications Hardware" href="hardware.html" />
<meta name="generator" content="sphinx-5.2.2, furo 2022.09.29"/>
<title>Supported Interfaces - Reticulum Network Stack 0.5.0 beta documentation</title>
<title>Supported Interfaces - Reticulum Network Stack 0.5.4 beta documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=d81277517bee4d6b0349d71bb2661d4890b5617c" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
@@ -141,7 +141,7 @@
</label>
</div>
<div class="header-center">
<a href="index.html"><div class="brand">Reticulum Network Stack 0.5.0 beta documentation</div></a>
<a href="index.html"><div class="brand">Reticulum Network Stack 0.5.4 beta documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -167,7 +167,7 @@
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
</div>
<span class="sidebar-brand-text">Reticulum Network Stack 0.5.0 beta documentation</span>
<span class="sidebar-brand-text">Reticulum Network Stack 0.5.4 beta documentation</span>
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
+3 -3
View File
@@ -6,7 +6,7 @@
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="Code Examples" href="examples.html" /><link rel="prev" title="Supported Interfaces" href="interfaces.html" />
<meta name="generator" content="sphinx-5.2.2, furo 2022.09.29"/>
<title>Building Networks - Reticulum Network Stack 0.5.0 beta documentation</title>
<title>Building Networks - Reticulum Network Stack 0.5.4 beta documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=d81277517bee4d6b0349d71bb2661d4890b5617c" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
@@ -141,7 +141,7 @@
</label>
</div>
<div class="header-center">
<a href="index.html"><div class="brand">Reticulum Network Stack 0.5.0 beta documentation</div></a>
<a href="index.html"><div class="brand">Reticulum Network Stack 0.5.4 beta documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -167,7 +167,7 @@
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
</div>
<span class="sidebar-brand-text">Reticulum Network Stack 0.5.0 beta documentation</span>
<span class="sidebar-brand-text">Reticulum Network Stack 0.5.4 beta documentation</span>
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
Binary file not shown.
+4 -4
View File
@@ -6,7 +6,7 @@
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="prev" title="Support Reticulum" href="support.html" />
<meta name="generator" content="sphinx-5.2.2, furo 2022.09.29"/>
<title>API Reference - Reticulum Network Stack 0.5.0 beta documentation</title>
<title>API Reference - Reticulum Network Stack 0.5.4 beta documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=d81277517bee4d6b0349d71bb2661d4890b5617c" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
@@ -141,7 +141,7 @@
</label>
</div>
<div class="header-center">
<a href="index.html"><div class="brand">Reticulum Network Stack 0.5.0 beta documentation</div></a>
<a href="index.html"><div class="brand">Reticulum Network Stack 0.5.4 beta documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -167,7 +167,7 @@
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
</div>
<span class="sidebar-brand-text">Reticulum Network Stack 0.5.0 beta documentation</span>
<span class="sidebar-brand-text">Reticulum Network Stack 0.5.4 beta documentation</span>
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
@@ -228,7 +228,7 @@ This chapter lists and explains all classes exposed by the Reticulum Network Sta
<p id="api-reticulum"><h3> Reticulum </h3></p>
<dl class="py class">
<dt class="sig sig-object py" id="RNS.Reticulum">
<em class="property"><span class="pre">class</span><span class="w"> </span></em><span class="sig-prename descclassname"><span class="pre">RNS.</span></span><span class="sig-name descname"><span class="pre">Reticulum</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">configdir</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">loglevel</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">logdest</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Reticulum" title="Permalink to this definition">#</a></dt>
<em class="property"><span class="pre">class</span><span class="w"> </span></em><span class="sig-prename descclassname"><span class="pre">RNS.</span></span><span class="sig-name descname"><span class="pre">Reticulum</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">configdir</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">loglevel</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">logdest</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">verbosity</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Reticulum" title="Permalink to this definition">#</a></dt>
<dd><p>This class is used to initialise access to Reticulum within a
program. You must create exactly one instance of this class before
carrying out any other RNS operations, such as creating destinations
+3 -3
View File
@@ -4,7 +4,7 @@
<meta name="viewport" content="width=device-width,initial-scale=1"/>
<meta name="color-scheme" content="light dark"><link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="#" />
<meta name="generator" content="sphinx-5.2.2, furo 2022.09.29"/><title>Search - Reticulum Network Stack 0.5.0 beta documentation</title><link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<meta name="generator" content="sphinx-5.2.2, furo 2022.09.29"/><title>Search - Reticulum Network Stack 0.5.4 beta documentation</title><link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=d81277517bee4d6b0349d71bb2661d4890b5617c" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo-extensions.css?digest=30d1aed668e5c3a91c3e3bf6a60b675221979f0e" />
@@ -138,7 +138,7 @@
</label>
</div>
<div class="header-center">
<a href="index.html"><div class="brand">Reticulum Network Stack 0.5.0 beta documentation</div></a>
<a href="index.html"><div class="brand">Reticulum Network Stack 0.5.4 beta documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -164,7 +164,7 @@
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
</div>
<span class="sidebar-brand-text">Reticulum Network Stack 0.5.0 beta documentation</span>
<span class="sidebar-brand-text">Reticulum Network Stack 0.5.4 beta documentation</span>
</a><form class="sidebar-search-container" method="get" action="#" role="search">
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
File diff suppressed because one or more lines are too long
+3 -3
View File
@@ -6,7 +6,7 @@
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="API Reference" href="reference.html" /><link rel="prev" title="Code Examples" href="examples.html" />
<meta name="generator" content="sphinx-5.2.2, furo 2022.09.29"/>
<title>Support Reticulum - Reticulum Network Stack 0.5.0 beta documentation</title>
<title>Support Reticulum - Reticulum Network Stack 0.5.4 beta documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=d81277517bee4d6b0349d71bb2661d4890b5617c" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
@@ -141,7 +141,7 @@
</label>
</div>
<div class="header-center">
<a href="index.html"><div class="brand">Reticulum Network Stack 0.5.0 beta documentation</div></a>
<a href="index.html"><div class="brand">Reticulum Network Stack 0.5.4 beta documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -167,7 +167,7 @@
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
</div>
<span class="sidebar-brand-text">Reticulum Network Stack 0.5.0 beta documentation</span>
<span class="sidebar-brand-text">Reticulum Network Stack 0.5.4 beta documentation</span>
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
+3 -3
View File
@@ -6,7 +6,7 @@
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="Communications Hardware" href="hardware.html" /><link rel="prev" title="Using Reticulum on Your System" href="using.html" />
<meta name="generator" content="sphinx-5.2.2, furo 2022.09.29"/>
<title>Understanding Reticulum - Reticulum Network Stack 0.5.0 beta documentation</title>
<title>Understanding Reticulum - Reticulum Network Stack 0.5.4 beta documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=d81277517bee4d6b0349d71bb2661d4890b5617c" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
@@ -141,7 +141,7 @@
</label>
</div>
<div class="header-center">
<a href="index.html"><div class="brand">Reticulum Network Stack 0.5.0 beta documentation</div></a>
<a href="index.html"><div class="brand">Reticulum Network Stack 0.5.4 beta documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -167,7 +167,7 @@
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
</div>
<span class="sidebar-brand-text">Reticulum Network Stack 0.5.0 beta documentation</span>
<span class="sidebar-brand-text">Reticulum Network Stack 0.5.4 beta documentation</span>
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
+5 -5
View File
@@ -6,7 +6,7 @@
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="Understanding Reticulum" href="understanding.html" /><link rel="prev" title="Getting Started Fast" href="gettingstartedfast.html" />
<meta name="generator" content="sphinx-5.2.2, furo 2022.09.29"/>
<title>Using Reticulum on Your System - Reticulum Network Stack 0.5.0 beta documentation</title>
<title>Using Reticulum on Your System - Reticulum Network Stack 0.5.4 beta documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=d81277517bee4d6b0349d71bb2661d4890b5617c" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
@@ -141,7 +141,7 @@
</label>
</div>
<div class="header-center">
<a href="index.html"><div class="brand">Reticulum Network Stack 0.5.0 beta documentation</div></a>
<a href="index.html"><div class="brand">Reticulum Network Stack 0.5.4 beta documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -167,7 +167,7 @@
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
</div>
<span class="sidebar-brand-text">Reticulum Network Stack 0.5.0 beta documentation</span>
<span class="sidebar-brand-text">Reticulum Network Stack 0.5.4 beta documentation</span>
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
@@ -420,7 +420,7 @@ AutoInterface[Local]
Traffic : 63.23 KB↑
80.17 KB↓
TCPInterface[RNS Testnet Frankfurt/frankfurt.rns.unsigned.io:4965]
TCPInterface[RNS Testnet Dublin/dublin.connect.reticulum.network:4965]
Status : Up
Mode : Full
Rate : 10.00 Mbps
@@ -459,7 +459,7 @@ destinations on the Reticulum network.</p>
rnpath c89b4da064bf66d280f0e4d8abfd9806
# Example output
Path found, destination &lt;c89b4da064bf66d280f0e4d8abfd9806&gt; is 4 hops away via &lt;f53a1c4278e0726bb73fcc623d6ce763&gt; on TCPInterface[Testnet/frankfurt.connect.reticulu.network:4965]
Path found, destination &lt;c89b4da064bf66d280f0e4d8abfd9806&gt; is 4 hops away via &lt;f53a1c4278e0726bb73fcc623d6ce763&gt; on TCPInterface[Testnet/dublin.connect.reticulum.network:4965]
</pre></div>
</div>
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>usage: rnpath [-h] [--config CONFIG] [--version] [-t] [-r] [-d] [-D] [-w seconds] [-v] [destination]
+3 -3
View File
@@ -6,7 +6,7 @@
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="Getting Started Fast" href="gettingstartedfast.html" /><link rel="prev" title="Reticulum Network Stack Manual" href="index.html" />
<meta name="generator" content="sphinx-5.2.2, furo 2022.09.29"/>
<title>What is Reticulum? - Reticulum Network Stack 0.5.0 beta documentation</title>
<title>What is Reticulum? - Reticulum Network Stack 0.5.4 beta documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=d81277517bee4d6b0349d71bb2661d4890b5617c" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
@@ -141,7 +141,7 @@
</label>
</div>
<div class="header-center">
<a href="index.html"><div class="brand">Reticulum Network Stack 0.5.0 beta documentation</div></a>
<a href="index.html"><div class="brand">Reticulum Network Stack 0.5.4 beta documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -167,7 +167,7 @@
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
</div>
<span class="sidebar-brand-text">Reticulum Network Stack 0.5.0 beta documentation</span>
<span class="sidebar-brand-text">Reticulum Network Stack 0.5.4 beta documentation</span>
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
+3
View File
@@ -13,6 +13,9 @@ project = 'Reticulum Network Stack'
copyright = '2023, Mark Qvist'
author = 'Mark Qvist'
exec(open("../../RNS/_version.py", "r").read())
version = __version__
# The full version, including alpha/beta/rc tags
import RNS
release = RNS._version.__version__+" beta"
+107 -32
View File
@@ -10,22 +10,45 @@ scenarios.
Standalone Reticulum Installation
=============================================
If you simply want to install Reticulum and related utilities on a system,
the easiest way is via ``pip``:
the easiest way is via the ``pip`` package manager:
.. code::
pip install rns
If you no not already have pip installed, you can install it using the package manager
If you do not already have pip installed, you can install it using the package manager
of your system with a command like ``sudo apt install python3-pip``,
``sudo pamac install python-pip`` or similar. You can also dowload the Reticulum release
wheels from GitHub, or other release channels, and install them offline using ``pip``:
``sudo pamac install python-pip`` or similar.
You can also dowload the Reticulum release wheels from GitHub, or other release channels,
and install them offline using ``pip``:
.. code::
pip install ./rns-0.4.6-py3-none-any.whl
pip install ./rns-0.5.1-py3-none-any.whl
Resolving Dependency & Installation Issues
=============================================
On some platforms, there may not be binary packages available for all dependencies, and
``pip`` installation may fail with an error message. In these cases, the issue can usually
be resolved by installing the development essentials packages for your platform:
.. code::
# Debian / Ubuntu / Derivatives
sudo apt install build-essential
# Arch / Manjaro / Derivatives
sudo pamac install base-devel
# Fedora
sudo dnf groupinstall "Development Tools" "Development Libraries"
With the base development packages installed, ``pip`` should be able to compile any missing
dependencies from source, and complete installation even on platforms that don't have pre-
compiled packages available.
Try Using a Reticulum-based Program
=============================================
@@ -203,12 +226,12 @@ by adding one of the following interfaces to your ``.reticulum/config`` file:
target_host = dublin.connect.reticulum.network
target_port = 4965
# TCP/IP interface to the Frankfurt hub
[[RNS Testnet Frankfurt]]
# TCP/IP interface to the BetweenTheBorders Hub (community-provided)
[[RNS Testnet BetweenTheBorders]]
type = TCPClientInterface
enabled = yes
target_host = frankfurt.connect.reticulum.network
target_port = 5377
target_host = betweentheborders.com
target_port = 4242
# Interface to I2P hub A
[[RNS Testnet I2P Hub A]]
@@ -268,7 +291,7 @@ For extended functionality, you can install optional dependencies:
.. code::
pip3 install pyserial netifaces
pip3 install pyserial
Further information can be found in the :ref:`API Reference<api-main>`.
@@ -283,7 +306,7 @@ don't use pip, but try this recipe:
.. code::
# Install dependencies
pip3 install cryptography pyserial netifaces
pip3 install cryptography pyserial
# Clone repository
git clone https://github.com/markqvist/Reticulum.git
@@ -320,30 +343,14 @@ the `disucssion forum on GitHub <https://github.com/markqvist/Reticulum/discussi
or ask one of the developers or maintainers for a good place to start.
Reticulum on ARM64
Platform-Specific Install Notes
==============================================
On some architectures, including ARM64, not all dependencies have precompiled
binaries. On such systems, you may need to install ``python3-dev`` before
installing Reticulum or programs that depend on Reticulum.
.. code::
Some platforms require a slightly different installation procedure, or have
various quirks that are worth being aware of. These are listed here.
# 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 Raspberry Pi
==============================================
It is currently recommended to use a 64-bit version of the Raspberry Pi OS
if you want to run Reticulum on Raspberry Pi computers, since 32-bit versions
don't always have packages available for some dependencies.
Reticulum on Android
==============================================
Android
^^^^^^^^^^^^^^^^^^^^^^^^
Reticulum can be used on Android in different ways. The easiest way to get
started is using an app like `Sideband <https://unsigned.io/sideband>`_.
@@ -409,6 +416,74 @@ 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. Until then you can use the `Sideband source code <https://github.com/markqvist/sideband>`_ as an example and startig point.
ARM64
^^^^^^^^^^^^^^^^^^^^^^^^
On some architectures, including ARM64, not all dependencies have precompiled
binaries. On such systems, you may 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
Raspberry Pi
^^^^^^^^^^^^^^^^^^^^^^^^^
It is currently recommended to use a 64-bit version of the Raspberry Pi OS
if you want to run Reticulum on Raspberry Pi computers, since 32-bit versions
don't always have packages available for some dependencies.
While it is possible to install and run Reticulum on 32-bit Rasperry Pi OSes,
it will require manually configuring and installing some packages, and is not
detailed in this manual.
Debian Bookworm
^^^^^^^^^^^^^^^^^^^^^^^^
On versions of Debian released after April 2023, it is no longer possible
to use ``pip`` to install packages onto your system. Unfortunately, you will need to
use the replacement ``pipx`` command instead, which places installed packages in an
isolated environment. This should not negatively affect Reticulum, but installation
via this method is not fully tested yet.
.. code::
# Install pipx
sudo apt install pipx
# Make installed programs available on the command line
pipx ensurepath
# Install Reticulum
pipx install rns
Ubuntu Lunar
^^^^^^^^^^^^^^^^^^^^^^^^
On versions of Ubuntu released after April 2023, it is no longer possible
to use ``pip`` to install packages onto your system. Unfortunately, you will need to
use the replacement ``pipx`` command instead, which places installed packages in an
isolated environment. This should not negatively affect Reticulum, but installation
via this method is not fully tested yet.
.. code::
# Install pipx
sudo apt install pipx
# Make installed programs available on the command line
pipx ensurepath
# Install Reticulum
pipx install rns
Pure-Python Reticulum
==============================================
In some rare cases, and on more obscure system types, it is not possible to
+4 -3
View File
@@ -1,15 +1,16 @@
******************************
Reticulum Network Stack Manual
******************************
This manual aims to provide you with all the information you need to
understand Reticulum, build networks or develop programs using it, or
to participate in the development of Reticulum itself.
.. only:: html
.. only:: builder_html
This manual is also available in `PDF <https://github.com/markqvist/Reticulum/releases/latest/download/Reticulum.Manual.pdf>`_ and `EPUB <https://github.com/markqvist/Reticulum/releases/latest/download/Reticulum.Manual.epub>`_ formats.
This manual is also available in `PDF <https://github.com/markqvist/Reticulum/releases/latest/download/Reticulum.Manual.pdf>`_ and `EPUB <https://github.com/markqvist/Reticulum/releases/latest/download/Reticulum.Manual.epub>`_ formats.
.. only:: html
.. only:: builder_html
Table Of Contents
=================
+2 -2
View File
@@ -223,7 +223,7 @@ interfaces, similar to the ``ifconfig`` program.
Traffic : 63.23 KB↑
80.17 KB↓
TCPInterface[RNS Testnet Frankfurt/frankfurt.rns.unsigned.io:4965]
TCPInterface[RNS Testnet Dublin/dublin.connect.reticulum.network:4965]
Status : Up
Mode : Full
Rate : 10.00 Mbps
@@ -266,7 +266,7 @@ destinations on the Reticulum network.
rnpath c89b4da064bf66d280f0e4d8abfd9806
# Example output
Path found, destination <c89b4da064bf66d280f0e4d8abfd9806> is 4 hops away via <f53a1c4278e0726bb73fcc623d6ce763> on TCPInterface[Testnet/frankfurt.connect.reticulu.network:4965]
Path found, destination <c89b4da064bf66d280f0e4d8abfd9806> is 4 hops away via <f53a1c4278e0726bb73fcc623d6ce763> on TCPInterface[Testnet/dublin.connect.reticulum.network:4965]
.. code:: text
+2 -2
View File
@@ -20,7 +20,7 @@ if pure_python:
long_description = long_description.replace("</p>", "</p>"+pure_notice)
else:
pkg_name = "rns"
requirements = ['cryptography>=3.4.7', 'pyserial>=3.5', 'netifaces']
requirements = ['cryptography>=3.4.7', 'pyserial>=3.5']
excluded_modules = exclude=["tests.*", "tests"]
packages = setuptools.find_packages(exclude=excluded_modules)
@@ -53,5 +53,5 @@ setuptools.setup(
]
},
install_requires=requirements,
python_requires='>=3.6',
python_requires='>=3.7',
)
+7 -2
View File
@@ -291,6 +291,8 @@ class TestChannel(unittest.TestCase):
raw = envelope.pack()
self.h.channel._receive(raw)
time.sleep(0.5)
self.assertEqual(1, handler1_called)
self.assertEqual(0, handler2_called)
@@ -299,6 +301,8 @@ class TestChannel(unittest.TestCase):
raw = envelope.pack()
self.h.channel._receive(raw)
time.sleep(0.5)
self.assertEqual(2, handler1_called)
self.assertEqual(1, handler2_called)
@@ -357,6 +361,8 @@ class TestChannel(unittest.TestCase):
self.h.channel._receive(packet.raw)
time.sleep(0.5)
self.assertEqual(1, len(decoded))
rx_message = decoded[0]
@@ -388,6 +394,7 @@ class TestChannel(unittest.TestCase):
packet = self.h.outlet.packets[0]
self.h.channel._receive(packet.raw)
time.sleep(0.2)
result = buffer.readline()
self.assertIsNotNone(result)
@@ -497,7 +504,5 @@ class TestChannel(unittest.TestCase):
self.assertTrue(len(result) == 0)
if __name__ == '__main__':
unittest.main(verbosity=2)
+72 -35
View File
@@ -4,6 +4,7 @@ import subprocess
import shlex
import threading
import time
import random
from unittest import skipIf
import RNS
import os
@@ -23,6 +24,8 @@ fixed_keys = [
("08bb35f92b06a0832991165a0d9b4fd91af7b7765ce4572aa6222070b11b767092b61b0fd18b3a59cae6deb9db6d4bfb1c7fcfe076cfd66eea7ddd5f877543b9", "d13712efc45ef87674fb5ac26c37c912"),
]
BUFFER_TEST_TARGET = 32000
def targets_job(caller):
cmd = "python -c \"from tests.link import targets; targets()\""
print("Opening subprocess for "+str(cmd)+"...", RNS.LOG_VERBOSE)
@@ -61,6 +64,7 @@ class TestLink(unittest.TestCase):
def tearDownClass(cls):
close_rns()
@skipIf(os.getenv('SKIP_NORMAL_TESTS') != None, "Skipping")
def test_0_valid_announce(self):
init_rns(self)
print("")
@@ -71,6 +75,7 @@ class TestLink(unittest.TestCase):
ap.pack()
self.assertEqual(RNS.Identity.validate_announce(ap), True)
@skipIf(os.getenv('SKIP_NORMAL_TESTS') != None, "Skipping")
def test_1_invalid_announce(self):
init_rns(self)
print("")
@@ -86,6 +91,7 @@ class TestLink(unittest.TestCase):
ap.send()
self.assertEqual(RNS.Identity.validate_announce(ap), False)
@skipIf(os.getenv('SKIP_NORMAL_TESTS') != None, "Skipping")
def test_2_establish(self):
init_rns(self)
print("")
@@ -105,6 +111,7 @@ class TestLink(unittest.TestCase):
time.sleep(0.5)
self.assertEqual(l1.status, RNS.Link.CLOSED)
@skipIf(os.getenv('SKIP_NORMAL_TESTS') != None, "Skipping")
def test_3_packets(self):
init_rns(self)
print("")
@@ -171,6 +178,7 @@ class TestLink(unittest.TestCase):
time.sleep(0.5)
self.assertEqual(l1.status, RNS.Link.CLOSED)
@skipIf(os.getenv('SKIP_NORMAL_TESTS') != None, "Skipping")
def test_4_micro_resource(self):
init_rns(self)
print("")
@@ -206,6 +214,7 @@ class TestLink(unittest.TestCase):
time.sleep(0.5)
self.assertEqual(l1.status, RNS.Link.CLOSED)
@skipIf(os.getenv('SKIP_NORMAL_TESTS') != None, "Skipping")
def test_5_mini_resource(self):
init_rns(self)
print("")
@@ -241,6 +250,7 @@ class TestLink(unittest.TestCase):
time.sleep(0.5)
self.assertEqual(l1.status, RNS.Link.CLOSED)
@skipIf(os.getenv('SKIP_NORMAL_TESTS') != None, "Skipping")
def test_6_small_resource(self):
init_rns(self)
print("")
@@ -276,6 +286,7 @@ class TestLink(unittest.TestCase):
self.assertEqual(l1.status, RNS.Link.CLOSED)
@skipIf(os.getenv('SKIP_NORMAL_TESTS') != None, "Skipping")
def test_7_medium_resource(self):
if RNS.Cryptography.backend() == "internal":
print("Skipping medium resource test...")
@@ -314,6 +325,7 @@ class TestLink(unittest.TestCase):
time.sleep(0.5)
self.assertEqual(l1.status, RNS.Link.CLOSED)
@skipIf(os.getenv('SKIP_NORMAL_TESTS') != None, "Skipping")
def test_9_large_resource(self):
if RNS.Cryptography.backend() == "internal":
print("Skipping large resource test...")
@@ -352,6 +364,7 @@ class TestLink(unittest.TestCase):
time.sleep(0.5)
self.assertEqual(l1.status, RNS.Link.CLOSED)
#@skipIf(os.getenv('SKIP_NORMAL_TESTS') != None, "Skipping")
def test_10_channel_round_trip(self):
global c_rns
init_rns(self)
@@ -393,13 +406,14 @@ class TestLink(unittest.TestCase):
self.assertEqual("Hello back", rx_message.data)
self.assertEqual(test_message.id, rx_message.id)
self.assertNotEqual(test_message.not_serialized, rx_message.not_serialized)
self.assertEqual(1, len(l1._channel._rx_ring))
self.assertEqual(0, len(l1._channel._rx_ring))
l1.teardown()
time.sleep(0.5)
self.assertEqual(l1.status, RNS.Link.CLOSED)
self.assertEqual(0, len(l1._channel._rx_ring))
# @skipIf(os.getenv('SKIP_NORMAL_TESTS') != None, "Skipping")
def test_11_buffer_round_trip(self):
global c_rns
init_rns(self)
@@ -442,8 +456,9 @@ class TestLink(unittest.TestCase):
time.sleep(0.5)
self.assertEqual(l1.status, RNS.Link.CLOSED)
# @skipIf(os.getenv('SKIP_NORMAL_TESTS') != None and os.getenv('RUN_SLOW_TESTS') == None, "Skipping")
def test_12_buffer_round_trip_big(self, local_bitrate = None):
global c_rns
global c_rns, buffer_read_target
init_rns(self)
print("")
print("Buffer round trip test")
@@ -478,7 +493,9 @@ class TestLink(unittest.TestCase):
buffer = None
received = []
def handle_data(ready_bytes: int):
global received_bytes
data = buffer.read(ready_bytes)
received.append(data)
@@ -487,48 +504,50 @@ class TestLink(unittest.TestCase):
# try to make the message big enough to split across packets, but
# small enough to make the test complete in a reasonable amount of time
seed_text = "0123456789"
message = seed_text*ceil(min(max(local_interface.bitrate / 8,
StreamDataMessage.MAX_DATA_LEN * 2 / len(seed_text)),
1000))
# seed_text = "0123456789"
# message = seed_text*ceil(min(max(local_interface.bitrate / 8,
# StreamDataMessage.MAX_DATA_LEN * 2 / len(seed_text)),
# 1000))
if local_interface.bitrate < 1000:
target_bytes = 3000
else:
target_bytes = BUFFER_TEST_TARGET
random.seed(154889)
message = random.randbytes(target_bytes)
buffer_read_target = len(message)
# the return message will have an appendage string " back at you"
# for every StreamDataMessage that arrives. To verify, we need
# to insert that string every MAX_DATA_LEN and also at the end.
expected_rx_message = ""
expected_rx_message = b""
for i in range(0, len(message)):
if i > 0 and (i % StreamDataMessage.MAX_DATA_LEN) == 0:
expected_rx_message += " back at you"
expected_rx_message += message[i]
expected_rx_message += " back at you"
expected_rx_message += " back at you".encode("utf-8")
expected_rx_message += bytes([message[i]])
expected_rx_message += " back at you".encode("utf-8")
# since the segments will be received at max length for a
# StreamDataMessage, the appended text will end up in a
# separate packet.
expected_chunk_count = ceil(len(message)/StreamDataMessage.MAX_DATA_LEN * 2)
print("Sending " + str(len(message)) + " bytes, receiving " + str(len(expected_rx_message)) + " bytes, " +
"expecting " + str(expected_chunk_count) + " chunks of " + str(StreamDataMessage.MAX_DATA_LEN) + " bytes")
transfer_sleep = max(expected_chunk_count * 3 * c_rns.MTU / local_interface.bitrate * 8, 3)
print("Will take up to " + str(round(transfer_sleep, 0)) + " seconds to transfer")
expected_ready_time = time.time() + transfer_sleep
buffer.write(message.encode("utf-8"))
print("Sending " + str(len(message)) + " bytes, receiving " + str(len(expected_rx_message)) + " bytes, ")
buffer.write(message)
buffer.flush()
# delay a reasonable time for the send and receive
# a chunk each way plus a little more for a proof each way
while time.time() < expected_ready_time and len(received) < expected_chunk_count:
time.sleep(0.1)
# sleep for at least one more chunk round trip in case there
# are more chunks than expected
if time.time() < expected_ready_time:
time.sleep(max(c_rns.MTU * 2 / local_interface.bitrate * 8, 1))
# Why does this not always work out correctly?
# self.assertEqual(expected_chunk_count, len(received))
timeout = time.time() + 4
while not time.time() > timeout:
time.sleep(1)
print(f"Received {len(received)} chunks so far")
time.sleep(1)
data = bytearray()
for rx in received:
data.extend(rx)
rx_message = data
rx_message = data.decode("utf-8")
print(f"Received {len(received)} chunks, totalling {len(rx_message)} bytes")
self.assertEqual(len(expected_rx_message), len(rx_message))
for i in range(0, len(expected_rx_message)):
@@ -546,7 +565,7 @@ class TestLink(unittest.TestCase):
# RUN_SLOW_TESTS=1 python tests/link.py TestLink.test_13_buffer_round_trip_big_slow
# Or
# make RUN_SLOW_TESTS=1 test
@skipIf(int(os.getenv('RUN_SLOW_TESTS', 0)) < 1, "Not running slow tests")
@skipIf(os.getenv('RUN_SLOW_TESTS') == None, "Not running slow tests")
def test_13_buffer_round_trip_big_slow(self):
self.test_12_buffer_round_trip_big(local_bitrate=410)
@@ -572,7 +591,7 @@ class TestLink(unittest.TestCase):
if __name__ == '__main__':
unittest.main(verbosity=1)
buffer_read_len = 0
def targets(yp=False):
if yp:
import yappi
@@ -610,21 +629,39 @@ def targets(yp=False):
channel = link.get_channel()
def handle_message(message):
message.data = message.data + " back"
channel.send(message)
if isinstance(message, MessageTest):
message.data = message.data + " back"
channel.send(message)
channel.register_message_type(MessageTest)
channel.add_message_handler(handle_message)
buffer = None
response_data = []
def handle_buffer(ready_bytes: int):
global buffer_read_len, BUFFER_TEST_TARGET
data = buffer.read(ready_bytes)
buffer.write((data.decode("utf-8") + " back at you").encode("utf-8"))
buffer.flush()
buffer_read_len += len(data)
response_data.append(data)
if data == "Hi there".encode("utf-8"):
RNS.log("Sending response")
for data in response_data:
buffer.write(data + " back at you".encode("utf-8"))
buffer.flush()
buffer_read_len = 0
if buffer_read_len == BUFFER_TEST_TARGET:
RNS.log("Sending response")
for data in response_data:
buffer.write(data + " back at you".encode("utf-8"))
buffer.flush()
buffer_read_len = 0
buffer = RNS.Buffer.create_bidirectional_buffer(0, 0, channel, handle_buffer)
m_rns = RNS.Reticulum("./tests/rnsconfig")
m_rns = RNS.Reticulum("./tests/rnsconfig", logdest=RNS.LOG_FILE, loglevel=RNS.LOG_EXTREME)
id1 = RNS.Identity.from_bytes(bytes.fromhex(fixed_keys[0][0]))
d1 = RNS.Destination(id1, RNS.Destination.IN, RNS.Destination.SINGLE, APP_NAME, "link", "establish")
d1.set_proof_strategy(RNS.Destination.PROVE_ALL)