[PATCH 10/18] NAN: Reuse the established NDC across all NDLs

Andrei Otcheretianski andrei.otcheretianski at intel.com
Mon Oct 12 08:46:44 PDT 2026


The NDC was negotiated from scratch for every NDL, so a device with
several peers ended up committed to a separate NDC per NDL instead of
a single shared one.

Once an NDC is established, propose it explicitly in the request and
in the (counter) response when possible, or reject the peer.

Signed-off-by: Andrei Otcheretianski <andrei.otcheretianski at intel.com>
---
 src/nan/nan.h                   |   6 ++
 src/nan/nan_util.c              |  95 +++++++++++++++++
 wpa_supplicant/nan_supplicant.c | 181 ++++++++++++++++++++++++--------
 3 files changed, 240 insertions(+), 42 deletions(-)

diff --git a/src/nan/nan.h b/src/nan/nan.h
index 5245a9baaa..5de815b920 100644
--- a/src/nan/nan.h
+++ b/src/nan/nan.h
@@ -956,6 +956,12 @@ bool nan_peer_npk_nik_caching_supported(struct nan_data *nan, const u8 *addr);
 int nan_get_peer_ndc_freq(struct nan_data *nan,
 			  const struct nan_peer_schedule *peer_sched,
 			  u8 map_idx);
+bool nan_peer_sched_conflicts_with_ndc(struct nan_data *nan,
+				       const struct nan_peer_schedule *peer_sched,
+				       int freq,
+				       const struct nan_time_bitmap *ndc);
+bool nan_tbm_equal(struct nan_data *nan, const struct nan_time_bitmap *a,
+		   const struct nan_time_bitmap *b);
 int nan_crypto_derive_nd_pmk(const char *pwd, const u8 *service_id,
 			     enum nan_cipher_suite_id csid,
 			     const u8 *peer_nmi, u8 *nd_pmk);
diff --git a/src/nan/nan_util.c b/src/nan/nan_util.c
index e04325e469..77e01f6a5f 100644
--- a/src/nan/nan_util.c
+++ b/src/nan/nan_util.c
@@ -1925,6 +1925,101 @@ int nan_get_peer_ndc_freq(struct nan_data *nan,
 }
 
 
