diff --git a/src/drivers/driver.h b/src/drivers/driver.h
index 75b418a28..79ee10185 100644
--- a/src/drivers/driver.h
+++ b/src/drivers/driver.h
@@ -2780,6 +2780,23 @@ struct wpa_driver_ops {
 			       size_t data_len, int encrypt,
 			       const u8 *own_addr, u32 flags);
 
+	/**
+	 * Supports data tx status?
+	 */
+	int (*supports_data_tx_status)(void *priv);
+
+	/**
+	 * send_eapol - Send an EAPOL packet (STA only)
+	 * @priv: private driver interface data
+	 * @addr: Destination MAC address
+	 * @data: EAPOL packet starting with IEEE 802.1X header
+	 * @data_len: Length of the EAPOL packet in octets
+	 *
+	 * Returns: 0 on success, -1 on failure
+	 */
+	int (*send_eapol)(void *priv, const u8 *addr, const u8 *data,
+			       size_t data_len);
+
 	/**
 	 * sta_deauth - Deauthenticate a station (AP only)
 	 * @priv: Private driver interface data
diff --git a/src/drivers/driver_nl80211.c b/src/drivers/driver_nl80211.c
index 1acdb4228..1b988bdc8 100644
--- a/src/drivers/driver_nl80211.c
+++ b/src/drivers/driver_nl80211.c
@@ -5000,6 +5000,13 @@ static int nl80211_send_eapol_data(struct i802_bss *bss,
 	return ret;
 }
 
+static int driver_nl80211_supports_data_tx_status(
+	void *priv)
+{
+	struct i802_bss *bss = priv;
+	struct wpa_driver_nl80211_data *drv = bss->drv;
+	return drv->data_tx_status;
+}
 
 static const u8 rfc1042_header[6] = { 0xaa, 0xaa, 0x03, 0x00, 0x00, 0x00 };
 
@@ -5067,6 +5074,16 @@ static int wpa_driver_nl80211_hapd_send_eapol(
 	return res;
 }
 
+static int wpa_driver_nl80211_send_eapol(
+	void *priv, const u8 *addr, const u8 *data,
+	size_t data_len)
+{
+	struct i802_bss *bss = priv;
+
+	return nl80211_send_eapol_data(bss, addr, data, data_len);
+}
+
+
 
 static int wpa_driver_nl80211_sta_set_flags(void *priv, const u8 *addr,
 					    unsigned int total_flags,
@@ -11046,6 +11063,8 @@ const struct wpa_driver_ops wpa_driver_nl80211_ops = {
 	.sta_add = wpa_driver_nl80211_sta_add,
 	.sta_remove = driver_nl80211_sta_remove,
 	.hapd_send_eapol = wpa_driver_nl80211_hapd_send_eapol,
+	.send_eapol = wpa_driver_nl80211_send_eapol,
+	.supports_data_tx_status = driver_nl80211_supports_data_tx_status,
 	.sta_set_flags = wpa_driver_nl80211_sta_set_flags,
 	.hapd_init = i802_init,
 	.hapd_deinit = i802_deinit,
diff --git a/src/rsn_supp/wpa.c b/src/rsn_supp/wpa.c
index 490b2dbf2..af753cdbf 100644
--- a/src/rsn_supp/wpa.c
+++ b/src/rsn_supp/wpa.c
@@ -715,6 +715,8 @@ static void wpa_supplicant_process_1_of_4(struct wpa_sm *sm,
 	}
 #endif /* CONFIG_P2P */
 
+	sm->waiting_for_4_of_4_sent = 0; /* not yet */
+
 	if (wpa_supplicant_send_2_of_4(sm, sm->bssid, key, ver, sm->snonce,
 				       kde, kde_len, ptk) < 0)
 		goto failed;
@@ -1427,15 +1429,14 @@ int wpa_supplicant_send_4_of_4(struct wpa_sm *sm, const unsigned char *dst,
 }
 
 
