[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