+/**
+ * nan_peer_sched_conflicts_with_ndc - Check if a peer cannot host an NDC
+ * @nan: Pointer to NAN data struct
+ * @peer_sched: Pointer to peer schedule struct
+ * @freq: Frequency of the NDC
+ * @ndc: NDC time bitmap
+ * Returns: true if the peer schedule is known and cannot host the NDC
+ *
+ * The NDC has to be in the intersection of the local and the peer schedules,
+ * so an already established NDC can only be reused with a peer that is
+ * available on its channel in all of its slots. A peer that didn't advertise
+ * its schedule yet is not considered conflicting - it will counter or reject
+ * the proposal instead.
+ */
+bool nan_peer_sched_conflicts_with_ndc(struct nan_data *nan,
+				       const struct nan_peer_schedule *peer_sched,
+				       int freq,
+				       const struct nan_time_bitmap *ndc)
+{
+	struct bitfield *ndc_bf;
+	bool known = false, covered = false;
+	u8 map_idx;
+
+	ndc_bf = nan_tbm_to_bf(nan, ndc);
+	if (!ndc_bf)
+		return false;
+
+	for (map_idx = 0; !covered && map_idx < peer_sched->n_maps; map_idx++) {
+		const struct nan_map *map = &peer_sched->maps[map_idx];
+		int i;
+
+		for (i = 0; i < map->n_chans; i++) {
+			struct bitfield *chan_bf;
+
+			known = true;
+			if (map->chans[i].chan.freq != freq)
+				continue;
+
+			chan_bf = nan_tbm_to_bf(nan, &map->chans[i].tbm);
+			if (!chan_bf) {
+				/* Don't report a conflict that wasn't checked */
+				wpa_printf(MSG_DEBUG,
+					   "NAN: Failed to convert peer channel bitmap");
+				bitfield_free(ndc_bf);
+				return false;
+			}
+
+			covered = bitfield_is_subset(chan_bf, ndc_bf) == 1;
+			bitfield_free(chan_bf);
+			if (covered)
+				break;
+		}
+	}
+
+	bitfield_free(ndc_bf);
+	return known && !covered;
+}
+
+
+/**
+ * nan_tbm_equal - Check if two time bitmaps describe the same slots
+ * @nan: Pointer to NAN data struct
+ * @a: First time bitmap
+ * @b: Second time bitmap
+ * Returns: true if both bitmaps cover exactly the same slots
+ *
+ * Different duration, period, offset and length combinations can describe the
+ * same slots, so the comparison is done on the normalized bitmaps.
+ */
+bool nan_tbm_equal(struct nan_data *nan, const struct nan_time_bitmap *a,
+		   const struct nan_time_bitmap *b)
+{
+	struct bitfield *a_bf, *b_bf;
+	bool equal = false;
+
+	/* An empty bitmap is not a schedule, so never report it as a match */
+	if (!a->len || !b->len)
+		return false;
+
+	a_bf = nan_tbm_to_bf(nan, a);
+	b_bf = nan_tbm_to_bf(nan, b);
+
+	if (a_bf && b_bf)
+		equal = bitfield_is_subset(a_bf, b_bf) == 1 &&
+			bitfield_is_subset(b_bf, a_bf) == 1;
+	else
+		wpa_printf(MSG_DEBUG,
+			   "NAN: Failed to convert time bitmap for comparison");
+
+	bitfield_free(a_bf);
+	bitfield_free(b_bf);
+	return equal;
+}
+
+
 /**
  * nan_get_chan_entry - Get channel entry for a given NAN scheduled channel
  * @nan: NAN module context from nan_init()
diff --git a/wpa_supplicant/nan_supplicant.c b/wpa_supplicant/nan_supplicant.c
index e19b0413d9..68ada904f2 100644
--- a/wpa_supplicant/nan_supplicant.c
+++ b/wpa_supplicant/nan_supplicant.c
@@ -777,9 +777,7 @@ static void wpas_nan_remove_ndi_sta(struct wpa_supplicant *wpa_s,
 }
 
 
-static int wpas_nan_get_ndc_map_id(struct wpa_supplicant *wpa_s,
-				   const struct nan_peer_schedule *peer_sched,
-				   u8 peer_map_id);
+static int wpas_nan_get_ndc_map_id(struct wpa_supplicant *wpa_s, int freq);
 
 static void wpas_nan_store_peer_ndc(struct wpa_supplicant *wpa_s,
 				    const u8 *peer_nmi)
@@ -806,8 +804,7 @@ static void wpas_nan_store_peer_ndc(struct wpa_supplicant *wpa_s,
 		if (freq < 0)
 			continue;
 
-		local_map_id = wpas_nan_get_ndc_map_id(wpa_s, &peer_sched,
-						      map_id);
+		local_map_id = wpas_nan_get_ndc_map_id(wpa_s, freq);
 		if (local_map_id < 0)
 			continue;
 
@@ -3021,20 +3018,9 @@ static void wpas_nan_fill_ndp_schedule(struct wpa_supplicant *wpa_s,
 }
 
 
-static int wpas_nan_get_ndc_map_id(struct wpa_supplicant *wpa_s,
-				   const struct nan_peer_schedule *peer_sched,
-				   u8 peer_map_id)
+static int wpas_nan_get_ndc_map_id(struct wpa_supplicant *wpa_s, int freq)
 {
 	int i;
-	int freq = nan_get_peer_ndc_freq(wpa_s->nan, peer_sched, peer_map_id);
-
-	if (freq < 0) {
-		wpa_printf(MSG_DEBUG,
-			   "NAN: Failed to get NDC frequency from peer schedule");
-		return -1;
-	}
-
-	wpa_printf(MSG_DEBUG, "NAN: Peer NDC frequency is %d MHz", freq);
 
 	for (i = 0; i < MAX_NAN_RADIOS; i++) {
 		struct nan_schedule_config *sched_cfg = &wpa_s->nan_sched[i];
@@ -3054,12 +3040,16 @@ static int wpas_nan_get_ndc_map_id(struct wpa_supplicant *wpa_s,
 }
 
 
-
+/*
+ * Anything the peer could have done differently is reported as a rejection
+ * through the response status. -1 is returned only when the confirm cannot
+ * be built at all.
+ */
 static int wpas_nan_select_ndc_copy_peers(struct wpa_supplicant *wpa_s,
 					  struct nan_ndp_params *ndp)
 {
 	struct nan_peer_schedule peer_sched;
-	int ret;
+	int ret, freq;
 	u8 map_id;
 
 	wpa_printf(MSG_DEBUG, "NAN: NDP CONF - use the NDC from peer");
@@ -3071,24 +3061,105 @@ static int wpas_nan_select_ndc_copy_peers(struct wpa_supplicant *wpa_s,
 	}
 
 	for (map_id = 0; map_id < peer_sched.n_maps; map_id++) {
-		if (peer_sched.maps[map_id].ndc.len) {
-			ret = wpas_nan_get_ndc_map_id(wpa_s, &peer_sched,
-						      map_id);
-			if (ret < 0) {
-				wpa_printf(MSG_DEBUG,
-					   "NAN: No local NDC map_id found for peer NDC");
-				return -1;
-			}
+		if (!peer_sched.maps[map_id].ndc.len)
+			continue;
 
-			ndp->sched.ndc_map_id = ret;
-			os_memcpy(&ndp->sched.ndc, &peer_sched.maps[map_id].ndc,
-				  sizeof(ndp->sched.ndc));
-			return 0;
+		freq = nan_get_peer_ndc_freq(wpa_s->nan, &peer_sched, map_id);
+		if (freq < 0) {
+			wpa_printf(MSG_DEBUG,
+				   "NAN: Failed to get NDC frequency from peer schedule");
+			goto reject;
 		}
+
+		/*
+		 * Must match our established NDC, or reject instead of
+		 * adopting a second one
+		 */
+		if (wpa_s->nan_ndc.valid &&
+		    (freq != wpa_s->nan_ndc.freq ||
+		     !nan_tbm_equal(wpa_s->nan, &peer_sched.maps[map_id].ndc,
+				    &wpa_s->nan_ndc.tbm))) {
+			wpa_printf(MSG_DEBUG,
+				   "NAN: Peer's NDC does not match the already established NDC (freq=%d)",
+				   wpa_s->nan_ndc.freq);
+			goto reject;
+		}
+
+		ret = wpas_nan_get_ndc_map_id(wpa_s, freq);
+		if (ret < 0) {
+			wpa_printf(MSG_DEBUG,
+				   "NAN: No local NDC map_id found for peer NDC");
+			goto reject;
+		}
+
+		ndp->sched.ndc_map_id = ret;
+		os_memcpy(&ndp->sched.ndc, &peer_sched.maps[map_id].ndc,
+			  sizeof(ndp->sched.ndc));
+		return 0;
 	}
 
 	wpa_printf(MSG_DEBUG, "NAN: No NDC found in peer schedule");
-	return -1;
+
+reject:
+	ndp->u.resp.status = NAN_NDP_STATUS_REJECTED;
+	ndp->u.resp.reason_code = NAN_REASON_NDL_UNACCEPTABLE;
+	return 0;
+}
+
+
+static void wpas_nan_select_established_ndc(struct wpa_supplicant *wpa_s,
+					    struct nan_ndp_params *ndp)
+{
+	struct nan_peer_schedule peer_sched;
+	int i;
+
+	/* Only a channel advertised for this NDP can carry the NDC */
+	for (i = 0; i < ndp->sched.n_chans; i++) {
+		if (ndp->sched.chans[i].chan.freq == wpa_s->nan_ndc.freq)
+			break;
+	}
+
+	if (i == ndp->sched.n_chans) {
+		wpa_printf(MSG_DEBUG,
+			   "NAN: Established NDC (freq=%d) is not part of this NDP schedule",
+			   wpa_s->nan_ndc.freq);
+
+		/* The NDC attribute is optional in a Request */
+		if (ndp->type == NAN_NDP_ACTION_REQ)
+			return;
+
+		goto reject;
+	}
+
+	/*
+	 * The NDC has to be in the intersection of the local and the peer
+	 * schedules. When building a Response the peer availability is known
+	 * from the Request, so an NDC that the peer cannot host is rejected
+	 * right away. When sending a Request the peer availability may still
+	 * change, so propose the NDC and let the peer counter or reject.
+	 */
+	if (ndp->type != NAN_NDP_ACTION_REQ &&
+	    !nan_peer_get_schedule_info(wpa_s->nan, ndp->ndp_id.peer_nmi,
+					&peer_sched) &&
+	    nan_peer_sched_conflicts_with_ndc(wpa_s->nan, &peer_sched,
+					      wpa_s->nan_ndc.freq,
+					      &wpa_s->nan_ndc.tbm)) {
+		wpa_printf(MSG_DEBUG,
+			   "NAN: Peer is not available on the established NDC (freq=%d)",
+			   wpa_s->nan_ndc.freq);
+		goto reject;
+	}
+
+	os_memcpy(&ndp->sched.ndc, &wpa_s->nan_ndc.tbm,
+		  sizeof(ndp->sched.ndc));
+	ndp->sched.ndc_map_id = ndp->sched.chans[i].map_id;
+	os_memcpy(ndp->sched.ndc_id, wpa_s->nan_ndc.id,
+		  sizeof(ndp->sched.ndc_id));
+	return;
+
+reject:
+	ndp->u.resp.status = NAN_NDP_STATUS_REJECTED;
+	ndp->u.resp.reason_code = NAN_REASON_NDL_UNACCEPTABLE;
 }
 
 
@@ -3099,15 +3170,32 @@ static int wpas_nan_select_ndc(struct wpa_supplicant *wpa_s,
 	bool slot_selected = false;
 	int i;
 
-	/* NDC attribute in request is optional, let the peer decide */
-	if (ndp->type == NAN_NDP_ACTION_REQ)
+	if (ndp->type == NAN_NDP_ACTION_REQ) {
+		/*
+		 * The NDC attribute is optional in a Request, so propose an
+		 * established NDC if there is one and otherwise let the peer
+		 * decide.
+		 */
+		if (wpa_s->nan_ndc.valid)
+			wpas_nan_select_established_ndc(wpa_s, ndp);
+
 		return 0;
+	}
 
-	/* For successfull confirm, copy peer's NDC */
+	/* For a successful confirm, copy the peer's NDC */
 	if (ndp->type == NAN_NDP_ACTION_CONF &&
 	    ndp->u.resp.status == NAN_NDP_STATUS_ACCEPTED)
 		return wpas_nan_select_ndc_copy_peers(wpa_s, ndp);
 
+	/*
+	 * Building a Response: reuse an established NDC instead of picking
+	 * a new one
+	 */
+	if (wpa_s->nan_ndc.valid) {
+		wpas_nan_select_established_ndc(wpa_s, ndp);
+		return 0;
+	}
+
 	tbm = &ndp->sched.chans[0].committed;
 #ifdef CONFIG_TESTING_OPTIONS
 	if (wpa_s->nan_force_conditional_sched) {
@@ -3212,7 +3300,15 @@ static int wpas_nan_set_ndp_schedule(struct wpa_supplicant *wpa_s,
 	/* Mark schedule as valid */
 	ndp->sched_valid = true;
 
-	return wpas_nan_select_ndc(wpa_s, ndp);
+	if (wpas_nan_select_ndc(wpa_s, ndp))
+		return -1;
+
+	/* NDC selection can still reject; a rejection carries no schedule */
+	if (ndp->type != NAN_NDP_ACTION_REQ &&
+	    ndp->u.resp.status == NAN_NDP_STATUS_REJECTED)
+		ndp->sched_valid = false;
+
+	return 0;
 }
 
 
@@ -3835,6 +3931,13 @@ int wpas_nan_ndp_response(struct wpa_supplicant *wpa_s, char *cmd)
 		goto fail;
 	}
 
+	/* NDC selection can still reject the response, so decide it first */
+	if (wpas_nan_set_ndp_schedule(wpa_s, &ndp) < 0) {
+		wpa_printf(MSG_INFO,
+			   "NAN: Failed to set NDP schedule");
+		goto fail;
+	}
+
 	if (ndp.u.resp.status == NAN_NDP_STATUS_ACCEPTED && ndi_wpa_s &&
 	    wpas_nan_ndp_response_set_gtk(wpa_s, ndi_wpa_s, handle, &ndp) < 0) {
 		wpa_printf(MSG_DEBUG,
@@ -3848,12 +3951,6 @@ int wpas_nan_ndp_response(struct wpa_supplicant *wpa_s, char *cmd)
 		   "Accepting" : "Rejecting",
 		   MAC2STR(ndp.ndp_id.peer_nmi), ndp.ndp_id.id);
 
-	if (wpas_nan_set_ndp_schedule(wpa_s, &ndp) < 0) {
-		wpa_printf(MSG_INFO,
-			   "NAN: Failed to set NDP schedule");
-		goto fail;
-	}
-
 	ret = nan_handle_ndp_setup(wpa_s->nan, &ndp);
 	if (ret < 0)
 		wpa_printf(MSG_INFO, "NAN: Failed to handle NDP response");
-- 
2.53.0




More information about the Hostap mailing list