blob: 527cceac09c72723e2847744270fd639ec4fc73b [file] [log] [blame]
// Copyright 2017 The Fuchsia 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 <wlan/mlme/ap/infra_bss.h>
#include <wlan/common/buffer_writer.h>
#include <wlan/common/channel.h>
#include <wlan/mlme/debug.h>
#include <wlan/mlme/device_caps.h>
#include <wlan/mlme/key.h>
#include <wlan/mlme/mac_frame.h>
#include <wlan/mlme/mlme.h>
#include <wlan/mlme/packet.h>
#include <wlan/mlme/service.h>
#include <zircon/status.h>
#include <zircon/syscalls.h>
namespace wlan {
namespace wlan_mlme = ::fuchsia::wlan::mlme;
InfraBss::InfraBss(DeviceInterface* device, fbl::unique_ptr<BeaconSender> bcn_sender,
const common::MacAddr& bssid, fbl::unique_ptr<Timer> timer)
: bssid_(bssid), device_(device), bcn_sender_(std::move(bcn_sender)), started_at_(0),
timer_mgr_(std::move(timer)) {
ZX_DEBUG_ASSERT(bcn_sender_ != nullptr);
}
InfraBss::~InfraBss() {
// The BSS should always be explicitly stopped.
// Throw in debug builds, stop in release ones.
ZX_DEBUG_ASSERT(!IsStarted());
// Ensure BSS is stopped correctly.
Stop();
}
void InfraBss::Start(const MlmeMsg<wlan_mlme::StartRequest>& req) {
if (IsStarted()) { return; }
// Move to requested channel.
auto chan = wlan_channel_t{
.primary = req.body()->channel,
// TODO(WLAN-908): Augment MLME-START.request and forgo a guessing in MLME.
.cbw = CBW20,
};
auto status = device_->SetChannel(chan);
if (status != ZX_OK) {
errorf("[infra-bss] [%s] requested start on channel %u failed: %d\n",
bssid_.ToString().c_str(), req.body()->channel, status);
}
chan_ = chan;
ZX_DEBUG_ASSERT(req.body()->dtim_period > 0);
if (req.body()->dtim_period == 0) {
ps_cfg_.SetDtimPeriod(1);
warnf(
"[infra-bss] [%s] received start request with reserved DTIM period of "
"0; falling back "
"to DTIM period of 1\n",
bssid_.ToString().c_str());
} else {
ps_cfg_.SetDtimPeriod(req.body()->dtim_period);
}
debugbss("[infra-bss] [%s] starting BSS\n", bssid_.ToString().c_str());
debugbss(" SSID: \"%s\"\n", debug::ToAsciiOrHexStr(req.body()->ssid).c_str());
debugbss(" Beacon Period: %u\n", req.body()->beacon_period);
debugbss(" DTIM Period: %u\n", req.body()->dtim_period);
debugbss(" Channel: %u\n", req.body()->channel);
// Keep track of start request which holds important configuration
// information.
req.body()->Clone(&start_req_);
// Start sending Beacon frames.
started_at_ = zx_clock_get(ZX_CLOCK_MONOTONIC);
bcn_sender_->Start(this, ps_cfg_, req);
device_->SetStatus(ETHMAC_STATUS_ONLINE);
}
void InfraBss::Stop() {
if (!IsStarted()) { return; }
debugbss("[infra-bss] [%s] stopping BSS\n", bssid_.ToString().c_str());
clients_.clear();
bcn_sender_->Stop();
started_at_ = 0;
device_->SetStatus(0);
}
bool InfraBss::IsStarted() {
return started_at_ != 0;
}
void InfraBss::HandleAnyFrame(fbl::unique_ptr<Packet> pkt) {
switch (pkt->peer()) {
case Packet::Peer::kEthernet: {
if (auto eth_frame = EthFrameView::CheckType(pkt.get()).CheckLength()) {
HandleEthFrame(eth_frame.IntoOwned(std::move(pkt)));
}
break;
}
case Packet::Peer::kWlan:
HandleAnyWlanFrame(std::move(pkt));
break;
default:
errorf("unknown Packet peer: %u\n", pkt->peer());
break;
}
}
void InfraBss::HandleAnyWlanFrame(fbl::unique_ptr<Packet> pkt) {
if (auto possible_mgmt_frame = MgmtFrameView<>::CheckType(pkt.get())) {
if (auto mgmt_frame = possible_mgmt_frame.CheckLength()) {
HandleAnyMgmtFrame(mgmt_frame.IntoOwned(std::move(pkt)));
}
} else if (auto possible_data_frame = DataFrameView<>::CheckType(pkt.get())) {
if (auto data_frame = possible_data_frame.CheckLength()) {
HandleAnyDataFrame(data_frame.IntoOwned(std::move(pkt)));
}
} else if (auto possible_ctrl_frame = CtrlFrameView<>::CheckType(pkt.get())) {
if (auto ctrl_frame = possible_ctrl_frame.CheckLength()) {
HandleAnyCtrlFrame(ctrl_frame.IntoOwned(std::move(pkt)));
}
}
}
void InfraBss::HandleAnyMgmtFrame(MgmtFrame<>&& frame) {
auto mgmt_frame = frame.View();
bool to_bss = (bssid_ == mgmt_frame.hdr()->addr1 && bssid_ == mgmt_frame.hdr()->addr3);
// Special treatment for ProbeRequests which can be addressed towards
// broadcast address.
if (auto possible_probe_req_frame = mgmt_frame.CheckBodyType<ProbeRequest>()) {
if (auto mgmt_probe_req_frame = possible_probe_req_frame.CheckLength()) {
// Drop all ProbeRequests which are neither targeted to this BSS nor to
// broadcast address.
auto hdr = mgmt_probe_req_frame.hdr();
bool to_bcast = hdr->addr1.IsBcast() && hdr->addr3.IsBcast();
if (!to_bss && !to_bcast) { return; }
// Valid ProbeRequest, let BeaconSender process and respond to it.
auto ra = mgmt_probe_req_frame.hdr()->addr2;
auto probe_req_frame = mgmt_probe_req_frame.NextFrame();
Span<const uint8_t> ie_chain = probe_req_frame.body_data();
bcn_sender_->SendProbeResponse(ra, ie_chain);
return;
}
return;
}
// Drop management frames which are not targeted towards this BSS.
if (!to_bss) { return; }
// Register the client if it's not yet known.
const auto& client_addr = mgmt_frame.hdr()->addr2;
if (!HasClient(client_addr)) {
if (auto auth_frame = mgmt_frame.CheckBodyType<Authentication>().CheckLength()) {
HandleNewClientAuthAttempt(auth_frame);
}
}
// Forward all frames to the correct client.
auto client = GetClient(client_addr);
if (client != nullptr) { client->HandleAnyMgmtFrame(std::move(frame)); }
}
void InfraBss::HandleAnyDataFrame(DataFrame<>&& frame) {
if (bssid_ != frame.hdr()->addr1) { return; }
// Let the correct RemoteClient instance process the received frame.
const auto& client_addr = frame.hdr()->addr2;
auto client = GetClient(client_addr);
if (client != nullptr) { client->HandleAnyDataFrame(std::move(frame)); }
}
void InfraBss::HandleAnyCtrlFrame(CtrlFrame<>&& frame) {
auto ctrl_frame = frame.View();
if (auto pspoll_frame = ctrl_frame.CheckBodyType<PsPollFrame>().CheckLength()) {
if (pspoll_frame.body()->bssid != bssid_) { return; }
const auto& client_addr = pspoll_frame.body()->ta;
auto client = GetClient(client_addr);
if (client == nullptr) { return; }
client->HandleAnyCtrlFrame(std::move(frame));
}
}
zx_status_t InfraBss::ScheduleTimeout(wlan_tu_t tus, const common::MacAddr& client_addr,
TimeoutId* id) {
return timer_mgr_.Schedule(timer_mgr_.Now() + WLAN_TU(tus), client_addr, id);
}
void InfraBss::CancelTimeout(TimeoutId id) {
timer_mgr_.Cancel(id);
}
zx_status_t InfraBss::HandleTimeout() {
zx_status_t status = timer_mgr_.HandleTimeout([&] (auto _now, auto addr, auto timeout_id) {
if (auto client = GetClient(addr)) {
client->HandleTimeout(timeout_id);
}
});
if (status != ZX_OK) {
errorf("[infra-bss] failed to rearm the timer: %s\n", zx_status_get_string(status));
}
return status;
}
void InfraBss::HandleEthFrame(EthFrame&& eth_frame) {
// Lookup client associated with incoming unicast frame.
auto& dest_addr = eth_frame.hdr()->dest;
if (dest_addr.IsUcast()) {
auto client = GetClient(dest_addr);
if (client != nullptr) { client->HandleAnyEthFrame(std::move(eth_frame)); }
} else {
// Process multicast frames ourselves.
if (auto data_frame = EthToDataFrame(eth_frame, false)) {
SendDataFrame(DataFrame<>(data_frame->Take()));
} else {
errorf("[infra-bss] [%s] couldn't convert ethernet frame\n", bssid_.ToString().c_str());
}
}
}
zx_status_t InfraBss::HandleMlmeMsg(const BaseMlmeMsg& msg) {
if (auto set_keys_req = msg.As<wlan_mlme::SetKeysRequest>()) {
return HandleMlmeSetKeysReq(*set_keys_req);
}
auto peer_addr = service::GetPeerAddr(msg);
if (!peer_addr.has_value()) {
warnf("[infra-bss] received unsupported MLME msg; ordinal: %u\n", msg.ordinal());
return ZX_ERR_INVALID_ARGS;
}
if (auto client = GetClient(peer_addr.value())) {
return client->HandleMlmeMsg(msg);
} else {
warnf("[infra-bss] unrecognized peer address in MlmeMsg: %s -- ordinal: %u\n",
peer_addr.value().ToString().c_str(), msg.ordinal());
}
return ZX_OK;
}
void InfraBss::HandleNewClientAuthAttempt(const MgmtFrameView<Authentication>& frame) {
auto& client_addr = frame.hdr()->addr2;
ZX_DEBUG_ASSERT(!HasClient(client_addr));
debugbss("[infra-bss] [%s] new client: %s\n", bssid_.ToString().c_str(),
client_addr.ToString().c_str());
// Else, create a new remote client instance.
auto client = fbl::make_unique<RemoteClient>(device_,
this, // bss
this, // client listener
client_addr);
clients_.emplace(client_addr, std::move(client));
}
zx_status_t InfraBss::HandleMlmeSetKeysReq(const MlmeMsg<wlan_mlme::SetKeysRequest>& req) {
debugfn();
if (!IsRsn()) {
warnf("[infra-bss] ignoring SetKeysRequest since AP is unprotected\n");
return ZX_ERR_BAD_STATE;
}
for (auto& key_desc : req.body()->keylist) {
auto key_config = ToKeyConfig(key_desc);
if (!key_config.has_value()) { return ZX_ERR_NOT_SUPPORTED; }
auto status = device_->SetKey(&key_config.value());
if (status != ZX_OK) {
errorf("Could not configure keys in hardware: %d\n", status);
return status;
}
}
return ZX_OK;
}
void InfraBss::HandleClientFailedAuth(const common::MacAddr& client_addr) {
debugfn();
StopTrackingClient(client_addr);
}
void InfraBss::HandleClientDeauth(const common::MacAddr& client_addr) {
debugfn();
StopTrackingClient(client_addr);
}
void InfraBss::StopTrackingClient(const wlan::common::MacAddr& client_addr) {
auto iter = clients_.find(client_addr);
ZX_DEBUG_ASSERT(iter != clients_.end());
if (iter == clients_.end()) {
errorf("[infra-bss] [%s] unknown client deauthenticated: %s\n", bssid_.ToString().c_str(),
client_addr.ToString().c_str());
return;
}
debugbss("[infra-bss] [%s] removing client %s\n", bssid_.ToString().c_str(),
client_addr.ToString().c_str());
clients_.erase(iter);
}
void InfraBss::HandleClientDisassociation(aid_t aid) {
debugfn();
ps_cfg_.GetTim()->SetTrafficIndication(aid, false);
}
void InfraBss::HandleClientBuChange(const common::MacAddr& client_addr, aid_t aid,
size_t bu_count) {
debugfn();
auto client = GetClient(client_addr);
ZX_DEBUG_ASSERT(client != nullptr);
if (client == nullptr) {
errorf("[infra-bss] [%s] received traffic indication for untracked client: %s\n",
bssid_.ToString().c_str(), client_addr.ToString().c_str());
return;
}
ZX_DEBUG_ASSERT(aid != kUnknownAid);
if (aid == kUnknownAid) {
errorf(
"[infra-bss] [%s] received traffic indication from client with unknown "
"AID: %s\n",
bssid_.ToString().c_str(), client_addr.ToString().c_str());
return;
}
ps_cfg_.GetTim()->SetTrafficIndication(aid, bu_count > 0);
}
bool InfraBss::HasClient(const common::MacAddr& client) {
return clients_.find(client) != clients_.end();
}
RemoteClientInterface* InfraBss::GetClient(const common::MacAddr& addr) {
auto iter = clients_.find(addr);
if (iter == clients_.end()) { return nullptr; }
return iter->second.get();
}
bool InfraBss::ShouldBufferFrame(const common::MacAddr& receiver_addr) const {
// Buffer non-GCR-SP frames when at least one client is dozing.
// Note: Currently group addressed service transmission is not supported and
// thus, every group message should get buffered.
return receiver_addr.IsGroupAddr() && ps_cfg_.GetTim()->HasDozingClients();
}
zx_status_t InfraBss::BufferFrame(fbl::unique_ptr<Packet> packet) {
// Drop oldest frame if queue reached its limit.
if (bu_queue_.size() >= kMaxGroupAddressedBu) {
bu_queue_.pop();
warnf("[infra-bss] [%s] dropping oldest group addressed frame\n",
bssid_.ToString().c_str());
}
debugps("[infra-bss] [%s] buffer outbound frame\n", bssid_.ToString().c_str());
bu_queue_.push(std::move(packet));
ps_cfg_.GetTim()->SetTrafficIndication(kGroupAdressedAid, true);
return ZX_OK;
}
zx_status_t InfraBss::SendDataFrame(DataFrame<>&& data_frame, uint32_t flags) {
if (ShouldBufferFrame(data_frame.hdr()->addr1)) { return BufferFrame(data_frame.Take()); }
// Ralink appears to setup BlockAck session AND AMPDU handling
// TODO(porce): Use a separate sequence number space in that case
CBW cbw = CBW20;
if (Ht().cbw_40_tx_ready && data_frame.hdr()->addr3.IsUcast()) {
// 40MHz direction does not matter here.
// Radio uses the operational channel setting. This indicates the
// bandwidth without direction.
cbw = CBW40;
}
return device_->SendWlan(data_frame.Take(), cbw, WLAN_PHY_HT, flags);
}
zx_status_t InfraBss::SendMgmtFrame(MgmtFrame<>&& mgmt_frame) {
if (ShouldBufferFrame(mgmt_frame.hdr()->addr1)) { return BufferFrame(mgmt_frame.Take()); }
return device_->SendWlan(mgmt_frame.Take(), CBW20, WLAN_PHY_OFDM);
}
zx_status_t InfraBss::DeliverEthernet(Span<const uint8_t> frame) {
return device_->DeliverEthernet(frame);
}
zx_status_t InfraBss::SendNextBu() {
ZX_DEBUG_ASSERT(bu_queue_.size() > 0);
if (bu_queue_.empty()) { return ZX_ERR_BAD_STATE; }
auto packet = std::move(bu_queue_.front());
bu_queue_.pop();
if (auto fc = packet->mut_field<FrameControl>(0)) {
// Set `more` bit if there are more BU available.
// IEEE Std 802.11-2016, 9.2.4.1.8
fc->set_more_data(bu_queue_.size() > 0);
debugps("[infra-bss] [%s] sent group addressed BU\n", bssid_.ToString().c_str());
return device_->SendWlan(std::move(packet), CBW20, WLAN_PHY_OFDM);
} else {
return ZX_ERR_BUFFER_TOO_SMALL;
}
}
std::optional<DataFrame<LlcHeader>> InfraBss::EthToDataFrame(const EthFrame& eth_frame,
bool needs_protection) {
size_t payload_len = eth_frame.body_len();
size_t max_frame_len = DataFrameHeader::max_len() + LlcHeader::max_len() + payload_len;
auto packet = GetWlanPacket(max_frame_len);
if (packet == nullptr) {
errorf("[infra-bss] [%s] cannot convert ethernet to data frame: out of packets (%zu)\n",
bssid_.ToString().c_str(), max_frame_len);
return {};
}
BufferWriter w(*packet);
auto data_hdr = w.Write<DataFrameHeader>();
data_hdr->fc.set_type(FrameType::kData);
data_hdr->fc.set_subtype(DataSubtype::kDataSubtype);
data_hdr->fc.set_from_ds(1);
data_hdr->fc.set_protected_frame(needs_protection ? 1 : 0);
data_hdr->addr1 = eth_frame.hdr()->dest;
data_hdr->addr2 = bssid_;
data_hdr->addr3 = eth_frame.hdr()->src;
data_hdr->sc.set_seq(NextSeq(*data_hdr));
auto llc_hdr = w.Write<LlcHeader>();
llc_hdr->dsap = kLlcSnapExtension;
llc_hdr->ssap = kLlcSnapExtension;
llc_hdr->control = kLlcUnnumberedInformation;
std::memcpy(llc_hdr->oui, kLlcOui, sizeof(llc_hdr->oui));
llc_hdr->protocol_id = eth_frame.hdr()->ether_type;
w.Write(eth_frame.body_data());
packet->set_len(w.WrittenBytes());
finspect("Outbound data frame: len %zu\n", w.WrittenBytes());
finspect(" wlan hdr: %s\n", debug::Describe(*data_hdr).c_str());
finspect(" llc hdr: %s\n", debug::Describe(*llc_hdr).c_str());
finspect(" frame : %s\n", debug::HexDump(packet->data(), packet->len()).c_str());
// Ralink appears to setup BlockAck session AND AMPDU handling
// TODO(porce): Use a separate sequence number space in that case
return DataFrame<LlcHeader>(std::move(packet));
}
void InfraBss::OnPreTbtt() {
bcn_sender_->UpdateBeacon(ps_cfg_);
ps_cfg_.NextDtimCount();
}
void InfraBss::OnBcnTxComplete() {
// Only send out multicast frames if the Beacon we just sent was a DTIM.
if (ps_cfg_.LastDtimCount() != 0) { return; }
if (bu_queue_.size() == 0) { return; }
debugps("[infra-bss] [%s] sending %zu group addressed BU\n", bssid_.ToString().c_str(),
bu_queue_.size());
while (bu_queue_.size() > 0) {
auto status = SendNextBu();
if (status != ZX_OK) {
errorf("[infra-bss] [%s] could not send group addressed BU: %d\n",
bssid_.ToString().c_str(), status);
return;
}
}
ps_cfg_.GetTim()->SetTrafficIndication(kGroupAdressedAid, false);
}
const common::MacAddr& InfraBss::bssid() const {
return bssid_;
}
uint64_t InfraBss::timestamp() {
zx_time_t now = zx_clock_get(ZX_CLOCK_MONOTONIC);
zx_duration_t uptime_ns = now - started_at_;
return uptime_ns / 1000; // as microseconds
}
seq_t InfraBss::NextSeq(const MgmtFrameHeader& hdr) {
return NextSeqNo(hdr, &seq_);
}
seq_t InfraBss::NextSeq(const MgmtFrameHeader& hdr, uint8_t aci) {
return NextSeqNo(hdr, aci, &seq_);
}
seq_t InfraBss::NextSeq(const DataFrameHeader& hdr) {
return NextSeqNo(hdr, &seq_);
}
bool InfraBss::IsRsn() const {
return !start_req_.rsne.is_null();
}
HtConfig InfraBss::Ht() const {
// TODO(NET-567): Reflect hardware capabilities and association negotiation
return HtConfig{
.ready = true,
.cbw_40_rx_ready = false,
.cbw_40_tx_ready = false,
};
}
const Span<const SupportedRate> InfraBss::Rates() const {
const auto rates =
GetRatesByChannel(device_->GetWlanInfo().ifc_info, device_->GetState()->channel().primary);
static_assert(sizeof(SupportedRate) == sizeof(rates[0]));
return {reinterpret_cast<const SupportedRate*>(rates.data()), rates.size()};
}
} // namespace wlan