| // Copyright 2014 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/wake_on_wifi.h" |
| |
| #include <errno.h> |
| #include <linux/nl80211.h> |
| #include <stdio.h> |
| |
| #include <algorithm> |
| #include <set> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include <base/cancelable_callback.h> |
| #include <chromeos/dbus/service_constants.h> |
| #include <components/timers/alarm_timer.h> |
| |
| #include "shill/error.h" |
| #include "shill/event_dispatcher.h" |
| #include "shill/ip_address_store.h" |
| #include "shill/logging.h" |
| #include "shill/metrics.h" |
| #include "shill/net/event_history.h" |
| #include "shill/net/netlink_manager.h" |
| #include "shill/net/nl80211_message.h" |
| #include "shill/property_accessor.h" |
| #include "shill/wifi/wifi.h" |
| |
| using base::Bind; |
| using base::Closure; |
| using std::pair; |
| using std::set; |
| using std::string; |
| using std::vector; |
| using timers::AlarmTimer; |
| |
| namespace shill { |
| |
| namespace Logging { |
| static auto kModuleLogScope = ScopeLogger::kWiFi; |
| static std::string ObjectID(WakeOnWiFi *w) { return "(wake_on_wifi)"; } |
| } |
| |
| const char WakeOnWiFi::kWakeOnIPAddressPatternsNotSupported[] = |
| "Wake on IP address patterns not supported by this WiFi device"; |
| const char WakeOnWiFi::kWakeOnWiFiNotSupported[] = "Wake on WiFi not supported"; |
| const int WakeOnWiFi::kVerifyWakeOnWiFiSettingsDelayMilliseconds = 300; |
| const int WakeOnWiFi::kMaxSetWakeOnPacketRetries = 2; |
| const int WakeOnWiFi::kMetricsReportingFrequencySeconds = 600; |
| const uint32_t WakeOnWiFi::kDefaultWakeToScanPeriodSeconds = 900; |
| const uint32_t WakeOnWiFi::kDefaultNetDetectScanPeriodSeconds = 120; |
| const uint32_t WakeOnWiFi::kImmediateDHCPLeaseRenewalThresholdSeconds = 60; |
| // We tolerate no more than 3 dark resumes per minute and 10 dark resumes per |
| // 10 minutes before we disable wake on WiFi on the NIC. |
| const int WakeOnWiFi::kDarkResumeFrequencySamplingPeriodShortMinutes = 1; |
| const int WakeOnWiFi::kDarkResumeFrequencySamplingPeriodLongMinutes = 10; |
| const int WakeOnWiFi::kMaxDarkResumesPerPeriodShort = 3; |
| const int WakeOnWiFi::kMaxDarkResumesPerPeriodLong = 10; |
| // If a connection is not established during dark resume, give up and prepare |
| // the system to wake on SSID 1 second before suspending again. |
| // TODO(samueltan): link this to |
| // Manager::kTerminationActionsTimeoutMilliseconds rather than hard-coding |
| // this value. |
| int64_t WakeOnWiFi::DarkResumeActionsTimeoutMilliseconds = 18500; |
| // Scanning 1 frequency takes ~100ms, so retrying 5 times on 8 frequencies will |
| // take about 4 seconds, which is how long a full scan typically takes. |
| const int WakeOnWiFi::kMaxFreqsForDarkResumeScanRetries = 8; |
| const int WakeOnWiFi::kMaxDarkResumeScanRetries = 5; |
| |
| WakeOnWiFi::WakeOnWiFi(NetlinkManager *netlink_manager, |
| EventDispatcher *dispatcher, Metrics *metrics) |
| : dispatcher_(dispatcher), |
| netlink_manager_(netlink_manager), |
| metrics_(metrics), |
| report_metrics_callback_( |
| Bind(&WakeOnWiFi::ReportMetrics, base::Unretained(this))), |
| num_set_wake_on_packet_retries_(0), |
| wake_on_wifi_max_patterns_(0), |
| wake_on_wifi_max_ssids_(0), |
| wiphy_index_(0), |
| wiphy_index_received_(false), |
| #if defined(DISABLE_WAKE_ON_WIFI) |
| wake_on_wifi_features_enabled_(kWakeOnWiFiFeaturesEnabledNotSupported), |
| #else |
| // Wake on WiFi features disabled by default at run-time for boards that |
| // support wake on WiFi. Rely on Chrome to enable appropriate features via |
| // DBus. |
| wake_on_wifi_features_enabled_(kWakeOnWiFiFeaturesEnabledNone), |
| #endif // DISABLE_WAKE_ON_WIFI |
| dhcp_lease_renewal_timer_(true, false), |
| wake_to_scan_timer_(true, false), |
| in_dark_resume_(false), |
| wake_to_scan_period_seconds_(kDefaultWakeToScanPeriodSeconds), |
| net_detect_scan_period_seconds_(kDefaultNetDetectScanPeriodSeconds), |
| last_wake_reason_(kWakeTriggerUnsupported), |
| force_wake_to_scan_timer_(false), |
| dark_resume_scan_retries_left_(0), |
| weak_ptr_factory_(this) { |
| netlink_manager_->AddBroadcastHandler(Bind( |
| &WakeOnWiFi::OnWakeupReasonReceived, weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| WakeOnWiFi::~WakeOnWiFi() {} |
| |
| void WakeOnWiFi::InitPropertyStore(PropertyStore *store) { |
| store->RegisterDerivedString( |
| kWakeOnWiFiFeaturesEnabledProperty, |
| StringAccessor(new CustomAccessor<WakeOnWiFi, string>( |
| this, &WakeOnWiFi::GetWakeOnWiFiFeaturesEnabled, |
| &WakeOnWiFi::SetWakeOnWiFiFeaturesEnabled))); |
| store->RegisterUint32(kWakeToScanPeriodSecondsProperty, |
| &wake_to_scan_period_seconds_); |
| store->RegisterUint32(kNetDetectScanPeriodSecondsProperty, |
| &net_detect_scan_period_seconds_); |
| store->RegisterBool(kForceWakeToScanTimerProperty, |
| &force_wake_to_scan_timer_); |
| } |
| |
| void WakeOnWiFi::StartMetricsTimer() { |
| #if !defined(DISABLE_WAKE_ON_WIFI) |
| dispatcher_->PostDelayedTask(report_metrics_callback_.callback(), |
| kMetricsReportingFrequencySeconds * 1000); |
| #endif // DISABLE_WAKE_ON_WIFI |
| } |
| |
| string WakeOnWiFi::GetWakeOnWiFiFeaturesEnabled(Error *error) { |
| return wake_on_wifi_features_enabled_; |
| } |
| |
| bool WakeOnWiFi::SetWakeOnWiFiFeaturesEnabled(const std::string &enabled, |
| Error *error) { |
| #if defined(DISABLE_WAKE_ON_WIFI) |
| error->Populate(Error::kNotSupported, kWakeOnWiFiNotSupported); |
| SLOG(this, 7) << __func__ << ": " << kWakeOnWiFiNotSupported; |
| return false; |
| #else |
| if (wake_on_wifi_features_enabled_ == enabled) { |
| return false; |
| } |
| if (enabled != kWakeOnWiFiFeaturesEnabledPacket && |
| enabled != kWakeOnWiFiFeaturesEnabledSSID && |
| enabled != kWakeOnWiFiFeaturesEnabledPacketSSID && |
| enabled != kWakeOnWiFiFeaturesEnabledNone) { |
| Error::PopulateAndLog(FROM_HERE, error, Error::kInvalidArguments, |
| "Invalid Wake on WiFi feature"); |
| return false; |
| } |
| wake_on_wifi_features_enabled_ = enabled; |
| return true; |
| #endif // DISABLE_WAKE_ON_WIFI |
| } |
| |
| void WakeOnWiFi::RunAndResetSuspendActionsDoneCallback(const Error &error) { |
| if (!suspend_actions_done_callback_.is_null()) { |
| suspend_actions_done_callback_.Run(error); |
| suspend_actions_done_callback_.Reset(); |
| } |
| } |
| |
| // static |
| bool WakeOnWiFi::ByteStringPairIsLessThan( |
| const std::pair<ByteString, ByteString> &lhs, |
| const std::pair<ByteString, ByteString> &rhs) { |
| // Treat the first value of the pair as the key. |
| return ByteString::IsLessThan(lhs.first, rhs.first); |
| } |
| |
| // static |
| void WakeOnWiFi::SetMask(ByteString *mask, uint32_t pattern_len, |
| uint32_t offset) { |
| // Round up number of bytes required for the mask. |
| int result_mask_len = (pattern_len + 8 - 1) / 8; |
| vector<unsigned char> result_mask(result_mask_len, 0); |
| // Set mask bits from offset to (pattern_len - 1) |
| int mask_index; |
| for (uint32_t curr_mask_bit = offset; curr_mask_bit < pattern_len; |
| ++curr_mask_bit) { |
| mask_index = curr_mask_bit / 8; |
| result_mask[mask_index] |= 1 << (curr_mask_bit % 8); |
| } |
| mask->Clear(); |
| mask->Append(ByteString(result_mask)); |
| } |
| |
| // static |
| bool WakeOnWiFi::CreateIPAddressPatternAndMask(const IPAddress &ip_addr, |
| ByteString *pattern, |
| ByteString *mask) { |
| if (ip_addr.family() == IPAddress::kFamilyIPv4) { |
| WakeOnWiFi::CreateIPV4PatternAndMask(ip_addr, pattern, mask); |
| return true; |
| } else if (ip_addr.family() == IPAddress::kFamilyIPv6) { |
| WakeOnWiFi::CreateIPV6PatternAndMask(ip_addr, pattern, mask); |
| return true; |
| } else { |
| LOG(ERROR) << "Unrecognized IP Address type."; |
| return false; |
| } |
| } |
| |
| // static |
| void WakeOnWiFi::CreateIPV4PatternAndMask(const IPAddress &ip_addr, |
| ByteString *pattern, |
| ByteString *mask) { |
| struct { |
| struct ethhdr eth_hdr; |
| struct iphdr ipv4_hdr; |
| } __attribute__((__packed__)) pattern_bytes; |
| memset(&pattern_bytes, 0, sizeof(pattern_bytes)); |
| CHECK_EQ(sizeof(pattern_bytes.ipv4_hdr.saddr), ip_addr.GetLength()); |
| memcpy(&pattern_bytes.ipv4_hdr.saddr, ip_addr.GetConstData(), |
| ip_addr.GetLength()); |
| int src_ip_offset = |
| reinterpret_cast<unsigned char *>(&pattern_bytes.ipv4_hdr.saddr) - |
| reinterpret_cast<unsigned char *>(&pattern_bytes); |
| int pattern_len = src_ip_offset + ip_addr.GetLength(); |
| pattern->Clear(); |
| pattern->Append(ByteString( |
| reinterpret_cast<const unsigned char *>(&pattern_bytes), pattern_len)); |
| WakeOnWiFi::SetMask(mask, pattern_len, src_ip_offset); |
| } |
| |
| // static |
| void WakeOnWiFi::CreateIPV6PatternAndMask(const IPAddress &ip_addr, |
| ByteString *pattern, |
| ByteString *mask) { |
| struct { |
| struct ethhdr eth_hdr; |
| struct ip6_hdr ipv6_hdr; |
| } __attribute__((__packed__)) pattern_bytes; |
| memset(&pattern_bytes, 0, sizeof(pattern_bytes)); |
| CHECK_EQ(sizeof(pattern_bytes.ipv6_hdr.ip6_src), ip_addr.GetLength()); |
| memcpy(&pattern_bytes.ipv6_hdr.ip6_src, ip_addr.GetConstData(), |
| ip_addr.GetLength()); |
| int src_ip_offset = |
| reinterpret_cast<unsigned char *>(&pattern_bytes.ipv6_hdr.ip6_src) - |
| reinterpret_cast<unsigned char *>(&pattern_bytes); |
| int pattern_len = src_ip_offset + ip_addr.GetLength(); |
| pattern->Clear(); |
| pattern->Append(ByteString( |
| reinterpret_cast<const unsigned char *>(&pattern_bytes), pattern_len)); |
| WakeOnWiFi::SetMask(mask, pattern_len, src_ip_offset); |
| } |
| |
| // static |
| bool WakeOnWiFi::ConfigureWiphyIndex(Nl80211Message *msg, int32_t index) { |
| if (!msg->attributes()->CreateU32Attribute(NL80211_ATTR_WIPHY, |
| "WIPHY index")) { |
| return false; |
| } |
| if (!msg->attributes()->SetU32AttributeValue(NL80211_ATTR_WIPHY, index)) { |
| return false; |
| } |
| return true; |
| } |
| |
| // static |
| bool WakeOnWiFi::ConfigureDisableWakeOnWiFiMessage( |
| SetWakeOnPacketConnMessage *msg, uint32_t wiphy_index, Error *error) { |
| if (!ConfigureWiphyIndex(msg, wiphy_index)) { |
| Error::PopulateAndLog(FROM_HERE, error, Error::kOperationFailed, |
| "Failed to configure Wiphy index."); |
| return false; |
| } |
| return true; |
| } |
| |
| // static |
| bool WakeOnWiFi::ConfigureSetWakeOnWiFiSettingsMessage( |
| SetWakeOnPacketConnMessage *msg, const set<WakeOnWiFiTrigger> &trigs, |
| const IPAddressStore &addrs, uint32_t wiphy_index, |
| uint32_t net_detect_scan_period_seconds, |
| const vector<ByteString> &ssid_whitelist, |
| Error *error) { |
| if (trigs.empty()) { |
| Error::PopulateAndLog(FROM_HERE, error, Error::kInvalidArguments, |
| "No triggers to configure."); |
| return false; |
| } |
| if (trigs.find(kWakeTriggerPattern) != trigs.end() && addrs.Empty()) { |
| Error::PopulateAndLog(FROM_HERE, error, Error::kInvalidArguments, |
| "No IP addresses to configure."); |
| return false; |
| } |
| if (!ConfigureWiphyIndex(msg, wiphy_index)) { |
| Error::PopulateAndLog(FROM_HERE, error, Error::kOperationFailed, |
| "Failed to configure Wiphy index."); |
| return false; |
| } |
| if (!msg->attributes()->CreateNestedAttribute(NL80211_ATTR_WOWLAN_TRIGGERS, |
| "WoWLAN Triggers")) { |
| Error::PopulateAndLog(FROM_HERE, error, Error::kOperationFailed, |
| "Could not create nested attribute " |
| "NL80211_ATTR_WOWLAN_TRIGGERS"); |
| return false; |
| } |
| if (!msg->attributes()->SetNestedAttributeHasAValue( |
| NL80211_ATTR_WOWLAN_TRIGGERS)) { |
| Error::PopulateAndLog(FROM_HERE, error, Error::kOperationFailed, |
| "Could not set nested attribute " |
| "NL80211_ATTR_WOWLAN_TRIGGERS"); |
| return false; |
| } |
| |
| AttributeListRefPtr triggers; |
| if (!msg->attributes()->GetNestedAttributeList(NL80211_ATTR_WOWLAN_TRIGGERS, |
| &triggers)) { |
| Error::PopulateAndLog(FROM_HERE, error, Error::kOperationFailed, |
| "Could not get nested attribute list " |
| "NL80211_ATTR_WOWLAN_TRIGGERS"); |
| return false; |
| } |
| // Add triggers. |
| for (WakeOnWiFiTrigger t : trigs) { |
| switch (t) { |
| case kWakeTriggerDisconnect: { |
| if (!triggers->CreateFlagAttribute(NL80211_WOWLAN_TRIG_DISCONNECT, |
| "Wake on Disconnect")) { |
| LOG(ERROR) << __func__ << "Could not create flag attribute " |
| "NL80211_WOWLAN_TRIG_DISCONNECT"; |
| return false; |
| } |
| if (!triggers->SetFlagAttributeValue(NL80211_WOWLAN_TRIG_DISCONNECT, |
| true)) { |
| LOG(ERROR) << __func__ << "Could not set flag attribute " |
| "NL80211_WOWLAN_TRIG_DISCONNECT"; |
| return false; |
| } |
| break; |
| } |
| case kWakeTriggerPattern: { |
| if (!triggers->CreateNestedAttribute(NL80211_WOWLAN_TRIG_PKT_PATTERN, |
| "Pattern trigger")) { |
| Error::PopulateAndLog(FROM_HERE, error, Error::kOperationFailed, |
| "Could not create nested attribute " |
| "NL80211_WOWLAN_TRIG_PKT_PATTERN"); |
| return false; |
| } |
| if (!triggers->SetNestedAttributeHasAValue( |
| NL80211_WOWLAN_TRIG_PKT_PATTERN)) { |
| Error::PopulateAndLog(FROM_HERE, error, Error::kOperationFailed, |
| "Could not set nested attribute " |
| "NL80211_WOWLAN_TRIG_PKT_PATTERN"); |
| return false; |
| } |
| AttributeListRefPtr patterns; |
| if (!triggers->GetNestedAttributeList(NL80211_WOWLAN_TRIG_PKT_PATTERN, |
| &patterns)) { |
| Error::PopulateAndLog(FROM_HERE, error, Error::kOperationFailed, |
| "Could not get nested attribute list " |
| "NL80211_WOWLAN_TRIG_PKT_PATTERN"); |
| return false; |
| } |
| uint8_t patnum = 1; |
| for (const IPAddress &addr : addrs.GetIPAddresses()) { |
| if (!CreateSinglePattern(addr, patterns, patnum++, error)) { |
| return false; |
| } |
| } |
| break; |
| } |
| case kWakeTriggerSSID: { |
| if (!triggers->CreateNestedAttribute(NL80211_WOWLAN_TRIG_NET_DETECT, |
| "Wake on SSID trigger")) { |
| Error::PopulateAndLog(FROM_HERE, error, Error::kOperationFailed, |
| "Could not create nested attribute " |
| "NL80211_WOWLAN_TRIG_NET_DETECT"); |
| return false; |
| } |
| if (!triggers->SetNestedAttributeHasAValue( |
| NL80211_WOWLAN_TRIG_NET_DETECT)) { |
| Error::PopulateAndLog(FROM_HERE, error, Error::kOperationFailed, |
| "Could not set nested attribute " |
| "NL80211_WOWLAN_TRIG_NET_DETECT"); |
| return false; |
| } |
| AttributeListRefPtr scan_attributes; |
| if (!triggers->GetNestedAttributeList(NL80211_WOWLAN_TRIG_NET_DETECT, |
| &scan_attributes)) { |
| Error::PopulateAndLog(FROM_HERE, error, Error::kOperationFailed, |
| "Could not get nested attribute list " |
| "NL80211_WOWLAN_TRIG_NET_DETECT"); |
| return false; |
| } |
| if (!scan_attributes->CreateU32Attribute( |
| NL80211_ATTR_SCHED_SCAN_INTERVAL, |
| "NL80211_ATTR_SCHED_SCAN_INTERVAL")) { |
| Error::PopulateAndLog(FROM_HERE, error, Error::kOperationFailed, |
| "Could not get create U32 attribute " |
| "NL80211_ATTR_SCHED_SCAN_INTERVAL"); |
| return false; |
| } |
| if (!scan_attributes->SetU32AttributeValue( |
| NL80211_ATTR_SCHED_SCAN_INTERVAL, |
| net_detect_scan_period_seconds * 1000)) { |
| Error::PopulateAndLog(FROM_HERE, error, Error::kOperationFailed, |
| "Could not get set U32 attribute " |
| "NL80211_ATTR_SCHED_SCAN_INTERVAL"); |
| return false; |
| } |
| if (!scan_attributes->CreateNestedAttribute( |
| NL80211_ATTR_SCHED_SCAN_MATCH, |
| "NL80211_ATTR_SCHED_SCAN_MATCH")) { |
| Error::PopulateAndLog(FROM_HERE, error, Error::kOperationFailed, |
| "Could not create nested attribute list " |
| "NL80211_ATTR_SCHED_SCAN_MATCH"); |
| return false; |
| } |
| if (!scan_attributes->SetNestedAttributeHasAValue( |
| NL80211_ATTR_SCHED_SCAN_MATCH)) { |
| Error::PopulateAndLog(FROM_HERE, error, Error::kOperationFailed, |
| "Could not set nested attribute " |
| "NL80211_ATTR_SCAN_SSIDS"); |
| return false; |
| } |
| AttributeListRefPtr ssids; |
| if (!scan_attributes->GetNestedAttributeList( |
| NL80211_ATTR_SCHED_SCAN_MATCH, &ssids)) { |
| Error::PopulateAndLog(FROM_HERE, error, Error::kOperationFailed, |
| "Could not get nested attribute list " |
| "NL80211_ATTR_SCHED_SCAN_MATCH"); |
| return false; |
| } |
| int ssid_num = 0; |
| for (const ByteString &ssid_bytes : |
| ssid_whitelist) { |
| if (!ssids->CreateNestedAttribute( |
| ssid_num, "NL80211_ATTR_SCHED_SCAN_MATCH_SINGLE")) { |
| Error::PopulateAndLog(FROM_HERE, error, Error::kOperationFailed, |
| "Could not create nested attribute list " |
| "NL80211_ATTR_SCHED_SCAN_MATCH_SINGLE"); |
| return false; |
| } |
| if (!ssids->SetNestedAttributeHasAValue(ssid_num)) { |
| Error::PopulateAndLog( |
| FROM_HERE, error, Error::kOperationFailed, |
| "Could not set value for nested attribute list " |
| "NL80211_ATTR_SCHED_SCAN_MATCH_SINGLE"); |
| return false; |
| } |
| AttributeListRefPtr single_ssid; |
| if (!ssids->GetNestedAttributeList(ssid_num, &single_ssid)) { |
| Error::PopulateAndLog(FROM_HERE, error, Error::kOperationFailed, |
| "Could not get nested attribute list " |
| "NL80211_ATTR_SCHED_SCAN_MATCH_SINGLE"); |
| return false; |
| } |
| if (!single_ssid->CreateRawAttribute( |
| NL80211_SCHED_SCAN_MATCH_ATTR_SSID, |
| "NL80211_SCHED_SCAN_MATCH_ATTR_SSID")) { |
| Error::PopulateAndLog( |
| FROM_HERE, error, Error::kOperationFailed, |
| "Could not create NL80211_SCHED_SCAN_MATCH_ATTR_SSID"); |
| return false; |
| } |
| if (!single_ssid->SetRawAttributeValue( |
| NL80211_SCHED_SCAN_MATCH_ATTR_SSID, ssid_bytes)) { |
| Error::PopulateAndLog( |
| FROM_HERE, error, Error::kOperationFailed, |
| "Could not set NL80211_SCHED_SCAN_MATCH_ATTR_SSID"); |
| return false; |
| } |
| ++ssid_num; |
| } |
| break; |
| } |
| default: { |
| LOG(ERROR) << __func__ << ": Unrecognized trigger"; |
| return false; |
| } |
| } |
| } |
| return true; |
| } |
| |
| // static |
| bool WakeOnWiFi::CreateSinglePattern(const IPAddress &ip_addr, |
| AttributeListRefPtr patterns, |
| uint8_t patnum, Error *error) { |
| ByteString pattern; |
| ByteString mask; |
| WakeOnWiFi::CreateIPAddressPatternAndMask(ip_addr, &pattern, &mask); |
| if (!patterns->CreateNestedAttribute(patnum, "Pattern info")) { |
| Error::PopulateAndLog(FROM_HERE, error, Error::kOperationFailed, |
| "Could not create nested attribute " |
| "patnum for SetWakeOnPacketConnMessage."); |
| return false; |
| } |
| if (!patterns->SetNestedAttributeHasAValue(patnum)) { |
| Error::PopulateAndLog(FROM_HERE, error, Error::kOperationFailed, |
| "Could not set nested attribute " |
| "patnum for SetWakeOnPacketConnMessage."); |
| return false; |
| } |
| |
| AttributeListRefPtr pattern_info; |
| if (!patterns->GetNestedAttributeList(patnum, &pattern_info)) { |
| Error::PopulateAndLog(FROM_HERE, error, Error::kOperationFailed, |
| "Could not get nested attribute list " |
| "patnum for SetWakeOnPacketConnMessage."); |
| return false; |
| } |
| // Add mask. |
| if (!pattern_info->CreateRawAttribute(NL80211_PKTPAT_MASK, "Mask")) { |
| Error::PopulateAndLog(FROM_HERE, error, Error::kOperationFailed, |
| "Could not add attribute NL80211_PKTPAT_MASK to " |
| "pattern_info."); |
| return false; |
| } |
| if (!pattern_info->SetRawAttributeValue(NL80211_PKTPAT_MASK, mask)) { |
| Error::PopulateAndLog(FROM_HERE, error, Error::kOperationFailed, |
| "Could not set attribute NL80211_PKTPAT_MASK in " |
| "pattern_info."); |
| return false; |
| } |
| |
| // Add pattern. |
| if (!pattern_info->CreateRawAttribute(NL80211_PKTPAT_PATTERN, "Pattern")) { |
| Error::PopulateAndLog(FROM_HERE, error, Error::kOperationFailed, |
| "Could not add attribute NL80211_PKTPAT_PATTERN to " |
| "pattern_info."); |
| return false; |
| } |
| if (!pattern_info->SetRawAttributeValue(NL80211_PKTPAT_PATTERN, pattern)) { |
| Error::PopulateAndLog(FROM_HERE, error, Error::kOperationFailed, |
| "Could not set attribute NL80211_PKTPAT_PATTERN in " |
| "pattern_info."); |
| return false; |
| } |
| |
| // Add offset. |
| if (!pattern_info->CreateU32Attribute(NL80211_PKTPAT_OFFSET, "Offset")) { |
| Error::PopulateAndLog(FROM_HERE, error, Error::kOperationFailed, |
| "Could not add attribute NL80211_PKTPAT_OFFSET to " |
| "pattern_info."); |
| return false; |
| } |
| if (!pattern_info->SetU32AttributeValue(NL80211_PKTPAT_OFFSET, 0)) { |
| Error::PopulateAndLog(FROM_HERE, error, Error::kOperationFailed, |
| "Could not set attribute NL80211_PKTPAT_OFFSET in " |
| "pattern_info."); |
| return false; |
| } |
| return true; |
| } |
| |
| // static |
| bool WakeOnWiFi::ConfigureGetWakeOnWiFiSettingsMessage( |
| GetWakeOnPacketConnMessage *msg, uint32_t wiphy_index, Error *error) { |
| if (!ConfigureWiphyIndex(msg, wiphy_index)) { |
| Error::PopulateAndLog(FROM_HERE, error, Error::kOperationFailed, |
| "Failed to configure Wiphy index."); |
| return false; |
| } |
| return true; |
| } |
| |
| // static |
| bool WakeOnWiFi::WakeOnWiFiSettingsMatch( |
| const Nl80211Message &msg, const set<WakeOnWiFiTrigger> &trigs, |
| const IPAddressStore &addrs, uint32_t net_detect_scan_period_seconds, |
| const vector<ByteString> &ssid_whitelist) { |
| if (msg.command() != NL80211_CMD_GET_WOWLAN && |
| msg.command() != NL80211_CMD_SET_WOWLAN) { |
| LOG(ERROR) << __func__ << ": " |
| << "Invalid message command"; |
| return false; |
| } |
| AttributeListConstRefPtr triggers; |
| if (!msg.const_attributes()->ConstGetNestedAttributeList( |
| NL80211_ATTR_WOWLAN_TRIGGERS, &triggers)) { |
| // No triggers in the returned message, which is valid iff we expect there |
| // to be no triggers programmed into the NIC. |
| return trigs.empty(); |
| } |
| // If we find a trigger in |msg| that we do not have a corresponding flag |
| // for in |trigs|, we have a mismatch. |
| bool unused_flag; |
| AttributeListConstRefPtr unused_list; |
| if (triggers->GetFlagAttributeValue(NL80211_WOWLAN_TRIG_DISCONNECT, |
| &unused_flag) && |
| trigs.find(kWakeTriggerDisconnect) == trigs.end()) { |
| SLOG(WiFi, nullptr, 3) |
| << __func__ << "Wake on disconnect trigger not expected but found"; |
| return false; |
| } |
| if (triggers->ConstGetNestedAttributeList(NL80211_WOWLAN_TRIG_PKT_PATTERN, |
| &unused_list) && |
| trigs.find(kWakeTriggerPattern) == trigs.end()) { |
| SLOG(WiFi, nullptr, 3) << __func__ |
| << "Wake on pattern trigger not expected but found"; |
| return false; |
| } |
| if (triggers->ConstGetNestedAttributeList(NL80211_WOWLAN_TRIG_NET_DETECT, |
| &unused_list) && |
| trigs.find(kWakeTriggerSSID) == trigs.end()) { |
| SLOG(WiFi, nullptr, 3) << __func__ |
| << "Wake on SSID trigger not expected but found"; |
| return false; |
| } |
| // Check that each expected trigger is present in |msg| with matching |
| // setting values. |
| for (WakeOnWiFiTrigger t : trigs) { |
| switch (t) { |
| case kWakeTriggerDisconnect: { |
| bool wake_on_disconnect; |
| if (!triggers->GetFlagAttributeValue(NL80211_WOWLAN_TRIG_DISCONNECT, |
| &wake_on_disconnect)) { |
| LOG(ERROR) << __func__ << ": " |
| << "Could not get the flag NL80211_WOWLAN_TRIG_DISCONNECT"; |
| return false; |
| } |
| if (!wake_on_disconnect) { |
| SLOG(WiFi, nullptr, 3) << __func__ |
| << "Wake on disconnect flag not set."; |
| return false; |
| } |
| break; |
| } |
| case kWakeTriggerPattern: { |
| // Create pattern and masks that we expect to find in |msg|. |
| set<pair<ByteString, ByteString>, |
| bool (*)(const pair<ByteString, ByteString> &, |
| const pair<ByteString, ByteString> &)> |
| expected_patt_mask_pairs(ByteStringPairIsLessThan); |
| ByteString temp_pattern; |
| ByteString temp_mask; |
| for (const IPAddress &addr : addrs.GetIPAddresses()) { |
| temp_pattern.Clear(); |
| temp_mask.Clear(); |
| CreateIPAddressPatternAndMask(addr, &temp_pattern, &temp_mask); |
| expected_patt_mask_pairs.emplace(temp_pattern, temp_mask); |
| } |
| // Check these expected pattern and masks against those actually |
| // contained in |msg|. |
| AttributeListConstRefPtr patterns; |
| if (!triggers->ConstGetNestedAttributeList( |
| NL80211_WOWLAN_TRIG_PKT_PATTERN, &patterns)) { |
| LOG(ERROR) << __func__ << ": " |
| << "Could not get nested attribute list " |
| "NL80211_WOWLAN_TRIG_PKT_PATTERN"; |
| return false; |
| } |
| bool pattern_mismatch_found = false; |
| size_t pattern_num_mismatch = expected_patt_mask_pairs.size(); |
| int pattern_index; |
| AttributeIdIterator pattern_iter(*patterns); |
| AttributeListConstRefPtr pattern_info; |
| ByteString returned_mask; |
| ByteString returned_pattern; |
| while (!pattern_iter.AtEnd()) { |
| returned_mask.Clear(); |
| returned_pattern.Clear(); |
| pattern_index = pattern_iter.GetId(); |
| if (!patterns->ConstGetNestedAttributeList(pattern_index, |
| &pattern_info)) { |
| LOG(ERROR) << __func__ << ": " |
| << "Could not get nested pattern attribute list #" |
| << pattern_index; |
| return false; |
| } |
| if (!pattern_info->GetRawAttributeValue(NL80211_PKTPAT_MASK, |
| &returned_mask)) { |
| LOG(ERROR) << __func__ << ": " |
| << "Could not get attribute NL80211_PKTPAT_MASK"; |
| return false; |
| } |
| if (!pattern_info->GetRawAttributeValue(NL80211_PKTPAT_PATTERN, |
| &returned_pattern)) { |
| LOG(ERROR) << __func__ << ": " |
| << "Could not get attribute NL80211_PKTPAT_PATTERN"; |
| return false; |
| } |
| if (expected_patt_mask_pairs.find(pair<ByteString, ByteString>( |
| returned_pattern, returned_mask)) == |
| expected_patt_mask_pairs.end()) { |
| pattern_mismatch_found = true; |
| break; |
| } else { |
| --pattern_num_mismatch; |
| } |
| pattern_iter.Advance(); |
| } |
| if (pattern_mismatch_found || pattern_num_mismatch) { |
| SLOG(WiFi, nullptr, 3) << __func__ |
| << "Wake on pattern pattern/mask mismatch"; |
| return false; |
| } |
| break; |
| } |
| case kWakeTriggerSSID: { |
| set<ByteString, bool (*)(const ByteString &, const ByteString &)> |
| expected_ssids(ssid_whitelist.begin(), ssid_whitelist.end(), |
| ByteString::IsLessThan); |
| AttributeListConstRefPtr scan_attributes; |
| if (!triggers->ConstGetNestedAttributeList( |
| NL80211_WOWLAN_TRIG_NET_DETECT, &scan_attributes)) { |
| LOG(ERROR) << __func__ << ": " |
| << "Could not get nested attribute list " |
| "NL80211_WOWLAN_TRIG_NET_DETECT"; |
| return false; |
| } |
| uint32_t interval; |
| if (!scan_attributes->GetU32AttributeValue( |
| NL80211_ATTR_SCHED_SCAN_INTERVAL, &interval)) { |
| LOG(ERROR) << __func__ << ": " |
| << "Could not get set U32 attribute " |
| "NL80211_ATTR_SCHED_SCAN_INTERVAL"; |
| return false; |
| } |
| if (interval != net_detect_scan_period_seconds * 1000) { |
| SLOG(WiFi, nullptr, 3) << __func__ |
| << "Net Detect scan period mismatch"; |
| return false; |
| } |
| AttributeListConstRefPtr ssids; |
| if (!scan_attributes->ConstGetNestedAttributeList( |
| NL80211_ATTR_SCHED_SCAN_MATCH, &ssids)) { |
| LOG(ERROR) << __func__ << ": " |
| << "Could not get nested attribute list " |
| "NL80211_ATTR_SCHED_SCAN_MATCH"; |
| return false; |
| } |
| bool ssid_mismatch_found = false; |
| size_t ssid_num_mismatch = expected_ssids.size(); |
| AttributeIdIterator ssid_iter(*ssids); |
| AttributeListConstRefPtr single_ssid; |
| ByteString ssid; |
| int ssid_index; |
| while (!ssid_iter.AtEnd()) { |
| ssid.Clear(); |
| ssid_index = ssid_iter.GetId(); |
| if (!ssids->ConstGetNestedAttributeList(ssid_index, &single_ssid)) { |
| LOG(ERROR) << __func__ << ": " |
| << "Could not get nested ssid attribute list #" |
| << ssid_index; |
| return false; |
| } |
| if (!single_ssid->GetRawAttributeValue( |
| NL80211_SCHED_SCAN_MATCH_ATTR_SSID, &ssid)) { |
| LOG(ERROR) << __func__ << ": " |
| << "Could not get attribute " |
| "NL80211_SCHED_SCAN_MATCH_ATTR_SSID"; |
| return false; |
| } |
| if (expected_ssids.find(ssid) == expected_ssids.end()) { |
| ssid_mismatch_found = true; |
| break; |
| } else { |
| --ssid_num_mismatch; |
| } |
| ssid_iter.Advance(); |
| } |
| if (ssid_mismatch_found || ssid_num_mismatch) { |
| SLOG(WiFi, nullptr, 3) << __func__ << "Net Detect SSID mismatch"; |
| return false; |
| } |
| break; |
| } |
| default: { |
| LOG(ERROR) << __func__ << ": Unrecognized trigger"; |
| return false; |
| } |
| } |
| } |
| return true; |
| } |
| |
| void WakeOnWiFi::AddWakeOnPacketConnection(const string &ip_endpoint, |
| Error *error) { |
| #if !defined(DISABLE_WAKE_ON_WIFI) |
| if (wake_on_wifi_triggers_supported_.find(kWakeTriggerPattern) == |
| wake_on_wifi_triggers_supported_.end()) { |
| Error::PopulateAndLog(FROM_HERE, error, Error::kNotSupported, |
| kWakeOnIPAddressPatternsNotSupported); |
| return; |
| } |
| IPAddress ip_addr(ip_endpoint); |
| if (!ip_addr.IsValid()) { |
| Error::PopulateAndLog(FROM_HERE, error, Error::kInvalidArguments, |
| "Invalid ip_address " + ip_endpoint); |
| return; |
| } |
| if (wake_on_packet_connections_.Count() >= wake_on_wifi_max_patterns_) { |
| Error::PopulateAndLog( |
| FROM_HERE, error, Error::kOperationFailed, |
| "Max number of IP address patterns already registered"); |
| return; |
| } |
| wake_on_packet_connections_.AddUnique(ip_addr); |
| #else |
| error->Populate(Error::kNotSupported, kWakeOnWiFiNotSupported); |
| SLOG(this, 7) << __func__ << ": " << kWakeOnWiFiNotSupported; |
| #endif // DISABLE_WAKE_ON_WIFI |
| } |
| |
| void WakeOnWiFi::RemoveWakeOnPacketConnection(const string &ip_endpoint, |
| Error *error) { |
| #if !defined(DISABLE_WAKE_ON_WIFI) |
| if (wake_on_wifi_triggers_supported_.find(kWakeTriggerPattern) == |
| wake_on_wifi_triggers_supported_.end()) { |
| Error::PopulateAndLog(FROM_HERE, error, Error::kNotSupported, |
| kWakeOnIPAddressPatternsNotSupported); |
| return; |
| } |
| IPAddress ip_addr(ip_endpoint); |
| if (!ip_addr.IsValid()) { |
| Error::PopulateAndLog(FROM_HERE, error, Error::kInvalidArguments, |
| "Invalid ip_address " + ip_endpoint); |
| return; |
| } |
| if (!wake_on_packet_connections_.Contains(ip_addr)) { |
| Error::PopulateAndLog(FROM_HERE, error, Error::kNotFound, |
| "No such IP address match registered to wake device"); |
| return; |
| } |
| wake_on_packet_connections_.Remove(ip_addr); |
| #else |
| error->Populate(Error::kNotSupported, kWakeOnWiFiNotSupported); |
| SLOG(this, 7) << __func__ << ": " << kWakeOnWiFiNotSupported; |
| #endif // DISABLE_WAKE_ON_WIFI |
| } |
| |
| void WakeOnWiFi::RemoveAllWakeOnPacketConnections(Error *error) { |
| #if !defined(DISABLE_WAKE_ON_WIFI) |
| if (wake_on_wifi_triggers_supported_.find(kWakeTriggerPattern) == |
| wake_on_wifi_triggers_supported_.end()) { |
| Error::PopulateAndLog(FROM_HERE, error, Error::kNotSupported, |
| kWakeOnIPAddressPatternsNotSupported); |
| return; |
| } |
| wake_on_packet_connections_.Clear(); |
| #else |
| error->Populate(Error::kNotSupported, kWakeOnWiFiNotSupported); |
| SLOG(this, 7) << __func__ << ": " << kWakeOnWiFiNotSupported; |
| #endif // DISABLE_WAKE_ON_WIFI |
| } |
| |
| void WakeOnWiFi::OnWakeOnWiFiSettingsErrorResponse( |
| NetlinkManager::AuxilliaryMessageType type, |
| const NetlinkMessage *raw_message) { |
| Error error(Error::kOperationFailed); |
| switch (type) { |
| case NetlinkManager::kErrorFromKernel: |
| if (!raw_message) { |
| error.Populate(Error::kOperationFailed, "Unknown error from kernel"); |
| break; |
| } |
| if (raw_message->message_type() == ErrorAckMessage::GetMessageType()) { |
| const ErrorAckMessage *error_ack_message = |
| dynamic_cast<const ErrorAckMessage *>(raw_message); |
| if (error_ack_message->error() == EOPNOTSUPP) { |
| error.Populate(Error::kNotSupported); |
| } |
| } |
| break; |
| |
| case NetlinkManager::kUnexpectedResponseType: |
| error.Populate(Error::kNotRegistered, |
| "Message not handled by regular message handler:"); |
| break; |
| |
| case NetlinkManager::kTimeoutWaitingForResponse: |
| // CMD_SET_WOWLAN messages do not receive responses, so this error type |
| // is received when NetlinkManager times out the message handler. Return |
| // immediately rather than run the done callback since this event does |
| // not signify the completion of suspend actions. |
| return; |
| break; |
| |
| default: |
| error.Populate( |
| Error::kOperationFailed, |
| "Unexpected auxilliary message type: " + std::to_string(type)); |
| break; |
| } |
| RunAndResetSuspendActionsDoneCallback(error); |
| } |
| |
| // static |
| void WakeOnWiFi::OnSetWakeOnPacketConnectionResponse( |
| const Nl80211Message &nl80211_message) { |
| // NOP because kernel does not send a response to NL80211_CMD_SET_WOWLAN |
| // requests. |
| } |
| |
| void WakeOnWiFi::RequestWakeOnPacketSettings() { |
| SLOG(this, 3) << __func__; |
| Error e; |
| GetWakeOnPacketConnMessage get_wowlan_msg; |
| CHECK(wiphy_index_received_); |
| if (!ConfigureGetWakeOnWiFiSettingsMessage(&get_wowlan_msg, wiphy_index_, |
| &e)) { |
| LOG(ERROR) << e.message(); |
| return; |
| } |
| netlink_manager_->SendNl80211Message( |
| &get_wowlan_msg, Bind(&WakeOnWiFi::VerifyWakeOnWiFiSettings, |
| weak_ptr_factory_.GetWeakPtr()), |
| Bind(&NetlinkManager::OnAckDoNothing), |
| Bind(&NetlinkManager::OnNetlinkMessageError)); |
| } |
| |
| void WakeOnWiFi::VerifyWakeOnWiFiSettings( |
| const Nl80211Message &nl80211_message) { |
| SLOG(this, 3) << __func__; |
| if (WakeOnWiFiSettingsMatch(nl80211_message, wake_on_wifi_triggers_, |
| wake_on_packet_connections_, |
| net_detect_scan_period_seconds_, |
| wake_on_ssid_whitelist_)) { |
| SLOG(this, 2) << __func__ << ": " |
| << "Wake on WiFi settings successfully verified"; |
| metrics_->NotifyVerifyWakeOnWiFiSettingsResult( |
| Metrics::kVerifyWakeOnWiFiSettingsResultSuccess); |
| RunAndResetSuspendActionsDoneCallback(Error(Error::kSuccess)); |
| } else { |
| LOG(ERROR) << __func__ << " failed: discrepancy between wake-on-packet " |
| "settings on NIC and those in local data " |
| "structure detected"; |
| metrics_->NotifyVerifyWakeOnWiFiSettingsResult( |
| Metrics::kVerifyWakeOnWiFiSettingsResultFailure); |
| RetrySetWakeOnPacketConnections(); |
| } |
| } |
| |
| void WakeOnWiFi::ApplyWakeOnWiFiSettings() { |
| SLOG(this, 3) << __func__; |
| if (!wiphy_index_received_) { |
| LOG(ERROR) << "Interface index not yet received"; |
| return; |
| } |
| if (wake_on_wifi_triggers_.empty()) { |
| SLOG(this, 1) << "No triggers to be programmed, so disable wake on WiFi"; |
| DisableWakeOnWiFi(); |
| return; |
| } |
| |
| Error error; |
| SetWakeOnPacketConnMessage set_wowlan_msg; |
| if (!ConfigureSetWakeOnWiFiSettingsMessage( |
| &set_wowlan_msg, wake_on_wifi_triggers_, wake_on_packet_connections_, |
| wiphy_index_, net_detect_scan_period_seconds_, |
| wake_on_ssid_whitelist_, &error)) { |
| LOG(ERROR) << error.message(); |
| RunAndResetSuspendActionsDoneCallback( |
| Error(Error::kOperationFailed, error.message())); |
| return; |
| } |
| if (!netlink_manager_->SendNl80211Message( |
| &set_wowlan_msg, |
| Bind(&WakeOnWiFi::OnSetWakeOnPacketConnectionResponse), |
| Bind(&NetlinkManager::OnAckDoNothing), |
| Bind(&WakeOnWiFi::OnWakeOnWiFiSettingsErrorResponse, |
| weak_ptr_factory_.GetWeakPtr()))) { |
| RunAndResetSuspendActionsDoneCallback( |
| Error(Error::kOperationFailed, "SendNl80211Message failed")); |
| return; |
| } |
| |
| verify_wake_on_packet_settings_callback_.Reset( |
| Bind(&WakeOnWiFi::RequestWakeOnPacketSettings, |
| weak_ptr_factory_.GetWeakPtr())); |
| dispatcher_->PostDelayedTask( |
| verify_wake_on_packet_settings_callback_.callback(), |
| kVerifyWakeOnWiFiSettingsDelayMilliseconds); |
| } |
| |
| void WakeOnWiFi::DisableWakeOnWiFi() { |
| SLOG(this, 3) << __func__; |
| Error error; |
| SetWakeOnPacketConnMessage disable_wowlan_msg; |
| CHECK(wiphy_index_received_); |
| if (!ConfigureDisableWakeOnWiFiMessage(&disable_wowlan_msg, wiphy_index_, |
| &error)) { |
| LOG(ERROR) << error.message(); |
| RunAndResetSuspendActionsDoneCallback( |
| Error(Error::kOperationFailed, error.message())); |
| return; |
| } |
| wake_on_wifi_triggers_.clear(); |
| if (!netlink_manager_->SendNl80211Message( |
| &disable_wowlan_msg, |
| Bind(&WakeOnWiFi::OnSetWakeOnPacketConnectionResponse), |
| Bind(&NetlinkManager::OnAckDoNothing), |
| Bind(&WakeOnWiFi::OnWakeOnWiFiSettingsErrorResponse, |
| weak_ptr_factory_.GetWeakPtr()))) { |
| RunAndResetSuspendActionsDoneCallback( |
| Error(Error::kOperationFailed, "SendNl80211Message failed")); |
| return; |
| } |
| |
| verify_wake_on_packet_settings_callback_.Reset( |
| Bind(&WakeOnWiFi::RequestWakeOnPacketSettings, |
| weak_ptr_factory_.GetWeakPtr())); |
| dispatcher_->PostDelayedTask( |
| verify_wake_on_packet_settings_callback_.callback(), |
| kVerifyWakeOnWiFiSettingsDelayMilliseconds); |
| } |
| |
| void WakeOnWiFi::RetrySetWakeOnPacketConnections() { |
| SLOG(this, 3) << __func__; |
| if (num_set_wake_on_packet_retries_ < kMaxSetWakeOnPacketRetries) { |
| ApplyWakeOnWiFiSettings(); |
| ++num_set_wake_on_packet_retries_; |
| } else { |
| SLOG(this, 3) << __func__ << ": max retry attempts reached"; |
| num_set_wake_on_packet_retries_ = 0; |
| RunAndResetSuspendActionsDoneCallback(Error(Error::kOperationFailed)); |
| } |
| } |
| |
| bool WakeOnWiFi::WakeOnPacketEnabledAndSupported() { |
| if (wake_on_wifi_features_enabled_ == kWakeOnWiFiFeaturesEnabledNone || |
| wake_on_wifi_features_enabled_ == |
| kWakeOnWiFiFeaturesEnabledNotSupported || |
| wake_on_wifi_features_enabled_ == kWakeOnWiFiFeaturesEnabledSSID) { |
| return false; |
| } |
| if (wake_on_wifi_triggers_supported_.find(kWakeTriggerPattern) == |
| wake_on_wifi_triggers_supported_.end()) { |
| return false; |
| } |
| return true; |
| } |
| |
| bool WakeOnWiFi::WakeOnSSIDEnabledAndSupported() { |
| if (wake_on_wifi_features_enabled_ == kWakeOnWiFiFeaturesEnabledNone || |
| wake_on_wifi_features_enabled_ == |
| kWakeOnWiFiFeaturesEnabledNotSupported || |
| wake_on_wifi_features_enabled_ == kWakeOnWiFiFeaturesEnabledPacket) { |
| return false; |
| } |
| if (wake_on_wifi_triggers_supported_.find(kWakeTriggerDisconnect) == |
| wake_on_wifi_triggers_supported_.end() || |
| wake_on_wifi_triggers_supported_.find(kWakeTriggerSSID) == |
| wake_on_wifi_triggers_supported_.end()) { |
| return false; |
| } |
| return true; |
| } |
| |
| void WakeOnWiFi::ReportMetrics() { |
| Metrics::WakeOnWiFiFeaturesEnabledState reported_state; |
| if (wake_on_wifi_features_enabled_ == kWakeOnWiFiFeaturesEnabledNone) { |
| reported_state = Metrics::kWakeOnWiFiFeaturesEnabledStateNone; |
| } else if (wake_on_wifi_features_enabled_ == |
| kWakeOnWiFiFeaturesEnabledPacket) { |
| reported_state = Metrics::kWakeOnWiFiFeaturesEnabledStatePacket; |
| } else if (wake_on_wifi_features_enabled_ == kWakeOnWiFiFeaturesEnabledSSID) { |
| reported_state = Metrics::kWakeOnWiFiFeaturesEnabledStateSSID; |
| } else if (wake_on_wifi_features_enabled_ == |
| kWakeOnWiFiFeaturesEnabledPacketSSID) { |
| reported_state = Metrics::kWakeOnWiFiFeaturesEnabledStatePacketSSID; |
| } else { |
| LOG(ERROR) << __func__ << ": " |
| << "Invalid wake on WiFi features state"; |
| return; |
| } |
| metrics_->NotifyWakeOnWiFiFeaturesEnabledState(reported_state); |
| StartMetricsTimer(); |
| } |
| |
| void WakeOnWiFi::ParseWakeOnWiFiCapabilities( |
| const Nl80211Message &nl80211_message) { |
| // Verify NL80211_CMD_NEW_WIPHY. |
| if (nl80211_message.command() != NewWiphyMessage::kCommand) { |
| LOG(ERROR) << "Received unexpected command:" << nl80211_message.command(); |
| return; |
| } |
| AttributeListConstRefPtr triggers_supported; |
| if (nl80211_message.const_attributes()->ConstGetNestedAttributeList( |
| NL80211_ATTR_WOWLAN_TRIGGERS_SUPPORTED, &triggers_supported)) { |
| bool disconnect_supported = false; |
| if (triggers_supported->GetFlagAttributeValue( |
| NL80211_WOWLAN_TRIG_DISCONNECT, &disconnect_supported)) { |
| if (disconnect_supported) { |
| wake_on_wifi_triggers_supported_.insert( |
| WakeOnWiFi::kWakeTriggerDisconnect); |
| SLOG(this, 7) << "Waking on disconnect supported by this WiFi device"; |
| } |
| } |
| ByteString pattern_data; |
| if (triggers_supported->GetRawAttributeValue( |
| NL80211_WOWLAN_TRIG_PKT_PATTERN, &pattern_data)) { |
| struct nl80211_pattern_support *patt_support = |
| reinterpret_cast<struct nl80211_pattern_support *>( |
| pattern_data.GetData()); |
| // Determine the IPV4 and IPV6 pattern lengths we will use by |
| // constructing dummy patterns and getting their lengths. |
| ByteString dummy_pattern; |
| ByteString dummy_mask; |
| WakeOnWiFi::CreateIPV4PatternAndMask(IPAddress("192.168.0.20"), |
| &dummy_pattern, &dummy_mask); |
| size_t ipv4_pattern_len = dummy_pattern.GetLength(); |
| WakeOnWiFi::CreateIPV6PatternAndMask( |
| IPAddress("FEDC:BA98:7654:3210:FEDC:BA98:7654:3210"), &dummy_pattern, |
| &dummy_mask); |
| size_t ipv6_pattern_len = dummy_pattern.GetLength(); |
| // Check if the pattern matching capabilities of this WiFi device will |
| // allow IPV4 and IPV6 patterns to be used. |
| if (patt_support->min_pattern_len <= |
| std::min(ipv4_pattern_len, ipv6_pattern_len) && |
| patt_support->max_pattern_len >= |
| std::max(ipv4_pattern_len, ipv6_pattern_len)) { |
| wake_on_wifi_triggers_supported_.insert( |
| WakeOnWiFi::kWakeTriggerPattern); |
| wake_on_wifi_max_patterns_ = patt_support->max_patterns; |
| SLOG(this, 7) << "Waking on up to " << wake_on_wifi_max_patterns_ |
| << " registered patterns of " |
| << patt_support->min_pattern_len << "-" |
| << patt_support->max_pattern_len |
| << " bytes supported by this WiFi device"; |
| } |
| } |
| if (triggers_supported->GetU32AttributeValue(NL80211_WOWLAN_TRIG_NET_DETECT, |
| &wake_on_wifi_max_ssids_)) { |
| wake_on_wifi_triggers_supported_.insert(WakeOnWiFi::kWakeTriggerSSID); |
| SLOG(this, 7) << "Waking on up to " << wake_on_wifi_max_ssids_ |
| << " whitelisted SSIDs supported by this WiFi device"; |
| } |
| } |
| } |
| |
| void WakeOnWiFi::OnWakeupReasonReceived(const NetlinkMessage &netlink_message) { |
| #if defined(DISABLE_WAKE_ON_WIFI) |
| SLOG(this, 7) << __func__ << ": " |
| << "Wake on WiFi not supported, so do nothing"; |
| #else |
| // We only handle wakeup reason messages in this handler, which is are |
| // nl80211 messages with the NL80211_CMD_SET_WOWLAN command. |
| if (netlink_message.message_type() != Nl80211Message::GetMessageType()) { |
| SLOG(this, 7) << __func__ << ": " |
| << "Not a NL80211 Message"; |
| return; |
| } |
| const Nl80211Message &wakeup_reason_msg = |
| *reinterpret_cast<const Nl80211Message *>(&netlink_message); |
| if (wakeup_reason_msg.command() != SetWakeOnPacketConnMessage::kCommand) { |
| SLOG(this, 7) << __func__ << ": " |
| << "Not a NL80211_CMD_SET_WOWLAN message"; |
| return; |
| } |
| uint32_t wiphy_index; |
| if (!wakeup_reason_msg.const_attributes()->GetU32AttributeValue( |
| NL80211_ATTR_WIPHY, &wiphy_index)) { |
| LOG(ERROR) << "NL80211_CMD_NEW_WIPHY had no NL80211_ATTR_WIPHY"; |
| return; |
| } |
| if (!wiphy_index_received_) { |
| SLOG(this, 7) << __func__ << ": " |
| << "Interface index not yet received"; |
| return; |
| } |
| if (wiphy_index != wiphy_index_) { |
| SLOG(this, 7) << __func__ << ": " |
| << "Wakeup reason not meant for this interface"; |
| return; |
| } |
| metrics_->NotifyWakeupReasonReceived(); |
| SLOG(this, 3) << __func__ << ": " |
| << "Parsing wakeup reason"; |
| AttributeListConstRefPtr triggers; |
| if (!wakeup_reason_msg.const_attributes()->ConstGetNestedAttributeList( |
| NL80211_ATTR_WOWLAN_TRIGGERS, &triggers)) { |
| SLOG(this, 3) << __func__ << ": " |
| << "Wakeup reason: Not wake on WiFi related"; |
| return; |
| } |
| bool wake_flag; |
| if (triggers->GetFlagAttributeValue(NL80211_WOWLAN_TRIG_DISCONNECT, |
| &wake_flag)) { |
| SLOG(this, 3) << __func__ << ": " |
| << "Wakeup reason: Disconnect"; |
| last_wake_reason_ = kWakeTriggerDisconnect; |
| return; |
| } |
| uint32_t wake_pattern_index; |
| if (triggers->GetU32AttributeValue(NL80211_WOWLAN_TRIG_PKT_PATTERN, |
| &wake_pattern_index)) { |
| SLOG(this, 3) << __func__ << ": " |
| << "Wakeup reason: Pattern " << wake_pattern_index; |
| last_wake_reason_ = kWakeTriggerPattern; |
| return; |
| } |
| AttributeListConstRefPtr results_list; |
| if (triggers->ConstGetNestedAttributeList( |
| NL80211_WOWLAN_TRIG_NET_DETECT_RESULTS, &results_list)) { |
| // It is possible that NL80211_WOWLAN_TRIG_NET_DETECT_RESULTS is present |
| // along with another wake trigger attribute. What this means is that the |
| // firmware has detected a network, but the platform did not actually wake |
| // on the detection of that network. In these cases, we will not parse the |
| // net detect results; we return after parsing and reporting the actual |
| // wakeup reason above. |
| SLOG(this, 3) << __func__ << ": " |
| << "Wakeup reason: SSID"; |
| last_wake_reason_ = kWakeTriggerSSID; |
| last_ssid_match_freqs_ = ParseWakeOnWakeOnSSIDResults(results_list); |
| return; |
| } |
| SLOG(this, 3) << __func__ << ": " |
| << "Wakeup reason: Not supported"; |
| #endif // DISABLE_WAKE_ON_WIFI |
| } |
| |
| void WakeOnWiFi::OnBeforeSuspend( |
| bool is_connected, |
| const vector<ByteString> &ssid_whitelist, |
| const ResultCallback &done_callback, |
| const Closure &renew_dhcp_lease_callback, |
| const Closure &remove_supplicant_networks_callback, bool have_dhcp_lease, |
| uint32_t time_to_next_lease_renewal) { |
| #if defined(DISABLE_WAKE_ON_WIFI) |
| // Wake on WiFi not supported, so immediately report success. |
| done_callback.Run(Error(Error::kSuccess)); |
| #else |
| LOG(INFO) << __func__ << ": Wake on WiFi features enabled: " |
| << wake_on_wifi_features_enabled_; |
| suspend_actions_done_callback_ = done_callback; |
| wake_on_ssid_whitelist_ = ssid_whitelist; |
| dark_resume_history_.Clear(); |
| if (have_dhcp_lease && is_connected && |
| time_to_next_lease_renewal < kImmediateDHCPLeaseRenewalThresholdSeconds) { |
| // Renew DHCP lease immediately if we have one that is expiring soon. |
| renew_dhcp_lease_callback.Run(); |
| dispatcher_->PostTask(Bind(&WakeOnWiFi::BeforeSuspendActions, |
| weak_ptr_factory_.GetWeakPtr(), is_connected, |
| false, time_to_next_lease_renewal, |
| remove_supplicant_networks_callback)); |
| } else { |
| dispatcher_->PostTask(Bind(&WakeOnWiFi::BeforeSuspendActions, |
| weak_ptr_factory_.GetWeakPtr(), is_connected, |
| have_dhcp_lease, time_to_next_lease_renewal, |
| remove_supplicant_networks_callback)); |
| } |
| #endif // DISABLE_WAKE_ON_WIFI |
| } |
| |
| void WakeOnWiFi::OnAfterResume() { |
| #if !defined(DISABLE_WAKE_ON_WIFI) |
| SLOG(this, 1) << __func__; |
| wake_to_scan_timer_.Stop(); |
| dhcp_lease_renewal_timer_.Stop(); |
| if (WakeOnPacketEnabledAndSupported() || WakeOnSSIDEnabledAndSupported()) { |
| // Unconditionally disable wake on WiFi on resume if these features |
| // were enabled before the last suspend. |
| DisableWakeOnWiFi(); |
| metrics_->NotifySuspendWithWakeOnWiFiEnabledDone(); |
| } |
| #endif // DISABLE_WAKE_ON_WIFI |
| } |
| |
| void WakeOnWiFi::OnDarkResume( |
| bool is_connected, |
| const vector<ByteString> &ssid_whitelist, |
| const ResultCallback &done_callback, |
| const Closure &renew_dhcp_lease_callback, |
| const InitiateScanCallback &initiate_scan_callback, |
| const Closure &remove_supplicant_networks_callback) { |
| #if defined(DISABLE_WAKE_ON_WIFI) |
| done_callback.Run(Error(Error::kSuccess)); |
| #else |
| LOG(INFO) << __func__ << ": " |
| << "Wake reason " << last_wake_reason_; |
| metrics_->NotifyWakeOnWiFiOnDarkResume(last_wake_reason_); |
| dark_resume_scan_retries_left_ = 0; |
| suspend_actions_done_callback_ = done_callback; |
| wake_on_ssid_whitelist_ = ssid_whitelist; |
| |
| if (!is_connected) { |
| // Only record dark resume events where we wake up disconnected, since there |
| // are valid scenarios where we would dark resume frequently in a connected |
| // state (e.g. when wake on packet is enabled when there is a brief spike in |
| // incoming network traffic). We check the connection status on dark resume |
| // rather than the |last_wake_reason_| in case the WiFi driver does not |
| // support wakeup reason reporting. |
| dark_resume_history_.RecordEvent(); |
| } |
| if (dark_resume_history_.CountEventsWithinInterval( |
| kDarkResumeFrequencySamplingPeriodShortMinutes * 60, |
| EventHistory::kClockTypeBoottime) >= kMaxDarkResumesPerPeriodShort || |
| dark_resume_history_.CountEventsWithinInterval( |
| kDarkResumeFrequencySamplingPeriodLongMinutes * 60, |
| EventHistory::kClockTypeBoottime) >= kMaxDarkResumesPerPeriodLong) { |
| LOG(ERROR) << __func__ << ": " |
| << "Too many dark resumes; disabling wake on WiFi temporarily"; |
| // If too many dark resumes have triggered recently, we are probably |
| // thrashing. Stop this by disabling wake on WiFi on the NIC, and |
| // starting the wake to scan timer so that normal wake on WiFi behavior |
| // resumes only |wake_to_scan_period_seconds_| later. |
| dhcp_lease_renewal_timer_.Stop(); |
| wake_to_scan_timer_.Start( |
| FROM_HERE, base::TimeDelta::FromSeconds(wake_to_scan_period_seconds_), |
| Bind(&WakeOnWiFi::OnTimerWakeDoNothing, base::Unretained(this))); |
| DisableWakeOnWiFi(); |
| dark_resume_history_.Clear(); |
| metrics_->NotifyWakeOnWiFiThrottled(); |
| last_ssid_match_freqs_.clear(); |
| return; |
| } |
| |
| switch (last_wake_reason_) { |
| case kWakeTriggerPattern: { |
| // Go back to suspend immediately since packet would have been delivered |
| // to userspace upon waking in dark resume. Do not reset the lease renewal |
| // timer since we are not getting a new lease. |
| dispatcher_->PostTask(Bind( |
| &WakeOnWiFi::BeforeSuspendActions, weak_ptr_factory_.GetWeakPtr(), |
| is_connected, false, 0, remove_supplicant_networks_callback)); |
| break; |
| } |
| case kWakeTriggerSSID: |
| case kWakeTriggerDisconnect: { |
| remove_supplicant_networks_callback.Run(); |
| metrics_->NotifyDarkResumeInitiateScan(); |
| InitiateScanInDarkResume(initiate_scan_callback, |
| last_wake_reason_ == kWakeTriggerSSID |
| ? last_ssid_match_freqs_ |
| : WiFi::FreqSet()); |
| break; |
| } |
| case kWakeTriggerUnsupported: |
| default: { |
| if (is_connected) { |
| renew_dhcp_lease_callback.Run(); |
| } else { |
| remove_supplicant_networks_callback.Run(); |
| metrics_->NotifyDarkResumeInitiateScan(); |
| InitiateScanInDarkResume(initiate_scan_callback, WiFi::FreqSet()); |
| } |
| } |
| } |
| |
| // Only set dark resume to true after checking if we need to disable wake on |
| // WiFi since calling WakeOnWiFi::DisableWakeOnWiFi directly bypasses |
| // WakeOnWiFi::BeforeSuspendActions where |in_dark_resume_| is set to false. |
| in_dark_resume_ = true; |
| // Assume that we are disconnected if we time out. Consequently, we do not |
| // need to start a DHCP lease renewal timer. |
| dark_resume_actions_timeout_callback_.Reset( |
| Bind(&WakeOnWiFi::BeforeSuspendActions, weak_ptr_factory_.GetWeakPtr(), |
| false, false, 0, remove_supplicant_networks_callback)); |
| dispatcher_->PostDelayedTask(dark_resume_actions_timeout_callback_.callback(), |
| DarkResumeActionsTimeoutMilliseconds); |
| #endif // DISABLE_WAKE_ON_WIFI |
| } |
| |
| void WakeOnWiFi::BeforeSuspendActions( |
| bool is_connected, |
| bool start_lease_renewal_timer, |
| uint32_t time_to_next_lease_renewal, |
| const Closure &remove_supplicant_networks_callback) { |
| LOG(INFO) << __func__ << ": " |
| << (is_connected ? "connected" : "not connected"); |
| // Note: No conditional compilation because all entry points to this functions |
| // are already conditionally compiled based on DISABLE_WAKE_ON_WIFI. |
| |
| metrics_->NotifyBeforeSuspendActions(is_connected, in_dark_resume_); |
| last_ssid_match_freqs_.clear(); |
| last_wake_reason_ = kWakeTriggerUnsupported; |
| // Add relevant triggers to be programmed into the NIC. |
| wake_on_wifi_triggers_.clear(); |
| if (!wake_on_packet_connections_.Empty() && |
| WakeOnPacketEnabledAndSupported() && is_connected) { |
| SLOG(this, 3) << __func__ << ": " |
| << "Enabling wake on pattern"; |
| wake_on_wifi_triggers_.insert(kWakeTriggerPattern); |
| } |
| if (WakeOnSSIDEnabledAndSupported()) { |
| if (is_connected) { |
| SLOG(this, 3) << __func__ << ": " |
| << "Enabling wake on disconnect"; |
| wake_on_wifi_triggers_.insert(kWakeTriggerDisconnect); |
| wake_on_wifi_triggers_.erase(kWakeTriggerSSID); |
| wake_to_scan_timer_.Stop(); |
| if (start_lease_renewal_timer) { |
| // Timer callback is NO-OP since dark resume logic (the |
| // kWakeTriggerUnsupported case) will initiate DHCP lease renewal. |
| dhcp_lease_renewal_timer_.Start( |
| FROM_HERE, base::TimeDelta::FromSeconds(time_to_next_lease_renewal), |
| Bind(&WakeOnWiFi::OnTimerWakeDoNothing, base::Unretained(this))); |
| } |
| } else { |
| // Force a disconnect in case supplicant is currently in the process of |
| // connecting, and remove all networks so scans triggered in dark resume |
| // are passive. |
| remove_supplicant_networks_callback.Run(); |
| dhcp_lease_renewal_timer_.Stop(); |
| wake_on_wifi_triggers_.erase(kWakeTriggerDisconnect); |
| if (!wake_on_ssid_whitelist_.empty()) { |
| SLOG(this, 3) << __func__ << ": " |
| << "Enabling wake on SSID"; |
| wake_on_wifi_triggers_.insert(kWakeTriggerSSID); |
| } |
| int num_extra_ssids = |
| wake_on_ssid_whitelist_.size() - wake_on_wifi_max_ssids_; |
| if (num_extra_ssids > 0 || force_wake_to_scan_timer_) { |
| SLOG(this, 3) << __func__ << ": " |
| << "Starting wake to scan timer - " |
| << (num_extra_ssids > 0 ? "extra SSIDs" : "forced"); |
| if (num_extra_ssids > 0) { |
| SLOG(this, 3) << __func__ << ": " << num_extra_ssids |
| << " extra SSIDs."; |
| } |
| // Start wake to scan timer in case the only SSIDs available for |
| // auto-connect during suspend are the ones that we do not program our |
| // NIC to wake on. |
| // Timer callback is NO-OP since dark resume logic (the |
| // kWakeTriggerUnsupported case) will initiate a passive scan. |
| wake_to_scan_timer_.Start( |
| FROM_HERE, |
| base::TimeDelta::FromSeconds(wake_to_scan_period_seconds_), |
| Bind(&WakeOnWiFi::OnTimerWakeDoNothing, base::Unretained(this))); |
| // Trim SSID list to the max size that the NIC supports. |
| wake_on_ssid_whitelist_.resize(wake_on_wifi_max_ssids_); |
| } |
| } |
| } |
| |
| // Only call Cancel() here since it deallocates the underlying callback that |
| // |remove_supplicant_networks_callback| references, which is invoked above. |
| dark_resume_actions_timeout_callback_.Cancel(); |
| |
| if (!in_dark_resume_ && wake_on_wifi_triggers_.empty()) { |
| // No need program NIC on normal resume in this case since wake on WiFi |
| // would already have been disabled on the last (non-dark) resume. |
| SLOG(this, 1) << "No need to disable wake on WiFi on NIC in regular " |
| "suspend"; |
| RunAndResetSuspendActionsDoneCallback(Error(Error::kSuccess)); |
| return; |
| } |
| |
| in_dark_resume_ = false; |
| ApplyWakeOnWiFiSettings(); |
| } |
| |
| // static |
| WiFi::FreqSet WakeOnWiFi::ParseWakeOnWakeOnSSIDResults( |
| AttributeListConstRefPtr results_list) { |
| WiFi::FreqSet freqs; |
| AttributeIdIterator results_iter(*results_list); |
| if (results_iter.AtEnd()) { |
| SLOG(WiFi, nullptr, 3) << __func__ << ": " |
| << "Wake on SSID results not available"; |
| return freqs; |
| } |
| AttributeListConstRefPtr result; |
| int ssid_num = 0; |
| for (; !results_iter.AtEnd(); results_iter.Advance()) { |
| if (!results_list->ConstGetNestedAttributeList(results_iter.GetId(), |
| &result)) { |
| LOG(ERROR) << __func__ << ": " |
| << "Could not get result #" << results_iter.GetId() |
| << " in ssid_results"; |
| return freqs; |
| } |
| ByteString ssid_bytestring; |
| if (!result->GetRawAttributeValue(NL80211_ATTR_SSID, &ssid_bytestring)) { |
| // We assume that the SSID attribute must be present in each result. |
| LOG(ERROR) << __func__ << ": " |
| << "No SSID available for result #" << results_iter.GetId(); |
| continue; |
| } |
| SLOG(WiFi, nullptr, 3) << "SSID " << ssid_num << ": " |
| << std::string(ssid_bytestring.GetConstData(), |
| ssid_bytestring.GetConstData() + |
| ssid_bytestring.GetLength()); |
| AttributeListConstRefPtr frequencies; |
| uint32_t freq_value; |
| if (result->ConstGetNestedAttributeList(NL80211_ATTR_SCAN_FREQUENCIES, |
| &frequencies)) { |
| AttributeIdIterator freq_iter(*frequencies); |
| for (; !freq_iter.AtEnd(); freq_iter.Advance()) { |
| if (frequencies->GetU32AttributeValue(freq_iter.GetId(), &freq_value)) { |
| freqs.insert(freq_value); |
| SLOG(WiFi, nullptr, 7) << "Frequency: " << freq_value; |
| } |
| } |
| } else { |
| SLOG(WiFi, nullptr, 3) << __func__ << ": " |
| << "No frequencies available for result #" |
| << results_iter.GetId(); |
| } |
| ++ssid_num; |
| } |
| return freqs; |
| } |
| |
| void WakeOnWiFi::InitiateScanInDarkResume( |
| const InitiateScanCallback &initiate_scan_callback, |
| const WiFi::FreqSet &freqs) { |
| SLOG(this, 3) << __func__; |
| if (!freqs.empty() && freqs.size() <= kMaxFreqsForDarkResumeScanRetries) { |
| SLOG(this, 3) << __func__ << ": " |
| << "Allowing up to " << kMaxDarkResumeScanRetries |
| << " retries for passive scan on " << freqs.size() |
| << " frequencies"; |
| dark_resume_scan_retries_left_ = kMaxDarkResumeScanRetries; |
| } |
| initiate_scan_callback.Run(freqs); |
| } |
| |
| void WakeOnWiFi::OnDHCPLeaseObtained(bool start_lease_renewal_timer, |
| uint32_t time_to_next_lease_renewal) { |
| SLOG(this, 3) << __func__; |
| if (in_dark_resume_) { |
| #if defined(DISABLE_WAKE_ON_WIFI) |
| SLOG(this, 3) << "Wake on WiFi not supported, so do nothing"; |
| #else |
| // If we obtain a DHCP lease, we are connected, so the callback to have |
| // supplicant remove networks will not be invoked in |
| // WakeOnWiFi::BeforeSuspendActions. |
| BeforeSuspendActions(true, start_lease_renewal_timer, |
| time_to_next_lease_renewal, base::Closure()); |
| #endif // DISABLE_WAKE_ON_WIFI |
| } else { |
| SLOG(this, 3) << "Not in dark resume, so do nothing"; |
| } |
| } |
| |
| void WakeOnWiFi::ReportConnectedToServiceAfterWake(bool is_connected) { |
| #if defined(DISABLE_WAKE_ON_WIFI) |
| metrics_->NotifyConnectedToServiceAfterWake( |
| is_connected |
| ? Metrics::kWiFiConnetionStatusAfterWakeOnWiFiDisabledWakeConnected |
| : Metrics:: |
| kWiFiConnetionStatusAfterWakeOnWiFiDisabledWakeNotConnected); |
| #else |
| if (WakeOnSSIDEnabledAndSupported()) { |
| // Only logged if wake on WiFi is supported and wake on SSID was enabled to |
| // maintain connectivity while suspended. |
| metrics_->NotifyConnectedToServiceAfterWake( |
| is_connected |
| ? Metrics::kWiFiConnetionStatusAfterWakeOnWiFiEnabledWakeConnected |
| : Metrics:: |
| kWiFiConnetionStatusAfterWakeOnWiFiEnabledWakeNotConnected); |
| } else { |
| metrics_->NotifyConnectedToServiceAfterWake( |
| is_connected |
| ? Metrics::kWiFiConnetionStatusAfterWakeOnWiFiDisabledWakeConnected |
| : Metrics:: |
| kWiFiConnetionStatusAfterWakeOnWiFiDisabledWakeNotConnected); |
| } |
| #endif // DISABLE_WAKE_ON_WIFI |
| } |
| |
| void WakeOnWiFi::OnNoAutoConnectableServicesAfterScan( |
| const vector<ByteString> &ssid_whitelist, |
| const Closure &remove_supplicant_networks_callback, |
| const InitiateScanCallback &initiate_scan_callback) { |
| #if !defined(DISABLE_WAKE_ON_WIFI) |
| SLOG(this, 3) << __func__ << ": " |
| << (in_dark_resume_ ? "In dark resume" : "Not in dark resume"); |
| if (!in_dark_resume_) { |
| return; |
| } |
| if (dark_resume_scan_retries_left_) { |
| --dark_resume_scan_retries_left_; |
| SLOG(this, 3) << __func__ << ": " |
| << "Retrying dark resume scan (" |
| << dark_resume_scan_retries_left_ << " tries left)"; |
| metrics_->NotifyDarkResumeScanRetry(); |
| // Note: a scan triggered by supplicant in dark resume might cause a |
| // retry, but we consider this acceptable. |
| initiate_scan_callback.Run(last_ssid_match_freqs_); |
| } else { |
| wake_on_ssid_whitelist_ = ssid_whitelist; |
| // Assume that if there are no services available for auto-connect, then we |
| // cannot be connected. Therefore, no need for lease renewal parameters. |
| BeforeSuspendActions(false, false, 0, remove_supplicant_networks_callback); |
| } |
| #endif // DISABLE_WAKE_ON_WIFI |
| } |
| |
| void WakeOnWiFi::OnWiphyIndexReceived(uint32_t index) { |
| wiphy_index_ = index; |
| wiphy_index_received_ = true; |
| } |
| |
| void WakeOnWiFi::OnScanStarted(bool is_active_scan) { |
| if (!in_dark_resume_) { |
| return; |
| } |
| if (last_wake_reason_ == kWakeTriggerUnsupported || |
| last_wake_reason_ == kWakeTriggerPattern) { |
| // We don't expect active scans to be started when we wake on pattern or |
| // RTC timers. |
| if (is_active_scan) { |
| LOG(ERROR) << "Unexpected active scan launched in dark resume"; |
| } |
| metrics_->NotifyScanStartedInDarkResume(is_active_scan); |
| } |
| } |
| |
| } // namespace shill |