mirror of
https://github.com/markqvist/Reticulum.git
synced 2026-07-23 16:28:12 -07:00
Compare commits
61 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| bfade97051 | |||
| 3c4ef622cc | |||
| 3898d63689 | |||
| ec8d43a548 | |||
| 5081255317 | |||
| 13a538168f | |||
| a5ed0a4321 | |||
| d93f9798ae | |||
| 88833f170b | |||
| cd6911ed53 | |||
| b95c51b96e | |||
| 3a36c367fe | |||
| cf6010da59 | |||
| bb2897445f | |||
| 406b141370 | |||
| 00b2f81a82 | |||
| 9adf045a31 | |||
| de0f399a16 | |||
| dca2a92829 | |||
| b1b3ff7143 | |||
| a0f0f31807 | |||
| 72db6e0ef2 | |||
| b70688883a | |||
| dd3ddb9d8a | |||
| 4ba1750c01 | |||
| eacff56fe2 | |||
| 8768e163ae | |||
| c0bc19d998 | |||
| c99ec922aa | |||
| 0a6f76e41b | |||
| 156ce9cd34 | |||
| 176092ebf9 | |||
| 98817e3f9d | |||
| 276b7bb92f | |||
| d1813e1ff0 | |||
| 5355a9bb20 | |||
| 48d17a8616 | |||
| 7e603f9606 | |||
| ac6f0199f5 | |||
| 71f6f721fd | |||
| 51b7dc7cd4 | |||
| 03ddb08248 | |||
| 422dc05549 | |||
| 50c0a354c9 | |||
| e98487c1cf | |||
| 50e03a24e8 | |||
| 0cc42568c7 | |||
| 41790ca707 | |||
| 20b1bfd01e | |||
| 1be5e10abe | |||
| 13a9ebed83 | |||
| aee8c3be0c | |||
| ef8ccf67ed | |||
| 91d531b053 | |||
| 0de169836e | |||
| a2ef978208 | |||
| 5b3bb050e7 | |||
| 9b9efe5fac | |||
| 482d5ac4cb | |||
| 804a7ddbe1 | |||
| 739fdd387e |
@@ -14,3 +14,4 @@ tests/rnsconfig/logfile*
|
|||||||
*.data
|
*.data
|
||||||
*.result
|
*.result
|
||||||
.buildinfo.bak
|
.buildinfo.bak
|
||||||
|
docs/Reticulum Manual.*
|
||||||
|
|||||||
+76
-116
@@ -1,14 +1,20 @@
|
|||||||
### 2026-05-28: RNS 1.3.2
|
### 2026-07-19: RNS 1.3.9
|
||||||
|
|
||||||
This release adds commit signing and validation support to the `rngit` system, as well as improvements to the blackhole functionality.
|
**Critical Security Update**: This release fixes a severe security flaw in `rnsh`. Due to the nature of the issue, I will not disclose any further details for the time being. Once operators have had time to update, I will provide a full report for transparency. If you use `rnsh`, update **right now**.
|
||||||
|
|
||||||
|
**Important**: The new version of `rnsh` changes default identity file locations, and these will now be sourced from `~/.rnsh/identity` (initiator) and `~/.rnsh/identity.default` (listener). Make sure you copy your old files to this directory, or specify a custom identity path using the command line arguments. The `--config` argument has also been renamed to `--rnsconfig`, and the `--config` argument will now specify the `rnsh` configuration directory instead of the RNS configuration directory, bringing the behavior into alignment with other RNS utilities.
|
||||||
|
|
||||||
|
Additionally, this release includes automated blocking of fast-flapping clients on `BackboneInterface` listeners (see the Interfaces chapter of the manual for details), and a number of improvements to resource handling. The logging system has also been improved, and path and destination information moved to a new `LOG_PATHING` loglevel, to decrease log noise.
|
||||||
|
|
||||||
**Changes**
|
**Changes**
|
||||||
- Extended blackhole functionality to immediately terminate links from blackholed identities
|
- Fixed a critical security issue in `rnsh`
|
||||||
- Added commit signing and validation to `rngit`
|
- Added automated blocking of fast-flapping clients to `BackboneInterface`
|
||||||
- Added commit hash inclusion in generated release manifest to `rngit`
|
- Added new `LOG_PATHING` loglevel, improved logging
|
||||||
- Added local `verify` operation shorthand to `rngit release`
|
- Added ability to make internal-mode interfaces discoverable
|
||||||
- Added option to configure blackhole update interval
|
- Added ability to get discoverable interface location from external script
|
||||||
- Added configuration option to log without timestamps
|
- Added `RESOURCE_RCL` signal on resource receiver cancel
|
||||||
|
- Improved resource handling and reliability
|
||||||
|
- Updated `rnsh` config args to work similarly to other RNS utilities
|
||||||
|
|
||||||
**Verified Retrieval**
|
**Verified Retrieval**
|
||||||
You can retrieve and verify this release over Reticulum using the built-in `rngit release` utility. To retrieve only the installation `.whl` package, and the release manifest for future updates, you can use:
|
You can retrieve and verify this release over Reticulum using the built-in `rngit release` utility. To retrieve only the installation `.whl` package, and the release manifest for future updates, you can use:
|
||||||
@@ -38,6 +44,68 @@ rnid -i bc7291552be7a58f361522990465165c -V rns_*.rsm *.rsg
|
|||||||
|
|
||||||
The `rnid` utility will then verify the signatures, and display whether they are valid. If the signature cannot be verified, the release has been tampered with and should be discarded.
|
The `rnid` utility will then verify the signatures, and display whether they are valid. If the signature cannot be verified, the release has been tampered with and should be discarded.
|
||||||
|
|
||||||
|
### 2026-07-10: RNS 1.3.8
|
||||||
|
|
||||||
|
This release fixes various inconsistencies in link and hop-count related APIs.
|
||||||
|
|
||||||
|
**Changes**
|
||||||
|
- Fixed inconsistent link traffic stats calculation
|
||||||
|
- Fixed link hop-count metric only being available on initiator side
|
||||||
|
- Fixed potential hop-count serialization error on transport
|
||||||
|
- Updated `WeaveInterface` to support latest Weave firmware
|
||||||
|
|
||||||
|
### 2026-07-03: RNS 1.3.7
|
||||||
|
|
||||||
|
This maintenance release improves announces propagation logic, and adds additional options for configuring announce propagation and interface behavior in transport mode.
|
||||||
|
|
||||||
|
**Changes**
|
||||||
|
- Added `internal` interface mode
|
||||||
|
- Added `recursive_prs` interface option
|
||||||
|
- Added `announces_from_internal` interface option
|
||||||
|
- Added new options to the manual
|
||||||
|
- Improved and cleaned up announce propagation logic
|
||||||
|
|
||||||
|
### 2026-07-03: RNS 1.3.6
|
||||||
|
|
||||||
|
This release contained a bug in the local instance transport handling, and was superseded by version `1.3.7`.
|
||||||
|
|
||||||
|
### 2026-06-01: RNS 1.3.5
|
||||||
|
|
||||||
|
This maintenance release contains an important fix for `AutoInterface` reliability when roaming between different physical networks.
|
||||||
|
|
||||||
|
**Changes**
|
||||||
|
- Fixed UDP listener replacement deadlocking inbound AutoInterface traffic when fast-roaming between physical interfaces or WiFi APs
|
||||||
|
- Fixed some paths never resolving when using other interfaces at the same time as a deadlocked AutoInterface
|
||||||
|
|
||||||
|
### 2026-05-29: RNS 1.3.4
|
||||||
|
|
||||||
|
This release fixes a regression that could cause sub-optimal path selection under conditions where the same announce was received within a very short timespan on different interfaces, as well as a few other bugs and inefficiencies.
|
||||||
|
|
||||||
|
**Changes**
|
||||||
|
- Fixed regression in inbound announce de-duplication
|
||||||
|
- Fixed missing check for shared instance type configuration conflict
|
||||||
|
- Fixed superfluous path state configuration on new announce from new destinations
|
||||||
|
- Improved cleanup of stale known destinations
|
||||||
|
- Improved shared instance RPC handling
|
||||||
|
|
||||||
|
### 2026-05-28: RNS 1.3.3
|
||||||
|
|
||||||
|
This release fixes a regression in persistence of known destination on Windows.
|
||||||
|
|
||||||
|
**Changes**
|
||||||
|
- Fixed regression in known destinations persist on Windows
|
||||||
|
|
||||||
|
### 2026-05-28: RNS 1.3.2
|
||||||
|
|
||||||
|
This release adds commit signing and validation support to the `rngit` system, as well as improvements to the blackhole functionality.
|
||||||
|
|
||||||
|
**Changes**
|
||||||
|
- Extended blackhole functionality to immediately terminate links from blackholed identities
|
||||||
|
- Added commit signing and validation to `rngit`
|
||||||
|
- Added commit hash inclusion in generated release manifest to `rngit`
|
||||||
|
- Added local `verify` operation shorthand to `rngit release`
|
||||||
|
- Added option to configure blackhole update interval
|
||||||
|
- Added configuration option to log without timestamps
|
||||||
|
|
||||||
### 2026-05-22: RNS 1.3.1
|
### 2026-05-22: RNS 1.3.1
|
||||||
|
|
||||||
@@ -46,34 +114,6 @@ This maintenance release fixes a single bug.
|
|||||||
**Changes**
|
**Changes**
|
||||||
- Fixed regression in request response transfer size accumulator
|
- Fixed regression in request response transfer size accumulator
|
||||||
|
|
||||||
**Verified Retrieval**
|
|
||||||
You can retrieve and verify this release over Reticulum using the built-in `rngit release` utility. To download all artifacts, and the release manifest for future updates, you can use the following command:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
rngit release rns://7649a50d84610232d1416b41d2896aff/reticulum/reticulum fetch latest:all --signer bc7291552be7a58f361522990465165c
|
|
||||||
```
|
|
||||||
|
|
||||||
To retrieve only the `.whl` package for installation, and the release manifest, you can use:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
rngit release rns://7649a50d84610232d1416b41d2896aff/reticulum/reticulum fetch "latest:rns-*-py3-none-any.whl" --signer bc7291552be7a58f361522990465165c
|
|
||||||
```
|
|
||||||
|
|
||||||
**Release Signatures**
|
|
||||||
Release artifacts include a signed `rsm` release manifest and `rsg` signature files that can be validated against the RNS release signing identity `<bc7291552be7a58f361522990465165c>` using `rngit` or `rnid`. To perform an offline verification of all release artifacts using a manifest:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
rngit release rns_*.rsm fetch latest:all --offline --signer bc7291552be7a58f361522990465165c
|
|
||||||
```
|
|
||||||
|
|
||||||
To verify releases using individual `rsg` files, while also verifying the manifest itself, download the `rsm` and `rsg` signatures, make sure they are in the same folder as the release artifact, and run `rnid` signature verification with the release identity as the required signer:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
rnid -i bc7291552be7a58f361522990465165c -V rns_*.rsm *.rsg
|
|
||||||
```
|
|
||||||
|
|
||||||
The `rnid` utility will then verify the signatures, and display whether they are valid. If the signature cannot be verified, the release has been tampered with and should be discarded.
|
|
||||||
|
|
||||||
### 2026-05-21: RNS 1.3.0
|
### 2026-05-21: RNS 1.3.0
|
||||||
|
|
||||||
This maintenance release fixes a number of bugs.
|
This maintenance release fixes a number of bugs.
|
||||||
@@ -88,28 +128,6 @@ This maintenance release fixes a number of bugs.
|
|||||||
- Adjusted timeouts for remote operations in `rngit`
|
- Adjusted timeouts for remote operations in `rngit`
|
||||||
- Updated documentation
|
- Updated documentation
|
||||||
|
|
||||||
**Verified Retrieval**
|
|
||||||
You can retrieve and verify this release over Reticulum using the built-in `rngit release` utility. To download all artifacts, and the release manifest for future updates, you can use the following command:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
rngit release rns://7649a50d84610232d1416b41d2896aff/reticulum/reticulum fetch latest:all --signer bc7291552be7a58f361522990465165c
|
|
||||||
```
|
|
||||||
|
|
||||||
To retrieve only the `.whl` package for installation, you can use:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
rngit release rns://7649a50d84610232d1416b41d2896aff/reticulum/reticulum fetch latest:rns-1.3.0-py3-none-any.whl --signer bc7291552be7a58f361522990465165c
|
|
||||||
```
|
|
||||||
|
|
||||||
**Release Signatures**
|
|
||||||
Release artifacts include a signed `rsm` release manifest and `rsg` signature files that can be validated against the RNS release signing identity `<bc7291552be7a58f361522990465165c>` using `rnid`. To verify files, download the `rsm` and `rsg` signatures, make sure they are in the same folder as the release artifact, and run `rnid` signature verification with the release identity as the required signer:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
rnid -i bc7291552be7a58f361522990465165c -V manifest.rsm *.rsg
|
|
||||||
```
|
|
||||||
|
|
||||||
The `rnid` utility will then verify the signatures, and display whether they are valid. If the signature cannot be verified, the release has been tampered with and should be discarded.
|
|
||||||
|
|
||||||
### 2026-05-19: RNS 1.2.9
|
### 2026-05-19: RNS 1.2.9
|
||||||
|
|
||||||
This release completes the operational functionality of the `rngit` system, which now has full release creation, fetch and verified update support using the `rngit release` command. Additionally, two chapters have been added to the manual should cover all the things that `rngit` is currently capable of.
|
This release completes the operational functionality of the `rngit` system, which now has full release creation, fetch and verified update support using the `rngit release` command. Additionally, two chapters have been added to the manual should cover all the things that `rngit` is currently capable of.
|
||||||
@@ -125,28 +143,6 @@ This release completes the operational functionality of the `rngit` system, whic
|
|||||||
- Improved page node ref link handling in `rngit`
|
- Improved page node ref link handling in `rngit`
|
||||||
- Improved logging in various locations
|
- Improved logging in various locations
|
||||||
|
|
||||||
**Verified Retrieval**
|
|
||||||
You can retrieve and verify this release over Reticulum using the built-in `rngit release` utility. To download all artifacts, and the release manifest for future updates, you can use the following command:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
rngit release rns://7649a50d84610232d1416b41d2896aff/reticulum/reticulum fetch latest:all --signer bc7291552be7a58f361522990465165c
|
|
||||||
```
|
|
||||||
|
|
||||||
To retrieve only the `.whl` package for installation, you can use:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
rngit release rns://7649a50d84610232d1416b41d2896aff/reticulum/reticulum fetch latest:rns-1.2.8-py3-none-any.whl --signer bc7291552be7a58f361522990465165c
|
|
||||||
```
|
|
||||||
|
|
||||||
**Release Signatures**
|
|
||||||
Release artifacts include a signed `rsm` release manifest and `rsg` signature files that can be validated against the RNS release signing identity `<bc7291552be7a58f361522990465165c>` using `rnid`. To verify files, download the `rsm` and `rsg` signatures, make sure they are in the same folder as the release artifact, and run `rnid` signature verification with the release identity as the required signer:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
rnid -i bc7291552be7a58f361522990465165c -V manifest.rsm *.rsg
|
|
||||||
```
|
|
||||||
|
|
||||||
The `rnid` utility will then verify the signatures, and display whether they are valid. If the signature cannot be verified, the release has been tampered with and should be discarded.
|
|
||||||
|
|
||||||
### 2026-05-18: RNS 1.2.8
|
### 2026-05-18: RNS 1.2.8
|
||||||
|
|
||||||
This release improves the `rngit` system with signed release manifest generation and automatic artifact signing. It also includes several additions to `rnid` and various minor fixes and improvements to the `rngit` system.
|
This release improves the `rngit` system with signed release manifest generation and automatic artifact signing. It also includes several additions to `rnid` and various minor fixes and improvements to the `rngit` system.
|
||||||
@@ -167,15 +163,6 @@ This release improves the `rngit` system with signed release manifest generation
|
|||||||
- Fixed various minor bugs and inconsistencies in `rngit`
|
- Fixed various minor bugs and inconsistencies in `rngit`
|
||||||
- Dropped `note` metadata field requirement from `rsg` structure
|
- Dropped `note` metadata field requirement from `rsg` structure
|
||||||
|
|
||||||
**Release Signatures**
|
|
||||||
Release artifacts include a signed `rsm` release manifest and `rsg` signature files that can be validated against the RNS release signing identity `<bc7291552be7a58f361522990465165c>` using `rnid`. To verify files, download the `rsm` and `rsg` signatures, make sure they are in the same folder as the release artifact, and run `rnid` signature verification with the release identity as the required signer:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
rnid -i bc7291552be7a58f361522990465165c -V manifest.rsm *.rsg
|
|
||||||
```
|
|
||||||
|
|
||||||
The `rnid` utility will then verify the signatures, and display whether they are valid. If the signature cannot be verified, the release has been tampered with and should be discarded.
|
|
||||||
|
|
||||||
### 2026-05-17: RNS 1.2.7
|
### 2026-05-17: RNS 1.2.7
|
||||||
|
|
||||||
This release significantly improves the `rngit` system with fork, mirroring and empty repository creation functionality, a new work document proposals feature, improvements to the transport core reliability and efficiency and various other tweaks and improvements.
|
This release significantly improves the `rngit` system with fork, mirroring and empty repository creation functionality, a new work document proposals feature, improvements to the transport core reliability and efficiency and various other tweaks and improvements.
|
||||||
@@ -193,15 +180,6 @@ This release significantly improves the `rngit` system with fork, mirroring and
|
|||||||
- Improved transfer completed feedback in `rncp`, thanks to **neutral**
|
- Improved transfer completed feedback in `rncp`, thanks to **neutral**
|
||||||
- Improved interface transport insertion and removal
|
- Improved interface transport insertion and removal
|
||||||
|
|
||||||
**Release Signatures**
|
|
||||||
Release artifacts include `rsg` signature files that can be validated against the RNS release signing identity `<bc7291552be7a58f361522990465165c>` using `rnid`. To verify files, download the `rsg` signatures, make sure they are in the same folder as the release artifact, and run `rnid` signature verification with the release identity as the required signer:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
rnid -i bc7291552be7a58f361522990465165c -V rns*.whl
|
|
||||||
```
|
|
||||||
|
|
||||||
The `rnid` utility will then verify the signatures, and display whether it is valid. If the signature cannot be verified, the file has been tampered with and should be thrown very far away in a jiffy.
|
|
||||||
|
|
||||||
### 2026-05-14: RNS 1.2.6
|
### 2026-05-14: RNS 1.2.6
|
||||||
|
|
||||||
This release adds further improvements to the `rnid` and `rngit` utilities, and includes several bugfixes and other improvements.
|
This release adds further improvements to the `rnid` and `rngit` utilities, and includes several bugfixes and other improvements.
|
||||||
@@ -223,15 +201,6 @@ This release adds further improvements to the `rnid` and `rngit` utilities, and
|
|||||||
- Fixed potential race condition in interface discovery
|
- Fixed potential race condition in interface discovery
|
||||||
- Fixed `rngit` remote helper hanging on startup if no client config had been created previously, and RNS loglevel was configured at debug or higher
|
- Fixed `rngit` remote helper hanging on startup if no client config had been created previously, and RNS loglevel was configured at debug or higher
|
||||||
|
|
||||||
**Release Signatures**
|
|
||||||
Release artifacts include `rsg` signature files that can be validated against the RNS release signing identity `<bc7291552be7a58f361522990465165c>` using `rnid`. To verify files, download the `rsg` signatures, make sure they are in the same folder as the release artifact, and run `rnid` signature verification with the release identity as the required signer:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
rnid -i bc7291552be7a58f361522990465165c -V rns*.whl
|
|
||||||
```
|
|
||||||
|
|
||||||
The `rnid` utility will then verify the signatures, and display whether it is valid. If the signature cannot be verified, the file has been tampered with and should be thrown very far away in a jiffy.
|
|
||||||
|
|
||||||
### 2026-05-09: RNS 1.2.5
|
### 2026-05-09: RNS 1.2.5
|
||||||
|
|
||||||
This release brings substantial improvements to path request handling, and should significantly reduce overall network and local transport node processing loads. Path requests are now automatically ingress and egress limited per interface and sub-interface. Although the defaults are effective and sane, and should work right out of the box bring an end to practically all the PR and announce spam going on lately, the backend is fully configurable for both defaults and per interface, if you want to fiddle with the settings.
|
This release brings substantial improvements to path request handling, and should significantly reduce overall network and local transport node processing loads. Path requests are now automatically ingress and egress limited per interface and sub-interface. Although the defaults are effective and sane, and should work right out of the box bring an end to practically all the PR and announce spam going on lately, the backend is fully configurable for both defaults and per interface, if you want to fiddle with the settings.
|
||||||
@@ -260,15 +229,6 @@ For all node ops out there, I'd recomment updating to this at some sort of semi-
|
|||||||
- Ensured canonical validation functions in `rngit`
|
- Ensured canonical validation functions in `rngit`
|
||||||
- Lots of other small fixes and stability improvements to `rngit`
|
- Lots of other small fixes and stability improvements to `rngit`
|
||||||
|
|
||||||
**Release Signatures**
|
|
||||||
Release artifacts include `rsg` signature files that can be validated against the RNS release signing identity `<bc7291552be7a58f361522990465165c>` using `rnid`. To verify files, download the `rsg` signatures, make sure they are in the same folder as the release artifact, and run `rnid` signature verification with the release identity as the required signer:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
rnid -i bc7291552be7a58f361522990465165c -V rns-1.2.5-py3-none-any.whl
|
|
||||||
```
|
|
||||||
|
|
||||||
The `rnid` utility will then verify the signatures, and display whether it is valid. If the signature cannot be verified, the file has been tampered with and should be thrown very far away in a jiffy.
|
|
||||||
|
|
||||||
### 2026-05-07: RNS 1.2.4
|
### 2026-05-07: RNS 1.2.4
|
||||||
|
|
||||||
This release brings a complete rewrite and update to the `rnid` utility, which is now a lot more useful, and better at finding and saving identities. It also includes a bunch of other improvements, such as expanded `rngit` functionality, better transport performance and a few bugfixes. Enjoy!
|
This release brings a complete rewrite and update to the `rnid` utility, which is now a lot more useful, and better at finding and saving identities. It also includes a bunch of other improvements, such as expanded `rngit` functionality, better transport performance and a few bugfixes. Enjoy!
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ manual:
|
|||||||
make -C docs latexpdf epub
|
make -C docs latexpdf epub
|
||||||
|
|
||||||
distcollect:
|
distcollect:
|
||||||
cp docs/Reticulum\ Manual.* dist
|
mv docs/Reticulum\ Manual.* dist
|
||||||
|
|
||||||
build_spkg: remove_symlinks build_sdist create_symlinks
|
build_spkg: remove_symlinks build_sdist create_symlinks
|
||||||
|
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ userland, and can run on practically any system that runs Python 3.
|
|||||||
## Read The Manual
|
## Read The Manual
|
||||||
The full documentation for Reticulum is available at [markqvist.github.io/Reticulum/manual/](https://markqvist.github.io/Reticulum/manual/).
|
The full documentation for Reticulum is available at [markqvist.github.io/Reticulum/manual/](https://markqvist.github.io/Reticulum/manual/).
|
||||||
|
|
||||||
You can also download the [Reticulum manual as a PDF](https://github.com/markqvist/Reticulum/raw/master/docs/Reticulum%20Manual.pdf) or [as an e-book in EPUB format](https://github.com/markqvist/Reticulum/raw/master/docs/Reticulum%20Manual.epub).
|
You can also download the [Reticulum manual as a PDF](https://github.com/markqvist/Reticulum/releases/latest/download/Reticulum.Manual.pdf) or [as an e-book in EPUB format](https://github.com/markqvist/Reticulum/releases/latest/download/Reticulum.Manual.epub).
|
||||||
|
|
||||||
For more info, see [reticulum.network](https://reticulum.network/) and [the FAQ section of the wiki](https://github.com/markqvist/Reticulum/wiki/Frequently-Asked-Questions).
|
For more info, see [reticulum.network](https://reticulum.network/) and [the FAQ section of the wiki](https://github.com/markqvist/Reticulum/wiki/Frequently-Asked-Questions).
|
||||||
|
|
||||||
@@ -85,6 +85,12 @@ The Reticulum Protocol is defined entirely and authoritatively by this reference
|
|||||||
implementation, and its associated manual. It is maintained by Mark Qvist,
|
implementation, and its associated manual. It is maintained by Mark Qvist,
|
||||||
identified by the Reticulum Identity `<bc7291552be7a58f361522990465165c>`.
|
identified by the Reticulum Identity `<bc7291552be7a58f361522990465165c>`.
|
||||||
|
|
||||||
|
No second-order protocol specification derivatives currently do, or will ever exist. You can't get a better specification than one that is executable, functional, testable, deployed and operating 24/7 across the planet.
|
||||||
|
|
||||||
|
There is not, and never will be a separate, derived "formal spec", "RFC" or anything similar for Reticulum:
|
||||||
|
|
||||||
|
**The reference implementation *is* the final, authoritative specification.**
|
||||||
|
|
||||||
Compatibility with the Reticulum Protocol is defined as having full interoperability,
|
Compatibility with the Reticulum Protocol is defined as having full interoperability,
|
||||||
and sufficient functional parity with this reference implementation. Any specific protocol
|
and sufficient functional parity with this reference implementation. Any specific protocol
|
||||||
implementation that achieves this is Reticulum. Any that does not is not Reticulum.
|
implementation that achieves this is Reticulum. Any that does not is not Reticulum.
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ No kernel modules or drivers are required. Reticulum runs completely in userland
|
|||||||
|
|
||||||
The full documentation for Reticulum is available on `_`!`[this node`:/page/blob.mu`g=reticulum|r=reticulum|ref=HEAD|path=docs/markdown/index.md]`!`_.
|
The full documentation for Reticulum is available on `_`!`[this node`:/page/blob.mu`g=reticulum|r=reticulum|ref=HEAD|path=docs/markdown/index.md]`!`_.
|
||||||
|
|
||||||
You can also download the `_`!`[Reticulum manual as a PDF`:/file/download`g=reticulum|r=reticulum|ref=HEAD|path=docs%2FReticulum+Manual.pdf]`!`_ or `_`!`[as an e-book in EPUB format`:/file/download`g=reticulum|r=reticulum|ref=HEAD|path=docs%2FReticulum+Manual.pdf]`!`_.
|
You can also download the `_`!`[Reticulum manual as a PDF`:/file/artifact`g=reticulum|r=reticulum|t=latest|a=Reticulum+Manual.pdf]`!`_ or `_`!`[as an e-book in EPUB format`:/file/artifact`g=reticulum|r=reticulum|t=latest|a=Reticulum+Manual.epub]`!`_.
|
||||||
|
|
||||||
>> Notable Features
|
>> Notable Features
|
||||||
|
|
||||||
@@ -58,6 +58,12 @@ You can also download the `_`!`[Reticulum manual as a PDF`:/file/download`g=reti
|
|||||||
|
|
||||||
The Python code in this repository is the Reference Implementation of Reticulum. The Reticulum Protocol is defined entirely and authoritatively by this reference implementation, and its associated manual. It is maintained by Mark Qvist, identified by the Reticulum Identity `B333<bc7291552be7a58f361522990465165c>`b.
|
The Python code in this repository is the Reference Implementation of Reticulum. The Reticulum Protocol is defined entirely and authoritatively by this reference implementation, and its associated manual. It is maintained by Mark Qvist, identified by the Reticulum Identity `B333<bc7291552be7a58f361522990465165c>`b.
|
||||||
|
|
||||||
|
No second-order protocol specification derivatives currently do, or will ever exist. You can't get a better specification than one that is executable, functional, testable, deployed and operating 24/7 across the planet.
|
||||||
|
|
||||||
|
There is not, and never will be a separate, derived "formal spec", "RFC" or anything similar for Reticulum:
|
||||||
|
|
||||||
|
`!The reference implementation `*is`* the final, authoritative specification.`!
|
||||||
|
|
||||||
Compatibility with the Reticulum Protocol is defined as having full interoperability, and sufficient functional parity with this reference implementation. Any specific protocol implementation that achieves this is Reticulum. Any that does not is not Reticulum.
|
Compatibility with the Reticulum Protocol is defined as having full interoperability, and sufficient functional parity with this reference implementation. Any specific protocol implementation that achieves this is Reticulum. Any that does not is not Reticulum.
|
||||||
|
|
||||||
The reference implementation is licensed under the Reticulum License.
|
The reference implementation is licensed under the Reticulum License.
|
||||||
|
|||||||
+28
-1
@@ -99,6 +99,33 @@ class InterfaceAnnouncer():
|
|||||||
|
|
||||||
if not interface_type in self.DISCOVERABLE_INTERFACE_TYPES: return None
|
if not interface_type in self.DISCOVERABLE_INTERFACE_TYPES: return None
|
||||||
else:
|
else:
|
||||||
|
if not RNS.vendor.platformutils.is_windows() and interface.discovery_location:
|
||||||
|
try:
|
||||||
|
discovery_location = self.sanitize(interface.discovery_location)
|
||||||
|
exec_path = os.path.expanduser(discovery_location)
|
||||||
|
if os.path.isfile(exec_path) and os.access(exec_path, os.X_OK):
|
||||||
|
RNS.log(f"Evaluating discovery location from executable at {exec_path}", RNS.LOG_DEBUG)
|
||||||
|
exec_result = subprocess.run([exec_path], stdout=subprocess.PIPE)
|
||||||
|
exec_stdout = exec_result.stdout.decode("utf-8")
|
||||||
|
if exec_result.returncode != 0: raise ValueError("Non-zero exit code from subprocess")
|
||||||
|
discovery_location = self.sanitize(exec_stdout)
|
||||||
|
location_components = discovery_location.replace(" ", "").split(",")
|
||||||
|
if len(location_components) != 3: raise ValueError(f"Invalid location component count: {len(location_components)}")
|
||||||
|
dlat = float(location_components[0])
|
||||||
|
dlon = float(location_components[1])
|
||||||
|
dhgt = float(location_components[2])
|
||||||
|
if dlat < -90 or dlat > 90: raise ValueError(f"Invalid latitude: {dlat}")
|
||||||
|
if dlon < -180 or dlat > 180: raise ValueError(f"Invalid longitude: {dlon}")
|
||||||
|
if dhgt < -4000 or dhgt > 1e6: raise ValueError(f"Invalid height: {dhgt}")
|
||||||
|
interface.discovery_latitude = dlat
|
||||||
|
interface.discovery_longitude = dlon
|
||||||
|
interface.discovery_height = dhgt
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
RNS.log(f"Error while getting reachable_on from executable at {interface.reachable_on}: {e}", RNS.LOG_ERROR)
|
||||||
|
RNS.log(f"Aborting discovery announce", RNS.LOG_ERROR)
|
||||||
|
return None
|
||||||
|
|
||||||
flags = 0x00
|
flags = 0x00
|
||||||
info = {INTERFACE_TYPE: interface_type,
|
info = {INTERFACE_TYPE: interface_type,
|
||||||
TRANSPORT: RNS.Reticulum.transport_enabled(),
|
TRANSPORT: RNS.Reticulum.transport_enabled(),
|
||||||
@@ -237,7 +264,7 @@ class InterfaceAnnounceHandler:
|
|||||||
valid = self.stamper.stamp_valid(stamp, self.required_value, workblock)
|
valid = self.stamper.stamp_valid(stamp, self.required_value, workblock)
|
||||||
|
|
||||||
if not valid:
|
if not valid:
|
||||||
RNS.log(f"Ignored discovered interface with invalid stamp", RNS.LOG_DEBUG)
|
RNS.log(f"Ignored discovered interface with insufficient stamp value {value}", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
|
||||||
return
|
return
|
||||||
|
|
||||||
if value < self.required_value: RNS.log(f"Ignored discovered interface with stamp value {value}", RNS.LOG_DEBUG)
|
if value < self.required_value: RNS.log(f"Ignored discovered interface with stamp value {value}", RNS.LOG_DEBUG)
|
||||||
|
|||||||
+15
-11
@@ -213,12 +213,12 @@ class Identity:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
RNS.log("Skipped recombining known destinations from disk, since an error occurred: "+str(e), RNS.LOG_WARNING)
|
RNS.log("Skipped recombining known destinations from disk, since an error occurred: "+str(e), RNS.LOG_WARNING)
|
||||||
|
|
||||||
RNS.log("Saving "+str(len(Identity.known_destinations))+" known destinations to storage...", RNS.LOG_VERBOSE)
|
RNS.log("Saving "+str(len(Identity.known_destinations))+" known destinations to storage...", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
|
||||||
temp_file = RNS.Reticulum.storagepath+f"/known_destinations.tmp.{time.time()}"
|
temp_file = RNS.Reticulum.storagepath+f"/known_destinations.tmp.{time.time()}"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with open(temp_file,"wb") as file: umsgpack.dump(Identity.known_destinations.copy(), file)
|
with open(temp_file,"wb") as file: umsgpack.dump(Identity.known_destinations.copy(), file)
|
||||||
os.rename(temp_file, RNS.Reticulum.storagepath+f"/known_destinations")
|
os.replace(temp_file, RNS.Reticulum.storagepath+f"/known_destinations")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
RNS.log(f"Error while serializing and writing known destinations: {e}", RNS.LOG_ERROR)
|
RNS.log(f"Error while serializing and writing known destinations: {e}", RNS.LOG_ERROR)
|
||||||
@@ -230,7 +230,7 @@ class Identity:
|
|||||||
if save_time < 1: time_str = str(round(save_time*1000,2))+"ms"
|
if save_time < 1: time_str = str(round(save_time*1000,2))+"ms"
|
||||||
else: time_str = str(round(save_time,2))+"s"
|
else: time_str = str(round(save_time,2))+"s"
|
||||||
|
|
||||||
RNS.log("Saved known destinations to storage in "+time_str, RNS.LOG_VERBOSE)
|
RNS.log("Saved known destinations to storage in "+time_str, RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
RNS.log("Error while saving known destinations to disk, the contained exception was: "+str(e), RNS.LOG_ERROR)
|
RNS.log("Error while saving known destinations to disk, the contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||||
@@ -316,6 +316,7 @@ class Identity:
|
|||||||
no_path = 0
|
no_path = 0
|
||||||
retained = 0
|
retained = 0
|
||||||
never_used = 0
|
never_used = 0
|
||||||
|
ratchetdir = RNS.Reticulum.storagepath+"/ratchets"
|
||||||
|
|
||||||
with Identity.known_destinations_lock: destination_hashes = list(Identity.known_destinations.keys())
|
with Identity.known_destinations_lock: destination_hashes = list(Identity.known_destinations.keys())
|
||||||
for destination_hash in destination_hashes:
|
for destination_hash in destination_hashes:
|
||||||
@@ -359,6 +360,12 @@ class Identity:
|
|||||||
Identity.known_destinations.pop(destination_hash)
|
Identity.known_destinations.pop(destination_hash)
|
||||||
removed += 1
|
removed += 1
|
||||||
|
|
||||||
|
try:
|
||||||
|
hexhash = RNS.hexrep(destination_hash, delimit=False)
|
||||||
|
ratchet_path = f"{ratchetdir}/{hexhash}"
|
||||||
|
if os.path.isfile(ratchet_path): os.unlink(ratchet_path)
|
||||||
|
except Exception as e: RNS.log(f"Could not clean stale ratchets for {RNS.prettyhexrep(destination_hash)}: {e}", RNS.LOG_WARNING)
|
||||||
|
|
||||||
# RNS.log(f"Total destinations: {total}, stale: {len(stale)}, removed: {removed}, no path: {no_path}, never used: {never_used}, with path: {total-no_path}, used: {total-never_used}, retained: {retained}. Completed in {RNS.prettyshorttime(time.time()-st)}", RNS.LOG_WARNING) # TODO: Remove
|
# RNS.log(f"Total destinations: {total}, stale: {len(stale)}, removed: {removed}, no path: {no_path}, never used: {never_used}, with path: {total-no_path}, used: {total-never_used}, retained: {retained}. Completed in {RNS.prettyshorttime(time.time()-st)}", RNS.LOG_WARNING) # TODO: Remove
|
||||||
if not RNS.Transport.owner.is_connected_to_shared_instance: Identity.save_known_destinations(recombine=False)
|
if not RNS.Transport.owner.is_connected_to_shared_instance: Identity.save_known_destinations(recombine=False)
|
||||||
|
|
||||||
@@ -595,21 +602,18 @@ class Identity:
|
|||||||
signal_str = " ["
|
signal_str = " ["
|
||||||
if packet.rssi != None:
|
if packet.rssi != None:
|
||||||
signal_str += "RSSI "+str(packet.rssi)+"dBm"
|
signal_str += "RSSI "+str(packet.rssi)+"dBm"
|
||||||
if packet.snr != None:
|
if packet.snr != None: signal_str += ", "
|
||||||
signal_str += ", "
|
if packet.snr != None: signal_str += "SNR "+str(packet.snr)+"dB"
|
||||||
if packet.snr != None:
|
|
||||||
signal_str += "SNR "+str(packet.snr)+"dB"
|
|
||||||
signal_str += "]"
|
signal_str += "]"
|
||||||
else:
|
|
||||||
signal_str = ""
|
else: signal_str = ""
|
||||||
|
|
||||||
if hasattr(packet, "transport_id") and packet.transport_id != None:
|
if hasattr(packet, "transport_id") and packet.transport_id != None:
|
||||||
RNS.log("Valid announce for "+RNS.prettyhexrep(destination_hash)+" "+str(packet.hops)+" hops away, received via "+RNS.prettyhexrep(packet.transport_id)+" on "+str(packet.receiving_interface)+signal_str, RNS.LOG_EXTREME) if RNS.sl(RNS.LOG_EXTREME) else None
|
RNS.log("Valid announce for "+RNS.prettyhexrep(destination_hash)+" "+str(packet.hops)+" hops away, received via "+RNS.prettyhexrep(packet.transport_id)+" on "+str(packet.receiving_interface)+signal_str, RNS.LOG_EXTREME) if RNS.sl(RNS.LOG_EXTREME) else None
|
||||||
else:
|
else:
|
||||||
RNS.log("Valid announce for "+RNS.prettyhexrep(destination_hash)+" "+str(packet.hops)+" hops away, received on "+str(packet.receiving_interface)+signal_str, RNS.LOG_EXTREME) if RNS.sl(RNS.LOG_EXTREME) else None
|
RNS.log("Valid announce for "+RNS.prettyhexrep(destination_hash)+" "+str(packet.hops)+" hops away, received on "+str(packet.receiving_interface)+signal_str, RNS.LOG_EXTREME) if RNS.sl(RNS.LOG_EXTREME) else None
|
||||||
|
|
||||||
if ratchet:
|
if ratchet: Identity._remember_ratchet(destination_hash, ratchet)
|
||||||
Identity._remember_ratchet(destination_hash, ratchet)
|
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|||||||
@@ -428,14 +428,22 @@ class AutoInterface(Interface):
|
|||||||
if ifname in self.interface_servers:
|
if ifname in self.interface_servers:
|
||||||
RNS.log("Shutting down previous UDP listener for "+str(self)+" "+str(ifname), RNS.LOG_DEBUG)
|
RNS.log("Shutting down previous UDP listener for "+str(self)+" "+str(ifname), RNS.LOG_DEBUG)
|
||||||
previous_server = self.interface_servers[ifname]
|
previous_server = self.interface_servers[ifname]
|
||||||
def shutdown_server():
|
def shutdown_server(): previous_server.shutdown()
|
||||||
previous_server.shutdown()
|
|
||||||
threading.Thread(target=shutdown_server, daemon=True).start()
|
threading.Thread(target=shutdown_server, daemon=True).start()
|
||||||
|
|
||||||
RNS.log("Starting new UDP listener for "+str(self)+" "+str(ifname), RNS.LOG_DEBUG)
|
RNS.log("Starting new UDP listener for "+str(self)+" "+str(ifname), RNS.LOG_DEBUG)
|
||||||
|
|
||||||
udp_server = socketserver.UDPServer(listen_address, self.handler_factory(self.process_incoming))
|
retry_delay = 1.25
|
||||||
self.interface_servers[ifname] = udp_server
|
listener_started = False
|
||||||
|
while not listener_started:
|
||||||
|
try:
|
||||||
|
time.sleep(retry_delay)
|
||||||
|
udp_server = socketserver.UDPServer(listen_address, self.handler_factory(self.process_incoming))
|
||||||
|
self.interface_servers[ifname] = udp_server
|
||||||
|
listener_started = True
|
||||||
|
except Exception as e:
|
||||||
|
RNS.log(f"Could not start new UDP listener for {self} on {listen_address}: {e}", RNS.LOG_WARNING)
|
||||||
|
RNS.log(f"Retrying in {retry_delay} seconds", RNS.LOG_WARNING)
|
||||||
|
|
||||||
thread = threading.Thread(target=udp_server.serve_forever)
|
thread = threading.Thread(target=udp_server.serve_forever)
|
||||||
thread.daemon = True
|
thread.daemon = True
|
||||||
|
|||||||
@@ -54,6 +54,13 @@ class BackboneInterface(Interface):
|
|||||||
DEFAULT_IFAC_SIZE = 16
|
DEFAULT_IFAC_SIZE = 16
|
||||||
AUTOCONFIGURE_MTU = True
|
AUTOCONFIGURE_MTU = True
|
||||||
|
|
||||||
|
BLOCK_FAST_FLAPPING = True
|
||||||
|
FAST_FLAP_THRESHOLD = 20
|
||||||
|
FAST_FLAP_GRACE = 5
|
||||||
|
FAST_FLAP_EXPIRY = 12*60*60
|
||||||
|
fast_flapping_lock = threading.Lock()
|
||||||
|
fast_flapping = {}
|
||||||
|
|
||||||
epoll = None
|
epoll = None
|
||||||
listener_filenos = {}
|
listener_filenos = {}
|
||||||
spawned_interface_filenos = {}
|
spawned_interface_filenos = {}
|
||||||
@@ -109,13 +116,17 @@ class BackboneInterface(Interface):
|
|||||||
|
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
c = Interface.get_config_obj(configuration)
|
c = Interface.get_config_obj(configuration)
|
||||||
name = c["name"]
|
name = c["name"]
|
||||||
device = c["device"] if "device" in c else None
|
device = c["device"] if "device" in c else None
|
||||||
port = int(c["port"]) if "port" in c else None
|
port = int(c["port"]) if "port" in c else None
|
||||||
bindip = c["listen_ip"] if "listen_ip" in c else None
|
bindip = c["listen_ip"] if "listen_ip" in c else None
|
||||||
bindport = int(c["listen_port"]) if "listen_port" in c else None
|
bindport = int(c["listen_port"]) if "listen_port" in c else None
|
||||||
prefer_ipv6 = c.as_bool("prefer_ipv6") if "prefer_ipv6" in c else False
|
prefer_ipv6 = c.as_bool("prefer_ipv6") if "prefer_ipv6" in c else False
|
||||||
|
flap_block = c.as_bool("block_fast_flapping") if "block_fast_flapping" in c else BackboneInterface.BLOCK_FAST_FLAPPING
|
||||||
|
flap_threshold = c.as_float("fast_flapping_threshold") if "fast_flapping_threshold" in c else BackboneInterface.FAST_FLAP_THRESHOLD
|
||||||
|
flap_grace = c.as_int("fast_flapping_grace") if "fast_flapping_grace" in c else BackboneInterface.FAST_FLAP_GRACE
|
||||||
|
flap_expiry = c.as_float("fast_flapping_block_time")*60 if "fast_flapping_block_time" in c else BackboneInterface.FAST_FLAP_EXPIRY
|
||||||
|
|
||||||
if port != None: bindport = port
|
if port != None: bindport = port
|
||||||
|
|
||||||
@@ -128,11 +139,13 @@ class BackboneInterface(Interface):
|
|||||||
self.mode = RNS.Interfaces.Interface.Interface.MODE_FULL
|
self.mode = RNS.Interfaces.Interface.Interface.MODE_FULL
|
||||||
self.spawned_interfaces = []
|
self.spawned_interfaces = []
|
||||||
self.supports_discovery = True
|
self.supports_discovery = True
|
||||||
|
self.block_fast_flapping = flap_block
|
||||||
|
self.fast_flap_threshold = flap_threshold
|
||||||
|
self.fast_flap_grace = flap_grace
|
||||||
|
self.fast_flap_expiry = flap_expiry
|
||||||
|
|
||||||
if bindport == None:
|
if bindport == None: raise SystemError(f"No TCP port configured for interface \"{name}\"")
|
||||||
raise SystemError(f"No TCP port configured for interface \"{name}\"")
|
else: self.bind_port = bindport
|
||||||
else:
|
|
||||||
self.bind_port = bindport
|
|
||||||
|
|
||||||
bind_address = None
|
bind_address = None
|
||||||
if device != None:
|
if device != None:
|
||||||
@@ -282,7 +295,9 @@ class BackboneInterface(Interface):
|
|||||||
if fileno in BackboneInterface.spawned_interface_filenos:
|
if fileno in BackboneInterface.spawned_interface_filenos:
|
||||||
try: BackboneInterface.epoll.modify(fileno, select.EPOLLOUT)
|
try: BackboneInterface.epoll.modify(fileno, select.EPOLLOUT)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
RNS.log(f"Error occurred on {interface} while modifying socket EPOLL state: {e}", RNS.LOG_WARNING)
|
if str(e).endswith("No such file or directory"): pass
|
||||||
|
elif str(e).endswith("Bad file descriptor"): pass
|
||||||
|
else: RNS.log(f"Error occurred on {interface} while modifying socket EPOLL state: {e}", RNS.LOG_WARNING)
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -302,7 +317,7 @@ class BackboneInterface(Interface):
|
|||||||
if client_socket and fileno == client_socket.fileno() and (event & select.EPOLLIN):
|
if client_socket and fileno == client_socket.fileno() and (event & select.EPOLLIN):
|
||||||
try: received_bytes = client_socket.recv(spawned_interface.HW_MTU)
|
try: received_bytes = client_socket.recv(spawned_interface.HW_MTU)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
RNS.log(f"Error while reading from {spawned_interface}: {e}", RNS.LOG_DEBUG)
|
RNS.log(f"Error while reading from {spawned_interface}: {e}", RNS.LOG_PATHING) if RNS.sl(RNS.LOG_PATHING) else None
|
||||||
received_bytes = b""
|
received_bytes = b""
|
||||||
|
|
||||||
if len(received_bytes): spawned_interface.receive(received_bytes)
|
if len(received_bytes): spawned_interface.receive(received_bytes)
|
||||||
@@ -325,7 +340,11 @@ class BackboneInterface(Interface):
|
|||||||
try: written = client_socket.send(spawned_interface.transmit_buffer)
|
try: written = client_socket.send(spawned_interface.transmit_buffer)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
written = 0
|
written = 0
|
||||||
if not spawned_interface.detached: RNS.log(f"Error while writing to {spawned_interface}: {e}", RNS.LOG_DEBUG)
|
if not spawned_interface.detached:
|
||||||
|
if RNS.sl(RNS.LOG_DEBUG):
|
||||||
|
if str(e).endswith("Connection timed out"): pass
|
||||||
|
elif str(e).endswith("Connection reset by peer"): pass
|
||||||
|
else: RNS.log(f"Error while writing to {spawned_interface}: {e}", RNS.LOG_DEBUG)
|
||||||
BackboneInterface.deregister_fileno(fileno)
|
BackboneInterface.deregister_fileno(fileno)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -399,8 +418,22 @@ class BackboneInterface(Interface):
|
|||||||
BackboneInterface.deregister_listeners()
|
BackboneInterface.deregister_listeners()
|
||||||
|
|
||||||
def incoming_connection(self, socket):
|
def incoming_connection(self, socket):
|
||||||
RNS.log("Accepting incoming connection", RNS.LOG_VERBOSE)
|
|
||||||
try:
|
try:
|
||||||
|
remote_ip = socket.getpeername()[0]
|
||||||
|
remote_port = str(socket.getpeername()[1])
|
||||||
|
if self.blocked_ip_count > 0:
|
||||||
|
with BackboneInterface.fast_flapping_lock:
|
||||||
|
if remote_ip in BackboneInterface.fast_flapping:
|
||||||
|
now = time.time()
|
||||||
|
ffe = BackboneInterface.fast_flapping[remote_ip]
|
||||||
|
started_flapping = ffe[0]
|
||||||
|
last_flap = ffe[1]
|
||||||
|
flaps = ffe[2]
|
||||||
|
if flaps > self.fast_flap_grace:
|
||||||
|
RNS.log(f"Ignoring incoming connection from fast-flapping IP {remote_ip}", RNS.LOG_PATHING) if RNS.sl(RNS.LOG_PATHING) else None
|
||||||
|
return False
|
||||||
|
|
||||||
|
RNS.log("Accepting incoming connection", RNS.LOG_PATHING) if RNS.sl(RNS.LOG_PATHING) else None
|
||||||
spawned_configuration = {"name": "Client on "+self.name, "target_host": None, "target_port": None}
|
spawned_configuration = {"name": "Client on "+self.name, "target_host": None, "target_port": None}
|
||||||
spawned_interface = BackboneClientInterface(self.owner, spawned_configuration, connected_socket=socket)
|
spawned_interface = BackboneClientInterface(self.owner, spawned_configuration, connected_socket=socket)
|
||||||
spawned_interface.OUT = self.OUT
|
spawned_interface.OUT = self.OUT
|
||||||
@@ -421,8 +454,8 @@ class BackboneInterface(Interface):
|
|||||||
spawned_interface.ic_pr_burst_freq = self.ic_pr_burst_freq
|
spawned_interface.ic_pr_burst_freq = self.ic_pr_burst_freq
|
||||||
|
|
||||||
spawned_interface.socket = socket
|
spawned_interface.socket = socket
|
||||||
spawned_interface.target_ip = socket.getpeername()[0]
|
spawned_interface.target_ip = remote_ip
|
||||||
spawned_interface.target_port = str(socket.getpeername()[1])
|
spawned_interface.target_port = remote_port
|
||||||
spawned_interface.parent_interface = self
|
spawned_interface.parent_interface = self
|
||||||
spawned_interface.bitrate = self.bitrate
|
spawned_interface.bitrate = self.bitrate
|
||||||
spawned_interface.optimise_mtu()
|
spawned_interface.optimise_mtu()
|
||||||
@@ -432,18 +465,12 @@ class BackboneInterface(Interface):
|
|||||||
spawned_interface.ifac_netkey = self.ifac_netkey
|
spawned_interface.ifac_netkey = self.ifac_netkey
|
||||||
if spawned_interface.ifac_netname != None or spawned_interface.ifac_netkey != None:
|
if spawned_interface.ifac_netname != None or spawned_interface.ifac_netkey != None:
|
||||||
ifac_origin = b""
|
ifac_origin = b""
|
||||||
if spawned_interface.ifac_netname != None:
|
if spawned_interface.ifac_netname != None: ifac_origin += RNS.Identity.full_hash(spawned_interface.ifac_netname.encode("utf-8"))
|
||||||
ifac_origin += RNS.Identity.full_hash(spawned_interface.ifac_netname.encode("utf-8"))
|
if spawned_interface.ifac_netkey != None: ifac_origin += RNS.Identity.full_hash(spawned_interface.ifac_netkey.encode("utf-8"))
|
||||||
if spawned_interface.ifac_netkey != None:
|
|
||||||
ifac_origin += RNS.Identity.full_hash(spawned_interface.ifac_netkey.encode("utf-8"))
|
|
||||||
|
|
||||||
ifac_origin_hash = RNS.Identity.full_hash(ifac_origin)
|
ifac_origin_hash = RNS.Identity.full_hash(ifac_origin)
|
||||||
spawned_interface.ifac_key = RNS.Cryptography.hkdf(
|
spawned_interface.ifac_key = RNS.Cryptography.hkdf(length=64, derive_from=ifac_origin_hash,
|
||||||
length=64,
|
salt=RNS.Reticulum.IFAC_SALT, context=None)
|
||||||
derive_from=ifac_origin_hash,
|
|
||||||
salt=RNS.Reticulum.IFAC_SALT,
|
|
||||||
context=None
|
|
||||||
)
|
|
||||||
spawned_interface.ifac_identity = RNS.Identity.from_bytes(spawned_interface.ifac_key)
|
spawned_interface.ifac_identity = RNS.Identity.from_bytes(spawned_interface.ifac_key)
|
||||||
spawned_interface.ifac_signature = spawned_interface.ifac_identity.sign(RNS.Identity.full_hash(spawned_interface.ifac_key))
|
spawned_interface.ifac_signature = spawned_interface.ifac_identity.sign(RNS.Identity.full_hash(spawned_interface.ifac_key))
|
||||||
|
|
||||||
@@ -452,12 +479,13 @@ class BackboneInterface(Interface):
|
|||||||
spawned_interface.announce_rate_penalty = self.announce_rate_penalty
|
spawned_interface.announce_rate_penalty = self.announce_rate_penalty
|
||||||
spawned_interface.mode = self.mode
|
spawned_interface.mode = self.mode
|
||||||
spawned_interface.HW_MTU = self.HW_MTU
|
spawned_interface.HW_MTU = self.HW_MTU
|
||||||
spawned_interface.online = True
|
RNS.log("Spawned new BackboneClient Interface: "+str(spawned_interface), RNS.LOG_PATHING) if RNS.sl(RNS.LOG_PATHING) else None
|
||||||
RNS.log("Spawned new BackboneClient Interface: "+str(spawned_interface), RNS.LOG_VERBOSE)
|
|
||||||
RNS.Transport.add_interface(spawned_interface)
|
RNS.Transport.add_interface(spawned_interface)
|
||||||
while spawned_interface in self.spawned_interfaces: self.spawned_interfaces.remove(spawned_interface)
|
while spawned_interface in self.spawned_interfaces: self.spawned_interfaces.remove(spawned_interface)
|
||||||
self.spawned_interfaces.append(spawned_interface)
|
self.spawned_interfaces.append(spawned_interface)
|
||||||
BackboneInterface.add_client_socket(socket, spawned_interface)
|
BackboneInterface.add_client_socket(socket, spawned_interface)
|
||||||
|
spawned_interface.spawned_at = time.time()
|
||||||
|
spawned_interface.online = True
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
RNS.log(f"An error occurred while accepting incoming connection on {self}: {e}", RNS.LOG_ERROR)
|
RNS.log(f"An error occurred while accepting incoming connection on {self}: {e}", RNS.LOG_ERROR)
|
||||||
@@ -491,15 +519,38 @@ class BackboneInterface(Interface):
|
|||||||
if callable(listener_socket.shutdown):
|
if callable(listener_socket.shutdown):
|
||||||
try: listener_socket.shutdown(socket.SHUT_RDWR)
|
try: listener_socket.shutdown(socket.SHUT_RDWR)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if str(e).endswith("Transport endpoint is not connected"): pass
|
if str(e).endswith("Transport endpoint is not connected"): pass
|
||||||
|
elif str(e).endswith("Bad file descriptor"): pass
|
||||||
else: RNS.log("Error while shutting down socket for "+str(self)+": "+str(e), RNS.LOG_ERROR)
|
else: RNS.log("Error while shutting down socket for "+str(self)+": "+str(e), RNS.LOG_ERROR)
|
||||||
|
|
||||||
def __str__(self):
|
@property
|
||||||
if ":" in self.bind_ip:
|
def blocked_ip_count(self):
|
||||||
ip_str = f"[{self.bind_ip}]"
|
if not self.block_fast_flapping: return 0
|
||||||
else:
|
else:
|
||||||
ip_str = f"{self.bind_ip}"
|
count = 0
|
||||||
|
expired = []
|
||||||
|
now = time.time()
|
||||||
|
with BackboneInterface.fast_flapping_lock:
|
||||||
|
for remote_ip in BackboneInterface.fast_flapping:
|
||||||
|
ffe = BackboneInterface.fast_flapping[remote_ip]
|
||||||
|
started_flapping = ffe[0]
|
||||||
|
last_flap = ffe[1]
|
||||||
|
flaps = ffe[2]
|
||||||
|
if now - last_flap > self.fast_flap_expiry: expired.append(remote_ip)
|
||||||
|
elif flaps > self.fast_flap_grace: count += 1
|
||||||
|
|
||||||
|
for remote_ip in expired:
|
||||||
|
if remote_ip in BackboneInterface.fast_flapping:
|
||||||
|
try:
|
||||||
|
BackboneInterface.fast_flapping.pop(remote_ip)
|
||||||
|
RNS.log(f"Fast-flapping block expired for {remote_ip}", RNS.LOG_DEBUG)
|
||||||
|
except Exception as e: RNS.log(f"Error while expiring fast-flapping block for {remote_ip}: {e}", RNS.LOG_ERROR)
|
||||||
|
|
||||||
|
return count
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
if ":" in self.bind_ip: ip_str = f"[{self.bind_ip}]"
|
||||||
|
else: ip_str = f"{self.bind_ip}"
|
||||||
return "BackboneInterface["+self.name+"/"+ip_str+":"+str(self.bind_port)+"]"
|
return "BackboneInterface["+self.name+"/"+ip_str+":"+str(self.bind_port)+"]"
|
||||||
|
|
||||||
|
|
||||||
@@ -710,6 +761,14 @@ class BackboneClientInterface(Interface):
|
|||||||
RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR)
|
RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||||
self.teardown()
|
self.teardown()
|
||||||
|
|
||||||
|
def check_frame_len(self, frame_len):
|
||||||
|
if frame_len <= RNS.Reticulum.HEADER_MINSIZE: return False
|
||||||
|
elif frame_len > self.HW_MTU + (self.ifac_size or 0): return False
|
||||||
|
else: return True
|
||||||
|
|
||||||
|
def invalid_frame(self, frame_len):
|
||||||
|
RNS.log(f"Invalid HDLC frame of {RNS.prettysize(frame_len)} received on {self}, dropping frame", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
|
||||||
|
|
||||||
def receive(self, data_in):
|
def receive(self, data_in):
|
||||||
try:
|
try:
|
||||||
if len(data_in) > 0:
|
if len(data_in) > 0:
|
||||||
@@ -723,12 +782,18 @@ class BackboneClientInterface(Interface):
|
|||||||
frame = self.frame_buffer[frame_start+1:frame_end]
|
frame = self.frame_buffer[frame_start+1:frame_end]
|
||||||
frame = frame.replace(bytes([HDLC.ESC, HDLC.FLAG ^ HDLC.ESC_MASK]), bytes([HDLC.FLAG]))
|
frame = frame.replace(bytes([HDLC.ESC, HDLC.FLAG ^ HDLC.ESC_MASK]), bytes([HDLC.FLAG]))
|
||||||
frame = frame.replace(bytes([HDLC.ESC, HDLC.ESC ^ HDLC.ESC_MASK]), bytes([HDLC.ESC]))
|
frame = frame.replace(bytes([HDLC.ESC, HDLC.ESC ^ HDLC.ESC_MASK]), bytes([HDLC.ESC]))
|
||||||
if len(frame) > RNS.Reticulum.HEADER_MINSIZE:
|
frame_len = len(frame)
|
||||||
self.process_incoming(frame)
|
if frame_len != 0:
|
||||||
|
if self.check_frame_len(frame_len): self.process_incoming(frame)
|
||||||
|
else: self.invalid_frame(len(frame))
|
||||||
|
|
||||||
self.frame_buffer = self.frame_buffer[frame_end:]
|
self.frame_buffer = self.frame_buffer[frame_end:]
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
if len(self.frame_buffer) > self.HW_MTU*2: self.frame_buffer = b""
|
||||||
flags_remaining = False
|
flags_remaining = False
|
||||||
else:
|
else:
|
||||||
|
self.frame_buffer = b""
|
||||||
flags_remaining = False
|
flags_remaining = False
|
||||||
|
|
||||||
else:
|
else:
|
||||||
@@ -738,7 +803,7 @@ class BackboneClientInterface(Interface):
|
|||||||
def job(): self.reconnect()
|
def job(): self.reconnect()
|
||||||
threading.Thread(target=job, daemon=True).start()
|
threading.Thread(target=job, daemon=True).start()
|
||||||
else:
|
else:
|
||||||
RNS.log("The socket for remote client "+str(self)+" was closed.", RNS.LOG_DEBUG)
|
RNS.log("The socket for remote client "+str(self)+" was closed.", RNS.LOG_PATHING) if RNS.sl(RNS.LOG_PATHING) else None
|
||||||
self.teardown()
|
self.teardown()
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -755,11 +820,29 @@ class BackboneClientInterface(Interface):
|
|||||||
def teardown(self):
|
def teardown(self):
|
||||||
if self.initiator and not self.detached:
|
if self.initiator and not self.detached:
|
||||||
RNS.log("The interface "+str(self)+" experienced an unrecoverable error and is being torn down. Restart Reticulum to attempt to open this interface again.", RNS.LOG_ERROR)
|
RNS.log("The interface "+str(self)+" experienced an unrecoverable error and is being torn down. Restart Reticulum to attempt to open this interface again.", RNS.LOG_ERROR)
|
||||||
if RNS.Reticulum.panic_on_interface_error:
|
if RNS.Reticulum.panic_on_interface_error: RNS.panic()
|
||||||
RNS.panic()
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
RNS.log("The interface "+str(self)+" is being torn down.", RNS.LOG_VERBOSE)
|
RNS.log("The interface "+str(self)+" is being torn down.", RNS.LOG_PATHING) if RNS.sl(RNS.LOG_PATHING) else None
|
||||||
|
if self.parent_interface.block_fast_flapping and hasattr(self, "spawned_at"):
|
||||||
|
connected_time = time.time() - self.spawned_at
|
||||||
|
if connected_time < self.parent_interface.fast_flap_threshold:
|
||||||
|
try:
|
||||||
|
now = time.time()
|
||||||
|
remote_id = f"{self.target_ip}"
|
||||||
|
with BackboneInterface.fast_flapping_lock:
|
||||||
|
if remote_id in BackboneInterface.fast_flapping: ffe = BackboneInterface.fast_flapping[remote_id]
|
||||||
|
else: ffe = [now, now, 0]
|
||||||
|
ffe[1] = now
|
||||||
|
ffe[2] += 1
|
||||||
|
with BackboneInterface.fast_flapping_lock: BackboneInterface.fast_flapping[remote_id] = ffe
|
||||||
|
|
||||||
|
dt = now-ffe[0]; fff = ffe[2]/dt if dt > 0 else None
|
||||||
|
freq_str = f" at {RNS.prettyfrequency(fff)}" if fff else ""
|
||||||
|
RNS.log(f"{self} is fast flapping{freq_str}, connection time was {RNS.prettytime(connected_time)}, {ffe[2]} fast flaps", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
|
||||||
|
if ffe[2] > self.parent_interface.fast_flap_grace: RNS.log(f"Ignoring further connections from {remote_id} due to fast-flapping", RNS.LOG_WARNING)
|
||||||
|
|
||||||
|
except Exception as e: RNS.log(f"Error while updating fast-flapping interface statistics: {e}", RNS.LOG_ERROR)
|
||||||
|
|
||||||
self.online = False
|
self.online = False
|
||||||
self.OUT = False
|
self.OUT = False
|
||||||
|
|||||||
+19
-25
@@ -48,10 +48,11 @@ class Interface:
|
|||||||
MODE_ROAMING = 0x04
|
MODE_ROAMING = 0x04
|
||||||
MODE_BOUNDARY = 0x05
|
MODE_BOUNDARY = 0x05
|
||||||
MODE_GATEWAY = 0x06
|
MODE_GATEWAY = 0x06
|
||||||
|
MODE_INTERNAL = 0x07
|
||||||
|
|
||||||
# Which interface modes a Transport Node should
|
# Which interface modes a Transport Node should
|
||||||
# actively discover paths for.
|
# actively discover paths for.
|
||||||
DISCOVER_PATHS_FOR = [MODE_ACCESS_POINT, MODE_GATEWAY, MODE_ROAMING]
|
DISCOVER_PATHS_FOR = [MODE_ACCESS_POINT, MODE_GATEWAY, MODE_ROAMING, MODE_INTERNAL]
|
||||||
|
|
||||||
# How many samples to use for announce
|
# How many samples to use for announce
|
||||||
# frequency calculations
|
# frequency calculations
|
||||||
@@ -106,6 +107,8 @@ class Interface:
|
|||||||
self.discoverable = False
|
self.discoverable = False
|
||||||
self.last_discovery_announce = 0
|
self.last_discovery_announce = 0
|
||||||
self.bootstrap_only = False
|
self.bootstrap_only = False
|
||||||
|
self.recursive_prs = False
|
||||||
|
self.announces_from_internal = True
|
||||||
self.parent_interface = None
|
self.parent_interface = None
|
||||||
self.spawned_interfaces = None
|
self.spawned_interfaces = None
|
||||||
self.tunnel_id = None
|
self.tunnel_id = None
|
||||||
@@ -197,36 +200,27 @@ class Interface:
|
|||||||
|
|
||||||
def optimise_mtu(self):
|
def optimise_mtu(self):
|
||||||
if self.AUTOCONFIGURE_MTU:
|
if self.AUTOCONFIGURE_MTU:
|
||||||
if self.bitrate >= 1_000_000_000:
|
if self.bitrate >= 1_000_000_000: self.HW_MTU = 524288
|
||||||
self.HW_MTU = 524288
|
elif self.bitrate > 750_000_000: self.HW_MTU = 262144
|
||||||
elif self.bitrate > 750_000_000:
|
elif self.bitrate > 400_000_000: self.HW_MTU = 131072
|
||||||
self.HW_MTU = 262144
|
elif self.bitrate > 200_000_000: self.HW_MTU = 65536
|
||||||
elif self.bitrate > 400_000_000:
|
elif self.bitrate > 100_000_000: self.HW_MTU = 32768
|
||||||
self.HW_MTU = 131072
|
elif self.bitrate > 10_000_000: self.HW_MTU = 16384
|
||||||
elif self.bitrate > 200_000_000:
|
elif self.bitrate > 5_000_000: self.HW_MTU = 8192
|
||||||
self.HW_MTU = 65536
|
elif self.bitrate > 2_000_000: self.HW_MTU = 4096
|
||||||
elif self.bitrate > 100_000_000:
|
elif self.bitrate > 1_000_000: self.HW_MTU = 2048
|
||||||
self.HW_MTU = 32768
|
elif self.bitrate > 62_500: self.HW_MTU = 1024
|
||||||
elif self.bitrate > 10_000_000:
|
else: self.HW_MTU = None
|
||||||
self.HW_MTU = 16384
|
|
||||||
elif self.bitrate > 5_000_000:
|
|
||||||
self.HW_MTU = 8192
|
|
||||||
elif self.bitrate > 2_000_000:
|
|
||||||
self.HW_MTU = 4096
|
|
||||||
elif self.bitrate > 1_000_000:
|
|
||||||
self.HW_MTU = 2048
|
|
||||||
elif self.bitrate > 62_500:
|
|
||||||
self.HW_MTU = 1024
|
|
||||||
else:
|
|
||||||
self.HW_MTU = None
|
|
||||||
|
|
||||||
RNS.log(f"{self} hardware MTU set to {self.HW_MTU}", RNS.LOG_DEBUG)
|
RNS.log(f"{self} hardware MTU set to {self.HW_MTU}", RNS.LOG_PATHING)
|
||||||
|
|
||||||
def age(self):
|
def age(self):
|
||||||
return time.time()-self.created
|
return time.time()-self.created
|
||||||
|
|
||||||
def hold_announce(self, announce_packet):
|
def hold_announce(self, announce_packet):
|
||||||
if announce_packet.destination_hash in self.held_announces:
|
if announce_packet.hops >= RNS.Transport.PATHFINDER_M-1:
|
||||||
|
return
|
||||||
|
elif announce_packet.destination_hash in self.held_announces:
|
||||||
self.held_announces[announce_packet.destination_hash] = announce_packet
|
self.held_announces[announce_packet.destination_hash] = announce_packet
|
||||||
elif not len(self.held_announces) >= self.ic_max_held_announces:
|
elif not len(self.held_announces) >= self.ic_max_held_announces:
|
||||||
self.held_announces[announce_packet.destination_hash] = announce_packet
|
self.held_announces[announce_packet.destination_hash] = announce_packet
|
||||||
|
|||||||
@@ -333,6 +333,13 @@ class TCPClientInterface(Interface):
|
|||||||
RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR)
|
RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||||
self.teardown()
|
self.teardown()
|
||||||
|
|
||||||
|
def check_frame_len(self, frame_len):
|
||||||
|
if frame_len <= RNS.Reticulum.HEADER_MINSIZE: return False
|
||||||
|
elif frame_len > self.HW_MTU + (self.ifac_size or 0): return False
|
||||||
|
else: return True
|
||||||
|
|
||||||
|
def invalid_frame(self, frame_len):
|
||||||
|
RNS.log(f"Invalid HDLC frame of {RNS.prettysize(frame_len)} received on {self}, dropping frame", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
|
||||||
|
|
||||||
def read_loop(self):
|
def read_loop(self):
|
||||||
try:
|
try:
|
||||||
@@ -389,12 +396,18 @@ class TCPClientInterface(Interface):
|
|||||||
frame = frame_buffer[frame_start+1:frame_end]
|
frame = frame_buffer[frame_start+1:frame_end]
|
||||||
frame = frame.replace(bytes([HDLC.ESC, HDLC.FLAG ^ HDLC.ESC_MASK]), bytes([HDLC.FLAG]))
|
frame = frame.replace(bytes([HDLC.ESC, HDLC.FLAG ^ HDLC.ESC_MASK]), bytes([HDLC.FLAG]))
|
||||||
frame = frame.replace(bytes([HDLC.ESC, HDLC.ESC ^ HDLC.ESC_MASK]), bytes([HDLC.ESC]))
|
frame = frame.replace(bytes([HDLC.ESC, HDLC.ESC ^ HDLC.ESC_MASK]), bytes([HDLC.ESC]))
|
||||||
if len(frame) > RNS.Reticulum.HEADER_MINSIZE:
|
frame_len = len(frame)
|
||||||
self.process_incoming(frame)
|
if frame_len != 0:
|
||||||
|
if self.check_frame_len(frame_len): self.process_incoming(frame)
|
||||||
|
else: self.invalid_frame(len(frame))
|
||||||
|
|
||||||
frame_buffer = frame_buffer[frame_end:]
|
frame_buffer = frame_buffer[frame_end:]
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
if len(frame_buffer) > self.HW_MTU*2: frame_buffer = b""
|
||||||
flags_remaining = False
|
flags_remaining = False
|
||||||
else:
|
else:
|
||||||
|
frame_buffer = b""
|
||||||
flags_remaining = False
|
flags_remaining = False
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
|||||||
+110
-128
@@ -109,30 +109,30 @@ class WDCL():
|
|||||||
self.switch_id = self.switch_identity.sig_pub_bytes[-4:]
|
self.switch_id = self.switch_identity.sig_pub_bytes[-4:]
|
||||||
self.switch_pub_bytes = self.switch_identity.sig_pub_bytes
|
self.switch_pub_bytes = self.switch_identity.sig_pub_bytes
|
||||||
|
|
||||||
self.rxb = 0
|
self.rxb = 0
|
||||||
self.txb = 0
|
self.txb = 0
|
||||||
self.owner = owner
|
self.owner = owner
|
||||||
self.as_interface = as_interface
|
self.as_interface = as_interface
|
||||||
self.device = device
|
self.device = device
|
||||||
self.device.connection = self
|
self.device.connection = self
|
||||||
self.pyserial = serial
|
self.pyserial = serial
|
||||||
self.serial = None
|
self.serial = None
|
||||||
self.port = port
|
self.port = port
|
||||||
self.speed = 3000000
|
self.speed = 3000000
|
||||||
self.databits = 8
|
self.databits = 8
|
||||||
self.parity = parity
|
self.parity = parity
|
||||||
self.stopbits = 1
|
self.stopbits = 1
|
||||||
self.timeout = 100
|
self.timeout = 100
|
||||||
self.online = False
|
self.online = False
|
||||||
self.frame_buffer = b""
|
self.frame_buffer = b""
|
||||||
self.next_tx = 0
|
self.next_tx = 0
|
||||||
self.should_run = True
|
self.should_run = True
|
||||||
self.receiver = None
|
self.receiver = None
|
||||||
self.wdcl_connected = False
|
self.wdcl_connected = False
|
||||||
self.reconnecting = False
|
self.reconnecting = False
|
||||||
self.frame_queue = deque()
|
self.frame_queue = deque()
|
||||||
if not self.as_interface:
|
|
||||||
self.id = RNS.Identity.full_hash(port.hwid.encode("utf-8"))
|
if not self.as_interface: self.id = RNS.Identity.full_hash(port.hwid.encode("utf-8"))
|
||||||
|
|
||||||
if self.as_interface:
|
if self.as_interface:
|
||||||
try:
|
try:
|
||||||
@@ -168,19 +168,10 @@ class WDCL():
|
|||||||
self.owner.wlog(f"Opening serial port {self.port.device}...")
|
self.owner.wlog(f"Opening serial port {self.port.device}...")
|
||||||
target_port = self.port.device
|
target_port = self.port.device
|
||||||
|
|
||||||
self.serial = self.pyserial.Serial(
|
self.serial = self.pyserial.Serial(port = target_port, baudrate = self.speed,
|
||||||
port = target_port,
|
bytesize = self.databits, parity = self.parity, stopbits = self.stopbits,
|
||||||
baudrate = self.speed,
|
xonxoff = False, rtscts = False, timeout = 0.250, inter_byte_timeout = None,
|
||||||
bytesize = self.databits,
|
write_timeout = None, dsrdtr = False)
|
||||||
parity = self.parity,
|
|
||||||
stopbits = self.stopbits,
|
|
||||||
xonxoff = False,
|
|
||||||
rtscts = False,
|
|
||||||
timeout = 0.250,
|
|
||||||
inter_byte_timeout = None,
|
|
||||||
write_timeout = None,
|
|
||||||
dsrdtr = False)
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
if self.port != None:
|
if self.port != None:
|
||||||
# Get device parameters
|
# Get device parameters
|
||||||
@@ -198,19 +189,9 @@ class WDCL():
|
|||||||
from usbserial4a.cdcacmserial4a import CdcAcmSerial
|
from usbserial4a.cdcacmserial4a import CdcAcmSerial
|
||||||
proxy = CdcAcmSerial
|
proxy = CdcAcmSerial
|
||||||
|
|
||||||
self.serial = proxy(
|
self.serial = proxy(self.port, baudrate = self.speed, bytesize = self.databits,
|
||||||
self.port,
|
parity = self.parity, stopbits = self.stopbits, xonxoff = False,
|
||||||
baudrate = self.speed,
|
rtscts = False, timeout = None, inter_byte_timeout = None, dsrdtr = False)
|
||||||
bytesize = self.databits,
|
|
||||||
parity = self.parity,
|
|
||||||
stopbits = self.stopbits,
|
|
||||||
xonxoff = False,
|
|
||||||
rtscts = False,
|
|
||||||
timeout = None,
|
|
||||||
inter_byte_timeout = None,
|
|
||||||
# write_timeout = wtimeout,
|
|
||||||
dsrdtr = False,
|
|
||||||
)
|
|
||||||
|
|
||||||
if vid == 0x0403:
|
if vid == 0x0403:
|
||||||
# Hardware parameters for FTDI devices @ 115200 baud
|
# Hardware parameters for FTDI devices @ 115200 baud
|
||||||
@@ -299,10 +280,9 @@ class WDCL():
|
|||||||
frame = frame.replace(bytes([HDLC.ESC, HDLC.ESC ^ HDLC.ESC_MASK]), bytes([HDLC.ESC]))
|
frame = frame.replace(bytes([HDLC.ESC, HDLC.ESC ^ HDLC.ESC_MASK]), bytes([HDLC.ESC]))
|
||||||
if len(frame) > WDCL.HEADER_MINSIZE: self.process_incoming(frame)
|
if len(frame) > WDCL.HEADER_MINSIZE: self.process_incoming(frame)
|
||||||
self.frame_buffer = self.frame_buffer[frame_end:]
|
self.frame_buffer = self.frame_buffer[frame_end:]
|
||||||
else:
|
|
||||||
flags_remaining = False
|
else: flags_remaining = False
|
||||||
else:
|
else: flags_remaining = False
|
||||||
flags_remaining = False
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.online = False
|
self.online = False
|
||||||
@@ -359,6 +339,7 @@ class Evt():
|
|||||||
ET_MSG = 0x0000
|
ET_MSG = 0x0000
|
||||||
ET_SYSTEM_BOOT = 0x0001
|
ET_SYSTEM_BOOT = 0x0001
|
||||||
ET_CORE_INIT = 0x0002
|
ET_CORE_INIT = 0x0002
|
||||||
|
ET_BOARD_INIT = 0x0003
|
||||||
ET_DRV_UART_INIT = 0x1000
|
ET_DRV_UART_INIT = 0x1000
|
||||||
ET_DRV_USB_CDC_INIT = 0x1010
|
ET_DRV_USB_CDC_INIT = 0x1010
|
||||||
ET_DRV_USB_CDC_HOST_AVAIL = 0x1011
|
ET_DRV_USB_CDC_HOST_AVAIL = 0x1011
|
||||||
@@ -426,77 +407,78 @@ class Evt():
|
|||||||
IF_TYPE_DMA = 0x10
|
IF_TYPE_DMA = 0x10
|
||||||
|
|
||||||
event_descriptions = {
|
event_descriptions = {
|
||||||
ET_SYSTEM_BOOT: "System boot",
|
ET_SYSTEM_BOOT: "System boot",
|
||||||
ET_CORE_INIT: "Core initialization",
|
ET_CORE_INIT: "Core initialization",
|
||||||
ET_DRV_UART_INIT: "UART driver initialization",
|
ET_BOARD_INIT: "Board hardware initialization",
|
||||||
ET_DRV_USB_CDC_INIT: "USB CDC driver initialization",
|
ET_DRV_UART_INIT: "UART driver initialization",
|
||||||
ET_DRV_USB_CDC_HOST_AVAIL: "USB CDC host became available",
|
ET_DRV_USB_CDC_INIT: "USB CDC driver initialization",
|
||||||
ET_DRV_USB_CDC_HOST_SUSPEND: "USB CDC host suspend",
|
ET_DRV_USB_CDC_HOST_AVAIL: "USB CDC host became available",
|
||||||
ET_DRV_USB_CDC_HOST_RESUME: "USB CDC host resume",
|
ET_DRV_USB_CDC_HOST_SUSPEND: "USB CDC host suspend",
|
||||||
ET_DRV_USB_CDC_CONNECTED: "USB CDC host connection",
|
ET_DRV_USB_CDC_HOST_RESUME: "USB CDC host resume",
|
||||||
ET_DRV_USB_CDC_READ_ERR: "USB CDC read error",
|
ET_DRV_USB_CDC_CONNECTED: "USB CDC host connection",
|
||||||
ET_DRV_USB_CDC_OVERFLOW: "USB CDC overflow occurred",
|
ET_DRV_USB_CDC_READ_ERR: "USB CDC read error",
|
||||||
ET_DRV_USB_CDC_DROPPED: "USB CDC dropped bytes",
|
ET_DRV_USB_CDC_OVERFLOW: "USB CDC overflow occurred",
|
||||||
ET_DRV_USB_CDC_TX_TIMEOUT: "USB CDC TX flush timeout",
|
ET_DRV_USB_CDC_DROPPED: "USB CDC dropped bytes",
|
||||||
ET_DRV_I2C_INIT: "I2C driver initialization",
|
ET_DRV_USB_CDC_TX_TIMEOUT: "USB CDC TX flush timeout",
|
||||||
ET_DRV_NVS_INIT: "NVS driver initialization",
|
ET_DRV_I2C_INIT: "I2C driver initialization",
|
||||||
ET_DRV_CRYPTO_INIT: "Cryptography driver initialization",
|
ET_DRV_NVS_INIT: "NVS driver initialization",
|
||||||
ET_DRV_W80211_INIT: "W802.11 driver initialization",
|
ET_DRV_CRYPTO_INIT: "Cryptography driver initialization",
|
||||||
ET_DRV_W80211_CHANNEL: "W802.11 channel configuration",
|
ET_DRV_W80211_INIT: "W802.11 driver initialization",
|
||||||
ET_DRV_W80211_POWER: "W802.11 TX power configuration",
|
ET_DRV_W80211_CHANNEL: "W802.11 channel configuration",
|
||||||
ET_DRV_DISPLAY_INIT: "Display driver initialization",
|
ET_DRV_W80211_POWER: "W802.11 TX power configuration",
|
||||||
|
ET_DRV_DISPLAY_INIT: "Display driver initialization",
|
||||||
ET_DRV_DISPLAY_BUS_AVAILABLE: "Display bus availability",
|
ET_DRV_DISPLAY_BUS_AVAILABLE: "Display bus availability",
|
||||||
ET_DRV_DISPLAY_IO_CONFIGURED: "Display I/O configuration",
|
ET_DRV_DISPLAY_IO_CONFIGURED: "Display I/O configuration",
|
||||||
ET_DRV_DISPLAY_PANEL_CREATED: "Display panel allocation",
|
ET_DRV_DISPLAY_PANEL_CREATED: "Display panel allocation",
|
||||||
ET_DRV_DISPLAY_PANEL_RESET: "Display panel reset",
|
ET_DRV_DISPLAY_PANEL_RESET: "Display panel reset",
|
||||||
ET_DRV_DISPLAY_PANEL_INIT: "Display panel initialization",
|
ET_DRV_DISPLAY_PANEL_INIT: "Display panel initialization",
|
||||||
ET_DRV_DISPLAY_PANEL_ENABLE: "Display panel activation",
|
ET_DRV_DISPLAY_PANEL_ENABLE: "Display panel activation",
|
||||||
ET_DRV_DISPLAY_REMOTE_ENABLE: "Remote display output activation",
|
ET_DRV_DISPLAY_REMOTE_ENABLE: "Remote display output activation",
|
||||||
ET_KRN_LOGGER_INIT: "Logging service initialization",
|
ET_KRN_LOGGER_INIT: "Logging service initialization",
|
||||||
ET_KRN_LOGGER_OUTPUT: "Logging service output activation",
|
ET_KRN_LOGGER_OUTPUT: "Logging service output activation",
|
||||||
ET_KRN_UI_INIT: "User interface service initialization",
|
ET_KRN_UI_INIT: "User interface service initialization",
|
||||||
ET_PROTO_WDCL_INIT: "WDCL protocol initialization",
|
ET_PROTO_WDCL_INIT: "WDCL protocol initialization",
|
||||||
ET_PROTO_WDCL_RUNNING: "WDCL protocol activation",
|
ET_PROTO_WDCL_RUNNING: "WDCL protocol activation",
|
||||||
ET_PROTO_WDCL_CONNECTION: "WDCL host connection",
|
ET_PROTO_WDCL_CONNECTION: "WDCL host connection",
|
||||||
ET_PROTO_WDCL_HOST_ENDPOINT: "Weave host endpoint",
|
ET_PROTO_WDCL_HOST_ENDPOINT: "Weave host endpoint",
|
||||||
ET_PROTO_WEAVE_INIT: "Weave protocol initialization",
|
ET_PROTO_WEAVE_INIT: "Weave protocol initialization",
|
||||||
ET_PROTO_WEAVE_RUNNING: "Weave protocol activation",
|
ET_PROTO_WEAVE_RUNNING: "Weave protocol activation",
|
||||||
ET_PROTO_WEAVE_EP_ALIVE: "Weave endpoint alive",
|
ET_PROTO_WEAVE_EP_ALIVE: "Weave endpoint alive",
|
||||||
ET_PROTO_WEAVE_EP_TIMEOUT: "Weave endpoint disappeared",
|
ET_PROTO_WEAVE_EP_TIMEOUT: "Weave endpoint disappeared",
|
||||||
ET_SRVCTL_REMOTE_DISPLAY: "Remote display service control event",
|
ET_SRVCTL_REMOTE_DISPLAY: "Remote display service control event",
|
||||||
ET_INTERFACE_REGISTERED: "Interface registration",
|
ET_INTERFACE_REGISTERED: "Interface registration",
|
||||||
ET_SYSERR_MEM_EXHAUSTED: "System memory exhausted",
|
ET_SYSERR_MEM_EXHAUSTED: "System memory exhausted",
|
||||||
}
|
}
|
||||||
|
|
||||||
interface_types = {
|
interface_types = {
|
||||||
IF_TYPE_USB: "usb",
|
IF_TYPE_USB: "usb",
|
||||||
IF_TYPE_UART: "uart",
|
IF_TYPE_UART: "uart",
|
||||||
IF_TYPE_W80211: "mw",
|
IF_TYPE_W80211: "mw",
|
||||||
IF_TYPE_BLE: "ble",
|
IF_TYPE_BLE: "ble",
|
||||||
IF_TYPE_LORA: "lora",
|
IF_TYPE_LORA: "lora",
|
||||||
IF_TYPE_ETHERNET: "eth",
|
IF_TYPE_ETHERNET: "eth",
|
||||||
IF_TYPE_WIFI: "wifi",
|
IF_TYPE_WIFI: "wifi",
|
||||||
IF_TYPE_TCP: "tcp",
|
IF_TYPE_TCP: "tcp",
|
||||||
IF_TYPE_UDP: "udp",
|
IF_TYPE_UDP: "udp",
|
||||||
IF_TYPE_IR: "ir",
|
IF_TYPE_IR: "ir",
|
||||||
IF_TYPE_AFSK: "afsk",
|
IF_TYPE_AFSK: "afsk",
|
||||||
IF_TYPE_GPIO: "gpio",
|
IF_TYPE_GPIO: "gpio",
|
||||||
IF_TYPE_SPI: "spi",
|
IF_TYPE_SPI: "spi",
|
||||||
IF_TYPE_I2C: "i2c",
|
IF_TYPE_I2C: "i2c",
|
||||||
IF_TYPE_CAN: "can",
|
IF_TYPE_CAN: "can",
|
||||||
IF_TYPE_DMA: "dma",
|
IF_TYPE_DMA: "dma",
|
||||||
}
|
}
|
||||||
|
|
||||||
channel_descriptions = {
|
channel_descriptions = {
|
||||||
1: "Channel 1 (2412 MHz)",
|
1: "Channel 1 (2412 MHz)",
|
||||||
2: "Channel 2 (2417 MHz)",
|
2: "Channel 2 (2417 MHz)",
|
||||||
3: "Channel 3 (2422 MHz)",
|
3: "Channel 3 (2422 MHz)",
|
||||||
4: "Channel 4 (2427 MHz)",
|
4: "Channel 4 (2427 MHz)",
|
||||||
5: "Channel 5 (2432 MHz)",
|
5: "Channel 5 (2432 MHz)",
|
||||||
6: "Channel 6 (2437 MHz)",
|
6: "Channel 6 (2437 MHz)",
|
||||||
7: "Channel 7 (2442 MHz)",
|
7: "Channel 7 (2442 MHz)",
|
||||||
8: "Channel 8 (2447 MHz)",
|
8: "Channel 8 (2447 MHz)",
|
||||||
9: "Channel 9 (2452 MHz)",
|
9: "Channel 9 (2452 MHz)",
|
||||||
10: "Channel 10 (2457 MHz)",
|
10: "Channel 10 (2457 MHz)",
|
||||||
11: "Channel 11 (2462 MHz)",
|
11: "Channel 11 (2462 MHz)",
|
||||||
12: "Channel 12 (2467 MHz)",
|
12: "Channel 12 (2467 MHz)",
|
||||||
@@ -529,23 +511,23 @@ class Evt():
|
|||||||
}
|
}
|
||||||
|
|
||||||
task_descriptions = {
|
task_descriptions = {
|
||||||
"taskLVGL": "Driver: UI Renderer",
|
"taskLVGL": "Driver: UI Renderer",
|
||||||
"ui_service": "Service: User Interface",
|
"ui_service": "Service: User Interface",
|
||||||
"TinyUSB": "Driver: USB",
|
"TinyUSB": "Driver: USB",
|
||||||
"drv_w80211": "Driver: W802.11",
|
"drv_w80211": "Driver: W802.11",
|
||||||
"system_stats": "System: Stats",
|
"system_stats": "System: Stats",
|
||||||
"core": "System: Core",
|
"core": "System: Core",
|
||||||
"protocol_wdcl": "Protocol: WDCL",
|
"protocol_wdcl": "Protocol: WDCL",
|
||||||
"protocol_weave": "Protocol: Weave",
|
"protocol_weave": "Protocol: Weave",
|
||||||
"tiT": "Protocol: TCP/IP",
|
"tiT": "Protocol: TCP/IP",
|
||||||
"ipc0": "System: CPU 0 IPC",
|
"ipc0": "System: CPU 0 IPC",
|
||||||
"ipc1": "System: CPU 1 IPC",
|
"ipc1": "System: CPU 1 IPC",
|
||||||
"esp_timer": "Driver: Timers",
|
"esp_timer": "Driver: Timers",
|
||||||
"Tmr Svc": "Service: Timers",
|
"Tmr Svc": "Service: Timers",
|
||||||
"kernel_logger": "Service: Logging",
|
"kernel_logger": "Service: Logging",
|
||||||
"remote_display": "Service: Remote Display",
|
"remote_display": "Service: Remote Display",
|
||||||
"wifi": "System: WiFi Hardware",
|
"wifi": "System: WiFi Hardware",
|
||||||
"sys_evt": "System: Kernel Events",
|
"sys_evt": "System: Kernel Events",
|
||||||
}
|
}
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|||||||
+112
-186
@@ -191,8 +191,7 @@ class Link:
|
|||||||
|
|
||||||
if len(data) == Link.ECPUBSIZE+Link.LINK_MTU_SIZE:
|
if len(data) == Link.ECPUBSIZE+Link.LINK_MTU_SIZE:
|
||||||
RNS.log("Link request includes MTU signalling", RNS.LOG_DEBUG) # TODO: Remove debug
|
RNS.log("Link request includes MTU signalling", RNS.LOG_DEBUG) # TODO: Remove debug
|
||||||
try:
|
try: link.mtu = Link.mtu_from_lr_packet(packet) or Reticulum.MTU
|
||||||
link.mtu = Link.mtu_from_lr_packet(packet) or Reticulum.MTU
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
RNS.trace_exception(e)
|
RNS.trace_exception(e)
|
||||||
link.mtu = RNS.Reticulum.MTU
|
link.mtu = RNS.Reticulum.MTU
|
||||||
@@ -277,6 +276,7 @@ class Link:
|
|||||||
self.initiator = False
|
self.initiator = False
|
||||||
self.prv = X25519PrivateKey.generate()
|
self.prv = X25519PrivateKey.generate()
|
||||||
self.sig_prv = self.owner.identity.sig_prv
|
self.sig_prv = self.owner.identity.sig_prv
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self.initiator = True
|
self.initiator = True
|
||||||
self.expected_hops = RNS.Transport.hops_to(self.destination.hash)
|
self.expected_hops = RNS.Transport.hops_to(self.destination.hash)
|
||||||
@@ -296,14 +296,11 @@ class Link:
|
|||||||
if peer_pub_bytes == None:
|
if peer_pub_bytes == None:
|
||||||
self.peer_pub = None
|
self.peer_pub = None
|
||||||
self.peer_pub_bytes = None
|
self.peer_pub_bytes = None
|
||||||
else:
|
|
||||||
self.load_peer(peer_pub_bytes, peer_sig_pub_bytes)
|
|
||||||
|
|
||||||
if established_callback != None:
|
else: self.load_peer(peer_pub_bytes, peer_sig_pub_bytes)
|
||||||
self.set_link_established_callback(established_callback)
|
|
||||||
|
|
||||||
if closed_callback != None:
|
if established_callback != None: self.set_link_established_callback(established_callback)
|
||||||
self.set_link_closed_callback(closed_callback)
|
if closed_callback != None: self.set_link_closed_callback(closed_callback)
|
||||||
|
|
||||||
if self.initiator:
|
if self.initiator:
|
||||||
signalling_bytes = b""
|
signalling_bytes = b""
|
||||||
@@ -334,8 +331,7 @@ class Link:
|
|||||||
self.peer_sig_pub_bytes = peer_sig_pub_bytes
|
self.peer_sig_pub_bytes = peer_sig_pub_bytes
|
||||||
self.peer_sig_pub = Ed25519PublicKey.from_public_bytes(self.peer_sig_pub_bytes)
|
self.peer_sig_pub = Ed25519PublicKey.from_public_bytes(self.peer_sig_pub_bytes)
|
||||||
|
|
||||||
if not hasattr(self.peer_pub, "curve"):
|
if not hasattr(self.peer_pub, "curve"): self.peer_pub.curve = Link.CURVE
|
||||||
self.peer_pub.curve = Link.CURVE
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def link_id_from_lr_packet(packet):
|
def link_id_from_lr_packet(packet):
|
||||||
@@ -447,8 +443,8 @@ class Link:
|
|||||||
thread = threading.Thread(target=self.callbacks.link_established, args=(self,))
|
thread = threading.Thread(target=self.callbacks.link_established, args=(self,))
|
||||||
thread.daemon = True
|
thread.daemon = True
|
||||||
thread.start()
|
thread.start()
|
||||||
else:
|
|
||||||
RNS.log("Invalid link proof signature received by "+str(self)+". Ignoring.", RNS.LOG_DEBUG)
|
else: RNS.log("Invalid link proof signature received by "+str(self)+". Ignoring.", RNS.LOG_DEBUG)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.status = Link.CLOSED
|
self.status = Link.CLOSED
|
||||||
@@ -497,35 +493,21 @@ class Link:
|
|||||||
request_packet = RNS.Packet(self, packed_request, RNS.Packet.DATA, context = RNS.Packet.REQUEST)
|
request_packet = RNS.Packet(self, packed_request, RNS.Packet.DATA, context = RNS.Packet.REQUEST)
|
||||||
packet_receipt = request_packet.send()
|
packet_receipt = request_packet.send()
|
||||||
|
|
||||||
if packet_receipt == False:
|
if packet_receipt == False: return False
|
||||||
return False
|
|
||||||
else:
|
else:
|
||||||
packet_receipt.set_timeout(timeout)
|
packet_receipt.set_timeout(timeout)
|
||||||
return RequestReceipt(
|
return RequestReceipt(self, packet_receipt = packet_receipt, response_callback = response_callback,
|
||||||
self,
|
failed_callback = failed_callback, progress_callback = progress_callback,
|
||||||
packet_receipt = packet_receipt,
|
timeout = timeout, request_size = len(packed_request))
|
||||||
response_callback = response_callback,
|
|
||||||
failed_callback = failed_callback,
|
|
||||||
progress_callback = progress_callback,
|
|
||||||
timeout = timeout,
|
|
||||||
request_size = len(packed_request),
|
|
||||||
)
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
request_id = RNS.Identity.truncated_hash(packed_request)
|
request_id = RNS.Identity.truncated_hash(packed_request)
|
||||||
RNS.log("Sending request "+RNS.prettyhexrep(request_id)+" as resource.", RNS.LOG_DEBUG)
|
RNS.log("Sending request "+RNS.prettyhexrep(request_id)+" as resource.", RNS.LOG_DEBUG)
|
||||||
request_resource = RNS.Resource(packed_request, self, request_id = request_id, is_response = False, timeout = timeout)
|
request_resource = RNS.Resource(packed_request, self, request_id = request_id, is_response = False, timeout = timeout)
|
||||||
|
|
||||||
return RequestReceipt(
|
return RequestReceipt(self, resource = request_resource, response_callback = response_callback,
|
||||||
self,
|
failed_callback = failed_callback, progress_callback = progress_callback,
|
||||||
resource = request_resource,
|
timeout = timeout, request_size = len(packed_request))
|
||||||
response_callback = response_callback,
|
|
||||||
failed_callback = failed_callback,
|
|
||||||
progress_callback = progress_callback,
|
|
||||||
timeout = timeout,
|
|
||||||
request_size = len(packed_request),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def update_mdu(self):
|
def update_mdu(self):
|
||||||
self.mdu = self.mtu - RNS.Reticulum.HEADER_MAXSIZE - RNS.Reticulum.IFAC_MIN_SIZE
|
self.mdu = self.mtu - RNS.Reticulum.HEADER_MAXSIZE - RNS.Reticulum.IFAC_MIN_SIZE
|
||||||
@@ -540,6 +522,7 @@ class Link:
|
|||||||
self.rtt = max(measured_rtt, rtt)
|
self.rtt = max(measured_rtt, rtt)
|
||||||
self.status = Link.ACTIVE
|
self.status = Link.ACTIVE
|
||||||
self.activated_at = time.time()
|
self.activated_at = time.time()
|
||||||
|
self.expected_hops = packet.hops
|
||||||
|
|
||||||
if self.rtt != None and self.establishment_cost != None and self.rtt > 0 and self.establishment_cost > 0:
|
if self.rtt != None and self.establishment_cost != None and self.rtt > 0 and self.establishment_cost > 0:
|
||||||
self.establishment_rate = self.establishment_cost/self.rtt
|
self.establishment_rate = self.establishment_cost/self.rtt
|
||||||
@@ -547,10 +530,8 @@ class Link:
|
|||||||
self.__update_keepalive()
|
self.__update_keepalive()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if self.owner.callbacks.link_established != None:
|
if self.owner.callbacks.link_established != None: self.owner.callbacks.link_established(self)
|
||||||
self.owner.callbacks.link_established(self)
|
except Exception as e: RNS.log("Error occurred in external link establishment callback. The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||||
except Exception as e:
|
|
||||||
RNS.log("Error occurred in external link establishment callback. The contained exception was: "+str(e), RNS.LOG_ERROR)
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
RNS.log("Error occurred while processing RTT packet, tearing down link. The contained exception was: "+str(e), RNS.LOG_ERROR)
|
RNS.log("Error occurred while processing RTT packet, tearing down link. The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||||
@@ -565,73 +546,57 @@ class Link:
|
|||||||
|
|
||||||
:param track: Whether or not to keep track of physical layer statistics. Value must be ``True`` or ``False``.
|
:param track: Whether or not to keep track of physical layer statistics. Value must be ``True`` or ``False``.
|
||||||
"""
|
"""
|
||||||
if track:
|
if track: self.__track_phy_stats = True
|
||||||
self.__track_phy_stats = True
|
else: self.__track_phy_stats = False
|
||||||
else:
|
|
||||||
self.__track_phy_stats = False
|
|
||||||
|
|
||||||
def get_rssi(self):
|
def get_rssi(self):
|
||||||
"""
|
"""
|
||||||
:returns: The physical layer *Received Signal Strength Indication* if available, otherwise ``None``. Physical layer statistics must be enabled on the link for this method to return a value.
|
:returns: The physical layer *Received Signal Strength Indication* if available, otherwise ``None``. Physical layer statistics must be enabled on the link for this method to return a value.
|
||||||
"""
|
"""
|
||||||
if self.__track_phy_stats:
|
if self.__track_phy_stats: return self.rssi
|
||||||
return self.rssi
|
else: return None
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def get_snr(self):
|
def get_snr(self):
|
||||||
"""
|
"""
|
||||||
:returns: The physical layer *Signal-to-Noise Ratio* if available, otherwise ``None``. Physical layer statistics must be enabled on the link for this method to return a value.
|
:returns: The physical layer *Signal-to-Noise Ratio* if available, otherwise ``None``. Physical layer statistics must be enabled on the link for this method to return a value.
|
||||||
"""
|
"""
|
||||||
if self.__track_phy_stats:
|
if self.__track_phy_stats: return self.snr
|
||||||
return self.snr
|
else: return None
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def get_q(self):
|
def get_q(self):
|
||||||
"""
|
"""
|
||||||
:returns: The physical layer *Link Quality* if available, otherwise ``None``. Physical layer statistics must be enabled on the link for this method to return a value.
|
:returns: The physical layer *Link Quality* if available, otherwise ``None``. Physical layer statistics must be enabled on the link for this method to return a value.
|
||||||
"""
|
"""
|
||||||
if self.__track_phy_stats:
|
if self.__track_phy_stats: return self.q
|
||||||
return self.q
|
else: return None
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def get_establishment_rate(self):
|
def get_establishment_rate(self):
|
||||||
"""
|
"""
|
||||||
:returns: The data transfer rate at which the link establishment procedure ocurred, in bits per second.
|
:returns: The data transfer rate at which the link establishment procedure ocurred, in bits per second.
|
||||||
"""
|
"""
|
||||||
if self.establishment_rate != None:
|
if self.establishment_rate != None: return self.establishment_rate*8
|
||||||
return self.establishment_rate*8
|
else: return None
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def get_mtu(self):
|
def get_mtu(self):
|
||||||
"""
|
"""
|
||||||
:returns: The MTU of an established link.
|
:returns: The MTU of an established link.
|
||||||
"""
|
"""
|
||||||
if self.status == Link.ACTIVE:
|
if self.status == Link.ACTIVE: return self.mtu
|
||||||
return self.mtu
|
else: return None
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def get_mdu(self):
|
def get_mdu(self):
|
||||||
"""
|
"""
|
||||||
:returns: The packet MDU of an established link.
|
:returns: The packet MDU of an established link.
|
||||||
"""
|
"""
|
||||||
if self.status == Link.ACTIVE:
|
if self.status == Link.ACTIVE: return self.mdu
|
||||||
return self.mdu
|
else: return None
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def get_expected_rate(self):
|
def get_expected_rate(self):
|
||||||
"""
|
"""
|
||||||
:returns: The packet expected in-flight data rate of an established link.
|
:returns: The packet expected in-flight data rate of an established link.
|
||||||
"""
|
"""
|
||||||
if self.status == Link.ACTIVE:
|
if self.status == Link.ACTIVE: return self.expected_rate
|
||||||
return self.expected_rate
|
else: return None
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def get_mode(self):
|
def get_mode(self):
|
||||||
"""
|
"""
|
||||||
@@ -649,10 +614,8 @@ class Link:
|
|||||||
"""
|
"""
|
||||||
:returns: The time in seconds since this link was established.
|
:returns: The time in seconds since this link was established.
|
||||||
"""
|
"""
|
||||||
if self.activated_at:
|
if self.activated_at: return time.time() - self.activated_at
|
||||||
return time.time() - self.activated_at
|
else: return None
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def no_inbound_for(self):
|
def no_inbound_for(self):
|
||||||
"""
|
"""
|
||||||
@@ -701,6 +664,7 @@ class Link:
|
|||||||
Closes the link and purges encryption keys. New keys will
|
Closes the link and purges encryption keys. New keys will
|
||||||
be used if a new link to the same destination is established.
|
be used if a new link to the same destination is established.
|
||||||
"""
|
"""
|
||||||
|
if self.status == Link.CLOSED: return
|
||||||
if self.status != Link.PENDING and self.status != Link.CLOSED: self.__teardown_packet()
|
if self.status != Link.PENDING and self.status != Link.CLOSED: self.__teardown_packet()
|
||||||
self.status = Link.CLOSED
|
self.status = Link.CLOSED
|
||||||
if self.initiator: self.teardown_reason = Link.INITIATOR_CLOSED
|
if self.initiator: self.teardown_reason = Link.INITIATOR_CLOSED
|
||||||
@@ -712,14 +676,11 @@ class Link:
|
|||||||
plaintext = self.decrypt(packet.data)
|
plaintext = self.decrypt(packet.data)
|
||||||
if plaintext == self.link_id:
|
if plaintext == self.link_id:
|
||||||
self.status = Link.CLOSED
|
self.status = Link.CLOSED
|
||||||
if self.initiator:
|
if self.initiator: self.teardown_reason = Link.DESTINATION_CLOSED
|
||||||
self.teardown_reason = Link.DESTINATION_CLOSED
|
else: self.teardown_reason = Link.INITIATOR_CLOSED
|
||||||
else:
|
|
||||||
self.teardown_reason = Link.INITIATOR_CLOSED
|
|
||||||
self.__update_phy_stats(packet)
|
self.__update_phy_stats(packet)
|
||||||
self.link_closed()
|
self.link_closed()
|
||||||
except Exception as e:
|
except Exception as e: pass
|
||||||
pass
|
|
||||||
|
|
||||||
def link_closed(self):
|
def link_closed(self):
|
||||||
for resource in self.incoming_resources: resource.cancel()
|
for resource in self.incoming_resources: resource.cancel()
|
||||||
@@ -752,9 +713,7 @@ class Link:
|
|||||||
while not self.status == Link.CLOSED:
|
while not self.status == Link.CLOSED:
|
||||||
while (self.watchdog_lock):
|
while (self.watchdog_lock):
|
||||||
rtt_wait = 0.025
|
rtt_wait = 0.025
|
||||||
if hasattr(self, "rtt") and self.rtt:
|
if hasattr(self, "rtt") and self.rtt: rtt_wait = self.rtt
|
||||||
rtt_wait = self.rtt
|
|
||||||
|
|
||||||
sleep(max(rtt_wait, 0.025))
|
sleep(max(rtt_wait, 0.025))
|
||||||
|
|
||||||
if not self.status == Link.CLOSED:
|
if not self.status == Link.CLOSED:
|
||||||
@@ -779,10 +738,8 @@ class Link:
|
|||||||
self.link_closed()
|
self.link_closed()
|
||||||
sleep_time = 0.001
|
sleep_time = 0.001
|
||||||
|
|
||||||
if self.initiator:
|
if self.initiator: RNS.log("Timeout waiting for link request proof", RNS.LOG_DEBUG)
|
||||||
RNS.log("Timeout waiting for link request proof", RNS.LOG_DEBUG)
|
else: RNS.log("Timeout waiting for RTT packet from link initiator", RNS.LOG_DEBUG)
|
||||||
else:
|
|
||||||
RNS.log("Timeout waiting for RTT packet from link initiator", RNS.LOG_DEBUG)
|
|
||||||
|
|
||||||
elif self.status == Link.ACTIVE:
|
elif self.status == Link.ACTIVE:
|
||||||
activated_at = self.activated_at if self.activated_at != None else 0
|
activated_at = self.activated_at if self.activated_at != None else 0
|
||||||
@@ -796,11 +753,10 @@ class Link:
|
|||||||
if time.time() >= last_inbound + self.stale_time:
|
if time.time() >= last_inbound + self.stale_time:
|
||||||
sleep_time = self.rtt * self.keepalive_timeout_factor + Link.STALE_GRACE
|
sleep_time = self.rtt * self.keepalive_timeout_factor + Link.STALE_GRACE
|
||||||
self.status = Link.STALE
|
self.status = Link.STALE
|
||||||
else:
|
|
||||||
sleep_time = self.keepalive
|
|
||||||
|
|
||||||
else:
|
else: sleep_time = self.keepalive
|
||||||
sleep_time = (last_inbound + self.keepalive) - time.time()
|
|
||||||
|
else: sleep_time = (last_inbound + self.keepalive) - time.time()
|
||||||
|
|
||||||
elif self.status == Link.STALE:
|
elif self.status == Link.STALE:
|
||||||
sleep_time = 0.001
|
sleep_time = 0.001
|
||||||
@@ -810,8 +766,7 @@ class Link:
|
|||||||
self.link_closed()
|
self.link_closed()
|
||||||
|
|
||||||
|
|
||||||
if sleep_time == 0:
|
if sleep_time == 0: RNS.log("Warning! Link watchdog sleep time of 0!", RNS.LOG_ERROR)
|
||||||
RNS.log("Warning! Link watchdog sleep time of 0!", RNS.LOG_ERROR)
|
|
||||||
if sleep_time == None or sleep_time < 0:
|
if sleep_time == None or sleep_time < 0:
|
||||||
RNS.log("Timing error! Tearing down link "+str(self)+" now.", RNS.LOG_ERROR)
|
RNS.log("Timing error! Tearing down link "+str(self)+" now.", RNS.LOG_ERROR)
|
||||||
self.teardown()
|
self.teardown()
|
||||||
@@ -825,7 +780,6 @@ class Link:
|
|||||||
self.snr = None
|
self.snr = None
|
||||||
self.q = None
|
self.q = None
|
||||||
|
|
||||||
|
|
||||||
def __update_phy_stats(self, packet, query_shared = True, force_update = False):
|
def __update_phy_stats(self, packet, query_shared = True, force_update = False):
|
||||||
if self.__track_phy_stats or force_update:
|
if self.__track_phy_stats or force_update:
|
||||||
if query_shared:
|
if query_shared:
|
||||||
@@ -834,12 +788,9 @@ class Link:
|
|||||||
if packet.snr == None: packet.snr = reticulum.get_packet_snr(packet.packet_hash)
|
if packet.snr == None: packet.snr = reticulum.get_packet_snr(packet.packet_hash)
|
||||||
if packet.q == None: packet.q = reticulum.get_packet_q(packet.packet_hash)
|
if packet.q == None: packet.q = reticulum.get_packet_q(packet.packet_hash)
|
||||||
|
|
||||||
if packet.rssi != None:
|
if packet.rssi != None: self.rssi = packet.rssi
|
||||||
self.rssi = packet.rssi
|
if packet.snr != None: self.snr = packet.snr
|
||||||
if packet.snr != None:
|
if packet.q != None: self.q = packet.q
|
||||||
self.snr = packet.snr
|
|
||||||
if packet.q != None:
|
|
||||||
self.q = packet.q
|
|
||||||
|
|
||||||
def __update_keepalive(self):
|
def __update_keepalive(self):
|
||||||
self.keepalive = max(min(self.rtt*(Link.KEEPALIVE_MAX/Link.KEEPALIVE_MAX_RTT), Link.KEEPALIVE_MAX), Link.KEEPALIVE_MIN)
|
self.keepalive = max(min(self.rtt*(Link.KEEPALIVE_MAX/Link.KEEPALIVE_MAX_RTT), Link.KEEPALIVE_MAX), Link.KEEPALIVE_MIN)
|
||||||
@@ -857,7 +808,7 @@ class Link:
|
|||||||
request_data = unpacked_request[2]
|
request_data = unpacked_request[2]
|
||||||
|
|
||||||
if path_hash in self.destination.request_handlers:
|
if path_hash in self.destination.request_handlers:
|
||||||
request_handler = self.destination.request_handlers[path_hash]
|
request_handler = self.destination.request_handlers[path_hash]
|
||||||
path = request_handler[0]
|
path = request_handler[0]
|
||||||
response_generator = request_handler[1]
|
response_generator = request_handler[1]
|
||||||
allow = request_handler[2]
|
allow = request_handler[2]
|
||||||
@@ -933,8 +884,8 @@ class Link:
|
|||||||
|
|
||||||
def job(): self.handle_request(request_id, request_data)
|
def job(): self.handle_request(request_id, request_data)
|
||||||
threading.Thread(target=job, daemon=True).start()
|
threading.Thread(target=job, daemon=True).start()
|
||||||
else:
|
|
||||||
RNS.log("Incoming request resource failed with status: "+RNS.hexrep([resource.status]), RNS.LOG_DEBUG)
|
else: RNS.log("Incoming request resource failed with status: "+RNS.hexrep([resource.status]), RNS.LOG_DEBUG)
|
||||||
|
|
||||||
def response_resource_concluded(self, resource):
|
def response_resource_concluded(self, resource):
|
||||||
if resource.status == RNS.Resource.COMPLETE:
|
if resource.status == RNS.Resource.COMPLETE:
|
||||||
@@ -965,8 +916,7 @@ class Link:
|
|||||||
|
|
||||||
:return: ``Channel`` object
|
:return: ``Channel`` object
|
||||||
"""
|
"""
|
||||||
if self._channel is None:
|
if self._channel is None: self._channel = Channel(LinkChannelOutlet(self))
|
||||||
self._channel = Channel(LinkChannelOutlet(self))
|
|
||||||
return self._channel
|
return self._channel
|
||||||
|
|
||||||
def receive(self, packet):
|
def receive(self, packet):
|
||||||
@@ -976,12 +926,10 @@ class Link:
|
|||||||
RNS.log(f"Link-associated packet received on unexpected interface {packet.receiving_interface} instead of {self.attached_interface}! Someone might be trying to manipulate your communication!", RNS.LOG_ERROR)
|
RNS.log(f"Link-associated packet received on unexpected interface {packet.receiving_interface} instead of {self.attached_interface}! Someone might be trying to manipulate your communication!", RNS.LOG_ERROR)
|
||||||
else:
|
else:
|
||||||
self.last_inbound = time.time()
|
self.last_inbound = time.time()
|
||||||
if packet.context != RNS.Packet.KEEPALIVE:
|
if packet.context != RNS.Packet.KEEPALIVE: self.last_data = self.last_inbound
|
||||||
self.last_data = self.last_inbound
|
|
||||||
self.rx += 1
|
self.rx += 1
|
||||||
self.rxbytes += len(packet.data)
|
self.rxbytes += len(packet.data)
|
||||||
if self.status == Link.STALE:
|
if self.status == Link.STALE: self.status = Link.ACTIVE
|
||||||
self.status = Link.ACTIVE
|
|
||||||
|
|
||||||
if packet.packet_type == RNS.Packet.DATA:
|
if packet.packet_type == RNS.Packet.DATA:
|
||||||
should_query = False
|
should_query = False
|
||||||
@@ -1002,10 +950,8 @@ class Link:
|
|||||||
elif self.destination.proof_strategy == RNS.Destination.PROVE_APP:
|
elif self.destination.proof_strategy == RNS.Destination.PROVE_APP:
|
||||||
if self.destination.callbacks.proof_requested:
|
if self.destination.callbacks.proof_requested:
|
||||||
try:
|
try:
|
||||||
if self.destination.callbacks.proof_requested(packet):
|
if self.destination.callbacks.proof_requested(packet): packet.prove()
|
||||||
packet.prove()
|
except Exception as e: RNS.log("Error while executing proof request callback from "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||||
except Exception as e:
|
|
||||||
RNS.log("Error while executing proof request callback from "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
|
|
||||||
|
|
||||||
elif packet.context == RNS.Packet.LINKIDENTIFY:
|
elif packet.context == RNS.Packet.LINKIDENTIFY:
|
||||||
plaintext = self.decrypt(packet.data)
|
plaintext = self.decrypt(packet.data)
|
||||||
@@ -1023,10 +969,11 @@ class Link:
|
|||||||
self.teardown()
|
self.teardown()
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self.__remote_identity = identity
|
if self.__remote_identity == None:
|
||||||
if self.callbacks.remote_identified != None:
|
self.__remote_identity = identity
|
||||||
try: self.callbacks.remote_identified(self, self.__remote_identity)
|
if self.callbacks.remote_identified != None:
|
||||||
except Exception as e: RNS.log(f"Error while executing remote identified callback from {self}. The contained exception was: "+str(e), RNS.LOG_ERROR)
|
try: self.callbacks.remote_identified(self, self.__remote_identity)
|
||||||
|
except Exception as e: RNS.log(f"Error while executing remote identified callback from {self}. The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||||
|
|
||||||
self.__update_phy_stats(packet, query_shared=True)
|
self.__update_phy_stats(packet, query_shared=True)
|
||||||
|
|
||||||
@@ -1039,8 +986,7 @@ class Link:
|
|||||||
def job(): self.handle_request(request_id, unpacked_request)
|
def job(): self.handle_request(request_id, unpacked_request)
|
||||||
threading.Thread(target=job, daemon=True).start()
|
threading.Thread(target=job, daemon=True).start()
|
||||||
self.__update_phy_stats(packet, query_shared=True)
|
self.__update_phy_stats(packet, query_shared=True)
|
||||||
except Exception as e:
|
except Exception as e: RNS.log("Error occurred while handling request. The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||||
RNS.log("Error occurred while handling request. The contained exception was: "+str(e), RNS.LOG_ERROR)
|
|
||||||
|
|
||||||
elif packet.context == RNS.Packet.RESPONSE:
|
elif packet.context == RNS.Packet.RESPONSE:
|
||||||
try:
|
try:
|
||||||
@@ -1053,8 +999,7 @@ class Link:
|
|||||||
def job(): self.handle_response(request_id, response_data, transfer_size, transfer_size, update_sizes=True)
|
def job(): self.handle_response(request_id, response_data, transfer_size, transfer_size, update_sizes=True)
|
||||||
threading.Thread(target=job, daemon=True).start()
|
threading.Thread(target=job, daemon=True).start()
|
||||||
self.__update_phy_stats(packet, query_shared=True)
|
self.__update_phy_stats(packet, query_shared=True)
|
||||||
except Exception as e:
|
except Exception as e: RNS.log("Error occurred while handling response. The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||||
RNS.log("Error occurred while handling response. The contained exception was: "+str(e), RNS.LOG_ERROR)
|
|
||||||
|
|
||||||
elif packet.context == RNS.Packet.LRRTT:
|
elif packet.context == RNS.Packet.LRRTT:
|
||||||
if not self.initiator:
|
if not self.initiator:
|
||||||
@@ -1069,36 +1014,39 @@ class Link:
|
|||||||
packet.plaintext = self.decrypt(packet.data)
|
packet.plaintext = self.decrypt(packet.data)
|
||||||
if packet.plaintext != None:
|
if packet.plaintext != None:
|
||||||
self.__update_phy_stats(packet, query_shared=True)
|
self.__update_phy_stats(packet, query_shared=True)
|
||||||
|
try:
|
||||||
if RNS.ResourceAdvertisement.is_request(packet):
|
if RNS.ResourceAdvertisement.is_request(packet):
|
||||||
RNS.Resource.accept(packet, callback=self.request_resource_concluded)
|
if self.destination.request_handlers:
|
||||||
elif RNS.ResourceAdvertisement.is_response(packet):
|
RNS.Resource.accept(packet, callback=self.request_resource_concluded)
|
||||||
request_id = RNS.ResourceAdvertisement.read_request_id(packet)
|
elif RNS.ResourceAdvertisement.is_response(packet):
|
||||||
for pending_request in self.pending_requests:
|
request_id = RNS.ResourceAdvertisement.read_request_id(packet)
|
||||||
if pending_request.request_id == request_id:
|
for pending_request in self.pending_requests:
|
||||||
response_resource = RNS.Resource.accept(packet, callback=self.response_resource_concluded, progress_callback=pending_request.response_resource_progress, request_id = request_id)
|
if pending_request.request_id == request_id:
|
||||||
if response_resource != None:
|
response_resource = RNS.Resource.accept(packet, callback=self.response_resource_concluded, progress_callback=pending_request.response_resource_progress, request_id = request_id)
|
||||||
if pending_request.response_size == None:
|
if response_resource != None:
|
||||||
pending_request.response_size = RNS.ResourceAdvertisement.read_size(packet)
|
if pending_request.response_size == None:
|
||||||
if pending_request.response_transfer_size == None:
|
pending_request.response_size = RNS.ResourceAdvertisement.read_size(packet)
|
||||||
pending_request.response_transfer_size = 0
|
if pending_request.response_transfer_size == None:
|
||||||
pending_request.response_transfer_size += RNS.ResourceAdvertisement.read_transfer_size(packet)
|
pending_request.response_transfer_size = 0
|
||||||
if pending_request.started_at == None:
|
pending_request.response_transfer_size += RNS.ResourceAdvertisement.read_transfer_size(packet)
|
||||||
pending_request.started_at = time.time()
|
if pending_request.started_at == None:
|
||||||
pending_request.response_resource_progress(response_resource)
|
pending_request.started_at = time.time()
|
||||||
|
pending_request.response_resource_progress(response_resource)
|
||||||
elif self.resource_strategy == Link.ACCEPT_NONE: pass
|
elif self.resource_strategy == Link.ACCEPT_NONE: pass
|
||||||
elif self.resource_strategy == Link.ACCEPT_APP:
|
elif self.resource_strategy == Link.ACCEPT_APP:
|
||||||
if self.callbacks.resource != None:
|
if self.callbacks.resource != None:
|
||||||
try:
|
try:
|
||||||
resource_advertisement = RNS.ResourceAdvertisement.unpack(packet.plaintext)
|
resource_advertisement = RNS.ResourceAdvertisement.unpack(packet.plaintext)
|
||||||
resource_advertisement.link = self
|
resource_advertisement.link = self
|
||||||
if self.callbacks.resource(resource_advertisement): RNS.Resource.accept(packet, self.callbacks.resource_concluded)
|
if self.callbacks.resource(resource_advertisement): RNS.Resource.accept(packet, self.callbacks.resource_concluded)
|
||||||
else: RNS.Resource.reject(packet)
|
else: RNS.Resource.reject(packet)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
RNS.log("Error while executing resource accept callback from "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
|
RNS.log("Error while executing resource accept callback from "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||||
elif self.resource_strategy == Link.ACCEPT_ALL:
|
elif self.resource_strategy == Link.ACCEPT_ALL:
|
||||||
RNS.Resource.accept(packet, self.callbacks.resource_concluded)
|
RNS.Resource.accept(packet, self.callbacks.resource_concluded)
|
||||||
|
except Exception as e:
|
||||||
|
RNS.log(f"Invalid resource advertisement on {self}: {e}", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
|
||||||
|
self.teardown()
|
||||||
|
|
||||||
elif packet.context == RNS.Packet.RESOURCE_REQ:
|
elif packet.context == RNS.Packet.RESOURCE_REQ:
|
||||||
plaintext = self.decrypt(packet.data)
|
plaintext = self.decrypt(packet.data)
|
||||||
@@ -1186,7 +1134,6 @@ class Link:
|
|||||||
|
|
||||||
self.watchdog_lock = False
|
self.watchdog_lock = False
|
||||||
|
|
||||||
|
|
||||||
def encrypt(self, plaintext):
|
def encrypt(self, plaintext):
|
||||||
try:
|
try:
|
||||||
if not self.token:
|
if not self.token:
|
||||||
@@ -1201,7 +1148,6 @@ class Link:
|
|||||||
RNS.log("Encryption on link "+str(self)+" failed. The contained exception was: "+str(e), RNS.LOG_ERROR)
|
RNS.log("Encryption on link "+str(self)+" failed. The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
|
|
||||||
def decrypt(self, ciphertext):
|
def decrypt(self, ciphertext):
|
||||||
try:
|
try:
|
||||||
if not self.token: self.token = Token(self.derived_key)
|
if not self.token: self.token = Token(self.derived_key)
|
||||||
@@ -1211,7 +1157,6 @@ class Link:
|
|||||||
RNS.log("Decryption failed on link "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
|
RNS.log("Decryption failed on link "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def sign(self, message):
|
def sign(self, message):
|
||||||
return self.sig_prv.sign(message)
|
return self.sig_prv.sign(message)
|
||||||
|
|
||||||
@@ -1219,8 +1164,7 @@ class Link:
|
|||||||
try:
|
try:
|
||||||
self.peer_sig_pub.verify(signature, message)
|
self.peer_sig_pub.verify(signature, message)
|
||||||
return True
|
return True
|
||||||
except Exception as e:
|
except Exception as e: return False
|
||||||
return False
|
|
||||||
|
|
||||||
def set_link_established_callback(self, callback):
|
def set_link_established_callback(self, callback):
|
||||||
self.callbacks.link_established = callback
|
self.callbacks.link_established = callback
|
||||||
@@ -1388,7 +1332,6 @@ class RequestReceipt():
|
|||||||
|
|
||||||
self.link.pending_requests.append(self)
|
self.link.pending_requests.append(self)
|
||||||
|
|
||||||
|
|
||||||
def request_resource_concluded(self, resource):
|
def request_resource_concluded(self, resource):
|
||||||
if resource.status == RNS.Resource.COMPLETE:
|
if resource.status == RNS.Resource.COMPLETE:
|
||||||
RNS.log("Request "+RNS.prettyhexrep(self.request_id)+" successfully sent as resource.", RNS.LOG_DEBUG)
|
RNS.log("Request "+RNS.prettyhexrep(self.request_id)+" successfully sent as resource.", RNS.LOG_DEBUG)
|
||||||
@@ -1411,7 +1354,6 @@ class RequestReceipt():
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
RNS.log("Error while executing request failed callback from "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
|
RNS.log("Error while executing request failed callback from "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||||
|
|
||||||
|
|
||||||
def __response_timeout_job(self):
|
def __response_timeout_job(self):
|
||||||
while self.status == RequestReceipt.DELIVERED:
|
while self.status == RequestReceipt.DELIVERED:
|
||||||
now = time.time()
|
now = time.time()
|
||||||
@@ -1421,7 +1363,6 @@ class RequestReceipt():
|
|||||||
|
|
||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
|
|
||||||
|
|
||||||
def request_timed_out(self, packet_receipt):
|
def request_timed_out(self, packet_receipt):
|
||||||
if self in self.link.pending_requests and self.status == RequestReceipt.DELIVERED:
|
if self in self.link.pending_requests and self.status == RequestReceipt.DELIVERED:
|
||||||
self.status = RequestReceipt.FAILED
|
self.status = RequestReceipt.FAILED
|
||||||
@@ -1430,9 +1371,7 @@ class RequestReceipt():
|
|||||||
|
|
||||||
if self.callbacks.failed != None:
|
if self.callbacks.failed != None:
|
||||||
try: self.callbacks.failed(self)
|
try: self.callbacks.failed(self)
|
||||||
except Exception as e:
|
except Exception as e: RNS.log("Error while executing request timed out callback from "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||||
RNS.log("Error while executing request timed out callback from "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
|
|
||||||
|
|
||||||
|
|
||||||
def response_resource_progress(self, resource):
|
def response_resource_progress(self, resource):
|
||||||
if resource != None:
|
if resource != None:
|
||||||
@@ -1449,13 +1388,10 @@ class RequestReceipt():
|
|||||||
self.progress = resource.get_progress()
|
self.progress = resource.get_progress()
|
||||||
|
|
||||||
if self.callbacks.progress != None:
|
if self.callbacks.progress != None:
|
||||||
try:
|
try: self.callbacks.progress(self)
|
||||||
self.callbacks.progress(self)
|
except Exception as e: RNS.log("Error while executing response progress callback from "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||||
except Exception as e:
|
|
||||||
RNS.log("Error while executing response progress callback from "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
|
|
||||||
else:
|
|
||||||
resource.cancel()
|
|
||||||
|
|
||||||
|
else: resource.cancel()
|
||||||
|
|
||||||
def response_received(self, response, metadata=None):
|
def response_received(self, response, metadata=None):
|
||||||
if not self.status == RequestReceipt.FAILED:
|
if not self.status == RequestReceipt.FAILED:
|
||||||
@@ -1474,13 +1410,11 @@ class RequestReceipt():
|
|||||||
|
|
||||||
if self.callbacks.progress != None:
|
if self.callbacks.progress != None:
|
||||||
try: self.callbacks.progress(self)
|
try: self.callbacks.progress(self)
|
||||||
except Exception as e:
|
except Exception as e: RNS.log("Error while executing response progress callback from "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||||
RNS.log("Error while executing response progress callback from "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
|
|
||||||
|
|
||||||
if self.callbacks.response != None:
|
if self.callbacks.response != None:
|
||||||
try: self.callbacks.response(self)
|
try: self.callbacks.response(self)
|
||||||
except Exception as e:
|
except Exception as e: RNS.log("Error while executing response received callback from "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||||
RNS.log("Error while executing response received callback from "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
|
|
||||||
|
|
||||||
def get_request_id(self):
|
def get_request_id(self):
|
||||||
"""
|
"""
|
||||||
@@ -1504,31 +1438,23 @@ class RequestReceipt():
|
|||||||
"""
|
"""
|
||||||
:returns: The response as *bytes* if it is ready, otherwise *None*.
|
:returns: The response as *bytes* if it is ready, otherwise *None*.
|
||||||
"""
|
"""
|
||||||
if self.status == RequestReceipt.READY:
|
if self.status == RequestReceipt.READY: return self.response
|
||||||
return self.response
|
else: return None
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def get_response_time(self):
|
def get_response_time(self):
|
||||||
"""
|
"""
|
||||||
:returns: The response time of the request in seconds.
|
:returns: The response time of the request in seconds.
|
||||||
"""
|
"""
|
||||||
if self.status == RequestReceipt.READY:
|
if self.status == RequestReceipt.READY: return self.response_concluded_at - self.started_at
|
||||||
return self.response_concluded_at - self.started_at
|
else: return None
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def concluded(self):
|
def concluded(self):
|
||||||
"""
|
"""
|
||||||
:returns: True if the associated request has concluded (successfully or with a failure), otherwise False.
|
:returns: True if the associated request has concluded (successfully or with a failure), otherwise False.
|
||||||
"""
|
"""
|
||||||
if self.status == RequestReceipt.READY:
|
if self.status == RequestReceipt.READY: return True
|
||||||
return True
|
elif self.status == RequestReceipt.FAILED: return True
|
||||||
elif self.status == RequestReceipt.FAILED:
|
else: return False
|
||||||
return True
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class RequestReceiptCallbacks:
|
class RequestReceiptCallbacks:
|
||||||
|
|||||||
+46
-73
@@ -244,6 +244,9 @@ class Packet:
|
|||||||
self.flags = self.raw[0]
|
self.flags = self.raw[0]
|
||||||
self.hops = self.raw[1]
|
self.hops = self.raw[1]
|
||||||
|
|
||||||
|
if self.hops >= RNS.Transport.PATHFINDER_M:
|
||||||
|
raise ValueError(f"Invalid hop count {self.hops}")
|
||||||
|
|
||||||
self.header_type = (self.flags & 0b01000000) >> 6
|
self.header_type = (self.flags & 0b01000000) >> 6
|
||||||
self.context_flag = (self.flags & 0b00100000) >> 5
|
self.context_flag = (self.flags & 0b00100000) >> 5
|
||||||
self.transport_type = (self.flags & 0b00010000) >> 4
|
self.transport_type = (self.flags & 0b00010000) >> 4
|
||||||
@@ -268,7 +271,7 @@ class Packet:
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
RNS.log("Received malformed packet, dropping it. The contained exception was: "+str(e), RNS.LOG_EXTREME) if RNS.sl(RNS.LOG_EXTREME) else None
|
RNS.log(f"Received malformed packet, dropping it. The contained exception was: {e}", RNS.LOG_EXTREME) if RNS.sl(RNS.LOG_EXTREME) else None
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def send(self):
|
def send(self):
|
||||||
@@ -278,6 +281,7 @@ 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.
|
: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 not self.sent:
|
if not self.sent:
|
||||||
|
if not self.packed: self.pack()
|
||||||
if self.destination.type == RNS.Destination.LINK:
|
if self.destination.type == RNS.Destination.LINK:
|
||||||
if self.destination.status == RNS.Link.CLOSED:
|
if self.destination.status == RNS.Link.CLOSED:
|
||||||
RNS.log("Attempt to transmit over a closed link, dropping packet", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
|
RNS.log("Attempt to transmit over a closed link, dropping packet", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
|
||||||
@@ -288,9 +292,7 @@ class Packet:
|
|||||||
else:
|
else:
|
||||||
self.destination.last_outbound = time.time()
|
self.destination.last_outbound = time.time()
|
||||||
self.destination.tx += 1
|
self.destination.tx += 1
|
||||||
self.destination.txbytes += len(self.data)
|
self.destination.txbytes += len(self.ciphertext)
|
||||||
|
|
||||||
if not self.packed: self.pack()
|
|
||||||
|
|
||||||
if RNS.Transport.outbound(self): return self.receipt
|
if RNS.Transport.outbound(self): return self.receipt
|
||||||
else:
|
else:
|
||||||
@@ -313,51 +315,40 @@ class Packet:
|
|||||||
# encrypted destinations
|
# encrypted destinations
|
||||||
self.pack()
|
self.pack()
|
||||||
|
|
||||||
if RNS.Transport.outbound(self):
|
if RNS.Transport.outbound(self): return self.receipt
|
||||||
return self.receipt
|
|
||||||
else:
|
else:
|
||||||
RNS.log("Re-send failed. No interfaces could process the outbound packet", RNS.LOG_WARNING)
|
RNS.log("Re-send failed. No interfaces could process the outbound packet", RNS.LOG_WARNING)
|
||||||
self.sent = False
|
self.sent = False
|
||||||
self.receipt = None
|
self.receipt = None
|
||||||
return False
|
return False
|
||||||
else:
|
|
||||||
raise IOError("Packet was not sent yet")
|
else: raise IOError("Packet was not sent yet")
|
||||||
|
|
||||||
def prove(self, destination=None):
|
def prove(self, destination=None):
|
||||||
if self.fromPacked and hasattr(self, "destination") and self.destination:
|
if self.fromPacked and hasattr(self, "destination") and self.destination:
|
||||||
if self.destination.identity and self.destination.identity.prv:
|
if self.destination.identity and self.destination.identity.prv:
|
||||||
self.destination.identity.prove(self, destination)
|
self.destination.identity.prove(self, destination)
|
||||||
elif self.fromPacked and hasattr(self, "link") and self.link:
|
elif self.fromPacked and hasattr(self, "link") and self.link: self.link.prove_packet(self)
|
||||||
self.link.prove_packet(self)
|
else: RNS.log("Could not prove packet associated with neither a destination nor a link", RNS.LOG_ERROR)
|
||||||
else:
|
|
||||||
RNS.log("Could not prove packet associated with neither a destination nor a link", RNS.LOG_ERROR)
|
|
||||||
|
|
||||||
# Generates a special destination that allows Reticulum
|
# Generates a special destination that allows Reticulum
|
||||||
# to direct the proof back to the proved packet's sender
|
# to direct the proof back to the proved packet's sender
|
||||||
def generate_proof_destination(self):
|
def generate_proof_destination(self): return ProofDestination(self)
|
||||||
return ProofDestination(self)
|
|
||||||
|
|
||||||
def validate_proof_packet(self, proof_packet):
|
def validate_proof_packet(self, proof_packet): return self.receipt.validate_proof_packet(proof_packet)
|
||||||
return self.receipt.validate_proof_packet(proof_packet)
|
|
||||||
|
|
||||||
def validate_proof(self, proof):
|
def validate_proof(self, proof): return self.receipt.validate_proof(proof)
|
||||||
return self.receipt.validate_proof(proof)
|
|
||||||
|
|
||||||
def update_hash(self):
|
def update_hash(self): self.packet_hash = self.get_hash()
|
||||||
self.packet_hash = self.get_hash()
|
|
||||||
|
|
||||||
def get_hash(self):
|
def get_hash(self): return RNS.Identity.full_hash(self.get_hashable_part())
|
||||||
return RNS.Identity.full_hash(self.get_hashable_part())
|
|
||||||
|
|
||||||
def getTruncatedHash(self):
|
def getTruncatedHash(self): return RNS.Identity.truncated_hash(self.get_hashable_part())
|
||||||
return RNS.Identity.truncated_hash(self.get_hashable_part())
|
|
||||||
|
|
||||||
def get_hashable_part(self):
|
def get_hashable_part(self):
|
||||||
hashable_part = bytes([self.raw[0] & 0b00001111])
|
hashable_part = bytes([self.raw[0] & 0b00001111])
|
||||||
if self.header_type == Packet.HEADER_2:
|
if self.header_type == Packet.HEADER_2: hashable_part += self.raw[(RNS.Identity.TRUNCATED_HASHLENGTH//8)+2:]
|
||||||
hashable_part += self.raw[(RNS.Identity.TRUNCATED_HASHLENGTH//8)+2:]
|
else: hashable_part += self.raw[2:]
|
||||||
else:
|
|
||||||
hashable_part += self.raw[2:]
|
|
||||||
|
|
||||||
return hashable_part
|
return hashable_part
|
||||||
|
|
||||||
@@ -365,36 +356,29 @@ class Packet:
|
|||||||
"""
|
"""
|
||||||
:returns: The physical layer *Received Signal Strength Indication* if available, otherwise ``None``.
|
:returns: The physical layer *Received Signal Strength Indication* if available, otherwise ``None``.
|
||||||
"""
|
"""
|
||||||
if self.rssi != None:
|
if self.rssi != None: return self.rssi
|
||||||
return self.rssi
|
else: return reticulum.get_packet_rssi(self.packet_hash)
|
||||||
else:
|
|
||||||
return reticulum.get_packet_rssi(self.packet_hash)
|
|
||||||
|
|
||||||
def get_snr(self):
|
def get_snr(self):
|
||||||
"""
|
"""
|
||||||
:returns: The physical layer *Signal-to-Noise Ratio* if available, otherwise ``None``.
|
:returns: The physical layer *Signal-to-Noise Ratio* if available, otherwise ``None``.
|
||||||
"""
|
"""
|
||||||
if self.snr != None:
|
if self.snr != None: return self.snr
|
||||||
return self.snr
|
else: return reticulum.get_packet_snr(self.packet_hash)
|
||||||
else:
|
|
||||||
return reticulum.get_packet_snr(self.packet_hash)
|
|
||||||
|
|
||||||
def get_q(self):
|
def get_q(self):
|
||||||
"""
|
"""
|
||||||
:returns: The physical layer *Link Quality* if available, otherwise ``None``.
|
:returns: The physical layer *Link Quality* if available, otherwise ``None``.
|
||||||
"""
|
"""
|
||||||
if self.q != None:
|
if self.q != None: return self.q
|
||||||
return self.q
|
else: return reticulum.get_packet_q(self.packet_hash)
|
||||||
else:
|
|
||||||
return reticulum.get_packet_q(self.packet_hash)
|
|
||||||
|
|
||||||
class ProofDestination:
|
class ProofDestination:
|
||||||
def __init__(self, packet):
|
def __init__(self, packet):
|
||||||
self.hash = packet.get_hash()[:RNS.Reticulum.TRUNCATED_HASHLENGTH//8];
|
self.hash = packet.get_hash()[:RNS.Reticulum.TRUNCATED_HASHLENGTH//8];
|
||||||
self.type = RNS.Destination.SINGLE
|
self.type = RNS.Destination.SINGLE
|
||||||
|
|
||||||
def encrypt(self, plaintext):
|
def encrypt(self, plaintext): return plaintext
|
||||||
return plaintext
|
|
||||||
|
|
||||||
|
|
||||||
class PacketReceipt:
|
class PacketReceipt:
|
||||||
@@ -463,18 +447,17 @@ class PacketReceipt:
|
|||||||
link.last_proof = self.concluded_at
|
link.last_proof = self.concluded_at
|
||||||
|
|
||||||
if self.callbacks.delivery != None:
|
if self.callbacks.delivery != None:
|
||||||
try:
|
try: self.callbacks.delivery(self)
|
||||||
self.callbacks.delivery(self)
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
RNS.log("An error occurred while evaluating external delivery callback for "+str(link), RNS.LOG_ERROR)
|
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)
|
RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||||
RNS.trace_exception(e)
|
RNS.trace_exception(e)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
else:
|
|
||||||
return False
|
else: return False
|
||||||
else:
|
else: return False
|
||||||
return False
|
|
||||||
elif len(proof) == PacketReceipt.IMPL_LENGTH:
|
elif len(proof) == PacketReceipt.IMPL_LENGTH:
|
||||||
pass
|
pass
|
||||||
# TODO: Why is this disabled?
|
# TODO: Why is this disabled?
|
||||||
@@ -491,8 +474,7 @@ class PacketReceipt:
|
|||||||
# else:
|
# else:
|
||||||
# RNS.log("invalid")
|
# RNS.log("invalid")
|
||||||
# return False
|
# return False
|
||||||
else:
|
else: return False
|
||||||
return False
|
|
||||||
|
|
||||||
# Validate a raw proof
|
# Validate a raw proof
|
||||||
def validate_proof(self, proof, proof_packet=None):
|
def validate_proof(self, proof, proof_packet=None):
|
||||||
@@ -509,24 +491,20 @@ class PacketReceipt:
|
|||||||
self.proof_packet = proof_packet
|
self.proof_packet = proof_packet
|
||||||
|
|
||||||
if self.callbacks.delivery != None:
|
if self.callbacks.delivery != None:
|
||||||
try:
|
try: self.callbacks.delivery(self)
|
||||||
self.callbacks.delivery(self)
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
RNS.log("Error while executing proof validated callback. The contained exception was: "+str(e), RNS.LOG_ERROR)
|
RNS.log("Error while executing proof validated callback. The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
else:
|
|
||||||
return False
|
else: return False
|
||||||
else:
|
else: return False
|
||||||
return False
|
|
||||||
elif len(proof) == PacketReceipt.IMPL_LENGTH:
|
elif len(proof) == PacketReceipt.IMPL_LENGTH:
|
||||||
# This is an implicit proof
|
# This is an implicit proof
|
||||||
|
|
||||||
if not hasattr(self.destination, "identity"):
|
if not hasattr(self.destination, "identity"): return False
|
||||||
return False
|
if self.destination.identity == None: return False
|
||||||
|
|
||||||
if self.destination.identity == None:
|
|
||||||
return False
|
|
||||||
|
|
||||||
signature = proof[:RNS.Identity.SIGLENGTH//8]
|
signature = proof[:RNS.Identity.SIGLENGTH//8]
|
||||||
proof_valid = self.destination.identity.validate(signature, self.hash)
|
proof_valid = self.destination.identity.validate(signature, self.hash)
|
||||||
@@ -537,16 +515,13 @@ class PacketReceipt:
|
|||||||
self.proof_packet = proof_packet
|
self.proof_packet = proof_packet
|
||||||
|
|
||||||
if self.callbacks.delivery != None:
|
if self.callbacks.delivery != None:
|
||||||
try:
|
try: self.callbacks.delivery(self)
|
||||||
self.callbacks.delivery(self)
|
except Exception as e: RNS.log("Error while executing proof validated callback. The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||||
except Exception as e:
|
|
||||||
RNS.log("Error while executing proof validated callback. The contained exception was: "+str(e), RNS.LOG_ERROR)
|
|
||||||
|
|
||||||
return True
|
return True
|
||||||
else:
|
|
||||||
return False
|
else: return False
|
||||||
else:
|
else: return False
|
||||||
return False
|
|
||||||
|
|
||||||
def get_rtt(self):
|
def get_rtt(self):
|
||||||
"""
|
"""
|
||||||
@@ -559,10 +534,8 @@ class PacketReceipt:
|
|||||||
|
|
||||||
def check_timeout(self):
|
def check_timeout(self):
|
||||||
if self.status == PacketReceipt.SENT and self.is_timed_out():
|
if self.status == PacketReceipt.SENT and self.is_timed_out():
|
||||||
if self.timeout == -1:
|
if self.timeout == -1: self.status = PacketReceipt.CULLED
|
||||||
self.status = PacketReceipt.CULLED
|
else: self.status = PacketReceipt.FAILED
|
||||||
else:
|
|
||||||
self.status = PacketReceipt.FAILED
|
|
||||||
|
|
||||||
self.concluded_at = time.time()
|
self.concluded_at = time.time()
|
||||||
|
|
||||||
|
|||||||
+1
-2
@@ -31,5 +31,4 @@
|
|||||||
class Resolver:
|
class Resolver:
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def resolve_identity(full_name):
|
def resolve_identity(full_name): pass
|
||||||
pass
|
|
||||||
+85
-98
@@ -149,7 +149,7 @@ class Resource:
|
|||||||
COMPLETE = 0x06
|
COMPLETE = 0x06
|
||||||
FAILED = 0x07
|
FAILED = 0x07
|
||||||
CORRUPT = 0x08
|
CORRUPT = 0x08
|
||||||
REJECTED = 0x00
|
REJECTED = 0x09
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def reject(advertisement_packet):
|
def reject(advertisement_packet):
|
||||||
@@ -239,7 +239,7 @@ class Resource:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
RNS.log("Could not decode resource advertisement, dropping resource", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
|
RNS.log(f"Could not decode resource advertisement, dropping resource: {e}", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Create a resource for transmission to a remote destination
|
# Create a resource for transmission to a remote destination
|
||||||
@@ -314,30 +314,26 @@ class Resource:
|
|||||||
self.input_file = data
|
self.input_file = data
|
||||||
|
|
||||||
elif isinstance(data, bytes):
|
elif isinstance(data, bytes):
|
||||||
data_size = len(data)
|
data_size = len(data)
|
||||||
self.total_size = data_size + self.metadata_size
|
self.total_size = data_size + self.metadata_size
|
||||||
|
|
||||||
resource_data = data
|
resource_data = data
|
||||||
self.total_segments = 1
|
self.total_segments = 1
|
||||||
self.segment_index = 1
|
self.segment_index = 1
|
||||||
self.split = False
|
self.split = False
|
||||||
|
|
||||||
elif data == None:
|
elif data == None: pass
|
||||||
pass
|
|
||||||
|
|
||||||
else:
|
else: raise TypeError("Invalid data instance type passed to resource initialisation")
|
||||||
raise TypeError("Invalid data instance type passed to resource initialisation")
|
|
||||||
|
|
||||||
if resource_data:
|
if resource_data:
|
||||||
if self.has_metadata: data = self.metadata + resource_data
|
if self.has_metadata: data = self.metadata + resource_data
|
||||||
else: data = resource_data
|
else: data = resource_data
|
||||||
|
|
||||||
self.status = Resource.NONE
|
self.status = Resource.NONE
|
||||||
self.link = link
|
self.link = link
|
||||||
if self.link.mtu:
|
if self.link.mtu: self.sdu = self.link.mtu - RNS.Reticulum.HEADER_MAXSIZE - RNS.Reticulum.IFAC_MIN_SIZE
|
||||||
self.sdu = self.link.mtu - RNS.Reticulum.HEADER_MAXSIZE - RNS.Reticulum.IFAC_MIN_SIZE
|
else: self.sdu = link.mdu or Resource.SDU
|
||||||
else:
|
|
||||||
self.sdu = link.mdu or Resource.SDU
|
|
||||||
self.max_retries = Resource.MAX_RETRIES
|
self.max_retries = Resource.MAX_RETRIES
|
||||||
self.max_adv_retries = Resource.MAX_ADV_RETRIES
|
self.max_adv_retries = Resource.MAX_ADV_RETRIES
|
||||||
self.retries_left = self.max_retries
|
self.retries_left = self.max_retries
|
||||||
@@ -482,12 +478,12 @@ class Resource:
|
|||||||
|
|
||||||
def hashmap_update_packet(self, plaintext):
|
def hashmap_update_packet(self, plaintext):
|
||||||
if not self.status == Resource.FAILED:
|
if not self.status == Resource.FAILED:
|
||||||
self.last_activity = time.time()
|
if self.waiting_for_hmu:
|
||||||
self.retries_left = self.max_retries
|
self.last_activity = time.time()
|
||||||
|
self.retries_left = self.max_retries
|
||||||
update = umsgpack.unpackb(plaintext[RNS.Identity.HASHLENGTH//8:])
|
|
||||||
self.hashmap_update(update[0], update[1])
|
|
||||||
|
|
||||||
|
update = umsgpack.unpackb(plaintext[RNS.Identity.HASHLENGTH//8:])
|
||||||
|
self.hashmap_update(update[0], update[1])
|
||||||
|
|
||||||
def hashmap_update(self, segment, hashmap):
|
def hashmap_update(self, segment, hashmap):
|
||||||
if not self.status == Resource.FAILED:
|
if not self.status == Resource.FAILED:
|
||||||
@@ -495,12 +491,16 @@ class Resource:
|
|||||||
seg_len = ResourceAdvertisement.HASHMAP_MAX_LEN
|
seg_len = ResourceAdvertisement.HASHMAP_MAX_LEN
|
||||||
hashes = len(hashmap)//Resource.MAPHASH_LEN
|
hashes = len(hashmap)//Resource.MAPHASH_LEN
|
||||||
for i in range(0,hashes):
|
for i in range(0,hashes):
|
||||||
if self.hashmap[i+segment*seg_len] == None:
|
if self.hashmap[i+segment*seg_len] == None: self.hashmap_height += 1
|
||||||
self.hashmap_height += 1
|
|
||||||
self.hashmap[i+segment*seg_len] = hashmap[i*Resource.MAPHASH_LEN:(i+1)*Resource.MAPHASH_LEN]
|
self.hashmap[i+segment*seg_len] = hashmap[i*Resource.MAPHASH_LEN:(i+1)*Resource.MAPHASH_LEN]
|
||||||
|
|
||||||
self.waiting_for_hmu = False
|
if hashes < 1:
|
||||||
self.request_next()
|
RNS.log("Invalid HMU received, cancelling transfer", RNS.LOG_ERROR)
|
||||||
|
self.cancel()
|
||||||
|
|
||||||
|
else:
|
||||||
|
self.waiting_for_hmu = False
|
||||||
|
self.request_next()
|
||||||
|
|
||||||
def get_map_hash(self, data):
|
def get_map_hash(self, data):
|
||||||
return RNS.Identity.full_hash(data+self.random_hash)[:Resource.MAPHASH_LEN]
|
return RNS.Identity.full_hash(data+self.random_hash)[:Resource.MAPHASH_LEN]
|
||||||
@@ -517,6 +517,14 @@ class Resource:
|
|||||||
prepare_thread = threading.Thread(target=self.__prepare_next_segment, daemon=True)
|
prepare_thread = threading.Thread(target=self.__prepare_next_segment, daemon=True)
|
||||||
prepare_thread.start()
|
prepare_thread.start()
|
||||||
|
|
||||||
|
def ensure_link(self):
|
||||||
|
if not self.link or self.link.status != RNS.Link.ACTIVE:
|
||||||
|
RNS.log(f"Invalid link state for {self}, aborting transfer", RNS.LOG_VERBOSE)
|
||||||
|
try: self.cancel()
|
||||||
|
except Exception as e: RNS.log(f"Error while cancelling resource on link-state abort: {e}", RNS.LOG_ERROR)
|
||||||
|
return False
|
||||||
|
else: return True
|
||||||
|
|
||||||
def __advertise_job(self):
|
def __advertise_job(self):
|
||||||
self.advertisement_packet = RNS.Packet(self.link, ResourceAdvertisement(self).pack(), context=RNS.Packet.RESOURCE_ADV)
|
self.advertisement_packet = RNS.Packet(self.link, ResourceAdvertisement(self).pack(), context=RNS.Packet.RESOURCE_ADV)
|
||||||
while not self.link.ready_for_new_resource():
|
while not self.link.ready_for_new_resource():
|
||||||
@@ -524,6 +532,7 @@ class Resource:
|
|||||||
sleep(0.25)
|
sleep(0.25)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
if not self.ensure_link(): return
|
||||||
self.advertisement_packet.send()
|
self.advertisement_packet.send()
|
||||||
self.last_activity = time.time()
|
self.last_activity = time.time()
|
||||||
self.started_transferring = self.last_activity
|
self.started_transferring = self.last_activity
|
||||||
@@ -541,18 +550,13 @@ class Resource:
|
|||||||
self.watchdog_job()
|
self.watchdog_job()
|
||||||
|
|
||||||
def update_eifr(self):
|
def update_eifr(self):
|
||||||
if self.rtt == None:
|
if self.rtt == None: rtt = self.link.rtt
|
||||||
rtt = self.link.rtt
|
else: rtt = self.rtt
|
||||||
else:
|
|
||||||
rtt = self.rtt
|
|
||||||
|
|
||||||
if self.req_data_rtt_rate != 0:
|
if self.req_data_rtt_rate != 0: expected_inflight_rate = self.req_data_rtt_rate*8
|
||||||
expected_inflight_rate = self.req_data_rtt_rate*8
|
|
||||||
else:
|
else:
|
||||||
if self.previous_eifr != None:
|
if self.previous_eifr != None: expected_inflight_rate = self.previous_eifr
|
||||||
expected_inflight_rate = self.previous_eifr
|
else: expected_inflight_rate = self.link.establishment_cost*8 / rtt
|
||||||
else:
|
|
||||||
expected_inflight_rate = self.link.establishment_cost*8 / rtt
|
|
||||||
|
|
||||||
self.eifr = expected_inflight_rate
|
self.eifr = expected_inflight_rate
|
||||||
if self.link: self.link.expected_rate = self.eifr
|
if self.link: self.link.expected_rate = self.eifr
|
||||||
@@ -566,8 +570,7 @@ class Resource:
|
|||||||
this_job_id = self.__watchdog_job_id
|
this_job_id = self.__watchdog_job_id
|
||||||
|
|
||||||
while self.status < Resource.ASSEMBLING and this_job_id == self.__watchdog_job_id:
|
while self.status < Resource.ASSEMBLING and this_job_id == self.__watchdog_job_id:
|
||||||
while self.watchdog_lock:
|
while self.watchdog_lock: sleep(0.025)
|
||||||
sleep(0.025)
|
|
||||||
|
|
||||||
sleep_time = None
|
sleep_time = None
|
||||||
if self.status == Resource.ADVERTISED:
|
if self.status == Resource.ADVERTISED:
|
||||||
@@ -581,6 +584,7 @@ class Resource:
|
|||||||
try:
|
try:
|
||||||
RNS.log("No part requests received, retrying resource advertisement...", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
|
RNS.log("No part requests received, retrying resource advertisement...", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
|
||||||
self.retries_left -= 1
|
self.retries_left -= 1
|
||||||
|
if not self.ensure_link(): return
|
||||||
self.advertisement_packet = RNS.Packet(self.link, ResourceAdvertisement(self).pack(), context=RNS.Packet.RESOURCE_ADV)
|
self.advertisement_packet = RNS.Packet(self.link, ResourceAdvertisement(self).pack(), context=RNS.Packet.RESOURCE_ADV)
|
||||||
self.advertisement_packet.send()
|
self.advertisement_packet.send()
|
||||||
self.last_activity = time.time()
|
self.last_activity = time.time()
|
||||||
@@ -657,13 +661,13 @@ class Resource:
|
|||||||
self.last_part_sent = time.time()
|
self.last_part_sent = time.time()
|
||||||
sleep_time = 0.001
|
sleep_time = 0.001
|
||||||
|
|
||||||
elif self.status == Resource.REJECTED:
|
elif self.status >= Resource.ASSEMBLING:
|
||||||
sleep_time = 0.001
|
sleep_time = 0.001
|
||||||
|
|
||||||
if sleep_time == 0:
|
if sleep_time == 0:
|
||||||
RNS.log("Warning! Link watchdog sleep time of 0!", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
|
RNS.log("Warning! Link watchdog sleep time of 0!", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
|
||||||
if sleep_time == None or sleep_time < 0:
|
if sleep_time == None or sleep_time < 0:
|
||||||
RNS.log("Timing error, cancelling resource transfer.", RNS.LOG_ERROR)
|
RNS.log(f"Timing error ({sleep_time}/{self.status}), cancelling resource transfer.", RNS.LOG_ERROR)
|
||||||
self.cancel()
|
self.cancel()
|
||||||
|
|
||||||
if sleep_time != None:
|
if sleep_time != None:
|
||||||
@@ -754,6 +758,7 @@ class Resource:
|
|||||||
try:
|
try:
|
||||||
proof = RNS.Identity.full_hash(self.data+self.hash)
|
proof = RNS.Identity.full_hash(self.data+self.hash)
|
||||||
proof_data = self.hash+proof
|
proof_data = self.hash+proof
|
||||||
|
if not self.ensure_link(): return
|
||||||
proof_packet = RNS.Packet(self.link, proof_data, packet_type=RNS.Packet.PROOF, context=RNS.Packet.RESOURCE_PRF)
|
proof_packet = RNS.Packet(self.link, proof_data, packet_type=RNS.Packet.PROOF, context=RNS.Packet.RESOURCE_PRF)
|
||||||
proof_packet.send()
|
proof_packet.send()
|
||||||
RNS.Transport.cache(proof_packet, force_cache=True)
|
RNS.Transport.cache(proof_packet, force_cache=True)
|
||||||
@@ -776,8 +781,8 @@ class Resource:
|
|||||||
advertise = False,
|
advertise = False,
|
||||||
auto_compress = self.auto_compress_option,
|
auto_compress = self.auto_compress_option,
|
||||||
sent_metadata_size = self.metadata_size)
|
sent_metadata_size = self.metadata_size)
|
||||||
if self.__progress_callback:
|
|
||||||
self.next_segment.progress_callback(self.__progress_callback)
|
if self.__progress_callback: self.next_segment.progress_callback(self.__progress_callback)
|
||||||
|
|
||||||
def validate_proof(self, proof_data):
|
def validate_proof(self, proof_data):
|
||||||
if not self.status == Resource.FAILED:
|
if not self.status == Resource.FAILED:
|
||||||
@@ -966,6 +971,7 @@ class Resource:
|
|||||||
request_packet = RNS.Packet(self.link, request_data, context = RNS.Packet.RESOURCE_REQ)
|
request_packet = RNS.Packet(self.link, request_data, context = RNS.Packet.RESOURCE_REQ)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
if not self.ensure_link(): return
|
||||||
request_packet.send()
|
request_packet.send()
|
||||||
self.last_activity = time.time()
|
self.last_activity = time.time()
|
||||||
self.req_sent = self.last_activity
|
self.req_sent = self.last_activity
|
||||||
@@ -1010,11 +1016,11 @@ class Resource:
|
|||||||
|
|
||||||
for part in requested_parts:
|
for part in requested_parts:
|
||||||
try:
|
try:
|
||||||
|
if not self.ensure_link(): return
|
||||||
if not part.sent:
|
if not part.sent:
|
||||||
part.send()
|
part.send()
|
||||||
self.sent_parts += 1
|
self.sent_parts += 1
|
||||||
else:
|
else: part.resend()
|
||||||
part.resend()
|
|
||||||
|
|
||||||
self.last_activity = time.time()
|
self.last_activity = time.time()
|
||||||
self.last_part_sent = self.last_activity
|
self.last_part_sent = self.last_activity
|
||||||
@@ -1032,8 +1038,7 @@ class Resource:
|
|||||||
search_end = self.receiver_min_consecutive_height+ResourceAdvertisement.COLLISION_GUARD_SIZE
|
search_end = self.receiver_min_consecutive_height+ResourceAdvertisement.COLLISION_GUARD_SIZE
|
||||||
for part in self.parts[search_start:search_end]:
|
for part in self.parts[search_start:search_end]:
|
||||||
part_index += 1
|
part_index += 1
|
||||||
if part.map_hash == last_map_hash:
|
if part.map_hash == last_map_hash: break
|
||||||
break
|
|
||||||
|
|
||||||
self.receiver_min_consecutive_height = max(part_index-1-Resource.WINDOW_MAX, 0)
|
self.receiver_min_consecutive_height = max(part_index-1-Resource.WINDOW_MAX, 0)
|
||||||
|
|
||||||
@@ -1041,9 +1046,7 @@ class Resource:
|
|||||||
RNS.log("Resource sequencing error, cancelling transfer!", RNS.LOG_ERROR)
|
RNS.log("Resource sequencing error, cancelling transfer!", RNS.LOG_ERROR)
|
||||||
self.cancel()
|
self.cancel()
|
||||||
return
|
return
|
||||||
else:
|
else: segment = part_index // ResourceAdvertisement.HASHMAP_MAX_LEN
|
||||||
segment = part_index // ResourceAdvertisement.HASHMAP_MAX_LEN
|
|
||||||
|
|
||||||
|
|
||||||
hashmap_start = segment*ResourceAdvertisement.HASHMAP_MAX_LEN
|
hashmap_start = segment*ResourceAdvertisement.HASHMAP_MAX_LEN
|
||||||
hashmap_end = min((segment+1)*ResourceAdvertisement.HASHMAP_MAX_LEN, len(self.parts))
|
hashmap_end = min((segment+1)*ResourceAdvertisement.HASHMAP_MAX_LEN, len(self.parts))
|
||||||
@@ -1052,10 +1055,16 @@ class Resource:
|
|||||||
for i in range(hashmap_start,hashmap_end):
|
for i in range(hashmap_start,hashmap_end):
|
||||||
hashmap += self.hashmap[i*Resource.MAPHASH_LEN:(i+1)*Resource.MAPHASH_LEN]
|
hashmap += self.hashmap[i*Resource.MAPHASH_LEN:(i+1)*Resource.MAPHASH_LEN]
|
||||||
|
|
||||||
|
if not hashmap:
|
||||||
|
RNS.log("Resource HMU error, cancelling transfer!", RNS.LOG_ERROR)
|
||||||
|
self.cancel()
|
||||||
|
return
|
||||||
|
|
||||||
hmu = self.hash+umsgpack.packb([segment, hashmap])
|
hmu = self.hash+umsgpack.packb([segment, hashmap])
|
||||||
hmu_packet = RNS.Packet(self.link, hmu, context = RNS.Packet.RESOURCE_HMU)
|
hmu_packet = RNS.Packet(self.link, hmu, context = RNS.Packet.RESOURCE_HMU)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
if not self.ensure_link(): return
|
||||||
hmu_packet.send()
|
hmu_packet.send()
|
||||||
self.last_activity = time.time()
|
self.last_activity = time.time()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -1090,10 +1099,14 @@ class Resource:
|
|||||||
try:
|
try:
|
||||||
cancel_packet = RNS.Packet(self.link, self.hash, context=RNS.Packet.RESOURCE_ICL)
|
cancel_packet = RNS.Packet(self.link, self.hash, context=RNS.Packet.RESOURCE_ICL)
|
||||||
cancel_packet.send()
|
cancel_packet.send()
|
||||||
except Exception as e:
|
except Exception as e: RNS.log("Could not send resource cancel packet, the contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||||
RNS.log("Could not send resource cancel packet, the contained exception was: "+str(e), RNS.LOG_ERROR)
|
|
||||||
self.link.cancel_outgoing_resource(self)
|
self.link.cancel_outgoing_resource(self)
|
||||||
else:
|
else:
|
||||||
|
if self.link.status == RNS.Link.ACTIVE:
|
||||||
|
try:
|
||||||
|
cancel_packet = RNS.Packet(self.link, self.hash, context=RNS.Packet.RESOURCE_RCL)
|
||||||
|
cancel_packet.send()
|
||||||
|
except Exception as e: RNS.log("Could not send resource cancel packet, the contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||||
self.link.cancel_incoming_resource(self)
|
self.link.cancel_incoming_resource(self)
|
||||||
|
|
||||||
if self.callback != None:
|
if self.callback != None:
|
||||||
@@ -1241,40 +1254,30 @@ class ResourceAdvertisement:
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def is_request(advertisement_packet):
|
def is_request(advertisement_packet):
|
||||||
adv = ResourceAdvertisement.unpack(advertisement_packet.plaintext)
|
adv = ResourceAdvertisement.unpack(advertisement_packet.plaintext)
|
||||||
if adv.q != None and adv.u:
|
if adv.q != None and adv.u: return True
|
||||||
return True
|
else: return False
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def is_response(advertisement_packet):
|
def is_response(advertisement_packet):
|
||||||
adv = ResourceAdvertisement.unpack(advertisement_packet.plaintext)
|
adv = ResourceAdvertisement.unpack(advertisement_packet.plaintext)
|
||||||
|
if adv.q != None and adv.p: return True
|
||||||
if adv.q != None and adv.p:
|
else: return False
|
||||||
return True
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def read_request_id(advertisement_packet):
|
def read_request_id(advertisement_packet):
|
||||||
adv = ResourceAdvertisement.unpack(advertisement_packet.plaintext)
|
adv = ResourceAdvertisement.unpack(advertisement_packet.plaintext)
|
||||||
return adv.q
|
return adv.q
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def read_transfer_size(advertisement_packet):
|
def read_transfer_size(advertisement_packet):
|
||||||
adv = ResourceAdvertisement.unpack(advertisement_packet.plaintext)
|
adv = ResourceAdvertisement.unpack(advertisement_packet.plaintext)
|
||||||
return adv.t
|
return adv.t
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def read_size(advertisement_packet):
|
def read_size(advertisement_packet):
|
||||||
adv = ResourceAdvertisement.unpack(advertisement_packet.plaintext)
|
adv = ResourceAdvertisement.unpack(advertisement_packet.plaintext)
|
||||||
return adv.d
|
return adv.d
|
||||||
|
|
||||||
|
|
||||||
def __init__(self, resource=None, request_id=None, is_response=False):
|
def __init__(self, resource=None, request_id=None, is_response=False):
|
||||||
self.link = None
|
self.link = None
|
||||||
if resource != None:
|
if resource != None:
|
||||||
@@ -1306,29 +1309,14 @@ class ResourceAdvertisement:
|
|||||||
# Flags
|
# Flags
|
||||||
self.f = 0x00 | self.x << 5 | self.p << 4 | self.u << 3 | self.s << 2 | self.c << 1 | self.e
|
self.f = 0x00 | self.x << 5 | self.p << 4 | self.u << 3 | self.s << 2 | self.c << 1 | self.e
|
||||||
|
|
||||||
def get_transfer_size(self):
|
def get_transfer_size(self): return self.t
|
||||||
return self.t
|
def get_data_size(self): return self.d
|
||||||
|
def get_parts(self): return self.n
|
||||||
def get_data_size(self):
|
def get_segments(self): return self.l
|
||||||
return self.d
|
def get_hash(self): return self.h
|
||||||
|
def is_compressed(self): return self.c
|
||||||
def get_parts(self):
|
def has_metadata(self): return self.x
|
||||||
return self.n
|
def get_link(self): return self.link
|
||||||
|
|
||||||
def get_segments(self):
|
|
||||||
return self.l
|
|
||||||
|
|
||||||
def get_hash(self):
|
|
||||||
return self.h
|
|
||||||
|
|
||||||
def is_compressed(self):
|
|
||||||
return self.c
|
|
||||||
|
|
||||||
def has_metadata(self):
|
|
||||||
return self.x
|
|
||||||
|
|
||||||
def get_link(self):
|
|
||||||
return self.link
|
|
||||||
|
|
||||||
def pack(self, segment=0):
|
def pack(self, segment=0):
|
||||||
hashmap_start = segment*ResourceAdvertisement.HASHMAP_MAX_LEN
|
hashmap_start = segment*ResourceAdvertisement.HASHMAP_MAX_LEN
|
||||||
@@ -1338,23 +1326,20 @@ class ResourceAdvertisement:
|
|||||||
for i in range(hashmap_start,hashmap_end):
|
for i in range(hashmap_start,hashmap_end):
|
||||||
hashmap += self.m[i*Resource.MAPHASH_LEN:(i+1)*Resource.MAPHASH_LEN]
|
hashmap += self.m[i*Resource.MAPHASH_LEN:(i+1)*Resource.MAPHASH_LEN]
|
||||||
|
|
||||||
dictionary = {
|
dictionary = { "t": self.t, # Transfer size
|
||||||
"t": self.t, # Transfer size
|
"d": self.d, # Data size
|
||||||
"d": self.d, # Data size
|
"n": self.n, # Number of parts
|
||||||
"n": self.n, # Number of parts
|
"h": self.h, # Resource hash
|
||||||
"h": self.h, # Resource hash
|
"r": self.r, # Resource random hash
|
||||||
"r": self.r, # Resource random hash
|
"o": self.o, # Original hash
|
||||||
"o": self.o, # Original hash
|
"i": self.i, # Segment index
|
||||||
"i": self.i, # Segment index
|
"l": self.l, # Total segments
|
||||||
"l": self.l, # Total segments
|
"q": self.q, # Request ID
|
||||||
"q": self.q, # Request ID
|
"f": self.f, # Resource flags
|
||||||
"f": self.f, # Resource flags
|
"m": hashmap }
|
||||||
"m": hashmap
|
|
||||||
}
|
|
||||||
|
|
||||||
return umsgpack.packb(dictionary)
|
return umsgpack.packb(dictionary)
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def unpack(data):
|
def unpack(data):
|
||||||
dictionary = umsgpack.unpackb(data)
|
dictionary = umsgpack.unpackb(data)
|
||||||
@@ -1378,4 +1363,6 @@ class ResourceAdvertisement:
|
|||||||
adv.p = True if ((adv.f >> 4) & 0x01) == 0x01 else False
|
adv.p = True if ((adv.f >> 4) & 0x01) == 0x01 else False
|
||||||
adv.x = True if ((adv.f >> 5) & 0x01) == 0x01 else False
|
adv.x = True if ((adv.f >> 5) & 0x01) == 0x01 else False
|
||||||
|
|
||||||
|
if adv.t > Resource.MAX_EFFICIENT_SIZE*3: raise ValueError("Invalid transfer size")
|
||||||
|
|
||||||
return adv
|
return adv
|
||||||
+237
-256
@@ -48,6 +48,7 @@ else:
|
|||||||
|
|
||||||
from RNS.vendor.configobj import ConfigObj
|
from RNS.vendor.configobj import ConfigObj
|
||||||
from threading import Lock
|
from threading import Lock
|
||||||
|
import RNS.vendor.umsgpack as mp
|
||||||
import configparser
|
import configparser
|
||||||
import multiprocessing.connection
|
import multiprocessing.connection
|
||||||
import importlib.util
|
import importlib.util
|
||||||
@@ -251,6 +252,8 @@ class Reticulum:
|
|||||||
|
|
||||||
Reticulum.__network_identity = None
|
Reticulum.__network_identity = None
|
||||||
Reticulum.__transport_enabled = False
|
Reticulum.__transport_enabled = False
|
||||||
|
Reticulum.__static_transport_identity = False
|
||||||
|
Reticulum.__local_hops_delta = False
|
||||||
Reticulum.__link_mtu_discovery = Reticulum.LINK_MTU_DISCOVERY
|
Reticulum.__link_mtu_discovery = Reticulum.LINK_MTU_DISCOVERY
|
||||||
Reticulum.__remote_management_enabled = False
|
Reticulum.__remote_management_enabled = False
|
||||||
Reticulum.__use_implicit_proof = True
|
Reticulum.__use_implicit_proof = True
|
||||||
@@ -346,7 +349,7 @@ class Reticulum:
|
|||||||
self.rpc_type = "AF_INET"
|
self.rpc_type = "AF_INET"
|
||||||
|
|
||||||
if self.rpc_key == None:
|
if self.rpc_key == None:
|
||||||
self.rpc_key = RNS.Identity.full_hash(RNS.Transport.identity.get_private_key())
|
self.rpc_key = RNS.Identity.full_hash(RNS.Transport.internal_identity().get_private_key())
|
||||||
|
|
||||||
if self.is_shared_instance:
|
if self.is_shared_instance:
|
||||||
self.rpc_listener = multiprocessing.connection.Listener(self.rpc_addr, family=self.rpc_type, authkey=self.rpc_key)
|
self.rpc_listener = multiprocessing.connection.Listener(self.rpc_addr, family=self.rpc_type, authkey=self.rpc_key)
|
||||||
@@ -385,11 +388,7 @@ class Reticulum:
|
|||||||
def __start_local_interface(self):
|
def __start_local_interface(self):
|
||||||
if self.share_instance:
|
if self.share_instance:
|
||||||
try:
|
try:
|
||||||
interface = LocalInterface.LocalServerInterface(
|
interface = LocalInterface.LocalServerInterface(RNS.Transport, self.local_interface_port, socket_path=self.local_socket_path)
|
||||||
RNS.Transport,
|
|
||||||
self.local_interface_port,
|
|
||||||
socket_path=self.local_socket_path
|
|
||||||
)
|
|
||||||
interface.OUT = True
|
interface.OUT = True
|
||||||
if hasattr(Reticulum, "_force_shared_instance_bitrate"):
|
if hasattr(Reticulum, "_force_shared_instance_bitrate"):
|
||||||
interface.bitrate = Reticulum._force_shared_instance_bitrate
|
interface.bitrate = Reticulum._force_shared_instance_bitrate
|
||||||
@@ -431,6 +430,7 @@ class Reticulum:
|
|||||||
Reticulum.__remote_management_enabled = False
|
Reticulum.__remote_management_enabled = False
|
||||||
Reticulum.__allow_probes = False
|
Reticulum.__allow_probes = False
|
||||||
RNS.log("Connected to locally available Reticulum instance via: "+str(interface), RNS.LOG_DEBUG)
|
RNS.log("Connected to locally available Reticulum instance via: "+str(interface), RNS.LOG_DEBUG)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
RNS.log("Local shared instance appears to be running, but it could not be connected", RNS.LOG_ERROR)
|
RNS.log("Local shared instance appears to be running, but it could not be connected", RNS.LOG_ERROR)
|
||||||
RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR)
|
RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||||
@@ -499,6 +499,14 @@ class Reticulum:
|
|||||||
v = self.config["reticulum"].as_bool(option)
|
v = self.config["reticulum"].as_bool(option)
|
||||||
if v == True: Reticulum.__transport_enabled = True
|
if v == True: Reticulum.__transport_enabled = True
|
||||||
|
|
||||||
|
if option == "static_transport_identity":
|
||||||
|
v = self.config["reticulum"].as_bool(option)
|
||||||
|
if v == True: Reticulum.__static_transport_identity = True
|
||||||
|
|
||||||
|
if option == "local_hops_delta":
|
||||||
|
v = self.config["reticulum"].as_bool(option)
|
||||||
|
if v == True: Reticulum.__local_hops_delta = True
|
||||||
|
|
||||||
if option == "network_identity":
|
if option == "network_identity":
|
||||||
if Reticulum.__network_identity == None:
|
if Reticulum.__network_identity == None:
|
||||||
path = self.config["reticulum"][option]
|
path = self.config["reticulum"][option]
|
||||||
@@ -668,6 +676,9 @@ class Reticulum:
|
|||||||
self.shared_instance_type = "tcp"
|
self.shared_instance_type = "tcp"
|
||||||
self.use_af_unix = False
|
self.use_af_unix = False
|
||||||
|
|
||||||
|
if self.shared_instance_type == "tcp":
|
||||||
|
self.local_socket_path = None
|
||||||
|
|
||||||
if self.local_socket_path == None and self.use_af_unix:
|
if self.local_socket_path == None and self.use_af_unix:
|
||||||
self.local_socket_path = "default"
|
self.local_socket_path = "default"
|
||||||
|
|
||||||
@@ -706,6 +717,8 @@ class Reticulum:
|
|||||||
interface_mode = Interface.Interface.MODE_BOUNDARY
|
interface_mode = Interface.Interface.MODE_BOUNDARY
|
||||||
elif c["mode"] == "gateway" or c["mode"] == "gw":
|
elif c["mode"] == "gateway" or c["mode"] == "gw":
|
||||||
interface_mode = Interface.Interface.MODE_GATEWAY
|
interface_mode = Interface.Interface.MODE_GATEWAY
|
||||||
|
elif c["mode"] == "internal":
|
||||||
|
interface_mode = Interface.Interface.MODE_INTERNAL
|
||||||
|
|
||||||
elif "mode" in c:
|
elif "mode" in c:
|
||||||
c["mode"] = str(c["mode"]).lower()
|
c["mode"] = str(c["mode"]).lower()
|
||||||
@@ -721,27 +734,24 @@ class Reticulum:
|
|||||||
interface_mode = Interface.Interface.MODE_BOUNDARY
|
interface_mode = Interface.Interface.MODE_BOUNDARY
|
||||||
elif c["mode"] == "gateway" or c["mode"] == "gw":
|
elif c["mode"] == "gateway" or c["mode"] == "gw":
|
||||||
interface_mode = Interface.Interface.MODE_GATEWAY
|
interface_mode = Interface.Interface.MODE_GATEWAY
|
||||||
|
elif c["mode"] == "internal":
|
||||||
|
interface_mode = Interface.Interface.MODE_INTERNAL
|
||||||
|
|
||||||
ifac_size = None
|
ifac_size = None
|
||||||
if "ifac_size" in c:
|
if "ifac_size" in c:
|
||||||
if c.as_int("ifac_size") >= Reticulum.IFAC_MIN_SIZE*8:
|
if c.as_int("ifac_size") >= Reticulum.IFAC_MIN_SIZE*8: ifac_size = c.as_int("ifac_size")//8
|
||||||
ifac_size = c.as_int("ifac_size")//8
|
|
||||||
|
|
||||||
ifac_netname = None
|
ifac_netname = None
|
||||||
if "networkname" in c:
|
if "networkname" in c:
|
||||||
if c["networkname"] != "":
|
if c["networkname"] != "": ifac_netname = c["networkname"]
|
||||||
ifac_netname = c["networkname"]
|
|
||||||
if "network_name" in c:
|
if "network_name" in c:
|
||||||
if c["network_name"] != "":
|
if c["network_name"] != "": ifac_netname = c["network_name"]
|
||||||
ifac_netname = c["network_name"]
|
|
||||||
|
|
||||||
ifac_netkey = None
|
ifac_netkey = None
|
||||||
if "passphrase" in c:
|
if "passphrase" in c:
|
||||||
if c["passphrase"] != "":
|
if c["passphrase"] != "": ifac_netkey = c["passphrase"]
|
||||||
ifac_netkey = c["passphrase"]
|
|
||||||
if "pass_phrase" in c:
|
if "pass_phrase" in c:
|
||||||
if c["pass_phrase"] != "":
|
if c["pass_phrase"] != "": ifac_netkey = c["pass_phrase"]
|
||||||
ifac_netkey = c["pass_phrase"]
|
|
||||||
|
|
||||||
ingress_control = True
|
ingress_control = True
|
||||||
if "ingress_control" in c: ingress_control = c.as_bool("ingress_control")
|
if "ingress_control" in c: ingress_control = c.as_bool("ingress_control")
|
||||||
@@ -770,29 +780,22 @@ class Reticulum:
|
|||||||
|
|
||||||
configured_bitrate = None
|
configured_bitrate = None
|
||||||
if "bitrate" in c:
|
if "bitrate" in c:
|
||||||
if c.as_int("bitrate") >= Reticulum.MINIMUM_BITRATE:
|
if c.as_int("bitrate") >= Reticulum.MINIMUM_BITRATE: configured_bitrate = c.as_int("bitrate")
|
||||||
configured_bitrate = c.as_int("bitrate")
|
|
||||||
|
|
||||||
announce_rate_target = None
|
announce_rate_target = None
|
||||||
if "announce_rate_target" in c:
|
if "announce_rate_target" in c:
|
||||||
if c.as_int("announce_rate_target") > 0:
|
if c.as_int("announce_rate_target") > 0: announce_rate_target = c.as_int("announce_rate_target")
|
||||||
announce_rate_target = c.as_int("announce_rate_target")
|
|
||||||
|
|
||||||
announce_rate_grace = None
|
announce_rate_grace = None
|
||||||
if "announce_rate_grace" in c:
|
if "announce_rate_grace" in c:
|
||||||
if c.as_int("announce_rate_grace") >= 0:
|
if c.as_int("announce_rate_grace") >= 0: announce_rate_grace = c.as_int("announce_rate_grace")
|
||||||
announce_rate_grace = c.as_int("announce_rate_grace")
|
|
||||||
|
|
||||||
announce_rate_penalty = None
|
announce_rate_penalty = None
|
||||||
if "announce_rate_penalty" in c:
|
if "announce_rate_penalty" in c:
|
||||||
if c.as_int("announce_rate_penalty") >= 0:
|
if c.as_int("announce_rate_penalty") >= 0: announce_rate_penalty = c.as_int("announce_rate_penalty")
|
||||||
announce_rate_penalty = c.as_int("announce_rate_penalty")
|
|
||||||
|
|
||||||
if announce_rate_target != None and announce_rate_grace == None:
|
if announce_rate_target != None and announce_rate_grace == None: announce_rate_grace = 0
|
||||||
announce_rate_grace = 0
|
if announce_rate_target != None and announce_rate_penalty == None: announce_rate_penalty = 0
|
||||||
|
|
||||||
if announce_rate_target != None and announce_rate_penalty == None:
|
|
||||||
announce_rate_penalty = 0
|
|
||||||
|
|
||||||
announce_cap = Reticulum.ANNOUNCE_CAP/100.0
|
announce_cap = Reticulum.ANNOUNCE_CAP/100.0
|
||||||
if "announce_cap" in c:
|
if "announce_cap" in c:
|
||||||
@@ -802,6 +805,12 @@ class Reticulum:
|
|||||||
bootstrap_only = False
|
bootstrap_only = False
|
||||||
if "bootstrap_only" in c: bootstrap_only = c.as_bool("bootstrap_only")
|
if "bootstrap_only" in c: bootstrap_only = c.as_bool("bootstrap_only")
|
||||||
|
|
||||||
|
recursive_prs = False
|
||||||
|
if "recursive_prs" in c: recursive_prs = c.as_bool("recursive_prs")
|
||||||
|
|
||||||
|
announces_from_internal = True
|
||||||
|
if "announces_from_internal" in c: announces_from_internal = c.as_bool("announces_from_internal")
|
||||||
|
|
||||||
ignore_config_warnings = False
|
ignore_config_warnings = False
|
||||||
if "ignore_config_warnings" in c: ignore_config_warnings = c.as_bool("ignore_config_warnings")
|
if "ignore_config_warnings" in c: ignore_config_warnings = c.as_bool("ignore_config_warnings")
|
||||||
|
|
||||||
@@ -820,6 +829,7 @@ class Reticulum:
|
|||||||
latitude = None
|
latitude = None
|
||||||
longitude = None
|
longitude = None
|
||||||
height = None
|
height = None
|
||||||
|
discovery_location = None
|
||||||
discovery_frequency = None
|
discovery_frequency = None
|
||||||
discovery_bandwidth = None
|
discovery_bandwidth = None
|
||||||
discovery_modulation = None
|
discovery_modulation = None
|
||||||
@@ -837,6 +847,7 @@ class Reticulum:
|
|||||||
if "discovery_encrypt" in c: discovery_encrypt = c.as_bool("discovery_encrypt")
|
if "discovery_encrypt" in c: discovery_encrypt = c.as_bool("discovery_encrypt")
|
||||||
if "reachable_on" in c: reachable_on = c["reachable_on"]
|
if "reachable_on" in c: reachable_on = c["reachable_on"]
|
||||||
if "publish_ifac" in c: publish_ifac = c.as_bool("publish_ifac")
|
if "publish_ifac" in c: publish_ifac = c.as_bool("publish_ifac")
|
||||||
|
if "location_cmd" in c: discovery_location = c["location_cmd"]
|
||||||
if "latitude" in c: latitude = c.as_float("latitude")
|
if "latitude" in c: latitude = c.as_float("latitude")
|
||||||
if "longitude" in c: longitude = c.as_float("longitude")
|
if "longitude" in c: longitude = c.as_float("longitude")
|
||||||
if "height" in c: height = c.as_float("height")
|
if "height" in c: height = c.as_float("height")
|
||||||
@@ -844,14 +855,14 @@ class Reticulum:
|
|||||||
if "discovery_bandwidth" in c: discovery_bandwidth = c.as_int("discovery_bandwidth")
|
if "discovery_bandwidth" in c: discovery_bandwidth = c.as_int("discovery_bandwidth")
|
||||||
if "discovery_modulation" in c: discovery_modulation = c.as_int("discovery_modulation")
|
if "discovery_modulation" in c: discovery_modulation = c.as_int("discovery_modulation")
|
||||||
|
|
||||||
if not interface_mode in [Interface.Interface.MODE_GATEWAY, Interface.Interface.MODE_ACCESS_POINT]:
|
if not interface_mode in [Interface.Interface.MODE_GATEWAY, Interface.Interface.MODE_ACCESS_POINT, Interface.Interface.MODE_INTERNAL]:
|
||||||
if not ignore_config_warnings:
|
if not ignore_config_warnings:
|
||||||
if c["type"] in ["RNodeInterface", "RNodeMultiInterface"]:
|
if c["type"] in ["RNodeInterface", "RNodeMultiInterface"]:
|
||||||
interface_mode = Interface.Interface.MODE_ACCESS_POINT
|
interface_mode = Interface.Interface.MODE_ACCESS_POINT
|
||||||
RNS.log(f"Discovery enabled on interface {name} without gateway or AP mode. Auto-configured to AP mode.", RNS.LOG_NOTICE)
|
RNS.log(f"Discovery enabled on interface {name} without gateway, internal or AP mode. Auto-configured to AP mode.", RNS.LOG_NOTICE)
|
||||||
else:
|
else:
|
||||||
interface_mode = Interface.Interface.MODE_GATEWAY
|
interface_mode = Interface.Interface.MODE_GATEWAY
|
||||||
RNS.log(f"Discovery enabled on interface {name} without gateway or AP mode. Auto-configured to gateway mode.", RNS.LOG_NOTICE)
|
RNS.log(f"Discovery enabled on interface {name} without gateway, internal or AP mode. Auto-configured to gateway mode.", RNS.LOG_NOTICE)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
def interface_post_init(interface):
|
def interface_post_init(interface):
|
||||||
@@ -868,38 +879,42 @@ class Reticulum:
|
|||||||
if ifac_size != None: interface.ifac_size = ifac_size
|
if ifac_size != None: interface.ifac_size = ifac_size
|
||||||
else: interface.ifac_size = interface.DEFAULT_IFAC_SIZE
|
else: interface.ifac_size = interface.DEFAULT_IFAC_SIZE
|
||||||
|
|
||||||
interface.discoverable = discoverable
|
interface.discoverable = discoverable
|
||||||
interface.discovery_announce_interval = discovery_announce_interval
|
interface.discovery_announce_interval = discovery_announce_interval
|
||||||
interface.discovery_publish_ifac = publish_ifac
|
interface.discovery_publish_ifac = publish_ifac
|
||||||
interface.reachable_on = reachable_on
|
interface.reachable_on = reachable_on
|
||||||
interface.discovery_name = discovery_name
|
interface.discovery_name = discovery_name
|
||||||
interface.discovery_encrypt = discovery_encrypt
|
interface.discovery_encrypt = discovery_encrypt
|
||||||
interface.discovery_stamp_value = discovery_stamp_value
|
interface.discovery_stamp_value = discovery_stamp_value
|
||||||
interface.discovery_latitude = latitude
|
interface.discovery_location = discovery_location
|
||||||
interface.discovery_longitude = longitude
|
interface.discovery_latitude = latitude
|
||||||
interface.discovery_height = height
|
interface.discovery_longitude = longitude
|
||||||
interface.discovery_frequency = discovery_frequency
|
interface.discovery_height = height
|
||||||
interface.discovery_bandwidth = discovery_bandwidth
|
interface.discovery_frequency = discovery_frequency
|
||||||
interface.discovery_modulation = discovery_modulation
|
interface.discovery_bandwidth = discovery_bandwidth
|
||||||
|
interface.discovery_modulation = discovery_modulation
|
||||||
|
|
||||||
interface.announce_rate_target = announce_rate_target
|
interface.recursive_prs = recursive_prs
|
||||||
interface.announce_rate_grace = announce_rate_grace
|
interface.announces_from_internal = announces_from_internal
|
||||||
interface.announce_rate_penalty = announce_rate_penalty
|
interface.announce_rate_target = announce_rate_target
|
||||||
interface.ingress_control = ingress_control
|
interface.announce_rate_grace = announce_rate_grace
|
||||||
if egress_control != None: interface.egress_control = egress_control
|
interface.announce_rate_penalty = announce_rate_penalty
|
||||||
if ic_max_held_announces != None: interface.ic_max_held_announces = ic_max_held_announces
|
interface.ingress_control = ingress_control
|
||||||
if ic_burst_hold != None: interface.ic_burst_hold = ic_burst_hold
|
|
||||||
if ic_burst_freq_new != None: interface.ic_burst_freq_new = ic_burst_freq_new
|
if egress_control != None: interface.egress_control = egress_control
|
||||||
if ic_burst_freq != None: interface.ic_burst_freq = ic_burst_freq
|
if ic_max_held_announces != None: interface.ic_max_held_announces = ic_max_held_announces
|
||||||
if ic_pr_burst_freq_new != None: interface.ic_pr_burst_freq_new = ic_pr_burst_freq_new
|
if ic_burst_hold != None: interface.ic_burst_hold = ic_burst_hold
|
||||||
if ic_pr_burst_freq != None: interface.ic_pr_burst_freq = ic_pr_burst_freq
|
if ic_burst_freq_new != None: interface.ic_burst_freq_new = ic_burst_freq_new
|
||||||
if ec_pr_freq != None: interface.ec_pr_freq = ec_pr_freq
|
if ic_burst_freq != None: interface.ic_burst_freq = ic_burst_freq
|
||||||
if ic_new_time != None: interface.ic_new_time = ic_new_time
|
if ic_pr_burst_freq_new != None: interface.ic_pr_burst_freq_new = ic_pr_burst_freq_new
|
||||||
if ic_burst_penalty != None: interface.ic_burst_penalty = ic_burst_penalty
|
if ic_pr_burst_freq != None: interface.ic_pr_burst_freq = ic_pr_burst_freq
|
||||||
|
if ec_pr_freq != None: interface.ec_pr_freq = ec_pr_freq
|
||||||
|
if ic_new_time != None: interface.ic_new_time = ic_new_time
|
||||||
|
if ic_burst_penalty != None: interface.ic_burst_penalty = ic_burst_penalty
|
||||||
if ic_held_release_interval != None: interface.ic_held_release_interval = ic_held_release_interval
|
if ic_held_release_interval != None: interface.ic_held_release_interval = ic_held_release_interval
|
||||||
|
|
||||||
interface.ifac_netname = ifac_netname
|
interface.ifac_netname = ifac_netname
|
||||||
interface.ifac_netkey = ifac_netkey
|
interface.ifac_netkey = ifac_netkey
|
||||||
|
|
||||||
if interface.ifac_netname != None or interface.ifac_netkey != None:
|
if interface.ifac_netname != None or interface.ifac_netkey != None:
|
||||||
ifac_origin = b""
|
ifac_origin = b""
|
||||||
@@ -911,12 +926,8 @@ class Reticulum:
|
|||||||
ifac_origin += RNS.Identity.full_hash(interface.ifac_netkey.encode("utf-8"))
|
ifac_origin += RNS.Identity.full_hash(interface.ifac_netkey.encode("utf-8"))
|
||||||
|
|
||||||
ifac_origin_hash = RNS.Identity.full_hash(ifac_origin)
|
ifac_origin_hash = RNS.Identity.full_hash(ifac_origin)
|
||||||
interface.ifac_key = RNS.Cryptography.hkdf(
|
interface.ifac_key = RNS.Cryptography.hkdf(length=64, derive_from=ifac_origin_hash,
|
||||||
length=64,
|
salt=self.ifac_salt, context=None)
|
||||||
derive_from=ifac_origin_hash,
|
|
||||||
salt=self.ifac_salt,
|
|
||||||
context=None
|
|
||||||
)
|
|
||||||
|
|
||||||
interface.ifac_identity = RNS.Identity.from_bytes(interface.ifac_key)
|
interface.ifac_identity = RNS.Identity.from_bytes(interface.ifac_key)
|
||||||
interface.ifac_signature = interface.ifac_identity.sign(RNS.Identity.full_hash(interface.ifac_key))
|
interface.ifac_signature = interface.ifac_identity.sign(RNS.Identity.full_hash(interface.ifac_key))
|
||||||
@@ -1040,7 +1051,9 @@ class Reticulum:
|
|||||||
RNS.panic()
|
RNS.panic()
|
||||||
|
|
||||||
def _add_interface(self, interface, mode = None, configured_bitrate=None, ifac_size=None, ifac_netname=None, ifac_netkey=None,
|
def _add_interface(self, interface, mode = None, configured_bitrate=None, ifac_size=None, ifac_netname=None, ifac_netkey=None,
|
||||||
announce_cap=None, announce_rate_target=None, announce_rate_grace=None, announce_rate_penalty=None, bootstrap_only=False):
|
announce_cap=None, announce_rate_target=None, announce_rate_grace=None, announce_rate_penalty=None,
|
||||||
|
bootstrap_only=False, recursive_prs=False, announces_from_internal=True):
|
||||||
|
|
||||||
if not self.is_connected_to_shared_instance:
|
if not self.is_connected_to_shared_instance:
|
||||||
if interface != None and issubclass(type(interface), RNS.Interfaces.Interface.Interface):
|
if interface != None and issubclass(type(interface), RNS.Interfaces.Interface.Interface):
|
||||||
|
|
||||||
@@ -1055,13 +1068,15 @@ class Reticulum:
|
|||||||
if ifac_size != None: interface.ifac_size = ifac_size
|
if ifac_size != None: interface.ifac_size = ifac_size
|
||||||
else: interface.ifac_size = interface.DEFAULT_IFAC_SIZE
|
else: interface.ifac_size = interface.DEFAULT_IFAC_SIZE
|
||||||
|
|
||||||
interface.announce_cap = announce_cap if announce_cap != None else Reticulum.ANNOUNCE_CAP/100.0
|
interface.recursive_prs = recursive_prs
|
||||||
interface.announce_rate_target = announce_rate_target
|
interface.announces_from_internal = announces_from_internal
|
||||||
interface.announce_rate_grace = announce_rate_grace
|
interface.announce_cap = announce_cap if announce_cap != None else Reticulum.ANNOUNCE_CAP/100.0
|
||||||
interface.announce_rate_penalty = announce_rate_penalty
|
interface.announce_rate_target = announce_rate_target
|
||||||
|
interface.announce_rate_grace = announce_rate_grace
|
||||||
|
interface.announce_rate_penalty = announce_rate_penalty
|
||||||
|
|
||||||
interface.ifac_netname = ifac_netname
|
interface.ifac_netname = ifac_netname
|
||||||
interface.ifac_netkey = ifac_netkey
|
interface.ifac_netkey = ifac_netkey
|
||||||
|
|
||||||
if interface.ifac_netname != None or interface.ifac_netkey != None:
|
if interface.ifac_netname != None or interface.ifac_netkey != None:
|
||||||
ifac_origin = b""
|
ifac_origin = b""
|
||||||
@@ -1073,12 +1088,8 @@ class Reticulum:
|
|||||||
ifac_origin += RNS.Identity.full_hash(interface.ifac_netkey.encode("utf-8"))
|
ifac_origin += RNS.Identity.full_hash(interface.ifac_netkey.encode("utf-8"))
|
||||||
|
|
||||||
ifac_origin_hash = RNS.Identity.full_hash(ifac_origin)
|
ifac_origin_hash = RNS.Identity.full_hash(ifac_origin)
|
||||||
interface.ifac_key = RNS.Cryptography.hkdf(
|
interface.ifac_key = RNS.Cryptography.hkdf(length=64, derive_from=ifac_origin_hash,
|
||||||
length=64,
|
salt=self.ifac_salt, context=None)
|
||||||
derive_from=ifac_origin_hash,
|
|
||||||
salt=self.ifac_salt,
|
|
||||||
context=None
|
|
||||||
)
|
|
||||||
|
|
||||||
interface.ifac_identity = RNS.Identity.from_bytes(interface.ifac_key)
|
interface.ifac_identity = RNS.Identity.from_bytes(interface.ifac_key)
|
||||||
interface.ifac_signature = interface.ifac_identity.sign(RNS.Identity.full_hash(interface.ifac_key))
|
interface.ifac_signature = interface.ifac_identity.sign(RNS.Identity.full_hash(interface.ifac_key))
|
||||||
@@ -1175,64 +1186,66 @@ class Reticulum:
|
|||||||
self.config = ConfigObj(__default_rns_config__)
|
self.config = ConfigObj(__default_rns_config__)
|
||||||
self.config.filename = Reticulum.configpath
|
self.config.filename = Reticulum.configpath
|
||||||
|
|
||||||
if not os.path.isdir(Reticulum.configdir):
|
if not os.path.isdir(Reticulum.configdir): os.makedirs(Reticulum.configdir)
|
||||||
os.makedirs(Reticulum.configdir)
|
|
||||||
self.config.write()
|
self.config.write()
|
||||||
|
|
||||||
|
def rpc_return(self, connection, response):
|
||||||
|
connection.send_bytes(mp.packb(response))
|
||||||
|
|
||||||
def rpc_loop(self):
|
def rpc_loop(self):
|
||||||
while RNS.Transport._should_run:
|
while RNS.Transport._should_run:
|
||||||
try:
|
try:
|
||||||
rpc_connection = self.rpc_listener.accept()
|
conn = self.rpc_listener.accept()
|
||||||
call = rpc_connection.recv()
|
call = mp.unpackb(conn.recv_bytes())
|
||||||
|
|
||||||
if "get" in call:
|
if "get" in call:
|
||||||
path = call["get"]
|
path = call["get"]
|
||||||
|
|
||||||
if path == "path_table":
|
if path == "path_table":
|
||||||
mh = call["max_hops"]
|
mh = call["max_hops"]
|
||||||
rpc_connection.send(self.get_path_table(max_hops=mh))
|
self.rpc_return(conn, self.get_path_table(max_hops=mh))
|
||||||
|
|
||||||
if path == "interface_stats": rpc_connection.send(self.get_interface_stats())
|
if path == "interface_stats": self.rpc_return(conn, self.get_interface_stats())
|
||||||
if path == "rate_table": rpc_connection.send(self.get_rate_table())
|
if path == "rate_table": self.rpc_return(conn, self.get_rate_table())
|
||||||
if path == "next_hop_if_name": rpc_connection.send(self.get_next_hop_if_name(call["destination_hash"]))
|
if path == "next_hop_if_name": self.rpc_return(conn, self.get_next_hop_if_name(call["destination_hash"]))
|
||||||
if path == "next_hop": rpc_connection.send(self.get_next_hop(call["destination_hash"]))
|
if path == "next_hop": self.rpc_return(conn, self.get_next_hop(call["destination_hash"]))
|
||||||
if path == "first_hop_timeout": rpc_connection.send(self.get_first_hop_timeout(call["destination_hash"]))
|
if path == "first_hop_timeout": self.rpc_return(conn, self.get_first_hop_timeout(call["destination_hash"]))
|
||||||
if path == "link_count": rpc_connection.send(self.get_link_count())
|
if path == "link_count": self.rpc_return(conn, self.get_link_count())
|
||||||
if path == "packet_rssi": rpc_connection.send(self.get_packet_rssi(call["packet_hash"]))
|
if path == "packet_rssi": self.rpc_return(conn, self.get_packet_rssi(call["packet_hash"]))
|
||||||
if path == "packet_snr": rpc_connection.send(self.get_packet_snr(call["packet_hash"]))
|
if path == "packet_snr": self.rpc_return(conn, self.get_packet_snr(call["packet_hash"]))
|
||||||
if path == "packet_q": rpc_connection.send(self.get_packet_q(call["packet_hash"]))
|
if path == "packet_q": self.rpc_return(conn, self.get_packet_q(call["packet_hash"]))
|
||||||
if path == "blackholed_identities": rpc_connection.send(self.get_blackholed_identities())
|
if path == "blackholed_identities": self.rpc_return(conn, self.get_blackholed_identities())
|
||||||
if path == "is_blackholed": rpc_connection.send(self.is_blackholed(call["identity_hash"]))
|
if path == "is_blackholed": self.rpc_return(conn, self.is_blackholed(call["identity_hash"]))
|
||||||
|
|
||||||
if "drop" in call:
|
if "drop" in call:
|
||||||
path = call["drop"]
|
path = call["drop"]
|
||||||
if path == "path": rpc_connection.send(self.drop_path(call["destination_hash"]))
|
if path == "path": self.rpc_return(conn, self.drop_path(call["destination_hash"]))
|
||||||
if path == "all_via": rpc_connection.send(self.drop_all_via(call["destination_hash"]))
|
if path == "all_via": self.rpc_return(conn, self.drop_all_via(call["destination_hash"]))
|
||||||
if path == "announce_queues": rpc_connection.send(self.drop_announce_queues())
|
if path == "announce_queues": self.rpc_return(conn, self.drop_announce_queues())
|
||||||
|
|
||||||
if "blackhole_identity" in call:
|
if "blackhole_identity" in call:
|
||||||
identity_hash = call["blackhole_identity"]
|
identity_hash = call["blackhole_identity"]
|
||||||
until = call["until"]
|
until = call["until"]
|
||||||
reason = call["reason"]
|
reason = call["reason"]
|
||||||
rpc_connection.send(self.blackhole_identity(identity_hash, until=until, reason=reason))
|
self.rpc_return(conn, self.blackhole_identity(identity_hash, until=until, reason=reason))
|
||||||
|
|
||||||
if "unblackhole_identity" in call:
|
if "unblackhole_identity" in call:
|
||||||
identity_hash = call["unblackhole_identity"]
|
identity_hash = call["unblackhole_identity"]
|
||||||
rpc_connection.send(self.unblackhole_identity(identity_hash))
|
self.rpc_return(conn, self.unblackhole_identity(identity_hash))
|
||||||
|
|
||||||
if "destination_data" in call:
|
if "destination_data" in call:
|
||||||
operation = call["destination_data"]
|
operation = call["destination_data"]
|
||||||
destination_hash = call["destination_hash"]
|
destination_hash = call["destination_hash"]
|
||||||
if operation == "used": rpc_connection.send(self._used_destination_data(destination_hash))
|
if operation == "used": self.rpc_return(conn, self._used_destination_data(destination_hash))
|
||||||
elif operation == "retain": rpc_connection.send(self._retain_destination_data(destination_hash))
|
elif operation == "retain": self.rpc_return(conn, self._retain_destination_data(destination_hash))
|
||||||
elif operation == "unretain": rpc_connection.send(self._unretain_destination_data(destination_hash))
|
elif operation == "unretain": self.rpc_return(conn, self._unretain_destination_data(destination_hash))
|
||||||
|
|
||||||
if "identity_data" in call:
|
if "identity_data" in call:
|
||||||
operation = call["identity_data"]
|
operation = call["identity_data"]
|
||||||
identity_hash = call["identity_hash"]
|
identity_hash = call["identity_hash"]
|
||||||
if operation == "retain": rpc_connection.send(self._retain_identity(identity_hash))
|
if operation == "retain": self.rpc_return(conn, self._retain_identity(identity_hash))
|
||||||
|
|
||||||
rpc_connection.close()
|
conn.close()
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
RNS.log("An error ocurred while handling RPC call from local client: "+str(e), RNS.LOG_ERROR)
|
RNS.log("An error ocurred while handling RPC call from local client: "+str(e), RNS.LOG_ERROR)
|
||||||
@@ -1243,8 +1256,8 @@ class Reticulum:
|
|||||||
if self.is_connected_to_shared_instance:
|
if self.is_connected_to_shared_instance:
|
||||||
try:
|
try:
|
||||||
rpc_connection = self.get_rpc_client()
|
rpc_connection = self.get_rpc_client()
|
||||||
rpc_connection.send({"destination_data": "used", "destination_hash": destination_hash})
|
rpc_connection.send_bytes(mp.packb({"destination_data": "used", "destination_hash": destination_hash}))
|
||||||
response = rpc_connection.recv()
|
response = mp.unpackb(rpc_connection.recv_bytes())
|
||||||
return response
|
return response
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -1257,8 +1270,8 @@ class Reticulum:
|
|||||||
if self.is_connected_to_shared_instance:
|
if self.is_connected_to_shared_instance:
|
||||||
try:
|
try:
|
||||||
rpc_connection = self.get_rpc_client()
|
rpc_connection = self.get_rpc_client()
|
||||||
rpc_connection.send({"destination_data": "retain", "destination_hash": destination_hash})
|
rpc_connection.send_bytes(mp.packb({"destination_data": "retain", "destination_hash": destination_hash}))
|
||||||
response = rpc_connection.recv()
|
response = mp.unpackb(rpc_connection.recv_bytes())
|
||||||
return response
|
return response
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -1271,8 +1284,8 @@ class Reticulum:
|
|||||||
if self.is_connected_to_shared_instance:
|
if self.is_connected_to_shared_instance:
|
||||||
try:
|
try:
|
||||||
rpc_connection = self.get_rpc_client()
|
rpc_connection = self.get_rpc_client()
|
||||||
rpc_connection.send({"destination_data": "unretain", "destination_hash": destination_hash})
|
rpc_connection.send_bytes(mp.packb({"destination_data": "unretain", "destination_hash": destination_hash}))
|
||||||
response = rpc_connection.recv()
|
response = mp.unpackb(rpc_connection.recv_bytes())
|
||||||
return response
|
return response
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -1288,8 +1301,8 @@ class Reticulum:
|
|||||||
if self.is_connected_to_shared_instance:
|
if self.is_connected_to_shared_instance:
|
||||||
try:
|
try:
|
||||||
rpc_connection = self.get_rpc_client()
|
rpc_connection = self.get_rpc_client()
|
||||||
rpc_connection.send({"identity_data": "retain", "identity_hash": identity_hash})
|
rpc_connection.send_bytes(mp.packb({"identity_data": "retain", "identity_hash": identity_hash}))
|
||||||
response = rpc_connection.recv()
|
response = mp.unpackb(rpc_connection.recv_bytes())
|
||||||
return response
|
return response
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -1301,34 +1314,29 @@ class Reticulum:
|
|||||||
def get_interface_stats(self):
|
def get_interface_stats(self):
|
||||||
if self.is_connected_to_shared_instance:
|
if self.is_connected_to_shared_instance:
|
||||||
rpc_connection = self.get_rpc_client()
|
rpc_connection = self.get_rpc_client()
|
||||||
rpc_connection.send({"get": "interface_stats"})
|
rpc_connection.send_bytes(mp.packb({"get": "interface_stats"}))
|
||||||
response = rpc_connection.recv()
|
response = mp.unpackb(rpc_connection.recv_bytes())
|
||||||
return response
|
return response
|
||||||
|
|
||||||
else:
|
else:
|
||||||
interfaces = []
|
interfaces = []
|
||||||
for interface in RNS.Transport.interfaces:
|
for interface in RNS.Transport.interfaces:
|
||||||
ifstats = {}
|
ifstats = {}
|
||||||
|
|
||||||
if hasattr(interface, "clients"):
|
if hasattr(interface, "clients"): ifstats["clients"] = interface.clients
|
||||||
ifstats["clients"] = interface.clients
|
else: ifstats["clients"] = None
|
||||||
else:
|
|
||||||
ifstats["clients"] = None
|
|
||||||
|
|
||||||
if hasattr(interface, "parent_interface") and interface.parent_interface != None:
|
if hasattr(interface, "parent_interface") and interface.parent_interface != None:
|
||||||
ifstats["parent_interface_name"] = str(interface.parent_interface)
|
ifstats["parent_interface_name"] = str(interface.parent_interface)
|
||||||
ifstats["parent_interface_hash"] = interface.parent_interface.get_hash()
|
ifstats["parent_interface_hash"] = interface.parent_interface.get_hash()
|
||||||
|
|
||||||
if hasattr(interface, "i2p") and hasattr(interface, "connectable"):
|
if hasattr(interface, "i2p") and hasattr(interface, "connectable"):
|
||||||
if interface.connectable:
|
if interface.connectable: ifstats["i2p_connectable"] = True
|
||||||
ifstats["i2p_connectable"] = True
|
else: ifstats["i2p_connectable"] = False
|
||||||
else:
|
|
||||||
ifstats["i2p_connectable"] = False
|
|
||||||
|
|
||||||
if hasattr(interface, "b32"):
|
if hasattr(interface, "b32"):
|
||||||
if interface.b32 != None:
|
if interface.b32 != None: ifstats["i2p_b32"] = interface.b32+".b32.i2p"
|
||||||
ifstats["i2p_b32"] = interface.b32+".b32.i2p"
|
else: ifstats["i2p_b32"] = None
|
||||||
else:
|
|
||||||
ifstats["i2p_b32"] = None
|
|
||||||
|
|
||||||
if hasattr(interface, "i2p_tunnel_state"):
|
if hasattr(interface, "i2p_tunnel_state"):
|
||||||
if interface.i2p_tunnel_state != None:
|
if interface.i2p_tunnel_state != None:
|
||||||
@@ -1343,37 +1351,20 @@ class Reticulum:
|
|||||||
else:
|
else:
|
||||||
ifstats["tunnelstate"] = None
|
ifstats["tunnelstate"] = None
|
||||||
|
|
||||||
if hasattr(interface, "r_airtime_short"):
|
if hasattr(interface, "r_airtime_short"): ifstats["airtime_short"] = interface.r_airtime_short
|
||||||
ifstats["airtime_short"] = interface.r_airtime_short
|
if hasattr(interface, "r_airtime_long"): ifstats["airtime_long"] = interface.r_airtime_long
|
||||||
|
if hasattr(interface, "r_channel_load_short"): ifstats["channel_load_short"] = interface.r_channel_load_short
|
||||||
if hasattr(interface, "r_airtime_long"):
|
if hasattr(interface, "r_channel_load_long"): ifstats["channel_load_long"] = interface.r_channel_load_long
|
||||||
ifstats["airtime_long"] = interface.r_airtime_long
|
if hasattr(interface, "r_noise_floor"): ifstats["noise_floor"] = interface.r_noise_floor
|
||||||
|
if hasattr(interface, "r_interference"): ifstats["interference"] = interface.r_interference
|
||||||
if hasattr(interface, "r_channel_load_short"):
|
if hasattr(interface, "cpu_temp"): ifstats["cpu_temp"] = interface.cpu_temp
|
||||||
ifstats["channel_load_short"] = interface.r_channel_load_short
|
if hasattr(interface, "cpu_load"): ifstats["cpu_load"] = interface.cpu_load
|
||||||
|
if hasattr(interface, "mem_load"): ifstats["mem_load"] = interface.mem_load
|
||||||
if hasattr(interface, "r_channel_load_long"):
|
|
||||||
ifstats["channel_load_long"] = interface.r_channel_load_long
|
|
||||||
|
|
||||||
if hasattr(interface, "r_noise_floor"):
|
|
||||||
ifstats["noise_floor"] = interface.r_noise_floor
|
|
||||||
|
|
||||||
if hasattr(interface, "r_interference"):
|
|
||||||
ifstats["interference"] = interface.r_interference
|
|
||||||
|
|
||||||
if hasattr(interface, "r_interference_l") and type(interface.r_interference_l) == list:
|
if hasattr(interface, "r_interference_l") and type(interface.r_interference_l) == list:
|
||||||
ifstats["interference_last_ts"] = interface.r_interference_l[0]
|
ifstats["interference_last_ts"] = interface.r_interference_l[0]
|
||||||
ifstats["interference_last_dbm"] = interface.r_interference_l[1]
|
ifstats["interference_last_dbm"] = interface.r_interference_l[1]
|
||||||
|
|
||||||
if hasattr(interface, "cpu_temp"):
|
|
||||||
ifstats["cpu_temp"] = interface.cpu_temp
|
|
||||||
|
|
||||||
if hasattr(interface, "cpu_load"):
|
|
||||||
ifstats["cpu_load"] = interface.cpu_load
|
|
||||||
|
|
||||||
if hasattr(interface, "mem_load"):
|
|
||||||
ifstats["mem_load"] = interface.mem_load
|
|
||||||
|
|
||||||
if hasattr(interface, "switch_id"):
|
if hasattr(interface, "switch_id"):
|
||||||
if interface.switch_id != None: ifstats["switch_id"] = RNS.hexrep(interface.switch_id)
|
if interface.switch_id != None: ifstats["switch_id"] = RNS.hexrep(interface.switch_id)
|
||||||
else: ifstats["switch_id"] = None
|
else: ifstats["switch_id"] = None
|
||||||
@@ -1387,39 +1378,26 @@ class Reticulum:
|
|||||||
else: ifstats["endpoint_id"] = None
|
else: ifstats["endpoint_id"] = None
|
||||||
|
|
||||||
if hasattr(interface, "r_battery_state"):
|
if hasattr(interface, "r_battery_state"):
|
||||||
if interface.r_battery_state != 0x00:
|
if interface.r_battery_state != 0x00: ifstats["battery_state"] = interface.get_battery_state_string()
|
||||||
ifstats["battery_state"] = interface.get_battery_state_string()
|
if hasattr(interface, "r_battery_percent"): ifstats["battery_percent"] = interface.r_battery_percent
|
||||||
|
|
||||||
if hasattr(interface, "r_battery_percent"):
|
|
||||||
ifstats["battery_percent"] = interface.r_battery_percent
|
|
||||||
|
|
||||||
if hasattr(interface, "bitrate"):
|
if hasattr(interface, "bitrate"):
|
||||||
if interface.bitrate != None:
|
if interface.bitrate != None: ifstats["bitrate"] = interface.bitrate
|
||||||
ifstats["bitrate"] = interface.bitrate
|
else: ifstats["bitrate"] = None
|
||||||
else:
|
|
||||||
ifstats["bitrate"] = None
|
|
||||||
|
|
||||||
if hasattr(interface, "current_rx_speed"):
|
if hasattr(interface, "current_rx_speed"):
|
||||||
if interface.current_rx_speed != None:
|
if interface.current_rx_speed != None: ifstats["rxs"] = interface.current_rx_speed
|
||||||
ifstats["rxs"] = interface.current_rx_speed
|
else: ifstats["rxs"] = 0
|
||||||
else:
|
else: ifstats["rxs"] = 0
|
||||||
ifstats["rxs"] = 0
|
|
||||||
else:
|
|
||||||
ifstats["rxs"] = 0
|
|
||||||
|
|
||||||
if hasattr(interface, "current_tx_speed"):
|
if hasattr(interface, "current_tx_speed"):
|
||||||
if interface.current_tx_speed != None:
|
if interface.current_tx_speed != None: ifstats["txs"] = interface.current_tx_speed
|
||||||
ifstats["txs"] = interface.current_tx_speed
|
else: ifstats["txs"] = 0
|
||||||
else:
|
else: ifstats["txs"] = 0
|
||||||
ifstats["txs"] = 0
|
|
||||||
else:
|
|
||||||
ifstats["txs"] = 0
|
|
||||||
|
|
||||||
if hasattr(interface, "peers"):
|
if hasattr(interface, "peers"):
|
||||||
if interface.peers != None:
|
if interface.peers != None: ifstats["peers"] = len(interface.peers)
|
||||||
ifstats["peers"] = len(interface.peers)
|
else: ifstats["peers"] = None
|
||||||
else:
|
|
||||||
ifstats["peers"] = None
|
|
||||||
|
|
||||||
if hasattr(interface, "ifac_signature"):
|
if hasattr(interface, "ifac_signature"):
|
||||||
ifstats["ifac_signature"] = interface.ifac_signature
|
ifstats["ifac_signature"] = interface.ifac_signature
|
||||||
@@ -1427,57 +1405,55 @@ class Reticulum:
|
|||||||
ifstats["ifac_netname"] = interface.ifac_netname
|
ifstats["ifac_netname"] = interface.ifac_netname
|
||||||
else:
|
else:
|
||||||
ifstats["ifac_signature"] = None
|
ifstats["ifac_signature"] = None
|
||||||
ifstats["ifac_size"] = None
|
ifstats["ifac_size"] = None
|
||||||
ifstats["ifac_netname"] = None
|
ifstats["ifac_netname"] = None
|
||||||
|
|
||||||
if hasattr(interface, "autoconnect_source"):
|
if hasattr(interface, "autoconnect_source"): ifstats["autoconnect_source"] = interface.autoconnect_source
|
||||||
ifstats["autoconnect_source"] = interface.autoconnect_source
|
else: ifstats["autoconnect_source"] = None
|
||||||
else:
|
|
||||||
ifstats["autoconnect_source"] = None
|
|
||||||
|
|
||||||
if hasattr(interface, "announce_queue"):
|
if hasattr(interface, "announce_queue"):
|
||||||
if interface.announce_queue != None:
|
if interface.announce_queue != None: ifstats["announce_queue"] = len(interface.announce_queue)
|
||||||
ifstats["announce_queue"] = len(interface.announce_queue)
|
else: ifstats["announce_queue"] = None
|
||||||
else:
|
|
||||||
ifstats["announce_queue"] = None
|
|
||||||
|
|
||||||
ifstats["name"] = str(interface)
|
if hasattr(interface, "blocked_ip_count"):
|
||||||
ifstats["short_name"] = str(interface.name)
|
ifstats["blocked_ips"] = interface.blocked_ip_count
|
||||||
ifstats["hash"] = interface.get_hash()
|
|
||||||
ifstats["type"] = str(type(interface).__name__)
|
ifstats["name"] = str(interface)
|
||||||
ifstats["rxb"] = interface.rxb
|
ifstats["short_name"] = str(interface.name)
|
||||||
ifstats["txb"] = interface.txb
|
ifstats["hash"] = interface.get_hash()
|
||||||
|
ifstats["type"] = str(type(interface).__name__)
|
||||||
|
ifstats["rxb"] = interface.rxb
|
||||||
|
ifstats["txb"] = interface.txb
|
||||||
ifstats["incoming_announce_frequency"] = interface.incoming_announce_frequency()
|
ifstats["incoming_announce_frequency"] = interface.incoming_announce_frequency()
|
||||||
ifstats["outgoing_announce_frequency"] = interface.outgoing_announce_frequency()
|
ifstats["outgoing_announce_frequency"] = interface.outgoing_announce_frequency()
|
||||||
ifstats["incoming_pr_frequency"] = interface.incoming_pr_frequency()
|
ifstats["incoming_pr_frequency"] = interface.incoming_pr_frequency()
|
||||||
ifstats["outgoing_pr_frequency"] = interface.outgoing_pr_frequency()
|
ifstats["outgoing_pr_frequency"] = interface.outgoing_pr_frequency()
|
||||||
ifstats["announce_rate_target"] = interface.announce_rate_target
|
ifstats["announce_rate_target"] = interface.announce_rate_target
|
||||||
ifstats["announce_rate_penalty"] = interface.announce_rate_penalty
|
ifstats["announce_rate_penalty"] = interface.announce_rate_penalty
|
||||||
ifstats["announce_rate_grace"] = interface.announce_rate_grace
|
ifstats["announce_rate_grace"] = interface.announce_rate_grace
|
||||||
ifstats["held_announces"] = len(interface.held_announces)
|
ifstats["held_announces"] = len(interface.held_announces)
|
||||||
ifstats["burst_active"] = interface.ic_burst_active
|
ifstats["burst_active"] = interface.ic_burst_active
|
||||||
ifstats["burst_activated"] = interface.ic_burst_activated
|
ifstats["burst_activated"] = interface.ic_burst_activated
|
||||||
ifstats["pr_burst_active"] = interface.ic_pr_burst_active
|
ifstats["pr_burst_active"] = interface.ic_pr_burst_active
|
||||||
ifstats["pr_burst_activated"] = interface.ic_pr_burst_activated
|
ifstats["pr_burst_activated"] = interface.ic_pr_burst_activated
|
||||||
ifstats["status"] = interface.online
|
ifstats["status"] = interface.online
|
||||||
ifstats["mode"] = interface.mode
|
ifstats["mode"] = interface.mode
|
||||||
|
|
||||||
interfaces.append(ifstats)
|
interfaces.append(ifstats)
|
||||||
|
|
||||||
stats = {}
|
stats = {}
|
||||||
stats["interfaces"] = interfaces
|
stats["interfaces"] = interfaces
|
||||||
stats["rxb"] = RNS.Transport.traffic_rxb
|
stats["rxb"] = RNS.Transport.traffic_rxb
|
||||||
stats["txb"] = RNS.Transport.traffic_txb
|
stats["txb"] = RNS.Transport.traffic_txb
|
||||||
stats["rxs"] = RNS.Transport.speed_rx
|
stats["rxs"] = RNS.Transport.speed_rx
|
||||||
stats["txs"] = RNS.Transport.speed_tx
|
stats["txs"] = RNS.Transport.speed_tx
|
||||||
|
|
||||||
if Reticulum.transport_enabled():
|
if Reticulum.transport_enabled():
|
||||||
stats["transport_id"] = RNS.Transport.identity.hash
|
stats["transport_id"] = RNS.Transport.identity.hash
|
||||||
stats["network_id"] = RNS.Transport.network_identity.hash if RNS.Transport.network_identity else None
|
stats["network_id"] = RNS.Transport.network_identity.hash if RNS.Transport.network_identity else None
|
||||||
stats["transport_uptime"] = time.time()-RNS.Transport.start_time
|
stats["transport_uptime"] = time.time()-RNS.Transport.start_time
|
||||||
if Reticulum.probe_destination_enabled():
|
if Reticulum.probe_destination_enabled(): stats["probe_responder"] = RNS.Transport.probe_destination.hash
|
||||||
stats["probe_responder"] = RNS.Transport.probe_destination.hash
|
else: stats["probe_responder"] = None
|
||||||
else:
|
|
||||||
stats["probe_responder"] = None
|
|
||||||
|
|
||||||
if importlib.util.find_spec('psutil') != None:
|
if importlib.util.find_spec('psutil') != None:
|
||||||
import psutil
|
import psutil
|
||||||
@@ -1491,8 +1467,8 @@ class Reticulum:
|
|||||||
def get_path_table(self, max_hops=None):
|
def get_path_table(self, max_hops=None):
|
||||||
if self.is_connected_to_shared_instance:
|
if self.is_connected_to_shared_instance:
|
||||||
rpc_connection = self.get_rpc_client()
|
rpc_connection = self.get_rpc_client()
|
||||||
rpc_connection.send({"get": "path_table", "max_hops": max_hops})
|
rpc_connection.send_bytes(mp.packb({"get": "path_table", "max_hops": max_hops}))
|
||||||
response = rpc_connection.recv()
|
response = mp.unpackb(rpc_connection.recv_bytes())
|
||||||
return response
|
return response
|
||||||
|
|
||||||
else:
|
else:
|
||||||
@@ -1500,14 +1476,10 @@ class Reticulum:
|
|||||||
for dst_hash in RNS.Transport.path_table:
|
for dst_hash in RNS.Transport.path_table:
|
||||||
path_hops = RNS.Transport.path_table[dst_hash][2]
|
path_hops = RNS.Transport.path_table[dst_hash][2]
|
||||||
if max_hops == None or path_hops <= max_hops:
|
if max_hops == None or path_hops <= max_hops:
|
||||||
entry = {
|
entry = { "hash": dst_hash, "timestamp": RNS.Transport.path_table[dst_hash][0],
|
||||||
"hash": dst_hash,
|
"via": RNS.Transport.path_table[dst_hash][1], "hops": path_hops,
|
||||||
"timestamp": RNS.Transport.path_table[dst_hash][0],
|
"expires": RNS.Transport.path_table[dst_hash][3],
|
||||||
"via": RNS.Transport.path_table[dst_hash][1],
|
"interface": str(RNS.Transport.path_table[dst_hash][5]) }
|
||||||
"hops": path_hops,
|
|
||||||
"expires": RNS.Transport.path_table[dst_hash][3],
|
|
||||||
"interface": str(RNS.Transport.path_table[dst_hash][5]),
|
|
||||||
}
|
|
||||||
path_table.append(entry)
|
path_table.append(entry)
|
||||||
|
|
||||||
return path_table
|
return path_table
|
||||||
@@ -1515,8 +1487,8 @@ class Reticulum:
|
|||||||
def get_rate_table(self):
|
def get_rate_table(self):
|
||||||
if self.is_connected_to_shared_instance:
|
if self.is_connected_to_shared_instance:
|
||||||
rpc_connection = self.get_rpc_client()
|
rpc_connection = self.get_rpc_client()
|
||||||
rpc_connection.send({"get": "rate_table"})
|
rpc_connection.send_bytes(mp.packb({"get": "rate_table"}))
|
||||||
response = rpc_connection.recv()
|
response = mp.unpackb(rpc_connection.recv_bytes())
|
||||||
return response
|
return response
|
||||||
|
|
||||||
else:
|
else:
|
||||||
@@ -1536,8 +1508,8 @@ class Reticulum:
|
|||||||
def drop_path(self, destination):
|
def drop_path(self, destination):
|
||||||
if self.is_connected_to_shared_instance:
|
if self.is_connected_to_shared_instance:
|
||||||
rpc_connection = self.get_rpc_client()
|
rpc_connection = self.get_rpc_client()
|
||||||
rpc_connection.send({"drop": "path", "destination_hash": destination})
|
rpc_connection.send_bytes(mp.packb({"drop": "path", "destination_hash": destination}))
|
||||||
response = rpc_connection.recv()
|
response = mp.unpackb(rpc_connection.recv_bytes())
|
||||||
return response
|
return response
|
||||||
|
|
||||||
else:
|
else:
|
||||||
@@ -1546,8 +1518,8 @@ class Reticulum:
|
|||||||
def drop_all_via(self, transport_hash):
|
def drop_all_via(self, transport_hash):
|
||||||
if self.is_connected_to_shared_instance:
|
if self.is_connected_to_shared_instance:
|
||||||
rpc_connection = self.get_rpc_client()
|
rpc_connection = self.get_rpc_client()
|
||||||
rpc_connection.send({"drop": "all_via", "destination_hash": transport_hash})
|
rpc_connection.send_bytes(mp.packb({"drop": "all_via", "destination_hash": transport_hash}))
|
||||||
response = rpc_connection.recv()
|
response = mp.unpackb(rpc_connection.recv_bytes())
|
||||||
return response
|
return response
|
||||||
|
|
||||||
else:
|
else:
|
||||||
@@ -1562,8 +1534,8 @@ class Reticulum:
|
|||||||
def drop_announce_queues(self):
|
def drop_announce_queues(self):
|
||||||
if self.is_connected_to_shared_instance:
|
if self.is_connected_to_shared_instance:
|
||||||
rpc_connection = self.get_rpc_client()
|
rpc_connection = self.get_rpc_client()
|
||||||
rpc_connection.send({"drop": "announce_queues"})
|
rpc_connection.send_bytes(mp.packb({"drop": "announce_queues"}))
|
||||||
response = rpc_connection.recv()
|
response = mp.unpackb(rpc_connection.recv_bytes())
|
||||||
return response
|
return response
|
||||||
|
|
||||||
else:
|
else:
|
||||||
@@ -1572,8 +1544,8 @@ class Reticulum:
|
|||||||
def get_next_hop_if_name(self, destination):
|
def get_next_hop_if_name(self, destination):
|
||||||
if self.is_connected_to_shared_instance:
|
if self.is_connected_to_shared_instance:
|
||||||
rpc_connection = self.get_rpc_client()
|
rpc_connection = self.get_rpc_client()
|
||||||
rpc_connection.send({"get": "next_hop_if_name", "destination_hash": destination})
|
rpc_connection.send_bytes(mp.packb({"get": "next_hop_if_name", "destination_hash": destination}))
|
||||||
response = rpc_connection.recv()
|
response = mp.unpackb(rpc_connection.recv_bytes())
|
||||||
return response
|
return response
|
||||||
|
|
||||||
else:
|
else:
|
||||||
@@ -1583,8 +1555,8 @@ class Reticulum:
|
|||||||
if self.is_connected_to_shared_instance:
|
if self.is_connected_to_shared_instance:
|
||||||
try:
|
try:
|
||||||
rpc_connection = self.get_rpc_client()
|
rpc_connection = self.get_rpc_client()
|
||||||
rpc_connection.send({"get": "first_hop_timeout", "destination_hash": destination})
|
rpc_connection.send_bytes(mp.packb({"get": "first_hop_timeout", "destination_hash": destination}))
|
||||||
response = rpc_connection.recv()
|
response = mp.unpackb(rpc_connection.recv_bytes())
|
||||||
|
|
||||||
if self.is_connected_to_shared_instance and hasattr(self, "_force_shared_instance_bitrate") and self._force_shared_instance_bitrate:
|
if self.is_connected_to_shared_instance and hasattr(self, "_force_shared_instance_bitrate") and self._force_shared_instance_bitrate:
|
||||||
simulated_latency = ((1/self._force_shared_instance_bitrate)*8)*RNS.Reticulum.MTU
|
simulated_latency = ((1/self._force_shared_instance_bitrate)*8)*RNS.Reticulum.MTU
|
||||||
@@ -1602,8 +1574,8 @@ class Reticulum:
|
|||||||
def get_next_hop(self, destination):
|
def get_next_hop(self, destination):
|
||||||
if self.is_connected_to_shared_instance:
|
if self.is_connected_to_shared_instance:
|
||||||
rpc_connection = self.get_rpc_client()
|
rpc_connection = self.get_rpc_client()
|
||||||
rpc_connection.send({"get": "next_hop", "destination_hash": destination})
|
rpc_connection.send_bytes(mp.packb({"get": "next_hop", "destination_hash": destination}))
|
||||||
response = rpc_connection.recv()
|
response = mp.unpackb(rpc_connection.recv_bytes())
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
@@ -1613,8 +1585,8 @@ class Reticulum:
|
|||||||
def get_link_count(self):
|
def get_link_count(self):
|
||||||
if self.is_connected_to_shared_instance:
|
if self.is_connected_to_shared_instance:
|
||||||
rpc_connection = self.get_rpc_client()
|
rpc_connection = self.get_rpc_client()
|
||||||
rpc_connection.send({"get": "link_count"})
|
rpc_connection.send_bytes(mp.packb({"get": "link_count"}))
|
||||||
response = rpc_connection.recv()
|
response = mp.unpackb(rpc_connection.recv_bytes())
|
||||||
return response
|
return response
|
||||||
|
|
||||||
else:
|
else:
|
||||||
@@ -1623,8 +1595,8 @@ class Reticulum:
|
|||||||
def get_packet_rssi(self, packet_hash):
|
def get_packet_rssi(self, packet_hash):
|
||||||
if self.is_connected_to_shared_instance:
|
if self.is_connected_to_shared_instance:
|
||||||
rpc_connection = self.get_rpc_client()
|
rpc_connection = self.get_rpc_client()
|
||||||
rpc_connection.send({"get": "packet_rssi", "packet_hash": packet_hash})
|
rpc_connection.send_bytes(mp.packb({"get": "packet_rssi", "packet_hash": packet_hash}))
|
||||||
response = rpc_connection.recv()
|
response = mp.unpackb(rpc_connection.recv_bytes())
|
||||||
return response
|
return response
|
||||||
|
|
||||||
else:
|
else:
|
||||||
@@ -1637,8 +1609,8 @@ class Reticulum:
|
|||||||
def get_packet_snr(self, packet_hash):
|
def get_packet_snr(self, packet_hash):
|
||||||
if self.is_connected_to_shared_instance:
|
if self.is_connected_to_shared_instance:
|
||||||
rpc_connection = self.get_rpc_client()
|
rpc_connection = self.get_rpc_client()
|
||||||
rpc_connection.send({"get": "packet_snr", "packet_hash": packet_hash})
|
rpc_connection.send_bytes(mp.packb({"get": "packet_snr", "packet_hash": packet_hash}))
|
||||||
response = rpc_connection.recv()
|
response = mp.unpackb(rpc_connection.recv_bytes())
|
||||||
return response
|
return response
|
||||||
|
|
||||||
else:
|
else:
|
||||||
@@ -1651,8 +1623,8 @@ class Reticulum:
|
|||||||
def get_packet_q(self, packet_hash):
|
def get_packet_q(self, packet_hash):
|
||||||
if self.is_connected_to_shared_instance:
|
if self.is_connected_to_shared_instance:
|
||||||
rpc_connection = self.get_rpc_client()
|
rpc_connection = self.get_rpc_client()
|
||||||
rpc_connection.send({"get": "packet_q", "packet_hash": packet_hash})
|
rpc_connection.send_bytes(mp.packb({"get": "packet_q", "packet_hash": packet_hash}))
|
||||||
response = rpc_connection.recv()
|
response = mp.unpackb(rpc_connection.recv_bytes())
|
||||||
return response
|
return response
|
||||||
|
|
||||||
else:
|
else:
|
||||||
@@ -1674,8 +1646,8 @@ class Reticulum:
|
|||||||
def get_blackholed_identities(self):
|
def get_blackholed_identities(self):
|
||||||
if self.is_connected_to_shared_instance:
|
if self.is_connected_to_shared_instance:
|
||||||
rpc_connection = self.get_rpc_client()
|
rpc_connection = self.get_rpc_client()
|
||||||
rpc_connection.send({"get": "blackholed_identities"})
|
rpc_connection.send_bytes(mp.packb({"get": "blackholed_identities"}))
|
||||||
response = rpc_connection.recv()
|
response = mp.unpackb(rpc_connection.recv_bytes())
|
||||||
return response
|
return response
|
||||||
|
|
||||||
else: return RNS.Transport.blackholed_identities
|
else: return RNS.Transport.blackholed_identities
|
||||||
@@ -1688,8 +1660,8 @@ class Reticulum:
|
|||||||
|
|
||||||
if self.is_connected_to_shared_instance:
|
if self.is_connected_to_shared_instance:
|
||||||
rpc_connection = self.get_rpc_client()
|
rpc_connection = self.get_rpc_client()
|
||||||
rpc_connection.send({"get": "is_blackholed", "identity_hash": identity_hash})
|
rpc_connection.send_bytes(mp.packb({"get": "is_blackholed", "identity_hash": identity_hash}))
|
||||||
response = rpc_connection.recv()
|
response = mp.unpackb(rpc_connection.recv_bytes())
|
||||||
return response
|
return response
|
||||||
|
|
||||||
else: return identity_hash in RNS.Transport.blackholed_identities
|
else: return identity_hash in RNS.Transport.blackholed_identities
|
||||||
@@ -1699,8 +1671,8 @@ class Reticulum:
|
|||||||
else:
|
else:
|
||||||
if self.is_connected_to_shared_instance:
|
if self.is_connected_to_shared_instance:
|
||||||
rpc_connection = self.get_rpc_client()
|
rpc_connection = self.get_rpc_client()
|
||||||
rpc_connection.send({"blackhole_identity": identity_hash, "until": until, "reason": reason})
|
rpc_connection.send_bytes(mp.packb({"blackhole_identity": identity_hash, "until": until, "reason": reason}))
|
||||||
response = rpc_connection.recv()
|
response = mp.unpackb(rpc_connection.recv_bytes())
|
||||||
return response
|
return response
|
||||||
|
|
||||||
else: return RNS.Transport.blackhole_identity(identity_hash, until=until, reason=reason)
|
else: return RNS.Transport.blackhole_identity(identity_hash, until=until, reason=reason)
|
||||||
@@ -1710,8 +1682,8 @@ class Reticulum:
|
|||||||
else:
|
else:
|
||||||
if self.is_connected_to_shared_instance:
|
if self.is_connected_to_shared_instance:
|
||||||
rpc_connection = self.get_rpc_client()
|
rpc_connection = self.get_rpc_client()
|
||||||
rpc_connection.send({"unblackhole_identity": identity_hash})
|
rpc_connection.send_bytes(mp.packb({"unblackhole_identity": identity_hash}))
|
||||||
response = rpc_connection.recv()
|
response = mp.unpackb(rpc_connection.recv_bytes())
|
||||||
return response
|
return response
|
||||||
|
|
||||||
else: return RNS.Transport.unblackhole_identity(identity_hash)
|
else: return RNS.Transport.unblackhole_identity(identity_hash)
|
||||||
@@ -1831,6 +1803,14 @@ class Reticulum:
|
|||||||
def max_autoconnected_interfaces():
|
def max_autoconnected_interfaces():
|
||||||
return Reticulum.__autoconnect_discovered_interfaces
|
return Reticulum.__autoconnect_discovered_interfaces
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def static_transport_identity():
|
||||||
|
return Reticulum.__static_transport_identity
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def local_hops_delta():
|
||||||
|
return Reticulum.__local_hops_delta
|
||||||
|
|
||||||
# Default configuration file:
|
# Default configuration file:
|
||||||
__default_rns_config__ = '''# This is the default Reticulum config file.
|
__default_rns_config__ = '''# This is the default Reticulum config file.
|
||||||
# You should probably edit it to include any additional,
|
# You should probably edit it to include any additional,
|
||||||
@@ -1918,7 +1898,7 @@ instance_name = default
|
|||||||
|
|
||||||
|
|
||||||
[logging]
|
[logging]
|
||||||
# Valid log levels are 0 through 7:
|
# Valid log levels are 0 through 8:
|
||||||
# 0: Log only critical information
|
# 0: Log only critical information
|
||||||
# 1: Log errors and lower log levels
|
# 1: Log errors and lower log levels
|
||||||
# 2: Log warnings and lower log levels
|
# 2: Log warnings and lower log levels
|
||||||
@@ -1926,7 +1906,8 @@ instance_name = default
|
|||||||
# 4: Log info and lower (this is the default)
|
# 4: Log info and lower (this is the default)
|
||||||
# 5: Verbose logging
|
# 5: Verbose logging
|
||||||
# 6: Debug logging
|
# 6: Debug logging
|
||||||
# 7: Extreme logging
|
# 7: Path logging
|
||||||
|
# 8: Extreme logging
|
||||||
|
|
||||||
loglevel = 4
|
loglevel = 4
|
||||||
|
|
||||||
|
|||||||
+197
-172
@@ -166,6 +166,7 @@ class Transport:
|
|||||||
local_client_rssi_cache = []
|
local_client_rssi_cache = []
|
||||||
local_client_snr_cache = []
|
local_client_snr_cache = []
|
||||||
local_client_q_cache = []
|
local_client_q_cache = []
|
||||||
|
local_hops_delta = 0
|
||||||
LOCAL_CLIENT_CACHE_MAXSIZE = 512
|
LOCAL_CLIENT_CACHE_MAXSIZE = 512
|
||||||
|
|
||||||
pending_local_path_requests = {}
|
pending_local_path_requests = {}
|
||||||
@@ -207,12 +208,15 @@ class Transport:
|
|||||||
identity = None
|
identity = None
|
||||||
network_identity = None
|
network_identity = None
|
||||||
|
|
||||||
|
_identity = None
|
||||||
_should_run = True
|
_should_run = True
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def internal_identity(): return Transport._identity
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def start(reticulum_instance):
|
def start(reticulum_instance):
|
||||||
Transport.owner = reticulum_instance
|
Transport.owner = reticulum_instance
|
||||||
Transport.PR_LOGLEVEL = RNS.LOG_EXTREME
|
|
||||||
|
|
||||||
if Transport.identity == None:
|
if Transport.identity == None:
|
||||||
transport_identity_path = RNS.Reticulum.storagepath+"/transport_identity"
|
transport_identity_path = RNS.Reticulum.storagepath+"/transport_identity"
|
||||||
@@ -226,6 +230,14 @@ class Transport:
|
|||||||
else:
|
else:
|
||||||
RNS.log("Loaded Transport Identity from storage", RNS.LOG_VERBOSE) if RNS.sl(RNS.LOG_VERBOSE) else None
|
RNS.log("Loaded Transport Identity from storage", RNS.LOG_VERBOSE) if RNS.sl(RNS.LOG_VERBOSE) else None
|
||||||
|
|
||||||
|
Transport._identity = Transport.identity
|
||||||
|
if not RNS.Reticulum.transport_enabled() and not RNS.Reticulum.static_transport_identity():
|
||||||
|
Transport._identity = Transport.identity
|
||||||
|
Transport.identity = RNS.Identity()
|
||||||
|
RNS.log(f"Initialized ephemeral transport identity {RNS.prettyhexrep(Transport.identity.hash)}", RNS.LOG_VERBOSE) if RNS.sl(RNS.LOG_VERBOSE) else None
|
||||||
|
|
||||||
|
if RNS.Reticulum.local_hops_delta(): Transport.local_hops_delta = (ord(os.urandom(1))%6)+2
|
||||||
|
|
||||||
packet_hashlist_path = RNS.Reticulum.storagepath+"/packet_hashlist"
|
packet_hashlist_path = RNS.Reticulum.storagepath+"/packet_hashlist"
|
||||||
if not Transport.owner.is_connected_to_shared_instance:
|
if not Transport.owner.is_connected_to_shared_instance:
|
||||||
if os.path.isfile(packet_hashlist_path):
|
if os.path.isfile(packet_hashlist_path):
|
||||||
@@ -290,7 +302,6 @@ class Transport:
|
|||||||
if RNS.Reticulum.transport_enabled():
|
if RNS.Reticulum.transport_enabled():
|
||||||
path_table_path = RNS.Reticulum.storagepath+"/destination_table"
|
path_table_path = RNS.Reticulum.storagepath+"/destination_table"
|
||||||
tunnel_table_path = RNS.Reticulum.storagepath+"/tunnels"
|
tunnel_table_path = RNS.Reticulum.storagepath+"/tunnels"
|
||||||
Transport.PR_LOGLEVEL = RNS.LOG_DEBUG
|
|
||||||
|
|
||||||
if os.path.isfile(path_table_path) and not Transport.owner.is_connected_to_shared_instance:
|
if os.path.isfile(path_table_path) and not Transport.owner.is_connected_to_shared_instance:
|
||||||
serialised_destinations = []
|
serialised_destinations = []
|
||||||
@@ -318,7 +329,7 @@ class Transport:
|
|||||||
del path_identity
|
del path_identity
|
||||||
|
|
||||||
if announce_packet != None and receiving_interface != None and blackholed == False:
|
if announce_packet != None and receiving_interface != None and blackholed == False:
|
||||||
announce_packet.unpack()
|
if not announce_packet.unpack(): continue
|
||||||
# We increase the hops, since reading a packet
|
# We increase the hops, since reading a packet
|
||||||
# from cache is equivalent to receiving it again
|
# from cache is equivalent to receiving it again
|
||||||
# over an interface. It is cached with it's non-
|
# over an interface. It is cached with it's non-
|
||||||
@@ -326,15 +337,15 @@ class Transport:
|
|||||||
announce_packet.hops += 1
|
announce_packet.hops += 1
|
||||||
with Transport.path_table_lock:
|
with Transport.path_table_lock:
|
||||||
Transport.path_table[destination_hash] = [timestamp, received_from, hops, expires, random_blobs, receiving_interface, announce_packet.packet_hash]
|
Transport.path_table[destination_hash] = [timestamp, received_from, hops, expires, random_blobs, receiving_interface, announce_packet.packet_hash]
|
||||||
RNS.log("Loaded path table entry for "+RNS.prettyhexrep(destination_hash)+" from storage", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
|
RNS.log("Loaded path table entry for "+RNS.prettyhexrep(destination_hash)+" from storage", RNS.LOG_PATHING) if RNS.sl(RNS.LOG_PATHING) else None
|
||||||
else:
|
else:
|
||||||
RNS.log("Could not reconstruct path table entry from storage for "+RNS.prettyhexrep(destination_hash), RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
|
RNS.log("Could not reconstruct path table entry from storage for "+RNS.prettyhexrep(destination_hash), RNS.LOG_PATHING) if RNS.sl(RNS.LOG_PATHING) else None
|
||||||
if announce_packet == None:
|
if announce_packet == None:
|
||||||
RNS.log("The announce packet could not be loaded from cache", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
|
RNS.log("The announce packet could not be loaded from cache", RNS.LOG_PATHING) if RNS.sl(RNS.LOG_PATHING) else None
|
||||||
if receiving_interface == None:
|
if receiving_interface == None:
|
||||||
RNS.log("The interface is no longer available", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
|
RNS.log("The interface is no longer available", RNS.LOG_PATHING) if RNS.sl(RNS.LOG_PATHING) else None
|
||||||
if blackholed:
|
if blackholed:
|
||||||
RNS.log("The associated identity is blackholed", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
|
RNS.log("The associated identity is blackholed", RNS.LOG_PATHING) if RNS.sl(RNS.LOG_PATHING) else None
|
||||||
|
|
||||||
if len(Transport.path_table) == 1: specifier = "entry"
|
if len(Transport.path_table) == 1: specifier = "entry"
|
||||||
else: specifier = "entries"
|
else: specifier = "entries"
|
||||||
@@ -371,7 +382,7 @@ class Transport:
|
|||||||
announce_packet = Transport.get_cached_packet(serialised_entry[7], packet_type="announce")
|
announce_packet = Transport.get_cached_packet(serialised_entry[7], packet_type="announce")
|
||||||
|
|
||||||
if announce_packet != None:
|
if announce_packet != None:
|
||||||
announce_packet.unpack()
|
if not announce_packet.unpack(): continue
|
||||||
# We increase the hops, since reading a packet
|
# We increase the hops, since reading a packet
|
||||||
# from cache is equivalent to receiving it again
|
# from cache is equivalent to receiving it again
|
||||||
# over an interface. It is cached with it's non-
|
# over an interface. It is cached with it's non-
|
||||||
@@ -495,8 +506,7 @@ class Transport:
|
|||||||
Transport.speed_rx = rxs
|
Transport.speed_rx = rxs
|
||||||
Transport.speed_tx = txs
|
Transport.speed_tx = txs
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e: RNS.log(f"An error occurred while counting interface traffic: {e}", RNS.LOG_ERROR)
|
||||||
RNS.log(f"An error occurred while counting interface traffic: {e}", RNS.LOG_ERROR)
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def jobloop():
|
def jobloop():
|
||||||
@@ -536,7 +546,7 @@ class Transport:
|
|||||||
last_path_request = Transport.path_requests[link.destination.hash]
|
last_path_request = Transport.path_requests[link.destination.hash]
|
||||||
|
|
||||||
if time.time() - last_path_request > Transport.PATH_REQUEST_MI:
|
if time.time() - last_path_request > Transport.PATH_REQUEST_MI:
|
||||||
RNS.log("Trying to rediscover path for "+RNS.prettyhexrep(link.destination.hash)+" since an attempted link was never established", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
|
RNS.log("Trying to rediscover path for "+RNS.prettyhexrep(link.destination.hash)+" since an attempted link was never established", RNS.LOG_PATHING) if RNS.sl(RNS.LOG_PATHING) else None
|
||||||
if not link.destination.hash in path_requests:
|
if not link.destination.hash in path_requests:
|
||||||
blocked_if = None
|
blocked_if = None
|
||||||
path_requests[link.destination.hash] = blocked_if
|
path_requests[link.destination.hash] = blocked_if
|
||||||
@@ -597,7 +607,7 @@ class Transport:
|
|||||||
announce_data = packet.data
|
announce_data = packet.data
|
||||||
announce_identity = RNS.Identity.recall(packet.destination_hash, _no_use=True)
|
announce_identity = RNS.Identity.recall(packet.destination_hash, _no_use=True)
|
||||||
if not announce_identity:
|
if not announce_identity:
|
||||||
RNS.log("Completed announce processing for "+RNS.prettyhexrep(destination_hash)+", the path was cleaned while waiting for announce rebroadcast", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
|
RNS.log("Completed announce processing for "+RNS.prettyhexrep(destination_hash)+", the path was cleaned while waiting for announce rebroadcast", RNS.LOG_PATHING) if RNS.sl(RNS.LOG_PATHING) else None
|
||||||
completed_announces.append(destination_hash)
|
completed_announces.append(destination_hash)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
@@ -616,12 +626,12 @@ class Transport:
|
|||||||
context_flag = packet.context_flag)
|
context_flag = packet.context_flag)
|
||||||
|
|
||||||
new_packet.hops = announce_entry[4]
|
new_packet.hops = announce_entry[4]
|
||||||
if block_rebroadcasts: RNS.log("Rebroadcasting announce as path response for "+RNS.prettyhexrep(announce_destination.hash)+" with hop count "+str(new_packet.hops), RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
|
if block_rebroadcasts: RNS.log("Rebroadcasting announce as path response for "+RNS.prettyhexrep(announce_destination.hash)+" with hop count "+str(new_packet.hops), RNS.LOG_PATHING) if RNS.sl(RNS.LOG_PATHING) else None
|
||||||
else: RNS.log("Rebroadcasting announce for "+RNS.prettyhexrep(announce_destination.hash)+" with hop count "+str(new_packet.hops), RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
|
else: RNS.log("Rebroadcasting announce for "+RNS.prettyhexrep(announce_destination.hash)+" with hop count "+str(new_packet.hops), RNS.LOG_PATHING) if RNS.sl(RNS.LOG_PATHING) else None
|
||||||
|
|
||||||
outgoing.append(new_packet)
|
outgoing.append(new_packet)
|
||||||
|
|
||||||
# This handles an edge case where a peer sends a past
|
# This handles an edge case where a peer sends a path
|
||||||
# request for a destination just after an announce for
|
# request for a destination just after an announce for
|
||||||
# said destination has arrived, but before it has been
|
# said destination has arrived, but before it has been
|
||||||
# rebroadcast locally. In such a case the actual announce
|
# rebroadcast locally. In such a case the actual announce
|
||||||
@@ -630,7 +640,7 @@ class Transport:
|
|||||||
if destination_hash in Transport.held_announces:
|
if destination_hash in Transport.held_announces:
|
||||||
held_entry = Transport.held_announces.pop(destination_hash)
|
held_entry = Transport.held_announces.pop(destination_hash)
|
||||||
Transport.announce_table[destination_hash] = held_entry
|
Transport.announce_table[destination_hash] = held_entry
|
||||||
RNS.log("Reinserting held announce into table", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
|
RNS.log("Reinserting held announce into table", RNS.LOG_PATHING) if RNS.sl(RNS.LOG_PATHING) else None
|
||||||
|
|
||||||
for destination_hash in completed_announces:
|
for destination_hash in completed_announces:
|
||||||
if destination_hash in Transport.announce_table: Transport.announce_table.pop(destination_hash)
|
if destination_hash in Transport.announce_table: Transport.announce_table.pop(destination_hash)
|
||||||
@@ -706,14 +716,14 @@ class Transport:
|
|||||||
# If the path has been invalidated between the time of
|
# If the path has been invalidated between the time of
|
||||||
# making the link request and now, try to rediscover it
|
# making the link request and now, try to rediscover it
|
||||||
if not Transport.has_path(link_entry[IDX_LT_DSTHASH]):
|
if not Transport.has_path(link_entry[IDX_LT_DSTHASH]):
|
||||||
RNS.log("Trying to rediscover path for "+RNS.prettyhexrep(link_entry[IDX_LT_DSTHASH])+" since an attempted link was never established, and path is now missing", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
|
RNS.log("Trying to rediscover path for "+RNS.prettyhexrep(link_entry[IDX_LT_DSTHASH])+" since an attempted link was never established, and path is now missing", RNS.LOG_PATHING) if RNS.sl(RNS.LOG_PATHING) else None
|
||||||
path_request_conditions = True
|
path_request_conditions = True
|
||||||
|
|
||||||
# If this link request was originated from a local client
|
# If this link request was originated from a local client
|
||||||
# attempt to rediscover a path to the destination, if this
|
# attempt to rediscover a path to the destination, if this
|
||||||
# has not already happened recently.
|
# has not already happened recently.
|
||||||
elif not path_request_throttle and lr_taken_hops == 0:
|
elif not path_request_throttle and lr_taken_hops == 0:
|
||||||
RNS.log("Trying to rediscover path for "+RNS.prettyhexrep(link_entry[IDX_LT_DSTHASH])+" since an attempted local client link was never established", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
|
RNS.log("Trying to rediscover path for "+RNS.prettyhexrep(link_entry[IDX_LT_DSTHASH])+" since an attempted local client link was never established", RNS.LOG_PATHING) if RNS.sl(RNS.LOG_PATHING) else None
|
||||||
path_request_conditions = True
|
path_request_conditions = True
|
||||||
|
|
||||||
# If the link destination was previously only 1 hop
|
# If the link destination was previously only 1 hop
|
||||||
@@ -722,7 +732,7 @@ class Transport:
|
|||||||
# In that case, try to discover a new path, and mark
|
# In that case, try to discover a new path, and mark
|
||||||
# the old one as unresponsive.
|
# the old one as unresponsive.
|
||||||
elif not path_request_throttle and Transport.hops_to(link_entry[IDX_LT_DSTHASH]) == 1:
|
elif not path_request_throttle and Transport.hops_to(link_entry[IDX_LT_DSTHASH]) == 1:
|
||||||
RNS.log("Trying to rediscover path for "+RNS.prettyhexrep(link_entry[IDX_LT_DSTHASH])+" since an attempted link was never established, and destination was previously local to an interface on this instance", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
|
RNS.log("Trying to rediscover path for "+RNS.prettyhexrep(link_entry[IDX_LT_DSTHASH])+" since an attempted link was never established, and destination was previously local to an interface on this instance", RNS.LOG_PATHING) if RNS.sl(RNS.LOG_PATHING) else None
|
||||||
path_request_conditions = True
|
path_request_conditions = True
|
||||||
blocked_if = link_entry[IDX_LT_RCVD_IF]
|
blocked_if = link_entry[IDX_LT_RCVD_IF]
|
||||||
|
|
||||||
@@ -744,7 +754,7 @@ class Transport:
|
|||||||
# changed. In that case, we try to discover a new path,
|
# changed. In that case, we try to discover a new path,
|
||||||
# and mark the old one as potentially unresponsive.
|
# and mark the old one as potentially unresponsive.
|
||||||
elif not path_request_throttle and lr_taken_hops == 1:
|
elif not path_request_throttle and lr_taken_hops == 1:
|
||||||
RNS.log("Trying to rediscover path for "+RNS.prettyhexrep(link_entry[IDX_LT_DSTHASH])+" since an attempted link was never established, and link initiator is local to an interface on this instance", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
|
RNS.log("Trying to rediscover path for "+RNS.prettyhexrep(link_entry[IDX_LT_DSTHASH])+" since an attempted link was never established, and link initiator is local to an interface on this instance", RNS.LOG_PATHING) if RNS.sl(RNS.LOG_PATHING) else None
|
||||||
path_request_conditions = True
|
path_request_conditions = True
|
||||||
blocked_if = link_entry[IDX_LT_RCVD_IF]
|
blocked_if = link_entry[IDX_LT_RCVD_IF]
|
||||||
|
|
||||||
@@ -780,11 +790,11 @@ class Transport:
|
|||||||
if time.time() > destination_expiry:
|
if time.time() > destination_expiry:
|
||||||
stale_paths.append(destination_hash)
|
stale_paths.append(destination_hash)
|
||||||
should_collect = True
|
should_collect = True
|
||||||
RNS.log("Path to "+RNS.prettyhexrep(destination_hash)+" timed out and was removed", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
|
RNS.log("Path to "+RNS.prettyhexrep(destination_hash)+" timed out and was removed", RNS.LOG_PATHING) if RNS.sl(RNS.LOG_PATHING) else None
|
||||||
elif not attached_interface in Transport.interfaces:
|
elif not attached_interface in Transport.interfaces:
|
||||||
stale_paths.append(destination_hash)
|
stale_paths.append(destination_hash)
|
||||||
should_collect = True
|
should_collect = True
|
||||||
RNS.log("Path to "+RNS.prettyhexrep(destination_hash)+" was removed since the attached interface no longer exists", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
|
RNS.log("Path to "+RNS.prettyhexrep(destination_hash)+" was removed since the attached interface no longer exists", RNS.LOG_PATHING) if RNS.sl(RNS.LOG_PATHING) else None
|
||||||
|
|
||||||
# Cull the pending path requests table
|
# Cull the pending path requests table
|
||||||
stale_path_requests = []
|
stale_path_requests = []
|
||||||
@@ -1137,7 +1147,7 @@ class Transport:
|
|||||||
# Insert packet into transport
|
# Insert packet into transport
|
||||||
new_flags = (RNS.Packet.HEADER_2) << 6 | (Transport.TRANSPORT) << 4 | (packet.flags & 0b00001111)
|
new_flags = (RNS.Packet.HEADER_2) << 6 | (Transport.TRANSPORT) << 4 | (packet.flags & 0b00001111)
|
||||||
new_raw = struct.pack("!B", new_flags)
|
new_raw = struct.pack("!B", new_flags)
|
||||||
new_raw += packet.raw[1:2]
|
new_raw += packet.raw[1:2] if not Transport.should_apply_delta(packet, outbound_interface) else struct.pack("!B", Transport.local_hops_delta)
|
||||||
new_raw += path_entry[IDX_PT_NEXT_HOP]
|
new_raw += path_entry[IDX_PT_NEXT_HOP]
|
||||||
new_raw += packet.raw[2:]
|
new_raw += packet.raw[2:]
|
||||||
packet_sent(packet)
|
packet_sent(packet)
|
||||||
@@ -1157,7 +1167,7 @@ class Transport:
|
|||||||
# Insert packet into transport
|
# Insert packet into transport
|
||||||
new_flags = (RNS.Packet.HEADER_2) << 6 | (Transport.TRANSPORT) << 4 | (packet.flags & 0b00001111)
|
new_flags = (RNS.Packet.HEADER_2) << 6 | (Transport.TRANSPORT) << 4 | (packet.flags & 0b00001111)
|
||||||
new_raw = struct.pack("!B", new_flags)
|
new_raw = struct.pack("!B", new_flags)
|
||||||
new_raw += packet.raw[1:2]
|
new_raw += packet.raw[1:2] if not Transport.should_apply_delta(packet, outbound_interface) else struct.pack("!B", Transport.local_hops_delta)
|
||||||
new_raw += path_entry[IDX_PT_NEXT_HOP]
|
new_raw += path_entry[IDX_PT_NEXT_HOP]
|
||||||
new_raw += packet.raw[2:]
|
new_raw += packet.raw[2:]
|
||||||
packet_sent(packet)
|
packet_sent(packet)
|
||||||
@@ -1170,7 +1180,9 @@ class Transport:
|
|||||||
# simply transmit the packet directly on that one.
|
# simply transmit the packet directly on that one.
|
||||||
else:
|
else:
|
||||||
packet_sent(packet)
|
packet_sent(packet)
|
||||||
Transport.transmit(outbound_interface, packet.raw)
|
if not Transport.should_apply_delta(packet, outbound_interface): raw = packet.raw
|
||||||
|
else: raw = Transport.mangle_hops(packet.raw, Transport.local_hops_delta)
|
||||||
|
Transport.transmit(outbound_interface, raw)
|
||||||
sent = True
|
sent = True
|
||||||
|
|
||||||
# If we don't have a known path for the destination, we'll
|
# If we don't have a known path for the destination, we'll
|
||||||
@@ -1184,63 +1196,68 @@ class Transport:
|
|||||||
should_transmit = True
|
should_transmit = True
|
||||||
|
|
||||||
if packet.destination.type == RNS.Destination.LINK:
|
if packet.destination.type == RNS.Destination.LINK:
|
||||||
if packet.destination.status == RNS.Link.CLOSED: should_transmit = False
|
if packet.destination.status == RNS.Link.CLOSED: continue
|
||||||
if interface != packet.destination.attached_interface: should_transmit = False
|
if interface != packet.destination.attached_interface: continue
|
||||||
|
|
||||||
if packet.attached_interface != None and interface != packet.attached_interface:
|
if packet.attached_interface != None and interface != packet.attached_interface: continue
|
||||||
should_transmit = False
|
|
||||||
|
|
||||||
if packet.packet_type == RNS.Packet.ANNOUNCE:
|
if packet.packet_type == RNS.Packet.ANNOUNCE:
|
||||||
if packet.attached_interface == None:
|
if packet.attached_interface == None:
|
||||||
if interface.mode == RNS.Interfaces.Interface.Interface.MODE_ACCESS_POINT:
|
ac_loglevel = RNS.LOG_PATHING
|
||||||
RNS.log("Blocking announce broadcast on "+str(interface)+" due to AP mode", RNS.LOG_EXTREME) if RNS.sl(RNS.LOG_EXTREME) else None
|
from_interface = Transport.next_hop_interface(packet.destination_hash)
|
||||||
|
local_destination = None
|
||||||
|
with Transport.destinations_map_lock:
|
||||||
|
if packet.destination_hash in Transport.destinations_map:
|
||||||
|
local_destination = Transport.destinations_map[packet.destination_hash]
|
||||||
|
|
||||||
|
if not local_destination and from_interface == None:
|
||||||
|
RNS.log("Blocking announce broadcast on "+str(interface)+" since next hop interface doesn't exist", ac_loglevel) if RNS.sl(ac_loglevel) else None
|
||||||
should_transmit = False
|
should_transmit = False
|
||||||
|
|
||||||
elif interface.mode == RNS.Interfaces.Interface.Interface.MODE_ROAMING:
|
elif not local_destination and interface.announces_from_internal == False and from_interface.mode == RNS.Interfaces.Interface.Interface.MODE_INTERNAL:
|
||||||
local_destination = None
|
RNS.log("Blocking announce broadcast on "+str(interface)+" due to internal-mode next hop interface", ac_loglevel) if RNS.sl(ac_loglevel) else None
|
||||||
with Transport.destinations_map_lock:
|
should_transmit = False
|
||||||
if packet.destination_hash in Transport.destinations_map:
|
|
||||||
local_destination = Transport.destinations_map[packet.destination_hash]
|
|
||||||
|
|
||||||
|
elif interface.mode == RNS.Interfaces.Interface.Interface.MODE_ACCESS_POINT:
|
||||||
|
RNS.log("Blocking announce broadcast on "+str(interface)+" due to AP mode", ac_loglevel) if RNS.sl(ac_loglevel) else None
|
||||||
|
should_transmit = False
|
||||||
|
|
||||||
|
elif not local_destination and interface.mode == RNS.Interfaces.Interface.Interface.MODE_INTERNAL:
|
||||||
|
if not hasattr(from_interface, "mode"):
|
||||||
|
should_transmit = False
|
||||||
|
RNS.log("Blocking announce broadcast on "+str(interface)+" since next hop interface has no mode configured", ac_loglevel) if RNS.sl(ac_loglevel) else None
|
||||||
|
else:
|
||||||
|
if from_interface.mode == RNS.Interfaces.Interface.Interface.MODE_BOUNDARY:
|
||||||
|
RNS.log("Blocking announce broadcast on "+str(interface)+" due to boundary-mode next-hop interface", ac_loglevel) if RNS.sl(ac_loglevel) else None
|
||||||
|
should_transmit = False
|
||||||
|
|
||||||
|
elif interface.mode == RNS.Interfaces.Interface.Interface.MODE_ROAMING:
|
||||||
if local_destination != None:
|
if local_destination != None:
|
||||||
# RNS.log("Allowing announce broadcast on roaming-mode interface from instance-local destination", RNS.LOG_EXTREME) if RNS.sl(RNS.LOG_EXTREME) else None
|
# RNS.log("Allowing announce broadcast on roaming-mode interface from instance-local destination", ac_loglevel) if RNS.sl(ac_loglevel) else None
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
from_interface = Transport.next_hop_interface(packet.destination_hash)
|
if not hasattr(from_interface, "mode"):
|
||||||
if from_interface == None or not hasattr(from_interface, "mode"):
|
|
||||||
should_transmit = False
|
should_transmit = False
|
||||||
if from_interface == None:
|
RNS.log("Blocking announce broadcast on "+str(interface)+" since next hop interface has no mode configured", ac_loglevel) if RNS.sl(ac_loglevel) else None
|
||||||
RNS.log("Blocking announce broadcast on "+str(interface)+" since next hop interface doesn't exist", RNS.LOG_EXTREME) if RNS.sl(RNS.LOG_EXTREME) else None
|
|
||||||
elif not hasattr(from_interface, "mode"):
|
|
||||||
RNS.log("Blocking announce broadcast on "+str(interface)+" since next hop interface has no mode configured", RNS.LOG_EXTREME) if RNS.sl(RNS.LOG_EXTREME) else None
|
|
||||||
else:
|
else:
|
||||||
if from_interface.mode == RNS.Interfaces.Interface.Interface.MODE_ROAMING:
|
if from_interface.mode == RNS.Interfaces.Interface.Interface.MODE_ROAMING:
|
||||||
RNS.log("Blocking announce broadcast on "+str(interface)+" due to roaming-mode next-hop interface", RNS.LOG_EXTREME) if RNS.sl(RNS.LOG_EXTREME) else None
|
RNS.log("Blocking announce broadcast on "+str(interface)+" due to roaming-mode next-hop interface", ac_loglevel) if RNS.sl(ac_loglevel) else None
|
||||||
should_transmit = False
|
should_transmit = False
|
||||||
elif from_interface.mode == RNS.Interfaces.Interface.Interface.MODE_BOUNDARY:
|
elif from_interface.mode == RNS.Interfaces.Interface.Interface.MODE_BOUNDARY:
|
||||||
RNS.log("Blocking announce broadcast on "+str(interface)+" due to boundary-mode next-hop interface", RNS.LOG_EXTREME) if RNS.sl(RNS.LOG_EXTREME) else None
|
RNS.log("Blocking announce broadcast on "+str(interface)+" due to boundary-mode next-hop interface", ac_loglevel) if RNS.sl(ac_loglevel) else None
|
||||||
should_transmit = False
|
should_transmit = False
|
||||||
|
|
||||||
elif interface.mode == RNS.Interfaces.Interface.Interface.MODE_BOUNDARY:
|
elif interface.mode == RNS.Interfaces.Interface.Interface.MODE_BOUNDARY:
|
||||||
local_destination = None
|
|
||||||
with Transport.destinations_map_lock:
|
|
||||||
if packet.destination_hash in Transport.destinations_map:
|
|
||||||
local_destination = Transport.destinations_map[packet.destination_hash]
|
|
||||||
|
|
||||||
if local_destination != None:
|
if local_destination != None:
|
||||||
# RNS.log("Allowing announce broadcast on boundary-mode interface from instance-local destination", RNS.LOG_EXTREME) if RNS.sl(RNS.LOG_EXTREME) else None
|
# RNS.log("Allowing announce broadcast on boundary-mode interface from instance-local destination", ac_loglevel) if RNS.sl(ac_loglevel) else None
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
from_interface = Transport.next_hop_interface(packet.destination_hash)
|
if not hasattr(from_interface, "mode"):
|
||||||
if from_interface == None or not hasattr(from_interface, "mode"):
|
|
||||||
should_transmit = False
|
should_transmit = False
|
||||||
if from_interface == None:
|
RNS.log("Blocking announce broadcast on "+str(interface)+" since next hop interface has no mode configured", ac_loglevel) if RNS.sl(ac_loglevel) else None
|
||||||
RNS.log("Blocking announce broadcast on "+str(interface)+" since next hop interface doesn't exist", RNS.LOG_EXTREME) if RNS.sl(RNS.LOG_EXTREME) else None
|
|
||||||
elif not hasattr(from_interface, "mode"):
|
|
||||||
RNS.log("Blocking announce broadcast on "+str(interface)+" since next hop interface has no mode configured", RNS.LOG_EXTREME) if RNS.sl(RNS.LOG_EXTREME) else None
|
|
||||||
else:
|
else:
|
||||||
if from_interface.mode == RNS.Interfaces.Interface.Interface.MODE_ROAMING:
|
if from_interface.mode == RNS.Interfaces.Interface.Interface.MODE_ROAMING:
|
||||||
RNS.log("Blocking announce broadcast on "+str(interface)+" due to roaming-mode next-hop interface", RNS.LOG_EXTREME) if RNS.sl(RNS.LOG_EXTREME) else None
|
RNS.log("Blocking announce broadcast on "+str(interface)+" due to roaming-mode next-hop interface", ac_loglevel) if RNS.sl(ac_loglevel) else None
|
||||||
should_transmit = False
|
should_transmit = False
|
||||||
|
|
||||||
else:
|
else:
|
||||||
@@ -1319,7 +1336,12 @@ class Transport:
|
|||||||
Transport.add_packet_hash(packet.packet_hash)
|
Transport.add_packet_hash(packet.packet_hash)
|
||||||
stored_hash = True
|
stored_hash = True
|
||||||
|
|
||||||
Transport.transmit(interface, packet.raw)
|
if not Transport.should_apply_delta(packet, interface): raw = packet.raw
|
||||||
|
else:
|
||||||
|
if packet.packet_type != RNS.Packet.ANNOUNCE: raw = Transport.mangle_hops(packet.raw, Transport.local_hops_delta)
|
||||||
|
else: raw = Transport.mangle_hops(packet.raw, Transport.local_hops_delta, transport_insert=packet.header_type==RNS.Packet.HEADER_1)
|
||||||
|
|
||||||
|
Transport.transmit(interface, raw)
|
||||||
if packet.packet_type == RNS.Packet.ANNOUNCE: interface.sent_announce()
|
if packet.packet_type == RNS.Packet.ANNOUNCE: interface.sent_announce()
|
||||||
if packet.destination.type == RNS.Destination.PLAIN and packet.is_outbound_pr: interface.sent_path_request()
|
if packet.destination.type == RNS.Destination.PLAIN and packet.is_outbound_pr: interface.sent_path_request()
|
||||||
packet_sent(packet)
|
packet_sent(packet)
|
||||||
@@ -1327,6 +1349,17 @@ class Transport:
|
|||||||
|
|
||||||
return sent
|
return sent
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def should_apply_delta(packet, interface):
|
||||||
|
return not Transport.owner.is_connected_to_shared_instance and packet.hops == 0 and Transport.local_hops_delta != 0 \
|
||||||
|
and not packet.destination.type == RNS.Destination.PLAIN and not packet.destination.type == RNS.Destination.GROUP \
|
||||||
|
and not interface in Transport.local_client_interfaces and not Transport.is_shared_instance(interface)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def mangle_hops(raw, hops, transport_insert=False):
|
||||||
|
if not transport_insert: return raw[0:1]+struct.pack("!B", hops)+raw[2:]
|
||||||
|
else: return struct.pack("!B", ord(raw[0:1]) | (RNS.Packet.HEADER_2<<6) | (Transport.TRANSPORT<<4))+struct.pack("!B", hops)+Transport.identity.hash+raw[2:]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def add_packet_hash(packet_hash):
|
def add_packet_hash(packet_hash):
|
||||||
if not Transport.owner.is_connected_to_shared_instance:
|
if not Transport.owner.is_connected_to_shared_instance:
|
||||||
@@ -1406,10 +1439,8 @@ class Transport:
|
|||||||
ifac = raw[2:2+interface.ifac_size]
|
ifac = raw[2:2+interface.ifac_size]
|
||||||
|
|
||||||
# Generate mask
|
# Generate mask
|
||||||
mask = RNS.Cryptography.hkdf(length=len(raw),
|
mask = RNS.Cryptography.hkdf(length=len(raw), derive_from=ifac,
|
||||||
derive_from=ifac,
|
salt=interface.ifac_key, context=None)
|
||||||
salt=interface.ifac_key,
|
|
||||||
context=None)
|
|
||||||
# Unmask payload
|
# Unmask payload
|
||||||
i = 0; unmasked_raw = b""
|
i = 0; unmasked_raw = b""
|
||||||
for byte in raw:
|
for byte in raw:
|
||||||
@@ -1511,9 +1542,13 @@ class Transport:
|
|||||||
# through a shared Reticulum instance
|
# through a shared Reticulum instance
|
||||||
from_local_client = (packet.receiving_interface in Transport.local_client_interfaces)
|
from_local_client = (packet.receiving_interface in Transport.local_client_interfaces)
|
||||||
for_local_client = (packet.packet_type != RNS.Packet.ANNOUNCE) and (packet.destination_hash in Transport.path_table and Transport.path_table[packet.destination_hash][IDX_PT_HOPS] == 0)
|
for_local_client = (packet.packet_type != RNS.Packet.ANNOUNCE) and (packet.destination_hash in Transport.path_table and Transport.path_table[packet.destination_hash][IDX_PT_HOPS] == 0)
|
||||||
for_local_client_link = (packet.packet_type != RNS.Packet.ANNOUNCE) and (packet.destination_hash in Transport.link_table and Transport.link_table[packet.destination_hash][IDX_LT_RCVD_IF] in Transport.local_client_interfaces)
|
local_client_nh = (packet.packet_type != RNS.Packet.ANNOUNCE) and (packet.destination_hash in Transport.link_table and Transport.link_table[packet.destination_hash][IDX_LT_NH_IF] in Transport.local_client_interfaces)
|
||||||
for_local_client_link |= (packet.packet_type != RNS.Packet.ANNOUNCE) and (packet.destination_hash in Transport.link_table and Transport.link_table[packet.destination_hash][IDX_LT_NH_IF] in Transport.local_client_interfaces)
|
local_client_rh = (packet.packet_type != RNS.Packet.ANNOUNCE) and (packet.destination_hash in Transport.link_table and Transport.link_table[packet.destination_hash][IDX_LT_RCVD_IF] in Transport.local_client_interfaces)
|
||||||
proof_for_local_client = (packet.destination_hash in Transport.reverse_table) and (Transport.reverse_table[packet.destination_hash][IDX_RT_RCVD_IF] in Transport.local_client_interfaces)
|
proof_for_local_client = (packet.destination_hash in Transport.reverse_table) and (Transport.reverse_table[packet.destination_hash][IDX_RT_RCVD_IF] in Transport.local_client_interfaces)
|
||||||
|
to_local_client = for_local_client or proof_for_local_client
|
||||||
|
instance_local_link = local_client_nh and local_client_rh
|
||||||
|
for_local_client_link = local_client_nh or local_client_rh
|
||||||
|
link_request_handled = False
|
||||||
|
|
||||||
# Plain broadcast packets from local clients are sent
|
# Plain broadcast packets from local clients are sent
|
||||||
# directly on all attached interfaces, since they are
|
# directly on all attached interfaces, since they are
|
||||||
@@ -1575,10 +1610,23 @@ class Transport:
|
|||||||
new_raw += struct.pack("!B", packet.hops)
|
new_raw += struct.pack("!B", packet.hops)
|
||||||
new_raw += packet.raw[(RNS.Identity.TRUNCATED_HASHLENGTH//8)+2:]
|
new_raw += packet.raw[(RNS.Identity.TRUNCATED_HASHLENGTH//8)+2:]
|
||||||
elif remaining_hops == 0:
|
elif remaining_hops == 0:
|
||||||
# Just increase hop count and transmit
|
if to_local_client and RNS.Transport.local_hops_delta != 0:
|
||||||
new_raw = packet.raw[0:1]
|
if packet.header_type == RNS.Packet.HEADER_2:
|
||||||
new_raw += struct.pack("!B", packet.hops)
|
# Strip transport headers and transmit
|
||||||
new_raw += packet.raw[2:]
|
new_flags = (RNS.Packet.HEADER_1) << 6 | (Transport.BROADCAST) << 4 | (packet.flags & 0b00001111)
|
||||||
|
new_raw = struct.pack("!B", new_flags)
|
||||||
|
new_raw += struct.pack("!B", packet.hops)
|
||||||
|
new_raw += packet.raw[(RNS.Identity.TRUNCATED_HASHLENGTH//8)+2:]
|
||||||
|
else:
|
||||||
|
# Just increase hop count and transmit
|
||||||
|
new_raw = packet.raw[0:1]
|
||||||
|
new_raw += struct.pack("!B", packet.hops)
|
||||||
|
new_raw += packet.raw[2:]
|
||||||
|
else:
|
||||||
|
# Just increase hop count and transmit
|
||||||
|
new_raw = packet.raw[0:1]
|
||||||
|
new_raw += struct.pack("!B", packet.hops)
|
||||||
|
new_raw += packet.raw[2:]
|
||||||
|
|
||||||
outbound_interface = Transport.path_table[packet.destination_hash][IDX_PT_RVCD_IF]
|
outbound_interface = Transport.path_table[packet.destination_hash][IDX_PT_RVCD_IF]
|
||||||
|
|
||||||
@@ -1593,11 +1641,11 @@ class Transport:
|
|||||||
nh_mtu = outbound_interface.HW_MTU
|
nh_mtu = outbound_interface.HW_MTU
|
||||||
if path_mtu:
|
if path_mtu:
|
||||||
if outbound_interface.HW_MTU == None:
|
if outbound_interface.HW_MTU == None:
|
||||||
RNS.log(f"No next-hop HW MTU, disabling link MTU upgrade", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
|
RNS.log(f"No next-hop HW MTU, disabling link MTU upgrade", RNS.LOG_PATHING) if RNS.sl(RNS.LOG_PATHING) else None
|
||||||
path_mtu = None
|
path_mtu = None
|
||||||
new_raw = new_raw[:-RNS.Link.LINK_MTU_SIZE]
|
new_raw = new_raw[:-RNS.Link.LINK_MTU_SIZE]
|
||||||
elif not outbound_interface.AUTOCONFIGURE_MTU and not outbound_interface.FIXED_MTU:
|
elif not outbound_interface.AUTOCONFIGURE_MTU and not outbound_interface.FIXED_MTU:
|
||||||
RNS.log(f"Outbound interface doesn't support MTU autoconfiguration, disabling link MTU upgrade", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
|
RNS.log(f"Outbound interface doesn't support MTU autoconfiguration, disabling link MTU upgrade", RNS.LOG_PATHING) if RNS.sl(RNS.LOG_PATHING) else None
|
||||||
path_mtu = None
|
path_mtu = None
|
||||||
new_raw = new_raw[:-RNS.Link.LINK_MTU_SIZE]
|
new_raw = new_raw[:-RNS.Link.LINK_MTU_SIZE]
|
||||||
else:
|
else:
|
||||||
@@ -1605,7 +1653,7 @@ class Transport:
|
|||||||
try:
|
try:
|
||||||
path_mtu = min(nh_mtu, ph_mtu)
|
path_mtu = min(nh_mtu, ph_mtu)
|
||||||
clamped_mtu = RNS.Link.signalling_bytes(path_mtu, mode)
|
clamped_mtu = RNS.Link.signalling_bytes(path_mtu, mode)
|
||||||
RNS.log(f"Clamping link MTU to {RNS.prettysize(path_mtu)}", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
|
RNS.log(f"Clamping link MTU to {RNS.prettysize(path_mtu)}", RNS.LOG_PATHING) if RNS.sl(RNS.LOG_PATHING) else None
|
||||||
new_raw = new_raw[:-RNS.Link.LINK_MTU_SIZE]+clamped_mtu
|
new_raw = new_raw[:-RNS.Link.LINK_MTU_SIZE]+clamped_mtu
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
RNS.log(f"Dropping link request packet. The contained exception was: {e}", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
|
RNS.log(f"Dropping link request packet. The contained exception was: {e}", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
|
||||||
@@ -1623,6 +1671,7 @@ class Transport:
|
|||||||
proof_timeout ] # 8: Proof timeout timestamp
|
proof_timeout ] # 8: Proof timeout timestamp
|
||||||
|
|
||||||
with Transport.link_table_lock: Transport.link_table[RNS.Link.link_id_from_lr_packet(packet)] = link_entry
|
with Transport.link_table_lock: Transport.link_table[RNS.Link.link_id_from_lr_packet(packet)] = link_entry
|
||||||
|
link_request_handled = True
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# Entry format is
|
# Entry format is
|
||||||
@@ -1632,6 +1681,7 @@ class Transport:
|
|||||||
|
|
||||||
with Transport.reverse_table_lock: Transport.reverse_table[packet.getTruncatedHash()] = reverse_entry
|
with Transport.reverse_table_lock: Transport.reverse_table[packet.getTruncatedHash()] = reverse_entry
|
||||||
|
|
||||||
|
if Transport.local_hops_delta != 0 and from_local_client and not to_local_client: new_raw = Transport.mangle_hops(new_raw, Transport.local_hops_delta)
|
||||||
Transport.transmit(outbound_interface, new_raw)
|
Transport.transmit(outbound_interface, new_raw)
|
||||||
with Transport.path_table_lock: Transport.path_table[packet.destination_hash][IDX_PT_TIMESTAMP] = time.time()
|
with Transport.path_table_lock: Transport.path_table[packet.destination_hash][IDX_PT_TIMESTAMP] = time.time()
|
||||||
|
|
||||||
@@ -1675,7 +1725,7 @@ class Transport:
|
|||||||
Transport.add_packet_hash(packet.packet_hash)
|
Transport.add_packet_hash(packet.packet_hash)
|
||||||
|
|
||||||
new_raw = packet.raw[0:1]
|
new_raw = packet.raw[0:1]
|
||||||
new_raw += struct.pack("!B", packet.hops)
|
new_raw += struct.pack("!B", packet.hops if not from_local_client or instance_local_link or Transport.local_hops_delta == 0 else Transport.local_hops_delta)
|
||||||
new_raw += packet.raw[2:]
|
new_raw += packet.raw[2:]
|
||||||
Transport.transmit(outbound_interface, new_raw)
|
Transport.transmit(outbound_interface, new_raw)
|
||||||
Transport.link_table[packet.destination_hash][IDX_LT_TIMESTAMP] = time.time()
|
Transport.link_table[packet.destination_hash][IDX_LT_TIMESTAMP] = time.time()
|
||||||
@@ -1737,8 +1787,7 @@ class Transport:
|
|||||||
with Transport.announce_table_lock:
|
with Transport.announce_table_lock:
|
||||||
if packet.destination_hash in Transport.announce_table: Transport.announce_table.pop(packet.destination_hash)
|
if packet.destination_hash in Transport.announce_table: Transport.announce_table.pop(packet.destination_hash)
|
||||||
|
|
||||||
else:
|
else: received_from = packet.destination_hash
|
||||||
received_from = packet.destination_hash
|
|
||||||
|
|
||||||
# Check if this announce should be inserted into
|
# Check if this announce should be inserted into
|
||||||
# announce and destination tables
|
# announce and destination tables
|
||||||
@@ -1755,6 +1804,7 @@ class Transport:
|
|||||||
random_blob = packet.data[RNS.Identity.KEYSIZE//8+RNS.Identity.NAME_HASH_LENGTH//8:RNS.Identity.KEYSIZE//8+RNS.Identity.NAME_HASH_LENGTH//8+10]
|
random_blob = packet.data[RNS.Identity.KEYSIZE//8+RNS.Identity.NAME_HASH_LENGTH//8:RNS.Identity.KEYSIZE//8+RNS.Identity.NAME_HASH_LENGTH//8+10]
|
||||||
random_blobs = []
|
random_blobs = []
|
||||||
with Transport.inbound_announce_lock:
|
with Transport.inbound_announce_lock:
|
||||||
|
announced_destination_known |= packet.destination_hash in Transport.path_table
|
||||||
if announced_destination_known:
|
if announced_destination_known:
|
||||||
random_blobs = Transport.path_table[packet.destination_hash][IDX_PT_RANDBLOBS]
|
random_blobs = Transport.path_table[packet.destination_hash][IDX_PT_RANDBLOBS]
|
||||||
|
|
||||||
@@ -1796,7 +1846,7 @@ class Transport:
|
|||||||
if not random_blob in random_blobs:
|
if not random_blob in random_blobs:
|
||||||
# TODO: Check that this ^ approach actually
|
# TODO: Check that this ^ approach actually
|
||||||
# works under all circumstances
|
# works under all circumstances
|
||||||
RNS.log("Replacing destination table entry for "+str(RNS.prettyhexrep(packet.destination_hash))+" with new announce due to expired path", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
|
RNS.log("Replacing path table entry for "+str(RNS.prettyhexrep(packet.destination_hash))+" with new announce due to expired path", RNS.LOG_PATHING) if RNS.sl(RNS.LOG_PATHING) else None
|
||||||
Transport.mark_path_unknown_state(packet.destination_hash)
|
Transport.mark_path_unknown_state(packet.destination_hash)
|
||||||
should_add = True
|
should_add = True
|
||||||
else:
|
else:
|
||||||
@@ -1807,7 +1857,7 @@ class Transport:
|
|||||||
# this announce before, update the path table.
|
# this announce before, update the path table.
|
||||||
if (announce_emitted > path_announce_emitted):
|
if (announce_emitted > path_announce_emitted):
|
||||||
if not random_blob in random_blobs:
|
if not random_blob in random_blobs:
|
||||||
RNS.log("Replacing destination table entry for "+str(RNS.prettyhexrep(packet.destination_hash))+" with new announce, since it was more recently emitted", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
|
RNS.log("Replacing path table entry for "+str(RNS.prettyhexrep(packet.destination_hash))+" with new announce, since it was more recently emitted", RNS.LOG_PATHING) if RNS.sl(RNS.LOG_PATHING) else None
|
||||||
Transport.mark_path_unknown_state(packet.destination_hash)
|
Transport.mark_path_unknown_state(packet.destination_hash)
|
||||||
should_add = True
|
should_add = True
|
||||||
else:
|
else:
|
||||||
@@ -1819,7 +1869,7 @@ class Transport:
|
|||||||
# allow updating the path table to this one.
|
# allow updating the path table to this one.
|
||||||
elif announce_emitted == path_announce_emitted:
|
elif announce_emitted == path_announce_emitted:
|
||||||
if Transport.path_is_unresponsive(packet.destination_hash):
|
if Transport.path_is_unresponsive(packet.destination_hash):
|
||||||
RNS.log("Replacing destination table entry for "+str(RNS.prettyhexrep(packet.destination_hash))+" with new announce, since previously tried path was unresponsive", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
|
RNS.log("Replacing path table entry for "+str(RNS.prettyhexrep(packet.destination_hash))+" with new announce, since previously tried path was unresponsive", RNS.LOG_PATHING) if RNS.sl(RNS.LOG_PATHING) else None
|
||||||
should_add = True
|
should_add = True
|
||||||
else:
|
else:
|
||||||
should_add = False
|
should_add = False
|
||||||
@@ -1827,7 +1877,6 @@ class Transport:
|
|||||||
else:
|
else:
|
||||||
# If this destination is unknown in our table
|
# If this destination is unknown in our table
|
||||||
# we should add it
|
# we should add it
|
||||||
Transport.mark_path_unknown_state(packet.destination_hash)
|
|
||||||
should_add = True
|
should_add = True
|
||||||
|
|
||||||
if should_add:
|
if should_add:
|
||||||
@@ -1886,7 +1935,7 @@ class Transport:
|
|||||||
if (RNS.Reticulum.transport_enabled() or is_from_local_client) and packet.context != RNS.Packet.PATH_RESPONSE:
|
if (RNS.Reticulum.transport_enabled() or is_from_local_client) and packet.context != RNS.Packet.PATH_RESPONSE:
|
||||||
# Insert announce into announce table for retransmission
|
# Insert announce into announce table for retransmission
|
||||||
|
|
||||||
if rate_blocked: RNS.log("Blocking rebroadcast of announce from "+RNS.prettyhexrep(packet.destination_hash)+" due to excessive announce rate", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
|
if rate_blocked: RNS.log("Blocking rebroadcast of announce from "+RNS.prettyhexrep(packet.destination_hash)+" due to excessive announce rate", RNS.LOG_PATHING) if RNS.sl(RNS.LOG_PATHING) else None
|
||||||
else:
|
else:
|
||||||
if is_from_local_client:
|
if is_from_local_client:
|
||||||
# If the announce is from a local client,
|
# If the announce is from a local client,
|
||||||
@@ -1944,17 +1993,10 @@ class Transport:
|
|||||||
if is_from_local_client and packet.context == RNS.Packet.PATH_RESPONSE:
|
if is_from_local_client and packet.context == RNS.Packet.PATH_RESPONSE:
|
||||||
for local_interface in Transport.local_client_interfaces:
|
for local_interface in Transport.local_client_interfaces:
|
||||||
if packet.receiving_interface != local_interface:
|
if packet.receiving_interface != local_interface:
|
||||||
new_announce = RNS.Packet(
|
new_announce = RNS.Packet(announce_destination, announce_data, RNS.Packet.ANNOUNCE, # <-- This one?
|
||||||
announce_destination,
|
context = announce_context, header_type = RNS.Packet.HEADER_2,
|
||||||
announce_data,
|
transport_type = Transport.TRANSPORT, transport_id = Transport.identity.hash,
|
||||||
RNS.Packet.ANNOUNCE, # <-- This one?
|
attached_interface = local_interface, context_flag = packet.context_flag)
|
||||||
context = announce_context,
|
|
||||||
header_type = RNS.Packet.HEADER_2,
|
|
||||||
transport_type = Transport.TRANSPORT,
|
|
||||||
transport_id = Transport.identity.hash,
|
|
||||||
attached_interface = local_interface,
|
|
||||||
context_flag = packet.context_flag,
|
|
||||||
)
|
|
||||||
|
|
||||||
new_announce.hops = packet.hops
|
new_announce.hops = packet.hops
|
||||||
new_announce.send()
|
new_announce.send()
|
||||||
@@ -1962,17 +2004,10 @@ class Transport:
|
|||||||
else:
|
else:
|
||||||
for local_interface in Transport.local_client_interfaces:
|
for local_interface in Transport.local_client_interfaces:
|
||||||
if packet.receiving_interface != local_interface:
|
if packet.receiving_interface != local_interface:
|
||||||
new_announce = RNS.Packet(
|
new_announce = RNS.Packet(announce_destination, announce_data, RNS.Packet.ANNOUNCE,
|
||||||
announce_destination,
|
context = announce_context, header_type = RNS.Packet.HEADER_2,
|
||||||
announce_data,
|
transport_type = Transport.TRANSPORT, transport_id = Transport.identity.hash,
|
||||||
RNS.Packet.ANNOUNCE,
|
attached_interface = local_interface, context_flag = packet.context_flag)
|
||||||
context = announce_context,
|
|
||||||
header_type = RNS.Packet.HEADER_2,
|
|
||||||
transport_type = Transport.TRANSPORT,
|
|
||||||
transport_id = Transport.identity.hash,
|
|
||||||
attached_interface = local_interface,
|
|
||||||
context_flag = packet.context_flag,
|
|
||||||
)
|
|
||||||
|
|
||||||
new_announce.hops = packet.hops
|
new_announce.hops = packet.hops
|
||||||
new_announce.send()
|
new_announce.send()
|
||||||
@@ -1986,7 +2021,7 @@ class Transport:
|
|||||||
|
|
||||||
interface_str = " on "+str(attached_interface)
|
interface_str = " on "+str(attached_interface)
|
||||||
|
|
||||||
RNS.log("Got matching announce, answering waiting discovery path request for "+RNS.prettyhexrep(packet.destination_hash)+interface_str, Transport.PR_LOGLEVEL) if RNS.sl(Transport.PR_LOGLEVEL) else None
|
RNS.log("Got matching announce, answering waiting discovery path request for "+RNS.prettyhexrep(packet.destination_hash)+interface_str, RNS.LOG_PATHING) if RNS.sl(RNS.LOG_PATHING) else None
|
||||||
announce_identity = RNS.Identity.recall(packet.destination_hash, _no_use=False)
|
announce_identity = RNS.Identity.recall(packet.destination_hash, _no_use=False)
|
||||||
announce_destination = RNS.Destination(announce_identity, RNS.Destination.OUT, RNS.Destination.SINGLE, "unknown", "unknown");
|
announce_destination = RNS.Destination(announce_identity, RNS.Destination.OUT, RNS.Destination.SINGLE, "unknown", "unknown");
|
||||||
announce_destination.hash = packet.destination_hash
|
announce_destination.hash = packet.destination_hash
|
||||||
@@ -1994,17 +2029,10 @@ class Transport:
|
|||||||
announce_context = RNS.Packet.NONE
|
announce_context = RNS.Packet.NONE
|
||||||
announce_data = packet.data
|
announce_data = packet.data
|
||||||
|
|
||||||
new_announce = RNS.Packet(
|
new_announce = RNS.Packet(announce_destination, announce_data, RNS.Packet.ANNOUNCE,
|
||||||
announce_destination,
|
context = RNS.Packet.PATH_RESPONSE, header_type = RNS.Packet.HEADER_2,
|
||||||
announce_data,
|
transport_type = Transport.TRANSPORT, transport_id = Transport.identity.hash,
|
||||||
RNS.Packet.ANNOUNCE,
|
attached_interface = attached_interface, context_flag = packet.context_flag)
|
||||||
context = RNS.Packet.PATH_RESPONSE,
|
|
||||||
header_type = RNS.Packet.HEADER_2,
|
|
||||||
transport_type = Transport.TRANSPORT,
|
|
||||||
transport_id = Transport.identity.hash,
|
|
||||||
attached_interface = attached_interface,
|
|
||||||
context_flag = packet.context_flag,
|
|
||||||
)
|
|
||||||
|
|
||||||
new_announce.hops = packet.hops
|
new_announce.hops = packet.hops
|
||||||
new_announce.send()
|
new_announce.send()
|
||||||
@@ -2013,7 +2041,7 @@ class Transport:
|
|||||||
path_table_entry = [now, received_from, announce_hops, expires, random_blobs, packet.receiving_interface, packet.packet_hash]
|
path_table_entry = [now, received_from, announce_hops, expires, random_blobs, packet.receiving_interface, packet.packet_hash]
|
||||||
with Transport.path_table_lock: Transport.path_table[packet.destination_hash] = path_table_entry
|
with Transport.path_table_lock: Transport.path_table[packet.destination_hash] = path_table_entry
|
||||||
Transport.mark_path_unknown_state(packet.destination_hash)
|
Transport.mark_path_unknown_state(packet.destination_hash)
|
||||||
RNS.log("Destination "+RNS.prettyhexrep(packet.destination_hash)+" is now "+str(announce_hops)+" hops away via "+RNS.prettyhexrep(received_from)+" on "+str(packet.receiving_interface), RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
|
RNS.log("Destination "+RNS.prettyhexrep(packet.destination_hash)+" is now "+str(announce_hops)+" hops away via "+RNS.prettyhexrep(received_from)+" on "+str(packet.receiving_interface), RNS.LOG_PATHING) if RNS.sl(RNS.LOG_PATHING) else None
|
||||||
if packet.destination_hash in Transport.path_requests:
|
if packet.destination_hash in Transport.path_requests:
|
||||||
RNS.Reticulum.get_instance()._used_destination_data(packet.destination_hash)
|
RNS.Reticulum.get_instance()._used_destination_data(packet.destination_hash)
|
||||||
|
|
||||||
@@ -2021,15 +2049,14 @@ class Transport:
|
|||||||
# announce to the tunnels table
|
# announce to the tunnels table
|
||||||
if hasattr(packet.receiving_interface, "tunnel_id") and packet.receiving_interface.tunnel_id != None:
|
if hasattr(packet.receiving_interface, "tunnel_id") and packet.receiving_interface.tunnel_id != None:
|
||||||
with Transport.tunnels_lock:
|
with Transport.tunnels_lock:
|
||||||
if not packet.receiving_interface.tunnel_id in Transport.tunnels:
|
if not packet.receiving_interface.tunnel_id in Transport.tunnels: RNS.log(f"Tunnel ID for {packet.receiving_interface} was not found in tunnel table", RNS.LOG_WARNING)
|
||||||
RNS.log(f"Tunnel ID for {packet.receiving_interface} was not found in tunnel table", RNS.LOG_WARNING)
|
|
||||||
else:
|
else:
|
||||||
tunnel_entry = Transport.tunnels[packet.receiving_interface.tunnel_id]
|
tunnel_entry = Transport.tunnels[packet.receiving_interface.tunnel_id]
|
||||||
paths = tunnel_entry[IDX_TT_PATHS]
|
paths = tunnel_entry[IDX_TT_PATHS]
|
||||||
paths[packet.destination_hash] = [now, received_from, announce_hops, expires, random_blobs, None, packet.packet_hash]
|
paths[packet.destination_hash] = [now, received_from, announce_hops, expires, random_blobs, None, packet.packet_hash]
|
||||||
expires = time.time() + Transport.TUNNEL_TIMEOUT
|
expires = time.time() + Transport.TUNNEL_TIMEOUT
|
||||||
tunnel_entry[IDX_TT_EXPIRES] = expires
|
tunnel_entry[IDX_TT_EXPIRES] = expires
|
||||||
RNS.log("Path to "+RNS.prettyhexrep(packet.destination_hash)+" associated with tunnel "+RNS.prettyhexrep(packet.receiving_interface.tunnel_id), RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
|
RNS.log("Path to "+RNS.prettyhexrep(packet.destination_hash)+" associated with tunnel "+RNS.prettyhexrep(packet.receiving_interface.tunnel_id), RNS.LOG_PATHING) if RNS.sl(RNS.LOG_PATHING) else None
|
||||||
|
|
||||||
# Call externally registered callbacks from apps
|
# Call externally registered callbacks from apps
|
||||||
# wanting to know when an announce arrives
|
# wanting to know when an announce arrives
|
||||||
@@ -2040,10 +2067,10 @@ class Transport:
|
|||||||
# the handlers aspect filter
|
# the handlers aspect filter
|
||||||
execute_callback = False
|
execute_callback = False
|
||||||
announce_identity = RNS.Identity.recall(packet.destination_hash, _no_use=True)
|
announce_identity = RNS.Identity.recall(packet.destination_hash, _no_use=True)
|
||||||
if handler.aspect_filter == None:
|
|
||||||
# If the handlers aspect filter is set to
|
# If the handlers aspect filter is set to
|
||||||
# None, we execute the callback in all cases
|
# None, we execute the callback in all cases
|
||||||
execute_callback = True
|
if handler.aspect_filter == None: execute_callback = True
|
||||||
else:
|
else:
|
||||||
handler_expected_hash = RNS.Destination.hash_from_name_and_identity(handler.aspect_filter, announce_identity)
|
handler_expected_hash = RNS.Destination.hash_from_name_and_identity(handler.aspect_filter, announce_identity)
|
||||||
if packet.destination_hash == handler_expected_hash: execute_callback = True
|
if packet.destination_hash == handler_expected_hash: execute_callback = True
|
||||||
@@ -2088,7 +2115,7 @@ class Transport:
|
|||||||
RNS.trace_exception(e)
|
RNS.trace_exception(e)
|
||||||
|
|
||||||
# Handling for link requests to local destinations
|
# Handling for link requests to local destinations
|
||||||
elif packet.packet_type == RNS.Packet.LINKREQUEST:
|
elif packet.packet_type == RNS.Packet.LINKREQUEST and not link_request_handled:
|
||||||
if packet.transport_id == None or packet.transport_id == Transport.identity.hash:
|
if packet.transport_id == None or packet.transport_id == Transport.identity.hash:
|
||||||
destination = None
|
destination = None
|
||||||
with Transport.destinations_map_lock:
|
with Transport.destinations_map_lock:
|
||||||
@@ -2131,7 +2158,7 @@ class Transport:
|
|||||||
if packet.context == RNS.Packet.CACHE_REQUEST:
|
if packet.context == RNS.Packet.CACHE_REQUEST:
|
||||||
cached_packet = Transport.get_cached_packet(packet.data)
|
cached_packet = Transport.get_cached_packet(packet.data)
|
||||||
if cached_packet != None:
|
if cached_packet != None:
|
||||||
cached_packet.unpack()
|
if not cached_packet.unpack(): return
|
||||||
RNS.Packet(destination=link, data=cached_packet.data,
|
RNS.Packet(destination=link, data=cached_packet.data,
|
||||||
packet_type=cached_packet.packet_type, context=cached_packet.context).send()
|
packet_type=cached_packet.packet_type, context=cached_packet.context).send()
|
||||||
|
|
||||||
@@ -2158,13 +2185,11 @@ class Transport:
|
|||||||
packet.destination = destination
|
packet.destination = destination
|
||||||
if destination.receive(packet):
|
if destination.receive(packet):
|
||||||
if destination.proof_strategy == RNS.Destination.PROVE_ALL: packet.prove()
|
if destination.proof_strategy == RNS.Destination.PROVE_ALL: packet.prove()
|
||||||
|
|
||||||
elif destination.proof_strategy == RNS.Destination.PROVE_APP:
|
elif destination.proof_strategy == RNS.Destination.PROVE_APP:
|
||||||
if destination.callbacks.proof_requested:
|
if destination.callbacks.proof_requested:
|
||||||
try:
|
try:
|
||||||
if destination.callbacks.proof_requested(packet): packet.prove()
|
if destination.callbacks.proof_requested(packet): packet.prove()
|
||||||
except Exception as e:
|
except Exception as e: RNS.log("Error while executing proof request callback. The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||||
RNS.log("Error while executing proof request callback. The contained exception was: "+str(e), RNS.LOG_ERROR)
|
|
||||||
|
|
||||||
# Handling for proofs and link-request proofs
|
# Handling for proofs and link-request proofs
|
||||||
elif packet.packet_type == RNS.Packet.PROOF:
|
elif packet.packet_type == RNS.Packet.PROOF:
|
||||||
@@ -2191,19 +2216,17 @@ class Transport:
|
|||||||
if peer_identity.validate(signature, signed_data):
|
if peer_identity.validate(signature, signed_data):
|
||||||
RNS.log("Link request proof validated for transport via "+str(link_entry[IDX_LT_RCVD_IF]), RNS.LOG_EXTREME) if RNS.sl(RNS.LOG_EXTREME) else None
|
RNS.log("Link request proof validated for transport via "+str(link_entry[IDX_LT_RCVD_IF]), RNS.LOG_EXTREME) if RNS.sl(RNS.LOG_EXTREME) else None
|
||||||
new_raw = packet.raw[0:1]
|
new_raw = packet.raw[0:1]
|
||||||
new_raw += struct.pack("!B", packet.hops)
|
new_raw += struct.pack("!B", packet.hops if not from_local_client or instance_local_link or Transport.local_hops_delta == 0 else Transport.local_hops_delta)
|
||||||
new_raw += packet.raw[2:]
|
new_raw += packet.raw[2:]
|
||||||
Transport.link_table[packet.destination_hash][IDX_LT_VALIDATED] = True
|
Transport.link_table[packet.destination_hash][IDX_LT_VALIDATED] = True
|
||||||
Transport.transmit(link_entry[IDX_LT_RCVD_IF], new_raw)
|
Transport.transmit(link_entry[IDX_LT_RCVD_IF], new_raw)
|
||||||
if not Transport.owner.is_connected_to_shared_instance:
|
if not Transport.owner.is_connected_to_shared_instance:
|
||||||
RNS.Identity._used_destination_data(link_entry[IDX_LT_DSTHASH])
|
RNS.Identity._used_destination_data(link_entry[IDX_LT_DSTHASH])
|
||||||
|
|
||||||
else:
|
else: RNS.log("Invalid link request proof in transport for link "+RNS.prettyhexrep(packet.destination_hash)+", dropping proof.", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
|
||||||
RNS.log("Invalid link request proof in transport for link "+RNS.prettyhexrep(packet.destination_hash)+", dropping proof.", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
|
|
||||||
|
|
||||||
except Exception as e: RNS.log("Could not transport link request proof. The contained exception was: "+str(e), RNS.LOG_DEBUG) if RNS.sl(LOG_DEBUG) else None
|
except Exception as e: RNS.log("Could not transport link request proof. The contained exception was: "+str(e), RNS.LOG_DEBUG) if RNS.sl(LOG_DEBUG) else None
|
||||||
else: RNS.log("Link request proof received on wrong interface, not transporting it.", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
|
else: RNS.log("Link request proof received on wrong interface, not transporting it.", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
|
||||||
else: RNS.log("Received link request proof with hop mismatch, not transporting it", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
|
else: RNS.log(f"Received link request proof with hop mismatch ({packet.hops}/{link_entry[IDX_LT_NH_IF]}), not transporting it", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# Check if we can deliver it to a local
|
# Check if we can deliver it to a local
|
||||||
@@ -2258,7 +2281,7 @@ class Transport:
|
|||||||
if packet.receiving_interface == reverse_entry[IDX_RT_OUTB_IF]:
|
if packet.receiving_interface == reverse_entry[IDX_RT_OUTB_IF]:
|
||||||
RNS.log("Proof received on correct interface, transporting it via "+str(reverse_entry[IDX_RT_RCVD_IF]), RNS.LOG_EXTREME) if RNS.sl(RNS.LOG_EXTREME) else None
|
RNS.log("Proof received on correct interface, transporting it via "+str(reverse_entry[IDX_RT_RCVD_IF]), RNS.LOG_EXTREME) if RNS.sl(RNS.LOG_EXTREME) else None
|
||||||
new_raw = packet.raw[0:1]
|
new_raw = packet.raw[0:1]
|
||||||
new_raw += struct.pack("!B", packet.hops)
|
new_raw += struct.pack("!B", packet.hops if not from_local_client or proof_for_local_client or Transport.local_hops_delta == 0 else Transport.local_hops_delta)
|
||||||
new_raw += packet.raw[2:]
|
new_raw += packet.raw[2:]
|
||||||
Transport.transmit(reverse_entry[IDX_RT_RCVD_IF], new_raw)
|
Transport.transmit(reverse_entry[IDX_RT_RCVD_IF], new_raw)
|
||||||
else:
|
else:
|
||||||
@@ -2339,14 +2362,14 @@ class Transport:
|
|||||||
def handle_tunnel(tunnel_id, interface):
|
def handle_tunnel(tunnel_id, interface):
|
||||||
expires = time.time() + Transport.TUNNEL_TIMEOUT
|
expires = time.time() + Transport.TUNNEL_TIMEOUT
|
||||||
if not tunnel_id in Transport.tunnels:
|
if not tunnel_id in Transport.tunnels:
|
||||||
RNS.log("Tunnel endpoint "+RNS.prettyhexrep(tunnel_id)+" established.", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
|
RNS.log("Tunnel endpoint "+RNS.prettyhexrep(tunnel_id)+" established.", RNS.LOG_PATHING) if RNS.sl(RNS.LOG_PATHING) else None
|
||||||
paths = {}
|
paths = {}
|
||||||
with Transport.tunnels_lock:
|
with Transport.tunnels_lock:
|
||||||
tunnel_entry = [tunnel_id, interface, paths, expires]
|
tunnel_entry = [tunnel_id, interface, paths, expires]
|
||||||
interface.tunnel_id = tunnel_id
|
interface.tunnel_id = tunnel_id
|
||||||
Transport.tunnels[tunnel_id] = tunnel_entry
|
Transport.tunnels[tunnel_id] = tunnel_entry
|
||||||
else:
|
else:
|
||||||
RNS.log("Tunnel endpoint "+RNS.prettyhexrep(tunnel_id)+" reappeared. Restoring paths...", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
|
RNS.log("Tunnel endpoint "+RNS.prettyhexrep(tunnel_id)+" reappeared. Restoring paths...", RNS.LOG_PATHING) if RNS.sl(RNS.LOG_PATHING) else None
|
||||||
tunnel_entry = Transport.tunnels[tunnel_id]
|
tunnel_entry = Transport.tunnels[tunnel_id]
|
||||||
tunnel_entry[IDX_TT_IF] = interface
|
tunnel_entry[IDX_TT_IF] = interface
|
||||||
tunnel_entry[IDX_TT_EXPIRES] = expires
|
tunnel_entry[IDX_TT_EXPIRES] = expires
|
||||||
@@ -2375,21 +2398,21 @@ class Transport:
|
|||||||
current_path_timebase = Transport.timebase_from_random_blobs(current_random_blobs)
|
current_path_timebase = Transport.timebase_from_random_blobs(current_random_blobs)
|
||||||
tunnel_announce_timebase = Transport.timebase_from_random_blobs(random_blobs)
|
tunnel_announce_timebase = Transport.timebase_from_random_blobs(random_blobs)
|
||||||
if tunnel_announce_timebase >= current_path_timebase: should_add = True
|
if tunnel_announce_timebase >= current_path_timebase: should_add = True
|
||||||
else: RNS.log("Did not restore path to "+RNS.prettyhexrep(destination_hash)+" because existing path is more recent", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
|
else: RNS.log("Did not restore path to "+RNS.prettyhexrep(destination_hash)+" because existing path is more recent", RNS.LOG_PATHING) if RNS.sl(RNS.LOG_PATHING) else None
|
||||||
else: RNS.log("Did not restore path to "+RNS.prettyhexrep(destination_hash)+" because a newer path with fewer hops exist", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
|
else: RNS.log("Did not restore path to "+RNS.prettyhexrep(destination_hash)+" because a newer path with fewer hops exist", RNS.LOG_PATHING) if RNS.sl(RNS.LOG_PATHING) else None
|
||||||
|
|
||||||
else:
|
else:
|
||||||
if time.time() < expires: should_add = True
|
if time.time() < expires: should_add = True
|
||||||
else: RNS.log("Did not restore path to "+RNS.prettyhexrep(destination_hash)+" because it has expired", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
|
else: RNS.log("Did not restore path to "+RNS.prettyhexrep(destination_hash)+" because it has expired", RNS.LOG_PATHING) if RNS.sl(RNS.LOG_PATHING) else None
|
||||||
|
|
||||||
if should_add:
|
if should_add:
|
||||||
with Transport.path_table_lock: Transport.path_table[destination_hash] = new_entry
|
with Transport.path_table_lock: Transport.path_table[destination_hash] = new_entry
|
||||||
RNS.log("Restored path to "+RNS.prettyhexrep(destination_hash)+" is now "+str(announce_hops)+" hops away via "+RNS.prettyhexrep(received_from)+" on "+str(receiving_interface), RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
|
RNS.log("Restored path to "+RNS.prettyhexrep(destination_hash)+" is now "+str(announce_hops)+" hops away via "+RNS.prettyhexrep(received_from)+" on "+str(receiving_interface), RNS.LOG_PATHING) if RNS.sl(RNS.LOG_PATHING) else None
|
||||||
|
|
||||||
else: deprecated_paths.append(destination_hash)
|
else: deprecated_paths.append(destination_hash)
|
||||||
|
|
||||||
for deprecated_path in deprecated_paths:
|
for deprecated_path in deprecated_paths:
|
||||||
RNS.log("Removing path to "+RNS.prettyhexrep(deprecated_path)+" from tunnel "+RNS.prettyhexrep(tunnel_id), RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
|
RNS.log("Removing path to "+RNS.prettyhexrep(deprecated_path)+" from tunnel "+RNS.prettyhexrep(tunnel_id), RNS.LOG_PATHING) if RNS.sl(RNS.LOG_PATHING) else None
|
||||||
with Transport.tunnels_lock: paths.pop(deprecated_path)
|
with Transport.tunnels_lock: paths.pop(deprecated_path)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -2656,7 +2679,8 @@ class Transport:
|
|||||||
:returns: The destination hash as *bytes* for the next hop to the specified destination, or *None* if the next hop is unknown.
|
:returns: The destination hash as *bytes* for the next hop to the specified destination, or *None* if the next hop is unknown.
|
||||||
"""
|
"""
|
||||||
with Transport.path_table_lock:
|
with Transport.path_table_lock:
|
||||||
if destination_hash in Transport.path_table: return Transport.path_table[destination_hash][IDX_PT_NEXT_HOP]
|
if destination_hash in Transport.path_table: return Transport.path_table[destination_hash][IDX_PT_NEXT_HOP]
|
||||||
|
elif destination_hash in Transport.destinations_map: return Transport.identity.hash
|
||||||
else: return None
|
else: return None
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -2666,7 +2690,8 @@ class Transport:
|
|||||||
:returns: The interface for the next hop to the specified destination, or *None* if the interface is unknown.
|
:returns: The interface for the next hop to the specified destination, or *None* if the interface is unknown.
|
||||||
"""
|
"""
|
||||||
with Transport.path_table_lock:
|
with Transport.path_table_lock:
|
||||||
if destination_hash in Transport.path_table: return Transport.path_table[destination_hash][IDX_PT_RVCD_IF]
|
if destination_hash in Transport.path_table: return Transport.path_table[destination_hash][IDX_PT_RVCD_IF]
|
||||||
|
elif destination_hash in Transport.destinations_map: return RNS.Reticulum.get_instance().shared_instance_interface
|
||||||
else: return None
|
else: return None
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -2904,7 +2929,7 @@ class Transport:
|
|||||||
tag=tag_bytes)
|
tag=tag_bytes)
|
||||||
|
|
||||||
else: RNS.log("Ignoring duplicate path request for "+RNS.prettyhexrep(destination_hash)+" with tag "+RNS.prettyhexrep(unique_tag), RNS.LOG_EXTREME) if RNS.sl(RNS.LOG_EXTREME) else None
|
else: RNS.log("Ignoring duplicate path request for "+RNS.prettyhexrep(destination_hash)+" with tag "+RNS.prettyhexrep(unique_tag), RNS.LOG_EXTREME) if RNS.sl(RNS.LOG_EXTREME) else None
|
||||||
else: RNS.log("Ignoring tagless path request for "+RNS.prettyhexrep(destination_hash), Transport.PR_LOGLEVEL) if RNS.sl(Transport.PR_LOGLEVEL) else None
|
else: RNS.log("Ignoring tagless path request for "+RNS.prettyhexrep(destination_hash), RNS.LOG_PATHING) if RNS.sl(RNS.LOG_PATHING) else None
|
||||||
except Exception as e: RNS.log(f"Error while handling path request. The contained exception was: {e}", RNS.LOG_ERROR)
|
except Exception as e: RNS.log(f"Error while handling path request. The contained exception was: {e}", RNS.LOG_ERROR)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -2915,11 +2940,12 @@ class Transport:
|
|||||||
if attached_interface != None:
|
if attached_interface != None:
|
||||||
should_ingress_limit = attached_interface.should_ingress_limit_pr()
|
should_ingress_limit = attached_interface.should_ingress_limit_pr()
|
||||||
if RNS.Reticulum.transport_enabled():
|
if RNS.Reticulum.transport_enabled():
|
||||||
if attached_interface.mode in RNS.Interfaces.Interface.Interface.DISCOVER_PATHS_FOR: should_search_for_unknown = True
|
if attached_interface.recursive_prs: should_search_for_unknown = True
|
||||||
|
elif attached_interface.mode in RNS.Interfaces.Interface.Interface.DISCOVER_PATHS_FOR: should_search_for_unknown = True
|
||||||
|
|
||||||
if RNS.sl(RNS.LOG_DEBUG):
|
if RNS.sl(RNS.LOG_PATHING):
|
||||||
interface_str = f" on {attached_interface}"
|
interface_str = f" on {attached_interface}"
|
||||||
RNS.log(f"Path request for {RNS.prettyhexrep(destination_hash)}{interface_str}", Transport.PR_LOGLEVEL)
|
RNS.log(f"Path request for {RNS.prettyhexrep(destination_hash)}{interface_str}", RNS.LOG_PATHING)
|
||||||
|
|
||||||
destination_exists_on_local_client = False
|
destination_exists_on_local_client = False
|
||||||
if len(Transport.local_client_interfaces) > 0:
|
if len(Transport.local_client_interfaces) > 0:
|
||||||
@@ -2938,7 +2964,7 @@ class Transport:
|
|||||||
|
|
||||||
if local_destination != None:
|
if local_destination != None:
|
||||||
local_destination.announce(path_response=True, tag=tag, attached_interface=attached_interface)
|
local_destination.announce(path_response=True, tag=tag, attached_interface=attached_interface)
|
||||||
RNS.log("Answering path request for "+RNS.prettyhexrep(destination_hash)+interface_str+", destination is local to this system", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
|
RNS.log("Answering path request for "+RNS.prettyhexrep(destination_hash)+interface_str+", destination is local to this system", RNS.LOG_PATHING) if RNS.sl(RNS.LOG_PATHING) else None
|
||||||
|
|
||||||
elif (RNS.Reticulum.transport_enabled() or is_from_local_client) and (destination_hash in Transport.path_table):
|
elif (RNS.Reticulum.transport_enabled() or is_from_local_client) and (destination_hash in Transport.path_table):
|
||||||
packet = Transport.get_cached_packet(Transport.path_table[destination_hash][IDX_PT_PACKET], packet_type="announce")
|
packet = Transport.get_cached_packet(Transport.path_table[destination_hash][IDX_PT_PACKET], packet_type="announce")
|
||||||
@@ -2949,10 +2975,10 @@ class Transport:
|
|||||||
RNS.log(f"Could not retrieve announce packet from cache while answering path request for {RNS.prettyhexrep(destination_hash)}, ignoring path request", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
|
RNS.log(f"Could not retrieve announce packet from cache while answering path request for {RNS.prettyhexrep(destination_hash)}, ignoring path request", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
|
||||||
|
|
||||||
elif attached_interface.mode == RNS.Interfaces.Interface.Interface.MODE_ROAMING and attached_interface == received_from:
|
elif attached_interface.mode == RNS.Interfaces.Interface.Interface.MODE_ROAMING and attached_interface == received_from:
|
||||||
RNS.log("Not answering path request on roaming-mode interface, since next hop is on same roaming-mode interface", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
|
RNS.log("Not answering path request on roaming-mode interface, since next hop is on same roaming-mode interface", RNS.LOG_PATHING) if RNS.sl(RNS.LOG_PATHING) else None
|
||||||
|
|
||||||
else:
|
else:
|
||||||
packet.unpack()
|
if not packet.unpack(): return
|
||||||
packet.hops = Transport.path_table[destination_hash][IDX_PT_HOPS]
|
packet.hops = Transport.path_table[destination_hash][IDX_PT_HOPS]
|
||||||
|
|
||||||
if requestor_transport_id != None and next_hop == requestor_transport_id:
|
if requestor_transport_id != None and next_hop == requestor_transport_id:
|
||||||
@@ -2962,9 +2988,9 @@ class Transport:
|
|||||||
# inefficient. There is probably a better way. Doing
|
# inefficient. There is probably a better way. Doing
|
||||||
# path invalidation here would decrease the network
|
# path invalidation here would decrease the network
|
||||||
# convergence time. Maybe just drop it?
|
# convergence time. Maybe just drop it?
|
||||||
RNS.log("Not answering path request for "+RNS.prettyhexrep(destination_hash)+interface_str+", since next hop is the requestor", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
|
RNS.log("Not answering path request for "+RNS.prettyhexrep(destination_hash)+interface_str+", since next hop is the requestor", RNS.LOG_PATHING) if RNS.sl(RNS.LOG_PATHING) else None
|
||||||
else:
|
else:
|
||||||
RNS.log("Answering path request for "+RNS.prettyhexrep(destination_hash)+interface_str+", path is known", Transport.PR_LOGLEVEL) if RNS.sl(Transport.PR_LOGLEVEL) else None
|
RNS.log("Answering path request for "+RNS.prettyhexrep(destination_hash)+interface_str+", path is known", RNS.LOG_PATHING) if RNS.sl(RNS.LOG_PATHING) else None
|
||||||
|
|
||||||
now = time.time()
|
now = time.time()
|
||||||
retries = Transport.PATHFINDER_R
|
retries = Transport.PATHFINDER_R
|
||||||
@@ -3006,7 +3032,7 @@ class Transport:
|
|||||||
elif is_from_local_client:
|
elif is_from_local_client:
|
||||||
# Forward path request on all interfaces
|
# Forward path request on all interfaces
|
||||||
# except the local client
|
# except the local client
|
||||||
RNS.log("Forwarding path request from local client for "+RNS.prettyhexrep(destination_hash)+interface_str+" to all other interfaces", Transport.PR_LOGLEVEL) if RNS.sl(Transport.PR_LOGLEVEL) else None
|
RNS.log("Forwarding path request from local client for "+RNS.prettyhexrep(destination_hash)+interface_str+" to all other interfaces", RNS.LOG_PATHING) if RNS.sl(RNS.LOG_PATHING) else None
|
||||||
request_tag = RNS.Identity.get_random_hash()
|
request_tag = RNS.Identity.get_random_hash()
|
||||||
for interface in Transport.interfaces:
|
for interface in Transport.interfaces:
|
||||||
if not interface == attached_interface:
|
if not interface == attached_interface:
|
||||||
@@ -3014,20 +3040,20 @@ class Transport:
|
|||||||
|
|
||||||
elif should_search_for_unknown:
|
elif should_search_for_unknown:
|
||||||
if destination_hash in Transport.discovery_path_requests:
|
if destination_hash in Transport.discovery_path_requests:
|
||||||
RNS.log("There is already a waiting path request for "+RNS.prettyhexrep(destination_hash)+" on behalf of path request"+interface_str, RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
|
RNS.log("There is already a waiting path request for "+RNS.prettyhexrep(destination_hash)+" on behalf of path request"+interface_str, RNS.LOG_PATHING) if RNS.sl(RNS.LOG_PATHING) else None
|
||||||
else:
|
else:
|
||||||
# Abort recursive path request if receiving
|
# Abort recursive path request if receiving
|
||||||
# interface has PR burst active, or should
|
# interface has PR burst active, or should
|
||||||
# otherwise ingress limit path requests.
|
# otherwise ingress limit path requests.
|
||||||
if should_ingress_limit:
|
if should_ingress_limit:
|
||||||
if RNS.sl(RNS.LOG_DEBUG):
|
if RNS.sl(RNS.LOG_PATHING):
|
||||||
interface_str = f" for {attached_interface}" if attached_interface else ""
|
interface_str = f" for {attached_interface}" if attached_interface else ""
|
||||||
RNS.log(f"Not sending recursive path request{interface_str} due to active ingress limiting", RNS.LOG_DEBUG)
|
RNS.log(f"Not sending recursive path request{interface_str} due to active ingress limiting", RNS.LOG_PATHING)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Forward path request on all interfaces
|
# Forward path request on all interfaces
|
||||||
# except the requestor interface
|
# except the requestor interface
|
||||||
RNS.log("Attempting to discover unknown path to "+RNS.prettyhexrep(destination_hash)+" on behalf of path request"+interface_str, RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
|
RNS.log("Attempting to discover unknown path to "+RNS.prettyhexrep(destination_hash)+" on behalf of path request"+interface_str, RNS.LOG_PATHING) if RNS.sl(RNS.LOG_PATHING) else None
|
||||||
pr_entry = { "destination_hash": destination_hash, "timeout": time.time()+Transport.PATH_REQUEST_TIMEOUT, "requesting_interface": attached_interface }
|
pr_entry = { "destination_hash": destination_hash, "timeout": time.time()+Transport.PATH_REQUEST_TIMEOUT, "requesting_interface": attached_interface }
|
||||||
with Transport.discovery_pr_lock: Transport.discovery_path_requests[destination_hash] = pr_entry
|
with Transport.discovery_pr_lock: Transport.discovery_path_requests[destination_hash] = pr_entry
|
||||||
|
|
||||||
@@ -3043,36 +3069,35 @@ class Transport:
|
|||||||
elif not is_from_local_client and len(Transport.local_client_interfaces) > 0:
|
elif not is_from_local_client and len(Transport.local_client_interfaces) > 0:
|
||||||
# Forward the path request on all local
|
# Forward the path request on all local
|
||||||
# client interfaces
|
# client interfaces
|
||||||
RNS.log("Forwarding path request for "+RNS.prettyhexrep(destination_hash)+interface_str+" to local clients", Transport.PR_LOGLEVEL) if RNS.sl(Transport.PR_LOGLEVEL) else None
|
RNS.log("Forwarding path request for "+RNS.prettyhexrep(destination_hash)+interface_str+" to local clients", RNS.LOG_PATHING) if RNS.sl(RNS.LOG_PATHING) else None
|
||||||
for interface in Transport.local_client_interfaces:
|
for interface in Transport.local_client_interfaces:
|
||||||
Transport.request_path(destination_hash, on_interface=interface)
|
Transport.request_path(destination_hash, on_interface=interface)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
RNS.log("Ignoring path request for "+RNS.prettyhexrep(destination_hash)+interface_str+", no path known", Transport.PR_LOGLEVEL) if RNS.sl(Transport.PR_LOGLEVEL) else None
|
RNS.log("Ignoring path request for "+RNS.prettyhexrep(destination_hash)+interface_str+", no path known", RNS.LOG_PATHING) if RNS.sl(RNS.LOG_PATHING) else None
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def from_local_client(packet):
|
def from_local_client(packet):
|
||||||
if hasattr(packet.receiving_interface, "parent_interface"):
|
if hasattr(packet.receiving_interface, "parent_interface"):
|
||||||
return Transport.is_local_client_interface(packet.receiving_interface)
|
return Transport.is_local_client_interface(packet.receiving_interface)
|
||||||
else:
|
else: return False
|
||||||
return False
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def is_local_client_interface(interface):
|
def is_local_client_interface(interface):
|
||||||
if hasattr(interface, "parent_interface"):
|
if hasattr(interface, "parent_interface"):
|
||||||
if hasattr(interface.parent_interface, "is_local_shared_instance"):
|
if hasattr(interface.parent_interface, "is_local_shared_instance"): return True
|
||||||
return True
|
else: return False
|
||||||
else:
|
else: return False
|
||||||
return False
|
|
||||||
else:
|
@staticmethod
|
||||||
return False
|
def is_shared_instance(interface):
|
||||||
|
if hasattr(interface, "is_local_shared_instance"): return True
|
||||||
|
else: return False
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def interface_to_shared_instance(interface):
|
def interface_to_shared_instance(interface):
|
||||||
if hasattr(interface, "is_connected_to_shared_instance"):
|
if hasattr(interface, "is_connected_to_shared_instance"): return True
|
||||||
return True
|
else: return False
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def detach_interfaces():
|
def detach_interfaces():
|
||||||
@@ -3263,7 +3288,7 @@ class Transport:
|
|||||||
if interface != None:
|
if interface != None:
|
||||||
# Get the destination entry from the destination table
|
# Get the destination entry from the destination table
|
||||||
if not destination_hash in path_table:
|
if not destination_hash in path_table:
|
||||||
RNS.log(f"Skipping persist for path table entry {RNS.prettyhexrep(destination_hash)}, no longer in table", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
|
RNS.log(f"Skipping persist for path table entry {RNS.prettyhexrep(destination_hash)}, no longer in table", RNS.LOG_PATHING) if RNS.sl(RNS.LOG_PATHING) else None
|
||||||
|
|
||||||
de = path_table[destination_hash]
|
de = path_table[destination_hash]
|
||||||
timestamp = de[IDX_PT_TIMESTAMP]
|
timestamp = de[IDX_PT_TIMESTAMP]
|
||||||
|
|||||||
+19
-8
@@ -100,7 +100,7 @@ __example_rns_config__ = '''# This is an example Reticulum config file.
|
|||||||
# always-on. This directive is optional and can be removed
|
# always-on. This directive is optional and can be removed
|
||||||
# for brevity.
|
# for brevity.
|
||||||
|
|
||||||
enable_transport = No
|
enable_transport = no
|
||||||
|
|
||||||
|
|
||||||
# By default, the first program to launch the Reticulum
|
# By default, the first program to launch the Reticulum
|
||||||
@@ -112,7 +112,7 @@ enable_transport = No
|
|||||||
# user, and should generally be turned on. This directive
|
# user, and should generally be turned on. This directive
|
||||||
# is optional and can be removed for brevity.
|
# is optional and can be removed for brevity.
|
||||||
|
|
||||||
share_instance = Yes
|
share_instance = yes
|
||||||
|
|
||||||
|
|
||||||
# If you want to run multiple *different* shared instances
|
# If you want to run multiple *different* shared instances
|
||||||
@@ -178,7 +178,7 @@ instance_name = default
|
|||||||
# the network. If this option is enabled, Reticulum will
|
# the network. If this option is enabled, Reticulum will
|
||||||
# collect interface information discovered from the network.
|
# collect interface information discovered from the network.
|
||||||
|
|
||||||
# discover_interfaces = No
|
# discover_interfaces = no
|
||||||
|
|
||||||
|
|
||||||
# If you only want to discover interfaces from specific
|
# If you only want to discover interfaces from specific
|
||||||
@@ -215,7 +215,7 @@ instance_name = default
|
|||||||
# an optional directive, and can be left out for brevity.
|
# an optional directive, and can be left out for brevity.
|
||||||
# This behaviour is disabled by default.
|
# This behaviour is disabled by default.
|
||||||
|
|
||||||
# panic_on_interface_error = No
|
# panic_on_interface_error = no
|
||||||
|
|
||||||
|
|
||||||
# When Transport is enabled, it is possible to allow the
|
# When Transport is enabled, it is possible to allow the
|
||||||
@@ -226,14 +226,15 @@ instance_name = default
|
|||||||
# Transport Instance, and printed to the log at startup.
|
# Transport Instance, and printed to the log at startup.
|
||||||
# Optional, and disabled by default.
|
# Optional, and disabled by default.
|
||||||
|
|
||||||
# respond_to_probes = No
|
# respond_to_probes = no
|
||||||
|
|
||||||
|
|
||||||
# You can publish your local list of blackholed identities
|
# You can publish your local list of blackholed identities
|
||||||
# for other transport instances to use for automatic,
|
# for other transport instances to use for automatic,
|
||||||
# network-wide blackhole management.
|
# network-wide blackhole management.
|
||||||
|
|
||||||
# publish_blackhole = No
|
# publish_blackhole = no
|
||||||
|
|
||||||
|
|
||||||
# List of remote transport identities from which to auto-
|
# List of remote transport identities from which to auto-
|
||||||
# matically source lists of blackholed identities.
|
# matically source lists of blackholed identities.
|
||||||
@@ -248,14 +249,23 @@ instance_name = default
|
|||||||
|
|
||||||
# blackhole_sources = 521c87a83afb8f29e4455e77930b973b
|
# blackhole_sources = 521c87a83afb8f29e4455e77930b973b
|
||||||
|
|
||||||
|
|
||||||
# You can set the interval in minutes at which remote
|
# You can set the interval in minutes at which remote
|
||||||
# blackhole sources are updated. Defaults to one hour.
|
# blackhole sources are updated. Defaults to one hour.
|
||||||
|
|
||||||
# blackhole_update_interval = 60
|
# blackhole_update_interval = 60
|
||||||
|
|
||||||
|
|
||||||
|
# When not running as a transport node, it is possible to
|
||||||
|
# force the use of the same, static transport identity
|
||||||
|
# for every instance start. Defaults to using a new identity
|
||||||
|
# at every instance start, if transport is disabled.
|
||||||
|
|
||||||
|
# static_transport_identity = no
|
||||||
|
|
||||||
|
|
||||||
[logging]
|
[logging]
|
||||||
# Valid log levels are 0 through 7:
|
# Valid log levels are 0 through 8:
|
||||||
# 0: Log only critical information
|
# 0: Log only critical information
|
||||||
# 1: Log errors and lower log levels
|
# 1: Log errors and lower log levels
|
||||||
# 2: Log warnings and lower log levels
|
# 2: Log warnings and lower log levels
|
||||||
@@ -263,7 +273,8 @@ instance_name = default
|
|||||||
# 4: Log info and lower (this is the default)
|
# 4: Log info and lower (this is the default)
|
||||||
# 5: Verbose logging
|
# 5: Verbose logging
|
||||||
# 6: Debug logging
|
# 6: Debug logging
|
||||||
# 7: Extreme logging
|
# 7: Path logging
|
||||||
|
# 8: Extreme logging
|
||||||
|
|
||||||
loglevel = 4
|
loglevel = 4
|
||||||
|
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
__version__ = "0.2.0"
|
__version__ = "0.3.0"
|
||||||
@@ -44,7 +44,8 @@ def setup_argument_parser():
|
|||||||
parser = argparse.ArgumentParser(description="Reticulum Remote Shell Utility", epilog="When specifying a command to execute, separate rnsh\noptions from the command and its arguments with --\n\nFor example:\n rnsh -l -- /bin/bash --login\n rnsh <destination> -- ls -la /tmp", formatter_class=argparse.RawDescriptionHelpFormatter)
|
parser = argparse.ArgumentParser(description="Reticulum Remote Shell Utility", epilog="When specifying a command to execute, separate rnsh\noptions from the command and its arguments with --\n\nFor example:\n rnsh -l -- /bin/bash --login\n rnsh <destination> -- ls -la /tmp", formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||||
|
|
||||||
# Common options
|
# Common options
|
||||||
parser.add_argument("--config", "-c", action="store", default=None, help="path to alternative Reticulum config directory", type=str)
|
parser.add_argument("--config", "-c", action="store", default=None, help="path to config directory", type=str)
|
||||||
|
parser.add_argument("--rnsconfig", action="store", default=None, help="path to alternative Reticulum config directory", type=str)
|
||||||
parser.add_argument("--identity", "-i", action="store", default=None, help="path to identity file to use", type=str)
|
parser.add_argument("--identity", "-i", action="store", default=None, help="path to identity file to use", type=str)
|
||||||
parser.add_argument("-v", "--verbose", action="count", default=0, help="increase verbosity")
|
parser.add_argument("-v", "--verbose", action="count", default=0, help="increase verbosity")
|
||||||
parser.add_argument("-q", "--quiet", action="count", default=0, help="decrease verbosity")
|
parser.add_argument("-q", "--quiet", action="count", default=0, help="decrease verbosity")
|
||||||
|
|||||||
@@ -53,8 +53,6 @@ class permit(AbstractContextManager):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, *exceptions): self._exceptions = exceptions
|
def __init__(self, *exceptions): self._exceptions = exceptions
|
||||||
|
|
||||||
def __enter__(self): pass
|
def __enter__(self): pass
|
||||||
|
|
||||||
def __exit__(self, exctype, excinst, exctb):
|
def __exit__(self, exctype, excinst, exctb):
|
||||||
return exctype is not None and not issubclass(exctype, self._exceptions)
|
return exctype is not None and not issubclass(exctype, self._exceptions)
|
||||||
|
|||||||
@@ -55,5 +55,4 @@ class SleepRate:
|
|||||||
return sleep_for if sleep_for > 0 else 0
|
return sleep_for if sleep_for > 0 else 0
|
||||||
|
|
||||||
async def sleep_async(self): await asyncio.sleep(self.next_sleep_time())
|
async def sleep_async(self): await asyncio.sleep(self.next_sleep_time())
|
||||||
|
|
||||||
def sleep_block(self): time.sleep(self.next_sleep_time())
|
def sleep_block(self): time.sleep(self.next_sleep_time())
|
||||||
|
|||||||
@@ -144,7 +144,7 @@ class RemoteExecutionError(Exception):
|
|||||||
def __init__(self, msg): self.msg = msg
|
def __init__(self, msg): self.msg = msg
|
||||||
|
|
||||||
|
|
||||||
async def _initiate_link(configdir, rnsconfigdir, identitypath=None, verbosity=0, quietness=0, noid=False, destination=None,
|
async def _initiate_link(configdir, rnsconfigdir, identitypath=None, logfile=None, verbosity=0, quietness=0, noid=False, destination=None,
|
||||||
timeout=RNS.Transport.PATH_REQUEST_TIMEOUT):
|
timeout=RNS.Transport.PATH_REQUEST_TIMEOUT):
|
||||||
global _identity, _reticulum, _link, _destination, _remote_exec_grace
|
global _identity, _reticulum, _link, _destination, _remote_exec_grace
|
||||||
|
|
||||||
@@ -160,11 +160,11 @@ async def _initiate_link(configdir, rnsconfigdir, identitypath=None, verbosity=0
|
|||||||
|
|
||||||
if _reticulum is None:
|
if _reticulum is None:
|
||||||
targetloglevel = compute_target_rns_loglevel(verbosity, quietness, RNS.LOG_ERROR)
|
targetloglevel = compute_target_rns_loglevel(verbosity, quietness, RNS.LOG_ERROR)
|
||||||
RNS.logfile = os.path.join(configdir, "logfile")
|
RNS.logfile = logfile
|
||||||
_reticulum = RNS.Reticulum(configdir=rnsconfigdir, loglevel=targetloglevel, logdest=RNS.LOG_FILE)
|
_reticulum = RNS.Reticulum(configdir=rnsconfigdir, loglevel=targetloglevel, logdest=RNS.LOG_FILE)
|
||||||
|
|
||||||
if _identity is None:
|
if _identity is None:
|
||||||
_identity = rnsh.prepare_identity(identitypath)
|
_identity = rnsh.prepare_identity(identity_path=identitypath, service_name=None, configdir=configdir)
|
||||||
|
|
||||||
if not RNS.Transport.has_path(destination_hash):
|
if not RNS.Transport.has_path(destination_hash):
|
||||||
RNS.Transport.request_path(destination_hash)
|
RNS.Transport.request_path(destination_hash)
|
||||||
@@ -211,8 +211,9 @@ async def _handle_error(errmsg: RNS.MessageBase):
|
|||||||
raise RemoteExecutionError(f"Remote error: {errmsg.msg}")
|
raise RemoteExecutionError(f"Remote error: {errmsg.msg}")
|
||||||
|
|
||||||
|
|
||||||
async def initiate(configdir: str, rnsconfigdir:str, identitypath: str, verbosity: int, quietness: int, noid: bool, destination: str,
|
async def initiate(configdir: str, rnsconfigdir:str, identitypath: str, logfile:str, verbosity: int, quietness: int, noid: bool, destination: str,
|
||||||
timeout: float, command: [str] | None = None):
|
timeout: float, command: [str] | None = None):
|
||||||
|
|
||||||
global _finished, _link
|
global _finished, _link
|
||||||
if timeout is None:
|
if timeout is None:
|
||||||
timeout = RNS.Transport.PATH_REQUEST_TIMEOUT
|
timeout = RNS.Transport.PATH_REQUEST_TIMEOUT
|
||||||
@@ -239,12 +240,6 @@ async def initiate(configdir: str, rnsconfigdir:str, identitypath: str, verbosit
|
|||||||
channel = _link.get_channel()
|
channel = _link.get_channel()
|
||||||
protocol.register_message_types(channel)
|
protocol.register_message_types(channel)
|
||||||
channel.add_message_handler(_client_message_handler)
|
channel.add_message_handler(_client_message_handler)
|
||||||
|
|
||||||
# Next step after linking and identifying: send version
|
|
||||||
# if not await _spin(lambda: messenger.is_outlet_ready(outlet), timeout=5, quiet=quietness > 0):
|
|
||||||
# print("Error bringing up link")
|
|
||||||
# return 253
|
|
||||||
|
|
||||||
channel.send(protocol.VersionInfoMessage())
|
channel.send(protocol.VersionInfoMessage())
|
||||||
try:
|
try:
|
||||||
vm = _pq.get(timeout=max(outlet.rtt * 20, 5))
|
vm = _pq.get(timeout=max(outlet.rtt * 20, 5))
|
||||||
@@ -283,10 +278,8 @@ async def initiate(configdir: str, rnsconfigdir:str, identitypath: str, verbosit
|
|||||||
return None
|
return None
|
||||||
elif b == "L":
|
elif b == "L":
|
||||||
line_mode = not line_mode
|
line_mode = not line_mode
|
||||||
if line_mode:
|
if line_mode: os.write(1, "\n\rLine-interactive mode enabled\n\r".encode("utf-8"))
|
||||||
os.write(1, "\n\rLine-interactive mode enabled\n\r".encode("utf-8"))
|
else: os.write(1, "\n\rLine-interactive mode disabled\n\r".encode("utf-8"))
|
||||||
else:
|
|
||||||
os.write(1, "\n\rLine-interactive mode disabled\n\r".encode("utf-8"))
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return b
|
return b
|
||||||
|
|||||||
@@ -118,25 +118,32 @@ def compute_target_rns_loglevel(verbosity: int, quietness: int, base_level: int
|
|||||||
|
|
||||||
except Exception: return base_level
|
except Exception: return base_level
|
||||||
|
|
||||||
async def listen(configdir, rnsconfigdir, command, identitypath=None, service_name=None, verbosity=0, quietness=0, allowed=None,
|
async def listen(configdir, rnsconfigdir, command, identitypath=None, logfile=None, service_name=None, verbosity=0, quietness=0,
|
||||||
allowed_file=None, disable_auth=None, announce_period=900, no_remote_command=True, remote_cmd_as_args=False,
|
allowed=None, allowed_file=None, disable_auth=None, announce_period=900, no_remote_command=True, remote_cmd_as_args=False,
|
||||||
loop: asyncio.AbstractEventLoop = None):
|
loop: asyncio.AbstractEventLoop = None):
|
||||||
global _identity, _allow_all, _allowed_identity_hashes, _allowed_file, _allowed_file_identity_hashes
|
global _identity, _allow_all, _allowed_identity_hashes, _allowed_file, _allowed_file_identity_hashes
|
||||||
global _reticulum, _cmd, _destination, _no_remote_command, _remote_cmd_as_args, _finished
|
global _reticulum, _cmd, _destination, _no_remote_command, _remote_cmd_as_args, _finished
|
||||||
|
|
||||||
if not loop: loop = asyncio.get_running_loop()
|
if not loop: loop = asyncio.get_running_loop()
|
||||||
if service_name is None or len(service_name) == 0:
|
if service_name is None or len(service_name) == 0: service_name = "default"
|
||||||
service_name = "default"
|
|
||||||
|
|
||||||
RNS.log(f"Using service name {service_name}", RNS.LOG_INFO)
|
RNS.log(f"Using service name {service_name}", RNS.LOG_INFO)
|
||||||
|
if logfile:
|
||||||
|
RNS.log(f"Logging to {logfile}", RNS.LOG_NOTICE)
|
||||||
|
logdest = RNS.LOG_FILE
|
||||||
|
RNS.logfile = logfile
|
||||||
|
else:
|
||||||
|
RNS.log(f"Logging to console", RNS.LOG_NOTICE)
|
||||||
|
logdest = RNS.LOG_STDOUT
|
||||||
|
|
||||||
# More -v should increase verbosity (higher RNS.loglevel); -q should decrease it
|
# More -v should increase verbosity (higher RNS.loglevel); -q should decrease it
|
||||||
targetloglevel = compute_target_rns_loglevel(verbosity, quietness, RNS.LOG_INFO)
|
targetloglevel = compute_target_rns_loglevel(verbosity, quietness, RNS.LOG_INFO)
|
||||||
_reticulum = RNS.Reticulum(configdir=rnsconfigdir, loglevel=targetloglevel)
|
_reticulum = RNS.Reticulum(configdir=rnsconfigdir, loglevel=targetloglevel)
|
||||||
_identity = rnsh.prepare_identity(identitypath, service_name)
|
_identity = rnsh.prepare_identity(identity_path=identitypath, service_name=service_name, configdir=configdir)
|
||||||
_destination = RNS.Destination(_identity, RNS.Destination.IN, RNS.Destination.SINGLE, rnsh.APP_NAME)
|
_destination = RNS.Destination(_identity, RNS.Destination.IN, RNS.Destination.SINGLE, rnsh.APP_NAME)
|
||||||
|
|
||||||
RNS.log(f"rnsh listening for commands on {RNS.prettyhexrep(_destination.hash)}", RNS.LOG_NOTICE)
|
RNS.log(f"rnsh listening for commands on {RNS.prettyhexrep(_destination.hash)}", RNS.LOG_NOTICE)
|
||||||
|
RNS.logdest = logdest
|
||||||
|
|
||||||
_cmd = command
|
_cmd = command
|
||||||
if _cmd is None or len(_cmd) == 0:
|
if _cmd is None or len(_cmd) == 0:
|
||||||
|
|||||||
@@ -88,8 +88,7 @@ class RetryThread(AbstractContextManager):
|
|||||||
self._thread = threading.Thread(name=name, target=self._thread_run, daemon=True)
|
self._thread = threading.Thread(name=name, target=self._thread_run, daemon=True)
|
||||||
self._thread.start()
|
self._thread.start()
|
||||||
|
|
||||||
def is_alive(self):
|
def is_alive(self): return self._thread.is_alive()
|
||||||
return self._thread.is_alive()
|
|
||||||
|
|
||||||
def close(self, loop: asyncio.AbstractEventLoop = None) -> asyncio.Future:
|
def close(self, loop: asyncio.AbstractEventLoop = None) -> asyncio.Future:
|
||||||
RNS.log("Stopping timer thread", RNS.LOG_DEBUG)
|
RNS.log("Stopping timer thread", RNS.LOG_DEBUG)
|
||||||
@@ -102,17 +101,12 @@ class RetryThread(AbstractContextManager):
|
|||||||
return self._finished
|
return self._finished
|
||||||
|
|
||||||
def wait(self, timeout: float = None):
|
def wait(self, timeout: float = None):
|
||||||
if timeout:
|
if timeout: timeout = timeout + time.time()
|
||||||
timeout = timeout + time.time()
|
|
||||||
|
|
||||||
while timeout is None or time.time() < timeout:
|
while timeout is None or time.time() < timeout:
|
||||||
with self._lock:
|
with self._lock: task_count = len(self._statuses)
|
||||||
task_count = len(self._statuses)
|
if task_count == 0: return
|
||||||
if task_count == 0:
|
|
||||||
return
|
|
||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
|
|
||||||
|
|
||||||
def _thread_run(self):
|
def _thread_run(self):
|
||||||
while self._run and self._finished is None:
|
while self._run and self._finished is None:
|
||||||
time.sleep(self._loop_period)
|
time.sleep(self._loop_period)
|
||||||
|
|||||||
+40
-25
@@ -57,43 +57,54 @@ loop: asyncio.AbstractEventLoop | None = None
|
|||||||
|
|
||||||
def _sanitize_service_name(service_name:str) -> str: return re.sub(r'\W+', '', service_name)
|
def _sanitize_service_name(service_name:str) -> str: return re.sub(r'\W+', '', service_name)
|
||||||
|
|
||||||
def prepare_identity(identity_path, service_name: str = None) -> tuple[RNS.Identity]:
|
def prepare_identity(identity_path=None, service_name=None, configdir=None):
|
||||||
service_name = _sanitize_service_name(service_name or "")
|
service_name = _sanitize_service_name(service_name or "")
|
||||||
if identity_path is None:
|
if not identity_path:
|
||||||
identity_path = RNS.Reticulum.identitypath + "/" + APP_NAME + \
|
identity_path = f"{configdir}/identity" + (f".{service_name}" if service_name and len(service_name) > 0 else "")
|
||||||
(f".{service_name}" if service_name and len(service_name) > 0 else "")
|
|
||||||
|
|
||||||
identity = None
|
identity = None
|
||||||
if os.path.isfile(identity_path):
|
if os.path.isfile(identity_path):
|
||||||
|
RNS.log(f"Loading identity from {identity_path}", RNS.LOG_VERBOSE)
|
||||||
identity = RNS.Identity.from_file(identity_path)
|
identity = RNS.Identity.from_file(identity_path)
|
||||||
|
|
||||||
if identity is None:
|
if identity is None:
|
||||||
RNS.log("No valid saved identity found, creating new...", RNS.LOG_INFO)
|
RNS.log(f"Creating identity {identity_path}", RNS.LOG_NOTICE)
|
||||||
identity = RNS.Identity()
|
identity = RNS.Identity()
|
||||||
identity.to_file(identity_path)
|
identity.to_file(identity_path)
|
||||||
|
|
||||||
return identity
|
return identity
|
||||||
|
|
||||||
|
|
||||||
def print_identity(configdir, identitypath, service_name, include_destination: bool):
|
def print_identity(configdir, rnsconfigdir, identitypath, service_name, include_destination: bool):
|
||||||
reticulum = RNS.Reticulum(configdir=configdir, loglevel=RNS.LOG_INFO)
|
reticulum = RNS.Reticulum(configdir=rnsconfigdir, loglevel=RNS.LOG_INFO)
|
||||||
if service_name and len(service_name) > 0:
|
if service_name and len(service_name) > 0: print(f"Using service name \"{service_name}\"")
|
||||||
print(f"Using service name \"{service_name}\"")
|
identity = prepare_identity(identity_path=identitypath, service_name=service_name, configdir=configdir)
|
||||||
identity = prepare_identity(identitypath, service_name)
|
|
||||||
destination = RNS.Destination(identity, RNS.Destination.IN, RNS.Destination.SINGLE, APP_NAME)
|
destination = RNS.Destination(identity, RNS.Destination.IN, RNS.Destination.SINGLE, APP_NAME)
|
||||||
print("Identity : " + str(identity))
|
print("Identity : " + str(identity))
|
||||||
if include_destination:
|
if include_destination: print("Listening on : " + RNS.prettyhexrep(destination.hash))
|
||||||
print("Listening on : " + RNS.prettyhexrep(destination.hash))
|
|
||||||
|
|
||||||
exit(0)
|
exit(0)
|
||||||
|
|
||||||
verbose_set = False
|
verbose_set = False
|
||||||
|
|
||||||
def ensure_config_directory():
|
def ensure_config_directory(configdir=None):
|
||||||
if os.path.isdir(os.path.expanduser("~/.config/rnsh")): return os.path.expanduser("~/.config/rnsh")
|
if configdir:
|
||||||
elif os.path.isdir(os.path.expanduser("~/.rnsh")): return os.path.expanduser("~/.rnsh")
|
if os.path.isdir(os.path.expanduser(configdir)): return os.path.expanduser(configdir)
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
RNS.log(f"Creating configuration directory: {os.path.expanduser(configdir)}")
|
||||||
|
os.makedirs(os.path.expanduser(configdir))
|
||||||
|
return os.path.expanduser(configdir)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
RNS.log(f"Could not get or create rnsh configuration directory, aborting", RNS.LOG_CRITICAL)
|
||||||
|
os._exit(1)
|
||||||
|
|
||||||
|
elif os.path.isdir(os.path.expanduser("~/.config/rnsh")): return os.path.expanduser("~/.config/rnsh")
|
||||||
|
elif os.path.isdir(os.path.expanduser("~/.rnsh")): return os.path.expanduser("~/.rnsh")
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
|
RNS.log(f"Creating configuration directory: {os.path.expanduser('~/.rnsh')}")
|
||||||
os.makedirs(os.path.expanduser("~/.rnsh"))
|
os.makedirs(os.path.expanduser("~/.rnsh"))
|
||||||
return os.path.expanduser("~/.rnsh")
|
return os.path.expanduser("~/.rnsh")
|
||||||
|
|
||||||
@@ -105,26 +116,30 @@ def ensure_config_directory():
|
|||||||
async def _rnsh_cli_main():
|
async def _rnsh_cli_main():
|
||||||
global verbose_set
|
global verbose_set
|
||||||
args, parser = parse_arguments()
|
args, parser = parse_arguments()
|
||||||
verbose_set = args.verbose > 0
|
verbose_set = args.verbose > 0
|
||||||
|
configdir = ensure_config_directory(args.config)
|
||||||
|
|
||||||
configdir = ensure_config_directory()
|
if not configdir:
|
||||||
|
RNS.log(f"Could not resolve rnsh configuration directory", RNS.LOG_CRITICAL)
|
||||||
|
os._exit(1)
|
||||||
|
|
||||||
if args.print_identity:
|
if args.print_identity:
|
||||||
print_identity(args.config, args.identity, args.service, args.listen)
|
print_identity(configdir, args.rnsconfig, args.identity, args.service, args.listen)
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
logfile = f"{configdir}/logfile"
|
||||||
|
|
||||||
if args.listen:
|
if args.listen:
|
||||||
allowed_file = None
|
allowed_file = None
|
||||||
dest_len = (RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2
|
dest_len = (RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2
|
||||||
if os.path.isfile(os.path.expanduser("~/.config/rnsh/allowed_identities")):
|
if os.path.isfile(os.path.expanduser(f"{configdir}/allowed_identities")):
|
||||||
allowed_file = os.path.expanduser("~/.config/rnsh/allowed_identities")
|
allowed_file = os.path.expanduser(f"{configdir}/allowed_identities")
|
||||||
elif os.path.isfile(os.path.expanduser("~/.rnsh/allowed_identities")):
|
|
||||||
allowed_file = os.path.expanduser("~/.rnsh/allowed_identities")
|
|
||||||
|
|
||||||
await listener.listen(configdir=configdir,
|
await listener.listen(configdir=configdir,
|
||||||
rnsconfigdir=args.config,
|
rnsconfigdir=args.rnsconfig,
|
||||||
command=args.command,
|
command=args.command,
|
||||||
identitypath=args.identity,
|
identitypath=args.identity,
|
||||||
|
logfile=logfile,
|
||||||
service_name=args.service,
|
service_name=args.service,
|
||||||
verbosity=args.verbose,
|
verbosity=args.verbose,
|
||||||
quietness=args.quiet,
|
quietness=args.quiet,
|
||||||
@@ -138,8 +153,9 @@ async def _rnsh_cli_main():
|
|||||||
|
|
||||||
if args.destination is not None:
|
if args.destination is not None:
|
||||||
return_code = await initiator.initiate(configdir=configdir,
|
return_code = await initiator.initiate(configdir=configdir,
|
||||||
rnsconfigdir=args.config,
|
rnsconfigdir=args.rnsconfig,
|
||||||
identitypath=args.identity,
|
identitypath=args.identity,
|
||||||
|
logfile=logfile,
|
||||||
verbosity=args.verbose,
|
verbosity=args.verbose,
|
||||||
quietness=args.quiet,
|
quietness=args.quiet,
|
||||||
noid=args.no_id,
|
noid=args.no_id,
|
||||||
@@ -170,5 +186,4 @@ def main():
|
|||||||
if verbose_set and exc: raise exc
|
if verbose_set and exc: raise exc
|
||||||
sys.exit(return_code if return_code is not None else 255)
|
sys.exit(return_code if return_code is not None else 255)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__": main()
|
if __name__ == "__main__": main()
|
||||||
|
|||||||
@@ -103,6 +103,8 @@ class ListenerSession:
|
|||||||
self.outlet.set_link_closed_callback(self._link_closed)
|
self.outlet.set_link_closed_callback(self._link_closed)
|
||||||
self.loop = loop
|
self.loop = loop
|
||||||
self.state: LSState = None
|
self.state: LSState = None
|
||||||
|
self.terminated = False
|
||||||
|
self.authenticated = False
|
||||||
self.remote_identity = None
|
self.remote_identity = None
|
||||||
self.term: str | None = None
|
self.term: str | None = None
|
||||||
self.stdin_is_pipe: bool = False
|
self.stdin_is_pipe: bool = False
|
||||||
@@ -122,8 +124,10 @@ class ListenerSession:
|
|||||||
self.return_code_sent = False
|
self.return_code_sent = False
|
||||||
self.process: process.CallbackSubprocess | None = None
|
self.process: process.CallbackSubprocess | None = None
|
||||||
|
|
||||||
if self.allow_all: self._set_state(LSState.LSSTATE_WAIT_VERS)
|
if not self.allow_all: self._set_state(LSState.LSSTATE_WAIT_IDENT)
|
||||||
else: self._set_state(LSState.LSSTATE_WAIT_IDENT)
|
else:
|
||||||
|
self._set_state(LSState.LSSTATE_WAIT_VERS)
|
||||||
|
self.authenticated = True
|
||||||
|
|
||||||
self.sessions.append(self)
|
self.sessions.append(self)
|
||||||
protocol.register_message_types(self.channel)
|
protocol.register_message_types(self.channel)
|
||||||
@@ -143,37 +147,31 @@ class ListenerSession:
|
|||||||
def _call(self, func: callable, delay: float = 0):
|
def _call(self, func: callable, delay: float = 0):
|
||||||
def call_inner():
|
def call_inner():
|
||||||
if delay == 0: func()
|
if delay == 0: func()
|
||||||
else: self.loop.call_later(delay, func)
|
else: self.loop.call_later(delay, func)
|
||||||
|
|
||||||
self.loop.call_soon_threadsafe(call_inner)
|
self.loop.call_soon_threadsafe(call_inner)
|
||||||
|
|
||||||
def send(self, message: RNS.MessageBase):
|
def send(self, message: RNS.MessageBase): self.channel.send(message)
|
||||||
self.channel.send(message)
|
def _protocol_error(self, name: str): self.terminate(f"Protocol error ({name})")
|
||||||
|
def _protocol_timeout_error(self, name: str): self.terminate(f"Protocol timeout error: {name}")
|
||||||
def _protocol_error(self, name: str):
|
|
||||||
self.terminate(f"Protocol error ({name})")
|
|
||||||
|
|
||||||
def _protocol_timeout_error(self, name: str):
|
|
||||||
self.terminate(f"Protocol timeout error: {name}")
|
|
||||||
|
|
||||||
def terminate(self, error: str = None):
|
def terminate(self, error: str = None):
|
||||||
|
self.terminated = True
|
||||||
|
self.state = LSState.LSSTATE_ERROR
|
||||||
with contextlib.suppress(Exception):
|
with contextlib.suppress(Exception):
|
||||||
RNS.log("Terminating session" + (f": {error}" if error else ""), RNS.LOG_DEBUG)
|
RNS.log("Terminating session" + (f": {error}" if error else ""), RNS.LOG_DEBUG)
|
||||||
if error and self.state != LSState.LSSTATE_TEARDOWN:
|
if error and self.state != LSState.LSSTATE_TEARDOWN:
|
||||||
with contextlib.suppress(Exception):
|
with contextlib.suppress(Exception):
|
||||||
self.send(protocol.ErrorMessage(error, True))
|
self.send(protocol.ErrorMessage(error, True))
|
||||||
|
|
||||||
self.state = LSState.LSSTATE_ERROR
|
|
||||||
self._terminate_process()
|
self._terminate_process()
|
||||||
self._call(self._prune, max(self.outlet.rtt * 3, process.CallbackSubprocess.PROCESS_PIPE_TIME+5))
|
self._call(self._prune, max(self.outlet.rtt * 3, process.CallbackSubprocess.PROCESS_PIPE_TIME+5))
|
||||||
|
|
||||||
def _prune(self):
|
def _prune(self):
|
||||||
self.state = LSState.LSSTATE_TEARDOWN
|
self.state = LSState.LSSTATE_TEARDOWN
|
||||||
RNS.log("Pruning session", RNS.LOG_DEBUG)
|
RNS.log("Pruning session", RNS.LOG_DEBUG)
|
||||||
with contextlib.suppress(ValueError):
|
with contextlib.suppress(ValueError): self.sessions.remove(self)
|
||||||
self.sessions.remove(self)
|
with contextlib.suppress(Exception): self.outlet.teardown()
|
||||||
with contextlib.suppress(Exception):
|
|
||||||
self.outlet.teardown()
|
|
||||||
|
|
||||||
def _check_protocol_timeout(self, fail_condition: Callable[[], bool], name: str):
|
def _check_protocol_timeout(self, fail_condition: Callable[[], bool], name: str):
|
||||||
timeout = True
|
timeout = True
|
||||||
@@ -183,7 +181,6 @@ class ListenerSession:
|
|||||||
|
|
||||||
def _link_closed(self, outlet: LSOutletBase):
|
def _link_closed(self, outlet: LSOutletBase):
|
||||||
outlet.unset_link_closed_callback()
|
outlet.unset_link_closed_callback()
|
||||||
|
|
||||||
if outlet != self.outlet:
|
if outlet != self.outlet:
|
||||||
RNS.log("Link closed received from incorrect outlet", RNS.LOG_DEBUG)
|
RNS.log("Link closed received from incorrect outlet", RNS.LOG_DEBUG)
|
||||||
return
|
return
|
||||||
@@ -200,11 +197,14 @@ class ListenerSession:
|
|||||||
if self.state not in [LSState.LSSTATE_WAIT_IDENT, LSState.LSSTATE_WAIT_VERS]:
|
if self.state not in [LSState.LSSTATE_WAIT_IDENT, LSState.LSSTATE_WAIT_VERS]:
|
||||||
self._protocol_error(LSState.LSSTATE_WAIT_IDENT.name)
|
self._protocol_error(LSState.LSSTATE_WAIT_IDENT.name)
|
||||||
|
|
||||||
if not self.allow_all and identity.hash not in self.allowed_identity_hashes and identity.hash not in self.allowed_file_identity_hashes:
|
if self.allow_all or identity.hash in self.allowed_identity_hashes or identity.hash in self.allowed_file_identity_hashes:
|
||||||
self.terminate("Identity is not allowed.")
|
self.authenticated = True
|
||||||
|
self.remote_identity = identity
|
||||||
|
self._set_state(LSState.LSSTATE_WAIT_VERS)
|
||||||
|
|
||||||
self.remote_identity = identity
|
else:
|
||||||
self._set_state(LSState.LSSTATE_WAIT_VERS)
|
self.authenticated = False
|
||||||
|
self.terminate("Identity not allowed")
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def pump_all(cls) -> True:
|
async def pump_all(cls) -> True:
|
||||||
@@ -241,8 +241,7 @@ class ListenerSession:
|
|||||||
if compressed_length < max_data_len and compressed_length < chunk_segment_length:
|
if compressed_length < max_data_len and compressed_length < chunk_segment_length:
|
||||||
comp_success = True
|
comp_success = True
|
||||||
break
|
break
|
||||||
else:
|
else: comp_try += 1
|
||||||
comp_try += 1
|
|
||||||
|
|
||||||
if comp_success:
|
if comp_success:
|
||||||
diff = max_data_len - len(compressed_chunk)
|
diff = max_data_len - len(compressed_chunk)
|
||||||
@@ -255,10 +254,8 @@ class ListenerSession:
|
|||||||
return comp_success, processed_length, chunk
|
return comp_success, processed_length, chunk
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if self.state != LSState.LSSTATE_RUNNING:
|
if self.state != LSState.LSSTATE_RUNNING: return False
|
||||||
return False
|
elif not self.channel.is_ready_to_send(): return False
|
||||||
elif not self.channel.is_ready_to_send():
|
|
||||||
return False
|
|
||||||
elif len(self.stderr_buf) > 0:
|
elif len(self.stderr_buf) > 0:
|
||||||
comp_success, processed_length, data = compress_adaptive(self.stderr_buf)
|
comp_success, processed_length, data = compress_adaptive(self.stderr_buf)
|
||||||
self.stderr_buf = self.stderr_buf[processed_length:]
|
self.stderr_buf = self.stderr_buf[processed_length:]
|
||||||
@@ -267,8 +264,7 @@ class ListenerSession:
|
|||||||
msg = protocol.StreamDataMessage(protocol.StreamDataMessage.STREAM_ID_STDERR,
|
msg = protocol.StreamDataMessage(protocol.StreamDataMessage.STREAM_ID_STDERR,
|
||||||
data, send_eof, comp_success)
|
data, send_eof, comp_success)
|
||||||
self.send(msg)
|
self.send(msg)
|
||||||
if send_eof:
|
if send_eof: self.stderr_eof_sent = True
|
||||||
self.stderr_eof_sent = True
|
|
||||||
return True
|
return True
|
||||||
elif len(self.stdout_buf) > 0:
|
elif len(self.stdout_buf) > 0:
|
||||||
comp_success, processed_length, data = compress_adaptive(self.stdout_buf)
|
comp_success, processed_length, data = compress_adaptive(self.stdout_buf)
|
||||||
@@ -278,8 +274,7 @@ class ListenerSession:
|
|||||||
msg = protocol.StreamDataMessage(protocol.StreamDataMessage.STREAM_ID_STDOUT,
|
msg = protocol.StreamDataMessage(protocol.StreamDataMessage.STREAM_ID_STDOUT,
|
||||||
data, send_eof, comp_success)
|
data, send_eof, comp_success)
|
||||||
self.send(msg)
|
self.send(msg)
|
||||||
if send_eof:
|
if send_eof: self.stdout_eof_sent = True
|
||||||
self.stdout_eof_sent = True
|
|
||||||
return True
|
return True
|
||||||
elif self.return_code is not None and not self.return_code_sent:
|
elif self.return_code is not None and not self.return_code_sent:
|
||||||
msg = protocol.CommandExitedMessage(self.return_code)
|
msg = protocol.CommandExitedMessage(self.return_code)
|
||||||
@@ -295,22 +290,32 @@ class ListenerSession:
|
|||||||
|
|
||||||
def _terminate_process(self):
|
def _terminate_process(self):
|
||||||
with contextlib.suppress(Exception):
|
with contextlib.suppress(Exception):
|
||||||
if self.process and self.process.running:
|
if self.process and self.process.running: self.process.terminate()
|
||||||
self.process.terminate()
|
|
||||||
|
|
||||||
def _start_cmd(self, cmdline: [str], pipe_stdin: bool, pipe_stdout: bool, pipe_stderr: bool, tcflags: [any],
|
def _start_cmd(self, cmdline: [str], pipe_stdin: bool, pipe_stdout: bool, pipe_stderr: bool, tcflags: [any],
|
||||||
term: str | None, rows: int, cols: int, hpix: int, vpix: int):
|
term: str | None, rows: int, cols: int, hpix: int, vpix: int):
|
||||||
|
|
||||||
|
if self.terminated:
|
||||||
|
RNS.log(f"Attempt to start command on terminated session: {cmdline}", RNS.LOG_WARNING)
|
||||||
|
return
|
||||||
|
|
||||||
|
if not self.authenticated:
|
||||||
|
RNS.log(f"Attempt to start command on unauthenticated session: {cmdline}", RNS.LOG_WARNING)
|
||||||
|
self.terminate("Invalid state")
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.state != LSState.LSSTATE_RUNNING:
|
||||||
|
RNS.log(f"Attempt to start command on session not in running state: {cmdline}", RNS.LOG_WARNING)
|
||||||
|
self.terminate("Invalid state")
|
||||||
|
return
|
||||||
|
|
||||||
self.cmdline = self.default_command
|
self.cmdline = self.default_command
|
||||||
if not self.allow_remote_command and cmdline and len(cmdline) > 0:
|
if not self.allow_remote_command and cmdline and len(cmdline) > 0:
|
||||||
self.terminate("Remote command line not allowed by listener")
|
self.terminate("Remote command line not allowed by listener")
|
||||||
return
|
return
|
||||||
|
|
||||||
if self.remote_cmd_as_args and cmdline and len(cmdline) > 0:
|
if self.remote_cmd_as_args and cmdline and len(cmdline) > 0: self.cmdline.extend(cmdline)
|
||||||
self.cmdline.extend(cmdline)
|
elif cmdline and len(cmdline) > 0: self.cmdline = cmdline
|
||||||
elif cmdline and len(cmdline) > 0:
|
|
||||||
self.cmdline = cmdline
|
|
||||||
|
|
||||||
|
|
||||||
self.stdin_is_pipe = pipe_stdin
|
self.stdin_is_pipe = pipe_stdin
|
||||||
self.stdout_is_pipe = pipe_stdout
|
self.stdout_is_pipe = pipe_stdout
|
||||||
@@ -318,11 +323,8 @@ class ListenerSession:
|
|||||||
self.tcflags = tcflags
|
self.tcflags = tcflags
|
||||||
self.term = term
|
self.term = term
|
||||||
|
|
||||||
def stdout(data: bytes):
|
def stdout(data: bytes): self.stdout_buf.extend(data)
|
||||||
self.stdout_buf.extend(data)
|
def stderr(data: bytes): self.stderr_buf.extend(data)
|
||||||
|
|
||||||
def stderr(data: bytes):
|
|
||||||
self.stderr_buf.extend(data)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.process = process.CallbackSubprocess(argv=self.cmdline,
|
self.process = process.CallbackSubprocess(argv=self.cmdline,
|
||||||
@@ -347,22 +349,28 @@ class ListenerSession:
|
|||||||
self.cols = cols
|
self.cols = cols
|
||||||
self.hpix = hpix
|
self.hpix = hpix
|
||||||
self.vpix = vpix
|
self.vpix = vpix
|
||||||
with contextlib.suppress(Exception):
|
with contextlib.suppress(Exception): self.process.set_winsize(rows, cols, hpix, vpix)
|
||||||
self.process.set_winsize(rows, cols, hpix, vpix)
|
|
||||||
|
|
||||||
def _received_stdin(self, data: bytes, eof: bool):
|
def _received_stdin(self, data: bytes, eof: bool):
|
||||||
if data and len(data) > 0:
|
if data and len(data) > 0: self.process.write(data)
|
||||||
self.process.write(data)
|
if eof: self.process.close_stdin()
|
||||||
if eof:
|
|
||||||
self.process.close_stdin()
|
|
||||||
|
|
||||||
def _handle_message(self, message: RNS.MessageBase):
|
def _handle_message(self, message: RNS.MessageBase):
|
||||||
|
if self.terminated:
|
||||||
|
RNS.log(f"Received packet, but session was terminated", RNS.LOG_ERROR)
|
||||||
|
return
|
||||||
|
if self.state > LSState.LSSTATE_RUNNING:
|
||||||
|
RNS.log(f"Received packet, but in state {self.state.name}", RNS.LOG_ERROR)
|
||||||
|
return
|
||||||
if self.state == LSState.LSSTATE_WAIT_IDENT:
|
if self.state == LSState.LSSTATE_WAIT_IDENT:
|
||||||
# Ignore any messages until the initiator has identified to avoid race conditions
|
# Ignore any messages until the initiator has identified to avoid race conditions
|
||||||
# between identity announcement and early protocol messages.
|
# between identity announcement and early protocol messages.
|
||||||
RNS.log("Ignoring message while waiting for identification", RNS.LOG_DEBUG)
|
RNS.log("Ignoring message while waiting for identification", RNS.LOG_DEBUG)
|
||||||
return
|
return
|
||||||
if self.state == LSState.LSSTATE_WAIT_VERS:
|
elif self.state == LSState.LSSTATE_WAIT_VERS:
|
||||||
|
if not self.authenticated:
|
||||||
|
self.terminate("Invalid state")
|
||||||
|
return
|
||||||
if not isinstance(message, protocol.VersionInfoMessage):
|
if not isinstance(message, protocol.VersionInfoMessage):
|
||||||
self._protocol_error(self.state.name)
|
self._protocol_error(self.state.name)
|
||||||
return
|
return
|
||||||
@@ -374,6 +382,9 @@ class ListenerSession:
|
|||||||
self._set_state(LSState.LSSTATE_WAIT_CMD)
|
self._set_state(LSState.LSSTATE_WAIT_CMD)
|
||||||
return
|
return
|
||||||
elif self.state == LSState.LSSTATE_WAIT_CMD:
|
elif self.state == LSState.LSSTATE_WAIT_CMD:
|
||||||
|
if not self.authenticated:
|
||||||
|
self.terminate("Invalid state")
|
||||||
|
return
|
||||||
if not isinstance(message, protocol.ExecuteCommandMesssage):
|
if not isinstance(message, protocol.ExecuteCommandMesssage):
|
||||||
return self._protocol_error(self.state.name)
|
return self._protocol_error(self.state.name)
|
||||||
RNS.log(f"Execute command message on link {self.outlet}: {message.cmdline}", RNS.LOG_VERBOSE)
|
RNS.log(f"Execute command message on link {self.outlet}: {message.cmdline}", RNS.LOG_VERBOSE)
|
||||||
@@ -382,6 +393,9 @@ class ListenerSession:
|
|||||||
message.tcflags, message.term, message.rows, message.cols, message.hpix, message.vpix)
|
message.tcflags, message.term, message.rows, message.cols, message.hpix, message.vpix)
|
||||||
return
|
return
|
||||||
elif self.state == LSState.LSSTATE_RUNNING:
|
elif self.state == LSState.LSSTATE_RUNNING:
|
||||||
|
if not self.authenticated:
|
||||||
|
self.terminate("Invalid state")
|
||||||
|
return
|
||||||
if isinstance(message, protocol.WindowSizeMessage):
|
if isinstance(message, protocol.WindowSizeMessage):
|
||||||
self._set_window_size(message.rows, message.cols, message.hpix, message.vpix)
|
self._set_window_size(message.rows, message.cols, message.hpix, message.vpix)
|
||||||
elif isinstance(message, protocol.StreamDataMessage):
|
elif isinstance(message, protocol.StreamDataMessage):
|
||||||
@@ -394,9 +408,6 @@ class ListenerSession:
|
|||||||
# echo noop only on listener--used for keepalive/connectivity check
|
# echo noop only on listener--used for keepalive/connectivity check
|
||||||
self.send(message)
|
self.send(message)
|
||||||
return
|
return
|
||||||
elif self.state in [LSState.LSSTATE_ERROR, LSState.LSSTATE_TEARDOWN]:
|
|
||||||
RNS.log(f"Received packet, but in state {self.state.name}", RNS.LOG_ERROR)
|
|
||||||
return
|
|
||||||
else:
|
else:
|
||||||
self._protocol_error("unexpected message")
|
self._protocol_error("unexpected message")
|
||||||
return
|
return
|
||||||
@@ -405,29 +416,20 @@ class ListenerSession:
|
|||||||
class RNSOutlet(LSOutletBase):
|
class RNSOutlet(LSOutletBase):
|
||||||
|
|
||||||
def set_initiator_identified_callback(self, cb: Callable[[LSOutletBase, _TIdentity], None]):
|
def set_initiator_identified_callback(self, cb: Callable[[LSOutletBase, _TIdentity], None]):
|
||||||
def inner_cb(link, identity: _TIdentity):
|
def inner_cb(link, identity: _TIdentity): cb(self, identity)
|
||||||
cb(self, identity)
|
|
||||||
|
|
||||||
self.link.set_remote_identified_callback(inner_cb)
|
self.link.set_remote_identified_callback(inner_cb)
|
||||||
|
|
||||||
def set_link_closed_callback(self, cb: Callable[[LSOutletBase], None]):
|
def set_link_closed_callback(self, cb: Callable[[LSOutletBase], None]):
|
||||||
def inner_cb(link):
|
def inner_cb(link): cb(self)
|
||||||
cb(self)
|
|
||||||
|
|
||||||
self.link.set_link_closed_callback(inner_cb)
|
self.link.set_link_closed_callback(inner_cb)
|
||||||
|
|
||||||
def unset_link_closed_callback(self):
|
def unset_link_closed_callback(self): self.link.set_link_closed_callback(None)
|
||||||
self.link.set_link_closed_callback(None)
|
def teardown(self): self.link.teardown()
|
||||||
|
|
||||||
def teardown(self):
|
|
||||||
self.link.teardown()
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def rtt(self) -> float:
|
def rtt(self) -> float: return self.link.rtt
|
||||||
return self.link.rtt
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self): return f"Outlet on {self.link}"
|
||||||
return f"Outlet RNS Link {self.link}"
|
|
||||||
|
|
||||||
def __init__(self, link: RNS.Link):
|
def __init__(self, link: RNS.Link):
|
||||||
self.link = link
|
self.link = link
|
||||||
@@ -435,7 +437,5 @@ class RNSOutlet(LSOutletBase):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_outlet(link: RNS.Link):
|
def get_outlet(link: RNS.Link):
|
||||||
if hasattr(link, "lsoutlet"):
|
if hasattr(link, "lsoutlet"): return link.lsoutlet
|
||||||
return link.lsoutlet
|
|
||||||
|
|
||||||
return RNSOutlet(link)
|
return RNSOutlet(link)
|
||||||
@@ -423,6 +423,7 @@ def program_setup(configdir, dispall=False, verbosity=0, name_filter=None, json=
|
|||||||
elif ifstat["mode"] == RNS.Interfaces.Interface.Interface.MODE_ROAMING: modestr = "Roaming"
|
elif ifstat["mode"] == RNS.Interfaces.Interface.Interface.MODE_ROAMING: modestr = "Roaming"
|
||||||
elif ifstat["mode"] == RNS.Interfaces.Interface.Interface.MODE_BOUNDARY: modestr = "Boundary"
|
elif ifstat["mode"] == RNS.Interfaces.Interface.Interface.MODE_BOUNDARY: modestr = "Boundary"
|
||||||
elif ifstat["mode"] == RNS.Interfaces.Interface.Interface.MODE_GATEWAY: modestr = "Gateway"
|
elif ifstat["mode"] == RNS.Interfaces.Interface.Interface.MODE_GATEWAY: modestr = "Gateway"
|
||||||
|
elif ifstat["mode"] == RNS.Interfaces.Interface.Interface.MODE_INTERNAL: modestr = "Internal"
|
||||||
else: modestr = "Full"
|
else: modestr = "Full"
|
||||||
|
|
||||||
|
|
||||||
@@ -449,6 +450,9 @@ def program_setup(configdir, dispall=False, verbosity=0, name_filter=None, json=
|
|||||||
clients_string = ""
|
clients_string = ""
|
||||||
else:
|
else:
|
||||||
clients_string = "Clients : "+str(clients)
|
clients_string = "Clients : "+str(clients)
|
||||||
|
if "blocked_ips" in ifstat:
|
||||||
|
p = ifstat["blocked_ips"] > 0
|
||||||
|
if p: clients_string += "\n Blocked : "+str(ifstat["blocked_ips"])+" IP"+"s" if p else ""
|
||||||
|
|
||||||
else:
|
else:
|
||||||
clients = None
|
clients = None
|
||||||
|
|||||||
+3
-1
@@ -70,7 +70,8 @@ LOG_NOTICE = 3
|
|||||||
LOG_INFO = 4
|
LOG_INFO = 4
|
||||||
LOG_VERBOSE = 5
|
LOG_VERBOSE = 5
|
||||||
LOG_DEBUG = 6
|
LOG_DEBUG = 6
|
||||||
LOG_EXTREME = 7
|
LOG_PATHING = 7
|
||||||
|
LOG_EXTREME = 8
|
||||||
|
|
||||||
LOG_STDOUT = 0x91
|
LOG_STDOUT = 0x91
|
||||||
LOG_FILE = 0x92
|
LOG_FILE = 0x92
|
||||||
@@ -102,6 +103,7 @@ def loglevelname(level):
|
|||||||
if (level == LOG_INFO): return "[Info] "
|
if (level == LOG_INFO): return "[Info] "
|
||||||
if (level == LOG_VERBOSE): return "[Verbose] "
|
if (level == LOG_VERBOSE): return "[Verbose] "
|
||||||
if (level == LOG_DEBUG): return "[Debug] "
|
if (level == LOG_DEBUG): return "[Debug] "
|
||||||
|
if (level == LOG_PATHING): return "[Pathing] "
|
||||||
if (level == LOG_EXTREME): return "[Extra] "
|
if (level == LOG_EXTREME): return "[Extra] "
|
||||||
|
|
||||||
return "Unknown"
|
return "Unknown"
|
||||||
|
|||||||
+1
-1
@@ -1 +1 @@
|
|||||||
__version__ = "1.3.2"
|
__version__ = "1.3.9"
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -1,4 +1,4 @@
|
|||||||
# Sphinx build info version 1
|
# Sphinx build info version 1
|
||||||
# This file records the configuration used when building these files. When it is not found, a full rebuild will be done.
|
# This file records the configuration used when building these files. When it is not found, a full rebuild will be done.
|
||||||
config: af83acf0d95a344cb0386ad87adf5695
|
config: b7a9e97674355dfa0f0218a058ae09df
|
||||||
tags: 645f666f9bcd5a90fca523b33c5a78b7
|
tags: 645f666f9bcd5a90fca523b33c5a78b7
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 35 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
@@ -1326,7 +1326,7 @@ Each document is a numbered directory containing:
|
|||||||
|
|
||||||
**Nomad Network Interface**
|
**Nomad Network Interface**
|
||||||
|
|
||||||
When the Nomad Network page node is enabled, work documents are viewable through the web interface. The work page lists all documents with their status, and clicking a document shows its full content and updates.
|
When the Nomad Network page node is enabled, work documents are viewable through the nomadnet interface. The work page lists all documents with their status, and clicking a document shows its full content and updates.
|
||||||
|
|
||||||
Cryptographic Attribution
|
Cryptographic Attribution
|
||||||
-------------------------
|
-------------------------
|
||||||
@@ -1491,3 +1491,22 @@ To view signature information for commits, use Git's standard ``--show-signature
|
|||||||
Refactored module
|
Refactored module
|
||||||
|
|
||||||
The output shows whether the commit signature is valid, and whether the author field matches the signing identity.
|
The output shows whether the commit signature is valid, and whether the author field matches the signing identity.
|
||||||
|
|
||||||
|
.. tip::
|
||||||
|
If you want to display both the identity hash and LXMF address for authors, you can generate a ``.mailmap`` file that resolves identities to LXMF addresses with the following script:
|
||||||
|
|
||||||
|
.. code::
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
|
||||||
|
cd $DIR
|
||||||
|
|
||||||
|
id_regex="<([0-9a-f]{32})( .*)*>"
|
||||||
|
extract_id="s/.*$id_regex/\1/g"
|
||||||
|
|
||||||
|
rm -f .mailmap
|
||||||
|
git shortlog -se | grep -Ee "$id_regex" | sed -r "$extract_id" | while read -r id ; do
|
||||||
|
if lxmf=$(rnid -i $id -H lxmf.delivery | grep -Ee "destination for this Identity is" | sed -r "$extract_id"); then
|
||||||
|
echo "<$id lxmf:$lxmf> <$id>" >> .mailmap
|
||||||
|
fi
|
||||||
|
done
|
||||||
@@ -230,6 +230,51 @@ specify the target Yggdrasil IPv6 address and port, like so:
|
|||||||
target_host = 201:5d78:af73:5caf:a4de:a79f:3278:71e5
|
target_host = 201:5d78:af73:5caf:a4de:a79f:3278:71e5
|
||||||
target_port = 4343
|
target_port = 4343
|
||||||
|
|
||||||
|
Automated Blocking
|
||||||
|
------------------
|
||||||
|
Listener instances of ``BackboneInterface`` will automatically block fast-flapping clients,
|
||||||
|
that repeatedly connect for a short amount of time, and then disconnect. Such behavior usually
|
||||||
|
occurs from clients trying to spam the network with announces or path requests, by connecting, dumping
|
||||||
|
a massive amount of requests, and then disconnecting in an attempt to bypass rate limits.
|
||||||
|
|
||||||
|
While such bypass attempts only have very limited effect on the amount of spam actually dumped (ingress limits
|
||||||
|
trigger immediately once a client exceeds rate limits), the behavior is often seen anyways, and
|
||||||
|
causes log noise and needless interface rotation. The fast-flapping block ensures such clients
|
||||||
|
are never allocated an interface on the transport instance.
|
||||||
|
|
||||||
|
Another common cause can simply be clients using implementations that are broken, or automated
|
||||||
|
scanning tools attempting to connect to the instance.
|
||||||
|
|
||||||
|
.. code:: ini
|
||||||
|
|
||||||
|
[[Backbone Listener]]
|
||||||
|
type = BackboneInterface
|
||||||
|
enabled = yes
|
||||||
|
listen_on = 0.0.0.0
|
||||||
|
port = 4242
|
||||||
|
|
||||||
|
# Whether to enable blocking
|
||||||
|
block_fast_flapping = yes
|
||||||
|
|
||||||
|
# How long an IP address stays
|
||||||
|
# blocked, in minutes. Set to
|
||||||
|
# 12 hours by default.
|
||||||
|
fast_flapping_block_time = 720
|
||||||
|
|
||||||
|
# The minimum time, in seconds,
|
||||||
|
# an interface must stay connected
|
||||||
|
# to be considered not fast-flapping.
|
||||||
|
fast_flapping_threshold = 20
|
||||||
|
|
||||||
|
# Amount of fast flaps a remote
|
||||||
|
# IP can perform before having
|
||||||
|
# blocking triggered.
|
||||||
|
fast_flapping_grace = 5
|
||||||
|
|
||||||
|
The configuration options listed in the example above are the *default values*, and you do
|
||||||
|
not need to add them for automated blocking to work, but you can use them to change the
|
||||||
|
behavior from the defaults, if necessary.
|
||||||
|
|
||||||
.. _interfaces-tcps:
|
.. _interfaces-tcps:
|
||||||
|
|
||||||
TCP Server Interface
|
TCP Server Interface
|
||||||
@@ -998,7 +1043,13 @@ On a real system, you should make the script robust enough to deal with intermit
|
|||||||
**Physical Location**
|
**Physical Location**
|
||||||
|
|
||||||
``latitude``, ``longitude``, ``height``
|
``latitude``, ``longitude``, ``height``
|
||||||
Optional physical coordinates for the interface. These are useful for mapping discovered interfaces geographically or for clients to automatically select the nearest access point. Coordinates should be in decimal degrees, height in meters.
|
Optional physical coordinates for the interface. These are useful for mapping discovered interfaces geographically or for clients to automatically select the nearest access point. Coordinates should be in decimal degrees, height in meters above mean sea level.
|
||||||
|
|
||||||
|
``location_cmd``
|
||||||
|
Optional path to executable or script that returns the physical coordinates for the interface. This can be used instead of manually setting ``latitude``, ``longitude`` and ``height``. Reticulum expects the script to output the location data to ``stdout`` on a single line, separated by commas, with values as floating point numbers in the format ``LAT, LON, HEIGHT``. Coordinates should be in decimal degrees, height in meters above mean sea level.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
The height value for interface discovery is specified in *height above mean sea level*! This is the geoid-corrected height value, and is distinct from GPS altitude. If you manually specify height, it will typically be set to what you find on a topographical map. If you are using a script to output the location data, make sure that you are using the geoid-corrected altitude, not height above the GPS ellipsoid. Most GPS systems will make both figures available.
|
||||||
|
|
||||||
**Radio Parameters**
|
**Radio Parameters**
|
||||||
|
|
||||||
@@ -1207,6 +1258,22 @@ These can be used to control various aspects of interface behaviour.
|
|||||||
link or a remote TCP tunnel) solely to discover better local
|
link or a remote TCP tunnel) solely to discover better local
|
||||||
infrastructure, which then supersedes the bootstrap interface.
|
infrastructure, which then supersedes the bootstrap interface.
|
||||||
|
|
||||||
|
|
||||||
|
* | The ``recursive_prs`` option allows you to enable recursive path
|
||||||
|
discovery on an interface regardless of its configured interface
|
||||||
|
mode. When this option is enabled, Reticulum will attempt to
|
||||||
|
recursively discover paths for path requests received on this
|
||||||
|
interface.
|
||||||
|
|
||||||
|
|
||||||
|
* | The ``announces_from_internal`` option controls whether any
|
||||||
|
announces *received on* an ``internal`` mode interface will
|
||||||
|
propagate *out* on the interface this option is set on. Note
|
||||||
|
that this controls *announce propagation*; even if announces
|
||||||
|
do not propagate out when received, paths to destinations on
|
||||||
|
``internal`` mode interfaces may still be resolvable by means
|
||||||
|
of path requests.
|
||||||
|
|
||||||
.. _interfaces-modes:
|
.. _interfaces-modes:
|
||||||
|
|
||||||
Interface Modes
|
Interface Modes
|
||||||
@@ -1284,6 +1351,15 @@ the default mode.
|
|||||||
public Transport Node available on the Internet, the interface
|
public Transport Node available on the Internet, the interface
|
||||||
connecting over the Internet should be set to ``boundary`` mode.
|
connecting over the Internet should be set to ``boundary`` mode.
|
||||||
|
|
||||||
|
* | The ``internal`` mode designates interfaces that belong to a
|
||||||
|
network different from any marked as ``boundary``. Announces from
|
||||||
|
a ``boundary`` interface will not propagate to interfaces set as
|
||||||
|
``internal``, but announces *will* propagate from ``internal`` *to*
|
||||||
|
``boundary``. Devices on the ``internal`` side of the network will
|
||||||
|
still be able to resolve paths to destinations across the boundary
|
||||||
|
when needed, since recursive path requests are enabled for ``internal``
|
||||||
|
mode interfaces by default.
|
||||||
|
|
||||||
For a table describing the impact of all modes on announce propagation,
|
For a table describing the impact of all modes on announce propagation,
|
||||||
please see the :ref:`Announce Propagation Rules<understanding-announcepropagation>` section.
|
please see the :ref:`Announce Propagation Rules<understanding-announcepropagation>` section.
|
||||||
|
|
||||||
|
|||||||
@@ -707,32 +707,58 @@ Announce Propagation Rules
|
|||||||
|
|
||||||
The following table illustrates the rules for automatically propagating announces from one interface type to another, for all possible combinations. For the purpose of announce propagation, the *Full* and *Gateway* modes are identical.
|
The following table illustrates the rules for automatically propagating announces from one interface type to another, for all possible combinations. For the purpose of announce propagation, the *Full* and *Gateway* modes are identical.
|
||||||
|
|
||||||
|
See the :ref:`Interface Modes<interfaces-modes>` section for a conceptual overview of the different interface modes, and how they are configured.
|
||||||
|
|
||||||
.. image:: graphics/if_mode_graph_b.png
|
.. image:: graphics/if_mode_graph_b.png
|
||||||
|
|
||||||
See the :ref:`Interface Modes<interfaces-modes>` section for a conceptual overview of the different interface modes, and how they are configured.
|
Or, represented in table form:
|
||||||
|
|
||||||
|
.. image:: graphics/if_mode_graph_c.png
|
||||||
|
|
||||||
..
|
..
|
||||||
(.. code-block:: text)
|
(.. code-block:: text)
|
||||||
Full ────── ✓ ──┐ ┌── ✓ ── Full
|
Full ────── ✓ ──┐ ┌── ✓ ── Full
|
||||||
AP ──────── ✓ ──┼───> Full >───┼── ✕ ── AP
|
AP ──────── ✓ ──┤ ├── ✕ ── AP
|
||||||
Boundary ── ✓ ──┤ ├── ✓ ── Boundary
|
Boundary ── ✓ ──┼───> Full >───┼── ✓ ── Boundary
|
||||||
|
Internal ── ✓ ──┤ ├── ✓ ── Internal
|
||||||
Roaming ─── ✓ ──┘ └── ✓ ── Roaming
|
Roaming ─── ✓ ──┘ └── ✓ ── Roaming
|
||||||
|
|
||||||
Full ────── ✕ ──┐ ┌── ✓ ── Full
|
Full ────── ✕ ──┐ ┌── ✓ ── Full
|
||||||
AP ──────── ✕ ──┼────> AP >────┼── ✕ ── AP
|
AP ──────── ✕ ──┤ ├── ✕ ── AP
|
||||||
Boundary ── ✕ ──┤ ├── ✓ ── Boundary
|
Boundary ── ✕ ──┼────> AP >────┼── ✓ ── Boundary
|
||||||
|
Internal ── ✕ ──┤ ├── ✓ ── Internal
|
||||||
Roaming ─── ✕ ──┘ └── ✓ ── Roaming
|
Roaming ─── ✕ ──┘ └── ✓ ── Roaming
|
||||||
|
|
||||||
Full ────── ✓ ──┐ ┌── ✓ ── Full
|
Full ────── ✓ ──┐ ┌── ✓ ── Full
|
||||||
AP ──────── ✓ ──┼─> Roaming >──┼── ✕ ── AP
|
AP ──────── ✓ ──┤ ├── ✕ ── AP
|
||||||
Boundary ── ✕ ──┤ ├── ✕ ── Boundary
|
Boundary ── ✕ ──┼─> Roaming >──┼── ✕ ── Boundary
|
||||||
|
Internal ── ✕ ──┤ ├── ✓ ── Internal
|
||||||
Roaming ─── ✕ ──┘ └── ✕ ── Roaming
|
Roaming ─── ✕ ──┘ └── ✕ ── Roaming
|
||||||
|
|
||||||
Full ────── ✓ ──┐ ┌── ✓ ── Full
|
Full ────── ✓ ──┐ ┌── ✓ ── Full
|
||||||
AP ──────── ✓ ──┼─> Boundary >─┼── ✕ ── AP
|
AP ──────── ✓ ──┤ ├── ✕ ── AP
|
||||||
Boundary ── ✓ ──┤ ├── ✓ ── Boundary
|
Boundary ── ✓ ──┼─> Boundary >─┼── ✓ ── Boundary
|
||||||
|
Internal ── ✓ ──┤ ├── ✕ ── Internal
|
||||||
Roaming ─── ✕ ──┘ └── ✕ ── Roaming
|
Roaming ─── ✕ ──┘ └── ✕ ── Roaming
|
||||||
|
|
||||||
|
Full ────── ✓ ──┐ ┌── ✓ ── Full
|
||||||
|
AP ──────── ✓ ──┤ ├── ✕ ── AP
|
||||||
|
Boundary ── ✕ ──┼─> Internal >─┼── ✓ ── Boundary
|
||||||
|
Internal ── ✓ ──┤ ├── ✓ ── Internal
|
||||||
|
Roaming ─── ✓ ──┘ └── ✕ ── Roaming
|
||||||
|
|
||||||
|
dest → Full AP Boundary Roaming Gateway Internal
|
||||||
|
source ↓
|
||||||
|
Full ✓ ✕ ✓ ✓ ✓ ✓
|
||||||
|
Gateway ✓ ✕ ✓ ✓ ✓ ✓
|
||||||
|
AP ✓ ✕ ✓ ✓ ✓ ✓
|
||||||
|
Boundary ✓ ✕ ✓ ✕ ✓ ✕
|
||||||
|
Internal ✓ ✕ ✓ ✕ ✓ ✓
|
||||||
|
Roaming ✓ ✕ ✕ ✕ ✓ ✓
|
||||||
|
|
||||||
|
✓ Will rebroadcast announce
|
||||||
|
✕ Will not rebroadcast announce
|
||||||
|
|
||||||
|
|
||||||
.. _understanding-primitives:
|
.. _understanding-primitives:
|
||||||
|
|
||||||
|
|||||||
@@ -1002,39 +1002,35 @@ available. These are only recognised immediately after a newline character:
|
|||||||
destination hexadecimal hash of the destination to connect to
|
destination hexadecimal hash of the destination to connect to
|
||||||
|
|
||||||
options:
|
options:
|
||||||
-h, --help show this help message and exit
|
-h, --help show this help message and exit
|
||||||
--config, -c CONFIG path to alternative Reticulum config directory
|
--config, -c PATH path to config directory
|
||||||
--identity, -i IDENTITY
|
--rnsconfig, -c PATH path to alternative Reticulum config directory
|
||||||
path to identity file to use
|
--identity, -i IDENTITY path to identity file to use
|
||||||
-v, --verbose increase verbosity
|
-v, --verbose increase verbosity
|
||||||
-q, --quiet decrease verbosity
|
-q, --quiet decrease verbosity
|
||||||
-p, --print-identity print identity and destination info and exit
|
-p, --print-identity print identity and destination info and exit
|
||||||
--version show program's version number and exit
|
--version show program's version number and exit
|
||||||
-l, --listen listen (server) mode; any command specified after --
|
-l, --listen listen (server) mode; any command specified after --
|
||||||
will be used as the default command when the initiator
|
will be used as the default command when the initiator
|
||||||
does not provide one or when remote command execution
|
does not provide one or when remote command execution
|
||||||
is disabled; if no command is specified, the default
|
is disabled; if no command is specified, the default
|
||||||
shell of the user running rnsh will be used
|
shell of the user running rnsh will be used
|
||||||
-s, --service SERVICE
|
-s, --service SERVICE service name for identity file if not the default
|
||||||
service name for identity file if not the default
|
-b, --announce PERIOD announce on startup and every PERIOD seconds; specify
|
||||||
-b, --announce PERIOD
|
0 to announce on startup only
|
||||||
announce on startup and every PERIOD seconds; specify
|
-a, --allowed HASH allow this identity to connect (may be specified
|
||||||
0 to announce on startup only
|
multiple times); allowed identities can also be
|
||||||
-a, --allowed HASH allow this identity to connect (may be specified
|
specified in ~/.rnsh/allowed_identities or
|
||||||
multiple times); allowed identities can also be
|
~/.config/rnsh/allowed_identities, one hash per line
|
||||||
specified in ~/.rnsh/allowed_identities or
|
-n, --no-auth disable authentication (allow any identity to connect)
|
||||||
~/.config/rnsh/allowed_identities, one hash per line
|
|
||||||
-n, --no-auth disable authentication (allow any identity to connect)
|
|
||||||
-A, --remote-command-as-args
|
-A, --remote-command-as-args
|
||||||
concatenate remote command to the argument list of the
|
concatenate remote command to the argument list of the
|
||||||
default program or shell
|
default program or shell
|
||||||
-C, --no-remote-command
|
-C, --no-remote-command disable executing command lines received from the
|
||||||
disable executing command lines received from the
|
remote initiator
|
||||||
remote initiator
|
-N, --no-id disable identity announcement on connect
|
||||||
-N, --no-id disable identity announcement on connect
|
-m, --mirror return with the exit code of the remote process
|
||||||
-m, --mirror return with the exit code of the remote process
|
-w, --timeout SECONDS connect and request timeout in seconds
|
||||||
-w, --timeout SECONDS
|
|
||||||
connect and request timeout in seconds
|
|
||||||
|
|
||||||
When specifying a command to execute, separate rnsh options from the command
|
When specifying a command to execute, separate rnsh options from the command
|
||||||
and its arguments with --. For example:
|
and its arguments with --. For example:
|
||||||
|
|||||||
@@ -14,6 +14,10 @@ img[src*="if_mode_graph_b.png"] {
|
|||||||
background-color: rgb(169, 177, 186);
|
background-color: rgb(169, 177, 186);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
img[src*="if_mode_graph_c.png"] {
|
||||||
|
background-color: rgb(169, 177, 186);
|
||||||
|
}
|
||||||
|
|
||||||
dt.sig {
|
dt.sig {
|
||||||
margin-bottom: 0.75rem;
|
margin-bottom: 0.75rem;
|
||||||
margin-top: 1.75rem;
|
margin-top: 1.75rem;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
const DOCUMENTATION_OPTIONS = {
|
const DOCUMENTATION_OPTIONS = {
|
||||||
VERSION: '1.3.2',
|
VERSION: '1.3.9',
|
||||||
LANGUAGE: 'en',
|
LANGUAGE: 'en',
|
||||||
COLLAPSE_INDEX: false,
|
COLLAPSE_INDEX: false,
|
||||||
BUILDER: 'html',
|
BUILDER: 'html',
|
||||||
|
|||||||
@@ -7,12 +7,12 @@
|
|||||||
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
||||||
|
|
||||||
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
||||||
<title>Distributed Development - Reticulum Network Stack 1.3.2 documentation</title>
|
<title>Distributed Development - Reticulum Network Stack 1.3.9 documentation</title>
|
||||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
|
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
|
||||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
|
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
|
||||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
||||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo-extensions.css?v=8dab3a3b" />
|
<link rel="stylesheet" type="text/css" href="_static/styles/furo-extensions.css?v=8dab3a3b" />
|
||||||
<link rel="stylesheet" type="text/css" href="_static/custom.css?v=bb3cebc5" />
|
<link rel="stylesheet" type="text/css" href="_static/custom.css?v=e1715074" />
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -180,7 +180,7 @@
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="header-center">
|
<div class="header-center">
|
||||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.3.2 documentation</div></a>
|
<a href="index.html"><div class="brand">Reticulum Network Stack 1.3.9 documentation</div></a>
|
||||||
</div>
|
</div>
|
||||||
<div class="header-right">
|
<div class="header-right">
|
||||||
<div class="theme-toggle-container theme-toggle-header">
|
<div class="theme-toggle-container theme-toggle-header">
|
||||||
@@ -204,7 +204,7 @@
|
|||||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.3.2 documentation</span>
|
<span class="sidebar-brand-text">Reticulum Network Stack 1.3.9 documentation</span>
|
||||||
|
|
||||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||||
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
||||||
@@ -431,7 +431,7 @@
|
|||||||
|
|
||||||
</aside>
|
</aside>
|
||||||
</div>
|
</div>
|
||||||
</div><script src="_static/documentation_options.js?v=8ca9e7a0"></script>
|
</div><script src="_static/documentation_options.js?v=98c25ec5"></script>
|
||||||
<script src="_static/doctools.js?v=9bcbadda"></script>
|
<script src="_static/doctools.js?v=9bcbadda"></script>
|
||||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||||
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
||||||
|
|||||||
@@ -7,12 +7,12 @@
|
|||||||
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
||||||
|
|
||||||
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
||||||
<title>Code Examples - Reticulum Network Stack 1.3.2 documentation</title>
|
<title>Code Examples - Reticulum Network Stack 1.3.9 documentation</title>
|
||||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
|
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
|
||||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
|
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
|
||||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
||||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo-extensions.css?v=8dab3a3b" />
|
<link rel="stylesheet" type="text/css" href="_static/styles/furo-extensions.css?v=8dab3a3b" />
|
||||||
<link rel="stylesheet" type="text/css" href="_static/custom.css?v=bb3cebc5" />
|
<link rel="stylesheet" type="text/css" href="_static/custom.css?v=e1715074" />
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -180,7 +180,7 @@
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="header-center">
|
<div class="header-center">
|
||||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.3.2 documentation</div></a>
|
<a href="index.html"><div class="brand">Reticulum Network Stack 1.3.9 documentation</div></a>
|
||||||
</div>
|
</div>
|
||||||
<div class="header-right">
|
<div class="header-right">
|
||||||
<div class="theme-toggle-container theme-toggle-header">
|
<div class="theme-toggle-container theme-toggle-header">
|
||||||
@@ -204,7 +204,7 @@
|
|||||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.3.2 documentation</span>
|
<span class="sidebar-brand-text">Reticulum Network Stack 1.3.9 documentation</span>
|
||||||
|
|
||||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||||
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
||||||
@@ -3648,7 +3648,7 @@
|
|||||||
|
|
||||||
</aside>
|
</aside>
|
||||||
</div>
|
</div>
|
||||||
</div><script src="_static/documentation_options.js?v=8ca9e7a0"></script>
|
</div><script src="_static/documentation_options.js?v=98c25ec5"></script>
|
||||||
<script src="_static/doctools.js?v=9bcbadda"></script>
|
<script src="_static/doctools.js?v=9bcbadda"></script>
|
||||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||||
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
||||||
|
|||||||
@@ -7,12 +7,12 @@
|
|||||||
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
||||||
|
|
||||||
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
||||||
<title>An Explanation of Reticulum for Human Beings - Reticulum Network Stack 1.3.2 documentation</title>
|
<title>An Explanation of Reticulum for Human Beings - Reticulum Network Stack 1.3.9 documentation</title>
|
||||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
|
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
|
||||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
|
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
|
||||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
||||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo-extensions.css?v=8dab3a3b" />
|
<link rel="stylesheet" type="text/css" href="_static/styles/furo-extensions.css?v=8dab3a3b" />
|
||||||
<link rel="stylesheet" type="text/css" href="_static/custom.css?v=bb3cebc5" />
|
<link rel="stylesheet" type="text/css" href="_static/custom.css?v=e1715074" />
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -180,7 +180,7 @@
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="header-center">
|
<div class="header-center">
|
||||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.3.2 documentation</div></a>
|
<a href="index.html"><div class="brand">Reticulum Network Stack 1.3.9 documentation</div></a>
|
||||||
</div>
|
</div>
|
||||||
<div class="header-right">
|
<div class="header-right">
|
||||||
<div class="theme-toggle-container theme-toggle-header">
|
<div class="theme-toggle-container theme-toggle-header">
|
||||||
@@ -204,7 +204,7 @@
|
|||||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.3.2 documentation</span>
|
<span class="sidebar-brand-text">Reticulum Network Stack 1.3.9 documentation</span>
|
||||||
|
|
||||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||||
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
||||||
@@ -296,7 +296,7 @@
|
|||||||
|
|
||||||
</aside>
|
</aside>
|
||||||
</div>
|
</div>
|
||||||
</div><script src="_static/documentation_options.js?v=8ca9e7a0"></script>
|
</div><script src="_static/documentation_options.js?v=98c25ec5"></script>
|
||||||
<script src="_static/doctools.js?v=9bcbadda"></script>
|
<script src="_static/doctools.js?v=9bcbadda"></script>
|
||||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||||
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
||||||
|
|||||||
@@ -5,12 +5,12 @@
|
|||||||
<meta name="color-scheme" content="light dark"><link rel="index" title="Index" href="#"><link rel="search" title="Search" href="search.html">
|
<meta name="color-scheme" content="light dark"><link rel="index" title="Index" href="#"><link rel="search" title="Search" href="search.html">
|
||||||
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
||||||
|
|
||||||
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 --><title>Index - Reticulum Network Stack 1.3.2 documentation</title>
|
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 --><title>Index - Reticulum Network Stack 1.3.9 documentation</title>
|
||||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
|
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
|
||||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
|
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
|
||||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
||||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo-extensions.css?v=8dab3a3b" />
|
<link rel="stylesheet" type="text/css" href="_static/styles/furo-extensions.css?v=8dab3a3b" />
|
||||||
<link rel="stylesheet" type="text/css" href="_static/custom.css?v=bb3cebc5" />
|
<link rel="stylesheet" type="text/css" href="_static/custom.css?v=e1715074" />
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -178,7 +178,7 @@
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="header-center">
|
<div class="header-center">
|
||||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.3.2 documentation</div></a>
|
<a href="index.html"><div class="brand">Reticulum Network Stack 1.3.9 documentation</div></a>
|
||||||
</div>
|
</div>
|
||||||
<div class="header-right">
|
<div class="header-right">
|
||||||
<div class="theme-toggle-container theme-toggle-header">
|
<div class="theme-toggle-container theme-toggle-header">
|
||||||
@@ -202,7 +202,7 @@
|
|||||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.3.2 documentation</span>
|
<span class="sidebar-brand-text">Reticulum Network Stack 1.3.9 documentation</span>
|
||||||
|
|
||||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||||
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
||||||
@@ -846,7 +846,7 @@
|
|||||||
|
|
||||||
</aside>
|
</aside>
|
||||||
</div>
|
</div>
|
||||||
</div><script src="_static/documentation_options.js?v=8ca9e7a0"></script>
|
</div><script src="_static/documentation_options.js?v=98c25ec5"></script>
|
||||||
<script src="_static/doctools.js?v=9bcbadda"></script>
|
<script src="_static/doctools.js?v=9bcbadda"></script>
|
||||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||||
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
||||||
|
|||||||
@@ -7,12 +7,12 @@
|
|||||||
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
||||||
|
|
||||||
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
||||||
<title>Getting Started Fast - Reticulum Network Stack 1.3.2 documentation</title>
|
<title>Getting Started Fast - Reticulum Network Stack 1.3.9 documentation</title>
|
||||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
|
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
|
||||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
|
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
|
||||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
||||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo-extensions.css?v=8dab3a3b" />
|
<link rel="stylesheet" type="text/css" href="_static/styles/furo-extensions.css?v=8dab3a3b" />
|
||||||
<link rel="stylesheet" type="text/css" href="_static/custom.css?v=bb3cebc5" />
|
<link rel="stylesheet" type="text/css" href="_static/custom.css?v=e1715074" />
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -180,7 +180,7 @@
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="header-center">
|
<div class="header-center">
|
||||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.3.2 documentation</div></a>
|
<a href="index.html"><div class="brand">Reticulum Network Stack 1.3.9 documentation</div></a>
|
||||||
</div>
|
</div>
|
||||||
<div class="header-right">
|
<div class="header-right">
|
||||||
<div class="theme-toggle-container theme-toggle-header">
|
<div class="theme-toggle-container theme-toggle-header">
|
||||||
@@ -204,7 +204,7 @@
|
|||||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.3.2 documentation</span>
|
<span class="sidebar-brand-text">Reticulum Network Stack 1.3.9 documentation</span>
|
||||||
|
|
||||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||||
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
||||||
@@ -776,7 +776,7 @@ section of this manual.</p>
|
|||||||
|
|
||||||
</aside>
|
</aside>
|
||||||
</div>
|
</div>
|
||||||
</div><script src="_static/documentation_options.js?v=8ca9e7a0"></script>
|
</div><script src="_static/documentation_options.js?v=98c25ec5"></script>
|
||||||
<script src="_static/doctools.js?v=9bcbadda"></script>
|
<script src="_static/doctools.js?v=9bcbadda"></script>
|
||||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||||
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
||||||
|
|||||||
+24
-6
@@ -7,12 +7,12 @@
|
|||||||
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
||||||
|
|
||||||
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
||||||
<title>Git Over Reticulum - Reticulum Network Stack 1.3.2 documentation</title>
|
<title>Git Over Reticulum - Reticulum Network Stack 1.3.9 documentation</title>
|
||||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
|
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
|
||||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
|
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
|
||||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
||||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo-extensions.css?v=8dab3a3b" />
|
<link rel="stylesheet" type="text/css" href="_static/styles/furo-extensions.css?v=8dab3a3b" />
|
||||||
<link rel="stylesheet" type="text/css" href="_static/custom.css?v=bb3cebc5" />
|
<link rel="stylesheet" type="text/css" href="_static/custom.css?v=e1715074" />
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -180,7 +180,7 @@
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="header-center">
|
<div class="header-center">
|
||||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.3.2 documentation</div></a>
|
<a href="index.html"><div class="brand">Reticulum Network Stack 1.3.9 documentation</div></a>
|
||||||
</div>
|
</div>
|
||||||
<div class="header-right">
|
<div class="header-right">
|
||||||
<div class="theme-toggle-container theme-toggle-header">
|
<div class="theme-toggle-container theme-toggle-header">
|
||||||
@@ -204,7 +204,7 @@
|
|||||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.3.2 documentation</span>
|
<span class="sidebar-brand-text">Reticulum Network Stack 1.3.9 documentation</span>
|
||||||
|
|
||||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||||
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
||||||
@@ -1306,7 +1306,7 @@ adm:9710b86ba12c42d1d8f30f74fe509286
|
|||||||
<li><p><code class="docutils literal notranslate"><span class="pre">N</span></code> - Numbered comment files (msgpack format)</p></li>
|
<li><p><code class="docutils literal notranslate"><span class="pre">N</span></code> - Numbered comment files (msgpack format)</p></li>
|
||||||
</ul>
|
</ul>
|
||||||
<p><strong>Nomad Network Interface</strong></p>
|
<p><strong>Nomad Network Interface</strong></p>
|
||||||
<p>When the Nomad Network page node is enabled, work documents are viewable through the web interface. The work page lists all documents with their status, and clicking a document shows its full content and updates.</p>
|
<p>When the Nomad Network page node is enabled, work documents are viewable through the nomadnet interface. The work page lists all documents with their status, and clicking a document shows its full content and updates.</p>
|
||||||
</section>
|
</section>
|
||||||
<section id="cryptographic-attribution">
|
<section id="cryptographic-attribution">
|
||||||
<h3>Cryptographic Attribution<a class="headerlink" href="#cryptographic-attribution" title="Link to this heading">¶</a></h3>
|
<h3>Cryptographic Attribution<a class="headerlink" href="#cryptographic-attribution" title="Link to this heading">¶</a></h3>
|
||||||
@@ -1437,6 +1437,24 @@ Date: Mon Jan 15 09:30:00 2026 +0100
|
|||||||
</pre></div>
|
</pre></div>
|
||||||
</div>
|
</div>
|
||||||
<p>The output shows whether the commit signature is valid, and whether the author field matches the signing identity.</p>
|
<p>The output shows whether the commit signature is valid, and whether the author field matches the signing identity.</p>
|
||||||
|
<div class="admonition tip">
|
||||||
|
<p class="admonition-title">Tip</p>
|
||||||
|
<p>If you want to display both the identity hash and LXMF address for authors, you can generate a <code class="docutils literal notranslate"><span class="pre">.mailmap</span></code> file that resolves identities to LXMF addresses with the following script:</p>
|
||||||
|
<div class="highlight-#!/bin/bash notranslate"><div class="highlight"><pre><span></span>DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
|
||||||
|
cd $DIR
|
||||||
|
|
||||||
|
id_regex="<([0-9a-f]{32})( .*)*>"
|
||||||
|
extract_id="s/.*$id_regex/\1/g"
|
||||||
|
|
||||||
|
rm -f .mailmap
|
||||||
|
git shortlog -se | grep -Ee "$id_regex" | sed -r "$extract_id" | while read -r id ; do
|
||||||
|
if lxmf=$(rnid -i $id -H lxmf.delivery | grep -Ee "destination for this Identity is" | sed -r "$extract_id"); then
|
||||||
|
echo "<$id lxmf:$lxmf> <$id>" >> .mailmap
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
</pre></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</section>
|
</section>
|
||||||
</section>
|
</section>
|
||||||
@@ -1576,7 +1594,7 @@ Date: Mon Jan 15 09:30:00 2026 +0100
|
|||||||
|
|
||||||
</aside>
|
</aside>
|
||||||
</div>
|
</div>
|
||||||
</div><script src="_static/documentation_options.js?v=8ca9e7a0"></script>
|
</div><script src="_static/documentation_options.js?v=98c25ec5"></script>
|
||||||
<script src="_static/doctools.js?v=9bcbadda"></script>
|
<script src="_static/doctools.js?v=9bcbadda"></script>
|
||||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||||
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
||||||
|
|||||||
@@ -7,12 +7,12 @@
|
|||||||
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
||||||
|
|
||||||
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
||||||
<title>Communications Hardware - Reticulum Network Stack 1.3.2 documentation</title>
|
<title>Communications Hardware - Reticulum Network Stack 1.3.9 documentation</title>
|
||||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
|
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
|
||||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
|
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
|
||||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
||||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo-extensions.css?v=8dab3a3b" />
|
<link rel="stylesheet" type="text/css" href="_static/styles/furo-extensions.css?v=8dab3a3b" />
|
||||||
<link rel="stylesheet" type="text/css" href="_static/custom.css?v=bb3cebc5" />
|
<link rel="stylesheet" type="text/css" href="_static/custom.css?v=e1715074" />
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -180,7 +180,7 @@
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="header-center">
|
<div class="header-center">
|
||||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.3.2 documentation</div></a>
|
<a href="index.html"><div class="brand">Reticulum Network Stack 1.3.9 documentation</div></a>
|
||||||
</div>
|
</div>
|
||||||
<div class="header-right">
|
<div class="header-right">
|
||||||
<div class="theme-toggle-container theme-toggle-header">
|
<div class="theme-toggle-container theme-toggle-header">
|
||||||
@@ -204,7 +204,7 @@
|
|||||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.3.2 documentation</span>
|
<span class="sidebar-brand-text">Reticulum Network Stack 1.3.9 documentation</span>
|
||||||
|
|
||||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||||
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
||||||
@@ -676,7 +676,7 @@ can be used with Reticulum. This includes virtual software modems such as
|
|||||||
|
|
||||||
</aside>
|
</aside>
|
||||||
</div>
|
</div>
|
||||||
</div><script src="_static/documentation_options.js?v=8ca9e7a0"></script>
|
</div><script src="_static/documentation_options.js?v=98c25ec5"></script>
|
||||||
<script src="_static/doctools.js?v=9bcbadda"></script>
|
<script src="_static/doctools.js?v=9bcbadda"></script>
|
||||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||||
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
||||||
|
|||||||
@@ -7,12 +7,12 @@
|
|||||||
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
||||||
|
|
||||||
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
||||||
<title>Reticulum Network Stack 1.3.2 documentation</title>
|
<title>Reticulum Network Stack 1.3.9 documentation</title>
|
||||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
|
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
|
||||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
|
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
|
||||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
||||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo-extensions.css?v=8dab3a3b" />
|
<link rel="stylesheet" type="text/css" href="_static/styles/furo-extensions.css?v=8dab3a3b" />
|
||||||
<link rel="stylesheet" type="text/css" href="_static/custom.css?v=bb3cebc5" />
|
<link rel="stylesheet" type="text/css" href="_static/custom.css?v=e1715074" />
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -180,7 +180,7 @@
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="header-center">
|
<div class="header-center">
|
||||||
<a href="#"><div class="brand">Reticulum Network Stack 1.3.2 documentation</div></a>
|
<a href="#"><div class="brand">Reticulum Network Stack 1.3.9 documentation</div></a>
|
||||||
</div>
|
</div>
|
||||||
<div class="header-right">
|
<div class="header-right">
|
||||||
<div class="theme-toggle-container theme-toggle-header">
|
<div class="theme-toggle-container theme-toggle-header">
|
||||||
@@ -204,7 +204,7 @@
|
|||||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.3.2 documentation</span>
|
<span class="sidebar-brand-text">Reticulum Network Stack 1.3.9 documentation</span>
|
||||||
|
|
||||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||||
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
||||||
@@ -486,6 +486,7 @@ to participate in the development of Reticulum itself.</p>
|
|||||||
<li class="toctree-l2"><a class="reference internal" href="interfaces.html#backbone-interface">Backbone Interface</a><ul>
|
<li class="toctree-l2"><a class="reference internal" href="interfaces.html#backbone-interface">Backbone Interface</a><ul>
|
||||||
<li class="toctree-l3"><a class="reference internal" href="interfaces.html#listeners">Listeners</a></li>
|
<li class="toctree-l3"><a class="reference internal" href="interfaces.html#listeners">Listeners</a></li>
|
||||||
<li class="toctree-l3"><a class="reference internal" href="interfaces.html#connecting-remotes">Connecting Remotes</a></li>
|
<li class="toctree-l3"><a class="reference internal" href="interfaces.html#connecting-remotes">Connecting Remotes</a></li>
|
||||||
|
<li class="toctree-l3"><a class="reference internal" href="interfaces.html#automated-blocking">Automated Blocking</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li class="toctree-l2"><a class="reference internal" href="interfaces.html#tcp-server-interface">TCP Server Interface</a></li>
|
<li class="toctree-l2"><a class="reference internal" href="interfaces.html#tcp-server-interface">TCP Server Interface</a></li>
|
||||||
@@ -719,7 +720,7 @@ to participate in the development of Reticulum itself.</p>
|
|||||||
|
|
||||||
</aside>
|
</aside>
|
||||||
</div>
|
</div>
|
||||||
</div><script src="_static/documentation_options.js?v=8ca9e7a0"></script>
|
</div><script src="_static/documentation_options.js?v=98c25ec5"></script>
|
||||||
<script src="_static/doctools.js?v=9bcbadda"></script>
|
<script src="_static/doctools.js?v=9bcbadda"></script>
|
||||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||||
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
||||||
|
|||||||
@@ -7,12 +7,12 @@
|
|||||||
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
||||||
|
|
||||||
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
||||||
<title>Configuring Interfaces - Reticulum Network Stack 1.3.2 documentation</title>
|
<title>Configuring Interfaces - Reticulum Network Stack 1.3.9 documentation</title>
|
||||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
|
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
|
||||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
|
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
|
||||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
||||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo-extensions.css?v=8dab3a3b" />
|
<link rel="stylesheet" type="text/css" href="_static/styles/furo-extensions.css?v=8dab3a3b" />
|
||||||
<link rel="stylesheet" type="text/css" href="_static/custom.css?v=bb3cebc5" />
|
<link rel="stylesheet" type="text/css" href="_static/custom.css?v=e1715074" />
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -180,7 +180,7 @@
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="header-center">
|
<div class="header-center">
|
||||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.3.2 documentation</div></a>
|
<a href="index.html"><div class="brand">Reticulum Network Stack 1.3.9 documentation</div></a>
|
||||||
</div>
|
</div>
|
||||||
<div class="header-right">
|
<div class="header-right">
|
||||||
<div class="theme-toggle-container theme-toggle-header">
|
<div class="theme-toggle-container theme-toggle-header">
|
||||||
@@ -204,7 +204,7 @@
|
|||||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.3.2 documentation</span>
|
<span class="sidebar-brand-text">Reticulum Network Stack 1.3.9 documentation</span>
|
||||||
|
|
||||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||||
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
||||||
@@ -460,6 +460,47 @@ specify the target Yggdrasil IPv6 address and port, like so:</p>
|
|||||||
</pre></div>
|
</pre></div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
<section id="automated-blocking">
|
||||||
|
<h3>Automated Blocking<a class="headerlink" href="#automated-blocking" title="Link to this heading">¶</a></h3>
|
||||||
|
<p>Listener instances of <code class="docutils literal notranslate"><span class="pre">BackboneInterface</span></code> will automatically block fast-flapping clients,
|
||||||
|
that repeatedly connect for a short amount of time, and then disconnect. Such behavior usually
|
||||||
|
occurs from clients trying to spam the network with announces or path requests, by connecting, dumping
|
||||||
|
a massive amount of requests, and then disconnecting in an attempt to bypass rate limits.</p>
|
||||||
|
<p>While such bypass attempts only have very limited effect on the amount of spam actually dumped (ingress limits
|
||||||
|
trigger immediately once a client exceeds rate limits), the behavior is often seen anyways, and
|
||||||
|
causes log noise and needless interface rotation. The fast-flapping block ensures such clients
|
||||||
|
are never allocated an interface on the transport instance.</p>
|
||||||
|
<p>Another common cause can simply be clients using implementations that are broken, or automated
|
||||||
|
scanning tools attempting to connect to the instance.</p>
|
||||||
|
<div class="highlight-ini notranslate"><div class="highlight"><pre><span></span><span class="k">[[Backbone Listener]]</span>
|
||||||
|
<span class="w"> </span><span class="na">type</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">BackboneInterface</span>
|
||||||
|
<span class="w"> </span><span class="na">enabled</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">yes</span>
|
||||||
|
<span class="w"> </span><span class="na">listen_on</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">0.0.0.0</span>
|
||||||
|
<span class="w"> </span><span class="na">port</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">4242</span>
|
||||||
|
|
||||||
|
<span class="w"> </span><span class="c1"># Whether to enable blocking</span>
|
||||||
|
<span class="w"> </span><span class="na">block_fast_flapping</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">yes</span>
|
||||||
|
|
||||||
|
<span class="w"> </span><span class="c1"># How long an IP address stays</span>
|
||||||
|
<span class="w"> </span><span class="c1"># blocked, in minutes. Set to</span>
|
||||||
|
<span class="w"> </span><span class="c1"># 12 hours by default.</span>
|
||||||
|
<span class="w"> </span><span class="na">fast_flapping_block_time</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">720</span>
|
||||||
|
|
||||||
|
<span class="w"> </span><span class="c1"># The minimum time, in seconds,</span>
|
||||||
|
<span class="w"> </span><span class="c1"># an interface must stay connected</span>
|
||||||
|
<span class="w"> </span><span class="c1"># to be considered not fast-flapping.</span>
|
||||||
|
<span class="w"> </span><span class="na">fast_flapping_threshold</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">20</span>
|
||||||
|
|
||||||
|
<span class="w"> </span><span class="c1"># Amount of fast flaps a remote</span>
|
||||||
|
<span class="w"> </span><span class="c1"># IP can perform before having</span>
|
||||||
|
<span class="w"> </span><span class="c1"># blocking triggered.</span>
|
||||||
|
<span class="w"> </span><span class="na">fast_flapping_grace</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">5</span>
|
||||||
|
</pre></div>
|
||||||
|
</div>
|
||||||
|
<p>The configuration options listed in the example above are the <em>default values</em>, and you do
|
||||||
|
not need to add them for automated blocking to work, but you can use them to change the
|
||||||
|
behavior from the defaults, if necessary.</p>
|
||||||
|
</section>
|
||||||
</section>
|
</section>
|
||||||
<section id="tcp-server-interface">
|
<section id="tcp-server-interface">
|
||||||
<span id="interfaces-tcps"></span><h2>TCP Server Interface<a class="headerlink" href="#tcp-server-interface" title="Link to this heading">¶</a></h2>
|
<span id="interfaces-tcps"></span><h2>TCP Server Interface<a class="headerlink" href="#tcp-server-interface" title="Link to this heading">¶</a></h2>
|
||||||
@@ -1154,9 +1195,15 @@ curl<span class="w"> </span>-s<span class="w"> </span>ip.me
|
|||||||
</dl>
|
</dl>
|
||||||
<p><strong>Physical Location</strong></p>
|
<p><strong>Physical Location</strong></p>
|
||||||
<dl class="simple">
|
<dl class="simple">
|
||||||
<dt><code class="docutils literal notranslate"><span class="pre">latitude</span></code>, <code class="docutils literal notranslate"><span class="pre">longitude</span></code>, <code class="docutils literal notranslate"><span class="pre">height</span></code></dt><dd><p>Optional physical coordinates for the interface. These are useful for mapping discovered interfaces geographically or for clients to automatically select the nearest access point. Coordinates should be in decimal degrees, height in meters.</p>
|
<dt><code class="docutils literal notranslate"><span class="pre">latitude</span></code>, <code class="docutils literal notranslate"><span class="pre">longitude</span></code>, <code class="docutils literal notranslate"><span class="pre">height</span></code></dt><dd><p>Optional physical coordinates for the interface. These are useful for mapping discovered interfaces geographically or for clients to automatically select the nearest access point. Coordinates should be in decimal degrees, height in meters above mean sea level.</p>
|
||||||
|
</dd>
|
||||||
|
<dt><code class="docutils literal notranslate"><span class="pre">location_cmd</span></code></dt><dd><p>Optional path to executable or script that returns the physical coordinates for the interface. This can be used instead of manually setting <code class="docutils literal notranslate"><span class="pre">latitude</span></code>, <code class="docutils literal notranslate"><span class="pre">longitude</span></code> and <code class="docutils literal notranslate"><span class="pre">height</span></code>. Reticulum expects the script to output the location data to <code class="docutils literal notranslate"><span class="pre">stdout</span></code> on a single line, separated by commas, with values as floating point numbers in the format <code class="docutils literal notranslate"><span class="pre">LAT,</span> <span class="pre">LON,</span> <span class="pre">HEIGHT</span></code>. Coordinates should be in decimal degrees, height in meters above mean sea level.</p>
|
||||||
</dd>
|
</dd>
|
||||||
</dl>
|
</dl>
|
||||||
|
<div class="admonition note">
|
||||||
|
<p class="admonition-title">Note</p>
|
||||||
|
<p>The height value for interface discovery is specified in <em>height above mean sea level</em>! This is the geoid-corrected height value, and is distinct from GPS altitude. If you manually specify height, it will typically be set to what you find on a topographical map. If you are using a script to output the location data, make sure that you are using the geoid-corrected altitude, not height above the GPS ellipsoid. Most GPS systems will make both figures available.</p>
|
||||||
|
</div>
|
||||||
<p><strong>Radio Parameters</strong></p>
|
<p><strong>Radio Parameters</strong></p>
|
||||||
<p>For physical radio interfaces like <code class="docutils literal notranslate"><span class="pre">RNodeInterface</span></code> or <code class="docutils literal notranslate"><span class="pre">KISSInterface</span></code>, the following optional parameters allow you to broadcast the operating frequency and characteristics, allowing clients to verify compatibility before connecting:</p>
|
<p>For physical radio interfaces like <code class="docutils literal notranslate"><span class="pre">RNodeInterface</span></code> or <code class="docutils literal notranslate"><span class="pre">KISSInterface</span></code>, the following optional parameters allow you to broadcast the operating frequency and characteristics, allowing clients to verify compatibility before connecting:</p>
|
||||||
<dl class="simple">
|
<dl class="simple">
|
||||||
@@ -1361,6 +1408,24 @@ link or a remote TCP tunnel) solely to discover better local
|
|||||||
infrastructure, which then supersedes the bootstrap interface.</div>
|
infrastructure, which then supersedes the bootstrap interface.</div>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
|
<li><div class="line-block">
|
||||||
|
<div class="line">The <code class="docutils literal notranslate"><span class="pre">recursive_prs</span></code> option allows you to enable recursive path
|
||||||
|
discovery on an interface regardless of its configured interface
|
||||||
|
mode. When this option is enabled, Reticulum will attempt to
|
||||||
|
recursively discover paths for path requests received on this
|
||||||
|
interface.</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li><div class="line-block">
|
||||||
|
<div class="line">The <code class="docutils literal notranslate"><span class="pre">announces_from_internal</span></code> option controls whether any
|
||||||
|
announces <em>received on</em> an <code class="docutils literal notranslate"><span class="pre">internal</span></code> mode interface will
|
||||||
|
propagate <em>out</em> on the interface this option is set on. Note
|
||||||
|
that this controls <em>announce propagation</em>; even if announces
|
||||||
|
do not propagate out when received, paths to destinations on
|
||||||
|
<code class="docutils literal notranslate"><span class="pre">internal</span></code> mode interfaces may still be resolvable by means
|
||||||
|
of path requests.</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div></blockquote>
|
</div></blockquote>
|
||||||
</section>
|
</section>
|
||||||
@@ -1451,6 +1516,17 @@ public Transport Node available on the Internet, the interface
|
|||||||
connecting over the Internet should be set to <code class="docutils literal notranslate"><span class="pre">boundary</span></code> mode.</div>
|
connecting over the Internet should be set to <code class="docutils literal notranslate"><span class="pre">boundary</span></code> mode.</div>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
|
<li><div class="line-block">
|
||||||
|
<div class="line">The <code class="docutils literal notranslate"><span class="pre">internal</span></code> mode designates interfaces that belong to a
|
||||||
|
network different from any marked as <code class="docutils literal notranslate"><span class="pre">boundary</span></code>. Announces from
|
||||||
|
a <code class="docutils literal notranslate"><span class="pre">boundary</span></code> interface will not propagate to interfaces set as
|
||||||
|
<code class="docutils literal notranslate"><span class="pre">internal</span></code>, but announces <em>will</em> propagate from <code class="docutils literal notranslate"><span class="pre">internal</span></code> <em>to</em>
|
||||||
|
<code class="docutils literal notranslate"><span class="pre">boundary</span></code>. Devices on the <code class="docutils literal notranslate"><span class="pre">internal</span></code> side of the network will
|
||||||
|
still be able to resolve paths to destinations across the boundary
|
||||||
|
when needed, since recursive path requests are enabled for <code class="docutils literal notranslate"><span class="pre">internal</span></code>
|
||||||
|
mode interfaces by default.</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div></blockquote>
|
</div></blockquote>
|
||||||
<p>For a table describing the impact of all modes on announce propagation,
|
<p>For a table describing the impact of all modes on announce propagation,
|
||||||
@@ -1738,6 +1814,7 @@ interface basis under the relevant interface configuration section.</p>
|
|||||||
<li><a class="reference internal" href="#backbone-interface">Backbone Interface</a><ul>
|
<li><a class="reference internal" href="#backbone-interface">Backbone Interface</a><ul>
|
||||||
<li><a class="reference internal" href="#listeners">Listeners</a></li>
|
<li><a class="reference internal" href="#listeners">Listeners</a></li>
|
||||||
<li><a class="reference internal" href="#connecting-remotes">Connecting Remotes</a></li>
|
<li><a class="reference internal" href="#connecting-remotes">Connecting Remotes</a></li>
|
||||||
|
<li><a class="reference internal" href="#automated-blocking">Automated Blocking</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li><a class="reference internal" href="#tcp-server-interface">TCP Server Interface</a></li>
|
<li><a class="reference internal" href="#tcp-server-interface">TCP Server Interface</a></li>
|
||||||
@@ -1774,7 +1851,7 @@ interface basis under the relevant interface configuration section.</p>
|
|||||||
|
|
||||||
</aside>
|
</aside>
|
||||||
</div>
|
</div>
|
||||||
</div><script src="_static/documentation_options.js?v=8ca9e7a0"></script>
|
</div><script src="_static/documentation_options.js?v=98c25ec5"></script>
|
||||||
<script src="_static/doctools.js?v=9bcbadda"></script>
|
<script src="_static/doctools.js?v=9bcbadda"></script>
|
||||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||||
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
||||||
|
|||||||
@@ -7,12 +7,12 @@
|
|||||||
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
||||||
|
|
||||||
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
||||||
<title>Reticulum License - Reticulum Network Stack 1.3.2 documentation</title>
|
<title>Reticulum License - Reticulum Network Stack 1.3.9 documentation</title>
|
||||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
|
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
|
||||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
|
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
|
||||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
||||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo-extensions.css?v=8dab3a3b" />
|
<link rel="stylesheet" type="text/css" href="_static/styles/furo-extensions.css?v=8dab3a3b" />
|
||||||
<link rel="stylesheet" type="text/css" href="_static/custom.css?v=bb3cebc5" />
|
<link rel="stylesheet" type="text/css" href="_static/custom.css?v=e1715074" />
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -180,7 +180,7 @@
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="header-center">
|
<div class="header-center">
|
||||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.3.2 documentation</div></a>
|
<a href="index.html"><div class="brand">Reticulum Network Stack 1.3.9 documentation</div></a>
|
||||||
</div>
|
</div>
|
||||||
<div class="header-right">
|
<div class="header-right">
|
||||||
<div class="theme-toggle-container theme-toggle-header">
|
<div class="theme-toggle-container theme-toggle-header">
|
||||||
@@ -204,7 +204,7 @@
|
|||||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.3.2 documentation</span>
|
<span class="sidebar-brand-text">Reticulum Network Stack 1.3.9 documentation</span>
|
||||||
|
|
||||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||||
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
||||||
@@ -345,7 +345,7 @@ SOFTWARE.
|
|||||||
|
|
||||||
</aside>
|
</aside>
|
||||||
</div>
|
</div>
|
||||||
</div><script src="_static/documentation_options.js?v=8ca9e7a0"></script>
|
</div><script src="_static/documentation_options.js?v=98c25ec5"></script>
|
||||||
<script src="_static/doctools.js?v=9bcbadda"></script>
|
<script src="_static/doctools.js?v=9bcbadda"></script>
|
||||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||||
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
||||||
|
|||||||
@@ -7,12 +7,12 @@
|
|||||||
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
||||||
|
|
||||||
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
||||||
<title>Building Networks - Reticulum Network Stack 1.3.2 documentation</title>
|
<title>Building Networks - Reticulum Network Stack 1.3.9 documentation</title>
|
||||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
|
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
|
||||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
|
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
|
||||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
||||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo-extensions.css?v=8dab3a3b" />
|
<link rel="stylesheet" type="text/css" href="_static/styles/furo-extensions.css?v=8dab3a3b" />
|
||||||
<link rel="stylesheet" type="text/css" href="_static/custom.css?v=bb3cebc5" />
|
<link rel="stylesheet" type="text/css" href="_static/custom.css?v=e1715074" />
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -180,7 +180,7 @@
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="header-center">
|
<div class="header-center">
|
||||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.3.2 documentation</div></a>
|
<a href="index.html"><div class="brand">Reticulum Network Stack 1.3.9 documentation</div></a>
|
||||||
</div>
|
</div>
|
||||||
<div class="header-right">
|
<div class="header-right">
|
||||||
<div class="theme-toggle-container theme-toggle-header">
|
<div class="theme-toggle-container theme-toggle-header">
|
||||||
@@ -204,7 +204,7 @@
|
|||||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.3.2 documentation</span>
|
<span class="sidebar-brand-text">Reticulum Network Stack 1.3.9 documentation</span>
|
||||||
|
|
||||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||||
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
||||||
@@ -664,7 +664,7 @@ differently than a mobile device roaming between radio cells.</p>
|
|||||||
|
|
||||||
</aside>
|
</aside>
|
||||||
</div>
|
</div>
|
||||||
</div><script src="_static/documentation_options.js?v=8ca9e7a0"></script>
|
</div><script src="_static/documentation_options.js?v=98c25ec5"></script>
|
||||||
<script src="_static/doctools.js?v=9bcbadda"></script>
|
<script src="_static/doctools.js?v=9bcbadda"></script>
|
||||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||||
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
||||||
|
|||||||
Binary file not shown.
@@ -7,12 +7,12 @@
|
|||||||
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
||||||
|
|
||||||
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
||||||
<title>API Reference - Reticulum Network Stack 1.3.2 documentation</title>
|
<title>API Reference - Reticulum Network Stack 1.3.9 documentation</title>
|
||||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
|
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
|
||||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
|
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
|
||||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
||||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo-extensions.css?v=8dab3a3b" />
|
<link rel="stylesheet" type="text/css" href="_static/styles/furo-extensions.css?v=8dab3a3b" />
|
||||||
<link rel="stylesheet" type="text/css" href="_static/custom.css?v=bb3cebc5" />
|
<link rel="stylesheet" type="text/css" href="_static/custom.css?v=e1715074" />
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -180,7 +180,7 @@
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="header-center">
|
<div class="header-center">
|
||||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.3.2 documentation</div></a>
|
<a href="index.html"><div class="brand">Reticulum Network Stack 1.3.9 documentation</div></a>
|
||||||
</div>
|
</div>
|
||||||
<div class="header-right">
|
<div class="header-right">
|
||||||
<div class="theme-toggle-container theme-toggle-header">
|
<div class="theme-toggle-container theme-toggle-header">
|
||||||
@@ -204,7 +204,7 @@
|
|||||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.3.2 documentation</span>
|
<span class="sidebar-brand-text">Reticulum Network Stack 1.3.9 documentation</span>
|
||||||
|
|
||||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||||
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
||||||
@@ -2519,7 +2519,7 @@ will announce it.</p>
|
|||||||
|
|
||||||
</aside>
|
</aside>
|
||||||
</div>
|
</div>
|
||||||
</div><script src="_static/documentation_options.js?v=8ca9e7a0"></script>
|
</div><script src="_static/documentation_options.js?v=98c25ec5"></script>
|
||||||
<script src="_static/doctools.js?v=9bcbadda"></script>
|
<script src="_static/doctools.js?v=9bcbadda"></script>
|
||||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||||
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
||||||
|
|||||||
@@ -8,11 +8,11 @@
|
|||||||
|
|
||||||
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
||||||
<meta name="robots" content="noindex" />
|
<meta name="robots" content="noindex" />
|
||||||
<title>Search - Reticulum Network Stack 1.3.2 documentation</title><link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
|
<title>Search - Reticulum Network Stack 1.3.9 documentation</title><link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
|
||||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
|
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
|
||||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
||||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo-extensions.css?v=8dab3a3b" />
|
<link rel="stylesheet" type="text/css" href="_static/styles/furo-extensions.css?v=8dab3a3b" />
|
||||||
<link rel="stylesheet" type="text/css" href="_static/custom.css?v=bb3cebc5" />
|
<link rel="stylesheet" type="text/css" href="_static/custom.css?v=e1715074" />
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -180,7 +180,7 @@
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="header-center">
|
<div class="header-center">
|
||||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.3.2 documentation</div></a>
|
<a href="index.html"><div class="brand">Reticulum Network Stack 1.3.9 documentation</div></a>
|
||||||
</div>
|
</div>
|
||||||
<div class="header-right">
|
<div class="header-right">
|
||||||
<div class="theme-toggle-container theme-toggle-header">
|
<div class="theme-toggle-container theme-toggle-header">
|
||||||
@@ -204,7 +204,7 @@
|
|||||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.3.2 documentation</span>
|
<span class="sidebar-brand-text">Reticulum Network Stack 1.3.9 documentation</span>
|
||||||
|
|
||||||
</a><form class="sidebar-search-container" method="get" action="#" role="search">
|
</a><form class="sidebar-search-container" method="get" action="#" role="search">
|
||||||
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
||||||
@@ -304,7 +304,7 @@
|
|||||||
|
|
||||||
</aside>
|
</aside>
|
||||||
</div>
|
</div>
|
||||||
</div><script src="_static/documentation_options.js?v=8ca9e7a0"></script>
|
</div><script src="_static/documentation_options.js?v=98c25ec5"></script>
|
||||||
<script src="_static/doctools.js?v=9bcbadda"></script>
|
<script src="_static/doctools.js?v=9bcbadda"></script>
|
||||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||||
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -7,12 +7,12 @@
|
|||||||
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
||||||
|
|
||||||
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
||||||
<title>Programs Using Reticulum - Reticulum Network Stack 1.3.2 documentation</title>
|
<title>Programs Using Reticulum - Reticulum Network Stack 1.3.9 documentation</title>
|
||||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
|
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
|
||||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
|
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
|
||||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
||||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo-extensions.css?v=8dab3a3b" />
|
<link rel="stylesheet" type="text/css" href="_static/styles/furo-extensions.css?v=8dab3a3b" />
|
||||||
<link rel="stylesheet" type="text/css" href="_static/custom.css?v=bb3cebc5" />
|
<link rel="stylesheet" type="text/css" href="_static/custom.css?v=e1715074" />
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -180,7 +180,7 @@
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="header-center">
|
<div class="header-center">
|
||||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.3.2 documentation</div></a>
|
<a href="index.html"><div class="brand">Reticulum Network Stack 1.3.9 documentation</div></a>
|
||||||
</div>
|
</div>
|
||||||
<div class="header-right">
|
<div class="header-right">
|
||||||
<div class="theme-toggle-container theme-toggle-header">
|
<div class="theme-toggle-container theme-toggle-header">
|
||||||
@@ -204,7 +204,7 @@
|
|||||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.3.2 documentation</span>
|
<span class="sidebar-brand-text">Reticulum Network Stack 1.3.9 documentation</span>
|
||||||
|
|
||||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||||
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
||||||
@@ -513,7 +513,7 @@ plugin system for expandability.</p>
|
|||||||
|
|
||||||
</aside>
|
</aside>
|
||||||
</div>
|
</div>
|
||||||
</div><script src="_static/documentation_options.js?v=8ca9e7a0"></script>
|
</div><script src="_static/documentation_options.js?v=98c25ec5"></script>
|
||||||
<script src="_static/doctools.js?v=9bcbadda"></script>
|
<script src="_static/doctools.js?v=9bcbadda"></script>
|
||||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||||
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
||||||
|
|||||||
@@ -7,12 +7,12 @@
|
|||||||
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
||||||
|
|
||||||
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
||||||
<title>Support Reticulum - Reticulum Network Stack 1.3.2 documentation</title>
|
<title>Support Reticulum - Reticulum Network Stack 1.3.9 documentation</title>
|
||||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
|
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
|
||||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
|
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
|
||||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
||||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo-extensions.css?v=8dab3a3b" />
|
<link rel="stylesheet" type="text/css" href="_static/styles/furo-extensions.css?v=8dab3a3b" />
|
||||||
<link rel="stylesheet" type="text/css" href="_static/custom.css?v=bb3cebc5" />
|
<link rel="stylesheet" type="text/css" href="_static/custom.css?v=e1715074" />
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -180,7 +180,7 @@
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="header-center">
|
<div class="header-center">
|
||||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.3.2 documentation</div></a>
|
<a href="index.html"><div class="brand">Reticulum Network Stack 1.3.9 documentation</div></a>
|
||||||
</div>
|
</div>
|
||||||
<div class="header-right">
|
<div class="header-right">
|
||||||
<div class="theme-toggle-container theme-toggle-header">
|
<div class="theme-toggle-container theme-toggle-header">
|
||||||
@@ -204,7 +204,7 @@
|
|||||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.3.2 documentation</span>
|
<span class="sidebar-brand-text">Reticulum Network Stack 1.3.9 documentation</span>
|
||||||
|
|
||||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||||
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
||||||
@@ -383,7 +383,7 @@ circumstances, so we rely on old-fashioned human feedback.</p>
|
|||||||
|
|
||||||
</aside>
|
</aside>
|
||||||
</div>
|
</div>
|
||||||
</div><script src="_static/documentation_options.js?v=8ca9e7a0"></script>
|
</div><script src="_static/documentation_options.js?v=98c25ec5"></script>
|
||||||
<script src="_static/doctools.js?v=9bcbadda"></script>
|
<script src="_static/doctools.js?v=9bcbadda"></script>
|
||||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||||
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
||||||
|
|||||||
@@ -7,12 +7,12 @@
|
|||||||
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
||||||
|
|
||||||
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
||||||
<title>Understanding Reticulum - Reticulum Network Stack 1.3.2 documentation</title>
|
<title>Understanding Reticulum - Reticulum Network Stack 1.3.9 documentation</title>
|
||||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
|
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
|
||||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
|
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
|
||||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
||||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo-extensions.css?v=8dab3a3b" />
|
<link rel="stylesheet" type="text/css" href="_static/styles/furo-extensions.css?v=8dab3a3b" />
|
||||||
<link rel="stylesheet" type="text/css" href="_static/custom.css?v=bb3cebc5" />
|
<link rel="stylesheet" type="text/css" href="_static/custom.css?v=e1715074" />
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -180,7 +180,7 @@
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="header-center">
|
<div class="header-center">
|
||||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.3.2 documentation</div></a>
|
<a href="index.html"><div class="brand">Reticulum Network Stack 1.3.9 documentation</div></a>
|
||||||
</div>
|
</div>
|
||||||
<div class="header-right">
|
<div class="header-right">
|
||||||
<div class="theme-toggle-container theme-toggle-header">
|
<div class="theme-toggle-container theme-toggle-header">
|
||||||
@@ -204,7 +204,7 @@
|
|||||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.3.2 documentation</span>
|
<span class="sidebar-brand-text">Reticulum Network Stack 1.3.9 documentation</span>
|
||||||
|
|
||||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||||
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
||||||
@@ -969,8 +969,10 @@ but excluding any interface access codes.
|
|||||||
<section id="announce-propagation-rules">
|
<section id="announce-propagation-rules">
|
||||||
<span id="understanding-announcepropagation"></span><h3>Announce Propagation Rules<a class="headerlink" href="#announce-propagation-rules" title="Link to this heading">¶</a></h3>
|
<span id="understanding-announcepropagation"></span><h3>Announce Propagation Rules<a class="headerlink" href="#announce-propagation-rules" title="Link to this heading">¶</a></h3>
|
||||||
<p>The following table illustrates the rules for automatically propagating announces from one interface type to another, for all possible combinations. For the purpose of announce propagation, the <em>Full</em> and <em>Gateway</em> modes are identical.</p>
|
<p>The following table illustrates the rules for automatically propagating announces from one interface type to another, for all possible combinations. For the purpose of announce propagation, the <em>Full</em> and <em>Gateway</em> modes are identical.</p>
|
||||||
<img alt="_images/if_mode_graph_b.png" src="_images/if_mode_graph_b.png" />
|
|
||||||
<p>See the <a class="reference internal" href="interfaces.html#interfaces-modes"><span class="std std-ref">Interface Modes</span></a> section for a conceptual overview of the different interface modes, and how they are configured.</p>
|
<p>See the <a class="reference internal" href="interfaces.html#interfaces-modes"><span class="std std-ref">Interface Modes</span></a> section for a conceptual overview of the different interface modes, and how they are configured.</p>
|
||||||
|
<img alt="_images/if_mode_graph_b.png" src="_images/if_mode_graph_b.png" />
|
||||||
|
<p>Or, represented in table form:</p>
|
||||||
|
<img alt="_images/if_mode_graph_c.png" src="_images/if_mode_graph_c.png" />
|
||||||
</section>
|
</section>
|
||||||
<section id="cryptographic-primitives">
|
<section id="cryptographic-primitives">
|
||||||
<span id="understanding-primitives"></span><h3>Cryptographic Primitives<a class="headerlink" href="#cryptographic-primitives" title="Link to this heading">¶</a></h3>
|
<span id="understanding-primitives"></span><h3>Cryptographic Primitives<a class="headerlink" href="#cryptographic-primitives" title="Link to this heading">¶</a></h3>
|
||||||
@@ -1116,7 +1118,7 @@ those risks are acceptable to you.</p>
|
|||||||
|
|
||||||
</aside>
|
</aside>
|
||||||
</div>
|
</div>
|
||||||
</div><script src="_static/documentation_options.js?v=8ca9e7a0"></script>
|
</div><script src="_static/documentation_options.js?v=98c25ec5"></script>
|
||||||
<script src="_static/doctools.js?v=9bcbadda"></script>
|
<script src="_static/doctools.js?v=9bcbadda"></script>
|
||||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||||
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
||||||
|
|||||||
+33
-37
@@ -7,12 +7,12 @@
|
|||||||
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
||||||
|
|
||||||
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
||||||
<title>Using Reticulum on Your System - Reticulum Network Stack 1.3.2 documentation</title>
|
<title>Using Reticulum on Your System - Reticulum Network Stack 1.3.9 documentation</title>
|
||||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
|
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
|
||||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
|
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
|
||||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
||||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo-extensions.css?v=8dab3a3b" />
|
<link rel="stylesheet" type="text/css" href="_static/styles/furo-extensions.css?v=8dab3a3b" />
|
||||||
<link rel="stylesheet" type="text/css" href="_static/custom.css?v=bb3cebc5" />
|
<link rel="stylesheet" type="text/css" href="_static/custom.css?v=e1715074" />
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -180,7 +180,7 @@
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="header-center">
|
<div class="header-center">
|
||||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.3.2 documentation</div></a>
|
<a href="index.html"><div class="brand">Reticulum Network Stack 1.3.9 documentation</div></a>
|
||||||
</div>
|
</div>
|
||||||
<div class="header-right">
|
<div class="header-right">
|
||||||
<div class="theme-toggle-container theme-toggle-header">
|
<div class="theme-toggle-container theme-toggle-header">
|
||||||
@@ -204,7 +204,7 @@
|
|||||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.3.2 documentation</span>
|
<span class="sidebar-brand-text">Reticulum Network Stack 1.3.9 documentation</span>
|
||||||
|
|
||||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||||
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
||||||
@@ -1106,39 +1106,35 @@ positional arguments:
|
|||||||
destination hexadecimal hash of the destination to connect to
|
destination hexadecimal hash of the destination to connect to
|
||||||
|
|
||||||
options:
|
options:
|
||||||
-h, --help show this help message and exit
|
-h, --help show this help message and exit
|
||||||
--config, -c CONFIG path to alternative Reticulum config directory
|
--config, -c PATH path to config directory
|
||||||
--identity, -i IDENTITY
|
--rnsconfig, -c PATH path to alternative Reticulum config directory
|
||||||
path to identity file to use
|
--identity, -i IDENTITY path to identity file to use
|
||||||
-v, --verbose increase verbosity
|
-v, --verbose increase verbosity
|
||||||
-q, --quiet decrease verbosity
|
-q, --quiet decrease verbosity
|
||||||
-p, --print-identity print identity and destination info and exit
|
-p, --print-identity print identity and destination info and exit
|
||||||
--version show program's version number and exit
|
--version show program's version number and exit
|
||||||
-l, --listen listen (server) mode; any command specified after --
|
-l, --listen listen (server) mode; any command specified after --
|
||||||
will be used as the default command when the initiator
|
will be used as the default command when the initiator
|
||||||
does not provide one or when remote command execution
|
does not provide one or when remote command execution
|
||||||
is disabled; if no command is specified, the default
|
is disabled; if no command is specified, the default
|
||||||
shell of the user running rnsh will be used
|
shell of the user running rnsh will be used
|
||||||
-s, --service SERVICE
|
-s, --service SERVICE service name for identity file if not the default
|
||||||
service name for identity file if not the default
|
-b, --announce PERIOD announce on startup and every PERIOD seconds; specify
|
||||||
-b, --announce PERIOD
|
0 to announce on startup only
|
||||||
announce on startup and every PERIOD seconds; specify
|
-a, --allowed HASH allow this identity to connect (may be specified
|
||||||
0 to announce on startup only
|
multiple times); allowed identities can also be
|
||||||
-a, --allowed HASH allow this identity to connect (may be specified
|
specified in ~/.rnsh/allowed_identities or
|
||||||
multiple times); allowed identities can also be
|
~/.config/rnsh/allowed_identities, one hash per line
|
||||||
specified in ~/.rnsh/allowed_identities or
|
-n, --no-auth disable authentication (allow any identity to connect)
|
||||||
~/.config/rnsh/allowed_identities, one hash per line
|
|
||||||
-n, --no-auth disable authentication (allow any identity to connect)
|
|
||||||
-A, --remote-command-as-args
|
-A, --remote-command-as-args
|
||||||
concatenate remote command to the argument list of the
|
concatenate remote command to the argument list of the
|
||||||
default program or shell
|
default program or shell
|
||||||
-C, --no-remote-command
|
-C, --no-remote-command disable executing command lines received from the
|
||||||
disable executing command lines received from the
|
remote initiator
|
||||||
remote initiator
|
-N, --no-id disable identity announcement on connect
|
||||||
-N, --no-id disable identity announcement on connect
|
-m, --mirror return with the exit code of the remote process
|
||||||
-m, --mirror return with the exit code of the remote process
|
-w, --timeout SECONDS connect and request timeout in seconds
|
||||||
-w, --timeout SECONDS
|
|
||||||
connect and request timeout in seconds
|
|
||||||
|
|
||||||
When specifying a command to execute, separate rnsh options from the command
|
When specifying a command to execute, separate rnsh options from the command
|
||||||
and its arguments with --. For example:
|
and its arguments with --. For example:
|
||||||
@@ -1639,7 +1635,7 @@ systemctl --user enable rnsd.service
|
|||||||
|
|
||||||
</aside>
|
</aside>
|
||||||
</div>
|
</div>
|
||||||
</div><script src="_static/documentation_options.js?v=8ca9e7a0"></script>
|
</div><script src="_static/documentation_options.js?v=98c25ec5"></script>
|
||||||
<script src="_static/doctools.js?v=9bcbadda"></script>
|
<script src="_static/doctools.js?v=9bcbadda"></script>
|
||||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||||
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
||||||
|
|||||||
@@ -7,12 +7,12 @@
|
|||||||
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
||||||
|
|
||||||
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
||||||
<title>What is Reticulum? - Reticulum Network Stack 1.3.2 documentation</title>
|
<title>What is Reticulum? - Reticulum Network Stack 1.3.9 documentation</title>
|
||||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
|
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
|
||||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
|
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
|
||||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
||||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo-extensions.css?v=8dab3a3b" />
|
<link rel="stylesheet" type="text/css" href="_static/styles/furo-extensions.css?v=8dab3a3b" />
|
||||||
<link rel="stylesheet" type="text/css" href="_static/custom.css?v=bb3cebc5" />
|
<link rel="stylesheet" type="text/css" href="_static/custom.css?v=e1715074" />
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -180,7 +180,7 @@
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="header-center">
|
<div class="header-center">
|
||||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.3.2 documentation</div></a>
|
<a href="index.html"><div class="brand">Reticulum Network Stack 1.3.9 documentation</div></a>
|
||||||
</div>
|
</div>
|
||||||
<div class="header-right">
|
<div class="header-right">
|
||||||
<div class="theme-toggle-container theme-toggle-header">
|
<div class="theme-toggle-container theme-toggle-header">
|
||||||
@@ -204,7 +204,7 @@
|
|||||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.3.2 documentation</span>
|
<span class="sidebar-brand-text">Reticulum Network Stack 1.3.9 documentation</span>
|
||||||
|
|
||||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||||
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
||||||
@@ -505,7 +505,7 @@ network, and vice versa.</p>
|
|||||||
|
|
||||||
</aside>
|
</aside>
|
||||||
</div>
|
</div>
|
||||||
</div><script src="_static/documentation_options.js?v=8ca9e7a0"></script>
|
</div><script src="_static/documentation_options.js?v=98c25ec5"></script>
|
||||||
<script src="_static/doctools.js?v=9bcbadda"></script>
|
<script src="_static/doctools.js?v=9bcbadda"></script>
|
||||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||||
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
||||||
|
|||||||
@@ -7,12 +7,12 @@
|
|||||||
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
||||||
|
|
||||||
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
||||||
<title>Zen of Reticulum - Reticulum Network Stack 1.3.2 documentation</title>
|
<title>Zen of Reticulum - Reticulum Network Stack 1.3.9 documentation</title>
|
||||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
|
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
|
||||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
|
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
|
||||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
||||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo-extensions.css?v=8dab3a3b" />
|
<link rel="stylesheet" type="text/css" href="_static/styles/furo-extensions.css?v=8dab3a3b" />
|
||||||
<link rel="stylesheet" type="text/css" href="_static/custom.css?v=bb3cebc5" />
|
<link rel="stylesheet" type="text/css" href="_static/custom.css?v=e1715074" />
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -180,7 +180,7 @@
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="header-center">
|
<div class="header-center">
|
||||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.3.2 documentation</div></a>
|
<a href="index.html"><div class="brand">Reticulum Network Stack 1.3.9 documentation</div></a>
|
||||||
</div>
|
</div>
|
||||||
<div class="header-right">
|
<div class="header-right">
|
||||||
<div class="theme-toggle-container theme-toggle-header">
|
<div class="theme-toggle-container theme-toggle-header">
|
||||||
@@ -204,7 +204,7 @@
|
|||||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.3.2 documentation</span>
|
<span class="sidebar-brand-text">Reticulum Network Stack 1.3.9 documentation</span>
|
||||||
|
|
||||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||||
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
||||||
@@ -677,7 +677,7 @@ Imagine a messaging app. You write it once. It works on a laptop connected to fi
|
|||||||
|
|
||||||
</aside>
|
</aside>
|
||||||
</div>
|
</div>
|
||||||
</div><script src="_static/documentation_options.js?v=8ca9e7a0"></script>
|
</div><script src="_static/documentation_options.js?v=98c25ec5"></script>
|
||||||
<script src="_static/doctools.js?v=9bcbadda"></script>
|
<script src="_static/doctools.js?v=9bcbadda"></script>
|
||||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||||
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
||||||
|
|||||||
@@ -1250,7 +1250,7 @@ Each document is a numbered directory containing:
|
|||||||
|
|
||||||
**Nomad Network Interface**
|
**Nomad Network Interface**
|
||||||
|
|
||||||
When the Nomad Network page node is enabled, work documents are viewable through the web interface. The work page lists all documents with their status, and clicking a document shows its full content and updates.
|
When the Nomad Network page node is enabled, work documents are viewable through the nomadnet interface. The work page lists all documents with their status, and clicking a document shows its full content and updates.
|
||||||
|
|
||||||
### Cryptographic Attribution
|
### Cryptographic Attribution
|
||||||
|
|
||||||
|
|||||||
@@ -161,6 +161,7 @@ to participate in the development of Reticulum itself.
|
|||||||
* [Backbone Interface](interfaces.md#backbone-interface)
|
* [Backbone Interface](interfaces.md#backbone-interface)
|
||||||
* [Listeners](interfaces.md#listeners)
|
* [Listeners](interfaces.md#listeners)
|
||||||
* [Connecting Remotes](interfaces.md#connecting-remotes)
|
* [Connecting Remotes](interfaces.md#connecting-remotes)
|
||||||
|
* [Automated Blocking](interfaces.md#automated-blocking)
|
||||||
* [TCP Server Interface](interfaces.md#tcp-server-interface)
|
* [TCP Server Interface](interfaces.md#tcp-server-interface)
|
||||||
* [TCP Client Interface](interfaces.md#tcp-client-interface)
|
* [TCP Client Interface](interfaces.md#tcp-client-interface)
|
||||||
* [UDP Interface](interfaces.md#udp-interface)
|
* [UDP Interface](interfaces.md#udp-interface)
|
||||||
|
|||||||
@@ -212,6 +212,51 @@ specify the target Yggdrasil IPv6 address and port, like so:
|
|||||||
target_port = 4343
|
target_port = 4343
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Automated Blocking
|
||||||
|
|
||||||
|
Listener instances of `BackboneInterface` will automatically block fast-flapping clients,
|
||||||
|
that repeatedly connect for a short amount of time, and then disconnect. Such behavior usually
|
||||||
|
occurs from clients trying to spam the network with announces or path requests, by connecting, dumping
|
||||||
|
a massive amount of requests, and then disconnecting in an attempt to bypass rate limits.
|
||||||
|
|
||||||
|
While such bypass attempts only have very limited effect on the amount of spam actually dumped (ingress limits
|
||||||
|
trigger immediately once a client exceeds rate limits), the behavior is often seen anyways, and
|
||||||
|
causes log noise and needless interface rotation. The fast-flapping block ensures such clients
|
||||||
|
are never allocated an interface on the transport instance.
|
||||||
|
|
||||||
|
Another common cause can simply be clients using implementations that are broken, or automated
|
||||||
|
scanning tools attempting to connect to the instance.
|
||||||
|
|
||||||
|
```ini
|
||||||
|
[[Backbone Listener]]
|
||||||
|
type = BackboneInterface
|
||||||
|
enabled = yes
|
||||||
|
listen_on = 0.0.0.0
|
||||||
|
port = 4242
|
||||||
|
|
||||||
|
# Whether to enable blocking
|
||||||
|
block_fast_flapping = yes
|
||||||
|
|
||||||
|
# How long an IP address stays
|
||||||
|
# blocked, in minutes. Set to
|
||||||
|
# 12 hours by default.
|
||||||
|
fast_flapping_block_time = 720
|
||||||
|
|
||||||
|
# The minimum time, in seconds,
|
||||||
|
# an interface must stay connected
|
||||||
|
# to be considered not fast-flapping.
|
||||||
|
fast_flapping_threshold = 20
|
||||||
|
|
||||||
|
# Amount of fast flaps a remote
|
||||||
|
# IP can perform before having
|
||||||
|
# blocking triggered.
|
||||||
|
fast_flapping_grace = 5
|
||||||
|
```
|
||||||
|
|
||||||
|
The configuration options listed in the example above are the *default values*, and you do
|
||||||
|
not need to add them for automated blocking to work, but you can use them to change the
|
||||||
|
behavior from the defaults, if necessary.
|
||||||
|
|
||||||
## TCP Server Interface
|
## TCP Server Interface
|
||||||
|
|
||||||
The TCP Server interface is suitable for allowing other peers to connect over
|
The TCP Server interface is suitable for allowing other peers to connect over
|
||||||
@@ -939,7 +984,13 @@ If you enable `discovery_encrypt` but do not configure a valid `network_identity
|
|||||||
**Physical Location**
|
**Physical Location**
|
||||||
|
|
||||||
`latitude`, `longitude`, `height`
|
`latitude`, `longitude`, `height`
|
||||||
: Optional physical coordinates for the interface. These are useful for mapping discovered interfaces geographically or for clients to automatically select the nearest access point. Coordinates should be in decimal degrees, height in meters.
|
: Optional physical coordinates for the interface. These are useful for mapping discovered interfaces geographically or for clients to automatically select the nearest access point. Coordinates should be in decimal degrees, height in meters above mean sea level.
|
||||||
|
|
||||||
|
`location_cmd`
|
||||||
|
: Optional path to executable or script that returns the physical coordinates for the interface. This can be used instead of manually setting `latitude`, `longitude` and `height`. Reticulum expects the script to output the location data to `stdout` on a single line, separated by commas, with values as floating point numbers in the format `LAT, LON, HEIGHT`. Coordinates should be in decimal degrees, height in meters above mean sea level.
|
||||||
|
|
||||||
|
#### NOTE
|
||||||
|
The height value for interface discovery is specified in *height above mean sea level*! This is the geoid-corrected height value, and is distinct from GPS altitude. If you manually specify height, it will typically be set to what you find on a topographical map. If you are using a script to output the location data, make sure that you are using the geoid-corrected altitude, not height above the GPS ellipsoid. Most GPS systems will make both figures available.
|
||||||
|
|
||||||
**Radio Parameters**
|
**Radio Parameters**
|
||||||
|
|
||||||
@@ -1136,6 +1187,20 @@ These can be used to control various aspects of interface behaviour.
|
|||||||
> link or a remote TCP tunnel) solely to discover better local
|
> link or a remote TCP tunnel) solely to discover better local
|
||||||
> infrastructure, which then supersedes the bootstrap interface.
|
> infrastructure, which then supersedes the bootstrap interface.
|
||||||
>
|
>
|
||||||
|
> * The `recursive_prs` option allows you to enable recursive path
|
||||||
|
> discovery on an interface regardless of its configured interface
|
||||||
|
> mode. When this option is enabled, Reticulum will attempt to
|
||||||
|
> recursively discover paths for path requests received on this
|
||||||
|
> interface.
|
||||||
|
>
|
||||||
|
> * The `announces_from_internal` option controls whether any
|
||||||
|
> announces *received on* an `internal` mode interface will
|
||||||
|
> propagate *out* on the interface this option is set on. Note
|
||||||
|
> that this controls *announce propagation*; even if announces
|
||||||
|
> do not propagate out when received, paths to destinations on
|
||||||
|
> `internal` mode interfaces may still be resolvable by means
|
||||||
|
> of path requests.
|
||||||
|
>
|
||||||
|
|
||||||
## Interface Modes
|
## Interface Modes
|
||||||
|
|
||||||
@@ -1211,6 +1276,15 @@ the default mode.
|
|||||||
> public Transport Node available on the Internet, the interface
|
> public Transport Node available on the Internet, the interface
|
||||||
> connecting over the Internet should be set to `boundary` mode.
|
> connecting over the Internet should be set to `boundary` mode.
|
||||||
>
|
>
|
||||||
|
> * The `internal` mode designates interfaces that belong to a
|
||||||
|
> network different from any marked as `boundary`. Announces from
|
||||||
|
> a `boundary` interface will not propagate to interfaces set as
|
||||||
|
> `internal`, but announces *will* propagate from `internal` *to*
|
||||||
|
> `boundary`. Devices on the `internal` side of the network will
|
||||||
|
> still be able to resolve paths to destinations across the boundary
|
||||||
|
> when needed, since recursive path requests are enabled for `internal`
|
||||||
|
> mode interfaces by default.
|
||||||
|
>
|
||||||
|
|
||||||
For a table describing the impact of all modes on announce propagation,
|
For a table describing the impact of all modes on announce propagation,
|
||||||
please see the [Announce Propagation Rules](understanding.md#understanding-announcepropagation) section.
|
please see the [Announce Propagation Rules](understanding.md#understanding-announcepropagation) section.
|
||||||
|
|||||||
@@ -632,30 +632,55 @@ but excluding any interface access codes.
|
|||||||
|
|
||||||
The following table illustrates the rules for automatically propagating announces from one interface type to another, for all possible combinations. For the purpose of announce propagation, the *Full* and *Gateway* modes are identical.
|
The following table illustrates the rules for automatically propagating announces from one interface type to another, for all possible combinations. For the purpose of announce propagation, the *Full* and *Gateway* modes are identical.
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
See the [Interface Modes](interfaces.md#interfaces-modes) section for a conceptual overview of the different interface modes, and how they are configured.
|
See the [Interface Modes](interfaces.md#interfaces-modes) section for a conceptual overview of the different interface modes, and how they are configured.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Or, represented in table form:
|
||||||
|
|
||||||
|

|
||||||
<!-- (.. code-block:: text)
|
<!-- (.. code-block:: text)
|
||||||
Full ────── ✓ ──┐ ┌── ✓ ── Full
|
Full ────── ✓ ──┐ ┌── ✓ ── Full
|
||||||
AP ──────── ✓ ──┼───> Full >───┼── ✕ ── AP
|
AP ──────── ✓ ──┤ ├── ✕ ── AP
|
||||||
Boundary ── ✓ ──┤ ├── ✓ ── Boundary
|
Boundary ── ✓ ──┼───> Full >───┼── ✓ ── Boundary
|
||||||
|
Internal ── ✓ ──┤ ├── ✓ ── Internal
|
||||||
Roaming ─── ✓ ──┘ └── ✓ ── Roaming
|
Roaming ─── ✓ ──┘ └── ✓ ── Roaming
|
||||||
|
|
||||||
Full ────── ✕ ──┐ ┌── ✓ ── Full
|
Full ────── ✕ ──┐ ┌── ✓ ── Full
|
||||||
AP ──────── ✕ ──┼────> AP >────┼── ✕ ── AP
|
AP ──────── ✕ ──┤ ├── ✕ ── AP
|
||||||
Boundary ── ✕ ──┤ ├── ✓ ── Boundary
|
Boundary ── ✕ ──┼────> AP >────┼── ✓ ── Boundary
|
||||||
|
Internal ── ✕ ──┤ ├── ✓ ── Internal
|
||||||
Roaming ─── ✕ ──┘ └── ✓ ── Roaming
|
Roaming ─── ✕ ──┘ └── ✓ ── Roaming
|
||||||
|
|
||||||
Full ────── ✓ ──┐ ┌── ✓ ── Full
|
Full ────── ✓ ──┐ ┌── ✓ ── Full
|
||||||
AP ──────── ✓ ──┼─> Roaming >──┼── ✕ ── AP
|
AP ──────── ✓ ──┤ ├── ✕ ── AP
|
||||||
Boundary ── ✕ ──┤ ├── ✕ ── Boundary
|
Boundary ── ✕ ──┼─> Roaming >──┼── ✕ ── Boundary
|
||||||
|
Internal ── ✕ ──┤ ├── ✓ ── Internal
|
||||||
Roaming ─── ✕ ──┘ └── ✕ ── Roaming
|
Roaming ─── ✕ ──┘ └── ✕ ── Roaming
|
||||||
|
|
||||||
Full ────── ✓ ──┐ ┌── ✓ ── Full
|
Full ────── ✓ ──┐ ┌── ✓ ── Full
|
||||||
AP ──────── ✓ ──┼─> Boundary >─┼── ✕ ── AP
|
AP ──────── ✓ ──┤ ├── ✕ ── AP
|
||||||
Boundary ── ✓ ──┤ ├── ✓ ── Boundary
|
Boundary ── ✓ ──┼─> Boundary >─┼── ✓ ── Boundary
|
||||||
Roaming ─── ✕ ──┘ └── ✕ ── Roaming -->
|
Internal ── ✓ ──┤ ├── ✕ ── Internal
|
||||||
|
Roaming ─── ✕ ──┘ └── ✕ ── Roaming
|
||||||
|
|
||||||
|
Full ────── ✓ ──┐ ┌── ✓ ── Full
|
||||||
|
AP ──────── ✓ ──┤ ├── ✕ ── AP
|
||||||
|
Boundary ── ✕ ──┼─> Internal >─┼── ✓ ── Boundary
|
||||||
|
Internal ── ✓ ──┤ ├── ✓ ── Internal
|
||||||
|
Roaming ─── ✓ ──┘ └── ✕ ── Roaming
|
||||||
|
|
||||||
|
dest → Full AP Boundary Roaming Gateway Internal
|
||||||
|
source ↓
|
||||||
|
Full ✓ ✕ ✓ ✓ ✓ ✓
|
||||||
|
Gateway ✓ ✕ ✓ ✓ ✓ ✓
|
||||||
|
AP ✓ ✕ ✓ ✓ ✓ ✓
|
||||||
|
Boundary ✓ ✕ ✓ ✕ ✓ ✕
|
||||||
|
Internal ✓ ✕ ✓ ✕ ✓ ✓
|
||||||
|
Roaming ✓ ✕ ✕ ✕ ✓ ✓
|
||||||
|
|
||||||
|
✓ Will rebroadcast announce
|
||||||
|
✕ Will not rebroadcast announce -->
|
||||||
|
|
||||||
### Cryptographic Primitives
|
### Cryptographic Primitives
|
||||||
|
|
||||||
|
|||||||
+28
-32
@@ -965,39 +965,35 @@ positional arguments:
|
|||||||
destination hexadecimal hash of the destination to connect to
|
destination hexadecimal hash of the destination to connect to
|
||||||
|
|
||||||
options:
|
options:
|
||||||
-h, --help show this help message and exit
|
-h, --help show this help message and exit
|
||||||
--config, -c CONFIG path to alternative Reticulum config directory
|
--config, -c PATH path to config directory
|
||||||
--identity, -i IDENTITY
|
--rnsconfig, -c PATH path to alternative Reticulum config directory
|
||||||
path to identity file to use
|
--identity, -i IDENTITY path to identity file to use
|
||||||
-v, --verbose increase verbosity
|
-v, --verbose increase verbosity
|
||||||
-q, --quiet decrease verbosity
|
-q, --quiet decrease verbosity
|
||||||
-p, --print-identity print identity and destination info and exit
|
-p, --print-identity print identity and destination info and exit
|
||||||
--version show program's version number and exit
|
--version show program's version number and exit
|
||||||
-l, --listen listen (server) mode; any command specified after --
|
-l, --listen listen (server) mode; any command specified after --
|
||||||
will be used as the default command when the initiator
|
will be used as the default command when the initiator
|
||||||
does not provide one or when remote command execution
|
does not provide one or when remote command execution
|
||||||
is disabled; if no command is specified, the default
|
is disabled; if no command is specified, the default
|
||||||
shell of the user running rnsh will be used
|
shell of the user running rnsh will be used
|
||||||
-s, --service SERVICE
|
-s, --service SERVICE service name for identity file if not the default
|
||||||
service name for identity file if not the default
|
-b, --announce PERIOD announce on startup and every PERIOD seconds; specify
|
||||||
-b, --announce PERIOD
|
0 to announce on startup only
|
||||||
announce on startup and every PERIOD seconds; specify
|
-a, --allowed HASH allow this identity to connect (may be specified
|
||||||
0 to announce on startup only
|
multiple times); allowed identities can also be
|
||||||
-a, --allowed HASH allow this identity to connect (may be specified
|
specified in ~/.rnsh/allowed_identities or
|
||||||
multiple times); allowed identities can also be
|
~/.config/rnsh/allowed_identities, one hash per line
|
||||||
specified in ~/.rnsh/allowed_identities or
|
-n, --no-auth disable authentication (allow any identity to connect)
|
||||||
~/.config/rnsh/allowed_identities, one hash per line
|
|
||||||
-n, --no-auth disable authentication (allow any identity to connect)
|
|
||||||
-A, --remote-command-as-args
|
-A, --remote-command-as-args
|
||||||
concatenate remote command to the argument list of the
|
concatenate remote command to the argument list of the
|
||||||
default program or shell
|
default program or shell
|
||||||
-C, --no-remote-command
|
-C, --no-remote-command disable executing command lines received from the
|
||||||
disable executing command lines received from the
|
remote initiator
|
||||||
remote initiator
|
-N, --no-id disable identity announcement on connect
|
||||||
-N, --no-id disable identity announcement on connect
|
-m, --mirror return with the exit code of the remote process
|
||||||
-m, --mirror return with the exit code of the remote process
|
-w, --timeout SECONDS connect and request timeout in seconds
|
||||||
-w, --timeout SECONDS
|
|
||||||
connect and request timeout in seconds
|
|
||||||
|
|
||||||
When specifying a command to execute, separate rnsh options from the command
|
When specifying a command to execute, separate rnsh options from the command
|
||||||
and its arguments with --. For example:
|
and its arguments with --. For example:
|
||||||
|
|||||||
@@ -14,6 +14,10 @@ img[src*="if_mode_graph_b.png"] {
|
|||||||
background-color: rgb(169, 177, 186);
|
background-color: rgb(169, 177, 186);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
img[src*="if_mode_graph_c.png"] {
|
||||||
|
background-color: rgb(169, 177, 186);
|
||||||
|
}
|
||||||
|
|
||||||
dt.sig {
|
dt.sig {
|
||||||
margin-bottom: 0.75rem;
|
margin-bottom: 0.75rem;
|
||||||
margin-top: 1.75rem;
|
margin-top: 1.75rem;
|
||||||
|
|||||||
+20
-1
@@ -1326,7 +1326,7 @@ Each document is a numbered directory containing:
|
|||||||
|
|
||||||
**Nomad Network Interface**
|
**Nomad Network Interface**
|
||||||
|
|
||||||
When the Nomad Network page node is enabled, work documents are viewable through the web interface. The work page lists all documents with their status, and clicking a document shows its full content and updates.
|
When the Nomad Network page node is enabled, work documents are viewable through the nomadnet interface. The work page lists all documents with their status, and clicking a document shows its full content and updates.
|
||||||
|
|
||||||
Cryptographic Attribution
|
Cryptographic Attribution
|
||||||
-------------------------
|
-------------------------
|
||||||
@@ -1491,3 +1491,22 @@ To view signature information for commits, use Git's standard ``--show-signature
|
|||||||
Refactored module
|
Refactored module
|
||||||
|
|
||||||
The output shows whether the commit signature is valid, and whether the author field matches the signing identity.
|
The output shows whether the commit signature is valid, and whether the author field matches the signing identity.
|
||||||
|
|
||||||
|
.. tip::
|
||||||
|
If you want to display both the identity hash and LXMF address for authors, you can generate a ``.mailmap`` file that resolves identities to LXMF addresses with the following script:
|
||||||
|
|
||||||
|
.. code::
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
|
||||||
|
cd $DIR
|
||||||
|
|
||||||
|
id_regex="<([0-9a-f]{32})( .*)*>"
|
||||||
|
extract_id="s/.*$id_regex/\1/g"
|
||||||
|
|
||||||
|
rm -f .mailmap
|
||||||
|
git shortlog -se | grep -Ee "$id_regex" | sed -r "$extract_id" | while read -r id ; do
|
||||||
|
if lxmf=$(rnid -i $id -H lxmf.delivery | grep -Ee "destination for this Identity is" | sed -r "$extract_id"); then
|
||||||
|
echo "<$id lxmf:$lxmf> <$id>" >> .mailmap
|
||||||
|
fi
|
||||||
|
done
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 35 KiB |
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
Binary file not shown.
@@ -230,6 +230,51 @@ specify the target Yggdrasil IPv6 address and port, like so:
|
|||||||
target_host = 201:5d78:af73:5caf:a4de:a79f:3278:71e5
|
target_host = 201:5d78:af73:5caf:a4de:a79f:3278:71e5
|
||||||
target_port = 4343
|
target_port = 4343
|
||||||
|
|
||||||
|
Automated Blocking
|
||||||
|
------------------
|
||||||
|
Listener instances of ``BackboneInterface`` will automatically block fast-flapping clients,
|
||||||
|
that repeatedly connect for a short amount of time, and then disconnect. Such behavior usually
|
||||||
|
occurs from clients trying to spam the network with announces or path requests, by connecting, dumping
|
||||||
|
a massive amount of requests, and then disconnecting in an attempt to bypass rate limits.
|
||||||
|
|
||||||
|
While such bypass attempts only have very limited effect on the amount of spam actually dumped (ingress limits
|
||||||
|
trigger immediately once a client exceeds rate limits), the behavior is often seen anyways, and
|
||||||
|
causes log noise and needless interface rotation. The fast-flapping block ensures such clients
|
||||||
|
are never allocated an interface on the transport instance.
|
||||||
|
|
||||||
|
Another common cause can simply be clients using implementations that are broken, or automated
|
||||||
|
scanning tools attempting to connect to the instance.
|
||||||
|
|
||||||
|
.. code:: ini
|
||||||
|
|
||||||
|
[[Backbone Listener]]
|
||||||
|
type = BackboneInterface
|
||||||
|
enabled = yes
|
||||||
|
listen_on = 0.0.0.0
|
||||||
|
port = 4242
|
||||||
|
|
||||||
|
# Whether to enable blocking
|
||||||
|
block_fast_flapping = yes
|
||||||
|
|
||||||
|
# How long an IP address stays
|
||||||
|
# blocked, in minutes. Set to
|
||||||
|
# 12 hours by default.
|
||||||
|
fast_flapping_block_time = 720
|
||||||
|
|
||||||
|
# The minimum time, in seconds,
|
||||||
|
# an interface must stay connected
|
||||||
|
# to be considered not fast-flapping.
|
||||||
|
fast_flapping_threshold = 20
|
||||||
|
|
||||||
|
# Amount of fast flaps a remote
|
||||||
|
# IP can perform before having
|
||||||
|
# blocking triggered.
|
||||||
|
fast_flapping_grace = 5
|
||||||
|
|
||||||
|
The configuration options listed in the example above are the *default values*, and you do
|
||||||
|
not need to add them for automated blocking to work, but you can use them to change the
|
||||||
|
behavior from the defaults, if necessary.
|
||||||
|
|
||||||
.. _interfaces-tcps:
|
.. _interfaces-tcps:
|
||||||
|
|
||||||
TCP Server Interface
|
TCP Server Interface
|
||||||
@@ -998,7 +1043,13 @@ On a real system, you should make the script robust enough to deal with intermit
|
|||||||
**Physical Location**
|
**Physical Location**
|
||||||
|
|
||||||
``latitude``, ``longitude``, ``height``
|
``latitude``, ``longitude``, ``height``
|
||||||
Optional physical coordinates for the interface. These are useful for mapping discovered interfaces geographically or for clients to automatically select the nearest access point. Coordinates should be in decimal degrees, height in meters.
|
Optional physical coordinates for the interface. These are useful for mapping discovered interfaces geographically or for clients to automatically select the nearest access point. Coordinates should be in decimal degrees, height in meters above mean sea level.
|
||||||
|
|
||||||
|
``location_cmd``
|
||||||
|
Optional path to executable or script that returns the physical coordinates for the interface. This can be used instead of manually setting ``latitude``, ``longitude`` and ``height``. Reticulum expects the script to output the location data to ``stdout`` on a single line, separated by commas, with values as floating point numbers in the format ``LAT, LON, HEIGHT``. Coordinates should be in decimal degrees, height in meters above mean sea level.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
The height value for interface discovery is specified in *height above mean sea level*! This is the geoid-corrected height value, and is distinct from GPS altitude. If you manually specify height, it will typically be set to what you find on a topographical map. If you are using a script to output the location data, make sure that you are using the geoid-corrected altitude, not height above the GPS ellipsoid. Most GPS systems will make both figures available.
|
||||||
|
|
||||||
**Radio Parameters**
|
**Radio Parameters**
|
||||||
|
|
||||||
@@ -1207,6 +1258,22 @@ These can be used to control various aspects of interface behaviour.
|
|||||||
link or a remote TCP tunnel) solely to discover better local
|
link or a remote TCP tunnel) solely to discover better local
|
||||||
infrastructure, which then supersedes the bootstrap interface.
|
infrastructure, which then supersedes the bootstrap interface.
|
||||||
|
|
||||||
|
|
||||||
|
* | The ``recursive_prs`` option allows you to enable recursive path
|
||||||
|
discovery on an interface regardless of its configured interface
|
||||||
|
mode. When this option is enabled, Reticulum will attempt to
|
||||||
|
recursively discover paths for path requests received on this
|
||||||
|
interface.
|
||||||
|
|
||||||
|
|
||||||
|
* | The ``announces_from_internal`` option controls whether any
|
||||||
|
announces *received on* an ``internal`` mode interface will
|
||||||
|
propagate *out* on the interface this option is set on. Note
|
||||||
|
that this controls *announce propagation*; even if announces
|
||||||
|
do not propagate out when received, paths to destinations on
|
||||||
|
``internal`` mode interfaces may still be resolvable by means
|
||||||
|
of path requests.
|
||||||
|
|
||||||
.. _interfaces-modes:
|
.. _interfaces-modes:
|
||||||
|
|
||||||
Interface Modes
|
Interface Modes
|
||||||
@@ -1284,6 +1351,15 @@ the default mode.
|
|||||||
public Transport Node available on the Internet, the interface
|
public Transport Node available on the Internet, the interface
|
||||||
connecting over the Internet should be set to ``boundary`` mode.
|
connecting over the Internet should be set to ``boundary`` mode.
|
||||||
|
|
||||||
|
* | The ``internal`` mode designates interfaces that belong to a
|
||||||
|
network different from any marked as ``boundary``. Announces from
|
||||||
|
a ``boundary`` interface will not propagate to interfaces set as
|
||||||
|
``internal``, but announces *will* propagate from ``internal`` *to*
|
||||||
|
``boundary``. Devices on the ``internal`` side of the network will
|
||||||
|
still be able to resolve paths to destinations across the boundary
|
||||||
|
when needed, since recursive path requests are enabled for ``internal``
|
||||||
|
mode interfaces by default.
|
||||||
|
|
||||||
For a table describing the impact of all modes on announce propagation,
|
For a table describing the impact of all modes on announce propagation,
|
||||||
please see the :ref:`Announce Propagation Rules<understanding-announcepropagation>` section.
|
please see the :ref:`Announce Propagation Rules<understanding-announcepropagation>` section.
|
||||||
|
|
||||||
|
|||||||
@@ -707,32 +707,58 @@ Announce Propagation Rules
|
|||||||
|
|
||||||
The following table illustrates the rules for automatically propagating announces from one interface type to another, for all possible combinations. For the purpose of announce propagation, the *Full* and *Gateway* modes are identical.
|
The following table illustrates the rules for automatically propagating announces from one interface type to another, for all possible combinations. For the purpose of announce propagation, the *Full* and *Gateway* modes are identical.
|
||||||
|
|
||||||
|
See the :ref:`Interface Modes<interfaces-modes>` section for a conceptual overview of the different interface modes, and how they are configured.
|
||||||
|
|
||||||
.. image:: graphics/if_mode_graph_b.png
|
.. image:: graphics/if_mode_graph_b.png
|
||||||
|
|
||||||
See the :ref:`Interface Modes<interfaces-modes>` section for a conceptual overview of the different interface modes, and how they are configured.
|
Or, represented in table form:
|
||||||
|
|
||||||
|
.. image:: graphics/if_mode_graph_c.png
|
||||||
|
|
||||||
..
|
..
|
||||||
(.. code-block:: text)
|
(.. code-block:: text)
|
||||||
Full ────── ✓ ──┐ ┌── ✓ ── Full
|
Full ────── ✓ ──┐ ┌── ✓ ── Full
|
||||||
AP ──────── ✓ ──┼───> Full >───┼── ✕ ── AP
|
AP ──────── ✓ ──┤ ├── ✕ ── AP
|
||||||
Boundary ── ✓ ──┤ ├── ✓ ── Boundary
|
Boundary ── ✓ ──┼───> Full >───┼── ✓ ── Boundary
|
||||||
|
Internal ── ✓ ──┤ ├── ✓ ── Internal
|
||||||
Roaming ─── ✓ ──┘ └── ✓ ── Roaming
|
Roaming ─── ✓ ──┘ └── ✓ ── Roaming
|
||||||
|
|
||||||
Full ────── ✕ ──┐ ┌── ✓ ── Full
|
Full ────── ✕ ──┐ ┌── ✓ ── Full
|
||||||
AP ──────── ✕ ──┼────> AP >────┼── ✕ ── AP
|
AP ──────── ✕ ──┤ ├── ✕ ── AP
|
||||||
Boundary ── ✕ ──┤ ├── ✓ ── Boundary
|
Boundary ── ✕ ──┼────> AP >────┼── ✓ ── Boundary
|
||||||
|
Internal ── ✕ ──┤ ├── ✓ ── Internal
|
||||||
Roaming ─── ✕ ──┘ └── ✓ ── Roaming
|
Roaming ─── ✕ ──┘ └── ✓ ── Roaming
|
||||||
|
|
||||||
Full ────── ✓ ──┐ ┌── ✓ ── Full
|
Full ────── ✓ ──┐ ┌── ✓ ── Full
|
||||||
AP ──────── ✓ ──┼─> Roaming >──┼── ✕ ── AP
|
AP ──────── ✓ ──┤ ├── ✕ ── AP
|
||||||
Boundary ── ✕ ──┤ ├── ✕ ── Boundary
|
Boundary ── ✕ ──┼─> Roaming >──┼── ✕ ── Boundary
|
||||||
|
Internal ── ✕ ──┤ ├── ✓ ── Internal
|
||||||
Roaming ─── ✕ ──┘ └── ✕ ── Roaming
|
Roaming ─── ✕ ──┘ └── ✕ ── Roaming
|
||||||
|
|
||||||
Full ────── ✓ ──┐ ┌── ✓ ── Full
|
Full ────── ✓ ──┐ ┌── ✓ ── Full
|
||||||
AP ──────── ✓ ──┼─> Boundary >─┼── ✕ ── AP
|
AP ──────── ✓ ──┤ ├── ✕ ── AP
|
||||||
Boundary ── ✓ ──┤ ├── ✓ ── Boundary
|
Boundary ── ✓ ──┼─> Boundary >─┼── ✓ ── Boundary
|
||||||
|
Internal ── ✓ ──┤ ├── ✕ ── Internal
|
||||||
Roaming ─── ✕ ──┘ └── ✕ ── Roaming
|
Roaming ─── ✕ ──┘ └── ✕ ── Roaming
|
||||||
|
|
||||||
|
Full ────── ✓ ──┐ ┌── ✓ ── Full
|
||||||
|
AP ──────── ✓ ──┤ ├── ✕ ── AP
|
||||||
|
Boundary ── ✕ ──┼─> Internal >─┼── ✓ ── Boundary
|
||||||
|
Internal ── ✓ ──┤ ├── ✓ ── Internal
|
||||||
|
Roaming ─── ✓ ──┘ └── ✕ ── Roaming
|
||||||
|
|
||||||
|
dest → Full AP Boundary Roaming Gateway Internal
|
||||||
|
source ↓
|
||||||
|
Full ✓ ✕ ✓ ✓ ✓ ✓
|
||||||
|
Gateway ✓ ✕ ✓ ✓ ✓ ✓
|
||||||
|
AP ✓ ✕ ✓ ✓ ✓ ✓
|
||||||
|
Boundary ✓ ✕ ✓ ✕ ✓ ✕
|
||||||
|
Internal ✓ ✕ ✓ ✕ ✓ ✓
|
||||||
|
Roaming ✓ ✕ ✕ ✕ ✓ ✓
|
||||||
|
|
||||||
|
✓ Will rebroadcast announce
|
||||||
|
✕ Will not rebroadcast announce
|
||||||
|
|
||||||
|
|
||||||
.. _understanding-primitives:
|
.. _understanding-primitives:
|
||||||
|
|
||||||
|
|||||||
+28
-32
@@ -1002,39 +1002,35 @@ available. These are only recognised immediately after a newline character:
|
|||||||
destination hexadecimal hash of the destination to connect to
|
destination hexadecimal hash of the destination to connect to
|
||||||
|
|
||||||
options:
|
options:
|
||||||
-h, --help show this help message and exit
|
-h, --help show this help message and exit
|
||||||
--config, -c CONFIG path to alternative Reticulum config directory
|
--config, -c PATH path to config directory
|
||||||
--identity, -i IDENTITY
|
--rnsconfig, -c PATH path to alternative Reticulum config directory
|
||||||
path to identity file to use
|
--identity, -i IDENTITY path to identity file to use
|
||||||
-v, --verbose increase verbosity
|
-v, --verbose increase verbosity
|
||||||
-q, --quiet decrease verbosity
|
-q, --quiet decrease verbosity
|
||||||
-p, --print-identity print identity and destination info and exit
|
-p, --print-identity print identity and destination info and exit
|
||||||
--version show program's version number and exit
|
--version show program's version number and exit
|
||||||
-l, --listen listen (server) mode; any command specified after --
|
-l, --listen listen (server) mode; any command specified after --
|
||||||
will be used as the default command when the initiator
|
will be used as the default command when the initiator
|
||||||
does not provide one or when remote command execution
|
does not provide one or when remote command execution
|
||||||
is disabled; if no command is specified, the default
|
is disabled; if no command is specified, the default
|
||||||
shell of the user running rnsh will be used
|
shell of the user running rnsh will be used
|
||||||
-s, --service SERVICE
|
-s, --service SERVICE service name for identity file if not the default
|
||||||
service name for identity file if not the default
|
-b, --announce PERIOD announce on startup and every PERIOD seconds; specify
|
||||||
-b, --announce PERIOD
|
0 to announce on startup only
|
||||||
announce on startup and every PERIOD seconds; specify
|
-a, --allowed HASH allow this identity to connect (may be specified
|
||||||
0 to announce on startup only
|
multiple times); allowed identities can also be
|
||||||
-a, --allowed HASH allow this identity to connect (may be specified
|
specified in ~/.rnsh/allowed_identities or
|
||||||
multiple times); allowed identities can also be
|
~/.config/rnsh/allowed_identities, one hash per line
|
||||||
specified in ~/.rnsh/allowed_identities or
|
-n, --no-auth disable authentication (allow any identity to connect)
|
||||||
~/.config/rnsh/allowed_identities, one hash per line
|
|
||||||
-n, --no-auth disable authentication (allow any identity to connect)
|
|
||||||
-A, --remote-command-as-args
|
-A, --remote-command-as-args
|
||||||
concatenate remote command to the argument list of the
|
concatenate remote command to the argument list of the
|
||||||
default program or shell
|
default program or shell
|
||||||
-C, --no-remote-command
|
-C, --no-remote-command disable executing command lines received from the
|
||||||
disable executing command lines received from the
|
remote initiator
|
||||||
remote initiator
|
-N, --no-id disable identity announcement on connect
|
||||||
-N, --no-id disable identity announcement on connect
|
-m, --mirror return with the exit code of the remote process
|
||||||
-m, --mirror return with the exit code of the remote process
|
-w, --timeout SECONDS connect and request timeout in seconds
|
||||||
-w, --timeout SECONDS
|
|
||||||
connect and request timeout in seconds
|
|
||||||
|
|
||||||
When specifying a command to execute, separate rnsh options from the command
|
When specifying a command to execute, separate rnsh options from the command
|
||||||
and its arguments with --. For example:
|
and its arguments with --. For example:
|
||||||
|
|||||||
Binary file not shown.
Reference in New Issue
Block a user