Added interface discovery source filtering by network identity

This commit is contained in:
Mark Qvist 2026-01-02 21:03:18 +01:00
commit 0b508a04b8
4 changed files with 90 additions and 33 deletions

View file

@ -161,6 +161,11 @@ class InterfaceAnnounceHandler:
def received_announce(self, destination_hash, announced_identity, app_data):
try:
discovery_sources = RNS.Reticulum.interface_discovery_sources()
if discovery_sources and not announced_identity.hash in discovery_sources:
RNS.log(f"Interface discovered from non-authorized network identity {RNS.prettyhexrep(announced_identity.hash)}, ignoring", RNS.LOG_DEBUG)
return
if app_data and len(app_data) > self.stamper.STAMP_SIZE+1:
flags = app_data[0]
app_data = app_data[1:]
@ -314,12 +319,19 @@ class InterfaceDiscovery():
def list_discovered_interfaces(self):
now = time.time()
discovered_interfaces = []
discovery_sources = RNS.Reticulum.interface_discovery_sources()
for filename in os.listdir(self.storagepath):
try:
filepath = os.path.join(self.storagepath, filename)
with open(filepath, "rb") as f: info = msgpack.unpackb(f.read())
should_remove = False
heard_delta = now-info["last_heard"]
if heard_delta > self.THRESHOLD_REMOVE:
if heard_delta > self.THRESHOLD_REMOVE: should_remove = True
elif discovery_sources and not "network_id" in info: should_remove = True
elif discovery_sources and not bytes.fromhex(info["network_id"]) in discovery_sources: should_remove = True
if should_remove:
os.unlink(filepath)
continue
@ -333,6 +345,7 @@ class InterfaceDiscovery():
except Exception as e:
RNS.log(f"Error while loading discovered interface data: {e}", RNS.LOG_ERROR)
RNS.log(f"The interface data file {os.path.join(self.storagepath, filename)} may be corrupt", RNS.LOG_ERROR)
RNS.trace_exception(e)
discovered_interfaces.sort(key=lambda info: (info["status_code"], info["value"], info["last_heard"]), reverse=True)

View file

