Compare commits

...

32 Commits

Author SHA1 Message Date
Mark Qvist c0bc19d998 Cleanup 2026-06-27 21:59:34 +02:00
Mark Qvist c99ec922aa Cleanup 2026-06-26 11:59:33 +02:00
Mark Qvist 0a6f76e41b Updated documentation 2026-06-25 21:28:06 +02:00
Mark Qvist 156ce9cd34 Updated readme 2026-06-25 21:17:34 +02:00
Mark Qvist 176092ebf9 Cleanup 2026-06-25 20:57:40 +02:00
Mark Qvist 98817e3f9d Improved announce propagation logic 2026-06-25 19:53:06 +02:00
Mark Qvist 276b7bb92f Updated docs 2026-06-25 17:36:45 +02:00
Mark Qvist d1813e1ff0 Added announces_from_internal interface option 2026-06-25 15:33:48 +02:00
Mark Qvist 5355a9bb20 Updated readme 2026-06-25 15:33:25 +02:00
Mark Qvist 48d17a8616 Fixed typo 2026-06-19 23:55:50 +02:00
Mark Qvist 7e603f9606 Updated documentation 2026-06-19 20:13:16 +02:00
Mark Qvist ac6f0199f5 Updated documentation 2026-06-19 20:13:03 +02:00
Mark Qvist 71f6f721fd Added internal interface mode 2026-06-19 19:27:52 +02:00
Mark Qvist 51b7dc7cd4 Updated version 2026-06-19 10:16:25 +02:00
Mark Qvist 03ddb08248 Added recursive_prs interface option 2026-06-19 10:15:32 +02:00
Mark Qvist 422dc05549 Updated changelog 2026-06-07 15:21:20 +02:00
Mark Qvist 50c0a354c9 Cleanup 2026-06-01 00:34:49 +02:00
Mark Qvist e98487c1cf Prepare release 2026-06-01 00:27:36 +02:00
Mark Qvist 50e03a24e8 Updated version 2026-06-01 00:01:33 +02:00
Mark Qvist 0cc42568c7 Fixed UDP listener replacement deadlocking AutoInterface traffic when fast-roaming between physical interfaces or WiFi APs 2026-05-31 23:54:46 +02:00
Mark Qvist 41790ca707 Added release manifest 2026-05-31 11:39:27 +02:00
Mark Qvist 20b1bfd01e Prepare release 2026-05-29 09:29:40 +02:00
Mark Qvist 1be5e10abe Updated makefile 2026-05-29 09:29:19 +02:00
Mark Qvist 13a9ebed83 Updated version 2026-05-29 09:19:28 +02:00
Mark Qvist aee8c3be0c Fixed missing check for instance type configuration conflict 2026-05-29 09:01:21 +02:00
Mark Qvist ef8ccf67ed Cleaned up superfluous path state call 2026-05-29 08:21:32 +02:00
Mark Qvist 91d531b053 Fixed regression in inbound announce deduplication 2026-05-29 08:19:04 +02:00
Mark Qvist 0de169836e Improved cleanup of stale known destinations 2026-05-29 07:53:29 +02:00
Mark Qvist a2ef978208 Use msgpack for shared instance RPC 2026-05-29 07:30:41 +02:00
Mark Qvist 5b3bb050e7 Fixed typo 2026-05-29 00:22:50 +02:00
Mark Qvist 9b9efe5fac Added mailmap generation tip to git documentation 2026-05-29 00:04:05 +02:00
Mark Qvist 482d5ac4cb Updated docs 2026-05-28 23:36:51 +02:00
59 changed files with 928 additions and 855 deletions
+1
View File
@@ -14,3 +14,4 @@ tests/rnsconfig/logfile*
*.data *.data
*.result *.result
.buildinfo.bak .buildinfo.bak
docs/Reticulum Manual.*
+21 -140
View File
@@ -1,9 +1,10 @@
### 2026-05-28: RNS 1.3.3 ### 2026-06-01: RNS 1.3.5
This release fixes a regression in persistence of known destination on Windows. This maintenance release contains an important fix for `AutoInterface` reliability when roaming between different physical networks.
**Changes** **Changes**
- Fixed regression in known destinations persist on Windows - 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
**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:
@@ -33,6 +34,23 @@ 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-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 ### 2026-05-28: RNS 1.3.2
@@ -46,35 +64,6 @@ This release adds commit signing and validation support to the `rngit` system, a
- Added option to configure blackhole update interval - Added option to configure blackhole update interval
- Added configuration option to log without timestamps - Added configuration option to log without timestamps
**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:
```sh
rngit release rns://7649a50d84610232d1416b41d2896aff/reticulum/reticulum fetch "latest:rns-*.whl" --signer bc7291552be7a58f361522990465165c
```
To download all artifacts, including the documentation and source archive, you can use the following command:
```sh
rngit release rns://7649a50d84610232d1416b41d2896aff/reticulum/reticulum fetch latest:all --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 verify --signer bc7291552be7a58f361522990465165c
```
To verify release artifacts 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-22: RNS 1.3.1 ### 2026-05-22: RNS 1.3.1
This maintenance release fixes a single bug. This maintenance release fixes a single bug.
@@ -82,34 +71,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.
@@ -124,28 +85,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.
@@ -161,28 +100,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.
@@ -203,15 +120,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.
@@ -229,15 +137,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.
@@ -259,15 +158,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.
@@ -296,15 +186,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!
+1 -1
View File
@@ -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
+7 -1
View File
@@ -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.
+7 -1
View File
@@ -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.
+12 -8
View File
@@ -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
+10 -2
View File
@@ -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)
retry_delay = 1.25
listener_started = False
while not listener_started:
try:
time.sleep(retry_delay)
udp_server = socketserver.UDPServer(listen_address, self.handler_factory(self.process_incoming)) udp_server = socketserver.UDPServer(listen_address, self.handler_factory(self.process_incoming))
self.interface_servers[ifname] = udp_server 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
+15 -23
View File
@@ -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,28 +200,17 @@ 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_DEBUG)
+71 -151
View File
@@ -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
@@ -547,10 +529,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 +545,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 +613,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):
""" """
@@ -712,14 +674,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 +711,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 +736,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 +751,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 +764,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 +778,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 +786,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)
@@ -933,8 +882,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 +914,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 +924,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 +948,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)
@@ -1039,8 +983,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 +996,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:
@@ -1186,7 +1128,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 +1142,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 +1151,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 +1158,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 +1326,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 +1348,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 +1357,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 +1365,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 +1382,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 +1404,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 +1432,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:
+40 -69
View File
@@ -313,51 +313,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 +354,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 +445,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 +472,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 +489,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 +513,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 +532,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
View File
@@ -31,5 +31,4 @@
class Resolver: class Resolver:
@staticmethod @staticmethod
def resolve_identity(full_name): def resolve_identity(full_name): pass
pass
+166 -192
View File
@@ -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")
@@ -882,10 +891,13 @@ class Reticulum:
interface.discovery_bandwidth = discovery_bandwidth interface.discovery_bandwidth = discovery_bandwidth
interface.discovery_modulation = discovery_modulation interface.discovery_modulation = discovery_modulation
interface.recursive_prs = recursive_prs
interface.announces_from_internal = announces_from_internal
interface.announce_rate_target = announce_rate_target interface.announce_rate_target = announce_rate_target
interface.announce_rate_grace = announce_rate_grace interface.announce_rate_grace = announce_rate_grace
interface.announce_rate_penalty = announce_rate_penalty interface.announce_rate_penalty = announce_rate_penalty
interface.ingress_control = ingress_control interface.ingress_control = ingress_control
if egress_control != None: interface.egress_control = egress_control if egress_control != None: interface.egress_control = egress_control
if ic_max_held_announces != None: interface.ic_max_held_announces = ic_max_held_announces if ic_max_held_announces != None: interface.ic_max_held_announces = ic_max_held_announces
if ic_burst_hold != None: interface.ic_burst_hold = ic_burst_hold if ic_burst_hold != None: interface.ic_burst_hold = ic_burst_hold
@@ -911,12 +923,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 +1048,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,6 +1065,8 @@ 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.recursive_prs = recursive_prs
interface.announces_from_internal = announces_from_internal
interface.announce_cap = announce_cap if announce_cap != None else Reticulum.ANNOUNCE_CAP/100.0 interface.announce_cap = announce_cap if announce_cap != None else Reticulum.ANNOUNCE_CAP/100.0
interface.announce_rate_target = announce_rate_target interface.announce_rate_target = announce_rate_target
interface.announce_rate_grace = announce_rate_grace interface.announce_rate_grace = announce_rate_grace
@@ -1073,12 +1085,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 +1183,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 +1253,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 +1267,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 +1281,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 +1298,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 +1311,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 +1348,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 +1375,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
@@ -1430,16 +1405,12 @@ class Reticulum:
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) ifstats["name"] = str(interface)
ifstats["short_name"] = str(interface.name) ifstats["short_name"] = str(interface.name)
@@ -1470,14 +1441,13 @@ class Reticulum:
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 +1461,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 +1470,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],
"via": RNS.Transport.path_table[dst_hash][1],
"hops": path_hops,
"expires": RNS.Transport.path_table[dst_hash][3], "expires": RNS.Transport.path_table[dst_hash][3],
"interface": str(RNS.Transport.path_table[dst_hash][5]), "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 +1481,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 +1502,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 +1512,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 +1528,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 +1538,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 +1549,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 +1568,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 +1579,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 +1589,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 +1603,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 +1617,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 +1640,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 +1654,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 +1665,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 +1676,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 +1797,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,
+110 -55
View File
@@ -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,8 +208,12 @@ 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
@@ -226,6 +231,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):
@@ -1137,7 +1150,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 +1170,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 +1183,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 +1199,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_EXTREME
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
elif not local_destination and interface.announces_from_internal == False and from_interface.mode == RNS.Interfaces.Interface.Interface.MODE_INTERNAL:
RNS.log("Blocking announce broadcast on "+str(interface)+" due to internal-mode next hop interface", ac_loglevel) if RNS.sl(ac_loglevel) else None
should_transmit = False
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 should_transmit = False
elif interface.mode == RNS.Interfaces.Interface.Interface.MODE_ROAMING: elif interface.mode == RNS.Interfaces.Interface.Interface.MODE_ROAMING:
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 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 +1339,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 +1352,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:
@@ -1514,6 +1550,8 @@ class Transport:
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) 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)
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) 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)
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 for_local_client_link or proof_for_local_client
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,6 +1613,19 @@ 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:
if to_local_client and RNS.Transport.local_hops_delta != 0:
if packet.header_type == RNS.Packet.HEADER_2:
# Strip transport headers and transmit
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 # Just increase hop count and transmit
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)
@@ -1623,6 +1674,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 +1684,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 +1728,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 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()
@@ -1755,6 +1808,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]
@@ -1827,7 +1881,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:
@@ -2088,7 +2141,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:
@@ -2191,7 +2244,7 @@ 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 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)
@@ -2258,7 +2311,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 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:
@@ -2657,6 +2710,7 @@ class Transport:
""" """
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
@@ -2667,6 +2721,7 @@ class Transport:
""" """
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
@@ -2915,7 +2970,8 @@ 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_DEBUG):
interface_str = f" on {attached_interface}" interface_str = f" on {attached_interface}"
@@ -3054,25 +3110,24 @@ class Transport:
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():
+16 -6
View File
@@ -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,12 +249,21 @@ 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 7:
# 0: Log only critical information # 0: Log only critical information
+1
View File
@@ -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"
+1 -1
View File
@@ -1 +1 @@
__version__ = "1.3.3" __version__ = "1.3.6"
Binary file not shown.
Binary file not shown.
+1 -1
View File
@@ -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: c608aeb54a4bb500d26d78accb0ff325
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

