[PATCH v2 3/3] PR PASN: Handle lost M3 and lost M3 ACK (align with P2P GO Neg Confirm)

Emily Xia xiayucheng at google.com
Fri Oct 30 02:23:15 PDT 2026


When PASN Auth3 (M3) is received by the Responder
(pr_pasn_handle_auth_3()) and the Responder immediately starts ranging,
if the 802.11 ACK for M3 is lost over the air (acked=0), the Initiator
previously gated pasn_result and get_ranging_params on (ret == 1 &&
acked) in pr_pasn_auth_tx_status(), freed pasn->frame, and never retried
M3 or started ranging. Because wpas_pr_pasn_auth_tx_status() returned 0,
the Initiator stayed wedged until the 10s wpas_pr_pasn_timeout expired
(PR_SESSION_END_NEG_FAILED) while the Responder was already ranging. On
the Responder side, an ACKed M2 followed by a lost M3 waited the full
10s failsafe timeout before tearing down.

Composing with the M1/M2 retry and replay recovery and aligning with P2P
GO Negotiation (p2p_go_neg_conf_cb and p2p_go_neg_resp_cb in
src/p2p/p2p_go_neg.c):

1. Initiator M3 retry, optimistic completion, and late M1 TX-status guard:
   - Add per-peer u8 pasn_m3_retries to struct pr_device (under
     CONFIG_PASN) and reset it to 0 in pr_reset_pasn_state() and
     pr_pasn_initialize().
   - In pr_pasn_auth_tx_status(), ignore stale TX status when
     mgmt->u.auth.auth_transaction != pasn->trans_seq before calling
     wpa_pasn_auth_tx_status() and return 0, so a delayed M1 TX status
     arriving after M2 RX (on drivers that hold cfg80211_mgmt_tx_status()
     until the off-channel dwell ends on receipt of the next RX frame)
     does not free the M3 frame stored in pasn->frame, and a delayed M2
     TX status arriving after M3 RX is ignored.
   - In pr_pasn_auth_tx_status() (with PR_PASN_M3_MAX_RETRY = 1), when
     wpa_pasn_auth_tx_status() returns 1 for M3:
     * If !pasn->frame, PASN already completed and handed off to ranging
       on the negotiated channel; ignore the duplicate/late Auth3 TX
       status and return 0.
     * On the first unacked M3 (!acked && pasn_m3_retries < 1),
       increment pasn_m3_retries and return 2 while preserving
       pasn->frame, so wpas_pr_pasn_auth_tx_status() schedules
       wpas_pr_pasn_auth_retry_timeout() to retransmit M3 after 100ms
       from eloop timer context (with 100ms wait matching initial M3).
       If send_mgmt fails in timer context, pr_pasn_auth_retransmit()
       frees pasn->frame and completes optimistically via
       pr_pasn_complete_auth3().
     * Otherwise (M3 ACKed, or the single retry was exhausted), complete
       PASN via pr_pasn_complete_auth3() without gating on acked, log
       when completing optimistically on an unacked M3, and free
       pasn->frame.
   - Extend the trans_seq guard in pr_pasn_auth_retransmit() to allow
     WLAN_AUTH_TR_SEQ_PASN_AUTH3 alongside AUTH1 and AUTH2.

2. Responder 1000ms M3 wait timer, role/state helper, and timer symmetry:
   - Keep the 3000ms recovery ROC and timeout on unacked Responder M2
     (ret == 4) and duplicate-M1 M2 replay.
   - Add PR_PASN_M3_WAIT_TIMEOUT_MS (1000ms) and
     wpas_pr_pasn_m3_wait_timeout() in pr_supplicant.c, armed via
     eloop_register_timeout(0, PR_PASN_M3_WAIT_TIMEOUT_MS * 1000, ...)
     on ACKed Responder M2 TX status (ret == 0, trans_seq == AUTH2) so a
     lost M3 after an ACKed M2 tears down in 1s rather than waiting for
     the 10s failsafe timeout.
   - Add wpas_pr_cancel_pasn_timeouts() cancelling wpas_pr_pasn_timeout,
     wpas_pr_pasn_m3_wait_timeout, and wpas_pr_pasn_auth_retry_timeout
     across all completion, timeout, and teardown paths (and cancel
     wpas_pr_pasn_m3_wait_timeout when duplicate/new M1 refreshes the
     Responder timer) so no stale timer can fire twice.
   - Add pr_is_pasn_responder_waiting_m3(pr, addr) checking
     dev->pasn_role == PR_ROLE_PASN_RESPONDER, dev->pasn != NULL, and
     dev->pasn->trans_seq == WLAN_AUTH_TR_SEQ_PASN_AUTH2 (waiting for M3),
     and use it in wpas_pr_pasn_auth_tx_status() and wpas_pr_pasn_auth_rx().

