diff -r 000000000000 -r c40eb8fe8501 wlan_bearer/wlanengine/wlan_common/wlanengine_common_3.1/src/core_operation_roam.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/wlan_bearer/wlanengine/wlan_common/wlanengine_common_3.1/src/core_operation_roam.cpp Tue Feb 02 02:03:13 2010 +0200 @@ -0,0 +1,1359 @@ +/* +* Copyright (c) 2005-2010 Nokia Corporation and/or its subsidiary(-ies). +* All rights reserved. +* This component and the accompanying materials are made available +* under the terms of the License "Eclipse Public License v1.0" +* which accompanies this distribution, and is available +* at the URL "http://www.eclipse.org/legal/epl-v10.html". +* +* Initial Contributors: +* Nokia Corporation - initial contribution. +* +* Contributors: +* +* Description: Statemachine for roaming +* +*/ + +/* +* %version: 75 % +*/ + +#include "core_operation_roam.h" +#include "core_server.h" +#include "core_tools.h" +#include "core_tools_parser.h" +#include "core_sub_operation_connect.h" +#include "core_sub_operation_adhoc.h" +#include "core_sub_operation_wep_connect.h" +#include "core_sub_operation_wpa_connect.h" +#include "core_sub_operation_roam_update_ts.h" +#include "core_operation_update_power_mode.h" +#include "core_frame_assoc_resp.h" +#include "core_frame_dot11.h" +#include "core_frame_qbss_load_ie.h" +#include "core_scan_list.h" +#include "core_frame_nr_ie.h" +#include "core_frame_action_nr.h" +#include "core_frame_action_rm.h" +#include "core_frame_radio_measurement_action.h" +#include "am_debug.h" + +const u32_t CORE_ROAMING_LIST_BONUS_PMKSA = 20; +const u32_t CORE_ROAMING_LIST_BONUS_WPX_FAST_ROAM = 20; + +/** The maximum amount association/authentication failures when WAPI is used. */ +const u32_t CORE_MAX_WAPI_AP_ASSOCIATION_FAILURE_COUNT = 10; + +/** Definitions for RRM Enabled Capabilities bitmask */ +const u64_t RRM_CAPABILITY_BIT_MASK_NEIGHBOR_REPORT = 0x0000000002; + +// ======== MEMBER FUNCTIONS ======== + +// --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- +// +core_operation_roam_c::core_operation_roam_c( + u32_t request_id, + core_server_c* server, + abs_core_driverif_c* drivers, + abs_core_server_callback_c* adaptation, + bool_t& is_connected, + u8_t tag, + u8_t min_required_rcpi, + const medium_time_s& min_medium_time, + const core_ssid_s& ssid, + const core_mac_address_s& bssid ) : + core_operation_base_c( core_operation_roam, request_id, server, drivers, adaptation, + core_base_flag_drivers_needed ), + is_connected_m( is_connected ), + tag_m( tag ), + min_required_rcpi_m( min_required_rcpi ), + min_medium_time_m( min_medium_time ), + ssid_m( ssid ), + bssid_m( bssid ), + roaming_list_m( ), + management_status_m( core_management_status_success ), + connect_status_m( core_connect_network_not_found ), + current_ap_m( NULL ), + current_bssid_m( ZERO_MAC_ADDR ), + assoc_ie_list_m( false_t ), + is_cached_sa_used_m( false_t ), + assoc_resp_m( NULL ) + { + DEBUG( "core_operation_roam_c::core_operation_roam_c()" ); + } + +// --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- +// +core_operation_roam_c::~core_operation_roam_c() + { + DEBUG( "core_operation_roam_c::~core_operation_roam_c()" ); + + for ( roaming_list_entry_s* iter = roaming_list_m.first(); iter; iter = roaming_list_m.next() ) + { + delete iter->ap_data; + } + + roaming_list_m.clear(); + assoc_ie_list_m.clear(); + delete assoc_resp_m; + current_ap_m = NULL; + } + +// --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- +// +core_error_e core_operation_roam_c::next_state() + { + DEBUG( "core_operation_roam_c::next_state()" ); + + switch ( operation_state_m ) + { + case core_state_init: + { + if( server_m->get_connection_data()->last_roam_reason() != core_roam_reason_initial_connect ) + { + server_m->get_core_settings().roam_metrics().inc_roam_attempt_count( + server_m->get_connection_data()->last_roam_reason() ); + } + + if( is_connected_m ) + { + current_bssid_m = server_m->get_connection_data()->current_ap_data()->bssid(); + + DEBUG6( "core_operation_roam_c::next_state() - current BSSID: %02X:%02X:%02X:%02X:%02X:%02X", + current_bssid_m.addr[0], current_bssid_m.addr[1], current_bssid_m.addr[2], + current_bssid_m.addr[3], current_bssid_m.addr[4], current_bssid_m.addr[5] ); + } + + DEBUG6( "core_operation_roam_c::next_state() - requested BSSID: %02X:%02X:%02X:%02X:%02X:%02X", + bssid_m.addr[0], bssid_m.addr[1], bssid_m.addr[2], + bssid_m.addr[3], bssid_m.addr[4], bssid_m.addr[5] ); + DEBUG1( "core_operation_roam_c::next_state() - minimum required RCPI value: %u", + min_required_rcpi_m ); + + /** + * Set all the traffic streams as not active and reset TSPEC parameters to the + * default values. + */ + core_traffic_stream_list_c& ts_list( + server_m->get_connection_data()->traffic_stream_list() ); + for( core_traffic_stream_c* iter = ts_list.first(); iter; iter = ts_list.next() ) + { + iter->set_status( core_traffic_stream_status_undefined ); + iter->reset_to_default_values(); + } + + if ( !is_connected_m ) + { + /** + * Indicate core_connection_state_searching state to adaptation. + */ + core_connection_state_e state = core_connection_state_searching; + server_m->get_core_settings().set_connection_state( state ); + + if ( server_m->get_connection_data()->last_connection_state() != state ) + { + u8_t buf[5]; + buf[0] = static_cast( state ); + adaptation_m->notify( + core_notification_connection_state_changed, + 1, + buf ); + + server_m->get_connection_data()->set_last_connection_state( + state ); + } + } + + server_m->get_scan_list().print_contents(); + + DEBUG1( "core_operation_roam_c::next_state() - tag entry for iterator is 0x%02X", + tag_m ); + core_scan_list_iterator_by_tag_and_ssid_and_bssid_c iter( + server_m->get_scan_list(), + tag_m, + ssid_m, + bssid_m ); + for ( core_ap_data_c* ap_data = iter.first(); ap_data; ap_data = iter.next() ) + { + if ( process_frame( *ap_data ) ) + { + DEBUG( "core_operation_roam_c::next_state() - AP added to roaming list" ); + } + } + +#ifdef _DEBUG + DEBUG( "core_operation_roam_c::next_state() - roaming list start" ); + + roaming_list_entry_s* entry_iter = roaming_list_m.first(); + TUint idx( 1 ); + + while ( entry_iter ) + { + const core_mac_address_s bssid = entry_iter->ap_data->bssid(); + + DEBUG8( "core_operation_roam_c::next_state() - entry #%02u %02X:%02X:%02X:%02X:%02X:%02X (RCPI %u)", + idx, bssid.addr[0], bssid.addr[1], bssid.addr[2], + bssid.addr[3], bssid.addr[4], bssid.addr[5], entry_iter->ap_data->rcpi() ); + + entry_iter = roaming_list_m.next(); + ++idx; + } + + DEBUG( "core_operation_roam_c::next_state() - roaming list end" ); +#endif // _DEBUG + + if ( roaming_list_m.first() ) + { + current_ap_m = roaming_list_m.first()->ap_data; + } + + /** + * We can proceed with the connection if there are entries in + * the roaming list OR we are going to start our own IBSS network. + */ + if( current_ap_m || + ( !is_connected_m && + server_m->get_connection_data()->iap_data().operating_mode() == core_operating_mode_ibss ) ) + { + operation_state_m = core_state_req_disable_userdata_before_connect; + + if ( !is_connected_m ) + { + return next_state(); + } + + DEBUG( "core_operation_roam_c::next_state() - disabling user data before connect" ); + + server_m->get_core_settings().roam_metrics().set_roam_ts_userdata_disabled(); + + drivers_m->disable_user_data( + request_id_m ); + } + else + { + DEBUG( "core_operation_roam_c::next_state() - no suitable AP found" ); + failure_reason_m = core_error_not_found; + if ( server_m->get_connection_data()->connect_status() == core_connect_undefined ) + { + server_m->get_connection_data()->set_connect_status( connect_status_m ); + } + + server_m->get_core_settings().roam_metrics().inc_roam_attempt_failed_count( + core_roam_failed_reason_no_suitable_ap ); + if ( server_m->get_connection_data()->last_roam_failed_reason() == core_roam_failed_reason_none ) + { + server_m->get_connection_data()->set_last_roam_failed_reason( core_roam_failed_reason_no_suitable_ap ); + } + + /** + * Set all traffic stream statuses and parameters back to the values + * they were before the roam attempt. + */ + core_traffic_stream_list_c& ts_list( + server_m->get_connection_data()->traffic_stream_list() ); + + for( core_traffic_stream_c* iter = ts_list.first(); iter; iter = ts_list.next() ) + { + iter->set_status( core_traffic_stream_status_active ); + iter->reset_to_previous_values(); + } + + return cancel(); + } + + break; + } + case core_state_req_disable_userdata_before_connect: + { + operation_state_m = core_state_req_connect; + +#ifdef _DEBUG + if( current_ap_m ) + { + const core_mac_address_s current_bssid = + current_ap_m->bssid(); + + DEBUG6( "core_operation_roam_c::next_state() - selecting BSSID %02X:%02X:%02X:%02X:%02X:%02X", + current_bssid.addr[0], current_bssid.addr[1], current_bssid.addr[2], + current_bssid.addr[3], current_bssid.addr[4], current_bssid.addr[5] ); + } + else + { + DEBUG("core_operation_roam_c::next_state() - establishing adhoc network"); + } +#endif // _DEBUG + + core_security_mode_e mode( + server_m->get_connection_data()->iap_data().security_mode() ); + bool_t is_ibss_mode( + server_m->get_connection_data()->iap_data().operating_mode() == core_operating_mode_ibss ); + + /** + * Establish Adhoc network + */ + if( !current_ap_m && + is_ibss_mode ) + { + core_operation_base_c* operation = new core_sub_operation_adhoc_c( + request_id_m, + server_m, + drivers_m, + adaptation_m, + ¤t_ap_m ); // conveying a pointer to pointer + + return run_sub_operation( operation ); + } + + /** + * IAP with no security. + */ + if ( mode == core_security_mode_allow_unsecure ) + { + core_operation_base_c* operation = new core_sub_operation_connect_c( + request_id_m, + server_m, + drivers_m, + adaptation_m, + is_connected_m, + management_status_m, + ssid_m, + *current_ap_m, + core_authentication_mode_open, + core_encryption_mode_disabled, + core_cipher_key_type_none, + assoc_ie_list_m, + is_ibss_mode ? NULL : &assoc_resp_m, + true_t, // ignored, pairwise key not used + true_t ); // ignored, group key not used + + return run_sub_operation( operation ); + } + + /** + * IAP with static WEP. + */ + if ( mode == core_security_mode_wep ) + { + core_operation_base_c* operation = new core_sub_operation_wep_connect_c( + request_id_m, + server_m, + drivers_m, + adaptation_m, + is_connected_m, + management_status_m, + ssid_m, + *current_ap_m, + server_m->get_connection_data()->iap_data().authentication_mode(), + core_encryption_mode_wep, + assoc_ie_list_m, + is_ibss_mode ? NULL : &assoc_resp_m ); + + return run_sub_operation( operation ); + } + + /** + * IAP with EAP. + */ + is_cached_sa_used_m = roaming_list_m.first()->is_cached_sa_available; + + core_operation_base_c* operation = new core_sub_operation_wpa_connect_c( + request_id_m, + server_m, + drivers_m, + adaptation_m, + is_connected_m, + management_status_m, + ssid_m, + *current_ap_m, + is_cached_sa_used_m, + assoc_ie_list_m, + is_ibss_mode ? NULL : &assoc_resp_m ); + + return run_sub_operation( operation ); + } + case core_state_req_connect: + { + operation_state_m = core_state_req_update_ts; + + DEBUG( "core_operation_roam_c::next_state() - connection success" ); + + /** + * Send information about the current AP to the subscribed clients. + * + * This has to be done before updating traffic stream statuses so + * that NIF won't try to recreate rejected streams. + */ + core_ap_information_s info = core_tools_parser_c::get_ap_info( + server_m->get_connection_data()->iap_data(), + *current_ap_m ); + DEBUG( "core_operation_roam_c::next_state() - notifying access point information:" ); + DEBUG1( "core_operation_roam_c::next_state() - is_ac_required_for_voice = %u", + info.is_ac_required_for_voice ); + DEBUG1( "core_operation_roam_c::next_state() - is_ac_required_for_video = %u", + info.is_ac_required_for_video ); + DEBUG1( "core_operation_roam_c::next_state() - is_ac_required_for_best_effort = %u", + info.is_ac_required_for_best_effort ); + DEBUG1( "core_operation_roam_c::next_state() - is_ac_required_for_background = %u", + info.is_ac_required_for_background ); + DEBUG1( "core_operation_roam_c::next_state() - is_wpx = %u", + info.is_wpx ); + adaptation_m->notify( + core_notification_ap_info_changed, + sizeof ( info ), + reinterpret_cast( &info ) ); + + if ( server_m->get_connection_data()->current_ap_data() && + server_m->get_connection_data()->current_ap_data()->bssid() != current_ap_m->bssid() ) + { + server_m->get_connection_data()->set_previous_ap_data( + *server_m->get_connection_data()->current_ap_data() ); + + /** + * EAPOL must notified about the disassociation. + */ + if ( server_m->get_connection_data()->iap_data().is_eap_used() || + server_m->get_connection_data()->iap_data().is_wapi_used() ) + { + core_mac_address_s bssid = + server_m->get_connection_data()->previous_ap_data()->bssid(); + + network_id_c network( + &bssid.addr[0], + MAC_ADDR_LEN, + &server_m->own_mac_addr().addr[0], + MAC_ADDR_LEN, + server_m->get_eapol_instance().ethernet_type() ); + + DEBUG6( "core_operation_roam_c::next_state() - EAPOL disassociation from BSSID %02X:%02X:%02X:%02X:%02X:%02X", + bssid.addr[0], bssid.addr[1], bssid.addr[2], + bssid.addr[3], bssid.addr[4], bssid.addr[5] ); + + server_m->get_eapol_instance().disassociation( &network ); + } + } + + server_m->get_connection_data()->set_current_ap_data( + *current_ap_m ); + + core_operation_base_c* operation = new core_sub_operation_roam_update_ts_c( + request_id_m, + server_m, + drivers_m, + adaptation_m, + *current_ap_m ); + + return run_sub_operation( operation ); + } + case core_state_req_update_ts: + { + operation_state_m = core_state_req_enable_userdata; + + DEBUG( "core_operation_roam_c::next_state() - allowing user data" ); + + drivers_m->enable_user_data( + request_id_m ); + + break; + } + case core_state_req_enable_userdata: + { + DEBUG( "core_operation_roam_c::next_state() - connection done" ); + + server_m->get_connection_data()->set_last_rcp_class( core_rcp_normal ); + server_m->get_connection_data()->set_ssid( ssid_m ); + server_m->get_core_settings().roam_metrics().set_roam_ts_userdata_enabled(); + server_m->get_core_settings().roam_metrics().inc_roam_success_count(); + + server_m->get_connection_data()->clear_ap_association_failure_count( + current_ap_m->bssid() ); + + server_m->get_connection_data()->clear_all_authentication_failure_counts(); + + server_m->get_connection_data()->remove_temporary_blacklist_entries( + core_ap_blacklist_reason_eapol_failure | + core_ap_blacklist_reason_max_association_failure_count ); + + if ( current_ap_m->bssid() != server_m->get_connection_data()->last_bssid() ) + { + DEBUG( "core_operation_roam_c::next_state() - BSSID changed, notifying change" ); + + const core_mac_address_s bssid = current_ap_m->bssid(); + + adaptation_m->notify( + core_notification_bssid_changed, + sizeof ( bssid ), + &bssid.addr[0] ); + + server_m->get_connection_data()->set_last_bssid( + bssid ); + } + + if ( !is_connected_m ) + { + core_connection_state_e state = server_m->get_connection_data()->connection_state(); + server_m->get_core_settings().set_connection_state( state ); + + if ( server_m->get_connection_data()->last_connection_state() != state ) + { + u8_t buf[5]; + buf[0] = static_cast( state ); + + adaptation_m->notify( + core_notification_connection_state_changed, + 1, + buf ); + + server_m->get_connection_data()->set_last_connection_state( + state ); + } + } + + if ( current_ap_m->is_wpx() ) + { + server_m->get_wpx_adaptation_instance().handle_wpx_roam_success( + *current_ap_m ); + } + + if( current_ap_m->rrm_capabilities() & RRM_CAPABILITY_BIT_MASK_NEIGHBOR_REPORT ) + { + handle_neighbor_request(); + } + + /** + * If roaming to another AP, schedule a power save update. + * + * This is not done on first connect because we do not want to disturb + * DHCP negotiation. + */ + if ( server_m->get_connection_data()->iap_data().operating_mode() == core_operating_mode_infrastructure && + server_m->get_connection_data()->previous_ap_data() ) + { + DEBUG( "core_operation_roam_c::next_state() - scheduling a power save update" ); + + core_operation_base_c* operation = new core_operation_update_power_mode_c( + REQUEST_ID_CORE_INTERNAL, + server_m, + drivers_m, + adaptation_m ); + + server_m->queue_int_operation( operation ); + } + + return core_error_ok; + } + case core_state_fail_connect: + { + DEBUG( "core_operation_roam_c::next_state() - connection failed" ); + + DEBUG1( "core_operation_roam_c::next_state() - failure_reason_m is %u", + failure_reason_m ); + DEBUG1( "core_operation_roam_c::next_state() - management_status_m is %u", + management_status_m ); + DEBUG1( "core_operation_roam_c::next_state() - is_cached_sa_used_m is %u", + is_cached_sa_used_m ); + + core_connect_status_e status = connect_status( + failure_reason_m, + management_status_m ); + + DEBUG1( "core_operation_roam_c::next_state() - connect_status is %u", + status ); + + server_m->get_connection_data()->set_connect_status( status ); + + const core_mac_address_s bssid = + current_ap_m->bssid(); + + DEBUG( "core_operation_roam_c::next_state() - removing current AP entries from scan list" ); + server_m->get_scan_list().remove_entries_by_bssid( + bssid ); + + update_roam_failure_count( + failure_reason_m, + management_status_m ); + + core_ap_blacklist_reason_e reason = + is_fatal_failure( + failure_reason_m, + management_status_m, + is_cached_sa_used_m ); + if( reason != core_ap_blacklist_reason_none ) + { + if ( reason == core_ap_blacklist_reason_eapol_failure ) + { + u8_t count_af( server_m->get_connection_data()->ap_authentication_failure_count( bssid ) ); + + u32_t max_ap_authentication_failure_count( + server_m->get_device_settings().max_ap_authentication_failure_count ); + + DEBUG1( "core_operation_roam_c::next_state() - this AP has failed %u time(s) in authentication earlier", + count_af ); + + if ( count_af >= max_ap_authentication_failure_count ) + { + DEBUG1( "core_operation_roam_c::next_state() - authentication failure count (%u), blacklisting the AP", count_af ); + server_m->get_connection_data()->add_mac_to_temporary_blacklist( + bssid, reason ); + } + else + { + DEBUG( "core_operation_roam_c::next_state() - increasing authentication failure count" ); + server_m->get_connection_data()->increase_ap_authentication_failure_count( bssid ); + } + } + else + { + DEBUG( "core_operation_roam_c::next_state() - fatal failure, blacklisting the AP"); + server_m->get_connection_data()->add_mac_to_temporary_blacklist( + bssid, reason ); + } + } + else + { + u8_t count( server_m->get_connection_data()->ap_association_failure_count( bssid ) ); + DEBUG1( "core_operation_roam_c::next_state() - this AP has failed %u time(s) earlier", + count ); + + u32_t max_ap_association_failure_count( + server_m->get_device_settings().max_ap_association_failure_count ); + if( server_m->get_connection_data()->iap_data().is_wapi_used() ) + { + max_ap_association_failure_count = CORE_MAX_WAPI_AP_ASSOCIATION_FAILURE_COUNT; + } + + if( count >= max_ap_association_failure_count - 1 ) + { + DEBUG1( "core_operation_roam_c::next_state() - failure count (%u) exceeded, blacklisting the AP", + max_ap_association_failure_count ); + server_m->get_connection_data()->add_mac_to_temporary_blacklist( + bssid, core_ap_blacklist_reason_max_association_failure_count ); + } + else + { + DEBUG( "core_operation_roam_c::next_state() - increasing AP failure count" ); + server_m->get_connection_data()->increase_ap_association_failure_count( bssid ); + } + } + + DEBUG1( "core_operation_roam_c::next_state() - setting tx power (%u)", + server_m->get_device_settings().tx_power_level ); + + operation_state_m = core_state_fail_set_tx_power; + + drivers_m->set_tx_power_level( + request_id_m, + server_m->get_device_settings().tx_power_level ); + + break; + } + case core_state_fail_set_tx_power: + { + u32_t tx_level = server_m->get_device_settings().tx_power_level; + if ( server_m->get_connection_data()->last_tx_level() != tx_level ) + { + DEBUG( "core_operation_roam_c::next_state() - TX level has changed, notifying change" ); + + adaptation_m->notify( + core_notification_tx_power_level_changed, + sizeof( tx_level ), + reinterpret_cast(&tx_level) ); + + server_m->get_connection_data()->set_last_tx_level( tx_level ); + } + + return failure_reason_m; + } + default: + { + ASSERT( false_t ); + } + } + + return core_error_request_pending; + } + +// --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- +// +core_error_e core_operation_roam_c::cancel() + { + DEBUG( "core_operation_roam_c::cancel() " ); + + switch ( operation_state_m ) + { + case core_state_req_connect: + { + operation_state_m = core_state_fail_connect; + + return next_state(); + } + default: + { + return failure_reason_m; + } + } + } + +// --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- +// +bool_t core_operation_roam_c::is_security_association_available( + const core_ap_data_c& ap_data ) + { + DEBUG( "core_operation_roam_c::is_security_association_available() " ); + + core_type_list_c network_id_list; + + /*const*/ core_mac_address_s bssid( ap_data.bssid() ); + network_id_c * network_id = new network_id_c( + &bssid.addr[0], + MAC_ADDR_LEN, + &server_m->own_mac_addr().addr[0], + MAC_ADDR_LEN, + server_m->get_eapol_instance().ethernet_type() ); + if ( !network_id ) + { + DEBUG( "core_operation_roam_c::is_security_association_available() - unable to create network_id_c" ); + return false_t; + } + + network_id_list.append( network_id ); + + wlan_eapol_if_eapol_key_authentication_type_e auth_type = core_tools_c::eap_authentication_type( + server_m->get_connection_data()->iap_data(), + ap_data ); + + core_error_e ret = server_m->get_eapol_instance().check_pmksa_cache( + network_id_list, + auth_type, + core_tools_c::eapol_cipher( ap_data.best_pairwise_cipher() ), + core_tools_c::eapol_cipher( ap_data.best_group_cipher() ) ); + if ( ret != core_error_ok ) + { + DEBUG1( "core_operation_roam_c::is_security_association_available() - check_pmksa_cache returned %i", ret ); + if ( ret == core_error_request_pending ) + { + DEBUG( "core_operation_roam_c::is_security_association_available() - check_pmksa_cache returns core_error_request_pending! Implement complete_check_pmksa_cache for asynchronous operation! " ); + } + + return false_t; + } + + // Ok, because ret == core_error_ok, we know that complete_check_pmksa_cache is already called from EAPOL. + // If ret == core_error_request_pending, we should wait until complete_check_pmksa_cache. + core_type_list_c< core_mac_address_s > & pmksa_list = server_m->get_eapol_instance().get_completed_check_pmksa_cache_list(); + + DEBUG1( "core_operation_roam_c::is_security_association_available() - pmksa_list.count() = %i", pmksa_list.count() ); + + if ( !pmksa_list.count() ) + { + pmksa_list.clear(); + return false_t; + } + + core_mac_address_s* list_obj = pmksa_list.first(); + if ( !list_obj ) + { + pmksa_list.clear(); + return false_t; + } + + if ( *list_obj != bssid ) + { + pmksa_list.clear(); + return false_t; + } + + pmksa_list.clear(); + + return true_t; + } + +// --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- +// +core_connect_status_e core_operation_roam_c::connect_status( + core_error_e request_status, + core_management_status_e management_status ) + { + DEBUG( "core_operation_roam_c::connect_status()" ); + + core_iap_data_c& iap_data( server_m->get_connection_data()->iap_data() ); + + if ( management_status == core_management_status_assoc_denied_full_ap ) + { + DEBUG( "core_operation_roam_c::connect_status() - AP full" ); + return core_connect_ap_full; + } + + if ( request_status == core_error_eapol_auth_start_timeout ) + { + DEBUG( "core_operation_roam_c::connect_status() - EAPOL authentication timeout before authentication was started" ); + return core_connect_eapol_auth_start_timeout; + } + + switch( iap_data.security_mode() ) + { + case core_security_mode_wep: + { + if ( management_status == core_management_status_auth_algo_not_supported ) + { + if ( iap_data.authentication_mode() == core_authentication_mode_open ) + { + DEBUG( "core_operation_roam_c::connect_status() - open authentication unsupported (WEP)" ); + return core_connect_wep_open_authentication_unsupported; + } + else + { + DEBUG( "core_operation_roam_c::connect_status() - shared authentication unsupported (WEP)" ); + return core_connect_wep_shared_authentication_unsupported; + } + } + else if ( iap_data.authentication_mode() == core_authentication_mode_shared && + ( management_status == core_management_status_auth_challenge_failure || + management_status == core_management_status_auth_frame_out_of_sequence || + request_status == core_error_timeout ) ) + { + DEBUG( "core_operation_roam_c::connect_status() - shared authentication failed (WEP)" ); + return core_connect_wep_shared_authentication_failed; + } + else if ( management_status == core_management_status_unsupported_capabilities && + !current_ap_m->is_privacy_enabled() ) + { + DEBUG( "core_operation_roam_c::connect_status() - association using privacy failed (WEP)" ); + return core_connect_iap_wep_but_ap_has_no_privacy; + } + + break; + } + case core_security_mode_allow_unsecure: + { + break; + } + case core_security_mode_802dot1x: + { + if ( request_status == core_error_eapol_total_failure || + request_status == core_error_eapol_failure ) + { + DEBUG( "core_operation_roam_c::connect_status() - 802.1x EAP failure" ); + return eap_connect_status( + iap_data.security_mode(), + server_m->get_connection_data()->last_failed_eap_type(), + server_m->get_connection_data()->last_eap_error() ); + } + else if ( management_status == core_management_status_auth_algo_not_supported ) + { + DEBUG( "core_operation_roam_c::connect_status() - authentication algorithm not supported (802.1x)" ); + return core_connect_802_1x_authentication_algorithm_not_supported; + } + + break; + } + case core_security_mode_wpa: + /** Falls through on purpose. */ + case core_security_mode_wpa2only: + { + if ( request_status == core_error_eapol_total_failure || + request_status == core_error_eapol_failure ) + { + if ( iap_data.is_psk_used() ) + { + DEBUG( "core_operation_roam_c::connect_status() - WPA-PSK failure" ); + return core_connect_wpa_psk_failure; + } + else + { + DEBUG( "core_operation_roam_c::connect_status() - WPA EAP failure" ); + return eap_connect_status( + iap_data.security_mode(), + server_m->get_connection_data()->last_failed_eap_type(), + server_m->get_connection_data()->last_eap_error() ); + } + } + + break; + } + case core_security_mode_wapi: + { + if ( request_status == core_error_eapol_total_failure || + request_status == core_error_eapol_failure ) + { + if ( iap_data.is_psk_used() ) + { + DEBUG( "core_operation_roam_c::connect_status() - WAPI-PSK failure" ); + return core_connect_wapi_psk_failure; + } + else + { + DEBUG( "core_operation_roam_c::connect_status() - WAPI certificate failure" ); + return core_connect_wapi_certificate_failure; + } + } + + break; + } + + default: + { + ASSERT( false_t ); + } + } + + return core_connect_undefined; + } + +// --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- +// +core_ap_blacklist_reason_e core_operation_roam_c::is_fatal_failure( + core_error_e request_status, + core_management_status_e management_status, + bool_t is_cached_sa_used ) const + { + DEBUG( "core_operation_roam_c::is_fatal_failure()" ); + + /** + * EAPOL failures in 802.1x/WPA mode are all fatal but blacklisting + * is only done with core_error_eapol_failure because others cause + * the connection to be shutdown immediately. + */ + if( request_status == core_error_eapol_failure && + server_m->get_connection_data()->iap_data().is_eap_used() ) + { + DEBUG( "core_operation_roam_c::is_fatal_failure() - fatal, EAP failure" ); + return core_ap_blacklist_reason_eapol_failure; + } + + /** + * Don't consider a failed (re-)association attempt as fatal since it's + * possible AP just doesn't have our authentication cached any more. + */ + if ( is_cached_sa_used ) + { + DEBUG( "core_operation_roam_c::is_fatal_failure() - not fatal, a reassociation attempt" ); + return core_ap_blacklist_reason_none; + } + + /** + * Non-fatal WPX management status codes should not cause blacklisting. + */ + if ( server_m->get_wpx_adaptation_instance().is_wpx_management_status( management_status ) && + !server_m->get_wpx_adaptation_instance().is_fatal_wpx_management_status( management_status ) ) + { + DEBUG( "core_operation_roam_c::is_fatal_failure() - not fatal, WPX-specific management status" ); + return core_ap_blacklist_reason_none; + } + + if ( management_status != core_management_status_success && + management_status != core_management_status_assoc_denied_full_ap ) + { + DEBUG1( "core_operation_roam_c::is_fatal_failure() - fatal, management status %u", + management_status ); + return core_ap_blacklist_reason_association_status; + } + + DEBUG( "core_operation_roam_c::is_fatal_failure() - not fatal" ); + return core_ap_blacklist_reason_none; + } + +// --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- +// +core_connect_status_e core_operation_roam_c::eap_connect_status( + core_security_mode_e security_mode, + u32_t eap_type, + u32_t eap_error ) const + { + DEBUG( "core_operation_roam_c::eap_connect_status()" ); + DEBUG1( "core_operation_roam_c::eap_connect_status() - eap_type %u", + eap_type ); + DEBUG1( "core_operation_roam_c::eap_connect_status() - eap_error %u", + eap_error ); + + core_connect_status_e status( core_connect_wpa_eap_failure ); + if ( security_mode == core_security_mode_802dot1x ) + { + status = core_connect_802_1x_failure; + } + + /** + * Handle EAP type specific errors. + */ + switch ( eap_type ) + { + case wlan_eapol_if_eap_type_tls: + { + switch ( eap_error ) + { + case wlan_eapol_if_eap_status_certificate_expired: + case wlan_eapol_if_eap_status_certificate_revoked: + status = core_connect_eap_tls_server_certificate_expired; + break; + case wlan_eapol_if_eap_status_unsupported_certificate: + status = core_connect_eap_tls_server_certificate_unknown; + break; + case wlan_eapol_if_eap_status_user_certificate_unknown: + status = core_connect_eap_tls_user_certificate_unknown; + break; + case wlan_eapol_if_eap_status_illegal_cipher_suite: + status = core_connect_eap_tls_illegal_cipher_suite; + break; + case wlan_eapol_if_eap_status_bad_certificate: + status = core_connect_eap_tls_user_rejected; + break; + default: + status = core_connect_eap_tls_failure; + } + break; + } + case wlan_eapol_if_eap_type_leap: + { + status = core_connect_eap_leap_failure; + break; + } + case wlan_eapol_if_eap_type_gsmsim: + { + switch ( eap_error ) + { + case wlan_eapol_if_eap_status_identity_query_failed: + status = core_connect_eap_sim_identity_query_failed; + break; + case wlan_eapol_if_eap_status_user_has_not_subscribed_to_the_requested_service: + status = core_connect_eap_sim_user_has_not_subscribed_to_the_requested_service; + break; + case wlan_eapol_if_eap_status_users_calls_are_barred: + status = core_connect_eap_sim_users_calls_are_barred; + break; + default: + status = core_connect_eap_sim_failure; + } + break; + } + case wlan_eapol_if_eap_type_ttls: + { + switch ( eap_error ) + { + case wlan_eapol_if_eap_status_certificate_expired: + case wlan_eapol_if_eap_status_certificate_revoked: + status = core_connect_eap_ttls_server_certificate_expired; + break; + case wlan_eapol_if_eap_status_unsupported_certificate: + status = core_connect_eap_ttls_server_certificate_unknown; + break; + case wlan_eapol_if_eap_status_user_certificate_unknown: + status = core_connect_eap_ttls_user_certificate_unknown; + break; + case wlan_eapol_if_eap_status_illegal_cipher_suite: + status = core_connect_eap_ttls_illegal_cipher_suite; + break; + case wlan_eapol_if_eap_status_bad_certificate: + status = core_connect_eap_ttls_user_rejected; + break; + default: + status = core_connect_eap_ttls_failure; + } + break; + } + case wlan_eapol_if_eap_type_aka: + { + switch ( eap_error ) + { + case wlan_eapol_if_eap_status_identity_query_failed: + status = core_connect_eap_sim_identity_query_failed; + break; + case wlan_eapol_if_eap_status_user_has_not_subscribed_to_the_requested_service: + status = core_connect_eap_sim_user_has_not_subscribed_to_the_requested_service; + break; + case wlan_eapol_if_eap_status_users_calls_are_barred: + status = core_connect_eap_sim_users_calls_are_barred; + break; + default: + status = core_connect_eap_aka_failure; + } + break; + } + case wlan_eapol_if_eap_type_peap: + { + switch ( eap_error ) + { + case wlan_eapol_if_eap_status_certificate_expired: + case wlan_eapol_if_eap_status_certificate_revoked: + status = core_connect_eap_peap_server_certificate_expired; + break; + case wlan_eapol_if_eap_status_unsupported_certificate: + status = core_connect_eap_peap_server_certificate_unknown; + break; + case wlan_eapol_if_eap_status_user_certificate_unknown: + status = core_connect_eap_peap_user_certificate_unknown; + break; + case wlan_eapol_if_eap_status_illegal_cipher_suite: + status = core_connect_eap_peap_illegal_cipher_suite; + break; + case wlan_eapol_if_eap_status_bad_certificate: + status = core_connect_eap_peap_user_rejected; + break; + default: + status = core_connect_eap_peap_failure; + } + break; + } + case wlan_eapol_if_eap_type_mschapv2: + { + switch ( eap_error ) + { + case wlan_eapol_if_eap_status_password_expired: + status = core_connect_eap_mschapv2_password_expired; + break; + case wlan_eapol_if_eap_status_no_dialin_permission: + status = core_connect_eap_mschapv2_no_dialin_permission; + break; + case wlan_eapol_if_eap_status_account_disabled: + status = core_connect_eap_mschapv2_account_disabled; + break; + case wlan_eapol_if_eap_status_restricted_logon_hours: + status = core_connect_eap_mschapv2_restricted_logon_hours; + break; + default: + status = core_connect_eap_mschapv2_failure; + } + break; + } + case wlan_eapol_if_eap_type_fast: + { + switch ( eap_error ) + { + case wlan_eapol_if_eap_status_tunnel_compromise_error: + status = core_connect_eap_fast_tunnel_compromise_error; + break; + case wlan_eapol_if_eap_status_unexpected_tlv_exhanged: + status = core_connect_eap_fast_unexpected_tlv_exhanged; + break; + case wlan_eapol_if_eap_status_no_pac_nor_certs_to_authenticate_with_provision_disabled: + status = core_connect_eap_fast_no_pac_nor_certs_to_authenticate_with_provision_disabled; + break; + case wlan_eapol_if_eap_status_no_matching_pac_for_aid: + status = core_connect_eap_fast_no_matching_pac_for_aid; + break; + case wlan_eapol_if_eap_status_pac_store_corrupted: + status = core_connect_eap_fast_pac_store_corrupted; + break; + case wlan_eapol_if_eap_status_authentication_failure: /* flow through on purpose */ + default: + status = core_connect_eap_fast_authentication_failed; + } + break; + } + default: + break; + } + + return status; + } + +// --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- +// +bool_t core_operation_roam_c::process_frame( + core_ap_data_c& ap_data ) + { + connect_status_m = core_tools_parser_c::is_ap_suitable( + server_m->get_wpx_adaptation_instance(), + ap_data, + server_m->get_connection_data()->iap_data(), + server_m->get_core_settings(), + *server_m->get_connection_data(), + min_required_rcpi_m, + min_medium_time_m, + server_m->is_cm_active(), + current_bssid_m ); + + if( connect_status_m == core_connect_ok ) + { + roaming_list_entry_s* entry = new roaming_list_entry_s; + if( !entry ) + { + DEBUG( "core_operation_roam_c::process_frame() - unable to roaming_list_entry_s instance()" ); + + return false_t; + } + + entry->ap_data = NULL; + entry->is_cached_sa_available = false_t; + + entry->ap_data = core_ap_data_c::instance( ap_data ); + if( !entry->ap_data ) + { + DEBUG( "core_operation_roam_c::process_frame() - unable to create core_ap_data_c instance()" ); + + delete entry; + entry = NULL; + + return false_t; + } + + u32_t entry_score( entry->ap_data->rcpi() ); + + /** + * If this is a roaming situation, APs supporting WPX fast-roam or with + * cached PMKSAs are preferred over others. + */ + if( server_m->get_connection_data()->current_ap_data() && + server_m->get_connection_data()->iap_data().is_802dot1x_used() && + entry->ap_data->is_wpx_fast_roam_available() && + is_security_association_available( *server_m->get_connection_data()->current_ap_data() ) ) + { + DEBUG( "core_operation_roam_c::process_frame() - WPX fast-roam supported" ); + + entry->is_cached_sa_available = true_t; + entry_score += CORE_ROAMING_LIST_BONUS_WPX_FAST_ROAM; + } + else if( server_m->get_connection_data()->iap_data().is_eap_used() && + is_security_association_available( *entry->ap_data ) ) + { + DEBUG( "core_operation_roam_c::process_frame() - a cached PMKSA found" ); + + entry->is_cached_sa_available = true_t; + entry_score += CORE_ROAMING_LIST_BONUS_PMKSA; + } + + // These conditions could be added to previous conditions... + if( server_m->get_connection_data()->current_ap_data() ) // previous connection exists + { + if( !server_m->get_connection_data()->current_ap_data()->is_wpx_fast_roam_available() && // previous connection was not WPX + entry->ap_data->is_wpx_fast_roam_available() ) // AND new connection is WPX + { + DEBUG( "core_operation_roam_c::process_frame() - reassociation is not possible, previous connection was not WPX, but new connection is WPX" ); + entry->is_cached_sa_available = false_t; + } + } + else // previous connection does not exists + { + if( entry->ap_data->is_wpx_fast_roam_available() ) // new connection has WPX + { + DEBUG( "core_operation_roam_c::process_frame() - reassociation is not possible, there is no previous connection and new is WPX" ); + entry->is_cached_sa_available = false_t; + } + } + + /** + * If AP advertises QBSS Load IE, check the amount of connected + * stations and adjust the score accordingly. + */ + const core_frame_qbss_load_ie_c* qbss_load_ie = entry->ap_data->qbss_load_ie(); + if( qbss_load_ie ) + { + DEBUG1( "core_ap_data_c::process_frame() - QBSS Load IE detected, %u station(s) associated", + qbss_load_ie->station_count() ); + + delete qbss_load_ie; + } + + roaming_list_m.append( + entry, + entry_score ); + + return true_t; + } + + DEBUG1( "core_operation_roam_c::process_frame() - AP is unsuitable, reason %u", + connect_status_m ); + + return false_t; + } + +// --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- +// +void core_operation_roam_c::update_roam_failure_count( + core_error_e request_status, + core_management_status_e management_status ) + { + core_roam_failed_reason_e failure_reason( core_roam_failed_reason_none ); + + if ( request_status == core_error_eapol_failure || + request_status == core_error_eapol_total_failure ) + { + failure_reason = core_roam_failed_reason_eapol_failure; + } + else if ( request_status == core_error_timeout ) + { + failure_reason = core_roam_failed_reason_timeout; + } + else if ( management_status != core_management_status_success ) + { + failure_reason = core_roam_failed_reason_ap_status_code; + } + else + { + failure_reason = core_roam_failed_reason_other_failure; + } + + server_m->get_core_settings().roam_metrics().inc_roam_attempt_failed_count( + failure_reason ); + if ( server_m->get_connection_data()->last_roam_failed_reason() == core_roam_failed_reason_none || + server_m->get_connection_data()->last_roam_failed_reason() == core_roam_failed_reason_no_suitable_ap ) + { + server_m->get_connection_data()->set_last_roam_failed_reason( failure_reason ); + } + } + +// --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- +// +void core_operation_roam_c::handle_neighbor_request() + { + /* neighbor report request */ + DEBUG( "core_operation_roam_c::handle_neighbor_request() - Neighbor report bit is on" ); + + core_frame_action_c* request = NULL; + + // create IE + core_frame_nr_ie_c* nr_request = core_frame_nr_ie_c::instance( ssid_m ); + if ( nr_request ) + { + // create frame, frame ownership will belong to caller of this method + request = core_frame_action_nr_c::instance( + 0, // Duration + current_ap_m->bssid(), // Destination + server_m->own_mac_addr(), // Source + current_ap_m->bssid(), // BSSID + 0, // Sequence Control + core_frame_radio_measurement_action_c::core_dot11_action_rm_type_neighbor_req, // Action Type + 0, // Dialog Token + nr_request ); + + if ( !request ) + { + DEBUG( "core_operation_roam_c::handle_neighbor_request() - Unable to instantiate core_frame_action_nr_c" ); + } + + delete nr_request; + nr_request = NULL; + } + else + { + DEBUG( "core_operation_roam_c::handle_neighbor_request() - Unable to instantiate core_frame_nr_ie_c" ); + } + + if ( request ) + { + DEBUG1( "core_operation_roam_c::handle_neighbor_request() - sending frame (%u bytes): ", + request->data_length() ); + DEBUG_BUFFER( + request->data_length(), + request->data() ); + + server_m->send_management_frame( + core_frame_type_dot11, + request->data_length(), + request->data(), + current_ap_m->bssid() ); + + delete request; + request = NULL; + } + }