| // Copyright 2018 The Chromium OS Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "shill/wifi/wake_on_wifi.h" |
| |
| #include <errno.h> |
| #include <linux/nl80211.h> |
| #include <stdio.h> |
| #include <sys/timerfd.h> |
| |
| #include <algorithm> |
| #include <set> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include <base/cancelable_callback.h> |
| #include <base/strings/string_number_conversions.h> |
| |
| #include <chromeos/dbus/service_constants.h> |
| |
| #include "shill/error.h" |
| #include "shill/event_dispatcher.h" |
| #include "shill/ip_address_store.h" |
| #include "shill/logging.h" |
| #include "shill/metrics.h" |
| #include "shill/net/event_history.h" |
| #include "shill/net/netlink_manager.h" |
| #include "shill/net/nl80211_message.h" |
| #include "shill/property_accessor.h" |
| #include "shill/wifi/wifi.h" |
| |
| 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(const WakeOnWiFi* w) { |
| return "(wake_on_wifi)"; |
| } |
| } // namespace Logging |
| |
| const char WakeOnWiFi::kWakeOnIPAddressPatternsNotSupported[] = |
| "Wake on IP address patterns not supported by this WiFi device"; |
| const char WakeOnWiFi::kWakeOnPatternsNotSupported[] = |
| "Wake on patterns not supported by this WiFi device"; |
| const char WakeOnWiFi::kMaxWakeOnPatternsReached[] = |
| "Max number of patterns already registered"; |
| 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, |
| const std::string& mac_address, |
| 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), |
| connected_before_suspend_(false), |
| mac_address_(mac_address), |
| min_pattern_len_(0), |
| record_wake_reason_callback_(record_wake_reason_callback), |
| weak_ptr_factory_(this) { |
| netlink_handler_ = |
| Bind(&WakeOnWiFi::OnWakeupReasonReceived, weak_ptr_factory_.GetWeakPtr()); |
| netlink_manager_->AddBroadcastHandler(netlink_handler_); |
| dhcp_lease_renewal_timer_ = timers::SimpleAlarmTimer::Create(); |
| wake_to_scan_timer_ = timers::SimpleAlarmTimer::Create(); |
| } |
| |
| WakeOnWiFi::~WakeOnWiFi() { |
| netlink_manager_->RemoveBroadcastHandler(netlink_handler_); |
| } |
| |
| void WakeOnWiFi::InitPropertyStore(PropertyStore* store) { |
| store->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(FROM_HERE, 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, |
| const vector<LengthOffset>& patternlen_offset_pair, |
| uint32_t expected_pat_len_bits) { |
| // Round up number of bytes required for the mask. |
| int result_mask_len = (expected_pat_len_bits + 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 (const auto& current_pair : patternlen_offset_pair) { |
| for (uint32_t curr_mask_bit = current_pair.offset; |
| curr_mask_bit < current_pair.length; ++curr_mask_bit) { |
| mask_index = curr_mask_bit / 8; |
| CHECK_LT(mask_index, result_mask_len); |
| result_mask[mask_index] |= 1 << (curr_mask_bit % 8); |
| } |
| } |
| mask->Clear(); |
| mask->Append(ByteString(result_mask)); |
| } |
| |
| // static |
| bool WakeOnWiFi::CreateIPAddressPatternAndMask(const IPAddress& ip_addr, |
| uint32_t min_pattern_len, |
| ByteString* pattern, |
| ByteString* mask) { |
| if (ip_addr.family() == IPAddress::kFamilyIPv4) { |
| WakeOnWiFi::CreateIPV4PatternAndMask(ip_addr, min_pattern_len, pattern, |
| mask); |
| return true; |
| } else if (ip_addr.family() == IPAddress::kFamilyIPv6) { |
| WakeOnWiFi::CreateIPV6PatternAndMask(ip_addr, pattern, mask, |
| min_pattern_len); |
| return true; |
| } else { |
| LOG(ERROR) << "Unrecognized IP Address type."; |
| return false; |
| } |
| } |
| |
| // static |
| bool WakeOnWiFi::ConvertIPProtoStrtoEnum( |
| const std::vector<std::string>& ip_proto_strs, |
| set<uint8_t>* ip_proto_enums, |
| Error* error) { |
| for (const auto& ip_proto : ip_proto_strs) { |
| if (ip_proto == kWakeOnIP) { |
| ip_proto_enums->insert(IPPROTO_IP); |
| } else if (ip_proto == kWakeOnICMP) { |
| ip_proto_enums->insert(IPPROTO_ICMP); |
| } else if (ip_proto == kWakeOnIGMP) { |
| ip_proto_enums->insert(IPPROTO_IGMP); |
| } else if (ip_proto == kWakeOnIPIP) { |
| ip_proto_enums->insert(IPPROTO_IPIP); |
| } else if (ip_proto == kWakeOnTCP) { |
| ip_proto_enums->insert(IPPROTO_TCP); |
| } else if (ip_proto == kWakeOnUDP) { |
| ip_proto_enums->insert(IPPROTO_UDP); |
| } else if (ip_proto == kWakeOnIDP) { |
| ip_proto_enums->insert(IPPROTO_IDP); |
| } else { |
| Error::PopulateAndLog( |
| FROM_HERE, error, Error::kInvalidArguments, |
| "Wake on Packet of type " + ip_proto + " not supported"); |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| // static |
| string WakeOnWiFi::ConvertIPProtoEnumtoStr(uint8_t ip_proto_enum) { |
| switch (ip_proto_enum) { |
| case IPPROTO_IP: |
| return kWakeOnIP; |
| case IPPROTO_ICMP: |
| return kWakeOnICMP; |
| case IPPROTO_IGMP: |
| return kWakeOnIGMP; |
| case IPPROTO_IPIP: |
| return kWakeOnIPIP; |
| case IPPROTO_TCP: |
| return kWakeOnTCP; |
| case IPPROTO_UDP: |
| return kWakeOnUDP; |
| case IPPROTO_IDP: |
| return kWakeOnIDP; |
| default: |
| return ""; |
| } |
| } |
| |
| // static |
| void WakeOnWiFi::CreateIPV4PatternAndMask(const IPAddress& ip_addr, |
| uint32_t min_pattern_len, |
| 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); |
| uint32_t pattern_len = src_ip_offset + ip_addr.GetLength(); |
| // If the length of the final pattern is less than min pattern length, |
| // fill the rest with zeros. |
| uint32_t expected_pattern_len = std::max(min_pattern_len, pattern_len); |
| pattern->Clear(); |
| pattern->Append( |
| ByteString(reinterpret_cast<const unsigned char*>(&pattern_bytes), |
| expected_pattern_len)); |
| vector<LengthOffset> patternlen_offset_pair; |
| patternlen_offset_pair.emplace_back(pattern_len, src_ip_offset); |
| WakeOnWiFi::SetMask(mask, patternlen_offset_pair, expected_pattern_len); |
| } |
| |
| // static |
| void WakeOnWiFi::CreateIPV6PatternAndMask(const IPAddress& ip_addr, |
| ByteString* pattern, |
| ByteString* mask, |
| uint32_t min_pattern_len) { |
| 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); |
| uint32_t pattern_len = src_ip_offset + ip_addr.GetLength(); |
| uint32_t expected_pattern_len = std::max(min_pattern_len, pattern_len); |
| pattern->Clear(); |
| pattern->Append( |
| ByteString(reinterpret_cast<const unsigned char*>(&pattern_bytes), |
| expected_pattern_len)); |
| vector<LengthOffset> patternlen_offset_pair; |
| patternlen_offset_pair.emplace_back(pattern_len, src_ip_offset); |
| WakeOnWiFi::SetMask(mask, patternlen_offset_pair, expected_pattern_len); |
| } |
| |
| // static |
| void WakeOnWiFi::CreatePacketTypePatternAndMaskforIPV4( |
| const std::string& mac_address, |
| uint32_t min_pattern_len, |
| uint8_t ip_protocol, |
| ByteString* pattern, |
| ByteString* mask) { |
| struct Pattern { |
| struct ethhdr eth_hdr; |
| struct iphdr ipv4_hdr; |
| } __attribute__((__packed__)) pattern_bytes; |
| vector<LengthOffset> patternlen_offset_pair; |
| memset(&pattern_bytes, 0, sizeof(pattern_bytes)); |
| vector<uint8_t> address_bytes; |
| static_assert(std::is_standard_layout<Pattern>::value, |
| "Pattern must be a standard layout type"); |
| CHECK(base::HexStringToBytes(mac_address, &address_bytes)); |
| CHECK_EQ(sizeof(pattern_bytes.eth_hdr.h_dest), address_bytes.size()); |
| std::copy(address_bytes.begin(), address_bytes.end(), |
| pattern_bytes.eth_hdr.h_dest); |
| uint32_t dst_hardware_offset = offsetof(Pattern, eth_hdr.h_dest); |
| uint32_t pattern_len = dst_hardware_offset + address_bytes.size(); |
| patternlen_offset_pair.emplace_back(pattern_len, dst_hardware_offset); |
| uint32_t eth_protocol_offset = offsetof(Pattern, eth_hdr.h_proto); |
| pattern_bytes.eth_hdr.h_proto = htons(ETH_P_IP); |
| uint32_t eth_protocol_len = |
| eth_protocol_offset + sizeof(pattern_bytes.eth_hdr.h_proto); |
| patternlen_offset_pair.emplace_back(eth_protocol_len, eth_protocol_offset); |
| pattern_len = std::max(pattern_len, eth_protocol_len); |
| pattern_bytes.ipv4_hdr.protocol = ip_protocol; |
| uint32_t ip_protocol_offset = offsetof(Pattern, ipv4_hdr.protocol); |
| uint32_t ip_protocol_pattern_len = |
| ip_protocol_offset + sizeof(pattern_bytes.ipv4_hdr.protocol); |
| patternlen_offset_pair.emplace_back(ip_protocol_pattern_len, |
| ip_protocol_offset); |
| pattern_len = std::max(pattern_len, ip_protocol_pattern_len); |
| uint32_t expected_pattern_len = std::max(min_pattern_len, pattern_len); |
| pattern->Clear(); |
| pattern->Append( |
| ByteString(reinterpret_cast<const unsigned char*>(&pattern_bytes), |
| expected_pattern_len)); |
| WakeOnWiFi::SetMask(mask, patternlen_offset_pair, expected_pattern_len); |
| } |
| |
| // static |
| void WakeOnWiFi::CreatePacketTypePatternAndMaskforIPV6( |
| const std::string& mac_address, |
| uint32_t min_pattern_len, |
| uint8_t ip_protocol, |
| ByteString* pattern, |
| ByteString* mask) { |
| struct Pattern { |
| struct ethhdr eth_hdr; |
| struct ip6_hdr ipv6_hdr; |
| } __attribute__((__packed__)) pattern_bytes; |
| vector<LengthOffset> patternlen_offset_pair; |
| static_assert(std::is_standard_layout<Pattern>::value, |
| "Pattern must be a standard layout type"); |
| memset(&pattern_bytes, 0, sizeof(pattern_bytes)); |
| vector<uint8_t> address_bytes; |
| CHECK(base::HexStringToBytes(mac_address, &address_bytes)); |
| CHECK_EQ(sizeof(pattern_bytes.eth_hdr.h_dest), address_bytes.size()); |
| std::copy(address_bytes.begin(), address_bytes.end(), |
| pattern_bytes.eth_hdr.h_dest); |
| uint32_t dst_hardware_offset = offsetof(Pattern, eth_hdr.h_dest); |
| uint32_t pattern_len = dst_hardware_offset + address_bytes.size(); |
| patternlen_offset_pair.emplace_back(pattern_len, dst_hardware_offset); |
| uint32_t eth_protocol_offset = offsetof(Pattern, eth_hdr.h_proto); |
| pattern_bytes.eth_hdr.h_proto = htons(ETH_P_IPV6); |
| uint32_t eth_protocol_len = |
| eth_protocol_offset + sizeof(pattern_bytes.eth_hdr.h_proto); |
| patternlen_offset_pair.emplace_back(eth_protocol_len, eth_protocol_offset); |
| pattern_len = std::max(pattern_len, eth_protocol_len); |
| pattern_bytes.ipv6_hdr.ip6_ctlun.ip6_un1.ip6_un1_nxt = ip_protocol; |
| uint32_t ip_protocol_offset = |
| offsetof(Pattern, ipv6_hdr.ip6_ctlun.ip6_un1.ip6_un1_nxt); |
| uint32_t ip_protocol_pattern_len = |
| ip_protocol_offset + |
| sizeof(pattern_bytes.ipv6_hdr.ip6_ctlun.ip6_un1.ip6_un1_nxt); |
| patternlen_offset_pair.emplace_back(ip_protocol_pattern_len, |
| ip_protocol_offset); |
| pattern_len = std::max(pattern_len, ip_protocol_pattern_len); |
| uint32_t expected_pattern_len = std::max(min_pattern_len, pattern_len); |
| pattern->Clear(); |
| pattern->Append( |
| ByteString(reinterpret_cast<const unsigned char*>(&pattern_bytes), |
| expected_pattern_len)); |
| WakeOnWiFi::SetMask(mask, patternlen_offset_pair, expected_pattern_len); |
| } |
| |
| // 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, |
| const set<uint8_t>& wake_on_packet_types, |
| const std::string& mac_address, |
| uint32_t pattern_min_len, |
| uint32_t net_detect_scan_period_seconds, |
| const vector<ByteString>& allowed_ssids, |
| 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 (base::Contains(trigs, kWakeTriggerPattern) && addrs.Empty() && |
| wake_on_packet_types.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; |
| ByteString pattern; |
| ByteString mask; |
| for (const IPAddress& addr : addrs.GetIPAddresses()) { |
| CreateIPAddressPatternAndMask(addr, pattern_min_len, &pattern, &mask); |
| if (!CreateSingleAttribute(pattern, mask, patterns, patnum++, |
| error)) { |
| return false; |
| } |
| } |
| if (!wake_on_packet_types.empty()) { |
| for (auto packet_type : wake_on_packet_types) { |
| CreatePacketTypePatternAndMaskforIPV4(mac_address, pattern_min_len, |
| packet_type, &pattern, &mask); |
| if (!CreateSingleAttribute(pattern, mask, patterns, patnum++, |
| error)) { |
| return false; |
| } |
| CreatePacketTypePatternAndMaskforIPV6(mac_address, pattern_min_len, |
| packet_type, &pattern, &mask); |
| if (!CreateSingleAttribute(pattern, mask, 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 : allowed_ssids) { |
| if (!ssids->CreateNestedAttribute( |
| ssid_num, "NL80211_ATTR_SCHED_SCAN_MATCH_SINGLE")) { |
| Error::PopulateAndLog(FROM_HERE, error, Error::kOperationFailed, |
| "Could not create nested attribute list " |
| "NL80211_ATTR_SCHED_SCAN_MATCH_SINGLE"); |
| return false; |
| } |
| if (!ssids->SetNestedAttributeHasAValue(ssid_num)) { |
| Error::PopulateAndLog( |
| FROM_HERE, error, Error::kOperationFailed, |
| "Could not set value for nested attribute list " |
| "NL80211_ATTR_SCHED_SCAN_MATCH_SINGLE"); |
| return false; |
| } |
| AttributeListRefPtr single_ssid; |
| if (!ssids->GetNestedAttributeList(ssid_num, &single_ssid)) { |
| Error::PopulateAndLog(FROM_HERE, error, Error::kOperationFailed, |
| "Could not get nested attribute list " |
| "NL80211_ATTR_SCHED_SCAN_MATCH_SINGLE"); |
| return false; |
| } |
| if (!single_ssid->CreateRawAttribute( |
| NL80211_SCHED_SCAN_MATCH_ATTR_SSID, |
| "NL80211_SCHED_SCAN_MATCH_ATTR_SSID")) { |
| Error::PopulateAndLog( |
| FROM_HERE, error, Error::kOperationFailed, |
| "Could not create NL80211_SCHED_SCAN_MATCH_ATTR_SSID"); |
| return false; |
| } |
| if (!single_ssid->SetRawAttributeValue( |
| NL80211_SCHED_SCAN_MATCH_ATTR_SSID, ssid_bytes)) { |
| Error::PopulateAndLog( |
| FROM_HERE, error, Error::kOperationFailed, |
| "Could not set NL80211_SCHED_SCAN_MATCH_ATTR_SSID"); |
| return false; |
| } |
| ++ssid_num; |
| } |
| break; |
| } |
| default: { |
| LOG(ERROR) << __func__ << ": Unrecognized trigger"; |
| return false; |
| } |
| } |
| } |
| return true; |
| #endif // DISABLE_WAKE_ON_WIFI |
| } |
| |
| // static |
| bool WakeOnWiFi::CreateSingleAttribute(const ByteString& pattern, |
| const ByteString& mask, |
| AttributeListRefPtr patterns, |
| uint8_t patnum, |
| Error* error) { |
| 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 set<uint8_t>& wake_on_packet_types, |
| const std::string& mac_address, |
| uint32_t min_pattern_len, |
| const vector<ByteString>& allowed_ssids) { |
| #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) && |
| !base::Contains(trigs, kWakeTriggerDisconnect)) { |
| SLOG(WiFi, nullptr, 3) |
| << __func__ << "Wake on disconnect trigger not expected but found"; |
| return false; |
| } |
| if (triggers->ConstGetNestedAttributeList(NL80211_WOWLAN_TRIG_PKT_PATTERN, |
| &unused_list) && |
| !base::Contains(trigs, kWakeTriggerPattern)) { |
| 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) && |
| !base::Contains(trigs, kWakeTriggerSSID)) { |
| SLOG(WiFi, nullptr, 3) << __func__ |
| << "Wake on SSID trigger not expected but found"; |
| return false; |
| } |
| // Check that each expected trigger is present in |msg| with matching |
| // setting values. |
| for (WakeOnWiFiTrigger t : trigs) { |
| switch (t) { |
| case kWakeTriggerDisconnect: { |
| bool wake_on_disconnect; |
| if (!triggers->GetFlagAttributeValue(NL80211_WOWLAN_TRIG_DISCONNECT, |
| &wake_on_disconnect)) { |
| LOG(ERROR) << __func__ << ": " |
| << "Could not get the flag NL80211_WOWLAN_TRIG_DISCONNECT"; |
| return false; |
| } |
| if (!wake_on_disconnect) { |
| SLOG(WiFi, nullptr, 3) |
| << __func__ << "Wake on disconnect flag not set."; |
| return false; |
| } |
| break; |
| } |
| case 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()) { |
| CreateIPAddressPatternAndMask(addr, min_pattern_len, &temp_pattern, |
| &temp_mask); |
| expected_patt_mask_pairs.emplace(temp_pattern, temp_mask); |
| } |
| if (!wake_on_packet_types.empty()) { |
| for (auto packet_type : wake_on_packet_types) { |
| CreatePacketTypePatternAndMaskforIPV4(mac_address, min_pattern_len, |
| packet_type, &temp_pattern, |
| &temp_mask); |
| expected_patt_mask_pairs.emplace(temp_pattern, temp_mask); |
| CreatePacketTypePatternAndMaskforIPV6(mac_address, min_pattern_len, |
| packet_type, &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 (!base::Contains( |
| expected_patt_mask_pairs, |
| std::make_pair(returned_pattern, returned_mask))) { |
| 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( |
| allowed_ssids.begin(), allowed_ssids.end(), ByteString::IsLessThan); |
| AttributeListConstRefPtr scan_attributes; |
| if (!triggers->ConstGetNestedAttributeList( |
| NL80211_WOWLAN_TRIG_NET_DETECT, &scan_attributes)) { |
| LOG(ERROR) << __func__ << ": " |
| << "Could not get nested attribute list " |
| "NL80211_WOWLAN_TRIG_NET_DETECT"; |
| return false; |
| } |
| uint32_t interval; |
| if (!scan_attributes->GetU32AttributeValue( |
| NL80211_ATTR_SCHED_SCAN_INTERVAL, &interval)) { |
| LOG(ERROR) << __func__ << ": " |
| << "Could not get set U32 attribute " |
| "NL80211_ATTR_SCHED_SCAN_INTERVAL"; |
| return false; |
| } |
| if (interval != net_detect_scan_period_seconds * 1000) { |
| SLOG(WiFi, nullptr, 3) |
| << __func__ << "Net Detect scan period mismatch"; |
| return false; |
| } |
| AttributeListConstRefPtr ssids; |
| if (!scan_attributes->ConstGetNestedAttributeList( |
| NL80211_ATTR_SCHED_SCAN_MATCH, &ssids)) { |
| LOG(ERROR) << __func__ << ": " |
| << "Could not get nested attribute list " |
| "NL80211_ATTR_SCHED_SCAN_MATCH"; |
| return false; |
| } |
| bool ssid_mismatch_found = false; |
| size_t ssid_num_mismatch = expected_ssids.size(); |
| AttributeIdIterator ssid_iter(*ssids); |
| AttributeListConstRefPtr single_ssid; |
| ByteString ssid; |
| int ssid_index; |
| while (!ssid_iter.AtEnd()) { |
| ssid.Clear(); |
| ssid_index = ssid_iter.GetId(); |
| if (!ssids->ConstGetNestedAttributeList(ssid_index, &single_ssid)) { |
| LOG(ERROR) << __func__ << ": " |
| << "Could not get nested ssid attribute list #" |
| << ssid_index; |
| return false; |
| } |
| if (!single_ssid->GetRawAttributeValue( |
| NL80211_SCHED_SCAN_MATCH_ATTR_SSID, &ssid)) { |
| LOG(ERROR) << __func__ << ": " |
| << "Could not get attribute " |
| "NL80211_SCHED_SCAN_MATCH_ATTR_SSID"; |
| return false; |
| } |
| if (!base::Contains(expected_ssids, ssid)) { |
| ssid_mismatch_found = true; |
| break; |
| } else { |
| --ssid_num_mismatch; |
| } |
| ssid_iter.Advance(); |
| } |
| if (ssid_mismatch_found || ssid_num_mismatch) { |
| SLOG(WiFi, nullptr, 3) << __func__ << "Net Detect SSID mismatch"; |
| return false; |
| } |
| break; |
| } |
| default: { |
| LOG(ERROR) << __func__ << ": Unrecognized trigger"; |
| return false; |
| } |
| } |
| } |
| return true; |
| #endif // DISABLE_WAKE_ON_WIFI |
| } |
| |
| void WakeOnWiFi::AddWakeOnPacketConnection(const string& ip_endpoint, |
| Error* error) { |
| #if !defined(DISABLE_WAKE_ON_WIFI) |
| if (!base::Contains(wake_on_wifi_triggers_supported_, kWakeTriggerPattern)) { |
| 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_packet_types_.size() >= |
| 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::AddWakeOnPacketOfTypes( |
| const std::vector<std::string>& packet_types, Error* error) { |
| #if !defined(DISABLE_WAKE_ON_WIFI) |
| set<uint8_t> ip_proto_enums; |
| if (!base::Contains(wake_on_wifi_triggers_supported_, kWakeTriggerPattern)) { |
| Error::PopulateAndLog(FROM_HERE, error, Error::kNotSupported, |
| kWakeOnPatternsNotSupported); |
| return; |
| } |
| // We should add 1 pattern for IPv4 and one for IPv6 for each packet type. |
| int num_patterns_for_packet_types = packet_types.size() * 2; |
| if (wake_on_packet_connections_.Count() + num_patterns_for_packet_types >= |
| wake_on_wifi_max_patterns_) { |
| Error::PopulateAndLog(FROM_HERE, error, Error::kOperationFailed, |
| kMaxWakeOnPatternsReached); |
| return; |
| } |
| if (!ConvertIPProtoStrtoEnum(packet_types, &ip_proto_enums, error)) |
| return; |
| wake_on_packet_types_.insert(ip_proto_enums.begin(), ip_proto_enums.end()); |
| #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 (!base::Contains(wake_on_wifi_triggers_supported_, kWakeTriggerPattern)) { |
| 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::RemoveWakeOnPacketOfTypes( |
| const std::vector<std::string>& packet_types, Error* error) { |
| #if !defined(DISABLE_WAKE_ON_WIFI) |
| if (!base::Contains(wake_on_wifi_triggers_supported_, kWakeTriggerPattern)) { |
| Error::PopulateAndLog(FROM_HERE, error, Error::kNotSupported, |
| kWakeOnPatternsNotSupported); |
| return; |
| } |
| |
| set<uint8_t> ip_proto_enums; |
| if (!ConvertIPProtoStrtoEnum(packet_types, &ip_proto_enums, error)) |
| return; |
| |
| for (auto ip_proto_enum : ip_proto_enums) { |
| if (base::Contains(wake_on_packet_types_, ip_proto_enum)) |
| wake_on_packet_types_.erase(ip_proto_enum); |
| } |
| |
| #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 (!base::Contains(wake_on_wifi_triggers_supported_, kWakeTriggerPattern)) { |
| Error::PopulateAndLog(FROM_HERE, error, Error::kNotSupported, |
| kWakeOnIPAddressPatternsNotSupported); |
| return; |
| } |
| wake_on_packet_connections_.Clear(); |
| wake_on_packet_types_.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_packet_types_, mac_address_, |
| min_pattern_len_, wake_on_allowed_ssids_)) { |
| SLOG(this, 2) << __func__ << ": " |
| << "Wake on WiFi settings successfully verified"; |
| metrics_->NotifyVerifyWakeOnWiFiSettingsResult( |
| Metrics::kVerifyWakeOnWiFiSettingsResultSuccess); |
| RunAndResetSuspendActionsDoneCallback(Error(Error::kSuccess)); |
| } else { |
| LOG(ERROR) << __func__ |
| << " failed: discrepancy between wake-on-packet " |
| "settings on NIC and those in local data " |
| "structure detected"; |
| metrics_->NotifyVerifyWakeOnWiFiSettingsResult( |
| Metrics::kVerifyWakeOnWiFiSettingsResultFailure); |
| 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_, wake_on_packet_types_, mac_address_, min_pattern_len_, |
| net_detect_scan_period_seconds_, wake_on_allowed_ssids_, &error)) { |
| LOG(ERROR) << error.message(); |
| RunAndResetSuspendActionsDoneCallback( |
| Error(Error::kOperationFailed, error.message())); |
| return; |
| } |
| if (!netlink_manager_->SendNl80211Message( |
| &set_wowlan_msg, |
| 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( |
| FROM_HERE, 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( |
| FROM_HERE, 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 (!base::Contains(wake_on_wifi_triggers_supported_, kWakeTriggerPattern)) { |
| 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 (!base::Contains(wake_on_wifi_triggers_supported_, |
| kWakeTriggerDisconnect) || |
| !base::Contains(wake_on_wifi_triggers_supported_, kWakeTriggerSSID)) { |
| return false; |
| } |
| return true; |
| } |
| |
| void WakeOnWiFi::ReportMetrics() { |
| Metrics::WakeOnWiFiFeaturesEnabledState reported_state; |
| if (wake_on_wifi_features_enabled_ == kWakeOnWiFiFeaturesEnabledNone) { |
| reported_state = Metrics::kWakeOnWiFiFeaturesEnabledStateNone; |
| } else if (wake_on_wifi_features_enabled_ == |
| 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; |
| // Currently intel WiFi chip doesn't wake on min len patterns(b/62726471). |
| // Adding 1 as a hack to fix this issue. |
| // TODO(ravisadineni): Remove this check after b/62726471 is fixed. |
| min_pattern_len_ = patt_support->min_pattern_len + 1; |
| WakeOnWiFi::CreateIPV4PatternAndMask(IPAddress("192.168.0.20"), |
| min_pattern_len_, &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, min_pattern_len_); |
| size_t ipv6_pattern_len = dummy_pattern.GetLength(); |
| WakeOnWiFi::CreatePacketTypePatternAndMaskforIPV4( |
| mac_address_, min_pattern_len_, IPPROTO_TCP, &dummy_pattern, |
| &dummy_mask); |
| size_t ipv4_packet_type_pattern_len = dummy_pattern.GetLength(); |
| WakeOnWiFi::CreatePacketTypePatternAndMaskforIPV6( |
| mac_address_, min_pattern_len_, IPPROTO_TCP, &dummy_pattern, |
| &dummy_mask); |
| size_t ipv6_packet_type_pattern_len = dummy_pattern.GetLength(); |
| size_t min_pattern_len = std::min({ipv4_pattern_len, ipv6_pattern_len, |
| ipv4_packet_type_pattern_len, |
| ipv6_packet_type_pattern_len}); |
| size_t max_pattern_len = std::max({ipv4_pattern_len, ipv6_pattern_len, |
| ipv4_packet_type_pattern_len, |
| ipv6_packet_type_pattern_len}); |
| // Check if the pattern matching capabilities of this WiFi device will |
| // allow all possible patterns to be used. |
| if (patt_support->min_pattern_len <= min_pattern_len && |
| patt_support->max_pattern_len >= max_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_ |
| << " 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>& allowed_ssids, |
| 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) { |
| connected_before_suspend_ = is_connected; |
| #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_allowed_ssids_ = allowed_ssids; |
| dark_resume_history_.Clear(); |
| if (have_dhcp_lease && is_connected && |
| time_to_next_lease_renewal < kImmediateDHCPLeaseRenewalThresholdSeconds) { |
| // Renew DHCP lease immediately if we have one that is expiring soon. |
| renew_dhcp_lease_callback.Run(); |
| dispatcher_->PostTask( |
| FROM_HERE, |
| Bind(&WakeOnWiFi::BeforeSuspendActions, weak_ptr_factory_.GetWeakPtr(), |
| is_connected, false, time_to_next_lease_renewal, |
| remove_supplicant_networks_callback)); |
| } else { |
| dispatcher_->PostTask( |
| FROM_HERE, |
| 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>& allowed_ssids, |
| 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_allowed_ssids_ = allowed_ssids; |
| |
| if (last_wake_reason_ == kWakeTriggerSSID || |
| last_wake_reason_ == kWakeTriggerDisconnect || |
| (last_wake_reason_ == kWakeTriggerUnsupported && !is_connected)) { |
| // We want to disable wake on WiFi in two specific cases of thrashing: |
| // 1) Repeatedly waking on SSID in the presence of an AP that the WiFi |
| // device cannot connect to |
| // 2) Repeatedly waking on disconnect because of a an AP that repeatedly |
| // disconnects the WiFi device but allows it to reconnect immediately |
| // Therefore, we only count dark resumes caused by either of these wake |
| // reasons when deciding whether or not to throttle wake on WiFi. |
| // |
| // In case the WiFi driver does not support wake reason reporting, we use |
| // the WiFi device's connection status on dark resume as a proxy for these |
| // wake reasons (i.e. when we wake on either SSID or disconnect, we should |
| // be disconnected). This is not reliable for wake on disconnect, as the |
| // WiFi device will report that it is connected as it enters dark |
| // resume (crbug.com/505072). |
| dark_resume_history_.RecordEvent(); |
| } |
| if (dark_resume_history_.CountEventsWithinInterval( |
| kDarkResumeFrequencySamplingPeriodShortMinutes * 60, |
| EventHistory::kClockTypeBoottime) >= kMaxDarkResumesPerPeriodShort || |
| dark_resume_history_.CountEventsWithinInterval( |
| kDarkResumeFrequencySamplingPeriodLongMinutes * 60, |
| EventHistory::kClockTypeBoottime) >= kMaxDarkResumesPerPeriodLong) { |
| LOG(ERROR) << __func__ << ": " |
| << "Too many dark resumes; disabling wake on WiFi temporarily"; |
| // If too many dark resumes have triggered recently, we are probably |
| // thrashing. Stop this by disabling wake on WiFi on the NIC, and |
| // starting the wake to scan timer so that normal wake on WiFi behavior |
| // resumes only |wake_to_scan_period_seconds_| later. |
| dhcp_lease_renewal_timer_->Stop(); |
| wake_to_scan_timer_->Start( |
| FROM_HERE, base::TimeDelta::FromSeconds(wake_to_scan_period_seconds_), |
| 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( |
| FROM_HERE, 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(FROM_HERE, |
| 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() || |
| !wake_on_packet_types_.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_allowed_ssids_.empty()) { |
| SLOG(this, 3) << __func__ << ": " |
| << "Enabling wake on SSID"; |
| wake_on_wifi_triggers_.insert(kWakeTriggerSSID); |
| } |
| int num_extra_ssids = |
| wake_on_allowed_ssids_.size() - wake_on_wifi_max_ssids_; |
| if (num_extra_ssids > 0 || force_wake_to_scan_timer_) { |
| SLOG(this, 3) << __func__ << ": " |
| << "Starting wake to scan timer - " |
| << (num_extra_ssids > 0 ? "extra SSIDs" : "forced"); |
| if (num_extra_ssids > 0) { |
| SLOG(this, 3) << __func__ << ": " << num_extra_ssids |
| << " extra SSIDs."; |
| } |
| // Start wake to scan timer in case the only SSIDs available for |
| // auto-connect during suspend are the ones that we do not program our |
| // NIC to wake on. |
| // Timer callback is NO-OP since dark resume logic (the |
| // kWakeTriggerUnsupported case) will initiate a passive scan. |
| wake_to_scan_timer_->Start( |
| FROM_HERE, |
| base::TimeDelta::FromSeconds(wake_to_scan_period_seconds_), |
| Bind(&WakeOnWiFi::OnTimerWakeDoNothing, base::Unretained(this))); |
| // Trim SSID list to the max size that the NIC supports. |
| wake_on_allowed_ssids_.resize(wake_on_wifi_max_ssids_); |
| } |
| } |
| } |
| |
| // Only call Cancel() here since it deallocates the underlying callback that |
| // |remove_supplicant_networks_callback| references, which is invoked above. |
| dark_resume_actions_timeout_callback_.Cancel(); |
| |
| if (!in_dark_resume_ && wake_on_wifi_triggers_.empty()) { |
| // No need program NIC on normal resume in this case since wake on WiFi |
| // would already have been disabled on the last (non-dark) resume. |
| SLOG(this, 1) << "No need to disable wake on WiFi on NIC in regular " |
| "suspend"; |
| RunAndResetSuspendActionsDoneCallback(Error(Error::kSuccess)); |
| return; |
| } |
| |
| in_dark_resume_ = false; |
| ApplyWakeOnWiFiSettings(); |
| } |
| |
| // static |
| WiFi::FreqSet WakeOnWiFi::ParseWakeOnSSIDResults( |
| AttributeListConstRefPtr results_list) { |
| WiFi::FreqSet freqs; |
| AttributeIdIterator results_iter(*results_list); |
| if (results_iter.AtEnd()) { |
| SLOG(WiFi, nullptr, 3) << __func__ << ": " |
| << "Wake on SSID results not available"; |
| return freqs; |
| } |
| AttributeListConstRefPtr result; |
| int ssid_num = 0; |
| for (; !results_iter.AtEnd(); results_iter.Advance()) { |
| if (!results_list->ConstGetNestedAttributeList(results_iter.GetId(), |
| &result)) { |
| LOG(ERROR) << __func__ << ": " |
| << "Could not get result #" << results_iter.GetId() |
| << " in ssid_results"; |
| return freqs; |
| } |
| ByteString ssid_bytestring; |
| if (!result->GetRawAttributeValue(NL80211_ATTR_SSID, &ssid_bytestring)) { |
| // We assume that the SSID attribute must be present in each result. |
| LOG(ERROR) << __func__ << ": " |
| << "No SSID available for result #" << results_iter.GetId(); |
| continue; |
| } |
| SLOG(WiFi, nullptr, 3) << "SSID " << ssid_num << ": " |
| << std::string(ssid_bytestring.GetConstData(), |
| ssid_bytestring.GetConstData() + |
| ssid_bytestring.GetLength()); |
| AttributeListConstRefPtr frequencies; |
| uint32_t freq_value; |
| if (result->ConstGetNestedAttributeList(NL80211_ATTR_SCAN_FREQUENCIES, |
| &frequencies)) { |
| AttributeIdIterator freq_iter(*frequencies); |
| for (; !freq_iter.AtEnd(); freq_iter.Advance()) { |
| if (frequencies->GetU32AttributeValue(freq_iter.GetId(), &freq_value)) { |
| freqs.insert(freq_value); |
| SLOG(WiFi, nullptr, 7) << "Frequency: " << freq_value; |
| } |
| } |
| } else { |
| SLOG(WiFi, nullptr, 3) |
| << __func__ << ": " |
| << "No frequencies available for result #" << results_iter.GetId(); |
| } |
| ++ssid_num; |
| } |
| return freqs; |
| } |
| |
| void WakeOnWiFi::InitiateScanInDarkResume( |
| const InitiateScanCallback& initiate_scan_callback, |
| const WiFi::FreqSet& freqs) { |
| SLOG(this, 3) << __func__; |
| if (!freqs.empty() && freqs.size() <= kMaxFreqsForDarkResumeScanRetries) { |
| SLOG(this, 3) << __func__ << ": " |
| << "Allowing up to " << kMaxDarkResumeScanRetries |
| << " retries for passive scan on " << freqs.size() |
| << " frequencies"; |
| dark_resume_scan_retries_left_ = kMaxDarkResumeScanRetries; |
| } |
| initiate_scan_callback.Run(freqs); |
| } |
| |
| void WakeOnWiFi::OnConnectedAndReachable(bool start_lease_renewal_timer, |
| uint32_t time_to_next_lease_renewal) { |
| SLOG(this, 3) << __func__; |
| if (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, |
| int seconds_in_suspend) { |
| Metrics::WiFiConnectionStatusAfterWake status; |
| #if defined(DISABLE_WAKE_ON_WIFI) |
| status = is_connected |
| ? Metrics::kWiFiConnectionStatusAfterWakeWoWOffConnected |
| : Metrics::kWiFiConnectionStatusAfterWakeWoWOffDisconnected; |
| #else |
| if (WakeOnWiFiDarkConnectEnabledAndSupported()) { |
| // Only logged if wake on WiFi is supported and wake on SSID was enabled to |
| // maintain connectivity while suspended. |
| status = is_connected |
| ? Metrics::kWiFiConnectionStatusAfterWakeWoWOnConnected |
| : Metrics::kWiFiConnectionStatusAfterWakeWoWOnDisconnected; |
| } else { |
| status = is_connected |
| ? Metrics::kWiFiConnectionStatusAfterWakeWoWOffConnected |
| : Metrics::kWiFiConnectionStatusAfterWakeWoWOffDisconnected; |
| } |
| #endif // DISABLE_WAKE_ON_WIFI |
| metrics_->NotifyConnectedToServiceAfterWake(status); |
| |
| // Only log time spent in suspended state for each |
| // connection status if wifi was connected before suspending |
| if (connected_before_suspend_) { |
| LOG(INFO) << "NotifySuspendDurationAfterWake: " |
| << "status: " << status |
| << "seconds_in_suspend: " << seconds_in_suspend; |
| metrics_->NotifySuspendDurationAfterWake(status, seconds_in_suspend); |
| } |
| } |
| |
| void WakeOnWiFi::OnNoAutoConnectableServicesAfterScan( |
| const vector<ByteString>& allowed_ssids, |
| 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_allowed_ssids_ = allowed_ssids; |
| // Assume that if there are no services available for auto-connect, then we |
| // cannot be connected. Therefore, no need for lease renewal parameters. |
| BeforeSuspendActions(false, false, 0, remove_supplicant_networks_callback); |
| } |
| #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 |