Index | Thread | Search

From:
Stefan Sperling <stsp@stsp.name>
Subject:
wifi protected management frame (PMF) support
To:
tech@openbsd.org
Date:
Sat, 22 Nov 2025 22:45:08 +0100

Download raw body.

Thread
This patch adds protected management frame support to iwm, iwx, and qwx.
Support for PMF is a prerequisite for WPA3.

I am sending this as one giant patch for testing. I do have incremental
changes with individual commit messages which make review a bit easier.
If you would like to review these diffs individually, please ask me to
send them to you.

Tested by me on:
iwm 7265, 9265	(offloads unicast PMF, multicast is done in software)
iwx AX200	(offloads both unicast and multicast PMF)
qwx QCNFA765	(offloads unicast PMF, multicast is done in software)

Use of PMF is controlled by the access point, so there is nothing to
configure with ifconfig. Please check if your access point offers settings
related to management frame protection related when testing this.
Tests in any combination of PMF disabled/optional/required across a range
of access points would be welcome.

In particular, I don't have any iwx "MA" devices to test with. There
could still be unexpected problems such as firmware crashes on these.
If you enable 'ifconfig iwx0 debug' then the driver should display the
name of its firmware file in dmesg. If this name begins with "iwx-ma-"
then you are using an MA device.

This patch also contains a drive-by fix for iwx MA devices: The driver
never removed crypto keys from firmware on these devices. I do not know
whether this bug actually caused issues in practice but it is worth fixing.

This effort is supported by the NLnet Foundation's NGI0 Commons Fund, and
relies on PMF support code committed to OpenBSD by Damien Bergamini in 2009.

I am also grateful for clues from public discussion among OpenIntelWireless
(hackintosh) developers, who wrote down their experiences with PMF here:
https://github.com/OpenIntelWireless/itlwm/pull/676

Throughout the code, "PMF" (protected management frames) and "MFP" (management
frame protection) are used interchangably. I don't intend to fix this.
Damien chose "MFP" in most places and changing this would cause needless churn.

M  share/man/man4/iwm.4                |    6+   0-
M  share/man/man4/iwx.4                |    6+   0-
M  share/man/man4/qwx.4                |   10+   0-
M  sys/dev/ic/qwx.c                    |  107+  21-
M  sys/dev/ic/qwxvar.h                 |    2+   0-
M  sys/dev/pci/if_iwm.c                |   54+   1-
M  sys/dev/pci/if_iwmvar.h             |    2+   0-
M  sys/dev/pci/if_iwx.c                |  167+  12-
M  sys/dev/pci/if_iwxreg.h             |   22+   4-
M  sys/dev/pci/if_iwxvar.h             |    9+   5-
M  sys/dev/pci/if_qwx_pci.c            |    2+   1-
M  sys/net80211/ieee80211_crypto.c     |   11+   5-
M  sys/net80211/ieee80211_input.c      |   22+   9-
M  sys/net80211/ieee80211_ioctl.c      |    1+   0-
M  sys/net80211/ieee80211_node.c       |    5+   0-
M  sys/net80211/ieee80211_output.c     |    1+   1-
M  sys/net80211/ieee80211_pae_input.c  |    9+   0-
M  sys/net80211/ieee80211_proto.c      |    2+   0-

18 files changed, 438 insertions(+), 59 deletions(-)

commit - a7f244ac5cee08f458e351af876c95d354c6d6e4
commit + 881c285030b176970507181531f1c9628160a001
blob - 51e6f730062c7a11127cb96dd267a04b7761fd2b
blob + 9300624d160ff42704c9b5cafa07e9572bb9e075
--- share/man/man4/iwm.4
+++ share/man/man4/iwm.4
@@ -73,6 +73,12 @@ The
 driver offloads both encryption and decryption of unicast data frames to the
 hardware for the CCMP cipher.
 .Pp
+The
+.Nm
+driver supports protected management frames (PMF) which prevents spoofing
+of management frames such as deauthentication, disassociation, and block-ack
+negotiation, on networks using WPA.
+.Pp
 In BSS mode the driver supports background scanning;
 see
 .Xr ifconfig 8 .
blob - dcfbdc9b9bf58238c8540791a3544cb55b7cc9c8
blob + 5db7fbde1ed3c16e071ec633614bfcfdb7b89806
--- share/man/man4/iwx.4
+++ share/man/man4/iwx.4
@@ -70,6 +70,12 @@ The
 driver offloads both encryption and decryption of unicast data frames to the
 hardware for the CCMP cipher.
 .Pp
+The
+.Nm
+driver supports protected management frames (PMF) which prevents spoofing
+of management frames such as deauthentication, disassociation, and block-ack
+negotiation, on networks using WPA.
+.Pp
 In BSS mode the driver supports background scanning;
 see
 .Xr ifconfig 8 .
blob - 834dc4be5246ecef57c3e4a34d0a3b3f4a0c6ce4
blob + de9a36265725c5c069c60782ecad548207218839
--- share/man/man4/qwx.4
+++ share/man/man4/qwx.4
@@ -66,6 +66,16 @@ hardware for the CCMP cipher.
 .Pp
 The
 .Nm
+driver supports protected management frames (PMF) which prevents spoofing
+of management frames such as deauthentication, disassociation, and block-ack
+negotiation, on networks using WPA.
+.Pp
+In BSS mode the driver supports background scanning;
+see
+.Xr ifconfig 8 .
+.Pp
+The
+.Nm
 driver can be configured at runtime with
 .Xr ifconfig 8
 or on boot with
blob - ad3588d5c0f88b8de4171f7617551bfc058e6a36
blob + 54763a0a479ab49c63d27dbd2de0102b232156b3
--- sys/dev/ic/qwx.c
+++ sys/dev/ic/qwx.c
@@ -350,6 +350,49 @@ qwx_del_task(struct qwx_softc *sc, struct taskq *taskq
 }
 
 void
