Added medium bitrate based timeout calculation helpers and RPC functions and added dynamic timeout calculation to rngit, by Zenith

master
Mark Qvist 2026-08-21 22:16:10 +02:00
parent 378840a6bf
commit 2b1b9589f1
No known key found for this signature in database
5 changed files with 71 additions and 23 deletions

View File

@ -1281,18 +1281,20 @@ class Reticulum:
mh = call["max_hops"]
self.rpc_return(conn, self.get_path_table(max_hops=mh))
if path == "interface_stats": self.rpc_return(conn, self.get_interface_stats())
if path == "rate_table": self.rpc_return(conn, self.get_rate_table())
if path == "next_hop_if_name": self.rpc_return(conn, self.get_next_hop_if_name(call["destination_hash"]))
if path == "next_hop": self.rpc_return(conn, self.get_next_hop(call["destination_hash"]))
if path == "first_hop_timeout": self.rpc_return(conn, self.get_first_hop_timeout(call["destination_hash"]))
if path == "link_count": self.rpc_return(conn, self.get_link_count())
if path == "active_link_count": self.rpc_return(conn, self.get_active_link_count())
if path == "packet_rssi": self.rpc_return(conn, self.get_packet_rssi(call["packet_hash"]))
if path == "packet_snr": self.rpc_return(conn, self.get_packet_snr(call["packet_hash"]))
if path == "packet_q": self.rpc_return(conn, self.get_packet_q(call["packet_hash"]))
if path == "blackholed_identities": self.rpc_return(conn, self.get_blackholed_identities())
if path == "is_blackholed": self.rpc_return(conn, self.is_blackholed(call["identity_hash"]))
if path == "interface_stats": self.rpc_return(conn, self.get_interface_stats())
if path == "rate_table": self.rpc_return(conn, self.get_rate_table())
if path == "next_hop_if_name": self.rpc_return(conn, self.get_next_hop_if_name(call["destination_hash"]))
if path == "next_hop": self.rpc_return(conn, self.get_next_hop(call["destination_hash"]))
if path == "first_hop_timeout": self.rpc_return(conn, self.get_first_hop_timeout(call["destination_hash"]))
if path == "lowest_interface_bitrate": self.rpc_return(conn, self.get_lowest_interface_bitrate())
if path == "medium_path_timeout": self.rpc_return(conn, self.get_medium_path_timeout())
if path == "link_count": self.rpc_return(conn, self.get_link_count())
if path == "active_link_count": self.rpc_return(conn, self.get_active_link_count())
if path == "packet_rssi": self.rpc_return(conn, self.get_packet_rssi(call["packet_hash"]))
if path == "packet_snr": self.rpc_return(conn, self.get_packet_snr(call["packet_hash"]))
if path == "packet_q": self.rpc_return(conn, self.get_packet_q(call["packet_hash"]))
if path == "blackholed_identities": self.rpc_return(conn, self.get_blackholed_identities())
if path == "is_blackholed": self.rpc_return(conn, self.is_blackholed(call["identity_hash"]))
if "drop" in call:
path = call["drop"]
@ -1731,6 +1733,45 @@ class Reticulum:
else:
return RNS.Transport.first_hop_timeout(destination)
def get_lowest_interface_bitrate(self):
"""
Returns the bitrate of the slowest currently online
interface, or None if no online interface bitrate
:returns: Lowest online interface bitrate in bits per second, or ``None``.
"""
if self.is_connected_to_shared_instance:
try:
rpc_connection = self.get_rpc_client()
rpc_connection.send_bytes(mp.packb({"get": "lowest_interface_bitrate"}))
return mp.unpackb(rpc_connection.recv_bytes())
except Exception as e:
RNS.log("An error occurred while getting lowest interface bitrate from shared instance: "+str(e), RNS.LOG_ERROR)
return None
else:
return RNS.Transport.lowest_interface_bitrate
def get_medium_path_timeout(self):
"""
Returns an estimate of a reasonable minimum path request timeout covering
a full round trip for an MTU on the slowest currently online interface
plus per hop grace
:returns: Timeout in seconds or 0 if it's unknown.
"""
if self.is_connected_to_shared_instance:
try:
rpc_connection = self.get_rpc_client()
rpc_connection.send_bytes(mp.packb({"get": "medium_path_timeout"}))
return mp.unpackb(rpc_connection.recv_bytes())
except Exception as e:
RNS.log("An error occurred while getting medium path timeout from shared instance: "+str(e), RNS.LOG_ERROR)
return 0
else:
return RNS.Transport.medium_path_timeout()
def get_next_hop(self, destination):
if self.is_connected_to_shared_instance:
rpc_connection = self.get_rpc_client()

View File

