| // Copyright 2018 The Chromium OS Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "shill/wifi/wifi.h" |
| |
| #include <inttypes.h> |
| #include <linux/if.h> // Needs definitions from netinet/ether.h |
| #include <linux/nl80211.h> |
| #include <netinet/ether.h> |
| #include <stdio.h> |
| #include <string.h> |
| |
| #include <algorithm> |
| #include <cstdint> |
| #include <cstdlib> |
| #include <map> |
| #include <memory> |
| #include <set> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include <base/bind.h> |
| #include <base/check.h> |
| #include <base/files/file_util.h> |
| #include <base/logging.h> |
| #include <base/notreached.h> |
| #include <base/numerics/safe_conversions.h> |
| #include <base/strings/string_util.h> |
| #include <base/strings/stringprintf.h> |
| #include <base/time/time.h> |
| #include <chromeos/dbus/service_constants.h> |
| |
| #include "shill/control_interface.h" |
| #include "shill/device.h" |
| #include "shill/eap_credentials.h" |
| #include "shill/error.h" |
| #include "shill/logging.h" |
| #include "shill/manager.h" |
| #include "shill/metrics.h" |
| #include "shill/net/ieee80211.h" |
| #include "shill/net/ip_address.h" |
| #include "shill/net/netlink_manager.h" |
| #include "shill/net/netlink_message.h" |
| #include "shill/net/nl80211_message.h" |
| #include "shill/net/rtnl_handler.h" |
| #include "shill/net/shill_time.h" |
| #include "shill/property_accessor.h" |
| #include "shill/scope_logger.h" |
| #include "shill/supplicant/supplicant_eap_state_handler.h" |
| #include "shill/supplicant/supplicant_interface_proxy_interface.h" |
| #include "shill/supplicant/supplicant_manager.h" |
| #include "shill/supplicant/supplicant_network_proxy_interface.h" |
| #include "shill/supplicant/supplicant_process_proxy_interface.h" |
| #include "shill/supplicant/wpa_supplicant.h" |
| #include "shill/technology.h" |
| #include "shill/wifi/wake_on_wifi.h" |
| #include "shill/wifi/wifi_cqm.h" |
| #include "shill/wifi/wifi_endpoint.h" |
| #include "shill/wifi/wifi_provider.h" |
| #include "shill/wifi/wifi_service.h" |
| |
| namespace shill { |
| |
| namespace Logging { |
| static auto kModuleLogScope = ScopeLogger::kWiFi; |
| static std::string ObjectID(const WiFi* w) { |
| return w->GetRpcIdentifier().value(); |
| } |
| } // namespace Logging |
| |
| // statics |
| const char* const WiFi::kDefaultBgscanMethod = |
| WPASupplicant::kNetworkBgscanMethodSimple; |
| const uint16_t WiFi::kDefaultScanIntervalSeconds = 60; |
| |
| // Scan interval while connected. |
| const uint16_t WiFi::kBackgroundScanIntervalSeconds = 360; |
| // Default background scan interval when there is only one endpoint on the |
| // network. We'd like to strike a balance between 1) not triggering too |
| // frequently with poor signal, 2) hardly triggering at all with good signal, |
| // and 3) being able to discover additional APs that weren't initially visible. |
| const int WiFi::kSingleEndpointBgscanIntervalSeconds = 86400; |
| // Age (in seconds) beyond which a BSS cache entry will not be preserved, |
| // across a suspend/resume. |
| const time_t WiFi::kMaxBSSResumeAgeSeconds = 10; |
| const char WiFi::kInterfaceStateUnknown[] = "shill-unknown"; |
| const int WiFi::kNumFastScanAttempts = 3; |
| const int WiFi::kFastScanIntervalSeconds = 10; |
| const int WiFi::kReconnectTimeoutSeconds = 10; |
| const int WiFi::kRequestStationInfoPeriodSeconds = 20; |
| // 1 second is less than the time it takes to scan and establish a new |
| // connection after waking, but should be enough time for supplicant to update |
| // its state. |
| const int WiFi::kPostWakeConnectivityReportDelayMilliseconds = 1000; |
| const uint32_t WiFi::kDefaultWiphyIndex = UINT32_MAX; |
| const int WiFi::kPostScanFailedDelayMilliseconds = 10000; |
| |
| // The default random MAC mask is FF:FF:FF:00:00:00. Bits which are a 1 in |
| // the mask stay the same during randomization, and bits which are 0 are |
| // randomized. This mask means the OUI will remain unchanged but the last |
| // three octets will be different. |
| const std::vector<unsigned char> WiFi::kRandomMacMask{255, 255, 255, 0, 0, 0}; |
| |
| const char WiFi::kWakeOnWiFiNotSupported[] = "Wake on WiFi not supported"; |
| |
| namespace { |
| const uint16_t kDefaultBgscanShortIntervalSeconds = 64; |
| const uint16_t kSingleEndpointBgscanShortIntervalSeconds = 360; |
| const int32_t kDefaultBgscanSignalThresholdDbm = -72; |
| // Delay between scans when supplicant finds "No suitable network". |
| const time_t kRescanIntervalSeconds = 1; |
| const int kPendingTimeoutSeconds = 15; |
| const int kMaxRetryCreateInterfaceAttempts = 6; |
| const int kRetryCreateInterfaceIntervalSeconds = 10; |
| const int16_t kDefaultDisconnectDbm = 0; |
| const int16_t kDefaultDisconnectThresholdDbm = -75; |
| const int kInvalidMaxSSIDs = -1; |
| |
| // Maximum time between two link monitor failures to declare this link (network) |
| // as unreliable. |
| constexpr auto kLinkUnreliableThreshold = base::TimeDelta::FromMinutes(60); |
| // Mark a unreliable service as reliable if no more link monitor failures in |
| // the below timeout after this unreliable service became connected again. |
| constexpr auto kLinkUnreliableResetTimeout = base::TimeDelta::FromMinutes(5); |
| |
| bool IsPrintableAsciiChar(char c) { |
| return (c >= ' ' && c <= '~'); |
| } |
| } // namespace |
| |
| WiFi::WiFi(Manager* manager, |
| const std::string& link, |
| const std::string& address, |
| int interface_index, |
| std::unique_ptr<WakeOnWiFiInterface> wake_on_wifi) |
| : Device(manager, link, address, interface_index, Technology::kWifi), |
| provider_(manager->wifi_provider()), |
| time_(Time::GetInstance()), |
| supplicant_connect_attempts_(0), |
| supplicant_present_(false), |
| supplicant_state_(kInterfaceStateUnknown), |
| supplicant_bss_(RpcIdentifier("(unknown)")), |
| supplicant_assoc_status_(IEEE_80211::kStatusCodeSuccessful), |
| supplicant_auth_status_(IEEE_80211::kStatusCodeSuccessful), |
| supplicant_disconnect_reason_(IEEE_80211::kReasonCodeInvalid), |
| disconnect_signal_dbm_(kDefaultDisconnectDbm), |
| disconnect_threshold_dbm_(kDefaultDisconnectThresholdDbm), |
| max_ssids_per_scan_(kInvalidMaxSSIDs), |
| supplicant_auth_mode_(WPASupplicant::kAuthModeUnknown), |
| need_bss_flush_(false), |
| resumed_at_((struct timeval){0}), |
| fast_scans_remaining_(kNumFastScanAttempts), |
| has_already_completed_(false), |
| is_roaming_in_progress_(false), |
| pending_eap_failure_(Service::kFailureNone), |
| is_debugging_connection_(false), |
| eap_state_handler_(new SupplicantEAPStateHandler()), |
| ipv4_gateway_found_(false), |
| ipv6_gateway_found_(false), |
| last_link_monitor_failed_time_(0), |
| bgscan_short_interval_seconds_(kDefaultBgscanShortIntervalSeconds), |
| bgscan_signal_threshold_dbm_(kDefaultBgscanSignalThresholdDbm), |
| scan_interval_seconds_(kDefaultScanIntervalSeconds), |
| netlink_manager_(NetlinkManager::GetInstance()), |
| random_mac_supported_(false), |
| random_mac_enabled_(false), |
| sched_scan_supported_(false), |
| scan_state_(kScanIdle), |
| scan_method_(kScanMethodNone), |
| broadcast_probe_was_skipped_(false), |
| receive_byte_count_at_connect_(0), |
| wiphy_index_(kDefaultWiphyIndex), |
| wifi_cqm_(new WiFiCQM(metrics(), this)), |
| wake_on_wifi_(std::move(wake_on_wifi)), |
| weak_ptr_factory_while_started_(this), |
| weak_ptr_factory_(this) { |
| scoped_supplicant_listener_.reset( |
| new SupplicantManager::ScopedSupplicantListener( |
| manager->supplicant_manager(), |
| base::Bind(&WiFi::OnSupplicantPresence, |
| weak_ptr_factory_.GetWeakPtr()))); |
| |
| PropertyStore* store = this->mutable_store(); |
| store->RegisterDerivedString( |
| kBgscanMethodProperty, |
| StringAccessor(new CustomAccessor<WiFi, std::string>( |
| this, &WiFi::GetBgscanMethod, &WiFi::SetBgscanMethod, |
| &WiFi::ClearBgscanMethod))); |
| HelpRegisterDerivedUint16(store, kBgscanShortIntervalProperty, |
| &WiFi::GetBgscanShortInterval, |
| &WiFi::SetBgscanShortInterval); |
| HelpRegisterDerivedInt32(store, kBgscanSignalThresholdProperty, |
| &WiFi::GetBgscanSignalThreshold, |
| &WiFi::SetBgscanSignalThreshold); |
| store->RegisterConstBool(kMacAddressRandomizationSupportedProperty, |
| &random_mac_supported_); |
| HelpRegisterDerivedBool(store, kMacAddressRandomizationEnabledProperty, |
| &WiFi::GetRandomMacEnabled, |
| &WiFi::SetRandomMacEnabled); |
| |
| store->RegisterDerivedKeyValueStore( |
| kLinkStatisticsProperty, |
| KeyValueStoreAccessor(new CustomAccessor<WiFi, KeyValueStore>( |
| this, &WiFi::GetLinkStatistics, nullptr))); |
| |
| // TODO(quiche): Decide if scan_pending_ is close enough to |
| // "currently scanning" that we don't care, or if we want to track |
| // scan pending/currently scanning/no scan scheduled as a tri-state |
| // kind of thing. |
| HelpRegisterConstDerivedBool(store, kScanningProperty, &WiFi::GetScanPending); |
| HelpRegisterConstDerivedUint16s(store, kWifiSupportedFrequenciesProperty, |
| &WiFi::GetAllScanFrequencies); |
| HelpRegisterDerivedUint16(store, kScanIntervalProperty, |
| &WiFi::GetScanInterval, &WiFi::SetScanInterval); |
| HelpRegisterConstDerivedBool(store, kWakeOnWiFiSupportedProperty, |
| &WiFi::GetWakeOnWiFiSupported); |
| if (wake_on_wifi_) { |
| wake_on_wifi_->InitPropertyStore(store); |
| } |
| ScopeLogger::GetInstance()->RegisterScopeEnableChangedCallback( |
| ScopeLogger::kWiFi, base::Bind(&WiFi::OnWiFiDebugScopeChanged, |
| weak_ptr_factory_.GetWeakPtr())); |
| CHECK(netlink_manager_); |
| netlink_handler_ = |
| base::Bind(&WiFi::HandleNetlinkBroadcast, weak_ptr_factory_.GetWeakPtr()); |
| netlink_manager_->AddBroadcastHandler(netlink_handler_); |
| SLOG(this, 2) << "WiFi device " << link_name() << " initialized."; |
| } |
| |
| WiFi::~WiFi() { |
| netlink_manager_->RemoveBroadcastHandler(netlink_handler_); |
| } |
| |
| void WiFi::Start(Error* error, |
| const EnabledStateChangedCallback& /*callback*/) { |
| SLOG(this, 2) << "WiFi " << link_name() << " starting."; |
| if (enabled()) { |
| return; |
| } |
| OnEnabledStateChanged(EnabledStateChangedCallback(), Error()); |
| if (error) { |
| error->Reset(); // indicate immediate completion |
| } |
| |
| // Subscribe to multicast events. |
| netlink_manager_->SubscribeToEvents(Nl80211Message::kMessageTypeString, |
| NetlinkManager::kEventTypeConfig); |
| netlink_manager_->SubscribeToEvents(Nl80211Message::kMessageTypeString, |
| NetlinkManager::kEventTypeScan); |
| netlink_manager_->SubscribeToEvents(Nl80211Message::kMessageTypeString, |
| NetlinkManager::kEventTypeRegulatory); |
| netlink_manager_->SubscribeToEvents(Nl80211Message::kMessageTypeString, |
| NetlinkManager::kEventTypeMlme); |
| GetPhyInfo(); |
| // Connect to WPA supplicant if it's already present. If not, we'll connect to |
| // it when it appears. |
| supplicant_connect_attempts_ = 0; |
| ConnectToSupplicant(); |
| if (wake_on_wifi_) { |
| wake_on_wifi_->Start(); |
| } |
| } |
| |
| void WiFi::Stop(Error* error, const EnabledStateChangedCallback& /*callback*/) { |
| SLOG(this, 2) << "WiFi " << link_name() << " stopping."; |
| // Unlike other devices, we leave the DBus name watcher in place here, because |
| // WiFi callbacks expect notifications even if the device is disabled. |
| DropConnection(); |
| StopScanTimer(); |
| for (const auto& endpoint : endpoint_by_rpcid_) { |
| provider_->OnEndpointRemoved(endpoint.second); |
| } |
| endpoint_by_rpcid_.clear(); |
| for (const auto& map_entry : rpcid_by_service_) { |
| RemoveNetwork(map_entry.second); |
| } |
| rpcid_by_service_.clear(); |
| // Remove interface from supplicant. |
| if (supplicant_present_ && supplicant_interface_proxy_) { |
| supplicant_process_proxy()->RemoveInterface(supplicant_interface_path_); |
| } |
| pending_scan_results_.reset(); |
| current_service_ = nullptr; // breaks a reference cycle |
| pending_service_ = nullptr; // breaks a reference cycle |
| is_debugging_connection_ = false; |
| SetScanState(kScanIdle, kScanMethodNone, __func__); |
| StopPendingTimer(); |
| StopReconnectTimer(); |
| StopRequestingStationInfo(); |
| |
| OnEnabledStateChanged(EnabledStateChangedCallback(), Error()); |
| if (error) |
| error->Reset(); // indicate immediate completion |
| weak_ptr_factory_while_started_.InvalidateWeakPtrs(); |
| |
| SLOG(this, 3) << "WiFi " << link_name() << " supplicant_interface_proxy_ " |
| << (supplicant_interface_proxy_.get() ? "is set." |
| : "is not set."); |
| SLOG(this, 3) << "WiFi " << link_name() << " pending_service_ " |
| << (pending_service_.get() ? "is set." : "is not set."); |
| SLOG(this, 3) << "WiFi " << link_name() << " has " |
| << endpoint_by_rpcid_.size() << " EndpointMap entries."; |
| } |
| |
| void WiFi::Scan(Error* /*error*/, const std::string& reason) { |
| if ((scan_state_ != kScanIdle) || |
| (current_service_.get() && current_service_->IsConnecting())) { |
| SLOG(this, 2) << "Ignoring scan request while scanning or connecting."; |
| return; |
| } |
| SLOG(this, 1) << __func__ << " on " << link_name() << " from " << reason; |
| // Needs to send a D-Bus message, but may be called from D-Bus |
| // signal handler context (via Manager::RequestScan). So defer work |
| // to event loop. |
| dispatcher()->PostTask( |
| FROM_HERE, base::Bind(&WiFi::ScanTask, |
| weak_ptr_factory_while_started_.GetWeakPtr())); |
| } |
| |
| int16_t WiFi::GetSignalLevelForActiveService() { |
| return current_service_ ? current_service_->SignalLevel() |
| : WiFiService::SignalLevelMin; |
| } |
| |
| void WiFi::AddPendingScanResult(const RpcIdentifier& path, |
| const KeyValueStore& properties, |
| bool is_removal) { |
| // BSS events might come immediately after Stop(). Don't bother stashing them |
| // at all. |
| if (!enabled()) { |
| return; |
| } |
| |
| if (!pending_scan_results_) { |
| pending_scan_results_.reset(new PendingScanResults( |
| base::Bind(&WiFi::PendingScanResultsHandler, |
| weak_ptr_factory_while_started_.GetWeakPtr()))); |
| dispatcher()->PostTask(FROM_HERE, |
| pending_scan_results_->callback.callback()); |
| } |
| pending_scan_results_->results.emplace_back(path, properties, is_removal); |
| } |
| |
| void WiFi::BSSAdded(const RpcIdentifier& path, |
| const KeyValueStore& properties) { |
| // Called from a D-Bus signal handler, and may need to send a D-Bus |
| // message. So defer work to event loop. |
| AddPendingScanResult(path, properties, false); |
| } |
| |
| void WiFi::BSSRemoved(const RpcIdentifier& path) { |
| // Called from a D-Bus signal handler, and may need to send a D-Bus |
| // message. So defer work to event loop. |
| AddPendingScanResult(path, {}, true); |
| } |
| |
| void WiFi::Certification(const KeyValueStore& properties) { |
| dispatcher()->PostTask( |
| FROM_HERE, |
| base::Bind(&WiFi::CertificationTask, |
| weak_ptr_factory_while_started_.GetWeakPtr(), properties)); |
| } |
| |
| void WiFi::EAPEvent(const std::string& status, const std::string& parameter) { |
| dispatcher()->PostTask( |
| FROM_HERE, base::Bind(&WiFi::EAPEventTask, |
| weak_ptr_factory_while_started_.GetWeakPtr(), |
| status, parameter)); |
| } |
| |
| void WiFi::PropertiesChanged(const KeyValueStore& properties) { |
| SLOG(this, 2) << __func__; |
| // Called from D-Bus signal handler, but may need to send a D-Bus |
| // message. So defer work to event loop. |
| dispatcher()->PostTask( |
| FROM_HERE, base::Bind(&WiFi::PropertiesChangedTask, |
| weak_ptr_factory_.GetWeakPtr(), properties)); |
| } |
| |
| void WiFi::ScanDone(const bool& success) { |
| // This log line should be kept at INFO level to support the Shill log |
| // processor. |
| LOG(INFO) << __func__; |
| |
| if (!enabled()) { |
| SLOG(this, 2) << "Ignoring scan completion while disabled"; |
| return; |
| } |
| |
| // Defer handling of scan result processing, because that processing |
| // may require the the registration of new D-Bus objects. And such |
| // registration can't be done in the context of a D-Bus signal |
| // handler. |
| if (pending_scan_results_) { |
| pending_scan_results_->is_complete = true; |
| return; |
| } |
| if (success) { |
| scan_failed_callback_.Cancel(); |
| dispatcher()->PostTask( |
| FROM_HERE, base::Bind(&WiFi::ScanDoneTask, |
| weak_ptr_factory_while_started_.GetWeakPtr())); |
| } else { |
| scan_failed_callback_.Reset(base::Bind( |
| &WiFi::ScanFailedTask, weak_ptr_factory_while_started_.GetWeakPtr())); |
| dispatcher()->PostDelayedTask(FROM_HERE, scan_failed_callback_.callback(), |
| kPostScanFailedDelayMilliseconds); |
| } |
| } |
| |
| void WiFi::ConnectTo(WiFiService* service, Error* error) { |
| CHECK(service) << "Can't connect to NULL service."; |
| RpcIdentifier network_rpcid; |
| |
| // Ignore this connection attempt if suppplicant is not present. |
| // This is possible when we try to connect right after WiFi |
| // boostrapping is completed (through weaved). Refer to b/24605760 |
| // for more information. |
| // Once supplicant is detected, shill will auto-connect to this |
| // service (if this service is configured for auto-connect) when |
| // it is discovered in the scan. |
| if (!supplicant_present_) { |
| LOG(WARNING) << "Trying to connect before supplicant is present"; |
| return; |
| } |
| |
| // TODO(quiche): Handle cases where already connected. |
| if (pending_service_ && pending_service_ == service) { |
| Error::PopulateAndLog( |
| FROM_HERE, error, Error::kInProgress, |
| base::StringPrintf( |
| "%s: ignoring ConnectTo %s, which is already pending", |
| link_name().c_str(), service->log_name().c_str())); |
| return; |
| } |
| |
| if (pending_service_ && pending_service_ != service) { |
| LOG(INFO) << "Connecting to: " << service->log_name() << ", " |
| << "mode: " << service->mode() << ", " |
| << "key management: " << service->key_management() << ", " |
| << "physical mode: " << service->physical_mode() << ", " |
| << "frequency: " << service->frequency(); |
| // This is a signal to SetPendingService(nullptr) to not modify the scan |
| // state since the overall story arc isn't reflected by the disconnect. |
| // It is, instead, described by the transition to either kScanFoundNothing |
| // or kScanConnecting (made by |SetPendingService|, below). |
| if (scan_method_ != kScanMethodNone) { |
| SetScanState(kScanTransitionToConnecting, scan_method_, __func__); |
| } |
| // Explicitly disconnect pending service. |
| pending_service_->set_expecting_disconnect(true); |
| DisconnectFrom(pending_service_.get()); |
| } |
| |
| Error unused_error; |
| network_rpcid = FindNetworkRpcidForService(service, &unused_error); |
| if (network_rpcid.value().empty()) { |
| KeyValueStore service_params = |
| service->GetSupplicantConfigurationParameters(); |
| const uint32_t scan_ssid = 1; // "True": Use directed probe. |
| service_params.Set<uint32_t>(WPASupplicant::kNetworkPropertyScanSSID, |
| scan_ssid); |
| std::string bgscan_string = AppendBgscan(service, &service_params); |
| service_params.Set<uint32_t>(WPASupplicant::kNetworkPropertyDisableVHT, |
| provider_->disable_vht()); |
| if (!supplicant_interface_proxy_->AddNetwork(service_params, |
| &network_rpcid)) { |
| Error::PopulateAndLog(FROM_HERE, error, Error::kOperationFailed, |
| "Failed to add network"); |
| SetScanState(kScanIdle, scan_method_, __func__); |
| return; |
| } |
| CHECK(!network_rpcid.value().empty()); // No DBus path should be empty. |
| service->set_bgscan_string(bgscan_string); |
| rpcid_by_service_[service] = network_rpcid; |
| } |
| |
| if (service->HasRecentConnectionIssues()) { |
| SetConnectionDebugging(true); |
| } |
| |
| supplicant_interface_proxy_->SelectNetwork(network_rpcid); |
| SetPendingService(service); |
| CHECK(current_service_.get() != pending_service_.get()); |
| |
| // SelectService here (instead of in LinkEvent, like Ethernet), so |
| // that, if we fail to bring up L2, we can attribute failure correctly. |
| // |
| // TODO(quiche): When we add code for dealing with connection failures, |
| // reconsider if this is the right place to change the selected service. |
| // see discussion in crbug.com/203282. |
| SelectService(service); |
| } |
| |
| void WiFi::DisconnectFromIfActive(WiFiService* service) { |
| SLOG(this, 2) << __func__ << " service " << service->log_name(); |
| |
| if (service != current_service_ && service != pending_service_) { |
| if (!service->IsActive(nullptr)) { |
| SLOG(this, 2) << "In " << __func__ << "(): " << service->log_name() |
| << " is not active, no need to initiate disconnect"; |
| return; |
| } |
| } |
| |
| DisconnectFrom(service); |
| } |
| |
| void WiFi::DisconnectFrom(WiFiService* service) { |
| SLOG(this, 2) << __func__ << " service " << service->log_name(); |
| |
| if (service != current_service_ && service != pending_service_) { |
| // TODO(quiche): Once we have asynchronous reply support, we should |
| // generate a D-Bus error here. (crbug.com/206812) |
| LOG(WARNING) << "In " << __func__ << "(): " |
| << " ignoring request to disconnect from: " |
| << service->log_name() |
| << " which is neither current nor pending"; |
| return; |
| } |
| |
| if (pending_service_ && service != pending_service_) { |
| // TODO(quiche): Once we have asynchronous reply support, we should |
| // generate a D-Bus error here. (crbug.com/206812) |
| LOG(WARNING) << "In " << __func__ << "(): " |
| << " ignoring request to disconnect from: " |
| << service->log_name() << " which is not the pending service."; |
| return; |
| } |
| |
| if (!pending_service_ && service != current_service_) { |
| // TODO(quiche): Once we have asynchronous reply support, we should |
| // generate a D-Bus error here. (crbug.com/206812) |
| LOG(WARNING) << "In " << __func__ << "(): " |
| << " ignoring request to disconnect from: " |
| << service->log_name() << " which is not the current service."; |
| return; |
| } |
| |
| if (pending_service_) { |
| // Since wpa_supplicant has not yet set CurrentBSS, we can't depend |
| // on this to drive the service state back to idle. Do that here. |
| // Update service state for pending service. |
| disconnect_signal_dbm_ = pending_service_->SignalLevel(); |
| ServiceDisconnected(pending_service_); |
| } else if (service) { |
| disconnect_signal_dbm_ = service->SignalLevel(); |
| } |
| |
| SetPendingService(nullptr); |
| StopReconnectTimer(); |
| StopRequestingStationInfo(); |
| |
| if (!supplicant_present_) { |
| LOG(ERROR) << "In " << __func__ << "(): " |
| << "wpa_supplicant is not present; silently resetting " |
| << "current_service_."; |
| if (current_service_ == selected_service()) { |
| DropConnection(); |
| } |
| current_service_ = nullptr; |
| return; |
| } |
| |
| bool disconnect_in_progress = true; |
| // We'll call RemoveNetwork and reset |current_service_| after |
| // supplicant notifies us that the CurrentBSS has changed. |
| if (!supplicant_interface_proxy_->Disconnect()) { |
| disconnect_in_progress = false; |
| } |
| |
| if (supplicant_state_ != WPASupplicant::kInterfaceStateCompleted || |
| !disconnect_in_progress) { |
| // Can't depend on getting a notification of CurrentBSS change. |
| // So effect changes immediately. For instance, this can happen when |
| // a disconnect is triggered by a BSS going away. |
| Error unused_error; |
| RemoveNetworkForService(service, &unused_error); |
| if (service == selected_service()) { |
| DropConnection(); |
| } else { |
| SLOG(this, 5) << __func__ << " skipping DropConnection, " |
| << "selected_service is " |
| << (selected_service() ? selected_service()->log_name() |
| : "(null)"); |
| } |
| current_service_ = nullptr; |
| } |
| |
| CHECK(current_service_ == nullptr || |
| current_service_.get() != pending_service_.get()); |
| } |
| |
| bool WiFi::DisableNetwork(const RpcIdentifier& network) { |
| std::unique_ptr<SupplicantNetworkProxyInterface> supplicant_network_proxy = |
| control_interface()->CreateSupplicantNetworkProxy(network); |
| if (!supplicant_network_proxy->SetEnabled(false)) { |
| LOG(ERROR) << "DisableNetwork for " << network.value() << " failed."; |
| return false; |
| } |
| return true; |
| } |
| |
| bool WiFi::RemoveNetwork(const RpcIdentifier& network) { |
| return supplicant_interface_proxy_->RemoveNetwork(network); |
| } |
| |
| bool WiFi::IsIdle() const { |
| return !current_service_ && !pending_service_; |
| } |
| |
| void WiFi::ClearCachedCredentials(const WiFiService* service) { |
| // Give up on the connection attempt for the pending service immediately since |
| // the credential for it had already changed. This will allow the Manager to |
| // start a new connection attempt for the pending service immediately without |
| // waiting for the pending connection timeout. |
| // current_service_ will get disconnect notification from the CurrentBSS |
| // change event, so no need to explicitly disconnect here. |
| if (service == pending_service_) { |
| LOG(INFO) << "Disconnect pending service: credential changed"; |
| DisconnectFrom(pending_service_.get()); |
| } |
| |
| Error unused_error; |
| RemoveNetworkForService(service, &unused_error); |
| } |
| |
| void WiFi::NotifyEndpointChanged(const WiFiEndpointConstRefPtr& endpoint) { |
| provider_->OnEndpointUpdated(endpoint); |
| } |
| |
| std::string WiFi::AppendBgscan(WiFiService* service, |
| KeyValueStore* service_params) const { |
| std::string method = bgscan_method_; |
| int short_interval = bgscan_short_interval_seconds_; |
| int signal_threshold = bgscan_signal_threshold_dbm_; |
| int scan_interval = kBackgroundScanIntervalSeconds; |
| if (method.empty()) { |
| // If multiple APs are detected for this SSID, configure the default method |
| // with pre-set parameters. Otherwise, use extended scan intervals. |
| method = kDefaultBgscanMethod; |
| if (service->GetEndpointCount() <= 1) { |
| SLOG(nullptr, 3) << "Background scan intervals extended -- single " |
| << "Endpoint for Service."; |
| short_interval = kSingleEndpointBgscanShortIntervalSeconds; |
| scan_interval = kSingleEndpointBgscanIntervalSeconds; |
| } |
| } else if (method == WPASupplicant::kNetworkBgscanMethodNone) { |
| SLOG(nullptr, 3) << "Background scan disabled -- chose None method."; |
| } else { |
| // If the background scan method was explicitly specified, honor the |
| // configured background scan interval. |
| scan_interval = scan_interval_seconds_; |
| } |
| std::string config_string; |
| if (method != WPASupplicant::kNetworkBgscanMethodNone) { |
| config_string = |
| base::StringPrintf("%s:%d:%d:%d", method.c_str(), short_interval, |
| signal_threshold, scan_interval); |
| } |
| SLOG(nullptr, 3) << "Background scan: '" << config_string << "'"; |
| service_params->Set<std::string>(WPASupplicant::kNetworkPropertyBgscan, |
| config_string); |
| return config_string; |
| } |
| |
| bool WiFi::ReconfigureBgscan(WiFiService* service) { |
| SLOG(this, 3) << __func__ << " for " << service->log_name(); |
| KeyValueStore bgscan_params; |
| std::string bgscan_string = AppendBgscan(service, &bgscan_params); |
| if (service->bgscan_string() == bgscan_string) { |
| SLOG(this, 3) << "No change in bgscan parameters."; |
| return false; |
| } |
| |
| Error unused_error; |
| RpcIdentifier id = FindNetworkRpcidForService(service, &unused_error); |
| if (id.value().empty()) { |
| return false; |
| } |
| |
| std::unique_ptr<SupplicantNetworkProxyInterface> network_proxy = |
| control_interface()->CreateSupplicantNetworkProxy(id); |
| if (!network_proxy->SetProperties(bgscan_params)) { |
| LOG(ERROR) << "SetProperties for " << id.value() << " failed."; |
| return false; |
| } |
| LOG(INFO) << "Updated bgscan parameters: " << bgscan_string; |
| service->set_bgscan_string(bgscan_string); |
| return true; |
| } |
| |
| bool WiFi::ReconfigureBgscanForRelevantServices() { |
| bool ret = true; |
| if (current_service_) { |
| ret = ReconfigureBgscan(current_service_.get()) && ret; |
| } |
| if (pending_service_) { |
| ret = ReconfigureBgscan(pending_service_.get()) && ret; |
| } |
| return ret; |
| } |
| |
| std::string WiFi::GetBgscanMethod(Error* /* error */) { |
| return bgscan_method_.empty() ? kDefaultBgscanMethod : bgscan_method_; |
| } |
| |
| bool WiFi::SetBgscanMethod(const std::string& method, Error* error) { |
| if (method != WPASupplicant::kNetworkBgscanMethodSimple && |
| method != WPASupplicant::kNetworkBgscanMethodLearn && |
| method != WPASupplicant::kNetworkBgscanMethodNone) { |
| const auto error_message = |
| base::StringPrintf("Unrecognized bgscan method %s", method.c_str()); |
| LOG(WARNING) << error_message; |
| error->Populate(Error::kInvalidArguments, error_message); |
| return false; |
| } |
| if (bgscan_method_ == method) { |
| return false; |
| } |
| bgscan_method_ = method; |
| return ReconfigureBgscanForRelevantServices(); |
| } |
| |
| bool WiFi::SetBgscanShortInterval(const uint16_t& seconds, Error* /*error*/) { |
| if (bgscan_short_interval_seconds_ == seconds) { |
| return false; |
| } |
| bgscan_short_interval_seconds_ = seconds; |
| return ReconfigureBgscanForRelevantServices(); |
| } |
| |
| bool WiFi::SetBgscanSignalThreshold(const int32_t& dbm, Error* /*error*/) { |
| if (bgscan_signal_threshold_dbm_ == dbm) { |
| return false; |
| } |
| bgscan_signal_threshold_dbm_ = dbm; |
| return ReconfigureBgscanForRelevantServices(); |
| } |
| |
| bool WiFi::SetScanInterval(const uint16_t& seconds, Error* /*error*/) { |
| if (scan_interval_seconds_ == seconds) { |
| return false; |
| } |
| scan_interval_seconds_ = seconds; |
| if (enabled()) { |
| StartScanTimer(); |
| } |
| // The scan interval affects both foreground scans (handled by |
| // |scan_timer_callback_|), and background scans (handled by |
| // supplicant). |
| return ReconfigureBgscanForRelevantServices(); |
| } |
| |
| bool WiFi::GetRandomMacEnabled(Error* /*error*/) { |
| return random_mac_enabled_; |
| } |
| |
| bool WiFi::SetRandomMacEnabled(const bool& enabled, Error* error) { |
| if (!supplicant_present_ || !supplicant_interface_proxy_.get()) { |
| SLOG(this, 2) << "Ignoring random MAC while supplicant is not present."; |
| return false; |
| } |
| |
| if (random_mac_enabled_ == enabled) { |
| return false; |
| } |
| if (!random_mac_supported_) { |
| Error::PopulateAndLog( |
| FROM_HERE, error, Error::kNotSupported, |
| "This WiFi device does not support MAC address randomization"); |
| return false; |
| } |
| if ((enabled && supplicant_interface_proxy_->EnableMacAddressRandomization( |
| kRandomMacMask, sched_scan_supported_)) || |
| (!enabled && |
| supplicant_interface_proxy_->DisableMacAddressRandomization())) { |
| random_mac_enabled_ = enabled; |
| return true; |
| } |
| return false; |
| } |
| |
| void WiFi::ClearBgscanMethod(Error* /*error*/) { |
| bgscan_method_.clear(); |
| } |
| |
| void WiFi::AssocStatusChanged(const int32_t new_assoc_status) { |
| SLOG(this, 3) << "WiFi " << link_name() |
| << " supplicant updated AssocStatusCode to " << new_assoc_status |
| << " (was " << supplicant_assoc_status_ << ")"; |
| if (supplicant_auth_status_ != IEEE_80211::kStatusCodeSuccessful) { |
| LOG(WARNING) << "Supplicant authentication status is set to " |
| << supplicant_auth_status_ |
| << " despite getting a new association status."; |
| supplicant_auth_status_ = IEEE_80211::kStatusCodeSuccessful; |
| } |
| supplicant_assoc_status_ = new_assoc_status; |
| } |
| |
| void WiFi::AuthStatusChanged(const int32_t new_auth_status) { |
| SLOG(this, 3) << "WiFi " << link_name() |
| << " supplicant updated AuthStatusCode to " << new_auth_status |
| << " (was " << supplicant_auth_status_ << ")"; |
| if (supplicant_assoc_status_ != IEEE_80211::kStatusCodeSuccessful) { |
| LOG(WARNING) << "Supplicant association status is set to " |
| << supplicant_assoc_status_ |
| << " despite getting a new authentication status."; |
| supplicant_assoc_status_ = IEEE_80211::kStatusCodeSuccessful; |
| } |
| supplicant_auth_status_ = new_auth_status; |
| } |
| |
| void WiFi::CurrentBSSChanged(const RpcIdentifier& new_bss) { |
| SLOG(this, 3) << "WiFi " << link_name() << " CurrentBSS " |
| << supplicant_bss_.value() << " -> " << new_bss.value(); |
| |
| // Store signal strength of BSS when disconnecting. |
| if (supplicant_bss_.value() != WPASupplicant::kCurrentBSSNull && |
| new_bss.value() == WPASupplicant::kCurrentBSSNull) { |
| const WiFiEndpointConstRefPtr endpoint(GetCurrentEndpoint()); |
| if (endpoint == nullptr) { |
| LOG(ERROR) << "Can't get endpoint for current supplicant BSS " |
| << supplicant_bss_.value(); |
| // Default to value that will not imply out of range error in |
| // ServiceDisconnected or PendingTimeoutHandler. |
| disconnect_signal_dbm_ = kDefaultDisconnectDbm; |
| } else { |
| disconnect_signal_dbm_ = endpoint->signal_strength(); |
| LOG(INFO) << "Current BSS signal strength at disconnect: " |
| << disconnect_signal_dbm_; |
| } |
| } |
| |
| supplicant_bss_ = new_bss; |
| has_already_completed_ = false; |
| is_roaming_in_progress_ = false; |
| if (current_service_) { |
| current_service_->SetIsRekeyInProgress(false); |
| } |
| |
| // Any change in CurrentBSS means supplicant is actively changing our |
| // connectivity. We no longer need to track any previously pending |
| // reconnect. |
| StopReconnectTimer(); |
| StopRequestingStationInfo(); |
| |
| if (new_bss.value() == WPASupplicant::kCurrentBSSNull) { |
| HandleDisconnect(); |
| if (!provider_->GetHiddenSSIDList().empty()) { |
| // Before disconnecting, wpa_supplicant probably scanned for |
| // APs. So, in the normal case, we defer to the timer for the next scan. |
| // |
| // However, in the case of hidden SSIDs, supplicant knows about |
| // at most one of them. (That would be the hidden SSID we were |
| // connected to, if applicable.) |
| // |
| // So, in this case, we initiate an immediate scan. This scan |
| // will include the hidden SSIDs we know about (up to the limit of |
| // kScanMAxSSIDsPerScan). |
| // |
| // We may want to reconsider this immediate scan, if/when shill |
| // takes greater responsibility for scanning (vs. letting |
| // supplicant handle most of it). |
| Scan(nullptr, __func__); |
| } |
| } else { |
| HandleRoam(new_bss); |
| } |
| |
| // Reset the EAP handler only after calling HandleDisconnect() above |
| // so our EAP state could be used to detect a failed authentication. |
| eap_state_handler_->Reset(); |
| pending_eap_failure_ = Service::kFailureNone; |
| |
| // If we are selecting a new service, or if we're clearing selection |
| // of a something other than the pending service, call SelectService. |
| // Otherwise skip SelectService, since this will cause the pending |
| // service to be marked as Idle. |
| if (current_service_ || selected_service() != pending_service_) { |
| SelectService(current_service_); |
| } |
| |
| // Invariant check: a Service can either be current, or pending, but |
| // not both. |
| CHECK(current_service_.get() != pending_service_.get() || |
| current_service_.get() == nullptr); |
| |
| // If we are no longer debugging a problematic WiFi connection, return |
| // to the debugging level indicated by the WiFi debugging scope. |
| if ((!current_service_ || !current_service_->HasRecentConnectionIssues()) && |
| (!pending_service_ || !pending_service_->HasRecentConnectionIssues())) { |
| SetConnectionDebugging(false); |
| } |
| } |
| |
| void WiFi::DisconnectReasonChanged(const int32_t new_value) { |
| int32_t sanitized_value = |
| (new_value == INT32_MIN) ? INT32_MAX : abs(new_value); |
| if (sanitized_value > IEEE_80211::kReasonCodeMax) { |
| LOG(WARNING) << "Received disconnect reason " << sanitized_value |
| << " from supplicant greater than kReasonCodeMax." |
| << " Perhaps WiFiReasonCode needs to be updated."; |
| sanitized_value = IEEE_80211::kReasonCodeMax; |
| } |
| auto new_reason = static_cast<IEEE_80211::WiFiReasonCode>(sanitized_value); |
| |
| std::string update; |
| if (supplicant_disconnect_reason_ != IEEE_80211::kReasonCodeInvalid) { |
| update = base::StringPrintf(" from %d", supplicant_disconnect_reason_); |
| } |
| |
| std::string new_disconnect_description = "Success"; |
| if (new_reason != 0) { |
| new_disconnect_description = IEEE_80211::ReasonToString(new_reason); |
| } |
| |
| LOG(INFO) << base::StringPrintf( |
| "WiFi %s supplicant updated DisconnectReason%s to %d (%s)", |
| link_name().c_str(), update.c_str(), new_reason, |
| new_disconnect_description.c_str()); |
| supplicant_disconnect_reason_ = new_reason; |
| |
| Metrics::WiFiDisconnectByWhom by_whom = (new_value < 0) |
| ? Metrics::kDisconnectedNotByAp |
| : Metrics::kDisconnectedByAp; |
| metrics()->Notify80211Disconnect(by_whom, new_reason); |
| } |
| |
| void WiFi::CurrentAuthModeChanged(const std::string& auth_mode) { |
| if (auth_mode != WPASupplicant::kAuthModeInactive && |
| auth_mode != WPASupplicant::kAuthModeUnknown) { |
| supplicant_auth_mode_ = auth_mode; |
| } |
| } |
| |
| void WiFi::HandleDisconnect() { |
| // Identify the affected service. We expect to get a disconnect |
| // event when we fall off a Service that we were connected |
| // to. However, we also allow for the case where we get a disconnect |
| // event while attempting to connect from a disconnected state. |
| WiFiService* affected_service = |
| current_service_.get() ? current_service_.get() : pending_service_.get(); |
| |
| if (!affected_service) { |
| SLOG(this, 2) << "WiFi " << link_name() |
| << " disconnected while not connected or connecting"; |
| return; |
| } |
| |
| SLOG(this, 2) << "WiFi " << link_name() << " disconnected from " |
| << " (or failed to connect to) " |
| << affected_service->log_name(); |
| |
| if (affected_service == current_service_.get() && pending_service_.get()) { |
| // Current service disconnected intentionally for network switching, |
| // set service state to idle. |
| affected_service->SetState(Service::kStateIdle); |
| } else { |
| // Perform necessary handling for disconnected service. |
| ServiceDisconnected(affected_service); |
| } |
| |
| current_service_ = nullptr; |
| |
| if (affected_service == selected_service()) { |
| // If our selected service has disconnected, destroy IP configuration state. |
| DropConnection(); |
| } |
| |
| Error error; |
| if (!DisableNetworkForService(affected_service, &error)) { |
| if (error.type() == Error::kNotFound) { |
| SLOG(this, 2) << "WiFi " << link_name() << " disconnected from " |
| << " (or failed to connect to) service " |
| << affected_service->log_name() << ", " |
| << "but could not find supplicant network to disable."; |
| } else { |
| LOG(ERROR) << "DisableNetwork failed on " << link_name() |
| << "for: " << affected_service->log_name() << "."; |
| } |
| } |
| |
| metrics()->NotifySignalAtDisconnect(*affected_service, |
| disconnect_signal_dbm_); |
| affected_service->NotifyCurrentEndpoint(nullptr); |
| metrics()->NotifyServiceDisconnect(*affected_service); |
| |
| if (affected_service == pending_service_.get()) { |
| // The attempt to connect to |pending_service_| failed. Clear |
| // |pending_service_|, to indicate we're no longer in the middle |
| // of a connect request. |
| SetPendingService(nullptr); |
| } else if (pending_service_) { |
| // We've attributed the disconnection to what was the |
| // |current_service_|, rather than the |pending_service_|. |
| // |
| // If we're wrong about that (i.e. supplicant reported this |
| // CurrentBSS change after attempting to connect to |
| // |pending_service_|), we're depending on supplicant to retry |
| // connecting to |pending_service_|, and delivering another |
| // CurrentBSS change signal in the future. |
| // |
| // Log this fact, to help us debug (in case our assumptions are |
| // wrong). |
| SLOG(this, 2) << "WiFi " << link_name() |
| << " pending connection to: " << pending_service_->log_name() |
| << " after disconnect"; |
| } |
| |
| // If we disconnect, initially scan at a faster frequency, to make sure |
| // we've found all available APs. |
| RestartFastScanAttempts(); |
| } |
| |
| void WiFi::ServiceDisconnected(WiFiServiceRefPtr affected_service) { |
| SLOG(this, 1) << __func__ << " service " << affected_service->log_name(); |
| |
| // Check if service was explicitly disconnected due to failure or |
| // is explicitly disconnected by user. |
| if (!affected_service->IsInFailState() && |
| !affected_service->explicitly_disconnected() && |
| !affected_service->expecting_disconnect()) { |
| // Check auth/assoc status codes and send metric if a status code indicates |
| // failure (otherwise logs and UMA will only contain status code failures |
| // caused by a pending connection timeout). |
| Service::ConnectFailure failure_from_status = ExamineStatusCodes(); |
| |
| // Determine disconnect failure reason. |
| Service::ConnectFailure failure; |
| if (SuspectCredentials(affected_service, &failure)) { |
| // If we've reached here, |SuspectCredentials| has already set |
| // |failure| to the appropriate value. |
| } else { |
| SLOG(this, 2) << "Supplicant disconnect reason: " |
| << IEEE_80211::ReasonToString( |
| supplicant_disconnect_reason_); |
| // Disconnected for some other reason. |
| // Map IEEE error codes to shill error codes. |
| switch (supplicant_disconnect_reason_) { |
| case IEEE_80211::kReasonCodeInactivity: |
| case IEEE_80211::kReasonCodeSenderHasLeft: |
| SLOG(this, 2) << "Disconnect signal: " << disconnect_signal_dbm_; |
| if (disconnect_signal_dbm_ <= disconnect_threshold_dbm_ && |
| disconnect_signal_dbm_ != kDefaultDisconnectDbm) { |
| failure = Service::kFailureOutOfRange; |
| } else { |
| failure = Service::kFailureDisconnect; |
| } |
| break; |
| case IEEE_80211::kReasonCodeNonAuthenticated: |
| case IEEE_80211::kReasonCodeReassociationNotAuthenticated: |
| case IEEE_80211::kReasonCodePreviousAuthenticationInvalid: |
| failure = Service::kFailureNotAuthenticated; |
| break; |
| case IEEE_80211::kReasonCodeNonAssociated: |
| failure = Service::kFailureNotAssociated; |
| break; |
| case IEEE_80211::kReasonCodeTooManySTAs: |
| failure = Service::kFailureTooManySTAs; |
| break; |
| case IEEE_80211::kReasonCode8021XAuth: |
| failure = Service::kFailureEAPAuthentication; |
| break; |
| default: |
| // If we don't have a failure type to set given the disconnect reason, |
| // see if assoc/auth status codes can lead to an informative failure |
| // reason. Will be kFailureUnknown if that isn't the case. |
| failure = failure_from_status; |
| break; |
| } |
| } |
| if (failure == Service::kFailureEAPAuthentication && |
| pending_eap_failure_ != Service::kFailureNone) { |
| failure = pending_eap_failure_; |
| } |
| if (!affected_service->ShouldIgnoreFailure()) { |
| affected_service->SetFailure(failure); |
| } |
| LOG(ERROR) << "Disconnected due to reason: " |
| << Service::ConnectFailureToString(failure); |
| } |
| |
| // Set service state back to idle, so this service can be used for |
| // future connections. |
| affected_service->SetState(Service::kStateIdle); |
| } |
| |
| Service::ConnectFailure WiFi::ExamineStatusCodes() const { |
| bool is_auth_error = |
| supplicant_auth_status_ != IEEE_80211::kStatusCodeSuccessful; |
| bool is_assoc_error = |
| supplicant_assoc_status_ != IEEE_80211::kStatusCodeSuccessful; |
| DCHECK(!(is_auth_error && is_assoc_error)); |
| if (!is_auth_error && !is_assoc_error) { |
| return Service::kFailureUnknown; |
| } |
| |
| int32_t status = supplicant_auth_status_; |
| std::string error_name = "Authentication"; |
| std::string metric_name = Metrics::kMetricWiFiAuthFailureType; |
| Service::ConnectFailure proposed_failure = Service::kFailureNotAuthenticated; |
| if (is_assoc_error) { |
| status = supplicant_assoc_status_; |
| error_name = "Association"; |
| metric_name = Metrics::kMetricWiFiAssocFailureType; |
| proposed_failure = Service::kFailureNotAssociated; |
| } |
| |
| LOG(INFO) << "WiFi Device " << link_name() << ": " << error_name << " error " |
| << status << " (" |
| << IEEE_80211::StatusToString( |
| static_cast<IEEE_80211::WiFiStatusCode>(status)) |
| << ")"; |
| metrics()->SendEnumToUMA(metric_name, status, IEEE_80211::kStatusCodeMax); |
| |
| if (status == IEEE_80211::kStatusCodeMaxSta) { |
| proposed_failure = Service::kFailureTooManySTAs; |
| } |
| return proposed_failure; |
| } |
| |
| // We use the term "Roam" loosely. In particular, we include the case |
| // where we "Roam" to a BSS from the disconnected state. |
| void WiFi::HandleRoam(const RpcIdentifier& new_bss) { |
| EndpointMap::iterator endpoint_it = endpoint_by_rpcid_.find(new_bss); |
| if (endpoint_it == endpoint_by_rpcid_.end()) { |
| LOG(WARNING) << "WiFi " << link_name() << " connected to unknown BSS " |
| << new_bss.value(); |
| return; |
| } |
| |
| const WiFiEndpointConstRefPtr endpoint(endpoint_it->second); |
| WiFiServiceRefPtr service = provider_->FindServiceForEndpoint(endpoint); |
| if (!service) { |
| LOG(WARNING) << "WiFi " << link_name() |
| << " could not find Service for Endpoint " |
| << endpoint->bssid_string() << " (service will be unchanged)"; |
| return; |
| } |
| |
| metrics()->NotifyAp80211kSupport( |
| endpoint->krv_support().neighbor_list_supported); |
| metrics()->NotifyAp80211rSupport(endpoint->krv_support().ota_ft_supported, |
| endpoint->krv_support().otds_ft_supported); |
| metrics()->NotifyAp80211vDMSSupport(endpoint->krv_support().dms_supported); |
| metrics()->NotifyAp80211vBSSMaxIdlePeriodSupport( |
| endpoint->krv_support().bss_max_idle_period_supported); |
| metrics()->NotifyAp80211vBSSTransitionSupport( |
| endpoint->krv_support().bss_transition_supported); |
| metrics()->NotifyHS20Support(endpoint->hs20_information().supported, |
| endpoint->hs20_information().version); |
| metrics()->NotifyMBOSupport(endpoint->mbo_support()); |
| |
| SLOG(this, 2) << "WiFi " << link_name() << " roamed to Endpoint " |
| << endpoint->bssid_string() << " " |
| << LogSSID(endpoint->ssid_string()); |
| |
| service->NotifyCurrentEndpoint(endpoint); |
| |
| if (pending_service_.get() && service.get() != pending_service_.get()) { |
| // The Service we've roamed on to is not the one we asked for. |
| // We assume that this is transient, and that wpa_supplicant |
| // is trying / will try to connect to |pending_service_|. |
| // |
| // If it succeeds, we'll end up back here, but with |service| |
| // pointing at the same service as |pending_service_|. |
| // |
| // If it fails, we'll process things in HandleDisconnect. |
| // |
| // So we leave |pending_service_| untouched. |
| SLOG(this, 2) << "WiFi " << link_name() << " new current Endpoint " |
| << endpoint->bssid_string() |
| << " is not part of pending service " |
| << pending_service_->log_name(); |
| |
| // Quick check: if we didn't roam onto |pending_service_|, we |
| // should still be on |current_service_|. |
| if (service.get() != current_service_.get()) { |
| LOG(WARNING) << "WiFi " << link_name() << " new current Endpoint " |
| << endpoint->bssid_string() |
| << " is neither part of pending service " |
| << pending_service_->log_name() |
| << " nor part of current service " |
| << (current_service_ ? current_service_->log_name() |
| : "(nullptr)"); |
| // wpa_supplicant has no knowledge of the pending_service_ at this point. |
| // Disconnect the pending_service_, so that it can be connectable again. |
| // Otherwise, we'd have to wait for the pending timeout to trigger the |
| // disconnect. This will speed up the connection attempt process for |
| // the pending_service_. |
| DisconnectFrom(pending_service_.get()); |
| } |
| return; |
| } |
| |
| if (pending_service_) { |
| // We assume service.get() == pending_service_.get() here, because |
| // of the return in the previous if clause. |
| // |
| // Boring case: we've connected to the service we asked |
| // for. Simply update |current_service_| and |pending_service_|. |
| current_service_ = service; |
| SetScanState(kScanConnected, scan_method_, __func__); |
| SetPendingService(nullptr); |
| return; |
| } |
| |
| // |pending_service_| was nullptr, so we weren't attempting to connect |
| // to a new Service. Quick check that we're still on |current_service_|. |
| if (service.get() != current_service_.get()) { |
| LOG(WARNING) << "WiFi " << link_name() << " new current Endpoint " |
| << endpoint->bssid_string() |
| << (current_service_.get() |
| ? base::StringPrintf( |
| " is not part of current service %s", |
| current_service_->log_name().c_str()) |
| : " with no current service"); |
| // We didn't expect to be here, but let's cope as well as we |
| // can. Update |current_service_| to keep it in sync with |
| // supplicant. |
| current_service_ = service; |
| |
| // If this service isn't already marked as actively connecting (likely, |
| // since this service is a bit of a surprise) set the service as |
| // associating. |
| if (!current_service_->IsConnecting()) { |
| current_service_->SetState(Service::kStateAssociating); |
| } |
| |
| return; |
| } |
| |
| // At this point, we know that |pending_service_| was nullptr, and that |
| // we're still on |current_service_|. We should track this roaming |
| // event so we can refresh our IPConfig if it succeeds. |
| is_roaming_in_progress_ = true; |
| |
| return; |
| } |
| |
| RpcIdentifier WiFi::FindNetworkRpcidForService(const WiFiService* service, |
| Error* error) { |
| ReverseServiceMap::const_iterator rpcid_it = rpcid_by_service_.find(service); |
| if (rpcid_it == rpcid_by_service_.end()) { |
| const auto error_message = base::StringPrintf( |
| "WiFi %s cannot find supplicant network rpcid for service %s", |
| link_name().c_str(), service->log_name().c_str()); |
| // There are contexts where this is not an error, such as when a service |
| // is clearing whatever cached credentials may not exist. |
| SLOG(this, 2) << error_message; |
| if (error) { |
| error->Populate(Error::kNotFound, error_message); |
| } |
| return RpcIdentifier(""); |
| } |
| |
| return rpcid_it->second; |
| } |
| |
| bool WiFi::DisableNetworkForService(const WiFiService* service, Error* error) { |
| RpcIdentifier rpcid = FindNetworkRpcidForService(service, error); |
| if (rpcid.value().empty()) { |
| // Error is already populated. |
| return false; |
| } |
| |
| if (!DisableNetwork(rpcid)) { |
| const auto error_message = base::StringPrintf( |
| "WiFi %s cannot disable network for service %s: " |
| "DBus operation failed for rpcid %s.", |
| link_name().c_str(), service->log_name().c_str(), |
| rpcid.value().c_str()); |
| Error::PopulateAndLog(FROM_HERE, error, Error::kOperationFailed, |
| error_message); |
| |
| // Make sure that such errored networks are removed, so problems do not |
| // propagate to future connection attempts. |
| RemoveNetwork(rpcid); |
| rpcid_by_service_.erase(service); |
| |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool WiFi::RemoveNetworkForService(const WiFiService* service, Error* error) { |
| RpcIdentifier rpcid = FindNetworkRpcidForService(service, error); |
| if (rpcid.value().empty()) { |
| // Error is already populated. |
| return false; |
| } |
| |
| // Erase the rpcid from our tables regardless of failure below, since even |
| // if in failure, we never want to use this network again. |
| rpcid_by_service_.erase(service); |
| |
| // TODO(quiche): Reconsider giving up immediately. Maybe give |
| // wpa_supplicant some time to retry, first. |
| if (!RemoveNetwork(rpcid)) { |
| const auto error_message = base::StringPrintf( |
| "WiFi %s cannot remove network for service %s: " |
| "DBus operation failed for rpcid %s.", |
| link_name().c_str(), service->log_name().c_str(), |
| rpcid.value().c_str()); |
| Error::PopulateAndLog(FROM_HERE, error, Error::kOperationFailed, |
| error_message); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| void WiFi::PendingScanResultsHandler() { |
| CHECK(pending_scan_results_); |
| SLOG(this, 2) << __func__ << " with " << pending_scan_results_->results.size() |
| << " results and is_complete set to " |
| << pending_scan_results_->is_complete; |
| for (const auto& result : pending_scan_results_->results) { |
| if (result.is_removal) { |
| BSSRemovedTask(result.path); |
| } else { |
| BSSAddedTask(result.path, result.properties); |
| } |
| } |
| if (pending_scan_results_->is_complete) { |
| ScanDoneTask(); |
| } |
| pending_scan_results_.reset(); |
| } |
| |
| bool WiFi::ParseWiphyIndex(const Nl80211Message& nl80211_message) { |
| // Verify NL80211_CMD_NEW_WIPHY. |
| if (nl80211_message.command() != NewWiphyMessage::kCommand) { |
| LOG(ERROR) << "Received unexpected command: " << nl80211_message.command(); |
| return false; |
| } |
| if (!nl80211_message.const_attributes()->GetU32AttributeValue( |
| NL80211_ATTR_WIPHY, &wiphy_index_)) { |
| LOG(ERROR) << "NL80211_CMD_NEW_WIPHY had no NL80211_ATTR_WIPHY"; |
| return false; |
| } |
| return true; |
| } |
| |
| void WiFi::ParseFeatureFlags(const Nl80211Message& nl80211_message) { |
| // Verify NL80211_CMD_NEW_WIPHY. |
| if (nl80211_message.command() != NewWiphyMessage::kCommand) { |
| LOG(ERROR) << "Received unexpected command: " << nl80211_message.command(); |
| return; |
| } |
| |
| // Look for scheduled scan support. |
| AttributeListConstRefPtr cmds; |
| if (nl80211_message.const_attributes()->ConstGetNestedAttributeList( |
| NL80211_ATTR_SUPPORTED_COMMANDS, &cmds)) { |
| AttributeIdIterator cmds_iter(*cmds); |
| for (; !cmds_iter.AtEnd(); cmds_iter.Advance()) { |
| uint32_t cmd; |
| if (!cmds->GetU32AttributeValue(cmds_iter.GetId(), &cmd)) { |
| LOG(ERROR) << "Failed to get supported cmd " << cmds_iter.GetId(); |
| return; |
| } |
| if (cmd == NL80211_CMD_START_SCHED_SCAN) |
| sched_scan_supported_ = true; |
| } |
| } |
| |
| uint32_t flag; |
| if (nl80211_message.const_attributes()->GetU32AttributeValue( |
| NL80211_ATTR_FEATURE_FLAGS, &flag)) { |
| // There are two flags for MAC randomization: one for regular scans and one |
| // for scheduled scans. Only look for the latter if scheduled scans are |
| // supported. |
| // |
| // This flag being set properly currently relies on the assumption that |
| // sched_scan_supported_ is set sometime before this codepath is called. |
| // A potential TODO to not rely on this assumption is to accumulate all |
| // split messages, log the DONE reply, and perform our determinations at the |
| // end (aka set this flag). More discussion can be found on |
| // crrev.com/c/3028791. |
| |
| random_mac_supported_ = |
| (flag & NL80211_FEATURE_SCAN_RANDOM_MAC_ADDR) && |
| (!sched_scan_supported_ || |
| (flag & NL80211_FEATURE_SCHED_SCAN_RANDOM_MAC_ADDR)); |
| if (random_mac_supported_) { |
| SLOG(this, 7) << __func__ << ": " |
| << "Supports random MAC: " << random_mac_supported_; |
| } |
| } |
| } |
| |
| void WiFi::HandleNetlinkBroadcast(const NetlinkMessage& netlink_message) { |
| // We only handle nl80211 commands. |
| if (netlink_message.message_type() != Nl80211Message::GetMessageType()) { |
| SLOG(this, 7) << __func__ << ": " |
| << "Not a NL80211 Message"; |
| return; |
| } |
| const Nl80211Message& nl80211_msg = |
| *reinterpret_cast<const Nl80211Message*>(&netlink_message); |
| |
| // Pass nl80211 message to appropriate handler function. |
| if (nl80211_msg.command() == TriggerScanMessage::kCommand) { |
| OnScanStarted(nl80211_msg); |
| } else if (nl80211_msg.command() == WiphyRegChangeMessage::kCommand || |
| nl80211_msg.command() == RegChangeMessage::kCommand) { |
| OnRegChange(nl80211_msg); |
| } else if (nl80211_msg.command() == NotifyCqmMessage::kCommand) { |
| if (wifi_cqm_) { |
| wifi_cqm_->OnCQMNotify(nl80211_msg); |
| } |
| } |
| } |
| |
| void WiFi::OnScanStarted(const Nl80211Message& scan_trigger_msg) { |
| if (scan_trigger_msg.command() != TriggerScanMessage::kCommand) { |
| SLOG(this, 7) << __func__ << ": " |
| << "Not a NL80211_CMD_TRIGGER_SCAN message"; |
| return; |
| } |
| uint32_t wiphy_index; |
| if (!scan_trigger_msg.const_attributes()->GetU32AttributeValue( |
| NL80211_ATTR_WIPHY, &wiphy_index)) { |
| LOG(ERROR) << "NL80211_CMD_TRIGGER_SCAN had no NL80211_ATTR_WIPHY"; |
| return; |
| } |
| if (wiphy_index != wiphy_index_) { |
| SLOG(this, 7) << __func__ << ": " |
| << "Scan trigger not meant for this interface"; |
| return; |
| } |
| bool is_active_scan = false; |
| AttributeListConstRefPtr ssids; |
| if (scan_trigger_msg.const_attributes()->ConstGetNestedAttributeList( |
| NL80211_ATTR_SCAN_SSIDS, &ssids)) { |
| AttributeIdIterator ssid_iter(*ssids); |
| // If any SSIDs (even the empty wild card) are reported, an active scan was |
| // launched. Otherwise, a passive scan was launched. |
| is_active_scan = !ssid_iter.AtEnd(); |
| } |
| if (wake_on_wifi_) { |
| wake_on_wifi_->OnScanStarted(is_active_scan); |
| } |
| } |
| |
| void WiFi::OnGetReg(const Nl80211Message& nl80211_message) { |
| if (nl80211_message.command() != GetRegMessage::kCommand) { |
| LOG(ERROR) << __func__ |
| << ": unexpected command: " << nl80211_message.command_string(); |
| return; |
| } |
| |
| // Extract country code. |
| std::string country_code; |
| if (!nl80211_message.const_attributes()->GetStringAttributeValue( |
| NL80211_ATTR_REG_ALPHA2, &country_code)) { |
| SLOG(this, 3) << "Regulatory message had no NL80211_ATTR_REG_ALPHA2"; |
| return; // If no alpha2 value present, ignore it. |
| } |
| HandleCountryChange(country_code); |
| |
| uint8_t region; |
| if (!nl80211_message.const_attributes()->GetU8AttributeValue( |
| NL80211_ATTR_DFS_REGION, ®ion)) { |
| SLOG(this, 1) << "Regulatory message has no DFS region, using: " |
| << NL80211_DFS_UNSET; |
| region = NL80211_DFS_UNSET; |
| } else { |
| SLOG(this, 1) << "DFS region: " << region; |
| } |
| |
| manager()->power_manager()->ChangeRegDomain( |
| static_cast<nl80211_dfs_regions>(region)); |
| } |
| |
| void WiFi::OnRegChange(const Nl80211Message& nl80211_message) { |
| if (nl80211_message.command() != WiphyRegChangeMessage::kCommand && |
| nl80211_message.command() != RegChangeMessage::kCommand) { |
| LOG(ERROR) << __func__ |
| << ": unexpected command: " << nl80211_message.command_string(); |
| return; |
| } |
| |
| // Ignore regulatory domain CHANGE events initiated by user. |
| uint32_t initiator; |
| if (!nl80211_message.const_attributes()->GetU32AttributeValue( |
| NL80211_ATTR_REG_INITIATOR, &initiator)) { |
| SLOG(this, 3) << "No NL80211_ATTR_REG_INITIATOR in command " |
| << nl80211_message.command_string(); |
| return; |
| } |
| if (initiator == NL80211_REGDOM_SET_BY_USER) { |
| SLOG(this, 3) << "Ignoring regulatory domain change initiated by user."; |
| return; |
| } |
| |
| // CHANGE events don't have all the useful attributes (e.g., |
| // NL80211_ATTR_DFS_REGION); request the full info now. |
| GetRegulatory(); |
| } |
| |
| void WiFi::HandleCountryChange(std::string country_code) { |
| // Variable to keep track of current regulatory domain to reduce noise in |
| // reported "change" events. |
| static int current_reg_dom_val = -1; |
| |
| // Get Regulatory Domain value from received country code. |
| int reg_dom_val = Metrics::GetRegulatoryDomainValue(country_code); |
| if (reg_dom_val == Metrics::RegulatoryDomain::kCountryCodeInvalid) { |
| LOG(ERROR) << "Unsupported NL80211_ATTR_REG_ALPHA2 attribute: " |
| << country_code; |
| } else { |
| SLOG(this, 3) << base::StringPrintf( |
| "Regulatory domain change message received with alpha2 %s (metric val: " |
| "%d)", |
| country_code.c_str(), reg_dom_val); |
| } |
| |
| // Only send to UMA when regulatory domain changes to reduce noise in metrics. |
| if (reg_dom_val != current_reg_dom_val) { |
| current_reg_dom_val = reg_dom_val; |
| metrics()->SendEnumToUMA(Metrics::kMetricRegulatoryDomain, reg_dom_val, |
| Metrics::RegulatoryDomain::kRegDomMaxValue); |
| } |
| } |
| |
| void WiFi::BSSAddedTask(const RpcIdentifier& path, |
| const KeyValueStore& properties) { |
| // Note: we assume that BSSIDs are unique across endpoints. This |
| // means that if an AP reuses the same BSSID for multiple SSIDs, we |
| // lose. |
| WiFiEndpointRefPtr endpoint( |
| new WiFiEndpoint(control_interface(), this, path, properties, metrics())); |
| SLOG(this, 5) << "Found endpoint. " |
| << "RPC path: " << path.value() << ", " |
| << LogSSID(endpoint->ssid_string()) << ", " |
| << "bssid: " << endpoint->bssid_string() << ", " |
| << "signal: " << endpoint->signal_strength() << ", " |
| << "security: " << endpoint->security_mode() << ", " |
| << "frequency: " << endpoint->frequency(); |
| |
| if (endpoint->ssid_string().empty()) { |
| // Don't bother trying to find or create a Service for an Endpoint |
| // without an SSID. We wouldn't be able to connect to it anyway. |
| return; |
| } |
| |
| if (endpoint->ssid()[0] == 0) { |
| // Assume that an SSID starting with nullptr is bogus/misconfigured, |
| // and filter it out. |
| return; |
| } |
| |
| if (endpoint->network_mode().empty()) { |
| // Unsupported modes (e.g., ad-hoc) should be ignored. |
| return; |
| } |
| |
| provider_->OnEndpointAdded(endpoint); |
| // Adding a single endpoint can change the bgscan parameters for no more than |
| // one active Service. Try pending_service_ only if current_service_ doesn't |
| // change. |
| if ((!current_service_ || !ReconfigureBgscan(current_service_.get())) && |
| pending_service_) { |
| ReconfigureBgscan(pending_service_.get()); |
| } |
| |
| // Do this last, to maintain the invariant that any Endpoint we |
| // know about has a corresponding Service. |
| // |
| // TODO(quiche): Write test to verify correct behavior in the case |
| // where we get multiple BSSAdded events for a single endpoint. |
| // (Old Endpoint's refcount should fall to zero, and old Endpoint |
| // should be destroyed.) |
| endpoint_by_rpcid_[path] = endpoint; |
| endpoint->Start(); |
| } |
| |
| void WiFi::BSSRemovedTask(const RpcIdentifier& path) { |
| EndpointMap::iterator i = endpoint_by_rpcid_.find(path); |
| if (i == endpoint_by_rpcid_.end()) { |
| SLOG(this, 1) << "WiFi " << link_name() << " could not find BSS " |
| << path.value() << " to remove."; |
| return; |
| } |
| |
| WiFiEndpointRefPtr endpoint = i->second; |
| CHECK(endpoint); |
| endpoint_by_rpcid_.erase(i); |
| |
| WiFiServiceRefPtr service = provider_->OnEndpointRemoved(endpoint); |
| if (!service) { |
| // Removing a single endpoint can change the bgscan parameters for no more |
| // than one active Service. Try pending_service_ only if current_service_ |
| // doesn't change. |
| if ((!current_service_ || !ReconfigureBgscan(current_service_.get())) && |
| pending_service_) { |
| ReconfigureBgscan(pending_service_.get()); |
| } |
| return; |
| } |
| Error unused_error; |
| RemoveNetworkForService(service.get(), &unused_error); |
| |
| bool disconnect_service = !service->HasEndpoints() && |
| (service->IsConnecting() || service->IsConnected()); |
| |
| if (disconnect_service) { |
| LOG(INFO) << "Disconnecting from: " << service->log_name() |
| << ": BSSRemoved"; |
| DisconnectFrom(service.get()); |
| } |
| } |
| |
| void WiFi::CertificationTask(const KeyValueStore& properties) { |
| // Events may come immediately after Stop(). |
| if (!enabled()) { |
| return; |
| } |
| |
| if (!current_service_) { |
| LOG(ERROR) << "WiFi " << link_name() << " " << __func__ |
| << " with no current service."; |
| return; |
| } |
| |
| std::string subject; |
| uint32_t depth; |
| if (WPASupplicant::ExtractRemoteCertification(properties, &subject, &depth)) { |
| current_service_->AddEAPCertification(subject, depth); |
| } |
| } |
| |
| void WiFi::EAPEventTask(const std::string& status, |
| const std::string& parameter) { |
| // Events may come immediately after Stop(). |
| if (!enabled()) { |
| return; |
| } |
| |
| if (!current_service_) { |
| LOG(ERROR) << "WiFi " << link_name() << " " << __func__ |
| << " with no current service."; |
| return; |
| } |
| Service::ConnectFailure failure = Service::kFailureNone; |
| eap_state_handler_->ParseStatus(status, parameter, &failure); |
| if (failure == Service::kFailurePinMissing) { |
| // wpa_supplicant can sometimes forget the PIN on disconnect from the AP. |
| const std::string& pin = current_service_->eap()->pin(); |
| Error unused_error; |
| RpcIdentifier rpcid = |
| FindNetworkRpcidForService(current_service_.get(), &unused_error); |
| if (!pin.empty() && !rpcid.value().empty()) { |
| // We have a PIN configured, so we can provide it back to wpa_supplicant. |
| LOG(INFO) << "Re-supplying PIN parameter to wpa_supplicant."; |
| supplicant_interface_proxy_->NetworkReply( |
| rpcid, WPASupplicant::kEAPRequestedParameterPin, pin); |
| failure = Service::kFailureNone; |
| } |
| } |
| if (failure != Service::kFailureNone) { |
| // Avoid a reporting failure twice by resetting EAP state handler early. |
| eap_state_handler_->Reset(); |
| pending_eap_failure_ = failure; |
| } |
| } |
| |
| void WiFi::PropertiesChangedTask(const KeyValueStore& properties) { |
| // TODO(quiche): Handle changes in other properties (e.g. signal |
| // strength). |
| |
| // Note that order matters here. In particular, we want to process |
| // changes in the current BSS before changes in state. This is so |
| // that we update the state of the correct Endpoint/Service. |
| // Also note that events may occur (briefly) after Stop(), so we need to make |
| // explicit decisions here on what to do when !enabled(). |
| if (enabled() && properties.Contains<RpcIdentifier>( |
| WPASupplicant::kInterfacePropertyCurrentBSS)) { |
| CurrentBSSChanged(properties.Get<RpcIdentifier>( |
| WPASupplicant::kInterfacePropertyCurrentBSS)); |
| } |
| |
| if (properties.Contains<std::string>( |
| WPASupplicant::kInterfacePropertyState)) { |
| StateChanged( |
| properties.Get<std::string>(WPASupplicant::kInterfacePropertyState)); |
| |
| // These properties should only be updated when there is a state change. |
| if (properties.Contains<std::string>( |
| WPASupplicant::kInterfacePropertyCurrentAuthMode)) { |
| CurrentAuthModeChanged(properties.Get<std::string>( |
| WPASupplicant::kInterfacePropertyCurrentAuthMode)); |
| } |
| |
| std::string suffix = GetSuffixFromAuthMode(supplicant_auth_mode_); |
| if (!suffix.empty()) { |
| if (properties.Contains<int32_t>( |
| WPASupplicant::kInterfacePropertyRoamTime)) { |
| // Network.Shill.WiFi.RoamTime.{PSK,FTPSK,EAP,FTEAP} |
| metrics()->SendToUMA( |
| base::StringPrintf("%s.%s", Metrics::kMetricWifiRoamTimePrefix, |
| suffix.c_str()), |
| properties.Get<int32_t>(WPASupplicant::kInterfacePropertyRoamTime), |
| Metrics::kMetricWifiRoamTimeMillisecondsMin, |
| Metrics::kMetricWifiRoamTimeMillisecondsMax, |
| Metrics::kMetricWifiRoamTimeNumBuckets); |
| } |
| |
| if (properties.Contains<bool>( |
| WPASupplicant::kInterfacePropertyRoamComplete)) { |
| // Network.Shill.WiFi.RoamComplete.{PSK,FTPSK,EAP,FTEAP} |
| metrics()->SendEnumToUMA( |
| base::StringPrintf("%s.%s", Metrics::kMetricWifiRoamCompletePrefix, |
| suffix.c_str()), |
| properties.Get<bool>(WPASupplicant::kInterfacePropertyRoamComplete) |
| ? Metrics::kWiFiRoamSuccess |
| : Metrics::kWiFiRoamFailure, |
| Metrics::kWiFiRoamCompleteMax); |
| } |
| |
| if (properties.Contains<int32_t>( |
| WPASupplicant::kInterfacePropertySessionLength)) { |
| // Network.Shill.WiFi.SessionLength.{PSK,FTPSK,EAP,FTEAP} |
| metrics()->SendToUMA( |
| base::StringPrintf("%s.%s", Metrics::kMetricWifiSessionLengthPrefix, |
| suffix.c_str()), |
| properties.Get<int32_t>( |
| WPASupplicant::kInterfacePropertySessionLength), |
| Metrics::kMetricWifiSessionLengthMillisecondsMin, |
| Metrics::kMetricWifiSessionLengthMillisecondsMax, |
| Metrics::kMetricWifiSessionLengthNumBuckets); |
| } |
| } |
| } |
| |
| if (properties.Contains<int32_t>( |
| WPASupplicant::kInterfacePropertyAssocStatusCode)) { |
| AssocStatusChanged(properties.Get<int32_t>( |
| WPASupplicant::kInterfacePropertyAssocStatusCode)); |
| } |
| |
| if (properties.Contains<int32_t>( |
| WPASupplicant::kInterfacePropertyAuthStatusCode)) { |
| AuthStatusChanged(properties.Get<int32_t>( |
| WPASupplicant::kInterfacePropertyAuthStatusCode)); |
| } |
| |
| if (properties.Contains<int32_t>( |
| WPASupplicant::kInterfacePropertyDisconnectReason)) { |
| DisconnectReasonChanged(properties.Get<int32_t>( |
| WPASupplicant::kInterfacePropertyDisconnectReason)); |
| } |
| } |
| |
| std::string WiFi::GetSuffixFromAuthMode(const std::string& auth_mode) const { |
| if (auth_mode == WPASupplicant::kAuthModeWPAPSK || |
| auth_mode == WPASupplicant::kAuthModeWPA2PSK || |
| auth_mode == WPASupplicant::kAuthModeBothPSK) { |
| return Metrics::kMetricWifiPSKSuffix; |
| } else if (auth_mode == WPASupplicant::kAuthModeFTPSK) { |
| return Metrics::kMetricWifiFTPSKSuffix; |
| } else if (auth_mode == WPASupplicant::kAuthModeFTEAP) { |
| return Metrics::kMetricWifiFTEAPSuffix; |
| } else if (base::StartsWith(auth_mode, WPASupplicant::kAuthModeEAPPrefix, |
| base::CompareCase::SENSITIVE)) { |
| return Metrics::kMetricWifiEAPSuffix; |
| } |
| return ""; |
| } |
| |
| void WiFi::ScanDoneTask() { |
| SLOG(this, 2) << __func__ << " need_bss_flush_ " << need_bss_flush_; |
| // Unsets this flag if it was set in InitiateScanInDarkResume since that scan |
| // has completed. |
| manager()->set_suppress_autoconnect(false); |
| if (wake_on_wifi_) { |
| wake_on_wifi_->OnScanCompleted(); |
| } |
| // Post |UpdateScanStateAfterScanDone| so it runs after any pending scan |
| // results have been processed. This allows connections on new BSSes to be |
| // started before we decide whether the scan was fruitful. |
| dispatcher()->PostTask( |
| FROM_HERE, base::Bind(&WiFi::UpdateScanStateAfterScanDone, |
| weak_ptr_factory_while_started_.GetWeakPtr())); |
| if (wake_on_wifi_ && (provider_->NumAutoConnectableServices() < 1) && |
| IsIdle()) { |
| // Ensure we are also idle in case we are in the midst of connecting to |
| // the only service that was available for auto-connect on the previous |
| // scan (which will cause it to show up as unavailable for auto-connect |
| // when we query the WiFiProvider this time). |
| wake_on_wifi_->OnNoAutoConnectableServicesAfterScan( |
| provider_->GetSsidsConfiguredForAutoConnect(), |
| base::Bind(&WiFi::RemoveSupplicantNetworks, |
| weak_ptr_factory_while_started_.GetWeakPtr()), |
| base::Bind(&WiFi::TriggerPassiveScan, |
| weak_ptr_factory_while_started_.GetWeakPtr())); |
| } |
| if (need_bss_flush_) { |
| CHECK(supplicant_interface_proxy_); |
| // Compute |max_age| relative to |resumed_at_|, to account for the |
| // time taken to scan. |
| struct timeval now; |
| uint32_t max_age; |
| time_->GetTimeMonotonic(&now); |
| max_age = kMaxBSSResumeAgeSeconds + (now.tv_sec - resumed_at_.tv_sec); |
| supplicant_interface_proxy_->FlushBSS(max_age); |
| need_bss_flush_ = false; |
| } |
| StartScanTimer(); |
| } |
| |
| void WiFi::ScanFailedTask() { |
| SLOG(this, 2) << __func__; |
| SetScanState(kScanIdle, kScanMethodNone, __func__); |
| } |
| |
| void WiFi::UpdateScanStateAfterScanDone() { |
| if (scan_method_ == kScanMethodFull) { |
| // Only notify the Manager on completion of full scans, since the manager |
| // will replace any cached geolocation info with the BSSes we have right |
| // now. |
| manager()->OnDeviceGeolocationInfoUpdated(this); |
| } |
| if (scan_state_ == kScanBackgroundScanning) { |
| // Going directly to kScanIdle (instead of to kScanFoundNothing) inhibits |
| // some UMA reporting in SetScanState. That's desired -- we don't want |
| // to report background scan results to UMA since the drivers may play |
| // background scans over a longer period in order to not interfere with |
| // traffic. |
| SetScanState(kScanIdle, kScanMethodNone, __func__); |
| } else if (scan_state_ != kScanIdle && IsIdle()) { |
| SetScanState(kScanFoundNothing, scan_method_, __func__); |
| } |
| } |
| |
| void WiFi::GetAndUseInterfaceCapabilities() { |
| KeyValueStore caps; |
| |
| if (!supplicant_interface_proxy_->GetCapabilities(&caps)) |
| LOG(ERROR) << "Failed to obtain interface capabilities"; |
| |
| ConfigureScanSSIDLimit(caps); |
| } |
| |
| void WiFi::ConfigureScanSSIDLimit(const KeyValueStore& caps) { |
| if (caps.Contains<int>(WPASupplicant::kInterfaceCapabilityMaxScanSSID)) { |
| int value = caps.Get<int>(WPASupplicant::kInterfaceCapabilityMaxScanSSID); |
| SLOG(this, 2) << "Obtained MaxScanSSID capability: " << value; |
| max_ssids_per_scan_ = |
| std::min(static_cast<int>(WPASupplicant::kMaxMaxSSIDsPerScan), |
| std::max(0, value)); |
| if (max_ssids_per_scan_ != value) |
| SLOG(this, 2) << "MaxScanSSID trimmed to: " << max_ssids_per_scan_; |
| } else { |
| LOG(WARNING) << "Missing MaxScanSSID capability, using default value: " |
| << WPASupplicant::kDefaultMaxSSIDsPerScan; |
| max_ssids_per_scan_ = WPASupplicant::kDefaultMaxSSIDsPerScan; |
| } |
| |
| if (max_ssids_per_scan_ <= 1) |
| LOG(WARNING) << "MaxScanSSID <= 1, scans will alternate between single " |
| << "hidden SSID and broadcast scan."; |
| } |
| |
| void WiFi::ScanTask() { |
| SLOG(this, 2) << "WiFi " << link_name() << " scan requested."; |
| if (!enabled()) { |
| SLOG(this, 2) << "Ignoring scan request while device is not enabled."; |
| SetScanState(kScanIdle, kScanMethodNone, __func__); // Probably redundant. |
| return; |
| } |
| if (!supplicant_present_ || !supplicant_interface_proxy_.get()) { |
| SLOG(this, 2) << "Ignoring scan request while supplicant is not present."; |
| SetScanState(kScanIdle, kScanMethodNone, __func__); |
| return; |
| } |
| if ((pending_service_.get() && pending_service_->IsConnecting()) || |
| (current_service_.get() && current_service_->IsConnecting())) { |
| SLOG(this, 2) << "Ignoring scan request while connecting to an AP."; |
| return; |
| } |
| KeyValueStore scan_args; |
| scan_args.Set<std::string>(WPASupplicant::kPropertyScanType, |
| WPASupplicant::kScanTypeActive); |
| |
| ByteArrays hidden_ssids = provider_->GetHiddenSSIDList(); |
| if (!hidden_ssids.empty()) { |
| // Determine how many hidden ssids to pass in, based on max_ssids_per_scan_ |
| if (max_ssids_per_scan_ > 1) { |
| // The empty '' "broadcast SSID" counts toward the max scan limit, so the |
| // capability needs to be >= 2 to have at least 1 hidden SSID. |
| if (hidden_ssids.size() >= static_cast<size_t>(max_ssids_per_scan_)) { |
| // TODO(b/172220260): Devise a better method for time-sharing with SSIDs |
| // that do not fit in |
| hidden_ssids.erase(hidden_ssids.begin() + max_ssids_per_scan_ - 1, |
| hidden_ssids.end()); |
| } |
| // Add Broadcast SSID, signified by an empty ByteArray. If we specify |
| // SSIDs to wpa_supplicant, we need to explicitly specify the default |
| // behavior of doing a broadcast probe. |
| hidden_ssids.push_back(ByteArray()); |
| |
| } else if (max_ssids_per_scan_ == 1) { |
| // Handle case where driver can only accept one scan_ssid at a time |
| AlternateSingleScans(&hidden_ssids); |
| } else { // if max_ssids_per_scan_ < 1 |
| hidden_ssids.resize(0); |
| } |
| |
| if (!hidden_ssids.empty()) { |
| scan_args.Set<ByteArrays>(WPASupplicant::kPropertyScanSSIDs, |
| hidden_ssids); |
| } |
| } |
| scan_args.Set<bool>(WPASupplicant::kPropertyScanAllowRoam, |
| manager()->scan_allow_roam()); |
| |
| if (!supplicant_interface_proxy_->Scan(scan_args)) { |
| // A scan may fail if, for example, the wpa_supplicant vanishing |
| // notification is posted after this task has already started running. |
| LOG(WARNING) << "Scan failed"; |
| return; |
| } |
| |
| // Only set the scan state/method if we are starting a full scan from |
| // scratch. |
| if (scan_state_ != kScanScanning) { |
| SetScanState(IsIdle() ? kScanScanning : kScanBackgroundScanning, |
| kScanMethodFull, __func__); |
| } |
| } |
| |
| void WiFi::AlternateSingleScans(ByteArrays* hidden_ssids) { |
| // Ensure at least one hidden SSID is probed. |
| if (broadcast_probe_was_skipped_) { |
| SLOG(this, 2) << "Doing broadcast probe instead of directed probe."; |
| hidden_ssids->resize(0); |
| } else { |
| SLOG(this, 2) << "Doing directed probe instead of broadcast probe."; |
| hidden_ssids->resize(1); |
| } |
| broadcast_probe_was_skipped_ = !broadcast_probe_was_skipped_; |
| } |
| |
| std::string WiFi::GetServiceLeaseName(const WiFiService& service) { |
| return service.GetStorageIdentifier(); |
| } |
| |
| const WiFiEndpointConstRefPtr WiFi::GetCurrentEndpoint() const { |
| EndpointMap::const_iterator endpoint_it = |
| endpoint_by_rpcid_.find(supplicant_bss_); |
| if (endpoint_it == endpoint_by_rpcid_.end()) { |
| return nullptr; |
| } |
| |
| return endpoint_it->second.get(); |
| } |
| |
| void WiFi::DestroyServiceLease(const WiFiService& service) { |
| DestroyIPConfigLease(GetServiceLeaseName(service)); |
| } |
| |
| void WiFi::StateChanged(const std::string& new_state) { |
| const std::string old_state = supplicant_state_; |
| supplicant_state_ = new_state; |
| LOG(INFO) << "WiFi " << link_name() << " " << __func__ << " " << old_state |
| << " -> " << new_state; |
| |
| if (old_state == WPASupplicant::kInterfaceStateDisconnected && |
| new_state != WPASupplicant::kInterfaceStateDisconnected) { |
| // The state has been changed from disconnect to something else, clearing |
| // out disconnect reason to avoid confusion about future disconnects. |
| SLOG(this, 3) << "WiFi clearing DisconnectReason for " << link_name(); |
| supplicant_disconnect_reason_ = IEEE_80211::kReasonCodeInvalid; |
| } |
| |
| // Identify the service to which the state change applies. If |
| // |pending_service_| is non-NULL, then the state change applies to |
| // |pending_service_|. Otherwise, it applies to |current_service_|. |
| // |
| // This policy is driven by the fact that the |pending_service_| |
| // doesn't become the |current_service_| until wpa_supplicant |
| // reports a CurrentBSS change to the |pending_service_|. And the |
| // CurrentBSS change won't be reported until the |pending_service_| |
| // reaches the WPASupplicant::kInterfaceStateCompleted state. |
| WiFiService* affected_service = |
| pending_service_.get() ? pending_service_.get() : current_service_.get(); |
| if (!affected_service) { |
| SLOG(this, 2) << "WiFi " << link_name() << " " << __func__ |
| << " with no service"; |
| return; |
| } |
| |
| if (new_state == WPASupplicant::kInterfaceStateCompleted) { |
| if (affected_service->IsConnected()) { |
| StopReconnectTimer(); |
| if (is_roaming_in_progress_) { |
| // This means wpa_supplicant completed a roam without an intervening |
| // disconnect. We should renew our DHCP lease just in case the new |
| // AP is on a different subnet than where we started. |
| // TODO(matthewmwang): Handle the IPv6 roam case. |
| is_roaming_in_progress_ = false; |
| if (ipconfig()) { |
| LOG(INFO) << link_name() << " renewing L3 configuration after roam."; |
| ipconfig()->RenewIP(); |
| affected_service->SetRoamState(Service::kRoamStateConfiguring); |
| } |
| } else if (affected_service->is_rekey_in_progress()) { |
| affected_service->SetIsRekeyInProgress(false); |
| LOG(INFO) << link_name() |
| << " EAP re-key complete. No need to renew L3 configuration."; |
| } |
| } else if (has_already_completed_) { |
| LOG(INFO) << link_name() << " L3 configuration already started."; |
| } else { |
| if (AcquireIPConfigWithLeaseName( |
| GetServiceLeaseName(*affected_service))) { |
| LOG(INFO) << link_name() << " is up; started L3 configuration."; |
| affected_service->SetState(Service::kStateConfiguring); |
| if (affected_service->IsSecurityMatch(kSecurityWep)) { |
| // With the overwhelming majority of WEP networks, we cannot assume |
| // our credentials are correct just because we have successfully |
| // connected. It is more useful to track received data as the L3 |
| // configuration proceeds to see if we can decrypt anything. |
| receive_byte_count_at_connect_ = GetReceiveByteCount(); |
| } else { |
| affected_service->ResetSuspectedCredentialFailures(); |
| } |
| } else { |
| LOG(ERROR) << "Unable to acquire DHCP config."; |
| } |
| } |
| has_already_completed_ = true; |
| } else if (new_state == WPASupplicant::kInterfaceStateAuthenticating || |
| new_state == WPASupplicant::kInterfaceStateAssociating || |
| new_state == WPASupplicant::kInterfaceStateAssociated || |
| new_state == WPASupplicant::kInterfaceState4WayHandshake || |
| new_state == WPASupplicant::kInterfaceStateGroupHandshake) { |
| if (new_state == WPASupplicant::kInterfaceStateAssociating) { |
| // Ensure auth status is kept up-to-date |
| supplicant_auth_status_ = IEEE_80211::kStatusCodeSuccessful; |
| } else if (new_state == WPASupplicant::kInterfaceStateAssociated) { |
| // Supplicant does not indicate successful association in assoc status |
| // messages, but we know at this point that 802.11 association succeeded |
| supplicant_assoc_status_ = IEEE_80211::kStatusCodeSuccessful; |
| } |
| |
| if (is_roaming_in_progress_) { |
| // Instead of transitioning into the associating state and potentially |
| // reordering the service list, set the roam state to keep track of the |
| // actual state. |
| affected_service->SetRoamState(Service::kRoamStateAssociating); |
| } else if (!affected_service->is_rekey_in_progress()) { |
| // Ignore transitions into these states when roaming is in progress, to |
| // avoid bothering the user when roaming, or re-keying. |
| if (old_state == WPASupplicant::kInterfaceStateCompleted) { |
| // Shill gets EAP events when a re-key happens in an 802.1X network, but |
| // nothing when it happens in a PSK network. Unless roaming is in |
| // progress, we assume supplicant state transitions from completed to an |
| // auth/assoc state are a result of a re-key. |
| affected_service->SetIsRekeyInProgress(true); |
| } else { |
| affected_service->SetState(Service::kStateAssociating); |
| } |
| } |
| // TODO(quiche): On backwards transitions, we should probably set |
| // a timeout for getting back into the completed state. At present, |
| // we depend on wpa_supplicant eventually reporting that CurrentBSS |
| // has changed. But there may be cases where that signal is not sent. |
| // (crbug.com/206208) |
| } else if (new_state == WPASupplicant::kInterfaceStateDisconnected && |
| affected_service == current_service_ && |
| affected_service->IsConnected()) { |
| // This means that wpa_supplicant failed in a re-connect attempt, but |
| // may still be reconnecting. Give wpa_supplicant a limited amount of |
| // time to transition out this condition by either connecting or changing |
| // CurrentBSS. |
| StartReconnectTimer(); |
| } else { |
| // Other transitions do not affect Service state. |
| // |
| // Note in particular that we ignore a State change into |
| // kInterfaceStateDisconnected, in favor of observing the corresponding |
| // change in CurrentBSS. |
| } |
| } |
| |
| bool WiFi::SuspectCredentials(WiFiServiceRefPtr service, |
| Service::ConnectFailure* failure) const { |
| if (service->IsSecurityMatch(kSecurityPsk)) { |
| if (supplicant_state_ == WPASupplicant::kInterfaceState4WayHandshake && |
| service->AddSuspectedCredentialFailure()) { |
| if (failure) { |
| *failure = Service::kFailureBadPassphrase; |
| } |
| return true; |
| } |
| } else if (service->IsSecurityMatch(kSecurity8021x)) { |
| if (eap_state_handler_->is_eap_in_progress() && |
| service->AddSuspectedCredentialFailure()) { |
| if (failure) { |
| *failure = Service::kFailureEAPAuthentication; |
| } |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| // static |
| bool WiFi::SanitizeSSID(std::string* ssid) { |
| CHECK(ssid); |
| |
| size_t ssid_len = ssid->length(); |
| size_t i; |
| bool changed = false; |
| |
| for (i = 0; i < ssid_len; ++i) { |
| if (!IsPrintableAsciiChar((*ssid)[i])) { |
| (*ssid)[i] = '?'; |
| changed = true; |
| } |
| } |
| |
| return changed; |
| } |
| |
| // static |
| std::string WiFi::LogSSID(const std::string& ssid) { |
| std::string out; |
| for (const auto& chr : ssid) { |
| // Replace '[' and ']' (in addition to non-printable characters) so that |
| // it's easy to match the right substring through a non-greedy regex. |
| if (chr == '[' || chr == ']' || !IsPrintableAsciiChar(chr)) { |
| base::StringAppendF(&out, "\\x%02x", chr); |
| } else { |
| out += chr; |
| } |
| } |
| return base::StringPrintf("[SSID=%s]", out.c_str()); |
| } |
| |
| void WiFi::OnUnreliableLink() { |
| SLOG(this, 2) << "Device " << link_name() << ": Link is unreliable."; |
| selected_service()->set_unreliable(true); |
| reliable_link_callback_.Cancel(); |
| metrics()->NotifyUnreliableLinkSignalStrength(Technology::kWifi, |
| selected_service()->strength()); |
| } |
| |
| void WiFi::OnReliableLink() { |
| SLOG(this, 2) << "Device " << link_name() << ": Link is reliable."; |
| selected_service()->set_unreliable(false); |
| } |
| |
| void WiFi::OnLinkMonitorFailure(IPAddress::Family family) { |
| SLOG(this, 2) << "Device " << link_name() |
| << ": Link Monitor indicates failure."; |
| |
| // Determine the reliability of the link. |
| time_t now; |
| time_->GetSecondsBoottime(&now); |
| if (last_link_monitor_failed_time_ != 0 && |
| now - last_link_monitor_failed_time_ < |
| kLinkUnreliableThreshold.InSeconds()) { |
| OnUnreliableLink(); |
| } |
| last_link_monitor_failed_time_ = now; |
| |
| // If we have never found the gateway, let's be conservative and not |
| // do anything, in case this network topology does not have a gateway. |
| if ((family == IPAddress::kFamilyIPv4 && !ipv4_gateway_found_) || |
| (family == IPAddress::kFamilyIPv6 && !ipv6_gateway_found_)) { |
| LOG(INFO) << "In " << __func__ << "(): " |
| << "Skipping reassociate since gateway was never found."; |
| return; |
| } |
| |
| if (!supplicant_present_) { |
| LOG(ERROR) << "In " << __func__ << "(): " |
| << "wpa_supplicant is not present. Cannot reassociate."; |
| return; |
| } |
| |
| if (!current_service_) { |
| LOG(INFO) << "No current service, skipping reassociate attempt."; |
| return; |
| } |
| |
| // Skip reassociate attempt if service is not reliable, meaning multiple link |
| // failures in short period of time. |
| if (current_service_->unreliable()) { |
| LOG(INFO) << "Current service is unreliable, skipping reassociate attempt."; |
| return; |
| } |
| |
| // This will force a transition out of connected, if we are actually |
| // connected. |
| if (!supplicant_interface_proxy_->Reattach()) { |
| LOG(ERROR) << "In " << __func__ << "(): failed to call Reattach()."; |
| return; |
| } |
| |
| // If we don't eventually get a transition back into a connected state, |
| // there is something wrong. |
| StartReconnectTimer(); |
| LOG(INFO) << "In " << __func__ << "(): Called Reattach()."; |
| } |
| |
| bool WiFi::ShouldUseArpGateway() const { |
| return !IsUsingStaticIP(); |
| } |
| |
| void WiFi::DisassociateFromService(const WiFiServiceRefPtr& service) { |
| SLOG(this, 2) << "In " << __func__ << " for service: " << service->log_name(); |
| DisconnectFromIfActive(service.get()); |
| if (service == selected_service()) { |
| DropConnection(); |
| } |
| Error unused_error; |
| RemoveNetworkForService(service.get(), &unused_error); |
| } |
| |
| std::vector<GeolocationInfo> WiFi::GetGeolocationObjects() const { |
| std::vector<GeolocationInfo> objects; |
| for (const auto& endpoint_entry : endpoint_by_rpcid_) { |
| GeolocationInfo geoinfo; |
| const WiFiEndpointRefPtr& endpoint = endpoint_entry.second; |
| geoinfo[kGeoMacAddressProperty] = endpoint->bssid_string(); |
| geoinfo[kGeoSignalStrengthProperty] = |
| base::StringPrintf("%d", endpoint->signal_strength()); |
| geoinfo[kGeoChannelProperty] = base::StringPrintf( |
| "%d", Metrics::WiFiFrequencyToChannel(endpoint->frequency())); |
| AddLastSeenTime(&geoinfo, endpoint->last_seen()); |
| objects.push_back(geoinfo); |
| } |
| return objects; |
| } |
| |
| void WiFi::HelpRegisterDerivedInt32(PropertyStore* store, |
| const std::string& name, |
| int32_t (WiFi::*get)(Error* error), |
| bool (WiFi::*set)(const int32_t& value, |
| Error* error)) { |
| store->RegisterDerivedInt32( |
| name, Int32Accessor(new CustomAccessor<WiFi, int32_t>(this, get, set))); |
| } |
| |
| void WiFi::HelpRegisterDerivedUint16(PropertyStore* store, |
| const std::string& name, |
| uint16_t (WiFi::*get)(Error* error), |
| bool (WiFi::*set)(const uint16_t& value, |
| Error* error)) { |
| store->RegisterDerivedUint16( |
| name, Uint16Accessor(new CustomAccessor<WiFi, uint16_t>(this, get, set))); |
| } |
| |
| void WiFi::HelpRegisterDerivedBool(PropertyStore* store, |
| const std::string& name, |
| bool (WiFi::*get)(Error* error), |
| bool (WiFi::*set)(const bool& value, |
| Error* error)) { |
| store->RegisterDerivedBool( |
| name, BoolAccessor(new CustomAccessor<WiFi, bool>(this, get, set))); |
| } |
| |
| void WiFi::HelpRegisterConstDerivedBool(PropertyStore* store, |
| const std::string& name, |
| bool (WiFi::*get)(Error* error)) { |
| store->RegisterDerivedBool( |
| name, BoolAccessor(new CustomAccessor<WiFi, bool>(this, get, nullptr))); |
| } |
| |
| void WiFi::HelpRegisterConstDerivedUint16s(PropertyStore* store, |
| const std::string& name, |
| Uint16s (WiFi::*get)(Error* error)) { |
| store->RegisterDerivedUint16s( |
| name, |
| Uint16sAccessor(new CustomAccessor<WiFi, Uint16s>(this, get, nullptr))); |
| } |
| |
| void WiFi::OnBeforeSuspend(const ResultCallback& callback) { |
| if (!enabled()) { |
| callback.Run(Error(Error::kSuccess)); |
| return; |
| } |
| LOG(INFO) << __func__ << ": " |
| << (IsConnectedToCurrentService() ? "connected" : "not connected"); |
| StopScanTimer(); |
| supplicant_process_proxy()->ExpectDisconnect(); |
| if (!wake_on_wifi_) { |
| callback.Run(Error(Error::kSuccess)); |
| return; |
| } |
| uint32_t time_to_next_lease_renewal; |
| bool have_dhcp_lease = |
| TimeToNextDHCPLeaseRenewal(&time_to_next_lease_renewal); |
| wake_on_wifi_->OnBeforeSuspend( |
| IsConnectedToCurrentService(), |
| provider_->GetSsidsConfiguredForAutoConnect(), callback, |
| base::Bind(&Device::RenewDHCPLease, |
| weak_ptr_factory_while_started_.GetWeakPtr(), false, nullptr), |
| base::Bind(&WiFi::RemoveSupplicantNetworks, |
| weak_ptr_factory_while_started_.GetWeakPtr()), |
| have_dhcp_lease, time_to_next_lease_renewal); |
| } |
| |
| void WiFi::OnDarkResume(const ResultCallback& callback) { |
| if (!enabled()) { |
| callback.Run(Error(Error::kSuccess)); |
| return; |
| } |
| LOG(INFO) << __func__ << ": " |
| << (IsConnectedToCurrentService() ? "connected" : "not connected"); |
| StopScanTimer(); |
| if (!wake_on_wifi_) { |
| callback.Run(Error(Error::kSuccess)); |
| return; |
| } |
| wake_on_wifi_->OnDarkResume( |
| IsConnectedToCurrentService(), |
| provider_->GetSsidsConfiguredForAutoConnect(), callback, |
| base::Bind(&Device::RenewDHCPLease, |
| weak_ptr_factory_while_started_.GetWeakPtr(), false, nullptr), |
| base::Bind(&WiFi::InitiateScanInDarkResume, |
| weak_ptr_factory_while_started_.GetWeakPtr()), |
| base::Bind(&WiFi::RemoveSupplicantNetworks, |
| weak_ptr_factory_while_started_.GetWeakPtr())); |
| } |
| |
| void WiFi::OnAfterResume() { |
| LOG(INFO) << __func__ << ": " |
| << (IsConnectedToCurrentService() ? "connected" : "not connected") |
| << ", " << (enabled() ? "enabled" : "disabled"); |
| Device::OnAfterResume(); // May refresh ipconfig_ |
| // We let the Device class do its thing, but we did nothing in |
| // OnBeforeSuspend(), so why undo anything now? |
| if (!enabled()) { |
| return; |
| } |
| dispatcher()->PostDelayedTask( |
| FROM_HERE, |
| base::Bind(&WiFi::ReportConnectedToServiceAfterWake, |
| weak_ptr_factory_while_started_.GetWeakPtr()), |
| kPostWakeConnectivityReportDelayMilliseconds); |
| if (wake_on_wifi_) { |
| wake_on_wifi_->OnAfterResume(); |
| } |
| |
| // We want to flush the BSS cache, but we don't want to conflict |
| // with an active connection attempt. So record the need to flush, |
| // and take care of flushing when the next scan completes. |
| // |
| // Note that supplicant will automatically expire old cache |
| // entries (after, e.g., a BSS is not found in two consecutive |
| // scans). However, our explicit flush accelerates re-association |
| // in cases where a BSS disappeared while we were asleep. (See, |
| // e.g. WiFiRoaming.005SuspendRoam.) |
| time_->GetTimeMonotonic(&resumed_at_); |
| need_bss_flush_ = true; |
| |
| if (!IsConnectedToCurrentService()) { |
| InitiateScan(); |
| } |
| |
| // Since we stopped the scan timer before suspending, start it again here. |
| StartScanTimer(); |
| |
| // Resume from sleep, could be in different location now. |
| // Ignore previous link monitor failures. |
| if (selected_service()) { |
| selected_service()->set_unreliable(false); |
| reliable_link_callback_.Cancel(); |
| } |
| last_link_monitor_failed_time_ = 0; |
| } |
| |
| void WiFi::AbortScan() { |
| SetScanState(kScanIdle, kScanMethodNone, __func__); |
| } |
| |
| void WiFi::InitiateScan() { |
| LOG(INFO) << __func__; |
| // Abort any current scan (at the shill-level; let any request that's |
| // already gone out finish) since we don't know when it started. |
| AbortScan(); |
| |
| if (IsIdle()) { |
| // Not scanning/connecting/connected, so let's get things rolling. |
| Scan(nullptr, __func__); |
| RestartFastScanAttempts(); |
| } else { |
| SLOG(this, 1) << __func__ |
| << " skipping scan, already connecting or connected."; |
| } |
| } |
| |
| void WiFi::InitiateScanInDarkResume(const FreqSet& freqs) { |
| LOG(INFO) << __func__; |
| AbortScan(); |
| if (!IsIdle()) { |
| SLOG(this, 1) << __func__ |
| << " skipping scan, already connecting or connected."; |
| return; |
| } |
| |
| CHECK(supplicant_interface_proxy_); |
| // Force complete flush of BSS cache since we want WPA supplicant and shill to |
| // have an accurate view of what endpoints are available in dark resume. This |
| // prevents either from performing incorrect actions that can prolong dark |
| // resume (e.g. attempting to auto-connect to a WiFi service whose endpoint |
| // disappeared before the dark resume). |
| if (!supplicant_interface_proxy_->FlushBSS(0)) { |
| LOG(WARNING) << __func__ << ": Failed to flush wpa_supplicant BSS cache"; |
| } |
| // Suppress any autoconnect attempts until this scan is done and endpoints |
| // are updated. |
| manager()->set_suppress_autoconnect(true); |
| |
| TriggerPassiveScan(freqs); |
| } |
| |
| void WiFi::TriggerPassiveScan(const FreqSet& freqs) { |
| LOG(INFO) << __func__; |
| TriggerScanMessage trigger_scan; |
| trigger_scan.attributes()->SetU32AttributeValue(NL80211_ATTR_IFINDEX, |
| interface_index()); |
| if (!freqs.empty()) { |
| SLOG(this, 3) << __func__ << ": " |
| << "Scanning on specific channels"; |
| trigger_scan.attributes()->CreateNl80211Attribute( |
| NL80211_ATTR_SCAN_FREQUENCIES, NetlinkMessage::MessageContext()); |
| |
| AttributeListRefPtr frequency_list; |
| if (!trigger_scan.attributes()->GetNestedAttributeList( |
| NL80211_ATTR_SCAN_FREQUENCIES, &frequency_list) || |
| !frequency_list) { |
| LOG(ERROR) << __func__ << ": " |
| << "Couldn't get NL80211_ATTR_SCAN_FREQUENCIES"; |
| } |
| trigger_scan.attributes()->SetNestedAttributeHasAValue( |
| NL80211_ATTR_SCAN_FREQUENCIES); |
| |
| std::string attribute_name; |
| int i = 0; |
| for (uint32_t freq : freqs) { |
| SLOG(this, 7) << __func__ << ": " |
| << "Frequency-" << i << ": " << freq; |
| attribute_name = base::StringPrintf("Frequency-%d", i); |
| frequency_list->CreateU32Attribute(i, attribute_name.c_str()); |
| frequency_list->SetU32AttributeValue(i, freq); |
| ++i; |
| } |
| } |
| |
| netlink_manager_->SendNl80211Message( |
| &trigger_scan, |
| base::Bind(&WiFi::OnTriggerPassiveScanResponse, |
| weak_ptr_factory_while_started_.GetWeakPtr()), |
| base::Bind(&NetlinkManager::OnAckDoNothing), |
| base::Bind(&NetlinkManager::OnNetlinkMessageError)); |
| } |
| |
| void WiFi::OnConnected() { |
| Device::OnConnected(); |
| if (current_service_ && current_service_->IsSecurityMatch(kSecurityWep)) { |
| // With a WEP network, we are now reasonably certain the credentials are |
| // correct, whereas with other network types we were able to determine |
| // this earlier when the association process succeeded. |
| current_service_->ResetSuspectedCredentialFailures(); |
| } |
| RequestStationInfo(); |
| |
| // Clears the link monitor states for the previous connection. |
| ipv4_gateway_found_ = false; |
| ipv6_gateway_found_ = false; |
| |
| if (selected_service()->unreliable()) { |
| // Post a delayed task to reset link back to reliable if no link failure is |
| // detected in the next 5 minutes. |
| reliable_link_callback_.Reset( |
| base::Bind(&WiFi::OnReliableLink, base::Unretained(this))); |
| dispatcher()->PostDelayedTask(FROM_HERE, reliable_link_callback_.callback(), |
| kLinkUnreliableResetTimeout.InMilliseconds()); |
| } |
| } |
| |
| void WiFi::OnSelectedServiceChanged(const ServiceRefPtr& old_service) { |
| // Reset link status for the previously selected service. |
| if (old_service) { |
| old_service->set_unreliable(false); |
| } |
| reliable_link_callback_.Cancel(); |
| last_link_monitor_failed_time_ = 0; |
| } |
| |
| void WiFi::OnIPConfigFailure() { |
| if (!current_service_) { |
| LOG(ERROR) << "WiFi " << link_name() << " " << __func__ |
| << " with no current service."; |
| return; |
| } |
| if (current_service_->IsSecurityMatch(kSecurityWep) && |
| GetReceiveByteCount() == receive_byte_count_at_connect_ && |
| current_service_->AddSuspectedCredentialFailure()) { |
| // If we've connected to a WEP network and haven't successfully |
| // decrypted any bytes at all during the configuration process, |
| // it is fair to suspect that our credentials to this network |
| // may not be correct. |
| Error error; |
| current_service_->DisconnectWithFailure(Service::kFailureBadPassphrase, |
| &error, __func__); |
| return; |
| } |
| |
| Device::OnIPConfigFailure(); |
| } |
| |
| void WiFi::RestartFastScanAttempts() { |
| if (!enabled()) { |
| SLOG(this, 2) << "Skpping fast scan attempts while not enabled."; |
| return; |
| } |
| fast_scans_remaining_ = kNumFastScanAttempts; |
| StartScanTimer(); |
| } |
| |
| void WiFi::StartScanTimer() { |
| SLOG(this, 2) << __func__; |
| if (scan_interval_seconds_ == 0) { |
| StopScanTimer(); |
| return; |
| } |
| scan_timer_callback_.Reset(base::Bind( |
| &WiFi::ScanTimerHandler, weak_ptr_factory_while_started_.GetWeakPtr())); |
| // Repeat the first few scans after disconnect relatively quickly so we |
| // have reasonable trust that no APs we are looking for are present. |
| size_t wait_time_milliseconds = fast_scans_remaining_ > 0 |
| ? kFastScanIntervalSeconds * 1000 |
| : scan_interval_seconds_ * 1000; |
| dispatcher()->PostDelayedTask(FROM_HERE, scan_timer_callback_.callback(), |
| wait_time_milliseconds); |
| SLOG(this, 5) << "Next scan scheduled for " << wait_time_milliseconds << "ms"; |
| } |
| |
| void WiFi::StopScanTimer() { |
| SLOG(this, 2) << __func__; |
| scan_timer_callback_.Cancel(); |
| } |
| |
| void WiFi::ScanTimerHandler() { |
| SLOG(this, 2) << "WiFi Device " << link_name() << ": " << __func__; |
| if (manager()->IsSuspending()) { |
| SLOG(this, 5) << "Not scanning: still in suspend"; |
| return; |
| } |
| if (scan_state_ == kScanIdle && IsIdle()) { |
| Scan(nullptr, __func__); |
| if (fast_scans_remaining_ > 0) { |
| --fast_scans_remaining_; |
| } |
| } else { |
| if (scan_state_ != kScanIdle) { |
| SLOG(this, 5) << "Skipping scan: scan_state_ is " << scan_state_; |
| } |
| if (current_service_) { |
| SLOG(this, 5) << "Skipping scan: current_service_ is service " |
| << current_service_->log_name(); |
| } |
| if (pending_service_) { |
| SLOG(this, 5) << "Skipping scan: pending_service_ is service" |
| << pending_service_->log_name(); |
| } |
| } |
| StartScanTimer(); |
| } |
| |
| void WiFi::StartPendingTimer() { |
| pending_timeout_callback_.Reset( |
| base::Bind(&WiFi::PendingTimeoutHandler, |
| weak_ptr_factory_while_started_.GetWeakPtr())); |
| dispatcher()->PostDelayedTask(FROM_HERE, pending_timeout_callback_.callback(), |
| kPendingTimeoutSeconds * 1000); |
| } |
| |
| void WiFi::StopPendingTimer() { |
| SLOG(this, 2) << "WiFi Device " << link_name() << ": " << __func__; |
| pending_timeout_callback_.Cancel(); |
| } |
| |
| void WiFi::SetPendingService(const WiFiServiceRefPtr& service) { |
| SLOG(this, 2) << "WiFi " << link_name() << " setting pending service to " |
| << (service ? service->log_name() : "<none>"); |
| if (service) { |
| SetScanState(kScanConnecting, scan_method_, __func__); |
| service->SetState(Service::kStateAssociating); |
| StartPendingTimer(); |
| } else { |
| // SetPendingService(nullptr) is called in the following cases: |
| // a) |ConnectTo|->|DisconnectFrom|. Connecting to a service, disconnect |
| // the old service (scan_state_ == kScanTransitionToConnecting). No |
| // state transition is needed here. |
| // b) |HandleRoam|. Connected to a service, it's no longer pending |
| // (scan_state_ == kScanIdle). No state transition is needed here. |
| // c) |DisconnectFrom| and |HandleDisconnect|. Disconnected/disconnecting |
| // from a service not during a scan (scan_state_ == kScanIdle). No |
| // state transition is needed here. |
| // d) |DisconnectFrom| and |HandleDisconnect|. Disconnected/disconnecting |
| // from a service during a scan (scan_state_ == kScanScanning or |
| // kScanConnecting). This is an odd case -- let's discard any |
| // statistics we're gathering by transitioning directly into kScanIdle. |
| if (scan_state_ == kScanScanning || |
| scan_state_ == kScanBackgroundScanning || |
| scan_state_ == kScanConnecting) { |
| SetScanState(kScanIdle, kScanMethodNone, __func__); |
| } |
| if (pending_service_) { |
| StopPendingTimer(); |
| } |
| } |
| pending_service_ = service; |
| } |
| |
| void WiFi::PendingTimeoutHandler() { |
| Error unused_error; |
| LOG(INFO) << "WiFi Device " << link_name() << ": " << __func__; |
| CHECK(pending_service_); |
| SetScanState(kScanFoundNothing, scan_method_, __func__); |
| WiFiServiceRefPtr pending_service = pending_service_; |
| |
| SLOG(this, 4) << "Supplicant authentication status: " |
| << supplicant_auth_status_; |
| SLOG(this, 4) << "Supplicant association status: " |
| << supplicant_assoc_status_; |
| |
| Service::ConnectFailure failure = ExamineStatusCodes(); |
| if (failure == Service::kFailureUnknown && pending_service_ && |
| pending_service_->SignalLevel() <= disconnect_threshold_dbm_ && |
| pending_service_->SignalLevel() != kDefaultDisconnectDbm) { |
| failure = Service::kFailureOutOfRange; |
| } |
| pending_service_->DisconnectWithFailure(failure, &unused_error, __func__); |
| |
| // A hidden service may have no endpoints, since wpa_supplicant |
| // failed to attain a CurrentBSS. If so, the service has no |
| // reference to |this| device and cannot call WiFi::DisconnectFrom() |
| // to reset pending_service_. In this case, we must perform the |
| // disconnect here ourselves. |
| if (pending_service_) { |
| CHECK(!pending_service_->HasEndpoints()); |
| LOG(INFO) << "Hidden service was not found."; |
| DisconnectFrom(pending_service_.get()); |
| } |
| |
| // DisconnectWithFailure will leave the pending service's state in failure |
| // state. Reset its state back to idle, to allow it to be connectable again. |
| pending_service->SetState(Service::kStateIdle); |
| } |
| |
| void WiFi::StartReconnectTimer() { |
| if (!reconnect_timeout_callback_.IsCancelled()) { |
| LOG(INFO) << "WiFi Device " << link_name() << ": " << __func__ |
| << ": reconnect timer already running."; |
| return; |
| } |
| LOG(INFO) << "WiFi Device " << link_name() << ": " << __func__; |
| reconnect_timeout_callback_.Reset( |
| base::Bind(&WiFi::ReconnectTimeoutHandler, |
| weak_ptr_factory_while_started_.GetWeakPtr())); |
| dispatcher()->PostDelayedTask(FROM_HERE, |
| reconnect_timeout_callback_.callback(), |
| kReconnectTimeoutSeconds * 1000); |
| } |
| |
| void WiFi::StopReconnectTimer() { |
| SLOG(this, 2) << "WiFi Device " << link_name() << ": " << __func__; |
| reconnect_timeout_callback_.Cancel(); |
| } |
| |
| void WiFi::ReconnectTimeoutHandler() { |
| LOG(INFO) << "WiFi Device " << link_name() << ": " << __func__; |
| reconnect_timeout_callback_.Cancel(); |
| CHECK(current_service_); |
| current_service_->SetFailure(Service::kFailureConnect); |
| DisconnectFrom(current_service_.get()); |
| } |
| |
| void WiFi::OnSupplicantPresence(bool present) { |
| LOG(INFO) << "WPA supplicant presence changed: " << present; |
| |
| if (present) { |
| if (supplicant_present_) { |
| // Restart the WiFi device if it's started already. This will reset the |
| // state and connect the device to the new WPA supplicant instance. |
| if (enabled()) { |
| Restart(); |
| } |
| return; |
| } |
| supplicant_present_ = true; |
| ConnectToSupplicant(); |
| return; |
| } |
| |
| if (!supplicant_present_) { |
| return; |
| } |
| supplicant_present_ = false; |
| // Restart the WiFi device if it's started already. This will effectively |
| // suspend the device until the WPA supplicant reappears. |
| if (enabled()) { |
| Restart(); |
| } |
| } |
| |
| void WiFi::OnWiFiDebugScopeChanged(bool enabled) { |
| SLOG(this, 2) << "WiFi debug scope changed; enable is now " << enabled; |
| if (!Device::enabled() || !supplicant_present_) { |
| SLOG(this, 2) << "Supplicant process proxy not connected."; |
| return; |
| } |
| std::string current_level; |
| if (!supplicant_process_proxy()->GetDebugLevel(¤t_level)) { |
| LOG(ERROR) << __func__ << ": Failed to get wpa_supplicant debug level."; |
| return; |
| } |
| |
| if (current_level != WPASupplicant::kDebugLevelInfo && |
| current_level != WPASupplicant::kDebugLevelDebug) { |
| SLOG(this, 2) << "WiFi debug level is currently " << current_level |
| << "; assuming that it is being controlled elsewhere."; |
| return; |
| } |
| std::string new_level = enabled ? WPASupplicant::kDebugLevelDebug |
| : WPASupplicant::kDebugLevelInfo; |
| |
| if (new_level == current_level) { |
| SLOG(this, 2) << "WiFi debug level is already the desired level " |
| << current_level; |
| return; |
| } |
| |
| if (!supplicant_process_proxy()->SetDebugLevel(new_level)) { |
| LOG(ERROR) << __func__ << ": Failed to set wpa_supplicant debug level."; |
| } |
| } |
| |
| void WiFi::SetConnectionDebugging(bool enabled) { |
| if (is_debugging_connection_ == enabled) { |
| return; |
| } |
| OnWiFiDebugScopeChanged(enabled || ScopeLogger::GetInstance()->IsScopeEnabled( |
| ScopeLogger::kWiFi)); |
| is_debugging_connection_ = enabled; |
| } |
| |
| void WiFi::SetSupplicantInterfaceProxy( |
| std::unique_ptr<SupplicantInterfaceProxyInterface> proxy) { |
| if (proxy) { |
| supplicant_interface_proxy_ = std::move(proxy); |
| } else { |
| supplicant_interface_proxy_.reset(); |
| } |
| } |
| |
| void WiFi::ConnectToSupplicant() { |
| LOG(INFO) << link_name() << ": " << (enabled() ? "enabled" : "disabled") |
| << " supplicant: " << (supplicant_present_ ? "present" : "absent") |
| << " proxy: " |
| << (supplicant_interface_proxy_.get() ? "non-null" : "null"); |
| if (!enabled() || !supplicant_present_) { |
| return; |
| } |
| OnWiFiDebugScopeChanged( |
| ScopeLogger::GetInstance()->IsScopeEnabled(ScopeLogger::kWiFi)); |
| |
| RpcIdentifier previous_supplicant_interface_path(supplicant_interface_path_); |
| |
| KeyValueStore create_interface_args; |
| create_interface_args.Set<std::string>(WPASupplicant::kInterfacePropertyName, |
| link_name()); |
| create_interface_args.Set<std::string>( |
| WPASupplicant::kInterfacePropertyDriver, WPASupplicant::kDriverNL80211); |
| create_interface_args.Set<std::string>( |
| WPASupplicant::kInterfacePropertyConfigFile, |
| WPASupplicant::kSupplicantConfPath); |
| supplicant_connect_attempts_++; |
| if (!supplicant_process_proxy()->CreateInterface( |
| create_interface_args, &supplicant_interface_path_)) { |
| // Interface might've already been created, attempt to retrieve it. |
| if (!supplicant_process_proxy()->GetInterface( |
| link_name(), &supplicant_interface_path_)) { |
| LOG(ERROR) << __func__ |
| << ": Failed to create interface with supplicant, attempt " |
| << supplicant_connect_attempts_; |
| |
| // Interface could not be created at the moment. This could be a |
| // transient error in trying to bring the interface UP, or it could be a |
| // persistent device failure. We continue to rety a few times until |
| // either we succeed or the device disappears or is disabled, in the hope |
| // that the device will recover. |
| if (supplicant_connect_attempts_ >= kMaxRetryCreateInterfaceAttempts) { |
| LOG(ERROR) << "Giving up."; |
| SetEnabled(false); |
| metrics()->NotifyWiFiSupplicantAbort(); |
| } else { |
| dispatcher()->PostDelayedTask( |
| FROM_HERE, |
| base::Bind(&WiFi::ConnectToSupplicant, |
| weak_ptr_factory_.GetWeakPtr()), |
| kRetryCreateInterfaceIntervalSeconds * 1000); |
| } |
| return; |
| } |
| } |
| |
| LOG(INFO) << "connected to supplicant on attempt " |
| << supplicant_connect_attempts_; |
| metrics()->NotifyWiFiSupplicantSuccess(supplicant_connect_attempts_); |
| |
| // Only (re)create the interface proxy if its D-Bus path changed, or if we |
| // haven't created one yet. This lets us watch interface properties |
| // immediately after Stop() (e.g., for metrics collection) and also allows |
| // tests to skip recreation (by retaining the same interface path). |
| if (!supplicant_interface_proxy_ || |
| previous_supplicant_interface_path != supplicant_interface_path_) { |
| SLOG(this, 2) << base::StringPrintf( |
| "Updating interface path from \"%s\" to \"%s\"", |
| previous_supplicant_interface_path.value().c_str(), |
| supplicant_interface_path_.value().c_str()); |
| SetSupplicantInterfaceProxy( |
| |