| /* |
| * WPA Supplicant - Driver event processing |
| * Copyright (c) 2003-2019, Jouni Malinen <j@w1.fi> |
| * |
| * This software may be distributed under the terms of the BSD license. |
| * See README for more details. |
| */ |
| |
| #include "includes.h" |
| |
| #include "common.h" |
| #include "eapol_supp/eapol_supp_sm.h" |
| #include "rsn_supp/wpa.h" |
| #include "eloop.h" |
| #include "config.h" |
| #include "l2_packet/l2_packet.h" |
| #include "wpa_supplicant_i.h" |
| #include "driver_i.h" |
| #include "pcsc_funcs.h" |
| #include "rsn_supp/preauth.h" |
| #include "rsn_supp/pmksa_cache.h" |
| #include "common/wpa_ctrl.h" |
| #include "eap_peer/eap.h" |
| #include "ap/hostapd.h" |
| #include "ap/sta_info.h" |
| #include "p2p/p2p.h" |
| #include "fst/fst.h" |
| #include "wnm_sta.h" |
| #include "notify.h" |
| #include "common/ieee802_11_defs.h" |
| #include "common/ieee802_11_common.h" |
| #include "common/gas_server.h" |
| #include "common/dpp.h" |
| #include "common/ptksa_cache.h" |
| #include "crypto/random.h" |
| #include "bssid_ignore.h" |
| #include "wpas_glue.h" |
| #include "wps_supplicant.h" |
| #include "ibss_rsn.h" |
| #include "sme.h" |
| #include "gas_query.h" |
| #include "p2p_supplicant.h" |
| #include "bgscan.h" |
| #include "autoscan.h" |
| #include "ap.h" |
| #include "bss.h" |
| #include "scan.h" |
| #include "offchannel.h" |
| #include "interworking.h" |
| #include "mesh.h" |
| #include "mesh_mpm.h" |
| #include "wmm_ac.h" |
| #include "nan_usd.h" |
| #include "dpp_supplicant.h" |
| #include "utils/crc32.h" |
| |
| |
| #define MAX_OWE_TRANSITION_BSS_SELECT_COUNT 5 |
| |
| |
| #ifndef CONFIG_NO_SCAN_PROCESSING |
| static int wpas_select_network_from_last_scan(struct wpa_supplicant *wpa_s, |
| int new_scan, int own_request, |
| bool trigger_6ghz_scan, |
| union wpa_event_data *data); |
| #endif /* CONFIG_NO_SCAN_PROCESSING */ |
| |
| |
| int wpas_temp_disabled(struct wpa_supplicant *wpa_s, struct wpa_ssid *ssid) |
| { |
| struct os_reltime now; |
| |
| if (ssid == NULL || ssid->disabled_until.sec == 0) |
| return 0; |
| |
| os_get_reltime(&now); |
| if (ssid->disabled_until.sec > now.sec) |
| return ssid->disabled_until.sec - now.sec; |
| |
| wpas_clear_temp_disabled(wpa_s, ssid, 0); |
| |
| return 0; |
| } |
| |
| |
| #ifndef CONFIG_NO_SCAN_PROCESSING |
| /** |
| * wpas_reenabled_network_time - Time until first network is re-enabled |
| * @wpa_s: Pointer to wpa_supplicant data |
| * Returns: If all enabled networks are temporarily disabled, returns the time |
| * (in sec) until the first network is re-enabled. Otherwise returns 0. |
| * |
| * This function is used in case all enabled networks are temporarily disabled, |
| * in which case it returns the time (in sec) that the first network will be |
| * re-enabled. The function assumes that at least one network is enabled. |
| */ |
| static int wpas_reenabled_network_time(struct wpa_supplicant *wpa_s) |
| { |
| struct wpa_ssid *ssid; |
| int disabled_for, res = 0; |
| |
| #ifdef CONFIG_INTERWORKING |
| if (wpa_s->conf->auto_interworking && wpa_s->conf->interworking && |
| wpa_s->conf->cred) |
| return 0; |
| #endif /* CONFIG_INTERWORKING */ |
| |
| for (ssid = wpa_s->conf->ssid; ssid; ssid = ssid->next) { |
| if (ssid->disabled) |
| continue; |
| |
| disabled_for = wpas_temp_disabled(wpa_s, ssid); |
| if (!disabled_for) |
| return 0; |
| |
| if (!res || disabled_for < res) |
| res = disabled_for; |
| } |
| |
| return res; |
| } |
| #endif /* CONFIG_NO_SCAN_PROCESSING */ |
| |
| |
| void wpas_network_reenabled(void *eloop_ctx, void *timeout_ctx) |
| { |
| struct wpa_supplicant *wpa_s = eloop_ctx; |
| |
| if (wpa_s->disconnected || wpa_s->wpa_state != WPA_SCANNING) |
| return; |
| |
| wpa_dbg(wpa_s, MSG_DEBUG, |
| "Try to associate due to network getting re-enabled"); |
| if (wpa_supplicant_fast_associate(wpa_s) != 1) { |
| wpa_supplicant_cancel_sched_scan(wpa_s); |
| wpa_supplicant_req_scan(wpa_s, 0, 0); |
| } |
| } |
| |
| |
| static struct wpa_bss * __wpa_supplicant_get_new_bss( |
| struct wpa_supplicant *wpa_s, const u8 *bssid, const u8 *ssid, |
| size_t ssid_len) |
| { |
| if (ssid && ssid_len > 0) |
| return wpa_bss_get(wpa_s, bssid, ssid, ssid_len); |
| else |
| return wpa_bss_get_bssid(wpa_s, bssid); |
| } |
| |
| |
| static struct wpa_bss * _wpa_supplicant_get_new_bss( |
| struct wpa_supplicant *wpa_s, const u8 *bssid, const u8 *ssid, |
| size_t ssid_len, bool try_update_scan_results) |
| { |
| struct wpa_bss *bss = __wpa_supplicant_get_new_bss(wpa_s, bssid, ssid, |
| ssid_len); |
| |
| if (bss || !try_update_scan_results) |
| return bss; |
| |
| wpa_supplicant_update_scan_results(wpa_s, bssid); |
| |
| return __wpa_supplicant_get_new_bss(wpa_s, bssid, ssid, ssid_len); |
| } |
| |
| |
| static struct wpa_bss * wpa_supplicant_get_new_bss( |
| struct wpa_supplicant *wpa_s, const u8 *bssid) |
| { |
| struct wpa_bss *bss = NULL; |
| struct wpa_ssid *ssid = wpa_s->current_ssid; |
| u8 drv_ssid[SSID_MAX_LEN]; |
| int res; |
| bool try_update_scan_results = true; |
| |
| res = wpa_drv_get_ssid(wpa_s, drv_ssid); |
| if (res > 0) { |
| bss = _wpa_supplicant_get_new_bss(wpa_s, bssid, drv_ssid, res, |
| try_update_scan_results); |
| try_update_scan_results = false; |
| } |
| if (!bss && ssid && ssid->ssid_len > 0) { |
| bss = _wpa_supplicant_get_new_bss(wpa_s, bssid, ssid->ssid, |
| ssid->ssid_len, |
| try_update_scan_results); |
| try_update_scan_results = false; |
| } |
| if (!bss) |
| bss = _wpa_supplicant_get_new_bss(wpa_s, bssid, NULL, 0, |
| try_update_scan_results); |
| |
| return bss; |
| } |
| |
| |
| static struct wpa_bss * |
| wpa_supplicant_update_current_bss(struct wpa_supplicant *wpa_s, const u8 *bssid) |
| { |
| struct wpa_bss *bss = wpa_supplicant_get_new_bss(wpa_s, bssid); |
| |
| if (bss) |
| wpa_s->current_bss = bss; |
| |
| return bss; |
| } |
| |
| |
| static void wpa_supplicant_update_link_bss(struct wpa_supplicant *wpa_s, |
| u8 link_id, const u8 *bssid) |
| { |
| struct wpa_bss *bss = wpa_supplicant_get_new_bss(wpa_s, bssid); |
| |
| if (bss) |
| wpa_s->links[link_id].bss = bss; |
| } |
| |
| |
| static int wpa_supplicant_select_config(struct wpa_supplicant *wpa_s, |
| union wpa_event_data *data) |
| { |
| struct wpa_ssid *ssid, *old_ssid; |
| struct wpa_bss *bss; |
| u8 drv_ssid[SSID_MAX_LEN]; |
| size_t drv_ssid_len; |
| int res; |
| |
| if (wpa_s->conf->ap_scan == 1 && wpa_s->current_ssid) { |
| wpa_supplicant_update_current_bss(wpa_s, wpa_s->bssid); |
| |
| if (wpa_s->current_ssid->ssid_len == 0) |
| return 0; /* current profile still in use */ |
| res = wpa_drv_get_ssid(wpa_s, drv_ssid); |
| if (res < 0) { |
| wpa_msg(wpa_s, MSG_INFO, |
| "Failed to read SSID from driver"); |
| return 0; /* try to use current profile */ |
| } |
| drv_ssid_len = res; |
| |
| if (drv_ssid_len == wpa_s->current_ssid->ssid_len && |
| os_memcmp(drv_ssid, wpa_s->current_ssid->ssid, |
| drv_ssid_len) == 0) |
| return 0; /* current profile still in use */ |
| |
| #ifdef CONFIG_OWE |
| if ((wpa_s->current_ssid->key_mgmt & WPA_KEY_MGMT_OWE) && |
| wpa_s->current_bss && |
| (wpa_s->current_bss->flags & WPA_BSS_OWE_TRANSITION) && |
| drv_ssid_len == wpa_s->current_bss->ssid_len && |
| os_memcmp(drv_ssid, wpa_s->current_bss->ssid, |
| drv_ssid_len) == 0) |
| return 0; /* current profile still in use */ |
| #endif /* CONFIG_OWE */ |
| |
| wpa_printf(MSG_DEBUG, |
| "Driver-initiated BSS selection changed the SSID to %s", |
| wpa_ssid_txt(drv_ssid, drv_ssid_len)); |
| wpa_msg_ctrl(wpa_s, MSG_DEBUG, |
| "Driver-initiated BSS selection changed the SSID to %s", |
| wpa_ctrl_ssid_txt(drv_ssid, drv_ssid_len)); |
| /* continue selecting a new network profile */ |
| } |
| |
| wpa_dbg(wpa_s, MSG_DEBUG, "Select network based on association " |
| "information"); |
| ssid = wpa_supplicant_get_ssid(wpa_s); |
| if (ssid == NULL) { |
| wpa_msg(wpa_s, MSG_INFO, |
| "No network configuration found for the current AP"); |
| return -1; |
| } |
| |
| if (wpas_network_disabled(wpa_s, ssid)) { |
| wpa_dbg(wpa_s, MSG_DEBUG, "Selected network is disabled"); |
| return -1; |
| } |
| |
| if (disallowed_bssid(wpa_s, wpa_s->bssid) || |
| disallowed_ssid(wpa_s, ssid->ssid, ssid->ssid_len)) { |
| wpa_dbg(wpa_s, MSG_DEBUG, "Selected BSS is disallowed"); |
| return -1; |
| } |
| |
| res = wpas_temp_disabled(wpa_s, ssid); |
| if (res > 0) { |
| wpa_dbg(wpa_s, MSG_DEBUG, "Selected network is temporarily " |
| "disabled for %d second(s)", res); |
| return -1; |
| } |
| |
| wpa_dbg(wpa_s, MSG_DEBUG, "Network configuration found for the " |
| "current AP"); |
| bss = wpa_supplicant_update_current_bss(wpa_s, wpa_s->bssid); |
| if (wpa_key_mgmt_wpa_any(ssid->key_mgmt)) { |
| u8 wpa_ie[80]; |
| size_t wpa_ie_len = sizeof(wpa_ie); |
| bool skip_default_rsne; |
| |
| /* Do not override RSNE/RSNXE with the default values if the |
| * driver indicated the actual values used in the |
| * (Re)Association Request frame. */ |
| skip_default_rsne = data && data->assoc_info.req_ies; |
| if (wpa_supplicant_set_suites(wpa_s, bss, ssid, |
| wpa_ie, &wpa_ie_len, |
| skip_default_rsne) < 0) |
| wpa_dbg(wpa_s, MSG_DEBUG, "Could not set WPA suites"); |
| } else { |
| wpa_supplicant_set_non_wpa_policy(wpa_s, ssid); |
| } |
| |
| if (wpa_s->current_ssid && wpa_s->current_ssid != ssid) |
| eapol_sm_invalidate_cached_session(wpa_s->eapol); |
| old_ssid = wpa_s->current_ssid; |
| wpa_s->current_ssid = ssid; |
| |
| wpa_supplicant_rsn_supp_set_config(wpa_s, wpa_s->current_ssid); |
| wpa_supplicant_initiate_eapol(wpa_s); |
| if (old_ssid != wpa_s->current_ssid) |
| wpas_notify_network_changed(wpa_s); |
| |
| return 0; |
| } |
| |
| |
| void wpa_supplicant_stop_countermeasures(void *eloop_ctx, void *sock_ctx) |
| { |
| struct wpa_supplicant *wpa_s = eloop_ctx; |
| |
| if (wpa_s->countermeasures) { |
| wpa_s->countermeasures = 0; |
| wpa_drv_set_countermeasures(wpa_s, 0); |
| wpa_msg(wpa_s, MSG_INFO, "WPA: TKIP countermeasures stopped"); |
| |
| /* |
| * It is possible that the device is sched scanning, which means |
| * that a connection attempt will be done only when we receive |
| * scan results. However, in this case, it would be preferable |
| * to scan and connect immediately, so cancel the sched_scan and |
| * issue a regular scan flow. |
| */ |
| wpa_supplicant_cancel_sched_scan(wpa_s); |
| wpa_supplicant_req_scan(wpa_s, 0, 0); |
| } |
| } |
| |
| |
| void wpas_reset_mlo_info(struct wpa_supplicant *wpa_s) |
| { |
| if (!wpa_s->valid_links) |
| return; |
| |
| wpa_s->valid_links = 0; |
| wpa_s->mlo_assoc_link_id = 0; |
| os_memset(wpa_s->ap_mld_addr, 0, ETH_ALEN); |
| os_memset(wpa_s->links, 0, sizeof(wpa_s->links)); |
| } |
| |
| |
| void wpa_supplicant_mark_disassoc(struct wpa_supplicant *wpa_s) |
| { |
| int bssid_changed; |
| |
| wnm_bss_keep_alive_deinit(wpa_s); |
| |
| #ifdef CONFIG_IBSS_RSN |
| ibss_rsn_deinit(wpa_s->ibss_rsn); |
| wpa_s->ibss_rsn = NULL; |
| #endif /* CONFIG_IBSS_RSN */ |
| |
| #ifdef CONFIG_AP |
| wpa_supplicant_ap_deinit(wpa_s); |
| #endif /* CONFIG_AP */ |
| |
| #ifdef CONFIG_HS20 |
| /* Clear possibly configured frame filters */ |
| wpa_drv_configure_frame_filters(wpa_s, 0); |
| #endif /* CONFIG_HS20 */ |
| |
| if (wpa_s->wpa_state == WPA_INTERFACE_DISABLED) |
| return; |
| |
| if (os_reltime_initialized(&wpa_s->session_start)) { |
| os_reltime_age(&wpa_s->session_start, &wpa_s->session_length); |
| wpa_s->session_start.sec = 0; |
| wpa_s->session_start.usec = 0; |
| wpas_notify_session_length(wpa_s); |
| } |
| |
| wpa_supplicant_set_state(wpa_s, WPA_DISCONNECTED); |
| bssid_changed = !is_zero_ether_addr(wpa_s->bssid); |
| os_memset(wpa_s->bssid, 0, ETH_ALEN); |
| os_memset(wpa_s->pending_bssid, 0, ETH_ALEN); |
| sme_clear_on_disassoc(wpa_s); |
| wpa_s->current_bss = NULL; |
| wpa_s->assoc_freq = 0; |
| |
| if (bssid_changed) |
| wpas_notify_bssid_changed(wpa_s); |
| |
| eapol_sm_notify_portEnabled(wpa_s->eapol, false); |
| eapol_sm_notify_portValid(wpa_s->eapol, false); |
| if (wpa_key_mgmt_wpa_psk(wpa_s->key_mgmt) || |
| wpa_s->key_mgmt == WPA_KEY_MGMT_OWE || |
| wpa_s->key_mgmt == WPA_KEY_MGMT_DPP || wpa_s->drv_authorized_port) |
| eapol_sm_notify_eap_success(wpa_s->eapol, false); |
| wpa_s->drv_authorized_port = 0; |
| wpa_s->ap_ies_from_associnfo = 0; |
| wpa_s->current_ssid = NULL; |
| eapol_sm_notify_config(wpa_s->eapol, NULL, NULL); |
| wpa_s->key_mgmt = 0; |
| wpa_s->allowed_key_mgmts = 0; |
| |
| #ifndef CONFIG_NO_RRM |
| wpas_rrm_reset(wpa_s); |
| #endif /* CONFIG_NO_RRM */ |
| wpa_s->wnmsleep_used = 0; |
| #ifdef CONFIG_WNM |
| wpa_s->wnm_mode = 0; |
| #endif /* CONFIG_WNM */ |
| wnm_clear_coloc_intf_reporting(wpa_s); |
| wpa_s->disable_mbo_oce = 0; |
| |
| #ifdef CONFIG_TESTING_OPTIONS |
| wpa_s->last_tk_alg = WPA_ALG_NONE; |
| os_memset(wpa_s->last_tk, 0, sizeof(wpa_s->last_tk)); |
| #endif /* CONFIG_TESTING_OPTIONS */ |
| wpa_s->ieee80211ac = 0; |
| |
| if (wpa_s->enabled_4addr_mode && wpa_drv_set_4addr_mode(wpa_s, 0) == 0) |
| wpa_s->enabled_4addr_mode = 0; |
| |
| wpa_s->wps_scan_done = false; |
| wpas_reset_mlo_info(wpa_s); |
| |
| #ifdef CONFIG_SME |
| wpa_s->sme.bss_max_idle_period = 0; |
| #endif /* CONFIG_SME */ |
| |
| wpa_s->ssid_verified = false; |
| wpa_s->bigtk_set = false; |
| } |
| |
| |
| static void wpa_find_assoc_pmkid(struct wpa_supplicant *wpa_s, bool authorized) |
| { |
| struct wpa_ie_data ie; |
| int pmksa_set = -1; |
| size_t i; |
| struct rsn_pmksa_cache_entry *cur_pmksa; |
| |
| /* Start with assumption of no PMKSA cache entry match for cases other |
| * than SAE. In particular, this is needed to generate the PMKSA cache |
| * entries for Suite B cases with driver-based roaming indication. */ |
| cur_pmksa = pmksa_cache_get_current(wpa_s->wpa); |
| if (cur_pmksa && !wpa_key_mgmt_sae(cur_pmksa->akmp)) |
| pmksa_cache_clear_current(wpa_s->wpa); |
| |
| if (wpa_sm_parse_own_wpa_ie(wpa_s->wpa, &ie) < 0 || |
| ie.pmkid == NULL) |
| return; |
| |
| for (i = 0; i < ie.num_pmkid; i++) { |
| pmksa_set = pmksa_cache_set_current(wpa_s->wpa, |
| ie.pmkid + i * PMKID_LEN, |
| NULL, NULL, 0, NULL, 0, |
| true); |
| if (pmksa_set == 0) { |
| eapol_sm_notify_pmkid_attempt(wpa_s->eapol); |
| if (authorized) |
| wpa_sm_set_pmk_from_pmksa(wpa_s->wpa); |
| break; |
| } |
| } |
| |
| wpa_dbg(wpa_s, MSG_DEBUG, "RSN: PMKID from assoc IE %sfound from " |
| "PMKSA cache", pmksa_set == 0 ? "" : "not "); |
| } |
| |
| |
| static void wpa_supplicant_event_pmkid_candidate(struct wpa_supplicant *wpa_s, |
| union wpa_event_data *data) |
| { |
| if (data == NULL) { |
| wpa_dbg(wpa_s, MSG_DEBUG, "RSN: No data in PMKID candidate " |
| "event"); |
| return; |
| } |
| wpa_dbg(wpa_s, MSG_DEBUG, "RSN: PMKID candidate event - bssid=" MACSTR |
| " index=%d preauth=%d", |
| MAC2STR(data->pmkid_candidate.bssid), |
| data->pmkid_candidate.index, |
| data->pmkid_candidate.preauth); |
| |
| pmksa_candidate_add(wpa_s->wpa, data->pmkid_candidate.bssid, |
| data->pmkid_candidate.index, |
| data->pmkid_candidate.preauth); |
| } |
| |
| |
| static int wpa_supplicant_dynamic_keys(struct wpa_supplicant *wpa_s) |
| { |
| if (wpa_s->key_mgmt == WPA_KEY_MGMT_NONE || |
| wpa_s->key_mgmt == WPA_KEY_MGMT_WPA_NONE) |
| return 0; |
| |
| #ifdef IEEE8021X_EAPOL |
| if (wpa_s->key_mgmt == WPA_KEY_MGMT_IEEE8021X_NO_WPA && |
| wpa_s->current_ssid && |
| !(wpa_s->current_ssid->eapol_flags & |
| (EAPOL_FLAG_REQUIRE_KEY_UNICAST | |
| EAPOL_FLAG_REQUIRE_KEY_BROADCAST))) { |
| /* IEEE 802.1X, but not using dynamic WEP keys (i.e., either |
| * plaintext or static WEP keys). */ |
| return 0; |
| } |
| #endif /* IEEE8021X_EAPOL */ |
| |
| return 1; |
| } |
| |
| |
| /** |
| * wpa_supplicant_scard_init - Initialize SIM/USIM access with PC/SC |
| * @wpa_s: pointer to wpa_supplicant data |
| * @ssid: Configuration data for the network |
| * Returns: 0 on success, -1 on failure |
| * |
| * This function is called when starting authentication with a network that is |
| * configured to use PC/SC for SIM/USIM access (EAP-SIM or EAP-AKA). |
| */ |
| int wpa_supplicant_scard_init(struct wpa_supplicant *wpa_s, |
| struct wpa_ssid *ssid) |
| { |
| #ifdef IEEE8021X_EAPOL |
| #ifdef PCSC_FUNCS |
| int aka = 0, sim = 0; |
| |
| if ((ssid != NULL && ssid->eap.pcsc == NULL) || |
| wpa_s->scard != NULL || wpa_s->conf->external_sim) |
| return 0; |
| |
| if (ssid == NULL || ssid->eap.eap_methods == NULL) { |
| sim = 1; |
| aka = 1; |
| } else { |
| struct eap_method_type *eap = ssid->eap.eap_methods; |
| while (eap->vendor != EAP_VENDOR_IETF || |
| eap->method != EAP_TYPE_NONE) { |
| if (eap->vendor == EAP_VENDOR_IETF) { |
| if (eap->method == EAP_TYPE_SIM) |
| sim = 1; |
| else if (eap->method == EAP_TYPE_AKA || |
| eap->method == EAP_TYPE_AKA_PRIME) |
| aka = 1; |
| } |
| eap++; |
| } |
| } |
| |
| if (eap_peer_get_eap_method(EAP_VENDOR_IETF, EAP_TYPE_SIM) == NULL) |
| sim = 0; |
| if (eap_peer_get_eap_method(EAP_VENDOR_IETF, EAP_TYPE_AKA) == NULL && |
| eap_peer_get_eap_method(EAP_VENDOR_IETF, EAP_TYPE_AKA_PRIME) == |
| NULL) |
| aka = 0; |
| |
| if (!sim && !aka) { |
| wpa_dbg(wpa_s, MSG_DEBUG, "Selected network is configured to " |
| "use SIM, but neither EAP-SIM nor EAP-AKA are " |
| "enabled"); |
| return 0; |
| } |
| |
| wpa_dbg(wpa_s, MSG_DEBUG, "Selected network is configured to use SIM " |
| "(sim=%d aka=%d) - initialize PCSC", sim, aka); |
| |
| wpa_s->scard = scard_init(wpa_s->conf->pcsc_reader); |
| if (wpa_s->scard == NULL) { |
| wpa_msg(wpa_s, MSG_WARNING, "Failed to initialize SIM " |
| "(pcsc-lite)"); |
| return -1; |
| } |
| wpa_sm_set_scard_ctx(wpa_s->wpa, wpa_s->scard); |
| eapol_sm_register_scard_ctx(wpa_s->eapol, wpa_s->scard); |
| #endif /* PCSC_FUNCS */ |
| #endif /* IEEE8021X_EAPOL */ |
| |
| return 0; |
| } |
| |
| |
| #ifndef CONFIG_NO_SCAN_PROCESSING |
| |
| #ifdef CONFIG_WEP |
| static int has_wep_key(struct wpa_ssid *ssid) |
| { |
| int i; |
| |
| for (i = 0; i < NUM_WEP_KEYS; i++) { |
| if (ssid->wep_key_len[i]) |
| return 1; |
| } |
| |
| return 0; |
| } |
| #endif /* CONFIG_WEP */ |
| |
| |
| static int wpa_supplicant_match_privacy(struct wpa_bss *bss, |
| struct wpa_ssid *ssid) |
| { |
| int privacy = 0; |
| |
| if (ssid->mixed_cell) |
| return 1; |
| |
| #ifdef CONFIG_WPS |
| if (ssid->key_mgmt & WPA_KEY_MGMT_WPS) |
| return 1; |
| #endif /* CONFIG_WPS */ |
| |
| #ifdef CONFIG_OWE |
| if ((ssid->key_mgmt & WPA_KEY_MGMT_OWE) && !ssid->owe_only) |
| return 1; |
| #endif /* CONFIG_OWE */ |
| |
| #ifdef CONFIG_WEP |
| if (has_wep_key(ssid)) |
| privacy = 1; |
| #endif /* CONFIG_WEP */ |
| |
| #ifdef IEEE8021X_EAPOL |
| if ((ssid->key_mgmt & WPA_KEY_MGMT_IEEE8021X_NO_WPA) && |
| ssid->eapol_flags & (EAPOL_FLAG_REQUIRE_KEY_UNICAST | |
| EAPOL_FLAG_REQUIRE_KEY_BROADCAST)) |
| privacy = 1; |
| #endif /* IEEE8021X_EAPOL */ |
| |
| if (wpa_key_mgmt_wpa(ssid->key_mgmt)) |
| privacy = 1; |
| |
| if (ssid->key_mgmt & WPA_KEY_MGMT_OSEN) |
| privacy = 1; |
| |
| if (bss->caps & IEEE80211_CAP_PRIVACY) |
| return privacy; |
| return !privacy; |
| } |
| |
| |
| static int wpa_supplicant_ssid_bss_match(struct wpa_supplicant *wpa_s, |
| struct wpa_ssid *ssid, |
| struct wpa_bss *bss, int debug_print) |
| { |
| struct wpa_ie_data ie; |
| int proto_match = 0; |
| const u8 *rsn_ie, *wpa_ie; |
| int ret; |
| #ifdef CONFIG_WEP |
| int wep_ok; |
| #endif /* CONFIG_WEP */ |
| bool is_6ghz_bss = is_6ghz_freq(bss->freq); |
| |
| ret = wpas_wps_ssid_bss_match(wpa_s, ssid, bss); |
| if (ret >= 0) |
| return ret; |
| |
| #ifdef CONFIG_WEP |
| /* Allow TSN if local configuration accepts WEP use without WPA/WPA2 */ |
| wep_ok = !wpa_key_mgmt_wpa(ssid->key_mgmt) && |
| (((ssid->key_mgmt & WPA_KEY_MGMT_NONE) && |
| ssid->wep_key_len[ssid->wep_tx_keyidx] > 0) || |
| (ssid->key_mgmt & WPA_KEY_MGMT_IEEE8021X_NO_WPA)); |
| #endif /* CONFIG_WEP */ |
| |
| rsn_ie = wpa_bss_get_ie(bss, WLAN_EID_RSN); |
| if (is_6ghz_bss && !rsn_ie) { |
| if (debug_print) |
| wpa_dbg(wpa_s, MSG_DEBUG, |
| " skip - 6 GHz BSS without RSNE"); |
| return 0; |
| } |
| |
| while ((ssid->proto & (WPA_PROTO_RSN | WPA_PROTO_OSEN)) && rsn_ie) { |
| proto_match++; |
| |
| if (wpa_parse_wpa_ie(rsn_ie, 2 + rsn_ie[1], &ie)) { |
| if (debug_print) |
| wpa_dbg(wpa_s, MSG_DEBUG, |
| " skip RSN IE - parse failed"); |
| break; |
| } |
| if (!ie.has_pairwise) |
| ie.pairwise_cipher = wpa_default_rsn_cipher(bss->freq); |
| if (!ie.has_group) |
| ie.group_cipher = wpa_default_rsn_cipher(bss->freq); |
| |
| if (is_6ghz_bss || !is_zero_ether_addr(bss->mld_addr)) { |
| /* WEP and TKIP are not allowed on 6 GHz/MLD */ |
| ie.pairwise_cipher &= ~(WPA_CIPHER_WEP40 | |
| WPA_CIPHER_WEP104 | |
| WPA_CIPHER_TKIP); |
| ie.group_cipher &= ~(WPA_CIPHER_WEP40 | |
| WPA_CIPHER_WEP104 | |
| WPA_CIPHER_TKIP); |
| } |
| |
| #ifdef CONFIG_WEP |
| if (wep_ok && |
| (ie.group_cipher & (WPA_CIPHER_WEP40 | WPA_CIPHER_WEP104))) |
| { |
| if (debug_print) |
| wpa_dbg(wpa_s, MSG_DEBUG, |
| " selected based on TSN in RSN IE"); |
| return 1; |
| } |
| #endif /* CONFIG_WEP */ |
| |
| if (!(ie.proto & ssid->proto) && |
| !(ssid->proto & WPA_PROTO_OSEN)) { |
| if (debug_print) |
| wpa_dbg(wpa_s, MSG_DEBUG, |
| " skip RSN IE - proto mismatch"); |
| break; |
| } |
| |
| if (!(ie.pairwise_cipher & ssid->pairwise_cipher)) { |
| if (debug_print) |
| wpa_dbg(wpa_s, MSG_DEBUG, |
| " skip RSN IE - PTK cipher mismatch"); |
| break; |
| } |
| |
| if (!(ie.group_cipher & ssid->group_cipher)) { |
| if (debug_print) |
| wpa_dbg(wpa_s, MSG_DEBUG, |
| " skip RSN IE - GTK cipher mismatch"); |
| break; |
| } |
| |
| if (ssid->group_mgmt_cipher && |
| !(ie.mgmt_group_cipher & ssid->group_mgmt_cipher)) { |
| if (debug_print) |
| wpa_dbg(wpa_s, MSG_DEBUG, |
| " skip RSN IE - group mgmt cipher mismatch"); |
| break; |
| } |
| |
| if (is_6ghz_bss) { |
| /* MFPC must be supported on 6 GHz */ |
| if (!(ie.capabilities & WPA_CAPABILITY_MFPC)) { |
| if (debug_print) |
| wpa_dbg(wpa_s, MSG_DEBUG, |
| " skip RSNE - 6 GHz without MFPC"); |
| break; |
| } |
| |
| /* WPA PSK is not allowed on the 6 GHz band */ |
| ie.key_mgmt &= ~(WPA_KEY_MGMT_PSK | |
| WPA_KEY_MGMT_FT_PSK | |
| WPA_KEY_MGMT_PSK_SHA256); |
| } |
| |
| if (!(ie.key_mgmt & ssid->key_mgmt)) { |
| if (debug_print) |
| wpa_dbg(wpa_s, MSG_DEBUG, |
| " skip RSN IE - key mgmt mismatch"); |
| break; |
| } |
| |
| if (!(ie.capabilities & WPA_CAPABILITY_MFPC) && |
| wpas_get_ssid_pmf(wpa_s, ssid) == |
| MGMT_FRAME_PROTECTION_REQUIRED) { |
| if (debug_print) |
| wpa_dbg(wpa_s, MSG_DEBUG, |
| " skip RSN IE - no mgmt frame protection"); |
| break; |
| } |
| if ((ie.capabilities & WPA_CAPABILITY_MFPR) && |
| wpas_get_ssid_pmf(wpa_s, ssid) == |
| NO_MGMT_FRAME_PROTECTION) { |
| if (debug_print) |
| wpa_dbg(wpa_s, MSG_DEBUG, |
| " skip RSN IE - no mgmt frame protection enabled but AP requires it"); |
| break; |
| } |
| |
| if (debug_print) |
| wpa_dbg(wpa_s, MSG_DEBUG, |
| " selected based on RSN IE"); |
| return 1; |
| } |
| |
| if (is_6ghz_bss) { |
| if (debug_print) |
| wpa_dbg(wpa_s, MSG_DEBUG, |
| " skip - 6 GHz BSS without matching RSNE"); |
| return 0; |
| } |
| |
| wpa_ie = wpa_bss_get_vendor_ie(bss, WPA_IE_VENDOR_TYPE); |
| |
| if (wpas_get_ssid_pmf(wpa_s, ssid) == MGMT_FRAME_PROTECTION_REQUIRED && |
| (!(ssid->key_mgmt & WPA_KEY_MGMT_OWE) || ssid->owe_only)) { |
| #ifdef CONFIG_OWE |
| if ((ssid->key_mgmt & WPA_KEY_MGMT_OWE) && ssid->owe_only && |
| !wpa_ie && !rsn_ie && |
| wpa_s->owe_transition_select && |
| wpa_bss_get_vendor_ie(bss, OWE_IE_VENDOR_TYPE) && |
| ssid->owe_transition_bss_select_count + 1 <= |
| MAX_OWE_TRANSITION_BSS_SELECT_COUNT) { |
| ssid->owe_transition_bss_select_count++; |
| if (debug_print) |
| wpa_dbg(wpa_s, MSG_DEBUG, |
| " skip OWE open BSS (selection count %d does not exceed %d)", |
| ssid->owe_transition_bss_select_count, |
| MAX_OWE_TRANSITION_BSS_SELECT_COUNT); |
| wpa_s->owe_transition_search = 1; |
| return 0; |
| } |
| #endif /* CONFIG_OWE */ |
| if (debug_print) |
| wpa_dbg(wpa_s, MSG_DEBUG, |
| " skip - MFP Required but network not MFP Capable"); |
| return 0; |
| } |
| |
| while ((ssid->proto & WPA_PROTO_WPA) && wpa_ie) { |
| proto_match++; |
| |
| if (wpa_parse_wpa_ie(wpa_ie, 2 + wpa_ie[1], &ie)) { |
| if (debug_print) |
| wpa_dbg(wpa_s, MSG_DEBUG, |
| " skip WPA IE - parse failed"); |
| break; |
| } |
| |
| #ifdef CONFIG_WEP |
| if (wep_ok && |
| (ie.group_cipher & (WPA_CIPHER_WEP40 | WPA_CIPHER_WEP104))) |
| { |
| if (debug_print) |
| wpa_dbg(wpa_s, MSG_DEBUG, |
| " selected based on TSN in WPA IE"); |
| return 1; |
| } |
| #endif /* CONFIG_WEP */ |
| |
| if (!(ie.proto & ssid->proto)) { |
| if (debug_print) |
| wpa_dbg(wpa_s, MSG_DEBUG, |
| " skip WPA IE - proto mismatch"); |
| break; |
| } |
| |
| if (!(ie.pairwise_cipher & ssid->pairwise_cipher)) { |
| if (debug_print) |
| wpa_dbg(wpa_s, MSG_DEBUG, |
| " skip WPA IE - PTK cipher mismatch"); |
| break; |
| } |
| |
| if (!(ie.group_cipher & ssid->group_cipher)) { |
| if (debug_print) |
| wpa_dbg(wpa_s, MSG_DEBUG, |
| " skip WPA IE - GTK cipher mismatch"); |
| break; |
| } |
| |
| if (!(ie.key_mgmt & ssid->key_mgmt)) { |
| if (debug_print) |
| wpa_dbg(wpa_s, MSG_DEBUG, |
| " skip WPA IE - key mgmt mismatch"); |
| break; |
| } |
| |
| if (debug_print) |
| wpa_dbg(wpa_s, MSG_DEBUG, |
| " selected based on WPA IE"); |
| return 1; |
| } |
| |
| if ((ssid->key_mgmt & WPA_KEY_MGMT_IEEE8021X_NO_WPA) && !wpa_ie && |
| !rsn_ie) { |
| if (debug_print) |
| wpa_dbg(wpa_s, MSG_DEBUG, |
| " allow for non-WPA IEEE 802.1X"); |
| return 1; |
| } |
| |
| #ifdef CONFIG_OWE |
| if ((ssid->key_mgmt & WPA_KEY_MGMT_OWE) && !ssid->owe_only && |
| !wpa_ie && !rsn_ie) { |
| if (wpa_s->owe_transition_select && |
| wpa_bss_get_vendor_ie(bss, OWE_IE_VENDOR_TYPE) && |
| ssid->owe_transition_bss_select_count + 1 <= |
| MAX_OWE_TRANSITION_BSS_SELECT_COUNT) { |
| ssid->owe_transition_bss_select_count++; |
| if (debug_print) |
| wpa_dbg(wpa_s, MSG_DEBUG, |
| " skip OWE transition BSS (selection count %d does not exceed %d)", |
| ssid->owe_transition_bss_select_count, |
| MAX_OWE_TRANSITION_BSS_SELECT_COUNT); |
| wpa_s->owe_transition_search = 1; |
| return 0; |
| } |
| if (debug_print) |
| wpa_dbg(wpa_s, MSG_DEBUG, |
| " allow in OWE transition mode"); |
| return 1; |
| } |
| #endif /* CONFIG_OWE */ |
| |
| if ((ssid->proto & (WPA_PROTO_WPA | WPA_PROTO_RSN)) && |
| wpa_key_mgmt_wpa(ssid->key_mgmt) && proto_match == 0) { |
| if (debug_print) |
| wpa_dbg(wpa_s, MSG_DEBUG, |
| " skip - no WPA/RSN proto match"); |
| return 0; |
| } |
| |
| if ((ssid->key_mgmt & WPA_KEY_MGMT_OSEN) && |
| wpa_bss_get_vendor_ie(bss, OSEN_IE_VENDOR_TYPE)) { |
| if (debug_print) |
| wpa_dbg(wpa_s, MSG_DEBUG, " allow in OSEN"); |
| return 1; |
| } |
| |
| if (!wpa_key_mgmt_wpa(ssid->key_mgmt)) { |
| if (debug_print) |
| wpa_dbg(wpa_s, MSG_DEBUG, " allow in non-WPA/WPA2"); |
| return 1; |
| } |
| |
| if (debug_print) |
| wpa_dbg(wpa_s, MSG_DEBUG, |
| " reject due to mismatch with WPA/WPA2"); |
| |
| return 0; |
| } |
| |
| |
| static int freq_allowed(int *freqs, int freq) |
| { |
| int i; |
| |
| if (freqs == NULL) |
| return 1; |
| |
| for (i = 0; freqs[i]; i++) |
| if (freqs[i] == freq) |
| return 1; |
| return 0; |
| } |
| |
| |
| static int rate_match(struct wpa_supplicant *wpa_s, struct wpa_ssid *ssid, |
| struct wpa_bss *bss, int debug_print) |
| { |
| const struct hostapd_hw_modes *mode = NULL, *modes; |
| const u8 scan_ie[2] = { WLAN_EID_SUPP_RATES, WLAN_EID_EXT_SUPP_RATES }; |
| const u8 *rate_ie; |
| int i, j, k; |
| |
| if (bss->freq == 0) |
| return 1; /* Cannot do matching without knowing band */ |
| |
| modes = wpa_s->hw.modes; |
| if (modes == NULL) { |
| /* |
| * The driver does not provide any additional information |
| * about the utilized hardware, so allow the connection attempt |
| * to continue. |
| */ |
| return 1; |
| } |
| |
| for (i = 0; i < wpa_s->hw.num_modes; i++) { |
| for (j = 0; j < modes[i].num_channels; j++) { |
| int freq = modes[i].channels[j].freq; |
| if (freq == bss->freq) { |
| if (mode && |
| mode->mode == HOSTAPD_MODE_IEEE80211G) |
| break; /* do not allow 802.11b replace |
| * 802.11g */ |
| mode = &modes[i]; |
| break; |
| } |
| } |
| } |
| |
| if (mode == NULL) |
| return 0; |
| |
| for (i = 0; i < (int) sizeof(scan_ie); i++) { |
| rate_ie = wpa_bss_get_ie(bss, scan_ie[i]); |
| if (rate_ie == NULL) |
| continue; |
| |
| for (j = 2; j < rate_ie[1] + 2; j++) { |
| int flagged = !!(rate_ie[j] & 0x80); |
| int r = (rate_ie[j] & 0x7f) * 5; |
| |
| /* |
| * IEEE Std 802.11n-2009 7.3.2.2: |
| * The new BSS Membership selector value is encoded |
| * like a legacy basic rate, but it is not a rate and |
| * only indicates if the BSS members are required to |
| * support the mandatory features of Clause 20 [HT PHY] |
| * in order to join the BSS. |
| */ |
| if (flagged && ((rate_ie[j] & 0x7f) == |
| BSS_MEMBERSHIP_SELECTOR_HT_PHY)) { |
| if (!ht_supported(mode)) { |
| if (debug_print) |
| wpa_dbg(wpa_s, MSG_DEBUG, |
| " hardware does not support HT PHY"); |
| return 0; |
| } |
| continue; |
| } |
| |
| /* There's also a VHT selector for 802.11ac */ |
| if (flagged && ((rate_ie[j] & 0x7f) == |
| BSS_MEMBERSHIP_SELECTOR_VHT_PHY)) { |
| if (!vht_supported(mode)) { |
| if (debug_print) |
| wpa_dbg(wpa_s, MSG_DEBUG, |
| " hardware does not support VHT PHY"); |
| return 0; |
| } |
| continue; |
| } |
| |
| if (flagged && ((rate_ie[j] & 0x7f) == |
| BSS_MEMBERSHIP_SELECTOR_HE_PHY)) { |
| if (!he_supported(mode, IEEE80211_MODE_INFRA)) { |
| if (debug_print) |
| wpa_dbg(wpa_s, MSG_DEBUG, |
| " hardware does not support HE PHY"); |
| return 0; |
| } |
| continue; |
| } |
| |
| if (flagged && ((rate_ie[j] & 0x7f) == |
| BSS_MEMBERSHIP_SELECTOR_EHT_PHY)) { |
| if (!eht_supported(mode, IEEE80211_MODE_INFRA)) { |
| if (debug_print) |
| wpa_dbg(wpa_s, MSG_DEBUG, |
| " hardware does not support EHT PHY"); |
| return 0; |
| } |
| continue; |
| } |
| |
| #ifdef CONFIG_SAE |
| if (flagged && ((rate_ie[j] & 0x7f) == |
| BSS_MEMBERSHIP_SELECTOR_SAE_H2E_ONLY)) { |
| if (wpa_s->conf->sae_pwe == |
| SAE_PWE_HUNT_AND_PECK && |
| !ssid->sae_password_id && |
| !is_6ghz_freq(bss->freq) && |
| wpa_key_mgmt_sae(ssid->key_mgmt)) { |
| if (debug_print) |
| wpa_dbg(wpa_s, MSG_DEBUG, |
| " SAE H2E disabled"); |
| #ifdef CONFIG_TESTING_OPTIONS |
| if (wpa_s->ignore_sae_h2e_only) { |
| wpa_dbg(wpa_s, MSG_DEBUG, |
| "TESTING: Ignore SAE H2E requirement mismatch"); |
| continue; |
| } |
| #endif /* CONFIG_TESTING_OPTIONS */ |
| return 0; |
| } |
| continue; |
| } |
| #endif /* CONFIG_SAE */ |
| |
| if (!flagged) |
| continue; |
| |
| /* check for legacy basic rates */ |
| for (k = 0; k < mode->num_rates; k++) { |
| if (mode->rates[k] == r) |
| break; |
| } |
| if (k == mode->num_rates) { |
| /* |
| * IEEE Std 802.11-2007 7.3.2.2 demands that in |
| * order to join a BSS all required rates |
| * have to be supported by the hardware. |
| */ |
| if (debug_print) |
| wpa_dbg(wpa_s, MSG_DEBUG, |
| " hardware does not support required rate %d.%d Mbps (freq=%d mode==%d num_rates=%d)", |
| r / 10, r % 10, |
| bss->freq, mode->mode, mode->num_rates); |
| return 0; |
| } |
| } |
| } |
| |
| return 1; |
| } |
| |
| |
| /* |
| * Test whether BSS is in an ESS. |
| * This is done differently in DMG (60 GHz) and non-DMG bands |
| */ |
| static int bss_is_ess(struct wpa_bss *bss) |
| { |
| if (bss_is_dmg(bss)) { |
| return (bss->caps & IEEE80211_CAP_DMG_MASK) == |
| IEEE80211_CAP_DMG_AP; |
| } |
| |
| return ((bss->caps & (IEEE80211_CAP_ESS | IEEE80211_CAP_IBSS)) == |
| IEEE80211_CAP_ESS); |
| } |
| |
| |
| static int match_mac_mask(const u8 *addr_a, const u8 *addr_b, const u8 *mask) |
| { |
| size_t i; |
| |
| for (i = 0; i < ETH_ALEN; i++) { |
| if ((addr_a[i] & mask[i]) != (addr_b[i] & mask[i])) |
| return 0; |
| } |
| return 1; |
| } |
| |
| |
| static int addr_in_list(const u8 *addr, const u8 *list, size_t num) |
| { |
| size_t i; |
| |
| for (i = 0; i < num; i++) { |
| const u8 *a = list + i * ETH_ALEN * 2; |
| const u8 *m = a + ETH_ALEN; |
| |
| if (match_mac_mask(a, addr, m)) |
| return 1; |
| } |
| return 0; |
| } |
| |
| |
| static void owe_trans_ssid(struct wpa_supplicant *wpa_s, struct wpa_bss *bss, |
| const u8 **ret_ssid, size_t *ret_ssid_len) |
| { |
| #ifdef CONFIG_OWE |
| const u8 *owe, *pos, *end, *bssid; |
| u8 ssid_len; |
| |
| owe = wpa_bss_get_vendor_ie(bss, OWE_IE_VENDOR_TYPE); |
| if (!owe || !wpa_bss_get_ie(bss, WLAN_EID_RSN)) |
| return; |
| |
| pos = owe + 6; |
| end = owe + 2 + owe[1]; |
| |
| if (end - pos < ETH_ALEN + 1) |
| return; |
| bssid = pos; |
| pos += ETH_ALEN; |
| ssid_len = *pos++; |
| if (end - pos < ssid_len || ssid_len > SSID_MAX_LEN) |
| return; |
| |
| /* Match the profile SSID against the OWE transition mode SSID on the |
| * open network. */ |
| wpa_dbg(wpa_s, MSG_DEBUG, "OWE: transition mode BSSID: " MACSTR |
| " SSID: %s", MAC2STR(bssid), wpa_ssid_txt(pos, ssid_len)); |
| *ret_ssid = pos; |
| *ret_ssid_len = ssid_len; |
| |
| if (!(bss->flags & WPA_BSS_OWE_TRANSITION)) { |
| struct wpa_ssid *ssid; |
| |
| for (ssid = wpa_s->conf->ssid; ssid; ssid = ssid->next) { |
| if (wpas_network_disabled(wpa_s, ssid)) |
| continue; |
| if (ssid->ssid_len == ssid_len && |
| os_memcmp(ssid->ssid, pos, ssid_len) == 0) { |
| /* OWE BSS in transition mode for a currently |
| * enabled OWE network. */ |
| wpa_dbg(wpa_s, MSG_DEBUG, |
| "OWE: transition mode OWE SSID for active OWE profile"); |
| bss->flags |= WPA_BSS_OWE_TRANSITION; |
| break; |
| } |
| } |
| } |
| #endif /* CONFIG_OWE */ |
| } |
| |
| |
| static bool wpas_valid_ml_bss(struct wpa_supplicant *wpa_s, struct wpa_bss *bss) |
| { |
| u16 removed_links; |
| |
| if (wpa_bss_parse_basic_ml_element(wpa_s, bss, NULL, NULL, NULL, NULL)) |
| return true; |
| |
| if (!bss->valid_links) |
| return true; |
| |
| /* Check if the current BSS is going to be removed */ |
| removed_links = wpa_bss_parse_reconf_ml_element(wpa_s, bss); |
| if (BIT(bss->mld_link_id) & removed_links) |
| return false; |
| |
| return true; |
| } |
| |
| |
| int disabled_freq(struct wpa_supplicant *wpa_s, int freq) |
| { |
| int i, j; |
| |
| if (!wpa_s->hw.modes || !wpa_s->hw.num_modes) |
| return 0; |
| |
| for (j = 0; j < wpa_s->hw.num_modes; j++) { |
| struct hostapd_hw_modes *mode = &wpa_s->hw.modes[j]; |
| |
| for (i = 0; i < mode->num_channels; i++) { |
| struct hostapd_channel_data *chan = &mode->channels[i]; |
| |
| if (chan->freq == freq) |
| return !!(chan->flag & HOSTAPD_CHAN_DISABLED); |
| } |
| } |
| |
| return 1; |
| } |
| |
| |
| static bool wpa_scan_res_ok(struct wpa_supplicant *wpa_s, struct wpa_ssid *ssid, |
| const u8 *match_ssid, size_t match_ssid_len, |
| struct wpa_bss *bss, int bssid_ignore_count, |
| bool debug_print); |
| |
| |
| #ifdef CONFIG_SAE_PK |
| static bool sae_pk_acceptable_bss_with_pk(struct wpa_supplicant *wpa_s, |
| struct wpa_bss *orig_bss, |
| struct wpa_ssid *ssid, |
| const u8 *match_ssid, |
| size_t match_ssid_len) |
| { |
| struct wpa_bss *bss; |
| |
| dl_list_for_each(bss, &wpa_s->bss, struct wpa_bss, list) { |
| int count; |
| const u8 *ie; |
| |
| if (bss == orig_bss) |
| continue; |
| ie = wpa_bss_get_ie(bss, WLAN_EID_RSNX); |
| if (!(ieee802_11_rsnx_capab(ie, WLAN_RSNX_CAPAB_SAE_PK))) |
| continue; |
| |
| /* TODO: Could be more thorough in checking what kind of |
| * signal strength or throughput estimate would be acceptable |
| * compared to the originally selected BSS. */ |
| if (bss->est_throughput < 2000) |
| return false; |
| |
| count = wpa_bssid_ignore_is_listed(wpa_s, bss->bssid); |
| if (wpa_scan_res_ok(wpa_s, ssid, match_ssid, match_ssid_len, |
| bss, count, 0)) |
| return true; |
| } |
| |
| return false; |
| } |
| #endif /* CONFIG_SAE_PK */ |
| |
| |
| static bool wpa_scan_res_ok(struct wpa_supplicant *wpa_s, struct wpa_ssid *ssid, |
| const u8 *match_ssid, size_t match_ssid_len, |
| struct wpa_bss *bss, int bssid_ignore_count, |
| bool debug_print) |
| { |
| int res; |
| bool wpa, check_ssid, osen, rsn_osen = false; |
| struct wpa_ie_data data; |
| #ifdef CONFIG_MBO |
| const u8 *assoc_disallow; |
| #endif /* CONFIG_MBO */ |
| #ifdef CONFIG_SAE |
| u8 rsnxe_capa = 0; |
| #endif /* CONFIG_SAE */ |
| const u8 *ie; |
| |
| ie = wpa_bss_get_vendor_ie(bss, WPA_IE_VENDOR_TYPE); |
| wpa = ie && ie[1]; |
| ie = wpa_bss_get_ie(bss, WLAN_EID_RSN); |
| wpa |= ie && ie[1]; |
| if (ie && wpa_parse_wpa_ie_rsn(ie, 2 + ie[1], &data) == 0 && |
| (data.key_mgmt & WPA_KEY_MGMT_OSEN)) |
| rsn_osen = true; |
| ie = wpa_bss_get_vendor_ie(bss, OSEN_IE_VENDOR_TYPE); |
| osen = ie != NULL; |
| |
| #ifdef CONFIG_SAE |
| ie = wpa_bss_get_ie(bss, WLAN_EID_RSNX); |
| if (ie && ie[1] >= 1) |
| rsnxe_capa = ie[2]; |
| #endif /* CONFIG_SAE */ |
| |
| check_ssid = wpa || ssid->ssid_len > 0; |
| |
| if (wpas_network_disabled(wpa_s, ssid)) { |
| if (debug_print) |
| wpa_dbg(wpa_s, MSG_DEBUG, " skip - disabled"); |
| return false; |
| } |
| |
| res = wpas_temp_disabled(wpa_s, ssid); |
| if (res > 0) { |
| if (debug_print) |
| wpa_dbg(wpa_s, MSG_DEBUG, |
| " skip - disabled temporarily for %d second(s)", |
| res); |
| return false; |
| } |
| |
| #ifdef CONFIG_WPS |
| if ((ssid->key_mgmt & WPA_KEY_MGMT_WPS) && bssid_ignore_count) { |
| if (debug_print) |
| wpa_dbg(wpa_s, MSG_DEBUG, |
| " skip - BSSID ignored (WPS)"); |
| return false; |
| } |
| |
| if (wpa && ssid->ssid_len == 0 && |
| wpas_wps_ssid_wildcard_ok(wpa_s, ssid, bss)) |
| check_ssid = false; |
| |
| if (!wpa && (ssid->key_mgmt & WPA_KEY_MGMT_WPS)) { |
| /* Only allow wildcard SSID match if an AP advertises active |
| * WPS operation that matches our mode. */ |
| check_ssid = ssid->ssid_len > 0 || |
| !wpas_wps_ssid_wildcard_ok(wpa_s, ssid, bss); |
| } |
| #endif /* CONFIG_WPS */ |
| |
| if (ssid->bssid_set && ssid->ssid_len == 0 && |
| ether_addr_equal(bss->bssid, ssid->bssid)) |
| check_ssid = false; |
| |
| if (check_ssid && |
| (match_ssid_len != ssid->ssid_len || |
| os_memcmp(match_ssid, ssid->ssid, match_ssid_len) != 0)) { |
| if (debug_print) |
| wpa_dbg(wpa_s, MSG_DEBUG, " skip - SSID mismatch"); |
| return false; |
| } |
| |
| if (ssid->bssid_set && |
| !ether_addr_equal(bss->bssid, ssid->bssid)) { |
| if (debug_print) |
| wpa_dbg(wpa_s, MSG_DEBUG, " skip - BSSID mismatch"); |
| return false; |
| } |
| |
| /* check the list of BSSIDs to ignore */ |
| if (ssid->num_bssid_ignore && |
| addr_in_list(bss->bssid, ssid->bssid_ignore, |
| ssid->num_bssid_ignore)) { |
| if (debug_print) |
| wpa_dbg(wpa_s, MSG_DEBUG, |
| " skip - BSSID configured to be ignored"); |
| return false; |
| } |
| |
| /* if there is a list of accepted BSSIDs, only accept those APs */ |
| if (ssid->num_bssid_accept && |
| !addr_in_list(bss->bssid, ssid->bssid_accept, |
| ssid->num_bssid_accept)) { |
| if (debug_print) |
| wpa_dbg(wpa_s, MSG_DEBUG, |
| " skip - BSSID not in list of accepted values"); |
| return false; |
| } |
| |
| if (!wpa_supplicant_ssid_bss_match(wpa_s, ssid, bss, debug_print)) |
| return false; |
| |
| if (!osen && !wpa && |
| !(ssid->key_mgmt & WPA_KEY_MGMT_NONE) && |
| !(ssid->key_mgmt & WPA_KEY_MGMT_WPS) && |
| !(ssid->key_mgmt & WPA_KEY_MGMT_OWE) && |
| !(ssid->key_mgmt & WPA_KEY_MGMT_IEEE8021X_NO_WPA)) { |
| if (debug_print) |
| wpa_dbg(wpa_s, MSG_DEBUG, |
| " skip - non-WPA network not allowed"); |
| return false; |
| } |
| |
| #ifdef CONFIG_WEP |
| if (wpa && !wpa_key_mgmt_wpa(ssid->key_mgmt) && has_wep_key(ssid)) { |
| if (debug_print) |
| wpa_dbg(wpa_s, MSG_DEBUG, |
| " skip - ignore WPA/WPA2 AP for WEP network block"); |
| return false; |
| } |
| #endif /* CONFIG_WEP */ |
| |
| if ((ssid->key_mgmt & WPA_KEY_MGMT_OSEN) && !osen && !rsn_osen) { |
| if (debug_print) |
| wpa_dbg(wpa_s, MSG_DEBUG, |
| " skip - non-OSEN network not allowed"); |
| return false; |
| } |
| |
| if (!wpa_supplicant_match_privacy(bss, ssid)) { |
| if (debug_print) |
| wpa_dbg(wpa_s, MSG_DEBUG, " skip - privacy mismatch"); |
| return false; |
| } |
| |
| if (ssid->mode != WPAS_MODE_MESH && !bss_is_ess(bss) && |
| !bss_is_pbss(bss)) { |
| if (debug_print) |
| wpa_dbg(wpa_s, MSG_DEBUG, |
| " skip - not ESS, PBSS, or MBSS"); |
| return false; |
| } |
| |
| if (ssid->pbss != 2 && ssid->pbss != bss_is_pbss(bss)) { |
| if (debug_print) |
| wpa_dbg(wpa_s, MSG_DEBUG, |
| " skip - PBSS mismatch (ssid %d bss %d)", |
| ssid->pbss, bss_is_pbss(bss)); |
| return false; |
| } |
| |
| if (!freq_allowed(ssid->freq_list, bss->freq)) { |
| if (debug_print) |
| wpa_dbg(wpa_s, MSG_DEBUG, |
| " skip - frequency not allowed"); |
| return false; |
| } |
| |
| #ifdef CONFIG_MESH |
| if (ssid->mode == WPAS_MODE_MESH && ssid->frequency > 0 && |
| ssid->frequency != bss->freq) { |
| if (debug_print) |
| wpa_dbg(wpa_s, MSG_DEBUG, |
| " skip - frequency not allowed (mesh)"); |
| return false; |
| } |
| #endif /* CONFIG_MESH */ |
| |
| if (!rate_match(wpa_s, ssid, bss, debug_print)) { |
| if (debug_print) |
| wpa_dbg(wpa_s, MSG_DEBUG, |
| " skip - rate sets do not match"); |
| return false; |
| } |
| |
| #ifdef CONFIG_SAE |
| /* When using SAE Password Identifier and when operationg on the 6 GHz |
| * band, only H2E is allowed. */ |
| if ((wpa_s->conf->sae_pwe == SAE_PWE_HASH_TO_ELEMENT || |
| is_6ghz_freq(bss->freq) || ssid->sae_password_id) && |
| wpa_s->conf->sae_pwe != SAE_PWE_FORCE_HUNT_AND_PECK && |
| wpa_key_mgmt_sae(ssid->key_mgmt) && |
| !(rsnxe_capa & BIT(WLAN_RSNX_CAPAB_SAE_H2E))) { |
| if (debug_print) |
| wpa_dbg(wpa_s, MSG_DEBUG, |
| " skip - SAE H2E required, but not supported by the AP"); |
| return false; |
| } |
| #endif /* CONFIG_SAE */ |
| |
| #ifdef CONFIG_SAE_PK |
| if (ssid->sae_pk == SAE_PK_MODE_ONLY && |
| !(rsnxe_capa & BIT(WLAN_RSNX_CAPAB_SAE_PK))) { |
| if (debug_print) |
| wpa_dbg(wpa_s, MSG_DEBUG, |
| " skip - SAE-PK required, but not supported by the AP"); |
| return false; |
| } |
| #endif /* CONFIG_SAE_PK */ |
| |
| #ifndef CONFIG_IBSS_RSN |
| if (ssid->mode == WPAS_MODE_IBSS && |
| !(ssid->key_mgmt & (WPA_KEY_MGMT_NONE | WPA_KEY_MGMT_WPA_NONE))) { |
| if (debug_print) |
| wpa_dbg(wpa_s, MSG_DEBUG, |
| " skip - IBSS RSN not supported in the build"); |
| return false; |
| } |
| #endif /* !CONFIG_IBSS_RSN */ |
| |
| #ifdef CONFIG_P2P |
| if (ssid->p2p_group && |
| !wpa_bss_get_vendor_ie(bss, P2P_IE_VENDOR_TYPE) && |
| !wpa_bss_get_vendor_ie_beacon(bss, P2P_IE_VENDOR_TYPE)) { |
| if (debug_print) |
| wpa_dbg(wpa_s, MSG_DEBUG, " skip - no P2P IE seen"); |
| return false; |
| } |
| |
| if (!is_zero_ether_addr(ssid->go_p2p_dev_addr)) { |
| struct wpabuf *p2p_ie; |
| u8 dev_addr[ETH_ALEN]; |
| |
| ie = wpa_bss_get_vendor_ie(bss, P2P_IE_VENDOR_TYPE); |
| if (!ie) { |
| if (debug_print) |
| wpa_dbg(wpa_s, MSG_DEBUG, |
| " skip - no P2P element"); |
| return false; |
| } |
| p2p_ie = wpa_bss_get_vendor_ie_multi(bss, P2P_IE_VENDOR_TYPE); |
| if (!p2p_ie) { |
| if (debug_print) |
| wpa_dbg(wpa_s, MSG_DEBUG, |
| " skip - could not fetch P2P element"); |
| return false; |
| } |
| |
| if (p2p_parse_dev_addr_in_p2p_ie(p2p_ie, dev_addr) < 0 || |
| !ether_addr_equal(dev_addr, ssid->go_p2p_dev_addr)) { |
| if (debug_print) |
| wpa_dbg(wpa_s, MSG_DEBUG, |
| " skip - no matching GO P2P Device Address in P2P element"); |
| wpabuf_free(p2p_ie); |
| return false; |
| } |
| wpabuf_free(p2p_ie); |
| } |
| |
| /* |
| * TODO: skip the AP if its P2P IE has Group Formation bit set in the |
| * P2P Group Capability Bitmap and we are not in Group Formation with |
| * that device. |
| */ |
| #endif /* CONFIG_P2P */ |
| |
| if (os_reltime_before(&bss->last_update, &wpa_s->scan_min_time)) { |
| struct os_reltime diff; |
| |
| os_reltime_sub(&wpa_s->scan_min_time, &bss->last_update, &diff); |
| if (debug_print) |
| wpa_dbg(wpa_s, MSG_DEBUG, |
| " skip - scan result not recent enough (%u.%06u seconds too old)", |
| (unsigned int) diff.sec, |
| (unsigned int) diff.usec); |
| return false; |
| } |
| #ifdef CONFIG_MBO |
| #ifdef CONFIG_TESTING_OPTIONS |
| if (wpa_s->ignore_assoc_disallow) |
| goto skip_assoc_disallow; |
| #endif /* CONFIG_TESTING_OPTIONS */ |
| assoc_disallow = wpas_mbo_check_assoc_disallow(bss); |
| if (assoc_disallow && assoc_disallow[1] >= 1) { |
| if (debug_print) |
| wpa_dbg(wpa_s, MSG_DEBUG, |
| " skip - MBO association disallowed (reason %u)", |
| assoc_disallow[2]); |
| return false; |
| } |
| |
| if (wpa_is_bss_tmp_disallowed(wpa_s, bss)) { |
| if (debug_print) |
| wpa_dbg(wpa_s, MSG_DEBUG, |
| " skip - AP temporarily disallowed"); |
| return false; |
| } |
| #ifdef CONFIG_TESTING_OPTIONS |
| skip_assoc_disallow: |
| #endif /* CONFIG_TESTING_OPTIONS */ |
| #endif /* CONFIG_MBO */ |
| |
| #ifdef CONFIG_DPP |
| if ((ssid->key_mgmt & WPA_KEY_MGMT_DPP) && |
| !wpa_sm_pmksa_exists(wpa_s->wpa, bss->bssid, wpa_s->own_addr, |
| ssid) && |
| (!ssid->dpp_connector || !ssid->dpp_netaccesskey || |
| !ssid->dpp_csign)) { |
| if (debug_print) |
| wpa_dbg(wpa_s, MSG_DEBUG, |
| " skip - no PMKSA entry for DPP"); |
| return false; |
| } |
| #endif /* CONFIG_DPP */ |
| |
| #ifdef CONFIG_SAE_PK |
| if (ssid->sae_pk == SAE_PK_MODE_AUTOMATIC && |
| wpa_key_mgmt_sae(ssid->key_mgmt) && |
| ((ssid->sae_password && |
| sae_pk_valid_password(ssid->sae_password)) || |
| (!ssid->sae_password && ssid->passphrase && |
| sae_pk_valid_password(ssid->passphrase))) && |
| !(rsnxe_capa & BIT(WLAN_RSNX_CAPAB_SAE_PK)) && |
| sae_pk_acceptable_bss_with_pk(wpa_s, bss, ssid, match_ssid, |
| match_ssid_len)) { |
| if (debug_print) |
| wpa_dbg(wpa_s, MSG_DEBUG, |
| " skip - another acceptable BSS with SAE-PK in the same ESS"); |
| return false; |
| } |
| #endif /* CONFIG_SAE_PK */ |
| |
| if (bss->ssid_len == 0) { |
| #ifdef CONFIG_OWE |
| const u8 *owe_ssid = NULL; |
| size_t owe_ssid_len = 0; |
| |
| owe_trans_ssid(wpa_s, bss, &owe_ssid, &owe_ssid_len); |
| if (owe_ssid && owe_ssid_len && |
| owe_ssid_len == ssid->ssid_len && |
| os_memcmp(owe_ssid, ssid->ssid, owe_ssid_len) == 0) { |
| if (debug_print) |
| wpa_dbg(wpa_s, MSG_DEBUG, |
| " skip - no SSID in BSS entry for a possible OWE transition mode BSS"); |
| int_array_add_unique(&wpa_s->owe_trans_scan_freq, |
| bss->freq); |
| return false; |
| } |
| #endif /* CONFIG_OWE */ |
| if (debug_print) |
| wpa_dbg(wpa_s, MSG_DEBUG, |
| " skip - no SSID known for the BSS"); |
| return false; |
| } |
| |
| if (!wpas_valid_ml_bss(wpa_s, bss)) { |
| if (debug_print) |
| wpa_dbg(wpa_s, MSG_DEBUG, |
| " skip - ML BSS going to be removed"); |
| return false; |
| } |
| |
| /* Matching configuration found */ |
| return true; |
| } |
| |
| |
| struct wpa_ssid * wpa_scan_res_match(struct wpa_supplicant *wpa_s, |
| int i, struct wpa_bss *bss, |
| struct wpa_ssid *group, |
| int only_first_ssid, int debug_print) |
| { |
| u8 wpa_ie_len, rsn_ie_len; |
| const u8 *ie; |
| struct wpa_ssid *ssid; |
| int osen; |
| const u8 *match_ssid; |
| size_t match_ssid_len; |
| int bssid_ignore_count; |
| |
| ie = wpa_bss_get_vendor_ie(bss, WPA_IE_VENDOR_TYPE); |
| wpa_ie_len = ie ? ie[1] : 0; |
| |
| ie = wpa_bss_get_ie(bss, WLAN_EID_RSN); |
| rsn_ie_len = ie ? ie[1] : 0; |
| |
| ie = wpa_bss_get_vendor_ie(bss, OSEN_IE_VENDOR_TYPE); |
| osen = ie != NULL; |
| |
| if (debug_print) { |
| wpa_dbg(wpa_s, MSG_DEBUG, "%d: " MACSTR |
| " ssid='%s' wpa_ie_len=%u rsn_ie_len=%u caps=0x%x level=%d freq=%d %s%s%s", |
| i, MAC2STR(bss->bssid), |
| wpa_ssid_txt(bss->ssid, bss->ssid_len), |
| wpa_ie_len, rsn_ie_len, bss->caps, bss->level, |
| bss->freq, |
| wpa_bss_get_vendor_ie(bss, WPS_IE_VENDOR_TYPE) ? |
| " wps" : "", |
| (wpa_bss_get_vendor_ie(bss, P2P_IE_VENDOR_TYPE) || |
| wpa_bss_get_vendor_ie_beacon(bss, P2P_IE_VENDOR_TYPE)) |
| ? " p2p" : "", |
| osen ? " osen=1" : ""); |
| } |
| |
| bssid_ignore_count = wpa_bssid_ignore_is_listed(wpa_s, bss->bssid); |
| if (bssid_ignore_count) { |
| int limit = 1; |
| if (wpa_supplicant_enabled_networks(wpa_s) == 1) { |
| /* |
| * When only a single network is enabled, we can |
| * trigger BSSID ignoring on the first failure. This |
| * should not be done with multiple enabled networks to |
| * avoid getting forced to move into a worse ESS on |
| * single error if there are no other BSSes of the |
| * current ESS. |
| */ |
| limit = 0; |
| } |
| if (bssid_ignore_count > limit) { |
| if (debug_print) { |
| wpa_dbg(wpa_s, MSG_DEBUG, |
| " skip - BSSID ignored (count=%d limit=%d)", |
| bssid_ignore_count, limit); |
| } |
| return NULL; |
| } |
| } |
| |
| match_ssid = bss->ssid; |
| match_ssid_len = bss->ssid_len; |
| owe_trans_ssid(wpa_s, bss, &match_ssid, &match_ssid_len); |
| |
| if (match_ssid_len == 0) { |
| if (debug_print) |
| wpa_dbg(wpa_s, MSG_DEBUG, " skip - SSID not known"); |
| return NULL; |
| } |
| |
| if (disallowed_bssid(wpa_s, bss->bssid)) { |
| if (debug_print) |
| wpa_dbg(wpa_s, MSG_DEBUG, " skip - BSSID disallowed"); |
| return NULL; |
| } |
| |
| if (disallowed_ssid(wpa_s, match_ssid, match_ssid_len)) { |
| if (debug_print) |
| wpa_dbg(wpa_s, MSG_DEBUG, " skip - SSID disallowed"); |
| return NULL; |
| } |
| |
| if (disabled_freq(wpa_s, bss->freq)) { |
| if (debug_print) |
| wpa_dbg(wpa_s, MSG_DEBUG, " skip - channel disabled"); |
| return NULL; |
| } |
| |
| if (wnm_is_bss_excluded(wpa_s, bss)) { |
| if (debug_print) |
| wpa_dbg(wpa_s, MSG_DEBUG, " skip - BSSID excluded"); |
| return NULL; |
| } |
| |
| for (ssid = group; ssid; ssid = only_first_ssid ? NULL : ssid->pnext) { |
| if (wpa_scan_res_ok(wpa_s, ssid, match_ssid, match_ssid_len, |
| bss, bssid_ignore_count, debug_print)) |
| return ssid; |
| } |
| |
| /* No matching configuration found */ |
| return NULL; |
| } |
| |
| |
| static struct wpa_bss * |
| wpa_supplicant_select_bss(struct wpa_supplicant *wpa_s, |
| struct wpa_ssid *group, |
| struct wpa_ssid **selected_ssid, |
| int only_first_ssid) |
| { |
| unsigned int i; |
| |
| if (wpa_s->current_ssid) { |
| struct wpa_ssid *ssid; |
| |
| wpa_dbg(wpa_s, MSG_DEBUG, |
| "Scan results matching the currently selected network"); |
| for (i = 0; i < wpa_s->last_scan_res_used; i++) { |
| struct wpa_bss *bss = wpa_s->last_scan_res[i]; |
| |
| ssid = wpa_scan_res_match(wpa_s, i, bss, group, |
| only_first_ssid, 0); |
| if (ssid != wpa_s->current_ssid) |
| continue; |
| wpa_dbg(wpa_s, MSG_DEBUG, "%u: " MACSTR |
| " freq=%d level=%d snr=%d est_throughput=%u", |
| i, MAC2STR(bss->bssid), bss->freq, bss->level, |
| bss->snr, bss->est_throughput); |
| } |
| } |
| |
| if (only_first_ssid) |
| wpa_dbg(wpa_s, MSG_DEBUG, "Try to find BSS matching pre-selected network id=%d", |
| group->id); |
| else |
| wpa_dbg(wpa_s, MSG_DEBUG, "Selecting BSS from priority group %d", |
| group->priority); |
| |
| for (i = 0; i < wpa_s->last_scan_res_used; i++) { |
| struct wpa_bss *bss = wpa_s->last_scan_res[i]; |
| |
| wpa_s->owe_transition_select = 1; |
| *selected_ssid = wpa_scan_res_match(wpa_s, i, bss, group, |
| only_first_ssid, 1); |
| wpa_s->owe_transition_select = 0; |
| if (!*selected_ssid) |
| continue; |
| wpa_dbg(wpa_s, MSG_DEBUG, " selected %sBSS " MACSTR |
| " ssid='%s'", |
| bss == wpa_s->current_bss ? "current ": "", |
| MAC2STR(bss->bssid), |
| wpa_ssid_txt(bss->ssid, bss->ssid_len)); |
| return bss; |
| } |
| |
| return NULL; |
| } |
| |
| |
| struct wpa_bss * wpa_supplicant_pick_network(struct wpa_supplicant *wpa_s, |
| struct wpa_ssid **selected_ssid) |
| { |
| struct wpa_bss *selected = NULL; |
| size_t prio; |
| struct wpa_ssid *next_ssid = NULL; |
| struct wpa_ssid *ssid; |
| |
| if (wpa_s->last_scan_res == NULL || |
| wpa_s->last_scan_res_used == 0) |
| return NULL; /* no scan results from last update */ |
| |
| if (wpa_s->next_ssid) { |
| /* check that next_ssid is still valid */ |
| for (ssid = wpa_s->conf->ssid; ssid; ssid = ssid->next) { |
| if (ssid == wpa_s->next_ssid) |
| break; |
| } |
| next_ssid = ssid; |
| wpa_s->next_ssid = NULL; |
| } |
| |
| while (selected == NULL) { |
| for (prio = 0; prio < wpa_s->conf->num_prio; prio++) { |
| if (next_ssid && next_ssid->priority == |
| wpa_s->conf->pssid[prio]->priority) { |
| selected = wpa_supplicant_select_bss( |
| wpa_s, next_ssid, selected_ssid, 1); |
| if (selected) |
| break; |
| } |
| selected = wpa_supplicant_select_bss( |
| wpa_s, wpa_s->conf->pssid[prio], |
| selected_ssid, 0); |
| if (selected) |
| break; |
| } |
| |
| if (!selected && |
| (wpa_s->bssid_ignore || wnm_active_bss_trans_mgmt(wpa_s)) && |
| !wpa_s->countermeasures) { |
| wpa_dbg(wpa_s, MSG_DEBUG, |
| "No APs found - clear BSSID ignore list and try again"); |
| wnm_btm_reset(wpa_s); |
| wpa_bssid_ignore_clear(wpa_s); |
| wpa_s->bssid_ignore_cleared = true; |
| } else if (selected == NULL) |
| break; |
| } |
| |
| ssid = *selected_ssid; |
| if (selected && ssid && ssid->mem_only_psk && !ssid->psk_set && |
| !ssid->passphrase && !ssid->ext_psk) { |
| const char *field_name, *txt = NULL; |
| |
| wpa_dbg(wpa_s, MSG_DEBUG, |
| "PSK/passphrase not yet available for the selected network"); |
| |
| wpas_notify_network_request(wpa_s, ssid, |
| WPA_CTRL_REQ_PSK_PASSPHRASE, NULL); |
| |
| field_name = wpa_supplicant_ctrl_req_to_string( |
| WPA_CTRL_REQ_PSK_PASSPHRASE, NULL, &txt); |
| if (field_name == NULL) |
| return NULL; |
| |
| wpas_send_ctrl_req(wpa_s, ssid, field_name, txt); |
| |
| selected = NULL; |
| } |
| |
| return selected; |
| } |
| |
| |
| static void wpa_supplicant_req_new_scan(struct wpa_supplicant *wpa_s, |
| int timeout_sec, int timeout_usec) |
| { |
| if (!wpa_supplicant_enabled_networks(wpa_s)) { |
| /* |
| * No networks are enabled; short-circuit request so |
| * we don't wait timeout seconds before transitioning |
| * to INACTIVE state. |
| */ |
| wpa_dbg(wpa_s, MSG_DEBUG, "Short-circuit new scan request " |
| "since there are no enabled networks"); |
| wpa_supplicant_set_state(wpa_s, WPA_INACTIVE); |
| return; |
| } |
| |
| wpa_s->scan_for_connection = 1; |
| wpa_supplicant_req_scan(wpa_s, timeout_sec, timeout_usec); |
| } |
| |
| |
| static bool ml_link_probe_scan(struct wpa_supplicant *wpa_s) |
| { |
| if (!wpa_s->ml_connect_probe_ssid || !wpa_s->ml_connect_probe_bss) |
| return false; |
| |
| wpa_msg(wpa_s, MSG_DEBUG, |
| "Request association with " MACSTR " after ML probe", |
| MAC2STR(wpa_s->ml_connect_probe_bss->bssid)); |
| |
| wpa_supplicant_associate(wpa_s, wpa_s->ml_connect_probe_bss, |
| wpa_s->ml_connect_probe_ssid); |
| |
| wpa_s->ml_connect_probe_ssid = NULL; |
| wpa_s->ml_connect_probe_bss = NULL; |
| |
| return true; |
| } |
| |
| |
| static int wpa_supplicant_connect_ml_missing(struct wpa_supplicant *wpa_s, |
| struct wpa_bss *selected, |
| struct wpa_ssid *ssid) |
| { |
| int *freqs; |
| u16 missing_links = 0, removed_links; |
| u8 ap_mld_id; |
| |
| if (!((wpa_s->drv_flags2 & WPA_DRIVER_FLAGS2_MLO) && |
| (wpa_s->drv_flags & WPA_DRIVER_FLAGS_SME))) |
| return 0; |
| |
| if (wpa_bss_parse_basic_ml_element(wpa_s, selected, NULL, |
| &missing_links, ssid, |
| &ap_mld_id) || |
| !missing_links) |
| return 0; |
| |
| removed_links = wpa_bss_parse_reconf_ml_element(wpa_s, selected); |
| missing_links &= ~removed_links; |
| |
| if (!missing_links) |
| return 0; |
| |
| wpa_dbg(wpa_s, MSG_DEBUG, |
| "MLD: Doing an ML probe for missing links 0x%04x", |
| missing_links); |
| |
| freqs = os_malloc(sizeof(int) * 2); |
| if (!freqs) |
| return 0; |
| |
| wpa_s->ml_connect_probe_ssid = ssid; |
| wpa_s->ml_connect_probe_bss = selected; |
| |
| freqs[0] = selected->freq; |
| freqs[1] = 0; |
| |
| wpa_s->manual_scan_passive = 0; |
| wpa_s->manual_scan_use_id = 0; |
| wpa_s->manual_scan_only_new = 0; |
| wpa_s->scan_id_count = 0; |
| os_free(wpa_s->manual_scan_freqs); |
| wpa_s->manual_scan_freqs = freqs; |
| |
| os_memcpy(wpa_s->ml_probe_bssid, selected->bssid, ETH_ALEN); |
| |
| /* |
| * In case the ML probe request is intended to retrieve information from |
| * the transmitted BSS, the AP MLD ID should be included and should be |
| * set to zero. |
| * In case the ML probe requested is intended to retrieve information |
| * from a non-transmitted BSS, the AP MLD ID should not be included. |
| */ |
| if (ap_mld_id) |
| wpa_s->ml_probe_mld_id = -1; |
| else |
| wpa_s->ml_probe_mld_id = 0; |
| |
| if (ssid && ssid->ssid_len) { |
| os_free(wpa_s->ssids_from_scan_req); |
| wpa_s->num_ssids_from_scan_req = 0; |
| |
| wpa_s->ssids_from_scan_req = |
| os_zalloc(sizeof(struct wpa_ssid_value)); |
| if (wpa_s->ssids_from_scan_req) { |
| wpa_printf(MSG_DEBUG, |
| "MLD: ML probe: With direct SSID"); |
| |
| wpa_s->num_ssids_from_scan_req = 1; |
| wpa_s->ssids_from_scan_req[0].ssid_len = ssid->ssid_len; |
| os_memcpy(wpa_s->ssids_from_scan_req[0].ssid, |
| ssid->ssid, ssid->ssid_len); |
| } |
| } |
| |
| wpa_s->ml_probe_links = missing_links; |
| |
| wpa_s->normal_scans = 0; |
| wpa_s->scan_req = MANUAL_SCAN_REQ; |
| wpa_s->after_wps = 0; |
| wpa_s->known_wps_freq = 0; |
| wpa_supplicant_req_scan(wpa_s, 0, 0); |
| |
| return 1; |
| } |
| |
| |
| int wpa_supplicant_connect(struct wpa_supplicant *wpa_s, |
| struct wpa_bss *selected, |
| struct wpa_ssid *ssid) |
| { |
| #ifdef IEEE8021X_EAPOL |
| if ((eap_is_wps_pbc_enrollee(&ssid->eap) && |
| wpas_wps_partner_link_overlap_detect(wpa_s)) || |
| wpas_wps_scan_pbc_overlap(wpa_s, selected, ssid)) { |
| wpa_msg(wpa_s, MSG_INFO, WPS_EVENT_OVERLAP |
| "PBC session overlap"); |
| wpas_notify_wps_event_pbc_overlap(wpa_s); |
| wpa_s->wps_overlap = true; |
| #ifdef CONFIG_P2P |
| if (wpa_s->p2p_group_interface == P2P_GROUP_INTERFACE_CLIENT || |
| wpa_s->p2p_in_provisioning) { |
| eloop_register_timeout(0, 0, wpas_p2p_pbc_overlap_cb, |
| wpa_s, NULL); |
| return -1; |
| } |
| #endif /* CONFIG_P2P */ |
| |
| #ifdef CONFIG_WPS |
| wpas_wps_pbc_overlap(wpa_s); |
| wpas_wps_cancel(wpa_s); |
| #endif /* CONFIG_WPS */ |
| return -1; |
| } |
| #endif /* IEEE8021X_EAPOL */ |
| |
| wpa_msg(wpa_s, MSG_DEBUG, |
| "Considering connect request: reassociate: %d selected: " |
| MACSTR " bssid: " MACSTR " pending: " MACSTR |
| " wpa_state: %s ssid=%p current_ssid=%p", |
| wpa_s->reassociate, MAC2STR(selected->bssid), |
| MAC2STR(wpa_s->bssid), MAC2STR(wpa_s->pending_bssid), |
| wpa_supplicant_state_txt(wpa_s->wpa_state), |
| ssid, wpa_s->current_ssid); |
| |
| /* |
| * Do not trigger new association unless the BSSID has changed or if |
| * reassociation is requested. If we are in process of associating with |
| * the selected BSSID, do not trigger new attempt. |
| */ |
| if (wpa_s->reassociate || |
| (!ether_addr_equal(selected->bssid, wpa_s->bssid) && |
| ((wpa_s->wpa_state != WPA_ASSOCIATING && |
| wpa_s->wpa_state != WPA_AUTHENTICATING) || |
| (!is_zero_ether_addr(wpa_s->pending_bssid) && |
| !ether_addr_equal(selected->bssid, wpa_s->pending_bssid)) || |
| (is_zero_ether_addr(wpa_s->pending_bssid) && |
| ssid != wpa_s->current_ssid)))) { |
| if (wpa_supplicant_scard_init(wpa_s, ssid)) { |
| wpa_supplicant_req_new_scan(wpa_s, 10, 0); |
| return 0; |
| } |
| |
| if (wpa_supplicant_connect_ml_missing(wpa_s, selected, ssid)) |
| return 0; |
| |
| wpa_msg(wpa_s, MSG_DEBUG, "Request association with " MACSTR, |
| MAC2STR(selected->bssid)); |
| wpa_supplicant_associate(wpa_s, selected, ssid); |
| } else { |
| wpa_dbg(wpa_s, MSG_DEBUG, "Already associated or trying to " |
| "connect with the selected AP"); |
| } |
| |
| return 0; |
| } |
| |
| |
| static struct wpa_ssid * |
| wpa_supplicant_pick_new_network(struct wpa_supplicant *wpa_s) |
| { |
| size_t prio; |
| struct wpa_ssid *ssid; |
| |
| for (prio = 0; prio < wpa_s->conf->num_prio; prio++) { |
| for (ssid = wpa_s->conf->pssid[prio]; ssid; ssid = ssid->pnext) |
| { |
| if (wpas_network_disabled(wpa_s, ssid)) |
| continue; |
| #ifndef CONFIG_IBSS_RSN |
| if (ssid->mode == WPAS_MODE_IBSS && |
| !(ssid->key_mgmt & (WPA_KEY_MGMT_NONE | |
| WPA_KEY_MGMT_WPA_NONE))) { |
| wpa_printf(MSG_INFO, |
| "IBSS RSN not supported in the build - cannot use the profile for SSID '%s'", |
| wpa_ssid_txt(ssid->ssid, |
| ssid->ssid_len)); |
| wpa_msg_ctrl(wpa_s, MSG_INFO, |
| "IBSS RSN not supported in the build - cannot use the profile for SSID '%s'", |
| wpa_ctrl_ssid_txt(ssid->ssid, |
| ssid->ssid_len)); |
| continue; |
| } |
| #endif /* !CONFIG_IBSS_RSN */ |
| if (ssid->mode == WPAS_MODE_IBSS || |
| ssid->mode == WPAS_MODE_AP || |
| ssid->mode == WPAS_MODE_MESH) |
| return ssid; |
| } |
| } |
| return NULL; |
| } |
| |
| |
| /* TODO: move the rsn_preauth_scan_result*() to be called from notify.c based |
| * on BSS added and BSS changed events */ |
| static void wpa_supplicant_rsn_preauth_scan_results( |
| struct wpa_supplicant *wpa_s) |
| { |
| struct wpa_bss *bss; |
| |
| if (rsn_preauth_scan_results(wpa_s->wpa) < 0) |
| return; |
| |
| dl_list_for_each(bss, &wpa_s->bss, struct wpa_bss, list) { |
| const u8 *ssid, *rsn; |
| |
| ssid = wpa_bss_get_ie(bss, WLAN_EID_SSID); |
| if (ssid == NULL) |
| continue; |
| |
| rsn = wpa_bss_get_ie(bss, WLAN_EID_RSN); |
| if (rsn == NULL) |
| continue; |
| |
| rsn_preauth_scan_result(wpa_s->wpa, bss->bssid, ssid, rsn); |
| } |
| |
| } |
| |
| |
| #ifndef CONFIG_NO_ROAMING |
| |
| static int wpas_get_snr_signal_info(u32 frequency, int avg_signal, int noise) |
| { |
| if (noise == WPA_INVALID_NOISE) { |
| if (IS_5GHZ(frequency)) { |
| noise = DEFAULT_NOISE_FLOOR_5GHZ; |
| } else if (is_6ghz_freq(frequency)) { |
| noise = DEFAULT_NOISE_FLOOR_6GHZ; |
| } else { |
| noise = DEFAULT_NOISE_FLOOR_2GHZ; |
| } |
| } |
| return avg_signal - noise; |
| } |
| |
| |
| static unsigned int |
| wpas_get_est_throughput_from_bss_snr(const struct wpa_supplicant *wpa_s, |
| const struct wpa_bss *bss, int snr) |
| { |
| int rate = wpa_bss_get_max_rate(bss); |
| const u8 *ies = wpa_bss_ie_ptr(bss); |
| size_t ie_len = bss->ie_len ? bss->ie_len : bss->beacon_ie_len; |
| enum chan_width max_cw = CHAN_WIDTH_UNKNOWN; |
| |
| return wpas_get_est_tpt(wpa_s, ies, ie_len, rate, snr, bss->freq, |
| &max_cw); |
| } |
| |
| |
| static int wpas_evaluate_band_score(int frequency) |
| { |
| if (is_6ghz_freq(frequency)) |
| return 2; |
| if (IS_5GHZ(frequency)) |
| return 1; |
| return 0; |
| } |
| |
| |
| #ifndef CHROMIUM |
| |
| int wpa_supplicant_need_to_roam_within_ess(struct wpa_supplicant *wpa_s, |
| struct wpa_bss *current_bss, |
| struct wpa_bss *selected) |
| { |
| int min_diff, diff; |
| int cur_band_score, sel_band_score; |
| int to_5ghz, to_6ghz; |
| int cur_level, sel_level; |
| unsigned int cur_est, sel_est; |
| struct wpa_signal_info si; |
| int cur_snr = 0; |
| int ret = 0; |
| const u8 *cur_ies = wpa_bss_ie_ptr(current_bss); |
| const u8 *sel_ies = wpa_bss_ie_ptr(selected); |
| size_t cur_ie_len = current_bss->ie_len ? current_bss->ie_len : |
| current_bss->beacon_ie_len; |
| size_t sel_ie_len = selected->ie_len ? selected->ie_len : |
| selected->beacon_ie_len; |
| |
| wpa_dbg(wpa_s, MSG_DEBUG, "Considering within-ESS reassociation"); |
| wpa_dbg(wpa_s, MSG_DEBUG, "Current BSS: " MACSTR |
| " freq=%d level=%d snr=%d est_throughput=%u", |
| MAC2STR(current_bss->bssid), |
| current_bss->freq, current_bss->level, |
| current_bss->snr, current_bss->est_throughput); |
| wpa_dbg(wpa_s, MSG_DEBUG, "Selected BSS: " MACSTR |
| " freq=%d level=%d snr=%d est_throughput=%u", |
| MAC2STR(selected->bssid), selected->freq, selected->level, |
| selected->snr, selected->est_throughput); |
| |
| if (wpas_ap_link_address(wpa_s, selected->bssid)) { |
| wpa_dbg(wpa_s, MSG_DEBUG, "MLD: associated to selected BSS"); |
| return 0; |
| } |
| |
| if (wpa_s->current_ssid->bssid_set && |
| ether_addr_equal(selected->bssid, wpa_s->current_ssid->bssid)) { |
| wpa_dbg(wpa_s, MSG_DEBUG, "Allow reassociation - selected BSS " |
| "has preferred BSSID"); |
| return 1; |
| } |
| |
| /* |
| * Try to poll the signal from the driver since this will allow to get |
| * more accurate values. In some cases, there can be big differences |
| * between the RSSI of the Probe Response frames of the AP we are |
| * associated with and the Beacon frames we hear from the same AP after |
| * association. This can happen, e.g., when there are two antennas that |
| * hear the AP very differently. If the driver chooses to hear the |
| * Probe Response frames during the scan on the "bad" antenna because |
| * it wants to save power, but knows to choose the other antenna after |
| * association, we will hear our AP with a low RSSI as part of the |
| * scan even when we can hear it decently on the other antenna. To cope |
| * with this, ask the driver to teach us how it hears the AP. Also, the |
| * scan results may be a bit old, since we can very quickly get fresh |
| * information about our currently associated AP. |
| */ |
| if (wpa_drv_signal_poll(wpa_s, &si) == 0 && |
| (si.data.avg_beacon_signal || si.data.avg_signal)) { |
| /* |
| * Normalize avg_signal to the RSSI over 20 MHz, as the |
| * throughput is estimated based on the RSSI over 20 MHz |
| */ |
| cur_level = si.data.avg_beacon_signal ? |
| si.data.avg_beacon_signal : |
| (si.data.avg_signal - |
| wpas_channel_width_rssi_bump(cur_ies, cur_ie_len, |
| si.chanwidth)); |
| cur_snr = wpas_get_snr_signal_info(si.frequency, cur_level, |
| si.current_noise); |
| |
| cur_est = wpas_get_est_throughput_from_bss_snr(wpa_s, |
| current_bss, |
| cur_snr); |
| wpa_dbg(wpa_s, MSG_DEBUG, |
| "Using signal poll values for the current BSS: level=%d snr=%d est_throughput=%u", |
| cur_level, cur_snr, cur_est); |
| } else { |
| /* Level and SNR are measured over 20 MHz channel */ |
| cur_level = current_bss->level; |
| cur_snr = current_bss->snr; |
| cur_est = current_bss->est_throughput; |
| } |
| |
| /* Adjust the SNR of BSSes based on the channel width. */ |
| cur_level += wpas_channel_width_rssi_bump(cur_ies, cur_ie_len, |
| current_bss->max_cw); |
| cur_snr = wpas_adjust_snr_by_chanwidth(cur_ies, cur_ie_len, |
| current_bss->max_cw, cur_snr); |
| |
| sel_est = selected->est_throughput; |
| sel_level = selected->level + |
| wpas_channel_width_rssi_bump(sel_ies, sel_ie_len, |
| selected->max_cw); |
| |
| if (sel_est > cur_est + 5000) { |
| wpa_dbg(wpa_s, MSG_DEBUG, |
| "Allow reassociation - selected BSS has better estimated throughput"); |
| return 1; |
| } |
| |
| to_5ghz = selected->freq > 4000 && current_bss->freq < 4000; |
| to_6ghz = is_6ghz_freq(selected->freq) && |
| !is_6ghz_freq(current_bss->freq); |
| |
| if (cur_level < 0 && |
| cur_level > sel_level + to_5ghz * 2 + to_6ghz * 2 && |
| sel_est < cur_est * 1.2) { |
| wpa_dbg(wpa_s, MSG_DEBUG, "Skip roam - Current BSS has better " |
| "signal level"); |
| return 0; |
| } |
| |
| if (cur_est > sel_est + 5000) { |
| wpa_dbg(wpa_s, MSG_DEBUG, |
| "Skip roam - Current BSS has better estimated throughput"); |
| return 0; |
| } |
| |
| if (cur_snr > GREAT_SNR) { |
| wpa_dbg(wpa_s, MSG_DEBUG, |
| "Skip roam - Current BSS has good SNR (%u > %u)", |
| cur_snr, GREAT_SNR); |
| return 0; |
| } |
| |
| if (cur_level < -85) /* ..-86 dBm */ |
| min_diff = 1; |
| else if (cur_level < -80) /* -85..-81 dBm */ |
| min_diff = 2; |
| else if (cur_level < -75) /* -80..-76 dBm */ |
| min_diff = 3; |
| else if (cur_level < -70) /* -75..-71 dBm */ |
| min_diff = 4; |
| else if (cur_level < 0) /* -70..-1 dBm */ |
| min_diff = 5; |
| else /* unspecified units (not in dBm) */ |
| min_diff = 2; |
| |
| if (cur_est > sel_est * 1.5) |
| min_diff += 10; |
| else if (cur_est > sel_est * 1.2) |
| min_diff += 5; |
| else if (cur_est > sel_est * 1.1) |
| min_diff += 2; |
| else if (cur_est > sel_est) |
| min_diff++; |
| else if (sel_est > cur_est * 1.5) |
| min_diff -= 10; |
| else if (sel_est > cur_est * 1.2) |
| min_diff -= 5; |
| else if (sel_est > cur_est * 1.1) |
| min_diff -= 2; |
| else if (sel_est > cur_est) |
| min_diff--; |
| |
| cur_band_score = wpas_evaluate_band_score(current_bss->freq); |
| sel_band_score = wpas_evaluate_band_score(selected->freq); |
| min_diff += (cur_band_score - sel_band_score) * 2; |
| if (wpa_s->signal_threshold && cur_level <= wpa_s->signal_threshold && |
| sel_level > wpa_s->signal_threshold) |
| min_diff -= 2; |
| diff = sel_level - cur_level; |
| if (diff < min_diff) { |
| wpa_dbg(wpa_s, MSG_DEBUG, |
| "Skip roam - too small difference in signal level (%d < %d)", |
| diff, min_diff); |
| ret = 0; |
| } else { |
| wpa_dbg(wpa_s, MSG_DEBUG, |
| "Allow reassociation due to difference in signal level (%d >= %d)", |
| diff, min_diff); |
| ret = 1; |
| } |
| wpa_msg_ctrl(wpa_s, MSG_INFO, "%scur_bssid=" MACSTR |
| " cur_freq=%d cur_level=%d cur_est=%d sel_bssid=" MACSTR |
| " sel_freq=%d sel_level=%d sel_est=%d", |
| ret ? WPA_EVENT_DO_ROAM : WPA_EVENT_SKIP_ROAM, |
| MAC2STR(current_bss->bssid), |
| current_bss->freq, cur_level, cur_est, |
| MAC2STR(selected->bssid), |
| selected->freq, sel_level, sel_est); |
| return ret; |
| } |
| #else /* CHROMIUM */ |
| |
| int wpa_supplicant_need_to_roam_within_ess(struct wpa_supplicant *wpa_s, |
| struct wpa_bss *current_bss, |
| struct wpa_bss *selected) |
| { |
| /* |
| * These constants are used to perform calculations to determine |
| * min_diff, the minimum RSSI difference needed to roam, and |
| * adjust_factor, the estimated throughput weight, based on the RSSI of |
| * the current AP. |
| * |
| * Currently min_diff and adjust_factor are bounded as follows: |
| * 4 <= min_diff <= 15, before applying 5GHz and bgscan threshold biases |
| * 0 <= adjust_factor <= 2.75 |
| */ |
| const int low_rssi = -85, high_rssi = -30; |
| const int rssi_bucket_size = 5; |
| const int min_diff_offset = 21, default_min_diff = 5; |
| const double adjust_factor_offset = 17, default_adjust_factor = 4; |
| const double adjust_factor_step = 0.25; |
| const double max_est_ratio = 2; |
| |
| int min_diff, diff; |
| int cur_band_score, sel_band_score; |
| int cur_level, sel_level, temp_level; |
| int adjust = 0; |
| double adjust_factor, est_ratio; |
| unsigned int cur_est, sel_est; |
| struct wpa_signal_info si; |
| int cur_snr = 0, sel_snr = 0; |
| int ret = 0; |
| const u8 *cur_ies = wpa_bss_ie_ptr(current_bss); |
| const u8 *sel_ies = wpa_bss_ie_ptr(selected); |
| size_t cur_ie_len = current_bss->ie_len ? current_bss->ie_len : |
| current_bss->beacon_ie_len; |
| size_t sel_ie_len = selected->ie_len ? selected->ie_len : |
| selected->beacon_ie_len; |
| |
| wpa_dbg(wpa_s, MSG_INFO, "Considering within-ESS reassociation"); |
| wpa_dbg(wpa_s, MSG_INFO, "Current BSS: " MACSTR |
| " freq=%d level=%d snr=%d est_throughput=%u", |
| MAC2STR(current_bss->bssid), |
| current_bss->freq, current_bss->level, |
| current_bss->snr, current_bss->est_throughput); |
| wpa_dbg(wpa_s, MSG_INFO, "Selected BSS: " MACSTR |
| " freq=%d level=%d snr=%d est_throughput=%u", |
| MAC2STR(selected->bssid), selected->freq, selected->level, |
| selected->snr, selected->est_throughput); |
| |
| if (wpas_ap_link_address(wpa_s, selected->bssid)) { |
| wpa_dbg(wpa_s, MSG_DEBUG, "MLD: associated to selected BSS"); |
| return 0; |
| } |
| |
| if (wpa_s->current_ssid->bssid_set && |
| os_memcmp(selected->bssid, wpa_s->current_ssid->bssid, ETH_ALEN) == |
| 0) { |
| wpa_dbg(wpa_s, MSG_INFO, "Allow reassociation - selected BSS " |
| "has preferred BSSID"); |
| return 1; |
| } |
| |
| /* |
| * Try to poll the signal from the driver since this will allow to get |
| * more accurate values. In some cases, there can be big differences |
| * between the RSSI of the Probe Response frames of the AP we are |
| * associated with and the Beacon frames we hear from the same AP after |
| * association. This can happen, e.g., when there are two antennas that |
| * hear the AP very differently. If the driver chooses to hear the |
| * Probe Response frames during the scan on the "bad" antenna because |
| * it wants to save power, but knows to choose the other antenna after |
| * association, we will hear our AP with a low RSSI as part of the |
| * scan even when we can hear it decently on the other antenna. To cope |
| * with this, ask the driver to teach us how it hears the AP. Also, the |
| * scan results may be a bit old, since we can very quickly get fresh |
| * information about our currently associated AP. |
| */ |
| if (wpa_drv_signal_poll(wpa_s, &si) == 0 && |
| (si.data.avg_beacon_signal || si.data.avg_signal)) { |
| /* |
| * Normalize avg_signal to the RSSI over 20 MHz, as the |
| * throughput is estimated based on the RSSI over 20 MHz |
| */ |
| cur_level = si.data.avg_beacon_signal ? |
| si.data.avg_beacon_signal : |
| (si.data.avg_signal - |
| wpas_channel_width_rssi_bump(cur_ies, cur_ie_len, |
| si.chanwidth)); |
| cur_snr = wpas_get_snr_signal_info(si.frequency, cur_level, |
| si.current_noise); |
| |
| cur_est = wpas_get_est_throughput_from_bss_snr(wpa_s, |
| current_bss, |
| cur_snr); |
| wpa_dbg(wpa_s, MSG_INFO, |
| "Using signal poll values for the current BSS: level=%d snr=%d est_throughput=%u", |
| cur_level, cur_snr, cur_est); |
| } else { |
| /* Level and SNR are measured over 20 MHz channel */ |
| cur_level = current_bss->level; |
| cur_snr = current_bss->snr; |
| cur_est = current_bss->est_throughput; |
| } |
| /* Adjust the SNR of BSSes based on the channel width. */ |
| cur_level += wpas_channel_width_rssi_bump(cur_ies, cur_ie_len, |
| current_bss->max_cw); |
| cur_snr = wpas_adjust_snr_by_chanwidth(cur_ies, cur_ie_len, |
| current_bss->max_cw, cur_snr); |
| |
| sel_est = selected->est_throughput; |
| sel_level = selected->level + |
| wpas_channel_width_rssi_bump(sel_ies, sel_ie_len, |
| selected->max_cw); |
| sel_snr = wpas_adjust_snr_by_chanwidth(sel_ies, sel_ie_len, |
| selected->max_cw, selected->snr); |
| |
| /* |
| * Short-circuit the roaming heuristic to bias toward association on |
| * 6GHz. Apply a 3dBm "roaming difficulty" so that natural RSSI |
| * fluctuation don't cause ping-ponging between 6GHz and non-6GHz BSSes. |
| */ |
| if (is_6ghz_freq(current_bss->freq) && !is_6ghz_freq(selected->freq)) { |
| if (cur_snr >= GREAT_SNR - 3) { |
| wpa_dbg(wpa_s, MSG_INFO, "Skip roam - bias toward 6GHz"); |
| return 0; |
| } |
| } else if (is_6ghz_freq(selected->freq) && |
| !is_6ghz_freq(current_bss->freq)) { |
| if (sel_snr >= GREAT_SNR + 3) { |
| wpa_dbg(wpa_s, MSG_INFO, "Allow roam - bias toward 6GHz"); |
| return 1; |
| } |
| } |
| |
| /* |
| * At low RSSI, we ignore estimated throughput gains and only consider |
| * RSSI gains. At higher RSSI, adjust_factor is multiplied by adjust |
| * (set below according to the selected AP's estimated throughput |
| * relative to the current AP's estimated throughput) to influence the |
| * min_diff. |
| */ |
| if (cur_level < 0) { |
| temp_level = cur_level; |
| if (temp_level < low_rssi) |
| temp_level = low_rssi; |
| else if (temp_level > high_rssi) |
| temp_level = high_rssi; |
| min_diff = min_diff_offset + temp_level / rssi_bucket_size; |
| adjust_factor = adjust_factor_offset + |
| temp_level / rssi_bucket_size; |
| } else { /* unspecified units (not in dBm) */ |
| min_diff = default_min_diff; |
| adjust_factor = default_adjust_factor; |
| } |
| adjust_factor *= adjust_factor_step; |
| |
| if (cur_est > sel_est) { |
| adjust = 1; |
| est_ratio = sel_est == 0 ? max_est_ratio : |
| (double) cur_est / sel_est; |
| } else { |
| adjust = -1; |
| est_ratio = cur_est == 0 ? max_est_ratio : |
| (double) sel_est / cur_est; |
| } |
| if (est_ratio > max_est_ratio) |
| est_ratio = max_est_ratio; |
| adjust *= (est_ratio - 1) * 10; |
| min_diff += (int) (adjust * adjust_factor); |
| |
| /* |
| * Networks of higher bands usually have lower traffic load and therefore |
| * often provide higher throughput even for identical estimated throughputs. |
| * We give scores 0, 1, and 2 to 2.4GHz, 5GHz and 6GHz bands respectively, |
| * so that min_diff is smaller and it is easier to roam to networks of |
| * higher bands. |
| */ |
| cur_band_score = wpas_evaluate_band_score(current_bss->freq); |
| sel_band_score = wpas_evaluate_band_score(selected->freq); |
| min_diff += (cur_band_score - sel_band_score) * 2; |
| if (wpa_s->signal_threshold && cur_level <= wpa_s->signal_threshold && |
| sel_level > wpa_s->signal_threshold) |
| min_diff -= 2; |
| diff = sel_level - cur_level; |
| if (diff < min_diff) { |
| wpa_dbg(wpa_s, MSG_INFO, |
| "Skip roam - too small difference in signal level (%d < %d)", |
| diff, min_diff); |
| ret = 0; |
| } else { |
| wpa_dbg(wpa_s, MSG_INFO, |
| "Allow reassociation due to difference in signal level (%d >= %d)", |
| diff, min_diff); |
| ret = 1; |
| } |
| wpa_msg_ctrl(wpa_s, MSG_INFO, "%scur_bssid=" MACSTR |
| " cur_freq=%d cur_level=%d cur_est=%d sel_bssid=" MACSTR |
| " sel_freq=%d sel_level=%d sel_est=%d", |
| ret ? WPA_EVENT_DO_ROAM : WPA_EVENT_SKIP_ROAM, |
| MAC2STR(current_bss->bssid), |
| current_bss->freq, cur_level, cur_est, |
| MAC2STR(selected->bssid), |
| selected->freq, sel_level, sel_est); |
| return ret; |
| } |
| #endif /* CHROMIUM */ |
| #endif /* CONFIG_NO_ROAMING */ |
| |
| |
| static int wpa_supplicant_need_to_roam(struct wpa_supplicant *wpa_s, |
| struct wpa_bss *selected, |
| struct wpa_ssid *ssid) |
| { |
| struct wpa_bss *current_bss = NULL; |
| const u8 *bssid; |
| |
| if (wpa_s->reassociate) |
| return 1; /* explicit request to reassociate */ |
| if (wpa_s->wpa_state < WPA_ASSOCIATED) |
| return 1; /* we are not associated; continue */ |
| if (wpa_s->current_ssid == NULL) |
| return 1; /* unknown current SSID */ |
| if (wpa_s->current_ssid != ssid) |
| return 1; /* different network block */ |
| |
| if (wpas_driver_bss_selection(wpa_s)) |
| return 0; /* Driver-based roaming */ |
| |
| if (wpa_s->valid_links) |
| bssid = wpa_s->links[wpa_s->mlo_assoc_link_id].bssid; |
| else |
| bssid = wpa_s->bssid; |
| |
| if (wpa_s->current_ssid->ssid) |
| current_bss = wpa_bss_get(wpa_s, bssid, |
| wpa_s->current_ssid->ssid, |
| wpa_s->current_ssid->ssid_len); |
| if (!current_bss) |
| current_bss = wpa_bss_get_bssid(wpa_s, bssid); |
| |
| if (!current_bss) |
| return 1; /* current BSS not seen in scan results */ |
| |
| if (current_bss == selected) |
| return 0; |
| |
| if (selected->last_update_idx > current_bss->last_update_idx) |
| return 1; /* current BSS not seen in the last scan */ |
| |
| #ifndef CONFIG_NO_ROAMING |
| return wpa_supplicant_need_to_roam_within_ess(wpa_s, current_bss, |
| selected); |
| #else /* CONFIG_NO_ROAMING */ |
| return 0; |
| #endif /* CONFIG_NO_ROAMING */ |
| } |
| |
| |
| static int wpas_trigger_6ghz_scan(struct wpa_supplicant *wpa_s, |
| union wpa_event_data *data) |
| { |
| struct wpa_driver_scan_params params; |
| unsigned int j; |
| |
| wpa_dbg(wpa_s, MSG_INFO, "Triggering 6GHz-only scan"); |
| os_memset(¶ms, 0, sizeof(params)); |
| params.non_coloc_6ghz = wpa_s->last_scan_non_coloc_6ghz; |
| for (j = 0; j < data->scan_info.num_ssids; j++) |
| params.ssids[j] = data->scan_info.ssids[j]; |
| params.num_ssids = data->scan_info.num_ssids; |
| wpa_add_scan_freqs_list(wpa_s, HOSTAPD_MODE_IEEE80211A, ¶ms, |
| true, false, false); |
| if (!wpa_supplicant_trigger_scan(wpa_s, ¶ms, true, true)) { |
| wpa_s->scan_in_progress_6ghz = true; |
| wpas_notify_scan_in_progress_6ghz(wpa_s); |
| os_free(params.freqs); |
| return 1; |
| } |
| wpa_dbg(wpa_s, MSG_INFO, "Failed to trigger 6GHz-only scan"); |
| os_free(params.freqs); |
| return 0; |
| } |
| |
| |
| static bool wpas_short_ssid_match(struct wpa_supplicant *wpa_s, |
| struct wpa_scan_results *scan_res) |
| { |
| size_t i; |
| u32 current_ssid_short = ieee80211_crc32(wpa_s->current_ssid->ssid, |
| wpa_s->current_ssid->ssid_len); |
| |
| for (i = 0; i < scan_res->num; i++) { |
| struct wpa_scan_res *res = scan_res->res[i]; |
| const u8 *rnr_ie, *ie_end; |
| const struct ieee80211_neighbor_ap_info *info; |
| size_t left; |
| |
| rnr_ie = wpa_scan_get_ie(res, WLAN_EID_REDUCED_NEIGHBOR_REPORT); |
| if (!rnr_ie) |
| continue; |
| |
| ie_end = rnr_ie + 2 + rnr_ie[1]; |
| rnr_ie += 2; |
| |
| info = (const struct ieee80211_neighbor_ap_info *) rnr_ie; |
| if (info->tbtt_info_len < 11) |
| continue; |
| left = ie_end - rnr_ie; |
| |
| if (left < sizeof(struct ieee80211_neighbor_ap_info)) |
| continue; |
| |
| left -= sizeof(struct ieee80211_neighbor_ap_info); |
| if (left < info->tbtt_info_len) |
| continue; |
| |
| rnr_ie += sizeof(struct ieee80211_neighbor_ap_info); |
| while (rnr_ie + 11 <= ie_end) { |
| /* skip TBTT offset and BSSID */ |
| u32 short_ssid = WPA_GET_LE32(rnr_ie + 1 + ETH_ALEN); |
| |
| if (short_ssid == current_ssid_short) |
| return true; |
| |
| rnr_ie += info->tbtt_info_len; |
| } |
| } |
| |
| return false; |
| } |
| |
| |
| /* |
| * Return a negative value if no scan results could be fetched or if scan |
| * results should not be shared with other virtual interfaces. |
| * Return 0 if scan results were fetched and may be shared with other |
| * interfaces. |
| * Return 1 if scan results may be shared with other virtual interfaces but may |
| * not trigger any operations. |
| * Return 2 if the interface was removed and cannot be used. |
| */ |
| static int _wpa_supplicant_event_scan_results(struct wpa_supplicant *wpa_s, |
| union wpa_event_data *data, |
| int own_request, int update_only) |
| { |
| struct wpa_scan_results *scan_res = NULL; |
| int ret = 0; |
| int ap = 0; |
| bool trigger_6ghz_scan; |
| bool short_ssid_match_found = false; |
| #ifndef CONFIG_NO_RANDOM_POOL |
| size_t i, num; |
| #endif /* CONFIG_NO_RANDOM_POOL */ |
| |
| #ifdef CONFIG_AP |
| if (wpa_s->ap_iface) |
| ap = 1; |
| #endif /* CONFIG_AP */ |
| |
| trigger_6ghz_scan = wpa_s->crossed_6ghz_dom && |
| wpa_s->last_scan_all_chan; |
| wpa_s->crossed_6ghz_dom = false; |
| wpa_s->last_scan_all_chan = false; |
| |
| wpa_supplicant_notify_scanning(wpa_s, 0); |
| |
| scan_res = wpa_supplicant_get_scan_results(wpa_s, |
| data ? &data->scan_info : |
| NULL, 1, NULL); |
| |
| if (wpa_s->scan_in_progress_6ghz) { |
| wpa_s->scan_in_progress_6ghz = false; |
| wpas_notify_scan_in_progress_6ghz(wpa_s); |
| } |
| |
| if (scan_res == NULL) { |
| if (wpa_s->conf->ap_scan == 2 || ap || |
| wpa_s->scan_res_handler == scan_only_handler) |
| return -1; |
| if (!own_request) |
| return -1; |
| if (data && data->scan_info.external_scan) |
| return -1; |
| if (wpa_s->scan_res_fail_handler) { |
| void (*handler)(struct wpa_supplicant *wpa_s); |
| |
| handler = wpa_s->scan_res_fail_handler; |
| wpa_s->scan_res_fail_handler = NULL; |
| handler(wpa_s); |
| } else { |
| wpa_dbg(wpa_s, MSG_DEBUG, |
| "Failed to get scan results - try scanning again"); |
| wpa_supplicant_req_new_scan(wpa_s, 1, 0); |
| } |
| |
| ret = -1; |
| goto scan_work_done; |
| } |
| |
| #ifndef CONFIG_NO_RANDOM_POOL |
| num = scan_res->num; |
| if (num > 10) |
| num = 10; |
| for (i = 0; i < num; i++) { |
| u8 buf[5]; |
| struct wpa_scan_res *res = scan_res->res[i]; |
| buf[0] = res->bssid[5]; |
| buf[1] = res->qual & 0xff; |
| buf[2] = res->noise & 0xff; |
| buf[3] = res->level & 0xff; |
| buf[4] = res->tsf & 0xff; |
| random_add_randomness(buf, sizeof(buf)); |
| } |
| #endif /* CONFIG_NO_RANDOM_POOL */ |
| |
| wpa_s->last_scan_external = data && data->scan_info.external_scan; |
| |
| if (update_only) { |
| ret = 1; |
| goto scan_work_done; |
| } |
| |
| if (own_request && wpa_s->scan_res_handler && |
| !(data && data->scan_info.external_scan)) { |
| void (*scan_res_handler)(struct wpa_supplicant *wpa_s, |
| struct wpa_scan_results *scan_res); |
| |
| scan_res_handler = wpa_s->scan_res_handler; |
| wpa_s->scan_res_handler = NULL; |
| scan_res_handler(wpa_s, scan_res); |
| ret = 1; |
| goto scan_work_done; |
| } |
| |
| wpa_dbg(wpa_s, MSG_DEBUG, "New scan results available (own=%u ext=%u)", |
| wpa_s->own_scan_running, |
| data ? data->scan_info.external_scan : 0); |
| if (wpa_s->last_scan_req == MANUAL_SCAN_REQ && |
| wpa_s->manual_scan_use_id && wpa_s->own_scan_running && |
| own_request && !(data && data->scan_info.external_scan)) { |
| wpa_msg_ctrl(wpa_s, MSG_INFO, WPA_EVENT_SCAN_RESULTS "id=%u", |
| wpa_s->manual_scan_id); |
| wpa_s->manual_scan_use_id = 0; |
| } else { |
| wpa_msg_ctrl(wpa_s, MSG_INFO, WPA_EVENT_SCAN_RESULTS); |
| } |
| wpas_notify_scan_results(wpa_s); |
| |
| wpas_notify_scan_done(wpa_s, 1); |
| |
| if (ap) { |
| wpa_dbg(wpa_s, MSG_DEBUG, "Ignore scan results in AP mode"); |
| #ifdef CONFIG_AP |
| if (wpa_s->ap_iface->scan_cb) |
| wpa_s->ap_iface->scan_cb(wpa_s->ap_iface); |
| #endif /* CONFIG_AP */ |
| goto scan_work_done; |
| } |
| |
| if (data && data->scan_info.external_scan) { |
| wpa_dbg(wpa_s, MSG_DEBUG, "Do not use results from externally requested scan operation for network selection"); |
| wpa_scan_results_free(scan_res); |
| return 0; |
| } |
| |
| if (wnm_scan_process(wpa_s, false) > 0) |
| goto scan_work_done; |
| |
| if (sme_proc_obss_scan(wpa_s) > 0) |
| goto scan_work_done; |
| |
| #ifndef CONFIG_NO_RRM |
| if (own_request && data && |
| wpas_beacon_rep_scan_process(wpa_s, scan_res, &data->scan_info) > 0) |
| goto scan_work_done; |
| #endif /* CONFIG_NO_RRM */ |
| |
| if (ml_link_probe_scan(wpa_s)) |
| goto scan_work_done; |
| |
| if ((wpa_s->conf->ap_scan == 2 && !wpas_wps_searching(wpa_s))) |
| goto scan_work_done; |
| |
| if (autoscan_notify_scan(wpa_s, scan_res)) |
| goto scan_work_done; |
| |
| if (wpa_s->disconnected) { |
| wpa_supplicant_set_state(wpa_s, WPA_DISCONNECTED); |
| goto scan_work_done; |
| } |
| |
| if (!wpas_driver_bss_selection(wpa_s) && |
| bgscan_notify_scan(wpa_s, scan_res) == 1) |
| goto scan_work_done; |
| |
| wpas_wps_update_ap_info(wpa_s, scan_res); |
| |
| if (wpa_s->wpa_state >= WPA_AUTHENTICATING && |
| wpa_s->wpa_state < WPA_COMPLETED) |
| goto scan_work_done; |
| |
| if (wpa_s->current_ssid && trigger_6ghz_scan && own_request && data && |
| wpas_short_ssid_match(wpa_s, scan_res)) { |
| wpa_dbg(wpa_s, MSG_INFO, "Short SSID match in scan results"); |
| short_ssid_match_found = true; |
| } |
| |
| wpa_scan_results_free(scan_res); |
| |
| if (own_request && wpa_s->scan_work) { |
| struct wpa_radio_work *work = wpa_s->scan_work; |
| wpa_s->scan_work = NULL; |
| radio_work_done(work); |
| } |
| |
| os_free(wpa_s->last_scan_freqs); |
| wpa_s->last_scan_freqs = NULL; |
| wpa_s->num_last_scan_freqs = 0; |
| if (own_request && data && |
| data->scan_info.freqs && data->scan_info.num_freqs) { |
| wpa_s->last_scan_freqs = os_malloc(sizeof(int) * |
| data->scan_info.num_freqs); |
| if (wpa_s->last_scan_freqs) { |
| os_memcpy(wpa_s->last_scan_freqs, |
| data->scan_info.freqs, |
| sizeof(int) * data->scan_info.num_freqs); |
| wpa_s->num_last_scan_freqs = data->scan_info.num_freqs; |
| } |
| } |
| |
| if (wpa_s->supp_pbc_active && !wpas_wps_partner_link_scan_done(wpa_s)) |
| return ret; |
| |
| if (short_ssid_match_found && wpas_trigger_6ghz_scan(wpa_s, data) > 0) |
| return 1; |
| |
| return wpas_select_network_from_last_scan(wpa_s, 1, own_request, |
| trigger_6ghz_scan, data); |
| |
| scan_work_done: |
| wpa_scan_results_free(scan_res); |
| if (own_request && wpa_s->scan_work) { |
| struct wpa_radio_work *work = wpa_s->scan_work; |
| wpa_s->scan_work = NULL; |
| radio_work_done(work); |
| } |
| return ret; |
| } |
| |
| |
| /** |
| * Select a network from the last scan |
| * @wpa_s: Pointer to wpa_supplicant data |
| * @new_scan: Whether this function was called right after a scan has finished |
| * @own_request: Whether the scan was requested by this interface |
| * @trigger_6ghz_scan: Whether to trigger a 6ghz-only scan when applicable |
| * @data: Scan data from scan that finished if applicable |
| * |
| * See _wpa_supplicant_event_scan_results() for return values. |
| */ |
| static int wpas_select_network_from_last_scan(struct wpa_supplicant *wpa_s, |
| int new_scan, int own_request, |
| bool trigger_6ghz_scan, |
| union wpa_event_data *data) |
| { |
| struct wpa_bss *selected; |
| struct wpa_ssid *ssid = NULL; |
| int time_to_reenable = wpas_reenabled_network_time(wpa_s); |
| |
| if (time_to_reenable > 0) { |
| wpa_dbg(wpa_s, MSG_DEBUG, |
| "Postpone network selection by %d seconds since all networks are disabled", |
| time_to_reenable); |
| eloop_cancel_timeout(wpas_network_reenabled, wpa_s, NULL); |
| eloop_register_timeout(time_to_reenable, 0, |
| wpas_network_reenabled, wpa_s, NULL); |
| return 0; |
| } |
| |
| if (wpa_s->p2p_mgmt) |
| return 0; /* no normal connection on p2p_mgmt interface */ |
| |
| wpa_s->owe_transition_search = 0; |
| #ifdef CONFIG_OWE |
| os_free(wpa_s->owe_trans_scan_freq); |
| wpa_s->owe_trans_scan_freq = NULL; |
| #endif /* CONFIG_OWE */ |
| selected = wpa_supplicant_pick_network(wpa_s, &ssid); |
| |
| #ifdef CONFIG_MESH |
| if (wpa_s->ifmsh) { |
| wpa_msg(wpa_s, MSG_INFO, |
| "Avoiding join because we already joined a mesh group"); |
| return 0; |
| } |
| #endif /* CONFIG_MESH */ |
| |
| if (selected) { |
| int skip; |
| skip = !wpa_supplicant_need_to_roam(wpa_s, selected, ssid); |
| if (skip) { |
| if (new_scan) |
| wpa_supplicant_rsn_preauth_scan_results(wpa_s); |
| return 0; |
| } |
| |
| wpa_s->suitable_network++; |
| |
| if (ssid != wpa_s->current_ssid && |
| wpa_s->wpa_state >= WPA_AUTHENTICATING) { |
| wpa_s->own_disconnect_req = 1; |
| wpa_supplicant_deauthenticate( |
| wpa_s, WLAN_REASON_DEAUTH_LEAVING); |
| } |
| |
| if (wpa_supplicant_connect(wpa_s, selected, ssid) < 0) { |
| wpa_dbg(wpa_s, MSG_DEBUG, "Connect failed"); |
| return -1; |
| } |
| wpa_s->supp_pbc_active = false; |
| |
| if (new_scan) |
| wpa_supplicant_rsn_preauth_scan_results(wpa_s); |
| /* |
| * Do not allow other virtual radios to trigger operations based |
| * on these scan results since we do not want them to start |
| * other associations at the same time. |
| */ |
| return 1; |
| } else { |
| wpa_s->no_suitable_network++; |
| wpa_dbg(wpa_s, MSG_DEBUG, "No suitable network found"); |
| ssid = wpa_supplicant_pick_new_network(wpa_s); |
| if (ssid) { |
| wpa_dbg(wpa_s, MSG_DEBUG, "Setup a new network"); |
| wpa_supplicant_associate(wpa_s, NULL, ssid); |
| if |