Compare commits

..

221 Commits

Author SHA1 Message Date
Mark Qvist fcb61e3ebf Updated documentation and manual 2022-07-08 12:14:03 +02:00
Mark Qvist eae788957a Updated version 2022-07-08 12:01:13 +02:00
Mark Qvist 045a9d8451 Fixed a race condition in link establishment flow 2022-07-08 11:14:35 +02:00
Mark Qvist da644d33ea Updated documentation 2022-07-08 00:32:03 +02:00
Mark Qvist e03fc38920 Updated documentation 2022-07-08 00:28:41 +02:00
Mark Qvist c36c0368ef Updated manual 2022-07-08 00:24:00 +02:00
Mark Qvist 3d979e2d65 Added Android compatibility to AES proxy class 2022-07-08 00:22:30 +02:00
Mark Qvist 5158613501 Fixed missing config section check 2022-07-08 00:21:48 +02:00
Mark Qvist b53185779a Updated documentation 2022-07-08 00:21:23 +02:00
Mark Qvist 5b63f84491 Updated readme 2022-07-05 00:45:58 +02:00
Mark Qvist fd2cc1231f Updated readme 2022-07-04 23:55:43 +02:00
Mark Qvist 76950ee3de Updated manual 2022-07-04 23:46:21 +02:00
markqvist 8565b2fdf5 Update README.md 2022-07-03 09:29:35 +02:00
markqvist 2a915eab2d Merge pull request #76 from joshuafuller/master
Fix some minor spelling errors
2022-07-03 09:28:40 +02:00
Joshua Fuller 36654c1414 Fix some minor spelling errors 2022-07-02 17:32:39 -05:00
Mark Qvist fdf0456cf0 Updated readme and docs 2022-07-02 18:41:57 +02:00
Mark Qvist 8cff18f8ce Improved cache handling 2022-07-02 15:15:47 +02:00
Mark Qvist 5e072affe4 Changed job timing 2022-07-02 13:34:17 +02:00
Mark Qvist fc4c7638a6 Added cache job scheduler 2022-07-02 13:24:07 +02:00
Mark Qvist 532f9ee665 Added cache cleaning 2022-07-02 13:12:54 +02:00
Mark Qvist 4a725de935 Improved rnx interactive mode 2022-07-02 10:38:35 +02:00
Mark Qvist 2335a71427 Fixed --no-auth option in rncp 2022-07-02 09:48:15 +02:00
Mark Qvist 3e70dd6134 Fixed --no-auth option in rncp 2022-07-02 09:33:05 +02:00
Mark Qvist 474521056b Updated packet sizes in docs 2022-07-02 08:45:57 +02:00
Mark Qvist d33154bfdb Cleanup 2022-07-02 08:45:40 +02:00
Mark Qvist 8f82a2b87f Updated documentation to reflect 128-bit address space 2022-07-01 23:34:02 +02:00
Mark Qvist 304610c682 Updated documentation to reflect 128-bit address space 2022-07-01 23:30:20 +02:00
Mark Qvist bc39a1acf1 Fixed static size index 2022-07-01 21:16:01 +02:00
Mark Qvist 20b7278f7b Updated documentation 2022-07-01 21:15:15 +02:00
Mark Qvist 1f66a9b0c0 Updated readme 2022-07-01 21:13:55 +02:00
Mark Qvist f464ecfcb5 Updated website 2022-07-01 17:31:07 +02:00
Mark Qvist 49fdeb9bc4 Updated readme 2022-07-01 17:30:51 +02:00
Mark Qvist 40560a31f2 Version updated 2022-07-01 10:27:31 +02:00
Mark Qvist f7d8a4b3b3 Updated tests 2022-06-30 20:37:51 +02:00
Mark Qvist c498bf5668 Updated examples 2022-06-30 20:07:48 +02:00
Mark Qvist 2e19304ebf Fixed static length calculation in proof destination generation 2022-06-30 19:33:35 +02:00
Mark Qvist 1cd7c85a52 Cleanup 2022-06-30 19:32:47 +02:00
Mark Qvist 171f43f4e3 Cleanup 2022-06-30 19:32:29 +02:00
Mark Qvist 09a1088437 Added description about Fernet modifications 2022-06-30 19:32:08 +02:00
Mark Qvist 6346bc54a8 Updated readme 2022-06-30 19:31:13 +02:00
Mark Qvist 40e25d8e40 Fixed static destination size 2022-06-30 19:12:44 +02:00
Mark Qvist e19438fdcc Added license headers 2022-06-30 19:10:51 +02:00
markqvist d85ea07b5e Update README.md 2022-06-30 14:07:58 +02:00
Mark Qvist 4dda0e8a5b Updated readme 2022-06-30 14:06:55 +02:00
Mark Qvist 5faf13d505 Expanded address space to 128 bits 2022-06-30 14:02:57 +02:00
Mark Qvist 2be1c7633d Updated readme 2022-06-27 20:17:23 +02:00
Mark Qvist 6ac2f437b9 Updated documentation 2022-06-22 23:26:08 +02:00
Mark Qvist 2fe9dec459 Updated documentation 2022-06-22 16:34:43 +02:00
Mark Qvist 8f8da080f5 Updated documentation 2022-06-22 16:20:01 +02:00
Mark Qvist 01a973db91 Updated documentation 2022-06-22 16:13:26 +02:00
Mark Qvist 1c4528dca1 Updated documentation 2022-06-22 16:10:54 +02:00
Mark Qvist a99031873d Updated documentation 2022-06-22 16:04:44 +02:00
Mark Qvist ab1186eaf7 Updated documentation 2022-06-22 15:48:45 +02:00
Mark Qvist 940c889440 Updated manual 2022-06-22 15:19:45 +02:00
Mark Qvist ac7c36029b Updated documentation 2022-06-22 15:19:18 +02:00
Mark Qvist c79811e040 Updated makefile 2022-06-22 10:12:05 +02:00
Mark Qvist 7545613c52 Updated documentation 2022-06-22 10:08:27 +02:00
Mark Qvist 7bd6da034a Updated readme 2022-06-22 10:00:43 +02:00
Mark Qvist 34f10d1196 Updated readme 2022-06-16 19:58:34 +02:00
Mark Qvist be84e8a731 Updated readme 2022-06-16 19:53:17 +02:00
Mark Qvist 7331bd2c09 Updated makefile 2022-06-14 13:45:48 +02:00
Mark Qvist 6bfd0bf4eb Resource profiling with yappi instead of cprofile 2022-06-14 13:44:12 +02:00
Mark Qvist 3013c10180 Updated readme 2022-06-13 17:28:03 +02:00
Mark Qvist 95a34dad4b Updated readme 2022-06-13 17:25:13 +02:00
Mark Qvist a3bc2ef38f Updated readme 2022-06-13 17:24:35 +02:00
Mark Qvist aa255d0713 Tuned I2PInterface socket timeouts 2022-06-13 15:45:53 +02:00
Mark Qvist 5a8152c589 Fixed I2PInterface status not being set on connectable interfaces 2022-06-12 21:34:54 +02:00
Mark Qvist 8a24dbae40 Added filter option to rnstatus utility 2022-06-12 19:08:47 +02:00
Mark Qvist 2f1329e581 Updated docs version 2022-06-12 18:57:08 +02:00
Mark Qvist 2166294a7a Optimised resource transfer speed on faster links 2022-06-12 18:56:49 +02:00
Mark Qvist 8042f5eaa1 Improved log output 2022-06-12 18:55:06 +02:00
Mark Qvist 1b1ab42aaa Updated readme 2022-06-12 13:28:16 +02:00
Mark Qvist ae8fcb88d8 Resource timeout tuning 2022-06-12 13:28:05 +02:00
Mark Qvist 98b232bc4c Updated link test 2022-06-12 11:58:54 +02:00
Mark Qvist d7a444556a Tuned TCP socket options 2022-06-12 11:50:09 +02:00
Mark Qvist 58eaceb48c Updated docs 2022-06-12 11:49:37 +02:00
Mark Qvist 3c81f93d4a Added link accept option to API 2022-06-12 11:49:24 +02:00
Mark Qvist 2685e043ea Fixed missing check for zero-length packets on IFAC-enabled interfaces. Fixes #65. 2022-06-11 18:52:33 +02:00
Mark Qvist 214ee9d771 Updated readme 2022-06-11 15:03:14 +02:00
Mark Qvist d39c1893e7 Cleanup 2022-06-11 14:11:58 +02:00
Mark Qvist 548cbd50d8 Improved I2PInterface error handling and stability 2022-06-11 13:52:56 +02:00
Mark Qvist 6b06875c42 Fixed potential undefined variable 2022-06-11 13:42:08 +02:00
Mark Qvist d7262c7cbe Fixed socket leak in I2PInterface 2022-06-11 11:27:01 +02:00
Mark Qvist d9a021465e Updated readme 2022-06-10 21:44:17 +02:00
Mark Qvist 8451bbe7e6 Tuned resource window 2022-06-10 18:17:48 +02:00
Mark Qvist 1ac7238347 Cleanup 2022-06-10 17:05:00 +02:00
Mark Qvist ea7762cbc0 Updated makefile 2022-06-10 16:37:02 +02:00
Mark Qvist c4a7d17b2f Updated tests 2022-06-10 16:36:30 +02:00
Mark Qvist c758c4d279 Updated readme 2022-06-10 13:14:16 +02:00
Mark Qvist d136eac32b Updated readme 2022-06-10 13:13:51 +02:00
Mark Qvist f74e6d12c9 Updated readme 2022-06-10 13:13:15 +02:00
Mark Qvist 6f68d6edc4 Updated readme 2022-06-10 13:12:07 +02:00
Mark Qvist 076d6b09c4 Updated makefile 2022-06-10 12:54:12 +02:00
Mark Qvist 8c484c786f Updated makefile 2022-06-10 12:50:48 +02:00
Mark Qvist 363d56d49d Enabled pure-python build 2022-06-10 12:46:20 +02:00
markqvist 2a581a9a9b Update README.md 2022-06-10 12:19:31 +02:00
Mark Qvist 2779852417 Updated readme 2022-06-10 12:15:49 +02:00
Mark Qvist e0f69344c2 Updated readme 2022-06-10 12:15:01 +02:00
Mark Qvist 469c9919cb Updated readme 2022-06-10 12:13:36 +02:00
Mark Qvist 6518370d79 Updated readme 2022-06-10 12:13:03 +02:00
Mark Qvist ffe61e701a Updated readme 2022-06-10 12:12:26 +02:00
Mark Qvist 7f65c767f0 Updated readme 2022-06-10 12:11:43 +02:00
Mark Qvist 157a54d4a4 Updated readme 2022-06-10 11:45:40 +02:00
Mark Qvist c8c0f77c81 Updated readme 2022-06-10 11:37:30 +02:00
Mark Qvist 4c3a82cf20 Updated readme 2022-06-10 11:36:32 +02:00
Mark Qvist 1ec83b535f Updated readme 2022-06-10 11:34:57 +02:00
Mark Qvist 31914a10aa Updated readme 2022-06-10 11:34:18 +02:00
Mark Qvist 6e369bf82f Updated readme 2022-06-10 11:33:54 +02:00
Mark Qvist 39059a365d Updated readme 2022-06-10 11:33:21 +02:00
Mark Qvist 0b2dba7977 Updated readme 2022-06-10 11:32:57 +02:00
Mark Qvist c6e2ba2cf3 Updated readme 2022-06-10 11:32:10 +02:00
Mark Qvist c5918395de Updated readme 2022-06-10 11:31:33 +02:00
Mark Qvist 861ac92c4c Updated readme 2022-06-10 11:29:39 +02:00
Mark Qvist 715e35d626 Updated readme 2022-06-10 11:28:59 +02:00
Mark Qvist a8ea7bcca6 Updated tests 2022-06-10 11:27:52 +02:00
Mark Qvist 534a8825eb Updated setup.py 2022-06-10 11:27:31 +02:00
Mark Qvist 89f3c0f649 Updated readme 2022-06-10 11:26:46 +02:00
Mark Qvist e4a82d5358 Updated link test 2022-06-09 21:49:13 +02:00
Mark Qvist 68cd79768b Added internal python-only AES-128-CBC implementation 2022-06-09 21:13:34 +02:00
Mark Qvist 701c624d0a Updated Identity tests 2022-06-09 21:12:26 +02:00
Mark Qvist ec90af750d Updated link tests 2022-06-09 19:54:20 +02:00
Mark Qvist 2c1b3a0e5b Optimised resource performance over varied network topologies 2022-06-09 19:29:33 +02:00
Mark Qvist 02968baa76 Added establishment cost property to Link 2022-06-09 19:28:31 +02:00
Mark Qvist 06fefebc08 Updated tests 2022-06-09 19:27:11 +02:00
Mark Qvist 513a82e363 Updated link test 2022-06-09 17:14:43 +02:00
Mark Qvist a4b80e7ddb Updated link test 2022-06-09 17:07:44 +02:00
Mark Qvist be6910e4e0 Work on Resource optimisation 2022-06-09 17:00:27 +02:00
Mark Qvist 0a8b755230 Transport optimisations 2022-06-09 16:54:47 +02:00
Mark Qvist d334613888 Removed delay 2022-06-09 16:48:31 +02:00
Mark Qvist 14bdcaf770 Added size print function 2022-06-09 14:46:36 +02:00
Mark Qvist 592c405067 Cleanup 2022-06-09 14:46:02 +02:00
Mark Qvist bb8012ad50 Updated test output 2022-06-09 14:45:30 +02:00
Mark Qvist 648e9a68b8 Added profiling info to LocalInterface 2022-06-09 14:45:00 +02:00
Mark Qvist 8c167b8f3d Updated tests 2022-06-09 13:32:32 +02:00
Mark Qvist bd933dc1df Updated gitignore 2022-06-09 13:30:19 +02:00
Mark Qvist 76f12b4854 Updated gitignore 2022-06-09 10:33:30 +02:00
Mark Qvist 30af212217 Added tests for Link 2022-06-09 10:33:13 +02:00
Mark Qvist 6c22ccc6d4 Updated makefile 2022-06-09 10:31:48 +02:00
Mark Qvist 26dae3830e Fixed unclosed socket in AutoInterface 2022-06-09 08:48:55 +02:00
Mark Qvist a776d59f03 Updated hashes tests 2022-06-08 23:32:56 +02:00
Mark Qvist 5b20caf759 Added tests for Identity 2022-06-08 23:28:55 +02:00
Mark Qvist a800ce43f3 Tests cleanup 2022-06-08 22:27:26 +02:00
Mark Qvist 7916b8e7f4 Automatic switch to internal backend on missing PyCA module 2022-06-08 21:25:46 +02:00
Mark Qvist 60e3c7348a Updated readme 2022-06-08 21:05:03 +02:00
Mark Qvist cc9970c83e Added tests for hashes 2022-06-08 21:04:29 +02:00
Mark Qvist c46b98f163 Added python-only fallback for SHA-256 and SHA-512 2022-06-08 21:03:58 +02:00
Mark Qvist 86061f9f47 Cleanup 2022-06-08 19:47:51 +02:00
Mark Qvist e0b795b4d0 Added internal python-only implementation of Ed25519 2022-06-08 19:47:09 +02:00
Mark Qvist 34efbc6100 Cleanup 2022-06-08 17:05:15 +02:00
Mark Qvist 94edc8eff3 Implemented proxies to pyca X25519 2022-06-08 17:03:40 +02:00
Mark Qvist e2aeb56c12 Renamed file 2022-06-08 15:54:48 +02:00
Mark Qvist 9a4325ce8e Constant time X25519 exchange 2022-06-08 15:52:37 +02:00
Mark Qvist 06fffe5a94 Use internal implementation for X25519 key exchanges 2022-06-08 13:36:23 +02:00
Mark Qvist 7a596882a8 Cleanup 2022-06-08 12:52:42 +02:00
Mark Qvist 76f86f782a Moved Destination Fernet to internal implementation 2022-06-08 12:37:24 +02:00
Mark Qvist 4bd5f05e0e Moved Link Fernet to internal implementation 2022-06-08 12:34:31 +02:00
Mark Qvist 5d3a0efc89 Moved Identity Fernet to internal implementation 2022-06-08 12:29:51 +02:00
Mark Qvist d1a461a2b3 Added multi-backend abstraction for AES-128 CBC primitive 2022-06-08 12:21:50 +02:00
Mark Qvist 0b1e7df31a Added internal Fernet implementation 2022-06-07 17:38:57 +02:00
Mark Qvist 301661c29e Set SHA-256 as default hash for HMAC 2022-06-07 17:33:08 +02:00
Mark Qvist b2b6708e8f Added python-only implementation of PKCS7 padding 2022-06-07 17:32:22 +02:00
Mark Qvist 19a033db96 Freed RNS from dependency on PyCA HMAC, HKDF and hashes 2022-06-07 15:48:23 +02:00
Mark Qvist 5bb510b589 Added internal python-only HKDF 2022-06-07 15:26:45 +02:00
Mark Qvist f1dcda82ac Added internal python-only HMAC implementation 2022-06-07 15:25:41 +02:00
Mark Qvist d24f3a490a Added internal abstraction to SHA-256 2022-06-07 15:21:19 +02:00
Mark Qvist 715a84c6f2 Moved hashing to native python3 hashlib 2022-06-07 12:51:41 +02:00
Mark Qvist 379e56b2ce Socket option check for OpenWRT compatibility 2022-06-07 12:40:50 +02:00
Mark Qvist c6df6293b2 Added hardware MTU parameter to interfaces 2022-05-29 15:43:50 +02:00
Mark Qvist d99d31097b Updated manual 2022-05-29 10:14:31 +02:00
Mark Qvist 54488cfeb5 Updated documentation 2022-05-29 10:13:25 +02:00
Mark Qvist d7e38d646e Updated readme 2022-05-29 09:48:53 +02:00
Mark Qvist b9057bee5f Updated readme 2022-05-29 09:48:31 +02:00
Mark Qvist 9bd64834ec Updated readme 2022-05-29 09:47:26 +02:00
Mark Qvist 9e20ba2dac Implemented I2PInterface recovery on I2P router restart 2022-05-28 02:24:01 +02:00
Mark Qvist 49ed335e19 Cleanup 2022-05-26 16:52:28 +02:00
Mark Qvist 85c71b0b7b Updated docs 2022-05-26 16:50:35 +02:00
Mark Qvist 33fac728f8 Improved link stale process and timeout calculations 2022-05-26 16:49:02 +02:00
Mark Qvist 49616a36cf Fixed I2P controller startup when event loop is not immediately ready 2022-05-26 09:54:56 +02:00
Mark Qvist 1e77f85cd4 Fixed rnx version output 2022-05-26 00:03:37 +02:00
Mark Qvist 9e316ab989 Fixed deprecated options in asyncio API for Python 3.10. Fixes #58. 2022-05-25 23:11:01 +02:00
Mark Qvist 94749e0dde Updated default configs 2022-05-25 23:10:05 +02:00
Mark Qvist a6dbc53209 Improved status display for I2P interfaces 2022-05-25 21:44:49 +02:00
Mark Qvist 3af5a8f3ed Improved I2P server tunnel error handling. Fixes #13. 2022-05-25 21:23:52 +02:00
Mark Qvist fb5172ff10 Improved I2P client tunnel error handling 2022-05-25 20:18:06 +02:00
Mark Qvist 24d6de8490 Updated docs 2022-05-25 15:51:20 +02:00
Mark Qvist d3ab0878e0 Improved I2P interface display in rnstatus 2022-05-25 15:50:54 +02:00
Mark Qvist 7848b7e396 Fixed invalid reference in rnx 2022-05-25 15:08:45 +02:00
Mark Qvist fc80dd2614 Improved rnstatus output 2022-05-25 14:21:04 +02:00
Mark Qvist e00a758b2a Updated readme 2022-05-24 21:00:46 +02:00
Mark Qvist d44ec745df Updated readme 2022-05-24 21:00:25 +02:00
Mark Qvist 7573ac1970 Updated readme 2022-05-24 20:58:58 +02:00
Mark Qvist 88390f0cbc Updated readme 2022-05-24 20:57:56 +02:00
Mark Qvist 3b8490ae9c Added rnx util to documentation 2022-05-24 20:47:45 +02:00
Mark Qvist 417ac9f8da Added rnx remote command utility 2022-05-24 20:14:43 +02:00
Mark Qvist fe5e74bc2b Improved rncp arguments 2022-05-24 20:13:54 +02:00
Mark Qvist 30f71857ae Added docstrings. Added request size to receipts. Fixed link stale time calculation on newly created links with no actual activity. 2022-05-24 20:13:11 +02:00
Mark Qvist c24233845e Implemented bandwidth cap for recursive path requests 2022-05-23 19:49:48 +02:00
Mark Qvist c0fbde5ad1 Added recursive path request loop avoidance 2022-05-23 18:14:45 +02:00
Mark Qvist 5da66402dd Fixed rncp output 2022-05-23 09:23:37 +02:00
Mark Qvist 3bf5694238 Fixed naming conflict in resource advertisements 2022-05-23 08:54:07 +02:00
Mark Qvist 9e6a5d5d91 Fix announce rate targets on I2PInterface peers 2022-05-23 00:28:06 +02:00
Mark Qvist cf3e47f469 Fixed interface mode inheritance 2022-05-23 00:06:26 +02:00
Mark Qvist f8db5a545d Fixed interface mode check 2022-05-23 00:00:14 +02:00
Mark Qvist a79f6e7efa Added rncp utility 2022-05-22 23:44:32 +02:00
Mark Qvist ac4606bcf7 Updated docs 2022-05-22 23:44:12 +02:00
Mark Qvist d1cb07356c Fixed missing recursive progress callback allocation in segmented resource transfer 2022-05-22 21:05:07 +02:00
Mark Qvist e811d54d0f Fixed bug in conditional resource acceptance callback 2022-05-22 19:09:44 +02:00
Mark Qvist 49c8ada478 Added standard identity storage folder 2022-05-22 19:09:16 +02:00
Mark Qvist 6ea7d78b31 Updated API reference 2022-05-22 19:08:32 +02:00
Mark Qvist 0ace84367b Improved link authentication callback 2022-05-22 19:08:03 +02:00
Mark Qvist e63e6821e0 Updated Destination docstrings 2022-05-22 17:11:30 +02:00
Mark Qvist 109132e09d Fixed expired AP and roaming paths not being removed at correct time. 2022-05-22 15:43:46 +02:00
Mark Qvist efd24ec134 Updated documentation 2022-05-22 15:18:09 +02:00
Mark Qvist eefa37f808 Updated documentation 2022-05-22 15:17:54 +02:00
Mark Qvist e4871f7667 Updated documentation 2022-05-22 15:17:25 +02:00
Mark Qvist 44ba5624bc Added gateway mode to rnstatus 2022-05-22 15:16:58 +02:00
Mark Qvist e9c5e3c189 Version bump 2022-05-22 14:29:29 +02:00
Mark Qvist f3ff71d9b8 Implemented unknown path discovery 2022-05-22 14:18:58 +02:00
Mark Qvist 81b92ffdc1 Added gateway interface mode 2022-05-22 11:14:33 +02:00
Mark Qvist 02bb9068cc Updated readme 2022-05-22 11:13:54 +02:00
Mark Qvist ecc9e84bc2 Fixed typo 2022-05-18 00:47:29 +02:00
123 changed files with 8403 additions and 1371 deletions
+5 -1
View File
@@ -7,4 +7,8 @@ RNS/Utilities/RNS
build
dist
docs/build
rns*.egg-info
rns*.egg-info
profile.data
tests/rnsconfig/storage
*.data
*.result
+6 -4
View File
@@ -120,14 +120,16 @@ def client(destination_hexhash, configpath, timeout=None):
# We need a binary representation of the destination
# hash that was entered on the command line
try:
if len(destination_hexhash) != 20:
dest_len = (RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2
if len(destination_hexhash) != dest_len:
raise ValueError(
"Destination length is invalid, must be 20 hexadecimal characters (10 bytes)"
"Destination length is invalid, must be {hex} hexadecimal characters ({byte} bytes).".format(hex=dest_len, byte=dest_len//2)
)
destination_hash = bytes.fromhex(destination_hexhash)
except:
RNS.log("Invalid destination entered. Check your input!\n")
except Exception as e:
RNS.log("Invalid destination entered. Check your input!")
RNS.log(str(e)+"\n")
exit()
# We must first initialise Reticulum
+6 -2
View File
@@ -215,8 +215,12 @@ def client(destination_hexhash, configpath):
# We need a binary representation of the destination
# hash that was entered on the command line
try:
if len(destination_hexhash) != 20:
raise ValueError("Destination length is invalid, must be 20 hexadecimal characters (10 bytes)")
dest_len = (RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2
if len(destination_hexhash) != dest_len:
raise ValueError(
"Destination length is invalid, must be {hex} hexadecimal characters ({byte} bytes).".format(hex=dest_len, byte=dest_len//2)
)
destination_hash = bytes.fromhex(destination_hexhash)
except:
RNS.log("Invalid destination entered. Check your input!\n")
+7 -3
View File
@@ -84,7 +84,7 @@ def client_connected(link):
def client_disconnected(link):
RNS.log("Client disconnected")
def remote_identified(identity):
def remote_identified(link, identity):
RNS.log("Remote identified as: "+str(identity))
def server_packet_received(message, packet):
@@ -124,8 +124,12 @@ def client(destination_hexhash, configpath):
# We need a binary representation of the destination
# hash that was entered on the command line
try:
if len(destination_hexhash) != 20:
raise ValueError("Destination length is invalid, must be 20 hexadecimal characters (10 bytes)")
dest_len = (RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2
if len(destination_hexhash) != dest_len:
raise ValueError(
"Destination length is invalid, must be {hex} hexadecimal characters ({byte} bytes).".format(hex=dest_len, byte=dest_len//2)
)
destination_hash = bytes.fromhex(destination_hexhash)
except:
RNS.log("Invalid destination entered. Check your input!\n")
+6 -2
View File
@@ -110,8 +110,12 @@ def client(destination_hexhash, configpath):
# We need a binary representation of the destination
# hash that was entered on the command line
try:
if len(destination_hexhash) != 20:
raise ValueError("Destination length is invalid, must be 20 hexadecimal characters (10 bytes)")
dest_len = (RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2
if len(destination_hexhash) != dest_len:
raise ValueError(
"Destination length is invalid, must be {hex} hexadecimal characters ({byte} bytes).".format(hex=dest_len, byte=dest_len//2)
)
destination_hash = bytes.fromhex(destination_hexhash)
except:
RNS.log("Invalid destination entered. Check your input!\n")
+6 -2
View File
@@ -110,8 +110,12 @@ def client(destination_hexhash, configpath):
# We need a binary representation of the destination
# hash that was entered on the command line
try:
if len(destination_hexhash) != 20:
raise ValueError("Destination length is invalid, must be 20 hexadecimal characters (10 bytes)")
dest_len = (RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2
if len(destination_hexhash) != dest_len:
raise ValueError(
"Destination length is invalid, must be {hex} hexadecimal characters ({byte} bytes).".format(hex=dest_len, byte=dest_len//2)
)
destination_hash = bytes.fromhex(destination_hexhash)
except:
RNS.log("Invalid destination entered. Check your input!\n")
+6 -2
View File
@@ -166,8 +166,12 @@ def client(destination_hexhash, configpath):
# We need a binary representation of the destination
# hash that was entered on the command line
try:
if len(destination_hexhash) != 20:
raise ValueError("Destination length is invalid, must be 20 hexadecimal characters (10 bytes)")
dest_len = (RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2
if len(destination_hexhash) != dest_len:
raise ValueError(
"Destination length is invalid, must be {hex} hexadecimal characters ({byte} bytes).".format(hex=dest_len, byte=dest_len//2)
)
destination_hash = bytes.fromhex(destination_hexhash)
except:
RNS.log("Invalid destination entered. Check your input!\n")
+36 -3
View File
@@ -1,9 +1,27 @@
all: release
test:
@echo Running tests...
python -m tests.all
clean:
@echo Cleaning...
-rm -r ./build
-rm -r ./dist
@-rm -rf ./build
@-rm -rf ./dist
@-rm -rf ./*.data
@-rm -rf ./__pycache__
@-rm -rf ./RNS/__pycache__
@-rm -rf ./RNS/Cryptography/__pycache__
@-rm -rf ./RNS/Cryptography/aes/__pycache__
@-rm -rf ./RNS/Cryptography/pure25519/__pycache__
@-rm -rf ./RNS/Interfaces/__pycache__
@-rm -rf ./RNS/Utilities/__pycache__
@-rm -rf ./RNS/vendor/__pycache__
@-rm -rf ./RNS/vendor/i2plib/__pycache__
@-rm -rf ./tests/__pycache__
@-rm -rf ./tests/rnsconfig/storage
@-rm -rf ./*.egg-info
@echo Done
remove_symlinks:
@echo Removing symlinks for build...
@@ -15,11 +33,26 @@ create_symlinks:
-ln -s ../RNS ./Examples/
-ln -s ../../RNS ./RNS/Utilities/
build_sdist_only:
python3 setup.py sdist
build_wheel:
python3 setup.py sdist bdist_wheel
release: remove_symlinks build_wheel create_symlinks
build_pure_wheel:
python3 setup.py sdist bdist_wheel --pure
documentation:
make -C docs html
manual:
make -C docs latexpdf
release: test remove_symlinks build_wheel build_pure_wheel documentation manual create_symlinks
upload:
@echo Ready to publish release, hit enter to continue
@read VOID
@echo Uploading to PyPi...
twine upload dist/*
@echo Release published
+136 -46
View File
@@ -5,11 +5,11 @@ Reticulum Network Stack β
Reticulum is the cryptography-based networking stack for wide-area networks built on 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, interconnectable 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 need IP or higher layers, although it is easy to use IP (with TCP or UDP) as the underlying carrier for Reticulum. It is therefore trivial to tunnel Reticulum over the Internet or private IP networks.
Reticulum is 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 free up overhead that has been utilised to implement a networking stack built directly on cryptographic principles, allowing resilience and stable functionality in open and trustless 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.
@@ -21,11 +21,11 @@ You can also [download the Reticulum manual as a PDF](https://github.com/markqvi
For more info, see [unsigned.io/projects/reticulum](https://unsigned.io/projects/reticulum/)
## Notable Features
- Coordination-less globally unique adressing and identification
- Coordination-less globally unique addressing and identification
- Fully self-configuring multi-hop routing
- Complete initiator anonymity, communicate without revealing your identity
- Asymmetric X25519 encryption and Ed25519 signatures as a basis for all communication
- Forward Secrecy with ephemereal Elliptic Curve Diffie-Hellman keys on Curve25519
- Forward Secrecy with ephemeral Elliptic Curve Diffie-Hellman keys on Curve25519
- Reticulum uses the [Fernet](https://github.com/fernet/spec/blob/master/Spec.md) specification for on-the-wire / over-the-air encryption
- Keys are ephemeral and derived from an ECDH key exchange on Curve25519
- AES-128 in CBC mode with PKCS7 padding
@@ -34,14 +34,14 @@ For more info, see [unsigned.io/projects/reticulum](https://unsigned.io/projects
- Unforgeable packet delivery confirmations
- A variety of supported interface types
- An intuitive and easy-to-use API
- Reliable and efficient transfer of arbritrary amounts of data
- Reliable and efficient transfer of arbitrary amounts of data
- Reticulum can handle a few bytes of data or files of many gigabytes
- Sequencing, transfer coordination and checksumming is automatic
- Sequencing, transfer coordination and checksumming are automatic
- The API is very easy to use, and provides transfer progress
- Lightweight, flexible and expandable Request/Response mechanism
- Efficient link establishment
- Total bandwidth cost of setting up an encrypted link is 3 packets totalling 237 bytes
- Low cost of keeping links open at only 0.62 bits per second
- Total bandwidth cost of setting up an encrypted link is 3 packets totaling 265 bytes
- Low cost of keeping links open at only 0.44 bits per second
## Examples of Reticulum Applications
If you want to quickly get an idea of what Reticulum can do, take a look at the following resources.
@@ -51,11 +51,11 @@ If you want to quickly get an idea of what Reticulum can do, take a look at the
- The Android, Linux and macOS app [Sideband](https://unsigned.io/sideband) has a graphical interface and focuses on ease of use.
## Where can Reticulum be used?
Over practically any medium that can support at least a half-duplex channel with 500 bits per second throughput, and an MTU of 500 bytes. Data radios, modems, LoRa radios, serial lines, AX.25 TNCs, amateur radio digital modes, ad-hoc WiFi, free-space optical links and similar systems are all examples of the types of interfaces Reticulum was designed for.
Over practically any medium that can support at least a half-duplex channel with 500 bits per second throughput, and an MTU of 500 bytes. Data radios, modems, LoRa radios, serial lines, AX.25 TNCs, amateur radio digital modes, 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://unsigned.io/projects/rnode/) has been designed specifically for use with Reticulum. It is possible to build yourself, or it can be purchased as a complete transceiver that just needs a USB connection to the host.
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.
Reticulum can also be encapsulated over existing IP networks, so there's nothing stopping you from using it over wired ethernet or your local WiFi network, 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.
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.
As an example, it's possible to set up a Raspberry Pi connected to both a LoRa radio, a packet radio TNC and a WiFi network. Once the interfaces are configured, Reticulum will take care of the rest, and any device on the WiFi network can communicate with nodes on the LoRa and packet radio sides of the network, and vice versa.
@@ -71,12 +71,21 @@ pip3 install rns
You can then start any program that uses Reticulum, or start Reticulum as a system service with [the rnsd utility](https://markqvist.github.io/Reticulum/manual/using.html#the-rnsd-utility).
When first started, Reticulum will create a default configuration file, providing basic connectivity to other Reticulum peers. The default config file contains examples for using Reticulum with LoRa transceivers (specifically [RNode](https://unsigned.io/projects/rnode/)), packet radio TNCs/modems, TCP and UDP.
When first started, Reticulum will create a default configuration file, providing basic connectivity to other Reticulum peers that might be locally reachable. The default config file contains a few examples, and references for creating a more complex configuration.
You can use the examples in the config file to expand communication over many mediums such as packet radio or LoRa (with [RNode](https://unsigned.io/projects/rnode/)), serial ports, or over fast IP links and the Internet using the UDP and TCP interfaces. For more detailed examples, take a look at the [Supported Interfaces](https://markqvist.github.io/Reticulum/manual/interfaces.html) section of the [Reticulum Manual](https://markqvist.github.io/Reticulum/manual/).
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/).
## Current Status
Reticulum should currently be considered beta software. All core protocol features are implemented and functioning, but additions will probably occur as real-world use is explored. There will be bugs. The API and wire-format can be considered relatively stable at the moment, but could change if warranted.
## 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/).
- The system daemon `rnsd` for running Reticulum as an always-available service
- An interface status utility called `rnstatus`, that displays information about interfaces
- The path lookup and management tool `rnpath` letting you view and modify path tables
- A diagnostics tool called `rnprobe` for checking connectivity to destinations
- A simple file transfer program called `rncp` making it easy to copy files to remote systems
- The remote command execution program `rnx` let's you run commands and programs and retrieve output from remote systems
All tools, including `rnx` and `rncp`, work reliably and well even over very low-bandwidth links like LoRa or Packet Radio.
## Supported interface types and devices
@@ -94,18 +103,34 @@ Currently, the following interfaces are supported:
- External programs via stdio or pipes
- Custom hardware via stdio or pipes
## Performance
Reticulum targets a *very* wide usable performance envelope, but prioritises functionality and performance over 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 500 bits per second to 20 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
Reticulum should currently be considered beta software. All core protocol features are implemented and functioning, but additions will probably occur as real-world use is explored. There will be bugs. The API and wire-format can be considered relatively stable at the moment, but could change if warranted.
## Development Roadmap
- Version 0.3.6
- Version 0.4.0
- Improving [the manual](https://markqvist.github.io/Reticulum/manual/) with sections specifically for beginners
- Version 0.3.7
- Performance and memory optimisations
- Utilities for managing identities, signing and encryption
- User friendly interface configuration tool
- Support for radio and modem interfaces on Android
- GUI interface configuration tool
- Easy way to share interface configurations, see [#19](https://github.com/markqvist/Reticulum/discussions/19)
- More interface types for even broader compatibility
- Plain ESP32 devices (ESP-Now, WiFi, Bluetooth, etc.)
- More LoRa transceivers
- AT-compatible modems
- IR Transceivers
- Planned, but not yet scheduled
- Distributed Destination Naming System
- Network-wide path balancing
- Globally routable multicast
- Bindings for other programming languages
- A portable Reticulum implementation in C, see [#21](https://github.com/markqvist/Reticulum/discussions/21)
- Easy way to share interface configurations, see [#19](https://github.com/markqvist/Reticulum/discussions/19)
- More interface types
- AT-compatible modems
- AWDL / OWL
- HF Modems
- CAN-bus
@@ -113,41 +138,52 @@ Currently, the following interfaces are supported:
- MQTT
- SPI
- i²c
- Planned, but not yet scheduled
- Globally routable multicast
- A portable Reticulum implementation in C, see [#21](https://github.com/markqvist/Reticulum/discussions/21)
- Tor
## Dependencies
The installation of the default `rns` package requires the dependencies listed below. Almost all systems and distributions have readily available packages for these dependencies, and when the `rns` package is installed with `pip`, they will be downloaded and installed as well.
- [PyCA/cryptography](https://github.com/pyca/cryptography)
- [netifaces](https://github.com/al45tair/netifaces)
- [pyserial](https://github.com/pyserial/pyserial)
## Dependencies:
- Python 3.6
- cryptography.io
- netifaces
- 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 `rnspure` package instead, which require no external dependencies for installation. Please note that the contents of the `rns` and `rnspure` packages are *identical*. The only difference is that the `rnspure` 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 `rnspure` 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 `rnspure` 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.
## Public Testnet
If you just want to get started experimenting without building any physical networks, you are welcome to join the Unsigned.io RNS Testnet. The testnet is just that, an informal network for testing and experimenting. It will be up most of the time, and anyone can join, but it also means that there's no guarantees for service availability.
The testnet runs the very latest version of Reticulum (often even a short while before it is publicly released). Sometimes experimental versions of Reticulum might be deployed to nodes on the testnet, which means strange behaviour might occur. If none of that scares you, you can join the testnet via eihter TCP or I2P. Just add one of the following interfaces to your Reticulum configuration file:
The testnet runs the very latest version of Reticulum (often even a short while before it is publicly released). Sometimes experimental versions of Reticulum might be deployed to nodes on the testnet, which means strange behaviour might occur. If none of that scares you, you can join the testnet via either TCP or I2P. Just add one of the following interfaces to your Reticulum configuration file:
```
# For connecting over TCP/IP:
[[RNS Testnet Frankfurt]]
# TCP/IP interface to the Dublin Hub
[[RNS Testnet Dublin]]
type = TCPClientInterface
interface_enabled = yes
outgoing = True
target_host = frankfurt.rns.unsigned.io
enabled = yes
target_host = dublin.connect.reticulum.network
target_port = 4965
# TCP/IP interface to the Frankfurt Hub
[[RNS Testnet Frankfurt]]
type = TCPClientInterface
enabled = yes
target_host = frankfurt.connect.reticulum.network
target_port = 5377
# For connecting over I2P:
[[RNS Testnet I2P Node A]]
# Interface to I2P Hub A
[[RNS Testnet I2P Hub A]]
type = I2PInterface
interface_enabled = yes
peers = ykzlw5ujbaqc2xkec4cpvgyxj257wcrmmgkuxqmqcur7cq3w3lha.b32.i2p
enabled = yes
peers = mrwqlsioq4hoo2lmeeud7dkfscnm7yxak7dmiyvsrnpfag3z5tsq.b32.i2p
# Interface to I2P Hub B
[[RNS Testnet I2P Hub B]]
type = I2PInterface
enabled = yes
peers = iwoqtz22dsr73aemwpw7guocplsjjoamyl7sogj33qtcd6ds4mza.b32.i2p
```
The testnet also contains a number of [Nomad Network](https://github.com/markqvist/nomadnet) nodes, and LXMF propagation nodes.
@@ -155,11 +191,65 @@ The testnet also contains a number of [Nomad Network](https://github.com/markqvi
## Support Reticulum
You can help support the continued development of open, free and private communications systems by donating via one of the following channels:
- Ethereum: 0x81F7B979fEa6134bA9FD5c701b3501A2e61E897a
- Bitcoin: 3CPmacGm34qYvR6XWLVEJmi2aNe3PZqUuq
- Monero:
```
84FpY1QbxHcgdseePYNmhTHcrgMX4nFf
BYtz2GKYToqHVVhJp8Eaw1Z1EedRnKD1
9b3B8NiLCGVxzKV17UMmmeEsCrPyA5w
```
- Ethereum
```
0x81F7B979fEa6134bA9FD5c701b3501A2e61E897a
```
- Bitcoin
```
3CPmacGm34qYvR6XWLVEJmi2aNe3PZqUuq
```
- Ko-Fi: https://ko-fi.com/markqvist
Are certain features in the development roadmap are important to you or your organisation? Make them a reality quickly by sponsoring their implementation.
## Caveat Emptor
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-breaking bugs. If you want to help out, or help sponsor an audit, please do get in touch.
## Cryptographic Primitives
Reticulum uses a simple suite of efficient, strong and modern cryptographic primitives, with widely available implementations that can be used both on general-purpose CPUs and on microcontrollers. The necessary primitives are:
- Ed25519 for signatures
- X22519 for ECDH key exchanges
- HKDF for key derivation
- Modified Fernet for encrypted tokens
- AES-128 in CBC mode
- HMAC for message authentication
- No Fernet version and timestamp fields
- SHA-256
- SHA-512
In the default installation configuration, the `X25519`, `Ed25519` and `AES-128-CBC` primitives are provided by [OpenSSL](https://www.openssl.org/) (via the [PyCA/cryptography](https://github.com/pyca/cryptography) package). The hashing functions `SHA-256` and `SHA-512` are provided by the standard Python [hashlib](https://docs.python.org/3/library/hashlib.html). The `HKDF`, `HMAC`, `Fernet` primitives, and the `PKCS7` padding function are always provided by the following internal implementations:
- [HKDF.py](RNS/Cryptography/HKDF.py)
- [HMAC.py](RNS/Cryptography/HMAC.py)
- [Fernet.py](RNS/Cryptography/Fernet.py)
- [PKCS7.py](RNS/Cryptography/PKCS7.py)
Reticulum also includes a complete implementation of all necessary primitives in pure Python. If OpenSSL & 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.
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](https://github.com/orgurar/python-aes) by [Or Gur Arie](https://github.com/orgurar), *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*
- [Netifaces](https://github.com/al45tair/netifaces) by [Alastair Houghton](https://github.com/al45tair), *MIT License*
- [Configobj](https://github.com/DiffSK/configobj) by Michael Foord, Nicola Larosa, Rob Dennis & Eli Courtwright, *BSD License*
- [Six](https://github.com/benjaminp/six) by [Benjamin Peterson](https://github.com/benjaminp), *MIT License*
- [Umsgpack.py](https://github.com/vsergeev/u-msgpack-python) by [Ivan A. Sergeev](https://github.com/vsergeev)
- [Python](https://www.python.org)
+68
View File
@@ -0,0 +1,68 @@
# MIT License
#
# Copyright (c) 2022 Mark Qvist / unsigned.io
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import RNS.Cryptography.Provider as cp
import RNS.vendor.platformutils as pu
if cp.PROVIDER == cp.PROVIDER_INTERNAL:
from .aes import AES
elif cp.PROVIDER == cp.PROVIDER_PYCA:
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
if pu.cryptography_old_api():
from cryptography.hazmat.backends import default_backend
class AES_128_CBC:
@staticmethod
def encrypt(plaintext, key, iv):
if cp.PROVIDER == cp.PROVIDER_INTERNAL:
cipher = AES(key)
return cipher.encrypt(plaintext, iv)
elif cp.PROVIDER == cp.PROVIDER_PYCA:
if not pu.cryptography_old_api():
cipher = Cipher(algorithms.AES(key), modes.CBC(iv))
else:
cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend())
encryptor = cipher.encryptor()
ciphertext = encryptor.update(plaintext) + encryptor.finalize()
return ciphertext
@staticmethod
def decrypt(ciphertext, key, iv):
if cp.PROVIDER == cp.PROVIDER_INTERNAL:
cipher = AES(key)
return cipher.decrypt(ciphertext, iv)
elif cp.PROVIDER == cp.PROVIDER_PYCA:
if not pu.cryptography_old_api():
cipher = Cipher(algorithms.AES(key), modes.CBC(iv))
else:
cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend())
decryptor = cipher.decryptor()
plaintext = decryptor.update(ciphertext) + decryptor.finalize()
return plaintext
+41
View File
@@ -0,0 +1,41 @@
import os
from .pure25519 import ed25519_oop as ed25519
class Ed25519PrivateKey:
def __init__(self, seed):
self.seed = seed
self.sk = ed25519.SigningKey(self.seed)
#self.vk = self.sk.get_verifying_key()
@classmethod
def generate(cls):
return cls.from_private_bytes(os.urandom(32))
@classmethod
def from_private_bytes(cls, data):
return cls(seed=data)
def private_bytes(self):
return self.seed
def public_key(self):
return Ed25519PublicKey.from_public_bytes(self.sk.vk_s)
def sign(self, message):
return self.sk.sign(message)
class Ed25519PublicKey:
def __init__(self, seed):
self.seed = seed
self.vk = ed25519.VerifyingKey(self.seed)
@classmethod
def from_public_bytes(cls, data):
return cls(data)
def public_bytes(self):
return self.vk.to_bytes()
def verify(self, signature, message):
self.vk.verify(signature, message)
+110
View File
@@ -0,0 +1,110 @@
# MIT License
#
# Copyright (c) 2022 Mark Qvist / unsigned.io
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import os
import time
from RNS.Cryptography import HMAC
from RNS.Cryptography import PKCS7
from RNS.Cryptography.AES import AES_128_CBC
class Fernet():
"""
This class provides a slightly modified implementation of the Fernet spec
found at: https://github.com/fernet/spec/blob/master/Spec.md
According to the spec, a Fernet token includes a one byte VERSION and
eight byte TIMESTAMP field at the start of each token. These fields are
not relevant to Reticulum. They are therefore stripped from this
implementation, since they incur overhead and leak initiator metadata.
"""
FERNET_OVERHEAD = 48 # Bytes
@staticmethod
def generate_key():
return os.urandom(32)
def __init__(self, key = None):
if key == None:
raise ValueError("Fernet key cannot be None")
if len(key) != 32:
raise ValueError("Fernet key must be 32 bytes, not "+str(len(key)))
self._signing_key = key[:16]
self._encryption_key = key[16:]
def verify_hmac(self, token):
if len(token) <= 32:
raise ValueError("Cannot verify HMAC on token of only "+str(len(token))+" bytes")
else:
received_hmac = token[-32:]
expected_hmac = HMAC.new(self._signing_key, token[:-32]).digest()
if received_hmac == expected_hmac:
return True
else:
return False
def encrypt(self, data = None):
iv = os.urandom(16)
current_time = int(time.time())
if not isinstance(data, bytes):
raise TypeError("Fernet token plaintext input must be bytes")
ciphertext = AES_128_CBC.encrypt(
plaintext = PKCS7.pad(data),
key = self._encryption_key,
iv = iv,
)
signed_parts = iv+ciphertext
return signed_parts + HMAC.new(self._signing_key, signed_parts).digest()
def decrypt(self, token = None):
if not isinstance(token, bytes):
raise TypeError("Fernet token must be bytes")
if not self.verify_hmac(token):
raise ValueError("Fernet token HMAC was invalid")
iv = token[:16]
ciphertext = token[16:-32]
try:
plaintext = PKCS7.unpad(
AES_128_CBC.decrypt(
ciphertext,
self._encryption_key,
iv,
)
)
return plaintext
except Exception as e:
raise ValueError("Could not decrypt Fernet token")
+57
View File
@@ -0,0 +1,57 @@
# MIT License
#
# Copyright (c) 2022 Mark Qvist / unsigned.io
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import hashlib
from math import ceil
from RNS.Cryptography import HMAC
def hkdf(length=None, derive_from=None, salt=None, context=None):
hash_len = 32
def hmac_sha256(key, data):
return HMAC.new(key, data).digest()
if length == None or length < 1:
raise ValueError("Invalid output key length")
if derive_from == "None" or derive_from == "":
raise ValueError("Cannot derive key from empty input material")
if salt == None or len(salt) == 0:
salt = bytes([0] * hash_len)
if salt == None:
salt = b""
if context == None:
context = b""
pseudorandom_key = hmac_sha256(salt, derive_from)
block = b""
derived = b""
for i in range(ceil(length / hash_len)):
block = hmac_sha256(pseudorandom_key, block + context + bytes([i + 1]))
derived += block
return derived[:length]
+183
View File
@@ -0,0 +1,183 @@
# This HMAC implementation comes directly from the HMAC implementation
# included in Python 3.10.4, and is almost completely identical. It has
# been modified to be a pure Python implementation, that is not dependent
# on the system having OpenSSL binaries installed.
import warnings as _warnings
import hashlib as _hashlib
trans_5C = bytes((x ^ 0x5C) for x in range(256))
trans_36 = bytes((x ^ 0x36) for x in range(256))
# The size of the digests returned by HMAC depends on the underlying
# hashing module used. Use digest_size from the instance of HMAC instead.
digest_size = None
class HMAC:
"""RFC 2104 HMAC class. Also complies with RFC 4231.
This supports the API for Cryptographic Hash Functions (PEP 247).
"""
blocksize = 64 # 512-bit HMAC; can be changed in subclasses.
__slots__ = (
"_hmac", "_inner", "_outer", "block_size", "digest_size"
)
def __init__(self, key, msg=None, digestmod=_hashlib.sha256):
"""Create a new HMAC object.
key: bytes or buffer, key for the keyed hash object.
msg: bytes or buffer, Initial input for the hash or None.
digestmod: A hash name suitable for hashlib.new(). *OR*
A hashlib constructor returning a new hash object. *OR*
A module supporting PEP 247.
Required as of 3.8, despite its position after the optional
msg argument. Passing it as a keyword argument is
recommended, though not required for legacy API reasons.
"""
if not isinstance(key, (bytes, bytearray)):
raise TypeError("key: expected bytes or bytearray, but got %r" % type(key).__name__)
if not digestmod:
raise TypeError("Missing required parameter 'digestmod'.")
self._hmac_init(key, msg, digestmod)
def _hmac_init(self, key, msg, digestmod):
if callable(digestmod):
digest_cons = digestmod
elif isinstance(digestmod, str):
digest_cons = lambda d=b'': _hashlib.new(digestmod, d)
else:
digest_cons = lambda d=b'': digestmod.new(d)
self._hmac = None
self._outer = digest_cons()
self._inner = digest_cons()
self.digest_size = self._inner.digest_size
if hasattr(self._inner, 'block_size'):
blocksize = self._inner.block_size
if blocksize < 16:
_warnings.warn('block_size of %d seems too small; using our '
'default of %d.' % (blocksize, self.blocksize),
RuntimeWarning, 2)
blocksize = self.blocksize
else:
_warnings.warn('No block_size attribute on given digest object; '
'Assuming %d.' % (self.blocksize),
RuntimeWarning, 2)
blocksize = self.blocksize
if len(key) > blocksize:
key = digest_cons(key).digest()
# self.blocksize is the default blocksize. self.block_size is
# effective block size as well as the public API attribute.
self.block_size = blocksize
key = key.ljust(blocksize, b'\0')
self._outer.update(key.translate(trans_5C))
self._inner.update(key.translate(trans_36))
if msg is not None:
self.update(msg)
@property
def name(self):
if self._hmac:
return self._hmac.name
else:
return f"hmac-{self._inner.name}"
def update(self, msg):
"""Feed data from msg into this hashing object."""
inst = self._hmac or self._inner
inst.update(msg)
def copy(self):
"""Return a separate copy of this hashing object.
An update to this copy won't affect the original object.
"""
# Call __new__ directly to avoid the expensive __init__.
other = self.__class__.__new__(self.__class__)
other.digest_size = self.digest_size
if self._hmac:
other._hmac = self._hmac.copy()
other._inner = other._outer = None
else:
other._hmac = None
other._inner = self._inner.copy()
other._outer = self._outer.copy()
return other
def _current(self):
"""Return a hash object for the current state.
To be used only internally with digest() and hexdigest().
"""
if self._hmac:
return self._hmac
else:
h = self._outer.copy()
h.update(self._inner.digest())
return h
def digest(self):
"""Return the hash value of this hashing object.
This returns the hmac value as bytes. The object is
not altered in any way by this function; you can continue
updating the object after calling this function.
"""
h = self._current()
return h.digest()
def hexdigest(self):
"""Like digest(), but returns a string of hexadecimal digits instead.
"""
h = self._current()
return h.hexdigest()
def new(key, msg=None, digestmod=_hashlib.sha256):
"""Create a new hashing object and return it.
key: bytes or buffer, The starting key for the hash.
msg: bytes or buffer, Initial input for the hash, or None.
digestmod: A hash name suitable for hashlib.new(). *OR*
A hashlib constructor returning a new hash object. *OR*
A module supporting PEP 247.
Required as of 3.8, despite its position after the optional
msg argument. Passing it as a keyword argument is
recommended, though not required for legacy API reasons.
You can now feed arbitrary bytes into the object using its update()
method, and can ask for the hash value at any time by calling its digest()
or hexdigest() methods.
"""
return HMAC(key, msg, digestmod)
def digest(key, msg, digest):
"""Fast inline implementation of HMAC.
key: bytes or buffer, The key for the keyed hash object.
msg: bytes or buffer, Input message.
digest: A hash name suitable for hashlib.new() for best performance. *OR*
A hashlib constructor returning a new hash object. *OR*
A module supporting PEP 247.
"""
if callable(digest):
digest_cons = digest
elif isinstance(digest, str):
digest_cons = lambda d=b'': _hashlib.new(digest, d)
else:
digest_cons = lambda d=b'': digest.new(d)
inner = digest_cons()
outer = digest_cons()
blocksize = getattr(inner, 'block_size', 64)
if len(key) > blocksize:
key = digest_cons(key).digest()
key = key + b'\x00' * (blocksize - len(key))
inner.update(key.translate(trans_36))
outer.update(key.translate(trans_5C))
inner.update(msg)
outer.update(inner.digest())
return outer.digest()
+34
View File
@@ -0,0 +1,34 @@
import importlib
if importlib.util.find_spec('hashlib') != None:
import hashlib
else:
hashlib = None
if hasattr(hashlib, "sha512"):
from hashlib import sha512 as ext_sha512
else:
from .SHA512 import sha512 as ext_sha512
if hasattr(hashlib, "sha256"):
from hashlib import sha256 as ext_sha256
else:
from .SHA256 import sha256 as ext_sha256
"""
The SHA primitives are abstracted here to allow platform-
aware hardware acceleration in the future. Currently only
uses Python's internal SHA-256 implementation. All SHA-256
calls in RNS end up here.
"""
def sha256(data):
digest = ext_sha256()
digest.update(data)
return digest.digest()
def sha512(data):
digest = ext_sha512()
digest.update(data)
return digest.digest()
+40
View File
@@ -0,0 +1,40 @@
# MIT License
#
# Copyright (c) 2022 Mark Qvist / unsigned.io
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
class PKCS7:
BLOCKSIZE = 16
@staticmethod
def pad(data, bs=BLOCKSIZE):
l = len(data)
n = bs-l%bs
v = bytes([n])
return data+v*n
@staticmethod
def unpad(data, bs=BLOCKSIZE):
l = len(data)
n = data[-1]
if n > bs:
raise ValueError("Cannot unpad, invalid padding length of "+str(n)+" bytes")
else:
return data[:l-n]
+38
View File
@@ -0,0 +1,38 @@
import importlib
PROVIDER_NONE = 0x00
PROVIDER_INTERNAL = 0x01
PROVIDER_PYCA = 0x02
PROVIDER = PROVIDER_NONE
pyca_v = None
use_pyca = False
try:
if importlib.util.find_spec('cryptography') != None:
import cryptography
pyca_v = cryptography.__version__
v = pyca_v.split(".")
if int(v[0]) == 2:
if int(v[1]) >= 8:
use_pyca = True
elif int(v[0]) >= 3:
use_pyca = True
except Exception as e:
pass
if use_pyca:
PROVIDER = PROVIDER_PYCA
else:
PROVIDER = PROVIDER_INTERNAL
def backend():
if PROVIDER == PROVIDER_NONE:
return "none"
elif PROVIDER == PROVIDER_INTERNAL:
return "internal"
elif PROVIDER == PROVIDER_PYCA:
return "openssl, PyCA "+str(pyca_v)
+90
View File
@@ -0,0 +1,90 @@
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey, Ed25519PublicKey
from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey, X25519PublicKey
# These proxy classes exist to create a uniform API accross
# cryptography primitive providers.
class X25519PrivateKeyProxy:
def __init__(self, real):
self.real = real
@classmethod
def generate(cls):
return cls(X25519PrivateKey.generate())
@classmethod
def from_private_bytes(cls, data):
return cls(X25519PrivateKey.from_private_bytes(data))
def private_bytes(self):
return self.real.private_bytes(
encoding=serialization.Encoding.Raw,
format=serialization.PrivateFormat.Raw,
encryption_algorithm=serialization.NoEncryption(),
)
def public_key(self):
return X25519PublicKeyProxy(self.real.public_key())
def exchange(self, peer_public_key):
return self.real.exchange(peer_public_key.real)
class X25519PublicKeyProxy:
def __init__(self, real):
self.real = real
@classmethod
def from_public_bytes(cls, data):
return cls(X25519PublicKey.from_public_bytes(data))
def public_bytes(self):
return self.real.public_bytes(
encoding=serialization.Encoding.Raw,
format=serialization.PublicFormat.Raw
)
class Ed25519PrivateKeyProxy:
def __init__(self, real):
self.real = real
@classmethod
def generate(cls):
return cls(Ed25519PrivateKey.generate())
@classmethod
def from_private_bytes(cls, data):
return cls(Ed25519PrivateKey.from_private_bytes(data))
def private_bytes(self):
return self.real.private_bytes(
encoding=serialization.Encoding.Raw,
format=serialization.PrivateFormat.Raw,
encryption_algorithm=serialization.NoEncryption()
)
def public_key(self):
return Ed25519PublicKeyProxy(self.real.public_key())
def sign(self, message):
return self.real.sign(message)
class Ed25519PublicKeyProxy:
def __init__(self, real):
self.real = real
@classmethod
def from_public_bytes(cls, data):
return cls(Ed25519PublicKey.from_public_bytes(data))
def public_bytes(self):
return self.real.public_bytes(
encoding=serialization.Encoding.Raw,
format=serialization.PublicFormat.Raw
)
def verify(self, signature, message):
self.real.verify(signature, message)
+129
View File
@@ -0,0 +1,129 @@
# MIT License
#
# Copyright (c) 2017 Thomas Dixon
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import copy
import struct
import sys
def new(m=None):
return sha256(m)
class sha256(object):
_k = (0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3,
0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5,
0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2)
_h = (0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19)
_output_size = 8
blocksize = 1
block_size = 64
digest_size = 32
def __init__(self, m=None):
self._buffer = b""
self._counter = 0
if m is not None:
if type(m) is not bytes:
raise TypeError('%s() argument 1 must be bytes, not %s' % (self.__class__.__name__, type(m).__name__))
self.update(m)
def _rotr(self, x, y):
return ((x >> y) | (x << (32-y))) & 0xFFFFFFFF
def _sha256_process(self, c):
w = [0]*64
w[0:16] = struct.unpack('!16L', c)
for i in range(16, 64):
s0 = self._rotr(w[i-15], 7) ^ self._rotr(w[i-15], 18) ^ (w[i-15] >> 3)
s1 = self._rotr(w[i-2], 17) ^ self._rotr(w[i-2], 19) ^ (w[i-2] >> 10)
w[i] = (w[i-16] + s0 + w[i-7] + s1) & 0xFFFFFFFF
a,b,c,d,e,f,g,h = self._h
for i in range(64):
s0 = self._rotr(a, 2) ^ self._rotr(a, 13) ^ self._rotr(a, 22)
maj = (a & b) ^ (a & c) ^ (b & c)
t2 = s0 + maj
s1 = self._rotr(e, 6) ^ self._rotr(e, 11) ^ self._rotr(e, 25)
ch = (e & f) ^ ((~e) & g)
t1 = h + s1 + ch + self._k[i] + w[i]
h = g
g = f
f = e
e = (d + t1) & 0xFFFFFFFF
d = c
c = b
b = a
a = (t1 + t2) & 0xFFFFFFFF
self._h = [(x+y) & 0xFFFFFFFF for x,y in zip(self._h, [a,b,c,d,e,f,g,h])]
def update(self, m):
if not m:
return
if type(m) is not bytes:
raise TypeError('%s() argument 1 must be bytes, not %s' % (sys._getframe().f_code.co_name, type(m).__name__))
self._buffer += m
self._counter += len(m)
while len(self._buffer) >= 64:
self._sha256_process(self._buffer[:64])
self._buffer = self._buffer[64:]
def digest(self):
mdi = self._counter & 0x3F
length = struct.pack('!Q', self._counter<<3)
if mdi < 56:
padlen = 55-mdi
else:
padlen = 119-mdi
r = self.copy()
r.update(b'\x80'+(b'\x00'*padlen)+length)
return b''.join([struct.pack('!L', i) for i in r._h[:self._output_size]])
def hexdigest(self):
return self.digest().encode('hex')
def copy(self):
return copy.deepcopy(self)
+129
View File
@@ -0,0 +1,129 @@
# MIT License
#
# Copyright (c) 2017 Thomas Dixon
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import copy, struct, sys
def new(m=None):
return sha512(m)
class sha512(object):
_k = (0x428a2f98d728ae22, 0x7137449123ef65cd, 0xb5c0fbcfec4d3b2f, 0xe9b5dba58189dbbc,
0x3956c25bf348b538, 0x59f111f1b605d019, 0x923f82a4af194f9b, 0xab1c5ed5da6d8118,
0xd807aa98a3030242, 0x12835b0145706fbe, 0x243185be4ee4b28c, 0x550c7dc3d5ffb4e2,
0x72be5d74f27b896f, 0x80deb1fe3b1696b1, 0x9bdc06a725c71235, 0xc19bf174cf692694,
0xe49b69c19ef14ad2, 0xefbe4786384f25e3, 0x0fc19dc68b8cd5b5, 0x240ca1cc77ac9c65,
0x2de92c6f592b0275, 0x4a7484aa6ea6e483, 0x5cb0a9dcbd41fbd4, 0x76f988da831153b5,
0x983e5152ee66dfab, 0xa831c66d2db43210, 0xb00327c898fb213f, 0xbf597fc7beef0ee4,
0xc6e00bf33da88fc2, 0xd5a79147930aa725, 0x06ca6351e003826f, 0x142929670a0e6e70,
0x27b70a8546d22ffc, 0x2e1b21385c26c926, 0x4d2c6dfc5ac42aed, 0x53380d139d95b3df,
0x650a73548baf63de, 0x766a0abb3c77b2a8, 0x81c2c92e47edaee6, 0x92722c851482353b,
0xa2bfe8a14cf10364, 0xa81a664bbc423001, 0xc24b8b70d0f89791, 0xc76c51a30654be30,
0xd192e819d6ef5218, 0xd69906245565a910, 0xf40e35855771202a, 0x106aa07032bbd1b8,
0x19a4c116b8d2d0c8, 0x1e376c085141ab53, 0x2748774cdf8eeb99, 0x34b0bcb5e19b48a8,
0x391c0cb3c5c95a63, 0x4ed8aa4ae3418acb, 0x5b9cca4f7763e373, 0x682e6ff3d6b2b8a3,
0x748f82ee5defb2fc, 0x78a5636f43172f60, 0x84c87814a1f0ab72, 0x8cc702081a6439ec,
0x90befffa23631e28, 0xa4506cebde82bde9, 0xbef9a3f7b2c67915, 0xc67178f2e372532b,
0xca273eceea26619c, 0xd186b8c721c0c207, 0xeada7dd6cde0eb1e, 0xf57d4f7fee6ed178,
0x06f067aa72176fba, 0x0a637dc5a2c898a6, 0x113f9804bef90dae, 0x1b710b35131c471b,
0x28db77f523047d84, 0x32caab7b40c72493, 0x3c9ebe0a15c9bebc, 0x431d67c49c100d4c,
0x4cc5d4becb3e42b6, 0x597f299cfc657e2a, 0x5fcb6fab3ad6faec, 0x6c44198c4a475817)
_h = (0x6a09e667f3bcc908, 0xbb67ae8584caa73b, 0x3c6ef372fe94f82b, 0xa54ff53a5f1d36f1,
0x510e527fade682d1, 0x9b05688c2b3e6c1f, 0x1f83d9abfb41bd6b, 0x5be0cd19137e2179)
_output_size = 8
blocksize = 1
block_size = 128
digest_size = 64
def __init__(self, m=None):
self._buffer = b''
self._counter = 0
if m is not None:
if type(m) is not bytes:
raise TypeError('%s() argument 1 must be bytes, not %s' % (self.__class__.__name__, type(m).__name__))
self.update(m)
def _rotr(self, x, y):
return ((x >> y) | (x << (64-y))) & 0xFFFFFFFFFFFFFFFF
def _sha512_process(self, chunk):
w = [0]*80
w[0:16] = struct.unpack('!16Q', chunk)
for i in range(16, 80):
s0 = self._rotr(w[i-15], 1) ^ self._rotr(w[i-15], 8) ^ (w[i-15] >> 7)
s1 = self._rotr(w[i-2], 19) ^ self._rotr(w[i-2], 61) ^ (w[i-2] >> 6)
w[i] = (w[i-16] + s0 + w[i-7] + s1) & 0xFFFFFFFFFFFFFFFF
a,b,c,d,e,f,g,h = self._h
for i in range(80):
s0 = self._rotr(a, 28) ^ self._rotr(a, 34) ^ self._rotr(a, 39)
maj = (a & b) ^ (a & c) ^ (b & c)
t2 = s0 + maj
s1 = self._rotr(e, 14) ^ self._rotr(e, 18) ^ self._rotr(e, 41)
ch = (e & f) ^ ((~e) & g)
t1 = h + s1 + ch + self._k[i] + w[i]
h = g
g = f
f = e
e = (d + t1) & 0xFFFFFFFFFFFFFFFF
d = c
c = b
b = a
a = (t1 + t2) & 0xFFFFFFFFFFFFFFFF
self._h = [(x+y) & 0xFFFFFFFFFFFFFFFF for x,y in zip(self._h, [a,b,c,d,e,f,g,h])]
def update(self, m):
if not m:
return
if type(m) is not bytes:
raise TypeError('%s() argument 1 must be bytes, not %s' % (sys._getframe().f_code.co_name, type(m).__name__))
self._buffer += m
self._counter += len(m)
while len(self._buffer) >= 128:
self._sha512_process(self._buffer[:128])
self._buffer = self._buffer[128:]
def digest(self):
mdi = self._counter & 0x7F
length = struct.pack('!Q', self._counter<<3)
if mdi < 112:
padlen = 111-mdi
else:
padlen = 239-mdi
r = self.copy()
r.update(b'\x80'+(b'\x00'*(padlen+8))+length)
return b''.join([struct.pack('!Q', i) for i in r._h[:self._output_size]])
def hexdigest(self):
return self.digest().encode('hex')
def copy(self):
return copy.deepcopy(self)
+171
View File
@@ -0,0 +1,171 @@
# By Nicko van Someren, 2021. This code is released into the public domain.
# Small modifications for use in Reticulum, and constant time key exchange
# added by Mark Qvist in 2022.
# WARNING! Only the X25519PrivateKey.exchange() method attempts to hide execution time.
# In the context of Reticulum, this is sufficient, but it may not be in other systems. If
# this code is to be used to provide cryptographic security in an environment where the
# start and end times of the execution can be guessed, inferred or measured then it is
# critical that steps are taken to hide the execution time, for instance by adding a
# delay so that encrypted packets are not sent until a fixed time after the _start_ of
# execution.
import os
import time
P = 2 ** 255 - 19
_A = 486662
def _point_add(point_n, point_m, point_diff):
"""Given the projection of two points and their difference, return their sum"""
(xn, zn) = point_n
(xm, zm) = point_m
(x_diff, z_diff) = point_diff
x = (z_diff << 2) * (xm * xn - zm * zn) ** 2
z = (x_diff << 2) * (xm * zn - zm * xn) ** 2
return x % P, z % P
def _point_double(point_n):
"""Double a point provided in projective coordinates"""
(xn, zn) = point_n
xn2 = xn ** 2
zn2 = zn ** 2
x = (xn2 - zn2) ** 2
xzn = xn * zn
z = 4 * xzn * (xn2 + _A * xzn + zn2)
return x % P, z % P
def _const_time_swap(a, b, swap):
"""Swap two values in constant time"""
index = int(swap) * 2
temp = (a, b, b, a)
return temp[index:index+2]
def _raw_curve25519(base, n):
"""Raise the point base to the power n"""
zero = (1, 0)
one = (base, 1)
mP, m1P = zero, one
for i in reversed(range(256)):
bit = bool(n & (1 << i))
mP, m1P = _const_time_swap(mP, m1P, bit)
mP, m1P = _point_double(mP), _point_add(mP, m1P, one)
mP, m1P = _const_time_swap(mP, m1P, bit)
x, z = mP
inv_z = pow(z, P - 2, P)
return (x * inv_z) % P
def _unpack_number(s):
"""Unpack 32 bytes to a 256 bit value"""
if len(s) != 32:
raise ValueError('Curve25519 values must be 32 bytes')
return int.from_bytes(s, "little")
def _pack_number(n):
"""Pack a value into 32 bytes"""
return n.to_bytes(32, "little")
def _fix_secret(n):
"""Mask a value to be an acceptable exponent"""
n &= ~7
n &= ~(128 << 8 * 31)
n |= 64 << 8 * 31
return n
def curve25519(base_point_raw, secret_raw):
"""Raise the base point to a given power"""
base_point = _unpack_number(base_point_raw)
secret = _fix_secret(_unpack_number(secret_raw))
return _pack_number(_raw_curve25519(base_point, secret))
def curve25519_base(secret_raw):
"""Raise the generator point to a given power"""
secret = _fix_secret(_unpack_number(secret_raw))
return _pack_number(_raw_curve25519(9, secret))
class X25519PublicKey:
def __init__(self, x):
self.x = x
@classmethod
def from_public_bytes(cls, data):
return cls(_unpack_number(data))
def public_bytes(self):
return _pack_number(self.x)
class X25519PrivateKey:
MIN_EXEC_TIME = 0.002
MAX_EXEC_TIME = 0.5
DELAY_WINDOW = 10
T_CLEAR = None
T_MAX = 0
def __init__(self, a):
self.a = a
@classmethod
def generate(cls):
return cls.from_private_bytes(os.urandom(32))
@classmethod
def from_private_bytes(cls, data):
return cls(_fix_secret(_unpack_number(data)))
def private_bytes(self):
return _pack_number(self.a)
def public_key(self):
return X25519PublicKey.from_public_bytes(_pack_number(_raw_curve25519(9, self.a)))
def exchange(self, peer_public_key):
if isinstance(peer_public_key, bytes):
peer_public_key = X25519PublicKey.from_public_bytes(peer_public_key)
start = time.time()
shared = _pack_number(_raw_curve25519(peer_public_key.x, self.a))
end = time.time()
duration = end-start
if X25519PrivateKey.T_CLEAR == None:
X25519PrivateKey.T_CLEAR = end + X25519PrivateKey.DELAY_WINDOW
if end > X25519PrivateKey.T_CLEAR:
X25519PrivateKey.T_CLEAR = end + X25519PrivateKey.DELAY_WINDOW
X25519PrivateKey.T_MAX = 0
if duration < X25519PrivateKey.T_MAX or duration < X25519PrivateKey.MIN_EXEC_TIME:
target = start+X25519PrivateKey.T_MAX
if target > start+X25519PrivateKey.MAX_EXEC_TIME:
target = start+X25519PrivateKey.MAX_EXEC_TIME
if target < start+X25519PrivateKey.MIN_EXEC_TIME:
target = start+X25519PrivateKey.MIN_EXEC_TIME
try:
time.sleep(target-time.time())
except Exception as e:
pass
elif duration > X25519PrivateKey.T_MAX:
X25519PrivateKey.T_MAX = duration
return shared
+24
View File
@@ -0,0 +1,24 @@
import os
import glob
from .Hashes import sha256
from .Hashes import sha512
from .HKDF import hkdf
from .PKCS7 import PKCS7
from .Fernet import Fernet
from .Provider import backend
import RNS.Cryptography.Provider as cp
if cp.PROVIDER == cp.PROVIDER_INTERNAL:
from RNS.Cryptography.X25519 import X25519PrivateKey, X25519PublicKey
from RNS.Cryptography.Ed25519 import Ed25519PrivateKey, Ed25519PublicKey
elif cp.PROVIDER == cp.PROVIDER_PYCA:
from RNS.Cryptography.Proxies import X25519PrivateKeyProxy as X25519PrivateKey
from RNS.Cryptography.Proxies import X25519PublicKeyProxy as X25519PublicKey
from RNS.Cryptography.Proxies import Ed25519PrivateKeyProxy as Ed25519PrivateKey
from RNS.Cryptography.Proxies import Ed25519PublicKeyProxy as Ed25519PublicKey
modules = glob.glob(os.path.dirname(__file__)+"/*.py")
__all__ = [ os.path.basename(f)[:-3] for f in modules if not f.endswith('__init__.py')]
+1
View File
@@ -0,0 +1 @@
from .aes import AES
+271
View File
@@ -0,0 +1,271 @@
# MIT License
# Copyright (c) 2021 Or Gur Arie
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
from .utils import *
class AES:
# AES-128 block size
block_size = 16
# AES-128 encrypts messages with 10 rounds
_rounds = 10
# initiate the AES objecy
def __init__(self, key):
"""
Initializes the object with a given key.
"""
# make sure key length is right
assert len(key) == AES.block_size
# ExpandKey
self._round_keys = self._expand_key(key)
# will perform the AES ExpandKey phase
def _expand_key(self, master_key):
"""
Expands and returns a list of key matrices for the given master_key.
"""
# Initialize round keys with raw key material.
key_columns = bytes2matrix(master_key)
iteration_size = len(master_key) // 4
# Each iteration has exactly as many columns as the key material.
i = 1
while len(key_columns) < (self._rounds + 1) * 4:
# Copy previous word.
word = list(key_columns[-1])
# Perform schedule_core once every "row".
if len(key_columns) % iteration_size == 0:
# Circular shift.
word.append(word.pop(0))
# Map to S-BOX.
word = [s_box[b] for b in word]
# XOR with first byte of R-CON, since the others bytes of R-CON are 0.
word[0] ^= r_con[i]
i += 1
elif len(master_key) == 32 and len(key_columns) % iteration_size == 4:
# Run word through S-box in the fourth iteration when using a
# 256-bit key.
word = [s_box[b] for b in word]
# XOR with equivalent word from previous iteration.
word = bytes(i^j for i, j in zip(word, key_columns[-iteration_size]))
key_columns.append(word)
# Group key words in 4x4 byte matrices.
return [key_columns[4*i : 4*(i+1)] for i in range(len(key_columns) // 4)]
# encrypt a single block of data with AES
def _encrypt_block(self, plaintext):
"""
Encrypts a single block of 16 byte long plaintext.
"""
# length of a single block
assert len(plaintext) == AES.block_size
# perform on a matrix
state = bytes2matrix(plaintext)
# AddRoundKey
add_round_key(state, self._round_keys[0])
# 9 main rounds
for i in range(1, self._rounds):
# SubBytes
sub_bytes(state)
# ShiftRows
shift_rows(state)
# MixCols
mix_columns(state)
# AddRoundKey
add_round_key(state, self._round_keys[i])
# last round, w/t AddRoundKey step
sub_bytes(state)
shift_rows(state)
add_round_key(state, self._round_keys[-1])
# return the encrypted matrix as bytes
return matrix2bytes(state)
# decrypt a single block of data with AES
def _decrypt_block(self, ciphertext):
"""
Decrypts a single block of 16 byte long ciphertext.
"""
# length of a single block
assert len(ciphertext) == AES.block_size
# perform on a matrix
state = bytes2matrix(ciphertext)
# in reverse order, last round is first
add_round_key(state, self._round_keys[-1])
inv_shift_rows(state)
inv_sub_bytes(state)
for i in range(self._rounds - 1, 0, -1):
# nain rounds
add_round_key(state, self._round_keys[i])
inv_mix_columns(state)
inv_shift_rows(state)
inv_sub_bytes(state)
# initial AddRoundKey phase
add_round_key(state, self._round_keys[0])
# return bytes
return matrix2bytes(state)
# will encrypt the entire data
def encrypt(self, plaintext, iv):
"""
Encrypts `plaintext` using CBC mode and PKCS#7 padding, with the given
initialization vector (iv).
"""
# iv length must be same as block size
assert len(iv) == AES.block_size
assert len(plaintext) % AES.block_size == 0
ciphertext_blocks = []
previous = iv
for plaintext_block in split_blocks(plaintext):
# in CBC mode every block is XOR'd with the previous block
xorred = xor_bytes(plaintext_block, previous)
# encrypt current block
block = self._encrypt_block(xorred)
previous = block
# append to ciphertext
ciphertext_blocks.append(block)
# return as bytes
return b''.join(ciphertext_blocks)
# will decrypt the entire data
def decrypt(self, ciphertext, iv):
"""
Decrypts `ciphertext` using CBC mode and PKCS#7 padding, with the given
initialization vector (iv).
"""
# iv length must be same as block size
assert len(iv) == AES.block_size
plaintext_blocks = []
previous = iv
for ciphertext_block in split_blocks(ciphertext):
# in CBC mode every block is XOR'd with the previous block
xorred = xor_bytes(previous, self._decrypt_block(ciphertext_block))
# append plaintext
plaintext_blocks.append(xorred)
previous = ciphertext_block
return b''.join(plaintext_blocks)
def test():
# modules and classes requiered for test only
import os
class bcolors:
OK = '\033[92m' #GREEN
WARNING = '\033[93m' #YELLOW
FAIL = '\033[91m' #RED
RESET = '\033[0m' #RESET COLOR
# will test AES class by performing an encryption / decryption
print("AES Tests")
print("=========")
# generate a secret key and print details
key = os.urandom(AES.block_size)
_aes = AES(key)
print(f"Algorithm: AES-CBC-{AES.block_size*8}")
print(f"Secret Key: {key.hex()}")
print()
# test single block encryption / decryption
iv = os.urandom(AES.block_size)
single_block_text = b"SingleBlock Text"
print("Single Block Tests")
print("------------------")
print(f"iv: {iv.hex()}")
print(f"plain text: '{single_block_text.decode()}'")
ciphertext_block = _aes._encrypt_block(single_block_text)
plaintext_block = _aes._decrypt_block(ciphertext_block)
print(f"Ciphertext Hex: {ciphertext_block.hex()}")
print(f"Plaintext: {plaintext_block.decode()}")
assert plaintext_block == single_block_text
print(bcolors.OK + "Single Block Test Passed Successfully" + bcolors.RESET)
print()
# test a less than a block length phrase
iv = os.urandom(AES.block_size)
short_text = b"Just Text"
print("Short Text Tests")
print("----------------")
print(f"iv: {iv.hex()}")
print(f"plain text: '{short_text.decode()}'")
ciphertext_short = _aes.encrypt(short_text, iv)
plaintext_short = _aes.decrypt(ciphertext_short, iv)
print(f"Ciphertext Hex: {ciphertext_short.hex()}")
print(f"Plaintext: {plaintext_short.decode()}")
assert short_text == plaintext_short
print(bcolors.OK + "Short Text Test Passed Successfully" + bcolors.RESET)
print()
# test an arbitrary length phrase
iv = os.urandom(AES.block_size)
text = b"This Text is longer than one block"
print("Arbitrary Length Tests")
print("----------------------")
print(f"iv: {iv.hex()}")
print(f"plain text: '{text.decode()}'")
ciphertext = _aes.encrypt(text, iv)
plaintext = _aes.decrypt(ciphertext, iv)
print(f"Ciphertext Hex: {ciphertext.hex()}")
print(f"Plaintext: {plaintext.decode()}")
assert text == plaintext
print(bcolors.OK + "Arbitrary Length Text Test Passed Successfully" + bcolors.RESET)
print()
if __name__ == "__main__":
# test AES class
test()
+159
View File
@@ -0,0 +1,159 @@
# MIT License
# Copyright (c) 2021 Or Gur Arie
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
'''
Utils class for AES encryption / decryption
'''
## AES lookup tables
# resource: https://en.wikipedia.org/wiki/Rijndael_S-box
s_box = (
0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76,
0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0,
0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15,
0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75,
0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84,
0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF,
0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8,
0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2,
0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73,
0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB,
0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79,
0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08,
0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A,
0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E,
0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF,
0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16,
)
inv_s_box = (
0x52, 0x09, 0x6A, 0xD5, 0x30, 0x36, 0xA5, 0x38, 0xBF, 0x40, 0xA3, 0x9E, 0x81, 0xF3, 0xD7, 0xFB,
0x7C, 0xE3, 0x39, 0x82, 0x9B, 0x2F, 0xFF, 0x87, 0x34, 0x8E, 0x43, 0x44, 0xC4, 0xDE, 0xE9, 0xCB,
0x54, 0x7B, 0x94, 0x32, 0xA6, 0xC2, 0x23, 0x3D, 0xEE, 0x4C, 0x95, 0x0B, 0x42, 0xFA, 0xC3, 0x4E,
0x08, 0x2E, 0xA1, 0x66, 0x28, 0xD9, 0x24, 0xB2, 0x76, 0x5B, 0xA2, 0x49, 0x6D, 0x8B, 0xD1, 0x25,
0x72, 0xF8, 0xF6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xD4, 0xA4, 0x5C, 0xCC, 0x5D, 0x65, 0xB6, 0x92,
0x6C, 0x70, 0x48, 0x50, 0xFD, 0xED, 0xB9, 0xDA, 0x5E, 0x15, 0x46, 0x57, 0xA7, 0x8D, 0x9D, 0x84,
0x90, 0xD8, 0xAB, 0x00, 0x8C, 0xBC, 0xD3, 0x0A, 0xF7, 0xE4, 0x58, 0x05, 0xB8, 0xB3, 0x45, 0x06,
0xD0, 0x2C, 0x1E, 0x8F, 0xCA, 0x3F, 0x0F, 0x02, 0xC1, 0xAF, 0xBD, 0x03, 0x01, 0x13, 0x8A, 0x6B,
0x3A, 0x91, 0x11, 0x41, 0x4F, 0x67, 0xDC, 0xEA, 0x97, 0xF2, 0xCF, 0xCE, 0xF0, 0xB4, 0xE6, 0x73,
0x96, 0xAC, 0x74, 0x22, 0xE7, 0xAD, 0x35, 0x85, 0xE2, 0xF9, 0x37, 0xE8, 0x1C, 0x75, 0xDF, 0x6E,
0x47, 0xF1, 0x1A, 0x71, 0x1D, 0x29, 0xC5, 0x89, 0x6F, 0xB7, 0x62, 0x0E, 0xAA, 0x18, 0xBE, 0x1B,
0xFC, 0x56, 0x3E, 0x4B, 0xC6, 0xD2, 0x79, 0x20, 0x9A, 0xDB, 0xC0, 0xFE, 0x78, 0xCD, 0x5A, 0xF4,
0x1F, 0xDD, 0xA8, 0x33, 0x88, 0x07, 0xC7, 0x31, 0xB1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xEC, 0x5F,
0x60, 0x51, 0x7F, 0xA9, 0x19, 0xB5, 0x4A, 0x0D, 0x2D, 0xE5, 0x7A, 0x9F, 0x93, 0xC9, 0x9C, 0xEF,
0xA0, 0xE0, 0x3B, 0x4D, 0xAE, 0x2A, 0xF5, 0xB0, 0xC8, 0xEB, 0xBB, 0x3C, 0x83, 0x53, 0x99, 0x61,
0x17, 0x2B, 0x04, 0x7E, 0xBA, 0x77, 0xD6, 0x26, 0xE1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0C, 0x7D,
)
## AES AddRoundKey
# Round constants https://en.wikipedia.org/wiki/AES_key_schedule#Round_constants
r_con = (
0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40,
0x80, 0x1B, 0x36, 0x6C, 0xD8, 0xAB, 0x4D, 0x9A,
0x2F, 0x5E, 0xBC, 0x63, 0xC6, 0x97, 0x35, 0x6A,
0xD4, 0xB3, 0x7D, 0xFA, 0xEF, 0xC5, 0x91, 0x39,
)
def add_round_key(s, k):
for i in range(4):
for j in range(4):
s[i][j] ^= k[i][j]
## AES SubBytes
def sub_bytes(s):
for i in range(4):
for j in range(4):
s[i][j] = s_box[s[i][j]]
def inv_sub_bytes(s):
for i in range(4):
for j in range(4):
s[i][j] = inv_s_box[s[i][j]]
## AES ShiftRows
def shift_rows(s):
s[0][1], s[1][1], s[2][1], s[3][1] = s[1][1], s[2][1], s[3][1], s[0][1]
s[0][2], s[1][2], s[2][2], s[3][2] = s[2][2], s[3][2], s[0][2], s[1][2]
s[0][3], s[1][3], s[2][3], s[3][3] = s[3][3], s[0][3], s[1][3], s[2][3]
def inv_shift_rows(s):
s[0][1], s[1][1], s[2][1], s[3][1] = s[3][1], s[0][1], s[1][1], s[2][1]
s[0][2], s[1][2], s[2][2], s[3][2] = s[2][2], s[3][2], s[0][2], s[1][2]
s[0][3], s[1][3], s[2][3], s[3][3] = s[1][3], s[2][3], s[3][3], s[0][3]
## AES MixColumns
# learned from http://cs.ucsb.edu/~koc/cs178/projects/JT/aes.c
xtime = lambda a: (((a << 1) ^ 0x1B) & 0xFF) if (a & 0x80) else (a << 1)
def mix_single_column(a):
# see Sec 4.1.2 in The Design of Rijndael
t = a[0] ^ a[1] ^ a[2] ^ a[3]
u = a[0]
a[0] ^= t ^ xtime(a[0] ^ a[1])
a[1] ^= t ^ xtime(a[1] ^ a[2])
a[2] ^= t ^ xtime(a[2] ^ a[3])
a[3] ^= t ^ xtime(a[3] ^ u)
def mix_columns(s):
for i in range(4):
mix_single_column(s[i])
def inv_mix_columns(s):
# see Sec 4.1.3 in The Design of Rijndael
for i in range(4):
u = xtime(xtime(s[i][0] ^ s[i][2]))
v = xtime(xtime(s[i][1] ^ s[i][3]))
s[i][0] ^= u
s[i][1] ^= v
s[i][2] ^= u
s[i][3] ^= v
mix_columns(s)
## AES Bytes
def bytes2matrix(text):
""" Converts a 16-byte array into a 4x4 matrix. """
return [list(text[i:i+4]) for i in range(0, len(text), 4)]
def matrix2bytes(matrix):
""" Converts a 4x4 matrix into a 16-byte array. """
return bytes(sum(matrix, []))
def xor_bytes(a, b):
""" Returns a new byte array with the elements xor'ed. """
return bytes(i^j for i, j in zip(a, b))
def split_blocks(message, block_size=16, require_padding=True):
assert len(message) % block_size == 0 or not require_padding
return [message[i:i+16] for i in range(0, len(message), block_size)]
+58
View File
@@ -0,0 +1,58 @@
# MIT License
#
# Copyright (c) 2015 Brian Warner and other contributors
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
from . import eddsa
class BadSignatureError(Exception):
pass
SECRETKEYBYTES = 64
PUBLICKEYBYTES = 32
SIGNATUREKEYBYTES = 64
def publickey(seed32):
assert len(seed32) == 32
vk32 = eddsa.publickey(seed32)
return vk32, seed32+vk32
def sign(msg, skvk):
assert len(skvk) == 64
sk = skvk[:32]
vk = skvk[32:]
sig = eddsa.signature(msg, sk, vk)
return sig+msg
def open(sigmsg, vk):
assert len(vk) == 32
sig = sigmsg[:64]
msg = sigmsg[64:]
try:
valid = eddsa.checkvalid(sig, msg, vk)
except ValueError as e:
raise BadSignatureError(e)
except Exception as e:
if str(e) == "decoding point that is not on curve":
raise BadSignatureError(e)
raise
if not valid:
raise BadSignatureError()
return msg
+368
View File
@@ -0,0 +1,368 @@
# MIT License
#
# Copyright (c) 2015 Brian Warner and other contributors
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import binascii, hashlib, itertools
Q = 2**255 - 19
L = 2**252 + 27742317777372353535851937790883648493
def inv(x):
return pow(x, Q-2, Q)
d = -121665 * inv(121666)
I = pow(2,(Q-1)//4,Q)
def xrecover(y):
xx = (y*y-1) * inv(d*y*y+1)
x = pow(xx,(Q+3)//8,Q)
if (x*x - xx) % Q != 0: x = (x*I) % Q
if x % 2 != 0: x = Q-x
return x
By = 4 * inv(5)
Bx = xrecover(By)
B = [Bx % Q,By % Q]
# Extended Coordinates: x=X/Z, y=Y/Z, x*y=T/Z
# http://www.hyperelliptic.org/EFD/g1p/auto-twisted-extended-1.html
def xform_affine_to_extended(pt):
(x, y) = pt
return (x%Q, y%Q, 1, (x*y)%Q) # (X,Y,Z,T)
def xform_extended_to_affine(pt):
(x, y, z, _) = pt
return ((x*inv(z))%Q, (y*inv(z))%Q)
def double_element(pt): # extended->extended
# dbl-2008-hwcd
(X1, Y1, Z1, _) = pt
A = (X1*X1)
B = (Y1*Y1)
C = (2*Z1*Z1)
D = (-A) % Q
J = (X1+Y1) % Q
E = (J*J-A-B) % Q
G = (D+B) % Q
F = (G-C) % Q
H = (D-B) % Q
X3 = (E*F) % Q
Y3 = (G*H) % Q
Z3 = (F*G) % Q
T3 = (E*H) % Q
return (X3, Y3, Z3, T3)
def add_elements(pt1, pt2): # extended->extended
# add-2008-hwcd-3 . Slightly slower than add-2008-hwcd-4, but -3 is
# unified, so it's safe for general-purpose addition
(X1, Y1, Z1, T1) = pt1
(X2, Y2, Z2, T2) = pt2
A = ((Y1-X1)*(Y2-X2)) % Q
B = ((Y1+X1)*(Y2+X2)) % Q
C = T1*(2*d)*T2 % Q
D = Z1*2*Z2 % Q
E = (B-A) % Q
F = (D-C) % Q
G = (D+C) % Q
H = (B+A) % Q
X3 = (E*F) % Q
Y3 = (G*H) % Q
T3 = (E*H) % Q
Z3 = (F*G) % Q
return (X3, Y3, Z3, T3)
def scalarmult_element_safe_slow(pt, n):
# this form is slightly slower, but tolerates arbitrary points, including
# those which are not in the main 1*L subgroup. This includes points of
# order 1 (the neutral element Zero), 2, 4, and 8.
assert n >= 0
if n==0:
return xform_affine_to_extended((0,1))
_ = double_element(scalarmult_element_safe_slow(pt, n>>1))
return add_elements(_, pt) if n&1 else _
def _add_elements_nonunfied(pt1, pt2): # extended->extended
# add-2008-hwcd-4 : NOT unified, only for pt1!=pt2. About 10% faster than
# the (unified) add-2008-hwcd-3, and safe to use inside scalarmult if you
# aren't using points of order 1/2/4/8
(X1, Y1, Z1, T1) = pt1
(X2, Y2, Z2, T2) = pt2
A = ((Y1-X1)*(Y2+X2)) % Q
B = ((Y1+X1)*(Y2-X2)) % Q
C = (Z1*2*T2) % Q
D = (T1*2*Z2) % Q
E = (D+C) % Q
F = (B-A) % Q
G = (B+A) % Q
H = (D-C) % Q
X3 = (E*F) % Q
Y3 = (G*H) % Q
Z3 = (F*G) % Q
T3 = (E*H) % Q
return (X3, Y3, Z3, T3)
def scalarmult_element(pt, n): # extended->extended
# This form only works properly when given points that are a member of
# the main 1*L subgroup. It will give incorrect answers when called with
# the points of order 1/2/4/8, including point Zero. (it will also work
# properly when given points of order 2*L/4*L/8*L)
assert n >= 0
if n==0:
return xform_affine_to_extended((0,1))
_ = double_element(scalarmult_element(pt, n>>1))
return _add_elements_nonunfied(_, pt) if n&1 else _
# points are encoded as 32-bytes little-endian, b255 is sign, b2b1b0 are 0
def encodepoint(P):
x = P[0]
y = P[1]
# MSB of output equals x.b0 (=x&1)
# rest of output is little-endian y
assert 0 <= y < (1<<255) # always < 0x7fff..ff
if x & 1:
y += 1<<255
return binascii.unhexlify("%064x" % y)[::-1]
def isoncurve(P):
x = P[0]
y = P[1]
return (-x*x + y*y - 1 - d*x*x*y*y) % Q == 0
class NotOnCurve(Exception):
pass
def decodepoint(s):
unclamped = int(binascii.hexlify(s[:32][::-1]), 16)
clamp = (1 << 255) - 1
y = unclamped & clamp # clear MSB
x = xrecover(y)
if bool(x & 1) != bool(unclamped & (1<<255)): x = Q-x
P = [x,y]
if not isoncurve(P): raise NotOnCurve("decoding point that is not on curve")
return P
# scalars are encoded as 32-bytes little-endian
def bytes_to_scalar(s):
assert len(s) == 32, len(s)
return int(binascii.hexlify(s[::-1]), 16)
def bytes_to_clamped_scalar(s):
# Ed25519 private keys clamp the scalar to ensure two things:
# 1: integer value is in L/2 .. L, to avoid small-logarithm
# non-wraparaound
# 2: low-order 3 bits are zero, so a small-subgroup attack won't learn
# any information
# set the top two bits to 01, and the bottom three to 000
a_unclamped = bytes_to_scalar(s)
AND_CLAMP = (1<<254) - 1 - 7
OR_CLAMP = (1<<254)
a_clamped = (a_unclamped & AND_CLAMP) | OR_CLAMP
return a_clamped
def random_scalar(entropy_f): # 0..L-1 inclusive
# reduce the bias to a safe level by generating 256 extra bits
oversized = int(binascii.hexlify(entropy_f(32+32)), 16)
return oversized % L
def password_to_scalar(pw):
oversized = hashlib.sha512(pw).digest()
return int(binascii.hexlify(oversized), 16) % L
def scalar_to_bytes(y):
y = y % L
assert 0 <= y < 2**256
return binascii.unhexlify("%064x" % y)[::-1]
# Elements, of various orders
def is_extended_zero(XYTZ):
# catch Zero
(X, Y, Z, T) = XYTZ
Y = Y % Q
Z = Z % Q
if X==0 and Y==Z and Y!=0:
return True
return False
class ElementOfUnknownGroup:
# This is used for points of order 2,4,8,2*L,4*L,8*L
def __init__(self, XYTZ):
assert isinstance(XYTZ, tuple)
assert len(XYTZ) == 4
self.XYTZ = XYTZ
def add(self, other):
if not isinstance(other, ElementOfUnknownGroup):
raise TypeError("elements can only be added to other elements")
sum_XYTZ = add_elements(self.XYTZ, other.XYTZ)
if is_extended_zero(sum_XYTZ):
return Zero
return ElementOfUnknownGroup(sum_XYTZ)
def scalarmult(self, s):
if isinstance(s, ElementOfUnknownGroup):
raise TypeError("elements cannot be multiplied together")
assert s >= 0
product = scalarmult_element_safe_slow(self.XYTZ, s)
return ElementOfUnknownGroup(product)
def to_bytes(self):
return encodepoint(xform_extended_to_affine(self.XYTZ))
def __eq__(self, other):
return self.to_bytes() == other.to_bytes()
def __ne__(self, other):
return not self == other
class Element(ElementOfUnknownGroup):
# this only holds elements in the main 1*L subgroup. It never holds Zero,
# or elements of order 1/2/4/8, or 2*L/4*L/8*L.
def add(self, other):
if not isinstance(other, ElementOfUnknownGroup):
raise TypeError("elements can only be added to other elements")
sum_element = ElementOfUnknownGroup.add(self, other)
if sum_element is Zero:
return sum_element
if isinstance(other, Element):
# adding two subgroup elements results in another subgroup
# element, or Zero, and we've already excluded Zero
return Element(sum_element.XYTZ)
# not necessarily a subgroup member, so assume not
return sum_element
def scalarmult(self, s):
if isinstance(s, ElementOfUnknownGroup):
raise TypeError("elements cannot be multiplied together")
# scalarmult of subgroup members can be done modulo the subgroup
# order, and using the faster non-unified function.
s = s % L
# scalarmult(s=0) gets you Zero
if s == 0:
return Zero
# scalarmult(s=1) gets you self, which is a subgroup member
# scalarmult(s<grouporder) gets you a different subgroup member
return Element(scalarmult_element(self.XYTZ, s))
# negation and subtraction only make sense for the main subgroup
def negate(self):
# slow. Prefer e.scalarmult(-pw) to e.scalarmult(pw).negate()
return Element(scalarmult_element(self.XYTZ, L-2))
def subtract(self, other):
return self.add(other.negate())
class _ZeroElement(ElementOfUnknownGroup):
def add(self, other):
return other # zero+anything = anything
def scalarmult(self, s):
return self # zero*anything = zero
def negate(self):
return self # -zero = zero
def subtract(self, other):
return self.add(other.negate())
Base = Element(xform_affine_to_extended(B))
Zero = _ZeroElement(xform_affine_to_extended((0,1))) # the neutral (identity) element
_zero_bytes = Zero.to_bytes()
def arbitrary_element(seed): # unknown DL
# TODO: if we don't need uniformity, maybe use just sha256 here?
hseed = hashlib.sha512(seed).digest()
y = int(binascii.hexlify(hseed), 16) % Q
# we try successive Y values until we find a valid point
for plus in itertools.count(0):
y_plus = (y + plus) % Q
x = xrecover(y_plus)
Pa = [x,y_plus] # no attempt to use both "positive" and "negative" X
# only about 50% of Y coordinates map to valid curve points (I think
# the other half give you points on the "twist").
if not isoncurve(Pa):
continue
P = ElementOfUnknownGroup(xform_affine_to_extended(Pa))
# even if the point is on our curve, it may not be in our particular
# (order=L) subgroup. The curve has order 8*L, so an arbitrary point
# could have order 1,2,4,8,1*L,2*L,4*L,8*L (everything which divides
# the group order).
# [I MAY BE COMPLETELY WRONG ABOUT THIS, but my brief statistical
# tests suggest it's not too far off] There are phi(x) points with
# order x, so:
# 1 element of order 1: [(x=0,y=1)=Zero]
# 1 element of order 2 [(x=0,y=-1)]
# 2 elements of order 4
# 4 elements of order 8
# L-1 elements of order L (including Base)
# L-1 elements of order 2*L
# 2*(L-1) elements of order 4*L
# 4*(L-1) elements of order 8*L
# So 50% of random points will have order 8*L, 25% will have order
# 4*L, 13% order 2*L, and 13% will have our desired order 1*L (and a
# vanishingly small fraction will have 1/2/4/8). If we multiply any
# of the 8*L points by 2, we're sure to get an 4*L point (and
# multiplying a 4*L point by 2 gives us a 2*L point, and so on).
# Multiplying a 1*L point by 2 gives us a different 1*L point. So
# multiplying by 8 gets us from almost any point into a uniform point
# on the correct 1*L subgroup.
P8 = P.scalarmult(8)
# if we got really unlucky and picked one of the 8 low-order points,
# multiplying by 8 will get us to the identity (Zero), which we check
# for explicitly.
if is_extended_zero(P8.XYTZ):
continue
# Test that we're finally in the right group. We want to scalarmult
# by L, and we want to *not* use the trick in Group.scalarmult()
# which does x%L, because that would bypass the check we care about.
# P is still an _ElementOfUnknownGroup, which doesn't use x%L because
# that's not correct for points outside the main group.
assert is_extended_zero(P8.scalarmult(L).XYTZ)
return Element(P8.XYTZ)
# never reached
def bytes_to_unknown_group_element(bytes):
# this accepts all elements, including Zero and wrong-subgroup ones
if bytes == _zero_bytes:
return Zero
XYTZ = xform_affine_to_extended(decodepoint(bytes))
return ElementOfUnknownGroup(XYTZ)
def bytes_to_element(bytes):
# this strictly only accepts elements in the right subgroup
P = bytes_to_unknown_group_element(bytes)
if P is Zero:
raise ValueError("element was Zero")
if not is_extended_zero(P.scalarmult(L).XYTZ):
raise ValueError("element is not in the right group")
# the point is in the expected 1*L subgroup, not in the 2/4/8 groups,
# or in the 2*L/4*L/8*L groups. Promote it to a correct-group Element.
return Element(P.XYTZ)
+213
View File
@@ -0,0 +1,213 @@
# MIT License
#
# Copyright (c) 2015 Brian Warner and other contributors
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import os
import base64
from . import _ed25519
BadSignatureError = _ed25519.BadSignatureError
def create_keypair(entropy=os.urandom):
SEEDLEN = int(_ed25519.SECRETKEYBYTES/2)
assert SEEDLEN == 32
seed = entropy(SEEDLEN)
sk = SigningKey(seed)
vk = sk.get_verifying_key()
return sk, vk
class BadPrefixError(Exception):
pass
def remove_prefix(s_bytes, prefix):
assert(type(s_bytes) == type(prefix))
if s_bytes[:len(prefix)] != prefix:
raise BadPrefixError("did not see expected '%s' prefix" % (prefix,))
return s_bytes[len(prefix):]
def to_ascii(s_bytes, prefix="", encoding="base64"):
"""Return a version-prefixed ASCII representation of the given binary
string. 'encoding' indicates how to do the encoding, and can be one of:
* base64
* base32
* base16 (or hex)
This function handles bytes, not bits, so it does not append any trailing
'=' (unlike standard base64.b64encode). It also lowercases the base32
output.
'prefix' will be prepended to the encoded form, and is useful for
distinguishing the purpose and version of the binary string. E.g. you
could prepend 'pub0-' to a VerifyingKey string to allow the receiving
code to raise a useful error if someone pasted in a signature string by
mistake.
"""
assert isinstance(s_bytes, bytes)
if not isinstance(prefix, bytes):
prefix = prefix.encode('ascii')
if encoding == "base64":
s_ascii = base64.b64encode(s_bytes).decode('ascii').rstrip("=")
elif encoding == "base32":
s_ascii = base64.b32encode(s_bytes).decode('ascii').rstrip("=").lower()
elif encoding in ("base16", "hex"):
s_ascii = base64.b16encode(s_bytes).decode('ascii').lower()
else:
raise NotImplementedError
return prefix+s_ascii.encode('ascii')
def from_ascii(s_ascii, prefix="", encoding="base64"):
"""This is the opposite of to_ascii. It will throw BadPrefixError if
the prefix is not found.
"""
if isinstance(s_ascii, bytes):
s_ascii = s_ascii.decode('ascii')
if isinstance(prefix, bytes):
prefix = prefix.decode('ascii')
s_ascii = remove_prefix(s_ascii.strip(), prefix)
if encoding == "base64":
s_ascii += "="*((4 - len(s_ascii)%4)%4)
s_bytes = base64.b64decode(s_ascii)
elif encoding == "base32":
s_ascii += "="*((8 - len(s_ascii)%8)%8)
s_bytes = base64.b32decode(s_ascii.upper())
elif encoding in ("base16", "hex"):
s_bytes = base64.b16decode(s_ascii.upper())
else:
raise NotImplementedError
return s_bytes
class SigningKey(object):
# this can only be used to reconstruct a key created by create_keypair().
def __init__(self, sk_s, prefix="", encoding=None):
assert isinstance(sk_s, bytes)
if not isinstance(prefix, bytes):
prefix = prefix.encode('ascii')
sk_s = remove_prefix(sk_s, prefix)
if encoding is not None:
sk_s = from_ascii(sk_s, encoding=encoding)
if len(sk_s) == 32:
# create from seed
vk_s, sk_s = _ed25519.publickey(sk_s)
else:
if len(sk_s) != 32+32:
raise ValueError("SigningKey takes 32-byte seed or 64-byte string")
self.sk_s = sk_s # seed+pubkey
self.vk_s = sk_s[32:] # just pubkey
def to_bytes(self, prefix=""):
if not isinstance(prefix, bytes):
prefix = prefix.encode('ascii')
return prefix+self.sk_s
def to_ascii(self, prefix="", encoding=None):
assert encoding
if not isinstance(prefix, bytes):
prefix = prefix.encode('ascii')
return to_ascii(self.to_seed(), prefix, encoding)
def to_seed(self, prefix=""):
if not isinstance(prefix, bytes):
prefix = prefix.encode('ascii')
return prefix+self.sk_s[:32]
def __eq__(self, them):
if not isinstance(them, object): return False
return (them.__class__ == self.__class__
and them.sk_s == self.sk_s)
def get_verifying_key(self):
return VerifyingKey(self.vk_s)
def sign(self, msg, prefix="", encoding=None):
assert isinstance(msg, bytes)
if not isinstance(prefix, bytes):
prefix = prefix.encode('ascii')
sig_and_msg = _ed25519.sign(msg, self.sk_s)
# the response is R+S+msg
sig_R = sig_and_msg[0:32]
sig_S = sig_and_msg[32:64]
msg_out = sig_and_msg[64:]
sig_out = sig_R + sig_S
assert msg_out == msg
if encoding:
return to_ascii(sig_out, prefix, encoding)
return prefix+sig_out
class VerifyingKey(object):
def __init__(self, vk_s, prefix="", encoding=None):
if not isinstance(prefix, bytes):
prefix = prefix.encode('ascii')
if not isinstance(vk_s, bytes):
vk_s = vk_s.encode('ascii')
assert isinstance(vk_s, bytes)
vk_s = remove_prefix(vk_s, prefix)
if encoding is not None:
vk_s = from_ascii(vk_s, encoding=encoding)
assert len(vk_s) == 32
self.vk_s = vk_s
def to_bytes(self, prefix=""):
if not isinstance(prefix, bytes):
prefix = prefix.encode('ascii')
return prefix+self.vk_s
def to_ascii(self, prefix="", encoding=None):
assert encoding
if not isinstance(prefix, bytes):
prefix = prefix.encode('ascii')
return to_ascii(self.vk_s, prefix, encoding)
def __eq__(self, them):
if not isinstance(them, object): return False
return (them.__class__ == self.__class__
and them.vk_s == self.vk_s)
def verify(self, sig, msg, prefix="", encoding=None):
if not isinstance(sig, bytes):
sig = sig.encode('ascii')
if not isinstance(prefix, bytes):
prefix = prefix.encode('ascii')
assert isinstance(sig, bytes)
assert isinstance(msg, bytes)
if encoding:
sig = from_ascii(sig, prefix, encoding)
else:
sig = remove_prefix(sig, prefix)
assert len(sig) == 64
sig_R = sig[:32]
sig_S = sig[32:]
sig_and_msg = sig_R + sig_S + msg
# this might raise BadSignatureError
msg2 = _ed25519.open(sig_and_msg, self.vk_s)
assert msg2 == msg
def selftest():
message = b"crypto libraries should always test themselves at powerup"
sk = SigningKey(b"priv0-VIsfn5OFGa09Un2MR6Hm7BQ5++xhcQskU2OGXG8jSJl4cWLZrRrVcSN2gVYMGtZT+3354J5jfmqAcuRSD9KIyg",
prefix="priv0-", encoding="base64")
vk = VerifyingKey(b"pub0-eHFi2a0a1XEjdoFWDBrWU/t9+eCeY35qgHLkUg/SiMo",
prefix="pub0-", encoding="base64")
assert sk.get_verifying_key() == vk
sig = sk.sign(message, prefix="sig0-", encoding="base64")
assert sig == b"sig0-E/QrwtSF52x8+q0l4ahA7eJbRKc777ClKNg217Q0z4fiYMCdmAOI+rTLVkiFhX6k3D+wQQfKdJYMxaTUFfv1DQ", sig
vk.verify(sig, message, prefix="sig0-", encoding="base64")
selftest()
+94
View File
@@ -0,0 +1,94 @@
# MIT License
#
# Copyright (c) 2015 Brian Warner and other contributors
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
from RNS.Cryptography.Hashes import sha512
from .basic import (bytes_to_clamped_scalar,
bytes_to_scalar, scalar_to_bytes,
bytes_to_element, Base)
import hashlib, binascii
def H(m):
return sha512(m)
def publickey(seed):
# turn first half of SHA512(seed) into scalar, then into point
assert len(seed) == 32
a = bytes_to_clamped_scalar(H(seed)[:32])
A = Base.scalarmult(a)
return A.to_bytes()
def Hint(m):
h = H(m)
return int(binascii.hexlify(h[::-1]), 16)
def signature(m,sk,pk):
assert len(sk) == 32 # seed
assert len(pk) == 32
h = H(sk[:32])
a_bytes, inter = h[:32], h[32:]
a = bytes_to_clamped_scalar(a_bytes)
r = Hint(inter + m)
R = Base.scalarmult(r)
R_bytes = R.to_bytes()
S = r + Hint(R_bytes + pk + m) * a
return R_bytes + scalar_to_bytes(S)
def checkvalid(s, m, pk):
if len(s) != 64: raise Exception("signature length is wrong")
if len(pk) != 32: raise Exception("public-key length is wrong")
R = bytes_to_element(s[:32])
A = bytes_to_element(pk)
S = bytes_to_scalar(s[32:])
h = Hint(s[:32] + pk + m)
v1 = Base.scalarmult(S)
v2 = R.add(A.scalarmult(h))
return v1==v2
# wrappers
import os
def create_signing_key():
seed = os.urandom(32)
return seed
def create_verifying_key(signing_key):
return publickey(signing_key)
def sign(skbytes, msg):
"""Return just the signature, given the message and just the secret
key."""
if len(skbytes) != 32:
raise ValueError("Bad signing key length %d" % len(skbytes))
vkbytes = create_verifying_key(skbytes)
sig = signature(msg, skbytes, vkbytes)
return sig
def verify(vkbytes, sig, msg):
if len(vkbytes) != 32:
raise ValueError("Bad verifying key length %d" % len(vkbytes))
if len(sig) != 64:
raise ValueError("Bad signature length %d" % len(sig))
rc = checkvalid(sig, msg, vkbytes)
if not rc:
raise ValueError("rc != 0", rc)
return True
+30 -19
View File
@@ -20,14 +20,11 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import base64
import math
import time
import RNS
from cryptography.fernet import Fernet
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.backends import default_backend
from RNS.Cryptography import Fernet
class Callbacks:
def __init__(self):
@@ -97,10 +94,7 @@ class Destination:
name = Destination.full_name(app_name, *aspects)
# Create a digest for the destination
digest = hashes.Hash(hashes.SHA256(), backend=default_backend())
digest.update(name.encode("UTF-8"))
return digest.finalize()[:10]
return RNS.Identity.full_hash(name.encode("utf-8"))[:RNS.Reticulum.TRUNCATED_HASHLENGTH//8]
@staticmethod
def app_and_aspects_from_name(full_name):
@@ -124,6 +118,8 @@ class Destination:
if "." in app_name: raise ValueError("Dots can't be used in app names")
if not type in Destination.types: raise ValueError("Unknown destination type")
if not direction in Destination.directions: raise ValueError("Unknown destination direction")
self.accept_link_requests = True
self.callbacks = Callbacks()
self.request_handlers = {}
self.type = type
@@ -203,13 +199,27 @@ class Destination:
RNS.Packet(self, announce_data, RNS.Packet.ANNOUNCE, context = announce_context).send()
def accepts_links(self, accepts = None):
"""
Set or query whether the destination accepts incoming link requests.
:param accepts: If ``True`` or ``False``, this method sets whether the destination accepts incoming link requests. If not provided or ``None``, the method returns whether the destination currently accepts link requests.
:returns: ``True`` or ``False`` depending on whether the destination accepts incoming link requests, if the *accepts* parameter is not provided or ``None``.
"""
if accepts == None:
return self.accept_link_requests
if accepts:
self.accept_link_requests = True
else:
self.accept_link_requests = False
def set_link_established_callback(self, callback):
"""
Registers a function to be called when a link has been established to
this destination.
:param callback: A function or method to be called.
:param callback: A function or method with the signature *callback(link)* to be called when a new link is established with this destination.
"""
self.callbacks.link_established = callback
@@ -218,7 +228,7 @@ class Destination:
Registers a function to be called when a packet has been received by
this destination.
:param callback: A function or method to be called.
:param callback: A function or method with the signature *callback(data, packet)* to be called when this destination receives a packet.
"""
self.callbacks.packet = callback
@@ -228,7 +238,7 @@ class Destination:
a packet sent to this destination. Allows control over when and if
proofs should be returned for received packets.
:param callback: A function or method to be called. The callback must return one of True or False. If the callback returns True, a proof will be sent. If it returns False, a proof will not be sent.
:param callback: A function or method to with the signature *callback(packet)* be called when a packet that requests a proof is received. The callback must return one of True or False. If the callback returns True, a proof will be sent. If it returns False, a proof will not be sent.
"""
self.callbacks.proof_requested = callback
@@ -298,9 +308,10 @@ class Destination:
def incoming_link_request(self, data, packet):
link = RNS.Link.validate_request(self, data, packet)
if link != None:
self.links.append(link)
if self.accept_link_requests:
link = RNS.Link.validate_request(self, data, packet)
if link != None:
self.links.append(link)
def create_keys(self):
"""
@@ -315,8 +326,8 @@ class Destination:
raise TypeError("A single destination holds keys through an Identity instance")
if self.type == Destination.GROUP:
self.prv_bytes = base64.urlsafe_b64decode(Fernet.generate_key())
self.prv = Fernet(base64.urlsafe_b64encode(self.prv_bytes))
self.prv_bytes = Fernet.generate_key()
self.prv = Fernet(self.prv_bytes)
def get_private_key(self):
@@ -348,7 +359,7 @@ class Destination:
if self.type == Destination.GROUP:
self.prv_bytes = key
self.prv = Fernet(base64.urlsafe_b64encode(self.prv_bytes))
self.prv = Fernet(self.prv_bytes)
def load_public_key(self, key):
if self.type != Destination.SINGLE:
@@ -373,7 +384,7 @@ class Destination:
if self.type == Destination.GROUP:
if hasattr(self, "prv") and self.prv != None:
try:
return base64.urlsafe_b64decode(self.prv.encrypt(plaintext))
return self.prv.encrypt(plaintext)
except Exception as e:
RNS.log("The GROUP destination could not encrypt data", RNS.LOG_ERROR)
RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR)
@@ -398,7 +409,7 @@ class Destination:
if self.type == Destination.GROUP:
if hasattr(self, "prv") and self.prv != None:
try:
return self.prv.decrypt(base64.urlsafe_b64encode(ciphertext))
return self.prv.decrypt(ciphertext)
except Exception as e:
RNS.log("The GROUP destination could not decrypt data", RNS.LOG_ERROR)
RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR)
+34 -65
View File
@@ -20,23 +20,18 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import base64
import math
import os
import RNS
import time
import atexit
import base64
from .vendor import umsgpack as umsgpack
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey, Ed25519PublicKey
from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey, X25519PublicKey
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
from cryptography.fernet import Fernet
import hashlib
from .vendor import umsgpack as umsgpack
from RNS.Cryptography import X25519PrivateKey, X25519PublicKey, Ed25519PrivateKey, Ed25519PublicKey
from RNS.Cryptography import Fernet
cio_default_backend = default_backend()
class Identity:
"""
@@ -58,9 +53,7 @@ class Identity:
"""
# Non-configurable constants
FERNET_VERSION = 0x80
FERNET_OVERHEAD = 57 # In bytes
OPTIMISED_FERNET_OVERHEAD = 54 # In bytes
FERNET_OVERHEAD = RNS.Cryptography.Fernet.FERNET_OVERHEAD
AES128_BLOCKSIZE = 16 # In bytes
HASHLENGTH = 256 # In bits
SIGLENGTH = KEYSIZE # In bits
@@ -142,8 +135,14 @@ class Identity:
if os.path.isfile(RNS.Reticulum.storagepath+"/known_destinations"):
try:
file = open(RNS.Reticulum.storagepath+"/known_destinations","rb")
Identity.known_destinations = umsgpack.load(file)
loaded_known_destinations = umsgpack.load(file)
file.close()
Identity.known_destinations = {}
for known_destination in loaded_known_destinations:
if len(known_destination) == RNS.Reticulum.TRUNCATED_HASHLENGTH//8:
Identity.known_destinations[known_destination] = loaded_known_destinations[known_destination]
RNS.log("Loaded "+str(len(Identity.known_destinations))+" known destination from storage", RNS.LOG_VERBOSE)
except:
RNS.log("Error loading known destinations from disk, file will be recreated on exit", RNS.LOG_ERROR)
@@ -158,10 +157,7 @@ class Identity:
:param data: Data to be hashed as *bytes*.
:returns: SHA-256 hash as *bytes*
"""
digest = hashes.Hash(hashes.SHA256(), backend=default_backend())
digest.update(data)
return digest.finalize()
return RNS.Cryptography.sha256(data)
@staticmethod
def truncated_hash(data):
@@ -297,30 +293,16 @@ class Identity:
def create_keys(self):
self.prv = X25519PrivateKey.generate()
self.prv_bytes = self.prv.private_bytes(
encoding=serialization.Encoding.Raw,
format=serialization.PrivateFormat.Raw,
encryption_algorithm=serialization.NoEncryption()
)
self.prv_bytes = self.prv.private_bytes()
self.sig_prv = Ed25519PrivateKey.generate()
self.sig_prv_bytes = self.sig_prv.private_bytes(
encoding=serialization.Encoding.Raw,
format=serialization.PrivateFormat.Raw,
encryption_algorithm=serialization.NoEncryption()
)
self.sig_prv_bytes = self.sig_prv.private_bytes()
self.pub = self.prv.public_key()
self.pub_bytes = self.pub.public_bytes(
encoding=serialization.Encoding.Raw,
format=serialization.PublicFormat.Raw
)
self.pub_bytes = self.pub.public_bytes()
self.sig_pub = self.sig_prv.public_key()
self.sig_pub_bytes = self.sig_pub.public_bytes(
encoding=serialization.Encoding.Raw,
format=serialization.PublicFormat.Raw
)
self.sig_pub_bytes = self.sig_pub.public_bytes()
self.update_hashes()
@@ -352,16 +334,10 @@ class Identity:
self.sig_prv = Ed25519PrivateKey.from_private_bytes(self.sig_prv_bytes)
self.pub = self.prv.public_key()
self.pub_bytes = self.pub.public_bytes(
encoding=serialization.Encoding.Raw,
format=serialization.PublicFormat.Raw
)
self.pub_bytes = self.pub.public_bytes()
self.sig_pub = self.sig_prv.public_key()
self.sig_pub_bytes = self.sig_pub.public_bytes(
encoding=serialization.Encoding.Raw,
format=serialization.PublicFormat.Raw
)
self.sig_pub_bytes = self.sig_pub.public_bytes()
self.update_hashes()
@@ -421,24 +397,19 @@ class Identity:
"""
if self.pub != None:
ephemeral_key = X25519PrivateKey.generate()
ephemeral_pub_bytes = ephemeral_key.public_key().public_bytes(
encoding=serialization.Encoding.Raw,
format=serialization.PublicFormat.Raw
)
ephemeral_pub_bytes = ephemeral_key.public_key().public_bytes()
shared_key = ephemeral_key.exchange(self.pub)
# TODO: Improve this re-allocation of HKDF
derived_key = HKDF(
algorithm=hashes.SHA256(),
derived_key = RNS.Cryptography.hkdf(
length=32,
derive_from=shared_key,
salt=self.get_salt(),
info=self.get_context(),
backend=cio_default_backend,
).derive(shared_key)
context=self.get_context(),
)
fernet = Fernet(base64.urlsafe_b64encode(derived_key))
ciphertext = base64.urlsafe_b64decode(fernet.encrypt(plaintext))
fernet = Fernet(derived_key)
ciphertext = fernet.encrypt(plaintext)
token = ephemeral_pub_bytes+ciphertext
return token
@@ -463,18 +434,16 @@ class Identity:
shared_key = self.prv.exchange(peer_pub)
# TODO: Improve this re-allocation of HKDF
derived_key = HKDF(
algorithm=hashes.SHA256(),
derived_key = RNS.Cryptography.hkdf(
length=32,
derive_from=shared_key,
salt=self.get_salt(),
info=self.get_context(),
backend=cio_default_backend,
).derive(shared_key)
context=self.get_context(),
)
fernet = Fernet(base64.urlsafe_b64encode(derived_key))
fernet = Fernet(derived_key)
ciphertext = ciphertext_token[Identity.KEYSIZE//8//2:]
plaintext = fernet.decrypt(base64.urlsafe_b64encode(ciphertext))
plaintext = fernet.decrypt(ciphertext)
except Exception as e:
RNS.log("Decryption by "+RNS.prettyhexrep(self.hash)+" failed: "+str(e), RNS.LOG_DEBUG)
+3 -1
View File
@@ -79,6 +79,8 @@ class AX25KISSInterface(Interface):
self.rxb = 0
self.txb = 0
self.HW_MTU = 564
self.pyserial = serial
self.serial = None
@@ -304,7 +306,7 @@ class AX25KISSInterface(Interface):
in_frame = True
command = KISS.CMD_UNKNOWN
data_buffer = b""
elif (in_frame and len(data_buffer) < RNS.Reticulum.MTU+AX25.HEADER_SIZE):
elif (in_frame and len(data_buffer) < self.HW_MTU+AX25.HEADER_SIZE):
if (len(data_buffer) == 0 and command == KISS.CMD_UNKNOWN):
# We only support one HDLC port for now, so
# strip off the port nibble
+8 -1
View File
@@ -60,6 +60,9 @@ class AutoInterface(Interface):
self.netifaces = netifaces
self.rxb = 0
self.txb = 0
self.HW_MTU = 1064
self.IN = True
self.OUT = False
self.name = name
@@ -165,7 +168,8 @@ class AutoInterface(Interface):
# Set up multicast socket
discovery_socket = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
discovery_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
discovery_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
if hasattr(socket, "SO_REUSEPORT"):
discovery_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
discovery_socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_MULTICAST_IF, if_struct)
# Join multicast group
@@ -291,6 +295,8 @@ class AutoInterface(Interface):
ifis = struct.pack("I", socket.if_nametoindex(ifname))
announce_socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_MULTICAST_IF, ifis)
announce_socket.sendto(discovery_token, addr_info[0][4])
announce_socket.close()
except Exception as e:
if (ifname in self.timed_out_interfaces and self.timed_out_interfaces[ifname] == False) or not ifname in self.timed_out_interfaces:
RNS.log(str(self)+" Detected possible carrier loss on "+str(ifname)+": "+str(e), RNS.LOG_WARNING)
@@ -332,6 +338,7 @@ class AutoInterface(Interface):
peer_addr = str(peer)+"%"+str(self.peers[peer][0])
addr_info = socket.getaddrinfo(peer_addr, self.data_port, socket.AF_INET6, socket.SOCK_DGRAM)
self.outbound_udp_socket.sendto(data, addr_info[0][4])
except Exception as e:
RNS.log("Could not transmit on "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
+302 -45
View File
@@ -72,10 +72,12 @@ class I2PController:
self.client_tunnels = {}
self.server_tunnels = {}
self.i2plib_tunnels = {}
self.loop = None
self.i2plib = i2plib
self.utils = i2plib.utils
self.sam_address = i2plib.get_sam_address()
self.ready = False
self.storagepath = rns_storagepath+"/i2p"
if not os.path.isdir(self.storagepath):
@@ -85,17 +87,35 @@ class I2PController:
def start(self):
asyncio.set_event_loop(asyncio.new_event_loop())
self.loop = asyncio.get_event_loop()
time.sleep(0.10)
if self.loop == None:
RNS.log("Could not get event loop for "+str(self)+", waiting for event loop to appear", RNS.LOG_VERBOSE)
while self.loop == None:
self.loop = asyncio.get_event_loop()
sleep(0.25)
try:
self.ready = True
self.loop.run_forever()
except Exception as e:
self.ready = False
RNS.log("Exception on event loop for "+str(self)+": "+str(e), RNS.LOG_ERROR)
finally:
self.loop.close()
def stop(self):
for task in asyncio.Task.all_tasks(loop=self.loop):
task.cancel()
for i2ptunnel in self.i2plib_tunnels:
if hasattr(i2ptunnel, "stop") and callable(i2ptunnel.stop):
i2ptunnel.stop()
if hasattr(asyncio.Task, "all_tasks") and callable(asyncio.Task.all_tasks):
for task in asyncio.Task.all_tasks(loop=self.loop):
task.cancel()
time.sleep(0.2)
self.loop.stop()
@@ -104,8 +124,13 @@ class I2PController:
return self.i2plib.utils.get_free_port()
def stop_tunnel(self, i2ptunnel):
if hasattr(i2ptunnel, "stop") and callable(i2ptunnel.stop):
i2ptunnel.stop()
def client_tunnel(self, owner, i2p_destination):
self.client_tunnels[i2p_destination] = False
self.i2plib_tunnels[i2p_destination] = None
while True:
if not self.client_tunnels[i2p_destination]:
@@ -113,29 +138,138 @@ class I2PController:
async def tunnel_up():
RNS.log("Bringing up I2P tunnel to "+str(owner)+", this may take a while...", RNS.LOG_INFO)
tunnel = self.i2plib.ClientTunnel(i2p_destination, owner.local_addr, sam_address=self.sam_address, loop=self.loop)
self.i2plib_tunnels[i2p_destination] = tunnel
await tunnel.run()
owner.awaiting_i2p_tunnel = False
RNS.log(str(owner)+ " tunnel setup complete", RNS.LOG_VERBOSE)
try:
self.loop.ext_owner = self
future = asyncio.run_coroutine_threadsafe(tunnel_up(), self.loop).result()
self.client_tunnels[i2p_destination] = True
self.loop.ext_owner = self
result = asyncio.run_coroutine_threadsafe(tunnel_up(), self.loop).result()
if not i2p_destination in self.i2plib_tunnels:
raise IOError("No tunnel control instance was created")
except Exception as e:
RNS.log("Error while setting up I2P tunnel: "+str(e))
raise e
else:
tn = self.i2plib_tunnels[i2p_destination]
if tn != None and hasattr(tn, "status"):
RNS.log("Waiting for status from I2P control process", RNS.LOG_EXTREME)
while not tn.status["setup_ran"]:
time.sleep(0.1)
RNS.log("Got status from I2P control process", RNS.LOG_EXTREME)
if tn.status["setup_failed"]:
self.stop_tunnel(tn)
raise tn.status["exception"]
else:
self.client_tunnels[i2p_destination] = True
owner.awaiting_i2p_tunnel = False
if owner.socket != None:
if hasattr(owner.socket, "close"):
if callable(owner.socket.close):
try:
owner.socket.shutdown(socket.SHUT_RDWR)
except Exception as e:
RNS.log("Error while shutting down socket for "+str(owner)+": "+str(e))
try:
owner.socket.close()
except Exception as e:
RNS.log("Error while closing socket for "+str(owner)+": "+str(e))
RNS.log(str(owner)+" tunnel setup complete", RNS.LOG_VERBOSE)
else:
raise IOError("Got no status response from SAM API")
except ConnectionRefusedError as e:
raise e
except ConnectionAbortedError as e:
raise e
except Exception as e:
raise IOError("Could not connect to I2P SAM API while configuring to "+str(owner)+". Check that I2P is running and SAM is enabled.")
RNS.log("Unexpected error type from I2P SAM: "+str(e), RNS.LOG_ERROR)
raise e
else:
i2ptunnel = self.i2plib_tunnels[i2p_destination]
if hasattr(i2ptunnel, "status"):
i2p_exception = i2ptunnel.status["exception"]
if i2ptunnel.status["setup_ran"] == False:
RNS.log(str(self)+" I2P tunnel setup did not complete", RNS.LOG_ERROR)
self.stop_tunnel(i2ptunnel)
return False
elif i2p_exception != None:
RNS.log("An error ocurred while setting up I2P tunnel to "+str(i2p_destination), RNS.LOG_ERROR)
if isinstance(i2p_exception, RNS.vendor.i2plib.exceptions.CantReachPeer):
RNS.log("The I2P daemon can't reach peer "+str(i2p_destination), RNS.LOG_ERROR)
elif isinstance(i2p_exception, RNS.vendor.i2plib.exceptions.DuplicatedDest):
RNS.log("The I2P daemon reported that the destination is already in use", RNS.LOG_ERROR)
elif isinstance(i2p_exception, RNS.vendor.i2plib.exceptions.DuplicatedId):
RNS.log("The I2P daemon reported that the ID is arleady in use", RNS.LOG_ERROR)
elif isinstance(i2p_exception, RNS.vendor.i2plib.exceptions.InvalidId):
RNS.log("The I2P daemon reported that the stream session ID doesn't exist", RNS.LOG_ERROR)
elif isinstance(i2p_exception, RNS.vendor.i2plib.exceptions.InvalidKey):
RNS.log("The I2P daemon reported that the key for "+str(i2p_destination)+" is invalid", RNS.LOG_ERROR)
elif isinstance(i2p_exception, RNS.vendor.i2plib.exceptions.KeyNotFound):
RNS.log("The I2P daemon could not find the key for "+str(i2p_destination), RNS.LOG_ERROR)
elif isinstance(i2p_exception, RNS.vendor.i2plib.exceptions.PeerNotFound):
RNS.log("The I2P daemon mould not find the peer "+str(i2p_destination), RNS.LOG_ERROR)
elif isinstance(i2p_exception, RNS.vendor.i2plib.exceptions.I2PError):
RNS.log("The I2P daemon experienced an unspecified error", RNS.LOG_ERROR)
elif isinstance(i2p_exception, RNS.vendor.i2plib.exceptions.Timeout):
RNS.log("I2P daemon timed out while setting up client tunnel to "+str(i2p_destination), RNS.LOG_ERROR)
RNS.log("Resetting I2P tunnel and retrying later", RNS.LOG_ERROR)
self.stop_tunnel(i2ptunnel)
return False
elif i2ptunnel.status["setup_failed"] == True:
RNS.log(str(self)+" Unspecified I2P tunnel setup error, resetting I2P tunnel", RNS.LOG_ERROR)
self.stop_tunnel(i2ptunnel)
return False
else:
RNS.log(str(self)+" Got no status from SAM API, resetting I2P tunnel", RNS.LOG_ERROR)
self.stop_tunnel(i2ptunnel)
return False
# Wait for status from I2P control process
time.sleep(5)
def server_tunnel(self, owner):
i2p_dest_hash = RNS.Identity.full_hash(RNS.Identity.full_hash(owner.name.encode("utf-8")))
i2p_keyfile = self.storagepath+"/"+RNS.hexrep(i2p_dest_hash, delimit=False)+".i2p"
while RNS.Transport.identity == None:
time.sleep(1)
# Old format
i2p_dest_hash_of = RNS.Identity.full_hash(RNS.Identity.full_hash(owner.name.encode("utf-8")))
i2p_keyfile_of = self.storagepath+"/"+RNS.hexrep(i2p_dest_hash_of, delimit=False)+".i2p"
# New format
i2p_dest_hash_nf = RNS.Identity.full_hash(RNS.Identity.full_hash(owner.name.encode("utf-8"))+RNS.Identity.full_hash(RNS.Transport.identity.hash))
i2p_keyfile_nf = self.storagepath+"/"+RNS.hexrep(i2p_dest_hash_nf, delimit=False)+".i2p"
# Use old format if a key is already present
if os.path.isfile(i2p_keyfile_of):
i2p_keyfile = i2p_keyfile_of
else:
i2p_keyfile = i2p_keyfile_nf
i2p_dest = None
if not os.path.isfile(i2p_keyfile):
@@ -154,20 +288,82 @@ class I2PController:
owner.b32 = i2p_b32
self.server_tunnels[i2p_b32] = False
self.i2plib_tunnels[i2p_b32] = None
while self.server_tunnels[i2p_b32] == False:
try:
async def tunnel_up():
RNS.log(str(owner)+" Bringing up I2P endpoint, this may take a while...", RNS.LOG_INFO)
tunnel = self.i2plib.ServerTunnel((owner.bind_ip, owner.bind_port), loop=self.loop, destination=i2p_dest, sam_address=self.sam_address)
await tunnel.run()
RNS.log(str(owner)+ " endpoint setup complete. Now reachable at: "+str(i2p_dest.base32)+".b32.i2p", RNS.LOG_VERBOSE)
while True:
if self.server_tunnels[i2p_b32] == False:
try:
async def tunnel_up():
RNS.log(str(owner)+" Bringing up I2P endpoint, this may take a while...", RNS.LOG_INFO)
tunnel = self.i2plib.ServerTunnel((owner.bind_ip, owner.bind_port), loop=self.loop, destination=i2p_dest, sam_address=self.sam_address)
self.i2plib_tunnels[i2p_b32] = tunnel
await tunnel.run()
owner.online = True
RNS.log(str(owner)+ " endpoint setup complete. Now reachable at: "+str(i2p_dest.base32)+".b32.i2p", RNS.LOG_VERBOSE)
asyncio.run_coroutine_threadsafe(tunnel_up(), self.loop).result()
self.server_tunnels[i2p_b32] = True
asyncio.run_coroutine_threadsafe(tunnel_up(), self.loop).result()
self.server_tunnels[i2p_b32] = True
except Exception as e:
raise IOError("Could not connect to I2P SAM API while configuring "+str(self)+". Check that I2P is running and SAM is enabled.")
except Exception as e:
raise e
else:
i2ptunnel = self.i2plib_tunnels[i2p_b32]
if hasattr(i2ptunnel, "status"):
i2p_exception = i2ptunnel.status["exception"]
if i2ptunnel.status["setup_ran"] == False:
RNS.log(str(self)+" I2P tunnel setup did not complete", RNS.LOG_ERROR)
self.stop_tunnel(i2ptunnel)
return False
elif i2p_exception != None:
RNS.log("An error ocurred while setting up I2P tunnel", RNS.LOG_ERROR)
if isinstance(i2p_exception, RNS.vendor.i2plib.exceptions.CantReachPeer):
RNS.log("The I2P daemon can't reach peer "+str(i2p_destination), RNS.LOG_ERROR)
elif isinstance(i2p_exception, RNS.vendor.i2plib.exceptions.DuplicatedDest):
RNS.log("The I2P daemon reported that the destination is already in use", RNS.LOG_ERROR)
elif isinstance(i2p_exception, RNS.vendor.i2plib.exceptions.DuplicatedId):
RNS.log("The I2P daemon reported that the ID is arleady in use", RNS.LOG_ERROR)
elif isinstance(i2p_exception, RNS.vendor.i2plib.exceptions.InvalidId):
RNS.log("The I2P daemon reported that the stream session ID doesn't exist", RNS.LOG_ERROR)
elif isinstance(i2p_exception, RNS.vendor.i2plib.exceptions.InvalidKey):
RNS.log("The I2P daemon reported that the key for "+str(i2p_destination)+" is invalid", RNS.LOG_ERROR)
elif isinstance(i2p_exception, RNS.vendor.i2plib.exceptions.KeyNotFound):
RNS.log("The I2P daemon could not find the key for "+str(i2p_destination), RNS.LOG_ERROR)
elif isinstance(i2p_exception, RNS.vendor.i2plib.exceptions.PeerNotFound):
RNS.log("The I2P daemon mould not find the peer "+str(i2p_destination), RNS.LOG_ERROR)
elif isinstance(i2p_exception, RNS.vendor.i2plib.exceptions.I2PError):
RNS.log("The I2P daemon experienced an unspecified error", RNS.LOG_ERROR)
elif isinstance(i2p_exception, RNS.vendor.i2plib.exceptions.Timeout):
RNS.log("I2P daemon timed out while setting up client tunnel to "+str(i2p_destination), RNS.LOG_ERROR)
RNS.log("Resetting I2P tunnel and retrying later", RNS.LOG_ERROR)
self.stop_tunnel(i2ptunnel)
return False
elif i2ptunnel.status["setup_failed"] == True:
RNS.log(str(self)+" Unspecified I2P tunnel setup error, resetting I2P tunnel", RNS.LOG_ERROR)
self.stop_tunnel(i2ptunnel)
return False
else:
RNS.log(str(self)+" Got no status from SAM API, resetting I2P tunnel", RNS.LOG_ERROR)
self.stop_tunnel(i2ptunnel)
return False
time.sleep(5)
@@ -183,14 +379,16 @@ class I2PInterfacePeer(Interface):
RECONNECT_MAX_TRIES = None
# TCP socket options
I2P_USER_TIMEOUT = 40
I2P_USER_TIMEOUT = 45
I2P_PROBE_AFTER = 10
I2P_PROBE_INTERVAL = 5
I2P_PROBES = 6
I2P_PROBE_INTERVAL = 9
I2P_PROBES = 5
def __init__(self, parent_interface, owner, name, target_i2p_dest=None, connected_socket=None, max_reconnect_tries=None):
self.rxb = 0
self.txb = 0
self.HW_MTU = 1064
self.IN = True
self.OUT = False
@@ -212,6 +410,10 @@ class I2PInterfacePeer(Interface):
self.mode = RNS.Interfaces.Interface.Interface.MODE_FULL
self.bitrate = I2PInterface.BITRATE_GUESS
self.announce_rate_target = None
self.announce_rate_grace = None
self.announce_rate_penalty = None
if max_reconnect_tries == None:
self.max_reconnect_tries = I2PInterfacePeer.RECONNECT_MAX_TRIES
else:
@@ -233,15 +435,26 @@ class I2PInterfacePeer(Interface):
self.initiator = True
self.bind_ip = "127.0.0.1"
self.bind_port = self.parent_interface.i2p.get_free_port()
self.local_addr = (self.bind_ip, self.bind_port)
self.target_ip = self.bind_ip
self.target_port = self.bind_port
self.awaiting_i2p_tunnel = True
def tunnel_job():
self.parent_interface.i2p.client_tunnel(self, target_i2p_dest)
while self.awaiting_i2p_tunnel:
try:
self.bind_port = self.parent_interface.i2p.get_free_port()
self.local_addr = (self.bind_ip, self.bind_port)
self.target_ip = self.bind_ip
self.target_port = self.bind_port
if not self.parent_interface.i2p.client_tunnel(self, target_i2p_dest):
RNS.log(str(self)+" I2P control process experienced an error, requesting new tunnel...", RNS.LOG_ERROR)
self.awaiting_i2p_tunnel = True
except Exception as e:
RNS.log("Error while while configuring "+str(self)+": "+str(e), RNS.LOG_ERROR)
RNS.log("Check that I2P is installed and running, and that SAM is enabled. Retrying tunnel setup later.", RNS.LOG_ERROR)
time.sleep(15)
thread = threading.Thread(target=tunnel_job)
thread.setDaemon(True)
@@ -294,12 +507,25 @@ class I2PInterfacePeer(Interface):
self.socket.setsockopt(socket.IPPROTO_TCP, TCP_KEEPIDLE, int(I2PInterfacePeer.TCP_PROBE_AFTER))
else:
self.socket.setsockopt(socket.IPPROTO_TCP, TCP_KEEPIDLE, int(I2PInterfacePeer.I2P_PROBE_AFTER))
def shutdown_socket(self, socket):
if callable(socket.close):
try:
socket.shutdown(socket.SHUT_RDWR)
except Exception as e:
RNS.log("Error while shutting down socket for "+str(self)+": "+str(e))
try:
socket.close()
except Exception as e:
RNS.log("Error while closing socket for "+str(self)+": "+str(e))
def detach(self):
RNS.log("Detaching "+str(self), RNS.LOG_DEBUG)
if self.socket != None:
if hasattr(self.socket, "close"):
if callable(self.socket.close):
RNS.log("Detaching "+str(self), RNS.LOG_DEBUG)
self.detached = True
try:
@@ -393,7 +619,7 @@ class I2PInterfacePeer(Interface):
def processOutgoing(self, data):
if self.online:
while self.writing:
time.sleep(0.01)
time.sleep(0.001)
try:
self.writing = True
@@ -406,6 +632,7 @@ class I2PInterfacePeer(Interface):
self.socket.sendall(data)
self.writing = False
self.txb += len(data)
if hasattr(self, "parent_interface") and self.parent_interface != None and self.parent_count:
self.parent_interface.txb += len(data)
@@ -439,7 +666,7 @@ class I2PInterfacePeer(Interface):
in_frame = True
command = KISS.CMD_UNKNOWN
data_buffer = b""
elif (in_frame and len(data_buffer) < RNS.Reticulum.MTU):
elif (in_frame and len(data_buffer) < self.HW_MTU):
if (len(data_buffer) == 0 and command == KISS.CMD_UNKNOWN):
# We only support one HDLC port for now, so
# strip off the port nibble
@@ -465,7 +692,7 @@ class I2PInterfacePeer(Interface):
elif (byte == HDLC.FLAG):
in_frame = True
data_buffer = b""
elif (in_frame and len(data_buffer) < RNS.Reticulum.MTU):
elif (in_frame and len(data_buffer) < self.HW_MTU):
if (byte == HDLC.ESC):
escape = True
else:
@@ -512,7 +739,8 @@ class I2PInterfacePeer(Interface):
self.IN = False
if hasattr(self, "parent_interface") and self.parent_interface != None:
self.parent_interface.clients -= 1
if self.parent_interface.clients > 0:
self.parent_interface.clients -= 1
if self in RNS.Transport.interfaces:
if not self.initiator:
@@ -526,9 +754,12 @@ class I2PInterfacePeer(Interface):
class I2PInterface(Interface):
BITRATE_GUESS = 256*1000
def __init__(self, owner, name, rns_storagepath, peers, connectable = True):
def __init__(self, owner, name, rns_storagepath, peers, connectable = False):
self.rxb = 0
self.txb = 0
self.HW_MTU = 1064
self.online = False
self.clients = 0
self.owner = owner
@@ -550,10 +781,25 @@ class I2PInterface(Interface):
self.address = (self.bind_ip, self.bind_port)
self.bitrate = I2PInterface.BITRATE_GUESS
self.online = False
i2p_thread = threading.Thread(target=self.i2p.start)
i2p_thread.setDaemon(True)
i2p_thread.start()
i2p_notready_warning = False
time.sleep(0.25)
if not self.i2p.ready:
RNS.log("I2P controller did not become available in time, waiting for controller", RNS.LOG_VERBOSE)
i2p_notready_warning = True
while not self.i2p.ready:
time.sleep(0.25)
if i2p_notready_warning == True:
RNS.log("I2P controller ready, continuing setup", RNS.LOG_VERBOSE)
def handlerFactory(callback):
def createHandler(*args, **keys):
return I2PInterfaceHandler(callback, *args, **keys)
@@ -568,7 +814,18 @@ class I2PInterface(Interface):
if self.connectable:
def tunnel_job():
self.i2p.server_tunnel(self)
while True:
try:
if not self.i2p.server_tunnel(self):
RNS.log(str(self)+" I2P control process experienced an error, requesting new tunnel...", RNS.LOG_ERROR)
self.online = False
except Exception as e:
RNS.log("Error while while configuring "+str(self)+": "+str(e), RNS.LOG_ERROR)
RNS.log("Check that I2P is installed and running, and that SAM is enabled. Retrying tunnel setup later.", RNS.LOG_ERROR)
time.sleep(15)
thread = threading.Thread(target=tunnel_job)
thread.setDaemon(True)
@@ -576,7 +833,7 @@ class I2PInterface(Interface):
if peers != None:
for peer_addr in peers:
interface_name = peer_addr
interface_name = self.name+" to "+peer_addr
peer_interface = I2PInterfacePeer(self, self.owner, interface_name, peer_addr)
peer_interface.OUT = True
peer_interface.IN = True
@@ -584,9 +841,6 @@ class I2PInterface(Interface):
peer_interface.parent_count = False
RNS.Transport.interfaces.append(peer_interface)
self.online = True
def incoming_connection(self, handler):
RNS.log("Accepting incoming I2P connection", RNS.LOG_VERBOSE)
interface_name = "Connected peer on "+self.name
@@ -602,6 +856,8 @@ class I2PInterface(Interface):
spawned_interface.announce_rate_target = self.announce_rate_target
spawned_interface.announce_rate_grace = self.announce_rate_grace
spawned_interface.announce_rate_penalty = self.announce_rate_penalty
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)
self.clients += 1
@@ -611,6 +867,7 @@ class I2PInterface(Interface):
pass
def detach(self):
RNS.log("Detaching "+str(self), RNS.LOG_DEBUG)
self.i2p.stop()
def __str__(self):
+6
View File
@@ -31,11 +31,17 @@ class Interface:
RPT = False
name = None
# Interface mode definitions
MODE_FULL = 0x01
MODE_POINT_TO_POINT = 0x02
MODE_ACCESS_POINT = 0x03
MODE_ROAMING = 0x04
MODE_BOUNDARY = 0x05
MODE_GATEWAY = 0x06
# Which interface modes a Transport Node
# should actively discover paths for.
DISCOVER_PATHS_FOR = [MODE_ACCESS_POINT, MODE_GATEWAY]
def __init__(self):
self.rxb = 0
+3 -1
View File
@@ -73,6 +73,8 @@ class KISSInterface(Interface):
self.rxb = 0
self.txb = 0
self.HW_MTU = 564
if beacon_data == None:
beacon_data = ""
@@ -279,7 +281,7 @@ class KISSInterface(Interface):
in_frame = True
command = KISS.CMD_UNKNOWN
data_buffer = b""
elif (in_frame and len(data_buffer) < RNS.Reticulum.MTU):
elif (in_frame and len(data_buffer) < self.HW_MTU):
if (len(data_buffer) == 0 and command == KISS.CMD_UNKNOWN):
# We only support one HDLC port for now, so
# strip off the port nibble
+15 -6
View File
@@ -49,6 +49,12 @@ class LocalClientInterface(Interface):
def __init__(self, owner, name, target_port = None, connected_socket=None):
self.rxb = 0
self.txb = 0
# TODO: Remove at some point
# self.rxptime = 0
self.HW_MTU = 1064
self.online = False
self.IN = True
@@ -117,7 +123,7 @@ class LocalClientInterface(Interface):
RNS.log("Connection attempt for "+str(self)+" failed: "+str(e), RNS.LOG_DEBUG)
if not self.never_connected:
RNS.log("Reconnected TCP socket for "+str(self)+".", RNS.LOG_INFO)
RNS.log("Reconnected socket for "+str(self)+".", RNS.LOG_INFO)
self.reconnecting = False
thread = threading.Thread(target=self.read_loop)
@@ -134,15 +140,18 @@ class LocalClientInterface(Interface):
self.rxb += len(data)
if hasattr(self, "parent_interface") and self.parent_interface != None:
self.parent_interface.rxb += len(data)
# TODO: Remove at some point
# processing_start = time.time()
self.owner.inbound(data, self)
# TODO: Remove at some point
# duration = time.time() - processing_start
# self.rxptime += duration
def processOutgoing(self, data):
if self.online:
while self.writing:
time.sleep(0.01)
try:
self.writing = True
data = bytes([HDLC.FLAG])+HDLC.escape(data)+bytes([HDLC.FLAG])
@@ -177,7 +186,7 @@ class LocalClientInterface(Interface):
elif (byte == HDLC.FLAG):
in_frame = True
data_buffer = b""
elif (in_frame and len(data_buffer) < RNS.Reticulum.MTU):
elif (in_frame and len(data_buffer) < self.HW_MTU):
if (byte == HDLC.ESC):
escape = True
else:
+3 -1
View File
@@ -56,6 +56,8 @@ class PipeInterface(Interface):
self.rxb = 0
self.txb = 0
self.HW_MTU = 1064
self.owner = owner
self.name = name
@@ -137,7 +139,7 @@ class PipeInterface(Interface):
elif (byte == HDLC.FLAG):
in_frame = True
data_buffer = b""
elif (in_frame and len(data_buffer) < RNS.Reticulum.MTU):
elif (in_frame and len(data_buffer) < self.HW_MTU):
if (byte == HDLC.ESC):
escape = True
else:
+4 -2
View File
@@ -111,6 +111,8 @@ class RNodeInterface(Interface):
self.rxb = 0
self.txb = 0
self.HW_MTU = 508
self.pyserial = serial
self.serial = None
@@ -374,7 +376,7 @@ class RNodeInterface(Interface):
self.bitrate = 0
def processIncoming(self, data):
self.rxb += len(data)
self.rxb += len(data)
self.owner.inbound(data, self)
self.r_stat_rssi = None
self.r_stat_snr = None
@@ -439,7 +441,7 @@ class RNodeInterface(Interface):
command = KISS.CMD_UNKNOWN
data_buffer = b""
command_buffer = b""
elif (in_frame and len(data_buffer) < RNS.Reticulum.MTU):
elif (in_frame and len(data_buffer) < self.HW_MTU):
if (len(data_buffer) == 0 and command == KISS.CMD_UNKNOWN):
command = byte
elif (command == KISS.CMD_DATA):
+3 -1
View File
@@ -62,6 +62,8 @@ class SerialInterface(Interface):
self.rxb = 0
self.txb = 0
self.HW_MTU = 564
self.pyserial = serial
self.serial = None
@@ -152,7 +154,7 @@ class SerialInterface(Interface):
elif (byte == HDLC.FLAG):
in_frame = True
data_buffer = b""
elif (in_frame and len(data_buffer) < RNS.Reticulum.MTU):
elif (in_frame and len(data_buffer) < self.HW_MTU):
if (byte == HDLC.ESC):
escape = True
else:
+21 -13
View File
@@ -65,20 +65,22 @@ class TCPClientInterface(Interface):
RECONNECT_MAX_TRIES = None
# TCP socket options
TCP_USER_TIMEOUT = 20
TCP_USER_TIMEOUT = 24
TCP_PROBE_AFTER = 5
TCP_PROBE_INTERVAL = 3
TCP_PROBES = 5
TCP_PROBE_INTERVAL = 2
TCP_PROBES = 12
I2P_USER_TIMEOUT = 40
I2P_USER_TIMEOUT = 45
I2P_PROBE_AFTER = 10
I2P_PROBE_INTERVAL = 5
I2P_PROBES = 6
I2P_PROBE_INTERVAL = 9
I2P_PROBES = 5
def __init__(self, owner, name, target_ip=None, target_port=None, connected_socket=None, max_reconnect_tries=None, kiss_framing=False, i2p_tunneled = False):
self.rxb = 0
self.txb = 0
self.HW_MTU = 1064
self.IN = True
self.OUT = False
self.socket = None
@@ -137,6 +139,7 @@ class TCPClientInterface(Interface):
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, int(TCPClientInterface.TCP_PROBE_AFTER))
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, int(TCPClientInterface.TCP_PROBE_INTERVAL))
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, int(TCPClientInterface.TCP_PROBES))
else:
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_USER_TIMEOUT, int(TCPClientInterface.I2P_USER_TIMEOUT * 1000))
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
@@ -224,7 +227,7 @@ class TCPClientInterface(Interface):
RNS.log("Connection attempt for "+str(self)+" failed: "+str(e), RNS.LOG_DEBUG)
if not self.never_connected:
RNS.log("Reconnected TCP socket for "+str(self)+".", RNS.LOG_INFO)
RNS.log("Reconnected socket for "+str(self)+".", RNS.LOG_INFO)
self.reconnecting = False
thread = threading.Thread(target=self.read_loop)
@@ -246,8 +249,8 @@ class TCPClientInterface(Interface):
def processOutgoing(self, data):
if self.online:
while self.writing:
time.sleep(0.01)
# while self.writing:
# time.sleep(0.01)
try:
self.writing = True
@@ -293,7 +296,7 @@ class TCPClientInterface(Interface):
in_frame = True
command = KISS.CMD_UNKNOWN
data_buffer = b""
elif (in_frame and len(data_buffer) < RNS.Reticulum.MTU):
elif (in_frame and len(data_buffer) < self.HW_MTU):
if (len(data_buffer) == 0 and command == KISS.CMD_UNKNOWN):
# We only support one HDLC port for now, so
# strip off the port nibble
@@ -319,7 +322,7 @@ class TCPClientInterface(Interface):
elif (byte == HDLC.FLAG):
in_frame = True
data_buffer = b""
elif (in_frame and len(data_buffer) < RNS.Reticulum.MTU):
elif (in_frame and len(data_buffer) < self.HW_MTU):
if (byte == HDLC.ESC):
escape = True
else:
@@ -333,10 +336,10 @@ class TCPClientInterface(Interface):
else:
self.online = False
if self.initiator and not self.detached:
RNS.log("TCP socket for "+str(self)+" was closed, attempting to reconnect...", RNS.LOG_WARNING)
RNS.log("The socket for "+str(self)+" was closed, attempting to reconnect...", RNS.LOG_WARNING)
self.reconnect()
else:
RNS.log("TCP socket for remote client "+str(self)+" was closed.", RNS.LOG_VERBOSE)
RNS.log("The socket for remote client "+str(self)+" was closed.", RNS.LOG_VERBOSE)
self.teardown()
break
@@ -405,6 +408,9 @@ class TCPServerInterface(Interface):
def __init__(self, owner, name, device=None, bindip=None, bindport=None, i2p_tunneled=False):
self.rxb = 0
self.txb = 0
self.HW_MTU = 1064
self.online = False
self.clients = 0
@@ -459,6 +465,8 @@ class TCPServerInterface(Interface):
spawned_interface.announce_rate_target = self.announce_rate_target
spawned_interface.announce_rate_grace = self.announce_rate_grace
spawned_interface.announce_rate_penalty = self.announce_rate_penalty
spawned_interface.mode = self.mode
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)
+3
View File
@@ -57,6 +57,9 @@ class UDPInterface(Interface):
def __init__(self, owner, name, device=None, bindip=None, bindport=None, forwardip=None, forwardport=None):
self.rxb = 0
self.txb = 0
self.HW_MTU = 1064
self.IN = True
self.OUT = False
self.name = name
+75 -60
View File
@@ -20,24 +20,16 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey, Ed25519PublicKey
from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey, X25519PublicKey
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
from cryptography.fernet import Fernet
from RNS.Cryptography import X25519PrivateKey, X25519PublicKey, Ed25519PrivateKey, Ed25519PublicKey
from RNS.Cryptography import Fernet
from time import sleep
from .vendor import umsgpack as umsgpack
import threading
import base64
import math
import time
import RNS
import traceback
cio_default_backend = default_backend()
class LinkCallbacks:
def __init__(self):
@@ -67,20 +59,34 @@ class Link:
ECPUBSIZE = 32+32
KEYSIZE = 32
MDU = math.floor((RNS.Reticulum.MTU-RNS.Reticulum.IFAC_MIN_SIZE-RNS.Reticulum.HEADER_MINSIZE-RNS.Identity.OPTIMISED_FERNET_OVERHEAD)/RNS.Identity.AES128_BLOCKSIZE)*RNS.Identity.AES128_BLOCKSIZE - 1
MDU = math.floor((RNS.Reticulum.MTU-RNS.Reticulum.IFAC_MIN_SIZE-RNS.Reticulum.HEADER_MINSIZE-RNS.Identity.FERNET_OVERHEAD)/RNS.Identity.AES128_BLOCKSIZE)*RNS.Identity.AES128_BLOCKSIZE - 1
ESTABLISHMENT_TIMEOUT_PER_HOP = RNS.Reticulum.DEFAULT_PER_HOP_TIMEOUT
"""
Default timeout for link establishment in seconds per hop to destination.
Timeout for link establishment in seconds per hop to destination.
"""
TRAFFIC_TIMEOUT_FACTOR = 6
KEEPALIVE_TIMEOUT_FACTOR = 4
"""
RTT timeout factor used in link timeout calculation.
"""
STALE_GRACE = 2
"""
Grace period in seconds used in link timeout calculation.
"""
KEEPALIVE = 360
"""
Interval for sending keep-alive packets on established links in seconds.
"""
STALE_TIME = 2*KEEPALIVE
"""
If no traffic or keep-alive packets are received within this period, the
link will be marked as stale, and a final keep-alive packet will be sent.
If after this no traffic or keep-alive packets are received within ``RTT`` *
``KEEPALIVE_TIMEOUT_FACTOR`` + ``STALE_GRACE``, the link is considered timed out,
and will be torn down.
"""
PENDING = 0x00
HANDSHAKE = 0x01
@@ -105,6 +111,7 @@ class Link:
link.set_link_id(packet)
link.destination = packet.destination
link.establishment_timeout = Link.ESTABLISHMENT_TIMEOUT_PER_HOP * max(1, packet.hops)
link.establishment_cost += len(packet.raw)
RNS.log("Validating link request "+RNS.prettyhexrep(link.link_id), RNS.LOG_VERBOSE)
link.handshake()
link.attached_interface = packet.receiving_interface
@@ -131,6 +138,7 @@ class Link:
if destination != None and destination.type != RNS.Destination.SINGLE:
raise TypeError("Links can only be established to the \"single\" destination type")
self.rtt = None
self.establishment_cost = 0
self.callbacks = LinkCallbacks()
self.resource_strategy = Link.ACCEPT_NONE
self.outgoing_resources = []
@@ -145,6 +153,7 @@ class Link:
self.traffic_timeout_factor = Link.TRAFFIC_TIMEOUT_FACTOR
self.keepalive_timeout_factor = Link.KEEPALIVE_TIMEOUT_FACTOR
self.keepalive = Link.KEEPALIVE
self.stale_time = Link.STALE_TIME
self.watchdog_lock = False
self.status = Link.PENDING
self.activated_at = None
@@ -166,16 +175,10 @@ class Link:
self.fernet = None
self.pub = self.prv.public_key()
self.pub_bytes = self.pub.public_bytes(
encoding=serialization.Encoding.Raw,
format=serialization.PublicFormat.Raw
)
self.pub_bytes = self.pub.public_bytes()
self.sig_pub = self.sig_prv.public_key()
self.sig_pub_bytes = self.sig_pub.public_bytes(
encoding=serialization.Encoding.Raw,
format=serialization.PublicFormat.Raw
)
self.sig_pub_bytes = self.sig_pub.public_bytes()
if peer_pub_bytes == None:
self.peer_pub = None
@@ -195,6 +198,7 @@ class Link:
self.request_data = self.pub_bytes+self.sig_pub_bytes
self.packet = RNS.Packet(destination, self.request_data, packet_type=RNS.Packet.LINKREQUEST)
self.packet.pack()
self.establishment_cost += len(self.packet.raw)
self.set_link_id(self.packet)
self.load_peer(peer_pub_bytes, peer_sig_pub_bytes)
self.handshake()
@@ -224,14 +228,13 @@ class Link:
self.status = Link.HANDSHAKE
self.shared_key = self.prv.exchange(self.peer_pub)
# TODO: Improve this re-allocation of HKDF
self.derived_key = HKDF(
algorithm=hashes.SHA256(),
self.derived_key = RNS.Cryptography.hkdf(
length=32,
derive_from=self.shared_key,
salt=self.get_salt(),
info=self.get_context(),
backend=cio_default_backend,
).derive(self.shared_key)
context=self.get_context(),
)
def prove(self):
signed_data = self.link_id+self.pub_bytes+self.sig_pub_bytes
@@ -240,6 +243,7 @@ class Link:
proof_data = signature
proof = RNS.Packet(self, proof_data, packet_type=RNS.Packet.PROOF, context=RNS.Packet.LRPROOF)
proof.send()
self.establishment_cost += len(proof.raw)
self.had_outbound()
@@ -259,6 +263,7 @@ class Link:
def validate_proof(self, packet):
if self.status == Link.HANDSHAKE:
if self.initiator and len(packet.data) == RNS.Identity.SIGLENGTH//8:
self.establishment_cost += len(packet.raw)
signed_data = self.link_id+self.peer_pub_bytes+self.peer_sig_pub_bytes
signature = packet.data[:RNS.Identity.SIGLENGTH//8]
@@ -334,7 +339,8 @@ class Link:
response_callback = response_callback,
failed_callback = failed_callback,
progress_callback = progress_callback,
timeout = timeout
timeout = timeout,
request_size = len(packed_request),
)
else:
@@ -348,7 +354,8 @@ class Link:
response_callback = response_callback,
failed_callback = failed_callback,
progress_callback = progress_callback,
timeout = timeout
timeout = timeout,
request_size = len(packed_request),
)
@@ -383,7 +390,9 @@ class Link:
"""
:returns: The time in seconds since last inbound packet on the link.
"""
return time.time() - self.last_inbound
activated_at = self.activated_at if self.activated_at != None else 0
last_inbound = max(self.last_inbound, activated_at)
return time.time() - last_inbound
def no_outbound_for(self):
"""
@@ -497,13 +506,21 @@ class Link:
sleep_time = 0.001
elif self.status == Link.ACTIVE:
if time.time() >= self.last_inbound + self.keepalive:
sleep_time = self.rtt * self.keepalive_timeout_factor + Link.STALE_GRACE
self.status = Link.STALE
activated_at = self.activated_at if self.activated_at != None else 0
last_inbound = max(self.last_inbound, activated_at)
if time.time() >= last_inbound + self.keepalive:
if self.initiator:
self.send_keepalive()
if time.time() >= last_inbound + self.stale_time:
sleep_time = self.rtt * self.keepalive_timeout_factor + Link.STALE_GRACE
self.status = Link.STALE
else:
sleep_time = self.keepalive
else:
sleep_time = (self.last_inbound + self.keepalive) - time.time()
sleep_time = (last_inbound + self.keepalive) - time.time()
elif self.status == Link.STALE:
sleep_time = 0.001
@@ -543,7 +560,7 @@ class Link:
allowed = False
if not allow == RNS.Destination.ALLOW_NONE:
if allow == RNS.Destination.ALLOW_LIST:
if self.__remote_identity.hash in allowed_list:
if self.__remote_identity != None and self.__remote_identity.hash in allowed_list:
allowed = True
elif allow == RNS.Destination.ALLOW_ALL:
allowed = True
@@ -650,7 +667,7 @@ class Link:
self.__remote_identity = identity
if self.callbacks.remote_identified != None:
try:
self.callbacks.remote_identified(self.__remote_identity)
self.callbacks.remote_identified(self, self.__remote_identity)
except Exception as e:
RNS.log("Error while executing remote identified callback from "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
@@ -687,19 +704,21 @@ class Link:
if RNS.ResourceAdvertisement.is_request(packet):
RNS.Resource.accept(packet, callback=self.request_resource_concluded)
elif RNS.ResourceAdvertisement.is_response(packet):
request_id = RNS.ResourceAdvertisement.get_request_id(packet)
request_id = RNS.ResourceAdvertisement.read_request_id(packet)
for pending_request in self.pending_requests:
if pending_request.request_id == request_id:
RNS.Resource.accept(packet, callback=self.response_resource_concluded, progress_callback=pending_request.response_resource_progress, request_id = request_id)
pending_request.response_size = RNS.ResourceAdvertisement.get_size(packet)
pending_request.response_transfer_size = RNS.ResourceAdvertisement.get_transfer_size(packet)
pending_request.response_size = RNS.ResourceAdvertisement.read_size(packet)
pending_request.response_transfer_size = RNS.ResourceAdvertisement.read_transfer_size(packet)
pending_request.started_at = time.time()
elif self.resource_strategy == Link.ACCEPT_NONE:
pass
elif self.resource_strategy == Link.ACCEPT_APP:
if self.callbacks.resource != None:
try:
if self.callbacks.resource(resource):
resource_advertisement = RNS.ResourceAdvertisement.unpack(packet.plaintext)
resource_advertisement.link = self
if self.callbacks.resource(resource_advertisement):
RNS.Resource.accept(packet, self.callbacks.resource_concluded)
except Exception as e:
RNS.log("Error while executing resource accept callback from "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
@@ -712,6 +731,7 @@ class Link:
resource_hash = plaintext[1+RNS.Resource.MAPHASH_LEN:RNS.Identity.HASHLENGTH//8+1+RNS.Resource.MAPHASH_LEN]
else:
resource_hash = plaintext[1:RNS.Identity.HASHLENGTH//8+1]
for resource in self.outgoing_resources:
if resource.hash == resource_hash:
# We need to check that this request has not been
@@ -763,21 +783,12 @@ class Link:
try:
if not self.fernet:
try:
self.fernet = Fernet(base64.urlsafe_b64encode(self.derived_key))
self.fernet = Fernet(self.derived_key)
except Exception as e:
RNS.log("Could not "+str(self)+" instantiate Fernet while performin encryption on link. The contained exception was: "+str(e), RNS.LOG_ERROR)
raise e
# The fernet token VERSION field is stripped here and
# reinserted on the receiving end, since it is always
# set to 0x80.
#
# Since we're also quite content with supporting time-
# stamps until the year 8921556 AD, we'll also strip 2
# bytes from the timestamp field and reinsert those as
# 0x00 when received.
ciphertext = base64.urlsafe_b64decode(self.fernet.encrypt(plaintext))[3:]
return ciphertext
return self.fernet.encrypt(plaintext)
except Exception as e:
RNS.log("Encryption on link "+str(self)+" failed. The contained exception was: "+str(e), RNS.LOG_ERROR)
@@ -787,15 +798,12 @@ class Link:
def decrypt(self, ciphertext):
try:
if not self.fernet:
self.fernet = Fernet(base64.urlsafe_b64encode(self.derived_key))
self.fernet = Fernet(self.derived_key)
plaintext = self.fernet.decrypt(base64.urlsafe_b64encode(bytes([RNS.Identity.FERNET_VERSION, 0x00, 0x00])+ciphertext))
return plaintext
return self.fernet.decrypt(ciphertext)
except Exception as e:
RNS.log("Decryption failed on link "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
# RNS.log(traceback.format_exc(), RNS.LOG_ERROR)
# TODO: Think long about implications here
# self.teardown()
def sign(self, message):
@@ -812,6 +820,12 @@ class Link:
self.callbacks.link_established = callback
def set_link_closed_callback(self, callback):
"""
Registers a function to be called when a link has been
torn down.
:param callback: A function or method with the signature *callback(link)* to be called.
"""
self.callbacks.link_closed = callback
def set_packet_callback(self, callback):
@@ -830,7 +844,7 @@ class Link:
the resource will be accepted. If it returns *False* it will
be ignored.
:param callback: A function or method with the signature *callback(resource)* to be called.
:param callback: A function or method with the signature *callback(resource)* to be called. Please note that only the basic information of the resource is available at this time, such as *get_transfer_size()*, *get_data_size()*, *get_parts()* and *is_compressed()*.
"""
self.callbacks.resource = callback
@@ -857,7 +871,7 @@ class Link:
Registers a function to be called when an initiating peer has
identified over this link.
:param callback: A function or method with the signature *callback(identity)* to be called.
:param callback: A function or method with the signature *callback(link, identity)* to be called.
"""
self.callbacks.remote_identified = callback
@@ -920,7 +934,7 @@ class RequestReceipt():
RECEIVING = 0x03
READY = 0x04
def __init__(self, link, packet_receipt = None, resource = None, response_callback = None, failed_callback = None, progress_callback = None, timeout = None):
def __init__(self, link, packet_receipt = None, resource = None, response_callback = None, failed_callback = None, progress_callback = None, timeout = None, request_size = None):
self.packet_receipt = packet_receipt
self.resource = resource
self.started_at = None
@@ -936,6 +950,7 @@ class RequestReceipt():
self.link = link
self.request_id = self.hash
self.request_size = request_size
self.response = None
self.response_transfer_size = None
+12 -10
View File
@@ -216,16 +216,18 @@ class Packet:
self.destination_type = (self.flags & 0b00001100) >> 2
self.packet_type = (self.flags & 0b00000011)
DST_LEN = RNS.Reticulum.TRUNCATED_HASHLENGTH//8
if self.header_type == Packet.HEADER_2:
self.transport_id = self.raw[2:12]
self.destination_hash = self.raw[12:22]
self.context = ord(self.raw[22:23])
self.data = self.raw[23:]
self.transport_id = self.raw[2:DST_LEN+2]
self.destination_hash = self.raw[DST_LEN+2:2*DST_LEN+2]
self.context = ord(self.raw[2*DST_LEN+2:2*DST_LEN+3])
self.data = self.raw[2*DST_LEN+3:]
else:
self.transport_id = None
self.destination_hash = self.raw[2:12]
self.context = ord(self.raw[12:13])
self.data = self.raw[13:]
self.destination_hash = self.raw[2:DST_LEN+2]
self.context = ord(self.raw[DST_LEN+2:DST_LEN+3])
self.data = self.raw[DST_LEN+3:]
self.packed = False
self.update_hash()
@@ -252,7 +254,7 @@ class Packet:
if not self.packed:
self.pack()
if RNS.Transport.outbound(self):
return self.receipt
else:
@@ -313,7 +315,7 @@ class Packet:
def get_hashable_part(self):
hashable_part = bytes([self.raw[0] & 0b00001111])
if self.header_type == Packet.HEADER_2:
hashable_part += self.raw[12:]
hashable_part += self.raw[(RNS.Identity.TRUNCATED_HASHLENGTH//8)+2:]
else:
hashable_part += self.raw[2:]
@@ -321,7 +323,7 @@ class Packet:
class ProofDestination:
def __init__(self, packet):
self.hash = packet.get_hash()[:10];
self.hash = packet.get_hash()[:RNS.Reticulum.TRUNCATED_HASHLENGTH//8];
self.type = RNS.Destination.SINGLE
def encrypt(self, plaintext):
+164 -34
View File
@@ -42,10 +42,39 @@ class Resource:
:param callback: An optional *callable* with the signature *callback(resource)*. Will be called when the resource transfer concludes.
:param progress_callback: An optional *callable* with the signature *callback(resource)*. Will be called whenever the resource transfer progress is updated.
"""
WINDOW_FLEXIBILITY = 4
WINDOW_MIN = 1
WINDOW_MAX = 10
# The initial window size at beginning of transfer
WINDOW = 4
# Absolute minimum window size during transfer
WINDOW_MIN = 1
# The maximum window size for transfers on slow links
WINDOW_MAX_SLOW = 10
# The maximum window size for transfers on fast links
WINDOW_MAX_FAST = 76
# For calculating maps and guard segments, this
# must be set to the global maximum window.
WINDOW_MAX = WINDOW_MAX_FAST
# If the fast rate is sustained for this many request
# rounds, the fast link window size will be allowed.
FAST_RATE_THRESHOLD = WINDOW_MAX_SLOW - WINDOW - 2
# If the RTT rate is higher than this value,
# the max window size for fast links will be used.
# The default is 50 Kbps (the value is stored in
# bytes per second, hence the "/ 8").
RATE_FAST = (50*1000) / 8
# The minimum allowed flexibility of the window size.
# The difference between window_max and window_min
# will never be smaller than this value.
WINDOW_FLEXIBILITY = 4
# Number of bytes in a map hash
MAPHASH_LEN = 4
SDU = RNS.Packet.MDU
RANDOM_HASH_SIZE = 4
@@ -74,9 +103,11 @@ class Resource:
PART_TIMEOUT_FACTOR = 4
PART_TIMEOUT_FACTOR_AFTER_RTT = 2
MAX_RETRIES = 5
MAX_RETRIES = 8
MAX_ADV_RETRIES = 4
SENDER_GRACE_TIME = 10
RETRY_GRACE_TIME = 0.25
PER_RETRY_DELAY = 0.5
WATCHDOG_MAX_SLEEP = 1
@@ -120,7 +151,7 @@ class Resource:
resource.outstanding_parts = 0
resource.parts = [None] * resource.total_parts
resource.window = Resource.WINDOW
resource.window_max = Resource.WINDOW_MAX
resource.window_max = Resource.WINDOW_MAX_SLOW
resource.window_min = Resource.WINDOW_MIN
resource.window_flexibility = Resource.WINDOW_FLEXIBILITY
resource.last_activity = time.time()
@@ -210,6 +241,7 @@ class Resource:
self.status = Resource.NONE
self.link = link
self.max_retries = Resource.MAX_RETRIES
self.max_adv_retries = Resource.MAX_ADV_RETRIES
self.retries_left = self.max_retries
self.timeout_factor = self.link.traffic_timeout_factor
self.part_timeout_factor = Resource.PART_TIMEOUT_FACTOR
@@ -219,6 +251,11 @@ class Resource:
self.__watchdog_job_id = 0
self.__progress_callback = progress_callback
self.rtt = None
self.rtt_rxd_bytes = 0
self.req_sent = 0
self.req_resp_rtt_rate = 0
self.rtt_rxd_bytes_at_part_req = 0
self.fast_rate_rounds = 0
self.request_id = request_id
self.is_response = is_response
@@ -372,6 +409,7 @@ class Resource:
self.adv_sent = self.last_activity
self.rtt = None
self.status = Resource.ADVERTISED
self.retries_left = self.max_adv_retries
self.link.register_outgoing_resource(self)
RNS.log("Sent resource advertisement for "+RNS.prettyhexrep(self.hash), RNS.LOG_DEBUG)
except Exception as e:
@@ -426,7 +464,9 @@ class Resource:
window_remaining = self.outstanding_parts
sleep_time = self.last_activity + (rtt*(self.part_timeout_factor+window_remaining)) + Resource.RETRY_GRACE_TIME - time.time()
retries_used = self.max_retries - self.retries_left
extra_wait = retries_used * Resource.PER_RETRY_DELAY
sleep_time = self.last_activity + (rtt*(self.part_timeout_factor+window_remaining)) + Resource.RETRY_GRACE_TIME + extra_wait - time.time()
if sleep_time < 0:
if self.retries_left > 0:
@@ -446,7 +486,8 @@ class Resource:
self.cancel()
sleep_time = 0.001
else:
max_wait = self.rtt * self.timeout_factor * self.max_retries + self.sender_grace_time
max_extra_wait = sum([(r+1) * Resource.PER_RETRY_DELAY for r in range(self.MAX_RETRIES)])
max_wait = self.rtt * self.timeout_factor * self.max_retries + self.sender_grace_time + max_extra_wait
sleep_time = self.last_activity + max_wait - time.time()
if sleep_time < 0:
RNS.log("Resource timed out waiting for part requests", RNS.LOG_DEBUG)
@@ -526,8 +567,11 @@ class Resource:
RNS.log("Error while executing resource assembled callback from "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
try:
self.data.close()
if hasattr(self.data, "close") and callable(self.data.close):
self.data.close()
os.unlink(self.storagepath)
except Exception as e:
RNS.log("Error while cleaning up resource files, the contained exception was:", RNS.LOG_ERROR)
RNS.log(str(e))
@@ -564,7 +608,7 @@ class Resource:
else:
# Otherwise we'll recursively create the
# next segment of the resource
Resource(self.input_file, self.link, callback = self.callback, segment_index = self.segment_index+1, original_hash=self.original_hash)
Resource(self.input_file, self.link, callback = self.callback, segment_index = self.segment_index+1, original_hash=self.original_hash, progress_callback = self.__progress_callback)
else:
pass
else:
@@ -582,7 +626,7 @@ class Resource:
if self.req_resp == None:
self.req_resp = self.last_activity
rtt = self.req_resp-self.req_sent
self.part_timeout_factor = Resource.PART_TIMEOUT_FACTOR_AFTER_RTT
if self.rtt == None:
self.rtt = self.link.rtt
@@ -592,6 +636,16 @@ class Resource:
elif rtt > self.rtt:
self.rtt = min(self.rtt + self.rtt*0.05, rtt)
if rtt > 0:
req_resp_cost = len(packet.raw)+self.req_sent_bytes
self.req_resp_rtt_rate = req_resp_cost / rtt
if self.req_resp_rtt_rate > Resource.RATE_FAST and self.fast_rate_rounds < Resource.FAST_RATE_THRESHOLD:
self.fast_rate_rounds += 1
if self.fast_rate_rounds == Resource.FAST_RATE_THRESHOLD:
self.window_max = Resource.WINDOW_MAX_FAST
if not self.status == Resource.FAILED:
self.status = Resource.TRANSFERRING
part_data = packet.data
@@ -603,6 +657,7 @@ class Resource:
if self.parts[i] == None:
# Insert data into parts list
self.parts[i] = part_data
self.rtt_rxd_bytes += len(part_data)
self.received_count += 1
self.outstanding_parts -= 1
@@ -636,6 +691,20 @@ class Resource:
if (self.window - self.window_min) > (self.window_flexibility-1):
self.window_min += 1
if self.req_sent != 0:
rtt = time.time()-self.req_sent
req_transferred = self.rtt_rxd_bytes - self.rtt_rxd_bytes_at_part_req
if rtt != 0:
self.req_data_rtt_rate = req_transferred/rtt
self.rtt_rxd_bytes_at_part_req = self.rtt_rxd_bytes
if self.req_data_rtt_rate > Resource.RATE_FAST and self.fast_rate_rounds < Resource.FAST_RATE_THRESHOLD:
self.fast_rate_rounds += 1
if self.fast_rate_rounds == Resource.FAST_RATE_THRESHOLD:
self.window_max = Resource.WINDOW_MAX_FAST
self.request_next()
else:
self.receiving_part = False
@@ -683,6 +752,7 @@ class Resource:
request_packet.send()
self.last_activity = time.time()
self.req_sent = self.last_activity
self.req_sent_bytes = len(request_packet.raw)
self.req_resp = None
except Exception as e:
RNS.log("Could not send resource request packet, cancelling resource", RNS.LOG_DEBUG)
@@ -707,27 +777,34 @@ class Resource:
requested_hashes = request_data[pad+RNS.Identity.HASHLENGTH//8:]
for i in range(0,len(requested_hashes)//Resource.MAPHASH_LEN):
requested_hash = requested_hashes[i*Resource.MAPHASH_LEN:(i+1)*Resource.MAPHASH_LEN]
search_start = self.receiver_min_consecutive_height
search_end = self.receiver_min_consecutive_height+ResourceAdvertisement.COLLISION_GUARD_SIZE
for part in self.parts[search_start:search_end]:
if part.map_hash == requested_hash:
try:
if not part.sent:
part.send()
self.sent_parts += 1
else:
part.resend()
self.last_activity = time.time()
self.last_part_sent = self.last_activity
break
except Exception as e:
RNS.log("Resource could not send parts, cancelling transfer!", RNS.LOG_DEBUG)
RNS.log("The contained exception was: "+str(e), RNS.LOG_DEBUG)
self.cancel()
# Define the search scope
search_start = self.receiver_min_consecutive_height
search_end = self.receiver_min_consecutive_height+ResourceAdvertisement.COLLISION_GUARD_SIZE
map_hashes = []
for i in range(0,len(requested_hashes)//Resource.MAPHASH_LEN):
map_hash = requested_hashes[i*Resource.MAPHASH_LEN:(i+1)*Resource.MAPHASH_LEN]
map_hashes.append(map_hash)
search_scope = self.parts[search_start:search_end]
requested_parts = list(filter(lambda part: part.map_hash in map_hashes, search_scope))
for part in requested_parts:
try:
if not part.sent:
part.send()
self.sent_parts += 1
else:
part.resend()
self.last_activity = time.time()
self.last_part_sent = self.last_activity
except Exception as e:
RNS.log("Resource could not send parts, cancelling transfer!", RNS.LOG_DEBUG)
RNS.log("The contained exception was: "+str(e), RNS.LOG_DEBUG)
self.cancel()
if wants_more_hashmap:
last_map_hash = request_data[1:Resource.MAPHASH_LEN+1]
@@ -826,8 +903,44 @@ class Resource:
progress = self.processed_parts / self.progress_total_parts
return progress
def get_transfer_size(self):
"""
:returns: The number of bytes needed to transfer the resource.
"""
return self.size
def get_data_size(self):
"""
:returns: The total data size of the resource.
"""
return self.total_size
def get_parts(self):
"""
:returns: The number of parts the resource will be transferred in.
"""
return self.total_parts
def get_segments(self):
"""
:returns: The number of segments the resource is divided into.
"""
return self.total_segments
def get_hash(self):
"""
:returns: The hash of the resource.
"""
return self.hash
def is_compressed(self):
"""
:returns: Whether the resource is compressed.
"""
return self.compressed
def __str__(self):
return "<"+RNS.hexrep(self.hash)+"/"+RNS.hexrep(self.link.link_id)+">"
return "<"+RNS.hexrep(self.hash,delimit=False)+"/"+RNS.hexrep(self.link.link_id,delimit=False)+">"
class ResourceAdvertisement:
@@ -857,19 +970,19 @@ class ResourceAdvertisement:
@staticmethod
def get_request_id(advertisement_packet):
def read_request_id(advertisement_packet):
adv = ResourceAdvertisement.unpack(advertisement_packet.plaintext)
return adv.q
@staticmethod
def get_transfer_size(advertisement_packet):
def read_transfer_size(advertisement_packet):
adv = ResourceAdvertisement.unpack(advertisement_packet.plaintext)
return adv.t
@staticmethod
def get_size(advertisement_packet):
def read_size(advertisement_packet):
adv = ResourceAdvertisement.unpack(advertisement_packet.plaintext)
return adv.d
@@ -903,6 +1016,23 @@ class ResourceAdvertisement:
# Flags
self.f = 0x00 | self.p << 4 | self.u << 3 | self.s << 2 | self.c << 1 | self.e
def get_transfer_size(self):
return self.t
def get_data_size(self):
return self.d
def get_parts(self):
return self.n
def get_segments(self):
return self.l
def get_hash(self):
return self.h
def is_compressed(self):
return self.c
def pack(self, segment=0):
hashmap_start = segment*ResourceAdvertisement.HASHMAP_MAX_LEN
+550 -444
View File
File diff suppressed because it is too large Load Diff
+239 -92
View File
@@ -26,7 +26,6 @@ import time
import math
import struct
import threading
import traceback
from time import sleep
from .vendor import umsgpack as umsgpack
@@ -64,12 +63,13 @@ class Transport:
# various situations
LOCAL_REBROADCASTS_MAX = 2 # How many local rebroadcasts of an announce is allowed
PATH_REQUEST_GRACE = 0.35 # Grace time before a path announcement is made, allows directly reachable peers to respond first
PATH_REQUEST_RW = 2 # Path request random window
PATH_REQUEST_TIMEOUT = 15 # Default timuout for client path requests in seconds
PATH_REQUEST_GRACE = 0.35 # Grace time before a path announcement is made, allows directly reachable peers to respond first
PATH_REQUEST_RW = 2 # Path request random window
LINK_TIMEOUT = RNS.Link.KEEPALIVE * 2
REVERSE_TIMEOUT = 30*60 # Reverse table entries are removed after max 30 minutes
DESTINATION_TIMEOUT = PATHFINDER_E # Destination table entries are removed if unused for one week
LINK_TIMEOUT = RNS.Link.STALE_TIME * 1.25
REVERSE_TIMEOUT = 30*60 # Reverse table entries are removed after 30 minutes
DESTINATION_TIMEOUT = 60*60*24*7 # Destination table entries are removed if unused for one week
MAX_RECEIPTS = 1024 # Maximum number of receipts to keep track of
MAX_RATE_TIMESTAMPS = 16 # Maximum number of announce timestamps to keep per destination
@@ -92,6 +92,10 @@ class Transport:
announce_handlers = [] # A table storing externally registered announce handlers
tunnels = {} # A table storing tunnels to other transport instances
announce_rate_table = {} # A table for keeping track of announce rates
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
# Transport control destinations are used
# for control purposes like path requests
@@ -177,29 +181,31 @@ class Transport:
for serialised_entry in serialised_destinations:
destination_hash = serialised_entry[0]
timestamp = serialised_entry[1]
received_from = serialised_entry[2]
hops = serialised_entry[3]
expires = serialised_entry[4]
random_blobs = serialised_entry[5]
receiving_interface = Transport.find_interface_from_hash(serialised_entry[6])
announce_packet = Transport.get_cached_packet(serialised_entry[7])
if announce_packet != None and receiving_interface != None:
announce_packet.unpack()
# We increase the hops, since reading a packet
# from cache is equivalent to receiving it again
# over an interface. It is cached with it's non-
# increased hop-count.
announce_packet.hops += 1
Transport.destination_table[destination_hash] = [timestamp, received_from, hops, expires, random_blobs, receiving_interface, announce_packet]
RNS.log("Loaded path table entry for "+RNS.prettyhexrep(destination_hash)+" from storage", RNS.LOG_DEBUG)
else:
RNS.log("Could not reconstruct path table entry from storage for "+RNS.prettyhexrep(destination_hash), RNS.LOG_DEBUG)
if announce_packet == None:
RNS.log("The announce packet could not be loaded from cache", RNS.LOG_DEBUG)
if receiving_interface == None:
RNS.log("The interface is no longer available", RNS.LOG_DEBUG)
if len(destination_hash) == RNS.Reticulum.TRUNCATED_HASHLENGTH//8:
timestamp = serialised_entry[1]
received_from = serialised_entry[2]
hops = serialised_entry[3]
expires = serialised_entry[4]
random_blobs = serialised_entry[5]
receiving_interface = Transport.find_interface_from_hash(serialised_entry[6])
announce_packet = Transport.get_cached_packet(serialised_entry[7])
if announce_packet != None and receiving_interface != None:
announce_packet.unpack()
# We increase the hops, since reading a packet
# from cache is equivalent to receiving it again
# over an interface. It is cached with it's non-
# increased hop-count.
announce_packet.hops += 1
Transport.destination_table[destination_hash] = [timestamp, received_from, hops, expires, random_blobs, receiving_interface, announce_packet]
RNS.log("Loaded path table entry for "+RNS.prettyhexrep(destination_hash)+" from storage", RNS.LOG_DEBUG)
else:
RNS.log("Could not reconstruct path table entry from storage for "+RNS.prettyhexrep(destination_hash), RNS.LOG_DEBUG)
if announce_packet == None:
RNS.log("The announce packet could not be loaded from cache", RNS.LOG_DEBUG)
if receiving_interface == None:
RNS.log("The interface is no longer available", RNS.LOG_DEBUG)
if len(Transport.destination_table) == 1:
specifier = "entry"
@@ -279,6 +285,7 @@ class Transport:
def jobs():
outgoing = []
Transport.jobs_running = True
try:
if not Transport.jobs_locked:
# Process receipts list for timed-out packets
@@ -352,10 +359,14 @@ class Transport:
Transport.announces_last_checked = time.time()
# Cull the packet hashlist if it has reached max size
# Cull the packet hashlist if it has reached its max size
if len(Transport.packet_hashlist) > Transport.hashlist_maxsize:
Transport.packet_hashlist = Transport.packet_hashlist[len(Transport.packet_hashlist)-Transport.hashlist_maxsize:len(Transport.packet_hashlist)-1]
# Cull the path request tags list if it has reached its max size
if len(Transport.discovery_pr_tags) > Transport.max_pr_tags:
Transport.discovery_pr_tags = Transport.discovery_pr_tags[len(Transport.discovery_pr_tags)-Transport.max_pr_tags:len(Transport.discovery_pr_tags)-1]
if time.time() > Transport.tables_last_culled + Transport.tables_cull_interval:
# Cull the reverse table according to timeout
stale_reverse_entries = []
@@ -377,13 +388,29 @@ class Transport:
destination_entry = Transport.destination_table[destination_hash]
attached_interface = destination_entry[5]
if time.time() > destination_entry[0] + Transport.DESTINATION_TIMEOUT:
if attached_interface != None and hasattr(attached_interface, "mode") and attached_interface.mode == RNS.Interfaces.Interface.Interface.MODE_ACCESS_POINT:
destination_expiry = destination_entry[0] + Transport.AP_PATH_TIME
elif attached_interface != None and hasattr(attached_interface, "mode") and attached_interface.mode == RNS.Interfaces.Interface.Interface.MODE_ROAMING:
destination_expiry = destination_entry[0] + Transport.ROAMING_PATH_TIME
else:
destination_expiry = destination_entry[0] + Transport.DESTINATION_TIMEOUT
if time.time() > destination_expiry:
stale_paths.append(destination_hash)
RNS.log("Path to "+RNS.prettyhexrep(destination_hash)+" timed out and was removed", RNS.LOG_DEBUG)
elif not attached_interface in Transport.interfaces:
stale_paths.append(destination_hash)
RNS.log("Path to "+RNS.prettyhexrep(destination_hash)+" was removed since the attached interface no longer exists", RNS.LOG_DEBUG)
# Cull the pending discovery path requests table
stale_discovery_path_requests = []
for destination_hash in Transport.discovery_path_requests:
entry = Transport.discovery_path_requests[destination_hash]
if time.time() > entry["timeout"]:
stale_discovery_path_requests.append(destination_hash)
RNS.log("Waiting path request for "+RNS.prettyhexrep(destination_hash)+" timed out and was removed", RNS.LOG_DEBUG)
# Cull the tunnel table
stale_tunnels = []
ti = 0
@@ -452,6 +479,17 @@ class Transport:
else:
RNS.log("Removed "+str(i)+" paths", RNS.LOG_EXTREME)
i = 0
for destination_hash in stale_discovery_path_requests:
Transport.discovery_path_requests.pop(destination_hash)
i += 1
if i > 0:
if i == 1:
RNS.log("Removed "+str(i)+" waiting path request", RNS.LOG_EXTREME)
else:
RNS.log("Removed "+str(i)+" waiting path requests", RNS.LOG_EXTREME)
i = 0
for tunnel_id in stale_tunnels:
Transport.tunnels.pop(tunnel_id)
@@ -468,7 +506,6 @@ class Transport:
except Exception as e:
RNS.log("An exception occurred while running Transport jobs.", RNS.LOG_ERROR)
RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR)
traceback.print_exc()
Transport.jobs_running = False
@@ -498,12 +535,13 @@ class Transport:
@staticmethod
def outbound(packet):
while (Transport.jobs_running):
# TODO: Profile actual impact here on faster links
sleep(0.01)
sleep(0.0005)
Transport.jobs_locked = True
# TODO: This updateHash call might be redundant
packet.update_hash()
# packet.update_hash()
sent = False
outbound_time = time.time()
@@ -622,10 +660,6 @@ class Transport:
tx_time = (len(packet.raw)*8) / interface.bitrate
wait_time = (tx_time / interface.announce_cap)
interface.announce_allowed_at = outbound_time + wait_time
# TODO: Clean
# wait_time_str = str(round(wait_time*1000,3))+"ms"
# RNS.log("Next announce on "+str(interface)+" allowed in "+wait_time_str, RNS.LOG_EXTREME)
else:
should_transmit = False
@@ -686,13 +720,14 @@ class Transport:
Transport.packet_hashlist.append(packet.packet_hash)
stored_hash = True
def send_packet():
Transport.transmit(interface, packet.raw)
thread = threading.Thread(target=send_packet)
thread.daemon = True
thread.start()
# TODO: Re-evaluate potential for blocking
# def send_packet():
# Transport.transmit(interface, packet.raw)
# thread = threading.Thread(target=send_packet)
# thread.daemon = True
# thread.start()
Transport.transmit(interface, packet.raw)
sent = True
if sent:
@@ -773,45 +808,46 @@ class Transport:
def inbound(raw, interface=None):
# If interface access codes are enabled,
# we must authenticate each packet.
if interface != None and hasattr(interface, "ifac_identity") and interface.ifac_identity != None:
# Check that IFAC flag is set
if raw[0] & 0x80 == 0x80:
if len(raw) > 2+interface.ifac_size:
# Extract IFAC
ifac = raw[2:2+interface.ifac_size]
if len(raw) > 1:
if interface != None and hasattr(interface, "ifac_identity") and interface.ifac_identity != None:
# Check that IFAC flag is set
if raw[0] & 0x80 == 0x80:
if len(raw) > 2+interface.ifac_size:
# Extract IFAC
ifac = raw[2:2+interface.ifac_size]
# Unset IFAC flag
new_header = bytes([raw[0] & 0x7f, raw[1]])
# Unset IFAC flag
new_header = bytes([raw[0] & 0x7f, raw[1]])
# Re-assemble packet
new_raw = new_header+raw[2+interface.ifac_size:]
# Re-assemble packet
new_raw = new_header+raw[2+interface.ifac_size:]
# Calculate expected IFAC
expected_ifac = interface.ifac_identity.sign(new_raw)[-interface.ifac_size:]
# Calculate expected IFAC
expected_ifac = interface.ifac_identity.sign(new_raw)[-interface.ifac_size:]
# Check it
if ifac == expected_ifac:
raw = new_raw
else:
return
# Check it
if ifac == expected_ifac:
raw = new_raw
else:
return
else:
# If the IFAC flag is not set, but should be,
# drop the packet.
return
else:
# If the IFAC flag is not set, but should be,
# drop the packet.
return
else:
# If the interface does not have IFAC enabled,
# check the received packet IFAC flag.
if raw[0] & 0x80 == 0x80:
# If the flag is set, drop the packet
return
# If the interface does not have IFAC enabled,
# check the received packet IFAC flag.
if raw[0] & 0x80 == 0x80:
# If the flag is set, drop the packet
return
while (Transport.jobs_running):
sleep(0.01)
sleep(0.0005)
if Transport.identity == None:
return
@@ -917,13 +953,13 @@ class Transport:
new_raw = packet.raw[0:1]
new_raw += struct.pack("!B", packet.hops)
new_raw += next_hop
new_raw += packet.raw[12:]
new_raw += packet.raw[(RNS.Identity.TRUNCATED_HASHLENGTH//8)+2:]
elif remaining_hops == 1:
# Strip transport headers and transmit
new_flags = (RNS.Packet.HEADER_1) << 6 | (Transport.BROADCAST) << 4 | (packet.flags & 0b00001111)
new_raw = struct.pack("!B", new_flags)
new_raw += struct.pack("!B", packet.hops)
new_raw += packet.raw[12:]
new_raw += packet.raw[(RNS.Identity.TRUNCATED_HASHLENGTH//8)+2:]
elif remaining_hops == 0:
# Just increase hop count and transmit
new_raw = packet.raw[0:1]
@@ -931,8 +967,6 @@ class Transport:
new_raw += packet.raw[2:]
outbound_interface = Transport.destination_table[packet.destination_hash][5]
Transport.transmit(outbound_interface, new_raw)
Transport.destination_table[packet.destination_hash][0] = time.time()
if packet.packet_type == RNS.Packet.LINKREQUEST:
# Entry format is
@@ -955,6 +989,9 @@ class Transport:
Transport.reverse_table[packet.getTruncatedHash()] = reverse_entry
Transport.transmit(outbound_interface, new_raw)
Transport.destination_table[packet.destination_hash][0] = time.time()
else:
# TODO: There should probably be some kind of REJECT
# mechanism here, to signal to the source that their
@@ -1207,6 +1244,7 @@ class Transport:
announce_context = RNS.Packet.NONE
announce_data = packet.data
# TODO: Shouldn't the context be PATH_RESPONSE in the first case here?
if Transport.from_local_client(packet) and packet.context == RNS.Packet.PATH_RESPONSE:
for local_interface in Transport.local_client_interfaces:
if packet.receiving_interface != local_interface:
@@ -1241,6 +1279,38 @@ class Transport:
new_announce.hops = packet.hops
new_announce.send()
# If we have any waiting discovery path requests
# for this destination, we retransmit to that
# interface immediately
if packet.destination_hash in Transport.discovery_path_requests:
pr_entry = Transport.discovery_path_requests[packet.destination_hash]
attached_interface = pr_entry["requesting_interface"]
interface_str = " on "+str(attached_interface)
RNS.log("Got matching announce, answering waiting discovery path request for "+RNS.prettyhexrep(packet.destination_hash)+interface_str, RNS.LOG_DEBUG)
announce_identity = RNS.Identity.recall(packet.destination_hash)
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()
announce_context = RNS.Packet.NONE
announce_data = packet.data
new_announce = RNS.Packet(
announce_destination,
announce_data,
RNS.Packet.ANNOUNCE,
context = RNS.Packet.PATH_RESPONSE,
header_type = RNS.Packet.HEADER_2,
transport_type = Transport.TRANSPORT,
transport_id = Transport.identity.hash,
attached_interface = attached_interface
)
new_announce.hops = packet.hops
new_announce.send()
destination_table_entry = [now, received_from, announce_hops, expires, random_blobs, packet.receiving_interface, packet]
Transport.destination_table[packet.destination_hash] = destination_table_entry
RNS.log("Destination "+RNS.prettyhexrep(packet.destination_hash)+" is now "+str(announce_hops)+" hops away via "+RNS.prettyhexrep(received_from)+" on "+str(packet.receiving_interface), RNS.LOG_DEBUG)
@@ -1323,7 +1393,7 @@ class Transport:
if packet.receiving_interface == link_entry[2]:
# TODO: Should we validate the LR proof at each transport
# step before transporting it?
RNS.log("Link request proof received on correct interface, transporting it via "+str(link_entry[4]), RNS.LOG_DEBUG)
# RNS.log("Link request proof received on correct interface, transporting it via "+str(link_entry[4]), RNS.LOG_EXTREME)
new_raw = packet.raw[0:1]
new_raw += struct.pack("!B", packet.hops)
new_raw += packet.raw[2:]
@@ -1685,7 +1755,7 @@ class Transport:
return False
@staticmethod
def request_path(destination_hash, on_interface=None):
def request_path(destination_hash, on_interface=None, tag=None, recursive=False):
"""
Requests a path to the destination from the network. If
another reachable peer on the network knows a path, it
@@ -1694,13 +1764,45 @@ class Transport:
:param destination_hash: A destination hash as *bytes*.
:param on_interface: If specified, the path request will only be sent on this interface. In normal use, Reticulum handles this automatically, and this parameter should not be used.
"""
if RNS.Reticulum.transport_enabled():
path_request_data = destination_hash+Transport.identity.hash+RNS.Identity.get_random_hash()
if tag == None:
request_tag = RNS.Identity.get_random_hash()
else:
path_request_data = destination_hash+RNS.Identity.get_random_hash()
request_tag = tag
if RNS.Reticulum.transport_enabled():
path_request_data = destination_hash+Transport.identity.hash+request_tag
else:
path_request_data = destination_hash+request_tag
path_request_dst = RNS.Destination(None, RNS.Destination.OUT, RNS.Destination.PLAIN, Transport.APP_NAME, "path", "request")
packet = RNS.Packet(path_request_dst, path_request_data, packet_type = RNS.Packet.DATA, transport_type = RNS.Transport.BROADCAST, header_type = RNS.Packet.HEADER_1, attached_interface = on_interface)
if on_interface != None and recursive:
if not hasattr(on_interface, "announce_cap"):
on_interface.announce_cap = RNS.Reticulum.ANNOUNCE_CAP
if not hasattr(on_interface, "announce_allowed_at"):
on_interface.announce_allowed_at = 0
if not hasattr(on_interface, "announce_queue"):
on_interface.announce_queue = []
queued_announces = True if len(on_interface.announce_queue) > 0 else False
if queued_announces:
# TODO: Reset to extra level, probably
RNS.log("Blocking recursive path request on "+str(on_interface)+" due to queued announces", RNS.LOG_DEBUG)
return
else:
now = time.time()
if now < on_interface.announce_allowed_at:
# TODO: Reset to extra level, probably
RNS.log("Blocking recursive path request on "+str(on_interface)+" due to active announce cap", RNS.LOG_DEBUG)
return
else:
tx_time = ((len(path_request_data)+RNS.Reticulum.HEADER_MINSIZE)*8) / on_interface.bitrate
wait_time = (tx_time / on_interface.announce_cap)
on_interface.announce_allowed_at = now + wait_time
packet.send()
@staticmethod
@@ -1710,8 +1812,9 @@ class Transport:
# hash in the packet, we assume those bytes are the
# destination being requested.
if len(data) >= RNS.Identity.TRUNCATED_HASHLENGTH//8:
# If there is also enough bytes for a trasport
# instance ID and at least one random byte, we
destination_hash = data[:RNS.Identity.TRUNCATED_HASHLENGTH//8]
# If there is also enough bytes for a transport
# instance ID and at least one tag byte, we
# assume the next bytes to be the trasport ID
# of the requesting transport instance.
if len(data) > (RNS.Identity.TRUNCATED_HASHLENGTH//8)*2:
@@ -1719,24 +1822,52 @@ class Transport:
else:
requesting_transport_instance = None
Transport.path_request(
data[:RNS.Identity.TRUNCATED_HASHLENGTH//8],
Transport.from_local_client(packet),
packet.receiving_interface,
requesting_transport_instance,
)
tag_bytes = None
if len(data) > (RNS.Identity.TRUNCATED_HASHLENGTH//8)*2:
tag_bytes = data[RNS.Identity.TRUNCATED_HASHLENGTH//8*2:]
elif len(data) > (RNS.Identity.TRUNCATED_HASHLENGTH//8):
tag_bytes = data[RNS.Identity.TRUNCATED_HASHLENGTH//8:]
if tag_bytes != None:
if len(tag_bytes) > RNS.Identity.TRUNCATED_HASHLENGTH//8:
tag_bytes = tag_bytes[:RNS.Identity.TRUNCATED_HASHLENGTH//8]
unique_tag = destination_hash+tag_bytes
if not unique_tag in Transport.discovery_pr_tags:
Transport.discovery_pr_tags.append(unique_tag)
Transport.path_request(
destination_hash,
Transport.from_local_client(packet),
packet.receiving_interface,
requestor_transport_id = requesting_transport_instance,
tag=tag_bytes
)
else:
RNS.log("Ignoring duplicate path request for "+RNS.prettyhexrep(destination_hash)+" with tag "+RNS.prettyhexrep(unique_tag), RNS.LOG_DEBUG)
else:
RNS.log("Ignoring tagless path request for "+RNS.prettyhexrep(destination_hash), RNS.LOG_DEBUG)
except Exception as e:
RNS.log("Error while handling path request. The contained exception was: "+str(e), RNS.LOG_ERROR)
@staticmethod
def path_request(destination_hash, is_from_local_client, attached_interface, requestor_transport_id=None):
def path_request(destination_hash, is_from_local_client, attached_interface, requestor_transport_id=None, tag=None):
should_search_for_unknown = 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 = ""
# TODO: Clean
# RNS.log("Path request for "+RNS.prettyhexrep(destination_hash)+interface_str, RNS.LOG_DEBUG)
RNS.log("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:
@@ -1764,7 +1895,7 @@ class Transport:
# path requests with transport instance keys is quite
# inefficient. There is probably a better way. Doing
# path invalidation here would decrease the network
# convergence time.
# convergence time. Maybe just drop it?
RNS.log("Not answering path request for "+RNS.prettyhexrep(destination_hash)+interface_str+", since next hop is the requestor", RNS.LOG_DEBUG)
else:
RNS.log("Answering path request for "+RNS.prettyhexrep(destination_hash)+interface_str+", path is known", RNS.LOG_DEBUG)
@@ -1801,12 +1932,28 @@ class Transport:
if not interface == attached_interface:
Transport.request_path(destination_hash, interface)
elif should_search_for_unknown:
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)
else:
# 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)
pr_entry = { "destination_hash": destination_hash, "timeout": time.time()+Transport.PATH_REQUEST_TIMEOUT, "requesting_interface": attached_interface }
Transport.discovery_path_requests[destination_hash] = pr_entry
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)
elif not is_from_local_client and len(Transport.local_client_interfaces) > 0:
# Forward the path request on all local
# client interfaces
RNS.log("Forwarding path request for "+RNS.prettyhexrep(destination_hash)+interface_str+" to local clients", RNS.LOG_DEBUG)
for interface in Transport.local_client_interfaces:
Transport.request_path(destination_hash, interface)
Transport.request_path(destination_hash, on_interface=interface)
else:
RNS.log("Ignoring path request for "+RNS.prettyhexrep(destination_hash)+interface_str+", no path known", RNS.LOG_DEBUG)
+389
View File
@@ -0,0 +1,389 @@
#!/usr/bin/env python3
# MIT License
#
# Copyright (c) 2016-2022 Mark Qvist / unsigned.io
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import RNS
import argparse
import time
import sys
import os
from RNS._version import __version__
APP_NAME = "rncp"
allow_all = False
allowed_identity_hashes = []
def receive(configdir, verbosity = 0, quietness = 0, allowed = [], display_identity = False, limit = None, disable_auth = None, disable_announce = False):
global allow_all, allowed_identity_hashes
identity = None
targetloglevel = 3+verbosity-quietness
reticulum = RNS.Reticulum(configdir=configdir, loglevel=targetloglevel)
identity_path = RNS.Reticulum.identitypath+"/"+APP_NAME
if os.path.isfile(identity_path):
identity = RNS.Identity.from_file(identity_path)
if identity == None:
RNS.log("No valid saved identity found, creating new...", RNS.LOG_INFO)
identity = RNS.Identity()
identity.to_file(identity_path)
destination = RNS.Destination(identity, RNS.Destination.IN, RNS.Destination.SINGLE, APP_NAME, "receive")
if display_identity:
print("Identity : "+str(identity))
print("Receiving on : "+RNS.prettyhexrep(destination.hash))
exit(0)
if disable_auth:
allow_all = True
else:
if allowed != None:
for a in allowed:
try:
dest_len = (RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2
if len(a) != dest_len:
raise ValueError("Allowed destination length is invalid, must be {hex} hexadecimal characters ({byte} bytes).".format(hex=dest_len, byte=dest_len//2))
try:
destination_hash = bytes.fromhex(a)
allowed_identity_hashes.append(destination_hash)
except Exception as e:
raise ValueError("Invalid destination entered. Check your input.")
except Exception as e:
print(str(e))
exit(1)
if len(allowed_identity_hashes) < 1 and not disable_auth:
print("Warning: No allowed identities configured, rncp will not accept any files!")
destination.set_link_established_callback(receive_link_established)
print("rncp ready to receive on "+RNS.prettyhexrep(destination.hash))
if not disable_announce:
destination.announce()
while True:
time.sleep(1)
def receive_link_established(link):
RNS.log("Incoming link established", RNS.LOG_VERBOSE)
link.set_remote_identified_callback(receive_sender_identified)
link.set_resource_strategy(RNS.Link.ACCEPT_APP)
link.set_resource_callback(receive_resource_callback)
link.set_resource_started_callback(receive_resource_started)
link.set_resource_concluded_callback(receive_resource_concluded)
def receive_sender_identified(link, identity):
global allow_all
if identity.hash in allowed_identity_hashes:
RNS.log("Authenticated sender", RNS.LOG_VERBOSE)
else:
if not allow_all:
RNS.log("Sender not allowed, tearing down link", RNS.LOG_VERBOSE)
link.teardown()
else:
pass
def receive_resource_callback(resource):
global allow_all
sender_identity = resource.link.get_remote_identity()
if sender_identity != None:
if sender_identity.hash in allowed_identity_hashes:
return True
if allow_all:
return True
return False
def receive_resource_started(resource):
if resource.link.get_remote_identity():
id_str = " from "+RNS.prettyhexrep(resource.link.get_remote_identity().hash)
else:
id_str = ""
print("Starting resource transfer "+RNS.prettyhexrep(resource.hash)+id_str)
def receive_resource_concluded(resource):
if resource.status == RNS.Resource.COMPLETE:
print(str(resource)+" completed")
if resource.total_size > 4:
filename_len = int.from_bytes(resource.data.read(2), "big")
filename = resource.data.read(filename_len).decode("utf-8")
counter = 0
saved_filename = filename
while os.path.isfile(saved_filename):
counter += 1
saved_filename = filename+"."+str(counter)
file = open(saved_filename, "wb")
file.write(resource.data.read())
file.close()
else:
print("Invalid data received, ignoring resource")
else:
print("Resource failed")
resource_done = False
current_resource = None
stats = []
speed = 0.0
def sender_progress(resource):
stats_max = 32
global current_resource, stats, speed, resource_done
current_resource = resource
now = time.time()
got = current_resource.get_progress()*current_resource.total_size
entry = [now, got]
stats.append(entry)
while len(stats) > stats_max:
stats.pop(0)
span = now - stats[0][0]
if span == 0:
speed = 0
else:
diff = got - stats[0][1]
speed = diff/span
if resource.status < RNS.Resource.COMPLETE:
resource_done = False
else:
resource_done = True
link = None
def send(configdir, verbosity = 0, quietness = 0, destination = None, file = None, timeout = RNS.Transport.PATH_REQUEST_TIMEOUT):
global current_resource, resource_done, link, speed
from tempfile import TemporaryFile
targetloglevel = 3+verbosity-quietness
try:
dest_len = (RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2
if len(destination) != dest_len:
raise ValueError("Allowed destination length is invalid, must be {hex} hexadecimal characters ({byte} bytes).".format(hex=dest_len, byte=dest_len//2))
try:
destination_hash = bytes.fromhex(destination)
except Exception as e:
raise ValueError("Invalid destination entered. Check your input.")
except Exception as e:
print(str(e))
exit(1)
file_path = os.path.expanduser(file)
if not os.path.isfile(file_path):
print("File not found")
exit(1)
temp_file = TemporaryFile()
real_file = open(file_path, "rb")
filename_bytes = os.path.basename(file_path).encode("utf-8")
filename_len = len(filename_bytes)
if filename_len > 0xFFFF:
print("Filename exceeds max size, cannot send")
exit(1)
else:
print("Preparing file...", end=" ")
temp_file.write(filename_len.to_bytes(2, "big"))
temp_file.write(filename_bytes)
temp_file.write(real_file.read())
temp_file.seek(0)
print("\r \r", end="")
reticulum = RNS.Reticulum(configdir=configdir, loglevel=targetloglevel)
identity_path = RNS.Reticulum.identitypath+"/"+APP_NAME
if os.path.isfile(identity_path):
identity = RNS.Identity.from_file(identity_path)
if identity == None:
RNS.log("No valid saved identity found, creating new...", RNS.LOG_INFO)
identity = RNS.Identity()
identity.to_file(identity_path)
if not RNS.Transport.has_path(destination_hash):
RNS.Transport.request_path(destination_hash)
print("Path to "+RNS.prettyhexrep(destination_hash)+" requested ", end=" ")
sys.stdout.flush()
i = 0
syms = "⢄⢂⢁⡁⡈⡐⡠"
estab_timeout = time.time()+timeout
while not RNS.Transport.has_path(destination_hash) and time.time() < estab_timeout:
time.sleep(0.1)
print(("\b\b"+syms[i]+" "), end="")
sys.stdout.flush()
i = (i+1)%len(syms)
if not RNS.Transport.has_path(destination_hash):
print("\r \rPath not found")
exit(1)
else:
print("\r \rEstablishing link with "+RNS.prettyhexrep(destination_hash)+" ", end=" ")
receiver_identity = RNS.Identity.recall(destination_hash)
receiver_destination = RNS.Destination(
receiver_identity,
RNS.Destination.OUT,
RNS.Destination.SINGLE,
APP_NAME,
"receive"
)
link = RNS.Link(receiver_destination)
while link.status != RNS.Link.ACTIVE and time.time() < estab_timeout:
time.sleep(0.1)
print(("\b\b"+syms[i]+" "), end="")
sys.stdout.flush()
i = (i+1)%len(syms)
if not RNS.Transport.has_path(destination_hash):
print("\r \rCould not establish link with "+RNS.prettyhexrep(destination_hash))
exit(1)
else:
print("\r \rAdvertising file resource ", end=" ")
link.identify(identity)
resource = RNS.Resource(temp_file, link, callback = sender_progress, progress_callback = sender_progress)
current_resource = resource
while resource.status < RNS.Resource.TRANSFERRING:
time.sleep(0.1)
print(("\b\b"+syms[i]+" "), end="")
sys.stdout.flush()
i = (i+1)%len(syms)
if resource.status > RNS.Resource.COMPLETE:
print("\r \rFile was not accepted by "+RNS.prettyhexrep(destination_hash))
exit(1)
else:
print("\r \rTransferring file ", end=" ")
while not resource_done:
time.sleep(0.1)
prg = current_resource.get_progress()
percent = round(prg * 100.0, 1)
stat_str = str(percent)+"% - " + size_str(int(prg*current_resource.total_size)) + " of " + size_str(current_resource.total_size) + " - " +size_str(speed, "b")+"ps"
print("\r \rTransferring file "+syms[i]+" "+stat_str, end=" ")
sys.stdout.flush()
i = (i+1)%len(syms)
if current_resource.status != RNS.Resource.COMPLETE:
print("\r \rThe transfer failed")
exit(1)
else:
print("\r \r"+str(file_path)+" copied to "+RNS.prettyhexrep(destination_hash))
link.teardown()
time.sleep(0.25)
real_file.close()
temp_file.close()
exit(0)
def main():
try:
parser = argparse.ArgumentParser(description="Reticulum File Transfer Utility")
parser.add_argument("file", nargs="?", default=None, help="file to be transferred", type=str)
parser.add_argument("destination", nargs="?", default=None, help="hexadecimal hash of the receiver", type=str)
parser.add_argument("--config", metavar="path", action="store", default=None, help="path to alternative Reticulum config directory", type=str)
parser.add_argument('-v', '--verbose', action='count', default=0, help="increase verbosity")
parser.add_argument('-q', '--quiet', action='count', default=0, help="decrease verbosity")
parser.add_argument('-p', '--print-identity', action='store_true', default=False, help="print identity and destination info and exit")
parser.add_argument("-r", '--receive', action='store_true', default=False, help="wait for incoming files")
parser.add_argument("-b", '--no-announce', action='store_true', default=False, help="don't announce at program start")
parser.add_argument('-a', metavar="allowed_hash", dest="allowed", action='append', help="accept from this identity", type=str)
parser.add_argument('-n', '--no-auth', action='store_true', default=False, help="accept files from anyone")
parser.add_argument("-w", action="store", metavar="seconds", type=float, help="sender timeout before giving up", default=RNS.Transport.PATH_REQUEST_TIMEOUT)
# parser.add_argument("--limit", action="store", metavar="files", type=float, help="maximum number of files to accept", default=None)
parser.add_argument("--version", action="version", version="rncp {version}".format(version=__version__))
args = parser.parse_args()
if args.receive or args.print_identity:
receive(
configdir = args.config,
verbosity=args.verbose,
quietness=args.quiet,
allowed = args.allowed,
display_identity=args.print_identity,
# limit=args.limit,
disable_auth=args.no_auth,
disable_announce=args.no_announce,
)
elif args.destination != None and args.file != None:
send(
configdir = args.config,
verbosity = args.verbose,
quietness = args.quiet,
destination = args.destination,
file = args.file,
timeout = args.w,
)
else:
print("")
parser.print_help()
print("")
except KeyboardInterrupt:
print("")
if resource != None:
resource.cancel()
if link != None:
link.teardown()
exit()
def size_str(num, suffix='B'):
units = ['','K','M','G','T','P','E','Z']
last_unit = 'Y'
if suffix == 'b':
num *= 8
units = ['','K','M','G','T','P','E','Z']
last_unit = 'Y'
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)
num /= 1000.0
return "%.2f%s%s" % (num, last_unit, suffix)
if __name__ == "__main__":
main()
+2 -2
View File
@@ -257,7 +257,7 @@ def main():
metavar="seconds",
type=float,
help="timeout before giving up",
default=15
default=RNS.Transport.PATH_REQUEST_TIMEOUT
)
parser.add_argument(
@@ -321,7 +321,7 @@ def pretty_date(time=False):
if second_diff < 3600:
return str(int(second_diff / 60)) + " minutes"
if second_diff < 7200:
return "an hour ago"
return "an hour"
if second_diff < 86400:
return str(int(second_diff / 3600)) + " hours"
if day_diff == 1:
+9 -9
View File
@@ -147,7 +147,7 @@ loglevel = 4
[[Default Interface]]
type = AutoInterface
interface_enabled = True
enabled = yes
# The following example enables communication with other
@@ -155,7 +155,7 @@ loglevel = 4
[[UDP Interface]]
type = UDPInterface
interface_enabled = False
enabled = no
listen_ip = 0.0.0.0
listen_port = 4242
forward_ip = 255.255.255.255
@@ -198,7 +198,7 @@ loglevel = 4
[[TCP Server Interface]]
type = TCPServerInterface
interface_enabled = False
enabled = no
# This configuration will listen on all IP
# interfaces on port 4242
@@ -224,7 +224,7 @@ loglevel = 4
[[TCP Client Interface]]
type = TCPClientInterface
interface_enabled = False
enabled = no
target_host = 127.0.0.1
target_port = 4242
@@ -237,9 +237,9 @@ loglevel = 4
[[I2P]]
type = I2PInterface
interface_enabled = yes
enabled = no
connectable = yes
peers = 5urvjicpzi7q3ybztsef4i5ow2aq4soktfj7zedz53s47r54jnqq.b32.i2p
peers = ykzlw5ujbaqc2xkec4cpvgyxj257wcrmmgkuxqmqcur7cq3w3lha.b32.i2p
# Here's an example of how to add a LoRa interface
@@ -249,7 +249,7 @@ loglevel = 4
type = RNodeInterface
# Enable interface if you want use it!
interface_enabled = False
enabled = no
# Serial port for the device
port = /dev/ttyUSB0
@@ -301,7 +301,7 @@ loglevel = 4
type = KISSInterface
# Enable interface if you want use it!
interface_enabled = False
enabled = no
# Serial port for the device
port = /dev/ttyUSB1
@@ -372,7 +372,7 @@ loglevel = 4
ssid = 0
# Enable interface if you want use it!
interface_enabled = False
enabled = no
# Serial port for the device
port = /dev/ttyUSB2
+83 -60
View File
@@ -46,7 +46,7 @@ def size_str(num, suffix='B'):
return "%.2f%s%s" % (num, last_unit, suffix)
def program_setup(configdir, dispall=False, verbosity = 0):
def program_setup(configdir, dispall=False, verbosity=0, name_filter=None):
reticulum = RNS.Reticulum(configdir = configdir, loglevel = 3+verbosity)
stats = None
@@ -59,79 +59,100 @@ def program_setup(configdir, dispall=False, verbosity = 0):
for ifstat in stats["interfaces"]:
name = ifstat["name"]
if dispall or not (name.startswith("LocalInterface[") or name.startswith("TCPInterface[Client")):
print("")
if dispall or not (
name.startswith("LocalInterface[") or
name.startswith("TCPInterface[Client") or
name.startswith("I2PInterfacePeer[Connected peer") or
(name.startswith("I2PInterface[") and ("i2p_connectable" in ifstat and ifstat["i2p_connectable"] == False))
):
if ifstat["status"]:
ss = "Up"
else:
ss = "Down"
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():
print("")
if ifstat["mode"] == RNS.Interfaces.Interface.Interface.MODE_ACCESS_POINT:
modestr = "Access Point"
elif ifstat["mode"] == RNS.Interfaces.Interface.Interface.MODE_POINT_TO_POINT:
modestr = "Point-to-Point"
elif ifstat["mode"] == RNS.Interfaces.Interface.Interface.MODE_ROAMING:
modestr = "Roaming"
elif ifstat["mode"] == RNS.Interfaces.Interface.Interface.MODE_BOUNDARY:
modestr = "Boundary"
else:
modestr = "Full"
if ifstat["clients"] != None:
clients = ifstat["clients"]
if name.startswith("Shared Instance["):
cnum = max(clients-1,0)
if cnum == 1:
spec_str = " program"
if ifstat["status"]:
ss = "Up"
else:
spec_str = " programs"
ss = "Down"
clients_string = "Serving : "+str(cnum)+spec_str
else:
clients_string = "Clients : "+str(clients)
if ifstat["mode"] == RNS.Interfaces.Interface.Interface.MODE_ACCESS_POINT:
modestr = "Access Point"
elif ifstat["mode"] == RNS.Interfaces.Interface.Interface.MODE_POINT_TO_POINT:
modestr = "Point-to-Point"
elif ifstat["mode"] == RNS.Interfaces.Interface.Interface.MODE_ROAMING:
modestr = "Roaming"
elif ifstat["mode"] == RNS.Interfaces.Interface.Interface.MODE_BOUNDARY:
modestr = "Boundary"
elif ifstat["mode"] == RNS.Interfaces.Interface.Interface.MODE_GATEWAY:
modestr = "Gateway"
else:
modestr = "Full"
else:
clients = None
print(" {n}".format(n=ifstat["name"]))
if ifstat["clients"] != None:
clients = ifstat["clients"]
if name.startswith("Shared Instance["):
cnum = max(clients-1,0)
if cnum == 1:
spec_str = " program"
else:
spec_str = " programs"
if "ifac_netname" in ifstat and ifstat["ifac_netname"] != None:
print(" Network : {nn}".format(nn=ifstat["ifac_netname"]))
clients_string = "Serving : "+str(cnum)+spec_str
elif name.startswith("I2PInterface["):
if "i2p_connectable" in ifstat and ifstat["i2p_connectable"] == True:
cnum = clients
if cnum == 1:
spec_str = " connected I2P endpoint"
else:
spec_str = " connected I2P endpoints"
print(" Status : {ss}".format(ss=ss))
clients_string = "Peers : "+str(cnum)+spec_str
else:
clients_string = ""
else:
clients_string = "Clients : "+str(clients)
if clients != None:
print(" "+clients_string)
else:
clients = None
if not (name.startswith("Shared Instance[") or name.startswith("TCPInterface[Client") or name.startswith("LocalInterface[")):
print(" Mode : {mode}".format(mode=modestr))
print(" {n}".format(n=ifstat["name"]))
if "bitrate" in ifstat and ifstat["bitrate"] != None:
print(" Rate : {ss}".format(ss=speed_str(ifstat["bitrate"])))
if "peers" in ifstat and ifstat["peers"] != None:
print(" Peers : {np} reachable".format(np=ifstat["peers"]))
if "ifac_netname" in ifstat and ifstat["ifac_netname"] != None:
print(" Network : {nn}".format(nn=ifstat["ifac_netname"]))
if "ifac_signature" in ifstat and ifstat["ifac_signature"] != None:
sigstr = "<…"+RNS.hexrep(ifstat["ifac_signature"][-5:], delimit=False)+">"
print(" Access : {nb}-bit IFAC by {sig}".format(nb=ifstat["ifac_size"]*8, sig=sigstr))
if "i2p_b32" in ifstat and ifstat["i2p_b32"] != None:
print(" I2P B32 : {ep}".format(ep=str(ifstat["i2p_b32"])))
print(" Status : {ss}".format(ss=ss))
if "announce_queue" in ifstat and ifstat["announce_queue"] != None and ifstat["announce_queue"] > 0:
aqn = ifstat["announce_queue"]
if aqn == 1:
print(" Queued : {np} announce".format(np=aqn))
else:
print(" Queued : {np} announces".format(np=aqn))
print(" Traffic : {txb}\n {rxb}".format(rxb=size_str(ifstat["rxb"]), txb=size_str(ifstat["txb"])))
if clients != None and clients_string != "":
print(" "+clients_string)
if not (name.startswith("Shared Instance[") or name.startswith("TCPInterface[Client") or name.startswith("LocalInterface[")):
print(" Mode : {mode}".format(mode=modestr))
if "bitrate" in ifstat and ifstat["bitrate"] != None:
print(" Rate : {ss}".format(ss=speed_str(ifstat["bitrate"])))
if "peers" in ifstat and ifstat["peers"] != None:
print(" Peers : {np} reachable".format(np=ifstat["peers"]))
if "ifac_signature" in ifstat and ifstat["ifac_signature"] != None:
sigstr = "<…"+RNS.hexrep(ifstat["ifac_signature"][-5:], delimit=False)+">"
print(" Access : {nb}-bit IFAC by {sig}".format(nb=ifstat["ifac_size"]*8, sig=sigstr))
if "i2p_b32" in ifstat and ifstat["i2p_b32"] != None:
print(" I2P B32 : {ep}".format(ep=str(ifstat["i2p_b32"])))
if "announce_queue" in ifstat and ifstat["announce_queue"] != None and ifstat["announce_queue"] > 0:
aqn = ifstat["announce_queue"]
if aqn == 1:
print(" Queued : {np} announce".format(np=aqn))
else:
print(" Queued : {np} announces".format(np=aqn))
print(" Traffic : {txb}\n {rxb}".format(rxb=size_str(ifstat["rxb"]), txb=size_str(ifstat["txb"])))
if "transport_id" in stats and stats["transport_id"] != None:
print("\n Reticulum Transport Instance "+RNS.prettyhexrep(stats["transport_id"])+" running")
print("\n Reticulum Transport Instance "+RNS.prettyhexrep(stats["transport_id"])+" is running")
print("")
@@ -153,6 +174,8 @@ def main():
)
parser.add_argument('-v', '--verbose', action='count', default=0)
parser.add_argument("filter", nargs="?", default=None, help="only display interfaces with names including filter", type=str)
args = parser.parse_args()
@@ -161,7 +184,7 @@ def main():
else:
configarg = None
program_setup(configdir = configarg, dispall = args.all, verbosity=args.verbose)
program_setup(configdir = configarg, dispall = args.all, verbosity=args.verbose, name_filter=args.filter)
except KeyboardInterrupt:
print("")
+714
View File
@@ -0,0 +1,714 @@
#!/usr/bin/env python3
# MIT License
#
# Copyright (c) 2016-2022 Mark Qvist / unsigned.io
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import RNS
import subprocess
import argparse
import shlex
import time
import sys
import tty
import os
from RNS._version import __version__
APP_NAME = "rnx"
identity = None
reticulum = None
allow_all = False
allowed_identity_hashes = []
def prepare_identity(identity_path):
global identity
if identity_path == None:
identity_path = RNS.Reticulum.identitypath+"/"+APP_NAME
if os.path.isfile(identity_path):
identity = RNS.Identity.from_file(identity_path)
if identity == None:
RNS.log("No valid saved identity found, creating new...", RNS.LOG_INFO)
identity = RNS.Identity()
identity.to_file(identity_path)
def listen(configdir, identitypath = None, verbosity = 0, quietness = 0, allowed = [], print_identity = False, disable_auth = None, disable_announce=False):
global identity, allow_all, allowed_identity_hashes, reticulum
targetloglevel = 3+verbosity-quietness
reticulum = RNS.Reticulum(configdir=configdir, loglevel=targetloglevel)
prepare_identity(identitypath)
destination = RNS.Destination(identity, RNS.Destination.IN, RNS.Destination.SINGLE, APP_NAME, "execute")
if print_identity:
print("Identity : "+str(identity))
print("Listening on : "+RNS.prettyhexrep(destination.hash))
exit(0)
if disable_auth:
allow_all = True
else:
if allowed != None:
for a in allowed:
try:
dest_len = (RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2
if len(a) != dest_len:
raise ValueError("Allowed destination length is invalid, must be {hex} hexadecimal characters ({byte} bytes).".format(hex=dest_len, byte=dest_len//2))
try:
destination_hash = bytes.fromhex(a)
allowed_identity_hashes.append(destination_hash)
except Exception as e:
raise ValueError("Invalid destination entered. Check your input.")
except Exception as e:
print(str(e))
exit(1)
if len(allowed_identity_hashes) < 1 and not disable_auth:
print("Warning: No allowed identities configured, rncx will not accept any commands!")
destination.set_link_established_callback(command_link_established)
if not allow_all:
destination.register_request_handler(
path = "command",
response_generator = execute_received_command,
allow = RNS.Destination.ALLOW_LIST,
allowed_list = allowed_identity_hashes
)
else:
destination.register_request_handler(
path = "command",
response_generator = execute_received_command,
allow = RNS.Destination.ALLOW_ALL,
)
RNS.log("rnx listening for commands on "+RNS.prettyhexrep(destination.hash))
if not disable_announce:
destination.announce()
while True:
time.sleep(1)
def command_link_established(link):
link.set_remote_identified_callback(initiator_identified)
link.set_link_closed_callback(command_link_closed)
RNS.log("Command link "+str(link)+" established")
def command_link_closed(link):
RNS.log("Command link "+str(link)+" closed")
def initiator_identified(link, identity):
global allow_all
RNS.log("Initiator of link "+str(link)+" identified as "+RNS.prettyhexrep(identity.hash))
if not allow_all and not identity.hash in allowed_identity_hashes:
RNS.log("Identity "+RNS.prettyhexrep(identity.hash)+" not allowed, tearing down link")
link.teardown()
def execute_received_command(path, data, request_id, remote_identity, requested_at):
command = data[0].decode("utf-8") # Command to execute
timeout = data[1] # Timeout in seconds
o_limit = data[2] # Size limit for stdout
e_limit = data[3] # Size limit for stderr
stdin = data[4] # Data passed to stdin
if remote_identity != None:
RNS.log("Executing command ["+command+"] for "+RNS.prettyhexrep(remote_identity.hash))
else:
RNS.log("Executing command ["+command+"] for unknown requestor")
result = [
False, # 0: Command was executed
None, # 1: Return value
None, # 2: Stdout
None, # 3: Stderr
None, # 4: Total stdout length
None, # 5: Total stderr length
time.time(), # 6: Started
None, # 7: Concluded
]
try:
process = subprocess.Popen(shlex.split(command), stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
result[0] = True
except Exception as e:
result[0] = False
return result
stdout = b""
stderr = b""
timed_out = False
if stdin != None:
process.stdin.write(stdin)
while True:
try:
stdout, stderr = process.communicate(timeout=1)
if process.poll() != None:
break
if len(stdout) > 0:
print(str(stdout))
sys.stdout.flush()
except subprocess.TimeoutExpired:
pass
if timeout != None and time.time() > result[6]+timeout:
RNS.log("Command ["+command+"] timed out and is being killed...")
process.terminate()
process.wait()
if process.poll() != None:
stdout, stderr = process.communicate()
else:
stdout = None
stderr = None
break
if timeout != None and time.time() < result[6]+timeout:
result[7] = time.time()
# Deliver result
result[1] = process.returncode
if o_limit != None and len(stdout) > o_limit:
if o_limit == 0:
result[2] = b""
else:
result[2] = stdout[0:o_limit]
else:
result[2] = stdout
if e_limit != None and len(stderr) > e_limit:
if e_limit == 0:
result[3] = b""
else:
result[3] = stderr[0:e_limit]
else:
result[3] = stderr
result[4] = len(stdout)
result[5] = len(stderr)
if timed_out:
RNS.log("Command timed out")
return result
if remote_identity != None:
RNS.log("Delivering result of command ["+str(command)+"] to "+RNS.prettyhexrep(remote_identity.hash))
else:
RNS.log("Delivering result of command ["+str(command)+"] to unknown requestor")
return result
def spin(until=None, msg=None, timeout=None):
i = 0
syms = "⢄⢂⢁⡁⡈⡐⡠"
if timeout != None:
timeout = time.time()+timeout
print(msg+" ", end=" ")
while (timeout == None or time.time()<timeout) and not until():
time.sleep(0.1)
print(("\b\b"+syms[i]+" "), end="")
sys.stdout.flush()
i = (i+1)%len(syms)
print("\r"+" "*len(msg)+" \r", end="")
if timeout != None and time.time() > timeout:
return False
else:
return True
current_progress = 0.0
stats = []
speed = 0.0
def spin_stat(until=None, timeout=None):
global current_progress, response_transfer_size, speed
i = 0
syms = "⢄⢂⢁⡁⡈⡐⡠"
if timeout != None:
timeout = time.time()+timeout
while (timeout == None or time.time()<timeout) and not until():
time.sleep(0.1)
prg = current_progress
percent = round(prg * 100.0, 1)
stat_str = str(percent)+"% - " + size_str(int(prg*response_transfer_size)) + " of " + size_str(response_transfer_size) + " - " +size_str(speed, "b")+"ps"
print("\r \rReceiving result "+syms[i]+" "+stat_str, end=" ")
sys.stdout.flush()
i = (i+1)%len(syms)
print("\r \r", end="")
if timeout != None and time.time() > timeout:
return False
else:
return True
def remote_execution_done(request_receipt):
pass
def remote_execution_progress(request_receipt):
stats_max = 32
global current_progress, response_transfer_size, speed
current_progress = request_receipt.progress
response_transfer_size = request_receipt.response_transfer_size
now = time.time()
got = current_progress*response_transfer_size
entry = [now, got]
stats.append(entry)
while len(stats) > stats_max:
stats.pop(0)
span = now - stats[0][0]
if span == 0:
speed = 0
else:
diff = got - stats[0][1]
speed = diff/span
link = None
listener_destination = None
remote_exec_grace = 2.0
def execute(configdir, identitypath = None, verbosity = 0, quietness = 0, detailed = False, mirror = False, noid = False, destination = None, command = None, stdin = None, stdoutl = None, stderrl = None, timeout = RNS.Transport.PATH_REQUEST_TIMEOUT, result_timeout = None, interactive = False):
global identity, reticulum, link, listener_destination, remote_exec_grace
try:
dest_len = (RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2
if len(destination) != dest_len:
raise ValueError("Allowed destination length is invalid, must be {hex} hexadecimal characters ({byte} bytes).".format(hex=dest_len, byte=dest_len//2))
try:
destination_hash = bytes.fromhex(destination)
except Exception as e:
raise ValueError("Invalid destination entered. Check your input.")
except Exception as e:
print(str(e))
exit(241)
if reticulum == None:
targetloglevel = 3+verbosity-quietness
reticulum = RNS.Reticulum(configdir=configdir, loglevel=targetloglevel)
if identity == None:
prepare_identity(identitypath)
if not RNS.Transport.has_path(destination_hash):
RNS.Transport.request_path(destination_hash)
if not spin(until=lambda: RNS.Transport.has_path(destination_hash), msg="Path to "+RNS.prettyhexrep(destination_hash)+" requested", timeout=timeout):
print("Path not found")
exit(242)
if listener_destination == None:
listener_identity = RNS.Identity.recall(destination_hash)
listener_destination = RNS.Destination(
listener_identity,
RNS.Destination.OUT,
RNS.Destination.SINGLE,
APP_NAME,
"execute"
)
if link == None or link.status == RNS.Link.CLOSED or link.status == RNS.Link.PENDING:
link = RNS.Link(listener_destination)
link.did_identify = False
if not spin(until=lambda: link.status == RNS.Link.ACTIVE, msg="Establishing link with "+RNS.prettyhexrep(destination_hash), timeout=timeout):
print("Could not establish link with "+RNS.prettyhexrep(destination_hash))
exit(243)
if not noid and not link.did_identify:
link.identify(identity)
link.did_identify = True
if stdin != None:
stdin = stdin.encode("utf-8")
request_data = [
command.encode("utf-8"), # Command to execute
timeout, # Timeout in seconds
stdoutl, # Size limit for stdout
stderrl, # Size limit for stderr
stdin, # Data passed to stdin
]
# TODO: Tune
rexec_timeout = timeout+link.rtt*4+remote_exec_grace
request_receipt = link.request(
path="command",
data=request_data,
response_callback=remote_execution_done,
failed_callback=remote_execution_done,
progress_callback=remote_execution_progress,
timeout=rexec_timeout
)
spin(
until=lambda:link.status == RNS.Link.CLOSED or (request_receipt.status != RNS.RequestReceipt.FAILED and request_receipt.status != RNS.RequestReceipt.SENT),
msg="Sending execution request",
timeout=rexec_timeout+0.5
)
if link.status == RNS.Link.CLOSED:
print("Could not request remote execution, link was closed")
exit(244)
if request_receipt.status == RNS.RequestReceipt.FAILED:
print("Could not request remote execution")
if interactive:
return
else:
exit(244)
spin(
until=lambda:request_receipt.status != RNS.RequestReceipt.DELIVERED,
msg="Command delivered, awaiting result",
timeout=timeout
)
if request_receipt.status == RNS.RequestReceipt.FAILED:
print("No result was received")
if interactive:
return
else:
exit(245)
spin_stat(
until=lambda:request_receipt.status != RNS.RequestReceipt.RECEIVING,
timeout=result_timeout
)
if request_receipt.status == RNS.RequestReceipt.FAILED:
print("Receiving result failed")
if interactive:
return
else:
exit(246)
if request_receipt.response != None:
try:
executed = request_receipt.response[0]
retval = request_receipt.response[1]
stdout = request_receipt.response[2]
stderr = request_receipt.response[3]
outlen = request_receipt.response[4]
errlen = request_receipt.response[5]
started = request_receipt.response[6]
concluded = request_receipt.response[7]
except Exception as e:
print("Received invalid result")
if interactive:
return
else:
exit(247)
if executed:
if detailed:
if stdout != None and len(stdout) > 0:
print(stdout.decode("utf-8"), end="")
if stderr != None and len(stderr) > 0:
print(stderr.decode("utf-8"), file=sys.stderr, end="")
sys.stdout.flush()
sys.stderr.flush()
print("\n--- End of remote output, rnx done ---")
if started != None and concluded != None:
cmd_duration = round(concluded - started, 3)
print("Remote command execution took "+str(cmd_duration)+" seconds")
total_size = request_receipt.response_size
if request_receipt.request_size != None:
total_size += request_receipt.request_size
transfer_duration = round(request_receipt.response_concluded_at - request_receipt.sent_at - cmd_duration, 3)
if transfer_duration == 1:
tdstr = " in 1 second"
elif transfer_duration < 10:
tdstr = " in "+str(transfer_duration)+" seconds"
else:
tdstr = " in "+pretty_time(transfer_duration)
spdstr = ", effective rate "+size_str(total_size/transfer_duration, "b")+"ps"
print("Transferred "+size_str(total_size)+tdstr+spdstr)
if outlen != None and stdout != None:
if len(stdout) < outlen:
tstr = ", "+str(len(stdout))+" bytes displayed"
else:
tstr = ""
print("Remote wrote "+str(outlen)+" bytes to stdout"+tstr)
if errlen != None and stderr != None:
if len(stderr) < errlen:
tstr = ", "+str(len(stderr))+" bytes displayed"
else:
tstr = ""
print("Remote wrote "+str(errlen)+" bytes to stderr"+tstr)
else:
if stdout != None and len(stdout) > 0:
print(stdout.decode("utf-8"), end="")
if stderr != None and len(stderr) > 0:
print(stderr.decode("utf-8"), file=sys.stderr, end="")
if (stdoutl != 0 and len(stdout) < outlen) or (stderrl != 0 and len(stderr) < errlen):
sys.stdout.flush()
sys.stderr.flush()
print("\nOutput truncated before being returned:")
if len(stdout) != 0 and len(stdout) < outlen:
print(" stdout truncated to "+str(len(stdout))+" bytes")
if len(stderr) != 0 and len(stderr) < errlen:
print(" stderr truncated to "+str(len(stderr))+" bytes")
else:
print("Remote could not execute command")
if interactive:
return
else:
exit(248)
else:
print("No response")
if interactive:
return
else:
exit(249)
try:
if not interactive:
link.teardown()
except Exception as e:
pass
if not interactive and mirror:
if request_receipt.response[1] != None:
exit(request_receipt.response[1])
else:
exit(240)
else:
if interactive:
if mirror:
return request_receipt.response[1]
else:
return None
else:
exit(0)
def main():
try:
parser = argparse.ArgumentParser(description="Reticulum Remote Execution Utility")
parser.add_argument("destination", nargs="?", default=None, help="hexadecimal hash of the listener", type=str)
parser.add_argument("command", nargs="?", default=None, help="command to be execute", type=str)
parser.add_argument("--config", metavar="path", action="store", default=None, help="path to alternative Reticulum config directory", type=str)
parser.add_argument('-v', '--verbose', action='count', default=0, help="increase verbosity")
parser.add_argument('-q', '--quiet', action='count', default=0, help="decrease verbosity")
parser.add_argument('-p', '--print-identity', action='store_true', default=False, help="print identity and destination info and exit")
parser.add_argument("-l", '--listen', action='store_true', default=False, help="listen for incoming commands")
parser.add_argument('-i', metavar="identity", action='store', dest="identity", default=None, help="path to identity to use", type=str)
parser.add_argument("-x", '--interactive', action='store_true', default=False, help="enter interactive mode")
parser.add_argument("-b", '--no-announce', action='store_true', default=False, help="don't announce at program start")
parser.add_argument('-a', metavar="allowed_hash", dest="allowed", action='append', help="accept from this identity", type=str)
parser.add_argument('-n', '--noauth', action='store_true', default=False, help="accept files from anyone")
parser.add_argument('-N', '--noid', action='store_true', default=False, help="don't identify to listener")
parser.add_argument("-d", '--detailed', action='store_true', default=False, help="show detailed result output")
parser.add_argument("-m", action='store_true', dest="mirror", default=False, help="mirror exit code of remote command")
parser.add_argument("-w", action="store", metavar="seconds", type=float, help="connect and request timeout before giving up", default=RNS.Transport.PATH_REQUEST_TIMEOUT)
parser.add_argument("-W", action="store", metavar="seconds", type=float, help="max result download time", default=None)
parser.add_argument("--stdin", action='store', default=None, help="pass input to stdin", type=str)
parser.add_argument("--stdout", action='store', default=None, help="max size in bytes of returned stdout", type=int)
parser.add_argument("--stderr", action='store', default=None, help="max size in bytes of returned stderr", type=int)
parser.add_argument("--version", action="version", version="rnx {version}".format(version=__version__))
args = parser.parse_args()
if args.listen or args.print_identity:
listen(
configdir = args.config,
identitypath = args.identity,
verbosity=args.verbose,
quietness=args.quiet,
allowed = args.allowed,
print_identity=args.print_identity,
disable_auth=args.noauth,
disable_announce=args.no_announce,
)
elif args.destination != None and args.command != None:
execute(
configdir = args.config,
identitypath = args.identity,
verbosity = args.verbose,
quietness = args.quiet,
detailed = args.detailed,
mirror = args.mirror,
noid = args.noid,
destination = args.destination,
command = args.command,
stdin = args.stdin,
stdoutl = args.stdout,
stderrl = args.stderr,
timeout = args.w,
result_timeout = args.W,
interactive = args.interactive,
)
if args.destination != None and args.interactive:
# command_history_max = 5000
# command_history = []
# command_current = ""
# history_idx = 0
# tty.setcbreak(sys.stdin.fileno())
code = None
while True:
try:
cstr = str(code) if code and code != 0 else ""
prompt = cstr+"> "
print(prompt,end="")
# cmdbuf = b""
# while True:
# ch = sys.stdin.read(1)
# cmdbuf += ch.encode("utf-8")
# print("\r"+prompt+cmdbuf.decode("utf-8"), end="")
command = input()
if command.lower() == "exit" or command.lower() == "quit":
exit(0)
except KeyboardInterrupt:
exit(0)
except EOFError:
exit(0)
if command.lower() == "clear":
print('\033c', end='')
# command_history.append(command)
# while len(command_history) > command_history_max:
# command_history.pop(0)
else:
code = execute(
configdir = args.config,
identitypath = args.identity,
verbosity = args.verbose,
quietness = args.quiet,
detailed = args.detailed,
mirror = args.mirror,
noid = args.noid,
destination = args.destination,
command = command,
stdin = None,
stdoutl = args.stdout,
stderrl = args.stderr,
timeout = args.w,
result_timeout = args.W,
interactive = True,
)
else:
print("")
parser.print_help()
print("")
except KeyboardInterrupt:
# tty.setnocbreak(sys.stdin.fileno())
print("")
if link != None:
link.teardown()
exit()
def size_str(num, suffix='B'):
units = ['','K','M','G','T','P','E','Z']
last_unit = 'Y'
if suffix == 'b':
num *= 8
units = ['','K','M','G','T','P','E','Z']
last_unit = 'Y'
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)
num /= 1000.0
return "%.2f%s%s" % (num, last_unit, suffix)
def pretty_time(time, verbose=False):
days = int(time // (24 * 3600))
time = time % (24 * 3600)
hours = int(time // 3600)
time %= 3600
minutes = int(time // 60)
time %= 60
seconds = round(time, 2)
ss = "" if seconds == 1 else "s"
sm = "" if minutes == 1 else "s"
sh = "" if hours == 1 else "s"
sd = "" if days == 1 else "s"
components = []
if days > 0:
components.append(str(days)+" day"+sd if verbose else str(days)+"d")
if hours > 0:
components.append(str(hours)+" hour"+sh if verbose else str(hours)+"h")
if minutes > 0:
components.append(str(minutes)+" minute"+sm if verbose else str(minutes)+"m")
if seconds > 0:
components.append(str(seconds)+" second"+ss if verbose else str(seconds)+"s")
i = 0
tstr = ""
for c in components:
i += 1
if i == 1:
pass
elif i < len(components):
tstr += ", "
elif i == len(components):
tstr += " and "
tstr += c
return tstr
if __name__ == "__main__":
main()
+72
View File
@@ -37,6 +37,8 @@ from .Destination import Destination
from .Packet import Packet
from .Packet import PacketReceipt
from .Resource import Resource, ResourceAdvertisement
from .Cryptography import HKDF
from .Cryptography import Hashes
modules = glob.glob(os.path.dirname(__file__)+"/*.py")
__all__ = [ os.path.basename(f)[:-3] for f in modules if not f.endswith('__init__.py')]
@@ -151,6 +153,76 @@ def prettyhexrep(data):
hexrep = "<"+delimiter.join("{:02x}".format(c) for c in data)+">"
return hexrep
def prettysize(num, suffix='B'):
units = ['','K','M','G','T','P','E','Z']
last_unit = 'Y'
if suffix == 'b':
num *= 8
units = ['','K','M','G','T','P','E','Z']
last_unit = 'Y'
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)
num /= 1000.0
return "%.2f%s%s" % (num, last_unit, suffix)
def prettytime(time, verbose=False):
days = int(time // (24 * 3600))
time = time % (24 * 3600)
hours = int(time // 3600)
time %= 3600
minutes = int(time // 60)
time %= 60
seconds = round(time, 2)
ss = "" if seconds == 1 else "s"
sm = "" if minutes == 1 else "s"
sh = "" if hours == 1 else "s"
sd = "" if days == 1 else "s"
components = []
if days > 0:
components.append(str(days)+" day"+sd if verbose else str(days)+"d")
if hours > 0:
components.append(str(hours)+" hour"+sh if verbose else str(hours)+"h")
if minutes > 0:
components.append(str(minutes)+" minute"+sm if verbose else str(minutes)+"m")
if seconds > 0:
components.append(str(seconds)+" second"+ss if verbose else str(seconds)+"s")
i = 0
tstr = ""
for c in components:
i += 1
if i == 1:
pass
elif i < len(components):
tstr += ", "
elif i == len(components):
tstr += " and "
tstr += c
return tstr
def phyparams():
print("Required Physical Layer MTU : "+str(Reticulum.MTU)+" bytes")
print("Plaintext Packet MDU : "+str(Packet.PLAIN_MDU)+" bytes")
print("Encrypted Packet MDU : "+str(Packet.ENCRYPTED_MDU)+" bytes")
print("Link Curve : "+str(Link.CURVE))
print("Link Packet MDU : "+str(Packet.ENCRYPTED_MDU)+" bytes")
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)
+1 -1
View File
@@ -1 +1 @@
__version__ = "0.3.6"
__version__ = "0.3.10"
+1 -1
View File
@@ -25,7 +25,7 @@ async def get_sam_socket(sam_address=sam.DEFAULT_ADDRESS, loop=None):
:param loop: (optional) event loop instance
:return: A (reader, writer) pair
"""
reader, writer = await asyncio.open_connection(*sam_address, loop=loop)
reader, writer = await asyncio.open_connection(*sam_address)
writer.write(sam.hello("3.1", "3.1"))
reply = parse_reply(await reader.readline())
if reply.ok:
+48 -16
View File
@@ -85,17 +85,35 @@ class ClientTunnel(I2PTunnel):
"""A coroutine used to run the tunnel"""
await self._pre_run()
self.status = { "setup_ran": False, "setup_failed": False, "exception": None, "connect_tasks": [] }
async def handle_client(client_reader, client_writer):
"""Handle local client connection"""
remote_reader, remote_writer = await aiosam.stream_connect(
self.session_name, self.remote_destination,
sam_address=self.sam_address, loop=self.loop)
asyncio.ensure_future(proxy_data(remote_reader, client_writer),
loop=self.loop)
asyncio.ensure_future(proxy_data(client_reader, remote_writer),
loop=self.loop)
try:
sc_task = aiosam.stream_connect(
self.session_name, self.remote_destination,
sam_address=self.sam_address, loop=self.loop)
self.status["connect_tasks"].append(sc_task)
remote_reader, remote_writer = await sc_task
asyncio.ensure_future(proxy_data(remote_reader, client_writer),
loop=self.loop)
asyncio.ensure_future(proxy_data(client_reader, remote_writer),
loop=self.loop)
self.server = await asyncio.start_server(handle_client, *self.local_address, loop=self.loop)
except Exception as e:
self.status["setup_ran"] = True
self.status["setup_failed"] = True
self.status["exception"] = e
try:
self.server = await asyncio.start_server(handle_client, *self.local_address)
self.status["setup_ran"] = True
except Exception as e:
self.status["setup_ran"] = True
self.status["setup_failed"] = True
self.status["exception"] = e
def stop(self):
super().stop()
@@ -117,26 +135,39 @@ class ServerTunnel(I2PTunnel):
"""A coroutine used to run the tunnel"""
await self._pre_run()
self.status = { "setup_ran": False, "setup_failed": False, "exception": None, "connect_tasks": [] }
async def handle_client(incoming, client_reader, client_writer):
# data and dest may come in one chunk
dest, data = incoming.split(b"\n", 1)
remote_destination = sam.Destination(dest.decode())
logger.debug("{} client connected: {}.b32.i2p".format(
self.session_name, remote_destination.base32))
try:
# data and dest may come in one chunk
dest, data = incoming.split(b"\n", 1)
remote_destination = sam.Destination(dest.decode())
logger.debug("{} client connected: {}.b32.i2p".format(
self.session_name, remote_destination.base32))
except Exception as e:
self.status["exception"] = e
self.status["setup_failed"] = True
data = None
try:
remote_reader, remote_writer = await asyncio.wait_for(
sc_task = asyncio.wait_for(
asyncio.open_connection(
host=self.local_address[0],
port=self.local_address[1], loop=self.loop),
timeout=5, loop=self.loop)
port=self.local_address[1]),
timeout=5)
self.status["connect_tasks"].append(sc_task)
remote_reader, remote_writer = await sc_task
if data: remote_writer.write(data)
asyncio.ensure_future(proxy_data(remote_reader, client_writer),
loop=self.loop)
asyncio.ensure_future(proxy_data(client_reader, remote_writer),
loop=self.loop)
except ConnectionRefusedError:
client_writer.close()
self.status["exception"] = e
self.status["setup_failed"] = True
async def server_loop():
try:
@@ -151,6 +182,7 @@ class ServerTunnel(I2PTunnel):
pass
self.server_loop = asyncio.ensure_future(server_loop(), loop=self.loop)
self.status["setup_ran"] = True
def stop(self):
super().stop()
Binary file not shown.
+1 -1
View File
@@ -1,4 +1,4 @@
# Sphinx build info version 1
# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done.
config: cb185315f69a86407f1dca0f5d7d25ef
config: da80514626d78d5783ce7fca1aa23483
tags: 645f666f9bcd5a90fca523b33c5a78b7
Binary file not shown.

After

Width:  |  Height:  |  Size: 191 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 259 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 562 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 249 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 236 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 236 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

After

Width:  |  Height:  |  Size: 63 KiB

+70 -30
View File
@@ -62,7 +62,6 @@ a look at `Sideband <https://unsigned.io/sideband>`_, which is available for And
Linux and macOS.
.. image:: screenshots/sideband_1.png
:width: 400px
:align: center
:target: _images/sideband_1.png
@@ -103,16 +102,16 @@ With Reticulum, you only need to configure what interfaces you want to communica
over. There is no need to configure address spaces, subnets, routing tables,
or other things you might be used to from other network types.
Once Reticulums knows which interfaces it should use, it will automatically
Once Reticulum knows which interfaces it should use, it will automatically
discover topography and configure transport of data to any destinations it
knows about.
In situations where you already have an established WiFi or ethernet network, and
many devices that want to utilise the same external Reticulum network (for example over
many devices that want to utilise the same external Reticulum network paths (for example over
LoRa), it will often be sufficient to let one system act as a Reticulum gateway, by
adding any external interfaces to this systems configuration, and enabling transport. Any
adding any external interfaces to the configuration of this system, and then enabling transport on it. Any
other device on your local WiFi will then be able to connect to this wider Reticulum
network just using the default interface configuration.
network just using the default (:ref:`AutoInterface<interfaces-auto>`) configuration.
Possibly, the examples in the config file are enough to get you started. If
you want more information, you can read the :ref:`Building Networks<networks-main>`
@@ -137,7 +136,7 @@ Hosting a publicly reachable instance over TCP also requires a publicly reachabl
which most Internet connections don't offer anymore.
The ``I2PInterface`` routes messages through the `Invisible Internet Protocol
(I2P) <https://geti2p.net/en/>`_. To properly use this interface, users must also run an I2P daemon in
(I2P) <https://geti2p.net/en/>`_. To use this interface, users must also run an I2P daemon in
parallel to ``rnsd``. For always-on I2P nodes it is recommended to use `i2pd <https://i2pd.website/>`_.
By default, I2P will encrypt and mix all traffic sent over the Internet, and
@@ -146,12 +145,13 @@ will also relay other I2P user's encrypted packets, which will use extra
bandwidth and compute power, but also makes timing attacks and other forms of
deep-packet-inspection much more difficult.
I2P also allows users to host globally available Reticulum instances from non-public IPs and behind firewalls.
I2P also allows users to host globally available Reticulum instances from non-public IPs and behind firewalls and NAT.
In general it is recommended to use an I2P node if you want to host a publically accessible
instance, while preserving anonymity. If you care more about performance, and a slightly
easier setup, use TCP.
Connect to the Public Testnet
===========================================
@@ -160,26 +160,61 @@ by adding one of the following interfaces to your ``.reticulum/config`` file:
.. code::
# For connecting over TCP/IP:
[[RNS Testnet Frankfurt]]
# TCP/IP interface to the Dublin hub
[[RNS Testnet Dublin]]
type = TCPClientInterface
interface_enabled = yes
outgoing = True
target_host = frankfurt.rns.unsigned.io
enabled = yes
target_host = dublin.connect.reticulum.network
target_port = 4965
# TCP/IP interface to the Frankfurt hub
[[RNS Testnet Dublin]]
type = TCPClientInterface
enabled = yes
target_host = frankfurt.connect.reticulum.network
target_port = 5377
# For connecting over I2P:
[[RNS Testnet I2P Node A]]
# Interface to I2P hub A
[[RNS Testnet I2P Hub A]]
type = I2PInterface
interface_enabled = yes
peers = ykzlw5ujbaqc2xkec4cpvgyxj257wcrmmgkuxqmqcur7cq3w3lha.b32.i2p
enabled = yes
peers = uxg5kubabakh3jtnvsipingbr5574dle7bubvip7llfvwx2tgrua.b32.i2p
Many other Reticulum instances are connecting to this testnet, and you can also join it
via other entry points if you know them. There is absolutely no control over the network
topography, usage or what types of instances connect. It will also occasionally be used
to test various failure scenarios, and there are no availability or service guarantees.
Adding Radio Interfaces
==============================================
Once you have Reticulum installed and working, you can add radio interfaces with
any compatible hardware you have available. Reticulum supports a wide range of radio
hardware, and if you already have any available, it is very likely that it will
work with Reticulum. For information on how to configure this, see the
:ref:`Interfaces<interfaces-main>` section of this manual.
If you do not already have transceiver hardware available, you can easily and
cheaply build an :ref:`RNode<rnode-main>`, which is a general-purpose long-range
digital radio transceiver, that integrates easily with Reticulum.
To build one yourself requires installing a custom firmware on a supported LoRa
development board with an auto-install script. Please see the :ref:`Communications Hardware<hardware-main>`
chapter for a guide. If you prefer purchasing a ready-made unit, you can refer to the
:ref:`list of suppliers<rnode-suppliers>`. For more information on RNode, you can also
refer to these additional external resources:
* `How To Make Your Own RNodes <https://unsigned.io/how-to-make-your-own-rnodes/>`_
* `Installing RNode Firmware on Compatible LoRa Devices <https://unsigned.io/installing-rnode-firmware-on-t-beam-and-lora32-devices/>`_
* `Private, Secure and Uncensorable Messaging Over a LoRa Mesh <https://unsigned.io/private-messaging-over-lora/>`_
* `RNode Firmware <https://github.com/markqvist/RNode_Firmware/>`_
If you have communications hardware that is not already supported by any of the
:ref:`existing interface types<interfaces-main>`, but you think would be suitable for use with Reticulum,
you are welcome to head over to the `GitHub discussion pages <https://github.com/markqvist/Reticulum/discussions>`_
and propose adding an interface for the hardware.
Develop a Program with Reticulum
===========================================
If you want to develop programs that use Reticulum, the easiest way to get
@@ -310,21 +345,26 @@ It is also possible to include Reticulum in apps compiled and distributed as
Android APKs. A detailed tutorial and example source code will be included
here at a later point.
Adding Radio Interfaces
Pure-Python Reticulum
==============================================
Once you have Reticulum installed and working, you can add radio interfaces with
any compatible hardware you have available. For information on how to configure
this, see the :ref:`Interfaces<interfaces-main>` section of this manual.
In some rare cases, and on more obscure system types, it is not possible to
install one or more dependencies
A range of common LoRa development boards and transceiver modules can be used
as interfaces with Reticulum. You can refer to the following external resources
for more information:
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 ``rnspure`` package instead of the ``rns`` package. The ``rnspure``
package requires no external dependencies for installation. Please note that the
actual contents of the ``rns`` and ``rnspure`` packages are *completely identical*.
The only difference is that the ``rnspure`` package lists no dependencies required
for installation.
* `How To Make Your Own RNodes <https://unsigned.io/how-to-make-your-own-rnodes/>`_
* `Installing RNode Firmware on Compatible LoRa Devices <https://unsigned.io/installing-rnode-firmware-on-t-beam-and-lora32-devices/>`_
* `Private, Secure and Uncensorable Messaging Over a LoRa Mesh <https://unsigned.io/private-messaging-over-lora/>`_
* `RNode Firmware <https://github.com/markqvist/RNode_Firmware/>`_
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``, it is perfectly possible to do so using
the `rnspure` package, but Reticulum will not be able to use serial-based interfaces.
All other available modules will still be loaded when needed.
If you have communications hardware that you think would be suitable for use with Reticulum,
you are welcome to head over to the `GitHub discussion pages <https://github.com/markqvist/Reticulum/discussions>`_
and propose adding an interface for the hardware.
**Please Note!** If you use the `rnspure` 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 :ref:`Cryptographic Primitives <understanding-primitives>`
section of this manual.
+245
View File
@@ -0,0 +1,245 @@
.. _hardware-main:
***********************
Communications Hardware
***********************
One of the truly valuable aspects of Reticulum is the ability to use it over
almost any conceivable kind of communications medium. The :ref:`interface types<interfaces-main>`
available for configuration in Reticulum are flexible enough to cover the use
of most wired and wireless communications hardware available, from decades-old
packet radio modems to modern millimeter-wave backhaul systems.
If you already have or operate some kind of communications hardware, there is a
very good chance that it will work with Reticulum out of the box. In case it does
not, it is possible to provide the necessary glue with very little effort using
for example the :ref:`PipeInterface<interfaces-pipe>` or the :ref:`TCPClientInterface<interfaces-tcpc>`
in combination with code like `TCP KISS Server <https://github.com/simplyequipped/tcpkissserver>`_
by `simplyequipped <https://github.com/simplyequipped>`_.
While this broad support and flexibility is very useful, an abundance of options
can sometimes make it difficult to know where to begin, especially when you are
starting from scratch.
This chapter will outline a few different sensible starting paths to get
real-world functional wireless communications up and running with minimal cost
and effort. Two fundamental devices categories will be covered, *RNodes* and
*WiFi-based radios*.
While there are many other device categories that are useful in building Reticulum
networks, knowing how to employ just these two will make it possible to build
a wide range of useful networks with little effort.
.. _rnode-main:
RNode
=====
Reliable and general-purpose long-range digital radio transceiver systems are
commonly either very expensive, difficult to set up and operate, hard to source,
power-hungry, or all of the above at the same time. In an attempt to alleviate
this situation, the transceiver system *RNode* was designed. It is important to
note that RNode is not one specific device, from one particular vendor, but
*an open plaform* that anyone can use to build interoperable digital transceivers
suited to their needs and particular situations.
An RNode is a general purpose, interoperable, low-power and long-range, reliable,
open and flexible radio communications device. Depending on its components, it can
operate on many different frequency bands, and use many different modulation
schemes, but most commonly, and for the purposes of this chapter, we will limit
the discussion to RNodes using *LoRa* modulation in common ISM bands.
**Avoid Confusion!** RNodes can use LoRa as a *physical-layer modulation*, but it
does not use, and has nothing to do with the *LoRaWAN* protocol and standard, commonly
used for centrally controlled IoT devices. RNodes use *raw LoRa modulation*, without
any additional protocol overhead. All high-level protocol funcionality is handled
directly by Reticulum.
.. _rnode-creating:
Creating RNodes
^^^^^^^^^^^^^^^
RNode has been designed as a system that is easy to replicate across time and
space. You can put together a functioning transceiver using commonly available
components, and a few open source software tools. While you can design and build RNodes
completely from scratch, to your exact desired specifications, this chapter
will explain the easiest possible approach to creating RNodes: Using common
LoRa development boards. This approach can be boiled down to two simple steps:
1. Obtain one or more supported development boards
2. Install the RNode firmware with the automated installer
Once the firmware has been installed and provisioned by the install script, it
is ready to use with any software that supports RNodes, including Reticulum.
The device can be used with Reticulum by adding an :ref:`RNodeInterface<interfaces-rnode>`
to the configuration.
.. _rnode-supported:
Supported Boards
^^^^^^^^^^^^^^^^
To create one or more RNodes, you will need to obtain supported development
boards. The following boards are supported by the auto-installer.
LilyGO LoRa32 v2.1
""""""""""""""""""
.. image:: graphics/board_t3v21.png
:width: 46%
:align: center
- **Supported Firmware Lines** v1.x & v2.x
- **Transceiver IC** Semtech SX1276
- **Device Platform** ESP32
- **Manufacturer** `LilyGO <https://lilygo.cn>`_
LilyGO LoRa32 v2.0
""""""""""""""""""
.. image:: graphics/board_t3v20.png
:width: 46%
:align: center
- **Supported Firmware Lines** v1.x & v2.x
- **Transceiver IC** Semtech SX1276
- **Device Platform** ESP32
- **Manufacturer** `LilyGO <https://lilygo.cn>`_
LilyGO T-Beam
"""""""""""""
.. image:: graphics/board_tbeam.png
:width: 75%
:align: center
- **Supported Firmware Lines** v1.x & v2.x
- **Transceiver IC** Semtech SX1276
- **Device Platform** ESP32
- **Manufacturer** `LilyGO <https://lilygo.cn>`_
Heltec LoRa32 v2.0
""""""""""""""""""
.. image:: graphics/board_heltec32.png
:width: 58%
:align: center
- **Supported Firmware Lines** v1.x & v2.x
- **Transceiver IC** Semtech SX1276
- **Device Platform** ESP32
- **Manufacturer** `Heltec Automation <https://heltec.org>`_
Unsigned RNode v2.x
"""""""""""""""""""
.. image:: graphics/board_rnodev2.png
:width: 58%
:align: center
- **Supported Firmware Lines** v1.x & v2.x
- **Transceiver IC** Semtech SX1276
- **Device Platform** ESP32
- **Manufacturer** `unsigned.io <https://unsigned.io>`_
Unsigned RNode v1.x
"""""""""""""""""""
.. image:: graphics/board_rnode.png
:width: 50%
:align: center
- **Supported Firmware Lines** v1.x
- **Transceiver IC** Semtech SX1276
- **Device Platform** AVR ATmega1284p
- **Manufacturer** `unsigned.io <https://unsigned.io>`_
.. _rnode-installation:
Installation
^^^^^^^^^^^^
Once you have obtained compatible boards, you can install the `RNode Firmware <https://github.com/markqvist/RNode_Firmware>`_
using the `RNode Configuration Utility <https://github.com/markqvist/rnodeconfigutil>`_.
Make sure that ``Python3`` and ``pip`` is installed on your system, and then install
the config utility with ``pip``:
.. code::
pip3 install rnodeconf
Once installation has completed, it is time to start installing the firmware on your
devices. Run ``rnodeconf`` in auto-install mode like so:
.. code::
rnodeconf --autoinstall
The utility will guide you through the installation process by asking a series of
questions about your hardware. Simply follow the guide, and the utility will
auto-install and configure your devices
**Important Note!** It is currently recommended to use the v1.x line of the RNode firmware,
even though the v2.x line is available for early testing. The v2.x line should still be
considered an experimental pre-release. Only use the v2.x firmware line if you want to test
out the absolutely newest version, and don't care about stability.
.. _rnode-usage:
Usage with Reticulum
^^^^^^^^^^^^^^^^^^^^
When the devices have been installed and provisioned, you can use them with Reticulum
by adding the :ref:`relevant interface section<interfaces-rnode>` to the configuration
file of Reticulum. For v1.x firmwares, you will have to specify all interface parameters,
such as serial port and on-air parameters. For v2.x firmwares, you just need to specify
the Connection ID of the RNode, and Reticulum will automatically locate and connect to the
RNode, using the parameters stored in the RNode itself.
.. _rnode-suppliers:
Suppliers
^^^^^^^^^
Get in touch if you want to have your RNode supplier listed here, or if you want help to
get started with producing RNodes.
WiFi-based Hardware
===================
It is possible to use all kinds of both short- and long-range Wifi-based hardware
with Reticulum. Any kind of hardware that fully supports bridged ethernet over the
WiFi interface will work with the :ref:`AutoInterface<interfaces-auto>` in Reticulum.
Most devices will behave like this by default, or allow it via configuration options.
This means that you can simply configure the physical links of the WiFi based devices,
and start communicating over them using Reticulum. It is not necessary to enable any IP
infrastructure such as DHCP servers, DNS or similar, as long as at least Ethernet is
available, and packets are passed transparently over the physical WiFi-based devices.
.. only:: html
.. image:: graphics/radio_rblhg5.png
:width: 49%
.. image:: graphics/radio_is5ac.png
:width: 49%
Below is a list of example WiFi (and similar) radios that work well for high capacity
Reticulum links over long distances:
- `Ubiquiti airMAX radios <https://store.ui.com/collections/operator-airmax-devices>`_
- `Ubiquiti LTU radios <https://store.ui.com/collections/operator-ltu>`_
- `MikroTik radios <https://mikrotik.com/products/group/wireless-systems>`_
This list is by no means exhaustive, and only serves as a few examples of radio hardware
that is relatively cheap while providing long range and high capacity for Reticulum
networks. As in all other cases, it is also possible for Reticulum to co-exist with IP
networks running concurrently on such devices.
Combining Hardware Types
========================
It is useful to combine different link and hardware types when designing and
building a network. One useful design pattern is to employ high-capacity point-to-point
links based on WiFi or millimeter-wave radios (with high-gain directional antennas)
for the network backbone, and using LoRa-based RNodes for covering large areas with
connectivity for client devices.
+10 -6
View File
@@ -11,15 +11,19 @@ to participate in the development of Reticulum itself.
whatis
gettingstartedfast
using
networks
interfaces
understanding
hardware
interfaces
networks
reference
examples
support
Indices and Tables
==================
.. only:: html
* :ref:`genindex`
* :ref:`search`
Indices and Tables
==================
* :ref:`genindex`
* :ref:`search`
+40 -18
View File
@@ -100,8 +100,8 @@ at.
To use the I2P interface, you must have an I2P router running
on your system. The easiest way to acheive this is to download and
install the `latest release <https://github.com/PurpleI2P/i2pd/releases/latest>`_
of the ``ì2pd`` package. For more details about I2P, see the
`geti2p.net website <https://geti2p.net/en/about/intro>`_.`
of the ``i2pd`` package. For more details about I2P, see the
`geti2p.net website <https://geti2p.net/en/about/intro>`_.
When an I2P router is running on your system, you can simply add
an I2P interface to reticulum:
@@ -274,10 +274,6 @@ with all peers in your local ethernet broadcast domain, the
:ref:`Auto Interface<interfaces-auto>` performs better, and is even
easier to use.
The below example is enabled by default on new Reticulum installations,
as it provides an easy way to get started and to test Reticulum on a
pre-existing LAN.
.. code::
# This example enables communication with other
@@ -634,32 +630,58 @@ Interface Modes
The optional ``mode`` setting is available on all interfaces, and allows
selecting the high-level behaviour of the interface from a number of modes.
These modes affect how Reticulum selects paths in the network, how announces
are propagated and how long paths are valid.
are propagated, how long paths are valid and how paths are discovered.
Configuring modes on interfaces is not strictly necessary, but can be useful
when building or connecting to more complex networks. When not running a
Transport Node, it is rarely useful to configure an interface mode.
Configuring modes on interfaces is **not** strictly necessary, but can be useful
when building or connecting to more complex networks. If your Reticulum
instance is not running a Transport Node, it is rarely useful to configure
interface modes, and in such cases interfaces should generally be left in
the default mode.
* | The default value is ``full``. In this mode, all discovery,
* | The default mode is ``full``. In this mode, all discovery,
meshing and transport functionality is activated.
* | The ``gateway`` mode (or shorthand ``gw``) also has all
discovery, meshing and transport functionality available,
but will additionally try to discover unknown paths on
behalf of other nodes residing on the ``gateway`` interface.
If Reticulum receives a path request for an unknown
destination, from a node on a ``gateway`` interface, it
will try to discover this path via all other active interfaces,
and forward the discovered path to the requestor if one is
found.
| If you want to allow other nodes to widely resolve paths or connect
to a network via an interface, it might be useful to put it in this
mode. By creating a chain of ``gateway`` interfaces, other
nodes will be able to immediately discover paths to any
destination along the chain.
| *Please note!* It is the interface *facing the clients* that
must be put into ``gateway`` mode for this to work, not
the interface facing the wider network (for this, the ``boundary``
mode can be useful, though).
* | In the ``access_point`` (or shorthand ``ap``) mode, the
interface will operate as a network access point. In this
mode, announces will not be automatically broadcasted on
the interface, and paths to destinations on the interface
will have a much shorter expiry time. This mode is useful
for creating interfaces that remain quiet, unless when
someone is actually using them. An example of this could
be a radio interface serving a wide area, where users are
expected to connect momentarily, use the network, and then
disappear again.
will have a much shorter expiry time. In addition, path
requests from clients on the access point interface will
be handled in the same way as the ``gateway`` interface.
| This mode is useful for creating interfaces that remain
quiet, until someone actually starts using them. An example
of this could be a radio interface serving a wide area,
where users are expected to connect momentarily, use the
network, and then disappear again.
* | The ``roaming`` mode should be used on interfaces that are
roaming (physically mobile), seen from the perspective of
other nodes in the network. As an example, if a vehicle is
equipped with an external LoRa interface, and an internal,
WiFi-based interface, that serves devices that are moving
_with_ the vehicle, the external LoRa interface should be
*with* the vehicle, the external LoRa interface should be
configured as ``roaming``, and the internal interface can
be left in the default mode. With transport enabled, such
a setup will allow all internal devices to reach each other,
+42
View File
@@ -0,0 +1,42 @@
.. _support-main:
*****************
Support Reticulum
*****************
You can help support the continued development of open, free and private communications
systems by donating, providing feedback and contributing code and learning resources.
Donations
=========
Donations are gratefully accepted via the following channels:
.. code:: text
Monero:
84FpY1QbxHcgdseePYNmhTHcrgMX4nFfBYtz2GKYToqHVVhJp8Eaw1Z1EedRnKD19b3B8NiLCGVxzKV17UMmmeEsCrPyA5w
Ethereum:
0x81F7B979fEa6134bA9FD5c701b3501A2e61E897a
Bitcoin:
3CPmacGm34qYvR6XWLVEJmi2aNe3PZqUuq
Ko-Fi:
https://ko-fi.com/markqvist
Are certain features in the development roadmap are important to you or your
organisation? Make them a reality quickly by sponsoring their implementation.
Provide Feedback
================
All feedback on the usage, functioning and potential dysfunctioning of any and
all components of the system is very valuable to the continued development and
improvement of Reticulum. Absolutely no automated analytics, telemetly, error
reporting or statistics is collected and reported by Reticulum under any
circumstances, so we rely on old-fashioned human feedback.
Contribute Code
===============
Join us on `the GitHub repository <https://github.com/markqvist/reticulum>`_ to
report issues, suggest functionality and contribute code to Reticulum.
+83 -32
View File
@@ -117,29 +117,29 @@ Reticulum uses the singular concept of *destinations*. Any application using Ret
networking stack will need to create one or more destinations to receive data, and know the
destinations it needs to send data to.
All destinations in Reticulum are represented as a 10 byte hash, derived from truncating a full
All destinations in Reticulum are _represented_ as a 16 byte hash. This hash is derived from truncating a full
SHA-256 hash of identifying characteristics of the destination. To users, the destination addresses
will be displayed as 10 bytes in hexadecimal representation, as in the following example: ``<80e29bf7cccaf31431b3>``.
will be displayed as 16 hexadecimal bytes, like this example: ``<13425ec15b621c1d928589718000d814>``.
The truncation size of 10 bytes (80 bits) for destinations has been choosen as a reasonable tradeoff between address space
The truncation size of 16 bytes (128 bits) for destinations has been choosen as a reasonable tradeoff
between address space
and packet overhead. The address space accomodated by this size can support many billions of
simultaneously active devices on the same network, while keeping packet overhead low, which is
essential on low-bandwidth networks. In the very unlikely case that this address space nears
congestion, a one-line code change can upgrade the Reticulum address space all the way up to 256
bits, ensuring the Reticulum address space could potentially support galactic-scale networks.
This is obviusly complete and ridiculous over-allocation, and as such, the current 80 bits should
This is obviusly complete and ridiculous over-allocation, and as such, the current 128 bits should
be sufficient, even far into the future.
By default Reticulum encrypts all data using elliptic curve cryptography. Any packet sent to a
destination is encrypted with a derived ephemeral key. Reticulum can also set up an encrypted
channel to a destination with *Forward Secrecy* and *Initiator Anonymity* using a elliptic
curve cryptography and ephemeral keys derived from a Diffie Hellman exchange on Curve25519. In
Reticulum terminology, this is called a *Link*. The multi-hop transport, coordination, verification
channel to a destination, called a *Link*. Both data sent over Links and single packets offer
*Forward Secrecy* and *Initiator Anonymity*, by using an Elliptic Curve Diffie Hellman key exchange
on Curve25519 to derive ephemeral keys. The multi-hop transport, coordination, verification
and reliability layers are fully autonomous and also based on elliptic curve cryptography.
Reticulum also offers symmetric key encryption for group-oriented communications, as well as
unencrypted packets for broadcast purposes, or situations where you need the communication to be in
plain text.
unencrypted packets for local broadcast purposes.
Reticulum can connect to a variety of interfaces such as radio modems, data radios and serial ports,
and offers the possibility to easily tunnel Reticulum traffic over IP links such as the Internet or
@@ -186,7 +186,7 @@ Destination Naming
^^^^^^^^^^^^^^^^^^
Destinations are created and named in an easy to understand dotted notation of *aspects*, and
represented on the network as a hash of this value. The hash is a SHA-256 truncated to 80 bits. The
represented on the network as a hash of this value. The hash is a SHA-256 truncated to 128 bits. The
top level aspect should always be a unique identifier for the application using the destination.
The next levels of aspects can be defined in any way by the creator of the application.
@@ -202,7 +202,7 @@ application name, a device type and measurement type, like this:
aspects : remotesensor, temperature
full name : environmentlogger.remotesensor.temperature
hash : fa7ddfab5213f916dea
hash : 4faf1b2e0a077e6a9d92fa051f256038
For the *single* destination, Reticulum will automatically append the associated public key as a
destination aspect before hashing. This is done to ensure only the correct destination is reached,
@@ -493,10 +493,10 @@ terms of bandwidth, so it can be used just for a short exchange, and then recrea
also rotate encryption keys. The link can also be kept alive for longer periods of time, if this is
more suitable to the application. The procedure also inserts the *link id* , a hash calculated from the link request packet, into the memory of forwarding nodes, which means that the communicating nodes can thereafter reach each other simply by referring to this *link id*.
The combined bandwidth cost of setting up a link is 3 packets totalling 237 bytes (more info in the
The combined bandwidth cost of setting up a link is 3 packets totalling 265 bytes (more info in the
:ref:`Binary Packet Format<understanding-packetformat>` section). The amount of bandwidth used on keeping
a link open is practically negligible, at 0.62 bits per second. Even on a slow 1200 bits per second packet
radio channel, 100 concurrent links will still leave 95% channel capacity for actual data.
a link open is practically negligible, at 0.45 bits per second. Even on a slow 1200 bits per second packet
radio channel, 100 concurrent links will still leave 96% channel capacity for actual data.
Link Establishment in Detail
@@ -683,7 +683,7 @@ Wire Format
A Reticulum packet is composed of the following fields:
[HEADER 2 bytes] [ADDRESSES 10/20 bytes] [CONTEXT 1 byte] [DATA 0-477 bytes]
[HEADER 2 bytes] [ADDRESSES 16/32 bytes] [CONTEXT 1 byte] [DATA 0-465 bytes]
* The HEADER field is 2 bytes long.
* Byte 1: [IFAC Flag], [Header Type], [Propagation Type], [Destination Type] and [Packet Type]
@@ -695,15 +695,15 @@ Wire Format
capabilities and configuration.
* The ADDRESSES field contains either 1 or 2 addresses.
* Each address is 10 bytes long.
* Each address is 16 bytes long.
* The Header Type flag in the HEADER field determines
whether the ADDRESSES field contains 1 or 2 addresses.
* Addresses are Reticulum hashes truncated to 10 bytes.
* Addresses are SHA-256 hashes truncated to 16 bytes.
* The CONTEXT field is 1 byte.
* It is used by Reticulum to determine packet context.
* The DATA field is between 0 and 477 bytes.
* The DATA field is between 0 and 465 bytes.
* It contains the packets data payload.
IFAC Flag
@@ -714,8 +714,8 @@ Wire Format
Header Types
-----------------
type 1 0 Two byte header, one 10 byte address field
type 2 1 Two byte header, two 10 byte address fields
type 1 0 Two byte header, one 16 byte address field
type 2 1 Two byte header, two 16 byte address fields
Propagation Types
@@ -747,7 +747,7 @@ Wire Format
HEADER FIELD DESTINATION FIELDS CONTEXT FIELD DATA FIELD
_______|_______ ________________|________________ ________|______ __|_
| | | | | | | |
01010000 00000100 [HASH1, 10 bytes] [HASH2, 10 bytes] [CONTEXT, 1 byte] [DATA]
01010000 00000100 [HASH1, 16 bytes] [HASH2, 16 bytes] [CONTEXT, 1 byte] [DATA]
|| | | | |
|| | | | +-- Hops = 4
|| | | +------- Packet Type = DATA
@@ -762,7 +762,7 @@ Wire Format
HEADER FIELD DESTINATION FIELD CONTEXT FIELD DATA FIELD
_______|_______ _______|_______ ________|______ __|_
| | | | | | | |
00000000 00000111 [HASH1, 10 bytes] [CONTEXT, 1 byte] [DATA]
00000000 00000111 [HASH1, 16 bytes] [CONTEXT, 1 byte] [DATA]
|| | | | |
|| | | | +-- Hops = 0
|| | | +------- Packet Type = DATA
@@ -777,7 +777,7 @@ Wire Format
HEADER FIELD IFAC FIELD DESTINATION FIELD CONTEXT FIELD DATA FIELD
_______|_______ ______|______ _______|_______ ________|______ __|_
| | | | | | | | | |
10000000 00000111 [IFAC, N bytes] [HASH1, 10 bytes] [CONTEXT, 1 byte] [DATA]
10000000 00000111 [IFAC, N bytes] [HASH1, 16 bytes] [CONTEXT, 1 byte] [DATA]
|| | | | |
|| | | | +-- Hops = 0
|| | | +------- Packet Type = DATA
@@ -795,12 +795,12 @@ Wire Format
wire size counting all fields including headers,
but excluding any interface access codes.
- Path Request : 33 bytes
- Announce : 151 bytes
- Link Request : 77 bytes
- Link Proof : 77 bytes
- Link RTT packet : 83 bytes
- Link keepalive : 14 bytes
- Path Request : 51 bytes
- Announce : 157 bytes
- Link Request : 83 bytes
- Link Proof : 83 bytes
- Link RTT packet : 99 bytes
- Link keepalive : 20 bytes
.. _understanding-announcepropagation:
@@ -809,12 +809,14 @@ Announce Propagation Rules
--------------------------
The following table illustrates the rules for automatically propagating announces
from one interface type to another, for all possible combinations. See the
:ref:`Interface Modes<interfaces-modes>` section for a conceptual overview of the
different interface modes, and how they are configured.
from one interface type to another, for all possible combinations. For the purpose
of announce propagation, the *Full* and *Gateway* modes are identical.
.. image:: graphics/if_mode_graph_b.png
See the :ref:`Interface Modes<interfaces-modes>` section for a conceptual overview
of the different interface modes, and how they are configured.
..
(.. code-block:: text)
Full ────── ✓ ──┐ ┌── ✓ ── Full
@@ -837,3 +839,52 @@ different interface modes, and how they are configured.
Boundary ── ✓ ──┤ ├── ✓ ── Boundary
Roaming ─── ✕ ──┘ └── ✕ ── Roaming
.. _understanding-primitives:
Cryptographic Primitives
------------------------
Reticulum has been designed to use a simple suite of efficient, strong and modern
cryptographic primitives, with widely available implementations that can be used
both on general-purpose CPUs and on microcontrollers. The necessary primitives are:
* Ed25519 for signatures
* X22519 for ECDH key exchanges
* HKDF for key derivation
* Fernet for encrypted tokens
* AES-128 in CBC mode
* HMAC for message authentication
* SHA-256
* SHA-512
In the default installation configuration, the ``X25519``, ``Ed25519`` and ``AES-128-CBC``
primitives are provided by `OpenSSL <https://www.openssl.org/>`_ (via the `PyCA/cryptography <https://github.com/pyca/cryptography>`_
package). The hashing functions ``SHA-256`` and ``SHA-512`` are provided by the standard
Python `hashlib <https://docs.python.org/3/library/hashlib.html>`_. The ``HKDF``, ``HMAC``,
``Fernet`` primitives, and the ``PKCS7`` padding function are always provided by the
following internal implementations:
- ``RNS/Cryptography/HKDF.py``
- ``RNS/Cryptography/HMAC.py``
- ``RNS/Cryptography/Fernet.py``
- ``RNS/Cryptography/PKCS7.py``
Reticulum also includes a complete implementation of all necessary primitives in pure Python.
If OpenSSL & 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.
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.
+115 -8
View File
@@ -138,6 +138,10 @@ take a look at the :ref:`Interfaces<interfaces-main>` chapter of this manual.
Included Utility Programs
-------------------------
Reticulum includes a range of useful utilities, both for managing your Reticulum
networks, and for carrying out common tasks over Reticulum networks, such as
transferring files to remote systems, and executing commands and programs remotely.
If you often use Reticulum from several different programs, or simply want
Reticulum to stay available all the time, for example if you are hosting
a transport node, you might want to run Reticulum as a separate service that
@@ -219,7 +223,7 @@ interfaces, similar to the ``ifconfig`` program.
Traffic : 8.49 KB↑
9.23 KB↓
Reticulum Transport Instance <5245a8efe1788c6a70e1> running
Reticulum Transport Instance <5245a8efe1788c6a1cd36144a270e13b> running
.. code:: text
@@ -244,14 +248,14 @@ destinations on the Reticulum network.
.. code:: text
# Run rnpath
rnpath eca6f4e4dc26ae329e61
rnpath c89b4da064bf66d280f0e4d8abfd9806
# Example output
Path found, destination <eca6f4e4dc26ae329e61> is 4 hops away via <56b115c30cd386cad69c> on TCPInterface[Testnet/frankfurt.rns.unsigned.io:4965]
Path found, destination <c89b4da064bf66d280f0e4d8abfd9806> is 4 hops away via <f53a1c4278e0726bb73fcc623d6ce763> on TCPInterface[Testnet/frankfurt.connect.reticulu.network:4965]
.. code:: text
usage: rnpath.py [-h] [--config CONFIG] [--version] [-t] [-r] [-d] [-D] [-w seconds] [-v] [destination]
usage: rnpath [-h] [--config CONFIG] [--version] [-t] [-r] [-d] [-D] [-w seconds] [-v] [destination]
Reticulum Path Discovery Utility
@@ -281,16 +285,16 @@ destinations will not have this option enabled, and will not be probable.
.. code:: text
# Run rnprobe
python3 -m RNS.Utilities.rnprobe example_utilities.echo.request 9382f334de63217a4278
rnprobe example_utilities.echo.request 2d03725b327348980d570f739a3a5708
# Example output
Sent 16 byte probe to <9382f334de63217a4278>
Valid reply received from <9382f334de63217a4278>
Sent 16 byte probe to <2d03725b327348980d570f739a3a5708>
Valid reply received from <2d03725b327348980d570f739a3a5708>
Round-trip time is 38.469 milliseconds over 2 hops
.. code:: text
usage: rnprobe.py [-h] [--config CONFIG] [--version] [-v] [full_name] [destination_hash]
usage: rnprobe [-h] [--config CONFIG] [--version] [-v] [full_name] [destination_hash]
Reticulum Probe Utility
@@ -305,6 +309,109 @@ destinations will not have this option enabled, and will not be probable.
-v, --verbose
The rncp Utility
================
The ``rncp`` utility is a simple file transfer tool. Using it, you can transfer
files through Reticulum.
.. code:: text
# Run rncp on the receiving system, specifying which identities
# are allowed to send files
rncp --receive -a 1726dbad538775b5bf9b0ea25a4079c8 -a c50cc4e4f7838b6c31f60ab9032cbc62
# From another system, copy a file to the receiving system
rncp ~/path/to/file.tgz 73cbd378bb0286ed11a707c13447bb1e
You can specify as many allowed senders as needed, or complete disable authentication.
.. code:: text
usage: rncp [-h] [--config path] [-v] [-q] [-p] [-r] [-b] [-a allowed_hash] [-n] [-w seconds] [--version] [file] [destination]
Reticulum File Transfer Utility
positional arguments:
file file to be transferred
destination hexadecimal hash of the receiver
optional arguments:
-h, --help show this help message and exit
--config path path to alternative Reticulum config directory
-v, --verbose increase verbosity
-q, --quiet decrease verbosity
-p, --print-identity print identity and destination info and exit
-r, --receive wait for incoming files
-b, --no-announce don't announce at program start
-a allowed_hash accept from this identity
-n, --no-auth accept files from anyone
-w seconds sender timeout before giving up
--version show program's version number and exit
-v, --verbose
The rnx Utility
================
The ``rnx`` utility is a basic remote command execution program. It allows you to
execute commands on remote systems over Reticulum, and to view returned command
output.
.. code:: text
# Run rnx on the listening system, specifying which identities
# are allowed to execute commands
rncp --listen -a 941bed5e228775e5a8079fc38b1ccf3f -a 1b03013c25f1c2ca068a4f080b844a10
# From another system, run a command
rnx 7a55144adf826958a9529a3bcf08b149 "cat /proc/cpuinfo"
# Or enter the interactive mode pseudo-shell
rnx 7a55144adf826958a9529a3bcf08b149 -x
# The default identity file is stored in
# ~/.reticulum/identities/rnx, but you can use
# another one, which will be created if it does
# not already exist
rnx 7a55144adf826958a9529a3bcf08b149 -i /path/to/identity -x
You can specify as many allowed senders as needed, or completely disable authentication.
.. code:: text
usage: rnx [-h] [--config path] [-v] [-q] [-p] [-l] [-i identity] [-x] [-b] [-a allowed_hash] [-n] [-N] [-d] [-m] [-w seconds] [-W seconds] [--stdin STDIN] [--stdout STDOUT] [--stderr STDERR] [--version]
[destination] [command]
Reticulum Remote Execution Utility
positional arguments:
destination hexadecimal hash of the listener
command command to be execute
optional arguments:
-h, --help show this help message and exit
--config path path to alternative Reticulum config directory
-v, --verbose increase verbosity
-q, --quiet decrease verbosity
-p, --print-identity print identity and destination info and exit
-l, --listen listen for incoming commands
-i identity path to identity to use
-x, --interactive enter interactive mode
-b, --no-announce don't announce at program start
-a allowed_hash accept from this identity
-n, --noauth accept files from anyone
-N, --noid don't identify to listener
-d, --detailed show detailed result output
-m mirror exit code of remote command
-w seconds connect and request timeout before giving up
-W seconds max result download time
--stdin STDIN pass input to stdin
--stdout STDOUT max size in bytes of returned stdout
--stderr STDERR max size in bytes of returned stderr
--version show program's version number and exit
Improving System Configuration
------------------------------
+3 -3
View File
@@ -46,11 +46,11 @@ What does Reticulum Offer?
* Efficient link establishment
* Total bandwidth cost of setting up a link is only 3 packets, totalling 237 bytes
* Total bandwidth cost of setting up a link is only 3 packets, totalling 265 bytes
* Low cost of keeping links open at only 0.62 bits per second
* Low cost of keeping links open at only 0.44 bits per second
* Reliable and efficient transfer of arbritrary amounts of data
* Reliable and efficient transfer of arbitrary amounts of data
* Reticulum can handle a few bytes of data or files of many gigabytes
+1 -1
View File
@@ -1,6 +1,6 @@
var DOCUMENTATION_OPTIONS = {
URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'),
VERSION: '0.3.6 beta',
VERSION: '0.3.10 beta',
LANGUAGE: 'None',
COLLAPSE_INDEX: false,
BUILDER: 'html',
+44 -16
View File
@@ -5,7 +5,7 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Code Examples &#8212; Reticulum Network Stack 0.3.6 beta documentation</title>
<title>Code Examples &#8212; Reticulum Network Stack 0.3.10 beta documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/classic.css" />
@@ -16,6 +16,7 @@
<link rel="index" title="Index" href="genindex.html" />
<link rel="search" title="Search" href="search.html" />
<link rel="next" title="Support Reticulum" href="support.html" />
<link rel="prev" title="API Reference" href="reference.html" />
</head><body>
<div class="related" role="navigation" aria-label="related navigation">
@@ -24,10 +25,13 @@
<li class="right" style="margin-right: 10px">
<a href="genindex.html" title="General Index"
accesskey="I">index</a></li>
<li class="right" >
<a href="support.html" title="Support Reticulum"
accesskey="N">next</a> |</li>
<li class="right" >
<a href="reference.html" title="API Reference"
accesskey="P">previous</a> |</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.6 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.10 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">Code Examples</a></li>
</ul>
</div>
@@ -586,14 +590,16 @@ the Packet interface.</p>
<span class="c1"># We need a binary representation of the destination</span>
<span class="c1"># hash that was entered on the command line</span>
<span class="k">try</span><span class="p">:</span>
<span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">destination_hexhash</span><span class="p">)</span> <span class="o">!=</span> <span class="mi">20</span><span class="p">:</span>
<span class="n">dest_len</span> <span class="o">=</span> <span class="p">(</span><span class="n">RNS</span><span class="o">.</span><span class="n">Reticulum</span><span class="o">.</span><span class="n">TRUNCATED_HASHLENGTH</span><span class="o">//</span><span class="mi">8</span><span class="p">)</span><span class="o">*</span><span class="mi">2</span>
<span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">destination_hexhash</span><span class="p">)</span> <span class="o">!=</span> <span class="n">dest_len</span><span class="p">:</span>
<span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span>
<span class="s2">&quot;Destination length is invalid, must be 20 hexadecimal characters (10 bytes)&quot;</span>
<span class="s2">&quot;Destination length is invalid, must be </span><span class="si">{hex}</span><span class="s2"> hexadecimal characters (</span><span class="si">{byte}</span><span class="s2"> bytes).&quot;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="nb">hex</span><span class="o">=</span><span class="n">dest_len</span><span class="p">,</span> <span class="n">byte</span><span class="o">=</span><span class="n">dest_len</span><span class="o">//</span><span class="mi">2</span><span class="p">)</span>
<span class="p">)</span>
<span class="n">destination_hash</span> <span class="o">=</span> <span class="nb">bytes</span><span class="o">.</span><span class="n">fromhex</span><span class="p">(</span><span class="n">destination_hexhash</span><span class="p">)</span>
<span class="k">except</span><span class="p">:</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;Invalid destination entered. Check your input!</span><span class="se">\n</span><span class="s2">&quot;</span><span class="p">)</span>
<span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;Invalid destination entered. Check your input!&quot;</span><span class="p">)</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="nb">str</span><span class="p">(</span><span class="n">e</span><span class="p">)</span><span class="o">+</span><span class="s2">&quot;</span><span class="se">\n</span><span class="s2">&quot;</span><span class="p">)</span>
<span class="n">exit</span><span class="p">()</span>
<span class="c1"># We must first initialise Reticulum</span>
@@ -912,8 +918,12 @@ destination, and passing traffic back and forth over the link.</p>
<span class="c1"># We need a binary representation of the destination</span>
<span class="c1"># hash that was entered on the command line</span>
<span class="k">try</span><span class="p">:</span>
<span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">destination_hexhash</span><span class="p">)</span> <span class="o">!=</span> <span class="mi">20</span><span class="p">:</span>
<span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="s2">&quot;Destination length is invalid, must be 20 hexadecimal characters (10 bytes)&quot;</span><span class="p">)</span>
<span class="n">dest_len</span> <span class="o">=</span> <span class="p">(</span><span class="n">RNS</span><span class="o">.</span><span class="n">Reticulum</span><span class="o">.</span><span class="n">TRUNCATED_HASHLENGTH</span><span class="o">//</span><span class="mi">8</span><span class="p">)</span><span class="o">*</span><span class="mi">2</span>
<span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">destination_hexhash</span><span class="p">)</span> <span class="o">!=</span> <span class="n">dest_len</span><span class="p">:</span>
<span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span>
<span class="s2">&quot;Destination length is invalid, must be </span><span class="si">{hex}</span><span class="s2"> hexadecimal characters (</span><span class="si">{byte}</span><span class="s2"> bytes).&quot;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="nb">hex</span><span class="o">=</span><span class="n">dest_len</span><span class="p">,</span> <span class="n">byte</span><span class="o">=</span><span class="n">dest_len</span><span class="o">//</span><span class="mi">2</span><span class="p">)</span>
<span class="p">)</span>
<span class="n">destination_hash</span> <span class="o">=</span> <span class="nb">bytes</span><span class="o">.</span><span class="n">fromhex</span><span class="p">(</span><span class="n">destination_hexhash</span><span class="p">)</span>
<span class="k">except</span><span class="p">:</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;Invalid destination entered. Check your input!</span><span class="se">\n</span><span class="s2">&quot;</span><span class="p">)</span>
@@ -1181,7 +1191,7 @@ the link has been established.</p>
<span class="k">def</span> <span class="nf">client_disconnected</span><span class="p">(</span><span class="n">link</span><span class="p">):</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;Client disconnected&quot;</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">remote_identified</span><span class="p">(</span><span class="n">identity</span><span class="p">):</span>
<span class="k">def</span> <span class="nf">remote_identified</span><span class="p">(</span><span class="n">link</span><span class="p">,</span> <span class="n">identity</span><span class="p">):</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;Remote identified as: &quot;</span><span class="o">+</span><span class="nb">str</span><span class="p">(</span><span class="n">identity</span><span class="p">))</span>
<span class="k">def</span> <span class="nf">server_packet_received</span><span class="p">(</span><span class="n">message</span><span class="p">,</span> <span class="n">packet</span><span class="p">):</span>
@@ -1221,8 +1231,12 @@ the link has been established.</p>
<span class="c1"># We need a binary representation of the destination</span>
<span class="c1"># hash that was entered on the command line</span>
<span class="k">try</span><span class="p">:</span>
<span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">destination_hexhash</span><span class="p">)</span> <span class="o">!=</span> <span class="mi">20</span><span class="p">:</span>
<span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="s2">&quot;Destination length is invalid, must be 20 hexadecimal characters (10 bytes)&quot;</span><span class="p">)</span>
<span class="n">dest_len</span> <span class="o">=</span> <span class="p">(</span><span class="n">RNS</span><span class="o">.</span><span class="n">Reticulum</span><span class="o">.</span><span class="n">TRUNCATED_HASHLENGTH</span><span class="o">//</span><span class="mi">8</span><span class="p">)</span><span class="o">*</span><span class="mi">2</span>
<span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">destination_hexhash</span><span class="p">)</span> <span class="o">!=</span> <span class="n">dest_len</span><span class="p">:</span>
<span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span>
<span class="s2">&quot;Destination length is invalid, must be </span><span class="si">{hex}</span><span class="s2"> hexadecimal characters (</span><span class="si">{byte}</span><span class="s2"> bytes).&quot;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="nb">hex</span><span class="o">=</span><span class="n">dest_len</span><span class="p">,</span> <span class="n">byte</span><span class="o">=</span><span class="n">dest_len</span><span class="o">//</span><span class="mi">2</span><span class="p">)</span>
<span class="p">)</span>
<span class="n">destination_hash</span> <span class="o">=</span> <span class="nb">bytes</span><span class="o">.</span><span class="n">fromhex</span><span class="p">(</span><span class="n">destination_hexhash</span><span class="p">)</span>
<span class="k">except</span><span class="p">:</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;Invalid destination entered. Check your input!</span><span class="se">\n</span><span class="s2">&quot;</span><span class="p">)</span>
@@ -1524,8 +1538,12 @@ the link has been established.</p>
<span class="c1"># We need a binary representation of the destination</span>
<span class="c1"># hash that was entered on the command line</span>
<span class="k">try</span><span class="p">:</span>
<span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">destination_hexhash</span><span class="p">)</span> <span class="o">!=</span> <span class="mi">20</span><span class="p">:</span>
<span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="s2">&quot;Destination length is invalid, must be 20 hexadecimal characters (10 bytes)&quot;</span><span class="p">)</span>
<span class="n">dest_len</span> <span class="o">=</span> <span class="p">(</span><span class="n">RNS</span><span class="o">.</span><span class="n">Reticulum</span><span class="o">.</span><span class="n">TRUNCATED_HASHLENGTH</span><span class="o">//</span><span class="mi">8</span><span class="p">)</span><span class="o">*</span><span class="mi">2</span>
<span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">destination_hexhash</span><span class="p">)</span> <span class="o">!=</span> <span class="n">dest_len</span><span class="p">:</span>
<span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span>
<span class="s2">&quot;Destination length is invalid, must be </span><span class="si">{hex}</span><span class="s2"> hexadecimal characters (</span><span class="si">{byte}</span><span class="s2"> bytes).&quot;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="nb">hex</span><span class="o">=</span><span class="n">dest_len</span><span class="p">,</span> <span class="n">byte</span><span class="o">=</span><span class="n">dest_len</span><span class="o">//</span><span class="mi">2</span><span class="p">)</span>
<span class="p">)</span>
<span class="n">destination_hash</span> <span class="o">=</span> <span class="nb">bytes</span><span class="o">.</span><span class="n">fromhex</span><span class="p">(</span><span class="n">destination_hexhash</span><span class="p">)</span>
<span class="k">except</span><span class="p">:</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;Invalid destination entered. Check your input!</span><span class="se">\n</span><span class="s2">&quot;</span><span class="p">)</span>
@@ -1921,8 +1939,12 @@ interface to efficiently pass files of any size over a Reticulum <a class="refer
<span class="c1"># We need a binary representation of the destination</span>
<span class="c1"># hash that was entered on the command line</span>
<span class="k">try</span><span class="p">:</span>
<span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">destination_hexhash</span><span class="p">)</span> <span class="o">!=</span> <span class="mi">20</span><span class="p">:</span>
<span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="s2">&quot;Destination length is invalid, must be 20 hexadecimal characters (10 bytes)&quot;</span><span class="p">)</span>
<span class="n">dest_len</span> <span class="o">=</span> <span class="p">(</span><span class="n">RNS</span><span class="o">.</span><span class="n">Reticulum</span><span class="o">.</span><span class="n">TRUNCATED_HASHLENGTH</span><span class="o">//</span><span class="mi">8</span><span class="p">)</span><span class="o">*</span><span class="mi">2</span>
<span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">destination_hexhash</span><span class="p">)</span> <span class="o">!=</span> <span class="n">dest_len</span><span class="p">:</span>
<span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span>
<span class="s2">&quot;Destination length is invalid, must be </span><span class="si">{hex}</span><span class="s2"> hexadecimal characters (</span><span class="si">{byte}</span><span class="s2"> bytes).&quot;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="nb">hex</span><span class="o">=</span><span class="n">dest_len</span><span class="p">,</span> <span class="n">byte</span><span class="o">=</span><span class="n">dest_len</span><span class="o">//</span><span class="mi">2</span><span class="p">)</span>
<span class="p">)</span>
<span class="n">destination_hash</span> <span class="o">=</span> <span class="nb">bytes</span><span class="o">.</span><span class="n">fromhex</span><span class="p">(</span><span class="n">destination_hexhash</span><span class="p">)</span>
<span class="k">except</span><span class="p">:</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;Invalid destination entered. Check your input!</span><span class="se">\n</span><span class="s2">&quot;</span><span class="p">)</span>
@@ -2336,6 +2358,9 @@ interface to efficiently pass files of any size over a Reticulum <a class="refer
<h4>Previous topic</h4>
<p class="topless"><a href="reference.html"
title="previous chapter">API Reference</a></p>
<h4>Next topic</h4>
<p class="topless"><a href="support.html"
title="next chapter">Support Reticulum</a></p>
<div role="note" aria-label="source link">
<h3>This Page</h3>
<ul class="this-page-menu">
@@ -2363,10 +2388,13 @@ interface to efficiently pass files of any size over a Reticulum <a class="refer
<li class="right" style="margin-right: 10px">
<a href="genindex.html" title="General Index"
>index</a></li>
<li class="right" >
<a href="support.html" title="Support Reticulum"
>next</a> |</li>
<li class="right" >
<a href="reference.html" title="API Reference"
>previous</a> |</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.6 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.10 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">Code Examples</a></li>
</ul>
</div>
+29 -7
View File
@@ -5,7 +5,7 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Index &#8212; Reticulum Network Stack 0.3.6 beta documentation</title>
<title>Index &#8212; Reticulum Network Stack 0.3.10 beta documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/classic.css" />
@@ -23,7 +23,7 @@
<li class="right" style="margin-right: 10px">
<a href="#" title="General Index"
accesskey="I">index</a></li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.6 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.10 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">Index</a></li>
</ul>
</div>
@@ -59,12 +59,14 @@
<h2 id="A">A</h2>
<table style="width: 100%" class="indextable genindextable"><tr>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="reference.html#RNS.Resource.advertise">advertise() (RNS.Resource method)</a>
<li><a href="reference.html#RNS.Destination.accepts_links">accepts_links() (RNS.Destination method)</a>
</li>
<li><a href="reference.html#RNS.Destination.announce">announce() (RNS.Destination method)</a>
<li><a href="reference.html#RNS.Resource.advertise">advertise() (RNS.Resource method)</a>
</li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="reference.html#RNS.Destination.announce">announce() (RNS.Destination method)</a>
</li>
<li><a href="reference.html#RNS.Reticulum.ANNOUNCE_CAP">ANNOUNCE_CAP (RNS.Reticulum attribute)</a>
</li>
<li><a href="reference.html#RNS.Destination.app_and_aspects_from_name">app_and_aspects_from_name() (RNS.Destination static method)</a>
@@ -149,6 +151,12 @@
<h2 id="G">G</h2>
<table style="width: 100%" class="indextable genindextable"><tr>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="reference.html#RNS.Resource.get_data_size">get_data_size() (RNS.Resource method)</a>
</li>
<li><a href="reference.html#RNS.Resource.get_hash">get_hash() (RNS.Resource method)</a>
</li>
<li><a href="reference.html#RNS.Resource.get_parts">get_parts() (RNS.Resource method)</a>
</li>
<li><a href="reference.html#RNS.Destination.get_private_key">get_private_key() (RNS.Destination method)</a>
<ul>
@@ -176,6 +184,8 @@
<li><a href="reference.html#RNS.RequestReceipt.get_response_time">get_response_time() (RNS.RequestReceipt method)</a>
</li>
<li><a href="reference.html#RNS.PacketReceipt.get_rtt">get_rtt() (RNS.PacketReceipt method)</a>
</li>
<li><a href="reference.html#RNS.Resource.get_segments">get_segments() (RNS.Resource method)</a>
</li>
<li><a href="reference.html#RNS.PacketReceipt.get_status">get_status() (RNS.PacketReceipt method)</a>
@@ -183,6 +193,8 @@
<li><a href="reference.html#RNS.RequestReceipt.get_status">(RNS.RequestReceipt method)</a>
</li>
</ul></li>
<li><a href="reference.html#RNS.Resource.get_transfer_size">get_transfer_size() (RNS.Resource method)</a>
</li>
</ul></td>
</tr></table>
@@ -207,11 +219,13 @@
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="reference.html#RNS.Link.identify">identify() (RNS.Link method)</a>
</li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="reference.html#RNS.Identity">Identity (class in RNS)</a>
</li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="reference.html#RNS.Link.inactive_for">inactive_for() (RNS.Link method)</a>
</li>
<li><a href="reference.html#RNS.Resource.is_compressed">is_compressed() (RNS.Resource method)</a>
</li>
</ul></td>
</tr></table>
@@ -223,6 +237,8 @@
</li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="reference.html#RNS.Link.KEEPALIVE_TIMEOUT_FACTOR">KEEPALIVE_TIMEOUT_FACTOR (RNS.Link attribute)</a>
</li>
<li><a href="reference.html#RNS.Identity.KEYSIZE">KEYSIZE (RNS.Identity attribute)</a>
</li>
</ul></td>
@@ -322,6 +338,8 @@
<li><a href="reference.html#RNS.Destination.set_default_app_data">set_default_app_data() (RNS.Destination method)</a>
</li>
<li><a href="reference.html#RNS.PacketReceipt.set_delivery_callback">set_delivery_callback() (RNS.PacketReceipt method)</a>
</li>
<li><a href="reference.html#RNS.Link.set_link_closed_callback">set_link_closed_callback() (RNS.Link method)</a>
</li>
<li><a href="reference.html#RNS.Destination.set_link_established_callback">set_link_established_callback() (RNS.Destination method)</a>
</li>
@@ -359,6 +377,10 @@
<li><a href="reference.html#RNS.Identity.sign">(RNS.Identity method)</a>
</li>
</ul></li>
<li><a href="reference.html#RNS.Link.STALE_GRACE">STALE_GRACE (RNS.Link attribute)</a>
</li>
<li><a href="reference.html#RNS.Link.STALE_TIME">STALE_TIME (RNS.Link attribute)</a>
</li>
</ul></td>
</tr></table>
@@ -418,7 +440,7 @@
<li class="right" style="margin-right: 10px">
<a href="#" title="General Index"
>index</a></li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.6 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.10 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">Index</a></li>
</ul>
</div>
+72 -37
View File
@@ -5,7 +5,7 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Getting Started Fast &#8212; Reticulum Network Stack 0.3.6 beta documentation</title>
<title>Getting Started Fast &#8212; Reticulum Network Stack 0.3.10 beta documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/classic.css" />
@@ -31,7 +31,7 @@
<li class="right" >
<a href="whatis.html" title="What is Reticulum?"
accesskey="P">previous</a> |</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.6 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.10 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">Getting Started Fast</a></li>
</ul>
</div>
@@ -87,7 +87,7 @@ program, reboot your system and try again.</p>
<p>If you would rather use a program with a graphical user interface, you can take
a look at <a class="reference external" href="https://unsigned.io/sideband">Sideband</a>, which is available for Android,
Linux and macOS.</p>
<a class="reference external image-reference" href="_images/sideband_1.png"><img alt="_images/sideband_1.png" class="align-center" src="_images/sideband_1.png" style="width: 400px;" /></a>
<a class="reference external image-reference" href="_images/sideband_1.png"><img alt="_images/sideband_1.png" class="align-center" src="_images/sideband_1.png" /></a>
<p>Sideband is currently in the early stages of development, but already provides basic
communication features, and interoperates with Nomad Network, or any other LXMF client.</p>
</div>
@@ -119,15 +119,15 @@ internet, to LoRa and Packet Radio interfaces.</p>
<p>With Reticulum, you only need to configure what interfaces you want to communicate
over. There is no need to configure address spaces, subnets, routing tables,
or other things you might be used to from other network types.</p>
<p>Once Reticulums knows which interfaces it should use, it will automatically
<p>Once Reticulum knows which interfaces it should use, it will automatically
discover topography and configure transport of data to any destinations it
knows about.</p>
<p>In situations where you already have an established WiFi or ethernet network, and
many devices that want to utilise the same external Reticulum network (for example over
many devices that want to utilise the same external Reticulum network paths (for example over
LoRa), it will often be sufficient to let one system act as a Reticulum gateway, by
adding any external interfaces to this systems configuration, and enabling transport. Any
adding any external interfaces to the configuration of this system, and then enabling transport on it. Any
other device on your local WiFi will then be able to connect to this wider Reticulum
network just using the default interface configuration.</p>
network just using the default (<a class="reference internal" href="interfaces.html#interfaces-auto"><span class="std std-ref">AutoInterface</span></a>) configuration.</p>
<p>Possibly, the examples in the config file are enough to get you started. If
you want more information, you can read the <a class="reference internal" href="networks.html#networks-main"><span class="std std-ref">Building Networks</span></a>
and <a class="reference internal" href="interfaces.html#interfaces-main"><span class="std std-ref">Interfaces</span></a> chapters of this manual.</p>
@@ -148,14 +148,14 @@ packet inspection to learn that a system is running Reticulum, and what other IP
Hosting a publicly reachable instance over TCP also requires a publicly reachable IP address,
which most Internet connections dont offer anymore.</p>
<p>The <code class="docutils literal notranslate"><span class="pre">I2PInterface</span></code> routes messages through the <a class="reference external" href="https://geti2p.net/en/">Invisible Internet Protocol
(I2P)</a>. To properly use this interface, users must also run an I2P daemon in
(I2P)</a>. To use this interface, users must also run an I2P daemon in
parallel to <code class="docutils literal notranslate"><span class="pre">rnsd</span></code>. For always-on I2P nodes it is recommended to use <a class="reference external" href="https://i2pd.website/">i2pd</a>.</p>
<p>By default, I2P will encrypt and mix all traffic sent over the Internet, and
hide both the sender and receiver Reticulum instance IP addresses. Running an I2P node
will also relay other I2P users encrypted packets, which will use extra
bandwidth and compute power, but also makes timing attacks and other forms of
deep-packet-inspection much more difficult.</p>
<p>I2P also allows users to host globally available Reticulum instances from non-public IPs and behind firewalls.</p>
<p>I2P also allows users to host globally available Reticulum instances from non-public IPs and behind firewalls and NAT.</p>
<p>In general it is recommended to use an I2P node if you want to host a publically accessible
instance, while preserving anonymity. If you care more about performance, and a slightly
easier setup, use TCP.</p>
@@ -164,20 +164,25 @@ easier setup, use TCP.</p>
<h2>Connect to the Public Testnet<a class="headerlink" href="#connect-to-the-public-testnet" title="Permalink to this headline"></a></h2>
<p>An experimental public testnet has been made accessible over both I2P and TCP. You can join it
by adding one of the following interfaces to your <code class="docutils literal notranslate"><span class="pre">.reticulum/config</span></code> file:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># For connecting over TCP/IP:</span>
<span class="p">[[</span><span class="n">RNS</span> <span class="n">Testnet</span> <span class="n">Frankfurt</span><span class="p">]]</span>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># TCP/IP interface to the Dublin hub</span>
<span class="p">[[</span><span class="n">RNS</span> <span class="n">Testnet</span> <span class="n">Dublin</span><span class="p">]]</span>
<span class="nb">type</span> <span class="o">=</span> <span class="n">TCPClientInterface</span>
<span class="n">interface_enabled</span> <span class="o">=</span> <span class="n">yes</span>
<span class="n">outgoing</span> <span class="o">=</span> <span class="kc">True</span>
<span class="n">target_host</span> <span class="o">=</span> <span class="n">frankfurt</span><span class="o">.</span><span class="n">rns</span><span class="o">.</span><span class="n">unsigned</span><span class="o">.</span><span class="n">io</span>
<span class="n">enabled</span> <span class="o">=</span> <span class="n">yes</span>
<span class="n">target_host</span> <span class="o">=</span> <span class="n">dublin</span><span class="o">.</span><span class="n">connect</span><span class="o">.</span><span class="n">reticulum</span><span class="o">.</span><span class="n">network</span>
<span class="n">target_port</span> <span class="o">=</span> <span class="mi">4965</span>
<span class="c1"># TCP/IP interface to the Frankfurt hub</span>
<span class="p">[[</span><span class="n">RNS</span> <span class="n">Testnet</span> <span class="n">Dublin</span><span class="p">]]</span>
<span class="nb">type</span> <span class="o">=</span> <span class="n">TCPClientInterface</span>
<span class="n">enabled</span> <span class="o">=</span> <span class="n">yes</span>
<span class="n">target_host</span> <span class="o">=</span> <span class="n">frankfurt</span><span class="o">.</span><span class="n">connect</span><span class="o">.</span><span class="n">reticulum</span><span class="o">.</span><span class="n">network</span>
<span class="n">target_port</span> <span class="o">=</span> <span class="mi">5377</span>
<span class="c1"># For connecting over I2P:</span>
<span class="p">[[</span><span class="n">RNS</span> <span class="n">Testnet</span> <span class="n">I2P</span> <span class="n">Node</span> <span class="n">A</span><span class="p">]]</span>
<span class="c1"># Interface to I2P hub A</span>
<span class="p">[[</span><span class="n">RNS</span> <span class="n">Testnet</span> <span class="n">I2P</span> <span class="n">Hub</span> <span class="n">A</span><span class="p">]]</span>
<span class="nb">type</span> <span class="o">=</span> <span class="n">I2PInterface</span>
<span class="n">interface_enabled</span> <span class="o">=</span> <span class="n">yes</span>
<span class="n">peers</span> <span class="o">=</span> <span class="n">ykzlw5ujbaqc2xkec4cpvgyxj257wcrmmgkuxqmqcur7cq3w3lha</span><span class="o">.</span><span class="n">b32</span><span class="o">.</span><span class="n">i2p</span>
<span class="n">enabled</span> <span class="o">=</span> <span class="n">yes</span>
<span class="n">peers</span> <span class="o">=</span> <span class="n">uxg5kubabakh3jtnvsipingbr5574dle7bubvip7llfvwx2tgrua</span><span class="o">.</span><span class="n">b32</span><span class="o">.</span><span class="n">i2p</span>
</pre></div>
</div>
<p>Many other Reticulum instances are connecting to this testnet, and you can also join it
@@ -185,6 +190,32 @@ via other entry points if you know them. There is absolutely no control over the
topography, usage or what types of instances connect. It will also occasionally be used
to test various failure scenarios, and there are no availability or service guarantees.</p>
</div>
<div class="section" id="adding-radio-interfaces">
<h2>Adding Radio Interfaces<a class="headerlink" href="#adding-radio-interfaces" title="Permalink to this headline"></a></h2>
<p>Once you have Reticulum installed and working, you can add radio interfaces with
any compatible hardware you have available. Reticulum supports a wide range of radio
hardware, and if you already have any available, it is very likely that it will
work with Reticulum. For information on how to configure this, see the
<a class="reference internal" href="interfaces.html#interfaces-main"><span class="std std-ref">Interfaces</span></a> section of this manual.</p>
<p>If you do not already have transceiver hardware available, you can easily and
cheaply build an <a class="reference internal" href="hardware.html#rnode-main"><span class="std std-ref">RNode</span></a>, which is a general-purpose long-range
digital radio transceiver, that integrates easily with Reticulum.</p>
<p>To build one yourself requires installing a custom firmware on a supported LoRa
development board with an auto-install script. Please see the <a class="reference internal" href="hardware.html#hardware-main"><span class="std std-ref">Communications Hardware</span></a>
chapter for a guide. If you prefer purchasing a ready-made unit, you can refer to the
<a class="reference internal" href="hardware.html#rnode-suppliers"><span class="std std-ref">list of suppliers</span></a>. For more information on RNode, you can also
refer to these additional external resources:</p>
<ul class="simple">
<li><p><a class="reference external" href="https://unsigned.io/how-to-make-your-own-rnodes/">How To Make Your Own RNodes</a></p></li>
<li><p><a class="reference external" href="https://unsigned.io/installing-rnode-firmware-on-t-beam-and-lora32-devices/">Installing RNode Firmware on Compatible LoRa Devices</a></p></li>
<li><p><a class="reference external" href="https://unsigned.io/private-messaging-over-lora/">Private, Secure and Uncensorable Messaging Over a LoRa Mesh</a></p></li>
<li><p><a class="reference external" href="https://github.com/markqvist/RNode_Firmware/">RNode Firmware</a></p></li>
</ul>
<p>If you have communications hardware that is not already supported by any of the
<a class="reference internal" href="interfaces.html#interfaces-main"><span class="std std-ref">existing interface types</span></a>, but you think would be suitable for use with Reticulum,
you are welcome to head over to the <a class="reference external" href="https://github.com/markqvist/Reticulum/discussions">GitHub discussion pages</a>
and propose adding an interface for the hardware.</p>
</div>
<div class="section" id="develop-a-program-with-reticulum">
<h2>Develop a Program with Reticulum<a class="headerlink" href="#develop-a-program-with-reticulum" title="Permalink to this headline"></a></h2>
<p>If you want to develop programs that use Reticulum, the easiest way to get
@@ -297,23 +328,26 @@ and a few extra commands are required.</p>
Android APKs. A detailed tutorial and example source code will be included
here at a later point.</p>
</div>
<div class="section" id="adding-radio-interfaces">
<h2>Adding Radio Interfaces<a class="headerlink" href="#adding-radio-interfaces" title="Permalink to this headline"></a></h2>
<p>Once you have Reticulum installed and working, you can add radio interfaces with
any compatible hardware you have available. For information on how to configure
this, see the <a class="reference internal" href="interfaces.html#interfaces-main"><span class="std std-ref">Interfaces</span></a> section of this manual.</p>
<p>A range of common LoRa development boards and transceiver modules can be used
as interfaces with Reticulum. You can refer to the following external resources
for more information:</p>
<ul class="simple">
<li><p><a class="reference external" href="https://unsigned.io/how-to-make-your-own-rnodes/">How To Make Your Own RNodes</a></p></li>
<li><p><a class="reference external" href="https://unsigned.io/installing-rnode-firmware-on-t-beam-and-lora32-devices/">Installing RNode Firmware on Compatible LoRa Devices</a></p></li>
<li><p><a class="reference external" href="https://unsigned.io/private-messaging-over-lora/">Private, Secure and Uncensorable Messaging Over a LoRa Mesh</a></p></li>
<li><p><a class="reference external" href="https://github.com/markqvist/RNode_Firmware/">RNode Firmware</a></p></li>
</ul>
<p>If you have communications hardware that you think would be suitable for use with Reticulum,
you are welcome to head over to the <a class="reference external" href="https://github.com/markqvist/Reticulum/discussions">GitHub discussion pages</a>
and propose adding an interface for the hardware.</p>
<div class="section" id="pure-python-reticulum">
<h2>Pure-Python Reticulum<a class="headerlink" href="#pure-python-reticulum" title="Permalink to this headline"></a></h2>
<p>In some rare cases, and on more obscure system types, it is not possible to
install one or more dependencies</p>
<p>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 <code class="docutils literal notranslate"><span class="pre">rnspure</span></code> package instead of the <code class="docutils literal notranslate"><span class="pre">rns</span></code> package. The <code class="docutils literal notranslate"><span class="pre">rnspure</span></code>
package requires no external dependencies for installation. Please note that the
actual contents of the <code class="docutils literal notranslate"><span class="pre">rns</span></code> and <code class="docutils literal notranslate"><span class="pre">rnspure</span></code> packages are <em>completely identical</em>.
The only difference is that the <code class="docutils literal notranslate"><span class="pre">rnspure</span></code> package lists no dependencies required
for installation.</p>
<p>No matter how Reticulum is installed and started, it will load external dependencies
only if they are <em>needed</em> and <em>available</em>. If for example you want to use Reticulum
on a system that cannot support <code class="docutils literal notranslate"><span class="pre">pyserial</span></code>, it is perfectly possible to do so using
the <cite>rnspure</cite> package, but Reticulum will not be able to use serial-based interfaces.
All other available modules will still be loaded when needed.</p>
<p><strong>Please Note!</strong> If you use the <cite>rnspure</cite> package to run Reticulum on systems that
do not support <a class="reference external" href="https://github.com/pyca/cryptography">PyCA/cryptography</a>, it is
important that you read and understand the <a class="reference internal" href="understanding.html#understanding-primitives"><span class="std std-ref">Cryptographic Primitives</span></a>
section of this manual.</p>
</div>
</div>
@@ -336,11 +370,12 @@ and propose adding an interface for the hardware.</p>
<li><a class="reference internal" href="#creating-a-network-with-reticulum">Creating a Network With Reticulum</a></li>
<li><a class="reference internal" href="#connecting-reticulum-instances-over-the-internet">Connecting Reticulum Instances Over the Internet</a></li>
<li><a class="reference internal" href="#connect-to-the-public-testnet">Connect to the Public Testnet</a></li>
<li><a class="reference internal" href="#adding-radio-interfaces">Adding Radio Interfaces</a></li>
<li><a class="reference internal" href="#develop-a-program-with-reticulum">Develop a Program with Reticulum</a></li>
<li><a class="reference internal" href="#participate-in-reticulum-development">Participate in Reticulum Development</a></li>
<li><a class="reference internal" href="#reticulum-on-arm64">Reticulum on ARM64</a></li>
<li><a class="reference internal" href="#reticulum-on-android">Reticulum on Android</a></li>
<li><a class="reference internal" href="#adding-radio-interfaces">Adding Radio Interfaces</a></li>
<li><a class="reference internal" href="#pure-python-reticulum">Pure-Python Reticulum</a></li>
</ul>
</li>
</ul>
@@ -384,7 +419,7 @@ and propose adding an interface for the hardware.</p>
<li class="right" >
<a href="whatis.html" title="What is Reticulum?"
>previous</a> |</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.6 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.10 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">Getting Started Fast</a></li>
</ul>
</div>
+319
View File
@@ -0,0 +1,319 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Communications Hardware &#8212; Reticulum Network Stack 0.3.10 beta documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/classic.css" />
<script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
<script src="_static/jquery.js"></script>
<script src="_static/underscore.js"></script>
<script src="_static/doctools.js"></script>
<link rel="index" title="Index" href="genindex.html" />
<link rel="search" title="Search" href="search.html" />
<link rel="next" title="Supported Interfaces" href="interfaces.html" />
<link rel="prev" title="Understanding Reticulum" href="understanding.html" />
</head><body>
<div class="related" role="navigation" aria-label="related navigation">
<h3>Navigation</h3>
<ul>
<li class="right" style="margin-right: 10px">
<a href="genindex.html" title="General Index"
accesskey="I">index</a></li>
<li class="right" >
<a href="interfaces.html" title="Supported Interfaces"
accesskey="N">next</a> |</li>
<li class="right" >
<a href="understanding.html" title="Understanding Reticulum"
accesskey="P">previous</a> |</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.10 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">Communications Hardware</a></li>
</ul>
</div>
<div class="document">
<div class="documentwrapper">
<div class="bodywrapper">
<div class="body" role="main">
<div class="section" id="communications-hardware">
<span id="hardware-main"></span><h1>Communications Hardware<a class="headerlink" href="#communications-hardware" title="Permalink to this headline"></a></h1>
<p>One of the truly valuable aspects of Reticulum is the ability to use it over
almost any conceivable kind of communications medium. The <a class="reference internal" href="interfaces.html#interfaces-main"><span class="std std-ref">interface types</span></a>
available for configuration in Reticulum are flexible enough to cover the use
of most wired and wireless communications hardware available, from decades-old
packet radio modems to modern millimeter-wave backhaul systems.</p>
<p>If you already have or operate some kind of communications hardware, there is a
very good chance that it will work with Reticulum out of the box. In case it does
not, it is possible to provide the necessary glue with very little effort using
for example the <a class="reference internal" href="interfaces.html#interfaces-pipe"><span class="std std-ref">PipeInterface</span></a> or the <a class="reference internal" href="interfaces.html#interfaces-tcpc"><span class="std std-ref">TCPClientInterface</span></a>
in combination with code like <a class="reference external" href="https://github.com/simplyequipped/tcpkissserver">TCP KISS Server</a>
by <a class="reference external" href="https://github.com/simplyequipped">simplyequipped</a>.</p>
<p>While this broad support and flexibility is very useful, an abundance of options
can sometimes make it difficult to know where to begin, especially when you are
starting from scratch.</p>
<p>This chapter will outline a few different sensible starting paths to get
real-world functional wireless communications up and running with minimal cost
and effort. Two fundamental devices categories will be covered, <em>RNodes</em> and
<em>WiFi-based radios</em>.</p>
<p>While there are many other device categories that are useful in building Reticulum
networks, knowing how to employ just these two will make it possible to build
a wide range of useful networks with little effort.</p>
<div class="section" id="rnode">
<span id="rnode-main"></span><h2>RNode<a class="headerlink" href="#rnode" title="Permalink to this headline"></a></h2>
<p>Reliable and general-purpose long-range digital radio transceiver systems are
commonly either very expensive, difficult to set up and operate, hard to source,
power-hungry, or all of the above at the same time. In an attempt to alleviate
this situation, the transceiver system <em>RNode</em> was designed. It is important to
note that RNode is not one specific device, from one particular vendor, but
<em>an open plaform</em> that anyone can use to build interoperable digital transceivers
suited to their needs and particular situations.</p>
<p>An RNode is a general purpose, interoperable, low-power and long-range, reliable,
open and flexible radio communications device. Depending on its components, it can
operate on many different frequency bands, and use many different modulation
schemes, but most commonly, and for the purposes of this chapter, we will limit
the discussion to RNodes using <em>LoRa</em> modulation in common ISM bands.</p>
<p><strong>Avoid Confusion!</strong> RNodes can use LoRa as a <em>physical-layer modulation</em>, but it
does not use, and has nothing to do with the <em>LoRaWAN</em> protocol and standard, commonly
used for centrally controlled IoT devices. RNodes use <em>raw LoRa modulation</em>, without
any additional protocol overhead. All high-level protocol funcionality is handled
directly by Reticulum.</p>
<div class="section" id="creating-rnodes">
<span id="rnode-creating"></span><h3>Creating RNodes<a class="headerlink" href="#creating-rnodes" title="Permalink to this headline"></a></h3>
<p>RNode has been designed as a system that is easy to replicate across time and
space. You can put together a functioning transceiver using commonly available
components, and a few open source software tools. While you can design and build RNodes
completely from scratch, to your exact desired specifications, this chapter
will explain the easiest possible approach to creating RNodes: Using common
LoRa development boards. This approach can be boiled down to two simple steps:</p>
<ol class="arabic simple">
<li><p>Obtain one or more supported development boards</p></li>
<li><p>Install the RNode firmware with the automated installer</p></li>
</ol>
<p>Once the firmware has been installed and provisioned by the install script, it
is ready to use with any software that supports RNodes, including Reticulum.
The device can be used with Reticulum by adding an <a class="reference internal" href="interfaces.html#interfaces-rnode"><span class="std std-ref">RNodeInterface</span></a>
to the configuration.</p>
</div>
<div class="section" id="supported-boards">
<span id="rnode-supported"></span><h3>Supported Boards<a class="headerlink" href="#supported-boards" title="Permalink to this headline"></a></h3>
<p>To create one or more RNodes, you will need to obtain supported development
boards. The following boards are supported by the auto-installer.</p>
<div class="section" id="lilygo-lora32-v2-1">
<h4>LilyGO LoRa32 v2.1<a class="headerlink" href="#lilygo-lora32-v2-1" title="Permalink to this headline"></a></h4>
<a class="reference internal image-reference" href="_images/board_t3v21.png"><img alt="_images/board_t3v21.png" class="align-center" src="_images/board_t3v21.png" style="width: 46%;" /></a>
<ul class="simple">
<li><p><strong>Supported Firmware Lines</strong> v1.x &amp; v2.x</p></li>
<li><p><strong>Transceiver IC</strong> Semtech SX1276</p></li>
<li><p><strong>Device Platform</strong> ESP32</p></li>
<li><p><strong>Manufacturer</strong> <a class="reference external" href="https://lilygo.cn">LilyGO</a></p></li>
</ul>
</div>
<div class="section" id="lilygo-lora32-v2-0">
<h4>LilyGO LoRa32 v2.0<a class="headerlink" href="#lilygo-lora32-v2-0" title="Permalink to this headline"></a></h4>
<a class="reference internal image-reference" href="_images/board_t3v20.png"><img alt="_images/board_t3v20.png" class="align-center" src="_images/board_t3v20.png" style="width: 46%;" /></a>
<ul class="simple">
<li><p><strong>Supported Firmware Lines</strong> v1.x &amp; v2.x</p></li>
<li><p><strong>Transceiver IC</strong> Semtech SX1276</p></li>
<li><p><strong>Device Platform</strong> ESP32</p></li>
<li><p><strong>Manufacturer</strong> <a class="reference external" href="https://lilygo.cn">LilyGO</a></p></li>
</ul>
</div>
<div class="section" id="lilygo-t-beam">
<h4>LilyGO T-Beam<a class="headerlink" href="#lilygo-t-beam" title="Permalink to this headline"></a></h4>
<a class="reference internal image-reference" href="_images/board_tbeam.png"><img alt="_images/board_tbeam.png" class="align-center" src="_images/board_tbeam.png" style="width: 75%;" /></a>
<ul class="simple">
<li><p><strong>Supported Firmware Lines</strong> v1.x &amp; v2.x</p></li>
<li><p><strong>Transceiver IC</strong> Semtech SX1276</p></li>
<li><p><strong>Device Platform</strong> ESP32</p></li>
<li><p><strong>Manufacturer</strong> <a class="reference external" href="https://lilygo.cn">LilyGO</a></p></li>
</ul>
</div>
<div class="section" id="heltec-lora32-v2-0">
<h4>Heltec LoRa32 v2.0<a class="headerlink" href="#heltec-lora32-v2-0" title="Permalink to this headline"></a></h4>
<a class="reference internal image-reference" href="_images/board_heltec32.png"><img alt="_images/board_heltec32.png" class="align-center" src="_images/board_heltec32.png" style="width: 58%;" /></a>
<ul class="simple">
<li><p><strong>Supported Firmware Lines</strong> v1.x &amp; v2.x</p></li>
<li><p><strong>Transceiver IC</strong> Semtech SX1276</p></li>
<li><p><strong>Device Platform</strong> ESP32</p></li>
<li><p><strong>Manufacturer</strong> <a class="reference external" href="https://heltec.org">Heltec Automation</a></p></li>
</ul>
</div>
<div class="section" id="unsigned-rnode-v2-x">
<h4>Unsigned RNode v2.x<a class="headerlink" href="#unsigned-rnode-v2-x" title="Permalink to this headline"></a></h4>
<a class="reference internal image-reference" href="_images/board_rnodev2.png"><img alt="_images/board_rnodev2.png" class="align-center" src="_images/board_rnodev2.png" style="width: 58%;" /></a>
<ul class="simple">
<li><p><strong>Supported Firmware Lines</strong> v1.x &amp; v2.x</p></li>
<li><p><strong>Transceiver IC</strong> Semtech SX1276</p></li>
<li><p><strong>Device Platform</strong> ESP32</p></li>
<li><p><strong>Manufacturer</strong> <a class="reference external" href="https://unsigned.io">unsigned.io</a></p></li>
</ul>
</div>
<div class="section" id="unsigned-rnode-v1-x">
<h4>Unsigned RNode v1.x<a class="headerlink" href="#unsigned-rnode-v1-x" title="Permalink to this headline"></a></h4>
<a class="reference internal image-reference" href="_images/board_rnode.png"><img alt="_images/board_rnode.png" class="align-center" src="_images/board_rnode.png" style="width: 50%;" /></a>
<ul class="simple">
<li><p><strong>Supported Firmware Lines</strong> v1.x</p></li>
<li><p><strong>Transceiver IC</strong> Semtech SX1276</p></li>
<li><p><strong>Device Platform</strong> AVR ATmega1284p</p></li>
<li><p><strong>Manufacturer</strong> <a class="reference external" href="https://unsigned.io">unsigned.io</a></p></li>
</ul>
</div>
</div>
<div class="section" id="installation">
<span id="rnode-installation"></span><h3>Installation<a class="headerlink" href="#installation" title="Permalink to this headline"></a></h3>
<p>Once you have obtained compatible boards, you can install the <a class="reference external" href="https://github.com/markqvist/RNode_Firmware">RNode Firmware</a>
using the <a class="reference external" href="https://github.com/markqvist/rnodeconfigutil">RNode Configuration Utility</a>.
Make sure that <code class="docutils literal notranslate"><span class="pre">Python3</span></code> and <code class="docutils literal notranslate"><span class="pre">pip</span></code> is installed on your system, and then install
the config utility with <code class="docutils literal notranslate"><span class="pre">pip</span></code>:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">pip3</span> <span class="n">install</span> <span class="n">rnodeconf</span>
</pre></div>
</div>
<p>Once installation has completed, it is time to start installing the firmware on your
devices. Run <code class="docutils literal notranslate"><span class="pre">rnodeconf</span></code> in auto-install mode like so:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">rnodeconf</span> <span class="o">--</span><span class="n">autoinstall</span>
</pre></div>
</div>
<p>The utility will guide you through the installation process by asking a series of
questions about your hardware. Simply follow the guide, and the utility will
auto-install and configure your devices</p>
<p><strong>Important Note!</strong> It is currently recommended to use the v1.x line of the RNode firmware,
even though the v2.x line is available for early testing. The v2.x line should still be
considered an experimental pre-release. Only use the v2.x firmware line if you want to test
out the absolutely newest version, and dont care about stability.</p>
</div>
<div class="section" id="usage-with-reticulum">
<span id="rnode-usage"></span><h3>Usage with Reticulum<a class="headerlink" href="#usage-with-reticulum" title="Permalink to this headline"></a></h3>
<p>When the devices have been installed and provisioned, you can use them with Reticulum
by adding the <a class="reference internal" href="interfaces.html#interfaces-rnode"><span class="std std-ref">relevant interface section</span></a> to the configuration
file of Reticulum. For v1.x firmwares, you will have to specify all interface parameters,
such as serial port and on-air parameters. For v2.x firmwares, you just need to specify
the Connection ID of the RNode, and Reticulum will automatically locate and connect to the
RNode, using the parameters stored in the RNode itself.</p>
</div>
<div class="section" id="suppliers">
<span id="rnode-suppliers"></span><h3>Suppliers<a class="headerlink" href="#suppliers" title="Permalink to this headline"></a></h3>
<p>Get in touch if you want to have your RNode supplier listed here, or if you want help to
get started with producing RNodes.</p>
</div>
</div>
<div class="section" id="wifi-based-hardware">
<h2>WiFi-based Hardware<a class="headerlink" href="#wifi-based-hardware" title="Permalink to this headline"></a></h2>
<p>It is possible to use all kinds of both short- and long-range Wifi-based hardware
with Reticulum. Any kind of hardware that fully supports bridged ethernet over the
WiFi interface will work with the <a class="reference internal" href="interfaces.html#interfaces-auto"><span class="std std-ref">AutoInterface</span></a> in Reticulum.
Most devices will behave like this by default, or allow it via configuration options.</p>
<p>This means that you can simply configure the physical links of the WiFi based devices,
and start communicating over them using Reticulum. It is not necessary to enable any IP
infrastructure such as DHCP servers, DNS or similar, as long as at least Ethernet is
available, and packets are passed transparently over the physical WiFi-based devices.</p>
<a class="reference internal image-reference" href="_images/radio_rblhg5.png"><img alt="_images/radio_rblhg5.png" src="_images/radio_rblhg5.png" style="width: 49%;" /></a>
<a class="reference internal image-reference" href="_images/radio_is5ac.png"><img alt="_images/radio_is5ac.png" src="_images/radio_is5ac.png" style="width: 49%;" /></a>
<p>Below is a list of example WiFi (and similar) radios that work well for high capacity
Reticulum links over long distances:</p>
<ul class="simple">
<li><p><a class="reference external" href="https://store.ui.com/collections/operator-airmax-devices">Ubiquiti airMAX radios</a></p></li>
<li><p><a class="reference external" href="https://store.ui.com/collections/operator-ltu">Ubiquiti LTU radios</a></p></li>
<li><p><a class="reference external" href="https://mikrotik.com/products/group/wireless-systems">MikroTik radios</a></p></li>
</ul>
<p>This list is by no means exhaustive, and only serves as a few examples of radio hardware
that is relatively cheap while providing long range and high capacity for Reticulum
networks. As in all other cases, it is also possible for Reticulum to co-exist with IP
networks running concurrently on such devices.</p>
</div>
<div class="section" id="combining-hardware-types">
<h2>Combining Hardware Types<a class="headerlink" href="#combining-hardware-types" title="Permalink to this headline"></a></h2>
<p>It is useful to combine different link and hardware types when designing and
building a network. One useful design pattern is to employ high-capacity point-to-point
links based on WiFi or millimeter-wave radios (with high-gain directional antennas)
for the network backbone, and using LoRa-based RNodes for covering large areas with
connectivity for client devices.</p>
</div>
</div>
<div class="clearer"></div>
</div>
</div>
</div>
<div class="sphinxsidebar" role="navigation" aria-label="main navigation">
<div class="sphinxsidebarwrapper">
<h3><a href="index.html">Table of Contents</a></h3>
<ul>
<li><a class="reference internal" href="#">Communications Hardware</a><ul>
<li><a class="reference internal" href="#rnode">RNode</a><ul>
<li><a class="reference internal" href="#creating-rnodes">Creating RNodes</a></li>
<li><a class="reference internal" href="#supported-boards">Supported Boards</a><ul>
<li><a class="reference internal" href="#lilygo-lora32-v2-1">LilyGO LoRa32 v2.1</a></li>
<li><a class="reference internal" href="#lilygo-lora32-v2-0">LilyGO LoRa32 v2.0</a></li>
<li><a class="reference internal" href="#lilygo-t-beam">LilyGO T-Beam</a></li>
<li><a class="reference internal" href="#heltec-lora32-v2-0">Heltec LoRa32 v2.0</a></li>
<li><a class="reference internal" href="#unsigned-rnode-v2-x">Unsigned RNode v2.x</a></li>
<li><a class="reference internal" href="#unsigned-rnode-v1-x">Unsigned RNode v1.x</a></li>
</ul>
</li>
<li><a class="reference internal" href="#installation">Installation</a></li>
<li><a class="reference internal" href="#usage-with-reticulum">Usage with Reticulum</a></li>
<li><a class="reference internal" href="#suppliers">Suppliers</a></li>
</ul>
</li>
<li><a class="reference internal" href="#wifi-based-hardware">WiFi-based Hardware</a></li>
<li><a class="reference internal" href="#combining-hardware-types">Combining Hardware Types</a></li>
</ul>
</li>
</ul>
<h4>Previous topic</h4>
<p class="topless"><a href="understanding.html"
title="previous chapter">Understanding Reticulum</a></p>
<h4>Next topic</h4>
<p class="topless"><a href="interfaces.html"
title="next chapter">Supported Interfaces</a></p>
<div role="note" aria-label="source link">
<h3>This Page</h3>
<ul class="this-page-menu">
<li><a href="_sources/hardware.rst.txt"
rel="nofollow">Show Source</a></li>
</ul>
</div>
<div id="searchbox" style="display: none" role="search">
<h3 id="searchlabel">Quick search</h3>
<div class="searchformwrapper">
<form class="search" action="search.html" method="get">
<input type="text" name="q" aria-labelledby="searchlabel" />
<input type="submit" value="Go" />
</form>
</div>
</div>
<script>$('#searchbox').show(0);</script>
</div>
</div>
<div class="clearer"></div>
</div>
<div class="related" role="navigation" aria-label="related navigation">
<h3>Navigation</h3>
<ul>
<li class="right" style="margin-right: 10px">
<a href="genindex.html" title="General Index"
>index</a></li>
<li class="right" >
<a href="interfaces.html" title="Supported Interfaces"
>next</a> |</li>
<li class="right" >
<a href="understanding.html" title="Understanding Reticulum"
>previous</a> |</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.10 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">Communications Hardware</a></li>
</ul>
</div>
<div class="footer" role="contentinfo">
&#169; Copyright 2022, Mark Qvist.
Created using <a href="https://www.sphinx-doc.org/">Sphinx</a> 4.0.1.
</div>
</body>
</html>
+53 -30
View File
@@ -5,7 +5,7 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Reticulum Network Stack Manual &#8212; Reticulum Network Stack 0.3.6 beta documentation</title>
<title>Reticulum Network Stack Manual &#8212; Reticulum Network Stack 0.3.10 beta documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/classic.css" />
@@ -27,7 +27,7 @@
<li class="right" >
<a href="whatis.html" title="What is Reticulum?"
accesskey="N">next</a> |</li>
<li class="nav-item nav-item-0"><a href="#">Reticulum Network Stack 0.3.6 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-0"><a href="#">Reticulum Network Stack 0.3.10 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">Reticulum Network Stack Manual</a></li>
</ul>
</div>
@@ -62,11 +62,12 @@ to participate in the development of Reticulum itself.</p>
<li class="toctree-l2"><a class="reference internal" href="gettingstartedfast.html#creating-a-network-with-reticulum">Creating a Network With Reticulum</a></li>
<li class="toctree-l2"><a class="reference internal" href="gettingstartedfast.html#connecting-reticulum-instances-over-the-internet">Connecting Reticulum Instances Over the Internet</a></li>
<li class="toctree-l2"><a class="reference internal" href="gettingstartedfast.html#connect-to-the-public-testnet">Connect to the Public Testnet</a></li>
<li class="toctree-l2"><a class="reference internal" href="gettingstartedfast.html#adding-radio-interfaces">Adding Radio Interfaces</a></li>
<li class="toctree-l2"><a class="reference internal" href="gettingstartedfast.html#develop-a-program-with-reticulum">Develop a Program with Reticulum</a></li>
<li class="toctree-l2"><a class="reference internal" href="gettingstartedfast.html#participate-in-reticulum-development">Participate in Reticulum Development</a></li>
<li class="toctree-l2"><a class="reference internal" href="gettingstartedfast.html#reticulum-on-arm64">Reticulum on ARM64</a></li>
<li class="toctree-l2"><a class="reference internal" href="gettingstartedfast.html#reticulum-on-android">Reticulum on Android</a></li>
<li class="toctree-l2"><a class="reference internal" href="gettingstartedfast.html#adding-radio-interfaces">Adding Radio Interfaces</a></li>
<li class="toctree-l2"><a class="reference internal" href="gettingstartedfast.html#pure-python-reticulum">Pure-Python Reticulum</a></li>
</ul>
</li>
<li class="toctree-l1"><a class="reference internal" href="using.html">Using Reticulum on Your System</a><ul>
@@ -76,6 +77,8 @@ to participate in the development of Reticulum itself.</p>
<li class="toctree-l3"><a class="reference internal" href="using.html#the-rnstatus-utility">The rnstatus Utility</a></li>
<li class="toctree-l3"><a class="reference internal" href="using.html#the-rnpath-utility">The rnpath Utility</a></li>
<li class="toctree-l3"><a class="reference internal" href="using.html#the-rnprobe-utility">The rnprobe Utility</a></li>
<li class="toctree-l3"><a class="reference internal" href="using.html#the-rncp-utility">The rncp Utility</a></li>
<li class="toctree-l3"><a class="reference internal" href="using.html#the-rnx-utility">The rnx Utility</a></li>
</ul>
</li>
<li class="toctree-l2"><a class="reference internal" href="using.html#improving-system-configuration">Improving System Configuration</a><ul>
@@ -85,32 +88,6 @@ to participate in the development of Reticulum itself.</p>
</li>
</ul>
</li>
<li class="toctree-l1"><a class="reference internal" href="networks.html">Building Networks</a><ul>
<li class="toctree-l2"><a class="reference internal" href="networks.html#concepts-overview">Concepts &amp; Overview</a></li>
<li class="toctree-l2"><a class="reference internal" href="networks.html#example-scenarios">Example Scenarios</a><ul>
<li class="toctree-l3"><a class="reference internal" href="networks.html#interconnected-lora-sites">Interconnected LoRa Sites</a></li>
<li class="toctree-l3"><a class="reference internal" href="networks.html#bridging-over-the-internet">Bridging Over the Internet</a></li>
<li class="toctree-l3"><a class="reference internal" href="networks.html#growth-and-convergence">Growth and Convergence</a></li>
</ul>
</li>
</ul>
</li>
<li class="toctree-l1"><a class="reference internal" href="interfaces.html">Supported Interfaces</a><ul>
<li class="toctree-l2"><a class="reference internal" href="interfaces.html#auto-interface">Auto Interface</a></li>
<li class="toctree-l2"><a class="reference internal" href="interfaces.html#i2p-interface">I2P Interface</a></li>
<li class="toctree-l2"><a class="reference internal" href="interfaces.html#tcp-server-interface">TCP Server Interface</a></li>
<li class="toctree-l2"><a class="reference internal" href="interfaces.html#tcp-client-interface">TCP Client Interface</a></li>
<li class="toctree-l2"><a class="reference internal" href="interfaces.html#udp-interface">UDP Interface</a></li>
<li class="toctree-l2"><a class="reference internal" href="interfaces.html#rnode-lora-interface">RNode LoRa Interface</a></li>
<li class="toctree-l2"><a class="reference internal" href="interfaces.html#serial-interface">Serial Interface</a></li>
<li class="toctree-l2"><a class="reference internal" href="interfaces.html#pipe-interface">Pipe Interface</a></li>
<li class="toctree-l2"><a class="reference internal" href="interfaces.html#kiss-interface">KISS Interface</a></li>
<li class="toctree-l2"><a class="reference internal" href="interfaces.html#ax-25-kiss-interface">AX.25 KISS Interface</a></li>
<li class="toctree-l2"><a class="reference internal" href="interfaces.html#common-interface-options">Common Interface Options</a></li>
<li class="toctree-l2"><a class="reference internal" href="interfaces.html#interface-modes">Interface Modes</a></li>
<li class="toctree-l2"><a class="reference internal" href="interfaces.html#announce-rate-control">Announce Rate Control</a></li>
</ul>
</li>
<li class="toctree-l1"><a class="reference internal" href="understanding.html">Understanding Reticulum</a><ul>
<li class="toctree-l2"><a class="reference internal" href="understanding.html#motivation">Motivation</a></li>
<li class="toctree-l2"><a class="reference internal" href="understanding.html#goals">Goals</a></li>
@@ -134,6 +111,46 @@ to participate in the development of Reticulum itself.</p>
<li class="toctree-l3"><a class="reference internal" href="understanding.html#interface-access-codes">Interface Access Codes</a></li>
<li class="toctree-l3"><a class="reference internal" href="understanding.html#wire-format">Wire Format</a></li>
<li class="toctree-l3"><a class="reference internal" href="understanding.html#announce-propagation-rules">Announce Propagation Rules</a></li>
<li class="toctree-l3"><a class="reference internal" href="understanding.html#cryptographic-primitives">Cryptographic Primitives</a></li>
</ul>
</li>
</ul>
</li>
<li class="toctree-l1"><a class="reference internal" href="hardware.html">Communications Hardware</a><ul>
<li class="toctree-l2"><a class="reference internal" href="hardware.html#rnode">RNode</a><ul>
<li class="toctree-l3"><a class="reference internal" href="hardware.html#creating-rnodes">Creating RNodes</a></li>
<li class="toctree-l3"><a class="reference internal" href="hardware.html#supported-boards">Supported Boards</a></li>
<li class="toctree-l3"><a class="reference internal" href="hardware.html#installation">Installation</a></li>
<li class="toctree-l3"><a class="reference internal" href="hardware.html#usage-with-reticulum">Usage with Reticulum</a></li>
<li class="toctree-l3"><a class="reference internal" href="hardware.html#suppliers">Suppliers</a></li>
</ul>
</li>
<li class="toctree-l2"><a class="reference internal" href="hardware.html#wifi-based-hardware">WiFi-based Hardware</a></li>
<li class="toctree-l2"><a class="reference internal" href="hardware.html#combining-hardware-types">Combining Hardware Types</a></li>
</ul>
</li>
<li class="toctree-l1"><a class="reference internal" href="interfaces.html">Supported Interfaces</a><ul>
<li class="toctree-l2"><a class="reference internal" href="interfaces.html#auto-interface">Auto Interface</a></li>
<li class="toctree-l2"><a class="reference internal" href="interfaces.html#i2p-interface">I2P Interface</a></li>
<li class="toctree-l2"><a class="reference internal" href="interfaces.html#tcp-server-interface">TCP Server Interface</a></li>
<li class="toctree-l2"><a class="reference internal" href="interfaces.html#tcp-client-interface">TCP Client Interface</a></li>
<li class="toctree-l2"><a class="reference internal" href="interfaces.html#udp-interface">UDP Interface</a></li>
<li class="toctree-l2"><a class="reference internal" href="interfaces.html#rnode-lora-interface">RNode LoRa Interface</a></li>
<li class="toctree-l2"><a class="reference internal" href="interfaces.html#serial-interface">Serial Interface</a></li>
<li class="toctree-l2"><a class="reference internal" href="interfaces.html#pipe-interface">Pipe Interface</a></li>
<li class="toctree-l2"><a class="reference internal" href="interfaces.html#kiss-interface">KISS Interface</a></li>
<li class="toctree-l2"><a class="reference internal" href="interfaces.html#ax-25-kiss-interface">AX.25 KISS Interface</a></li>
<li class="toctree-l2"><a class="reference internal" href="interfaces.html#common-interface-options">Common Interface Options</a></li>
<li class="toctree-l2"><a class="reference internal" href="interfaces.html#interface-modes">Interface Modes</a></li>
<li class="toctree-l2"><a class="reference internal" href="interfaces.html#announce-rate-control">Announce Rate Control</a></li>
</ul>
</li>
<li class="toctree-l1"><a class="reference internal" href="networks.html">Building Networks</a><ul>
<li class="toctree-l2"><a class="reference internal" href="networks.html#concepts-overview">Concepts &amp; Overview</a></li>
<li class="toctree-l2"><a class="reference internal" href="networks.html#example-scenarios">Example Scenarios</a><ul>
<li class="toctree-l3"><a class="reference internal" href="networks.html#interconnected-lora-sites">Interconnected LoRa Sites</a></li>
<li class="toctree-l3"><a class="reference internal" href="networks.html#bridging-over-the-internet">Bridging Over the Internet</a></li>
<li class="toctree-l3"><a class="reference internal" href="networks.html#growth-and-convergence">Growth and Convergence</a></li>
</ul>
</li>
</ul>
@@ -164,6 +181,12 @@ to participate in the development of Reticulum itself.</p>
<li class="toctree-l2"><a class="reference internal" href="examples.html#filetransfer">Filetransfer</a></li>
</ul>
</li>
<li class="toctree-l1"><a class="reference internal" href="support.html">Support Reticulum</a><ul>
<li class="toctree-l2"><a class="reference internal" href="support.html#donations">Donations</a></li>
<li class="toctree-l2"><a class="reference internal" href="support.html#provide-feedback">Provide Feedback</a></li>
<li class="toctree-l2"><a class="reference internal" href="support.html#contribute-code">Contribute Code</a></li>
</ul>
</li>
</ul>
</div>
<div class="section" id="indices-and-tables">
@@ -223,7 +246,7 @@ to participate in the development of Reticulum itself.</p>
<li class="right" >
<a href="whatis.html" title="What is Reticulum?"
>next</a> |</li>
<li class="nav-item nav-item-0"><a href="#">Reticulum Network Stack 0.3.6 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-0"><a href="#">Reticulum Network Stack 0.3.10 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">Reticulum Network Stack Manual</a></li>
</ul>
</div>
+58 -30
View File
@@ -5,7 +5,7 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Supported Interfaces &#8212; Reticulum Network Stack 0.3.6 beta documentation</title>
<title>Supported Interfaces &#8212; Reticulum Network Stack 0.3.10 beta documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/classic.css" />
@@ -16,8 +16,8 @@
<link rel="index" title="Index" href="genindex.html" />
<link rel="search" title="Search" href="search.html" />
<link rel="next" title="Understanding Reticulum" href="understanding.html" />
<link rel="prev" title="Building Networks" href="networks.html" />
<link rel="next" title="Building Networks" href="networks.html" />
<link rel="prev" title="Communications Hardware" href="hardware.html" />
</head><body>
<div class="related" role="navigation" aria-label="related navigation">
<h3>Navigation</h3>
@@ -26,12 +26,12 @@
<a href="genindex.html" title="General Index"
accesskey="I">index</a></li>
<li class="right" >
<a href="understanding.html" title="Understanding Reticulum"
<a href="networks.html" title="Building Networks"
accesskey="N">next</a> |</li>
<li class="right" >
<a href="networks.html" title="Building Networks"
<a href="hardware.html" title="Communications Hardware"
accesskey="P">previous</a> |</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.6 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.10 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">Supported Interfaces</a></li>
</ul>
</div>
@@ -121,8 +121,8 @@ at.</p>
<p>To use the I2P interface, you must have an I2P router running
on your system. The easiest way to acheive this is to download and
install the <a class="reference external" href="https://github.com/PurpleI2P/i2pd/releases/latest">latest release</a>
of the <code class="docutils literal notranslate"><span class="pre">ì2pd</span></code> package. For more details about I2P, see the
<a class="reference external" href="https://geti2p.net/en/about/intro">geti2p.net website</a>.`</p>
of the <code class="docutils literal notranslate"><span class="pre">i2pd</span></code> package. For more details about I2P, see the
<a class="reference external" href="https://geti2p.net/en/about/intro">geti2p.net website</a>.</p>
<p>When an I2P router is running on your system, you can simply add
an I2P interface to reticulum:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="p">[[</span><span class="n">I2P</span><span class="p">]]</span>
@@ -263,9 +263,6 @@ especially on WiFi. If your goal is simply to enable easy communication
with all peers in your local ethernet broadcast domain, the
<a class="reference internal" href="#interfaces-auto"><span class="std std-ref">Auto Interface</span></a> performs better, and is even
easier to use.</p>
<p>The below example is enabled by default on new Reticulum installations,
as it provides an easy way to get started and to test Reticulum on a
pre-existing LAN.</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># This example enables communication with other</span>
<span class="c1"># local Reticulum peers over UDP.</span>
@@ -611,28 +608,59 @@ option, to set the interface speed in <em>bits per second</em>.</div>
<p>The optional <code class="docutils literal notranslate"><span class="pre">mode</span></code> setting is available on all interfaces, and allows
selecting the high-level behaviour of the interface from a number of modes.
These modes affect how Reticulum selects paths in the network, how announces
are propagated and how long paths are valid.</p>
<p>Configuring modes on interfaces is not strictly necessary, but can be useful
when building or connecting to more complex networks. When not running a
Transport Node, it is rarely useful to configure an interface mode.</p>
are propagated, how long paths are valid and how paths are discovered.</p>
<p>Configuring modes on interfaces is <strong>not</strong> strictly necessary, but can be useful
when building or connecting to more complex networks. If your Reticulum
instance is not running a Transport Node, it is rarely useful to configure
interface modes, and in such cases interfaces should generally be left in
the default mode.</p>
<blockquote>
<div><ul>
<li><div class="line-block">
<div class="line">The default value is <code class="docutils literal notranslate"><span class="pre">full</span></code>. In this mode, all discovery,
<div class="line">The default mode is <code class="docutils literal notranslate"><span class="pre">full</span></code>. In this mode, all discovery,
meshing and transport functionality is activated.</div>
</div>
</li>
<li><div class="line-block">
<div class="line">The <code class="docutils literal notranslate"><span class="pre">gateway</span></code> mode (or shorthand <code class="docutils literal notranslate"><span class="pre">gw</span></code>) also has all
discovery, meshing and transport functionality available,
but will additionally try to discover unknown paths on
behalf of other nodes residing on the <code class="docutils literal notranslate"><span class="pre">gateway</span></code> interface.
If Reticulum receives a path request for an unknown
destination, from a node on a <code class="docutils literal notranslate"><span class="pre">gateway</span></code> interface, it
will try to discover this path via all other active interfaces,
and forward the discovered path to the requestor if one is
found.</div>
</div>
<div class="line-block">
<div class="line">If you want to allow other nodes to widely resolve paths or connect
to a network via an interface, it might be useful to put it in this
mode. By creating a chain of <code class="docutils literal notranslate"><span class="pre">gateway</span></code> interfaces, other
nodes will be able to immediately discover paths to any
destination along the chain.</div>
</div>
<div class="line-block">
<div class="line"><em>Please note!</em> It is the interface <em>facing the clients</em> that
must be put into <code class="docutils literal notranslate"><span class="pre">gateway</span></code> mode for this to work, not
the interface facing the wider network (for this, the <code class="docutils literal notranslate"><span class="pre">boundary</span></code>
mode can be useful, though).</div>
</div>
</li>
<li><div class="line-block">
<div class="line">In the <code class="docutils literal notranslate"><span class="pre">access_point</span></code> (or shorthand <code class="docutils literal notranslate"><span class="pre">ap</span></code>) mode, the
interface will operate as a network access point. In this
mode, announces will not be automatically broadcasted on
the interface, and paths to destinations on the interface
will have a much shorter expiry time. This mode is useful
for creating interfaces that remain quiet, unless when
someone is actually using them. An example of this could
be a radio interface serving a wide area, where users are
expected to connect momentarily, use the network, and then
disappear again.</div>
will have a much shorter expiry time. In addition, path
requests from clients on the access point interface will
be handled in the same way as the <code class="docutils literal notranslate"><span class="pre">gateway</span></code> interface.</div>
</div>
<div class="line-block">
<div class="line">This mode is useful for creating interfaces that remain
quiet, until someone actually starts using them. An example
of this could be a radio interface serving a wide area,
where users are expected to connect momentarily, use the
network, and then disappear again.</div>
</div>
</li>
<li><div class="line-block">
@@ -641,7 +669,7 @@ roaming (physically mobile), seen from the perspective of
other nodes in the network. As an example, if a vehicle is
equipped with an external LoRa interface, and an internal,
WiFi-based interface, that serves devices that are moving
_with_ the vehicle, the external LoRa interface should be
<em>with</em> the vehicle, the external LoRa interface should be
configured as <code class="docutils literal notranslate"><span class="pre">roaming</span></code>, and the internal interface can
be left in the default mode. With transport enabled, such
a setup will allow all internal devices to reach each other,
@@ -745,11 +773,11 @@ that a large span of network types can seamlessly <em>co-exist</em> and intercon
</ul>
<h4>Previous topic</h4>
<p class="topless"><a href="networks.html"
title="previous chapter">Building Networks</a></p>
<p class="topless"><a href="hardware.html"
title="previous chapter">Communications Hardware</a></p>
<h4>Next topic</h4>
<p class="topless"><a href="understanding.html"
title="next chapter">Understanding Reticulum</a></p>
<p class="topless"><a href="networks.html"
title="next chapter">Building Networks</a></p>
<div role="note" aria-label="source link">
<h3>This Page</h3>
<ul class="this-page-menu">
@@ -778,12 +806,12 @@ that a large span of network types can seamlessly <em>co-exist</em> and intercon
<a href="genindex.html" title="General Index"
>index</a></li>
<li class="right" >
<a href="understanding.html" title="Understanding Reticulum"
<a href="networks.html" title="Building Networks"
>next</a> |</li>
<li class="right" >
<a href="networks.html" title="Building Networks"
<a href="hardware.html" title="Communications Hardware"
>previous</a> |</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.6 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.10 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">Supported Interfaces</a></li>
</ul>
</div>
+13 -13
View File
@@ -5,7 +5,7 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Building Networks &#8212; Reticulum Network Stack 0.3.6 beta documentation</title>
<title>Building Networks &#8212; Reticulum Network Stack 0.3.10 beta documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/classic.css" />
@@ -16,8 +16,8 @@
<link rel="index" title="Index" href="genindex.html" />
<link rel="search" title="Search" href="search.html" />
<link rel="next" title="Supported Interfaces" href="interfaces.html" />
<link rel="prev" title="Using Reticulum on Your System" href="using.html" />
<link rel="next" title="API Reference" href="reference.html" />
<link rel="prev" title="Supported Interfaces" href="interfaces.html" />
</head><body>
<div class="related" role="navigation" aria-label="related navigation">
<h3>Navigation</h3>
@@ -26,12 +26,12 @@
<a href="genindex.html" title="General Index"
accesskey="I">index</a></li>
<li class="right" >
<a href="interfaces.html" title="Supported Interfaces"
<a href="reference.html" title="API Reference"
accesskey="N">next</a> |</li>
<li class="right" >
<a href="using.html" title="Using Reticulum on Your System"
<a href="interfaces.html" title="Supported Interfaces"
accesskey="P">previous</a> |</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.6 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.10 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">Building Networks</a></li>
</ul>
</div>
@@ -234,11 +234,11 @@ connected outliers are now an integral part of the network.</p>
</ul>
<h4>Previous topic</h4>
<p class="topless"><a href="using.html"
title="previous chapter">Using Reticulum on Your System</a></p>
<h4>Next topic</h4>
<p class="topless"><a href="interfaces.html"
title="next chapter">Supported Interfaces</a></p>
title="previous chapter">Supported Interfaces</a></p>
<h4>Next topic</h4>
<p class="topless"><a href="reference.html"
title="next chapter">API Reference</a></p>
<div role="note" aria-label="source link">
<h3>This Page</h3>
<ul class="this-page-menu">
@@ -267,12 +267,12 @@ connected outliers are now an integral part of the network.</p>
<a href="genindex.html" title="General Index"
>index</a></li>
<li class="right" >
<a href="interfaces.html" title="Supported Interfaces"
<a href="reference.html" title="API Reference"
>next</a> |</li>
<li class="right" >
<a href="using.html" title="Using Reticulum on Your System"
<a href="interfaces.html" title="Supported Interfaces"
>previous</a> |</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.6 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.10 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">Building Networks</a></li>
</ul>
</div>
Binary file not shown.
+125 -17
View File
@@ -5,7 +5,7 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>API Reference &#8212; Reticulum Network Stack 0.3.6 beta documentation</title>
<title>API Reference &#8212; Reticulum Network Stack 0.3.10 beta documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/classic.css" />
@@ -17,7 +17,7 @@
<link rel="index" title="Index" href="genindex.html" />
<link rel="search" title="Search" href="search.html" />
<link rel="next" title="Code Examples" href="examples.html" />
<link rel="prev" title="Understanding Reticulum" href="understanding.html" />
<link rel="prev" title="Building Networks" href="networks.html" />
</head><body>
<div class="related" role="navigation" aria-label="related navigation">
<h3>Navigation</h3>
@@ -29,9 +29,9 @@
<a href="examples.html" title="Code Examples"
accesskey="N">next</a> |</li>
<li class="right" >
<a href="understanding.html" title="Understanding Reticulum"
<a href="networks.html" title="Building Networks"
accesskey="P">previous</a> |</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.6 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.10 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">API Reference</a></li>
</ul>
</div>
@@ -157,7 +157,7 @@ for all encrypted communication over Reticulum networks.</p>
<dl class="py attribute">
<dt class="sig sig-object py" id="RNS.Identity.TRUNCATED_HASHLENGTH">
<span class="sig-name descname"><span class="pre">TRUNCATED_HASHLENGTH</span></span><em class="property"> <span class="pre">=</span> <span class="pre">80</span></em><a class="headerlink" href="#RNS.Identity.TRUNCATED_HASHLENGTH" title="Permalink to this definition"></a></dt>
<span class="sig-name descname"><span class="pre">TRUNCATED_HASHLENGTH</span></span><em class="property"> <span class="pre">=</span> <span class="pre">128</span></em><a class="headerlink" href="#RNS.Identity.TRUNCATED_HASHLENGTH" title="Permalink to this definition"></a></dt>
<dd><p>Constant specifying the truncated hash length (in bits) used by Reticulum
for addressable hashes and other purposes. Non-configurable.</p>
</dd></dl>
@@ -477,6 +477,20 @@ relevant interfaces. Application specific data can be added to the announce.</p>
</dl>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Destination.accepts_links">
<span class="sig-name descname"><span class="pre">accepts_links</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">accepts</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Destination.accepts_links" title="Permalink to this definition"></a></dt>
<dd><p>Set or query whether the destination accepts incoming link requests.</p>
<dl class="field-list simple">
<dt class="field-odd">Parameters</dt>
<dd class="field-odd"><p><strong>accepts</strong> If <code class="docutils literal notranslate"><span class="pre">True</span></code> or <code class="docutils literal notranslate"><span class="pre">False</span></code>, this method sets whether the destination accepts incoming link requests. If not provided or <code class="docutils literal notranslate"><span class="pre">None</span></code>, the method returns whether the destination currently accepts link requests.</p>
</dd>
<dt class="field-even">Returns</dt>
<dd class="field-even"><p><code class="docutils literal notranslate"><span class="pre">True</span></code> or <code class="docutils literal notranslate"><span class="pre">False</span></code> depending on whether the destination accepts incoming link requests, if the <em>accepts</em> parameter is not provided or <code class="docutils literal notranslate"><span class="pre">None</span></code>.</p>
</dd>
</dl>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Destination.set_link_established_callback">
<span class="sig-name descname"><span class="pre">set_link_established_callback</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">callback</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Destination.set_link_established_callback" title="Permalink to this definition"></a></dt>
@@ -484,7 +498,7 @@ relevant interfaces. Application specific data can be added to the announce.</p>
this destination.</p>
<dl class="field-list simple">
<dt class="field-odd">Parameters</dt>
<dd class="field-odd"><p><strong>callback</strong> A function or method to be called.</p>
<dd class="field-odd"><p><strong>callback</strong> A function or method with the signature <em>callback(link)</em> to be called when a new link is established with this destination.</p>
</dd>
</dl>
</dd></dl>
@@ -496,7 +510,7 @@ this destination.</p>
this destination.</p>
<dl class="field-list simple">
<dt class="field-odd">Parameters</dt>
<dd class="field-odd"><p><strong>callback</strong> A function or method to be called.</p>
<dd class="field-odd"><p><strong>callback</strong> A function or method with the signature <em>callback(data, packet)</em> to be called when this destination receives a packet.</p>
</dd>
</dl>
</dd></dl>
@@ -509,7 +523,7 @@ a packet sent to this destination. Allows control over when and if
proofs should be returned for received packets.</p>
<dl class="field-list simple">
<dt class="field-odd">Parameters</dt>
<dd class="field-odd"><p><strong>callback</strong> A function or method to be called. The callback must return one of True or False. If the callback returns True, a proof will be sent. If it returns False, a proof will not be sent.</p>
<dd class="field-odd"><p><strong>callback</strong> A function or method to with the signature <em>callback(packet)</em> be called when a packet that requests a proof is received. The callback must return one of True or False. If the callback returns True, a proof will be sent. If it returns False, a proof will not be sent.</p>
</dd>
</dl>
</dd></dl>
@@ -688,7 +702,7 @@ destinations, reticulum will use ephemeral keys, and offers <strong>Forward Secr
<dl class="py attribute">
<dt class="sig sig-object py" id="RNS.Packet.PLAIN_MDU">
<span class="sig-name descname"><span class="pre">PLAIN_MDU</span></span><em class="property"> <span class="pre">=</span> <span class="pre">476</span></em><a class="headerlink" href="#RNS.Packet.PLAIN_MDU" title="Permalink to this definition"></a></dt>
<span class="sig-name descname"><span class="pre">PLAIN_MDU</span></span><em class="property"> <span class="pre">=</span> <span class="pre">464</span></em><a class="headerlink" href="#RNS.Packet.PLAIN_MDU" title="Permalink to this definition"></a></dt>
<dd><p>The maximum size of the payload data in a single unencrypted packet</p>
</dd></dl>
@@ -808,7 +822,19 @@ connectivity with the specified destination.</p>
<dl class="py attribute">
<dt class="sig sig-object py" id="RNS.Link.ESTABLISHMENT_TIMEOUT_PER_HOP">
<span class="sig-name descname"><span class="pre">ESTABLISHMENT_TIMEOUT_PER_HOP</span></span><em class="property"> <span class="pre">=</span> <span class="pre">5</span></em><a class="headerlink" href="#RNS.Link.ESTABLISHMENT_TIMEOUT_PER_HOP" title="Permalink to this definition"></a></dt>
<dd><p>Default timeout for link establishment in seconds per hop to destination.</p>
<dd><p>Timeout for link establishment in seconds per hop to destination.</p>
</dd></dl>
<dl class="py attribute">
<dt class="sig sig-object py" id="RNS.Link.KEEPALIVE_TIMEOUT_FACTOR">
<span class="sig-name descname"><span class="pre">KEEPALIVE_TIMEOUT_FACTOR</span></span><em class="property"> <span class="pre">=</span> <span class="pre">4</span></em><a class="headerlink" href="#RNS.Link.KEEPALIVE_TIMEOUT_FACTOR" title="Permalink to this definition"></a></dt>
<dd><p>RTT timeout factor used in link timeout calculation.</p>
</dd></dl>
<dl class="py attribute">
<dt class="sig sig-object py" id="RNS.Link.STALE_GRACE">
<span class="sig-name descname"><span class="pre">STALE_GRACE</span></span><em class="property"> <span class="pre">=</span> <span class="pre">2</span></em><a class="headerlink" href="#RNS.Link.STALE_GRACE" title="Permalink to this definition"></a></dt>
<dd><p>Grace period in seconds used in link timeout calculation.</p>
</dd></dl>
<dl class="py attribute">
@@ -817,6 +843,16 @@ connectivity with the specified destination.</p>
<dd><p>Interval for sending keep-alive packets on established links in seconds.</p>
</dd></dl>
<dl class="py attribute">
<dt class="sig sig-object py" id="RNS.Link.STALE_TIME">
<span class="sig-name descname"><span class="pre">STALE_TIME</span></span><em class="property"> <span class="pre">=</span> <span class="pre">720</span></em><a class="headerlink" href="#RNS.Link.STALE_TIME" title="Permalink to this definition"></a></dt>
<dd><p>If no traffic or keep-alive packets are received within this period, the
link will be marked as stale, and a final keep-alive packet will be sent.
If after this no traffic or keep-alive packets are received within <code class="docutils literal notranslate"><span class="pre">RTT</span></code> *
<code class="docutils literal notranslate"><span class="pre">KEEPALIVE_TIMEOUT_FACTOR</span></code> + <code class="docutils literal notranslate"><span class="pre">STALE_GRACE</span></code>, the link is considered timed out,
and will be torn down.</p>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Link.identify">
<span class="sig-name descname"><span class="pre">identify</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">identity</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Link.identify" title="Permalink to this definition"></a></dt>
@@ -898,6 +934,18 @@ thus preserved. This method can be used for authentication.</p>
be used if a new link to the same destination is established.</p>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Link.set_link_closed_callback">
<span class="sig-name descname"><span class="pre">set_link_closed_callback</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">callback</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Link.set_link_closed_callback" title="Permalink to this definition"></a></dt>
<dd><p>Registers a function to be called when a link has been
torn down.</p>
<dl class="field-list simple">
<dt class="field-odd">Parameters</dt>
<dd class="field-odd"><p><strong>callback</strong> A function or method with the signature <em>callback(link)</em> to be called.</p>
</dd>
</dl>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Link.set_packet_callback">
<span class="sig-name descname"><span class="pre">set_packet_callback</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">callback</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Link.set_packet_callback" title="Permalink to this definition"></a></dt>
@@ -919,7 +967,7 @@ the resource will be accepted. If it returns <em>False</em> it will
be ignored.</p>
<dl class="field-list simple">
<dt class="field-odd">Parameters</dt>
<dd class="field-odd"><p><strong>callback</strong> A function or method with the signature <em>callback(resource)</em> to be called.</p>
<dd class="field-odd"><p><strong>callback</strong> A function or method with the signature <em>callback(resource)</em> to be called. Please note that only the basic information of the resource is available at this time, such as <em>get_transfer_size()</em>, <em>get_data_size()</em>, <em>get_parts()</em> and <em>is_compressed()</em>.</p>
</dd>
</dl>
</dd></dl>
@@ -955,7 +1003,7 @@ transferring over this link.</p>
identified over this link.</p>
<dl class="field-list simple">
<dt class="field-odd">Parameters</dt>
<dd class="field-odd"><p><strong>callback</strong> A function or method with the signature <em>callback(identity)</em> to be called.</p>
<dd class="field-odd"><p><strong>callback</strong> A function or method with the signature <em>callback(link, identity)</em> to be called.</p>
</dd>
</dl>
</dd></dl>
@@ -1081,6 +1129,66 @@ the resource advertisement it will begin transferring.</p>
</dl>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Resource.get_transfer_size">
<span class="sig-name descname"><span class="pre">get_transfer_size</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Resource.get_transfer_size" title="Permalink to this definition"></a></dt>
<dd><dl class="field-list simple">
<dt class="field-odd">Returns</dt>
<dd class="field-odd"><p>The number of bytes needed to transfer the resource.</p>
</dd>
</dl>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Resource.get_data_size">
<span class="sig-name descname"><span class="pre">get_data_size</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Resource.get_data_size" title="Permalink to this definition"></a></dt>
<dd><dl class="field-list simple">
<dt class="field-odd">Returns</dt>
<dd class="field-odd"><p>The total data size of the resource.</p>
</dd>
</dl>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Resource.get_parts">
<span class="sig-name descname"><span class="pre">get_parts</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Resource.get_parts" title="Permalink to this definition"></a></dt>
<dd><dl class="field-list simple">
<dt class="field-odd">Returns</dt>
<dd class="field-odd"><p>The number of parts the resource will be transferred in.</p>
</dd>
</dl>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Resource.get_segments">
<span class="sig-name descname"><span class="pre">get_segments</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Resource.get_segments" title="Permalink to this definition"></a></dt>
<dd><dl class="field-list simple">
<dt class="field-odd">Returns</dt>
<dd class="field-odd"><p>The number of segments the resource is divided into.</p>
</dd>
</dl>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Resource.get_hash">
<span class="sig-name descname"><span class="pre">get_hash</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Resource.get_hash" title="Permalink to this definition"></a></dt>
<dd><dl class="field-list simple">
<dt class="field-odd">Returns</dt>
<dd class="field-odd"><p>The hash of the resource.</p>
</dd>
</dl>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Resource.is_compressed">
<span class="sig-name descname"><span class="pre">is_compressed</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Resource.is_compressed" title="Permalink to this definition"></a></dt>
<dd><dl class="field-list simple">
<dt class="field-odd">Returns</dt>
<dd class="field-odd"><p>Whether the resource is compressed.</p>
</dd>
</dl>
</dd></dl>
</dd></dl>
</div>
@@ -1173,7 +1281,7 @@ Transport system of Reticulum.</p>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Transport.request_path">
<em class="property"><span class="pre">static</span> </em><span class="sig-name descname"><span class="pre">request_path</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">destination_hash</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">on_interface</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Transport.request_path" title="Permalink to this definition"></a></dt>
<em class="property"><span class="pre">static</span> </em><span class="sig-name descname"><span class="pre">request_path</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">destination_hash</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">on_interface</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">tag</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">recursive</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">False</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Transport.request_path" title="Permalink to this definition"></a></dt>
<dd><p>Requests a path to the destination from the network. If
another reachable peer on the network knows a path, it
will announce it.</p>
@@ -1220,8 +1328,8 @@ will announce it.</p>
</ul>
<h4>Previous topic</h4>
<p class="topless"><a href="understanding.html"
title="previous chapter">Understanding Reticulum</a></p>
<p class="topless"><a href="networks.html"
title="previous chapter">Building Networks</a></p>
<h4>Next topic</h4>
<p class="topless"><a href="examples.html"
title="next chapter">Code Examples</a></p>
@@ -1256,9 +1364,9 @@ will announce it.</p>
<a href="examples.html" title="Code Examples"
>next</a> |</li>
<li class="right" >
<a href="understanding.html" title="Understanding Reticulum"
<a href="networks.html" title="Building Networks"
>previous</a> |</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.6 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.10 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">API Reference</a></li>
</ul>
</div>
+3 -3
View File
@@ -5,7 +5,7 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Search &#8212; Reticulum Network Stack 0.3.6 beta documentation</title>
<title>Search &#8212; Reticulum Network Stack 0.3.10 beta documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/classic.css" />
@@ -29,7 +29,7 @@
<li class="right" style="margin-right: 10px">
<a href="genindex.html" title="General Index"
accesskey="I">index</a></li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.6 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.10 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">Search</a></li>
</ul>
</div>
@@ -85,7 +85,7 @@
<li class="right" style="margin-right: 10px">
<a href="genindex.html" title="General Index"
>index</a></li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.6 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.10 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">Search</a></li>
</ul>
</div>
File diff suppressed because one or more lines are too long
+137
View File
@@ -0,0 +1,137 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Support Reticulum &#8212; Reticulum Network Stack 0.3.10 beta documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/classic.css" />
<script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
<script src="_static/jquery.js"></script>
<script src="_static/underscore.js"></script>
<script src="_static/doctools.js"></script>
<link rel="index" title="Index" href="genindex.html" />
<link rel="search" title="Search" href="search.html" />
<link rel="prev" title="Code Examples" href="examples.html" />
</head><body>
<div class="related" role="navigation" aria-label="related navigation">
<h3>Navigation</h3>
<ul>
<li class="right" style="margin-right: 10px">
<a href="genindex.html" title="General Index"
accesskey="I">index</a></li>
<li class="right" >
<a href="examples.html" title="Code Examples"
accesskey="P">previous</a> |</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.10 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">Support Reticulum</a></li>
</ul>
</div>
<div class="document">
<div class="documentwrapper">
<div class="bodywrapper">
<div class="body" role="main">
<div class="section" id="support-reticulum">
<span id="support-main"></span><h1>Support Reticulum<a class="headerlink" href="#support-reticulum" title="Permalink to this headline"></a></h1>
<p>You can help support the continued development of open, free and private communications
systems by donating, providing feedback and contributing code and learning resources.</p>
<div class="section" id="donations">
<h2>Donations<a class="headerlink" href="#donations" title="Permalink to this headline"></a></h2>
<p>Donations are gratefully accepted via the following channels:</p>
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>Monero:
84FpY1QbxHcgdseePYNmhTHcrgMX4nFfBYtz2GKYToqHVVhJp8Eaw1Z1EedRnKD19b3B8NiLCGVxzKV17UMmmeEsCrPyA5w
Ethereum:
0x81F7B979fEa6134bA9FD5c701b3501A2e61E897a
Bitcoin:
3CPmacGm34qYvR6XWLVEJmi2aNe3PZqUuq
Ko-Fi:
https://ko-fi.com/markqvist
</pre></div>
</div>
<p>Are certain features in the development roadmap are important to you or your
organisation? Make them a reality quickly by sponsoring their implementation.</p>
</div>
<div class="section" id="provide-feedback">
<h2>Provide Feedback<a class="headerlink" href="#provide-feedback" title="Permalink to this headline"></a></h2>
<p>All feedback on the usage, functioning and potential dysfunctioning of any and
all components of the system is very valuable to the continued development and
improvement of Reticulum. Absolutely no automated analytics, telemetly, error
reporting or statistics is collected and reported by Reticulum under any
circumstances, so we rely on old-fashioned human feedback.</p>
</div>
<div class="section" id="contribute-code">
<h2>Contribute Code<a class="headerlink" href="#contribute-code" title="Permalink to this headline"></a></h2>
<p>Join us on <a class="reference external" href="https://github.com/markqvist/reticulum">the GitHub repository</a> to
report issues, suggest functionality and contribute code to Reticulum.</p>
</div>
</div>
<div class="clearer"></div>
</div>
</div>
</div>
<div class="sphinxsidebar" role="navigation" aria-label="main navigation">
<div class="sphinxsidebarwrapper">
<h3><a href="index.html">Table of Contents</a></h3>
<ul>
<li><a class="reference internal" href="#">Support Reticulum</a><ul>
<li><a class="reference internal" href="#donations">Donations</a></li>
<li><a class="reference internal" href="#provide-feedback">Provide Feedback</a></li>
<li><a class="reference internal" href="#contribute-code">Contribute Code</a></li>
</ul>
</li>
</ul>
<h4>Previous topic</h4>
<p class="topless"><a href="examples.html"
title="previous chapter">Code Examples</a></p>
<div role="note" aria-label="source link">
<h3>This Page</h3>
<ul class="this-page-menu">
<li><a href="_sources/support.rst.txt"
rel="nofollow">Show Source</a></li>
</ul>
</div>
<div id="searchbox" style="display: none" role="search">
<h3 id="searchlabel">Quick search</h3>
<div class="searchformwrapper">
<form class="search" action="search.html" method="get">
<input type="text" name="q" aria-labelledby="searchlabel" />
<input type="submit" value="Go" />
</form>
</div>
</div>
<script>$('#searchbox').show(0);</script>
</div>
</div>
<div class="clearer"></div>
</div>
<div class="related" role="navigation" aria-label="related navigation">
<h3>Navigation</h3>
<ul>
<li class="right" style="margin-right: 10px">
<a href="genindex.html" title="General Index"
>index</a></li>
<li class="right" >
<a href="examples.html" title="Code Examples"
>previous</a> |</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.10 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">Support Reticulum</a></li>
</ul>
</div>
<div class="footer" role="contentinfo">
&#169; Copyright 2022, Mark Qvist.
Created using <a href="https://www.sphinx-doc.org/">Sphinx</a> 4.0.1.
</div>
</body>
</html>
+87 -45
View File
@@ -5,7 +5,7 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Understanding Reticulum &#8212; Reticulum Network Stack 0.3.6 beta documentation</title>
<title>Understanding Reticulum &#8212; Reticulum Network Stack 0.3.10 beta documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/classic.css" />
@@ -16,8 +16,8 @@
<link rel="index" title="Index" href="genindex.html" />
<link rel="search" title="Search" href="search.html" />
<link rel="next" title="API Reference" href="reference.html" />
<link rel="prev" title="Supported Interfaces" href="interfaces.html" />
<link rel="next" title="Communications Hardware" href="hardware.html" />
<link rel="prev" title="Using Reticulum on Your System" href="using.html" />
</head><body>
<div class="related" role="navigation" aria-label="related navigation">
<h3>Navigation</h3>
@@ -26,12 +26,12 @@
<a href="genindex.html" title="General Index"
accesskey="I">index</a></li>
<li class="right" >
<a href="reference.html" title="API Reference"
<a href="hardware.html" title="Communications Hardware"
accesskey="N">next</a> |</li>
<li class="right" >
<a href="interfaces.html" title="Supported Interfaces"
<a href="using.html" title="Using Reticulum on Your System"
accesskey="P">previous</a> |</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.6 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.10 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">Understanding Reticulum</a></li>
</ul>
</div>
@@ -165,26 +165,26 @@ to be transported over multiple hops in a complex network to reach the recipient
Reticulum uses the singular concept of <em>destinations</em>. Any application using Reticulum as its
networking stack will need to create one or more destinations to receive data, and know the
destinations it needs to send data to.</p>
<p>All destinations in Reticulum are represented as a 10 byte hash, derived from truncating a full
<p>All destinations in Reticulum are _represented_ as a 16 byte hash. This hash is derived from truncating a full
SHA-256 hash of identifying characteristics of the destination. To users, the destination addresses
will be displayed as 10 bytes in hexadecimal representation, as in the following example: <code class="docutils literal notranslate"><span class="pre">&lt;80e29bf7cccaf31431b3&gt;</span></code>.</p>
<p>The truncation size of 10 bytes (80 bits) for destinations has been choosen as a reasonable tradeoff between address space
will be displayed as 16 hexadecimal bytes, like this example: <code class="docutils literal notranslate"><span class="pre">&lt;13425ec15b621c1d928589718000d814&gt;</span></code>.</p>
<p>The truncation size of 16 bytes (128 bits) for destinations has been choosen as a reasonable tradeoff
between address space
and packet overhead. The address space accomodated by this size can support many billions of
simultaneously active devices on the same network, while keeping packet overhead low, which is
essential on low-bandwidth networks. In the very unlikely case that this address space nears
congestion, a one-line code change can upgrade the Reticulum address space all the way up to 256
bits, ensuring the Reticulum address space could potentially support galactic-scale networks.
This is obviusly complete and ridiculous over-allocation, and as such, the current 80 bits should
This is obviusly complete and ridiculous over-allocation, and as such, the current 128 bits should
be sufficient, even far into the future.</p>
<p>By default Reticulum encrypts all data using elliptic curve cryptography. Any packet sent to a
destination is encrypted with a derived ephemeral key. Reticulum can also set up an encrypted
channel to a destination with <em>Forward Secrecy</em> and <em>Initiator Anonymity</em> using a elliptic
curve cryptography and ephemeral keys derived from a Diffie Hellman exchange on Curve25519. In
Reticulum terminology, this is called a <em>Link</em>. The multi-hop transport, coordination, verification
channel to a destination, called a <em>Link</em>. Both data sent over Links and single packets offer
<em>Forward Secrecy</em> and <em>Initiator Anonymity</em>, by using an Elliptic Curve Diffie Hellman key exchange
on Curve25519 to derive ephemeral keys. The multi-hop transport, coordination, verification
and reliability layers are fully autonomous and also based on elliptic curve cryptography.</p>
<p>Reticulum also offers symmetric key encryption for group-oriented communications, as well as
unencrypted packets for broadcast purposes, or situations where you need the communication to be in
plain text.</p>
unencrypted packets for local broadcast purposes.</p>
<p>Reticulum can connect to a variety of interfaces such as radio modems, data radios and serial ports,
and offers the possibility to easily tunnel Reticulum traffic over IP links such as the Internet or
private IP networks.</p>
@@ -234,7 +234,7 @@ out requests and responses, large data transfers and more.</p>
<div class="section" id="destination-naming">
<span id="understanding-destinationnaming"></span><h4>Destination Naming<a class="headerlink" href="#destination-naming" title="Permalink to this headline"></a></h4>
<p>Destinations are created and named in an easy to understand dotted notation of <em>aspects</em>, and
represented on the network as a hash of this value. The hash is a SHA-256 truncated to 80 bits. The
represented on the network as a hash of this value. The hash is a SHA-256 truncated to 128 bits. The
top level aspect should always be a unique identifier for the application using the destination.
The next levels of aspects can be defined in any way by the creator of the application.</p>
<p>Aspects can be as long and as plentiful as required, and a resulting long destination name will not
@@ -245,7 +245,7 @@ application name, a device type and measurement type, like this:</p>
aspects : remotesensor, temperature
full name : environmentlogger.remotesensor.temperature
hash : fa7ddfab5213f916dea
hash : 4faf1b2e0a077e6a9d92fa051f256038
</pre></div>
</div>
<p>For the <em>single</em> destination, Reticulum will automatically append the associated public key as a
@@ -546,10 +546,10 @@ At the same time we establish an efficient encrypted channel. The setup of this
terms of bandwidth, so it can be used just for a short exchange, and then recreated as needed, which will
also rotate encryption keys. The link can also be kept alive for longer periods of time, if this is
more suitable to the application. The procedure also inserts the <em>link id</em> , a hash calculated from the link request packet, into the memory of forwarding nodes, which means that the communicating nodes can thereafter reach each other simply by referring to this <em>link id</em>.</p>
<p>The combined bandwidth cost of setting up a link is 3 packets totalling 237 bytes (more info in the
<p>The combined bandwidth cost of setting up a link is 3 packets totalling 265 bytes (more info in the
<a class="reference internal" href="#understanding-packetformat"><span class="std std-ref">Binary Packet Format</span></a> section). The amount of bandwidth used on keeping
a link open is practically negligible, at 0.62 bits per second. Even on a slow 1200 bits per second packet
radio channel, 100 concurrent links will still leave 95% channel capacity for actual data.</p>
a link open is practically negligible, at 0.45 bits per second. Even on a slow 1200 bits per second packet
radio channel, 100 concurrent links will still leave 96% channel capacity for actual data.</p>
<div class="section" id="link-establishment-in-detail">
<h4>Link Establishment in Detail<a class="headerlink" href="#link-establishment-in-detail" title="Permalink to this headline"></a></h4>
<p>After exploring the basics of the announce mechanism, finding a path through the network, and an overview
@@ -737,7 +737,7 @@ pass onto the network.</p>
A Reticulum packet is composed of the following fields:
[HEADER 2 bytes] [ADDRESSES 10/20 bytes] [CONTEXT 1 byte] [DATA 0-477 bytes]
[HEADER 2 bytes] [ADDRESSES 16/32 bytes] [CONTEXT 1 byte] [DATA 0-465 bytes]
* The HEADER field is 2 bytes long.
* Byte 1: [IFAC Flag], [Header Type], [Propagation Type], [Destination Type] and [Packet Type]
@@ -749,15 +749,15 @@ A Reticulum packet is composed of the following fields:
capabilities and configuration.
* The ADDRESSES field contains either 1 or 2 addresses.
* Each address is 10 bytes long.
* Each address is 16 bytes long.
* The Header Type flag in the HEADER field determines
whether the ADDRESSES field contains 1 or 2 addresses.
* Addresses are Reticulum hashes truncated to 10 bytes.
* Addresses are SHA-256 hashes truncated to 16 bytes.
* The CONTEXT field is 1 byte.
* It is used by Reticulum to determine packet context.
* The DATA field is between 0 and 477 bytes.
* The DATA field is between 0 and 465 bytes.
* It contains the packets data payload.
IFAC Flag
@@ -768,8 +768,8 @@ authenticated 1 Interface authentication is included in packet
Header Types
-----------------
type 1 0 Two byte header, one 10 byte address field
type 2 1 Two byte header, two 10 byte address fields
type 1 0 Two byte header, one 16 byte address field
type 2 1 Two byte header, two 16 byte address fields
Propagation Types
@@ -801,7 +801,7 @@ proof 11
HEADER FIELD DESTINATION FIELDS CONTEXT FIELD DATA FIELD
_______|_______ ________________|________________ ________|______ __|_
| | | | | | | |
01010000 00000100 [HASH1, 10 bytes] [HASH2, 10 bytes] [CONTEXT, 1 byte] [DATA]
01010000 00000100 [HASH1, 16 bytes] [HASH2, 16 bytes] [CONTEXT, 1 byte] [DATA]
|| | | | |
|| | | | +-- Hops = 4
|| | | +------- Packet Type = DATA
@@ -816,7 +816,7 @@ proof 11
HEADER FIELD DESTINATION FIELD CONTEXT FIELD DATA FIELD
_______|_______ _______|_______ ________|______ __|_
| | | | | | | |
00000000 00000111 [HASH1, 10 bytes] [CONTEXT, 1 byte] [DATA]
00000000 00000111 [HASH1, 16 bytes] [CONTEXT, 1 byte] [DATA]
|| | | | |
|| | | | +-- Hops = 0
|| | | +------- Packet Type = DATA
@@ -831,7 +831,7 @@ proof 11
HEADER FIELD IFAC FIELD DESTINATION FIELD CONTEXT FIELD DATA FIELD
_______|_______ ______|______ _______|_______ ________|______ __|_
| | | | | | | | | |
10000000 00000111 [IFAC, N bytes] [HASH1, 10 bytes] [CONTEXT, 1 byte] [DATA]
10000000 00000111 [IFAC, N bytes] [HASH1, 16 bytes] [CONTEXT, 1 byte] [DATA]
|| | | | |
|| | | | +-- Hops = 0
|| | | +------- Packet Type = DATA
@@ -849,22 +849,63 @@ packet types. The size listed are the complete on-
wire size counting all fields including headers,
but excluding any interface access codes.
- Path Request : 33 bytes
- Announce : 151 bytes
- Link Request : 77 bytes
- Link Proof : 77 bytes
- Link RTT packet : 83 bytes
- Link keepalive : 14 bytes
- Path Request : 51 bytes
- Announce : 157 bytes
- Link Request : 83 bytes
- Link Proof : 83 bytes
- Link RTT packet : 99 bytes
- Link keepalive : 20 bytes
</pre></div>
</div>
</div>
<div class="section" id="announce-propagation-rules">
<span id="understanding-announcepropagation"></span><h3>Announce Propagation Rules<a class="headerlink" href="#announce-propagation-rules" title="Permalink to this headline"></a></h3>
<p>The following table illustrates the rules for automatically propagating announces
from one interface type to another, for all possible combinations. See the
<a class="reference internal" href="interfaces.html#interfaces-modes"><span class="std std-ref">Interface Modes</span></a> section for a conceptual overview of the
different interface modes, and how they are configured.</p>
from one interface type to another, for all possible combinations. For the purpose
of announce propagation, the <em>Full</em> and <em>Gateway</em> modes are identical.</p>
<img alt="_images/if_mode_graph_b.png" src="_images/if_mode_graph_b.png" />
<p>See the <a class="reference internal" href="interfaces.html#interfaces-modes"><span class="std std-ref">Interface Modes</span></a> section for a conceptual overview
of the different interface modes, and how they are configured.</p>
</div>
<div class="section" id="cryptographic-primitives">
<span id="understanding-primitives"></span><h3>Cryptographic Primitives<a class="headerlink" href="#cryptographic-primitives" title="Permalink to this headline"></a></h3>
<p>Reticulum has been designed to use a simple suite of efficient, strong and modern
cryptographic primitives, with widely available implementations that can be used
both on general-purpose CPUs and on microcontrollers. The necessary primitives are:</p>
<ul class="simple">
<li><p>Ed25519 for signatures</p></li>
<li><p>X22519 for ECDH key exchanges</p></li>
<li><p>HKDF for key derivation</p></li>
<li><p>Fernet for encrypted tokens</p>
<ul>
<li><p>AES-128 in CBC mode</p></li>
<li><p>HMAC for message authentication</p></li>
</ul>
</li>
<li><p>SHA-256</p></li>
<li><p>SHA-512</p></li>
</ul>
<p>In the default installation configuration, the <code class="docutils literal notranslate"><span class="pre">X25519</span></code>, <code class="docutils literal notranslate"><span class="pre">Ed25519</span></code> and <code class="docutils literal notranslate"><span class="pre">AES-128-CBC</span></code>
primitives are provided by <a class="reference external" href="https://www.openssl.org/">OpenSSL</a> (via the <a class="reference external" href="https://github.com/pyca/cryptography">PyCA/cryptography</a>
package). The hashing functions <code class="docutils literal notranslate"><span class="pre">SHA-256</span></code> and <code class="docutils literal notranslate"><span class="pre">SHA-512</span></code> are provided by the standard
Python <a class="reference external" href="https://docs.python.org/3/library/hashlib.html">hashlib</a>. The <code class="docutils literal notranslate"><span class="pre">HKDF</span></code>, <code class="docutils literal notranslate"><span class="pre">HMAC</span></code>,
<code class="docutils literal notranslate"><span class="pre">Fernet</span></code> primitives, and the <code class="docutils literal notranslate"><span class="pre">PKCS7</span></code> padding function are always provided by the
following internal implementations:</p>
<ul class="simple">
<li><p><code class="docutils literal notranslate"><span class="pre">RNS/Cryptography/HKDF.py</span></code></p></li>
<li><p><code class="docutils literal notranslate"><span class="pre">RNS/Cryptography/HMAC.py</span></code></p></li>
<li><p><code class="docutils literal notranslate"><span class="pre">RNS/Cryptography/Fernet.py</span></code></p></li>
<li><p><code class="docutils literal notranslate"><span class="pre">RNS/Cryptography/PKCS7.py</span></code></p></li>
</ul>
<p>Reticulum also includes a complete implementation of all necessary primitives in pure Python.
If OpenSSL &amp; 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 <em>much</em> 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.</p>
<p>If you want to use the internal pure-python primitives, it is <strong>highly advisable</strong> 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.</p>
</div>
</div>
</div>
@@ -907,6 +948,7 @@ different interface modes, and how they are configured.</p>
<li><a class="reference internal" href="#interface-access-codes">Interface Access Codes</a></li>
<li><a class="reference internal" href="#wire-format">Wire Format</a></li>
<li><a class="reference internal" href="#announce-propagation-rules">Announce Propagation Rules</a></li>
<li><a class="reference internal" href="#cryptographic-primitives">Cryptographic Primitives</a></li>
</ul>
</li>
</ul>
@@ -914,11 +956,11 @@ different interface modes, and how they are configured.</p>
</ul>
<h4>Previous topic</h4>
<p class="topless"><a href="interfaces.html"
title="previous chapter">Supported Interfaces</a></p>
<p class="topless"><a href="using.html"
title="previous chapter">Using Reticulum on Your System</a></p>
<h4>Next topic</h4>
<p class="topless"><a href="reference.html"
title="next chapter">API Reference</a></p>
<p class="topless"><a href="hardware.html"
title="next chapter">Communications Hardware</a></p>
<div role="note" aria-label="source link">
<h3>This Page</h3>
<ul class="this-page-menu">
@@ -947,12 +989,12 @@ different interface modes, and how they are configured.</p>
<a href="genindex.html" title="General Index"
>index</a></li>
<li class="right" >
<a href="reference.html" title="API Reference"
<a href="hardware.html" title="Communications Hardware"
>next</a> |</li>
<li class="right" >
<a href="interfaces.html" title="Supported Interfaces"
<a href="using.html" title="Using Reticulum on Your System"
>previous</a> |</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.6 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.10 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">Understanding Reticulum</a></li>
</ul>
</div>
+114 -16
View File
@@ -5,7 +5,7 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Using Reticulum on Your System &#8212; Reticulum Network Stack 0.3.6 beta documentation</title>
<title>Using Reticulum on Your System &#8212; Reticulum Network Stack 0.3.10 beta documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/classic.css" />
@@ -16,7 +16,7 @@
<link rel="index" title="Index" href="genindex.html" />
<link rel="search" title="Search" href="search.html" />
<link rel="next" title="Building Networks" href="networks.html" />
<link rel="next" title="Understanding Reticulum" href="understanding.html" />
<link rel="prev" title="Getting Started Fast" href="gettingstartedfast.html" />
</head><body>
<div class="related" role="navigation" aria-label="related navigation">
@@ -26,12 +26,12 @@
<a href="genindex.html" title="General Index"
accesskey="I">index</a></li>
<li class="right" >
<a href="networks.html" title="Building Networks"
<a href="understanding.html" title="Understanding Reticulum"
accesskey="N">next</a> |</li>
<li class="right" >
<a href="gettingstartedfast.html" title="Getting Started Fast"
accesskey="P">previous</a> |</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.6 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.10 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">Using Reticulum on Your System</a></li>
</ul>
</div>
@@ -169,6 +169,9 @@ take a look at the <a class="reference internal" href="interfaces.html#interface
</div>
<div class="section" id="included-utility-programs">
<h2>Included Utility Programs<a class="headerlink" href="#included-utility-programs" title="Permalink to this headline"></a></h2>
<p>Reticulum includes a range of useful utilities, both for managing your Reticulum
networks, and for carrying out common tasks over Reticulum networks, such as
transferring files to remote systems, and executing commands and programs remotely.</p>
<p>If you often use Reticulum from several different programs, or simply want
Reticulum to stay available all the time, for example if you are hosting
a transport node, you might want to run Reticulum as a separate service that
@@ -240,7 +243,7 @@ RNodeInterface[RNode UHF]
Traffic : 8.49 KB↑
9.23 KB↓
Reticulum Transport Instance &lt;5245a8efe1788c6a70e1&gt; running
Reticulum Transport Instance &lt;5245a8efe1788c6a1cd36144a270e13b&gt; running
</pre></div>
</div>
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>usage: rnstatus [-h] [--config CONFIG] [--version] [-a] [-v]
@@ -261,13 +264,13 @@ optional arguments:
<p>With the <code class="docutils literal notranslate"><span class="pre">rnpath</span></code> utility, you can look up and view paths for
destinations on the Reticulum network.</p>
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span># Run rnpath
rnpath eca6f4e4dc26ae329e61
rnpath c89b4da064bf66d280f0e4d8abfd9806
# Example output
Path found, destination &lt;eca6f4e4dc26ae329e61&gt; is 4 hops away via &lt;56b115c30cd386cad69c&gt; on TCPInterface[Testnet/frankfurt.rns.unsigned.io:4965]
Path found, destination &lt;c89b4da064bf66d280f0e4d8abfd9806&gt; is 4 hops away via &lt;f53a1c4278e0726bb73fcc623d6ce763&gt; on TCPInterface[Testnet/frankfurt.connect.reticulu.network:4965]
</pre></div>
</div>
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>usage: rnpath.py [-h] [--config CONFIG] [--version] [-t] [-r] [-d] [-D] [-w seconds] [-v] [destination]
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>usage: rnpath [-h] [--config CONFIG] [--version] [-t] [-r] [-d] [-D] [-w seconds] [-v] [destination]
Reticulum Path Discovery Utility
@@ -294,15 +297,15 @@ to the <code class="docutils literal notranslate"><span class="pre">ping</span><
specified destination is configured to send proofs for received packets. Many
destinations will not have this option enabled, and will not be probable.</p>
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span># Run rnprobe
python3 -m RNS.Utilities.rnprobe example_utilities.echo.request 9382f334de63217a4278
rnprobe example_utilities.echo.request 2d03725b327348980d570f739a3a5708
# Example output
Sent 16 byte probe to &lt;9382f334de63217a4278&gt;
Valid reply received from &lt;9382f334de63217a4278&gt;
Sent 16 byte probe to &lt;2d03725b327348980d570f739a3a5708&gt;
Valid reply received from &lt;2d03725b327348980d570f739a3a5708&gt;
Round-trip time is 38.469 milliseconds over 2 hops
</pre></div>
</div>
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>usage: rnprobe.py [-h] [--config CONFIG] [--version] [-v] [full_name] [destination_hash]
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>usage: rnprobe [-h] [--config CONFIG] [--version] [-v] [full_name] [destination_hash]
Reticulum Probe Utility
@@ -318,6 +321,99 @@ optional arguments:
</pre></div>
</div>
</div>
<div class="section" id="the-rncp-utility">
<h3>The rncp Utility<a class="headerlink" href="#the-rncp-utility" title="Permalink to this headline"></a></h3>
<p>The <code class="docutils literal notranslate"><span class="pre">rncp</span></code> utility is a simple file transfer tool. Using it, you can transfer
files through Reticulum.</p>
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span># Run rncp on the receiving system, specifying which identities
# are allowed to send files
rncp --receive -a 1726dbad538775b5bf9b0ea25a4079c8 -a c50cc4e4f7838b6c31f60ab9032cbc62
# From another system, copy a file to the receiving system
rncp ~/path/to/file.tgz 73cbd378bb0286ed11a707c13447bb1e
</pre></div>
</div>
<p>You can specify as many allowed senders as needed, or complete disable authentication.</p>
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>usage: rncp [-h] [--config path] [-v] [-q] [-p] [-r] [-b] [-a allowed_hash] [-n] [-w seconds] [--version] [file] [destination]
Reticulum File Transfer Utility
positional arguments:
file file to be transferred
destination hexadecimal hash of the receiver
optional arguments:
-h, --help show this help message and exit
--config path path to alternative Reticulum config directory
-v, --verbose increase verbosity
-q, --quiet decrease verbosity
-p, --print-identity print identity and destination info and exit
-r, --receive wait for incoming files
-b, --no-announce don&#39;t announce at program start
-a allowed_hash accept from this identity
-n, --no-auth accept files from anyone
-w seconds sender timeout before giving up
--version show program&#39;s version number and exit
-v, --verbose
</pre></div>
</div>
</div>
<div class="section" id="the-rnx-utility">
<h3>The rnx Utility<a class="headerlink" href="#the-rnx-utility" title="Permalink to this headline"></a></h3>
<p>The <code class="docutils literal notranslate"><span class="pre">rnx</span></code> utility is a basic remote command execution program. It allows you to
execute commands on remote systems over Reticulum, and to view returned command
output.</p>
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span># Run rnx on the listening system, specifying which identities
# are allowed to execute commands
rncp --listen -a 941bed5e228775e5a8079fc38b1ccf3f -a 1b03013c25f1c2ca068a4f080b844a10
# From another system, run a command
rnx 7a55144adf826958a9529a3bcf08b149 &quot;cat /proc/cpuinfo&quot;
# Or enter the interactive mode pseudo-shell
rnx 7a55144adf826958a9529a3bcf08b149 -x
# The default identity file is stored in
# ~/.reticulum/identities/rnx, but you can use
# another one, which will be created if it does
# not already exist
rnx 7a55144adf826958a9529a3bcf08b149 -i /path/to/identity -x
</pre></div>
</div>
<p>You can specify as many allowed senders as needed, or completely disable authentication.</p>
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>usage: rnx [-h] [--config path] [-v] [-q] [-p] [-l] [-i identity] [-x] [-b] [-a allowed_hash] [-n] [-N] [-d] [-m] [-w seconds] [-W seconds] [--stdin STDIN] [--stdout STDOUT] [--stderr STDERR] [--version]
[destination] [command]
Reticulum Remote Execution Utility
positional arguments:
destination hexadecimal hash of the listener
command command to be execute
optional arguments:
-h, --help show this help message and exit
--config path path to alternative Reticulum config directory
-v, --verbose increase verbosity
-q, --quiet decrease verbosity
-p, --print-identity print identity and destination info and exit
-l, --listen listen for incoming commands
-i identity path to identity to use
-x, --interactive enter interactive mode
-b, --no-announce don&#39;t announce at program start
-a allowed_hash accept from this identity
-n, --noauth accept files from anyone
-N, --noid don&#39;t identify to listener
-d, --detailed show detailed result output
-m mirror exit code of remote command
-w seconds connect and request timeout before giving up
-W seconds max result download time
--stdin STDIN pass input to stdin
--stdout STDOUT max size in bytes of returned stdout
--stderr STDERR max size in bytes of returned stderr
--version show program&#39;s version number and exit
</pre></div>
</div>
</div>
</div>
<div class="section" id="improving-system-configuration">
<h2>Improving System Configuration<a class="headerlink" href="#improving-system-configuration" title="Permalink to this headline"></a></h2>
@@ -415,6 +511,8 @@ WantedBy=multi-user.target
<li><a class="reference internal" href="#the-rnstatus-utility">The rnstatus Utility</a></li>
<li><a class="reference internal" href="#the-rnpath-utility">The rnpath Utility</a></li>
<li><a class="reference internal" href="#the-rnprobe-utility">The rnprobe Utility</a></li>
<li><a class="reference internal" href="#the-rncp-utility">The rncp Utility</a></li>
<li><a class="reference internal" href="#the-rnx-utility">The rnx Utility</a></li>
</ul>
</li>
<li><a class="reference internal" href="#improving-system-configuration">Improving System Configuration</a><ul>
@@ -430,8 +528,8 @@ WantedBy=multi-user.target
<p class="topless"><a href="gettingstartedfast.html"
title="previous chapter">Getting Started Fast</a></p>
<h4>Next topic</h4>
<p class="topless"><a href="networks.html"
title="next chapter">Building Networks</a></p>
<p class="topless"><a href="understanding.html"
title="next chapter">Understanding Reticulum</a></p>
<div role="note" aria-label="source link">
<h3>This Page</h3>
<ul class="this-page-menu">
@@ -460,12 +558,12 @@ WantedBy=multi-user.target
<a href="genindex.html" title="General Index"
>index</a></li>
<li class="right" >
<a href="networks.html" title="Building Networks"
<a href="understanding.html" title="Understanding Reticulum"
>next</a> |</li>
<li class="right" >
<a href="gettingstartedfast.html" title="Getting Started Fast"
>previous</a> |</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.6 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.10 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">Using Reticulum on Your System</a></li>
</ul>
</div>
+6 -6
View File
@@ -5,7 +5,7 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>What is Reticulum? &#8212; Reticulum Network Stack 0.3.6 beta documentation</title>
<title>What is Reticulum? &#8212; Reticulum Network Stack 0.3.10 beta documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/classic.css" />
@@ -31,7 +31,7 @@
<li class="right" >
<a href="index.html" title="Reticulum Network Stack Manual"
accesskey="P">previous</a> |</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.6 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.10 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">What is Reticulum?</a></li>
</ul>
</div>
@@ -72,11 +72,11 @@
<li><p>An intuitive and developer-friendly API</p></li>
<li><p>Efficient link establishment</p>
<ul>
<li><p>Total bandwidth cost of setting up a link is only 3 packets, totalling 237 bytes</p></li>
<li><p>Low cost of keeping links open at only 0.62 bits per second</p></li>
<li><p>Total bandwidth cost of setting up a link is only 3 packets, totalling 265 bytes</p></li>
<li><p>Low cost of keeping links open at only 0.44 bits per second</p></li>
</ul>
</li>
<li><p>Reliable and efficient transfer of arbritrary amounts of data</p>
<li><p>Reliable and efficient transfer of arbitrary amounts of data</p>
<ul>
<li><p>Reticulum can handle a few bytes of data or files of many gigabytes</p></li>
<li><p>Sequencing, transfer coordination and checksumming is automatic</p></li>
@@ -211,7 +211,7 @@ network, and vice versa.</p>
<li class="right" >
<a href="index.html" title="Reticulum Network Stack Manual"
>previous</a> |</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.6 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.10 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">What is Reticulum?</a></li>
</ul>
</div>
+1 -1
View File
@@ -22,7 +22,7 @@ copyright = '2022, Mark Qvist'
author = 'Mark Qvist'
# The full version, including alpha/beta/rc tags
release = '0.3.6 beta'
release = '0.3.10 beta'
# -- General configuration ---------------------------------------------------
+70 -30
View File
@@ -62,7 +62,6 @@ a look at `Sideband <https://unsigned.io/sideband>`_, which is available for And
Linux and macOS.
.. image:: screenshots/sideband_1.png
:width: 400px
:align: center
:target: _images/sideband_1.png
@@ -103,16 +102,16 @@ With Reticulum, you only need to configure what interfaces you want to communica
over. There is no need to configure address spaces, subnets, routing tables,
or other things you might be used to from other network types.
Once Reticulums knows which interfaces it should use, it will automatically
Once Reticulum knows which interfaces it should use, it will automatically
discover topography and configure transport of data to any destinations it
knows about.
In situations where you already have an established WiFi or ethernet network, and
many devices that want to utilise the same external Reticulum network (for example over
many devices that want to utilise the same external Reticulum network paths (for example over
LoRa), it will often be sufficient to let one system act as a Reticulum gateway, by
adding any external interfaces to this systems configuration, and enabling transport. Any
adding any external interfaces to the configuration of this system, and then enabling transport on it. Any
other device on your local WiFi will then be able to connect to this wider Reticulum
network just using the default interface configuration.
network just using the default (:ref:`AutoInterface<interfaces-auto>`) configuration.
Possibly, the examples in the config file are enough to get you started. If
you want more information, you can read the :ref:`Building Networks<networks-main>`
@@ -137,7 +136,7 @@ Hosting a publicly reachable instance over TCP also requires a publicly reachabl
which most Internet connections don't offer anymore.
The ``I2PInterface`` routes messages through the `Invisible Internet Protocol
(I2P) <https://geti2p.net/en/>`_. To properly use this interface, users must also run an I2P daemon in
(I2P) <https://geti2p.net/en/>`_. To use this interface, users must also run an I2P daemon in
parallel to ``rnsd``. For always-on I2P nodes it is recommended to use `i2pd <https://i2pd.website/>`_.
By default, I2P will encrypt and mix all traffic sent over the Internet, and
@@ -146,12 +145,13 @@ will also relay other I2P user's encrypted packets, which will use extra
bandwidth and compute power, but also makes timing attacks and other forms of
deep-packet-inspection much more difficult.
I2P also allows users to host globally available Reticulum instances from non-public IPs and behind firewalls.
I2P also allows users to host globally available Reticulum instances from non-public IPs and behind firewalls and NAT.
In general it is recommended to use an I2P node if you want to host a publically accessible
instance, while preserving anonymity. If you care more about performance, and a slightly
easier setup, use TCP.
Connect to the Public Testnet
===========================================
@@ -160,26 +160,61 @@ by adding one of the following interfaces to your ``.reticulum/config`` file:
.. code::
# For connecting over TCP/IP:
[[RNS Testnet Frankfurt]]
# TCP/IP interface to the Dublin hub
[[RNS Testnet Dublin]]
type = TCPClientInterface
interface_enabled = yes
outgoing = True
target_host = frankfurt.rns.unsigned.io
enabled = yes
target_host = dublin.connect.reticulum.network
target_port = 4965
# TCP/IP interface to the Frankfurt hub
[[RNS Testnet Dublin]]
type = TCPClientInterface
enabled = yes
target_host = frankfurt.connect.reticulum.network
target_port = 5377
# For connecting over I2P:
[[RNS Testnet I2P Node A]]
# Interface to I2P hub A
[[RNS Testnet I2P Hub A]]
type = I2PInterface
interface_enabled = yes
peers = ykzlw5ujbaqc2xkec4cpvgyxj257wcrmmgkuxqmqcur7cq3w3lha.b32.i2p
enabled = yes
peers = uxg5kubabakh3jtnvsipingbr5574dle7bubvip7llfvwx2tgrua.b32.i2p
Many other Reticulum instances are connecting to this testnet, and you can also join it
via other entry points if you know them. There is absolutely no control over the network
topography, usage or what types of instances connect. It will also occasionally be used
to test various failure scenarios, and there are no availability or service guarantees.
Adding Radio Interfaces
==============================================
Once you have Reticulum installed and working, you can add radio interfaces with
any compatible hardware you have available. Reticulum supports a wide range of radio
hardware, and if you already have any available, it is very likely that it will
work with Reticulum. For information on how to configure this, see the
:ref:`Interfaces<interfaces-main>` section of this manual.
If you do not already have transceiver hardware available, you can easily and
cheaply build an :ref:`RNode<rnode-main>`, which is a general-purpose long-range
digital radio transceiver, that integrates easily with Reticulum.
To build one yourself requires installing a custom firmware on a supported LoRa
development board with an auto-install script. Please see the :ref:`Communications Hardware<hardware-main>`
chapter for a guide. If you prefer purchasing a ready-made unit, you can refer to the
:ref:`list of suppliers<rnode-suppliers>`. For more information on RNode, you can also
refer to these additional external resources:
* `How To Make Your Own RNodes <https://unsigned.io/how-to-make-your-own-rnodes/>`_
* `Installing RNode Firmware on Compatible LoRa Devices <https://unsigned.io/installing-rnode-firmware-on-t-beam-and-lora32-devices/>`_
* `Private, Secure and Uncensorable Messaging Over a LoRa Mesh <https://unsigned.io/private-messaging-over-lora/>`_
* `RNode Firmware <https://github.com/markqvist/RNode_Firmware/>`_
If you have communications hardware that is not already supported by any of the
:ref:`existing interface types<interfaces-main>`, but you think would be suitable for use with Reticulum,
you are welcome to head over to the `GitHub discussion pages <https://github.com/markqvist/Reticulum/discussions>`_
and propose adding an interface for the hardware.
Develop a Program with Reticulum
===========================================
If you want to develop programs that use Reticulum, the easiest way to get
@@ -310,21 +345,26 @@ It is also possible to include Reticulum in apps compiled and distributed as
Android APKs. A detailed tutorial and example source code will be included
here at a later point.
Adding Radio Interfaces
Pure-Python Reticulum
==============================================
Once you have Reticulum installed and working, you can add radio interfaces with
any compatible hardware you have available. For information on how to configure
this, see the :ref:`Interfaces<interfaces-main>` section of this manual.
In some rare cases, and on more obscure system types, it is not possible to
install one or more dependencies
A range of common LoRa development boards and transceiver modules can be used
as interfaces with Reticulum. You can refer to the following external resources
for more information:
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 ``rnspure`` package instead of the ``rns`` package. The ``rnspure``
package requires no external dependencies for installation. Please note that the
actual contents of the ``rns`` and ``rnspure`` packages are *completely identical*.
The only difference is that the ``rnspure`` package lists no dependencies required
for installation.
* `How To Make Your Own RNodes <https://unsigned.io/how-to-make-your-own-rnodes/>`_
* `Installing RNode Firmware on Compatible LoRa Devices <https://unsigned.io/installing-rnode-firmware-on-t-beam-and-lora32-devices/>`_
* `Private, Secure and Uncensorable Messaging Over a LoRa Mesh <https://unsigned.io/private-messaging-over-lora/>`_
* `RNode Firmware <https://github.com/markqvist/RNode_Firmware/>`_
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``, it is perfectly possible to do so using
the `rnspure` package, but Reticulum will not be able to use serial-based interfaces.
All other available modules will still be loaded when needed.
If you have communications hardware that you think would be suitable for use with Reticulum,
you are welcome to head over to the `GitHub discussion pages <https://github.com/markqvist/Reticulum/discussions>`_
and propose adding an interface for the hardware.
**Please Note!** If you use the `rnspure` 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 :ref:`Cryptographic Primitives <understanding-primitives>`
section of this manual.
Binary file not shown.

After

Width:  |  Height:  |  Size: 191 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 259 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 562 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 249 KiB

Some files were not shown because too many files have changed in this diff Show More