diff --git a/Kconfig b/Kconfig index 2dba868a97..4330006c89 100644 --- a/Kconfig +++ b/Kconfig @@ -629,3 +629,4 @@ mainmenu "Espressif IoT Development Framework Configuration" - CONFIG_BOOTLOADER_CACHE_32BIT_ADDR_QUAD_FLASH - CONFIG_MBEDTLS_USE_CRYPTO_ROM_IMPL - CONFIG_ESP_WIFI_EAP_TLS1_3 + - CONFIG_ESP_WIFI_ENABLE_ROAMING_APP diff --git a/components/esp_wifi/CMakeLists.txt b/components/esp_wifi/CMakeLists.txt index 29caded012..5df6dc4b27 100644 --- a/components/esp_wifi/CMakeLists.txt +++ b/components/esp_wifi/CMakeLists.txt @@ -37,7 +37,10 @@ if(CONFIG_ESP_WIFI_ENABLED OR CONFIG_ESP_HOST_WIFI_ENABLED) endif() if(CONFIG_ESP_WIFI_NAN_ENABLE) - list(APPEND srcs "wifi_apps/src/nan_app.c") + list(APPEND srcs "wifi_apps/nan_app/src/nan_app.c") + endif() + if(CONFIG_ESP_WIFI_ENABLE_ROAMING_APP) + list(APPEND srcs "wifi_apps/roaming_app/src/roaming_app.c") endif() set(local_include_dirs include/local) else() @@ -50,10 +53,13 @@ else() endif() idf_component_register(SRCS "${srcs}" - INCLUDE_DIRS "include" "wifi_apps/include" ${local_include_dirs} + INCLUDE_DIRS "include" "wifi_apps/include" "wifi_apps/nan_app/include" ${local_include_dirs} + REQUIRES esp_event esp_phy esp_netif PRIV_REQUIRES driver esptool_py esp_pm esp_timer nvs_flash wpa_supplicant hal lwip esp_coex ${extra_priv_requires} + PRIV_INCLUDE_DIRS ../wpa_supplicant/src/ ../wpa_supplicant/esp_supplicant/src/ + wifi_apps/roaming_app/include LDFRAGMENTS "${ldfragments}") if(CONFIG_ESP_WIFI_ENABLED OR CONFIG_ESP_HOST_WIFI_ENABLED) diff --git a/components/esp_wifi/Kconfig b/components/esp_wifi/Kconfig index f9c534a510..72157799a7 100644 --- a/components/esp_wifi/Kconfig +++ b/components/esp_wifi/Kconfig @@ -574,6 +574,26 @@ menu "Wi-Fi" help Select this option to enable WiFi Multiband operation certification support. + config ESP_WIFI_ENABLE_ROAMING_APP + bool "Advanced support for Wi-Fi Roaming (Experimental)" + depends on IDF_EXPERIMENTAL_FEATURES + default n + select ESP_WIFI_SCAN_CACHE + help + Enable Espressif's roaming app to allow for efficient Wi-Fi roaming. + This includes configurable periodic environment scans, maintaining a cache of the + best APs, handling low rssi events etc. + + Risk Warning + - Please note that this feature is still experimental and enabling this potentially can + lead to unpredictable scanning, connection and roaming attempts. + We are still working on tuning and optimising this feature to ensure reliable and stable use. + + menu "Configure roaming App" + depends on ESP_WIFI_ENABLE_ROAMING_APP + rsource "wifi_apps/roaming_app/src/Kconfig.roaming" + endmenu + config ESP_WIFI_DPP_SUPPORT bool "Enable DPP support" default n diff --git a/components/esp_wifi/wifi_apps/include/esp_nan.h b/components/esp_wifi/wifi_apps/nan_app/include/esp_nan.h similarity index 100% rename from components/esp_wifi/wifi_apps/include/esp_nan.h rename to components/esp_wifi/wifi_apps/nan_app/include/esp_nan.h diff --git a/components/esp_wifi/wifi_apps/src/nan_app.c b/components/esp_wifi/wifi_apps/nan_app/src/nan_app.c similarity index 99% rename from components/esp_wifi/wifi_apps/src/nan_app.c rename to components/esp_wifi/wifi_apps/nan_app/src/nan_app.c index 8e52bc7de7..240420e036 100644 --- a/components/esp_wifi/wifi_apps/src/nan_app.c +++ b/components/esp_wifi/wifi_apps/nan_app/src/nan_app.c @@ -533,7 +533,7 @@ static void nan_app_action_ndp_confirm(void *arg, esp_event_base_t event_base, i } if (nan_find_ndl(evt->ndp_id, NULL) == NULL) { - /* As ndl isn't found, timeout has occured for NDP response and datapath request is rejected */ + /* As ndl isn't found, timeout has occurred for NDP response and datapath request is rejected */ goto done; } if (evt->status == NDP_STATUS_REJECTED) { diff --git a/components/esp_wifi/wifi_apps/roaming_app/include/esp_roaming.h b/components/esp_wifi/wifi_apps/roaming_app/include/esp_roaming.h new file mode 100644 index 0000000000..7f5e581e08 --- /dev/null +++ b/components/esp_wifi/wifi_apps/roaming_app/include/esp_roaming.h @@ -0,0 +1,131 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "sdkconfig.h" +#include "esp_log.h" +#include "esp_err.h" +#include "esp_wifi_types.h" +#include "utils/common.h" +#include +#ifdef __cplusplus +extern "C" { +#endif + +#define SUPPLICANT_CANDIDATE_LIST_EXPIRY 10 + +/* Global Roaming Configuration */ +#define ROAMING_BACKOFF_TIME CONFIG_ESP_WIFI_ROAMING_BACKOFF_TIME + +/* Low RSSI based roaming configuration */ +#define LOW_RSSI_ROAMING_ENABLED CONFIG_ESP_WIFI_ROAMING_LOW_RSSI_ROAMING +#if LOW_RSSI_ROAMING_ENABLED +#define ROAMING_LOW_RSSI_THRESHOLD CONFIG_ESP_WIFI_ROAMING_LOW_RSSI_THRESHOLD +#define RSSI_THRESHOLD_REDUCTION_OFFSET CONFIG_ESP_WIFI_ROAMING_LOW_RSSI_OFFSET +#endif /*LOW_RSSI_ROAMING_ENABLED*/ + +/* Periodic Scan based Roaming configuration */ +#define PERIODIC_SCAN_MONITORING CONFIG_ESP_WIFI_ROAMING_PERIODIC_SCAN_MONITOR +#if PERIODIC_SCAN_MONITORING +#define SCAN_MONITOR_INTERVAL CONFIG_ESP_WIFI_ROAMING_SCAN_MONITOR_INTERVAL +#define SCAN_MONITOR_RSSI_THRESHOLD CONFIG_ESP_WIFI_ROAMING_PERIODIC_SCAN_THRESHOLD +#define SCAN_ROAM_RSSI_DIFF CONFIG_ESP_WIFI_ROAMING_SCAN_ROAM_RSSI_DIFF +#endif /* PERIODIC_SCAN_MONITORING */ + +/* Scan configuration */ +#define SCAN_TIME_MIN_DURATION CONFIG_ESP_WIFI_ROAMING_SCAN_MIN_SCAN_TIME +#define SCAN_TIME_MAX_DURATION CONFIG_ESP_WIFI_ROAMING_SCAN_MAX_SCAN_TIME +#define HOME_CHANNEL_DWELL_TIME CONFIG_ESP_WIFI_ROAMING_HOME_CHANNEL_DWELL_TIME +#define SCAN_PREFERRED_CHAN_LIST CONFIG_ESP_WIFI_ROAMING_SCAN_CHAN_LIST +#define DEFAULT_PREFERRED_SCAN_CHAN_LIST "None" +#define SCAN_RESULTS_USABILITY_WINDOW CONFIG_ESP_WIFI_ROAMING_SCAN_EXPIRY_WINDOW +#define MAX_CANDIDATE_COUNT CONFIG_ESP_WIFI_ROAMING_MAX_CANDIDATES + +/* Legacy roaming configuration */ +#define LEGACY_ROAM_ENABLED CONFIG_ESP_WIFI_ROAMING_LEGACY_ROAMING + +#define BSS_TM_RETRY_COUNT CONFIG_ESP_WIFI_NETWORK_ASSISTED_ROAMING_RETRY_COUNT + +/* Network Assisted Roaming */ +#define NETWORK_ASSISTED_ROAMING_ENABLED CONFIG_ESP_WIFI_ROAMING_NETWORK_ASSISTED_ROAM + +/* Periodic RRM configuration */ +#define PERIODIC_RRM_MONITORING CONFIG_ESP_WIFI_ROAMING_PERIODIC_RRM_MONITORING +#if PERIODIC_RRM_MONITORING +#define RRM_MONITOR_TIME CONFIG_ESP_WIFI_ROAMING_RRM_MONITOR_TIME +#define RRM_MONITOR_RSSI_THRESHOLD CONFIG_ESP_WIFI_ROAMING_RRM_MONITOR_THRESHOLD +#endif /*PERIODIC_RRM_MONITORING*/ + +#define MAX_SCAN_CHAN_LIST_COUNT 14 + +#define MAX_NEIGHBOR_LEN 512 + +#define IS_PSK(authmode) \ + (((authmode == WIFI_AUTH_WPA_PSK) || (authmode == WIFI_AUTH_WPA2_PSK) || \ + (authmode == WIFI_AUTH_WPA_WPA2_PSK) || (authmode == WIFI_AUTH_WPA3_PSK) || \ + (authmode == WIFI_AUTH_WPA2_WPA3_PSK) || (authmode == WIFI_AUTH_WAPI_PSK) ? 1 : 0)) + +#define OWE_COMPATIBLE(curr_auth, cand_auth) \ + ((((curr_auth == WIFI_AUTH_OPEN) || (curr_auth == WIFI_AUTH_OWE)) && ((cand_auth == WIFI_AUTH_OPEN) || (cand_auth == WIFI_AUTH_OWE)))? 1 : 0) + +#define PSK_COMPATIBLE(curr_auth, cand_auth) \ + ((IS_PSK(curr_auth) && IS_PSK(cand_auth)) ? 1 : 0) + +struct scanned_ap_info { + uint16_t current_count; + struct timeval time; + wifi_ap_record_t ap_records[MAX_CANDIDATE_COUNT]; +}; +struct cand_bss { + uint8_t channel; + uint8_t bssid[ETH_ALEN]; +}; + +struct roaming_app { + wifi_scan_config_t scan_params; + bool scan_ongoing; + int8_t current_rssi_threshold; + char *btm_neighbor_list; + struct timeval last_roamed_time; + wifi_ap_record_t ap_info; + struct scanned_ap_info scanned_aps; + bool btm_support; + bool rrm_support; + +#if LOW_RSSI_ROAMING_ENABLED + int8_t current_low_rssi_threshold; +#endif +#if LEGACY_ROAM_ENABLED && NETWORK_ASSISTED_ROAMING_ENABLED + uint8_t btm_attempt; +#endif +#if LEGACY_ROAM_ENABLED + bool force_roam_ongoing; +#endif +#if PERIODIC_RRM_MONITORING + bool periodic_rrm_active; + bool rrm_request_active; +#endif +#if PERIODIC_SCAN_MONITORING + bool periodic_scan_active; +#endif +}; + +void init_roaming_app(void); +void deinit_roaming_app(void); + +#if PERIODIC_RRM_MONITORING +void roaming_app_periodic_rrm_internal_handler(void *data, void *ctx); +#endif /*PERIODIC_RRM_MONITORING*/ + +#if PERIODIC_SCAN_MONITORING +void roaming_app_periodic_scan_internal_handler(void *data, void *ctx); +#endif /*PERIODIC_SCAN_ROAM_MONITORING*/ + +void roaming_app_trigger_roam_internal_handler(void *data, void *ctx); +#ifdef __cplusplus +} +#endif diff --git a/components/esp_wifi/wifi_apps/roaming_app/src/Kconfig.roaming b/components/esp_wifi/wifi_apps/roaming_app/src/Kconfig.roaming new file mode 100644 index 0000000000..aa79fbdf93 --- /dev/null +++ b/components/esp_wifi/wifi_apps/roaming_app/src/Kconfig.roaming @@ -0,0 +1,174 @@ +# Visible if ESP_WIFI_ENABLE_ROAMING_APP enabled in components/esp_wifi/Kconfig +menu "Roaming triggers" + config ESP_WIFI_ROAMING_LOW_RSSI_ROAMING + bool "Use Low RSSI to trigger roaming." + default y + help + Enable to use a RSSI threshold to trigger roaming. + + config ESP_WIFI_ROAMING_LOW_RSSI_THRESHOLD + depends on ESP_WIFI_ROAMING_LOW_RSSI_ROAMING + int "WiFi RSSI threshold to trigger roaming" + range -99 -30 + default -60 + help + WiFi RSSI threshold to trigger roaming value in dBm (-99 to -1). Values under -30 dbm + might lead to a flood of low rssi events. This interferes with normal functioning and + TX/Rx performance. + + config ESP_WIFI_ROAMING_LOW_RSSI_OFFSET + depends on ESP_WIFI_ROAMING_LOW_RSSI_ROAMING + int "Offset by which to reset the RSSI Threshold after attempt to roam." + range 0 99 + default 5 + help + Decide the offset by which to decrease the Low RSSI threshold set by ESP_WIFI_ROAMING_LOW_RSSI_THRESHOLD + after each failed attempt to roam. This allows for the station to keep scanning for better AP's after + the Low RSSI threshold is reached in a stepped manner, rather than only attempting to roam the first time + the current AP's RSSI breaches the set RSSI threshold. + + config ESP_WIFI_ROAMING_PERIODIC_SCAN_MONITOR + bool "Conduct periodic scans to check if a better AP is available" + default y + help + Conduct periodic scans periodically to check if a better AP is available. + + config ESP_WIFI_ROAMING_PERIODIC_SCAN_THRESHOLD + int "Threshold at which to begin periodic scanning for a better AP." + depends on ESP_WIFI_ROAMING_PERIODIC_SCAN_MONITOR + range -99 -1 + default -50 + help + Threshold at which the station will begin scanning to find an AP with better RSSI. + + config ESP_WIFI_ROAMING_SCAN_MONITOR_INTERVAL + int "Time intervals (in seconds) at which station will initiate a scan" + depends on ESP_WIFI_ROAMING_PERIODIC_SCAN_MONITOR + range 1 1500 + default 30 + help + Intervals at which station will periodically scan to check if better AP is available + + config ESP_WIFI_ROAMING_SCAN_ROAM_RSSI_DIFF + int "RSSI difference b/w current AP and candidate AP to initiate connection" + depends on ESP_WIFI_ROAMING_PERIODIC_SCAN_MONITOR + default 15 + range 0 99 + help + Minimum RSSI difference b/w current AP and a potential roaming candidate AP + to trigger a roaming attempt. +endmenu #"Roaming triggers" + +menu "Roaming Methods" + config ESP_WIFI_ROAMING_LEGACY_ROAMING + bool "Support Legacy roaming approach" + default y + help + Roaming between APs that do not support 802.11kv. + This will allow station to roam even when connection is not BTM supported, + by forcefully disconnecting from current AP and connecting to better AP. + + config ESP_WIFI_ROAMING_NETWORK_ASSISTED_ROAM + bool "Support Network Assisted roaming using 802.11kv" + depends on ESP_WIFI_11KV_SUPPORT + default y + help + Roaming between APs using network assisted Roaming. + This involves BSS Transition Management mechanisms outlined in 802.11v. + Note that this moves the responsibility to the AP's network, and hence isn't + guaranteed to cause the station to attempt to roam each time. + + config ESP_WIFI_NETWORK_ASSISTED_ROAMING_RETRY_COUNT + int "Retry count after which to switch to legacy roaming" + depends on ESP_WIFI_ROAMING_NETWORK_ASSISTED_ROAM + depends on ESP_WIFI_ROAMING_LEGACY_ROAMING + range 1 5 + default 2 + help + Retry threshold after which the station should stop using Network Assisted + roaming methods and start using legacy roaming instead. + +endmenu #"Roaming Methods" + +menu "Scan Configuration" + + config ESP_WIFI_ROAMING_SCAN_MIN_SCAN_TIME + int "Minimum duration (in milliseconds) of station's per channel active scan" + default 10 + range 0 120 + help + Minimum duration of active scanning per channel in milliseconds. + + config ESP_WIFI_ROAMING_SCAN_MAX_SCAN_TIME + int "Maximum duration (in milliseconds) of station's per channel active scan time" + default 70 + range 30 120 + help + Maximum duration of active scanning per channel in milliseconds. + + config ESP_WIFI_ROAMING_HOME_CHANNEL_DWELL_TIME + int "Home channel dwell time scanning between consecutive channels" + default 30 + range 30 150 + help + If connected, duration for which the station will return to it's home channel for Tx/Rx of + frames stored in buffers between scanning on consecutive channels. + + config ESP_WIFI_ROAMING_SCAN_CHAN_LIST + string "Preferred channel list for scanning" + default "None" + help + Channels your wireless network operates on to allow for faster scanning. + Specify the channels(between 1-14) in a comma separated manner. + + config ESP_WIFI_ROAMING_SCAN_EXPIRY_WINDOW + int "Scan results expiry window (in seconds)" + default 10 + range 5 20 + help + Duration for which the results from the most recent scans can be used + by the roaming app for determining the roaming candidates. + + config ESP_WIFI_ROAMING_MAX_CANDIDATES + int "Max Candidates in the network" + default 3 + range 3 20 + help + Max candidates that can be considered while scanning as a part of the + network at one time. + +endmenu #"Scan Configuration" + +config ESP_WIFI_ROAMING_BACKOFF_TIME + int "Default time to wait between subsequent roaming attempts." + default 15 + range 0 120 + help + Time to wait (in seconds) by station before registering for the RSSI event again + or start continuous montoring to find better AP. + +config ESP_WIFI_ROAMING_PERIODIC_RRM_MONITORING + bool "Send periodic neighbor report request to AP for internal list updation" + depends on ESP_WIFI_11KV_SUPPORT + default y + help + This option will enable station to keep sending RRM neighbor list request to AP and + update its internal list. + +config ESP_WIFI_ROAMING_RRM_MONITOR_TIME + int "Time interval (in seconds) between neighbor report requests to an AP" + depends on ESP_WIFI_ROAMING_PERIODIC_RRM_MONITORING + default 60 + range 0 1500 + help + Enable this to send periodic neighbor report requests to the AP. + These neighbor report requests provide information about other APs in the same managed + network. This information is used for more intelligent roaming. + +config ESP_WIFI_ROAMING_RRM_MONITOR_THRESHOLD + int "Threshold for sending periodic neighbor report requests" + depends on ESP_WIFI_ROAMING_PERIODIC_RRM_MONITORING + default -20 + range -99 0 + help + The RSSI threshold beyond which we start sending periodic neighbor report requests. diff --git a/components/esp_wifi/wifi_apps/roaming_app/src/README.md b/components/esp_wifi/wifi_apps/roaming_app/src/README.md new file mode 100644 index 0000000000..fae12e93a9 --- /dev/null +++ b/components/esp_wifi/wifi_apps/roaming_app/src/README.md @@ -0,0 +1,92 @@ +**Introduction** + +The advanced Wi-Fi roaming app has been written with the intention of simplifying the process of developing applications that will function in a network environment that allows for roaming between the service areas of multiple compatible APs. It gathers basic approaches about how different roaming mechanisms and APIs can be integrated for an efficient solution to roaming and bundles into an easy to user yet highly configurable module. + +**How to use:** + +To enable the roaming app in the menuconfig, please navigate to Component Settings --> Wi-Fi and then enable “Advanced support for Wi-Fi Roaming” + + +**Configuring the advanced Wi-Fi roaming app :** + +After enabling the roaming app in the menuconfig, this roaming app can be configured to best suit your application requirements and the network environment. The configurations are classified into Roaming Triggers (better understood as “Under what conditions to roam?”), Roaming methods (better understood as “How to Roam’), and then some additional configurations such as scanning parameters, backoff times, periodic neighbor report requests etc. + + +**Roaming Triggers: (Roaming Module Settings --> Roaming Triggers)** + +There are broadly two different Roaming triggers you can choose from: + +1) Low RSSI triggered roaming : + +If enabled, in this method the roaming app sets a Wi-Fi Threshold (configured by “Wi-Fi RSSI threshold to trigger roaming”), which when reached by the connection to the current AP, triggers a check for a better AP and if found will trigger roaming. + +Every time the threshold is reached, a new threshold needs to be set at an even lower rssi threshold since if we fail to find a better AP the first time the RSSI threshold is reached there will be no further attempts to find better APs leading to possible disconnection as the only avenue for finding a better AP. The offset by which the next RSSI threshold can be set is decided by “Wi-Fi RSSI threshold to trigger roaming” which defaults to 5. + +Also please note that if the AP we connect to, upon connecting itself has a worse RSSI than the threshold set in the configuration, the new RSSI threshold is set to the current RSSI - offset. Additionally, the RSSI threshold gets reset to the configured value if the RSSI ever goes below the configured threshold by the offset amount. + +2) Periodic Scan based roaming: + +Unlike Low RSSI triggered roaming, which is a bit reactive to the changing network environment, periodic scanning-based roaming (enabled by “Conduct periodic scans to check if a better AP is available”) allows for a more active approach to ensuring that you are connected to the best AP in the network. You can also decide the threshold after which you want to conduct the periodic scans with a default of –20dbm. (Configured by “Threshold at which to begin periodic scanning for a better AP”) + +The intervals at which this periodic scan will take place can also be configured through “Time intervals at which station will initiate a scan”. This defaults to 30 seconds.The RSSI difference between a candidate AP and the current AP, which can be considered as acceptable to initiate roaming can also be configured as the “RSSI difference b/w current AP and a candidate AP to initiate roaming” + +**Please note that at least one of the two Roaming Triggers needs to be enabled.** + + +**Roaming Methods** + +Currently 2 methods of roaming are supported by the roaming app. (Roaming App Settings --> Roaming Methods) + +1) Network Assisted Roaming: + +Enabled by “Support Network Assisted Roaming using 802.11kv”, this method primarily uses the BSS transition Management mechanisms outlined in IEEE 802.11v. It uses Candidates received from neighbor report requests (if enabled, explained later.) and scanning results to Send BSS transition Management Queries to the AP it is currently associated to. Depending on the current radio environment and vendor implementation on the side of the AP, this could then lead to BSS Transition Management Requests and corresponding BSS Transition Management responses which could lead to a seamless transition from one AP to another. For a better understanding of the mechanisms involved and the general implementation please look up the IEEE 802.11v specification and upstream wpa_supplicant’s implementation. + +Please note that for this to work as expected, the APs should support 802.11k & 802.11v and be setup in a network where they are aware of each other. + +2) Legacy Roaming approach. + +Enabled by “Support Legacy roaming approach”, this is designed for scenarios where there is no support for 802.11v amongst the APs of the network. In this scenario, upon determining the better AP to connect to, the station forcibly will disconnect and attempt to connect to the AP with the better AP. + +Please note that if both approaches are enabled, there is an additional configuration (“Retry count after which to switch to legacy roaming”) that allows developers to decide how many times should the station attempt to roam using the Network Assisted roaming methods before switching to using the legacy approach. + +**Please note that at least one of the two roaming methods needs to be selected.** + + +**Scan configuration** + +The scan configuration allows for configuring the parameters for the scans that are conducted in various scenarios during the functioning of the roaming app. Please note that all scans conducted by the roaming app are active scans. + +The minimum and maximum active scanning duration for each channel in milliseconds can be configured through “Minimum duration of active scan time for a station” & “Maximum duration of active scan time for a station”. If connected, the “Home channel dwell time between scanning consecutive channels” configuration decides for how long the station will return to the home channel for Tx of various frames in the buffer, and Rx of frames buffered by the AP. + +Additionally, if channels of operation of the APs that the application designed will work with is known, you can provide this information as an ordered list of comma-separated channels. (configured using “Preferred channel list for scanning”) (for e.g. 1,6,9,11) Only those channels will be scanned in the order mentioned. Keeping in mind that network discovery/scanning is the process that takes up most of the time in roaming as well connecting to an AP, providing such a list could significantly increase the efficiency of the roaming app and process. + +This module can trigger scans due to several reasons. The configuration “Scan results expiry window” decides the duration for which different modules will use the most recent scan results instead of triggering a new scan of their own. + + +**Backoff Time:** + +Successive roaming attempts by multiple roaming triggers simultaneously could lead to performance degradation, hence a backoff time can be configured (“Default time to wait between subsequent roaming attempts”). A roaming attempt can be made using any method only once the configured backoff time has passed since the last roaming attempt. + + +**Periodic Neighbor Report Requests:** + +Neighbor Report requests are a part of the IEEE 802.11k specification, and hence the intended network would need to support and be setup in a way to support its mechanisms. Periodic neighbor report requests provide vital information about the network and other APs in the vicinity that are candidate APs of the same network. This can be enabled using “Send Periodic Neighbor Report requests for updating the internal list”. he frequency of these requests is controlled by “Time interval between Periodic Neighbor report Requests”. There can also be a RSSI threshold (“Threshold for sending periodic neighbor report requests”) after which you wish to consider sending these requests however this is set by default to 0. + + +**Notes :** + +1) Advanced roaming support is disabled by default. + +2) When enabling the advanced roaming support , it is expected that the bssid to connect to is not specifically set by the application. This would defeat the purpose of roaming between different APs of the network. Hence if the BSSID is set (in wifi_config_t.sta) , it will be unset at the first disconnection/connection. + +3) For roaming to work as expected, the APs between which the station is expected to roam must have the same or compatible authmode. These include : + + Open <--> OWE + + PSK based authmodes <--> PSK based authmodes (Does not include WEP) + + Enterprise <--> Enterprise. + + Also note that this module would also prevent roaming to an AP that does not clear the authmode threshold set by wifi_config_t.sta. + +4) As a general guideline for configuring the values of the roaming app it would be helpful to understand the trade-off between roaming and general performance of the overall application. Roaming includes processes such as scanning, disconnecting, connecting, etc which take up time away from other tasks of application. Deciding the several configurations in this module needs to be done while prioritising between being connected to the best AP, seamless transfer between APs and performance based on the requirements of the end application. For example, setting the thresholds of processes outlined above to a lower value or reducing the intervals between the aforementioned periodic tasks would make the roaming more aggressive while interrupting the general operation of the whole application more frequently. diff --git a/components/esp_wifi/wifi_apps/roaming_app/src/roaming_app.c b/components/esp_wifi/wifi_apps/roaming_app/src/roaming_app.c new file mode 100644 index 0000000000..271e0d04c0 --- /dev/null +++ b/components/esp_wifi/wifi_apps/roaming_app/src/roaming_app.c @@ -0,0 +1,822 @@ +/* + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/event_groups.h" +#include "esp_wifi.h" +#include "esp_wnm.h" +#include "esp_rrm.h" +#include "esp_mbo.h" +#include "esp_event.h" +#include "esp_log.h" +#include "esp_mac.h" +#include "nvs_flash.h" +#include +#include "regex.h" +#include +#include "esp_roaming.h" +#include "utils/common.h" +#include "esp_wifi_driver.h" +#include "utils/eloop.h" +#include "rom/ets_sys.h" +#include "common/ieee802_11_defs.h" +static struct roaming_app g_roaming_app; + +typedef void (* scan_done_cb_t)(void *arg, ETS_STATUS status); +extern int esp_wifi_promiscuous_scan_start(wifi_scan_config_t *config, scan_done_cb_t cb); +static void *scan_results_lock = NULL; +#define ROAM_SCAN_RESULTS_LOCK() os_mutex_lock(scan_results_lock) +#define ROAM_SCAN_RESULTS_UNLOCK() os_mutex_unlock(scan_results_lock) + +static void *neighbor_list_lock = NULL; +#define ROAM_NEIGHBOR_LIST_LOCK() os_mutex_lock(neighbor_list_lock) +#define ROAM_NEIGHBOR_LIST_UNLOCK() os_mutex_unlock(neighbor_list_lock) + +static int wifi_post_roam_event(struct cand_bss *bss); +static void determine_best_ap(int8_t rssi_threshold); + +static const char *ROAMING_TAG = "ROAM"; + +static inline long time_diff_sec(struct timeval *a, struct timeval *b) +{ + return (a->tv_sec - b->tv_sec); +} +static void roaming_app_get_ap_info(wifi_ap_record_t *ap_info) +{ + esp_wifi_sta_get_ap_info(ap_info); +#if LOW_RSSI_ROAMING_ENABLED + /* + * If the current rssi is below the configured rssi threshold for + * low rssi based roaming and the current rssi threshold is below that, + * we should reset the rssi threshold back to the configured rssi threshold */ + if ((ap_info->rssi > ROAMING_LOW_RSSI_THRESHOLD) && (g_roaming_app.current_low_rssi_threshold < ROAMING_LOW_RSSI_THRESHOLD)) { + g_roaming_app.current_low_rssi_threshold = ROAMING_LOW_RSSI_THRESHOLD; + esp_wifi_set_rssi_threshold(ROAMING_LOW_RSSI_THRESHOLD); + ESP_LOGD(ROAMING_TAG, "Reset the low rssi threshold back to %d", ROAMING_LOW_RSSI_THRESHOLD); + } +#endif /*LOW_RSSI_ROAMING_ENABLED*/ +} +#if LEGACY_ROAM_ENABLED +static void legacy_roam_clear_bssid_flag(void) +{ + wifi_config_t *config = {0}; + + config = os_zalloc(sizeof(wifi_config_t)); + if (!config) { + ESP_LOGE(ROAMING_TAG, "failed to allocate memory"); + return; + } + + esp_wifi_get_config(WIFI_IF_STA, config); + if (config->sta.bssid_set) { + config->sta.bssid_set = 0; + esp_wifi_set_config(WIFI_IF_STA, config); + } + os_free(config); + ESP_LOGD(ROAMING_TAG, "cleared bssid flag"); +} +#endif /*LEGACY_ROAM_ENABLED*/ + +static int8_t initialize_roaming_event(void) +{ +#if LEGACY_ROAM_ENABLED + /* + * Resetting the Bssid param as it is possible that a previous force + * roam has set config to connect to a specific bssid and now further + * roaming attempts using BTM could lead to a spiral of connecting to + * the previous AP */ + if (g_roaming_app.force_roam_ongoing) { + legacy_roam_clear_bssid_flag(); + } +#endif /*LEGACY_ROAM_ENABLED*/ + return ESP_OK; +} + +#if PERIODIC_RRM_MONITORING +static void init_periodic_rrm_event(void) +{ + if (!neighbor_list_lock) { + neighbor_list_lock = os_recursive_mutex_create(); + if (!neighbor_list_lock) { + ESP_LOGE(ROAMING_TAG, "%s: failed to create roaming neighbor list lock", __func__); + } + } + ESP_LOGV(ROAMING_TAG, "Initialised Periodic RRM Monitoring event!"); + g_roaming_app.periodic_rrm_active = true; + if (eloop_register_timeout(RRM_MONITOR_TIME, 0, roaming_app_periodic_rrm_internal_handler, NULL, NULL)) { + ESP_LOGE(ROAMING_TAG, "Could not register periodic neighbor report event."); + } +} +#endif /*PERIODIC_RRM_MONITORING*/ + +#if PERIODIC_SCAN_MONITORING +static void init_periodic_scan_roam_event(void) +{ + ESP_LOGV(ROAMING_TAG, "Initialised Periodic Scan Roam event!"); + g_roaming_app.periodic_scan_active = true; + if (eloop_register_timeout(SCAN_MONITOR_INTERVAL, 0, roaming_app_periodic_scan_internal_handler, NULL, NULL)) { + ESP_LOGE(ROAMING_TAG, "Could not register periodic scan monitoring event"); + } +} +#endif /*PERIODIC_SCAN_ROAM_MONITORING*/ + +static void roaming_app_disconnected_event_handler(void* arg, esp_event_base_t event_base, + int32_t event_id, void* event_data) +{ + +#if PERIODIC_RRM_MONITORING + g_roaming_app.periodic_rrm_active = false; +#endif /*PERIODIC_RRM_MONITORING*/ + +#if PERIODIC_SCAN_MONITORING + g_roaming_app.periodic_scan_active = false; +#endif /*PERIODIC_SCAN_MONITORING*/ + + wifi_event_sta_disconnected_t *disconn = event_data; + ESP_LOGD(ROAMING_TAG, "station got disconnected reason=%d", disconn->reason); + if (disconn->reason == WIFI_REASON_ROAMING) { + ESP_LOGD(ROAMING_TAG, "station roaming, do nothing"); + } else { +#if LEGACY_ROAM_ENABLED + /* + * Resetting the Bssid param as it is possible that a previous force + * roam has set config to connect to a specific bssid and now further + * roaming attempts using BTM could lead to a spiral of connecting to + * the previous AP */ + if (g_roaming_app.force_roam_ongoing) { + legacy_roam_clear_bssid_flag(); + } +#endif /*LEGACY_ROAM_ENABLED*/ + esp_wifi_connect(); + } +} + +static void roaming_app_connected_event_handler(void* arg, esp_event_base_t event_base, + int32_t event_id, void* event_data) +{ +#if LOW_RSSI_ROAMING_ENABLED + roaming_app_get_ap_info(&g_roaming_app.ap_info); + g_roaming_app.scan_params.ssid = g_roaming_app.ap_info.ssid; + if (g_roaming_app.ap_info.rssi < ROAMING_LOW_RSSI_THRESHOLD) { + /* To ensure that the threshold is set to one offset below the current AP RSSI + * in case, the AP is already below the RSSI threshold */ + g_roaming_app.current_low_rssi_threshold = g_roaming_app.ap_info.rssi - RSSI_THRESHOLD_REDUCTION_OFFSET; + } else { + g_roaming_app.current_low_rssi_threshold = ROAMING_LOW_RSSI_THRESHOLD; + } + ESP_LOGD(ROAMING_TAG, "setting rssi threshold as %d", g_roaming_app.current_low_rssi_threshold); + esp_wifi_set_rssi_threshold(g_roaming_app.current_low_rssi_threshold); +#endif /*LOW_RSSI_ROAMING_ENABLED*/ + g_roaming_app.rrm_support = esp_rrm_is_rrm_supported_connection(); + g_roaming_app.btm_support = esp_wnm_is_btm_supported_connection(); + ESP_LOGD(ROAMING_TAG, "Station connected, RRM %ssupported, BTM %ssupported", + g_roaming_app.rrm_support ? " " : "not ", + g_roaming_app.btm_support ? " " : "not "); + gettimeofday(&g_roaming_app.last_roamed_time, NULL); + if (!initialize_roaming_event()) { +#if PERIODIC_RRM_MONITORING + if (g_roaming_app.rrm_support) { + init_periodic_rrm_event(); + } +#endif /*PERIODIC_RRM_MONITORING*/ +#if PERIODIC_SCAN_MONITORING + init_periodic_scan_roam_event(); +#endif /*PERIODIC_SCAN_ROAM_MONITORING*/ + ESP_LOGD(ROAMING_TAG, "Initialised initialise roaming events!"); + } else { + ESP_LOGE(ROAMING_TAG, "Failed to Initialise roaming events"); + } +#if LEGACY_ROAM_ENABLED + g_roaming_app.force_roam_ongoing = true; +#endif /*LEGACY_ROAM_ENABLED*/ +} +#define MAX_NEIGHBOR_LEN 512 +#if PERIODIC_RRM_MONITORING +static char * get_btm_neighbor_list(uint8_t *report, size_t report_len) +{ + size_t len = 0; + const uint8_t *data; + int ret = 0; + + /* + * Neighbor Report element (IEEE P802.11-REVmc/D5.0) + * BSSID[6] + * BSSID Information[4] + * Operating Class[1] + * Channel Number[1] + * PHY Type[1] + * Optional Subelements[variable] + */ +#define NR_IE_MIN_LEN (ETH_ALEN + 4 + 1 + 1 + 1) + + if (!report || report_len == 0) { + ESP_LOGE(ROAMING_TAG, "RRM neighbor report is not valid"); + return NULL; + } + + char *buf = os_calloc(1, MAX_NEIGHBOR_LEN); + if (!buf) { + ESP_LOGE(ROAMING_TAG, "get_btm_list : Memory alloc failed for buf"); + return NULL; + } + data = report; + while (report_len >= 2 + NR_IE_MIN_LEN) { + const uint8_t *nr; + uint8_t nr_len = data[1]; + const uint8_t *pos = data, *end; + + if (pos[0] != WLAN_EID_NEIGHBOR_REPORT || + nr_len < NR_IE_MIN_LEN) { + ESP_LOGD(ROAMING_TAG, "CTRL: Invalid Neighbor Report element: id=%u len=%u", + data[0], nr_len); + ret = -1; + goto cleanup; + } + + if (2U + nr_len > report_len) { + ESP_LOGD(ROAMING_TAG, "CTRL: Invalid Neighbor Report element: id=%u len=%zu nr_len=%u", + data[0], report_len, nr_len); + ret = -1; + goto cleanup; + } + pos += 2; + end = pos + nr_len; + + nr = pos; + pos += NR_IE_MIN_LEN; + + while (end - pos > 2) { + uint8_t s_len; + s_len = *pos++; + if (s_len > end - pos) { + ret = -1; + goto cleanup; + } + pos += s_len; + } + ESP_LOGD(ROAMING_TAG, "RMM neighbor report bssid=" MACSTR + " info=0x%" PRIx32 " op_class=%u chan=%u phy_type=%u", + MAC2STR(nr), WPA_GET_LE32(nr + ETH_ALEN), + nr[ETH_ALEN + 4], nr[ETH_ALEN + 5], + nr[ETH_ALEN + 6]); + /* neighbor start */ + len += snprintf(buf + len, MAX_NEIGHBOR_LEN - len, " neighbor="); + /* bssid */ + len += snprintf(buf + len, MAX_NEIGHBOR_LEN - len, MACSTR, MAC2STR(nr)); + /* , */ + len += snprintf(buf + len, MAX_NEIGHBOR_LEN - len, ","); + /* bssid info */ + len += snprintf(buf + len, MAX_NEIGHBOR_LEN - len, "0x%04" PRIx32 "", WPA_GET_LE32(nr + ETH_ALEN)); + len += snprintf(buf + len, MAX_NEIGHBOR_LEN - len, ","); + /* operating class */ + len += snprintf(buf + len, MAX_NEIGHBOR_LEN - len, "%u", nr[ETH_ALEN + 4]); + len += snprintf(buf + len, MAX_NEIGHBOR_LEN - len, ","); + /* channel number */ + len += snprintf(buf + len, MAX_NEIGHBOR_LEN - len, "%u", nr[ETH_ALEN + 5]); + len += snprintf(buf + len, MAX_NEIGHBOR_LEN - len, ","); + /* phy type */ + len += snprintf(buf + len, MAX_NEIGHBOR_LEN - len, "%u", nr[ETH_ALEN + 6]); + /* optional elements, skip */ + + data = end; + report_len -= 2 + nr_len; + } + +cleanup: + if (ret < 0) { + os_free(buf); + buf = NULL; + } + return buf; +} +static void roaming_app_neighbor_report_recv_handler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data) +{ + if (!g_roaming_app.rrm_request_active) { + ESP_LOGV(ROAMING_TAG, "Not the response for our Neighbor Report Request"); + return; + } + g_roaming_app.rrm_request_active = false; + + if (!event_data) { + ESP_LOGE(ROAMING_TAG, "No data received for neighbor report"); + return; + } + + wifi_event_neighbor_report_t *neighbor_report_event = (wifi_event_neighbor_report_t*)event_data; + ESP_LOGD(ROAMING_TAG, "Received cb for Neighbor Report Request"); + + uint8_t *pos = (uint8_t *)neighbor_report_event->report; + if (!pos) { + ESP_LOGE(ROAMING_TAG, "Neighbor report is empty"); + return; + } + + uint8_t report_len = neighbor_report_event->report_len; + /* dump report info */ + ESP_LOGD(ROAMING_TAG, "rrm: neighbor report len=%d", report_len); + ESP_LOG_BUFFER_HEXDUMP(ROAMING_TAG, pos, report_len, ESP_LOG_DEBUG); + + ROAM_NEIGHBOR_LIST_LOCK(); + if (g_roaming_app.btm_neighbor_list) { + os_free(g_roaming_app.btm_neighbor_list); + g_roaming_app.btm_neighbor_list = NULL; + } + /* create neighbor list */ + g_roaming_app.btm_neighbor_list = get_btm_neighbor_list(pos + 1, report_len - 1); + ROAM_NEIGHBOR_LIST_UNLOCK(); + +} +#endif /*PERIODIC_RRM_MONITORING*/ +#if LOW_RSSI_ROAMING_ENABLED +static void roaming_app_rssi_low_handler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data) +{ + wifi_event_bss_rssi_low_t *event = event_data; + ESP_LOGI(ROAMING_TAG, "%s:bss rssi is=%ld", __func__, event->rssi); + + roaming_app_get_ap_info(&g_roaming_app.ap_info); + determine_best_ap(0); + g_roaming_app.current_low_rssi_threshold -= RSSI_THRESHOLD_REDUCTION_OFFSET; + ESP_LOGD(ROAMING_TAG, "Resetting RSSI Threshold to %d", g_roaming_app.current_low_rssi_threshold); + esp_wifi_set_rssi_threshold(g_roaming_app.current_low_rssi_threshold); + +} +#endif + +#if NETWORK_ASSISTED_ROAMING_ENABLED +static void trigger_network_assisted_roam(void) +{ +#if PERIODIC_RRM_MONITORING + ROAM_NEIGHBOR_LIST_LOCK(); +#endif /*PERIODIC_RRM_MONITORING*/ + if (esp_wnm_send_bss_transition_mgmt_query(REASON_RSSI, g_roaming_app.btm_neighbor_list, 1) < 0) { + ESP_LOGD(ROAMING_TAG, "failed to send btm query"); + } +#if PERIODIC_RRM_MONITORING + ROAM_NEIGHBOR_LIST_UNLOCK(); +#endif /*PERIODIC_RRM_MONITORING*/ + ESP_LOGD(ROAMING_TAG, "Sent BTM Query"); + gettimeofday(&g_roaming_app.last_roamed_time, NULL); + g_roaming_app.btm_attempt++; + +} +#endif /*NETWORK_ASSISTED_ROAMING*/ + +#if LEGACY_ROAM_ENABLED +static void trigger_legacy_roam(struct cand_bss *bss) +{ + wifi_config_t wifi_cfg = {0}; + esp_wifi_get_config(WIFI_IF_STA, &wifi_cfg); + wifi_cfg.sta.channel = bss->channel; + wifi_cfg.sta.bssid_set = true; + os_memcpy(wifi_cfg.sta.bssid, bss->bssid, ETH_ALEN); + esp_wifi_internal_issue_disconnect(WIFI_REASON_BSS_TRANSITION_DISASSOC); + esp_wifi_set_config(WIFI_IF_STA, &wifi_cfg); + esp_wifi_connect(); + ESP_LOGI(ROAMING_TAG, "Disconnecting and connecting to "MACSTR" on account of better rssi",MAC2STR(bss->bssid)); + gettimeofday(&g_roaming_app.last_roamed_time, NULL); + g_roaming_app.force_roam_ongoing = true; +} +#endif /*LEGACY_ROAM_ENABLED*/ + +void roaming_app_trigger_roam(struct cand_bss *bss) +{ + struct timeval now; + gettimeofday(&now, NULL); + ESP_LOGD(ROAMING_TAG,"Processing trigger roaming request."); + if (time_diff_sec(&now, &g_roaming_app.last_roamed_time) < ROAMING_BACKOFF_TIME ) { + ESP_LOGD(ROAMING_TAG,"Ignoring request as time difference to last request is %ld",time_diff_sec(&now, &g_roaming_app.last_roamed_time)); + goto free_bss; + } +#if NETWORK_ASSISTED_ROAMING_ENABLED + if (g_roaming_app.btm_support) { +#if LEGACY_ROAM_ENABLED && NETWORK_ASSISTED_ROAMING_ENABLED + if (g_roaming_app.btm_attempt <= BSS_TM_RETRY_COUNT) { +#endif + trigger_network_assisted_roam(); + goto free_bss; +#if LEGACY_ROAM_ENABLED && NETWORK_ASSISTED_ROAMING_ENABLED + } else { + ESP_LOGD(ROAMING_TAG, "Not Sending BTM query as this method has failed too many times."); + g_roaming_app.btm_attempt = 0; + } +#endif + } +#endif /*NETWORK_ASSISTED_ROAMING_ENABLED*/ +#if LEGACY_ROAM_ENABLED + trigger_legacy_roam(bss); +#endif /*LEGACY_ROAM_ENABLED*/ +free_bss : + os_free(bss); +} + +void roaming_app_trigger_roam_internal_handler(void *ctx, void *data) +{ + if (!data) { + ESP_LOGE(ROAMING_TAG, "No data received for roaming event"); + } else { + roaming_app_trigger_roam((struct cand_bss *)data); + } + +} + +static int wifi_post_roam_event(struct cand_bss *bss) +{ + if (bss) { + struct cand_bss *cand_bss = (struct cand_bss *)os_zalloc(sizeof(struct cand_bss)); + if (!cand_bss) { + ESP_LOGE(ROAMING_TAG, "Cannot allocate data for roaming event"); + return -1; + } + os_memcpy(cand_bss, bss, sizeof(struct cand_bss)); + /* trigger the roaming event */ + if (eloop_register_timeout(0, 0, roaming_app_trigger_roam_internal_handler, NULL, (void*)cand_bss)) { + ESP_LOGE(ROAMING_TAG, "Could not register roaming event."); + os_free(cand_bss); + return -1; + } + } else { + ESP_LOGE(ROAMING_TAG, "Cannot trigger roaming event without any candidate APs"); + return -1; + } + return 0; + +} + +void print_ap_records(struct scanned_ap_info *ap_info) +{ + ESP_LOGD(ROAMING_TAG, "Scanned AP List"); + for (int i = 0; i < ap_info->current_count ; i++) { + ESP_LOGD(ROAMING_TAG, "%d. ssid : %s bssid :"MACSTR" channel : %d rssi : %d authmode : %d", i, + ap_info->ap_records[i].ssid,MAC2STR(ap_info->ap_records[i].bssid), + ap_info->ap_records[i].primary,ap_info->ap_records[i].rssi, ap_info->ap_records[i].authmode); + } + +} + + +#if PERIODIC_RRM_MONITORING +static void periodic_rrm_request(struct timeval *now) +{ + roaming_app_get_ap_info(&g_roaming_app.ap_info); + if (esp_rrm_is_rrm_supported_connection() && (g_roaming_app.ap_info.rssi < RRM_MONITOR_RSSI_THRESHOLD)) { + if (esp_rrm_send_neighbor_report_request() < 0) { + ESP_LOGE(ROAMING_TAG, "failed to send neighbor report request"); + return; + } + g_roaming_app.rrm_request_active = true; + } +} +#else +static void periodic_rrm_request(struct timeval *now) { } +#endif + +static bool candidate_security_match(wifi_ap_record_t candidate) +{ + wifi_auth_mode_t curr_auth = g_roaming_app.ap_info.authmode; + wifi_auth_mode_t cand_auth = candidate.authmode; + ESP_LOGV(ROAMING_TAG, "Cand authmode : %d, Current Authmode : %d", cand_auth, curr_auth); + if (cand_auth == curr_auth) { + ESP_LOGV(ROAMING_TAG, "Authmode matched!"); + return true; + } + wifi_config_t wifi_cfg = {0}; + esp_wifi_get_config(WIFI_IF_STA, &wifi_cfg); + if (wifi_cfg.sta.owe_enabled && OWE_COMPATIBLE(curr_auth, cand_auth)) { + /* + * OWE <--> Open allowed if threshold is Open + */ + if (wifi_cfg.sta.threshold.authmode == WIFI_AUTH_OPEN) { + ESP_LOGV(ROAMING_TAG, "transition between OWE and open permitted"); + return true; + } else { + ESP_LOGV(ROAMING_TAG, "transition between OWE and open not permitted"); + return false; + } + } else if (wifi_cfg.sta.threshold.authmode > cand_auth) { + /* If the authmode of the candidate AP is less than our threshold, it + * will fail during connection*/ + ESP_LOGV(ROAMING_TAG, "Authmode threshold failure %d -> %d", wifi_cfg.sta.threshold.authmode, cand_auth); + return false; + } else if (PSK_COMPATIBLE(curr_auth, cand_auth)) { + /* + * PSK based authmodes are compatible with each other for roaming + */ + ESP_LOGV(ROAMING_TAG, "Roaming between a PSK APs"); + return true; + } + return false; +} + +static bool candidate_profile_match(wifi_ap_record_t candidate) +{ + return candidate_security_match(candidate); +} +/* Remember to always call this function with the ROAM_SCAN_RESULTS_LOCK */ +static void parse_scan_results_and_roam(void) +{ + int8_t rssi_threshold = g_roaming_app.current_rssi_threshold; + uint8_t best_rssi_diff = rssi_threshold; + struct cand_bss *best_ap = NULL; + int8_t rssi_diff = 0; + uint8_t i; + int8_t best_ap_index = -1; + wifi_ap_record_t ap_info; + roaming_app_get_ap_info(&ap_info); + for (i = 0; i < g_roaming_app.scanned_aps.current_count; i++) { + rssi_diff = g_roaming_app.scanned_aps.ap_records[i].rssi - ap_info.rssi; + ESP_LOGD(ROAMING_TAG, "The difference between ("MACSTR", "MACSTR") with rssi (%d,%d) is : %d while the threshold is %d and the best rssi diff yet is %d, thecand_auth is %d", + MAC2STR(g_roaming_app.scanned_aps.ap_records[i].bssid),MAC2STR(ap_info.bssid), + g_roaming_app.scanned_aps.ap_records[i].rssi, ap_info.rssi, + rssi_diff, rssi_threshold, best_rssi_diff, g_roaming_app.scanned_aps.ap_records[i].authmode); + if ((memcmp(g_roaming_app.scanned_aps.ap_records[i].bssid, ap_info.bssid, ETH_ALEN) != 0) && + candidate_profile_match(g_roaming_app.scanned_aps.ap_records[i]) && rssi_diff > best_rssi_diff ) { + best_rssi_diff = rssi_diff; + best_ap_index = i; + } + } + if (best_ap_index >= 0) { + best_ap = (struct cand_bss*)os_zalloc(sizeof(struct cand_bss)); + if (!best_ap) { + ESP_LOGE(ROAMING_TAG,"Memory Allocation for candidate bss failed"); + return; + } + os_memcpy(best_ap->bssid,g_roaming_app.scanned_aps.ap_records[best_ap_index].bssid , ETH_ALEN); + best_ap->channel = g_roaming_app.scanned_aps.ap_records[best_ap_index].primary; + } + + if (best_ap) { + ESP_LOGI(ROAMING_TAG,"Found a better AP "MACSTR" at channel %d", MAC2STR(best_ap->bssid), best_ap->channel); + if (wifi_post_roam_event(best_ap)) { + ESP_LOGE(ROAMING_TAG, "Posting of roaming event failed"); + } + os_free(best_ap); + } else { + ESP_LOGI(ROAMING_TAG, "Could not find a better AP with the threshold set to %d", g_roaming_app.current_rssi_threshold + 1); + } +} + +static void scan_done_event_handler(void *arg, ETS_STATUS status) +{ + if (status == ETS_OK) { + ROAM_SCAN_RESULTS_LOCK(); + ESP_LOGD(ROAMING_TAG, "Scan Done properly"); + g_roaming_app.scanned_aps.current_count = MAX_CANDIDATE_COUNT; + esp_wifi_scan_get_ap_records(&g_roaming_app.scanned_aps.current_count, g_roaming_app.scanned_aps.ap_records); + print_ap_records(&g_roaming_app.scanned_aps); + parse_scan_results_and_roam(); + g_roaming_app.scan_ongoing = false; + ROAM_SCAN_RESULTS_UNLOCK(); + } else { + ESP_LOGD(ROAMING_TAG, "Scan Done with error %d ", status); + } +} +static void conduct_scan(void) +{ + /* Update scan time in global structure */ + gettimeofday(&g_roaming_app.scanned_aps.time, NULL); + /* Issue scan */ + os_memset(&g_roaming_app.scanned_aps, 0, sizeof(struct scanned_ap_info)); + if (esp_wifi_promiscuous_scan_start(&g_roaming_app.scan_params, scan_done_event_handler) < 0) { + ESP_LOGE(ROAMING_TAG, "failed to issue scan"); + return; + } + ESP_LOGI(ROAMING_TAG, "Issued Scan"); +} + +static void determine_best_ap(int8_t rssi_threshold) +{ + struct timeval now; + gettimeofday(&now, NULL); + ROAM_SCAN_RESULTS_LOCK(); + /* If the scan results are recent enough or a scan is already ongoing we should not trigger a new scan */ + if (!g_roaming_app.scan_ongoing) { + g_roaming_app.scan_ongoing = true; + g_roaming_app.current_rssi_threshold = rssi_threshold; + if (time_diff_sec(&now,&g_roaming_app.scanned_aps.time) > SCAN_RESULTS_USABILITY_WINDOW) { + conduct_scan(); + } else { + parse_scan_results_and_roam(); + g_roaming_app.scan_ongoing = false; + } + } else if(rssi_threshold < g_roaming_app.current_rssi_threshold) { + g_roaming_app.current_rssi_threshold = rssi_threshold; + } + ROAM_SCAN_RESULTS_UNLOCK(); +} +#if PERIODIC_SCAN_MONITORING +static void periodic_scan_roam(struct timeval *now) +{ +#if NETWORK_ASSISTED_ROAMING_ENABLED && !LEGACY_ROAM_ENABLED + /* + * In case we support only network assisted roaming, there is no need to scan + * if we currently have over 10secs to go for backoff time to expire + * as the results produced by a scan at this time would not be used by + * supplicant to build candidate lists. + * */ + if (time_diff_sec(now, &g_roaming_app.last_roamed_time) < ROAMING_BACKOFF_TIME - SUPPLICANT_CANDIDATE_LIST_EXPIRY) { + return; + } +#endif /*NETWORK_ASSISTED_ROAMING_ENABLED && !LEGACY_ROAM_ENABLED*/ + /* If the current RSSI is not worse than the configured threshold + * for station initiated roam, then do not trigger roam */ + roaming_app_get_ap_info(&g_roaming_app.ap_info); + if (g_roaming_app.ap_info.rssi > SCAN_MONITOR_RSSI_THRESHOLD) { + return; + } + + determine_best_ap(SCAN_ROAM_RSSI_DIFF - 1); +} +#endif /*PERIODIC_SCAN_MONITORING*/ + +#if PERIODIC_RRM_MONITORING +void roaming_app_periodic_rrm_internal_handler(void *data, void *ctx) +{ + struct timeval now; + if (g_roaming_app.periodic_rrm_active) { + ESP_LOGD(ROAMING_TAG,"Triggered periodic rrm event!"); + gettimeofday(&now, NULL); + /* This will be done every RRM_MONITOR_TIME */ + periodic_rrm_request(&now); + + if (eloop_register_timeout(RRM_MONITOR_TIME, 0, roaming_app_periodic_rrm_internal_handler, NULL, NULL)) { + ESP_LOGE(ROAMING_TAG,"Could not register periodic neighbor report request event."); + } + } +} +#endif /*PERIODIC_RRM_MONITORING*/ + +#if PERIODIC_SCAN_MONITORING +void roaming_app_periodic_scan_internal_handler(void *data, void *ctx) +{ + struct timeval now; + if (g_roaming_app.periodic_scan_active) { + ESP_LOGD(ROAMING_TAG,"Started the periodic scan roam event!"); + gettimeofday(&now, NULL); + + /* This will be done every SCAN_MONITOR_INTERVAL */ + periodic_scan_roam(&now); + + if (eloop_register_timeout(SCAN_MONITOR_INTERVAL, 0, roaming_app_periodic_scan_internal_handler, NULL, NULL)) { + ESP_LOGE(ROAMING_TAG,"Could not register periodic scan event."); + } + } +} +#endif /*PERIODIC_SCAN_ROAM_MONITORING*/ +static bool validate_scan_chan_list(const char* scan_chan_list) +{ + regex_t regex; + const char* pattern = "^[0-9]+(,[0-9]+)*$"; + uint8_t ret = regcomp(®ex, pattern, REG_EXTENDED); + if (ret != 0) { + ESP_LOGE(ROAMING_TAG, "Regex compilation failed."); + return false; + } + + ret = regexec(®ex, scan_chan_list, 0, NULL, 0); + regfree(®ex); + + if (ret == REG_NOMATCH) { + return false; + } + + /* Check for consecutive commas or comma at start/end */ + if (strstr(scan_chan_list, ",,") != NULL || scan_chan_list[0] == ',' || scan_chan_list[strlen(scan_chan_list) - 1] == ',') { + return false; + } + + return true; +} + +static int8_t parse_scan_chan_list(void) +{ + int8_t ret = 0; + char *scan_chan_string = NULL; + if (validate_scan_chan_list(SCAN_PREFERRED_CHAN_LIST) == false) { + ESP_LOGE(ROAMING_TAG, "scan chan list validation failed."); + ret = -1; + goto cleanup; + } + scan_chan_string = (char *)os_zalloc(sizeof(char) * strlen(SCAN_PREFERRED_CHAN_LIST) + 1); + if (scan_chan_string == NULL) { + ESP_LOGE(ROAMING_TAG, "Memory allocation failed."); + ret = -1; + goto cleanup; + } + strlcpy(scan_chan_string, SCAN_PREFERRED_CHAN_LIST, strlen(SCAN_PREFERRED_CHAN_LIST) + 1); + char* token; + token = strsep(&scan_chan_string, ","); + + g_roaming_app.scan_params.channel_bitmap.ghz_2_channels = 0; + + while (token != NULL) { + uint8_t channel = atoi(token); + /* Check if the number is within the required range */ + if (channel >= 1 && channel <= 14) { + /* Check if the number is already present in the array */ + g_roaming_app.scan_params.channel_bitmap.ghz_2_channels |= (1 << channel); + } else { + ESP_LOGE(ROAMING_TAG, "Channel out of range: %d", channel); + ret = -1; + goto cleanup; + } + token = strsep(&scan_chan_string, ","); + } + +cleanup: + if (scan_chan_string) { + os_free(scan_chan_string); + } + return ret; +} + +esp_err_t init_scan_params(void) +{ + if (!scan_results_lock) { + scan_results_lock = os_recursive_mutex_create(); + if (!scan_results_lock) { + ESP_LOGE(ROAMING_TAG, "%s: failed to create scan results lock", __func__); + return ESP_FAIL; + } + } + + if (strcmp(DEFAULT_PREFERRED_SCAN_CHAN_LIST, SCAN_PREFERRED_CHAN_LIST)) { + ESP_ERROR_CHECK(parse_scan_chan_list()); + } + + g_roaming_app.scan_params.scan_type = 0; + g_roaming_app.scan_params.scan_time.active.min = SCAN_TIME_MIN_DURATION; + g_roaming_app.scan_params.scan_time.active.max = SCAN_TIME_MAX_DURATION; + g_roaming_app.scan_params.home_chan_dwell_time = HOME_CHANNEL_DWELL_TIME; + gettimeofday(&g_roaming_app.scanned_aps.time, NULL); + return ESP_OK; +} + +void init_roaming_app(void) +{ +#if !LOW_RSSI_ROAMING_ENABLED && !PERIODIC_SCAN_MONITORING + ESP_LOGE(ROAMING_TAG, "No roaming method enabled. Roaming app cannot be initialized"); + return; +#endif + +#if !NETWORK_ASSISTED_ROAMING_ENABLED && !LEGACY_ROAM_ENABLED + ESP_LOGE(ROAMING_TAG, "No roaming method enabled. Roaming app cannot be initialized"); + return; +#endif + ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, WIFI_EVENT_STA_CONNECTED, roaming_app_connected_event_handler, NULL)); + ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, WIFI_EVENT_STA_DISCONNECTED, roaming_app_disconnected_event_handler, NULL)); +#if LOW_RSSI_ROAMING_ENABLED + ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, WIFI_EVENT_STA_BSS_RSSI_LOW, + &roaming_app_rssi_low_handler, NULL)); +#endif /*ROAMING_LOW_RSSI_THRESHOLD*/ + ESP_ERROR_CHECK(init_scan_params()); +#if PERIODIC_RRM_MONITORING + ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, WIFI_EVENT_STA_NEIGHBOR_REP, + &roaming_app_neighbor_report_recv_handler, NULL)); +#endif /*PERIODIC_RRM_MONITORING*/ + ESP_LOGI(ROAMING_TAG, "Roaming app initialization done"); +} + +void deinit_roaming_app(void) +{ +#if !LOW_RSSI_ROAMING_ENABLED && !PERIODIC_SCAN_MONITORING + ESP_LOGE(ROAMING_TAG, "No roaming trigger enabled. Roaming app cannot be de-initialized"); + return; +#endif + +#if !NETWORK_ASSISTED_ROAMING_ENABLED && !LEGACY_ROAM_ENABLED + ESP_LOGE(ROAMING_TAG, "No roaming trigger enabled. Roaming app cannot be de-initialized"); + return; +#endif + /* Unregister Event handlers */ + ESP_ERROR_CHECK(esp_event_handler_unregister(WIFI_EVENT, WIFI_EVENT_STA_CONNECTED, roaming_app_connected_event_handler)); + ESP_ERROR_CHECK(esp_event_handler_unregister(WIFI_EVENT, WIFI_EVENT_STA_DISCONNECTED, roaming_app_disconnected_event_handler)); +#if LOW_RSSI_ROAMING_ENABLED + ESP_ERROR_CHECK(esp_event_handler_unregister(WIFI_EVENT, WIFI_EVENT_STA_BSS_RSSI_LOW, + &roaming_app_rssi_low_handler)); +#endif /*ROAMING_LOW_RSSI_THRESHOLD*/ + +#if PERIODIC_RRM_MONITORING + ESP_ERROR_CHECK(esp_event_handler_unregister(WIFI_EVENT, WIFI_EVENT_STA_NEIGHBOR_REP, + &roaming_app_neighbor_report_recv_handler)); +#endif /*PERIODIC_RRM_MONITORING*/ + + /* Disabling the periodic scan and RRM events */ +#if PERIODIC_RRM_MONITORING + g_roaming_app.periodic_rrm_active = false; +#endif /*PERIODIC_RRM_MONITORING*/ + +#if PERIODIC_SCAN_MONITORING + g_roaming_app.periodic_scan_active = false; +#endif /*PERIODIC_SCAN_MONITORING*/ + os_mutex_delete(neighbor_list_lock); + neighbor_list_lock = NULL; + os_mutex_delete(scan_results_lock); + scan_results_lock = NULL; +} diff --git a/components/wpa_supplicant/CMakeLists.txt b/components/wpa_supplicant/CMakeLists.txt index 0913bd5e06..ebbe874f2f 100644 --- a/components/wpa_supplicant/CMakeLists.txt +++ b/components/wpa_supplicant/CMakeLists.txt @@ -226,6 +226,7 @@ idf_component_register(SRCS "${srcs}" "${esp_srcs}" "${tls_src}" "${roaming_src} "${crypto_src}" "${mbo_src}" "${dpp_src}" "${wps_registrar_src}" INCLUDE_DIRS include port/include esp_supplicant/include PRIV_INCLUDE_DIRS src src/utils esp_supplicant/src src/crypto + ../esp_wifi/wifi_apps/roaming_app/include LDFRAGMENTS ${linker_fragments} PRIV_REQUIRES mbedtls esp_timer esp_wifi) diff --git a/components/wpa_supplicant/esp_supplicant/src/esp_common.c b/components/wpa_supplicant/esp_supplicant/src/esp_common.c index f84821d699..97f41ba758 100644 --- a/components/wpa_supplicant/esp_supplicant/src/esp_common.c +++ b/components/wpa_supplicant/esp_supplicant/src/esp_common.c @@ -23,6 +23,9 @@ #include "rsn_supp/wpa_i.h" #include "rsn_supp/wpa.h" #include "esp_private/wifi.h" +#if CONFIG_ESP_WIFI_ENABLE_ROAMING_APP +#include "esp_roaming.h" +#endif /* Utility Functions */ esp_err_t esp_supplicant_str_to_mac(const char *str, uint8_t dest[6]) @@ -300,7 +303,7 @@ static int ieee80211_handle_rx_frm(u8 type, u8 *frame, size_t len, u8 *sender, #ifdef CONFIG_MBO bool mbo_bss_profile_match(u8 *bssid) { - /* Incase supplicant wants drivers to skip this BSS, return false */ + /* In case supplicant wants drivers to skip this BSS, return false */ struct wpa_bss *bss = wpa_bss_get_bssid(&g_wpa_supp, bssid); if (!bss) { return true; @@ -419,6 +422,9 @@ void esp_supplicant_common_deinit(void) } s_supplicant_task_init_done = false; #endif /* CONFIG_SUPPLICANT_TASK */ +#if CONFIG_ESP_WIFI_ENABLE_ROAMING_APP + deinit_roaming_app(); +#endif #endif /* defined(CONFIG_IEEE80211KV) || defined(CONFIG_IEEE80211R) */ } @@ -732,6 +738,16 @@ static uint8_t get_extended_caps_ie(uint8_t *ie, size_t len) return ext_caps_ie_len + 2; } +#else /* CONFIG_IEEE80211KV */ +bool esp_rrm_is_rrm_supported_connection(void) +{ + return false; +} + +bool esp_wnm_is_btm_supported_connection(void) +{ + return false; +} #endif /* CONFIG_IEEE80211KV */ void esp_set_scan_ie(void) diff --git a/components/wpa_supplicant/esp_supplicant/src/esp_wpa_main.c b/components/wpa_supplicant/esp_supplicant/src/esp_wpa_main.c index acde48189b..eb1066d557 100644 --- a/components/wpa_supplicant/esp_supplicant/src/esp_wpa_main.c +++ b/components/wpa_supplicant/esp_supplicant/src/esp_wpa_main.c @@ -39,6 +39,9 @@ #include "ap/sta_info.h" #include "wps/wps_defs.h" #include "wps/wps.h" +#if CONFIG_ESP_WIFI_ENABLE_ROAMING_APP +#include "esp_roaming.h" +#endif #ifdef CONFIG_DPP #include "common/dpp.h" @@ -469,6 +472,10 @@ int esp_supplicant_init(void) ret = esp_wifi_internal_wapi_init(); #endif +#if CONFIG_ESP_WIFI_ENABLE_ROAMING_APP + init_roaming_app(); +#endif + return ret; } diff --git a/docs/doxygen/Doxyfile b/docs/doxygen/Doxyfile index 98e9c5237f..7355144003 100644 --- a/docs/doxygen/Doxyfile +++ b/docs/doxygen/Doxyfile @@ -219,7 +219,7 @@ INPUT = \ $(PROJECT_PATH)/components/wpa_supplicant/esp_supplicant/include/esp_rrm.h \ $(PROJECT_PATH)/components/wpa_supplicant/esp_supplicant/include/esp_wnm.h \ $(PROJECT_PATH)/components/wpa_supplicant/esp_supplicant/include/esp_wps.h \ - $(PROJECT_PATH)/components/esp_wifi/wifi_apps/include/esp_nan.h \ + $(PROJECT_PATH)/components/esp_wifi/wifi_apps/nan_app/include/esp_nan.h \ $(PROJECT_PATH)/components/esp-tls/esp_tls_errors.h \ $(PROJECT_PATH)/components/esp-tls/esp_tls.h \ $(PROJECT_PATH)/components/fatfs/diskio/diskio_impl.h \ @@ -310,7 +310,7 @@ INPUT = \ $(PROJECT_PATH)/components/wpa_supplicant/esp_supplicant/include/esp_dpp.h \ $(PROJECT_PATH)/components/wpa_supplicant/esp_supplicant/include/esp_supplicant_utils.h \ -## Target specific headers are in seperate Doxyfile files +## Target specific headers are in separate Doxyfile files @INCLUDE = $(PROJECT_PATH)/docs/doxygen/Doxyfile_$(IDF_TARGET) ## Get warnings for functions that have no documentation for their parameters or return value