1638 lines
66 KiB
C++
1638 lines
66 KiB
C++
//
|
|
// Copyright (C) 2014 The Android Open Source Project
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
//
|
|
|
|
#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>
|
|
#if defined(__ANDROID__)
|
|
#include <dbus/service_constants.h>
|
|
#else
|
|
#include <chromeos/dbus/service_constants.h>
|
|
#endif // __ANDROID__
|
|
|
|
#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;
|
|
|
|
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;
|
|
const char WakeOnWiFi::kWakeReasonStringPattern[] = "WiFi.Pattern";
|
|
const char WakeOnWiFi::kWakeReasonStringDisconnect[] = "WiFi.Disconnect";
|
|
const char WakeOnWiFi::kWakeReasonStringSSID[] = "WiFi.SSID";
|
|
|
|
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_(
|
|
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
|
|
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),
|
|
record_wake_reason_callback_(record_wake_reason_callback),
|
|
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 != kWakeOnWiFiFeaturesEnabledDarkConnect &&
|
|
enabled != kWakeOnWiFiFeaturesEnabledPacketDarkConnect &&
|
|
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 defined(DISABLE_WAKE_ON_WIFI)
|
|
return false;
|
|
#else
|
|
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;
|
|
#endif // DISABLE_WAKE_ON_WIFI
|
|
}
|
|
|
|
// 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 defined(DISABLE_WAKE_ON_WIFI)
|
|
return false;
|
|
#else
|
|
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>, decltype(&ByteStringPairIsLessThan)>
|
|
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, decltype(&ByteString::IsLessThan)> 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;
|
|
#endif // DISABLE_WAKE_ON_WIFI
|
|
}
|
|
|
|
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 =
|
|
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::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::WakeOnWiFiPacketEnabledAndSupported() {
|
|
if (wake_on_wifi_features_enabled_ == kWakeOnWiFiFeaturesEnabledNone ||
|
|
wake_on_wifi_features_enabled_ ==
|
|
kWakeOnWiFiFeaturesEnabledNotSupported ||
|
|
wake_on_wifi_features_enabled_ == kWakeOnWiFiFeaturesEnabledDarkConnect) {
|
|
return false;
|
|
}
|
|
if (wake_on_wifi_triggers_supported_.find(kWakeTriggerPattern) ==
|
|
wake_on_wifi_triggers_supported_.end()) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool WakeOnWiFi::WakeOnWiFiDarkConnectEnabledAndSupported() {
|
|
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_ ==
|
|
kWakeOnWiFiFeaturesEnabledDarkConnect) {
|
|
reported_state = Metrics::kWakeOnWiFiFeaturesEnabledStateDarkConnect;
|
|
} else if (wake_on_wifi_features_enabled_ ==
|
|
kWakeOnWiFiFeaturesEnabledPacketDarkConnect) {
|
|
reported_state = Metrics::kWakeOnWiFiFeaturesEnabledStatePacketDarkConnect;
|
|
} 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 !defined(DISABLE_WAKE_ON_WIFI)
|
|
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";
|
|
}
|
|
}
|
|
#endif // DISABLE_WAKE_ON_WIFI
|
|
}
|
|
|
|
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;
|
|
record_wake_reason_callback_.Run(kWakeReasonStringDisconnect);
|
|
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;
|
|
record_wake_reason_callback_.Run(kWakeReasonStringPattern);
|
|
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(kWakeReasonStringSSID);
|
|
last_ssid_match_freqs_ = ParseWakeOnSSIDResults(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 (WakeOnWiFiPacketEnabledAndSupported() ||
|
|
WakeOnWiFiDarkConnectEnabledAndSupported()) {
|
|
// 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 (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_),
|
|
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() &&
|
|
WakeOnWiFiPacketEnabledAndSupported() && is_connected) {
|
|
SLOG(this, 3) << __func__ << ": "
|
|
<< "Enabling wake on pattern";
|
|
wake_on_wifi_triggers_.insert(kWakeTriggerPattern);
|
|
}
|
|
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),
|
|
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::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 (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 (WakeOnWiFiDarkConnectEnabledAndSupported()) {
|
|
// 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
|