diff --git a/common/scripts/mesh-11s-mr.sh b/common/scripts/mesh-11s-mr.sh deleted file mode 100755 index 8bd1d9f9e..000000000 --- a/common/scripts/mesh-11s-mr.sh +++ /dev/null @@ -1,347 +0,0 @@ -#!/bin/bash - -function help -{ - echo - cat << EOF -Usage: sudo ./mesh_11s-mr.sh [] []" -Parameters: - - - - - - - - - - - optional - - optional - -example: - For One Radio: - sudo ./mesh-11s-mr.sh mesh 192.168.1.2 255.255.255.0 00:11:22:33:44:55 1234567890 mymesh2 5220 30 fi wlan1 phy1 - Automatic detection: - sudo ./mesh-11s-mr.sh mesh 192.168.1.2 255.255.255.0 00:11:22:33:44:55 1234567890 mymesh2 5220 30 fi - Create simple AP for debug purposes: - sudo ./mesh-11s-mr.sh ap - Turn all radios off: - sudo ./mesh-11s-mr.sh off -EOF - exit -} - -find_mesh_wifi_device() -{ - # arguments: - # $1 = bus (usb, pci, sdio..) - # $2 = wifi device vendor - # $3 = wifi device id list - - # return values: device_list - # format example = "phy0,wlp1s0 phy1,wlp2s0 phy2,wlp3s0" - - device_list="" - - echo "## Searching WIFI card: bus=$1 deviceVendor=$2 deviceID=$3" - - case "$1" in - pci) - # tested only with Qualcomm cards - phynames=$(ls /sys/class/ieee80211/) - for device in $3; do - for phy in $phynames; do - device_id="$(cat /sys/bus/pci/devices/*/ieee80211/"$phy"/device/device 2>/dev/null)" - device_vendor="$(cat /sys/bus/pci/devices/*/ieee80211/"$phy"/device/vendor 2>/dev/null)" - if [ "$device_id" = "$device" ] && [ "$device_vendor" = "$2" ]; then - retval_phy=$phy - retval_name=$(ls /sys/class/ieee80211/"$phy"/device/net/) - # add "phy,name" pair to device_list (space as separator for pairs) - device_list="$device_list$retval_phy,$retval_name " - fi - done - done - ;; - usb) - device_list="" - ;; - sdio) - device_list="" - ;; - esac -} - -create_meshpoint() -{ - # parameters: - # 1 2 3 4 5 6 7 8 9 10 11 - # - - echo "create_meshpoint $1 $2 $3 $4 $5 $6 $7 $8 $9 ${10} ${11}" - if [[ -z "$1" || -z "$2" || -z "$3" || -z "$4" || -z "$5" || -z "$6" ]] - then - echo "check arguments..." - help - fi - -cat </var/run/wpa_supplicant-11s_"${10}".conf -ctrl_interface=DIR=/var/run/wpa_supplicant -# use 'ap_scan=2' on all devices connected to the network -# this is unnecessary if you only want the network to be created when no other networks.. -ap_scan=1 -country=$9 -p2p_disabled=1 -#autoscan=periodic:10 -#bgscan="simple:30:-45:300" -network={ - ssid="$6" - bssid=$4 - mode=5 - frequency=$7 - psk="$5" - key_mgmt=SAE - mesh_fwding=0 - ieee80211w=2 -} -EOF - - echo "Killing wpa_supplicant for ${10}..." - pkill -f "/var/run/wpa_supplicant-11s_${10}" 2>/dev/null - rm -fr /var/run/wpa_supplicant/"${10}" - - - killall alfred 2>/dev/null - killall batadv-vis 2>/dev/null - rm -f /var/run/alfred.sock - - modprobe batman-adv - - echo "$wifidev down.." - iw dev "${10}" del - iw phy "${11}" interface add "${10}" type mp - - echo "${10} create 11s.." - ifconfig "${10}" mtu 1560 - - echo "${10} up.." - ip link set "${10}" up - batctl if add "${10}" - - echo "bat0 up.." - ifconfig bat0 up - echo "bat0 ip address.." - ifconfig bat0 "$2" netmask "$3" - echo - ifconfig bat0 - - sleep 3 - - # for visualisation - (alfred -i bat0 -m)& - echo "started alfred" - (batadv-vis -i bat0 -s)& - echo "started batadv-vis" - - wpa_supplicant -i "${10}" -c /var/run/wpa_supplicant-11s_"${10}".conf -D nl80211 -C /var/run/wpa_supplicant/ -B -} - -create_ap() -{ - # parameters: - # 1 2 3 4 5 6 7 8 9 10 11 - # - - if [[ -z "$1" ]] - then - echo "check arguments..." - help - fi - - # find ap parameters from enclave - if [ -z "$DRONE_DEVICE_ID" ] - then - echo "DRONE_DEVICE_ID not available" - -cat </var/run/wpa_supplicant-ap_"${10}".conf -ctrl_interface=DIR=/var/run/wpa_supplicant -ap_scan=1 -p2p_disabled=1 -network={ - ssid="debug-hotspot" - mode=2 - frequency=5220 - key_mgmt=WPA-PSK - psk="1234567890" - pairwise=CCMP - group=CCMP - proto=RSN -} -EOF - else - echo "DRONE_DEVICE_ID available" - -cat </var/run/wpa_supplicant-ap_"${10}".conf -ctrl_interface=DIR=/var/run/wpa_supplicant -ap_scan=1 -p2p_disabled=1 -network={ - ssid="$DRONE_DEVICE_ID" - mode=2 - frequency=5220 - key_mgmt=WPA-PSK - psk="1234567890" - pairwise=CCMP - group=CCMP - proto=RSN -} -EOF - - fi - - echo "Killing wpa_supplicant ${10}..." - pkill -f "/var/run/wpa_supplicant-*_${10}" 2>/dev/null - rm -fr /var/run/wpa_supplicant/"${10}" - - echo "create $wifidev" - iw dev "${10}" del - iw phy "${11}" interface add "${10}" type managed - - echo "$wifidev up.." - ip link set "${10}" up - batctl if add "${10}" - - echo "set ip address.." - # set AP ipaddr - ifconfig "${10}" 192.168.1.1 netmask 255.255.255.0 - - # set bat0 ipaddr - if [ -z "$DRONE_DEVICE_ID" ] - then - # DRONE_DEVICE_ID not available set default - ifconfig bat0 192.168.1.1 netmask 255.255.255.0 - else - declare -i ip - ip=10#$(echo "$DRONE_DEVICE_ID" | tr -d -c 0-9) - ifconfig bat0 192.168.1."$ip" netmask 255.255.255.0 - fi - - echo "bat0 up.." - ifconfig bat0 up - echo - ifconfig bat0 - - route del -net 192.168.1.0 netmask 255.255.255.0 dev bat0 - route add -net 192.168.1.0 netmask 255.255.255.0 dev bat0 metric 1 - - # TODO DHCP server? - # dhserver - - wpa_supplicant -B -i "${10}" -c /var/run/wpa_supplicant-ap_"${10}".conf -D nl80211 -C /var/run/wpa_supplicant/ -} - -off() -{ - # parameters: - # 1 2 3 4 5 6 7 8 9 10 11 - # - - # kills all mesh script started wpa_supplicant processes (ap/mesh) - pkill -f "/var/run/wpa_supplicant-" 2>/dev/null - rm -fr /var/run/wpa_supplicant/"${10}" - killall alfred 2>/dev/null - killall batadv-vis 2>/dev/null - rm -f /var/run/alfred.sock 2>/dev/null -} - -init_device() -{ - # parameters: - # 1 2 3 4 5 6 7 8 9 10 11 - # - - echo "- init_device $1" - echo - - case "$1" in - mesh) - create_meshpoint "$@" - ;; - ap) - create_ap "$@" - ;; - off) - # stop all processes which were started - off "$@" - ;; - *) - help - ;; - esac -} - -### MAIN ### -main () -{ - # parameters - # 1 2 3 4 5 6 7 8 9 10 11 - # - - if [[ -z "$1" ]]; then - help - exit - fi - - echo "Solving wifi device name and phy name.." - echo - #---------------- - - count=0 - - rfkill unblock all - - if [[ -z "${10}" ]]; then - # multiple wifi options --> can be detected as follows: - # manufacturer 0x168c = Qualcomm - # devices = 0x0034 0x003c 9462/988x 11s - # 0x003e 6174 adhoc - list="0x0034 0x003c 0x003e" - for dev in $list; do - find_mesh_wifi_device "pci" 0x168c "$dev" - - if [ "$device_list" != "" ]; then - for pair in $device_list; do - phyname=$(echo "$pair" | cut -f1 -d ',') - wifidev=$(echo "$pair" | cut -f2 -d ',') - - echo " --------------------------------------------------------" - - count=$((count+1)) - echo "- #$count Found: $wifidev $phyname" - - if [ "$count" -gt 1 ]; then - # ACS - Automatic Channel Selection, not yet working for wpa/ap - # TODO Hardcoded frequency used - init_device "$1" "$2" "$3" "$4" "$5" "$6" "2412" "$8" "$9" "$wifidev" "$phyname" - else - init_device "$1" "$2" "$3" "$4" "$5" "$6" "$7" "$8" "$9" "$wifidev" "$phyname" - fi - - if [ "$1" = "ap" ]; then - # only one AP is initialised - exit 0 - fi - done - else - echo "- Not Found: 0x168c $dev - (not installed)" - fi - echo - done - else - wifidev=${10} - phyname=${11} - init_device "$1" "$2" "$3" "$4" "$5" "$6" "$7" "$8" "$9" "${10}" "${11}" - fi -} - -main "$@" - diff --git a/common/scripts/mesh-11s_nats.sh b/common/scripts/mesh-11s_nats.sh index 860489541..2ccbae5b5 100755 --- a/common/scripts/mesh-11s_nats.sh +++ b/common/scripts/mesh-11s_nats.sh @@ -20,22 +20,82 @@ wait_for_intf() { done } -calculate_wifi_channel() { - # arguments: - # $1 = wifi frequency - # return values: retval_band, retval_channel as global - # Set 2.4/5GHz frequency band and channel +mesh_kill() { + # $1 is process string to kill - if [ "$1" -ge "5160" ] && [ "$1" -le "5885" ]; then - retval_band="a" - retval_channel=$((("$1"-5000)/5)) - elif [ "$1" -ge "2412" ] && [ "$1" -le "2472" ]; then - retval_band="g" - retval_channel=$((("$1"-2407)/5)) + # shellcheck disable=SC2009 + # pgrep is not available + pid=$(ps aux | grep "$1" | awk '{print $1}') + + # check pid is numeric value + if [ "$pid" -eq "$pid" ] 2>/dev/null; then + echo "killing $pid" + kill -9 "$pid" + fi +} + +add_network_intf_to_bridge() { + _bridge_name=$1 + _interfaces=$2 + + _lan1=0 + if [[ "$_interfaces" == *"lan1"* ]]; then + _lan1=1 + fi + # Loop through the interface names and add them to the bridge if available + for _interface in $_interfaces; do + # Check if the interface exists + if ip link show dev "$_interface" > /dev/null 2>&1; then + echo "Adding $_interface to $_bridge_name" + brctl addif "$_bridge_name" "$_interface" 2>/dev/null else - echo "ERROR! frequency out of range!" - exit 1 + echo "Interface $_interface not found. Skipping." + fi + done + + if [ "$_lan1" -eq 1 ]; then + # Add lan1 to bridge + brctl delif "$_bridge_name" eth0 2>/dev/null + fi +} + +fix_iface_mac_addresses() { + # handles the case when the mac address of the batman interface the same as the eth0. + # For batman interface, the mac address is set to 00:0Y:XX:XX:XX:XX by utilizing eth0 mac address. + # from 00:0Y:XX:XX:XX:XX Y is the batman interface index + + # batman interface is bat0, take the last char for the numbering + batman_iface_index="$(echo "$batman_iface" | cut -b 4)" + + #Create static mac addr for Batman if + eth0_mac="$(ip -brief link | grep eth0 | awk '{print $3; exit}')" + batif_mac="00:0${batman_iface_index}:$(echo "$eth0_mac" | cut -b 7-17)" + ifconfig "$batman_iface" down + ifconfig "$batman_iface" hw ether "$batif_mac" + ifconfig "$batman_iface" up + ifconfig "$bridge_name" down + ifconfig "$bridge_name" hw ether "$eth0_mac" + ifconfig "$bridge_name" up +} + +calculate_network_address() { + # with ip 192.168.1.2 and mask 255.255.255.0 --> network=192.168.1.0 + _IP_ADDRESS=$1 + _NETMASK=$2 + + # Split the IP and MASK into octets + IFS=. read -r -a ip_octets <<< "$_IP_ADDRESS" + IFS=. read -r -a mask_octets <<< "$_NETMASK" + + # Calculate the network address + network="" + for ((i=0; i<4; i++)); do + network_octet=$(( 10#${ip_octets[i]} & 10#${mask_octets[i]} )) + network="${network}${network_octet}" + if [ "$i" -lt 3 ]; then + network="${network}." fi + done } find_ethernet_port() { @@ -59,14 +119,179 @@ find_ethernet_port() { fi } +calculate_wifi_channel() { + # arguments: + # $1 = wifi frequency + # return values: retval_band, retval_channel as global + # Set 2.4/5GHz frequency band and channel + + if [ "$1" -ge "5160" ] && [ "$1" -le "5885" ]; then + retval_band="a" + retval_channel=$((("$1"-5000)/5)) + elif [ "$1" -ge "2412" ] && [ "$1" -le "2472" ]; then + retval_band="g" + retval_channel=$((("$1"-2407)/5)) + else + echo "ERROR! frequency out of range!" + exit 1 + fi +} + +find_phy_interface() { + # Argument: + # $1 = Wi-Fi interface name + + # Return value: retval_phy as global + + echo "Find PHY interface for Wi-Fi interface: $1" + echo + phy_names=$(ls /sys/class/ieee80211/) + + for phy in $phy_names; do + if [ -d "/sys/class/ieee80211/$phy/device/net/$1" ]; then + retval_phy=$phy + break + else + retval_phy="" + fi + done +} + mode_execute() { # parameters: # $1 = mode case "$mode" in + "halow") + MAX_RETRIES=5 + RETRY_COUNT=0 + cat </var/run/wpa_supplicant-11s_"$INDEX".conf +ctrl_interface=DIR=/var/run/wpa_supplicant_"$INDEX" +ap_scan=1 +country=$cc +mesh_max_inactivity=50 +p2p_disabled=1 +network={ + ssid="$ssid" + bssid=$mac + mode=5 + ieee80211w=2 + frequency=$freq + key_mgmt=SAE + psk="$psk" + mesh_fwding=0 +} +EOF + + if [ "$routing_algo" == "batman-adv" ]; then + echo "batman-adv" + elif [ "$routing_algo" == "olsr" ]; then + mesh_kill "[o]lsrd -i $wifidev -d 0" + fi + + modprobe mac80211 + + # Radio spi bus number + BUSNO=$(for i in /sys/class/spi_master/*/; do + if [ -e "$i/device" ] && [ "$(basename "$(readlink "$i/device")")" == "spi-ft232h.0" ]; then + echo "${i//[^0-9]/}" + fi + done) + + # remove nrc module before init + rmmod nrc.ko + + # set initial country code + logger "country code set($wifidev): $cc" + iw reg set "$cc" + + # Check the return code of halow bring up + while [ $RETRY_COUNT -lt $MAX_RETRIES ]; do + logger "nrc module load retry count: $RETRY_COUNT" + insmod /lib/modules/"$KERNEL_VERSION"/kernel/drivers/staging/nrc/nrc.ko fw_name=nrc7292_cspi.bin bd_name=nrc7292_bd.dat spi_bus_num="$BUSNO" spi_polling_interval=3 hifspeed=20000000 spi_gpio_irq=-1 power_save=0 sw_enc=0 + sleep 2 + # Bring up halow interface + echo "$wifidev up.." + ifconfig "$wifidev" up + if [ $? -eq 255 ]; then + echo "Failed to init HaLow. (Attempt $((RETRY_COUNT + 1)))" + rmmod nrc.ko + RETRY_COUNT=$((RETRY_COUNT + 1)) + else + break + fi + done + + echo "$wifidev create 11s.." + ifconfig "$wifidev" mtu 1460 + + sleep 1 + + if [ "$routing_algo" == "batman-adv" ]; then + batctl if add "$wifidev" + echo "$batman_iface up.." + ifconfig "$batman_iface" up + echo "$batman_iface ip address.." + ifconfig "$batman_iface" "$ipaddr" netmask "$nmask" + echo "$batman_iface mtu size" + ifconfig "$batman_iface" mtu 1460 + echo + ifconfig "$batman_iface" + + elif [ "$routing_algo" == "olsr" ]; then + ifconfig "$wifidev" "$ipaddr" netmask "$nmask" + # Enable debug level as necessary + (olsrd -i "$wifidev" -d 0)& + fi + + add_network_intf_to_bridge "$bridge_name" "$bridge_interfaces" + ifconfig "$bridge_name" "$bridge_ip" netmask "$nmask" + ifconfig "$bridge_name" up + echo + ifconfig "$bridge_name" + fix_iface_mac_addresses + + if [ "$routing_algo" == "batman-adv" ]; then + sleep 3 + # for visualisation + if ps aux | grep -q "[b]atadv-vis -i $batman_iface -s"; then + echo "batadv-vis is already running." + else + (batadv-vis -i "$batman_iface" -s) & + echo "batadv-vis started." + fi + fi + + # Add forwarding rules from AP to "$batman_iface" interface + iptables -P FORWARD ACCEPT + route del -net "$network" gw 0.0.0.0 netmask "$nmask" dev "$bridge_name" + route add -net "$network" gw "$bridge_ip" netmask "$nmask" dev "$bridge_name" + iptables -A FORWARD --in-interface "$_mesh_vif" -j ACCEPT + iptables --table nat -A POSTROUTING --out-interface "$bridge_ip" -j MASQUERADE + + sleep 5 + + # Radio parameters + echo "set radio parameters" + # /usr/local/bin/cli_app set txpwr fixed 23 + + # Batman parameters + batctl "$wifidev" hop_penalty 30 + + echo "Longer range tweak.." + #if [ "$priority" == "long_range" ]; then + # /usr/local/bin/cli_app ... + #elif [ "$priority" == "low_latency" ]; then + # /usr/local/bin/cli_app ... + #else + # /usr/local/bin/cli_app ... + #fi + + wpa_supplicant -i "$wifidev" -c /var/run/wpa_supplicant-11s_"$INDEX".conf -D nl80211 -C /var/run/wpa_supplicant_"$INDEX"/ -f /tmp/wpa_supplicant_11s_"$INDEX".log + ;; "mesh") - cat </var/run/wpa_supplicant-11s_"$wifidev".conf -ctrl_interface=DIR=/var/run/wpa_supplicant + cat </var/run/wpa_supplicant-11s_"$INDEX".conf +ctrl_interface=DIR=/var/run/wpa_supplicant_$INDEX # use 'ap_scan=2' on all devices connected to the network # this is unnecessary if you only want the network to be created when no other networks.. ap_scan=1 @@ -88,19 +313,13 @@ network={ } EOF - echo "Killing wpa_supplicant..." - if [ "$routing_algo" == "batman-adv" ]; then - killall alfred 2>/dev/null - killall batadv-vis 2>/dev/null - rm -f /var/run/alfred.sock - - #Check if batman_adv is built-in module + echo "batman-adv" if [[ -d "/sys/module/batman_adv" ]]; then modprobe batman-adv fi elif [ "$routing_algo" == "olsr" ]; then - killall olsrd 2>/dev/null + mesh_kill "[o]lsrd -i $wifidev -d 0" fi echo "$wifidev down.." @@ -124,22 +343,15 @@ EOF if [ "$routing_algo" == "batman-adv" ]; then batctl if add "$wifidev" - echo "bat0 up.." - ifconfig bat0 up - echo "bat0 ip address.." - ifconfig bat0 "$ipaddr" netmask "$nmask" - echo "bat0 mtu size" - ifconfig bat0 mtu 1460 + echo "$batman_iface up.." + ifconfig "$batman_iface" up + echo "$batman_iface ip address.." + ifconfig "$batman_iface" "$ipaddr" netmask "$nmask" + echo "$batman_iface mtu size" + ifconfig "$batman_iface" mtu 1460 echo - ifconfig bat0 + ifconfig "$batman_iface" - sleep 3 - - # for visualisation - (alfred -i bat0 -m)& - echo "started alfred" - (batadv-vis -i bat0 -s)& - echo "started batadv-vis" elif [ "$routing_algo" == "olsr" ]; then ifconfig "$wifidev" "$ipaddr" netmask "$nmask" # Enable debug level as necessary @@ -149,22 +361,35 @@ EOF # Radio parameters iw dev "$wifidev" set txpower limit "$txpwr"00 - brctl addif br-lan bat0 $eth_port - ifconfig br-lan "$br_lan_ip" netmask "255.255.255.0" - ifconfig br-lan up + add_network_intf_to_bridge "$bridge_name" "$bridge_interfaces" + ifconfig "$bridge_name" "$bridge_ip" netmask "$nmask" + ifconfig "$bridge_name" up echo - ifconfig br-lan - # Add forwarding rules from AP to bat0 interface + ifconfig "$bridge_name" + fix_iface_mac_addresses + + if [ "$routing_algo" == "batman-adv" ]; then + sleep 3 + # for visualisation + if ps aux | grep -q "[b]atadv-vis -i $batman_iface -s"; then + echo "batadv-vis is already running." + else + (batadv-vis -i "$batman_iface" -s) & + echo "batadv-vis started." + fi + fi + + # Add forwarding rules from AP to "$batman_iface" interface iptables -P FORWARD ACCEPT - route del -net 192.168.1.0 gw 0.0.0.0 netmask 255.255.255.0 dev br-lan - route add -net 192.168.1.0 gw "$br_lan_ip" netmask 255.255.255.0 dev br-lan - iptables -A FORWARD --in-interface bat0 -j ACCEPT - iptables --table nat -A POSTROUTING --out-interface "$br_lan_ip" -j MASQUERADE + route del -net "$network" gw 0.0.0.0 netmask "$nmask" dev "$bridge_name" + route add -net "$network" gw "$bridge_ip" netmask "$nmask" dev "$bridge_name" + iptables -A FORWARD --in-interface "$batman_iface" -j ACCEPT + iptables --table nat -A POSTROUTING --out-interface "$bridge_ip" -j MASQUERADE - wpa_supplicant -i "$wifidev" -c /var/run/wpa_supplicant-11s_"$wifidev".conf -D nl80211 -C /var/run/wpa_supplicant/ -f /tmp/wpa_supplicant_11s.log + wpa_supplicant -i "$wifidev" -c /var/run/wpa_supplicant-11s_"$INDEX".conf -D nl80211 -C /var/run/wpa_supplicant_"$INDEX"/ -f /tmp/wpa_supplicant_11s_"$INDEX".log ;; "ap+mesh_mcc") - wait_for_intf "br-lan" + wait_for_intf "$bridge_name" ifname_ap="$(ifconfig -a | grep -m 1 "wlan*" | awk -F':' '{ print $1 }')" ap_if_mac="$(ip -brief link | grep "$ifname_ap" | awk '{print $3; exit}')" ssid="comms_sleeve#$(echo "$ap_if_mac" | cut -b 13-14,16-17)" @@ -173,7 +398,7 @@ EOF ifconfig "$ifname_ap" up # AP hostapd config - cat </var/run/hostapd-"$ifname_ap".conf + cat </var/run/hostapd-"$INDEX".conf country_code=$cc interface=$ifname_ap ssid=$ssid @@ -193,32 +418,34 @@ EOF # Bridge AP and Mesh if [ "$routing_algo" = "olsr" ]; then - brctl addif br-lan "$wifidev" "$ifname_ap" + brctl addif "$bridge_name" "$wifidev" "$ifname_ap" iptables -A FORWARD --in-interface "$wifidev" -j ACCEPT - killall olsrd 2>/dev/null - (olsrd -i br-lan -d 0)& + mesh_kill "[o]lsrd -i $bridge_name -d 0" + (olsrd -i "$bridge_name" -d 0)& else ##batman-adv### - ifconfig br-lan down - brctl addif br-lan "$ifname_ap" - ifconfig br-lan down - iptables -A FORWARD --in-interface bat0 -j ACCEPT + ifconfig "$bridge_name" down + add_network_intf_to_bridge "$bridge_name" "$ifname_ap" + ifconfig "$bridge_name" down + iptables -A FORWARD --in-interface "$batman_iface" -j ACCEPT fi - ifconfig br-lan "$br_lan_ip" netmask "255.255.255.0" - ifconfig br-lan up + ifconfig "$bridge_name" "$bridge_ip" netmask "$nmask" + ifconfig "$bridge_name" up echo - ifconfig br-lan - # Add forwading rules from AP to bat0 interface + ifconfig "$bridge_name" + # Create static mac addr for Batman if and br-lan + fix_iface_mac_addresses + # Add forwading rules from AP to "$batman_iface" interface iptables -P FORWARD ACCEPT - route del -net 192.168.1.0 gw 0.0.0.0 netmask 255.255.255.0 dev br-lan - route add -net 192.168.1.0 gw "$br_lan_ip" netmask 255.255.255.0 dev br-lan + route del -net "$network" gw 0.0.0.0 netmask "$nmask" dev "$bridge_name" + route add -net "$network" gw "$bridge_ip" netmask "$nmask" dev "$bridge_name" iptables --table nat -A POSTROUTING --out-interface "$ifname_ap" -j MASQUERADE # Start AP - /usr/sbin/hostapd -B /var/run/hostapd-"$ifname_ap".conf -f /tmp/hostapd.log + /usr/sbin/hostapd -B /var/run/hostapd-"$INDEX".conf -f /tmp/hostapd_"$INDEX".log ;; "ap+mesh_scc") - wait_for_intf "br-lan" + wait_for_intf "$bridge_name" # chanbw config mount -t debugfs none /sys/kernel/debug if [ -f "/sys/kernel/debug/ieee80211/phy0/ath9k/chanbw" ]; then @@ -228,10 +455,8 @@ EOF # RPi activity led config echo "phy0tx" > /sys/class/leds/led0/trigger - #Create static mac addr for Batman if - eth0_mac="$(ip -brief link | grep eth0 | awk '{print $3; exit}')" - batif_mac="00:00:$(echo "$eth0_mac" | cut -b 7-17)" - ifconfig bat0 hw ether "$batif_mac" + #Create static mac addr for Batman if and br-lan + fix_iface_mac_addresses # Radio parameters iw dev "$wifidev" set txpower limit "$txpwr"00 @@ -246,7 +471,7 @@ EOF calculate_wifi_channel "$freq" # AP hostapd config - cat </var/run/hostapd-"$ifname_ap".conf + cat </var/run/hostapd-"$INDEX".conf ctrl_interface=/var/run/hostapd interface=$ifname_ap hw_mode=$retval_band @@ -275,30 +500,17 @@ ieee80211n=1 #ieee80211ac=1 #vht_capab=[MAX-MPDU-11454][RXLDPC][SHORT-GI-80][TX-STBC-2BY1][RX-STBC-1] EOF - ifconfig br-lan down - brctl addif br-lan "$ifname_ap" - ifconfig br-lan "$ipaddr" netmask "$nmask" - ifconfig br-lan up + ifconfig "$bridge_name" down + add_network_intf_to_bridge "$bridge_name" "$ifname_ap" + ifconfig "$bridge_name" "$ipaddr" netmask "$nmask" + ifconfig "$bridge_name" up echo - ifconfig br-lan + ifconfig "$bridge_name" iptables -P FORWARD ACCEPT - ip addr flush dev bat0 + ip addr flush dev "$batman_iface" # Start AP - /usr/sbin/hostapd -B /var/run/hostapd-"$ifname_ap".conf -f /tmp/hostapd.log - ;; - off) - # service off - pkill -f "/var/run/wpa_supplicant-" 2>/dev/null - rm -fr /var/run/wpa_supplicant/"$wifidev" - killall hostapd 2>/dev/null - if [ "$routing_algo" == "batman-adv" ]; then - killall alfred 2>/dev/null - killall batadv-vis 2>/dev/null - rm -f /var/run/alfred.sock 2>/dev/null - elif [ "$routing_algo" == "olsr" ]; then - killall olsrd 2>/dev/null - fi + /usr/sbin/hostapd -B /var/run/hostapd-"$INDEX".conf -f /tmp/hostapd_"$INDEX".log ;; *) exit 1 @@ -308,55 +520,105 @@ EOF ### MAIN ###################################################################### main () { + # $1 = mode + # $2 = index number + source /opt/mesh-helper.sh + + echo "index=$2 mode=$1" + INDEX=$2 + # sources mesh configuration - source_configuration - - # Modes: mesh, ap+mesh_scc, ap+mesh_mcc - # MODE=mesh - # IP=10.20.15.3 - # MASK=255.255.255.0 - # MAC=00:11:22:33:44:55 - # KEY=1234567890 - # ESSID=gold - # FREQ=5805 - # FREQ_MCC=2412 - # TXPOWER=30 - # COUNTRY=AE - # MESH_VIF=wlp1s0 - # PHY=phy0 - # ROUTING=batman-adv - # ROLE=drone - # PRIORITY=long_range - # set bridge ip, sets br_lan_ip - generate_br_lan_ip - - # local find eth port + source_configuration "0" + source_configuration "${INDEX:2}" + + # linux kernel release + KERNEL_VERSION=$(uname -r) + + # Modes: mesh, ap+mesh_scc, ap+mesh_mcc, halow + # mesh 1.0 with NATS communication setup + # Modes: mesh, ap+mesh_scc, ap+mesh_mcc, halow + # example from mesh_default.conf: + # id0_MODE=mesh + # id0_IP=10.20.15.3 + # id0_MASK=255.255.255.0 + # id0_MAC=00:11:22:33:44:55 + # id0_KEY=1234567890 + # id0_ESSID=gold + # id0_FREQ=5805 + # id0_TXPOWER=30 + # id0_COUNTRY=AE + # id0_MESH_VIF=wlp1s0 + # id0_PHY=phy0 + # id0_BATMAN_IFACE=bat0 + # id0_FREQ_MCC=2412 + # id0_ROUTING=batman-adv + # id0_PRIORITY=long_range + # BRIDGE="br-mesh eth1 eth0 lan1" + # ROLE=drone + generate_lan_bridge_ip + # to get bridge_ip warning free + bridge_ip=$bridge_ip + find_ethernet_port - echo $eth_port + # to get eth_port warning free + eth_port=$eth_port - if [ "$1" == "mesh" ] || "$1" == "off"; then + # default mesh handling and power off radio + if [ "$1" == "mesh" ]; then mode=$1 else # shellcheck disable=SC2153 - mode="$MODE" + _mode="${INDEX}_MODE" + mode="${!_mode}" fi - ssid=$ESSID - ipaddr=$IP - nmask=$MASK - mac=$MAC - # shellcheck disable=SC2153 - freq=$FREQ + + _ssid="${INDEX}_ESSID" + ssid="${!_ssid}" + + _ipaddr="${INDEX}_IP" + ipaddr="${!_ipaddr}" + + _nmask="${INDEX}_MASK" + nmask="${!_nmask}" + + _mac="${INDEX}_MAC" + mac="${!_mac}" + + _freq="${INDEX}_FREQ" + freq="${!_freq}" + + _freq_mcc="${INDEX}_FREQ_MCC" + freq_mcc="${!_freq_mcc}" + + _cc="${INDEX}_COUNTRY" + cc="${!_cc}" + + _psk="${INDEX}_KEY" + psk="${!_psk}" + + _txpwr="${INDEX}_TXPOWER" + txpwr="${!_txpwr}" + + _algo="${INDEX}_ROUTING" + algo="${!_algo}" + + _mesh_vif="${INDEX}_MESH_VIF" + wifidev="${!_mesh_vif}" + + find_phy_interface "$wifidev" + phyname=$retval_phy + + _priority="${INDEX}_PRIORITY" + priority="${!_priority}" + + _batman_iface="${INDEX}_BATMAN_IFACE" + batman_iface="${!_batman_iface}" + # shellcheck disable=SC2153 - freq_mcc=$FREQ_MCC - cc=$COUNTRY - psk=$KEY - txpwr=$TXPOWER - algo=$ROUTING - wifidev=$MESH_VIF - phyname=$PHY - priority=$PRIORITY - role=$ROLE + # shellcheck disable=SC2034 + # this is for the future use + role=${ROLE} echo "Used: $wifidev $phyname" @@ -366,9 +628,20 @@ main () { routing_algo=$algo fi - brctl addbr br-lan 2>/dev/null + # e.g. BRIDGE=br-mesh eth0 eth1 lan1 + bridge_name=$(echo "$BRIDGE" | cut -d' ' -f1) + bridge_interfaces=$(echo "$BRIDGE" | cut -d' ' -f2-) + + if brctl show "$bridge_name" &>/dev/null; then + echo "Bridge $bridge_name already exists." + else + # Create the bridge if it doesn't exist + brctl addbr "$bridge_name" 2>/dev/null + echo "Bridge $bridge_name created." + fi + calculate_network_address "$bridge_ip" "$nmask" mode_execute "$mode" } -main "$@" \ No newline at end of file +main "$@" diff --git a/common/scripts/mesh-helper.sh b/common/scripts/mesh-helper.sh index 0e56fb5d4..ec1c49447 100644 --- a/common/scripts/mesh-helper.sh +++ b/common/scripts/mesh-helper.sh @@ -26,7 +26,7 @@ __get_cpu_serial_number() { # this function generates the identity id from the mac address and the cpu serial number generate_identity_id() { # Split the input into an array of interface names - IFS=' ' read -ra interface_array <<< "wlp1s0 eth0 wlan0 wlan1" + IFS=' ' read -ra interface_array <<< "wlp1s0 wlp2s0 wlp3s0 halow1 eth0 wlan0 wlan1" mac_address=$(__get_mac_address "${interface_array[@]}") cpu_serial_number=$(__get_cpu_serial_number) @@ -43,21 +43,35 @@ EOF fi } -generate_br_lan_ip() { +generate_lan_bridge_ip() { local mesh_if_mac - mesh_if_mac="$(ip -brief link | grep "$MESH_VIF" | awk '{print $3; exit}')" + + bridge_name=$(echo "$BRIDGE" | cut -d' ' -f1) + + mesh_if_mac=$(cat /sys/class/net/"$id0_MESH_VIF"/address) + if [ -z "$mesh_if_mac" ]; then + echo "generate_lan_bridge_ip: MAC not found for id0_MESH_VIF! Configuration error?" > /dev/kmsg + mesh_if_mac="$(cat /sys/class/net/eth0/address)" + fi local ip_random ip_random="$(echo "$mesh_if_mac" | cut -b 16-17)" - br_lan_ip="192.168.1."$((16#$ip_random)) + bridge_ip="192.168.1."$((16#$ip_random)) + + # legacy support + br_lan_ip=$bridge_ip } source_configuration() { - if [ -f "/opt/mesh.conf" ]; then + # $1: index of the mesh.conf file to be used + CONFIGURATION_INDEX=$1 + + if [ -f "/opt/${CONFIGURATION_INDEX}_mesh.conf" ]; then # Calculate hash for mesh.conf file - hash=$(sha256sum /opt/mesh.conf | awk '{print $1}') + hash=$(sha256sum /opt/"${CONFIGURATION_INDEX}"_mesh.conf | awk '{print $1}') # Compare calculated hash with the one created when setting has been applied - if diff <(printf %s "$hash") /opt/mesh.conf_hash; then - source /opt/mesh.conf + if diff <(printf %s "$hash") /opt/"${CONFIGURATION_INDEX}"_mesh.conf_hash; then + # shellcheck disable=SC1090 + source /opt/"${CONFIGURATION_INDEX}"_mesh.conf else # Revert to default mesh if [ -f "/opt/mesh_default.conf" ]; then 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..ea38f995a 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): @@ -39,10 +39,10 @@ def mesh_visual(self): :return: mesh visualisation """ - return f"[{self.batman_visual.latest_topology}," \ - f"{self.batman.latest_stat}]". \ - replace(": ", ":"). \ - replace(", ", ",") + return ( + f"[{self.batman_visual.latest_topology}," + f"{self.batman.latest_stat}]".replace(": ", ":").replace(", ", ",") + ) def run(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 @@ -85,31 +86,40 @@ def __init__(self, server: str, port: str, interval: int = 1000): self.main_logger = logging.getLogger("comms") self.main_logger.setLevel(logging.DEBUG) log_formatter = logging.Formatter( - fmt='%(asctime)s :: %(name)-18s :: %(levelname)-8s :: %(message)s') + fmt="%(asctime)s :: %(name)-18s :: %(levelname)-8s :: %(message)s" + ) console_handler = logging.StreamHandler() console_handler.setFormatter(log_formatter) self.main_logger.addHandler(console_handler) - self.comms_status = comms_status.CommsStatus(self.main_logger.getChild("status")) - self.settings = comms_settings.CommsSettings(self.comms_status, - self.main_logger.getChild("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")) + self.c_status = [] + # TODO how many radios? + for i in range(0, 3): + self.c_status.append( + comms_status.CommsStatus( + self.main_logger.getChild(f"status {str(i)}"), i + ) + ) + + self.settings = comms_settings.CommsSettings( + self.c_status, self.main_logger.getChild("settings") + ) + + for cstat in self.c_status: + if cstat.index < len(self.settings.mesh_vif): + cstat.wifi_interface = self.settings.mesh_vif[cstat.index] + + self.command = comms_command.Command( + server, port, self.c_status, self.main_logger.getChild("command") + ) + self.telemetry = MeshTelemetry( + self.interval, self.main_logger.getChild("telemetry") + ) # logger for this module and derived from main logger self.logger = self.main_logger.getChild("controller") -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 - - # pylint: disable=too-many-arguments, too-many-locals, too-many-statements async def main(server, port, keyfile=None, certfile=None, interval=1000): """ @@ -117,7 +127,6 @@ async def main(server, port, keyfile=None, certfile=None, interval=1000): """ cc = CommsController(server, port, interval) nats_client = NATS() - csac = CommsCsa() status, _, identity_dict = cc.command.get_identity() @@ -139,9 +148,10 @@ def signal_handler(): asyncio.create_task(nats_client.close()) asyncio.create_task(stop()) - for sig in ('SIGINT', 'SIGTERM'): - asyncio.get_running_loop().add_signal_handler(getattr(signal, sig), - signal_handler) + for sig in ("SIGINT", "SIGTERM"): + asyncio.get_running_loop().add_signal_handler( + getattr(signal, sig), signal_handler + ) async def disconnected_cb(): cc.logger.debug("Got disconnected...") @@ -157,34 +167,20 @@ async def reconnected_cb(): # Connect to NATS server with TLS enabled if ssl_context is provided if ssl_context: - await nats_client.connect(f"tls://{server}:{port}", - tls=ssl_context, - reconnected_cb=reconnected_cb, - disconnected_cb=disconnected_cb, - max_reconnect_attempts=-1) + await nats_client.connect( + f"tls://{server}:{port}", + tls=ssl_context, + reconnected_cb=reconnected_cb, + disconnected_cb=disconnected_cb, + max_reconnect_attempts=-1, + ) else: - await nats_client.connect(f"nats://{server}:{port}", - reconnected_cb=reconnected_cb, - disconnected_cb=disconnected_cb, - max_reconnect_attempts=-1) - - async def handle_settings_csa_post(ret): - if ret == "OK": - ret = "ACK" - elif ret == "TRIGGER": - cmd = json.dumps({"api_version": 1, "cmd": "APPLY"}) - cc.command.handle_command(cmd, cc, True, csac.delay) - elif ret == "COUNT": - ret = "COUNT" # not to send ACK/NACK - else: - ret = "NACK" - - if ret in ("ACK", "NACK") and csac.ack_sent is False: - response = {'status': ret} - cc.logger.debug("publish response: %s", str(response)) - await nats_client.publish("comms.settings_csa", - json.dumps(response).encode("utf-8")) - csac.ack_sent = True + await nats_client.connect( + f"nats://{server}:{port}", + reconnected_cb=reconnected_cb, + disconnected_cb=disconnected_cb, + max_reconnect_attempts=-1, + ) async def message_handler(message): # reply = message.reply @@ -195,37 +191,42 @@ async def message_handler(message): if subject == f"comms.settings.{identity}": ret, info = cc.settings.handle_mesh_settings(data) - elif subject == "comms.settings_csa": - ret, info, delay = cc.settings.handle_mesh_settings_csa(data) - csac.delay = delay - csac.ack_sent = "status" in data - - elif subject == f"comms.command.{identity}" or subject == "comms.identity": + elif subject == f"comms.channel_change.{identity}": + ret, info, _index = cc.settings.handle_mesh_settings_channel_change(data) + if ret == "TRIGGER": + cmd = json.dumps( + {"api_version": 1, "cmd": "APPLY", "radio_index": f"{_index}"} + ) + ret, info, resp = cc.command.handle_command(cmd, cc) + elif subject in (f"comms.command.{identity}", "comms.identity"): ret, info, resp = cc.command.handle_command(data, cc) elif subject == f"comms.status.{identity}": ret, info = "OK", "Returning current status" - if subject == "comms.settings_csa": - await handle_settings_csa_post(ret) - else: - # Update status info - cc.comms_status.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} - - if resp != "": - response['data'] = resp - - cc.logger.debug("Sending response: %s", str(response)[:1000]) - await message.respond(json.dumps(response).encode("utf-8")) + # Update status info + _ = [item.refresh_status() for item in cc.c_status] + + response = { + "status": ret, + "info": info, + "mesh_status": [item.mesh_status for item in cc.c_status], + "mesh_cfg_status": [item.mesh_cfg_status for item in cc.c_status], + "visualisation_active": [ + item.is_visualisation_active for item in cc.c_status + ], + "mesh_radio_on": [item.is_mesh_radio_on for item in cc.c_status], + "ap_radio_on": [item.is_ap_radio_on for item in cc.c_status], + "security_status": [item.security_status for item in cc.c_status], + } + + if resp != "": + response["data"] = resp + + cc.logger.debug("Sending response: %s", str(response)[:1000]) + await message.respond(json.dumps(response).encode("utf-8")) await nats_client.subscribe(f"comms.settings.{identity}", cb=message_handler) - await nats_client.subscribe("comms.settings_csa", cb=message_handler) + await nats_client.subscribe(f"comms.channel_change.{identity}", cb=message_handler) await nats_client.subscribe(f"comms.command.{identity}", cb=message_handler) await nats_client.subscribe("comms.identity", cb=message_handler) await nats_client.subscribe(f"comms.status.{identity}", cb=message_handler) @@ -242,16 +243,15 @@ async def message_handler(message): cc.logger.error("Error:", e) -if __name__ == '__main__': - parser = argparse.ArgumentParser(description='Mesh Settings') - parser.add_argument('-s', '--server', help='Server IP', required=True) - parser.add_argument('-p', '--port', help='Server port', required=True) - parser.add_argument('-k', '--keyfile', help='TLS keyfile', required=False) - parser.add_argument('-c', '--certfile', help='TLS certfile', required=False) +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Mesh Settings") + parser.add_argument("-s", "--server", help="Server IP", required=True) + parser.add_argument("-p", "--port", help="Server port", required=True) + parser.add_argument("-k", "--keyfile", help="TLS keyfile", required=False) + parser.add_argument("-c", "--certfile", help="TLS certfile", required=False) args = parser.parse_args() loop = asyncio.new_event_loop() - loop.run_until_complete(main(args.server, args.port, - args.keyfile, args.certfile)) + loop.run_until_complete(main(args.server, args.port, args.keyfile, args.certfile)) loop.run_forever() loop.close() diff --git a/modules/sc-mesh-secure-deployment/src/nats/initd/S9011sNatsMesh b/modules/sc-mesh-secure-deployment/src/nats/initd/S9011sNatsMesh index 807485329..ded58882a 100644 --- a/modules/sc-mesh-secure-deployment/src/nats/initd/S9011sNatsMesh +++ b/modules/sc-mesh-secure-deployment/src/nats/initd/S9011sNatsMesh @@ -10,35 +10,62 @@ # automatically ### END INIT INFO +source /opt/mesh-helper.sh +# sources mesh configuration + +RADIO_INDEX=$2 # $2: index of the mesh.conf file to be used e.g. "id0" + +source_configuration "${RADIO_INDEX:2}" + DAEMON="mesh-11s_nats.sh" DIR="/opt/" -PIDFILE="/var/run/$DAEMON.pid" -LOG_FILE=/opt/mesh_11s.log +_WIFI="${RADIO_INDEX}_MESH_VIF" +_MODE="${RADIO_INDEX}_MODE" +PIDFILE="/var/run/${DAEMON}_${RADIO_INDEX}.pid" +LOG_FILE=/opt/mesh_11s_${RADIO_INDEX}.log -source /opt/mesh-helper.sh -# sources mesh configuration -source_configuration +SYNC_LOCK="/var/run/mesh_start.lock" # shellcheck source=/dev/null [ -r "/etc/default/$DAEMON" ] && . "/etc/default/$DAEMON" start() { + if mkdir $SYNC_LOCK 2>/dev/null; then + echo "I have a lock $RADIO_INDEX PID $$" + else + echo "Instance is already running. Waiting..." + # Wait for the lock directory to be removed, indicating the previous instance finished + while [ -d "$SYNC_LOCK" ]; do + sleep $(( (RANDOM % 5) + 1 )) + done + mkdir "$SYNC_LOCK" + echo "I have a lock $$" + fi + printf 'Starting %s: ' "$DAEMON" - start-stop-daemon -b -m -S -q -p "$PIDFILE" -a /bin/bash -- -c "exec $DIR$DAEMON mesh > $LOG_FILE 2>&1" + if [ "${!_MODE}" == "ap+mesh_scc" ] || [ "${!_MODE}" == "ap+mesh_mcc" ]; then + mode=mesh + else + mode=${!_MODE} + fi + start-stop-daemon -b -m -S -q -p "$PIDFILE" -a /bin/bash -- -c "exec $DIR$DAEMON $mode $RADIO_INDEX > $LOG_FILE 2>&1" status=$? if [ "$status" -eq 0 ]; then echo "OK" else echo "FAIL" fi + + rmdir "$SYNC_LOCK" + return "$status" } stop() { printf 'Stopping %s: ' "$DAEMON" - NEW_PID=$(ps ax | grep -E 'wpa_supplicant\-11s' | awk {'print $1'}) - kill -9 $NEW_PID - rm -fr /var/run/wpa_supplicant/* 2>/dev/null - ifconfig "$MESH_VIF" down 2>/dev/null + kill -9 "$(cat "$PIDFILE")" + kill -9 "$(ps ax | grep -E "[w]pa_supplicant\-11s_$RADIO_INDEX" | awk {'print $1'})" + rm -fr /var/run/wpa_supplicant_"${RADIO_INDEX}" 2>/dev/null + ifconfig "${!_WIFI}" down 2>/dev/null start-stop-daemon -K -o -q -p "$PIDFILE" status=$? if [ "$status" -eq 0 ]; then diff --git a/modules/sc-mesh-secure-deployment/src/nats/initd/S90APoint b/modules/sc-mesh-secure-deployment/src/nats/initd/S90APoint index 4684af249..a1c3669db 100644 --- a/modules/sc-mesh-secure-deployment/src/nats/initd/S90APoint +++ b/modules/sc-mesh-secure-deployment/src/nats/initd/S90APoint @@ -10,22 +10,27 @@ # automatically ### END INIT INFO -DAEMON="mesh-11s_nats.sh" -DIR="/opt/" -PIDFILE="/var/run/{$DAEMON}_hostapd.pid" -LOG_FILE=/opt/mesh-11s_AP.log - source /opt/mesh-helper.sh # sources mesh configuration -source_configuration + +RADIO_INDEX=$2 # $1: index of the mesh.conf file to be used e.g. "id0" + +source_configuration "${RADIO_INDEX:2}" + +DAEMON="mesh-11s_nats.sh" +DIR="/opt/" +_WIFI="${RADIO_INDEX}_MESH_VIF" +_MODE="${RADIO_INDEX}_MODE" +PIDFILE="/var/run/${DAEMON}_hostapd_${RADIO_INDEX}.pid" +LOG_FILE=/opt/mesh-11s_AP_${RADIO_INDEX}.log # shellcheck source=/dev/null [ -r "/etc/default/$DAEMON" ] && . "/etc/default/$DAEMON" start() { - if [ "$MODE" == "ap+mesh_mcc" ] || [ "$MODE" == "ap+mesh_scc" ]; then + if [ "${!_MODE}" == "ap+mesh_mcc" ] || [ "${!_MODE}" == "ap+mesh_scc" ]; then printf 'Starting %s: ' "$DAEMON" - start-stop-daemon -b -m -S -q -p "$PIDFILE" -a /bin/bash -- -c "exec $DIR$DAEMON > $LOG_FILE 2>&1" + start-stop-daemon -b -m -S -q -p "$PIDFILE" -a /bin/bash -- -c "exec $DIR$DAEMON ${!_MODE} ${RADIO_INDEX} > $LOG_FILE 2>&1" status=$? if [ "$status" -eq 0 ]; then echo "OK" @@ -41,12 +46,12 @@ start() { stop() { printf 'Stopping %s: ' "$DAEMON" # shellcheck disable=SC2009 #pgrep is not available - NEW_PID=$(ps ax | grep -E '[h]ostapd' | awk {'print $1'}) + NEW_PID=$(ps ax | grep -E "[h]ostapd-$RADIO_INDEX" | awk {'print $1'}) kill -9 "$NEW_PID" start-stop-daemon -K -o -q -p "$PIDFILE" rm -fr /var/run/hostapd/* 2>/dev/null - ifconfig "$MESH_VIF"-1 down 2>/dev/null - iw dev "$MESH_VIF"-1 del 2>/dev/null + ifconfig "${!_WIFI}"-1 down 2>/dev/null + iw dev "${!_WIFI}"-1 del 2>/dev/null ifname_ap="$(ifconfig -a | grep -m 1 "wlan*" | awk -F':' '{ print $1 }')" ifconfig "$ifname_ap" down 2>/dev/null iw dev "$ifname_ap" del 2>/dev/null diff --git a/modules/sc-mesh-secure-deployment/src/nats/initd/S90Alfred b/modules/sc-mesh-secure-deployment/src/nats/initd/S90Alfred new file mode 100644 index 000000000..8780331cb --- /dev/null +++ b/modules/sc-mesh-secure-deployment/src/nats/initd/S90Alfred @@ -0,0 +1,70 @@ +#!/bin/bash +### BEGIN INIT INFO +# Provides: S90Alfred +# Short-Description: alfred +# Description: Starts alfred to provide visualization data +### END INIT INFO + +DAEMON="alfred" +DIR="/usr/bin/" +PIDFILE="/var/run/$DAEMON.pid" +# shellcheck source=/dev/null +[ -r "/etc/default/$DAEMON" ] && . "/etc/default/$DAEMON" +LOG_FILE=/opt/alfred.log + +source /opt/mesh-helper.sh +# sources mesh configuration +source_configuration "id0" +bridge_name=$(echo "$BRIDGE" | cut -d' ' -f1) + +ARGS="-i $bridge_name -m" + +wait_for_bridge() { + while [ ! -d "/sys/class/net/$bridge_name" ]; do + sleep 1 + done +} + +start() { + echo "$ARGS" + printf 'Starting %s: ' "$DAEMON" + start-stop-daemon -b -m -S -q -p "$PIDFILE" -a /bin/bash -- -c "exec $DIR$DAEMON \ + $ARGS > $LOG_FILE 2>&1" + status=$? + if [ "$status" -eq 0 ]; then + echo "OK" + else + echo "FAIL" + fi + return "$status" +} +stop() { + printf 'Stopping %s: ' "$DAEMON" + start-stop-daemon -K -q -p "$PIDFILE" + status=$? + if [ "$status" -eq 0 ]; then + rm -f "$PIDFILE" + echo "OK" + else + echo "FAIL" + fi + return "$status" +} +restart() { + stop + sleep 1 + start +} +case "$1" in + start|stop|restart) + "$1";; + reload) + # Restart, since there is no true "reload" feature. + restart;; + *) + echo "Usage: $0 {start|stop|restart|reload}" + exit 1 +esac + +exit 0 + diff --git a/modules/sc-mesh-secure-deployment/src/nats/initd/S90nats_discovery b/modules/sc-mesh-secure-deployment/src/nats/initd/S90nats_discovery index 7da763190..67a7d484a 100644 --- a/modules/sc-mesh-secure-deployment/src/nats/initd/S90nats_discovery +++ b/modules/sc-mesh-secure-deployment/src/nats/initd/S90nats_discovery @@ -13,7 +13,8 @@ PIDFILE="/var/run/$DAEMON.pid" LOG_FILE=/opt/comms_discovery.log source /opt/mesh-helper.sh -source_configuration +# for ROLE +source_configuration "0" KEY_FILE="/etc/ssl/private/comms_auth_private_key.pem" CERT_FILE="/etc/ssl/certs/comms_auth_cert.pem" diff --git a/modules/sc-mesh-secure-deployment/src/nats/requirements/requirements.txt b/modules/sc-mesh-secure-deployment/src/nats/requirements/requirements.txt index a7d2b4b52..7ee6da723 100644 --- a/modules/sc-mesh-secure-deployment/src/nats/requirements/requirements.txt +++ b/modules/sc-mesh-secure-deployment/src/nats/requirements/requirements.txt @@ -1,4 +1,5 @@ cryptography==3.4.8 PyKCS11==1.5.11 requests==2.31.0 -pycryptodome==3.18.0 \ No newline at end of file +pycryptodome==3.18.0 +nats-py==2.1.7 \ No newline at end of file diff --git a/modules/sc-mesh-secure-deployment/src/nats/scripts/cli_channel_change.py b/modules/sc-mesh-secure-deployment/src/nats/scripts/cli_channel_change.py new file mode 100644 index 000000000..50e3da4cf --- /dev/null +++ b/modules/sc-mesh-secure-deployment/src/nats/scripts/cli_channel_change.py @@ -0,0 +1,22 @@ +import asyncio +import client +import json +import config + +async def main(): + # Connect to NATS! + nc = await client.connect_nats() + cmd_dict = {"frequency": "2452", "radio_index": "1"} + cmd = json.dumps(cmd_dict) + rep = await nc.request(f"comms.channel_change.{config.MODULE_IDENTITY}", cmd.encode(), timeout=10) + print(f"Published to comms.channel_change: {cmd}") + parameters = json.loads(rep.data) + print(json.dumps(parameters, indent=2)) + await nc.close() + exit(0) + + +if __name__ == '__main__': + loop = asyncio.get_event_loop() + loop.run_until_complete(main()) + loop.close() diff --git a/modules/sc-mesh-secure-deployment/src/nats/scripts/cli_command_get_config_hostapd.py b/modules/sc-mesh-secure-deployment/src/nats/scripts/cli_command_get_config_hostapd.py index cfe38039f..b3f7539fa 100644 --- a/modules/sc-mesh-secure-deployment/src/nats/scripts/cli_command_get_config_hostapd.py +++ b/modules/sc-mesh-secure-deployment/src/nats/scripts/cli_command_get_config_hostapd.py @@ -9,7 +9,7 @@ async def main(): # Connect to NATS! nc = await client.connect_nats() - cmd_dict = {"api_version": 1, "cmd": "GET_CONFIG", "param": "HOSTAPD_CONFIG"} + cmd_dict = {"api_version": 1, "cmd": "GET_CONFIG", "param": "HOSTAPD_CONFIG", "radio_index": "0"} cmd = json.dumps(cmd_dict) rep = await nc.request(f"comms.command.{config.MODULE_IDENTITY}", cmd.encode(), diff --git a/modules/sc-mesh-secure-deployment/src/nats/scripts/cli_command_get_config_wpa.py b/modules/sc-mesh-secure-deployment/src/nats/scripts/cli_command_get_config_wpa.py index 1a22c0383..51e0b046a 100644 --- a/modules/sc-mesh-secure-deployment/src/nats/scripts/cli_command_get_config_wpa.py +++ b/modules/sc-mesh-secure-deployment/src/nats/scripts/cli_command_get_config_wpa.py @@ -9,7 +9,7 @@ async def main(): # Connect to NATS! nc = await client.connect_nats() - cmd_dict = {"api_version": 1, "cmd": "GET_CONFIG", "param": "WPA_CONFIG"} + cmd_dict = {"api_version": 1, "cmd": "GET_CONFIG", "param": "WPA_CONFIG", "radio_index": "0"} cmd = json.dumps(cmd_dict) rep = await nc.request(f"comms.command.{config.MODULE_IDENTITY}", cmd.encode(), diff --git a/modules/sc-mesh-secure-deployment/src/nats/scripts/cli_command_logs_hostapd.py b/modules/sc-mesh-secure-deployment/src/nats/scripts/cli_command_logs_hostapd.py index 9f572cab2..ea6dad762 100644 --- a/modules/sc-mesh-secure-deployment/src/nats/scripts/cli_command_logs_hostapd.py +++ b/modules/sc-mesh-secure-deployment/src/nats/scripts/cli_command_logs_hostapd.py @@ -9,7 +9,7 @@ async def main(): # Connect to NATS! nc = await client.connect_nats() - cmd_dict = {"api_version": 1, "cmd": "LOGS", "param": "HOSTAPD"} + cmd_dict = {"api_version": 1, "cmd": "LOGS", "param": "HOSTAPD", "radio_index": "0"} cmd = json.dumps(cmd_dict) rep = await nc.request(f"comms.command.{config.MODULE_IDENTITY}", cmd.encode(), diff --git a/modules/sc-mesh-secure-deployment/src/nats/scripts/cli_command_logs_wpa.py b/modules/sc-mesh-secure-deployment/src/nats/scripts/cli_command_logs_wpa.py index 70c79d57c..4314f9820 100644 --- a/modules/sc-mesh-secure-deployment/src/nats/scripts/cli_command_logs_wpa.py +++ b/modules/sc-mesh-secure-deployment/src/nats/scripts/cli_command_logs_wpa.py @@ -9,7 +9,7 @@ async def main(): # Connect to NATS! nc = await client.connect_nats() - cmd_dict = {"api_version": 1, "cmd": "LOGS", "param": "WPA"} + cmd_dict = {"api_version": 1, "cmd": "LOGS", "param": "WPA", "radio_index": "0"} cmd = json.dumps(cmd_dict) rep = await nc.request(f"comms.command.{config.MODULE_IDENTITY}", cmd.encode(), diff --git a/modules/sc-mesh-secure-deployment/src/nats/scripts/cli_radio_off.py b/modules/sc-mesh-secure-deployment/src/nats/scripts/cli_radio_off.py index 91c09a258..e55146f12 100644 --- a/modules/sc-mesh-secure-deployment/src/nats/scripts/cli_radio_off.py +++ b/modules/sc-mesh-secure-deployment/src/nats/scripts/cli_radio_off.py @@ -8,7 +8,7 @@ async def main(): # Connect to NATS! nc = await client.connect_nats() - cmd_dict = {"api_version": 1, "cmd": "DOWN"} + cmd_dict = {"api_version": 1, "cmd": "DOWN", "radio_index": "0"} cmd = json.dumps(cmd_dict) rep = await nc.request(f"comms.command.{config.MODULE_IDENTITY}", cmd.encode(), timeout=2) diff --git a/modules/sc-mesh-secure-deployment/src/nats/scripts/cli_radio_on.py b/modules/sc-mesh-secure-deployment/src/nats/scripts/cli_radio_on.py index 7c875ac2d..8ee7e55a6 100644 --- a/modules/sc-mesh-secure-deployment/src/nats/scripts/cli_radio_on.py +++ b/modules/sc-mesh-secure-deployment/src/nats/scripts/cli_radio_on.py @@ -8,7 +8,7 @@ async def main(): # Connect to NATS! nc = await client.connect_nats() - cmd_dict = {"api_version": 1, "cmd": "UP"} + cmd_dict = {"api_version": 1, "cmd": "UP", "radio_index": "0"} cmd = json.dumps(cmd_dict) rep = await nc.request(f"comms.command.{config.MODULE_IDENTITY}", cmd.encode(), timeout=2) 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 bbd7001e3..e9264cc37 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,18 +8,19 @@ async def main(): # Connect to NATS! nc = await client.connect_nats() - cmd_dict = {"api_version": 1, "cmd": "APPLY"} + cmd_dict = {"api_version": 1, "cmd": "APPLY", "radio_index": "2"} cmd = json.dumps(cmd_dict) - rep = await nc.request(f"comms.command.{config.MODULE_IDENTITY}", - cmd.encode(), timeout=10) + rep = await nc.request( + f"comms.command.{config.MODULE_IDENTITY}", cmd.encode(), timeout=15 + ) parameters = json.loads(rep.data) - print(parameters) + print(json.dumps(parameters, indent=2)) await nc.close() exit(0) -if __name__ == '__main__': +if __name__ == "__main__": loop = asyncio.get_event_loop() loop.run_until_complete(main()) loop.close() 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 eb37c41f6..30cf29d5b 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 @@ -3,27 +3,80 @@ import json import config - async def main(): # Connect to NATS! nc = await client.connect_nats() - cmd_dict = {"api_version": 1, "ssid": "test_mesh", "key": "1234567890", - "ap_mac": "00:11:22:33:44:55", "country": "FI", "frequency": "5220", - "frequency_mcc": "2412", "routing": "batman-adv", "priority": "long_range", - "ip": "192.168.1.2", "subnet": "255.255.255.0", "tx_power": "5", "mode": "mesh", - "role": f"{config.MODULE_ROLE}"} + cmd_dict = { + "api_version": 1, + "role": f"{config.MODULE_ROLE}", # sleeve, drone, gcs + "radios": [ + { + "radio_index": "0", + "ssid": "test_mesh2", + "key": "1234567890", + "ap_mac": "00:11:22:33:44:55", + "country": "US", # all radios must have the same country + "frequency": "2412", + "frequency_mcc": "2412", # multiradio not supporting + "routing": "batman-adv", + "priority": "long_range", + "ip": "10.20.15.3", + "subnet": "255.255.255.0", + "tx_power": "15", + "mode": "mesh", # ap+mesh_scc, mesh, halow + "mesh_vif": "wlp2s0", + "batman_iface": "bat0", + }, + { + "radio_index": "1", + "ssid": "test_mesh", + "key": "1234567890", + "ap_mac": "00:11:22:33:44:55", + "country": "US", # all radios must have the same country + "frequency": "5220", + "frequency_mcc": "2412", # multiradio not supporting + "routing": "batman-adv", + "priority": "long_range", + "ip": "10.20.15.3", + "subnet": "255.255.255.0", + "tx_power": "15", + "mode": "mesh", # ap+mesh_scc, mesh, halow + "mesh_vif": "wlp3s0", # this needs to be correct + "batman_iface": "bat0", + }, + { + "radio_index": "2", + "ssid": "test_mesh3", + "key": "1234567890", + "ap_mac": "00:11:22:33:44:55", + "country": "US", # all radios must have the same country + "frequency": "5190", + "frequency_mcc": "2412", # multiradio not supporting + "routing": "batman-adv", + "priority": "long_range", + "ip": "10.20.15.3", + "subnet": "255.255.255.0", + "tx_power": "30", + "mode": "halow", # ap+mesh_scc, mesh, halow + "mesh_vif": "halow1", + "batman_iface": "bat0", + }, + ], + "bridge": "br-lan bat0 eth1 lan1 eth0 usb0" + } + cmd = json.dumps(cmd_dict) - rep = await nc.request(f"comms.settings.{config.MODULE_IDENTITY}", - cmd.encode(), - timeout=2) + rep = await nc.request( + f"comms.settings.{config.MODULE_IDENTITY}", cmd.encode(), timeout=2 + ) parameters = json.loads(rep.data) - print(parameters) + print(json.dumps(parameters, indent=2)) await nc.close() exit(0) -if __name__ == '__main__': +if __name__ == "__main__": loop = asyncio.get_event_loop() loop.run_until_complete(main()) loop.close() diff --git a/modules/sc-mesh-secure-deployment/src/nats/scripts/cli_settings_request_csa.py b/modules/sc-mesh-secure-deployment/src/nats/scripts/cli_settings_request_csa.py deleted file mode 100644 index 8b3b61837..000000000 --- a/modules/sc-mesh-secure-deployment/src/nats/scripts/cli_settings_request_csa.py +++ /dev/null @@ -1,20 +0,0 @@ -import asyncio -import client -import json - - -async def main(): - # Connect to NATS! - nc = await client.connect_nats() - cmd_dict = {"frequency": "2412", "delay": "1", "amount": "2"} - cmd = json.dumps(cmd_dict) - rep = await nc.publish("comms.settings_csa", cmd.encode()) - print(f"Published to comms.settings_csa: {cmd} ({rep})") - await nc.close() - exit(0) - - -if __name__ == '__main__': - loop = asyncio.get_event_loop() - loop.run_until_complete(main()) - loop.close() diff --git a/modules/sc-mesh-secure-deployment/src/nats/scripts/cli_settings_request_mcc.py b/modules/sc-mesh-secure-deployment/src/nats/scripts/cli_settings_request_mcc.py deleted file mode 100644 index 841802a75..000000000 --- a/modules/sc-mesh-secure-deployment/src/nats/scripts/cli_settings_request_mcc.py +++ /dev/null @@ -1,29 +0,0 @@ -import asyncio -import client -import json -import config - - -async def main(): - # Connect to NATS! - nc = await client.connect_nats() - cmd_dict = {"api_version": 1, "ssid": "test_mesh", "key": "1234567890", - "ap_mac": "00:11:22:33:44:55", "country": "FI", "frequency": "5220", - "frequency_mcc": "2412", "routing": "batman-adv", "priority": "long_range", - "ip": "192.168.1.2", "subnet": "255.255.255.0", "tx_power": "5", - "mode": "ap+mesh_mcc", "role": f"{config.MODULE_ROLE}"} - cmd = json.dumps(cmd_dict) - rep = await nc.request(f"comms.settings.{config.MODULE_IDENTITY}", - cmd.encode(), - timeout=2) - parameters = json.loads(rep.data) - print(parameters) - - await nc.close() - exit(0) - - -if __name__ == '__main__': - loop = asyncio.get_event_loop() - loop.run_until_complete(main()) - loop.close() diff --git a/modules/sc-mesh-secure-deployment/src/nats/scripts/cli_settings_request_scc.py b/modules/sc-mesh-secure-deployment/src/nats/scripts/cli_settings_request_scc.py deleted file mode 100644 index 4bff03be5..000000000 --- a/modules/sc-mesh-secure-deployment/src/nats/scripts/cli_settings_request_scc.py +++ /dev/null @@ -1,29 +0,0 @@ -import asyncio -import client -import json -import config - - -async def main(): - # Connect to NATS! - nc = await client.connect_nats() - cmd_dict = {"api_version": 1, "ssid": "test_mesh", "key": "1234567890", - "ap_mac": "00:11:22:33:44:55", "country": "FI", "frequency": "5220", - "frequency_mcc": "2412", "routing": "batman-adv", "priority": "long_range", - "ip": "192.168.1.2", "subnet": "255.255.255.0", "tx_power": "5", - "mode": "ap+mesh_scc", "role": f"{config.MODULE_ROLE}"} - cmd = json.dumps(cmd_dict) - rep = await nc.request(f"comms.settings.{config.MODULE_IDENTITY}", - cmd.encode(), - timeout=2) - parameters = json.loads(rep.data) - print(parameters) - - await nc.close() - exit(0) - - -if __name__ == '__main__': - loop = asyncio.get_event_loop() - loop.run_until_complete(main()) - loop.close() diff --git a/modules/sc-mesh-secure-deployment/src/nats/scripts/cli_settings_revoke.py b/modules/sc-mesh-secure-deployment/src/nats/scripts/cli_settings_revoke.py index a3767f215..7d553e0fa 100644 --- a/modules/sc-mesh-secure-deployment/src/nats/scripts/cli_settings_revoke.py +++ b/modules/sc-mesh-secure-deployment/src/nats/scripts/cli_settings_revoke.py @@ -8,7 +8,7 @@ async def main(): # Connect to NATS! nc = await client.connect_nats() - cmd_dict = {"api_version": 1, "cmd": "REVOKE"} + cmd_dict = {"api_version": 1, "cmd": "REVOKE", "radio_index": "0"} cmd = json.dumps(cmd_dict) rep = await nc.request(f"comms.command.{config.MODULE_IDENTITY}", cmd.encode(), diff --git a/modules/sc-mesh-secure-deployment/src/nats/scripts/cli_subscribe_settings_csa.py b/modules/sc-mesh-secure-deployment/src/nats/scripts/cli_subscribe_settings_csa.py deleted file mode 100644 index ba3ac16a6..000000000 --- a/modules/sc-mesh-secure-deployment/src/nats/scripts/cli_subscribe_settings_csa.py +++ /dev/null @@ -1,56 +0,0 @@ -import asyncio -import signal -import json -from nats.aio.client import Client as NATS -import client - - -async def run(loop): - nc = NATS() - - async def error_cb(e): - print("Error:", e) - - async def closed_cb(): - print("Connection to NATS is closed.") - if nc.is_closed: - return - await nc.close() - - async def reconnected_cb(): - print("Connected to NATS ...") - - async def subscribe_handler(msg): - subject = msg.subject - reply = msg.reply - data = json.loads(msg.data.decode()) - print("Received a message on '{subject} {reply}': {data}".format( - subject=subject, reply=reply, data=data)) - - try: - await client.connect(nc, reconnected_cb=reconnected_cb, - closed_cb=closed_cb, - max_reconnect_attempts=-1) - except Exception as e: - print(e) - - print(f"Connected to NATS at: {nc.connected_url.netloc}") - - def signal_handler(): - if nc.is_closed: - return - print("Disconnecting...") - loop.create_task(nc.close()) - - for sig in ('SIGINT', 'SIGTERM'): - loop.add_signal_handler(getattr(signal, sig), signal_handler) - - await nc.subscribe("comms.settings_csa", "", cb=subscribe_handler) - -if __name__ == '__main__': - loop = asyncio.get_event_loop() - loop.run_until_complete(run(loop)) - try: - loop.run_forever() - finally: - loop.close() diff --git a/modules/sc-mesh-secure-deployment/src/nats/scripts/cli_visual_start.py b/modules/sc-mesh-secure-deployment/src/nats/scripts/cli_visual_start.py index 3bc777a0b..2b1d07b0b 100644 --- a/modules/sc-mesh-secure-deployment/src/nats/scripts/cli_visual_start.py +++ b/modules/sc-mesh-secure-deployment/src/nats/scripts/cli_visual_start.py @@ -7,7 +7,7 @@ async def main(): # Connect to NATS! nc = await client.connect_nats() - cmd_dict = {"api_version": 1, "cmd": "ENABLE_VISUALISATION", "interval": "1000"} + cmd_dict = {"api_version": 1, "cmd": "ENABLE_VISUALISATION", "interval": "1000", "radio_index": "0"} cmd = json.dumps(cmd_dict) rep = await nc.request(f"comms.command.{config.MODULE_IDENTITY}", cmd.encode(), diff --git a/modules/sc-mesh-secure-deployment/src/nats/scripts/cli_visual_stop.py b/modules/sc-mesh-secure-deployment/src/nats/scripts/cli_visual_stop.py index 3a98f87a7..6e6d91e21 100644 --- a/modules/sc-mesh-secure-deployment/src/nats/scripts/cli_visual_stop.py +++ b/modules/sc-mesh-secure-deployment/src/nats/scripts/cli_visual_stop.py @@ -7,7 +7,7 @@ async def main(): # Connect to NATS! nc = await client.connect_nats() - cmd_dict = {"api_version": 1, "cmd": "DISABLE_VISUALISATION", "interval": "1000"} + cmd_dict = {"api_version": 1, "cmd": "DISABLE_VISUALISATION", "interval": "1000", "radio_index": "0"} cmd = json.dumps(cmd_dict) rep = await nc.request(f"comms.command.{config.MODULE_IDENTITY}", cmd.encode(), diff --git a/modules/sc-mesh-secure-deployment/src/nats/scripts/cli_visual_subscribe.py b/modules/sc-mesh-secure-deployment/src/nats/scripts/cli_visual_subscribe.py index 557ff72a8..2edcce458 100644 --- a/modules/sc-mesh-secure-deployment/src/nats/scripts/cli_visual_subscribe.py +++ b/modules/sc-mesh-secure-deployment/src/nats/scripts/cli_visual_subscribe.py @@ -24,13 +24,12 @@ async def subscribe_handler(msg): subject = msg.subject reply = msg.reply data = json.loads(msg.data.decode()) - print("Received a message on '{subject} {reply}': {data}".format( - subject=subject, reply=reply, data=data)) + print(f"Received a message on '{subject} {reply}': {data}") try: - await client.connect(nc, reconnected_cb=reconnected_cb, + await client.connect(nc, recon_cb=reconnected_cb, closed_cb=closed_cb, - max_reconnect_attempts=-1) + max_reconnection_attempts=-1) except Exception as e: print(e) diff --git a/modules/sc-mesh-secure-deployment/src/nats/scripts/client.py b/modules/sc-mesh-secure-deployment/src/nats/scripts/client.py index 52366244f..bae914316 100644 --- a/modules/sc-mesh-secure-deployment/src/nats/scripts/client.py +++ b/modules/sc-mesh-secure-deployment/src/nats/scripts/client.py @@ -27,7 +27,7 @@ async def connect_nats(): return nats_client -async def connect(nats_client: Client, recon_cb=None, closed_cb=None, max_recon_attempts=None): +async def connect(nats_client: Client, recon_cb=None, closed_cb=None, max_reconnection_attempts=None): if os.path.exists(client_cert) and \ os.path.exists(key) and \ os.path.exists(ca_cert): @@ -40,12 +40,12 @@ async def connect(nats_client: Client, recon_cb=None, closed_cb=None, max_recon_ tls=ssl_context, reconnected_cb=recon_cb, closed_cb=closed_cb, - max_reconnect_attempts=max_recon_attempts) + max_reconnect_attempts=max_reconnection_attempts) else: await nats_client.connect(f"{config.MODULE_IP}:{config.MODULE_PORT}", reconnected_cb=recon_cb, closed_cb=closed_cb, - max_reconnect_attempts=max_recon_attempts) + max_reconnect_attempts=max_reconnection_attempts) return None diff --git a/modules/sc-mesh-secure-deployment/src/nats/scripts/config.py b/modules/sc-mesh-secure-deployment/src/nats/scripts/config.py index db201fd58..5324e92cf 100644 --- a/modules/sc-mesh-secure-deployment/src/nats/scripts/config.py +++ b/modules/sc-mesh-secure-deployment/src/nats/scripts/config.py @@ -8,7 +8,7 @@ print("No identity found!!!!!!!!!!!!!!!!!!!!!!!!") print("Please run _cli_command_get_identity.py to get the identity (creates identity.py)") -MODULE_IP = "10.10.10.2" # or 192.168.1.x - brlan ip address +MODULE_IP = "10.10.20.2" # or 192.168.1.x - brlan ip address MODULE_PORT = "4222" # IPv6 connection example @@ -16,4 +16,4 @@ # MODULE_PORT = "6222" MODULE_ROLE = "drone" # drone, sleeve or gcs -MODULE_IDENTITY = IDENTITY # messages are sent to this device +MODULE_IDENTITY = IDENTITY # messages are sent to this device diff --git a/modules/sc-mesh-secure-deployment/src/nats/src/batadvvis.py b/modules/sc-mesh-secure-deployment/src/nats/src/batadvvis.py index 0245b7ed7..66b7cfa5a 100644 --- a/modules/sc-mesh-secure-deployment/src/nats/src/batadvvis.py +++ b/modules/sc-mesh-secure-deployment/src/nats/src/batadvvis.py @@ -12,10 +12,14 @@ class BatAdvVis: """ command = 'batadv-vis' - def __init__(self, loop_interval:float = 1.0): + def __init__(self, loop_interval:float = 1.0, batman_interface:str = "bat0" ): + """ + init method + """ + self.__batman_interface = batman_interface self.latest_topology = "{}" self.thread_running = False - self.interval = loop_interval + self.__interval = loop_interval @staticmethod def remove_interfaces(visual_lines): @@ -77,7 +81,7 @@ def run(self): self.thread_running = True while self.thread_running: self.latest_topology = self.get() - time.sleep(self.interval) + time.sleep(self.__interval) # Real user is mesh_executor diff --git a/modules/sc-mesh-secure-deployment/src/nats/src/batstat.py b/modules/sc-mesh-secure-deployment/src/nats/src/batstat.py index e0227c2da..bdf3a8a05 100644 --- a/modules/sc-mesh-secure-deployment/src/nats/src/batstat.py +++ b/modules/sc-mesh-secure-deployment/src/nats/src/batstat.py @@ -5,8 +5,9 @@ import re import json from time import sleep -import netifaces from datetime import datetime +import netifaces + class STATUS: # pylint: disable=too-few-public-methods """ @@ -22,29 +23,31 @@ def __init__(self): self.accesspoint = "AP" # accesspoint configuration in use self.not_avail = "NA" # Not Available - +# pylint: disable=too-many-instance-attributes class Batman: """ Batman mesh class """ - def __init__(self, loop_interval: float = 1.0): - self.topology = {} - self.device_template = {} - self.device_rssi_list = [] - self.device_noise_dict = {} + def __init__(self, loop_interval: float = 1.0, batman_interface: str = "bat0"): + self.batman_interface = batman_interface + self.topology: {} = {} + self.device_template: {} = {} + self.device_rssi_list_per_radio: [dict, ...] = [] + self.device_noise_list_per_radio: [dict, ...] = [] self.mesh_status = STATUS() - self._status = self.mesh_status.no_config # "MESH/OFF/NO_CONFIG/ONGOING/AP/ERROR" - self.iw_type = self.mesh_status.not_avail # iw dev info - type - self.network_interface = self.mesh_status.not_avail - self.freq = self.mesh_status.not_avail - self.txpower = self.mesh_status.not_avail - self.iw_state = self.mesh_status.not_avail - self.country = self.mesh_status.not_avail - self.hw_name = self.mesh_status.not_avail - self.thread_running = False - self.latest_stat = "" - self.interval = loop_interval + self.status: [str, ...] = [self.mesh_status.no_config] # "MESH/OFF/NO_CONFIG/ + # ONGOING/AP/ERROR" + self.iw_type: [str, ...] = [self.mesh_status.not_avail] # iw dev info - type + self.network_interface: [str, ...] = [self.mesh_status.not_avail] + self.freq: [str, ...] = [self.mesh_status.not_avail] + self.txpower: [str, ...] = [self.mesh_status.not_avail] + self.iw_state: [str, ...] = [self.mesh_status.not_avail] + self.country: [str, ...] = [self.mesh_status.not_avail] + self.hw_name: [str, ...] = [self.mesh_status.not_avail] + self.thread_running: bool = False + self.latest_stat: str = "" + self.interval: float = loop_interval def _update_interface_name(self): """ @@ -52,42 +55,49 @@ def _update_interface_name(self): :return: None """ - out = subprocess.Popen(['batctl', 'if'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) - try: - self.network_interface = out.stdout.readline().decode('utf-8').split(":")[0] - except (TypeError, IndexError): - self.network_interface = self.mesh_status.not_avail + with subprocess.Popen(['batctl', 'meshif', self.batman_interface, 'if'], + stdout=subprocess.PIPE, stderr=subprocess.PIPE) as out: + + try: + self.network_interface = [line.decode('utf-8').split(":")[0] for line in out.stdout] + except (TypeError, IndexError): + self.network_interface = [self.mesh_status.not_avail] def _update_survey_dump(self): """ - Update survey dump info, self.freq, self.noise + Update survey dump info, freq, noise :return: None """ - dump = subprocess.Popen(['iw', 'dev', self.network_interface, 'survey', 'dump'], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - - self.device_noise_dict = {} - freq = 0 - noise = 0 - try: - for line in dump.stdout: - line = line.decode("utf-8") - if "frequency:" in line: - freq = re.findall(r'\s*frequency:\s+(\d+) ', line)[0] - noise = 0 - elif "noise:" in line: - noise = re.findall(r'\s*noise:\s+(-*\d+) ', line)[0] - - if noise and freq: - self.device_noise_dict[freq] = noise - freq = 0 - noise = 0 - except (IndexError, TypeError): - self.device_noise_dict = [self.mesh_status.not_avail] - print("ERROR survey") + self.device_noise_list_per_radio = [] + + for interface in self.network_interface: + with subprocess.Popen(['iw', 'dev', interface, 'survey', 'dump'], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) as dump: + + radio_noise_dict = {} + freq = 0 + noise = 0 + + try: + for line in dump.stdout: + line = line.decode("utf-8") + if "frequency:" in line: + freq = re.findall(r'\s*frequency:\s+(\d+) ', line)[0] + noise = 0 + elif "noise:" in line: + noise = re.findall(r'\s*noise:\s+(-*\d+) ', line)[0] + + if noise and freq: + radio_noise_dict[freq] = noise + freq = 0 + noise = 0 + radio_noise_dict["status"] = self.mesh_status.mesh + self.device_noise_list_per_radio.append(radio_noise_dict) + except (IndexError, TypeError): + print("ERROR survey") def _update_station_dump_info(self): """ @@ -95,23 +105,28 @@ def _update_station_dump_info(self): :return: None """ - dump = subprocess.Popen(['iw', 'dev', self.network_interface, 'station', 'dump'], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - - self.device_rssi_list = [] - mac = "" - rssi = [] - for line in dump.stdout: - line = line.decode("utf-8") - if "Station" in line: - mac = line.split(" ")[1] - rssi = [] - elif "signal:" in line: - rssi = re.findall(r'-\d+', line) - if mac and rssi: - self.device_rssi_list.append((mac, rssi)) + self.device_rssi_list_per_radio = [] + + for interface in self.network_interface: + with subprocess.Popen(['iw', 'dev', interface, 'station', 'dump'], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) as dump: + + mac_and_rssi_dict = {} mac = "" + rssi = [] + for line in dump.stdout: + line = line.decode("utf-8") + if "Station" in line: + mac = line.split(" ")[1] + rssi = [] + elif "signal:" in line: + rssi = re.findall(r'-\d+', line) + if mac and rssi: + mac_and_rssi_dict[mac] = rssi + mac = "" + + self.device_rssi_list_per_radio.append(mac_and_rssi_dict) def _update_iw_info(self): """ @@ -120,22 +135,28 @@ def _update_iw_info(self): :return: None """ - dump = subprocess.Popen(['iw', 'dev', self.network_interface, 'info'], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - try: - for line in dump.stdout: - line = line.decode("utf-8") - if "channel" in line: - self.freq = re.findall(r'\((\d+) MHz\)', line)[0] - elif "txpower" in line: - self.txpower = re.findall(r'\d+.\d+', line)[0] - elif "type" in line: - self.iw_state = re.findall(r'type (\w+)', line)[0] - except (IndexError, TypeError): - self.freq = self.mesh_status.not_avail - self.txpower = self.mesh_status.not_avail - self.iw_state = self.mesh_status.not_avail + self.freq = [] + self.txpower = [] + self.iw_state = [] + + for interface in self.network_interface: + + with subprocess.Popen(['iw', 'dev', interface, 'info'], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) as dump: + try: + for line in dump.stdout: + line = line.decode("utf-8") + if "channel" in line: + self.freq.append(str(re.findall(r'\((\d+) MHz\)', line)[0])) + elif "txpower" in line: + self.txpower.append(str(re.findall(r'\d+.\d+', line)[0])) + elif "type" in line: + self.iw_state.append(str(re.findall(r'type (\w+)', line)[0])) + except (IndexError, TypeError): + self.freq.append(self.mesh_status.not_avail) + self.txpower.append(self.mesh_status.not_avail) + self.iw_state.append(self.mesh_status.not_avail) def _update_iw_type(self): """ @@ -144,18 +165,22 @@ def _update_iw_type(self): :return: None """ - if self.iw_state == "managed": - self.status = self.mesh_status.no_config - elif self.iw_state == "AP": - self.status = self.mesh_status.accesspoint - elif self.iw_state == "mesh": - self.status = self.mesh_status.mesh - elif self.iw_state == "IBSS": - self.status = self.mesh_status.mesh - elif self.iw_state == self.mesh_status.not_avail: - self.status = self.mesh_status.not_avail - else: - self.status = self.mesh_status.error + self.status: [str, ...] = [] + + # for loop network_interfaces with index + for index in range(len(self.network_interface)): + if self.iw_state[index] == "managed": + self.status.append(self.mesh_status.no_config) + elif self.iw_state[index] == "AP": + self.status.append(self.mesh_status.accesspoint) + elif self.iw_state[index] == "mesh": + self.status.append(self.mesh_status.mesh) + elif self.iw_state[index] == "IBSS": + self.status.append(self.mesh_status.mesh) + elif self.iw_state[index] == self.mesh_status.not_avail: + self.status.append(self.mesh_status.not_avail) + else: + self.status.append(self.mesh_status.error) def _update_iw_reg(self): """ @@ -164,58 +189,46 @@ def _update_iw_reg(self): :return: None """ - dump = subprocess.Popen(['iw', 'reg', 'get'], stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - try: - for line in dump.stdout: - line = line.decode("utf-8") - if "country" in line: - self.country = re.findall(r'country (\w+)', line)[0] - except (IndexError, TypeError): - self.country = self.mesh_status.not_avail + with subprocess.Popen(['iw', 'reg', 'get'], stdout=subprocess.PIPE, + stderr=subprocess.PIPE) as dump: + try: + for line in dump.stdout: + line = line.decode("utf-8") + if "country" in line: + self.country = re.findall(r'country (\w+)', line)[0] + break + except (IndexError, TypeError): + self.country = self.mesh_status.not_avail @property - def _get_my_mac(self): + def _get_my_mac(self) -> [str, ...]: """ Get device mac :return: mac or not_avail """ self._update_interface_name() - try: - return netifaces.ifaddresses(self.network_interface)[netifaces.AF_PACKET][0]['addr'] - except (ValueError, TypeError, IndexError): - return self.mesh_status.not_avail + macs = [] + for interface in self.network_interface: + try: + macs.append(netifaces.ifaddresses(interface)[netifaces.AF_PACKET][0]['addr']) + except (ValueError, TypeError, IndexError): + return [self.mesh_status.not_avail] + + return macs - def _get_my_rssi(self, mac): + def _get_my_rssi(self, mac: str) -> [str, ...]: """ Get device rssi values :return: rssi value or not_avail """ - result = [r for (m, r) in self.device_rssi_list if m == mac] - if result: - return result[0] + for index in range(len(self.network_interface)): + if mac in self.device_rssi_list_per_radio[index]: + return self.device_rssi_list_per_radio[index][mac] return [self.mesh_status.not_avail] - @property - def status(self): - """ - Status getter - - :return: status - """ - return self._status - - @status.setter - def status(self, new_status): - """ - Status setter - - :return: None - """ - self._status = new_status def _update_device_info(self): self._update_interface_name() # Wi-Fi interface name @@ -237,14 +250,23 @@ def _create_template(self): :return: None """ + noise_per_used_channel = [] + self._update_device_info() + for index in range(0, len(self.network_interface)): + if self.freq[index] in self.device_noise_list_per_radio[index]: + noise_per_used_channel.append( + self.device_noise_list_per_radio[index][self.freq[index]]) + else: + noise_per_used_channel.append(self.mesh_status.not_avail) + self.topology = {'ts': self._timestamp(), # timestamp + 'batman': self.batman_interface, # batman interface name + 'netifs': self.network_interface, # network interfaces 'status': self.status, # "MESH/OFF/NO_CONFIG/ONGOING/AP/" 'my_mac': self._get_my_mac, # e.g. 00:11:22:33:44:55 - 'noise': self.device_noise_dict[self.freq] - if self.freq in self.device_noise_dict - else self.mesh_status.not_avail, + 'noise': noise_per_used_channel, 'freq': self.freq, 'txpower': self.txpower, 'country': self.country, @@ -268,13 +290,13 @@ def update_stat_data(self): route = [] try: - proc = subprocess.Popen(['batctl', 'o'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + with subprocess.Popen(['batctl', 'meshif', self.batman_interface, 'o' , '-H'], + stdout=subprocess.PIPE, stderr=subprocess.PIPE) as proc: - for line in proc.stdout: - device = dict(self.device_template) - aux = line.split() + for line in proc.stdout: + device = dict(self.device_template) + aux = line.split() - if b"Originator" not in aux[0] and b"B.A.T" not in line: # active route check if b"*" in aux[0]: index = 1 @@ -290,7 +312,7 @@ def update_stat_data(self): device['nhr'] = self._get_my_rssi(device['nh']) route.append(device) - self.topology['devices'] = route + self.topology['devices'] = route except FileNotFoundError: self.topology['devices'] = self.device_template # if not succeed, return empty template print("update_stat_data: ERROR") 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 f0d8bc723..14ced4a05 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 @@ -1,16 +1,21 @@ """ Comms command control NATS node """ +# pylint: disable=invalid-name, broad-except, too-many-branches + import json import subprocess import base64 from shlex import quote import hashlib import os -import netifaces as ni +from typing import Tuple, Union, List + +import netifaces as ni # type: ignore from .comms_common import STATUS, COMMAND from .comms_status import CommsStatus +from .comms_interface_info import CommsInterfaces class LogFiles: # pylint: disable=too-few-public-methods @@ -26,8 +31,8 @@ class LogFiles: # pylint: disable=too-few-public-methods # Log files CONTROLLER_LOG = "/opt/comms_controller.log" - WPA_LOG = "/var/log/wpa_supplicant_11s.log" - HOSTAPD_LOG = "/var/log/hostapd.log" + WPA_LOG = "/var/log/wpa_supplicant_11s" # e.g. _id0.log + HOSTAPD_LOG = "/var/log/hostapd" # e.g. _id0.log DMESG_CMD = "dmesg" @@ -35,6 +40,7 @@ class ConfigFiles: # pylint: disable=too-few-public-methods """ ConfigFiles class """ + # Commands WPA = "WPA_CONFIG" HOSTAPD = "HOSTAPD_CONFIG" @@ -43,30 +49,29 @@ class ConfigFiles: # pylint: disable=too-few-public-methods IDENTITY = "/opt/identity" -class Command: # pylint: disable=too-few-public-methods, too-many-instance-attributes +class Command: # pylint: disable=too-many-instance-attributes """ Command class """ - def __init__(self, server, port, comms_status: CommsStatus, logger): + def __init__(self, server, port, comms_status: List[CommsStatus], logger): self.nats_server = server self.port = port self.logger = logger self.api_version = 1 self.command = "" self.param = "" + self.radio_index = "" self.interval = 1 self.comms_status = comms_status - def handle_command(self, msg: str, cc, csa=False, delay="0") -> (str, str, str): + def handle_command(self, msg: str, cc) -> Tuple[str, str, str]: """ handler for commands Args: msg: JSON formatted data from NATS message. cc: CommsController class - csa: bool: True if CSA is active - delay: str: delay for channel change in csa case Returns: str: OK/FAIL @@ -76,16 +81,21 @@ def handle_command(self, msg: str, cc, csa=False, delay="0") -> (str, str, str): data = "" try: parameters = json.loads(msg) - print(parameters) self.api_version = int(parameters["api_version"]) self.command = quote(str(parameters["cmd"])) if "param" in parameters: self.param = quote(str(parameters["param"])) if "interval" in parameters: self.interval = int(parameters["interval"]) + if "radio_index" in parameters: + self.radio_index = parameters["radio_index"] self.logger.debug("Command: %s", self.command) - except (json.decoder.JSONDecodeError, KeyError, - TypeError, AttributeError) as error: + except ( + json.decoder.JSONDecodeError, + KeyError, + TypeError, + AttributeError, + ) as error: ret, info = "FAIL", "JSON format not correct" + str(error) cc.logger.debug("Command failed, %s", info) return ret, info, data @@ -95,7 +105,7 @@ def handle_command(self, msg: str, cc, csa=False, delay="0") -> (str, str, str): elif self.command == COMMAND.revoke: ret, info = self.__revoke(cc) elif self.command == COMMAND.apply: - ret, info = self.__apply_mission_config(csa, delay) + ret, info = self.__apply_mission_config() elif self.command == COMMAND.wifi_down: ret, info = self.__radio_down() elif self.command == COMMAND.wifi_up: @@ -111,12 +121,12 @@ def handle_command(self, msg: str, cc, csa=False, delay="0") -> (str, str, str): elif self.command == COMMAND.get_config: ret, info, data = self.__get_configs(self.param) elif self.command == COMMAND.get_identity: - ret, info, data = self.get_identity() + ret, info, data = self.get_identity() # type: ignore[assignment] else: ret, info = "FAIL", "Command not supported" return ret, info, data - def __revoke(self, cc) -> (str, str): + def __revoke(self, cc) -> Tuple[str, str]: """ Restores device back to warehouse state without flashing. Note! Wi-Fi transmitter gets activates as well even if it @@ -125,51 +135,68 @@ def __revoke(self, cc) -> (str, str): Returns: tuple: (str, str) """ - config_file_path = "/opt/mesh.conf" - hash_file_path = "/opt/mesh.conf_hash" - pending_config_file_path = "/opt/mesh_stored.conf" + config_file_path = f"/opt/{self.radio_index}_mesh.conf" + hash_file_path = f"/opt/{self.radio_index}_mesh.conf_hash" + pending_config_file_path = f"/opt/{self.radio_index}_mesh_stored.conf" if os.path.exists(config_file_path): try: os.remove(config_file_path) - except: - self.logger.error("Failed to delete mission config") + except Exception as e: + self.logger.error("Failed to delete mission config, %s", e) return "FAIL", "Failed to delete mission config" if os.path.exists(pending_config_file_path): try: os.remove(pending_config_file_path) - except: - self.logger.error("Failed to delete pending config") + except Exception as e: + self.logger.error("Failed to delete pending config, %s", e) if os.path.exists(hash_file_path): try: os.remove(hash_file_path) - except: + except Exception as e: # Not fatal - self.logger.debug("Failed to delete hash file") + self.logger.debug("Failed to delete hash file, %s", e) + + processes = [ + "/opt/S9011sNatsMesh", + "/opt/S90APoint", + "/opt/S90nats_discovery", + ] - for process in ["/opt/S9011sNatsMesh", "/opt/S90APoint", "/opt/S90nats_discovery"]: + for process in processes: # Restart mesh with default settings - ret = subprocess.run([process, "restart"], - shell=False, check=True, capture_output=True) + ret = subprocess.run( + [process, "restart", "id" + self.radio_index], + shell=False, + check=True, + capture_output=True, + ) if ret.returncode != 0: self.logger.error("Default mesh starting failed ") - return "FAIL", f"default mesh starting failed {process} " \ - + str(ret.returncode) \ - + str(ret.stdout) \ - + str(ret.stderr) + return ( + "FAIL", + f"default mesh starting failed {process} " + + str(ret.returncode) + + str(ret.stdout) + + str(ret.stderr), + ) self.logger.debug("Default mesh command applied") - if self.comms_status.is_visualisation_active: - ret, _, _ = self.__disable_visualisation(cc) - if ret == "FAIL": - return "FAIL", "Revoke failed partially." \ - + " Visualisation is still active" + + if self.comms_status[int(self.radio_index)].is_visualisation_active: + status, _ = self.__disable_visualisation(cc) + if status == "FAIL": + return ( + "FAIL", + "Revoke failed partially." + + " Visualisation is still active", + ) return "OK", "Mesh settings revoked" - def __apply_mission_config(self, csa=False, delay="0") -> (str, str): + def __apply_mission_config(self) -> Tuple[str, str]: """ Replaces active mesh configuration file with previously stored content and restarts S9011Mesh with new configs. @@ -177,23 +204,33 @@ 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") - except: - self.logger.error("Error replacing active config file!") + os.replace( + f"/opt/{self.radio_index}_mesh_stored.conf", + f"/opt/{self.radio_index}_mesh.conf", + ) + except Exception as e: + self.logger.error("Error replacing active config file! %s", e) return "FAIL", "Error replacing active config file" # Create hash file for active config bookkeeping try: - with open("/opt/mesh.conf", "rb") as f: - data = f.read() + with open(f"/opt/{self.radio_index}_mesh.conf", "rb") as file: + data = file.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", + encoding="utf-8", + ) as f_hash: f_hash.write(hash_hex) - except: - self.logger.error("Error writing hash file!") + except Exception as e: + self.logger.error("Error writing hash file!, %s", e) # Return failure amd give client a possibility to retry # instead of running initd script that could cause loss # of connection. @@ -203,86 +240,93 @@ def __apply_mission_config(self, csa=False, delay="0") -> (str, str): # matches then mission config is applied. Otherwise, default mesh # is applied. That logic is based on assumption that some # wireless connectivity needs to be ensured after reboot. - if csa: - processes = ["/opt/S9011sNatsMesh", "/opt/S90APoint"] - else: - processes = ["/opt/S9011sNatsMesh", "/opt/S90APoint", "/opt/S90nats_discovery"] + processes = [ + "/opt/S9011sNatsMesh", + "/opt/S90APoint", + "/opt/S90nats_discovery", + ] for process in processes: # delay before restarting mesh using delay - if delay != "0": - ret = subprocess.run(["sleep", delay], - shell=False, check=True, - capture_output=True) - ret = subprocess.run([process, "restart"], - shell=False, check=True, - capture_output=True) + ret = subprocess.run( + [process, "restart", "id" + self.radio_index], + shell=False, + check=True, + capture_output=True, + ) if ret.returncode != 0: - return "FAIL", f"mesh starting failed {process}" \ - + str(ret.returncode) \ - + str(ret.stdout) \ - + str(ret.stderr) + return "FAIL", f"mesh starting failed {process}" + str( + ret.returncode + ) + str(ret.stdout) + str(ret.stderr) self.logger.debug("Mission configurations applied") return "OK", "Mission configurations applied" - else: - self.logger.debug("No mission config to apply!") - return "FAIL", "No setting to apply" - def __radio_down(self) -> (str, str): + self.logger.debug("No mission config to apply!") + return "FAIL", "No setting to apply" + + def __radio_down(self) -> Tuple[str, str]: for process in ["/opt/S9011sNatsMesh", "/opt/S90APoint"]: - ret = subprocess.run([process, "stop"], - shell=False, check=True, capture_output=True) + 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") - return "FAIL", f"Radio deactivation failed {process} " \ - + str(ret.returncode) \ - + str(ret.stdout) \ - + str(ret.stderr) + return "FAIL", f"Radio deactivation failed {process} " + str( + ret.returncode + ) + str(ret.stdout) + str(ret.stderr) self.logger.debug("Radio deactivated") return "OK", "Radio deactivated" - def __radio_up(self) -> (str, str): + def __radio_up(self) -> Tuple[str, str]: for process in ["/opt/S9011sNatsMesh", "/opt/S90APoint"]: - ret = subprocess.run([process, "start"], - shell=False, check=True, - capture_output=True) + ret = subprocess.run( + [process, "start", "id" + self.radio_index], + shell=False, + check=True, + capture_output=True, + ) if ret.returncode != 0: self.logger.error("Failed to activate radio") - return "FAIL", f"Radio activation failed {process} " \ - + str(ret.returncode) \ - + str(ret.stdout) \ - + str(ret.stderr) + return "FAIL", f"Radio activation failed {process} " + str( + ret.returncode + ) + str(ret.stdout) + str(ret.stderr) self.logger.debug("Radio activated") return "OK", "Radio activated" - def __enable_visualisation(self, cc) -> (str, str): + def __enable_visualisation(self, cc) -> Tuple[str, str]: try: cc.telemetry.run() - except: - self.logger.error("Failed to enable visualisation") + except Exception as e: + self.logger.error("Failed to enable visualisation, %s", e) 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): + def __disable_visualisation(self, cc) -> Tuple[str, str]: try: cc.telemetry.stop() cc.visualisation_enabled = False - except: - self.logger.error("Failed to disable visualisation") + except Exception as e: + self.logger.error("Failed to disable visualisation, %s", e) 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: + @staticmethod + def __read_log_file(filename) -> bytes: """ read file and return the content as bytes and base64 encoded @@ -290,76 +334,101 @@ def __read_log_file(self, filename) -> bytes: return: (int, bytes) """ # read as bytes as b64encode expects bytes - with open(filename, "rb") as f: - file_log = f.read() + with open(filename, "rb") as file: + file_log = file.read() return base64.b64encode(file_log) - def __get_logs(self, param) -> (str, str, str): + def __get_logs(self, param) -> Tuple[str, str, str]: file = "" try: files = LogFiles() if param == files.WPA: - file_b64 = self.__read_log_file(files.WPA_LOG) + file_b64 = self.__read_log_file( + files.WPA_LOG + "_id" + self.radio_index + ".log" + ) elif param == files.HOSTAPD: - file_b64 = self.__read_log_file(files.HOSTAPD_LOG) + file_b64 = self.__read_log_file( + files.HOSTAPD_LOG + "_id" + self.radio_index + ".log" + ) elif param == files.CONTROLLER: file_b64 = self.__read_log_file(files.CONTROLLER_LOG) elif param == files.DMESG: - ret = subprocess.run([files.DMESG_CMD], - shell=False, check=True, - capture_output=True) + ret = subprocess.run( + [files.DMESG_CMD], + shell=False, + check=True, + capture_output=True, + ) if ret.returncode != 0: - return "FAIL", f"{file} file read failed", None + return "FAIL", f"{file} file read failed", "" file_b64 = base64.b64encode(ret.stdout) else: - return "FAIL", "Log file not supported", None + return "FAIL", "Log file not supported", "" - except: - return "FAIL", f"{param} log reading failed", None + except Exception as e: + self.logger.error("Log reading failed, %s", e) + return "FAIL", f"{param} log reading failed", "" self.logger.debug("__getlogs done") return "OK", "wpa_supplicant log", file_b64.decode() - def __get_configs(self, param) -> (str, str, str): - file_b64 = b'None' + def __get_configs(self, param) -> Tuple[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 if_name: - file_b64 = self.__read_log_file( - f"/var/run/wpa_supplicant-11s_{if_name}.conf") + file_b64 = self.__read_log_file( + f"/var/run/wpa_supplicant-11s_id{self.radio_index}.conf" + ) elif param == files.HOSTAPD: - if_name = self.comms_status.ap_interface_name - if if_name: - file_b64 = self.__read_log_file( - f"/var/run/hostapd-{if_name}.conf") + file_b64 = self.__read_log_file( + f"/var/run/hostapd-{self.radio_index}.conf" + ) else: - return "FAIL", "Parameter not supported", None + return "FAIL", "Parameter not supported", "" - except: - return "FAIL", "Not able to get config file", None + except Exception as e: + self.logger.error("Not able to get identity, %s", e) + return "FAIL", "Not able to get config file", "" self.logger.debug("__get_configs done") - if not if_name: - return "FAIL", f"{param}, interface not active", None - else: - return "OK", f"{param}", file_b64.decode() + return "OK", f"{param}", file_b64.decode() + + def get_identity(self) -> Tuple[str, str, Union[str, dict]]: + """ + Gathers identity, NATS server url and wireless interface + device information and returns that in JSON compatible + dictionary format. + + Returns: + tuple: (str, str, str | dict) -- A tuple that contains + always textual status and description elements. 3rd + element is JSON compatible in normal cases but in + case of failure it is an empty string. + """ - 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() + with open(files.IDENTITY, "rb") as file: + identity = file.read() identity_dict["identity"] = identity.decode().strip() - identity_dict["nats_url"] = f"nats://{ni.ifaddresses('br-lan')[ni.AF_INET][0]['addr']}:4222" - - except: - return "FAIL", "Not able to get identity file", None + # todo hardcoded interface name + # pylint: disable=c-extension-no-member + nats_ip = ni.ifaddresses("br-lan")[ni.AF_INET][0]["addr"] + # pylint: enable=c-extension-no-member + identity_dict["nats_url"] = f"nats://{nats_ip}:4222" + identity_dict[ + "interfaces" + ] = CommsInterfaces().get_wireless_device_info() # type: ignore + except Exception as e: + self.logger.error("Not able to get identity, %s", e) + return "FAIL", "Not able to get identity file", "" self.logger.debug("get_identity done") - return "OK", "Identity and NATS URL", identity_dict \ No newline at end of file + return "OK", "Identity and NATS URL", identity_dict diff --git a/modules/sc-mesh-secure-deployment/src/nats/src/comms_common.py b/modules/sc-mesh-secure-deployment/src/nats/src/comms_common.py index 05263727e..298603bdf 100644 --- a/modules/sc-mesh-secure-deployment/src/nats/src/comms_common.py +++ b/modules/sc-mesh-secure-deployment/src/nats/src/comms_common.py @@ -8,33 +8,33 @@ class STATUS: # pylint: disable=too-few-public-methods """ Comms status values """ - no_status = "MESH_NO_STATUS" # no stat available - mesh_default = "MESH_DEFAULT" # mesh default settings - mesh_default_connected = "MESH_DEFAULT_CONNECTED" # mesh default connected - mesh_cfg_not_stored = "MESH_CONFIGURATION_NOT_STORED" # mesh configuration not stored - mesh_cfg_stored = "MESH_CONFIGURATION_PENDING" # new mission onfiguration stored but not active - mesh_cfg_applied = "MESH_CONFIGURATION_APPLIED" # mission mesh configuration has been applied - mesh_cfg_tampered = "MESH_CONFIGURATION_TAMPERED" # mission mesh configuration file doesn't match with hash - mesh_mission_not_connected = "MESH_MISSION_NOT_CONNECTED" # mesh mission not connected - mesh_mission_connected = "MESH_MISSION_CONNECTED" # mesh mission connected - mesh_fail = "MESH_FAIL" # fails + no_status: str = "MESH_NO_STATUS" # no stat available + mesh_default: str = "MESH_DEFAULT" # mesh default settings + mesh_default_connected: str = "MESH_DEFAULT_CONNECTED" # mesh default connected + mesh_cfg_not_stored: str = "MESH_CONFIGURATION_NOT_STORED" # mesh configuration not stored + mesh_cfg_stored: str = "MESH_CONFIGURATION_PENDING" # new mission onfiguration stored but not active + mesh_cfg_applied: str = "MESH_CONFIGURATION_APPLIED" # mission mesh configuration has been applied + mesh_cfg_tampered: str = "MESH_CONFIGURATION_TAMPERED" # mission mesh configuration file doesn't match with hash + mesh_mission_not_connected: str = "MESH_MISSION_NOT_CONNECTED" # mesh mission not connected + mesh_mission_connected: str = "MESH_MISSION_CONNECTED" # mesh mission connected + mesh_fail: str = "MESH_FAIL" # fails - security_provisioned = "SECURITY_PROVISIONED" # security provisioned - security_non_provisioned = "SECURITY_NON_PROVISIONED" # security non provisioned - security_compromised = "SECURITY_COMPROMISED" + security_provisioned: str = "SECURITY_PROVISIONED" # security provisioned + security_non_provisioned: str = "SECURITY_NON_PROVISIONED" # security non provisioned + security_compromised: str = "SECURITY_COMPROMISED" # TBD add more status class COMMAND: # pylint: disable=too-few-public-methods """ Comms control commands """ - revoke = "REVOKE" # Activate default mesh and revoke/delete mission keys - apply = "APPLY" # Take mission config into use - wifi_down = "DOWN" # Deactivate Wi-Fi transmitter - wifi_up = "UP" # Activate Wi-Fi transmitter - reboot = "REBOOT" # Reboot comms module - get_logs = "LOGS" # Command to send basic logs as response - enable_visualisation = "ENABLE_VISUALISATION" - disable_visualisation = "DISABLE_VISUALISATION" - get_config = "GET_CONFIG" - get_identity = "GET_IDENTITY" + revoke: str = "REVOKE" # Activate default mesh and revoke/delete mission keys + apply: str = "APPLY" # Take mission config into use + wifi_down: str = "DOWN" # Deactivate Wi-Fi transmitter + wifi_up: str = "UP" # Activate Wi-Fi transmitter + reboot: str = "REBOOT" # Reboot comms module + get_logs: str = "LOGS" # Command to send basic logs as response + enable_visualisation: str = "ENABLE_VISUALISATION" + disable_visualisation: str = "DISABLE_VISUALISATION" + get_config: str = "GET_CONFIG" + get_identity: str = "GET_IDENTITY" diff --git a/modules/sc-mesh-secure-deployment/src/nats/src/comms_interface_info.py b/modules/sc-mesh-secure-deployment/src/nats/src/comms_interface_info.py new file mode 100644 index 000000000..39adb96af --- /dev/null +++ b/modules/sc-mesh-secure-deployment/src/nats/src/comms_interface_info.py @@ -0,0 +1,215 @@ +""" +This module contains CommsInterfaces class which provides +an interface to get a list wireless interfaces and their +properties. +""" +# pylint: disable=too-few-public-methods, too-many-locals + +import os +import subprocess +from typing import Tuple, List, Dict + + +class CommsInterfaceInfo: + """ + A class to store wireless interface device information. + """ + + def __init__(self): + self.if_name: str = "" + self.mac: str = "" + self.bus_type: str = "" + self.driver: str = "" + self.vendor_id: str = "" + self.device_id: str = "" + + +class CommsInterfaces: + """ + A class to gather a list if wireless interfaces and their properties. + """ + + def __init__(self): + self.interfaces: List[CommsInterfaceInfo] = [] + + def __get_wireless_device_info(self): + """ + Gathers a list of available wlp* and halow* interface devices and their + properties. + + Arguments: + None + + Returns: + None + """ + net_path = "/sys/class/net" + device_names = os.listdir(net_path) + + self.interfaces.clear() + + for device_name in device_names: + if_info = CommsInterfaceInfo() + device_path = os.path.join(net_path, device_name) + + if "-" in device_name: + continue + + if device_name.startswith("wlp") or device_name.startswith( + "halow" + ): + if_info.if_name = device_name + # Read MAC address + mac_address_file = os.path.join(device_path, "address") + with open(mac_address_file, "r", encoding="utf-8") as file: + if_info.mac = file.read().strip() + + # Determine bus type + if "pci" in os.readlink(device_path): + if_info.bus_type = "PCI" + elif ".usb" in os.readlink( + device_path + ) and "spi" in os.readlink(device_path): + if_info.bus_type = "USB+SPI" + elif "spi" in os.readlink(device_path): + if_info.bus_type = "SPI" + + # Get driver information + uevent_file = os.path.join(device_path, "device", "uevent") + with open(uevent_file, "r", encoding="utf-8") as file: + lines = file.readlines() + for line in lines: + if line.startswith("DRIVER="): + if_info.driver = line.split("=")[1].strip() + + # Get vendor information if available + vendor_file = os.path.join(device_path, "device", "vendor") + try: + with open(vendor_file, "r", encoding="utf-8") as file: + if_info.vendor_id = file.read().strip() + except FileNotFoundError: + if_info.vendor_id = "N/A" + + # Get device information if available + device_file = os.path.join(device_path, "device", "device") + try: + with open(device_file, "r", encoding="utf-8") as file: + if_info.device_id = file.read().strip() + except FileNotFoundError: + if_info.device_id = "N/A" + + # 2nd approach to get vendor and device id for HaLow device + if if_info.vendor_id == "N/A" or if_info.device_id == "N/A": + if if_info.if_name.startswith("halow"): + ( + if_info.vendor_id, + if_info.device_id, + ) = self.__get_halow_vendor_dev_info(device_path) + + self.interfaces.append(if_info) + # end of method + + @staticmethod + def __get_halow_vendor_dev_info(dev_path: str) -> Tuple[str, str]: + """ + Checks from HaLow device's link information and in case it is connected + to USB then vendor_id and device_id information is read from USB device + properties. + + Arguments: + dev_path (str) -- Halow device path under /sys/class/net/. + + Returns: + Tuple[str, str] -- A tuple that contains vendor_id and device_id. + """ + vendor_id = "N/A" + device_id = "N/A" + found_usb_bus = False + bus_no = "N/A" + port_no = "N/A" + sub_port = "N/A" + dev_no = "N/A" + + try: # pylint: disable=too-many-nested-blocks + link_info = os.readlink(dev_path) + if "halow" in link_info and ".usb" in link_info: + # Find the index of ".usb" + usb_index = link_info.find(".usb") + if usb_index != -1: + usb_path = link_info[usb_index + 1:] + parts = usb_path.split("/") + + if len(parts) >= 3: + bus_no = parts[1][3:] + port_no = parts[2][2:] + sub_port = parts[3][4:] + + first_match = f"/: Bus 0{bus_no}.Port {port_no}" + second_match = f"|__ Port {sub_port}:" + + lsusb_t_output = subprocess.check_output( + ["lsusb", "-t"] + ).decode("utf-8") + lsusb_t_lines = lsusb_t_output.split("\n") + for line in lsusb_t_lines: + if first_match in line: + found_usb_bus = True + continue + if found_usb_bus and second_match in line: + parts = line.split(": ") + for part in parts: + if "Dev" in part: + sub_parts = part.split(",") + dev_parts = sub_parts[0].split(" ") + dev_no = dev_parts[1] + break + # exit loop + break + + # Now get the vendor and device id using lsusb + bus_no = bus_no.zfill(3) + dev_no = dev_no.zfill(3) + match_criteria = f"Bus {bus_no} Device {dev_no}" + + lsusb_output = subprocess.check_output(["lsusb"]).decode( + "utf-8" + ) + lsusb_lines = lsusb_output.split("\n") + + for line in lsusb_lines: + if match_criteria in line: + parts = line.split(" ") + vendor_and_dev_id = parts[5].split(":") + vendor_id = vendor_and_dev_id[0] + device_id = vendor_and_dev_id[1] + break + except Exception: # pylint: disable = broad-except + pass + return vendor_id, device_id + + def get_wireless_device_info(self) -> List[Dict[str, str]]: + """ + Returns a list of wireless interface device properties in JSON + compatible format. + + Arguments: + None + + Returns: + List[Dict[str, str]]: -- A list of interaface dictionaries. + """ + # Refresh radio interfaces list each time + self.__get_wireless_device_info() + info_list = [] + + for device in self.interfaces: + info = { + "interface_name": device.if_name, + "mac_address": device.mac, + "bus_type": device.bus_type, + "driver": device.driver, + "vendor_id": device.vendor_id, + "device_id": device.device_id, + } + info_list.append(info) + return info_list 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 9b7f47b3f..0f73375e3 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,9 @@ """ import json from shlex import quote -import subprocess +import logging +import re +import os try: import comms_common as comms @@ -20,269 +22,407 @@ class CommsSettings: # pylint: disable=too-few-public-methods, too-many-instanc Comms settings class """ - def __init__(self, comms_status: cs.CommsStatus, logger): - self.logger = logger - self.api_version = 1 - self.ssid = "" - self.key = "" - self.ap_mac = "" - self.country = "" - self.frequency = "" - self.frequency_mcc = "" - self.ip_address = "" - self.subnet = "" - self.tx_power = "" - self.mode = "" - self.routing = "" - self.priority = "" - self.role = "" - self.mesh_vif = "" - self.phy = "" - self.msversion = "" - self.delay = "" # delay for channel change + def __init__(self, comms_status: [cs.CommsStatus, ...], logger): + self.logger: logging = logger + self.api_version: int = 1 + self.radio_index = [] + self.ssid = [] + self.key = [] + self.ap_mac = [] + self.country = [] + self.frequency = [] + self.frequency_mcc = [] + self.ip_address = [] + self.subnet = [] + self.tx_power = [] + self.mode = [] + self.routing = [] + self.priority = [] + self.role: str = "" + self.mesh_vif = [] + # self.phy = [] + self.batman_iface = [] + self.bridge: str = "" + self.msversion: str = "" + self.delay: str = "" # delay for channel change self.comms_status = comms_status - self.csa_state = 0 # 0: not started, 1: stored, 2: triggered - self.csa_count = 0 # number of CSA triggered - self.device_amount = "0" + # TODO: check can we do this + ret, info = self.__load_settings() + self.logger.debug("load settings: %s, %s", ret, info) - def validate_mesh_settings(self) -> (str, str): + def validate_mesh_settings(self, index: int) -> (str, str): """ Validate mesh settings """ + + self.logger.debug("validate mesh settings") + # pylint: disable=too-many-return-statements - if validation.validate_ssid(self.ssid) is False: + if validation.validate_ssid(self.ssid[index]) is False: return "FAIL", "Invalid SSID" + self.logger.debug("validate mesh settings ssid ok") - if validation.validate_wpa3_psk(self.key) is False: + if validation.validate_wpa3_psk(self.key[index]) is False: return "FAIL", "Invalid WPA3 PSK" + self.logger.debug("validate mesh settings wpa3 ok") - if validation.validate_ip_address(self.ip_address) is False: + if validation.validate_ip_address(self.ip_address[index]) is False: return "FAIL", "Invalid IP address" + self.logger.debug("validate mesh settings ip ok") - if validation.validate_mode(self.mode) is False: + if validation.validate_mode(self.mode[index]) is False: return "FAIL", "Invalid mode" + self.logger.debug("validate mesh settings mode ok") - if validation.validate_frequency(int(self.frequency)) is False: + if validation.validate_frequency(int(self.frequency[index])) is False: return "FAIL", "Invalid frequency" + self.logger.debug("validate mesh settings freq ok") - if validation.validate_frequency(int(self.frequency_mcc)) is False: + if validation.validate_frequency(int(self.frequency_mcc[index])) is False: return "FAIL", "Invalid mcc frequency" + self.logger.debug("validate mesh settings mcc freq ok") - if validation.validate_country_code(self.country) is False: + if validation.validate_country_code(self.country[index]) is False: return "FAIL", "Invalid country code" + self.logger.debug("validate mesh settings country ok") - if validation.validate_netmask(self.subnet) is False: + if validation.validate_netmask(self.subnet[index]) is False: return "FAIL", "Invalid subnet" + self.logger.debug("validate mesh settings subnet ok") - if validation.validate_tx_power(int(self.tx_power)) is False: + if validation.validate_tx_power(int(self.tx_power[index])) is False: return "FAIL", "Invalid tx power" + self.logger.debug("validate mesh settings tx power ok") - if validation.validate_routing(self.routing) is False: + if validation.validate_routing(self.routing[index]) is False: return "FAIL", "Invalid routing algo" + self.logger.debug("validate mesh settings routing ok") - if validation.validate_priority(self.priority) is False: + if validation.validate_priority(self.priority[index]) is False: return "FAIL", "Invalid priority" + self.logger.debug("validate mesh settings priority ok") if validation.validate_role(self.role) is False: return "FAIL", "Invalid role" + self.logger.debug("validate mesh settings role ok") + + if validation.validate_mesh_vif(self.mesh_vif[index]) is False: + return "FAIL", "Invalid mesh vif" + self.logger.debug("validate mesh settings mesh vif ok") + + # if validation.validate_phy(self.phy[index]) is False: + # return "FAIL", "Invalid phy" + # self.logger.debug("validate mesh settings phy ok") + + if validation.validate_batman_iface(self.batman_iface[index]) is False: + return "FAIL", "Invalid batman iface" + self.logger.debug("validate mesh settings batman iface ok") return "OK", "Mesh settings OK" - def handle_mesh_settings_csa(self, msg: str, path="/opt", - file="mesh_stored.conf") -> (str, str, str): + def __clean_all_settings(self) -> None: + """ + Clean all settings + """ + self.radio_index: [int, ...] = [] + self.ssid: [str, ...] = [] + self.key: [str, ...] = [] + self.ap_mac: [str, ...] = [] + self.country: [str, ...] = [] + self.frequency: [str, ...] = [] + self.frequency_mcc: [str, ...] = [] + self.ip_address: [str, ...] = [] + self.subnet: [str, ...] = [] + self.tx_power: [str, ...] = [] + self.mode: [str, ...] = [] + self.routing: [str, ...] = [] + self.priority: [str, ...] = [] + self.mesh_vif: [str, ...] = [] + # self.phy = [] + self.batman_iface: [str, ...] = [] + + def handle_mesh_settings_channel_change( + self, msg: str, path="/opt", file="mesh_stored.conf" + ) -> (str, str, str): """ Handle mesh settings """ try: parameters = json.loads(msg) - if "status" in parameters and self.csa_state == 1: - self.csa_count = self.csa_count + 1 - if self.csa_count >= int(self.device_amount): - self.csa_state = 2 - self.logger.debug(f"Trigger channel switch to {self.frequency}") - return "TRIGGER", "Channel switch triggered", self.delay - return "COUNT", "Channel switch count", self.delay - - self.csa_state = 0 + ret, info = self.__load_settings() self.logger.debug("load settings: %s, %s", ret, info) - #self.api_version = int(parameters["api_version"]) - self.frequency, self.delay, self.device_amount= map(quote, - (str(parameters["frequency"]), - str(parameters["delay"]), - str(parameters["amount"]))) + freq, radio_index = map( + quote, (str(parameters["frequency"]), str(parameters["radio_index"])) + ) - if validation.validate_delay(self.delay) and validation.validate_frequency(int(self.frequency)): - ret, info = "OK", "CSA settings OK" + if validation.validate_radio_index( + radio_index + ) and validation.validate_frequency(int(freq)): + self.frequency[int(radio_index)] = freq + ret, info = "TRIGGER", "Channel change settings OK" else: - ret, info = "FAIL", "Invalid delay or frequency" + ret, info = ( + "FAIL", + f"Invalid radio_index or frequency index {radio_index}", + ) self.logger.debug(" settings validation: %s, %s", ret, info) + if ret == "FAIL": - self.comms_status.mesh_cfg_status = \ - comms.STATUS.mesh_cfg_not_stored + self.comms_status[ + int(radio_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) - self.logger.debug("save settings: %s, %s", ret, info) - self.csa_state = 1 - self.csa_count = 0 - - return ret, info, self.delay - - except (json.decoder.JSONDecodeError, KeyError, - TypeError, AttributeError) as error: - self.comms_status.mesh_cfg_status = \ - comms.STATUS.mesh_cfg_not_stored + ret, info = self.__save_settings(path, file, int(radio_index)) + if ret == "OK": + ret = "TRIGGER" + self.logger.debug("save settings for channel change: %s, %s", ret, info) + return ret, info, str(radio_index) + + except ( + json.decoder.JSONDecodeError, + KeyError, + TypeError, + AttributeError, + ) as error: + try: + radio_index + except NameError: + radio_index = 0 + self.comms_status[ + radio_index + ].mesh_cfg_status = comms.STATUS.mesh_cfg_not_stored ret, info = "FAIL", "JSON format not correct" + str(error) self.logger.error("csa settings validation: %s, %s", ret, info) - return ret, info, self.delay + return ret, info, "-1" - def handle_mesh_settings(self, msg: str, path="/opt", - file="mesh_stored.conf") -> (str, str): + def handle_mesh_settings( + self, msg: str, path="/opt", file="mesh_stored.conf" + ) -> (str, str): """ Handle mesh settings """ try: - parameters = json.loads(msg) - print(parameters) + parameters_set = json.loads(msg) + self.msversion = "nats" - self.api_version = int(parameters["api_version"]) - self.ssid = quote(str(parameters["ssid"])) - self.key = quote(str(parameters["key"])) - self.ap_mac = quote(str(parameters["ap_mac"])) - self.country = quote(str(parameters["country"]).lower()) - self.frequency = quote(str(parameters["frequency"])) - self.frequency_mcc = quote(str(parameters["frequency_mcc"])) - self.ip_address = quote(str(parameters["ip"])) - self.subnet = quote(str(parameters["subnet"])) - self.tx_power = quote(str(parameters["tx_power"])) - self.mode = quote(str(parameters["mode"])) - self.routing = quote(str(parameters["routing"])) - self.priority = quote(str(parameters["priority"])) - self.role = quote(str(parameters["role"])) - # not currently in json mesh_settings - self.mesh_vif = "wlp1s0" - # not currently in json mesh_settings - self.phy = "phy0" - - ret, info = self.validate_mesh_settings() - 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) - - 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 + self.api_version = int(parameters_set["api_version"]) + self.role = quote(str(parameters_set["role"])) + + self.__clean_all_settings() + + # sort radios by index + parameters_set["radios"] = sorted( + parameters_set["radios"], key=lambda k: k.get("radio_index", 0) + ) + + for parameters in parameters_set["radios"]: + self.radio_index.append(int(parameters["radio_index"])) + self.ssid.append(quote(str(parameters["ssid"]))) + self.key.append(quote(str(parameters["key"]))) + self.ap_mac.append(quote(str(parameters["ap_mac"]))) + self.country.append(quote(str(parameters["country"]).lower())) + self.frequency.append(quote(str(parameters["frequency"]))) + self.frequency_mcc.append(quote(str(parameters["frequency_mcc"]))) + self.ip_address.append(quote(str(parameters["ip"]))) + self.subnet.append(quote(str(parameters["subnet"]))) + self.tx_power.append(quote(str(parameters["tx_power"]))) + self.mode.append(quote(str(parameters["mode"]))) + self.routing.append(quote(str(parameters["routing"]))) + self.priority.append(quote(str(parameters["priority"]))) + self.mesh_vif.append(quote(str(parameters["mesh_vif"]))) + # self.phy.append(quote(str(parameters["phy"]))) + self.batman_iface.append(quote(str(parameters["batman_iface"]))) + + self.bridge = quote(str(parameters_set["bridge"])) + + for index in self.radio_index: + self.logger.debug("Mesh settings validation index: %s", str(index)) + ret, info = self.validate_mesh_settings(index) + + 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( + "Settings validation failed: %s, %s, id %s", + ret, + info, + str(index), + ) + _, _ = self.__load_settings() # to restore cleaned settings + return ret, info + ", id " + str(index) + + # separate loop to avoid saving if validation fails + for index in self.radio_index: + ret, info = self.__save_settings(path, file, index) + self.logger.debug( + "save settings index %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, id %s", ret, info, str(index) + ) + _, _ = self.__load_settings() # to restore cleaned settings + return ret, info + ", id " + str(index) + + except ( + json.decoder.JSONDecodeError, + KeyError, + TypeError, + AttributeError, + ) as error: + try: + index + except NameError: + index = 0 + self.comms_status[index].mesh_cfg_status = comms.STATUS.mesh_cfg_not_stored + ret, _ = "FAIL", self.comms_status[index].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"MSVERSION={quote(self.msversion)}\n") - mesh_conf.write(f"MODE={quote(self.mode)}\n") - mesh_conf.write(f"IP={quote(self.ip_address)}\n") - mesh_conf.write(f"MASK={quote(self.subnet)}\n") - mesh_conf.write(f"MAC={quote(self.ap_mac)}\n") - mesh_conf.write(f"KEY={quote(self.key)}\n") - mesh_conf.write(f"ESSID={quote(self.ssid)}\n") - mesh_conf.write(f"FREQ={quote(self.frequency)}\n") - mesh_conf.write(f"FREQ_MCC={quote(self.frequency_mcc)}\n") - mesh_conf.write(f"TXPOWER={quote(self.tx_power)}\n") - mesh_conf.write(f"COUNTRY={quote(self.country).upper()}\n") - mesh_conf.write(f"ROUTING={quote(self.routing)}\n") mesh_conf.write(f"ROLE={quote(self.role)}\n") - mesh_conf.write(f"PRIORITY={quote(self.priority)}\n") - # not currently in json mesh_settings - mesh_conf.write(f"MESH_VIF={quote(self.mesh_vif)}\n") - # not currently in json mesh_settings - mesh_conf.write(f"PHY={quote(self.phy)}\n") + mesh_conf.write(f"MSVERSION={quote(self.msversion)}\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 = \ - comms.STATUS.mesh_cfg_not_stored - self.logger.error("not able to write new %s", file) + self.comms_status[index].mesh_cfg_status = comms.STATUS.mesh_cfg_not_stored + 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): - import re - pattern = r'(\w+)=(.*?)(?:\s*#.*)?$' + 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): """ Load mesh settings return: OK, FAIL """ - config_file_path = "/opt/mesh_default.conf" - mission_config_file_path = "/opt/mesh.conf" + config_file_path: str = "/opt/mesh_default.conf" + + self.__clean_all_settings() 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: + if index == 0: + self.logger.debug( + "mesh config file %s not found, loading default", file + ) + with open(config_file_path, "r", encoding="utf-8") as mesh_conf: + mesh_conf_lines = mesh_conf.read() + self.__read_configs(mesh_conf_lines) + else: + self.logger.debug("index not supported for default: %s", index) + 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 a900f2702..e9015578e 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,146 +6,163 @@ import copy import hashlib import os +from logging import Logger + 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 = "" - self.bssid = "" - self.freq = "" - self.ssid = "" - self.id = "" - self.mode = "" - self.pairwise_cipher = "UNKNOWN" - self.group_cipher = "UNKNOWN" - self.key_mgmt = "UNKNOWN" - self.wpa_state = "UNKNOWN" - self.address = "" - self.uuid = "" + self.interface: str = "" + self.bssid: str = "" + self.freq: str = "" + self.ssid: str = "" + self.id: str = "" + self.mode: str = "" + self.pairwise_cipher: str = "UNKNOWN" + self.group_cipher: str = "UNKNOWN" + self.key_mgmt: str = "UNKNOWN" + self.wpa_state: str = "UNKNOWN" + self.address: str = "" + self.uuid: str = "" def reset(self): """ Reset wpa_supplicant status """ - self.interface = "" - self.bssid = "" - self.freq = "" - self.ssid = "" - self.id = "" - self.mode = "" - self.pairwise_cipher = "UNKNOWN" - self.group_cipher = "UNKNOWN" - self.key_mgmt = "UNKNOWN" - self.wpa_state = "INTERFACE_DISABLED" - self.address = "" - self.uuid = "" + self.interface: str = "" + self.bssid: str = "" + self.freq: str = "" + self.ssid: str = "" + self.id: str = "" + self.mode: str = "" + self.pairwise_cipher: str = "UNKNOWN" + self.group_cipher: str = "UNKNOWN" + self.key_mgmt: str = "UNKNOWN" + self.wpa_state: str = "INTERFACE_DISABLED" + self.address: str = "" + self.uuid: str = "" def __eq__(self, other): if isinstance(other, CommsStatus.WpaStatus): - return self.interface == other.interface and \ - self.bssid == other.bssid and \ - self.freq == other.freq and \ - self.ssid == other.ssid and \ - self.id == other.id and \ - 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.address == other.address and \ - self.uuid == other.uuid + return ( + self.interface == other.interface + and self.bssid == other.bssid + and self.freq == other.freq + and self.ssid == other.ssid + and self.id == other.id + and 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.address == other.address + and self.uuid == other.uuid + ) return False class HostapdStatus: """ Maintains hostapd status """ + def __init__(self): - self.interface = "" - self.state = "DISABLED" - self.phy = "" - self.freq = "" - self.channel = "" - self.beacon_int = "" - self.ssid = "" + self.interface: str = "" + self.state: str = "DISABLED" + self.phy: str = "" + self.freq: str = "" + self.channel: str = "" + self.beacon_int: str = "" + self.ssid: str = "" def reset(self): """ Reset hostapd status """ - self.interface = "" - self.state = "DISABLED" - self.phy = "" - self.freq = "" - self.channel = "" - self.beacon_int = "" - self.ssid = "" + self.interface: str = "" + self.state: str = "DISABLED" + self.phy: str = "" + self.freq: str = "" + self.channel: str = "" + self.beacon_int: str = "" + self.ssid: str = "" def __eq__(self, other): if isinstance(other, CommsStatus.HostapdStatus): - return self.interface == other.interface and \ - self.state == other.state and \ - self.phy == other.phy and \ - self.freq == other.freq and \ - self.channel == other.channel and \ - self.beacon_int == other.beacon_int and \ - self.ssid == other.ssid + return ( + self.interface == other.interface + and self.state == other.state + and self.phy == other.phy + and self.freq == other.freq + and self.channel == other.channel + and self.beacon_int == other.beacon_int + and self.ssid == other.ssid + ) return False - def __init__(self, logger): + def __init__(self, logger: Logger, index: int): + self.index: int = index + self.wifi_interface: str = "" self.__lock = threading.Lock() - self.__thread_running = False + self.__thread_running: bool = False self.__logger = logger self.__wpa_status = self.WpaStatus() self.__old_wpa_status = copy.copy(self.__wpa_status) self.__hostapd_status = self.HostapdStatus() self.__old_hostapd_status = copy.copy(self.__hostapd_status) - self.__mesh_status = STATUS.no_status - self.__mesh_cfg_status = STATUS.mesh_default - self.__security_status = STATUS.security_non_provisioned - self.__is_mission_cfg = False # True when mission cfg has been applied - self.__is_mesh_radio_on = True # True since mesh is started via initd - self.__is_visualisation_active = False - self.__is_hash_file = False - self.__is_ap_radio_on = False + self.__mesh_status: str = STATUS.no_status + self.__mesh_cfg_status: str = STATUS.mesh_default + self.__security_status: str = STATUS.security_non_provisioned + self.__is_mission_cfg: bool = ( + False # True when mission cfg has been applied + ) + self.__is_mesh_radio_on: bool = ( + True # True since mesh is started via initd + ) + self.__is_visualisation_active: bool = False + self.__is_hash_file: bool = False + self.__is_ap_radio_on: bool = False # Refresh status self.__update_status() @property - def security_status(self): + def security_status(self) -> str: """ Get security status """ return self.__security_status @property - def mesh_status(self): + def mesh_status(self) -> str: """ Get mesh status """ return self.__mesh_status @property - def mesh_cfg_status(self): + def mesh_cfg_status(self) -> str: """ Get mesh configuration status """ return self.__mesh_cfg_status @mesh_cfg_status.setter - def mesh_cfg_status(self, status: STATUS): + def mesh_cfg_status(self, status: str): """ Set mesh configuration status """ - if status is STATUS.mesh_cfg_stored \ - or status is STATUS.mesh_cfg_not_stored: + if ( + status is STATUS.mesh_cfg_stored + or status is STATUS.mesh_cfg_not_stored + ): self.__mesh_cfg_status = status @property @@ -209,7 +226,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. @@ -263,8 +280,10 @@ def __update_status(self): # pylint: disable=too-many-branches self.__logger.debug(error) self.__hostapd_status.reset() - if self.__hostapd_status.state == "DISABLED" or \ - self.__hostapd_status == "UNINITIALIZED": + if ( + self.__hostapd_status.state == "DISABLED" + or self.__hostapd_status == "UNINITIALIZED" + ): self.__is_ap_radio_on = False else: # ENABLED, COUNTRY_UPDATE, ACS, HT_SCAN, DFS self.__is_ap_radio_on = True @@ -277,8 +296,9 @@ def __update_status(self): # pylint: disable=too-many-branches # /etc/ssl/certs/comms_auth_cert.pem and /etc/ssl/certs/root-ca.cert.pem files # are used to check whether security is provisioned or not. # If those files are missing, security is not provisioned. - if os.path.exists("/etc/ssl/certs/comms_auth_cert.pem") and \ - os.path.exists("/etc/ssl/certs/root-ca.cert.pem"): + if os.path.exists( + "/etc/ssl/certs/comms_auth_cert.pem" + ) and os.path.exists("/etc/ssl/certs/root-ca.cert.pem"): self.__security_status = STATUS.security_provisioned else: self.__security_status = STATUS.security_non_provisioned @@ -286,35 +306,54 @@ 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"] - awk_command = ["awk", '{print $1}'] + try: + grep_command = [ + "grep", + "-E", + f"[w]pa_supplicant-11s_id{str(self.index)}.conf", + ] + except IndexError: + self.__logger.error("IndexError: index=%s", str(self.index)) + return "" + awk_command = ["awk", "{print $1}"] ps_process = subprocess.Popen(ps_command, stdout=subprocess.PIPE) - grep_process = subprocess.Popen(grep_command, stdin=ps_process.stdout, - stdout=subprocess.PIPE) - awk_process = subprocess.Popen(awk_command, stdin=grep_process.stdout, - stdout=subprocess.PIPE) + grep_process = subprocess.Popen( + grep_command, stdin=ps_process.stdout, stdout=subprocess.PIPE + ) + awk_process = subprocess.Popen( + awk_command, stdin=grep_process.stdout, stdout=subprocess.PIPE + ) # Wait for completion and store the output output, error = awk_process.communicate() if error: - raise RuntimeError("Error getting wpa_supplicant PID: {}".format(error)) + raise RuntimeError( + f"Error getting wpa_supplicant PID: {str(error)}" + ) + + self.__logger.debug("wpa_supplicant PID: %s", output.decode().strip()) return output.decode() def __get_wpa_cli_status(self): """ Get wpa_supplicant states via wpa_cli tool. """ - wpa_cli_command = ["wpa_cli", "status"] - proc = subprocess.Popen(wpa_cli_command, - stdout=subprocess.PIPE, stderr=subprocess.PIPE) + wpa_cli_command = [ + "wpa_cli", + "-p", + f"/var/run/wpa_supplicant_id{str(self.index)}/", + "status", + ] + proc = subprocess.Popen( + wpa_cli_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE + ) output, error = proc.communicate() if error: raise RuntimeError("Error from wpa_cli: {}".format(error)) @@ -322,7 +361,7 @@ def __get_wpa_cli_status(self): for line in output_lines: if "Selected interface " in line: interface = line.split("Selected interface ")[1].strip() - self.__wpa_status.interface = interface.strip("\'") + self.__wpa_status.interface = interface.strip("'") elif "bssid=" in line: self.__wpa_status.bssid = line.split("bssid=")[1].strip() elif "freq=" in line: @@ -334,13 +373,19 @@ def __get_wpa_cli_status(self): elif "mode=" in line: self.__wpa_status.mode = line.split("mode=")[1].strip() elif "pairwise_cipher=" in line: - self.__wpa_status.pairwise_cipher = line.split("pairwise_cipher=")[1].strip() + self.__wpa_status.pairwise_cipher = line.split( + "pairwise_cipher=" + )[1].strip() elif "group_cipher=" in line: - self.__wpa_status.group_cipher = line.split("group_cipher=")[1].strip() + self.__wpa_status.group_cipher = line.split("group_cipher=")[ + 1 + ].strip() elif "key_mgmt=" in line: self.__wpa_status.key_mgmt = line.split("key_mgmt=")[1].strip() elif "wpa_state=" in line: - self.__wpa_status.wpa_state = line.split("wpa_state=")[1].strip() + self.__wpa_status.wpa_state = line.split("wpa_state=")[ + 1 + ].strip() elif "address=" in line: self.__wpa_status.address = line.split("address=")[1].strip() elif "uuid=" in line: @@ -356,8 +401,12 @@ def __get_wpa_cli_status(self): self.__logger.debug("ssid=%s", self.__wpa_status.ssid) self.__logger.debug("id=%s", self.__wpa_status.id) self.__logger.debug("mode=%s", self.__wpa_status.mode) - self.__logger.debug("pairwise_cipher=%s", self.__wpa_status.pairwise_cipher) - self.__logger.debug("group_cipher=%s", self.__wpa_status.group_cipher) + self.__logger.debug( + "pairwise_cipher=%s", self.__wpa_status.pairwise_cipher + ) + self.__logger.debug( + "group_cipher=%s", self.__wpa_status.group_cipher + ) self.__logger.debug("key_mgmt=%s", self.__wpa_status.key_mgmt) self.__logger.debug("wpa_state=%s", self.__wpa_status.wpa_state) self.__logger.debug("address=%s", self.__wpa_status.address) @@ -371,18 +420,20 @@ def __get_hostapd_pid() -> str: # Run commands in pieces ps_command = ["ps", "ax"] grep_command = ["grep", "-E", r"hostapd\-"] - awk_command = ["awk", '{print $1}'] + awk_command = ["awk", "{print $1}"] ps_process = subprocess.Popen(ps_command, stdout=subprocess.PIPE) - grep_process = subprocess.Popen(grep_command, stdin=ps_process.stdout, - stdout=subprocess.PIPE) - awk_process = subprocess.Popen(awk_command, stdin=grep_process.stdout, - stdout=subprocess.PIPE) + grep_process = subprocess.Popen( + grep_command, stdin=ps_process.stdout, stdout=subprocess.PIPE + ) + awk_process = subprocess.Popen( + awk_command, stdin=grep_process.stdout, stdout=subprocess.PIPE + ) # Wait for completion and store the output output, error = awk_process.communicate() if error: - raise RuntimeError("Error getting hostapd PID: {}".format(error)) + raise RuntimeError("Error getting hostapd PID: {!r}".format(error)) return output.decode() def __get_hostapd_cli_status(self): @@ -390,8 +441,9 @@ def __get_hostapd_cli_status(self): Get wpa_supplicant states via wpa_cli tool. """ hostapd_cli_command = ["hostapd_cli", "status"] - proc = subprocess.Popen(hostapd_cli_command, - stdout=subprocess.PIPE, stderr=subprocess.PIPE) + proc = subprocess.Popen( + hostapd_cli_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE + ) output, error = proc.communicate() if error: raise RuntimeError("Error from hostapd_cli: {}".format(error)) @@ -399,7 +451,7 @@ def __get_hostapd_cli_status(self): for line in output_lines: if "Selected interface " in line: interface = line.split("Selected interface ")[1].strip() - self.__hostapd_status.interface = interface.strip("\'") + self.__hostapd_status.interface = interface.strip("'") elif "state=" in line: self.__hostapd_status.state = line.split("state=")[1].strip() elif "phy=" in line: @@ -407,9 +459,13 @@ def __get_hostapd_cli_status(self): elif "freq=" in line: self.__hostapd_status.freq = line.split("freq=")[1].strip() elif "channel=" in line: - self.__hostapd_status.channel = line.split("channel=")[1].strip() + self.__hostapd_status.channel = line.split("channel=")[ + 1 + ].strip() elif "beacon_int=" in line: - self.__hostapd_status.beacon_int = line.split("beacon_int=")[1].strip() + self.__hostapd_status.beacon_int = line.split("beacon_int=")[ + 1 + ].strip() elif "ssid[0]=" in line and "bssid[0]" not in line: self.__hostapd_status.ssid = line.split("ssid[0]=")[1].strip() @@ -417,12 +473,16 @@ def __get_hostapd_cli_status(self): # Update copy of hostapd self.__old_hostapd_status = copy.copy(self.__hostapd_status) # Debug - self.__logger.debug("interface=%s", self.__hostapd_status.interface) + self.__logger.debug( + "interface=%s", self.__hostapd_status.interface + ) self.__logger.debug("state=%s", self.__hostapd_status.state) self.__logger.debug("phy=%s", self.__hostapd_status.phy) self.__logger.debug("freq=%s", self.__hostapd_status.freq) self.__logger.debug("channel=%s", self.__hostapd_status.channel) - self.__logger.debug("beacon_int=%s", self.__hostapd_status.beacon_int) + self.__logger.debug( + "beacon_int=%s", self.__hostapd_status.beacon_int + ) self.__logger.debug("ssid=%s", self.__hostapd_status.ssid) def __get_mission_cfg_status(self): @@ -430,9 +490,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: @@ -441,7 +501,7 @@ def __get_mission_cfg_status(self): hash_obj = hashlib.sha256(config) hash_hex = hash_obj.hexdigest() try: - with open(hash_file_path, 'r') as f: + with open(hash_file_path, "r") as f: data = f.read() self.__is_hash_file = True if hash_hex == data: @@ -470,7 +530,12 @@ def __get_mission_cfg_status(self): self.__mesh_cfg_status = STATUS.mesh_cfg_not_stored # Log only changes - if old_mesh_cfg_status != self.__mesh_cfg_status or \ - old_is_mission_cfg != self.__is_mission_cfg: - self.__logger.debug("mesh_cfg_status=%s, is_mission_cfg=%s, ", - self.__mesh_cfg_status, self.__is_mission_cfg) + if ( + old_mesh_cfg_status != self.__mesh_cfg_status + or old_is_mission_cfg != self.__is_mission_cfg + ): + self.__logger.debug( + "mesh_cfg_status=%s, is_mission_cfg=%s, ", + self.__mesh_cfg_status, + self.__is_mission_cfg, + ) diff --git a/modules/sc-mesh-secure-deployment/src/nats/src/validation.py b/modules/sc-mesh-secure-deployment/src/nats/src/validation.py index e15f4264e..5a1bded71 100644 --- a/modules/sc-mesh-secure-deployment/src/nats/src/validation.py +++ b/modules/sc-mesh-secure-deployment/src/nats/src/validation.py @@ -170,7 +170,7 @@ def validate_mode(mode: str) -> bool: Validates a given wifi mode. Returns True if the mode is valid, False otherwise. """ - if mode in ("mesh", "ap+mesh_scc", "ap+mesh_mcc"): + if mode in ("mesh", "ap+mesh_scc", "ap+mesh_mcc", "halow"): return True return False @@ -220,9 +220,12 @@ def validate_role(role: str) -> bool: Validates a given role. Returns True if the role is valid, False otherwise. """ - if role in ("drone", "sleeve", "gcs"): - return True - return False + try: + if role in ("drone", "sleeve", "gcs"): + return True + return False + except (ValueError, TypeError, AttributeError): + return False def validate_delay(delay: str) -> bool: """ @@ -234,5 +237,52 @@ def validate_delay(delay: str) -> bool: if delay < 0 or delay == 0: return False return True + except (ValueError, TypeError, AttributeError): + return False + +def validate_radio_index(radio_index: str) -> bool: + """ + Validates a given radio index. + Returns True if the radio index is valid, False otherwise. + """ + try: + radio_index = int(radio_index) + if radio_index < 0 or radio_index > 10: + return False + return True + except (ValueError, TypeError, AttributeError): + return False + +# def validate_phy(phy: str) -> bool: +# """ +# Validates a given phy. +# Returns True if the phy is valid, False otherwise. +# """ +# if "phy" in phy and int(phy.replace("phy", "")) >= 0: +# return True +# return False + +def validate_mesh_vif(mesh_vif: str) -> bool: + """ + Validates a given mesh vif. + Returns True if the mesh vif is valid, False otherwise. + """ + # todo add more checks + try: + if mesh_vif.startswith("wl") or mesh_vif.startswith("halow"): + return True + return False + except (ValueError, TypeError, AttributeError): + return False + +def validate_batman_iface(batman_iface: str) -> bool: + """ + Validates a given batman iface. + Returns True if the batman iface is valid, False otherwise. + """ + try: + if "bat" in batman_iface and int(batman_iface.replace("bat", "")) >= 0: + return True + return False except (ValueError, TypeError, AttributeError): return False \ No newline at end of file diff --git a/modules/sc-mesh-secure-deployment/src/nats/tests/test_settings.py b/modules/sc-mesh-secure-deployment/src/nats/tests/test_settings.py index e86abecd7..2e2e604f5 100644 --- a/modules/sc-mesh-secure-deployment/src/nats/tests/test_settings.py +++ b/modules/sc-mesh-secure-deployment/src/nats/tests/test_settings.py @@ -4,6 +4,7 @@ # pylint: disable=import-error, wrong-import-position, unused-import, \ # disable=unresolved-reference, undefined-variable, too-long import unittest +import json import logging from .context import comms_settings as comms from .context import comms_status as comms_status @@ -18,21 +19,143 @@ def test_handle_mesh_settings(self): Test cases for handle_mesh_settings() """ - - # settings json is valid logger = logging.getLogger("test") - cs = comms_status.CommsStatus(logger) + cs: [comms_status, ...] = [comms_status.CommsStatus(logger, "0"), + comms_status.CommsStatus(logger, "1"), + comms_status.CommsStatus(logger, "2")] settings = comms.CommsSettings(cs, logger) - ret, mesh_status = settings.handle_mesh_settings("""{"api_version": 1,"ssid": "test_mesh", "key": "1234567890","ap_mac": "00:11:22:33:44:55","country": "fi","frequency": "5220","frequency_mcc": "2412","ip": "192.168.1.2","subnet": "255.255.255.0","tx_power": "5","mode": "mesh","routing": "batman-adv", "priority":"long_range","role":"gcs"}""", "./tests", "test.conf") + + cmd_dict = { + "api_version": 1, + "role": "drone", # sleeve, drone, gcs + "radios": [ + { + "radio_index": "0", + "ssid": "test_mesh2", + "key": "1234567890", + "ap_mac": "00:11:22:33:44:55", + "country": "US", # all radios must have the same country + "frequency": "2412", + "frequency_mcc": "2412", # multiradio not supporting + "routing": "batman-adv", + "priority": "long_range", + "ip": "10.20.15.3", + "subnet": "255.255.255.0", + "tx_power": "15", + "mode": "mesh", # ap+mesh_scc, mesh, halow + "mesh_vif": "wlp2s0", + "batman_iface": "bat0", + }, + { + "radio_index": "1", + "ssid": "test_mesh", + "key": "1234567890", + "ap_mac": "00:11:22:33:44:55", + "country": "US", # all radios must have the same country + "frequency": "5220", + "frequency_mcc": "2412", # multiradio not supporting + "routing": "batman-adv", + "priority": "long_range", + "ip": "10.20.15.3", + "subnet": "255.255.255.0", + "tx_power": "15", + "mode": "mesh", # ap+mesh_scc, mesh, halow + "mesh_vif": "wlp3s0", # this needs to be correct + "batman_iface": "bat0", + }, + { + "radio_index": "2", + "ssid": "test_mesh3", + "key": "1234567890", + "ap_mac": "00:11:22:33:44:55", + "country": "US", # all radios must have the same country + "frequency": "5190", + "frequency_mcc": "2412", # multiradio not supporting + "routing": "batman-adv", + "priority": "long_range", + "ip": "10.20.15.3", + "subnet": "255.255.255.0", + "tx_power": "30", + "mode": "halow", # ap+mesh_scc, mesh, halow + "mesh_vif": "halow1", + "batman_iface": "bat0", + }, + ], + "bridge": "br-lan bat0 eth1 lan1 eth0 usb0", + } + + jsoned = json.dumps(cmd_dict) + ret, mesh_status = settings.handle_mesh_settings(jsoned, "./tests", "mesh.conf") + print(f"ret: {ret}, mesh_status: {mesh_status}") self.assertEqual(ret, "OK", msg=f"ret: {ret}, mesh_status: {mesh_status}") # settings json is invalid - ret, mesh_status = settings.handle_mesh_settings("""{}""", "./tests", "test.conf") + ret, mesh_status = settings.handle_mesh_settings("""{}""", "./tests", "mesh.conf") self.assertEqual(ret, "FAIL", msg=f"ret: {ret}, mesh_status: {mesh_status}") + # + # # # settings json is invalid + + cmd_dict = { + "api_version": 1, + "role": "drone", # sleeve, drone, gcs + "radios": [ + { + "radio_index": "0", + "ssid": "test_mesh2", + "key": "1234567890", + "ap_mac": "00:11:22:33:44:55", + "country": "US", # all radios must have the same country + "frequency": "2400", + "frequency_mcc": "2412", # multiradio not supporting + "routing": "batman-adv", + "priority": "long_range", + "ip": "10.20.15.3", + "subnet": "255.255.255.0", + "tx_power": "15", + "mode": "mesh", # ap+mesh_scc, mesh, halow + "mesh_vif": "wlp2s0.2", + "batman_iface": "bat", + }, + { + "radio_index": "1", + "ssid": "test_mesh", + "key": "1234567890", + "ap_mac": "00:11:22:33:44:55", + "country": "US", # all radios must have the same country + "frequency": "5220", + "frequency_mcc": "2412", # multiradio not supporting + "routing": "batman", + "priority": "long_range", + "ip": "10.20.15.3", + "subnet": "255.255.255.0", + "tx_power": "15", + "mode": "mesh", # ap+mesh_scc, mesh, halow + "mesh_vif": "wlp3s0", # this needs to be correct + "batman_iface": "bat0", + }, + { + "radio_index": "2", + "ssid": "test_mesh3", + "key": "123456 7890", + "ap_mac": "00:11:22:33:44:55", + "country": "US", # all radios must have the same country + "frequency": "5190", + "frequency_mcc": "2412", # multiradio not supporting + "routing": "None", + "priority": "looong_range", + "ip": "10.20.15.3", + "subnet": "255.255.255.0", + "tx_power": "30", + "mode": "halow", # ap+mesh_scc, mesh, halow + "mesh_vif": "halow1", + "batman_iface": "bat0", + }, + ], + "bridge to far": "br-lan bat0 eth1 lan1 eth0 usb0", + } - # # settings json is invalid - ret, mesh_status = settings.handle_mesh_settings("""{"api_version": 1,"ssid": "test;_mesh", "key":"1230","ap_mac": "00:11:22:33:44:55","country": "fi","frequency": "5220","ip": "2522.168.1.2","subnet": "a.255.255.0","tx_power": "5","mode": "mesh","priority":"long_range","role":"gcs"}}""", "./tests", "test.conf") + ret, mesh_status = settings.handle_mesh_settings("""{"radio_index": "0", "api_version": 1,"ssid": "test;_mesh", "key":"1230","ap_mac": "00:11:22:33:44:55","country": "fi","frequency": "5220","ip": "2522.168.1.2","subnet": "a.255.255.0","tx_power": "5","mode": "mesh","priority":"long_range","role":"gcs"}}""", "./tests", "test.conf") self.assertEqual(ret, "FAIL", msg=f"ret: {ret}, mesh_status: {mesh_status}") if __name__ == '__main__': diff --git a/modules/sc-mesh-secure-deployment/src/nats/tests/test_validation.py b/modules/sc-mesh-secure-deployment/src/nats/tests/test_validation.py index 193a56d0c..fddbb47fc 100644 --- a/modules/sc-mesh-secure-deployment/src/nats/tests/test_validation.py +++ b/modules/sc-mesh-secure-deployment/src/nats/tests/test_validation.py @@ -160,5 +160,86 @@ def test_validate_routing(self): # routing is invalid self.assertFalse(validation.validate_routing(1)) + def test_validate_priority(self): + """ + Test cases for validate_priority(priority) + """ + # priority is invalid + self.assertFalse(validation.validate_priority('none')) + # priority is valid + self.assertTrue(validation.validate_priority('long_range')) + # priority is valid + self.assertFalse(validation.validate_priority('high')) + # priority is invalid + self.assertFalse(validation.validate_priority('nonee')) + # priority is invalid + self.assertFalse(validation.validate_priority('long_rang')) + # priority is invalid + self.assertFalse(validation.validate_priority('hig')) + # priority is invalid + self.assertFalse(validation.validate_priority(1)) + # priotity is valid + self.assertTrue(validation.validate_priority("high_throughput")) + + def test_validate_radio_index(self): + """ + Test cases for validate_radio_index(radio_index) + """ + # radio index is valid + self.assertTrue(validation.validate_radio_index("0")) + # radio index is valid + self.assertTrue(validation.validate_radio_index("1")) + # radio index is valid + self.assertTrue(validation.validate_radio_index("2")) + # radio index is invalid + self.assertTrue(validation.validate_radio_index("3")) + # radio index is invalid + self.assertFalse(validation.validate_radio_index("-1")) + self.assertFalse(validation.validate_radio_index(-1)) + + def test_validate_mesh_vif(self): + """ + Test cases for validate_mesh_vif(mesh_vif) + """ + # mesh vif is valid + self.assertTrue(validation.validate_mesh_vif("wlp2s0")) + # mesh vif is valid + self.assertTrue(validation.validate_mesh_vif("wlp3s0")) + # mesh vif is valid + self.assertTrue(validation.validate_mesh_vif("halow1")) + # mesh vif is valid + self.assertTrue(validation.validate_mesh_vif("wlp2s1")) + # mesh vif is valid + self.assertTrue(validation.validate_mesh_vif("wlp3s1")) + # mesh vif is valid + self.assertTrue(validation.validate_mesh_vif("halow2")) + # mesh vif is invalid + self.assertFalse(validation.validate_mesh_vif("lan1")) + # mesh vif is invalid + self.assertFalse(validation.validate_mesh_vif("eth0")) + # mesh vif is invalid + self.assertFalse(validation.validate_mesh_vif("usb0")) + # mesh vif is invalid + self.assertFalse(validation.validate_mesh_vif(0)) + + def test_validate_batman_iface(self): + """ + Test cases for validate_batman_iface(batman_iface) + """ + # batman iface is valid + self.assertTrue(validation.validate_batman_iface("bat0")) + # batman iface is valid + self.assertTrue(validation.validate_batman_iface("bat1")) + # batman iface is valid + self.assertTrue(validation.validate_batman_iface("bat2")) + # batman iface is invalid + self.assertFalse(validation.validate_batman_iface("3")) + # batman iface is invalid + self.assertFalse(validation.validate_batman_iface(0)) + + + + + if __name__ == '__main__': unittest.main() diff --git a/modules/utils/docker/entrypoint_nats.sh b/modules/utils/docker/entrypoint_nats.sh index 065933de4..01b556437 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 "0" if [ "$MSVERSION" != "nats" ]; then if [ -f "/usr/local/bin/entrypoint.sh" ]; then @@ -18,20 +18,42 @@ else generate_identity_id fi + # Do not continue in case halow init has not finished + while ps aux | grep [i]nit_halow > /dev/null; do + sleep 1 + done + echo "set bridge ip" - generate_br_lan_ip + generate_lan_bridge_ip echo "starting 11s mesh service" - /opt/S9011sNatsMesh start + # todo for loop range 0..3 + /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 + /opt/S90APoint start id0 + if [ -f "/opt/1_mesh.conf" ]; then + /opt/S90APoint start id1 + fi + if [ -f "/opt/2_mesh.conf" ]; then + /opt/S90APoint start id2 + fi echo "wait for bridge to be up..." - while ! (ifconfig | grep -e "$br_lan_ip") > /dev/null; do + while ! (ifconfig | grep -e "$bridge_ip") > /dev/null; do sleep 1 done + sleep 3 + + /opt/S90Alfred start + echo "starting provisioning agent" # blocks execution until provisioning is done or timeout (30s) # IP address and port are passed as arguments and hardcoded. TODO: mDNS