blob: 1b1d7579bcc591f02adfa56732673de8bbcb36f5 [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/client/client_mlme.h>
#include <wlan/common/bitfield.h>
#include <wlan/common/channel.h>
#include <wlan/common/logging.h>
#include <wlan/mlme/client/client_factory.h>
#include <wlan/mlme/client/scanner.h>
#include <wlan/mlme/client/station.h>
#include <wlan/mlme/debug.h>
#include <wlan/mlme/mac_frame.h>
#include <wlan/mlme/packet.h>
#include <wlan/mlme/service.h>
#include <wlan/mlme/timer.h>
#include <wlan/mlme/timer_manager.h>
#include <wlan/mlme/wlan.h>
#include <fuchsia/wlan/mlme/cpp/fidl.h>
#include <lib/zx/time.h>
#include <zircon/assert.h>
#include <zircon/syscalls.h>
#include <cinttypes>
#include <cstring>
#include <sstream>
namespace wlan {
namespace wlan_mlme = ::fuchsia::wlan::mlme;
namespace wlan_stats = ::fuchsia::wlan::stats;
ClientMlme::ClientMlme(DeviceInterface* device) : device_(device), on_channel_handler_(this) {
debugfn();
}
ClientMlme::~ClientMlme() = default;
zx_status_t ClientMlme::Init() {
debugfn();
fbl::unique_ptr<Timer> timer;
ObjectId timer_id;
timer_id.set_subtype(to_enum_type(ObjectSubtype::kTimer));
timer_id.set_target(to_enum_type(ObjectTarget::kChannelScheduler));
zx_status_t status = device_->GetTimer(ToPortKey(PortKeyType::kMlme, timer_id.val()), &timer);
if (status != ZX_OK) {
errorf("could not create channel scheduler timer: %d\n", status);
return status;
}
chan_sched_.reset(new ChannelScheduler(&on_channel_handler_, device_, std::move(timer)));
scanner_.reset(new Scanner(device_, chan_sched_.get()));
return status;
}
zx_status_t ClientMlme::HandleTimeout(const ObjectId id) {
switch (id.target()) {
case to_enum_type(ObjectTarget::kChannelScheduler):
chan_sched_->HandleTimeout();
break;
case to_enum_type(ObjectTarget::kStation):
if (sta_ != nullptr) {
sta_->HandleTimeout();
} else {
warnf("timeout for unknown STA: %zu\n", id.mac());
}
break;
default:
ZX_DEBUG_ASSERT(0);
return ZX_ERR_NOT_SUPPORTED;
}
return ZX_OK;
}
void ClientMlme::HwScanComplete(uint8_t result_code) {
if (result_code == WLAN_HW_SCAN_SUCCESS) {
scanner_->HandleHwScanComplete();
} else {
scanner_->HandleHwScanAborted();
}
}
zx_status_t ClientMlme::HandleMlmeMsg(const BaseMlmeMsg& msg) {
if (auto scan_req = msg.As<wlan_mlme::ScanRequest>()) {
// Let the Scanner handle all MLME-SCAN.requests.
return scanner_->HandleMlmeScanReq(*scan_req);
} else if (auto join_req = msg.As<wlan_mlme::JoinRequest>()) {
// An MLME-JOIN-request will synchronize the MLME with the request's BSS.
// Synchronization is mandatory for spawning a client and starting its association flow.
Unjoin();
return HandleMlmeJoinReq(*join_req);
} else if (!join_ctx_.has_value()) {
warnf("rx'ed MLME message (ordinal: %u) before synchronizing with a BSS\n", msg.ordinal());
return ZX_ERR_BAD_STATE;
}
// TODO(hahnr): Keys should not be handled in the STA and instead in the MLME.
// For now, shortcut into the STA and leave this change as a follow-up.
if (auto setkeys_req = msg.As<wlan_mlme::SetKeysRequest>()) {
if (sta_ != nullptr) {
return sta_->SetKeys(setkeys_req->body()->keylist);
} else {
warnf("rx'ed MLME message (ordinal: %u) before authenticating with a BSS\n",
msg.ordinal());
return ZX_ERR_BAD_STATE;
}
}
// All remaining message must use the same BSS this MLME synchronized to before.
auto peer_addr = service::GetPeerAddr(msg);
if (!peer_addr.has_value()) {
warnf("rx'ed unsupported MLME msg (ordinal: %u)\n", msg.ordinal());
return ZX_ERR_INVALID_ARGS;
} else if (peer_addr.value() != join_ctx_->bssid()) {
warnf("rx'ed MLME msg (ordinal: %u) with unexpected peer addr; expected: %s ; actual: %s\n",
msg.ordinal(), join_ctx_->bssid().ToString().c_str(), peer_addr->ToString().c_str());
return ZX_ERR_INVALID_ARGS;
}
// This will spawn a new client instance and start association flow.
if (auto auth_req = msg.As<wlan_mlme::AuthenticateRequest>()) {
auto status = SpawnStation();
if (status != ZX_OK) {
errorf("error spawning STA: %d\n", status);
return service::SendAuthConfirm(device_, join_ctx_->bssid(),
wlan_mlme::AuthenticateResultCodes::REFUSED);
}
// Let station handle the request itself.
return sta_->Authenticate(auth_req->body()->auth_type,
auth_req->body()->auth_failure_timeout);
}
// If the STA exists, forward all incoming MLME messages.
if (sta_ == nullptr) {
warnf("rx'ed MLME message (ordinal: %u) before authenticating with a BSS\n", msg.ordinal());
return ZX_ERR_BAD_STATE;
}
if (auto deauth_req = msg.As<wlan_mlme::DeauthenticateRequest>()) {
return sta_->Deauthenticate(deauth_req->body()->reason_code);
} else if (auto assoc_req = msg.As<wlan_mlme::AssociateRequest>()) {
return sta_->Associate(*assoc_req->body()->rsn);
} else if (auto eapol_req = msg.As<wlan_mlme::EapolRequest>()) {
auto body = eapol_req->body();
return sta_->SendEapolFrame(body->data, common::MacAddr(body->src_addr),
common::MacAddr(body->dst_addr));
} else if (auto setctrlport_req = msg.As<wlan_mlme::SetControlledPortRequest>()) {
sta_->UpdateControlledPort(setctrlport_req->body()->state);
return ZX_OK;
} else {
warnf("rx'ed unsupported MLME message for client; ordinal: %u\n", msg.ordinal());
return ZX_ERR_BAD_STATE;
}
}
zx_status_t ClientMlme::HandleFramePacket(fbl::unique_ptr<Packet> pkt) {
switch (pkt->peer()) {
case Packet::Peer::kEthernet: {
// For outbound frame (Ethernet frame), hand to station directly so
// station sends frame to device when on channel, or buffers it when
// off channel.
if (sta_ != nullptr) {
if (auto eth_frame = EthFrameView::CheckType(pkt.get()).CheckLength()) {
sta_->HandleEthFrame(eth_frame.IntoOwned(std::move(pkt)));
}
}
break;
}
case Packet::Peer::kWlan: {
chan_sched_->HandleIncomingFrame(std::move(pkt));
break;
}
default:
errorf("unknown Packet peer: %u\n", pkt->peer());
break;
}
return ZX_OK;
}
zx_status_t ClientMlme::HandleMlmeJoinReq(const MlmeMsg<wlan_mlme::JoinRequest>& req) {
debugfn();
wlan_mlme::BSSDescription bss;
auto status = req.body()->selected_bss.Clone(&bss);
if (status != ZX_OK) {
errorf("error cloning MLME-JOIN.request: %d\n", status);
return status;
}
JoinContext join_ctx(std::move(bss), req.body()->phy, req.body()->cbw);
auto join_chan = join_ctx.channel();
debugjoin("setting channel to %s\n", common::ChanStrLong(join_chan).c_str());
status = chan_sched_->SetChannel(join_chan);
if (status != ZX_OK) {
errorf("could not set WLAN channel to %s: %d\n", common::ChanStrLong(join_chan).c_str(),
status);
service::SendJoinConfirm(device_, wlan_mlme::JoinResultCodes::JOIN_FAILURE_TIMEOUT);
return status;
}
// Notify driver about BSS.
wlan_bss_config_t cfg{
.bss_type = WLAN_BSS_TYPE_INFRASTRUCTURE,
.remote = true,
};
join_ctx.bssid().CopyTo(cfg.bssid);
status = device_->ConfigureBss(&cfg);
if (status != ZX_OK) {
errorf("error configuring BSS in driver; aborting: %d\n", status);
// TODO(hahnr): JoinResultCodes needs to define better result codes.
return service::SendJoinConfirm(device_, wlan_mlme::JoinResultCodes::JOIN_FAILURE_TIMEOUT);
}
join_ctx_ = std::move(join_ctx);
// Send confirmation for successful synchronization to SME.
return service::SendJoinConfirm(device_, wlan_mlme::JoinResultCodes::SUCCESS);
}
zx_status_t ClientMlme::SpawnStation() {
if (!join_ctx_.has_value()) { return ZX_ERR_BAD_STATE; }
auto client = CreateDefaultClient(device_, &join_ctx_.value(), chan_sched_.get());
if (!client) { return ZX_ERR_INTERNAL; }
sta_ = std::move(client);
return ZX_OK;
}
void ClientMlme::OnChannelHandlerImpl::PreSwitchOffChannel() {
debugfn();
if (mlme_->sta_ != nullptr) { mlme_->sta_->PreSwitchOffChannel(); }
}
void ClientMlme::OnChannelHandlerImpl::HandleOnChannelFrame(fbl::unique_ptr<Packet> packet) {
debugfn();
// Only WLAN frame is handed to channel handler since all Ethernet frames are handed
// over to station directly.
ZX_DEBUG_ASSERT(packet->peer() == Packet::Peer::kWlan);
if (auto mgmt_frame = MgmtFrameView<>::CheckType(packet.get()).CheckLength()) {
if (auto bcn_frame = mgmt_frame.CheckBodyType<Beacon>().CheckLength()) {
mlme_->scanner_->HandleBeacon(bcn_frame);
}
}
if (mlme_->sta_ != nullptr) { mlme_->sta_->HandleWlanFrame(std::move(packet)); }
}
void ClientMlme::OnChannelHandlerImpl::ReturnedOnChannel() {
debugfn();
if (mlme_->sta_ != nullptr) { mlme_->sta_->BackToMainChannel(); }
}
wlan_stats::MlmeStats ClientMlme::GetMlmeStats() const {
wlan_stats::MlmeStats mlme_stats{};
if (sta_ != nullptr) { mlme_stats.set_client_mlme_stats(sta_->stats()); }
return mlme_stats;
}
void ClientMlme::ResetMlmeStats() {
if (sta_ != nullptr) { sta_->ResetStats(); }
}
bool ClientMlme::OnChannel() {
if (chan_sched_ != nullptr) { return chan_sched_->OnChannel(); }
return false;
}
void ClientMlme::Unjoin() {
join_ctx_.reset();
sta_.reset();
}
} // namespace wlan