blob: c46399790439d17d981d80272950cfc808f6ec6b [file] [log] [blame]
// Copyright 2019 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 "src/connectivity/wlan/drivers/testing/lib/sim-fake-ap/sim-fake-ap.h"
#include <zircon/assert.h>
namespace wlan::simulation {
namespace wlan_ieee80211 = ::fuchsia::wlan::ieee80211;
void FakeAp::SetChannel(const wlan_channel_t& channel) {
// Time until next beacon.
zx::duration diff_to_next_beacon = beacon_state_.next_beacon_time - environment_->GetTime();
// If any station is associating with this AP, trigger channel switch.
if (GetNumAssociatedClient() > 0 && beacon_state_.is_beaconing &&
(csa_beacon_interval_ >= diff_to_next_beacon)) {
// If a new CSA is triggered, then it will override the previous one, and schedule a new channel
// switch time.
uint8_t cs_count = 0;
// This is the time period start from next beacon to the end of CSA beacon interval.
zx::duration cover = csa_beacon_interval_ - diff_to_next_beacon;
// This value is zero means next beacon is scheduled at the same time as CSA beacon interval
// end, and due to the mechanism of sim_env, this beacon will be sent out because it's scheduled
// earlier than we actually change channel.
if (cover.get() == 0) {
cs_count = 1;
} else {
cs_count = cover / beacon_state_.beacon_frame_.interval_ +
(cover % beacon_state_.beacon_frame_.interval_ ? 1 : 0);
}
if (beacon_state_.is_switching_channel) {
CancelNotification(beacon_state_.channel_switch_notification_id);
}
beacon_state_.beacon_frame_.AddCsaIe(channel, cs_count);
beacon_state_.channel_after_csa = channel;
environment_->ScheduleNotification(std::bind(&FakeAp::HandleStopCsaBeaconNotification, this),
csa_beacon_interval_,
&beacon_state_.channel_switch_notification_id);
beacon_state_.is_switching_channel = true;
} else {
tx_info_.channel = channel;
}
}
void FakeAp::SetBssid(const common::MacAddr& bssid) {
bssid_ = bssid;
beacon_state_.beacon_frame_.bssid_ = bssid;
}
void FakeAp::SetSsid(const wlan_ssid_t& ssid) {
ssid_ = ssid;
beacon_state_.beacon_frame_.AddSsidIe(ssid);
}
void FakeAp::SetCsaBeaconInterval(zx::duration interval) {
// Meaningless to set CSA_beacon_interval to 0.
ZX_ASSERT(interval.get() != 0);
csa_beacon_interval_ = interval;
}
zx_status_t FakeAp::SetSecurity(struct Security sec) {
// Should we clean the associated client list when we change security protocol?
if (!clients_.empty())
return ZX_ERR_BAD_STATE;
security_ = sec;
if (sec.cipher_suite != IEEE80211_CIPHER_SUITE_NONE)
beacon_state_.beacon_frame_.capability_info_.set_privacy(1);
if (sec.cipher_suite == IEEE80211_CIPHER_SUITE_WEP_40 ||
sec.cipher_suite == IEEE80211_CIPHER_SUITE_TKIP ||
sec.cipher_suite == IEEE80211_CIPHER_SUITE_WEP_104) {
}
return ZX_OK;
}
bool FakeAp::CanReceiveChannel(const wlan_channel_t& channel) {
// For now, require an exact match
return ((channel.primary == tx_info_.channel.primary) && (channel.cbw == tx_info_.channel.cbw) &&
(channel.secondary80 == tx_info_.channel.secondary80));
}
void FakeAp::ScheduleNextBeacon() {
environment_->ScheduleNotification(std::bind(&FakeAp::HandleBeaconNotification, this),
beacon_state_.beacon_frame_.interval_,
&beacon_state_.beacon_notification_id);
beacon_state_.next_beacon_time = environment_->GetTime() + beacon_state_.beacon_frame_.interval_;
}
void FakeAp::EnableBeacon(zx::duration beacon_period) {
if (beacon_state_.is_beaconing) {
// If we're already beaconing, we want to cancel any pending scheduled beacons before
// restarting with the new beacon period.
DisableBeacon();
}
// First beacon is sent out immediately
SimBeaconFrame tmp_beacon_frame(beacon_state_.beacon_frame_);
if (CheckIfErrInjBeaconEnabled()) {
tmp_beacon_frame = beacon_state_.beacon_mutator(tmp_beacon_frame);
}
environment_->Tx(tmp_beacon_frame, tx_info_, this);
beacon_state_.is_beaconing = true;
beacon_state_.beacon_frame_.interval_ = beacon_period;
ScheduleNextBeacon();
}
void FakeAp::DisableBeacon() {
// If it is not beaconing, do nothing.
if (!beacon_state_.is_beaconing) {
return;
}
// If we stop beaconing when channel is switching, we cancel the channel switch event and directly
// set channel to new channel.
if (beacon_state_.is_switching_channel) {
tx_info_.channel = beacon_state_.channel_after_csa;
beacon_state_.is_switching_channel = false;
CancelNotification(beacon_state_.channel_switch_notification_id);
}
beacon_state_.is_beaconing = false;
CancelNotification(beacon_state_.beacon_notification_id);
}
inline void FakeAp::CancelNotification(uint64_t id) {
ZX_ASSERT(environment_->CancelNotification(id) == ZX_OK);
}
std::shared_ptr<FakeAp::Client> FakeAp::AddClient(common::MacAddr mac_addr) {
auto client = std::make_shared<Client>(mac_addr, Client::NOT_AUTHENTICATED);
clients_.push_back(client);
return client;
}
std::shared_ptr<FakeAp::Client> FakeAp::FindClient(wlan::common::MacAddr mac_addr) {
for (auto it = clients_.begin(); it != clients_.end(); it++) {
if (mac_addr == (*it)->mac_addr_) {
return *it;
}
}
return std::shared_ptr<FakeAp::Client>(nullptr);
}
void FakeAp::RemoveClient(common::MacAddr mac_addr) {
for (auto it = clients_.begin(); it != clients_.end();) {
if (mac_addr == (*it)->mac_addr_) {
it = clients_.erase(it);
} else {
it++;
}
}
}
uint32_t FakeAp::GetNumAssociatedClient() const {
uint32_t client_count = 0;
for (auto it = clients_.begin(); it != clients_.end(); it++) {
if ((*it)->status_ == Client::ASSOCIATED) {
client_count++;
}
}
return client_count;
}
void FakeAp::ScheduleAssocResp(wlan_ieee80211::StatusCode status, const common::MacAddr& dst) {
environment_->ScheduleNotification(
std::bind(&FakeAp::HandleAssocRespNotification, this, status, dst), assoc_resp_interval_);
}
void FakeAp::ScheduleProbeResp(const common::MacAddr& dst) {
environment_->ScheduleNotification(std::bind(&FakeAp::HandleProbeRespNotification, this, dst),
probe_resp_interval_);
}
void FakeAp::ScheduleAuthResp(std::shared_ptr<const SimAuthFrame> auth_frame_in,
wlan_ieee80211::StatusCode status) {
SimAuthFrame auth_resp_frame(bssid_, auth_frame_in->src_addr_, auth_frame_in->seq_num_,
auth_frame_in->auth_type_, status);
auth_resp_frame.sec_proto_type_ = security_.sec_type;
if (security_.auth_handling_mode == AUTH_TYPE_SAE) {
// Here we copy the SAE payload from the auth req frame to auth resp frame without any
// modification for test purpose.
auth_resp_frame.payload_ = auth_frame_in->payload_;
} else {
// The seq_num of SAE authentication frame is {1, 1, 2, 2} instead of {1, 2, 3, 4}.
auth_resp_frame.seq_num_ += 1;
}
environment_->ScheduleNotification(
std::bind(&FakeAp::HandleAuthRespNotification, this, auth_resp_frame), auth_resp_interval_);
}
void FakeAp::ScheduleQosData(bool toDS, bool fromDS, const common::MacAddr& addr1,
const common::MacAddr& addr2, const common::MacAddr& addr3,
const std::vector<uint8_t>& payload) {
environment_->ScheduleNotification(std::bind(&FakeAp::HandleQosDataNotification, this, toDS,
fromDS, addr1, addr2, addr3, payload),
data_forward_interval_);
}
void FakeAp::Rx(std::shared_ptr<const SimFrame> frame, std::shared_ptr<const WlanRxInfo> info) {
// Make sure we heard it
if (!CanReceiveChannel(info->channel)) {
return;
}
switch (frame->FrameType()) {
case SimFrame::FRAME_TYPE_MGMT: {
RxMgmtFrame(std::static_pointer_cast<const SimManagementFrame>(frame));
break;
}
case SimFrame::FRAME_TYPE_DATA: {
RxDataFrame(std::static_pointer_cast<const SimDataFrame>(frame));
break;
}
default:
break;
}
}
void FakeAp::RxMgmtFrame(std::shared_ptr<const SimManagementFrame> mgmt_frame) {
using wlan_ieee80211::StatusCode;
switch (mgmt_frame->MgmtFrameType()) {
case SimManagementFrame::FRAME_TYPE_PROBE_REQ: {
auto probe_req_frame = std::static_pointer_cast<const SimProbeReqFrame>(mgmt_frame);
ScheduleProbeResp(probe_req_frame->src_addr_);
break;
}
case SimManagementFrame::FRAME_TYPE_ASSOC_REQ: {
auto assoc_req_frame = std::static_pointer_cast<const SimAssocReqFrame>(mgmt_frame);
// Ignore requests that are not for us
if (assoc_req_frame->bssid_ != bssid_) {
return;
}
if (assoc_handling_mode_ == ASSOC_IGNORED) {
return;
}
if ((assoc_req_frame->ssid_.len != ssid_.len) ||
memcmp(assoc_req_frame->ssid_.ssid, ssid_.ssid, ssid_.len)) {
ScheduleAssocResp(StatusCode::REFUSED_REASON_UNSPECIFIED, assoc_req_frame->src_addr_);
return;
}
if (assoc_handling_mode_ == ASSOC_REFUSED_TEMPORARILY) {
ScheduleAssocResp(StatusCode::REFUSED_TEMPORARILY, assoc_req_frame->src_addr_);
return;
}
if (assoc_handling_mode_ == ASSOC_REFUSED) {
ScheduleAssocResp(StatusCode::REFUSED_REASON_UNSPECIFIED, assoc_req_frame->src_addr_);
return;
}
auto client = FindClient(assoc_req_frame->src_addr_);
if (!client) {
ScheduleAssocResp(StatusCode::REFUSED_REASON_UNSPECIFIED, assoc_req_frame->src_addr_);
return;
}
// Make sure the client is not associated.
if (client->status_ == Client::ASSOCIATED) {
ScheduleAssocResp(StatusCode::REFUSED_TEMPORARILY, assoc_req_frame->src_addr_);
return;
}
if (client->status_ != Client::AUTHENTICATED) {
// If the status of this client is AUTHENTICATING, we also remove it from the list.
RemoveClient(assoc_req_frame->src_addr_);
ScheduleAssocResp(StatusCode::REFUSED_REASON_UNSPECIFIED, assoc_req_frame->src_addr_);
return;
}
client->status_ = Client::ASSOCIATED;
ScheduleAssocResp(StatusCode::SUCCESS, assoc_req_frame->src_addr_);
break;
}
case SimManagementFrame::FRAME_TYPE_DISASSOC_REQ: {
auto disassoc_req_frame = std::static_pointer_cast<const SimDisassocReqFrame>(mgmt_frame);
// Ignore requests that are not for us
if (disassoc_req_frame->dst_addr_ != bssid_) {
return;
}
// Make sure the client is already associated
for (auto client : clients_) {
if (client->mac_addr_ == disassoc_req_frame->src_addr_ &&
client->status_ == Client::ASSOCIATED) {
// Client is already associated
RemoveClient(disassoc_req_frame->src_addr_);
return;
}
}
break;
}
case SimManagementFrame::FRAME_TYPE_AUTH: {
auto auth_req_frame = std::static_pointer_cast<const SimAuthFrame>(mgmt_frame);
if (auth_req_frame->dst_addr_ != bssid_) {
return;
}
if (assoc_handling_mode_ == ASSOC_IGNORED) {
return;
}
if (assoc_handling_mode_ == ASSOC_REFUSED) {
ScheduleAuthResp(auth_req_frame, StatusCode::REFUSED_REASON_UNSPECIFIED);
return;
}
if (security_.sec_type != auth_req_frame->sec_proto_type_) {
ScheduleAuthResp(auth_req_frame, StatusCode::REFUSED_REASON_UNSPECIFIED);
return;
}
// If it's not matching AP's authentication handling mode, just reply a refuse.
if (auth_req_frame->auth_type_ != security_.auth_handling_mode) {
RemoveClient(auth_req_frame->src_addr_);
ScheduleAuthResp(auth_req_frame, StatusCode::REFUSED_REASON_UNSPECIFIED);
return;
}
// Filt out frames in which sec_type does not match auth_type.
if ((security_.sec_type == SEC_PROTO_TYPE_WPA1 ||
security_.sec_type == SEC_PROTO_TYPE_WPA2) &&
auth_req_frame->auth_type_ != AUTH_TYPE_OPEN) {
ScheduleAuthResp(auth_req_frame, StatusCode::REFUSED_REASON_UNSPECIFIED);
return;
}
if (security_.sec_type == SEC_PROTO_TYPE_WPA3 &&
auth_req_frame->auth_type_ != AUTH_TYPE_SAE) {
ScheduleAuthResp(auth_req_frame, StatusCode::REFUSED_REASON_UNSPECIFIED);
return;
}
// Filt out frames in which seq_num does not match auth_type.
if (auth_req_frame->seq_num_ != 1 &&
(auth_req_frame->seq_num_ != 3 || auth_req_frame->auth_type_ == AUTH_TYPE_OPEN) &&
(auth_req_frame->seq_num_ != 2 || auth_req_frame->auth_type_ != AUTH_TYPE_SAE)) {
RemoveClient(auth_req_frame->src_addr_);
return;
}
// Status of auth req should be WLAN_STATUS_CODE_SUCCESS
if (auth_req_frame->status_ != StatusCode::SUCCESS) {
RemoveClient(auth_req_frame->src_addr_);
return;
}
// Make sure this client is not associated to continue authentication
auto client = FindClient(auth_req_frame->src_addr_);
if (client && client->status_ == Client::ASSOCIATED) {
return;
}
if (!client) {
// A new client need to conduct authentication
if (auth_req_frame->seq_num_ != 1)
return;
client = AddClient(auth_req_frame->src_addr_);
}
switch (security_.auth_handling_mode) {
case AUTH_TYPE_OPEN:
// Even when the client status is AUTHENTICATED, we will send out a auth resp frame to let
// client's status catch up in a same pace as AP.
client->status_ = Client::AUTHENTICATED;
break;
case AUTH_TYPE_SHARED_KEY:
if (client->status_ == Client::NOT_AUTHENTICATED) {
// We've already checked whether the seq_num is 1.
client->status_ = Client::AUTHENTICATING;
} else if (client->status_ == Client::AUTHENTICATING) {
if (auth_req_frame->seq_num_ == 3) {
if (security_.expect_challenge_failure) {
// Refuse authentication if this AP has been configured to.
// TODO (fxb/61139): Actually check the challenge response rather than hardcoding
// authentication success or failure using expect_challenge_failure.
RemoveClient(auth_req_frame->src_addr_);
ScheduleAuthResp(auth_req_frame, StatusCode::CHALLENGE_FAILURE);
return;
}
client->status_ = Client::AUTHENTICATED;
}
// If the seq num is 1, we will just send out a resp and keep the status.
}
// If the status is already AUTHENTICATED, we will just send out a resp and keep the
// status.
break;
case AUTH_TYPE_SAE:
if (client->status_ == Client::NOT_AUTHENTICATED) {
// We've already checked whether the seq_num is 1.
client->status_ = Client::AUTHENTICATING;
} else if (client->status_ == Client::AUTHENTICATING) {
if (auth_req_frame->seq_num_ == 2) {
client->status_ = Client::AUTHENTICATED;
}
}
break;
default:
ZX_ASSERT_MSG(false, "Unsupported auth_handling_mode.");
}
ScheduleAuthResp(auth_req_frame, StatusCode::SUCCESS);
break;
}
default:
break;
}
}
void FakeAp::RxDataFrame(std::shared_ptr<const SimDataFrame> data_frame) {
switch (data_frame->DataFrameType()) {
case SimDataFrame::FRAME_TYPE_QOS_DATA:
// If we are not the intended receiver, ignore it
if (data_frame->addr1_ != bssid_) {
return;
}
// IEEE Std 802.11-2016, 9.2.4.1.4
if (data_frame->toDS_ && data_frame->fromDS_) {
ZX_ASSERT_MSG(false, "No support for Mesh data frames in Fake AP\n");
} else if (data_frame->toDS_ && !data_frame->fromDS_) {
// Currently no sim support for any PAE and any higher level protocols, so just check other
// local clients in infrastructure BSS, otherwise don't deliver anything
for (auto client : clients_) {
if (data_frame->addr3_ == client->mac_addr_) {
// Forward frame to destination
ScheduleQosData(false, true, client->mac_addr_, bssid_, data_frame->addr2_,
data_frame->payload_);
break;
}
}
} else {
// Under the assumption that the Fake AP does not support Mesh data frames
ZX_ASSERT_MSG(false,
"Data frame addressed to AP but marked destination as STA, frame invalid\n");
}
break;
default:
break;
}
}
zx_status_t FakeAp::DisassocSta(const common::MacAddr& sta_mac, uint16_t reason) {
// Make sure the client is already associated
SimDisassocReqFrame disassoc_req_frame(bssid_, sta_mac, reason);
for (auto client : clients_) {
if (client->mac_addr_ == sta_mac && client->status_ == Client::ASSOCIATED) {
// Client is already associated
environment_->Tx(disassoc_req_frame, tx_info_, this);
RemoveClient(sta_mac);
return ZX_OK;
}
}
// client not found
return ZX_ERR_INVALID_ARGS;
}
void FakeAp::HandleBeaconNotification() {
ZX_ASSERT(beacon_state_.is_beaconing);
SimBeaconFrame tmp_beacon_frame(beacon_state_.beacon_frame_);
if (CheckIfErrInjBeaconEnabled()) {
tmp_beacon_frame = beacon_state_.beacon_mutator(tmp_beacon_frame);
}
environment_->Tx(tmp_beacon_frame, tx_info_, this);
// Channel switch count decrease by 1 each time after sending a CSA beacon.
if (beacon_state_.is_switching_channel) {
auto csa_generic_ie = beacon_state_.beacon_frame_.FindIe(InformationElement::IE_TYPE_CSA);
ZX_ASSERT(csa_generic_ie != nullptr);
auto csa_ie = std::static_pointer_cast<CsaInformationElement>(csa_generic_ie);
ZX_ASSERT(csa_ie->channel_switch_count_ > 0);
csa_ie->channel_switch_count_--;
}
ScheduleNextBeacon();
}
void FakeAp::AddErrInjBeacon(std::function<SimBeaconFrame(const SimBeaconFrame&)> beacon_mutator) {
beacon_state_.beacon_mutator = std::move(beacon_mutator);
}
void FakeAp::DelErrInjBeacon() { beacon_state_.beacon_mutator = nullptr; }
bool FakeAp::CheckIfErrInjBeaconEnabled() const { return beacon_state_.beacon_mutator != nullptr; }
void FakeAp::HandleStopCsaBeaconNotification() {
ZX_ASSERT(beacon_state_.is_beaconing);
beacon_state_.beacon_frame_.RemoveIe(InformationElement::IE_TYPE_CSA);
tx_info_.channel = beacon_state_.channel_after_csa;
beacon_state_.is_switching_channel = false;
}
void FakeAp::HandleAssocRespNotification(wlan_ieee80211::StatusCode status, common::MacAddr dst) {
SimAssocRespFrame assoc_resp_frame(bssid_, dst, status);
assoc_resp_frame.capability_info_.set_val(beacon_state_.beacon_frame_.capability_info_.val());
environment_->Tx(assoc_resp_frame, tx_info_, this);
}
void FakeAp::HandleProbeRespNotification(common::MacAddr dst) {
SimProbeRespFrame probe_resp_frame(bssid_, dst, ssid_);
probe_resp_frame.capability_info_.set_val(beacon_state_.beacon_frame_.capability_info_.val());
environment_->Tx(probe_resp_frame, tx_info_, this);
}
void FakeAp::HandleAuthRespNotification(SimAuthFrame auth_resp_frame) {
environment_->Tx(auth_resp_frame, tx_info_, this);
}
void FakeAp::HandleQosDataNotification(bool toDS, bool fromDS, const common::MacAddr& addr1,
const common::MacAddr& addr2, const common::MacAddr& addr3,
const std::vector<uint8_t>& payload) {
SimQosDataFrame data_frame(toDS, fromDS, addr1, addr2, addr3, 0, payload);
environment_->Tx(data_frame, tx_info_, this);
}
void FakeAp::SetAssocHandling(enum AssocHandling mode) { assoc_handling_mode_ = mode; }
} // namespace wlan::simulation