-static void wpa_supplicant_process_3_of_4(struct wpa_sm *sm,
-					  const struct wpa_eapol_key *key,
-					  u16 ver, const u8 *key_data,
-					  size_t key_data_len)
+static void wpa_supplicant_process_3_of_4_send(struct wpa_sm *sm,
+					       const struct wpa_eapol_key *key,
+					       u16 ver, const u8 *key_data,
+					       size_t key_data_len)
 {
 	u16 key_info, keylen;
 	struct wpa_eapol_ie_parse ie;
 
-	wpa_sm_set_state(sm, WPA_4WAY_HANDSHAKE);
 	wpa_dbg(sm->ctx->msg_ctx, MSG_DEBUG, "WPA: RX message 3 of 4-Way "
 		"Handshake from " MACSTR " (ver=%d)", MAC2STR(sm->bssid), ver);
 
@@ -1495,70 +1496,16 @@ static void wpa_supplicant_process_3_of_4(struct wpa_sm *sm,
 	}
 #endif /* CONFIG_P2P */
 
+	sm->waiting_for_4_of_4_sent = 1;
 	if (wpa_supplicant_send_4_of_4(sm, sm->bssid, key, ver, key_info,
 				       &sm->ptk) < 0) {
+		sm->waiting_for_4_of_4_sent = 0;
 		goto failed;
 	}
-
-	/* SNonce was successfully used in msg 3/4, so mark it to be renewed
-	 * for the next 4-Way Handshake. If msg 3 is received again, the old
-	 * SNonce will still be used to avoid changing PTK. */
-	sm->renew_snonce = 1;
-
-	if (key_info & WPA_KEY_INFO_INSTALL) {
-		if (wpa_supplicant_install_ptk(sm, key))
-			goto failed;
-	}
-
-	if (key_info & WPA_KEY_INFO_SECURE) {
-		wpa_sm_mlme_setprotection(
-			sm, sm->bssid, MLME_SETPROTECTION_PROTECT_TYPE_RX,
-			MLME_SETPROTECTION_KEY_TYPE_PAIRWISE);
-		eapol_sm_notify_portValid(sm->eapol, TRUE);
-	}
-	wpa_sm_set_state(sm, WPA_GROUP_HANDSHAKE);
-
-	if (sm->group_cipher == WPA_CIPHER_GTK_NOT_USED) {
-		wpa_supplicant_key_neg_complete(sm, sm->bssid,
-						key_info & WPA_KEY_INFO_SECURE);
-	} else if (ie.gtk &&
-	    wpa_supplicant_pairwise_gtk(sm, key,
-					ie.gtk, ie.gtk_len, key_info) < 0) {
-		wpa_msg(sm->ctx->msg_ctx, MSG_INFO,
-			"RSN: Failed to configure GTK");
-		goto failed;
-	}
-
-	if (ieee80211w_set_keys(sm, &ie) < 0) {
-		wpa_msg(sm->ctx->msg_ctx, MSG_INFO,
-			"RSN: Failed to configure IGTK");
-		goto failed;
-	}
-
-	if (ie.gtk)
-		wpa_sm_set_rekey_offload(sm);
-
-	/* Add PMKSA cache entry for Suite B AKMs here since PMKID can be
-	 * calculated only after KCK has been derived. Though, do not replace an
-	 * existing PMKSA entry after each 4-way handshake (i.e., new KCK/PMKID)
-	 * to avoid unnecessary changes of PMKID while continuing to use the
-	 * same PMK. */
-	if (sm->proto == WPA_PROTO_RSN && wpa_key_mgmt_suite_b(sm->key_mgmt) &&
-	    !sm->cur_pmksa) {
-		struct rsn_pmksa_cache_entry *sa;
-
-		sa = pmksa_cache_add(sm->pmksa, sm->pmk, sm->pmk_len, NULL,
-				     sm->ptk.kck, sm->ptk.kck_len,
-				     sm->bssid, sm->own_addr,
-				     sm->network_ctx, sm->key_mgmt, NULL);
-		if (!sm->cur_pmksa)
-			sm->cur_pmksa = sa;
-	}
-
-	sm->msg_3_of_4_ok = 1;
 	return;
 
 failed:
+	wpa_sm_set_state(sm, WPA_4WAY_HANDSHAKE);
 	wpa_sm_deauthenticate(sm, WLAN_REASON_UNSPECIFIED);
 }
 
