diff --git a/garnet/lib/wlan/protocol/include/wlan/protocol/if-impl.h b/garnet/lib/wlan/protocol/include/wlan/protocol/if-impl.h
index edd8014a6c55b51808277b63dbc3bcfefff9a6ae..3f1860df525add476670a970ae3430cfafcdd1b1 100644
--- a/garnet/lib/wlan/protocol/include/wlan/protocol/if-impl.h
+++ b/garnet/lib/wlan/protocol/include/wlan/protocol/if-impl.h
@@ -242,6 +242,38 @@ typedef struct wlanif_eapol_req {
     uint8_t* data;
 } wlanif_eapol_req_t;
 
+// Bits used to request management frame subtypes to be captured. Also used by driver to indicate
+// which management frame subtypes are supported for capture.
+//
+// These values are set at `1 << MgmtFrameSubtypeValue`
+// See IEEE Std 802.11-2016, 9.2.4.1.3, for value of each management frame subtype
+enum {
+    WLAN_MGMT_CAPTURE_FLAG_ASSOC_REQ = 1 << 0,
+    WLAN_MGMT_CAPTURE_FLAG_ASSOC_RESP = 1 << 1,
+    WLAN_MGMT_CAPTURE_FLAG_REASSOC_REQ = 1 << 2,
+    WLAN_MGMT_CAPTURE_FLAG_REASSOC_RESP = 1 << 3,
+    WLAN_MGMT_CAPTURE_FLAG_PROBE_REQ = 1 << 4,
+    WLAN_MGMT_CAPTURE_FLAG_PROBE_RESP = 1 << 5,
+    WLAN_MGMT_CAPTURE_FLAG_TIMING_AD = 1 << 6,
+
+    WLAN_MGMT_CAPTURE_FLAG_BEACON = 1 << 8,
+    WLAN_MGMT_CAPTURE_FLAG_ATIM = 1 << 9,
+    WLAN_MGMT_CAPTURE_FLAG_DISASSOC = 1 << 10,
+    WLAN_MGMT_CAPTURE_FLAG_AUTH = 1 << 11,
+    WLAN_MGMT_CAPTURE_FLAG_DEAUTH = 1 << 12,
+    WLAN_MGMT_CAPTURE_FLAG_ACTION = 1 << 13,
+    WLAN_MGMT_CAPTURE_FLAG_ACTION_NO_ACK = 1 << 14,
+};
+
+typedef struct wlanif_start_capture_frames_req {
+    uint32_t mgmt_frame_flags;
+} wlanif_start_capture_frames_req_t;
+
+typedef struct wlanif_start_capture_frames_resp {
+    int32_t status;
+    uint32_t supported_mgmt_frames;
+} wlanif_start_capture_frames_resp_t;
+
 typedef struct wlanif_scan_result {
     uint64_t txn_id;
     wlanif_bss_description_t bss;
@@ -459,6 +491,11 @@ typedef struct wlanif_stats_query_response {
     wlanif_stats_t stats;
 } wlanif_stats_query_response_t;
 
+typedef struct wlanif_captured_frame_result {
+    size_t data_len;
+    uint8_t* data;
+} wlanif_captured_frame_result_t;
+
 typedef struct wlanif_impl_ifc {
     // MLME operations
     void (*on_scan_result)(void* cookie, wlanif_scan_result_t* result);
@@ -480,6 +517,7 @@ typedef struct wlanif_impl_ifc {
     void (*signal_report)(void* cookie, wlanif_signal_report_indication_t* ind);
     void (*eapol_ind)(void* cookie, wlanif_eapol_indication_t* ind);
     void (*stats_query_resp)(void* cookie, wlanif_stats_query_response_t* resp);
+    void (*relay_captured_frame)(void* cookie, wlanif_captured_frame_result_t* result);
 
     // Data operations
     void (*data_recv)(void* cookie, void* data, size_t length, uint32_t flags);
@@ -512,6 +550,9 @@ typedef struct wlanif_impl_protocol_ops {
 
     // MLME extensions
     void (*stats_query_req)(void* ctx);
+    void (*start_capture_frames)(void* ctx, wlanif_start_capture_frames_req_t* req,
+                                 wlanif_start_capture_frames_resp_t* resp);
+    void (*stop_capture_frames)(void* ctx);
 
     // Data operations
     zx_status_t (*data_queue_tx)(void* ctx, uint32_t options, ethmac_netbuf_t* netbuf);
diff --git a/sdk/fidl/fuchsia.wlan.mlme/wlan_mlme.fidl b/sdk/fidl/fuchsia.wlan.mlme/wlan_mlme.fidl
index 3a081e98cc7ca736a72887cb238af9a9e121c537..5a14e859ab61750cd57c47e8662689b4443a8eeb 100644
--- a/sdk/fidl/fuchsia.wlan.mlme/wlan_mlme.fidl
+++ b/sdk/fidl/fuchsia.wlan.mlme/wlan_mlme.fidl
@@ -674,9 +674,9 @@ struct ResetRequest {
 // MLME-START.request (IEEE Std 802.11-2016, 6.3.11.2)
 
 // See dot11CountryString of IEEE Std 802.11-2016, Annex C
-const uint8 countryEnvironAll = 32;        // an ASCII ' ' character
-const uint8 countryEnvironOutdoor = 79;    // an ASCII 'O' character
-const uint8 countryEnvironIndoor = 73;     // an ASCII 'I' character
+const uint8 countryEnvironAll = 32; // an ASCII ' ' character
+const uint8 countryEnvironOutdoor = 79; // an ASCII 'O' character
+const uint8 countryEnvironIndoor = 73; // an ASCII 'I' character
 const uint8 countryEnvironNonCountry = 88; // an ASCII 'X' character
 
 // Information derived from Country Element, IEEE Std 802.11-2016, 9.4.2.9.
@@ -955,4 +955,8 @@ protocol MLME {
 
     ListMinstrelPeers() -> (MinstrelListResponse resp);
     GetMinstrelStats(MinstrelStatsRequest req) -> (MinstrelStatsResponse resp);
+
+    StartCaptureFrames(StartCaptureFramesRequest req) -> (StartCaptureFramesResponse resp);
+    StopCaptureFrames();
+    -> RelayCapturedFrame(CapturedFrameResult result);
 };
diff --git a/sdk/fidl/fuchsia.wlan.mlme/wlan_mlme_ext.fidl b/sdk/fidl/fuchsia.wlan.mlme/wlan_mlme_ext.fidl
index 43ff5eb8ad74fdaeb3612e6f42e8899d2156cfa4..97920f967aac6eadecba6225620633b0c4e8b9d6 100644
--- a/sdk/fidl/fuchsia.wlan.mlme/wlan_mlme_ext.fidl
+++ b/sdk/fidl/fuchsia.wlan.mlme/wlan_mlme_ext.fidl
@@ -82,5 +82,42 @@ enum ControlledPortState {
     OPEN = 1;
 };
 
+// START_CAPTURE_FRAMES.request
+
+/// Bits used to request management frame subtypes to be captured. Also used in
+/// StartCaptureFramesResponse to indicate what management frames are supported.
+///
+/// These values are set at `1 << MgmtFrameSubtypeValue`
+/// See IEEE Std 802.11-2016, 9.2.4.1.3, for value of each management frame subtype
+bits MgmtFrameCaptureFlags:uint32 {
+    ASSOC_REQ = 0x1;
+    ASSOC_RESP = 0x2;
+    REASSOC_REQ = 0x4;
+    REASSOC_RESP = 0x8;
+    PROBE_REQ = 0x10;
+    PROBE_RESP = 0x20;
+    TIMING_AD = 0x40;
+    BEACON = 0x100;
+    ATIM = 0x200;
+    DISASSOC = 0x400;
+    AUTH = 0x800;
+    DEAUTH = 0x1000;
+    ACTION = 0x2000;
+    ACTION_NO_ACK = 0x4000;
+};
+
+struct StartCaptureFramesRequest {
+    MgmtFrameCaptureFlags mgmt_frame_flags;
+};
+
+struct StartCaptureFramesResponse {
+    int32 status;
+    MgmtFrameCaptureFlags supported_mgmt_frames;
+};
+
+struct CapturedFrameResult {
+    bytes frame;
+};
+
 // Method ordinals are defined in wlan_mlme.fidl to prevent error prone overlaps with official
 // MLME primitives.
diff --git a/src/connectivity/wlan/drivers/third_party/broadcom/brcmfmac/cfg80211.c b/src/connectivity/wlan/drivers/third_party/broadcom/brcmfmac/cfg80211.c
index ce85cc88158918a59461ff4a9a33d25ad6aad54a..6a7499d4c1cf880857b1407439669c91c7659aa9 100644
--- a/src/connectivity/wlan/drivers/third_party/broadcom/brcmfmac/cfg80211.c
+++ b/src/connectivity/wlan/drivers/third_party/broadcom/brcmfmac/cfg80211.c
@@ -3411,6 +3411,17 @@ zx_status_t brcmf_hook_data_queue_tx(void* ctx, uint32_t options, ethmac_netbuf_
     return ZX_OK;
 }
 
+void brcmf_hook_start_capture_frames(void* ctx, wlanif_start_capture_frames_req_t* req,
+                                     wlanif_start_capture_frames_resp_t* resp) {
+    brcmf_err("start_capture_frames not supported\n");
+    resp->status = ZX_ERR_NOT_SUPPORTED;
+    resp->supported_mgmt_frames = 0;
+}
+
+void brcmf_hook_stop_capture_frames(void* ctx) {
+    brcmf_err("stop_capture_frames not supported\n");
+}
+
 static wlanif_impl_protocol_ops_t if_impl_proto_ops = {
     .start = brcmf_if_start,
     .stop = brcmf_if_stop,
@@ -3431,6 +3442,8 @@ static wlanif_impl_protocol_ops_t if_impl_proto_ops = {
     .query = brcmf_hook_query,
     .stats_query_req = brcmf_hook_stats_query_req,
     .data_queue_tx = brcmf_hook_data_queue_tx,
+    .start_capture_frames = brcmf_hook_start_capture_frames,
+    .stop_capture_frames = brcmf_hook_stop_capture_frames,
 };
 
 static void brcmf_release_zx_if_device(void* ctx) {
diff --git a/src/connectivity/wlan/drivers/wlanif/convert.cpp b/src/connectivity/wlan/drivers/wlanif/convert.cpp
index c8693b025d01c2bd8262edc439dc44902d44c7b9..0b06163a8429c53d3aad2689f8d2b017fcff675f 100644
--- a/src/connectivity/wlan/drivers/wlanif/convert.cpp
+++ b/src/connectivity/wlan/drivers/wlanif/convert.cpp
@@ -930,4 +930,99 @@ void ConvertIfaceStats(wlan_stats::IfaceStats* fidl_stats, const wlanif_stats_t&
     }
 }
 
+uint32_t ConvertMgmtCaptureFlags(wlan_mlme::MgmtFrameCaptureFlags fidl_flags) {
+    uint32_t ret_flags = 0;
+    uint32_t flags = static_cast<uint32_t>(fidl_flags);
+    if ((flags & static_cast<uint32_t>(wlan_mlme::MgmtFrameCaptureFlags::ASSOC_REQ)) != 0) {
+        ret_flags |= WLAN_MGMT_CAPTURE_FLAG_ASSOC_REQ;
+    }
+    if ((flags & static_cast<uint32_t>(wlan_mlme::MgmtFrameCaptureFlags::ASSOC_RESP)) != 0) {
+        ret_flags |= WLAN_MGMT_CAPTURE_FLAG_ASSOC_RESP;
+    }
+    if ((flags & static_cast<uint32_t>(wlan_mlme::MgmtFrameCaptureFlags::REASSOC_REQ)) != 0) {
+        ret_flags |= WLAN_MGMT_CAPTURE_FLAG_REASSOC_REQ;
+    }
+    if ((flags & static_cast<uint32_t>(wlan_mlme::MgmtFrameCaptureFlags::REASSOC_RESP)) != 0) {
+        ret_flags |= WLAN_MGMT_CAPTURE_FLAG_REASSOC_RESP;
+    }
+    if ((flags & static_cast<uint32_t>(wlan_mlme::MgmtFrameCaptureFlags::PROBE_REQ)) != 0) {
+        ret_flags |= WLAN_MGMT_CAPTURE_FLAG_PROBE_REQ;
+    }
+    if ((flags & static_cast<uint32_t>(wlan_mlme::MgmtFrameCaptureFlags::PROBE_RESP)) != 0) {
+        ret_flags |= WLAN_MGMT_CAPTURE_FLAG_PROBE_RESP;
+    }
+    if ((flags & static_cast<uint32_t>(wlan_mlme::MgmtFrameCaptureFlags::TIMING_AD)) != 0) {
+        ret_flags |= WLAN_MGMT_CAPTURE_FLAG_TIMING_AD;
+    }
+    if ((flags & static_cast<uint32_t>(wlan_mlme::MgmtFrameCaptureFlags::BEACON)) != 0) {
+        ret_flags |= WLAN_MGMT_CAPTURE_FLAG_BEACON;
+    }
+    if ((flags & static_cast<uint32_t>(wlan_mlme::MgmtFrameCaptureFlags::ATIM)) != 0) {
+        ret_flags |= WLAN_MGMT_CAPTURE_FLAG_ATIM;
+    }
+    if ((flags & static_cast<uint32_t>(wlan_mlme::MgmtFrameCaptureFlags::DISASSOC)) != 0) {
+        ret_flags |= WLAN_MGMT_CAPTURE_FLAG_DISASSOC;
+    }
+    if ((flags & static_cast<uint32_t>(wlan_mlme::MgmtFrameCaptureFlags::AUTH)) != 0) {
+        ret_flags |= WLAN_MGMT_CAPTURE_FLAG_AUTH;
+    }
+    if ((flags & static_cast<uint32_t>(wlan_mlme::MgmtFrameCaptureFlags::DEAUTH)) != 0) {
+        ret_flags |= WLAN_MGMT_CAPTURE_FLAG_DEAUTH;
+    }
+    if ((flags & static_cast<uint32_t>(wlan_mlme::MgmtFrameCaptureFlags::ACTION)) != 0) {
+        ret_flags |= WLAN_MGMT_CAPTURE_FLAG_ACTION;
+    }
+    if ((flags & static_cast<uint32_t>(wlan_mlme::MgmtFrameCaptureFlags::ACTION_NO_ACK)) != 0) {
+        ret_flags |= WLAN_MGMT_CAPTURE_FLAG_ACTION_NO_ACK;
+    }
+    return ret_flags;
+}
+
+wlan_mlme::MgmtFrameCaptureFlags ConvertMgmtCaptureFlags(uint32_t ddk_flags) {
+    uint32_t ret_flags = 0;
+    if ((ddk_flags & WLAN_MGMT_CAPTURE_FLAG_ASSOC_REQ) != 0) {
+        ret_flags |= static_cast<uint32_t>(wlan_mlme::MgmtFrameCaptureFlags::ASSOC_REQ);
+    }
+    if ((ddk_flags & WLAN_MGMT_CAPTURE_FLAG_ASSOC_RESP) != 0) {
+        ret_flags |= static_cast<uint32_t>(wlan_mlme::MgmtFrameCaptureFlags::ASSOC_RESP);
+    }
+    if ((ddk_flags & WLAN_MGMT_CAPTURE_FLAG_REASSOC_REQ) != 0) {
+        ret_flags |= static_cast<uint32_t>(wlan_mlme::MgmtFrameCaptureFlags::REASSOC_REQ);
+    }
+    if ((ddk_flags & WLAN_MGMT_CAPTURE_FLAG_REASSOC_RESP) != 0) {
+        ret_flags |= static_cast<uint32_t>(wlan_mlme::MgmtFrameCaptureFlags::REASSOC_RESP);
+    }
+    if ((ddk_flags & WLAN_MGMT_CAPTURE_FLAG_PROBE_REQ) != 0) {
+        ret_flags |= static_cast<uint32_t>(wlan_mlme::MgmtFrameCaptureFlags::PROBE_REQ);
+    }
+    if ((ddk_flags & WLAN_MGMT_CAPTURE_FLAG_PROBE_RESP) != 0) {
+        ret_flags |= static_cast<uint32_t>(wlan_mlme::MgmtFrameCaptureFlags::PROBE_RESP);
+    }
+    if ((ddk_flags & WLAN_MGMT_CAPTURE_FLAG_TIMING_AD) != 0) {
+        ret_flags |= static_cast<uint32_t>(wlan_mlme::MgmtFrameCaptureFlags::TIMING_AD);
+    }
+    if ((ddk_flags & WLAN_MGMT_CAPTURE_FLAG_BEACON) != 0) {
+        ret_flags |= static_cast<uint32_t>(wlan_mlme::MgmtFrameCaptureFlags::BEACON);
+    }
+    if ((ddk_flags & WLAN_MGMT_CAPTURE_FLAG_ATIM) != 0) {
+        ret_flags |= static_cast<uint32_t>(wlan_mlme::MgmtFrameCaptureFlags::ATIM);
+    }
+    if ((ddk_flags & WLAN_MGMT_CAPTURE_FLAG_DISASSOC) != 0) {
+        ret_flags |= static_cast<uint32_t>(wlan_mlme::MgmtFrameCaptureFlags::DISASSOC);
+    }
+    if ((ddk_flags & WLAN_MGMT_CAPTURE_FLAG_AUTH) != 0) {
+        ret_flags |= static_cast<uint32_t>(wlan_mlme::MgmtFrameCaptureFlags::AUTH);
+    }
+    if ((ddk_flags & WLAN_MGMT_CAPTURE_FLAG_DEAUTH) != 0) {
+        ret_flags |= static_cast<uint32_t>(wlan_mlme::MgmtFrameCaptureFlags::DEAUTH);
+    }
+    if ((ddk_flags & WLAN_MGMT_CAPTURE_FLAG_ACTION) != 0) {
+        ret_flags |= static_cast<uint32_t>(wlan_mlme::MgmtFrameCaptureFlags::ACTION);
+    }
+    if ((ddk_flags & WLAN_MGMT_CAPTURE_FLAG_ACTION_NO_ACK) != 0) {
+        ret_flags |= static_cast<uint32_t>(wlan_mlme::MgmtFrameCaptureFlags::ACTION_NO_ACK);
+    }
+    return static_cast<wlan_mlme::MgmtFrameCaptureFlags>(ret_flags);
+}
+
 }  // namespace wlanif
diff --git a/src/connectivity/wlan/drivers/wlanif/convert.h b/src/connectivity/wlan/drivers/wlanif/convert.h
index 0d7bb07f1440031caea91ab8c7f9b747b316af98..3a2d18cbe6cc522bb21090f75e080f1b986b000a 100644
--- a/src/connectivity/wlan/drivers/wlanif/convert.h
+++ b/src/connectivity/wlan/drivers/wlanif/convert.h
@@ -46,5 +46,7 @@ uint8_t ConvertAssocResultCode(::fuchsia::wlan::mlme::AssociateResultCodes code)
 void ConvertBandCapabilities(::fuchsia::wlan::mlme::BandCapabilities* fidl_band,
                              const wlanif_band_capabilities_t& band);
 void ConvertIfaceStats(::fuchsia::wlan::stats::IfaceStats* fidl_stats, const wlanif_stats_t& stats);
+uint32_t ConvertMgmtCaptureFlags(::fuchsia::wlan::mlme::MgmtFrameCaptureFlags fidl_flags);
+::fuchsia::wlan::mlme::MgmtFrameCaptureFlags ConvertMgmtCaptureFlags(uint32_t ddk_flags);
 
 }  // namespace wlanif
diff --git a/src/connectivity/wlan/drivers/wlanif/device.cpp b/src/connectivity/wlan/drivers/wlanif/device.cpp
index 8eee372e59e7cd8f8d599e6ad06cdcc0ad9ff444..c03cccad9fa932b7be208fddf6c448aa0a194f20 100644
--- a/src/connectivity/wlan/drivers/wlanif/device.cpp
+++ b/src/connectivity/wlan/drivers/wlanif/device.cpp
@@ -87,6 +87,8 @@ static wlanif_impl_ifc_t wlanif_impl_ifc_ops = {
                      { DEV(cookie)->EapolInd(ind); },
     .stats_query_resp = [](void* cookie, wlanif_stats_query_response_t* resp)
                             { DEV(cookie)->StatsQueryResp(resp); },
+    .relay_captured_frame = [](void* cookie, wlanif_captured_frame_result_t* result)
+                                { DEV(cookie)->RelayCapturedFrame(result); },
 
     // Ethernet operations
     .data_recv = [](void* cookie, void* data, size_t length, uint32_t flags)
@@ -652,6 +654,26 @@ void Device::SetControlledPort(wlan_mlme::SetControlledPortRequest req) {
     }
 }
 
+void Device::StartCaptureFrames(::fuchsia::wlan::mlme::StartCaptureFramesRequest req,
+                                StartCaptureFramesCallback cb) {
+    wlanif_start_capture_frames_req_t impl_req = {};
+    impl_req.mgmt_frame_flags = ConvertMgmtCaptureFlags(req.mgmt_frame_flags);
+
+    wlanif_start_capture_frames_resp_t impl_resp = {};
+
+    // forward request to driver
+    wlanif_impl_.ops->start_capture_frames(wlanif_impl_.ctx, &impl_req, &impl_resp);
+
+    wlan_mlme::StartCaptureFramesResponse resp;
+    resp.status = impl_resp.status;
+    resp.supported_mgmt_frames = ConvertMgmtCaptureFlags(impl_resp.supported_mgmt_frames);
+    cb(resp);
+}
+
+void Device::StopCaptureFrames() {
+    wlanif_impl_.ops->stop_capture_frames(wlanif_impl_.ctx);
+}
+
 void Device::OnScanResult(wlanif_scan_result_t* result) {
     std::lock_guard<std::mutex> lock(lock_);
     if (!binding_.is_bound()) {
@@ -958,6 +980,19 @@ void Device::StatsQueryResp(wlanif_stats_query_response_t* resp) {
     binding_.events().StatsQueryResp(std::move(fidl_resp));
 }
 
+void Device::RelayCapturedFrame(wlanif_captured_frame_result* result) {
+    std::lock_guard<std::mutex> lock(lock_);
+    if (!binding_.is_bound()) {
+        return;
+    }
+
+    wlan_mlme::CapturedFrameResult fidl_result;
+    fidl_result.frame.resize(result->data_len);
+    fidl_result.frame.assign(result->data, result->data + result->data_len);
+
+    binding_.events().RelayCapturedFrame(std::move(fidl_result));
+}
+
 zx_status_t Device::EthStart(const ethmac_ifc_protocol_t* ifc) {
     std::lock_guard<std::mutex> lock(lock_);
     ethmac_ifc_ = *ifc;
diff --git a/src/connectivity/wlan/drivers/wlanif/device.h b/src/connectivity/wlan/drivers/wlanif/device.h
index ccfe259667a92b24f14851cfcca15ab138c70cfd..8007dc595fe76f777436d12be926085532d371fc 100644
--- a/src/connectivity/wlan/drivers/wlanif/device.h
+++ b/src/connectivity/wlan/drivers/wlanif/device.h
@@ -59,6 +59,9 @@ class Device : public ::fuchsia::wlan::mlme::MLME {
     void MeshPeeringEstablished(::fuchsia::wlan::mlme::MeshPeeringParams params) override;
     void GetMeshPathTableReq(::fuchsia::wlan::mlme::GetMeshPathTableRequest req,
                              GetMeshPathTableReqCallback cb) override;
+    void StartCaptureFrames(::fuchsia::wlan::mlme::StartCaptureFramesRequest req,
+                            StartCaptureFramesCallback cb) override;
+    void StopCaptureFrames() override;
 
     // wlanif_impl_ifc (wlanif-impl -> ::fuchsia::wlan::mlme)
     void OnScanResult(wlanif_scan_result_t* result);
@@ -78,6 +81,7 @@ class Device : public ::fuchsia::wlan::mlme::MLME {
     void SignalReport(wlanif_signal_report_indication_t* ind);
     void EapolInd(wlanif_eapol_indication_t* ind);
     void StatsQueryResp(wlanif_stats_query_response_t* resp);
+    void RelayCapturedFrame(wlanif_captured_frame_result* result);
 
     // wlanif_protocol_t (ethmac_protocol -> wlanif_impl_protocol)
     zx_status_t EthStart(const ethmac_ifc_protocol_t* ifc);