Index | Thread | Search

From:
Stuart Henderson <stu@spacehopper.org>
Subject:
Re: wifi protected management frame (PMF) support
To:
tech@openbsd.org
Date:
Sun, 23 Nov 2025 13:48:37 +0000

Download raw body.

Thread
works for me against hapax2 (QCN-5052) running routeros 7.20.4 with PMF
required;

iwx0 at pci0 dev 20 function 3 "Intel Wi-Fi 6 AX211" rev 0x01, msix
iwx0: hw rev 0x370, fw 77.f92b5fed.0, pnvm ce1a5094, address 2c:33:58:0a:b5:54
iwx0: using firmware iwx-so-a0-gf-a0-77


On 2025/11/22 22:45, Stefan Sperling wrote:
> 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,
>