mirror of
https://github.com/markqvist/Reticulum.git
synced 2026-05-19 06:14:47 -07:00
Implemented network identity handling
This commit is contained in:
@@ -5,6 +5,7 @@ import threading
|
|||||||
from .vendor import umsgpack as msgpack
|
from .vendor import umsgpack as msgpack
|
||||||
|
|
||||||
NAME = 0xFF
|
NAME = 0xFF
|
||||||
|
TRANSPORT_ID = 0xFE
|
||||||
INTERFACE_TYPE = 0x00
|
INTERFACE_TYPE = 0x00
|
||||||
TRANSPORT = 0x01
|
TRANSPORT = 0x01
|
||||||
REACHABLE_ON = 0x02
|
REACHABLE_ON = 0x02
|
||||||
@@ -45,7 +46,10 @@ class InterfaceAnnouncer():
|
|||||||
self.stamper = LXStamper
|
self.stamper = LXStamper
|
||||||
self.stamp_cache = {}
|
self.stamp_cache = {}
|
||||||
|
|
||||||
self.discovery_destination = RNS.Destination(self.owner.identity, RNS.Destination.IN, RNS.Destination.SINGLE,
|
if self.owner.has_network_identity(): identity = self.owner.network_identity
|
||||||
|
else: identity = self.owner.identity
|
||||||
|
|
||||||
|
self.discovery_destination = RNS.Destination(identity, RNS.Destination.IN, RNS.Destination.SINGLE,
|
||||||
APP_NAME, "discovery", "interface")
|
APP_NAME, "discovery", "interface")
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
@@ -86,11 +90,13 @@ class InterfaceAnnouncer():
|
|||||||
def get_interface_announce_data(self, interface):
|
def get_interface_announce_data(self, interface):
|
||||||
interface_type = type(interface).__name__
|
interface_type = type(interface).__name__
|
||||||
stamp_value = interface.discovery_stamp_value if interface.discovery_stamp_value else self.DEFAULT_STAMP_VALUE
|
stamp_value = interface.discovery_stamp_value if interface.discovery_stamp_value else self.DEFAULT_STAMP_VALUE
|
||||||
|
|
||||||
if not interface_type in self.DISCOVERABLE_INTERFACE_TYPES: return None
|
if not interface_type in self.DISCOVERABLE_INTERFACE_TYPES: return None
|
||||||
else:
|
else:
|
||||||
flags = bytes([0x00])
|
flags = bytes([0x00])
|
||||||
info = {INTERFACE_TYPE: interface_type,
|
info = {INTERFACE_TYPE: interface_type,
|
||||||
TRANSPORT: RNS.Reticulum.transport_enabled(),
|
TRANSPORT: RNS.Reticulum.transport_enabled(),
|
||||||
|
TRANSPORT_ID: RNS.Transport.identity.hash,
|
||||||
NAME: self.sanitize(interface.discovery_name),
|
NAME: self.sanitize(interface.discovery_name),
|
||||||
LATITUDE: interface.discovery_latitude,
|
LATITUDE: interface.discovery_latitude,
|
||||||
LONGITUDE: interface.discovery_longitude,
|
LONGITUDE: interface.discovery_longitude,
|
||||||
@@ -137,6 +143,9 @@ class InterfaceAnnouncer():
|
|||||||
return flags+packed+stamp
|
return flags+packed+stamp
|
||||||
|
|
||||||
class InterfaceAnnounceHandler:
|
class InterfaceAnnounceHandler:
|
||||||
|
FLAG_SIGNED = 0b00000001
|
||||||
|
FLAG_ENCRYPTED = 0b00000010
|
||||||
|
|
||||||
def __init__(self, required_value=InterfaceAnnouncer.DEFAULT_STAMP_VALUE, callback=None):
|
def __init__(self, required_value=InterfaceAnnouncer.DEFAULT_STAMP_VALUE, callback=None):
|
||||||
import importlib.util
|
import importlib.util
|
||||||
if importlib.util.find_spec('LXMF') != None: from LXMF import LXStamper
|
if importlib.util.find_spec('LXMF') != None: from LXMF import LXStamper
|
||||||
@@ -153,7 +162,11 @@ class InterfaceAnnounceHandler:
|
|||||||
def received_announce(self, destination_hash, announced_identity, app_data):
|
def received_announce(self, destination_hash, announced_identity, app_data):
|
||||||
try:
|
try:
|
||||||
if app_data and len(app_data) > self.stamper.STAMP_SIZE+1:
|
if app_data and len(app_data) > self.stamper.STAMP_SIZE+1:
|
||||||
|
flags = app_data[0]
|
||||||
app_data = app_data[1:]
|
app_data = app_data[1:]
|
||||||
|
signed = flags & self.FLAG_SIGNED
|
||||||
|
encrypted = flags & self.FLAG_ENCRYPTED
|
||||||
|
|
||||||
stamp = app_data[-self.stamper.STAMP_SIZE:]
|
stamp = app_data[-self.stamper.STAMP_SIZE:]
|
||||||
packed = app_data[:-self.stamper.STAMP_SIZE]
|
packed = app_data[:-self.stamper.STAMP_SIZE]
|
||||||
infohash = RNS.Identity.full_hash(packed)
|
infohash = RNS.Identity.full_hash(packed)
|
||||||
@@ -177,7 +190,8 @@ class InterfaceAnnounceHandler:
|
|||||||
"received": time.time(),
|
"received": time.time(),
|
||||||
"stamp": stamp,
|
"stamp": stamp,
|
||||||
"value": value,
|
"value": value,
|
||||||
"identity": RNS.hexrep(announced_identity.hash, delimit=False),
|
"transport_id": RNS.hexrep(unpacked[TRANSPORT_ID], delimit=False),
|
||||||
|
"network_id": RNS.hexrep(announced_identity.hash, delimit=False),
|
||||||
"hops": RNS.Transport.hops_to(destination_hash),
|
"hops": RNS.Transport.hops_to(destination_hash),
|
||||||
"latitude": unpacked[LATITUDE],
|
"latitude": unpacked[LATITUDE],
|
||||||
"longitude": unpacked[LONGITUDE],
|
"longitude": unpacked[LONGITUDE],
|
||||||
@@ -195,7 +209,7 @@ class InterfaceAnnounceHandler:
|
|||||||
cfg_name = info["name"]
|
cfg_name = info["name"]
|
||||||
cfg_remote = info["reachable_on"]
|
cfg_remote = info["reachable_on"]
|
||||||
cfg_port = info["port"]
|
cfg_port = info["port"]
|
||||||
cfg_identity = info["identity"]
|
cfg_identity = info["transport_id"]
|
||||||
cfg_netname = info["ifac_netname"] if "ifac_netname" in info else None
|
cfg_netname = info["ifac_netname"] if "ifac_netname" in info else None
|
||||||
cfg_netkey = info["ifac_netkey"] if "ifac_netkey" in info else None
|
cfg_netkey = info["ifac_netkey"] if "ifac_netkey" in info else None
|
||||||
cfg_netname_str = f"\n network_name = {cfg_netname}" if cfg_netname else ""
|
cfg_netname_str = f"\n network_name = {cfg_netname}" if cfg_netname else ""
|
||||||
@@ -207,7 +221,7 @@ class InterfaceAnnounceHandler:
|
|||||||
info["reachable_on"] = unpacked[REACHABLE_ON]
|
info["reachable_on"] = unpacked[REACHABLE_ON]
|
||||||
cfg_name = info["name"]
|
cfg_name = info["name"]
|
||||||
cfg_remote = info["reachable_on"]
|
cfg_remote = info["reachable_on"]
|
||||||
cfg_identity = info["identity"]
|
cfg_identity = info["transport_id"]
|
||||||
cfg_netname = info["ifac_netname"] if "ifac_netname" in info else None
|
cfg_netname = info["ifac_netname"] if "ifac_netname" in info else None
|
||||||
cfg_netkey = info["ifac_netkey"] if "ifac_netkey" in info else None
|
cfg_netkey = info["ifac_netkey"] if "ifac_netkey" in info else None
|
||||||
cfg_netname_str = f"\n network_name = {cfg_netname}" if cfg_netname else ""
|
cfg_netname_str = f"\n network_name = {cfg_netname}" if cfg_netname else ""
|
||||||
@@ -225,7 +239,7 @@ class InterfaceAnnounceHandler:
|
|||||||
cfg_bandwidth = info["bandwidth"]
|
cfg_bandwidth = info["bandwidth"]
|
||||||
cfg_sf = info["sf"]
|
cfg_sf = info["sf"]
|
||||||
cfg_cr = info["cr"]
|
cfg_cr = info["cr"]
|
||||||
cfg_identity = info["identity"]
|
cfg_identity = info["transport_id"]
|
||||||
cfg_netname = info["ifac_netname"] if "ifac_netname" in info else None
|
cfg_netname = info["ifac_netname"] if "ifac_netname" in info else None
|
||||||
cfg_netkey = info["ifac_netkey"] if "ifac_netkey" in info else None
|
cfg_netkey = info["ifac_netkey"] if "ifac_netkey" in info else None
|
||||||
cfg_netname_str = f"\n network_name = {cfg_netname}" if cfg_netname else ""
|
cfg_netname_str = f"\n network_name = {cfg_netname}" if cfg_netname else ""
|
||||||
@@ -239,7 +253,7 @@ class InterfaceAnnounceHandler:
|
|||||||
info["channel"] = unpacked[CHANNEL]
|
info["channel"] = unpacked[CHANNEL]
|
||||||
info["modulation"] = unpacked[MODULATION]
|
info["modulation"] = unpacked[MODULATION]
|
||||||
cfg_name = info["name"]
|
cfg_name = info["name"]
|
||||||
cfg_identity = info["identity"]
|
cfg_identity = info["transport_id"]
|
||||||
cfg_netname = info["ifac_netname"] if "ifac_netname" in info else None
|
cfg_netname = info["ifac_netname"] if "ifac_netname" in info else None
|
||||||
cfg_netkey = info["ifac_netkey"] if "ifac_netkey" in info else None
|
cfg_netkey = info["ifac_netkey"] if "ifac_netkey" in info else None
|
||||||
cfg_netname_str = f"\n network_name = {cfg_netname}" if cfg_netname else ""
|
cfg_netname_str = f"\n network_name = {cfg_netname}" if cfg_netname else ""
|
||||||
@@ -255,7 +269,7 @@ class InterfaceAnnounceHandler:
|
|||||||
cfg_frequency = info["frequency"]
|
cfg_frequency = info["frequency"]
|
||||||
cfg_bandwidth = info["bandwidth"]
|
cfg_bandwidth = info["bandwidth"]
|
||||||
cfg_modulation = info["modulation"]
|
cfg_modulation = info["modulation"]
|
||||||
cfg_identity = info["identity"]
|
cfg_identity = info["transport_id"]
|
||||||
cfg_netname = info["ifac_netname"] if "ifac_netname" in info else None
|
cfg_netname = info["ifac_netname"] if "ifac_netname" in info else None
|
||||||
cfg_netkey = info["ifac_netkey"] if "ifac_netkey" in info else None
|
cfg_netkey = info["ifac_netkey"] if "ifac_netkey" in info else None
|
||||||
cfg_netname_str = f"\n network_name = {cfg_netname}" if cfg_netname else ""
|
cfg_netname_str = f"\n network_name = {cfg_netname}" if cfg_netname else ""
|
||||||
@@ -263,7 +277,7 @@ class InterfaceAnnounceHandler:
|
|||||||
cfg_identity_str = f"\n transport_identity = {cfg_identity}"
|
cfg_identity_str = f"\n transport_identity = {cfg_identity}"
|
||||||
info["config_entry"] = f"[[{cfg_name}]]\n type = KISSInterface\n enabled = yes\n port = \n # Frequency: {cfg_frequency}\n # Bandwidth: {cfg_bandwidth}\n # Modulation: {cfg_modulation}{cfg_identity_str}{cfg_netname_str}{cfg_netkey_str}"
|
info["config_entry"] = f"[[{cfg_name}]]\n type = KISSInterface\n enabled = yes\n port = \n # Frequency: {cfg_frequency}\n # Bandwidth: {cfg_bandwidth}\n # Modulation: {cfg_modulation}{cfg_identity_str}{cfg_netname_str}{cfg_netkey_str}"
|
||||||
|
|
||||||
discovery_hash_material = info["identity"]+info["name"]
|
discovery_hash_material = info["transport_id"]+info["name"]
|
||||||
info["discovery_hash"] = RNS.Identity.full_hash(discovery_hash_material.encode("utf-8"))
|
info["discovery_hash"] = RNS.Identity.full_hash(discovery_hash_material.encode("utf-8"))
|
||||||
|
|
||||||
RNS.log(f"Discovered interface with stamp value {value}: {info}", RNS.LOG_DEBUG)
|
RNS.log(f"Discovered interface with stamp value {value}: {info}", RNS.LOG_DEBUG)
|
||||||
@@ -280,7 +294,6 @@ class InterfaceDiscovery():
|
|||||||
STATUS_STALE = 0
|
STATUS_STALE = 0
|
||||||
STATUS_UNKNOWN = 100
|
STATUS_UNKNOWN = 100
|
||||||
STATUS_AVAILABLE = 1000
|
STATUS_AVAILABLE = 1000
|
||||||
|
|
||||||
STATUS_CODE_MAP = {"available": STATUS_AVAILABLE, "unknown": STATUS_UNKNOWN, "stale": STATUS_STALE}
|
STATUS_CODE_MAP = {"available": STATUS_AVAILABLE, "unknown": STATUS_UNKNOWN, "stale": STATUS_STALE}
|
||||||
|
|
||||||
def __init__(self, required_value=InterfaceAnnouncer.DEFAULT_STAMP_VALUE, callback=None, discover_interfaces=True):
|
def __init__(self, required_value=InterfaceAnnouncer.DEFAULT_STAMP_VALUE, callback=None, discover_interfaces=True):
|
||||||
|
|||||||
@@ -251,6 +251,7 @@ class Reticulum:
|
|||||||
Reticulum.blackholepath = Reticulum.configdir+"/storage/blackhole"
|
Reticulum.blackholepath = Reticulum.configdir+"/storage/blackhole"
|
||||||
Reticulum.interfacepath = Reticulum.configdir+"/interfaces"
|
Reticulum.interfacepath = Reticulum.configdir+"/interfaces"
|
||||||
|
|
||||||
|
Reticulum.__network_identity = None
|
||||||
Reticulum.__transport_enabled = False
|
Reticulum.__transport_enabled = False
|
||||||
Reticulum.__link_mtu_discovery = Reticulum.LINK_MTU_DISCOVERY
|
Reticulum.__link_mtu_discovery = Reticulum.LINK_MTU_DISCOVERY
|
||||||
Reticulum.__remote_management_enabled = False
|
Reticulum.__remote_management_enabled = False
|
||||||
@@ -482,6 +483,29 @@ class Reticulum:
|
|||||||
v = self.config["reticulum"].as_bool(option)
|
v = self.config["reticulum"].as_bool(option)
|
||||||
if v == True: Reticulum.__transport_enabled = True
|
if v == True: Reticulum.__transport_enabled = True
|
||||||
|
|
||||||
|
if option == "network_identity":
|
||||||
|
if Reticulum.__network_identity == None:
|
||||||
|
path = self.config["reticulum"][option]
|
||||||
|
identitypath = os.path.expanduser(path)
|
||||||
|
try:
|
||||||
|
network_identity = None
|
||||||
|
if not os.path.isfile(identitypath):
|
||||||
|
network_identity = RNS.Identity()
|
||||||
|
network_identity.to_file(identitypath)
|
||||||
|
RNS.log(f"Network identity generated and persisted to {identitypath}", RNS.LOG_VERBOSE)
|
||||||
|
|
||||||
|
else:
|
||||||
|
network_identity = RNS.Identity.from_file(identitypath)
|
||||||
|
RNS.log(f"Network identity loaded from {identitypath}", RNS.LOG_VERBOSE)
|
||||||
|
|
||||||
|
if network_identity:
|
||||||
|
Reticulum.__network_identity = network_identity
|
||||||
|
RNS.Transport.set_network_identity(Reticulum.__network_identity)
|
||||||
|
|
||||||
|
else: raise ValueError("Network identity initialisation failed")
|
||||||
|
|
||||||
|
except Exception as e: raise ValueError(f"Could not set network identity from {path}: {e}")
|
||||||
|
|
||||||
if option == "link_mtu_discovery":
|
if option == "link_mtu_discovery":
|
||||||
v = self.config["reticulum"].as_bool(option)
|
v = self.config["reticulum"].as_bool(option)
|
||||||
if v == True: Reticulum.__link_mtu_discovery = True
|
if v == True: Reticulum.__link_mtu_discovery = True
|
||||||
@@ -669,6 +693,7 @@ class Reticulum:
|
|||||||
discovery_announce_interval = None
|
discovery_announce_interval = None
|
||||||
discovery_stamp_value = None
|
discovery_stamp_value = None
|
||||||
discovery_name = None
|
discovery_name = None
|
||||||
|
discovery_sign = False
|
||||||
reachable_on = None
|
reachable_on = None
|
||||||
publish_ifac = False
|
publish_ifac = False
|
||||||
latitude = None
|
latitude = None
|
||||||
@@ -688,6 +713,7 @@ class Reticulum:
|
|||||||
if discovery_announce_interval == None: discovery_announce_interval = 6*60*60
|
if discovery_announce_interval == None: discovery_announce_interval = 6*60*60
|
||||||
if "discovery_stamp_value" in c: discovery_stamp_value = c.as_int("discovery_stamp_value")
|
if "discovery_stamp_value" in c: discovery_stamp_value = c.as_int("discovery_stamp_value")
|
||||||
if "discovery_name" in c: discovery_name = c["discovery_name"]
|
if "discovery_name" in c: discovery_name = c["discovery_name"]
|
||||||
|
if "discovery_sign" in c: discovery_sign = c.as_bool("discovery_sign")
|
||||||
if "reachable_on" in c: reachable_on = c["reachable_on"]
|
if "reachable_on" in c: reachable_on = c["reachable_on"]
|
||||||
if "publish_ifac" in c: publish_ifac = c.as_bool("publish_ifac")
|
if "publish_ifac" in c: publish_ifac = c.as_bool("publish_ifac")
|
||||||
if "latitude" in c: latitude = c.as_float("latitude")
|
if "latitude" in c: latitude = c.as_float("latitude")
|
||||||
@@ -718,6 +744,7 @@ class Reticulum:
|
|||||||
interface.discovery_publish_ifac = publish_ifac
|
interface.discovery_publish_ifac = publish_ifac
|
||||||
interface.reachable_on = reachable_on
|
interface.reachable_on = reachable_on
|
||||||
interface.discovery_name = discovery_name
|
interface.discovery_name = discovery_name
|
||||||
|
interface.discovery_sign = discovery_sign
|
||||||
interface.discovery_stamp_value = discovery_stamp_value
|
interface.discovery_stamp_value = discovery_stamp_value
|
||||||
interface.discovery_latitude = latitude
|
interface.discovery_latitude = latitude
|
||||||
interface.discovery_longitude = longitude
|
interface.discovery_longitude = longitude
|
||||||
|
|||||||
@@ -174,6 +174,7 @@ class Transport:
|
|||||||
traffic_captured = None
|
traffic_captured = None
|
||||||
|
|
||||||
identity = None
|
identity = None
|
||||||
|
network_identity = None
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def start(reticulum_instance):
|
def start(reticulum_instance):
|
||||||
@@ -231,6 +232,14 @@ class Transport:
|
|||||||
Transport.mgmt_hashes.append(Transport.blackhole_destination.hash)
|
Transport.mgmt_hashes.append(Transport.blackhole_destination.hash)
|
||||||
RNS.log(f"Enabled blackhole list publishing for transport identity {RNS.prettyhexrep(Transport.identity.hash)}", RNS.LOG_NOTICE)
|
RNS.log(f"Enabled blackhole list publishing for transport identity {RNS.prettyhexrep(Transport.identity.hash)}", RNS.LOG_NOTICE)
|
||||||
|
|
||||||
|
if Transport.network_identity and not Transport.owner.is_connected_to_shared_instance:
|
||||||
|
Transport.instance_destination = RNS.Destination(Transport.network_identity, RNS.Destination.IN, RNS.Destination.SINGLE, Transport.APP_NAME, "network", "instance", RNS.hexrep(Transport.network_identity.hash, delimit=False))
|
||||||
|
Transport.network_destination = RNS.Destination(Transport.network_identity, RNS.Destination.IN, RNS.Destination.SINGLE, Transport.APP_NAME, "network")
|
||||||
|
Transport.mgmt_destinations.append(Transport.instance_destination)
|
||||||
|
Transport.mgmt_destinations.append(Transport.network_destination)
|
||||||
|
Transport.mgmt_hashes.append(Transport.instance_destination)
|
||||||
|
Transport.mgmt_hashes.append(Transport.network_destination)
|
||||||
|
|
||||||
# Defer cleaning packet cache for 60 seconds
|
# Defer cleaning packet cache for 60 seconds
|
||||||
Transport.cache_last_cleaned = time.time() + 60
|
Transport.cache_last_cleaned = time.time() + 60
|
||||||
|
|
||||||
@@ -374,6 +383,16 @@ class Transport:
|
|||||||
|
|
||||||
gc.collect()
|
gc.collect()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def set_network_identity(identity):
|
||||||
|
if not Transport.network_identity:
|
||||||
|
Transport.network_identity = identity
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def has_network_identity():
|
||||||
|
if Transport.network_identity: return True
|
||||||
|
else: return False
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def prioritize_interfaces():
|
def prioritize_interfaces():
|
||||||
try: Transport.interfaces.sort(key=lambda interface: interface.bitrate, reverse=True)
|
try: Transport.interfaces.sort(key=lambda interface: interface.bitrate, reverse=True)
|
||||||
@@ -3172,7 +3191,7 @@ class Transport:
|
|||||||
if len(filename) != dest_len: raise ValueError(f"Identity hash length for blackhole source {filename} is invalid")
|
if len(filename) != dest_len: raise ValueError(f"Identity hash length for blackhole source {filename} is invalid")
|
||||||
source_identity_hash = bytes.fromhex(filename)
|
source_identity_hash = bytes.fromhex(filename)
|
||||||
if not source_identity_hash in RNS.Reticulum.blackhole_sources():
|
if not source_identity_hash in RNS.Reticulum.blackhole_sources():
|
||||||
RNS.log(f"Skipping disabled blackhole source {RNS.prettyhexrep(source_identity_hash)}", RNS.LOG_INFO)
|
RNS.log(f"Skipping disabled blackhole source {RNS.prettyhexrep(source_identity_hash)}", RNS.LOG_VERBOSE)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
sourcepath = os.path.join(RNS.Reticulum.blackholepath, filename)
|
sourcepath = os.path.join(RNS.Reticulum.blackholepath, filename)
|
||||||
|
|||||||
@@ -176,6 +176,19 @@ instance_name = default
|
|||||||
# required_discovery_value = 14
|
# 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
|
||||||
|
# a set of instances. If sending interface discovery
|
||||||
|
# announces, these will all be signed by the specified
|
||||||
|
# network identity, and other nodes discovering your
|
||||||
|
# interfaces will be able to identify that they belong
|
||||||
|
# to the same network, even though they exist on different
|
||||||
|
# transport nodes.
|
||||||
|
|
||||||
|
# network_identity = ~/.reticulum/storage/identity/network
|
||||||
|
|
||||||
|
|
||||||
# You can configure Reticulum to panic and forcibly close
|
# You can configure Reticulum to panic and forcibly close
|
||||||
# if an unrecoverable interface error occurs, such as the
|
# if an unrecoverable interface error occurs, such as the
|
||||||
# hardware device for an interface disappearing. This is
|
# hardware device for an interface disappearing. This is
|
||||||
|
|||||||
@@ -215,8 +215,12 @@ def program_setup(configdir, dispall=False, verbosity=0, name_filter=None, json=
|
|||||||
location = f"{lat}, {lon}{height}"
|
location = f"{lat}, {lon}{height}"
|
||||||
else: location = "Unknown"
|
else: location = "Unknown"
|
||||||
|
|
||||||
|
network = None
|
||||||
|
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 idx > 0: print("\n"+"="*32+"\n")
|
||||||
|
if network: print(f"Network ID : {network}")
|
||||||
print(f"Name : {name}")
|
print(f"Name : {name}")
|
||||||
print(f"Type : {if_type}")
|
print(f"Type : {if_type}")
|
||||||
print(f"Status : {status_display}")
|
print(f"Status : {status_display}")
|
||||||
|
|||||||
Reference in New Issue
Block a user