+20 -1
View File
@@ -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
+25
View File
@@ -1207,6 +1207,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 +1300,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.
+35 -9
View File
@@ -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:
+4
View File
@@ -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 -1
View File
@@ -1,5 +1,5 @@
const DOCUMENTATION_OPTIONS = { const DOCUMENTATION_OPTIONS = {
VERSION: '1.3.2', VERSION: '1.3.6',
LANGUAGE: 'en', LANGUAGE: 'en',
COLLAPSE_INDEX: false, COLLAPSE_INDEX: false,
BUILDER: 'html', BUILDER: 'html',
+5 -5
View File
@@ -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.6 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.6 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.6 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=70d8fd07"></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 -5
View File
@@ -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.6 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.6 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.6 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=70d8fd07"></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 -5
View File
@@ -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.6 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.6 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.6 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=70d8fd07"></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 -5
View File
@@ -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.6 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.6 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.6 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=70d8fd07"></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 -5
View File
@@ -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.6 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.6 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.6 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=70d8fd07"></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
View File
@@ -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.6 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.6 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.6 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=&quot;$( cd &quot;$( dirname &quot;${BASH_SOURCE[0]}&quot; )&quot; &gt;/dev/null 2&gt;&amp;1 &amp;&amp; pwd )&quot;
cd $DIR
id_regex=&quot;&lt;([0-9a-f]{32})( .*)*&gt;&quot;
extract_id=&quot;s/.*$id_regex/\1/g&quot;
rm -f .mailmap
git shortlog -se | grep -Ee &quot;$id_regex&quot; | sed -r &quot;$extract_id&quot; | while read -r id ; do
if lxmf=$(rnid -i $id -H lxmf.delivery | grep -Ee &quot;destination for this Identity is&quot; | sed -r &quot;$extract_id&quot;); then
echo &quot;&lt;$id lxmf:$lxmf&gt; &lt;$id&gt;&quot; &gt;&gt; .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=70d8fd07"></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 -5
View File
@@ -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.6 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.6 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.6 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=70d8fd07"></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 -5
View File
@@ -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.6 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.6 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.6 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">
@@ -719,7 +719,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=70d8fd07"></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>
+34 -5
View File
@@ -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.6 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.6 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.6 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">
@@ -1361,6 +1361,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 +1469,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,
@@ -1774,7 +1803,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=70d8fd07"></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 -5
View File
@@ -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.6 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.6 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.6 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=70d8fd07"></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 -5
View File
@@ -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.6 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.6 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.6 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=70d8fd07"></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.
+5 -5
View File
@@ -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.6 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.6 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.6 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=70d8fd07"></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 -5
View File
@@ -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.6 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.6 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.6 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=70d8fd07"></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
+5 -5
View File
@@ -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.6 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.6 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.6 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=70d8fd07"></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 -5
View File
@@ -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.6 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.6 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.6 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=70d8fd07"></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 -6
View File
@@ -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.6 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.6 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.6 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=70d8fd07"></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 -5
View File
@@ -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.6 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.6 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.6 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">
@@ -1639,7 +1639,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=70d8fd07"></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 -5
View File
@@ -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.6 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.6 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.6 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=70d8fd07"></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 -5
View File
@@ -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.6 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.6 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.6 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=70d8fd07"></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>
+1 -1
View File
@@ -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
+23
View File
@@ -1136,6 +1136,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 +1225,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.
+36 -11
View File
@@ -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.
![image](graphics/if_mode_graph_b.png)
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.
![image](graphics/if_mode_graph_b.png)
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
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
+4
View File
@@ -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
View File
@@ -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.
+25
View File
@@ -1207,6 +1207,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 +1300,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.
+35 -9
View File
@@ -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:
BIN
View File
Binary file not shown.