@@ -2076,6 +2023,137 @@ static int wpa_supp_aead_decrypt(struct wpa_sm *sm, u8 *buf, size_t buf_len,
 }
 #endif /* CONFIG_FILS */
 
+static void wpa_supplicant_process_4_of_4_sent(struct wpa_sm *sm)
+{
+	size_t key_data_len;
+	struct wpa_eapol_key *key;
+	u16 key_info, ver;
+	struct wpa_eapol_ie_parse ie;
+	u8 *mic, *key_data;
+	size_t mic_len;
+	u8 *buf = sm->last_3_of_4_buf;
+
+	mic_len = wpa_mic_len(sm->key_mgmt, sm->pmk_len);
+
+	key = (struct wpa_eapol_key *) (buf + sizeof(struct ieee802_1x_hdr));
+	mic = (u8 *) (key + 1);
+	key_data = mic + mic_len + 2;
+
+	wpa_sm_set_state(sm, WPA_4WAY_HANDSHAKE);
+	wpa_dbg(sm->ctx->msg_ctx, MSG_DEBUG, "WPA: Transmitted 4 of 4-Way "
+		"Handshake from " MACSTR, MAC2STR(sm->bssid));
+
+	key_info = WPA_GET_BE16(key->key_info);
+	ver = key_info & WPA_KEY_INFO_TYPE_MASK;
+
+	sm->waiting_for_4_of_4_sent = 0;
+
+	key_data_len = WPA_GET_BE16(mic + mic_len);
+
+	/* Decrypt the key so we can parse the IEs, etc */
+#ifdef CONFIG_FILS
+	if (!mic_len && (key_info & WPA_KEY_INFO_ENCR_KEY_DATA)) {
+		if (wpa_supp_aead_decrypt(sm, buf, sm->last_3_of_4_len, &key_data_len))
+			goto failed;
+	}
+#endif /* CONFIG_FILS */
+
+	if ((sm->proto == WPA_PROTO_RSN || sm->proto == WPA_PROTO_OSEN) &&
+	    (key_info & WPA_KEY_INFO_ENCR_KEY_DATA) && mic_len) {
+		if (wpa_supplicant_decrypt_key_data(sm, key, mic_len,
+						    ver, key_data,
+						    &key_data_len))
+			goto failed;
+	}
+
+	wpa_hexdump(MSG_DEBUG, "WPA: IE KeyData", key_data, key_data_len);
+	if (wpa_supplicant_parse_ies(key_data, key_data_len, &ie) < 0)
+		goto failed;
+
+	/* SNonce was successfully used in msg 3/4, so mark it to be renewed
+	 * for the next 4-Way Handshake. If msg 3 is received again, the old
+	 * SNonce will still be used to avoid changing PTK. */
+	sm->renew_snonce = 1;
+
+	if (key_info & WPA_KEY_INFO_INSTALL) {
+		if (wpa_supplicant_install_ptk(sm, key))
+			goto failed;
+	}
+
+	if (key_info & WPA_KEY_INFO_SECURE) {
+		wpa_sm_mlme_setprotection(
+			sm, sm->bssid, MLME_SETPROTECTION_PROTECT_TYPE_RX,
+			MLME_SETPROTECTION_KEY_TYPE_PAIRWISE);
+		eapol_sm_notify_portValid(sm->eapol, TRUE);
+	}
+	wpa_sm_set_state(sm, WPA_GROUP_HANDSHAKE);
+
+	if (sm->group_cipher == WPA_CIPHER_GTK_NOT_USED) {
+		wpa_supplicant_key_neg_complete(sm, sm->bssid,
+						key_info & WPA_KEY_INFO_SECURE);
+	} else if (ie.gtk &&
+		   wpa_supplicant_pairwise_gtk(sm, key,
+					       ie.gtk, ie.gtk_len, key_info) < 0) {
+		wpa_msg(sm->ctx->msg_ctx, MSG_INFO,
+			"RSN: Failed to configure GTK");
+		goto failed;
+	}
+
+	if (ieee80211w_set_keys(sm, &ie) < 0) {
+		wpa_msg(sm->ctx->msg_ctx, MSG_INFO,
+			"RSN: Failed to configure IGTK");
+		goto failed;
+	}
+
+	if (ie.gtk)
+		wpa_sm_set_rekey_offload(sm);
+
+	/* Add PMKSA cache entry for Suite B AKMs here since PMKID can be
+	 * calculated only after KCK has been derived. Though, do not replace an
+	 * existing PMKSA entry after each 4-way handshake (i.e., new KCK/PMKID)
+	 * to avoid unnecessary changes of PMKID while continuing to use the
+	 * same PMK. */
+	if (sm->proto == WPA_PROTO_RSN && wpa_key_mgmt_suite_b(sm->key_mgmt) &&
+	    !sm->cur_pmksa) {
+		struct rsn_pmksa_cache_entry *sa;
+
+		sa = pmksa_cache_add(sm->pmksa, sm->pmk, sm->pmk_len, NULL,
+				     sm->ptk.kck, sm->ptk.kck_len,
+				     sm->bssid, sm->own_addr,
+				     sm->network_ctx, sm->key_mgmt, NULL);
+		if (!sm->cur_pmksa)
+			sm->cur_pmksa = sa;
+	}
+
+	sm->msg_3_of_4_ok = 1;
+	return;
+
+failed:
+	wpa_sm_deauthenticate(sm, WLAN_REASON_UNSPECIFIED);
+}
+
+void wpa_sm_eapol_tx_status_available(struct wpa_sm *sm, int is_available)
+{
+	sm->eapol_tx_status_available = is_available;
+}
+
+/* De-auth if return is < 0 */
+int wpa_sm_eapol_tx_status(struct wpa_sm *sm, const u8 *dst,
+			   const u8 *buf, size_t len, int ack)
+{
+	wpa_dbg(sm->ctx->msg_ctx, MSG_DEBUG,
+		"EAPOL_TX_STATUS: ACK(%d) waiting 4/4-tx-status: %d", ack, sm->waiting_for_4_of_4_sent);
+	if (ack && sm->waiting_for_4_of_4_sent) {
+		wpa_supplicant_process_4_of_4_sent(sm);
+	}
+	else if (!ack && sm->waiting_for_4_of_4_sent) {
+		wpa_dbg(sm->ctx->msg_ctx, MSG_DEBUG,
+			"EAPOL 4/4 Not acked, disconnecting");
+		return -1;
+	}
+	return 0;
+}
+
 #ifdef CONFIG_TESTING_OPTIONS
 /* Mostly same as below, but this should not change any state.  Returns the
  * message type so we can make decisions before feeding this into the state
@@ -2436,8 +2514,19 @@ int wpa_sm_rx_eapol(struct wpa_sm *sm, const u8 *src_addr,
 		if (key_info & (WPA_KEY_INFO_MIC |
 				WPA_KEY_INFO_ENCR_KEY_DATA)) {
 			/* 3/4 4-Way Handshake */
