blob: e2851707b9cb7079a8afeb11499aa2cce4958f25 [file] [edit]
// Copyright 2022 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <algorithm>
#include <numeric>
#include <string_view>
#include <utility>
#include <base/containers/contains.h>
#include <base/rand_util.h>
#include <net-base/attribute_list.h>
#include <net-base/netlink_attribute.h>
#include "shill/logging.h"
#include "shill/supplicant/wpa_supplicant.h"
#include "shill/wifi/local_device.h"
#include "shill/wifi/wifi.h"
#include "shill/wifi/wifi_phy.h"
namespace shill {
namespace Logging {
static auto kModuleLogScope = ScopeLogger::kWiFi;
} // namespace Logging
WiFiPhy::WiFiPhy(uint32_t phy_index)
: phy_index_(phy_index), reg_self_managed_(false) {}
WiFiPhy::~WiFiPhy() = default;
void WiFiPhy::AddWiFiDevice(WiFiConstRefPtr device) {
wifi_devices_.insert(device);
}
void WiFiPhy::DeleteWiFiDevice(std::string_view link_name) {
auto link_name_matches = [link_name](auto const& device) {
return device->link_name() == link_name;
};
std::erase_if(wifi_devices_, link_name_matches);
}
void WiFiPhy::WiFiDeviceStateChanged(WiFiConstRefPtr device) {
if (!base::Contains(wifi_devices_, device)) {
LOG(INFO) << "Phy " << phy_index_
<< " received state change for unregistered device: "
<< device->link_name();
return;
}
}
void WiFiPhy::AddWiFiLocalDevice(LocalDeviceConstRefPtr device) {
wifi_local_devices_.insert(device);
}
void WiFiPhy::DeleteWiFiLocalDevice(LocalDeviceConstRefPtr device) {
wifi_local_devices_.erase(device);
}
// TODO(b/248103586): Move NL80211_CMD_NEW_WIPHY parsing out of WiFiPhy and into
// WiFiProvider.
void WiFiPhy::OnNewWiphy(const Nl80211Message& nl80211_message) {
if (nl80211_message.const_attributes()->IsFlagAttributeTrue(
NL80211_ATTR_WIPHY_SELF_MANAGED_REG)) {
reg_self_managed_ = true;
}
ParseInterfaceTypes(nl80211_message);
// TODO(b/244630773): Parse out the message and store phy information.
ParseConcurrency(nl80211_message);
ParseFrequencies(nl80211_message);
}
bool WiFiPhy::SupportsIftype(nl80211_iftype iftype) const {
return base::Contains(supported_ifaces_, iftype);
}
void WiFiPhy::ParseInterfaceTypes(const Nl80211Message& nl80211_message) {
net_base::AttributeListConstRefPtr ifaces;
if (nl80211_message.const_attributes()->ConstGetNestedAttributeList(
NL80211_ATTR_SUPPORTED_IFTYPES, &ifaces)) {
net_base::AttributeIdIterator ifaces_iter(*ifaces);
for (; !ifaces_iter.AtEnd(); ifaces_iter.Advance()) {
uint32_t iface;
if (!ifaces->GetU32AttributeValue(ifaces_iter.GetId(), &iface)) {
LOG(ERROR) << "Failed to get supported iface type "
<< ifaces_iter.GetId();
continue;
}
if (iface < 0 || iface > NL80211_IFTYPE_MAX) {
LOG(ERROR) << "Invalid iface type: " << iface;
continue;
}
supported_ifaces_.insert(nl80211_iftype(iface));
}
}
}
void WiFiPhy::ParseConcurrency(const Nl80211Message& nl80211_message) {
// Check that the message contains concurrency combinations.
net_base::AttributeListConstRefPtr interface_combinations_attr;
if (!nl80211_message.const_attributes()->ConstGetNestedAttributeList(
NL80211_ATTR_INTERFACE_COMBINATIONS, &interface_combinations_attr)) {
return;
}
// Iterate over the combinations in the message.
concurrency_combs_.clear();
net_base::AttributeIdIterator comb_iter(*interface_combinations_attr);
for (; !comb_iter.AtEnd(); comb_iter.Advance()) {
struct ConcurrencyCombination comb;
net_base::AttributeListConstRefPtr iface_comb_attr;
if (!interface_combinations_attr->ConstGetNestedAttributeList(
comb_iter.GetId(), &iface_comb_attr)) {
continue; // Next combination.
}
// Check that the combination has limits.
net_base::AttributeListConstRefPtr iface_limits_attr;
if (!iface_comb_attr->ConstGetNestedAttributeList(NL80211_IFACE_COMB_LIMITS,
&iface_limits_attr)) {
continue; // Next combination.
}
iface_comb_attr->GetU32AttributeValue(NL80211_IFACE_COMB_MAXNUM,
&comb.max_num);
iface_comb_attr->GetU32AttributeValue(NL80211_IFACE_COMB_NUM_CHANNELS,
&comb.num_channels);
net_base::AttributeIdIterator limit_iter(*iface_limits_attr);
for (; !limit_iter.AtEnd(); limit_iter.Advance()) {
struct IfaceLimit limit;
net_base::AttributeListConstRefPtr limit_attr;
if (!iface_limits_attr->ConstGetNestedAttributeList(limit_iter.GetId(),
&limit_attr)) {
LOG(WARNING) << "Interface combination limit " << limit_iter.GetId()
<< " not found";
// If we reach this line then the message is malformed and we should
// stop parsing it.
return;
}
limit_attr->GetU32AttributeValue(NL80211_IFACE_LIMIT_MAX, &limit.max);
// Check that the limit contains interface types.
net_base::AttributeListConstRefPtr iface_types_attr;
if (!limit_attr->ConstGetNestedAttributeList(NL80211_IFACE_LIMIT_TYPES,
&iface_types_attr)) {
continue;
}
for (uint32_t iftype = NL80211_IFTYPE_UNSPECIFIED;
iftype < NUM_NL80211_IFTYPES; iftype++) {
if (iface_types_attr->GetFlagAttributeValue(iftype, nullptr)) {
limit.iftypes.push_back(nl80211_iftype(iftype));
}
}
comb.limits.push_back(limit);
}
concurrency_combs_.insert(comb);
}
}
void WiFiPhy::PhyDumpComplete() {
SLOG(3) << __func__;
std::swap(frequencies_, temp_freqs_);
temp_freqs_.clear();
DumpFrequencies();
}
void WiFiPhy::ParseFrequencies(const Nl80211Message& nl80211_message) {
// Code below depends on being able to pack all flags into bits.
static_assert(
sizeof(WiFiPhy::Frequency::flags) * CHAR_BIT > NL80211_FREQUENCY_ATTR_MAX,
"Not enough bits to hold all possible flags");
SLOG(3) << __func__;
if (!(nl80211_message.flags() & NLM_F_MULTI)) {
return;
}
net_base::AttributeListConstRefPtr bands_list;
if (nl80211_message.const_attributes()->ConstGetNestedAttributeList(
NL80211_ATTR_WIPHY_BANDS, &bands_list)) {
net_base::AttributeIdIterator bands_iter(*bands_list);
for (; !bands_iter.AtEnd(); bands_iter.Advance()) {
// Each band has nested attributes and ...
net_base::AttributeListConstRefPtr band_attrs;
if (bands_list->ConstGetNestedAttributeList(bands_iter.GetId(),
&band_attrs)) {
int current_band = bands_iter.GetId();
// ... we are interested in freqs (which itself is a nested attribute).
net_base::AttributeListConstRefPtr freqs_list;
if (!band_attrs->ConstGetNestedAttributeList(NL80211_BAND_ATTR_FREQS,
&freqs_list)) {
continue;
}
net_base::AttributeIdIterator freqs_iter(*freqs_list);
for (; !freqs_iter.AtEnd(); freqs_iter.Advance()) {
net_base::AttributeListConstRefPtr freq_attrs;
if (freqs_list->ConstGetNestedAttributeList(freqs_iter.GetId(),
&freq_attrs)) {
Frequency freq;
for (auto attr = net_base::AttributeIdIterator(*freq_attrs);
!attr.AtEnd(); attr.Advance()) {
if (attr.GetType() == net_base::NetlinkAttribute::kTypeFlag) {
freq.flags |= 1 << attr.GetId();
} else {
if (attr.GetId() == NL80211_FREQUENCY_ATTR_FREQ) {
freq_attrs->GetU32AttributeValue(attr.GetId(), &freq.value);
} else {
if (!freq_attrs->GetU32AttributeValue(
attr.GetId(), &freq.attributes[attr.GetId()])) {
LOG(WARNING) << "Failed to read frequency attribute: "
<< attr.GetId();
}
}
}
}
if (freq.value == 0) {
continue;
}
SLOG(3) << "Found frequency: " << freq.value;
auto& fvec = temp_freqs_[current_band];
auto it =
std::find_if(std::begin(fvec), std::end(fvec),
[&](auto& f) { return f.value == freq.value; });
if (it == fvec.end()) {
temp_freqs_[current_band].emplace_back(std::move(freq));
} else {
LOG(WARNING) << "Repeated frequency in WIPHY dump: "
<< freq.value;
*it = std::move(freq);
}
}
}
}
}
}
}
bool WiFiPhy::SupportAPMode() const {
return SupportsIftype(NL80211_IFTYPE_AP);
}
bool WiFiPhy::SupportP2PMode() const {
return SupportsIftype(NL80211_IFTYPE_P2P_GO) &&
SupportsIftype(NL80211_IFTYPE_P2P_CLIENT);
}
bool WiFiPhy::CombSupportsConcurrency(
ConcurrencyCombination comb,
std::multiset<nl80211_iftype> desired_iftypes) {
if (comb.max_num < desired_iftypes.size()) {
return false;
}
// Keep a count of the number of interfaces that will be used from each limit.
std::vector<uint32_t> iface_counts(comb.limits.size(), 0);
// Step through each desired interface.
for (auto desired_iface : desired_iftypes) {
bool iface_found = false;
// Step through each limit of |comb|.
for (uint32_t i = 0; i < comb.limits.size(); i++) {
auto limit = comb.limits[i];
if (base::Contains(limit.iftypes, desired_iface)) {
iface_found = true;
// If we find desired iftype within |comb|, increment the count for this
// limit.
iface_counts[i]++;
if (iface_counts[i] > limit.max) {
return false;
}
break;
}
}
if (!iface_found) {
return false;
}
}
return true;
}
uint32_t WiFiPhy::SupportsConcurrency(
const std::multiset<nl80211_iftype>& desired_iftypes) const {
for (auto comb : concurrency_combs_) {
if (CombSupportsConcurrency(comb, desired_iftypes)) {
return comb.num_channels;
}
}
return 0;
}
bool WiFiPhy::SupportAPSTAConcurrency() const {
uint32_t num_channels =
SupportsConcurrency({NL80211_IFTYPE_AP, NL80211_IFTYPE_STATION});
return (num_channels > 0);
}
// Get all possible RemovalCandidates from a given vector of interfaces. The
// possible removal candidates are every possible subset of |ifaces|.
WiFiPhy::RemovalCandidateSet WiFiPhy::GetAllCandidates(
std::vector<ConcurrentIface> ifaces) {
RemovalCandidateSet candidates;
// Seed candidates with the empty set.
candidates.insert({{}});
// Create a RemovalCandidate for every possible subset of interfaces.
for (auto iface : ifaces) {
std::vector<RemovalCandidate> new_candidates;
// For each existing candidate, and add a copy of it with |iface| appended.
for (auto candidate : candidates) {
auto new_candidate = candidate;
new_candidate.insert(iface);
new_candidates.push_back(new_candidate);
}
for (auto new_candidate : new_candidates) {
candidates.insert(new_candidate);
}
}
return candidates;
}
std::optional<std::multiset<nl80211_iftype>> WiFiPhy::RequestNewIface(
nl80211_iftype desired_type, Priority priority) const {
// The set of ifaces which we may consider removing to create the desired
// iface.
std::vector<WiFiPhy::ConcurrentIface> removable_ifaces;
std::multiset<nl80211_iftype> active_iftypes;
for (auto dev : wifi_devices_) {
if (dev->supplicant_state() ==
WPASupplicant::kInterfaceStateInterfaceDisabled) {
continue;
}
if (dev->priority() <= priority) {
removable_ifaces.push_back({NL80211_IFTYPE_STATION, dev->priority()});
}
active_iftypes.insert(NL80211_IFTYPE_STATION);
}
for (auto dev : wifi_local_devices_) {
nl80211_iftype iftype;
switch (dev->iface_type()) {
case LocalDevice::IfaceType::kAP:
iftype = NL80211_IFTYPE_AP;
break;
case LocalDevice::IfaceType::kP2PGO:
iftype = NL80211_IFTYPE_P2P_GO;
break;
case LocalDevice::IfaceType::kP2PClient:
iftype = NL80211_IFTYPE_P2P_CLIENT;
break;
case LocalDevice::IfaceType::kUnknown:
NOTREACHED() << "unknown iface type in local device "
<< dev->link_name().value_or("(no_link_name)");
}
if (dev->priority() <= priority) {
removable_ifaces.push_back({iftype, dev->priority()});
}
active_iftypes.insert(iftype);
}
RemovalCandidateSet removal_candidates = GetAllCandidates(removable_ifaces);
// RemovalCandidateSets are sorted by preferability, so we can exit early when
// we find a valid candidate.
for (auto removal_candidate : removal_candidates) {
std::multiset<nl80211_iftype> concurrency_attempt = active_iftypes;
// Determine whether we can operate the interfaces with removal candidates
// removed and the desired iface added.
for (auto iface : removal_candidate) {
concurrency_attempt.erase(concurrency_attempt.find(iface.iftype));
}
concurrency_attempt.insert(desired_type);
// Require at least one supported channel per interface type.
// TODO(b/337055427): Optimize this by allowing fewer required channels if
// some of the interfaces can share a channel.
uint32_t num_required_channels = concurrency_attempt.size();
uint32_t num_supported_channels = SupportsConcurrency(concurrency_attempt);
if (num_supported_channels < num_required_channels) {
continue;
}
std::multiset<nl80211_iftype> ret;
for (auto iface : removal_candidate) {
ret.insert(iface.iftype);
}
return ret;
}
return std::nullopt;
}
void WiFiPhy::DumpFrequencies() const {
SLOG(3) << "Available frequencies:";
for (auto band : frequencies_) {
for (auto& freq : band.second) {
SLOG(3) << " Frequency " << freq.value << ", flag 0x" << std::hex
<< freq.flags;
}
}
}
std::optional<int> WiFiPhy::SelectFrequency(WiFiBand band) const {
LOG(INFO) << "Select Frequency from band: " << band;
DumpFrequencies();
if (frequencies_.empty()) {
LOG(ERROR) << "No valid band found";
return std::nullopt;
}
size_t total_freqs = std::accumulate(
frequencies_.begin(), frequencies_.end(), 0,
[](auto acc, auto band) { return acc + band.second.size(); });
if (total_freqs == 0) {
LOG(ERROR) << "No valid frequency found";
return std::nullopt;
}
std::vector<int> band_idxs;
switch (band) {
case WiFiBand::kLowBand:
band_idxs = {NL80211_BAND_2GHZ};
break;
case WiFiBand::kHighBand:
band_idxs = {NL80211_BAND_5GHZ};
break;
case WiFiBand::kAllBands:
default:
// Note that the order matters - preferred band comes first.
band_idxs = {NL80211_BAND_5GHZ, NL80211_BAND_2GHZ};
}
int selected = -1;
std::vector<uint32_t> freqs;
for (auto bidx : band_idxs) {
auto band = frequencies_.find(bidx);
if (band == frequencies_.end()) {
continue;
}
freqs.reserve(band->second.size());
for (auto& freq : band->second) {
if (freq.flags & (1 << NL80211_FREQUENCY_ATTR_DISABLED |
1 << NL80211_FREQUENCY_ATTR_NO_IR |
1 << NL80211_FREQUENCY_ATTR_RADAR) ||
IsWiFiLimitedFreq(freq.value)) {
SLOG(3) << "Skipping freq: " << freq.value;
continue;
}
freqs.push_back(freq.value);
}
// We are moving now to a less preferred band, so if we have valid frequency
// let's just keep it.
if (!freqs.empty()) {
selected = freqs[base::RandInt(0, freqs.size() - 1)];
break;
}
}
if (selected == -1) {
LOG(ERROR) << "No usable frequency found";
return std::nullopt;
} else {
LOG(INFO) << "Selected frequency: " << selected;
}
return selected;
}
// Operators to facilitate interface combination logging.
std::ostream& operator<<(std::ostream& out, const nl80211_iftype& it) {
switch (it) {
case NL80211_IFTYPE_ADHOC:
return out << "IBSS";
case NL80211_IFTYPE_STATION:
return out << "STA";
case NL80211_IFTYPE_AP:
return out << "AP";
case NL80211_IFTYPE_P2P_CLIENT:
return out << "P2P_CLIENT";
case NL80211_IFTYPE_P2P_GO:
return out << "P2P_GO";
case NL80211_IFTYPE_P2P_DEVICE:
return out << "P2P_DEVICE";
default:
return out << "unknown(" << int(it) << ")";
}
}
std::ostream& operator<<(std::ostream& out,
const std::vector<nl80211_iftype>& it) {
out << "{ ";
for (const auto& i : it)
out << i << ", ";
out << "\b\b }";
return out;
}
std::ostream& operator<<(std::ostream& out, const IfaceLimit& il) {
return out << "{ iftypes: " << il.iftypes << ", max:" << il.max << " }";
}
std::ostream& operator<<(std::ostream& out, const std::vector<IfaceLimit>& il) {
out << "{ ";
for (const auto& c : il)
out << c << ", ";
out << "\b\b }";
return out;
}
std::ostream& operator<<(std::ostream& out, const ConcurrencyCombination& cc) {
return out << "{ limits: " << cc.limits << ", max_num:" << cc.max_num
<< ", num_channels: " << cc.num_channels << " }";
}
std::ostream& operator<<(std::ostream& out,
const std::vector<ConcurrencyCombination>& cc) {
out << "{ ";
for (const auto& c : cc)
out << c << ", ";
out << "\b\b }";
return out;
}
} // namespace shill