Compare commits

..

54 Commits

Author SHA1 Message Date
Mark Qvist c6df6293b2 Added hardware MTU parameter to interfaces 2022-05-29 15:43:50 +02:00
Mark Qvist d99d31097b Updated manual 2022-05-29 10:14:31 +02:00
Mark Qvist 54488cfeb5 Updated documentation 2022-05-29 10:13:25 +02:00
Mark Qvist d7e38d646e Updated readme 2022-05-29 09:48:53 +02:00
Mark Qvist b9057bee5f Updated readme 2022-05-29 09:48:31 +02:00
Mark Qvist 9bd64834ec Updated readme 2022-05-29 09:47:26 +02:00
Mark Qvist 9e20ba2dac Implemented I2PInterface recovery on I2P router restart 2022-05-28 02:24:01 +02:00
Mark Qvist 49ed335e19 Cleanup 2022-05-26 16:52:28 +02:00
Mark Qvist 85c71b0b7b Updated docs 2022-05-26 16:50:35 +02:00
Mark Qvist 33fac728f8 Improved link stale process and timeout calculations 2022-05-26 16:49:02 +02:00
Mark Qvist 49616a36cf Fixed I2P controller startup when event loop is not immediately ready 2022-05-26 09:54:56 +02:00
Mark Qvist 1e77f85cd4 Fixed rnx version output 2022-05-26 00:03:37 +02:00
Mark Qvist 9e316ab989 Fixed deprecated options in asyncio API for Python 3.10. Fixes #58. 2022-05-25 23:11:01 +02:00
Mark Qvist 94749e0dde Updated default configs 2022-05-25 23:10:05 +02:00
Mark Qvist a6dbc53209 Improved status display for I2P interfaces 2022-05-25 21:44:49 +02:00
Mark Qvist 3af5a8f3ed Improved I2P server tunnel error handling. Fixes #13. 2022-05-25 21:23:52 +02:00
Mark Qvist fb5172ff10 Improved I2P client tunnel error handling 2022-05-25 20:18:06 +02:00
Mark Qvist 24d6de8490 Updated docs 2022-05-25 15:51:20 +02:00
Mark Qvist d3ab0878e0 Improved I2P interface display in rnstatus 2022-05-25 15:50:54 +02:00
Mark Qvist 7848b7e396 Fixed invalid reference in rnx 2022-05-25 15:08:45 +02:00
Mark Qvist fc80dd2614 Improved rnstatus output 2022-05-25 14:21:04 +02:00
Mark Qvist e00a758b2a Updated readme 2022-05-24 21:00:46 +02:00
Mark Qvist d44ec745df Updated readme 2022-05-24 21:00:25 +02:00
Mark Qvist 7573ac1970 Updated readme 2022-05-24 20:58:58 +02:00
Mark Qvist 88390f0cbc Updated readme 2022-05-24 20:57:56 +02:00
Mark Qvist 3b8490ae9c Added rnx util to documentation 2022-05-24 20:47:45 +02:00
Mark Qvist 417ac9f8da Added rnx remote command utility 2022-05-24 20:14:43 +02:00
Mark Qvist fe5e74bc2b Improved rncp arguments 2022-05-24 20:13:54 +02:00
Mark Qvist 30f71857ae Added docstrings. Added request size to receipts. Fixed link stale time calculation on newly created links with no actual activity. 2022-05-24 20:13:11 +02:00
Mark Qvist c24233845e Implemented bandwidth cap for recursive path requests 2022-05-23 19:49:48 +02:00
Mark Qvist c0fbde5ad1 Added recursive path request loop avoidance 2022-05-23 18:14:45 +02:00
Mark Qvist 5da66402dd Fixed rncp output 2022-05-23 09:23:37 +02:00
Mark Qvist 3bf5694238 Fixed naming conflict in resource advertisements 2022-05-23 08:54:07 +02:00
Mark Qvist 9e6a5d5d91 Fix announce rate targets on I2PInterface peers 2022-05-23 00:28:06 +02:00
Mark Qvist cf3e47f469 Fixed interface mode inheritance 2022-05-23 00:06:26 +02:00
Mark Qvist f8db5a545d Fixed interface mode check 2022-05-23 00:00:14 +02:00
Mark Qvist a79f6e7efa Added rncp utility 2022-05-22 23:44:32 +02:00
Mark Qvist ac4606bcf7 Updated docs 2022-05-22 23:44:12 +02:00
Mark Qvist d1cb07356c Fixed missing recursive progress callback allocation in segmented resource transfer 2022-05-22 21:05:07 +02:00
Mark Qvist e811d54d0f Fixed bug in conditional resource acceptance callback 2022-05-22 19:09:44 +02:00
Mark Qvist 49c8ada478 Added standard identity storage folder 2022-05-22 19:09:16 +02:00
Mark Qvist 6ea7d78b31 Updated API reference 2022-05-22 19:08:32 +02:00
Mark Qvist 0ace84367b Improved link authentication callback 2022-05-22 19:08:03 +02:00
Mark Qvist e63e6821e0 Updated Destination docstrings 2022-05-22 17:11:30 +02:00
Mark Qvist 109132e09d Fixed expired AP and roaming paths not being removed at correct time. 2022-05-22 15:43:46 +02:00
Mark Qvist efd24ec134 Updated documentation 2022-05-22 15:18:09 +02:00
Mark Qvist eefa37f808 Updated documentation 2022-05-22 15:17:54 +02:00
Mark Qvist e4871f7667 Updated documentation 2022-05-22 15:17:25 +02:00
Mark Qvist 44ba5624bc Added gateway mode to rnstatus 2022-05-22 15:16:58 +02:00
Mark Qvist e9c5e3c189 Version bump 2022-05-22 14:29:29 +02:00
Mark Qvist f3ff71d9b8 Implemented unknown path discovery 2022-05-22 14:18:58 +02:00
Mark Qvist 81b92ffdc1 Added gateway interface mode 2022-05-22 11:14:33 +02:00
Mark Qvist 02bb9068cc Updated readme 2022-05-22 11:13:54 +02:00
Mark Qvist ecc9e84bc2 Fixed typo 2022-05-18 00:47:29 +02:00
49 changed files with 2416 additions and 292 deletions
+23 -5
View File
@@ -5,9 +5,9 @@ Reticulum Network Stack β
Reticulum is the cryptography-based networking stack for wide-area networks built on readily available hardware. It can operate even with very high latency and extremely low bandwidth. Reticulum allows you to build wide-area networks with off-the-shelf tools, and offers end-to-end encryption and connectivity, initiator anonymity, autoconfiguring cryptographically backed multi-hop transport, efficient addressing, unforgeable delivery acknowledgements and more.
The vision of Reticulum is to allow anyone to be their own network operator, and to make it cheap and easy to cover vast areas with a myriad of independent, interconnectable and autonomous networks. Reticulum **is not** *one network*, it **is a tool** for building *thousands of networks*. Networks without kill-switches, surveillance, censorship and control. Networks that can freely interoperate, associate and disassociate with each other, and require no central oversight. Networks for human beings. *Networks for the people*.
The vision of Reticulum is to allow anyone to be their own network operator, and to make it cheap and easy to cover vast areas with a myriad of independent, interconnectable and autonomous networks. Reticulum **is not** *one* network. It is **a tool** for building *thousands of networks*. Networks without kill-switches, surveillance, censorship and control. Networks that can freely interoperate, associate and disassociate with each other, and require no central oversight. Networks for human beings. *Networks for the people*.
Reticulum is a complete networking stack, and does not need IP or higher layers, although it is easy to use IP (with TCP or UDP) as the underlying carrier for Reticulum. It is therefore trivial to tunnel Reticulum over the Internet or private IP networks.
Reticulum is a complete networking stack, and does not rely on IP or higher layers, but it is possible to use IP as the underlying carrier for Reticulum. It is therefore trivial to tunnel Reticulum over the Internet or private IP networks.
Having no dependencies on traditional networking stacks free up overhead that has been utilised to implement a networking stack built directly on cryptographic principles, allowing resilience and stable functionality in open and trustless networks.
@@ -75,6 +75,18 @@ When first started, Reticulum will create a default configuration file, providin
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/).
## Included Utilities
Reticulum includes a range of useful utilities for managing your networks, viewing status and information, and other tasks. You can read more about these programs in the [Included Utility Programs](https://markqvist.github.io/Reticulum/manual/using.html#included-utility-programs) section of the [Reticulum Manual](https://markqvist.github.io/Reticulum/manual/).
- The system daemon `rnsd` for running Reticulum as an always-available service
- An interface status utility called `rnstatus`, that displays information about interfaces
- The path lookup and and management tool `rnpath` letting you view and modify path tables
- A diagnostics tool called `rnprobe` for checking connectivity to destinations
- A simple file transfer program called `rncp` making easy to copy files to remote systems
- The remote command execution program `rnx` that let's you run commands and programs and retrieve output from remote systems
All tools, including `rnx` and `rncp`, work reliably and well even over very low-bandwidth links like LoRa or Packet Radio.
## 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.
@@ -95,11 +107,11 @@ Currently, the following interfaces are supported:
- Custom hardware via stdio or pipes
## Development Roadmap
- Version 0.3.6
- Version 0.3.8
- Improving [the manual](https://markqvist.github.io/Reticulum/manual/) with sections specifically for beginners
- Version 0.3.7
- Utilities for managing identities, signing and encryption
- Support for radio and modem interfaces on Android
- GUI interface configuration tool
- User friendly interface configuration tool
- Easy way to share interface configurations, see [#19](https://github.com/markqvist/Reticulum/discussions/19)
- More interface types for even broader compatibility
- Plain ESP32 devices (ESP-Now, WiFi, Bluetooth, etc.)
@@ -111,10 +123,16 @@ Currently, the following interfaces are supported:
- CAN-bus
- ZeroMQ
- MQTT
- IrDA / IrPHY
- SPI
- i²c
- Version 0.3.9
- A portable cryptography core, supporting multiple backends
- Performance optimisations
- Memory optimisations
- Planned, but not yet scheduled
- Globally routable multicast
- Bindings for other programming languages
- A portable Reticulum implementation in C, see [#21](https://github.com/markqvist/Reticulum/discussions/21)
+3 -3
View File
@@ -209,7 +209,7 @@ class Destination:
Registers a function to be called when a link has been established to
this destination.
:param callback: A function or method to be called.
:param callback: A function or method with the signature *callback(link)* to be called when a new link is established with this destination.
"""
self.callbacks.link_established = callback
@@ -218,7 +218,7 @@ class Destination:
Registers a function to be called when a packet has been received by
this destination.
:param callback: A function or method to be called.
:param callback: A function or method with the signature *callback(data, packet)* to be called when this destination receives a packet.
"""
self.callbacks.packet = callback
@@ -228,7 +228,7 @@ class Destination:
a packet sent to this destination. Allows control over when and if
proofs should be returned for received packets.
:param callback: A function or method to be called. The callback must return one of True or False. If the callback returns True, a proof will be sent. If it returns False, a proof will not be sent.
:param callback: A function or method to with the signature *callback(packet)* be called when a packet that requests a proof is received. The callback must return one of True or False. If the callback returns True, a proof will be sent. If it returns False, a proof will not be sent.
"""
self.callbacks.proof_requested = callback
+3 -1
View File
@@ -79,6 +79,8 @@ class AX25KISSInterface(Interface):
self.rxb = 0
self.txb = 0
self.HW_MTU = 564
self.pyserial = serial
self.serial = None
@@ -304,7 +306,7 @@ class AX25KISSInterface(Interface):
in_frame = True
command = KISS.CMD_UNKNOWN
data_buffer = b""
elif (in_frame and len(data_buffer) < RNS.Reticulum.MTU+AX25.HEADER_SIZE):
elif (in_frame and len(data_buffer) < self.HW_MTU+AX25.HEADER_SIZE):
if (len(data_buffer) == 0 and command == KISS.CMD_UNKNOWN):
# We only support one HDLC port for now, so
# strip off the port nibble
+3
View File
@@ -60,6 +60,9 @@ class AutoInterface(Interface):
self.netifaces = netifaces
self.rxb = 0
self.txb = 0
self.HW_MTU = 1064
self.IN = True
self.OUT = False
self.name = name
+198 -37
View File
@@ -72,10 +72,12 @@ class I2PController:
self.client_tunnels = {}
self.server_tunnels = {}
self.i2plib_tunnels = {}
self.loop = None
self.i2plib = i2plib
self.utils = i2plib.utils
self.sam_address = i2plib.get_sam_address()
self.ready = False
self.storagepath = rns_storagepath+"/i2p"
if not os.path.isdir(self.storagepath):
@@ -85,9 +87,20 @@ class I2PController:
def start(self):
asyncio.set_event_loop(asyncio.new_event_loop())
self.loop = asyncio.get_event_loop()
time.sleep(0.10)
if self.loop == None:
RNS.log("Could not get event loop for "+str(self)+", waiting for event loop to appear", RNS.LOG_VERBOSE)
while self.loop == None:
self.loop = asyncio.get_event_loop()
sleep(0.25)
try:
self.ready = True
self.loop.run_forever()
except Exception as e:
self.ready = False
RNS.log("Exception on event loop for "+str(self)+": "+str(e), RNS.LOG_ERROR)
finally:
self.loop.close()
@@ -106,6 +119,7 @@ class I2PController:
def client_tunnel(self, owner, i2p_destination):
self.client_tunnels[i2p_destination] = False
self.i2plib_tunnels[i2p_destination] = None
while True:
if not self.client_tunnels[i2p_destination]:
@@ -113,29 +127,102 @@ class I2PController:
async def tunnel_up():
RNS.log("Bringing up I2P tunnel to "+str(owner)+", this may take a while...", RNS.LOG_INFO)
tunnel = self.i2plib.ClientTunnel(i2p_destination, owner.local_addr, sam_address=self.sam_address, loop=self.loop)
self.i2plib_tunnels[i2p_destination] = tunnel
await tunnel.run()
owner.awaiting_i2p_tunnel = False
RNS.log(str(owner)+ " tunnel setup complete", RNS.LOG_VERBOSE)
try:
self.loop.ext_owner = self
future = asyncio.run_coroutine_threadsafe(tunnel_up(), self.loop).result()
self.client_tunnels[i2p_destination] = True
self.loop.ext_owner = self
result = asyncio.run_coroutine_threadsafe(tunnel_up(), self.loop).result()
if not i2p_destination in self.i2plib_tunnels:
raise IOError("No tunnel control instance was created")
except Exception as e:
RNS.log("Error while setting up I2P tunnel: "+str(e))
raise e
else:
tn = self.i2plib_tunnels[i2p_destination]
if tn != None and hasattr(tn, "status"):
RNS.log("Waiting for status from I2P control process", RNS.LOG_EXTREME)
while not tn.status["setup_ran"]:
time.sleep(0.1)
RNS.log("Got status from I2P control process", RNS.LOG_EXTREME)
if tn.status["setup_failed"]:
raise tn.status["exception"]
else:
self.client_tunnels[i2p_destination] = True
owner.awaiting_i2p_tunnel = False
if owner.socket != None:
if hasattr(owner.socket, "close"):
if callable(owner.socket.close):
try:
owner.socket.shutdown(socket.SHUT_RDWR)
except Exception as e:
RNS.log("Error while shutting down socket for "+str(owner)+": "+str(e))
try:
owner.socket.close()
except Exception as e:
RNS.log("Error while closing socket for "+str(owner)+": "+str(e))
RNS.log(str(owner)+" tunnel setup complete", RNS.LOG_VERBOSE)
else:
raise IOError("Got no status response from SAM API")
except ConnectionRefusedError as e:
raise e
except ConnectionAbortedError as e:
raise e
except Exception as e:
raise IOError("Could not connect to I2P SAM API while configuring to "+str(owner)+". Check that I2P is running and SAM is enabled.")
RNS.log("Unexpected error type from I2P SAM: "+str(e), RNS.LOG_ERROR)
raise e
else:
i2ptunnel = self.i2plib_tunnels[i2p_destination]
if hasattr(i2ptunnel, "status"):
# TODO: Remove
# RNS.log(str(i2ptunnel.status))
i2p_exception = i2ptunnel.status["exception"]
if i2ptunnel.status["setup_ran"] == False:
RNS.log(str(self)+" I2P tunnel setup did not complete", RNS.LOG_ERROR)
return False
elif i2p_exception != None:
RNS.log(str(self)+" An error ocurred while setting up I2P tunnel. The contained exception was: "+str(i2p_exception), RNS.LOG_ERROR)
RNS.log("Resetting I2P tunnel", RNS.LOG_ERROR)
return False
elif i2ptunnel.status["setup_failed"] == True:
RNS.log(str(self)+" Unspecified I2P tunnel setup error, resetting I2P tunnel", RNS.LOG_ERROR)
return False
else:
RNS.log(str(self)+" Got no status from SAM API, resetting I2P tunnel", RNS.LOG_ERROR)
return False
time.sleep(5)
def server_tunnel(self, owner):
i2p_dest_hash = RNS.Identity.full_hash(RNS.Identity.full_hash(owner.name.encode("utf-8")))
i2p_keyfile = self.storagepath+"/"+RNS.hexrep(i2p_dest_hash, delimit=False)+".i2p"
while RNS.Transport.identity == None:
time.sleep(1)
# Old format
i2p_dest_hash_of = RNS.Identity.full_hash(RNS.Identity.full_hash(owner.name.encode("utf-8")))
i2p_keyfile_of = self.storagepath+"/"+RNS.hexrep(i2p_dest_hash_of, delimit=False)+".i2p"
# New format
i2p_dest_hash_nf = RNS.Identity.full_hash(RNS.Identity.full_hash(owner.name.encode("utf-8"))+RNS.Identity.full_hash(RNS.Transport.identity.hash))
i2p_keyfile_nf = self.storagepath+"/"+RNS.hexrep(i2p_dest_hash_nf, delimit=False)+".i2p"
# Use old format if a key is already present
if os.path.isfile(i2p_keyfile_of):
i2p_keyfile = i2p_keyfile_of
else:
i2p_keyfile = i2p_keyfile_nf
i2p_dest = None
if not os.path.isfile(i2p_keyfile):
@@ -154,20 +241,48 @@ class I2PController:
owner.b32 = i2p_b32
self.server_tunnels[i2p_b32] = False
self.i2plib_tunnels[i2p_b32] = None
while self.server_tunnels[i2p_b32] == False:
try:
async def tunnel_up():
RNS.log(str(owner)+" Bringing up I2P endpoint, this may take a while...", RNS.LOG_INFO)
tunnel = self.i2plib.ServerTunnel((owner.bind_ip, owner.bind_port), loop=self.loop, destination=i2p_dest, sam_address=self.sam_address)
await tunnel.run()
RNS.log(str(owner)+ " endpoint setup complete. Now reachable at: "+str(i2p_dest.base32)+".b32.i2p", RNS.LOG_VERBOSE)
while True:
if self.server_tunnels[i2p_b32] == False:
try:
async def tunnel_up():
RNS.log(str(owner)+" Bringing up I2P endpoint, this may take a while...", RNS.LOG_INFO)
tunnel = self.i2plib.ServerTunnel((owner.bind_ip, owner.bind_port), loop=self.loop, destination=i2p_dest, sam_address=self.sam_address)
self.i2plib_tunnels[i2p_b32] = tunnel
await tunnel.run()
RNS.log(str(owner)+ " endpoint setup complete. Now reachable at: "+str(i2p_dest.base32)+".b32.i2p", RNS.LOG_VERBOSE)
asyncio.run_coroutine_threadsafe(tunnel_up(), self.loop).result()
self.server_tunnels[i2p_b32] = True
asyncio.run_coroutine_threadsafe(tunnel_up(), self.loop).result()
self.server_tunnels[i2p_b32] = True
except Exception as e:
raise IOError("Could not connect to I2P SAM API while configuring "+str(self)+". Check that I2P is running and SAM is enabled.")
except Exception as e:
raise e
else:
i2ptunnel = self.i2plib_tunnels[i2p_b32]
if hasattr(i2ptunnel, "status"):
# TODO: Remove
# RNS.log(str(i2ptunnel.status))
i2p_exception = i2ptunnel.status["exception"]
if i2ptunnel.status["setup_ran"] == False:
RNS.log(str(self)+" I2P tunnel setup did not complete", RNS.LOG_ERROR)
return False
elif i2p_exception != None:
RNS.log(str(self)+" An error ocurred while setting up I2P tunnel. The contained exception was: "+str(i2p_exception), RNS.LOG_ERROR)
RNS.log("Resetting I2P tunnel", RNS.LOG_ERROR)
return False
elif i2ptunnel.status["setup_failed"] == True:
RNS.log(str(self)+" Unspecified I2P tunnel setup error, resetting I2P tunnel", RNS.LOG_ERROR)
return False
else:
RNS.log(str(self)+" Got no status from SAM API, resetting I2P tunnel", RNS.LOG_ERROR)
return False
time.sleep(5)
@@ -191,6 +306,8 @@ class I2PInterfacePeer(Interface):
def __init__(self, parent_interface, owner, name, target_i2p_dest=None, connected_socket=None, max_reconnect_tries=None):
self.rxb = 0
self.txb = 0
self.HW_MTU = 1064
self.IN = True
self.OUT = False
@@ -212,6 +329,10 @@ class I2PInterfacePeer(Interface):
self.mode = RNS.Interfaces.Interface.Interface.MODE_FULL
self.bitrate = I2PInterface.BITRATE_GUESS
self.announce_rate_target = None
self.announce_rate_grace = None
self.announce_rate_penalty = None
if max_reconnect_tries == None:
self.max_reconnect_tries = I2PInterfacePeer.RECONNECT_MAX_TRIES
else:
@@ -233,15 +354,26 @@ class I2PInterfacePeer(Interface):
self.initiator = True
self.bind_ip = "127.0.0.1"
self.bind_port = self.parent_interface.i2p.get_free_port()
self.local_addr = (self.bind_ip, self.bind_port)
self.target_ip = self.bind_ip
self.target_port = self.bind_port
self.awaiting_i2p_tunnel = True
def tunnel_job():
self.parent_interface.i2p.client_tunnel(self, target_i2p_dest)
while self.awaiting_i2p_tunnel:
try:
self.bind_port = self.parent_interface.i2p.get_free_port()
self.local_addr = (self.bind_ip, self.bind_port)
self.target_ip = self.bind_ip
self.target_port = self.bind_port
if not self.parent_interface.i2p.client_tunnel(self, target_i2p_dest):
RNS.log(str(self)+" I2P control process experienced an error, requesting new tunnel...", RNS.LOG_ERROR)
self.awaiting_i2p_tunnel = True
except Exception as e:
RNS.log("Error while while configuring "+str(self)+": "+str(e), RNS.LOG_ERROR)
RNS.log("Check that I2P is installed and running, and that SAM is enabled. Retrying tunnel setup later.", RNS.LOG_ERROR)
time.sleep(15)
thread = threading.Thread(target=tunnel_job)
thread.setDaemon(True)
@@ -439,7 +571,7 @@ class I2PInterfacePeer(Interface):
in_frame = True
command = KISS.CMD_UNKNOWN
data_buffer = b""
elif (in_frame and len(data_buffer) < RNS.Reticulum.MTU):
elif (in_frame and len(data_buffer) < self.HW_MTU):
if (len(data_buffer) == 0 and command == KISS.CMD_UNKNOWN):
# We only support one HDLC port for now, so
# strip off the port nibble
@@ -465,7 +597,7 @@ class I2PInterfacePeer(Interface):
elif (byte == HDLC.FLAG):
in_frame = True
data_buffer = b""
elif (in_frame and len(data_buffer) < RNS.Reticulum.MTU):
elif (in_frame and len(data_buffer) < self.HW_MTU):
if (byte == HDLC.ESC):
escape = True
else:
@@ -512,7 +644,8 @@ class I2PInterfacePeer(Interface):
self.IN = False
if hasattr(self, "parent_interface") and self.parent_interface != None:
self.parent_interface.clients -= 1
if self.parent_interface.clients > 0:
self.parent_interface.clients -= 1
if self in RNS.Transport.interfaces:
if not self.initiator:
@@ -526,9 +659,12 @@ class I2PInterfacePeer(Interface):
class I2PInterface(Interface):
BITRATE_GUESS = 256*1000
def __init__(self, owner, name, rns_storagepath, peers, connectable = True):
def __init__(self, owner, name, rns_storagepath, peers, connectable = False):
self.rxb = 0
self.txb = 0
self.HW_MTU = 1064
self.online = False
self.clients = 0
self.owner = owner
@@ -550,10 +686,25 @@ class I2PInterface(Interface):
self.address = (self.bind_ip, self.bind_port)
self.bitrate = I2PInterface.BITRATE_GUESS
self.online = False
i2p_thread = threading.Thread(target=self.i2p.start)
i2p_thread.setDaemon(True)
i2p_thread.start()
i2p_notready_warning = False
time.sleep(0.25)
if not self.i2p.ready:
RNS.log("I2P controller did not become available in time, waiting for controller", RNS.LOG_VERBOSE)
i2p_notready_warning = True
while not self.i2p.ready:
time.sleep(0.25)
if i2p_notready_warning == True:
RNS.log("I2P controller ready, continuing setup", RNS.LOG_VERBOSE)
def handlerFactory(callback):
def createHandler(*args, **keys):
return I2PInterfaceHandler(callback, *args, **keys)
@@ -568,7 +719,18 @@ class I2PInterface(Interface):
if self.connectable:
def tunnel_job():
self.i2p.server_tunnel(self)
while True:
try:
if not self.i2p.server_tunnel(self):
RNS.log(str(self)+" I2P control process experienced an error, requesting new tunnel...", RNS.LOG_ERROR)
self.online = False
except Exception as e:
RNS.log("Error while while configuring "+str(self)+": "+str(e), RNS.LOG_ERROR)
RNS.log("Check that I2P is installed and running, and that SAM is enabled. Retrying tunnel setup later.", RNS.LOG_ERROR)
time.sleep(15)
thread = threading.Thread(target=tunnel_job)
thread.setDaemon(True)
@@ -576,7 +738,7 @@ class I2PInterface(Interface):
if peers != None:
for peer_addr in peers:
interface_name = peer_addr
interface_name = self.name+" to "+peer_addr
peer_interface = I2PInterfacePeer(self, self.owner, interface_name, peer_addr)
peer_interface.OUT = True
peer_interface.IN = True
@@ -584,9 +746,6 @@ class I2PInterface(Interface):
peer_interface.parent_count = False
RNS.Transport.interfaces.append(peer_interface)
self.online = True
def incoming_connection(self, handler):
RNS.log("Accepting incoming I2P connection", RNS.LOG_VERBOSE)
interface_name = "Connected peer on "+self.name
@@ -602,6 +761,8 @@ class I2PInterface(Interface):
spawned_interface.announce_rate_target = self.announce_rate_target
spawned_interface.announce_rate_grace = self.announce_rate_grace
spawned_interface.announce_rate_penalty = self.announce_rate_penalty
spawned_interface.mode = self.mode
spawned_interface.HW_MTU = self.HW_MTU
RNS.log("Spawned new I2PInterface Peer: "+str(spawned_interface), RNS.LOG_VERBOSE)
RNS.Transport.interfaces.append(spawned_interface)
self.clients += 1
+6
View File
@@ -31,11 +31,17 @@ class Interface:
RPT = False
name = None
# Interface mode definitions
MODE_FULL = 0x01
MODE_POINT_TO_POINT = 0x02
MODE_ACCESS_POINT = 0x03
MODE_ROAMING = 0x04
MODE_BOUNDARY = 0x05
MODE_GATEWAY = 0x06
# Which interface modes a Transport Node
# should actively discover paths for.
DISCOVER_PATHS_FOR = [MODE_ACCESS_POINT, MODE_GATEWAY]
def __init__(self):
self.rxb = 0
+3 -1
View File
@@ -73,6 +73,8 @@ class KISSInterface(Interface):
self.rxb = 0
self.txb = 0
self.HW_MTU = 564
if beacon_data == None:
beacon_data = ""
@@ -279,7 +281,7 @@ class KISSInterface(Interface):
in_frame = True
command = KISS.CMD_UNKNOWN
data_buffer = b""
elif (in_frame and len(data_buffer) < RNS.Reticulum.MTU):
elif (in_frame and len(data_buffer) < self.HW_MTU):
if (len(data_buffer) == 0 and command == KISS.CMD_UNKNOWN):
# We only support one HDLC port for now, so
# strip off the port nibble
+4 -1
View File
@@ -49,6 +49,9 @@ class LocalClientInterface(Interface):
def __init__(self, owner, name, target_port = None, connected_socket=None):
self.rxb = 0
self.txb = 0
self.HW_MTU = 1064
self.online = False
self.IN = True
@@ -177,7 +180,7 @@ class LocalClientInterface(Interface):
elif (byte == HDLC.FLAG):
in_frame = True
data_buffer = b""
elif (in_frame and len(data_buffer) < RNS.Reticulum.MTU):
elif (in_frame and len(data_buffer) < self.HW_MTU):
if (byte == HDLC.ESC):
escape = True
else:
+3 -1
View File
@@ -56,6 +56,8 @@ class PipeInterface(Interface):
self.rxb = 0
self.txb = 0
self.HW_MTU = 1064
self.owner = owner
self.name = name
@@ -137,7 +139,7 @@ class PipeInterface(Interface):
elif (byte == HDLC.FLAG):
in_frame = True
data_buffer = b""
elif (in_frame and len(data_buffer) < RNS.Reticulum.MTU):
elif (in_frame and len(data_buffer) < self.HW_MTU):
if (byte == HDLC.ESC):
escape = True
else:
+3 -1
View File
@@ -111,6 +111,8 @@ class RNodeInterface(Interface):
self.rxb = 0
self.txb = 0
self.HW_MTU = 508
self.pyserial = serial
self.serial = None
@@ -439,7 +441,7 @@ class RNodeInterface(Interface):
command = KISS.CMD_UNKNOWN
data_buffer = b""
command_buffer = b""
elif (in_frame and len(data_buffer) < RNS.Reticulum.MTU):
elif (in_frame and len(data_buffer) < self.HW_MTU):
if (len(data_buffer) == 0 and command == KISS.CMD_UNKNOWN):
command = byte
elif (command == KISS.CMD_DATA):
+3 -1
View File
@@ -62,6 +62,8 @@ class SerialInterface(Interface):
self.rxb = 0
self.txb = 0
self.HW_MTU = 564
self.pyserial = serial
self.serial = None
@@ -152,7 +154,7 @@ class SerialInterface(Interface):
elif (byte == HDLC.FLAG):
in_frame = True
data_buffer = b""
elif (in_frame and len(data_buffer) < RNS.Reticulum.MTU):
elif (in_frame and len(data_buffer) < self.HW_MTU):
if (byte == HDLC.ESC):
escape = True
else:
+9 -2
View File
@@ -79,6 +79,8 @@ class TCPClientInterface(Interface):
self.rxb = 0
self.txb = 0
self.HW_MTU = 1064
self.IN = True
self.OUT = False
self.socket = None
@@ -293,7 +295,7 @@ class TCPClientInterface(Interface):
in_frame = True
command = KISS.CMD_UNKNOWN
data_buffer = b""
elif (in_frame and len(data_buffer) < RNS.Reticulum.MTU):
elif (in_frame and len(data_buffer) < self.HW_MTU):
if (len(data_buffer) == 0 and command == KISS.CMD_UNKNOWN):
# We only support one HDLC port for now, so
# strip off the port nibble
@@ -319,7 +321,7 @@ class TCPClientInterface(Interface):
elif (byte == HDLC.FLAG):
in_frame = True
data_buffer = b""
elif (in_frame and len(data_buffer) < RNS.Reticulum.MTU):
elif (in_frame and len(data_buffer) < self.HW_MTU):
if (byte == HDLC.ESC):
escape = True
else:
@@ -405,6 +407,9 @@ class TCPServerInterface(Interface):
def __init__(self, owner, name, device=None, bindip=None, bindport=None, i2p_tunneled=False):
self.rxb = 0
self.txb = 0
self.HW_MTU = 1064
self.online = False
self.clients = 0
@@ -459,6 +464,8 @@ class TCPServerInterface(Interface):
spawned_interface.announce_rate_target = self.announce_rate_target
spawned_interface.announce_rate_grace = self.announce_rate_grace
spawned_interface.announce_rate_penalty = self.announce_rate_penalty
spawned_interface.mode = self.mode
spawned_interface.HW_MTU = self.HW_MTU
spawned_interface.online = True
RNS.log("Spawned new TCPClient Interface: "+str(spawned_interface), RNS.LOG_VERBOSE)
RNS.Transport.interfaces.append(spawned_interface)
+3
View File
@@ -57,6 +57,9 @@ class UDPInterface(Interface):
def __init__(self, owner, name, device=None, bindip=None, bindport=None, forwardip=None, forwardport=None):
self.rxb = 0
self.txb = 0
self.HW_MTU = 1064
self.IN = True
self.OUT = False
self.name = name
+53 -17
View File
@@ -71,16 +71,30 @@ class Link:
ESTABLISHMENT_TIMEOUT_PER_HOP = RNS.Reticulum.DEFAULT_PER_HOP_TIMEOUT
"""
Default timeout for link establishment in seconds per hop to destination.
Timeout for link establishment in seconds per hop to destination.
"""
TRAFFIC_TIMEOUT_FACTOR = 6
KEEPALIVE_TIMEOUT_FACTOR = 4
"""
RTT timeout factor used in link timeout calculation.
"""
STALE_GRACE = 2
"""
Grace period in seconds used in link timeout calculation.
"""
KEEPALIVE = 360
"""
Interval for sending keep-alive packets on established links in seconds.
"""
STALE_TIME = 2*KEEPALIVE
"""
If no traffic or keep-alive packets are received within this period, the
link will be marked as stale, and a final keep-alive packet will be sent.
If after this no traffic or keep-alive packets are received within ``RTT`` *
``KEEPALIVE_TIMEOUT_FACTOR`` + ``STALE_GRACE``, the link is considered timed out,
and will be torn down.
"""
PENDING = 0x00
HANDSHAKE = 0x01
@@ -145,6 +159,7 @@ class Link:
self.traffic_timeout_factor = Link.TRAFFIC_TIMEOUT_FACTOR
self.keepalive_timeout_factor = Link.KEEPALIVE_TIMEOUT_FACTOR
self.keepalive = Link.KEEPALIVE
self.stale_time = Link.STALE_TIME
self.watchdog_lock = False
self.status = Link.PENDING
self.activated_at = None
@@ -334,7 +349,8 @@ class Link:
response_callback = response_callback,
failed_callback = failed_callback,
progress_callback = progress_callback,
timeout = timeout
timeout = timeout,
request_size = len(packed_request),
)
else:
@@ -348,7 +364,8 @@ class Link:
response_callback = response_callback,
failed_callback = failed_callback,
progress_callback = progress_callback,
timeout = timeout
timeout = timeout,
request_size = len(packed_request),
)
@@ -383,7 +400,9 @@ class Link:
"""
:returns: The time in seconds since last inbound packet on the link.
"""
return time.time() - self.last_inbound
activated_at = self.activated_at if self.activated_at != None else 0
last_inbound = max(self.last_inbound, activated_at)
return time.time() - last_inbound
def no_outbound_for(self):
"""
@@ -497,13 +516,21 @@ class Link:
sleep_time = 0.001
elif self.status == Link.ACTIVE:
if time.time() >= self.last_inbound + self.keepalive:
sleep_time = self.rtt * self.keepalive_timeout_factor + Link.STALE_GRACE
self.status = Link.STALE
activated_at = self.activated_at if self.activated_at != None else 0
last_inbound = max(self.last_inbound, activated_at)
if time.time() >= last_inbound + self.keepalive:
if self.initiator:
self.send_keepalive()
if time.time() >= last_inbound + self.stale_time:
sleep_time = self.rtt * self.keepalive_timeout_factor + Link.STALE_GRACE
self.status = Link.STALE
else:
sleep_time = self.keepalive
else:
sleep_time = (self.last_inbound + self.keepalive) - time.time()
sleep_time = (last_inbound + self.keepalive) - time.time()
elif self.status == Link.STALE:
sleep_time = 0.001
@@ -543,7 +570,7 @@ class Link:
allowed = False
if not allow == RNS.Destination.ALLOW_NONE:
if allow == RNS.Destination.ALLOW_LIST:
if self.__remote_identity.hash in allowed_list:
if self.__remote_identity != None and self.__remote_identity.hash in allowed_list:
allowed = True
elif allow == RNS.Destination.ALLOW_ALL:
allowed = True
@@ -650,7 +677,7 @@ class Link:
self.__remote_identity = identity
if self.callbacks.remote_identified != None:
try:
self.callbacks.remote_identified(self.__remote_identity)
self.callbacks.remote_identified(self, self.__remote_identity)
except Exception as e:
RNS.log("Error while executing remote identified callback from "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
@@ -687,19 +714,21 @@ class Link:
if RNS.ResourceAdvertisement.is_request(packet):
RNS.Resource.accept(packet, callback=self.request_resource_concluded)
elif RNS.ResourceAdvertisement.is_response(packet):
request_id = RNS.ResourceAdvertisement.get_request_id(packet)
request_id = RNS.ResourceAdvertisement.read_request_id(packet)
for pending_request in self.pending_requests:
if pending_request.request_id == request_id:
RNS.Resource.accept(packet, callback=self.response_resource_concluded, progress_callback=pending_request.response_resource_progress, request_id = request_id)
pending_request.response_size = RNS.ResourceAdvertisement.get_size(packet)
pending_request.response_transfer_size = RNS.ResourceAdvertisement.get_transfer_size(packet)
pending_request.response_size = RNS.ResourceAdvertisement.read_size(packet)
pending_request.response_transfer_size = RNS.ResourceAdvertisement.read_transfer_size(packet)
pending_request.started_at = time.time()
elif self.resource_strategy == Link.ACCEPT_NONE:
pass
elif self.resource_strategy == Link.ACCEPT_APP:
if self.callbacks.resource != None:
try:
if self.callbacks.resource(resource):
resource_advertisement = RNS.ResourceAdvertisement.unpack(packet.plaintext)
resource_advertisement.link = self
if self.callbacks.resource(resource_advertisement):
RNS.Resource.accept(packet, self.callbacks.resource_concluded)
except Exception as e:
RNS.log("Error while executing resource accept callback from "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
@@ -812,6 +841,12 @@ class Link:
self.callbacks.link_established = callback
def set_link_closed_callback(self, callback):
"""
Registers a function to be called when a link has been
torn down.
:param callback: A function or method with the signature *callback(link)* to be called.
"""
self.callbacks.link_closed = callback
def set_packet_callback(self, callback):
@@ -830,7 +865,7 @@ class Link:
the resource will be accepted. If it returns *False* it will
be ignored.
:param callback: A function or method with the signature *callback(resource)* to be called.
:param callback: A function or method with the signature *callback(resource)* to be called. Please note that only the basic information of the resource is available at this time, such as *get_transfer_size()*, *get_data_size()*, *get_parts()* and *is_compressed()*.
"""
self.callbacks.resource = callback
@@ -857,7 +892,7 @@ class Link:
Registers a function to be called when an initiating peer has
identified over this link.
:param callback: A function or method with the signature *callback(identity)* to be called.
:param callback: A function or method with the signature *callback(link, identity)* to be called.
"""
self.callbacks.remote_identified = callback
@@ -920,7 +955,7 @@ class RequestReceipt():
RECEIVING = 0x03
READY = 0x04
def __init__(self, link, packet_receipt = None, resource = None, response_callback = None, failed_callback = None, progress_callback = None, timeout = None):
def __init__(self, link, packet_receipt = None, resource = None, response_callback = None, failed_callback = None, progress_callback = None, timeout = None, request_size = None):
self.packet_receipt = packet_receipt
self.resource = resource
self.started_at = None
@@ -936,6 +971,7 @@ class RequestReceipt():
self.link = link
self.request_id = self.hash
self.request_size = request_size
self.response = None
self.response_transfer_size = None
+62 -6
View File
@@ -526,8 +526,11 @@ class Resource:
RNS.log("Error while executing resource assembled callback from "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
try:
self.data.close()
if hasattr(self.data, "close") and callable(self.data.close):
self.data.close()
os.unlink(self.storagepath)
except Exception as e:
RNS.log("Error while cleaning up resource files, the contained exception was:", RNS.LOG_ERROR)
RNS.log(str(e))
@@ -564,7 +567,7 @@ class Resource:
else:
# Otherwise we'll recursively create the
# next segment of the resource
Resource(self.input_file, self.link, callback = self.callback, segment_index = self.segment_index+1, original_hash=self.original_hash)
Resource(self.input_file, self.link, callback = self.callback, segment_index = self.segment_index+1, original_hash=self.original_hash, progress_callback = self.__progress_callback)
else:
pass
else:
@@ -826,8 +829,44 @@ class Resource:
progress = self.processed_parts / self.progress_total_parts
return progress
def get_transfer_size(self):
"""
:returns: The number of bytes needed to transfer the resource.
"""
return self.size
def get_data_size(self):
"""
:returns: The total data size of the resource.
"""
return self.total_size
def get_parts(self):
"""
:returns: The number of parts the resource will be transferred in.
"""
return self.total_parts
def get_segments(self):
"""
:returns: The number of segments the resource is divided into.
"""
return self.total_segments
def get_hash(self):
"""
:returns: The hash of the resource.
"""
return self.hash
def is_compressed(self):
"""
:returns: Whether the resource is compressed.
"""
return self.compressed
def __str__(self):
return "<"+RNS.hexrep(self.hash)+"/"+RNS.hexrep(self.link.link_id)+">"
return "<"+RNS.hexrep(self.hash,delimit=False)+"/"+RNS.hexrep(self.link.link_id,delimit=False)+">"
class ResourceAdvertisement:
@@ -857,19 +896,19 @@ class ResourceAdvertisement:
@staticmethod
def get_request_id(advertisement_packet):
def read_request_id(advertisement_packet):
adv = ResourceAdvertisement.unpack(advertisement_packet.plaintext)
return adv.q
@staticmethod
def get_transfer_size(advertisement_packet):
def read_transfer_size(advertisement_packet):
adv = ResourceAdvertisement.unpack(advertisement_packet.plaintext)
return adv.t
@staticmethod
def get_size(advertisement_packet):
def read_size(advertisement_packet):
adv = ResourceAdvertisement.unpack(advertisement_packet.plaintext)
return adv.d
@@ -903,6 +942,23 @@ class ResourceAdvertisement:
# Flags
self.f = 0x00 | self.p << 4 | self.u << 3 | self.s << 2 | self.c << 1 | self.e
def get_transfer_size(self):
return self.t
def get_data_size(self):
return self.d
def get_parts(self):
return self.n
def get_segments(self):
return self.l
def get_hash(self):
return self.h
def is_compressed(self):
return self.c
def pack(self, segment=0):
hashmap_start = segment*ResourceAdvertisement.HASHMAP_MAX_LEN
+18 -4
View File
@@ -181,6 +181,7 @@ class Reticulum:
Reticulum.storagepath = Reticulum.configdir+"/storage"
Reticulum.cachepath = Reticulum.configdir+"/storage/cache"
Reticulum.resourcepath = Reticulum.configdir+"/storage/resources"
Reticulum.identitypath = Reticulum.configdir+"/storage/identities"
Reticulum.__transport_enabled = False
Reticulum.__use_implicit_proof = True
@@ -216,6 +217,9 @@ class Reticulum:
if not os.path.isdir(Reticulum.resourcepath):
os.makedirs(Reticulum.resourcepath)
if not os.path.isdir(Reticulum.identitypath):
os.makedirs(Reticulum.identitypath)
if os.path.isfile(self.configpath):
try:
self.config = ConfigObj(self.configpath)
@@ -354,6 +358,8 @@ class Reticulum:
interface_mode = Interface.Interface.MODE_ROAMING
elif c["interface_mode"] == "boundary":
interface_mode = Interface.Interface.MODE_BOUNDARY
elif c["mode"] == "gateway" or c["mode"] == "gw":
interface_mode = Interface.Interface.MODE_GATEWAY
elif "mode" in c:
c["mode"] = str(c["mode"]).lower()
@@ -367,6 +373,8 @@ class Reticulum:
interface_mode = Interface.Interface.MODE_ROAMING
elif c["mode"] == "boundary":
interface_mode = Interface.Interface.MODE_BOUNDARY
elif c["mode"] == "gateway" or c["mode"] == "gw":
interface_mode = Interface.Interface.MODE_GATEWAY
ifac_size = None
if "ifac_size" in c:
@@ -527,7 +535,7 @@ class Reticulum:
else:
interface.OUT = True
if interface_mode != Interface.Interface.MODE_FULL:
if interface_mode == Interface.Interface.MODE_ACCESS_POINT:
RNS.log(str(interface)+" does not support Access Point mode, reverting to default mode: Full", RNS.LOG_WARNING)
interface_mode = Interface.Interface.MODE_FULL
@@ -562,7 +570,7 @@ class Reticulum:
else:
interface.OUT = True
if interface_mode != Interface.Interface.MODE_FULL:
if interface_mode == Interface.Interface.MODE_ACCESS_POINT:
RNS.log(str(interface)+" does not support Access Point mode, reverting to default mode: Full", RNS.LOG_WARNING)
interface_mode = Interface.Interface.MODE_FULL
@@ -593,7 +601,7 @@ class Reticulum:
else:
interface.OUT = True
if interface_mode != Interface.Interface.MODE_FULL:
if interface_mode == Interface.Interface.MODE_ACCESS_POINT:
RNS.log(str(interface)+" does not support Access Point mode, reverting to default mode: Full", RNS.LOG_WARNING)
interface_mode = Interface.Interface.MODE_FULL
@@ -928,6 +936,12 @@ class Reticulum:
else:
ifstats["clients"] = None
if hasattr(interface, "i2p") and hasattr(interface, "connectable"):
if interface.connectable:
ifstats["i2p_connectable"] = True
else:
ifstats["i2p_connectable"] = False
if hasattr(interface, "b32"):
if interface.b32 != None:
ifstats["i2p_b32"] = interface.b32+".b32.i2p"
@@ -1197,6 +1211,6 @@ loglevel = 4
[[Default Interface]]
type = AutoInterface
interface_enabled = True
enabled = Yes
'''.splitlines()
+184 -23
View File
@@ -64,10 +64,11 @@ class Transport:
# various situations
LOCAL_REBROADCASTS_MAX = 2 # How many local rebroadcasts of an announce is allowed
PATH_REQUEST_GRACE = 0.35 # Grace time before a path announcement is made, allows directly reachable peers to respond first
PATH_REQUEST_RW = 2 # Path request random window
PATH_REQUEST_TIMEOUT = 15 # Default timuout for client path requests in seconds
PATH_REQUEST_GRACE = 0.35 # Grace time before a path announcement is made, allows directly reachable peers to respond first
PATH_REQUEST_RW = 2 # Path request random window
LINK_TIMEOUT = RNS.Link.KEEPALIVE * 2
LINK_TIMEOUT = RNS.Link.STALE_TIME * 1.25
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
@@ -92,6 +93,10 @@ class Transport:
announce_handlers = [] # A table storing externally registered announce handlers
tunnels = {} # A table storing tunnels to other transport instances
announce_rate_table = {} # A table for keeping track of announce rates
discovery_path_requests = {} # A table for keeping track of path requests on behalf of other nodes
discovery_pr_tags = [] # A table for keeping track of tagged path requests
max_pr_tags = 32000 # Maximum amount of unique path request tags to remember
# Transport control destinations are used
# for control purposes like path requests
@@ -279,6 +284,10 @@ class Transport:
def jobs():
outgoing = []
Transport.jobs_running = True
# TODO: Remove at some point
# start_time = time.time()
try:
if not Transport.jobs_locked:
# Process receipts list for timed-out packets
@@ -352,10 +361,14 @@ class Transport:
Transport.announces_last_checked = time.time()
# Cull the packet hashlist if it has reached max size
# Cull the packet hashlist if it has reached its max size
if len(Transport.packet_hashlist) > Transport.hashlist_maxsize:
Transport.packet_hashlist = Transport.packet_hashlist[len(Transport.packet_hashlist)-Transport.hashlist_maxsize:len(Transport.packet_hashlist)-1]
# Cull the path request tags list if it has reached its max size
if len(Transport.discovery_pr_tags) > Transport.max_pr_tags:
Transport.discovery_pr_tags = Transport.discovery_pr_tags[len(Transport.discovery_pr_tags)-Transport.max_pr_tags:len(Transport.discovery_pr_tags)-1]
if time.time() > Transport.tables_last_culled + Transport.tables_cull_interval:
# Cull the reverse table according to timeout
stale_reverse_entries = []
@@ -377,13 +390,29 @@ class Transport:
destination_entry = Transport.destination_table[destination_hash]
attached_interface = destination_entry[5]
if time.time() > destination_entry[0] + Transport.DESTINATION_TIMEOUT:
if attached_interface != None and hasattr(attached_interface, "mode") and attached_interface.mode == RNS.Interfaces.Interface.Interface.MODE_ACCESS_POINT:
destination_expiry = destination_entry[0] + Transport.AP_PATH_TIME
elif attached_interface != None and hasattr(attached_interface, "mode") and attached_interface.mode == RNS.Interfaces.Interface.Interface.MODE_ROAMING:
destination_expiry = destination_entry[0] + Transport.ROAMING_PATH_TIME
else:
destination_expiry = destination_entry[0] + Transport.DESTINATION_TIMEOUT
if time.time() > destination_expiry:
stale_paths.append(destination_hash)
RNS.log("Path to "+RNS.prettyhexrep(destination_hash)+" timed out and was removed", RNS.LOG_DEBUG)
elif not attached_interface in Transport.interfaces:
stale_paths.append(destination_hash)
RNS.log("Path to "+RNS.prettyhexrep(destination_hash)+" was removed since the attached interface no longer exists", RNS.LOG_DEBUG)
# Cull the pending discovery path requests table
stale_discovery_path_requests = []
for destination_hash in Transport.discovery_path_requests:
entry = Transport.discovery_path_requests[destination_hash]
if time.time() > entry["timeout"]:
stale_discovery_path_requests.append(destination_hash)
RNS.log("Waiting path request for "+RNS.prettyhexrep(destination_hash)+" timed out and was removed", RNS.LOG_DEBUG)
# Cull the tunnel table
stale_tunnels = []
ti = 0
@@ -452,6 +481,17 @@ class Transport:
else:
RNS.log("Removed "+str(i)+" paths", RNS.LOG_EXTREME)
i = 0
for destination_hash in stale_discovery_path_requests:
Transport.discovery_path_requests.pop(destination_hash)
i += 1
if i > 0:
if i == 1:
RNS.log("Removed "+str(i)+" waiting path request", RNS.LOG_EXTREME)
else:
RNS.log("Removed "+str(i)+" waiting path requests", RNS.LOG_EXTREME)
i = 0
for tunnel_id in stale_tunnels:
Transport.tunnels.pop(tunnel_id)
@@ -472,6 +512,13 @@ class Transport:
Transport.jobs_running = False
# TODO: Remove at some point
# end_time = time.time()
# if RNS.loglevel >= RNS.LOG_EXTREME:
# duration = round((end_time - start_time) * 1000, 2)
# if duration > 1:
# RNS.log("Transport jobs took "+str(duration)+"ms", RNS.LOG_EXTREME)
for packet in outgoing:
packet.send()
@@ -502,8 +549,10 @@ class Transport:
sleep(0.01)
Transport.jobs_locked = True
# TODO: This updateHash call might be redundant
packet.update_hash()
sent = False
outbound_time = time.time()
@@ -1207,6 +1256,7 @@ class Transport:
announce_context = RNS.Packet.NONE
announce_data = packet.data
# TODO: Shouldn't the context be PATH_RESPONSE in the first case here?
if Transport.from_local_client(packet) and packet.context == RNS.Packet.PATH_RESPONSE:
for local_interface in Transport.local_client_interfaces:
if packet.receiving_interface != local_interface:
@@ -1241,6 +1291,38 @@ class Transport:
new_announce.hops = packet.hops
new_announce.send()
# If we have any waiting discovery path requests
# for this destination, we retransmit to that
# interface immediately
if packet.destination_hash in Transport.discovery_path_requests:
pr_entry = Transport.discovery_path_requests[packet.destination_hash]
attached_interface = pr_entry["requesting_interface"]
interface_str = " on "+str(attached_interface)
RNS.log("Got matching announce, answering waiting discovery path request for "+RNS.prettyhexrep(packet.destination_hash)+interface_str, RNS.LOG_DEBUG)
announce_identity = RNS.Identity.recall(packet.destination_hash)
announce_destination = RNS.Destination(announce_identity, RNS.Destination.OUT, RNS.Destination.SINGLE, "unknown", "unknown");
announce_destination.hash = packet.destination_hash
announce_destination.hexhash = announce_destination.hash.hex()
announce_context = RNS.Packet.NONE
announce_data = packet.data
new_announce = RNS.Packet(
announce_destination,
announce_data,
RNS.Packet.ANNOUNCE,
context = RNS.Packet.PATH_RESPONSE,
header_type = RNS.Packet.HEADER_2,
transport_type = Transport.TRANSPORT,
transport_id = Transport.identity.hash,
attached_interface = attached_interface
)
new_announce.hops = packet.hops
new_announce.send()
destination_table_entry = [now, received_from, announce_hops, expires, random_blobs, packet.receiving_interface, packet]
Transport.destination_table[packet.destination_hash] = destination_table_entry
RNS.log("Destination "+RNS.prettyhexrep(packet.destination_hash)+" is now "+str(announce_hops)+" hops away via "+RNS.prettyhexrep(received_from)+" on "+str(packet.receiving_interface), RNS.LOG_DEBUG)
@@ -1323,7 +1405,7 @@ class Transport:
if packet.receiving_interface == link_entry[2]:
# TODO: Should we validate the LR proof at each transport
# step before transporting it?
RNS.log("Link request proof received on correct interface, transporting it via "+str(link_entry[4]), RNS.LOG_DEBUG)
# RNS.log("Link request proof received on correct interface, transporting it via "+str(link_entry[4]), RNS.LOG_EXTREME)
new_raw = packet.raw[0:1]
new_raw += struct.pack("!B", packet.hops)
new_raw += packet.raw[2:]
@@ -1685,7 +1767,7 @@ class Transport:
return False
@staticmethod
def request_path(destination_hash, on_interface=None):
def request_path(destination_hash, on_interface=None, tag=None, recursive=False):
"""
Requests a path to the destination from the network. If
another reachable peer on the network knows a path, it
@@ -1694,13 +1776,45 @@ class Transport:
:param destination_hash: A destination hash as *bytes*.
:param on_interface: If specified, the path request will only be sent on this interface. In normal use, Reticulum handles this automatically, and this parameter should not be used.
"""
if RNS.Reticulum.transport_enabled():
path_request_data = destination_hash+Transport.identity.hash+RNS.Identity.get_random_hash()
if tag == None:
request_tag = RNS.Identity.get_random_hash()
else:
path_request_data = destination_hash+RNS.Identity.get_random_hash()
request_tag = tag
if RNS.Reticulum.transport_enabled():
path_request_data = destination_hash+Transport.identity.hash+request_tag
else:
path_request_data = destination_hash+request_tag
path_request_dst = RNS.Destination(None, RNS.Destination.OUT, RNS.Destination.PLAIN, Transport.APP_NAME, "path", "request")
packet = RNS.Packet(path_request_dst, path_request_data, packet_type = RNS.Packet.DATA, transport_type = RNS.Transport.BROADCAST, header_type = RNS.Packet.HEADER_1, attached_interface = on_interface)
if on_interface != None and recursive:
if not hasattr(on_interface, "announce_cap"):
on_interface.announce_cap = RNS.Reticulum.ANNOUNCE_CAP
if not hasattr(on_interface, "announce_allowed_at"):
on_interface.announce_allowed_at = 0
if not hasattr(on_interface, "announce_queue"):
on_interface.announce_queue = []
queued_announces = True if len(on_interface.announce_queue) > 0 else False
if queued_announces:
# TODO: Reset to extra level, probably
RNS.log("Blocking recursive path request on "+str(on_interface)+" due to queued announces", RNS.LOG_DEBUG)
return
else:
now = time.time()
if now < on_interface.announce_allowed_at:
# TODO: Reset to extra level, probably
RNS.log("Blocking recursive path request on "+str(on_interface)+" due to active announce cap", RNS.LOG_DEBUG)
return
else:
tx_time = ((len(path_request_data)+RNS.Reticulum.HEADER_MINSIZE)*8) / on_interface.bitrate
wait_time = (tx_time / on_interface.announce_cap)
on_interface.announce_allowed_at = now + wait_time
packet.send()
@staticmethod
@@ -1710,8 +1824,9 @@ class Transport:
# hash in the packet, we assume those bytes are the
# destination being requested.
if len(data) >= RNS.Identity.TRUNCATED_HASHLENGTH//8:
# If there is also enough bytes for a trasport
# instance ID and at least one random byte, we
destination_hash = data[:RNS.Identity.TRUNCATED_HASHLENGTH//8]
# If there is also enough bytes for a transport
# instance ID and at least one tag byte, we
# assume the next bytes to be the trasport ID
# of the requesting transport instance.
if len(data) > (RNS.Identity.TRUNCATED_HASHLENGTH//8)*2:
@@ -1719,24 +1834,54 @@ class Transport:
else:
requesting_transport_instance = None
Transport.path_request(
data[:RNS.Identity.TRUNCATED_HASHLENGTH//8],
Transport.from_local_client(packet),
packet.receiving_interface,
requesting_transport_instance,
)
tag_bytes = None
if len(data) > (RNS.Identity.TRUNCATED_HASHLENGTH//8)*2:
tag_bytes = data[RNS.Identity.TRUNCATED_HASHLENGTH//8*2:]
elif len(data) > (RNS.Identity.TRUNCATED_HASHLENGTH//8):
tag_bytes = data[RNS.Identity.TRUNCATED_HASHLENGTH//8:]
if tag_bytes != None:
if len(tag_bytes) > RNS.Identity.TRUNCATED_HASHLENGTH//8:
tag_bytes = tag_bytes[:RNS.Identity.TRUNCATED_HASHLENGTH//8]
unique_tag = destination_hash+tag_bytes
if not unique_tag in Transport.discovery_pr_tags:
Transport.discovery_pr_tags.append(unique_tag)
Transport.path_request(
destination_hash,
Transport.from_local_client(packet),
packet.receiving_interface,
requestor_transport_id = requesting_transport_instance,
tag=tag_bytes
)
else:
# TODO: Reset this to debug level
RNS.log("Ignoring duplicate path request for "+RNS.prettyhexrep(destination_hash)+" with tag "+RNS.prettyhexrep(unique_tag), RNS.LOG_WARNING)
else:
# TODO: Reset this to debug level
RNS.log("Ignoring tagless path request for "+RNS.prettyhexrep(destination_hash), RNS.LOG_WARNING)
except Exception as e:
RNS.log("Error while handling path request. The contained exception was: "+str(e), RNS.LOG_ERROR)
@staticmethod
def path_request(destination_hash, is_from_local_client, attached_interface, requestor_transport_id=None):
def path_request(destination_hash, is_from_local_client, attached_interface, requestor_transport_id=None, tag=None):
should_search_for_unknown = False
if attached_interface != None:
if RNS.Reticulum.transport_enabled() and attached_interface.mode in RNS.Interfaces.Interface.Interface.DISCOVER_PATHS_FOR:
should_search_for_unknown = True
interface_str = " on "+str(attached_interface)
else:
interface_str = ""
# TODO: Clean
# RNS.log("Path request for "+RNS.prettyhexrep(destination_hash)+interface_str, RNS.LOG_DEBUG)
RNS.log("Path request for "+RNS.prettyhexrep(destination_hash)+interface_str, RNS.LOG_DEBUG)
destination_exists_on_local_client = False
if len(Transport.local_client_interfaces) > 0:
@@ -1764,7 +1909,7 @@ class Transport:
# path requests with transport instance keys is quite
# inefficient. There is probably a better way. Doing
# path invalidation here would decrease the network
# convergence time.
# convergence time. Maybe just drop it?
RNS.log("Not answering path request for "+RNS.prettyhexrep(destination_hash)+interface_str+", since next hop is the requestor", RNS.LOG_DEBUG)
else:
RNS.log("Answering path request for "+RNS.prettyhexrep(destination_hash)+interface_str+", path is known", RNS.LOG_DEBUG)
@@ -1801,12 +1946,28 @@ class Transport:
if not interface == attached_interface:
Transport.request_path(destination_hash, interface)
elif should_search_for_unknown:
if destination_hash in Transport.discovery_path_requests:
RNS.log("There is already a waiting path request for "+RNS.prettyhexrep(destination_hash)+" on behalf of path request"+interface_str, RNS.LOG_DEBUG)
else:
# Forward path request on all interfaces
# except the requestor interface
RNS.log("Attempting to discover unknown path to "+RNS.prettyhexrep(destination_hash)+" on behalf of path request"+interface_str, RNS.LOG_DEBUG)
pr_entry = { "destination_hash": destination_hash, "timeout": time.time()+Transport.PATH_REQUEST_TIMEOUT, "requesting_interface": attached_interface }
Transport.discovery_path_requests[destination_hash] = pr_entry
for interface in Transport.interfaces:
if not interface == attached_interface:
# Use the previously extracted tag from this path request
# on the new path requests as well, to avoid potential loops
Transport.request_path(destination_hash, on_interface=interface, tag=tag, recursive=True)
elif not is_from_local_client and len(Transport.local_client_interfaces) > 0:
# Forward the path request on all local
# client interfaces
RNS.log("Forwarding path request for "+RNS.prettyhexrep(destination_hash)+interface_str+" to local clients", RNS.LOG_DEBUG)
for interface in Transport.local_client_interfaces:
Transport.request_path(destination_hash, interface)
Transport.request_path(destination_hash, on_interface=interface)
else:
RNS.log("Ignoring path request for "+RNS.prettyhexrep(destination_hash)+interface_str+", no path known", RNS.LOG_DEBUG)
+381
View File
@@ -0,0 +1,381 @@
#!/usr/bin/env python3
# MIT License
#
# Copyright (c) 2016-2022 Mark Qvist / unsigned.io
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import RNS
import argparse
import time
import sys
import os
from RNS._version import __version__
APP_NAME = "rncp"
allow_all = False
allowed_identity_hashes = []
def receive(configdir, verbosity = 0, quietness = 0, allowed = [], display_identity = False, limit = None, disable_auth = None,disable_announce=False):
global allow_all, allowed_identity_hashes
identity = None
targetloglevel = 3+verbosity-quietness
reticulum = RNS.Reticulum(configdir=configdir, loglevel=targetloglevel)
identity_path = RNS.Reticulum.identitypath+"/"+APP_NAME
if os.path.isfile(identity_path):
identity = RNS.Identity.from_file(identity_path)
if identity == None:
RNS.log("No valid saved identity found, creating new...", RNS.LOG_INFO)
identity = RNS.Identity()
identity.to_file(identity_path)
destination = RNS.Destination(identity, RNS.Destination.IN, RNS.Destination.SINGLE, APP_NAME, "receive")
if display_identity:
print("Identity : "+str(identity))
print("Receiving on : "+RNS.prettyhexrep(destination.hash))
exit(0)
if disable_auth:
allow_all = True
else:
if allowed != None:
for a in allowed:
try:
dest_len = (RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2
if len(a) != dest_len:
raise ValueError("Allowed destination length is invalid, must be {hex} hexadecimal characters ({byte} bytes).".format(hex=dest_len, byte=dest_len//2))
try:
destination_hash = bytes.fromhex(a)
allowed_identity_hashes.append(destination_hash)
except Exception as e:
raise ValueError("Invalid destination entered. Check your input.")
except Exception as e:
print(str(e))
exit(1)
if len(allowed_identity_hashes) < 1 and not disable_auth:
print("Warning: No allowed identities configured, rncp will not accept any files!")
destination.set_link_established_callback(receive_link_established)
print("rncp ready to receive on "+RNS.prettyhexrep(destination.hash))
if not disable_announce:
destination.announce()
while True:
time.sleep(1)
def receive_link_established(link):
RNS.log("Incoming link established", RNS.LOG_VERBOSE)
link.set_remote_identified_callback(receive_sender_identified)
link.set_resource_strategy(RNS.Link.ACCEPT_APP)
link.set_resource_callback(receive_resource_callback)
link.set_resource_started_callback(receive_resource_started)
link.set_resource_concluded_callback(receive_resource_concluded)
def receive_sender_identified(link, identity):
if identity.hash in allowed_identity_hashes:
RNS.log("Authenticated sender", RNS.LOG_VERBOSE)
else:
RNS.log("Sender not allowed, tearing down link", RNS.LOG_VERBOSE)
link.teardown()
def receive_resource_callback(resource):
sender_identity = resource.link.get_remote_identity()
if sender_identity != None:
if sender_identity.hash in allowed_identity_hashes:
print("Allowing sender")
return True
print("Rejecting sender")
return False
def receive_resource_started(resource):
if resource.link.get_remote_identity():
id_str = " from "+RNS.prettyhexrep(resource.link.get_remote_identity().hash)
else:
id_str = ""
print("Starting resource transfer "+RNS.prettyhexrep(resource.hash)+id_str)
def receive_resource_concluded(resource):
if resource.status == RNS.Resource.COMPLETE:
print(str(resource)+" completed")
if resource.total_size > 4:
filename_len = int.from_bytes(resource.data.read(2), "big")
filename = resource.data.read(filename_len).decode("utf-8")
counter = 0
saved_filename = filename
while os.path.isfile(saved_filename):
counter += 1
saved_filename = filename+"."+str(counter)
file = open(saved_filename, "wb")
file.write(resource.data.read())
file.close()
else:
print("Invalid data received, ignoring resource")
else:
print("Resource failed")
resource_done = False
current_resource = None
stats = []
speed = 0.0
def sender_progress(resource):
stats_max = 32
global current_resource, stats, speed, resource_done
current_resource = resource
now = time.time()
got = current_resource.get_progress()*current_resource.total_size
entry = [now, got]
stats.append(entry)
while len(stats) > stats_max:
stats.pop(0)
span = now - stats[0][0]
if span == 0:
speed = 0
else:
diff = got - stats[0][1]
speed = diff/span
if resource.status < RNS.Resource.COMPLETE:
resource_done = False
else:
resource_done = True
link = None
def send(configdir, verbosity = 0, quietness = 0, destination = None, file = None, timeout = RNS.Transport.PATH_REQUEST_TIMEOUT):
global current_resource, resource_done, link, speed
from tempfile import TemporaryFile
targetloglevel = 3+verbosity-quietness
try:
dest_len = (RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2
if len(destination) != dest_len:
raise ValueError("Allowed destination length is invalid, must be {hex} hexadecimal characters ({byte} bytes).".format(hex=dest_len, byte=dest_len//2))
try:
destination_hash = bytes.fromhex(destination)
except Exception as e:
raise ValueError("Invalid destination entered. Check your input.")
except Exception as e:
print(str(e))
exit(1)
file_path = os.path.expanduser(file)
if not os.path.isfile(file_path):
print("File not found")
exit(1)
temp_file = TemporaryFile()
real_file = open(file_path, "rb")
filename_bytes = os.path.basename(file_path).encode("utf-8")
filename_len = len(filename_bytes)
if filename_len > 0xFFFF:
print("Filename exceeds max size, cannot send")
exit(1)
else:
print("Preparing file...", end=" ")
temp_file.write(filename_len.to_bytes(2, "big"))
temp_file.write(filename_bytes)
temp_file.write(real_file.read())
temp_file.seek(0)
print("\r \r", end="")
reticulum = RNS.Reticulum(configdir=configdir, loglevel=targetloglevel)
identity_path = RNS.Reticulum.identitypath+"/"+APP_NAME
if os.path.isfile(identity_path):
identity = RNS.Identity.from_file(identity_path)
if identity == None:
RNS.log("No valid saved identity found, creating new...", RNS.LOG_INFO)
identity = RNS.Identity()
identity.to_file(identity_path)
if not RNS.Transport.has_path(destination_hash):
RNS.Transport.request_path(destination_hash)
print("Path to "+RNS.prettyhexrep(destination_hash)+" requested ", end=" ")
sys.stdout.flush()
i = 0
syms = "⢄⢂⢁⡁⡈⡐⡠"
estab_timeout = time.time()+timeout
while not RNS.Transport.has_path(destination_hash) and time.time() < estab_timeout:
time.sleep(0.1)
print(("\b\b"+syms[i]+" "), end="")
sys.stdout.flush()
i = (i+1)%len(syms)
if not RNS.Transport.has_path(destination_hash):
print("\r \rPath not found")
exit(1)
else:
print("\r \rEstablishing link with "+RNS.prettyhexrep(destination_hash)+" ", end=" ")
receiver_identity = RNS.Identity.recall(destination_hash)
receiver_destination = RNS.Destination(
receiver_identity,
RNS.Destination.OUT,
RNS.Destination.SINGLE,
APP_NAME,
"receive"
)
link = RNS.Link(receiver_destination)
while link.status != RNS.Link.ACTIVE and time.time() < estab_timeout:
time.sleep(0.1)
print(("\b\b"+syms[i]+" "), end="")
sys.stdout.flush()
i = (i+1)%len(syms)
if not RNS.Transport.has_path(destination_hash):
print("\r \rCould not establish link with "+RNS.prettyhexrep(destination_hash))
exit(1)
else:
print("\r \rAdvertising file resource ", end=" ")
link.identify(identity)
resource = RNS.Resource(temp_file, link, callback = sender_progress, progress_callback = sender_progress)
current_resource = resource
while resource.status < RNS.Resource.TRANSFERRING:
time.sleep(0.1)
print(("\b\b"+syms[i]+" "), end="")
sys.stdout.flush()
i = (i+1)%len(syms)
if resource.status > RNS.Resource.COMPLETE:
print("\r \rFile was not accepted by "+RNS.prettyhexrep(destination_hash))
exit(1)
else:
print("\r \rTransferring file ", end=" ")
while not resource_done:
time.sleep(0.1)
prg = current_resource.get_progress()
percent = round(prg * 100.0, 1)
stat_str = str(percent)+"% - " + size_str(int(prg*current_resource.total_size)) + " of " + size_str(current_resource.total_size) + " - " +size_str(speed, "b")+"ps"
print("\r \rTransferring file "+syms[i]+" "+stat_str, end=" ")
sys.stdout.flush()
i = (i+1)%len(syms)
if current_resource.status != RNS.Resource.COMPLETE:
print("\r \rThe transfer failed")
exit(1)
else:
print("\r \r"+str(file_path)+" copied to "+RNS.prettyhexrep(destination_hash))
link.teardown()
time.sleep(0.25)
real_file.close()
temp_file.close()
exit(0)
def main():
try:
parser = argparse.ArgumentParser(description="Reticulum File Transfer Utility")
parser.add_argument("file", nargs="?", default=None, help="file to be transferred", type=str)
parser.add_argument("destination", nargs="?", default=None, help="hexadecimal hash of the receiver", type=str)
parser.add_argument("--config", metavar="path", action="store", default=None, help="path to alternative Reticulum config directory", type=str)
parser.add_argument('-v', '--verbose', action='count', default=0, help="increase verbosity")
parser.add_argument('-q', '--quiet', action='count', default=0, help="decrease verbosity")
parser.add_argument('-p', '--print-identity', action='store_true', default=False, help="print identity and destination info and exit")
parser.add_argument("-r", '--receive', action='store_true', default=False, help="wait for incoming files")
parser.add_argument("-b", '--no-announce', action='store_true', default=False, help="don't announce at program start")
parser.add_argument('-a', metavar="allowed_hash", dest="allowed", action='append', help="accept from this identity", type=str)
parser.add_argument('-n', '--no-auth', action='store_true', default=False, help="accept files from anyone")
parser.add_argument("-w", action="store", metavar="seconds", type=float, help="sender timeout before giving up", default=RNS.Transport.PATH_REQUEST_TIMEOUT)
# parser.add_argument("--limit", action="store", metavar="files", type=float, help="maximum number of files to accept", default=None)
parser.add_argument("--version", action="version", version="rncp {version}".format(version=__version__))
args = parser.parse_args()
if args.receive or args.print_identity:
receive(
configdir = args.config,
verbosity=args.verbose,
quietness=args.quiet,
allowed = args.allowed,
display_identity=args.print_identity,
# limit=args.limit,
disable_auth=args.no_auth,
disable_announce=args.no_announce,
)
elif args.destination != None and args.file != None:
send(
configdir = args.config,
verbosity = args.verbose,
quietness = args.quiet,
destination = args.destination,
file = args.file,
timeout = args.w,
)
else:
print("")
parser.print_help()
print("")
except KeyboardInterrupt:
print("")
if resource != None:
resource.cancel()
if link != None:
link.teardown()
exit()
def size_str(num, suffix='B'):
units = ['','K','M','G','T','P','E','Z']
last_unit = 'Y'
if suffix == 'b':
num *= 8
units = ['','K','M','G','T','P','E','Z']
last_unit = 'Y'
for unit in units:
if abs(num) < 1000.0:
if unit == "":
return "%.0f %s%s" % (num, unit, suffix)
else:
return "%.2f %s%s" % (num, unit, suffix)
num /= 1000.0
return "%.2f%s%s" % (num, last_unit, suffix)
if __name__ == "__main__":
main()
+2 -2
View File
@@ -257,7 +257,7 @@ def main():
metavar="seconds",
type=float,
help="timeout before giving up",
default=15
default=RNS.Transport.PATH_REQUEST_TIMEOUT
)
parser.add_argument(
@@ -321,7 +321,7 @@ def pretty_date(time=False):
if second_diff < 3600:
return str(int(second_diff / 60)) + " minutes"
if second_diff < 7200:
return "an hour ago"
return "an hour"
if second_diff < 86400:
return str(int(second_diff / 3600)) + " hours"
if day_diff == 1:
+9 -9
View File
@@ -147,7 +147,7 @@ loglevel = 4
[[Default Interface]]
type = AutoInterface
interface_enabled = True
enabled = yes
# The following example enables communication with other
@@ -155,7 +155,7 @@ loglevel = 4
[[UDP Interface]]
type = UDPInterface
interface_enabled = False
enabled = no
listen_ip = 0.0.0.0
listen_port = 4242
forward_ip = 255.255.255.255
@@ -198,7 +198,7 @@ loglevel = 4
[[TCP Server Interface]]
type = TCPServerInterface
interface_enabled = False
enabled = no
# This configuration will listen on all IP
# interfaces on port 4242
@@ -224,7 +224,7 @@ loglevel = 4
[[TCP Client Interface]]
type = TCPClientInterface
interface_enabled = False
enabled = no
target_host = 127.0.0.1
target_port = 4242
@@ -237,9 +237,9 @@ loglevel = 4
[[I2P]]
type = I2PInterface
interface_enabled = yes
enabled = no
connectable = yes
peers = 5urvjicpzi7q3ybztsef4i5ow2aq4soktfj7zedz53s47r54jnqq.b32.i2p
peers = ykzlw5ujbaqc2xkec4cpvgyxj257wcrmmgkuxqmqcur7cq3w3lha.b32.i2p
# Here's an example of how to add a LoRa interface
@@ -249,7 +249,7 @@ loglevel = 4
type = RNodeInterface
# Enable interface if you want use it!
interface_enabled = False
enabled = no
# Serial port for the device
port = /dev/ttyUSB0
@@ -301,7 +301,7 @@ loglevel = 4
type = KISSInterface
# Enable interface if you want use it!
interface_enabled = False
enabled = no
# Serial port for the device
port = /dev/ttyUSB1
@@ -372,7 +372,7 @@ loglevel = 4
ssid = 0
# Enable interface if you want use it!
interface_enabled = False
enabled = no
# Serial port for the device
port = /dev/ttyUSB2
+76 -56
View File
@@ -59,79 +59,99 @@ def program_setup(configdir, dispall=False, verbosity = 0):
for ifstat in stats["interfaces"]:
name = ifstat["name"]
if dispall or not (name.startswith("LocalInterface[") or name.startswith("TCPInterface[Client")):
print("")
if dispall or not (
name.startswith("LocalInterface[") or
name.startswith("TCPInterface[Client") or
name.startswith("I2PInterfacePeer[Connected peer") or
(name.startswith("I2PInterface[") and ("i2p_connectable" in ifstat and ifstat["i2p_connectable"] == False))
):
if ifstat["status"]:
ss = "Up"
else:
ss = "Down"
if not (name.startswith("I2PInterface[") and ("i2p_connectable" in ifstat and ifstat["i2p_connectable"] == False)):
print("")
if ifstat["mode"] == RNS.Interfaces.Interface.Interface.MODE_ACCESS_POINT:
modestr = "Access Point"
elif ifstat["mode"] == RNS.Interfaces.Interface.Interface.MODE_POINT_TO_POINT:
modestr = "Point-to-Point"
elif ifstat["mode"] == RNS.Interfaces.Interface.Interface.MODE_ROAMING:
modestr = "Roaming"
elif ifstat["mode"] == RNS.Interfaces.Interface.Interface.MODE_BOUNDARY:
modestr = "Boundary"
else:
modestr = "Full"
if ifstat["status"]:
ss = "Up"
else:
ss = "Down"
if ifstat["mode"] == RNS.Interfaces.Interface.Interface.MODE_ACCESS_POINT:
modestr = "Access Point"
elif ifstat["mode"] == RNS.Interfaces.Interface.Interface.MODE_POINT_TO_POINT:
modestr = "Point-to-Point"
elif ifstat["mode"] == RNS.Interfaces.Interface.Interface.MODE_ROAMING:
modestr = "Roaming"
elif ifstat["mode"] == RNS.Interfaces.Interface.Interface.MODE_BOUNDARY:
modestr = "Boundary"
elif ifstat["mode"] == RNS.Interfaces.Interface.Interface.MODE_GATEWAY:
modestr = "Gateway"
else:
modestr = "Full"
if ifstat["clients"] != None:
clients = ifstat["clients"]
if name.startswith("Shared Instance["):
cnum = max(clients-1,0)
if cnum == 1:
spec_str = " program"
if ifstat["clients"] != None:
clients = ifstat["clients"]
if name.startswith("Shared Instance["):
cnum = max(clients-1,0)
if cnum == 1:
spec_str = " program"
else:
spec_str = " programs"
clients_string = "Serving : "+str(cnum)+spec_str
elif name.startswith("I2PInterface["):
if "i2p_connectable" in ifstat and ifstat["i2p_connectable"] == True:
cnum = clients
if cnum == 1:
spec_str = " connected I2P endpoint"
else:
spec_str = " connected I2P endpoints"
clients_string = "Peers : "+str(cnum)+spec_str
else:
clients_string = ""
else:
spec_str = " programs"
clients_string = "Clients : "+str(clients)
clients_string = "Serving : "+str(cnum)+spec_str
else:
clients_string = "Clients : "+str(clients)
clients = None
else:
clients = None
print(" {n}".format(n=ifstat["name"]))
print(" {n}".format(n=ifstat["name"]))
if "ifac_netname" in ifstat and ifstat["ifac_netname"] != None:
print(" Network : {nn}".format(nn=ifstat["ifac_netname"]))
if "ifac_netname" in ifstat and ifstat["ifac_netname"] != None:
print(" Network : {nn}".format(nn=ifstat["ifac_netname"]))
print(" Status : {ss}".format(ss=ss))
print(" Status : {ss}".format(ss=ss))
if clients != None and clients_string != "":
print(" "+clients_string)
if clients != None:
print(" "+clients_string)
if not (name.startswith("Shared Instance[") or name.startswith("TCPInterface[Client") or name.startswith("LocalInterface[")):
print(" Mode : {mode}".format(mode=modestr))
if not (name.startswith("Shared Instance[") or name.startswith("TCPInterface[Client") or name.startswith("LocalInterface[")):
print(" Mode : {mode}".format(mode=modestr))
if "bitrate" in ifstat and ifstat["bitrate"] != None:
print(" Rate : {ss}".format(ss=speed_str(ifstat["bitrate"])))
if "peers" in ifstat and ifstat["peers"] != None:
print(" Peers : {np} reachable".format(np=ifstat["peers"]))
if "bitrate" in ifstat and ifstat["bitrate"] != None:
print(" Rate : {ss}".format(ss=speed_str(ifstat["bitrate"])))
if "peers" in ifstat and ifstat["peers"] != None:
print(" Peers : {np} reachable".format(np=ifstat["peers"]))
if "ifac_signature" in ifstat and ifstat["ifac_signature"] != None:
sigstr = "<…"+RNS.hexrep(ifstat["ifac_signature"][-5:], delimit=False)+">"
print(" Access : {nb}-bit IFAC by {sig}".format(nb=ifstat["ifac_size"]*8, sig=sigstr))
if "i2p_b32" in ifstat and ifstat["i2p_b32"] != None:
print(" I2P B32 : {ep}".format(ep=str(ifstat["i2p_b32"])))
if "ifac_signature" in ifstat and ifstat["ifac_signature"] != None:
sigstr = "<…"+RNS.hexrep(ifstat["ifac_signature"][-5:], delimit=False)+">"
print(" Access : {nb}-bit IFAC by {sig}".format(nb=ifstat["ifac_size"]*8, sig=sigstr))
if "i2p_b32" in ifstat and ifstat["i2p_b32"] != None:
print(" I2P B32 : {ep}".format(ep=str(ifstat["i2p_b32"])))
if "announce_queue" in ifstat and ifstat["announce_queue"] != None and ifstat["announce_queue"] > 0:
aqn = ifstat["announce_queue"]
if aqn == 1:
print(" Queued : {np} announce".format(np=aqn))
else:
print(" Queued : {np} announces".format(np=aqn))
print(" Traffic : {txb}\n {rxb}".format(rxb=size_str(ifstat["rxb"]), txb=size_str(ifstat["txb"])))
if "announce_queue" in ifstat and ifstat["announce_queue"] != None and ifstat["announce_queue"] > 0:
aqn = ifstat["announce_queue"]
if aqn == 1:
print(" Queued : {np} announce".format(np=aqn))
else:
print(" Queued : {np} announces".format(np=aqn))
print(" Traffic : {txb}\n {rxb}".format(rxb=size_str(ifstat["rxb"]), txb=size_str(ifstat["txb"])))
if "transport_id" in stats and stats["transport_id"] != None:
print("\n Reticulum Transport Instance "+RNS.prettyhexrep(stats["transport_id"])+" running")
print("\n Reticulum Transport Instance "+RNS.prettyhexrep(stats["transport_id"])+" is running")
print("")
+697
View File
@@ -0,0 +1,697 @@
#!/usr/bin/env python3
# MIT License
#
# Copyright (c) 2016-2022 Mark Qvist / unsigned.io
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import RNS
import subprocess
import argparse
import shlex
import time
import sys
import tty
import os
from RNS._version import __version__
APP_NAME = "rnx"
identity = None
reticulum = None
allow_all = False
allowed_identity_hashes = []
def prepare_identity(identity_path):
global identity
if identity_path == None:
identity_path = RNS.Reticulum.identitypath+"/"+APP_NAME
if os.path.isfile(identity_path):
identity = RNS.Identity.from_file(identity_path)
if identity == None:
RNS.log("No valid saved identity found, creating new...", RNS.LOG_INFO)
identity = RNS.Identity()
identity.to_file(identity_path)
def listen(configdir, identitypath = None, verbosity = 0, quietness = 0, allowed = [], print_identity = False, disable_auth = None, disable_announce=False):
global identity, allow_all, allowed_identity_hashes, reticulum
targetloglevel = 3+verbosity-quietness
reticulum = RNS.Reticulum(configdir=configdir, loglevel=targetloglevel)
prepare_identity(identitypath)
destination = RNS.Destination(identity, RNS.Destination.IN, RNS.Destination.SINGLE, APP_NAME, "execute")
if print_identity:
print("Identity : "+str(identity))
print("Listening on : "+RNS.prettyhexrep(destination.hash))
exit(0)
if disable_auth:
allow_all = True
else:
if allowed != None:
for a in allowed:
try:
dest_len = (RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2
if len(a) != dest_len:
raise ValueError("Allowed destination length is invalid, must be {hex} hexadecimal characters ({byte} bytes).".format(hex=dest_len, byte=dest_len//2))
try:
destination_hash = bytes.fromhex(a)
allowed_identity_hashes.append(destination_hash)
except Exception as e:
raise ValueError("Invalid destination entered. Check your input.")
except Exception as e:
print(str(e))
exit(1)
if len(allowed_identity_hashes) < 1 and not disable_auth:
print("Warning: No allowed identities configured, rncx will not accept any commands!")
destination.set_link_established_callback(command_link_established)
if not allow_all:
destination.register_request_handler(
path = "command",
response_generator = execute_received_command,
allow = RNS.Destination.ALLOW_LIST,
allowed_list = allowed_identity_hashes
)
else:
destination.register_request_handler(
path = "command",
response_generator = execute_received_command,
allow = RNS.Destination.ALLOW_ALL,
)
RNS.log("rnx listening for commands on "+RNS.prettyhexrep(destination.hash))
if not disable_announce:
destination.announce()
while True:
time.sleep(1)
def command_link_established(link):
link.set_remote_identified_callback(initiator_identified)
link.set_link_closed_callback(command_link_closed)
RNS.log("Command link "+str(link)+" established")
def command_link_closed(link):
RNS.log("Command link "+str(link)+" closed")
def initiator_identified(link, identity):
global allow_all
RNS.log("Initiator of link "+str(link)+" identified as "+RNS.prettyhexrep(identity.hash))
if not allow_all and not identity.hash in allowed_identity_hashes:
RNS.log("Identity "+RNS.prettyhexrep(identity.hash)+" not allowed, tearing down link")
link.teardown()
def execute_received_command(path, data, request_id, remote_identity, requested_at):
command = data[0].decode("utf-8") # Command to execute
timeout = data[1] # Timeout in seconds
o_limit = data[2] # Size limit for stdout
e_limit = data[3] # Size limit for stderr
stdin = data[4] # Data passed to stdin
if remote_identity != None:
RNS.log("Executing command ["+command+"] for "+RNS.prettyhexrep(remote_identity.hash))
else:
RNS.log("Executing command ["+command+"] for unknown requestor")
result = [
False, # 0: Command was executed
None, # 1: Return value
None, # 2: Stdout
None, # 3: Stderr
None, # 4: Total stdout length
None, # 5: Total stderr length
time.time(), # 6: Started
None, # 7: Concluded
]
try:
process = subprocess.Popen(shlex.split(command), stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
result[0] = True
except Exception as e:
result[0] = False
return result
stdout = b""
stderr = b""
timed_out = False
if stdin != None:
process.stdin.write(stdin)
while True:
try:
stdout, stderr = process.communicate(timeout=1)
if process.poll() != None:
break
if len(stdout) > 0:
print(str(stdout))
sys.stdout.flush()
except subprocess.TimeoutExpired:
pass
if timeout != None and time.time() > result[6]+timeout:
RNS.log("Command ["+command+"] timed out and is being killed...")
process.terminate()
process.wait()
if process.poll() != None:
stdout, stderr = process.communicate()
else:
stdout = None
stderr = None
break
if timeout != None and time.time() < result[6]+timeout:
result[7] = time.time()
# Deliver result
result[1] = process.returncode
if o_limit != None and len(stdout) > o_limit:
if o_limit == 0:
result[2] = b""
else:
result[2] = stdout[0:o_limit]
else:
result[2] = stdout
if e_limit != None and len(stderr) > e_limit:
if e_limit == 0:
result[3] = b""
else:
result[3] = stderr[0:e_limit]
else:
result[3] = stderr
result[4] = len(stdout)
result[5] = len(stderr)
if timed_out:
RNS.log("Command timed out")
return result
if remote_identity != None:
RNS.log("Delivering result of command ["+str(command)+"] to "+RNS.prettyhexrep(remote_identity.hash))
else:
RNS.log("Delivering result of command ["+str(command)+"] to unknown requestor")
return result
def spin(until=None, msg=None, timeout=None):
i = 0
syms = "⢄⢂⢁⡁⡈⡐⡠"
if timeout != None:
timeout = time.time()+timeout
print(msg+" ", end=" ")
while (timeout == None or time.time()<timeout) and not until():
time.sleep(0.1)
print(("\b\b"+syms[i]+" "), end="")
sys.stdout.flush()
i = (i+1)%len(syms)
print("\r"+" "*len(msg)+" \r", end="")
if timeout != None and time.time() > timeout:
return False
else:
return True
current_progress = 0.0
stats = []
speed = 0.0
def spin_stat(until=None, timeout=None):
global current_progress, response_transfer_size, speed
i = 0
syms = "⢄⢂⢁⡁⡈⡐⡠"
if timeout != None:
timeout = time.time()+timeout
while (timeout == None or time.time()<timeout) and not until():
time.sleep(0.1)
prg = current_progress
percent = round(prg * 100.0, 1)
stat_str = str(percent)+"% - " + size_str(int(prg*response_transfer_size)) + " of " + size_str(response_transfer_size) + " - " +size_str(speed, "b")+"ps"
print("\r \rReceiving result "+syms[i]+" "+stat_str, end=" ")
sys.stdout.flush()
i = (i+1)%len(syms)
print("\r \r", end="")
if timeout != None and time.time() > timeout:
return False
else:
return True
def remote_execution_done(request_receipt):
pass
def remote_execution_progress(request_receipt):
stats_max = 32
global current_progress, response_transfer_size, speed
current_progress = request_receipt.progress
response_transfer_size = request_receipt.response_transfer_size
now = time.time()
got = current_progress*response_transfer_size
entry = [now, got]
stats.append(entry)
while len(stats) > stats_max:
stats.pop(0)
span = now - stats[0][0]
if span == 0:
speed = 0
else:
diff = got - stats[0][1]
speed = diff/span
link = None
listener_destination = None
remote_exec_grace = 2.0
def execute(configdir, identitypath = None, verbosity = 0, quietness = 0, detailed = False, mirror = False, noid = False, destination = None, command = None, stdin = None, stdoutl = None, stderrl = None, timeout = RNS.Transport.PATH_REQUEST_TIMEOUT, result_timeout = None, interactive = False):
global identity, reticulum, link, listener_destination, remote_exec_grace
try:
dest_len = (RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2
if len(destination) != dest_len:
raise ValueError("Allowed destination length is invalid, must be {hex} hexadecimal characters ({byte} bytes).".format(hex=dest_len, byte=dest_len//2))
try:
destination_hash = bytes.fromhex(destination)
except Exception as e:
raise ValueError("Invalid destination entered. Check your input.")
except Exception as e:
print(str(e))
exit(241)
if reticulum == None:
targetloglevel = 3+verbosity-quietness
reticulum = RNS.Reticulum(configdir=configdir, loglevel=targetloglevel)
if identity == None:
prepare_identity(identitypath)
if not RNS.Transport.has_path(destination_hash):
RNS.Transport.request_path(destination_hash)
if not spin(until=lambda: RNS.Transport.has_path(destination_hash), msg="Path to "+RNS.prettyhexrep(destination_hash)+" requested", timeout=timeout):
print("Path not found")
exit(242)
if listener_destination == None:
listener_identity = RNS.Identity.recall(destination_hash)
listener_destination = RNS.Destination(
listener_identity,
RNS.Destination.OUT,
RNS.Destination.SINGLE,
APP_NAME,
"execute"
)
if link == None or link.status == RNS.Link.CLOSED or link.status == RNS.Link.PENDING:
link = RNS.Link(listener_destination)
if not spin(until=lambda: link.status == RNS.Link.ACTIVE, msg="Establishing link with "+RNS.prettyhexrep(destination_hash), timeout=timeout):
print("Could not establish link with "+RNS.prettyhexrep(destination_hash))
exit(243)
if not noid:
link.identify(identity)
if stdin != None:
stdin = stdin.encode("utf-8")
request_data = [
command.encode("utf-8"), # Command to execute
timeout, # Timeout in seconds
stdoutl, # Size limit for stdout
stderrl, # Size limit for stderr
stdin, # Data passed to stdin
]
# TODO: Tune
rexec_timeout = timeout+link.rtt*4+remote_exec_grace
request_receipt = link.request(
path="command",
data=request_data,
response_callback=remote_execution_done,
failed_callback=remote_execution_done,
progress_callback=remote_execution_progress,
timeout=rexec_timeout
)
spin(
until=lambda:link.status == RNS.Link.CLOSED or (request_receipt.status != RNS.RequestReceipt.FAILED and request_receipt.status != RNS.RequestReceipt.SENT),
msg="Sending execution request",
timeout=rexec_timeout+0.5
)
if link.status == RNS.Link.CLOSED:
print("Could not request remote execution, link was closed")
exit(244)
if request_receipt.status == RNS.RequestReceipt.FAILED:
print("Could not request remote execution")
exit(244)
spin(
until=lambda:request_receipt.status != RNS.RequestReceipt.DELIVERED,
msg="Command delivered, awaiting result",
timeout=timeout
)
if request_receipt.status == RNS.RequestReceipt.FAILED:
print("No result was received")
exit(245)
spin_stat(
until=lambda:request_receipt.status != RNS.RequestReceipt.RECEIVING,
timeout=result_timeout
)
if request_receipt.status == RNS.RequestReceipt.FAILED:
print("Receiving result failed")
exit(246)
if request_receipt.response != None:
try:
executed = request_receipt.response[0]
retval = request_receipt.response[1]
stdout = request_receipt.response[2]
stderr = request_receipt.response[3]
outlen = request_receipt.response[4]
errlen = request_receipt.response[5]
started = request_receipt.response[6]
concluded = request_receipt.response[7]
except Exception as e:
print("Received invalid result")
exit(247)
if executed:
if detailed:
if stdout != None and len(stdout) > 0:
print(stdout.decode("utf-8"), end="")
if stderr != None and len(stderr) > 0:
print(stderr.decode("utf-8"), file=sys.stderr, end="")
sys.stdout.flush()
sys.stderr.flush()
print("\n--- End of remote output, rnx done ---")
if started != None and concluded != None:
cmd_duration = round(concluded - started, 3)
print("Remote command execution took "+str(cmd_duration)+" seconds")
total_size = request_receipt.response_size
if request_receipt.request_size != None:
total_size += request_receipt.request_size
transfer_duration = round(request_receipt.response_concluded_at - request_receipt.sent_at - cmd_duration, 3)
if transfer_duration == 1:
tdstr = " in 1 second"
elif transfer_duration < 10:
tdstr = " in "+str(transfer_duration)+" seconds"
else:
tdstr = " in "+pretty_time(transfer_duration)
spdstr = ", effective rate "+size_str(total_size/transfer_duration, "b")+"ps"
print("Transferred "+size_str(total_size)+tdstr+spdstr)
if outlen != None and stdout != None:
if len(stdout) < outlen:
tstr = ", "+str(len(stdout))+" bytes displayed"
else:
tstr = ""
print("Remote wrote "+str(outlen)+" bytes to stdout"+tstr)
if errlen != None and stderr != None:
if len(stderr) < errlen:
tstr = ", "+str(len(stderr))+" bytes displayed"
else:
tstr = ""
print("Remote wrote "+str(errlen)+" bytes to stderr"+tstr)
else:
if stdout != None and len(stdout) > 0:
print(stdout.decode("utf-8"), end="")
if stderr != None and len(stderr) > 0:
print(stderr.decode("utf-8"), file=sys.stderr, end="")
if (stdoutl != 0 and len(stdout) < outlen) or (stderrl != 0 and len(stderr) < errlen):
sys.stdout.flush()
sys.stderr.flush()
print("\nOutput truncated before being returned:")
if len(stdout) != 0 and len(stdout) < outlen:
print(" stdout truncated to "+str(len(stdout))+" bytes")
if len(stderr) != 0 and len(stderr) < errlen:
print(" stderr truncated to "+str(len(stderr))+" bytes")
else:
print("Remote could not execute command")
if interactive:
return
else:
exit(248)
else:
print("No response")
exit(249)
try:
if not interactive:
link.teardown()
except Exception as e:
pass
if not interactive and mirror:
if request_receipt.response[1] != None:
exit(request_receipt.response[1])
else:
exit(240)
else:
if interactive:
if mirror:
return request_receipt.response[1]
else:
return None
else:
exit(0)
def main():
try:
parser = argparse.ArgumentParser(description="Reticulum Remote Execution Utility")
parser.add_argument("destination", nargs="?", default=None, help="hexadecimal hash of the listener", type=str)
parser.add_argument("command", nargs="?", default=None, help="command to be execute", type=str)
parser.add_argument("--config", metavar="path", action="store", default=None, help="path to alternative Reticulum config directory", type=str)
parser.add_argument('-v', '--verbose', action='count', default=0, help="increase verbosity")
parser.add_argument('-q', '--quiet', action='count', default=0, help="decrease verbosity")
parser.add_argument('-p', '--print-identity', action='store_true', default=False, help="print identity and destination info and exit")
parser.add_argument("-l", '--listen', action='store_true', default=False, help="listen for incoming commands")
parser.add_argument('-i', metavar="identity", action='store', dest="identity", default=None, help="path to identity to use", type=str)
parser.add_argument("-x", '--interactive', action='store_true', default=False, help="enter interactive mode")
parser.add_argument("-b", '--no-announce', action='store_true', default=False, help="don't announce at program start")
parser.add_argument('-a', metavar="allowed_hash", dest="allowed", action='append', help="accept from this identity", type=str)
parser.add_argument('-n', '--noauth', action='store_true', default=False, help="accept files from anyone")
parser.add_argument('-N', '--noid', action='store_true', default=False, help="don't identify to listener")
parser.add_argument("-d", '--detailed', action='store_true', default=False, help="show detailed result output")
parser.add_argument("-m", action='store_true', dest="mirror", default=False, help="mirror exit code of remote command")
parser.add_argument("-w", action="store", metavar="seconds", type=float, help="connect and request timeout before giving up", default=RNS.Transport.PATH_REQUEST_TIMEOUT)
parser.add_argument("-W", action="store", metavar="seconds", type=float, help="max result download time", default=None)
parser.add_argument("--stdin", action='store', default=None, help="pass input to stdin", type=str)
parser.add_argument("--stdout", action='store', default=None, help="max size in bytes of returned stdout", type=int)
parser.add_argument("--stderr", action='store', default=None, help="max size in bytes of returned stderr", type=int)
parser.add_argument("--version", action="version", version="rnx {version}".format(version=__version__))
args = parser.parse_args()
if args.listen or args.print_identity:
listen(
configdir = args.config,
identitypath = args.identity,
verbosity=args.verbose,
quietness=args.quiet,
allowed = args.allowed,
print_identity=args.print_identity,
disable_auth=args.noauth,
disable_announce=args.no_announce,
)
elif args.destination != None and args.command != None:
execute(
configdir = args.config,
identitypath = args.identity,
verbosity = args.verbose,
quietness = args.quiet,
detailed = args.detailed,
mirror = args.mirror,
noid = args.noid,
destination = args.destination,
command = args.command,
stdin = args.stdin,
stdoutl = args.stdout,
stderrl = args.stderr,
timeout = args.w,
result_timeout = args.W,
interactive = args.interactive,
)
if args.destination != None and args.interactive:
# command_history_max = 5000
# command_history = []
# command_current = ""
# history_idx = 0
# tty.setcbreak(sys.stdin.fileno())
code = None
while True:
try:
cstr = str(code) if code and code != 0 else ""
prompt = cstr+"> "
print(prompt,end="")
# cmdbuf = b""
# while True:
# ch = sys.stdin.read(1)
# cmdbuf += ch.encode("utf-8")
# print("\r"+prompt+cmdbuf.decode("utf-8"), end="")
command = input()
if command.lower() == "exit" or command.lower() == "quit":
exit(0)
except KeyboardInterrupt:
exit(0)
except EOFError:
exit(0)
if command.lower() == "clear":
print('\033c', end='')
# command_history.append(command)
# while len(command_history) > command_history_max:
# command_history.pop(0)
else:
code = execute(
configdir = args.config,
identitypath = args.identity,
verbosity = args.verbose,
quietness = args.quiet,
detailed = args.detailed,
mirror = args.mirror,
noid = args.noid,
destination = args.destination,
command = command,
stdin = None,
stdoutl = args.stdout,
stderrl = args.stderr,
timeout = args.w,
result_timeout = args.W,
interactive = True,
)
else:
print("")
parser.print_help()
print("")
except KeyboardInterrupt:
# tty.setnocbreak(sys.stdin.fileno())
print("")
if link != None:
link.teardown()
exit()
def size_str(num, suffix='B'):
units = ['','K','M','G','T','P','E','Z']
last_unit = 'Y'
if suffix == 'b':
num *= 8
units = ['','K','M','G','T','P','E','Z']
last_unit = 'Y'
for unit in units:
if abs(num) < 1000.0:
if unit == "":
return "%.0f %s%s" % (num, unit, suffix)
else:
return "%.2f %s%s" % (num, unit, suffix)
num /= 1000.0
return "%.2f%s%s" % (num, last_unit, suffix)
def pretty_time(time, verbose=False):
days = int(time // (24 * 3600))
time = time % (24 * 3600)
hours = int(time // 3600)
time %= 3600
minutes = int(time // 60)
time %= 60
seconds = round(time, 2)
ss = "" if seconds == 1 else "s"
sm = "" if minutes == 1 else "s"
sh = "" if hours == 1 else "s"
sd = "" if days == 1 else "s"
components = []
if days > 0:
components.append(str(days)+" day"+sd if verbose else str(days)+"d")
if hours > 0:
components.append(str(hours)+" hour"+sh if verbose else str(hours)+"h")
if minutes > 0:
components.append(str(minutes)+" minute"+sm if verbose else str(minutes)+"m")
if seconds > 0:
components.append(str(seconds)+" second"+ss if verbose else str(seconds)+"s")
i = 0
tstr = ""
for c in components:
i += 1
if i == 1:
pass
elif i < len(components):
tstr += ", "
elif i == len(components):
tstr += " and "
tstr += c
return tstr
if __name__ == "__main__":
main()
+1 -1
View File
@@ -1 +1 @@
__version__ = "0.3.6"
__version__ = "0.3.7"
+1 -1
View File
@@ -25,7 +25,7 @@ async def get_sam_socket(sam_address=sam.DEFAULT_ADDRESS, loop=None):
:param loop: (optional) event loop instance
:return: A (reader, writer) pair
"""
reader, writer = await asyncio.open_connection(*sam_address, loop=loop)
reader, writer = await asyncio.open_connection(*sam_address)
writer.write(sam.hello("3.1", "3.1"))
reply = parse_reply(await reader.readline())
if reply.ok:
+47 -16
View File
@@ -85,17 +85,35 @@ class ClientTunnel(I2PTunnel):
"""A coroutine used to run the tunnel"""
await self._pre_run()
self.status = { "setup_ran": False, "setup_failed": False, "exception": None, "connect_tasks": [] }
async def handle_client(client_reader, client_writer):
"""Handle local client connection"""
remote_reader, remote_writer = await aiosam.stream_connect(
self.session_name, self.remote_destination,
sam_address=self.sam_address, loop=self.loop)
asyncio.ensure_future(proxy_data(remote_reader, client_writer),
loop=self.loop)
asyncio.ensure_future(proxy_data(client_reader, remote_writer),
loop=self.loop)
try:
sc_task = aiosam.stream_connect(
self.session_name, self.remote_destination,
sam_address=self.sam_address, loop=self.loop)
self.status["connect_tasks"].append(sc_task)
remote_reader, remote_writer = await sc_task
asyncio.ensure_future(proxy_data(remote_reader, client_writer),
loop=self.loop)
asyncio.ensure_future(proxy_data(client_reader, remote_writer),
loop=self.loop)
self.server = await asyncio.start_server(handle_client, *self.local_address, loop=self.loop)
except Exception as e:
self.status["setup_ran"] = True
self.status["setup_failed"] = True
self.status["exception"] = e
try:
self.server = await asyncio.start_server(handle_client, *self.local_address)
self.status["setup_ran"] = True
except Exception as e:
self.status["setup_ran"] = True
self.status["setup_failed"] = True
self.status["exception"] = e
def stop(self):
super().stop()
@@ -117,26 +135,38 @@ class ServerTunnel(I2PTunnel):
"""A coroutine used to run the tunnel"""
await self._pre_run()
self.status = { "setup_ran": False, "setup_failed": False, "exception": None, "connect_tasks": [] }
async def handle_client(incoming, client_reader, client_writer):
# data and dest may come in one chunk
dest, data = incoming.split(b"\n", 1)
remote_destination = sam.Destination(dest.decode())
logger.debug("{} client connected: {}.b32.i2p".format(
self.session_name, remote_destination.base32))
try:
# data and dest may come in one chunk
dest, data = incoming.split(b"\n", 1)
remote_destination = sam.Destination(dest.decode())
logger.debug("{} client connected: {}.b32.i2p".format(
self.session_name, remote_destination.base32))
except Exception as e:
self.status["exception"] = e
self.status["setup_failed"] = True
try:
remote_reader, remote_writer = await asyncio.wait_for(
sc_task = asyncio.wait_for(
asyncio.open_connection(
host=self.local_address[0],
port=self.local_address[1], loop=self.loop),
timeout=5, loop=self.loop)
port=self.local_address[1]),
timeout=5)
self.status["connect_tasks"].append(sc_task)
remote_reader, remote_writer = await sc_task
if data: remote_writer.write(data)
asyncio.ensure_future(proxy_data(remote_reader, client_writer),
loop=self.loop)
asyncio.ensure_future(proxy_data(client_reader, remote_writer),
loop=self.loop)
except ConnectionRefusedError:
client_writer.close()
self.status["exception"] = e
self.status["setup_failed"] = True
async def server_loop():
try:
@@ -151,6 +181,7 @@ class ServerTunnel(I2PTunnel):
pass
self.server_loop = asyncio.ensure_future(server_loop(), loop=self.loop)
self.status["setup_ran"] = True
def stop(self):
super().stop()
Binary file not shown.
+1 -1
View File
@@ -1,4 +1,4 @@
# Sphinx build info version 1
# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done.
config: cb185315f69a86407f1dca0f5d7d25ef
config: 3ea52ff0bfd9431c8886e9a105e9d835
tags: 645f666f9bcd5a90fca523b33c5a78b7
+39 -13
View File
@@ -100,8 +100,8 @@ at.
To use the I2P interface, you must have an I2P router running
on your system. The easiest way to acheive this is to download and
install the `latest release <https://github.com/PurpleI2P/i2pd/releases/latest>`_
of the ``ì2pd`` package. For more details about I2P, see the
`geti2p.net website <https://geti2p.net/en/about/intro>`_.`
of the ``i2pd`` package. For more details about I2P, see the
`geti2p.net website <https://geti2p.net/en/about/intro>`_.
When an I2P router is running on your system, you can simply add
an I2P interface to reticulum:
@@ -634,25 +634,51 @@ Interface Modes
The optional ``mode`` setting is available on all interfaces, and allows
selecting the high-level behaviour of the interface from a number of modes.
These modes affect how Reticulum selects paths in the network, how announces
are propagated and how long paths are valid.
are propagated, how long paths are valid and how paths are discovered.
Configuring modes on interfaces is not strictly necessary, but can be useful
when building or connecting to more complex networks. When not running a
Transport Node, it is rarely useful to configure an interface mode.
Configuring modes on interfaces is **not** strictly necessary, but can be useful
when building or connecting to more complex networks. If your Reticulum
instance is not running a Transport Node, it is rarely useful to configure
interface modes, and in such cases interfaces should generally be left in
the default mode.
* | The default value is ``full``. In this mode, all discovery,
* | The default mode is ``full``. In this mode, all discovery,
meshing and transport functionality is activated.
* | The ``gateway`` mode (or shorthand ``gw``) also has all
discovery, meshing and transport functionality available,
but will additionally try to discover unknown paths on
behalf of other nodes residing on the ``gateway`` interface.
If Reticulum receives a path request for an unknown
destination, from a node on a ``gateway`` interface, it
will try to discover this path via all other active interfaces,
and forward the discovered path to the requestor if one is
found.
| If you want to allow other nodes to widely resolve paths or connect
to a network via an interface, it might be useful to put it in this
mode. By creating a chain of ``gateway`` interfaces, other
nodes will be able to immediately discover paths to any
destination along the chain.
| *Please note!* It is the interface *facing the clients* that
must be put into ``gateway`` mode for this to work, not
the interface facing the wider network (for this, the ``boundary``
mode can be useful, though).
* | In the ``access_point`` (or shorthand ``ap``) mode, the
interface will operate as a network access point. In this
mode, announces will not be automatically broadcasted on
the interface, and paths to destinations on the interface
will have a much shorter expiry time. This mode is useful
for creating interfaces that remain quiet, unless when
someone is actually using them. An example of this could
be a radio interface serving a wide area, where users are
expected to connect momentarily, use the network, and then
disappear again.
will have a much shorter expiry time. In addition, path
requests from clients on the access point interface will
be handled in the same way as the ``gateway`` interface.
| This mode is useful for creating interfaces that remain
quiet, until someone actually starts using them. An example
of this could be a radio interface serving a wide area,
where users are expected to connect momentarily, use the
network, and then disappear again.
* | The ``roaming`` mode should be used on interfaces that are
roaming (physically mobile), seen from the perspective of
+5 -3
View File
@@ -809,12 +809,14 @@ Announce Propagation Rules
--------------------------
The following table illustrates the rules for automatically propagating announces
from one interface type to another, for all possible combinations. See the
:ref:`Interface Modes<interfaces-modes>` section for a conceptual overview of the
different interface modes, and how they are configured.
from one interface type to another, for all possible combinations. For the purpose
of announce propagation, the *Full* and *Gateway* modes are identical.
.. image:: graphics/if_mode_graph_b.png
See the :ref:`Interface Modes<interfaces-modes>` section for a conceptual overview
of the different interface modes, and how they are configured.
..
(.. code-block:: text)
Full ────── ✓ ──┐ ┌── ✓ ── Full
+110 -3
View File
@@ -138,6 +138,10 @@ take a look at the :ref:`Interfaces<interfaces-main>` chapter of this manual.
Included Utility Programs
-------------------------
Reticulum includes a range of useful utilities, both for managing your Reticulum
networks, and for carrying out common tasks over Reticulum networks, such as
transferring files to remote systems, and executing commands and programs remotely.
If you often use Reticulum from several different programs, or simply want
Reticulum to stay available all the time, for example if you are hosting
a transport node, you might want to run Reticulum as a separate service that
@@ -251,7 +255,7 @@ destinations on the Reticulum network.
.. code:: text
usage: rnpath.py [-h] [--config CONFIG] [--version] [-t] [-r] [-d] [-D] [-w seconds] [-v] [destination]
usage: rnpath [-h] [--config CONFIG] [--version] [-t] [-r] [-d] [-D] [-w seconds] [-v] [destination]
Reticulum Path Discovery Utility
@@ -281,7 +285,7 @@ destinations will not have this option enabled, and will not be probable.
.. code:: text
# Run rnprobe
python3 -m RNS.Utilities.rnprobe example_utilities.echo.request 9382f334de63217a4278
rnprobe example_utilities.echo.request 9382f334de63217a4278
# Example output
Sent 16 byte probe to <9382f334de63217a4278>
@@ -290,7 +294,7 @@ destinations will not have this option enabled, and will not be probable.
.. code:: text
usage: rnprobe.py [-h] [--config CONFIG] [--version] [-v] [full_name] [destination_hash]
usage: rnprobe [-h] [--config CONFIG] [--version] [-v] [full_name] [destination_hash]
Reticulum Probe Utility
@@ -305,6 +309,109 @@ destinations will not have this option enabled, and will not be probable.
-v, --verbose
The rncp Utility
================
The ``rncp`` utility is a simple file transfer tool. Using it, you can transfer
files through Reticulum.
.. code:: text
# Run rncp on the receiving system, specifying which identities
# are allowed to send files
rncp --receive -a 940ea3f9e1037d38758f -a e28d5aee4317c24a9041
# From another system, copy a file to the receiving system
rncp ~/path/to/file.tgz 256320d405d6d525d1e9
You can specify as many allowed senders as needed, or complete disable authentication.
.. code:: text
usage: rncp [-h] [--config path] [-v] [-q] [-p] [-r] [-b] [-a allowed_hash] [-n] [-w seconds] [--version] [file] [destination]
Reticulum File Transfer Utility
positional arguments:
file file to be transferred
destination hexadecimal hash of the receiver
optional arguments:
-h, --help show this help message and exit
--config path path to alternative Reticulum config directory
-v, --verbose increase verbosity
-q, --quiet decrease verbosity
-p, --print-identity print identity and destination info and exit
-r, --receive wait for incoming files
-b, --no-announce don't announce at program start
-a allowed_hash accept from this identity
-n, --no-auth accept files from anyone
-w seconds sender timeout before giving up
--version show program's version number and exit
-v, --verbose
The rnx Utility
================
The ``rnx`` utility is a basic remote command execution program. It allows you to
execute commands on remote systems over Reticulum, and to view returned command
output.
.. code:: text
# Run rnx on the listening system, specifying which identities
# are allowed to execute commands
rncp --listen -a 8111c4ff2968ab0c1286 -a 590256654482b4ba4038
# From another system, run a command
rnx ad9a4c9da60089d41c29 "cat /proc/cpuinfo"
# Or enter the interactive mode pseudo-shell
rnx ad9a4c9da60089d41c29 -x
# The default identity file is stored in
# ~/.reticulum/identities/rnx, but you can use
# another one, which will be created if it does
# not already exist
rnx ad9a4c9da60089d41c29 -i /path/to/identity
You can specify as many allowed senders as needed, or complete disable authentication.
.. code:: text
usage: rnx [-h] [--config path] [-v] [-q] [-p] [-l] [-i identity] [-x] [-b] [-a allowed_hash] [-n] [-N] [-d] [-m] [-w seconds] [-W seconds] [--stdin STDIN] [--stdout STDOUT] [--stderr STDERR] [--version]
[destination] [command]
Reticulum Remote Execution Utility
positional arguments:
destination hexadecimal hash of the listener
command command to be execute
optional arguments:
-h, --help show this help message and exit
--config path path to alternative Reticulum config directory
-v, --verbose increase verbosity
-q, --quiet decrease verbosity
-p, --print-identity print identity and destination info and exit
-l, --listen listen for incoming commands
-i identity path to identity to use
-x, --interactive enter interactive mode
-b, --no-announce don't announce at program start
-a allowed_hash accept from this identity
-n, --noauth accept files from anyone
-N, --noid don't identify to listener
-d, --detailed show detailed result output
-m mirror exit code of remote command
-w seconds connect and request timeout before giving up
-W seconds max result download time
--stdin STDIN pass input to stdin
--stdout STDOUT max size in bytes of returned stdout
--stderr STDERR max size in bytes of returned stderr
--version show program's version number and exit
Improving System Configuration
------------------------------
+1 -1
View File
@@ -1,6 +1,6 @@
var DOCUMENTATION_OPTIONS = {
URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'),
VERSION: '0.3.6 beta',
VERSION: '0.3.7 beta',
LANGUAGE: 'None',
COLLAPSE_INDEX: false,
BUILDER: 'html',
+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>Code Examples &#8212; Reticulum Network Stack 0.3.6 beta documentation</title>
<title>Code Examples &#8212; Reticulum Network Stack 0.3.7 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="reference.html" title="API Reference"
accesskey="P">previous</a> |</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.6 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.7 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">Code Examples</a></li>
</ul>
</div>
@@ -2366,7 +2366,7 @@ 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.3.6 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.7 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">Code Examples</a></li>
</ul>
</div>
+25 -5
View File
@@ -5,7 +5,7 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Index &#8212; Reticulum Network Stack 0.3.6 beta documentation</title>
<title>Index &#8212; Reticulum Network Stack 0.3.7 beta documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/classic.css" />
@@ -23,7 +23,7 @@
<li class="right" style="margin-right: 10px">
<a href="#" title="General Index"
accesskey="I">index</a></li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.6 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.7 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">Index</a></li>
</ul>
</div>
@@ -149,6 +149,12 @@
<h2 id="G">G</h2>
<table style="width: 100%" class="indextable genindextable"><tr>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="reference.html#RNS.Resource.get_data_size">get_data_size() (RNS.Resource method)</a>
</li>
<li><a href="reference.html#RNS.Resource.get_hash">get_hash() (RNS.Resource method)</a>
</li>
<li><a href="reference.html#RNS.Resource.get_parts">get_parts() (RNS.Resource method)</a>
</li>
<li><a href="reference.html#RNS.Destination.get_private_key">get_private_key() (RNS.Destination method)</a>
<ul>
@@ -176,6 +182,8 @@
<li><a href="reference.html#RNS.RequestReceipt.get_response_time">get_response_time() (RNS.RequestReceipt method)</a>
</li>
<li><a href="reference.html#RNS.PacketReceipt.get_rtt">get_rtt() (RNS.PacketReceipt method)</a>
</li>
<li><a href="reference.html#RNS.Resource.get_segments">get_segments() (RNS.Resource method)</a>
</li>
<li><a href="reference.html#RNS.PacketReceipt.get_status">get_status() (RNS.PacketReceipt method)</a>
@@ -183,6 +191,8 @@
<li><a href="reference.html#RNS.RequestReceipt.get_status">(RNS.RequestReceipt method)</a>
</li>
</ul></li>
<li><a href="reference.html#RNS.Resource.get_transfer_size">get_transfer_size() (RNS.Resource method)</a>
</li>
</ul></td>
</tr></table>
@@ -207,11 +217,13 @@
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="reference.html#RNS.Link.identify">identify() (RNS.Link method)</a>
</li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="reference.html#RNS.Identity">Identity (class in RNS)</a>
</li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="reference.html#RNS.Link.inactive_for">inactive_for() (RNS.Link method)</a>
</li>
<li><a href="reference.html#RNS.Resource.is_compressed">is_compressed() (RNS.Resource method)</a>
</li>
</ul></td>
</tr></table>
@@ -223,6 +235,8 @@
</li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="reference.html#RNS.Link.KEEPALIVE_TIMEOUT_FACTOR">KEEPALIVE_TIMEOUT_FACTOR (RNS.Link attribute)</a>
</li>
<li><a href="reference.html#RNS.Identity.KEYSIZE">KEYSIZE (RNS.Identity attribute)</a>
</li>
</ul></td>
@@ -322,6 +336,8 @@
<li><a href="reference.html#RNS.Destination.set_default_app_data">set_default_app_data() (RNS.Destination method)</a>
</li>
<li><a href="reference.html#RNS.PacketReceipt.set_delivery_callback">set_delivery_callback() (RNS.PacketReceipt method)</a>
</li>
<li><a href="reference.html#RNS.Link.set_link_closed_callback">set_link_closed_callback() (RNS.Link method)</a>
</li>
<li><a href="reference.html#RNS.Destination.set_link_established_callback">set_link_established_callback() (RNS.Destination method)</a>
</li>
@@ -359,6 +375,10 @@
<li><a href="reference.html#RNS.Identity.sign">(RNS.Identity method)</a>
</li>
</ul></li>
<li><a href="reference.html#RNS.Link.STALE_GRACE">STALE_GRACE (RNS.Link attribute)</a>
</li>
<li><a href="reference.html#RNS.Link.STALE_TIME">STALE_TIME (RNS.Link attribute)</a>
</li>
</ul></td>
</tr></table>
@@ -418,7 +438,7 @@
<li class="right" style="margin-right: 10px">
<a href="#" title="General Index"
>index</a></li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.6 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.7 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">Index</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>Getting Started Fast &#8212; Reticulum Network Stack 0.3.6 beta documentation</title>
<title>Getting Started Fast &#8212; Reticulum Network Stack 0.3.7 beta documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/classic.css" />
@@ -31,7 +31,7 @@
<li class="right" >
<a href="whatis.html" title="What is Reticulum?"
accesskey="P">previous</a> |</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.6 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.7 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">Getting Started Fast</a></li>
</ul>
</div>
@@ -384,7 +384,7 @@ and propose adding an interface for the hardware.</p>
<li class="right" >
<a href="whatis.html" title="What is Reticulum?"
>previous</a> |</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.6 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.7 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">Getting Started Fast</a></li>
</ul>
</div>
+5 -3
View File
@@ -5,7 +5,7 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Reticulum Network Stack Manual &#8212; Reticulum Network Stack 0.3.6 beta documentation</title>
<title>Reticulum Network Stack Manual &#8212; Reticulum Network Stack 0.3.7 beta documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/classic.css" />
@@ -27,7 +27,7 @@
<li class="right" >
<a href="whatis.html" title="What is Reticulum?"
accesskey="N">next</a> |</li>
<li class="nav-item nav-item-0"><a href="#">Reticulum Network Stack 0.3.6 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-0"><a href="#">Reticulum Network Stack 0.3.7 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">Reticulum Network Stack Manual</a></li>
</ul>
</div>
@@ -76,6 +76,8 @@ to participate in the development of Reticulum itself.</p>
<li class="toctree-l3"><a class="reference internal" href="using.html#the-rnstatus-utility">The rnstatus Utility</a></li>
<li class="toctree-l3"><a class="reference internal" href="using.html#the-rnpath-utility">The rnpath Utility</a></li>
<li class="toctree-l3"><a class="reference internal" href="using.html#the-rnprobe-utility">The rnprobe Utility</a></li>
<li class="toctree-l3"><a class="reference internal" href="using.html#the-rncp-utility">The rncp Utility</a></li>
<li class="toctree-l3"><a class="reference internal" href="using.html#the-rnx-utility">The rnx Utility</a></li>
</ul>
</li>
<li class="toctree-l2"><a class="reference internal" href="using.html#improving-system-configuration">Improving System Configuration</a><ul>
@@ -223,7 +225,7 @@ to participate in the development of Reticulum itself.</p>
<li class="right" >
<a href="whatis.html" title="What is Reticulum?"
>next</a> |</li>
<li class="nav-item nav-item-0"><a href="#">Reticulum Network Stack 0.3.6 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-0"><a href="#">Reticulum Network Stack 0.3.7 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">Reticulum Network Stack Manual</a></li>
</ul>
</div>
+47 -16
View File
@@ -5,7 +5,7 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Supported Interfaces &#8212; Reticulum Network Stack 0.3.6 beta documentation</title>
<title>Supported Interfaces &#8212; Reticulum Network Stack 0.3.7 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="networks.html" title="Building Networks"
accesskey="P">previous</a> |</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.6 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.7 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">Supported Interfaces</a></li>
</ul>
</div>
@@ -121,8 +121,8 @@ at.</p>
<p>To use the I2P interface, you must have an I2P router running
on your system. The easiest way to acheive this is to download and
install the <a class="reference external" href="https://github.com/PurpleI2P/i2pd/releases/latest">latest release</a>
of the <code class="docutils literal notranslate"><span class="pre">ì2pd</span></code> package. For more details about I2P, see the
<a class="reference external" href="https://geti2p.net/en/about/intro">geti2p.net website</a>.`</p>
of the <code class="docutils literal notranslate"><span class="pre">i2pd</span></code> package. For more details about I2P, see the
<a class="reference external" href="https://geti2p.net/en/about/intro">geti2p.net website</a>.</p>
<p>When an I2P router is running on your system, you can simply add
an I2P interface to reticulum:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="p">[[</span><span class="n">I2P</span><span class="p">]]</span>
@@ -611,28 +611,59 @@ option, to set the interface speed in <em>bits per second</em>.</div>
<p>The optional <code class="docutils literal notranslate"><span class="pre">mode</span></code> setting is available on all interfaces, and allows
selecting the high-level behaviour of the interface from a number of modes.
These modes affect how Reticulum selects paths in the network, how announces
are propagated and how long paths are valid.</p>
<p>Configuring modes on interfaces is not strictly necessary, but can be useful
when building or connecting to more complex networks. When not running a
Transport Node, it is rarely useful to configure an interface mode.</p>
are propagated, how long paths are valid and how paths are discovered.</p>
<p>Configuring modes on interfaces is <strong>not</strong> strictly necessary, but can be useful
when building or connecting to more complex networks. If your Reticulum
instance is not running a Transport Node, it is rarely useful to configure
interface modes, and in such cases interfaces should generally be left in
the default mode.</p>
<blockquote>
<div><ul>
<li><div class="line-block">
<div class="line">The default value is <code class="docutils literal notranslate"><span class="pre">full</span></code>. In this mode, all discovery,
<div class="line">The default mode is <code class="docutils literal notranslate"><span class="pre">full</span></code>. In this mode, all discovery,
meshing and transport functionality is activated.</div>
</div>
</li>
<li><div class="line-block">
<div class="line">The <code class="docutils literal notranslate"><span class="pre">gateway</span></code> mode (or shorthand <code class="docutils literal notranslate"><span class="pre">gw</span></code>) also has all
discovery, meshing and transport functionality available,
but will additionally try to discover unknown paths on
behalf of other nodes residing on the <code class="docutils literal notranslate"><span class="pre">gateway</span></code> interface.
If Reticulum receives a path request for an unknown
destination, from a node on a <code class="docutils literal notranslate"><span class="pre">gateway</span></code> interface, it
will try to discover this path via all other active interfaces,
and forward the discovered path to the requestor if one is
found.</div>
</div>
<div class="line-block">
<div class="line">If you want to allow other nodes to widely resolve paths or connect
to a network via an interface, it might be useful to put it in this
mode. By creating a chain of <code class="docutils literal notranslate"><span class="pre">gateway</span></code> interfaces, other
nodes will be able to immediately discover paths to any
destination along the chain.</div>
</div>
<div class="line-block">
<div class="line"><em>Please note!</em> It is the interface <em>facing the clients</em> that
must be put into <code class="docutils literal notranslate"><span class="pre">gateway</span></code> mode for this to work, not
the interface facing the wider network (for this, the <code class="docutils literal notranslate"><span class="pre">boundary</span></code>
mode can be useful, though).</div>
</div>
</li>
<li><div class="line-block">
<div class="line">In the <code class="docutils literal notranslate"><span class="pre">access_point</span></code> (or shorthand <code class="docutils literal notranslate"><span class="pre">ap</span></code>) mode, the
interface will operate as a network access point. In this
mode, announces will not be automatically broadcasted on
the interface, and paths to destinations on the interface
will have a much shorter expiry time. This mode is useful
for creating interfaces that remain quiet, unless when
someone is actually using them. An example of this could
be a radio interface serving a wide area, where users are
expected to connect momentarily, use the network, and then
disappear again.</div>
will have a much shorter expiry time. In addition, path
requests from clients on the access point interface will
be handled in the same way as the <code class="docutils literal notranslate"><span class="pre">gateway</span></code> interface.</div>
</div>
<div class="line-block">
<div class="line">This mode is useful for creating interfaces that remain
quiet, until someone actually starts using them. An example
of this could be a radio interface serving a wide area,
where users are expected to connect momentarily, use the
network, and then disappear again.</div>
</div>
</li>
<li><div class="line-block">
@@ -783,7 +814,7 @@ that a large span of network types can seamlessly <em>co-exist</em> and intercon
<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.6 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.7 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">Supported Interfaces</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>Building Networks &#8212; Reticulum Network Stack 0.3.6 beta documentation</title>
<title>Building Networks &#8212; Reticulum Network Stack 0.3.7 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="using.html" title="Using Reticulum on Your System"
accesskey="P">previous</a> |</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.6 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.7 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">Building Networks</a></li>
</ul>
</div>
@@ -272,7 +272,7 @@ connected outliers are now an integral part of the network.</p>
<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.6 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.7 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">Building Networks</a></li>
</ul>
</div>
Binary file not shown.
+104 -10
View File
@@ -5,7 +5,7 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>API Reference &#8212; Reticulum Network Stack 0.3.6 beta documentation</title>
<title>API Reference &#8212; Reticulum Network Stack 0.3.7 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="understanding.html" title="Understanding Reticulum"
accesskey="P">previous</a> |</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.6 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.7 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">API Reference</a></li>
</ul>
</div>
@@ -484,7 +484,7 @@ relevant interfaces. Application specific data can be added to the announce.</p>
this destination.</p>
<dl class="field-list simple">
<dt class="field-odd">Parameters</dt>
<dd class="field-odd"><p><strong>callback</strong> A function or method to be called.</p>
<dd class="field-odd"><p><strong>callback</strong> A function or method with the signature <em>callback(link)</em> to be called when a new link is established with this destination.</p>
</dd>
</dl>
</dd></dl>
@@ -496,7 +496,7 @@ this destination.</p>
this destination.</p>
<dl class="field-list simple">
<dt class="field-odd">Parameters</dt>
<dd class="field-odd"><p><strong>callback</strong> A function or method to be called.</p>
<dd class="field-odd"><p><strong>callback</strong> A function or method with the signature <em>callback(data, packet)</em> to be called when this destination receives a packet.</p>
</dd>
</dl>
</dd></dl>
@@ -509,7 +509,7 @@ a packet sent to this destination. Allows control over when and if
proofs should be returned for received packets.</p>
<dl class="field-list simple">
<dt class="field-odd">Parameters</dt>
<dd class="field-odd"><p><strong>callback</strong> A function or method to be called. The callback must return one of True or False. If the callback returns True, a proof will be sent. If it returns False, a proof will not be sent.</p>
<dd class="field-odd"><p><strong>callback</strong> A function or method to with the signature <em>callback(packet)</em> be called when a packet that requests a proof is received. The callback must return one of True or False. If the callback returns True, a proof will be sent. If it returns False, a proof will not be sent.</p>
</dd>
</dl>
</dd></dl>
@@ -808,7 +808,19 @@ connectivity with the specified destination.</p>
<dl class="py attribute">
<dt class="sig sig-object py" id="RNS.Link.ESTABLISHMENT_TIMEOUT_PER_HOP">
<span class="sig-name descname"><span class="pre">ESTABLISHMENT_TIMEOUT_PER_HOP</span></span><em class="property"> <span class="pre">=</span> <span class="pre">5</span></em><a class="headerlink" href="#RNS.Link.ESTABLISHMENT_TIMEOUT_PER_HOP" title="Permalink to this definition"></a></dt>
<dd><p>Default timeout for link establishment in seconds per hop to destination.</p>
<dd><p>Timeout for link establishment in seconds per hop to destination.</p>
</dd></dl>
<dl class="py attribute">
<dt class="sig sig-object py" id="RNS.Link.KEEPALIVE_TIMEOUT_FACTOR">
<span class="sig-name descname"><span class="pre">KEEPALIVE_TIMEOUT_FACTOR</span></span><em class="property"> <span class="pre">=</span> <span class="pre">4</span></em><a class="headerlink" href="#RNS.Link.KEEPALIVE_TIMEOUT_FACTOR" title="Permalink to this definition"></a></dt>
<dd><p>RTT timeout factor used in link timeout calculation.</p>
</dd></dl>
<dl class="py attribute">
<dt class="sig sig-object py" id="RNS.Link.STALE_GRACE">
<span class="sig-name descname"><span class="pre">STALE_GRACE</span></span><em class="property"> <span class="pre">=</span> <span class="pre">2</span></em><a class="headerlink" href="#RNS.Link.STALE_GRACE" title="Permalink to this definition"></a></dt>
<dd><p>Grace period in seconds used in link timeout calculation.</p>
</dd></dl>
<dl class="py attribute">
@@ -817,6 +829,16 @@ connectivity with the specified destination.</p>
<dd><p>Interval for sending keep-alive packets on established links in seconds.</p>
</dd></dl>
<dl class="py attribute">
<dt class="sig sig-object py" id="RNS.Link.STALE_TIME">
<span class="sig-name descname"><span class="pre">STALE_TIME</span></span><em class="property"> <span class="pre">=</span> <span class="pre">720</span></em><a class="headerlink" href="#RNS.Link.STALE_TIME" title="Permalink to this definition"></a></dt>
<dd><p>If no traffic or keep-alive packets are received within this period, the
link will be marked as stale, and a final keep-alive packet will be sent.
If after this no traffic or keep-alive packets are received within <code class="docutils literal notranslate"><span class="pre">RTT</span></code> *
<code class="docutils literal notranslate"><span class="pre">KEEPALIVE_TIMEOUT_FACTOR</span></code> + <code class="docutils literal notranslate"><span class="pre">STALE_GRACE</span></code>, the link is considered timed out,
and will be torn down.</p>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Link.identify">
<span class="sig-name descname"><span class="pre">identify</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">identity</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Link.identify" title="Permalink to this definition"></a></dt>
@@ -898,6 +920,18 @@ thus preserved. This method can be used for authentication.</p>
be used if a new link to the same destination is established.</p>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Link.set_link_closed_callback">
<span class="sig-name descname"><span class="pre">set_link_closed_callback</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">callback</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Link.set_link_closed_callback" title="Permalink to this definition"></a></dt>
<dd><p>Registers a function to be called when a link has been
torn down.</p>
<dl class="field-list simple">
<dt class="field-odd">Parameters</dt>
<dd class="field-odd"><p><strong>callback</strong> A function or method with the signature <em>callback(link)</em> to be called.</p>
</dd>
</dl>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Link.set_packet_callback">
<span class="sig-name descname"><span class="pre">set_packet_callback</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">callback</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Link.set_packet_callback" title="Permalink to this definition"></a></dt>
@@ -919,7 +953,7 @@ the resource will be accepted. If it returns <em>False</em> it will
be ignored.</p>
<dl class="field-list simple">
<dt class="field-odd">Parameters</dt>
<dd class="field-odd"><p><strong>callback</strong> A function or method with the signature <em>callback(resource)</em> to be called.</p>
<dd class="field-odd"><p><strong>callback</strong> A function or method with the signature <em>callback(resource)</em> to be called. Please note that only the basic information of the resource is available at this time, such as <em>get_transfer_size()</em>, <em>get_data_size()</em>, <em>get_parts()</em> and <em>is_compressed()</em>.</p>
</dd>
</dl>
</dd></dl>
@@ -955,7 +989,7 @@ transferring over this link.</p>
identified over this link.</p>
<dl class="field-list simple">
<dt class="field-odd">Parameters</dt>
<dd class="field-odd"><p><strong>callback</strong> A function or method with the signature <em>callback(identity)</em> to be called.</p>
<dd class="field-odd"><p><strong>callback</strong> A function or method with the signature <em>callback(link, identity)</em> to be called.</p>
</dd>
</dl>
</dd></dl>
@@ -1081,6 +1115,66 @@ the resource advertisement it will begin transferring.</p>
</dl>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Resource.get_transfer_size">
<span class="sig-name descname"><span class="pre">get_transfer_size</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Resource.get_transfer_size" title="Permalink to this definition"></a></dt>
<dd><dl class="field-list simple">
<dt class="field-odd">Returns</dt>
<dd class="field-odd"><p>The number of bytes needed to transfer the resource.</p>
</dd>
</dl>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Resource.get_data_size">
<span class="sig-name descname"><span class="pre">get_data_size</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Resource.get_data_size" title="Permalink to this definition"></a></dt>
<dd><dl class="field-list simple">
<dt class="field-odd">Returns</dt>
<dd class="field-odd"><p>The total data size of the resource.</p>
</dd>
</dl>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Resource.get_parts">
<span class="sig-name descname"><span class="pre">get_parts</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Resource.get_parts" title="Permalink to this definition"></a></dt>
<dd><dl class="field-list simple">
<dt class="field-odd">Returns</dt>
<dd class="field-odd"><p>The number of parts the resource will be transferred in.</p>
</dd>
</dl>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Resource.get_segments">
<span class="sig-name descname"><span class="pre">get_segments</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Resource.get_segments" title="Permalink to this definition"></a></dt>
<dd><dl class="field-list simple">
<dt class="field-odd">Returns</dt>
<dd class="field-odd"><p>The number of segments the resource is divided into.</p>
</dd>
</dl>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Resource.get_hash">
<span class="sig-name descname"><span class="pre">get_hash</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Resource.get_hash" title="Permalink to this definition"></a></dt>
<dd><dl class="field-list simple">
<dt class="field-odd">Returns</dt>
<dd class="field-odd"><p>The hash of the resource.</p>
</dd>
</dl>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Resource.is_compressed">
<span class="sig-name descname"><span class="pre">is_compressed</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Resource.is_compressed" title="Permalink to this definition"></a></dt>
<dd><dl class="field-list simple">
<dt class="field-odd">Returns</dt>
<dd class="field-odd"><p>Whether the resource is compressed.</p>
</dd>
</dl>
</dd></dl>
</dd></dl>
</div>
@@ -1173,7 +1267,7 @@ Transport system of Reticulum.</p>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Transport.request_path">
<em class="property"><span class="pre">static</span> </em><span class="sig-name descname"><span class="pre">request_path</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">destination_hash</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">on_interface</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Transport.request_path" title="Permalink to this definition"></a></dt>
<em class="property"><span class="pre">static</span> </em><span class="sig-name descname"><span class="pre">request_path</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">destination_hash</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">on_interface</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">tag</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">recursive</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">False</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Transport.request_path" title="Permalink to this definition"></a></dt>
<dd><p>Requests a path to the destination from the network. If
another reachable peer on the network knows a path, it
will announce it.</p>
@@ -1258,7 +1352,7 @@ will announce it.</p>
<li class="right" >
<a href="understanding.html" title="Understanding Reticulum"
>previous</a> |</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.6 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.7 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">API Reference</a></li>
</ul>
</div>
+3 -3
View File
@@ -5,7 +5,7 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Search &#8212; Reticulum Network Stack 0.3.6 beta documentation</title>
<title>Search &#8212; Reticulum Network Stack 0.3.7 beta documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/classic.css" />
@@ -29,7 +29,7 @@
<li class="right" style="margin-right: 10px">
<a href="genindex.html" title="General Index"
accesskey="I">index</a></li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.6 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.7 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">Search</a></li>
</ul>
</div>
@@ -85,7 +85,7 @@
<li class="right" style="margin-right: 10px">
<a href="genindex.html" title="General Index"
>index</a></li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.6 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.7 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
+7 -6
View File
@@ -5,7 +5,7 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Understanding Reticulum &#8212; Reticulum Network Stack 0.3.6 beta documentation</title>
<title>Understanding Reticulum &#8212; Reticulum Network Stack 0.3.7 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="interfaces.html" title="Supported Interfaces"
accesskey="P">previous</a> |</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.6 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.7 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">Understanding Reticulum</a></li>
</ul>
</div>
@@ -861,10 +861,11 @@ but excluding any interface access codes.
<div class="section" id="announce-propagation-rules">
<span id="understanding-announcepropagation"></span><h3>Announce Propagation Rules<a class="headerlink" href="#announce-propagation-rules" title="Permalink to this headline"></a></h3>
<p>The following table illustrates the rules for automatically propagating announces
from one interface type to another, for all possible combinations. See the
<a class="reference internal" href="interfaces.html#interfaces-modes"><span class="std std-ref">Interface Modes</span></a> section for a conceptual overview of the
different interface modes, and how they are configured.</p>
from one interface type to another, for all possible combinations. For the purpose
of announce propagation, the <em>Full</em> and <em>Gateway</em> modes are identical.</p>
<img alt="_images/if_mode_graph_b.png" src="_images/if_mode_graph_b.png" />
<p>See the <a class="reference internal" href="interfaces.html#interfaces-modes"><span class="std std-ref">Interface Modes</span></a> section for a conceptual overview
of the different interface modes, and how they are configured.</p>
</div>
</div>
</div>
@@ -952,7 +953,7 @@ different interface modes, and how they are configured.</p>
<li class="right" >
<a href="interfaces.html" title="Supported Interfaces"
>previous</a> |</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.6 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.7 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">Understanding Reticulum</a></li>
</ul>
</div>
+104 -6
View File
@@ -5,7 +5,7 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Using Reticulum on Your System &#8212; Reticulum Network Stack 0.3.6 beta documentation</title>
<title>Using Reticulum on Your System &#8212; Reticulum Network Stack 0.3.7 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="gettingstartedfast.html" title="Getting Started Fast"
accesskey="P">previous</a> |</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.6 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.7 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">Using Reticulum on Your System</a></li>
</ul>
</div>
@@ -169,6 +169,9 @@ take a look at the <a class="reference internal" href="interfaces.html#interface
</div>
<div class="section" id="included-utility-programs">
<h2>Included Utility Programs<a class="headerlink" href="#included-utility-programs" title="Permalink to this headline"></a></h2>
<p>Reticulum includes a range of useful utilities, both for managing your Reticulum
networks, and for carrying out common tasks over Reticulum networks, such as
transferring files to remote systems, and executing commands and programs remotely.</p>
<p>If you often use Reticulum from several different programs, or simply want
Reticulum to stay available all the time, for example if you are hosting
a transport node, you might want to run Reticulum as a separate service that
@@ -267,7 +270,7 @@ rnpath eca6f4e4dc26ae329e61
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] [-t] [-r] [-d] [-D] [-w seconds] [-v] [destination]
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>usage: rnpath [-h] [--config CONFIG] [--version] [-t] [-r] [-d] [-D] [-w seconds] [-v] [destination]
Reticulum Path Discovery Utility
@@ -294,7 +297,7 @@ to the <code class="docutils literal notranslate"><span class="pre">ping</span><
specified destination is configured to send proofs for received packets. Many
destinations will not have this option enabled, and will not be probable.</p>
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span># Run rnprobe
python3 -m RNS.Utilities.rnprobe example_utilities.echo.request 9382f334de63217a4278
rnprobe example_utilities.echo.request 9382f334de63217a4278
# Example output
Sent 16 byte probe to &lt;9382f334de63217a4278&gt;
@@ -302,7 +305,7 @@ 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]
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>usage: rnprobe [-h] [--config CONFIG] [--version] [-v] [full_name] [destination_hash]
Reticulum Probe Utility
@@ -318,6 +321,99 @@ optional arguments:
</pre></div>
</div>
</div>
<div class="section" id="the-rncp-utility">
<h3>The rncp Utility<a class="headerlink" href="#the-rncp-utility" title="Permalink to this headline"></a></h3>
<p>The <code class="docutils literal notranslate"><span class="pre">rncp</span></code> utility is a simple file transfer tool. Using it, you can transfer
files through Reticulum.</p>
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span># Run rncp on the receiving system, specifying which identities
# are allowed to send files
rncp --receive -a 940ea3f9e1037d38758f -a e28d5aee4317c24a9041
# From another system, copy a file to the receiving system
rncp ~/path/to/file.tgz 256320d405d6d525d1e9
</pre></div>
</div>
<p>You can specify as many allowed senders as needed, or complete disable authentication.</p>
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>usage: rncp [-h] [--config path] [-v] [-q] [-p] [-r] [-b] [-a allowed_hash] [-n] [-w seconds] [--version] [file] [destination]
Reticulum File Transfer Utility
positional arguments:
file file to be transferred
destination hexadecimal hash of the receiver
optional arguments:
-h, --help show this help message and exit
--config path path to alternative Reticulum config directory
-v, --verbose increase verbosity
-q, --quiet decrease verbosity
-p, --print-identity print identity and destination info and exit
-r, --receive wait for incoming files
-b, --no-announce don&#39;t announce at program start
-a allowed_hash accept from this identity
-n, --no-auth accept files from anyone
-w seconds sender timeout before giving up
--version show program&#39;s version number and exit
-v, --verbose
</pre></div>
</div>
</div>
<div class="section" id="the-rnx-utility">
<h3>The rnx Utility<a class="headerlink" href="#the-rnx-utility" title="Permalink to this headline"></a></h3>
<p>The <code class="docutils literal notranslate"><span class="pre">rnx</span></code> utility is a basic remote command execution program. It allows you to
execute commands on remote systems over Reticulum, and to view returned command
output.</p>
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span># Run rnx on the listening system, specifying which identities
# are allowed to execute commands
rncp --listen -a 8111c4ff2968ab0c1286 -a 590256654482b4ba4038
# From another system, run a command
rnx ad9a4c9da60089d41c29 &quot;cat /proc/cpuinfo&quot;
# Or enter the interactive mode pseudo-shell
rnx ad9a4c9da60089d41c29 -x
# The default identity file is stored in
# ~/.reticulum/identities/rnx, but you can use
# another one, which will be created if it does
# not already exist
rnx ad9a4c9da60089d41c29 -i /path/to/identity
</pre></div>
</div>
<p>You can specify as many allowed senders as needed, or complete disable authentication.</p>
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>usage: rnx [-h] [--config path] [-v] [-q] [-p] [-l] [-i identity] [-x] [-b] [-a allowed_hash] [-n] [-N] [-d] [-m] [-w seconds] [-W seconds] [--stdin STDIN] [--stdout STDOUT] [--stderr STDERR] [--version]
[destination] [command]
Reticulum Remote Execution Utility
positional arguments:
destination hexadecimal hash of the listener
command command to be execute
optional arguments:
-h, --help show this help message and exit
--config path path to alternative Reticulum config directory
-v, --verbose increase verbosity
-q, --quiet decrease verbosity
-p, --print-identity print identity and destination info and exit
-l, --listen listen for incoming commands
-i identity path to identity to use
-x, --interactive enter interactive mode
-b, --no-announce don&#39;t announce at program start
-a allowed_hash accept from this identity
-n, --noauth accept files from anyone
-N, --noid don&#39;t identify to listener
-d, --detailed show detailed result output
-m mirror exit code of remote command
-w seconds connect and request timeout before giving up
-W seconds max result download time
--stdin STDIN pass input to stdin
--stdout STDOUT max size in bytes of returned stdout
--stderr STDERR max size in bytes of returned stderr
--version show program&#39;s version number and exit
</pre></div>
</div>
</div>
</div>
<div class="section" id="improving-system-configuration">
<h2>Improving System Configuration<a class="headerlink" href="#improving-system-configuration" title="Permalink to this headline"></a></h2>
@@ -415,6 +511,8 @@ WantedBy=multi-user.target
<li><a class="reference internal" href="#the-rnstatus-utility">The rnstatus Utility</a></li>
<li><a class="reference internal" href="#the-rnpath-utility">The rnpath Utility</a></li>
<li><a class="reference internal" href="#the-rnprobe-utility">The rnprobe Utility</a></li>
<li><a class="reference internal" href="#the-rncp-utility">The rncp Utility</a></li>
<li><a class="reference internal" href="#the-rnx-utility">The rnx Utility</a></li>
</ul>
</li>
<li><a class="reference internal" href="#improving-system-configuration">Improving System Configuration</a><ul>
@@ -465,7 +563,7 @@ WantedBy=multi-user.target
<li class="right" >
<a href="gettingstartedfast.html" title="Getting Started Fast"
>previous</a> |</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.6 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.7 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">Using Reticulum on Your System</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>What is Reticulum? &#8212; Reticulum Network Stack 0.3.6 beta documentation</title>
<title>What is Reticulum? &#8212; Reticulum Network Stack 0.3.7 beta documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/classic.css" />
@@ -31,7 +31,7 @@
<li class="right" >
<a href="index.html" title="Reticulum Network Stack Manual"
accesskey="P">previous</a> |</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.6 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.7 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">What is Reticulum?</a></li>
</ul>
</div>
@@ -211,7 +211,7 @@ network, and vice versa.</p>
<li class="right" >
<a href="index.html" title="Reticulum Network Stack Manual"
>previous</a> |</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.6 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.3.7 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">What is Reticulum?</a></li>
</ul>
</div>
+1 -1
View File
@@ -22,7 +22,7 @@ copyright = '2022, Mark Qvist'
author = 'Mark Qvist'
# The full version, including alpha/beta/rc tags
release = '0.3.6 beta'
release = '0.3.7 beta'
# -- General configuration ---------------------------------------------------
+39 -13
View File
@@ -100,8 +100,8 @@ at.
To use the I2P interface, you must have an I2P router running
on your system. The easiest way to acheive this is to download and
install the `latest release <https://github.com/PurpleI2P/i2pd/releases/latest>`_
of the ``ì2pd`` package. For more details about I2P, see the
`geti2p.net website <https://geti2p.net/en/about/intro>`_.`
of the ``i2pd`` package. For more details about I2P, see the
`geti2p.net website <https://geti2p.net/en/about/intro>`_.
When an I2P router is running on your system, you can simply add
an I2P interface to reticulum:
@@ -634,25 +634,51 @@ Interface Modes
The optional ``mode`` setting is available on all interfaces, and allows
selecting the high-level behaviour of the interface from a number of modes.
These modes affect how Reticulum selects paths in the network, how announces
are propagated and how long paths are valid.
are propagated, how long paths are valid and how paths are discovered.
Configuring modes on interfaces is not strictly necessary, but can be useful
when building or connecting to more complex networks. When not running a
Transport Node, it is rarely useful to configure an interface mode.
Configuring modes on interfaces is **not** strictly necessary, but can be useful
when building or connecting to more complex networks. If your Reticulum
instance is not running a Transport Node, it is rarely useful to configure
interface modes, and in such cases interfaces should generally be left in
the default mode.
* | The default value is ``full``. In this mode, all discovery,
* | The default mode is ``full``. In this mode, all discovery,
meshing and transport functionality is activated.
* | The ``gateway`` mode (or shorthand ``gw``) also has all
discovery, meshing and transport functionality available,
but will additionally try to discover unknown paths on
behalf of other nodes residing on the ``gateway`` interface.
If Reticulum receives a path request for an unknown
destination, from a node on a ``gateway`` interface, it
will try to discover this path via all other active interfaces,
and forward the discovered path to the requestor if one is
found.
| If you want to allow other nodes to widely resolve paths or connect
to a network via an interface, it might be useful to put it in this
mode. By creating a chain of ``gateway`` interfaces, other
nodes will be able to immediately discover paths to any
destination along the chain.
| *Please note!* It is the interface *facing the clients* that
must be put into ``gateway`` mode for this to work, not
the interface facing the wider network (for this, the ``boundary``
mode can be useful, though).
* | In the ``access_point`` (or shorthand ``ap``) mode, the
interface will operate as a network access point. In this
mode, announces will not be automatically broadcasted on
the interface, and paths to destinations on the interface
will have a much shorter expiry time. This mode is useful
for creating interfaces that remain quiet, unless when
someone is actually using them. An example of this could
be a radio interface serving a wide area, where users are
expected to connect momentarily, use the network, and then
disappear again.
will have a much shorter expiry time. In addition, path
requests from clients on the access point interface will
be handled in the same way as the ``gateway`` interface.
| This mode is useful for creating interfaces that remain
quiet, until someone actually starts using them. An example
of this could be a radio interface serving a wide area,
where users are expected to connect momentarily, use the
network, and then disappear again.
* | The ``roaming`` mode should be used on interfaces that are
roaming (physically mobile), seen from the perspective of
+5 -3
View File
@@ -809,12 +809,14 @@ Announce Propagation Rules
--------------------------
The following table illustrates the rules for automatically propagating announces
from one interface type to another, for all possible combinations. See the
:ref:`Interface Modes<interfaces-modes>` section for a conceptual overview of the
different interface modes, and how they are configured.
from one interface type to another, for all possible combinations. For the purpose
of announce propagation, the *Full* and *Gateway* modes are identical.
.. image:: graphics/if_mode_graph_b.png
See the :ref:`Interface Modes<interfaces-modes>` section for a conceptual overview
of the different interface modes, and how they are configured.
..
(.. code-block:: text)
Full ────── ✓ ──┐ ┌── ✓ ── Full
+110 -3
View File
@@ -138,6 +138,10 @@ take a look at the :ref:`Interfaces<interfaces-main>` chapter of this manual.
Included Utility Programs
-------------------------
Reticulum includes a range of useful utilities, both for managing your Reticulum
networks, and for carrying out common tasks over Reticulum networks, such as
transferring files to remote systems, and executing commands and programs remotely.
If you often use Reticulum from several different programs, or simply want
Reticulum to stay available all the time, for example if you are hosting
a transport node, you might want to run Reticulum as a separate service that
@@ -251,7 +255,7 @@ destinations on the Reticulum network.
.. code:: text
usage: rnpath.py [-h] [--config CONFIG] [--version] [-t] [-r] [-d] [-D] [-w seconds] [-v] [destination]
usage: rnpath [-h] [--config CONFIG] [--version] [-t] [-r] [-d] [-D] [-w seconds] [-v] [destination]
Reticulum Path Discovery Utility
@@ -281,7 +285,7 @@ destinations will not have this option enabled, and will not be probable.
.. code:: text
# Run rnprobe
python3 -m RNS.Utilities.rnprobe example_utilities.echo.request 9382f334de63217a4278
rnprobe example_utilities.echo.request 9382f334de63217a4278
# Example output
Sent 16 byte probe to <9382f334de63217a4278>
@@ -290,7 +294,7 @@ destinations will not have this option enabled, and will not be probable.
.. code:: text
usage: rnprobe.py [-h] [--config CONFIG] [--version] [-v] [full_name] [destination_hash]
usage: rnprobe [-h] [--config CONFIG] [--version] [-v] [full_name] [destination_hash]
Reticulum Probe Utility
@@ -305,6 +309,109 @@ destinations will not have this option enabled, and will not be probable.
-v, --verbose
The rncp Utility
================
The ``rncp`` utility is a simple file transfer tool. Using it, you can transfer
files through Reticulum.
.. code:: text
# Run rncp on the receiving system, specifying which identities
# are allowed to send files
rncp --receive -a 940ea3f9e1037d38758f -a e28d5aee4317c24a9041
# From another system, copy a file to the receiving system
rncp ~/path/to/file.tgz 256320d405d6d525d1e9
You can specify as many allowed senders as needed, or complete disable authentication.
.. code:: text
usage: rncp [-h] [--config path] [-v] [-q] [-p] [-r] [-b] [-a allowed_hash] [-n] [-w seconds] [--version] [file] [destination]
Reticulum File Transfer Utility
positional arguments:
file file to be transferred
destination hexadecimal hash of the receiver
optional arguments:
-h, --help show this help message and exit
--config path path to alternative Reticulum config directory
-v, --verbose increase verbosity
-q, --quiet decrease verbosity
-p, --print-identity print identity and destination info and exit
-r, --receive wait for incoming files
-b, --no-announce don't announce at program start
-a allowed_hash accept from this identity
-n, --no-auth accept files from anyone
-w seconds sender timeout before giving up
--version show program's version number and exit
-v, --verbose
The rnx Utility
================
The ``rnx`` utility is a basic remote command execution program. It allows you to
execute commands on remote systems over Reticulum, and to view returned command
output.
.. code:: text
# Run rnx on the listening system, specifying which identities
# are allowed to execute commands
rncp --listen -a 8111c4ff2968ab0c1286 -a 590256654482b4ba4038
# From another system, run a command
rnx ad9a4c9da60089d41c29 "cat /proc/cpuinfo"
# Or enter the interactive mode pseudo-shell
rnx ad9a4c9da60089d41c29 -x
# The default identity file is stored in
# ~/.reticulum/identities/rnx, but you can use
# another one, which will be created if it does
# not already exist
rnx ad9a4c9da60089d41c29 -i /path/to/identity
You can specify as many allowed senders as needed, or complete disable authentication.
.. code:: text
usage: rnx [-h] [--config path] [-v] [-q] [-p] [-l] [-i identity] [-x] [-b] [-a allowed_hash] [-n] [-N] [-d] [-m] [-w seconds] [-W seconds] [--stdin STDIN] [--stdout STDOUT] [--stderr STDERR] [--version]
[destination] [command]
Reticulum Remote Execution Utility
positional arguments:
destination hexadecimal hash of the listener
command command to be execute
optional arguments:
-h, --help show this help message and exit
--config path path to alternative Reticulum config directory
-v, --verbose increase verbosity
-q, --quiet decrease verbosity
-p, --print-identity print identity and destination info and exit
-l, --listen listen for incoming commands
-i identity path to identity to use
-x, --interactive enter interactive mode
-b, --no-announce don't announce at program start
-a allowed_hash accept from this identity
-n, --noauth accept files from anyone
-N, --noid don't identify to listener
-d, --detailed show detailed result output
-m mirror exit code of remote command
-w seconds connect and request timeout before giving up
-W seconds max result download time
--stdin STDIN pass input to stdin
--stdout STDOUT max size in bytes of returned stdout
--stderr STDERR max size in bytes of returned stderr
--version show program's version number and exit
Improving System Configuration
------------------------------
+2 -1
View File
@@ -26,7 +26,8 @@ setuptools.setup(
'rnstatus=RNS.Utilities.rnstatus:main',
'rnprobe=RNS.Utilities.rnprobe:main',
'rnpath=RNS.Utilities.rnpath:main',
'rncp=RNS.Utilities.rncp:main',
'rnx=RNS.Utilities.rnx:main',
]
},
install_requires=['cryptography>=3.4.7', 'pyserial>=3.5', 'netifaces'],