@ -1753,9 +1753,7 @@ class Transport:
Transport.discovery_path_requests[destination_hash]["requesting_interfaces"].append(packet.receiving_interface)
else:
if not Transport.lowest_interface_bitrate: medium_timeout = 0
else: medium_timeout = 2*(RNS.Reticulum.MTU*8/max(Transport.lowest_interface_bitrate, RNS.Reticulum.MINIMUM_BITRATE)) + RNS.Reticulum.DEFAULT_PER_HOP_TIMEOUT
discovery_timeout = max(Transport.PATH_REQUEST_TIMEOUT, medium_timeout)
discovery_timeout = max(Transport.PATH_REQUEST_TIMEOUT, Transport.medium_path_timeout())
pr_entry = { "destination_hash": destination_hash, "timeout": time.time()+discovery_timeout,
"requesting_interfaces": [packet.receiving_interface], "engaged": False }
@ -3094,6 +3092,12 @@ class Transport:
if interface != None and interface.bitrate: return ((1/interface.bitrate)*8)*RNS.Reticulum.MTU
else: return 0
@staticmethod
def medium_path_timeout():
# A full round trip for an MTU on the slowest online interface
if not Transport.lowest_interface_bitrate: return 0
return 2*(RNS.Reticulum.MTU*8/max(Transport.lowest_interface_bitrate, RNS.Reticulum.MINIMUM_BITRATE)) + RNS.Reticulum.DEFAULT_PER_HOP_TIMEOUT
@staticmethod
def link_count():
return len(Transport.link_table)
@ -3431,9 +3435,7 @@ class Transport:
# except the requestor interface. The discovery
# timeout must also cover a full round trip for
# an MTU on the slowest online interface.
if not Transport.lowest_interface_bitrate: medium_timeout = 0
else: medium_timeout = 2*(RNS.Reticulum.MTU*8/max(Transport.lowest_interface_bitrate, RNS.Reticulum.MINIMUM_BITRATE)) + RNS.Reticulum.DEFAULT_PER_HOP_TIMEOUT
discovery_timeout = max(Transport.PATH_REQUEST_TIMEOUT, medium_timeout)
discovery_timeout = max(Transport.PATH_REQUEST_TIMEOUT, Transport.medium_path_timeout())
RNS.log("Attempting to discover unknown path to "+RNS.prettyhexrep(destination_hash)+" on behalf of path request"+interface_str, RNS.LOG_PATHING) if RNS.sl(RNS.LOG_PATHING) else None
pr_entry = { "destination_hash": destination_hash, "timeout": time.time()+discovery_timeout,

View File

@ -40,6 +40,7 @@ import subprocess
from RNS._version import __version__
from RNS.Utilities.rngit import APP_NAME
from RNS.Utilities.rngit.util import medium_path_timeout
from RNS.vendor.configobj import ConfigObj
from tempfile import TemporaryDirectory
@ -223,7 +224,7 @@ class ReticulumGitClient():
RNS.log(f"Requesting path to {RNS.prettyhexrep(destination_hash)}", RNS.LOG_DEBUG)
sys.stderr.write(f"Requesting path..."); sys.stderr.flush()
if not RNS.Transport.await_path(destination_hash, timeout=self.path_timeout):
if not RNS.Transport.await_path(destination_hash, timeout=medium_path_timeout(self.path_timeout)):
sys.stderr.write(f"\n"); sys.stderr.flush()
self.abort(f"Could not resolve path to {RNS.prettyhexrep(destination_hash)}")
@ -335,10 +336,10 @@ class ReticulumGitClient():
try: self.connect_server()
except Exception as e: self.abort(str(e))
timeout = self.link_timeout
timeout = max(self.link_timeout, self.link.establishment_timeout)
while not self.link_ready and not self.link_failed and timeout > 0:
time.sleep(0.5)
timeout -= 1
timeout -= 0.5
if not self.link_ready: self.abort("Failed to establish link")

View File

@ -46,7 +46,7 @@ from datetime import datetime, timezone
from RNS._version import __version__
from RNS.Utilities.rngit import APP_NAME
from RNS.Utilities.rngit.pages import NomadNetworkNode
from RNS.Utilities.rngit.util import san_ref, san_refs, san_sha
from RNS.Utilities.rngit.util import san_ref, san_refs, san_sha, medium_path_timeout
from RNS.vendor.configobj import ConfigObj
from RNS.vendor import umsgpack as mp
from RNS.Utilities.rnid import create_rsg, validate_rsg, get_rsg_hash
@ -465,7 +465,7 @@ class ReticulumGitClient():
def connect_remote(self, remote):
destination_hash = self.parse_remote_destination_url(remote)
print(f"Requesting path... ", end="")
if not RNS.Transport.await_path(destination_hash, timeout=self.path_timeout):
if not RNS.Transport.await_path(destination_hash, timeout=medium_path_timeout(self.path_timeout)):
print(f"\n", end="")
self.abort(f"Could not resolve path to {RNS.prettyhexrep(destination_hash)}")
@ -479,6 +479,7 @@ class ReticulumGitClient():
self.link = RNS.Link(self.destination)
self.link.set_link_established_callback(self.link_established)
self.link.set_link_closed_callback(self.link_closed)
self.link_timeout = max(self.link_timeout, self.link.establishment_timeout)
def link_established(self, link):
print(f"\rLink established ", end="")

View File

@ -814,3 +814,6 @@ class MarkdownToMicron:
def convert_markdown_to_micron(text):
converter = MarkdownToMicron()
return converter.format_block(text)
def medium_path_timeout(default_timeout):
return max(default_timeout, RNS.Reticulum.get_instance().get_medium_path_timeout())