blob: ac074ba232bfcd2dd46e90ac5aa0dec4d3a7e0ff [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 "dispatcher.h"
#include "lib/wlan/fidl/wlan_mlme.fidl-common.h"
#include "lib/wlan/fidl/wlan_mlme_ext.fidl-common.h"
#include <ddk/protocol/wlan.h>
#include <fbl/unique_ptr.h>
#include <wlan/common/channel.h>
#include <wlan/common/mac_frame.h>
#include <wlan/mlme/ap_mlme.h>
#include <wlan/mlme/client_mlme.h>
#include <wlan/mlme/frame_handler.h>
#include <wlan/mlme/packet.h>
#include <wlan/mlme/serialize.h>
#include <zircon/types.h>
#include <cinttypes>
#include <cstring>
#include <sstream>
namespace wlan {
namespace {
template <unsigned int N, typename T> T align(T t) {
static_assert(N > 1 && !(N & (N - 1)), "alignment must be with a power of 2");
return (t + (N - 1)) & ~(N - 1);
}
void DumpPacket(const Packet& packet) {
const uint8_t* p = packet.data();
for (size_t i = 0; i < packet.len(); i++) {
if (i % 16 == 0) { std::printf("\nwlan: "); }
std::printf("%02x ", p[i]);
}
std::printf("\n");
}
void DumpRxInfo(const wlan_rx_info_t& rxinfo) {
std::printf(
"WLAN RxInfo: "
"flags %08x valid_fields %08x phy %u data_rate %u chan %s "
"mcs %u rssi %u rcpi %u snr %u \n",
rxinfo.rx_flags, rxinfo.valid_fields, rxinfo.phy, rxinfo.data_rate,
common::ChanStr(rxinfo.chan).c_str(), rxinfo.mcs, rxinfo.rssi, rxinfo.rcpi, rxinfo.snr);
}
void DumpFrameHeader(const FrameHeader& hdr, size_t len) {
// TODO(porce): Introspect the frame type in general, and support Control Frames.
std::printf(
"WLAN Frame: Len %zu"
"\n "
"Proto %u Type %u Subtype %u ToDs %u FromDs %u Frag %u Retry %u PwrMgmt %u MoreData %u "
"Protected %u Htc %u Duration %u Seq [%u:%u]"
"\n "
"[Addr1] %s [Addr2] %s [Addr3] %s"
"\n",
len, hdr.fc.protocol_version(), hdr.fc.type(), hdr.fc.subtype(), hdr.fc.to_ds(),
hdr.fc.from_ds(), hdr.fc.more_frag(), hdr.fc.retry(), hdr.fc.pwr_mgmt(), hdr.fc.more_data(),
hdr.fc.protected_frame(), hdr.fc.htc_order(), hdr.duration, hdr.sc.frag(), hdr.sc.seq(),
MACSTR(hdr.addr1), MACSTR(hdr.addr2), MACSTR(hdr.addr3));
}
#define DEBUG_DUMP_WLAN_FRAME(packet) \
do { \
if (kLogLevel & kLogWlanFrameTrace) { \
auto rxinfo = packet->ctrl_data<wlan_rx_info_t>(); \
DumpRxInfo(*rxinfo); \
auto hdr = packet->field<FrameHeader>(0); \
DumpFrameHeader(*hdr, packet->len()); \
} \
} while (false)
} // namespace
Dispatcher::Dispatcher(DeviceInterface* device) : device_(device) {
debugfn();
}
Dispatcher::~Dispatcher() {}
template <>
zx_status_t Dispatcher::HandleMlmeMethod<DeviceQueryRequest>(const Packet* packet, Method method);
zx_status_t Dispatcher::HandlePacket(const Packet* packet) {
debugfn();
ZX_DEBUG_ASSERT(packet != nullptr);
ZX_DEBUG_ASSERT(packet->peer() != Packet::Peer::kUnknown);
debughdr("packet data=%p len=%zu peer=%s\n", packet->data(), packet->len(),
packet->peer() == Packet::Peer::kWlan
? "Wlan"
: packet->peer() == Packet::Peer::kEthernet
? "Ethernet"
: packet->peer() == Packet::Peer::kService ? "Service" : "Unknown");
if (kLogLevel & kLogDataPacketTrace) { DumpPacket(*packet); }
// If there is no active MLME, block all packets but service ones.
// MLME-JOIN.request and MLME-START.request implicitly select a mode and initialize the MLME.
// DEVICE_QUERY.request is used to obtain device capabilities.
auto service_msg = (packet->peer() == Packet::Peer::kService);
if (mlme_ == nullptr && !service_msg) {
errorf("received packet with no active MLME\n");
return ZX_OK;
}
zx_status_t status = ZX_OK;
switch (packet->peer()) {
case Packet::Peer::kService:
status = HandleSvcPacket(packet);
break;
case Packet::Peer::kEthernet:
status = HandleEthPacket(packet);
break;
case Packet::Peer::kWlan: {
auto fc = packet->field<FrameControl>(0);
debughdr("FrameControl type: %u subtype: %u\n", fc->type(), fc->subtype());
// TODO(porce): Handle HTC field.
if (fc->HasHtCtrl()) {
warnf("WLAN frame (type %u:%u) HTC field is present but not handled. Drop.", fc->type(),
fc->subtype());
status = ZX_ERR_NOT_SUPPORTED;
break;
}
switch (fc->type()) {
case FrameType::kManagement:
DEBUG_DUMP_WLAN_FRAME(packet);
status = HandleMgmtPacket(packet);
break;
case FrameType::kControl:
status = HandleCtrlPacket(packet);
break;
case FrameType::kData:
DEBUG_DUMP_WLAN_FRAME(packet);
status = HandleDataPacket(packet);
break;
default:
warnf("unknown MAC frame type %u\n", fc->type());
status = ZX_ERR_NOT_SUPPORTED;
break;
}
break;
}
default:
break;
}
return status;
}
zx_status_t Dispatcher::HandlePortPacket(uint64_t key) {
debugfn();
ZX_DEBUG_ASSERT(ToPortKeyType(key) == PortKeyType::kMlme);
ObjectId id(ToPortKeyId(key));
switch (id.subtype()) {
case to_enum_type(ObjectSubtype::kTimer): {
auto status = mlme_->HandleTimeout(id);
if (status == ZX_ERR_NOT_SUPPORTED) {
warnf("unknown MLME timer target: %u\n", id.target());
}
break;
}
default:
warnf("unknown MLME event subtype: %u\n", id.subtype());
}
return ZX_OK;
}
zx_status_t Dispatcher::HandleCtrlPacket(const Packet* packet) {
debugfn();
// Currently not used.
return ZX_OK;
}
zx_status_t Dispatcher::HandleDataPacket(const Packet* packet) {
debugfn();
auto hdr = packet->field<DataFrameHeader>(0);
if (hdr == nullptr) {
errorf("short data packet len=%zu\n", packet->len());
return ZX_OK;
}
auto rxinfo = packet->ctrl_data<wlan_rx_info_t>();
ZX_DEBUG_ASSERT(rxinfo);
switch (hdr->fc.subtype()) {
case DataSubtype::kNull: {
auto frame = ImmutableDataFrame<NilHeader>(hdr, nullptr, 0);
return mlme_->HandleFrame(frame, *rxinfo);
}
case DataSubtype::kDataSubtype:
// Fall-through
case DataSubtype::kQosdata:
break;
default:
warnf("unsupported data subtype %02x\n", hdr->fc.subtype());
return ZX_OK;
}
auto llc_offset = hdr->len();
if (rxinfo->rx_flags & WLAN_RX_INFO_FLAGS_FRAME_BODY_PADDING_4) {
llc_offset = align<4>(llc_offset);
}
auto llc = packet->field<LlcHeader>(llc_offset);
if (llc == nullptr) {
errorf("short data packet len=%zu\n", packet->len());
return ZX_ERR_IO;
}
if (packet->len() < kDataPayloadHeader) {
errorf("short LLC packet len=%zu\n", packet->len());
return ZX_ERR_IO;
}
size_t llc_len = packet->len() - llc_offset;
auto frame = ImmutableDataFrame<LlcHeader>(hdr, llc, llc_len);
return mlme_->HandleFrame(frame, *rxinfo);
}
zx_status_t Dispatcher::HandleMgmtPacket(const Packet* packet) {
debugfn();
auto hdr = packet->field<MgmtFrameHeader>(0);
if (hdr == nullptr) {
errorf("short mgmt packet len=%zu\n", packet->len());
return ZX_OK;
}
debughdr("Frame control: %04x duration: %u seq: %u frag: %u\n", hdr->fc.val(), hdr->duration,
hdr->sc.seq(), hdr->sc.frag());
const common::MacAddr& dst = hdr->addr1;
const common::MacAddr& src = hdr->addr2;
const common::MacAddr& bssid = hdr->addr3;
debughdr("dest: %s source: %s bssid: %s\n", MACSTR(dst), MACSTR(src), MACSTR(bssid));
auto rxinfo = packet->ctrl_data<wlan_rx_info_t>();
ZX_DEBUG_ASSERT(rxinfo);
size_t payload_len = packet->len() - hdr->len();
switch (hdr->fc.subtype()) {
case ManagementSubtype::kBeacon: {
auto beacon = packet->field<Beacon>(hdr->len());
if (beacon == nullptr) {
errorf("beacon packet too small (len=%zd)\n", payload_len);
return ZX_ERR_IO;
}
auto frame = ImmutableMgmtFrame<Beacon>(hdr, beacon, payload_len);
return mlme_->HandleFrame(frame, *rxinfo);
}
case ManagementSubtype::kProbeResponse: {
auto proberesp = packet->field<ProbeResponse>(hdr->len());
if (proberesp == nullptr) {
errorf("probe response packet too small (len=%zd)\n", payload_len);
return ZX_ERR_IO;
}
auto frame = ImmutableMgmtFrame<ProbeResponse>(hdr, proberesp, payload_len);
return mlme_->HandleFrame(frame, *rxinfo);
}
case ManagementSubtype::kAuthentication: {
auto auth = packet->field<Authentication>(hdr->len());
if (auth == nullptr) {
errorf("authentication packet too small (len=%zd)\n", payload_len);
return ZX_ERR_IO;
}
auto frame = ImmutableMgmtFrame<Authentication>(hdr, auth, payload_len);
return mlme_->HandleFrame(frame, *rxinfo);
}
case ManagementSubtype::kDeauthentication: {
auto deauth = packet->field<Deauthentication>(hdr->len());
if (deauth == nullptr) {
errorf("deauthentication packet too small (len=%zd)\n", payload_len);
return ZX_ERR_IO;
}
auto frame = ImmutableMgmtFrame<Deauthentication>(hdr, deauth, payload_len);
return mlme_->HandleFrame(frame, *rxinfo);
}
case ManagementSubtype::kAssociationRequest: {
auto authreq = packet->field<AssociationRequest>(hdr->len());
if (authreq == nullptr) {
errorf("assocation request packet too small (len=%zd)\n", payload_len);
return ZX_ERR_IO;
}
auto frame = ImmutableMgmtFrame<AssociationRequest>(hdr, authreq, payload_len);
return mlme_->HandleFrame(frame, *rxinfo);
}
case ManagementSubtype::kAssociationResponse: {
auto authresp = packet->field<AssociationResponse>(hdr->len());
if (authresp == nullptr) {
errorf("assocation response packet too small (len=%zd)\n", payload_len);
return ZX_ERR_IO;
}
auto frame = ImmutableMgmtFrame<AssociationResponse>(hdr, authresp, payload_len);
return mlme_->HandleFrame(frame, *rxinfo);
}
case ManagementSubtype::kDisassociation: {
auto disassoc = packet->field<Disassociation>(hdr->len());
if (disassoc == nullptr) {
errorf("disassociation packet too small (len=%zd)\n", payload_len);
return ZX_ERR_IO;
}
auto frame = ImmutableMgmtFrame<Disassociation>(hdr, disassoc, payload_len);
return mlme_->HandleFrame(frame, *rxinfo);
}
case ManagementSubtype::kAction: {
auto action = packet->field<ActionFrame>(hdr->len());
if (action == nullptr) {
errorf("action packet too small (len=%zd)\n", payload_len);
return ZX_ERR_IO;
}
if (!hdr->IsAction()) {
errorf("action packet is not an action\n");
return ZX_ERR_IO;
}
HandleActionPacket(packet, hdr, action, rxinfo);
}
default:
if (!dst.IsBcast()) {
// TODO(porce): Evolve this logic to support AP mode.
debugf("Rxed Mgmt frame (type: %d) but not handled\n", hdr->fc.subtype());
}
break;
}
return ZX_OK;
}
zx_status_t Dispatcher::HandleActionPacket(const Packet* packet, const MgmtFrameHeader* hdr,
const ActionFrame* action,
const wlan_rx_info_t* rxinfo) {
if (action->category != action::Category::kBlockAck) {
verbosef("Rxed Action frame with category %d. Not handled.\n", action->category);
return ZX_OK;
}
size_t payload_len = packet->len() - hdr->len();
auto ba_frame = packet->field<ActionFrameBlockAck>(hdr->len());
if (ba_frame == nullptr) {
errorf("bloackack packet too small (len=%zd)\n", payload_len);
return ZX_ERR_IO;
}
switch (ba_frame->action) {
case action::BaAction::kAddBaRequest: {
auto addbar = packet->field<AddBaRequestFrame>(hdr->len());
if (addbar == nullptr) {
errorf("addbar packet too small (len=%zd)\n", payload_len);
return ZX_ERR_IO;
}
// TODO(porce): Support AddBar. Work with lower mac.
// TODO(porce): Make this conditional depending on the hardware capability.
auto frame = ImmutableMgmtFrame<AddBaRequestFrame>(hdr, addbar, payload_len);
return mlme_->HandleFrame(frame, *rxinfo);
break;
}
case action::BaAction::kAddBaResponse: {
auto addba_resp = packet->field<AddBaResponseFrame>(hdr->len());
if (addba_resp == nullptr) {
errorf("addba_resp packet too small (len=%zd)\n", payload_len);
return ZX_ERR_IO;
}
auto frame = ImmutableMgmtFrame<AddBaResponseFrame>(hdr, addba_resp, payload_len);
return mlme_->HandleFrame(frame, *rxinfo);
break;
}
case action::BaAction::kDelBa:
// fall-through
default:
warnf("BlockAck action frame with action %u not handled.\n", ba_frame->action);
break;
}
return ZX_OK;
}
zx_status_t Dispatcher::HandleEthPacket(const Packet* packet) {
debugfn();
auto hdr = packet->field<EthernetII>(0);
if (hdr == nullptr) {
errorf("short ethernet frame len=%zu\n", packet->len());
return ZX_ERR_IO;
}
auto payload = packet->field<uint8_t>(sizeof(hdr));
size_t payload_len = packet->len() - sizeof(hdr);
auto frame = ImmutableBaseFrame<EthernetII>(hdr, payload, payload_len);
return mlme_->HandleFrame(frame);
}
zx_status_t Dispatcher::HandleSvcPacket(const Packet* packet) {
debugfn();
const uint8_t* bytes = packet->data();
auto hdr = FromBytes<ServiceHeader>(bytes, packet->len());
if (hdr == nullptr) {
errorf("short service packet len=%zu\n", packet->len());
return ZX_OK;
}
debughdr("service packet txn_id=%" PRIu64 " flags=%u ordinal=%u\n", hdr->txn_id, hdr->flags,
hdr->ordinal);
auto method = static_cast<Method>(hdr->ordinal);
if (method == Method::DEVICE_QUERY_request) {
return HandleMlmeMethod<DeviceQueryRequest>(packet, method);
}
// Only a subset of requests are supported before an MLME has been initialized.
if (mlme_ == nullptr) {
switch (method) {
case Method::SCAN_request:
// fallthrough
case Method::JOIN_request: {
mlme_.reset(new ClientMlme(device_));
auto status = mlme_->Init();
if (status != ZX_OK) {
errorf("Client MLME could not be initialized\n");
mlme_.reset();
return status;
}
break;
}
case Method::START_request: {
mlme_.reset(new ApMlme(device_));
auto status = mlme_->Init();
if (status != ZX_OK) {
errorf("AP MLME could not be initialized\n");
mlme_.reset();
return status;
}
break;
}
default:
warnf("unknown MLME method %u with no active MLME\n", method);
return ZX_OK;
}
}
switch (method) {
case Method::RESET_request:
// Let currently active MLME handle RESET request, then, reset MLME.
HandleMlmeMethod<ResetRequest>(packet, method);
mlme_.reset();
return ZX_OK;
case Method::START_request:
return HandleMlmeMethodInlinedStruct<StartRequest>(packet, method);
case Method::SCAN_request:
return HandleMlmeMethod<ScanRequest>(packet, method);
case Method::JOIN_request:
return HandleMlmeMethod<JoinRequest>(packet, method);
case Method::AUTHENTICATE_request:
return HandleMlmeMethod<AuthenticateRequest>(packet, method);
case Method::DEAUTHENTICATE_request:
return HandleMlmeMethod<DeauthenticateRequest>(packet, method);
case Method::ASSOCIATE_request:
return HandleMlmeMethod<AssociateRequest>(packet, method);
case Method::EAPOL_request:
return HandleMlmeMethod<EapolRequest>(packet, method);
case Method::SETKEYS_request:
return HandleMlmeMethod<SetKeysRequest>(packet, method);
default:
warnf("unknown MLME method %u\n", hdr->ordinal);
return ZX_ERR_NOT_SUPPORTED;
}
}
template <typename Message, typename FidlStruct>
zx_status_t Dispatcher::HandleMlmeMethod(const Packet* packet, Method method) {
FidlStruct req;
auto status = DeserializeServiceMsg<Message>(*packet, method, &req);
if (status != ZX_OK) {
errorf("could not deserialize MLME Method %d: %d\n", method, status);
return status;
}
ZX_DEBUG_ASSERT(!req.is_null());
return mlme_->HandleFrame(method, *req);
}
template <>
zx_status_t Dispatcher::HandleMlmeMethod<DeviceQueryRequest>(const Packet* unused_packet,
Method method) {
debugfn();
ZX_DEBUG_ASSERT(method == Method::DEVICE_QUERY_request);
auto resp = DeviceQueryResponse::New();
resp->modes.resize(0);
resp->bands.resize(0);
const wlanmac_info_t& info = device_->GetWlanInfo();
if (info.mac_modes & WLAN_MAC_MODE_STA) { resp->modes.push_back(MacMode::STA); }
if (info.mac_modes & WLAN_MAC_MODE_AP) { resp->modes.push_back(MacMode::AP); }
for (uint8_t band_idx = 0; band_idx < info.num_bands; band_idx++) {
const wlan_band_info_t& band_info = info.bands[band_idx];
auto band = BandCapabilities::New();
band->basic_rates.resize(0);
for (size_t rate_idx = 0; rate_idx < sizeof(band_info.basic_rates); rate_idx++) {
if (band_info.basic_rates[rate_idx] != 0) {
band->basic_rates.push_back(band_info.basic_rates[rate_idx]);
}
}
const wlan_chan_list_t& chan_list = band_info.supported_channels;
band->base_frequency = chan_list.base_freq;
band->channels.resize(0);
for (size_t chan_idx = 0; chan_idx < sizeof(chan_list.channels); chan_idx++) {
if (chan_list.channels[chan_idx] != 0) {
band->channels.push_back(chan_list.channels[chan_idx]);
}
}
resp->bands.push_back(std::move(band));
}
size_t buf_len = sizeof(ServiceHeader) + resp->GetSerializedSize();
fbl::unique_ptr<Buffer> buffer = GetBuffer(buf_len);
if (buffer == nullptr) { return ZX_ERR_NO_RESOURCES; }
auto packet = fbl::unique_ptr<Packet>(new Packet(std::move(buffer), buf_len));
packet->set_peer(Packet::Peer::kService);
zx_status_t status = SerializeServiceMsg(packet.get(), Method::DEVICE_QUERY_confirm, resp);
if (status != ZX_OK) {
errorf("could not serialize DeviceQueryResponse: %d\n", status);
return status;
}
return device_->SendService(std::move(packet));
}
zx_status_t Dispatcher::PreChannelChange(wlan_channel_t chan) {
debugfn();
if (mlme_ != nullptr) { mlme_->PreChannelChange(chan); }
return ZX_OK;
}
zx_status_t Dispatcher::PostChannelChange() {
debugfn();
if (mlme_ != nullptr) { mlme_->PostChannelChange(); }
return ZX_OK;
}
} // namespace wlan