From ca2fc9675046d5a3c9a6d187f63d2d2187f938dc Mon Sep 17 00:00:00 2001 From: Mika Joenpera Date: Wed, 11 Oct 2023 15:37:32 +0300 Subject: [PATCH] Add wpa/hostapad status handling based in index - comms_status.py - status handling rework - comms_nats_controller.py - refactoring status instance handling - comms_settings.py - save/load fixes Jira-Id: SCDI-43 Signed-off-by: Mika Joenpera --- common/scripts/mesh-11s_nats.sh | 3 - .../src/nats/comms_nats_controller.py | 40 +++--- .../src/nats/src/comms_settings.py | 118 ++++++++++-------- .../src/nats/src/comms_status.py | 39 ++++-- modules/utils/docker/entrypoint_nats.sh | 2 +- 5 files changed, 109 insertions(+), 93 deletions(-) diff --git a/common/scripts/mesh-11s_nats.sh b/common/scripts/mesh-11s_nats.sh index c0a8e3bdb..3696c5968 100755 --- a/common/scripts/mesh-11s_nats.sh +++ b/common/scripts/mesh-11s_nats.sh @@ -536,9 +536,6 @@ main () { _mesh_vif="${INDEX}_MESH_VIF" wifidev="${!_mesh_vif}" - #_phy="${INDEX}_PHY" - #phyname="${!_phy}" - find_phy_interface "$wifidev" phyname=$retval_phy 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 61acd4daa..3ae5e36b9 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 @@ -93,11 +93,15 @@ def __init__(self, server: str, port: str, interval: int = 1000): 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"))) + for i in range(0, 3): + self.comms_status.append(comms_status.CommsStatus(self.main_logger.getChild(f"status {str(i)}"), i)) + self.settings = comms_settings.CommsSettings(self.comms_status, self.main_logger.getChild("settings")) + + for cstat in self.comms_status: + cstat.settings(self.settings) + self.command = comms_command.Command(server, port, self.comms_status, self.main_logger.getChild("command")) self.telemetry = MeshTelemetry(self.interval, self.main_logger.getChild("telemetry")) @@ -215,29 +219,15 @@ async def message_handler(message): await handle_settings_csa_post(ret) else: # Update status info - # TODO how many radios? - cc.comms_status[0].refresh_status() - cc.comms_status[1].refresh_status() - cc.comms_status[2].refresh_status() + _ = [item.refresh_status() for item in cc.comms_status] + response = {'status': ret, 'info': info, - '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] + 'mesh_status': [item.mesh_status for item in cc.comms_status], + 'mesh_cfg_status': [item.mesh_cfg_status for item in cc.comms_status], + 'visualisation_active': [item.is_visualisation_active for item in cc.comms_status], + 'mesh_radio_on': [item.is_mesh_radio_on for item in cc.comms_status], + 'ap_radio_on': [item.is_ap_radio_on for item in cc.comms_status], + 'security_status': [item.security_status for item in cc.comms_status] } if resp != "": 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 190a8b6c8..9f288b79f 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 @@ -5,6 +5,7 @@ from shlex import quote import logging import re +import os try: import comms_common as comms @@ -48,6 +49,8 @@ def __init__(self, comms_status: [cs.CommsStatus, ...], logger): self.csa_state: int = 0 # 0: not started, 1: stored, 2: triggered self.csa_count: int = 0 # number of CSA triggered self.device_amount: str = "0" # number of devices to trigger CSA from nats message + ret, info = self.__load_settings() + self.logger.debug("load settings: %s, %s", ret, info) def validate_mesh_settings(self, index: int) -> (str, str): """ @@ -156,7 +159,7 @@ def handle_mesh_settings_csa(self, msg: str, path="/opt", comms.STATUS.mesh_cfg_not_stored self.logger.error("save settings failed: %s, %s", ret, info) else: - ret, info = self.__save_settings(path, file) + ret, info = self.__save_settings(path, file, index) self.logger.debug("save settings: %s, %s", ret, info) self.csa_state = 1 self.csa_count = 0 @@ -179,7 +182,6 @@ def handle_mesh_settings(self, msg: str, path="/opt", """ try: parameters_set = json.loads(msg) - print(parameters_set) self.msversion = "nats" self.api_version = int(parameters_set["api_version"]) self.role = quote(str(parameters_set["role"])) @@ -253,7 +255,7 @@ def __save_settings(self, path: str, file: str, index: int) -> (str, str): 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") + mesh_conf.write(f"BRIDGE={self.bridge}\n") except: self.comms_status[index].mesh_cfg_status = \ @@ -265,46 +267,56 @@ def __save_settings(self, path: str, file: str, index: int) -> (str, str): self.logger.debug("%s written", f"{str(index)}_{file}") return "OK", "Mesh configuration stored" - def __read_configs(self, mesh_conf_lines): + def __read_configs(self, mesh_conf_lines) -> None: pattern: str = r'(\w+)=(.*?)(?:\s*#.*)?$' # Find all key-value pairs in the text matches = re.findall(pattern, mesh_conf_lines, re.MULTILINE) for match in matches: - print(f"{match[0]}={match[1]}") - if match[0] == "MODE": - self.mode = match[1] - elif match[0] == "IP": - self.ip_address = match[1] - elif match[0] == "MASK": - self.subnet = match[1] - elif match[0] == "MAC": - self.ap_mac = match[1] - elif match[0] == "KEY": - self.key = match[1] - elif match[0] == "ESSID": - self.ssid = match[1] - elif match[0] == "FREQ": - self.frequency = match[1] - elif match[0] == "FREQ_MCC": - self.frequency_mcc = match[1] - elif match[0] == "TXPOWER": - self.tx_power = match[1] - elif match[0] == "COUNTRY": - self.country = match[1] - elif match[0] == "ROUTING": - self.routing = match[1] - elif match[0] == "ROLE": - self.role = match[1] - elif match[0] == "PRIORITY": - self.priority = match[1] - elif match[0] == "MESH_VIF": - self.mesh_vif = match[1] - elif match[0] == "PHY": - self.phy = match[1] - elif match[0] == "MSVERSION": - self.msversion = match[1] - else: - self.logger.debug("unknown config parameter: %s", match[0]) + if "id" in match[0]: + index = int(match[0].split("_")[0].replace("id", "")) + name_parts = match[0].split("_")[1:] + name = "_".join(name_parts) + if name == "MODE": + self.mode.append(match[1]) + elif name == "IP": + self.ip_address.append(match[1]) + elif name == "MASK": + self.subnet.append(match[1]) + elif name == "MAC": + self.ap_mac.append(match[1]) + elif name == "KEY": + self.key.append(match[1]) + elif name == "ESSID": + self.ssid.append(match[1]) + elif name == "FREQ": + self.frequency.append(match[1]) + elif name == "FREQ_MCC": + self.frequency_mcc.append(match[1]) + elif name == "TXPOWER": + self.tx_power.append(match[1]) + elif name == "COUNTRY": + self.country.append(match[1]) + elif name == "ROUTING": + self.routing.append(match[1]) + elif name == "PRIORITY": + self.priority.append(match[1]) + elif name == "MESH_VIF": + self.mesh_vif.append(match[1]) + elif name == "PHY": + self.phy.append(match[1]) + elif name == "BATMAN_IFACE": + self.batman_iface.append(match[1]) + else: + self.logger.error("unknown config parameter: %s", name) + else: # global config without index + if match[0] == "MSVERSION": + self.msversion = match[1] + elif match[0] == "BRIDGE": + self.bridge = match[1] + elif match[0] == "ROLE": + self.role = match[1] + else: + pass #self.logger.error("unknown config parameter: %s", match[0]) def __load_settings(self) -> (str, str): """ @@ -312,18 +324,22 @@ def __load_settings(self) -> (str, str): return: OK, FAIL """ config_file_path: str = "/opt/mesh_default.conf" - mission_config_file_path: str = "/opt/mesh.conf" try: - with open(mission_config_file_path, "r", encoding="utf-8") as mesh_conf: - mesh_conf_lines = mesh_conf.read() - self.__read_configs(mesh_conf_lines) - except FileNotFoundError: - try: - with open(config_file_path, "r", encoding="utf-8") as mesh_conf: - mesh_conf_lines = mesh_conf.read() - self.__read_configs(mesh_conf_lines) - except FileNotFoundError: - self.logger.error("not able to read mesh config files") - return "FAIL", "not able to read mesh config files" + for index in range(0, len(self.comms_status)): + file = f"/opt/{str(index)}_mesh.conf" + if os.path.exists(file): + self.logger.debug("mesh config file %s found", file) + with open(file, "r", encoding="utf-8") as mesh_conf: + mesh_conf_lines = mesh_conf.read() + self.__read_configs(mesh_conf_lines) + else: + self.logger.debug("mesh config file %s not found, loading default", file) + if index == 0: + with open(config_file_path, "r", encoding="utf-8") as mesh_conf: + mesh_conf_lines = mesh_conf.read() + self.__read_configs(mesh_conf_lines) + except: + self.logger.error("not able to read mesh config files") + return "FAIL", "not able to read mesh config files" return "OK", "Mesh configuration loaded" diff --git a/modules/sc-mesh-secure-deployment/src/nats/src/comms_status.py b/modules/sc-mesh-secure-deployment/src/nats/src/comms_status.py index ea4b86680..fcfad0030 100644 --- a/modules/sc-mesh-secure-deployment/src/nats/src/comms_status.py +++ b/modules/sc-mesh-secure-deployment/src/nats/src/comms_status.py @@ -6,17 +6,19 @@ import copy import hashlib import os +import logging from .comms_common import STATUS - -class CommsStatus: # pylint: disable=too-many-instance-attributes +class CommsStatus: # pylint: disable=too-many-instance-attributes """ Maintains mesh and radio statuses """ + class WpaStatus: """ Maintains wpa_supplicant status """ + def __init__(self): self.interface: str = "" self.bssid: str = "" @@ -58,7 +60,7 @@ def __eq__(self, other): self.pairwise_cipher == other.pairwise_cipher and \ self.group_cipher == other.group_cipher and \ self.key_mgmt == other.key_mgmt and \ - self.wpa_state == other.wpa_state and\ + self.wpa_state == other.wpa_state and \ self.address == other.address and \ self.uuid == other.uuid return False @@ -67,6 +69,7 @@ class HostapdStatus: """ Maintains hostapd status """ + def __init__(self): self.interface: str = "" self.state: str = "DISABLED" @@ -99,8 +102,8 @@ def __eq__(self, other): self.ssid == other.ssid return False - def __init__(self, logger, index): - self.__index = index + def __init__(self, logger: logging, index: int): + self.__index: int = index self.__lock = threading.Lock() self.__thread_running: bool = False self.__logger = logger @@ -116,7 +119,14 @@ def __init__(self, logger, index): self.__is_visualisation_active: bool = False self.__is_hash_file: bool = False self.__is_ap_radio_on: bool = False + self.__settings = None # Refresh status + + def settings(self, value): + """ + Set mesh settings + """ + self.__settings = value self.__update_status() @property @@ -210,7 +220,7 @@ def refresh_status(self): return "OK", "Current status read" - def __update_status(self): # pylint: disable=too-many-branches + def __update_status(self): # pylint: disable=too-many-branches """ Get mesh configuration status and wpa_supplicant status and update internal states accordingly. @@ -287,14 +297,17 @@ def __update_status(self): # pylint: disable=too-many-branches # Unlock thread self.__lock.release() - @staticmethod - def __get_wpa_supplicant_pid() -> str: + def __get_wpa_supplicant_pid(self) -> str: """ Get wpa_supplicant process ID. """ # Run commands in pieces ps_command = ["ps", "ax"] - grep_command = ["grep", "-E", r"wpa_supplicant\-11s"] + try: + grep_command = ["grep", "-E", + f"[w]pa_supplicant-11s_id{str(self.__index)}_{self.__settings.mesh_vif[self.__index]}.conf"] + except IndexError: + return "" awk_command = ["awk", '{print $1}'] ps_process = subprocess.Popen(ps_command, stdout=subprocess.PIPE) @@ -313,7 +326,7 @@ def __get_wpa_cli_status(self): """ Get wpa_supplicant states via wpa_cli tool. """ - wpa_cli_command = ["wpa_cli", "status"] + wpa_cli_command = ["wpa_cli", "-i", f"{self.__settings.mesh_vif[self.__index]}" "status"] proc = subprocess.Popen(wpa_cli_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) output, error = proc.communicate() @@ -431,9 +444,9 @@ def __get_mission_cfg_status(self): Checks whether mission config file exists and does it match with hash file of previously applied settings. """ - config_file_path = "/opt/mesh.conf" - pending_config_file_path = "/opt/mesh_stored.conf" - hash_file_path = "/opt/mesh.conf_hash" + config_file_path = f"/opt/{str(self.__index)}_mesh.conf" + pending_config_file_path = f"/opt/{str(self.__index)}_mesh_stored.conf" + hash_file_path = f"/opt/{str(self.__index)}_mesh.conf_hash" old_mesh_cfg_status = self.__mesh_cfg_status old_is_mission_cfg = self.__is_mission_cfg try: diff --git a/modules/utils/docker/entrypoint_nats.sh b/modules/utils/docker/entrypoint_nats.sh index 90853a105..2debf5290 100755 --- a/modules/utils/docker/entrypoint_nats.sh +++ b/modules/utils/docker/entrypoint_nats.sh @@ -3,7 +3,7 @@ source /opt/mesh-helper.sh # sources mesh configuration and sets start_opts -source_configuration +source_configuration "id0" if [ "$MSVERSION" != "nats" ]; then if [ -f "/usr/local/bin/entrypoint.sh" ]; then