3. Return code semantics across Auth1, Auth2, and Auth3 handlers:
   - Make pr_pasn_handle_auth_1(), pr_pasn_handle_auth_2(), and
     pr_pasn_handle_auth_3() return 1 only when the frame is accepted and
     a valid state transition occurs (M2 sent/replayed, M3 sent, or M3
     accepted and ranging params requested), 0 when ignored, and < 0 on
     error.
   - Prevent wpas_pr_pasn_auth_rx() from prematurely cancelling active M1
     or M3 retry timers on the initiator upon receiving late duplicate or
     out-of-order Auth2 frames.
   - Prevent wpas_pr_pasn_auth_rx() from resetting the responder's 1s M3
     wait timer or 10s failsafe timeout when an incoming duplicate Auth1 is
     ignored or its retransmission fails.
   - Add clean error cleanup (cancel ROC, abort responder, notify
     termination) in wpas_pr_check_pd_wdev_create() if interface creation
     fails.

Signed-off-by: Emily Xia <xiayucheng at google.com>
Signed-off-by: Sunil Ravi <sunilravi at google.com>
---
Changes in v2:
- In wpas_pr_cancel_remain_on_channel_cb(), verify that the responder is
  still waiting for M3 (pr_is_pasn_responder_waiting_m3) before restarting
  the next ROC chunk; otherwise, cancel all active PASN timeouts and
  abort cleanly.
- Adapt wpas_pr_schedule_responder_roc() call to match the 2-parameter
  signature from Patch 2.

 src/common/proximity_ranging.c | 191 +++++++++++++++++++++++++--------
 src/common/proximity_ranging.h |   2 +
 wpa_supplicant/pr_supplicant.c | 126 ++++++++++++++++------
 3 files changed, 245 insertions(+), 74 deletions(-)

