diff --git a/wifidmd/Makefile b/wifidmd/Makefile index 4cd8d5355..bdeb8050e 100644 --- a/wifidmd/Makefile +++ b/wifidmd/Makefile @@ -5,7 +5,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=wifidmd -PKG_VERSION:=1.4.10 +PKG_VERSION:=1.4.11 LOCAL_DEV:=0 ifneq ($(LOCAL_DEV),1) diff --git a/wifidmd/files/etc/wifidmd/bbf_config_reload.sh b/wifidmd/files/etc/wifidmd/bbf_config_reload.sh index 24f672dac..39208db30 100755 --- a/wifidmd/files/etc/wifidmd/bbf_config_reload.sh +++ b/wifidmd/files/etc/wifidmd/bbf_config_reload.sh @@ -2,197 +2,323 @@ # Script: bbf_config_reload.sh # Description: -# This script reloads WiFi Configs based on input args. -# Input args should be one of the below or both with space separate -# - "wireless" -# - "mapcontroller" -# +# Reloads WiFi configurations based on provided arguments. +# # Usage: -# sh bbf_config_reload.sh wireless mapcontroller -# +# sh bbf_config_reload.sh [wireless] [mapcontroller] +# +# Input arguments: +# wireless Reload only wireless configuration +# mapcontroller Reload only mapcontroller configuration +# # Actions: -# - If both wireless and mapcontroller → Reload mapcontroller (SIGHUP) -# - If only wireless → Commit wireless config via ubus -# - If only mapcontroller → Reload mapcontroller (SIGHUP) -# - Otherwise → Do nothing +# - If both "wireless" and "mapcontroller" are specified: +# -> Reload mapcontroller (send SIGHUP) +# - If only "wireless" is specified: +# -> Commit wireless configuration via ubus +# - If only "mapcontroller" is specified: +# -> Reload mapcontroller (send SIGHUP) +# - If no valid arguments are provided: +# -> Do nothing -log() { - echo "${@}"|logger -t bbf.config.wifi.reload -p info +. /lib/functions.sh +. /usr/share/libubox/jshn.sh + +MAPCONTROLLER=0 +WIRELESS=0 +MAX_RETRIES=15 + +info() { + echo "${@}" | logger -t bbf.config.wifi.reload -p info } -input="$@" +debug() { + echo "${@}" +} -# Validate input -if [ -z "$input" ]; then - log "Error: No input provided" - exit 1 -fi +json_add_aps() { + json_add_object + json_add_string ssid "$1" + json_add_string band "$2" + json_add_string encryption "$3" + json_add_string key "$4" + json_add_string ifname "$5" + json_close_object +} -# Normalize inputs (default to 0 if not set) -wireless=0 -mapcontroller=0 +parse_mapcontroller_ap() { + local section="$1" -for arg in ${input}; do - if [ "${arg}" == "wireless" ]; then - wireless=1 + config_get type "$section" type "fronthaul" + [ "$type" = "fronthaul" ] || return 0 + + config_get enabled "$section" enabled 1 + [ "$enabled" = "1" ] || return 0 + + config_get ssid "$section" ssid + [ -n "$ssid" ] || return 0 + + config_get band "$section" band + [ -n "$band" ] || return 0 + + case "$band" in + 2|5|6) ;; + *) return 0 ;; + esac + + config_get encryption "$section" encryption "none" + config_get key "$section" key + + json_add_aps "$ssid" "$band" "$encryption" "$key" "" + debug "mapcontroller AP added: ssid=|$ssid| band=|$band| encryption=|$encryption| key=|$key|" +} + +parse_wireless_ap() { + local section="$1" + + config_get disabled "$section" disabled 0 + [ "$disabled" = "1" ] && return 0 + + config_get multi_ap "$section" multi_ap 0 + case "$multi_ap" in + 1|2) return 0 ;; + esac + + config_get ssid "$section" ssid + [ -n "$ssid" ] || return 0 + + config_get device "$section" device + [ -n "$device" ] || return 0 + + config_get band "$device" band + case "$band" in + 2g) band="2" ;; + 5g) band="5" ;; + 6g) band="6" ;; + *) return 0 ;; + esac + + config_get radio_disabled "$device" disabled 0 + [ "$radio_disabled" = "1" ] && return 0 + + config_get encryption "$section" encryption "none" + config_get key "$section" key + + for try in $(seq 1 "$MAX_RETRIES"); do + config_get ifname "$section" ifname + [ -n "$ifname" ] && break + debug "FAIL: no ifname found for ssid=|$ssid| band=|$band| (try $try/$MAX_RETRIES)" + sleep 1 + config_load wireless + done + + if [ -z "$ifname" ]; then + info "FAIL: could not match wireless AP with ssid=|$ssid| band=|$band| after $MAX_RETRIES tries" + return O fi - if [ "${arg}" == "mapcontroller" ]; then - mapcontroller=1 + json_add_aps "$ssid" "$band" "$encryption" "$key" "$ifname" + debug "wireless AP added: ssid=|$ssid| band=|$band| ifname=|$ifname|" +} + +match_ap_to_wireless_interface() { + local section="$1" + + config_get disabled "$section" disabled 0 + [ "$disabled" = "1" ] && return 0 + + config_get multi_ap "$section" multi_ap 0 + [ "$multi_ap" = "2" ] || return 0 + + config_get ssid "$section" ssid + [ "$ssid" = "$SSID" ] || return 0 + + config_get device "$section" device + [ -n "$device" ] || return 0 + + config_get band "$device" band + case "$band" in + 2g) band="2" ;; + 5g) band="5" ;; + 6g) band="6" ;; + *) return 0 ;; + esac + [ "$band" = "$BAND" ] || return 0 + + config_get radio_disabled "$device" disabled 0 + [ "$radio_disabled" = "1" ] && RADIO_DISABLED=1 && return 0 + + config_get key "$section" key + [ -n "$KEY" ] && [ "$key" != "$KEY" ] && return 0 + + config_get encryption "$section" encryption "none" + [ -n "$ENCRYPTION" ] && [ "$encryption" != "$ENCRYPTION" ] && return 0 + + config_get MATCHED_IFNAME "$section" ifname +} + +match_aps_to_wireless_interfaces() { + config_load wireless + + json_get_keys indices + + for i in $indices; do + json_select "$i" + + json_get_var SSID ssid + json_get_var BAND band + json_get_var ENCRYPTION encryption + json_get_var KEY key + + debug "matching AP[$i]: ssid=|$SSID| band=|$BAND| encryption=|$ENCRYPTION| key=|$KEY|" + + MATCHED_IFNAME="" + RADIO_DISABLED=0 + + for try in $(seq 1 "$MAX_RETRIES"); do + config_foreach match_ap_to_wireless_interface wifi-iface + + if [ -n "$MATCHED_IFNAME" ]; then + json_add_string ifname "$MATCHED_IFNAME" + debug "matched AP[$i]: ssid=|$SSID| band=|$BAND| -> ifname=|$MATCHED_IFNAME|" + break + fi + + [ "$RADIO_DISABLED" = "1" ] && break + + sleep 1 + config_load wireless + done + + if [ -z "$MATCHED_IFNAME" ] && [ "$RADIO_DISABLED" != "1" ]; then + info "FAIL: no match for AP[$i]: ssid=|$SSID| band=|$BAND| after $MAX_RETRIES tries" + fi + + json_select .. + done +} + +verify_aps_runtime_state() { + json_get_keys indices + + for i in $indices; do + json_select "$i" + + json_get_var ifname ifname + json_get_var ssid ssid + json_get_var band band + + [ -z "$ifname" ] && json_select .. && continue + + debug "validating runtime AP[$i]: ssid=|$ssid| band=|$band| ifname=|$ifname|" + + case "$band" in + 2) bandstr="2.4GHz" ;; + 5) bandstr="5GHz" ;; + 6) bandstr="6GHz" ;; + *) json_select .. && continue ;; + esac + + ok=0 + for try in $(seq 1 "$MAX_RETRIES"); do + json="$(ubus call wifi.ap."$ifname" status 2>/dev/null)" + + if [ -z "$json" ]; then + debug "FAIL: empty ubus response for wifi.ap.$ifname status (try $try/$MAX_RETRIES)" + sleep 2 + continue + fi + + ubus_ssid="$(echo "$json" | jsonfilter -q -e '@.ssid')" + ubus_band="$(echo "$json" | jsonfilter -q -e '@.band')" + + [ "$ubus_ssid" = "$ssid" -a "$ubus_band" = "$bandstr" ] && { + ok=1 + break + } + + sleep 2 + done + + [ "$ok" -ne 1 ] && info "FAIL: runtime validation failed for AP[$i]: ssid=|$ssid| band=|$band| ifname=|$ifname|" + json_select .. + debug "runtime validation completed for AP[$i]: ssid=|$ssid| band=|$band| ifname=|$ifname|" + done +} + +validate_mapcontroller_changes() { + json_init + json_add_array aps + config_load mapcontroller + config_foreach parse_mapcontroller_ap ap + match_aps_to_wireless_interfaces + verify_aps_runtime_state + json_cleanup +} + +validate_wireless_changes() { + json_init + json_add_array aps + config_load wireless + config_foreach parse_wireless_ap wifi-iface + verify_aps_runtime_state + json_cleanup +} + +reload_mapcontroller() { + pid="$(pidof mapcontroller)" + if [ -n "$pid" ]; then + info "Reloading mapcontroller (PID: $pid)..." + kill -SIGHUP "$pid" + sleep 5 + validate_mapcontroller_changes + else + info "Warning: mapcontroller process not found" fi +} + +reload_wireless() { + info "Reloading wireless config..." + ubus call uci commit '{"config":"wireless"}' + sleep 5 + validate_wireless_changes +} + +dmmap_commit_new_aps() { + DMAP_PATH="uci -q -c /etc/bbfdm/dmmap" + for sec in $($DMAP_PATH show WiFi | grep "=AccessPoint" | cut -d. -f2 | cut -d= -f1); do + is_new=$($DMAP_PATH get WiFi.${sec}.__is_new__) + [ "$is_new" = "1" ] || continue + + sec_name=$($DMAP_PATH get WiFi.${sec}.__section_name__) + case "$sec_name" in + wireless.*) MAPCONTROLLER=0 ;; + esac + $DMAP_PATH delete WiFi.${sec}.__is_new__ + done + + $DMAP_PATH commit WiFi +} + +for arg in "$@"; do + case "$arg" in + wireless) WIRELESS=1 ;; + mapcontroller) MAPCONTROLLER=1 ;; + *) + info "Unknown option: $arg" + exit 1 + ;; + esac done +dmmap_commit_new_aps -################################################################################ -# wait_for_wifi_reload -# -# Description: -# Currently, there is no direct way to determine when WiFi reload -# operations have fully completed. The ubus API does not provide any -# event or flag indicating that WiFi services have finished reloading. -# -# To work around this, we use a timing-based heuristic: -# - We repeatedly run `ubus call wifi status` every 1 second. -# - Each call is timed (using `date +%s` before and after). -# - Normally, this command returns almost instantly (<1s). -# - However, during a WiFi reload, the call may take longer -# (several seconds) while the subsystem is busy. -# - Once we detect that `ubus call wifi status` takes longer than -# 2 seconds to return, we assume the reload has been applied -# and completed successfully. -# -# Additional wait constraints: -# - Minimum wait: 5 seconds (to ensure reload started). -# - Maximum wait: 15 seconds (to prevent indefinite blocking). -# - If the threshold is not met within 15s, we log a timeout warning. -################################################################################ -wait_for_wifi_reload() { - MAX_ITER=15 - MIN_ITER=5 - THRESH=2 # seconds - - # Check if wifi ubus object exists - ubus -t 2 wait_for wifi >/dev/null 2>&1 - if [ "$?" -ne 0 ]; then - log "wifi ubus object not available, skipping wait logic" - return 0 - fi - - #log "Waiting for WiFi reload (min ${MIN_ITER}s, max ${MAX_ITER}s)..." - - iter=0 - while [ "${iter}" -lt "${MAX_ITER}" ]; do - iter=$((iter + 1)) - start=$(date +%s) - ubus call wifi status >/dev/null 2>&1 - #rc=$? - end=$(date +%s) - elapsed=$((end - start)) - - #log "wait_for_wifi_reload: iter=${iter}, rc=${rc}, elapsed=${elapsed}s" - - # If ubus took >2s and we've waited at least MIN_ITER → assume reload done - if [ "${elapsed}" -gt "${THRESH}" ] && [ "${iter}" -ge "${MIN_ITER}" ]; then - log "Detected long ubus response (${elapsed}s) after ${iter}s → assuming WiFi reload complete" - return 0 - fi - - # Sleep 1s between checks - if [ "${iter}" -lt "${MAX_ITER}" ]; then - sleep 1 - fi - done - - log "Timeout after ${MAX_ITER}s — WiFi reload not confirmed" - return 1 -} - -############################################################################### -# mark_ap_instances_applied -# -# Description: -# Finalizes newly created WiFi AccessPoint instances in the dmmap WiFi -# configuration. -# -# An AccessPoint instance is considered a *new instance* when the option -# __is_new__ exists and its value is "1". -# -# New instance handling logic: -# -# • The AccessPoint's controller-side section name is obtained from -# the __section_name__ option (e.g., "mapcontroller.xxx" or "wireless.xxx"). -# -# • For new instances where __section_name__ begins with "mapcontroller.": -# - Remove __is_new__ -# - No additional actions required -# -# • For new instances where __section_name__ begins with "wireless.": -# - Set mapcontroller=0 (indicates mapcontroller reload is not required) -# - Remove __is_new__ -# -# • For all other prefixes: -# - Remove __is_new__ only -# -# After all new instances are processed, the dmmap WiFi configuration is committed. -############################################################################### -mark_ap_instances_applied() { - for sec in $(uci -q -c /etc/bbfdm/dmmap show WiFi | grep "=AccessPoint" | cut -d. -f2 | cut -d= -f1); do - is_new=$(uci -q -c /etc/bbfdm/dmmap get WiFi.${sec}.__is_new__) - - if [ "${is_new}" = "1" ]; then - config_sec_name=$(uci -q -c /etc/bbfdm/dmmap get WiFi.${sec}.__section_name__) - case "${config_sec_name}" in - mapcontroller.*) - # New mapcontroller AP instance — remove creation flag only - uci -q -c /etc/bbfdm/dmmap delete WiFi.${sec}.__is_new__ - ;; - - wireless.*) - # New wireless AP instance — skip mapcontroller reload - mapcontroller=0 - uci -q -c /etc/bbfdm/dmmap delete WiFi.${sec}.__is_new__ - ;; - - *) - # Other AP instance types — remove creation flag only - uci -q -c /etc/bbfdm/dmmap delete WiFi.${sec}.__is_new__ - ;; - esac - fi - done - - uci -q -c /etc/bbfdm/dmmap commit WiFi -} - -# Define function to reload mapcontroller -reload_mapcontroller() { - pid=$(pidof mapcontroller) - if [ -n "$pid" ]; then - log "Reloading mapcontroller (PID: $pid)..." - kill -SIGHUP "$pid" - wait_for_wifi_reload - else - log "Warning: mapcontroller process not found" - fi -} - -# Define function to commit wireless config -commit_wireless_config() { - log "Committing wireless config..." - ubus call uci commit '{"config":"wireless"}' - wait_for_wifi_reload -} - -# Finalize newly created AP AccessPoint instances -mark_ap_instances_applied - -# Apply logic based on flags -if [ "$mapcontroller" -eq 1 ]; then +if [ "$MAPCONTROLLER" -eq 1 ]; then reload_mapcontroller -elif [ "$wireless" -eq 1 ]; then - commit_wireless_config +elif [ "$WIRELESS" -eq 1 ]; then + reload_wireless else - log "No action needed." + info "No valid arguments provided." exit 1 fi