Compare commits

..

163 Commits

Author SHA1 Message Date
Mark Qvist 6c989eb38e Prepare release 2026-05-19 01:08:42 +02:00
Mark Qvist 137d73ad0d Updated version 2026-05-19 00:51:12 +02:00
Mark Qvist 58d4162f6d Updated rngit documentation 2026-05-19 00:48:14 +02:00
Mark Qvist 67625395fe Updated logging 2026-05-18 22:48:18 +02:00
Mark Qvist f62512381a Updated rngit documentation 2026-05-18 22:01:55 +02:00
Mark Qvist 888e3102de Added offline RSM release manifest verification 2026-05-18 17:55:10 +02:00
Mark Qvist f83435c697 Updated rngit documentation 2026-05-18 17:13:31 +02:00
Mark Qvist 5243d646f0 Improved known destination persist reliability 2026-05-18 14:57:17 +02:00
Mark Qvist bdb284ce5d Improved page node ref handling in commit links 2026-05-18 14:46:00 +02:00
Mark Qvist 1b34820601 Added ability to fetch new verified releases directly from RSM-embedded release manifest data. Added local release generation and signing with the --local option to rnid. 2026-05-18 13:49:01 +02:00
Mark Qvist 01010dd599 Added version getter to setup.py 2026-05-18 13:46:18 +02:00
Mark Qvist da32709f7c Updated makefile 2026-05-18 13:45:40 +02:00
Mark Qvist cdc6159a15 Added canonical release RSM structure validator to rnid 2026-05-18 13:32:54 +02:00
Mark Qvist eb2f7ae455 Implemented remote HEAD tracking for forks and mirrors in rngit 2026-05-18 12:39:22 +02:00
Mark Qvist a74f1bd89f Save manifest on release fetch 2026-05-18 11:52:46 +02:00
Mark Qvist 0dd063a32e Clear previous request progress 2026-05-18 03:37:52 +02:00
Mark Qvist 9885a70a88 Prepare release 2026-05-18 03:32:46 +02:00
Mark Qvist e4a85de089 Actually send manifest and rsgs 2026-05-18 03:31:40 +02:00
Mark Qvist ca0d2dffbe Prepare release 2026-05-18 03:06:16 +02:00
Mark Qvist 511d169c77 Cleanup 2026-05-18 03:03:30 +02:00
Mark Qvist 19bc8ef85c Cleanup 2026-05-18 03:00:52 +02:00
Mark Qvist 1e33d3eebb Updated version 2026-05-18 02:52:30 +02:00
Mark Qvist d18f434583 Implemented rsm-verified release fetching with embedded artifact signatures 2026-05-18 02:51:53 +02:00
Mark Qvist 64749b4d18 Cleanup. Prepared artifact fetch. 2026-05-18 01:24:15 +02:00
Mark Qvist 875d8ef7eb Updated changelog 2026-05-18 01:22:59 +02:00
Mark Qvist 20283f1536 Added automatic signing and release manifest generation to rnid release 2026-05-18 00:44:41 +02:00
Mark Qvist c4af328802 Dropped note meta field requirement from rsg structure 2026-05-18 00:10:22 +02:00
Mark Qvist a2193b9ffd Dropped note meta field requirement from rsg structure 2026-05-18 00:05:29 +02:00
Mark Qvist d2542fd49b Added blocked identities handling and push stats ignore to rngit 2026-05-17 23:26:19 +02:00
Mark Qvist 0333884877 Handle silly links 2026-05-17 23:09:21 +02:00
Mark Qvist 63947ed69a Oops 2026-05-17 23:07:44 +02:00
Mark Qvist d6d18ce29c Fixed micron tags escaping to where they shouldn't go and wreaking havoc on the rest of the pafe. Looking at you table cell truncator. 2026-05-17 23:01:36 +02:00
Mark Qvist 2ef58d8b59 Better table cell truncation method 2026-05-17 22:01:30 +02:00
Mark Qvist 15f2d1635e Added fork and mirror sync time to repo pages 2026-05-17 21:14:26 +02:00
Mark Qvist c83b71f49a Cleanup 2026-05-17 20:45:38 +02:00
Mark Qvist f0824fd71e Added RSM metadata embedding and spec validation to rnid 2026-05-17 20:29:40 +02:00
Mark Qvist 8dde60658f Added validate.py for spec validation 2026-05-17 20:27:16 +02:00
Mark Qvist 9437648ae5 Adjusted logging 2026-05-17 19:06:24 +02:00
Mark Qvist 71b19aca2c Added signed message from file creation to rnid. Added signed message metadata output option to rnid. 2026-05-17 18:29:44 +02:00
Mark Qvist 7d320f8cd5 Fixed missing working identity check on message signing op 2026-05-17 17:27:18 +02:00
Mark Qvist 340d0883a7 Updated docs 2026-05-17 17:24:44 +02:00
Mark Qvist 66096acc29 Added ability to render raw micron in markdown files 2026-05-17 17:22:25 +02:00
Mark Qvist e35100d865 Updated docs 2026-05-17 16:51:20 +02:00
Mark Qvist 128455ef01 Implemented remote permissions management in rngit 2026-05-17 16:49:44 +02:00
Mark Qvist 10156cc90e Updated rngit docs 2026-05-17 15:19:42 +02:00
Mark Qvist 4f5482f2ae Implemented identity and destination aliases in rngit 2026-05-17 15:09:00 +02:00
Mark Qvist 2b9fdae74b Fixed typo 2026-05-17 12:49:53 +02:00
Mark Qvist 7506caa0da Fixed f-string for old snakes 2026-05-17 12:25:21 +02:00
Mark Qvist b1f522277c Prepare release 2026-05-17 00:56:08 +02:00
Mark Qvist af6e0c9ecf Updated changelog 2026-05-17 00:47:15 +02:00
Mark Qvist 176567e3f1 Updated version 2026-05-17 00:39:15 +02:00
Mark Qvist 15cd4268ac Cleanup 2026-05-17 00:38:51 +02:00
Mark Qvist 9307db16c4 Allow disabling mirroring interval 2026-05-17 00:17:12 +02:00
Mark Qvist 0f29ab629a Updated rngit documentation 2026-05-17 00:07:16 +02:00
Mark Qvist b2a4ceb853 Updated default config 2026-05-16 23:37:45 +02:00
Mark Qvist 6c7f1d068b Implemented fork and mirror sync from upstreams 2026-05-16 23:02:45 +02:00
Mark Qvist b76beb602d Added scaffolding for periodic upstream mirror sync and manual fork/mirror sync 2026-05-16 22:06:16 +02:00
Mark Qvist 0c68f6491a Added fork and mirror indications to rngit page node 2026-05-16 21:14:02 +02:00
Mark Qvist 038981474a Added fork and mirroring support to rngit CLI and node 2026-05-16 20:21:01 +02:00
Mark Qvist df0b4a5165 Implemented rngit remote repo create 2026-05-16 17:35:00 +02:00
Mark Qvist db7359f56d Preparation for create, fork and mirror functionality. Refactored and expanded permissions system. Added group .allowed files. Prepared dynamic permissions resolution. Basic functional scaffolding for create/fork/mirror. 2026-05-16 16:16:10 +02:00
Mark Qvist 12e45b6483 Added work document proposals 2026-05-16 02:11:00 +02:00
Mark Qvist ba8fca6f87 Nicer stats page 2026-05-15 23:21:56 +02:00
Mark Qvist 9b99b72f61 Cleanup 2026-05-15 22:15:17 +02:00
Mark Qvist 03cfbc2eb6 Added half-block chart rendering 2026-05-15 22:09:27 +02:00
Mark Qvist c92872a81b Added download stats to rngit 2026-05-15 20:12:07 +02:00
Mark Qvist f3f4d9bca3 Cleanup 2026-05-15 17:32:10 +02:00
Mark Qvist e7a317f0a0 Use canonical Transport interface list add/removes. Improved announce cache cleaning. Adjusted logging. 2026-05-15 17:08:22 +02:00
Mark Qvist d5b64a4af3 Cleaned up log/print consistency for listener/initiator modes in rncp 2026-05-15 14:40:55 +02:00
Mark Qvist 5667a0bbac Better transfer completed feedback in rncp, thanks to neutral 2026-05-15 14:27:17 +02:00
Mark Qvist 7e46422c16 Auto-set latest release on creation 2026-05-15 00:58:17 +02:00
Mark Qvist 869a803149 Updated logging 2026-05-14 23:55:01 +02:00
Mark Qvist f744e4d9a3 Updated logging 2026-05-14 23:32:33 +02:00
Mark Qvist 1a7607cba3 Improved shared instance RPC error handling 2026-05-14 19:16:52 +02:00
Mark Qvist d881c111f6 Added latest release management to rngit 2026-05-14 14:13:42 +02:00
Mark Qvist bdac57ec0b Readme formatting 2026-05-14 12:02:54 +02:00
Mark Qvist c15f566cfa Updated readme 2026-05-14 11:58:05 +02:00
Mark Qvist bdc79b9097 Updated readme 2026-05-14 11:55:11 +02:00
Mark Qvist 102eccb77d Updated readme 2026-05-14 11:54:36 +02:00
Mark Qvist e8b236c7d8 Updated readme 2026-05-14 11:54:05 +02:00
Mark Qvist d69491eb80 Updated readme 2026-05-14 11:52:18 +02:00
Mark Qvist 256a4d0b92 Cleanup 2026-05-14 11:48:44 +02:00
Mark Qvist c5add012c1 Updated readme 2026-05-14 11:46:39 +02:00
Mark Qvist 6ecc8933b4 Updated readme 2026-05-14 11:44:07 +02:00
Mark Qvist 42b5661979 Updated readme 2026-05-14 11:35:30 +02:00
Mark Qvist 6333fb39bf Updated readme 2026-05-14 10:45:48 +02:00
Mark Qvist ea27a8b8a7 Updated readme 2026-05-14 10:43:57 +02:00
Mark Qvist 358f9c3b0c Updated readme 2026-05-14 10:42:33 +02:00
Mark Qvist cb3ef69072 Updated readme 2026-05-14 10:33:36 +02:00
Mark Qvist eee9354657 Updated readme 2026-05-14 10:26:21 +02:00
Mark Qvist ff86a1d7e6 Updated readme 2026-05-14 10:19:53 +02:00
Mark Qvist e49f31322c Redirect blob to tree page if target is a tree 2026-05-14 10:07:03 +02:00
Mark Qvist 95502e2c21 Prepare release 2026-05-14 01:56:30 +02:00
Mark Qvist 3dd4145e62 Updated changelog 2026-05-14 01:53:33 +02:00
Mark Qvist 1d7ddc3f8a Implemented rngit work document signing 2026-05-14 01:51:22 +02:00
Mark Qvist d731b4396c Repo page rendering 2026-05-14 00:32:22 +02:00
Mark Qvist c186a1f6b0 Updated version 2026-05-14 00:16:33 +02:00
Mark Qvist a049ec8b7b Updated changelog 2026-05-14 00:16:28 +02:00
Mark Qvist 4c93f6c7f4 Added local URL resolution to repo frontpage markdown readme renderer 2026-05-13 23:41:07 +02:00
Mark Qvist 35c7a89b19 Fixed typo 2026-05-13 22:58:50 +02:00
Mark Qvist c86b9c9703 Fixed missing none check in interface discovery sanitizer thanks to PAzter1101 2026-05-13 10:34:58 +02:00
Mark Qvist 64ebdd0ee3 Cleanup 2026-05-13 01:19:51 +02:00
Mark Qvist 9179b914d5 Added embedded message signing, validation and viewing to rnid 2026-05-13 01:14:41 +02:00
Mark Qvist eb5d46b20b Added file decryption for multiple file path inputs and shell expansions to rnid 2026-05-12 23:20:28 +02:00
Mark Qvist 54c36f515b Added file encryption for multiple file path inputs and shell expansions to rnid 2026-05-12 23:14:01 +02:00
Mark Qvist 5c5668a4fc Added signature creation for multiple file path inputs and shell expansions to rnid 2026-05-12 23:09:50 +02:00
Mark Qvist eeefb60c89 Added signature validation of multiple file path inputs and shell expansions to rnid 2026-05-12 23:00:06 +02:00
Mark Qvist 018df10a26 Fixed rngit remote helper hanging on startup if no client config had been created previously, and RNS loglevel was configured at debug or higher 2026-05-12 22:21:53 +02:00
Mark Qvist 93ead77435 Added workdoc downloads 2026-05-12 21:47:10 +02:00
Mark Qvist bd0e1ad0ca Better workdoc page handling 2026-05-12 21:05:15 +02:00
Mark Qvist d0ceeacb37 Allow setting title on workdoc edit 2026-05-12 15:04:02 +02:00
Mark Qvist 7d5fb6a13f Cleanup 2026-05-11 23:31:25 +02:00
Mark Qvist 855ef7bfd1 Base256 encoding 2026-05-11 23:22:13 +02:00
Mark Qvist 323890021a Better remote monitor loop 2026-05-11 00:20:02 +02:00
Mark Qvist e004e7592b Added lock to interface discovery 2026-05-10 00:29:48 +02:00
Mark Qvist 0ebec014e5 Improved release page 2026-05-10 00:26:55 +02:00
Mark Qvist 1b624cc0e2 Updated manual 2026-05-09 19:20:38 +02:00
Mark Qvist e8d161c0d5 Yes, that was indeed a bit overkill 2026-05-09 19:17:38 +02:00
Mark Qvist e5c7dd7ec7 Prepare release 2026-05-09 18:59:29 +02:00
Mark Qvist 7d6ed59e6e Added hex/b32/b64 output to rnid rsg signature generator 2026-05-09 18:34:28 +02:00
Mark Qvist 11e4e7953a Consistency 2026-05-09 15:11:33 +02:00
Mark Qvist a5b292ee81 Dreaming of a universe without escape characters 2026-05-09 14:58:43 +02:00
Mark Qvist d619bafb8d People use tabs, I guess 2026-05-09 13:51:55 +02:00
Mark Qvist 0119a589dc Improved transport jobs error handling 2026-05-09 13:32:32 +02:00
Mark Qvist b7346bed4d Fixed announce processing edge case where path was cleaned while waiting for announce rebroadcast 2026-05-09 13:29:31 +02:00
Mark Qvist fcea57cb8e Added burst filter to rnstatus 2026-05-09 13:28:49 +02:00
Mark Qvist 8d8af5e60a Improved git command timeout logging 2026-05-09 12:51:28 +02:00
Mark Qvist 1a732ac1c1 Adjusted logging 2026-05-09 12:35:39 +02:00
Mark Qvist f827d945be Implemented path request ingress burst control and egress limiting 2026-05-09 04:43:22 +02:00
Mark Qvist e03c4ee455 Added path request burst control to manual 2026-05-09 03:21:09 +02:00
Mark Qvist 35e7ccb773 Fixed invalid handling of corrupted discovery file 2026-05-09 02:52:01 +02:00
Mark Qvist a932a10492 Inherit egress and PR burst settings from parent interface 2026-05-09 02:27:31 +02:00
Mark Qvist c5108c3a19 Added path request frequency sorting to rnstatus 2026-05-09 01:45:04 +02:00
Mark Qvist 767782e425 Cleanup 2026-05-09 01:27:22 +02:00
Mark Qvist 60c440a3b6 Transport logic for path request ingress and egress control 2026-05-09 01:14:40 +02:00
Mark Qvist 6551a25877 Cleanup 2026-05-09 01:10:49 +02:00
Mark Qvist 70db2c5369 Updated log levels 2026-05-09 01:08:19 +02:00
Mark Qvist 8ed31d0dc8 Added path request frequency monitoring support to interfaces subsystem 2026-05-09 00:51:44 +02:00
Mark Qvist ef1ecb35e1 Fixed formatting 2026-05-09 00:50:19 +02:00
Mark Qvist 6768f10631 Improved discovery persist error handling 2026-05-09 00:26:42 +02:00
Mark Qvist fee6a53473 Added path request frequency display to rnstatus 2026-05-09 00:05:39 +02:00
Mark Qvist bbfa3b0aa0 Use validation functions canonically from util 2026-05-08 20:03:48 +02:00
Mark Qvist 325ae654ef Template rendering sequence 2026-05-08 18:24:30 +02:00
Mark Qvist 8655a4fb37 Cleaned up error messages 2026-05-08 18:18:28 +02:00
Mark Qvist b30d272ee6 Ensure non-corrupting stats writes 2026-05-08 17:37:32 +02:00
Mark Qvist cc90ac2853 Fixed workdoc limit 2026-05-08 17:27:56 +02:00
Mark Qvist 55473f39cb Improved rngit error logging 2026-05-08 17:25:46 +02:00
Mark Qvist 6d73881b07 Ensure error return consistency 2026-05-08 17:08:27 +02:00
Mark Qvist d107cd4b42 Cleanup 2026-05-08 17:00:49 +02:00
Mark Qvist 33247e21b2 Added AutoInterface per-peer announce rate display to rnstatus 2026-05-08 16:48:50 +02:00
Mark Qvist 6bdc769af3 Ensure SHA validation is canonical 2026-05-08 16:22:21 +02:00
Mark Qvist e923ccbf1b Improved ref name validation in rngit 2026-05-08 16:07:16 +02:00
Mark Qvist d402ee33a2 Formatting and cleanup 2026-05-08 12:00:39 +02:00
Mark Qvist d8d420745f Removed programs from docs using non-verified/LLM-generated implementations of Reticulum 2026-05-08 11:33:51 +02:00
Mark Qvist 524f2068cd Fixed regression in link close handling in rnstatus and rnpath remote management 2026-05-08 02:47:43 +02:00
Mark Qvist 5db089ff19 Updated version 2026-05-08 02:15:28 +02:00
Mark Qvist 08d6780c73 Tuned default IC params. Show burst status in rnstatus. 2026-05-08 01:13:49 +02:00
Mark Qvist ca3f0bba6d Cleanup 2026-05-08 00:28:02 +02:00
Mark Qvist 830327e4a2 IC default config 2026-05-08 00:26:01 +02:00
Mark Qvist f96409dfa9 IC config stuff 2026-05-08 00:11:18 +02:00
Mark Qvist 18e2da7d2b Updated manual 2026-05-07 21:08:24 +02:00
Mark Qvist dfd046afb6 Fixed f-string for old snakes 2026-05-07 20:59:44 +02:00
Mark Qvist 63d7f1e295 Fixed page formatting 2026-05-07 20:49:20 +02:00
74 changed files with 9528 additions and 1534 deletions
+159
View File
@@ -1,3 +1,162 @@
### 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.
**Changes**
- Added full `rngit` documentation to the manual
- Added offline `.rsm` release manifest verification
- Added the ability to fetch release updates directly from `.rsm` manifests
- Added canonical `.rsm` release structure validator to `rnid` for import
- Added `.rsm` manifest saving when using `rngit release fetch`
- Added remote `HEAD` tracking for forks and mirros to `rngit`
- Improved known destinations persist reliability
- Improved page node ref link handling in `rngit`
- 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
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.
**Changes**
- Added signed release manifest generation to `rngit release`
- Added verified release fetching to `rngit release`
- Added automatic artifact signing to `rngit release`
- Added signed message creation from file to `rnid`
- Added signed message metadata output option to `rnid`
- Added `rsm` metadata embedding and spec validation to `rnid`
- Added identity and destination aliases to `rngit`
- Added blocked identities option to `rngit`
- Added ability to render raw micron in markdown files to `rngit`
- Added fork and mirror last sync time to repository page in `rngit`
- Better handling of silly links in `rngit`
- Fixed markdown table cell truncation not closing micron tags
- Fixed various minor bugs and inconsistencies in `rngit`
- 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
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.
**Changes**
- Added work document proposals functionality to `rngit`
- Added fork and mirroring support to `rngit`
- Added ability to create new repositories remotely to `rngit`
- Added latest release management to `rngit`
- Added download stats to `rngit`
- Improved shared instance RPC error handling
- Improved announce cache cleaning
- Improved `rngit` page node link handling
- Improved stats pages `rngit`
- Improved transfer completed feedback in `rncp`, thanks to **neutral**
- 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
This release adds further improvements to the `rnid` and `rngit` utilities, and includes several bugfixes and other improvements.
**Changes**
- Added embedded message signing, validation and viewing to `rnid`
- Added file encryption for multiple file path inputs and shell expansions to `rnid`
- Added file decryption for multiple file path inputs and shell expansions to `rnid`
- Added signature creation for multiple file path inputs and shell expansions to `rnid`
- Added signature validation of multiple file path inputs and shell expansions to `rnid`
- Added workdoc signing and validation to `rngit`
- Added ability to edit workdoc titles to `rngit`
- Added ability to download workdocs via the `nomadnet` interface to `rngit`
- Added local URL resolution to the `rngit` repository frontpage markdown readme renderer
- Improved `rnstatus` remote monitor loop
- Improved `rngit` workdoc page handling
- Improved `rngit` release page rendering
- Fixed missing none check in interface discovery sanitizer thanks to PAzter1101
- 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
**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
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.
People who have written (ahem... *prompted into existence*) strange applications, that believed sending 25 random path requests every 10 seconds to try and punch holes through announce limiting, will now most likely find any potential users of such applications complaining that they are losing the ability to resolve paths alltogether, which is (entirely) by design, of course. Seriously, don't do crap like that.
You can read more about how the new ingress and egress controls work in the updated manual sections, in the Interfaces chapter.
For all node ops out there, I'd recomment updating to this at some sort of semi-expedient, but of course not un-leisurely pace, so peace and order on the networks can be restored.
**Changes**
- Added path request ingress and egress control with sane defaults for transport nodes
- Added full configurability of ingress and egress controls per interface and for instance-wide defaults
- Significantly improved transport logic for path request and announce handling
- Added path request frequency display to `rnstatus`
- Added AutoInterface per-peer announe rate display to `rnstatus`
- Added abilit to filter interfaces by burst state to `rnstatus`
- Added hex/base32/base64 ASCII-wrapped output to `rnid` signature generator
- Tuned default ingress control parameters
- Fixed regression in link close handling in `rnstatus` and `rnpath` remote management handling
- Fixed invalid handling of corrupted interface discovery files
- Fixed announce processing edge case handling if path was cleaned while waiting for rebroadcast
- Improved `rngit` error logging
- Improved transport background jobs error handling
- Fixed various edge-cases and inconsistencies in markdown rendering in `rngit`
- Ensured canonical validation functions in `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
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!
+17 -4
View File
@@ -56,22 +56,35 @@ documentation:
manual:
make -C docs latexpdf epub
distcollect:
cp docs/Reticulum\ Manual.* dist
build_spkg: remove_symlinks build_sdist create_symlinks
release: test remove_symlinks build_sdist build_wheel build_pure_wheel documentation manual create_symlinks
release: test remove_symlinks build_sdist build_wheel build_pure_wheel documentation manual distcollect create_symlinks
debug: remove_symlinks build_wheel build_pure_wheel create_symlinks
upload: upload-rns upload-rnspure
local: release sign
upload-rns:
sign:
rngit release rns://7649a50d84610232d1416b41d2896aff/reticulum/reticulum create $$(python setup.py --getversion):dist --name rns --local
upload:
@echo Ready to publish release over Reticulum
@read VOID
rngit release rns://7649a50d84610232d1416b41d2896aff/reticulum/reticulum create $$(python setup.py --getversion):dist --name rns
upload-pip: upload-rns-pip upload-rnspure-pip
upload-rns-pip:
@echo Ready to publish rns release, hit enter to continue
@read VOID
@echo Uploading to PyPi...
twine upload dist/rns-*.whl dist/rns-*.tar.gz
@echo Release published
upload-rnspure:
upload-rnspure-pip:
@echo Ready to publish rnspure release, hit enter to continue
@read VOID
@echo Uploading to PyPi...
+73 -67
View File
@@ -1,22 +1,22 @@
>> Reticulum Network Stack
To understand the foundational philosophy and goals of this system, read the `!`[Zen of Reticulum`:/page/blob.mu`g=reticulum|r=reticulum|ref=HEAD|path=Zen+of+Reticulum.md]`!.
To understand the foundational philosophy and goals of this system, read the `_`!`[Zen of Reticulum`:/page/blob.mu`g=reticulum|r=reticulum|ref=HEAD|path=Zen+of+Reticulum.md]`!`_.
Reticulum is the cryptography-based networking stack for building local and wide-area networks with readily available hardware. It can operate even with very high latency and extremely low bandwidth. Reticulum allows you to build wide-area networks with off-the-shelf tools, and offers end-to-end encryption and connectivity, initiator anonymity, autoconfiguring cryptographically backed multi-hop transport, efficient addressing, unforgeable delivery acknowledgements and more.
The vision of Reticulum is to allow anyone to be their own network operator, and to make it cheap and easy to cover vast areas with a myriad of independent, inter-connectable and autonomous networks. Reticulum **is not** *one* network. It is **a tool** for building *thousands of networks*. Networks without kill-switches, surveillance, censorship and control. Networks that can freely interoperate, associate and disassociate with each other, and require no central oversight. Networks for human beings. *Networks for the people*.
The vision of Reticulum is to allow anyone to be their own network operator, and to make it cheap and easy to cover vast areas with a myriad of independent, inter-connectable and autonomous networks. Reticulum `!is not`! `*one`* network. It is `!a tool`! for building `*thousands of networks`*. Networks without kill-switches, surveillance, censorship and control. Networks that can freely interoperate, associate and disassociate with each other, and require no central oversight. Networks for human beings. `*Networks for the people`*.
Reticulum is a complete networking stack, and does not rely on IP or higher layers, but it is possible to use IP as the underlying carrier for Reticulum. It is therefore trivial to tunnel Reticulum over the Internet or private IP networks.
Having no dependencies on traditional networking stacks frees up overhead that has been used to implement a networking stack built directly on cryptographic principles, allowing resilience and stable functionality, even in open and trustless networks.
No kernel modules or drivers are required. Reticulum runs completely in userland, and can run on practically any system that runs Python 3.
No kernel modules or drivers are required. Reticulum runs completely in userland, and can run on practically any system that runs Python.
>> Read The Manual
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/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]`!`_.
>> Notable Features
@@ -66,33 +66,31 @@ The Reticulum Protocol was dedicated to the Public Domain in 2016.
>> Examples of Reticulum Applications
If you want to quickly get an idea of what Reticulum can do, take a look at the [Programs Using Reticulum](https://reticulum.network/manual/software.html) section of the manual, or the following resources:
If you want to quickly get an idea of what Reticulum can do, take a look at the `_`!`[Programs Using Reticulum`:/page/blob.mu`g=reticulum|r=reticulum|ref=HEAD|path=docs/markdown/software.md]`!`_ section of the manual, or the following resources:
[LXMF](https://github.com/markqvist/lxmf) is a distributed, delay and disruption tolerant message transfer
protocol built on Reticulum
`_`!`[LXMF`a8d24177d946de4f1f0a0fe1af9a1338:/page/repo.mu`g=reticulum|r=lxmf]`!`_ is a distributed, delay and disruption tolerant message transfer protocol built on Reticulum
• The [LXST](https://github.com/markqvist/lxst) protocol and framework provides real-time audio and signals
transport over Reticulum. It includes primitives and utilities for building voice-based applications and
hardware devices, such as the `B333rnphone`b program, that can be used to build hardware telephones.
• The `_`!`[LXST`a8d24177d946de4f1f0a0fe1af9a1338:/page/repo.mu`g=reticulum|r=lxst]`!`_ protocol and framework provides real-time audio and signals transport over Reticulum. It
includes primitives and utilities for building voice-based applications and hardware devices,
such as the `B333rnphone`b program, that can be used to build hardware telephones.
• For an off-grid, encrypted and resilient mesh communications platform, see [Nomad Network](https://github.com/markqvist/NomadNet)
• For an off-grid, encrypted and resilient mesh communications platform, see `_`!`[Nomad Network`a8d24177d946de4f1f0a0fe1af9a1338:/page/repo.mu`g=reticulum|r=nomadnet]`!`_.
• The Android, Linux, macOS and Windows app [Sideband](https://github.com/markqvist/Sideband) has a graphical
interface and many advanced features, such as file transfers, image and voice messages, real-time voice calls,
a distributed telemetry system, mapping capabilities and full plugin extensibility.
• The Android, Linux, macOS and Windows app `_`!`[Sideband`a8d24177d946de4f1f0a0fe1af9a1338:/page/repo.mu`g=reticulum|r=sideband]`!`_ has a graphical interface and many advanced
features, such as file transfers, image and voice messages, real-time voice calls, a distributed
telemetry system, mapping capabilities and full plugin extensibility.
• [MeshChatX](https://git.quad4.io/RNS-Things/MeshChatX) is a full-featured LXMF client with many built-in tools
and functionalities, that also supports image and voice messages, file transfers and voice calls. It also
includes a built-in page browser for browsing Nomad Network nodes.
`_`!`[MeshChatX`c10d80b1a42fa958c37a6cc30dc04f53]`!`_ (`_`!`[source`5399f5a0212477618821e91e88ce053b:/page/repo.mu`g=quad4|r=MeshChatX]`_`!) is a full-featured LXMF client with many built-in tools and functionalities,
that also supports image and voice messages, file transfers and voice calls. It also includes a
built-in page browser for browsing Nomad Network nodes.
• You can use the included [rnsh](https://reticulum.network/manual/using.html#the-rnsh-utility) program to
establish remote shell sessions over Reticulum.
• You can use the included `_`!`[rnsh`:/page/blob.mu`g=reticulum|r=reticulum|ref=HEAD|path=docs/markdown/using.md|anchor=the-rnsh-utility]`!`_ program to establish remote shell sessions over Reticulum.
>> Where can Reticulum be used?
Over practically any medium that can support at least a half-duplex channel with greater throughput than 5 bits per second, and an MTU of 500 bytes. Data radios, modems, LoRa radios, serial lines, AX.25 TNCs, amateur radio digital modes, WiFi and Ethernet devices, free-space optical links, and similar systems are all examples of the types of physical devices Reticulum can use.
An open-source LoRa-based interface called [RNode](https://markqvist.github.io/Reticulum/manual/hardware.html#rnode) has been designed specifically for use with Reticulum. It is possible to build yourself, or it can be purchased as a complete transceiver that just needs a USB connection to the host.
An open-source LoRa-based interface called `_`!`[RNode`:/page/blob.mu`g=reticulum|r=reticulum|ref=HEAD|path=docs/markdown/hardware.md|anchor=rnode]`!`_ has been designed specifically for use with Reticulum. It is possible to build yourself, or it can be purchased as a complete transceiver that just needs a USB connection to the host.
Reticulum can also be encapsulated over existing IP networks, so there's nothing stopping you from using it over wired Ethernet, your local WiFi network or the Internet, where it'll work just as well. In fact, one of the strengths of Reticulum is how easily it allows you to connect different mediums into a self-configuring, resilient and encrypted mesh, using any available mixture of available infrastructure.
@@ -100,9 +98,9 @@ As an example, it's possible to set up a Raspberry Pi connected to both a LoRa r
>> How do I get started?
The best way to get started with the Reticulum Network Stack depends on what you want to do. For full details and examples, have a look at the [Getting Started Fast](https://markqvist.github.io/Reticulum/manual/gettingstartedfast.html) section of the [Reticulum Manual](https://markqvist.github.io/Reticulum/manual/).
The best way to get started with the Reticulum Network Stack depends on what you want to do. For full details and examples, have a look at the `_`!`[Getting Started Fast`:/page/blob.mu`g=reticulum|r=reticulum|ref=HEAD|path=docs/markdown/gettingstartedfast.md]`!`_ section of the `_`!`[Reticulum Manual`:/page/blob.mu`g=reticulum|r=reticulum|ref=HEAD|path=docs/markdown/index.md]`!`_.
To simply install Reticulum and related utilities on your system, the easiest way is via `B333pip`b. You can then start any program that uses Reticulum, or start Reticulum as a system service with [the rnsd utility](https://markqvist.github.io/Reticulum/manual/using.html#the-rnsd-utility).
To simply install Reticulum and related utilities on your system, the easiest way is via `B333pip`b. You can then start any program that uses Reticulum, or start Reticulum as a system service with `_`!`[the rnsd utility`:/page/blob.mu`g=reticulum|r=reticulum|ref=HEAD|path=docs/markdown/using.md|anchor=the-rnsd-utility]`!`_.
`B333
`=
@@ -131,10 +129,11 @@ When first started, Reticulum will create a default configuration file, providin
If you have an old version of `B333pip`b on your system, you may need to upgrade it first with `B333pip install pip --upgrade`b. If you no not already have `B333pip`b installed, you can install it using the package manager of your system with `B333sudo apt install python3-pip`b or similar.
For more detailed examples on how to expand communication over many mediums such as packet radio or LoRa, serial ports, or over fast IP links and the Internet using the UDP and TCP interfaces, take a look at the [Supported Interfaces](https://markqvist.github.io/Reticulum/manual/interfaces.html) section of the [Reticulum Manual](https://markqvist.github.io/Reticulum/manual/).
For more detailed examples on how to expand communication over many mediums such as packet radio or LoRa, serial ports, or over fast IP links and the Internet using the UDP and TCP interfaces, take a look at the `_`!`[Supported Interfaces`:/page/blob.mu`g=reticulum|r=reticulum|ref=HEAD|path=docs/markdown/interfaces.md]`!`_ section of the `_`!`[Reticulum Manual`:/page/blob.mu`g=reticulum|r=reticulum|ref=HEAD|path=docs/markdown/index.md]`!`_.
>> Included Utilities
Reticulum includes a range of useful utilities for managing your networks, viewing status and information, and other tasks. You can read more about these programs in the [Included Utility Programs](https://markqvist.github.io/Reticulum/manual/using.html#included-utility-programs) section of the [Reticulum Manual](https://markqvist.github.io/Reticulum/manual/).
Reticulum includes a range of useful utilities for managing your networks, viewing status and information, and other tasks. You can read more about these programs in the `_`!`[Included Utility Programs`:/page/blob.mu`g=reticulum|r=reticulum|ref=HEAD|path=docs/markdown/using.md|anchor=included-utility-programs]`!`_ section of the `_`!`[Reticulum Manual`:/page/blob.mu`g=reticulum|r=reticulum|ref=HEAD|path=docs/markdown/index.md]`!`_.
• The system daemon `B333rnsd`b for running Reticulum as an always-available service
• An interface status utility called `B333rnstatus`b, that displays information about interfaces
@@ -147,16 +146,14 @@ Reticulum includes a range of useful utilities for managing your networks, viewi
• The `B333rngit`b program provides a full multi-repository Git node for serving repositories over Reticulum
• The included `B333git-remote-rns`b helper allows you to interact with Git repositories over Reticulum
All tools, including `B333rnx`b and `B333rncp`b, work reliably and well even over very low-bandwidth links like LoRa or Packet Radio. For full-featured remote shells over Reticulum, also have a look at the [rnsh](https://github.com/acehoss/rnsh) program.
>> Supported interface types and devices
Reticulum implements a range of generalised interface types that covers most of the communications hardware that Reticulum can run over. If your hardware is not supported, it's [simple to implement a custom interface module](https://markqvist.github.io/Reticulum/manual/interfaces.html#custom-interfaces).
Reticulum implements a range of generalised interface types that covers most of the communications hardware that Reticulum can run over. If your hardware is not supported, it's `_`!`[simple to implement a custom interface module`:/page/blob.mu`g=reticulum|r=reticulum|ref=HEAD|path=docs/markdown/interfaces.md|anchor=custom-interfaces]`!`_.
Currently, the following built-in interfaces are supported:
• Any Ethernet device
• LoRa using [RNode](https://unsigned.io/rnode/)
• LoRa using `_`!`[RNode`a8d24177d946de4f1f0a0fe1af9a1338:/page/repo.mu`g=reticulum|r=rnode_firmware]`!`_
• Packet Radio TNCs (with or without AX.25)
• KISS-compatible hardware and software modems
• Any device with a serial port
@@ -166,64 +163,72 @@ Currently, the following built-in interfaces are supported:
• Custom hardware via stdio or pipes
>> Performance
Reticulum targets a *very* wide usable performance envelope, but prioritises functionality and performance on low-bandwidth mediums. The goal is to provide a dynamic performance envelope from 250 bits per second, to 1 gigabit per second on normal hardware.
Reticulum targets a `*very`* wide usable performance envelope, but prioritises functionality and performance on low-bandwidth mediums. The goal is to provide a dynamic performance envelope from 250 bits per second, to 1 gigabit per second on normal hardware.
Currently, the usable performance envelope is approximately 150 bits per second to 500 megabits per second, with physical mediums faster than that not being saturated. Performance beyond the current level is intended for future upgrades, but not highly prioritised at this point in time.
>> Current Status
All core protocol features are implemented and functioning, but additions will probably occur as real-world use is explored and understood. The API and wire-format can be considered stable.
>> Dependencies
The installation of the default `B333rns`b package requires only two external dependencies, listed below. Almost all systems and distributions have readily available packages for these dependencies, and when the `B333rns`b package is installed with `B333pip`b, they will be downloaded and installed as well.
[PyCA/cryptography](https://github.com/pyca/cryptography)
[pyserial](https://github.com/pyserial/pyserial)
• PyCA/cryptography
• pyserial
On more unusual systems, and in some rare cases, it might not be possible to install or even compile one or more of the above modules. In such situations, you can use the `B333rnspure`b package instead, which require no external dependencies for installation. Please note that the contents of the `B333rns`b and `B333rnspure`b packages are *identical*. The only difference is that the `B333rnspure`b package lists no dependencies required for installation.
On more unusual systems, and in some rare cases, it might not be possible to install or even compile one or more of the above modules. In such situations, you can use the `B333rnspure`b package instead, which require no external dependencies for installation. Please note that the contents of the `B333rns`b and `B333rnspure`b packages are `*identical`*. The only difference is that the `B333rnspure`b package lists no dependencies required for installation.
No matter how Reticulum is installed and started, it will load external dependencies only if they are *needed* and *available*. If for example you want to use Reticulum on a system that cannot support [pyserial](https://github.com/pyserial/pyserial), it is perfectly possible to do so using the `B333rnspure`b package, but Reticulum will not be able to use serial-based interfaces. All other available modules will still be loaded when needed.
No matter how Reticulum is installed and started, it will load external dependencies only if they are `*needed`* and `*available`*. If for example you want to use Reticulum on a system that cannot support `B333pyserial`b, it is perfectly possible to do so using the `B333rnspure`b package, but Reticulum will not be able to use serial-based interfaces. All other available modules will still be loaded when needed.
**Please Note!** If you use the `B333rnspure`b package to run Reticulum on systems that do not support [PyCA/cryptography](https://github.com/pyca/cryptography), it is important that you read and understand the [Cryptographic Primitives](#cryptographic-primitives) section of this document.
`!Please Note!`! If you use the `B333rnspure`b package to run Reticulum on systems that do not support PyCA/cryptography, it is important that you read and understand the `!Cryptographic Primitives`! section of this document.
>> Bootstrapping Connectivity
Reticulum is not a service you subscribe to, nor is it a single global network you "join". Reticulum provides functionality for discovering available public interfaces over the network itself, and the broader community has provided various directories of publicly available entrypoints to bootstrap connectivity.
To learn how to establish initial connectivity over Reticulum, read the [Bootstrapping Connectivity](https://reticulum.network/manual/gettingstartedfast.html#bootstrapping-connectivity) section of the manual.
To learn how to establish initial connectivity over Reticulum, read the `_`!`[Bootstrapping Connectivity`:/page/blob.mu`g=reticulum|r=reticulum|ref=HEAD|path=docs/markdown/gettingstartedfast.md|anchor=bootstrapping-connectivity]`!`_ section of the manual.
If you already have a general idea of how this works, you can use community-run sites such as [directory.rns.recipes](https://directory.rns.recipes/) and [rmap.world](https://rmap.world) to find interface definitions for initial connectivity to the global distributed Reticulum backbone.
If you already have a general idea of how this works, you can use community-run sites such as `_`!`[rns.recipes`9ce92808be498e9e05590ff27cbfdfe4]`!`_ and `_`!`[rmap.world`a4a5e861626ce97c9aa544d9ecdf6d22]`!`_ to find interface definitions for initial connectivity to the global distributed Reticulum backbone.
>> Public Testnet
***Important!** Historically, a developer-targeted testnet was made available by the Reticulum project itself. As the amount of global Reticulum nodes and entrypoints have grown to a substantial quantity, this public testnet, including the Amsterdam Testnet entrypoint, has now been decommissioned. If your still have instances that relied on this entrypoint for connectivity, transition to using the distributed backbone instead. Reticulum now includes a full on-network interface discovery and connectivity bootstrapping system. Read the [Bootstrapping Connectivity](https://reticulum.network/manual/gettingstartedfast.html#bootstrapping-connectivity) section of the manual for pointers.*
`!`*Important!`! Historically, a developer-targeted testnet was made available by the Reticulum project itself. As the amount of global Reticulum nodes and entrypoints have grown to a substantial quantity, this public testnet, including the Amsterdam Testnet entrypoint, has now been decommissioned. If you still have instances that relied on this entrypoint for connectivity, transition to using the distributed backbone instead. Reticulum now includes a full on-network interface discovery and connectivity bootstrapping system. Read the `_`[Bootstrapping Connectivity`:/page/blob.mu`g=reticulum|r=reticulum|ref=HEAD|path=docs/markdown/gettingstartedfast.md|anchor=bootstrapping-connectivity]`_ section of the manual for pointers.`*
>> Support Reticulum
For this to be possible, I need your help. Please support the continued development of open, free and private communications systems by donating via one of the following channels:
• Monero:
`!Monero`!
84FpY1QbxHcgdseePYNmhTHcrgMX4nFfBYtz2GKYToqHVVhJp8Eaw1Z1EedRnKD19b3B8NiLCGVxzKV17UMmmeEsCrPyA5w
• Bitcoin
`!Bitcoin`!
bc1pgqgu8h8xvj4jtafslq396v7ju7hkgymyrzyqft4llfslz5vp99psqfk3a6
• Ethereum
`!Ethereum`!
0x91C421DdfB8a30a49A71d63447ddb54cEBe3465E
• Liberapay: https://liberapay.com/Reticulum/
`!Liberapay`!
`[https://liberapay.com/Reticulum/]
• Ko-Fi: https://ko-fi.com/markqvist
`!Ko-Fi`!
`[https://ko-fi.com/markqvist]
>> Cryptographic Primitives
Reticulum uses a simple suite of efficient, strong and well-tested cryptographic primitives, with widely available implementations that can be used both on general-purpose CPUs and on microcontrollers.
One of the primary considerations for choosing this particular set of primitives is that they can be implemented *safely* with relatively few pitfalls, on practically all current computing platforms.
One of the primary considerations for choosing this particular set of primitives is that they can be implemented `*safely`* with relatively few pitfalls, on practically all current computing platforms.
The primitives listed here **are authoritative**. Anything claiming to be Reticulum, but not using these exact primitives **is not** Reticulum, and possibly an intentionally compromised or weakened clone. The utilised primitives are:
The primitives listed here `!are authoritative`!. Anything `*claiming`* to be Reticulum, but not using these exact primitives `FTA35050`!is not`!`f Reticulum, and possibly an intentionally compromised or weakened clone. The utilised primitives are:
• Reticulum Identity Keys are 512-bit Curve25519 keysets
• A 256-bit Ed25519 key for signatures
• A 256-bit X22519 key for ECDH key exchanges
• HKDF for key derivation
• Encrypted tokens are based on the [Fernet spec](https://github.com/fernet/spec/)
• Encrypted tokens are based on the `_`!`[Fernet spec`https://github.com/fernet/spec/]`!`_
• Ephemeral keys derived from an ECDH key exchange on Curve25519
• HMAC using SHA256 for message authentication
• IVs must be generated through `B333os.urandom()`b or better
@@ -232,36 +237,37 @@ The primitives listed here **are authoritative**. Anything claiming to be Reticu
• SHA-256
• SHA-512
In the default installation configuration, the `B333X25519`b, `B333Ed25519`b, and `B333AES-256-CBC`b primitives are provided by [OpenSSL](https://www.openssl.org/) (via the [PyCA/cryptography](https://github.com/pyca/cryptography) package). The hashing functions `B333SHA-256`b and `B333SHA-512`b are provided by the standard Python [hashlib](https://docs.python.org/3/library/hashlib.html). The `B333HKDF`b, `B333HMAC`b, `B333Token`b primitives, and the `B333PKCS7`b padding function are always provided by the following internal implementations:
In the default installation configuration, the `B333X25519`b, `B333Ed25519`b, and `B333AES-256-CBC`b primitives are provided by `_`!`[OpenSSL`https://www.openssl.org/]`!`_ (via the `_`!`[PyCA/cryptography`https://github.com/pyca/cryptography]`!`_ package). The hashing functions `B333SHA-256`b and `B333SHA-512`b are provided by the standard Python `_`!`[hashlib`https://docs.python.org/3/library/hashlib.html]`!`_. The `B333HKDF`b, `B333HMAC`b, `B333Token`b primitives, and the `B333PKCS7`b padding function are always provided by the following internal implementations:
• [HKDF.py](RNS/Cryptography/HKDF.py)
• [HMAC.py](RNS/Cryptography/HMAC.py)
• [Token.py](RNS/Cryptography/Token.py)
• [PKCS7.py](RNS/Cryptography/PKCS7.py)
`_`!`[HKDF.py`:/page/blob.mu`g=reticulum|r=reticulum|ref=HEAD|path=RNS/Cryptography/HKDF.py]`!`_
`_`!`[HMAC.py`:/page/blob.mu`g=reticulum|r=reticulum|ref=HEAD|path=RNS/Cryptography/HMAC.py]`!`_
`_`!`[Token.py`:/page/blob.mu`g=reticulum|r=reticulum|ref=HEAD|path=RNS/Cryptography/Token.py]`!`_
`_`!`[PKCS7.py`:/page/blob.mu`g=reticulum|r=reticulum|ref=HEAD|path=RNS/Cryptography/PKCS7.py]`!`_
Reticulum also includes a complete implementation of all necessary primitives in pure Python. If OpenSSL and PyCA are not available on the system when Reticulum is started, Reticulum will instead use the internal pure-python primitives. A trivial consequence of this is performance, with the OpenSSL backend being *much* faster. The most important consequence however, is the potential loss of security by using primitives that has not seen the same amount of scrutiny, testing and review as those from OpenSSL.
Reticulum also includes a complete implementation of all necessary primitives in pure Python. If OpenSSL and PyCA are not available on the system when Reticulum is started, Reticulum will instead use the internal pure-python primitives. A trivial consequence of this is performance, with the OpenSSL backend being `*much`* faster. The most important consequence however, is the potential loss of security by using primitives that has not seen the same amount of scrutiny, testing and review as those from OpenSSL.
Please note that by default, installing Reticulum will **require** OpenSSL and PyCA to also be automatically installed if not already available. It is only possible to use the pure-python primitives if this requirement is specifically overridden by the user, for example by installing the `B333rnspure`b package instead of the normal `B333rns`b package, or by running directly from local source-code.
Please note that by default, installing Reticulum will `!require`! OpenSSL and PyCA to also be automatically installed if not already available. It is only possible to use the pure-python primitives if this requirement is specifically overridden by the user, for example by installing the `B333rnspure`b package instead of the normal `B333rns`b package, or by running directly from local source-code.
If you want to use the internal pure-python primitives, it is **highly advisable** that you have a good understanding of the risks that this pose, and make an informed decision on whether those risks are acceptable to you.
If you want to use the internal pure-python primitives, it is `!highly advisable`! that you have a good understanding of the risks that this pose, and make an informed decision on whether those risks are acceptable to you.
Reticulum is relatively young software, and should be considered as such. While it has been built with cryptography best-practices very foremost in mind, it _has not_ been externally security audited, and there could very well be privacy or security breaking bugs. If you want to help out, or help sponsor an audit, please do get in touch.
>> Acknowledgements & Credits
Reticulum can only exist because of the mountain of Open Source work it was built on top of, the contributions of everyone involved, and everyone that has supported the project through the years. To everyone who has helped, thank you so much.
A number of other modules and projects are either part of, or used by Reticulum. Sincere thanks to the authors and contributors of the following projects:
• [PyCA/cryptography](https://github.com/pyca/cryptography), *BSD License*
• [Pure-25519](https://github.com/warner/python-pure25519) by [Brian Warner](https://github.com/warner), *MIT License*
• [Pysha2](https://github.com/thomdixon/pysha2) by [Thom Dixon](https://github.com/thomdixon), *MIT License*
• [Python AES-128](https://github.com/orgurar/python-aes) by [Or Gur Arie](https://github.com/orgurar), *MIT License*
• [Python AES-256](https://github.com/boppreh/aes) by [BoppreH](https://github.com/boppreh), *MIT License*
• [Curve25519.py](https://gist.github.com/nickovs/cc3c22d15f239a2640c185035c06f8a3#file-curve25519-py) by [Nicko van Someren](https://gist.github.com/nickovs), *Public Domain*
• [I2Plib](https://github.com/l-n-s/i2plib) by [Viktor Villainov](https://github.com/l-n-s)
• [PySerial](https://github.com/pyserial/pyserial) by Chris Liechti, *BSD License*
• [Configobj](https://github.com/DiffSK/configobj) by Michael Foord, Nicola Larosa, Rob Dennis & Eli Courtwright, *BSD License*
• [ifaddr](https://github.com/pydron/ifaddr) by Stefan C. Mueller, *MIT License*
• [Umsgpack.py](https://github.com/vsergeev/u-msgpack-python) by [Ivan A. Sergeev](https://github.com/vsergeev)
• [rnsh](https://github.com/acehoss/rnsh) by [Aaron Heise](https://github.com/acehoss)
• [Python](https://www.python.org)
`_`!`[PyCA/cryptography`https://github.com/pyca/cryptography]`!`_, `*BSD License`*
`_`!`[Pure-25519`https://github.com/warner/python-pure25519]`!`_, by `_`!`[Brian Warner`https://github.com/warner]`!`_, `*MIT License`*
`_`!`[Pysha2`https://github.com/thomdixon/pysha2]`!`_ by `_`!`[Thom Dixon`https://github.com/thomdixon]`!`_, `*MIT License`*
`_`!`[Python AES-128`https://github.com/orgurar/python-aes]`!`_ by `_`!`[Or Gur Arie`https://github.com/orgurar]`!`_, `*MIT License`*
`_`!`[Python AES-256`https://github.com/boppreh/aes]`!`_ by `_`!`[BoppreH`https://github.com/boppreh]`!`_, `*MIT License`*
`_`!`[Curve25519.py`https://gist.github.com/nickovs/cc3c22d15f239a2640c185035c06f8a3]`!`_ by `_`!`[Nicko van Someren`https://gist.github.com/nickovs]`!`_, `*Public Domain`*
`_`!`[I2Plib`https://github.com/l-n-s/i2plib]`!`_ by `_`!`[Viktor Villainov`https://github.com/l-n-s]`!`_
`_`!`[PySerial`https://github.com/pyserial/pyserial]`!`_ by Chris Liechti, `*BSD License`*
`_`!`[Configobj`https://github.com/DiffSK/configobj]`!`_ by Michael Foord, Nicola Larosa, Rob Dennis & Eli Courtwright, `*BSD License`*
`_`!`[ifaddr`https://github.com/pydron/ifaddr]`!`_ by Stefan C. Mueller, `*MIT License`*
`_`!`[Umsgpack.py`https://github.com/vsergeev/u-msgpack-python]`!`_ by `_`!`[Ivan A. Sergeev`https://github.com/vsergeev]`!`_
`_`!`[rnsh`https://github.com/acehoss/rnsh]`!`_ by `_`!`[Aaron Heise`https://github.com/acehoss]`!`_
`_`!`[Python`https://www.python.org]`!`_
+48 -37
View File
@@ -6,6 +6,7 @@ import random
import threading
import ipaddress
import subprocess
from threading import Lock
from .vendor import umsgpack as msgpack
NAME = 0xFF
@@ -86,6 +87,7 @@ class InterfaceAnnouncer():
RNS.trace_exception(e)
def sanitize(self, in_str):
if in_str == None: return None
sanitized = in_str.replace("\n", "")
sanitized = sanitized.replace("\r", "")
sanitized = sanitized.strip()
@@ -374,6 +376,8 @@ class InterfaceDiscovery():
AUTOCONNECT_TYPES = ["BackboneInterface", "TCPServerInterface"]
DISCOVERABLE_TYPES = ["BackboneInterface", "TCPServerInterface", "I2PInterface", "RNodeInterface", "WeaveInterface", "KISSInterface"]
discovery_lock = Lock()
def __init__(self, required_value=InterfaceAnnouncer.DEFAULT_STAMP_VALUE, callback=None, discover_interfaces=True):
if not required_value: required_value = InterfaceAnnouncer.DEFAULT_STAMP_VALUE
@@ -401,8 +405,10 @@ class InterfaceDiscovery():
discovery_sources = RNS.Reticulum.interface_discovery_sources()
for filename in os.listdir(self.storagepath):
try:
filepath = os.path.join(self.storagepath, filename)
with open(filepath, "rb") as f: info = msgpack.unpackb(f.read())
with self.discovery_lock:
filepath = os.path.join(self.storagepath, filename)
with open(filepath, "rb") as f: info = msgpack.unpackb(f.read())
should_remove = False
heard_delta = now-info["last_heard"]
info["name"] = InterfaceAnnounceHandler.sanitize_name(info["name"])
@@ -434,8 +440,8 @@ class InterfaceDiscovery():
if should_append: discovered_interfaces.append(info)
except Exception as e:
RNS.log(f"Error while loading discovered interface data: {e}", RNS.LOG_ERROR)
RNS.log(f"The interface data file {os.path.join(self.storagepath, filename)} may be corrupt", RNS.LOG_ERROR)
RNS.log(f"Error while loading discovered interface data: {e}", RNS.LOG_WARNING)
RNS.log(f"The interface data file {os.path.join(self.storagepath, filename)} may be corrupt", RNS.LOG_WARNING)
RNS.trace_exception(e)
discovered_interfaces.sort(key=lambda info: (info["status_code"], info["value"], info["last_heard"]), reverse=True)
@@ -453,41 +459,45 @@ class InterfaceDiscovery():
filename = RNS.hexrep(discovery_hash, delimit=False)
filepath = os.path.join(self.storagepath, filename)
RNS.log(f"Discovered {interface_type} {hops} hop{ms} away with stamp value {value}: {name}", RNS.LOG_DEBUG)
if not os.path.isfile(filepath):
try:
with open(filepath, "wb") as f:
info["discovered"] = info["received"]
info["last_heard"] = info["received"]
info["heard_count"] = 0
f.write(msgpack.packb(info))
except Exception as e:
RNS.log(f"Error while persisting discovered interface data: {e}", RNS.LOG_ERROR)
RNS.trace_exception(e)
return
with self.discovery_lock:
if not os.path.isfile(filepath):
try:
with open(filepath, "wb") as f:
info["discovered"] = info["received"]
info["last_heard"] = info["received"]
info["heard_count"] = 0
f.write(msgpack.packb(info))
except Exception as e:
RNS.log(f"Error while persisting discovered interface data: {e}", RNS.LOG_ERROR)
RNS.trace_exception(e)
return
else:
discovered = None
heard_count = None
try:
with open(filepath, "rb") as f:
last_info = msgpack.unpackb(f.read())
discovered = last_info["discovered"]
heard_count = last_info["heard_count"]
else:
discovered = None
heard_count = None
try:
try:
with open(filepath, "rb") as f:
last_info = msgpack.unpackb(f.read())
discovered = last_info["discovered"]
heard_count = last_info["heard_count"]
if discovered == None: discovered = info["discovered"]
if heard_count == None: heard_count = 0
except Exception as e: RNS.log(f"Error while reading existing data for discovered interface, re-creating data", RNS.LOG_ERROR)
with open(filepath, "wb") as f:
info["discovered"] = discovered
info["last_heard"] = info["received"]
info["heard_count"] = heard_count+1
f.write(msgpack.packb(info))
if discovered == None: discovered = info["received"]
if heard_count == None: heard_count = 0
except Exception as e:
RNS.log(f"Error while persisting discovered interface data: {e}", RNS.LOG_ERROR)
RNS.trace_exception(e)
return
with open(filepath, "wb") as f:
info["discovered"] = discovered
info["last_heard"] = info["received"]
info["heard_count"] = heard_count+1
f.write(msgpack.packb(info))
except Exception as e:
RNS.log(f"Error while persisting discovered interface data: {e}", RNS.LOG_ERROR)
RNS.trace_exception(e)
return
except Exception as e:
RNS.log(f"Error processing discovered interface data: {e}", RNS.LOG_ERROR)
@@ -566,7 +576,7 @@ class InterfaceDiscovery():
def teardown_interface(self, interface):
interface.detach()
if interface in RNS.Transport.interfaces: RNS.Transport.interfaces.remove(interface)
RNS.Transport.remove_interface(interface)
if interface in self.monitored_interfaces: self.monitored_interfaces.remove(interface)
def autoconnect_count(self):
@@ -658,10 +668,11 @@ class InterfaceDiscovery():
RNS.log(f"Auto-connecting discovered {interface_type} {interface_name}")
interface.autoconnect_hash = endpoint_hash
interface.autoconnect_source = info["network_id"]
mode = RNS.Interfaces.Interface.Interface.MODE_GATEWAY if RNS.Reticulum.transport_enabled() else None
ar_target = RNS.Reticulum.get_instance()._default_ar_target() if RNS.Reticulum.transport_enabled() else None
ar_penalty = RNS.Reticulum.get_instance()._default_ar_penalty() if RNS.Reticulum.transport_enabled() else None
ar_grace = RNS.Reticulum.get_instance()._default_ar_grace() if RNS.Reticulum.transport_enabled() else None
RNS.Reticulum.get_instance()._add_interface(interface, ifac_netname=ifac_netname, ifac_netkey=ifac_netkey, configured_bitrate=5E6,
RNS.Reticulum.get_instance()._add_interface(interface, mode=mode, ifac_netname=ifac_netname, ifac_netkey=ifac_netkey, configured_bitrate=5E6,
announce_rate_target=ar_target, announce_rate_grace=ar_grace, announce_rate_penalty=ar_penalty)
self.monitor_interface(interface)
+11 -2
View File
@@ -212,8 +212,17 @@ class Identity:
RNS.log("Skipped recombining known destinations from disk, since an error occurred: "+str(e), RNS.LOG_WARNING)
RNS.log("Saving "+str(len(Identity.known_destinations))+" known destinations to storage...", RNS.LOG_VERBOSE)
with open(RNS.Reticulum.storagepath+"/known_destinations","wb") as file:
umsgpack.dump(Identity.known_destinations.copy(), file)
temp_file = RNS.Reticulum.storagepath+f"/known_destinations.tmp.{time.time()}"
try:
with open(temp_file,"wb") as file: umsgpack.dump(Identity.known_destinations.copy(), file)
os.rename(temp_file, RNS.Reticulum.storagepath+f"/known_destinations")
except Exception as e:
RNS.log(f"Error while serializing and writing known destinations: {e}", RNS.LOG_ERROR)
try: os.unlink(temp_file)
except Exception as e: RNS.log(f"Could not clean up temporary file {temp_file}: {e}", RNS.LOG_WARNING)
raise e
save_time = time.time() - save_start
if save_time < 1: time_str = str(round(save_time*1000,2))+"ms"
+7 -2
View File
@@ -539,6 +539,11 @@ class AutoInterface(Interface):
spawned_interface.ic_new_time = self.ic_new_time
spawned_interface.ic_burst_penalty = self.ic_burst_penalty
spawned_interface.ic_held_release_interval = self.ic_held_release_interval
spawned_interface.egress_control = self.egress_control
spawned_interface.ec_pr_freq = self.ec_pr_freq
spawned_interface.ic_pr_burst_freq_new = self.ic_pr_burst_freq_new
spawned_interface.ic_pr_burst_freq = self.ic_pr_burst_freq
spawned_interface.parent_interface = self
spawned_interface.bitrate = self.bitrate
@@ -569,7 +574,7 @@ class AutoInterface(Interface):
spawned_interface.mode = self.mode
spawned_interface.HW_MTU = self.HW_MTU
spawned_interface.online = True
RNS.Transport.interfaces.append(spawned_interface)
RNS.Transport.add_interface(spawned_interface)
if addr in self.spawned_interfaces:
self.spawned_interfaces[addr].detach()
self.spawned_interfaces[addr].teardown()
@@ -661,7 +666,7 @@ class AutoInterfacePeer(Interface):
except Exception as e:
RNS.log(f"Could not remove {self} from parent interface on detach. The contained exception was: {e}", RNS.LOG_ERROR)
if self in RNS.Transport.interfaces: RNS.Transport.interfaces.remove(self)
RNS.Transport.remove_interface(self)
class AutoInterfaceHandler(socketserver.BaseRequestHandler):
def __init__(self, callback, *args, **keys):
+84 -16
View File
@@ -156,6 +156,58 @@ class BackboneInterface(Interface):
else:
raise SystemError("Insufficient parameters to create listener")
__last_ic_burst_check = 0
__last_ic_burst_state = False
@property
def ic_burst_active(self):
if time.time() > self.__last_ic_burst_check + 2:
self.__last_ic_burst_state = any(i.ic_burst_active for i in self.spawned_interfaces)
return self.__last_ic_burst_state
@ic_burst_active.setter
def ic_burst_active(self, value): pass
__ic_burst_activated_check = 0
__ic_burst_activated = 0
@property
def ic_burst_activated(self):
if time.time() > self.__ic_burst_activated_check + 2:
activated = [i.ic_burst_activated for i in self.spawned_interfaces if i.ic_burst_active]
if activated: self.__ic_burst_activated = min(activated)
return self.__ic_burst_activated
@ic_burst_activated.setter
def ic_burst_activated(self, value): pass
__last_ic_pr_burst_check = 0
__last_ic_pr_burst_state = False
@property
def ic_pr_burst_active(self):
if time.time() > self.__last_ic_pr_burst_check + 2:
self.__last_ic_pr_burst_state = any(i.ic_pr_burst_active for i in self.spawned_interfaces)
return self.__last_ic_pr_burst_state
@ic_pr_burst_active.setter
def ic_pr_burst_active(self, value): pass
__ic_pr_burst_activated_check = 0
__ic_pr_burst_activated = 0
@property
def ic_pr_burst_activated(self):
if time.time() > self.__ic_pr_burst_activated_check + 2:
activated = [i.ic_pr_burst_activated for i in self.spawned_interfaces if i.ic_pr_burst_active]
if activated: self.__ic_pr_burst_activated = min(activated)
return self.__ic_pr_burst_activated
@ic_pr_burst_activated.setter
def ic_pr_burst_activated(self, value): pass
@staticmethod
def start():
if not BackboneInterface._job_active: threading.Thread(target=BackboneInterface.__job, daemon=True).start()
@@ -196,17 +248,17 @@ class BackboneInterface(Interface):
@staticmethod
def register_in(fileno):
if fileno < 0:
RNS.log(f"Attempt to register invalid file descriptor {fileno}", RNS.LOG_ERROR)
RNS.log(f"Attempt to register invalid file descriptor {fileno}", RNS.LOG_WARNING)
return
try: BackboneInterface.epoll.register(fileno, select.EPOLLIN)
except Exception as e:
RNS.log(f"An error occurred while registering EPOLL_IN for file descriptor {fileno}: {e}", RNS.LOG_ERROR)
RNS.log(f"An error occurred while registering EPOLL_IN for file descriptor {fileno}: {e}", RNS.LOG_WARNING)
@staticmethod
def deregister_fileno(fileno):
if fileno < 0:
RNS.log(f"Attempt to deregister invalid file descriptor {fileno}", RNS.LOG_ERROR)
RNS.log(f"Attempt to deregister invalid file descriptor {fileno}", RNS.LOG_DEBUG)
return
try: BackboneInterface.epoll.unregister(fileno)
@@ -288,7 +340,7 @@ class BackboneInterface(Interface):
except Exception as e: RNS.log(f"Error while removing spawned interface from {pif}: {e}", RNS.LOG_ERROR)
try: client_socket.close()
except Exception as e: RNS.log(f"Error while closing socket for {spawned_interface}: {e}", RNS.LOG_ERROR)
except Exception as e: RNS.log(f"Error while closing socket for {spawned_interface}: {e}", RNS.LOG_WARNING)
spawned_interface.receive(b"")
spawned_interface.transmit_buffer = spawned_interface.transmit_buffer[written:]
@@ -320,18 +372,24 @@ class BackboneInterface(Interface):
elif fileno in BackboneInterface.listener_filenos:
owner_interface, server_socket = BackboneInterface.listener_filenos[fileno]
if fileno == server_socket.fileno() and (event & select.EPOLLIN):
client_socket, address = server_socket.accept()
client_socket.setblocking(0)
if not owner_interface.incoming_connection(client_socket):
try:
client_socket, address = server_socket.accept()
client_socket.setblocking(0)
if not owner_interface.incoming_connection(client_socket):
try: client_socket.close()
except Exception as e: RNS.log(f"Error while closing socket for failed incoming connection: {e}", RNS.LOG_WARNING)
except:
RNS.log(f"Accepting socket failed for incoming connection: {e}", RNS.LOG_WARNING)
try: client_socket.close()
except Exception as e: RNS.log(f"Error while closing socket for failed incoming connection: {e}", RNS.LOG_ERROR)
except Exception as e: RNS.log(f"Error while closing socket for failed incoming socket accept: {e}", RNS.LOG_WARNING)
elif fileno == server_socket.fileno() and (event & select.EPOLLHUP):
try: BackboneInterface.deregister_fileno(fileno)
except Exception as e: RNS.log(f"Error while deregistering listener file descriptor {fileno}: {e}", RNS.LOG_ERROR)
try: server_socket.close()
except Exception as e: RNS.log(f"Error while closing listener socket for {server_socket}: {e}", RNS.LOG_ERROR)
except Exception as e: RNS.log(f"Error while closing listener socket for {server_socket}: {e}", RNS.LOG_WARNING)
except Exception as e:
RNS.log(f"BackboneInterface error: {e}", RNS.LOG_ERROR)
@@ -356,6 +414,11 @@ class BackboneInterface(Interface):
spawned_interface.ic_new_time = self.ic_new_time
spawned_interface.ic_burst_penalty = self.ic_burst_penalty
spawned_interface.ic_held_release_interval = self.ic_held_release_interval
spawned_interface.egress_control = self.egress_control
spawned_interface.ec_pr_freq = self.ec_pr_freq
spawned_interface.ic_pr_burst_freq_new = self.ic_pr_burst_freq_new
spawned_interface.ic_pr_burst_freq = self.ic_pr_burst_freq
spawned_interface.socket = socket
spawned_interface.target_ip = socket.getpeername()[0]
@@ -391,7 +454,7 @@ class BackboneInterface(Interface):
spawned_interface.HW_MTU = self.HW_MTU
spawned_interface.online = True
RNS.log("Spawned new BackboneClient Interface: "+str(spawned_interface), RNS.LOG_VERBOSE)
RNS.Transport.interfaces.append(spawned_interface)
RNS.Transport.add_interface(spawned_interface)
while spawned_interface in self.spawned_interfaces: self.spawned_interfaces.remove(spawned_interface)
self.spawned_interfaces.append(spawned_interface)
BackboneInterface.add_client_socket(socket, spawned_interface)
@@ -408,6 +471,12 @@ class BackboneInterface(Interface):
def sent_announce(self, from_spawned=False):
if from_spawned: self.oa_freq_deque.append(time.time())
def received_path_request(self, from_spawned=False):
if from_spawned: self.ip_freq_deque.append(time.time())
def sent_path_request(self, from_spawned=False):
if from_spawned: self.op_freq_deque.append(time.time())
def process_outgoing(self, data):
pass
@@ -578,8 +647,8 @@ class BackboneClientInterface(Interface):
except Exception as e:
if initial:
RNS.log("Initial connection for "+str(self)+" could not be established: "+str(e), RNS.LOG_ERROR)
RNS.log("Leaving unconnected and retrying connection in "+str(BackboneClientInterface.RECONNECT_WAIT)+" seconds.", RNS.LOG_ERROR)
RNS.log("Initial connection for "+str(self)+" could not be established: "+str(e), RNS.LOG_WARNING)
RNS.log("Leaving unconnected and retrying connection in "+str(BackboneClientInterface.RECONNECT_WAIT)+" seconds.", RNS.LOG_WARNING)
return False
else:
@@ -602,7 +671,7 @@ class BackboneClientInterface(Interface):
attempts += 1
if self.max_reconnect_tries != None and attempts > self.max_reconnect_tries:
RNS.log("Max reconnection attempts reached for "+str(self), RNS.LOG_ERROR)
RNS.log("Max reconnection attempts reached for "+str(self), RNS.LOG_WARNING)
self.teardown()
break
@@ -700,9 +769,8 @@ class BackboneClientInterface(Interface):
while self in self.parent_interface.spawned_interfaces:
self.parent_interface.spawned_interfaces.remove(self)
if self in RNS.Transport.interfaces:
if not self.initiator:
RNS.Transport.interfaces.remove(self)
if not self.initiator:
RNS.Transport.remove_interface(self)
def __str__(self):
+15 -5
View File
@@ -826,9 +826,8 @@ class I2PInterfacePeer(Interface):
while self in self.parent_interface.spawned_interfaces:
self.parent_interface.spawned_interfaces.remove(self)
if self in RNS.Transport.interfaces:
if not self.initiator:
RNS.Transport.interfaces.remove(self)
if not self.initiator:
RNS.Transport.remove_interface(self)
def __str__(self):
@@ -940,7 +939,7 @@ class I2PInterface(Interface):
peer_interface.IN = True
peer_interface.parent_interface = self
peer_interface.parent_count = False
RNS.Transport.interfaces.append(peer_interface)
RNS.Transport.add_interface(peer_interface)
def incoming_connection(self, handler):
RNS.log("Accepting incoming I2P connection", RNS.LOG_VERBOSE)
@@ -957,6 +956,11 @@ class I2PInterface(Interface):
spawned_interface.ic_new_time = self.ic_new_time
spawned_interface.ic_burst_penalty = self.ic_burst_penalty
spawned_interface.ic_held_release_interval = self.ic_held_release_interval
spawned_interface.egress_control = self.egress_control
spawned_interface.ec_pr_freq = self.ec_pr_freq
spawned_interface.ic_pr_burst_freq_new = self.ic_pr_burst_freq_new
spawned_interface.ic_pr_burst_freq = self.ic_pr_burst_freq
spawned_interface.parent_interface = self
spawned_interface.online = True
@@ -988,7 +992,7 @@ class I2PInterface(Interface):
spawned_interface.mode = self.mode
spawned_interface.HW_MTU = self.HW_MTU
RNS.log("Spawned new I2PInterface Peer: "+str(spawned_interface), RNS.LOG_VERBOSE)
RNS.Transport.interfaces.append(spawned_interface)
RNS.Transport.add_interface(spawned_interface)
while spawned_interface in self.spawned_interfaces:
self.spawned_interfaces.remove(spawned_interface)
self.spawned_interfaces.append(spawned_interface)
@@ -1003,6 +1007,12 @@ class I2PInterface(Interface):
def sent_announce(self, from_spawned=False):
if from_spawned: self.oa_freq_deque.append(time.time())
def received_path_request(self, from_spawned=False):
if from_spawned: self.ip_freq_deque.append(time.time())
def sent_path_request(self, from_spawned=False):
if from_spawned: self.op_freq_deque.append(time.time())
def detach(self):
RNS.log("Detaching "+str(self), RNS.LOG_DEBUG)
self.i2p.stop()
+115 -29
View File
@@ -55,8 +55,15 @@ class Interface:
# How many samples to use for announce
# frequency calculations
IA_FREQ_SAMPLES = 128
OA_FREQ_SAMPLES = 128
IA_FREQ_SAMPLES = 48
OA_FREQ_SAMPLES = 48
IP_FREQ_SAMPLES = 48
OP_FREQ_SAMPLES = 48
AR_MINFREQ_HZ = 0.1
PR_MINFREQ_HZ = 0.1
AR_FREQ_DECAY = 1/AR_MINFREQ_HZ
PR_FREQ_DECAY = 1/PR_MINFREQ_HZ
# Maximum amount of ingress limited announces
# to hold at any given time.
@@ -66,12 +73,17 @@ class Interface:
# considered to be newly created. Two
# hours by default.
IC_NEW_TIME = 2*60*60
IC_BURST_FREQ_NEW = 6
IC_BURST_FREQ = 35
IC_BURST_HOLD = 1*60
IC_BURST_FREQ_NEW = 3
IC_BURST_FREQ = 10
IC_PR_BURST_FREQ_NEW = 3
IC_PR_BURST_FREQ = 8
IC_BURST_HOLD = 15
IC_BURST_PENALTY = 15
IC_HELD_RELEASE_INTERVAL = 2
IC_DEQUE_MIN_SAMPLE = 32
IC_HELD_RELEASE_INTERVAL = 5
IC_DEQUE_MIN_SAMPLE = 2
IC_BURST_MIN_SAMPLES = 6
EC_PR_FREQ = 5
EGRESS_CONTROL = False
# Default announce rate targets
DEFAULT_AR_TARGET = 3600
@@ -90,29 +102,38 @@ class Interface:
self.bitrate = 62500
self.HW_MTU = None
self.supports_discovery = False
self.discoverable = False
self.last_discovery_announce = 0
self.bootstrap_only = False
self.parent_interface = None
self.spawned_interfaces = None
self.tunnel_id = None
self.ingress_control = True
self.phy_keepalive = False
self.ic_max_held_announces = Interface.MAX_HELD_ANNOUNCES
self.ic_burst_hold = Interface.IC_BURST_HOLD
self.ic_burst_active = False
self.ic_burst_activated = 0
self.ic_held_release = 0
self.ic_burst_freq_new = Interface.IC_BURST_FREQ_NEW
self.ic_burst_freq = Interface.IC_BURST_FREQ
self.ic_new_time = Interface.IC_NEW_TIME
self.ic_burst_penalty = Interface.IC_BURST_PENALTY
self.ic_held_release_interval = Interface.IC_HELD_RELEASE_INTERVAL
self.held_announces = {}
self.supports_discovery = False
self.discoverable = False
self.last_discovery_announce = 0
self.bootstrap_only = False
self.parent_interface = None
self.spawned_interfaces = None
self.tunnel_id = None
self.ingress_control = True
self.phy_keepalive = False
self.ic_burst_active = False
self.ic_burst_activated = 0
self.ic_pr_burst_active = False
self.ic_pr_burst_activated = 0
self.ic_held_release = 0
self.ic_max_held_announces = RNS.Reticulum.get_instance()._default_ic_max_held_announces()
self.ic_burst_hold = RNS.Reticulum.get_instance()._default_ic_burst_hold()
self.ic_burst_freq_new = RNS.Reticulum.get_instance()._default_ic_burst_freq_new()
self.ic_burst_freq = RNS.Reticulum.get_instance()._default_ic_burst_freq()
self.ic_pr_burst_freq_new = RNS.Reticulum.get_instance()._default_ic_pr_burst_freq_new()
self.ic_pr_burst_freq = RNS.Reticulum.get_instance()._default_ic_pr_burst_freq()
self.ic_new_time = RNS.Reticulum.get_instance()._default_ic_new_time()
self.ic_burst_penalty = RNS.Reticulum.get_instance()._default_ic_burst_penalty()
self.ic_held_release_interval = RNS.Reticulum.get_instance()._default_ic_held_release_interval()
self.ec_pr_freq = RNS.Reticulum.get_instance()._default_ec_pr_freq()
self.egress_control = RNS.Reticulum.get_instance()._default_egress_control()
self.held_announces = {}
self.ia_freq_deque = deque(maxlen=Interface.IA_FREQ_SAMPLES)
self.oa_freq_deque = deque(maxlen=Interface.OA_FREQ_SAMPLES)
self.ip_freq_deque = deque(maxlen=Interface.IA_FREQ_SAMPLES)
self.op_freq_deque = deque(maxlen=Interface.OA_FREQ_SAMPLES)
def get_hash(self):
return RNS.Identity.full_hash(str(self).encode("utf-8"))
@@ -128,7 +149,7 @@ class Interface:
if self.ic_burst_active:
if ia_freq < freq_threshold and time.time() > self.ic_burst_activated+self.ic_burst_hold:
self.ic_burst_active = False
if len(self.ia_freq_deque) >= self.IC_BURST_MIN_SAMPLES: self.ic_burst_active = False
return True
@@ -143,6 +164,37 @@ class Interface:
else: return False
def should_ingress_limit_pr(self):
if self.ingress_control:
freq_threshold = self.ic_pr_burst_freq_new if self.age() < self.ic_new_time else self.ic_pr_burst_freq
ip_freq = self.incoming_pr_frequency()
if self.ic_pr_burst_active:
if ip_freq < freq_threshold and time.time() > self.ic_pr_burst_activated+self.ic_burst_hold:
self.ic_pr_burst_active = False
return True
else:
if ip_freq > freq_threshold:
self.ic_pr_burst_active = True
self.ic_pr_burst_activated = time.time()
return True
else: return False
else: return False
def should_egress_limit_pr(self):
if self.egress_control:
freq_threshold = self.ec_pr_freq
op_freq = self.outgoing_pr_frequency()
if op_freq > freq_threshold:
if len(self.op_freq_deque) >= self.IC_BURST_MIN_SAMPLES: return True
return False
def optimise_mtu(self):
if self.AUTOCONFIGURE_MTU:
if self.bitrate >= 1_000_000_000:
@@ -168,7 +220,7 @@ class Interface:
else:
self.HW_MTU = None
RNS.log(f"{self} hardware MTU set to {self.HW_MTU}", RNS.LOG_DEBUG) # TODO: Remove debug
RNS.log(f"{self} hardware MTU set to {self.HW_MTU}", RNS.LOG_DEBUG)
def age(self):
return time.time()-self.created
@@ -214,12 +266,23 @@ class Interface:
if hasattr(self, "parent_interface") and self.parent_interface != None:
self.parent_interface.sent_announce(from_spawned=True)
def received_path_request(self, from_spawned=False):
self.ip_freq_deque.append(time.time())
if hasattr(self, "parent_interface") and self.parent_interface != None:
self.parent_interface.received_path_request(from_spawned=True)
def sent_path_request(self, from_spawned=False):
self.op_freq_deque.append(time.time())
if hasattr(self, "parent_interface") and self.parent_interface != None:
self.parent_interface.sent_path_request(from_spawned=True)
def incoming_announce_frequency(self):
n = len(self.ia_freq_deque)
if not n > self.IC_DEQUE_MIN_SAMPLE: return 0
else:
oldest = self.ia_freq_deque[0]
span = time.time() - oldest
if span > self.AR_FREQ_DECAY: self.ia_freq_deque.popleft()
if span <= 0: return 0
hz = n / span
return hz
@@ -230,6 +293,29 @@ class Interface:
else:
oldest = self.oa_freq_deque[0]
span = time.time() - oldest
if span > self.AR_FREQ_DECAY: self.oa_freq_deque.popleft()
if span <= 0: return 0
hz = n / span
return hz
def incoming_pr_frequency(self):
n = len(self.ip_freq_deque)
if not n > self.IC_DEQUE_MIN_SAMPLE: return 0
else:
oldest = self.ip_freq_deque[0]
span = time.time() - oldest
if span > self.PR_FREQ_DECAY: self.ip_freq_deque.popleft()
if span <= 0: return 0
hz = n / span
return hz
def outgoing_pr_frequency(self):
n = len(self.op_freq_deque)
if not len(self.op_freq_deque) > 1: return 0
else:
oldest = self.op_freq_deque[0]
span = time.time() - oldest
if span > self.PR_FREQ_DECAY: self.op_freq_deque.popleft()
if span <= 0: return 0
hz = n / span
return hz
+9 -4
View File
@@ -347,8 +347,7 @@ class LocalClientInterface(Interface):
self.OUT = False
self.IN = False
if self in RNS.Transport.interfaces:
RNS.Transport.interfaces.remove(self)
RNS.Transport.remove_interface(self)
if self in RNS.Transport.local_client_interfaces:
RNS.Transport.local_client_interfaces.remove(self)
@@ -458,7 +457,7 @@ class LocalServerInterface(Interface):
spawned_interface.socket_path = self.socket_path
if hasattr(self, "_force_bitrate"): spawned_interface._force_bitrate = self._force_bitrate
RNS.Transport.interfaces.append(spawned_interface)
RNS.Transport.add_interface(spawned_interface)
RNS.Transport.local_client_interfaces.append(spawned_interface)
BackboneInterface.add_client_socket(client_socket, spawned_interface)
self.clients += 1
@@ -474,7 +473,7 @@ class LocalServerInterface(Interface):
spawned_interface.parent_interface = self
spawned_interface.bitrate = self.bitrate
if hasattr(self, "_force_bitrate"): spawned_interface._force_bitrate = self._force_bitrate
RNS.Transport.interfaces.append(spawned_interface)
RNS.Transport.add_interface(spawned_interface)
RNS.Transport.local_client_interfaces.append(spawned_interface)
self.clients += 1
spawned_interface.read_loop()
@@ -488,6 +487,12 @@ class LocalServerInterface(Interface):
def sent_announce(self, from_spawned=False):
if from_spawned: self.oa_freq_deque.append(time.time())
def received_path_request(self, from_spawned=False):
if from_spawned: self.ip_freq_deque.append(time.time())
def sent_path_request(self, from_spawned=False):
if from_spawned: self.op_freq_deque.append(time.time())
def __str__(self):
if self.socket_path: return "Shared Instance["+str(self.socket_path.replace("\0", ""))+"]"
else: return "Shared Instance["+str(self.bind_port)+"]"
+8 -3
View File
@@ -375,7 +375,7 @@ class RNodeMultiInterface(Interface):
interface.mode = self.mode
interface.HW_MTU = self.HW_MTU
interface.detected = True
RNS.Transport.interfaces.append(interface)
RNS.Transport.add_interface(interface)
RNS.log("Spawned new RNode subinterface: "+str(interface), RNS.LOG_VERBOSE)
self.clients += 1
@@ -549,6 +549,12 @@ class RNodeMultiInterface(Interface):
def sent_announce(self, from_spawned=False):
if from_spawned: self.oa_freq_deque.append(time.time())
def received_path_request(self, from_spawned=False):
if from_spawned: self.ip_freq_deque.append(time.time())
def sent_path_request(self, from_spawned=False):
if from_spawned: self.op_freq_deque.append(time.time())
def readLoop(self):
try:
in_frame = False
@@ -903,8 +909,7 @@ class RNodeMultiInterface(Interface):
def teardown_subinterfaces(self):
for interface in self.subinterfaces:
if interface != 0:
if interface in RNS.Transport.interfaces:
RNS.Transport.interfaces.remove(interface)
RNS.Transport.remove_interface(interface)
self.subinterfaces[interface.index] = 0
def should_ingress_limit(self):
+14 -4
View File
@@ -436,9 +436,8 @@ class TCPClientInterface(Interface):
while self in self.parent_interface.spawned_interfaces:
self.parent_interface.spawned_interfaces.remove(self)
if self in RNS.Transport.interfaces:
if not self.initiator:
RNS.Transport.interfaces.remove(self)
if not self.initiator:
RNS.Transport.remove_interface(self)
def __str__(self):
@@ -589,6 +588,11 @@ class TCPServerInterface(Interface):
spawned_interface.ic_burst_penalty = self.ic_burst_penalty
spawned_interface.ic_held_release_interval = self.ic_held_release_interval
spawned_interface.egress_control = self.egress_control
spawned_interface.ec_pr_freq = self.ec_pr_freq
spawned_interface.ic_pr_burst_freq_new = self.ic_pr_burst_freq_new
spawned_interface.ic_pr_burst_freq = self.ic_pr_burst_freq
spawned_interface.target_ip = handler.client_address[0]
spawned_interface.target_port = str(handler.client_address[1])
spawned_interface.parent_interface = self
@@ -622,7 +626,7 @@ class TCPServerInterface(Interface):
spawned_interface.HW_MTU = self.HW_MTU
spawned_interface.online = True
RNS.log("Spawned new TCPClient Interface: "+str(spawned_interface), RNS.LOG_VERBOSE)
RNS.Transport.interfaces.append(spawned_interface)
RNS.Transport.add_interface(spawned_interface)
while spawned_interface in self.spawned_interfaces:
self.spawned_interfaces.remove(spawned_interface)
self.spawned_interfaces.append(spawned_interface)
@@ -634,6 +638,12 @@ class TCPServerInterface(Interface):
def sent_announce(self, from_spawned=False):
if from_spawned: self.oa_freq_deque.append(time.time())
def received_path_request(self, from_spawned=False):
if from_spawned: self.ip_freq_deque.append(time.time())
def sent_path_request(self, from_spawned=False):
if from_spawned: self.op_freq_deque.append(time.time())
def process_outgoing(self, data):
pass
+2 -3
View File
@@ -981,7 +981,7 @@ class WeaveInterface(Interface):
spawned_interface.mode = self.mode
spawned_interface.HW_MTU = self.HW_MTU
spawned_interface._online = True
RNS.Transport.interfaces.append(spawned_interface)
RNS.Transport.add_interface(spawned_interface)
if endpoint_addr in self.spawned_interfaces:
self.spawned_interfaces[endpoint_addr].detach()
self.spawned_interfaces[endpoint_addr].teardown()
@@ -1097,5 +1097,4 @@ class WeaveInterfacePeer(Interface):
except Exception as e:
RNS.log(f"Could not remove {self} from parent interface on detach. The contained exception was: {e}", RNS.LOG_ERROR)
if self in RNS.Transport.interfaces:
RNS.Transport.interfaces.remove(self)
RNS.Transport.remove_interface(self)
+2 -1
View File
@@ -117,7 +117,7 @@ class Packet:
__slots__ = "hops", "header", "header_type", "packet_type", "transport_type", "context", "context_flag", "destination"
__slots__ += "transport_id", "data", "flags", "raw", "packed", "sent", "create_receipt", "receipt", "fromPacked", "MTU"
__slots__ += "sent_at", "packet_hash", "ratchet_id", "attached_interface", "receiving_interface", "rssi", "snr", "q"
__slots__ += "ciphertext", "plaintext", "destination_hash", "destination_type", "link", "map_hash"
__slots__ += "ciphertext", "plaintext", "destination_hash", "destination_type", "link", "map_hash", "is_outbound_pr"
def __init__(self, destination, data, packet_type = DATA, context = NONE, transport_type = RNS.Transport.BROADCAST,
header_type = HEADER_1, transport_id = None, attached_interface = None, create_receipt = True, context_flag=FLAG_UNSET):
@@ -161,6 +161,7 @@ class Packet:
self.attached_interface = attached_interface
self.receiving_interface = None
self.is_outbound_pr = False
self.rssi = None
self.snr = None
self.q = None
+162 -35
View File
@@ -249,22 +249,33 @@ class Reticulum:
Reticulum.blackholepath = Reticulum.configdir+"/storage/blackhole"
Reticulum.interfacepath = Reticulum.configdir+"/interfaces"
Reticulum.__network_identity = None
Reticulum.__transport_enabled = False
Reticulum.__link_mtu_discovery = Reticulum.LINK_MTU_DISCOVERY
Reticulum.__remote_management_enabled = False
Reticulum.__use_implicit_proof = True
Reticulum.__allow_probes = False
Reticulum.__discovery_enabled = False
Reticulum.__discover_interfaces = False
Reticulum.__network_identity = None
Reticulum.__transport_enabled = False
Reticulum.__link_mtu_discovery = Reticulum.LINK_MTU_DISCOVERY
Reticulum.__remote_management_enabled = False
Reticulum.__use_implicit_proof = True
Reticulum.__allow_probes = False
Reticulum.__discovery_enabled = False
Reticulum.__discover_interfaces = False
Reticulum.__autoconnect_discovered_interfaces = False
Reticulum.__required_discovery_value = None
Reticulum.__publish_blackhole = False
Reticulum.__blackhole_sources = []
Reticulum.__interface_sources = []
Reticulum.__default_ar_target = None
Reticulum.__default_ar_penalty = None
Reticulum.__default_ar_grace = None
Reticulum.__required_discovery_value = None
Reticulum.__publish_blackhole = False
Reticulum.__blackhole_sources = []
Reticulum.__interface_sources = []
Reticulum.__default_ar_target = None
Reticulum.__default_ar_penalty = None
Reticulum.__default_ar_grace = None
Reticulum.__ic_max_held_announces = None
Reticulum.__ic_burst_hold = None
Reticulum.__ic_burst_freq_new = None
Reticulum.__ic_burst_freq = None
Reticulum.__ic_pr_burst_freq_new = None
Reticulum.__ic_pr_burst_freq = None
Reticulum.__ic_new_time = None
Reticulum.__ic_burst_penalty = None
Reticulum.__ic_held_release_interval = None
Reticulum.__ec_pr_freq = None
Reticulum.__egress_control = None
Reticulum.panic_on_interface_error = False
@@ -391,7 +402,7 @@ class Reticulum:
RNS.log("Existing shared instance required, but this instance started as shared instance. Aborting startup.", RNS.LOG_VERBOSE)
else:
RNS.Transport.interfaces.append(interface)
RNS.Transport.add_interface(interface)
self.shared_instance_interface = interface
self.is_shared_instance = True
RNS.log("Started shared instance interface: "+str(interface), RNS.LOG_DEBUG)
@@ -411,7 +422,7 @@ class Reticulum:
interface._force_bitrate = True
RNS.log(f"Forcing shared instance bitrate of {RNS.prettyspeed(interface.bitrate)}", RNS.LOG_WARNING)
interface.optimise_mtu()
RNS.Transport.interfaces.append(interface)
RNS.Transport.add_interface(interface)
self.is_shared_instance = False
self.is_standalone_instance = False
self.is_connected_to_shared_instance = True
@@ -596,6 +607,51 @@ class Reticulum:
v = self.config["reticulum"].as_int(option)
if v >= 0: Reticulum.__default_ar_grace = v
if option == "ic_max_held_announces":
v = self.config["reticulum"].as_int(option)
if v >= 0: Reticulum.__ic_max_held_announces = v
if option == "ic_burst_hold":
v = self.config["reticulum"].as_float(option)
if v >= 0: Reticulum.__ic_burst_hold = v
if option == "ic_burst_freq_new":
v = self.config["reticulum"].as_float(option)
if v >= 0: Reticulum.__ic_burst_freq_new = v
if option == "ic_burst_freq":
v = self.config["reticulum"].as_float(option)
if v >= 0: Reticulum.__ic_burst_freq = v
if option == "ic_pr_burst_freq_new":
v = self.config["reticulum"].as_float(option)
if v >= 0: Reticulum.__ic_pr_burst_freq_new = v
if option == "ic_pr_burst_freq":
v = self.config["reticulum"].as_float(option)
if v >= 0: Reticulum.__ic_pr_burst_freq = v
if option == "ec_pr_freq":
v = self.config["reticulum"].as_float(option)
if v >= 0: Reticulum.__ec_pr_freq = v
if option == "egress_control":
v = self.config["reticulum"].as_bool(option)
if v >= 0: Reticulum.__egress_control = v
if option == "ic_new_time":
v = self.config["reticulum"].as_float(option)
if v >= 0: Reticulum.__ic_new_time = v
if option == "ic_burst_penalty":
v = self.config["reticulum"].as_float(option)
if v >= 0: Reticulum.__ic_burst_penalty = v
if option == "ic_held_release_interval":
v = self.config["reticulum"].as_float(option)
if v >= 0: Reticulum.__ic_held_release_interval = v
if RNS.compiled: RNS.log("Reticulum running in compiled mode", RNS.LOG_DEBUG)
else: RNS.log("Reticulum running in interpreted mode", RNS.LOG_DEBUG)
@@ -683,6 +739,8 @@ class Reticulum:
ingress_control = True
if "ingress_control" in c: ingress_control = c.as_bool("ingress_control")
egress_control = None
if "egress_control" in c: egress_control = c.as_bool("egress_control")
ic_max_held_announces = None
if "ic_max_held_announces" in c: ic_max_held_announces = c.as_int("ic_max_held_announces")
ic_burst_hold = None
@@ -691,6 +749,12 @@ class Reticulum:
if "ic_burst_freq_new" in c: ic_burst_freq_new = c.as_float("ic_burst_freq_new")
ic_burst_freq = None
if "ic_burst_freq" in c: ic_burst_freq = c.as_float("ic_burst_freq")
ic_pr_burst_freq_new = None
if "ic_pr_burst_freq_new" in c: ic_pr_burst_freq_new = c.as_float("ic_pr_burst_freq_new")
ic_pr_burst_freq = None
if "ic_pr_burst_freq" in c: ic_pr_burst_freq = c.as_float("ic_pr_burst_freq")
ec_pr_freq = None
if "ec_pr_freq" in c: ec_pr_freq = c.as_float("ec_pr_freq")
ic_new_time = None
if "ic_new_time" in c: ic_new_time = c.as_float("ic_new_time")
ic_burst_penalty = None
@@ -816,10 +880,14 @@ class Reticulum:
interface.announce_rate_grace = announce_rate_grace
interface.announce_rate_penalty = announce_rate_penalty
interface.ingress_control = ingress_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_burst_hold != None: interface.ic_burst_hold = ic_burst_hold
if ic_burst_freq_new != None: interface.ic_burst_freq_new = ic_burst_freq_new
if ic_burst_freq != None: interface.ic_burst_freq = ic_burst_freq
if ic_pr_burst_freq_new != None: interface.ic_pr_burst_freq_new = ic_pr_burst_freq_new
if ic_pr_burst_freq != None: interface.ic_pr_burst_freq = ic_pr_burst_freq
if ec_pr_freq != None: interface.ec_pr_freq = ec_pr_freq
if ic_new_time != None: interface.ic_new_time = ic_new_time
if ic_burst_penalty != None: interface.ic_burst_penalty = ic_burst_penalty
if ic_held_release_interval != None: interface.ic_held_release_interval = ic_held_release_interval
@@ -847,7 +915,7 @@ class Reticulum:
interface.ifac_identity = RNS.Identity.from_bytes(interface.ifac_key)
interface.ifac_signature = interface.ifac_identity.sign(RNS.Identity.full_hash(interface.ifac_key))
RNS.Transport.interfaces.append(interface)
RNS.Transport.add_interface(interface)
interface.final_init()
interface = None
@@ -1009,7 +1077,7 @@ class Reticulum:
interface.ifac_identity = RNS.Identity.from_bytes(interface.ifac_key)
interface.ifac_signature = interface.ifac_identity.sign(RNS.Identity.full_hash(interface.ifac_key))
RNS.Transport.interfaces.append(interface)
RNS.Transport.add_interface(interface)
interface.final_init()
def _default_ar_target(self):
@@ -1021,6 +1089,39 @@ class Reticulum:
def _default_ar_grace(self):
return self.__default_ar_grace or RNS.Interfaces.Interface.Interface.DEFAULT_AR_GRACE
def _default_ic_max_held_announces(self):
return self.__ic_max_held_announces or RNS.Interfaces.Interface.Interface.MAX_HELD_ANNOUNCES
def _default_ic_burst_hold(self):
return self.__ic_burst_hold or RNS.Interfaces.Interface.Interface.IC_BURST_HOLD
def _default_ic_burst_freq_new(self):
return self.__ic_burst_freq_new or RNS.Interfaces.Interface.Interface.IC_BURST_FREQ_NEW
def _default_ic_burst_freq(self):
return self.__ic_burst_freq or RNS.Interfaces.Interface.Interface.IC_BURST_FREQ
def _default_ic_pr_burst_freq_new(self):
return self.__ic_pr_burst_freq_new or RNS.Interfaces.Interface.Interface.IC_PR_BURST_FREQ_NEW
def _default_ic_pr_burst_freq(self):
return self.__ic_pr_burst_freq or RNS.Interfaces.Interface.Interface.IC_PR_BURST_FREQ
def _default_ec_pr_freq(self):
return self.__ec_pr_freq or RNS.Interfaces.Interface.Interface.EC_PR_FREQ
def _default_egress_control(self):
return self.__egress_control or RNS.Interfaces.Interface.Interface.EGRESS_CONTROL
def _default_ic_new_time(self):
return self.__ic_new_time or RNS.Interfaces.Interface.Interface.IC_NEW_TIME
def _default_ic_burst_penalty(self):
return self.__ic_burst_penalty or RNS.Interfaces.Interface.Interface.IC_BURST_PENALTY
def _default_ic_held_release_interval(self):
return self.__ic_held_release_interval or RNS.Interfaces.Interface.Interface.IC_HELD_RELEASE_INTERVAL
def _should_persist_data(self, background=False):
if time.time() > self.last_data_persist+Reticulum.GRACIOUS_PERSIST_INTERVAL:
def job(): self.__persist_data(background=background)
@@ -1133,28 +1234,43 @@ class Reticulum:
def _used_destination_data(self, destination_hash):
if self.is_connected_to_shared_instance:
rpc_connection = self.get_rpc_client()
rpc_connection.send({"destination_data": "used", "destination_hash": destination_hash})
response = rpc_connection.recv()
return response
try:
rpc_connection = self.get_rpc_client()
rpc_connection.send({"destination_data": "used", "destination_hash": destination_hash})
response = rpc_connection.recv()
return response
except Exception as e:
RNS.log(f"Shared instance RPC failed while setting destination data use: {e}", RNS.LOG_ERROR)
return False
else: return RNS.Identity._used_destination_data(destination_hash)
def _retain_destination_data(self, destination_hash):
if self.is_connected_to_shared_instance:
rpc_connection = self.get_rpc_client()
rpc_connection.send({"destination_data": "retain", "destination_hash": destination_hash})
response = rpc_connection.recv()
return response
try:
rpc_connection = self.get_rpc_client()
rpc_connection.send({"destination_data": "retain", "destination_hash": destination_hash})
response = rpc_connection.recv()
return response
except Exception as e:
RNS.log(f"Shared instance RPC failed while retaining destination data: {e}", RNS.LOG_ERROR)
return False
else: return RNS.Identity._retain_destination_data(destination_hash)
def _unretain_destination_data(self, destination_hash):
if self.is_connected_to_shared_instance:
rpc_connection = self.get_rpc_client()
rpc_connection.send({"destination_data": "unretain", "destination_hash": destination_hash})
response = rpc_connection.recv()
return response
try:
rpc_connection = self.get_rpc_client()
rpc_connection.send({"destination_data": "unretain", "destination_hash": destination_hash})
response = rpc_connection.recv()
return response
except Exception as e:
RNS.log(f"Shared instance RPC failed while unretaining destination data: {e}", RNS.LOG_ERROR)
return False
else: return RNS.Identity._unretain_destination_data(destination_hash)
@@ -1163,10 +1279,15 @@ class Reticulum:
raise TypeError("Cannot retain identity, not a valid identity hash")
if self.is_connected_to_shared_instance:
rpc_connection = self.get_rpc_client()
rpc_connection.send({"identity_data": "retain", "identity_hash": identity_hash})
response = rpc_connection.recv()
return response
try:
rpc_connection = self.get_rpc_client()
rpc_connection.send({"identity_data": "retain", "identity_hash": identity_hash})
response = rpc_connection.recv()
return response
except Exception as e:
RNS.log(f"Shared instance RPC failed while retaining identity: {e}", RNS.LOG_ERROR)
return False
else: return RNS.Identity._retain_identity(identity_hash)
@@ -1321,10 +1442,16 @@ class Reticulum:
ifstats["txb"] = interface.txb
ifstats["incoming_announce_frequency"] = interface.incoming_announce_frequency()
ifstats["outgoing_announce_frequency"] = interface.outgoing_announce_frequency()
ifstats["incoming_pr_frequency"] = interface.incoming_pr_frequency()
ifstats["outgoing_pr_frequency"] = interface.outgoing_pr_frequency()
ifstats["announce_rate_target"] = interface.announce_rate_target
ifstats["announce_rate_penalty"] = interface.announce_rate_penalty
ifstats["announce_rate_grace"] = interface.announce_rate_grace
ifstats["held_announces"] = len(interface.held_announces)
ifstats["burst_active"] = interface.ic_burst_active
ifstats["burst_activated"] = interface.ic_burst_activated
ifstats["pr_burst_active"] = interface.ic_pr_burst_active
ifstats["pr_burst_activated"] = interface.ic_pr_burst_activated
ifstats["status"] = interface.online
ifstats["mode"] = interface.mode
+143 -73
View File
@@ -38,6 +38,7 @@ import inspect
import threading
from time import sleep
from threading import Lock
from collections import deque
from .vendor import umsgpack as umsgpack
from RNS.Interfaces.BackboneInterface import BackboneInterface
@@ -124,6 +125,7 @@ class Transport:
discovery_path_requests = {} # A table for keeping track of path requests on behalf of other nodes
discovery_pr_tags = [] # A table for keeping track of tagged path requests
max_pr_tags = 32000 # Maximum amount of unique path request tags to remember
max_queued_discovery_prs = 32 # Maximum amount of queued discovery path requests
interfaces_lock = Lock()
destinations_lock = Lock()
@@ -415,6 +417,18 @@ class Transport:
gc.collect()
@staticmethod
def add_interface(interface):
with Transport.interfaces_lock:
if not interface in Transport.interfaces:
Transport.interfaces.append(interface)
@staticmethod
def remove_interface(interface):
with Transport.interfaces_lock:
if interface in Transport.interfaces:
Transport.interfaces.remove(interface)
@staticmethod
def set_network_identity(identity):
if not Transport.network_identity:
@@ -580,40 +594,41 @@ class Transport:
if block_rebroadcasts: announce_context = RNS.Packet.PATH_RESPONSE
announce_data = packet.data
announce_identity = RNS.Identity.recall(packet.destination_hash, _no_use=True)
announce_destination = RNS.Destination(announce_identity, RNS.Destination.OUT, RNS.Destination.SINGLE, "unknown", "unknown");
announce_destination.hash = packet.destination_hash
announce_destination.hexhash = announce_destination.hash.hex()
new_packet = RNS.Packet(
announce_destination,
announce_data,
RNS.Packet.ANNOUNCE,
context = announce_context,
header_type = RNS.Packet.HEADER_2,
transport_type = Transport.TRANSPORT,
transport_id = Transport.identity.hash,
attached_interface = attached_interface,
context_flag = packet.context_flag,
)
if not announce_identity:
RNS.log("Completed announce processing for "+RNS.prettyhexrep(destination_hash)+", the path was cleaned while waiting for announce rebroadcast", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
completed_announces.append(destination_hash)
new_packet.hops = announce_entry[4]
if block_rebroadcasts:
RNS.log("Rebroadcasting announce as path response for "+RNS.prettyhexrep(announce_destination.hash)+" with hop count "+str(new_packet.hops), RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
else:
RNS.log("Rebroadcasting announce for "+RNS.prettyhexrep(announce_destination.hash)+" with hop count "+str(new_packet.hops), RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
outgoing.append(new_packet)
announce_destination = RNS.Destination(announce_identity, RNS.Destination.OUT, RNS.Destination.SINGLE, "unknown", "unknown");
announce_destination.hash = packet.destination_hash
announce_destination.hexhash = announce_destination.hash.hex()
new_packet = RNS.Packet(announce_destination,
announce_data,
RNS.Packet.ANNOUNCE,
context = announce_context,
header_type = RNS.Packet.HEADER_2,
transport_type = Transport.TRANSPORT,
transport_id = Transport.identity.hash,
attached_interface = attached_interface,
context_flag = packet.context_flag)
# This handles an edge case where a peer sends a past
# request for a destination just after an announce for
# said destination has arrived, but before it has been
# rebroadcast locally. In such a case the actual announce
# is temporarily held, and then reinserted when the path
# request has been served to the peer.
if destination_hash in Transport.held_announces:
held_entry = Transport.held_announces.pop(destination_hash)
Transport.announce_table[destination_hash] = held_entry
RNS.log("Reinserting held announce into table", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
new_packet.hops = announce_entry[4]
if block_rebroadcasts: RNS.log("Rebroadcasting announce as path response for "+RNS.prettyhexrep(announce_destination.hash)+" with hop count "+str(new_packet.hops), RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
else: RNS.log("Rebroadcasting announce for "+RNS.prettyhexrep(announce_destination.hash)+" with hop count "+str(new_packet.hops), RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
outgoing.append(new_packet)
# This handles an edge case where a peer sends a past
# request for a destination just after an announce for
# said destination has arrived, but before it has been
# rebroadcast locally. In such a case the actual announce
# is temporarily held, and then reinserted when the path
# request has been served to the peer.
if destination_hash in Transport.held_announces:
held_entry = Transport.held_announces.pop(destination_hash)
Transport.announce_table[destination_hash] = held_entry
RNS.log("Reinserting held announce into table", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
for destination_hash in completed_announces:
if destination_hash in Transport.announce_table: Transport.announce_table.pop(destination_hash)
@@ -772,10 +787,14 @@ class Transport:
# Cull the pending path requests table
stale_path_requests = []
with Transport.path_requests_lock:
for destination_hash in Transport.path_requests:
if time.time() > Transport.path_requests[destination_hash] + Transport.PATH_REQUEST_GATE_TIMEOUT:
stale_path_requests.append(destination_hash)
RNS.log("Path request entry for "+RNS.prettyhexrep(destination_hash)+" timed out and was removed", RNS.LOG_EXTREME) if RNS.sl(RNS.LOG_EXTREME) else None
try:
for destination_hash in Transport.path_requests:
if time.time() > Transport.path_requests[destination_hash] + Transport.PATH_REQUEST_GATE_TIMEOUT:
stale_path_requests.append(destination_hash)
RNS.log("Path request entry for "+RNS.prettyhexrep(destination_hash)+" timed out and was removed", RNS.LOG_EXTREME) if RNS.sl(RNS.LOG_EXTREME) else None
except Exception as e:
RNS.log(f"Could not complete stale path request enumeration in this job round, retrying later: {e}", RNS.LOG_WARNING)
# Cull the pending discovery path requests table
stale_discovery_path_requests = []
@@ -917,6 +936,8 @@ class Transport:
Transport.prioritize_interfaces()
try:
for interface in Transport.interfaces:
interface.should_ingress_limit()
interface.should_ingress_limit_pr()
interface.process_held_announces()
if interface.phy_keepalive: interface.send_keepalive()
Transport.interface_last_jobs = time.time()
@@ -980,15 +1001,50 @@ class Transport:
RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR)
RNS.trace_exception(e) # TODO: Remove
for packet in outgoing: packet.send()
if outgoing:
def job(): Transport.handle_outgoing_announces(outgoing)
threading.Thread(target=job).start()
for destination_hash in path_requests:
blocked_if = path_requests[destination_hash]
if blocked_if == None: Transport.request_path(destination_hash)
else:
for interface in Transport.interfaces:
if interface != blocked_if: Transport.request_path(destination_hash, on_interface=interface)
else: pass
if path_requests:
with Transport.discovery_pr_tx_lock:
for destination_hash in path_requests:
if not destination_hash in Transport.pending_discovery_prs:
if not len(Transport.pending_discovery_prs) >= Transport.max_queued_discovery_prs:
Transport.pending_discovery_prs.append([destination_hash, path_requests[destination_hash]])
if len(Transport.pending_discovery_prs):
def job(): Transport.handle_disovery_path_requests()
threading.Thread(target=job).start()
discovery_pr_tx_throttle = 0.5
discovery_pr_tx_lock = Lock()
discovery_pr_handle_lock = Lock()
pending_discovery_prs = deque(maxlen=max_queued_discovery_prs)
@staticmethod
def handle_disovery_path_requests():
if Transport.discovery_pr_handle_lock.locked(): return
with Transport.discovery_pr_handle_lock:
while len(Transport.pending_discovery_prs):
time.sleep(Transport.discovery_pr_tx_throttle)
destination_hash = None
blocked_if = None
with Transport.discovery_pr_tx_lock:
entry = Transport.pending_discovery_prs.popleft()
destination_hash = entry[0]
blocked_if = entry[1]
if destination_hash:
if blocked_if == None: Transport.request_path(destination_hash)
else:
for interface in Transport.interfaces:
if interface != blocked_if: Transport.request_path(destination_hash, on_interface=interface)
else: pass
@staticmethod
def handle_outgoing_announces(outgoing):
for packet in sorted(outgoing, key=lambda p: p.hops): packet.send()
@staticmethod
def transmit(interface, raw):
@@ -1263,6 +1319,7 @@ class Transport:
Transport.transmit(interface, packet.raw)
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()
packet_sent(packet)
sent = True
@@ -1549,7 +1606,7 @@ class Transport:
RNS.log(f"Clamping link MTU to {RNS.prettysize(path_mtu)}", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
new_raw = new_raw[:-RNS.Link.LINK_MTU_SIZE]+clamped_mtu
except Exception as e:
RNS.log(f"Dropping link request packet. The contained exception was: {e}", RNS.LOG_WARNING)
RNS.log(f"Dropping link request packet. The contained exception was: {e}", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
return
# Entry format is
@@ -1629,10 +1686,12 @@ class Transport:
# announces, queueing rebroadcasts of these, and removal
# of queued announce rebroadcasts once handed to the next node.
if packet.packet_type == RNS.Packet.ANNOUNCE:
if interface != None and RNS.Identity.validate_announce(packet, only_validate_signature=True):
interface.received_announce()
announce_signature_valid = RNS.Identity.validate_announce(packet, only_validate_signature=True)
if not announce_signature_valid: return
elif interface != None: interface.received_announce()
announced_destination_known = packet.destination_hash in Transport.path_table
if not packet.destination_hash in Transport.path_table:
if not announced_destination_known:
# This is an unknown destination, and we'll apply
# potential ingress limiting. Already known
# destinations will have re-announces controlled
@@ -1694,7 +1753,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_blobs = []
with Transport.inbound_announce_lock:
if packet.destination_hash in Transport.path_table:
if announced_destination_known:
random_blobs = Transport.path_table[packet.destination_hash][IDX_PT_RANDBLOBS]
# If we already have a path to the announced
@@ -2140,13 +2199,9 @@ class Transport:
else:
RNS.log("Invalid link request proof in transport for link "+RNS.prettyhexrep(packet.destination_hash)+", dropping proof.", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
except Exception as e:
RNS.log("Error while transporting link request proof. The contained exception was: "+str(e), RNS.LOG_ERROR)
else:
RNS.log("Link request proof received on wrong interface, not transporting it.", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
else:
RNS.log("Received link request proof with hop mismatch, not transporting it", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
except Exception as e: RNS.log("Could not transport link request proof. The contained exception was: "+str(e), RNS.LOG_DEBUG) if RNS.sl(LOG_DEBUG) else None
else: RNS.log("Link request proof received on wrong interface, not transporting it.", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
else: RNS.log("Received link request proof with hop mismatch, not transporting it", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
else:
# Check if we can deliver it to a local
@@ -2475,16 +2530,17 @@ class Transport:
def clean_announce_cache():
st = time.time()
target_path = os.path.join(RNS.Reticulum.cachepath, "announces")
with Transport.path_table_lock: active_paths = [Transport.path_table[dst_hash][6] for dst_hash in Transport.path_table]
with Transport.tunnels_lock: tunnel_paths = list(set([path_dict[dst_hash][6] for path_dict in [Transport.tunnels[tunnel_id][2] for tunnel_id in Transport.tunnels] for dst_hash in path_dict]))
cached_announce_hashes = os.listdir(target_path)
with Transport.path_table_lock: active_path_hashes = list(set([Transport.path_table[dst_hash][IDX_PT_PACKET] for dst_hash in Transport.path_table]))
with Transport.tunnels_lock: tunnel_path_hashes = list(set([path_dict[dst_hash][IDX_PT_PACKET] for path_dict in [Transport.tunnels[tunnel_id][IDX_TT_PATHS] for tunnel_id in Transport.tunnels] for dst_hash in path_dict]))
removed = 0; total = 0
for packet_hash in os.listdir(target_path):
for packet_hash in cached_announce_hashes:
remove = False
full_path = os.path.join(target_path, packet_hash)
if os.path.isfile(full_path):
try: target_hash = bytes.fromhex(packet_hash)
except: remove = True
if (not target_hash in active_paths) and (not target_hash in tunnel_paths): remove = True
if (not target_hash in active_path_hashes) and (not target_hash in tunnel_path_hashes): remove = True
if remove: os.unlink(full_path); removed += 1
total += 1
@@ -2747,8 +2803,10 @@ class Transport:
wait_time = (tx_time / on_interface.announce_cap)
on_interface.announce_allowed_at = now + wait_time
packet.is_outbound_pr = True
packet.send()
Transport.path_requests[destination_hash] = time.time()
with Transport.path_requests_lock: Transport.path_requests[destination_hash] = time.time()
@staticmethod
def remote_status_handler(path, data, request_id, link_id, remote_identity, requested_at):
@@ -2832,6 +2890,7 @@ class Transport:
unique_tag = destination_hash+tag_bytes
if packet.receiving_interface: packet.receiving_interface.received_path_request()
with Transport.discovery_pr_tags_lock:
if not unique_tag in Transport.discovery_pr_tags:
Transport.discovery_pr_tags.append(unique_tag)
@@ -2843,23 +2902,22 @@ class Transport:
tag=tag_bytes)
else: RNS.log("Ignoring duplicate path request for "+RNS.prettyhexrep(destination_hash)+" with tag "+RNS.prettyhexrep(unique_tag), RNS.LOG_EXTREME) if RNS.sl(RNS.LOG_EXTREME) else None
else: RNS.log("Ignoring tagless path request for "+RNS.prettyhexrep(destination_hash), RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
except Exception as e: RNS.log("Error while handling path request. The contained exception was: "+str(e), RNS.LOG_ERROR)
except Exception as e: RNS.log(f"Error while handling path request. The contained exception was: {e}", RNS.LOG_ERROR)
@staticmethod
def path_request(destination_hash, is_from_local_client, attached_interface, requestor_transport_id=None, tag=None):
should_search_for_unknown = False
should_ingress_limit = False
if attached_interface != None:
if RNS.Reticulum.transport_enabled() and attached_interface.mode in RNS.Interfaces.Interface.Interface.DISCOVER_PATHS_FOR:
should_search_for_unknown = True
interface_str = " on "+str(attached_interface)
else: interface_str = ""
should_ingress_limit = attached_interface.should_ingress_limit_pr()
if RNS.Reticulum.transport_enabled():
if attached_interface.mode in RNS.Interfaces.Interface.Interface.DISCOVER_PATHS_FOR: should_search_for_unknown = True
RNS.log("Path request for "+RNS.prettyhexrep(destination_hash)+interface_str, RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
if RNS.sl(RNS.LOG_DEBUG):
interface_str = f" on {attached_interface}"
RNS.log(f"Path request for {RNS.prettyhexrep(destination_hash)}{interface_str}", RNS.LOG_DEBUG)
destination_exists_on_local_client = False
if len(Transport.local_client_interfaces) > 0:
@@ -2886,7 +2944,7 @@ class Transport:
received_from = Transport.path_table[destination_hash][IDX_PT_RVCD_IF]
if packet == None:
RNS.log("Could not retrieve announce packet from cache while answering path request for "+RNS.prettyhexrep(destination_hash), RNS.LOG_ERROR)
RNS.log(f"Could not retrieve announce packet from cache while answering path request for {RNS.prettyhexrep(destination_hash)}, ignoring path request", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
elif attached_interface.mode == RNS.Interfaces.Interface.Interface.MODE_ROAMING and attached_interface == received_from:
RNS.log("Not answering path request on roaming-mode interface, since next hop is on same roaming-mode interface", RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
@@ -2956,6 +3014,15 @@ class Transport:
if destination_hash in Transport.discovery_path_requests:
RNS.log("There is already a waiting path request for "+RNS.prettyhexrep(destination_hash)+" on behalf of path request"+interface_str, RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
else:
# Abort recursive path request if receiving
# interface has PR burst active, or should
# otherwise ingress limit path requests.
if should_ingress_limit:
if RNS.sl(RNS.LOG_DEBUG):
interface_str = f" for {attached_interface}" if attached_interface else ""
RNS.log(f"Not sending recursive path request{interface_str} due to active ingress limiting", RNS.LOG_DEBUG)
return
# Forward path request on all interfaces
# except the requestor interface
RNS.log("Attempting to discover unknown path to "+RNS.prettyhexrep(destination_hash)+" on behalf of path request"+interface_str, RNS.LOG_DEBUG) if RNS.sl(RNS.LOG_DEBUG) else None
@@ -2964,9 +3031,12 @@ class Transport:
for interface in Transport.interfaces:
if not interface == attached_interface:
# Use the previously extracted tag from this path request
# on the new path requests as well, to avoid potential loops
Transport.request_path(destination_hash, on_interface=interface, tag=tag, recursive=True)
if interface.should_egress_limit_pr():
RNS.log(f"Not sending recursive path request on {interface} due to active egress limiting", RNS.LOG_EXTREME) if RNS.sl(RNS.LOG_EXTREME) else None
else:
# Use the previously extracted tag from this path request
# on the new path requests as well, to avoid potential loops
Transport.request_path(destination_hash, on_interface=interface, tag=tag, recursive=True)
elif not is_from_local_client and len(Transport.local_client_interfaces) > 0:
# Forward the path request on all local
+8 -7
View File
@@ -163,11 +163,11 @@ def listen(configdir, identitypath = None, verbosity = 0, quietness = 0, allowed
except Exception as e:
raise ValueError("Invalid destination entered. Check your input.")
except Exception as e:
print(str(e))
RNS.log(f"Could not apply allowed identity: {e}", RNS.LOG_ERROR)
RNS.exit(1)
if len(allowed_identity_hashes) < 1 and not disable_auth:
print("Warning: No allowed identities configured, rncp will not accept any files!")
RNS.log("No allowed identities configured, rncp will not accept any files!", RNS.LOG_WARNING)
def fetch_request(path, data, request_id, link_id, remote_identity, requested_at):
global allow_fetch, fetch_jail, fetch_auto_compress
@@ -217,7 +217,7 @@ def listen(configdir, identitypath = None, verbosity = 0, quietness = 0, allowed
else:
destination.register_request_handler("fetch_file", response_generator=fetch_request, allow=RNS.Destination.ALLOW_LIST, allowed_list=allowed_identity_hashes)
print("rncp listening on "+RNS.prettyhexrep(destination.hash))
RNS.log("rncp listening on "+RNS.prettyhexrep(destination.hash), RNS.LOG_INFO)
if announce >= 0:
def job():
@@ -271,15 +271,15 @@ def receive_resource_started(resource):
else:
id_str = ""
print("Starting resource transfer "+RNS.prettyhexrep(resource.hash)+id_str)
RNS.log("Starting resource transfer "+RNS.prettyhexrep(resource.hash)+id_str, RNS.LOG_INFO)
def receive_resource_concluded(resource):
global save_path, allow_overwrite_on_receive
if resource.status == RNS.Resource.COMPLETE:
print(str(resource)+" completed")
RNS.log(f"Incoming resource {resource} completed", RNS.LOG_INFO)
if resource.metadata == None:
print("Invalid data received, ignoring resource")
RNS.log("Invalid data received, ignoring resource", RNS.LOG_WARNING)
return
else:
@@ -306,13 +306,14 @@ def receive_resource_concluded(resource):
full_save_path = saved_filename+"."+str(counter)
shutil.move(resource.data.name, full_save_path)
RNS.log("Saved received file to "+full_save_path, RNS.LOG_NOTICE)
except Exception as e:
RNS.log(f"An error occurred while saving received resource: {e}", RNS.LOG_ERROR)
return
else:
print("Resource failed")
RNS.log("Resource failed", RNS.LOG_INFO)
resource_done = False
current_resource = None
+47 -6
View File
@@ -101,6 +101,7 @@ class ReticulumGitClient():
self.config = None
self.ready = False
self.destination_aliases = {}
self.remote_identity = None
self.destination = None
self.link = None
@@ -138,12 +139,6 @@ class ReticulumGitClient():
self.configpath = self.configdir+"/client_config"
self.identitypath = self.configdir+"/client_identity"
RNS.logfile = self.logfile
try: self.reticulum = RNS.Reticulum(configdir=rnsconfigdir, logdest=RNS.LOG_FILE)
except Exception as e:
print(f"Failed to initialize Reticulum: {e}", file=sys.stderr)
return
if os.path.isfile(self.configpath):
try: self.config = ConfigObj(self.configpath)
except Exception as e:
@@ -152,6 +147,12 @@ class ReticulumGitClient():
else: self.__create_default_config()
RNS.logfile = self.logfile
try: self.reticulum = RNS.Reticulum(configdir=rnsconfigdir, logdest=RNS.LOG_FILE)
except Exception as e:
print(f"Failed to initialize Reticulum: {e}", file=sys.stderr)
return
self.__apply_config()
self.ready = True
@@ -170,6 +171,18 @@ class ReticulumGitClient():
section = self.config["client"]
if "ref_batch_size" in section: self.ref_batch_size = max(0, min(1024, section.as_int("ref_batch_size")))
if "aliases" in self.config:
section = self.config["aliases"]
for alias in section:
alias_hexhash = section[alias]
len_ok = len(alias_hexhash) == RNS.Identity.TRUNCATED_HASHLENGTH//8*2
try: alias_hash = bytes.fromhex(alias_hexhash)
except: alias_hash = None
alias_exists = alias in self.destination_aliases
if not len_ok or not alias_hash: continue
if alias_exists: continue
self.destination_aliases[alias] = RNS.hexrep(alias_hash, delimit=False)
if not os.path.isfile(self.identitypath):
identity = RNS.Identity()
identity.to_file(self.identitypath)
@@ -185,6 +198,19 @@ class ReticulumGitClient():
else: self.identity = identity
self.destination_hexhash = self.__resolve_destination_alias(self.destination_hexhash)
def __resolve_destination_alias(self, alias):
def resolve(alias):
len_match = len(alias) == RNS.Identity.TRUNCATED_HASHLENGTH//8*2
try: hash_bytes = bytes.fromhex(alias)
except: hash_bytes = None
if len_match and hash_bytes: return alias
else: return self.destination_aliases[alias] if alias in self.destination_aliases else alias
resolved = resolve(alias)
return resolved
def abort(self, reason=None, code=255):
if not reason: reason = "Unknown reason"
print(f"git-remote-rns failed: {reason}", file=sys.stderr)
@@ -656,6 +682,21 @@ __default_rngit_config__ = '''# This is the default rngit client config file.
ref_batch_size = 25
[aliases]
# You can define aliases for commonly used destination
# hashes in this section. Each line must be in the format
# aliased_name = DESTINATION_HASH
#
# These hashes are used for resolving remote destinations.
# For rngit node permissions and identity resolution,
# aliases must be defined in ~/.rngit/config.
# my_node = 063d38912bffc850af4a1b8a270a9d85
# bobs_node = 714981d03e41deda0e4468cb274414cc
[logging]
# Valid log levels are 0 through 7:
# 0: Log only critical information
+5 -4
View File
@@ -165,15 +165,15 @@ class SyntaxHighlighter:
except Exception as e:
RNS.log(f"Pygments highlighting failed, falling back: {e}", RNS.LOG_WARNING)
return self._plain_text(content)
return self._plain_text(content).replace("\\", "\\\\")
# TODO: Implement Python tokenize fallback for .py files.
# For now, route to plain text
if filename and filename.endswith(".py"):
return self._plain_text(content)
return self._plain_text(content).replace("\\", "\\\\")
# Universal fallback
return self._plain_text(content)
return self._plain_text(content).replace("\\", "\\\\")
def _highlight_pygments(self, content, filename=None, language=None):
from pygments.lexers import get_lexer_for_filename, guess_lexer, get_lexer_by_name
@@ -301,7 +301,8 @@ class MicronFormatter:
return None
@staticmethod
def _escape_value(value: str) -> str: return value.replace("`", "\\`")
def _escape_value(value):
return value.replace("\\", "\\\\").replace("`", "\\`")
# Required by Pygments formatter API, returns None for Micron
def get_style_defs(self, arg=None): return None
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+133 -28
View File
@@ -31,6 +31,51 @@
import re
import RNS
# Validate ref names according to https://git-scm.com/docs/git-check-ref-format
# This may be a bit overkill, since git validates names as well, but why not.
def san_ref(ref):
if ref.startswith("-"): return None
if ref.startswith("/"): return None
if ref.endswith("/"): return None
if ref.endswith("."): return None
if " " in ref: return None
if not "/" in ref: return None
if ".." in ref: return None
if "/." in ref: return None
if "//" in ref: return None
if "\\" in ref: return None
for comp in ref.split("/"):
if comp.endswith(".lock"): return None
if not all(ord(c) >= 40 for c in ref): return None # Any control character
if "\x7f" in ref: return None # ASCII DEL (177)
if "~" in ref: return None
if "^" in ref: return None
if ":" in ref: return None
if "?" in ref: return None
if "*" in ref: return None
if "[" in ref: return None
if "@{" in ref: return None
if "@" == ref: return None
return ref
def san_refs(refs):
if not type(refs) == list: return None
for ref in refs:
if not san_ref(ref): return None
return refs
# Git SHA format validation
def san_sha(sha):
if len(sha) < 40: return None
try: bytes.fromhex(sha)
except: return None
return sha
class MarkdownToMicron:
BOLD = "`!"
BOLD_END = "`!"
@@ -88,6 +133,10 @@ class MarkdownToMicron:
self.__local_url_scope = self.local_url_scope
self.syntax_highlighter = syntax_highlighter
self.wcwidth = None
self.bold_links = True
self.underline_links = True
self.link_color = None
try:
import wcwidth
@@ -107,6 +156,7 @@ class MarkdownToMicron:
return w if w is not None and w >= 0 else len(text)
def format_block(self, text, url_scope=None):
# text = text.replace("\\", "\\\\") # Now handled in format_line instead
lines = text.split('\n')
result_lines = []
in_code_block = False
@@ -158,19 +208,21 @@ class MarkdownToMicron:
code_content = '\n'.join(code_buffer)
if self.syntax_highlighter and code_block_lang:
try:
highlighted = self.syntax_highlighter.highlight(code_content, language=code_block_lang)
result_lines.append(f"{self.CODE_BG}{self.CODE_FG}")
result_lines.append(highlighted)
result_lines.append(self.CODE_RESET)
if code_block_lang.lower() == "rawmu": result_lines.append(code_content)
else:
try:
highlighted = self.syntax_highlighter.highlight(code_content, language=code_block_lang)
result_lines.append(f"{self.CODE_BG}{self.CODE_FG}")
result_lines.append(highlighted)
result_lines.append(self.CODE_RESET)
except Exception:
# Fallback to plain literal block on any error
result_lines.append(f"{self.CODE_BG}{self.CODE_FG}")
result_lines.append(self.LITERAL_START)
result_lines.append(self._escape_literals(code_content))
result_lines.append(self.LITERAL_END)
result_lines.append(self.CODE_RESET)
except Exception:
# Fallback to plain literal block on any error
result_lines.append(f"{self.CODE_BG}{self.CODE_FG}")
result_lines.append(self.LITERAL_START)
result_lines.append(self._escape_literals(code_content))
result_lines.append(self.LITERAL_END)
result_lines.append(self.CODE_RESET)
else:
result_lines.append(f"{self.CODE_BG}{self.CODE_FG}")
result_lines.append(self.LITERAL_START)
@@ -183,10 +235,6 @@ class MarkdownToMicron:
for line in lines:
is_fence, lang_hint = self._detect_code_fence(line)
if line.startswith("-") and not line.startswith("---") and not line.startswith("- "): line = f"\\{line}"
if line.startswith(">"): line = f"\\{line}"
if line.startswith("<"): line = f"\\{line}"
if is_fence:
# Flush any pending structures before code fence
flush_quote_buffer()
@@ -255,6 +303,11 @@ class MarkdownToMicron:
def format_line(self, line, mode="normal"):
if mode == "codeblock": return self._escape_literals(line)
line = line.replace("\\", "\\\\")
if line.startswith("-") and not line.startswith("---") and not line.startswith("- "): line = f"\\{line}"
if line.startswith("<"): line = f"\\{line}"
# if line.startswith(">"): line = f"\\{line}" # Now handled by blockquotes
if self.HORIZONTAL_RULE_RE.match(line): return self._format_horizontal_rule()
@@ -296,8 +349,15 @@ class MarkdownToMicron:
url = f"{self.local_url_scope}{url}"
if anchor: url = f"{url}|anchor={anchor}"
undl = "`_" if self.underline_links else ""
bold = "`!" if self.bold_links else ""
text = text.replace('`', '')
return f"`!`[{text}`{url}]`!"
link = f"{undl}{bold}`[{text}`{url}]{bold}{undl}"
if self.link_color and len(self.link_color) == 3: link = f"`F{self.link_color}{link}`f"
if self.link_color and len(self.link_color) == 6: link = f"`FT{self.link_color}{link}`f"
return link
text = re.sub(r'\x00LINK(\d+)\x00', restore_link, text)
@@ -626,19 +686,64 @@ class MarkdownToMicron:
return " " * left + text + " " * right
else:
return text + " " * padding
def _truncate_cell(self, text, width):
if self._visible_width(text) <= width: return text
stripped = text
stripped = re.sub(r'`[FB][0-9a-fA-F]{3}', '', stripped)
stripped = re.sub(r'`[!*_]', '', stripped)
stripped = re.sub(r'`f`b', '', stripped)
if len(stripped) <= width - 1: return text
truncated = stripped[:width - 1] + ""
return truncated
truncation_point = len(text)
while truncation_point > 0 and self._visible_width(text[0:truncation_point]) >= width:
truncation_point -= 1
truncated = text[:truncation_point]
# Yes, this is convoluted, but if someone else has
# a better idea on how to handle unclosed micron
# tags in the truncated cells, I'm all ears.
active_tags = set()
fg_active = False
bg_active = False
i = 0
while i < len(truncated):
if truncated[i] == '`':
if i + 1 < len(truncated):
tag_char = truncated[i + 1]
if tag_char in '!*_=':
if tag_char in active_tags: active_tags.remove(tag_char)
else: active_tags.add(tag_char)
i += 2
continue
elif tag_char == 'f':
fg_active = False
i += 2
continue
elif tag_char == 'b':
bg_active = False
i += 2
continue
elif tag_char == 'F':
fg_active = True
if i + 2 < len(truncated) and truncated[i + 2] == 'T': i += 8
else: i += 5
continue
elif tag_char == 'B':
bg_active = True
if i + 2 < len(truncated) and truncated[i + 2] == 'T': i += 8
else: i += 5
continue
i += 1
closers = []
if fg_active: closers.append('`f')
if bg_active: closers.append('`b')
for fmt in active_tags: closers.append(f'`{fmt}')
return truncated + ''.join(closers) + ""
def _wrap_text(self, text, width):
if not text: return [""]
+368 -34
View File
@@ -40,15 +40,20 @@ import base64
from RNS._version import __version__
from RNS.vendor import umsgpack as mp
from RNS.vendor.configobj import ConfigObj
from RNS.vendor.validate import Validator
from RNS.Cryptography.Hashes import sha256
from RNS.Cryptography.Hashes import file_sha256
APP_NAME = "rns"
DEFAULT_ASPECTS = f"{APP_NAME}.id"
NO_MESSAGE = 0x01
NO_META = 0x02
PRV_EXT = "rid"
PUB_EXT = "pub"
SIG_EXT = "rsg"
MSG_EXT = "rsm"
ENCRYPT_EXT = "rfe"
CHUNK_BLOCKS = 1024*1024
ENC_CHUNK = CHUNK_BLOCKS*RNS.Identity.AES256_BLOCKSIZE
@@ -69,6 +74,8 @@ R_INVALID_ASPECTS = 9
R_INVALID_SIGNATURE = 10
R_FILE_EXISTS = 11
R_DECRYPT_FAILED = 12
R_INVALID_ARGS = 250
R_SEQUENCE_ERROR = 251
R_READ_ERROR = 252
R_WRITE_ERROR = 253
R_UNKNOWN_ERROR = 254
@@ -78,7 +85,7 @@ reticulum = None
def validate_args(args):
ops = 0;
for o in [args.encrypt, args.decrypt, args.validate, args.sign]:
for o in [args.encrypt, args.decrypt, args.validate, args.sign, args.sign_message]:
if o: ops += 1
if ops > 1: print("This utility currently only supports one of the encrypt, decrypt, sign or verify operations per invocation"); exit(1)
@@ -88,9 +95,9 @@ def validate_args(args):
if g > 1: print("The -i, -g, -m and -M args are mutually exclusive"); exit(1)
g = 0;
for a in [args.base64, args.base32]:
for a in [args.base64, args.base32, args.base256, args.hex]:
if a: g += 1
if g > 1: print("The -b and -B args are mutually exclusive"); exit(1)
if g > 1: print("The -b, -B, --hex and --base256 args are mutually exclusive"); exit(1)
return True
@@ -114,14 +121,18 @@ def main():
# Operations
parser.add_argument("-a", "--announce", metavar="aspects", action="store", nargs="?", const=DEFAULT_ASPECTS, default=None, help="announce a destination based on this Identity")
parser.add_argument("-H", "--hash", metavar="aspects", action="store", default=None, help="show destination hashes for other aspects for this Identity")
parser.add_argument("-d", "--decrypt", metavar="file", action="store", default=None, help="decrypt file")
parser.add_argument("-e", "--encrypt", metavar="file", action="store", default=None, help="encrypt file")
parser.add_argument("-V", "--validate", metavar="path", action="store", default=None, help="validate signature")
parser.add_argument("-s", "--sign", metavar="path", action="store", default=None, help="sign file")
parser.add_argument("-d", "--decrypt", metavar="file", action="store", nargs="*", default=None, help="decrypt file")
parser.add_argument("-e", "--encrypt", metavar="file", action="store", nargs="*", default=None, help="encrypt file")
parser.add_argument("-V", "--validate", metavar="path", action="store", nargs="*", default=None, help="validate signature")
parser.add_argument("-s", "--sign", metavar="path", action="store", nargs="*", default=None, help="sign file")
parser.add_argument("-S", "--sign-message", metavar="text", action="store", nargs="?", const=NO_MESSAGE, default=None, help="create embedded signed message")
parser.add_argument("-E", "--embed-meta", metavar="path", action="store", nargs="?", const=NO_META, default=None, help="embed metadata structure from file")
parser.add_argument("--meta-spec", metavar="path", action="store", nargs="?", default=None, help="validate metadata for embedding with spec from file")
parser.add_argument("--raw", action="store_true", default=False, help="sign raw input data instead of hashing first")
# I/O Control
parser.add_argument("-w", "--write", metavar="file", action="store", default=None, help="output file path", type=str)
parser.add_argument("-w", "--write", metavar="path", action="store", default=None, help="output file path", type=str)
parser.add_argument("-r", "--read", metavar="path", action="store", default=None, help="input file path for operations with optional file input", type=str)
parser.add_argument("-f", "--force", action="store_true", default=None, help="write output even if it overwrites existing files")
parser.add_argument("-I", "--stdin", action="store_true", default=False, help=argparse.SUPPRESS) # help="read input from STDIN instead of file"
parser.add_argument("-O", "--stdout", action="store_true", default=False, help=argparse.SUPPRESS) # help="write output to STDOUT instead of file"
@@ -134,15 +145,18 @@ def main():
parser.add_argument("-P", "--print-private", action="store_true", default=False, help="allow displaying private keys")
# Formatting Control
parser.add_argument("-b", "--base64", action="store_true", default=False, help="Use base64-encoded input and output")
parser.add_argument("-B", "--base32", action="store_true", default=False, help="Use base32-encoded input and output")
parser.add_argument("-b", "--base64", action="store_true", default=False, help="Use base64-encoded input and output")
parser.add_argument("-U", "--base256", action="store_true", default=False, help="Use base256-encoded input and output")
parser.add_argument("-F", "--hex", action="store_true", default=False, help="Use hex-encoded input and output")
parser.add_argument("--meta", action="store_true", default=False, help="Display RSM metadata if available")
parser.add_argument("--version", action="version", version="rnid {version}".format(version=__version__))
args = parser.parse_args()
validate_args(args)
op_requires_identity = (args.sign or args.encrypt or args.decrypt or args.announce or args.write
op_requires_identity = (args.sign or args.sign_message or args.encrypt or args.decrypt or args.announce or args.write
or args.print_identity or args.print_identity or args.export_pub or args.export_prv)
identity = get_operating_identity(args, allow_none=not op_requires_identity, no_cache=args.no_cache); op = False
@@ -154,6 +168,7 @@ def main():
if args.announce: announce(args, identity); op = True
if args.validate: validate(args, identity or args.identity); op = True
if args.sign: sign(args, identity); op = True
if args.sign_message: sign_message(args, identity); op = True
if args.encrypt: encrypt(args, identity); op = True
if args.decrypt: decrypt(args, identity); op = True
if args.write: write_identity(args, identity); op = True
@@ -375,9 +390,9 @@ def announce(args, identity):
except Exception as e: print(f"An error ocurred while attempting to send the announce: {e}"); exit(R_UNKNOWN_ERROR)
###################################
# Signing & Validation Operations #
###################################
########################################
# Canonical RSG Manipulation Functions #
########################################
def get_rsg_data(rsg):
rsg_data = None
@@ -386,11 +401,23 @@ def get_rsg_data(rsg):
elif type(rsg) == str:
try: rsg_data = base64.urlsafe_b64decode(rsg)
except: pass
try: rsg_data = base64.b32decode(rsg)
try: rsg_data = base64.b32decode(rsg.strip(RSG_PADDING))
except: pass
try: rsg_data = bytes.fromhex(rsg.strip(RSG_PADDING))
except: pass
try: rsg_data = RNS.b256_to_bytes(rsg.strip(RSG_PADDING.decode("utf-8")))
except: pass
return rsg_data
def extract_signed_rsg_data(rsg):
siglen = RNS.Identity.SIGLENGTH//8
rsg_data = get_rsg_data(rsg)
envelope = rsg_data[siglen:]
try: return mp.unpackb(envelope)
except: return None
def get_rsg_hash(message):
sha = None
if type(message) == bytes: sha = sha256(message)
@@ -436,7 +463,6 @@ def validate_rsg(rsg, message=None, required_signer=None):
if not "meta" in signed_data: return False, None, None
if not "signer" in signed_data["meta"]: return False, None, None
if not "pubkey" in signed_data["meta"]: return False, None, None
if not "note" in signed_data["meta"]: return False, None, None
try:
if type(required_signer) == RNS.Identity:
@@ -459,34 +485,149 @@ def validate_rsg(rsg, message=None, required_signer=None):
return False, signed_data, signing_identity
def create_rsg(signer_identity, message, note=None, meta=None, output="bin"):
if not output in ["bin", "hex", "base32", "base64"]: raise TypeError(f"Invalid output format for rsg creation")
if not type(signer_identity) == RNS.Identity: raise TypeError(f"{signer_identity} is not a Reticulum Identity")
if not signer_identity.get_private_key(): raise ValueError(f"{signer_identity} does not hold a private key")
def create_rsg(signer_identity, message, embed=False, meta=None, output="bin"):
if not output in ["bin", "hex", "base32", "base256", "base64"]: raise TypeError(f"Invalid output format for rsg creation")
if not type(signer_identity) == RNS.Identity: raise TypeError(f"{signer_identity} is not a Reticulum Identity")
if not signer_identity.get_private_key(): raise ValueError(f"{signer_identity} does not hold a private key")
signed_data = { "hashtype": "sha256", "hash": get_rsg_hash(message),
"meta": { "signer": signer_identity.hash,
"pubkey": signer_identity.get_public_key(),
"note" : note } }
"note" : None } } # TODO: Remove default note field in 1.2.9
if embed:
if type(message) == str: message = message.encode("utf-8")
signed_data["message"] = message
if meta and type(meta) == dict:
for key in meta:
if not key in signed_data["meta"]: signed_data["meta"]["key"] = meta["key"]
if not key in signed_data["meta"]: signed_data["meta"][key] = meta[key]
envelope = mp.packb(signed_data)
signature = signer_identity.sign(envelope)
rsg_data = signature+envelope
return signature+envelope
if output == "bin": rsg = rsg_data
elif output == "hex": rsg = RNS.hexrep(rsg_data, delimit=False).encode("ascii")
elif output == "base32": rsg = base64.b32encode(rsg_data)
elif output == "base64": rsg = base64.urlsafe_b64encode(rsg_data)
elif output == "base256": rsg = RNS.b256rep(rsg_data)
else: return None
def validate(args, identity):
return rsg
RSG_ASCII_HEADER = b"#### Start of rsg data "
RSG_ASCII_FOOTER = b" End of rsg data ####"
RSG_ASCII_ROW_WIDTH = 64
RSG_PADDING = b"="
def wrap_rsg(rsg):
if type(rsg) == str: return wrap_rsg_str(rsg)
def pad(chunk): return chunk+(RSG_ASCII_ROW_WIDTH-len(chunk))*RSG_PADDING
header = RSG_ASCII_HEADER+b"#"*(RSG_ASCII_ROW_WIDTH-len(RSG_ASCII_HEADER))
footer = b"#"*(RSG_ASCII_ROW_WIDTH-len(RSG_ASCII_FOOTER))+RSG_ASCII_FOOTER
wrapped = header+b"\n"
read = 0
while len(rsg):
chunk = rsg[:RSG_ASCII_ROW_WIDTH]
if len(chunk) < RSG_ASCII_ROW_WIDTH: chunk = pad(chunk)
wrapped += chunk+b"\n"; read += len(chunk)
rsg = rsg[len(chunk):]
wrapped += footer
return wrapped.decode("ascii")
def wrap_rsg_str(rsg):
def pad(chunk): return chunk+(RSG_ASCII_ROW_WIDTH-len(chunk))*RSG_PADDING.decode("utf-8")
header = RSG_ASCII_HEADER.decode("utf-8")+"#"*(RSG_ASCII_ROW_WIDTH-len(RSG_ASCII_HEADER.decode("utf-8")))
footer = "#"*(RSG_ASCII_ROW_WIDTH-len(RSG_ASCII_FOOTER.decode("utf-8")))+RSG_ASCII_FOOTER.decode("utf-8")
wrapped = header+"\n"
read = 0
while len(rsg):
chunk = rsg[:RSG_ASCII_ROW_WIDTH]
if len(chunk) < RSG_ASCII_ROW_WIDTH: chunk = pad(chunk)
wrapped += chunk+"\n"; read += len(chunk)
rsg = rsg[len(chunk):]
wrapped += footer
return wrapped
def unwrap_rsg(wrapped_rsg):
unwrapped = ""
if type(wrapped_rsg) == bytes: wrapped_rsg = wrapped_rsg.decode("ascii")
elif type(wrapped_rsg) == str: pass
else: return None
for line in wrapped_rsg.splitlines():
if not line.strip(): continue
if line.startswith("#"): continue
unwrapped += line
return unwrapped if unwrapped else None
def rsg_meta_from_file(path, spec_path=None):
if spec_path: meta_spec = ConfigObj(spec_path)
else: meta_spec = None
parsed = ConfigObj(path, configspec=meta_spec)
if meta_spec:
validation = parsed.validate(Validator())
if not validation == True: raise ValueError("Metadata did not pass spec validation")
return parsed.dict()
def rsg_meta_from_str(meta, spec=None):
if spec: meta_spec = ConfigObj(spec.splitlines())
else: meta_spec = None
parsed = ConfigObj(meta.splitlines(), configspec=meta_spec)
if meta_spec:
validation = parsed.validate(Validator())
if not validation == True: raise ValueError("Metadata did not pass spec validation")
return parsed.dict()
def check_release_rsm_structure(signed_data):
release_meta = signed_data.get("meta", None)
if not release_meta: return "No release metadata in manifest"
release_name = release_meta.get("name", None)
release_version = release_meta.get("version", None)
release_origin = release_meta.get("origin", None)
release_origin_path = release_meta.get("path", None)
if not release_name or not release_version: return "Incomplete package data in manifest"
if not release_origin or not release_origin_path: return "Incomplete release origin data in manifest"
if "/" in release_name or "/" in release_version: return "Invalid data in release manifest"
if len(release_origin) != RNS.Identity.TRUNCATED_HASHLENGTH//8: return "Invalid origin hash length in manifest"
if not type(release_origin) == bytes: return "Invalid origin hash in manifest"
return True
###################################
# Signing & Validation Operations #
###################################
def validate(args, identity, __recursive=False):
if type(args.validate) == list:
paths = args.validate.copy()
validated = 0
for path in paths:
args.validate = path
code = validate(args, identity, __recursive=True)
if code != 0: print(f"Sequence error on recursive signature validation"); exit(R_SEQUENCE_ERROR)
else: validated += 1
if len(paths) != validated: print(f"Sequence error on recursive signature validation"); exit(R_SEQUENCE_ERROR)
else: exit(R_OK)
msg_ext = f".{MSG_EXT}"
sig_ext = f".{SIG_EXT}"
validate_path = os.path.expanduser(args.validate)
path_is_msgfile = validate_path.lower().endswith(msg_ext)
path_is_sigfile = validate_path.lower().endswith(sig_ext)
if path_is_sigfile: signature_path = validate_path; file_path = validate_path[:-len(sig_ext)]
else: signature_path = f"{validate_path}{sig_ext}"; file_path = validate_path
signature_exists = os.path.isfile(signature_path)
file_exists = os.path.isfile(file_path)
if path_is_msgfile: return validate_message(args, identity, __recursive=__recursive)
if not file_exists: print(f"The validation target \"{file_path}\" does not exist"); exit(R_NO_FILE)
if not signature_exists: print(f"No signature file exists for \"{file_path}\""); exit(R_NO_FILE)
@@ -511,7 +652,7 @@ def validate(args, identity):
identity_str = RNS.prettyhexrep(identity) if type(identity) == bytes else f"{identity}"
signer_description = f"\nThis file was NOT signed by {identity_str or signing_identity}" if identity else ""
if not valid: print(f"Invalid signature {signature_path} for file {file_path}{signer_description}"); exit(R_INVALID_SIGNATURE)
else: print(f"Signature is valid, the file {file_path} was signed by {signing_identity}"); exit(R_OK)
else: print(f"Signature is valid, the file {file_path} was signed by {signing_identity}"); return exit(R_OK) if not __recursive else R_OK
except Exception as e: print(f"Error while validating {signature_path}: {e}"); exit(R_UNKNOWN_ERROR)
@@ -530,16 +671,100 @@ def validate(args, identity):
except Exception as e: print(f"Could not validate signature: {e}"); exit(R_READ_ERROR)
def sign(args, identity):
def validate_message(args, identity, __recursive=False):
msg_ext = f".{MSG_EXT}"
validate_path = os.path.expanduser(args.validate)
path_is_msgfile = validate_path.lower().endswith(msg_ext)
if path_is_msgfile: signature_path = validate_path
signature_exists = os.path.isfile(signature_path)
if not signature_exists: print(f"The signature file \"{signature_path}\" does not exist"); exit(R_NO_FILE)
try:
with open(signature_path, "rb") as fh: rsg = fh.read()
except Exception as e: print(f"Could not read rsg: {e}"); exit(R_READ_ERROR)
if type(identity) == str:
if not len(identity) == RNS.Reticulum.TRUNCATED_HASHLENGTH//8*2: print("Invalid identity hash length"); exit(R_INVALID_IDENTITY)
try: identity = bytes.fromhex(identity)
except Exception as e: print(f"Invalid identity hash: {e}"); exit(R_INVALID_IDENTITY)
try:
rsm_contents = extract_signed_rsg_data(rsg)
if not "message" in rsm_contents: print(f"No embedded message in {signature_path}"); exit(R_INVALID_SIGNATURE)
valid, signed_data, signing_identity = validate_rsg(rsg, message=rsm_contents["message"], required_signer=identity)
identity_str = RNS.prettyhexrep(identity) if type(identity) == bytes else f"{identity}"
signer_description = f"\nThe message was NOT signed by {identity_str or signing_identity}" if identity else ""
if not valid: print(f"Invalid signature in {signature_path}{signer_description}"); exit(R_INVALID_SIGNATURE)
else:
if args.meta:
print("RSM Metadata\n============\n")
def recurse(entry, key, level=1):
try:
indent = " "*level
if type(entry) == dict:
print(f"d{indent}{key}:")
for key in entry: recurse(entry[key], key, level=level+1)
else:
maxwidth = 64
etype = "u"
if type(entry) == str: etype = "s"
elif type(entry) == bytes: etype = "b"
elif type(entry) == list: etype = "l"
elif type(entry) == dict: etype = "d"
elif type(entry) == int: etype = "i"
elif type(entry) == float: etype = "f"
elif entry == None: etype = "N"
if key == "note" and entry == None: return # TODO: Remove this check in 1.2.9
if type(entry) == bytes: entry = RNS.hexrep(entry, delimit=False)
leadin = f"{etype}{indent}{key}="; leadln = len(leadin)
entry = f"{entry}"; chunk = entry[:maxwidth]; entry = entry[maxwidth:]
print(f"{leadin}{chunk}")
while len(entry): chunk = entry[:maxwidth]; entry = entry[maxwidth:]; print(f" "*leadln+chunk)
except: print(f"E{indent}{key}=<Decode Error>")
meta = signed_data["meta"]
for key in meta: entry = meta[key]; recurse(entry, key)
print("\nValidation\n==========")
c = ":" if not args.meta else ""
f = " following" if not args.meta else ""
print(f"\nSignature is valid, the{f} message was signed by {signing_identity}{c}\n")
if args.meta: print("Message\n=======\n")
print(signed_data["message"].decode("utf-8"))
return exit(R_OK) if not __recursive else R_OK
except Exception as e: print(f"Error while validating {signature_path}: {e}"); exit(R_UNKNOWN_ERROR)
def sign(args, identity, __recursive=False):
if type(args.sign) == list:
paths = args.sign.copy()
signed = 0
for path in paths:
args.sign = path
code = sign(args, identity, __recursive=True)
if code != 0: print(f"Sequence error on recursive signature creation"); exit(R_SEQUENCE_ERROR)
else: signed += 1
if len(paths) != signed: print(f"Sequence error on recursive signature creation"); exit(R_SEQUENCE_ERROR)
else: exit(R_OK)
sig_ext = f".{SIG_EXT}"
sign_path = os.path.expanduser(args.sign)
rsg_path = f"{sign_path}{sig_ext}"
file_exists = os.path.isfile(sign_path)
signature_exists = os.path.isfile(rsg_path)
if args.base32: output = "base32"
elif args.base64: output = "base64"
elif args.base256: output = "base256"
elif args.hex: output = "hex"
else: output = "bin"
if not identity.get_private_key(): print(f"Cannot sign \"{sign_path}\", the identity does not hold a private key"); exit(R_NO_PRVKEY)
if not file_exists: print(f"The file \"{sign_path}\" does not exist"); exit(R_NO_FILE)
if signature_exists and not args.force:
if output == "bin" and signature_exists and not args.force:
print(f"The signature file \"{rsg_path}\" already exists, not overwriting"); exit(R_FILE_EXISTS)
try:
@@ -548,21 +773,91 @@ def sign(args, identity):
with open(rsg_path, "wb") as fh: fh.write(identity.sign(data))
else:
with open(sign_path, "rb") as in_file:
rsg = create_rsg(identity, in_file)
if not rsg: print(f"No signature created, not writing"); exit(R_UNKNOWN_ERROR)
with open(sign_path, "rb") as in_file: rsg = create_rsg(identity, in_file, output=output)
if not rsg: print(f"No signature created, not writing"); exit(R_UNKNOWN_ERROR)
if output == "bin":
with open(rsg_path, "wb") as out_file: out_file.write(rsg)
print(f"Signed file {sign_path} with {identity}"); exit(R_OK)
elif output in ["base32", "base64", "base256", "hex"]: print(f"\n{wrap_rsg(rsg)}\n")
else: print("No valid output format specified"); exit(R_INVALID_ARGS)
print(f"Signed file {sign_path} with {identity}"); return exit(R_OK) if not __recursive else R_OK
except Exception as e: print(f"Could not sign {sign_path}: {e}"); exit(R_UNKNOWN_ERROR)
def sign_message(args, identity):
message = args.sign_message
meta = None
if args.base32: output = "base32"
elif args.base64: output = "base64"
elif args.base256: output = "base256"
elif args.hex: output = "hex"
else: output = "bin"
if output == "bin" and not args.write: print("No write path specified"); exit(R_INVALID_ARGS)
if not identity: print(f"Cannot sign, no working identity available"); exit(R_NO_IDENTITY)
if not identity.get_private_key(): print(f"Cannot sign, the identity does not hold a private key"); exit(R_NO_PRVKEY)
if args.read:
if message != NO_MESSAGE: print("Both an input file and command-line provided message was specified, aborting"); exit(R_INVALID_ARGS)
sign_path = os.path.expanduser(args.read)
if not os.path.isfile(sign_path): print(f"The file {sign_path} does not exist"); exit(R_NO_FILE)
with open(sign_path, "r", encoding="utf-8") as fh: message = fh.read()
if message == NO_MESSAGE: message = get_editor_content()
if not message: print("No message specified"); exit(R_INVALID_ARGS)
if args.embed_meta:
meta_path = os.path.expanduser(args.embed_meta)
meta_spec_path = meta_path+".spec" if not args.meta_spec else args.meta_spec
if not os.path.isfile(meta_path): print(f"Metadata file {meta_path} does not exist"); exit(R_NO_FILE)
if not os.path.isfile(meta_spec_path): meta_spec_path = None
spec_info = f" using spec from {meta_spec_path}" if meta_spec_path else ""
print(f"Embedding metadata from {meta_path}{spec_info}")
try: meta = rsg_meta_from_file(meta_path, spec_path=meta_spec_path)
except Exception as e: print(f"Could not load metadata from {meta_path}: {e}"); exit(R_UNKNOWN_ERROR)
try:
rsg = create_rsg(identity, message, embed=True, meta=meta, output=output)
if not rsg: print(f"No signature created, not writing"); exit(R_UNKNOWN_ERROR)
if output == "bin":
sig_ext = f".{MSG_EXT}"
rsg_path = os.path.expanduser(args.write)
rsg_path = f"{rsg_path}{sig_ext}" if not rsg_path.endswith(sig_ext) else rsg_path
signature_exists = os.path.isfile(rsg_path)
if signature_exists and not args.force: print(f"The signature file \"{rsg_path}\" already exists, not overwriting"); exit(R_FILE_EXISTS)
with open(rsg_path, "wb") as out_file: out_file.write(rsg)
print(f"Message signed with {identity} saved to {rsg_path}"); exit(R_OK)
elif output in ["base32", "base64", "base256", "hex"]: print(f"\n{wrap_rsg(rsg)}\n")
else: print("No valid output format specified"); exit(R_INVALID_ARGS)
print(f"Message signed with {identity}"); exit(R_OK)
except Exception as e: print(f"Could not sign message: {e}"); exit(R_UNKNOWN_ERROR)
######################################
# Encryption & Decryption Operations #
######################################
def encrypt(args, identity):
def encrypt(args, identity, __recursive=False):
if type(args.encrypt) == list:
paths = args.encrypt.copy()
encrypted = 0
for path in paths:
args.encrypt = path
code = encrypt(args, identity, __recursive=True)
if code != 0: print(f"Sequence error on recursive file encryption"); exit(R_SEQUENCE_ERROR)
else: encrypted += 1
if len(paths) != encrypted: print(f"Sequence error on recursive file encryption"); exit(R_SEQUENCE_ERROR)
else: exit(R_OK)
enc_ext = f".{ENCRYPT_EXT}"
encrypt_path = os.path.expanduser(args.encrypt)
rfe_path = args.write if args.write else f"{encrypt_path}{enc_ext}"
@@ -591,9 +886,21 @@ def encrypt(args, identity):
except Exception as e: print(f"\nError writing encrypted output to {rfe_path}: {e}"); exit(R_WRITE_ERROR)
except Exception as e: print(f"\nError reading {encrypt_path} for encryption: {e}"); exit(R_WRITE_ERROR)
print(f"\nFile {encrypt_path} encrypted for {identity} to {rfe_path}"); exit(R_OK)
print(f"\nFile {encrypt_path} encrypted for {identity} to {rfe_path}"); return exit(R_OK) if not __recursive else R_OK
def decrypt(args, identity, __recursive=False):
if type(args.decrypt) == list:
paths = args.decrypt.copy()
decrypted = 0
for path in paths:
args.decrypt = path
code = decrypt(args, identity, __recursive=True)
if code != 0: print(f"Sequence error on recursive file decryption"); exit(R_SEQUENCE_ERROR)
else: decrypted += 1
if len(paths) != decrypted: print(f"Sequence error on recursive file decryption"); exit(R_SEQUENCE_ERROR)
else: exit(R_OK)
def decrypt(args, identity):
enc_ext = f".{ENCRYPT_EXT}"
rfe_path = os.path.expanduser(args.decrypt)
if not rfe_path.endswith(enc_ext): print(f"The file {rfe_path} does not appear to be a Reticulum encrypted file"); exit(R_INVALID_FILE)
@@ -630,7 +937,7 @@ def decrypt(args, identity):
except Exception as e: print(f"\nError writing decrypted output to {decrypt_path}: {e}"); exit(R_WRITE_ERROR)
except Exception as e: print(f"\nError reading {rfe_path} for decryption: {e}"); exit(R_WRITE_ERROR)
print(f"\nFile {rfe_path} decrypted to {decrypt_path}"); exit(R_OK)
print(f"\nFile {rfe_path} decrypted to {decrypt_path}"); return exit(R_OK) if not __recursive else R_OK
################
@@ -725,6 +1032,33 @@ def export_prv_identity(args, identity):
# Helper & Utility Functions #
##############################
def get_editor_content():
import subprocess
from tempfile import NamedTemporaryFile
template = ""
editor = os.environ.get("EDITOR", "")
if not editor:
for fallback in ["nano", "vim", "vi"]:
try:
subprocess.run(["which", fallback], check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
editor = fallback
break
except subprocess.CalledProcessError: continue
if not editor: print("Could not launch editor"); exit(R_READ_ERROR);
try:
with NamedTemporaryFile(mode="w+", suffix=".tmp", delete=False) as tmp:
tmp_path = tmp.name
tmp.write(template)
result = subprocess.run([editor, tmp_path])
if result.returncode != 0: print(f"Editor exited with error code {result.returncode}"); os.unlink(tmp_path); exit(R_READ_ERROR)
with open(tmp_path, "r") as f: content = f.read()
os.unlink(tmp_path)
return content.encode("utf-8")
except Exception as e: print(f"Could not get content from editor: {e}"); exit(R_READ_ERROR)
def spin(until=None, msg=None, timeout=None):
i = 0
syms = "⢄⢂⢁⡁⡈⡐⡠"
+2 -1
View File
@@ -59,7 +59,8 @@ def connect_remote(destination_hash, auth_identity, timeout, no_output = False,
remote_identity = RNS.Identity.recall(destination_hash)
def remote_link_closed(link):
if link.teardown_reason == RNS.Link.TIMEOUT:
if link.teardown_reason == RNS.Link.INITIATOR_CLOSED: return
elif link.teardown_reason == RNS.Link.TIMEOUT:
if not no_output:
print(output_rst_str, end="")
print("The link timed out, exiting now")
+122 -53
View File
@@ -60,8 +60,11 @@ def size_str(num, suffix='B'):
request_result = None
request_concluded = False
first_remote_req = True
remote_destination = None
remote_link = None
def get_remote_status(destination_hash, include_lstats, identity, no_output=False, timeout=RNS.Transport.PATH_REQUEST_TIMEOUT):
global request_result, request_concluded
global request_result, request_concluded, first_remote_req, remote_destination, remote_link
link_count = None
if not RNS.Transport.has_path(destination_hash):
@@ -81,7 +84,8 @@ def get_remote_status(destination_hash, include_lstats, identity, no_output=Fals
remote_identity = RNS.Identity.recall(destination_hash)
def remote_link_closed(link):
if link.teardown_reason == RNS.Link.TIMEOUT:
if link.teardown_reason == RNS.Link.INITIATOR_CLOSED: return
elif link.teardown_reason == RNS.Link.TIMEOUT:
if not no_output:
print("\r \r", end="")
print("The link timed out, exiting now")
@@ -107,44 +111,50 @@ def get_remote_status(destination_hash, include_lstats, identity, no_output=Fals
response = request_receipt.response
if isinstance(response, list):
status = response[0]
if len(response) > 1:
link_count = response[1]
else:
link_count = None
if len(response) > 1: link_count = response[1]
else: link_count = None
request_result = (status, link_count)
request_concluded = True
def remote_link_established(link):
if not no_output:
global first_remote_req
if not no_output and first_remote_req:
print("\r \r", end="")
print("Sending request...", end=" ")
sys.stdout.flush()
link.identify(identity)
link.request("/status", data = [include_lstats], response_callback = got_response, failed_callback = request_failed)
first_remote_req = False
if not no_output:
if not remote_link and not no_output:
print("\r \r", end="")
print("Establishing link with remote transport instance...", end=" ")
sys.stdout.flush()
remote_destination = RNS.Destination(remote_identity, RNS.Destination.OUT, RNS.Destination.SINGLE, "rnstransport", "remote", "management")
link = RNS.Link(remote_destination)
link.set_link_established_callback(remote_link_established)
link.set_link_closed_callback(remote_link_closed)
if not remote_destination:
remote_destination = RNS.Destination(remote_identity, RNS.Destination.OUT, RNS.Destination.SINGLE, "rnstransport", "remote", "management")
if remote_link and remote_link.status == RNS.Link.ACTIVE:
request_concluded = False
remote_link.request("/status", data = [include_lstats], response_callback = got_response, failed_callback = request_failed)
while not request_concluded:
time.sleep(0.1)
else:
remote_link = RNS.Link(remote_destination)
remote_link.set_link_established_callback(remote_link_established)
remote_link.set_link_closed_callback(remote_link_closed)
while not request_concluded: time.sleep(0.1)
if request_result != None:
print("\r \r", end="")
return request_result
def program_setup(configdir, dispall=False, verbosity=0, name_filter=None, json=False, astats=False, lstats=False, sorting=None, sort_reverse=False,
remote=None, management_identity=None, remote_timeout=RNS.Transport.PATH_REQUEST_TIMEOUT, must_exit=True, rns_instance=None,
traffic_totals=False, discovered_interfaces=False, config_entries=False):
def program_setup(configdir, dispall=False, verbosity=0, name_filter=None, json=False, astats=False, pstats=False, lstats=False, sorting=None,
sort_reverse=False, remote=None, management_identity=None, remote_timeout=RNS.Transport.PATH_REQUEST_TIMEOUT, must_exit=True,
rns_instance=None, traffic_totals=False, discovered_interfaces=False, config_entries=False, burst_filter=False):
if remote: require_shared = False
else: require_shared = True
@@ -300,28 +310,22 @@ def program_setup(configdir, dispall=False, verbosity=0, name_filter=None, json=
if remote:
try:
if management_identity is None:
raise ValueError("Remote management requires an identity file. Use -i to specify the path to a management identity.")
if management_identity is None: raise ValueError("Remote management requires an identity file. Use -i to specify the path to a management identity.")
dest_len = (RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2
if len(remote) != dest_len:
raise ValueError("Destination length is invalid, must be {hex} hexadecimal characters ({byte} bytes).".format(hex=dest_len, byte=dest_len//2))
if len(remote) != dest_len: raise ValueError("Destination length is invalid, must be {hex} hexadecimal characters ({byte} bytes).".format(hex=dest_len, byte=dest_len//2))
try:
identity_hash = bytes.fromhex(remote)
destination_hash = RNS.Destination.hash_from_name_and_identity("rnstransport.remote.management", identity_hash)
except Exception as e:
raise ValueError("Invalid destination entered. Check your input.")
except Exception as e: raise ValueError("Invalid destination entered. Check your input.")
identity = RNS.Identity.from_file(os.path.expanduser(management_identity))
if identity == None:
raise ValueError("Could not load management identity from "+str(management_identity))
if identity == None: raise ValueError("Could not load management identity from "+str(management_identity))
try:
remote_status = get_remote_status(destination_hash, lstats, identity, no_output=json, timeout=remote_timeout)
if remote_status != None:
stats, link_count = remote_status
except Exception as e:
raise e
if remote_status != None: stats, link_count = remote_status
except Exception as e: raise e
except Exception as e:
print(str(e))
@@ -375,6 +379,10 @@ def program_setup(configdir, dispall=False, verbosity=0, name_filter=None, json=
interfaces.sort(key=lambda i: i["incoming_announce_frequency"], reverse=not sort_reverse)
if sorting == "atx":
interfaces.sort(key=lambda i: i["outgoing_announce_frequency"], reverse=not sort_reverse)
if sorting == "prx":
interfaces.sort(key=lambda i: i["incoming_pr_frequency"], reverse=not sort_reverse)
if sorting == "ptx":
interfaces.sort(key=lambda i: i["outgoing_pr_frequency"], reverse=not sort_reverse)
if sorting == "held":
interfaces.sort(key=lambda i: i["held_announces"], reverse=not sort_reverse)
@@ -393,7 +401,18 @@ def program_setup(configdir, dispall=False, verbosity=0, name_filter=None, json=
):
if not (name.startswith("I2PInterface[") and ("i2p_connectable" in ifstat and ifstat["i2p_connectable"] == False)):
if name_filter == None or name_filter.lower() in name.lower():
if name_filter == None and burst_filter == None: show_if = True
elif not burst_filter:
if not name_filter or name_filter.lower() in name.lower(): show_if = True
else: show_if = False
elif burst_filter:
burst_act = True if ("burst_active" in ifstat and "pr_burst_active" in ifstat) and (ifstat["burst_active"] or ifstat["pr_burst_active"]) else False
nfilt = name_filter.lower() in name.lower() if name_filter else False
if burst_act or nfilt: show_if = True
else: show_if = False
else: show_if = True
if show_if:
print("")
if ifstat["status"]: ss = "Up"
@@ -542,26 +561,71 @@ def program_setup(configdir, dispall=False, verbosity=0, name_filter=None, json=
elif art and arp != None: art_str = f"(t:{RNS.prettytime(art)}/p:{RNS.prettytime(arp)})"
elif art: art_str = f"(t:{RNS.prettytime(art)})"
else: art_str = ""
burst_str = ""
if "burst_active" in ifstat and ifstat["burst_active"]:
for_str = RNS.prettytime(time.time()-ifstat["burst_activated"])
burst_str = f" burst for {for_str}"
pburst_str = ""
if "pr_burst_active" in ifstat and ifstat["pr_burst_active"]:
for_str = RNS.prettytime(time.time()-ifstat["pr_burst_activated"])
pburst_str = f"burst for {for_str}"
rxb_str = ""+RNS.prettysize(ifstat["rxb"])
txb_str = ""+RNS.prettysize(ifstat["txb"])
strdiff = len(rxb_str)-len(txb_str)
if strdiff > 0: txb_str += " "*strdiff
elif strdiff < 0: rxb_str += " "*-strdiff
asr = False
if astats and "incoming_announce_frequency" in ifstat and ifstat["incoming_announce_frequency"] != None:
oaf = RNS.prettyfrequency(ifstat["outgoing_announce_frequency"])+""
iaf = RNS.prettyfrequency(ifstat["incoming_announce_frequency"])+""
if clients != None and clients > 0: pc_str = f"{RNS.prettyfrequency(ifstat["outgoing_announce_frequency"]/clients)}/c"
oan = ifstat["outgoing_announce_frequency"]
ian = ifstat["incoming_announce_frequency"]
if name.startswith("Shared Instance[") and clients and clients > 0: oan = oan-(oan/clients) # Sub rnstatus own part
oaf = RNS.prettyfrequency(oan, d=1, lpf=True)
iaf = RNS.prettyfrequency(ian, d=1, lpf=True)
cspec = "c"
if clients == None and "peers" in ifstat and ifstat["peers"]: clients = ifstat["peers"]; cspec = "p"
if clients != None and clients > 0: pc_str = f"{RNS.prettyfrequency(ifstat['outgoing_announce_frequency']/clients, d=1, lpf=True)}/{cspec}"
else: pc_str = ""
strdiff = len(oaf)-len(iaf)
if strdiff > 0: iaf += " "*strdiff
elif strdiff < 0: oaf += " "*-strdiff
strdiff = len(rxb_str)-len(oaf)
if strdiff > 0: oaf += " "*strdiff
elif strdiff < 0: txb_str += " "*-strdiff; rxb_str += " "*-strdiff
asr = True
psr = False
if pstats and "incoming_pr_frequency" in ifstat and ifstat["incoming_pr_frequency"] != None:
opn = ifstat["outgoing_pr_frequency"]
ipn = ifstat["incoming_pr_frequency"]
if name.startswith("Shared Instance[") and clients and clients > 0: opn = opn-(opn/clients) # Sub rnstatus own part
if astats:
opf = ""+RNS.prettyfrequency(opn, d=1, lpf=True)
ipf = ""+RNS.prettyfrequency(ipn, d=1, lpf=True)
else:
opf = RNS.prettyfrequency(opn,d=1, lpf=True)+""
ipf = RNS.prettyfrequency(ipn,d=1, lpf=True)+""
cspec = "c"
if clients == None and "peers" in ifstat and ifstat["peers"]: clients = ifstat["peers"]; cspec = "p"
if clients != None and clients > 0: rpc_str = f"{RNS.prettyfrequency(ifstat['outgoing_pr_frequency']/clients, d=1, lpf=True)}/{cspec}"
else: rpc_str = ""
psr = True
if not asr: iaf = ""; oaf = ""
if not psr: ipf = ""; opf = ""
amlen = max(len(iaf), len(oaf))
iaf += (amlen-len(iaf))*" "+""
oaf += (amlen-len(oaf))*" "+""
mlen = max(max(len(iaf), len(oaf), len(rxb_str), len(txb_str), len(ipf), len(opf)), 10)
iaf += (mlen-len(iaf))*" "
oaf += (mlen-len(oaf))*" "
ipf += (mlen-len(ipf))*" "
opf += (mlen-len(opf))*" "
rxb_str += (mlen-len(rxb_str))*" "
txb_str += (mlen-len(txb_str))*" "
if psr:
print(f" Path Rqs. : {opf} {rpc_str}")
print(f" {ipf} {pburst_str}")
if asr:
print(f" Announces : {oaf} {pc_str}")
print(f" {iaf} {art_str}")
print(f" {iaf} {art_str}{burst_str}")
rxstat = rxb_str
txstat = txb_str
@@ -624,9 +688,11 @@ def main(must_exit=True, rns_instance=None):
parser.add_argument("-a", "--all", action="store_true", help="show all interfaces", default=False)
parser.add_argument("-A", "--announce-stats", action="store_true", help="show announce stats", default=False)
parser.add_argument("-P", "--pr-stats", action="store_true", help="show path request stats", default=False)
parser.add_argument("-l", "--link-stats", action="store_true", help="show link stats", default=False)
parser.add_argument("-B", "--burst", action="store_true", help="only show interfaces with active bursts", default=False)
parser.add_argument("-t", "--totals", action="store_true", help="display traffic totals", default=False)
parser.add_argument("-s", "--sort", action="store", help="sort interfaces by [rate, traffic, rx, tx, rxs, txs, announces, arx, atx, held]", default=None, type=str)
parser.add_argument("-s", "--sort", action="store", help="sort interfaces by [rate, traffic, rx, tx, rxs, txs, announces, arx, atx, prx, ptx, held]", default=None, type=str)
parser.add_argument("-r", "--reverse", action="store_true", help="reverse sorting", default=False)
parser.add_argument("-j", "--json", action="store_true", help="output in JSON format", default=False)
parser.add_argument("-R", action="store", metavar="hash", help="transport identity hash of remote instance to get status from", default=None, type=str)
@@ -654,15 +720,16 @@ def main(must_exit=True, rns_instance=None):
exit(1)
while True:
st = time.time()
buffer = io.StringIO()
old_stdout = sys.stdout
sys.stdout = buffer
try:
program_setup(configdir = configarg, dispall = args.all, verbosity=args.verbose, name_filter=args.filter, json=args.json,
astats=args.announce_stats, lstats=args.link_stats, sorting=args.sort, sort_reverse=args.reverse, remote=args.R,
management_identity=args.i, remote_timeout=args.w, must_exit=False, rns_instance=reticulum, traffic_totals=args.totals,
discovered_interfaces=args.discovered, config_entries=args.D)
astats=args.announce_stats, pstats=args.pr_stats, lstats=args.link_stats, sorting=args.sort, sort_reverse=args.reverse,
remote=args.R, management_identity=args.i, remote_timeout=args.w, must_exit=False, rns_instance=reticulum,
traffic_totals=args.totals, discovered_interfaces=args.discovered, config_entries=args.D, burst_filter=args.burst)
finally:
sys.stdout = old_stdout
@@ -670,14 +737,16 @@ def main(must_exit=True, rns_instance=None):
output = buffer.getvalue()
print("\033[H\033[2J", end="")
print(output, end="", flush=True)
time.sleep(args.monitor_interval)
td = time.time()-st
sleeptime = max(args.monitor_interval-td, 0.2)
time.sleep(sleeptime)
else:
program_setup(configdir = configarg, dispall = args.all, verbosity=args.verbose, name_filter=args.filter, json=args.json,
astats=args.announce_stats, lstats=args.link_stats, sorting=args.sort, sort_reverse=args.reverse, remote=args.R,
management_identity=args.i, remote_timeout=args.w, must_exit=must_exit, rns_instance=rns_instance, traffic_totals=args.totals,
discovered_interfaces=args.discovered, config_entries=args.D)
astats=args.announce_stats, pstats=args.pr_stats, lstats=args.link_stats, sorting=args.sort, sort_reverse=args.reverse,
remote=args.R, management_identity=args.i, remote_timeout=args.w, must_exit=must_exit, rns_instance=rns_instance,
traffic_totals=args.totals, discovered_interfaces=args.discovered, config_entries=args.D, burst_filter=args.burst)
except KeyboardInterrupt:
print("")
+95 -114
View File
@@ -94,22 +94,14 @@ _always_override_destination = False
logging_lock = threading.Lock()
def loglevelname(level):
if (level == LOG_CRITICAL):
return "[Critical]"
if (level == LOG_ERROR):
return "[Error] "
if (level == LOG_WARNING):
return "[Warning] "
if (level == LOG_NOTICE):
return "[Notice] "
if (level == LOG_INFO):
return "[Info] "
if (level == LOG_VERBOSE):
return "[Verbose] "
if (level == LOG_DEBUG):
return "[Debug] "
if (level == LOG_EXTREME):
return "[Extra] "
if (level == LOG_CRITICAL): return "[Critical]"
if (level == LOG_ERROR): return "[Error] "
if (level == LOG_WARNING): return "[Warning] "
if (level == LOG_NOTICE): return "[Notice] "
if (level == LOG_INFO): return "[Info] "
if (level == LOG_VERBOSE): return "[Verbose] "
if (level == LOG_DEBUG): return "[Debug] "
if (level == LOG_EXTREME): return "[Extra] "
return "Unknown"
@@ -133,13 +125,10 @@ def log(msg, level=3, _override_destination = False, pt=False):
global _always_override_destination, compact_log_fmt
msg = str(msg)
if loglevel >= level:
if pt:
logstring = "["+precise_timestamp_str(time.time())+"] "+loglevelname(level)+" "+msg
if pt: logstring = "["+precise_timestamp_str(time.time())+"] "+loglevelname(level)+" "+msg
else:
if not compact_log_fmt:
logstring = "["+timestamp_str(time.time())+"] "+loglevelname(level)+" "+msg
else:
logstring = "["+timestamp_str(time.time())+"] "+msg
if not compact_log_fmt: logstring = "["+timestamp_str(time.time())+"] "+loglevelname(level)+" "+msg
else: logstring = "["+timestamp_str(time.time())+"] "+msg
with logging_lock:
if (logdest == LOG_STDOUT or _always_override_destination or _override_destination):
@@ -182,14 +171,11 @@ def trace_exception(e):
log(exception_info, LOG_ERROR)
def hexrep(data, delimit=True):
try:
iter(data)
except TypeError:
data = [data]
try: iter(data)
except TypeError: data = [data]
delimiter = ":"
if not delimit:
delimiter = ""
if not delimit: delimiter = ""
hexrep = delimiter.join("{:02x}".format(c) for c in data)
return hexrep
@@ -198,11 +184,6 @@ def prettyhexrep(data):
hexrep = "<"+delimiter.join("{:02x}".format(c) for c in data)+">"
return hexrep
def prettyb256rep(data):
delimiter = ""
b256rep = "<"+delimiter.join(b256_rep(c) for c in data)+">"
return b256rep
def prettyspeed(num, suffix="b"):
return prettysize(num/8, suffix=suffix)+"ps"
@@ -217,23 +198,24 @@ def prettysize(num, suffix='B'):
for unit in units:
if abs(num) < 1000.0:
if unit == "":
return "%.0f %s%s" % (num, unit, suffix)
else:
return "%.2f %s%s" % (num, unit, suffix)
if unit == "": return "%.0f %s%s" % (num, unit, suffix)
else: return "%.2f %s%s" % (num, unit, suffix)
num /= 1000.0
return "%.2f%s%s" % (num, last_unit, suffix)
def prettyfrequency(hz, suffix="Hz"):
def prettyfrequency(hz, suffix="Hz", d=2, lpf=False):
if hz == 0: return "0 Hz"
num = hz*1e6
units = ["µ", "m", "", "K","M","G","T","P","E","Z"]
if not lpf: num = hz*1e6
else: num = hz
if not lpf: units = ["µ", "m", "", "K","M","G","T","P","E","Z"]
else: units = ["", "K","M","G","T","P","E","Z"]
last_unit = "Y"
for unit in units:
if abs(num) < 1000.0:
return "%.2f %s%s" % (num, unit, suffix)
if d == 2: return "%.2f %s%s" % (num, unit, suffix)
else: return "%s %s%s" % (str(round(num,d)), unit, suffix)
num /= 1000.0
return "%.2f%s%s" % (num, last_unit, suffix)
@@ -248,8 +230,7 @@ def prettydistance(m, suffix="m"):
if unit == "m": divisor = 10
if unit == "c": divisor = 100
if abs(num) < divisor:
return "%.2f %s%s" % (num, unit, suffix)
if abs(num) < divisor: return "%.2f %s%s" % (num, unit, suffix)
num /= divisor
return "%.2f %s%s" % (num, last_unit, suffix)
@@ -266,10 +247,8 @@ def prettytime(time, verbose=False, compact=False):
time %= 3600
minutes = int(time // 60)
time %= 60
if compact:
seconds = int(time)
else:
seconds = round(time, 2)
if compact: seconds = int(time)
else: seconds = round(time, 2)
ss = "" if seconds == 1 else "s"
sm = "" if minutes == 1 else "s"
@@ -298,22 +277,16 @@ def prettytime(time, verbose=False, compact=False):
tstr = ""
for c in components:
i += 1
if i == 1:
pass
elif i < len(components):
tstr += ", "
elif i == len(components):
tstr += " and "
if i == 1: pass
elif i < len(components): tstr += ", "
elif i == len(components): tstr += " and "
tstr += c
if tstr == "":
return "0s"
if tstr == "": return "0s"
else:
if not neg:
return tstr
else:
return f"-{tstr}"
if not neg: return tstr
else: return f"-{tstr}"
def prettyshorttime(time, verbose=False, compact=False):
neg = False
@@ -325,10 +298,8 @@ def prettyshorttime(time, verbose=False, compact=False):
seconds = int(time // 1e6); time %= 1e6
milliseconds = int(time // 1e3); time %= 1e3
if compact:
microseconds = int(time)
else:
microseconds = round(time, 2)
if compact: microseconds = int(time)
else: microseconds = round(time, 2)
ss = "" if seconds == 1 else "s"
sms = "" if milliseconds == 1 else "s"
@@ -352,22 +323,16 @@ def prettyshorttime(time, verbose=False, compact=False):
tstr = ""
for c in components:
i += 1
if i == 1:
pass
elif i < len(components):
tstr += ", "
elif i == len(components):
tstr += " and "
if i == 1: pass
elif i < len(components): tstr += ", "
elif i == len(components): tstr += " and "
tstr += c
if tstr == "":
return "0us"
if tstr == "": return "0us"
else:
if not neg:
return tstr
else:
return f"-{tstr}"
if not neg: return tstr
else: return f"-{tstr}"
def phyparams():
print("Required Physical Layer MTU : "+str(Reticulum.MTU)+" bytes")
@@ -378,8 +343,7 @@ def phyparams():
print("Link Public Key Size : "+str(Link.ECPUBSIZE*8)+" bits")
print("Link Private Key Size : "+str(Link.KEYSIZE*8)+" bits")
def panic():
os._exit(255)
def panic(): os._exit(255)
exit_called = False
def exit(code=0):
@@ -400,8 +364,7 @@ class Profiler:
@staticmethod
def get_profiler(tag=None, super_tag=None):
if tag in Profiler.profilers:
return Profiler.profilers[tag]
if tag in Profiler.profilers: return Profiler.profilers[tag]
else:
profiler = Profiler(tag, super_tag)
Profiler.profilers[tag] = profiler
@@ -413,13 +376,14 @@ class Profiler:
self.pause_started = None
self.tag = tag
self.super_tag = super_tag
if self.super_tag in Profiler.profilers:
self.super_profiler = Profiler.profilers[self.super_tag]
self.pause_super = self.super_profiler.pause
self.resume_super = self.super_profiler.resume
else:
def noop(self=None):
pass
def noop(self=None): pass
self.super_profiler = None
self.pause_super = noop
self.resume_super = noop
@@ -429,8 +393,7 @@ class Profiler:
tag = self.tag
super_tag = self.super_tag
thread_ident = threading.get_ident()
if not tag in Profiler.tags:
Profiler.tags[tag] = {"threads": {}, "super": super_tag}
if not tag in Profiler.tags: Profiler.tags[tag] = {"threads": {}, "super": super_tag}
if not thread_ident in Profiler.tags[tag]["threads"]:
Profiler.tags[tag]["threads"][thread_ident] = {"current_start": None, "captures": []}
@@ -466,8 +429,7 @@ class Profiler:
self.resume_super()
@staticmethod
def ran():
return Profiler._ran
def ran(): return Profiler._ran
@staticmethod
def results():
@@ -484,41 +446,35 @@ class Profiler:
sample_count = len(thread_captures)
if sample_count > 1:
thread_results = {
"count": sample_count,
"mean": mean(thread_captures),
"median": median(thread_captures),
"stdev": stdev(thread_captures)
}
thread_results = { "count": sample_count,
"mean": mean(thread_captures),
"median": median(thread_captures),
"stdev": stdev(thread_captures) }
elif sample_count == 1:
thread_results = {
"count": sample_count,
"mean": mean(thread_captures),
"median": median(thread_captures),
"stdev": None
}
thread_results = { "count": sample_count,
"mean": mean(thread_captures),
"median": median(thread_captures),
"stdev": None }
tag_captures.extend(thread_captures)
sample_count = len(tag_captures)
if sample_count > 1:
tag_results = {
"name": tag,
"super": tag_entry["super"],
"count": len(tag_captures),
"mean": mean(tag_captures),
"median": median(tag_captures),
"stdev": stdev(tag_captures)
}
tag_results = { "name": tag,
"super": tag_entry["super"],
"count": len(tag_captures),
"mean": mean(tag_captures),
"median": median(tag_captures),
"stdev": stdev(tag_captures) }
elif sample_count == 1:
tag_results = {
"name": tag,
"super": tag_entry["super"],
"count": len(tag_captures),
"mean": mean(tag_captures),
"median": median(tag_captures),
"stdev": None
}
tag_results = { "name": tag,
"super": tag_entry["super"],
"count": len(tag_captures),
"mean": mean(tag_captures),
"median": median(tag_captures),
"stdev": None }
results[tag] = tag_results
@@ -552,6 +508,8 @@ class Profiler:
profile = Profiler.get_profiler
# The base-256 table is likely to change. Currently, it is just
# experimental, so don't count on it too much just yet.
b256 = [
# 0 1 2 3 4 5 6 7 8 9 A B C D F F
"a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p", # 0x0 Latin & numerals
@@ -572,4 +530,27 @@ b256 = [
"𐌳","𐌸","𐌾","𐐀","𐐁","𐐂","𐐆","𐐇","𐐈","𐐉","𐐊","𐐋","𐐌","𐐍","𐐎","𐐏", # 0xF Gothic & Deseret
]
def b256_rep(input_byte): return b256[int(input_byte)]
def b256rep(data): return "".join(bytes_to_b256(data))
def prettyb256rep(data): return f"<{b256rep(data)}>"
def b256_to_byte(point):
if not type(point) == str or not len(point) == 1: raise TypeError("Invalid input data for base256 byte decode")
try: return b256.index(point)
except Exception as e: raise ValueError(f"Could not decode base256 byte: {e}")
def b256_to_bytes(b256rep):
if not type(b256rep) == str: raise TypeError("Invalid input data for base256 decode")
try: return bytes([b256.index(c) for c in b256rep])
except Exception as e: raise ValueError(f"Could not decode base256: {e}")
def byte_to_b256(input_byte):
if type(input_byte) == bytes and not len(input_byte) == 1: TypeError("Invalid input data for base256 byte encode")
if type(input_byte) == bytes and len(input_byte) == 1: input_byte = ord(input_byte)
if not type(input_byte) == int: raise TypeError("Invalid input data for base256 byte encode")
try: return b256[int(input_byte)]
except Exception as e: raise TypeError(f"Could not encode byte to base256: {e}")
def bytes_to_b256(data):
if not type(data) == bytes: raise TypeError("Invalid input data for base256 encode")
try: return [byte_to_b256(c) for c in data]
except Exception as e: raise TypeError(f"Could not encode to base256: {e}")
+1 -1
View File
@@ -1 +1 @@
__version__ = "1.2.4"
__version__ = "1.2.9"
+537
View File
@@ -0,0 +1,537 @@
# validate.py
# -*- coding: utf-8 -*-
# pylint: disable=
#
# A Validator object.
#
# Copyright (C) 2005-2014:
# (name) : (email)
# Michael Foord: fuzzyman AT voidspace DOT org DOT uk
# Mark Andrews: mark AT la-la DOT com
# Nicola Larosa: nico AT tekNico DOT net
# Rob Dennis: rdennis AT gmail DOT com
# Eli Courtwright: eli AT courtwright DOT org
# This software is licensed under the terms of the BSD license.
# http://opensource.org/licenses/BSD-3-Clause
# ConfigObj 5 - main repository for documentation and issue tracking:
# https://github.com/DiffSK/configobj
import re
import sys
from pprint import pprint
__version__ = '1.0.1'
__all__ = (
'dottedQuadToNum',
'numToDottedQuad',
'ValidateError',
'VdtUnknownCheckError',
'VdtParamError',
'VdtTypeError',
'VdtValueError',
'VdtValueTooSmallError',
'VdtValueTooBigError',
'VdtValueTooShortError',
'VdtValueTooLongError',
'VdtMissingValue',
'Validator',
'is_integer',
'is_float',
'is_boolean',
'is_list',
'is_tuple',
'is_ip_addr',
'is_string',
'is_int_list',
'is_bool_list',
'is_float_list',
'is_string_list',
'is_ip_addr_list',
'is_mixed_list',
'is_option',
)
_list_arg = re.compile(r'''
(?:
([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*list\(
(
(?:
\s*
(?:
(?:".*?")| # double quotes
(?:'.*?')| # single quotes
(?:[^'",\s\)][^,\)]*?) # unquoted
)
\s*,\s*
)*
(?:
(?:".*?")| # double quotes
(?:'.*?')| # single quotes
(?:[^'",\s\)][^,\)]*?) # unquoted
)? # last one
)
\)
)
''', re.VERBOSE | re.DOTALL) # two groups
_list_members = re.compile(r'''
(
(?:".*?")| # double quotes
(?:'.*?')| # single quotes
(?:[^'",\s=][^,=]*?) # unquoted
)
(?:
(?:\s*,\s*)|(?:\s*$) # comma
)
''', re.VERBOSE | re.DOTALL) # one group
_paramstring = r'''
(?:
(
(?:
[a-zA-Z_][a-zA-Z0-9_]*\s*=\s*list\(
(?:
\s*
(?:
(?:".*?")| # double quotes
(?:'.*?')| # single quotes
(?:[^'",\s\)][^,\)]*?) # unquoted
)
\s*,\s*
)*
(?:
(?:".*?")| # double quotes
(?:'.*?')| # single quotes
(?:[^'",\s\)][^,\)]*?) # unquoted
)? # last one
\)
)|
(?:
(?:".*?")| # double quotes
(?:'.*?')| # single quotes
(?:[^'",\s=][^,=]*?)| # unquoted
(?: # keyword argument
[a-zA-Z_][a-zA-Z0-9_]*\s*=\s*
(?:
(?:".*?")| # double quotes
(?:'.*?')| # single quotes
(?:[^'",\s=][^,=]*?) # unquoted
)
)
)
)
(?:
(?:\s*,\s*)|(?:\s*$) # comma
)
)
'''
_matchstring = '^%s*' % _paramstring
def dottedQuadToNum(ip):
# import here to avoid it when ip_addr values are not used
import socket, struct
try:
return struct.unpack('!L',
socket.inet_aton(ip.strip()))[0]
except socket.error:
raise ValueError('Not a good dotted-quad IP: %s' % ip)
return
def numToDottedQuad(num):
# import here to avoid it when ip_addr values are not used
import socket, struct
# no need to intercept here, 4294967295L is fine
if num > int(4294967295) or num < 0:
raise ValueError('Not a good numeric IP: %s' % num)
try:
return socket.inet_ntoa(
struct.pack('!L', int(num)))
except (socket.error, struct.error, OverflowError):
raise ValueError('Not a good numeric IP: %s' % num)
class ValidateError(Exception):
"""
This error indicates that the check failed.
It can be the base class for more specific errors.
"""
class VdtMissingValue(ValidateError):
"""No value was supplied to a check that needed one."""
class VdtUnknownCheckError(ValidateError):
def __init__(self, value):
ValidateError.__init__(self, 'the check "{}" is unknown.'.format(value))
class VdtParamError(SyntaxError):
NOT_GIVEN = object()
def __init__(self, name_or_msg, value=NOT_GIVEN):
if value is self.NOT_GIVEN:
SyntaxError.__init__(self, name_or_msg)
else:
SyntaxError.__init__(self, 'passed an incorrect value "{}" for parameter "{}".'.format(value, name_or_msg))
class VdtTypeError(ValidateError):
def __init__(self, value):
ValidateError.__init__(self, 'the value "{}" is of the wrong type.'.format(value))
class VdtValueError(ValidateError):
def __init__(self, value):
ValidateError.__init__(self, 'the value "{}" is unacceptable.'.format(value))
class VdtValueTooSmallError(VdtValueError):
def __init__(self, value):
ValidateError.__init__(self, 'the value "{}" is too small.'.format(value))
class VdtValueTooBigError(VdtValueError):
def __init__(self, value):
ValidateError.__init__(self, 'the value "{}" is too big.'.format(value))
class VdtValueTooShortError(VdtValueError):
def __init__(self, value):
ValidateError.__init__(
self,
'the value "{}" is too short.'.format(value))
class VdtValueTooLongError(VdtValueError):
def __init__(self, value):
ValidateError.__init__(self, 'the value "{}" is too long.'.format(value))
class Validator(object):
# this regex does the initial parsing of the checks
_func_re = re.compile(r'([^\(\)]+?)\((.*)\)', re.DOTALL)
# this regex takes apart keyword arguments
_key_arg = re.compile(r'^([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*(.*)$', re.DOTALL)
# this regex finds keyword=list(....) type values
_list_arg = _list_arg
# this regex takes individual values out of lists - in one pass
_list_members = _list_members
# These regexes check a set of arguments for validity
# and then pull the members out
_paramfinder = re.compile(_paramstring, re.VERBOSE | re.DOTALL)
_matchfinder = re.compile(_matchstring, re.VERBOSE | re.DOTALL)
def __init__(self, functions=None):
self.functions = {
'': self._pass,
'integer': is_integer,
'float': is_float,
'boolean': is_boolean,
'ip_addr': is_ip_addr,
'string': is_string,
'list': is_list,
'tuple': is_tuple,
'int_list': is_int_list,
'float_list': is_float_list,
'bool_list': is_bool_list,
'ip_addr_list': is_ip_addr_list,
'string_list': is_string_list,
'mixed_list': is_mixed_list,
'pass': self._pass,
'option': is_option,
'force_list': force_list,
}
if functions is not None:
self.functions.update(functions)
# tekNico: for use by ConfigObj
self.baseErrorClass = ValidateError
self._cache = {}
def check(self, check, value, missing=False):
fun_name, fun_args, fun_kwargs, default = self._parse_with_caching(check)
if missing:
if default is None:
# no information needed here - to be handled by caller
raise VdtMissingValue()
value = self._handle_none(default)
if value is None:
return None
return self._check_value(value, fun_name, fun_args, fun_kwargs)
def _handle_none(self, value):
if value == 'None':
return None
elif value in ("'None'", '"None"'):
# Special case a quoted None
value = self._unquote(value)
return value
def _parse_with_caching(self, check):
if check in self._cache:
fun_name, fun_args, fun_kwargs, default = self._cache[check]
# We call list and dict below to work with *copies* of the data
# rather than the original (which are mutable of course)
fun_args = list(fun_args)
fun_kwargs = dict(fun_kwargs)
else:
fun_name, fun_args, fun_kwargs, default = self._parse_check(check)
fun_kwargs = {str(key): value for (key, value) in list(fun_kwargs.items())}
self._cache[check] = fun_name, list(fun_args), dict(fun_kwargs), default
return fun_name, fun_args, fun_kwargs, default
def _check_value(self, value, fun_name, fun_args, fun_kwargs):
try:
fun = self.functions[fun_name]
except KeyError:
raise VdtUnknownCheckError(fun_name)
else:
return fun(value, *fun_args, **fun_kwargs)
def _parse_check(self, check):
fun_match = self._func_re.match(check)
if fun_match:
fun_name = fun_match.group(1)
arg_string = fun_match.group(2)
arg_match = self._matchfinder.match(arg_string)
if arg_match is None:
# Bad syntax
raise VdtParamError('Bad syntax in check "%s".' % check)
fun_args = []
fun_kwargs = {}
# pull out args of group 2
for arg in self._paramfinder.findall(arg_string):
# args may need whitespace removing (before removing quotes)
arg = arg.strip()
listmatch = self._list_arg.match(arg)
if listmatch:
key, val = self._list_handle(listmatch)
fun_kwargs[key] = val
continue
keymatch = self._key_arg.match(arg)
if keymatch:
val = keymatch.group(2)
if not val in ("'None'", '"None"'):
# Special case a quoted None
val = self._unquote(val)
fun_kwargs[keymatch.group(1)] = val
continue
fun_args.append(self._unquote(arg))
else:
# allows for function names without (args)
return check, (), {}, None
# Default must be deleted if the value is specified too,
# otherwise the check function will get a spurious "default" keyword arg
default = fun_kwargs.pop('default', None)
return fun_name, fun_args, fun_kwargs, default
def _unquote(self, val):
if (len(val) >= 2) and (val[0] in ("'", '"')) and (val[0] == val[-1]):
val = val[1:-1]
return val
def _list_handle(self, listmatch):
out = []
name = listmatch.group(1)
args = listmatch.group(2)
for arg in self._list_members.findall(args):
out.append(self._unquote(arg))
return name, out
def _pass(self, value):
return value
def get_default_value(self, check):
fun_name, fun_args, fun_kwargs, default = self._parse_with_caching(check)
if default is None:
raise KeyError('Check "%s" has no default value.' % check)
value = self._handle_none(default)
if value is None:
return value
return self._check_value(value, fun_name, fun_args, fun_kwargs)
def _is_num_param(names, values, to_float=False):
fun = to_float and float or int
out_params = []
for (name, val) in zip(names, values):
if val is None:
out_params.append(val)
elif isinstance(val, (int, float, str)):
try:
out_params.append(fun(val))
except ValueError:
raise VdtParamError(name, val)
else:
raise VdtParamError(name, val)
return out_params
# built in checks
# you can override these by setting the appropriate name
# in Validator.functions
# note: if the params are specified wrongly in your input string,
# you will also raise errors.
def is_integer(value, min=None, max=None):
(min_val, max_val) = _is_num_param( # pylint: disable=unbalanced-tuple-unpacking
('min', 'max'), (min, max))
if not isinstance(value, (int, str)):
raise VdtTypeError(value)
if isinstance(value, str):
# if it's a string - does it represent an integer ?
try:
value = int(value)
except ValueError:
raise VdtTypeError(value)
if (min_val is not None) and (value < min_val):
raise VdtValueTooSmallError(value)
if (max_val is not None) and (value > max_val):
raise VdtValueTooBigError(value)
return value
def is_float(value, min=None, max=None):
(min_val, max_val) = _is_num_param(
('min', 'max'), (min, max), to_float=True)
if not isinstance(value, (int, float, str)):
raise VdtTypeError(value)
if not isinstance(value, float):
# if it's a string - does it represent a float ?
try:
value = float(value)
except ValueError:
raise VdtTypeError(value)
if (min_val is not None) and (value < min_val):
raise VdtValueTooSmallError(value)
if (max_val is not None) and (value > max_val):
raise VdtValueTooBigError(value)
return value
bool_dict = {
True: True, 'on': True, '1': True, 'true': True, 'yes': True,
False: False, 'off': False, '0': False, 'false': False, 'no': False,
}
def is_boolean(value):
if isinstance(value, str):
try:
return bool_dict[value.lower()]
except KeyError:
raise VdtTypeError(value)
# we do an equality test rather than an identity test
# this ensures Python 2.2 compatibility
# and allows 0 and 1 to represent True and False
if value == False:
return False
elif value == True:
return True
else:
raise VdtTypeError(value)
def is_ip_addr(value):
if not isinstance(value, str):
raise VdtTypeError(value)
value = value.strip()
try:
dottedQuadToNum(value)
except ValueError:
raise VdtValueError(value)
return value
def is_list(value, min=None, max=None):
(min_len, max_len) = _is_num_param( # pylint: disable=unbalanced-tuple-unpacking
('min', 'max'), (min, max))
if isinstance(value, str):
raise VdtTypeError(value)
try:
num_members = len(value)
except TypeError:
raise VdtTypeError(value)
if min_len is not None and num_members < min_len:
raise VdtValueTooShortError(value)
if max_len is not None and num_members > max_len:
raise VdtValueTooLongError(value)
return list(value)
def is_tuple(value, min=None, max=None):
return tuple(is_list(value, min, max))
def is_string(value, min=None, max=None):
if not isinstance(value, str):
raise VdtTypeError(value)
(min_len, max_len) = _is_num_param(
('min', 'max'), (min, max))
try:
num_members = len(value)
except TypeError:
raise VdtTypeError(value)
if min_len is not None and num_members < min_len:
raise VdtValueTooShortError(value)
if max_len is not None and num_members > max_len:
raise VdtValueTooLongError(value)
return value
def is_int_list(value, min=None, max=None):
return [is_integer(mem) for mem in is_list(value, min, max)]
def is_bool_list(value, min=None, max=None):
return [is_boolean(mem) for mem in is_list(value, min, max)]
def is_float_list(value, min=None, max=None):
return [is_float(mem) for mem in is_list(value, min, max)]
def is_string_list(value, min=None, max=None):
if isinstance(value, str):
raise VdtTypeError(value)
return [is_string(mem) for mem in is_list(value, min, max)]
def is_ip_addr_list(value, min=None, max=None):
return [is_ip_addr(mem) for mem in is_list(value, min, max)]
def force_list(value, min=None, max=None):
if not isinstance(value, (list, tuple)):
value = [value]
return is_list(value, min, max)
fun_dict = {
int: is_integer,
'int': is_integer,
'integer': is_integer,
float: is_float,
'float': is_float,
'ip_addr': is_ip_addr,
str: is_string,
'str': is_string,
'string': is_string,
bool: is_boolean,
'bool': is_boolean,
'boolean': is_boolean,
}
def is_mixed_list(value, *args):
try: length = len(value)
except TypeError: raise VdtTypeError(value)
if length < len(args): raise VdtValueTooShortError(value)
elif length > len(args): raise VdtValueTooLongError(value)
try: return [fun_dict[arg](val) for arg, val in zip(args, value)]
except KeyError as cause: raise VdtParamError('mixed_list', cause)
def is_option(value, *options):
if not isinstance(value, str): raise VdtTypeError(value)
if not value in options: raise VdtValueError(value)
return value
+68
View File
@@ -0,0 +1,68 @@
Recently, and mostly from people who I've never seen before, the opinions about how this project should be run has started flooding in again. In a recent forum thread of such opinions, specifically about:
- The decision to no longer mirror release notes on GitHub.
- Some people feeling there were too many "barriers to entry" to joining RNS development.
- The project not really being "open source" because random strangers couldn't just "contribute".
Joakim posted some very relevant observations about how Reticulum operates, along with the following quote:
> The modern industrial system has a built-in tendency to grow; it cannot really work unless it is growing. The word “stability” has been struck from its dictionary and replaced by “stagnation”. Its continuous growth pursues no particular aims or objectives: it is growth for the sake of growing. No one even enquires after its final shape. There is none; there is no “saturation point”.
That E. F. Schumacher quote perfectly illustrates the ontological schism that makes it so tiresome to deal with stuff like this.
There is, in this day and age, between different people, widely different base conceptual integrations of what "open source" means. For many people, "open source" has become synonymous **not** with skilled people working together in a coordinated and careful way on complex engineering challenges, but a sort of growth- and attention-focused "free-for-all" *behavioral* codex that must be followed above all else; a *social* modus operandi of fake inclusivity where everyone "should have their voice heard", and adherence to that specific process is weighed much higher than the final results.
I do not subscribe to, and consequently do not operate the Reticulum project under *any* versions of that idea.
**Here's the statistical, boring reality:**
- Around 90% of pull requests and "recommendations" I received when people could just submit stuff via GitHub would
have *severely* broken things, introduced bugs or security issues, created roadblocks for future work, or otherwise
damaged the software. Usually just for the sake of satisfying a random newcomers "idea" or personal preference.
- Similarly, around 90% "bug reports" were actually people asking for help, because of having failed to read even the
most basic parts of the documentation.
- The people with the least amount of understanding, skill and effort invested tend to be loudest and most vocal. When
all you have is "opinions", those are iterated upon ad infinitum, apparently.
Can you imagine how much time that wasted? Can you imagine what we could have accomplished with that time instead?
The only thing that this creates is *noise* and confusion. Clogging up the mental and physical workspaces, of people who are actually investing time and effort on the project with stuff like that is objectively just taking time that could have been used on development, and replacing it with *nothing*.
I was receiving *actual* bug reports, pull requests, proper technical investigations and patches via methods outside GitHub and "public" internet-based channels *way* before GitHub interaction and similar was closed down. That was were almost *all* of the real contributions were coming from, anyway. Apparently, and not unsurprisingly, the people who has invested the time and effort to understand Reticulum also prefer to collaborate in this way. Since leaving the GitHub madhouse behind, the signal-to-noise ratio has **significantly** increased.
Managing a public "issue" tracker with global read/write access is a futile and useless endeavor. Consider this:
- User A reports a "bug" that is really just a failure of understanding.
- User B sees this and seconds is, proposing a "fix" that in continued failure of understanding would actually break functionality X.
- User C joins the bandwagon and asks why this hasn't already been implemented like that? It's obvious!
The sensible response here from the developer is closing the issue with "No. Go RTFM". Today, though, this usually results in hurt feelings, animosity towards the developer and in some cases (as experienced and documented in the case of RNS), months of perfidious personal vendetta against the developer for being so brazen as to suggest the user was wrong and wasting people's time.
When this pattern repeats, over and over, the only sensible, measured and constructive course of action is to shrug your shoulders and say:
*"This system is fundamentally broken. It ain't working. I can give up here, or I can go build something better that has a chance of working."*
So, now it's your turn. Go look at the diffs for the last six months. What does it look like I have been doing?
But I will be damned straight with you all, and say that part of that solution is **absolutely** to erect barriers to entry. You can fucking bet your arse on that. I don't want opinionated man-babies running around in my living-room at 3am. I don't want to clean up the floor after a wannabe "dev-ops stars" with LLMs and a peripheral case of influencitis has puked all over the office.
- If you want to join the fun of changing core networking code that thousands of people rely on for communication
daily, you better know what the fuck you're doing.
- I'm not here to provide validation and hugs to random strangers. I'm here to make sure the reference implementation
of Reticulum works.
- If you cannot figure out how to submit a patch or valid bug investigation over RNS, you cannot expect I will take
you seriously. At all.
If someone can't handle that, they should find their entertainment elsewhere.
I've said it before: I've provided the information and code required to make Reticulum *work*, and build networked systems, protocols and applications on top of it. That information is deep, complex, and requires you to read hundreds of pages, and put in weeks of efforts to get the *full* picture. A lot less is required to get started, but it *will* still be a steep learning experience.
This is a full networking stack, based on some pretty complex principles, for crying out loud. It's **not** a `hello_world` designed to make you feel good about yourself. It turns almost everything you know about networked systems on its head. That's **challenging** for *anyone*. Climb the mountain, and it will be satisfying in the end. Refuse to climb... Well, what do you think will happen?
As for barriers to entry of *using* RNS and related programs, utilities and clients, it's not my task to teach every single user how to do X, Y and Z. The information *is* out there. If it wasn't organized optimally for your way of learning, you can choose to "raise your concerns" about it, discuss "the fact of it" on a forum or chatroom, or: *You can choose to remedy that, and help others along*.
I sure know what *I* would have done.
Binary file not shown.
Binary file not shown.
+1 -1
View File
@@ -1,4 +1,4 @@
# 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.
config: 33b573b4fa6e799ddf8fd65e522e0b14
config: eeadc4cc4157f9b7fb8f04414d9f0832
tags: 645f666f9bcd5a90fca523b33c5a78b7
Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

+148
View File
@@ -0,0 +1,148 @@
.. _distributed-development:
***********************
Distributed Development
***********************
This chapter of the manual provides the conceptual basis for understanding *why* ``rngit`` exists, what it aims to achieve, and the kinds of spaces it seeks to reestablish. For the practical details of operating the system, refer to the :ref:`Git Over Reticulum<git-main>` chapter.
The Original Architecture
=========================
When Torvalds created Git in 2005, he designed a tool that reflected a specific philosophy of collaboration. Every copy of a repository would be a complete, sovereign instance. There was no central server, no single point of failure, no gatekeeper. Developers would be able to work independently, exchange patches directly, and maintain their own branches indefinitely. This concept was - and is - both beautiful and revolutionary. It's execution is peer-to-peer not as a marketing term, but in the most foundational sense: As fundamental, structural reality.
Such a design emerged from necessity. The Linux kernel development process operated across geographical boundaries, time zones, and organizational affiliations. Contributors did not "log in" to a shared server to do their work; they maintained their own trees, and the flow of code between these trees was negotiated through patches, reviews, and merge decisions. The architecture of Git mirrored the social architecture of the community: Autonomous, competent, and fundamentally distributed in its technical operation.
*The result of that work is, in the most direct sense, what makes it possible for you to read this today.*
There's something very important to take note of here: With Git, developers could collaborate effectively and perfectly well without any central server being present, without platform-mediated visibility into each other's work, and without a centralized authority validating their contributions. They needed *only* a protocol for exchanging differences and a mechanism for verification of authorship. Everything else - social organization, quality control, release management - was handled by careful *human judgment* operating on top of the technical substrate.
What Git provided was not a development environment, but a **language for versioning**. It specified how to represent history, how to compute differences, how to merge divergent branches. It did not specify who could participate, how they should communicate, or what workflows they should follow. These were left to the competence and discretion of the creators using the system.
The Platform Interregnum
========================
What followed represents a very familiar pattern: Tools designed to distribute power were re-centralized by platforms that offered convenience in exchange for control. GitHub, GitLab, and similar services reintroduced the centralization that Git had eliminated architecturally. The activity feed replaced durable artifacts with ephemeral notifications. The social graph and open interaction became as important as the code itself, if not more.
This re-centralization was not technical, as such. It was **ontological**. When every developer pushes to the same server, when every merge is in theory controllable by a platform, when every issue is tracked in a database controlled by a corporation, the nature of collaboration changes. The platform, and its social dynamics, becomes the ground of reality. The platform mediates not just the technical exchange of information and the programmatics, but the social recognition and codices of contribution, the future archival prospects of the work, and the very identity of the project itself.
The consequences extend beyond individual inconvenience. Centralized platforms create single points of failure for entire ecosystem. When a platform changes its terms of service, suspends accounts, removes repositories or ceases operation, entire project histories and community relationships can be disrupted or destroyed. The extractive economics of platform capitalism mean that value created by open-source communities is captured by corporations, while communities remain dependent on infrastructure they do not control. And the surveillance inherent in platform operation means that every action - every commit, every comment, every page view - is logged, analyzed, and potentially monetized or weaponized.
More insidiously, platforms have completely reshaped the culture of development itself. They have created what we could call the **Teahouse Developer**: A participant who treats engineering projects as social venues for opinion-sharing rather than sites of disciplined and careful production. These personages have no actual stakes in the projects they act as leeches upon, and only a very base consciousness of the damage they are incurring in order to feed their attention and external validation dependencies.
When platforms optimize for engagement, when growth is the only metric, when every user with an opinion must have their voice heard, when a random social process is elevated to higher importance than results, the signal-to-noise ratio collapses catastrophically. Competent engineers find themselves drowning in feedback from the incompetent, managing the emotional needs and dysregulations of drive-by commentators rather than solving technical problems.
The platform model is predicated on **unsaturable expansion**. Like almost any industrial system, it cannot function without growth. It pursues no particular aims; it is growth for the sake of growing. There is no saturation point, no concept of "enough". Every barrier to entry must be put down to the very lowest common denominator, every voice must be amplified, every interaction must be converted into content that feeds the machine. This is fundamentally incompatible with the nature of social beings itself. It is also incompatible with serious engineering, which requires focus, discernment, and the right of people who know better to say "no".
Restoration
===========
The ``rngit`` system represents a return to Git's original architectural principles, fortified with cryptographic networking capabilities that were not available in 2005. The ``rngit`` system *is* Git - but running over Reticulum. Welcome back to a world where your work is your own, but where everyone can still reach you - if you want them to.
Just as Git eliminated the need for a central version control server, ``rngit`` eliminates the need for a central hosting platform, "servers" or any kinds of middle-men between the people actually doing the work. By operating over Reticulum, it eliminates the visibility of development activity to platform operators, network observers, state actors and other malicious third-parties.
In this model, the repository node is a **sovereign entity**. It is reachable from anywhere in the Reticulum network but owned, operated, and controlled by the developer or community that runs it. It is an actual home for creative output, not an extraction mechanism to which dues are paid. The node operator decides who may contribute, what standards must be met, and which voices are worth listening to. This is not exclusion; it is **discernment**. It is the necessary exercise of judgment that separates engineering from theatrics.
I did not create this in a fit of nostalgia. I created it because it is a necessary response to the failures of the centralized model. Git's technical architecture was - and *is* - correct. It was the social and economic superstructure built atop it that introduced fragility, exploitation, and environments toxic to actual creativity. By returning to first principles - distributed version control on distributed infrastructure - we recover not just a technical capability, but a mode of collaboration that respects the autonomy of individual developers and the sovereignty of actual communities.
Protocols Over Platforms
========================
The distinction between platforms and protocols is fundamental to understanding the architecture of sovereignty in networked systems. A platform is a service you access; a protocol is a grammar you speak; actions you live. A platform requires permission to enter, a protocol requires only *comprehension* to employ. A platform can change its rules, suspend your account, or cease operation entirely, a protocol persists as long as there are participants who *understand* and *use* it. A protocol is an *idea*, a platform is a machine that turns its users into products.
Platforms operate on a client-server model that inherently creates power asymmetry. Even when platforms are built atop open-source software, the operational instance remains a black box of corporate control. You *may* be able to download *some* of your data, but you cannot download the connections to the people that are the true value-base of the platform, or take them with you if you want to leave.
Protocols, by contrast, are agreements. They specify how systems should communicate, but not who may communicate or on what terms. Email is a protocol; Gmail is a platform. HTTP is a protocol; Facebook is a platform. Git is a protocol; GitHub is a platform. The protocol persists regardless of any particular implementation's success or failure.
The power of protocols lies in their **permissionlessness**. Anyone can implement a protocol without approval. Anyone can extend it, fork it, or use it for purposes unforeseen by its creators. This creates resilience: protocols cannot be easily censored, monopolized, or shut down because they exist as shared understanding rather than centralized infrastructure.
Reticulum is a protocol in this strict sense. It specifies how packets should be formatted, how paths should be discovered, how encryption should be applied. The ``rngit`` system extends this protocol approach to development workflows. It is not an external platform that hosts your repositories; it is a protocol for exchanging repository data, release artifacts, and work documents over Reticulum's encrypted transport. But with a few commands and an old computer, it creates your own infrastructure for hosting repositories, or sharing them with who you choose. *That* is how tools should function, in case we had forgotten.
Unlike platforms, which extract value by creating dependency, there is no entity that can grant or deny you the privilege of running ``rngit``. Your Reticulum identity is not endowed by any platform; it is generated locally and certified by its own cryptographic properties. Your repositories are hosted on nodes you control or nodes operated by communities you trust. Your relationships with other developers are peer-to-peer connections established through cryptographic addressing, not social graph connections managed by recommendation algorithms.
On a platform, exit means abandonment: you lose your history, your relationships, your visibility. With protocols, exit is just migration. When you change your infrastructure, your identity and your work travel with you. There are no middlemen between you and your collaborators. If push comes to shove, you can write your entire life's work and connections to an SD card, swim across the lake, and set up camp on the other side.
Sovereignty Through Infrastructure
==================================
The concept of sovereignty - supreme authority within a territory - has traditionally been applied to nation-states. But in an age where creative work is conducted through digital infrastructure, sovereignty is essential for individuals and communities. **Creative sovereignty** means having supreme authority over the artifacts you produce, the processes by which you produce them, and the terms under which they are distributed. It means not merely legal ownership of copyright, but operational control of the infrastructure that mediates creation, collaboration, and dissemination.
Centralized development platforms strip away most layers of sovereignty. When you host code on a corporate platform, you retain *some* legal ownership of copyright, but you surrender complete operational control. The platform decides what content is acceptable, who can access it, and how it is presented. They can delete your repository, suspend your account, or change the visibility of your work without consent. In reality, legal ownership becomes meaningless as operational control is ceded.
Running your own ``rngit`` node restores this sovereignty. You control the hardware, the network configuration, the backup strategies, and the access permissions. You decide what constitutes acceptable use, who may contribute, and how contributions are evaluated. Taking this responsibility on yourself is an assertion that your creative work is not a product to be harvested by platform economics, but an autonomous activity to be conducted on your own terms.
This sovereignty and responsibility extends to the entry barriers you establish. The ``rngit`` system allows you to configure access controls that filter participants based on cryptographic identity and demonstrated competence. If, for example, someone cannot navigate a command line, or use Reticulum to submit a patch, they most likely lack the required competence to modify your code. In a world that apparently labels this as "exclusion", I would simply refer to it as a minimally acceptable level of quality control.
Such a stance protects projects from the noise that so often overwhelms and completely dilutes platform-based development, where every user with an opinion believes themselves entitled to attention and access to the decision process.
Artifact-Centered Workflows
===========================
Contemporary platform-based development has shifted focus from durable artifacts to ephemeral *activity*. It does not matter what constitutes this activity, as long as it's there. The primary interface is not the repository itself, not the produced artifacts, but the activity feed: *Notifications* of commits, comments, pull requests, and social interactions. Work is measured by velocity, throughput, and the constant stream of updates. This activity-centric model creates constant urgency, discourages discernment, encourages reactive rather than reflective work patterns, and produces vast quantities of ephemeral and useless communication that obscures actual project state and productivity.
The ``rngit`` system enables a return to **artifact-centered workflows**, where the focus is on durable, attributable, versioned outputs rather than the stream of notifications surrounding them. The fundamental unit of work is the commit - signed, immutable records of change. The fundamental unit of production is the signed artifact - a self-verifying package of functionality. The fundamental unit of discussion is the work document - a structured, threaded conversation attached to repositories.
Artifacts can persist independently of any platform's continued operation. A commit signed with your Reticulum identity is attributable to you regardless of where it is stored. A release signed with your private key is verifiable as authentic regardless of which network it traverses, and can be verified offline on any system running Reticulum. The work exists as **cryptographic fact**, distributed over the planet, not as database entries in a corporate cloud.
Such a shift has real psychological consequences. When work is measured in artifacts rather than activity, the pace changes. There is no need for constant visibility, no pressure to perform busyness. Developers can work deeply, reflectively, and submit complete solutions rather than incremental updates designed to maintain presence in an activity feed. The work becomes **substantial**, in the physical sense of the word, rather than performative.
Composable Primitives
=====================
The ``rngit`` system is not a monolithic application prescribing a specific workflow; it is a collection of **composable primitives** that can be arranged to support diverse creative processes. Understanding these primitives as separate, orthogonal capabilities enables users to construct workflows suited to their specific needs and to recombine these primitives in ways unforeseen by the system's designers.
The core primitives include:
* **Repository Hosting**: Bare Git repositories served over Reticulum links, accessible via standard Git commands through the ``rns://`` URL scheme.
* **Identity-Based Access Control**: Fine-grained permissions managed through cryptographically verifiable identity hashes, configurable at the group, repository, or document level.
* **Release Distribution**: Cryptographically signed release artifacts with embedded provenance information, verifiable offline and distributable through any Reticulum or physical path.
* **Work Document Tracking**: Structured, threaded work management attached to repositories, with precise permission controls, and the ability to contain updates or discussions.
* **Forking and Mirroring**: Automated replication of repositories from any accessible Git URL, with metadata tracking upstream relationships for synchronization.
* **Nomad Network Integration**: Page node functionality for browsing repository contents, commit history, and release information through the Nomad Network protocol.
These primitives can be composed into workflows ranging from single-developer projects to complex multi-organizational collaborations. A solo developer might use only repository hosting and release distribution. A research group might add work document tracking for structured peer review. A software distribution network might combine mirroring with cryptographic release verification to create resilient update channels.
The entire system is incredibly light-weight, and can host hundreds of repositories on a Raspberry Pi.
Composability is essential because **creative work is diverse**. Software development, academic research, technical writing, hardware design, music production and data analysis all have different requirements for collaboration, review, and distribution. A platform prescribes a single workflow and forces all users to conform. A protocol provides primitives and allows users to construct workflows appropriate to their domain.
With ``rngit``, you can re-build the system into anything you can imagine. Everything can be modified, extended and hooked into. Adding functionality or automation is never further away than a shell script, a cron-job, or a Python modification of the source.
Distribution Without Intermediaries
===================================
Creating software is only part of the work. Then comes actually getting it to the people needing to use it. Centralized platforms handle distribution through their own infrastructure: Content delivery networks, central package registries, and download servers accessed through platform-controlled interfaces. This convenience masks a fundamental dependency: Your ability to distribute depends on the platform's continued operation, their policies regarding your content, and their technical infrastructure's reach.
The ``rngit`` release system enables distribution strategies **decoupled from any single infrastructure provider**. Releases are cryptographically signed using Ed25519 signatures and packaged in signed release manifests (``.rsm`` files). These manifests contain embedded signatures for each artifact. The manifest provides full verifiability of all release information, and contains embedded release artifact lists, per-file ``.rsg`` signatures, origin information, and the creator's Reticulum Identity. It can also be used to fetch verified updates of the software package over the network, and can always be verified completely offline.
Because releases are self-verifying, they can traverse any network or physical path that Reticulum can establish. A release can travel over LoRa radio, be carried on USB drives through areas without internet connectivity, disseminated over a mirror network, or be distributed through store-and-forward mechanisms on intermittent infrastructure. Recipients can verify authenticity regardless of how they obtained the files. This is particularly valuable in low-connectivity environments where Reticulum may be the only available communication channel.
The ``rngit release`` command provides tools for creating, publishing, fetching, and verifying releases. When fetching a release using an ``.rsm`` manifest, the system validates the manifest signature against the required Reticulum Identity, extracts the origin node and repository path, connects to the origin over Reticulum, retrieves the latest release manifest, and verifies each downloaded artifact against the signatures embedded in the manifest. If any verification fails, the fetch aborts, preventing installation of corrupted or tampered files.
This cryptographic verification replaces the trust model of platform distribution. Instead of trusting that a platform has not been compromised, users verify that artifacts match the signatures created by the developer's identity. It doesn't matter *how* they obtained the artifacts, they can **always** be verified. This security model shifts from **institutional trust** (just believe in the goodness of the platform) to **cryptographic proof** (verify the signatures).
Long Archive
============
Software development is often conceived as an activity of the present only: Solving today's problems, meeting current deadlines, responding to immediate feedback. But the artifacts produced - code, documentation, releases - have lifespans extending *far* beyond their creation. They may be used for decades, studied by future developers, depended upon by systems not yet imagined, or preserved as historical records of technological development.
The ``rngit`` system is designed with this **extended timeframe** in mind, supporting the creation of archives that are durable, portable, and intelligible across generational timescales. Git repositories are always internally complete; they contain full history and can be migrated to new infrastructure without loss of information. Everything that ``rngit`` adds on top of this is stored in normal files in standard formats right next to the Git repository folders, not an esoteric database-cluster two thousand kilometers away. Because releases are cryptographically signed, they remain verifiable as authentic regardless of when or where they are retrieved. Because the system operates over Reticulum, it can function over communication mediums that may outlast the internet as we know it.
This long-term perspective influences technical decisions. The use of well-established cryptographic primitives ensures that signatures will remain verifiable for centuries. The use of standard formats ensures that repositories will remain readable by future tools. The protocol-based architecture ensures that the system can evolve without losing compatibility with existing data.
For critical infrastructure, this archival durability is not optional; it is essential. Communication systems, cryptographic libraries, and safety-critical code must remain available and verifiable for the lifespans of the systems that depend on them. The ``rngit`` system provides the tools to create such archives: distributed across multiple nodes, cryptographically verified, and independent of any corporate or governmental infrastructure, which as history has shown repeatedly, does *not* persist.
Start Of The Road
=================
Distributed development and production over Reticulum is a *different mode of existence* for creative work. It restores the autonomy originally created by Git. It provides local sovereignty over production infrastructure, composability of workflow, and durability of artifact. It lets you filter participation through competence and cryptography rather than incentives of platform operators, raising the quality and enjoyment of work, and protecting the focus of real engineering and creative expression.
This is not a system for everyone, and that is the point. It requires investment - in understanding Reticulum, in configuring infrastructure, in establishing workflows. It requires accepting responsibility for your own tools rather than delegating them to platform operators. It requires the discipline to maintain your own node, manage your own backups, and nurture your own community.
But for those who make this investment, the returns are substantial. You gain **immunity from platform failure**; your work persists regardless of corporate decisions or service outages. You gain **shelter from surveillance**; your development activity is visible only to those that *you* choose to involve. You gain **control over process**; you decide how work is conducted, reviewed, and released, unmediated by terms of service, algorithmic feeds and thousands of uninformed and irrelevant opinions.
Most importantly, though, you regain the **dignity of craft**. Development becomes an activity conducted among peers, equals among equals, mediated by skill and cryptographic proof rather than corporate permission, producing artifacts that stand as independent testimony to competence, functionality or beauty rather than as content feeding engagement metrics. The *work* becomes the point. The artifacts become durable. And the network becomes *one* of the tools you wield in this endeavor.
File diff suppressed because it is too large Load Diff
+1
View File
@@ -27,6 +27,7 @@ to participate in the development of Reticulum itself.
hardware
interfaces
networks
distributed
git
support
examples
+63 -7
View File
@@ -1372,11 +1372,12 @@ a large amount of bogus destinations, and then disconnect, these destination wil
never make it into path tables and waste network bandwidth on retransmitted
announces.
**It's important to note** that the ingress control works at the level of *individual
sub-interfaces*. As an example, this means that one client on a :ref:`TCP Server Interface<interfaces-tcps>`
cannot disrupt processing of incoming announces for other connected clients on the same
:ref:`TCP Server Interface<interfaces-tcps>`. All other clients on the same interface will still have new announces
processed without interruption.
.. note::
It's important to remember that the ingress control works at the level of *individual
sub-interfaces*. As an example, this means that one client on a :ref:`TCP Server Interface<interfaces-tcps>`
cannot disrupt processing of incoming announces for other connected clients on the same
:ref:`TCP Server Interface<interfaces-tcps>`. All other clients on the same interface
will still have new announces processed without interruption.
By default, Reticulum will handle this automatically, and ingress announce
control will be enabled on interface where it is sensible to do so. It should
@@ -1384,8 +1385,7 @@ generally not be neccessary to modify the ingress control configuration,
but all the parameters are exposed for configuration if needed.
* | The ``ingress_control`` option tells Reticulum whether or not
to enable announce ingress control on the interface. Defaults to
``True``.
to enable ingress control on the interface. Defaults to ``True``.
* | The ``ic_new_time`` option configures how long (in seconds) an
interface is considered newly spawned. Defaults to ``2*60*60`` seconds. This
@@ -1422,3 +1422,59 @@ but all the parameters are exposed for configuration if needed.
must pass between releasing each held announce from the queue. Defaults
to ``30`` seconds.
All of the above settings can be configured both as instance-wide defaults
under the ``[reticulum]`` section of the configuration file, or on a per-
interface basis under the relevant interface configuration section.
Path Request Burst Control
==========================
In addition the announce controls for newly created destination, Reticulum will also
monitor incoming path request activity, and enforce burst controls if per-client rates
exceed configured limits. Once path request burst control is activated on an
interface, path requests will no longer be propagated further on the network.
As with announce burst control, this happens on a per sub-interface basis. One
client connecting to a public gateway will not be able to disrupt path request
processing for other clients.
.. warning::
Applications that send large amounts of unnecessary path requests will very
quickly get rate limited by transport nodes, and the entire system they are
running on will not be able to resolve any paths on the network, until the
burst subsides and hold period expires. **Do not** write applications like
this. Only request paths for destinations you need to communicate with.
By default, Reticulum will handle this automatically, and ingress path request
control will be enabled on interface where it is sensible to do so. It should
generally not be neccessary to modify the ingress control configuration,
but all the parameters are exposed for configuration if needed.
* | The ``ingress_control`` option tells Reticulum whether or not
to enable ingress control on the interface. Defaults to ``True``.
* | The ``ic_new_time`` option configures how long (in seconds) an
interface is considered newly spawned. Defaults to ``2*60*60`` seconds. This
option is useful on publicly accessible interfaces that spawn new
sub-interfaces when a new client connects.
* | The ``ic_pr_burst_freq_new`` option sets the maximum path request
ingress frequency for newly spawned interfaces. Defaults to ``3``
path requests per second.
* | The ``ic_pr_burst_freq`` option sets the maximum path request
ingress frequency for other interfaces. Defaults to ``8`` path requests
per second.
*If an interface exceeds its burst frequency, incoming path requests
from that system will not traverse the network further.*
* | The ``egress_control`` option enables hard-limiting path request egress
control per-interface. Defaults to ``False``
* | The ``ec_pr_freq`` option sets the hard limit for outbound path requests
per second on a given interface.
All of the above settings can be configured both as instance-wide defaults
under the ``[reticulum]`` section of the configuration file, or on a per-
interface basis under the relevant interface configuration section.
+1 -51
View File
@@ -110,7 +110,7 @@ plugin system for expandability.
MeshChatX
^^^^^^^^
A `Reticulum MeshChat fork from the future <https://git.quad4.io/RNS-Things/MeshChatX>`_, with the goal of providing everything you need for Reticulum, LXMF, and LXST in one beautiful and feature-rich application. This project is separate from the original Reticulum MeshChat project, and is not affiliated with the original project.
A `Reticulum MeshChat fork from the future <https://git.quad4.io/RNS-Things/MeshChatX>`_, with the goal of providing everything you need for Reticulum, LXMF, and LXST in one beautiful and feature-rich application. This project is separate from the original `Reticulum MeshChat <https://github.com/liamcottle/reticulum-meshchat>`_ project, and is not affiliated with the original project, but is a much more up-to-date, comprehensive and well-maintained fork.
.. only:: html
@@ -127,56 +127,6 @@ A `Reticulum MeshChat fork from the future <https://git.quad4.io/RNS-Things/Mesh
Features include full LXST support, custom voicemail, phonebook, contact sharing, and ringtone support, multi-identity handling, modern UI/UX, offline documentation, expanded tools, page archiving, integrated maps, telemetry and improved application security.
.. raw:: latex
\newpage
MeshChat
^^^^^^^^
The `Reticulum MeshChat <https://github.com/liamcottle/reticulum-meshchat>`_ application
is a user-friendly LXMF client for Linux, macOS and Windows, that also includes a Nomad Network
page browser and other interesting functionality.
.. only:: html
.. image:: screenshots/meshchat_1.webp
:align: center
:target: https://github.com/liamcottle/reticulum-meshchat
.. only:: latex
.. image:: screenshots/meshchat_1.png
:align: center
:target: https://github.com/liamcottle/reticulum-meshchat
Reticulum MeshChat is of course also compatible with Sideband and Nomad Network, or
any other LXMF client.
Columba
^^^^^^^
`Columba <https://github.com/torlando-tech/columba/>`_ is a simple and familiar LXMF
messaging app Android, built with a native Android interface and Material Design 3.
.. only:: html
.. image:: screenshots/columba.webp
:align: center
:width: 25%
:target: https://github.com/torlando-tech/columba/
.. only:: latex
.. image:: screenshots/columba.png
:align: center
:width: 25%
:target: https://github.com/torlando-tech/columba/
While still in early and very active development, it is of course also compatible
with all other LXMF clients, and allows you to message seamlessly with anyone else
using LXMF.
.. raw:: latex
\newpage
+4
View File
@@ -31,6 +31,10 @@ Donations are gratefully accepted via the following channels:
Are certain features in the development roadmap are important to you or your
organisation? Make them a reality quickly by sponsoring their implementation.
.. raw:: latex
\newpage
Provide Feedback
================
Feedback on the usage, functioning and potential dysfunctioning of any and
+1 -1
View File
@@ -1,5 +1,5 @@
const DOCUMENTATION_OPTIONS = {
VERSION: '1.2.4',
VERSION: '1.2.9',
LANGUAGE: 'en',
COLLAPSE_INDEX: false,
BUILDER: 'html',
+441
View File
@@ -0,0 +1,441 @@
<!doctype html>
<html class="no-js" lang="en" data-content_root="./">
<head><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="color-scheme" content="light dark"><meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="index" title="Index" href="genindex.html"><link rel="search" title="Search" href="search.html"><link rel="next" title="Git Over Reticulum" href="git.html"><link rel="prev" title="Building Networks" href="networks.html">
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
<title>Distributed Development - Reticulum Network Stack 1.2.9 documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
<link rel="stylesheet" type="text/css" href="_static/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/custom.css?v=bb3cebc5" />
<style>
body {
--color-code-background: #f2f2f2;
--color-code-foreground: #1e1e1e;
}
@media not print {
body[data-theme="dark"] {
--color-code-background: #202020;
--color-code-foreground: #d0d0d0;
--color-background-primary: #202b38;
--color-background-secondary: #161f27;
--color-foreground-primary: #dbdbdb;
--color-foreground-secondary: #a9b1ba;
--color-brand-primary: #41adff;
--color-background-hover: #161f27;
--color-api-name: #ffbe85;
--color-api-pre-name: #efae75;
}
@media (prefers-color-scheme: dark) {
body:not([data-theme="light"]) {
--color-code-background: #202020;
--color-code-foreground: #d0d0d0;
--color-background-primary: #202b38;
--color-background-secondary: #161f27;
--color-foreground-primary: #dbdbdb;
--color-foreground-secondary: #a9b1ba;
--color-brand-primary: #41adff;
--color-background-hover: #161f27;
--color-api-name: #ffbe85;
--color-api-pre-name: #efae75;
}
}
}
</style></head>
<body>
<script>
document.body.dataset.theme = localStorage.getItem("theme") || "auto";
</script>
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
<symbol id="svg-toc" viewBox="0 0 24 24">
<title>Contents</title>
<svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 1024 1024">
<path d="M408 442h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm-8 204c0 4.4 3.6 8 8 8h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56zm504-486H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 632H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM115.4 518.9L271.7 642c5.8 4.6 14.4.5 14.4-6.9V388.9c0-7.4-8.5-11.5-14.4-6.9L115.4 505.1a8.74 8.74 0 0 0 0 13.8z"/>
</svg>
</symbol>
<symbol id="svg-menu" viewBox="0 0 24 24">
<title>Menu</title>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather-menu">
<line x1="3" y1="12" x2="21" y2="12"></line>
<line x1="3" y1="6" x2="21" y2="6"></line>
<line x1="3" y1="18" x2="21" y2="18"></line>
</svg>
</symbol>
<symbol id="svg-arrow-right" viewBox="0 0 24 24">
<title>Expand</title>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather-chevron-right">
<polyline points="9 18 15 12 9 6"></polyline>
</svg>
</symbol>
<symbol id="svg-sun" viewBox="0 0 24 24">
<title>Light mode</title>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="1" stroke-linecap="round" stroke-linejoin="round" class="feather-sun">
<circle cx="12" cy="12" r="5"></circle>
<line x1="12" y1="1" x2="12" y2="3"></line>
<line x1="12" y1="21" x2="12" y2="23"></line>
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
<line x1="1" y1="12" x2="3" y2="12"></line>
<line x1="21" y1="12" x2="23" y2="12"></line>
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
</svg>
</symbol>
<symbol id="svg-moon" viewBox="0 0 24 24">
<title>Dark mode</title>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="1" stroke-linecap="round" stroke-linejoin="round" class="icon-tabler-moon">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M12 3c.132 0 .263 0 .393 0a7.5 7.5 0 0 0 7.92 12.446a9 9 0 1 1 -8.313 -12.454z" />
</svg>
</symbol>
<symbol id="svg-sun-with-moon" viewBox="0 0 24 24">
<title>Auto light/dark, in light mode</title>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="1" stroke-linecap="round" stroke-linejoin="round"
class="icon-custom-derived-from-feather-sun-and-tabler-moon">
<path style="opacity: 50%" d="M 5.411 14.504 C 5.471 14.504 5.532 14.504 5.591 14.504 C 3.639 16.319 4.383 19.569 6.931 20.352 C 7.693 20.586 8.512 20.551 9.25 20.252 C 8.023 23.207 4.056 23.725 2.11 21.184 C 0.166 18.642 1.702 14.949 4.874 14.536 C 5.051 14.512 5.231 14.5 5.411 14.5 L 5.411 14.504 Z"/>
<line x1="14.5" y1="3.25" x2="14.5" y2="1.25"/>
<line x1="14.5" y1="15.85" x2="14.5" y2="17.85"/>
<line x1="10.044" y1="5.094" x2="8.63" y2="3.68"/>
<line x1="19" y1="14.05" x2="20.414" y2="15.464"/>
<line x1="8.2" y1="9.55" x2="6.2" y2="9.55"/>
<line x1="20.8" y1="9.55" x2="22.8" y2="9.55"/>
<line x1="10.044" y1="14.006" x2="8.63" y2="15.42"/>
<line x1="19" y1="5.05" x2="20.414" y2="3.636"/>
<circle cx="14.5" cy="9.55" r="3.6"/>
</svg>
</symbol>
<symbol id="svg-moon-with-sun" viewBox="0 0 24 24">
<title>Auto light/dark, in dark mode</title>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="1" stroke-linecap="round" stroke-linejoin="round"
class="icon-custom-derived-from-feather-sun-and-tabler-moon">
<path d="M 8.282 7.007 C 8.385 7.007 8.494 7.007 8.595 7.007 C 5.18 10.184 6.481 15.869 10.942 17.24 C 12.275 17.648 13.706 17.589 15 17.066 C 12.851 22.236 5.91 23.143 2.505 18.696 C -0.897 14.249 1.791 7.786 7.342 7.063 C 7.652 7.021 7.965 7 8.282 7 L 8.282 7.007 Z"/>
<line style="opacity: 50%" x1="18" y1="3.705" x2="18" y2="2.5"/>
<line style="opacity: 50%" x1="18" y1="11.295" x2="18" y2="12.5"/>
<line style="opacity: 50%" x1="15.316" y1="4.816" x2="14.464" y2="3.964"/>
<line style="opacity: 50%" x1="20.711" y1="10.212" x2="21.563" y2="11.063"/>
<line style="opacity: 50%" x1="14.205" y1="7.5" x2="13.001" y2="7.5"/>
<line style="opacity: 50%" x1="21.795" y1="7.5" x2="23" y2="7.5"/>
<line style="opacity: 50%" x1="15.316" y1="10.184" x2="14.464" y2="11.036"/>
<line style="opacity: 50%" x1="20.711" y1="4.789" x2="21.563" y2="3.937"/>
<circle style="opacity: 50%" cx="18" cy="7.5" r="2.169"/>
</svg>
</symbol>
<symbol id="svg-pencil" viewBox="0 0 24 24">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="1" stroke-linecap="round" stroke-linejoin="round" class="icon-tabler-pencil-code">
<path d="M4 20h4l10.5 -10.5a2.828 2.828 0 1 0 -4 -4l-10.5 10.5v4" />
<path d="M13.5 6.5l4 4" />
<path d="M20 21l2 -2l-2 -2" />
<path d="M17 17l-2 2l2 2" />
</svg>
</symbol>
<symbol id="svg-eye" viewBox="0 0 24 24">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="1" stroke-linecap="round" stroke-linejoin="round" class="icon-tabler-eye-code">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M10 12a2 2 0 1 0 4 0a2 2 0 0 0 -4 0" />
<path
d="M11.11 17.958c-3.209 -.307 -5.91 -2.293 -8.11 -5.958c2.4 -4 5.4 -6 9 -6c3.6 0 6.6 2 9 6c-.21 .352 -.427 .688 -.647 1.008" />
<path d="M20 21l2 -2l-2 -2" />
<path d="M17 17l-2 2l2 2" />
</svg>
</symbol>
</svg>
<input type="checkbox" class="sidebar-toggle" name="__navigation" id="__navigation" aria-label="Toggle site navigation sidebar">
<input type="checkbox" class="sidebar-toggle" name="__toc" id="__toc" aria-label="Toggle table of contents sidebar">
<label class="overlay sidebar-overlay" for="__navigation"></label>
<label class="overlay toc-overlay" for="__toc"></label>
<a class="skip-to-content muted-link" href="#furo-main-content">Skip to content</a>
<div class="page">
<header class="mobile-header">
<div class="header-left">
<label class="nav-overlay-icon" for="__navigation">
<span class="icon"><svg><use href="#svg-menu"></use></svg></span>
</label>
</div>
<div class="header-center">
<a href="index.html"><div class="brand">Reticulum Network Stack 1.2.9 documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
<button class="theme-toggle" aria-label="Toggle Light / Dark / Auto color theme">
<svg class="theme-icon-when-auto-light"><use href="#svg-sun-with-moon"></use></svg>
<svg class="theme-icon-when-auto-dark"><use href="#svg-moon-with-sun"></use></svg>
<svg class="theme-icon-when-dark"><use href="#svg-moon"></use></svg>
<svg class="theme-icon-when-light"><use href="#svg-sun"></use></svg>
</button>
</div>
<label class="toc-overlay-icon toc-header-icon" for="__toc">
<span class="icon"><svg><use href="#svg-toc"></use></svg></span>
</label>
</div>
</header>
<aside class="sidebar-drawer">
<div class="sidebar-container">
<div class="sidebar-sticky"><a class="sidebar-brand" href="index.html">
<div class="sidebar-logo-container">
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
</div>
<span class="sidebar-brand-text">Reticulum Network Stack 1.2.9 documentation</span>
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
<input type="hidden" name="check_keywords" value="yes">
<input type="hidden" name="area" value="default">
</form>
<div id="searchbox"></div><div class="sidebar-scroll"><div class="sidebar-tree">
<ul class="current">
<li class="toctree-l1"><a class="reference internal" href="whatis.html">What is Reticulum?</a></li>
<li class="toctree-l1"><a class="reference internal" href="gettingstartedfast.html">Getting Started Fast</a></li>
<li class="toctree-l1"><a class="reference internal" href="zen.html">Zen of Reticulum</a></li>
<li class="toctree-l1"><a class="reference internal" href="software.html">Programs Using Reticulum</a></li>
<li class="toctree-l1"><a class="reference internal" href="using.html">Using Reticulum on Your System</a></li>
<li class="toctree-l1"><a class="reference internal" href="understanding.html">Understanding Reticulum</a></li>
<li class="toctree-l1"><a class="reference internal" href="hardware.html">Communications Hardware</a></li>
<li class="toctree-l1"><a class="reference internal" href="interfaces.html">Configuring Interfaces</a></li>
<li class="toctree-l1"><a class="reference internal" href="networks.html">Building Networks</a></li>
<li class="toctree-l1 current current-page"><a class="current reference internal" href="#">Distributed Development</a></li>
<li class="toctree-l1"><a class="reference internal" href="git.html">Git Over Reticulum</a></li>
<li class="toctree-l1"><a class="reference internal" href="support.html">Support Reticulum</a></li>
<li class="toctree-l1"><a class="reference internal" href="examples.html">Code Examples</a></li>
<li class="toctree-l1"><a class="reference internal" href="license.html">Reticulum License</a></li>
</ul>
<ul>
<li class="toctree-l1"><a class="reference internal" href="reference.html">API Reference</a></li>
</ul>
</div>
</div>
</div>
</div>
</aside>
<div class="main">
<div class="content">
<div class="article-container">
<a href="#" class="back-to-top muted-link">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path d="M13 20h-2V8l-5.5 5.5-1.42-1.42L12 4.16l7.92 7.92-1.42 1.42L13 8v12z"></path>
</svg>
<span>Back to top</span>
</a>
<div class="content-icon-container">
<div class="theme-toggle-container theme-toggle-content">
<button class="theme-toggle" aria-label="Toggle Light / Dark / Auto color theme">
<svg class="theme-icon-when-auto-light"><use href="#svg-sun-with-moon"></use></svg>
<svg class="theme-icon-when-auto-dark"><use href="#svg-moon-with-sun"></use></svg>
<svg class="theme-icon-when-dark"><use href="#svg-moon"></use></svg>
<svg class="theme-icon-when-light"><use href="#svg-sun"></use></svg>
</button>
</div>
<label class="toc-overlay-icon toc-content-icon" for="__toc">
<span class="icon"><svg><use href="#svg-toc"></use></svg></span>
</label>
</div>
<article role="main" id="furo-main-content">
<section id="distributed-development">
<span id="id1"></span><h1>Distributed Development<a class="headerlink" href="#distributed-development" title="Link to this heading"></a></h1>
<p>This chapter of the manual provides the conceptual basis for understanding <em>why</em> <code class="docutils literal notranslate"><span class="pre">rngit</span></code> exists, what it aims to achieve, and the kinds of spaces it seeks to reestablish. For the practical details of operating the system, refer to the <a class="reference internal" href="git.html#git-main"><span class="std std-ref">Git Over Reticulum</span></a> chapter.</p>
<section id="the-original-architecture">
<h2>The Original Architecture<a class="headerlink" href="#the-original-architecture" title="Link to this heading"></a></h2>
<p>When Torvalds created Git in 2005, he designed a tool that reflected a specific philosophy of collaboration. Every copy of a repository would be a complete, sovereign instance. There was no central server, no single point of failure, no gatekeeper. Developers would be able to work independently, exchange patches directly, and maintain their own branches indefinitely. This concept was - and is - both beautiful and revolutionary. Its execution is peer-to-peer not as a marketing term, but in the most foundational sense: As fundamental, structural reality.</p>
<p>Such a design emerged from necessity. The Linux kernel development process operated across geographical boundaries, time zones, and organizational affiliations. Contributors did not “log in” to a shared server to do their work; they maintained their own trees, and the flow of code between these trees was negotiated through patches, reviews, and merge decisions. The architecture of Git mirrored the social architecture of the community: Autonomous, competent, and fundamentally distributed in its technical operation.</p>
<p><em>The result of that work is, in the most direct sense, what makes it possible for you to read this today.</em></p>
<p>Theres something very important to take note of here: With Git, developers could collaborate effectively and perfectly well without any central server being present, without platform-mediated visibility into each others work, and without a centralized authority validating their contributions. They needed <em>only</em> a protocol for exchanging differences and a mechanism for verification of authorship. Everything else - social organization, quality control, release management - was handled by careful <em>human judgment</em> operating on top of the technical substrate.</p>
<p>What Git provided was not a development environment, but a <strong>language for versioning</strong>. It specified how to represent history, how to compute differences, how to merge divergent branches. It did not specify who could participate, how they should communicate, or what workflows they should follow. These were left to the competence and discretion of the creators using the system.</p>
</section>
<section id="the-platform-interregnum">
<h2>The Platform Interregnum<a class="headerlink" href="#the-platform-interregnum" title="Link to this heading"></a></h2>
<p>What followed represents a very familiar pattern: Tools designed to distribute power were re-centralized by platforms that offered convenience in exchange for control. GitHub, GitLab, and similar services reintroduced the centralization that Git had eliminated architecturally. The activity feed replaced durable artifacts with ephemeral notifications. The social graph and open interaction became as important as the code itself, if not more.</p>
<p>This re-centralization was not technical, as such. It was <strong>ontological</strong>. When every developer pushes to the same server, when every merge is in theory controllable by a platform, when every issue is tracked in a database controlled by a corporation, the nature of collaboration changes. The platform, and its social dynamics, becomes the ground of reality. The platform mediates not just the technical exchange of information and the programmatics, but the social recognition and codices of contribution, the future archival prospects of the work, and the very identity of the project itself.</p>
<p>The consequences extend beyond individual inconvenience. Centralized platforms create single points of failure for entire ecosystem. When a platform changes its terms of service, suspends accounts, removes repositories or ceases operation, entire project histories and community relationships can be disrupted or destroyed. The extractive economics of platform capitalism mean that value created by open-source communities is captured by corporations, while communities remain dependent on infrastructure they do not control. And the surveillance inherent in platform operation means that every action - every commit, every comment, every page view - is logged, analyzed, and potentially monetized or weaponized.</p>
<p>More insidiously, platforms have completely reshaped the culture of development itself. They have created what we could call the <strong>Teahouse Developer</strong>: A participant who treats engineering projects as social venues for opinion-sharing rather than sites of disciplined and careful production. These personages have no actual stakes in the projects they act as leeches upon, and only a very base consciousness of the damage they are incurring in order to feed their attention and external validation dependencies.</p>
<p>When platforms optimize for engagement, when growth is the only metric, when every user with an opinion must have their voice heard, when a random social process is elevated to higher importance than results, the signal-to-noise ratio collapses catastrophically. Competent engineers find themselves drowning in feedback from the incompetent, managing the emotional needs and dysregulations of drive-by commentators rather than solving technical problems.</p>
<p>The platform model is predicated on <strong>unsaturable expansion</strong>. Like almost any industrial system, it cannot function without growth. It pursues no particular aims; it is growth for the sake of growing. There is no saturation point, no concept of “enough”. Every barrier to entry must be put down to the very lowest common denominator, every voice must be amplified, every interaction must be converted into content that feeds the machine. This is fundamentally incompatible with the nature of social beings itself. It is also incompatible with serious engineering, which requires focus, discernment, and the right of people who know better to say “no”.</p>
</section>
<section id="restoration">
<h2>Restoration<a class="headerlink" href="#restoration" title="Link to this heading"></a></h2>
<p>The <code class="docutils literal notranslate"><span class="pre">rngit</span></code> system represents a return to Gits original architectural principles, fortified with cryptographic networking capabilities that were not available in 2005. The <code class="docutils literal notranslate"><span class="pre">rngit</span></code> system <em>is</em> Git - but running over Reticulum. Welcome back to a world where your work is your own, but where everyone can still reach you - if you want them to.</p>
<p>Just as Git eliminated the need for a central version control server, <code class="docutils literal notranslate"><span class="pre">rngit</span></code> eliminates the need for a central hosting platform, “servers” or any kinds of middle-men between the people actually doing the work. By operating over Reticulum, it eliminates the visibility of development activity to platform operators, network observers, state actors and other malicious third-parties.</p>
<p>In this model, the repository node is a <strong>sovereign entity</strong>. It is reachable from anywhere in the Reticulum network but owned, operated, and controlled by the developer or community that runs it. It is an actual home for creative output, not an extraction mechanism to which dues are paid. The node operator decides who may contribute, what standards must be met, and which voices are worth listening to. This is not exclusion; it is <strong>discernment</strong>. It is the necessary exercise of judgment that separates engineering from theatrics.</p>
<p>I did not create this in a fit of nostalgia. I created it because it is a necessary response to the failures of the centralized model. Gits technical architecture was - and <em>is</em> - correct. It was the social and economic superstructure built atop it that introduced fragility, exploitation, and environments toxic to actual creativity. By returning to first principles - distributed version control on distributed infrastructure - we recover not just a technical capability, but a mode of collaboration that respects the autonomy of individual developers and the sovereignty of actual communities.</p>
</section>
<section id="protocols-over-platforms">
<h2>Protocols Over Platforms<a class="headerlink" href="#protocols-over-platforms" title="Link to this heading"></a></h2>
<p>The distinction between platforms and protocols is fundamental to understanding the architecture of sovereignty in networked systems. A platform is a service you access; a protocol is a grammar you speak; actions you live. A platform requires permission to enter, a protocol requires only <em>comprehension</em> to employ. A platform can change its rules, suspend your account, or cease operation entirely, a protocol persists as long as there are participants who <em>understand</em> and <em>use</em> it. A protocol is an <em>idea</em>, a platform is a machine that turns its users into products.</p>
<p>Platforms operate on a client-server model that inherently creates power asymmetry. Even when platforms are built atop open-source software, the operational instance remains a black box of corporate control. You <em>may</em> be able to download <em>some</em> of your data, but you cannot download the connections to the people that are the true value-base of the platform, or take them with you if you want to leave.</p>
<p>Protocols, by contrast, are agreements. They specify how systems should communicate, but not who may communicate or on what terms. Email is a protocol; Gmail is a platform. HTTP is a protocol; Facebook is a platform. Git is a protocol; GitHub is a platform. The protocol persists regardless of any particular implementations success or failure.</p>
<p>The power of protocols lies in their <strong>permissionlessness</strong>. Anyone can implement a protocol without approval. Anyone can extend it, fork it, or use it for purposes unforeseen by its creators. This creates resilience: protocols cannot be easily censored, monopolized, or shut down because they exist as shared understanding rather than centralized infrastructure.</p>
<p>Reticulum is a protocol in this strict sense. It specifies how packets should be formatted, how paths should be discovered, how encryption should be applied. The <code class="docutils literal notranslate"><span class="pre">rngit</span></code> system extends this protocol approach to development workflows. It is not an external platform that hosts your repositories; it is a protocol for exchanging repository data, release artifacts, and work documents over Reticulums encrypted transport. But with a few commands and an old computer, it creates your own infrastructure for hosting repositories, or sharing them with who you choose. <em>That</em> is how tools should function, in case we had forgotten.</p>
<p>Unlike platforms, which extract value by creating dependency, there is no entity that can grant or deny you the privilege of running <code class="docutils literal notranslate"><span class="pre">rngit</span></code>. Your Reticulum identity is not endowed by any platform; it is generated locally and certified by its own cryptographic properties. Your repositories are hosted on nodes you control or nodes operated by communities you trust. Your relationships with other developers are peer-to-peer connections established through cryptographic addressing, not social graph connections managed by recommendation algorithms.</p>
<p>On a platform, exit means abandonment: you lose your history, your relationships, your visibility. With protocols, exit is just migration. When you change your infrastructure, your identity and your work travel with you. There are no middlemen between you and your collaborators. If push comes to shove, you can write your entire lifes work and connections to an SD card, swim across the lake, and set up camp on the other side.</p>
</section>
<section id="sovereignty-through-infrastructure">
<h2>Sovereignty Through Infrastructure<a class="headerlink" href="#sovereignty-through-infrastructure" title="Link to this heading"></a></h2>
<p>The concept of sovereignty - supreme authority within a territory - has traditionally been applied to nation-states. But in an age where creative work is conducted through digital infrastructure, sovereignty is essential for individuals and communities. <strong>Creative sovereignty</strong> means having supreme authority over the artifacts you produce, the processes by which you produce them, and the terms under which they are distributed. It means not merely legal ownership of copyright, but operational control of the infrastructure that mediates creation, collaboration, and dissemination.</p>
<p>Centralized development platforms strip away most layers of sovereignty. When you host code on a corporate platform, you retain <em>some</em> legal ownership of copyright, but you surrender complete operational control. The platform decides what content is acceptable, who can access it, and how it is presented. They can delete your repository, suspend your account, or change the visibility of your work without consent. In reality, legal ownership becomes meaningless as operational control is ceded.</p>
<p>Running your own <code class="docutils literal notranslate"><span class="pre">rngit</span></code> node restores this sovereignty. You control the hardware, the network configuration, the backup strategies, and the access permissions. You decide what constitutes acceptable use, who may contribute, and how contributions are evaluated. Taking this responsibility on yourself is an assertion that your creative work is not a product to be harvested by platform economics, but an autonomous activity to be conducted on your own terms.</p>
<p>This sovereignty and responsibility extends to the entry barriers you establish. The <code class="docutils literal notranslate"><span class="pre">rngit</span></code> system allows you to configure access controls that filter participants based on cryptographic identity and demonstrated competence. If, for example, someone cannot navigate a command line, or use Reticulum to submit a patch, they most likely lack the required competence to modify your code. In a world that apparently labels this as “exclusion”, I would simply refer to it as a minimally acceptable level of quality control.</p>
<p>Such a stance protects projects from the noise that so often overwhelms and completely dilutes platform-based development, where every user with an opinion believes themselves entitled to attention and access to the decision process.</p>
</section>
<section id="artifact-centered-workflows">
<h2>Artifact-Centered Workflows<a class="headerlink" href="#artifact-centered-workflows" title="Link to this heading"></a></h2>
<p>Contemporary platform-based development has shifted focus from durable artifacts to ephemeral <em>activity</em>. It does not matter what constitutes this activity, as long as its there. The primary interface is not the repository itself, not the produced artifacts, but the activity feed: <em>Notifications</em> of commits, comments, pull requests, and social interactions. Work is measured by velocity, throughput, and the constant stream of updates. This activity-centric model creates constant urgency, discourages discernment, encourages reactive rather than reflective work patterns, and produces vast quantities of ephemeral and useless communication that obscures actual project state and productivity.</p>
<p>The <code class="docutils literal notranslate"><span class="pre">rngit</span></code> system enables a return to <strong>artifact-centered workflows</strong>, where the focus is on durable, attributable, versioned outputs rather than the stream of notifications surrounding them. The fundamental unit of work is the commit - signed, immutable records of change. The fundamental unit of production is the signed artifact - a self-verifying package of functionality. The fundamental unit of discussion is the work document - a structured, threaded conversation attached to repositories.</p>
<p>Artifacts can persist independently of any platforms continued operation. A commit signed with your Reticulum identity is attributable to you regardless of where it is stored. A release signed with your private key is verifiable as authentic regardless of which network it traverses, and can be verified offline on any system running Reticulum. The work exists as <strong>cryptographic fact</strong>, distributed over the planet, not as database entries in a corporate cloud.</p>
<p>Such a shift has real psychological consequences. When work is measured in artifacts rather than activity, the pace changes. There is no need for constant visibility, no pressure to perform busyness. Developers can work deeply, reflectively, and submit complete solutions rather than incremental updates designed to maintain presence in an activity feed. The work becomes <strong>substantial</strong>, in the physical sense of the word, rather than performative.</p>
</section>
<section id="composable-primitives">
<h2>Composable Primitives<a class="headerlink" href="#composable-primitives" title="Link to this heading"></a></h2>
<p>The <code class="docutils literal notranslate"><span class="pre">rngit</span></code> system is not a monolithic application prescribing a specific workflow; it is a collection of <strong>composable primitives</strong> that can be arranged to support diverse creative processes. Understanding these primitives as separate, orthogonal capabilities enables users to construct workflows suited to their specific needs and to recombine these primitives in ways unforeseen by the systems designers.</p>
<p>The core primitives include:</p>
<ul class="simple">
<li><p><strong>Repository Hosting</strong>: Bare Git repositories served over Reticulum links, accessible via standard Git commands through the <code class="docutils literal notranslate"><span class="pre">rns://</span></code> URL scheme.</p></li>
<li><p><strong>Identity-Based Access Control</strong>: Fine-grained permissions managed through cryptographically verifiable identity hashes, configurable at the group, repository, or document level.</p></li>
<li><p><strong>Release Distribution</strong>: Cryptographically signed release artifacts with embedded provenance information, verifiable offline and distributable through any Reticulum or physical path.</p></li>
<li><p><strong>Work Document Tracking</strong>: Structured, threaded work management attached to repositories, with precise permission controls, and the ability to contain updates or discussions.</p></li>
<li><p><strong>Forking and Mirroring</strong>: Automated replication of repositories from any accessible Git URL, with metadata tracking upstream relationships for synchronization.</p></li>
<li><p><strong>Nomad Network Integration</strong>: Page node functionality for browsing repository contents, commit history, and release information through the Nomad Network protocol.</p></li>
</ul>
<p>These primitives can be composed into workflows ranging from single-developer projects to complex multi-organizational collaborations. A solo developer might use only repository hosting and release distribution. A research group might add work document tracking for structured peer review. A software distribution network might combine mirroring with cryptographic release verification to create resilient update channels.</p>
<p>The entire system is incredibly light-weight, and can host hundreds of repositories on a Raspberry Pi.</p>
<p>Composability is essential because <strong>creative work is diverse</strong>. Software development, academic research, technical writing, hardware design, music production and data analysis all have different requirements for collaboration, review, and distribution. A platform prescribes a single workflow and forces all users to conform. A protocol provides primitives and allows users to construct workflows appropriate to their domain.</p>
<p>With <code class="docutils literal notranslate"><span class="pre">rngit</span></code>, you can re-build the system into anything you can imagine. Everything can be modified, extended and hooked into. Adding functionality or automation is never further away than a shell script, a cron-job, or a Python modification of the source.</p>
</section>
<section id="distribution-without-intermediaries">
<h2>Distribution Without Intermediaries<a class="headerlink" href="#distribution-without-intermediaries" title="Link to this heading"></a></h2>
<p>Creating software is only part of the work. Then comes actually getting it to the people needing to use it. Centralized platforms handle distribution through their own infrastructure: Content delivery networks, central package registries, and download servers accessed through platform-controlled interfaces. This convenience masks a fundamental dependency: Your ability to distribute depends on the platforms continued operation, their policies regarding your content, and their technical infrastructures reach.</p>
<p>The <code class="docutils literal notranslate"><span class="pre">rngit</span></code> release system enables distribution strategies <strong>decoupled from any single infrastructure provider</strong>. Releases are cryptographically signed using Ed25519 signatures and packaged in signed release manifests (<code class="docutils literal notranslate"><span class="pre">.rsm</span></code> files). These manifests contain embedded signatures for each artifact. The manifest provides full verifiability of all release information, and contains embedded release artifact lists, per-file <code class="docutils literal notranslate"><span class="pre">.rsg</span></code> signatures, origin information, and the creators Reticulum Identity. It can also be used to fetch verified updates of the software package over the network, and can always be verified completely offline.</p>
<p>Because releases are self-verifying, they can traverse any network or physical path that Reticulum can establish. A release can travel over LoRa radio, be carried on USB drives through areas without internet connectivity, disseminated over a mirror network, or be distributed through store-and-forward mechanisms on intermittent infrastructure. Recipients can verify authenticity regardless of how they obtained the files. This is particularly valuable in low-connectivity environments where Reticulum may be the only available communication channel.</p>
<p>The <code class="docutils literal notranslate"><span class="pre">rngit</span> <span class="pre">release</span></code> command provides tools for creating, publishing, fetching, and verifying releases. When fetching a release using an <code class="docutils literal notranslate"><span class="pre">.rsm</span></code> manifest, the system validates the manifest signature against the required Reticulum Identity, extracts the origin node and repository path, connects to the origin over Reticulum, retrieves the latest release manifest, and verifies each downloaded artifact against the signatures embedded in the manifest. If any verification fails, the fetch aborts, preventing installation of corrupted or tampered files.</p>
<p>This cryptographic verification replaces the trust model of platform distribution. Instead of trusting that a platform has not been compromised, users verify that artifacts match the signatures created by the developers identity. It doesnt matter <em>how</em> they obtained the artifacts, they can <strong>always</strong> be verified. This security model shifts from <strong>institutional trust</strong> (just believe in the goodness of the platform) to <strong>cryptographic proof</strong> (verify the signatures).</p>
</section>
<section id="long-archive">
<h2>Long Archive<a class="headerlink" href="#long-archive" title="Link to this heading"></a></h2>
<p>Software development is often conceived as an activity of the present only: Solving todays problems, meeting current deadlines, responding to immediate feedback. But the artifacts produced - code, documentation, releases - have lifespans extending <em>far</em> beyond their creation. They may be used for decades, studied by future developers, depended upon by systems not yet imagined, or preserved as historical records of technological development.</p>
<p>The <code class="docutils literal notranslate"><span class="pre">rngit</span></code> system is designed with this <strong>extended timeframe</strong> in mind, supporting the creation of archives that are durable, portable, and intelligible across generational timescales. Git repositories are always internally complete; they contain full history and can be migrated to new infrastructure without loss of information. Everything that <code class="docutils literal notranslate"><span class="pre">rngit</span></code> adds on top of this is stored in normal files in standard formats right next to the Git repository folders, not an esoteric database-cluster two thousand kilometers away. Because releases are cryptographically signed, they remain verifiable as authentic regardless of when or where they are retrieved. Because the system operates over Reticulum, it can function over communication mediums that may outlast the internet as we know it.</p>
<p>This long-term perspective influences technical decisions. The use of well-established cryptographic primitives ensures that signatures will remain verifiable for centuries. The use of standard formats ensures that repositories will remain readable by future tools. The protocol-based architecture ensures that the system can evolve without losing compatibility with existing data.</p>
<p>For critical infrastructure, this archival durability is not optional; it is essential. Communication systems, cryptographic libraries, and safety-critical code must remain available and verifiable for the lifespans of the systems that depend on them. The <code class="docutils literal notranslate"><span class="pre">rngit</span></code> system provides the tools to create such archives: distributed across multiple nodes, cryptographically verified, and independent of any corporate or governmental infrastructure, which as history has shown repeatedly, does <em>not</em> persist.</p>
</section>
<section id="start-of-the-road">
<h2>Start Of The Road<a class="headerlink" href="#start-of-the-road" title="Link to this heading"></a></h2>
<p>Distributed development and production over Reticulum is a <em>different mode of existence</em> for creative work. It restores the autonomy originally created by Git. It provides local sovereignty over production infrastructure, composability of workflow, and durability of artifact. It lets you filter participation through competence and cryptography rather than incentives of platform operators, raising the quality and enjoyment of work, and protecting the focus of real engineering and creative expression.</p>
<p>This is not a system for everyone, and that is the point. It requires investment - in understanding Reticulum, in configuring infrastructure, in establishing workflows. It requires accepting responsibility for your own tools rather than delegating them to platform operators. It requires the discipline to maintain your own node, manage your own backups, and nurture your own community.</p>
<p>But for those who make this investment, the returns are substantial. You gain <strong>immunity from platform failure</strong>; your work persists regardless of corporate decisions or service outages. You gain <strong>shelter from surveillance</strong>; your development activity is visible only to those that <em>you</em> choose to involve. You gain <strong>control over process</strong>; you decide how work is conducted, reviewed, and released, unmediated by terms of service, algorithmic feeds and thousands of uninformed and irrelevant opinions.</p>
<p>Most importantly, though, you regain the <strong>dignity of craft</strong>. Development becomes an activity conducted among peers, equals among equals, mediated by skill and cryptographic proof rather than corporate permission, producing artifacts that stand as independent testimony to competence, functionality or beauty rather than as content feeding engagement metrics. The <em>work</em> becomes the point. The artifacts become durable. And the network becomes <em>one</em> of the tools you wield in this endeavor.</p>
</section>
</section>
</article>
</div>
<footer>
<div class="related-pages">
<a class="next-page" href="git.html">
<div class="page-info">
<div class="context">
<span>Next</span>
</div>
<div class="title">Git Over Reticulum</div>
</div>
<svg class="furo-related-icon"><use href="#svg-arrow-right"></use></svg>
</a>
<a class="prev-page" href="networks.html">
<svg class="furo-related-icon"><use href="#svg-arrow-right"></use></svg>
<div class="page-info">
<div class="context">
<span>Previous</span>
</div>
<div class="title">Building Networks</div>
</div>
</a>
</div>
<div class="bottom-of-page">
<div class="left-details">
<div class="copyright">
Copyright &#169; 2025, Mark Qvist
</div>
Generated with <a href="https://www.sphinx-doc.org/">Sphinx</a> and
<a href="https://github.com/pradyunsg/furo">Furo</a>
</div>
<div class="right-details">
</div>
</div>
</footer>
</div>
<aside class="toc-drawer">
<div class="toc-sticky toc-scroll">
<div class="toc-title-container">
<span class="toc-title">
On this page
</span>
</div>
<div class="toc-tree-container">
<div class="toc-tree">
<ul>
<li><a class="reference internal" href="#">Distributed Development</a><ul>
<li><a class="reference internal" href="#the-original-architecture">The Original Architecture</a></li>
<li><a class="reference internal" href="#the-platform-interregnum">The Platform Interregnum</a></li>
<li><a class="reference internal" href="#restoration">Restoration</a></li>
<li><a class="reference internal" href="#protocols-over-platforms">Protocols Over Platforms</a></li>
<li><a class="reference internal" href="#sovereignty-through-infrastructure">Sovereignty Through Infrastructure</a></li>
<li><a class="reference internal" href="#artifact-centered-workflows">Artifact-Centered Workflows</a></li>
<li><a class="reference internal" href="#composable-primitives">Composable Primitives</a></li>
<li><a class="reference internal" href="#distribution-without-intermediaries">Distribution Without Intermediaries</a></li>
<li><a class="reference internal" href="#long-archive">Long Archive</a></li>
<li><a class="reference internal" href="#start-of-the-road">Start Of The Road</a></li>
</ul>
</li>
</ul>
</div>
</div>
</div>
</aside>
</div>
</div><script src="_static/documentation_options.js?v=e917149c"></script>
<script src="_static/doctools.js?v=9bcbadda"></script>
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
<script src="_static/clipboard.min.js?v=a7894cd8"></script>
<script src="_static/copybutton.js?v=f281be69"></script>
</body>
</html>
+5 -4
View File
@@ -7,7 +7,7 @@
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
<title>Code Examples - Reticulum Network Stack 1.2.4 documentation</title>
<title>Code Examples - Reticulum Network Stack 1.2.9 documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
@@ -180,7 +180,7 @@
</label>
</div>
<div class="header-center">
<a href="index.html"><div class="brand">Reticulum Network Stack 1.2.4 documentation</div></a>
<a href="index.html"><div class="brand">Reticulum Network Stack 1.2.9 documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -204,7 +204,7 @@
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
</div>
<span class="sidebar-brand-text">Reticulum Network Stack 1.2.4 documentation</span>
<span class="sidebar-brand-text">Reticulum Network Stack 1.2.9 documentation</span>
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
@@ -222,6 +222,7 @@
<li class="toctree-l1"><a class="reference internal" href="hardware.html">Communications Hardware</a></li>
<li class="toctree-l1"><a class="reference internal" href="interfaces.html">Configuring Interfaces</a></li>
<li class="toctree-l1"><a class="reference internal" href="networks.html">Building Networks</a></li>
<li class="toctree-l1"><a class="reference internal" href="distributed.html">Distributed Development</a></li>
<li class="toctree-l1"><a class="reference internal" href="git.html">Git Over Reticulum</a></li>
<li class="toctree-l1"><a class="reference internal" href="support.html">Support Reticulum</a></li>
<li class="toctree-l1 current current-page"><a class="current reference internal" href="#">Code Examples</a></li>
@@ -3664,7 +3665,7 @@ will be fully on-par with natively included interfaces, including all supported
</aside>
</div>
</div><script src="_static/documentation_options.js?v=928db92d"></script>
</div><script src="_static/documentation_options.js?v=e917149c"></script>
<script src="_static/doctools.js?v=9bcbadda"></script>
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
+5 -4
View File
@@ -7,7 +7,7 @@
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
<title>An Explanation of Reticulum for Human Beings - Reticulum Network Stack 1.2.4 documentation</title>
<title>An Explanation of Reticulum for Human Beings - Reticulum Network Stack 1.2.9 documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
@@ -180,7 +180,7 @@
</label>
</div>
<div class="header-center">
<a href="index.html"><div class="brand">Reticulum Network Stack 1.2.4 documentation</div></a>
<a href="index.html"><div class="brand">Reticulum Network Stack 1.2.9 documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -204,7 +204,7 @@
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
</div>
<span class="sidebar-brand-text">Reticulum Network Stack 1.2.4 documentation</span>
<span class="sidebar-brand-text">Reticulum Network Stack 1.2.9 documentation</span>
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
@@ -222,6 +222,7 @@
<li class="toctree-l1"><a class="reference internal" href="hardware.html">Communications Hardware</a></li>
<li class="toctree-l1"><a class="reference internal" href="interfaces.html">Configuring Interfaces</a></li>
<li class="toctree-l1"><a class="reference internal" href="networks.html">Building Networks</a></li>
<li class="toctree-l1"><a class="reference internal" href="distributed.html">Distributed Development</a></li>
<li class="toctree-l1"><a class="reference internal" href="git.html">Git Over Reticulum</a></li>
<li class="toctree-l1"><a class="reference internal" href="support.html">Support Reticulum</a></li>
<li class="toctree-l1"><a class="reference internal" href="examples.html">Code Examples</a></li>
@@ -295,7 +296,7 @@
</aside>
</div>
</div><script src="_static/documentation_options.js?v=928db92d"></script>
</div><script src="_static/documentation_options.js?v=e917149c"></script>
<script src="_static/doctools.js?v=9bcbadda"></script>
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
+5 -4
View File
@@ -5,7 +5,7 @@
<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">
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 --><title>Index - Reticulum Network Stack 1.2.4 documentation</title>
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 --><title>Index - Reticulum Network Stack 1.2.9 documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
@@ -178,7 +178,7 @@
</label>
</div>
<div class="header-center">
<a href="index.html"><div class="brand">Reticulum Network Stack 1.2.4 documentation</div></a>
<a href="index.html"><div class="brand">Reticulum Network Stack 1.2.9 documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -202,7 +202,7 @@
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
</div>
<span class="sidebar-brand-text">Reticulum Network Stack 1.2.4 documentation</span>
<span class="sidebar-brand-text">Reticulum Network Stack 1.2.9 documentation</span>
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
@@ -220,6 +220,7 @@
<li class="toctree-l1"><a class="reference internal" href="hardware.html">Communications Hardware</a></li>
<li class="toctree-l1"><a class="reference internal" href="interfaces.html">Configuring Interfaces</a></li>
<li class="toctree-l1"><a class="reference internal" href="networks.html">Building Networks</a></li>
<li class="toctree-l1"><a class="reference internal" href="distributed.html">Distributed Development</a></li>
<li class="toctree-l1"><a class="reference internal" href="git.html">Git Over Reticulum</a></li>
<li class="toctree-l1"><a class="reference internal" href="support.html">Support Reticulum</a></li>
<li class="toctree-l1"><a class="reference internal" href="examples.html">Code Examples</a></li>
@@ -839,7 +840,7 @@
</aside>
</div>
</div><script src="_static/documentation_options.js?v=928db92d"></script>
</div><script src="_static/documentation_options.js?v=e917149c"></script>
<script src="_static/doctools.js?v=9bcbadda"></script>
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
+5 -4
View File
@@ -7,7 +7,7 @@
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
<title>Getting Started Fast - Reticulum Network Stack 1.2.4 documentation</title>
<title>Getting Started Fast - Reticulum Network Stack 1.2.9 documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
@@ -180,7 +180,7 @@
</label>
</div>
<div class="header-center">
<a href="index.html"><div class="brand">Reticulum Network Stack 1.2.4 documentation</div></a>
<a href="index.html"><div class="brand">Reticulum Network Stack 1.2.9 documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -204,7 +204,7 @@
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
</div>
<span class="sidebar-brand-text">Reticulum Network Stack 1.2.4 documentation</span>
<span class="sidebar-brand-text">Reticulum Network Stack 1.2.9 documentation</span>
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
@@ -222,6 +222,7 @@
<li class="toctree-l1"><a class="reference internal" href="hardware.html">Communications Hardware</a></li>
<li class="toctree-l1"><a class="reference internal" href="interfaces.html">Configuring Interfaces</a></li>
<li class="toctree-l1"><a class="reference internal" href="networks.html">Building Networks</a></li>
<li class="toctree-l1"><a class="reference internal" href="distributed.html">Distributed Development</a></li>
<li class="toctree-l1"><a class="reference internal" href="git.html">Git Over Reticulum</a></li>
<li class="toctree-l1"><a class="reference internal" href="support.html">Support Reticulum</a></li>
<li class="toctree-l1"><a class="reference internal" href="examples.html">Code Examples</a></li>
@@ -967,7 +968,7 @@ All other available modules will still be loaded when needed.</p>
</aside>
</div>
</div><script src="_static/documentation_options.js?v=928db92d"></script>
</div><script src="_static/documentation_options.js?v=e917149c"></script>
<script src="_static/doctools.js?v=9bcbadda"></script>
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
+732 -68
View File
File diff suppressed because it is too large Load Diff
+5 -4
View File
@@ -7,7 +7,7 @@
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
<title>Communications Hardware - Reticulum Network Stack 1.2.4 documentation</title>
<title>Communications Hardware - Reticulum Network Stack 1.2.9 documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
@@ -180,7 +180,7 @@
</label>
</div>
<div class="header-center">
<a href="index.html"><div class="brand">Reticulum Network Stack 1.2.4 documentation</div></a>
<a href="index.html"><div class="brand">Reticulum Network Stack 1.2.9 documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -204,7 +204,7 @@
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
</div>
<span class="sidebar-brand-text">Reticulum Network Stack 1.2.4 documentation</span>
<span class="sidebar-brand-text">Reticulum Network Stack 1.2.9 documentation</span>
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
@@ -222,6 +222,7 @@
<li class="toctree-l1 current current-page"><a class="current reference internal" href="#">Communications Hardware</a></li>
<li class="toctree-l1"><a class="reference internal" href="interfaces.html">Configuring Interfaces</a></li>
<li class="toctree-l1"><a class="reference internal" href="networks.html">Building Networks</a></li>
<li class="toctree-l1"><a class="reference internal" href="distributed.html">Distributed Development</a></li>
<li class="toctree-l1"><a class="reference internal" href="git.html">Git Over Reticulum</a></li>
<li class="toctree-l1"><a class="reference internal" href="support.html">Support Reticulum</a></li>
<li class="toctree-l1"><a class="reference internal" href="examples.html">Code Examples</a></li>
@@ -675,7 +676,7 @@ can be used with Reticulum. This includes virtual software modems such as
</aside>
</div>
</div><script src="_static/documentation_options.js?v=928db92d"></script>
</div><script src="_static/documentation_options.js?v=e917149c"></script>
<script src="_static/doctools.js?v=9bcbadda"></script>
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
+79 -13
View File
@@ -7,7 +7,7 @@
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
<title>Reticulum Network Stack 1.2.4 documentation</title>
<title>Reticulum Network Stack 1.2.9 documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
@@ -180,7 +180,7 @@
</label>
</div>
<div class="header-center">
<a href="#"><div class="brand">Reticulum Network Stack 1.2.4 documentation</div></a>
<a href="#"><div class="brand">Reticulum Network Stack 1.2.9 documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -204,7 +204,7 @@
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
</div>
<span class="sidebar-brand-text">Reticulum Network Stack 1.2.4 documentation</span>
<span class="sidebar-brand-text">Reticulum Network Stack 1.2.9 documentation</span>
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
@@ -222,6 +222,7 @@
<li class="toctree-l1"><a class="reference internal" href="hardware.html">Communications Hardware</a></li>
<li class="toctree-l1"><a class="reference internal" href="interfaces.html">Configuring Interfaces</a></li>
<li class="toctree-l1"><a class="reference internal" href="networks.html">Building Networks</a></li>
<li class="toctree-l1"><a class="reference internal" href="distributed.html">Distributed Development</a></li>
<li class="toctree-l1"><a class="reference internal" href="git.html">Git Over Reticulum</a></li>
<li class="toctree-l1"><a class="reference internal" href="support.html">Support Reticulum</a></li>
<li class="toctree-l1"><a class="reference internal" href="examples.html">Code Examples</a></li>
@@ -378,8 +379,6 @@ to participate in the development of Reticulum itself.</p>
<li class="toctree-l3"><a class="reference internal" href="software.html#retipedia">Retipedia</a></li>
<li class="toctree-l3"><a class="reference internal" href="software.html#sideband">Sideband</a></li>
<li class="toctree-l3"><a class="reference internal" href="software.html#meshchatx">MeshChatX</a></li>
<li class="toctree-l3"><a class="reference internal" href="software.html#meshchat">MeshChat</a></li>
<li class="toctree-l3"><a class="reference internal" href="software.html#columba">Columba</a></li>
<li class="toctree-l3"><a class="reference internal" href="software.html#reticulum-relay-chat">Reticulum Relay Chat</a></li>
<li class="toctree-l3"><a class="reference internal" href="software.html#retibbs">RetiBBS</a></li>
<li class="toctree-l3"><a class="reference internal" href="software.html#rbrowser">RBrowser</a></li>
@@ -394,7 +393,7 @@ to participate in the development of Reticulum itself.</p>
</li>
<li class="toctree-l2"><a class="reference internal" href="software.html#protocols">Protocols</a><ul>
<li class="toctree-l3"><a class="reference internal" href="software.html#lxmf">LXMF</a></li>
<li class="toctree-l3"><a class="reference internal" href="software.html#id17">LXST</a></li>
<li class="toctree-l3"><a class="reference internal" href="software.html#id16">LXST</a></li>
<li class="toctree-l3"><a class="reference internal" href="software.html#rrc">RRC</a></li>
</ul>
</li>
@@ -511,6 +510,7 @@ to participate in the development of Reticulum itself.</p>
<li class="toctree-l2"><a class="reference internal" href="interfaces.html#interfaces-modes">Interface Modes</a></li>
<li class="toctree-l2"><a class="reference internal" href="interfaces.html#announce-rate-control">Announce Rate Control</a></li>
<li class="toctree-l2"><a class="reference internal" href="interfaces.html#new-destination-rate-limiting">New Destination Rate Limiting</a></li>
<li class="toctree-l2"><a class="reference internal" href="interfaces.html#path-request-burst-control">Path Request Burst Control</a></li>
</ul>
</li>
<li class="toctree-l1"><a class="reference internal" href="networks.html">Building Networks</a><ul>
@@ -524,14 +524,80 @@ to participate in the development of Reticulum itself.</p>
</li>
</ul>
</li>
<li class="toctree-l1"><a class="reference internal" href="distributed.html">Distributed Development</a><ul>
<li class="toctree-l2"><a class="reference internal" href="distributed.html#the-original-architecture">The Original Architecture</a></li>
<li class="toctree-l2"><a class="reference internal" href="distributed.html#the-platform-interregnum">The Platform Interregnum</a></li>
<li class="toctree-l2"><a class="reference internal" href="distributed.html#restoration">Restoration</a></li>
<li class="toctree-l2"><a class="reference internal" href="distributed.html#protocols-over-platforms">Protocols Over Platforms</a></li>
<li class="toctree-l2"><a class="reference internal" href="distributed.html#sovereignty-through-infrastructure">Sovereignty Through Infrastructure</a></li>
<li class="toctree-l2"><a class="reference internal" href="distributed.html#artifact-centered-workflows">Artifact-Centered Workflows</a></li>
<li class="toctree-l2"><a class="reference internal" href="distributed.html#composable-primitives">Composable Primitives</a></li>
<li class="toctree-l2"><a class="reference internal" href="distributed.html#distribution-without-intermediaries">Distribution Without Intermediaries</a></li>
<li class="toctree-l2"><a class="reference internal" href="distributed.html#long-archive">Long Archive</a></li>
<li class="toctree-l2"><a class="reference internal" href="distributed.html#start-of-the-road">Start Of The Road</a></li>
</ul>
</li>
<li class="toctree-l1"><a class="reference internal" href="git.html">Git Over Reticulum</a><ul>
<li class="toctree-l2"><a class="reference internal" href="git.html#the-rngit-utility">The rngit Utility</a></li>
<li class="toctree-l2"><a class="reference internal" href="git.html#repository-structure">Repository Structure</a></li>
<li class="toctree-l2"><a class="reference internal" href="git.html#serving-pages-over-nomad-network">Serving Pages Over Nomad Network</a></li>
<li class="toctree-l2"><a class="reference internal" href="git.html#formatting-syntax-highlighting">Formatting &amp; Syntax Highlighting</a></li>
<li class="toctree-l2"><a class="reference internal" href="git.html#customizing-templates">Customizing Templates</a></li>
<li class="toctree-l2"><a class="reference internal" href="git.html#release-management">Release Management</a></li>
<li class="toctree-l2"><a class="reference internal" href="git.html#work-documents">Work Documents</a></li>
<li class="toctree-l2"><a class="reference internal" href="git.html#repository-creation-management">Repository Creation &amp; Management</a><ul>
<li class="toctree-l3"><a class="reference internal" href="git.html#creating-empty-repositories">Creating Empty Repositories</a></li>
<li class="toctree-l3"><a class="reference internal" href="git.html#forking-repositories">Forking Repositories</a></li>
<li class="toctree-l3"><a class="reference internal" href="git.html#mirroring-repositories">Mirroring Repositories</a></li>
<li class="toctree-l3"><a class="reference internal" href="git.html#automatic-mirror-synchronization">Automatic Mirror Synchronization</a></li>
<li class="toctree-l3"><a class="reference internal" href="git.html#manual-synchronization">Manual Synchronization</a></li>
<li class="toctree-l3"><a class="reference internal" href="git.html#git-configuration-parameters">Git Configuration Parameters</a></li>
</ul>
</li>
<li class="toctree-l2"><a class="reference internal" href="git.html#repository-structure">Repository Structure</a><ul>
<li class="toctree-l3"><a class="reference internal" href="git.html#configuration">Configuration</a></li>
</ul>
</li>
<li class="toctree-l2"><a class="reference internal" href="git.html#permissions">Permissions</a><ul>
<li class="toctree-l3"><a class="reference internal" href="git.html#permission-types">Permission Types</a></li>
<li class="toctree-l3"><a class="reference internal" href="git.html#permission-hierarchy">Permission Hierarchy</a></li>
<li class="toctree-l3"><a class="reference internal" href="git.html#configuration-methods">Configuration Methods</a></li>
<li class="toctree-l3"><a class="reference internal" href="git.html#work-document-permissions">Work Document Permissions</a></li>
<li class="toctree-l3"><a class="reference internal" href="git.html#creator-permissions">Creator Permissions</a></li>
<li class="toctree-l3"><a class="reference internal" href="git.html#permission-examples">Permission Examples</a></li>
<li class="toctree-l3"><a class="reference internal" href="git.html#permission-short-forms">Permission Short Forms</a></li>
<li class="toctree-l3"><a class="reference internal" href="git.html#permission-configuration-locations">Permission Configuration Locations</a></li>
</ul>
</li>
<li class="toctree-l2"><a class="reference internal" href="git.html#remote-permission-management">Remote Permission Management</a><ul>
<li class="toctree-l3"><a class="reference internal" href="git.html#managing-group-permissions">Managing Group Permissions</a></li>
<li class="toctree-l3"><a class="reference internal" href="git.html#managing-repository-permissions">Managing Repository Permissions</a></li>
<li class="toctree-l3"><a class="reference internal" href="git.html#permission-validation">Permission Validation</a></li>
</ul>
</li>
<li class="toctree-l2"><a class="reference internal" href="git.html#identity-destination-aliases">Identity &amp; Destination Aliases</a></li>
<li class="toctree-l2"><a class="reference internal" href="git.html#serving-pages-over-nomad-network">Serving Pages Over Nomad Network</a><ul>
<li class="toctree-l3"><a class="reference internal" href="git.html#enabling-the-git-page-node">Enabling the Git Page Node</a></li>
<li class="toctree-l3"><a class="reference internal" href="git.html#accessing-repository-pages">Accessing Repository Pages</a></li>
<li class="toctree-l3"><a class="reference internal" href="git.html#formatting-syntax-highlighting">Formatting &amp; Syntax Highlighting</a></li>
<li class="toctree-l3"><a class="reference internal" href="git.html#customizing-templates">Customizing Templates</a></li>
<li class="toctree-l3"><a class="reference internal" href="git.html#repository-statistics">Repository Statistics</a></li>
<li class="toctree-l3"><a class="reference internal" href="git.html#configuration-example">Configuration Example</a></li>
</ul>
</li>
<li class="toctree-l2"><a class="reference internal" href="git.html#verified-releases">Verified Releases</a><ul>
<li class="toctree-l3"><a class="reference internal" href="git.html#obtaining-verified-releases">Obtaining Verified Releases</a></li>
<li class="toctree-l3"><a class="reference internal" href="git.html#creating-signed-releases">Creating Signed Releases</a></li>
</ul>
</li>
<li class="toctree-l2"><a class="reference internal" href="git.html#release-management">Release Management</a><ul>
<li class="toctree-l3"><a class="reference internal" href="git.html#the-release-workflow">The Release Workflow</a></li>
<li class="toctree-l3"><a class="reference internal" href="git.html#release-storage-structure">Release Storage &amp; Structure</a></li>
<li class="toctree-l3"><a class="reference internal" href="git.html#command-line-interaction">Command-Line Interaction</a></li>
</ul>
</li>
<li class="toctree-l2"><a class="reference internal" href="git.html#work-documents">Work Documents</a><ul>
<li class="toctree-l3"><a class="reference internal" href="git.html#working-with-work-documents">Working With Work Documents</a></li>
<li class="toctree-l3"><a class="reference internal" href="git.html#proposing-work-documents">Proposing Work Documents</a></li>
<li class="toctree-l3"><a class="reference internal" href="git.html#state-management">State Management</a></li>
<li class="toctree-l3"><a class="reference internal" href="git.html#managing-work-document-permissions">Managing Work Document Permissions</a></li>
<li class="toctree-l3"><a class="reference internal" href="git.html#cryptographic-attribution">Cryptographic Attribution</a></li>
</ul>
</li>
</ul>
</li>
<li class="toctree-l1"><a class="reference internal" href="support.html">Support Reticulum</a><ul>
@@ -644,7 +710,7 @@ to participate in the development of Reticulum itself.</p>
</aside>
</div>
</div><script src="_static/documentation_options.js?v=928db92d"></script>
</div><script src="_static/documentation_options.js?v=e917149c"></script>
<script src="_static/doctools.js?v=9bcbadda"></script>
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
+83 -9
View File
@@ -7,7 +7,7 @@
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
<title>Configuring Interfaces - Reticulum Network Stack 1.2.4 documentation</title>
<title>Configuring Interfaces - Reticulum Network Stack 1.2.9 documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
@@ -180,7 +180,7 @@
</label>
</div>
<div class="header-center">
<a href="index.html"><div class="brand">Reticulum Network Stack 1.2.4 documentation</div></a>
<a href="index.html"><div class="brand">Reticulum Network Stack 1.2.9 documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -204,7 +204,7 @@
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
</div>
<span class="sidebar-brand-text">Reticulum Network Stack 1.2.4 documentation</span>
<span class="sidebar-brand-text">Reticulum Network Stack 1.2.9 documentation</span>
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
@@ -222,6 +222,7 @@
<li class="toctree-l1"><a class="reference internal" href="hardware.html">Communications Hardware</a></li>
<li class="toctree-l1 current current-page"><a class="current reference internal" href="#">Configuring Interfaces</a></li>
<li class="toctree-l1"><a class="reference internal" href="networks.html">Building Networks</a></li>
<li class="toctree-l1"><a class="reference internal" href="distributed.html">Distributed Development</a></li>
<li class="toctree-l1"><a class="reference internal" href="git.html">Git Over Reticulum</a></li>
<li class="toctree-l1"><a class="reference internal" href="support.html">Support Reticulum</a></li>
<li class="toctree-l1"><a class="reference internal" href="examples.html">Code Examples</a></li>
@@ -1533,11 +1534,14 @@ also means, that should a node decide to connect to a public interface, announce
a large amount of bogus destinations, and then disconnect, these destination will
never make it into path tables and waste network bandwidth on retransmitted
announces.</p>
<p><strong>Its important to note</strong> that the ingress control works at the level of <em>individual
<div class="admonition note">
<p class="admonition-title">Note</p>
<p>Its important to remember that the ingress control works at the level of <em>individual
sub-interfaces</em>. As an example, this means that one client on a <a class="reference internal" href="#interfaces-tcps"><span class="std std-ref">TCP Server Interface</span></a>
cannot disrupt processing of incoming announces for other connected clients on the same
<a class="reference internal" href="#interfaces-tcps"><span class="std std-ref">TCP Server Interface</span></a>. All other clients on the same interface will still have new announces
processed without interruption.</p>
<a class="reference internal" href="#interfaces-tcps"><span class="std std-ref">TCP Server Interface</span></a>. All other clients on the same interface
will still have new announces processed without interruption.</p>
</div>
<p>By default, Reticulum will handle this automatically, and ingress announce
control will be enabled on interface where it is sensible to do so. It should
generally not be neccessary to modify the ingress control configuration,
@@ -1546,8 +1550,7 @@ but all the parameters are exposed for configuration if needed.</p>
<div><ul>
<li><div class="line-block">
<div class="line">The <code class="docutils literal notranslate"><span class="pre">ingress_control</span></code> option tells Reticulum whether or not
to enable announce ingress control on the interface. Defaults to
<code class="docutils literal notranslate"><span class="pre">True</span></code>.</div>
to enable ingress control on the interface. Defaults to <code class="docutils literal notranslate"><span class="pre">True</span></code>.</div>
</div>
</li>
<li><div class="line-block">
@@ -1602,6 +1605,76 @@ to <code class="docutils literal notranslate"><span class="pre">30</span></code>
</li>
</ul>
</div></blockquote>
<p>All of the above settings can be configured both as instance-wide defaults
under the <code class="docutils literal notranslate"><span class="pre">[reticulum]</span></code> section of the configuration file, or on a per-
interface basis under the relevant interface configuration section.</p>
</section>
<section id="path-request-burst-control">
<h2>Path Request Burst Control<a class="headerlink" href="#path-request-burst-control" title="Link to this heading"></a></h2>
<p>In addition the announce controls for newly created destination, Reticulum will also
monitor incoming path request activity, and enforce burst controls if per-client rates
exceed configured limits. Once path request burst control is activated on an
interface, path requests will no longer be propagated further on the network.
As with announce burst control, this happens on a per sub-interface basis. One
client connecting to a public gateway will not be able to disrupt path request
processing for other clients.</p>
<div class="admonition warning">
<p class="admonition-title">Warning</p>
<p>Applications that send large amounts of unnecessary path requests will very
quickly get rate limited by transport nodes, and the entire system they are
running on will not be able to resolve any paths on the network, until the
burst subsides and hold period expires. <strong>Do not</strong> write applications like
this. Only request paths for destinations you need to communicate with.</p>
</div>
<p>By default, Reticulum will handle this automatically, and ingress path request
control will be enabled on interface where it is sensible to do so. It should
generally not be neccessary to modify the ingress control configuration,
but all the parameters are exposed for configuration if needed.</p>
<blockquote>
<div><ul>
<li><div class="line-block">
<div class="line">The <code class="docutils literal notranslate"><span class="pre">ingress_control</span></code> option tells Reticulum whether or not
to enable ingress control on the interface. Defaults to <code class="docutils literal notranslate"><span class="pre">True</span></code>.</div>
</div>
</li>
<li><div class="line-block">
<div class="line">The <code class="docutils literal notranslate"><span class="pre">ic_new_time</span></code> option configures how long (in seconds) an
interface is considered newly spawned. Defaults to <code class="docutils literal notranslate"><span class="pre">2*60*60</span></code> seconds. This
option is useful on publicly accessible interfaces that spawn new
sub-interfaces when a new client connects.</div>
</div>
</li>
<li><div class="line-block">
<div class="line">The <code class="docutils literal notranslate"><span class="pre">ic_pr_burst_freq_new</span></code> option sets the maximum path request
ingress frequency for newly spawned interfaces. Defaults to <code class="docutils literal notranslate"><span class="pre">3</span></code>
path requests per second.</div>
</div>
</li>
<li><div class="line-block">
<div class="line">The <code class="docutils literal notranslate"><span class="pre">ic_pr_burst_freq</span></code> option sets the maximum path request
ingress frequency for other interfaces. Defaults to <code class="docutils literal notranslate"><span class="pre">8</span></code> path requests
per second.</div>
</div>
<blockquote>
<div><p><em>If an interface exceeds its burst frequency, incoming path requests
from that system will not traverse the network further.</em></p>
</div></blockquote>
</li>
<li><div class="line-block">
<div class="line">The <code class="docutils literal notranslate"><span class="pre">egress_control</span></code> option enables hard-limiting path request egress
control per-interface. Defaults to <code class="docutils literal notranslate"><span class="pre">False</span></code></div>
</div>
</li>
<li><div class="line-block">
<div class="line">The <code class="docutils literal notranslate"><span class="pre">ec_pr_freq</span></code> option sets the hard limit for outbound path requests
per second on a given interface.</div>
</div>
</li>
</ul>
</div></blockquote>
<p>All of the above settings can be configured both as instance-wide defaults
under the <code class="docutils literal notranslate"><span class="pre">[reticulum]</span></code> section of the configuration file, or on a per-
interface basis under the relevant interface configuration section.</p>
</section>
</section>
@@ -1689,6 +1762,7 @@ to <code class="docutils literal notranslate"><span class="pre">30</span></code>
<li><a class="reference internal" href="#interfaces-modes">Interface Modes</a></li>
<li><a class="reference internal" href="#announce-rate-control">Announce Rate Control</a></li>
<li><a class="reference internal" href="#new-destination-rate-limiting">New Destination Rate Limiting</a></li>
<li><a class="reference internal" href="#path-request-burst-control">Path Request Burst Control</a></li>
</ul>
</li>
</ul>
@@ -1700,7 +1774,7 @@ to <code class="docutils literal notranslate"><span class="pre">30</span></code>
</aside>
</div>
</div><script src="_static/documentation_options.js?v=928db92d"></script>
</div><script src="_static/documentation_options.js?v=e917149c"></script>
<script src="_static/doctools.js?v=9bcbadda"></script>
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
+5 -4
View File
@@ -7,7 +7,7 @@
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
<title>Reticulum License - Reticulum Network Stack 1.2.4 documentation</title>
<title>Reticulum License - Reticulum Network Stack 1.2.9 documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
@@ -180,7 +180,7 @@
</label>
</div>
<div class="header-center">
<a href="index.html"><div class="brand">Reticulum Network Stack 1.2.4 documentation</div></a>
<a href="index.html"><div class="brand">Reticulum Network Stack 1.2.9 documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -204,7 +204,7 @@
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
</div>
<span class="sidebar-brand-text">Reticulum Network Stack 1.2.4 documentation</span>
<span class="sidebar-brand-text">Reticulum Network Stack 1.2.9 documentation</span>
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
@@ -222,6 +222,7 @@
<li class="toctree-l1"><a class="reference internal" href="hardware.html">Communications Hardware</a></li>
<li class="toctree-l1"><a class="reference internal" href="interfaces.html">Configuring Interfaces</a></li>
<li class="toctree-l1"><a class="reference internal" href="networks.html">Building Networks</a></li>
<li class="toctree-l1"><a class="reference internal" href="distributed.html">Distributed Development</a></li>
<li class="toctree-l1"><a class="reference internal" href="git.html">Git Over Reticulum</a></li>
<li class="toctree-l1"><a class="reference internal" href="support.html">Support Reticulum</a></li>
<li class="toctree-l1"><a class="reference internal" href="examples.html">Code Examples</a></li>
@@ -344,7 +345,7 @@ SOFTWARE.
</aside>
</div>
</div><script src="_static/documentation_options.js?v=928db92d"></script>
</div><script src="_static/documentation_options.js?v=e917149c"></script>
<script src="_static/doctools.js?v=9bcbadda"></script>
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
+8 -7
View File
@@ -3,11 +3,11 @@
<head><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="color-scheme" content="light dark"><meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="index" title="Index" href="genindex.html"><link rel="search" title="Search" href="search.html"><link rel="next" title="Git Over Reticulum" href="git.html"><link rel="prev" title="Configuring Interfaces" href="interfaces.html">
<link rel="index" title="Index" href="genindex.html"><link rel="search" title="Search" href="search.html"><link rel="next" title="Distributed Development" href="distributed.html"><link rel="prev" title="Configuring Interfaces" href="interfaces.html">
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
<title>Building Networks - Reticulum Network Stack 1.2.4 documentation</title>
<title>Building Networks - Reticulum Network Stack 1.2.9 documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
@@ -180,7 +180,7 @@
</label>
</div>
<div class="header-center">
<a href="index.html"><div class="brand">Reticulum Network Stack 1.2.4 documentation</div></a>
<a href="index.html"><div class="brand">Reticulum Network Stack 1.2.9 documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -204,7 +204,7 @@
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
</div>
<span class="sidebar-brand-text">Reticulum Network Stack 1.2.4 documentation</span>
<span class="sidebar-brand-text">Reticulum Network Stack 1.2.9 documentation</span>
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
@@ -222,6 +222,7 @@
<li class="toctree-l1"><a class="reference internal" href="hardware.html">Communications Hardware</a></li>
<li class="toctree-l1"><a class="reference internal" href="interfaces.html">Configuring Interfaces</a></li>
<li class="toctree-l1 current current-page"><a class="current reference internal" href="#">Building Networks</a></li>
<li class="toctree-l1"><a class="reference internal" href="distributed.html">Distributed Development</a></li>
<li class="toctree-l1"><a class="reference internal" href="git.html">Git Over Reticulum</a></li>
<li class="toctree-l1"><a class="reference internal" href="support.html">Support Reticulum</a></li>
<li class="toctree-l1"><a class="reference internal" href="examples.html">Code Examples</a></li>
@@ -594,12 +595,12 @@ differently than a mobile device roaming between radio cells.</p>
<footer>
<div class="related-pages">
<a class="next-page" href="git.html">
<a class="next-page" href="distributed.html">
<div class="page-info">
<div class="context">
<span>Next</span>
</div>
<div class="title">Git Over Reticulum</div>
<div class="title">Distributed Development</div>
</div>
<svg class="furo-related-icon"><use href="#svg-arrow-right"></use></svg>
</a>
@@ -663,7 +664,7 @@ differently than a mobile device roaming between radio cells.</p>
</aside>
</div>
</div><script src="_static/documentation_options.js?v=928db92d"></script>
</div><script src="_static/documentation_options.js?v=e917149c"></script>
<script src="_static/doctools.js?v=9bcbadda"></script>
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
Binary file not shown.
+5 -4
View File
@@ -7,7 +7,7 @@
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
<title>API Reference - Reticulum Network Stack 1.2.4 documentation</title>
<title>API Reference - Reticulum Network Stack 1.2.9 documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
@@ -180,7 +180,7 @@
</label>
</div>
<div class="header-center">
<a href="index.html"><div class="brand">Reticulum Network Stack 1.2.4 documentation</div></a>
<a href="index.html"><div class="brand">Reticulum Network Stack 1.2.9 documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -204,7 +204,7 @@
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
</div>
<span class="sidebar-brand-text">Reticulum Network Stack 1.2.4 documentation</span>
<span class="sidebar-brand-text">Reticulum Network Stack 1.2.9 documentation</span>
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
@@ -222,6 +222,7 @@
<li class="toctree-l1"><a class="reference internal" href="hardware.html">Communications Hardware</a></li>
<li class="toctree-l1"><a class="reference internal" href="interfaces.html">Configuring Interfaces</a></li>
<li class="toctree-l1"><a class="reference internal" href="networks.html">Building Networks</a></li>
<li class="toctree-l1"><a class="reference internal" href="distributed.html">Distributed Development</a></li>
<li class="toctree-l1"><a class="reference internal" href="git.html">Git Over Reticulum</a></li>
<li class="toctree-l1"><a class="reference internal" href="support.html">Support Reticulum</a></li>
<li class="toctree-l1"><a class="reference internal" href="examples.html">Code Examples</a></li>
@@ -2484,7 +2485,7 @@ will announce it.</p>
</aside>
</div>
</div><script src="_static/documentation_options.js?v=928db92d"></script>
</div><script src="_static/documentation_options.js?v=e917149c"></script>
<script src="_static/doctools.js?v=9bcbadda"></script>
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
+5 -4
View File
@@ -8,7 +8,7 @@
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
<meta name="robots" content="noindex" />
<title>Search - Reticulum Network Stack 1.2.4 documentation</title><link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
<title>Search - Reticulum Network Stack 1.2.9 documentation</title><link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo-extensions.css?v=8dab3a3b" />
@@ -180,7 +180,7 @@
</label>
</div>
<div class="header-center">
<a href="index.html"><div class="brand">Reticulum Network Stack 1.2.4 documentation</div></a>
<a href="index.html"><div class="brand">Reticulum Network Stack 1.2.9 documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -204,7 +204,7 @@
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
</div>
<span class="sidebar-brand-text">Reticulum Network Stack 1.2.4 documentation</span>
<span class="sidebar-brand-text">Reticulum Network Stack 1.2.9 documentation</span>
</a><form class="sidebar-search-container" method="get" action="#" role="search">
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
@@ -222,6 +222,7 @@
<li class="toctree-l1"><a class="reference internal" href="hardware.html">Communications Hardware</a></li>
<li class="toctree-l1"><a class="reference internal" href="interfaces.html">Configuring Interfaces</a></li>
<li class="toctree-l1"><a class="reference internal" href="networks.html">Building Networks</a></li>
<li class="toctree-l1"><a class="reference internal" href="distributed.html">Distributed Development</a></li>
<li class="toctree-l1"><a class="reference internal" href="git.html">Git Over Reticulum</a></li>
<li class="toctree-l1"><a class="reference internal" href="support.html">Support Reticulum</a></li>
<li class="toctree-l1"><a class="reference internal" href="examples.html">Code Examples</a></li>
@@ -303,7 +304,7 @@
</aside>
</div>
</div><script src="_static/documentation_options.js?v=928db92d"></script>
</div><script src="_static/documentation_options.js?v=e917149c"></script>
<script src="_static/doctools.js?v=9bcbadda"></script>
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
File diff suppressed because one or more lines are too long
+9 -30
View File
@@ -7,7 +7,7 @@
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
<title>Programs Using Reticulum - Reticulum Network Stack 1.2.4 documentation</title>
<title>Programs Using Reticulum - Reticulum Network Stack 1.2.9 documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
@@ -180,7 +180,7 @@
</label>
</div>
<div class="header-center">
<a href="index.html"><div class="brand">Reticulum Network Stack 1.2.4 documentation</div></a>
<a href="index.html"><div class="brand">Reticulum Network Stack 1.2.9 documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -204,7 +204,7 @@
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
</div>
<span class="sidebar-brand-text">Reticulum Network Stack 1.2.4 documentation</span>
<span class="sidebar-brand-text">Reticulum Network Stack 1.2.9 documentation</span>
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
@@ -222,6 +222,7 @@
<li class="toctree-l1"><a class="reference internal" href="hardware.html">Communications Hardware</a></li>
<li class="toctree-l1"><a class="reference internal" href="interfaces.html">Configuring Interfaces</a></li>
<li class="toctree-l1"><a class="reference internal" href="networks.html">Building Networks</a></li>
<li class="toctree-l1"><a class="reference internal" href="distributed.html">Distributed Development</a></li>
<li class="toctree-l1"><a class="reference internal" href="git.html">Git Over Reticulum</a></li>
<li class="toctree-l1"><a class="reference internal" href="support.html">Support Reticulum</a></li>
<li class="toctree-l1"><a class="reference internal" href="examples.html">Code Examples</a></li>
@@ -328,31 +329,11 @@ plugin system for expandability.</p>
</section>
<section id="meshchatx">
<h3>MeshChatX<a class="headerlink" href="#meshchatx" title="Link to this heading"></a></h3>
<p>A <a class="reference external" href="https://git.quad4.io/RNS-Things/MeshChatX">Reticulum MeshChat fork from the future</a>, with the goal of providing everything you need for Reticulum, LXMF, and LXST in one beautiful and feature-rich application. This project is separate from the original Reticulum MeshChat project, and is not affiliated with the original project.</p>
<p>A <a class="reference external" href="https://git.quad4.io/RNS-Things/MeshChatX">Reticulum MeshChat fork from the future</a>, with the goal of providing everything you need for Reticulum, LXMF, and LXST in one beautiful and feature-rich application. This project is separate from the original <a class="reference external" href="https://github.com/liamcottle/reticulum-meshchat">Reticulum MeshChat</a> project, and is not affiliated with the original project, but is a much more up-to-date, comprehensive and well-maintained fork.</p>
<a class="reference external image-reference" href="https://git.quad4.io/RNS-Things/MeshChatX"><img alt="_images/meshchatx.webp" class="align-center" src="_images/meshchatx.webp" />
</a>
<p>Features include full LXST support, custom voicemail, phonebook, contact sharing, and ringtone support, multi-identity handling, modern UI/UX, offline documentation, expanded tools, page archiving, integrated maps, telemetry and improved application security.</p>
</section>
<section id="meshchat">
<h3>MeshChat<a class="headerlink" href="#meshchat" title="Link to this heading"></a></h3>
<p>The <a class="reference external" href="https://github.com/liamcottle/reticulum-meshchat">Reticulum MeshChat</a> application
is a user-friendly LXMF client for Linux, macOS and Windows, that also includes a Nomad Network
page browser and other interesting functionality.</p>
<a class="reference external image-reference" href="https://github.com/liamcottle/reticulum-meshchat"><img alt="_images/meshchat_1.webp" class="align-center" src="_images/meshchat_1.webp" />
</a>
<p>Reticulum MeshChat is of course also compatible with Sideband and Nomad Network, or
any other LXMF client.</p>
</section>
<section id="columba">
<h3>Columba<a class="headerlink" href="#columba" title="Link to this heading"></a></h3>
<p><a class="reference external" href="https://github.com/torlando-tech/columba/">Columba</a> is a simple and familiar LXMF
messaging app Android, built with a native Android interface and Material Design 3.</p>
<a class="reference external image-reference" href="https://github.com/torlando-tech/columba/"><img alt="_images/columba.webp" class="align-center" src="_images/columba.webp" style="width: 25%;" />
</a>
<p>While still in early and very active development, it is of course also compatible
with all other LXMF clients, and allows you to message seamlessly with anyone else
using LXMF.</p>
</section>
<section id="reticulum-relay-chat">
<h3>Reticulum Relay Chat<a class="headerlink" href="#reticulum-relay-chat" title="Link to this heading"></a></h3>
<p><a class="reference external" href="https://rrc.kc1awv.net/">Reticulum Relay Chat</a> is a live chat system built on top of the Reticulum Network Stack. It exists to let people talk to each other in real time over Reticulum without dragging in message databases, synchronization engines, or architectural commitments they did not ask for.</p>
@@ -417,8 +398,8 @@ using LXMF.</p>
<p>LXMF is efficient enough that it can deliver messages over extremely low-bandwidth systems such as packet radio or LoRa. Encrypted LXMF messages can also be encoded as QR-codes or text-based URIs, allowing completely analog paper message transport.</p>
<p>Using Propagation Nodes, LXMF also offer a way to store and forward messages to users or endpoints that are not directly reachable at the time of message emission.</p>
</section>
<section id="id17">
<h3>LXST<a class="headerlink" href="#id17" title="Link to this heading"></a></h3>
<section id="id16">
<h3>LXST<a class="headerlink" href="#id16" title="Link to this heading"></a></h3>
<p><a class="reference external" href="https://github.com/markqvist/lxst">LXST</a> is a simple and flexible real-time streaming format and delivery protocol that allows a wide variety of applications, while using as little bandwidth as possible. It is built on top of Reticulum and offers zero-conf stream routing, end-to-end encryption and Forward Secrecy, and can be transported over any kind of medium that Reticulum supports. It currently powers real-time voice and telephony applications over Reticulum.</p>
</section>
<section id="rrc">
@@ -502,8 +483,6 @@ using LXMF.</p>
<li><a class="reference internal" href="#retipedia">Retipedia</a></li>
<li><a class="reference internal" href="#sideband">Sideband</a></li>
<li><a class="reference internal" href="#meshchatx">MeshChatX</a></li>
<li><a class="reference internal" href="#meshchat">MeshChat</a></li>
<li><a class="reference internal" href="#columba">Columba</a></li>
<li><a class="reference internal" href="#reticulum-relay-chat">Reticulum Relay Chat</a></li>
<li><a class="reference internal" href="#retibbs">RetiBBS</a></li>
<li><a class="reference internal" href="#rbrowser">RBrowser</a></li>
@@ -518,7 +497,7 @@ using LXMF.</p>
</li>
<li><a class="reference internal" href="#protocols">Protocols</a><ul>
<li><a class="reference internal" href="#lxmf">LXMF</a></li>
<li><a class="reference internal" href="#id17">LXST</a></li>
<li><a class="reference internal" href="#id16">LXST</a></li>
<li><a class="reference internal" href="#rrc">RRC</a></li>
</ul>
</li>
@@ -534,7 +513,7 @@ using LXMF.</p>
</aside>
</div>
</div><script src="_static/documentation_options.js?v=928db92d"></script>
</div><script src="_static/documentation_options.js?v=e917149c"></script>
<script src="_static/doctools.js?v=9bcbadda"></script>
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
+5 -4
View File
@@ -7,7 +7,7 @@
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
<title>Support Reticulum - Reticulum Network Stack 1.2.4 documentation</title>
<title>Support Reticulum - Reticulum Network Stack 1.2.9 documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
@@ -180,7 +180,7 @@
</label>
</div>
<div class="header-center">
<a href="index.html"><div class="brand">Reticulum Network Stack 1.2.4 documentation</div></a>
<a href="index.html"><div class="brand">Reticulum Network Stack 1.2.9 documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -204,7 +204,7 @@
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
</div>
<span class="sidebar-brand-text">Reticulum Network Stack 1.2.4 documentation</span>
<span class="sidebar-brand-text">Reticulum Network Stack 1.2.9 documentation</span>
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
@@ -222,6 +222,7 @@
<li class="toctree-l1"><a class="reference internal" href="hardware.html">Communications Hardware</a></li>
<li class="toctree-l1"><a class="reference internal" href="interfaces.html">Configuring Interfaces</a></li>
<li class="toctree-l1"><a class="reference internal" href="networks.html">Building Networks</a></li>
<li class="toctree-l1"><a class="reference internal" href="distributed.html">Distributed Development</a></li>
<li class="toctree-l1"><a class="reference internal" href="git.html">Git Over Reticulum</a></li>
<li class="toctree-l1 current current-page"><a class="current reference internal" href="#">Support Reticulum</a></li>
<li class="toctree-l1"><a class="reference internal" href="examples.html">Code Examples</a></li>
@@ -382,7 +383,7 @@ circumstances, so we rely on old-fashioned human feedback.</p>
</aside>
</div>
</div><script src="_static/documentation_options.js?v=928db92d"></script>
</div><script src="_static/documentation_options.js?v=e917149c"></script>
<script src="_static/doctools.js?v=9bcbadda"></script>
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
+5 -4
View File
@@ -7,7 +7,7 @@
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
<title>Understanding Reticulum - Reticulum Network Stack 1.2.4 documentation</title>
<title>Understanding Reticulum - Reticulum Network Stack 1.2.9 documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
@@ -180,7 +180,7 @@
</label>
</div>
<div class="header-center">
<a href="index.html"><div class="brand">Reticulum Network Stack 1.2.4 documentation</div></a>
<a href="index.html"><div class="brand">Reticulum Network Stack 1.2.9 documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -204,7 +204,7 @@
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
</div>
<span class="sidebar-brand-text">Reticulum Network Stack 1.2.4 documentation</span>
<span class="sidebar-brand-text">Reticulum Network Stack 1.2.9 documentation</span>
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
@@ -222,6 +222,7 @@
<li class="toctree-l1"><a class="reference internal" href="hardware.html">Communications Hardware</a></li>
<li class="toctree-l1"><a class="reference internal" href="interfaces.html">Configuring Interfaces</a></li>
<li class="toctree-l1"><a class="reference internal" href="networks.html">Building Networks</a></li>
<li class="toctree-l1"><a class="reference internal" href="distributed.html">Distributed Development</a></li>
<li class="toctree-l1"><a class="reference internal" href="git.html">Git Over Reticulum</a></li>
<li class="toctree-l1"><a class="reference internal" href="support.html">Support Reticulum</a></li>
<li class="toctree-l1"><a class="reference internal" href="examples.html">Code Examples</a></li>
@@ -1337,7 +1338,7 @@ those risks are acceptable to you.</p>
</aside>
</div>
</div><script src="_static/documentation_options.js?v=928db92d"></script>
</div><script src="_static/documentation_options.js?v=e917149c"></script>
<script src="_static/doctools.js?v=9bcbadda"></script>
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
+5 -4
View File
@@ -7,7 +7,7 @@
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
<title>Using Reticulum on Your System - Reticulum Network Stack 1.2.4 documentation</title>
<title>Using Reticulum on Your System - Reticulum Network Stack 1.2.9 documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
@@ -180,7 +180,7 @@
</label>
</div>
<div class="header-center">
<a href="index.html"><div class="brand">Reticulum Network Stack 1.2.4 documentation</div></a>
<a href="index.html"><div class="brand">Reticulum Network Stack 1.2.9 documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -204,7 +204,7 @@
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
</div>
<span class="sidebar-brand-text">Reticulum Network Stack 1.2.4 documentation</span>
<span class="sidebar-brand-text">Reticulum Network Stack 1.2.9 documentation</span>
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
@@ -222,6 +222,7 @@
<li class="toctree-l1"><a class="reference internal" href="hardware.html">Communications Hardware</a></li>
<li class="toctree-l1"><a class="reference internal" href="interfaces.html">Configuring Interfaces</a></li>
<li class="toctree-l1"><a class="reference internal" href="networks.html">Building Networks</a></li>
<li class="toctree-l1"><a class="reference internal" href="distributed.html">Distributed Development</a></li>
<li class="toctree-l1"><a class="reference internal" href="git.html">Git Over Reticulum</a></li>
<li class="toctree-l1"><a class="reference internal" href="support.html">Support Reticulum</a></li>
<li class="toctree-l1"><a class="reference internal" href="examples.html">Code Examples</a></li>
@@ -1635,7 +1636,7 @@ systemctl --user enable rnsd.service
</aside>
</div>
</div><script src="_static/documentation_options.js?v=928db92d"></script>
</div><script src="_static/documentation_options.js?v=e917149c"></script>
<script src="_static/doctools.js?v=9bcbadda"></script>
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
+5 -4
View File
@@ -7,7 +7,7 @@
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
<title>What is Reticulum? - Reticulum Network Stack 1.2.4 documentation</title>
<title>What is Reticulum? - Reticulum Network Stack 1.2.9 documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
@@ -180,7 +180,7 @@
</label>
</div>
<div class="header-center">
<a href="index.html"><div class="brand">Reticulum Network Stack 1.2.4 documentation</div></a>
<a href="index.html"><div class="brand">Reticulum Network Stack 1.2.9 documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -204,7 +204,7 @@
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
</div>
<span class="sidebar-brand-text">Reticulum Network Stack 1.2.4 documentation</span>
<span class="sidebar-brand-text">Reticulum Network Stack 1.2.9 documentation</span>
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
@@ -222,6 +222,7 @@
<li class="toctree-l1"><a class="reference internal" href="hardware.html">Communications Hardware</a></li>
<li class="toctree-l1"><a class="reference internal" href="interfaces.html">Configuring Interfaces</a></li>
<li class="toctree-l1"><a class="reference internal" href="networks.html">Building Networks</a></li>
<li class="toctree-l1"><a class="reference internal" href="distributed.html">Distributed Development</a></li>
<li class="toctree-l1"><a class="reference internal" href="git.html">Git Over Reticulum</a></li>
<li class="toctree-l1"><a class="reference internal" href="support.html">Support Reticulum</a></li>
<li class="toctree-l1"><a class="reference internal" href="examples.html">Code Examples</a></li>
@@ -504,7 +505,7 @@ network, and vice versa.</p>
</aside>
</div>
</div><script src="_static/documentation_options.js?v=928db92d"></script>
</div><script src="_static/documentation_options.js?v=e917149c"></script>
<script src="_static/doctools.js?v=9bcbadda"></script>
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
+5 -4
View File
@@ -7,7 +7,7 @@
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
<title>Zen of Reticulum - Reticulum Network Stack 1.2.4 documentation</title>
<title>Zen of Reticulum - Reticulum Network Stack 1.2.9 documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
@@ -180,7 +180,7 @@
</label>
</div>
<div class="header-center">
<a href="index.html"><div class="brand">Reticulum Network Stack 1.2.4 documentation</div></a>
<a href="index.html"><div class="brand">Reticulum Network Stack 1.2.9 documentation</div></a>
</div>
<div class="header-right">
<div class="theme-toggle-container theme-toggle-header">
@@ -204,7 +204,7 @@
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
</div>
<span class="sidebar-brand-text">Reticulum Network Stack 1.2.4 documentation</span>
<span class="sidebar-brand-text">Reticulum Network Stack 1.2.9 documentation</span>
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
@@ -222,6 +222,7 @@
<li class="toctree-l1"><a class="reference internal" href="hardware.html">Communications Hardware</a></li>
<li class="toctree-l1"><a class="reference internal" href="interfaces.html">Configuring Interfaces</a></li>
<li class="toctree-l1"><a class="reference internal" href="networks.html">Building Networks</a></li>
<li class="toctree-l1"><a class="reference internal" href="distributed.html">Distributed Development</a></li>
<li class="toctree-l1"><a class="reference internal" href="git.html">Git Over Reticulum</a></li>
<li class="toctree-l1"><a class="reference internal" href="support.html">Support Reticulum</a></li>
<li class="toctree-l1"><a class="reference internal" href="examples.html">Code Examples</a></li>
@@ -676,7 +677,7 @@ Imagine a messaging app. You write it once. It works on a laptop connected to fi
</aside>
</div>
</div><script src="_static/documentation_options.js?v=928db92d"></script>
</div><script src="_static/documentation_options.js?v=e917149c"></script>
<script src="_static/doctools.js?v=9bcbadda"></script>
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
+130
View File
@@ -0,0 +1,130 @@
# Distributed Development
This chapter of the manual provides the conceptual basis for understanding *why* `rngit` exists, what it aims to achieve, and the kinds of spaces it seeks to reestablish. For the practical details of operating the system, refer to the [Git Over Reticulum](git.md#git-main) chapter.
## The Original Architecture
When Torvalds created Git in 2005, he designed a tool that reflected a specific philosophy of collaboration. Every copy of a repository would be a complete, sovereign instance. There was no central server, no single point of failure, no gatekeeper. Developers would be able to work independently, exchange patches directly, and maintain their own branches indefinitely. This concept was - and is - both beautiful and revolutionary. Its execution is peer-to-peer not as a marketing term, but in the most foundational sense: As fundamental, structural reality.
Such a design emerged from necessity. The Linux kernel development process operated across geographical boundaries, time zones, and organizational affiliations. Contributors did not “log in” to a shared server to do their work; they maintained their own trees, and the flow of code between these trees was negotiated through patches, reviews, and merge decisions. The architecture of Git mirrored the social architecture of the community: Autonomous, competent, and fundamentally distributed in its technical operation.
*The result of that work is, in the most direct sense, what makes it possible for you to read this today.*
Theres something very important to take note of here: With Git, developers could collaborate effectively and perfectly well without any central server being present, without platform-mediated visibility into each others work, and without a centralized authority validating their contributions. They needed *only* a protocol for exchanging differences and a mechanism for verification of authorship. Everything else - social organization, quality control, release management - was handled by careful *human judgment* operating on top of the technical substrate.
What Git provided was not a development environment, but a **language for versioning**. It specified how to represent history, how to compute differences, how to merge divergent branches. It did not specify who could participate, how they should communicate, or what workflows they should follow. These were left to the competence and discretion of the creators using the system.
## The Platform Interregnum
What followed represents a very familiar pattern: Tools designed to distribute power were re-centralized by platforms that offered convenience in exchange for control. GitHub, GitLab, and similar services reintroduced the centralization that Git had eliminated architecturally. The activity feed replaced durable artifacts with ephemeral notifications. The social graph and open interaction became as important as the code itself, if not more.
This re-centralization was not technical, as such. It was **ontological**. When every developer pushes to the same server, when every merge is in theory controllable by a platform, when every issue is tracked in a database controlled by a corporation, the nature of collaboration changes. The platform, and its social dynamics, becomes the ground of reality. The platform mediates not just the technical exchange of information and the programmatics, but the social recognition and codices of contribution, the future archival prospects of the work, and the very identity of the project itself.
The consequences extend beyond individual inconvenience. Centralized platforms create single points of failure for entire ecosystem. When a platform changes its terms of service, suspends accounts, removes repositories or ceases operation, entire project histories and community relationships can be disrupted or destroyed. The extractive economics of platform capitalism mean that value created by open-source communities is captured by corporations, while communities remain dependent on infrastructure they do not control. And the surveillance inherent in platform operation means that every action - every commit, every comment, every page view - is logged, analyzed, and potentially monetized or weaponized.
More insidiously, platforms have completely reshaped the culture of development itself. They have created what we could call the **Teahouse Developer**: A participant who treats engineering projects as social venues for opinion-sharing rather than sites of disciplined and careful production. These personages have no actual stakes in the projects they act as leeches upon, and only a very base consciousness of the damage they are incurring in order to feed their attention and external validation dependencies.
When platforms optimize for engagement, when growth is the only metric, when every user with an opinion must have their voice heard, when a random social process is elevated to higher importance than results, the signal-to-noise ratio collapses catastrophically. Competent engineers find themselves drowning in feedback from the incompetent, managing the emotional needs and dysregulations of drive-by commentators rather than solving technical problems.
The platform model is predicated on **unsaturable expansion**. Like almost any industrial system, it cannot function without growth. It pursues no particular aims; it is growth for the sake of growing. There is no saturation point, no concept of “enough”. Every barrier to entry must be put down to the very lowest common denominator, every voice must be amplified, every interaction must be converted into content that feeds the machine. This is fundamentally incompatible with the nature of social beings itself. It is also incompatible with serious engineering, which requires focus, discernment, and the right of people who know better to say “no”.
## Restoration
The `rngit` system represents a return to Gits original architectural principles, fortified with cryptographic networking capabilities that were not available in 2005. The `rngit` system *is* Git - but running over Reticulum. Welcome back to a world where your work is your own, but where everyone can still reach you - if you want them to.
Just as Git eliminated the need for a central version control server, `rngit` eliminates the need for a central hosting platform, “servers” or any kinds of middle-men between the people actually doing the work. By operating over Reticulum, it eliminates the visibility of development activity to platform operators, network observers, state actors and other malicious third-parties.
In this model, the repository node is a **sovereign entity**. It is reachable from anywhere in the Reticulum network but owned, operated, and controlled by the developer or community that runs it. It is an actual home for creative output, not an extraction mechanism to which dues are paid. The node operator decides who may contribute, what standards must be met, and which voices are worth listening to. This is not exclusion; it is **discernment**. It is the necessary exercise of judgment that separates engineering from theatrics.
I did not create this in a fit of nostalgia. I created it because it is a necessary response to the failures of the centralized model. Gits technical architecture was - and *is* - correct. It was the social and economic superstructure built atop it that introduced fragility, exploitation, and environments toxic to actual creativity. By returning to first principles - distributed version control on distributed infrastructure - we recover not just a technical capability, but a mode of collaboration that respects the autonomy of individual developers and the sovereignty of actual communities.
## Protocols Over Platforms
The distinction between platforms and protocols is fundamental to understanding the architecture of sovereignty in networked systems. A platform is a service you access; a protocol is a grammar you speak; actions you live. A platform requires permission to enter, a protocol requires only *comprehension* to employ. A platform can change its rules, suspend your account, or cease operation entirely, a protocol persists as long as there are participants who *understand* and *use* it. A protocol is an *idea*, a platform is a machine that turns its users into products.
Platforms operate on a client-server model that inherently creates power asymmetry. Even when platforms are built atop open-source software, the operational instance remains a black box of corporate control. You *may* be able to download *some* of your data, but you cannot download the connections to the people that are the true value-base of the platform, or take them with you if you want to leave.
Protocols, by contrast, are agreements. They specify how systems should communicate, but not who may communicate or on what terms. Email is a protocol; Gmail is a platform. HTTP is a protocol; Facebook is a platform. Git is a protocol; GitHub is a platform. The protocol persists regardless of any particular implementations success or failure.
The power of protocols lies in their **permissionlessness**. Anyone can implement a protocol without approval. Anyone can extend it, fork it, or use it for purposes unforeseen by its creators. This creates resilience: protocols cannot be easily censored, monopolized, or shut down because they exist as shared understanding rather than centralized infrastructure.
Reticulum is a protocol in this strict sense. It specifies how packets should be formatted, how paths should be discovered, how encryption should be applied. The `rngit` system extends this protocol approach to development workflows. It is not an external platform that hosts your repositories; it is a protocol for exchanging repository data, release artifacts, and work documents over Reticulums encrypted transport. But with a few commands and an old computer, it creates your own infrastructure for hosting repositories, or sharing them with who you choose. *That* is how tools should function, in case we had forgotten.
Unlike platforms, which extract value by creating dependency, there is no entity that can grant or deny you the privilege of running `rngit`. Your Reticulum identity is not endowed by any platform; it is generated locally and certified by its own cryptographic properties. Your repositories are hosted on nodes you control or nodes operated by communities you trust. Your relationships with other developers are peer-to-peer connections established through cryptographic addressing, not social graph connections managed by recommendation algorithms.
On a platform, exit means abandonment: you lose your history, your relationships, your visibility. With protocols, exit is just migration. When you change your infrastructure, your identity and your work travel with you. There are no middlemen between you and your collaborators. If push comes to shove, you can write your entire lifes work and connections to an SD card, swim across the lake, and set up camp on the other side.
## Sovereignty Through Infrastructure
The concept of sovereignty - supreme authority within a territory - has traditionally been applied to nation-states. But in an age where creative work is conducted through digital infrastructure, sovereignty is essential for individuals and communities. **Creative sovereignty** means having supreme authority over the artifacts you produce, the processes by which you produce them, and the terms under which they are distributed. It means not merely legal ownership of copyright, but operational control of the infrastructure that mediates creation, collaboration, and dissemination.
Centralized development platforms strip away most layers of sovereignty. When you host code on a corporate platform, you retain *some* legal ownership of copyright, but you surrender complete operational control. The platform decides what content is acceptable, who can access it, and how it is presented. They can delete your repository, suspend your account, or change the visibility of your work without consent. In reality, legal ownership becomes meaningless as operational control is ceded.
Running your own `rngit` node restores this sovereignty. You control the hardware, the network configuration, the backup strategies, and the access permissions. You decide what constitutes acceptable use, who may contribute, and how contributions are evaluated. Taking this responsibility on yourself is an assertion that your creative work is not a product to be harvested by platform economics, but an autonomous activity to be conducted on your own terms.
This sovereignty and responsibility extends to the entry barriers you establish. The `rngit` system allows you to configure access controls that filter participants based on cryptographic identity and demonstrated competence. If, for example, someone cannot navigate a command line, or use Reticulum to submit a patch, they most likely lack the required competence to modify your code. In a world that apparently labels this as “exclusion”, I would simply refer to it as a minimally acceptable level of quality control.
Such a stance protects projects from the noise that so often overwhelms and completely dilutes platform-based development, where every user with an opinion believes themselves entitled to attention and access to the decision process.
## Artifact-Centered Workflows
Contemporary platform-based development has shifted focus from durable artifacts to ephemeral *activity*. It does not matter what constitutes this activity, as long as its there. The primary interface is not the repository itself, not the produced artifacts, but the activity feed: *Notifications* of commits, comments, pull requests, and social interactions. Work is measured by velocity, throughput, and the constant stream of updates. This activity-centric model creates constant urgency, discourages discernment, encourages reactive rather than reflective work patterns, and produces vast quantities of ephemeral and useless communication that obscures actual project state and productivity.
The `rngit` system enables a return to **artifact-centered workflows**, where the focus is on durable, attributable, versioned outputs rather than the stream of notifications surrounding them. The fundamental unit of work is the commit - signed, immutable records of change. The fundamental unit of production is the signed artifact - a self-verifying package of functionality. The fundamental unit of discussion is the work document - a structured, threaded conversation attached to repositories.
Artifacts can persist independently of any platforms continued operation. A commit signed with your Reticulum identity is attributable to you regardless of where it is stored. A release signed with your private key is verifiable as authentic regardless of which network it traverses, and can be verified offline on any system running Reticulum. The work exists as **cryptographic fact**, distributed over the planet, not as database entries in a corporate cloud.
Such a shift has real psychological consequences. When work is measured in artifacts rather than activity, the pace changes. There is no need for constant visibility, no pressure to perform busyness. Developers can work deeply, reflectively, and submit complete solutions rather than incremental updates designed to maintain presence in an activity feed. The work becomes **substantial**, in the physical sense of the word, rather than performative.
## Composable Primitives
The `rngit` system is not a monolithic application prescribing a specific workflow; it is a collection of **composable primitives** that can be arranged to support diverse creative processes. Understanding these primitives as separate, orthogonal capabilities enables users to construct workflows suited to their specific needs and to recombine these primitives in ways unforeseen by the systems designers.
The core primitives include:
* **Repository Hosting**: Bare Git repositories served over Reticulum links, accessible via standard Git commands through the `rns://` URL scheme.
* **Identity-Based Access Control**: Fine-grained permissions managed through cryptographically verifiable identity hashes, configurable at the group, repository, or document level.
* **Release Distribution**: Cryptographically signed release artifacts with embedded provenance information, verifiable offline and distributable through any Reticulum or physical path.
* **Work Document Tracking**: Structured, threaded work management attached to repositories, with precise permission controls, and the ability to contain updates or discussions.
* **Forking and Mirroring**: Automated replication of repositories from any accessible Git URL, with metadata tracking upstream relationships for synchronization.
* **Nomad Network Integration**: Page node functionality for browsing repository contents, commit history, and release information through the Nomad Network protocol.
These primitives can be composed into workflows ranging from single-developer projects to complex multi-organizational collaborations. A solo developer might use only repository hosting and release distribution. A research group might add work document tracking for structured peer review. A software distribution network might combine mirroring with cryptographic release verification to create resilient update channels.
The entire system is incredibly light-weight, and can host hundreds of repositories on a Raspberry Pi.
Composability is essential because **creative work is diverse**. Software development, academic research, technical writing, hardware design, music production and data analysis all have different requirements for collaboration, review, and distribution. A platform prescribes a single workflow and forces all users to conform. A protocol provides primitives and allows users to construct workflows appropriate to their domain.
With `rngit`, you can re-build the system into anything you can imagine. Everything can be modified, extended and hooked into. Adding functionality or automation is never further away than a shell script, a cron-job, or a Python modification of the source.
## Distribution Without Intermediaries
Creating software is only part of the work. Then comes actually getting it to the people needing to use it. Centralized platforms handle distribution through their own infrastructure: Content delivery networks, central package registries, and download servers accessed through platform-controlled interfaces. This convenience masks a fundamental dependency: Your ability to distribute depends on the platforms continued operation, their policies regarding your content, and their technical infrastructures reach.
The `rngit` release system enables distribution strategies **decoupled from any single infrastructure provider**. Releases are cryptographically signed using Ed25519 signatures and packaged in signed release manifests (`.rsm` files). These manifests contain embedded signatures for each artifact. The manifest provides full verifiability of all release information, and contains embedded release artifact lists, per-file `.rsg` signatures, origin information, and the creators Reticulum Identity. It can also be used to fetch verified updates of the software package over the network, and can always be verified completely offline.
Because releases are self-verifying, they can traverse any network or physical path that Reticulum can establish. A release can travel over LoRa radio, be carried on USB drives through areas without internet connectivity, disseminated over a mirror network, or be distributed through store-and-forward mechanisms on intermittent infrastructure. Recipients can verify authenticity regardless of how they obtained the files. This is particularly valuable in low-connectivity environments where Reticulum may be the only available communication channel.
The `rngit release` command provides tools for creating, publishing, fetching, and verifying releases. When fetching a release using an `.rsm` manifest, the system validates the manifest signature against the required Reticulum Identity, extracts the origin node and repository path, connects to the origin over Reticulum, retrieves the latest release manifest, and verifies each downloaded artifact against the signatures embedded in the manifest. If any verification fails, the fetch aborts, preventing installation of corrupted or tampered files.
This cryptographic verification replaces the trust model of platform distribution. Instead of trusting that a platform has not been compromised, users verify that artifacts match the signatures created by the developers identity. It doesnt matter *how* they obtained the artifacts, they can **always** be verified. This security model shifts from **institutional trust** (just believe in the goodness of the platform) to **cryptographic proof** (verify the signatures).
## Long Archive
Software development is often conceived as an activity of the present only: Solving todays problems, meeting current deadlines, responding to immediate feedback. But the artifacts produced - code, documentation, releases - have lifespans extending *far* beyond their creation. They may be used for decades, studied by future developers, depended upon by systems not yet imagined, or preserved as historical records of technological development.
The `rngit` system is designed with this **extended timeframe** in mind, supporting the creation of archives that are durable, portable, and intelligible across generational timescales. Git repositories are always internally complete; they contain full history and can be migrated to new infrastructure without loss of information. Everything that `rngit` adds on top of this is stored in normal files in standard formats right next to the Git repository folders, not an esoteric database-cluster two thousand kilometers away. Because releases are cryptographically signed, they remain verifiable as authentic regardless of when or where they are retrieved. Because the system operates over Reticulum, it can function over communication mediums that may outlast the internet as we know it.
This long-term perspective influences technical decisions. The use of well-established cryptographic primitives ensures that signatures will remain verifiable for centuries. The use of standard formats ensures that repositories will remain readable by future tools. The protocol-based architecture ensures that the system can evolve without losing compatibility with existing data.
For critical infrastructure, this archival durability is not optional; it is essential. Communication systems, cryptographic libraries, and safety-critical code must remain available and verifiable for the lifespans of the systems that depend on them. The `rngit` system provides the tools to create such archives: distributed across multiple nodes, cryptographically verified, and independent of any corporate or governmental infrastructure, which as history has shown repeatedly, does *not* persist.
## Start Of The Road
Distributed development and production over Reticulum is a *different mode of existence* for creative work. It restores the autonomy originally created by Git. It provides local sovereignty over production infrastructure, composability of workflow, and durability of artifact. It lets you filter participation through competence and cryptography rather than incentives of platform operators, raising the quality and enjoyment of work, and protecting the focus of real engineering and creative expression.
This is not a system for everyone, and that is the point. It requires investment - in understanding Reticulum, in configuring infrastructure, in establishing workflows. It requires accepting responsibility for your own tools rather than delegating them to platform operators. It requires the discipline to maintain your own node, manage your own backups, and nurture your own community.
But for those who make this investment, the returns are substantial. You gain **immunity from platform failure**; your work persists regardless of corporate decisions or service outages. You gain **shelter from surveillance**; your development activity is visible only to those that *you* choose to involve. You gain **control over process**; you decide how work is conducted, reviewed, and released, unmediated by terms of service, algorithmic feeds and thousands of uninformed and irrelevant opinions.
Most importantly, though, you regain the **dignity of craft**. Development becomes an activity conducted among peers, equals among equals, mediated by skill and cryptographic proof rather than corporate permission, producing artifacts that stand as independent testimony to competence, functionality or beauty rather than as content feeding engagement metrics. The *work* becomes the point. The artifacts become durable. And the network becomes *one* of the tools you wield in this endeavor.
+767 -57
View File
File diff suppressed because it is too large Load Diff
+52 -5
View File
@@ -82,8 +82,6 @@ to participate in the development of Reticulum itself.
* [Retipedia](software.md#retipedia)
* [Sideband](software.md#sideband)
* [MeshChatX](software.md#meshchatx)
* [MeshChat](software.md#meshchat)
* [Columba](software.md#columba)
* [Reticulum Relay Chat](software.md#reticulum-relay-chat)
* [RetiBBS](software.md#retibbs)
* [RBrowser](software.md#rbrowser)
@@ -96,7 +94,7 @@ to participate in the development of Reticulum itself.
* [RNMon](software.md#rnmon)
* [Protocols](software.md#protocols)
* [LXMF](software.md#lxmf)
* [LXST](software.md#id17)
* [LXST](software.md#id16)
* [RRC](software.md#rrc)
* [Interface Modules & Connectivity Resources](software.md#interface-modules-connectivity-resources)
* [Using Reticulum on Your System](using.md)
@@ -183,6 +181,7 @@ to participate in the development of Reticulum itself.
* [Interface Modes](interfaces.md#interfaces-modes)
* [Announce Rate Control](interfaces.md#announce-rate-control)
* [New Destination Rate Limiting](interfaces.md#new-destination-rate-limiting)
* [Path Request Burst Control](interfaces.md#path-request-burst-control)
* [Building Networks](networks.md)
* [Concepts & Overview](networks.md#concepts-overview)
* [Introductory Considerations](networks.md#introductory-considerations)
@@ -190,14 +189,62 @@ to participate in the development of Reticulum itself.
* [Transport Nodes and Instances](networks.md#transport-nodes-and-instances)
* [Trustless Networking](networks.md#trustless-networking)
* [Heterogeneous Connectivity](networks.md#heterogeneous-connectivity)
* [Distributed Development](distributed.md)
* [The Original Architecture](distributed.md#the-original-architecture)
* [The Platform Interregnum](distributed.md#the-platform-interregnum)
* [Restoration](distributed.md#restoration)
* [Protocols Over Platforms](distributed.md#protocols-over-platforms)
* [Sovereignty Through Infrastructure](distributed.md#sovereignty-through-infrastructure)
* [Artifact-Centered Workflows](distributed.md#artifact-centered-workflows)
* [Composable Primitives](distributed.md#composable-primitives)
* [Distribution Without Intermediaries](distributed.md#distribution-without-intermediaries)
* [Long Archive](distributed.md#long-archive)
* [Start Of The Road](distributed.md#start-of-the-road)
* [Git Over Reticulum](git.md)
* [The rngit Utility](git.md#the-rngit-utility)
* [Repository Creation & Management](git.md#repository-creation-management)
* [Creating Empty Repositories](git.md#creating-empty-repositories)
* [Forking Repositories](git.md#forking-repositories)
* [Mirroring Repositories](git.md#mirroring-repositories)
* [Automatic Mirror Synchronization](git.md#automatic-mirror-synchronization)
* [Manual Synchronization](git.md#manual-synchronization)
* [Git Configuration Parameters](git.md#git-configuration-parameters)
* [Repository Structure](git.md#repository-structure)
* [Configuration](git.md#configuration)
* [Permissions](git.md#permissions)
* [Permission Types](git.md#permission-types)
* [Permission Hierarchy](git.md#permission-hierarchy)
* [Configuration Methods](git.md#configuration-methods)
* [Work Document Permissions](git.md#work-document-permissions)
* [Creator Permissions](git.md#creator-permissions)
* [Permission Examples](git.md#permission-examples)
* [Permission Short Forms](git.md#permission-short-forms)
* [Permission Configuration Locations](git.md#permission-configuration-locations)
* [Remote Permission Management](git.md#remote-permission-management)
* [Managing Group Permissions](git.md#managing-group-permissions)
* [Managing Repository Permissions](git.md#managing-repository-permissions)
* [Permission Validation](git.md#permission-validation)
* [Identity & Destination Aliases](git.md#identity-destination-aliases)
* [Serving Pages Over Nomad Network](git.md#serving-pages-over-nomad-network)
* [Formatting & Syntax Highlighting](git.md#formatting-syntax-highlighting)
* [Customizing Templates](git.md#customizing-templates)
* [Enabling the Git Page Node](git.md#enabling-the-git-page-node)
* [Accessing Repository Pages](git.md#accessing-repository-pages)
* [Formatting & Syntax Highlighting](git.md#formatting-syntax-highlighting)
* [Customizing Templates](git.md#customizing-templates)
* [Repository Statistics](git.md#repository-statistics)
* [Configuration Example](git.md#configuration-example)
* [Verified Releases](git.md#verified-releases)
* [Obtaining Verified Releases](git.md#obtaining-verified-releases)
* [Creating Signed Releases](git.md#creating-signed-releases)
* [Release Management](git.md#release-management)
* [The Release Workflow](git.md#the-release-workflow)
* [Release Storage & Structure](git.md#release-storage-structure)
* [Command-Line Interaction](git.md#command-line-interaction)
* [Work Documents](git.md#work-documents)
* [Working With Work Documents](git.md#working-with-work-documents)
* [Proposing Work Documents](git.md#proposing-work-documents)
* [State Management](git.md#state-management)
* [Managing Work Document Permissions](git.md#managing-work-document-permissions)
* [Cryptographic Attribution](git.md#cryptographic-attribution)
* [Support Reticulum](support.md)
* [Donations](support.md#donations)
* [Provide Feedback](support.md#provide-feedback)
+61 -6
View File
@@ -1295,11 +1295,12 @@ a large amount of bogus destinations, and then disconnect, these destination wil
never make it into path tables and waste network bandwidth on retransmitted
announces.
**Its important to note** that the ingress control works at the level of *individual
#### NOTE
Its important to remember that the ingress control works at the level of *individual
sub-interfaces*. As an example, this means that one client on a [TCP Server Interface](#interfaces-tcps)
cannot disrupt processing of incoming announces for other connected clients on the same
[TCP Server Interface](#interfaces-tcps). All other clients on the same interface will still have new announces
processed without interruption.
[TCP Server Interface](#interfaces-tcps). All other clients on the same interface
will still have new announces processed without interruption.
By default, Reticulum will handle this automatically, and ingress announce
control will be enabled on interface where it is sensible to do so. It should
@@ -1307,8 +1308,7 @@ generally not be neccessary to modify the ingress control configuration,
but all the parameters are exposed for configuration if needed.
> * The `ingress_control` option tells Reticulum whether or not
> to enable announce ingress control on the interface. Defaults to
> `True`.
> to enable ingress control on the interface. Defaults to `True`.
> <br/>
> * The `ic_new_time` option configures how long (in seconds) an
> interface is considered newly spawned. Defaults to `2*60*60` seconds. This
@@ -1343,4 +1343,59 @@ but all the parameters are exposed for configuration if needed.
> * The `ic_held_release_interval` option sets how much time (in seconds)
> must pass between releasing each held announce from the queue. Defaults
> to `30` seconds.
> <br/>
> <br/>
All of the above settings can be configured both as instance-wide defaults
under the `[reticulum]` section of the configuration file, or on a per-
interface basis under the relevant interface configuration section.
## Path Request Burst Control
In addition the announce controls for newly created destination, Reticulum will also
monitor incoming path request activity, and enforce burst controls if per-client rates
exceed configured limits. Once path request burst control is activated on an
interface, path requests will no longer be propagated further on the network.
As with announce burst control, this happens on a per sub-interface basis. One
client connecting to a public gateway will not be able to disrupt path request
processing for other clients.
#### WARNING
Applications that send large amounts of unnecessary path requests will very
quickly get rate limited by transport nodes, and the entire system they are
running on will not be able to resolve any paths on the network, until the
burst subsides and hold period expires. **Do not** write applications like
this. Only request paths for destinations you need to communicate with.
By default, Reticulum will handle this automatically, and ingress path request
control will be enabled on interface where it is sensible to do so. It should
generally not be neccessary to modify the ingress control configuration,
but all the parameters are exposed for configuration if needed.
> * The `ingress_control` option tells Reticulum whether or not
> to enable ingress control on the interface. Defaults to `True`.
> <br/>
> * The `ic_new_time` option configures how long (in seconds) an
> interface is considered newly spawned. Defaults to `2*60*60` seconds. This
> option is useful on publicly accessible interfaces that spawn new
> sub-interfaces when a new client connects.
> <br/>
> * The `ic_pr_burst_freq_new` option sets the maximum path request
> ingress frequency for newly spawned interfaces. Defaults to `3`
> path requests per second.
> <br/>
> * The `ic_pr_burst_freq` option sets the maximum path request
> ingress frequency for other interfaces. Defaults to `8` path requests
> per second.
> <br/>
> > *If an interface exceeds its burst frequency, incoming path requests
> > from that system will not traverse the network further.*
> * The `egress_control` option enables hard-limiting path request egress
> control per-interface. Defaults to `False`
> <br/>
> * The `ec_pr_freq` option sets the hard limit for outbound path requests
> per second on a given interface.
> <br/>
All of the above settings can be configured both as instance-wide defaults
under the `[reticulum]` section of the configuration file, or on a per-
interface basis under the relevant interface configuration section.
+1 -19
View File
@@ -72,28 +72,10 @@ plugin system for expandability.
### MeshChatX
A [Reticulum MeshChat fork from the future](https://git.quad4.io/RNS-Things/MeshChatX), with the goal of providing everything you need for Reticulum, LXMF, and LXST in one beautiful and feature-rich application. This project is separate from the original Reticulum MeshChat project, and is not affiliated with the original project.
A [Reticulum MeshChat fork from the future](https://git.quad4.io/RNS-Things/MeshChatX), with the goal of providing everything you need for Reticulum, LXMF, and LXST in one beautiful and feature-rich application. This project is separate from the original [Reticulum MeshChat](https://github.com/liamcottle/reticulum-meshchat) project, and is not affiliated with the original project, but is a much more up-to-date, comprehensive and well-maintained fork.
Features include full LXST support, custom voicemail, phonebook, contact sharing, and ringtone support, multi-identity handling, modern UI/UX, offline documentation, expanded tools, page archiving, integrated maps, telemetry and improved application security.
### MeshChat
The [Reticulum MeshChat](https://github.com/liamcottle/reticulum-meshchat) application
is a user-friendly LXMF client for Linux, macOS and Windows, that also includes a Nomad Network
page browser and other interesting functionality.
Reticulum MeshChat is of course also compatible with Sideband and Nomad Network, or
any other LXMF client.
### Columba
[Columba](https://github.com/torlando-tech/columba/) is a simple and familiar LXMF
messaging app Android, built with a native Android interface and Material Design 3.
While still in early and very active development, it is of course also compatible
with all other LXMF clients, and allows you to message seamlessly with anyone else
using LXMF.
### Reticulum Relay Chat
[Reticulum Relay Chat](https://rrc.kc1awv.net/) is a live chat system built on top of the Reticulum Network Stack. It exists to let people talk to each other in real time over Reticulum without dragging in message databases, synchronization engines, or architectural commitments they did not ask for.
+148
View File
@@ -0,0 +1,148 @@
.. _distributed-development:
***********************
Distributed Development
***********************
This chapter of the manual provides the conceptual basis for understanding *why* ``rngit`` exists, what it aims to achieve, and the kinds of spaces it seeks to reestablish. For the practical details of operating the system, refer to the :ref:`Git Over Reticulum<git-main>` chapter.
The Original Architecture
=========================
When Torvalds created Git in 2005, he designed a tool that reflected a specific philosophy of collaboration. Every copy of a repository would be a complete, sovereign instance. There was no central server, no single point of failure, no gatekeeper. Developers would be able to work independently, exchange patches directly, and maintain their own branches indefinitely. This concept was - and is - both beautiful and revolutionary. It's execution is peer-to-peer not as a marketing term, but in the most foundational sense: As fundamental, structural reality.
Such a design emerged from necessity. The Linux kernel development process operated across geographical boundaries, time zones, and organizational affiliations. Contributors did not "log in" to a shared server to do their work; they maintained their own trees, and the flow of code between these trees was negotiated through patches, reviews, and merge decisions. The architecture of Git mirrored the social architecture of the community: Autonomous, competent, and fundamentally distributed in its technical operation.
*The result of that work is, in the most direct sense, what makes it possible for you to read this today.*
There's something very important to take note of here: With Git, developers could collaborate effectively and perfectly well without any central server being present, without platform-mediated visibility into each other's work, and without a centralized authority validating their contributions. They needed *only* a protocol for exchanging differences and a mechanism for verification of authorship. Everything else - social organization, quality control, release management - was handled by careful *human judgment* operating on top of the technical substrate.
What Git provided was not a development environment, but a **language for versioning**. It specified how to represent history, how to compute differences, how to merge divergent branches. It did not specify who could participate, how they should communicate, or what workflows they should follow. These were left to the competence and discretion of the creators using the system.
The Platform Interregnum
========================
What followed represents a very familiar pattern: Tools designed to distribute power were re-centralized by platforms that offered convenience in exchange for control. GitHub, GitLab, and similar services reintroduced the centralization that Git had eliminated architecturally. The activity feed replaced durable artifacts with ephemeral notifications. The social graph and open interaction became as important as the code itself, if not more.
This re-centralization was not technical, as such. It was **ontological**. When every developer pushes to the same server, when every merge is in theory controllable by a platform, when every issue is tracked in a database controlled by a corporation, the nature of collaboration changes. The platform, and its social dynamics, becomes the ground of reality. The platform mediates not just the technical exchange of information and the programmatics, but the social recognition and codices of contribution, the future archival prospects of the work, and the very identity of the project itself.
The consequences extend beyond individual inconvenience. Centralized platforms create single points of failure for entire ecosystem. When a platform changes its terms of service, suspends accounts, removes repositories or ceases operation, entire project histories and community relationships can be disrupted or destroyed. The extractive economics of platform capitalism mean that value created by open-source communities is captured by corporations, while communities remain dependent on infrastructure they do not control. And the surveillance inherent in platform operation means that every action - every commit, every comment, every page view - is logged, analyzed, and potentially monetized or weaponized.
More insidiously, platforms have completely reshaped the culture of development itself. They have created what we could call the **Teahouse Developer**: A participant who treats engineering projects as social venues for opinion-sharing rather than sites of disciplined and careful production. These personages have no actual stakes in the projects they act as leeches upon, and only a very base consciousness of the damage they are incurring in order to feed their attention and external validation dependencies.
When platforms optimize for engagement, when growth is the only metric, when every user with an opinion must have their voice heard, when a random social process is elevated to higher importance than results, the signal-to-noise ratio collapses catastrophically. Competent engineers find themselves drowning in feedback from the incompetent, managing the emotional needs and dysregulations of drive-by commentators rather than solving technical problems.
The platform model is predicated on **unsaturable expansion**. Like almost any industrial system, it cannot function without growth. It pursues no particular aims; it is growth for the sake of growing. There is no saturation point, no concept of "enough". Every barrier to entry must be put down to the very lowest common denominator, every voice must be amplified, every interaction must be converted into content that feeds the machine. This is fundamentally incompatible with the nature of social beings itself. It is also incompatible with serious engineering, which requires focus, discernment, and the right of people who know better to say "no".
Restoration
===========
The ``rngit`` system represents a return to Git's original architectural principles, fortified with cryptographic networking capabilities that were not available in 2005. The ``rngit`` system *is* Git - but running over Reticulum. Welcome back to a world where your work is your own, but where everyone can still reach you - if you want them to.
Just as Git eliminated the need for a central version control server, ``rngit`` eliminates the need for a central hosting platform, "servers" or any kinds of middle-men between the people actually doing the work. By operating over Reticulum, it eliminates the visibility of development activity to platform operators, network observers, state actors and other malicious third-parties.
In this model, the repository node is a **sovereign entity**. It is reachable from anywhere in the Reticulum network but owned, operated, and controlled by the developer or community that runs it. It is an actual home for creative output, not an extraction mechanism to which dues are paid. The node operator decides who may contribute, what standards must be met, and which voices are worth listening to. This is not exclusion; it is **discernment**. It is the necessary exercise of judgment that separates engineering from theatrics.
I did not create this in a fit of nostalgia. I created it because it is a necessary response to the failures of the centralized model. Git's technical architecture was - and *is* - correct. It was the social and economic superstructure built atop it that introduced fragility, exploitation, and environments toxic to actual creativity. By returning to first principles - distributed version control on distributed infrastructure - we recover not just a technical capability, but a mode of collaboration that respects the autonomy of individual developers and the sovereignty of actual communities.
Protocols Over Platforms
========================
The distinction between platforms and protocols is fundamental to understanding the architecture of sovereignty in networked systems. A platform is a service you access; a protocol is a grammar you speak; actions you live. A platform requires permission to enter, a protocol requires only *comprehension* to employ. A platform can change its rules, suspend your account, or cease operation entirely, a protocol persists as long as there are participants who *understand* and *use* it. A protocol is an *idea*, a platform is a machine that turns its users into products.
Platforms operate on a client-server model that inherently creates power asymmetry. Even when platforms are built atop open-source software, the operational instance remains a black box of corporate control. You *may* be able to download *some* of your data, but you cannot download the connections to the people that are the true value-base of the platform, or take them with you if you want to leave.
Protocols, by contrast, are agreements. They specify how systems should communicate, but not who may communicate or on what terms. Email is a protocol; Gmail is a platform. HTTP is a protocol; Facebook is a platform. Git is a protocol; GitHub is a platform. The protocol persists regardless of any particular implementation's success or failure.
The power of protocols lies in their **permissionlessness**. Anyone can implement a protocol without approval. Anyone can extend it, fork it, or use it for purposes unforeseen by its creators. This creates resilience: protocols cannot be easily censored, monopolized, or shut down because they exist as shared understanding rather than centralized infrastructure.
Reticulum is a protocol in this strict sense. It specifies how packets should be formatted, how paths should be discovered, how encryption should be applied. The ``rngit`` system extends this protocol approach to development workflows. It is not an external platform that hosts your repositories; it is a protocol for exchanging repository data, release artifacts, and work documents over Reticulum's encrypted transport. But with a few commands and an old computer, it creates your own infrastructure for hosting repositories, or sharing them with who you choose. *That* is how tools should function, in case we had forgotten.
Unlike platforms, which extract value by creating dependency, there is no entity that can grant or deny you the privilege of running ``rngit``. Your Reticulum identity is not endowed by any platform; it is generated locally and certified by its own cryptographic properties. Your repositories are hosted on nodes you control or nodes operated by communities you trust. Your relationships with other developers are peer-to-peer connections established through cryptographic addressing, not social graph connections managed by recommendation algorithms.
On a platform, exit means abandonment: you lose your history, your relationships, your visibility. With protocols, exit is just migration. When you change your infrastructure, your identity and your work travel with you. There are no middlemen between you and your collaborators. If push comes to shove, you can write your entire life's work and connections to an SD card, swim across the lake, and set up camp on the other side.
Sovereignty Through Infrastructure
==================================
The concept of sovereignty - supreme authority within a territory - has traditionally been applied to nation-states. But in an age where creative work is conducted through digital infrastructure, sovereignty is essential for individuals and communities. **Creative sovereignty** means having supreme authority over the artifacts you produce, the processes by which you produce them, and the terms under which they are distributed. It means not merely legal ownership of copyright, but operational control of the infrastructure that mediates creation, collaboration, and dissemination.
Centralized development platforms strip away most layers of sovereignty. When you host code on a corporate platform, you retain *some* legal ownership of copyright, but you surrender complete operational control. The platform decides what content is acceptable, who can access it, and how it is presented. They can delete your repository, suspend your account, or change the visibility of your work without consent. In reality, legal ownership becomes meaningless as operational control is ceded.
Running your own ``rngit`` node restores this sovereignty. You control the hardware, the network configuration, the backup strategies, and the access permissions. You decide what constitutes acceptable use, who may contribute, and how contributions are evaluated. Taking this responsibility on yourself is an assertion that your creative work is not a product to be harvested by platform economics, but an autonomous activity to be conducted on your own terms.
This sovereignty and responsibility extends to the entry barriers you establish. The ``rngit`` system allows you to configure access controls that filter participants based on cryptographic identity and demonstrated competence. If, for example, someone cannot navigate a command line, or use Reticulum to submit a patch, they most likely lack the required competence to modify your code. In a world that apparently labels this as "exclusion", I would simply refer to it as a minimally acceptable level of quality control.
Such a stance protects projects from the noise that so often overwhelms and completely dilutes platform-based development, where every user with an opinion believes themselves entitled to attention and access to the decision process.
Artifact-Centered Workflows
===========================
Contemporary platform-based development has shifted focus from durable artifacts to ephemeral *activity*. It does not matter what constitutes this activity, as long as it's there. The primary interface is not the repository itself, not the produced artifacts, but the activity feed: *Notifications* of commits, comments, pull requests, and social interactions. Work is measured by velocity, throughput, and the constant stream of updates. This activity-centric model creates constant urgency, discourages discernment, encourages reactive rather than reflective work patterns, and produces vast quantities of ephemeral and useless communication that obscures actual project state and productivity.
The ``rngit`` system enables a return to **artifact-centered workflows**, where the focus is on durable, attributable, versioned outputs rather than the stream of notifications surrounding them. The fundamental unit of work is the commit - signed, immutable records of change. The fundamental unit of production is the signed artifact - a self-verifying package of functionality. The fundamental unit of discussion is the work document - a structured, threaded conversation attached to repositories.
Artifacts can persist independently of any platform's continued operation. A commit signed with your Reticulum identity is attributable to you regardless of where it is stored. A release signed with your private key is verifiable as authentic regardless of which network it traverses, and can be verified offline on any system running Reticulum. The work exists as **cryptographic fact**, distributed over the planet, not as database entries in a corporate cloud.
Such a shift has real psychological consequences. When work is measured in artifacts rather than activity, the pace changes. There is no need for constant visibility, no pressure to perform busyness. Developers can work deeply, reflectively, and submit complete solutions rather than incremental updates designed to maintain presence in an activity feed. The work becomes **substantial**, in the physical sense of the word, rather than performative.
Composable Primitives
=====================
The ``rngit`` system is not a monolithic application prescribing a specific workflow; it is a collection of **composable primitives** that can be arranged to support diverse creative processes. Understanding these primitives as separate, orthogonal capabilities enables users to construct workflows suited to their specific needs and to recombine these primitives in ways unforeseen by the system's designers.
The core primitives include:
* **Repository Hosting**: Bare Git repositories served over Reticulum links, accessible via standard Git commands through the ``rns://`` URL scheme.
* **Identity-Based Access Control**: Fine-grained permissions managed through cryptographically verifiable identity hashes, configurable at the group, repository, or document level.
* **Release Distribution**: Cryptographically signed release artifacts with embedded provenance information, verifiable offline and distributable through any Reticulum or physical path.
* **Work Document Tracking**: Structured, threaded work management attached to repositories, with precise permission controls, and the ability to contain updates or discussions.
* **Forking and Mirroring**: Automated replication of repositories from any accessible Git URL, with metadata tracking upstream relationships for synchronization.
* **Nomad Network Integration**: Page node functionality for browsing repository contents, commit history, and release information through the Nomad Network protocol.
These primitives can be composed into workflows ranging from single-developer projects to complex multi-organizational collaborations. A solo developer might use only repository hosting and release distribution. A research group might add work document tracking for structured peer review. A software distribution network might combine mirroring with cryptographic release verification to create resilient update channels.
The entire system is incredibly light-weight, and can host hundreds of repositories on a Raspberry Pi.
Composability is essential because **creative work is diverse**. Software development, academic research, technical writing, hardware design, music production and data analysis all have different requirements for collaboration, review, and distribution. A platform prescribes a single workflow and forces all users to conform. A protocol provides primitives and allows users to construct workflows appropriate to their domain.
With ``rngit``, you can re-build the system into anything you can imagine. Everything can be modified, extended and hooked into. Adding functionality or automation is never further away than a shell script, a cron-job, or a Python modification of the source.
Distribution Without Intermediaries
===================================
Creating software is only part of the work. Then comes actually getting it to the people needing to use it. Centralized platforms handle distribution through their own infrastructure: Content delivery networks, central package registries, and download servers accessed through platform-controlled interfaces. This convenience masks a fundamental dependency: Your ability to distribute depends on the platform's continued operation, their policies regarding your content, and their technical infrastructure's reach.
The ``rngit`` release system enables distribution strategies **decoupled from any single infrastructure provider**. Releases are cryptographically signed using Ed25519 signatures and packaged in signed release manifests (``.rsm`` files). These manifests contain embedded signatures for each artifact. The manifest provides full verifiability of all release information, and contains embedded release artifact lists, per-file ``.rsg`` signatures, origin information, and the creator's Reticulum Identity. It can also be used to fetch verified updates of the software package over the network, and can always be verified completely offline.
Because releases are self-verifying, they can traverse any network or physical path that Reticulum can establish. A release can travel over LoRa radio, be carried on USB drives through areas without internet connectivity, disseminated over a mirror network, or be distributed through store-and-forward mechanisms on intermittent infrastructure. Recipients can verify authenticity regardless of how they obtained the files. This is particularly valuable in low-connectivity environments where Reticulum may be the only available communication channel.
The ``rngit release`` command provides tools for creating, publishing, fetching, and verifying releases. When fetching a release using an ``.rsm`` manifest, the system validates the manifest signature against the required Reticulum Identity, extracts the origin node and repository path, connects to the origin over Reticulum, retrieves the latest release manifest, and verifies each downloaded artifact against the signatures embedded in the manifest. If any verification fails, the fetch aborts, preventing installation of corrupted or tampered files.
This cryptographic verification replaces the trust model of platform distribution. Instead of trusting that a platform has not been compromised, users verify that artifacts match the signatures created by the developer's identity. It doesn't matter *how* they obtained the artifacts, they can **always** be verified. This security model shifts from **institutional trust** (just believe in the goodness of the platform) to **cryptographic proof** (verify the signatures).
Long Archive
============
Software development is often conceived as an activity of the present only: Solving today's problems, meeting current deadlines, responding to immediate feedback. But the artifacts produced - code, documentation, releases - have lifespans extending *far* beyond their creation. They may be used for decades, studied by future developers, depended upon by systems not yet imagined, or preserved as historical records of technological development.
The ``rngit`` system is designed with this **extended timeframe** in mind, supporting the creation of archives that are durable, portable, and intelligible across generational timescales. Git repositories are always internally complete; they contain full history and can be migrated to new infrastructure without loss of information. Everything that ``rngit`` adds on top of this is stored in normal files in standard formats right next to the Git repository folders, not an esoteric database-cluster two thousand kilometers away. Because releases are cryptographically signed, they remain verifiable as authentic regardless of when or where they are retrieved. Because the system operates over Reticulum, it can function over communication mediums that may outlast the internet as we know it.
This long-term perspective influences technical decisions. The use of well-established cryptographic primitives ensures that signatures will remain verifiable for centuries. The use of standard formats ensures that repositories will remain readable by future tools. The protocol-based architecture ensures that the system can evolve without losing compatibility with existing data.
For critical infrastructure, this archival durability is not optional; it is essential. Communication systems, cryptographic libraries, and safety-critical code must remain available and verifiable for the lifespans of the systems that depend on them. The ``rngit`` system provides the tools to create such archives: distributed across multiple nodes, cryptographically verified, and independent of any corporate or governmental infrastructure, which as history has shown repeatedly, does *not* persist.
Start Of The Road
=================
Distributed development and production over Reticulum is a *different mode of existence* for creative work. It restores the autonomy originally created by Git. It provides local sovereignty over production infrastructure, composability of workflow, and durability of artifact. It lets you filter participation through competence and cryptography rather than incentives of platform operators, raising the quality and enjoyment of work, and protecting the focus of real engineering and creative expression.
This is not a system for everyone, and that is the point. It requires investment - in understanding Reticulum, in configuring infrastructure, in establishing workflows. It requires accepting responsibility for your own tools rather than delegating them to platform operators. It requires the discipline to maintain your own node, manage your own backups, and nurture your own community.
But for those who make this investment, the returns are substantial. You gain **immunity from platform failure**; your work persists regardless of corporate decisions or service outages. You gain **shelter from surveillance**; your development activity is visible only to those that *you* choose to involve. You gain **control over process**; you decide how work is conducted, reviewed, and released, unmediated by terms of service, algorithmic feeds and thousands of uninformed and irrelevant opinions.
Most importantly, though, you regain the **dignity of craft**. Development becomes an activity conducted among peers, equals among equals, mediated by skill and cryptographic proof rather than corporate permission, producing artifacts that stand as independent testimony to competence, functionality or beauty rather than as content feeding engagement metrics. The *work* becomes the point. The artifacts become durable. And the network becomes *one* of the tools you wield in this endeavor.
+824 -57
View File
File diff suppressed because it is too large Load Diff
+1
View File
@@ -27,6 +27,7 @@ to participate in the development of Reticulum itself.
hardware
interfaces
networks
distributed
git
support
examples
+63 -7
View File
@@ -1372,11 +1372,12 @@ a large amount of bogus destinations, and then disconnect, these destination wil
never make it into path tables and waste network bandwidth on retransmitted
announces.
**It's important to note** that the ingress control works at the level of *individual
sub-interfaces*. As an example, this means that one client on a :ref:`TCP Server Interface<interfaces-tcps>`
cannot disrupt processing of incoming announces for other connected clients on the same
:ref:`TCP Server Interface<interfaces-tcps>`. All other clients on the same interface will still have new announces
processed without interruption.
.. note::
It's important to remember that the ingress control works at the level of *individual
sub-interfaces*. As an example, this means that one client on a :ref:`TCP Server Interface<interfaces-tcps>`
cannot disrupt processing of incoming announces for other connected clients on the same
:ref:`TCP Server Interface<interfaces-tcps>`. All other clients on the same interface
will still have new announces processed without interruption.
By default, Reticulum will handle this automatically, and ingress announce
control will be enabled on interface where it is sensible to do so. It should
@@ -1384,8 +1385,7 @@ generally not be neccessary to modify the ingress control configuration,
but all the parameters are exposed for configuration if needed.
* | The ``ingress_control`` option tells Reticulum whether or not
to enable announce ingress control on the interface. Defaults to
``True``.
to enable ingress control on the interface. Defaults to ``True``.
* | The ``ic_new_time`` option configures how long (in seconds) an
interface is considered newly spawned. Defaults to ``2*60*60`` seconds. This
@@ -1422,3 +1422,59 @@ but all the parameters are exposed for configuration if needed.
must pass between releasing each held announce from the queue. Defaults
to ``30`` seconds.
All of the above settings can be configured both as instance-wide defaults
under the ``[reticulum]`` section of the configuration file, or on a per-
interface basis under the relevant interface configuration section.
Path Request Burst Control
==========================
In addition the announce controls for newly created destination, Reticulum will also
monitor incoming path request activity, and enforce burst controls if per-client rates
exceed configured limits. Once path request burst control is activated on an
interface, path requests will no longer be propagated further on the network.
As with announce burst control, this happens on a per sub-interface basis. One
client connecting to a public gateway will not be able to disrupt path request
processing for other clients.
.. warning::
Applications that send large amounts of unnecessary path requests will very
quickly get rate limited by transport nodes, and the entire system they are
running on will not be able to resolve any paths on the network, until the
burst subsides and hold period expires. **Do not** write applications like
this. Only request paths for destinations you need to communicate with.
By default, Reticulum will handle this automatically, and ingress path request
control will be enabled on interface where it is sensible to do so. It should
generally not be neccessary to modify the ingress control configuration,
but all the parameters are exposed for configuration if needed.
* | The ``ingress_control`` option tells Reticulum whether or not
to enable ingress control on the interface. Defaults to ``True``.
* | The ``ic_new_time`` option configures how long (in seconds) an
interface is considered newly spawned. Defaults to ``2*60*60`` seconds. This
option is useful on publicly accessible interfaces that spawn new
sub-interfaces when a new client connects.
* | The ``ic_pr_burst_freq_new`` option sets the maximum path request
ingress frequency for newly spawned interfaces. Defaults to ``3``
path requests per second.
* | The ``ic_pr_burst_freq`` option sets the maximum path request
ingress frequency for other interfaces. Defaults to ``8`` path requests
per second.
*If an interface exceeds its burst frequency, incoming path requests
from that system will not traverse the network further.*
* | The ``egress_control`` option enables hard-limiting path request egress
control per-interface. Defaults to ``False``
* | The ``ec_pr_freq`` option sets the hard limit for outbound path requests
per second on a given interface.
All of the above settings can be configured both as instance-wide defaults
under the ``[reticulum]`` section of the configuration file, or on a per-
interface basis under the relevant interface configuration section.
+1 -51
View File
@@ -110,7 +110,7 @@ plugin system for expandability.
MeshChatX
^^^^^^^^
A `Reticulum MeshChat fork from the future <https://git.quad4.io/RNS-Things/MeshChatX>`_, with the goal of providing everything you need for Reticulum, LXMF, and LXST in one beautiful and feature-rich application. This project is separate from the original Reticulum MeshChat project, and is not affiliated with the original project.
A `Reticulum MeshChat fork from the future <https://git.quad4.io/RNS-Things/MeshChatX>`_, with the goal of providing everything you need for Reticulum, LXMF, and LXST in one beautiful and feature-rich application. This project is separate from the original `Reticulum MeshChat <https://github.com/liamcottle/reticulum-meshchat>`_ project, and is not affiliated with the original project, but is a much more up-to-date, comprehensive and well-maintained fork.
.. only:: html
@@ -127,56 +127,6 @@ A `Reticulum MeshChat fork from the future <https://git.quad4.io/RNS-Things/Mesh
Features include full LXST support, custom voicemail, phonebook, contact sharing, and ringtone support, multi-identity handling, modern UI/UX, offline documentation, expanded tools, page archiving, integrated maps, telemetry and improved application security.
.. raw:: latex
\newpage
MeshChat
^^^^^^^^
The `Reticulum MeshChat <https://github.com/liamcottle/reticulum-meshchat>`_ application
is a user-friendly LXMF client for Linux, macOS and Windows, that also includes a Nomad Network
page browser and other interesting functionality.
.. only:: html
.. image:: screenshots/meshchat_1.webp
:align: center
:target: https://github.com/liamcottle/reticulum-meshchat
.. only:: latex
.. image:: screenshots/meshchat_1.png
:align: center
:target: https://github.com/liamcottle/reticulum-meshchat
Reticulum MeshChat is of course also compatible with Sideband and Nomad Network, or
any other LXMF client.
Columba
^^^^^^^
`Columba <https://github.com/torlando-tech/columba/>`_ is a simple and familiar LXMF
messaging app Android, built with a native Android interface and Material Design 3.
.. only:: html
.. image:: screenshots/columba.webp
:align: center
:width: 25%
:target: https://github.com/torlando-tech/columba/
.. only:: latex
.. image:: screenshots/columba.png
:align: center
:width: 25%
:target: https://github.com/torlando-tech/columba/
While still in early and very active development, it is of course also compatible
with all other LXMF clients, and allows you to message seamlessly with anyone else
using LXMF.
.. raw:: latex
\newpage
+4
View File
@@ -31,6 +31,10 @@ Donations are gratefully accepted via the following channels:
Are certain features in the development roadmap are important to you or your
organisation? Make them a reality quickly by sponsoring their implementation.
.. raw:: latex
\newpage
Provide Feedback
================
Feedback on the usage, functioning and potential dysfunctioning of any and
+4
View File
@@ -13,6 +13,10 @@ exec(open("RNS/_version.py", "r").read())
with open("README.md", "r") as fh:
long_description = fh.read()
if "--getversion" in sys.argv:
print(__version__, end="")
exit(0)
if pure_python:
pkg_name = "rnspure"
requirements = []