-			wpa_supplicant_process_3_of_4(sm, key, ver, key_data,
-						      key_data_len);
+			/* Save buffer for doing the second half of the 4/4 processing
+			 * once we get 4/4 ack status
+			 */
+			int my_len = sizeof(sm->last_3_of_4_buf);
+			if (len < my_len)
+				my_len = len;
+			memcpy(sm->last_3_of_4_buf, buf, my_len);
+			sm->last_3_of_4_len = my_len;
+
+			wpa_supplicant_process_3_of_4_send(sm, key, ver, key_data,
+							   key_data_len);
+			if (!sm->eapol_tx_status_available)
+				wpa_supplicant_process_4_of_4_sent(sm);
 		} else {
 			/* 1/4 4-Way Handshake */
 			wpa_supplicant_process_1_of_4(sm, src_addr, key,
diff --git a/src/rsn_supp/wpa.h b/src/rsn_supp/wpa.h
index ad64f2f2d..0bf944dae 100644
--- a/src/rsn_supp/wpa.h
+++ b/src/rsn_supp/wpa.h
@@ -215,6 +215,9 @@ void wpa_sm_set_ptk_kck_kek(struct wpa_sm *sm,
 			    const u8 *ptk_kck, size_t ptk_kck_len,
 			    const u8 *ptk_kek, size_t ptk_kek_len);
 int wpa_fils_is_completed(struct wpa_sm *sm);
+int wpa_sm_eapol_tx_status(struct wpa_sm *sm, const u8 *dst,
+			   const u8 *buf, size_t len, int ack);
+void wpa_sm_eapol_tx_status_available(struct wpa_sm *sm, int is_available);
 
 #else /* CONFIG_NO_WPA */
 
@@ -402,6 +405,16 @@ static inline int wpa_fils_is_completed(struct wpa_sm *sm)
 	return 0;
 }
 
+static int wpa_sm_eapol_tx_status(struct wpa_sm *sm, const u8 *dst,
+			   const u8 *buf, size_t len, int ack)
+{
+	return 0;
+}
+
+static void wpa_sm_eapol_tx_status_available(struct wpa_sm *sm, int is_available)
+{
+}
+
 #endif /* CONFIG_NO_WPA */
 
 #ifdef CONFIG_IEEE80211R
diff --git a/src/rsn_supp/wpa_i.h b/src/rsn_supp/wpa_i.h
index 893d370c5..081cda561 100644
--- a/src/rsn_supp/wpa_i.h
+++ b/src/rsn_supp/wpa_i.h
@@ -175,6 +175,11 @@ struct wpa_sm {
 	struct crypto_ecdh *owe_ecdh;
 	u16 owe_group;
 #endif /* CONFIG_OWE */
+
+	u8 waiting_for_4_of_4_sent; /* boolean */
+	u8 eapol_tx_status_available;
+	u16 last_3_of_4_len;
+	u8 last_3_of_4_buf[1500];
 };
 
 
diff --git a/wpa_supplicant/driver_i.h b/wpa_supplicant/driver_i.h
index db9f76eda..342db5fd0 100644
--- a/wpa_supplicant/driver_i.h
+++ b/wpa_supplicant/driver_i.h
@@ -356,6 +356,23 @@ static inline int wpa_drv_hapd_send_eapol(struct wpa_supplicant *wpa_s,
 	return -1;
 }
 
+static inline int wpa_drv_supports_data_tx_status(struct wpa_supplicant *wpa_s)
+{
+	if (wpa_s->driver->supports_data_tx_status)
+		return wpa_s->driver->supports_data_tx_status(wpa_s->drv_priv);
+	return 0;
+}
+
+static inline int wpa_drv_send_eapol(struct wpa_supplicant *wpa_s,
+					  const u8 *addr, const u8 *data,
+					  size_t data_len)
+{
+	if (wpa_s->driver->hapd_send_eapol)
+		return wpa_s->driver->send_eapol(wpa_s->drv_priv, addr,
+						      data, data_len);
+	return -1;
+}
+
 static inline int wpa_drv_sta_set_flags(struct wpa_supplicant *wpa_s,
 					const u8 *addr, int total_flags,
 					int flags_or, int flags_and)
diff --git a/wpa_supplicant/events.c b/wpa_supplicant/events.c
index b2e4d631a..2205a77f5 100644
--- a/wpa_supplicant/events.c
+++ b/wpa_supplicant/events.c
@@ -4372,13 +4372,25 @@ void wpa_supplicant_event(void *ctx, enum wpa_event_type event,
 		}
 #endif /* CONFIG_AP */
 		break;
-#ifdef CONFIG_AP
 	case EVENT_EAPOL_TX_STATUS:
-		ap_eapol_tx_status(wpa_s, data->eapol_tx_status.dst,
-				   data->eapol_tx_status.data,
-				   data->eapol_tx_status.data_len,
-				   data->eapol_tx_status.ack);
+		if (wpa_s->ap_iface) {
+			ap_eapol_tx_status(wpa_s, data->eapol_tx_status.dst,
+					   data->eapol_tx_status.data,
+					   data->eapol_tx_status.data_len,
+					   data->eapol_tx_status.ack);
+		}
+		else {
+			if (wpa_sm_eapol_tx_status(wpa_s->wpa, data->eapol_tx_status.dst,
+						   data->eapol_tx_status.data,
+						   data->eapol_tx_status.data_len,
+						   data->eapol_tx_status.ack) < 0) {
+				wpa_s->own_disconnect_req = 1;
+				wpa_supplicant_deauthenticate(
+					wpa_s, WLAN_REASON_4WAY_HANDSHAKE_TIMEOUT);
+			}
+		}
 		break;
+#ifdef CONFIG_AP
 	case EVENT_DRIVER_CLIENT_POLL_OK:
 		ap_client_poll_ok(wpa_s, data->client_poll.addr);
 		break;
diff --git a/wpa_supplicant/wpas_glue.c b/wpa_supplicant/wpas_glue.c
index 2ef396024..56ef2596f 100644
--- a/wpa_supplicant/wpas_glue.c
+++ b/wpa_supplicant/wpas_glue.c
@@ -97,6 +97,9 @@ static u8 * wpa_alloc_eapol(const struct wpa_supplicant *wpa_s, u8 type,
 static int wpa_ether_send(struct wpa_supplicant *wpa_s, const u8 *dest,
 			  u16 proto, const u8 *buf, size_t len)
 {
+	int ret;
+	int tx_status_avail = 0;
+
 #ifdef CONFIG_TESTING_OPTIONS
 	if (wpa_s->ext_eapol_frame_io && proto == ETH_P_EAPOL) {
 		size_t hex_len = 2 * len + 1;
@@ -111,6 +114,18 @@ static int wpa_ether_send(struct wpa_supplicant *wpa_s, const u8 *dest,
 		return 0;
 	}
 #endif /* CONFIG_TESTING_OPTIONS */
+	ret = wpa_drv_send_eapol(wpa_s, dest, buf, len);
+
+	if (wpa_drv_supports_data_tx_status(wpa_s))
+		tx_status_avail = (ret >= 0);
+
+	wpa_sm_eapol_tx_status_available(wpa_s->wpa, tx_status_avail);
+
+	wpa_dbg(wpa_s, MSG_DEBUG, "wpa_drv_send_eapol rv (%d) tx-status-avail: %d",
+		ret, tx_status_avail);
+
+	if (ret >= 0)
+		return ret;
 
 	if (wpa_s->l2) {
 		return l2_packet_send(wpa_s->l2, dest, proto, buf, len);
@@ -153,8 +168,10 @@ static int wpa_supplicant_eapol_send(void *ctx, int type, const u8 *buf,
 		 * EAPOL frames (mainly, EAPOL-Start) from EAPOL state
 		 * machines. */
 		wpa_printf(MSG_DEBUG, "WPA: drop TX EAPOL in non-IEEE 802.1X "
-			   "mode (type=%d len=%lu)", type,
-			   (unsigned long) len);
+			   "mode (type=%d len=%lu key_mgmt=%d is-psk=%d) "
+			   "(this message is normal for PSK connections)", type,
+			   (unsigned long) len, (unsigned int)(wpa_s->key_mgmt),
+			   wpa_key_mgmt_wpa_psk(wpa_s->key_mgmt));
 		return -1;
 	}
 
