//
// 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