blob: 9dfb282f12395defc25f2da84b4a9376830174ae [file] [log] [blame]
// 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/wake_on_wifi.h"
#include <errno.h>
#include <linux/nl80211.h>
#include <stdio.h>
#include <sys/timerfd.h>
#include <algorithm>
#include <set>
#include <string>
#include <utility>
#include <vector>
#include <base/cancelable_callback.h>
#include <base/check.h>
#include <base/check_op.h>
#include <base/containers/contains.h>
#include <base/logging.h>
#include <base/strings/string_number_conversions.h>
#include <chromeos/dbus/service_constants.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"
namespace shill {
namespace Logging {
static auto kModuleLogScope = ScopeLogger::kWiFi;
static std::string ObjectID(const WakeOnWiFi* w) {
return "(wake_on_wifi)";
}
} // namespace Logging
const char WakeOnWiFi::kWakeOnWiFiNotAllowed[] = "Wake on WiFi not allowed";
const int WakeOnWiFi::kVerifyWakeOnWiFiSettingsDelayMilliseconds = 300;
const int WakeOnWiFi::kMaxSetWakeOnWiFiRetries = 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,
RecordWakeReasonCallback record_wake_reason_callback)
: dispatcher_(dispatcher),
netlink_manager_(netlink_manager),
metrics_(metrics),
report_metrics_callback_(
base::Bind(&WakeOnWiFi::ReportMetrics, base::Unretained(this))),
num_set_wake_on_wifi_retries_(0),
wake_on_wifi_max_ssids_(0),
wiphy_index_(0),
wiphy_index_received_(false),
wake_on_wifi_allowed_(false),
// 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),
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),
connected_before_suspend_(false),
record_wake_reason_callback_(record_wake_reason_callback),
weak_ptr_factory_(this) {
netlink_handler_ = base::Bind(&WakeOnWiFi::OnWakeupReasonReceived,
weak_ptr_factory_.GetWeakPtr());
netlink_manager_->AddBroadcastHandler(netlink_handler_);
dhcp_lease_renewal_timer_ = timers::SimpleAlarmTimer::Create();
wake_to_scan_timer_ = timers::SimpleAlarmTimer::Create();
}
WakeOnWiFi::~WakeOnWiFi() {
netlink_manager_->RemoveBroadcastHandler(netlink_handler_);
}
void WakeOnWiFi::InitPropertyStore(PropertyStore* store) {
store->RegisterDerivedBool(kWakeOnWiFiAllowedProperty,
BoolAccessor(new CustomAccessor<WakeOnWiFi, bool>(
this, &WakeOnWiFi::GetWakeOnWiFiAllowed,
&WakeOnWiFi::SetWakeOnWiFiAllowed)));
store->RegisterDerivedString(
kWakeOnWiFiFeaturesEnabledProperty,
StringAccessor(new CustomAccessor<WakeOnWiFi, std::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_);
store->RegisterDerivedString(
kLastWakeReasonProperty,
StringAccessor(new CustomAccessor<WakeOnWiFi, std::string>(
this, &WakeOnWiFi::GetLastWakeReason, nullptr)));
}
void WakeOnWiFi::StartMetricsTimer() {
dispatcher_->PostDelayedTask(FROM_HERE, report_metrics_callback_.callback(),
kMetricsReportingFrequencySeconds * 1000);
}
void WakeOnWiFi::Start() {
StartMetricsTimer();
}
bool WakeOnWiFi::GetWakeOnWiFiAllowed(Error* /*error*/) {
return wake_on_wifi_allowed_;
}
bool WakeOnWiFi::SetWakeOnWiFiAllowed(const bool& allowed, Error* error) {
if (wake_on_wifi_allowed_ == allowed) {
return false;
}
// Disable all WiFi features first.
if (!allowed) {
SetWakeOnWiFiFeaturesEnabled(kWakeOnWiFiFeaturesEnabledNone, error);
}
wake_on_wifi_allowed_ = allowed;
return true;
}
std::string WakeOnWiFi::GetWakeOnWiFiFeaturesEnabled(Error* error) {
return wake_on_wifi_features_enabled_;
}
bool WakeOnWiFi::SetWakeOnWiFiFeaturesEnabled(const std::string& enabled,
Error* error) {
if (!wake_on_wifi_allowed_) {
error->Populate(Error::kNotSupported, kWakeOnWiFiNotAllowed);
SLOG(this, 7) << __func__ << ": " << kWakeOnWiFiNotAllowed;
return false;
}
if (wake_on_wifi_features_enabled_ == enabled) {
return false;
}
if (enabled != kWakeOnWiFiFeaturesEnabledDarkConnect &&
enabled != kWakeOnWiFiFeaturesEnabledNone) {
Error::PopulateAndLog(FROM_HERE, error, Error::kInvalidArguments,
"Invalid Wake on WiFi feature");
return false;
}
wake_on_wifi_features_enabled_ = enabled;
return true;
}
std::string WakeOnWiFi::GetLastWakeReason(Error* /*error*/) {
switch (last_wake_reason_) {
case kWakeTriggerDisconnect:
return kWakeOnWiFiReasonDisconnect;
case kWakeTriggerSSID:
return kWakeOnWiFiReasonSSID;
default:
return kWakeOnWiFiReasonUnknown;
}
}
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::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(SetWakeOnWiFiMessage* 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(
SetWakeOnWiFiMessage* msg,
const std::set<WakeOnWiFiTrigger>& trigs,
uint32_t wiphy_index,
uint32_t net_detect_scan_period_seconds,
const std::vector<ByteString>& allowed_ssids,
Error* error) {
if (trigs.empty()) {
Error::PopulateAndLog(FROM_HERE, error, Error::kInvalidArguments,
"No triggers 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 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 : allowed_ssids) {
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::ConfigureGetWakeOnWiFiSettingsMessage(
GetWakeOnWiFiMessage* 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 std::set<WakeOnWiFiTrigger>& trigs,
uint32_t net_detect_scan_period_seconds,
const std::vector<ByteString>& allowed_ssids) {
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) &&
!base::Contains(trigs, kWakeTriggerDisconnect)) {
SLOG(WiFi, nullptr, 3)
<< __func__ << ": Wake on disconnect trigger not expected but found";
return false;
}
if (triggers->ConstGetNestedAttributeList(NL80211_WOWLAN_TRIG_NET_DETECT,
&unused_list) &&
!base::Contains(trigs, kWakeTriggerSSID)) {
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 kWakeTriggerSSID: {
std::set<ByteString, decltype(&ByteString::IsLessThan)> expected_ssids(
allowed_ssids.begin(), allowed_ssids.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 (!base::Contains(expected_ssids, ssid)) {
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::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 =
static_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::OnSetWakeOnWiFiConnectionResponse(
const Nl80211Message& nl80211_message) {
// NOP because kernel does not send a response to NL80211_CMD_SET_WOWLAN
// requests.
}
void WakeOnWiFi::RequestWakeOnWiFiSettings() {
SLOG(this, 3) << __func__;
Error e;
GetWakeOnWiFiMessage 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,
base::Bind(&WakeOnWiFi::VerifyWakeOnWiFiSettings,
weak_ptr_factory_.GetWeakPtr()),
base::Bind(&NetlinkManager::OnAckDoNothing),
base::Bind(&NetlinkManager::OnNetlinkMessageError));
}
void WakeOnWiFi::VerifyWakeOnWiFiSettings(
const Nl80211Message& nl80211_message) {
SLOG(this, 3) << __func__;
if (WakeOnWiFiSettingsMatch(nl80211_message, wake_on_wifi_triggers_,
net_detect_scan_period_seconds_,
wake_on_allowed_ssids_)) {
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);
RetrySetWakeOnWiFiConnections();
}
}
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;
SetWakeOnWiFiMessage set_wowlan_msg;
if (!ConfigureSetWakeOnWiFiSettingsMessage(
&set_wowlan_msg, wake_on_wifi_triggers_, wiphy_index_,
net_detect_scan_period_seconds_, wake_on_allowed_ssids_, &error)) {
LOG(ERROR) << error.message();
RunAndResetSuspendActionsDoneCallback(
Error(Error::kOperationFailed, error.message()));
return;
}
if (!netlink_manager_->SendNl80211Message(
&set_wowlan_msg,
base::Bind(&WakeOnWiFi::OnSetWakeOnWiFiConnectionResponse),
base::Bind(&NetlinkManager::OnAckDoNothing),
base::Bind(&WakeOnWiFi::OnWakeOnWiFiSettingsErrorResponse,
weak_ptr_factory_.GetWeakPtr()))) {
RunAndResetSuspendActionsDoneCallback(
Error(Error::kOperationFailed, "SendNl80211Message failed"));
return;
}
verify_wake_on_wifi_settings_callback_.Reset(base::Bind(
&WakeOnWiFi::RequestWakeOnWiFiSettings, weak_ptr_factory_.GetWeakPtr()));
dispatcher_->PostDelayedTask(
FROM_HERE, verify_wake_on_wifi_settings_callback_.callback(),
kVerifyWakeOnWiFiSettingsDelayMilliseconds);
}
void WakeOnWiFi::DisableWakeOnWiFi() {
SLOG(this, 3) << __func__;
Error error;
SetWakeOnWiFiMessage 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,
base::Bind(&WakeOnWiFi::OnSetWakeOnWiFiConnectionResponse),
base::Bind(&NetlinkManager::OnAckDoNothing),
base::Bind(&WakeOnWiFi::OnWakeOnWiFiSettingsErrorResponse,
weak_ptr_factory_.GetWeakPtr()))) {
RunAndResetSuspendActionsDoneCallback(
Error(Error::kOperationFailed, "SendNl80211Message failed"));
return;
}
verify_wake_on_wifi_settings_callback_.Reset(base::Bind(
&WakeOnWiFi::RequestWakeOnWiFiSettings, weak_ptr_factory_.GetWeakPtr()));
dispatcher_->PostDelayedTask(
FROM_HERE, verify_wake_on_wifi_settings_callback_.callback(),
kVerifyWakeOnWiFiSettingsDelayMilliseconds);
}
void WakeOnWiFi::RetrySetWakeOnWiFiConnections() {
SLOG(this, 3) << __func__;
if (num_set_wake_on_wifi_retries_ < kMaxSetWakeOnWiFiRetries) {
ApplyWakeOnWiFiSettings();
++num_set_wake_on_wifi_retries_;
} else {
SLOG(this, 3) << __func__ << ": max retry attempts reached";
num_set_wake_on_wifi_retries_ = 0;
RunAndResetSuspendActionsDoneCallback(Error(Error::kOperationFailed));
}
}
bool WakeOnWiFi::WakeOnWiFiDisabled() {
return wake_on_wifi_features_enabled_ == kWakeOnWiFiFeaturesEnabledNone;
}
bool WakeOnWiFi::WakeOnWiFiDarkConnectEnabledAndSupported() {
if (wake_on_wifi_features_enabled_ == kWakeOnWiFiFeaturesEnabledNone) {
return false;
}
if (!base::Contains(wake_on_wifi_triggers_supported_,
kWakeTriggerDisconnect) ||
!base::Contains(wake_on_wifi_triggers_supported_, kWakeTriggerSSID)) {
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_ ==
kWakeOnWiFiFeaturesEnabledDarkConnect) {
reported_state = Metrics::kWakeOnWiFiFeaturesEnabledStateDarkConnect;
} 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";
}
}
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_
<< " SSIDs supported by this WiFi device";
}
}
}
void WakeOnWiFi::OnWakeupReasonReceived(const NetlinkMessage& netlink_message) {
// 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() != SetWakeOnWiFiMessage::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;
record_wake_reason_callback_.Run(GetLastWakeReason(nullptr));
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;
record_wake_reason_callback_.Run(GetLastWakeReason(nullptr));
last_ssid_match_freqs_ = ParseWakeOnSSIDResults(results_list);
return;
}
SLOG(this, 3) << __func__ << ": "
<< "Wakeup reason: Not supported";
}
void WakeOnWiFi::OnBeforeSuspend(
bool is_connected,
const std::vector<ByteString>& allowed_ssids,
const ResultCallback& done_callback,
const base::Closure& renew_dhcp_lease_callback,
const base::Closure& remove_supplicant_networks_callback,
bool have_dhcp_lease,
uint32_t time_to_next_lease_renewal) {
connected_before_suspend_ = is_connected;
if (WakeOnWiFiDisabled()) {
// Wake on WiFi not supported or not enabled, so immediately report success.
done_callback.Run(Error(Error::kSuccess));
return;
}
LOG(INFO) << __func__ << ": Wake on WiFi features enabled: "
<< wake_on_wifi_features_enabled_;
suspend_actions_done_callback_ = done_callback;
wake_on_allowed_ssids_ = allowed_ssids;
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(
FROM_HERE, base::Bind(&WakeOnWiFi::BeforeSuspendActions,
weak_ptr_factory_.GetWeakPtr(), is_connected,
false, time_to_next_lease_renewal,
remove_supplicant_networks_callback));
} else {
dispatcher_->PostTask(
FROM_HERE, base::Bind(&WakeOnWiFi::BeforeSuspendActions,
weak_ptr_factory_.GetWeakPtr(), is_connected,
have_dhcp_lease, time_to_next_lease_renewal,
remove_supplicant_networks_callback));
}
}
void WakeOnWiFi::OnAfterResume() {
SLOG(this, 1) << __func__;
wake_to_scan_timer_->Stop();
dhcp_lease_renewal_timer_->Stop();
if (WakeOnWiFiDarkConnectEnabledAndSupported()) {
// Unconditionally disable wake on WiFi on resume if these features
// were enabled before the last suspend.
DisableWakeOnWiFi();
metrics_->NotifySuspendWithWakeOnWiFiEnabledDone();
}
}
void WakeOnWiFi::OnDarkResume(
bool is_connected,
const std::vector<ByteString>& allowed_ssids,
const ResultCallback& done_callback,
const base::Closure& renew_dhcp_lease_callback,
const InitiateScanCallback& initiate_scan_callback,
const base::Closure& remove_supplicant_networks_callback) {
if (WakeOnWiFiDisabled()) {
// Wake on WiFi not supported or not enabled, so immediately report success.
done_callback.Run(Error(Error::kSuccess));
return;
}
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_allowed_ssids_ = allowed_ssids;
if (last_wake_reason_ == kWakeTriggerSSID ||
last_wake_reason_ == kWakeTriggerDisconnect ||
(last_wake_reason_ == kWakeTriggerUnsupported && !is_connected)) {
// We want to disable wake on WiFi in two specific cases of thrashing:
// 1) Repeatedly waking on SSID in the presence of an AP that the WiFi
// device cannot connect to
// 2) Repeatedly waking on disconnect because of a an AP that repeatedly
// disconnects the WiFi device but allows it to reconnect immediately
// Therefore, we only count dark resumes caused by either of these wake
// reasons when deciding whether or not to throttle wake on WiFi.
//
// In case the WiFi driver does not support wake reason reporting, we use
// the WiFi device's connection status on dark resume as a proxy for these
// wake reasons (i.e. when we wake on either SSID or disconnect, we should
// be disconnected). This is not reliable for wake on disconnect, as the
// WiFi device will report that it is connected as it enters dark
// resume (crbug.com/505072).
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_),
base::Bind(&WakeOnWiFi::OnTimerWakeDoNothing, base::Unretained(this)));
DisableWakeOnWiFi();
dark_resume_history_.Clear();
metrics_->NotifyWakeOnWiFiThrottled();
last_ssid_match_freqs_.clear();
return;
}
switch (last_wake_reason_) {
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(base::Bind(
&WakeOnWiFi::BeforeSuspendActions, weak_ptr_factory_.GetWeakPtr(), false,
false, 0, remove_supplicant_networks_callback));
dispatcher_->PostDelayedTask(FROM_HERE,
dark_resume_actions_timeout_callback_.callback(),
DarkResumeActionsTimeoutMilliseconds);
}
void WakeOnWiFi::BeforeSuspendActions(
bool is_connected,
bool start_lease_renewal_timer,
uint32_t time_to_next_lease_renewal,
const base::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 (WakeOnWiFiDarkConnectEnabledAndSupported()) {
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),
base::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_allowed_ssids_.empty()) {
SLOG(this, 3) << __func__ << ": "
<< "Enabling wake on SSID";
wake_on_wifi_triggers_.insert(kWakeTriggerSSID);
}
int num_extra_ssids =
wake_on_allowed_ssids_.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_),
base::Bind(&WakeOnWiFi::OnTimerWakeDoNothing,
base::Unretained(this)));
// Trim SSID list to the max size that the NIC supports.
wake_on_allowed_ssids_.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::ParseWakeOnSSIDResults(
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::OnConnectedAndReachable(bool start_lease_renewal_timer,
uint32_t time_to_next_lease_renewal) {
SLOG(this, 3) << __func__;
if (WakeOnWiFiDisabled()) {
SLOG(this, 3) << "Wake on WiFi not enabled";
}
if (!in_dark_resume_) {
SLOG(this, 3) << "Not in dark resume";
return;
}
BeforeSuspendActions(true, start_lease_renewal_timer,
time_to_next_lease_renewal, base::Closure());
}
void WakeOnWiFi::ReportConnectedToServiceAfterWake(bool is_connected,
int seconds_in_suspend) {
Metrics::WiFiConnectionStatusAfterWake status;
if (WakeOnWiFiDarkConnectEnabledAndSupported()) {
// Only logged if wake on WiFi is supported and wake on SSID was enabled to
// maintain connectivity while suspended.
status = is_connected
? Metrics::kWiFiConnectionStatusAfterWakeWoWOnConnected
: Metrics::kWiFiConnectionStatusAfterWakeWoWOnDisconnected;
} else {
status = is_connected
? Metrics::kWiFiConnectionStatusAfterWakeWoWOffConnected
: Metrics::kWiFiConnectionStatusAfterWakeWoWOffDisconnected;
}
metrics_->NotifyConnectedToServiceAfterWake(status);
// Only log time spent in suspended state for each
// connection status if wifi was connected before suspending
if (connected_before_suspend_) {
LOG(INFO) << "NotifySuspendDurationAfterWake: "
<< "status: " << status
<< "seconds_in_suspend: " << seconds_in_suspend;
metrics_->NotifySuspendDurationAfterWake(status, seconds_in_suspend);
}
}
void WakeOnWiFi::OnNoAutoConnectableServicesAfterScan(
const std::vector<ByteString>& allowed_ssids,
const base::Closure& remove_supplicant_networks_callback,
const InitiateScanCallback& initiate_scan_callback) {
SLOG(this, 3) << __func__ << ": "
<< (in_dark_resume_ ? "In dark resume" : "Not in dark resume");
if (WakeOnWiFiDisabled()) {
// The scan is not triggered by us, ignore.
return;
}
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_allowed_ssids_ = allowed_ssids;
// 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);
}
}
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) {
// We don't expect active scans to be started when we wake on RTC timers.
if (is_active_scan) {
LOG(ERROR) << "Unexpected active scan launched in dark resume";
}
metrics_->NotifyScanStartedInDarkResume(is_active_scan);
}
}
void WakeOnWiFi::OnScanCompleted() {
if (in_dark_resume_) {
metrics_->NotifyDarkResumeScanResultsReceived();
}
}
} // namespace shill