340 lines
12 KiB
C++
340 lines
12 KiB
C++
//
|
|
// Copyright (C) 2013 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/scan_session.h"
|
|
|
|
#include <algorithm>
|
|
#include <set>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include <base/bind.h>
|
|
#include <base/memory/weak_ptr.h>
|
|
#include <base/stl_util.h>
|
|
#include <base/strings/stringprintf.h>
|
|
|
|
#include "shill/event_dispatcher.h"
|
|
#include "shill/logging.h"
|
|
#include "shill/metrics.h"
|
|
#include "shill/net/netlink_manager.h"
|
|
#include "shill/net/netlink_message.h"
|
|
#include "shill/net/nl80211_attribute.h"
|
|
#include "shill/net/nl80211_message.h"
|
|
|
|
using base::Bind;
|
|
using base::StringPrintf;
|
|
using std::set;
|
|
using std::string;
|
|
using std::vector;
|
|
|
|
namespace shill {
|
|
|
|
namespace Logging {
|
|
static auto kModuleLogScope = ScopeLogger::kWiFi;
|
|
static string ObjectID(ScanSession* s) { return "(scan_session)"; }
|
|
}
|
|
|
|
const float ScanSession::kAllFrequencies = 1.1;
|
|
const uint64_t ScanSession::kScanRetryDelayMilliseconds = 200; // Arbitrary.
|
|
const size_t ScanSession::kScanRetryCount = 50;
|
|
|
|
ScanSession::ScanSession(
|
|
NetlinkManager* netlink_manager,
|
|
EventDispatcher* dispatcher,
|
|
const WiFiProvider::FrequencyCountList& previous_frequencies,
|
|
const set<uint16_t>& available_frequencies,
|
|
uint32_t ifindex,
|
|
const FractionList& fractions,
|
|
size_t min_frequencies,
|
|
size_t max_frequencies,
|
|
OnScanFailed on_scan_failed,
|
|
Metrics* metrics)
|
|
: weak_ptr_factory_(this),
|
|
netlink_manager_(netlink_manager),
|
|
dispatcher_(dispatcher),
|
|
frequency_list_(previous_frequencies),
|
|
total_connections_(0),
|
|
total_connects_provided_(0),
|
|
total_fraction_wanted_(0.0),
|
|
wifi_interface_index_(ifindex),
|
|
ssids_(ByteString::IsLessThan),
|
|
fractions_(fractions),
|
|
min_frequencies_(min_frequencies),
|
|
max_frequencies_(max_frequencies),
|
|
on_scan_failed_(on_scan_failed),
|
|
scan_tries_left_(kScanRetryCount),
|
|
found_error_(false),
|
|
metrics_(metrics) {
|
|
sort(frequency_list_.begin(), frequency_list_.end(),
|
|
&ScanSession::CompareFrequencyCount);
|
|
// Add to |frequency_list_| all the frequencies from |available_frequencies|
|
|
// that aren't in |previous_frequencies|.
|
|
set<uint16_t> seen_frequencies;
|
|
for (const auto& freq_conn : frequency_list_) {
|
|
seen_frequencies.insert(freq_conn.frequency);
|
|
total_connections_ += freq_conn.connection_count;
|
|
}
|
|
for (const auto freq : available_frequencies) {
|
|
if (!ContainsKey(seen_frequencies, freq)) {
|
|
frequency_list_.push_back(WiFiProvider::FrequencyCount(freq, 0));
|
|
}
|
|
}
|
|
|
|
SLOG(this, 6) << "Frequency connections vector:";
|
|
for (const auto& freq_conn : frequency_list_) {
|
|
SLOG(this, 6) << " freq[" << freq_conn.frequency << "] = "
|
|
<< freq_conn.connection_count;
|
|
}
|
|
|
|
original_frequency_count_ = frequency_list_.size();
|
|
ebusy_timer_.Pause();
|
|
}
|
|
|
|
ScanSession::~ScanSession() {
|
|
const int kLogLevel = 6;
|
|
ReportResults(kLogLevel);
|
|
}
|
|
|
|
bool ScanSession::HasMoreFrequencies() const {
|
|
return !frequency_list_.empty();
|
|
}
|
|
|
|
vector<uint16_t> ScanSession::GetScanFrequencies(float fraction_wanted,
|
|
size_t min_frequencies,
|
|
size_t max_frequencies) {
|
|
DCHECK_GE(fraction_wanted, 0);
|
|
total_fraction_wanted_ += fraction_wanted;
|
|
float total_connects_wanted = total_fraction_wanted_ * total_connections_;
|
|
|
|
vector<uint16_t> frequencies;
|
|
WiFiProvider::FrequencyCountList::iterator freq_connect =
|
|
frequency_list_.begin();
|
|
SLOG(this, 7) << "Scanning for frequencies:";
|
|
while (freq_connect != frequency_list_.end()) {
|
|
if (frequencies.size() >= min_frequencies) {
|
|
if (total_connects_provided_ >= total_connects_wanted)
|
|
break;
|
|
if (frequencies.size() >= max_frequencies)
|
|
break;
|
|
}
|
|
uint16_t frequency = freq_connect->frequency;
|
|
size_t connection_count = freq_connect->connection_count;
|
|
total_connects_provided_ += connection_count;
|
|
frequencies.push_back(frequency);
|
|
SLOG(this, 7) << " freq[" << frequency << "] = " << connection_count;
|
|
|
|
freq_connect = frequency_list_.erase(freq_connect);
|
|
}
|
|
return frequencies;
|
|
}
|
|
|
|
void ScanSession::InitiateScan() {
|
|
float fraction_wanted = kAllFrequencies;
|
|
if (!fractions_.empty()) {
|
|
fraction_wanted = fractions_.front();
|
|
fractions_.pop_front();
|
|
}
|
|
current_scan_frequencies_ = GetScanFrequencies(fraction_wanted,
|
|
min_frequencies_,
|
|
max_frequencies_);
|
|
DoScan(current_scan_frequencies_);
|
|
}
|
|
|
|
void ScanSession::ReInitiateScan() {
|
|
ebusy_timer_.Pause();
|
|
DoScan(current_scan_frequencies_);
|
|
}
|
|
|
|
void ScanSession::DoScan(const vector<uint16_t>& scan_frequencies) {
|
|
if (scan_frequencies.empty()) {
|
|
LOG(INFO) << "Not sending empty frequency list";
|
|
return;
|
|
}
|
|
TriggerScanMessage trigger_scan;
|
|
trigger_scan.attributes()->CreateNl80211Attribute(
|
|
NL80211_ATTR_SCAN_FREQUENCIES, NetlinkMessage::MessageContext());
|
|
trigger_scan.attributes()->CreateNl80211Attribute(
|
|
NL80211_ATTR_SCAN_SSIDS, NetlinkMessage::MessageContext());
|
|
trigger_scan.attributes()->SetU32AttributeValue(NL80211_ATTR_IFINDEX,
|
|
wifi_interface_index_);
|
|
AttributeListRefPtr frequency_list;
|
|
if (!trigger_scan.attributes()->GetNestedAttributeList(
|
|
NL80211_ATTR_SCAN_FREQUENCIES, &frequency_list) || !frequency_list) {
|
|
LOG(FATAL) << "Couldn't get NL80211_ATTR_SCAN_FREQUENCIES.";
|
|
}
|
|
trigger_scan.attributes()->SetNestedAttributeHasAValue(
|
|
NL80211_ATTR_SCAN_FREQUENCIES);
|
|
|
|
SLOG(this, 6) << "We have requested scan frequencies:";
|
|
string attribute_name;
|
|
int i = 0;
|
|
for (const auto freq : scan_frequencies) {
|
|
SLOG(this, 6) << " " << freq;
|
|
attribute_name = StringPrintf("Frequency-%d", i);
|
|
frequency_list->CreateU32Attribute(i, attribute_name.c_str());
|
|
frequency_list->SetU32AttributeValue(i, freq);
|
|
++i;
|
|
}
|
|
|
|
if (!ssids_.empty()) {
|
|
AttributeListRefPtr ssid_list;
|
|
if (!trigger_scan.attributes()->GetNestedAttributeList(
|
|
NL80211_ATTR_SCAN_SSIDS, &ssid_list) || !ssid_list) {
|
|
LOG(FATAL) << "Couldn't get NL80211_ATTR_SCAN_SSIDS attribute.";
|
|
}
|
|
trigger_scan.attributes()->SetNestedAttributeHasAValue(
|
|
NL80211_ATTR_SCAN_SSIDS);
|
|
int i = 0;
|
|
string attribute_name;
|
|
for (const auto& ssid : ssids_) {
|
|
attribute_name = StringPrintf("NL80211_ATTR_SSID_%d", i);
|
|
ssid_list->CreateRawAttribute(i, attribute_name.c_str());
|
|
ssid_list->SetRawAttributeValue(i, ssid);
|
|
++i;
|
|
}
|
|
// Add an empty one at the end so we ask for a broadcast in addition to
|
|
// the specific SSIDs.
|
|
attribute_name = StringPrintf("NL80211_ATTR_SSID_%d", i);
|
|
ssid_list->CreateRawAttribute(i, attribute_name.c_str());
|
|
ssid_list->SetRawAttributeValue(i, ByteString());
|
|
}
|
|
netlink_manager_->SendNl80211Message(
|
|
&trigger_scan,
|
|
Bind(&ScanSession::OnTriggerScanResponse,
|
|
weak_ptr_factory_.GetWeakPtr()),
|
|
Bind(&NetlinkManager::OnAckDoNothing),
|
|
Bind(&ScanSession::OnTriggerScanErrorResponse,
|
|
weak_ptr_factory_.GetWeakPtr()));
|
|
}
|
|
|
|
void ScanSession::OnTriggerScanResponse(const Nl80211Message& netlink_message) {
|
|
LOG(WARNING) << "Didn't expect _this_ netlink message, here:";
|
|
netlink_message.Print(0, 0);
|
|
on_scan_failed_.Run();
|
|
return;
|
|
}
|
|
|
|
void ScanSession::OnTriggerScanErrorResponse(
|
|
NetlinkManager::AuxilliaryMessageType type,
|
|
const NetlinkMessage* netlink_message) {
|
|
switch (type) {
|
|
case NetlinkManager::kErrorFromKernel: {
|
|
if (!netlink_message) {
|
|
LOG(ERROR) << __func__ << ": Message failed: NetlinkManager Error.";
|
|
found_error_ = true;
|
|
on_scan_failed_.Run();
|
|
break;
|
|
}
|
|
if (netlink_message->message_type() !=
|
|
ErrorAckMessage::GetMessageType()) {
|
|
LOG(ERROR) << __func__ << ": Message failed: Not an error.";
|
|
found_error_ = true;
|
|
on_scan_failed_.Run();
|
|
break;
|
|
}
|
|
const ErrorAckMessage* error_ack_message =
|
|
static_cast<const ErrorAckMessage*>(netlink_message);
|
|
if (error_ack_message->error()) {
|
|
LOG(ERROR) << __func__ << ": Message failed: "
|
|
<< error_ack_message->ToString();
|
|
if (error_ack_message->error() == EBUSY) {
|
|
if (scan_tries_left_ == 0) {
|
|
LOG(ERROR) << "Retried progressive scan " << kScanRetryCount
|
|
<< " times and failed each time. Giving up.";
|
|
found_error_ = true;
|
|
on_scan_failed_.Run();
|
|
scan_tries_left_ = kScanRetryCount;
|
|
return;
|
|
}
|
|
--scan_tries_left_;
|
|
SLOG(this, 3) << __func__ << " - trying again (" << scan_tries_left_
|
|
<< " remaining after this)";
|
|
ebusy_timer_.Resume();
|
|
dispatcher_->PostDelayedTask(Bind(&ScanSession::ReInitiateScan,
|
|
weak_ptr_factory_.GetWeakPtr()),
|
|
kScanRetryDelayMilliseconds);
|
|
break;
|
|
}
|
|
found_error_ = true;
|
|
on_scan_failed_.Run();
|
|
} else {
|
|
SLOG(this, 6) << __func__ << ": Message ACKed";
|
|
}
|
|
}
|
|
break;
|
|
|
|
case NetlinkManager::kUnexpectedResponseType:
|
|
LOG(ERROR) << "Message not handled by regular message handler:";
|
|
if (netlink_message) {
|
|
netlink_message->Print(0, 0);
|
|
}
|
|
found_error_ = true;
|
|
on_scan_failed_.Run();
|
|
break;
|
|
|
|
case NetlinkManager::kTimeoutWaitingForResponse:
|
|
// This is actually expected since, in the working case, a trigger scan
|
|
// message gets its responses broadcast rather than unicast.
|
|
break;
|
|
|
|
default:
|
|
LOG(ERROR) << "Unexpected auxiliary message type: " << type;
|
|
found_error_ = true;
|
|
on_scan_failed_.Run();
|
|
break;
|
|
}
|
|
}
|
|
|
|
void ScanSession::ReportResults(int log_level) {
|
|
SLOG(this, log_level) << "------ ScanSession finished ------";
|
|
SLOG(this, log_level) << "Scanned "
|
|
<< original_frequency_count_ - frequency_list_.size()
|
|
<< " frequencies (" << frequency_list_.size()
|
|
<< " remaining)";
|
|
if (found_error_) {
|
|
SLOG(this, log_level) << "ERROR encountered during scan ("
|
|
<< current_scan_frequencies_.size() << " frequencies"
|
|
<< " dangling - counted as scanned but, really, not)";
|
|
} else {
|
|
SLOG(this, log_level) << "No error encountered during scan.";
|
|
}
|
|
|
|
base::TimeDelta elapsed_time;
|
|
ebusy_timer_.GetElapsedTime(&elapsed_time);
|
|
if (metrics_) {
|
|
metrics_->SendToUMA(Metrics::kMetricWiFiScanTimeInEbusyMilliseconds,
|
|
elapsed_time.InMilliseconds(),
|
|
Metrics::kMetricTimeToScanMillisecondsMin,
|
|
Metrics::kMetricTimeToScanMillisecondsMax,
|
|
Metrics::kMetricTimeToScanMillisecondsNumBuckets);
|
|
}
|
|
SLOG(this, log_level) << "Spent " << elapsed_time.InMillisecondsRoundedUp()
|
|
<< " milliseconds waiting for EBUSY.";
|
|
}
|
|
|
|
void ScanSession::AddSsid(const ByteString& ssid) {
|
|
ssids_.insert(ssid);
|
|
}
|
|
|
|
// static
|
|
bool ScanSession::CompareFrequencyCount(
|
|
const WiFiProvider::FrequencyCount& first,
|
|
const WiFiProvider::FrequencyCount& second) {
|
|
return first.connection_count > second.connection_count;
|
|
}
|
|
|
|
} // namespace shill
|
|
|