Compare commits

...

244 Commits

Author SHA1 Message Date
Mark Qvist 95d3346da6 Fixed I2P interface missing attribute 2022-02-26 21:37:50 +01:00
Mark Qvist d4aabc8b89 Added I2P base32 address output to rnstatus utility 2022-02-26 21:04:54 +01:00
Mark Qvist d487609dcf Updated docs and manual 2022-02-26 20:46:14 +01:00
Mark Qvist c96c82f1d1 Updated manual 2022-02-26 19:35:37 +01:00
Mark Qvist cb023cde40 Fixed potential race condition in resource assembly 2022-02-26 18:27:11 +01:00
Mark Qvist 17be289f37 Updated documentation and manual 2022-02-25 22:34:41 +01:00
Mark Qvist b8105e23ff Fixed TCP interface mode reference 2022-02-25 22:10:55 +01:00
Mark Qvist f378d09cbe Updated documentation and manual 2022-02-25 21:50:03 +01:00
Mark Qvist 4dfa62833c Restructured default config 2022-02-25 21:48:25 +01:00
Mark Qvist 2ec6d3ba6c Updated I2P interface documentation 2022-02-25 21:41:43 +01:00
Mark Qvist 15d027e11e Restructured default config and added config example to rnsd 2022-02-25 21:41:24 +01:00
Mark Qvist 87a274d177 Added I2P interface documentation 2022-02-25 21:26:34 +01:00
Mark Qvist f8272793b4 Tuned AutoInterface timeouts 2022-02-25 20:29:47 +01:00
Mark Qvist 3a215be859 Interface mode defaults 2022-02-25 18:56:09 +01:00
Mark Qvist 0e1279d012 Added Access Point interface mode 2022-02-25 18:47:55 +01:00
Mark Qvist 8ec356a28e Interface outbound option enabled by default 2022-02-25 13:31:52 +01:00
Mark Qvist 49d7808835 i2plib license 2022-02-24 01:45:42 +01:00
Mark Qvist 48184134e4 Improved I2P Interface 2022-02-24 01:30:10 +01:00
Mark Qvist 987ff0658b Version bump 2022-02-23 22:53:16 +01:00
Mark Qvist 27dea7c524 Implemented I2PInterface 2022-02-23 22:43:08 +01:00
Mark Qvist 9c6fd132d4 Work on I2P Interface 2022-02-23 22:15:06 +01:00
Mark Qvist 8d58bb62ab Work on I2P Interface 2022-02-23 21:47:30 +01:00
Mark Qvist c357f7a94e Work on I2P Interface 2022-02-23 21:39:29 +01:00
Mark Qvist 4b3ead3db2 Work on I2P Interface 2022-02-23 21:29:18 +01:00
Mark Qvist b62e9af5d4 Work on I2P Interface 2022-02-23 21:19:43 +01:00
Mark Qvist fa82989a2e Preliminary I2P Interface support 2022-02-23 17:40:31 +01:00
Mark Qvist 07a65609b4 Updated documentation 2022-02-22 21:39:16 +01:00
Mark Qvist 257bd95da8 AutoInterface carrier loss detection 2022-02-22 20:16:02 +01:00
Mark Qvist 1ccfa9079c Work on AutoInterface recovery on WiFi carrier loss 2022-02-22 14:49:43 +01:00
Mark Qvist 57226201ff Fixed I2P tunneled config keyword 2022-02-22 14:45:36 +01:00
Mark Qvist d9419cd895 Merge branch 'master' of github.com:markqvist/Reticulum 2022-02-22 14:43:19 +01:00
Mark Qvist aae10ede72 Work on AutoInterface recovery on WiFi carrier loss 2022-02-22 14:43:14 +01:00
Mark Qvist 291b3056cd Updated docs 2022-02-01 23:07:18 +01:00
Mark Qvist 3f53c89d32 Added I2P-tunneled mode to TCP interfaces 2022-01-31 23:31:29 +01:00
Mark Qvist 05288d7c97 Updated documentation for release 2022-01-27 23:49:24 +01:00
Mark Qvist b403441074 Version updated 2022-01-27 23:30:44 +01:00
Mark Qvist d3a23e3b00 RNodeInterface firmware version check 2022-01-22 22:46:47 +01:00
markqvist 329d83587e Merge pull request #10 from 4c3e/4c3e-osx-nameerror-fix
OSX NameError fix
2022-01-22 21:39:01 +01:00
Mark Qvist 0a4dd64434 Improved support for ESP32-based RNodes 2022-01-22 21:36:49 +01:00
4c3e b96cbf1014 OSX NameError fix
Had the following error when trying to run Reticulum on OSX High Sierra:
line 115, in set_timeouts_osx
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
NameError: name 'sock' is not defined
This fix resolved the problem for me.
2022-01-21 01:34:55 +00:00
Mark Qvist 485558cd6b Updated documentation and manual 2022-01-14 22:21:13 +01:00
Mark Qvist 8d93867a22 Updated manual 2022-01-14 22:16:02 +01:00
markqvist 6b20a98adc Update README.md 2022-01-12 12:53:18 +01:00
Mark Qvist f3d04ba90f Improved AutoInterface handling on Android 2022-01-12 12:12:04 +01:00
Mark Qvist 1d2564cedb Interface import on Android 2022-01-12 12:02:00 +01:00
Mark Qvist bec8473695 Better Android detection 2022-01-12 11:50:03 +01:00
Mark Qvist 25620415a0 Updated platform utils 2022-01-12 11:18:24 +01:00
Mark Qvist b6df952995 Platform version check for Windows 2022-01-12 10:16:59 +01:00
Mark Qvist a72aaf12ca Platform version check for Windows 2022-01-12 10:07:44 +01:00
Mark Qvist b978a993b2 Version update 2022-01-11 03:07:34 +01:00
Mark Qvist 5ae00264e8 Preliminaly ESP32 support for RNodeInterface 2022-01-11 03:07:03 +01:00
Mark Qvist 5396b80e80 Updated example 2022-01-11 03:06:35 +01:00
Mark Qvist fdaa58a6fa Improved malformed packet detection 2022-01-11 03:06:16 +01:00
Mark Qvist 4253175627 Cleanup 2021-12-11 20:10:31 +01:00
Mark Qvist 81158c27e4 Cleanup 2021-12-11 18:41:28 +01:00
Mark Qvist eeb424ecee Link request debug 2021-12-11 18:33:09 +01:00
Mark Qvist 0273328b23 Link proof debug 2021-12-11 18:19:51 +01:00
Mark Qvist 20dfbcf0cc Link activation time 2021-12-11 17:26:45 +01:00
Mark Qvist c96e067839 Added proper requester interface detection for path requests for destinations behind local clients. 2021-12-11 16:50:03 +01:00
Mark Qvist 9ff37543f3 Adjusted request timeout 2021-12-11 16:42:57 +01:00
Mark Qvist 974ca48cb4 Adjusted peering timing 2021-12-11 16:42:15 +01:00
Mark Qvist 167d48c8ce Updated peering timeouts 2021-12-11 15:41:34 +01:00
Mark Qvist f253b08774 Updated documentation and manual 2021-12-10 20:10:11 +01:00
Mark Qvist 1c768e9219 Removed log statement 2021-12-10 18:55:17 +01:00
Mark Qvist df39cff520 Added recovery to local shared interfaces if master RNS instance is restarted 2021-12-10 18:32:24 +01:00
Mark Qvist e1e31692d7 UDP socket contructor and doc update 2021-12-10 16:23:35 +01:00
Mark Qvist 293a834c35 Log output and cleanup 2021-12-10 14:48:30 +01:00
Mark Qvist 1bbdd9b3f5 Ignore interfaces on Darwin 2021-12-10 11:10:09 +01:00
Mark Qvist d4b6b6ee59 Ignore interfaces on Darwin 2021-12-10 11:00:48 +01:00
Mark Qvist fca03bbdce Ignore AWDL interfaces on Darwin 2021-12-10 10:58:28 +01:00
Mark Qvist 29aa4f9315 Updated AutoInterface IPv6 bind address 2021-12-10 10:35:25 +01:00
Mark Qvist d5cac30a85 Log cleanup 2021-12-10 09:48:57 +01:00
Mark Qvist 6500bc7390 Updated docs 2021-12-09 18:53:28 +01:00
Mark Qvist 81fed10855 Updated readme 2021-12-09 18:35:38 +01:00
Mark Qvist a39876106b Updated readme 2021-12-09 18:35:01 +01:00
Mark Qvist 90b39774d1 Updated readme 2021-12-09 18:34:07 +01:00
Mark Qvist 006c70cd09 Updated documentation 2021-12-09 18:12:18 +01:00
Mark Qvist 02945f960d Updated timing 2021-12-09 17:44:19 +01:00
Mark Qvist e401ec870d Updated readme 2021-12-09 17:04:20 +01:00
Mark Qvist 90174fcc28 Cleanup 2021-12-09 17:02:13 +01:00
Mark Qvist c18ebed419 Added auto interface 2021-12-09 16:07:36 +01:00
Mark Qvist 1d180a96f6 Updated dependencies 2021-12-08 20:47:14 +01:00
Mark Qvist 4241990690 Implemented AutoInterface outbound traffic and multicast discovery listeners 2021-12-08 20:46:53 +01:00
Mark Qvist 3d49076602 Compatibility with IPv6 based interfaces 2021-12-08 20:45:41 +01:00
Mark Qvist 2e0dd278b6 Updated announce example 2021-12-08 20:45:03 +01:00
Mark Qvist b432a7c7de Updated documentation 2021-12-08 20:42:48 +01:00
Mark Qvist c0383fa2b0 Updated docs 2021-12-06 19:38:03 +01:00
Mark Qvist 98d66e2ba5 Updated documentation 2021-12-06 14:10:22 +01:00
Mark Qvist 2e4fcc659c Added KISS framing option to TCP client interface 2021-12-06 13:07:12 +01:00
Mark Qvist 8fe7c19c59 Updated documentation 2021-12-05 23:31:01 +01:00
Mark Qvist 27b46c9e89 Updated documentation 2021-12-05 23:28:15 +01:00
Mark Qvist 70a3637a98 Updated documentation 2021-12-05 23:26:52 +01:00
Mark Qvist 2e0476e6b9 Updated documentation 2021-12-05 23:24:30 +01:00
Mark Qvist 39911190aa Updated documentation 2021-12-05 16:07:53 +01:00
Mark Qvist 9e9606b8cf Systemd service support and documentation update 2021-12-05 16:05:43 +01:00
Mark Qvist 8be1acee0a Added auto reconnection for disconnected serial-based devices 2021-12-05 14:35:25 +01:00
Mark Qvist ba39a69175 Timeout default structure updated 2021-12-05 11:45:13 +01:00
Mark Qvist a692d29c90 Reconnect on serial port errors for KISS interface 2021-12-05 11:44:30 +01:00
Mark Qvist 7092589388 Updated documentation 2021-12-02 18:33:00 +01:00
Mark Qvist 2d3969aa3d Added makefile 2021-12-01 19:23:19 +01:00
Mark Qvist 1443f4c104 Updated umsgpack to 2.7.1 2021-12-01 19:20:24 +01:00
Mark Qvist d2232f19ba Removed pyserial dependency 2021-12-01 14:05:33 +01:00
Mark Qvist c44c6f9086 Conditional imports for serial-based interfaces 2021-12-01 13:57:40 +01:00
Mark Qvist 259c2aa397 Conditional imports for serial-based interfaces 2021-12-01 13:39:51 +01:00
Mark Qvist 10854bfdbc Added conditional import of netifaces 2021-12-01 11:46:19 +01:00
Mark Qvist f5236878b0 Added Android platform detection 2021-12-01 11:40:44 +01:00
Mark Qvist daf72f4237 Version updated 2021-12-01 11:40:31 +01:00
Mark Qvist 652b884d72 Added conditional import of netifaces 2021-12-01 11:39:40 +01:00
Mark Qvist ea3716f48e Added Android platform detection 2021-12-01 11:39:06 +01:00
Mark Qvist 165e620043 Improved shutdown handling on interrupt. Updated gitignore. 2021-11-04 17:15:58 +01:00
Mark Qvist 58f43b163e Updated docs 2021-10-15 19:26:53 +02:00
Mark Qvist 448ea8ceb5 Added try statements for various callbacks 2021-10-15 14:36:50 +02:00
Mark Qvist f7e8fc4719 Updated docs 2021-10-14 21:06:16 +02:00
Mark Qvist 1d6c877b4c Added RSSI and SNR to echo example 2021-10-12 18:31:46 +02:00
Mark Qvist c3dcd9366d Added RSSI and SNR to echo example 2021-10-12 18:09:02 +02:00
Mark Qvist 8d01586a5a Added RSSI and SNR to echo example 2021-10-12 18:04:55 +02:00
Mark Qvist 3e5f613f66 Fixed typo 2021-10-12 16:36:29 +02:00
Mark Qvist 614a139cd4 Merge branch 'master' of github.com:markqvist/Reticulum 2021-10-12 16:34:25 +02:00
Mark Qvist 1cf6570c2d Added RSSI and SNR reporting to packets on supported interfaces 2021-10-12 16:34:17 +02:00
Mark Qvist d207cbcd9c Update README.md 2021-10-11 15:26:21 +02:00
Mark Qvist 18b20f2d8d Update README.md 2021-10-11 15:26:07 +02:00
Mark Qvist c37533d2c7 Updated docs 2021-10-10 00:27:04 +02:00
Mark Qvist fd13e20165 Updated version 2021-10-09 23:23:44 +02:00
Mark Qvist 66ce58f0f4 Implemented path updating for moving nodes 2021-10-09 22:13:27 +02:00
Mark Qvist e8ee26f78d Emission timestamp in announce. 2021-10-09 21:36:01 +02:00
Mark Qvist c0fb419fe1 Fixed Resource string representation. Added emission timestamp in announce. 2021-10-09 21:30:34 +02:00
Mark Qvist 4ef369cdd8 Added logfile rotation 2021-10-08 19:23:10 +02:00
Mark Qvist a2f18b1daf Updated docs 2021-10-08 18:50:38 +02:00
Mark Qvist 2e411fa1de Updated docs 2021-10-08 18:49:13 +02:00
Mark Qvist 549dc40be6 Updated docs 2021-10-08 18:48:06 +02:00
Mark Qvist 1a99597f4d Updated documentation 2021-10-08 18:31:43 +02:00
Mark Qvist b21e0bee20 Updated documentation 2021-10-08 18:30:17 +02:00
Mark Qvist be8389a906 Updated readme 2021-10-08 17:54:17 +02:00
Mark Qvist 4ca00c6973 Added path expiry check to tunnel restoration 2021-10-08 17:09:31 +02:00
Mark Qvist 95f81cab7f Added path expiry check to tunnel restoration 2021-10-08 17:09:11 +02:00
Mark Qvist 60917f0eea Fixed interface detachment on TCP initiator interfaces 2021-10-08 17:06:00 +02:00
Mark Qvist de800f0ea7 Updated log output 2021-10-08 11:57:23 +02:00
Mark Qvist 5dad76879c Improved known destination saving on shared instances 2021-10-08 08:52:50 +02:00
Mark Qvist 75c3180933 Improved shared instance and local client handling 2021-10-03 15:23:12 +02:00
Mark Qvist 4c6ba97dca Updated readme 2021-09-26 12:34:19 +02:00
Mark Qvist cd6427cc9d Fixed rnstatus output 2021-09-25 23:56:56 +02:00
Mark Qvist 1749393732 Fixed rnstatus output 2021-09-25 23:44:59 +02:00
Mark Qvist dcde5035b9 Updated docs 2021-09-25 23:22:33 +02:00
Mark Qvist c14f6aa14a Updated documentation 2021-09-25 21:39:31 +02:00
Mark Qvist 77fe621cba Updated readme 2021-09-25 17:37:35 +02:00
Mark Qvist 129b1d0713 Updated readme 2021-09-25 17:35:51 +02:00
Mark Qvist 161eeca509 Updated logging 2021-09-25 15:39:42 +02:00
Mark Qvist f25906d44e Improved path utility output 2021-09-25 11:27:43 +02:00
Mark Qvist dd5133751e Updated utilities 2021-09-25 11:03:43 +02:00
Mark Qvist 5f8a55b702 Updated readme 2021-09-24 20:32:48 +02:00
Mark Qvist 7991db5c74 Added rnstatus utility 2021-09-24 20:10:04 +02:00
Mark Qvist f5510f9777 Added verbosity options to rnsd 2021-09-24 20:05:24 +02:00
Mark Qvist 05e0b17fbf Improved rnprobe utility output. 2021-09-24 16:49:07 +02:00
Mark Qvist 7e9d608530 Improved shutdown handling for local shared instances 2021-09-24 16:42:31 +02:00
Mark Qvist 3d4ac0126b Added signal handler and interface detachment oon exit. 2021-09-24 16:09:07 +02:00
Mark Qvist 81cdb0b7e6 Updated version 2021-09-24 15:34:33 +02:00
Mark Qvist c71660a9c3 Added verbosity level to utilities 2021-09-24 15:34:03 +02:00
Mark Qvist 9c1ac46989 Added loglevel override 2021-09-24 15:18:06 +02:00
Mark Qvist c5b792f64a Added rnprobe utility 2021-09-24 14:20:12 +02:00
Mark Qvist 76d75e9a3e Updated rnpath utility 2021-09-24 14:16:25 +02:00
Mark Qvist 9edb641058 Updated utility name 2021-09-24 14:15:15 +02:00
Mark Qvist 1bc2d4015e Fixed bug in reverse table culling 2021-09-24 14:14:34 +02:00
Mark Qvist ab4f3ad8ae Updated logging and default config 2021-09-24 14:13:31 +02:00
Mark Qvist 16dae81844 Fixed regression in TCPInterface client spawner. 2021-09-24 14:11:04 +02:00
Mark Qvist e9e2ffbe0d Improved log output from local interfaces 2021-09-24 14:10:18 +02:00
Mark Qvist dc36644a1e Added rnpath utility 2021-09-24 12:42:24 +02:00
Mark Qvist 8436bc5ba3 Update rnsd utility description 2021-09-24 11:26:29 +02:00
Mark Qvist 858d54f90d Added utility entry points 2021-09-24 11:21:08 +02:00
Mark Qvist 9323fd22ee Improved TCP client interface recovery 2021-09-24 11:20:10 +02:00
Mark Qvist 544e15afdf Added rnsd utility 2021-09-24 11:17:23 +02:00
Mark Qvist acae9e34c2 Improved link status detection and recovery of TCP interfaces over unreliable IP links. 2021-09-23 16:07:57 +02:00
Mark Qvist aaf0ace027 Updated version 2021-09-18 23:32:08 +02:00
Mark Qvist d8b76b4bc5 Improved config parsing 2021-09-18 23:24:12 +02:00
Mark Qvist d29ff38a05 Updated documentation 2021-09-18 23:13:36 +02:00
Mark Qvist 65e8487b39 Added TCP client reconnection on TCP socket drop 2021-09-18 22:49:04 +02:00
Mark Qvist 6362e04567 Cleanup 2021-09-18 21:52:28 +02:00
Mark Qvist 711b754dcc Implemented tunnel saving/restoring. 2021-09-18 21:47:37 +02:00
Mark Qvist 1351316f17 Implemented endpoint tunneling and path restoration on intermittent link layer connections. 2021-09-18 20:38:23 +02:00
Mark Qvist 7af14cec84 Work on tunnels 2021-09-18 20:33:42 +02:00
Mark Qvist 0687ee2231 Work on tunnels 2021-09-18 20:31:43 +02:00
Mark Qvist 872075a31e Work on tunnels 2021-09-18 20:13:51 +02:00
Mark Qvist d8f0380aa9 Work on tunnels 2021-09-18 20:10:39 +02:00
Mark Qvist 569f9bd2b1 Work on tunnels 2021-09-18 19:46:28 +02:00
Mark Qvist 450b88d0f0 Work on tunnels 2021-09-18 19:14:30 +02:00
Mark Qvist 1cb9df109a Work on tunnels 2021-09-18 19:12:09 +02:00
Mark Qvist 80455c9614 Work on tunnels 2021-09-18 19:08:45 +02:00
Mark Qvist c1e280d896 Work on tunnels 2021-09-18 18:54:57 +02:00
Mark Qvist 4a2925cdea Work on tunnels 2021-09-18 18:54:01 +02:00
Mark Qvist 7f38c32e90 Work on tunnels 2021-09-18 18:40:49 +02:00
Mark Qvist 8646be0dcf Work on tunnels 2021-09-18 18:40:27 +02:00
Mark Qvist 6b3cc07740 Work on tunnels 2021-09-18 18:35:10 +02:00
Mark Qvist 3b57b0013b Work on tunnels 2021-09-18 18:34:00 +02:00
Mark Qvist 24d8f39dd1 Work on tunnels 2021-09-18 18:33:28 +02:00
Mark Qvist 58e4bf3c80 Work on tunnels 2021-09-18 18:32:12 +02:00
Mark Qvist 1da8a0c8f1 Work on tunnels 2021-09-18 18:26:50 +02:00
Mark Qvist 8b8d4410ef Work on tunnels 2021-09-18 18:21:32 +02:00
Mark Qvist 7d804daa8f Work on tunnels 2021-09-18 18:19:42 +02:00
Mark Qvist ce00822cb0 Work on tunnels 2021-09-18 18:11:23 +02:00
Mark Qvist 6d6c91edaf Updated docs 2021-09-18 18:10:58 +02:00
Mark Qvist 8432cf40c2 Updated documentation 2021-09-18 16:29:47 +02:00
Mark Qvist 5e21bdd233 Improved link teardown handling. 2021-09-16 20:40:37 +02:00
Mark Qvist c7e5f4612a Updated documentation. 2021-09-11 16:11:44 +02:00
Mark Qvist f80e09cb5c Included six internally. 2021-09-11 16:03:35 +02:00
Mark Qvist 91d94f2f6f Fixed incorrect transfer size indications on single-packet request responses with msgpacked dictionaries as payloads. 2021-09-10 21:35:30 +02:00
Mark Qvist 53ca86ebfc Merge branch 'master' of github.com:markqvist/Reticulum 2021-09-05 20:06:21 +02:00
Mark Qvist 534bb28900 Fixed removal of non-existing receipts. 2021-09-05 20:05:12 +02:00
Mark Qvist 0de5ec73ad Merge pull request #8 from xtoddx/master
Record dependency on six
2021-09-05 15:02:49 +02:00
xtoddx c0f627b50b Record dependency on six 2021-09-04 22:58:42 -04:00
Mark Qvist 5629a062a5 Added resource window timeout recalculations during transfer. 2021-09-03 22:53:25 +02:00
Mark Qvist 83232f0446 Work on resource timing. 2021-09-03 22:20:16 +02:00
Mark Qvist aa794514b3 Work on resource timing. 2021-09-03 22:01:58 +02:00
Mark Qvist 07cf180ea8 Added continous resource timeout adjustment. Fixes missing response timeout check. 2021-09-03 21:08:20 +02:00
Mark Qvist 42a3d23e99 Optimised resource transfer timings. Improved request/response timeout handling. 2021-09-03 18:53:37 +02:00
Mark Qvist d28c888d1c Improved link request/response handling. 2021-09-03 16:24:47 +02:00
Mark Qvist 58d48c18f4 Improved link request/response handling. 2021-09-03 16:23:31 +02:00
Mark Qvist ecf0c55fd6 Improved link establishment. 2021-09-03 16:14:08 +02:00
Mark Qvist 32e4c262ef Improved link timeout handling. 2021-09-03 15:47:42 +02:00
Mark Qvist f87a6a57df Added link error handling. 2021-09-03 15:08:38 +02:00
Mark Qvist 6373f159f8 Added link error handling. 2021-09-03 14:42:59 +02:00
Mark Qvist ad9f548eeb Improved request timeout calculation and handling. 2021-09-03 14:22:53 +02:00
Mark Qvist 425f0153d0 Added flow control timeouts to AX.25 interface and optimised timeouts. 2021-09-03 10:56:49 +02:00
Mark Qvist cd9daaefee Removed option to allow unencrypted links. 2021-09-03 10:13:48 +02:00
Mark Qvist 0fe76d50f6 Improved documentation. 2021-09-02 20:35:42 +02:00
Mark Qvist 9562803bb3 Optimised sent Fernet token data. 2021-09-02 18:42:17 +02:00
Mark Qvist e9c89209c7 Optimised sent Fernet token data. 2021-09-02 18:34:58 +02:00
Mark Qvist cd8de64201 Implemented ability to change MTU. 2021-09-02 18:00:03 +02:00
Mark Qvist 40f7a6d359 Added resource HMU/part request hash filter. Fixes #7. 2021-09-02 14:44:53 +02:00
Mark Qvist 0c96508cca Updated default config. 2021-08-29 13:48:12 +02:00
Mark Qvist 1fd59f1a02 Fixed resource sequencing fail handling. 2021-08-29 13:46:31 +02:00
Mark Qvist 0a0d0af821 Updated docs. 2021-08-29 13:13:51 +02:00
Mark Qvist b694cbdc91 Improved announce handling for local clients. 2021-08-29 12:43:54 +02:00
Mark Qvist 71c3333e10 Improved announce handling for local clients. 2021-08-29 12:28:30 +02:00
Mark Qvist 972fcdee22 Fixed identity saving. 2021-08-29 01:24:21 +02:00
Mark Qvist 17dbfe6401 Updated speed test example. 2021-08-29 00:13:50 +02:00
Mark Qvist 781cb4712d Fixed request packet receipts timing out in spite of delivery. 2021-08-28 23:53:51 +02:00
Mark Qvist cdb08325cc Fixed timeout calculation condition. 2021-08-28 20:34:41 +02:00
Mark Qvist 62d954d7bf Fixed timeout calculation condition. 2021-08-28 20:21:50 +02:00
Mark Qvist 4bbf1ae57d Updated docs 2021-08-28 20:10:00 +02:00
Mark Qvist 2678aeb6a1 Improved timeout calculation and handling. 2021-08-28 20:01:01 +02:00
Mark Qvist 6d441dac02 Better resource advertisement timeout. 2021-08-28 14:41:25 +02:00
Mark Qvist 66b2be87f4 Added speedtest example. 2021-08-27 22:59:34 +02:00
Mark Qvist 2e7126ef39 Cleaned up log statements 2021-08-27 22:51:16 +02:00
Mark Qvist c0f909850b Updated docs. 2021-08-27 19:54:57 +02:00
Mark Qvist a199e4c929 Improved link and resource callbacks and resource handling. 2021-08-27 19:52:48 +02:00
90 changed files with 10587 additions and 1366 deletions
+1
View File
@@ -3,6 +3,7 @@
testutils
TODO
Examples/RNS
RNS/Utilities/RNS
build
dist
docs/build
+1 -1
View File
@@ -15,7 +15,7 @@ import RNS
APP_NAME = "example_utilities"
# We initialise two lists of strings to use as app_data
fruits = ["Peach", "Quince", "Date palm", "Tangerine", "Pomelo", "Carambola", "Grape"]
fruits = ["Peach", "Quince", "Date", "Tangerine", "Pomelo", "Carambola", "Grape"]
noble_gases = ["Helium", "Neon", "Argon", "Krypton", "Xenon", "Radon", "Oganesson"]
# This initialisation is executed when the program is started
+49 -2
View File
@@ -22,6 +22,8 @@ APP_NAME = "example_utilities"
# This initialisation is executed when the users chooses
# to run as a server
def server(configpath):
global reticulum
# We must first initialise Reticulum
reticulum = RNS.Reticulum(configpath)
@@ -78,11 +80,32 @@ def announceLoop(destination):
def server_callback(message, packet):
global reticulum
# Tell the user that we received an echo request, and
# that we are going to send a reply to the requester.
# Sending the proof is handled automatically, since we
# set up the destination to prove all incoming packets.
RNS.log("Received packet from echo client, proof sent")
reception_stats = ""
if reticulum.is_connected_to_shared_instance:
reception_rssi = reticulum.get_packet_rssi(packet.packet_hash)
reception_snr = reticulum.get_packet_snr(packet.packet_hash)
if reception_rssi != None:
reception_stats += " [RSSI "+str(reception_rssi)+" dBm]"
if reception_snr != None:
reception_stats += " [SNR "+str(reception_snr)+" dBm]"
else:
if packet.rssi != None:
reception_stats += " [RSSI "+str(packet.rssi)+" dBm]"
if packet.snr != None:
reception_stats += " [SNR "+str(packet.snr)+" dB]"
RNS.log("Received packet from echo client, proof sent"+reception_stats)
##########################################################
@@ -92,6 +115,8 @@ def server_callback(message, packet):
# This initialisation is executed when the users chooses
# to run as a client
def client(destination_hexhash, configpath, timeout=None):
global reticulum
# We need a binary representation of the destination
# hash that was entered on the command line
try:
@@ -188,6 +213,8 @@ def client(destination_hexhash, configpath, timeout=None):
# This function is called when our reply destination
# receives a proof packet.
def packet_delivered(receipt):
global reticulum
if receipt.status == RNS.PacketReceipt.DELIVERED:
rtt = receipt.get_rtt()
if (rtt >= 1):
@@ -197,10 +224,30 @@ def packet_delivered(receipt):
rtt = round(rtt*1000, 3)
rttstring = str(rtt)+" milliseconds"
reception_stats = ""
if reticulum.is_connected_to_shared_instance:
reception_rssi = reticulum.get_packet_rssi(receipt.proof_packet.packet_hash)
reception_snr = reticulum.get_packet_snr(receipt.proof_packet.packet_hash)
if reception_rssi != None:
reception_stats += " [RSSI "+str(reception_rssi)+" dBm]"
if reception_snr != None:
reception_stats += " [SNR "+str(reception_snr)+" dB]"
else:
if receipt.proof_packet != None:
if receipt.proof_packet.rssi != None:
reception_stats += " [RSSI "+str(receipt.proof_packet.rssi)+" dBm]"
if receipt.proof_packet.snr != None:
reception_stats += " [SNR "+str(receipt.proof_packet.snr)+" dB]"
RNS.log(
"Valid reply received from "+
RNS.prettyhexrep(receipt.destination.hash)+
", round-trip time is "+rttstring
", round-trip time is "+rttstring+
reception_stats
)
# This function is called if a packet times out.
+1 -1
View File
@@ -358,7 +358,7 @@ def print_menu():
print("")
while menu_mode == "downloading":
global current_download
percent = round(current_download.progress() * 100.0, 1)
percent = round(current_download.get_progress() * 100.0, 1)
print(("\rProgress: "+str(percent)+" % "), end=' ')
sys.stdout.flush()
time.sleep(0.1)
+343
View File
@@ -0,0 +1,343 @@
##########################################################
# This RNS example demonstrates a simple speedtest #
# program to measure link throughput. #
# #
# The current configuration is suited for testing fast #
# links. If you want to measure slow links like LoRa or #
# packet radio, you must significantly lower the #
# data_cap variable, which defines how much data is sent #
# for each test. #
##########################################################
import os
import sys
import time
import argparse
import RNS
# Let's define an app name. We'll use this for all
# destinations we create.
APP_NAME = "example_utilities"
##########################################################
#### Server Part #########################################
##########################################################
latest_client_link = None
first_packet_at = None
last_packet_at = None
received_data = 0
rc = 0
data_cap = 2*1024*1024
printed = False
# This initialisation is executed when the users chooses
# to run as a server
def server(configpath):
# We must first initialise Reticulum
reticulum = RNS.Reticulum(configpath)
# Randomly create a new identity for our link example
server_identity = RNS.Identity()
# We create a destination that clients can connect to. We
# want clients to create links to this destination, so we
# need to create a "single" destination type.
server_destination = RNS.Destination(
server_identity,
RNS.Destination.IN,
RNS.Destination.SINGLE,
APP_NAME,
"speedtest"
)
# We configure a function that will get called every time
# a new client creates a link to this destination.
server_destination.set_link_established_callback(client_connected)
# Everything's ready!
# Let's Wait for client requests or user input
server_loop(server_destination)
def server_loop(destination):
# Let the user know that everything is ready
RNS.log(
"Speedtest "+
RNS.prettyhexrep(destination.hash)+
" running, waiting for a connection."
)
RNS.log("Hit enter to manually send an announce (Ctrl-C to quit)")
# We enter a loop that runs until the users exits.
# If the user hits enter, we will announce our server
# destination on the network, which will let clients
# know how to create messages directed towards it.
while True:
entered = input()
destination.announce()
RNS.log("Sent announce from "+RNS.prettyhexrep(destination.hash))
# When a client establishes a link to our server
# destination, this function will be called with
# a reference to the link.
def client_connected(link):
global latest_client_link, first_packet_at, rc
RNS.log("Client connected")
first_packet_at = time.time()
rc = 0
link.set_link_closed_callback(client_disconnected)
link.set_packet_callback(server_packet_received)
latest_client_link = link
def client_disconnected(link):
RNS.log("Client disconnected")
# A convenience function for printing a human-
# readable file size
def size_str(num, suffix='B'):
units = ['','Ki','Mi','Gi','Ti','Pi','Ei','Zi']
last_unit = 'Yi'
if suffix == 'b':
num *= 8
units = ['','K','M','G','T','P','E','Z']
last_unit = 'Y'
for unit in units:
if abs(num) < 1024.0:
return "%3.2f %s%s" % (num, unit, suffix)
num /= 1024.0
return "%.2f %s%s" % (num, last_unit, suffix)
def server_packet_received(message, packet):
global latest_client_link, first_packet_at, last_packet_at, received_data, rc, data_cap
received_data += len(packet.data)
rc += 1
if rc >= 50:
RNS.log(size_str(received_data))
rc = 0
if received_data > data_cap:
rcv_d = received_data
received_data = 0
rc = 0
last_packet_at = time.time()
# Print statistics
download_time = last_packet_at-first_packet_at
hours, rem = divmod(download_time, 3600)
minutes, seconds = divmod(rem, 60)
timestring = "{:0>2}:{:0>2}:{:05.2f}".format(int(hours),int(minutes),seconds)
print("")
print("")
print("--- Statistics -----")
print("\tTime taken : "+timestring)
print("\tData transferred : "+size_str(rcv_d))
print("\tTransfer rate : "+size_str(rcv_d/download_time, suffix='b')+"/s")
print("")
sys.stdout.flush()
latest_client_link.teardown()
time.sleep(0.2)
rc = 0
received_data = 0
# latest_client_link.teardown()
# os._exit(0)
##########################################################
#### Client Part #########################################
##########################################################
# A reference to the server link
server_link = None
# This initialisation is executed when the users chooses
# to run as a client
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)")
destination_hash = bytes.fromhex(destination_hexhash)
except:
RNS.log("Invalid destination entered. Check your input!\n")
exit()
# We must first initialise Reticulum
reticulum = RNS.Reticulum(configpath)
# Check if we know a path to the destination
if not RNS.Transport.has_path(destination_hash):
RNS.log("Destination is not yet known. Requesting path and waiting for announce to arrive...")
RNS.Transport.request_path(destination_hash)
while not RNS.Transport.has_path(destination_hash):
time.sleep(0.1)
# Recall the server identity
server_identity = RNS.Identity.recall(destination_hash)
# Inform the user that we'll begin connecting
RNS.log("Establishing link with server...")
# When the server identity is known, we set
# up a destination
server_destination = RNS.Destination(
server_identity,
RNS.Destination.OUT,
RNS.Destination.SINGLE,
APP_NAME,
"speedtest"
)
# And create a link
link = RNS.Link(server_destination)
# We'll also set up functions to inform the
# user when the link is established or closed
link.set_link_established_callback(link_established)
link.set_link_closed_callback(link_closed)
# Everything is set up, so let's enter a loop
# for the user to interact with the example
client_loop()
def client_loop():
global server_link
# Wait for the link to become active
while not server_link:
time.sleep(0.1)
should_quit = False
while not should_quit:
try:
text = input()
# Check if we should quit the example
if text == "quit" or text == "q" or text == "exit":
should_quit = True
server_link.teardown()
except Exception as e:
raise e
# This function is called when a link
# has been established with the server
def link_established(link):
# We store a reference to the link
# instance for later use
global server_link, data_cap, printed
server_link = link
data_sent = 0
# Inform the user that the server is
# connected
RNS.log("Link established with server,sending...")
rd = os.urandom(RNS.Link.MDU)
started = time.time()
while link.status == RNS.Link.ACTIVE and data_sent < data_cap*1.25:
RNS.Packet(server_link, rd, create_receipt=False).send()
data_sent += len(rd)
if data_sent > data_cap and not printed:
printed = True
ended = time.time()
# Print statistics
download_time = ended-started
hours, rem = divmod(download_time, 3600)
minutes, seconds = divmod(rem, 60)
timestring = "{:0>2}:{:0>2}:{:05.2f}".format(int(hours),int(minutes),seconds)
print("")
print("")
print("--- Statistics -----")
print("\tTime taken : "+timestring)
print("\tData transferred : "+size_str(data_sent))
print("\tTransfer rate : "+size_str(data_sent/download_time, suffix='b')+"/s")
print("")
sys.stdout.flush()
time.sleep(0.1)
# When a link is closed, we'll inform the
# user, and exit the program
def link_closed(link):
if link.teardown_reason == RNS.Link.TIMEOUT:
RNS.log("The link timed out, exiting now")
elif link.teardown_reason == RNS.Link.DESTINATION_CLOSED:
RNS.log("The link was closed by the server, exiting now")
else:
RNS.log("Link closed, exiting now")
RNS.Reticulum.exit_handler()
time.sleep(1.5)
os._exit(0)
def client_packet_received(message, packet):
pass
##########################################################
#### Program Startup #####################################
##########################################################
# This part of the program runs at startup,
# and parses input of from the user, and then
# starts up the desired program mode.
if __name__ == "__main__":
try:
parser = argparse.ArgumentParser(description="Speedtest example")
parser.add_argument(
"-s",
"--server",
action="store_true",
help="wait for incoming requests from clients"
)
parser.add_argument(
"--config",
action="store",
default=None,
help="path to alternative Reticulum config directory",
type=str
)
parser.add_argument(
"destination",
nargs="?",
default=None,
help="hexadecimal hash of the server destination",
type=str
)
args = parser.parse_args()
if args.config:
configarg = args.config
else:
configarg = None
if args.server:
server(configarg)
else:
if (args.destination == None):
print("")
parser.print_help()
print("")
else:
client(args.destination, configarg)
except KeyboardInterrupt:
print("")
exit()
+25
View File
@@ -0,0 +1,25 @@
all: release
clean:
@echo Cleaning...
-rm -r ./build
-rm -r ./dist
remove_symlinks:
@echo Removing symlinks for build...
-rm Examples/RNS
-rm RNS/Utilities/RNS
create_symlinks:
@echo Creating symlinks...
-ln -s ../RNS ./Examples/
-ln -s ../../RNS ./RNS/Utilities/
build_wheel:
python3 setup.py sdist bdist_wheel
release: remove_symlinks build_wheel create_symlinks
upload:
@echo Uploading to PyPi...
twine upload dist/*
+51 -26
View File
@@ -3,12 +3,13 @@ Reticulum Network Stack β
Reticulum is a cryptography-based networking stack for wide-area networks built on readily available hardware, and can operate even with very high latency and extremely low bandwidth. Reticulum allows you to build very wide-area networks with off-the-shelf tools, and offers end-to-end encryption, autoconfiguring cryptographically backed multi-hop transport, efficient addressing, unforgeable packet acknowledgements and more.
Reticulum is a complete networking stack, and does not use IP or higher layers, although it is easy to utilise 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 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.
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.
No kernel modules or drivers are required. Reticulum runs completely in userland, and can run on practically any system that runs Python 3.
## Read The Manual
The full documentation for Reticulum is available at [markqvist.github.io/Reticulum/manual/](https://markqvist.github.io/Reticulum/manual/).
You can also [download the Reticulum manual as a PDF](https://github.com/markqvist/Reticulum/raw/master/docs/Reticulum%20Manual.pdf)
@@ -18,13 +19,14 @@ For more info, see [unsigned.io/projects/reticulum](https://unsigned.io/projects
## Notable Features
- Coordination-less globally unique adressing 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
- Reticulum uses the [Fernet](https://github.com/fernet/spec/blob/master/Spec.md) specification for encryption
- 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
- HMAC using SHA256 for authentication
- IVs are generated through os.urandom()
- Keys are ephemeral and derived from an ECDH key exchange on Curve25519
- Unforgeable packet delivery confirmations
- A variety of supported interface types
- An intuitive and easy-to-use API
@@ -34,11 +36,17 @@ For more info, see [unsigned.io/projects/reticulum](https://unsigned.io/projects
- 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 a link is 3 packets totalling 240 bytes
- Total bandwidth cost of setting up a link is 3 packets totalling 237 bytes
- Low cost of keeping links open at only 0.62 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.
- For an off-grid, encrypted and resilient mesh communications platform, see [Nomad Network](https://github.com/markqvist/NomadNet)
- For a distributed, delay and disruption tolerant message transfer protocol built on Reticulum, see [LXMF](https://github.com/markqvist/lxmf)
## Where can Reticulum be used?
Over practically any medium that can support at least a half-duplex channel with 1.000 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, ad-hoc WiFi, free-space optical links and similar systems are all examples of the types of interfaces Reticulum was designed for.
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.
@@ -46,6 +54,22 @@ Reticulum can also be encapsulated over existing IP networks, so there's nothing
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.
## How do I get started?
The best way to get started with the Reticulum Network Stack depends on what
you want to do. For full details and examples, have a look at the [Getting Started Fast](https://markqvist.github.io/Reticulum/manual/gettingstartedfast.html) section of the [Reticulum Manual](https://markqvist.github.io/Reticulum/manual/).
To simply install Reticulum and related utilities on your system, the easiest way is via pip:
```bash
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.
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/).
## 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.
@@ -60,33 +84,34 @@ Reticulum implements a range of generalised interface types that covers most of
- TCP over IP networks
- UDP over IP networks
## What is currently being worked on?
- API documentation
- Useful example programs and utilities
- A delay and disruption tolerant message transfer protocol built on Reticulum, see [LXMF](https://github.com/markqvist/lxmf)
- A few useful-in-the-real-world apps built with Reticulum
## Can I use Reticulum on amateur radio spectrum?
Some countries still ban the use of encryption when operating under an amateur radio license. Reticulum offers several encryptionless modes, while still using cryptographic principles for station verification, link establishment, data integrity verification, acknowledgements and routing. It is therefore perfectly possible to include Reticulum in amateur radio use, even if your country bans encryption.
## Planned Features
- More interface types for even broader compatibility
- ESP32 devices (ESP-Now, Bluetooth, etc.)
- More LoRa transceivers
- AT-compatible modems
- AWDL / OWL
- HF Modems
- CAN-bus
- ZeroMQ
- MQTT
- SPI
- i²c
- Globally routable multicast
## Dependencies:
- Python 3
- Python 3.6
- cryptography.io
- netifaces
- pyserial
## How do I get started?
The best way to get started with the Reticulum Network Stack depends on what
you want to do. For full details and examples, have a look at the [Getting Started Fast](https://markqvist.github.io/Reticulum/manual/gettingstartedfast.html) section of the [Reticulum Manual](https://markqvist.github.io/Reticulum/manual/).
## Support Reticulum
You can help support the continued development of open, free and private communications systems by donating via one of the following channels:
If you just need Reticulum as a dependency for another application, the easiest way is via pip:
```bash
pip3 install rns
```
The default config file contains examples for using Reticulum with LoRa transceivers (specifically [RNode](https://unsigned.io/projects/rnode/)), packet radio TNCs/modems and UDP. By default a UDP interface is already enabled in the default config, which will enable Reticulum communication in your local ethernet broadcast domain.
You can use the examples in the config file to expand communication over other mediums such as packet radio or LoRa, or over fast IP links using the UDP interface. I'll add in-depth tutorials and explanations on these topics later. For now, the included examples will hopefully be enough to get started.
- Ethereum: 0x81F7B979fEa6134bA9FD5c701b3501A2e61E897a
- Bitcoin: 3CPmacGm34qYvR6XWLVEJmi2aNe3PZqUuq
- Ko-Fi: https://ko-fi.com/markqvist
## Caveat Emptor
Reticulum is experimental 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.
Reticulum is experimental 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.
+8 -3
View File
@@ -1,5 +1,6 @@
import base64
import math
import time
import RNS
from cryptography.fernet import Fernet
@@ -22,7 +23,7 @@ class Destination:
encrypted communication with it.
:param identity: An instance of :ref:`RNS.Identity<api-identity>`. Can hold only public keys for an outgoing destination, or holding private keys for an ingoing.
:param direction: ``RNS.Destination.IN`` or ``RNS.Destination.OUT``
:param direction: ``RNS.Destination.IN`` or ``RNS.Destination.OUT``.
:param type: ``RNS.Destination.SINGLE``, ``RNS.Destination.GROUP`` or ``RNS.Destination.PLAIN``.
:param app_name: A string specifying the app name.
:param \*aspects: Any non-zero number of string arguments.
@@ -146,7 +147,7 @@ class Destination:
:param path_response: Internal flag used by :ref:`RNS.Transport<api-transport>`. Ignore.
"""
destination_hash = self.hash
random_hash = RNS.Identity.get_random_hash()
random_hash = RNS.Identity.get_random_hash()[0:5]+int(time.time()).to_bytes(5, "big")
if app_data == None and self.default_app_data != None:
if isinstance(self.default_app_data, bytes):
@@ -262,7 +263,11 @@ class Destination:
if plaintext != None:
if packet.packet_type == RNS.Packet.DATA:
if self.callbacks.packet != None:
self.callbacks.packet(plaintext, packet)
try:
self.callbacks.packet(plaintext, packet)
except Exception as e:
RNS.log("Error while executing receive callback from "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
def incoming_link_request(self, data, packet):
link = RNS.Link.validate_request(self, data, packet)
+55 -44
View File
@@ -34,12 +34,13 @@ class Identity:
"""
# Non-configurable constants
AES_HMAC_OVERHEAD = 58 # In bytes
FERNET_VERSION = 0x80
FERNET_OVERHEAD = 54 # In bytes
AES128_BLOCKSIZE = 16 # In bytes
HASHLENGTH = 256 # In bits
SIGLENGTH = KEYSIZE # In bits
TRUNCATED_HASHLENGTH = 80 # In bits
TRUNCATED_HASHLENGTH = RNS.Reticulum.TRUNCATED_HASHLENGTH
"""
Constant specifying the truncated hash length (in bits) used by Reticulum
for addressable hashes and other purposes. Non-configurable.
@@ -64,16 +65,13 @@ class Identity:
:param destination_hash: Destination hash as *bytes*.
:returns: An :ref:`RNS.Identity<api-identity>` instance that can be used to create an outgoing :ref:`RNS.Destination<api-destination>`, or *None* if the destination is unknown.
"""
RNS.log("Searching for "+RNS.prettyhexrep(destination_hash)+"...", RNS.LOG_EXTREME)
if destination_hash in Identity.known_destinations:
identity_data = Identity.known_destinations[destination_hash]
identity = Identity(create_keys=False)
identity.load_public_key(identity_data[2])
identity.app_data = identity_data[3]
RNS.log("Found "+RNS.prettyhexrep(destination_hash)+" in known destinations", RNS.LOG_EXTREME)
return identity
else:
RNS.log("Could not find "+RNS.prettyhexrep(destination_hash)+" in known destinations", RNS.LOG_EXTREME)
return None
@staticmethod
@@ -84,22 +82,35 @@ class Identity:
:param destination_hash: Destination hash as *bytes*.
:returns: *Bytes* containing app_data, or *None* if the destination is unknown.
"""
RNS.log("Searching for app_data for "+RNS.prettyhexrep(destination_hash)+"...", RNS.LOG_EXTREME)
if destination_hash in Identity.known_destinations:
app_data = Identity.known_destinations[destination_hash][3]
RNS.log("Found "+RNS.prettyhexrep(destination_hash)+" app_data in known destinations", RNS.LOG_EXTREME)
return app_data
else:
RNS.log("Could not find "+RNS.prettyhexrep(destination_hash)+" app_data in known destinations", RNS.LOG_EXTREME)
return None
@staticmethod
def save_known_destinations():
RNS.log("Saving known destinations to storage...", RNS.LOG_VERBOSE)
file = open(RNS.Reticulum.storagepath+"/known_destinations","wb")
umsgpack.dump(Identity.known_destinations, file)
file.close()
RNS.log("Done saving known destinations to storage", RNS.LOG_VERBOSE)
try:
storage_known_destinations = {}
if os.path.isfile(RNS.Reticulum.storagepath+"/known_destinations"):
try:
file = open(RNS.Reticulum.storagepath+"/known_destinations","rb")
storage_known_destinations = umsgpack.load(file)
file.close()
except:
pass
for destination_hash in storage_known_destinations:
if not destination_hash in Identity.known_destinations:
Identity.known_destinations[destination_hash] = storage_known_destinations[destination_hash]
RNS.log("Saving known destinations to storage...", RNS.LOG_VERBOSE)
file = open(RNS.Reticulum.storagepath+"/known_destinations","wb")
umsgpack.dump(Identity.known_destinations, file)
file.close()
RNS.log("Done saving known destinations to storage", RNS.LOG_VERBOSE)
except Exception as e:
RNS.log("Error while saving known destinations to disk, the contained exception was: "+str(e), RNS.LOG_ERROR)
@staticmethod
def load_known_destinations():
@@ -112,7 +123,7 @@ class Identity:
except:
RNS.log("Error loading known destinations from disk, file will be recreated on exit", RNS.LOG_ERROR)
else:
RNS.log("Destinations file does not exist, so no known destinations loaded", RNS.LOG_VERBOSE)
RNS.log("Destinations file does not exist, no known destinations loaded", RNS.LOG_VERBOSE)
@staticmethod
def full_hash(data):
@@ -182,6 +193,22 @@ class Identity:
Identity.save_known_destinations()
@staticmethod
def from_bytes(prv_bytes):
"""
Create a new :ref:`RNS.Identity<api-identity>` instance from *bytes* of private key.
Can be used to load previously created and saved identities into Reticulum.
:param prv_bytes: The *bytes* of private a saved private key. **HAZARD!** Never use this to generate a new key by feeding random data in prv_bytes.
:returns: A :ref:`RNS.Identity<api-identity>` instance, or *None* if the *bytes* data was invalid.
"""
identity = Identity(create_keys=False)
if identity.load_private_key(prv_bytes):
return identity
else:
return None
@staticmethod
def from_file(path):
"""
@@ -197,21 +224,23 @@ class Identity:
else:
return None
@staticmethod
def from_bytes(prv_bytes):
def to_file(self, path):
"""
Create a new :ref:`RNS.Identity<api-identity>` instance from *bytes* of private key.
Can be used to load previously created and saved identities into Reticulum.
Saves the identity to a file. This will write the private key to disk,
and anyone with access to this file will be able to decrypt all
communication for the identity. Be very careful with this method.
:param prv_bytes: The *bytes* of private a saved private key. **HAZARD!** Never not use this to generate a new key by feeding random data in prv_bytes.
:returns: A :ref:`RNS.Identity<api-identity>` instance, or *None* if the *bytes* data was invalid.
:param path: The full path specifying where to save the identity.
:returns: True if the file was saved, otherwise False.
"""
identity = Identity(create_keys=False)
if identity.load_private_key(prv_bytes):
return identity
else:
return None
try:
with open(path, "wb") as key_file:
key_file.write(self.get_private_key())
return True
return False
except Exception as e:
RNS.log("Error while saving identity to "+str(path), RNS.LOG_ERROR)
RNS.log("The contained exception was: "+str(e))
def __init__(self,create_keys=True):
# Initialize keys to none
@@ -331,24 +360,6 @@ class Identity:
self.hash = Identity.truncated_hash(self.get_public_key())
self.hexhash = self.hash.hex()
def to_file(self, path):
"""
Saves the identity to a file. This will write the private key to disk,
and anyone with access to this file will be able to decrypt all
communication for the identity. Be very careful with this method.
:param path: The full path specifying where to save the identity.
:returns: True if the file was saved, otherwise False.
"""
try:
with open(path, "wb") as key_file:
key_file.write(self.get_public_key())
return True
return False
except Exception as e:
RNS.log("Error while saving identity to "+str(path), RNS.LOG_ERROR)
RNS.log("The contained exception was: "+str(e))
def load(self, path):
try:
with open(path, "rb") as key_file:
+83 -42
View File
@@ -2,7 +2,6 @@
from .Interface import Interface
from time import sleep
import sys
import serial
import threading
import time
import RNS
@@ -48,6 +47,18 @@ class AX25KISSInterface(Interface):
serial = None
def __init__(self, owner, name, callsign, ssid, port, speed, databits, parity, stopbits, preamble, txtail, persistence, slottime, flow_control):
import importlib
if importlib.util.find_spec('serial') != None:
import serial
else:
RNS.log("Using the AX.25 KISS interface requires a serial communication module to be installed.", RNS.LOG_CRITICAL)
RNS.log("You can install one with the command: python3 -m pip install pyserial", RNS.LOG_CRITICAL)
RNS.panic()
self.rxb = 0
self.txb = 0
self.pyserial = serial
self.serial = None
self.owner = owner
self.name = name
@@ -62,13 +73,12 @@ class AX25KISSInterface(Interface):
self.stopbits = stopbits
self.timeout = 100
self.online = False
# TODO: Sane default and make this configurable
# TODO: Changed to 25ms instead of 100ms, check it
self.txdelay = 0.025
self.packet_queue = []
self.flow_control = flow_control
self.interface_ready = False
self.flow_control_timeout = 5
self.flow_control_locked = time.time()
if (len(self.src_call) < 3 or len(self.src_call) > 6):
raise ValueError("Invalid callsign for "+str(self))
@@ -88,44 +98,48 @@ class AX25KISSInterface(Interface):
self.parity = serial.PARITY_ODD
try:
RNS.log("Opening serial port "+self.port+"...")
self.serial = serial.Serial(
port = self.port,
baudrate = self.speed,
bytesize = self.databits,
parity = self.parity,
stopbits = self.stopbits,
xonxoff = False,
rtscts = False,
timeout = 0,
inter_byte_timeout = None,
write_timeout = None,
dsrdtr = False,
)
self.open_port()
except Exception as e:
RNS.log("Could not open serial port for interface "+str(self), RNS.LOG_ERROR)
raise e
if self.serial.is_open:
# Allow time for interface to initialise before config
sleep(2.0)
thread = threading.Thread(target=self.readLoop)
thread.setDaemon(True)
thread.start()
self.online = True
RNS.log("Serial port "+self.port+" is now open")
RNS.log("Configuring AX.25 KISS interface parameters...")
self.setPreamble(self.preamble)
self.setTxTail(self.txtail)
self.setPersistence(self.persistence)
self.setSlotTime(self.slottime)
self.setFlowControl(self.flow_control)
self.interface_ready = True
RNS.log("AX.25 KISS interface configured")
sleep(2)
self.configure_device()
else:
raise IOError("Could not open serial port")
def open_port(self):
RNS.log("Opening serial port "+self.port+"...", RNS.LOG_VERBOSE)
self.serial = self.pyserial.Serial(
port = self.port,
baudrate = self.speed,
bytesize = self.databits,
parity = self.parity,
stopbits = self.stopbits,
xonxoff = False,
rtscts = False,
timeout = 0,
inter_byte_timeout = None,
write_timeout = None,
dsrdtr = False,
)
def configure_device(self):
# Allow time for interface to initialise before config
sleep(2.0)
thread = threading.Thread(target=self.readLoop)
thread.setDaemon(True)
thread.start()
self.online = True
RNS.log("Serial port "+self.port+" is now open")
RNS.log("Configuring AX.25 KISS interface parameters...")
self.setPreamble(self.preamble)
self.setTxTail(self.txtail)
self.setPersistence(self.persistence)
self.setSlotTime(self.slottime)
self.setFlowControl(self.flow_control)
self.interface_ready = True
RNS.log("AX.25 KISS interface configured")
def setPreamble(self, preamble):
preamble_ms = preamble
@@ -189,14 +203,17 @@ class AX25KISSInterface(Interface):
def processIncoming(self, data):
if (len(data) > AX25.HEADER_SIZE):
self.rxb += len(data)
self.owner.inbound(data[AX25.HEADER_SIZE:], self)
def processOutgoing(self,data):
datalen = len(data)
if self.online:
if self.interface_ready:
if self.flow_control:
self.interface_ready = False
self.flow_control_locked = time.time()
encoded_dst_ssid = bytes([0x60 | (self.dst_ssid << 1)])
encoded_src_ssid = bytes([0x60 | (self.src_ssid << 1) | 0x01])
@@ -223,11 +240,9 @@ class AX25KISSInterface(Interface):
data = data.replace(bytes([0xc0]), bytes([0xdb])+bytes([0xdc]))
kiss_frame = bytes([KISS.FEND])+bytes([0x00])+data+bytes([KISS.FEND])
if (self.txdelay > 0):
RNS.log(str(self.name)+" delaying TX for "+str(self.txdelay)+" seconds", RNS.LOG_EXTREME)
sleep(self.txdelay)
written = self.serial.write(kiss_frame)
self.txb += datalen
if written != len(kiss_frame):
if self.flow_control:
self.interface_ready = True
@@ -284,8 +299,6 @@ class AX25KISSInterface(Interface):
escape = False
data_buffer = data_buffer+bytes([byte])
elif (command == KISS.CMD_READY):
# TODO: add timeout and reset if ready
# command never arrives
self.process_queue()
else:
time_since_last = int(time.time()*1000) - last_read_ms
@@ -294,12 +307,40 @@ class AX25KISSInterface(Interface):
in_frame = False
command = KISS.CMD_UNKNOWN
escape = False
sleep(0.08)
sleep(0.05)
if self.flow_control:
if not self.interface_ready:
if time.time() > self.flow_control_locked + self.flow_control_timeout:
RNS.log("Interface "+str(self)+" is unlocking flow control due to time-out. This should not happen. Your hardware might have missed a flow-control READY command, or maybe it does not support flow-control.", RNS.LOG_WARNING)
self.process_queue()
except Exception as e:
self.online = False
RNS.log("A serial port error occurred, the contained exception was: "+str(e), RNS.LOG_ERROR)
RNS.log("The interface "+str(self.name)+" is now offline. Restart Reticulum to attempt reconnection.", RNS.LOG_ERROR)
RNS.log("The interface "+str(self)+" experienced an unrecoverable error and is now offline.", RNS.LOG_ERROR)
if RNS.Reticulum.panic_on_interface_error:
RNS.panic()
RNS.log("Reticulum will attempt to reconnect the interface periodically.", RNS.LOG_ERROR)
self.online = False
self.serial.close()
self.reconnect_port()
def reconnect_port(self):
while not self.online:
try:
time.sleep(5)
RNS.log("Attempting to reconnect serial port "+str(self.port)+" for "+str(self)+"...", RNS.LOG_VERBOSE)
self.open_port()
if self.serial.is_open:
self.configure_device()
except Exception as e:
RNS.log("Error while reconnecting port, the contained exception was: "+str(e), RNS.LOG_ERROR)
RNS.log("Reconnected serial port for "+str(self))
def __str__(self):
return "AX25KISSInterface["+self.name+"]"
+323
View File
@@ -0,0 +1,323 @@
from .Interface import Interface
import socketserver
import threading
import socket
import struct
import time
import sys
import RNS
class AutoInterface(Interface):
DEFAULT_DISCOVERY_PORT = 29716
DEFAULT_DATA_PORT = 42671
DEFAULT_GROUP_ID = "reticulum".encode("utf-8")
SCOPE_LINK = "2"
SCOPE_ADMIN = "4"
SCOPE_SITE = "5"
SCOPE_ORGANISATION = "8"
SCOPE_GLOBAL = "e"
PEERING_TIMEOUT = 7.5
DARWIN_IGNORE_IFS = ["awdl0", "llw0", "lo0", "en5"]
ANDROID_IGNORE_IFS = ["dummy0", "lo", "tun0"]
def __init__(self, owner, name, group_id=None, discovery_scope=None, discovery_port=None, data_port=None, allowed_interfaces=None, ignored_interfaces=None):
import importlib
if importlib.util.find_spec('netifaces') != None:
import netifaces
else:
RNS.log("Using AutoInterface requires the netifaces module.", RNS.LOG_CRITICAL)
RNS.log("You can install it with the command: python3 -m pip install netifaces", RNS.LOG_CRITICAL)
RNS.panic()
self.netifaces = netifaces
self.rxb = 0
self.txb = 0
self.IN = True
self.OUT = False
self.name = name
self.online = False
self.peers = {}
self.link_local_addresses = []
self.adopted_interfaces = {}
self.multicast_echoes = {}
self.timed_out_interfaces = {}
self.outbound_udp_socket = None
self.announce_interval = AutoInterface.PEERING_TIMEOUT/5.0
self.peer_job_interval = AutoInterface.PEERING_TIMEOUT*1.1
self.peering_timeout = AutoInterface.PEERING_TIMEOUT
self.multicast_echo_timeout = AutoInterface.PEERING_TIMEOUT/2
if allowed_interfaces == None:
self.allowed_interfaces = []
else:
self.allowed_interfaces = allowed_interfaces
if ignored_interfaces == None:
self.ignored_interfaces = []
else:
self.ignored_interfaces = ignored_interfaces
if group_id == None:
self.group_id = AutoInterface.DEFAULT_GROUP_ID
else:
self.group_id = group_id.encode("utf-8")
if discovery_port == None:
self.discovery_port = AutoInterface.DEFAULT_DISCOVERY_PORT
else:
self.discovery_port = discovery_port
if data_port == None:
self.data_port = AutoInterface.DEFAULT_DATA_PORT
else:
self.data_port = data_port
if discovery_scope == None:
self.discovery_scope = AutoInterface.SCOPE_LINK
elif str(discovery_scope).lower() == "link":
self.discovery_scope = AutoInterface.SCOPE_LINK
elif str(discovery_scope).lower() == "admin":
self.discovery_scope = AutoInterface.SCOPE_ADMIN
elif str(discovery_scope).lower() == "site":
self.discovery_scope = AutoInterface.SCOPE_SITE
elif str(discovery_scope).lower() == "organisation":
self.discovery_scope = AutoInterface.SCOPE_ORGANISATION
elif str(discovery_scope).lower() == "global":
self.discovery_scope = AutoInterface.SCOPE_GLOBAL
self.group_hash = RNS.Identity.full_hash(self.group_id)
g = self.group_hash
#gt = "{:02x}".format(g[1]+(g[0]<<8))
gt = "0"
gt += ":"+"{:02x}".format(g[3]+(g[2]<<8))
gt += ":"+"{:02x}".format(g[5]+(g[4]<<8))
gt += ":"+"{:02x}".format(g[7]+(g[6]<<8))
gt += ":"+"{:02x}".format(g[9]+(g[8]<<8))
gt += ":"+"{:02x}".format(g[11]+(g[10]<<8))
gt += ":"+"{:02x}".format(g[13]+(g[12]<<8))
self.mcast_discovery_address = "ff1"+self.discovery_scope+":"+gt
suitable_interfaces = 0
for ifname in self.netifaces.interfaces():
if RNS.vendor.platformutils.is_darwin() and ifname in AutoInterface.DARWIN_IGNORE_IFS and not ifname in self.allowed_interfaces:
RNS.log(str(self)+" skipping Darwin AWDL or tethering interface "+str(ifname), RNS.LOG_EXTREME)
elif RNS.vendor.platformutils.is_darwin() and ifname == "lo0":
RNS.log(str(self)+" skipping Darwin loopback interface "+str(ifname), RNS.LOG_EXTREME)
elif RNS.vendor.platformutils.is_android() and ifname in AutoInterface.ANDROID_IGNORE_IFS and not ifname in self.allowed_interfaces:
RNS.log(str(self)+" skipping Android system interface "+str(ifname), RNS.LOG_EXTREME)
elif ifname in self.ignored_interfaces:
RNS.log(str(self)+" ignoring disallowed interface "+str(ifname), RNS.LOG_EXTREME)
else:
if len(self.allowed_interfaces) > 0 and not ifname in self.allowed_interfaces:
RNS.log(str(self)+" ignoring interface "+str(ifname)+" since it was not allowed", RNS.LOG_EXTREME)
else:
addresses = self.netifaces.ifaddresses(ifname)
if self.netifaces.AF_INET6 in addresses:
link_local_addr = None
for address in addresses[self.netifaces.AF_INET6]:
if "addr" in address:
if address["addr"].startswith("fe80:"):
link_local_addr = address["addr"]
self.link_local_addresses.append(link_local_addr.split("%")[0])
self.adopted_interfaces[ifname] = link_local_addr.split("%")[0]
self.multicast_echoes[ifname] = time.time()
RNS.log(str(self)+" Selecting link-local address "+str(link_local_addr)+" for interface "+str(ifname), RNS.LOG_EXTREME)
if link_local_addr == None:
RNS.log(str(self)+" No link-local IPv6 address configured for "+str(ifname)+", skipping interface", RNS.LOG_EXTREME)
else:
mcast_addr = self.mcast_discovery_address
RNS.log(str(self)+" Creating multicast discovery listener on "+str(ifname)+" with address "+str(mcast_addr), RNS.LOG_EXTREME)
# Struct with interface index
if_struct = struct.pack("I", socket.if_nametoindex(ifname))
# 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)
discovery_socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_MULTICAST_IF, if_struct)
# Join multicast group
mcast_group = socket.inet_pton(socket.AF_INET6, mcast_addr) + if_struct
discovery_socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_JOIN_GROUP, mcast_group)
# Bind socket
addr_info = socket.getaddrinfo(mcast_addr+"%"+ifname, self.discovery_port, socket.AF_INET6, socket.SOCK_DGRAM)
discovery_socket.bind(addr_info[0][4])
# Set up thread for discovery packets
def discovery_loop():
self.discovery_handler(discovery_socket, ifname)
thread = threading.Thread(target=discovery_loop)
thread.setDaemon(True)
thread.start()
suitable_interfaces += 1
if suitable_interfaces == 0:
RNS.log(str(self)+" could not autoconfigure. This interface currently provides no connectivity.", RNS.LOG_WARNING)
else:
self.receives = True
peering_wait = self.announce_interval*1.2
RNS.log(str(self)+" discovering peers for "+str(round(peering_wait, 2))+" seconds...", RNS.LOG_VERBOSE)
def handlerFactory(callback):
def createHandler(*args, **keys):
return AutoInterfaceHandler(callback, *args, **keys)
return createHandler
self.owner = owner
socketserver.UDPServer.address_family = socket.AF_INET6
for ifname in self.adopted_interfaces:
local_addr = self.adopted_interfaces[ifname]+"%"+ifname
addr_info = socket.getaddrinfo(local_addr, self.data_port, socket.AF_INET6, socket.SOCK_DGRAM)
address = addr_info[0][4]
self.server = socketserver.UDPServer(address, handlerFactory(self.processIncoming))
thread = threading.Thread(target=self.server.serve_forever)
thread.setDaemon(True)
thread.start()
job_thread = threading.Thread(target=self.peer_jobs)
job_thread.setDaemon(True)
job_thread.start()
time.sleep(peering_wait)
self.online = True
def discovery_handler(self, socket, ifname):
def announce_loop():
self.announce_handler(ifname)
thread = threading.Thread(target=announce_loop)
thread.setDaemon(True)
thread.start()
while True:
data, ipv6_src = socket.recvfrom(1024)
expected_hash = RNS.Identity.full_hash(self.group_id+ipv6_src[0].encode("utf-8"))
if data == expected_hash:
self.add_peer(ipv6_src[0], ifname)
else:
RNS.log(str(self)+" received peering packet on "+str(ifname)+" from "+str(ipv6_src[0])+", but authentication hash was incorrect.", RNS.LOG_DEBUG)
def peer_jobs(self):
while True:
time.sleep(self.peer_job_interval)
now = time.time()
timed_out_peers = []
# Check for timed out peers
for peer_addr in self.peers:
peer = self.peers[peer_addr]
last_heard = peer[1]
if now > last_heard+self.peering_timeout:
timed_out_peers.append(peer_addr)
# Remove any timed out peers
for peer_addr in timed_out_peers:
removed_peer = self.peers.pop(peer_addr)
RNS.log(str(self)+" removed peer "+str(peer_addr)+" on "+str(removed_peer[0]), RNS.LOG_DEBUG)
for ifname in self.adopted_interfaces:
last_multicast_echo = 0
if ifname in self.multicast_echoes:
last_multicast_echo = self.multicast_echoes[ifname]
if now - last_multicast_echo > self.multicast_echo_timeout:
if ifname in self.timed_out_interfaces and self.timed_out_interfaces[ifname] == False:
RNS.log("Multicast echo timeout for "+str(ifname)+". Carrier lost.", RNS.LOG_WARNING)
self.timed_out_interfaces[ifname] = True
else:
if ifname in self.timed_out_interfaces and self.timed_out_interfaces[ifname] == True:
RNS.log(str(self)+" Carrier recovered on "+str(ifname), RNS.LOG_WARNING)
self.timed_out_interfaces[ifname] = False
def announce_handler(self, ifname):
while True:
self.peer_announce(ifname)
time.sleep(self.announce_interval)
def peer_announce(self, ifname):
try:
link_local_address = self.adopted_interfaces[ifname]
discovery_token = RNS.Identity.full_hash(self.group_id+link_local_address.encode("utf-8"))
announce_socket = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
addr_info = socket.getaddrinfo(self.mcast_discovery_address, self.discovery_port, socket.AF_INET6, socket.SOCK_DGRAM)
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])
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)
else:
pass
def add_peer(self, addr, ifname):
if addr in self.link_local_addresses:
ifname = None
for interface_name in self.adopted_interfaces:
if self.adopted_interfaces[interface_name] == addr:
ifname = interface_name
if ifname != None:
self.multicast_echoes[ifname] = time.time()
else:
RNS.log(str(self)+" received multicast echo on unexpected interface "+str(ifname), RNS.LOG_WARNING)
else:
if not addr in self.peers:
self.peers[addr] = [ifname, time.time()]
RNS.log(str(self)+" added peer "+str(addr)+" on "+str(ifname), RNS.LOG_DEBUG)
else:
self.refresh_peer(addr)
def refresh_peer(self, addr):
self.peers[addr][1] = time.time()
def processIncoming(self, data):
self.rxb += len(data)
self.owner.inbound(data, self)
def processOutgoing(self,data):
for peer in self.peers:
try:
if self.outbound_udp_socket == None:
self.outbound_udp_socket = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
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)
self.txb += len(data)
def __str__(self):
return "AutoInterface["+self.name+"]"
class AutoInterfaceHandler(socketserver.BaseRequestHandler):
def __init__(self, callback, *args, **keys):
self.callback = callback
socketserver.BaseRequestHandler.__init__(self, *args, **keys)
def handle(self):
data = self.request[0]
self.callback(data)
+593
View File
@@ -0,0 +1,593 @@
from .Interface import Interface
import socketserver
import threading
import platform
import socket
import time
import sys
import os
import RNS
import asyncio
class HDLC():
FLAG = 0x7E
ESC = 0x7D
ESC_MASK = 0x20
@staticmethod
def escape(data):
data = data.replace(bytes([HDLC.ESC]), bytes([HDLC.ESC, HDLC.ESC^HDLC.ESC_MASK]))
data = data.replace(bytes([HDLC.FLAG]), bytes([HDLC.ESC, HDLC.FLAG^HDLC.ESC_MASK]))
return data
class KISS():
FEND = 0xC0
FESC = 0xDB
TFEND = 0xDC
TFESC = 0xDD
CMD_DATA = 0x00
CMD_UNKNOWN = 0xFE
@staticmethod
def escape(data):
data = data.replace(bytes([0xdb]), bytes([0xdb, 0xdd]))
data = data.replace(bytes([0xc0]), bytes([0xdb, 0xdc]))
return data
# TODO: Neater shutdown of the event loop and
# better error handling is needed. Sometimes
# errors occur in I2P that leave tunnel setup
# hanging indefinitely, and right now we have
# no way of catching it. Sometimes the server
# and client tasks are also not cancelled on
# shutdown, which leads to errors dumped to
# the console. This should also be remedied.
class I2PController:
def __init__(self, rns_storagepath):
import RNS.vendor.i2plib as i2plib
import RNS.vendor.i2plib.utils
self.client_tunnels = {}
self.server_tunnels = {}
self.loop = None
self.i2plib = i2plib
self.utils = i2plib.utils
self.sam_address = i2plib.get_sam_address()
self.storagepath = rns_storagepath+"/i2p"
if not os.path.isdir(self.storagepath):
os.makedirs(self.storagepath)
def start(self):
asyncio.set_event_loop(asyncio.new_event_loop())
self.loop = asyncio.get_event_loop()
try:
self.loop.run_forever()
except Exception as e:
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()
self.loop.stop()
def get_free_port(self):
return self.i2plib.utils.get_free_port()
def client_tunnel(self, owner, i2p_destination):
self.client_tunnels[i2p_destination] = False
while True:
if not self.client_tunnels[i2p_destination]:
try:
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)
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
except Exception as e:
RNS.log("Error while setting up I2P tunnel: "+str(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.")
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"
i2p_dest = None
if not os.path.isfile(i2p_keyfile):
coro = self.i2plib.new_destination(sam_address=self.sam_address, loop=self.loop)
i2p_dest = asyncio.run_coroutine_threadsafe(coro, self.loop).result()
key_file = open(i2p_keyfile, "w")
key_file.write(i2p_dest.private_key.base64)
key_file.close()
else:
key_file = open(i2p_keyfile, "r")
prvd = key_file.read()
key_file.close()
i2p_dest = self.i2plib.Destination(data=prvd, has_private_key=True)
i2p_b32 = i2p_dest.base32
owner.b32 = i2p_b32
self.server_tunnels[i2p_b32] = False
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)
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.")
time.sleep(5)
def get_loop(self):
return asyncio.get_event_loop()
class ThreadingI2PServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
pass
class I2PInterfacePeer(Interface):
RECONNECT_WAIT = 15
RECONNECT_MAX_TRIES = None
# TCP socket options
I2P_USER_TIMEOUT = 40
I2P_PROBE_AFTER = 10
I2P_PROBE_INTERVAL = 5
I2P_PROBES = 6
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.IN = True
self.OUT = False
self.socket = None
self.parent_interface = parent_interface
self.parent_count = True
self.name = name
self.initiator = False
self.reconnecting = False
self.never_connected = True
self.owner = owner
self.writing = False
self.online = False
self.detached = False
self.kiss_framing = False
self.i2p_tunneled = True
self.i2p_dest = None
self.i2p_tunnel_ready = False
self.mode = RNS.Interfaces.Interface.Interface.MODE_FULL
if max_reconnect_tries == None:
self.max_reconnect_tries = I2PInterfacePeer.RECONNECT_MAX_TRIES
else:
self.max_reconnect_tries = max_reconnect_tries
if connected_socket != None:
self.receives = True
self.target_ip = None
self.target_port = None
self.socket = connected_socket
if platform.system() == "Linux":
self.set_timeouts_linux()
elif platform.system() == "Darwin":
self.set_timeouts_osx()
elif target_i2p_dest != None:
self.receives = True
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)
thread = threading.Thread(target=tunnel_job)
thread.setDaemon(True)
thread.start()
def wait_job():
while self.awaiting_i2p_tunnel:
time.sleep(0.25)
if not self.kiss_framing:
self.wants_tunnel = True
if not self.connect(initial=True):
thread = threading.Thread(target=self.reconnect)
thread.setDaemon(True)
thread.start()
else:
thread = threading.Thread(target=self.read_loop)
thread.setDaemon(True)
thread.start()
thread = threading.Thread(target=wait_job)
thread.setDaemon(True)
thread.start()
def set_timeouts_linux(self):
if not self.i2p_tunneled:
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_USER_TIMEOUT, int(I2PInterfacePeer.TCP_USER_TIMEOUT * 1000))
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, int(I2PInterfacePeer.TCP_PROBE_AFTER))
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, int(I2PInterfacePeer.TCP_PROBE_INTERVAL))
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, int(I2PInterfacePeer.TCP_PROBES))
else:
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_USER_TIMEOUT, int(I2PInterfacePeer.I2P_USER_TIMEOUT * 1000))
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, int(I2PInterfacePeer.I2P_PROBE_AFTER))
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, int(I2PInterfacePeer.I2P_PROBE_INTERVAL))
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, int(I2PInterfacePeer.I2P_PROBES))
def set_timeouts_osx(self):
if hasattr(socket, "TCP_KEEPALIVE"):
TCP_KEEPIDLE = socket.TCP_KEEPALIVE
else:
TCP_KEEPIDLE = 0x10
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
if not self.i2p_tunneled:
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 detach(self):
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:
self.socket.shutdown(socket.SHUT_RDWR)
except Exception as e:
RNS.log("Error while shutting down socket for "+str(self)+": "+str(e))
try:
self.socket.close()
except Exception as e:
RNS.log("Error while closing socket for "+str(self)+": "+str(e))
self.socket = None
def connect(self, initial=False):
try:
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.socket.connect((self.target_ip, self.target_port))
self.online = True
except Exception as e:
if initial:
if not self.awaiting_i2p_tunnel:
RNS.log("Initial connection for "+str(self)+" could not be established: "+str(e), RNS.LOG_ERROR)
RNS.log("Leaving unconnected and retrying connection in "+str(I2PInterfacePeer.RECONNECT_WAIT)+" seconds.", RNS.LOG_ERROR)
return False
else:
raise e
if platform.system() == "Linux":
self.set_timeouts_linux()
elif platform.system() == "Darwin":
self.set_timeouts_osx()
self.online = True
self.writing = False
self.never_connected = False
if not self.kiss_framing and self.wants_tunnel:
RNS.Transport.synthesize_tunnel(self)
return True
def reconnect(self):
if self.initiator:
if not self.reconnecting:
self.reconnecting = True
attempts = 0
while not self.online:
time.sleep(I2PInterfacePeer.RECONNECT_WAIT)
attempts += 1
if self.max_reconnect_tries != None and attempts > self.max_reconnect_tries:
RNS.log("Max reconnection attempts reached for "+str(self), RNS.LOG_ERROR)
self.teardown()
break
try:
self.connect()
except Exception as e:
if not self.awaiting_i2p_tunnel:
RNS.log("Connection attempt for "+str(self)+" failed: "+str(e), RNS.LOG_DEBUG)
else:
RNS.log(str(self)+" still waiting for I2P tunnel to appear", RNS.LOG_VERBOSE)
if not self.never_connected:
RNS.log(str(self)+" Re-established connection via I2P tunnel", RNS.LOG_INFO)
self.reconnecting = False
thread = threading.Thread(target=self.read_loop)
thread.setDaemon(True)
thread.start()
if not self.kiss_framing:
RNS.Transport.synthesize_tunnel(self)
else:
RNS.log("Attempt to reconnect on a non-initiator I2P interface. This should not happen.", RNS.LOG_ERROR)
raise IOError("Attempt to reconnect on a non-initiator I2P interface")
def processIncoming(self, data):
self.rxb += len(data)
if hasattr(self, "parent_interface") and self.parent_interface != None and self.parent_count:
self.parent_interface.rxb += len(data)
self.owner.inbound(data, self)
def processOutgoing(self, data):
if self.online:
while self.writing:
time.sleep(0.01)
try:
self.writing = True
if self.kiss_framing:
data = bytes([KISS.FEND])+bytes([KISS.CMD_DATA])+KISS.escape(data)+bytes([KISS.FEND])
else:
data = bytes([HDLC.FLAG])+HDLC.escape(data)+bytes([HDLC.FLAG])
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)
except Exception as e:
RNS.log("Exception occurred while transmitting via "+str(self)+", tearing down interface", RNS.LOG_ERROR)
RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR)
self.teardown()
def read_loop(self):
try:
in_frame = False
escape = False
data_buffer = b""
command = KISS.CMD_UNKNOWN
while True:
data_in = self.socket.recv(4096)
if len(data_in) > 0:
pointer = 0
while pointer < len(data_in):
byte = data_in[pointer]
pointer += 1
if self.kiss_framing:
# Read loop for KISS framing
if (in_frame and byte == KISS.FEND and command == KISS.CMD_DATA):
in_frame = False
self.processIncoming(data_buffer)
elif (byte == KISS.FEND):
in_frame = True
command = KISS.CMD_UNKNOWN
data_buffer = b""
elif (in_frame and len(data_buffer) < RNS.Reticulum.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
byte = byte & 0x0F
command = byte
elif (command == KISS.CMD_DATA):
if (byte == KISS.FESC):
escape = True
else:
if (escape):
if (byte == KISS.TFEND):
byte = KISS.FEND
if (byte == KISS.TFESC):
byte = KISS.FESC
escape = False
data_buffer = data_buffer+bytes([byte])
else:
# Read loop for HDLC framing
if (in_frame and byte == HDLC.FLAG):
in_frame = False
self.processIncoming(data_buffer)
elif (byte == HDLC.FLAG):
in_frame = True
data_buffer = b""
elif (in_frame and len(data_buffer) < RNS.Reticulum.MTU):
if (byte == HDLC.ESC):
escape = True
else:
if (escape):
if (byte == HDLC.FLAG ^ HDLC.ESC_MASK):
byte = HDLC.FLAG
if (byte == HDLC.ESC ^ HDLC.ESC_MASK):
byte = HDLC.ESC
escape = False
data_buffer = data_buffer+bytes([byte])
else:
self.online = False
if self.initiator and not self.detached:
RNS.log("Socket for "+str(self)+" was closed, attempting to reconnect...", RNS.LOG_WARNING)
self.reconnect()
else:
RNS.log("Socket for remote client "+str(self)+" was closed.", RNS.LOG_VERBOSE)
self.teardown()
break
except Exception as e:
self.online = False
RNS.log("An interface error occurred for "+str(self)+", the contained exception was: "+str(e), RNS.LOG_WARNING)
if self.initiator:
RNS.log("Attempting to reconnect...", RNS.LOG_WARNING)
self.reconnect()
else:
self.teardown()
def teardown(self):
if self.initiator and not self.detached:
RNS.log("The interface "+str(self)+" experienced an unrecoverable error and is being torn down. Restart Reticulum to attempt to open this interface again.", RNS.LOG_ERROR)
if RNS.Reticulum.panic_on_interface_error:
RNS.panic()
else:
RNS.log("The interface "+str(self)+" is being torn down.", RNS.LOG_VERBOSE)
self.online = False
self.OUT = False
self.IN = False
if hasattr(self, "parent_interface") and self.parent_interface != None:
self.parent_interface.clients -= 1
if self in RNS.Transport.interfaces:
if not self.initiator:
RNS.Transport.interfaces.remove(self)
def __str__(self):
return "I2PInterfacePeer["+str(self.name)+"]"
class I2PInterface(Interface):
def __init__(self, owner, name, rns_storagepath, peers, connectable = True):
self.rxb = 0
self.txb = 0
self.online = False
self.clients = 0
self.owner = owner
self.connectable = connectable
self.i2p_tunneled = True
self.mode = RNS.Interfaces.Interface.Interface.MODE_FULL
self.b32 = None
self.i2p = I2PController(rns_storagepath)
self.IN = True
self.OUT = False
self.name = name
self.receives = True
self.bind_ip = "127.0.0.1"
self.bind_port = self.i2p.get_free_port()
self.address = (self.bind_ip, self.bind_port)
i2p_thread = threading.Thread(target=self.i2p.start)
i2p_thread.setDaemon(True)
i2p_thread.start()
def handlerFactory(callback):
def createHandler(*args, **keys):
return I2PInterfaceHandler(callback, *args, **keys)
return createHandler
ThreadingI2PServer.allow_reuse_address = True
self.server = ThreadingI2PServer(self.address, handlerFactory(self.incoming_connection))
thread = threading.Thread(target=self.server.serve_forever)
thread.setDaemon(True)
thread.start()
if self.connectable:
def tunnel_job():
self.i2p.server_tunnel(self)
thread = threading.Thread(target=tunnel_job)
thread.setDaemon(True)
thread.start()
if peers != None:
for peer_addr in peers:
interface_name = peer_addr
peer_interface = I2PInterfacePeer(self, self.owner, interface_name, peer_addr)
peer_interface.OUT = True
peer_interface.IN = True
peer_interface.parent_interface = self
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
spawned_interface = I2PInterfacePeer(self, self.owner, interface_name, connected_socket=handler.request)
spawned_interface.OUT = True
spawned_interface.IN = True
spawned_interface.parent_interface = self
spawned_interface.online = True
RNS.log("Spawned new I2PInterface Peer: "+str(spawned_interface), RNS.LOG_VERBOSE)
RNS.Transport.interfaces.append(spawned_interface)
self.clients += 1
spawned_interface.read_loop()
def processOutgoing(self, data):
pass
def detach(self):
self.i2p.stop()
def __str__(self):
return "I2PInterface["+self.name+"]"
class I2PInterfaceHandler(socketserver.BaseRequestHandler):
def __init__(self, callback, *args, **keys):
self.callback = callback
socketserver.BaseRequestHandler.__init__(self, *args, **keys)
def handle(self):
self.callback(handler=self)
+11 -3
View File
@@ -7,9 +7,17 @@ class Interface:
RPT = False
name = None
MODE_FULL = 0x01
MODE_POINT_TO_POINT = 0x02
MODE_ACCESS_POINT = 0x03
def __init__(self):
pass
self.rxb = 0
self.txb = 0
self.online = False
def get_hash(self):
# TODO: Maybe expand this to something more unique
return RNS.Identity.full_hash(str(self).encode("utf-8"))
return RNS.Identity.full_hash(str(self).encode("utf-8"))
def detach(self):
pass
+78 -34
View File
@@ -1,7 +1,6 @@
from .Interface import Interface
from time import sleep
import sys
import serial
import threading
import time
import RNS
@@ -40,9 +39,21 @@ class KISSInterface(Interface):
serial = None
def __init__(self, owner, name, port, speed, databits, parity, stopbits, preamble, txtail, persistence, slottime, flow_control, beacon_interval, beacon_data):
import importlib
if importlib.util.find_spec('serial') != None:
import serial
else:
RNS.log("Using the KISS interface requires a serial communication module to be installed.", RNS.LOG_CRITICAL)
RNS.log("You can install one with the command: python3 -m pip install pyserial", RNS.LOG_CRITICAL)
RNS.panic()
self.rxb = 0
self.txb = 0
if beacon_data == None:
beacon_data = ""
self.pyserial = serial
self.serial = None
self.owner = owner
self.name = name
@@ -60,7 +71,7 @@ class KISSInterface(Interface):
self.packet_queue = []
self.flow_control = flow_control
self.interface_ready = False
self.flow_control_timeout = 10
self.flow_control_timeout = 5
self.flow_control_locked = time.time()
self.preamble = preamble if preamble != None else 350;
@@ -75,44 +86,52 @@ class KISSInterface(Interface):
self.parity = serial.PARITY_ODD
try:
RNS.log("Opening serial port "+self.port+"...")
self.serial = serial.Serial(
port = self.port,
baudrate = self.speed,
bytesize = self.databits,
parity = self.parity,
stopbits = self.stopbits,
xonxoff = False,
rtscts = False,
timeout = 0,
inter_byte_timeout = None,
write_timeout = None,
dsrdtr = False,
)
self.open_port()
except Exception as e:
RNS.log("Could not open serial port "+self.port, RNS.LOG_ERROR)
raise e
if self.serial.is_open:
# Allow time for interface to initialise before config
sleep(2.0)
thread = threading.Thread(target=self.readLoop)
thread.setDaemon(True)
thread.start()
self.online = True
RNS.log("Serial port "+self.port+" is now open")
RNS.log("Configuring KISS interface parameters...")
self.setPreamble(self.preamble)
self.setTxTail(self.txtail)
self.setPersistence(self.persistence)
self.setSlotTime(self.slottime)
self.setFlowControl(self.flow_control)
self.interface_ready = True
RNS.log("KISS interface configured")
self.configure_device()
else:
raise IOError("Could not open serial port")
def open_port(self):
RNS.log("Opening serial port "+self.port+"...", RNS.LOG_VERBOSE)
self.serial = self.pyserial.Serial(
port = self.port,
baudrate = self.speed,
bytesize = self.databits,
parity = self.parity,
stopbits = self.stopbits,
xonxoff = False,
rtscts = False,
timeout = 0,
inter_byte_timeout = None,
write_timeout = None,
dsrdtr = False,
)
def configure_device(self):
# Allow time for interface to initialise before config
sleep(2.0)
thread = threading.Thread(target=self.readLoop)
thread.setDaemon(True)
thread.start()
self.online = True
RNS.log("Serial port "+self.port+" is now open")
RNS.log("Configuring KISS interface parameters...")
self.setPreamble(self.preamble)
self.setTxTail(self.txtail)
self.setPersistence(self.persistence)
self.setSlotTime(self.slottime)
self.setFlowControl(self.flow_control)
self.interface_ready = True
RNS.log("KISS interface configured")
def setPreamble(self, preamble):
preamble_ms = preamble
preamble = int(preamble_ms / 10)
@@ -174,10 +193,12 @@ class KISSInterface(Interface):
def processIncoming(self, data):
self.rxb += len(data)
self.owner.inbound(data, self)
def processOutgoing(self,data):
datalen = len(data)
if self.online:
if self.interface_ready:
if self.flow_control:
@@ -189,6 +210,7 @@ class KISSInterface(Interface):
frame = bytes([KISS.FEND])+bytes([0x00])+data+bytes([KISS.FEND])
written = self.serial.write(frame)
self.txb += datalen
if data == self.beacon_d:
self.first_tx = None
@@ -259,12 +281,12 @@ class KISSInterface(Interface):
in_frame = False
command = KISS.CMD_UNKNOWN
escape = False
sleep(0.08)
sleep(0.05)
if self.flow_control:
if not self.interface_ready:
if time.time() > self.flow_control_locked + self.flow_control_timeout:
RNS.log("Interface "+str(self)+" is unlocking flow control due to time-out. This should not happen. Your hardware might have missed a flow-control READY command.", RNS.LOG_WARNING)
RNS.log("Interface "+str(self)+" is unlocking flow control due to time-out. This should not happen. Your hardware might have missed a flow-control READY command, or maybe it does not support flow-control.", RNS.LOG_WARNING)
self.process_queue()
if self.beacon_i != None and self.beacon_d != None:
@@ -277,7 +299,29 @@ class KISSInterface(Interface):
except Exception as e:
self.online = False
RNS.log("A serial port error occurred, the contained exception was: "+str(e), RNS.LOG_ERROR)
RNS.log("The interface "+str(self.name)+" is now offline. Restart Reticulum to attempt reconnection.", RNS.LOG_ERROR)
RNS.log("The interface "+str(self)+" experienced an unrecoverable error and is now offline.", RNS.LOG_ERROR)
if RNS.Reticulum.panic_on_interface_error:
RNS.panic()
RNS.log("Reticulum will attempt to reconnect the interface periodically.", RNS.LOG_ERROR)
self.online = False
self.serial.close()
self.reconnect_port()
def reconnect_port(self):
while not self.online:
try:
time.sleep(5)
RNS.log("Attempting to reconnect serial port "+str(self.port)+" for "+str(self)+"...", RNS.LOG_VERBOSE)
self.open_port()
if self.serial.is_open:
self.configure_device()
except Exception as e:
RNS.log("Error while reconnecting port, the contained exception was: "+str(e), RNS.LOG_ERROR)
RNS.log("Reconnected serial port for "+str(self))
def __str__(self):
return "KISSInterface["+self.name+"]"
+116 -9
View File
@@ -22,13 +22,22 @@ class ThreadingTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
pass
class LocalClientInterface(Interface):
RECONNECT_WAIT = 3
def __init__(self, owner, name, target_port = None, connected_socket=None):
self.rxb = 0
self.txb = 0
self.online = False
self.IN = True
self.OUT = False
self.socket = None
self.parent_interface = None
self.reconnecting = False
self.never_connected = True
self.detached = False
self.name = name
self.mode = RNS.Interfaces.Interface.Interface.MODE_FULL
if connected_socket != None:
self.receives = True
@@ -36,15 +45,13 @@ class LocalClientInterface(Interface):
self.target_port = None
self.socket = connected_socket
self.is_connected_to_shared_instance = False
elif target_port != None:
self.receives = True
self.target_ip = "127.0.0.1"
self.target_port = target_port
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.socket.connect((self.target_ip, self.target_port))
self.is_connected_to_shared_instance = True
self.connect()
self.owner = owner
self.online = True
@@ -55,9 +62,55 @@ class LocalClientInterface(Interface):
thread.setDaemon(True)
thread.start()
def connect(self):
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.socket.connect((self.target_ip, self.target_port))
self.online = True
self.is_connected_to_shared_instance = True
self.never_connected = False
return True
def reconnect(self):
if self.is_connected_to_shared_instance:
if not self.reconnecting:
self.reconnecting = True
attempts = 0
while not self.online:
time.sleep(LocalClientInterface.RECONNECT_WAIT)
attempts += 1
try:
self.connect()
except Exception as e:
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)
self.reconnecting = False
thread = threading.Thread(target=self.read_loop)
thread.setDaemon(True)
thread.start()
RNS.Transport.shared_connection_reappeared()
else:
RNS.log("Attempt to reconnect on a non-initiator shared local interface. This should not happen.", RNS.LOG_ERROR)
raise IOError("Attempt to reconnect on a non-initiator local interface")
def processIncoming(self, data):
self.rxb += len(data)
if hasattr(self, "parent_interface") and self.parent_interface != None:
self.parent_interface.rxb += len(data)
self.owner.inbound(data, self)
def processOutgoing(self, data):
if self.online:
while self.writing:
@@ -68,6 +121,10 @@ class LocalClientInterface(Interface):
data = bytes([HDLC.FLAG])+HDLC.escape(data)+bytes([HDLC.FLAG])
self.socket.sendall(data)
self.writing = False
self.txb += len(data)
if hasattr(self, "parent_interface") and self.parent_interface != None:
self.parent_interface.txb += len(data)
except Exception as e:
RNS.log("Exception occurred while transmitting via "+str(self)+", tearing down interface", RNS.LOG_ERROR)
RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR)
@@ -105,8 +162,14 @@ class LocalClientInterface(Interface):
escape = False
data_buffer = data_buffer+bytes([byte])
else:
RNS.log("Socket for "+str(self)+" was closed, tearing down interface", RNS.LOG_VERBOSE)
self.teardown()
self.online = False
if self.is_connected_to_shared_instance and not self.detached:
RNS.log("Socket for "+str(self)+" was closed, attempting to reconnect...", RNS.LOG_WARNING)
RNS.Transport.shared_connection_disappeared()
self.reconnect()
else:
self.teardown(nowarning=True)
break
@@ -116,7 +179,26 @@ class LocalClientInterface(Interface):
RNS.log("Tearing down "+str(self), RNS.LOG_ERROR)
self.teardown()
def teardown(self):
def detach(self):
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:
self.socket.shutdown(socket.SHUT_RDWR)
except Exception as e:
RNS.log("Error while shutting down socket for "+str(self)+": "+str(e))
try:
self.socket.close()
except Exception as e:
RNS.log("Error while closing socket for "+str(self)+": "+str(e))
self.socket = None
def teardown(self, nowarning=False):
self.online = False
self.OUT = False
self.IN = False
@@ -126,6 +208,19 @@ class LocalClientInterface(Interface):
if self in RNS.Transport.local_client_interfaces:
RNS.Transport.local_client_interfaces.remove(self)
if hasattr(self, "parent_interface") and self.parent_interface != None:
self.parent_interface.clients -= 1
if nowarning == False:
RNS.log("The interface "+str(self)+" experienced an unrecoverable error and is being torn down. Restart Reticulum to attempt to open this interface again.", RNS.LOG_ERROR)
if RNS.Reticulum.panic_on_interface_error:
RNS.panic()
if self.is_connected_to_shared_instance:
if nowarning == False:
RNS.log("Permanently lost connection to local shared RNS instance. Exiting now.", RNS.LOG_CRITICAL)
RNS.exit()
def __str__(self):
@@ -135,9 +230,15 @@ class LocalClientInterface(Interface):
class LocalServerInterface(Interface):
def __init__(self, owner, bindport=None):
self.rxb = 0
self.txb = 0
self.online = False
self.clients = 0
self.IN = True
self.OUT = False
self.name = "Reticulum"
self.mode = RNS.Interfaces.Interface.Interface.MODE_FULL
if (bindport != None):
self.receives = True
@@ -153,12 +254,17 @@ class LocalServerInterface(Interface):
self.is_local_shared_instance = True
address = (self.bind_ip, self.bind_port)
ThreadingTCPServer.allow_reuse_address = True
self.server = ThreadingTCPServer(address, handlerFactory(self.incoming_connection))
thread = threading.Thread(target=self.server.serve_forever)
thread.setDaemon(True)
thread.start()
self.online = True
def incoming_connection(self, handler):
interface_name = str(str(handler.client_address[1]))
@@ -171,13 +277,14 @@ class LocalServerInterface(Interface):
RNS.log("Accepting new connection to shared instance: "+str(spawned_interface), RNS.LOG_VERBOSE)
RNS.Transport.interfaces.append(spawned_interface)
RNS.Transport.local_client_interfaces.append(spawned_interface)
self.clients += 1
spawned_interface.read_loop()
def processOutgoing(self, data):
pass
def __str__(self):
return "Shared Instance ["+str(self.bind_port)+"]"
return "Shared Instance["+str(self.bind_port)+"]"
class LocalInterfaceHandler(socketserver.BaseRequestHandler):
def __init__(self, callback, *args, **keys):
+179 -37
View File
@@ -2,7 +2,6 @@
from .Interface import Interface
from time import sleep
import sys
import serial
import threading
import time
import math
@@ -31,8 +30,11 @@ class KISS():
CMD_STAT_SNR = 0x24
CMD_BLINK = 0x30
CMD_RANDOM = 0x40
CMD_PLATFORM = 0x48
CMD_MCU = 0x49
CMD_FW_VERSION = 0x50
CMD_ROM_READ = 0x51
CMD_RESET = 0x55
DETECT_REQ = 0x73
DETECT_RESP = 0x46
@@ -46,6 +48,9 @@ class KISS():
ERROR_TXFAILED = 0x02
ERROR_EEPROM_LOCKED = 0x03
PLATFORM_AVR = 0x90
PLATFORM_ESP32 = 0x80
@staticmethod
def escape(data):
data = data.replace(bytes([0xdb]), bytes([0xdb, 0xdd]))
@@ -71,7 +76,22 @@ class RNodeInterface(Interface):
CALLSIGN_MAX_LEN = 32
REQUIRED_FW_VER_MAJ = 1
REQUIRED_FW_VER_MIN = 26
def __init__(self, owner, name, port, frequency = None, bandwidth = None, txpower = None, sf = None, cr = None, flow_control = False, id_interval = None, id_callsign = None):
import importlib
if importlib.util.find_spec('serial') != None:
import serial
else:
RNS.log("Using the RNode interface requires a serial communication module to be installed.", RNS.LOG_CRITICAL)
RNS.log("You can install one with the command: python3 -m pip install pyserial", RNS.LOG_CRITICAL)
RNS.panic()
self.rxb = 0
self.txb = 0
self.pyserial = serial
self.serial = None
self.owner = owner
self.name = name
@@ -90,6 +110,12 @@ class RNodeInterface(Interface):
self.cr = cr
self.state = KISS.RADIO_STATE_OFF
self.bitrate = 0
self.platform = None
self.mcu = None
self.detected = False
self.firmware_ok = False
self.maj_version = 0
self.min_version = 0
self.last_id = 0
self.first_tx = None
@@ -147,46 +173,64 @@ class RNodeInterface(Interface):
raise ValueError("The configuration for "+str(self)+" contains errors, interface is offline")
try:
RNS.log("Opening serial port "+self.port+"...")
self.serial = serial.Serial(
port = self.port,
baudrate = self.speed,
bytesize = self.databits,
parity = self.parity,
stopbits = self.stopbits,
xonxoff = False,
rtscts = False,
timeout = 0,
inter_byte_timeout = None,
write_timeout = None,
dsrdtr = False,
)
self.open_port()
except Exception as e:
RNS.log("Could not open serial port for interface "+str(self), RNS.LOG_ERROR)
raise e
if self.serial.is_open:
sleep(2.0)
thread = threading.Thread(target=self.readLoop)
thread.setDaemon(True)
thread.start()
self.online = True
RNS.log("Serial port "+self.port+" is now open")
RNS.log("Configuring RNode interface...", RNS.LOG_VERBOSE)
self.initRadio()
if (self.validateRadioState()):
self.interface_ready = True
RNS.log(str(self)+" is configured and powered up")
sleep(1.0)
else:
RNS.log("After configuring "+str(self)+", the reported radio parameters did not match your configuration.", RNS.LOG_ERROR)
RNS.log("Make sure that your hardware actually supports the parameters specified in the configuration", RNS.LOG_ERROR)
RNS.log("Aborting RNode startup", RNS.LOG_ERROR)
self.serial.close()
raise IOError("RNode interface did not pass validation")
self.configure_device()
else:
raise IOError("Could not open serial port")
def open_port(self):
RNS.log("Opening serial port "+self.port+"...")
self.serial = self.pyserial.Serial(
port = self.port,
baudrate = self.speed,
bytesize = self.databits,
parity = self.parity,
stopbits = self.stopbits,
xonxoff = False,
rtscts = False,
timeout = 0,
inter_byte_timeout = None,
write_timeout = None,
dsrdtr = False,
)
def configure_device(self):
sleep(2.0)
thread = threading.Thread(target=self.readLoop)
thread.setDaemon(True)
thread.start()
self.detect()
sleep(0.1)
if not self.detected:
raise IOError("Could not detect device")
else:
if self.platform == KISS.PLATFORM_ESP32:
RNS.log("Resetting ESP32-based device before configuration...", RNS.LOG_VERBOSE)
self.hard_reset()
self.online = True
RNS.log("Serial port "+self.port+" is now open")
RNS.log("Configuring RNode interface...", RNS.LOG_VERBOSE)
self.initRadio()
if (self.validateRadioState()):
self.interface_ready = True
RNS.log(str(self)+" is configured and powered up")
sleep(1.0)
else:
RNS.log("After configuring "+str(self)+", the reported radio parameters did not match your configuration.", RNS.LOG_ERROR)
RNS.log("Make sure that your hardware actually supports the parameters specified in the configuration", RNS.LOG_ERROR)
RNS.log("Aborting RNode startup", RNS.LOG_ERROR)
self.serial.close()
raise IOError("RNode interface did not pass configuration validation")
def initRadio(self):
self.setFrequency()
@@ -196,6 +240,19 @@ class RNodeInterface(Interface):
self.setCodingRate()
self.setRadioState(KISS.RADIO_STATE_ON)
def detect(self):
kiss_command = bytes([KISS.FEND, KISS.CMD_DETECT, KISS.DETECT_REQ, KISS.FEND, KISS.CMD_FW_VERSION, 0x00, KISS.FEND, KISS.CMD_PLATFORM, 0x00, KISS.FEND, KISS.CMD_MCU, 0x00, KISS.FEND])
written = self.serial.write(kiss_command)
if written != len(kiss_command):
raise IOError("An IO error occurred while detecting hardware for "+self(str))
def hard_reset(self):
kiss_command = bytes([KISS.FEND, KISS.CMD_RESET, 0xf8, KISS.FEND])
written = self.serial.write(kiss_command)
if written != len(kiss_command):
raise IOError("An IO error occurred while restarting device")
sleep(2.25);
def setFrequency(self):
c1 = self.frequency >> 24
c2 = self.frequency >> 16 & 0xFF
@@ -242,13 +299,28 @@ class RNodeInterface(Interface):
raise IOError("An IO error occurred while configuring coding rate for "+self(str))
def setRadioState(self, state):
self.state = state
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_RADIO_STATE])+bytes([state])+bytes([KISS.FEND])
written = self.serial.write(kiss_command)
if written != len(kiss_command):
raise IOError("An IO error occurred while configuring radio state for "+self(str))
def validate_firmware(self):
if (self.maj_version >= RNodeInterface.REQUIRED_FW_VER_MAJ):
if (self.min_version >= RNodeInterface.REQUIRED_FW_VER_MIN):
self.firmware_ok = True
if self.firmware_ok:
return
RNS.log("The firmware version of the connected RNode is "+str(self.maj_version)+"."+str(self.min_version), RNS.LOG_ERROR)
RNS.log("This version of Reticulum requires at least version "+str(RNodeInterface.REQUIRED_FW_VER_MAJ)+"."+str(RNodeInterface.REQUIRED_FW_VER_MIN), RNS.LOG_ERROR)
RNS.log("Please update your RNode firmware with rnodeconf (https://github.com/markqvist/rnodeconfigutil/)")
RNS.panic()
def validateRadioState(self):
RNS.log("Validating radio configuration for "+str(self)+"...", RNS.LOG_VERBOSE)
RNS.log("Wating for radio configuration validation for "+str(self)+"...", RNS.LOG_VERBOSE)
sleep(0.25);
if (self.frequency != self.r_frequency):
RNS.log("Frequency mismatch", RNS.LOG_ERROR)
@@ -262,6 +334,9 @@ class RNodeInterface(Interface):
if (self.sf != self.r_sf):
RNS.log("Spreading factor mismatch", RNS.LOG_ERROR)
self.validcfg = False
if (self.state != self.r_state):
RNS.log("Radio state mismatch", RNS.LOG_ERROR)
self.validcfg = False
if (self.validcfg):
return True
@@ -273,15 +348,19 @@ class RNodeInterface(Interface):
try:
self.bitrate = self.r_sf * ( (4.0/self.r_cr) / (math.pow(2,self.r_sf)/(self.r_bandwidth/1000)) ) * 1000
self.bitrate_kbps = round(self.bitrate/1000.0, 2)
RNS.log(str(self)+" On-air bitrate is now "+str(self.bitrate_kbps)+ " kbps", RNS.LOG_INFO)
RNS.log(str(self)+" On-air bitrate is now "+str(self.bitrate_kbps)+ " kbps", RNS.LOG_VERBOSE)
except:
self.bitrate = 0
def processIncoming(self, data):
self.rxb += len(data)
self.owner.inbound(data, self)
self.r_stat_rssi = None
self.r_stat_snr = None
def processOutgoing(self,data):
datalen = len(data)
if self.online:
if self.interface_ready:
if self.flow_control:
@@ -297,6 +376,7 @@ class RNodeInterface(Interface):
frame = bytes([0xc0])+bytes([0x00])+data+bytes([0xc0])
written = self.serial.write(frame)
self.txb += datalen
if written != len(frame):
raise IOError("Serial interface only wrote "+str(written)+" bytes of "+str(len(data)))
@@ -397,8 +477,30 @@ class RNodeInterface(Interface):
self.updateBitrate()
elif (command == KISS.CMD_RADIO_STATE):
self.r_state = byte
if self.r_state:
pass
#RNS.log(str(self)+" Radio reporting state is online", RNS.LOG_DEBUG)
else:
RNS.log(str(self)+" Radio reporting state is offline", RNS.LOG_DEBUG)
elif (command == KISS.CMD_RADIO_LOCK):
self.r_lock = byte
elif (command == KISS.CMD_FW_VERSION):
if (byte == KISS.FESC):
escape = True
else:
if (escape):
if (byte == KISS.TFEND):
byte = KISS.FEND
if (byte == KISS.TFESC):
byte = KISS.FESC
escape = False
command_buffer = command_buffer+bytes([byte])
if (len(command_buffer) == 2):
self.maj_version = int(command_buffer[0])
self.min_version = int(command_buffer[1])
self.validate_firmware()
elif (command == KISS.CMD_STAT_RX):
if (byte == KISS.FESC):
escape = True
@@ -433,15 +535,33 @@ class RNodeInterface(Interface):
self.r_stat_snr = int.from_bytes(bytes([byte]), byteorder="big", signed=True) * 0.25
elif (command == KISS.CMD_RANDOM):
self.r_random = byte
elif (command == KISS.CMD_PLATFORM):
self.platform = byte
elif (command == KISS.CMD_MCU):
self.mcu = byte
elif (command == KISS.CMD_ERROR):
if (byte == KISS.ERROR_INITRADIO):
RNS.log(str(self)+" hardware initialisation error (code "+RNS.hexrep(byte)+")", RNS.LOG_ERROR)
raise IOError("Radio initialisation failure")
elif (byte == KISS.ERROR_INITRADIO):
RNS.log(str(self)+" hardware TX error (code "+RNS.hexrep(byte)+")", RNS.LOG_ERROR)
raise IOError("Hardware transmit failure")
else:
RNS.log(str(self)+" hardware error (code "+RNS.hexrep(byte)+")", RNS.LOG_ERROR)
raise IOError("Unknown hardware failure")
elif (command == KISS.CMD_RESET):
if (byte == 0xF8):
if self.platform == KISS.PLATFORM_ESP32:
if self.online:
RNS.log("Detected reset while device was online, reinitialising device...", RNS.LOG_ERROR)
raise IOError("ESP32 reset")
elif (command == KISS.CMD_READY):
self.process_queue()
elif (command == KISS.CMD_DETECT):
if byte == KISS.DETECT_RESP:
self.detected = True
else:
self.detected = False
else:
time_since_last = int(time.time()*1000) - last_read_ms
@@ -463,8 +583,30 @@ class RNodeInterface(Interface):
except Exception as e:
self.online = False
RNS.log("A serial port error occurred, the contained exception was: "+str(e), RNS.LOG_ERROR)
RNS.log("The interface "+str(self.name)+" is now offline. Restart Reticulum to attempt reconnection.", RNS.LOG_ERROR)
RNS.log("The interface "+str(self)+" experienced an unrecoverable error and is now offline.", RNS.LOG_ERROR)
if RNS.Reticulum.panic_on_interface_error:
RNS.panic()
RNS.log("Reticulum will attempt to reconnect the interface periodically.", RNS.LOG_ERROR)
self.online = False
self.serial.close()
self.reconnect_port()
def reconnect_port(self):
while not self.online:
try:
time.sleep(3.5)
RNS.log("Attempting to reconnect serial port "+str(self.port)+" for "+str(self)+"...", RNS.LOG_VERBOSE)
self.open_port()
if self.serial.is_open:
self.configure_device()
except Exception as e:
RNS.log("Error while reconnecting port, the contained exception was: "+str(e), RNS.LOG_ERROR)
RNS.log("Reconnected serial port for "+str(self))
def __str__(self):
return "RNodeInterface["+self.name+"]"
return "RNodeInterface["+str(self.name)+"]"
+66 -22
View File
@@ -1,7 +1,6 @@
from .Interface import Interface
from time import sleep
import sys
import serial
import threading
import time
import RNS
@@ -31,6 +30,18 @@ class SerialInterface(Interface):
serial = None
def __init__(self, owner, name, port, speed, databits, parity, stopbits):
import importlib
if importlib.util.find_spec('serial') != None:
import serial
else:
RNS.log("Using the Serial interface requires a serial communication module to be installed.", RNS.LOG_CRITICAL)
RNS.log("You can install one with the command: python3 -m pip install pyserial", RNS.LOG_CRITICAL)
RNS.panic()
self.rxb = 0
self.txb = 0
self.pyserial = serial
self.serial = None
self.owner = owner
self.name = name
@@ -49,36 +60,45 @@ class SerialInterface(Interface):
self.parity = serial.PARITY_ODD
try:
RNS.log("Opening serial port "+self.port+"...")
self.serial = serial.Serial(
port = self.port,
baudrate = self.speed,
bytesize = self.databits,
parity = self.parity,
stopbits = self.stopbits,
xonxoff = False,
rtscts = False,
timeout = 0,
inter_byte_timeout = None,
write_timeout = None,
dsrdtr = False,
)
self.open_port()
except Exception as e:
RNS.log("Could not open serial port for interface "+str(self), RNS.LOG_ERROR)
raise e
if self.serial.is_open:
sleep(0.5)
thread = threading.Thread(target=self.readLoop)
thread.setDaemon(True)
thread.start()
self.online = True
RNS.log("Serial port "+self.port+" is now open")
self.configure_device()
else:
raise IOError("Could not open serial port")
def open_port(self):
RNS.log("Opening serial port "+self.port+"...", RNS.LOG_VERBOSE)
self.serial = self.pyserial.Serial(
port = self.port,
baudrate = self.speed,
bytesize = self.databits,
parity = self.parity,
stopbits = self.stopbits,
xonxoff = False,
rtscts = False,
timeout = 0,
inter_byte_timeout = None,
write_timeout = None,
dsrdtr = False,
)
def configure_device(self):
sleep(0.5)
thread = threading.Thread(target=self.readLoop)
thread.setDaemon(True)
thread.start()
self.online = True
RNS.log("Serial port "+self.port+" is now open")
def processIncoming(self, data):
self.rxb += len(data)
self.owner.inbound(data, self)
@@ -86,6 +106,7 @@ class SerialInterface(Interface):
if self.online:
data = bytes([HDLC.FLAG])+HDLC.escape(data)+bytes([HDLC.FLAG])
written = self.serial.write(data)
self.txb += len(data)
if written != len(data):
raise IOError("Serial interface only wrote "+str(written)+" bytes of "+str(len(data)))
@@ -127,10 +148,33 @@ class SerialInterface(Interface):
in_frame = False
escape = False
sleep(0.08)
except Exception as e:
self.online = False
RNS.log("A serial port error occurred, the contained exception was: "+str(e), RNS.LOG_ERROR)
RNS.log("The interface "+str(self.name)+" is now offline. Restart Reticulum to attempt reconnection.", RNS.LOG_ERROR)
RNS.log("The interface "+str(self)+" experienced an unrecoverable error and is now offline.", RNS.LOG_ERROR)
if RNS.Reticulum.panic_on_interface_error:
RNS.panic()
RNS.log("Reticulum will attempt to reconnect the interface periodically.", RNS.LOG_ERROR)
self.online = False
self.serial.close()
self.reconnect_port()
def reconnect_port(self):
while not self.online:
try:
time.sleep(5)
RNS.log("Attempting to reconnect serial port "+str(self.port)+" for "+str(self)+"...", RNS.LOG_VERBOSE)
self.open_port()
if self.serial.is_open:
self.configure_device()
except Exception as e:
RNS.log("Error while reconnecting port, the contained exception was: "+str(e), RNS.LOG_ERROR)
RNS.log("Reconnected serial port for "+str(self))
def __str__(self):
return "SerialInterface["+self.name+"]"
+290 -36
View File
@@ -1,7 +1,7 @@
from .Interface import Interface
import socketserver
import threading
import netifaces
import platform
import socket
import time
import sys
@@ -19,17 +19,62 @@ class HDLC():
data = data.replace(bytes([HDLC.FLAG]), bytes([HDLC.ESC, HDLC.FLAG^HDLC.ESC_MASK]))
return data
class KISS():
FEND = 0xC0
FESC = 0xDB
TFEND = 0xDC
TFESC = 0xDD
CMD_DATA = 0x00
CMD_UNKNOWN = 0xFE
@staticmethod
def escape(data):
data = data.replace(bytes([0xdb]), bytes([0xdb, 0xdd]))
data = data.replace(bytes([0xc0]), bytes([0xdb, 0xdc]))
return data
class ThreadingTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
pass
class TCPClientInterface(Interface):
RECONNECT_WAIT = 5
RECONNECT_MAX_TRIES = None
def __init__(self, owner, name, target_ip=None, target_port=None, connected_socket=None):
# TCP socket options
TCP_USER_TIMEOUT = 20
TCP_PROBE_AFTER = 5
TCP_PROBE_INTERVAL = 3
TCP_PROBES = 5
I2P_USER_TIMEOUT = 40
I2P_PROBE_AFTER = 10
I2P_PROBE_INTERVAL = 5
I2P_PROBES = 6
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.IN = True
self.OUT = False
self.socket = None
self.parent_interface = None
self.name = name
self.initiator = False
self.reconnecting = False
self.never_connected = True
self.owner = owner
self.writing = False
self.online = False
self.detached = False
self.kiss_framing = kiss_framing
self.i2p_tunneled = i2p_tunneled
self.mode = RNS.Interfaces.Interface.Interface.MODE_FULL
if max_reconnect_tries == None:
self.max_reconnect_tries = TCPClientInterface.RECONNECT_MAX_TRIES
else:
self.max_reconnect_tries = max_reconnect_tries
if connected_socket != None:
self.receives = True
@@ -37,24 +82,141 @@ class TCPClientInterface(Interface):
self.target_port = None
self.socket = connected_socket
if platform.system() == "Linux":
self.set_timeouts_linux()
elif platform.system() == "Darwin":
self.set_timeouts_osx()
elif target_ip != None and target_port != None:
self.receives = True
self.target_ip = target_ip
self.target_port = target_port
self.initiator = True
if not self.connect(initial=True):
thread = threading.Thread(target=self.reconnect)
thread.setDaemon(True)
thread.start()
else:
thread = threading.Thread(target=self.read_loop)
thread.setDaemon(True)
thread.start()
if not self.kiss_framing:
self.wants_tunnel = True
def set_timeouts_linux(self):
if not self.i2p_tunneled:
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_USER_TIMEOUT, int(TCPClientInterface.TCP_USER_TIMEOUT * 1000))
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
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)
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, int(TCPClientInterface.I2P_PROBE_AFTER))
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, int(TCPClientInterface.I2P_PROBE_INTERVAL))
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, int(TCPClientInterface.I2P_PROBES))
def set_timeouts_osx(self):
if hasattr(socket, "TCP_KEEPALIVE"):
TCP_KEEPIDLE = socket.TCP_KEEPALIVE
else:
TCP_KEEPIDLE = 0x10
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
if not self.i2p_tunneled:
self.socket.setsockopt(socket.IPPROTO_TCP, TCP_KEEPIDLE, int(TCPClientInterface.TCP_PROBE_AFTER))
else:
self.socket.setsockopt(socket.IPPROTO_TCP, TCP_KEEPIDLE, int(TCPClientInterface.I2P_PROBE_AFTER))
def detach(self):
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:
self.socket.shutdown(socket.SHUT_RDWR)
except Exception as e:
RNS.log("Error while shutting down socket for "+str(self)+": "+str(e))
try:
self.socket.close()
except Exception as e:
RNS.log("Error while closing socket for "+str(self)+": "+str(e))
self.socket = None
def connect(self, initial=False):
try:
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.socket.connect((self.target_ip, self.target_port))
self.online = True
except Exception as e:
if initial:
RNS.log("Initial connection for "+str(self)+" could not be established: "+str(e), RNS.LOG_ERROR)
RNS.log("Leaving unconnected and retrying connection in "+str(TCPClientInterface.RECONNECT_WAIT)+" seconds.", RNS.LOG_ERROR)
return False
else:
raise e
self.owner = owner
if platform.system() == "Linux":
self.set_timeouts_linux()
elif platform.system() == "Darwin":
self.set_timeouts_osx()
self.online = True
self.writing = False
self.never_connected = False
if connected_socket == None:
thread = threading.Thread(target=self.read_loop)
thread.setDaemon(True)
thread.start()
return True
def reconnect(self):
if self.initiator:
if not self.reconnecting:
self.reconnecting = True
attempts = 0
while not self.online:
time.sleep(TCPClientInterface.RECONNECT_WAIT)
attempts += 1
if self.max_reconnect_tries != None and attempts > self.max_reconnect_tries:
RNS.log("Max reconnection attempts reached for "+str(self), RNS.LOG_ERROR)
self.teardown()
break
try:
self.connect()
except Exception as e:
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)
self.reconnecting = False
thread = threading.Thread(target=self.read_loop)
thread.setDaemon(True)
thread.start()
if not self.kiss_framing:
RNS.Transport.synthesize_tunnel(self)
else:
RNS.log("Attempt to reconnect on a non-initiator TCP interface. This should not happen.", RNS.LOG_ERROR)
raise IOError("Attempt to reconnect on a non-initiator TCP interface")
def processIncoming(self, data):
self.rxb += len(data)
if hasattr(self, "parent_interface") and self.parent_interface != None:
self.parent_interface.rxb += len(data)
self.owner.inbound(data, self)
def processOutgoing(self, data):
@@ -64,9 +226,18 @@ class TCPClientInterface(Interface):
try:
self.writing = True
data = bytes([HDLC.FLAG])+HDLC.escape(data)+bytes([HDLC.FLAG])
if self.kiss_framing:
data = bytes([KISS.FEND])+bytes([KISS.CMD_DATA])+KISS.escape(data)+bytes([KISS.FEND])
else:
data = bytes([HDLC.FLAG])+HDLC.escape(data)+bytes([HDLC.FLAG])
self.socket.sendall(data)
self.writing = False
self.txb += len(data)
if hasattr(self, "parent_interface") and self.parent_interface != None:
self.parent_interface.txb += len(data)
except Exception as e:
RNS.log("Exception occurred while transmitting via "+str(self)+", tearing down interface", RNS.LOG_ERROR)
RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR)
@@ -78,6 +249,7 @@ class TCPClientInterface(Interface):
in_frame = False
escape = False
data_buffer = b""
command = KISS.CMD_UNKNOWN
while True:
data_in = self.socket.recv(4096)
@@ -86,41 +258,94 @@ class TCPClientInterface(Interface):
while pointer < len(data_in):
byte = data_in[pointer]
pointer += 1
if (in_frame and byte == HDLC.FLAG):
in_frame = False
self.processIncoming(data_buffer)
elif (byte == HDLC.FLAG):
in_frame = True
data_buffer = b""
elif (in_frame and len(data_buffer) < RNS.Reticulum.MTU):
if (byte == HDLC.ESC):
escape = True
else:
if (escape):
if (byte == HDLC.FLAG ^ HDLC.ESC_MASK):
byte = HDLC.FLAG
if (byte == HDLC.ESC ^ HDLC.ESC_MASK):
byte = HDLC.ESC
escape = False
data_buffer = data_buffer+bytes([byte])
if self.kiss_framing:
# Read loop for KISS framing
if (in_frame and byte == KISS.FEND and command == KISS.CMD_DATA):
in_frame = False
self.processIncoming(data_buffer)
elif (byte == KISS.FEND):
in_frame = True
command = KISS.CMD_UNKNOWN
data_buffer = b""
elif (in_frame and len(data_buffer) < RNS.Reticulum.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
byte = byte & 0x0F
command = byte
elif (command == KISS.CMD_DATA):
if (byte == KISS.FESC):
escape = True
else:
if (escape):
if (byte == KISS.TFEND):
byte = KISS.FEND
if (byte == KISS.TFESC):
byte = KISS.FESC
escape = False
data_buffer = data_buffer+bytes([byte])
else:
# Read loop for HDLC framing
if (in_frame and byte == HDLC.FLAG):
in_frame = False
self.processIncoming(data_buffer)
elif (byte == HDLC.FLAG):
in_frame = True
data_buffer = b""
elif (in_frame and len(data_buffer) < RNS.Reticulum.MTU):
if (byte == HDLC.ESC):
escape = True
else:
if (escape):
if (byte == HDLC.FLAG ^ HDLC.ESC_MASK):
byte = HDLC.FLAG
if (byte == HDLC.ESC ^ HDLC.ESC_MASK):
byte = HDLC.ESC
escape = False
data_buffer = data_buffer+bytes([byte])
else:
RNS.log("TCP socket for "+str(self)+" was closed, tearing down interface", RNS.LOG_VERBOSE)
self.teardown()
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)
self.reconnect()
else:
RNS.log("TCP socket for remote client "+str(self)+" was closed.", RNS.LOG_VERBOSE)
self.teardown()
break
except Exception as e:
self.online = False
RNS.log("An interface error occurred, the contained exception was: "+str(e), RNS.LOG_ERROR)
RNS.log("Tearing down "+str(self), RNS.LOG_ERROR)
self.teardown()
RNS.log("An interface error occurred for "+str(self)+", the contained exception was: "+str(e), RNS.LOG_WARNING)
if self.initiator:
RNS.log("Attempting to reconnect...", RNS.LOG_WARNING)
self.reconnect()
else:
self.teardown()
def teardown(self):
if self.initiator and not self.detached:
RNS.log("The interface "+str(self)+" experienced an unrecoverable error and is being torn down. Restart Reticulum to attempt to open this interface again.", RNS.LOG_ERROR)
if RNS.Reticulum.panic_on_interface_error:
RNS.panic()
else:
RNS.log("The interface "+str(self)+" is being torn down.", RNS.LOG_VERBOSE)
self.online = False
self.OUT = False
self.IN = False
if hasattr(self, "parent_interface") and self.parent_interface != None:
self.parent_interface.clients -= 1
if self in RNS.Transport.interfaces:
RNS.Transport.interfaces.remove(self)
if not self.initiator:
RNS.Transport.interfaces.remove(self)
def __str__(self):
@@ -130,16 +355,39 @@ class TCPClientInterface(Interface):
class TCPServerInterface(Interface):
@staticmethod
def get_address_for_if(name):
return netifaces.ifaddresses(name)[netifaces.AF_INET][0]['addr']
import importlib
if importlib.util.find_spec('netifaces') != None:
import netifaces
return netifaces.ifaddresses(name)[netifaces.AF_INET][0]['addr']
else:
RNS.log("Getting interface addresses from device names requires the netifaces module.", RNS.LOG_CRITICAL)
RNS.log("You can install it with the command: python3 -m pip install netifaces", RNS.LOG_CRITICAL)
RNS.panic()
@staticmethod
def get_broadcast_for_if(name):
return netifaces.ifaddresses(name)[netifaces.AF_INET][0]['broadcast']
import importlib
if importlib.util.find_spec('netifaces') != None:
import netifaces
return netifaces.ifaddresses(name)[netifaces.AF_INET][0]['broadcast']
else:
RNS.log("Getting interface addresses from device names requires the netifaces module.", RNS.LOG_CRITICAL)
RNS.log("You can install it with the command: python3 -m pip install netifaces", RNS.LOG_CRITICAL)
RNS.panic()
def __init__(self, owner, name, device=None, bindip=None, bindport=None):
def __init__(self, owner, name, device=None, bindip=None, bindport=None, i2p_tunneled=False):
self.rxb = 0
self.txb = 0
self.online = False
self.clients = 0
self.IN = True
self.OUT = False
self.name = name
self.i2p_tunneled = i2p_tunneled
self.mode = RNS.Interfaces.Interface.Interface.MODE_FULL
if device != None:
bindip = TCPServerInterface.get_address_for_if(device)
@@ -155,24 +403,30 @@ class TCPServerInterface(Interface):
self.owner = owner
address = (self.bind_ip, self.bind_port)
ThreadingTCPServer.allow_reuse_address = True
self.server = ThreadingTCPServer(address, handlerFactory(self.incoming_connection))
thread = threading.Thread(target=self.server.serve_forever)
thread.setDaemon(True)
thread.start()
self.online = True
def incoming_connection(self, handler):
RNS.log("Accepting incoming TCP connection", RNS.LOG_VERBOSE)
interface_name = "Client on "+self.name
spawned_interface = TCPClientInterface(self.owner, interface_name, target_ip=None, target_port=None, connected_socket=handler.request)
spawned_interface = TCPClientInterface(self.owner, interface_name, target_ip=None, target_port=None, connected_socket=handler.request, i2p_tunneled=self.i2p_tunneled)
spawned_interface.OUT = self.OUT
spawned_interface.IN = self.IN
spawned_interface.target_ip = handler.client_address[0]
spawned_interface.target_port = str(handler.client_address[1])
spawned_interface.parent_interface = self
spawned_interface.online = True
RNS.log("Spawned new TCPClient Interface: "+str(spawned_interface), RNS.LOG_VERBOSE)
RNS.Transport.interfaces.append(spawned_interface)
self.clients += 1
spawned_interface.read_loop()
def processOutgoing(self, data):
@@ -187,4 +441,4 @@ class TCPInterfaceHandler(socketserver.BaseRequestHandler):
socketserver.BaseRequestHandler.__init__(self, *args, **keys)
def handle(self):
self.callback(handler=self)
self.callback(handler=self)
+32 -6
View File
@@ -1,7 +1,6 @@
from .Interface import Interface
import socketserver
import threading
import netifaces
import socket
import time
import sys
@@ -12,15 +11,33 @@ class UDPInterface(Interface):
@staticmethod
def get_address_for_if(name):
return netifaces.ifaddresses(name)[netifaces.AF_INET][0]['addr']
import importlib
if importlib.util.find_spec('netifaces') != None:
import netifaces
return netifaces.ifaddresses(name)[netifaces.AF_INET][0]['addr']
else:
RNS.log("Getting interface addresses from device names requires the netifaces module.", RNS.LOG_CRITICAL)
RNS.log("You can install it with the command: python3 -m pip install netifaces", RNS.LOG_CRITICAL)
RNS.panic()
@staticmethod
def get_broadcast_for_if(name):
return netifaces.ifaddresses(name)[netifaces.AF_INET][0]['broadcast']
import importlib
if importlib.util.find_spec('netifaces') != None:
import netifaces
return netifaces.ifaddresses(name)[netifaces.AF_INET][0]['broadcast']
else:
RNS.log("Getting interface addresses from device names requires the netifaces module.", RNS.LOG_CRITICAL)
RNS.log("You can install it with the command: python3 -m pip install netifaces", RNS.LOG_CRITICAL)
RNS.panic()
def __init__(self, owner, name, device=None, bindip=None, bindport=None, forwardip=None, forwardport=None):
self.rxb = 0
self.txb = 0
self.IN = True
self.OUT = False
self.name = name
self.online = False
if device != None:
if bindip == None:
@@ -41,12 +58,15 @@ class UDPInterface(Interface):
self.owner = owner
address = (self.bind_ip, self.bind_port)
socketserver.UDPServer.address_family = socket.AF_INET
self.server = socketserver.UDPServer(address, handlerFactory(self.processIncoming))
thread = threading.Thread(target=self.server.serve_forever)
thread.setDaemon(True)
thread.start()
self.online = True
if (forwardip != None and forwardport != None):
self.forwards = True
self.forward_ip = forwardip
@@ -54,12 +74,18 @@ class UDPInterface(Interface):
def processIncoming(self, data):
self.rxb += len(data)
self.owner.inbound(data, self)
def processOutgoing(self,data):
udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
udp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
udp_socket.sendto(data, (self.forward_ip, self.forward_port))
try:
udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
udp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
udp_socket.sendto(data, (self.forward_ip, self.forward_port))
self.txb += len(data)
except Exception as e:
RNS.log("Could not transmit on "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
def __str__(self):
+328 -159
View File
@@ -27,12 +27,13 @@ class LinkCallbacks:
class Link:
"""
This class.
This class is used to establish and manage links to other peers. When a
link instance is created, Reticulum will attempt to establish verified
connectivity with the specified destination.
:param destination: A :ref:`RNS.Destination<api-destination>` instance which to establish a link to.
:param owner: Internal use by :ref:`RNS.Transport<api-Transport>`, ignore this argument.
:param peer_pub_bytes: Internal use, ignore this argument.
:param peer_sig_pub_bytes: Internal use, ignore this argument.
:param established_callback: An optional function or method with the signature *callback(link)* to be called when the link has been established.
:param closed_callback: An optional function or method with the signature *callback(link)* to be called when the link is closed.
"""
CURVE = RNS.Identity.CURVE
"""
@@ -42,16 +43,15 @@ class Link:
ECPUBSIZE = 32+32
KEYSIZE = 32
MDU = math.floor((RNS.Reticulum.MDU-RNS.Identity.AES_HMAC_OVERHEAD)/RNS.Identity.AES128_BLOCKSIZE)*RNS.Identity.AES128_BLOCKSIZE - 1
MDU = math.floor((RNS.Reticulum.MTU-RNS.Reticulum.HEADER_MINSIZE-RNS.Identity.FERNET_OVERHEAD)/RNS.Identity.AES128_BLOCKSIZE)*RNS.Identity.AES128_BLOCKSIZE - 1
# TODO: This should not be hardcoded,
# but calculated from something like
# first-hop RTT latency and distance
DEFAULT_TIMEOUT = 15.0
ESTABLISHMENT_TIMEOUT_PER_HOP = RNS.Reticulum.DEFAULT_PER_HOP_TIMEOUT
"""
Default timeout for link establishment in seconds.
Default timeout for link establishment in seconds per hop to destination.
"""
TIMEOUT_FACTOR = 3
TRAFFIC_TIMEOUT_FACTOR = 6
KEEPALIVE_TIMEOUT_FACTOR = 4
STALE_GRACE = 2
KEEPALIVE = 360
"""
@@ -80,6 +80,7 @@ class Link:
link = Link(owner = owner, peer_pub_bytes=data[:Link.ECPUBSIZE//2], peer_sig_pub_bytes=data[Link.ECPUBSIZE//2:Link.ECPUBSIZE])
link.set_link_id(packet)
link.destination = packet.destination
link.establishment_timeout = Link.ESTABLISHMENT_TIMEOUT_PER_HOP * max(1, packet.hops)
RNS.log("Validating link request "+RNS.prettyhexrep(link.link_id), RNS.LOG_VERBOSE)
link.handshake()
link.attached_interface = packet.receiving_interface
@@ -94,7 +95,7 @@ class Link:
except Exception as e:
RNS.log("Validating link request failed", RNS.LOG_VERBOSE)
traceback.print_exc()
RNS.log("exc: "+str(e))
return None
else:
@@ -102,7 +103,7 @@ class Link:
return None
def __init__(self, destination=None, owner=None, peer_pub_bytes = None, peer_sig_pub_bytes = None):
def __init__(self, destination=None, established_callback = None, closed_callback = None, owner=None, peer_pub_bytes = None, peer_sig_pub_bytes = None):
if destination != None and destination.type != RNS.Destination.SINGLE:
raise TypeError("Links can only be established to the \"single\" destination type")
self.rtt = None
@@ -117,24 +118,24 @@ class Link:
self.rx = 0
self.txbytes = 0
self.rxbytes = 0
self.default_timeout = Link.DEFAULT_TIMEOUT
self.proof_timeout = self.default_timeout
self.timeout_factor = Link.TIMEOUT_FACTOR
self.traffic_timeout_factor = Link.TRAFFIC_TIMEOUT_FACTOR
self.keepalive_timeout_factor = Link.KEEPALIVE_TIMEOUT_FACTOR
self.keepalive = Link.KEEPALIVE
self.watchdog_lock = False
self.status = Link.PENDING
self.activated_at = None
self.type = RNS.Destination.LINK
self.owner = owner
self.destination = destination
self.attached_interface = None
self.__remote_identity = None
self.__encryption_disabled = False
if self.destination == None:
self.initiator = False
self.prv = self.owner.identity.prv
self.sig_prv = self.owner.identity.sig_prv
else:
self.initiator = True
self.establishment_timeout = Link.ESTABLISHMENT_TIMEOUT_PER_HOP * max(1, RNS.Transport.hops_to(destination.hash))
self.prv = X25519PrivateKey.generate()
self.sig_prv = Ed25519PrivateKey.generate()
@@ -158,6 +159,12 @@ class Link:
else:
self.load_peer(peer_pub_bytes, peer_sig_pub_bytes)
if established_callback != None:
self.set_link_established_callback(established_callback)
if closed_callback != None:
self.set_link_closed_callback(closed_callback)
if (self.initiator):
peer_pub_bytes = self.destination.identity.get_public_key()[:Link.ECPUBSIZE//2]
peer_sig_pub_bytes = self.destination.identity.get_public_key()[Link.ECPUBSIZE//2:Link.ECPUBSIZE]
@@ -172,7 +179,7 @@ class Link:
self.start_watchdog()
self.packet.send()
self.had_outbound()
RNS.log("Link request "+RNS.prettyhexrep(self.link_id)+" sent to "+str(self.destination), RNS.LOG_VERBOSE)
RNS.log("Link request "+RNS.prettyhexrep(self.link_id)+" sent to "+str(self.destination), RNS.LOG_DEBUG)
def load_peer(self, peer_pub_bytes, peer_sig_pub_bytes):
@@ -208,6 +215,7 @@ class Link:
proof.send()
self.had_outbound()
def prove_packet(self, packet):
signature = self.sign(packet.packet_hash)
# TODO: Hardcoded as explicit proof for now
@@ -222,29 +230,30 @@ class Link:
self.had_outbound()
def validate_proof(self, packet):
if self.initiator and len(packet.data) == RNS.Identity.SIGLENGTH//8:
signed_data = self.link_id+self.peer_pub_bytes+self.peer_sig_pub_bytes
signature = packet.data[:RNS.Identity.SIGLENGTH//8]
if self.destination.identity.validate(signature, signed_data):
self.rtt = time.time() - self.request_time
self.attached_interface = packet.receiving_interface
self.__remote_identity = self.destination.identity
RNS.Transport.activate_link(self)
RNS.log("Link "+str(self)+" established with "+str(self.destination)+", RTT is "+str(self.rtt), RNS.LOG_VERBOSE)
rtt_data = umsgpack.packb(self.rtt)
rtt_packet = RNS.Packet(self, rtt_data, context=RNS.Packet.LRRTT)
RNS.log("Sending RTT packet", RNS.LOG_EXTREME);
rtt_packet.send()
self.had_outbound()
if self.status == Link.HANDSHAKE:
if self.initiator and len(packet.data) == RNS.Identity.SIGLENGTH//8:
signed_data = self.link_id+self.peer_pub_bytes+self.peer_sig_pub_bytes
signature = packet.data[:RNS.Identity.SIGLENGTH//8]
if self.destination.identity.validate(signature, signed_data):
self.rtt = time.time() - self.request_time
self.attached_interface = packet.receiving_interface
self.__remote_identity = self.destination.identity
RNS.Transport.activate_link(self)
RNS.log("Link "+str(self)+" established with "+str(self.destination)+", RTT is "+str(self.rtt), RNS.LOG_VERBOSE)
rtt_data = umsgpack.packb(self.rtt)
rtt_packet = RNS.Packet(self, rtt_data, context=RNS.Packet.LRRTT)
rtt_packet.send()
self.had_outbound()
self.status = Link.ACTIVE
if self.callbacks.link_established != None:
thread = threading.Thread(target=self.callbacks.link_established, args=(self,))
thread.setDaemon(True)
thread.start()
else:
RNS.log("Invalid link proof signature received by "+str(self)+". Ignoring.", RNS.LOG_DEBUG)
self.status = Link.ACTIVE
self.activated_at = time.time()
if self.callbacks.link_established != None:
thread = threading.Thread(target=self.callbacks.link_established, args=(self,))
thread.setDaemon(True)
thread.start()
else:
RNS.log("Invalid link proof signature received by "+str(self)+". Ignoring.", RNS.LOG_DEBUG)
def identify(self, identity):
@@ -266,38 +275,53 @@ class Link:
self.had_outbound()
def request(self, path, data = None, response_callback = None, failed_callback = None):
def request(self, path, data = None, response_callback = None, failed_callback = None, progress_callback = None, timeout = None):
"""
Sends a request to the remote peer.
:param path: The request path.
:param response_callback: A function or method with the signature *response_callback(request_receipt)* to be called when a response is received. See the :ref:`Request Example<example-request>` for more info.
:param failed_callback: A function or method with the signature *failed_callback(request_receipt)* to be called when a request fails. See the :ref:`Request Example<example-request>` for more info.
:param response_callback: An optional function or method with the signature *response_callback(request_receipt)* to be called when a response is received. See the :ref:`Request Example<example-request>` for more info.
:param failed_callback: An optional function or method with the signature *failed_callback(request_receipt)* to be called when a request fails. See the :ref:`Request Example<example-request>` for more info.
:param progress_callback: An optional function or method with the signature *progress_callback(request_receipt)* to be called when progress is made receiving the response. Progress can be accessed as a float between 0.0 and 1.0 by the *request_receipt.progress* property.
:param timeout: An optional timeout in seconds for the request. If *None* is supplied it will be calculated based on link RTT.
:returns: A :ref:`RNS.RequestReceipt<api-requestreceipt>` instance if the request was sent, or *False* if it was not.
"""
request_path_hash = RNS.Identity.truncated_hash(path.encode("utf-8"))
unpacked_request = [time.time(), request_path_hash, data]
packed_request = umsgpack.packb(unpacked_request)
if timeout == None:
timeout = self.rtt * self.traffic_timeout_factor + RNS.Resource.RESPONSE_MAX_GRACE_TIME/4.0
if len(packed_request) <= Link.MDU:
request_packet = RNS.Packet(self, packed_request, RNS.Packet.DATA, context = RNS.Packet.REQUEST)
packet_receipt = request_packet.send()
return RequestReceipt(
self,
packet_receipt = request_packet.send(),
response_callback = response_callback,
failed_callback = failed_callback
)
if packet_receipt == False:
return False
else:
packet_receipt.set_timeout(timeout)
return RequestReceipt(
self,
packet_receipt = packet_receipt,
response_callback = response_callback,
failed_callback = failed_callback,
progress_callback = progress_callback,
timeout = timeout
)
else:
request_id = RNS.Identity.truncated_hash(packed_request)
RNS.log("Sending request "+RNS.prettyhexrep(request_id)+" as resource.", RNS.LOG_DEBUG)
request_resource = RNS.Resource(packed_request, self, request_id = request_id, is_response = False)
request_resource = RNS.Resource(packed_request, self, request_id = request_id, is_response = False, timeout = timeout)
return RequestReceipt(
self,
resource = request_resource,
response_callback = response_callback,
failed_callback = failed_callback
failed_callback = failed_callback,
progress_callback = progress_callback,
timeout = timeout
)
@@ -313,6 +337,8 @@ class Link:
rtt = umsgpack.unpackb(plaintext)
self.rtt = max(measured_rtt, rtt)
self.status = Link.ACTIVE
self.activated_at = time.time()
if self.owner.callbacks.link_established != None:
self.owner.callbacks.link_established(self)
@@ -394,8 +420,17 @@ class Link:
self.shared_key = None
self.derived_key = None
if self.destination != None:
if self.destination.direction == RNS.Destination.IN:
if self in self.destination.links:
self.destination.links.remove(self)
if self.callbacks.link_closed != None:
self.callbacks.link_closed(self)
try:
self.callbacks.link_closed(self)
except Exception as e:
RNS.log("Error while executing link closed callback from "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
def start_watchdog(self):
thread = threading.Thread(target=self.__watchdog_job)
@@ -411,9 +446,9 @@ class Link:
# Link was initiated, but no response
# from destination yet
if self.status == Link.PENDING:
next_check = self.request_time + self.proof_timeout
next_check = self.request_time + self.establishment_timeout
sleep_time = next_check - time.time()
if time.time() >= self.request_time + self.proof_timeout:
if time.time() >= self.request_time + self.establishment_timeout:
RNS.log("Link establishment timed out", RNS.LOG_VERBOSE)
self.status = Link.CLOSED
self.teardown_reason = Link.TIMEOUT
@@ -421,10 +456,14 @@ class Link:
sleep_time = 0.001
elif self.status == Link.HANDSHAKE:
next_check = self.request_time + self.proof_timeout
next_check = self.request_time + self.establishment_timeout
sleep_time = next_check - time.time()
if time.time() >= self.request_time + self.proof_timeout:
RNS.log("Timeout waiting for RTT packet from link initiator", RNS.LOG_DEBUG)
if time.time() >= self.request_time + self.establishment_timeout:
if self.initiator:
RNS.log("Timeout waiting for link request proof", RNS.LOG_DEBUG)
else:
RNS.log("Timeout waiting for RTT packet from link initiator", RNS.LOG_DEBUG)
self.status = Link.CLOSED
self.teardown_reason = Link.TIMEOUT
self.link_closed()
@@ -432,7 +471,7 @@ class Link:
elif self.status == Link.ACTIVE:
if time.time() >= self.last_inbound + self.keepalive:
sleep_time = self.rtt * self.timeout_factor + Link.STALE_GRACE
sleep_time = self.rtt * self.keepalive_timeout_factor + Link.STALE_GRACE
self.status = Link.STALE
if self.initiator:
self.send_keepalive()
@@ -462,53 +501,58 @@ class Link:
self.had_outbound()
def handle_request(self, request_id, unpacked_request):
requested_at = unpacked_request[0]
path_hash = unpacked_request[1]
request_data = unpacked_request[2]
if self.status == Link.ACTIVE:
requested_at = unpacked_request[0]
path_hash = unpacked_request[1]
request_data = unpacked_request[2]
if path_hash in self.destination.request_handlers:
request_handler = self.destination.request_handlers[path_hash]
path = request_handler[0]
response_generator = request_handler[1]
allow = request_handler[2]
allowed_list = request_handler[3]
if path_hash in self.destination.request_handlers:
request_handler = self.destination.request_handlers[path_hash]
path = request_handler[0]
response_generator = request_handler[1]
allow = request_handler[2]
allowed_list = request_handler[3]
allowed = False
if not allow == RNS.Destination.ALLOW_NONE:
if allow == RNS.Destination.ALLOW_LIST:
if self.__remote_identity.hash in allowed_list:
allowed = False
if not allow == RNS.Destination.ALLOW_NONE:
if allow == RNS.Destination.ALLOW_LIST:
if self.__remote_identity.hash in allowed_list:
allowed = True
elif allow == RNS.Destination.ALLOW_ALL:
allowed = True
elif allow == RNS.Destination.ALLOW_ALL:
allowed = True
if allowed:
RNS.log("Handling request "+RNS.prettyhexrep(request_id)+" for: "+str(path), RNS.LOG_DEBUG)
response = response_generator(path, request_data, request_id, self.__remote_identity, requested_at)
if response != None:
packed_response = umsgpack.packb([request_id, response])
if allowed:
RNS.log("Handling request "+RNS.prettyhexrep(request_id)+" for: "+str(path), RNS.LOG_DEBUG)
response = response_generator(path, request_data, request_id, self.__remote_identity, requested_at)
if response != None:
packed_response = umsgpack.packb([request_id, response])
if len(packed_response) <= Link.MDU:
RNS.Packet(self, packed_response, RNS.Packet.DATA, context = RNS.Packet.RESPONSE).send()
else:
response_resource = RNS.Resource(packed_response, self, request_id = request_id, is_response = True)
else:
identity_string = RNS.prettyhexrep(self.get_remote_identity()) if self.get_remote_identity() != None else "<Unknown>"
RNS.log("Request "+RNS.prettyhexrep(request_id)+" from "+identity_string+" not allowed for: "+str(path), RNS.LOG_DEBUG)
if len(packed_response) <= Link.MDU:
RNS.Packet(self, packed_response, RNS.Packet.DATA, context = RNS.Packet.RESPONSE).send()
else:
response_resource = RNS.Resource(packed_response, self, request_id = request_id, is_response = True)
else:
identity_string = str(self.get_remote_identity()) if self.get_remote_identity() != None else "<Unknown>"
RNS.log("Request "+RNS.prettyhexrep(request_id)+" from "+identity_string+" not allowed for: "+str(path), RNS.LOG_DEBUG)
def handle_response(self, request_id, response_data):
remove = None
for pending_request in self.pending_requests:
if pending_request.request_id == request_id:
remove = pending_request
try:
pending_request.response_received(response_data)
except Exception as e:
RNS.log("Error occurred while handling response. The contained exception was: "+str(e), RNS.LOG_ERROR)
def handle_response(self, request_id, response_data, response_size, response_transfer_size):
if self.status == Link.ACTIVE:
remove = None
for pending_request in self.pending_requests:
if pending_request.request_id == request_id:
remove = pending_request
try:
pending_request.response_size = response_size
pending_request.response_transfer_size = response_transfer_size
pending_request.response_received(response_data)
except Exception as e:
RNS.log("Error occurred while handling response. The contained exception was: "+str(e), RNS.LOG_ERROR)
break
break
if remove != None:
self.pending_requests.remove(remove)
if remove != None:
if remove in self.pending_requests:
self.pending_requests.remove(remove)
def request_resource_concluded(self, resource):
if resource.status == RNS.Resource.COMPLETE:
@@ -528,9 +572,12 @@ class Link:
request_id = unpacked_response[0]
response_data = unpacked_response[1]
self.handle_response(request_id, response_data)
self.handle_response(request_id, response_data, resource.total_size, resource.size)
else:
RNS.log("Incoming response resource failed with status: "+RNS.hexrep([resource.status]), RNS.LOG_DEBUG)
for pending_request in self.pending_requests:
if pending_request.request_id == resource.request_id:
pending_request.request_timed_out(None)
def receive(self, packet):
self.watchdog_lock = True
@@ -557,7 +604,10 @@ class Link:
elif self.destination.proof_strategy == RNS.Destination.PROVE_APP:
if self.destination.callbacks.proof_requested:
self.destination.callbacks.proof_requested(packet)
try:
self.destination.callbacks.proof_requested(packet)
except Exception as e:
RNS.log("Error while executing proof request callback from "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
elif packet.context == RNS.Packet.LINKIDENTIFY:
plaintext = self.decrypt(packet.data)
@@ -572,7 +622,10 @@ class Link:
if identity.validate(signature, signed_data):
self.__remote_identity = identity
if self.callbacks.remote_identified != None:
self.callbacks.remote_identified(self.__remote_identity)
try:
self.callbacks.remote_identified(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)
elif packet.context == RNS.Packet.REQUEST:
try:
@@ -589,7 +642,8 @@ class Link:
unpacked_response = umsgpack.unpackb(packed_response)
request_id = unpacked_response[0]
response_data = unpacked_response[1]
self.handle_response(request_id, response_data)
transfer_size = len(umsgpack.packb(response_data))-2
self.handle_response(request_id, response_data, transfer_size, transfer_size)
except Exception as e:
RNS.log("Error occurred while handling response. The contained exception was: "+str(e), RNS.LOG_ERROR)
@@ -606,13 +660,22 @@ class Link:
if RNS.ResourceAdvertisement.is_request(packet):
RNS.Resource.accept(packet, callback=self.request_resource_concluded)
elif RNS.ResourceAdvertisement.is_response(packet):
RNS.Resource.accept(packet, callback=self.response_resource_concluded)
request_id = RNS.ResourceAdvertisement.get_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.started_at = time.time()
elif self.resource_strategy == Link.ACCEPT_NONE:
pass
elif self.resource_strategy == Link.ACCEPT_APP:
if self.callbacks.resource != None:
if self.callbacks.resource(resource):
RNS.Resource.accept(packet, self.callbacks.resource_concluded)
try:
if self.callbacks.resource(resource):
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)
elif self.resource_strategy == Link.ACCEPT_ALL:
RNS.Resource.accept(packet, self.callbacks.resource_concluded)
@@ -624,7 +687,11 @@ class Link:
resource_hash = plaintext[1:RNS.Identity.HASHLENGTH//8+1]
for resource in self.outgoing_resources:
if resource.hash == resource_hash:
resource.request(plaintext)
# We need to check that this request has not been
# received before in order to avoid sequencing errors.
if not packet.packet_hash in resource.req_hashlist:
resource.req_hashlist.append(packet.packet_hash)
resource.request(plaintext)
elif packet.context == RNS.Packet.RESOURCE_HMU:
plaintext = self.decrypt(packet.data)
@@ -666,32 +733,42 @@ class Link:
def encrypt(self, plaintext):
if self.__encryption_disabled:
return plaintext
try:
if not self.fernet:
self.fernet = Fernet(base64.urlsafe_b64encode(self.derived_key))
try:
self.fernet = Fernet(base64.urlsafe_b64encode(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
ciphertext = base64.urlsafe_b64decode(self.fernet.encrypt(plaintext))
# 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
except Exception as e:
RNS.log("Encryption on link "+str(self)+" failed. The contained exception was: "+str(e), RNS.LOG_ERROR)
raise e
def decrypt(self, ciphertext):
if self.__encryption_disabled:
return ciphertext
try:
if not self.fernet:
self.fernet = Fernet(base64.urlsafe_b64encode(self.derived_key))
plaintext = self.fernet.decrypt(base64.urlsafe_b64encode(ciphertext))
plaintext = self.fernet.decrypt(base64.urlsafe_b64encode(bytes([RNS.Identity.FERNET_VERSION, 0x00, 0x00])+ciphertext))
return plaintext
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: Do we really need to do this? Or can we recover somehow?
self.teardown()
# RNS.log(traceback.format_exc(), RNS.LOG_ERROR)
# TODO: Think long about implications here
# self.teardown()
def sign(self, message):
@@ -799,68 +876,71 @@ class Link:
else:
return True
def disable_encryption(self):
"""
HAZARDOUS. This will downgrade the link to encryptionless. All
information over the link will be sent in plaintext. Never use
this in production applications. Should only be used for debugging
purposes, and will disappear in a future version.
If encryptionless links are not explicitly allowed in the users
configuration file, Reticulum will terminate itself along with the
client application and throw an error message to the user.
"""
if (RNS.Reticulum.should_allow_unencrypted()):
RNS.log("The link "+str(self)+" was downgraded to an encryptionless link", RNS.LOG_NOTICE)
self.__encryption_disabled = True
else:
RNS.log("Attempt to disable encryption on link, but encryptionless links are not allowed by config.", RNS.LOG_CRITICAL)
RNS.log("Shutting down Reticulum now!", RNS.LOG_CRITICAL)
RNS.panic()
def encryption_disabled(self):
return self.__encryption_disabled
def __str__(self):
return RNS.prettyhexrep(self.link_id)
class RequestReceipt():
"""
An instance of this class is returned by the ``request`` method of ``RNS.Link``
instances. It should never be instantiated manually. It provides methods to
check status, response time and response data when the request concludes.
"""
FAILED = 0x00
SENT = 0x01
DELIVERED = 0x02
READY = 0x03
RECEIVING = 0x03
READY = 0x04
def __init__(self, link, packet_receipt = None, resource = None, response_callback = None, failed_callback = None):
def __init__(self, link, packet_receipt = None, resource = None, response_callback = None, failed_callback = None, progress_callback = None, timeout = None):
self.packet_receipt = packet_receipt
self.resource = resource
self.started_at = None
if self.packet_receipt != None:
self.hash = packet_receipt.truncated_hash
self.packet_receipt.set_timeout_callback(self.request_timed_out)
self.started_at = time.time()
elif self.resource != None:
self.hash = resource.request_id
resource.set_callback(self.request_resource_concluded)
self.link = link
self.request_id = self.hash
self.link = link
self.request_id = self.hash
self.response = None
self.status = RequestReceipt.SENT
self.sent_at = time.time()
self.timeout = RNS.Packet.TIMEOUT
self.concluded_at = None
self.response = None
self.response_transfer_size = None
self.response_size = None
self.status = RequestReceipt.SENT
self.sent_at = time.time()
self.progress = 0
self.concluded_at = None
self.response_concluded_at = None
if timeout != None:
self.timeout = timeout
else:
raise ValueError("No timeout specified for request receipt")
self.callbacks = RequestReceiptCallbacks()
self.callbacks.response = response_callback
self.callbacks.failed = failed_callback
self.callbacks.progress = progress_callback
self.link.pending_requests.append(self)
def request_resource_concluded(self, resource):
if resource.status == RNS.Resource.COMPLETE:
RNS.log("Request "+RNS.prettyhexrep(self.request_id)+" successfully sent as resource.", RNS.LOG_DEBUG)
self.started_at = time.time()
self.status = RequestReceipt.DELIVERED
self.__resource_response_timeout = time.time()+self.timeout
response_timeout_thread = threading.Thread(target=self.__response_timeout_job)
response_timeout_thread.setDaemon(True)
response_timeout_thread.start()
else:
RNS.log("Sending request "+RNS.prettyhexrep(self.request_id)+" as resource failed with status: "+RNS.hexrep([resource.status]), RNS.LOG_DEBUG)
self.status = RequestReceipt.FAILED
@@ -868,7 +948,20 @@ class RequestReceipt():
self.link.pending_requests.remove(self)
if self.callbacks.failed != None:
self.callbacks.failed(self)
try:
self.callbacks.failed(self)
except Exception as e:
RNS.log("Error while executing request failed callback from "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
def __response_timeout_job(self):
while self.status == RequestReceipt.DELIVERED:
now = time.time()
if now > self.__resource_response_timeout:
self.request_timed_out(None)
time.sleep(0.1)
def request_timed_out(self, packet_receipt):
self.status = RequestReceipt.FAILED
@@ -876,23 +969,99 @@ class RequestReceipt():
self.link.pending_requests.remove(self)
if self.callbacks.failed != None:
self.callbacks.failed(self)
try:
self.callbacks.failed(self)
except Exception as e:
RNS.log("Error while executing request timed out callback from "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
def response_resource_progress(self, resource):
if not self.status == RequestReceipt.FAILED:
self.status = RequestReceipt.RECEIVING
if self.packet_receipt != None:
self.packet_receipt.status = RNS.PacketReceipt.DELIVERED
self.packet_receipt.proved = True
self.packet_receipt.concluded_at = time.time()
if self.packet_receipt.callbacks.delivery != None:
self.packet_receipt.callbacks.delivery(self.packet_receipt)
self.progress = resource.get_progress()
if self.callbacks.progress != None:
try:
self.callbacks.progress(self)
except Exception as e:
RNS.log("Error while executing response progress callback from "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
else:
resource.cancel()
def response_received(self, response):
self.response = response
if not self.status == RequestReceipt.FAILED:
self.progress = 1.0
self.response = response
self.status = RequestReceipt.READY
self.response_concluded_at = time.time()
if self.packet_receipt != None:
self.packet_receipt.status = RNS.PacketReceipt.DELIVERED
self.packet_receipt.proved = True
self.packet_receipt.concluded_at = time.time()
if self.packet_receipt.callbacks.delivery != None:
self.packet_receipt.callbacks.delivery(self)
if self.packet_receipt != None:
self.packet_receipt.status = RNS.PacketReceipt.DELIVERED
self.packet_receipt.proved = True
self.packet_receipt.concluded_at = time.time()
if self.packet_receipt.callbacks.delivery != None:
self.packet_receipt.callbacks.delivery(self.packet_receipt)
if self.callbacks.progress != None:
try:
self.callbacks.progress(self)
except Exception as e:
RNS.log("Error while executing response progress callback from "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
if self.callbacks.response != None:
try:
self.callbacks.response(self)
except Exception as e:
RNS.log("Error while executing response received callback from "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
def get_request_id(self):
"""
:returns: The request ID as *bytes*.
"""
return self.request_id
def get_status(self):
"""
:returns: The current status of the request, one of ``RNS.RequestReceipt.FAILED``, ``RNS.RequestReceipt.SENT``, ``RNS.RequestReceipt.DELIVERED``, ``RNS.RequestReceipt.READY``.
"""
return self.status
def get_progress(self):
"""
:returns: The progress of a response being received as a *float* between 0.0 and 1.0.
"""
return self.progress
def get_response(self):
"""
:returns: The response as *bytes* if it is ready, otherwise *None*.
"""
if self.status == RequestReceipt.READY:
return self.response
else:
return None
def get_response_time(self):
"""
:returns: The response time of the request in seconds.
"""
if self.status == RequestReceipt.READY:
return self.response_concluded_at - self.started_at
else:
return None
if self.callbacks.response != None:
self.callbacks.response(self)
class RequestReceiptCallbacks:
def __init__(self):
self.response = None
self.failed = None
self.failed = None
self.progress = None
+59 -40
View File
@@ -20,11 +20,6 @@ class Packet:
:param destination: A :ref:`RNS.Destination<api-destination>` instance to which the packet will be sent.
:param data: The data payload to be included in the packet as *bytes*.
:param create_receipt: Specifies whether a :ref:`RNS.PacketReceipt<api-packetreceipt>` should be created when instantiating the packet.
:param type: Internal use by :ref:`RNS.Transport<api-transport>`. Defaults to ``RNS.Packet.DATA``, and should not be specified.
:param context: Internal use by :ref:`RNS.Transport<api-transport>`. Ignore.
:param transport_type: Internal use by :ref:`RNS.Transport<api-transport>`. Ignore.
:param transport_id: Internal use by :ref:`RNS.Transport<api-transport>`. Ignore.
:param attached_interface: Internal use by :ref:`RNS.Transport<api-transport>`. Ignore.
"""
# Packet types
@@ -71,19 +66,16 @@ class Packet:
# With an MTU of 500, the maximum of data we can
# send in a single encrypted packet is given by
# the below calculation; 383 bytes.
ENCRYPTED_MDU = math.floor((RNS.Reticulum.MDU-RNS.Identity.AES_HMAC_OVERHEAD-RNS.Identity.KEYSIZE//16)/RNS.Identity.AES128_BLOCKSIZE)*RNS.Identity.AES128_BLOCKSIZE - 1
ENCRYPTED_MDU = math.floor((RNS.Reticulum.MDU-RNS.Identity.FERNET_OVERHEAD-RNS.Identity.KEYSIZE//16)/RNS.Identity.AES128_BLOCKSIZE)*RNS.Identity.AES128_BLOCKSIZE - 1
"""
The maximum size of the payload data in a single encrypted packet
"""
PLAIN_MDU = MDU
PLAIN_MDU = MDU
"""
The maximum size of the payload data in a single unencrypted packet
"""
# TODO: This should be calculated
# more intelligently
# Default packet timeout
TIMEOUT = 60
TIMEOUT_PER_HOP = RNS.Reticulum.DEFAULT_PER_HOP_TIMEOUT
def __init__(self, destination, data, packet_type = DATA, context = NONE, transport_type = RNS.Transport.BROADCAST, header_type = HEADER_1, transport_id = None, attached_interface = None, create_receipt = True):
if destination != None:
@@ -119,6 +111,8 @@ class Packet:
self.attached_interface = attached_interface
self.receiving_interface = None
self.rssi = None
self.snr = None
def get_packed_flags(self):
if self.context == Packet.LRPROOF:
@@ -191,27 +185,33 @@ class Packet:
def unpack(self):
self.flags = self.raw[0]
self.hops = self.raw[1]
try:
self.flags = self.raw[0]
self.hops = self.raw[1]
self.header_type = (self.flags & 0b11000000) >> 6
self.transport_type = (self.flags & 0b00110000) >> 4
self.destination_type = (self.flags & 0b00001100) >> 2
self.packet_type = (self.flags & 0b00000011)
self.header_type = (self.flags & 0b11000000) >> 6
self.transport_type = (self.flags & 0b00110000) >> 4
self.destination_type = (self.flags & 0b00001100) >> 2
self.packet_type = (self.flags & 0b00000011)
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:]
else:
self.transport_id = None
self.destination_hash = self.raw[2:12]
self.context = ord(self.raw[12:13])
self.data = self.raw[13:]
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:]
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.packed = False
self.update_hash()
self.packed = False
self.update_hash()
return True
except Exception as e:
RNS.log("Received malformed packet, dropping it. The contained exception was: "+str(e), RNS.LOG_EXTREME)
return False
def send(self):
"""
@@ -310,8 +310,8 @@ class PacketReceipt:
"""
The PacketReceipt class is used to receive notifications about
:ref:`RNS.Packet<api-packet>` instances sent over the network. Instances
of this class should never be created manually, but always returned
from a the *send()* method of a :ref:`RNS.Packet<api-packet>` instance.
of this class are never created manually, but always returned from
the *send()* method of a :ref:`RNS.Packet<api-packet>` instance.
"""
# Receipt status constants
FAILED = 0x00
@@ -329,12 +329,18 @@ class PacketReceipt:
self.truncated_hash = packet.getTruncatedHash()
self.sent = True
self.sent_at = time.time()
self.timeout = Packet.TIMEOUT
self.proved = False
self.status = PacketReceipt.SENT
self.destination = packet.destination
self.callbacks = PacketReceiptCallbacks()
self.concluded_at = None
self.proof_packet = None
if packet.destination.type == RNS.Destination.LINK:
self.timeout = packet.destination.rtt * packet.destination.traffic_timeout_factor
else:
self.timeout = Packet.TIMEOUT_PER_HOP * RNS.Transport.hops_to(self.destination.hash)
def get_status(self):
"""
@@ -345,12 +351,12 @@ class PacketReceipt:
# Validate a proof packet
def validate_proof_packet(self, proof_packet):
if hasattr(proof_packet, "link") and proof_packet.link:
return self.validate_link_proof(proof_packet.data, proof_packet.link)
return self.validate_link_proof(proof_packet.data, proof_packet.link, proof_packet)
else:
return self.validate_proof(proof_packet.data)
return self.validate_proof(proof_packet.data, proof_packet)
# Validate a raw proof for a link
def validate_link_proof(self, proof, link):
def validate_link_proof(self, proof, link, proof_packet=None):
# TODO: Hardcoded as explicit proofs for now
if True or len(proof) == PacketReceipt.EXPL_LENGTH:
# This is an explicit proof
@@ -362,6 +368,8 @@ class PacketReceipt:
self.status = PacketReceipt.DELIVERED
self.proved = True
self.concluded_at = time.time()
self.proof_packet = proof_packet
if self.callbacks.delivery != None:
self.callbacks.delivery(self)
return True
@@ -389,7 +397,7 @@ class PacketReceipt:
return False
# Validate a raw proof
def validate_proof(self, proof):
def validate_proof(self, proof, proof_packet=None):
if len(proof) == PacketReceipt.EXPL_LENGTH:
# This is an explicit proof
proof_hash = proof[:RNS.Identity.HASHLENGTH//8]
@@ -400,8 +408,14 @@ class PacketReceipt:
self.status = PacketReceipt.DELIVERED
self.proved = True
self.concluded_at = time.time()
self.proof_packet = proof_packet
if self.callbacks.delivery != None:
self.callbacks.delivery(self)
try:
self.callbacks.delivery(self)
except Exception as e:
RNS.log("Error while executing proof validated callback. The contained exception was: "+str(e), RNS.LOG_ERROR)
return True
else:
return False
@@ -418,8 +432,14 @@ class PacketReceipt:
self.status = PacketReceipt.DELIVERED
self.proved = True
self.concluded_at = time.time()
self.proof_packet = proof_packet
if self.callbacks.delivery != None:
self.callbacks.delivery(self)
try:
self.callbacks.delivery(self)
except Exception as e:
RNS.log("Error while executing proof validated callback. The contained exception was: "+str(e), RNS.LOG_ERROR)
return True
else:
return False
@@ -436,7 +456,7 @@ class PacketReceipt:
return (self.sent_at+self.timeout < time.time())
def check_timeout(self):
if self.is_timed_out():
if self.status == PacketReceipt.SENT and self.is_timed_out():
if self.timeout == -1:
self.status = PacketReceipt.CULLED
else:
@@ -448,7 +468,6 @@ class PacketReceipt:
thread = threading.Thread(target=self.callbacks.timeout, args=(self,))
thread.setDaemon(True)
thread.start()
#self.callbacks.timeout(self)
def set_timeout(self, timeout):
+99 -51
View File
@@ -15,14 +15,10 @@ class Resource:
:param data: The data to be transferred. Can be *bytes* or an open *file handle*. See the :ref:`Filetransfer Example<example-filetransfer>` for details.
:param link: The :ref:`RNS.Link<api-link>` instance on which to transfer the data.
:param advertise: Whether to automatically advertise the resource. Can be *True* or *False*.
:param auto_compress: Whether to auto-compress the resource. Can be *True* or *False*.
:param callback: A *callable* with the signature *callback(resource)*. Will be called when the resource transfer concludes.
:param progress_callback: A *callable* with the signature *callback(resource)*. Will be called whenever the resource transfer progress is updated.
:param segment_index: Internal use, ignore.
:param original_hash: Internal use, ignore.
:param is_request: Internal use, ignore.
:param is_response: Internal use, ignore.
:param advertise: Optional. Whether to automatically advertise the resource. Can be *True* or *False*.
:param auto_compress: Optional. Whether to auto-compress the resource. Can be *True* or *False*.
: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
@@ -36,26 +32,31 @@ class Resource:
# maximum size a resource should be, if
# it is to be handled within reasonable
# time constraint, even on small systems.
#
# A small system in this regard is
# defined as a Raspberry Pi, which should
# be able to compress, encrypt and hash-map
# the resource in about 10 seconds.
#
# This constant will be used when determining
# how to sequence the sending of large resources.
MAX_EFFICIENT_SIZE = 16 * 1024 * 1024
#
# Capped at 16777215 (0xFFFFFF) per segment to
# fit in 3 bytes in resource advertisements.
MAX_EFFICIENT_SIZE = 16 * 1024 * 1024 - 1
RESPONSE_MAX_GRACE_TIME = 10
# The maximum size to auto-compress with
# bz2 before sending.
AUTO_COMPRESS_MAX_SIZE = MAX_EFFICIENT_SIZE
# TODO: Should be allocated more
# intelligently
# TODO: Set higher
MAX_RETRIES = 5
SENDER_GRACE_TIME = 10
RETRY_GRACE_TIME = 0.25
PART_TIMEOUT_FACTOR = 4
PART_TIMEOUT_FACTOR_AFTER_RTT = 2
MAX_RETRIES = 5
SENDER_GRACE_TIME = 10
RETRY_GRACE_TIME = 0.25
WATCHDOG_MAX_SLEEP = 1
HASHMAP_IS_NOT_EXHAUSTED = 0x00
HASHMAP_IS_EXHAUSTED = 0xFF
@@ -72,11 +73,11 @@ class Resource:
CORRUPT = 0x08
@staticmethod
def accept(advertisement_packet, callback=None, progress_callback = None):
def accept(advertisement_packet, callback=None, progress_callback = None, request_id = None):
try:
adv = ResourceAdvertisement.unpack(advertisement_packet.plaintext)
resource = Resource(None, advertisement_packet.link)
resource = Resource(None, advertisement_packet.link, request_id = request_id)
resource.status = Resource.TRANSFERRING
resource.flags = adv.f
@@ -122,7 +123,10 @@ class Resource:
RNS.log("Accepting resource advertisement for "+RNS.prettyhexrep(resource.hash), RNS.LOG_DEBUG)
if resource.link.callbacks.resource_started != None:
resource.link.callbacks.resource_started(resource)
try:
resource.link.callbacks.resource_started(resource)
except Exception as e:
RNS.log("Error while executing resource started callback from "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
resource.hashmap_update(0, resource.hashmap_raw)
@@ -136,9 +140,11 @@ class Resource:
# Create a resource for transmission to a remote destination
# The data passed can be either a bytes-array or a file opened
# in binary read mode.
def __init__(self, data, link, advertise=True, auto_compress=True, callback=None, progress_callback=None, segment_index = 1, original_hash = None, request_id = None, is_response = False):
def __init__(self, data, link, advertise=True, auto_compress=True, callback=None, progress_callback=None, timeout = None, segment_index = 1, original_hash = None, request_id = None, is_response = False):
data_size = None
resource_data = None
self.assembly_lock = False
if hasattr(data, "read"):
data_size = os.stat(data.name).st_size
self.total_size = data_size
@@ -183,8 +189,8 @@ class Resource:
self.link = link
self.max_retries = Resource.MAX_RETRIES
self.retries_left = self.max_retries
self.default_timeout = self.link.default_timeout
self.timeout_factor = self.link.timeout_factor
self.timeout_factor = self.link.traffic_timeout_factor
self.part_timeout_factor = Resource.PART_TIMEOUT_FACTOR
self.sender_grace_time = Resource.SENDER_GRACE_TIME
self.hmu_retry_ok = False
self.watchdog_lock = False
@@ -194,8 +200,14 @@ class Resource:
self.request_id = request_id
self.is_response = is_response
self.req_hashlist = []
self.receiver_min_consecutive_height = 0
if timeout != None:
self.timeout = timeout
else:
self.timeout = self.link.rtt * self.link.traffic_timeout_factor
if data != None:
self.initiator = True
self.callback = callback
@@ -238,11 +250,8 @@ class Resource:
# make optimal use of packet MTU on an entire
# encrypted stream. The Resource instance will
# use it's underlying link directly to encrypt.
if not self.link.encryption_disabled():
self.data = self.link.encrypt(self.data)
self.encrypted = True
else:
self.encrypted = False
self.data = self.link.encrypt(self.data)
self.encrypted = True
self.size = len(self.data)
self.sent_parts = 0
@@ -317,10 +326,6 @@ class Resource:
self.request_next()
def get_map_hash(self, data):
# TODO: This will break if running unencrypted,
# uncompressed transfers on streams with long blocks
# of identical bytes. Doing so would be very silly
# anyways but maybe it should be handled gracefully.
return RNS.Identity.full_hash(data+self.random_hash)[:Resource.MAPHASH_LEN]
def advertise(self):
@@ -370,7 +375,7 @@ class Resource:
sleep_time = None
if self.status == Resource.ADVERTISED:
sleep_time = (self.adv_sent+self.default_timeout)-time.time()
sleep_time = (self.adv_sent+self.timeout)-time.time()
if sleep_time < 0:
if self.retries_left <= 0:
RNS.log("Resource transfer timeout after sending advertisement", RNS.LOG_DEBUG)
@@ -391,9 +396,16 @@ class Resource:
elif self.status == Resource.TRANSFERRING:
if not self.initiator:
rtt = self.link.rtt if self.rtt == None else self.rtt
sleep_time = self.last_activity + (rtt*self.timeout_factor) + Resource.RETRY_GRACE_TIME - time.time()
if self.rtt == None:
rtt = self.link.rtt
else:
rtt = self.rtt
window_remaining = self.outstanding_parts
sleep_time = self.last_activity + (rtt*(self.part_timeout_factor+window_remaining)) + Resource.RETRY_GRACE_TIME - time.time()
if sleep_time < 0:
if self.retries_left > 0:
RNS.log("Timed out waiting for parts, requesting retry", RNS.LOG_DEBUG)
@@ -443,7 +455,7 @@ class Resource:
self.cancel()
if sleep_time != None:
sleep(sleep_time)
sleep(min(sleep_time, Resource.WATCHDOG_MAX_SLEEP))
def assemble(self):
if not self.status == Resource.FAILED:
@@ -486,7 +498,10 @@ class Resource:
if self.segment_index == self.total_segments:
if self.callback != None:
self.data = open(self.storagepath, "rb")
self.callback(self)
try:
self.callback(self)
except Exception as e:
RNS.log("Error while executing resource assembled callback from "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
try:
self.data.close()
@@ -520,7 +535,10 @@ class Resource:
# If all segments were processed, we'll
# signal that the resource sending concluded
if self.callback != None:
self.callback(self)
try:
self.callback(self)
except Exception as e:
RNS.log("Error while executing resource concluded callback from "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
else:
# Otherwise we'll recursively create the
# next segment of the resource
@@ -542,11 +560,15 @@ 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 = rtt
self.rtt = self.link.rtt
self.watchdog_job()
elif self.rtt < rtt:
self.rtt = rtt
elif rtt < self.rtt:
self.rtt = max(self.rtt - self.rtt*0.05, rtt)
elif rtt > self.rtt:
self.rtt = min(self.rtt + self.rtt*0.05, rtt)
if not self.status == Resource.FAILED:
self.status = Resource.TRANSFERRING
@@ -571,14 +593,18 @@ class Resource:
self.consecutive_completed_height = cp
cp += 1
if self.__progress_callback != None:
try:
self.__progress_callback(self)
except Exception as e:
RNS.log("Error while executing progress callback from "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
i += 1
self.receiving_part = False
if self.__progress_callback != None:
self.__progress_callback(self)
if self.outstanding_parts == 0 and self.received_count == self.total_parts:
if self.received_count == self.total_parts and not self.assembly_lock:
self.assembly_lock = True
self.assemble()
elif self.outstanding_parts == 0:
# TODO: Figure out if there is a mathematically
@@ -696,6 +722,7 @@ class Resource:
if part_index % ResourceAdvertisement.HASHMAP_MAX_LEN != 0:
RNS.log("Resource sequencing error, cancelling transfer!", RNS.LOG_ERROR)
self.cancel()
return
else:
segment = part_index // ResourceAdvertisement.HASHMAP_MAX_LEN
@@ -722,7 +749,10 @@ class Resource:
self.status = Resource.AWAITING_PROOF
if self.__progress_callback != None:
self.__progress_callback(self)
try:
self.__progress_callback(self)
except Exception as e:
RNS.log("Error while executing progress callback from "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
def cancel(self):
"""
@@ -742,8 +772,11 @@ class Resource:
self.link.cancel_incoming_resource(self)
if self.callback != None:
self.link.resource_concluded(self)
self.callback(self)
try:
self.link.resource_concluded(self)
self.callback(self)
except Exception as e:
RNS.log("Error while executing callbacks on resource cancel from "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
def set_callback(self, callback):
self.callback = callback
@@ -751,7 +784,7 @@ class Resource:
def progress_callback(self, callback):
self.__progress_callback = callback
def progress(self):
def get_progress(self):
"""
:returns: The current progress of the resource transfer as a *float* between 0.0 and 1.0.
"""
@@ -772,13 +805,16 @@ class Resource:
return progress
def __str__(self):
return RNS.prettyhexrep(self.hash)+str(self.link)
return "<"+RNS.hexrep(self.hash)+"/"+RNS.hexrep(self.link.link_id)+">"
class ResourceAdvertisement:
HASHMAP_MAX_LEN = 70
OVERHEAD = 128
HASHMAP_MAX_LEN = math.floor((RNS.Link.MDU-OVERHEAD)/Resource.MAPHASH_LEN)
COLLISION_GUARD_SIZE = 2*Resource.WINDOW_MAX+HASHMAP_MAX_LEN
assert HASHMAP_MAX_LEN > 0, "The configured MTU is too small to include any map hashes in resource advertisments"
@staticmethod
def is_request(advertisement_packet):
adv = ResourceAdvertisement.unpack(advertisement_packet.plaintext)
@@ -804,6 +840,18 @@ class ResourceAdvertisement:
return adv.q
@staticmethod
def get_transfer_size(advertisement_packet):
adv = ResourceAdvertisement.unpack(advertisement_packet.plaintext)
return adv.t
@staticmethod
def get_size(advertisement_packet):
adv = ResourceAdvertisement.unpack(advertisement_packet.plaintext)
return adv.d
def __init__(self, resource=None, request_id=None, is_response=False):
if resource != None:
self.t = resource.size # Transfer size
+363 -324
View File
@@ -1,7 +1,20 @@
from .Interfaces import *
import configparser
from .vendor.platformutils import get_platform
if get_platform() == "android":
from .Interfaces import Interface
from .Interfaces import LocalInterface
from .Interfaces import AutoInterface
from .Interfaces import TCPInterface
from .Interfaces import UDPInterface
from .Interfaces import I2PInterface
else:
from .Interfaces import *
from .vendor.configobj import ConfigObj
import RNS
import configparser
import multiprocessing.connection
import signal
import threading
import atexit
import struct
import array
@@ -36,15 +49,34 @@ class Reticulum:
other programs to use on demand.
"""
# The default RNS MTU is 500 bytes. This number has been chosen as
# a balance between compatibility with existing hardware devices
# on one hand, and the ability to use sufficiently high cryptographic
# key sizes on the other. In custom RNS network implementations, it
# is possible to raise this value, but doing so will completely break
# compatibility with all other RNS networks. An identical MTU is a
# prerequisite for peers to communicate in the same network.
# Future minimum will probably be locked in at 244 bytes to support
# networks with segments of different MTUs. Absolute minimum is 211.
MTU = 500
HEADER_MAXSIZE = 23
"""
The MTU that Reticulum adheres to, and will expect other peers to
adhere to. By default, the MTU is 500 bytes. In custom RNS network
implementations, it is possible to change this value, but doing so will
completely break compatibility with all other RNS networks. An identical
MTU is a prerequisite for peers to communicate in the same network.
Unless you really know what you are doing, the MTU should be left at
the default value.
"""
# TODO: To reach the 300bps level without unreasonably impacting
# performance on faster links, we need a mechanism for setting
# this value more intelligently. One option could be inferring it
# from interface speed, but a better general approach would most
# probably be to let Reticulum somehow continously build a map of
# per-hop latencies and use this map for the timeout calculation.
DEFAULT_PER_HOP_TIMEOUT = 5
# Length of truncated hashes in bits.
TRUNCATED_HASHLENGTH = 80
HEADER_MINSIZE = 2+1+(TRUNCATED_HASHLENGTH//8)*1
HEADER_MAXSIZE = 2+1+(TRUNCATED_HASHLENGTH//8)*2
MDU = MTU - HEADER_MAXSIZE
router = None
@@ -67,7 +99,19 @@ class Reticulum:
RNS.Transport.exit_handler()
RNS.Identity.exit_handler()
def __init__(self,configdir=None):
@staticmethod
def sigint_handler(signal, frame):
RNS.Transport.detach_interfaces()
RNS.exit()
@staticmethod
def sigterm_handler(signal, frame):
RNS.Transport.detach_interfaces()
RNS.exit()
def __init__(self,configdir=None, loglevel=None):
"""
Initialises and starts a Reticulum instance. This must be
done before any other operations, and Reticulum will not
@@ -76,6 +120,8 @@ class Reticulum:
:param configdir: Full path to a Reticulum configuration directory.
"""
RNS.vendor.platformutils.platform_checks()
if configdir != None:
Reticulum.configdir = configdir
@@ -84,12 +130,24 @@ class Reticulum:
Reticulum.cachepath = Reticulum.configdir+"/storage/cache"
Reticulum.resourcepath = Reticulum.configdir+"/storage/resources"
Reticulum.__allow_unencrypted = False
Reticulum.__transport_enabled = False
Reticulum.__use_implicit_proof = True
Reticulum.panic_on_interface_error = False
self.local_interface_port = 37428
self.share_instance = True
self.local_control_port = 37429
self.share_instance = True
self.rpc_listener = None
self.requested_loglevel = loglevel
if self.requested_loglevel != None:
if self.requested_loglevel > RNS.LOG_EXTREME:
self.requested_loglevel = RNS.LOG_EXTREME
if self.requested_loglevel < RNS.LOG_CRITICAL:
self.requested_loglevel = RNS.LOG_CRITICAL
RNS.loglevel = self.requested_loglevel
self.is_shared_instance = False
self.is_connected_to_shared_instance = False
@@ -107,7 +165,6 @@ class Reticulum:
if os.path.isfile(self.configpath):
try:
self.config = ConfigObj(self.configpath)
RNS.log("Configuration loaded from "+self.configpath)
except Exception as e:
RNS.log("Could not parse the configuration at "+self.configpath, RNS.LOG_ERROR)
RNS.log("Check your configuration file for errors!", RNS.LOG_ERROR)
@@ -115,16 +172,29 @@ class Reticulum:
else:
RNS.log("Could not load config file, creating default configuration file...")
self.__create_default_config()
RNS.log("Default config file created. Make any necessary changes in "+Reticulum.configdir+"/config and start Reticulum again.")
RNS.log("Exiting now!")
exit(1)
RNS.log("Default config file created. Make any necessary changes in "+Reticulum.configdir+"/config and restart Reticulum if needed.")
import time
time.sleep(1.5)
self.__apply_config()
RNS.log("Configuration loaded from "+self.configpath, RNS.LOG_VERBOSE)
RNS.Identity.load_known_destinations()
RNS.Transport.start(self)
self.rpc_addr = ("127.0.0.1", self.local_control_port)
self.rpc_key = RNS.Identity.full_hash(RNS.Transport.identity.get_private_key())
if self.is_shared_instance:
self.rpc_listener = multiprocessing.connection.Listener(self.rpc_addr, authkey=self.rpc_key)
thread = threading.Thread(target=self.rpc_loop)
thread.setDaemon(True)
thread.start()
atexit.register(Reticulum.exit_handler)
signal.signal(signal.SIGINT, Reticulum.sigint_handler)
signal.signal(signal.SIGTERM, Reticulum.sigterm_handler)
def __start_local_interface(self):
if self.share_instance:
@@ -135,6 +205,7 @@ class Reticulum:
)
interface.OUT = True
RNS.Transport.interfaces.append(interface)
self.is_shared_instance = True
RNS.log("Started shared instance interface: "+str(interface), RNS.LOG_DEBUG)
except Exception as e:
@@ -149,6 +220,7 @@ class Reticulum:
self.is_shared_instance = False
self.is_standalone_instance = False
self.is_connected_to_shared_instance = True
Reticulum.__transport_enabled = False
RNS.log("Connected to local shared instance via: "+str(interface), RNS.LOG_DEBUG)
except Exception as e:
RNS.log("Local shared instance appears to be running, but it could not be connected", RNS.LOG_ERROR)
@@ -165,7 +237,7 @@ class Reticulum:
if "logging" in self.config:
for option in self.config["logging"]:
value = self.config["logging"][option]
if option == "loglevel":
if option == "loglevel" and self.requested_loglevel == None:
RNS.loglevel = int(value)
if RNS.loglevel < 0:
RNS.loglevel = 0
@@ -181,41 +253,79 @@ class Reticulum:
if option == "shared_instance_port":
value = int(self.config["reticulum"][option])
self.local_interface_port = value
if option == "instance_control_port":
value = int(self.config["reticulum"][option])
self.local_control_port = value
if option == "enable_transport":
v = self.config["reticulum"].as_bool(option)
if v == True:
Reticulum.__transport_enabled = True
if option == "panic_on_interface_error":
v = self.config["reticulum"].as_bool(option)
if v == True:
Reticulum.panic_on_interface_error = True
if option == "use_implicit_proof":
v = self.config["reticulum"].as_bool(option)
if v == True:
Reticulum.__use_implicit_proof = True
if v == False:
Reticulum.__use_implicit_proof = False
if option == "allow_unencrypted":
v = self.config["reticulum"].as_bool(option)
if v == True:
RNS.log("", RNS.LOG_CRITICAL)
RNS.log("! ! ! ! ! ! ! ! !", RNS.LOG_CRITICAL)
RNS.log("", RNS.LOG_CRITICAL)
RNS.log("Danger! Encryptionless links have been allowed in the config file!", RNS.LOG_CRITICAL)
RNS.log("Beware of the consequences! Any data sent over a link can potentially be intercepted,", RNS.LOG_CRITICAL)
RNS.log("read and modified! If you are not absolutely sure that you want this,", RNS.LOG_CRITICAL)
RNS.log("you should exit Reticulum NOW and change your config file!", RNS.LOG_CRITICAL)
RNS.log("", RNS.LOG_CRITICAL)
RNS.log("! ! ! ! ! ! ! ! !", RNS.LOG_CRITICAL)
RNS.log("", RNS.LOG_CRITICAL)
Reticulum.__allow_unencrypted = True
self.__start_local_interface()
if self.is_shared_instance or self.is_standalone_instance:
RNS.log("Bringing up system interfaces...", RNS.LOG_DEBUG)
interface_names = []
for name in self.config["interfaces"]:
if not name in interface_names:
c = self.config["interfaces"][name]
interface_mode = Interface.Interface.MODE_FULL
if "mode" in c:
if c["mode"] == "full":
interface_mode = Interface.Interface.MODE_FULL
elif c["mode"] == "accesspoint" or c["mode"] == "ap":
interface_mode = Interface.Interface.MODE_ACCESS_POINT
elif c["mode"] == "pointtopoint" or c["mode"] == "ptp":
interface_mode = Interface.Interface.MODE_POINT_TO_POINT
try:
if ("interface_enabled" in c) and c.as_bool("interface_enabled") == True:
if c["type"] == "AutoInterface":
if not RNS.vendor.platformutils.is_windows():
group_id = c["group_id"] if "group_id" in c else None
discovery_scope = c["discovery_scope"] if "discovery_scope" in c else None
discovery_port = int(c["discovery_port"]) if "discovery_port" in c else None
data_port = int(c["data_port"]) if "data_port" in c else None
allowed_interfaces = c.as_list("devices") if "devices" in c else None
ignored_interfaces = c.as_list("ignored_devices") if "ignored_devices" in c else None
interface = AutoInterface.AutoInterface(
RNS.Transport,
name,
group_id,
discovery_scope,
discovery_port,
data_port,
allowed_interfaces,
ignored_interfaces
)
if "outgoing" in c and c.as_bool("outgoing") == False:
interface.OUT = False
else:
interface.OUT = True
interface.mode = interface_mode
RNS.Transport.interfaces.append(interface)
else:
RNS.log("AutoInterface is not currently supported on Windows, disabling interface.", RNS.LOG_ERROR);
RNS.log("Please remove this AutoInterface instance from your configuration file.", RNS.LOG_ERROR);
RNS.log("You will have to manually configure other interfaces for connectivity.", RNS.LOG_ERROR);
if c["type"] == "UDPInterface":
device = c["device"] if "device" in c else None
port = int(c["port"]) if "port" in c else None
@@ -240,10 +350,12 @@ class Reticulum:
forward_port
)
if "outgoing" in c and c.as_bool("outgoing") == True:
interface.OUT = True
else:
if "outgoing" in c and c.as_bool("outgoing") == False:
interface.OUT = False
else:
interface.OUT = True
interface.mode = interface_mode
RNS.Transport.interfaces.append(interface)
@@ -253,6 +365,7 @@ class Reticulum:
port = int(c["port"]) if "port" in c else None
listen_ip = c["listen_ip"] if "listen_ip" in c else None
listen_port = int(c["listen_port"]) if "listen_port" in c else None
i2p_tunneled = c.as_bool("i2p_tunneled") if "i2p_tunneled" in c else False
if port != None:
listen_port = port
@@ -262,29 +375,76 @@ class Reticulum:
name,
device,
listen_ip,
listen_port
listen_port,
i2p_tunneled
)
if "outgoing" in c and c.as_bool("outgoing") == True:
interface.OUT = True
else:
if "outgoing" in c and c.as_bool("outgoing") == False:
interface.OUT = False
else:
interface.OUT = True
if interface_mode != Interface.Interface.MODE_FULL:
RNS.log(str(interface)+" does not support Access Point mode, reverting to default mode: Full", RNS.LOG_WARNING)
interface_mode = Interface.Interface.MODE_FULL
interface.mode = interface_mode
RNS.Transport.interfaces.append(interface)
if c["type"] == "TCPClientInterface":
kiss_framing = False
if "kiss_framing" in c and c.as_bool("kiss_framing") == True:
kiss_framing = True
i2p_tunneled = c.as_bool("i2p_tunneled") if "i2p_tunneled" in c else False
interface = TCPInterface.TCPClientInterface(
RNS.Transport,
name,
c["target_host"],
int(c["target_port"])
int(c["target_port"]),
kiss_framing = kiss_framing,
i2p_tunneled = i2p_tunneled
)
if "outgoing" in c and c.as_bool("outgoing") == True:
interface.OUT = True
else:
if "outgoing" in c and c.as_bool("outgoing") == False:
interface.OUT = False
else:
interface.OUT = True
if interface_mode != Interface.Interface.MODE_FULL:
RNS.log(str(interface)+" does not support Access Point mode, reverting to default mode: Full", RNS.LOG_WARNING)
interface_mode = Interface.Interface.MODE_FULL
interface.mode = interface_mode
RNS.Transport.interfaces.append(interface)
if c["type"] == "I2PInterface":
i2p_peers = c.as_list("peers") if "peers" in c else None
connectable = c.as_bool("connectable") if "connectable" in c else False
interface = I2PInterface.I2PInterface(
RNS.Transport,
name,
Reticulum.storagepath,
i2p_peers,
connectable = connectable,
)
if "outgoing" in c and c.as_bool("outgoing") == False:
interface.OUT = False
else:
interface.OUT = True
if interface_mode != Interface.Interface.MODE_FULL:
RNS.log(str(interface)+" does not support Access Point mode, reverting to default mode: Full", RNS.LOG_WARNING)
interface_mode = Interface.Interface.MODE_FULL
interface.mode = interface_mode
RNS.Transport.interfaces.append(interface)
@@ -309,10 +469,12 @@ class Reticulum:
stopbits
)
if "outgoing" in c and c["outgoing"].lower() == "true":
interface.OUT = True
else:
if "outgoing" in c and c.as_bool("outgoing") == False:
interface.OUT = False
else:
interface.OUT = True
interface.mode = interface_mode
RNS.Transport.interfaces.append(interface)
@@ -350,10 +512,12 @@ class Reticulum:
beacon_data
)
if "outgoing" in c and c["outgoing"].lower() == "true":
interface.OUT = True
else:
if "outgoing" in c and c.as_bool("outgoing") == False:
interface.OUT = False
else:
interface.OUT = True
interface.mode = interface_mode
RNS.Transport.interfaces.append(interface)
@@ -392,10 +556,12 @@ class Reticulum:
flow_control
)
if "outgoing" in c and c["outgoing"].lower() == "true":
interface.OUT = True
else:
if "outgoing" in c and c.as_bool("outgoing") == False:
interface.OUT = False
else:
interface.OUT = True
interface.mode = interface_mode
RNS.Transport.interfaces.append(interface)
@@ -428,14 +594,16 @@ class Reticulum:
id_callsign = id_callsign
)
if "outgoing" in c and c["outgoing"].lower() == "true":
interface.OUT = True
else:
if "outgoing" in c and c.as_bool("outgoing") == False:
interface.OUT = False
else:
interface.OUT = True
interface.mode = interface_mode
RNS.Transport.interfaces.append(interface)
else:
RNS.log("Skipping disabled interface \""+name+"\"", RNS.LOG_NOTICE)
RNS.log("Skipping disabled interface \""+name+"\"", RNS.LOG_DEBUG)
except Exception as e:
RNS.log("The interface \""+name+"\" could not be created. Check your configuration file for errors!", RNS.LOG_ERROR)
@@ -444,6 +612,9 @@ class Reticulum:
else:
RNS.log("The interface name \""+name+"\" was already used. Check your configuration file for errors!", RNS.LOG_ERROR)
RNS.panic()
RNS.log("System interfaces are ready", RNS.LOG_DEBUG)
def __create_default_config(self):
@@ -453,17 +624,113 @@ class Reticulum:
if not os.path.isdir(Reticulum.configdir):
os.makedirs(Reticulum.configdir)
self.config.write()
self.__apply_config()
@staticmethod
def should_allow_unencrypted():
"""
Returns whether unencrypted links are allowed by the
current configuration.
def rpc_loop(self):
while True:
try:
rpc_connection = self.rpc_listener.accept()
call = rpc_connection.recv()
if "get" in call:
path = call["get"]
if path == "interface_stats":
rpc_connection.send(self.get_interface_stats())
if path == "next_hop_if_name":
rpc_connection.send(self.get_next_hop_if_name(call["destination_hash"]))
if path == "next_hop":
rpc_connection.send(self.get_next_hop(call["destination_hash"]))
if path == "packet_rssi":
rpc_connection.send(self.get_packet_rssi(call["packet_hash"]))
if path == "packet_snr":
rpc_connection.send(self.get_packet_snr(call["packet_hash"]))
rpc_connection.close()
except Exception as e:
RNS.log("An error ocurred while handling RPC call from local client: "+str(e), RNS.LOG_ERROR)
def get_interface_stats(self):
if self.is_connected_to_shared_instance:
rpc_connection = multiprocessing.connection.Client(self.rpc_addr, authkey=self.rpc_key)
rpc_connection.send({"get": "interface_stats"})
response = rpc_connection.recv()
return response
else:
stats = []
for interface in RNS.Transport.interfaces:
ifstats = {}
if hasattr(interface, "clients"):
ifstats["clients"] = interface.clients
else:
ifstats["clients"] = None
if hasattr(interface, "b32"):
if interface.b32 != None:
ifstats["i2p_b32"] = interface.b32+".b32.i2p"
else:
ifstats["i2p_b32"] = None
ifstats["name"] = str(interface)
ifstats["rxb"] = interface.rxb
ifstats["txb"] = interface.txb
ifstats["status"] = interface.online
stats.append(ifstats)
return stats
def get_next_hop_if_name(self, destination):
if self.is_connected_to_shared_instance:
rpc_connection = multiprocessing.connection.Client(self.rpc_addr, authkey=self.rpc_key)
rpc_connection.send({"get": "next_hop_if_name", "destination_hash": destination})
response = rpc_connection.recv()
return response
else:
return str(RNS.Transport.next_hop_interface(destination))
def get_next_hop(self, destination):
if self.is_connected_to_shared_instance:
rpc_connection = multiprocessing.connection.Client(self.rpc_addr, authkey=self.rpc_key)
rpc_connection.send({"get": "next_hop", "destination_hash": destination})
response = rpc_connection.recv()
return response
else:
return RNS.Transport.next_hop(destination)
def get_packet_rssi(self, packet_hash):
if self.is_connected_to_shared_instance:
rpc_connection = multiprocessing.connection.Client(self.rpc_addr, authkey=self.rpc_key)
rpc_connection.send({"get": "packet_rssi", "packet_hash": packet_hash})
response = rpc_connection.recv()
return response
else:
for entry in RNS.Transport.local_client_rssi_cache:
if entry[0] == packet_hash:
return entry[1]
return None
def get_packet_snr(self, packet_hash):
if self.is_connected_to_shared_instance:
rpc_connection = multiprocessing.connection.Client(self.rpc_addr, authkey=self.rpc_key)
rpc_connection.send({"get": "packet_snr", "packet_hash": packet_hash})
response = rpc_connection.recv()
return response
else:
for entry in RNS.Transport.local_client_snr_cache:
if entry[0] == packet_hash:
return entry[1]
return None
:returns: True if the current running configuration allows downgrading links to plaintext. False if not.
"""
return Reticulum.__allow_unencrypted
@staticmethod
def should_use_implicit_proof():
@@ -493,16 +760,14 @@ __default_rns_config__ = '''# This is the default Reticulum config file.
# You should probably edit it to include any additional,
# interfaces and settings you might need.
# Only the most basic options are included in this default
# configuration. To see a more verbose, and much longer,
# configuration example, you can run the command:
# rnsd --exampleconfig
[reticulum]
# Don't allow unencrypted links by default.
# If you REALLY need to allow unencrypted links, for example
# for debug or regulatory purposes, this can be set to true.
# This directive is optional and can be removed for brevity.
allow_unencrypted = False
# If you enable Transport, your system will route traffic
# for other peers, pass announces and serve path requests.
# This should be done for systems that are suited to act
@@ -526,11 +791,22 @@ share_instance = Yes
# If you want to run multiple *different* shared instances
# on the same system, you will need to specify a different
# shared instance port for each. The default is given below,
# and again, this option is optional and can be left out.
# on the same system, you will need to specify different
# shared instance ports for each. The defaults are given
# below, and again, these options can be left out if you
# don't need them.
shared_instance_port = 37428
instance_control_port = 37429
# You can configure Reticulum to panic and forcibly close
# if an unrecoverable interface error occurs, such as the
# hardware device for an interface disappearing. This is
# an optional directive, and can be left out for brevity.
# This behaviour is disabled by default.
panic_on_interface_error = No
[logging]
@@ -556,252 +832,15 @@ loglevel = 4
[interfaces]
# This interface enables communication with other
# local Reticulum nodes over UDP. You can modify it
# to suit your needs or turn it off completely.
# As a minimum, you should probably specify the
# network device you want to communicate on, such
# as eth0 or wlan0.
[[Default UDP Interface]]
type = UDPInterface
# link-local Reticulum nodes over UDP. It does not
# need any functional IP infrastructure like routers
# or DHCP servers, but will require that at least link-
# local IPv6 is enabled in your operating system, which
# should be enabled by default in almost any OS. See
# the Reticulum Manual for more configuration options.
[[Default Interface]]
type = AutoInterface
interface_enabled = True
outgoing = True
device = eth0
port = 4242
# Assuming the eth0 device has the address
# 10.55.0.72/24, the above configuration would
# be equivalent to the following manual setup.
# Note that we are both listening and forwarding
# to the network segments broadcast address.
# listen_ip = 10.55.0.255
# listen_port = 4242
# forward_ip = 10.55.0.255
# forward_port = 4242
# You can of course also communicate only with
# a single IP address
# listen_ip = 10.55.0.15
# listen_port = 4242
# forward_ip = 10.55.0.16
# forward_port = 4242
# This example demonstrates a TCP server interface.
# It will listen for incoming connections on the
# specified IP address and port number.
[[TCP Server Interface]]
type = TCPServerInterface
interface_enabled = False
outgoing = True
# This configuration will listen on all IP
# interfaces on port 4242
listen_ip = 0.0.0.0
listen_port = 4242
# Alternatively you can bind to a specific IP
# listen_ip = 10.0.0.88
# listen_port = 4242
# Or a specific network device
# device = eth0
# port = 4242
# To connect to a TCP server interface, you would
# naturally use the TCP client interface. Here's
# an example. The target_host can either be an IP
# address or a hostname
[[TCP Client Interface]]
type = TCPClientInterface
interface_enabled = False
outgoing = True
target_host = 127.0.0.1
target_port = 4242
# Here's an example of how to add a LoRa interface
# using the RNode LoRa transceiver.
[[RNode LoRa Interface]]
type = RNodeInterface
# Enable interface if you want use it!
interface_enabled = False
# Allow transmit on interface. Setting
# this to false will create a listen-
# only interface.
outgoing = true
# Serial port for the device
port = /dev/ttyUSB0
# Set frequency to 867.2 MHz
frequency = 867200000
# Set LoRa bandwidth to 125 KHz
bandwidth = 125000
# Set TX power to 7 dBm (5 mW)
txpower = 7
# Select spreading factor 8. Valid
# range is 7 through 12, with 7
# being the fastest and 12 having
# the longest range.
spreadingfactor = 8
# Select coding rate 5. Valid range
# is 5 throough 8, with 5 being the
# fastest, and 8 the longest range.
codingrate = 5
# You can configure the RNode to send
# out identification on the channel with
# a set interval by configuring the
# following two parameters. The trans-
# ceiver will only ID if the set
# interval has elapsed since it's last
# actual transmission. The interval is
# configured in seconds.
# This option is commented out and not
# used by default.
# id_callsign = MYCALL-0
# id_interval = 600
# For certain homebrew RNode interfaces
# with low amounts of RAM, using packet
# flow control can be useful. By default
# it is disabled.
flow_control = False
# An example KISS modem interface. Useful for running
# Reticulum over packet radio hardware.
[[Packet Radio KISS Interface]]
type = KISSInterface
# Enable interface if you want use it!
interface_enabled = False
# Allow transmit on interface.
outgoing = true
# Serial port for the device
port = /dev/ttyUSB1
# Set the serial baud-rate and other
# configuration parameters.
speed = 115200
databits = 8
parity = none
stopbits = 1
# Set the modem preamble. A 150ms
# preamble should be a reasonable
# default, but may need to be
# increased for radios with slow-
# opening squelch and long TX/RX
# turnaround
preamble = 150
# Set the modem TX tail. In most
# cases this should be kept as low
# as possible to not waste airtime.
txtail = 10
# Configure CDMA parameters. These
# settings are reasonable defaults.
persistence = 200
slottime = 20
# You can configure the interface to send
# out identification on the channel with
# a set interval by configuring the
# following two parameters. The KISS
# interface will only ID if the set
# interval has elapsed since it's last
# actual transmission. The interval is
# configured in seconds.
# This option is commented out and not
# used by default.
# id_callsign = MYCALL-0
# id_interval = 600
# Whether to use KISS flow-control.
# This is useful for modems that have
# a small internal packet buffer, but
# support packet flow control instead.
flow_control = false
# If you're using Reticulum on amateur radio spectrum,
# you might want to use the AX.25 KISS interface. This
# way, Reticulum will automatically encapsulate it's
# traffic in AX.25 and also identify your stations
# transmissions with your callsign and SSID.
#
# Only do this if you really need to! Reticulum doesn't
# need the AX.25 layer for anything, and it incurs extra
# overhead on every packet to encapsulate in AX.25.
#
# A more efficient way is to use the plain KISS interface
# with the beaconing functionality described above.
[[Packet Radio AX.25 KISS Interface]]
type = AX25KISSInterface
# Set the station callsign and SSID
callsign = NO1CLL
ssid = 0
# Enable interface if you want use it!
interface_enabled = False
# Allow transmit on interface.
outgoing = true
# Serial port for the device
port = /dev/ttyUSB2
# Set the serial baud-rate and other
# configuration parameters.
speed = 115200
databits = 8
parity = none
stopbits = 1
# Whether to use KISS flow-control.
# This is useful for modems with a
# small internal packet buffer.
flow_control = false
# Set the modem preamble. A 150ms
# preamble should be a reasonable
# default, but may need to be
# increased for radios with slow-
# opening squelch and long TX/RX
# turnaround
preamble = 150
# Set the modem TX tail. In most
# cases this should be kept as low
# as possible to not waste airtime.
txtail = 10
# Configure CDMA parameters. These
# settings are reasonable defaults.
persistence = 200
slottime = 20
'''.splitlines()
'''.splitlines()
+574 -100
View File
@@ -9,6 +9,10 @@ from time import sleep
from .vendor import umsgpack as umsgpack
class Transport:
"""
Through static methods of this class you can interact with the
Transport system of Reticulum.
"""
# Constants
BROADCAST = 0x00;
TRANSPORT = 0x01;
@@ -22,41 +26,46 @@ class Transport:
APP_NAME = "rnstransport"
PATHFINDER_M = 18 # Max hops
PATHFINDER_C = 2.0 # Decay constant
PATHFINDER_R = 1 # Retransmit retries
PATHFINDER_T = 10 # Retry grace period
PATHFINDER_RW = 10 # Random window for announce rebroadcast
PATHFINDER_E = 60*15 # Path expiration in seconds
PATHFINDER_M = 128 # Max hops
"""
Maximum amount of hops that Reticulum will transport a packet.
"""
PATHFINDER_C = 2.0 # Decay constant
PATHFINDER_R = 1 # Retransmit retries
PATHFINDER_T = 10 # Retry grace period
PATHFINDER_RW = 10 # Random window for announce rebroadcast
PATHFINDER_E = 60*60*24*7 # Path expiration in seconds
AP_PATH_TIME = 60*60*24 # Expiration for Access Point paths
# TODO: Calculate an optimal number for this in
# various situations
LOCAL_REBROADCASTS_MAX = 2 # How many local rebroadcasts of an announce is allowed
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_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 = 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
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
MAX_RECEIPTS = 1024 # Maximum number of receipts to keep track of
interfaces = [] # All active interfaces
destinations = [] # All active destinations
pending_links = [] # Links that are being established
active_links = [] # Links that are active
packet_hashlist = [] # A list of packet hashes for duplicate detection
receipts = [] # Receipts of all outgoing packets for proof processing
interfaces = [] # All active interfaces
destinations = [] # All active destinations
pending_links = [] # Links that are being established
active_links = [] # Links that are active
packet_hashlist = [] # A list of packet hashes for duplicate detection
receipts = [] # Receipts of all outgoing packets for proof processing
# TODO: "destination_table" should really be renamed to "path_table"
# Notes on memory usage: 1 megabyte of memory can store approximately
# 55.100 path table entries or approximately 22.300 link table entries.
announce_table = {} # A table for storing announces currently waiting to be retransmitted
destination_table = {} # A lookup table containing the next hop to a given destination
reverse_table = {} # A lookup table for storing packet hashes used to return proofs and replies
link_table = {} # A lookup table containing hops for links
held_announces = {} # A table containing temporarily held announce-table entries
announce_handlers = [] # A table storing externally registered announce handlers
announce_table = {} # A table for storing announces currently waiting to be retransmitted
destination_table = {} # A lookup table containing the next hop to a given destination
reverse_table = {} # A lookup table for storing packet hashes used to return proofs and replies
link_table = {} # A lookup table containing hops for links
held_announces = {} # A table containing temporarily held announce-table entries
announce_handlers = [] # A table storing externally registered announce handlers
tunnels = {} # A table storing tunnels to other transport instances
# Transport control destinations are used
# for control purposes like path requests
@@ -68,6 +77,12 @@ class Transport:
# Reticulum instance
local_client_interfaces = []
local_client_rssi_cache = []
local_client_snr_cache = []
LOCAL_CLIENT_CACHE_MAXSIZE = 512
pending_local_path_requests = {}
jobs_locked = False
jobs_running = False
job_interval = 0.250
@@ -112,12 +127,19 @@ class Transport:
Transport.control_destinations.append(Transport.path_request_destination)
Transport.control_hashes.append(Transport.path_request_destination.hash)
Transport.tunnel_synthesize_destination = RNS.Destination(None, RNS.Destination.IN, RNS.Destination.PLAIN, Transport.APP_NAME, "tunnel", "synthesize")
Transport.tunnel_synthesize_destination.set_packet_callback(Transport.tunnel_synthesize_handler)
Transport.control_destinations.append(Transport.tunnel_synthesize_handler)
Transport.control_hashes.append(Transport.tunnel_synthesize_destination.hash)
thread = threading.Thread(target=Transport.jobloop)
thread.setDaemon(True)
thread.start()
if RNS.Reticulum.transport_enabled():
destination_table_path = RNS.Reticulum.storagepath+"/destination_table"
tunnel_table_path = RNS.Reticulum.storagepath+"/tunnels"
if os.path.isfile(destination_table_path) and not Transport.owner.is_connected_to_shared_instance:
serialised_destinations = []
try:
@@ -161,7 +183,63 @@ class Transport:
except Exception as e:
RNS.log("Could not load destination table from storage, the contained exception was: "+str(e), RNS.LOG_ERROR)
RNS.log("Transport instance "+str(Transport.identity)+" started")
if os.path.isfile(tunnel_table_path) and not Transport.owner.is_connected_to_shared_instance:
serialised_tunnels = []
try:
file = open(tunnel_table_path, "rb")
serialised_tunnels = umsgpack.unpackb(file.read())
file.close()
for serialised_tunnel in serialised_tunnels:
tunnel_id = serialised_tunnel[0]
interface_hash = serialised_tunnel[1]
serialised_paths = serialised_tunnel[2]
expires = serialised_tunnel[3]
tunnel_paths = {}
for serialised_entry in serialised_paths:
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:
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
tunnel_path = [timestamp, received_from, hops, expires, random_blobs, receiving_interface, announce_packet]
tunnel_paths[destination_hash] = tunnel_path
tunnel = [tunnel_id, None, tunnel_paths, expires]
Transport.tunnels[tunnel_id] = tunnel
if len(Transport.destination_table) == 1:
specifier = "entry"
else:
specifier = "entries"
RNS.log("Loaded "+str(len(Transport.tunnels))+" tunnel table "+specifier+" from storage", RNS.LOG_VERBOSE)
except Exception as e:
RNS.log("Could not load tunnel table from storage, the contained exception was: "+str(e), RNS.LOG_ERROR)
RNS.log("Transport instance "+str(Transport.identity)+" started", RNS.LOG_VERBOSE)
# Synthesize tunnels for any interfaces wanting it
for interface in Transport.interfaces:
interface.tunnel_id = None
if hasattr(interface, "wants_tunnel") and interface.wants_tunnel:
Transport.synthesize_tunnel(interface)
@staticmethod
def jobloop():
@@ -180,7 +258,7 @@ class Transport:
while len(Transport.receipts) > Transport.MAX_RECEIPTS:
culled_receipt = Transport.receipts.pop(0)
culled_receipt.timeout = -1
receipt.check_timeout()
culled_receipt.check_timeout()
for receipt in Transport.receipts:
receipt.check_timeout()
@@ -246,15 +324,16 @@ class Transport:
# Cull the packet hashlist if it has reached max size
while (len(Transport.packet_hashlist) > Transport.hashlist_maxsize):
Transport.packet_hashlist.pop(0)
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]
if time.time() > Transport.tables_last_culled + Transport.tables_cull_interval:
# Cull the reverse table according to timeout
stale_reverse_entries = []
for truncated_packet_hash in Transport.reverse_table:
reverse_entry = Transport.reverse_table[truncated_packet_hash]
if time.time() > reverse_entry[2] + Transport.REVERSE_TIMEOUT:
Transport.reverse_table.pop(truncated_packet_hash)
stale_reverse_entries.append(truncated_packet_hash)
# Cull the link table according to timeout
stale_links = []
@@ -272,11 +351,56 @@ class Transport:
if time.time() > destination_entry[0] + Transport.DESTINATION_TIMEOUT:
stale_paths.append(destination_hash)
RNS.log("Path to "+RNS.prettyhexrep(destination_hash)+" timed out and was removed", RNS.LOG_DEBUG)
if not attached_interface in Transport.interfaces:
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 tunnel table
stale_tunnels = []
ti = 0
for tunnel_id in Transport.tunnels:
tunnel_entry = Transport.tunnels[tunnel_id]
expires = tunnel_entry[3]
if time.time() > expires:
stale_tunnels.append(tunnel_id)
RNS.log("Tunnel "+RNS.prettyhexrep(tunnel_id)+" timed out and was removed", RNS.LOG_DEBUG)
else:
stale_tunnel_paths = []
tunnel_paths = tunnel_entry[2]
for tunnel_path in tunnel_paths:
tunnel_path_entry = tunnel_paths[tunnel_path]
if time.time() > tunnel_path_entry[0] + Transport.DESTINATION_TIMEOUT:
stale_tunnel_paths.append(tunnel_path)
RNS.log("Tunnel path to "+RNS.prettyhexrep(tunnel_path)+" timed out and was removed", RNS.LOG_DEBUG)
for tunnel_path in stale_tunnel_paths:
tunnel_paths.pop(tunnel_path)
ti += 1
if ti > 0:
if ti == 1:
RNS.log("Removed "+str(ti)+" tunnel path", RNS.LOG_DEBUG)
else:
RNS.log("Removed "+str(ti)+" tunnel paths", RNS.LOG_DEBUG)
i = 0
for truncated_packet_hash in stale_reverse_entries:
Transport.reverse_table.pop(truncated_packet_hash)
i += 1
if i > 0:
if i == 1:
RNS.log("Dropped "+str(i)+" reverse table entry", RNS.LOG_DEBUG)
else:
RNS.log("Dropped "+str(i)+" reverse table entries", RNS.LOG_DEBUG)
i = 0
for link_id in stale_links:
Transport.link_table.pop(link_id)
@@ -299,6 +423,17 @@ class Transport:
else:
RNS.log("Removed "+str(i)+" paths", RNS.LOG_DEBUG)
i = 0
for tunnel_id in stale_tunnels:
Transport.tunnels.pop(tunnel_id)
i += 1
if i > 0:
if i == 1:
RNS.log("Removed "+str(i)+" tunnel", RNS.LOG_DEBUG)
else:
RNS.log("Removed "+str(i)+" tunnels", RNS.LOG_DEBUG)
Transport.tables_last_culled = time.time()
except Exception as e:
@@ -338,8 +473,6 @@ class Transport:
new_raw += packet.raw[1:2]
new_raw += Transport.destination_table[packet.destination_hash][1]
new_raw += packet.raw[2:]
# TODO: Remove at some point
# RNS.log("Packet was inserted into transport via "+RNS.prettyhexrep(Transport.destination_table[packet.destination_hash][1])+" on: "+str(outbound_interface), RNS.LOG_EXTREME)
outbound_interface.processOutgoing(new_raw)
Transport.destination_table[packet.destination_hash][0] = time.time()
sent = True
@@ -359,8 +492,6 @@ class Transport:
new_raw += packet.raw[1:2]
new_raw += Transport.destination_table[packet.destination_hash][1]
new_raw += packet.raw[2:]
# TODO: Remove at some point
# RNS.log("Packet was inserted into transport via "+RNS.prettyhexrep(Transport.destination_table[packet.destination_hash][1])+" on: "+str(outbound_interface), RNS.LOG_EXTREME)
outbound_interface.processOutgoing(new_raw)
Transport.destination_table[packet.destination_hash][0] = time.time()
sent = True
@@ -381,20 +512,24 @@ class Transport:
for interface in Transport.interfaces:
if interface.OUT:
should_transmit = True
if packet.destination.type == RNS.Destination.LINK:
if packet.destination.status == RNS.Link.CLOSED:
should_transmit = False
if interface != packet.destination.attached_interface:
should_transmit = False
if packet.attached_interface != None and interface != packet.attached_interface:
should_transmit = False
if packet.packet_type == RNS.Packet.ANNOUNCE:
if packet.attached_interface == None and interface.mode == RNS.Interfaces.Interface.Interface.MODE_ACCESS_POINT:
should_transmit = False
if should_transmit:
RNS.log("Transmitting "+str(len(packet.raw))+" bytes on: "+str(interface), RNS.LOG_EXTREME)
RNS.log("Hash is "+RNS.prettyhexrep(packet.packet_hash), RNS.LOG_EXTREME)
if not stored_hash:
Transport.packet_hashlist.append(packet.packet_hash)
stored_hash = True
interface.processOutgoing(packet.raw)
sent = True
@@ -452,22 +587,40 @@ class Transport:
@staticmethod
def inbound(raw, interface=None):
while (Transport.jobs_running):
# TODO: Decrease this for performance
sleep(0.1)
sleep(0.01)
Transport.jobs_locked = True
packet = RNS.Packet(None, raw)
packet.unpack()
if not packet.unpack():
return
packet.receiving_interface = interface
packet.hops += 1
RNS.log(str(interface)+" received packet with hash "+RNS.prettyhexrep(packet.packet_hash), RNS.LOG_EXTREME)
if interface != None:
if hasattr(interface, "r_stat_rssi"):
if interface.r_stat_rssi != None:
packet.rssi = interface.r_stat_rssi
if len(Transport.local_client_interfaces) > 0:
Transport.local_client_rssi_cache.append([packet.packet_hash, packet.rssi])
while len(Transport.local_client_rssi_cache) > Transport.LOCAL_CLIENT_CACHE_MAXSIZE:
Transport.local_client_rssi_cache.pop()
if hasattr(interface, "r_stat_snr"):
if interface.r_stat_rssi != None:
packet.snr = interface.r_stat_snr
if len(Transport.local_client_interfaces) > 0:
Transport.local_client_snr_cache.append([packet.packet_hash, packet.snr])
while len(Transport.local_client_snr_cache) > Transport.LOCAL_CLIENT_CACHE_MAXSIZE:
Transport.local_client_snr_cache.pop()
if len(Transport.local_client_interfaces) > 0:
if Transport.is_local_client_interface(interface):
packet.hops -= 1
elif Transport.interface_to_shared_instance(interface):
packet.hops -= 1
@@ -528,11 +681,10 @@ class Transport:
# accordingly if we are.
if packet.transport_id != None and packet.packet_type != RNS.Packet.ANNOUNCE:
if packet.transport_id == Transport.identity.hash:
RNS.log("Received packet in transport for "+RNS.prettyhexrep(packet.destination_hash)+" with matching transport ID, transporting it...", RNS.LOG_DEBUG)
if packet.destination_hash in Transport.destination_table:
next_hop = Transport.destination_table[packet.destination_hash][1]
remaining_hops = Transport.destination_table[packet.destination_hash][2]
RNS.log("Next hop to destination is "+RNS.prettyhexrep(next_hop)+" with "+str(remaining_hops)+" hops remaining, transporting it.", RNS.LOG_DEBUG)
if remaining_hops > 1:
# Just increase hop count and transmit
new_raw = packet.raw[0:1]
@@ -580,7 +732,7 @@ class Transport:
# TODO: There should probably be some kind of REJECT
# mechanism here, to signal to the source that their
# expected path failed.
RNS.log("Got packet in transport, but no known path to final destination. Dropping packet.", RNS.LOG_DEBUG)
RNS.log("Got packet in transport, but no known path to final destination "+RNS.prettyhexrep(packet.destination_hash)+". Dropping packet.", RNS.LOG_DEBUG)
# Link transport handling. Directs packets according
# to entries in the link tables
@@ -624,7 +776,7 @@ class Transport:
# of queued announce rebroadcasts once handed to the next node.
if packet.packet_type == RNS.Packet.ANNOUNCE:
local_destination = next((d for d in Transport.destinations if d.hash == packet.destination_hash), None)
if local_destination == None and RNS.Identity.validate_announce(packet):
if local_destination == None and RNS.Identity.validate_announce(packet):
if packet.transport_id != None:
received_from = packet.transport_id
@@ -657,7 +809,8 @@ class Transport:
# First, check that the announce is not for a destination
# local to this system, and that hops are less than the max
if (not any(packet.destination_hash == d.hash for d in Transport.destinations) and packet.hops < Transport.PATHFINDER_M+1):
random_blob = packet.data[RNS.Identity.KEYSIZE//8+10:RNS.Identity.KEYSIZE//8+20]
random_blob = packet.data[RNS.Identity.KEYSIZE//8:RNS.Identity.KEYSIZE//8+RNS.Reticulum.TRUNCATED_HASHLENGTH//8]
announce_emitted = int.from_bytes(random_blob[5:10], "big")
random_blobs = []
if packet.destination_hash in Transport.destination_table:
random_blobs = Transport.destination_table[packet.destination_hash][4]
@@ -678,8 +831,18 @@ class Transport:
else:
# If an announce arrives with a larger hop
# count than we already have in the table,
# ignore it, unless the path is expired
if (time.time() > Transport.destination_table[packet.destination_hash][3]):
# ignore it, unless the path is expired, or
# the emission timestamp is more recent.
now = time.time()
path_expires = Transport.destination_table[packet.destination_hash][3]
path_announce_emitted = 0
for path_random_blob in random_blobs:
path_announce_emitted = max(path_announce_emitted, int.from_bytes(path_random_blob[5:10], "big"))
if path_announce_emitted >= announce_emitted:
break
if (now >= path_expires):
# We also check that the announce hash is
# different from ones we've already heard,
# to avoid loops in the network
@@ -691,7 +854,13 @@ class Transport:
else:
should_add = False
else:
should_add = False
if (announce_emitted > path_announce_emitted):
if not random_blob in random_blobs:
RNS.log("Replacing destination table entry for "+str(RNS.prettyhexrep(packet.destination_hash))+" with new announce, since it was more recently emitted", RNS.LOG_DEBUG)
should_add = True
else:
should_add = False
else:
# If this destination is unknown in our table
# we should add it
@@ -700,20 +869,25 @@ class Transport:
if should_add:
now = time.time()
retries = 0
expires = now + Transport.PATHFINDER_E
announce_hops = packet.hops
local_rebroadcasts = 0
block_rebroadcasts = False
attached_interface = None
retransmit_timeout = now + math.pow(Transport.PATHFINDER_C, packet.hops) + (RNS.rand() * Transport.PATHFINDER_RW)
if packet.receiving_interface.mode == RNS.Interfaces.Interface.Interface.MODE_ACCESS_POINT:
expires = now + Transport.AP_PATH_TIME
else:
expires = now + Transport.PATHFINDER_E
random_blobs.append(random_blob)
if (RNS.Reticulum.transport_enabled() or Transport.from_local_client(packet)) and packet.context != RNS.Packet.PATH_RESPONSE:
# If the announce is from a local client,
# we announce it immediately, but only one
# time.
# Insert announce into announce table for retransmission
if Transport.from_local_client(packet):
# If the announce is from a local client,
# it is announced immediately, but only one time.
retransmit_timeout = now
retries = Transport.PATHFINDER_R
@@ -729,6 +903,29 @@ class Transport:
attached_interface
]
# TODO: Check from_local_client once and store result
elif Transport.from_local_client(packet) and packet.context == RNS.Packet.PATH_RESPONSE:
# If this is a path response from a local client,
# check if any external interfaces have pending
# path requests.
if packet.destination_hash in Transport.pending_local_path_requests:
desiring_interface = Transport.pending_local_path_requests.pop(packet.destination_hash)
retransmit_timeout = now
retries = Transport.PATHFINDER_R
Transport.announce_table[packet.destination_hash] = [
now,
retransmit_timeout,
retries,
received_from,
announce_hops,
packet,
local_rebroadcasts,
block_rebroadcasts,
attached_interface
]
# If we have any local clients connected, we re-
# transmit the announce to them immediately
if (len(Transport.local_client_interfaces)):
@@ -739,48 +936,80 @@ class Transport:
announce_context = RNS.Packet.NONE
announce_data = packet.data
new_announce = RNS.Packet(
announce_destination,
announce_data,
RNS.Packet.ANNOUNCE,
context = announce_context,
header_type = RNS.Packet.HEADER_2,
transport_type = Transport.TRANSPORT,
transport_id = Transport.identity.hash,
attached_interface = attached_interface
)
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:
new_announce = RNS.Packet(
announce_destination,
announce_data,
RNS.Packet.ANNOUNCE,
context = announce_context,
header_type = RNS.Packet.HEADER_2,
transport_type = Transport.TRANSPORT,
transport_id = Transport.identity.hash,
attached_interface = local_interface
)
new_announce.hops = packet.hops
new_announce.send()
new_announce.hops = packet.hops
new_announce.send()
else:
for local_interface in Transport.local_client_interfaces:
if packet.receiving_interface != local_interface:
new_announce = RNS.Packet(
announce_destination,
announce_data,
RNS.Packet.ANNOUNCE,
context = announce_context,
header_type = RNS.Packet.HEADER_2,
transport_type = Transport.TRANSPORT,
transport_id = Transport.identity.hash,
attached_interface = local_interface
)
Transport.destination_table[packet.destination_hash] = [now, received_from, announce_hops, expires, random_blobs, packet.receiving_interface, packet]
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("Path to "+RNS.prettyhexrep(packet.destination_hash)+" is now "+str(announce_hops)+" hops away via "+RNS.prettyhexrep(received_from)+" on "+str(packet.receiving_interface), RNS.LOG_VERBOSE)
# If the receiving interface is a tunnel, we add the
# announce to the tunnels table
if hasattr(packet.receiving_interface, "tunnel_id") and packet.receiving_interface.tunnel_id != None:
tunnel_entry = Transport.tunnels[packet.receiving_interface.tunnel_id]
paths = tunnel_entry[2]
paths[packet.destination_hash] = destination_table_entry
expires = time.time() + Transport.DESTINATION_TIMEOUT
tunnel_entry[3] = expires
RNS.log("Path to "+RNS.prettyhexrep(packet.destination_hash)+" associated with tunnel "+RNS.prettyhexrep(packet.receiving_interface.tunnel_id), RNS.LOG_VERBOSE)
# Call externally registered callbacks from apps
# wanting to know when an announce arrives
for handler in Transport.announce_handlers:
try:
# Check that the announced destination matches
# the handlers aspect filter
execute_callback = False
if handler.aspect_filter == None:
# If the handlers aspect filter is set to
# None, we execute the callback in all cases
execute_callback = True
else:
announce_identity = RNS.Identity.recall(packet.destination_hash)
handler_expected_hash = RNS.Destination.hash_from_name_and_identity(handler.aspect_filter, announce_identity)
if packet.destination_hash == handler_expected_hash:
if packet.context != RNS.Packet.PATH_RESPONSE:
for handler in Transport.announce_handlers:
try:
# Check that the announced destination matches
# the handlers aspect filter
execute_callback = False
if handler.aspect_filter == None:
# If the handlers aspect filter is set to
# None, we execute the callback in all cases
execute_callback = True
if execute_callback:
handler.received_announce(
destination_hash=packet.destination_hash,
announced_identity=announce_identity,
app_data=RNS.Identity.recall_app_data(packet.destination_hash)
)
except Exception as e:
RNS.log("Error while processing external announce callback.", RNS.LOG_ERROR)
RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR)
else:
announce_identity = RNS.Identity.recall(packet.destination_hash)
handler_expected_hash = RNS.Destination.hash_from_name_and_identity(handler.aspect_filter, announce_identity)
if packet.destination_hash == handler_expected_hash:
execute_callback = True
if execute_callback:
handler.received_announce(
destination_hash=packet.destination_hash,
announced_identity=announce_identity,
app_data=RNS.Identity.recall_app_data(packet.destination_hash)
)
except Exception as e:
RNS.log("Error while processing external announce callback.", RNS.LOG_ERROR)
RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR)
# Handling for linkrequests to local destinations
elif packet.packet_type == RNS.Packet.LINKREQUEST:
@@ -807,8 +1036,11 @@ class Transport:
elif destination.proof_strategy == RNS.Destination.PROVE_APP:
if destination.callbacks.proof_requested:
if destination.callbacks.proof_requested(packet):
packet.prove()
try:
if destination.callbacks.proof_requested(packet):
packet.prove()
except Exception as e:
RNS.log("Error while executing proof request callback. The contained exception was: "+str(e), RNS.LOG_ERROR)
# Handling for proofs and link-request proofs
elif packet.packet_type == RNS.Packet.PROOF:
@@ -869,15 +1101,120 @@ class Transport:
if receipt.hash == proof_hash:
receipt_validated = receipt.validate_proof_packet(packet)
else:
# TODO: This looks like it should actually
# be rewritten when implicit proofs are added.
# In case of an implicit proof, we have
# to check every single outstanding receipt
receipt_validated = receipt.validate_proof_packet(packet)
if receipt_validated:
Transport.receipts.remove(receipt)
if receipt in Transport.receipts:
Transport.receipts.remove(receipt)
Transport.jobs_locked = False
@staticmethod
def synthesize_tunnel(interface):
interface_hash = interface.get_hash()
public_key = RNS.Transport.identity.get_public_key()
random_hash = RNS.Identity.get_random_hash()
tunnel_id_data = public_key+interface_hash
tunnel_id = RNS.Identity.full_hash(tunnel_id_data)
signed_data = tunnel_id_data+random_hash
signature = Transport.identity.sign(signed_data)
data = signed_data+signature
tnl_snth_dst = RNS.Destination(None, RNS.Destination.OUT, RNS.Destination.PLAIN, Transport.APP_NAME, "tunnel", "synthesize")
packet = RNS.Packet(tnl_snth_dst, data, packet_type = RNS.Packet.DATA, transport_type = RNS.Transport.BROADCAST, header_type = RNS.Packet.HEADER_1, attached_interface = interface)
packet.send()
interface.wants_tunnel = False
@staticmethod
def tunnel_synthesize_handler(data, packet):
try:
expected_length = RNS.Identity.KEYSIZE//8+RNS.Identity.HASHLENGTH//8+RNS.Reticulum.TRUNCATED_HASHLENGTH//8+RNS.Identity.SIGLENGTH//8
if len(data) == expected_length:
public_key = data[:RNS.Identity.KEYSIZE//8]
interface_hash = data[RNS.Identity.KEYSIZE//8:RNS.Identity.KEYSIZE//8+RNS.Identity.HASHLENGTH//8]
tunnel_id_data = public_key+interface_hash
tunnel_id = RNS.Identity.full_hash(tunnel_id_data)
random_hash = data[RNS.Identity.KEYSIZE//8+RNS.Identity.HASHLENGTH//8:RNS.Identity.KEYSIZE//8+RNS.Identity.HASHLENGTH//8+RNS.Reticulum.TRUNCATED_HASHLENGTH//8]
signature = data[RNS.Identity.KEYSIZE//8+RNS.Identity.HASHLENGTH//8+RNS.Reticulum.TRUNCATED_HASHLENGTH//8:expected_length]
signed_data = tunnel_id_data+random_hash
remote_transport_identity = RNS.Identity(create_keys=False)
remote_transport_identity.load_public_key(public_key)
if remote_transport_identity.validate(signature, signed_data):
Transport.handle_tunnel(tunnel_id, packet.receiving_interface)
except Exception as e:
RNS.log("An error occurred while validating tunnel establishment packet.", RNS.LOG_DEBUG)
RNS.log("The contained exception was: "+str(e), RNS.LOG_DEBUG)
@staticmethod
def handle_tunnel(tunnel_id, interface):
expires = time.time() + Transport.DESTINATION_TIMEOUT
if not tunnel_id in Transport.tunnels:
RNS.log("Tunnel endpoint "+RNS.prettyhexrep(tunnel_id)+" established.", RNS.LOG_DEBUG)
paths = {}
tunnel_entry = [tunnel_id, interface, paths, expires]
interface.tunnel_id = tunnel_id
Transport.tunnels[tunnel_id] = tunnel_entry
else:
RNS.log("Tunnel endpoint "+RNS.prettyhexrep(tunnel_id)+" reappeared. Restoring paths...", RNS.LOG_DEBUG)
tunnel_entry = Transport.tunnels[tunnel_id]
tunnel_entry[1] = interface
tunnel_entry[3] = expires
interface.tunnel_id = tunnel_id
paths = tunnel_entry[2]
deprecated_paths = []
for destination_hash, path_entry in paths.items():
received_from = path_entry[1]
announce_hops = path_entry[2]
expires = path_entry[3]
random_blobs = path_entry[4]
receiving_interface = interface
packet = path_entry[6]
new_entry = [time.time(), received_from, announce_hops, expires, random_blobs, receiving_interface, packet]
should_add = False
if destination_hash in Transport.destination_table:
old_entry = Transport.destination_table[destination_hash]
old_hops = old_entry[2]
old_expires = old_entry[3]
if announce_hops <= old_hops or time.time() > old_expires:
should_add = True
else:
RNS.log("Did not restore path to "+RNS.prettyhexrep(packet.destination_hash)+" because a newer path with fewer hops exist", RNS.LOG_DEBUG)
else:
if time.time() < expires:
should_add = True
else:
RNS.log("Did not restore path to "+RNS.prettyhexrep(packet.destination_hash)+" because it has expired", RNS.LOG_DEBUG)
if should_add:
Transport.destination_table[destination_hash] = new_entry
RNS.log("Restored path to "+RNS.prettyhexrep(packet.destination_hash)+" is now "+str(announce_hops)+" hops away via "+RNS.prettyhexrep(received_from)+" on "+str(receiving_interface), RNS.LOG_DEBUG)
else:
deprecated_paths.append(destination_hash)
for deprecated_path in deprecated_paths:
RNS.log("Removing path to "+RNS.prettyhexrep(deprecated_path)+" from tunnel "+RNS.prettyhexrep(tunnel_id), RNS.LOG_DEBUG)
paths.pop(deprecated_path)
@staticmethod
def register_destination(destination):
destination.MTU = RNS.Reticulum.MTU
@@ -885,8 +1222,13 @@ class Transport:
for registered_destination in Transport.destinations:
if destination.hash == registered_destination.hash:
raise KeyError("Attempt to register an already registered destination.")
Transport.destinations.append(destination)
if Transport.owner.is_connected_to_shared_instance:
if destination.type == RNS.Destination.SINGLE:
destination.announce(path_response=True)
@staticmethod
def deregister_destination(destination):
if destination in Transport.destinations:
@@ -1033,6 +1375,39 @@ class Transport:
else:
return False
@staticmethod
def hops_to(destination_hash):
"""
:param destination_hash: A destination hash as *bytes*.
:returns: The number of hops to the specified destination, or ``RNS.Transport.PATHFINDER_M`` if the number of hops is unknown.
"""
if destination_hash in Transport.destination_table:
return Transport.destination_table[destination_hash][2]
else:
return Transport.PATHFINDER_M
@staticmethod
def next_hop(destination_hash):
"""
:param destination_hash: A destination hash as *bytes*.
:returns: The destination hash as *bytes* for the next hop to the specified destination, or *None* if the next hop is unknown.
"""
if destination_hash in Transport.destination_table:
return Transport.destination_table[destination_hash][1]
else:
return None
@staticmethod
def next_hop_interface(destination_hash):
"""
:param destination_hash: A destination hash as *bytes*.
:returns: The interface for the next hop to the specified destination, or *None* if the interface is unknown.
"""
if destination_hash in Transport.destination_table:
return Transport.destination_table[destination_hash][5]
else:
return None
@staticmethod
def request_path(destination_hash):
"""
@@ -1056,24 +1431,37 @@ class Transport:
@staticmethod
def path_request_handler(data, packet):
if len(data) >= RNS.Identity.TRUNCATED_HASHLENGTH//8:
Transport.path_request(
data[:RNS.Identity.TRUNCATED_HASHLENGTH//8],
Transport.from_local_client(packet),
packet.receiving_interface
)
try:
if len(data) >= RNS.Identity.TRUNCATED_HASHLENGTH//8:
Transport.path_request(
data[:RNS.Identity.TRUNCATED_HASHLENGTH//8],
Transport.from_local_client(packet),
packet.receiving_interface
)
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):
RNS.log("Path request for "+RNS.prettyhexrep(destination_hash), RNS.LOG_DEBUG)
destination_exists_on_local_client = False
if len(Transport.local_client_interfaces) > 0:
if destination_hash in Transport.destination_table:
destination_interface = Transport.destination_table[destination_hash][5]
if Transport.is_local_client_interface(destination_interface):
destination_exists_on_local_client = True
Transport.pending_local_path_requests[destination_hash] = attached_interface
local_destination = next((d for d in Transport.destinations if d.hash == destination_hash), None)
if local_destination != None:
RNS.log("Destination is local to this system, announcing", RNS.LOG_DEBUG)
local_destination.announce(path_response=True)
elif (RNS.Reticulum.transport_enabled() or is_from_local_client) and destination_hash in Transport.destination_table:
elif (RNS.Reticulum.transport_enabled() or is_from_local_client) and (destination_hash in Transport.destination_table):
RNS.log("Path found, inserting announce for transmission", RNS.LOG_DEBUG)
packet = Transport.destination_table[destination_hash][6]
received_from = Transport.destination_table[destination_hash][5]
@@ -1141,6 +1529,40 @@ class Transport:
else:
return False
@staticmethod
def detach_interfaces():
for interface in Transport.interfaces:
interface.detach()
for interface in Transport.local_client_interfaces:
interface.detach()
@staticmethod
def shared_connection_disappeared():
for link in Transport.active_links:
link.teardown()
for link in Transport.pending_links:
link.teardown()
Transport.announce_table = {}
Transport.destination_table = {}
Transport.reverse_table = {}
Transport.link_table = {}
Transport.held_announces = {}
Transport.announce_handlers = []
Transport.tunnels = {}
@staticmethod
def shared_connection_reappeared():
if Transport.owner.is_connected_to_shared_instance:
for registered_destination in Transport.destinations:
if registered_destination.type == RNS.Destination.SINGLE:
registered_destination.announce(path_response=True)
@staticmethod
def exit_handler():
try:
@@ -1201,3 +1623,55 @@ class Transport:
RNS.log("Done saving "+str(len(serialised_destinations))+" path table entries to storage", RNS.LOG_VERBOSE)
except Exception as e:
RNS.log("Could not save path table to storage, the contained exception was: "+str(e), RNS.LOG_ERROR)
RNS.log("Saving tunnel table to storage...", RNS.LOG_VERBOSE)
try:
serialised_tunnels = []
for tunnel_id in Transport.tunnels:
te = Transport.tunnels[tunnel_id]
interface = te[1]
tunnel_paths = te[2]
expires = te[3]
if interface != None:
interface_hash = interface.get_hash()
else:
interface_hash = None
serialised_paths = []
for destination_hash in tunnel_paths:
de = tunnel_paths[destination_hash]
timestamp = de[0]
received_from = de[1]
hops = de[2]
expires = de[3]
random_blobs = de[4]
packet_hash = de[6].get_hash()
serialised_entry = [
destination_hash,
timestamp,
received_from,
hops,
expires,
random_blobs,
interface_hash,
packet_hash
]
serialised_paths.append(serialised_entry)
Transport.cache(de[6], force_cache=True)
serialised_tunnel = [tunnel_id, interface_hash, serialised_paths, expires]
serialised_tunnels.append(serialised_tunnel)
tunnels_path = RNS.Reticulum.storagepath+"/tunnels"
file = open(tunnels_path, "wb")
file.write(umsgpack.packb(serialised_tunnels))
file.close()
RNS.log("Done saving "+str(len(serialised_tunnels))+" tunnel table entries to storage", RNS.LOG_VERBOSE)
except Exception as e:
RNS.log("Could not save tunnel table to storage, the contained exception was: "+str(e), RNS.LOG_ERROR)
+5
View File
@@ -0,0 +1,5 @@
import os
import glob
modules = glob.glob(os.path.dirname(__file__)+"/*.py")
__all__ = [ os.path.basename(f)[:-3] for f in modules if not f.endswith('__init__.py')]
+97
View File
@@ -0,0 +1,97 @@
#!/usr/bin/env python3
import RNS
import sys
import time
import argparse
from RNS._version import __version__
def program_setup(configdir, destination_hexhash, verbosity):
try:
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))
try:
destination_hash = bytes.fromhex(destination_hexhash)
except Exception as e:
raise ValueError("Invalid destination entered. Check your input.")
except Exception as e:
print(str(e))
exit()
reticulum = RNS.Reticulum(configdir = configdir, loglevel = 3+verbosity)
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 = "⢄⢂⢁⡁⡈⡐⡠"
while not RNS.Transport.has_path(destination_hash):
time.sleep(0.1)
print(("\b\b"+syms[i]+" "), end="")
sys.stdout.flush()
i = (i+1)%len(syms)
hops = RNS.Transport.hops_to(destination_hash)
next_hop = RNS.prettyhexrep(reticulum.get_next_hop(destination_hash))
next_hop_interface = reticulum.get_next_hop_if_name(destination_hash)
if hops != 1:
ms = "s"
else:
ms = ""
print("\rPath found, destination "+RNS.prettyhexrep(destination_hash)+" is "+str(hops)+" hop"+ms+" away via "+next_hop+" on "+next_hop_interface)
def main():
try:
parser = argparse.ArgumentParser(description="Reticulum Path Discovery Utility")
parser.add_argument("--config",
action="store",
default=None,
help="path to alternative Reticulum config directory",
type=str
)
parser.add_argument(
"--version",
action="version",
version="rnpath {version}".format(version=__version__)
)
parser.add_argument(
"destination",
nargs="?",
default=None,
help="hexadecimal hash of the destination",
type=str
)
parser.add_argument('-v', '--verbose', action='count', default=0)
args = parser.parse_args()
if args.config:
configarg = args.config
else:
configarg = None
if not args.destination:
print("")
parser.print_help()
print("")
else:
program_setup(configdir = configarg, destination_hexhash = args.destination, verbosity = args.verbose)
except KeyboardInterrupt:
print("")
exit()
if __name__ == "__main__":
main()
+192
View File
@@ -0,0 +1,192 @@
#!/usr/bin/env python3
import RNS
import os
import sys
import time
import argparse
from RNS._version import __version__
DEFAULT_PROBE_SIZE = 16
def program_setup(configdir, destination_hexhash, size=DEFAULT_PROBE_SIZE, full_name = None, verbosity = 0):
if full_name == None:
print("The full destination name including application name aspects must be specified for the destination")
exit()
try:
app_name, aspects = RNS.Destination.app_and_aspects_from_name(full_name)
except Exception as e:
print(str(e))
exit()
try:
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))
try:
destination_hash = bytes.fromhex(destination_hexhash)
except Exception as e:
raise ValueError("Invalid destination entered. Check your input.")
except Exception as e:
print(str(e))
exit()
if verbosity > 0:
more_output = True
verbosity -= 1
else:
more_output = False
verbosity -= 1
reticulum = RNS.Reticulum(configdir = configdir, loglevel = 3+verbosity)
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 = "⢄⢂⢁⡁⡈⡐⡠"
while not RNS.Transport.has_path(destination_hash):
time.sleep(0.1)
print(("\b\b"+syms[i]+" "), end="")
sys.stdout.flush()
i = (i+1)%len(syms)
server_identity = RNS.Identity.recall(destination_hash)
request_destination = RNS.Destination(
server_identity,
RNS.Destination.OUT,
RNS.Destination.SINGLE,
app_name,
*aspects
)
probe = RNS.Packet(request_destination, os.urandom(size))
receipt = probe.send()
if more_output:
more = " via "+RNS.prettyhexrep(reticulum.get_next_hop(destination_hash))+" on "+str(reticulum.get_next_hop_if_name(destination_hash))
else:
more = ""
print("\rSent "+str(size)+" byte probe to "+RNS.prettyhexrep(destination_hash)+more+" ", end=" ")
i = 0
while not receipt.status == RNS.PacketReceipt.DELIVERED:
time.sleep(0.1)
print(("\b\b"+syms[i]+" "), end="")
sys.stdout.flush()
i = (i+1)%len(syms)
print("\b\b ")
sys.stdout.flush()
hops = RNS.Transport.hops_to(destination_hash)
if hops != 1:
ms = "s"
else:
ms = ""
rtt = receipt.get_rtt()
if (rtt >= 1):
rtt = round(rtt, 3)
rttstring = str(rtt)+" seconds"
else:
rtt = round(rtt*1000, 3)
rttstring = str(rtt)+" milliseconds"
reception_stats = ""
if reticulum.is_connected_to_shared_instance:
reception_rssi = reticulum.get_packet_rssi(receipt.proof_packet.packet_hash)
reception_snr = reticulum.get_packet_snr(receipt.proof_packet.packet_hash)
if reception_rssi != None:
reception_stats += " [RSSI "+str(reception_rssi)+" dBm]"
if reception_snr != None:
reception_stats += " [SNR "+str(reception_snr)+" dB]"
else:
if receipt.proof_packet != None:
if receipt.proof_packet.rssi != None:
reception_stats += " [RSSI "+str(receipt.proof_packet.rssi)+" dBm]"
if receipt.proof_packet.snr != None:
reception_stats += " [SNR "+str(receipt.proof_packet.snr)+" dB]"
print(
"Valid reply received from "+
RNS.prettyhexrep(receipt.destination.hash)+
"\nRound-trip time is "+rttstring+
" over "+str(hops)+" hop"+ms+
reception_stats
)
def main():
try:
parser = argparse.ArgumentParser(description="Reticulum Probe Utility")
parser.add_argument("--config",
action="store",
default=None,
help="path to alternative Reticulum config directory",
type=str
)
parser.add_argument(
"--version",
action="version",
version="rnprobe {version}".format(version=__version__)
)
parser.add_argument(
"full_name",
nargs="?",
default=None,
help="full destination name in dotted notation",
type=str
)
parser.add_argument(
"destination_hash",
nargs="?",
default=None,
help="hexadecimal hash of the destination",
type=str
)
parser.add_argument('-v', '--verbose', action='count', default=0)
args = parser.parse_args()
if args.config:
configarg = args.config
else:
configarg = None
if not args.destination_hash:
print("")
parser.print_help()
print("")
else:
program_setup(
configdir = configarg,
destination_hexhash = args.destination_hash,
full_name = args.full_name,
verbosity = args.verbose
)
except KeyboardInterrupt:
print("")
exit()
if __name__ == "__main__":
main()
+391
View File
@@ -0,0 +1,391 @@
#!/usr/bin/env python3
import RNS
import argparse
import time
from RNS._version import __version__
def program_setup(configdir, verbosity = 0, quietness = 0, service = False):
targetloglevel = 3+verbosity-quietness
if service:
RNS.logdest = RNS.LOG_FILE
RNS.logfile = RNS.Reticulum.configdir+"/logfile"
targetloglevel = None
reticulum = RNS.Reticulum(configdir=configdir, loglevel=targetloglevel)
RNS.log("Started rnsd version {version}".format(version=__version__), RNS.LOG_NOTICE)
while True:
time.sleep(1)
def main():
try:
parser = argparse.ArgumentParser(description="Reticulum Network Stack Daemon")
parser.add_argument("--config", action="store", default=None, help="path to alternative Reticulum config directory", type=str)
parser.add_argument('-v', '--verbose', action='count', default=0)
parser.add_argument('-q', '--quiet', action='count', default=0)
parser.add_argument('-s', '--service', action='store_true', default=False, help="rnsd is running as a service and should log to file")
parser.add_argument("--exampleconfig", action='store_true', default=False, help="print verbose configuration example to stdout and exit")
parser.add_argument("--version", action="version", version="rnsd {version}".format(version=__version__))
args = parser.parse_args()
if args.exampleconfig:
print(__example_rns_config__)
exit()
if args.config:
configarg = args.config
else:
configarg = None
program_setup(configdir = configarg, verbosity=args.verbose, quietness=args.quiet, service=args.service)
except KeyboardInterrupt:
print("")
exit()
__example_rns_config__ = '''# This is an example Reticulum config file.
# You should probably edit it to include any additional,
# interfaces and settings you might need.
[reticulum]
# If you enable Transport, your system will route traffic
# for other peers, pass announces and serve path requests.
# This should be done for systems that are suited to act
# as transport nodes, ie. if they are stationary and
# always-on. This directive is optional and can be removed
# for brevity.
enable_transport = False
# By default, the first program to launch the Reticulum
# Network Stack will create a shared instance, that other
# programs can communicate with. Only the shared instance
# opens all the configured interfaces directly, and other
# local programs communicate with the shared instance over
# a local socket. This is completely transparent to the
# user, and should generally be turned on. This directive
# is optional and can be removed for brevity.
share_instance = Yes
# If you want to run multiple *different* shared instances
# on the same system, you will need to specify different
# shared instance ports for each. The defaults are given
# below, and again, these options can be left out if you
# don't need them.
shared_instance_port = 37428
instance_control_port = 37429
# You can configure Reticulum to panic and forcibly close
# if an unrecoverable interface error occurs, such as the
# hardware device for an interface disappearing. This is
# an optional directive, and can be left out for brevity.
# This behaviour is disabled by default.
panic_on_interface_error = No
[logging]
# Valid log levels are 0 through 7:
# 0: Log only critical information
# 1: Log errors and lower log levels
# 2: Log warnings and lower log levels
# 3: Log notices and lower log levels
# 4: Log info and lower (this is the default)
# 5: Verbose logging
# 6: Debug logging
# 7: Extreme logging
loglevel = 4
# The interfaces section defines the physical and virtual
# interfaces Reticulum will use to communicate on. This
# section will contain examples for a variety of interface
# types. You can modify these or use them as a basis for
# your own config, or simply remove the unused ones.
[interfaces]
# This interface enables communication with other
# link-local Reticulum nodes over UDP. It does not
# need any functional IP infrastructure like routers
# or DHCP servers, but will require that at least link-
# local IPv6 is enabled in your operating system, which
# should be enabled by default in almost any OS. See
# the Reticulum Manual for more configuration options.
[[Default Interface]]
type = AutoInterface
interface_enabled = True
# The following example enables communication with other
# local Reticulum peers using UDP broadcasts.
[[UDP Interface]]
type = UDPInterface
interface_enabled = False
listen_ip = 0.0.0.0
listen_port = 4242
forward_ip = 255.255.255.255
forward_port = 4242
# The above configuration will allow communication
# within the local broadcast domains of all local
# IP interfaces.
# Instead of specifying listen_ip, listen_port,
# forward_ip and forward_port, you can also bind
# to a specific network device like below.
# device = eth0
# port = 4242
# Assuming the eth0 device has the address
# 10.55.0.72/24, the above configuration would
# be equivalent to the following manual setup.
# Note that we are both listening and forwarding to
# the broadcast address of the network segments.
# listen_ip = 10.55.0.255
# listen_port = 4242
# forward_ip = 10.55.0.255
# forward_port = 4242
# You can of course also communicate only with
# a single IP address
# listen_ip = 10.55.0.15
# listen_port = 4242
# forward_ip = 10.55.0.16
# forward_port = 4242
# This example demonstrates a TCP server interface.
# It will listen for incoming connections on the
# specified IP address and port number.
[[TCP Server Interface]]
type = TCPServerInterface
interface_enabled = False
# This configuration will listen on all IP
# interfaces on port 4242
listen_ip = 0.0.0.0
listen_port = 4242
# Alternatively you can bind to a specific IP
# listen_ip = 10.0.0.88
# listen_port = 4242
# Or a specific network device
# device = eth0
# port = 4242
# To connect to a TCP server interface, you would
# naturally use the TCP client interface. Here's
# an example. The target_host can either be an IP
# address or a hostname
[[TCP Client Interface]]
type = TCPClientInterface
interface_enabled = False
target_host = 127.0.0.1
target_port = 4242
# This example shows how to make your Reticulum
# instance available over I2P, and connect to
# another I2P peer. Please be aware that you
# must have an I2P router running on your system
# with the SAMv3 API enabled for this to work.
[[I2P]]
type = I2PInterface
interface_enabled = yes
connectable = yes
peers = 5urvjicpzi7q3ybztsef4i5ow2aq4soktfj7zedz53s47r54jnqq.b32.i2p
# Here's an example of how to add a LoRa interface
# using the RNode LoRa transceiver.
[[RNode LoRa Interface]]
type = RNodeInterface
# Enable interface if you want use it!
interface_enabled = False
# Serial port for the device
port = /dev/ttyUSB0
# Set frequency to 867.2 MHz
frequency = 867200000
# Set LoRa bandwidth to 125 KHz
bandwidth = 125000
# Set TX power to 7 dBm (5 mW)
txpower = 7
# Select spreading factor 8. Valid
# range is 7 through 12, with 7
# being the fastest and 12 having
# the longest range.
spreadingfactor = 8
# Select coding rate 5. Valid range
# is 5 throough 8, with 5 being the
# fastest, and 8 the longest range.
codingrate = 5
# You can configure the RNode to send
# out identification on the channel with
# a set interval by configuring the
# following two parameters. The trans-
# ceiver will only ID if the set
# interval has elapsed since it's last
# actual transmission. The interval is
# configured in seconds.
# This option is commented out and not
# used by default.
# id_callsign = MYCALL-0
# id_interval = 600
# For certain homebrew RNode interfaces
# with low amounts of RAM, using packet
# flow control can be useful. By default
# it is disabled.
flow_control = False
# An example KISS modem interface. Useful for running
# Reticulum over packet radio hardware.
[[Packet Radio KISS Interface]]
type = KISSInterface
# Enable interface if you want use it!
interface_enabled = False
# Serial port for the device
port = /dev/ttyUSB1
# Set the serial baud-rate and other
# configuration parameters.
speed = 115200
databits = 8
parity = none
stopbits = 1
# Set the modem preamble. A 150ms
# preamble should be a reasonable
# default, but may need to be
# increased for radios with slow-
# opening squelch and long TX/RX
# turnaround
preamble = 150
# Set the modem TX tail. In most
# cases this should be kept as low
# as possible to not waste airtime.
txtail = 10
# Configure CDMA parameters. These
# settings are reasonable defaults.
persistence = 200
slottime = 20
# You can configure the interface to send
# out identification on the channel with
# a set interval by configuring the
# following two parameters. The KISS
# interface will only ID if the set
# interval has elapsed since it's last
# actual transmission. The interval is
# configured in seconds.
# This option is commented out and not
# used by default.
# id_callsign = MYCALL-0
# id_interval = 600
# Whether to use KISS flow-control.
# This is useful for modems that have
# a small internal packet buffer, but
# support packet flow control instead.
flow_control = false
# If you're using Reticulum on amateur radio spectrum,
# you might want to use the AX.25 KISS interface. This
# way, Reticulum will automatically encapsulate it's
# traffic in AX.25 and also identify your stations
# transmissions with your callsign and SSID.
#
# Only do this if you really need to! Reticulum doesn't
# need the AX.25 layer for anything, and it incurs extra
# overhead on every packet to encapsulate in AX.25.
#
# A more efficient way is to use the plain KISS interface
# with the beaconing functionality described above.
[[Packet Radio AX.25 KISS Interface]]
type = AX25KISSInterface
# Set the station callsign and SSID
callsign = NO1CLL
ssid = 0
# Enable interface if you want use it!
interface_enabled = False
# Serial port for the device
port = /dev/ttyUSB2
# Set the serial baud-rate and other
# configuration parameters.
speed = 115200
databits = 8
parity = none
stopbits = 1
# Whether to use KISS flow-control.
# This is useful for modems with a
# small internal packet buffer.
flow_control = false
# Set the modem preamble. A 150ms
# preamble should be a reasonable
# default, but may need to be
# increased for radios with slow-
# opening squelch and long TX/RX
# turnaround
preamble = 150
# Set the modem TX tail. In most
# cases this should be kept as low
# as possible to not waste airtime.
txtail = 10
# Configure CDMA parameters. These
# settings are reasonable defaults.
persistence = 200
slottime = 20
'''
if __name__ == "__main__":
main()
+97
View File
@@ -0,0 +1,97 @@
#!/usr/bin/env python3
import RNS
import argparse
from RNS._version import __version__
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 program_setup(configdir, dispall=False, verbosity = 0):
reticulum = RNS.Reticulum(configdir = configdir, loglevel = 3+verbosity)
ifstats = reticulum.get_interface_stats()
if ifstats != None:
for ifstat in ifstats:
name = ifstat["name"]
if dispall or not (name.startswith("LocalInterface[") or name.startswith("TCPInterface[Client")):
print("")
if ifstat["status"]:
ss = "Up"
else:
ss = "Down"
if ifstat["clients"] != None:
clients = ifstat["clients"]
if name.startswith("Shared Instance["):
clients_string = "Connected applications: "+str(max(clients-1,0))
else:
clients_string = "Connected clients: "+str(clients)
else:
clients = None
print(" {n}".format(n=ifstat["name"]))
print("\tStatus: {ss}".format(ss=ss))
if "i2p_b32" in ifstat:
print("\tI2P B32: {ep}".format(ep=str(ifstat["i2p_b32"])))
if clients != None:
print("\t"+clients_string)
print("\tRX: {rxb}\n\tTX: {txb}".format(rxb=size_str(ifstat["rxb"]), txb=size_str(ifstat["txb"])))
print("")
else:
print("Could not get RNS status")
def main():
try:
parser = argparse.ArgumentParser(description="Reticulum Network Stack Status")
parser.add_argument("--config", action="store", default=None, help="path to alternative Reticulum config directory", type=str)
parser.add_argument("--version", action="version", version="rnstatus {version}".format(version=__version__))
parser.add_argument(
"-a",
"--all",
action="store_true",
help="show all interfaces",
default=False
)
parser.add_argument('-v', '--verbose', action='count', default=0)
args = parser.parse_args()
if args.config:
configarg = args.config
else:
configarg = None
program_setup(configdir = configarg, dispall = args.all, verbosity=args.verbose)
except KeyboardInterrupt:
print("")
exit()
if __name__ == "__main__":
main()
+24 -2
View File
@@ -9,7 +9,7 @@ from ._version import __version__
from .Reticulum import Reticulum
from .Identity import Identity
from .Link import Link
from .Link import Link, RequestReceipt
from .Transport import Transport
from .Destination import Destination
from .Packet import Packet
@@ -31,6 +31,8 @@ LOG_EXTREME = 7
LOG_STDOUT = 0x91
LOG_FILE = 0x92
LOG_MAXSIZE = 5*1024*1024
loglevel = LOG_NOTICE
logfile = None
logdest = LOG_STDOUT
@@ -65,6 +67,10 @@ def loglevelname(level):
def version():
return __version__
def host_os():
from .vendor.platformutils import get_platform
return get_platform()
def log(msg, level=3, _override_destination = False):
global _always_override_destination
@@ -82,6 +88,13 @@ def log(msg, level=3, _override_destination = False):
file = open(logfile, "a")
file.write(logstring+"\n")
file.close()
if os.path.getsize(logfile) > LOG_MAXSIZE:
prevfile = logfile+".1"
if os.path.isfile(prevfile):
os.unlink(prevfile)
os.rename(logfile, prevfile)
logging_lock.release()
except Exception as e:
logging_lock.release()
@@ -96,6 +109,11 @@ def rand():
return result
def hexrep(data, delimit=True):
try:
iter(data)
except TypeError:
data = [data]
delimiter = ":"
if not delimit:
delimiter = ""
@@ -108,4 +126,8 @@ def prettyhexrep(data):
return hexrep
def panic():
os._exit(255)
os._exit(255)
def exit():
print("")
sys.exit(0)
+1 -1
View File
@@ -1 +1 @@
__version__ = "0.2.2"
__version__ = "0.3.3"
-2
View File
@@ -1,7 +1,5 @@
import os
import glob
__version__ = "0.1.9"
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 -1
View File
@@ -19,7 +19,7 @@ import sys
from codecs import BOM_UTF8, BOM_UTF16, BOM_UTF16_BE, BOM_UTF16_LE
import six
import RNS.vendor.six as six
__version__ = '5.0.6'
# imported lazily to avoid startup performance hit if it isn't used
+20
View File
@@ -0,0 +1,20 @@
Copyright (c) 2018 Viktor Villainov
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.
+25
View File
@@ -0,0 +1,25 @@
"""
A modern asynchronous library for building I2P applications.
"""
from .__version__ import (
__title__, __description__, __url__, __version__,
__author__, __author_email__, __license__, __copyright__
)
from .sam import Destination, PrivateKey
from .aiosam import (
get_sam_socket, dest_lookup, new_destination,
create_session, stream_connect, stream_accept,
Session, StreamConnection, StreamAcceptor
)
from .tunnel import ClientTunnel, ServerTunnel
from .utils import get_sam_address
from .exceptions import (
CantReachPeer, DuplicatedDest, DuplicatedId, I2PError,
InvalidId, InvalidKey, KeyNotFound, PeerNotFound, Timeout,
)
+8
View File
@@ -0,0 +1,8 @@
__title__ = 'i2plib'
__description__ = 'A modern asynchronous library for building I2P applications.'
__url__ = 'https://github.com/l-n-s/i2plib'
__version__ = '0.0.14'
__author__ = 'Viktor Villainov'
__author_email__ = 'supervillain@riseup.net'
__license__ = 'MIT'
__copyright__ = 'Copyright 2018 Viktor Villainov'
+258
View File
@@ -0,0 +1,258 @@
import asyncio
from . import sam
from . import exceptions
from . import utils
from .log import logger
def parse_reply(data):
if not data:
raise ConnectionAbortedError("Empty response: SAM API went offline")
try:
msg = sam.Message(data.decode().strip())
logger.debug("SAM reply: "+str(msg))
except:
raise ConnectionAbortedError("Invalid SAM response")
return msg
async def get_sam_socket(sam_address=sam.DEFAULT_ADDRESS, loop=None):
"""A couroutine used to create a new SAM socket.
:param sam_address: (optional) SAM API address
:param loop: (optional) event loop instance
:return: A (reader, writer) pair
"""
reader, writer = await asyncio.open_connection(*sam_address, loop=loop)
writer.write(sam.hello("3.1", "3.1"))
reply = parse_reply(await reader.readline())
if reply.ok:
return (reader, writer)
else:
writer.close()
raise exceptions.SAM_EXCEPTIONS[reply["RESULT"]]()
async def dest_lookup(domain, sam_address=sam.DEFAULT_ADDRESS,
loop=None):
"""A coroutine used to lookup a full I2P destination by .i2p domain or
.b32.i2p address.
:param domain: Address to be resolved, can be a .i2p domain or a .b32.i2p
address.
:param sam_address: (optional) SAM API address
:param loop: (optional) Event loop instance
:return: An instance of :class:`Destination`
"""
reader, writer = await get_sam_socket(sam_address, loop)
writer.write(sam.naming_lookup(domain))
reply = parse_reply(await reader.readline())
writer.close()
if reply.ok:
return sam.Destination(reply["VALUE"])
else:
raise exceptions.SAM_EXCEPTIONS[reply["RESULT"]]()
async def new_destination(sam_address=sam.DEFAULT_ADDRESS, loop=None,
sig_type=sam.Destination.default_sig_type):
"""A coroutine used to generate a new destination with a private key of a
chosen signature type.
:param sam_address: (optional) SAM API address
:param loop: (optional) Event loop instance
:param sig_type: (optional) Signature type
:return: An instance of :class:`Destination`
"""
reader, writer = await get_sam_socket(sam_address, loop)
writer.write(sam.dest_generate(sig_type))
reply = parse_reply(await reader.readline())
writer.close()
return sam.Destination(reply["PRIV"], has_private_key=True)
async def create_session(session_name, sam_address=sam.DEFAULT_ADDRESS,
loop=None, style="STREAM",
signature_type=sam.Destination.default_sig_type,
destination=None, options={}):
"""A coroutine used to create a new SAM session.
:param session_name: Session nick name
:param sam_address: (optional) SAM API address
:param loop: (optional) Event loop instance
:param style: (optional) Session style, can be STREAM, DATAGRAM, RAW
:param signature_type: (optional) If the destination is TRANSIENT, this
signature type is used
:param destination: (optional) Destination to use in this session. Can be
a base64 encoded string, :class:`Destination`
instance or None. TRANSIENT destination is used when it
is None.
:param options: (optional) A dict object with i2cp options
:return: A (reader, writer) pair
"""
logger.debug("Creating session {}".format(session_name))
if destination:
if type(destination) == sam.Destination:
destination = destination
else:
destination = sam.Destination(
destination, has_private_key=True)
dest_string = destination.private_key.base64
else:
dest_string = sam.TRANSIENT_DESTINATION
options = " ".join(["{}={}".format(k, v) for k, v in options.items()])
reader, writer = await get_sam_socket(sam_address, loop)
writer.write(sam.session_create(
style, session_name, dest_string, options))
reply = parse_reply(await reader.readline())
if reply.ok:
if not destination:
destination = sam.Destination(
reply["DESTINATION"], has_private_key=True)
logger.debug(destination.base32)
logger.debug("Session created {}".format(session_name))
return (reader, writer)
else:
writer.close()
raise exceptions.SAM_EXCEPTIONS[reply["RESULT"]]()
async def stream_connect(session_name, destination,
sam_address=sam.DEFAULT_ADDRESS, loop=None):
"""A coroutine used to connect to a remote I2P destination.
:param session_name: Session nick name
:param destination: I2P destination to connect to
:param sam_address: (optional) SAM API address
:param loop: (optional) Event loop instance
:return: A (reader, writer) pair
"""
logger.debug("Connecting stream {}".format(session_name))
if isinstance(destination, str) and not destination.endswith(".i2p"):
destination = sam.Destination(destination)
elif isinstance(destination, str):
destination = await dest_lookup(destination, sam_address, loop)
reader, writer = await get_sam_socket(sam_address, loop)
writer.write(sam.stream_connect(session_name, destination.base64,
silent="false"))
reply = parse_reply(await reader.readline())
if reply.ok:
logger.debug("Stream connected {}".format(session_name))
return (reader, writer)
else:
writer.close()
raise exceptions.SAM_EXCEPTIONS[reply["RESULT"]]()
async def stream_accept(session_name, sam_address=sam.DEFAULT_ADDRESS,
loop=None):
"""A coroutine used to accept a connection from the I2P network.
:param session_name: Session nick name
:param sam_address: (optional) SAM API address
:param loop: (optional) Event loop instance
:return: A (reader, writer) pair
"""
reader, writer = await get_sam_socket(sam_address, loop)
writer.write(sam.stream_accept(session_name, silent="false"))
reply = parse_reply(await reader.readline())
if reply.ok:
return (reader, writer)
else:
writer.close()
raise exceptions.SAM_EXCEPTIONS[reply["RESULT"]]()
### Context managers
class Session:
"""Async SAM session context manager.
:param session_name: Session nick name
:param sam_address: (optional) SAM API address
:param loop: (optional) Event loop instance
:param style: (optional) Session style, can be STREAM, DATAGRAM, RAW
:param signature_type: (optional) If the destination is TRANSIENT, this
signature type is used
:param destination: (optional) Destination to use in this session. Can be
a base64 encoded string, :class:`Destination`
instance or None. TRANSIENT destination is used when it
is None.
:param options: (optional) A dict object with i2cp options
:return: :class:`Session` object
"""
def __init__(self, session_name, sam_address=sam.DEFAULT_ADDRESS,
loop=None, style="STREAM",
signature_type=sam.Destination.default_sig_type,
destination=None, options={}):
self.session_name = session_name
self.sam_address = sam_address
self.loop = loop
self.style = style
self.signature_type = signature_type
self.destination = destination
self.options = options
async def __aenter__(self):
self.reader, self.writer = await create_session(self.session_name,
sam_address=self.sam_address, loop=self.loop, style=self.style,
signature_type=self.signature_type,
destination=self.destination, options=self.options)
return self
async def __aexit__(self, exc_type, exc, tb):
### TODO handle exceptions
self.writer.close()
class StreamConnection:
"""Async stream connection context manager.
:param session_name: Session nick name
:param destination: I2P destination to connect to
:param sam_address: (optional) SAM API address
:param loop: (optional) Event loop instance
:return: :class:`StreamConnection` object
"""
def __init__(self, session_name, destination,
sam_address=sam.DEFAULT_ADDRESS, loop=None):
self.session_name = session_name
self.sam_address = sam_address
self.loop = loop
self.destination = destination
async def __aenter__(self):
self.reader, self.writer = await stream_connect(self.session_name,
self.destination, sam_address=self.sam_address, loop=self.loop)
self.read = self.reader.read
self.write = self.writer.write
return self
async def __aexit__(self, exc_type, exc, tb):
### TODO handle exceptions
self.writer.close()
class StreamAcceptor:
"""Async stream acceptor context manager.
:param session_name: Session nick name
:param sam_address: (optional) SAM API address
:param loop: (optional) Event loop instance
:return: :class:`StreamAcceptor` object
"""
def __init__(self, session_name, sam_address=sam.DEFAULT_ADDRESS,
loop=None):
self.session_name = session_name
self.sam_address = sam_address
self.loop = loop
async def __aenter__(self):
self.reader, self.writer = await stream_accept(self.session_name,
sam_address=self.sam_address, loop=self.loop)
self.read = self.reader.read
self.write = self.writer.write
return self
async def __aexit__(self, exc_type, exc, tb):
### TODO handle exceptions
self.writer.close()
+44
View File
@@ -0,0 +1,44 @@
# SAM exceptions
class SAMException(IOError):
"""Base class for SAM exceptions"""
class CantReachPeer(SAMException):
"""The peer exists, but cannot be reached"""
class DuplicatedDest(SAMException):
"""The specified Destination is already in use"""
class DuplicatedId(SAMException):
"""The nickname is already associated with a session"""
class I2PError(SAMException):
"""A generic I2P error"""
class InvalidId(SAMException):
"""STREAM SESSION ID doesn't exist"""
class InvalidKey(SAMException):
"""The specified key is not valid (bad format, etc.)"""
class KeyNotFound(SAMException):
"""The naming system can't resolve the given name"""
class PeerNotFound(SAMException):
"""The peer cannot be found on the network"""
class Timeout(SAMException):
"""The peer cannot be found on the network"""
SAM_EXCEPTIONS = {
"CANT_REACH_PEER": CantReachPeer,
"DUPLICATED_DEST": DuplicatedDest,
"DUPLICATED_ID": DuplicatedId,
"I2P_ERROR": I2PError,
"INVALID_ID": InvalidId,
"INVALID_KEY": InvalidKey,
"KEY_NOT_FOUND": KeyNotFound,
"PEER_NOT_FOUND": PeerNotFound,
"TIMEOUT": Timeout,
}
+5
View File
@@ -0,0 +1,5 @@
"""Logging configuration."""
import logging
# Name the logger after the package.
logger = logging.getLogger(__package__)
+147
View File
@@ -0,0 +1,147 @@
from base64 import b64decode, b64encode, b32encode
from hashlib import sha256
import struct
import re
I2P_B64_CHARS = "-~"
def i2p_b64encode(x):
"""Encode I2P destination"""
return b64encode(x, altchars=I2P_B64_CHARS.encode()).decode()
def i2p_b64decode(x):
"""Decode I2P destination"""
return b64decode(x, altchars=I2P_B64_CHARS, validate=True)
SAM_BUFSIZE = 4096
DEFAULT_ADDRESS = ("127.0.0.1", 7656)
DEFAULT_MIN_VER = "3.1"
DEFAULT_MAX_VER = "3.1"
TRANSIENT_DESTINATION = "TRANSIENT"
VALID_BASE32_ADDRESS = re.compile(r"^([a-zA-Z0-9]{52}).b32.i2p$")
VALID_BASE64_ADDRESS = re.compile(r"^([a-zA-Z0-9-~=]{516,528})$")
class Message(object):
"""Parse SAM message to an object"""
def __init__(self, s):
self.opts = {}
if type(s) != str:
self._reply_string = s.decode().strip()
else:
self._reply_string = s
self.cmd, self.action, opts = self._reply_string.split(" ", 2)
for v in opts.split(" "):
data = v.split("=", 1) if "=" in v else (v, True)
self.opts[data[0]] = data[1]
def __getitem__(self, key):
return self.opts[key]
@property
def ok(self):
return self["RESULT"] == "OK"
def __repr__(self):
return self._reply_string
# SAM request messages
def hello(min_version, max_version):
return "HELLO VERSION MIN={} MAX={}\n".format(min_version,
max_version).encode()
def session_create(style, session_id, destination, options=""):
return "SESSION CREATE STYLE={} ID={} DESTINATION={} {}\n".format(
style, session_id, destination, options).encode()
def stream_connect(session_id, destination, silent="false"):
return "STREAM CONNECT ID={} DESTINATION={} SILENT={}\n".format(
session_id, destination, silent).encode()
def stream_accept(session_id, silent="false"):
return "STREAM ACCEPT ID={} SILENT={}\n".format(session_id, silent).encode()
def stream_forward(session_id, port, options=""):
return "STREAM FORWARD ID={} PORT={} {}\n".format(
session_id, port, options).encode()
def naming_lookup(name):
return "NAMING LOOKUP NAME={}\n".format(name).encode()
def dest_generate(signature_type):
return "DEST GENERATE SIGNATURE_TYPE={}\n".format(signature_type).encode()
class Destination(object):
"""I2P destination
https://geti2p.net/spec/common-structures#destination
:param data: (optional) Base64 encoded data or binary data
:param path: (optional) A path to a file with binary data
:param has_private_key: (optional) Does data have a private key?
"""
ECDSA_SHA256_P256 = 1
ECDSA_SHA384_P384 = 2
ECDSA_SHA512_P521 = 3
EdDSA_SHA512_Ed25519 = 7
default_sig_type = EdDSA_SHA512_Ed25519
_pubkey_size = 256
_signkey_size = 128
_min_cert_size = 3
def __init__(self, data=None, path=None, has_private_key=False):
#: Binary destination
self.data = bytes()
#: Base64 encoded destination
self.base64 = ""
#: :class:`RNS.vendor.i2plib.PrivateKey` instance or None
self.private_key = None
if path:
with open(path, "rb") as f: data = f.read()
if data and has_private_key:
self.private_key = PrivateKey(data)
cert_len = struct.unpack("!H", self.private_key.data[385:387])[0]
data = self.private_key.data[:387+cert_len]
if not data:
raise Exception("Can't create a destination with no data")
self.data = data if type(data) == bytes else i2p_b64decode(data)
self.base64 = data if type(data) == str else i2p_b64encode(data)
def __repr__(self):
return "<Destination: {}>".format(self.base32)
@property
def base32(self):
"""Base32 destination hash of this destination"""
desthash = sha256(self.data).digest()
return b32encode(desthash).decode()[:52].lower()
class PrivateKey(object):
"""I2P private key
https://geti2p.net/spec/common-structures#keysandcert
:param data: Base64 encoded data or binary data
"""
def __init__(self, data):
#: Binary private key
self.data = data if type(data) == bytes else i2p_b64decode(data)
#: Base64 encoded private key
self.base64 = data if type(data) == str else i2p_b64encode(data)
+202
View File
@@ -0,0 +1,202 @@
import logging
import asyncio
import argparse
from . import sam
from . import aiosam
from . import utils
from .log import logger
BUFFER_SIZE = 65536
async def proxy_data(reader, writer):
"""Proxy data from reader to writer"""
try:
while True:
data = await reader.read(BUFFER_SIZE)
if not data:
break
writer.write(data)
except Exception as e:
logger.debug('proxy_data_task exception {}'.format(e))
finally:
try:
writer.close()
except RuntimeError:
pass
logger.debug('close connection')
class I2PTunnel(object):
"""Base I2P Tunnel object, not to be used directly
:param local_address: A local address to use for a tunnel.
E.g. ("127.0.0.1", 6668)
:param destination: (optional) Destination to use for this tunnel. Can be
a base64 encoded string, :class:`Destination`
instance or None. A new destination is created when it
is None.
:param session_name: (optional) Session nick name. A new session nickname is
generated if not specified.
:param options: (optional) A dict object with i2cp options
:param loop: (optional) Event loop instance
:param sam_address: (optional) SAM API address
"""
def __init__(self, local_address, destination=None, session_name=None,
options={}, loop=None, sam_address=sam.DEFAULT_ADDRESS):
self.local_address = local_address
self.destination = destination
self.session_name = session_name or utils.generate_session_id()
self.options = options
self.loop = loop
self.sam_address = sam_address
async def _pre_run(self):
if not self.destination:
self.destination = await aiosam.new_destination(
sam_address=self.sam_address, loop=self.loop)
_, self.session_writer = await aiosam.create_session(
self.session_name, style=self.style, options=self.options,
sam_address=self.sam_address,
loop=self.loop, destination=self.destination)
def stop(self):
"""Stop the tunnel"""
self.session_writer.close()
class ClientTunnel(I2PTunnel):
"""Client tunnel, a subclass of tunnel.I2PTunnel
If you run a client tunnel with a local address ("127.0.0.1", 6668) and
a remote destination "irc.echelon.i2p", all connections to 127.0.0.1:6668
will be proxied to irc.echelon.i2p.
:param remote_destination: Remote I2P destination, can be either .i2p
domain, .b32.i2p address, base64 destination or
:class:`Destination` instance
"""
def __init__(self, remote_destination, *args, **kwargs):
super().__init__(*args, **kwargs)
self.style = "STREAM"
self.remote_destination = remote_destination
async def run(self):
"""A coroutine used to run the tunnel"""
await self._pre_run()
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)
self.server = await asyncio.start_server(handle_client, *self.local_address, loop=self.loop)
def stop(self):
super().stop()
self.server.close()
class ServerTunnel(I2PTunnel):
"""Server tunnel, a subclass of tunnel.I2PTunnel
If you want to expose a local service 127.0.0.1:80 to the I2P network, run
a server tunnel with a local address ("127.0.0.1", 80). If you don't
provide a private key or a session name, it will use a TRANSIENT
destination.
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.style = "STREAM"
async def run(self):
"""A coroutine used to run the tunnel"""
await self._pre_run()
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:
remote_reader, remote_writer = await asyncio.wait_for(
asyncio.open_connection(
host=self.local_address[0],
port=self.local_address[1], loop=self.loop),
timeout=5, loop=self.loop)
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()
async def server_loop():
try:
while True:
client_reader, client_writer = await aiosam.stream_accept(
self.session_name, sam_address=self.sam_address,
loop=self.loop)
incoming = await client_reader.read(BUFFER_SIZE)
asyncio.ensure_future(handle_client(
incoming, client_reader, client_writer), loop=self.loop)
except asyncio.CancelledError:
pass
self.server_loop = asyncio.ensure_future(server_loop(), loop=self.loop)
def stop(self):
super().stop()
self.server_loop.cancel()
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('type', metavar="TYPE", choices=('server', 'client'),
help="Tunnel type (server or client)")
parser.add_argument('address', metavar="ADDRESS",
help="Local address (e.g. 127.0.0.1:8000)")
parser.add_argument('--debug', '-d', action='store_true',
help='Debugging')
parser.add_argument('--key', '-k', default='', metavar='PRIVATE_KEY',
help='Path to private key file')
parser.add_argument('--destination', '-D', default='',
metavar='DESTINATION', help='Remote destination')
args = parser.parse_args()
SAM_ADDRESS = utils.get_sam_address()
logging.basicConfig(level=logging.DEBUG if args.debug else logging.INFO)
loop = asyncio.get_event_loop()
loop.set_debug(args.debug)
if args.key:
destination = sam.Destination(path=args.key, has_private_key=True)
else:
destination = None
local_address = utils.address_from_string(args.address)
if args.type == "client":
tunnel = ClientTunnel(args.destination, local_address, loop=loop,
destination=destination, sam_address=SAM_ADDRESS)
elif args.type == "server":
tunnel = ServerTunnel(local_address, loop=loop, destination=destination,
sam_address=SAM_ADDRESS)
asyncio.ensure_future(tunnel.run(), loop=loop)
try:
loop.run_forever()
except KeyboardInterrupt:
tunnel.stop()
finally:
loop.stop()
loop.close()
+42
View File
@@ -0,0 +1,42 @@
import socket
import os
import random
import string
from . import sam
def get_free_port():
"""Get a free port on your local host"""
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(('', 0))
free_port = s.getsockname()[1]
s.close()
return free_port
def is_address_accessible(address):
"""Check if address is accessible or down"""
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
is_accessible = s.connect_ex(address) == 0
s.close()
return is_accessible
def address_from_string(address_string):
"""Address tuple from host:port string"""
address = address_string.split(":")
return (address[0], int(address[1]))
def get_sam_address():
"""
Get SAM address from environment variable I2P_SAM_ADDRESS, or use a default
value
"""
value = os.getenv("I2P_SAM_ADDRESS")
return address_from_string(value) if value else sam.DEFAULT_ADDRESS
def generate_session_id(length=6):
"""Generate random session id"""
rand = random.SystemRandom()
sid = [rand.choice(string.ascii_letters) for _ in range(length)]
return "reticulum-" + "".join(sid)
+38
View File
@@ -0,0 +1,38 @@
def get_platform():
from os import environ
if "ANDROID_ARGUMENT" in environ:
return "android"
elif "ANDROID_ROOT" in environ:
return "android"
else:
import sys
return sys.platform
def is_darwin():
if get_platform() == "darwin":
return True
else:
return False
def is_android():
if get_platform() == "android":
return True
else:
return False
def is_windows():
if str(get_platform()).startswith("win"):
return True
else:
return False
def platform_checks():
if is_windows():
import sys
if sys.version_info.major >= 3 and sys.version_info.minor >= 8:
pass
else:
import RNS
RNS.log("On Windows, Reticulum requires Python 3.8 or higher.", RNS.LOG_ERROR)
RNS.log("Please update Python to run Reticulum.", RNS.LOG_ERROR)
RNS.panic()
+998
View File
@@ -0,0 +1,998 @@
# Copyright (c) 2010-2020 Benjamin Peterson
#
# 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.
"""Utilities for writing code that runs on Python 2 and 3"""
from __future__ import absolute_import
import functools
import itertools
import operator
import sys
import types
__author__ = "Benjamin Peterson <benjamin@python.org>"
__version__ = "1.16.0"
# Useful for very coarse version differentiation.
PY2 = sys.version_info[0] == 2
PY3 = sys.version_info[0] == 3
PY34 = sys.version_info[0:2] >= (3, 4)
if PY3:
string_types = str,
integer_types = int,
class_types = type,
text_type = str
binary_type = bytes
MAXSIZE = sys.maxsize
else:
string_types = basestring,
integer_types = (int, long)
class_types = (type, types.ClassType)
text_type = unicode
binary_type = str
if sys.platform.startswith("java"):
# Jython always uses 32 bits.
MAXSIZE = int((1 << 31) - 1)
else:
# It's possible to have sizeof(long) != sizeof(Py_ssize_t).
class X(object):
def __len__(self):
return 1 << 31
try:
len(X())
except OverflowError:
# 32-bit
MAXSIZE = int((1 << 31) - 1)
else:
# 64-bit
MAXSIZE = int((1 << 63) - 1)
del X
if PY34:
from importlib.util import spec_from_loader
else:
spec_from_loader = None
def _add_doc(func, doc):
"""Add documentation to a function."""
func.__doc__ = doc
def _import_module(name):
"""Import module, returning the module after the last dot."""
__import__(name)
return sys.modules[name]
class _LazyDescr(object):
def __init__(self, name):
self.name = name
def __get__(self, obj, tp):
result = self._resolve()
setattr(obj, self.name, result) # Invokes __set__.
try:
# This is a bit ugly, but it avoids running this again by
# removing this descriptor.
delattr(obj.__class__, self.name)
except AttributeError:
pass
return result
class MovedModule(_LazyDescr):
def __init__(self, name, old, new=None):
super(MovedModule, self).__init__(name)
if PY3:
if new is None:
new = name
self.mod = new
else:
self.mod = old
def _resolve(self):
return _import_module(self.mod)
def __getattr__(self, attr):
_module = self._resolve()
value = getattr(_module, attr)
setattr(self, attr, value)
return value
class _LazyModule(types.ModuleType):
def __init__(self, name):
super(_LazyModule, self).__init__(name)
self.__doc__ = self.__class__.__doc__
def __dir__(self):
attrs = ["__doc__", "__name__"]
attrs += [attr.name for attr in self._moved_attributes]
return attrs
# Subclasses should override this
_moved_attributes = []
class MovedAttribute(_LazyDescr):
def __init__(self, name, old_mod, new_mod, old_attr=None, new_attr=None):
super(MovedAttribute, self).__init__(name)
if PY3:
if new_mod is None:
new_mod = name
self.mod = new_mod
if new_attr is None:
if old_attr is None:
new_attr = name
else:
new_attr = old_attr
self.attr = new_attr
else:
self.mod = old_mod
if old_attr is None:
old_attr = name
self.attr = old_attr
def _resolve(self):
module = _import_module(self.mod)
return getattr(module, self.attr)
class _SixMetaPathImporter(object):
"""
A meta path importer to import six.moves and its submodules.
This class implements a PEP302 finder and loader. It should be compatible
with Python 2.5 and all existing versions of Python3
"""
def __init__(self, six_module_name):
self.name = six_module_name
self.known_modules = {}
def _add_module(self, mod, *fullnames):
for fullname in fullnames:
self.known_modules[self.name + "." + fullname] = mod
def _get_module(self, fullname):
return self.known_modules[self.name + "." + fullname]
def find_module(self, fullname, path=None):
if fullname in self.known_modules:
return self
return None
def find_spec(self, fullname, path, target=None):
if fullname in self.known_modules:
return spec_from_loader(fullname, self)
return None
def __get_module(self, fullname):
try:
return self.known_modules[fullname]
except KeyError:
raise ImportError("This loader does not know module " + fullname)
def load_module(self, fullname):
try:
# in case of a reload
return sys.modules[fullname]
except KeyError:
pass
mod = self.__get_module(fullname)
if isinstance(mod, MovedModule):
mod = mod._resolve()
else:
mod.__loader__ = self
sys.modules[fullname] = mod
return mod
def is_package(self, fullname):
"""
Return true, if the named module is a package.
We need this method to get correct spec objects with
Python 3.4 (see PEP451)
"""
return hasattr(self.__get_module(fullname), "__path__")
def get_code(self, fullname):
"""Return None
Required, if is_package is implemented"""
self.__get_module(fullname) # eventually raises ImportError
return None
get_source = get_code # same as get_code
def create_module(self, spec):
return self.load_module(spec.name)
def exec_module(self, module):
pass
_importer = _SixMetaPathImporter(__name__)
class _MovedItems(_LazyModule):
"""Lazy loading of moved objects"""
__path__ = [] # mark as package
_moved_attributes = [
MovedAttribute("cStringIO", "cStringIO", "io", "StringIO"),
MovedAttribute("filter", "itertools", "builtins", "ifilter", "filter"),
MovedAttribute("filterfalse", "itertools", "itertools", "ifilterfalse", "filterfalse"),
MovedAttribute("input", "__builtin__", "builtins", "raw_input", "input"),
MovedAttribute("intern", "__builtin__", "sys"),
MovedAttribute("map", "itertools", "builtins", "imap", "map"),
MovedAttribute("getcwd", "os", "os", "getcwdu", "getcwd"),
MovedAttribute("getcwdb", "os", "os", "getcwd", "getcwdb"),
MovedAttribute("getoutput", "commands", "subprocess"),
MovedAttribute("range", "__builtin__", "builtins", "xrange", "range"),
MovedAttribute("reload_module", "__builtin__", "importlib" if PY34 else "imp", "reload"),
MovedAttribute("reduce", "__builtin__", "functools"),
MovedAttribute("shlex_quote", "pipes", "shlex", "quote"),
MovedAttribute("StringIO", "StringIO", "io"),
MovedAttribute("UserDict", "UserDict", "collections"),
MovedAttribute("UserList", "UserList", "collections"),
MovedAttribute("UserString", "UserString", "collections"),
MovedAttribute("xrange", "__builtin__", "builtins", "xrange", "range"),
MovedAttribute("zip", "itertools", "builtins", "izip", "zip"),
MovedAttribute("zip_longest", "itertools", "itertools", "izip_longest", "zip_longest"),
MovedModule("builtins", "__builtin__"),
MovedModule("configparser", "ConfigParser"),
MovedModule("collections_abc", "collections", "collections.abc" if sys.version_info >= (3, 3) else "collections"),
MovedModule("copyreg", "copy_reg"),
MovedModule("dbm_gnu", "gdbm", "dbm.gnu"),
MovedModule("dbm_ndbm", "dbm", "dbm.ndbm"),
MovedModule("_dummy_thread", "dummy_thread", "_dummy_thread" if sys.version_info < (3, 9) else "_thread"),
MovedModule("http_cookiejar", "cookielib", "http.cookiejar"),
MovedModule("http_cookies", "Cookie", "http.cookies"),
MovedModule("html_entities", "htmlentitydefs", "html.entities"),
MovedModule("html_parser", "HTMLParser", "html.parser"),
MovedModule("http_client", "httplib", "http.client"),
MovedModule("email_mime_base", "email.MIMEBase", "email.mime.base"),
MovedModule("email_mime_image", "email.MIMEImage", "email.mime.image"),
MovedModule("email_mime_multipart", "email.MIMEMultipart", "email.mime.multipart"),
MovedModule("email_mime_nonmultipart", "email.MIMENonMultipart", "email.mime.nonmultipart"),
MovedModule("email_mime_text", "email.MIMEText", "email.mime.text"),
MovedModule("BaseHTTPServer", "BaseHTTPServer", "http.server"),
MovedModule("CGIHTTPServer", "CGIHTTPServer", "http.server"),
MovedModule("SimpleHTTPServer", "SimpleHTTPServer", "http.server"),
MovedModule("cPickle", "cPickle", "pickle"),
MovedModule("queue", "Queue"),
MovedModule("reprlib", "repr"),
MovedModule("socketserver", "SocketServer"),
MovedModule("_thread", "thread", "_thread"),
MovedModule("tkinter", "Tkinter"),
MovedModule("tkinter_dialog", "Dialog", "tkinter.dialog"),
MovedModule("tkinter_filedialog", "FileDialog", "tkinter.filedialog"),
MovedModule("tkinter_scrolledtext", "ScrolledText", "tkinter.scrolledtext"),
MovedModule("tkinter_simpledialog", "SimpleDialog", "tkinter.simpledialog"),
MovedModule("tkinter_tix", "Tix", "tkinter.tix"),
MovedModule("tkinter_ttk", "ttk", "tkinter.ttk"),
MovedModule("tkinter_constants", "Tkconstants", "tkinter.constants"),
MovedModule("tkinter_dnd", "Tkdnd", "tkinter.dnd"),
MovedModule("tkinter_colorchooser", "tkColorChooser",
"tkinter.colorchooser"),
MovedModule("tkinter_commondialog", "tkCommonDialog",
"tkinter.commondialog"),
MovedModule("tkinter_tkfiledialog", "tkFileDialog", "tkinter.filedialog"),
MovedModule("tkinter_font", "tkFont", "tkinter.font"),
MovedModule("tkinter_messagebox", "tkMessageBox", "tkinter.messagebox"),
MovedModule("tkinter_tksimpledialog", "tkSimpleDialog",
"tkinter.simpledialog"),
MovedModule("urllib_parse", __name__ + ".moves.urllib_parse", "urllib.parse"),
MovedModule("urllib_error", __name__ + ".moves.urllib_error", "urllib.error"),
MovedModule("urllib", __name__ + ".moves.urllib", __name__ + ".moves.urllib"),
MovedModule("urllib_robotparser", "robotparser", "urllib.robotparser"),
MovedModule("xmlrpc_client", "xmlrpclib", "xmlrpc.client"),
MovedModule("xmlrpc_server", "SimpleXMLRPCServer", "xmlrpc.server"),
]
# Add windows specific modules.
if sys.platform == "win32":
_moved_attributes += [
MovedModule("winreg", "_winreg"),
]
for attr in _moved_attributes:
setattr(_MovedItems, attr.name, attr)
if isinstance(attr, MovedModule):
_importer._add_module(attr, "moves." + attr.name)
del attr
_MovedItems._moved_attributes = _moved_attributes
moves = _MovedItems(__name__ + ".moves")
_importer._add_module(moves, "moves")
class Module_six_moves_urllib_parse(_LazyModule):
"""Lazy loading of moved objects in six.moves.urllib_parse"""
_urllib_parse_moved_attributes = [
MovedAttribute("ParseResult", "urlparse", "urllib.parse"),
MovedAttribute("SplitResult", "urlparse", "urllib.parse"),
MovedAttribute("parse_qs", "urlparse", "urllib.parse"),
MovedAttribute("parse_qsl", "urlparse", "urllib.parse"),
MovedAttribute("urldefrag", "urlparse", "urllib.parse"),
MovedAttribute("urljoin", "urlparse", "urllib.parse"),
MovedAttribute("urlparse", "urlparse", "urllib.parse"),
MovedAttribute("urlsplit", "urlparse", "urllib.parse"),
MovedAttribute("urlunparse", "urlparse", "urllib.parse"),
MovedAttribute("urlunsplit", "urlparse", "urllib.parse"),
MovedAttribute("quote", "urllib", "urllib.parse"),
MovedAttribute("quote_plus", "urllib", "urllib.parse"),
MovedAttribute("unquote", "urllib", "urllib.parse"),
MovedAttribute("unquote_plus", "urllib", "urllib.parse"),
MovedAttribute("unquote_to_bytes", "urllib", "urllib.parse", "unquote", "unquote_to_bytes"),
MovedAttribute("urlencode", "urllib", "urllib.parse"),
MovedAttribute("splitquery", "urllib", "urllib.parse"),
MovedAttribute("splittag", "urllib", "urllib.parse"),
MovedAttribute("splituser", "urllib", "urllib.parse"),
MovedAttribute("splitvalue", "urllib", "urllib.parse"),
MovedAttribute("uses_fragment", "urlparse", "urllib.parse"),
MovedAttribute("uses_netloc", "urlparse", "urllib.parse"),
MovedAttribute("uses_params", "urlparse", "urllib.parse"),
MovedAttribute("uses_query", "urlparse", "urllib.parse"),
MovedAttribute("uses_relative", "urlparse", "urllib.parse"),
]
for attr in _urllib_parse_moved_attributes:
setattr(Module_six_moves_urllib_parse, attr.name, attr)
del attr
Module_six_moves_urllib_parse._moved_attributes = _urllib_parse_moved_attributes
_importer._add_module(Module_six_moves_urllib_parse(__name__ + ".moves.urllib_parse"),
"moves.urllib_parse", "moves.urllib.parse")
class Module_six_moves_urllib_error(_LazyModule):
"""Lazy loading of moved objects in six.moves.urllib_error"""
_urllib_error_moved_attributes = [
MovedAttribute("URLError", "urllib2", "urllib.error"),
MovedAttribute("HTTPError", "urllib2", "urllib.error"),
MovedAttribute("ContentTooShortError", "urllib", "urllib.error"),
]
for attr in _urllib_error_moved_attributes:
setattr(Module_six_moves_urllib_error, attr.name, attr)
del attr
Module_six_moves_urllib_error._moved_attributes = _urllib_error_moved_attributes
_importer._add_module(Module_six_moves_urllib_error(__name__ + ".moves.urllib.error"),
"moves.urllib_error", "moves.urllib.error")
class Module_six_moves_urllib_request(_LazyModule):
"""Lazy loading of moved objects in six.moves.urllib_request"""
_urllib_request_moved_attributes = [
MovedAttribute("urlopen", "urllib2", "urllib.request"),
MovedAttribute("install_opener", "urllib2", "urllib.request"),
MovedAttribute("build_opener", "urllib2", "urllib.request"),
MovedAttribute("pathname2url", "urllib", "urllib.request"),
MovedAttribute("url2pathname", "urllib", "urllib.request"),
MovedAttribute("getproxies", "urllib", "urllib.request"),
MovedAttribute("Request", "urllib2", "urllib.request"),
MovedAttribute("OpenerDirector", "urllib2", "urllib.request"),
MovedAttribute("HTTPDefaultErrorHandler", "urllib2", "urllib.request"),
MovedAttribute("HTTPRedirectHandler", "urllib2", "urllib.request"),
MovedAttribute("HTTPCookieProcessor", "urllib2", "urllib.request"),
MovedAttribute("ProxyHandler", "urllib2", "urllib.request"),
MovedAttribute("BaseHandler", "urllib2", "urllib.request"),
MovedAttribute("HTTPPasswordMgr", "urllib2", "urllib.request"),
MovedAttribute("HTTPPasswordMgrWithDefaultRealm", "urllib2", "urllib.request"),
MovedAttribute("AbstractBasicAuthHandler", "urllib2", "urllib.request"),
MovedAttribute("HTTPBasicAuthHandler", "urllib2", "urllib.request"),
MovedAttribute("ProxyBasicAuthHandler", "urllib2", "urllib.request"),
MovedAttribute("AbstractDigestAuthHandler", "urllib2", "urllib.request"),
MovedAttribute("HTTPDigestAuthHandler", "urllib2", "urllib.request"),
MovedAttribute("ProxyDigestAuthHandler", "urllib2", "urllib.request"),
MovedAttribute("HTTPHandler", "urllib2", "urllib.request"),
MovedAttribute("HTTPSHandler", "urllib2", "urllib.request"),
MovedAttribute("FileHandler", "urllib2", "urllib.request"),
MovedAttribute("FTPHandler", "urllib2", "urllib.request"),
MovedAttribute("CacheFTPHandler", "urllib2", "urllib.request"),
MovedAttribute("UnknownHandler", "urllib2", "urllib.request"),
MovedAttribute("HTTPErrorProcessor", "urllib2", "urllib.request"),
MovedAttribute("urlretrieve", "urllib", "urllib.request"),
MovedAttribute("urlcleanup", "urllib", "urllib.request"),
MovedAttribute("URLopener", "urllib", "urllib.request"),
MovedAttribute("FancyURLopener", "urllib", "urllib.request"),
MovedAttribute("proxy_bypass", "urllib", "urllib.request"),
MovedAttribute("parse_http_list", "urllib2", "urllib.request"),
MovedAttribute("parse_keqv_list", "urllib2", "urllib.request"),
]
for attr in _urllib_request_moved_attributes:
setattr(Module_six_moves_urllib_request, attr.name, attr)
del attr
Module_six_moves_urllib_request._moved_attributes = _urllib_request_moved_attributes
_importer._add_module(Module_six_moves_urllib_request(__name__ + ".moves.urllib.request"),
"moves.urllib_request", "moves.urllib.request")
class Module_six_moves_urllib_response(_LazyModule):
"""Lazy loading of moved objects in six.moves.urllib_response"""
_urllib_response_moved_attributes = [
MovedAttribute("addbase", "urllib", "urllib.response"),
MovedAttribute("addclosehook", "urllib", "urllib.response"),
MovedAttribute("addinfo", "urllib", "urllib.response"),
MovedAttribute("addinfourl", "urllib", "urllib.response"),
]
for attr in _urllib_response_moved_attributes:
setattr(Module_six_moves_urllib_response, attr.name, attr)
del attr
Module_six_moves_urllib_response._moved_attributes = _urllib_response_moved_attributes
_importer._add_module(Module_six_moves_urllib_response(__name__ + ".moves.urllib.response"),
"moves.urllib_response", "moves.urllib.response")
class Module_six_moves_urllib_robotparser(_LazyModule):
"""Lazy loading of moved objects in six.moves.urllib_robotparser"""
_urllib_robotparser_moved_attributes = [
MovedAttribute("RobotFileParser", "robotparser", "urllib.robotparser"),
]
for attr in _urllib_robotparser_moved_attributes:
setattr(Module_six_moves_urllib_robotparser, attr.name, attr)
del attr
Module_six_moves_urllib_robotparser._moved_attributes = _urllib_robotparser_moved_attributes
_importer._add_module(Module_six_moves_urllib_robotparser(__name__ + ".moves.urllib.robotparser"),
"moves.urllib_robotparser", "moves.urllib.robotparser")
class Module_six_moves_urllib(types.ModuleType):
"""Create a six.moves.urllib namespace that resembles the Python 3 namespace"""
__path__ = [] # mark as package
parse = _importer._get_module("moves.urllib_parse")
error = _importer._get_module("moves.urllib_error")
request = _importer._get_module("moves.urllib_request")
response = _importer._get_module("moves.urllib_response")
robotparser = _importer._get_module("moves.urllib_robotparser")
def __dir__(self):
return ['parse', 'error', 'request', 'response', 'robotparser']
_importer._add_module(Module_six_moves_urllib(__name__ + ".moves.urllib"),
"moves.urllib")
def add_move(move):
"""Add an item to six.moves."""
setattr(_MovedItems, move.name, move)
def remove_move(name):
"""Remove item from six.moves."""
try:
delattr(_MovedItems, name)
except AttributeError:
try:
del moves.__dict__[name]
except KeyError:
raise AttributeError("no such move, %r" % (name,))
if PY3:
_meth_func = "__func__"
_meth_self = "__self__"
_func_closure = "__closure__"
_func_code = "__code__"
_func_defaults = "__defaults__"
_func_globals = "__globals__"
else:
_meth_func = "im_func"
_meth_self = "im_self"
_func_closure = "func_closure"
_func_code = "func_code"
_func_defaults = "func_defaults"
_func_globals = "func_globals"
try:
advance_iterator = next
except NameError:
def advance_iterator(it):
return it.next()
next = advance_iterator
try:
callable = callable
except NameError:
def callable(obj):
return any("__call__" in klass.__dict__ for klass in type(obj).__mro__)
if PY3:
def get_unbound_function(unbound):
return unbound
create_bound_method = types.MethodType
def create_unbound_method(func, cls):
return func
Iterator = object
else:
def get_unbound_function(unbound):
return unbound.im_func
def create_bound_method(func, obj):
return types.MethodType(func, obj, obj.__class__)
def create_unbound_method(func, cls):
return types.MethodType(func, None, cls)
class Iterator(object):
def next(self):
return type(self).__next__(self)
callable = callable
_add_doc(get_unbound_function,
"""Get the function out of a possibly unbound function""")
get_method_function = operator.attrgetter(_meth_func)
get_method_self = operator.attrgetter(_meth_self)
get_function_closure = operator.attrgetter(_func_closure)
get_function_code = operator.attrgetter(_func_code)
get_function_defaults = operator.attrgetter(_func_defaults)
get_function_globals = operator.attrgetter(_func_globals)
if PY3:
def iterkeys(d, **kw):
return iter(d.keys(**kw))
def itervalues(d, **kw):
return iter(d.values(**kw))
def iteritems(d, **kw):
return iter(d.items(**kw))
def iterlists(d, **kw):
return iter(d.lists(**kw))
viewkeys = operator.methodcaller("keys")
viewvalues = operator.methodcaller("values")
viewitems = operator.methodcaller("items")
else:
def iterkeys(d, **kw):
return d.iterkeys(**kw)
def itervalues(d, **kw):
return d.itervalues(**kw)
def iteritems(d, **kw):
return d.iteritems(**kw)
def iterlists(d, **kw):
return d.iterlists(**kw)
viewkeys = operator.methodcaller("viewkeys")
viewvalues = operator.methodcaller("viewvalues")
viewitems = operator.methodcaller("viewitems")
_add_doc(iterkeys, "Return an iterator over the keys of a dictionary.")
_add_doc(itervalues, "Return an iterator over the values of a dictionary.")
_add_doc(iteritems,
"Return an iterator over the (key, value) pairs of a dictionary.")
_add_doc(iterlists,
"Return an iterator over the (key, [values]) pairs of a dictionary.")
if PY3:
def b(s):
return s.encode("latin-1")
def u(s):
return s
unichr = chr
import struct
int2byte = struct.Struct(">B").pack
del struct
byte2int = operator.itemgetter(0)
indexbytes = operator.getitem
iterbytes = iter
import io
StringIO = io.StringIO
BytesIO = io.BytesIO
del io
_assertCountEqual = "assertCountEqual"
if sys.version_info[1] <= 1:
_assertRaisesRegex = "assertRaisesRegexp"
_assertRegex = "assertRegexpMatches"
_assertNotRegex = "assertNotRegexpMatches"
else:
_assertRaisesRegex = "assertRaisesRegex"
_assertRegex = "assertRegex"
_assertNotRegex = "assertNotRegex"
else:
def b(s):
return s
# Workaround for standalone backslash
def u(s):
return unicode(s.replace(r'\\', r'\\\\'), "unicode_escape")
unichr = unichr
int2byte = chr
def byte2int(bs):
return ord(bs[0])
def indexbytes(buf, i):
return ord(buf[i])
iterbytes = functools.partial(itertools.imap, ord)
import StringIO
StringIO = BytesIO = StringIO.StringIO
_assertCountEqual = "assertItemsEqual"
_assertRaisesRegex = "assertRaisesRegexp"
_assertRegex = "assertRegexpMatches"
_assertNotRegex = "assertNotRegexpMatches"
_add_doc(b, """Byte literal""")
_add_doc(u, """Text literal""")
def assertCountEqual(self, *args, **kwargs):
return getattr(self, _assertCountEqual)(*args, **kwargs)
def assertRaisesRegex(self, *args, **kwargs):
return getattr(self, _assertRaisesRegex)(*args, **kwargs)
def assertRegex(self, *args, **kwargs):
return getattr(self, _assertRegex)(*args, **kwargs)
def assertNotRegex(self, *args, **kwargs):
return getattr(self, _assertNotRegex)(*args, **kwargs)
if PY3:
exec_ = getattr(moves.builtins, "exec")
def reraise(tp, value, tb=None):
try:
if value is None:
value = tp()
if value.__traceback__ is not tb:
raise value.with_traceback(tb)
raise value
finally:
value = None
tb = None
else:
def exec_(_code_, _globs_=None, _locs_=None):
"""Execute code in a namespace."""
if _globs_ is None:
frame = sys._getframe(1)
_globs_ = frame.f_globals
if _locs_ is None:
_locs_ = frame.f_locals
del frame
elif _locs_ is None:
_locs_ = _globs_
exec("""exec _code_ in _globs_, _locs_""")
exec_("""def reraise(tp, value, tb=None):
try:
raise tp, value, tb
finally:
tb = None
""")
if sys.version_info[:2] > (3,):
exec_("""def raise_from(value, from_value):
try:
raise value from from_value
finally:
value = None
""")
else:
def raise_from(value, from_value):
raise value
print_ = getattr(moves.builtins, "print", None)
if print_ is None:
def print_(*args, **kwargs):
"""The new-style print function for Python 2.4 and 2.5."""
fp = kwargs.pop("file", sys.stdout)
if fp is None:
return
def write(data):
if not isinstance(data, basestring):
data = str(data)
# If the file has an encoding, encode unicode with it.
if (isinstance(fp, file) and
isinstance(data, unicode) and
fp.encoding is not None):
errors = getattr(fp, "errors", None)
if errors is None:
errors = "strict"
data = data.encode(fp.encoding, errors)
fp.write(data)
want_unicode = False
sep = kwargs.pop("sep", None)
if sep is not None:
if isinstance(sep, unicode):
want_unicode = True
elif not isinstance(sep, str):
raise TypeError("sep must be None or a string")
end = kwargs.pop("end", None)
if end is not None:
if isinstance(end, unicode):
want_unicode = True
elif not isinstance(end, str):
raise TypeError("end must be None or a string")
if kwargs:
raise TypeError("invalid keyword arguments to print()")
if not want_unicode:
for arg in args:
if isinstance(arg, unicode):
want_unicode = True
break
if want_unicode:
newline = unicode("\n")
space = unicode(" ")
else:
newline = "\n"
space = " "
if sep is None:
sep = space
if end is None:
end = newline
for i, arg in enumerate(args):
if i:
write(sep)
write(arg)
write(end)
if sys.version_info[:2] < (3, 3):
_print = print_
def print_(*args, **kwargs):
fp = kwargs.get("file", sys.stdout)
flush = kwargs.pop("flush", False)
_print(*args, **kwargs)
if flush and fp is not None:
fp.flush()
_add_doc(reraise, """Reraise an exception.""")
if sys.version_info[0:2] < (3, 4):
# This does exactly the same what the :func:`py3:functools.update_wrapper`
# function does on Python versions after 3.2. It sets the ``__wrapped__``
# attribute on ``wrapper`` object and it doesn't raise an error if any of
# the attributes mentioned in ``assigned`` and ``updated`` are missing on
# ``wrapped`` object.
def _update_wrapper(wrapper, wrapped,
assigned=functools.WRAPPER_ASSIGNMENTS,
updated=functools.WRAPPER_UPDATES):
for attr in assigned:
try:
value = getattr(wrapped, attr)
except AttributeError:
continue
else:
setattr(wrapper, attr, value)
for attr in updated:
getattr(wrapper, attr).update(getattr(wrapped, attr, {}))
wrapper.__wrapped__ = wrapped
return wrapper
_update_wrapper.__doc__ = functools.update_wrapper.__doc__
def wraps(wrapped, assigned=functools.WRAPPER_ASSIGNMENTS,
updated=functools.WRAPPER_UPDATES):
return functools.partial(_update_wrapper, wrapped=wrapped,
assigned=assigned, updated=updated)
wraps.__doc__ = functools.wraps.__doc__
else:
wraps = functools.wraps
def with_metaclass(meta, *bases):
"""Create a base class with a metaclass."""
# This requires a bit of explanation: the basic idea is to make a dummy
# metaclass for one level of class instantiation that replaces itself with
# the actual metaclass.
class metaclass(type):
def __new__(cls, name, this_bases, d):
if sys.version_info[:2] >= (3, 7):
# This version introduced PEP 560 that requires a bit
# of extra care (we mimic what is done by __build_class__).
resolved_bases = types.resolve_bases(bases)
if resolved_bases is not bases:
d['__orig_bases__'] = bases
else:
resolved_bases = bases
return meta(name, resolved_bases, d)
@classmethod
def __prepare__(cls, name, this_bases):
return meta.__prepare__(name, bases)
return type.__new__(metaclass, 'temporary_class', (), {})
def add_metaclass(metaclass):
"""Class decorator for creating a class with a metaclass."""
def wrapper(cls):
orig_vars = cls.__dict__.copy()
slots = orig_vars.get('__slots__')
if slots is not None:
if isinstance(slots, str):
slots = [slots]
for slots_var in slots:
orig_vars.pop(slots_var)
orig_vars.pop('__dict__', None)
orig_vars.pop('__weakref__', None)
if hasattr(cls, '__qualname__'):
orig_vars['__qualname__'] = cls.__qualname__
return metaclass(cls.__name__, cls.__bases__, orig_vars)
return wrapper
def ensure_binary(s, encoding='utf-8', errors='strict'):
"""Coerce **s** to six.binary_type.
For Python 2:
- `unicode` -> encoded to `str`
- `str` -> `str`
For Python 3:
- `str` -> encoded to `bytes`
- `bytes` -> `bytes`
"""
if isinstance(s, binary_type):
return s
if isinstance(s, text_type):
return s.encode(encoding, errors)
raise TypeError("not expecting type '%s'" % type(s))
def ensure_str(s, encoding='utf-8', errors='strict'):
"""Coerce *s* to `str`.
For Python 2:
- `unicode` -> encoded to `str`
- `str` -> `str`
For Python 3:
- `str` -> `str`
- `bytes` -> decoded to `str`
"""
# Optimization: Fast return for the common case.
if type(s) is str:
return s
if PY2 and isinstance(s, text_type):
return s.encode(encoding, errors)
elif PY3 and isinstance(s, binary_type):
return s.decode(encoding, errors)
elif not isinstance(s, (text_type, binary_type)):
raise TypeError("not expecting type '%s'" % type(s))
return s
def ensure_text(s, encoding='utf-8', errors='strict'):
"""Coerce *s* to six.text_type.
For Python 2:
- `unicode` -> `unicode`
- `str` -> `unicode`
For Python 3:
- `str` -> `str`
- `bytes` -> decoded to `str`
"""
if isinstance(s, binary_type):
return s.decode(encoding, errors)
elif isinstance(s, text_type):
return s
else:
raise TypeError("not expecting type '%s'" % type(s))
def python_2_unicode_compatible(klass):
"""
A class decorator that defines __unicode__ and __str__ methods under Python 2.
Under Python 3 it does nothing.
To support Python 2 and 3 with a single code base, define a __str__ method
returning text and apply this decorator to the class.
"""
if PY2:
if '__str__' not in klass.__dict__:
raise ValueError("@python_2_unicode_compatible cannot be applied "
"to %s because it doesn't define __str__()." %
klass.__name__)
klass.__unicode__ = klass.__str__
klass.__str__ = lambda self: self.__unicode__().encode('utf-8')
return klass
# Complete the moves implementation.
# This code is at the end of this module to speed up module loading.
# Turn this module into a package.
__path__ = [] # required for PEP 302 and PEP 451
__package__ = __name__ # see PEP 366 @ReservedAssignment
if globals().get("__spec__") is not None:
__spec__.submodule_search_locations = [] # PEP 451 @UndefinedVariable
# Remove other six meta path importers, since they cause problems. This can
# happen if six is removed from sys.modules and then reloaded. (Setuptools does
# this for some reason.)
if sys.meta_path:
for i, importer in enumerate(sys.meta_path):
# Here's some real nastiness: Another "instance" of the six module might
# be floating around. Therefore, we can't use isinstance() to check for
# the six meta path importer, since the other six instance will have
# inserted an importer with different class.
if (type(importer).__name__ == "_SixMetaPathImporter" and
importer.name == __name__):
del sys.meta_path[i]
break
del i, importer
# Finally, add the importer to the meta path import hook.
sys.meta_path.append(_importer)
+252 -128
View File
@@ -1,4 +1,4 @@
# u-msgpack-python v2.5.0 - v at sergeev.io
# u-msgpack-python v2.7.1 - v at sergeev.io
# https://github.com/vsergeev/u-msgpack-python
#
# u-msgpack-python is a lightweight MessagePack serializer and deserializer
@@ -10,7 +10,7 @@
#
# MIT License
#
# Copyright (c) 2013-2016 vsergeev / Ivan (Vanya) A. Sergeev
# Copyright (c) 2013-2020 vsergeev / Ivan (Vanya) A. Sergeev
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -31,7 +31,7 @@
# THE SOFTWARE.
#
"""
u-msgpack-python v2.5.0 - v at sergeev.io
u-msgpack-python v2.7.1 - v at sergeev.io
https://github.com/vsergeev/u-msgpack-python
u-msgpack-python is a lightweight MessagePack serializer and deserializer
@@ -49,10 +49,15 @@ import datetime
import sys
import io
__version__ = "2.5.0"
if sys.version_info[0:2] >= (3, 3):
from collections.abc import Hashable
else:
from collections import Hashable
__version__ = "2.7.1"
"Module version string"
version = (2, 5, 0)
version = (2, 7, 1)
"Module version tuple"
@@ -61,7 +66,7 @@ version = (2, 5, 0)
##############################################################################
# Extension type for application-defined types and data
class Ext:
class Ext(object):
"""
The Ext class facilitates creating a serializable extension object to store
an application-defined type and data byte array.
@@ -75,23 +80,33 @@ class Ext:
type: application-defined type integer
data: application-defined data byte array
TypeError:
Type is not an integer.
ValueError:
Type is out of range of -128 to 127.
TypeError::
Data is not type 'bytes' (Python 3) or not type 'str' (Python 2).
Example:
>>> foo = umsgpack.Ext(0x05, b"\x01\x02\x03")
>>> foo = umsgpack.Ext(5, b"\x01\x02\x03")
>>> umsgpack.packb({u"special stuff": foo, u"awesome": True})
'\x82\xa7awesome\xc3\xadspecial stuff\xc7\x03\x05\x01\x02\x03'
>>> bar = umsgpack.unpackb(_)
>>> print(bar["special stuff"])
Ext Object (Type: 0x05, Data: 01 02 03)
Ext Object (Type: 5, Data: 01 02 03)
>>>
"""
# Check type is type int
# Check type is type int and in range
if not isinstance(type, int):
raise TypeError("ext type is not type integer")
# Check data is type bytes
elif not (-2**7 <= type <= 2**7 - 1):
raise ValueError("ext type value {:d} is out of range (-128 to 127)".format(type))
# Check data is type bytes or str
elif sys.version_info[0] == 3 and not isinstance(data, bytes):
raise TypeError("ext data is not type \'bytes\'")
elif sys.version_info[0] == 2 and not isinstance(data, str):
raise TypeError("ext data is not type \'str\'")
self.type = type
self.data = data
@@ -99,9 +114,8 @@ class Ext:
"""
Compare this Ext object with another for equality.
"""
return (isinstance(other, self.__class__) and
self.type == other.type and
self.data == other.data)
return isinstance(other, self.__class__) \
and self.type == other.type and self.data == other.data
def __ne__(self, other):
"""
@@ -113,8 +127,8 @@ class Ext:
"""
String representation of this Ext object.
"""
s = "Ext Object (Type: 0x%02x, Data: " % self.type
s += " ".join(["0x%02x" % ord(self.data[i:i + 1])
s = "Ext Object (Type: {:d}, Data: ".format(self.type)
s += " ".join(["0x{:02}".format(ord(self.data[i:i + 1]))
for i in xrange(min(len(self.data), 8))])
if len(self.data) > 8:
s += " ..."
@@ -130,7 +144,52 @@ class Ext:
class InvalidString(bytes):
"""Subclass of bytes to hold invalid UTF-8 strings."""
pass
##############################################################################
# Ext Serializable Decorator
##############################################################################
_ext_class_to_type = {}
_ext_type_to_class = {}
def ext_serializable(ext_type):
"""
Return a decorator to register a class for automatic packing and unpacking
with the specified Ext type code. The application class should implement a
`packb()` method that returns serialized bytes, and an `unpackb()` class
method or static method that accepts serialized bytes and returns an
instance of the application class.
Args:
ext_type: application-defined Ext type code
Raises:
TypeError:
Ext type is not an integer.
ValueError:
Ext type is out of range of -128 to 127.
ValueError:
Ext type or class already registered.
"""
def wrapper(cls):
if not isinstance(ext_type, int):
raise TypeError("Ext type is not type integer")
elif not (-2**7 <= ext_type <= 2**7 - 1):
raise ValueError("Ext type value {:d} is out of range of -128 to 127".format(ext_type))
elif ext_type in _ext_type_to_class:
raise ValueError("Ext type {:d} already registered with class {:s}".format(ext_type, repr(_ext_type_to_class[ext_type])))
elif cls in _ext_class_to_type:
raise ValueError("Class {:s} already registered with Ext type {:d}".format(repr(cls), ext_type))
_ext_type_to_class[ext_type] = cls
_ext_class_to_type[cls] = ext_type
return cls
return wrapper
##############################################################################
# Exceptions
@@ -140,39 +199,32 @@ class InvalidString(bytes):
# Base Exception classes
class PackException(Exception):
"Base class for exceptions encountered during packing."
pass
class UnpackException(Exception):
"Base class for exceptions encountered during unpacking."
pass
# Packing error
class UnsupportedTypeException(PackException):
"Object type not supported for packing."
pass
# Unpacking error
class InsufficientDataException(UnpackException):
"Insufficient data to unpack the serialized object."
pass
class InvalidStringException(UnpackException):
"Invalid UTF-8 string encountered during unpacking."
pass
class UnsupportedTimestampException(UnpackException):
"Unsupported timestamp format encountered during unpacking."
pass
class ReservedCodeException(UnpackException):
"Reserved code encountered during unpacking."
pass
class UnhashableKeyException(UnpackException):
@@ -180,12 +232,10 @@ class UnhashableKeyException(UnpackException):
Unhashable key encountered during map unpacking.
The serialized map cannot be deserialized into a Python dictionary.
"""
pass
class DuplicateKeyException(UnpackException):
"Duplicate key encountered during map unpacking."
pass
# Backwards compatibility
@@ -250,15 +300,15 @@ def _pack_integer(obj, fp, options):
else:
raise UnsupportedTypeException("huge signed int")
else:
if obj <= 127:
if obj < 128:
fp.write(struct.pack("B", obj))
elif obj <= 2**8 - 1:
elif obj < 2**8:
fp.write(b"\xcc" + struct.pack("B", obj))
elif obj <= 2**16 - 1:
elif obj < 2**16:
fp.write(b"\xcd" + struct.pack(">H", obj))
elif obj <= 2**32 - 1:
elif obj < 2**32:
fp.write(b"\xce" + struct.pack(">I", obj))
elif obj <= 2**64 - 1:
elif obj < 2**64:
fp.write(b"\xcf" + struct.pack(">Q", obj))
else:
raise UnsupportedTypeException("huge unsigned int")
@@ -285,94 +335,99 @@ def _pack_float(obj, fp, options):
def _pack_string(obj, fp, options):
obj = obj.encode('utf-8')
if len(obj) <= 31:
fp.write(struct.pack("B", 0xa0 | len(obj)) + obj)
elif len(obj) <= 2**8 - 1:
fp.write(b"\xd9" + struct.pack("B", len(obj)) + obj)
elif len(obj) <= 2**16 - 1:
fp.write(b"\xda" + struct.pack(">H", len(obj)) + obj)
elif len(obj) <= 2**32 - 1:
fp.write(b"\xdb" + struct.pack(">I", len(obj)) + obj)
obj_len = len(obj)
if obj_len < 32:
fp.write(struct.pack("B", 0xa0 | obj_len) + obj)
elif obj_len < 2**8:
fp.write(b"\xd9" + struct.pack("B", obj_len) + obj)
elif obj_len < 2**16:
fp.write(b"\xda" + struct.pack(">H", obj_len) + obj)
elif obj_len < 2**32:
fp.write(b"\xdb" + struct.pack(">I", obj_len) + obj)
else:
raise UnsupportedTypeException("huge string")
def _pack_binary(obj, fp, options):
if len(obj) <= 2**8 - 1:
fp.write(b"\xc4" + struct.pack("B", len(obj)) + obj)
elif len(obj) <= 2**16 - 1:
fp.write(b"\xc5" + struct.pack(">H", len(obj)) + obj)
elif len(obj) <= 2**32 - 1:
fp.write(b"\xc6" + struct.pack(">I", len(obj)) + obj)
obj_len = len(obj)
if obj_len < 2**8:
fp.write(b"\xc4" + struct.pack("B", obj_len) + obj)
elif obj_len < 2**16:
fp.write(b"\xc5" + struct.pack(">H", obj_len) + obj)
elif obj_len < 2**32:
fp.write(b"\xc6" + struct.pack(">I", obj_len) + obj)
else:
raise UnsupportedTypeException("huge binary string")
def _pack_oldspec_raw(obj, fp, options):
if len(obj) <= 31:
fp.write(struct.pack("B", 0xa0 | len(obj)) + obj)
elif len(obj) <= 2**16 - 1:
fp.write(b"\xda" + struct.pack(">H", len(obj)) + obj)
elif len(obj) <= 2**32 - 1:
fp.write(b"\xdb" + struct.pack(">I", len(obj)) + obj)
obj_len = len(obj)
if obj_len < 32:
fp.write(struct.pack("B", 0xa0 | obj_len) + obj)
elif obj_len < 2**16:
fp.write(b"\xda" + struct.pack(">H", obj_len) + obj)
elif obj_len < 2**32:
fp.write(b"\xdb" + struct.pack(">I", obj_len) + obj)
else:
raise UnsupportedTypeException("huge raw string")
def _pack_ext(obj, fp, options):
if len(obj.data) == 1:
obj_len = len(obj.data)
if obj_len == 1:
fp.write(b"\xd4" + struct.pack("B", obj.type & 0xff) + obj.data)
elif len(obj.data) == 2:
elif obj_len == 2:
fp.write(b"\xd5" + struct.pack("B", obj.type & 0xff) + obj.data)
elif len(obj.data) == 4:
elif obj_len == 4:
fp.write(b"\xd6" + struct.pack("B", obj.type & 0xff) + obj.data)
elif len(obj.data) == 8:
elif obj_len == 8:
fp.write(b"\xd7" + struct.pack("B", obj.type & 0xff) + obj.data)
elif len(obj.data) == 16:
elif obj_len == 16:
fp.write(b"\xd8" + struct.pack("B", obj.type & 0xff) + obj.data)
elif len(obj.data) <= 2**8 - 1:
fp.write(b"\xc7" +
struct.pack("BB", len(obj.data), obj.type & 0xff) + obj.data)
elif len(obj.data) <= 2**16 - 1:
fp.write(b"\xc8" +
struct.pack(">HB", len(obj.data), obj.type & 0xff) + obj.data)
elif len(obj.data) <= 2**32 - 1:
fp.write(b"\xc9" +
struct.pack(">IB", len(obj.data), obj.type & 0xff) + obj.data)
elif obj_len < 2**8:
fp.write(b"\xc7" + struct.pack("BB", obj_len, obj.type & 0xff) + obj.data)
elif obj_len < 2**16:
fp.write(b"\xc8" + struct.pack(">HB", obj_len, obj.type & 0xff) + obj.data)
elif obj_len < 2**32:
fp.write(b"\xc9" + struct.pack(">IB", obj_len, obj.type & 0xff) + obj.data)
else:
raise UnsupportedTypeException("huge ext data")
def _pack_ext_timestamp(obj, fp, options):
delta = obj - _epoch
if not obj.tzinfo:
# Object is naive datetime, convert to aware date time,
# assuming UTC timezone
delta = obj.replace(tzinfo=_utc_tzinfo) - _epoch
else:
# Object is aware datetime
delta = obj - _epoch
seconds = delta.seconds + delta.days * 86400
microseconds = delta.microseconds
if microseconds == 0 and 0 <= seconds <= 2**32 - 1:
# 32-bit timestamp
fp.write(b"\xd6\xff" +
struct.pack(">I", seconds))
fp.write(b"\xd6\xff" + struct.pack(">I", seconds))
elif 0 <= seconds <= 2**34 - 1:
# 64-bit timestamp
value = ((microseconds * 1000) << 34) | seconds
fp.write(b"\xd7\xff" +
struct.pack(">Q", value))
fp.write(b"\xd7\xff" + struct.pack(">Q", value))
elif -2**63 <= abs(seconds) <= 2**63 - 1:
# 96-bit timestamp
fp.write(b"\xc7\x0c\xff" +
struct.pack(">I", microseconds * 1000) +
struct.pack(">q", seconds))
fp.write(b"\xc7\x0c\xff" + struct.pack(">Iq", microseconds * 1000, seconds))
else:
raise UnsupportedTypeException("huge timestamp")
def _pack_array(obj, fp, options):
if len(obj) <= 15:
fp.write(struct.pack("B", 0x90 | len(obj)))
elif len(obj) <= 2**16 - 1:
fp.write(b"\xdc" + struct.pack(">H", len(obj)))
elif len(obj) <= 2**32 - 1:
fp.write(b"\xdd" + struct.pack(">I", len(obj)))
obj_len = len(obj)
if obj_len < 16:
fp.write(struct.pack("B", 0x90 | obj_len))
elif obj_len < 2**16:
fp.write(b"\xdc" + struct.pack(">H", obj_len))
elif obj_len < 2**32:
fp.write(b"\xdd" + struct.pack(">I", obj_len))
else:
raise UnsupportedTypeException("huge array")
@@ -381,12 +436,13 @@ def _pack_array(obj, fp, options):
def _pack_map(obj, fp, options):
if len(obj) <= 15:
fp.write(struct.pack("B", 0x80 | len(obj)))
elif len(obj) <= 2**16 - 1:
fp.write(b"\xde" + struct.pack(">H", len(obj)))
elif len(obj) <= 2**32 - 1:
fp.write(b"\xdf" + struct.pack(">I", len(obj)))
obj_len = len(obj)
if obj_len < 16:
fp.write(struct.pack("B", 0x80 | obj_len))
elif obj_len < 2**16:
fp.write(b"\xde" + struct.pack(">H", obj_len))
elif obj_len < 2**32:
fp.write(b"\xdf" + struct.pack(">I", obj_len))
else:
raise UnsupportedTypeException("huge array")
@@ -435,9 +491,14 @@ def _pack2(obj, fp, **options):
_pack_nil(obj, fp, options)
elif ext_handlers and obj.__class__ in ext_handlers:
_pack_ext(ext_handlers[obj.__class__](obj), fp, options)
elif obj.__class__ in _ext_class_to_type:
try:
_pack_ext(Ext(_ext_class_to_type[obj.__class__], obj.packb()), fp, options)
except AttributeError:
raise NotImplementedError("Ext serializable class {:s} is missing implementation of packb()".format(repr(obj.__class__)))
elif isinstance(obj, bool):
_pack_boolean(obj, fp, options)
elif isinstance(obj, int) or isinstance(obj, long):
elif isinstance(obj, (int, long)):
_pack_integer(obj, fp, options)
elif isinstance(obj, float):
_pack_float(obj, fp, options)
@@ -449,7 +510,7 @@ def _pack2(obj, fp, **options):
_pack_string(obj, fp, options)
elif isinstance(obj, str):
_pack_binary(obj, fp, options)
elif isinstance(obj, list) or isinstance(obj, tuple):
elif isinstance(obj, (list, tuple)):
_pack_array(obj, fp, options)
elif isinstance(obj, dict):
_pack_map(obj, fp, options)
@@ -464,9 +525,19 @@ def _pack2(obj, fp, **options):
_pack_ext(ext_handlers[t](obj), fp, options)
else:
raise UnsupportedTypeException(
"unsupported type: %s" % str(type(obj)))
"unsupported type: {:s}".format(str(type(obj))))
elif _ext_class_to_type:
# Linear search for superclass
t = next((t for t in _ext_class_to_type if isinstance(obj, t)), None)
if t:
try:
_pack_ext(Ext(_ext_class_to_type[t], obj.packb()), fp, options)
except AttributeError:
raise NotImplementedError("Ext serializable class {:s} is missing implementation of packb()".format(repr(t)))
else:
raise UnsupportedTypeException("unsupported type: {:s}".format(str(type(obj))))
else:
raise UnsupportedTypeException("unsupported type: %s" % str(type(obj)))
raise UnsupportedTypeException("unsupported type: {:s}".format(str(type(obj))))
# Pack for Python 3, with unicode 'str' type, 'bytes' type, and no 'long' type
@@ -507,6 +578,11 @@ def _pack3(obj, fp, **options):
_pack_nil(obj, fp, options)
elif ext_handlers and obj.__class__ in ext_handlers:
_pack_ext(ext_handlers[obj.__class__](obj), fp, options)
elif obj.__class__ in _ext_class_to_type:
try:
_pack_ext(Ext(_ext_class_to_type[obj.__class__], obj.packb()), fp, options)
except AttributeError:
raise NotImplementedError("Ext serializable class {:s} is missing implementation of packb()".format(repr(obj.__class__)))
elif isinstance(obj, bool):
_pack_boolean(obj, fp, options)
elif isinstance(obj, int):
@@ -521,7 +597,7 @@ def _pack3(obj, fp, **options):
_pack_string(obj, fp, options)
elif isinstance(obj, bytes):
_pack_binary(obj, fp, options)
elif isinstance(obj, list) or isinstance(obj, tuple):
elif isinstance(obj, (list, tuple)):
_pack_array(obj, fp, options)
elif isinstance(obj, dict):
_pack_map(obj, fp, options)
@@ -536,10 +612,20 @@ def _pack3(obj, fp, **options):
_pack_ext(ext_handlers[t](obj), fp, options)
else:
raise UnsupportedTypeException(
"unsupported type: %s" % str(type(obj)))
"unsupported type: {:s}".format(str(type(obj))))
elif _ext_class_to_type:
# Linear search for superclass
t = next((t for t in _ext_class_to_type if isinstance(obj, t)), None)
if t:
try:
_pack_ext(Ext(_ext_class_to_type[t], obj.packb()), fp, options)
except AttributeError:
raise NotImplementedError("Ext serializable class {:s} is missing implementation of packb()".format(repr(t)))
else:
raise UnsupportedTypeException("unsupported type: {:s}".format(str(type(obj))))
else:
raise UnsupportedTypeException(
"unsupported type: %s" % str(type(obj)))
"unsupported type: {:s}".format(str(type(obj))))
def _packb2(obj, **options):
@@ -613,9 +699,20 @@ def _packb3(obj, **options):
def _read_except(fp, n):
if n == 0:
return b""
data = fp.read(n)
if len(data) < n:
if len(data) == 0:
raise InsufficientDataException()
while len(data) < n:
chunk = fp.read(n - len(data))
if len(chunk) == 0:
raise InsufficientDataException()
data += chunk
return data
@@ -640,21 +737,21 @@ def _unpack_integer(code, fp, options):
return struct.unpack(">I", _read_except(fp, 4))[0]
elif code == b'\xcf':
return struct.unpack(">Q", _read_except(fp, 8))[0]
raise Exception("logic error, not int: 0x%02x" % ord(code))
raise Exception("logic error, not int: 0x{:02x}".format(ord(code)))
def _unpack_reserved(code, fp, options):
if code == b'\xc1':
raise ReservedCodeException(
"encountered reserved code: 0x%02x" % ord(code))
"encountered reserved code: 0x{:02x}".format(ord(code)))
raise Exception(
"logic error, not reserved code: 0x%02x" % ord(code))
"logic error, not reserved code: 0x{:02x}".format(ord(code)))
def _unpack_nil(code, fp, options):
if code == b'\xc0':
return None
raise Exception("logic error, not nil: 0x%02x" % ord(code))
raise Exception("logic error, not nil: 0x{:02x}".format(ord(code)))
def _unpack_boolean(code, fp, options):
@@ -662,7 +759,7 @@ def _unpack_boolean(code, fp, options):
return False
elif code == b'\xc3':
return True
raise Exception("logic error, not boolean: 0x%02x" % ord(code))
raise Exception("logic error, not boolean: 0x{:02x}".format(ord(code)))
def _unpack_float(code, fp, options):
@@ -670,7 +767,7 @@ def _unpack_float(code, fp, options):
return struct.unpack(">f", _read_except(fp, 4))[0]
elif code == b'\xcb':
return struct.unpack(">d", _read_except(fp, 8))[0]
raise Exception("logic error, not float: 0x%02x" % ord(code))
raise Exception("logic error, not float: 0x{:02x}".format(ord(code)))
def _unpack_string(code, fp, options):
@@ -683,7 +780,7 @@ def _unpack_string(code, fp, options):
elif code == b'\xdb':
length = struct.unpack(">I", _read_except(fp, 4))[0]
else:
raise Exception("logic error, not string: 0x%02x" % ord(code))
raise Exception("logic error, not string: 0x{:02x}".format(ord(code)))
# Always return raw bytes in compatibility mode
global compatibility
@@ -707,7 +804,7 @@ def _unpack_binary(code, fp, options):
elif code == b'\xc6':
length = struct.unpack(">I", _read_except(fp, 4))[0]
else:
raise Exception("logic error, not binary: 0x%02x" % ord(code))
raise Exception("logic error, not binary: 0x{:02x}".format(ord(code)))
return _read_except(fp, length)
@@ -730,43 +827,48 @@ def _unpack_ext(code, fp, options):
elif code == b'\xc9':
length = struct.unpack(">I", _read_except(fp, 4))[0]
else:
raise Exception("logic error, not ext: 0x%02x" % ord(code))
raise Exception("logic error, not ext: 0x{:02x}".format(ord(code)))
ext_type = struct.unpack("b", _read_except(fp, 1))[0]
ext_data = _read_except(fp, length)
# Create extension object
ext = Ext(ext_type, ext_data)
# Unpack with ext handler, if we have one
ext_handlers = options.get("ext_handlers")
if ext_handlers and ext.type in ext_handlers:
return ext_handlers[ext.type](ext)
if ext_handlers and ext_type in ext_handlers:
return ext_handlers[ext_type](Ext(ext_type, ext_data))
# Unpack with ext classes, if type is registered
if ext_type in _ext_type_to_class:
try:
return _ext_type_to_class[ext_type].unpackb(ext_data)
except AttributeError:
raise NotImplementedError("Ext serializable class {:s} is missing implementation of unpackb()".format(repr(_ext_type_to_class[ext_type])))
# Timestamp extension
if ext.type == -1:
return _unpack_ext_timestamp(ext, options)
if ext_type == -1:
return _unpack_ext_timestamp(ext_data, options)
return ext
return Ext(ext_type, ext_data)
def _unpack_ext_timestamp(ext, options):
if len(ext.data) == 4:
def _unpack_ext_timestamp(ext_data, options):
obj_len = len(ext_data)
if obj_len == 4:
# 32-bit timestamp
seconds = struct.unpack(">I", ext.data)[0]
seconds = struct.unpack(">I", ext_data)[0]
microseconds = 0
elif len(ext.data) == 8:
elif obj_len == 8:
# 64-bit timestamp
value = struct.unpack(">Q", ext.data)[0]
value = struct.unpack(">Q", ext_data)[0]
seconds = value & 0x3ffffffff
microseconds = (value >> 34) // 1000
elif len(ext.data) == 12:
elif obj_len == 12:
# 96-bit timestamp
seconds = struct.unpack(">q", ext.data[4:12])[0]
microseconds = struct.unpack(">I", ext.data[0:4])[0] // 1000
seconds = struct.unpack(">q", ext_data[4:12])[0]
microseconds = struct.unpack(">I", ext_data[0:4])[0] // 1000
else:
raise UnsupportedTimestampException(
"unsupported timestamp with data length %d" % len(ext.data))
"unsupported timestamp with data length {:d}".format(len(ext_data)))
return _epoch + datetime.timedelta(seconds=seconds,
microseconds=microseconds)
@@ -780,7 +882,10 @@ def _unpack_array(code, fp, options):
elif code == b'\xdd':
length = struct.unpack(">I", _read_except(fp, 4))[0]
else:
raise Exception("logic error, not array: 0x%02x" % ord(code))
raise Exception("logic error, not array: 0x{:02x}".format(ord(code)))
if options.get('use_tuple'):
return tuple((_unpack(fp, options) for i in xrange(length)))
return [_unpack(fp, options) for i in xrange(length)]
@@ -799,10 +904,9 @@ def _unpack_map(code, fp, options):
elif code == b'\xdf':
length = struct.unpack(">I", _read_except(fp, 4))[0]
else:
raise Exception("logic error, not map: 0x%02x" % ord(code))
raise Exception("logic error, not map: 0x{:02x}".format(ord(code)))
d = {} if not options.get('use_ordered_dict') \
else collections.OrderedDict()
d = {} if not options.get('use_ordered_dict') else collections.OrderedDict()
for _ in xrange(length):
# Unpack key
k = _unpack(fp, options)
@@ -810,12 +914,12 @@ def _unpack_map(code, fp, options):
if isinstance(k, list):
# Attempt to convert list into a hashable tuple
k = _deep_list_to_tuple(k)
elif not isinstance(k, collections.Hashable):
elif not isinstance(k, Hashable):
raise UnhashableKeyException(
"encountered unhashable key: %s, %s" % (str(k), str(type(k))))
"encountered unhashable key: \"{:s}\" ({:s})".format(str(k), str(type(k))))
elif k in d:
raise DuplicateKeyException(
"encountered duplicate key: %s, %s" % (str(k), str(type(k))))
"encountered duplicate key: \"{:s}\" ({:s})".format(str(k), str(type(k))))
# Unpack value
v = _unpack(fp, options)
@@ -824,7 +928,7 @@ def _unpack_map(code, fp, options):
d[k] = v
except TypeError:
raise UnhashableKeyException(
"encountered unhashable key: %s" % str(k))
"encountered unhashable key: \"{:s}\"".format(str(k)))
return d
@@ -848,6 +952,8 @@ def _unpack2(fp, **options):
Ext into an object
use_ordered_dict (bool): unpack maps into OrderedDict, instead of
unordered dict (default False)
use_tuple (bool): unpacks arrays into tuples, instead of lists (default
False)
allow_invalid_utf8 (bool): unpack invalid strings into instances of
InvalidString, for access to the bytes
(default False)
@@ -892,6 +998,8 @@ def _unpack3(fp, **options):
Ext into an object
use_ordered_dict (bool): unpack maps into OrderedDict, instead of
unordered dict (default False)
use_tuple (bool): unpacks arrays into tuples, instead of lists (default
False)
allow_invalid_utf8 (bool): unpack invalid strings into instances of
InvalidString, for access to the bytes
(default False)
@@ -937,6 +1045,8 @@ def _unpackb2(s, **options):
Ext into an object
use_ordered_dict (bool): unpack maps into OrderedDict, instead of
unordered dict (default False)
use_tuple (bool): unpacks arrays into tuples, instead of lists (default
False)
allow_invalid_utf8 (bool): unpack invalid strings into instances of
InvalidString, for access to the bytes
(default False)
@@ -985,6 +1095,8 @@ def _unpackb3(s, **options):
Ext into an object
use_ordered_dict (bool): unpack maps into OrderedDict, instead of
unordered dict (default False)
use_tuple (bool): unpacks arrays into tuples, instead of lists (default
False)
allow_invalid_utf8 (bool): unpack invalid strings into instances of
InvalidString, for access to the bytes
(default False)
@@ -1045,9 +1157,21 @@ def __init():
if sys.version_info[0] == 3:
_utc_tzinfo = datetime.timezone.utc
else:
_utc_tzinfo = None
class UTC(datetime.tzinfo):
ZERO = datetime.timedelta(0)
# Calculate epoch datetime
def utcoffset(self, dt):
return UTC.ZERO
def tzname(self, dt):
return "UTC"
def dst(self, dt):
return UTC.ZERO
_utc_tzinfo = UTC()
# Calculate an aware epoch datetime
_epoch = datetime.datetime(1970, 1, 1, tzinfo=_utc_tzinfo)
# Auto-detect system float precision
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: 966ae7177c1d48c9ee15971994c623b5
config: 8cd01657672a2b3a4d1c8ecc92b32a11
tags: 645f666f9bcd5a90fca523b33c5a78b7
Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

+4 -3
View File
@@ -1,8 +1,9 @@
.. _examples-main:
********
Examples
********
*************
Code Examples
*************
A number of examples are included in the source distribution of Reticulum.
You can use these examples to learn how to write your own programs.
+127 -6
View File
@@ -6,19 +6,71 @@ The best way to get started with the Reticulum Network Stack depends on what
you want to do. This guide will outline sensible starting paths for different
scenarios.
Try Using a Reticulum-based Program
=============================================
If you simply want to try using a program built with Reticulum, you can take
a look at `Nomad Network <https://github.com/markqvist/nomadnet>`_, which
provides a basic encrypted communications suite built completely on Reticulum.
provides a complete encrypted communications suite built with Reticulum.
.. image:: screenshots/nomadnet3.png
:target: _images/nomadnet3.png
.. image:: screenshots/nomadnet_3.png
:target: _images/nomadnet_3.png
`Nomad Network <https://github.com/markqvist/nomadnet>`_ is a user-facing client
in the development for the messaging and information-sharing protocol
for the messaging and information-sharing protocol
`LXMF <https://github.com/markqvist/lxmf>`_, another project built with Reticulum.
You can install Nomad Network via pip:
.. code::
# Install ...
pip3 install nomadnet
# ... and run
nomadnet
**Please Note**: If this is the very first time you use pip to install a program
on your system, you might need to reboot your system for your program to become
available. If you get a "command not found" error or similar when running the
program, reboot your system and try again.
Using the Included Utilities
=============================================
Reticulum comes with a range of included utilities that make it easier to
manage your network, check connectivity and make Reticulum available to other
programs on your system.
You can use ``rnsd`` to run Reticulum as a background or foreground service,
and the ``rnstatus``, ``rnpath`` and ``rnprobe`` utilities to view and query
network status and connectivity.
To learn more about these utility programs, have a look at the
:ref:`Using Reticulum on Your System<using-main>` chapter of this manual.
Creating a Network With Reticulum
=============================================
To create a network, you will need to specify one or more *interfaces* for
Reticulum to use. This is done in the Reticulum configuration file, which by
default is located at ``~/.reticulum/config``. You can edit this file by hand,
or use the interactive ``rnsconfig`` utility.
When Reticulum is started for the first time, it will create a default
configuration file, with one active interface. This default interface uses
your existing ethernet network (if there is one), and only allows you to
communicate with other Reticulum peers within your local broadcast domain.
To communicate further, you will have to add one or more interfaces. The default
configuration includes a number of examples, ranging from using TCP over the
internet, to LoRa and Packet Radio interfaces.
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>`
and :ref:`Interfaces<interfaces-main>` chapters of this manual.
Develop a Program with Reticulum
===========================================
If you want to develop programs that use Reticulum, the easiest way to get
@@ -32,6 +84,13 @@ The above command will install Reticulum and dependencies, and you will be
ready to import and use RNS in your own programs. The next step will most
likely be to look at some :ref:`Example Programs<examples-main>`.
For extended functionality, you can install optional dependencies:
.. code::
pip3 install pyserial netifaces
Further information can be found in the :ref:`API Reference<api-main>`.
@@ -44,7 +103,7 @@ don't use pip, but try this recipe:
.. code::
# Install dependencies
pip3 install cryptography pyserial
pip3 install cryptography pyserial netifaces
# Clone repository
git clone https://github.com/markqvist/Reticulum.git
@@ -75,4 +134,66 @@ don't use pip, but try this recipe:
python3 Examples/Filetransfer.py -h
When you have experimented with the basic examples, it's time to go read the
:ref:`Understanding Reticulum<understanding-main>` chapter.
:ref:`Understanding Reticulum<understanding-main>` chapter.
Reticulum on ARM64
==============================================
On some architectures, including ARM64, not all dependencies have precompiled
binaries. On such systems, you will need to install ``python3-dev`` before
installing Reticulum or programs that depend on Reticulum.
.. code::
# Install Python and development packages
sudo apt update
sudo apt install python3 python3-pip python3-dev
# Install Reticulum
python3 -m pip install rns
Reticulum on Android
==============================================
Reticulum can be used on Android in different ways. The easiest way to get
started is using the `Termux app <https://termux.com/>`_, at the time of writing
available on `F-droid <https://f-droid.org>`_.
Termux is a terminal emulator and Linux environment for Android based devices,
which includes the ability to use many different programs and libraries,
including Reticulum.
Since the Python cryptography.io module does not offer pre-built wheels for
Android, the standard one-line install of Reticulum does not work on Android,
and a few extra commands are required.
From within Termux, execute the following:
.. code::
# First, make sure indexes and packages are up to date.
pkg update
pkg upgrade
# Then install dependencies for the cryptography library.
pkg install python build-essential openssl libffi rust
# Make sure pip is up to date, and install the wheel module.
pip3 install wheel pip --upgrade
# To allow the installer to build the cryptography module,
# we need to let it know what platform we are compiling for:
export CARGO_BUILD_TARGET="aarch64-linux-android"
# Start the install process for the cryptography module.
# Depending on your device, this can take several minutes,
# since the module must be compiled locally on your device.
pip3 install cryptography
# If the above installation succeeds, you can now install
# Reticulum and any related software
pip3 install rns
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.
+5 -2
View File
@@ -2,14 +2,17 @@
Reticulum Network Stack Manual
******************************
This manual aims to provide you with all the information you need to
understand Reticulum, develop programs using it, or to participate in
the development of Reticulum itself.
understand Reticulum, build networks or develop programs using it, or
to participate in the development of Reticulum itself.
.. toctree::
:maxdepth: 3
whatis
gettingstartedfast
using
networks
interfaces
understanding
reference
examples
+549
View File
@@ -0,0 +1,549 @@
.. _interfaces-main:
********************
Supported Interfaces
********************
Reticulum supports using many kinds of devices as networking interfaces, and
allows you to mix and match them in any way you choose. The number of distinct
network topologies you can create with Reticulum is more or less endless, but
common to them all is that you will need to define one or more *interfaces*
for Reticulum to use.
The following sections describe the interfaces currently available in Reticulum,
and gives example configurations for the respective interface types.
For a high-level overview of how networks can be formed over different interface
types, have a look at the :ref:`Building Networks<networks-main>` chapter of this
manual.
.. _interfaces-auto:
Auto Interface
==============
The Auto Interface enables communication with other discoverable Reticulum
nodes over autoconfigured IPv6 and UDP. It does not need any functional IP
infrastructure like routers or DHCP servers, but will require at least some
sort of switching medium between peers (a wired switch, a hub, a WiFi access
point or similar), and that link-local IPv6 is enabled in your operating
system, which should be enabled by default in almost all OSes.
.. code::
# This example demonstrates a TCP server interface.
# It will listen for incoming connections on the
# specified IP address and port number.
[[Default Interface]]
type = AutoInterface
interface_enabled = True
# You can create multiple isolated Reticulum
# networks on the same physical LAN by
# specifying different Group IDs.
group_id = reticulum
# You can also select specifically which
# kernel networking devices to use.
devices = wlan0,eth1
# Or let AutoInterface use all suitable
# devices except for a list of ignored ones.
ignored_devices = tun0,eth0
If you are connected to the Internet with IPv6, and your provider will route
IPv6 multicast, you can potentially configure the Auto Interface to globally
autodiscover other Reticulum nodes within your selected Group ID. You can specify
the discovery scope by setting it to one of ``link``, ``admin``, ``site``,
``organisation`` or ``global``.
.. code::
[[Default Interface]]
type = AutoInterface
interface_enabled = True
# Configure global discovery
group_id = custom_network_name
discovery_scope = global
# Other configuration options
discovery_port = 48555
data_port = 49555
.. _interfaces-i2p:
I2P Interface
=============
The I2P interface lets you connect Reticulum instances over the
`Invisible Internet Protocol <https://i2pd.website>`_. This can be
especially useful in cases where you want to host a globally reachable
Reticulum instance, but do not have access to any public IP addresses,
have a frequently changing IP address, or have firewalls blocking
inbound traffic.
Using the I2P interface, you will get a globally reachable, portable
and persistent I2P address that your Reticulum instance can be reached
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>`_.`
When an I2P router is running on your system, you can simply add
an I2P interface to reticulum:
.. code::
[[I2P]]
type = I2PInterface
interface_enabled = yes
connectable = yes
On the first start, Reticulum will generate a new I2P address for the
interface and start listening for inbound traffic on it. This can take
a while the first time, especially if your I2P router was also just
started, and is not yet well-connected to the I2P network. When ready,
you should see I2P base32 address printed to your log file. You can
also inspect the status of the interface using the ``rnstatus`` utility.
To connect to other Reticulum instances over I2P, just add a comma-separated
list of I2P base32 addresses to the ``peers`` option of the interface:
.. code::
[[I2P]]
type = I2PInterface
interface_enabled = yes
connectable = yes
peers = 5urvjicpzi7q3ybztsef4i5ow2aq4soktfj7zedz53s47r54jnqq.b32.i2p
It can take anywhere from a few seconds to a few minutes to establish
I2P connections to the desired peers, so Reticulum handles the process
in the background, and will output relevant events to the log.
**Please Note!** While the I2P interface is the simplest way to use
Reticulum over I2P, it is also possible to tunnel the TCP server and
client interfaces over I2P manually. This can be useful in situations
where more control is needed, but requires manual tunnel setup through
the I2P daemon configuration.
It is important to note that the two methods are *interchangably compatible*.
You can use the I2PInterface to connect to a TCPServerInterface that
was manually tunneled over I2P, for example. This offers a high degree
of flexibility in network setup, while retaining ease of use in simpler
use-cases.
.. _interfaces-tcps:
TCP Server Interface
====================
The TCP Server interface is suitable for allowing other peers to connect over
the Internet or private IP networks. When a TCP server interface has been
configured, other Reticulum peers can connect to it with a TCP Client interface.
.. code::
# This example demonstrates a TCP server interface.
# It will listen for incoming connections on the
# specified IP address and port number.
[[TCP Server Interface]]
type = TCPServerInterface
interface_enabled = True
# This configuration will listen on all IP
# interfaces on port 4242
listen_ip = 0.0.0.0
listen_port = 4242
# Alternatively you can bind to a specific IP
# listen_ip = 10.0.0.88
# listen_port = 4242
# Or a specific network device
# device = eth0
# port = 4242
**Please Note!** The TCP interfaces support tunneling over I2P, but to do so reliably,
you must use the i2p_tunneled option:
.. code::
[[TCP Server on I2P]]
type = TCPServerInterface
interface_enabled = yes
listen_ip = 127.0.0.1
listen_port = 5001
i2p_tunneled = yes
.. _interfaces-tcpc:
TCP Client Interface
====================
To connect to a TCP server interface, you would naturally use the TCP client
interface. Many TCP Client interfaces from different peers can connect to the
same TCP Server interface at the same time.
.. code::
# Here's an example of a TCP Client interface. The
# target_host can either be an IP address or a hostname.
[[TCP Client Interface]]
type = TCPClientInterface
interface_enabled = True
target_host = 127.0.0.1
target_port = 4242
It is also possible to use this interface type to connect via other programs
or hardware devices that expose a KISS interface on a TCP port, for example
software-based soundmodems. To do this, use the ``kiss_framing`` option:
.. code::
# Here's an example of a TCP Client interface that connects
# to a software TNC soundmodem on a KISS over TCP port.
[[TCP KISS Interface]]
type = TCPClientInterface
interface_enabled = True
kiss_framing = True
target_host = 127.0.0.1
target_port = 8001
**Caution!** Only use the KISS framing option when connecting to external devices
and programs like soundmodems and similar over TCP. When using the
``TCPClientInterface`` in conjunction with the ``TCPServerInterface`` you should
never enable ``kiss_framing``, since this will disable internal reliability and
recovery mechanisms that greatly improves performance over unreliable and
intermittent TCP links.
**Please Note!** The TCP interfaces support tunneling over I2P, but to do so reliably,
you must use the i2p_tunneled option:
.. code::
[[TCP Client over I2P]]
type = TCPClientInterface
interface_enabled = yes
target_host = 127.0.0.1
target_port = 5001
i2p_tunneled = yes
.. _interfaces-udp:
UDP Interface
=============
A UDP interface can be useful for communicating over IP networks, both
private and the internet. It can also allow broadcast communication
over IP networks, so it can provide an easy way to enable connectivity
with all other peers on a local area network.
*Please Note!* Using broadcast UDP traffic has performance implications,
especially on WiFi. If your goal is simply to enable easy communication
with all peers in your local ethernet broadcast domain, the
:ref:`Auto Interface<interfaces-auto>` performs better, and is just as
easy 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
# local Reticulum peers over UDP.
[[Default UDP Interface]]
type = UDPInterface
interface_enabled = True
listen_ip = 0.0.0.0
listen_port = 4242
forward_ip = 255.255.255.255
forward_port = 4242
# The above configuration will allow communication
# within the local broadcast domains of all local
# IP interfaces.
# Instead of specifying listen_ip, listen_port,
# forward_ip and forward_port, you can also bind
# to a specific network device like below.
# device = eth0
# port = 4242
# Assuming the eth0 device has the address
# 10.55.0.72/24, the above configuration would
# be equivalent to the following manual setup.
# Note that we are both listening and forwarding to
# the broadcast address of the network segments.
# listen_ip = 10.55.0.255
# listen_port = 4242
# forward_ip = 10.55.0.255
# forward_port = 4242
# You can of course also communicate only with
# a single IP address
# listen_ip = 10.55.0.15
# listen_port = 4242
# forward_ip = 10.55.0.16
# forward_port = 4242
.. _interfaces-rnode:
RNode LoRa Interface
====================
To use Reticulum over LoRa, the `RNode <https://unsigned.io/rnode/>`_ interface
can be used, and offers full control over LoRa parameters.
.. code::
# Here's an example of how to add a LoRa interface
# using the RNode LoRa transceiver.
[[RNode LoRa Interface]]
type = RNodeInterface
# Enable interface if you want use it!
interface_enabled = True
# Serial port for the device
port = /dev/ttyUSB0
# Set frequency to 867.2 MHz
frequency = 867200000
# Set LoRa bandwidth to 125 KHz
bandwidth = 125000
# Set TX power to 7 dBm (5 mW)
txpower = 7
# Select spreading factor 8. Valid
# range is 7 through 12, with 7
# being the fastest and 12 having
# the longest range.
spreadingfactor = 8
# Select coding rate 5. Valid range
# is 5 throough 8, with 5 being the
# fastest, and 8 the longest range.
codingrate = 5
# You can configure the RNode to send
# out identification on the channel with
# a set interval by configuring the
# following two parameters.
# id_callsign = MYCALL-0
# id_interval = 600
# For certain homebrew RNode interfaces
# with low amounts of RAM, using packet
# flow control can be useful. By default
# it is disabled.
flow_control = False
.. _interfaces-serial:
Serial Interface
================
Reticulum can be used over serial ports directly, or over any device with a
serial port, that will transparently pass data. Useful for communicating
directly over a wire-pair, or for using devices such as data radios and lasers.
.. code::
[[Serial Interface]]
type = SerialInterface
interface_enabled = True
# Serial port for the device
port = /dev/ttyUSB0
# Set the serial baud-rate and other
# configuration parameters.
speed = 115200
databits = 8
parity = none
stopbits = 1
.. _interfaces-kiss:
KISS Interface
==============
With the KISS interface, you can use Reticulum over a variety of packet
radio modems and TNCs, including `OpenModem <https://unsigned.io/openmodem/>`_.
KISS interfaces can also be configured to periodically send out beacons
for station identification purposes.
.. code::
[[Packet Radio KISS Interface]]
type = KISSInterface
interface_enabled = True
# Serial port for the device
port = /dev/ttyUSB1
# Set the serial baud-rate and other
# configuration parameters.
speed = 115200
databits = 8
parity = none
stopbits = 1
# Set the modem preamble.
preamble = 150
# Set the modem TX tail.
txtail = 10
# Configure CDMA parameters. These
# settings are reasonable defaults.
persistence = 200
slottime = 20
# You can configure the interface to send
# out identification on the channel with
# a set interval by configuring the
# following two parameters. The KISS
# interface will only ID if the set
# interval has elapsed since it's last
# actual transmission. The interval is
# configured in seconds.
# This option is commented out and not
# used by default.
# id_callsign = MYCALL-0
# id_interval = 600
# Whether to use KISS flow-control.
# This is useful for modems that have
# a small internal packet buffer, but
# support packet flow control instead.
flow_control = false
.. _interfaces-ax25:
AX.25 KISS Interface
====================
If you're using Reticulum on amateur radio spectrum, you might want to
use the AX.25 KISS interface. This way, Reticulum will automatically
encapsulate it's traffic in AX.25 and also identify your stations
transmissions with your callsign and SSID.
Only do this if you really need to! Reticulum doesn't need the AX.25
layer for anything, and it incurs extra overhead on every packet to
encapsulate in AX.25.
A more efficient way is to use the plain KISS interface with the
beaconing functionality described above.
.. code::
[[Packet Radio AX.25 KISS Interface]]
type = AX25KISSInterface
# Set the station callsign and SSID
callsign = NO1CLL
ssid = 0
# Enable interface if you want use it!
interface_enabled = True
# Serial port for the device
port = /dev/ttyUSB2
# Set the serial baud-rate and other
# configuration parameters.
speed = 115200
databits = 8
parity = none
stopbits = 1
# Set the modem preamble. A 150ms
# preamble should be a reasonable
# default, but may need to be
# increased for radios with slow-
# opening squelch and long TX/RX
# turnaround
preamble = 150
# Set the modem TX tail. In most
# cases this should be kept as low
# as possible to not waste airtime.
txtail = 10
# Configure CDMA parameters. These
# settings are reasonable defaults.
persistence = 200
slottime = 20
# Whether to use KISS flow-control.
# This is useful for modems with a
# small internal packet buffer.
flow_control = false
.. _interfaces-options:
Common Interface Options
========================
A number of general options can be used to control various
aspects of interface behaviour.
The ``interface_enabled`` option tells Reticulum whether or not
to bring up the interface. Defaults to ``False``. For any
interface to be brought up, the ``interface_enabled`` option
must be set to ``True`` or ``Yes``.
The ``outgoing`` option sets whether an interface is allowed
to transmit. Defaults to ``True``. If set to ``False`` the
interface will only receive data, and never transmit.
The ``interface_mode`` option allows selecting the high-level
behaviour of the interface from a number of options.
- The default value is ``full``. In this mode, all discovery,
meshing and transpor functionality is available.
- 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 are mostly 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.
+150
View File
@@ -0,0 +1,150 @@
.. _networks-main:
*****************
Building Networks
*****************
This chapter will provide you with the knowledge needed to build networks with
Reticulum, which can often be easier than using traditional stacks, since you
don't have to worry about coordinating addresses, subnets and routing for an
entire network that you might not know how will evolve in the future. With
Reticulum, you can simply add more segments to your network when it becomes
necesarry, and Reticulum will handle the convergence of the entire network
automatically.
Concepts & Overview
--------------------
There are important points that need to be kept in mind when building networks
with Reticulum:
* | In a Reticulum network, any node can autonomously generate as many adresses
(called *destinations* in Reticulum terminology) as it needs, which become
globally reachable to the rest of the network. There is no central point of
control over the adress space.
* | Reticulum was designed to handle both very small, and very large networks.
While the adress space can support billions of endpoints, Reticulum is
also very useful when just a few devices needs to communicate.
* | Reticulum provides sender/initiator anonymity by default. There is no way
to filter traffic or discriminate it based on the source of the traffic.
* | All traffic is encrypted using ephemeral keys generated by an Elliptic Curve
Diffie-Hellman key exchange on Curve25519. There is no way to inspect traffic
contents, and no way to prioritise or throttle certain kinds of traffic.
All transport and routing layers are thus completely agnostic to traffic type,
and will pass all traffic equally.
* | Reticulum can function both with and without infrastructure. When *transport
nodes* are available, they can route traffic over multiple hops for other
nodes, and will function as a distributed cryptographic keystore. When there
is no transport nodes available, all nodes that are within communication range
can still communicate.
* | Every node can become a transport node, simply by enabling it in it's
configuration, but there is no need for every node on the network to be a
transport node. Letting every node be a transport node will in most cases
degrade the performance and reliability of the network.
In general terms, if a node is stationary, well-connected and kept running
most of the time, it is a good candidate to be a transport node. For optimal
performance, a network should contain the amount of transport nodes that
provides connectivity to the intended area / topography, and not many more
than that.
Reticulum allows you to mix very different kinds of networking mediums into a
unified mesh, or to keep everything within one medium. You could build a "virtual
network" running entirely over the Internet, where all nodes communicate over TCP
and UDP "channels". You could also build such a network using MQTT or ZeroMQ as
the underlying carrier for Reticulum.
However, most real-world networks will probably involve either some form of
wireless or direct hardline communications. To allow Reticulum to communicate
over any type of medium, you must specify it in the configuration file, by default
located at ``~/.reticulum/config``. See the :ref:`Supported Interfaces<interfaces-main>`
chapter of this manual for interface configuration examples.
Any number of interfaces can be configured, and Reticulum will automatically
decide which are suitable to use in any given situation, depending on where
traffic needs to flow.
Example Scenarios
-----------------
This section illustrates a few example scenarios, and how they would, in general
terms, be planned, implemented and configured.
Interconnected LoRa Sites
=========================
An organisation wants to provide communication and information services to it's
members, which are located mainly in three separate areas. Three suitable hill-top
locations are found, where the organisation can install equipment: Site A, B and C.
Since the amount of data that needs to be exchanged between users is mainly text-
based, the bandwidth requirements are low, and LoRa radios are chosen to connect
users to the network.
Due to the hill-top locations found, there is radio line-of-sight between site A
and B, and also between site B and C. Because of this, the organisation does not
need to use the Internet to interconnect the sites, but purchases four Point-to-Point
WiFi based radios for interconnecting the sites.
At each site, a Raspberry Pi is installed to function as a gateway. A LoRa radio
is connected to the Pi with a USB cable, and the WiFi radio is connected to the
ethernet port of the Pi. At site B, two WiFi radios are needed to be able to reach
both site A and site C, so an extra ethernet adapter is connected to the Pi in
this location.
Once the hardware has been installed, Reticulum is installed on all the Pis, and at
site A and C, one interface is added for the LoRa radio, as well as one for the WiFi
radio. At site B, an interface for the LoRa radio, and one interface for each WiFi
radio is added to the Reticulum configuration file. The transport node option is
enabled in the configuration of all three gateways.
The network is now operational, and ready to serve users across all three areas.
The organisation prepares a LoRa radio that is supplied to the end users, along
with a Reticulum configuration file, that contains the right parameters for
communicating with the LoRa radios installed at the gateway sites.
Once users connect to the network, anyone will be able to communicate with anyone
else across all three sites.
Bridging Over the Internet
==========================
As the organisation grows, several new communities form in places too far away
from the core network to be reachable over WiFi links. New gateways similar to those
previously installed are set up for the new communities at the new sites D and E, but
they are islanded from the core network, and only serve the local users.
After investigating the options, it is found that it is possible to install an
Internet connection at site A, and an interface on the Internet connection is
configured for Reticulum on the Raspberry Pi at site A.
A member of the organisation at site D, named Dori, is willing to help by sharing
the Internet connection she already has in her home, and is able to leave a Raspberry
Pi running. A new Reticulum interface is configured on her Pi, connecting to the newly
enabled Internet interface on the gateway at site A. Dori is now connected to both
all the nodes at her own local site (through the hill-top LoRa gateway), and all the
combined users of sites A, B and C. She then enables transport on her node, and
traffic from site D can now reach everyone at site A, B and C, and vice versa.
Growth and Convergence
======================
As the organisation grows, more gateways are added to keep up with the growing user
base. Some local gateways even add VHF radios and packet modems to reach outlying users
and communities that are out of reach for the LoRa radios and WiFi backhauls.
As more sites, gateways and users are connected, the amount of coordination required
is kept to a minimum. If one community wants to add connectivity to the next one
over, it can simply be done without having to involve everyone or coordinate address
space or routing tables.
With the added geographical coverage, the operators at site A one day find that
the original internet bridged interfaces are no longer utilised. The network has
converged to be completely self-connected, and the sites that were once poorly
connected outliers are now an integral part of the network.
+12 -4
View File
@@ -39,7 +39,7 @@ Destination
Packet
------
.. autoclass:: RNS.Packet
.. autoclass:: RNS.Packet(destination, data, create_receipt = True)
:members:
.. _api-packetreceipt:
@@ -47,7 +47,7 @@ Packet
Packet Receipt
--------------
.. autoclass:: RNS.PacketReceipt
.. autoclass:: RNS.PacketReceipt()
:members:
.. _api-link:
@@ -55,7 +55,15 @@ Packet Receipt
Link
----
.. autoclass:: RNS.Link
.. autoclass:: RNS.Link(destination, established_callback=None, closed_callback = None)
:members:
.. _api-requestreceipt:
Request Receipt
---------------
.. autoclass:: RNS.RequestReceipt()
:members:
.. _api-resource:
@@ -63,7 +71,7 @@ Link
Resource
--------
.. autoclass:: RNS.Resource
.. autoclass:: RNS.Resource(data, link, advertise=True, auto_compress=True, callback=None, progress_callback=None, timeout=None)
:members:
.. _api-transport:
+20 -20
View File
@@ -52,7 +52,7 @@ by using multiple hops).
Goals
=====
To be as widely usable and easy to implement as possible, the following goals have been used to
To be as widely usable and easy to use as possible, the following goals have been used to
guide the design of Reticulum:
@@ -67,9 +67,12 @@ guide the design of Reticulum:
it can be easily replicated.
* **Very low bandwidth requirements**
Reticulum should be able to function reliably over links with a transmission capacity as low
as *1,000 bps*.
as *500 bps*.
* **Encryption by default**
Reticulum must use encryption by default where possible and applicable.
Reticulum must use strong encryption by default for all communication.
* **Initiator Anonymity**
It must be possible to communicate over a Reticulum network without revealing any identifying
information about oneself.
* **Unlicensed use**
Reticulum shall be functional over physical communication mediums that do not require any
form of license to use. Reticulum must be designed in a way, so it is usable over ISM radio
@@ -99,7 +102,7 @@ Introduction & Basic Functionality
Reticulum is a networking stack suited for high-latency, low-bandwidth links. Reticulum is at its
core a *message oriented* system. It is suited for both local point-to-point or point-to-multipoint
scenarios where alle nodes are within range of each other, as well as scenarios where packets need
to be transported over multiple hops to reach the recipient.
to be transported over multiple hops in a complex network to reach the recipient.
Reticulum does away with the idea of addresses and ports known from IP, TCP and UDP. Instead
Reticulum uses the singular concept of *destinations*. Any application using Reticulum as its
@@ -110,9 +113,9 @@ All destinations in Reticulum are represented internally as 10 bytes, derived fr
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>``.
By default Reticulum encrypts all data using public-key cryptography. Any message sent to a
destination is encrypted with that destinations public key. Reticulum can also set up an encrypted
channel to a destination with *Perfect Forward Secrecy* and *Initiator Anonymity* using a elliptic
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*.
@@ -135,17 +138,17 @@ destinations. Reticulum uses three different basic destination types, and one sp
* **Single**
The *single* destination type defines a public-key encrypted destination. Any data sent to this
destination will be encrypted with the destinations public key, and will only be readable by
the creator of the destination.
The *single* destination type is always identified by a unique public key. Any data sent to this
destination will be encrypted using ephemeral keys derived from an ECDH key exchange, and will
only be readable by the creator of the destination, who holds the corresponding private key.
* **Group**
The *group* destination type defines a symmetrically encrypted destination. Data sent to this
destination will be encrypted with a symmetric key, and will be readable by anyone in
possession of the key. The *group* destination can be used just as well by only two peers, as it
can by many.
possession of the key.
* **Plain**
A *plain* destination type is unencrypted, and suited for traffic that should be broadcast to a
number of users, or should be readable by anyone. Traffic to a *plain* destination is not encrypted.
Generally, *plain* destinations can be used for broadcast information intended to be public.
* **Link**
A *link* is a special destination type, that serves as an abstract channel to a *single*
destination, directly connected or over multiple hops. The *link* also offers reliability and
@@ -429,7 +432,7 @@ 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 240 bytes (more info in the
The combined bandwidth cost of setting up a link is 3 packets totalling 237 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.
@@ -507,7 +510,7 @@ the transfer is needed.
This is the purpose of the Reticulum :ref:`Resource<api-resource>`. A *Resource* can automatically
handle the reliable transfer of an arbitrary amount of data over an established :ref:`Link<api-link>`.
Resources can auto-compress data, will handle breaking the data into individual packets, sequencing
the transfer and reassembling the data on the other end.
the transfer, integrity verification and reassembling the data on the other end.
:ref:`Resources<api-resource>` are programmatically very simple to use, and only requires a few lines
of codes to reliably transfer any amount of data. They can be used to transfer data stored in memory,
@@ -581,6 +584,7 @@ Node Types
Currently Reticulum defines two node types, the *Station* and the *Peer*. A node is a *station* if it fixed
in one place, and if it is intended to be kept online most of the time. Otherwise the node is a *peer*.
This distinction is made by the user configuring the node, and is used to determine what nodes on the
network will help forward traffic, and what nodes rely on other nodes for connectivity.
@@ -596,10 +600,6 @@ Currently, Reticulum is completely priority-agnostic regarding general traffic.
on a first-come, first-serve basis. Announce re-transmission are handled according to the re-transmission
times and priorities described earlier in this chapter.
It is possible that a prioritisation engine could be added to Reticulum in the future, but in
the light of Reticulums goal of equal access, doing so would need to be the subject of careful
investigation of the consequences first.
.. _understanding-packetformat:
@@ -701,5 +701,5 @@ Binary Packet Format
- Announce : 151 bytes
- Link Request : 77 bytes
- Link Proof : 77 bytes
- Link RTT packet : 86 bytes
- Link keepalive : 14 bytes
- Link RTT packet : 83 bytes
- Link keepalive : 14 bytes
+263
View File
@@ -0,0 +1,263 @@
.. _using-main:
******************************
Using Reticulum on Your System
******************************
Reticulum is not installed as a driver or kernel module, as one might expect
of a networking stack. Instead, Reticulum is distributed as a Python module.
This means that no special privileges are required to install or use it.
Any program or application that uses Reticulum will automatically load and
initialise Reticulum when it starts.
In many cases, this approach is sufficient. When any program needs to use
Reticulum, it is loaded, initialised, interfaces are brought up, and the
program can now communicate over Reticulum. If another program starts up
and also wants access to the same Reticulum network, the instance is simply
shared. This works for any number of programs running concurrently, and is
very easy to use, but depending on your use case, there are other options.
Included Utility Programs
-------------------------
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
other programs, applications and services can utilise.
The rnsd Utility
================
To do so is very easy. Simply run the included ``rnsd`` command. When ``rnsd``
is running, it will keep all configured interfaces open, handle transport if
it is enabled, and allow any other programs to immediately utilise the
Reticulum network it is configured for.
You can even run multiple instances of rnsd with different configurations on
the same system.
.. code:: text
# Install Reticulum
pip3 install rns
# Run rnsd
rnsd
.. code:: text
usage: rnsd [-h] [--config CONFIG] [-v] [-q] [--version]
Reticulum Network Stack Daemon
optional arguments:
-h, --help show this help message and exit
--config CONFIG path to alternative Reticulum config directory
-v, --verbose
-q, --quiet
--version show program's version number and exit
You can easily add ``rnsd`` as an always-on service by :ref:`configuring a service<using-systemd>`.
The rnstatus Utility
====================
Using the ``rnstatus`` utility, you can view the status of configured Reticulum
interfaces, similar to the ``ifconfig`` program.
.. code:: text
# Run rnstatus
rnstatus
# Example output
Shared Instance[37428]
Status: Up
Connected applications: 1
RX: 1.13 KB
TX: 1.07 KB
UDPInterface[Default UDP Interface/0.0.0.0:4242]
Status: Up
RX: 1.01 KB
TX: 1.01 KB
TCPInterface[RNS Testnet Frankfurt/frankfurt.rns.unsigned.io:4965]
Status: Up
RX: 1.37 KB
TX: 9.02 KB
.. code:: text
usage: rnsd [-h] [--config CONFIG] [-v] [-q] [--version]
Reticulum Network Stack Daemon
optional arguments:
-h, --help show this help message and exit
--config CONFIG path to alternative Reticulum config directory
-v, --verbose
-q, --quiet
--version show program's version number and exit
The rnpath Utility
====================
With the ``rnpath`` utility, you can look up and view paths for
destinations on the Reticulum network.
.. code:: text
# Run rnpath
rnpath eca6f4e4dc26ae329e61
# Example output
Path found, destination <eca6f4e4dc26ae329e61> is 4 hops away via <56b115c30cd386cad69c> on TCPInterface[Testnet/frankfurt.rns.unsigned.io:4965]
.. code:: text
usage: rnpath.py [-h] [--config CONFIG] [--version] [-v] [destination]
Reticulum Path Discovery Utility
positional arguments:
destination hexadecimal hash of the destination
optional arguments:
-h, --help show this help message and exit
--config CONFIG path to alternative Reticulum config directory
--version show program's version number and exit
-v, --verbose
The rnprobe Utility
====================
The ``rnprobe`` utility lets you probe a destination for connectivity, similar
to the ``ping`` program. Please note that probes will only be answered if the
specified destination is configured to send proofs for received packets. Many
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
# Example output
Sent 16 byte probe to <9382f334de63217a4278>
Valid reply received from <9382f334de63217a4278>
Round-trip time is 38.469 milliseconds over 2 hops
.. code:: text
usage: rnprobe.py [-h] [--config CONFIG] [--version] [-v] [full_name] [destination_hash]
Reticulum Probe Utility
positional arguments:
full_name full destination name in dotted notation
destination_hash hexadecimal hash of the destination
optional arguments:
-h, --help show this help message and exit
--config CONFIG path to alternative Reticulum config directory
--version show program's version number and exit
-v, --verbose
Improving System Configuration
------------------------------
If you are setting up a system for permanent use with Reticulum, there is a
few system configuration changes that can make this easier to administrate.
These changes will be detailed here.
Fixed Serial Port Names
=======================
On a Reticulum node with several serial port based interfaces, it can be
beneficial to use the fixed name device nodes for the serial ports, instead
of the dynamically allocated shorthands such as ``/dev/ttyUSB0``. Under most
Debian-based distributions, including Ubuntu and Raspberry Pi OS, these nodes
can be found under ``/dev/serial/by-id``.
You can use such a device path directly in place of the numbered shorthands.
Here is an example of a packet radio TNC configured as such:
.. code:: text
[[Packet Radio KISS Interface]]
type = KISSInterface
interface_enabled = True
outgoing = true
port = /dev/serial/by-id/usb-FTDI_FT230X_Basic_UART_43891CKM-if00-port0
speed = 115200
databits = 8
parity = none
stopbits = 1
preamble = 150
txtail = 10
persistence = 200
slottime = 20
Using this methodology avoids potential naming mix-ups where physical devices
might be plugged and unplugged in different orders, or when node name
assignment varies from one boot to another.
.. _using-systemd:
Reticulum as a System Service
=============================
Instead of starting Reticulum manually, you can install ``rnsd`` as a system
service and have it start automatically at boot.
If you installed Reticulum with ``pip``, the ``rnsd`` program will most likely
be located in a user-local installation path only, which means ``systemd`` will not
be able to execute it. In this case, you can simply symlink the ``rnsd`` program
into a directory that is in systemd's path:
.. code:: text
sudo ln -s $(which rnsd) /usr/local/bin/
You can then create the service file ``/etc/systemd/system/rnsd.service`` with the
following content:
.. code:: text
[Unit]
Description=Reticulum Network Stack Daemon
After=multi-user.target
[Service]
# If you run Reticulum on WiFi devices,
# or other devices that need some extra
# time to initialise, you might want to
# add a short delay before Reticulum is
# started by systemd:
# ExecStartPre=/bin/sleep 10
Type=simple
Restart=always
RestartSec=3
User=USERNAMEHERE
ExecStart=rnsd --service
[Install]
WantedBy=multi-user.target
Be sure to replace ``USERNAMEHERE`` with the user you want to run ``rnsd`` as.
To manually start ``rnsd`` run:
.. code:: text
sudo systemctl start rnsd
If you want to automatically start ``rnsd`` at boot, run:
.. code:: text
sudo systemctl enable rnsd
+20 -16
View File
@@ -2,13 +2,13 @@
What is Reticulum?
******************
Reticulum is a cryptography-based networking stack for wide-area networks built on readily available hardware, and can operate even with very high latency and extremely low bandwidth.
Reticulum is a cryptography-based networking stack for wide-area networks built on readily available hardware, that can operate even with very high latency and extremely low bandwidth.
Reticulum allows you to build very wide-area networks with off-the-shelf tools, and offers end-to-end encryption, autoconfiguring cryptographically backed multi-hop transport, efficient addressing, unforgeable packet acknowledgements and more.
Reticulum is a complete networking stack, and does not use IP or higher layers, although it is easy to utilise 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 built directly on cryptographic principles, allowing resilience and stable functionality in open and trustless networks.
Reticulum is a complete networking stack, and does not need IP or higher layers, although it is easy to utilise 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 built directly on cryptographic principles, allowing resilience and stable functionality in open and trustless networks.
No kernel modules or drivers are required. Reticulum runs completely in userland, and can run on practically any system that runs Python 3.
No kernel modules or drivers are required. Reticulum runs completely in userland, and can run on practically any system that runs Python 3. Reticulum runs well even on small single-board computers like the Pi Zero.
Current Status
@@ -16,22 +16,21 @@ 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.
Caveat Emptor
==============
Reticulum is an experimental networking stack, 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. To be considered even remotely secure, Reticulum needs a very thourough security review by independt cryptographers and security researchers. If you want to help out, or help sponsor an audit, please do get in touch.
What does Reticulum Offer?
==========================
* Coordination-less globally unique adressing 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
* Reticulum uses the `Fernet <https://github.com/fernet/spec/blob/master/Spec.md>`_ specification for encryption
* Reticulum uses the `Fernet <https://github.com/fernet/spec/blob/master/Spec.md>`_ specification for on-the-wire / over-the-air encryption
* All keys are ephemeral and derived from an ECDH key exchange on Curve25519
* AES-128 in CBC mode with PKCS7 padding
@@ -39,8 +38,6 @@ What does Reticulum Offer?
* IVs are generated through os.urandom()
* Keys are ephemeral and derived from an ECDH key exchange on Curve25519
* Unforgeable packet delivery confirmations
* A variety of supported interface types
@@ -57,7 +54,7 @@ What does Reticulum Offer?
* Efficient link establishment
* Total bandwidth cost of setting up a link is only 3 packets, totalling 240 bytes
* Total bandwidth cost of setting up a link is only 3 packets, totalling 237 bytes
* Low cost of keeping links open at only 0.62 bits per second
@@ -65,7 +62,7 @@ What does Reticulum Offer?
Where can Reticulum be Used?
============================
Over practically any medium that can support at least a half-duplex channel
with 1.000 bits per second throughput, and an MTU of 500 bytes. Data radios,
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.
@@ -87,8 +84,8 @@ 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.
Supported Interface Types and Devices
=====================================
Interface Types and Devices
===========================
Reticulum implements a range of generalised interface types that covers most of the communications hardware that Reticulum can run over. If your hardware is not supported, it's relatively simple to implement an interface class. Currently, the following interfaces are supported:
* Any ethernet device
@@ -101,4 +98,11 @@ Reticulum implements a range of generalised interface types that covers most of
* TCP over IP networks
* UDP over IP networks
* UDP over IP networks
For a full list and more details, see the :ref:`Supported Interfaces<interfaces-main>` chapter.
Caveat Emptor
==============
Reticulum is an experimental networking stack, 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. To be considered secure, Reticulum needs a thourough security review by independt cryptographers and security researchers. If you want to help out, or help sponsor an audit, please do get in touch.
+1 -1
View File
@@ -1,6 +1,6 @@
var DOCUMENTATION_OPTIONS = {
URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'),
VERSION: '0.2.2 beta',
VERSION: '0.3.3 beta',
LANGUAGE: 'None',
COLLAPSE_INDEX: false,
BUILDER: 'html',
+59 -12
View File
@@ -5,7 +5,7 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Examples &#8212; Reticulum Network Stack 0.2.2 beta documentation</title>
<title>Code Examples &#8212; Reticulum Network Stack 0.3.3 beta documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/classic.css" />
@@ -27,8 +27,8 @@
<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.2.2 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">Examples</a></li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.3 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">Code Examples</a></li>
</ul>
</div>
@@ -37,8 +37,8 @@
<div class="bodywrapper">
<div class="body" role="main">
<div class="section" id="examples">
<span id="examples-main"></span><h1>Examples<a class="headerlink" href="#examples" title="Permalink to this headline"></a></h1>
<div class="section" id="code-examples">
<span id="examples-main"></span><h1>Code Examples<a class="headerlink" href="#code-examples" title="Permalink to this headline"></a></h1>
<p>A number of examples are included in the source distribution of Reticulum.
You can use these examples to learn how to write your own programs.</p>
<div class="section" id="minimal">
@@ -174,7 +174,7 @@ notifications about announces from relevant destinations.</p>
<span class="n">APP_NAME</span> <span class="o">=</span> <span class="s2">&quot;example_utilities&quot;</span>
<span class="c1"># We initialise two lists of strings to use as app_data</span>
<span class="n">fruits</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&quot;Peach&quot;</span><span class="p">,</span> <span class="s2">&quot;Quince&quot;</span><span class="p">,</span> <span class="s2">&quot;Date palm&quot;</span><span class="p">,</span> <span class="s2">&quot;Tangerine&quot;</span><span class="p">,</span> <span class="s2">&quot;Pomelo&quot;</span><span class="p">,</span> <span class="s2">&quot;Carambola&quot;</span><span class="p">,</span> <span class="s2">&quot;Grape&quot;</span><span class="p">]</span>
<span class="n">fruits</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&quot;Peach&quot;</span><span class="p">,</span> <span class="s2">&quot;Quince&quot;</span><span class="p">,</span> <span class="s2">&quot;Date&quot;</span><span class="p">,</span> <span class="s2">&quot;Tangerine&quot;</span><span class="p">,</span> <span class="s2">&quot;Pomelo&quot;</span><span class="p">,</span> <span class="s2">&quot;Carambola&quot;</span><span class="p">,</span> <span class="s2">&quot;Grape&quot;</span><span class="p">]</span>
<span class="n">noble_gases</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&quot;Helium&quot;</span><span class="p">,</span> <span class="s2">&quot;Neon&quot;</span><span class="p">,</span> <span class="s2">&quot;Argon&quot;</span><span class="p">,</span> <span class="s2">&quot;Krypton&quot;</span><span class="p">,</span> <span class="s2">&quot;Xenon&quot;</span><span class="p">,</span> <span class="s2">&quot;Radon&quot;</span><span class="p">,</span> <span class="s2">&quot;Oganesson&quot;</span><span class="p">]</span>
<span class="c1"># This initialisation is executed when the program is started</span>
@@ -488,6 +488,8 @@ the Packet interface.</p>
<span class="c1"># This initialisation is executed when the users chooses</span>
<span class="c1"># to run as a server</span>
<span class="k">def</span> <span class="nf">server</span><span class="p">(</span><span class="n">configpath</span><span class="p">):</span>
<span class="k">global</span> <span class="n">reticulum</span>
<span class="c1"># We must first initialise Reticulum</span>
<span class="n">reticulum</span> <span class="o">=</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Reticulum</span><span class="p">(</span><span class="n">configpath</span><span class="p">)</span>
@@ -544,11 +546,32 @@ the Packet interface.</p>
<span class="k">def</span> <span class="nf">server_callback</span><span class="p">(</span><span class="n">message</span><span class="p">,</span> <span class="n">packet</span><span class="p">):</span>
<span class="k">global</span> <span class="n">reticulum</span>
<span class="c1"># Tell the user that we received an echo request, and</span>
<span class="c1"># that we are going to send a reply to the requester.</span>
<span class="c1"># Sending the proof is handled automatically, since we</span>
<span class="c1"># set up the destination to prove all incoming packets.</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;Received packet from echo client, proof sent&quot;</span><span class="p">)</span>
<span class="n">reception_stats</span> <span class="o">=</span> <span class="s2">&quot;&quot;</span>
<span class="k">if</span> <span class="n">reticulum</span><span class="o">.</span><span class="n">is_connected_to_shared_instance</span><span class="p">:</span>
<span class="n">reception_rssi</span> <span class="o">=</span> <span class="n">reticulum</span><span class="o">.</span><span class="n">get_packet_rssi</span><span class="p">(</span><span class="n">packet</span><span class="o">.</span><span class="n">packet_hash</span><span class="p">)</span>
<span class="n">reception_snr</span> <span class="o">=</span> <span class="n">reticulum</span><span class="o">.</span><span class="n">get_packet_snr</span><span class="p">(</span><span class="n">packet</span><span class="o">.</span><span class="n">packet_hash</span><span class="p">)</span>
<span class="k">if</span> <span class="n">reception_rssi</span> <span class="o">!=</span> <span class="kc">None</span><span class="p">:</span>
<span class="n">reception_stats</span> <span class="o">+=</span> <span class="s2">&quot; [RSSI &quot;</span><span class="o">+</span><span class="nb">str</span><span class="p">(</span><span class="n">reception_rssi</span><span class="p">)</span><span class="o">+</span><span class="s2">&quot; dBm]&quot;</span>
<span class="k">if</span> <span class="n">reception_snr</span> <span class="o">!=</span> <span class="kc">None</span><span class="p">:</span>
<span class="n">reception_stats</span> <span class="o">+=</span> <span class="s2">&quot; [SNR &quot;</span><span class="o">+</span><span class="nb">str</span><span class="p">(</span><span class="n">reception_snr</span><span class="p">)</span><span class="o">+</span><span class="s2">&quot; dBm]&quot;</span>
<span class="k">else</span><span class="p">:</span>
<span class="k">if</span> <span class="n">packet</span><span class="o">.</span><span class="n">rssi</span> <span class="o">!=</span> <span class="kc">None</span><span class="p">:</span>
<span class="n">reception_stats</span> <span class="o">+=</span> <span class="s2">&quot; [RSSI &quot;</span><span class="o">+</span><span class="nb">str</span><span class="p">(</span><span class="n">packet</span><span class="o">.</span><span class="n">rssi</span><span class="p">)</span><span class="o">+</span><span class="s2">&quot; dBm]&quot;</span>
<span class="k">if</span> <span class="n">packet</span><span class="o">.</span><span class="n">snr</span> <span class="o">!=</span> <span class="kc">None</span><span class="p">:</span>
<span class="n">reception_stats</span> <span class="o">+=</span> <span class="s2">&quot; [SNR &quot;</span><span class="o">+</span><span class="nb">str</span><span class="p">(</span><span class="n">packet</span><span class="o">.</span><span class="n">snr</span><span class="p">)</span><span class="o">+</span><span class="s2">&quot; dB]&quot;</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;Received packet from echo client, proof sent&quot;</span><span class="o">+</span><span class="n">reception_stats</span><span class="p">)</span>
<span class="c1">##########################################################</span>
@@ -558,6 +581,8 @@ the Packet interface.</p>
<span class="c1"># This initialisation is executed when the users chooses</span>
<span class="c1"># to run as a client</span>
<span class="k">def</span> <span class="nf">client</span><span class="p">(</span><span class="n">destination_hexhash</span><span class="p">,</span> <span class="n">configpath</span><span class="p">,</span> <span class="n">timeout</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span>
<span class="k">global</span> <span class="n">reticulum</span>
<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>
@@ -654,6 +679,8 @@ the Packet interface.</p>
<span class="c1"># This function is called when our reply destination</span>
<span class="c1"># receives a proof packet.</span>
<span class="k">def</span> <span class="nf">packet_delivered</span><span class="p">(</span><span class="n">receipt</span><span class="p">):</span>
<span class="k">global</span> <span class="n">reticulum</span>
<span class="k">if</span> <span class="n">receipt</span><span class="o">.</span><span class="n">status</span> <span class="o">==</span> <span class="n">RNS</span><span class="o">.</span><span class="n">PacketReceipt</span><span class="o">.</span><span class="n">DELIVERED</span><span class="p">:</span>
<span class="n">rtt</span> <span class="o">=</span> <span class="n">receipt</span><span class="o">.</span><span class="n">get_rtt</span><span class="p">()</span>
<span class="k">if</span> <span class="p">(</span><span class="n">rtt</span> <span class="o">&gt;=</span> <span class="mi">1</span><span class="p">):</span>
@@ -663,10 +690,30 @@ the Packet interface.</p>
<span class="n">rtt</span> <span class="o">=</span> <span class="nb">round</span><span class="p">(</span><span class="n">rtt</span><span class="o">*</span><span class="mi">1000</span><span class="p">,</span> <span class="mi">3</span><span class="p">)</span>
<span class="n">rttstring</span> <span class="o">=</span> <span class="nb">str</span><span class="p">(</span><span class="n">rtt</span><span class="p">)</span><span class="o">+</span><span class="s2">&quot; milliseconds&quot;</span>
<span class="n">reception_stats</span> <span class="o">=</span> <span class="s2">&quot;&quot;</span>
<span class="k">if</span> <span class="n">reticulum</span><span class="o">.</span><span class="n">is_connected_to_shared_instance</span><span class="p">:</span>
<span class="n">reception_rssi</span> <span class="o">=</span> <span class="n">reticulum</span><span class="o">.</span><span class="n">get_packet_rssi</span><span class="p">(</span><span class="n">receipt</span><span class="o">.</span><span class="n">proof_packet</span><span class="o">.</span><span class="n">packet_hash</span><span class="p">)</span>
<span class="n">reception_snr</span> <span class="o">=</span> <span class="n">reticulum</span><span class="o">.</span><span class="n">get_packet_snr</span><span class="p">(</span><span class="n">receipt</span><span class="o">.</span><span class="n">proof_packet</span><span class="o">.</span><span class="n">packet_hash</span><span class="p">)</span>
<span class="k">if</span> <span class="n">reception_rssi</span> <span class="o">!=</span> <span class="kc">None</span><span class="p">:</span>
<span class="n">reception_stats</span> <span class="o">+=</span> <span class="s2">&quot; [RSSI &quot;</span><span class="o">+</span><span class="nb">str</span><span class="p">(</span><span class="n">reception_rssi</span><span class="p">)</span><span class="o">+</span><span class="s2">&quot; dBm]&quot;</span>
<span class="k">if</span> <span class="n">reception_snr</span> <span class="o">!=</span> <span class="kc">None</span><span class="p">:</span>
<span class="n">reception_stats</span> <span class="o">+=</span> <span class="s2">&quot; [SNR &quot;</span><span class="o">+</span><span class="nb">str</span><span class="p">(</span><span class="n">reception_snr</span><span class="p">)</span><span class="o">+</span><span class="s2">&quot; dB]&quot;</span>
<span class="k">else</span><span class="p">:</span>
<span class="k">if</span> <span class="n">receipt</span><span class="o">.</span><span class="n">proof_packet</span> <span class="o">!=</span> <span class="kc">None</span><span class="p">:</span>
<span class="k">if</span> <span class="n">receipt</span><span class="o">.</span><span class="n">proof_packet</span><span class="o">.</span><span class="n">rssi</span> <span class="o">!=</span> <span class="kc">None</span><span class="p">:</span>
<span class="n">reception_stats</span> <span class="o">+=</span> <span class="s2">&quot; [RSSI &quot;</span><span class="o">+</span><span class="nb">str</span><span class="p">(</span><span class="n">receipt</span><span class="o">.</span><span class="n">proof_packet</span><span class="o">.</span><span class="n">rssi</span><span class="p">)</span><span class="o">+</span><span class="s2">&quot; dBm]&quot;</span>
<span class="k">if</span> <span class="n">receipt</span><span class="o">.</span><span class="n">proof_packet</span><span class="o">.</span><span class="n">snr</span> <span class="o">!=</span> <span class="kc">None</span><span class="p">:</span>
<span class="n">reception_stats</span> <span class="o">+=</span> <span class="s2">&quot; [SNR &quot;</span><span class="o">+</span><span class="nb">str</span><span class="p">(</span><span class="n">receipt</span><span class="o">.</span><span class="n">proof_packet</span><span class="o">.</span><span class="n">snr</span><span class="p">)</span><span class="o">+</span><span class="s2">&quot; dB]&quot;</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span>
<span class="s2">&quot;Valid reply received from &quot;</span><span class="o">+</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">prettyhexrep</span><span class="p">(</span><span class="n">receipt</span><span class="o">.</span><span class="n">destination</span><span class="o">.</span><span class="n">hash</span><span class="p">)</span><span class="o">+</span>
<span class="s2">&quot;, round-trip time is &quot;</span><span class="o">+</span><span class="n">rttstring</span>
<span class="s2">&quot;, round-trip time is &quot;</span><span class="o">+</span><span class="n">rttstring</span><span class="o">+</span>
<span class="n">reception_stats</span>
<span class="p">)</span>
<span class="c1"># This function is called if a packet times out.</span>
@@ -2017,7 +2064,7 @@ interface to efficiently pass files of any size over a Reticulum <a class="refer
<span class="nb">print</span><span class="p">(</span><span class="s2">&quot;&quot;</span><span class="p">)</span>
<span class="k">while</span> <span class="n">menu_mode</span> <span class="o">==</span> <span class="s2">&quot;downloading&quot;</span><span class="p">:</span>
<span class="k">global</span> <span class="n">current_download</span>
<span class="n">percent</span> <span class="o">=</span> <span class="nb">round</span><span class="p">(</span><span class="n">current_download</span><span class="o">.</span><span class="n">progress</span><span class="p">()</span> <span class="o">*</span> <span class="mf">100.0</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
<span class="n">percent</span> <span class="o">=</span> <span class="nb">round</span><span class="p">(</span><span class="n">current_download</span><span class="o">.</span><span class="n">get_progress</span><span class="p">()</span> <span class="o">*</span> <span class="mf">100.0</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
<span class="nb">print</span><span class="p">((</span><span class="s2">&quot;</span><span class="se">\r</span><span class="s2">Progress: &quot;</span><span class="o">+</span><span class="nb">str</span><span class="p">(</span><span class="n">percent</span><span class="p">)</span><span class="o">+</span><span class="s2">&quot; % &quot;</span><span class="p">),</span> <span class="n">end</span><span class="o">=</span><span class="s1">&#39; &#39;</span><span class="p">)</span>
<span class="n">sys</span><span class="o">.</span><span class="n">stdout</span><span class="o">.</span><span class="n">flush</span><span class="p">()</span>
<span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mf">0.1</span><span class="p">)</span>
@@ -2273,7 +2320,7 @@ interface to efficiently pass files of any size over a Reticulum <a class="refer
<div class="sphinxsidebarwrapper">
<h3><a href="index.html">Table of Contents</a></h3>
<ul>
<li><a class="reference internal" href="#">Examples</a><ul>
<li><a class="reference internal" href="#">Code Examples</a><ul>
<li><a class="reference internal" href="#minimal">Minimal</a></li>
<li><a class="reference internal" href="#announce">Announce</a></li>
<li><a class="reference internal" href="#broadcast">Broadcast</a></li>
@@ -2319,8 +2366,8 @@ interface to efficiently pass files of any size over a Reticulum <a class="refer
<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.2.2 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">Examples</a></li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.3 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">Code Examples</a></li>
</ul>
</div>
<div class="footer" role="contentinfo">
+47 -18
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.2.2 beta documentation</title>
<title>Index &#8212; Reticulum Network Stack 0.3.3 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.2.2 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.3 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">Index</a></li>
</ul>
</div>
@@ -47,6 +47,7 @@
| <a href="#I"><strong>I</strong></a>
| <a href="#K"><strong>K</strong></a>
| <a href="#L"><strong>L</strong></a>
| <a href="#M"><strong>M</strong></a>
| <a href="#N"><strong>N</strong></a>
| <a href="#P"><strong>P</strong></a>
| <a href="#R"><strong>R</strong></a>
@@ -98,8 +99,6 @@
<li><a href="reference.html#RNS.Identity.decrypt">(RNS.Identity method)</a>
</li>
</ul></li>
<li><a href="reference.html#RNS.Link.DEFAULT_TIMEOUT">DEFAULT_TIMEOUT (RNS.Link attribute)</a>
</li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="reference.html#RNS.Transport.deregister_announce_handler">deregister_announce_handler() (RNS.Transport static method)</a>
@@ -107,8 +106,6 @@
<li><a href="reference.html#RNS.Destination.deregister_request_handler">deregister_request_handler() (RNS.Destination method)</a>
</li>
<li><a href="reference.html#RNS.Destination">Destination (class in RNS)</a>
</li>
<li><a href="reference.html#RNS.Link.disable_encryption">disable_encryption() (RNS.Link method)</a>
</li>
</ul></td>
</tr></table>
@@ -125,6 +122,8 @@
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="reference.html#RNS.Packet.ENCRYPTED_MDU">ENCRYPTED_MDU (RNS.Packet attribute)</a>
</li>
<li><a href="reference.html#RNS.Link.ESTABLISHMENT_TIMEOUT_PER_HOP">ESTABLISHMENT_TIMEOUT_PER_HOP (RNS.Link attribute)</a>
</li>
</ul></td>
</tr></table>
@@ -152,20 +151,36 @@
<ul>
<li><a href="reference.html#RNS.Identity.get_private_key">(RNS.Identity method)</a>
</li>
</ul></li>
<li><a href="reference.html#RNS.RequestReceipt.get_progress">get_progress() (RNS.RequestReceipt method)</a>
<ul>
<li><a href="reference.html#RNS.Resource.get_progress">(RNS.Resource method)</a>
</li>
</ul></li>
<li><a href="reference.html#RNS.Identity.get_public_key">get_public_key() (RNS.Identity method)</a>
</li>
<li><a href="reference.html#RNS.Identity.get_random_hash">get_random_hash() (RNS.Identity static method)</a>
</li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="reference.html#RNS.Identity.get_random_hash">get_random_hash() (RNS.Identity static method)</a>
</li>
<li><a href="reference.html#RNS.Link.get_remote_identity">get_remote_identity() (RNS.Link method)</a>
</li>
<li><a href="reference.html#RNS.RequestReceipt.get_request_id">get_request_id() (RNS.RequestReceipt method)</a>
</li>
<li><a href="reference.html#RNS.RequestReceipt.get_response">get_response() (RNS.RequestReceipt method)</a>
</li>
<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.PacketReceipt.get_status">get_status() (RNS.PacketReceipt method)</a>
<ul>
<li><a href="reference.html#RNS.RequestReceipt.get_status">(RNS.RequestReceipt method)</a>
</li>
</ul></li>
</ul></td>
</tr></table>
@@ -174,11 +189,13 @@
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="reference.html#RNS.Transport.has_path">has_path() (RNS.Transport static method)</a>
</li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="reference.html#RNS.Destination.hash">hash() (RNS.Destination static method)</a>
</li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="reference.html#RNS.Destination.hash_from_name_and_identity">hash_from_name_and_identity() (RNS.Destination static method)</a>
</li>
<li><a href="reference.html#RNS.Transport.hops_to">hops_to() (RNS.Transport static method)</a>
</li>
</ul></td>
</tr></table>
@@ -227,13 +244,25 @@
</ul></td>
</tr></table>
<h2 id="M">M</h2>
<table style="width: 100%" class="indextable genindextable"><tr>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="reference.html#RNS.Reticulum.MTU">MTU (RNS.Reticulum attribute)</a>
</li>
</ul></td>
</tr></table>
<h2 id="N">N</h2>
<table style="width: 100%" class="indextable genindextable"><tr>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="reference.html#RNS.Link.no_inbound_for">no_inbound_for() (RNS.Link method)</a>
<li><a href="reference.html#RNS.Transport.next_hop">next_hop() (RNS.Transport static method)</a>
</li>
<li><a href="reference.html#RNS.Transport.next_hop_interface">next_hop_interface() (RNS.Transport static method)</a>
</li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="reference.html#RNS.Link.no_inbound_for">no_inbound_for() (RNS.Link method)</a>
</li>
<li><a href="reference.html#RNS.Link.no_outbound_for">no_outbound_for() (RNS.Link method)</a>
</li>
</ul></td>
@@ -248,9 +277,9 @@
</li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="reference.html#RNS.Packet.PLAIN_MDU">PLAIN_MDU (RNS.Packet attribute)</a>
<li><a href="reference.html#RNS.Transport.PATHFINDER_M">PATHFINDER_M (RNS.Transport attribute)</a>
</li>
<li><a href="reference.html#RNS.Resource.progress">progress() (RNS.Resource method)</a>
<li><a href="reference.html#RNS.Packet.PLAIN_MDU">PLAIN_MDU (RNS.Packet attribute)</a>
</li>
</ul></td>
</tr></table>
@@ -266,11 +295,13 @@
</li>
<li><a href="reference.html#RNS.Destination.register_request_handler">register_request_handler() (RNS.Destination method)</a>
</li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="reference.html#RNS.Link.request">request() (RNS.Link method)</a>
</li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="reference.html#RNS.Transport.request_path">request_path() (RNS.Transport static method)</a>
</li>
<li><a href="reference.html#RNS.RequestReceipt">RequestReceipt (class in RNS)</a>
</li>
<li><a href="reference.html#RNS.Packet.resend">resend() (RNS.Packet method)</a>
</li>
@@ -317,8 +348,6 @@
<li><a href="reference.html#RNS.PacketReceipt.set_timeout">set_timeout() (RNS.PacketReceipt method)</a>
</li>
<li><a href="reference.html#RNS.PacketReceipt.set_timeout_callback">set_timeout_callback() (RNS.PacketReceipt method)</a>
</li>
<li><a href="reference.html#RNS.Reticulum.should_allow_unencrypted">should_allow_unencrypted() (RNS.Reticulum static method)</a>
</li>
<li><a href="reference.html#RNS.Reticulum.should_use_implicit_proof">should_use_implicit_proof() (RNS.Reticulum static method)</a>
</li>
@@ -387,7 +416,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.2.2 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.3 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">Index</a></li>
</ul>
</div>
+114 -12
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.2.2 beta documentation</title>
<title>Getting Started Fast &#8212; Reticulum Network Stack 0.3.3 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="Understanding Reticulum" href="understanding.html" />
<link rel="next" title="Using Reticulum on Your System" href="using.html" />
<link rel="prev" title="What is Reticulum?" href="whatis.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="understanding.html" title="Understanding Reticulum"
<a href="using.html" title="Using Reticulum on Your System"
accesskey="N">next</a> |</li>
<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.2.2 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.3 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">Getting Started Fast</a></li>
</ul>
</div>
@@ -50,11 +50,51 @@ scenarios.</p>
<h2>Try Using a Reticulum-based Program<a class="headerlink" href="#try-using-a-reticulum-based-program" title="Permalink to this headline"></a></h2>
<p>If you simply want to try using a program built with Reticulum, you can take
a look at <a class="reference external" href="https://github.com/markqvist/nomadnet">Nomad Network</a>, which
provides a basic encrypted communications suite built completely on Reticulum.</p>
<a class="reference external image-reference" href="_images/nomadnet3.png"><img alt="_images/nomadnet3.png" src="_images/nomadnet3.png" /></a>
provides a complete encrypted communications suite built with Reticulum.</p>
<a class="reference external image-reference" href="_images/nomadnet_3.png"><img alt="_images/nomadnet_3.png" src="_images/nomadnet_3.png" /></a>
<p><a class="reference external" href="https://github.com/markqvist/nomadnet">Nomad Network</a> is a user-facing client
in the development for the messaging and information-sharing protocol
for the messaging and information-sharing protocol
<a class="reference external" href="https://github.com/markqvist/lxmf">LXMF</a>, another project built with Reticulum.</p>
<p>You can install Nomad Network via pip:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># Install ...</span>
<span class="n">pip3</span> <span class="n">install</span> <span class="n">nomadnet</span>
<span class="c1"># ... and run</span>
<span class="n">nomadnet</span>
</pre></div>
</div>
<p><strong>Please Note</strong>: If this is the very first time you use pip to install a program
on your system, you might need to reboot your system for your program to become
available. If you get a “command not found” error or similar when running the
program, reboot your system and try again.</p>
</div>
<div class="section" id="using-the-included-utilities">
<h2>Using the Included Utilities<a class="headerlink" href="#using-the-included-utilities" title="Permalink to this headline"></a></h2>
<p>Reticulum comes with a range of included utilities that make it easier to
manage your network, check connectivity and make Reticulum available to other
programs on your system.</p>
<p>You can use <code class="docutils literal notranslate"><span class="pre">rnsd</span></code> to run Reticulum as a background or foreground service,
and the <code class="docutils literal notranslate"><span class="pre">rnstatus</span></code>, <code class="docutils literal notranslate"><span class="pre">rnpath</span></code> and <code class="docutils literal notranslate"><span class="pre">rnprobe</span></code> utilities to view and query
network status and connectivity.</p>
<p>To learn more about these utility programs, have a look at the
<a class="reference internal" href="using.html#using-main"><span class="std std-ref">Using Reticulum on Your System</span></a> chapter of this manual.</p>
</div>
<div class="section" id="creating-a-network-with-reticulum">
<h2>Creating a Network With Reticulum<a class="headerlink" href="#creating-a-network-with-reticulum" title="Permalink to this headline"></a></h2>
<p>To create a network, you will need to specify one or more <em>interfaces</em> for
Reticulum to use. This is done in the Reticulum configuration file, which by
default is located at <code class="docutils literal notranslate"><span class="pre">~/.reticulum/config</span></code>. You can edit this file by hand,
or use the interactive <code class="docutils literal notranslate"><span class="pre">rnsconfig</span></code> utility.</p>
<p>When Reticulum is started for the first time, it will create a default
configuration file, with one active interface. This default interface uses
your existing ethernet network (if there is one), and only allows you to
communicate with other Reticulum peers within your local broadcast domain.</p>
<p>To communicate further, you will have to add one or more interfaces. The default
configuration includes a number of examples, ranging from using TCP over the
internet, to LoRa and Packet Radio interfaces.</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>
</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>
@@ -66,6 +106,10 @@ started is to install the latest release of Reticulum via pip:</p>
<p>The above command will install Reticulum and dependencies, and you will be
ready to import and use RNS in your own programs. The next step will most
likely be to look at some <a class="reference internal" href="examples.html#examples-main"><span class="std std-ref">Example Programs</span></a>.</p>
<p>For extended functionality, you can install optional dependencies:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">pip3</span> <span class="n">install</span> <span class="n">pyserial</span> <span class="n">netifaces</span>
</pre></div>
</div>
<p>Further information can be found in the <a class="reference internal" href="reference.html#api-main"><span class="std std-ref">API Reference</span></a>.</p>
</div>
<div class="section" id="participate-in-reticulum-development">
@@ -74,7 +118,7 @@ likely be to look at some <a class="reference internal" href="examples.html#exam
utilities, youll want to get the latest source from GitHub. In that case,
dont use pip, but try this recipe:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># Install dependencies</span>
<span class="n">pip3</span> <span class="n">install</span> <span class="n">cryptography</span> <span class="n">pyserial</span>
<span class="n">pip3</span> <span class="n">install</span> <span class="n">cryptography</span> <span class="n">pyserial</span> <span class="n">netifaces</span>
<span class="c1"># Clone repository</span>
<span class="n">git</span> <span class="n">clone</span> <span class="n">https</span><span class="p">:</span><span class="o">//</span><span class="n">github</span><span class="o">.</span><span class="n">com</span><span class="o">/</span><span class="n">markqvist</span><span class="o">/</span><span class="n">Reticulum</span><span class="o">.</span><span class="n">git</span>
@@ -108,6 +152,60 @@ dont use pip, but try this recipe:</p>
<p>When you have experimented with the basic examples, its time to go read the
<a class="reference internal" href="understanding.html#understanding-main"><span class="std std-ref">Understanding Reticulum</span></a> chapter.</p>
</div>
<div class="section" id="reticulum-on-arm64">
<h2>Reticulum on ARM64<a class="headerlink" href="#reticulum-on-arm64" title="Permalink to this headline"></a></h2>
<p>On some architectures, including ARM64, not all dependencies have precompiled
binaries. On such systems, you will need to install <code class="docutils literal notranslate"><span class="pre">python3-dev</span></code> before
installing Reticulum or programs that depend on Reticulum.</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># Install Python and development packages</span>
<span class="n">sudo</span> <span class="n">apt</span> <span class="n">update</span>
<span class="n">sudo</span> <span class="n">apt</span> <span class="n">install</span> <span class="n">python3</span> <span class="n">python3</span><span class="o">-</span><span class="n">pip</span> <span class="n">python3</span><span class="o">-</span><span class="n">dev</span>
<span class="c1"># Install Reticulum</span>
<span class="n">python3</span> <span class="o">-</span><span class="n">m</span> <span class="n">pip</span> <span class="n">install</span> <span class="n">rns</span>
</pre></div>
</div>
</div>
<div class="section" id="reticulum-on-android">
<h2>Reticulum on Android<a class="headerlink" href="#reticulum-on-android" title="Permalink to this headline"></a></h2>
<p>Reticulum can be used on Android in different ways. The easiest way to get
started is using the <a class="reference external" href="https://termux.com/">Termux app</a>, at the time of writing
available on <a class="reference external" href="https://f-droid.org">F-droid</a>.</p>
<p>Termux is a terminal emulator and Linux environment for Android based devices,
which includes the ability to use many different programs and libraries,
including Reticulum.</p>
<p>Since the Python cryptography.io module does not offer pre-built wheels for
Android, the standard one-line install of Reticulum does not work on Android,
and a few extra commands are required.</p>
<p>From within Termux, execute the following:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># First, make sure indexes and packages are up to date.</span>
<span class="n">pkg</span> <span class="n">update</span>
<span class="n">pkg</span> <span class="n">upgrade</span>
<span class="c1"># Then install dependencies for the cryptography library.</span>
<span class="n">pkg</span> <span class="n">install</span> <span class="n">python</span> <span class="n">build</span><span class="o">-</span><span class="n">essential</span> <span class="n">openssl</span> <span class="n">libffi</span> <span class="n">rust</span>
<span class="c1"># Make sure pip is up to date, and install the wheel module.</span>
<span class="n">pip3</span> <span class="n">install</span> <span class="n">wheel</span> <span class="n">pip</span> <span class="o">--</span><span class="n">upgrade</span>
<span class="c1"># To allow the installer to build the cryptography module,</span>
<span class="c1"># we need to let it know what platform we are compiling for:</span>
<span class="n">export</span> <span class="n">CARGO_BUILD_TARGET</span><span class="o">=</span><span class="s2">&quot;aarch64-linux-android&quot;</span>
<span class="c1"># Start the install process for the cryptography module.</span>
<span class="c1"># Depending on your device, this can take several minutes,</span>
<span class="c1"># since the module must be compiled locally on your device.</span>
<span class="n">pip3</span> <span class="n">install</span> <span class="n">cryptography</span>
<span class="c1"># If the above installation succeeds, you can now install</span>
<span class="c1"># Reticulum and any related software</span>
<span class="n">pip3</span> <span class="n">install</span> <span class="n">rns</span>
</pre></div>
</div>
<p>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.</p>
</div>
</div>
@@ -121,8 +219,12 @@ dont use pip, but try this recipe:</p>
<ul>
<li><a class="reference internal" href="#">Getting Started Fast</a><ul>
<li><a class="reference internal" href="#try-using-a-reticulum-based-program">Try Using a Reticulum-based Program</a></li>
<li><a class="reference internal" href="#using-the-included-utilities">Using the Included Utilities</a></li>
<li><a class="reference internal" href="#creating-a-network-with-reticulum">Creating a Network With Reticulum</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>
</ul>
</li>
</ul>
@@ -131,8 +233,8 @@ dont use pip, but try this recipe:</p>
<p class="topless"><a href="whatis.html"
title="previous chapter">What is Reticulum?</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="using.html"
title="next chapter">Using Reticulum on Your System</a></p>
<div role="note" aria-label="source link">
<h3>This Page</h3>
<ul class="this-page-menu">
@@ -161,12 +263,12 @@ dont use pip, but try this recipe:</p>
<a href="genindex.html" title="General Index"
>index</a></li>
<li class="right" >
<a href="understanding.html" title="Understanding Reticulum"
<a href="using.html" title="Using Reticulum on Your System"
>next</a> |</li>
<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.2.2 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.3 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">Getting Started Fast</a></li>
</ul>
</div>
+51 -8
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.2.2 beta documentation</title>
<title>Reticulum Network Stack Manual &#8212; Reticulum Network Stack 0.3.3 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.2.2 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-0"><a href="#">Reticulum Network Stack 0.3.3 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">Reticulum Network Stack Manual</a></li>
</ul>
</div>
@@ -40,22 +40,64 @@
<div class="section" id="reticulum-network-stack-manual">
<h1>Reticulum Network Stack Manual<a class="headerlink" href="#reticulum-network-stack-manual" title="Permalink to this headline"></a></h1>
<p>This manual aims to provide you with all the information you need to
understand Reticulum, develop programs using it, or to participate in
the development of Reticulum itself.</p>
understand Reticulum, build networks or develop programs using it, or
to participate in the development of Reticulum itself.</p>
<div class="toctree-wrapper compound">
<ul>
<li class="toctree-l1"><a class="reference internal" href="whatis.html">What is Reticulum?</a><ul>
<li class="toctree-l2"><a class="reference internal" href="whatis.html#current-status">Current Status</a></li>
<li class="toctree-l2"><a class="reference internal" href="whatis.html#caveat-emptor">Caveat Emptor</a></li>
<li class="toctree-l2"><a class="reference internal" href="whatis.html#what-does-reticulum-offer">What does Reticulum Offer?</a></li>
<li class="toctree-l2"><a class="reference internal" href="whatis.html#where-can-reticulum-be-used">Where can Reticulum be Used?</a></li>
<li class="toctree-l2"><a class="reference internal" href="whatis.html#supported-interface-types-and-devices">Supported Interface Types and Devices</a></li>
<li class="toctree-l2"><a class="reference internal" href="whatis.html#interface-types-and-devices">Interface Types and Devices</a></li>
<li class="toctree-l2"><a class="reference internal" href="whatis.html#caveat-emptor">Caveat Emptor</a></li>
</ul>
</li>
<li class="toctree-l1"><a class="reference internal" href="gettingstartedfast.html">Getting Started Fast</a><ul>
<li class="toctree-l2"><a class="reference internal" href="gettingstartedfast.html#try-using-a-reticulum-based-program">Try Using a Reticulum-based Program</a></li>
<li class="toctree-l2"><a class="reference internal" href="gettingstartedfast.html#using-the-included-utilities">Using the Included Utilities</a></li>
<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#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>
</ul>
</li>
<li class="toctree-l1"><a class="reference internal" href="using.html">Using Reticulum on Your System</a><ul>
<li class="toctree-l2"><a class="reference internal" href="using.html#included-utility-programs">Included Utility Programs</a><ul>
<li class="toctree-l3"><a class="reference internal" href="using.html#the-rnsd-utility">The rnsd Utility</a></li>
<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>
</ul>
</li>
<li class="toctree-l2"><a class="reference internal" href="using.html#improving-system-configuration">Improving System Configuration</a><ul>
<li class="toctree-l3"><a class="reference internal" href="using.html#fixed-serial-port-names">Fixed Serial Port Names</a></li>
<li class="toctree-l3"><a class="reference internal" href="using.html#reticulum-as-a-system-service">Reticulum as a System Service</a></li>
</ul>
</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#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>
</ul>
</li>
<li class="toctree-l1"><a class="reference internal" href="understanding.html">Understanding Reticulum</a><ul>
@@ -91,13 +133,14 @@ the development of Reticulum itself.</p>
<li class="toctree-l3"><a class="reference internal" href="reference.html#packet">Packet</a></li>
<li class="toctree-l3"><a class="reference internal" href="reference.html#packet-receipt">Packet Receipt</a></li>
<li class="toctree-l3"><a class="reference internal" href="reference.html#link">Link</a></li>
<li class="toctree-l3"><a class="reference internal" href="reference.html#request-receipt">Request Receipt</a></li>
<li class="toctree-l3"><a class="reference internal" href="reference.html#resource">Resource</a></li>
<li class="toctree-l3"><a class="reference internal" href="reference.html#transport">Transport</a></li>
</ul>
</li>
</ul>
</li>
<li class="toctree-l1"><a class="reference internal" href="examples.html">Examples</a><ul>
<li class="toctree-l1"><a class="reference internal" href="examples.html">Code Examples</a><ul>
<li class="toctree-l2"><a class="reference internal" href="examples.html#minimal">Minimal</a></li>
<li class="toctree-l2"><a class="reference internal" href="examples.html#announce">Announce</a></li>
<li class="toctree-l2"><a class="reference internal" href="examples.html#broadcast">Broadcast</a></li>
@@ -167,7 +210,7 @@ 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.2.2 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-0"><a href="#">Reticulum Network Stack 0.3.3 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">Reticulum Network Stack Manual</a></li>
</ul>
</div>
+592
View File
@@ -0,0 +1,592 @@
<!DOCTYPE html>
<html>
<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.3 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="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>
<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="understanding.html" title="Understanding Reticulum"
accesskey="N">next</a> |</li>
<li class="right" >
<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.3 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">Supported Interfaces</a></li>
</ul>
</div>
<div class="document">
<div class="documentwrapper">
<div class="bodywrapper">
<div class="body" role="main">
<div class="section" id="supported-interfaces">
<span id="interfaces-main"></span><h1>Supported Interfaces<a class="headerlink" href="#supported-interfaces" title="Permalink to this headline"></a></h1>
<p>Reticulum supports using many kinds of devices as networking interfaces, and
allows you to mix and match them in any way you choose. The number of distinct
network topologies you can create with Reticulum is more or less endless, but
common to them all is that you will need to define one or more <em>interfaces</em>
for Reticulum to use.</p>
<p>The following sections describe the interfaces currently available in Reticulum,
and gives example configurations for the respective interface types.</p>
<p>For a high-level overview of how networks can be formed over different interface
types, have a look at the <a class="reference internal" href="networks.html#networks-main"><span class="std std-ref">Building Networks</span></a> chapter of this
manual.</p>
<div class="section" id="auto-interface">
<span id="interfaces-auto"></span><h2>Auto Interface<a class="headerlink" href="#auto-interface" title="Permalink to this headline"></a></h2>
<p>The Auto Interface enables communication with other discoverable Reticulum
nodes over autoconfigured IPv6 and UDP. It does not need any functional IP
infrastructure like routers or DHCP servers, but will require at least some
sort of switching medium between peers (a wired switch, a hub, a WiFi access
point or similar), and that link-local IPv6 is enabled in your operating
system, which should be enabled by default in almost all OSes.</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># This example demonstrates a TCP server interface.</span>
<span class="c1"># It will listen for incoming connections on the</span>
<span class="c1"># specified IP address and port number.</span>
<span class="p">[[</span><span class="n">Default</span> <span class="n">Interface</span><span class="p">]]</span>
<span class="nb">type</span> <span class="o">=</span> <span class="n">AutoInterface</span>
<span class="n">interface_enabled</span> <span class="o">=</span> <span class="kc">True</span>
<span class="c1"># You can create multiple isolated Reticulum</span>
<span class="c1"># networks on the same physical LAN by</span>
<span class="c1"># specifying different Group IDs.</span>
<span class="n">group_id</span> <span class="o">=</span> <span class="n">reticulum</span>
<span class="c1"># You can also select specifically which</span>
<span class="c1"># kernel networking devices to use.</span>
<span class="n">devices</span> <span class="o">=</span> <span class="n">wlan0</span><span class="p">,</span><span class="n">eth1</span>
<span class="c1"># Or let AutoInterface use all suitable</span>
<span class="c1"># devices except for a list of ignored ones.</span>
<span class="n">ignored_devices</span> <span class="o">=</span> <span class="n">tun0</span><span class="p">,</span><span class="n">eth0</span>
</pre></div>
</div>
<p>If you are connected to the Internet with IPv6, and your provider will route
IPv6 multicast, you can potentially configure the Auto Interface to globally
autodiscover other Reticulum nodes within your selected Group ID. You can specify
the discovery scope by setting it to one of <code class="docutils literal notranslate"><span class="pre">link</span></code>, <code class="docutils literal notranslate"><span class="pre">admin</span></code>, <code class="docutils literal notranslate"><span class="pre">site</span></code>,
<code class="docutils literal notranslate"><span class="pre">organisation</span></code> or <code class="docutils literal notranslate"><span class="pre">global</span></code>.</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="p">[[</span><span class="n">Default</span> <span class="n">Interface</span><span class="p">]]</span>
<span class="nb">type</span> <span class="o">=</span> <span class="n">AutoInterface</span>
<span class="n">interface_enabled</span> <span class="o">=</span> <span class="kc">True</span>
<span class="c1"># Configure global discovery</span>
<span class="n">group_id</span> <span class="o">=</span> <span class="n">custom_network_name</span>
<span class="n">discovery_scope</span> <span class="o">=</span> <span class="k">global</span>
<span class="c1"># Other configuration options</span>
<span class="n">discovery_port</span> <span class="o">=</span> <span class="mi">48555</span>
<span class="n">data_port</span> <span class="o">=</span> <span class="mi">49555</span>
</pre></div>
</div>
</div>
<div class="section" id="i2p-interface">
<span id="interfaces-i2p"></span><h2>I2P Interface<a class="headerlink" href="#i2p-interface" title="Permalink to this headline"></a></h2>
<p>The I2P interface lets you connect Reticulum instances over the
<a class="reference external" href="https://i2pd.website">Invisible Internet Protocol</a>. This can be
especially useful in cases where you want to host a globally reachable
Reticulum instance, but do not have access to any public IP addresses,
have a frequently changing IP address, or have firewalls blocking
inbound traffic.</p>
<p>Using the I2P interface, you will get a globally reachable, portable
and persistent I2P address that your Reticulum instance can be reached
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>
<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>
<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">connectable</span> <span class="o">=</span> <span class="n">yes</span>
</pre></div>
</div>
<p>On the first start, Reticulum will generate a new I2P address for the
interface and start listening for inbound traffic on it. This can take
a while the first time, especially if your I2P router was also just
started, and is not yet well-connected to the I2P network. When ready,
you should see I2P base32 address printed to your log file. You can
also inspect the status of the interface using the <code class="docutils literal notranslate"><span class="pre">rnstatus</span></code> utility.</p>
<p>To connect to other Reticulum instances over I2P, just add a comma-separated
list of I2P base32 addresses to the <code class="docutils literal notranslate"><span class="pre">peers</span></code> option of the interface:</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>
<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">connectable</span> <span class="o">=</span> <span class="n">yes</span>
<span class="n">peers</span> <span class="o">=</span> <span class="mi">5</span><span class="n">urvjicpzi7q3ybztsef4i5ow2aq4soktfj7zedz53s47r54jnqq</span><span class="o">.</span><span class="n">b32</span><span class="o">.</span><span class="n">i2p</span>
</pre></div>
</div>
<p>It can take anywhere from a few seconds to a few minutes to establish
I2P connections to the desired peers, so Reticulum handles the process
in the background, and will output relevant events to the log.</p>
<p><strong>Please Note!</strong> While the I2P interface is the simplest way to use
Reticulum over I2P, it is also possible to tunnel the TCP server and
client interfaces over I2P manually. This can be useful in situations
where more control is needed, but requires manual tunnel setup through
the I2P daemon configuration.</p>
<p>It is important to note that the two methods are <em>interchangably compatible</em>.
You can use the I2PInterface to connect to a TCPServerInterface that
was manually tunneled over I2P, for example. This offers a high degree
of flexibility in network setup, while retaining ease of use in simpler
use-cases.</p>
</div>
<div class="section" id="tcp-server-interface">
<span id="interfaces-tcps"></span><h2>TCP Server Interface<a class="headerlink" href="#tcp-server-interface" title="Permalink to this headline"></a></h2>
<p>The TCP Server interface is suitable for allowing other peers to connect over
the Internet or private IP networks. When a TCP server interface has been
configured, other Reticulum peers can connect to it with a TCP Client interface.</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># This example demonstrates a TCP server interface.</span>
<span class="c1"># It will listen for incoming connections on the</span>
<span class="c1"># specified IP address and port number.</span>
<span class="p">[[</span><span class="n">TCP</span> <span class="n">Server</span> <span class="n">Interface</span><span class="p">]]</span>
<span class="nb">type</span> <span class="o">=</span> <span class="n">TCPServerInterface</span>
<span class="n">interface_enabled</span> <span class="o">=</span> <span class="kc">True</span>
<span class="c1"># This configuration will listen on all IP</span>
<span class="c1"># interfaces on port 4242</span>
<span class="n">listen_ip</span> <span class="o">=</span> <span class="mf">0.0</span><span class="o">.</span><span class="mf">0.0</span>
<span class="n">listen_port</span> <span class="o">=</span> <span class="mi">4242</span>
<span class="c1"># Alternatively you can bind to a specific IP</span>
<span class="c1"># listen_ip = 10.0.0.88</span>
<span class="c1"># listen_port = 4242</span>
<span class="c1"># Or a specific network device</span>
<span class="c1"># device = eth0</span>
<span class="c1"># port = 4242</span>
</pre></div>
</div>
<p><strong>Please Note!</strong> The TCP interfaces support tunneling over I2P, but to do so reliably,
you must use the i2p_tunneled option:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="p">[[</span><span class="n">TCP</span> <span class="n">Server</span> <span class="n">on</span> <span class="n">I2P</span><span class="p">]]</span>
<span class="nb">type</span> <span class="o">=</span> <span class="n">TCPServerInterface</span>
<span class="n">interface_enabled</span> <span class="o">=</span> <span class="n">yes</span>
<span class="n">listen_ip</span> <span class="o">=</span> <span class="mf">127.0</span><span class="o">.</span><span class="mf">0.1</span>
<span class="n">listen_port</span> <span class="o">=</span> <span class="mi">5001</span>
<span class="n">i2p_tunneled</span> <span class="o">=</span> <span class="n">yes</span>
</pre></div>
</div>
</div>
<div class="section" id="tcp-client-interface">
<span id="interfaces-tcpc"></span><h2>TCP Client Interface<a class="headerlink" href="#tcp-client-interface" title="Permalink to this headline"></a></h2>
<p>To connect to a TCP server interface, you would naturally use the TCP client
interface. Many TCP Client interfaces from different peers can connect to the
same TCP Server interface at the same time.</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># Here&#39;s an example of a TCP Client interface. The</span>
<span class="c1"># target_host can either be an IP address or a hostname.</span>
<span class="p">[[</span><span class="n">TCP</span> <span class="n">Client</span> <span class="n">Interface</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="kc">True</span>
<span class="n">target_host</span> <span class="o">=</span> <span class="mf">127.0</span><span class="o">.</span><span class="mf">0.1</span>
<span class="n">target_port</span> <span class="o">=</span> <span class="mi">4242</span>
</pre></div>
</div>
<p>It is also possible to use this interface type to connect via other programs
or hardware devices that expose a KISS interface on a TCP port, for example
software-based soundmodems. To do this, use the <code class="docutils literal notranslate"><span class="pre">kiss_framing</span></code> option:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># Here&#39;s an example of a TCP Client interface that connects</span>
<span class="c1"># to a software TNC soundmodem on a KISS over TCP port.</span>
<span class="p">[[</span><span class="n">TCP</span> <span class="n">KISS</span> <span class="n">Interface</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="kc">True</span>
<span class="n">kiss_framing</span> <span class="o">=</span> <span class="kc">True</span>
<span class="n">target_host</span> <span class="o">=</span> <span class="mf">127.0</span><span class="o">.</span><span class="mf">0.1</span>
<span class="n">target_port</span> <span class="o">=</span> <span class="mi">8001</span>
</pre></div>
</div>
<p><strong>Caution!</strong> Only use the KISS framing option when connecting to external devices
and programs like soundmodems and similar over TCP. When using the
<code class="docutils literal notranslate"><span class="pre">TCPClientInterface</span></code> in conjunction with the <code class="docutils literal notranslate"><span class="pre">TCPServerInterface</span></code> you should
never enable <code class="docutils literal notranslate"><span class="pre">kiss_framing</span></code>, since this will disable internal reliability and
recovery mechanisms that greatly improves performance over unreliable and
intermittent TCP links.</p>
<p><strong>Please Note!</strong> The TCP interfaces support tunneling over I2P, but to do so reliably,
you must use the i2p_tunneled option:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="p">[[</span><span class="n">TCP</span> <span class="n">Client</span> <span class="n">over</span> <span class="n">I2P</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">target_host</span> <span class="o">=</span> <span class="mf">127.0</span><span class="o">.</span><span class="mf">0.1</span>
<span class="n">target_port</span> <span class="o">=</span> <span class="mi">5001</span>
<span class="n">i2p_tunneled</span> <span class="o">=</span> <span class="n">yes</span>
</pre></div>
</div>
</div>
<div class="section" id="udp-interface">
<span id="interfaces-udp"></span><h2>UDP Interface<a class="headerlink" href="#udp-interface" title="Permalink to this headline"></a></h2>
<p>A UDP interface can be useful for communicating over IP networks, both
private and the internet. It can also allow broadcast communication
over IP networks, so it can provide an easy way to enable connectivity
with all other peers on a local area network.</p>
<p><em>Please Note!</em> Using broadcast UDP traffic has performance implications,
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 just as
easy 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>
<span class="p">[[</span><span class="n">Default</span> <span class="n">UDP</span> <span class="n">Interface</span><span class="p">]]</span>
<span class="nb">type</span> <span class="o">=</span> <span class="n">UDPInterface</span>
<span class="n">interface_enabled</span> <span class="o">=</span> <span class="kc">True</span>
<span class="n">listen_ip</span> <span class="o">=</span> <span class="mf">0.0</span><span class="o">.</span><span class="mf">0.0</span>
<span class="n">listen_port</span> <span class="o">=</span> <span class="mi">4242</span>
<span class="n">forward_ip</span> <span class="o">=</span> <span class="mf">255.255</span><span class="o">.</span><span class="mf">255.255</span>
<span class="n">forward_port</span> <span class="o">=</span> <span class="mi">4242</span>
<span class="c1"># The above configuration will allow communication</span>
<span class="c1"># within the local broadcast domains of all local</span>
<span class="c1"># IP interfaces.</span>
<span class="c1"># Instead of specifying listen_ip, listen_port,</span>
<span class="c1"># forward_ip and forward_port, you can also bind</span>
<span class="c1"># to a specific network device like below.</span>
<span class="c1"># device = eth0</span>
<span class="c1"># port = 4242</span>
<span class="c1"># Assuming the eth0 device has the address</span>
<span class="c1"># 10.55.0.72/24, the above configuration would</span>
<span class="c1"># be equivalent to the following manual setup.</span>
<span class="c1"># Note that we are both listening and forwarding to</span>
<span class="c1"># the broadcast address of the network segments.</span>
<span class="c1"># listen_ip = 10.55.0.255</span>
<span class="c1"># listen_port = 4242</span>
<span class="c1"># forward_ip = 10.55.0.255</span>
<span class="c1"># forward_port = 4242</span>
<span class="c1"># You can of course also communicate only with</span>
<span class="c1"># a single IP address</span>
<span class="c1"># listen_ip = 10.55.0.15</span>
<span class="c1"># listen_port = 4242</span>
<span class="c1"># forward_ip = 10.55.0.16</span>
<span class="c1"># forward_port = 4242</span>
</pre></div>
</div>
</div>
<div class="section" id="rnode-lora-interface">
<span id="interfaces-rnode"></span><h2>RNode LoRa Interface<a class="headerlink" href="#rnode-lora-interface" title="Permalink to this headline"></a></h2>
<p>To use Reticulum over LoRa, the <a class="reference external" href="https://unsigned.io/rnode/">RNode</a> interface
can be used, and offers full control over LoRa parameters.</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># Here&#39;s an example of how to add a LoRa interface</span>
<span class="c1"># using the RNode LoRa transceiver.</span>
<span class="p">[[</span><span class="n">RNode</span> <span class="n">LoRa</span> <span class="n">Interface</span><span class="p">]]</span>
<span class="nb">type</span> <span class="o">=</span> <span class="n">RNodeInterface</span>
<span class="c1"># Enable interface if you want use it!</span>
<span class="n">interface_enabled</span> <span class="o">=</span> <span class="kc">True</span>
<span class="c1"># Serial port for the device</span>
<span class="n">port</span> <span class="o">=</span> <span class="o">/</span><span class="n">dev</span><span class="o">/</span><span class="n">ttyUSB0</span>
<span class="c1"># Set frequency to 867.2 MHz</span>
<span class="n">frequency</span> <span class="o">=</span> <span class="mi">867200000</span>
<span class="c1"># Set LoRa bandwidth to 125 KHz</span>
<span class="n">bandwidth</span> <span class="o">=</span> <span class="mi">125000</span>
<span class="c1"># Set TX power to 7 dBm (5 mW)</span>
<span class="n">txpower</span> <span class="o">=</span> <span class="mi">7</span>
<span class="c1"># Select spreading factor 8. Valid</span>
<span class="c1"># range is 7 through 12, with 7</span>
<span class="c1"># being the fastest and 12 having</span>
<span class="c1"># the longest range.</span>
<span class="n">spreadingfactor</span> <span class="o">=</span> <span class="mi">8</span>
<span class="c1"># Select coding rate 5. Valid range</span>
<span class="c1"># is 5 throough 8, with 5 being the</span>
<span class="c1"># fastest, and 8 the longest range.</span>
<span class="n">codingrate</span> <span class="o">=</span> <span class="mi">5</span>
<span class="c1"># You can configure the RNode to send</span>
<span class="c1"># out identification on the channel with</span>
<span class="c1"># a set interval by configuring the</span>
<span class="c1"># following two parameters.</span>
<span class="c1"># id_callsign = MYCALL-0</span>
<span class="c1"># id_interval = 600</span>
<span class="c1"># For certain homebrew RNode interfaces</span>
<span class="c1"># with low amounts of RAM, using packet</span>
<span class="c1"># flow control can be useful. By default</span>
<span class="c1"># it is disabled.</span>
<span class="n">flow_control</span> <span class="o">=</span> <span class="kc">False</span>
</pre></div>
</div>
</div>
<div class="section" id="serial-interface">
<span id="interfaces-serial"></span><h2>Serial Interface<a class="headerlink" href="#serial-interface" title="Permalink to this headline"></a></h2>
<p>Reticulum can be used over serial ports directly, or over any device with a
serial port, that will transparently pass data. Useful for communicating
directly over a wire-pair, or for using devices such as data radios and lasers.</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="p">[[</span><span class="n">Serial</span> <span class="n">Interface</span><span class="p">]]</span>
<span class="nb">type</span> <span class="o">=</span> <span class="n">SerialInterface</span>
<span class="n">interface_enabled</span> <span class="o">=</span> <span class="kc">True</span>
<span class="c1"># Serial port for the device</span>
<span class="n">port</span> <span class="o">=</span> <span class="o">/</span><span class="n">dev</span><span class="o">/</span><span class="n">ttyUSB0</span>
<span class="c1"># Set the serial baud-rate and other</span>
<span class="c1"># configuration parameters.</span>
<span class="n">speed</span> <span class="o">=</span> <span class="mi">115200</span>
<span class="n">databits</span> <span class="o">=</span> <span class="mi">8</span>
<span class="n">parity</span> <span class="o">=</span> <span class="n">none</span>
<span class="n">stopbits</span> <span class="o">=</span> <span class="mi">1</span>
</pre></div>
</div>
</div>
<div class="section" id="kiss-interface">
<span id="interfaces-kiss"></span><h2>KISS Interface<a class="headerlink" href="#kiss-interface" title="Permalink to this headline"></a></h2>
<p>With the KISS interface, you can use Reticulum over a variety of packet
radio modems and TNCs, including <a class="reference external" href="https://unsigned.io/openmodem/">OpenModem</a>.
KISS interfaces can also be configured to periodically send out beacons
for station identification purposes.</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="p">[[</span><span class="n">Packet</span> <span class="n">Radio</span> <span class="n">KISS</span> <span class="n">Interface</span><span class="p">]]</span>
<span class="nb">type</span> <span class="o">=</span> <span class="n">KISSInterface</span>
<span class="n">interface_enabled</span> <span class="o">=</span> <span class="kc">True</span>
<span class="c1"># Serial port for the device</span>
<span class="n">port</span> <span class="o">=</span> <span class="o">/</span><span class="n">dev</span><span class="o">/</span><span class="n">ttyUSB1</span>
<span class="c1"># Set the serial baud-rate and other</span>
<span class="c1"># configuration parameters.</span>
<span class="n">speed</span> <span class="o">=</span> <span class="mi">115200</span>
<span class="n">databits</span> <span class="o">=</span> <span class="mi">8</span>
<span class="n">parity</span> <span class="o">=</span> <span class="n">none</span>
<span class="n">stopbits</span> <span class="o">=</span> <span class="mi">1</span>
<span class="c1"># Set the modem preamble.</span>
<span class="n">preamble</span> <span class="o">=</span> <span class="mi">150</span>
<span class="c1"># Set the modem TX tail.</span>
<span class="n">txtail</span> <span class="o">=</span> <span class="mi">10</span>
<span class="c1"># Configure CDMA parameters. These</span>
<span class="c1"># settings are reasonable defaults.</span>
<span class="n">persistence</span> <span class="o">=</span> <span class="mi">200</span>
<span class="n">slottime</span> <span class="o">=</span> <span class="mi">20</span>
<span class="c1"># You can configure the interface to send</span>
<span class="c1"># out identification on the channel with</span>
<span class="c1"># a set interval by configuring the</span>
<span class="c1"># following two parameters. The KISS</span>
<span class="c1"># interface will only ID if the set</span>
<span class="c1"># interval has elapsed since it&#39;s last</span>
<span class="c1"># actual transmission. The interval is</span>
<span class="c1"># configured in seconds.</span>
<span class="c1"># This option is commented out and not</span>
<span class="c1"># used by default.</span>
<span class="c1"># id_callsign = MYCALL-0</span>
<span class="c1"># id_interval = 600</span>
<span class="c1"># Whether to use KISS flow-control.</span>
<span class="c1"># This is useful for modems that have</span>
<span class="c1"># a small internal packet buffer, but</span>
<span class="c1"># support packet flow control instead.</span>
<span class="n">flow_control</span> <span class="o">=</span> <span class="n">false</span>
</pre></div>
</div>
</div>
<div class="section" id="ax-25-kiss-interface">
<span id="interfaces-ax25"></span><h2>AX.25 KISS Interface<a class="headerlink" href="#ax-25-kiss-interface" title="Permalink to this headline"></a></h2>
<p>If youre using Reticulum on amateur radio spectrum, you might want to
use the AX.25 KISS interface. This way, Reticulum will automatically
encapsulate its traffic in AX.25 and also identify your stations
transmissions with your callsign and SSID.</p>
<p>Only do this if you really need to! Reticulum doesnt need the AX.25
layer for anything, and it incurs extra overhead on every packet to
encapsulate in AX.25.</p>
<p>A more efficient way is to use the plain KISS interface with the
beaconing functionality described above.</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="p">[[</span><span class="n">Packet</span> <span class="n">Radio</span> <span class="n">AX</span><span class="o">.</span><span class="mi">25</span> <span class="n">KISS</span> <span class="n">Interface</span><span class="p">]]</span>
<span class="nb">type</span> <span class="o">=</span> <span class="n">AX25KISSInterface</span>
<span class="c1"># Set the station callsign and SSID</span>
<span class="n">callsign</span> <span class="o">=</span> <span class="n">NO1CLL</span>
<span class="n">ssid</span> <span class="o">=</span> <span class="mi">0</span>
<span class="c1"># Enable interface if you want use it!</span>
<span class="n">interface_enabled</span> <span class="o">=</span> <span class="kc">True</span>
<span class="c1"># Serial port for the device</span>
<span class="n">port</span> <span class="o">=</span> <span class="o">/</span><span class="n">dev</span><span class="o">/</span><span class="n">ttyUSB2</span>
<span class="c1"># Set the serial baud-rate and other</span>
<span class="c1"># configuration parameters.</span>
<span class="n">speed</span> <span class="o">=</span> <span class="mi">115200</span>
<span class="n">databits</span> <span class="o">=</span> <span class="mi">8</span>
<span class="n">parity</span> <span class="o">=</span> <span class="n">none</span>
<span class="n">stopbits</span> <span class="o">=</span> <span class="mi">1</span>
<span class="c1"># Set the modem preamble. A 150ms</span>
<span class="c1"># preamble should be a reasonable</span>
<span class="c1"># default, but may need to be</span>
<span class="c1"># increased for radios with slow-</span>
<span class="c1"># opening squelch and long TX/RX</span>
<span class="c1"># turnaround</span>
<span class="n">preamble</span> <span class="o">=</span> <span class="mi">150</span>
<span class="c1"># Set the modem TX tail. In most</span>
<span class="c1"># cases this should be kept as low</span>
<span class="c1"># as possible to not waste airtime.</span>
<span class="n">txtail</span> <span class="o">=</span> <span class="mi">10</span>
<span class="c1"># Configure CDMA parameters. These</span>
<span class="c1"># settings are reasonable defaults.</span>
<span class="n">persistence</span> <span class="o">=</span> <span class="mi">200</span>
<span class="n">slottime</span> <span class="o">=</span> <span class="mi">20</span>
<span class="c1"># Whether to use KISS flow-control.</span>
<span class="c1"># This is useful for modems with a</span>
<span class="c1"># small internal packet buffer.</span>
<span class="n">flow_control</span> <span class="o">=</span> <span class="n">false</span>
</pre></div>
</div>
</div>
<div class="section" id="common-interface-options">
<span id="interfaces-options"></span><h2>Common Interface Options<a class="headerlink" href="#common-interface-options" title="Permalink to this headline"></a></h2>
<p>A number of general options can be used to control various
aspects of interface behaviour.</p>
<p>The <code class="docutils literal notranslate"><span class="pre">interface_enabled</span></code> option tells Reticulum whether or not
to bring up the interface. Defaults to <code class="docutils literal notranslate"><span class="pre">False</span></code>. For any
interface to be brought up, the <code class="docutils literal notranslate"><span class="pre">interface_enabled</span></code> option
must be set to <code class="docutils literal notranslate"><span class="pre">True</span></code> or <code class="docutils literal notranslate"><span class="pre">Yes</span></code>.</p>
<p>The <code class="docutils literal notranslate"><span class="pre">outgoing</span></code> option sets whether an interface is allowed
to transmit. Defaults to <code class="docutils literal notranslate"><span class="pre">True</span></code>. If set to <code class="docutils literal notranslate"><span class="pre">False</span></code> the
interface will only receive data, and never transmit.</p>
<p>The <code class="docutils literal notranslate"><span class="pre">interface_mode</span></code> option allows selecting the high-level
behaviour of the interface from a number of options.</p>
<blockquote>
<div><ul class="simple">
<li><p>The default value is <code class="docutils literal notranslate"><span class="pre">full</span></code>. In this mode, all discovery,
meshing and transpor functionality is available.</p></li>
<li><p>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 are mostly 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.</p></li>
</ul>
</div></blockquote>
</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="#">Supported Interfaces</a><ul>
<li><a class="reference internal" href="#auto-interface">Auto Interface</a></li>
<li><a class="reference internal" href="#i2p-interface">I2P Interface</a></li>
<li><a class="reference internal" href="#tcp-server-interface">TCP Server Interface</a></li>
<li><a class="reference internal" href="#tcp-client-interface">TCP Client Interface</a></li>
<li><a class="reference internal" href="#udp-interface">UDP Interface</a></li>
<li><a class="reference internal" href="#rnode-lora-interface">RNode LoRa Interface</a></li>
<li><a class="reference internal" href="#serial-interface">Serial Interface</a></li>
<li><a class="reference internal" href="#kiss-interface">KISS Interface</a></li>
<li><a class="reference internal" href="#ax-25-kiss-interface">AX.25 KISS Interface</a></li>
<li><a class="reference internal" href="#common-interface-options">Common Interface Options</a></li>
</ul>
</li>
</ul>
<h4>Previous topic</h4>
<p class="topless"><a href="networks.html"
title="previous chapter">Building Networks</a></p>
<h4>Next topic</h4>
<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">
<li><a href="_sources/interfaces.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="understanding.html" title="Understanding Reticulum"
>next</a> |</li>
<li class="right" >
<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.3 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">Supported Interfaces</a></li>
</ul>
</div>
<div class="footer" role="contentinfo">
&#169; Copyright 2021, Mark Qvist.
Created using <a href="https://www.sphinx-doc.org/">Sphinx</a> 4.0.1.
</div>
</body>
</html>
+259
View File
@@ -0,0 +1,259 @@
<!DOCTYPE html>
<html>
<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.3 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="Using Reticulum on Your System" href="using.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="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.3 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">Building Networks</a></li>
</ul>
</div>
<div class="document">
<div class="documentwrapper">
<div class="bodywrapper">
<div class="body" role="main">
<div class="section" id="building-networks">
<span id="networks-main"></span><h1>Building Networks<a class="headerlink" href="#building-networks" title="Permalink to this headline"></a></h1>
<p>This chapter will provide you with the knowledge needed to build networks with
Reticulum, which can often be easier than using traditional stacks, since you
dont have to worry about coordinating addresses, subnets and routing for an
entire network that you might not know how will evolve in the future. With
Reticulum, you can simply add more segments to your network when it becomes
necesarry, and Reticulum will handle the convergence of the entire network
automatically.</p>
<div class="section" id="concepts-overview">
<h2>Concepts &amp; Overview<a class="headerlink" href="#concepts-overview" title="Permalink to this headline"></a></h2>
<p>There are important points that need to be kept in mind when building networks
with Reticulum:</p>
<blockquote>
<div><ul>
<li><div class="line-block">
<div class="line">In a Reticulum network, any node can autonomously generate as many adresses
(called <em>destinations</em> in Reticulum terminology) as it needs, which become
globally reachable to the rest of the network. There is no central point of
control over the adress space.</div>
</div>
</li>
<li><div class="line-block">
<div class="line">Reticulum was designed to handle both very small, and very large networks.
While the adress space can support billions of endpoints, Reticulum is
also very useful when just a few devices needs to communicate.</div>
</div>
</li>
<li><div class="line-block">
<div class="line">Reticulum provides sender/initiator anonymity by default. There is no way
to filter traffic or discriminate it based on the source of the traffic.</div>
</div>
</li>
<li><div class="line-block">
<div class="line">All traffic is encrypted using ephemeral keys generated by an Elliptic Curve
Diffie-Hellman key exchange on Curve25519. There is no way to inspect traffic
contents, and no way to prioritise or throttle certain kinds of traffic.
All transport and routing layers are thus completely agnostic to traffic type,
and will pass all traffic equally.</div>
</div>
</li>
<li><div class="line-block">
<div class="line">Reticulum can function both with and without infrastructure. When <em>transport
nodes</em> are available, they can route traffic over multiple hops for other
nodes, and will function as a distributed cryptographic keystore. When there
is no transport nodes available, all nodes that are within communication range
can still communicate.</div>
</div>
</li>
<li><div class="line-block">
<div class="line">Every node can become a transport node, simply by enabling it in its
configuration, but there is no need for every node on the network to be a
transport node. Letting every node be a transport node will in most cases
degrade the performance and reliability of the network.</div>
</div>
<blockquote>
<div><p>In general terms, if a node is stationary, well-connected and kept running
most of the time, it is a good candidate to be a transport node. For optimal
performance, a network should contain the amount of transport nodes that
provides connectivity to the intended area / topography, and not many more
than that.</p>
</div></blockquote>
</li>
</ul>
</div></blockquote>
<p>Reticulum allows you to mix very different kinds of networking mediums into a
unified mesh, or to keep everything within one medium. You could build a “virtual
network” running entirely over the Internet, where all nodes communicate over TCP
and UDP “channels”. You could also build such a network using MQTT or ZeroMQ as
the underlying carrier for Reticulum.</p>
<p>However, most real-world networks will probably involve either some form of
wireless or direct hardline communications. To allow Reticulum to communicate
over any type of medium, you must specify it in the configuration file, by default
located at <code class="docutils literal notranslate"><span class="pre">~/.reticulum/config</span></code>. See the <a class="reference internal" href="interfaces.html#interfaces-main"><span class="std std-ref">Supported Interfaces</span></a>
chapter of this manual for interface configuration examples.</p>
<p>Any number of interfaces can be configured, and Reticulum will automatically
decide which are suitable to use in any given situation, depending on where
traffic needs to flow.</p>
</div>
<div class="section" id="example-scenarios">
<h2>Example Scenarios<a class="headerlink" href="#example-scenarios" title="Permalink to this headline"></a></h2>
<p>This section illustrates a few example scenarios, and how they would, in general
terms, be planned, implemented and configured.</p>
<div class="section" id="interconnected-lora-sites">
<h3>Interconnected LoRa Sites<a class="headerlink" href="#interconnected-lora-sites" title="Permalink to this headline"></a></h3>
<p>An organisation wants to provide communication and information services to its
members, which are located mainly in three separate areas. Three suitable hill-top
locations are found, where the organisation can install equipment: Site A, B and C.</p>
<p>Since the amount of data that needs to be exchanged between users is mainly text-
based, the bandwidth requirements are low, and LoRa radios are chosen to connect
users to the network.</p>
<p>Due to the hill-top locations found, there is radio line-of-sight between site A
and B, and also between site B and C. Because of this, the organisation does not
need to use the Internet to interconnect the sites, but purchases four Point-to-Point
WiFi based radios for interconnecting the sites.</p>
<p>At each site, a Raspberry Pi is installed to function as a gateway. A LoRa radio
is connected to the Pi with a USB cable, and the WiFi radio is connected to the
ethernet port of the Pi. At site B, two WiFi radios are needed to be able to reach
both site A and site C, so an extra ethernet adapter is connected to the Pi in
this location.</p>
<p>Once the hardware has been installed, Reticulum is installed on all the Pis, and at
site A and C, one interface is added for the LoRa radio, as well as one for the WiFi
radio. At site B, an interface for the LoRa radio, and one interface for each WiFi
radio is added to the Reticulum configuration file. The transport node option is
enabled in the configuration of all three gateways.</p>
<p>The network is now operational, and ready to serve users across all three areas.
The organisation prepares a LoRa radio that is supplied to the end users, along
with a Reticulum configuration file, that contains the right parameters for
communicating with the LoRa radios installed at the gateway sites.</p>
<p>Once users connect to the network, anyone will be able to communicate with anyone
else across all three sites.</p>
</div>
<div class="section" id="bridging-over-the-internet">
<h3>Bridging Over the Internet<a class="headerlink" href="#bridging-over-the-internet" title="Permalink to this headline"></a></h3>
<p>As the organisation grows, several new communities form in places too far away
from the core network to be reachable over WiFi links. New gateways similar to those
previously installed are set up for the new communities at the new sites D and E, but
they are islanded from the core network, and only serve the local users.</p>
<p>After investigating the options, it is found that it is possible to install an
Internet connection at site A, and an interface on the Internet connection is
configured for Reticulum on the Raspberry Pi at site A.</p>
<p>A member of the organisation at site D, named Dori, is willing to help by sharing
the Internet connection she already has in her home, and is able to leave a Raspberry
Pi running. A new Reticulum interface is configured on her Pi, connecting to the newly
enabled Internet interface on the gateway at site A. Dori is now connected to both
all the nodes at her own local site (through the hill-top LoRa gateway), and all the
combined users of sites A, B and C. She then enables transport on her node, and
traffic from site D can now reach everyone at site A, B and C, and vice versa.</p>
</div>
<div class="section" id="growth-and-convergence">
<h3>Growth and Convergence<a class="headerlink" href="#growth-and-convergence" title="Permalink to this headline"></a></h3>
<p>As the organisation grows, more gateways are added to keep up with the growing user
base. Some local gateways even add VHF radios and packet modems to reach outlying users
and communities that are out of reach for the LoRa radios and WiFi backhauls.</p>
<p>As more sites, gateways and users are connected, the amount of coordination required
is kept to a minimum. If one community wants to add connectivity to the next one
over, it can simply be done without having to involve everyone or coordinate address
space or routing tables.</p>
<p>With the added geographical coverage, the operators at site A one day find that
the original internet bridged interfaces are no longer utilised. The network has
converged to be completely self-connected, and the sites that were once poorly
connected outliers are now an integral part of the network.</p>
</div>
</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="#">Building Networks</a><ul>
<li><a class="reference internal" href="#concepts-overview">Concepts &amp; Overview</a></li>
<li><a class="reference internal" href="#example-scenarios">Example Scenarios</a><ul>
<li><a class="reference internal" href="#interconnected-lora-sites">Interconnected LoRa Sites</a></li>
<li><a class="reference internal" href="#bridging-over-the-internet">Bridging Over the Internet</a></li>
<li><a class="reference internal" href="#growth-and-convergence">Growth and Convergence</a></li>
</ul>
</li>
</ul>
</li>
</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>
<div role="note" aria-label="source link">
<h3>This Page</h3>
<ul class="this-page-menu">
<li><a href="_sources/networks.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="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.3 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">Building Networks</a></li>
</ul>
</div>
<div class="footer" role="contentinfo">
&#169; Copyright 2021, Mark Qvist.
Created using <a href="https://www.sphinx-doc.org/">Sphinx</a> 4.0.1.
</div>
</body>
</html>
Binary file not shown.
+176 -82
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.2.2 beta documentation</title>
<title>API Reference &#8212; Reticulum Network Stack 0.3.3 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="Examples" href="examples.html" />
<link rel="next" title="Code Examples" href="examples.html" />
<link rel="prev" title="Understanding Reticulum" href="understanding.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="examples.html" title="Examples"
<a href="examples.html" title="Code Examples"
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.2.2 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.3 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">API Reference</a></li>
</ul>
</div>
@@ -51,7 +51,7 @@
<span id="api-reticulum"></span><h3>Reticulum<a class="headerlink" href="#reticulum" title="Permalink to this headline"></a></h3>
<dl class="py class">
<dt class="sig sig-object py" id="RNS.Reticulum">
<em class="property"><span class="pre">class</span> </em><span class="sig-prename descclassname"><span class="pre">RNS.</span></span><span class="sig-name descname"><span class="pre">Reticulum</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">configdir</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Reticulum" title="Permalink to this definition"></a></dt>
<em class="property"><span class="pre">class</span> </em><span class="sig-prename descclassname"><span class="pre">RNS.</span></span><span class="sig-name descname"><span class="pre">Reticulum</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">configdir</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">loglevel</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Reticulum" title="Permalink to this definition"></a></dt>
<dd><p>This class is used to initialise access to Reticulum within a
program. You must create exactly one instance of this class before
carrying out any other RNS operations, such as creating destinations
@@ -72,16 +72,16 @@ terminated (unless killed forcibly).</p>
programs that use RNS starting and terminating at different times,
it will be advantageous to run a master RNS instance as a daemon for
other programs to use on demand.</p>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Reticulum.should_allow_unencrypted">
<em class="property"><span class="pre">static</span> </em><span class="sig-name descname"><span class="pre">should_allow_unencrypted</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Reticulum.should_allow_unencrypted" title="Permalink to this definition"></a></dt>
<dd><p>Returns whether unencrypted links are allowed by the
current configuration.</p>
<dl class="field-list simple">
<dt class="field-odd">Returns</dt>
<dd class="field-odd"><p>True if the current running configuration allows downgrading links to plaintext. False if not.</p>
</dd>
</dl>
<dl class="py attribute">
<dt class="sig sig-object py" id="RNS.Reticulum.MTU">
<span class="sig-name descname"><span class="pre">MTU</span></span><em class="property"> <span class="pre">=</span> <span class="pre">500</span></em><a class="headerlink" href="#RNS.Reticulum.MTU" title="Permalink to this definition"></a></dt>
<dd><p>The MTU that Reticulum adheres to, and will expect other peers to
adhere to. By default, the MTU is 500 bytes. In custom RNS network
implementations, it is possible to change this value, but doing so will
completely break compatibility with all other RNS networks. An identical
MTU is a prerequisite for peers to communicate in the same network.</p>
<p>Unless you really know what you are doing, the MTU should be left at
the default value.</p>
</dd></dl>
<dl class="py method">
@@ -215,6 +215,21 @@ for addressable hashes and other purposes. Non-configurable.</p>
</dl>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Identity.from_bytes">
<em class="property"><span class="pre">static</span> </em><span class="sig-name descname"><span class="pre">from_bytes</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">prv_bytes</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Identity.from_bytes" title="Permalink to this definition"></a></dt>
<dd><p>Create a new <a class="reference internal" href="#api-identity"><span class="std std-ref">RNS.Identity</span></a> instance from <em>bytes</em> of private key.
Can be used to load previously created and saved identities into Reticulum.</p>
<dl class="field-list simple">
<dt class="field-odd">Parameters</dt>
<dd class="field-odd"><p><strong>prv_bytes</strong> The <em>bytes</em> of private a saved private key. <strong>HAZARD!</strong> Never use this to generate a new key by feeding random data in prv_bytes.</p>
</dd>
<dt class="field-even">Returns</dt>
<dd class="field-even"><p>A <a class="reference internal" href="#api-identity"><span class="std std-ref">RNS.Identity</span></a> instance, or <em>None</em> if the <em>bytes</em> data was invalid.</p>
</dd>
</dl>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Identity.from_file">
<em class="property"><span class="pre">static</span> </em><span class="sig-name descname"><span class="pre">from_file</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">path</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Identity.from_file" title="Permalink to this definition"></a></dt>
@@ -231,16 +246,17 @@ Can be used to load previously created and saved identities into Reticulum.</p>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Identity.from_bytes">
<em class="property"><span class="pre">static</span> </em><span class="sig-name descname"><span class="pre">from_bytes</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">prv_bytes</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Identity.from_bytes" title="Permalink to this definition"></a></dt>
<dd><p>Create a new <a class="reference internal" href="#api-identity"><span class="std std-ref">RNS.Identity</span></a> instance from <em>bytes</em> of private key.
Can be used to load previously created and saved identities into Reticulum.</p>
<dt class="sig sig-object py" id="RNS.Identity.to_file">
<span class="sig-name descname"><span class="pre">to_file</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">path</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Identity.to_file" title="Permalink to this definition"></a></dt>
<dd><p>Saves the identity to a file. This will write the private key to disk,
and anyone with access to this file will be able to decrypt all
communication for the identity. Be very careful with this method.</p>
<dl class="field-list simple">
<dt class="field-odd">Parameters</dt>
<dd class="field-odd"><p><strong>prv_bytes</strong> The <em>bytes</em> of private a saved private key. <strong>HAZARD!</strong> Never not use this to generate a new key by feeding random data in prv_bytes.</p>
<dd class="field-odd"><p><strong>path</strong> The full path specifying where to save the identity.</p>
</dd>
<dt class="field-even">Returns</dt>
<dd class="field-even"><p>A <a class="reference internal" href="#api-identity"><span class="std std-ref">RNS.Identity</span></a> instance, or <em>None</em> if the <em>bytes</em> data was invalid.</p>
<dd class="field-even"><p>True if the file was saved, otherwise False.</p>
</dd>
</dl>
</dd></dl>
@@ -293,22 +309,6 @@ Can be used to load previously created and saved identities into Reticulum.</p>
</dl>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Identity.to_file">
<span class="sig-name descname"><span class="pre">to_file</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">path</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Identity.to_file" title="Permalink to this definition"></a></dt>
<dd><p>Saves the identity to a file. This will write the private key to disk,
and anyone with access to this file will be able to decrypt all
communication for the identity. Be very careful with this method.</p>
<dl class="field-list simple">
<dt class="field-odd">Parameters</dt>
<dd class="field-odd"><p><strong>path</strong> The full path specifying where to save the identity.</p>
</dd>
<dt class="field-even">Returns</dt>
<dd class="field-even"><p>True if the file was saved, otherwise False.</p>
</dd>
</dl>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Identity.encrypt">
<span class="sig-name descname"><span class="pre">encrypt</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">plaintext</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Identity.encrypt" title="Permalink to this definition"></a></dt>
@@ -398,7 +398,7 @@ encrypted communication with it.</p>
<dt class="field-odd">Parameters</dt>
<dd class="field-odd"><ul class="simple">
<li><p><strong>identity</strong> An instance of <a class="reference internal" href="#api-identity"><span class="std std-ref">RNS.Identity</span></a>. Can hold only public keys for an outgoing destination, or holding private keys for an ingoing.</p></li>
<li><p><strong>direction</strong> <code class="docutils literal notranslate"><span class="pre">RNS.Destination.IN</span></code> or <code class="docutils literal notranslate"><span class="pre">RNS.Destination.OUT</span></code></p></li>
<li><p><strong>direction</strong> <code class="docutils literal notranslate"><span class="pre">RNS.Destination.IN</span></code> or <code class="docutils literal notranslate"><span class="pre">RNS.Destination.OUT</span></code>.</p></li>
<li><p><strong>type</strong> <code class="docutils literal notranslate"><span class="pre">RNS.Destination.SINGLE</span></code>, <code class="docutils literal notranslate"><span class="pre">RNS.Destination.GROUP</span></code> or <code class="docutils literal notranslate"><span class="pre">RNS.Destination.PLAIN</span></code>.</p></li>
<li><p><strong>app_name</strong> A string specifying the app name.</p></li>
<li><p><strong>*aspects</strong> Any non-zero number of string arguments.</p></li>
@@ -645,7 +645,7 @@ unless other app_data is specified in the <em>announce</em> method.</p>
<span id="api-packet"></span><h3>Packet<a class="headerlink" href="#packet" title="Permalink to this headline"></a></h3>
<dl class="py class">
<dt class="sig sig-object py" id="RNS.Packet">
<em class="property"><span class="pre">class</span> </em><span class="sig-prename descclassname"><span class="pre">RNS.</span></span><span class="sig-name descname"><span class="pre">Packet</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">destination</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">data</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">packet_type</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">0</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">context</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">0</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">transport_type</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">0</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">header_type</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">0</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">transport_id</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">attached_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">create_receipt</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">True</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Packet" title="Permalink to this definition"></a></dt>
<em class="property"><span class="pre">class</span> </em><span class="sig-prename descclassname"><span class="pre">RNS.</span></span><span class="sig-name descname"><span class="pre">Packet</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">destination</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">data</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">create_receipt</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">True</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Packet" title="Permalink to this definition"></a></dt>
<dd><p>The Packet class is used to create packet instances that can be sent
over a Reticulum network. Packets to will automatically be encrypted if
they are adressed to a <code class="docutils literal notranslate"><span class="pre">RNS.Destination.SINGLE</span></code> destination,
@@ -660,11 +660,6 @@ destinations, reticulum will use ephemeral keys, and offers <strong>Forward Secr
<li><p><strong>destination</strong> A <a class="reference internal" href="#api-destination"><span class="std std-ref">RNS.Destination</span></a> instance to which the packet will be sent.</p></li>
<li><p><strong>data</strong> The data payload to be included in the packet as <em>bytes</em>.</p></li>
<li><p><strong>create_receipt</strong> Specifies whether a <a class="reference internal" href="#api-packetreceipt"><span class="std std-ref">RNS.PacketReceipt</span></a> should be created when instantiating the packet.</p></li>
<li><p><strong>type</strong> Internal use by <a class="reference internal" href="#api-transport"><span class="std std-ref">RNS.Transport</span></a>. Defaults to <code class="docutils literal notranslate"><span class="pre">RNS.Packet.DATA</span></code>, and should not be specified.</p></li>
<li><p><strong>context</strong> Internal use by <a class="reference internal" href="#api-transport"><span class="std std-ref">RNS.Transport</span></a>. Ignore.</p></li>
<li><p><strong>transport_type</strong> Internal use by <a class="reference internal" href="#api-transport"><span class="std std-ref">RNS.Transport</span></a>. Ignore.</p></li>
<li><p><strong>transport_id</strong> Internal use by <a class="reference internal" href="#api-transport"><span class="std std-ref">RNS.Transport</span></a>. Ignore.</p></li>
<li><p><strong>attached_interface</strong> Internal use by <a class="reference internal" href="#api-transport"><span class="std std-ref">RNS.Transport</span></a>. Ignore.</p></li>
</ul>
</dd>
</dl>
@@ -709,11 +704,11 @@ destinations, reticulum will use ephemeral keys, and offers <strong>Forward Secr
<span id="api-packetreceipt"></span><h3>Packet Receipt<a class="headerlink" href="#packet-receipt" title="Permalink to this headline"></a></h3>
<dl class="py class">
<dt class="sig sig-object py" id="RNS.PacketReceipt">
<em class="property"><span class="pre">class</span> </em><span class="sig-prename descclassname"><span class="pre">RNS.</span></span><span class="sig-name descname"><span class="pre">PacketReceipt</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">packet</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.PacketReceipt" title="Permalink to this definition"></a></dt>
<em class="property"><span class="pre">class</span> </em><span class="sig-prename descclassname"><span class="pre">RNS.</span></span><span class="sig-name descname"><span class="pre">PacketReceipt</span></span><a class="headerlink" href="#RNS.PacketReceipt" title="Permalink to this definition"></a></dt>
<dd><p>The PacketReceipt class is used to receive notifications about
<a class="reference internal" href="#api-packet"><span class="std std-ref">RNS.Packet</span></a> instances sent over the network. Instances
of this class should never be created manually, but always returned
from a the <em>send()</em> method of a <a class="reference internal" href="#api-packet"><span class="std std-ref">RNS.Packet</span></a> instance.</p>
of this class are never created manually, but always returned from
the <em>send()</em> method of a <a class="reference internal" href="#api-packet"><span class="std std-ref">RNS.Packet</span></a> instance.</p>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.PacketReceipt.get_status">
<span class="sig-name descname"><span class="pre">get_status</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#RNS.PacketReceipt.get_status" title="Permalink to this definition"></a></dt>
@@ -774,15 +769,16 @@ from a the <em>send()</em> method of a <a class="reference internal" href="#api-
<span id="api-link"></span><h3>Link<a class="headerlink" href="#link" title="Permalink to this headline"></a></h3>
<dl class="py class">
<dt class="sig sig-object py" id="RNS.Link">
<em class="property"><span class="pre">class</span> </em><span class="sig-prename descclassname"><span class="pre">RNS.</span></span><span class="sig-name descname"><span class="pre">Link</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">destination</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">owner</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">peer_pub_bytes</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">peer_sig_pub_bytes</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.Link" title="Permalink to this definition"></a></dt>
<dd><p>This class.</p>
<em class="property"><span class="pre">class</span> </em><span class="sig-prename descclassname"><span class="pre">RNS.</span></span><span class="sig-name descname"><span class="pre">Link</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">destination</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">established_callback</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">closed_callback</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.Link" title="Permalink to this definition"></a></dt>
<dd><p>This class is used to establish and manage links to other peers. When a
link instance is created, Reticulum will attempt to establish verified
connectivity with the specified destination.</p>
<dl class="field-list simple">
<dt class="field-odd">Parameters</dt>
<dd class="field-odd"><ul class="simple">
<li><p><strong>destination</strong> A <a class="reference internal" href="#api-destination"><span class="std std-ref">RNS.Destination</span></a> instance which to establish a link to.</p></li>
<li><p><strong>owner</strong> Internal use by <a class="reference internal" href="#api-transport"><span class="std std-ref">RNS.Transport</span></a>, ignore this argument.</p></li>
<li><p><strong>peer_pub_bytes</strong> Internal use, ignore this argument.</p></li>
<li><p><strong>peer_sig_pub_bytes</strong> Internal use, ignore this argument.</p></li>
<li><p><strong>established_callback</strong> An optional function or method with the signature <em>callback(link)</em> to be called when the link has been established.</p></li>
<li><p><strong>closed_callback</strong> An optional function or method with the signature <em>callback(link)</em> to be called when the link is closed.</p></li>
</ul>
</dd>
</dl>
@@ -793,9 +789,9 @@ from a the <em>send()</em> method of a <a class="reference internal" href="#api-
</dd></dl>
<dl class="py attribute">
<dt class="sig sig-object py" id="RNS.Link.DEFAULT_TIMEOUT">
<span class="sig-name descname"><span class="pre">DEFAULT_TIMEOUT</span></span><em class="property"> <span class="pre">=</span> <span class="pre">15.0</span></em><a class="headerlink" href="#RNS.Link.DEFAULT_TIMEOUT" title="Permalink to this definition"></a></dt>
<dd><p>Default timeout for link establishment in seconds.</p>
<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></dl>
<dl class="py attribute">
@@ -820,16 +816,21 @@ thus preserved. This method can be used for authentication.</p>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Link.request">
<span class="sig-name descname"><span class="pre">request</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">path</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">data</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">response_callback</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">failed_callback</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.Link.request" title="Permalink to this definition"></a></dt>
<span class="sig-name descname"><span class="pre">request</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">path</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">data</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">response_callback</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">failed_callback</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">progress_callback</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">timeout</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.Link.request" title="Permalink to this definition"></a></dt>
<dd><p>Sends a request to the remote peer.</p>
<dl class="field-list simple">
<dt class="field-odd">Parameters</dt>
<dd class="field-odd"><ul class="simple">
<li><p><strong>path</strong> The request path.</p></li>
<li><p><strong>response_callback</strong> A function or method with the signature <em>response_callback(request_receipt)</em> to be called when a response is received. See the <a class="reference internal" href="examples.html#example-request"><span class="std std-ref">Request Example</span></a> for more info.</p></li>
<li><p><strong>failed_callback</strong> A function or method with the signature <em>failed_callback(request_receipt)</em> to be called when a request fails. See the <a class="reference internal" href="examples.html#example-request"><span class="std std-ref">Request Example</span></a> for more info.</p></li>
<li><p><strong>response_callback</strong> An optional function or method with the signature <em>response_callback(request_receipt)</em> to be called when a response is received. See the <a class="reference internal" href="examples.html#example-request"><span class="std std-ref">Request Example</span></a> for more info.</p></li>
<li><p><strong>failed_callback</strong> An optional function or method with the signature <em>failed_callback(request_receipt)</em> to be called when a request fails. See the <a class="reference internal" href="examples.html#example-request"><span class="std std-ref">Request Example</span></a> for more info.</p></li>
<li><p><strong>progress_callback</strong> An optional function or method with the signature <em>progress_callback(request_receipt)</em> to be called when progress is made receiving the response. Progress can be accessed as a float between 0.0 and 1.0 by the <em>request_receipt.progress</em> property.</p></li>
<li><p><strong>timeout</strong> An optional timeout in seconds for the request. If <em>None</em> is supplied it will be calculated based on link RTT.</p></li>
</ul>
</dd>
<dt class="field-even">Returns</dt>
<dd class="field-even"><p>A <a class="reference internal" href="#api-requestreceipt"><span class="std std-ref">RNS.RequestReceipt</span></a> instance if the request was sent, or <em>False</em> if it was not.</p>
</dd>
</dl>
</dd></dl>
@@ -956,16 +957,65 @@ identified over this link.</p>
</dl>
</dd></dl>
</dd></dl>
</div>
<div class="section" id="request-receipt">
<span id="api-requestreceipt"></span><h3>Request Receipt<a class="headerlink" href="#request-receipt" title="Permalink to this headline"></a></h3>
<dl class="py class">
<dt class="sig sig-object py" id="RNS.RequestReceipt">
<em class="property"><span class="pre">class</span> </em><span class="sig-prename descclassname"><span class="pre">RNS.</span></span><span class="sig-name descname"><span class="pre">RequestReceipt</span></span><a class="headerlink" href="#RNS.RequestReceipt" title="Permalink to this definition"></a></dt>
<dd><p>An instance of this class is returned by the <code class="docutils literal notranslate"><span class="pre">request</span></code> method of <code class="docutils literal notranslate"><span class="pre">RNS.Link</span></code>
instances. It should never be instantiated manually. It provides methods to
check status, response time and response data when the request concludes.</p>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Link.disable_encryption">
<span class="sig-name descname"><span class="pre">disable_encryption</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Link.disable_encryption" title="Permalink to this definition"></a></dt>
<dd><p>HAZARDOUS. This will downgrade the link to encryptionless. All
information over the link will be sent in plaintext. Never use
this in production applications. Should only be used for debugging
purposes, and will disappear in a future version.</p>
<p>If encryptionless links are not explicitly allowed in the users
configuration file, Reticulum will terminate itself along with the
client application and throw an error message to the user.</p>
<dt class="sig sig-object py" id="RNS.RequestReceipt.get_request_id">
<span class="sig-name descname"><span class="pre">get_request_id</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#RNS.RequestReceipt.get_request_id" 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 request ID as <em>bytes</em>.</p>
</dd>
</dl>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.RequestReceipt.get_status">
<span class="sig-name descname"><span class="pre">get_status</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#RNS.RequestReceipt.get_status" 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 current status of the request, one of <code class="docutils literal notranslate"><span class="pre">RNS.RequestReceipt.FAILED</span></code>, <code class="docutils literal notranslate"><span class="pre">RNS.RequestReceipt.SENT</span></code>, <code class="docutils literal notranslate"><span class="pre">RNS.RequestReceipt.DELIVERED</span></code>, <code class="docutils literal notranslate"><span class="pre">RNS.RequestReceipt.READY</span></code>.</p>
</dd>
</dl>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.RequestReceipt.get_progress">
<span class="sig-name descname"><span class="pre">get_progress</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#RNS.RequestReceipt.get_progress" 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 progress of a response being received as a <em>float</em> between 0.0 and 1.0.</p>
</dd>
</dl>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.RequestReceipt.get_response">
<span class="sig-name descname"><span class="pre">get_response</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#RNS.RequestReceipt.get_response" 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 response as <em>bytes</em> if it is ready, otherwise <em>None</em>.</p>
</dd>
</dl>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.RequestReceipt.get_response_time">
<span class="sig-name descname"><span class="pre">get_response_time</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#RNS.RequestReceipt.get_response_time" 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 response time of the request in seconds.</p>
</dd>
</dl>
</dd></dl>
</dd></dl>
@@ -975,7 +1025,7 @@ client application and throw an error message to the user.</p>
<span id="api-resource"></span><h3>Resource<a class="headerlink" href="#resource" title="Permalink to this headline"></a></h3>
<dl class="py class">
<dt class="sig sig-object py" id="RNS.Resource">
<em class="property"><span class="pre">class</span> </em><span class="sig-prename descclassname"><span class="pre">RNS.</span></span><span class="sig-name descname"><span class="pre">Resource</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">data</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">link</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">advertise</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">True</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">auto_compress</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">True</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">callback</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">progress_callback</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">segment_index</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">1</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">original_hash</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">request_id</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">is_response</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.Resource" title="Permalink to this definition"></a></dt>
<em class="property"><span class="pre">class</span> </em><span class="sig-prename descclassname"><span class="pre">RNS.</span></span><span class="sig-name descname"><span class="pre">Resource</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">data</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">link</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">advertise</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">True</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">auto_compress</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">True</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">callback</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">progress_callback</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">timeout</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.Resource" title="Permalink to this definition"></a></dt>
<dd><p>The Resource class allows transferring arbitrary amounts
of data over a link. It will automatically handle sequencing,
compression, coordination and checksumming.</p>
@@ -984,14 +1034,10 @@ compression, coordination and checksumming.</p>
<dd class="field-odd"><ul class="simple">
<li><p><strong>data</strong> The data to be transferred. Can be <em>bytes</em> or an open <em>file handle</em>. See the <a class="reference internal" href="examples.html#example-filetransfer"><span class="std std-ref">Filetransfer Example</span></a> for details.</p></li>
<li><p><strong>link</strong> The <a class="reference internal" href="#api-link"><span class="std std-ref">RNS.Link</span></a> instance on which to transfer the data.</p></li>
<li><p><strong>advertise</strong> Whether to automatically advertise the resource. Can be <em>True</em> or <em>False</em>.</p></li>
<li><p><strong>auto_compress</strong> Whether to auto-compress the resource. Can be <em>True</em> or <em>False</em>.</p></li>
<li><p><strong>callback</strong> A <em>callable</em> with the signature <em>callback(resource)</em>. Will be called when the resource transfer concludes.</p></li>
<li><p><strong>progress_callback</strong> A <em>callable</em> with the signature <em>callback(resource)</em>. Will be called whenever the resource transfer progress is updated.</p></li>
<li><p><strong>segment_index</strong> Internal use, ignore.</p></li>
<li><p><strong>original_hash</strong> Internal use, ignore.</p></li>
<li><p><strong>is_request</strong> Internal use, ignore.</p></li>
<li><p><strong>is_response</strong> Internal use, ignore.</p></li>
<li><p><strong>advertise</strong> Optional. Whether to automatically advertise the resource. Can be <em>True</em> or <em>False</em>.</p></li>
<li><p><strong>auto_compress</strong> Optional. Whether to auto-compress the resource. Can be <em>True</em> or <em>False</em>.</p></li>
<li><p><strong>callback</strong> An optional <em>callable</em> with the signature <em>callback(resource)</em>. Will be called when the resource transfer concludes.</p></li>
<li><p><strong>progress_callback</strong> An optional <em>callable</em> with the signature <em>callback(resource)</em>. Will be called whenever the resource transfer progress is updated.</p></li>
</ul>
</dd>
</dl>
@@ -1009,8 +1055,8 @@ the resource advertisement it will begin transferring.</p>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Resource.progress">
<span class="sig-name descname"><span class="pre">progress</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Resource.progress" title="Permalink to this definition"></a></dt>
<dt class="sig sig-object py" id="RNS.Resource.get_progress">
<span class="sig-name descname"><span class="pre">get_progress</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Resource.get_progress" 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 current progress of the resource transfer as a <em>float</em> between 0.0 and 1.0.</p>
@@ -1026,7 +1072,15 @@ the resource advertisement it will begin transferring.</p>
<dl class="py class">
<dt class="sig sig-object py" id="RNS.Transport">
<em class="property"><span class="pre">class</span> </em><span class="sig-prename descclassname"><span class="pre">RNS.</span></span><span class="sig-name descname"><span class="pre">Transport</span></span><a class="headerlink" href="#RNS.Transport" title="Permalink to this definition"></a></dt>
<dd><dl class="py method">
<dd><p>Through static methods of this class you can interact with the
Transport system of Reticulum.</p>
<dl class="py attribute">
<dt class="sig sig-object py" id="RNS.Transport.PATHFINDER_M">
<span class="sig-name descname"><span class="pre">PATHFINDER_M</span></span><em class="property"> <span class="pre">=</span> <span class="pre">128</span></em><a class="headerlink" href="#RNS.Transport.PATHFINDER_M" title="Permalink to this definition"></a></dt>
<dd><p>Maximum amount of hops that Reticulum will transport a packet.</p>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Transport.register_announce_handler">
<em class="property"><span class="pre">static</span> </em><span class="sig-name descname"><span class="pre">register_announce_handler</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">handler</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Transport.register_announce_handler" title="Permalink to this definition"></a></dt>
<dd><p>Registers an announce handler.</p>
@@ -1061,6 +1115,45 @@ the resource advertisement it will begin transferring.</p>
</dl>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Transport.hops_to">
<em class="property"><span class="pre">static</span> </em><span class="sig-name descname"><span class="pre">hops_to</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">destination_hash</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Transport.hops_to" title="Permalink to this definition"></a></dt>
<dd><dl class="field-list simple">
<dt class="field-odd">Parameters</dt>
<dd class="field-odd"><p><strong>destination_hash</strong> A destination hash as <em>bytes</em>.</p>
</dd>
<dt class="field-even">Returns</dt>
<dd class="field-even"><p>The number of hops to the specified destination, or <code class="docutils literal notranslate"><span class="pre">RNS.Transport.PATHFINDER_M</span></code> if the number of hops is unknown.</p>
</dd>
</dl>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Transport.next_hop">
<em class="property"><span class="pre">static</span> </em><span class="sig-name descname"><span class="pre">next_hop</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">destination_hash</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Transport.next_hop" title="Permalink to this definition"></a></dt>
<dd><dl class="field-list simple">
<dt class="field-odd">Parameters</dt>
<dd class="field-odd"><p><strong>destination_hash</strong> A destination hash as <em>bytes</em>.</p>
</dd>
<dt class="field-even">Returns</dt>
<dd class="field-even"><p>The destination hash as <em>bytes</em> for the next hop to the specified destination, or <em>None</em> if the next hop is unknown.</p>
</dd>
</dl>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Transport.next_hop_interface">
<em class="property"><span class="pre">static</span> </em><span class="sig-name descname"><span class="pre">next_hop_interface</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">destination_hash</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Transport.next_hop_interface" title="Permalink to this definition"></a></dt>
<dd><dl class="field-list simple">
<dt class="field-odd">Parameters</dt>
<dd class="field-odd"><p><strong>destination_hash</strong> A destination hash as <em>bytes</em>.</p>
</dd>
<dt class="field-even">Returns</dt>
<dd class="field-even"><p>The interface for the next hop to the specified destination, or <em>None</em> if the interface is unknown.</p>
</dd>
</dl>
</dd></dl>
<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><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Transport.request_path" title="Permalink to this definition"></a></dt>
@@ -1097,6 +1190,7 @@ will announce it.</p>
<li><a class="reference internal" href="#packet">Packet</a></li>
<li><a class="reference internal" href="#packet-receipt">Packet Receipt</a></li>
<li><a class="reference internal" href="#link">Link</a></li>
<li><a class="reference internal" href="#request-receipt">Request Receipt</a></li>
<li><a class="reference internal" href="#resource">Resource</a></li>
<li><a class="reference internal" href="#transport">Transport</a></li>
</ul>
@@ -1110,7 +1204,7 @@ will announce it.</p>
title="previous chapter">Understanding Reticulum</a></p>
<h4>Next topic</h4>
<p class="topless"><a href="examples.html"
title="next chapter">Examples</a></p>
title="next chapter">Code Examples</a></p>
<div role="note" aria-label="source link">
<h3>This Page</h3>
<ul class="this-page-menu">
@@ -1139,12 +1233,12 @@ will announce it.</p>
<a href="genindex.html" title="General Index"
>index</a></li>
<li class="right" >
<a href="examples.html" title="Examples"
<a href="examples.html" title="Code Examples"
>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.2.2 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.3 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.2.2 beta documentation</title>
<title>Search &#8212; Reticulum Network Stack 0.3.3 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.2.2 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.3 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.2.2 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.3 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
+32 -29
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.2.2 beta documentation</title>
<title>Understanding Reticulum &#8212; Reticulum Network Stack 0.3.3 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="API Reference" href="reference.html" />
<link rel="prev" title="Getting Started Fast" href="gettingstartedfast.html" />
<link rel="prev" title="Supported Interfaces" href="interfaces.html" />
</head><body>
<div class="related" role="navigation" aria-label="related navigation">
<h3>Navigation</h3>
@@ -29,9 +29,9 @@
<a href="reference.html" title="API Reference"
accesskey="N">next</a> |</li>
<li class="right" >
<a href="gettingstartedfast.html" title="Getting Started Fast"
<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.2.2 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.3 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">Understanding Reticulum</a></li>
</ul>
</div>
@@ -80,7 +80,7 @@ by using multiple hops).</p>
</div>
<div class="section" id="goals">
<span id="understanding-goals"></span><h2>Goals<a class="headerlink" href="#goals" title="Permalink to this headline"></a></h2>
<p>To be as widely usable and easy to implement as possible, the following goals have been used to
<p>To be as widely usable and easy to use as possible, the following goals have been used to
guide the design of Reticulum:</p>
<ul class="simple">
<li><dl class="simple">
@@ -100,12 +100,18 @@ it can be easily replicated.</p>
</li>
<li><dl class="simple">
<dt><strong>Very low bandwidth requirements</strong></dt><dd><p>Reticulum should be able to function reliably over links with a transmission capacity as low
as <em>1,000 bps</em>.</p>
as <em>500 bps</em>.</p>
</dd>
</dl>
</li>
<li><dl class="simple">
<dt><strong>Encryption by default</strong></dt><dd><p>Reticulum must use encryption by default where possible and applicable.</p>
<dt><strong>Encryption by default</strong></dt><dd><p>Reticulum must use strong encryption by default for all communication.</p>
</dd>
</dl>
</li>
<li><dl class="simple">
<dt><strong>Initiator Anonymity</strong></dt><dd><p>It must be possible to communicate over a Reticulum network without revealing any identifying
information about oneself.</p>
</dd>
</dl>
</li>
@@ -148,7 +154,7 @@ needs to be purchased.</p>
<p>Reticulum is a networking stack suited for high-latency, low-bandwidth links. Reticulum is at its
core a <em>message oriented</em> system. It is suited for both local point-to-point or point-to-multipoint
scenarios where alle nodes are within range of each other, as well as scenarios where packets need
to be transported over multiple hops to reach the recipient.</p>
to be transported over multiple hops in a complex network to reach the recipient.</p>
<p>Reticulum does away with the idea of addresses and ports known from IP, TCP and UDP. Instead
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
@@ -156,9 +162,9 @@ destinations it needs to send data to.</p>
<p>All destinations in Reticulum are represented internally as 10 bytes, 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>By default Reticulum encrypts all data using public-key cryptography. Any message sent to a
destination is encrypted with that destinations public key. Reticulum can also set up an encrypted
channel to a destination with <em>Perfect Forward Secrecy</em> and <em>Initiator Anonymity</em> using a elliptic
<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>.</p>
<p>Reticulum also offers symmetric key encryption for group-oriented communications, as well as
@@ -174,23 +180,23 @@ private IP networks.</p>
destinations. Reticulum uses three different basic destination types, and one special:</p>
<ul class="simple">
<li><dl class="simple">
<dt><strong>Single</strong></dt><dd><p>The <em>single</em> destination type defines a public-key encrypted destination. Any data sent to this
destination will be encrypted with the destinations public key, and will only be readable by
the creator of the destination.</p>
<dt><strong>Single</strong></dt><dd><p>The <em>single</em> destination type is always identified by a unique public key. Any data sent to this
destination will be encrypted using ephemeral keys derived from an ECDH key exchange, and will
only be readable by the creator of the destination, who holds the corresponding private key.</p>
</dd>
</dl>
</li>
<li><dl class="simple">
<dt><strong>Group</strong></dt><dd><p>The <em>group</em> destination type defines a symmetrically encrypted destination. Data sent to this
destination will be encrypted with a symmetric key, and will be readable by anyone in
possession of the key. The <em>group</em> destination can be used just as well by only two peers, as it
can by many.</p>
possession of the key.</p>
</dd>
</dl>
</li>
<li><dl class="simple">
<dt><strong>Plain</strong></dt><dd><p>A <em>plain</em> destination type is unencrypted, and suited for traffic that should be broadcast to a
number of users, or should be readable by anyone. Traffic to a <em>plain</em> destination is not encrypted.</p>
number of users, or should be readable by anyone. Traffic to a <em>plain</em> destination is not encrypted.
Generally, <em>plain</em> destinations can be used for broadcast information intended to be public.</p>
</dd>
</dl>
</li>
@@ -490,7 +496,7 @@ 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 240 bytes (more info in the
<p>The combined bandwidth cost of setting up a link is 3 packets totalling 237 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>
@@ -575,7 +581,7 @@ the transfer is needed.</p>
<p>This is the purpose of the Reticulum <a class="reference internal" href="reference.html#api-resource"><span class="std std-ref">Resource</span></a>. A <em>Resource</em> can automatically
handle the reliable transfer of an arbitrary amount of data over an established <a class="reference internal" href="reference.html#api-link"><span class="std std-ref">Link</span></a>.
Resources can auto-compress data, will handle breaking the data into individual packets, sequencing
the transfer and reassembling the data on the other end.</p>
the transfer, integrity verification and reassembling the data on the other end.</p>
<p><a class="reference internal" href="reference.html#api-resource"><span class="std std-ref">Resources</span></a> are programmatically very simple to use, and only requires a few lines
of codes to reliably transfer any amount of data. They can be used to transfer data stored in memory,
or stream data directly from files.</p>
@@ -654,8 +660,8 @@ treated more as a reference than as essential reading.</p>
<div class="section" id="node-types">
<h3>Node Types<a class="headerlink" href="#node-types" title="Permalink to this headline"></a></h3>
<p>Currently Reticulum defines two node types, the <em>Station</em> and the <em>Peer</em>. A node is a <em>station</em> if it fixed
in one place, and if it is intended to be kept online most of the time. Otherwise the node is a <em>peer</em>.
This distinction is made by the user configuring the node, and is used to determine what nodes on the
in one place, and if it is intended to be kept online most of the time. Otherwise the node is a <em>peer</em>.</p>
<p>This distinction is made by the user configuring the node, and is used to determine what nodes on the
network will help forward traffic, and what nodes rely on other nodes for connectivity.</p>
<p>If a node is a <em>Peer</em> it should be given the configuration directive <code class="docutils literal notranslate"><span class="pre">enable_transport</span> <span class="pre">=</span> <span class="pre">No</span></code>.</p>
<p>If it is a <em>Station</em>, it should be given the configuration directive <code class="docutils literal notranslate"><span class="pre">enable_transport</span> <span class="pre">=</span> <span class="pre">Yes</span></code>.</p>
@@ -665,9 +671,6 @@ network will help forward traffic, and what nodes rely on other nodes for connec
<p>Currently, Reticulum is completely priority-agnostic regarding general traffic. All traffic is handled
on a first-come, first-serve basis. Announce re-transmission are handled according to the re-transmission
times and priorities described earlier in this chapter.</p>
<p>It is possible that a prioritisation engine could be added to Reticulum in the future, but in
the light of Reticulums goal of equal access, doing so would need to be the subject of careful
investigation of the consequences first.</p>
</div>
<div class="section" id="binary-packet-format">
<span id="understanding-packetformat"></span><h3>Binary Packet Format<a class="headerlink" href="#binary-packet-format" title="Permalink to this headline"></a></h3>
@@ -764,7 +767,7 @@ proof 11
- Announce : 151 bytes
- Link Request : 77 bytes
- Link Proof : 77 bytes
- Link RTT packet : 86 bytes
- Link RTT packet : 83 bytes
- Link keepalive : 14 bytes
</pre></div>
</div>
@@ -815,8 +818,8 @@ proof 11
</ul>
<h4>Previous topic</h4>
<p class="topless"><a href="gettingstartedfast.html"
title="previous chapter">Getting Started Fast</a></p>
<p class="topless"><a href="interfaces.html"
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>
@@ -851,9 +854,9 @@ proof 11
<a href="reference.html" title="API Reference"
>next</a> |</li>
<li class="right" >
<a href="gettingstartedfast.html" title="Getting Started Fast"
<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.2.2 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.3 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">Understanding Reticulum</a></li>
</ul>
</div>
+342
View File
@@ -0,0 +1,342 @@
<!DOCTYPE html>
<html>
<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.3 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="Building Networks" href="networks.html" />
<link rel="prev" title="Getting Started Fast" href="gettingstartedfast.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="networks.html" title="Building Networks"
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.3 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">Using Reticulum on Your System</a></li>
</ul>
</div>
<div class="document">
<div class="documentwrapper">
<div class="bodywrapper">
<div class="body" role="main">
<div class="section" id="using-reticulum-on-your-system">
<span id="using-main"></span><h1>Using Reticulum on Your System<a class="headerlink" href="#using-reticulum-on-your-system" title="Permalink to this headline"></a></h1>
<p>Reticulum is not installed as a driver or kernel module, as one might expect
of a networking stack. Instead, Reticulum is distributed as a Python module.
This means that no special privileges are required to install or use it.
Any program or application that uses Reticulum will automatically load and
initialise Reticulum when it starts.</p>
<p>In many cases, this approach is sufficient. When any program needs to use
Reticulum, it is loaded, initialised, interfaces are brought up, and the
program can now communicate over Reticulum. If another program starts up
and also wants access to the same Reticulum network, the instance is simply
shared. This works for any number of programs running concurrently, and is
very easy to use, but depending on your use case, there are other options.</p>
<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>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
other programs, applications and services can utilise.</p>
<div class="section" id="the-rnsd-utility">
<h3>The rnsd Utility<a class="headerlink" href="#the-rnsd-utility" title="Permalink to this headline"></a></h3>
<p>To do so is very easy. Simply run the included <code class="docutils literal notranslate"><span class="pre">rnsd</span></code> command. When <code class="docutils literal notranslate"><span class="pre">rnsd</span></code>
is running, it will keep all configured interfaces open, handle transport if
it is enabled, and allow any other programs to immediately utilise the
Reticulum network it is configured for.</p>
<p>You can even run multiple instances of rnsd with different configurations on
the same system.</p>
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span># Install Reticulum
pip3 install rns
# Run rnsd
rnsd
</pre></div>
</div>
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>usage: rnsd [-h] [--config CONFIG] [-v] [-q] [--version]
Reticulum Network Stack Daemon
optional arguments:
-h, --help show this help message and exit
--config CONFIG path to alternative Reticulum config directory
-v, --verbose
-q, --quiet
--version show program&#39;s version number and exit
</pre></div>
</div>
<p>You can easily add <code class="docutils literal notranslate"><span class="pre">rnsd</span></code> as an always-on service by <a class="reference internal" href="#using-systemd"><span class="std std-ref">configuring a service</span></a>.</p>
</div>
<div class="section" id="the-rnstatus-utility">
<h3>The rnstatus Utility<a class="headerlink" href="#the-rnstatus-utility" title="Permalink to this headline"></a></h3>
<p>Using the <code class="docutils literal notranslate"><span class="pre">rnstatus</span></code> utility, you can view the status of configured Reticulum
interfaces, similar to the <code class="docutils literal notranslate"><span class="pre">ifconfig</span></code> program.</p>
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span># Run rnstatus
rnstatus
# Example output
Shared Instance[37428]
Status: Up
Connected applications: 1
RX: 1.13 KB
TX: 1.07 KB
UDPInterface[Default UDP Interface/0.0.0.0:4242]
Status: Up
RX: 1.01 KB
TX: 1.01 KB
TCPInterface[RNS Testnet Frankfurt/frankfurt.rns.unsigned.io:4965]
Status: Up
RX: 1.37 KB
TX: 9.02 KB
</pre></div>
</div>
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>usage: rnsd [-h] [--config CONFIG] [-v] [-q] [--version]
Reticulum Network Stack Daemon
optional arguments:
-h, --help show this help message and exit
--config CONFIG path to alternative Reticulum config directory
-v, --verbose
-q, --quiet
--version show program&#39;s version number and exit
</pre></div>
</div>
</div>
<div class="section" id="the-rnpath-utility">
<h3>The rnpath Utility<a class="headerlink" href="#the-rnpath-utility" title="Permalink to this headline"></a></h3>
<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
# Example output
Path found, destination &lt;eca6f4e4dc26ae329e61&gt; is 4 hops away via &lt;56b115c30cd386cad69c&gt; on TCPInterface[Testnet/frankfurt.rns.unsigned.io:4965]
</pre></div>
</div>
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>usage: rnpath.py [-h] [--config CONFIG] [--version] [-v] [destination]
Reticulum Path Discovery Utility
positional arguments:
destination hexadecimal hash of the destination
optional arguments:
-h, --help show this help message and exit
--config CONFIG path to alternative Reticulum config directory
--version show program&#39;s version number and exit
-v, --verbose
</pre></div>
</div>
</div>
<div class="section" id="the-rnprobe-utility">
<h3>The rnprobe Utility<a class="headerlink" href="#the-rnprobe-utility" title="Permalink to this headline"></a></h3>
<p>The <code class="docutils literal notranslate"><span class="pre">rnprobe</span></code> utility lets you probe a destination for connectivity, similar
to the <code class="docutils literal notranslate"><span class="pre">ping</span></code> program. Please note that probes will only be answered if the
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
# Example output
Sent 16 byte probe to &lt;9382f334de63217a4278&gt;
Valid reply received from &lt;9382f334de63217a4278&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]
Reticulum Probe Utility
positional arguments:
full_name full destination name in dotted notation
destination_hash hexadecimal hash of the destination
optional arguments:
-h, --help show this help message and exit
--config CONFIG path to alternative Reticulum config directory
--version show program&#39;s version number and exit
-v, --verbose
</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>
<p>If you are setting up a system for permanent use with Reticulum, there is a
few system configuration changes that can make this easier to administrate.
These changes will be detailed here.</p>
<div class="section" id="fixed-serial-port-names">
<h3>Fixed Serial Port Names<a class="headerlink" href="#fixed-serial-port-names" title="Permalink to this headline"></a></h3>
<p>On a Reticulum node with several serial port based interfaces, it can be
beneficial to use the fixed name device nodes for the serial ports, instead
of the dynamically allocated shorthands such as <code class="docutils literal notranslate"><span class="pre">/dev/ttyUSB0</span></code>. Under most
Debian-based distributions, including Ubuntu and Raspberry Pi OS, these nodes
can be found under <code class="docutils literal notranslate"><span class="pre">/dev/serial/by-id</span></code>.</p>
<p>You can use such a device path directly in place of the numbered shorthands.
Here is an example of a packet radio TNC configured as such:</p>
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>[[Packet Radio KISS Interface]]
type = KISSInterface
interface_enabled = True
outgoing = true
port = /dev/serial/by-id/usb-FTDI_FT230X_Basic_UART_43891CKM-if00-port0
speed = 115200
databits = 8
parity = none
stopbits = 1
preamble = 150
txtail = 10
persistence = 200
slottime = 20
</pre></div>
</div>
<p>Using this methodology avoids potential naming mix-ups where physical devices
might be plugged and unplugged in different orders, or when node name
assignment varies from one boot to another.</p>
</div>
<div class="section" id="reticulum-as-a-system-service">
<span id="using-systemd"></span><h3>Reticulum as a System Service<a class="headerlink" href="#reticulum-as-a-system-service" title="Permalink to this headline"></a></h3>
<p>Instead of starting Reticulum manually, you can install <code class="docutils literal notranslate"><span class="pre">rnsd</span></code> as a system
service and have it start automatically at boot.</p>
<p>If you installed Reticulum with <code class="docutils literal notranslate"><span class="pre">pip</span></code>, the <code class="docutils literal notranslate"><span class="pre">rnsd</span></code> program will most likely
be located in a user-local installation path only, which means <code class="docutils literal notranslate"><span class="pre">systemd</span></code> will not
be able to execute it. In this case, you can simply symlink the <code class="docutils literal notranslate"><span class="pre">rnsd</span></code> program
into a directory that is in systemds path:</p>
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>sudo ln -s $(which rnsd) /usr/local/bin/
</pre></div>
</div>
<p>You can then create the service file <code class="docutils literal notranslate"><span class="pre">/etc/systemd/system/rnsd.service</span></code> with the
following content:</p>
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>[Unit]
Description=Reticulum Network Stack Daemon
After=multi-user.target
[Service]
# If you run Reticulum on WiFi devices,
# or other devices that need some extra
# time to initialise, you might want to
# add a short delay before Reticulum is
# started by systemd:
# ExecStartPre=/bin/sleep 10
Type=simple
Restart=always
RestartSec=3
User=USERNAMEHERE
ExecStart=rnsd --service
[Install]
WantedBy=multi-user.target
</pre></div>
</div>
<p>Be sure to replace <code class="docutils literal notranslate"><span class="pre">USERNAMEHERE</span></code> with the user you want to run <code class="docutils literal notranslate"><span class="pre">rnsd</span></code> as.</p>
<p>To manually start <code class="docutils literal notranslate"><span class="pre">rnsd</span></code> run:</p>
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>sudo systemctl start rnsd
</pre></div>
</div>
<p>If you want to automatically start <code class="docutils literal notranslate"><span class="pre">rnsd</span></code> at boot, run:</p>
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>sudo systemctl enable rnsd
</pre></div>
</div>
</div>
</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="#">Using Reticulum on Your System</a><ul>
<li><a class="reference internal" href="#included-utility-programs">Included Utility Programs</a><ul>
<li><a class="reference internal" href="#the-rnsd-utility">The rnsd Utility</a></li>
<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>
</ul>
</li>
<li><a class="reference internal" href="#improving-system-configuration">Improving System Configuration</a><ul>
<li><a class="reference internal" href="#fixed-serial-port-names">Fixed Serial Port Names</a></li>
<li><a class="reference internal" href="#reticulum-as-a-system-service">Reticulum as a System Service</a></li>
</ul>
</li>
</ul>
</li>
</ul>
<h4>Previous topic</h4>
<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>
<div role="note" aria-label="source link">
<h3>This Page</h3>
<ul class="this-page-menu">
<li><a href="_sources/using.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="networks.html" title="Building Networks"
>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.3 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">Using Reticulum on Your System</a></li>
</ul>
</div>
<div class="footer" role="contentinfo">
&#169; Copyright 2021, Mark Qvist.
Created using <a href="https://www.sphinx-doc.org/">Sphinx</a> 4.0.1.
</div>
</body>
</html>
+20 -18
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.2.2 beta documentation</title>
<title>What is Reticulum? &#8212; Reticulum Network Stack 0.3.3 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.2.2 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.3 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">What is Reticulum?</a></li>
</ul>
</div>
@@ -43,31 +43,28 @@
<div class="section" id="what-is-reticulum">
<h1>What is Reticulum?<a class="headerlink" href="#what-is-reticulum" title="Permalink to this headline"></a></h1>
<p>Reticulum is a cryptography-based networking stack for wide-area networks built on readily available hardware, and can operate even with very high latency and extremely low bandwidth.</p>
<p>Reticulum is a cryptography-based networking stack for wide-area networks built on readily available hardware, that can operate even with very high latency and extremely low bandwidth.</p>
<p>Reticulum allows you to build very wide-area networks with off-the-shelf tools, and offers end-to-end encryption, autoconfiguring cryptographically backed multi-hop transport, efficient addressing, unforgeable packet acknowledgements and more.</p>
<p>Reticulum is a complete networking stack, and does not use IP or higher layers, although it is easy to utilise 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 built directly on cryptographic principles, allowing resilience and stable functionality in open and trustless networks.</p>
<p>No kernel modules or drivers are required. Reticulum runs completely in userland, and can run on practically any system that runs Python 3.</p>
<p>Reticulum is a complete networking stack, and does not need IP or higher layers, although it is easy to utilise 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 built directly on cryptographic principles, allowing resilience and stable functionality in open and trustless networks.</p>
<p>No kernel modules or drivers are required. Reticulum runs completely in userland, and can run on practically any system that runs Python 3. Reticulum runs well even on small single-board computers like the Pi Zero.</p>
<div class="section" id="current-status">
<h2>Current Status<a class="headerlink" href="#current-status" title="Permalink to this headline"></a></h2>
<p>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.</p>
</div>
<div class="section" id="caveat-emptor">
<h2>Caveat Emptor<a class="headerlink" href="#caveat-emptor" title="Permalink to this headline"></a></h2>
<p>Reticulum is an experimental networking stack, 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. To be considered even remotely secure, Reticulum needs a very thourough security review by independt cryptographers and security researchers. If you want to help out, or help sponsor an audit, please do get in touch.</p>
</div>
<div class="section" id="what-does-reticulum-offer">
<h2>What does Reticulum Offer?<a class="headerlink" href="#what-does-reticulum-offer" title="Permalink to this headline"></a></h2>
<ul class="simple">
<li><p>Coordination-less globally unique adressing and identification</p></li>
<li><p>Fully self-configuring multi-hop routing</p></li>
<li><p>Complete initiator anonymity, communicate without revealing your identity</p></li>
<li><p>Asymmetric X25519 encryption and Ed25519 signatures as a basis for all communication</p></li>
<li><p>Forward Secrecy with ephemereal Elliptic Curve Diffie-Hellman keys on Curve25519</p></li>
<li><p>Reticulum uses the <a class="reference external" href="https://github.com/fernet/spec/blob/master/Spec.md">Fernet</a> specification for encryption</p>
<li><p>Reticulum uses the <a class="reference external" href="https://github.com/fernet/spec/blob/master/Spec.md">Fernet</a> specification for on-the-wire / over-the-air encryption</p>
<ul>
<li><p>All keys are ephemeral and derived from an ECDH key exchange on Curve25519</p></li>
<li><p>AES-128 in CBC mode with PKCS7 padding</p></li>
<li><p>HMAC using SHA256 for authentication</p></li>
<li><p>IVs are generated through os.urandom()</p></li>
<li><p>Keys are ephemeral and derived from an ECDH key exchange on Curve25519</p></li>
</ul>
</li>
<li><p>Unforgeable packet delivery confirmations</p></li>
@@ -82,7 +79,7 @@
</li>
<li><p>Efficient link establishment</p>
<ul>
<li><p>Total bandwidth cost of setting up a link is only 3 packets, totalling 240 bytes</p></li>
<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>
</ul>
</li>
@@ -91,7 +88,7 @@
<div class="section" id="where-can-reticulum-be-used">
<h2>Where can Reticulum be Used?<a class="headerlink" href="#where-can-reticulum-be-used" title="Permalink to this headline"></a></h2>
<p>Over practically any medium that can support at least a half-duplex channel
with 1.000 bits per second throughput, and an MTU of 500 bytes. Data radios,
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.</p>
@@ -110,8 +107,8 @@ 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.</p>
</div>
<div class="section" id="supported-interface-types-and-devices">
<h2>Supported Interface Types and Devices<a class="headerlink" href="#supported-interface-types-and-devices" title="Permalink to this headline"></a></h2>
<div class="section" id="interface-types-and-devices">
<h2>Interface Types and Devices<a class="headerlink" href="#interface-types-and-devices" title="Permalink to this headline"></a></h2>
<p>Reticulum implements a range of generalised interface types that covers most of the communications hardware that Reticulum can run over. If your hardware is not supported, its relatively simple to implement an interface class. Currently, the following interfaces are supported:</p>
<ul class="simple">
<li><p>Any ethernet device</p></li>
@@ -121,6 +118,11 @@ network, and vice versa.</p>
<li><p>TCP over IP networks</p></li>
<li><p>UDP over IP networks</p></li>
</ul>
<p>For a full list and more details, see the <a class="reference internal" href="interfaces.html#interfaces-main"><span class="std std-ref">Supported Interfaces</span></a> chapter.</p>
</div>
<div class="section" id="caveat-emptor">
<h2>Caveat Emptor<a class="headerlink" href="#caveat-emptor" title="Permalink to this headline"></a></h2>
<p>Reticulum is an experimental networking stack, 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. To be considered secure, Reticulum needs a thourough security review by independt cryptographers and security researchers. If you want to help out, or help sponsor an audit, please do get in touch.</p>
</div>
</div>
@@ -135,10 +137,10 @@ network, and vice versa.</p>
<ul>
<li><a class="reference internal" href="#">What is Reticulum?</a><ul>
<li><a class="reference internal" href="#current-status">Current Status</a></li>
<li><a class="reference internal" href="#caveat-emptor">Caveat Emptor</a></li>
<li><a class="reference internal" href="#what-does-reticulum-offer">What does Reticulum Offer?</a></li>
<li><a class="reference internal" href="#where-can-reticulum-be-used">Where can Reticulum be Used?</a></li>
<li><a class="reference internal" href="#supported-interface-types-and-devices">Supported Interface Types and Devices</a></li>
<li><a class="reference internal" href="#interface-types-and-devices">Interface Types and Devices</a></li>
<li><a class="reference internal" href="#caveat-emptor">Caveat Emptor</a></li>
</ul>
</li>
</ul>
@@ -182,7 +184,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.2.2 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.3 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">What is Reticulum?</a></li>
</ul>
</div>
+2 -2
View File
@@ -22,7 +22,7 @@ copyright = '2021, Mark Qvist'
author = 'Mark Qvist'
# The full version, including alpha/beta/rc tags
release = '0.2.2 beta'
release = '0.3.3 beta'
# -- General configuration ---------------------------------------------------
@@ -65,4 +65,4 @@ html_static_path = ['_static']
# return False
# def setup(app):
# app.connect('autodoc-skip-member', check_skip_member)
# app.connect('autodoc-skip-member', check_skip_member)
+4 -3
View File
@@ -1,8 +1,9 @@
.. _examples-main:
********
Examples
********
*************
Code Examples
*************
A number of examples are included in the source distribution of Reticulum.
You can use these examples to learn how to write your own programs.
+127 -6
View File
@@ -6,19 +6,71 @@ The best way to get started with the Reticulum Network Stack depends on what
you want to do. This guide will outline sensible starting paths for different
scenarios.
Try Using a Reticulum-based Program
=============================================
If you simply want to try using a program built with Reticulum, you can take
a look at `Nomad Network <https://github.com/markqvist/nomadnet>`_, which
provides a basic encrypted communications suite built completely on Reticulum.
provides a complete encrypted communications suite built with Reticulum.
.. image:: screenshots/nomadnet3.png
:target: _images/nomadnet3.png
.. image:: screenshots/nomadnet_3.png
:target: _images/nomadnet_3.png
`Nomad Network <https://github.com/markqvist/nomadnet>`_ is a user-facing client
in the development for the messaging and information-sharing protocol
for the messaging and information-sharing protocol
`LXMF <https://github.com/markqvist/lxmf>`_, another project built with Reticulum.
You can install Nomad Network via pip:
.. code::
# Install ...
pip3 install nomadnet
# ... and run
nomadnet
**Please Note**: If this is the very first time you use pip to install a program
on your system, you might need to reboot your system for your program to become
available. If you get a "command not found" error or similar when running the
program, reboot your system and try again.
Using the Included Utilities
=============================================
Reticulum comes with a range of included utilities that make it easier to
manage your network, check connectivity and make Reticulum available to other
programs on your system.
You can use ``rnsd`` to run Reticulum as a background or foreground service,
and the ``rnstatus``, ``rnpath`` and ``rnprobe`` utilities to view and query
network status and connectivity.
To learn more about these utility programs, have a look at the
:ref:`Using Reticulum on Your System<using-main>` chapter of this manual.
Creating a Network With Reticulum
=============================================
To create a network, you will need to specify one or more *interfaces* for
Reticulum to use. This is done in the Reticulum configuration file, which by
default is located at ``~/.reticulum/config``. You can edit this file by hand,
or use the interactive ``rnsconfig`` utility.
When Reticulum is started for the first time, it will create a default
configuration file, with one active interface. This default interface uses
your existing ethernet network (if there is one), and only allows you to
communicate with other Reticulum peers within your local broadcast domain.
To communicate further, you will have to add one or more interfaces. The default
configuration includes a number of examples, ranging from using TCP over the
internet, to LoRa and Packet Radio interfaces.
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>`
and :ref:`Interfaces<interfaces-main>` chapters of this manual.
Develop a Program with Reticulum
===========================================
If you want to develop programs that use Reticulum, the easiest way to get
@@ -32,6 +84,13 @@ The above command will install Reticulum and dependencies, and you will be
ready to import and use RNS in your own programs. The next step will most
likely be to look at some :ref:`Example Programs<examples-main>`.
For extended functionality, you can install optional dependencies:
.. code::
pip3 install pyserial netifaces
Further information can be found in the :ref:`API Reference<api-main>`.
@@ -44,7 +103,7 @@ don't use pip, but try this recipe:
.. code::
# Install dependencies
pip3 install cryptography pyserial
pip3 install cryptography pyserial netifaces
# Clone repository
git clone https://github.com/markqvist/Reticulum.git
@@ -75,4 +134,66 @@ don't use pip, but try this recipe:
python3 Examples/Filetransfer.py -h
When you have experimented with the basic examples, it's time to go read the
:ref:`Understanding Reticulum<understanding-main>` chapter.
:ref:`Understanding Reticulum<understanding-main>` chapter.
Reticulum on ARM64
==============================================
On some architectures, including ARM64, not all dependencies have precompiled
binaries. On such systems, you will need to install ``python3-dev`` before
installing Reticulum or programs that depend on Reticulum.
.. code::
# Install Python and development packages
sudo apt update
sudo apt install python3 python3-pip python3-dev
# Install Reticulum
python3 -m pip install rns
Reticulum on Android
==============================================
Reticulum can be used on Android in different ways. The easiest way to get
started is using the `Termux app <https://termux.com/>`_, at the time of writing
available on `F-droid <https://f-droid.org>`_.
Termux is a terminal emulator and Linux environment for Android based devices,
which includes the ability to use many different programs and libraries,
including Reticulum.
Since the Python cryptography.io module does not offer pre-built wheels for
Android, the standard one-line install of Reticulum does not work on Android,
and a few extra commands are required.
From within Termux, execute the following:
.. code::
# First, make sure indexes and packages are up to date.
pkg update
pkg upgrade
# Then install dependencies for the cryptography library.
pkg install python build-essential openssl libffi rust
# Make sure pip is up to date, and install the wheel module.
pip3 install wheel pip --upgrade
# To allow the installer to build the cryptography module,
# we need to let it know what platform we are compiling for:
export CARGO_BUILD_TARGET="aarch64-linux-android"
# Start the install process for the cryptography module.
# Depending on your device, this can take several minutes,
# since the module must be compiled locally on your device.
pip3 install cryptography
# If the above installation succeeds, you can now install
# Reticulum and any related software
pip3 install rns
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.
+5 -2
View File
@@ -2,14 +2,17 @@
Reticulum Network Stack Manual
******************************
This manual aims to provide you with all the information you need to
understand Reticulum, develop programs using it, or to participate in
the development of Reticulum itself.
understand Reticulum, build networks or develop programs using it, or
to participate in the development of Reticulum itself.
.. toctree::
:maxdepth: 3
whatis
gettingstartedfast
using
networks
interfaces
understanding
reference
examples
+549
View File
@@ -0,0 +1,549 @@
.. _interfaces-main:
********************
Supported Interfaces
********************
Reticulum supports using many kinds of devices as networking interfaces, and
allows you to mix and match them in any way you choose. The number of distinct
network topologies you can create with Reticulum is more or less endless, but
common to them all is that you will need to define one or more *interfaces*
for Reticulum to use.
The following sections describe the interfaces currently available in Reticulum,
and gives example configurations for the respective interface types.
For a high-level overview of how networks can be formed over different interface
types, have a look at the :ref:`Building Networks<networks-main>` chapter of this
manual.
.. _interfaces-auto:
Auto Interface
==============
The Auto Interface enables communication with other discoverable Reticulum
nodes over autoconfigured IPv6 and UDP. It does not need any functional IP
infrastructure like routers or DHCP servers, but will require at least some
sort of switching medium between peers (a wired switch, a hub, a WiFi access
point or similar), and that link-local IPv6 is enabled in your operating
system, which should be enabled by default in almost all OSes.
.. code::
# This example demonstrates a TCP server interface.
# It will listen for incoming connections on the
# specified IP address and port number.
[[Default Interface]]
type = AutoInterface
interface_enabled = True
# You can create multiple isolated Reticulum
# networks on the same physical LAN by
# specifying different Group IDs.
group_id = reticulum
# You can also select specifically which
# kernel networking devices to use.
devices = wlan0,eth1
# Or let AutoInterface use all suitable
# devices except for a list of ignored ones.
ignored_devices = tun0,eth0
If you are connected to the Internet with IPv6, and your provider will route
IPv6 multicast, you can potentially configure the Auto Interface to globally
autodiscover other Reticulum nodes within your selected Group ID. You can specify
the discovery scope by setting it to one of ``link``, ``admin``, ``site``,
``organisation`` or ``global``.
.. code::
[[Default Interface]]
type = AutoInterface
interface_enabled = True
# Configure global discovery
group_id = custom_network_name
discovery_scope = global
# Other configuration options
discovery_port = 48555
data_port = 49555
.. _interfaces-i2p:
I2P Interface
=============
The I2P interface lets you connect Reticulum instances over the
`Invisible Internet Protocol <https://i2pd.website>`_. This can be
especially useful in cases where you want to host a globally reachable
Reticulum instance, but do not have access to any public IP addresses,
have a frequently changing IP address, or have firewalls blocking
inbound traffic.
Using the I2P interface, you will get a globally reachable, portable
and persistent I2P address that your Reticulum instance can be reached
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>`_.`
When an I2P router is running on your system, you can simply add
an I2P interface to reticulum:
.. code::
[[I2P]]
type = I2PInterface
interface_enabled = yes
connectable = yes
On the first start, Reticulum will generate a new I2P address for the
interface and start listening for inbound traffic on it. This can take
a while the first time, especially if your I2P router was also just
started, and is not yet well-connected to the I2P network. When ready,
you should see I2P base32 address printed to your log file. You can
also inspect the status of the interface using the ``rnstatus`` utility.
To connect to other Reticulum instances over I2P, just add a comma-separated
list of I2P base32 addresses to the ``peers`` option of the interface:
.. code::
[[I2P]]
type = I2PInterface
interface_enabled = yes
connectable = yes
peers = 5urvjicpzi7q3ybztsef4i5ow2aq4soktfj7zedz53s47r54jnqq.b32.i2p
It can take anywhere from a few seconds to a few minutes to establish
I2P connections to the desired peers, so Reticulum handles the process
in the background, and will output relevant events to the log.
**Please Note!** While the I2P interface is the simplest way to use
Reticulum over I2P, it is also possible to tunnel the TCP server and
client interfaces over I2P manually. This can be useful in situations
where more control is needed, but requires manual tunnel setup through
the I2P daemon configuration.
It is important to note that the two methods are *interchangably compatible*.
You can use the I2PInterface to connect to a TCPServerInterface that
was manually tunneled over I2P, for example. This offers a high degree
of flexibility in network setup, while retaining ease of use in simpler
use-cases.
.. _interfaces-tcps:
TCP Server Interface
====================
The TCP Server interface is suitable for allowing other peers to connect over
the Internet or private IP networks. When a TCP server interface has been
configured, other Reticulum peers can connect to it with a TCP Client interface.
.. code::
# This example demonstrates a TCP server interface.
# It will listen for incoming connections on the
# specified IP address and port number.
[[TCP Server Interface]]
type = TCPServerInterface
interface_enabled = True
# This configuration will listen on all IP
# interfaces on port 4242
listen_ip = 0.0.0.0
listen_port = 4242
# Alternatively you can bind to a specific IP
# listen_ip = 10.0.0.88
# listen_port = 4242
# Or a specific network device
# device = eth0
# port = 4242
**Please Note!** The TCP interfaces support tunneling over I2P, but to do so reliably,
you must use the i2p_tunneled option:
.. code::
[[TCP Server on I2P]]
type = TCPServerInterface
interface_enabled = yes
listen_ip = 127.0.0.1
listen_port = 5001
i2p_tunneled = yes
.. _interfaces-tcpc:
TCP Client Interface
====================
To connect to a TCP server interface, you would naturally use the TCP client
interface. Many TCP Client interfaces from different peers can connect to the
same TCP Server interface at the same time.
.. code::
# Here's an example of a TCP Client interface. The
# target_host can either be an IP address or a hostname.
[[TCP Client Interface]]
type = TCPClientInterface
interface_enabled = True
target_host = 127.0.0.1
target_port = 4242
It is also possible to use this interface type to connect via other programs
or hardware devices that expose a KISS interface on a TCP port, for example
software-based soundmodems. To do this, use the ``kiss_framing`` option:
.. code::
# Here's an example of a TCP Client interface that connects
# to a software TNC soundmodem on a KISS over TCP port.
[[TCP KISS Interface]]
type = TCPClientInterface
interface_enabled = True
kiss_framing = True
target_host = 127.0.0.1
target_port = 8001
**Caution!** Only use the KISS framing option when connecting to external devices
and programs like soundmodems and similar over TCP. When using the
``TCPClientInterface`` in conjunction with the ``TCPServerInterface`` you should
never enable ``kiss_framing``, since this will disable internal reliability and
recovery mechanisms that greatly improves performance over unreliable and
intermittent TCP links.
**Please Note!** The TCP interfaces support tunneling over I2P, but to do so reliably,
you must use the i2p_tunneled option:
.. code::
[[TCP Client over I2P]]
type = TCPClientInterface
interface_enabled = yes
target_host = 127.0.0.1
target_port = 5001
i2p_tunneled = yes
.. _interfaces-udp:
UDP Interface
=============
A UDP interface can be useful for communicating over IP networks, both
private and the internet. It can also allow broadcast communication
over IP networks, so it can provide an easy way to enable connectivity
with all other peers on a local area network.
*Please Note!* Using broadcast UDP traffic has performance implications,
especially on WiFi. If your goal is simply to enable easy communication
with all peers in your local ethernet broadcast domain, the
:ref:`Auto Interface<interfaces-auto>` performs better, and is just as
easy 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
# local Reticulum peers over UDP.
[[Default UDP Interface]]
type = UDPInterface
interface_enabled = True
listen_ip = 0.0.0.0
listen_port = 4242
forward_ip = 255.255.255.255
forward_port = 4242
# The above configuration will allow communication
# within the local broadcast domains of all local
# IP interfaces.
# Instead of specifying listen_ip, listen_port,
# forward_ip and forward_port, you can also bind
# to a specific network device like below.
# device = eth0
# port = 4242
# Assuming the eth0 device has the address
# 10.55.0.72/24, the above configuration would
# be equivalent to the following manual setup.
# Note that we are both listening and forwarding to
# the broadcast address of the network segments.
# listen_ip = 10.55.0.255
# listen_port = 4242
# forward_ip = 10.55.0.255
# forward_port = 4242
# You can of course also communicate only with
# a single IP address
# listen_ip = 10.55.0.15
# listen_port = 4242
# forward_ip = 10.55.0.16
# forward_port = 4242
.. _interfaces-rnode:
RNode LoRa Interface
====================
To use Reticulum over LoRa, the `RNode <https://unsigned.io/rnode/>`_ interface
can be used, and offers full control over LoRa parameters.
.. code::
# Here's an example of how to add a LoRa interface
# using the RNode LoRa transceiver.
[[RNode LoRa Interface]]
type = RNodeInterface
# Enable interface if you want use it!
interface_enabled = True
# Serial port for the device
port = /dev/ttyUSB0
# Set frequency to 867.2 MHz
frequency = 867200000
# Set LoRa bandwidth to 125 KHz
bandwidth = 125000
# Set TX power to 7 dBm (5 mW)
txpower = 7
# Select spreading factor 8. Valid
# range is 7 through 12, with 7
# being the fastest and 12 having
# the longest range.
spreadingfactor = 8
# Select coding rate 5. Valid range
# is 5 throough 8, with 5 being the
# fastest, and 8 the longest range.
codingrate = 5
# You can configure the RNode to send
# out identification on the channel with
# a set interval by configuring the
# following two parameters.
# id_callsign = MYCALL-0
# id_interval = 600
# For certain homebrew RNode interfaces
# with low amounts of RAM, using packet
# flow control can be useful. By default
# it is disabled.
flow_control = False
.. _interfaces-serial:
Serial Interface
================
Reticulum can be used over serial ports directly, or over any device with a
serial port, that will transparently pass data. Useful for communicating
directly over a wire-pair, or for using devices such as data radios and lasers.
.. code::
[[Serial Interface]]
type = SerialInterface
interface_enabled = True
# Serial port for the device
port = /dev/ttyUSB0
# Set the serial baud-rate and other
# configuration parameters.
speed = 115200
databits = 8
parity = none
stopbits = 1
.. _interfaces-kiss:
KISS Interface
==============
With the KISS interface, you can use Reticulum over a variety of packet
radio modems and TNCs, including `OpenModem <https://unsigned.io/openmodem/>`_.
KISS interfaces can also be configured to periodically send out beacons
for station identification purposes.
.. code::
[[Packet Radio KISS Interface]]
type = KISSInterface
interface_enabled = True
# Serial port for the device
port = /dev/ttyUSB1
# Set the serial baud-rate and other
# configuration parameters.
speed = 115200
databits = 8
parity = none
stopbits = 1
# Set the modem preamble.
preamble = 150
# Set the modem TX tail.
txtail = 10
# Configure CDMA parameters. These
# settings are reasonable defaults.
persistence = 200
slottime = 20
# You can configure the interface to send
# out identification on the channel with
# a set interval by configuring the
# following two parameters. The KISS
# interface will only ID if the set
# interval has elapsed since it's last
# actual transmission. The interval is
# configured in seconds.
# This option is commented out and not
# used by default.
# id_callsign = MYCALL-0
# id_interval = 600
# Whether to use KISS flow-control.
# This is useful for modems that have
# a small internal packet buffer, but
# support packet flow control instead.
flow_control = false
.. _interfaces-ax25:
AX.25 KISS Interface
====================
If you're using Reticulum on amateur radio spectrum, you might want to
use the AX.25 KISS interface. This way, Reticulum will automatically
encapsulate it's traffic in AX.25 and also identify your stations
transmissions with your callsign and SSID.
Only do this if you really need to! Reticulum doesn't need the AX.25
layer for anything, and it incurs extra overhead on every packet to
encapsulate in AX.25.
A more efficient way is to use the plain KISS interface with the
beaconing functionality described above.
.. code::
[[Packet Radio AX.25 KISS Interface]]
type = AX25KISSInterface
# Set the station callsign and SSID
callsign = NO1CLL
ssid = 0
# Enable interface if you want use it!
interface_enabled = True
# Serial port for the device
port = /dev/ttyUSB2
# Set the serial baud-rate and other
# configuration parameters.
speed = 115200
databits = 8
parity = none
stopbits = 1
# Set the modem preamble. A 150ms
# preamble should be a reasonable
# default, but may need to be
# increased for radios with slow-
# opening squelch and long TX/RX
# turnaround
preamble = 150
# Set the modem TX tail. In most
# cases this should be kept as low
# as possible to not waste airtime.
txtail = 10
# Configure CDMA parameters. These
# settings are reasonable defaults.
persistence = 200
slottime = 20
# Whether to use KISS flow-control.
# This is useful for modems with a
# small internal packet buffer.
flow_control = false
.. _interfaces-options:
Common Interface Options
========================
A number of general options can be used to control various
aspects of interface behaviour.
The ``interface_enabled`` option tells Reticulum whether or not
to bring up the interface. Defaults to ``False``. For any
interface to be brought up, the ``interface_enabled`` option
must be set to ``True`` or ``Yes``.
The ``outgoing`` option sets whether an interface is allowed
to transmit. Defaults to ``True``. If set to ``False`` the
interface will only receive data, and never transmit.
The ``interface_mode`` option allows selecting the high-level
behaviour of the interface from a number of options.
- The default value is ``full``. In this mode, all discovery,
meshing and transpor functionality is available.
- 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 are mostly 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.
+150
View File
@@ -0,0 +1,150 @@
.. _networks-main:
*****************
Building Networks
*****************
This chapter will provide you with the knowledge needed to build networks with
Reticulum, which can often be easier than using traditional stacks, since you
don't have to worry about coordinating addresses, subnets and routing for an
entire network that you might not know how will evolve in the future. With
Reticulum, you can simply add more segments to your network when it becomes
necesarry, and Reticulum will handle the convergence of the entire network
automatically.
Concepts & Overview
--------------------
There are important points that need to be kept in mind when building networks
with Reticulum:
* | In a Reticulum network, any node can autonomously generate as many adresses
(called *destinations* in Reticulum terminology) as it needs, which become
globally reachable to the rest of the network. There is no central point of
control over the adress space.
* | Reticulum was designed to handle both very small, and very large networks.
While the adress space can support billions of endpoints, Reticulum is
also very useful when just a few devices needs to communicate.
* | Reticulum provides sender/initiator anonymity by default. There is no way
to filter traffic or discriminate it based on the source of the traffic.
* | All traffic is encrypted using ephemeral keys generated by an Elliptic Curve
Diffie-Hellman key exchange on Curve25519. There is no way to inspect traffic
contents, and no way to prioritise or throttle certain kinds of traffic.
All transport and routing layers are thus completely agnostic to traffic type,
and will pass all traffic equally.
* | Reticulum can function both with and without infrastructure. When *transport
nodes* are available, they can route traffic over multiple hops for other
nodes, and will function as a distributed cryptographic keystore. When there
is no transport nodes available, all nodes that are within communication range
can still communicate.
* | Every node can become a transport node, simply by enabling it in it's
configuration, but there is no need for every node on the network to be a
transport node. Letting every node be a transport node will in most cases
degrade the performance and reliability of the network.
In general terms, if a node is stationary, well-connected and kept running
most of the time, it is a good candidate to be a transport node. For optimal
performance, a network should contain the amount of transport nodes that
provides connectivity to the intended area / topography, and not many more
than that.
Reticulum allows you to mix very different kinds of networking mediums into a
unified mesh, or to keep everything within one medium. You could build a "virtual
network" running entirely over the Internet, where all nodes communicate over TCP
and UDP "channels". You could also build such a network using MQTT or ZeroMQ as
the underlying carrier for Reticulum.
However, most real-world networks will probably involve either some form of
wireless or direct hardline communications. To allow Reticulum to communicate
over any type of medium, you must specify it in the configuration file, by default
located at ``~/.reticulum/config``. See the :ref:`Supported Interfaces<interfaces-main>`
chapter of this manual for interface configuration examples.
Any number of interfaces can be configured, and Reticulum will automatically
decide which are suitable to use in any given situation, depending on where
traffic needs to flow.
Example Scenarios
-----------------
This section illustrates a few example scenarios, and how they would, in general
terms, be planned, implemented and configured.
Interconnected LoRa Sites
=========================
An organisation wants to provide communication and information services to it's
members, which are located mainly in three separate areas. Three suitable hill-top
locations are found, where the organisation can install equipment: Site A, B and C.
Since the amount of data that needs to be exchanged between users is mainly text-
based, the bandwidth requirements are low, and LoRa radios are chosen to connect
users to the network.
Due to the hill-top locations found, there is radio line-of-sight between site A
and B, and also between site B and C. Because of this, the organisation does not
need to use the Internet to interconnect the sites, but purchases four Point-to-Point
WiFi based radios for interconnecting the sites.
At each site, a Raspberry Pi is installed to function as a gateway. A LoRa radio
is connected to the Pi with a USB cable, and the WiFi radio is connected to the
ethernet port of the Pi. At site B, two WiFi radios are needed to be able to reach
both site A and site C, so an extra ethernet adapter is connected to the Pi in
this location.
Once the hardware has been installed, Reticulum is installed on all the Pis, and at
site A and C, one interface is added for the LoRa radio, as well as one for the WiFi
radio. At site B, an interface for the LoRa radio, and one interface for each WiFi
radio is added to the Reticulum configuration file. The transport node option is
enabled in the configuration of all three gateways.
The network is now operational, and ready to serve users across all three areas.
The organisation prepares a LoRa radio that is supplied to the end users, along
with a Reticulum configuration file, that contains the right parameters for
communicating with the LoRa radios installed at the gateway sites.
Once users connect to the network, anyone will be able to communicate with anyone
else across all three sites.
Bridging Over the Internet
==========================
As the organisation grows, several new communities form in places too far away
from the core network to be reachable over WiFi links. New gateways similar to those
previously installed are set up for the new communities at the new sites D and E, but
they are islanded from the core network, and only serve the local users.
After investigating the options, it is found that it is possible to install an
Internet connection at site A, and an interface on the Internet connection is
configured for Reticulum on the Raspberry Pi at site A.
A member of the organisation at site D, named Dori, is willing to help by sharing
the Internet connection she already has in her home, and is able to leave a Raspberry
Pi running. A new Reticulum interface is configured on her Pi, connecting to the newly
enabled Internet interface on the gateway at site A. Dori is now connected to both
all the nodes at her own local site (through the hill-top LoRa gateway), and all the
combined users of sites A, B and C. She then enables transport on her node, and
traffic from site D can now reach everyone at site A, B and C, and vice versa.
Growth and Convergence
======================
As the organisation grows, more gateways are added to keep up with the growing user
base. Some local gateways even add VHF radios and packet modems to reach outlying users
and communities that are out of reach for the LoRa radios and WiFi backhauls.
As more sites, gateways and users are connected, the amount of coordination required
is kept to a minimum. If one community wants to add connectivity to the next one
over, it can simply be done without having to involve everyone or coordinate address
space or routing tables.
With the added geographical coverage, the operators at site A one day find that
the original internet bridged interfaces are no longer utilised. The network has
converged to be completely self-connected, and the sites that were once poorly
connected outliers are now an integral part of the network.
+12 -4
View File
@@ -39,7 +39,7 @@ Destination
Packet
------
.. autoclass:: RNS.Packet
.. autoclass:: RNS.Packet(destination, data, create_receipt = True)
:members:
.. _api-packetreceipt:
@@ -47,7 +47,7 @@ Packet
Packet Receipt
--------------
.. autoclass:: RNS.PacketReceipt
.. autoclass:: RNS.PacketReceipt()
:members:
.. _api-link:
@@ -55,7 +55,15 @@ Packet Receipt
Link
----
.. autoclass:: RNS.Link
.. autoclass:: RNS.Link(destination, established_callback=None, closed_callback = None)
:members:
.. _api-requestreceipt:
Request Receipt
---------------
.. autoclass:: RNS.RequestReceipt()
:members:
.. _api-resource:
@@ -63,7 +71,7 @@ Link
Resource
--------
.. autoclass:: RNS.Resource
.. autoclass:: RNS.Resource(data, link, advertise=True, auto_compress=True, callback=None, progress_callback=None, timeout=None)
:members:
.. _api-transport:
Binary file not shown.

Before

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

+20 -20
View File
@@ -52,7 +52,7 @@ by using multiple hops).
Goals
=====
To be as widely usable and easy to implement as possible, the following goals have been used to
To be as widely usable and easy to use as possible, the following goals have been used to
guide the design of Reticulum:
@@ -67,9 +67,12 @@ guide the design of Reticulum:
it can be easily replicated.
* **Very low bandwidth requirements**
Reticulum should be able to function reliably over links with a transmission capacity as low
as *1,000 bps*.
as *500 bps*.
* **Encryption by default**
Reticulum must use encryption by default where possible and applicable.
Reticulum must use strong encryption by default for all communication.
* **Initiator Anonymity**
It must be possible to communicate over a Reticulum network without revealing any identifying
information about oneself.
* **Unlicensed use**
Reticulum shall be functional over physical communication mediums that do not require any
form of license to use. Reticulum must be designed in a way, so it is usable over ISM radio
@@ -99,7 +102,7 @@ Introduction & Basic Functionality
Reticulum is a networking stack suited for high-latency, low-bandwidth links. Reticulum is at its
core a *message oriented* system. It is suited for both local point-to-point or point-to-multipoint
scenarios where alle nodes are within range of each other, as well as scenarios where packets need
to be transported over multiple hops to reach the recipient.
to be transported over multiple hops in a complex network to reach the recipient.
Reticulum does away with the idea of addresses and ports known from IP, TCP and UDP. Instead
Reticulum uses the singular concept of *destinations*. Any application using Reticulum as its
@@ -110,9 +113,9 @@ All destinations in Reticulum are represented internally as 10 bytes, derived fr
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>``.
By default Reticulum encrypts all data using public-key cryptography. Any message sent to a
destination is encrypted with that destinations public key. Reticulum can also set up an encrypted
channel to a destination with *Perfect Forward Secrecy* and *Initiator Anonymity* using a elliptic
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*.
@@ -135,17 +138,17 @@ destinations. Reticulum uses three different basic destination types, and one sp
* **Single**
The *single* destination type defines a public-key encrypted destination. Any data sent to this
destination will be encrypted with the destinations public key, and will only be readable by
the creator of the destination.
The *single* destination type is always identified by a unique public key. Any data sent to this
destination will be encrypted using ephemeral keys derived from an ECDH key exchange, and will
only be readable by the creator of the destination, who holds the corresponding private key.
* **Group**
The *group* destination type defines a symmetrically encrypted destination. Data sent to this
destination will be encrypted with a symmetric key, and will be readable by anyone in
possession of the key. The *group* destination can be used just as well by only two peers, as it
can by many.
possession of the key.
* **Plain**
A *plain* destination type is unencrypted, and suited for traffic that should be broadcast to a
number of users, or should be readable by anyone. Traffic to a *plain* destination is not encrypted.
Generally, *plain* destinations can be used for broadcast information intended to be public.
* **Link**
A *link* is a special destination type, that serves as an abstract channel to a *single*
destination, directly connected or over multiple hops. The *link* also offers reliability and
@@ -429,7 +432,7 @@ 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 240 bytes (more info in the
The combined bandwidth cost of setting up a link is 3 packets totalling 237 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.
@@ -507,7 +510,7 @@ the transfer is needed.
This is the purpose of the Reticulum :ref:`Resource<api-resource>`. A *Resource* can automatically
handle the reliable transfer of an arbitrary amount of data over an established :ref:`Link<api-link>`.
Resources can auto-compress data, will handle breaking the data into individual packets, sequencing
the transfer and reassembling the data on the other end.
the transfer, integrity verification and reassembling the data on the other end.
:ref:`Resources<api-resource>` are programmatically very simple to use, and only requires a few lines
of codes to reliably transfer any amount of data. They can be used to transfer data stored in memory,
@@ -581,6 +584,7 @@ Node Types
Currently Reticulum defines two node types, the *Station* and the *Peer*. A node is a *station* if it fixed
in one place, and if it is intended to be kept online most of the time. Otherwise the node is a *peer*.
This distinction is made by the user configuring the node, and is used to determine what nodes on the
network will help forward traffic, and what nodes rely on other nodes for connectivity.
@@ -596,10 +600,6 @@ Currently, Reticulum is completely priority-agnostic regarding general traffic.
on a first-come, first-serve basis. Announce re-transmission are handled according to the re-transmission
times and priorities described earlier in this chapter.
It is possible that a prioritisation engine could be added to Reticulum in the future, but in
the light of Reticulums goal of equal access, doing so would need to be the subject of careful
investigation of the consequences first.
.. _understanding-packetformat:
@@ -701,5 +701,5 @@ Binary Packet Format
- Announce : 151 bytes
- Link Request : 77 bytes
- Link Proof : 77 bytes
- Link RTT packet : 86 bytes
- Link keepalive : 14 bytes
- Link RTT packet : 83 bytes
- Link keepalive : 14 bytes
+263
View File
@@ -0,0 +1,263 @@
.. _using-main:
******************************
Using Reticulum on Your System
******************************
Reticulum is not installed as a driver or kernel module, as one might expect
of a networking stack. Instead, Reticulum is distributed as a Python module.
This means that no special privileges are required to install or use it.
Any program or application that uses Reticulum will automatically load and
initialise Reticulum when it starts.
In many cases, this approach is sufficient. When any program needs to use
Reticulum, it is loaded, initialised, interfaces are brought up, and the
program can now communicate over Reticulum. If another program starts up
and also wants access to the same Reticulum network, the instance is simply
shared. This works for any number of programs running concurrently, and is
very easy to use, but depending on your use case, there are other options.
Included Utility Programs
-------------------------
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
other programs, applications and services can utilise.
The rnsd Utility
================
To do so is very easy. Simply run the included ``rnsd`` command. When ``rnsd``
is running, it will keep all configured interfaces open, handle transport if
it is enabled, and allow any other programs to immediately utilise the
Reticulum network it is configured for.
You can even run multiple instances of rnsd with different configurations on
the same system.
.. code:: text
# Install Reticulum
pip3 install rns
# Run rnsd
rnsd
.. code:: text
usage: rnsd [-h] [--config CONFIG] [-v] [-q] [--version]
Reticulum Network Stack Daemon
optional arguments:
-h, --help show this help message and exit
--config CONFIG path to alternative Reticulum config directory
-v, --verbose
-q, --quiet
--version show program's version number and exit
You can easily add ``rnsd`` as an always-on service by :ref:`configuring a service<using-systemd>`.
The rnstatus Utility
====================
Using the ``rnstatus`` utility, you can view the status of configured Reticulum
interfaces, similar to the ``ifconfig`` program.
.. code:: text
# Run rnstatus
rnstatus
# Example output
Shared Instance[37428]
Status: Up
Connected applications: 1
RX: 1.13 KB
TX: 1.07 KB
UDPInterface[Default UDP Interface/0.0.0.0:4242]
Status: Up
RX: 1.01 KB
TX: 1.01 KB
TCPInterface[RNS Testnet Frankfurt/frankfurt.rns.unsigned.io:4965]
Status: Up
RX: 1.37 KB
TX: 9.02 KB
.. code:: text
usage: rnsd [-h] [--config CONFIG] [-v] [-q] [--version]
Reticulum Network Stack Daemon
optional arguments:
-h, --help show this help message and exit
--config CONFIG path to alternative Reticulum config directory
-v, --verbose
-q, --quiet
--version show program's version number and exit
The rnpath Utility
====================
With the ``rnpath`` utility, you can look up and view paths for
destinations on the Reticulum network.
.. code:: text
# Run rnpath
rnpath eca6f4e4dc26ae329e61
# Example output
Path found, destination <eca6f4e4dc26ae329e61> is 4 hops away via <56b115c30cd386cad69c> on TCPInterface[Testnet/frankfurt.rns.unsigned.io:4965]
.. code:: text
usage: rnpath.py [-h] [--config CONFIG] [--version] [-v] [destination]
Reticulum Path Discovery Utility
positional arguments:
destination hexadecimal hash of the destination
optional arguments:
-h, --help show this help message and exit
--config CONFIG path to alternative Reticulum config directory
--version show program's version number and exit
-v, --verbose
The rnprobe Utility
====================
The ``rnprobe`` utility lets you probe a destination for connectivity, similar
to the ``ping`` program. Please note that probes will only be answered if the
specified destination is configured to send proofs for received packets. Many
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
# Example output
Sent 16 byte probe to <9382f334de63217a4278>
Valid reply received from <9382f334de63217a4278>
Round-trip time is 38.469 milliseconds over 2 hops
.. code:: text
usage: rnprobe.py [-h] [--config CONFIG] [--version] [-v] [full_name] [destination_hash]
Reticulum Probe Utility
positional arguments:
full_name full destination name in dotted notation
destination_hash hexadecimal hash of the destination
optional arguments:
-h, --help show this help message and exit
--config CONFIG path to alternative Reticulum config directory
--version show program's version number and exit
-v, --verbose
Improving System Configuration
------------------------------
If you are setting up a system for permanent use with Reticulum, there is a
few system configuration changes that can make this easier to administrate.
These changes will be detailed here.
Fixed Serial Port Names
=======================
On a Reticulum node with several serial port based interfaces, it can be
beneficial to use the fixed name device nodes for the serial ports, instead
of the dynamically allocated shorthands such as ``/dev/ttyUSB0``. Under most
Debian-based distributions, including Ubuntu and Raspberry Pi OS, these nodes
can be found under ``/dev/serial/by-id``.
You can use such a device path directly in place of the numbered shorthands.
Here is an example of a packet radio TNC configured as such:
.. code:: text
[[Packet Radio KISS Interface]]
type = KISSInterface
interface_enabled = True
outgoing = true
port = /dev/serial/by-id/usb-FTDI_FT230X_Basic_UART_43891CKM-if00-port0
speed = 115200
databits = 8
parity = none
stopbits = 1
preamble = 150
txtail = 10
persistence = 200
slottime = 20
Using this methodology avoids potential naming mix-ups where physical devices
might be plugged and unplugged in different orders, or when node name
assignment varies from one boot to another.
.. _using-systemd:
Reticulum as a System Service
=============================
Instead of starting Reticulum manually, you can install ``rnsd`` as a system
service and have it start automatically at boot.
If you installed Reticulum with ``pip``, the ``rnsd`` program will most likely
be located in a user-local installation path only, which means ``systemd`` will not
be able to execute it. In this case, you can simply symlink the ``rnsd`` program
into a directory that is in systemd's path:
.. code:: text
sudo ln -s $(which rnsd) /usr/local/bin/
You can then create the service file ``/etc/systemd/system/rnsd.service`` with the
following content:
.. code:: text
[Unit]
Description=Reticulum Network Stack Daemon
After=multi-user.target
[Service]
# If you run Reticulum on WiFi devices,
# or other devices that need some extra
# time to initialise, you might want to
# add a short delay before Reticulum is
# started by systemd:
# ExecStartPre=/bin/sleep 10
Type=simple
Restart=always
RestartSec=3
User=USERNAMEHERE
ExecStart=rnsd --service
[Install]
WantedBy=multi-user.target
Be sure to replace ``USERNAMEHERE`` with the user you want to run ``rnsd`` as.
To manually start ``rnsd`` run:
.. code:: text
sudo systemctl start rnsd
If you want to automatically start ``rnsd`` at boot, run:
.. code:: text
sudo systemctl enable rnsd
+20 -16
View File
@@ -2,13 +2,13 @@
What is Reticulum?
******************
Reticulum is a cryptography-based networking stack for wide-area networks built on readily available hardware, and can operate even with very high latency and extremely low bandwidth.
Reticulum is a cryptography-based networking stack for wide-area networks built on readily available hardware, that can operate even with very high latency and extremely low bandwidth.
Reticulum allows you to build very wide-area networks with off-the-shelf tools, and offers end-to-end encryption, autoconfiguring cryptographically backed multi-hop transport, efficient addressing, unforgeable packet acknowledgements and more.
Reticulum is a complete networking stack, and does not use IP or higher layers, although it is easy to utilise 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 built directly on cryptographic principles, allowing resilience and stable functionality in open and trustless networks.
Reticulum is a complete networking stack, and does not need IP or higher layers, although it is easy to utilise 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 built directly on cryptographic principles, allowing resilience and stable functionality in open and trustless networks.
No kernel modules or drivers are required. Reticulum runs completely in userland, and can run on practically any system that runs Python 3.
No kernel modules or drivers are required. Reticulum runs completely in userland, and can run on practically any system that runs Python 3. Reticulum runs well even on small single-board computers like the Pi Zero.
Current Status
@@ -16,22 +16,21 @@ 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.
Caveat Emptor
==============
Reticulum is an experimental networking stack, 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. To be considered even remotely secure, Reticulum needs a very thourough security review by independt cryptographers and security researchers. If you want to help out, or help sponsor an audit, please do get in touch.
What does Reticulum Offer?
==========================
* Coordination-less globally unique adressing 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
* Reticulum uses the `Fernet <https://github.com/fernet/spec/blob/master/Spec.md>`_ specification for encryption
* Reticulum uses the `Fernet <https://github.com/fernet/spec/blob/master/Spec.md>`_ specification for on-the-wire / over-the-air encryption
* All keys are ephemeral and derived from an ECDH key exchange on Curve25519
* AES-128 in CBC mode with PKCS7 padding
@@ -39,8 +38,6 @@ What does Reticulum Offer?
* IVs are generated through os.urandom()
* Keys are ephemeral and derived from an ECDH key exchange on Curve25519
* Unforgeable packet delivery confirmations
* A variety of supported interface types
@@ -57,7 +54,7 @@ What does Reticulum Offer?
* Efficient link establishment
* Total bandwidth cost of setting up a link is only 3 packets, totalling 240 bytes
* Total bandwidth cost of setting up a link is only 3 packets, totalling 237 bytes
* Low cost of keeping links open at only 0.62 bits per second
@@ -65,7 +62,7 @@ What does Reticulum Offer?
Where can Reticulum be Used?
============================
Over practically any medium that can support at least a half-duplex channel
with 1.000 bits per second throughput, and an MTU of 500 bytes. Data radios,
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.
@@ -87,8 +84,8 @@ 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.
Supported Interface Types and Devices
=====================================
Interface Types and Devices
===========================
Reticulum implements a range of generalised interface types that covers most of the communications hardware that Reticulum can run over. If your hardware is not supported, it's relatively simple to implement an interface class. Currently, the following interfaces are supported:
* Any ethernet device
@@ -101,4 +98,11 @@ Reticulum implements a range of generalised interface types that covers most of
* TCP over IP networks
* UDP over IP networks
* UDP over IP networks
For a full list and more details, see the :ref:`Supported Interfaces<interfaces-main>` chapter.
Caveat Emptor
==============
Reticulum is an experimental networking stack, 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. To be considered secure, Reticulum needs a thourough security review by independt cryptographers and security researchers. If you want to help out, or help sponsor an audit, please do get in touch.
+12 -3
View File
@@ -20,6 +20,15 @@ setuptools.setup(
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
],
install_requires=['cryptography>=3.4.7', 'pyserial', 'netifaces>=0.10.4'],
python_requires='>=3.5',
)
entry_points= {
'console_scripts': [
'rnsd=RNS.Utilities.rnsd:main',
'rnstatus=RNS.Utilities.rnstatus:main',
'rnprobe=RNS.Utilities.rnprobe:main',
'rnpath=RNS.Utilities.rnpath:main',
]
},
install_requires=['cryptography>=3.4.7', 'pyserial>=3.5', 'netifaces'],
python_requires='>=3.6',
)