+qwx_mfp_leave_done(struct ieee80211com *ic, struct ieee80211_node *ni)
+{
+	struct qwx_softc *sc = ic->ic_softc;
+	struct ifnet *ifp = &ic->ic_if;
+
+	if ((ifp->if_flags & IFF_RUNNING) &&
+	    ic->ic_state == IEEE80211_S_RUN &&
+	    (ni->ni_flags & IEEE80211_NODE_MFP)) {
+		sc->deauth_sent = 1;
+		wakeup(&sc->deauth_sent);
+	}
+}
+
+void
+qwx_mfp_leave(struct qwx_softc *sc)
+{
+	struct ieee80211com *ic = &sc->sc_ic;
+	struct ieee80211_node *ni = (void *)ic->ic_bss;
+
+	ic->ic_xflags |= IEEE80211_F_TX_MGMT_ONLY;
+	sc->deauth_sent = 0;
+
+	ni->ni_unref_cb = qwx_mfp_leave_done;
+	ni->ni_unref_arg = NULL;
+	ni->ni_unref_arg_size = 0;
+
+	/*
+	 * Send an authenticated deauth frame in order to let our AP know we
+	 * are leaving. This allows our AP to tear down MFP state cleanly.
+	 * Otherwise we would remain locked out of this AP until a timeout
+	 * of stale MFP state occurs at the AP, which might take a while.
+	 */
+	if (IEEE80211_SEND_MGMT(ic, ni, IEEE80211_FC0_SUBTYPE_DEAUTH,
+	    IEEE80211_REASON_AUTH_LEAVE) != 0) {
+		ni->ni_unref_cb = NULL;
+		return;
+	}
+
+	if (tsleep_nsec(&sc->deauth_sent, 0, "qwxlv", MSEC_TO_NSEC(500)) != 0)
+		ni->ni_unref_cb = NULL;
+}
+
+void
 qwx_stop(struct ifnet *ifp)
 {
 	struct qwx_softc *sc = ifp->if_softc;
@@ -373,6 +416,13 @@ qwx_stop(struct ifnet *ifp)
 	qwx_del_task(sc, systq, &sc->bgscan_task);
 	refcnt_finalize(&sc->task_refs, "qwxstop");
 
+	clear_bit(ATH11K_FLAG_CRASH_FLUSH, sc->sc_flags);
+
+	if (ic->ic_opmode == IEEE80211_M_STA &&
+	    ic->ic_state == IEEE80211_S_RUN &&
+	    (ic->ic_bss->ni_flags & IEEE80211_NODE_MFP))
+		qwx_mfp_leave(sc);
+
 	qwx_setkey_clear(sc);
 
 	ifp->if_timer = sc->sc_tx_timer = 0;
@@ -380,8 +430,6 @@ qwx_stop(struct ifnet *ifp)
 	ifp->if_flags &= ~IFF_RUNNING;
 	ifq_clr_oactive(&ifp->if_snd);
 
-	clear_bit(ATH11K_FLAG_CRASH_FLUSH, sc->sc_flags);
-
 	/*
 	 * Manually run the newstate task's code for switching to INIT state.
 	 * This reconfigures firmware state to stop scanning, or disassociate
@@ -661,7 +709,8 @@ qwx_set_key(struct ieee80211com *ic, struct ieee80211_
 
 	if (test_bit(ATH11K_FLAG_HW_CRYPTO_DISABLED, sc->sc_flags) ||
 	    k->k_cipher == IEEE80211_CIPHER_WEP40 ||
-	    k->k_cipher == IEEE80211_CIPHER_WEP104)
+	    k->k_cipher == IEEE80211_CIPHER_WEP104 ||
+	    k->k_cipher == IEEE80211_CIPHER_BIP)
 		return ieee80211_set_key(ic, ni, k);
 
 	return qwx_queue_setkey_cmd(ic, ni, k, QWX_ADD_KEY);
@@ -675,7 +724,8 @@ qwx_delete_key(struct ieee80211com *ic, struct ieee802
 
 	if (test_bit(ATH11K_FLAG_HW_CRYPTO_DISABLED, sc->sc_flags) ||
 	    k->k_cipher == IEEE80211_CIPHER_WEP40 ||
-	    k->k_cipher == IEEE80211_CIPHER_WEP104) {
+	    k->k_cipher == IEEE80211_CIPHER_WEP104 ||
+	    k->k_cipher == IEEE80211_CIPHER_BIP) {
 		ieee80211_delete_key(ic, ni, k);
 		return;
 	}
@@ -13513,23 +13563,25 @@ qwx_mgmt_rx_event(struct qwx_softc *sc, struct mbuf *m
 
 	wh = mtod(m, struct ieee80211_frame *);
 	ni = ieee80211_find_rxnode(ic, wh);
-#if 0
+
 	/* In case of PMF, FW delivers decrypted frames with Protected Bit set.
 	 * Don't clear that. Also, FW delivers broadcast management frames
 	 * (ex: group privacy action frames in mesh) as encrypted payload.
 	 */
-	if (ieee80211_has_protected(hdr->frame_control) &&
-	    !is_multicast_ether_addr(ieee80211_get_DA(hdr))) {
-		status->flag |= RX_FLAG_DECRYPTED;
-
+	if ((wh->i_fc[1] & IEEE80211_FC1_PROTECTED) &&
+	    !IEEE80211_IS_MULTICAST(wh->i_addr1)) {
+		rxi.rxi_flags |= IEEE80211_RXI_HWDEC;
+#if 0
 		if (!ieee80211_is_robust_mgmt_frame(skb)) {
 			status->flag |= RX_FLAG_IV_STRIPPED |
 					RX_FLAG_MMIC_STRIPPED;
 			hdr->frame_control = __cpu_to_le16(fc &
 					     ~IEEE80211_FCTL_PROTECTED);
 		}
+#endif
 	}
 
+#if 0
 	if (ieee80211_is_beacon(hdr->frame_control))
 		ath11k_mac_handle_beacon(ar, skb);
 #endif
@@ -25122,6 +25174,18 @@ qwx_dp_tx(struct qwx_softc *sc, struct qwx_vif *arvif,
 	    FIELD_PREP(DP_TX_DESC_ID_POOL_ID, pool_id);
 	ti.encap_type = qwx_dp_tx_get_encap_type(sc);
 
+	if (ti.encap_type != HAL_TCL_ENCAP_TYPE_RAW &&
+	    IEEE80211_IS_MULTICAST(wh->i_addr1) &&
+	    (wh->i_fc[1] & IEEE80211_FC1_PROTECTED) &&
+	    (ni->ni_flags & IEEE80211_NODE_MFP) &&
+	    k->k_cipher == IEEE80211_CIPHER_BIP) {
+		/* BIP is done in software crypto. */
+		if ((m = ieee80211_encrypt(ic, m, k)) == NULL)
+			return ENOBUFS;
+		/* 802.11 header may have moved. */
+		wh = mtod(m, struct ieee80211_frame *);
+	}
+
 	ti.meta_data_flags = arvif->tcl_metadata;
 
 	if ((wh->i_fc[1] & IEEE80211_FC1_PROTECTED) &&
@@ -25147,6 +25211,7 @@ qwx_dp_tx(struct qwx_softc *sc, struct qwx_vif *arvif,
 					return ENOSPC;
 				}
 				break;
+			case IEEE80211_CIPHER_BIP:
 			default:
 				ti.encrypt_type = HAL_ENCRYPT_TYPE_OPEN;
 				break;
@@ -25342,32 +25407,53 @@ qwx_mac_mgmt_tx_wmi(struct qwx_softc *sc, struct qwx_v
 {
 	struct qwx_txmgmt_queue *txmgmt = &arvif->txmgmt;
 	struct qwx_tx_data *tx_data;
+	struct ieee80211_frame *wh;
 	int buf_id;
 	int ret;
+	uint8_t subtype;
 
 	buf_id = txmgmt->cur;
 
 	DNPRINTF(QWX_D_MAC, "%s: tx mgmt frame, buf id %d\n", __func__, buf_id);
 
-	if (txmgmt->queued >= nitems(txmgmt->data))
+	if (txmgmt->queued >= nitems(txmgmt->data)) {
+		m_freem(m);
 		return ENOSPC;
+	}
 
 	tx_data = &txmgmt->data[buf_id];
-#if 0
-	if (!(info->flags & IEEE80211_TX_CTL_HW_80211_ENCAP)) {
-		if ((ieee80211_is_action(hdr->frame_control) ||
-		     ieee80211_is_deauth(hdr->frame_control) ||
-		     ieee80211_is_disassoc(hdr->frame_control)) &&
-		     ieee80211_has_protected(hdr->frame_control)) {
-			skb_put(skb, IEEE80211_CCMP_MIC_LEN);
+
+	wh = mtod(m, struct ieee80211_frame *);
+	subtype = wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK;
+
+	if ((ni->ni_flags & IEEE80211_NODE_MFP) &&
+	    (wh->i_fc[1] & IEEE80211_FC1_PROTECTED) &&
+	    (subtype == IEEE80211_FC0_SUBTYPE_DISASSOC ||
+	     subtype == IEEE80211_FC0_SUBTYPE_DEAUTH ||
+	     subtype == IEEE80211_FC0_SUBTYPE_ACTION)) {
+		int off;
+
+		/* Make space for CCMP header. */
+		if (m_makespace(m, ieee80211_get_hdrlen(wh),
+		    IEEE80211_CCMP_HDRLEN, &off) == NULL) {
+			m_freem(m);
+			return ENOMEM;
 		}
+
+		/* Add trailing space for CCMP MIC. */
+		if (m_makespace(m, m->m_pkthdr.len,
+		    IEEE80211_CCMP_MICLEN, &off) == NULL) {
+			m_freem(m);
+			return ENOMEM;
+		}
 	}
-#endif
+
 	ret = bus_dmamap_load_mbuf(sc->sc_dmat, tx_data->map,
 	    m, BUS_DMA_WRITE | BUS_DMA_NOWAIT);
 	if (ret && ret != EFBIG) {
 		printf("%s: failed to map mgmt Tx buffer: %d\n",
 		    sc->sc_dev.dv_xname, ret);
+		m_freem(m);
 		return ret;
 	}
 	if (ret) {
@@ -25390,6 +25476,7 @@ qwx_mac_mgmt_tx_wmi(struct qwx_softc *sc, struct qwx_v
 	if (ret) {
 		printf("%s: failed to send mgmt frame: %d\n",
 		    sc->sc_dev.dv_xname, ret);
+		m_freem(m);
 		goto err_unmap_buf;
 	}
 	tx_data->ni = ni;
@@ -26110,12 +26197,11 @@ qwx_peer_assoc_h_crypto(struct qwx_softc *sc, struct q
 		if (ni->ni_rsnprotos == IEEE80211_PROTO_WPA)
 			arg->need_gtk_2_way = 1;
 	}
-#if 0
-	if (sta->mfp) {
+
+	if (ni->ni_flags & IEEE80211_NODE_MFP) {
 		/* TODO: Need to check if FW supports PMF? */
 		arg->is_pmf_enabled = true;
 	}
-#endif
 }
 
 int
blob - 19e9c240e0b896774314a28f9449f0eb164b9338
blob + 0cb96e4436da925ea03241c598ba60f21d90e472
--- sys/dev/ic/qwxvar.h
+++ sys/dev/ic/qwxvar.h
@@ -1835,6 +1835,8 @@ struct qwx_softc {
 	enum ieee80211_state	ns_nstate;
 	int			ns_arg;
 
+	int			deauth_sent;
+
 	/* Task for setting encryption keys and its arguments. */
 	struct task		setkey_task;
 	/*
blob - 749a4c86102ca448faff79649f664196437bb043
blob + c71bb809b6c58cb19f0b5ca2daf98799e62bd983
--- sys/dev/pci/if_iwm.c
+++ sys/dev/pci/if_iwm.c
@@ -9124,6 +9124,8 @@ iwm_set_key_v1(struct ieee80211com *ic, struct ieee802
 	    IWM_STA_KEY_FLG_KEYID_MSK));
 	if (k->k_flags & IEEE80211_KEY_GROUP)
 		cmd.common.key_flags |= htole16(IWM_STA_KEY_MULTICAST);
+	if (ni->ni_flags & IEEE80211_NODE_MFP)
+		cmd.common.key_flags |= htole16(IWM_STA_KEY_MFP);
 
 	memcpy(cmd.common.key, k->k_key, MIN(sizeof(cmd.common.key), k->k_len));
 	cmd.common.key_offset = 0;
@@ -9157,6 +9159,8 @@ iwm_set_key(struct ieee80211com *ic, struct ieee80211_
 	    IWM_STA_KEY_FLG_KEYID_MSK));
 	if (k->k_flags & IEEE80211_KEY_GROUP)
 		cmd.common.key_flags |= htole16(IWM_STA_KEY_MULTICAST);
+	if (ni->ni_flags & IEEE80211_NODE_MFP)
+		cmd.common.key_flags |= htole16(IWM_STA_KEY_MFP);
 
 	memcpy(cmd.common.key, k->k_key, MIN(sizeof(cmd.common.key), k->k_len));
 	cmd.common.key_offset = 0;
@@ -10499,6 +10503,49 @@ iwm_start(struct ifnet *ifp)
 }
 
 void
+iwm_mfp_leave_done(struct ieee80211com *ic, struct ieee80211_node *ni)
+{
+	struct iwm_softc *sc = ic->ic_softc;
+	struct ifnet *ifp = IC2IFP(&sc->sc_ic);
+
+	if ((ifp->if_flags & IFF_RUNNING) &&
+	    ic->ic_state == IEEE80211_S_RUN &&
+	    (ni->ni_flags & IEEE80211_NODE_MFP)) {
+		sc->deauth_sent = 1;
+		wakeup(&sc->deauth_sent);
+	}
+}
+
+void
+iwm_mfp_leave(struct iwm_softc *sc)
+{
+	struct ieee80211com *ic = &sc->sc_ic;
+	struct ieee80211_node *ni = (void *)ic->ic_bss;
+
+	ic->ic_xflags |= IEEE80211_F_TX_MGMT_ONLY;
+	sc->deauth_sent = 0;
+
+	ni->ni_unref_cb = iwm_mfp_leave_done;
+	ni->ni_unref_arg = NULL;
+	ni->ni_unref_arg_size = 0;
+
+	/*
+	 * Send an authenticated deauth frame in order to let our AP know we
+	 * are leaving. This allows our AP to tear down MFP state cleanly.
+	 * Otherwise we would remain locked out of this AP until a timeout
+	 * of stale MFP state occurs at the AP, which might take a while.
+	 */
+	if (IEEE80211_SEND_MGMT(ic, ni, IEEE80211_FC0_SUBTYPE_DEAUTH,
+	    IEEE80211_REASON_AUTH_LEAVE) != 0) {
+		ni->ni_unref_cb = NULL;
+		return;
+	}
+
+	if (tsleep_nsec(&sc->deauth_sent, 0, "iwmlv", MSEC_TO_NSEC(500)) != 0)
+		ni->ni_unref_cb = NULL;
+}
+
+void
 iwm_stop(struct ifnet *ifp)
 {
 	struct iwm_softc *sc = ifp->if_softc;
@@ -10520,6 +10567,11 @@ iwm_stop(struct ifnet *ifp)
 	KASSERT(sc->task_refs.r_refs >= 1);
 	refcnt_finalize(&sc->task_refs, "iwmstop");
 
+	if (ic->ic_opmode == IEEE80211_M_STA &&
+	    ic->ic_state == IEEE80211_S_RUN &&
+	    (ic->ic_bss->ni_flags & IEEE80211_NODE_MFP))
+		iwm_mfp_leave(sc);
+
 	iwm_stop_device(sc);
 
 	free(sc->bgscan_unref_arg, M_DEVBUF, sc->bgscan_unref_arg_size);
@@ -11943,7 +11995,8 @@ iwm_attach(struct device *parent, struct device *self,
 	    IEEE80211_C_SCANALLBAND |	/* device scans all bands at once */
 	    IEEE80211_C_MONITOR |	/* monitor mode supported */
 	    IEEE80211_C_SHSLOT |	/* short slot time supported */
-	    IEEE80211_C_SHPREAMBLE;	/* short preamble supported */
+	    IEEE80211_C_SHPREAMBLE |	/* short preamble supported */
+	    IEEE80211_C_MFP;		/* management frame protection */
 
 	ic->ic_htcaps = IEEE80211_HTCAP_SGI20 | IEEE80211_HTCAP_SGI40;
 	ic->ic_htcaps |= IEEE80211_HTCAP_CBW20_40;
blob - 113a547137cac0aa956e8069f94dcc76918950f7
blob + d951e4a81be77de01fbd0f13161c3fa63e893e9c
--- sys/dev/pci/if_iwmvar.h
+++ sys/dev/pci/if_iwmvar.h
@@ -487,6 +487,8 @@ struct iwm_softc {
 	enum ieee80211_state	ns_nstate;
 	int			ns_arg;
 
+	int			deauth_sent;
+
 	/* Task for firmware BlockAck setup/teardown and its arguments. */
 	struct task		ba_task;
 	struct iwm_ba_task_data	ba_rx;
blob - 8d48397c0ec35388af23de2c26b2087dffc650e2
blob + 218cdbee9ea115d62962eb18b6efce158e501ebc
--- sys/dev/pci/if_iwx.c
+++ sys/dev/pci/if_iwx.c
@@ -6315,7 +6315,8 @@ iwx_tx(struct iwx_softc *sc, struct mbuf *m, struct ie
 
 	if (wh->i_fc[1] & IEEE80211_FC1_PROTECTED) {
                 k = ieee80211_get_txkey(ic, wh, ni);
-		if (k->k_cipher != IEEE80211_CIPHER_CCMP) {
+		if (k->k_cipher != IEEE80211_CIPHER_CCMP &&
+		    (k->k_flags & IEEE80211_KEY_IGTK) == 0) {
 			if ((m = ieee80211_encrypt(ic, m, k)) == NULL)
 				return ENOBUFS;
 			/* 802.11 header may have moved. */
@@ -6985,6 +6986,9 @@ iwx_mld_add_sta_cmd(struct iwx_softc *sc, struct iwx_n
 		sta_cmd.tx_ampdu_max_size = htole32(aggsize);
 	}
 
+	if (in->in_ni.ni_flags & IEEE80211_NODE_MFP)
+		sta_cmd.mfp = htole32(1);
+
 	return iwx_send_cmd_pdu(sc,
 	    IWX_WIDE_ID(IWX_MAC_CONF_GROUP, IWX_STA_CONFIG_CMD),
 	    0, sizeof(sta_cmd), &sta_cmd);
@@ -8765,7 +8769,8 @@ iwx_set_key(struct ieee80211com *ic, struct ieee80211_
 	struct iwx_setkey_task_arg *a;
 	int err;
 
-	if (k->k_cipher != IEEE80211_CIPHER_CCMP) {
+	if (k->k_cipher != IEEE80211_CIPHER_CCMP &&
+	    (k->k_flags & IEEE80211_KEY_IGTK) == 0) {
 		/* Fallback to software crypto for other ciphers. */
 		err = ieee80211_set_key(ic, ni, k);
 		if (!err && in != NULL && (k->k_flags & IEEE80211_KEY_GROUP))
@@ -8787,8 +8792,9 @@ iwx_set_key(struct ieee80211com *ic, struct ieee80211_
 }
 
 int
-iwx_mld_add_sta_key_cmd(struct iwx_softc *sc, int sta_id,
-    struct ieee80211_node *ni, struct ieee80211_key *k)
+iwx_mld_set_sta_key_cmd(struct iwx_softc *sc, int sta_id,
+    struct ieee80211_node *ni, struct ieee80211_key *k,
+    int remove_key)
 {
 	struct ieee80211com *ic = &sc->sc_ic;
 	struct iwx_sec_key_cmd cmd;
@@ -8797,6 +8803,10 @@ iwx_mld_add_sta_key_cmd(struct iwx_softc *sc, int sta_
 
 	if (k->k_flags & IEEE80211_KEY_GROUP)
 		flags |= IWX_SEC_KEY_FLAG_MCAST_KEY;
+	else if (k->k_flags & IEEE80211_KEY_IGTK)
+		flags |= IWX_SEC_KEY_FLAG_MCAST_KEY | IWX_SEC_KEY_FLAG_MFP;
+	else if (ni->ni_flags & IEEE80211_NODE_MFP)
+		flags |= IWX_SEC_KEY_FLAG_MFP;
 
 	memset(&cmd, 0, sizeof(cmd));
 	cmd.u.add.sta_mask = htole32(1 << sta_id);
@@ -8804,12 +8814,18 @@ iwx_mld_add_sta_key_cmd(struct iwx_softc *sc, int sta_
 	cmd.u.add.key_flags = htole32(flags);
 	cmd.u.add.tx_seq = htole64(k->k_tsc);
 	memcpy(cmd.u.add.key, k->k_key, k->k_len);
-	cmd.action = IWX_FW_CTXT_ACTION_ADD;
+	if (remove_key)
+		cmd.action = IWX_FW_CTXT_ACTION_REMOVE;
+	else
+		cmd.action = IWX_FW_CTXT_ACTION_ADD;
 
 	err = iwx_send_cmd_pdu(sc,
 	    IWX_WIDE_ID(IWX_DATA_PATH_GROUP, IWX_SEC_KEY_CMD),
-	    0, sizeof(cmd), &cmd);
+	    remove_key ? IWX_CMD_ASYNC : 0, sizeof(cmd), &cmd);
 	if (err) {
+		if (remove_key)
+			return err;
+
 		IEEE80211_SEND_MGMT(ic, ni, IEEE80211_FC0_SUBTYPE_DEAUTH,
 		    IEEE80211_REASON_AUTH_LEAVE);
 		ieee80211_new_state(ic, IEEE80211_S_SCAN, -1);
@@ -8837,8 +8853,11 @@ iwx_add_sta_key_cmd(struct iwx_softc *sc, int sta_id,
 	if (k->k_flags & IEEE80211_KEY_GROUP) {
 		cmd.common.key_offset = 1;
 		cmd.common.key_flags |= htole16(IWX_STA_KEY_MULTICAST);
-	} else
+	} else {
 		cmd.common.key_offset = 0;
+		if (ni->ni_flags & IEEE80211_NODE_MFP)
+			cmd.common.key_flags |= htole16(IWX_STA_KEY_MFP);
+	}
 
 	memcpy(cmd.common.key, k->k_key, MIN(sizeof(cmd.common.key), k->k_len));
 	cmd.common.sta_id = sta_id;
@@ -8863,16 +8882,81 @@ iwx_add_sta_key_cmd(struct iwx_softc *sc, int sta_id,
 }
 
 int
+iwx_set_sta_igtk(struct iwx_softc *sc, int sta_id, struct ieee80211_node *ni,
+    struct ieee80211_key *k, int remove_key)
+{
+	struct iwx_mgmt_mcast_key_cmd igtk_cmd = {};
+
+	/* Verify key details match the required command's expectations. */
+	if (k == &ni->ni_pairwise_key  ||
+	    (k->k_id != 4 && k->k_id != 5 &&
+	    k->k_id != 6 && k->k_id != 7) ||
+	    /* TODO: Other ciphers for WPA3? */
+	    k->k_cipher != IEEE80211_CIPHER_BIP)
+		return EINVAL;
+
+	if (!isset(sc->sc_enabled_capa,
+	    IWX_UCODE_TLV_CAPA_MULTI_QUEUE_RX_SUPPORT) &&
+	    k->k_cipher != IEEE80211_CIPHER_BIP)
+		return EINVAL;
+
+	igtk_cmd.key_id = htole32(k->k_id);
+	igtk_cmd.sta_id = htole32(sta_id);
+
+	if (remove_key) {
+		igtk_cmd.ctrl_flags |= htole32(IWX_STA_KEY_NOT_VALID);
+	} else {
+		switch (k->k_cipher) {
+		case IEEE80211_CIPHER_BIP:
+			igtk_cmd.ctrl_flags |= htole32(IWX_STA_KEY_FLG_CCM);
+			break;
+		/* TODO: Other ciphers for WPA3? */
+		default:
+			return EINVAL;
+		}
+
+		memcpy(igtk_cmd.igtk, k->k_key, k->k_len);
+		igtk_cmd.receive_seq_cnt = htole64(k->k_mgmt_rsc);
+	}
+
+	DPRINTF(("%s %sIGTK (%d) for sta %u\n",
+	    remove_key ? "removing" : "installing",
+	    k->k_id >= 6 ? "B" : "", k->k_id, sta_id));
+
+	if (!isset(sc->sc_enabled_capa,
+	    IWX_UCODE_TLV_CAPA_MULTI_QUEUE_RX_SUPPORT)) {
+		struct iwx_mgmt_mcast_key_cmd_v1 igtk_cmd_v1 = {
+			.ctrl_flags = igtk_cmd.ctrl_flags,
+			.key_id = igtk_cmd.key_id,
+			.sta_id = igtk_cmd.sta_id,
+			.receive_seq_cnt = igtk_cmd.receive_seq_cnt
+		};
+
+		memcpy(igtk_cmd_v1.igtk, igtk_cmd.igtk,
+		       sizeof(igtk_cmd_v1.igtk));
+		return iwx_send_cmd_pdu(sc, IWX_MGMT_MCAST_KEY,
+		    remove_key ? IWX_CMD_ASYNC : 0,
+		    sizeof(igtk_cmd_v1), &igtk_cmd_v1);
+	}
+
+	return iwx_send_cmd_pdu(sc, IWX_MGMT_MCAST_KEY,
+	    remove_key ? IWX_CMD_ASYNC : 0, sizeof(igtk_cmd), &igtk_cmd);
+}
+
+int
 iwx_add_sta_key(struct iwx_softc *sc, int sta_id, struct ieee80211_node *ni,
     struct ieee80211_key *k)
 {
 	struct ieee80211com *ic = &sc->sc_ic;
 	struct iwx_node *in = (void *)ni;
-	const int want_keymask = (IWX_NODE_FLAG_HAVE_PAIRWISE_KEY |
+	int want_keymask = (IWX_NODE_FLAG_HAVE_PAIRWISE_KEY |
 	    IWX_NODE_FLAG_HAVE_GROUP_KEY);
 	uint8_t sec_key_ver;
 	int err;
 
+	if (ni->ni_flags & IEEE80211_NODE_MFP)
+		want_keymask |= IWX_NODE_FLAG_HAVE_INTEGRITY_GROUP_KEY;
+
 	/*
 	 * Keys are stored in 'ni' so 'k' is valid if 'ni' is valid.
 	 * Currently we only implement station mode where 'ni' is always
@@ -8883,13 +8967,18 @@ iwx_add_sta_key(struct iwx_softc *sc, int sta_id, stru
 	sec_key_ver = iwx_lookup_cmd_ver(sc, IWX_DATA_PATH_GROUP,
 	    IWX_SEC_KEY_CMD);
 	if (sec_key_ver != 0 && sec_key_ver != IWX_FW_CMD_VER_UNKNOWN)
-		err = iwx_mld_add_sta_key_cmd(sc, sta_id, ni, k);
+		err = iwx_mld_set_sta_key_cmd(sc, sta_id, ni, k, 0);
+	else if (k->k_flags & IEEE80211_KEY_IGTK)
+		err = iwx_set_sta_igtk(sc, sta_id, ni, k, 0);
 	else
 		err = iwx_add_sta_key_cmd(sc, sta_id, ni, k);
 	if (err)
 		return err;
 
-	if (k->k_flags & IEEE80211_KEY_GROUP)
+	if (k->k_flags & IEEE80211_KEY_IGTK) {
+		in->in_flags |= IWX_NODE_FLAG_HAVE_INTEGRITY_GROUP_KEY;
+		ic->ic_igtk_kid = k->k_id;
+	} else if (k->k_flags & IEEE80211_KEY_GROUP)
 		in->in_flags |= IWX_NODE_FLAG_HAVE_GROUP_KEY;
 	else
 		in->in_flags |= IWX_NODE_FLAG_HAVE_PAIRWISE_KEY;
@@ -8897,6 +8986,8 @@ iwx_add_sta_key(struct iwx_softc *sc, int sta_id, stru
 	if ((in->in_flags & want_keymask) == want_keymask) {
 		DPRINTF(("marking port %s valid\n",
 		    ether_sprintf(ni->ni_macaddr)));
+		if (ni->ni_flags & IEEE80211_NODE_MFP)
+			ni->ni_flags |= IEEE80211_NODE_TXMGMTPROT;
 		ni->ni_port_valid = 1;
 		ieee80211_set_link_state(ic, LINK_STATE_UP);
 	}
@@ -8934,8 +9025,10 @@ iwx_delete_key(struct ieee80211com *ic, struct ieee802
 {
 	struct iwx_softc *sc = ic->ic_softc;
 	struct iwx_add_sta_key_cmd cmd;
+	uint8_t sec_key_ver;
 
-	if (k->k_cipher != IEEE80211_CIPHER_CCMP) {
+	if (k->k_cipher != IEEE80211_CIPHER_CCMP &&
+	    (k->k_flags & IEEE80211_KEY_IGTK) == 0) {
 		/* Fallback to software crypto for other ciphers. */
                 ieee80211_delete_key(ic, ni, k);
 		return;
@@ -8944,6 +9037,18 @@ iwx_delete_key(struct ieee80211com *ic, struct ieee802
 	if ((sc->sc_flags & IWX_FLAG_STA_ACTIVE) == 0)
 		return;
 
+	sec_key_ver = iwx_lookup_cmd_ver(sc, IWX_DATA_PATH_GROUP,
+	    IWX_SEC_KEY_CMD);
+	if (sec_key_ver != 0 && sec_key_ver != IWX_FW_CMD_VER_UNKNOWN) {
+		iwx_mld_set_sta_key_cmd(sc, IWX_STATION_ID, ni, k, 1);
+		return;
+	}
+
+	if (k->k_flags & IEEE80211_KEY_IGTK) {
+		iwx_set_sta_igtk(sc, IWX_STATION_ID, ni, k, 1);
+		return;
+	}
+
 	memset(&cmd, 0, sizeof(cmd));
 
 	cmd.common.key_flags = htole16(IWX_STA_KEY_NOT_VALID |
@@ -9656,6 +9761,49 @@ iwx_start(struct ifnet *ifp)
 }
 
 void
+iwx_mfp_leave_done(struct ieee80211com *ic, struct ieee80211_node *ni)
+{
+	struct iwx_softc *sc = ic->ic_softc;
+	struct ifnet *ifp = IC2IFP(&sc->sc_ic);
+
+	if ((ifp->if_flags & IFF_RUNNING) &&
+	    ic->ic_state == IEEE80211_S_RUN &&
+	    (ni->ni_flags & IEEE80211_NODE_MFP)) {
+		sc->deauth_sent = 1;
+		wakeup(&sc->deauth_sent);
+	}
+}
+
+void
+iwx_mfp_leave(struct iwx_softc *sc)
+{
+	struct ieee80211com *ic = &sc->sc_ic;
+	struct ieee80211_node *ni = (void *)ic->ic_bss;
+
+	ic->ic_xflags |= IEEE80211_F_TX_MGMT_ONLY;
+	sc->deauth_sent = 0;
+
+	ni->ni_unref_cb = iwx_mfp_leave_done;
+	ni->ni_unref_arg = NULL;
+	ni->ni_unref_arg_size = 0;
+
+	/*
+	 * Send an authenticated deauth frame in order to let our AP know we
+	 * are leaving. This allows our AP to tear down MFP state cleanly.
+	 * Otherwise we would remain locked out of this AP until a timeout
+	 * of stale MFP state occurs at the AP, which might take a while.
+	 */
+	if (IEEE80211_SEND_MGMT(ic, ni, IEEE80211_FC0_SUBTYPE_DEAUTH,
+	    IEEE80211_REASON_AUTH_LEAVE) != 0) {
+		ni->ni_unref_cb = NULL;
+		return;
+	}
+
+	if (tsleep_nsec(&sc->deauth_sent, 0, "iwxlv", MSEC_TO_NSEC(500)) != 0)
+		ni->ni_unref_cb = NULL;
+}
+
+void
 iwx_stop(struct ifnet *ifp)
 {
 	struct iwx_softc *sc = ifp->if_softc;
@@ -9680,6 +9828,11 @@ iwx_stop(struct ifnet *ifp)
 	KASSERT(sc->task_refs.r_refs >= 1);
 	refcnt_finalize(&sc->task_refs, "iwxstop");
 
+	if (ic->ic_opmode == IEEE80211_M_STA &&
+	    ic->ic_state == IEEE80211_S_RUN &&
+	    (ic->ic_bss->ni_flags & IEEE80211_NODE_MFP))
+		iwx_mfp_leave(sc);
+
 	iwx_stop_device(sc);
 
 	free(sc->bgscan_unref_arg, M_DEVBUF, sc->bgscan_unref_arg_size);
@@ -10348,6 +10501,7 @@ iwx_rx_pkt(struct iwx_softc *sc, struct iwx_rx_data *d
 		case IWX_WIDE_ID(IWX_REGULATORY_AND_NVM_GROUP,
 		    IWX_NVM_GET_INFO):
 		case IWX_ADD_STA_KEY:
+		case IWX_MGMT_MCAST_KEY:
 		case IWX_PHY_CONFIGURATION_CMD:
 		case IWX_TX_ANT_CONFIGURATION_CMD:
 		case IWX_ADD_STA:
@@ -11688,7 +11842,8 @@ iwx_attach(struct device *parent, struct device *self,
 	    IEEE80211_C_SCANALLBAND |	/* device scans all bands at once */
 	    IEEE80211_C_MONITOR |	/* monitor mode supported */
 	    IEEE80211_C_SHSLOT |	/* short slot time supported */
-	    IEEE80211_C_SHPREAMBLE;	/* short preamble supported */
+	    IEEE80211_C_SHPREAMBLE |	/* short preamble supported */
+	    IEEE80211_C_MFP;		/* management frame protection */
 
 	ic->ic_htcaps = IEEE80211_HTCAP_SGI20 | IEEE80211_HTCAP_SGI40;
 	ic->ic_htcaps |= IEEE80211_HTCAP_CBW20_40;
blob - 9fefe2bf161c6ed371bbf80317a011cbe1f6ab20
blob + 0eaa20b91b3fac87e6f3e135f0cea043dcead8df
--- sys/dev/pci/if_iwxreg.h
+++ sys/dev/pci/if_iwxreg.h
@@ -8040,16 +8040,34 @@ struct iwx_rm_sta_cmd {
  * @key_id:
  * @receive_seq_cnt: initial RSC/PN needed for replay check
  */
-struct iwx_mgmt_mcast_key_cmd {
+struct iwx_mgmt_mcast_key_cmd_v1 {
 	uint32_t ctrl_flags;
-	uint8_t IGTK[16];
-	uint8_t K1[16];
-	uint8_t K2[16];
+	uint8_t igtk[16];
+	uint8_t k1[16];
+	uint8_t k2[16];
 	uint32_t key_id;
 	uint32_t sta_id;
 	uint64_t receive_seq_cnt;
 } __packed; /* SEC_MGMT_MULTICAST_KEY_CMD_API_S_VER_1 */
 
+/**
+ * struct iwx_mgmt_mcast_key_cmd - IGTK command
+ * ( MGMT_MCAST_KEY = 0x1f )
+ * @ctrl_flags: &enum iwx_sta_key_flag
+ * @igtk: IGTK master key
+ * @sta_id: station ID that support IGTK
+ * @key_id: key ID
+ * @receive_seq_cnt: initial RSC/PN needed for replay check
+ */
+struct iwx_mgmt_mcast_key_cmd {
+	uint32_t ctrl_flags;
+	uint8_t igtk[32];
+	uint32_t key_id;
+	uint32_t sta_id;
+	uint64_t receive_seq_cnt;
+} __packed; /* SEC_MGMT_MULTICAST_KEY_CMD_API_S_VER_2 */
+
+
 struct iwx_wep_key {
 	uint8_t key_index;
 	uint8_t key_offset;
blob - e82e5bf0fab076d18d1cb8399d9cb15e5bc9e703
blob + f2b8b479a17f1feeccaae2fa7917247e6e2ecc77
--- sys/dev/pci/if_iwxvar.h
+++ sys/dev/pci/if_iwxvar.h
@@ -652,6 +652,8 @@ struct iwx_softc {
 	enum ieee80211_state	ns_nstate;
 	int			ns_arg;
 
+	int			deauth_sent;
+
 	/* Task for firmware BlockAck setup/teardown and its arguments. */
 	struct task		ba_task;
 	struct iwx_ba_task_data	ba_rx;
@@ -660,13 +662,13 @@ struct iwx_softc {
 	/* Task for setting encryption keys and its arguments. */
 	struct task		setkey_task;
 	/*
-	 * At present we need to process at most two keys at once:
-	 * Our pairwise key and a group key.
+	 * At present we need to process at most three keys at once:
+	 * Our pairwise key, a group key, and an integrity group key.
 	 * When hostap mode is implemented this array needs to grow or
 	 * it might become a bottleneck for associations that occur at
 	 * roughly the same time.
 	 */
-	struct iwx_setkey_task_arg setkey_arg[2];
+	struct iwx_setkey_task_arg setkey_arg[3];
 	int setkey_cur;
 	int setkey_tail;
 	int setkey_nkeys;
@@ -843,9 +845,11 @@ struct iwx_node {
 	struct iwx_rxq_dup_data dup_data;
 
 	int in_flags;
-#define IWX_NODE_FLAG_HAVE_PAIRWISE_KEY	0x01
-#define IWX_NODE_FLAG_HAVE_GROUP_KEY	0x02
+#define IWX_NODE_FLAG_HAVE_PAIRWISE_KEY		0x01
+#define IWX_NODE_FLAG_HAVE_GROUP_KEY		0x02
+#define IWX_NODE_FLAG_HAVE_INTEGRITY_GROUP_KEY	0x04
 };
+
 #define IWX_STATION_ID 0
 #define IWX_AUX_STA_ID 1
 #define IWX_MONITOR_STA_ID 2
blob - a6aad7988908629238321cc6d0ce8380b37108a3
blob + 4389af23dbd213e9cca3d237a22f11cdbd669095
--- sys/dev/pci/if_qwx_pci.c
+++ sys/dev/pci/if_qwx_pci.c
@@ -1084,7 +1084,8 @@ unsupported_wcn6855_soc:
 	    IEEE80211_C_MONITOR |	/* monitor mode supported */
 #endif
 	    IEEE80211_C_SHSLOT |	/* short slot time supported */
-	    IEEE80211_C_SHPREAMBLE;	/* short preamble supported */
+	    IEEE80211_C_SHPREAMBLE |	/* short preamble supported */
+	    IEEE80211_C_MFP;		/* management frame protection */
 
 	ic->ic_sup_rates[IEEE80211_MODE_11A] = ieee80211_std_rateset_11a;
 	ic->ic_sup_rates[IEEE80211_MODE_11B] = ieee80211_std_rateset_11b;
blob - 8873924c2dbd2e7b8d122568425bbefd8d012d7c
blob + 08628c88b0cb9505f9e3ca32e8d88f62724d1010
--- sys/net80211/ieee80211_crypto.c
+++ sys/net80211/ieee80211_crypto.c
@@ -386,8 +386,6 @@ ieee80211_derive_ptk(enum ieee80211_akm akm, const u_i
     const u_int8_t *aa, const u_int8_t *spa, const u_int8_t *anonce,
     const u_int8_t *snonce, struct ieee80211_ptk *ptk)
 {
-	void (*kdf)(const u_int8_t *, size_t, const u_int8_t *, size_t,
-	    const u_int8_t *, size_t, u_int8_t *, size_t);
 	u_int8_t buf[2 * IEEE80211_ADDR_LEN + 2 * EAPOL_KEY_NONCE_LEN];
 	int ret;
 
@@ -401,9 +399,17 @@ ieee80211_derive_ptk(enum ieee80211_akm akm, const u_i
 	memcpy(&buf[12], ret ? anonce : snonce, EAPOL_KEY_NONCE_LEN);
 	memcpy(&buf[44], ret ? snonce : anonce, EAPOL_KEY_NONCE_LEN);
 
-	kdf = ieee80211_is_sha256_akm(akm) ? ieee80211_kdf : ieee80211_prf;
-	(*kdf)(pmk, IEEE80211_PMK_LEN, "Pairwise key expansion", 23,
-	    buf, sizeof buf, (u_int8_t *)ptk, sizeof(*ptk));
+	if (ieee80211_is_sha256_akm(akm)) {
+		ieee80211_kdf(pmk, IEEE80211_PMK_LEN, "Pairwise key expansion",
+		    22 /* KDF omits \0 */, buf, sizeof buf, (u_int8_t *)ptk,
+		    /* expected output size of 48 is mixed into hash */
+		    MIN(48, sizeof(*ptk)));
+		CTASSERT(sizeof(struct ieee80211_ptk) >= 48);
+	} else {
+		ieee80211_prf(pmk, IEEE80211_PMK_LEN, "Pairwise key expansion",
+		    23 /* PRF uses \0 */, buf, sizeof buf, (u_int8_t *)ptk,
+		    sizeof(*ptk));
+	}
 }
 
 static void
blob - 85825651f470812045f2a1ef79ce5d743485f892
blob + 50278acd28854cf7e3e20fe94ecb3874adf5a0a9
--- sys/net80211/ieee80211_input.c
+++ sys/net80211/ieee80211_input.c
@@ -637,26 +637,39 @@ ieee80211_inputm(struct ifnet *ifp, struct mbuf *m, st
 		}
 
 		if (ni->ni_flags & IEEE80211_NODE_RXMGMTPROT) {
+			int is_multicast, is_protected;
+
+			is_multicast = IEEE80211_IS_MULTICAST(wh->i_addr1);
+			is_protected = (wh->i_fc[1] & IEEE80211_FC1_PROTECTED);
+
 			/* MMPDU protection is on for Rx */
 			if (subtype == IEEE80211_FC0_SUBTYPE_DISASSOC ||
 			    subtype == IEEE80211_FC0_SUBTYPE_DEAUTH ||
 			    subtype == IEEE80211_FC0_SUBTYPE_ACTION) {
-				if (!IEEE80211_IS_MULTICAST(wh->i_addr1) &&
-				    !(wh->i_fc[1] & IEEE80211_FC1_PROTECTED)) {
+				if (rxi->rxi_flags & IEEE80211_RXI_HWDEC) {
+					m = ieee80211_input_hwdecrypt(ic, ni,
+					    m, rxi);
+					if (m == NULL)
+						goto out;
+				} else if (!is_multicast && !is_protected) {
 					/* unicast mgmt not encrypted */
+					ic->ic_stats.is_rx_unencrypted++;
 					goto out;
+				} else {
+					/* do software decryption */
+					m = ieee80211_decrypt(ic, m, ni);
+					if (m == NULL) {
+						ic->ic_stats.is_rx_wepfail++;
+						goto out;
+					}
 				}
-				/* do software decryption */
-				m = ieee80211_decrypt(ic, m, ni);
-				if (m == NULL) {
-					/* XXX stats */
-					goto out;
-				}
 				wh = mtod(m, struct ieee80211_frame *);
 			}
 		} else if ((ic->ic_flags & IEEE80211_F_RSNON) &&
-		    (wh->i_fc[1] & IEEE80211_FC1_PROTECTED)) {
+		    ((wh->i_fc[1] & IEEE80211_FC1_PROTECTED) ||
+		    (rxi->rxi_flags & IEEE80211_RXI_HWDEC))) {
 			/* encrypted but MMPDU Rx protection off for TA */
+			ic->ic_stats.is_rx_nowep++;
 			goto out;
 		}
 
blob - 07eda83a1077caebda97a8f7f8cbfe87eaf37328
blob + 817cd7cd85a125caa127781c667ede83da328c63
--- sys/net80211/ieee80211_ioctl.c
+++ sys/net80211/ieee80211_ioctl.c
@@ -200,6 +200,7 @@ void
 ieee80211_disable_rsn(struct ieee80211com *ic)
 {
 	ic->ic_flags &= ~(IEEE80211_F_PSK | IEEE80211_F_RSNON);
+	ic->ic_flags &= ~IEEE80211_F_MFPR;
 	explicit_bzero(ic->ic_psk, sizeof(ic->ic_psk));
 	ic->ic_rsnprotos = 0;
 	ic->ic_rsnakms = 0;
blob - 1511a34bbbdda3e021a8fa171ab2b2a0ebe7e0f3
blob + 3f7e20bc8aafccf3af63d153f971646a0b5de213
--- sys/net80211/ieee80211_node.c
+++ sys/net80211/ieee80211_node.c
@@ -2817,6 +2817,8 @@ ieee80211_node_join_rsn(struct ieee80211com *ic, struc
 	ni->ni_key_count = 0;
 	ni->ni_port_valid = 0;
 	ni->ni_flags &= ~IEEE80211_NODE_TXRXPROT;
+	ni->ni_flags &= ~IEEE80211_NODE_RXMGMTPROT;
+	ni->ni_flags &= ~IEEE80211_NODE_TXMGMTPROT;
 	ni->ni_flags &= ~IEEE80211_NODE_RSN_NEW_PTK;
 	ni->ni_replaycnt = -1;	/* XXX */
 	ni->ni_rsn_retries = 0;
@@ -3055,6 +3057,9 @@ ieee80211_node_leave_rsn(struct ieee80211com *ic, stru
 
 	ni->ni_rsn_retries = 0;
 	ni->ni_flags &= ~IEEE80211_NODE_TXRXPROT;
+	ni->ni_flags &= ~IEEE80211_NODE_RXMGMTPROT;
+	ni->ni_flags &= ~IEEE80211_NODE_TXMGMTPROT;
+	ni->ni_flags &= ~IEEE80211_NODE_RSN_NEW_PTK;
 	ni->ni_port_valid = 0;
 	(*ic->ic_delete_key)(ic, ni, &ni->ni_pairwise_key);
 }
blob - 84dc77f23a3abfda7c4771ec9816695ac1b7e4b1
blob + 54592092069f6dcc942deffe9b263665f8b4c61e
--- sys/net80211/ieee80211_output.c
+++ sys/net80211/ieee80211_output.c
@@ -208,7 +208,7 @@ ieee80211_mgmt_output(struct ifnet *ifp, struct ieee80
 	IEEE80211_ADDR_COPY(wh->i_addr3, ni->ni_bssid);
 
 	/* check if protection is required for this mgmt frame */
-	if ((ic->ic_caps & IEEE80211_C_MFP) &&
+	if ((ni->ni_flags & IEEE80211_NODE_MFP) &&
 	    (type == IEEE80211_FC0_SUBTYPE_DISASSOC ||
 	     type == IEEE80211_FC0_SUBTYPE_DEAUTH ||
 	     type == IEEE80211_FC0_SUBTYPE_ACTION)) {
blob - daf550f591fdf675e073f82a968efbdbb49fdb46
blob + ecdcb664e41815b81d32ab9d8be431b1319f4267
--- sys/net80211/ieee80211_pae_input.c
+++ sys/net80211/ieee80211_pae_input.c
@@ -644,6 +644,8 @@ ieee80211_recv_4way_msg3(struct ieee80211com *ic,
 			/* install the IGTK */
 			switch ((*ic->ic_set_key)(ic, ni, k)) {
 			case 0:
+				ni->ni_flags |= IEEE80211_NODE_TXMGMTPROT;
+				ic->ic_igtk_kid = kid;
 				break;
 			case EBUSY:
 				deferlink = 1;
@@ -652,6 +654,8 @@ ieee80211_recv_4way_msg3(struct ieee80211com *ic,
 				reason = IEEE80211_REASON_AUTH_LEAVE;
 				goto deauth;
 			}
+
+			ni->ni_flags |= IEEE80211_NODE_RXMGMTPROT;
 		}
 	}
 	if (info & EAPOL_KEY_INSTALL)
@@ -932,12 +936,17 @@ ieee80211_recv_rsn_group_msg1(struct ieee80211com *ic,
 			/* install the IGTK */
 			switch ((*ic->ic_set_key)(ic, ni, k)) {
 			case 0:
+				ni->ni_flags |= IEEE80211_NODE_TXMGMTPROT;
+				ic->ic_igtk_kid = kid;
+				break;
 			case EBUSY:
 				break;
 			default:
 				reason = IEEE80211_REASON_AUTH_LEAVE;
 				goto deauth;
 			}
+
+			ni->ni_flags |= IEEE80211_NODE_RXMGMTPROT;
 		}
 	}
 	if (info & EAPOL_KEY_SECURE) {
blob - 81fe56bdd53d0487bd5f2b0c69bfe588690e6caf
blob + 96b3a2e971f10089a61815886c9f5cb8a9d99544
--- sys/net80211/ieee80211_proto.c
+++ sys/net80211/ieee80211_proto.c
@@ -948,6 +948,8 @@ ieee80211_auth_open(struct ieee80211com *ic, const str
 		if (ic->ic_flags & IEEE80211_F_RSNON) {
 			/* XXX not here! */
 			ic->ic_bss->ni_flags &= ~IEEE80211_NODE_TXRXPROT;
+			ic->ic_bss->ni_flags &= ~IEEE80211_NODE_RXMGMTPROT;
+			ic->ic_bss->ni_flags &= ~IEEE80211_NODE_TXMGMTPROT;
 			ic->ic_bss->ni_port_valid = 0;
 			ic->ic_bss->ni_replaycnt_ok = 0;
 			(*ic->ic_delete_key)(ic, ic->ic_bss,