diff --git a/src/common/proximity_ranging.c b/src/common/proximity_ranging.c
index 4d1058032..44b45349e 100644
--- a/src/common/proximity_ranging.c
+++ b/src/common/proximity_ranging.c
@@ -145,6 +145,7 @@ void pr_reset_pasn_state(struct pr_data *pr, const u8 *addr)
 		if (dev) {
 			dev->pasn_role = PR_ROLE_IDLE;
 #ifdef CONFIG_PASN
+			dev->pasn_m3_retries = 0;
 			if (dev->pasn)
 				wpa_pasn_reset(dev->pasn);
 #endif /* CONFIG_PASN */
@@ -155,6 +156,7 @@ void pr_reset_pasn_state(struct pr_data *pr, const u8 *addr)
 	dl_list_for_each(dev, &pr->devices, struct pr_device, list) {
 		dev->pasn_role = PR_ROLE_IDLE;
 #ifdef CONFIG_PASN
+		dev->pasn_m3_retries = 0;
 		if (dev->pasn)
 			wpa_pasn_reset(dev->pasn);
 #endif /* CONFIG_PASN */
@@ -1820,6 +1822,7 @@ static int pr_pasn_initialize(struct pr_data *pr, struct pr_device *dev,
 	struct wpabuf *rsnxe;
 	struct pasn_data *pasn;
 
+	dev->pasn_m3_retries = 0;
 	if (dev->pasn) {
 		wpa_pasn_reset(dev->pasn);
 	} else {
@@ -2131,6 +2134,48 @@ out:
 }
 
 
+#define PR_PASN_M3_MAX_RETRY 1
+
+static void pr_pasn_complete_auth3(struct pr_data *pr, struct pr_device *dev)
+{
+	u8 self_format_bw, peer_format_bw;
+
+	if (pr->cfg->pasn_result)
+		pr->cfg->pasn_result(pr->cfg->cb_ctx, dev->ranging_role,
+				     dev->protocol_type, dev->final_op_class,
+				     dev->final_op_channel, pr->cfg->country);
+
+	if (pr->cfg->get_ranging_params) {
+		if (dev->protocol_type & PR_EDCA_BASED_RANGING) {
+			self_format_bw = pr->cfg->edca_format_and_bw;
+			peer_format_bw = dev->edca_caps.edca_hw_caps &
+				EDCA_FORMAT_AND_BW_MASK;
+		} else if ((dev->protocol_type &
+			    PR_NTB_SECURE_LTF_BASED_RANGING) ||
+			   (dev->protocol_type & PR_NTB_OPEN_BASED_RANGING)) {
+			self_format_bw = pr->cfg->ntb_format_and_bw;
+			peer_format_bw = dev->ntb_caps.ntb_hw_caps &
+				NTB_FORMAT_AND_BW_MASK;
+		} else {
+			wpa_printf(MSG_INFO,
+				   "PR PASN: Invalid protocol type: %u",
+				   dev->protocol_type);
+			return;
+		}
+
+		pr->cfg->get_ranging_params(pr->cfg->cb_ctx, pr->cfg->dev_addr,
+					    dev->pr_device_addr,
+					    dev->ranging_role,
+					    dev->protocol_type,
+					    dev->final_op_class,
+					    dev->final_op_channel,
+					    self_format_bw,
+					    peer_format_bw);
+		pr_flush(pr);
+	}
+}
+
+
 int pr_pasn_auth_tx_status(struct pr_data *pr, const u8 *data, size_t data_len,
 			   bool acked)
 {
@@ -2139,7 +2184,6 @@ int pr_pasn_auth_tx_status(struct pr_data *pr, const u8 *data, size_t data_len,
 	struct pasn_data *pasn;
 	const struct ieee80211_mgmt *mgmt =
 		(const struct ieee80211_mgmt *) data;
-	u8 self_format_bw, peer_format_bw;
 
 	if (!pr || !data ||
 	    data_len < offsetof(struct ieee80211_mgmt, u.auth.variable))
@@ -2158,6 +2202,19 @@ int pr_pasn_auth_tx_status(struct pr_data *pr, const u8 *data, size_t data_len,
 	wpa_printf(MSG_DEBUG, "PR PASN: TX status from " MACSTR " ack=%d",
 		   MAC2STR(mgmt->da), acked);
 
+	/*
+	 * Ignore stale TX status when trans_seq has already advanced
+	 * so we do not free a newer pasn->frame (such as M3 queued during M2 RX).
+	 */
+	if (le_to_host16(mgmt->u.auth.auth_transaction) != pasn->trans_seq) {
+		wpa_printf(MSG_DEBUG,
+			   "PR PASN: Ignore stale Auth%u TX status "
+			   "(trans_seq=%u) from " MACSTR,
+			   le_to_host16(mgmt->u.auth.auth_transaction),
+			   pasn->trans_seq, MAC2STR(mgmt->da));
+		return 0;
+	}
+
 	ret = wpa_pasn_auth_tx_status(pasn, data, data_len, acked);
 
 	/*
@@ -2181,41 +2238,40 @@ int pr_pasn_auth_tx_status(struct pr_data *pr, const u8 *data, size_t data_len,
 	 */
 	if (ret == 0 && pasn->trans_seq == WLAN_AUTH_TR_SEQ_PASN_AUTH2)
 		return acked ? 0 : 4;
-	if (ret == 1 && acked && pr->cfg->pasn_result)
-		pr->cfg->pasn_result(pr->cfg->cb_ctx, dev->ranging_role,
-				     dev->protocol_type, dev->final_op_class,
-				     dev->final_op_channel, pr->cfg->country);
 
-	if (ret == 1 && acked && pr->cfg->get_ranging_params) {
-		if (dev->protocol_type & PR_EDCA_BASED_RANGING) {
-			self_format_bw = pr->cfg->edca_format_and_bw;
-			peer_format_bw = dev->edca_caps.edca_hw_caps &
-				EDCA_FORMAT_AND_BW_MASK;
-		} else if ((dev->protocol_type &
-			    PR_NTB_SECURE_LTF_BASED_RANGING) ||
-			   (dev->protocol_type & PR_NTB_OPEN_BASED_RANGING)) {
-			self_format_bw = pr->cfg->ntb_format_and_bw;
-			peer_format_bw = dev->ntb_caps.ntb_hw_caps &
-				NTB_FORMAT_AND_BW_MASK;
-		} else {
-			wpa_printf(MSG_INFO,
-				   "PR PASN: Invalid protocol type: %u",
-				   dev->protocol_type);
-			goto out;
+	if (ret == 1) {
+		/*
+		 * pasn->frame holds the serialized Auth3 buffer queued on M2
+		 * RX and is freed below once PASN completes and hands off to
+		 * ranging on the negotiated channel. A NULL frame here means
+		 * PASN already completed and this is a duplicate/late Auth3
+		 * TX status.
+		 */
+		if (!pasn->frame) {
+			wpa_printf(MSG_DEBUG,
+				   "PR PASN: Auth3 already completed for "
+				   MACSTR ", ignore TX status",
+				   MAC2STR(mgmt->da));
+			return 0;
 		}
-
-		pr->cfg->get_ranging_params(pr->cfg->cb_ctx, pr->cfg->dev_addr,
-					    dev->pr_device_addr,
-					    dev->ranging_role,
-					    dev->protocol_type,
-					    dev->final_op_class,
-					    dev->final_op_channel,
-					    self_format_bw,
-					    peer_format_bw);
-		pr_flush(pr);
+		if (!acked && dev->pasn_m3_retries < PR_PASN_M3_MAX_RETRY) {
+			dev->pasn_m3_retries++;
+			wpa_printf(MSG_DEBUG,
+				   "PR PASN: Auth3 not acked by " MACSTR
+				   ", schedule retry (%u/%u)",
+				   MAC2STR(mgmt->da), dev->pasn_m3_retries,
+				   PR_PASN_M3_MAX_RETRY);
+			return 2;
+		}
+		if (!acked) {
+			wpa_printf(MSG_DEBUG,
+				   "PR PASN: Assume Auth3 TX was received by "
+				   MACSTR " even though ACK was not reported",
+				   MAC2STR(mgmt->da));
+		}
+		pr_pasn_complete_auth3(pr, dev);
 	}
 
-out:
 	wpabuf_free(pasn->frame);
 	pasn->frame = NULL;
 
@@ -2227,6 +2283,8 @@ int pr_pasn_auth_retransmit(struct pr_data *pr, const u8 *addr)
 {
 	struct pr_device *dev;
 	struct pasn_data *pasn;
+	unsigned int wait_time;
+	int ret;
 
 	dev = pr_get_device(pr, addr);
 	if (!dev || !dev->pasn || !dev->pasn->frame)
@@ -2234,13 +2292,27 @@ int pr_pasn_auth_retransmit(struct pr_data *pr, const u8 *addr)
 
 	pasn = dev->pasn;
 	if (pasn->trans_seq != WLAN_AUTH_TR_SEQ_PASN_AUTH1 &&
-	    pasn->trans_seq != WLAN_AUTH_TR_SEQ_PASN_AUTH2)
+	    pasn->trans_seq != WLAN_AUTH_TR_SEQ_PASN_AUTH2 &&
+	    pasn->trans_seq != WLAN_AUTH_TR_SEQ_PASN_AUTH3)
 		return -1;
 
-	wpa_printf(MSG_DEBUG, "PR PASN: retransmit Authentication frame %u to "
-		   MACSTR, pasn->trans_seq, MAC2STR(addr));
-	return pasn->send_mgmt(pasn->cb_ctx, wpabuf_head(pasn->frame),
-			       wpabuf_len(pasn->frame), 0, pasn->freq, 1000);
+	wait_time = pasn->trans_seq == WLAN_AUTH_TR_SEQ_PASN_AUTH3 ? 100 : 1000;
+	wpa_printf(MSG_DEBUG,
+		   "PR PASN: retransmit Authentication frame %u to " MACSTR,
+		   pasn->trans_seq, MAC2STR(addr));
+	ret = pasn->send_mgmt(pasn->cb_ctx, wpabuf_head(pasn->frame),
+			      wpabuf_len(pasn->frame), 0, pasn->freq,
+			      wait_time);
+	if (ret < 0 && pasn->trans_seq == WLAN_AUTH_TR_SEQ_PASN_AUTH3) {
+		wpa_printf(MSG_DEBUG,
+			   "PR PASN: Auth3 retry TX failed, assume received by "
+			   MACSTR, MAC2STR(addr));
+		wpabuf_free(pasn->frame);
+		pasn->frame = NULL;
+		pr_pasn_complete_auth3(pr, dev);
+	}
+
+	return ret;
 }
 
 
@@ -2529,6 +2601,12 @@ fail:
 }
 
 
+/*
+ * Returns:
+ *   1 if Auth1 is accepted and Auth2 is transmitted (initial or replayed)
+ *   0 if the frame is ignored
+ * < 0 on error
+ */
 static int pr_pasn_handle_auth_1(struct pr_data *pr, struct pr_device *dev,
 				 const struct ieee80211_mgmt *mgmt, size_t len,
 				 int freq)
@@ -2575,8 +2653,9 @@ static int pr_pasn_handle_auth_1(struct pr_data *pr, struct pr_device *dev,
 				wpa_printf(MSG_DEBUG,
 					   "PR PASN: Retransmit Auth2 to "
 					   MACSTR, MAC2STR(mgmt->sa));
-				pr_pasn_auth_retransmit(pr,
-							dev->pr_device_addr);
+				if (pr_pasn_auth_retransmit(
+					    pr, dev->pr_device_addr) == 0)
+					return 1;
 				return 0;
 			}
 			wpa_printf(MSG_DEBUG,
@@ -2646,7 +2725,7 @@ static int pr_pasn_handle_auth_1(struct pr_data *pr, struct pr_device *dev,
 		pr->cfg->negotiation_started(pr->cfg->cb_ctx, mgmt->sa,
 					     dev->ranging_role,
 					     dev->protocol_type);
-	ret = 0;
+	ret = 1;
 
 fail:
 	wpabuf_free(dev->ranging_wrapper);
@@ -2655,6 +2734,12 @@ fail:
 }
 
 
+/*
+ * Returns:
+ *   1 if Auth2 is accepted and Auth3 is transmitted
+ *   0 if the frame is ignored
+ * < 0 on error
+ */
 static int pr_pasn_handle_auth_2(struct pr_data *pr, struct pr_device *dev,
 				 const struct ieee80211_mgmt *mgmt, size_t len)
 {
@@ -2714,7 +2799,8 @@ static int pr_pasn_handle_auth_2(struct pr_data *pr, struct pr_device *dev,
 		wpa_printf(MSG_INFO, "PR PASN: Key configuration failed");
 		goto fail;
 	}
-	ret = 0;
+	pr->auth_retry_count = 0;
+	ret = 1;
 
 fail:
 	wpabuf_free(dev->ranging_wrapper);
@@ -2722,7 +2808,12 @@ fail:
 	return ret;
 }
 
-
+/*
+ * Returns:
+ *   1 if Auth3 is accepted and ranging parameters are requested
+ *   0 if the frame is ignored
+ * < 0 on error
+ */
 static int pr_pasn_handle_auth_3(struct pr_data *pr, struct pr_device *dev,
 				 const struct ieee80211_mgmt *mgmt, size_t len)
 {
@@ -2793,7 +2884,7 @@ static int pr_pasn_handle_auth_3(struct pr_data *pr, struct pr_device *dev,
 					    self_format_bw,
 					    peer_format_bw);
 	pr_flush(pr);
-	return 0;
+	return 1;
 
 fail:
 	/* Clear the keys as M3 processing failed */
@@ -2846,4 +2937,18 @@ int pr_pasn_auth_rx(struct pr_data *pr, const struct ieee80211_mgmt *mgmt,
 	return -1;
 }
 
+
+bool pr_is_pasn_responder_waiting_m3(struct pr_data *pr, const u8 *addr)
+{
+	struct pr_device *dev;
+
+	if (!pr || !addr)
+		return false;
+
+	dev = pr_get_device(pr, addr);
+	return dev && dev->pasn_role == PR_ROLE_PASN_RESPONDER &&
+		dev->pasn &&
+		dev->pasn->trans_seq == WLAN_AUTH_TR_SEQ_PASN_AUTH2;
+}
+
 #endif /* CONFIG_PASN */
diff --git a/src/common/proximity_ranging.h b/src/common/proximity_ranging.h
index 1fb607ff3..9310d1b9d 100644
--- a/src/common/proximity_ranging.h
+++ b/src/common/proximity_ranging.h
@@ -430,6 +430,7 @@ struct pr_device {
 	struct pasn_data *pasn;
 	struct wpabuf *ranging_wrapper;
 	enum pr_pasn_role pasn_role;
+	u8 pasn_m3_retries;
 #endif /* CONFIG_PASN */
 
 	u8 ranging_role;
@@ -683,5 +684,6 @@ int pr_pasn_auth_tx_status(struct pr_data *pr, const u8 *data, size_t data_len,
 int pr_pasn_auth_retransmit(struct pr_data *pr, const u8 *addr);
 int pr_pasn_auth_rx(struct pr_data *pr, const struct ieee80211_mgmt *mgmt,
 		    size_t len, int freq);
+bool pr_is_pasn_responder_waiting_m3(struct pr_data *pr, const u8 *addr);
 
 #endif /* PROXIMITY_RANGING_H */
diff --git a/wpa_supplicant/pr_supplicant.c b/wpa_supplicant/pr_supplicant.c
index 0d3bab045..2e9df5d30 100644
--- a/wpa_supplicant/pr_supplicant.c
+++ b/wpa_supplicant/pr_supplicant.c
@@ -22,6 +22,7 @@
 
 #ifdef CONFIG_PASN
 static void wpas_pr_pasn_timeout(void *eloop_ctx, void *timeout_ctx);
+static void wpas_pr_pasn_m3_wait_timeout(void *eloop_ctx, void *timeout_ctx);
 static void wpas_pr_pasn_roc_total_timeout(void *eloop_ctx, void *timeout_ctx);
 static void wpas_pr_pasn_auth_work_done(struct wpa_supplicant *wpa_s);
 static void wpas_pr_pasn_auth_retry_timeout(void *eloop_ctx, void *timeout_ctx);
@@ -33,6 +34,8 @@ static void wpas_pr_cancel_roc(struct wpa_supplicant *wpa_s);
 #define PR_PASN_M2_DWELL_MS 1000
 /* Responder ROC duration (ms) on Auth2 TX failure to catch retry Auth1 */
 #define PR_PASN_M2_RETRY_ROC_DURATION_MS 3000
+/* Responder wait timeout (ms) for M3 after Auth2 TX is ACKed */
+#define PR_PASN_M3_WAIT_TIMEOUT_MS 1000
 /* Initiator PASN authentication timeout (s) */
 #define PR_PASN_AUTH_TIMEOUT           10
 /* Retry interval (ms) for unacked PASN Authentication frame 1 */
@@ -41,6 +44,13 @@ static void wpas_pr_cancel_roc(struct wpa_supplicant *wpa_s);
 #define PR_PASN_AUTH1_ACKED_RETRY_INTERVAL_MS 512
 /* Maximum number of PASN authentication frame 1 retransmissions */
 #define PR_PASN_MAX_AUTH_RETRIES 3
+
+static void wpas_pr_cancel_pasn_timeouts(struct wpa_supplicant *wpa_s)
+{
+	eloop_cancel_timeout(wpas_pr_pasn_timeout, wpa_s, NULL);
+	eloop_cancel_timeout(wpas_pr_pasn_m3_wait_timeout, wpa_s, NULL);
+	eloop_cancel_timeout(wpas_pr_pasn_auth_retry_timeout, wpa_s, NULL);
+}
 #endif /* CONFIG_PASN */
 
 
@@ -586,8 +596,7 @@ static void wpas_pr_ranging_params(void *ctx, const u8 *dev_addr,
 	 * prematurely stop the PD wdev while ranging is in progress.
 	 * Cleanup happens on COMPLETE event or session timeout.
 	 */
-	eloop_cancel_timeout(wpas_pr_pasn_timeout, wpa_s, NULL);
-	eloop_cancel_timeout(wpas_pr_pasn_auth_retry_timeout, wpa_s, NULL);
+	wpas_pr_cancel_pasn_timeouts(wpa_s);
 	wpas_pr_pasn_auth_work_done(wpa_s);
 	wpas_pr_cancel_roc(wpa_s);
 
@@ -867,9 +876,8 @@ void wpas_pr_deinit(struct wpa_supplicant *wpa_s)
 	}
 
 #ifdef CONFIG_PASN
-	eloop_cancel_timeout(wpas_pr_pasn_timeout, wpa_s, NULL);
+	wpas_pr_cancel_pasn_timeouts(wpa_s);
 	eloop_cancel_timeout(wpas_pr_pasn_roc_total_timeout, wpa_s, NULL);
-	eloop_cancel_timeout(wpas_pr_pasn_auth_retry_timeout, wpa_s, NULL);
 #endif /* CONFIG_PASN */
 	eloop_cancel_timeout(wpas_pr_ranging_session_timeout, wpa_s, NULL);
 }
@@ -1220,7 +1228,7 @@ static void wpas_pr_pasn_roc_start_cb(struct wpa_radio_work *work, int deinit)
 		wpa_printf(MSG_ERROR,
 			   "PR PASN: Failed to start ROC for responder");
 		wpas_pr_pasn_roc_work_done(wpa_s);
-		eloop_cancel_timeout(wpas_pr_pasn_timeout, wpa_s, NULL);
+		wpas_pr_cancel_pasn_timeouts(wpa_s);
 		wpas_pr_pd_stop(wpa_s);
 		wpas_pr_pasn_abort_responder(wpa_s);
 		wpas_notify_pr_ranging_terminated(wpa_s,
@@ -1264,7 +1272,7 @@ static void wpas_pr_schedule_responder_roc(struct wpa_supplicant *wpa_s,
 	return;
 
 fail:
-	eloop_cancel_timeout(wpas_pr_pasn_timeout, wpa_s, NULL);
+	wpas_pr_cancel_pasn_timeouts(wpa_s);
 	wpas_pr_pd_stop(wpa_s);
 	wpas_pr_pasn_abort_responder(wpa_s);
 	wpas_notify_pr_ranging_terminated(wpa_s, PR_SESSION_END_NEG_FAILED);
@@ -1294,10 +1302,21 @@ void wpas_pr_cancel_remain_on_channel_cb(struct wpa_supplicant *wpa_s,
 	}
 
 	if (wpa_s->global->pr && wpa_s->global->pr->pr_pasn_params) {
+		if (pr_is_pasn_responder_waiting_m3(
+			    wpa_s->global->pr,
+			    wpa_s->global->pr->pr_pasn_params->peer_addr)) {
+			wpa_printf(MSG_DEBUG,
+				   "PR PASN: Responder M2 retry ROC chunk expired, "
+				   "restarting next chunk");
+			wpas_pr_schedule_responder_roc(wpa_s, freq);
+			return;
+		}
+
 		wpa_printf(MSG_DEBUG,
-			   "PR PASN: Responder M2 retry ROC chunk expired, "
-			   "restarting next chunk");
-		wpas_pr_schedule_responder_roc(wpa_s, freq);
+			   "PR PASN: Responder M2 retry ROC expired, "
+			   "aborting PASN");
+		wpas_pr_cancel_pasn_timeouts(wpa_s);
+		wpas_pr_pasn_timeout(wpa_s, NULL);
 		return;
 	}
 
@@ -1310,7 +1329,7 @@ static void wpas_pr_pasn_timeout(void *eloop_ctx, void *timeout_ctx)
 {
 	struct wpa_supplicant *wpa_s = eloop_ctx;
 
-	eloop_cancel_timeout(wpas_pr_pasn_auth_retry_timeout, wpa_s, NULL);
+	wpas_pr_cancel_pasn_timeouts(wpa_s);
 	wpas_pr_cancel_roc(wpa_s);
 
 	if (wpa_s->pr_pasn_auth_work) {
@@ -1333,6 +1352,16 @@ static void wpas_pr_pasn_timeout(void *eloop_ctx, void *timeout_ctx)
 }
 
 
+static void wpas_pr_pasn_m3_wait_timeout(void *eloop_ctx, void *timeout_ctx)
+{
+	struct wpa_supplicant *wpa_s = eloop_ctx;
+
+	wpa_printf(MSG_DEBUG,
+		   "PR PASN: M3 wait timeout expired after M2 TX");
+	wpas_pr_pasn_timeout(wpa_s, timeout_ctx);
+}
+
+
 static void wpas_pr_pasn_auth_start_cb(struct wpa_radio_work *work, int deinit)
 {
 	int ret;
@@ -1343,9 +1372,7 @@ static void wpas_pr_pasn_auth_start_cb(struct wpa_radio_work *work, int deinit)
 
 	if (deinit) {
 		if (work->started) {
-			eloop_cancel_timeout(wpas_pr_pasn_timeout, wpa_s, NULL);
-			eloop_cancel_timeout(wpas_pr_pasn_auth_retry_timeout,
-					     wpa_s, NULL);
+			wpas_pr_cancel_pasn_timeouts(wpa_s);
 			wpa_s->pr_pasn_auth_work = NULL;
 		}
 
@@ -1449,6 +1476,8 @@ int wpas_pr_initiate_pasn_auth(struct wpa_supplicant *wpa_s,
 		    pr->pr_pasn_params->continuous_ranging_session_time > 0)
 			roc_time_ms = pr->pr_pasn_params->continuous_ranging_session_time;
 
+		eloop_cancel_timeout(wpas_pr_pasn_roc_total_timeout, wpa_s,
+				     NULL);
 		eloop_register_timeout(roc_time_ms / 1000,
 				       (roc_time_ms % 1000) * 1000,
 				       wpas_pr_pasn_roc_total_timeout,
@@ -1750,6 +1779,7 @@ int wpas_pr_pasn_auth_tx_status(struct wpa_supplicant *wpa_s, const u8 *data,
 {
 	struct pr_data *pr = wpa_s->global->pr;
 	const struct ieee80211_mgmt *mgmt;
+	u16 auth_transaction;
 	int ret;
 
 	if (!wpa_s->pr_pasn_auth_work && is_zero_ether_addr(wpa_s->pd_addr) &&
@@ -1764,11 +1794,13 @@ int wpas_pr_pasn_auth_tx_status(struct wpa_supplicant *wpa_s, const u8 *data,
 	if (!ether_addr_equal(mgmt->da, pr->pr_pasn_params->peer_addr))
 		return -1;
 
+	auth_transaction = le_to_host16(mgmt->u.auth.auth_transaction);
 	ret = pr_pasn_auth_tx_status(pr, data, data_len, acked);
 	if (ret == 2) {
 		/*
-		 * M1 was not ACKed (peer may not be on-channel yet). Keep
-		 * retrying every 100ms across the 10s PASN timeout window.
+		 * Unacked M1 (retried across the 10s PASN timeout window) or
+		 * unacked M3 (retried once): schedule retransmission after
+		 * 100ms from eloop timer context.
 		 */
 		eloop_cancel_timeout(wpas_pr_pasn_auth_retry_timeout, wpa_s,
 				     NULL);
@@ -1814,10 +1846,13 @@ int wpas_pr_pasn_auth_tx_status(struct wpa_supplicant *wpa_s, const u8 *data,
 		 * no retry M1 or M3 arrives, similar to P2P GO Negotiation
 		 * Response recovery.
 		 */
-		if (pr && pr->pr_pasn_params) {
+		if (pr && pr->pr_pasn_params &&
+		    pr_is_pasn_responder_waiting_m3(pr, mgmt->da)) {
 			wpas_pr_cancel_roc(wpa_s);
 			wpas_pr_schedule_responder_roc(
 				wpa_s, pr->pr_pasn_params->freq);
+			eloop_cancel_timeout(wpas_pr_pasn_m3_wait_timeout,
+					     wpa_s, NULL);
 			eloop_cancel_timeout(wpas_pr_pasn_timeout, wpa_s,
 					     NULL);
 			eloop_register_timeout(
@@ -1826,6 +1861,21 @@ int wpas_pr_pasn_auth_tx_status(struct wpa_supplicant *wpa_s, const u8 *data,
 		}
 		return 0;
 	}
+	if (ret == 0 && auth_transaction == WLAN_AUTH_TR_SEQ_PASN_AUTH2 &&
+	    acked && pr_is_pasn_responder_waiting_m3(pr, mgmt->da)) {
+		/*
+		 * M2 was ACKed by the initiator. Arm a 1s wait timer for M3
+		 * so that if M3 is lost over the air, the responder tears down
+		 * in 1s rather than waiting for the 10s failsafe timeout.
+		 */
+		wpa_printf(MSG_DEBUG,
+			   "PR PASN: M2 ACKed, wait %d ms for M3",
+			   PR_PASN_M3_WAIT_TIMEOUT_MS);
+		eloop_cancel_timeout(wpas_pr_pasn_m3_wait_timeout, wpa_s, NULL);
+		eloop_register_timeout(0, PR_PASN_M3_WAIT_TIMEOUT_MS * 1000,
+				       wpas_pr_pasn_m3_wait_timeout, wpa_s,
+				       NULL);
+	}
 
 	return ret < 0 ? -1 : 0;
 }
@@ -1938,8 +1988,13 @@ int wpas_pr_pasn_auth_rx(struct wpa_supplicant *wpa_s,
 	 */
 	if (wpa_s->pr_responder_mode) {
 		if (auth_transaction == WLAN_AUTH_TR_SEQ_PASN_AUTH1) {
-			if (wpas_pr_check_pd_wdev_create(wpa_s) < 0)
+			if (wpas_pr_check_pd_wdev_create(wpa_s) < 0) {
+				wpas_pr_cancel_roc(wpa_s);
+				wpas_pr_pasn_abort_responder(wpa_s);
+				wpas_notify_pr_ranging_terminated(
+					wpa_s, PR_SESSION_END_NEG_FAILED);
 				return -1;
+			}
 
 			/*
 			 * Cancel the total-budget timer first so it does not
@@ -1967,7 +2022,7 @@ int wpas_pr_pasn_auth_rx(struct wpa_supplicant *wpa_s,
 			wpa_printf(MSG_DEBUG,
 				   "PR PASN: M1 processed, proceeding with PASN");
 		}
-	} else if (!wpa_s->pr_pasn_auth_work) {
+	} else if (pr_is_pasn_responder_waiting_m3(pr, mgmt->sa)) {
 		/*
 		 * Cancel any active post-M2-NACK recovery ROC on the responder
 		 * when duplicate M1 or M3 arrives from the peer.
@@ -1978,39 +2033,49 @@ int wpas_pr_pasn_auth_rx(struct wpa_supplicant *wpa_s,
 	ret = pr_pasn_auth_rx(pr, mgmt, len, freq);
 
 	if (ret < 0) {
-		eloop_cancel_timeout(wpas_pr_pasn_timeout, wpa_s, NULL);
-		eloop_cancel_timeout(wpas_pr_pasn_auth_retry_timeout, wpa_s,
-				     NULL);
+		wpas_pr_cancel_pasn_timeouts(wpa_s);
 		wpas_pr_pasn_auth_work_done(wpa_s);
 		wpas_pr_cancel_roc(wpa_s);
 		wpas_pr_pd_stop(wpa_s);
 		wpas_pr_clear_ranging_params(wpa_s->global->pr);
 		wpas_notify_pr_ranging_terminated(wpa_s,
 						  PR_SESSION_END_NEG_FAILED);
-	} else if (len >= offsetof(struct ieee80211_mgmt, u.auth.variable)) {
+		return ret;
+	}
+
+	if (ret == 1) {
 		u16 auth_transaction;
 
 		auth_transaction = le_to_host16(mgmt->u.auth.auth_transaction);
-		if (auth_transaction == WLAN_AUTH_TR_SEQ_PASN_AUTH1 &&
-		    !wpa_s->pr_pasn_auth_work) {
+		if (auth_transaction == WLAN_AUTH_TR_SEQ_PASN_AUTH1) {
 			/*
 			 * Arm or refresh the PASN negotiation timeout on the
-			 * responder so that if M2 TX fails or M3 never arrives,
-			 * the PD wdev and ranging params are cleaned up rather
-			 * than leaving the responder wedged.
+			 * responder when a valid Auth1 is accepted (initial M1
+			 * or duplicate M1 that replayed M2) so that if M2 TX
+			 * fails or M3 never arrives, the PD wdev and ranging
+			 * params are cleaned up rather than leaving the
+			 * responder wedged. Cancel any M3 wait timer from a
+			 * prior M2 attempt before replaying or restarting M2.
 			 */
+			eloop_cancel_timeout(wpas_pr_pasn_m3_wait_timeout,
+					     wpa_s, NULL);
 			eloop_cancel_timeout(wpas_pr_pasn_timeout, wpa_s, NULL);
 			eloop_register_timeout(PR_PASN_AUTH_TIMEOUT, 0,
 					       wpas_pr_pasn_timeout, wpa_s,
 					       NULL);
 		} else if (auth_transaction == WLAN_AUTH_TR_SEQ_PASN_AUTH2) {
-			pr->auth_retry_count = 0;
+			/*
+			 * Valid Auth2 accepted on the initiator at
+			 * trans_seq == AUTH1 and M3 sent; cancel any pending
+			 * M1 retry timer.
+			 */
 			eloop_cancel_timeout(wpas_pr_pasn_auth_retry_timeout,
 					     wpa_s, NULL);
 		}
+		/* Auth3 session timeout are canceled in wpas_pr_ranging_params() callback */
 	}
 
-	return ret;
+	return 0;
 }
 
 
@@ -2045,8 +2110,7 @@ void wpas_pr_abort_ranging(struct wpa_supplicant *wpa_s)
 	/* Stop PD wdev and cleanup all ranging resources */
 	wpas_pr_pd_stop(wpa_s);
 
-	eloop_cancel_timeout(wpas_pr_pasn_timeout, wpa_s, NULL);
-	eloop_cancel_timeout(wpas_pr_pasn_auth_retry_timeout, wpa_s, NULL);
+	wpas_pr_cancel_pasn_timeouts(wpa_s);
 	/* Free ranging params so a new session can be started */
 	wpas_pr_clear_ranging_params(pr);
 	wpas_notify_pr_ranging_terminated(wpa_s, PR_SESSION_END_USER_ABORT);
-- 
2.56.0.385.gac671c819a-goog




More information about the Hostap mailing list