[PATCH v2 2/3] PR: Recover from PASN M2 TX failure and handle duplicate M1
Emily Xia
xiayucheng at google.com
Fri Oct 30 02:23:14 PDT 2026
When the Proximity Ranging PASN responder receives Authentication frame
1 (M1), it cancels its remain-on-channel work and sends Authentication
frame 2 (M2). Previously, if M2 transmission failed over the air, the
responder had no retransmission path, rejected any retransmitted M1 from
the initiator, and lacked a negotiation timeout after M1 receipt,
leaving the responder wedged until a subsequent session timed out.
Adopt the P2P GO Negotiation Request/Response/Confirmation recovery
pattern for Proximity Ranging PASN:
1. In wpas_pr_pasn_send_mgmt(), apply a 1000ms off-channel dwell
(PR_PASN_M2_DWELL_MS, NL80211_ATTR_DURATION) when the PR responder
transmits PASN Auth2 (M2) while keeping wait = 0 in the generic PASN
responder for AP/P2P/NAN, so driver/firmware MAC retries complete
without truncation and the radio remains on channel to receive M3 or
a fallback M1 retry after initial ROC is cancelled.
2. Route EVENT_TX_STATUS for PR PASN frames to wpas_pr_pasn_auth_tx_status
for both initiator and responder roles, preserving the cached M2
frame (pasn->frame) on the responder across both ack == 0 and
ack == 1 until M3 arrives or the session times out, without
autonomous responder M2 retries.
3. When M2 transmission fails on the responder (ack == 0), schedule a
remain-on-channel (PR_PASN_M2_RETRY_ROC_DURATION_MS) so the
responder remains on the PASN channel to receive the initiator's
retransmitted M1 even if the driver ends the off-channel TX dwell
early upon exhausting MAC retries, using max_remain_on_chan as
per-chunk duration and restarting chunks across the retry window.
If the retry ROC expires without receiving a duplicate M1 or M3,
cleanly abort the PASN session, notify the framework, and tear down
the PD wdev and ranging parameters.
4. Retain the existing 100ms retry loop across the 10s timeout window
when M1 is unacknowledged (ret == 2); once M1 is acknowledged
(ret == 3), preserve the M1 buffer on the initiator and arm a 512ms
(500 TU) fallback retry timer bounded by PR_PASN_MAX_AUTH_RETRIES (3)
in case M2 is lost over the air, aborting PASN once retries expire
without M2 rather than waiting for the 10s timeout, and cancelling
the timer once M2 arrives.
5. On the responder, if a duplicate M1 with identical payload arrives
while waiting for M3 (trans_seq == 2), retransmit the cached M2
frame without regenerating ephemeral ECDH keys; if a new M1 arrives,
restart responder negotiation and refresh the 10s PASN timeout.
6. Arm wpas_pr_pasn_timeout on the responder upon receiving M1, free
cached M1/M2 buffers once M3 succeeds, and cancel the timeout when
ranging parameters are finalized or aborted.
7. In wpas_pr_pasn_auth_rx(), validate the active PR request, expected
peer (SA), local address (DA), auth algorithm, and auth transaction
before passing the frame to the common PASN processing code or
updating ROC/PD state, allowing invalid frames to be rejected early.
Signed-off-by: Emily Xia <xiayucheng at google.com>
Signed-off-by: Sunil Ravi <sunilravi at google.com>
---
Changes in v2:
- In responder M2 recovery, use max_remain_on_chan as per-chunk duration
and restart chunks across the 3000ms retry ROC window instead of passing
an explicit 3000ms duration to a single ROC request, matching DPP/P2P
ROC chunking and avoiding driver limits.
- Simplify timer registration using 0, PR_PASN_M2_RETRY_ROC_DURATION_MS * 1000.
- Update commit message to reflect chunked ROC restart behavior.
src/common/proximity_ranging.c | 73 +++++++++--
src/common/proximity_ranging.h | 3 +
wpa_supplicant/events.c | 7 +-
wpa_supplicant/pr_supplicant.c | 228 ++++++++++++++++++++++++++++++---
4 files changed, 281 insertions(+), 30 deletions(-)
diff --git a/src/common/proximity_ranging.c b/src/common/proximity_ranging.c
index e8b8d2078..4d1058032 100644
--- a/src/common/proximity_ranging.c
+++ b/src/common/proximity_ranging.c
@@ -1938,6 +1938,7 @@ static int pr_pasn_initialize(struct pr_data *pr, struct pr_device *dev,
pasn->cb_ctx = pr->cfg->cb_ctx;
pasn->send_mgmt = pr->cfg->pasn_send_mgmt;
pasn->freq = freq;
+ pr->auth_retry_count = 0;
return 0;
}
@@ -2140,15 +2141,17 @@ int pr_pasn_auth_tx_status(struct pr_data *pr, const u8 *data, size_t data_len,
(const struct ieee80211_mgmt *) data;
u8 self_format_bw, peer_format_bw;
- if (!pr)
+ if (!pr || !data ||
+ data_len < offsetof(struct ieee80211_mgmt, u.auth.variable))
return -1;
dev = pr_get_device(pr, mgmt->da);
if (!dev || !dev->pasn) {
wpa_printf(MSG_INFO,
- "PR PASN: Peer not found " MACSTR ", ignore the frame",
+ "PR PASN: Peer not found " MACSTR
+ ", ignore the frame",
MAC2STR(mgmt->da));
- return 0;
+ return -1;
}
pasn = dev->pasn;
@@ -2163,7 +2166,21 @@ int pr_pasn_auth_tx_status(struct pr_data *pr, const u8 *data, size_t data_len,
*/
if (ret == 2)
return ret;
-
+ /*
+ * Authentication frame 1 was acked, but M2 may be lost over the air.
+ * Preserve pasn->frame and ask caller to arm a fallback retry timer
+ * if M2 does not arrive, similar to P2P GO Negotiation Request.
+ */
+ if (ret == 0 && pasn->trans_seq == WLAN_AUTH_TR_SEQ_PASN_AUTH1 &&
+ acked)
+ return 3;
+ /*
+ * Preserve Authentication frame 2 until M3 arrives so the responder
+ * can replay the cached M2 if the initiator retransmits a duplicate
+ * M1, similar to P2P GO Negotiation Response.
+ */
+ 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,
@@ -2216,8 +2233,12 @@ int pr_pasn_auth_retransmit(struct pr_data *pr, const u8 *addr)
return -1;
pasn = dev->pasn;
- wpa_printf(MSG_DEBUG, "PR PASN: retransmit Authentication frame 1 to "
- MACSTR, MAC2STR(addr));
+ if (pasn->trans_seq != WLAN_AUTH_TR_SEQ_PASN_AUTH1 &&
+ pasn->trans_seq != WLAN_AUTH_TR_SEQ_PASN_AUTH2)
+ 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);
}
@@ -2536,12 +2557,36 @@ static int pr_pasn_handle_auth_1(struct pr_data *pr, struct pr_device *dev,
return 0;
}
- if (dev->pasn_role == PR_ROLE_PASN_RESPONDER && dev->pasn &&
- dev->pasn->trans_seq >= WLAN_AUTH_TR_SEQ_PASN_AUTH2) {
- wpa_printf(MSG_INFO,
- "PR PASN: Ignoring duplicate Auth1 (trans_seq=%u)",
- dev->pasn->trans_seq);
- return 0;
+ if (dev->pasn_role == PR_ROLE_PASN_RESPONDER && dev->pasn) {
+ if (dev->pasn->trans_seq >= WLAN_AUTH_TR_SEQ_PASN_AUTH3) {
+ wpa_printf(MSG_INFO,
+ "PR PASN: Ignore dup Auth1 (trans_seq=%u)",
+ dev->pasn->trans_seq);
+ return 0;
+ }
+ if (dev->pasn->trans_seq == WLAN_AUTH_TR_SEQ_PASN_AUTH2) {
+ if (dev->pasn->frame && dev->pasn->auth1 &&
+ len > IEEE80211_HDRLEN &&
+ wpabuf_len(dev->pasn->auth1) ==
+ len - IEEE80211_HDRLEN &&
+ os_memcmp(wpabuf_head(dev->pasn->auth1),
+ ((const u8 *) mgmt) + IEEE80211_HDRLEN,
+ len - IEEE80211_HDRLEN) == 0) {
+ wpa_printf(MSG_DEBUG,
+ "PR PASN: Retransmit Auth2 to "
+ MACSTR, MAC2STR(mgmt->sa));
+ pr_pasn_auth_retransmit(pr,
+ dev->pr_device_addr);
+ return 0;
+ }
+ wpa_printf(MSG_DEBUG,
+ "PR PASN: New Auth1 in state 2, restart");
+ if (!(dev->protocol_type & PR_EDCA_BASED_RANGING) &&
+ pr->cfg->clear_keys)
+ pr->cfg->clear_keys(pr->cfg->cb_ctx,
+ pr->cfg->dev_addr,
+ dev->pr_device_addr);
+ }
}
if (pr_process_pasn_ranging_wrapper(pr, dev, mgmt, len, 1)) {
@@ -2712,6 +2757,10 @@ static int pr_pasn_handle_auth_3(struct pr_data *pr, struct pr_device *dev,
goto fail;
}
dev->pasn->trans_seq = WLAN_AUTH_TR_SEQ_PASN_AUTH3;
+ wpabuf_free(dev->pasn->frame);
+ dev->pasn->frame = NULL;
+ wpabuf_free(dev->pasn->auth1);
+ dev->pasn->auth1 = NULL;
if (pr->cfg->pasn_result)
pr->cfg->pasn_result(pr->cfg->cb_ctx, dev->ranging_role,
diff --git a/src/common/proximity_ranging.h b/src/common/proximity_ranging.h
index 785659501..1fb607ff3 100644
--- a/src/common/proximity_ranging.h
+++ b/src/common/proximity_ranging.h
@@ -629,6 +629,9 @@ struct pr_data {
/* Set when final measurement result received; blocks further results */
bool ranging_final_received;
+
+ /* Number of PASN authentication frame retries in current session */
+ unsigned int auth_retry_count;
};
/* PR Device Identity Resolution Attribute parameters */
diff --git a/wpa_supplicant/events.c b/wpa_supplicant/events.c
index c0798db71..937969bde 100644
--- a/wpa_supplicant/events.c
+++ b/wpa_supplicant/events.c
@@ -7307,10 +7307,15 @@ void wpa_supplicant_event(void *ctx, enum wpa_event_type event,
break;
#endif /* CONFIG_P2P */
#ifdef CONFIG_PR
+ /*
+ * Do not gate on wpa_s->pr_pasn_auth_work here because it is
+ * only allocated on the PASN initiator and is NULL on the
+ * responder; non-PR frames are filtered inside
+ * wpas_pr_pasn_auth_tx_status().
+ */
if (data->tx_status.type == WLAN_FC_TYPE_MGMT &&
data->tx_status.stype == WLAN_FC_STYPE_AUTH &&
!wpa_s->pasn_auth_work &&
- wpa_s->pr_pasn_auth_work &&
wpas_pr_pasn_auth_tx_status(wpa_s,
data->tx_status.data,
data->tx_status.data_len,
diff --git a/wpa_supplicant/pr_supplicant.c b/wpa_supplicant/pr_supplicant.c
index a275f2937..0d3bab045 100644
--- a/wpa_supplicant/pr_supplicant.c
+++ b/wpa_supplicant/pr_supplicant.c
@@ -25,13 +25,22 @@ static void wpas_pr_pasn_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);
+static void wpas_pr_cancel_roc(struct wpa_supplicant *wpa_s);
/* Total listen window (ms) for the PASN responder ROC */
#define PR_PASN_RESPONDER_ROC_DURATION 10000
+/* Off-channel dwell time (ms) when PR responder transmits PASN Auth2 (M2) */
+#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
/* Initiator PASN authentication timeout (s) */
#define PR_PASN_AUTH_TIMEOUT 10
/* Retry interval (ms) for unacked PASN Authentication frame 1 */
#define PR_PASN_AUTH1_RETRY_INTERVAL_MS 100
+/* Fallback retry interval (ms) for acked PASN Auth1 when Auth2 is lost */
+#define PR_PASN_AUTH1_ACKED_RETRY_INTERVAL_MS 512
+/* Maximum number of PASN authentication frame 1 retransmissions */
+#define PR_PASN_MAX_AUTH_RETRIES 3
#endif /* CONFIG_PASN */
@@ -356,6 +365,27 @@ static int wpas_pr_pasn_send_mgmt(void *ctx, const u8 *data, size_t data_len,
unsigned int wait)
{
struct wpa_supplicant *wpa_s = ctx;
+#ifdef CONFIG_PASN
+ const struct ieee80211_mgmt *mgmt;
+ u16 fc;
+
+ /*
+ * Only the PR responder sends PASN Auth2 (M2). The generic PASN
+ * responder keeps wait = 0 for AP/P2P/NAN, so apply the 1000ms
+ * off-channel dwell here for PR M2 transmissions.
+ */
+ if (wait == 0 && data &&
+ data_len >= offsetof(struct ieee80211_mgmt, u.auth.variable)) {
+ mgmt = (const struct ieee80211_mgmt *) data;
+ fc = le_to_host16(mgmt->frame_control);
+ if (WLAN_FC_GET_TYPE(fc) == WLAN_FC_TYPE_MGMT &&
+ WLAN_FC_GET_STYPE(fc) == WLAN_FC_STYPE_AUTH &&
+ le_to_host16(mgmt->u.auth.auth_alg) == WLAN_AUTH_PASN &&
+ le_to_host16(mgmt->u.auth.auth_transaction) ==
+ WLAN_AUTH_TR_SEQ_PASN_AUTH2)
+ wait = PR_PASN_M2_DWELL_MS;
+ }
+#endif /* CONFIG_PASN */
return wpa_drv_send_mlme(wpa_s, data, data_len, noack, freq, wait);
}
@@ -399,6 +429,7 @@ static void wpas_pr_clear_ranging_params(struct pr_data *pr)
os_free(pr->pr_pasn_params);
pr->pr_pasn_params = NULL;
pr->ranging_final_received = false;
+ pr->auth_retry_count = 0;
}
}
@@ -556,7 +587,9 @@ static void wpas_pr_ranging_params(void *ctx, const u8 *dev_addr,
* 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_pasn_auth_work_done(wpa_s);
+ wpas_pr_cancel_roc(wpa_s);
/* Trigger ranging measurement after successful PASN authentication */
if (wpas_pr_trigger_ranging(wpa_s, peer_addr, freq, op_class,
@@ -1163,7 +1196,8 @@ static void wpas_pr_pasn_roc_start_cb(struct wpa_radio_work *work, int deinit)
* regardless of whether the work was started or not - the
* ROC will never fire now.
*/
- wpas_pr_pasn_abort_responder(wpa_s);
+ if (wpa_s->pr_responder_mode)
+ wpas_pr_pasn_abort_responder(wpa_s);
os_free(rwork);
work->ctx = NULL;
return;
@@ -1186,6 +1220,8 @@ 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_pd_stop(wpa_s);
wpas_pr_pasn_abort_responder(wpa_s);
wpas_notify_pr_ranging_terminated(wpa_s,
PR_SESSION_END_NEG_FAILED);
@@ -1216,6 +1252,8 @@ static void wpas_pr_schedule_responder_roc(struct wpa_supplicant *wpa_s,
!is_zero_ether_addr(wpa_s->pr_responder_src_addr))
os_memcpy(rwork->src_addr, wpa_s->pr_responder_src_addr,
ETH_ALEN);
+ else if (!is_zero_ether_addr(wpa_s->pd_addr))
+ os_memcpy(rwork->src_addr, wpa_s->pd_addr, ETH_ALEN);
if (!radio_add_work(wpa_s, freq, "pr-pasn-roc", 0,
wpas_pr_pasn_roc_start_cb, rwork)) {
@@ -1226,7 +1264,10 @@ 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_pd_stop(wpa_s);
wpas_pr_pasn_abort_responder(wpa_s);
+ wpas_notify_pr_ranging_terminated(wpa_s, PR_SESSION_END_NEG_FAILED);
}
@@ -1252,6 +1293,14 @@ void wpas_pr_cancel_remain_on_channel_cb(struct wpa_supplicant *wpa_s,
return;
}
+ if (wpa_s->global->pr && wpa_s->global->pr->pr_pasn_params) {
+ 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: ROC total timeout reached, responder done");
}
@@ -1262,6 +1311,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_roc(wpa_s);
if (wpa_s->pr_pasn_auth_work) {
wpas_pr_pasn_cancel_auth_work(wpa_s);
@@ -1699,13 +1749,27 @@ int wpas_pr_pasn_auth_tx_status(struct wpa_supplicant *wpa_s, const u8 *data,
size_t data_len, bool acked)
{
struct pr_data *pr = wpa_s->global->pr;
+ const struct ieee80211_mgmt *mgmt;
int ret;
- if (!wpa_s->pr_pasn_auth_work && is_zero_ether_addr(wpa_s->pd_addr))
+ if (!wpa_s->pr_pasn_auth_work && is_zero_ether_addr(wpa_s->pd_addr) &&
+ (!pr || !pr->pr_pasn_params))
+ return -1;
+
+ if (!pr || !pr->pr_pasn_params || !data ||
+ data_len < offsetof(struct ieee80211_mgmt, u.auth.variable))
+ return -1;
+
+ mgmt = (const struct ieee80211_mgmt *) data;
+ if (!ether_addr_equal(mgmt->da, pr->pr_pasn_params->peer_addr))
return -1;
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.
+ */
eloop_cancel_timeout(wpas_pr_pasn_auth_retry_timeout, wpa_s,
NULL);
eloop_register_timeout(0,
@@ -1714,8 +1778,56 @@ int wpas_pr_pasn_auth_tx_status(struct wpa_supplicant *wpa_s, const u8 *data,
wpa_s, NULL);
return 0;
}
+ if (ret == 3) {
+ /*
+ * M1 was ACKed by the responder, but M2 may be lost over the
+ * air. Arm a 512ms (500 TU) fallback retry timer (up to
+ * PR_PASN_MAX_AUTH_RETRIES times) to retransmit M1 so the
+ * responder replays its cached M2; after the final retry, arm
+ * one last 512ms window for M2 before aborting PASN instead
+ * of waiting for the 10s PASN timeout.
+ */
+ eloop_cancel_timeout(wpas_pr_pasn_auth_retry_timeout, wpa_s,
+ NULL);
+ if (pr->auth_retry_count >= PR_PASN_MAX_AUTH_RETRIES) {
+ wpa_printf(MSG_DEBUG,
+ "PR PASN: Max acked M1 retries (%u) "
+ "reached, wait %dms before aborting",
+ pr->auth_retry_count,
+ PR_PASN_AUTH1_ACKED_RETRY_INTERVAL_MS);
+ pr->auth_retry_count = PR_PASN_MAX_AUTH_RETRIES + 1;
+ } else {
+ pr->auth_retry_count++;
+ }
+ eloop_register_timeout(0,
+ PR_PASN_AUTH1_ACKED_RETRY_INTERVAL_MS *
+ 1000,
+ wpas_pr_pasn_auth_retry_timeout,
+ wpa_s, NULL);
+ return 0;
+ }
+ if (ret == 4) {
+ /*
+ * M2 was not ACKed by the initiator. Schedule ROC
+ * on the responder to stay on-channel for the initiator's
+ * retransmitted M1, and arm a timeout to tear down if
+ * no retry M1 or M3 arrives, similar to P2P GO Negotiation
+ * Response recovery.
+ */
+ if (pr && pr->pr_pasn_params) {
+ wpas_pr_cancel_roc(wpa_s);
+ wpas_pr_schedule_responder_roc(
+ wpa_s, pr->pr_pasn_params->freq);
+ eloop_cancel_timeout(wpas_pr_pasn_timeout, wpa_s,
+ NULL);
+ eloop_register_timeout(
+ 0, PR_PASN_M2_RETRY_ROC_DURATION_MS * 1000,
+ wpas_pr_pasn_timeout, wpa_s, NULL);
+ }
+ return 0;
+ }
- return ret;
+ return ret < 0 ? -1 : 0;
}
@@ -1727,6 +1839,16 @@ static void wpas_pr_pasn_auth_retry_timeout(void *eloop_ctx, void *timeout_ctx)
if (!pr || !pr->pr_pasn_params)
return;
+ if (pr->auth_retry_count > PR_PASN_MAX_AUTH_RETRIES) {
+ wpa_printf(MSG_DEBUG,
+ "PR PASN: Max acked M1 retries (%u) exhausted, "
+ "aborting PASN",
+ PR_PASN_MAX_AUTH_RETRIES);
+ eloop_cancel_timeout(wpas_pr_pasn_timeout, wpa_s, NULL);
+ wpas_pr_pasn_timeout(wpa_s, NULL);
+ return;
+ }
+
pr_pasn_auth_retransmit(pr, pr->pr_pasn_params->peer_addr);
}
@@ -1757,22 +1879,64 @@ int wpas_pr_pasn_auth_rx(struct wpa_supplicant *wpa_s,
int freq)
{
struct pr_data *pr = wpa_s->global->pr;
+ const u8 *own_addr;
+ u16 auth_alg, auth_transaction;
int ret;
- if (!pr)
+ if (!pr || !pr->pr_pasn_params)
return -2;
+ if (!mgmt || len < offsetof(struct ieee80211_mgmt, u.auth.variable))
+ return -1;
+
+ if (!ether_addr_equal(mgmt->sa, pr->pr_pasn_params->peer_addr)) {
+ wpa_printf(MSG_DEBUG,
+ "PR PASN: Ignore frame from unexpected SA " MACSTR
+ " (expected " MACSTR ")",
+ MAC2STR(mgmt->sa),
+ MAC2STR(pr->pr_pasn_params->peer_addr));
+ return -1;
+ }
+
+ if (!is_zero_ether_addr(wpa_s->pd_addr))
+ own_addr = wpa_s->pd_addr;
+ else if (!is_zero_ether_addr(pr->pr_pasn_params->src_addr))
+ own_addr = pr->pr_pasn_params->src_addr;
+ else
+ own_addr = wpa_s->own_addr;
+
+ if (!ether_addr_equal(mgmt->da, own_addr)) {
+ wpa_printf(MSG_DEBUG,
+ "PR PASN: Ignore frame with unexpected DA " MACSTR
+ " (expected " MACSTR ")",
+ MAC2STR(mgmt->da), MAC2STR(own_addr));
+ return -1;
+ }
+
+ auth_alg = le_to_host16(mgmt->u.auth.auth_alg);
+ if (auth_alg != WLAN_AUTH_PASN) {
+ wpa_printf(MSG_DEBUG,
+ "PR PASN: Unexpected auth_alg=%u, ignore frame",
+ auth_alg);
+ return -1;
+ }
+
+ auth_transaction = le_to_host16(mgmt->u.auth.auth_transaction);
+ if (auth_transaction != WLAN_AUTH_TR_SEQ_PASN_AUTH1 &&
+ auth_transaction != WLAN_AUTH_TR_SEQ_PASN_AUTH2 &&
+ auth_transaction != WLAN_AUTH_TR_SEQ_PASN_AUTH3) {
+ wpa_printf(MSG_DEBUG,
+ "PR PASN: Invalid auth_transaction=%u, ignore frame",
+ auth_transaction);
+ return -1;
+ }
+
/*
* Responder path: when we are waiting for PASN M1 on the parent
* interface ROC, create the PD wdev on first receipt of Auth1 before
* handing the frame to the common layer.
*/
- if (wpa_s->pr_responder_mode &&
- len >= offsetof(struct ieee80211_mgmt, u.auth.variable)) {
- u16 auth_transaction;
-
- auth_transaction = le_to_host16(mgmt->u.auth.auth_transaction);
-
+ 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)
return -1;
@@ -1785,6 +1949,10 @@ int wpas_pr_pasn_auth_rx(struct wpa_supplicant *wpa_s,
eloop_cancel_timeout(wpas_pr_pasn_roc_total_timeout,
wpa_s, NULL);
+ /* Clear responder mode before cancelling ROC */
+ wpa_s->pr_responder_mode = false;
+ os_memset(wpa_s->pr_responder_src_addr, 0, ETH_ALEN);
+
/*
* Cancel ROC on the listening interface; the dedicated
* PR interface (if created) will handle all subsequent
@@ -1796,24 +1964,50 @@ int wpas_pr_pasn_auth_rx(struct wpa_supplicant *wpa_s,
*/
wpas_pr_cancel_roc(wpa_s);
- /* Clear responder mode */
- wpa_s->pr_responder_mode = false;
- os_memset(wpa_s->pr_responder_src_addr, 0, ETH_ALEN);
-
wpa_printf(MSG_DEBUG,
"PR PASN: M1 processed, proceeding with PASN");
}
+ } else if (!wpa_s->pr_pasn_auth_work) {
+ /*
+ * Cancel any active post-M2-NACK recovery ROC on the responder
+ * when duplicate M1 or M3 arrives from the peer.
+ */
+ wpas_pr_cancel_roc(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_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)) {
+ 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) {
+ /*
+ * 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.
+ */
+ 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;
+ eloop_cancel_timeout(wpas_pr_pasn_auth_retry_timeout,
+ wpa_s, NULL);
+ }
}
return ret;
@@ -1844,14 +2038,14 @@ void wpas_pr_abort_ranging(struct wpa_supplicant *wpa_s)
*/
wpas_pr_pasn_cancel_auth_work(wpa_s);
wpa_s->pr_pasn_auth_work = NULL;
- if (wpa_s->pr_responder_mode) {
- wpas_pr_cancel_roc(wpa_s);
+ wpas_pr_cancel_roc(wpa_s);
+ if (wpa_s->pr_responder_mode)
wpas_pr_pasn_abort_responder(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);
/* Free ranging params so a new session can be started */
wpas_pr_clear_ranging_params(pr);
--
2.56.0.385.gac671c819a-goog
More information about the Hostap
mailing list