@ -262,6 +262,7 @@ class Reticulum:
Reticulum.__required_discovery_value = None
Reticulum.__publish_blackhole = False
Reticulum.__blackhole_sources = []
Reticulum.__interface_sources = []
Reticulum.panic_on_interface_error = False
@ -565,6 +566,15 @@ class Reticulum:
try: source_identity_hash = bytes.fromhex(hexhash)
except Exception as e: raise ValueError(f"Invalid identity hash for remote blackhole source: {hexhash}")
if not source_identity_hash in Reticulum.__blackhole_sources: Reticulum.__blackhole_sources.append(source_identity_hash)
if option == "interface_discovery_sources":
v = self.config["reticulum"].as_list(option)
for hexhash in v:
dest_len = (RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2
if len(hexhash) != dest_len: raise ValueError(f"Identity hash length for interface discovery source {hexhash} is invalid, must be {dest_len} hexadecimal characters ({dest_len//2} bytes).")
try: source_identity_hash = bytes.fromhex(hexhash)
except Exception as e: raise ValueError(f"Invalid identity hash for interface discovery source: {hexhash}")
if not source_identity_hash in Reticulum.__interface_sources: Reticulum.__interface_sources.append(source_identity_hash)
if RNS.compiled: RNS.log("Reticulum running in compiled mode", RNS.LOG_DEBUG)
else: RNS.log("Reticulum running in interpreted mode", RNS.LOG_DEBUG)
@ -1524,6 +1534,16 @@ class Reticulum:
"""
return Reticulum.__blackhole_sources
@staticmethod
def interface_discovery_sources():
"""
Returns the list of network identity hashes from which
interfaces are discovered.
:returns: A list of identity hashes.
"""
return Reticulum.__interface_sources
# Default configuration file:
__default_rns_config__ = '''# This is the default Reticulum config file.
# You should probably edit it to include any additional,

View file

@ -160,22 +160,6 @@ instance_name = default
# remote_management_allowed = 9fb6d773498fb3feda407ed8ef2c3229, 2d882c5586e548d79b5af27bca1776dc
# You can configure whether Reticulum should discover
# available interfaces from other Transport Instances over
# the network. If this option is enabled, Reticulum will
# collect interface information discovered from the network.
# discover_interfaces = No
# To prevent interface discovery spamming, a valid crypto-
# graphic stamp is required per announced interface. You
# can configure the minimum required value to accept as
# valid for discovered interfaces.
# required_discovery_value = 14
# For easier management, discovery and configuration of
# networks with many individual transport instances,
# you can specify a network identity to be used across
@ -189,6 +173,42 @@ instance_name = default
# network_identity = ~/.reticulum/storage/identity/network
# You can configure whether Reticulum should discover
# available interfaces from other Transport Instances over
# the network. If this option is enabled, Reticulum will
# collect interface information discovered from the network.
# discover_interfaces = No
# If you only want to discover interfaces from specific
# networks, you can provide a list of network identities
# from which to discover interfaces. If this option is not
# provided, interfaces will be discovered from all transport
# instances on all connected networks.
# interface_discovery_sources = 78616ff7c4b8d3886d67d494b440f333, cb127015e13aa6ea1e0a606cdc9123d0
# It is possible to automatically bring up and connect new
# interfaces discovered over the network. This option is
# disabled by default, but allows you to specify a maximum
# number of discovered interfaces to automatically connect.
# Additionally, if this option is enabled, Reticulum will
# also try to autoconnect available auto-discovered inter-
# faces on startup, up to the maximum number specified.
# autoconnect_discovered_interfaces = 0
# To prevent interface discovery spamming, a valid crypto-
# graphic stamp is required per announced interface. You
# can configure the minimum required value to accept as
# valid for discovered interfaces.
# required_discovery_value = 14
# You can configure Reticulum to panic and forcibly close
# if an unrecoverable interface error occurs, such as the
# hardware device for an interface disappearing. This is

View file

@ -215,29 +215,33 @@ def program_setup(configdir, dispall=False, verbosity=0, name_filter=None, json=
location = f"{lat}, {lon}{height}"
else: location = "Unknown"
transport_id = None
network = None
if "transport_id" in i: transport_id = i["transport_id"]
if "transport_id" in i and "network_id" in i and i["transport_id"] != i["network_id"]:
network = i["network_id"]
if idx > 0: print("\n"+"="*32+"\n")
if network: print(f"Network ID : {network}")
print(f"Name : {name}")
print(f"Type : {if_type}")
print(f"Status : {status_display}")
print(f"Transport : {transport_str}")
print(f"Distance : {i['hops']} hop{'' if i['hops'] == 1 else 's'}")
print(f"Discovered : {discovered_display}")
print(f"Last Heard : {last_heard_display}")
print(f"Location : {location}")
if idx > 0: print("\n"+"="*32+"\n")
if network: print(f"Network ID : {network}")
if transport_id: print(f"Transport ID : {transport_id}")
if "frequency" in i: print(f"Frequency : {i['frequency']:,} Hz")
if "bandwidth" in i: print(f"Bandwidth : {i['bandwidth']:,} Hz")
if "sf" in i: print(f"Sprd.Factor : {i['sf']}")
if "cr" in i: print(f"Coding Rate : {i['cr']}")
if "modulation" in i: print(f"Modulation : {i['modulation']}")
if "reachable_on" in i: print(f"Address : {i['reachable_on']}:{i['port']}")
print(f"Name : {name}")
print(f"Type : {if_type}")
print(f"Status : {status_display}")
print(f"Transport : {transport_str}")
print(f"Distance : {i['hops']} hop{'' if i['hops'] == 1 else 's'}")
print(f"Discovered : {discovered_display}")
print(f"Last Heard : {last_heard_display}")
print(f"Location : {location}")
print(f"Stamp Value : {i['value']}")
if "frequency" in i: print(f"Frequency : {i['frequency']:,} Hz")
if "bandwidth" in i: print(f"Bandwidth : {i['bandwidth']:,} Hz")
if "sf" in i: print(f"Sprd. Factor : {i['sf']}")
if "cr" in i: print(f"Coding Rate : {i['cr']}")
if "modulation" in i: print(f"Modulation : {i['modulation']}")
if "reachable_on" in i: print(f"Address : {i['reachable_on']}:{i['port']}")
print(f"Stamp Value : {i['value']}")
print(f"\nConfiguration Entry:")
config_lines = i["config_entry"].split('\n')