From 73c37a0f7b98b7a7934c74fd19f55bd3318ab063 Mon Sep 17 00:00:00 2001 From: Mika Joenpera Date: Wed, 11 Oct 2023 08:44:07 +0300 Subject: [PATCH] Initial multiradio status skeleton Jira-Id: SCDI-43 Signed-off-by: Mika Joenpera --- .../src/nats/comms_nats_controller.py | 42 +++++++--- .../src/nats/scripts/cli_settings_apply.py | 2 +- .../src/nats/scripts/cli_settings_request.py | 6 +- .../src/nats/src/comms_command.py | 31 +++---- .../src/nats/src/comms_settings.py | 80 ++++++++----------- modules/utils/docker/entrypoint_nats.sh | 8 +- 6 files changed, 95 insertions(+), 74 deletions(-) diff --git a/modules/sc-mesh-secure-deployment/src/nats/comms_nats_controller.py b/modules/sc-mesh-secure-deployment/src/nats/comms_nats_controller.py index 81706b176..61acd4daa 100644 --- a/modules/sc-mesh-secure-deployment/src/nats/comms_nats_controller.py +++ b/modules/sc-mesh-secure-deployment/src/nats/comms_nats_controller.py @@ -29,8 +29,8 @@ def __init__(self, loop_interval: int = 1000, logger: logging.Logger = None): # milliseconds to seconds self.interval = float(loop_interval / 1000.0) self.logger = logger - self.batman_visual = batadvvis.BatAdvVis(self.interval*0.2) - self.batman = batstat.Batman(self.interval*0.2) + self.batman_visual = batadvvis.BatAdvVis(self.interval * 0.2) + self.batman = batstat.Batman(self.interval * 0.2) self.visualisation_enabled = False def mesh_visual(self): @@ -76,6 +76,7 @@ class CommsController: # pylint: disable=too-few-public-methods """ Mesh network """ + def __init__(self, server: str, port: str, interval: int = 1000): self.nats_server = server self.port = port @@ -90,7 +91,11 @@ def __init__(self, server: str, port: str, interval: int = 1000): console_handler.setFormatter(log_formatter) self.main_logger.addHandler(console_handler) - self.comms_status = comms_status.CommsStatus(self.main_logger.getChild("status")) + self.comms_status = [] + #TODO how many radios? + self.comms_status.append(comms_status.CommsStatus(self.main_logger.getChild("status 0"))) + self.comms_status.append(comms_status.CommsStatus(self.main_logger.getChild("status 1"))) + self.comms_status.append(comms_status.CommsStatus(self.main_logger.getChild("status 2"))) self.settings = comms_settings.CommsSettings(self.comms_status, self.main_logger.getChild("settings")) self.command = comms_command.Command(server, port, self.comms_status, @@ -105,6 +110,7 @@ class CommsCsa: # pylint: disable=too-few-public-methods """ Comms CSA class to storage settings for CSA for a state change """ + def __init__(self): self.delay = "0" self.ack_sent = False @@ -209,14 +215,30 @@ async def message_handler(message): await handle_settings_csa_post(ret) else: # Update status info - cc.comms_status.refresh_status() + # TODO how many radios? + cc.comms_status[0].refresh_status() + cc.comms_status[1].refresh_status() + cc.comms_status[2].refresh_status() response = {'status': ret, 'info': info, - 'mesh_status': cc.comms_status.mesh_status, - 'mesh_cfg_status': cc.comms_status.mesh_cfg_status, - 'visualisation_active': cc.comms_status.is_visualisation_active, - 'mesh_radio_on': cc.comms_status.is_mesh_radio_on, - 'ap_radio_on': cc.comms_status.is_ap_radio_on, - 'security_status': cc.comms_status.security_status} + 'mesh_status': [cc.comms_status[0].mesh_status, + cc.comms_status[1].mesh_status, + cc.comms_status[2].mesh_status], + 'mesh_cfg_status': [cc.comms_status[0].mesh_cfg_status, + cc.comms_status[1].mesh_cfg_status, + cc.comms_status[2].mesh_cfg_status], + 'visualisation_active': [cc.comms_status[0].is_visualisation_active, + cc.comms_status[1].is_visualisation_active, + cc.comms_status[2].is_visualisation_active], + 'mesh_radio_on': [cc.comms_status[0].is_mesh_radio_on, + cc.comms_status[1].is_mesh_radio_on, + cc.comms_status[2].is_mesh_radio_on], + 'ap_radio_on': [cc.comms_status[0].is_ap_radio_on, + cc.comms_status[1].is_ap_radio_on, + cc.comms_status[2].is_ap_radio_on], + 'security_status': [cc.comms_status[0].security_status, + cc.comms_status[1].security_status, + cc.comms_status[2].security_status] + } if resp != "": response['data'] = resp diff --git a/modules/sc-mesh-secure-deployment/src/nats/scripts/cli_settings_apply.py b/modules/sc-mesh-secure-deployment/src/nats/scripts/cli_settings_apply.py index 655008c9b..4bad56871 100644 --- a/modules/sc-mesh-secure-deployment/src/nats/scripts/cli_settings_apply.py +++ b/modules/sc-mesh-secure-deployment/src/nats/scripts/cli_settings_apply.py @@ -8,7 +8,7 @@ async def main(): # Connect to NATS! nc = await client.connect_nats() - cmd_dict = {"api_version": 1, "cmd": "APPLY", "radio_index": "0"} + cmd_dict = {"api_version": 1, "cmd": "APPLY", "radio_index": "1"} cmd = json.dumps(cmd_dict) rep = await nc.request( f"comms.command.{config.MODULE_IDENTITY}", cmd.encode(), timeout=10 diff --git a/modules/sc-mesh-secure-deployment/src/nats/scripts/cli_settings_request.py b/modules/sc-mesh-secure-deployment/src/nats/scripts/cli_settings_request.py index e42e0c2d3..eb9308345 100644 --- a/modules/sc-mesh-secure-deployment/src/nats/scripts/cli_settings_request.py +++ b/modules/sc-mesh-secure-deployment/src/nats/scripts/cli_settings_request.py @@ -43,12 +43,12 @@ async def main(): "subnet": "255.255.255.0", "tx_power": "5", "mode": "mesh", - "mesh_vif": "wlp1s0", - "phy": "phy0", + "mesh_vif": "wlp1s2", + "phy": "phy1", "batman_iface": "bat0", }, ], - "bridge": [ { "br-mesh": "bat0 eth0 eth1 lan1" } ] + "bridge": "br-lan bat0 lan1" } cmd = json.dumps(cmd_dict) diff --git a/modules/sc-mesh-secure-deployment/src/nats/src/comms_command.py b/modules/sc-mesh-secure-deployment/src/nats/src/comms_command.py index bcdab0765..9511349d4 100644 --- a/modules/sc-mesh-secure-deployment/src/nats/src/comms_command.py +++ b/modules/sc-mesh-secure-deployment/src/nats/src/comms_command.py @@ -48,7 +48,7 @@ class Command: # pylint: disable=too-few-public-methods, too-many-instance-attr Command class """ - def __init__(self, server, port, comms_status: CommsStatus, logger): + def __init__(self, server, port, comms_status: [CommsStatus, ...], logger): self.nats_server = server self.port = port self.logger = logger @@ -164,7 +164,7 @@ def __revoke(self, cc) -> (str, str): + str(ret.stderr) self.logger.debug("Default mesh command applied") - if self.comms_status.is_visualisation_active: + if self.comms_status[int(self.radio_index)].is_visualisation_active: ret, _, _ = self.__disable_visualisation(cc) if ret == "FAIL": return "FAIL", "Revoke failed partially." \ @@ -180,20 +180,20 @@ def __apply_mission_config(self, csa=False, delay="0") -> (str, str): Returns: tuple: (str, str) """ - if self.comms_status.mesh_cfg_status == STATUS.mesh_cfg_stored: + if self.comms_status[int(self.radio_index)].mesh_cfg_status == STATUS.mesh_cfg_stored: try: - os.replace("/opt/mesh_stored.conf", "/opt/mesh.conf") + os.replace(f"/opt/{self.radio_index}_mesh_stored.conf", f"/opt/{self.radio_index}_mesh.conf") except: self.logger.error("Error replacing active config file!") return "FAIL", "Error replacing active config file" # Create hash file for active config bookkeeping try: - with open("/opt/mesh.conf", "rb") as f: + with open(f"/opt/{self.radio_index}_mesh.conf", "rb") as f: data = f.read() hash_obj = hashlib.sha256(data) hash_hex = hash_obj.hexdigest() - with open("/opt/mesh.conf_hash", "w") as f_hash: + with open(f"/opt/{self.radio_index}_mesh.conf_hash", "w") as f_hash: f_hash.write(hash_hex) except: self.logger.error("Error writing hash file!") @@ -235,7 +235,7 @@ def __apply_mission_config(self, csa=False, delay="0") -> (str, str): def __radio_down(self) -> (str, str): for process in ["/opt/S9011sNatsMesh", "/opt/S90APoint"]: - ret = subprocess.run([process, "stop"], + ret = subprocess.run([process, "stop", "id" + self.radio_index], shell=False, check=True, capture_output=True) if ret.returncode != 0: self.logger.error("Failed to deactivate radio") @@ -250,7 +250,7 @@ def __radio_down(self) -> (str, str): def __radio_up(self) -> (str, str): for process in ["/opt/S9011sNatsMesh", "/opt/S90APoint"]: - ret = subprocess.run([process, "start"], + ret = subprocess.run([process, "start", "id" + self.radio_index], shell=False, check=True, capture_output=True) if ret.returncode != 0: @@ -271,7 +271,7 @@ def __enable_visualisation(self, cc) -> (str, str): return "FAIL", "Failed to enable visualisation" self.logger.debug("Visualisation enabled") - self.comms_status.is_visualisation_active = True + self.comms_status[int(self.radio_index)].is_visualisation_active = True return "OK", "Visualisation enabled" def __disable_visualisation(self, cc) -> (str, str): @@ -283,7 +283,7 @@ def __disable_visualisation(self, cc) -> (str, str): return "FAIL", "Failed to disable visualisation" self.logger.debug("Visualisation disabled") - self.comms_status.is_visualisation_active = False + self.comms_status[int(self.radio_index)].is_visualisation_active = False return "OK", "Visualisation disabled" def __read_log_file(self, filename) -> bytes: @@ -328,14 +328,14 @@ def __get_configs(self, param) -> (str, str, str): file_b64 = b'None' try: files = ConfigFiles() - self.comms_status.refresh_status() + self.comms_status[int(self.radio_index)].refresh_status() if param == files.WPA: - if_name = self.comms_status.mesh_interface_name + if_name = self.comms_status[int(self.radio_index)].mesh_interface_name if if_name: file_b64 = self.__read_log_file( f"/var/run/wpa_supplicant-11s_{if_name}.conf") elif param == files.HOSTAPD: - if_name = self.comms_status.ap_interface_name + if_name = self.comms_status[int(self.radio_index)].ap_interface_name if if_name: file_b64 = self.__read_log_file( f"/var/run/hostapd-{if_name}.conf") @@ -355,11 +355,14 @@ def get_identity(self) -> (str, str, dict): identity_dict = {} try: files = ConfigFiles() - self.comms_status.refresh_status() + self.comms_status[0].refresh_status() + self.comms_status[1].refresh_status() + self.comms_status[2].refresh_status() with open(files.IDENTITY, "rb") as f: identity = f.read() identity_dict["identity"] = identity.decode().strip() + # todo hardcoded interface name identity_dict["nats_url"] = f"nats://{ni.ifaddresses('br-lan')[ni.AF_INET][0]['addr']}:4222" except: diff --git a/modules/sc-mesh-secure-deployment/src/nats/src/comms_settings.py b/modules/sc-mesh-secure-deployment/src/nats/src/comms_settings.py index 009143089..190a8b6c8 100644 --- a/modules/sc-mesh-secure-deployment/src/nats/src/comms_settings.py +++ b/modules/sc-mesh-secure-deployment/src/nats/src/comms_settings.py @@ -3,7 +3,6 @@ """ import json from shlex import quote -import subprocess import logging import re @@ -22,7 +21,7 @@ class CommsSettings: # pylint: disable=too-few-public-methods, too-many-instanc Comms settings class """ - def __init__(self, comms_status: cs.CommsStatus, logger): + def __init__(self, comms_status: [cs.CommsStatus, ...], logger): self.logger: logging = logger self.api_version: int = 1 self.radio_index = [] @@ -42,7 +41,7 @@ def __init__(self, comms_status: cs.CommsStatus, logger): self.mesh_vif = [] self.phy = [] self.batman_iface = [] - self.bridge = [] + self.bridge: str = "" self.msversion: str = "" self.delay:str = "" # delay for channel change self.comms_status = comms_status @@ -203,76 +202,67 @@ def handle_mesh_settings(self, msg: str, path="/opt", self.phy.append(quote(str(parameters["phy"]))) self.batman_iface.append(quote(str(parameters["batman_iface"]))) - bridges = parameters_set["bridge"] - for bridge in bridges: - for key, values in bridge.items(): - self.bridge.append(f"{quote(str(key))} {quote(str(values))}") + self.bridge = quote(str(parameters_set["bridge"])) ret, info = "FAIL", "Before validation" for index in self.radio_index: self.logger.debug("Mesh settings validation index: %s", str(index)) ret, info = self.validate_mesh_settings(index) - if ret == "FAIL": - break - self.logger.debug("Mesh settings validation: %s, %s", ret, info) - if ret == "FAIL": - self.comms_status.mesh_cfg_status = \ - comms.STATUS.mesh_cfg_not_stored - self.logger.error("save settings failed: %s, %s", ret, info) - else: - ret, info = self.__save_settings(path, file) - self.logger.debug("save settings: %s, %s", ret, info) + self.logger.debug("Mesh settings validation id %s: %s, %s", str(index), ret, info) + if ret == "FAIL": + self.comms_status[index].mesh_cfg_status = \ + comms.STATUS.mesh_cfg_not_stored + self.logger.error("save settings failed: %s, %s", ret, info) + else: + ret, info = self.__save_settings(path, file, index) + self.logger.debug("save settings index %s: %s, %s", str(index), ret, info) except (json.decoder.JSONDecodeError, KeyError, TypeError, AttributeError) as error: self.comms_status.mesh_cfg_status = \ comms.STATUS.mesh_cfg_not_stored - ret, status = "FAIL", self.comms_status.mesh_cfg_status + ret, _ = "FAIL", self.comms_status.mesh_cfg_status info = "JSON format not correct" + str(error) self.logger.error("Mesh settings validation: %s, %s", ret, info) return ret, info - def __save_settings(self, path: str, file: str) -> (str, str): + def __save_settings(self, path: str, file: str, index: int) -> (str, str): """ Save mesh settings """ try: - with open(f"{path}/{file}", "w", encoding="utf-8") as mesh_conf: + with open(f"{path}/{str(index)}_{file}", "w", encoding="utf-8") as mesh_conf: # not currently in json mesh_settings mesh_conf.write(f"ROLE={quote(self.role)}\n") mesh_conf.write(f"MSVERSION={quote(self.msversion)}\n") - for index in self.radio_index: - mesh_conf.write(f"id{index}_MODE={quote(self.mode[index])}\n") - mesh_conf.write(f"id{index}_IP={quote(self.ip_address[index])}\n") - mesh_conf.write(f"id{index}_MASK={quote(self.subnet[index])}\n") - mesh_conf.write(f"id{index}_MAC={quote(self.ap_mac[index])}\n") - mesh_conf.write(f"id{index}_KEY={quote(self.key[index])}\n") - mesh_conf.write(f"id{index}_ESSID={quote(self.ssid[index])}\n") - mesh_conf.write(f"id{index}_FREQ={quote(self.frequency[index])}\n") - mesh_conf.write(f"id{index}_FREQ_MCC={quote(self.frequency_mcc[index])}\n") - mesh_conf.write(f"id{index}_TXPOWER={quote(self.tx_power[index])}\n") - mesh_conf.write(f"id{index}_COUNTRY={quote(self.country[index]).upper()}\n") - mesh_conf.write(f"id{index}_ROUTING={quote(self.routing[index])}\n") - mesh_conf.write(f"id{index}_PRIORITY={quote(self.priority[index])}\n") - mesh_conf.write(f"id{index}_MESH_VIF={quote(self.mesh_vif[index])}\n") - mesh_conf.write(f"id{index}_PHY={quote(self.phy[index])}\n") - mesh_conf.write(f"id{index}_BATMAN_IFACE={quote(self.batman_iface[index])}\n") - - mesh_conf.write("BRIDGE=") - for name in self.bridge: - mesh_conf.write(f"{name},") - mesh_conf.write("\n") + mesh_conf.write(f"id{str(index)}_MODE={quote(self.mode[index])}\n") + mesh_conf.write(f"id{str(index)}_IP={quote(self.ip_address[index])}\n") + mesh_conf.write(f"id{str(index)}_MASK={quote(self.subnet[index])}\n") + mesh_conf.write(f"id{str(index)}_MAC={quote(self.ap_mac[index])}\n") + mesh_conf.write(f"id{str(index)}_KEY={quote(self.key[index])}\n") + mesh_conf.write(f"id{str(index)}_ESSID={quote(self.ssid[index])}\n") + mesh_conf.write(f"id{str(index)}_FREQ={quote(self.frequency[index])}\n") + mesh_conf.write(f"id{str(index)}_FREQ_MCC={quote(self.frequency_mcc[index])}\n") + mesh_conf.write(f"id{str(index)}_TXPOWER={quote(self.tx_power[index])}\n") + mesh_conf.write(f"id{str(index)}_COUNTRY={quote(self.country[index]).upper()}\n") + mesh_conf.write(f"id{str(index)}_ROUTING={quote(self.routing[index])}\n") + mesh_conf.write(f"id{str(index)}_PRIORITY={quote(self.priority[index])}\n") + mesh_conf.write(f"id{str(index)}_MESH_VIF={quote(self.mesh_vif[index])}\n") + mesh_conf.write(f"id{str(index)}_PHY={quote(self.phy[index])}\n") + mesh_conf.write(f"id{str(index)}_BATMAN_IFACE={quote(self.batman_iface[index])}\n") + mesh_conf.write(f"BRIDGE=\"{self.bridge}\"\n") + except: - self.comms_status.mesh_cfg_status = \ + self.comms_status[index].mesh_cfg_status = \ comms.STATUS.mesh_cfg_not_stored - self.logger.error("not able to write new %s", file) + self.logger.error("not able to write new %s", f"{str(index)}_{file}") return "FAIL", "not able to write new mesh.conf" - self.comms_status.mesh_cfg_status = comms.STATUS.mesh_cfg_stored - self.logger.debug("%s written", file) + self.comms_status[index].mesh_cfg_status = comms.STATUS.mesh_cfg_stored + self.logger.debug("%s written", f"{str(index)}_{file}") return "OK", "Mesh configuration stored" def __read_configs(self, mesh_conf_lines): diff --git a/modules/utils/docker/entrypoint_nats.sh b/modules/utils/docker/entrypoint_nats.sh index 30aa40f85..90853a105 100755 --- a/modules/utils/docker/entrypoint_nats.sh +++ b/modules/utils/docker/entrypoint_nats.sh @@ -23,6 +23,13 @@ else echo "starting 11s mesh service" /opt/S9011sNatsMesh start id0 + if [ -f "opt/1_mesh.conf" ]; then + /opt/S9011sNatsMesh start id1 + fi + if [ -f "opt/2_mesh.conf" ]; then + /opt/S9011sNatsMesh start id2 + fi + echo "starting AP service" /opt/S90APoint start id0 @@ -50,7 +57,6 @@ else echo "starting radvd & socat" radvd -C /etc/radvd.conf # TODO: for some reason init.d is not working - /opt/S90socat start # socat is used to provide IPv6 NATS IF echo "starting comms services" /opt/S90comms_controller start