mirror of
https://github.com/markqvist/Reticulum.git
synced 2026-06-25 05:14:28 -07:00
Compare commits
25 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e1340e87eb | |||
| e9bfef2131 | |||
| b408699e65 | |||
| 3d1c508868 | |||
| 84e0746c9c | |||
| b5658c4865 | |||
| d413a4bc53 | |||
| ce5ab902b6 | |||
| 294408b0bb | |||
| 53372fbe4c | |||
| 7fdac2118b | |||
| 1dbf78ed71 | |||
| c9101a0c21 | |||
| 2e6264c04b | |||
| e0aa46ba22 | |||
| 8093c3cd2c | |||
| c6778e4e29 | |||
| c77548d299 | |||
| 26d435ea64 | |||
| c3f0d98e41 | |||
| 3c50f4aee9 | |||
| 4a930ba82a | |||
| 866e63f0fe | |||
| d461cfa8ce | |||
| 18708636fb |
@@ -1,3 +1,50 @@
|
||||
### 2026-04-21: RNS 1.1.7
|
||||
|
||||
**Changes**
|
||||
- Added periodic known destination data cleaning based on local relevance.
|
||||
- Improved resource transfer sequencing timing calculations and reliability.
|
||||
- Improved BackboneInterface error handling on EPOLL errors.
|
||||
- Ensured non-background data persist runs synchronously.
|
||||
|
||||
**Release Hashes**
|
||||
```
|
||||
4d9702c5d9bb8a3c8b94766cb51cccad5afd78d615af9a6b146730347044e6f0 rns-1.1.7-py3-none-any.whl
|
||||
172dede7656b41b85e4319354ed04649b518e58c54586da7e443579c620a0a5b rnspure-1.1.7-py3-none-any.whl
|
||||
```
|
||||
|
||||
**Release Signatures**
|
||||
Release artifacts include `rsg` signature files that can be validated against the RNS release signing identity `<bc7291552be7a58f361522990465165c>` using `rnid`:
|
||||
|
||||
```sh
|
||||
rnid -i bc7291552be7a58f361522990465165c -V rns-1.1.7-py3-none-any.whl.rsg
|
||||
```
|
||||
|
||||
### 2026-04-18: RNS 1.1.6
|
||||
|
||||
**Changes**
|
||||
- Improved transport memory consumption.
|
||||
- Improved transport tunnel handling.
|
||||
- Improved gracious transport data persist handling.
|
||||
- Added ingress control bypass for pending path requests.
|
||||
- Added local destinations lookup map for better transport efficiency to local destinations.
|
||||
- Fixed disk I/O bound thread execution time starvation on cache management jobs.
|
||||
- Fixed invalid EPOLL modification error handler.
|
||||
- Fixed incorrect default IFAC size for autoconnected, discovered interfaces. Thanks @taprootmx!
|
||||
- Ensure loop-originating closures have variables captured at iteration-time. Thanks @taprootmx!
|
||||
|
||||
**Release Hashes**
|
||||
```
|
||||
2ce4451668f8c464295cc269188c232e7805ddd618ec0135550a5e6809df5de0 rns-1.1.6-py3-none-any.whl
|
||||
ba3e541e69a2f4892177383c8ec4e7d172d298546317e08270928c0163865aa3 rnspure-1.1.6-py3-none-any.wh
|
||||
```
|
||||
|
||||
**Release Signatures**
|
||||
Release artifacts include `rsg` signature files that can be validated against the RNS release signing identity `<bc7291552be7a58f361522990465165c>` using `rnid`:
|
||||
|
||||
```sh
|
||||
rnid -i bc7291552be7a58f361522990465165c -V rns-1.1.6-py3-none-any.whl.rsg
|
||||
```
|
||||
|
||||
### 2026-04-13: RNS 1.1.5
|
||||
|
||||
**Changes**
|
||||
@@ -21,6 +68,8 @@ Release artifacts include `rsg` signature files that can be validated against th
|
||||
rnid -i bc7291552be7a58f361522990465165c -V rns-1.1.5-py3-none-any.whl.rsg
|
||||
```
|
||||
|
||||
#
|
||||
|
||||
### 2026-03-12: RNS 1.1.4
|
||||
|
||||
**Changes**
|
||||
|
||||
+12
-22
@@ -295,33 +295,26 @@ class Destination:
|
||||
app_data = returned_app_data
|
||||
|
||||
signed_data = self.hash+self.identity.get_public_key()+self.name_hash+random_hash+ratchet
|
||||
if app_data != None:
|
||||
signed_data += app_data
|
||||
if app_data != None: signed_data += app_data
|
||||
|
||||
signature = self.identity.sign(signed_data)
|
||||
announce_data = self.identity.get_public_key()+self.name_hash+random_hash+ratchet+signature
|
||||
|
||||
if app_data != None:
|
||||
announce_data += app_data
|
||||
if app_data != None: announce_data += app_data
|
||||
|
||||
self.path_responses[tag] = [time.time(), announce_data]
|
||||
|
||||
if path_response:
|
||||
announce_context = RNS.Packet.PATH_RESPONSE
|
||||
else:
|
||||
announce_context = RNS.Packet.NONE
|
||||
if path_response: announce_context = RNS.Packet.PATH_RESPONSE
|
||||
else: announce_context = RNS.Packet.NONE
|
||||
|
||||
if ratchet:
|
||||
context_flag = RNS.Packet.FLAG_SET
|
||||
else:
|
||||
context_flag = RNS.Packet.FLAG_UNSET
|
||||
if ratchet: context_flag = RNS.Packet.FLAG_SET
|
||||
else: context_flag = RNS.Packet.FLAG_UNSET
|
||||
|
||||
announce_packet = RNS.Packet(self, announce_data, RNS.Packet.ANNOUNCE, context = announce_context,
|
||||
attached_interface = attached_interface, context_flag=context_flag)
|
||||
if send:
|
||||
announce_packet.send()
|
||||
else:
|
||||
return announce_packet
|
||||
|
||||
if send: announce_packet.send()
|
||||
else: return announce_packet
|
||||
|
||||
def accepts_links(self, accepts = None):
|
||||
"""
|
||||
@@ -330,13 +323,10 @@ class Destination:
|
||||
:param accepts: If ``True`` or ``False``, this method sets whether the destination accepts incoming link requests. If not provided or ``None``, the method returns whether the destination currently accepts link requests.
|
||||
:returns: ``True`` or ``False`` depending on whether the destination accepts incoming link requests, if the *accepts* parameter is not provided or ``None``.
|
||||
"""
|
||||
if accepts == None:
|
||||
return self.accept_link_requests
|
||||
if accepts == None: return self.accept_link_requests
|
||||
|
||||
if accepts:
|
||||
self.accept_link_requests = True
|
||||
else:
|
||||
self.accept_link_requests = False
|
||||
if accepts: self.accept_link_requests = True
|
||||
else: self.accept_link_requests = False
|
||||
|
||||
def set_link_established_callback(self, callback):
|
||||
"""
|
||||
|
||||
+130
-30
@@ -94,17 +94,25 @@ class Identity:
|
||||
known_ratchets = {}
|
||||
|
||||
ratchet_persist_lock = threading.Lock()
|
||||
known_destinations_lock = threading.Lock()
|
||||
|
||||
@staticmethod
|
||||
def remember(packet_hash, destination_hash, public_key, app_data = None):
|
||||
if len(public_key) != Identity.KEYSIZE//8:
|
||||
raise TypeError("Can't remember "+RNS.prettyhexrep(destination_hash)+", the public key size of "+str(len(public_key))+" is not valid.", RNS.LOG_ERROR)
|
||||
else:
|
||||
Identity.known_destinations[destination_hash] = [time.time(), packet_hash, public_key, app_data]
|
||||
|
||||
with Identity.known_destinations_lock:
|
||||
if not destination_hash in Identity.known_destinations:
|
||||
Identity.known_destinations[destination_hash] = [time.time(), packet_hash, public_key, app_data, 0]
|
||||
else:
|
||||
entry = Identity.known_destinations[destination_hash]
|
||||
entry[0] = time.time()
|
||||
entry[1] = packet_hash
|
||||
entry[2] = public_key
|
||||
entry[3] = app_data
|
||||
|
||||
@staticmethod
|
||||
def recall(target_hash, from_identity_hash=False):
|
||||
def recall(target_hash, from_identity_hash=False, _no_use=False):
|
||||
"""
|
||||
Recall identity for a destination or identity hash. By default, this function
|
||||
will return the identity associated with a given *destination* hash. As an
|
||||
@@ -120,6 +128,7 @@ class Identity:
|
||||
if from_identity_hash:
|
||||
for destination_hash in Identity.known_destinations:
|
||||
if target_hash == Identity.truncated_hash(Identity.known_destinations[destination_hash][2]):
|
||||
if not _no_use: RNS.Reticulum.get_instance()._used_destination_data(destination_hash)
|
||||
identity_data = Identity.known_destinations[destination_hash]
|
||||
identity = Identity(create_keys=False)
|
||||
identity.load_public_key(identity_data[2])
|
||||
@@ -130,6 +139,7 @@ class Identity:
|
||||
|
||||
else:
|
||||
if target_hash in Identity.known_destinations:
|
||||
if not _no_use: RNS.Reticulum.get_instance()._used_destination_data(target_hash)
|
||||
identity_data = Identity.known_destinations[target_hash]
|
||||
identity = Identity(create_keys=False)
|
||||
identity.load_public_key(identity_data[2])
|
||||
@@ -146,7 +156,7 @@ class Identity:
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def recall_app_data(destination_hash):
|
||||
def recall_app_data(destination_hash, _no_use=False):
|
||||
"""
|
||||
Recall last heard app_data for a destination hash.
|
||||
|
||||
@@ -154,13 +164,14 @@ class Identity:
|
||||
:returns: *Bytes* containing app_data, or *None* if the destination is unknown.
|
||||
"""
|
||||
if destination_hash in Identity.known_destinations:
|
||||
if not _no_use: RNS.Reticulum.get_instance()._used_destination_data(destination_hash)
|
||||
app_data = Identity.known_destinations[destination_hash][3]
|
||||
return app_data
|
||||
else:
|
||||
return None
|
||||
|
||||
else: return None
|
||||
|
||||
@staticmethod
|
||||
def save_known_destinations():
|
||||
def save_known_destinations(background=False, recombine=True):
|
||||
# TODO: Improve the storage method so we don't have to
|
||||
# deserialize and serialize the entire table on every
|
||||
# save, but the only changes. It might be possible to
|
||||
@@ -181,32 +192,33 @@ class Identity:
|
||||
Identity.saving_known_destinations = True
|
||||
save_start = time.time()
|
||||
|
||||
storage_known_destinations = {}
|
||||
if os.path.isfile(RNS.Reticulum.storagepath+"/known_destinations"):
|
||||
if recombine:
|
||||
storage_known_destinations = {}
|
||||
if os.path.isfile(RNS.Reticulum.storagepath+"/known_destinations"):
|
||||
try:
|
||||
with open(RNS.Reticulum.storagepath+"/known_destinations","rb") as file:
|
||||
storage_known_destinations = umsgpack.load(file)
|
||||
|
||||
except: pass
|
||||
|
||||
try:
|
||||
with open(RNS.Reticulum.storagepath+"/known_destinations","rb") as file:
|
||||
storage_known_destinations = umsgpack.load(file)
|
||||
|
||||
except: pass
|
||||
for destination_hash in storage_known_destinations:
|
||||
if not destination_hash in Identity.known_destinations:
|
||||
with Identity.known_destinations_lock:
|
||||
Identity.known_destinations[destination_hash] = storage_known_destinations[destination_hash]
|
||||
|
||||
except Exception as e:
|
||||
RNS.log("Skipped recombining known destinations from disk, since an error occurred: "+str(e), RNS.LOG_WARNING)
|
||||
|
||||
try:
|
||||
for destination_hash in storage_known_destinations:
|
||||
if not destination_hash in Identity.known_destinations:
|
||||
Identity.known_destinations[destination_hash] = storage_known_destinations[destination_hash]
|
||||
except Exception as e:
|
||||
RNS.log("Skipped recombining known destinations from disk, since an error occurred: "+str(e), RNS.LOG_WARNING)
|
||||
|
||||
RNS.log("Saving "+str(len(Identity.known_destinations))+" known destinations to storage...", RNS.LOG_DEBUG)
|
||||
RNS.log("Saving "+str(len(Identity.known_destinations))+" known destinations to storage...", RNS.LOG_VERBOSE)
|
||||
with open(RNS.Reticulum.storagepath+"/known_destinations","wb") as file:
|
||||
umsgpack.dump(Identity.known_destinations.copy(), file)
|
||||
|
||||
save_time = time.time() - save_start
|
||||
if save_time < 1:
|
||||
time_str = str(round(save_time*1000,2))+"ms"
|
||||
else:
|
||||
time_str = str(round(save_time,2))+"s"
|
||||
if save_time < 1: time_str = str(round(save_time*1000,2))+"ms"
|
||||
else: time_str = str(round(save_time,2))+"s"
|
||||
|
||||
RNS.log("Saved known destinations to storage in "+time_str, RNS.LOG_DEBUG)
|
||||
RNS.log("Saved known destinations to storage in "+time_str, RNS.LOG_VERBOSE)
|
||||
|
||||
except Exception as e:
|
||||
RNS.log("Error while saving known destinations to disk, the contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
@@ -217,6 +229,7 @@ class Identity:
|
||||
@staticmethod
|
||||
def load_known_destinations():
|
||||
if os.path.isfile(RNS.Reticulum.storagepath+"/known_destinations"):
|
||||
st = time.time()
|
||||
try:
|
||||
with open(RNS.Reticulum.storagepath+"/known_destinations","rb") as file:
|
||||
loaded_known_destinations = umsgpack.load(file)
|
||||
@@ -224,15 +237,102 @@ class Identity:
|
||||
Identity.known_destinations = {}
|
||||
for known_destination in loaded_known_destinations:
|
||||
if len(known_destination) == RNS.Reticulum.TRUNCATED_HASHLENGTH//8:
|
||||
Identity.known_destinations[known_destination] = loaded_known_destinations[known_destination]
|
||||
if len(loaded_known_destinations[known_destination]) < 5:
|
||||
e = loaded_known_destinations[known_destination]
|
||||
loaded_known_destinations[known_destination] = [e[0], e[1], e[2], e[3], 0]
|
||||
|
||||
RNS.log("Loaded "+str(len(Identity.known_destinations))+" known destination from storage", RNS.LOG_VERBOSE)
|
||||
with Identity.known_destinations_lock:
|
||||
Identity.known_destinations[known_destination] = loaded_known_destinations[known_destination]
|
||||
|
||||
RNS.log(f"Loaded {len(Identity.known_destinations)} known destination from storage in {RNS.prettyshorttime(time.time()-st)}", RNS.LOG_VERBOSE)
|
||||
|
||||
except Exception as e:
|
||||
RNS.log("Error loading known destinations from disk, file will be recreated on exit", RNS.LOG_ERROR)
|
||||
RNS.trace_exception(e)
|
||||
else:
|
||||
RNS.log("Destinations file does not exist, no known destinations loaded", RNS.LOG_VERBOSE)
|
||||
|
||||
@staticmethod
|
||||
def _used_destination_data(destination_hash):
|
||||
with Identity.known_destinations_lock:
|
||||
if destination_hash in Identity.known_destinations:
|
||||
if not Identity.known_destinations[destination_hash][4] < 0:
|
||||
Identity.known_destinations[destination_hash][4] = time.time()
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def _retain_destination_data(destination_hash):
|
||||
with Identity.known_destinations_lock:
|
||||
if destination_hash in Identity.known_destinations:
|
||||
Identity.known_destinations[destination_hash][4] = -1
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def _unretain_destination_data(destination_hash):
|
||||
with Identity.known_destinations_lock:
|
||||
if destination_hash in Identity.known_destinations:
|
||||
Identity.known_destinations[destination_hash][4] = time.time()
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def clean_known_destinations():
|
||||
now = time.time()
|
||||
st = now
|
||||
total = len(Identity.known_destinations)
|
||||
stale = []
|
||||
no_path = 0
|
||||
retained = 0
|
||||
never_used = 0
|
||||
for destination_hash in Identity.known_destinations:
|
||||
try:
|
||||
if RNS.Transport.has_path(destination_hash): has_path = True
|
||||
else:
|
||||
has_path = False
|
||||
no_path += 1
|
||||
|
||||
with Identity.known_destinations_lock:
|
||||
if destination_hash in Identity.known_destinations:
|
||||
last_announce = Identity.known_destinations[destination_hash][0]
|
||||
last_use = 0
|
||||
was_used = False
|
||||
is_retained = False
|
||||
|
||||
if Identity.known_destinations[destination_hash][4] > 0:
|
||||
was_used = True
|
||||
last_use = Identity.known_destinations[destination_hash][4]
|
||||
|
||||
elif Identity.known_destinations[destination_hash][4] == 0:
|
||||
was_used = False
|
||||
never_used += 1
|
||||
|
||||
elif Identity.known_destinations[destination_hash][4] == -1:
|
||||
is_retained = True
|
||||
retained += 1
|
||||
|
||||
unused_for = time.time() - Identity.known_destinations[destination_hash][4]
|
||||
|
||||
if not is_retained and not has_path:
|
||||
if not was_used and now - last_announce > RNS.Transport.UNUSED_DESTINATION_LINGER: stale.append(destination_hash)
|
||||
elif unused_for > RNS.Transport.DESTINATION_TIMEOUT*1.25: stale.append(destination_hash)
|
||||
|
||||
except Exception as e: RNS.log(f"Faulty entry for {RNS.prettyhexrep(destination_hash)} while cleaning known destinations: {e}", RNS.LOG_DEBUG)
|
||||
|
||||
removed = 0
|
||||
for destination_hash in stale:
|
||||
with Identity.known_destinations_lock:
|
||||
if destination_hash in Identity.known_destinations:
|
||||
Identity.known_destinations.pop(destination_hash)
|
||||
removed += 1
|
||||
|
||||
# RNS.log(f"Total destinations: {total}, stale: {len(stale)}, removed: {removed}, no path: {no_path}, never used: {never_used}, with path: {total-no_path}, used: {total-never_used}, retained: {retained}. Completed in {RNS.prettyshorttime(time.time()-st)}", RNS.LOG_WARNING) # TODO: Remove
|
||||
if not RNS.Transport.owner.is_connected_to_shared_instance: Identity.save_known_destinations(recombine=False)
|
||||
|
||||
@staticmethod
|
||||
def full_hash(data):
|
||||
"""
|
||||
@@ -491,9 +591,9 @@ class Identity:
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def persist_data():
|
||||
def persist_data(background=False):
|
||||
if not RNS.Transport.owner.is_connected_to_shared_instance:
|
||||
Identity.save_known_destinations()
|
||||
Identity.save_known_destinations(background=background)
|
||||
|
||||
@staticmethod
|
||||
def exit_handler():
|
||||
|
||||
@@ -228,10 +228,10 @@ class BackboneInterface(Interface):
|
||||
if interface.socket:
|
||||
fileno = interface.socket.fileno()
|
||||
if fileno in BackboneInterface.spawned_interface_filenos:
|
||||
try:
|
||||
BackboneInterface.epoll.modify(interface.socket.fileno(), select.EPOLLOUT)
|
||||
try: BackboneInterface.epoll.modify(fileno, select.EPOLLOUT)
|
||||
except Exception as e:
|
||||
RNS.trace_exception(e)
|
||||
RNS.log(f"Error occurred on {interface} while modifying socket EPOLL state: {e}", RNS.LOG_WARNING)
|
||||
raise e
|
||||
|
||||
@staticmethod
|
||||
def __job():
|
||||
@@ -270,8 +270,7 @@ class BackboneInterface(Interface):
|
||||
spawned_interface.receive(received_bytes)
|
||||
|
||||
elif client_socket and fileno == client_socket.fileno() and (event & select.EPOLLOUT):
|
||||
try:
|
||||
written = client_socket.send(spawned_interface.transmit_buffer)
|
||||
try: written = client_socket.send(spawned_interface.transmit_buffer)
|
||||
except Exception as e:
|
||||
written = 0
|
||||
if not spawned_interface.detached: RNS.log(f"Error while writing to {spawned_interface}: {e}", RNS.LOG_DEBUG)
|
||||
@@ -293,7 +292,11 @@ class BackboneInterface(Interface):
|
||||
spawned_interface.receive(b"")
|
||||
|
||||
spawned_interface.transmit_buffer = spawned_interface.transmit_buffer[written:]
|
||||
if len(spawned_interface.transmit_buffer) == 0: BackboneInterface.epoll.modify(fileno, select.EPOLLIN)
|
||||
try:
|
||||
if len(spawned_interface.transmit_buffer) == 0: BackboneInterface.epoll.modify(fileno, select.EPOLLIN)
|
||||
except Exception as e:
|
||||
RNS.log(f"Error while setting EPOLLIN on {spawned_interface}: {e}", RNS.LOG_ERROR)
|
||||
|
||||
spawned_interface.txb += written
|
||||
if spawned_interface.parent_interface: spawned_interface.parent_interface.txb += written
|
||||
|
||||
|
||||
@@ -55,8 +55,8 @@ class Interface:
|
||||
|
||||
# How many samples to use for announce
|
||||
# frequency calculations
|
||||
IA_FREQ_SAMPLES = 12
|
||||
OA_FREQ_SAMPLES = 12
|
||||
IA_FREQ_SAMPLES = 128
|
||||
OA_FREQ_SAMPLES = 128
|
||||
|
||||
# Maximum amount of ingress limited announces
|
||||
# to hold at any given time.
|
||||
@@ -66,12 +66,12 @@ class Interface:
|
||||
# considered to be newly created. Two
|
||||
# hours by default.
|
||||
IC_NEW_TIME = 2*60*60
|
||||
IC_BURST_FREQ_NEW = 3.5
|
||||
IC_BURST_FREQ = 12
|
||||
IC_BURST_FREQ_NEW = 6
|
||||
IC_BURST_FREQ = 35
|
||||
IC_BURST_HOLD = 1*60
|
||||
IC_BURST_PENALTY = 5*60
|
||||
IC_HELD_RELEASE_INTERVAL = 30
|
||||
IC_DEQUE_MIN_SAMPLE = 8
|
||||
IC_BURST_PENALTY = 15
|
||||
IC_HELD_RELEASE_INTERVAL = 2
|
||||
IC_DEQUE_MIN_SAMPLE = 32
|
||||
|
||||
AUTOCONFIGURE_MTU = False
|
||||
FIXED_MTU = False
|
||||
@@ -123,13 +123,14 @@ class Interface:
|
||||
if self.ic_burst_active:
|
||||
if ia_freq < freq_threshold and time.time() > self.ic_burst_activated+self.ic_burst_hold:
|
||||
self.ic_burst_active = False
|
||||
self.ic_held_release = time.time() + self.ic_burst_penalty
|
||||
|
||||
return True
|
||||
|
||||
else:
|
||||
if ia_freq > freq_threshold:
|
||||
self.ic_burst_active = True
|
||||
self.ic_burst_activated = time.time()
|
||||
self.ic_held_release = time.time() + self.ic_burst_penalty
|
||||
return True
|
||||
|
||||
else: return False
|
||||
@@ -174,7 +175,7 @@ class Interface:
|
||||
|
||||
def process_held_announces(self):
|
||||
try:
|
||||
if not self.should_ingress_limit() and len(self.held_announces) > 0 and time.time() > self.ic_held_release:
|
||||
if len(self.held_announces) > 0 and time.time() > self.ic_held_release:
|
||||
freq_threshold = self.ic_burst_freq_new if self.age() < self.ic_new_time else self.ic_burst_freq
|
||||
ia_freq = self.incoming_announce_frequency()
|
||||
if ia_freq < freq_threshold:
|
||||
|
||||
@@ -328,7 +328,8 @@ class LocalClientInterface(Interface):
|
||||
if hasattr(self, "parent_interface") and self.parent_interface != None:
|
||||
self.parent_interface.clients -= 1
|
||||
if hasattr(RNS.Transport, "owner") and RNS.Transport.owner != None:
|
||||
RNS.Transport.owner._should_persist_data()
|
||||
background = not self.detached
|
||||
RNS.Transport.owner._should_persist_data(background=background)
|
||||
|
||||
if nowarning == False:
|
||||
RNS.log("The interface "+str(self)+" experienced an unrecoverable error and is being torn down. Restart Reticulum to attempt to open this interface again.", RNS.LOG_ERROR)
|
||||
|
||||
+5
-9
@@ -722,12 +722,9 @@ class Link:
|
||||
pass
|
||||
|
||||
def link_closed(self):
|
||||
for resource in self.incoming_resources:
|
||||
resource.cancel()
|
||||
for resource in self.outgoing_resources:
|
||||
resource.cancel()
|
||||
if self._channel:
|
||||
self._channel._shutdown()
|
||||
for resource in self.incoming_resources: resource.cancel()
|
||||
for resource in self.outgoing_resources: resource.cancel()
|
||||
if self._channel: self._channel._shutdown()
|
||||
|
||||
self.prv = None
|
||||
self.pub = None
|
||||
@@ -741,8 +738,7 @@ class Link:
|
||||
self.destination.links.remove(self)
|
||||
|
||||
if self.callbacks.link_closed != None:
|
||||
try:
|
||||
self.callbacks.link_closed(self)
|
||||
try: self.callbacks.link_closed(self)
|
||||
except Exception as e:
|
||||
RNS.log("Error while executing link closed callback from "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
|
||||
@@ -1181,7 +1177,7 @@ class Link:
|
||||
resource_hash = packet.data[0:RNS.Identity.HASHLENGTH//8]
|
||||
for resource in self.outgoing_resources:
|
||||
if resource_hash == resource.hash:
|
||||
def job(): resource.validate_proof(packet.data)
|
||||
def job(resource=resource): resource.validate_proof(packet.data)
|
||||
threading.Thread(target=job, daemon=True).start()
|
||||
self.__update_phy_stats(packet, query_shared=True)
|
||||
|
||||
|
||||
+2
-2
@@ -293,7 +293,7 @@ class Packet:
|
||||
|
||||
if RNS.Transport.outbound(self): return self.receipt
|
||||
else:
|
||||
RNS.log("No interfaces could process the outbound packet", RNS.LOG_ERROR)
|
||||
RNS.log("No interfaces could process the outbound packet", RNS.LOG_DEBUG)
|
||||
self.sent = False
|
||||
self.receipt = None
|
||||
return False
|
||||
@@ -315,7 +315,7 @@ class Packet:
|
||||
if RNS.Transport.outbound(self):
|
||||
return self.receipt
|
||||
else:
|
||||
RNS.log("No interfaces could process the outbound packet", RNS.LOG_ERROR)
|
||||
RNS.log("Re-send failed. No interfaces could process the outbound packet", RNS.LOG_WARNING)
|
||||
self.sent = False
|
||||
self.receipt = None
|
||||
return False
|
||||
|
||||
+5
-2
@@ -126,6 +126,7 @@ class Resource:
|
||||
PART_TIMEOUT_FACTOR = 4
|
||||
PART_TIMEOUT_FACTOR_AFTER_RTT = 2
|
||||
PROOF_TIMEOUT_FACTOR = 3
|
||||
HMU_WAIT_FACTOR = 3.5
|
||||
MAX_RETRIES = 16
|
||||
MAX_ADV_RETRIES = 4
|
||||
SENDER_GRACE_TIME = 10.0
|
||||
@@ -594,15 +595,16 @@ class Resource:
|
||||
extra_wait = retries_used * Resource.PER_RETRY_DELAY
|
||||
|
||||
self.update_eifr()
|
||||
expected_hmu_wait_remaining = (self.sdu*8*self.HMU_WAIT_FACTOR)/self.eifr if self.waiting_for_hmu or self.outstanding_parts == 0 else 0
|
||||
expected_tof_remaining = (self.outstanding_parts*self.sdu*8)/self.eifr
|
||||
|
||||
if self.req_resp_rtt_rate != 0:
|
||||
sleep_time = self.last_activity + self.part_timeout_factor*expected_tof_remaining + Resource.RETRY_GRACE_TIME + extra_wait - time.time()
|
||||
sleep_time = self.last_activity + self.part_timeout_factor*expected_tof_remaining + expected_hmu_wait_remaining + Resource.RETRY_GRACE_TIME + extra_wait - time.time()
|
||||
else:
|
||||
sleep_time = self.last_activity + self.part_timeout_factor*((3*self.sdu)/self.eifr) + Resource.RETRY_GRACE_TIME + extra_wait - time.time()
|
||||
|
||||
# TODO: Remove debug at some point
|
||||
# RNS.log(f"EIFR {RNS.prettyspeed(self.eifr)}, ETOF {RNS.prettyshorttime(expected_tof_remaining)} ", RNS.LOG_DEBUG, pt=True)
|
||||
# RNS.log(f"EIFR {RNS.prettyspeed(self.eifr)}, ETOF {RNS.prettyshorttime(expected_tof_remaining)}, EHWR {RNS.prettyshorttime(expected_hmu_wait_remaining)}", RNS.LOG_DEBUG, pt=True)
|
||||
# RNS.log(f"Resource ST {RNS.prettyshorttime(sleep_time)}, RTT {RNS.prettyshorttime(self.rtt or self.link.rtt)}, {self.outstanding_parts} left", RNS.LOG_DEBUG, pt=True)
|
||||
|
||||
if sleep_time < 0:
|
||||
@@ -959,6 +961,7 @@ class Resource:
|
||||
self.last_activity = time.time()
|
||||
self.req_sent = self.last_activity
|
||||
self.req_sent_bytes = len(request_packet.raw)
|
||||
self.rtt_rxd_bytes_at_part_req = self.rtt_rxd_bytes
|
||||
self.req_resp = None
|
||||
|
||||
except Exception as e:
|
||||
|
||||
+55
-14
@@ -47,6 +47,7 @@ else:
|
||||
from RNS.Interfaces import *
|
||||
|
||||
from RNS.vendor.configobj import ConfigObj
|
||||
from threading import Lock
|
||||
import configparser
|
||||
import multiprocessing.connection
|
||||
import importlib.util
|
||||
@@ -171,6 +172,8 @@ class Reticulum:
|
||||
cachepath = ""
|
||||
interfacepath = ""
|
||||
|
||||
gracious_persist_lock = Lock()
|
||||
|
||||
__instance = None
|
||||
|
||||
__interface_detach_ran = False
|
||||
@@ -361,11 +364,11 @@ class Reticulum:
|
||||
now = time.time()
|
||||
|
||||
if now > self.last_cache_clean+Reticulum.CLEAN_INTERVAL:
|
||||
self.__clean_caches()
|
||||
self.__clean_caches(background=True)
|
||||
self.last_cache_clean = time.time()
|
||||
|
||||
if now > self.last_data_persist+Reticulum.PERSIST_INTERVAL:
|
||||
self.__persist_data()
|
||||
self.__persist_data(background=True)
|
||||
|
||||
time.sleep(Reticulum.JOB_INTERVAL)
|
||||
|
||||
@@ -960,7 +963,7 @@ class Reticulum:
|
||||
interface.optimise_mtu()
|
||||
|
||||
if ifac_size != None: interface.ifac_size = ifac_size
|
||||
else: interface.ifac_size = 8
|
||||
else: interface.ifac_size = interface.DEFAULT_IFAC_SIZE
|
||||
|
||||
interface.announce_cap = announce_cap if announce_cap != None else Reticulum.ANNOUNCE_CAP/100.0
|
||||
interface.announce_rate_target = announce_rate_target
|
||||
@@ -993,16 +996,20 @@ class Reticulum:
|
||||
RNS.Transport.interfaces.append(interface)
|
||||
interface.final_init()
|
||||
|
||||
def _should_persist_data(self):
|
||||
def _should_persist_data(self, background=False):
|
||||
if time.time() > self.last_data_persist+Reticulum.GRACIOUS_PERSIST_INTERVAL:
|
||||
self.__persist_data()
|
||||
def job(): self.__persist_data(background=background)
|
||||
if background: threading.Thread(target=job, daemon=True).start()
|
||||
else: job()
|
||||
|
||||
def __persist_data(self):
|
||||
RNS.Transport.persist_data()
|
||||
RNS.Identity.persist_data()
|
||||
self.last_data_persist = time.time()
|
||||
def __persist_data(self, background=False):
|
||||
if Reticulum.gracious_persist_lock.locked(): return
|
||||
with Reticulum.gracious_persist_lock:
|
||||
RNS.Transport.persist_data(background=background)
|
||||
RNS.Identity.persist_data(background=background)
|
||||
self.last_data_persist = time.time()
|
||||
|
||||
def __clean_caches(self):
|
||||
def __clean_caches(self, background=False):
|
||||
RNS.log("Cleaning resource and packet caches...", RNS.LOG_EXTREME)
|
||||
now = time.time()
|
||||
|
||||
@@ -1013,8 +1020,8 @@ class Reticulum:
|
||||
filepath = self.resourcepath + "/" + filename
|
||||
mtime = os.path.getmtime(filepath)
|
||||
age = now - mtime
|
||||
if age > Reticulum.RESOURCE_CACHE:
|
||||
os.unlink(filepath)
|
||||
if age > Reticulum.RESOURCE_CACHE: os.unlink(filepath)
|
||||
if background: time.sleep(0.001)
|
||||
|
||||
except Exception as e:
|
||||
RNS.log("Error while cleaning resources cache, the contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
@@ -1026,8 +1033,8 @@ class Reticulum:
|
||||
filepath = self.cachepath + "/" + filename
|
||||
mtime = os.path.getmtime(filepath)
|
||||
age = now - mtime
|
||||
if age > RNS.Transport.DESTINATION_TIMEOUT:
|
||||
os.unlink(filepath)
|
||||
if age > RNS.Transport.DESTINATION_TIMEOUT: os.unlink(filepath)
|
||||
if background: time.sleep(0.001)
|
||||
|
||||
except Exception as e:
|
||||
RNS.log("Error while cleaning resources cache, the contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
@@ -1080,6 +1087,13 @@ class Reticulum:
|
||||
identity_hash = call["unblackhole_identity"]
|
||||
rpc_connection.send(self.unblackhole_identity(identity_hash))
|
||||
|
||||
if "destination_data" in call:
|
||||
operation = call["destination_data"]
|
||||
destination_hash = call["destination_hash"]
|
||||
if operation == "used": rpc_connection.send(self._used_destination_data(destination_hash))
|
||||
elif operation == "retain": rpc_connection.send(self._retain_destination_data(destination_hash))
|
||||
elif operation == "unretain": rpc_connection.send(self._unretain_destination_data(destination_hash))
|
||||
|
||||
rpc_connection.close()
|
||||
|
||||
except Exception as e:
|
||||
@@ -1087,6 +1101,33 @@ class Reticulum:
|
||||
|
||||
def get_rpc_client(self): return multiprocessing.connection.Client(self.rpc_addr, family=self.rpc_type, authkey=self.rpc_key)
|
||||
|
||||
def _used_destination_data(self, destination_hash):
|
||||
if self.is_connected_to_shared_instance:
|
||||
rpc_connection = self.get_rpc_client()
|
||||
rpc_connection.send({"destination_data": "used", "destination_hash": destination_hash})
|
||||
response = rpc_connection.recv()
|
||||
return response
|
||||
|
||||
else: return RNS.Identity._used_destination_data(destination_hash)
|
||||
|
||||
def _retain_destination_data(self, destination_hash):
|
||||
if self.is_connected_to_shared_instance:
|
||||
rpc_connection = self.get_rpc_client()
|
||||
rpc_connection.send({"destination_data": "retain", "destination_hash": destination_hash})
|
||||
response = rpc_connection.recv()
|
||||
return response
|
||||
|
||||
else: return RNS.Identity._retain_destination_data(destination_hash)
|
||||
|
||||
def _unretain_destination_data(self, destination_hash):
|
||||
if self.is_connected_to_shared_instance:
|
||||
rpc_connection = self.get_rpc_client()
|
||||
rpc_connection.send({"destination_data": "unretain", "destination_hash": destination_hash})
|
||||
response = rpc_connection.recv()
|
||||
return response
|
||||
|
||||
else: return RNS.Identity._unretain_destination_data(destination_hash)
|
||||
|
||||
def get_interface_stats(self):
|
||||
if self.is_connected_to_shared_instance:
|
||||
rpc_connection = self.get_rpc_client()
|
||||
|
||||
+370
-207
@@ -76,6 +76,7 @@ class Transport:
|
||||
LOCAL_REBROADCASTS_MAX = 2 # How many local rebroadcasts of an announce is allowed
|
||||
|
||||
PATH_REQUEST_TIMEOUT = 15 # Default timeout for client path requests in seconds
|
||||
PATH_REQUEST_GATE_TIMEOUT = 120 # Default timeout for client path request gate control in seconds
|
||||
PATH_REQUEST_GRACE = 0.4 # Grace time before a path announcement is made, allows directly reachable peers to respond first
|
||||
PATH_REQUEST_RG = 1.5 # Extra grace time for roaming-mode interfaces to allow more suitable peers to respond first
|
||||
PATH_REQUEST_MI = 20 # Minimum interval in seconds for automated path requests
|
||||
@@ -87,6 +88,9 @@ class Transport:
|
||||
LINK_TIMEOUT = RNS.Link.STALE_TIME * 1.25
|
||||
REVERSE_TIMEOUT = 8*60 # Reverse table entries are removed after 8 minutes
|
||||
DESTINATION_TIMEOUT = 60*60*24*7 # Destination table entries are removed if unused for one week
|
||||
UNUSED_DESTINATION_LINGER = 6*60 # Linger time for pathless and never used destinations
|
||||
TUNNEL_TIMEOUT = 60*60*8 # Tunnel table entries are removed if unused for eight hours
|
||||
TUNNEL_PATH_TIMEOUT = 60*60*8 # Tunnel path table entries are removed if unused for eight hours
|
||||
MAX_RECEIPTS = 1024 # Maximum number of receipts to keep track of
|
||||
MAX_RATE_TIMESTAMPS = 16 # Maximum number of announce timestamps to keep per destination
|
||||
PERSIST_RANDOM_BLOBS = 32 # Maximum number of random blobs per destination to persist to disk
|
||||
@@ -94,6 +98,7 @@ class Transport:
|
||||
|
||||
interfaces = [] # All active interfaces
|
||||
destinations = [] # All active destinations
|
||||
destinations_map = {} # Destination hash map of active destinations
|
||||
pending_links = [] # Links that are being established
|
||||
active_links = [] # Links that are active
|
||||
packet_hashlist = set() # A list of packet hashes for duplicate detection
|
||||
@@ -119,7 +124,9 @@ class Transport:
|
||||
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
|
||||
|
||||
interfaces_lock = Lock()
|
||||
destinations_lock = Lock()
|
||||
destinations_map_lock = Lock()
|
||||
inbound_announce_lock = Lock()
|
||||
announce_table_lock = Lock()
|
||||
announce_rate_table_lock = Lock()
|
||||
@@ -137,6 +144,7 @@ class Transport:
|
||||
pending_local_prs_lock = Lock()
|
||||
path_states_lock = Lock()
|
||||
jobs_lock = Lock()
|
||||
cache_clean_lock = Lock()
|
||||
|
||||
# Transport control destinations are used
|
||||
# for control purposes like path requests
|
||||
@@ -171,6 +179,8 @@ class Transport:
|
||||
pending_prs_check_interval = 30.0
|
||||
cache_last_cleaned = 0.0
|
||||
cache_clean_interval = 5*60
|
||||
destinations_last_cleaned = 0.0
|
||||
known_destinations_interval = 5*60
|
||||
tables_last_culled = 0.0
|
||||
tables_cull_interval = 5.0
|
||||
interface_last_jobs = 0.0
|
||||
@@ -257,6 +267,9 @@ class Transport:
|
||||
|
||||
# Defer cleaning packet cache for 60 seconds
|
||||
Transport.cache_last_cleaned = time.time() + 60
|
||||
|
||||
# Defer cleaning known destinations
|
||||
Transport.destinations_last_cleaned = time.time()
|
||||
|
||||
# Defer sending management announces for 15 seconds
|
||||
Transport.last_mgmt_announce = time.time() - Transport.mgmt_announce_interval + 15
|
||||
@@ -291,7 +304,7 @@ class Transport:
|
||||
blackholed = False
|
||||
|
||||
if len(Transport.blackholed_identities) > 0:
|
||||
path_identity = RNS.Identity.recall(destination_hash)
|
||||
path_identity = RNS.Identity.recall(destination_hash, _no_use=True)
|
||||
if path_identity in Transport.blackholed_identities: blackholed = True
|
||||
del path_identity
|
||||
|
||||
@@ -302,7 +315,8 @@ class Transport:
|
||||
# over an interface. It is cached with it's non-
|
||||
# increased hop-count.
|
||||
announce_packet.hops += 1
|
||||
Transport.path_table[destination_hash] = [timestamp, received_from, hops, expires, random_blobs, receiving_interface, announce_packet.packet_hash]
|
||||
with Transport.path_table_lock:
|
||||
Transport.path_table[destination_hash] = [timestamp, received_from, hops, expires, random_blobs, receiving_interface, announce_packet.packet_hash]
|
||||
RNS.log("Loaded path table entry for "+RNS.prettyhexrep(destination_hash)+" from storage", RNS.LOG_DEBUG)
|
||||
else:
|
||||
RNS.log("Could not reconstruct path table entry from storage for "+RNS.prettyhexrep(destination_hash), RNS.LOG_DEBUG)
|
||||
@@ -360,10 +374,10 @@ class Transport:
|
||||
|
||||
if len(tunnel_paths) > 0:
|
||||
tunnel = [tunnel_id, None, tunnel_paths, expires]
|
||||
Transport.tunnels[tunnel_id] = tunnel
|
||||
with Transport.tunnels_lock: Transport.tunnels[tunnel_id] = tunnel
|
||||
|
||||
if len(Transport.path_table) == 1: specifier = "entry"
|
||||
else: specifier = "entries"
|
||||
if len(Transport.tunnels) == 1: specifier = "entry"
|
||||
else: specifier = "entries"
|
||||
|
||||
RNS.log("Loaded "+str(len(Transport.tunnels))+" tunnel table "+specifier+" from storage", RNS.LOG_VERBOSE)
|
||||
gc.collect()
|
||||
@@ -407,8 +421,9 @@ class Transport:
|
||||
|
||||
@staticmethod
|
||||
def prioritize_interfaces():
|
||||
try: Transport.interfaces.sort(key=lambda interface: interface.bitrate, reverse=True)
|
||||
except Exception as e: RNS.log(f"Could not prioritize interfaces according to bitrate. The contained exception was: {e}", RNS.LOG_ERROR)
|
||||
with Transport.interfaces_lock:
|
||||
try: Transport.interfaces.sort(key=lambda interface: interface.bitrate, reverse=True)
|
||||
except Exception as e: RNS.log(f"Could not prioritize interfaces according to bitrate. The contained exception was: {e}", RNS.LOG_ERROR)
|
||||
|
||||
@staticmethod
|
||||
def enable_discovery():
|
||||
@@ -558,7 +573,7 @@ class Transport:
|
||||
announce_context = RNS.Packet.NONE
|
||||
if block_rebroadcasts: announce_context = RNS.Packet.PATH_RESPONSE
|
||||
announce_data = packet.data
|
||||
announce_identity = RNS.Identity.recall(packet.destination_hash)
|
||||
announce_identity = RNS.Identity.recall(packet.destination_hash, _no_use=True)
|
||||
announce_destination = RNS.Destination(announce_identity, RNS.Destination.OUT, RNS.Destination.SINGLE, "unknown", "unknown");
|
||||
announce_destination.hash = packet.destination_hash
|
||||
announce_destination.hexhash = announce_destination.hash.hex()
|
||||
@@ -607,10 +622,14 @@ class Transport:
|
||||
|
||||
# Cull invalidated path requests
|
||||
if time.time() > Transport.pending_prs_last_checked+Transport.pending_prs_check_interval:
|
||||
stale_local_prs = []
|
||||
with Transport.pending_local_prs_lock:
|
||||
for destination_hash in Transport.pending_local_path_requests:
|
||||
if not Transport.pending_local_path_requests[destination_hash] in Transport.interfaces:
|
||||
Transport.pending_local_path_requests.pop(destination_hash)
|
||||
stale_local_prs.append(destination_hash)
|
||||
|
||||
for destination_hash in stale_local_prs:
|
||||
Transport.pending_local_path_requests.pop(destination_hash)
|
||||
|
||||
Transport.pending_prs_last_checked = time.time()
|
||||
|
||||
@@ -744,6 +763,14 @@ class Transport:
|
||||
should_collect = True
|
||||
RNS.log("Path to "+RNS.prettyhexrep(destination_hash)+" was removed since the attached interface no longer exists", RNS.LOG_DEBUG)
|
||||
|
||||
# Cull the pending path requests table
|
||||
stale_path_requests = []
|
||||
with Transport.path_requests_lock:
|
||||
for destination_hash in Transport.path_requests:
|
||||
if time.time() > Transport.path_requests[destination_hash] + Transport.PATH_REQUEST_GATE_TIMEOUT:
|
||||
stale_path_requests.append(destination_hash)
|
||||
RNS.log("Path request entry for "+RNS.prettyhexrep(destination_hash)+" timed out and was removed", RNS.LOG_EXTREME)
|
||||
|
||||
# Cull the pending discovery path requests table
|
||||
stale_discovery_path_requests = []
|
||||
with Transport.discovery_pr_lock:
|
||||
@@ -762,9 +789,12 @@ class Transport:
|
||||
tunnel_entry = Transport.tunnels[tunnel_id]
|
||||
|
||||
expires = tunnel_entry[IDX_TT_EXPIRES]
|
||||
if time.time() > expires:
|
||||
stale_tunnels.append(tunnel_id)
|
||||
should_collect = True
|
||||
if expires > time.time() + Transport.TUNNEL_TIMEOUT*2:
|
||||
stale_tunnels.append(tunnel_id); should_collect = True
|
||||
RNS.log("Tunnel "+RNS.prettyhexrep(tunnel_id)+" with excessive expiry was removed", RNS.LOG_EXTREME)
|
||||
|
||||
elif time.time() > expires:
|
||||
stale_tunnels.append(tunnel_id); should_collect = True
|
||||
RNS.log("Tunnel "+RNS.prettyhexrep(tunnel_id)+" timed out and was removed", RNS.LOG_EXTREME)
|
||||
|
||||
else:
|
||||
@@ -777,11 +807,25 @@ class Transport:
|
||||
for tunnel_path in tunnel_paths:
|
||||
tunnel_path_entry = tunnel_paths[tunnel_path]
|
||||
|
||||
if time.time() > tunnel_path_entry[0] + Transport.DESTINATION_TIMEOUT:
|
||||
stale_tunnel_paths.append(tunnel_path)
|
||||
should_collect = True
|
||||
if time.time() > tunnel_path_entry[0] + Transport.TUNNEL_PATH_TIMEOUT:
|
||||
stale_tunnel_paths.append(tunnel_path); should_collect = True
|
||||
RNS.log("Tunnel path to "+RNS.prettyhexrep(tunnel_path)+" timed out and was removed", RNS.LOG_EXTREME)
|
||||
|
||||
else:
|
||||
active_path = None
|
||||
with Transport.path_table_lock:
|
||||
if tunnel_path in Transport.path_table: active_path = Transport.path_table[tunnel_path]
|
||||
|
||||
if active_path:
|
||||
random_blobs = tunnel_path_entry[4]
|
||||
current_random_blobs = active_path[IDX_PT_RANDBLOBS]
|
||||
current_path_timebase = Transport.timebase_from_random_blobs(current_random_blobs)
|
||||
tunnel_announce_timebase = Transport.timebase_from_random_blobs(random_blobs)
|
||||
|
||||
if current_path_timebase > tunnel_announce_timebase:
|
||||
stale_tunnel_paths.append(tunnel_path); should_collect = True
|
||||
RNS.log("Tunnel path to "+RNS.prettyhexrep(tunnel_path)+" was removed due to more recent active path", RNS.LOG_EXTREME)
|
||||
|
||||
for tunnel_path in stale_tunnel_paths:
|
||||
tunnel_paths.pop(tunnel_path)
|
||||
ti += 1
|
||||
@@ -820,6 +864,16 @@ class Transport:
|
||||
if i == 1: RNS.log("Removed "+str(i)+" path", RNS.LOG_EXTREME)
|
||||
else: RNS.log("Removed "+str(i)+" paths", RNS.LOG_EXTREME)
|
||||
|
||||
i = 0
|
||||
with Transport.path_requests_lock:
|
||||
for destination_hash in stale_path_requests:
|
||||
Transport.path_requests.pop(destination_hash)
|
||||
i += 1
|
||||
|
||||
if i > 0:
|
||||
if i == 1: RNS.log("Removed "+str(i)+" path request entry", RNS.LOG_EXTREME)
|
||||
else: RNS.log("Removed "+str(i)+" path request entries", RNS.LOG_EXTREME)
|
||||
|
||||
i = 0
|
||||
with Transport.discovery_pr_lock:
|
||||
for destination_hash in stale_discovery_path_requests:
|
||||
@@ -864,7 +918,15 @@ class Transport:
|
||||
|
||||
# Clean packet caches
|
||||
if time.time() > Transport.cache_last_cleaned+Transport.cache_clean_interval:
|
||||
Transport.clean_cache()
|
||||
Transport.cache_last_cleaned = time.time()
|
||||
def job(): Transport.clean_cache()
|
||||
threading.Thread(target=job, daemon=True).start()
|
||||
|
||||
# Clean known destinations
|
||||
if time.time() > Transport.destinations_last_cleaned+Transport.known_destinations_interval:
|
||||
Transport.destinations_last_cleaned = time.time()
|
||||
def job(): RNS.Identity.clean_known_destinations()
|
||||
threading.Thread(target=job, daemon=True).start()
|
||||
|
||||
# Send announces for management destinations
|
||||
if time.time() > Transport.last_mgmt_announce+Transport.mgmt_announce_interval:
|
||||
@@ -908,6 +970,7 @@ class Transport:
|
||||
except Exception as e:
|
||||
RNS.log("An exception occurred while running Transport jobs.", RNS.LOG_ERROR)
|
||||
RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
RNS.trace_exception(e) # TODO: Remove
|
||||
|
||||
for packet in outgoing: packet.send()
|
||||
|
||||
@@ -1068,8 +1131,10 @@ class Transport:
|
||||
should_transmit = False
|
||||
|
||||
elif interface.mode == RNS.Interfaces.Interface.Interface.MODE_ROAMING:
|
||||
with Transport.destinations_lock:
|
||||
local_destination = next((d for d in Transport.destinations if d.hash == packet.destination_hash), None)
|
||||
local_destination = None
|
||||
with Transport.destinations_map_lock:
|
||||
if packet.destination_hash in Transport.destinations_map:
|
||||
local_destination = Transport.destinations_map[packet.destination_hash]
|
||||
|
||||
if local_destination != None:
|
||||
# RNS.log("Allowing announce broadcast on roaming-mode interface from instance-local destination", RNS.LOG_EXTREME)
|
||||
@@ -1091,8 +1156,11 @@ class Transport:
|
||||
should_transmit = False
|
||||
|
||||
elif interface.mode == RNS.Interfaces.Interface.Interface.MODE_BOUNDARY:
|
||||
with Transport.destinations_lock:
|
||||
local_destination = next((d for d in Transport.destinations if d.hash == packet.destination_hash), None)
|
||||
local_destination = None
|
||||
with Transport.destinations_map_lock:
|
||||
if packet.destination_hash in Transport.destinations_map:
|
||||
local_destination = Transport.destinations_map[packet.destination_hash]
|
||||
|
||||
if local_destination != None:
|
||||
# RNS.log("Allowing announce broadcast on boundary-mode interface from instance-local destination", RNS.LOG_EXTREME)
|
||||
pass
|
||||
@@ -1553,12 +1621,19 @@ class Transport:
|
||||
# potential ingress limiting. Already known
|
||||
# destinations will have re-announces controlled
|
||||
# by normal announce rate limiting.
|
||||
if interface.should_ingress_limit():
|
||||
if packet.destination_hash in Transport.path_requests or packet.destination_hash in Transport.discovery_path_requests:
|
||||
# RNS.log(f"Skipping ingress limit check for {RNS.prettyhexrep(packet.destination_hash)} due to waiting path requests", RNS.LOG_DEBUG)
|
||||
pass
|
||||
|
||||
elif interface.should_ingress_limit():
|
||||
interface.hold_announce(packet)
|
||||
return
|
||||
|
||||
with Transport.destinations_lock:
|
||||
local_destination = next((d for d in Transport.destinations if d.hash == packet.destination_hash), None)
|
||||
local_destination = None
|
||||
with Transport.destinations_map_lock:
|
||||
if packet.destination_hash in Transport.destinations_map:
|
||||
local_destination = Transport.destinations_map[packet.destination_hash]
|
||||
|
||||
if local_destination == None and RNS.Identity.validate_announce(packet):
|
||||
if packet.transport_id != None:
|
||||
received_from = packet.transport_id
|
||||
@@ -1575,14 +1650,15 @@ class Transport:
|
||||
if announce_entry[IDX_AT_RETRIES] > 0:
|
||||
if announce_entry[IDX_AT_LCL_RBRD] >= Transport.LOCAL_REBROADCASTS_MAX:
|
||||
RNS.log("Completed announce processing for "+RNS.prettyhexrep(packet.destination_hash)+", local rebroadcast limit reached", RNS.LOG_EXTREME)
|
||||
if packet.destination_hash in Transport.announce_table: Transport.announce_table.pop(packet.destination_hash)
|
||||
with Transport.announce_table_lock:
|
||||
if packet.destination_hash in Transport.announce_table: Transport.announce_table.pop(packet.destination_hash)
|
||||
|
||||
if packet.hops-1 == announce_entry[IDX_AT_HOPS]+1 and announce_entry[IDX_AT_RETRIES] > 0:
|
||||
now = time.time()
|
||||
if now < announce_entry[IDX_AT_RTRNS_TMO]:
|
||||
RNS.log("Rebroadcasted announce for "+RNS.prettyhexrep(packet.destination_hash)+" has been passed on to another node, no further tries needed", RNS.LOG_EXTREME)
|
||||
if packet.destination_hash in Transport.announce_table:
|
||||
Transport.announce_table.pop(packet.destination_hash)
|
||||
with Transport.announce_table_lock:
|
||||
if packet.destination_hash in Transport.announce_table: Transport.announce_table.pop(packet.destination_hash)
|
||||
|
||||
else:
|
||||
received_from = packet.destination_hash
|
||||
@@ -1593,7 +1669,9 @@ class Transport:
|
||||
|
||||
# First, check that the announce is not for a destination
|
||||
# local to this system, and that hops are less than the max
|
||||
with Transport.destinations_lock: local_and_hops_condition = (not any(packet.destination_hash == d.hash for d in Transport.destinations) and packet.hops < Transport.PATHFINDER_M+1)
|
||||
with Transport.destinations_map_lock:
|
||||
local_and_hops_condition = (packet.hops < Transport.PATHFINDER_M+1) and (not packet.destination_hash in Transport.destinations_map)
|
||||
|
||||
if local_and_hops_condition:
|
||||
announce_emitted = Transport.announce_emitted(packet)
|
||||
|
||||
@@ -1706,9 +1784,7 @@ class Transport:
|
||||
else:
|
||||
rate_entry["last"] = now
|
||||
|
||||
else:
|
||||
rate_blocked = True
|
||||
|
||||
else: rate_blocked = True
|
||||
|
||||
retries = 0
|
||||
announce_hops = packet.hops
|
||||
@@ -1779,7 +1855,7 @@ class Transport:
|
||||
# If we have any local clients connected, we re-
|
||||
# transmit the announce to them immediately
|
||||
if (len(Transport.local_client_interfaces)):
|
||||
announce_identity = RNS.Identity.recall(packet.destination_hash)
|
||||
announce_identity = RNS.Identity.recall(packet.destination_hash, _no_use=True)
|
||||
announce_destination = RNS.Destination(announce_identity, RNS.Destination.OUT, RNS.Destination.SINGLE, "unknown", "unknown");
|
||||
announce_destination.hash = packet.destination_hash
|
||||
announce_destination.hexhash = announce_destination.hash.hex()
|
||||
@@ -1833,7 +1909,7 @@ class Transport:
|
||||
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_identity = RNS.Identity.recall(packet.destination_hash, _no_use=False)
|
||||
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()
|
||||
@@ -1859,17 +1935,22 @@ class Transport:
|
||||
path_table_entry = [now, received_from, announce_hops, expires, random_blobs, packet.receiving_interface, packet.packet_hash]
|
||||
with Transport.path_table_lock: Transport.path_table[packet.destination_hash] = path_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)
|
||||
if packet.destination_hash in Transport.path_requests:
|
||||
RNS.Reticulum.get_instance()._used_destination_data(packet.destination_hash)
|
||||
|
||||
# If the receiving interface is a tunnel, we add the
|
||||
# announce to the tunnels table
|
||||
if hasattr(packet.receiving_interface, "tunnel_id") and packet.receiving_interface.tunnel_id != None:
|
||||
with Transport.tunnels_lock:
|
||||
tunnel_entry = Transport.tunnels[packet.receiving_interface.tunnel_id]
|
||||
paths = tunnel_entry[IDX_TT_PATHS]
|
||||
paths[packet.destination_hash] = [now, received_from, announce_hops, expires, random_blobs, None, packet.packet_hash]
|
||||
expires = time.time() + Transport.DESTINATION_TIMEOUT
|
||||
tunnel_entry[IDX_TT_EXPIRES] = expires
|
||||
RNS.log("Path to "+RNS.prettyhexrep(packet.destination_hash)+" associated with tunnel "+RNS.prettyhexrep(packet.receiving_interface.tunnel_id), RNS.LOG_DEBUG)
|
||||
if not packet.receiving_interface.tunnel_id in Transport.tunnels:
|
||||
RNS.log(f"Tunnel ID for {packet.receiving_interface} was not found in tunnel table", RNS.LOG_WARNING)
|
||||
else:
|
||||
tunnel_entry = Transport.tunnels[packet.receiving_interface.tunnel_id]
|
||||
paths = tunnel_entry[IDX_TT_PATHS]
|
||||
paths[packet.destination_hash] = [now, received_from, announce_hops, expires, random_blobs, None, packet.packet_hash]
|
||||
expires = time.time() + Transport.TUNNEL_TIMEOUT
|
||||
tunnel_entry[IDX_TT_EXPIRES] = expires
|
||||
RNS.log("Path to "+RNS.prettyhexrep(packet.destination_hash)+" associated with tunnel "+RNS.prettyhexrep(packet.receiving_interface.tunnel_id), RNS.LOG_DEBUG)
|
||||
|
||||
# Call externally registered callbacks from apps
|
||||
# wanting to know when an announce arrives
|
||||
@@ -1879,7 +1960,7 @@ class Transport:
|
||||
# Check that the announced destination matches
|
||||
# the handlers aspect filter
|
||||
execute_callback = False
|
||||
announce_identity = RNS.Identity.recall(packet.destination_hash)
|
||||
announce_identity = RNS.Identity.recall(packet.destination_hash, _no_use=True)
|
||||
if handler.aspect_filter == None:
|
||||
# If the handlers aspect filter is set to
|
||||
# None, we execute the callback in all cases
|
||||
@@ -1896,25 +1977,25 @@ class Transport:
|
||||
|
||||
if execute_callback:
|
||||
if len(inspect.signature(handler.received_announce).parameters) == 3:
|
||||
def job():
|
||||
def job(handler=handler, packet=packet, announce_identity=announce_identity):
|
||||
handler.received_announce(destination_hash=packet.destination_hash,
|
||||
announced_identity=announce_identity,
|
||||
app_data=RNS.Identity.recall_app_data(packet.destination_hash))
|
||||
app_data=RNS.Identity.recall_app_data(packet.destination_hash, _no_use=True))
|
||||
threading.Thread(target=job, daemon=True).start()
|
||||
|
||||
elif len(inspect.signature(handler.received_announce).parameters) == 4:
|
||||
def job():
|
||||
def job(handler=handler, packet=packet, announce_identity=announce_identity):
|
||||
handler.received_announce(destination_hash=packet.destination_hash,
|
||||
announced_identity=announce_identity,
|
||||
app_data=RNS.Identity.recall_app_data(packet.destination_hash),
|
||||
app_data=RNS.Identity.recall_app_data(packet.destination_hash, _no_use=True),
|
||||
announce_packet_hash = packet.packet_hash)
|
||||
threading.Thread(target=job, daemon=True).start()
|
||||
|
||||
elif len(inspect.signature(handler.received_announce).parameters) == 5:
|
||||
def job():
|
||||
def job(handler=handler, packet=packet, announce_identity=announce_identity):
|
||||
handler.received_announce(destination_hash=packet.destination_hash,
|
||||
announced_identity=announce_identity,
|
||||
app_data=RNS.Identity.recall_app_data(packet.destination_hash),
|
||||
app_data=RNS.Identity.recall_app_data(packet.destination_hash, _no_use=True),
|
||||
announce_packet_hash = packet.packet_hash,
|
||||
is_path_response = packet.context == RNS.Packet.PATH_RESPONSE)
|
||||
threading.Thread(target=job, daemon=True).start()
|
||||
@@ -1930,73 +2011,81 @@ class Transport:
|
||||
# Handling for link requests to local destinations
|
||||
elif packet.packet_type == RNS.Packet.LINKREQUEST:
|
||||
if packet.transport_id == None or packet.transport_id == Transport.identity.hash:
|
||||
for destination in Transport.destinations:
|
||||
if destination.hash == packet.destination_hash and destination.type == packet.destination_type:
|
||||
path_mtu = RNS.Link.mtu_from_lr_packet(packet)
|
||||
mode = RNS.Link.mode_from_lr_packet(packet)
|
||||
if packet.receiving_interface.AUTOCONFIGURE_MTU or packet.receiving_interface.FIXED_MTU:
|
||||
nh_mtu = packet.receiving_interface.HW_MTU
|
||||
destination = None
|
||||
with Transport.destinations_map_lock:
|
||||
if packet.destination_hash in Transport.destinations_map:
|
||||
destination = Transport.destinations_map[packet.destination_hash]
|
||||
|
||||
if destination and destination.type == packet.destination_type:
|
||||
path_mtu = RNS.Link.mtu_from_lr_packet(packet)
|
||||
mode = RNS.Link.mode_from_lr_packet(packet)
|
||||
if packet.receiving_interface.AUTOCONFIGURE_MTU or packet.receiving_interface.FIXED_MTU:
|
||||
nh_mtu = packet.receiving_interface.HW_MTU
|
||||
else:
|
||||
nh_mtu = RNS.Reticulum.MTU
|
||||
|
||||
if path_mtu:
|
||||
if packet.receiving_interface.HW_MTU == None:
|
||||
path_mtu = None
|
||||
packet.data = packet.data[:-RNS.Link.LINK_MTU_SIZE]
|
||||
else:
|
||||
nh_mtu = RNS.Reticulum.MTU
|
||||
if nh_mtu < path_mtu:
|
||||
try:
|
||||
path_mtu = nh_mtu
|
||||
clamped_mtu = RNS.Link.signalling_bytes(path_mtu, mode)
|
||||
packet.data = packet.data[:-RNS.Link.LINK_MTU_SIZE]+clamped_mtu
|
||||
except Exception as e:
|
||||
RNS.log(f"Dropping link request packet to local destination. The contained exception was: {e}", RNS.LOG_WARNING)
|
||||
return
|
||||
|
||||
if path_mtu:
|
||||
if packet.receiving_interface.HW_MTU == None:
|
||||
RNS.log(f"No next-hop HW MTU, disabling link MTU upgrade", RNS.LOG_DEBUG) # TODO: Remove debug
|
||||
path_mtu = None
|
||||
packet.data = packet.data[:-RNS.Link.LINK_MTU_SIZE]
|
||||
else:
|
||||
if nh_mtu < path_mtu:
|
||||
try:
|
||||
path_mtu = nh_mtu
|
||||
clamped_mtu = RNS.Link.signalling_bytes(path_mtu, mode)
|
||||
RNS.log(f"Clamping link MTU to {RNS.prettysize(nh_mtu)}", RNS.LOG_DEBUG) # TODO: Remove debug
|
||||
packet.data = packet.data[:-RNS.Link.LINK_MTU_SIZE]+clamped_mtu
|
||||
except Exception as e:
|
||||
RNS.log(f"Dropping link request packet to local destination. The contained exception was: {e}", RNS.LOG_WARNING)
|
||||
return
|
||||
|
||||
packet.destination = destination
|
||||
destination.receive(packet)
|
||||
packet.destination = destination
|
||||
destination.receive(packet)
|
||||
|
||||
# Handling for local data packets
|
||||
elif packet.packet_type == RNS.Packet.DATA:
|
||||
if packet.destination_type == RNS.Destination.LINK:
|
||||
for link in Transport.active_links:
|
||||
if link.link_id == packet.destination_hash:
|
||||
if link.attached_interface == packet.receiving_interface:
|
||||
packet.link = link
|
||||
if packet.context == RNS.Packet.CACHE_REQUEST:
|
||||
cached_packet = Transport.get_cached_packet(packet.data)
|
||||
if cached_packet != None:
|
||||
cached_packet.unpack()
|
||||
RNS.Packet(destination=link, data=cached_packet.data,
|
||||
packet_type=cached_packet.packet_type, context=cached_packet.context).send()
|
||||
with Transport.active_links_lock:
|
||||
for link in Transport.active_links:
|
||||
if link.link_id == packet.destination_hash:
|
||||
if link.attached_interface == packet.receiving_interface:
|
||||
packet.link = link
|
||||
if packet.context == RNS.Packet.CACHE_REQUEST:
|
||||
cached_packet = Transport.get_cached_packet(packet.data)
|
||||
if cached_packet != None:
|
||||
cached_packet.unpack()
|
||||
RNS.Packet(destination=link, data=cached_packet.data,
|
||||
packet_type=cached_packet.packet_type, context=cached_packet.context).send()
|
||||
|
||||
else: link.receive(packet)
|
||||
break
|
||||
|
||||
else: link.receive(packet)
|
||||
|
||||
else:
|
||||
# In the strange and rare case that an interface
|
||||
# is partly malfunctioning, and a link-associated
|
||||
# packet is being received on an interface that
|
||||
# has failed sending, and transport has failed over
|
||||
# to another path, we remove this packet hash from
|
||||
# the filter hashlist so the link can receive the
|
||||
# packet when it finally arrives over another path.
|
||||
while packet.packet_hash in Transport.packet_hashlist:
|
||||
Transport.packet_hashlist.remove(packet.packet_hash)
|
||||
else:
|
||||
# In the strange and rare case that an interface
|
||||
# is partly malfunctioning, and a link-associated
|
||||
# packet is being received on an interface that
|
||||
# has failed sending, and transport has failed over
|
||||
# to another path, we remove this packet hash from
|
||||
# the filter hashlist so the link can receive the
|
||||
# packet when it finally arrives over another path.
|
||||
while packet.packet_hash in Transport.packet_hashlist:
|
||||
Transport.packet_hashlist.remove(packet.packet_hash)
|
||||
else:
|
||||
for destination in Transport.destinations:
|
||||
if destination.hash == packet.destination_hash and destination.type == packet.destination_type:
|
||||
packet.destination = destination
|
||||
if destination.receive(packet):
|
||||
if destination.proof_strategy == RNS.Destination.PROVE_ALL: packet.prove()
|
||||
destination = None
|
||||
with Transport.destinations_map_lock:
|
||||
if packet.destination_hash in Transport.destinations_map:
|
||||
destination = Transport.destinations_map[packet.destination_hash]
|
||||
|
||||
elif destination.proof_strategy == RNS.Destination.PROVE_APP:
|
||||
if destination.callbacks.proof_requested:
|
||||
try:
|
||||
if destination.callbacks.proof_requested(packet): packet.prove()
|
||||
except Exception as e:
|
||||
RNS.log("Error while executing proof request callback. The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
if destination and destination.type == packet.destination_type:
|
||||
packet.destination = destination
|
||||
if destination.receive(packet):
|
||||
if destination.proof_strategy == RNS.Destination.PROVE_ALL: packet.prove()
|
||||
|
||||
elif destination.proof_strategy == RNS.Destination.PROVE_APP:
|
||||
if destination.callbacks.proof_requested:
|
||||
try:
|
||||
if destination.callbacks.proof_requested(packet): packet.prove()
|
||||
except Exception as e:
|
||||
RNS.log("Error while executing proof request callback. The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
|
||||
# Handling for proofs and link-request proofs
|
||||
elif packet.packet_type == RNS.Packet.PROOF:
|
||||
@@ -2014,7 +2103,7 @@ class Transport:
|
||||
signalling_bytes = RNS.Link.signalling_bytes(RNS.Link.mtu_from_lp_packet(packet), RNS.Link.mode_from_lp_packet(packet))
|
||||
|
||||
peer_pub_bytes = packet.data[RNS.Identity.SIGLENGTH//8:RNS.Identity.SIGLENGTH//8+RNS.Link.ECPUBSIZE//2]
|
||||
peer_identity = RNS.Identity.recall(link_entry[IDX_LT_DSTHASH])
|
||||
peer_identity = RNS.Identity.recall(link_entry[IDX_LT_DSTHASH], _no_use=True)
|
||||
peer_sig_pub_bytes = peer_identity.get_public_key()[RNS.Link.ECPUBSIZE//2:RNS.Link.ECPUBSIZE]
|
||||
|
||||
signed_data = packet.destination_hash+peer_pub_bytes+peer_sig_pub_bytes+signalling_bytes
|
||||
@@ -2027,6 +2116,8 @@ class Transport:
|
||||
new_raw += packet.raw[2:]
|
||||
Transport.link_table[packet.destination_hash][IDX_LT_VALIDATED] = True
|
||||
Transport.transmit(link_entry[IDX_LT_RCVD_IF], new_raw)
|
||||
if not Transport.owner.is_connected_to_shared_instance:
|
||||
RNS.Identity._used_destination_data(link_entry[IDX_LT_DSTHASH])
|
||||
|
||||
else:
|
||||
RNS.log("Invalid link request proof in transport for link "+RNS.prettyhexrep(packet.destination_hash)+", dropping proof.", RNS.LOG_DEBUG)
|
||||
@@ -2038,48 +2129,57 @@ class Transport:
|
||||
RNS.log("Link request proof received on wrong interface, not transporting it.", RNS.LOG_DEBUG)
|
||||
else:
|
||||
RNS.log("Received link request proof with hop mismatch, not transporting it", RNS.LOG_DEBUG)
|
||||
|
||||
else:
|
||||
# Check if we can deliver it to a local
|
||||
# pending link
|
||||
for link in Transport.pending_links:
|
||||
if link.link_id == packet.destination_hash:
|
||||
# We need to also allow an expected hops value of
|
||||
# PATHFINDER_M, since in some cases, the number of hops
|
||||
# to the destination will be unknown at link creation
|
||||
# time. The real chance of this occuring is likely to be
|
||||
# extremely small, and this allowance could probably
|
||||
# be discarded without major issues, but it is kept
|
||||
# for now to ensure backwards compatibility.
|
||||
|
||||
# TODO: Probably reset check back to
|
||||
# if packet.hops == link.expected_hops:
|
||||
# within one of the next releases
|
||||
pending_link = None
|
||||
with Transport.pending_links_lock:
|
||||
for link in Transport.pending_links:
|
||||
if link.link_id == packet.destination_hash:
|
||||
# We need to also allow an expected hops value of
|
||||
# PATHFINDER_M, since in some cases, the number of hops
|
||||
# to the destination will be unknown at link creation
|
||||
# time. The real chance of this occuring is likely to be
|
||||
# extremely small, and this allowance could probably
|
||||
# be discarded without major issues, but it is kept
|
||||
# for now to ensure backwards compatibility.
|
||||
|
||||
if packet.hops == link.expected_hops or link.expected_hops == RNS.Transport.PATHFINDER_M:
|
||||
# Add this packet to the filter hashlist if we
|
||||
# have determined that it's actually destined
|
||||
# for this system, and then validate the proof
|
||||
Transport.add_packet_hash(packet.packet_hash)
|
||||
link.validate_proof(packet)
|
||||
# TODO: Probably reset check back to
|
||||
# if packet.hops == link.expected_hops:
|
||||
# within one of the next releases
|
||||
|
||||
if packet.hops == link.expected_hops or link.expected_hops == RNS.Transport.PATHFINDER_M:
|
||||
# Add this packet to the filter hashlist if we
|
||||
# have determined that it's actually destined
|
||||
# for this system, and then validate the proof
|
||||
Transport.add_packet_hash(packet.packet_hash)
|
||||
pending_link = link
|
||||
break
|
||||
|
||||
if pending_link: pending_link.validate_proof(packet)
|
||||
|
||||
elif packet.context == RNS.Packet.RESOURCE_PRF:
|
||||
for link in Transport.active_links:
|
||||
if link.link_id == packet.destination_hash:
|
||||
link.receive(packet)
|
||||
else:
|
||||
if packet.destination_type == RNS.Destination.LINK:
|
||||
with Transport.active_links_lock:
|
||||
for link in Transport.active_links:
|
||||
if link.link_id == packet.destination_hash:
|
||||
packet.link = link
|
||||
link.receive(packet)
|
||||
break
|
||||
else:
|
||||
if packet.destination_type == RNS.Destination.LINK:
|
||||
with Transport.active_links_lock:
|
||||
for link in Transport.active_links:
|
||||
if link.link_id == packet.destination_hash:
|
||||
packet.link = link
|
||||
break
|
||||
|
||||
if len(packet.data) == RNS.PacketReceipt.EXPL_LENGTH:
|
||||
proof_hash = packet.data[:RNS.Identity.HASHLENGTH//8]
|
||||
else:
|
||||
proof_hash = None
|
||||
if len(packet.data) == RNS.PacketReceipt.EXPL_LENGTH: proof_hash = packet.data[:RNS.Identity.HASHLENGTH//8]
|
||||
else: proof_hash = None
|
||||
|
||||
# Check if this proof needs to be transported
|
||||
if (RNS.Reticulum.transport_enabled() or from_local_client or proof_for_local_client) and packet.destination_hash in Transport.reverse_table:
|
||||
reverse_entry = Transport.reverse_table.pop(packet.destination_hash)
|
||||
with Transport.reverse_table_lock: reverse_entry = Transport.reverse_table.pop(packet.destination_hash)
|
||||
if packet.receiving_interface == reverse_entry[IDX_RT_OUTB_IF]:
|
||||
RNS.log("Proof received on correct interface, transporting it via "+str(reverse_entry[IDX_RT_RCVD_IF]), RNS.LOG_EXTREME)
|
||||
new_raw = packet.raw[0:1]
|
||||
@@ -2089,20 +2189,21 @@ class Transport:
|
||||
else:
|
||||
RNS.log("Proof received on wrong interface, not transporting it.", RNS.LOG_DEBUG)
|
||||
|
||||
for receipt in Transport.receipts:
|
||||
receipt_validated = False
|
||||
if proof_hash != None:
|
||||
# Only test validation if hash matches
|
||||
if receipt.hash == proof_hash:
|
||||
with Transport.receipts_lock:
|
||||
for receipt in Transport.receipts:
|
||||
receipt_validated = False
|
||||
if proof_hash != None:
|
||||
# Only test validation if hash matches
|
||||
if receipt.hash == proof_hash:
|
||||
receipt_validated = receipt.validate_proof_packet(packet)
|
||||
else:
|
||||
# In case of an implicit proof, we have
|
||||
# to check every single outstanding receipt
|
||||
receipt_validated = receipt.validate_proof_packet(packet)
|
||||
else:
|
||||
# In case of an implicit proof, we have
|
||||
# to check every single outstanding receipt
|
||||
receipt_validated = receipt.validate_proof_packet(packet)
|
||||
|
||||
if receipt_validated:
|
||||
if receipt in Transport.receipts:
|
||||
Transport.receipts.remove(receipt)
|
||||
if receipt_validated:
|
||||
if receipt in Transport.receipts:
|
||||
Transport.receipts.remove(receipt)
|
||||
|
||||
@staticmethod
|
||||
def synthesize_tunnel(interface):
|
||||
@@ -2151,19 +2252,21 @@ class Transport:
|
||||
|
||||
@staticmethod
|
||||
def void_tunnel_interface(tunnel_id):
|
||||
if tunnel_id in Transport.tunnels:
|
||||
RNS.log(f"Voiding tunnel interface {Transport.tunnels[tunnel_id][IDX_TT_IF]}", RNS.LOG_EXTREME)
|
||||
Transport.tunnels[tunnel_id][IDX_TT_IF] = None
|
||||
with Transport.tunnels_lock:
|
||||
if tunnel_id in Transport.tunnels:
|
||||
RNS.log(f"Voiding tunnel interface {Transport.tunnels[tunnel_id][IDX_TT_IF]}", RNS.LOG_EXTREME)
|
||||
Transport.tunnels[tunnel_id][IDX_TT_IF] = None
|
||||
|
||||
@staticmethod
|
||||
def handle_tunnel(tunnel_id, interface):
|
||||
expires = time.time() + Transport.DESTINATION_TIMEOUT
|
||||
expires = time.time() + Transport.TUNNEL_TIMEOUT
|
||||
if not tunnel_id in Transport.tunnels:
|
||||
RNS.log("Tunnel endpoint "+RNS.prettyhexrep(tunnel_id)+" established.", RNS.LOG_DEBUG)
|
||||
paths = {}
|
||||
tunnel_entry = [tunnel_id, interface, paths, expires]
|
||||
interface.tunnel_id = tunnel_id
|
||||
Transport.tunnels[tunnel_id] = tunnel_entry
|
||||
with Transport.tunnels_lock:
|
||||
tunnel_entry = [tunnel_id, interface, paths, expires]
|
||||
interface.tunnel_id = tunnel_id
|
||||
Transport.tunnels[tunnel_id] = tunnel_entry
|
||||
else:
|
||||
RNS.log("Tunnel endpoint "+RNS.prettyhexrep(tunnel_id)+" reappeared. Restoring paths...", RNS.LOG_DEBUG)
|
||||
tunnel_entry = Transport.tunnels[tunnel_id]
|
||||
@@ -2173,36 +2276,64 @@ class Transport:
|
||||
paths = tunnel_entry[IDX_TT_PATHS]
|
||||
|
||||
deprecated_paths = []
|
||||
for destination_hash, path_entry in paths.items():
|
||||
received_from = path_entry[1]
|
||||
announce_hops = path_entry[2]
|
||||
expires = path_entry[3]
|
||||
random_blobs = list(set(path_entry[4]))
|
||||
receiving_interface = interface
|
||||
packet_hash = path_entry[6]
|
||||
new_entry = [time.time(), received_from, announce_hops, expires, random_blobs, receiving_interface, packet_hash]
|
||||
with Transport.tunnels_lock:
|
||||
for destination_hash, path_entry in paths.items():
|
||||
received_from = path_entry[1]
|
||||
announce_hops = path_entry[2]
|
||||
expires = path_entry[3]
|
||||
random_blobs = list(set(path_entry[4]))
|
||||
receiving_interface = interface
|
||||
packet_hash = path_entry[6]
|
||||
new_entry = [time.time(), received_from, announce_hops, expires, random_blobs, receiving_interface, packet_hash]
|
||||
|
||||
should_add = False
|
||||
if destination_hash in Transport.path_table:
|
||||
old_entry = Transport.path_table[destination_hash]
|
||||
old_hops = old_entry[IDX_PT_HOPS]
|
||||
old_expires = old_entry[IDX_PT_EXPIRES]
|
||||
if announce_hops <= old_hops or time.time() > old_expires: should_add = True
|
||||
else: RNS.log("Did not restore path to "+RNS.prettyhexrep(destination_hash)+" because a newer path with fewer hops exist", RNS.LOG_DEBUG)
|
||||
|
||||
else:
|
||||
if time.time() < expires: should_add = True
|
||||
else: RNS.log("Did not restore path to "+RNS.prettyhexrep(destination_hash)+" because it has expired", RNS.LOG_DEBUG)
|
||||
should_add = False
|
||||
with Transport.path_table_lock:
|
||||
if destination_hash in Transport.path_table:
|
||||
old_entry = Transport.path_table[destination_hash]
|
||||
old_hops = old_entry[IDX_PT_HOPS]
|
||||
old_expires = old_entry[IDX_PT_EXPIRES]
|
||||
if announce_hops <= old_hops or time.time() > old_expires:
|
||||
current_random_blobs = Transport.path_table[destination_hash][IDX_PT_RANDBLOBS]
|
||||
current_path_timebase = Transport.timebase_from_random_blobs(current_random_blobs)
|
||||
tunnel_announce_timebase = Transport.timebase_from_random_blobs(random_blobs)
|
||||
if tunnel_announce_timebase >= current_path_timebase: should_add = True
|
||||
else: RNS.log("Did not restore path to "+RNS.prettyhexrep(destination_hash)+" because existing path is more recent", RNS.LOG_DEBUG)
|
||||
else: RNS.log("Did not restore path to "+RNS.prettyhexrep(destination_hash)+" because a newer path with fewer hops exist", RNS.LOG_DEBUG)
|
||||
|
||||
else:
|
||||
if time.time() < expires: should_add = True
|
||||
else: RNS.log("Did not restore path to "+RNS.prettyhexrep(destination_hash)+" because it has expired", RNS.LOG_DEBUG)
|
||||
|
||||
if should_add:
|
||||
Transport.path_table[destination_hash] = new_entry
|
||||
RNS.log("Restored path to "+RNS.prettyhexrep(destination_hash)+" is now "+str(announce_hops)+" hops away via "+RNS.prettyhexrep(received_from)+" on "+str(receiving_interface), RNS.LOG_DEBUG)
|
||||
else:
|
||||
deprecated_paths.append(destination_hash)
|
||||
if should_add:
|
||||
with Transport.path_table_lock: Transport.path_table[destination_hash] = new_entry
|
||||
RNS.log("Restored path to "+RNS.prettyhexrep(destination_hash)+" is now "+str(announce_hops)+" hops away via "+RNS.prettyhexrep(received_from)+" on "+str(receiving_interface), RNS.LOG_DEBUG)
|
||||
|
||||
else: deprecated_paths.append(destination_hash)
|
||||
|
||||
for deprecated_path in deprecated_paths:
|
||||
RNS.log("Removing path to "+RNS.prettyhexrep(deprecated_path)+" from tunnel "+RNS.prettyhexrep(tunnel_id), RNS.LOG_DEBUG)
|
||||
paths.pop(deprecated_path)
|
||||
with Transport.tunnels_lock: paths.pop(deprecated_path)
|
||||
|
||||
@staticmethod
|
||||
def clean_destinations_map():
|
||||
with Transport.destinations_lock:
|
||||
for destination in Transport.destinations:
|
||||
with Transport.destinations_map_lock:
|
||||
if not destination.hash in Transport.destinations_map:
|
||||
Transport.destinations_map[destination.hash] = destination
|
||||
|
||||
with Transport.destinations_map_lock:
|
||||
stale_destination_hashes = []
|
||||
for destination_hash in Transport.destinations_map:
|
||||
with Transport.destinations_lock:
|
||||
found = False
|
||||
for destination in Transport.destinations:
|
||||
if destination.hash == destination_hash: found = True
|
||||
|
||||
if not found: stale_destination_hashes.append(destination_hash)
|
||||
|
||||
for destination_hash in stale_destination_hashes:
|
||||
Transport.destinations_map.pop(destination_hash)
|
||||
|
||||
@staticmethod
|
||||
def register_destination(destination):
|
||||
@@ -2215,6 +2346,9 @@ class Transport:
|
||||
|
||||
Transport.destinations.append(destination)
|
||||
|
||||
with Transport.destinations_map_lock:
|
||||
Transport.destinations_map[destination.hash] = destination
|
||||
|
||||
if Transport.owner.is_connected_to_shared_instance:
|
||||
if destination.type == RNS.Destination.SINGLE:
|
||||
def job():
|
||||
@@ -2227,6 +2361,10 @@ class Transport:
|
||||
with Transport.destinations_lock:
|
||||
if destination in Transport.destinations: Transport.destinations.remove(destination)
|
||||
|
||||
with Transport.destinations_map_lock:
|
||||
if destination.hash in Transport.destinations_map:
|
||||
Transport.destinations_map.pop(destination.hash)
|
||||
|
||||
@staticmethod
|
||||
def register_link(link):
|
||||
RNS.log("Registering link "+str(link), RNS.LOG_EXTREME)
|
||||
@@ -2295,16 +2433,30 @@ class Transport:
|
||||
@staticmethod
|
||||
def clean_cache():
|
||||
if not Transport.owner.is_connected_to_shared_instance:
|
||||
Transport.clean_announce_cache()
|
||||
Transport.cache_last_cleaned = time.time()
|
||||
if Transport.cache_clean_lock.locked():
|
||||
RNS.log(f"Cache clean job still running, postponing until next scheduler interval", RNS.LOG_VERBOSE)
|
||||
|
||||
else:
|
||||
try:
|
||||
acquired_lock = Transport.cache_clean_lock.acquire(blocking=False)
|
||||
if acquired_lock:
|
||||
Transport.clean_announce_cache()
|
||||
Transport.cache_last_cleaned = time.time()
|
||||
|
||||
except Exception as e:
|
||||
RNS.log(f"An error occurred while launching the cache clean job. The contained exception was: {e}", RNS.LOG_ERROR)
|
||||
RNS.trace_exception(e)
|
||||
|
||||
finally:
|
||||
if acquired_lock: Transport.cache_clean_lock.release()
|
||||
|
||||
@staticmethod
|
||||
def clean_announce_cache():
|
||||
st = time.time()
|
||||
target_path = os.path.join(RNS.Reticulum.cachepath, "announces")
|
||||
active_paths = [Transport.path_table[dst_hash][6] for dst_hash in Transport.path_table]
|
||||
tunnel_paths = list(set([path_dict[dst_hash][6] for path_dict in [Transport.tunnels[tunnel_id][2] for tunnel_id in Transport.tunnels] for dst_hash in path_dict]))
|
||||
removed = 0
|
||||
with Transport.path_table_lock: active_paths = [Transport.path_table[dst_hash][6] for dst_hash in Transport.path_table]
|
||||
with Transport.tunnels_lock: tunnel_paths = list(set([path_dict[dst_hash][6] for path_dict in [Transport.tunnels[tunnel_id][2] for tunnel_id in Transport.tunnels] for dst_hash in path_dict]))
|
||||
removed = 0; total = 0
|
||||
for packet_hash in os.listdir(target_path):
|
||||
remove = False
|
||||
full_path = os.path.join(target_path, packet_hash)
|
||||
@@ -2313,9 +2465,12 @@ class Transport:
|
||||
except: remove = True
|
||||
if (not target_hash in active_paths) and (not target_hash in tunnel_paths): remove = True
|
||||
if remove: os.unlink(full_path); removed += 1
|
||||
total += 1
|
||||
|
||||
if removed > 0:
|
||||
RNS.log(f"Removed {removed} cached announces in {RNS.prettytime(time.time()-st)}", RNS.LOG_DEBUG)
|
||||
# Low priority, yield thread
|
||||
time.sleep(0.001)
|
||||
|
||||
if removed > 0: RNS.log(f"Removed {removed} cached announces in {RNS.prettytime(time.time()-st)}", RNS.LOG_DEBUG)
|
||||
|
||||
# When caching packets to storage, they are written
|
||||
# exactly as they arrived over their interface. This
|
||||
@@ -2447,8 +2602,8 @@ class Transport:
|
||||
if next_hop_interface != None:
|
||||
if next_hop_interface.AUTOCONFIGURE_MTU or next_hop_interface.FIXED_MTU: return next_hop_interface.HW_MTU
|
||||
else: return None
|
||||
else:
|
||||
return None
|
||||
|
||||
else: return None
|
||||
|
||||
@staticmethod
|
||||
def next_hop_per_bit_latency(destination_hash):
|
||||
@@ -2511,7 +2666,8 @@ class Transport:
|
||||
def path_is_unresponsive(destination_hash):
|
||||
with Transport.path_states_lock:
|
||||
if destination_hash in Transport.path_states:
|
||||
if Transport.path_states[destination_hash] == Transport.STATE_UNRESPONSIVE: return True
|
||||
if Transport.path_states[destination_hash] == Transport.STATE_UNRESPONSIVE:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
@@ -2693,8 +2849,12 @@ class Transport:
|
||||
destination_exists_on_local_client = True
|
||||
with Transport.pending_local_prs_lock:
|
||||
Transport.pending_local_path_requests[destination_hash] = attached_interface
|
||||
|
||||
local_destination = next((d for d in Transport.destinations if d.hash == destination_hash), None)
|
||||
RNS.Reticulum.get_instance()._used_destination_data(destination_hash)
|
||||
|
||||
local_destination = None
|
||||
with Transport.destinations_map_lock:
|
||||
if destination_hash in Transport.destinations_map: local_destination = Transport.destinations_map[destination_hash]
|
||||
|
||||
if local_destination != None:
|
||||
local_destination.announce(path_response=True, tag=tag, attached_interface=attached_interface)
|
||||
RNS.log("Answering path request for "+RNS.prettyhexrep(destination_hash)+interface_str+", destination is local to this system", RNS.LOG_DEBUG)
|
||||
@@ -2757,7 +2917,10 @@ class Transport:
|
||||
held_entry = Transport.announce_table[packet.destination_hash]
|
||||
Transport.held_announces[packet.destination_hash] = held_entry
|
||||
|
||||
Transport.announce_table[packet.destination_hash] = [now, retransmit_timeout, retries, received_from, announce_hops, packet, local_rebroadcasts, block_rebroadcasts, attached_interface]
|
||||
with Transport.announce_table_lock:
|
||||
Transport.announce_table[packet.destination_hash] = [now, retransmit_timeout, retries, received_from, announce_hops, packet, local_rebroadcasts, block_rebroadcasts, attached_interface]
|
||||
|
||||
if not Transport.owner.is_connected_to_shared_instance: RNS.Identity._used_destination_data(packet.destination_hash)
|
||||
|
||||
elif is_from_local_client:
|
||||
# Forward path request on all interfaces
|
||||
@@ -2776,7 +2939,7 @@ class Transport:
|
||||
# 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
|
||||
with Transport.discovery_pr_lock: Transport.discovery_path_requests[destination_hash] = pr_entry
|
||||
|
||||
for interface in Transport.interfaces:
|
||||
if not interface == attached_interface:
|
||||
@@ -2851,7 +3014,7 @@ class Transport:
|
||||
elif type(interface) == RNS.Interfaces.LocalInterface.LocalClientInterface:
|
||||
local_interfaces.append(interface)
|
||||
else:
|
||||
def detach_job():
|
||||
def detach_job(interface=interface):
|
||||
RNS.log(f"Detaching {interface}", RNS.LOG_EXTREME)
|
||||
interface.detach()
|
||||
dt = threading.Thread(target=detach_job, daemon=False)
|
||||
@@ -2876,11 +3039,11 @@ class Transport:
|
||||
|
||||
@staticmethod
|
||||
def shared_connection_disappeared():
|
||||
for link in Transport.active_links:
|
||||
link.teardown()
|
||||
with Transport.active_links_lock:
|
||||
for link in Transport.active_links: link.teardown()
|
||||
|
||||
for link in Transport.pending_links:
|
||||
link.teardown()
|
||||
with Transport.pending_links_lock:
|
||||
for link in Transport.pending_links: link.teardown()
|
||||
|
||||
Transport.announce_table = {}
|
||||
Transport.path_table = {}
|
||||
@@ -2892,11 +3055,10 @@ class Transport:
|
||||
@staticmethod
|
||||
def shared_connection_reappeared():
|
||||
if Transport.owner.is_connected_to_shared_instance:
|
||||
for registered_destination in Transport.destinations:
|
||||
for registered_destination in Transport.destinations.copy():
|
||||
if registered_destination.type == RNS.Destination.SINGLE:
|
||||
registered_destination.announce(path_response=True)
|
||||
|
||||
|
||||
@staticmethod
|
||||
def drop_announce_queues():
|
||||
for interface in Transport.interfaces:
|
||||
@@ -2931,7 +3093,7 @@ class Transport:
|
||||
return announce_emitted
|
||||
|
||||
@staticmethod
|
||||
def save_packet_hashlist():
|
||||
def save_packet_hashlist(background=False):
|
||||
if not Transport.owner.is_connected_to_shared_instance:
|
||||
if hasattr(Transport, "saving_packet_hashlist"):
|
||||
wait_interval = 0.2
|
||||
@@ -2968,7 +3130,7 @@ class Transport:
|
||||
|
||||
|
||||
@staticmethod
|
||||
def save_path_table():
|
||||
def save_path_table(background=False):
|
||||
if not Transport.owner.is_connected_to_shared_instance:
|
||||
if hasattr(Transport, "saving_path_table"):
|
||||
wait_interval = 0.2
|
||||
@@ -3042,7 +3204,7 @@ class Transport:
|
||||
|
||||
|
||||
@staticmethod
|
||||
def save_tunnel_table():
|
||||
def save_tunnel_table(background=False):
|
||||
if not Transport.owner.is_connected_to_shared_instance:
|
||||
if hasattr(Transport, "saving_tunnel_table"):
|
||||
wait_interval = 0.2
|
||||
@@ -3116,10 +3278,10 @@ class Transport:
|
||||
gc.collect()
|
||||
|
||||
@staticmethod
|
||||
def persist_data():
|
||||
Transport.save_packet_hashlist()
|
||||
Transport.save_path_table()
|
||||
Transport.save_tunnel_table()
|
||||
def persist_data(background=False):
|
||||
Transport.save_packet_hashlist(background=background)
|
||||
Transport.save_path_table(background=background)
|
||||
Transport.save_tunnel_table(background=background)
|
||||
|
||||
@staticmethod
|
||||
def exit_handler():
|
||||
@@ -3202,7 +3364,7 @@ class Transport:
|
||||
drop_destinations = []
|
||||
for destination_hash in Transport.path_table.copy():
|
||||
try:
|
||||
associated_identity = RNS.Identity.recall(destination_hash)
|
||||
associated_identity = RNS.Identity.recall(destination_hash, _no_use=True)
|
||||
if associated_identity and associated_identity.hash in Transport.blackholed_identities:
|
||||
if not destination_hash in drop_destinations: drop_destinations.append(destination_hash)
|
||||
except Exception as e:
|
||||
@@ -3210,7 +3372,8 @@ class Transport:
|
||||
|
||||
for destination_hash in drop_destinations:
|
||||
try:
|
||||
if destination_hash in Transport.path_table: Transport.path_table.pop(destination_hash)
|
||||
with Transport.path_table_lock:
|
||||
if destination_hash in Transport.path_table: Transport.path_table.pop(destination_hash)
|
||||
except Exception as e:
|
||||
RNS.log(f"Error while dropping blackhole-associated destination from path table: {e}", RNS.LOG_ERROR)
|
||||
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
__version__ = "1.1.5"
|
||||
__version__ = "1.1.7"
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -1,4 +1,4 @@
|
||||
# Sphinx build info version 1
|
||||
# This file records the configuration used when building these files. When it is not found, a full rebuild will be done.
|
||||
config: 0a02f7ea18b4c8fc12a9bb273be595c7
|
||||
config: 93f6eab163a291fbdb28b0b7666c1971
|
||||
tags: 645f666f9bcd5a90fca523b33c5a78b7
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
const DOCUMENTATION_OPTIONS = {
|
||||
VERSION: '1.1.5',
|
||||
VERSION: '1.1.7',
|
||||
LANGUAGE: 'en',
|
||||
COLLAPSE_INDEX: false,
|
||||
BUILDER: 'html',
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
||||
|
||||
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
||||
<title>Code Examples - Reticulum Network Stack 1.1.5 documentation</title>
|
||||
<title>Code Examples - Reticulum Network Stack 1.1.7 documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
||||
@@ -180,7 +180,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.1.5 documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.1.7 documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -204,7 +204,7 @@
|
||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.1.5 documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.1.7 documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
||||
@@ -3663,7 +3663,7 @@ will be fully on-par with natively included interfaces, including all supported
|
||||
|
||||
</aside>
|
||||
</div>
|
||||
</div><script src="_static/documentation_options.js?v=a48ae3df"></script>
|
||||
</div><script src="_static/documentation_options.js?v=370aedac"></script>
|
||||
<script src="_static/doctools.js?v=9bcbadda"></script>
|
||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
||||
|
||||
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
||||
<title>An Explanation of Reticulum for Human Beings - Reticulum Network Stack 1.1.5 documentation</title>
|
||||
<title>An Explanation of Reticulum for Human Beings - Reticulum Network Stack 1.1.7 documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
||||
@@ -180,7 +180,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.1.5 documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.1.7 documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -204,7 +204,7 @@
|
||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.1.5 documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.1.7 documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
||||
@@ -294,7 +294,7 @@
|
||||
|
||||
</aside>
|
||||
</div>
|
||||
</div><script src="_static/documentation_options.js?v=a48ae3df"></script>
|
||||
</div><script src="_static/documentation_options.js?v=370aedac"></script>
|
||||
<script src="_static/doctools.js?v=9bcbadda"></script>
|
||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<meta name="color-scheme" content="light dark"><link rel="index" title="Index" href="#"><link rel="search" title="Search" href="search.html">
|
||||
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
||||
|
||||
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 --><title>Index - Reticulum Network Stack 1.1.5 documentation</title>
|
||||
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 --><title>Index - Reticulum Network Stack 1.1.7 documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
||||
@@ -178,7 +178,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.1.5 documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.1.7 documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -202,7 +202,7 @@
|
||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.1.5 documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.1.7 documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
||||
@@ -836,7 +836,7 @@
|
||||
|
||||
</aside>
|
||||
</div>
|
||||
</div><script src="_static/documentation_options.js?v=a48ae3df"></script>
|
||||
</div><script src="_static/documentation_options.js?v=370aedac"></script>
|
||||
<script src="_static/doctools.js?v=9bcbadda"></script>
|
||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
||||
|
||||
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
||||
<title>Getting Started Fast - Reticulum Network Stack 1.1.5 documentation</title>
|
||||
<title>Getting Started Fast - Reticulum Network Stack 1.1.7 documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
||||
@@ -180,7 +180,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.1.5 documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.1.7 documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -204,7 +204,7 @@
|
||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.1.5 documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.1.7 documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
||||
@@ -966,7 +966,7 @@ All other available modules will still be loaded when needed.</p>
|
||||
|
||||
</aside>
|
||||
</div>
|
||||
</div><script src="_static/documentation_options.js?v=a48ae3df"></script>
|
||||
</div><script src="_static/documentation_options.js?v=370aedac"></script>
|
||||
<script src="_static/doctools.js?v=9bcbadda"></script>
|
||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
||||
|
||||
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
||||
<title>Communications Hardware - Reticulum Network Stack 1.1.5 documentation</title>
|
||||
<title>Communications Hardware - Reticulum Network Stack 1.1.7 documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
||||
@@ -180,7 +180,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.1.5 documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.1.7 documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -204,7 +204,7 @@
|
||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.1.5 documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.1.7 documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
||||
@@ -674,7 +674,7 @@ can be used with Reticulum. This includes virtual software modems such as
|
||||
|
||||
</aside>
|
||||
</div>
|
||||
</div><script src="_static/documentation_options.js?v=a48ae3df"></script>
|
||||
</div><script src="_static/documentation_options.js?v=370aedac"></script>
|
||||
<script src="_static/doctools.js?v=9bcbadda"></script>
|
||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
||||
|
||||
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
||||
<title>Reticulum Network Stack 1.1.5 documentation</title>
|
||||
<title>Reticulum Network Stack 1.1.7 documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
||||
@@ -180,7 +180,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="#"><div class="brand">Reticulum Network Stack 1.1.5 documentation</div></a>
|
||||
<a href="#"><div class="brand">Reticulum Network Stack 1.1.7 documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -204,7 +204,7 @@
|
||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.1.5 documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.1.7 documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
||||
@@ -631,7 +631,7 @@ to participate in the development of Reticulum itself.</p>
|
||||
|
||||
</aside>
|
||||
</div>
|
||||
</div><script src="_static/documentation_options.js?v=a48ae3df"></script>
|
||||
</div><script src="_static/documentation_options.js?v=370aedac"></script>
|
||||
<script src="_static/doctools.js?v=9bcbadda"></script>
|
||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
||||
|
||||
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
||||
<title>Configuring Interfaces - Reticulum Network Stack 1.1.5 documentation</title>
|
||||
<title>Configuring Interfaces - Reticulum Network Stack 1.1.7 documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
||||
@@ -180,7 +180,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.1.5 documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.1.7 documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -204,7 +204,7 @@
|
||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.1.5 documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.1.7 documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
||||
@@ -1684,7 +1684,7 @@ to <code class="docutils literal notranslate"><span class="pre">30</span></code>
|
||||
|
||||
</aside>
|
||||
</div>
|
||||
</div><script src="_static/documentation_options.js?v=a48ae3df"></script>
|
||||
</div><script src="_static/documentation_options.js?v=370aedac"></script>
|
||||
<script src="_static/doctools.js?v=9bcbadda"></script>
|
||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
||||
|
||||
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
||||
<title>Reticulum License - Reticulum Network Stack 1.1.5 documentation</title>
|
||||
<title>Reticulum License - Reticulum Network Stack 1.1.7 documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
||||
@@ -180,7 +180,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.1.5 documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.1.7 documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -204,7 +204,7 @@
|
||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.1.5 documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.1.7 documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
||||
@@ -343,7 +343,7 @@ SOFTWARE.
|
||||
|
||||
</aside>
|
||||
</div>
|
||||
</div><script src="_static/documentation_options.js?v=a48ae3df"></script>
|
||||
</div><script src="_static/documentation_options.js?v=370aedac"></script>
|
||||
<script src="_static/doctools.js?v=9bcbadda"></script>
|
||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
||||
|
||||
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
||||
<title>Building Networks - Reticulum Network Stack 1.1.5 documentation</title>
|
||||
<title>Building Networks - Reticulum Network Stack 1.1.7 documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
||||
@@ -180,7 +180,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.1.5 documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.1.7 documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -204,7 +204,7 @@
|
||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.1.5 documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.1.7 documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
||||
@@ -662,7 +662,7 @@ differently than a mobile device roaming between radio cells.</p>
|
||||
|
||||
</aside>
|
||||
</div>
|
||||
</div><script src="_static/documentation_options.js?v=a48ae3df"></script>
|
||||
</div><script src="_static/documentation_options.js?v=370aedac"></script>
|
||||
<script src="_static/doctools.js?v=9bcbadda"></script>
|
||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
||||
|
||||
Binary file not shown.
@@ -7,7 +7,7 @@
|
||||
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
||||
|
||||
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
||||
<title>API Reference - Reticulum Network Stack 1.1.5 documentation</title>
|
||||
<title>API Reference - Reticulum Network Stack 1.1.7 documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
||||
@@ -180,7 +180,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.1.5 documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.1.7 documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -204,7 +204,7 @@
|
||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.1.5 documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.1.7 documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
||||
@@ -506,7 +506,7 @@ for addressable hashes and other purposes. Non-configurable.</p>
|
||||
|
||||
<dl class="py method">
|
||||
<dt class="sig sig-object py" id="RNS.Identity.recall">
|
||||
<em class="property"><span class="k"><span class="pre">static</span></span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">recall</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">target_hash</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">from_identity_hash</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.Identity.recall" title="Link to this definition">¶</a></dt>
|
||||
<em class="property"><span class="k"><span class="pre">static</span></span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">recall</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">target_hash</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">from_identity_hash</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">False</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">_no_use</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.Identity.recall" title="Link to this definition">¶</a></dt>
|
||||
<dd><p>Recall identity for a destination or identity hash. By default, this function
|
||||
will return the identity associated with a given <em>destination</em> hash. As an
|
||||
example, if you know the <code class="docutils literal notranslate"><span class="pre">lxmf.delivery</span></code> destination hash of an endpoint,
|
||||
@@ -528,7 +528,7 @@ search for an identity from a known <em>identity hash</em>, by setting the
|
||||
|
||||
<dl class="py method">
|
||||
<dt class="sig sig-object py" id="RNS.Identity.recall_app_data">
|
||||
<em class="property"><span class="k"><span class="pre">static</span></span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">recall_app_data</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">destination_hash</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Identity.recall_app_data" title="Link to this definition">¶</a></dt>
|
||||
<em class="property"><span class="k"><span class="pre">static</span></span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">recall_app_data</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">_no_use</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.Identity.recall_app_data" title="Link to this definition">¶</a></dt>
|
||||
<dd><p>Recall last heard app_data for a destination hash.</p>
|
||||
<dl class="field-list simple">
|
||||
<dt class="field-odd">Parameters<span class="colon">:</span></dt>
|
||||
@@ -2472,7 +2472,7 @@ will announce it.</p>
|
||||
|
||||
</aside>
|
||||
</div>
|
||||
</div><script src="_static/documentation_options.js?v=a48ae3df"></script>
|
||||
</div><script src="_static/documentation_options.js?v=370aedac"></script>
|
||||
<script src="_static/doctools.js?v=9bcbadda"></script>
|
||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
||||
<meta name="robots" content="noindex" />
|
||||
<title>Search - Reticulum Network Stack 1.1.5 documentation</title><link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
|
||||
<title>Search - Reticulum Network Stack 1.1.7 documentation</title><link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo-extensions.css?v=8dab3a3b" />
|
||||
@@ -180,7 +180,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.1.5 documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.1.7 documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -204,7 +204,7 @@
|
||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.1.5 documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.1.7 documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="#" role="search">
|
||||
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
||||
@@ -302,7 +302,7 @@
|
||||
|
||||
</aside>
|
||||
</div>
|
||||
</div><script src="_static/documentation_options.js?v=a48ae3df"></script>
|
||||
</div><script src="_static/documentation_options.js?v=370aedac"></script>
|
||||
<script src="_static/doctools.js?v=9bcbadda"></script>
|
||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -7,7 +7,7 @@
|
||||
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
||||
|
||||
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
||||
<title>Programs Using Reticulum - Reticulum Network Stack 1.1.5 documentation</title>
|
||||
<title>Programs Using Reticulum - Reticulum Network Stack 1.1.7 documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
||||
@@ -180,7 +180,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.1.5 documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.1.7 documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -204,7 +204,7 @@
|
||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.1.5 documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.1.7 documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
||||
@@ -533,7 +533,7 @@ using LXMF.</p>
|
||||
|
||||
</aside>
|
||||
</div>
|
||||
</div><script src="_static/documentation_options.js?v=a48ae3df"></script>
|
||||
</div><script src="_static/documentation_options.js?v=370aedac"></script>
|
||||
<script src="_static/doctools.js?v=9bcbadda"></script>
|
||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
||||
|
||||
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
||||
<title>Support Reticulum - Reticulum Network Stack 1.1.5 documentation</title>
|
||||
<title>Support Reticulum - Reticulum Network Stack 1.1.7 documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
||||
@@ -180,7 +180,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.1.5 documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.1.7 documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -204,7 +204,7 @@
|
||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.1.5 documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.1.7 documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
||||
@@ -381,7 +381,7 @@ circumstances, so we rely on old-fashioned human feedback.</p>
|
||||
|
||||
</aside>
|
||||
</div>
|
||||
</div><script src="_static/documentation_options.js?v=a48ae3df"></script>
|
||||
</div><script src="_static/documentation_options.js?v=370aedac"></script>
|
||||
<script src="_static/doctools.js?v=9bcbadda"></script>
|
||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
||||
|
||||
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
||||
<title>Understanding Reticulum - Reticulum Network Stack 1.1.5 documentation</title>
|
||||
<title>Understanding Reticulum - Reticulum Network Stack 1.1.7 documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
||||
@@ -180,7 +180,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.1.5 documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.1.7 documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -204,7 +204,7 @@
|
||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.1.5 documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.1.7 documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
||||
@@ -1336,7 +1336,7 @@ those risks are acceptable to you.</p>
|
||||
|
||||
</aside>
|
||||
</div>
|
||||
</div><script src="_static/documentation_options.js?v=a48ae3df"></script>
|
||||
</div><script src="_static/documentation_options.js?v=370aedac"></script>
|
||||
<script src="_static/doctools.js?v=9bcbadda"></script>
|
||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
||||
|
||||
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
||||
<title>Using Reticulum on Your System - Reticulum Network Stack 1.1.5 documentation</title>
|
||||
<title>Using Reticulum on Your System - Reticulum Network Stack 1.1.7 documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
||||
@@ -180,7 +180,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.1.5 documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.1.7 documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -204,7 +204,7 @@
|
||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.1.5 documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.1.7 documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
||||
@@ -1395,7 +1395,7 @@ systemctl --user enable rnsd.service
|
||||
|
||||
</aside>
|
||||
</div>
|
||||
</div><script src="_static/documentation_options.js?v=a48ae3df"></script>
|
||||
</div><script src="_static/documentation_options.js?v=370aedac"></script>
|
||||
<script src="_static/doctools.js?v=9bcbadda"></script>
|
||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
||||
|
||||
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
||||
<title>What is Reticulum? - Reticulum Network Stack 1.1.5 documentation</title>
|
||||
<title>What is Reticulum? - Reticulum Network Stack 1.1.7 documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
||||
@@ -180,7 +180,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.1.5 documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.1.7 documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -204,7 +204,7 @@
|
||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.1.5 documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.1.7 documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
||||
@@ -503,7 +503,7 @@ network, and vice versa.</p>
|
||||
|
||||
</aside>
|
||||
</div>
|
||||
</div><script src="_static/documentation_options.js?v=a48ae3df"></script>
|
||||
</div><script src="_static/documentation_options.js?v=370aedac"></script>
|
||||
<script src="_static/doctools.js?v=9bcbadda"></script>
|
||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<link rel="prefetch" href="_static/rns_logo_512.png" as="image">
|
||||
|
||||
<!-- Generated with Sphinx 8.2.3 and Furo 2025.09.25.dev1 -->
|
||||
<title>Zen of Reticulum - Reticulum Network Stack 1.1.5 documentation</title>
|
||||
<title>Zen of Reticulum - Reticulum Network Stack 1.1.7 documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=d111a655" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?v=580074bf" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
|
||||
@@ -180,7 +180,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.1.5 documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 1.1.7 documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -204,7 +204,7 @@
|
||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.1.5 documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 1.1.7 documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
||||
@@ -675,7 +675,7 @@ Imagine a messaging app. You write it once. It works on a laptop connected to fi
|
||||
|
||||
</aside>
|
||||
</div>
|
||||
</div><script src="_static/documentation_options.js?v=a48ae3df"></script>
|
||||
</div><script src="_static/documentation_options.js?v=370aedac"></script>
|
||||
<script src="_static/doctools.js?v=9bcbadda"></script>
|
||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||
<script src="_static/scripts/furo.js?v=46bd48cc"></script>
|
||||
|
||||
Reference in New Issue
Block a user