[RFC v2 61/99] wpa_supplicant: Support NAN_NDP_REQUEST command
Andrei Otcheretianski
andrei.otcheretianski at intel.com
Tue Dec 23 03:52:05 PST 2025
Parse the command and trigger NDP establishment flow.
Currently only open connection is supported, so don't support parsing
security parameters yet.
Signed-off-by: Andrei Otcheretianski <andrei.otcheretianski at intel.com>
---
wpa_supplicant/ctrl_iface.c | 3 +
wpa_supplicant/nan_supplicant.c | 448 ++++++++++++++++++++++++++++++++
wpa_supplicant/nan_supplicant.h | 1 +
3 files changed, 452 insertions(+)
diff --git a/wpa_supplicant/ctrl_iface.c b/wpa_supplicant/ctrl_iface.c
index 1a1158f75d..3a8b3298cc 100644
--- a/wpa_supplicant/ctrl_iface.c
+++ b/wpa_supplicant/ctrl_iface.c
@@ -14350,6 +14350,9 @@ char * wpa_supplicant_ctrl_iface_process(struct wpa_supplicant *wpa_s,
} else if (os_strncmp(buf, "NAN_SCHED_CONFIG_MAP ", 21) == 0) {
if (wpas_nan_sched_config_map(wpa_s, buf + 21) < 0)
reply_len = -1;
+ } else if (os_strncmp(buf, "NAN_NDP_REQUEST ", 16) == 0) {
+ if (wpas_nan_ndp_request(wpa_s, buf + 16) < 0)
+ reply_len = -1;
#endif /* CONFIG_NAN */
} else {
os_memcpy(reply, "UNKNOWN COMMAND\n", 16);
diff --git a/wpa_supplicant/nan_supplicant.c b/wpa_supplicant/nan_supplicant.c
index 820786bbf2..bb17805ba4 100644
--- a/wpa_supplicant/nan_supplicant.c
+++ b/wpa_supplicant/nan_supplicant.c
@@ -706,6 +706,454 @@ out:
}
+static struct wpabuf * wpas_nan_build_ndp_elems(struct wpa_supplicant *wpa_s)
+{
+ struct hostapd_hw_modes *ht_mode = NULL, *vht_mode = NULL;
+ struct ieee80211_ht_capabilities *ht_cap;
+ size_t len;
+ struct wpabuf *buf;
+ int i;
+
+ for (i = 0; i < wpa_s->hw.num_modes; i++) {
+ /* TODO: Support 6 GHz */
+ if (wpa_s->hw.modes[i].is_6ghz ||
+ (wpa_s->hw.modes[i].mode != HOSTAPD_MODE_IEEE80211A &&
+ wpa_s->hw.modes[i].mode != HOSTAPD_MODE_IEEE80211G))
+ continue;
+
+ if (wpa_s->hw.modes[i].ht_capab)
+ ht_mode = &wpa_s->hw.modes[i];
+
+ if (wpa_s->hw.modes[i].vht_capab)
+ vht_mode = &wpa_s->hw.modes[i];
+
+ /* Assume that if VHT is supported HT will be reported too,
+ * otherwise we could take HT and VHT from different modes
+ */
+ if (ht_mode && vht_mode)
+ break;
+ }
+
+ if (!ht_mode) {
+ wpa_printf(MSG_DEBUG,
+ "NAN: No suitable mode found for HT/VHT capabilities");
+ return NULL;
+ }
+
+ /* Include HT and VHT capability elements */
+ len = 2 + sizeof(struct ieee80211_ht_capabilities);
+ if (vht_mode)
+ len += 2 + sizeof(struct ieee80211_vht_capabilities);
+
+ buf = wpabuf_alloc(len);
+ if (!buf)
+ return NULL;
+
+ wpabuf_put_u8(buf, WLAN_EID_HT_CAP);
+ wpabuf_put_u8(buf, sizeof(*ht_cap));
+ ht_cap = wpabuf_put(buf, sizeof(*ht_cap));
+ ht_cap->ht_capabilities_info = host_to_le16(ht_mode->ht_capab);
+ ht_cap->a_mpdu_params = ht_mode->a_mpdu_params;
+ os_memcpy(ht_cap->supported_mcs_set, ht_mode->mcs_set,
+ sizeof(ht_cap->supported_mcs_set));
+
+ if (vht_mode) {
+ struct ieee80211_vht_capabilities *vht_cap;
+
+ wpabuf_put_u8(buf, WLAN_EID_VHT_CAP);
+ wpabuf_put_u8(buf, sizeof(*vht_cap));
+ vht_cap = wpabuf_put(buf, sizeof(*vht_cap));
+ vht_cap->vht_capabilities_info = host_to_le32(vht_mode->vht_capab);
+ os_memcpy(&vht_cap->vht_supported_mcs_set,
+ vht_mode->vht_mcs_set,
+ sizeof(vht_cap->vht_supported_mcs_set));
+ }
+
+ /* TODO: Add HE capabilities */
+ return buf;
+}
+
+
+static void wpas_nan_fill_ndp_schedule(struct wpa_supplicant *wpa_s,
+ struct nan_schedule *sched)
+{
+ int map_id;
+
+ os_memset(sched, 0, sizeof(*sched));
+
+ /* Fill the NAN schedule structure from the schedule config */
+ for (map_id = 0; map_id < MAX_NAN_RADIOS; map_id++) {
+ int i;
+ struct nan_schedule_config *sched_cfg =
+ &wpa_s->nan_sched[map_id];
+
+ for (i = 0; i < wpa_s->nan_sched[map_id].num_channels; i++) {
+ struct nan_chan_schedule *chan_sched;
+ const u8 *bitmap_data;
+ size_t bitmap_len;
+
+ /* None of these should happen */
+ if (!sched_cfg->channels[i].time_bitmap) {
+ wpa_printf(MSG_DEBUG,
+ "NAN: Missing time bitmap for map_id %d freq %d",
+ map_id + 1,
+ sched_cfg->channels[i].freq);
+ return;
+ }
+
+ bitmap_len =
+ wpabuf_len(sched_cfg->channels[i].time_bitmap);
+ bitmap_data =
+ wpabuf_head(sched_cfg->channels[i].time_bitmap);
+ if (bitmap_len > NAN_TIME_BITMAP_MAX_LEN) {
+ wpa_printf(MSG_DEBUG,
+ "NAN: Time bitmap length %zu exceeds maximum %d",
+ bitmap_len,
+ NAN_TIME_BITMAP_MAX_LEN);
+ return;
+ }
+
+ chan_sched = &sched->chans[sched->n_chans++];
+ chan_sched->map_id = map_id + 1;
+ chan_sched->chan.freq = sched_cfg->channels[i].freq;
+ chan_sched->chan.center_freq1 =
+ sched_cfg->channels[i].center_freq1;
+ chan_sched->chan.center_freq2 =
+ sched_cfg->channels[i].center_freq2;
+ chan_sched->chan.bandwidth =
+ sched_cfg->channels[i].bandwidth;
+
+ chan_sched->committed.duration =
+ wpa_s->nan_sched_slot_duration >> 5;
+ chan_sched->committed.period =
+ ffs(wpa_s->nan_schedule_period) - 7;
+ chan_sched->committed.offset = 0;
+ chan_sched->committed.len = bitmap_len;
+ os_memcpy(chan_sched->committed.bitmap, bitmap_data,
+ bitmap_len);
+ wpa_printf(MSG_DEBUG,
+ "NAN: NDP schedule channel added: map_id=%d freq=%d center_freq1=%d center_freq2=%d bandwidth=%d",
+ chan_sched->map_id,
+ chan_sched->chan.freq,
+ chan_sched->chan.center_freq1,
+ chan_sched->chan.center_freq2,
+ chan_sched->chan.bandwidth);
+ }
+ }
+ /* Mark all supported radios - for potential availability */
+ sched->map_ids_bitmap = (BIT(wpa_s->nan_num_radios) - 1) << 1;
+}
+
+
+static int wpas_nan_get_ndc_map_id(struct wpa_supplicant *wpa_s,
+ struct nan_peer_schedule *peer_sched,
+ u8 peer_map_id)
+{
+ 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];
+ int j;
+
+ for (j = 0; j < sched_cfg->num_channels; j++) {
+ if (sched_cfg->channels[j].freq == freq) {
+ wpa_printf(MSG_DEBUG,
+ "NAN: Found local NDC map_id %d for peer NDC freq %d",
+ i + 1, freq);
+ return i + 1;
+ }
+ }
+ }
+
+ return -1;
+}
+
+
+static int wpas_nan_select_ndc(struct wpa_supplicant *wpa_s,
+ struct nan_ndp_params *ndp)
+{
+ int i;
+
+ /* NDC attribute in request is optional, let the peer decide */
+ if (ndp->type == NAN_NDP_ACTION_REQ)
+ return 0;
+
+ /* For successfull confirm, copy peer's NDC */
+ if (ndp->type == NAN_NDP_ACTION_CONF &&
+ ndp->u.resp.status == NAN_NDP_STATUS_ACCEPTED) {
+ struct nan_peer_schedule peer_sched;
+ int ret;
+ u8 map_id;
+
+ wpa_printf(MSG_DEBUG,
+ "NAN: NDP CONF - use the NDC from peer");
+ ret = nan_peer_get_schedule_info(wpa_s->nan, ndp->ndp_id.peer_nmi,
+ &peer_sched);
+ if (ret) {
+ wpa_printf(MSG_DEBUG,
+ "NAN: Failed to get peer schedule info");
+ return -1;
+ }
+
+ for (map_id = 0; map_id < peer_sched.n_maps; map_id++) {
+ if (peer_sched.maps[map_id].ndc.len) {
+ int 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;
+ }
+
+ 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;
+ }
+
+ os_memcpy(&ndp->sched.ndc, &ndp->sched.chans[0].committed,
+ sizeof(ndp->sched.ndc));
+ os_memset(ndp->sched.ndc.bitmap, 0,
+ sizeof(ndp->sched.ndc.bitmap));
+ ndp->sched.ndc_map_id = ndp->sched.chans[0].map_id;
+
+ /*
+ * For default NDC channels (6, 149, 44) take the first slot after
+ * DW. Note that if the slot duration is 16 TUs we need to select
+ * the next slot after DW.
+ * If the first channel is not one of default NDC channels, select the
+ * first available slot.
+ */
+ if (ndp->sched.chans[0].chan.freq == 5745 ||
+ ndp->sched.chans[0].chan.freq == 5220) {
+ int dw_bit, byte_idx, bit_in_byte;
+
+ dw_bit = 128 / wpa_s->nan_sched_slot_duration;
+ dw_bit += !!(wpa_s->nan_sched_slot_duration == 16);
+ byte_idx = dw_bit / 8;
+ bit_in_byte = dw_bit % 8;
+
+ if (ndp->sched.chans[0].committed.bitmap[byte_idx] &
+ BIT(bit_in_byte)) {
+ ndp->sched.ndc.bitmap[byte_idx] = BIT(bit_in_byte);
+ return 0;
+ }
+ } else if (ndp->sched.chans[0].chan.freq == 2437 &&
+ (wpa_s->nan_sched_slot_duration == 16)) {
+ if (ndp->sched.chans[0].committed.bitmap[0] & 0x02) {
+ ndp->sched.ndc.bitmap[0] = 0x02;
+ return 0;
+ }
+ }
+
+ /* For other cases, select first available slot */
+ for (i = 0; i < NAN_TIME_BITMAP_MAX_LEN; i++) {
+ if (ndp->sched.chans[0].committed.bitmap[0]) {
+ ndp->sched.ndc.bitmap[i] =
+ ndp->sched.chans[0].committed.bitmap[i] &
+ (~ndp->sched.chans[0].committed.bitmap[i] + 1);
+ break;
+ }
+ }
+
+ return 0;
+}
+
+
+static int wpas_nan_set_ndp_schedule(struct wpa_supplicant *wpa_s,
+ struct nan_ndp_params *ndp)
+{
+ /* Set schedule for request or successful response */
+ if (ndp->type != NAN_NDP_ACTION_REQ &&
+ ndp->u.resp.status == NAN_NDP_STATUS_REJECTED)
+ return 0;
+
+ wpas_nan_fill_ndp_schedule(wpa_s, &ndp->sched);
+
+ if (!ndp->sched.n_chans) {
+ wpa_printf(MSG_DEBUG,
+ "NAN: No channels configured for NDP schedule");
+ return -1;
+ }
+
+ /* Set sequence ID */
+ ndp->sched.sequence_id = wpa_s->schedule_sequence_id;
+
+ /* Add additional elements */
+ ndp->sched.elems = wpas_nan_build_ndp_elems(wpa_s);
+
+ /* Mark schedule as valid */
+ ndp->sched_valid = true;
+
+ return wpas_nan_select_ndc(wpa_s, ndp);
+}
+
+
+/* Command format NAN_NDP_REQUEST handle=<id> ndi=<ifname> peer_nmi=<nmi>
+ peer_id=<peer_instance_id> ssi=<hexdata> qos=<slots:latency> */
+int wpas_nan_ndp_request(struct wpa_supplicant *wpa_s, char *cmd)
+{
+ struct nan_ndp_params ndp;
+ struct wpabuf *ssi_buf = NULL;
+ char *token, *context = NULL;
+ char *pos;
+ int handle = -1;
+ int ret = -1;
+
+ os_memset(&ndp, 0, sizeof(ndp));
+
+ if (!wpas_nan_ready(wpa_s))
+ return -1;
+
+ ndp.type = NAN_NDP_ACTION_REQ;
+ ndp.qos.min_slots = NAN_QOS_MIN_SLOTS_NO_PREF;
+ ndp.qos.max_latency = NAN_QOS_MAX_LATENCY_NO_PREF;
+
+ /* Parse command parameters */
+ while ((token = str_token(cmd, " ", &context))) {
+ pos = os_strchr(token, '=');
+ if (!pos) {
+ wpa_printf(MSG_DEBUG,
+ "NAN: Invalid parameter format: %s",
+ token);
+ goto fail;
+ }
+ *pos++ = '\0';
+
+ if (os_strcmp(token, "handle") == 0) {
+ handle = atoi(pos);
+
+ /* Get service ID from the local handle */
+ if (!nan_de_is_valid_instance_id(wpa_s->nan_de,
+ handle,
+ false,
+ ndp.u.req.service_id)) {
+ wpa_printf(MSG_DEBUG,
+ "NAN: Invalid subscribe handle: %d",
+ handle);
+ goto fail;
+ }
+ } else if (os_strcmp(token, "ndi") == 0) {
+ struct wpa_supplicant *ndi_wpa_s;
+
+ ndi_wpa_s = wpa_supplicant_get_iface(wpa_s->global, pos);
+ if (!ndi_wpa_s) {
+ wpa_printf(MSG_DEBUG,
+ "NAN: NDI interface not found: %s",
+ pos);
+ goto fail;
+ }
+
+ if (!ndi_wpa_s->nan_data) {
+ wpa_printf(MSG_DEBUG,
+ "NAN: Interface %s is not a NAN data interface",
+ pos);
+ goto fail;
+ }
+
+ os_memcpy(ndp.ndp_id.init_ndi, ndi_wpa_s->own_addr,
+ ETH_ALEN);
+
+ } else if (os_strcmp(token, "peer_nmi") == 0) {
+ if (hwaddr_aton(pos, ndp.ndp_id.peer_nmi) < 0) {
+ wpa_printf(MSG_DEBUG,
+ "NAN: Invalid peer NMI address: %s",
+ pos);
+ goto fail;
+ }
+
+ } else if (os_strcmp(token, "peer_id") == 0) {
+ ndp.u.req.publish_inst_id = atoi(pos);
+ } else if (os_strcmp(token, "ssi") == 0) {
+ ssi_buf = wpabuf_parse_bin(pos);
+ if (!ssi_buf) {
+ wpa_printf(MSG_DEBUG,
+ "NAN: Invalid SSI data: %s", pos);
+ goto fail;
+ }
+
+ ndp.ssi_len = wpabuf_len(ssi_buf);
+ ndp.ssi = wpabuf_head(ssi_buf);
+
+ } else if (os_strcmp(token, "qos") == 0) {
+ if (sscanf(pos, "%hhu:%hu",
+ &ndp.qos.min_slots,
+ &ndp.qos.max_latency) != 2) {
+ wpa_printf(MSG_DEBUG,
+ "NAN: Invalid QoS parameter: %s",
+ pos);
+ goto fail;
+ }
+
+ } else {
+ wpa_printf(MSG_DEBUG, "NAN: Unknown parameter: %s",
+ token);
+ goto fail;
+ }
+ }
+
+ /* Validate required parameters */
+ if (handle < 0) {
+ wpa_printf(MSG_DEBUG,
+ "NAN: Missing required parameter: handle");
+ goto fail;
+ }
+
+ if (!ndp.u.req.publish_inst_id) {
+ wpa_printf(MSG_DEBUG,
+ "NAN: Missing required parameter: peer_id");
+ goto fail;
+ }
+
+ if (is_zero_ether_addr(ndp.ndp_id.init_ndi)) {
+ wpa_printf(MSG_DEBUG,
+ "NAN: Missing required parameter: ndi");
+ goto fail;
+ }
+
+ if (is_zero_ether_addr(ndp.ndp_id.peer_nmi)) {
+ wpa_printf(MSG_DEBUG,
+ "NAN: Missing required parameter: peer_nmi");
+ goto fail;
+ }
+
+ if (wpas_nan_set_ndp_schedule(wpa_s, &ndp)) {
+ wpa_printf(MSG_DEBUG,
+ "NAN: Failed to set NDP schedule");
+ goto fail;
+ }
+
+ wpa_printf(MSG_DEBUG, "NAN: Requesting NDP with peer " MACSTR
+ " using handle %d", MAC2STR(ndp.ndp_id.peer_nmi),
+ ndp.u.req.publish_inst_id);
+ ret = nan_handle_ndp_setup(wpa_s->nan, &ndp);
+fail:
+ wpabuf_free(ndp.sched.elems);
+ wpabuf_free(ssi_buf);
+
+ return ret;
+}
+
+
void wpas_nan_cluster_join(struct wpa_supplicant *wpa_s,
const u8 *cluster_id,
bool new_cluster)
diff --git a/wpa_supplicant/nan_supplicant.h b/wpa_supplicant/nan_supplicant.h
index 7e521c7465..e98e38815d 100644
--- a/wpa_supplicant/nan_supplicant.h
+++ b/wpa_supplicant/nan_supplicant.h
@@ -25,6 +25,7 @@ void wpas_nan_cluster_join(struct wpa_supplicant *wpa_s,
bool new_cluster);
void wpas_nan_next_dw(struct wpa_supplicant *wpa_s, u32 freq);
int wpas_nan_sched_config_map(struct wpa_supplicant *wpa_s, const char *cmd);
+int wpas_nan_ndp_request(struct wpa_supplicant *wpa_s, char *cmd);
#else /* CONFIG_NAN */
--
2.49.0
More information about the Hostap
mailing list