[RFC 06/56] NAN: Add support for parsing NAN action frames

Andrei Otcheretianski andrei.otcheretianski at intel.com
Sun Dec 7 03:18:15 PST 2025


From: Ilan Peer <ilan.peer at intel.com>

Add support for parsing a NAN Action Frame (NAF) and
storing the relevant parsed attributes.

Signed-off-by: Ilan Peer <ilan.peer at intel.com>
---
 src/common/ieee802_11_defs.h |  11 ++
 src/nan/Makefile             |   2 +-
 src/nan/nan_i.h              |  51 ++++++
 src/nan/nan_util.c           | 308 +++++++++++++++++++++++++++++++++++
 wpa_supplicant/Makefile      |   1 +
 5 files changed, 372 insertions(+), 1 deletion(-)
 create mode 100644 src/nan/nan_util.c

diff --git a/src/common/ieee802_11_defs.h b/src/common/ieee802_11_defs.h
index 28032bbc82..1a87f976fe 100644
--- a/src/common/ieee802_11_defs.h
+++ b/src/common/ieee802_11_defs.h
@@ -1210,12 +1210,23 @@ struct ieee80211_mgmt {
 					 * Basic Multi-Link element (optional) */
 					u8 variable[];
 				} STRUCT_PACKED link_reconf_resp;
+				struct {
+					u8 action_code;
+					u8 oui[3];
+					u8 oui_type;
+					u8 subtype;
+					u8 variable[0];
+				} STRUCT_PACKED naf;
 			} u;
 		} STRUCT_PACKED action;
 	} u;
 } STRUCT_PACKED;
 
 
+#define IEEE80211_MIN_ACTION_LEN(type)	\
+	(offsetof(struct ieee80211_mgmt, u.action.u.type) + \
+	 sizeof(((struct ieee80211_mgmt *)0)->u.action.u.type))
+
 #define IEEE80211_MAX_MMPDU_SIZE 2304
 
 /* Rx MCS bitmask is in the first 77 bits of supported_mcs_set */
diff --git a/src/nan/Makefile b/src/nan/Makefile
index bd8a66a5b3..2c58f9cb6c 100644
--- a/src/nan/Makefile
+++ b/src/nan/Makefile
@@ -1,3 +1,3 @@
-LIB_OBJS= nan.o
+LIB_OBJS= nan.o nan_util.o
 
 include ../lib.rules
diff --git a/src/nan/nan_i.h b/src/nan/nan_i.h
index 1efed82ff5..62b27881e9 100644
--- a/src/nan/nan_i.h
+++ b/src/nan/nan_i.h
@@ -9,6 +9,9 @@
 #ifndef NAN_I_H
 #define NAN_I_H
 
+#include "common/ieee802_11_defs.h"
+#include "common/nan_defs.h"
+#include "nan.h"
 #include "list.h"
 
 struct nan_config;
@@ -38,5 +41,53 @@ struct nan_data {
 	struct dl_list peer_list;
 };
 
+struct nan_attrs_entry {
+	struct dl_list list;
+	const u8 *ptr;
+	u16 len;
+};
+
+struct nan_attrs {
+	struct dl_list serv_desc_ext;
+	struct dl_list avail;
+	struct dl_list ndc;
+	struct dl_list dev_capa;
+	struct dl_list element_container;
+
+	const u8 *ndp;
+	const u8 *ndl;
+	const u8 *ndl_qos;
+	const u8 *cipher_suite_info;
+	const u8 *sec_ctxt_info;
+	const u8 *shared_key_desc;
+
+	u16 ndp_len;
+	u16 ndl_len;
+	u16 ndl_qos_len;
+	u16 cipher_suite_info_len;
+	u16 sec_ctxt_info_len;
+	u16 shared_key_desc_len;
+};
+
+struct nan_msg {
+	u8 oui_type;
+	u8 oui_subtype;
+	struct nan_attrs attrs;
+
+	/* the full frame is required for the NDP security flows, that compute
+	 * the NDP authentication token over the entire frame body.
+	 */
+	const struct ieee80211_mgmt *mgmt;
+	size_t len;
+};
+
 struct nan_peer * nan_get_peer(struct nan_data *nan, const u8 *addr);
+bool nan_is_naf(struct nan_data *nan, const struct ieee80211_mgmt *mgmt,
+		size_t len);
+int nan_parse_attrs(struct nan_data *nan, const u8 *data, size_t len,
+		    struct nan_attrs *attrs);
+int nan_parse_naf(struct nan_data *nan, const struct ieee80211_mgmt *mgmt,
+		  size_t len, struct nan_msg *msg);
+void nan_attrs_clear(struct nan_data *nan, struct nan_attrs *attrs);
+
 #endif
diff --git a/src/nan/nan_util.c b/src/nan/nan_util.c
new file mode 100644
index 0000000000..4e034ecfd9
--- /dev/null
+++ b/src/nan/nan_util.c
@@ -0,0 +1,308 @@
+/*
+ * Wi-Fi Aware - NAN module utils
+ * Copyright (C) 2025 Intel Corporation
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+#include "includes.h"
+#include "common.h"
+#include "nan_i.h"
+
+
+static void nan_attrs_clear_list(struct nan_data *nan,
+				 struct dl_list *list)
+{
+	struct nan_attrs_entry *entry, *pentry;
+
+	dl_list_for_each_safe(entry, pentry, list,
+			      struct nan_attrs_entry,
+			      list) {
+		dl_list_del(&entry->list);
+		os_free(entry);
+	}
+}
+
+
+/*
+ * nan_attrs_clear - Free data from nan parsing
+ *
+ * @nan: NAN module context from nan_init()
+ * @attrs: Parsed nan_attrs
+ */
+void nan_attrs_clear(struct nan_data *nan, struct nan_attrs *attrs)
+{
+	nan_attrs_clear_list(nan, &attrs->serv_desc_ext);
+	nan_attrs_clear_list(nan, &attrs->avail);
+	nan_attrs_clear_list(nan, &attrs->ndc);
+	nan_attrs_clear_list(nan, &attrs->dev_capa);
+	nan_attrs_clear_list(nan, &attrs->element_container);
+
+	os_memset(attrs, 0, sizeof(*attrs));
+}
+
+
+/*
+ * nan_parse_attrs - Parse NAN attributes
+ *
+ * @nan: NAN module context from nan_init()
+ * @data: Buffer holding the attributes
+ * @len: Length of &data
+ * @attrs: On return would hold the parsed attributes
+ * Returns: 0 on success; positive or negative indicate an error
+ *
+ * Note: In case of success, the caller must free temporary memory allocations
+ * by calling nan_attrs_clear() when the parsed data is not needed anymore.
+ */
+int nan_parse_attrs(struct nan_data *nan, const u8 *data, size_t len,
+		    struct nan_attrs *attrs)
+{
+	struct nan_attrs_entry *entry;
+	const u8 *pos = data;
+	const u8 *end = pos + len;
+
+	os_memset(attrs, 0, sizeof(*attrs));
+
+	dl_list_init(&attrs->serv_desc_ext);
+	dl_list_init(&attrs->avail);
+	dl_list_init(&attrs->ndc);
+	dl_list_init(&attrs->dev_capa);
+	dl_list_init(&attrs->element_container);
+
+	while (pos + 3 < end) {
+		u8 id = *pos++;
+		u16 attr_len = WPA_GET_LE16(pos);
+
+		pos += 2;
+		if ((pos + attr_len) > end)
+			goto fail;
+
+		switch (id) {
+		case NAN_ATTR_SDEA:
+			entry = os_malloc(sizeof(*entry));
+			if (!entry)
+				goto fail;
+
+			entry->ptr = pos;
+			entry->len = attr_len;
+			dl_list_add_tail(&attrs->serv_desc_ext, &entry->list);
+			break;
+		case NAN_ATTR_DEVICE_CAPABILITY:
+			/* Validate Device Capability attribute length */
+			if (attr_len != sizeof(struct nan_device_capa))
+				break;
+
+			entry = os_malloc(sizeof(*entry));
+			if (!entry)
+				goto fail;
+
+			entry->ptr = pos;
+			entry->len = attr_len;
+			dl_list_add_tail(&attrs->dev_capa, &entry->list);
+			break;
+		case NAN_ATTR_NDP:
+			/* Validate minimal NDP attribute length */
+			if (attr_len < sizeof(struct ieee80211_ndp))
+				break;
+
+			attrs->ndp = pos;
+			attrs->ndp_len = attr_len;
+			break;
+		case NAN_ATTR_NAN_AVAILABILITY:
+			/* Validate minimal Availability attribute length */
+			if (attr_len < sizeof(struct nan_avail))
+				break;
+
+			entry = os_malloc(sizeof(*entry));
+			if (!entry)
+				goto fail;
+
+			entry->ptr = pos;
+			entry->len = attr_len;
+			dl_list_add_tail(&attrs->avail, &entry->list);
+			break;
+		case NAN_ATTR_NDC:
+			/* Validate minimal NDC attribute length */
+			if (attr_len < sizeof(struct ieee80211_ndc))
+				break;
+
+			entry = os_malloc(sizeof(*entry));
+			if (!entry)
+				goto fail;
+
+			entry->ptr = pos;
+			entry->len = attr_len;
+			dl_list_add_tail(&attrs->ndc, &entry->list);
+			break;
+		case NAN_ATTR_NDL:
+			/* Validate minimal NDL attribute length */
+			if (attr_len < sizeof(struct ieee80211_ndl))
+				break;
+
+			attrs->ndl = pos;
+			attrs->ndl_len = attr_len;
+			break;
+		case NAN_ATTR_NDL_QOS:
+			/* Validate QoS attribute length */
+			if (attr_len != sizeof(struct ieee80211_nan_qos))
+				break;
+
+			attrs->ndl_qos = pos;
+			attrs->ndl_qos_len = attr_len;
+			break;
+		case NAN_ATTR_ELEM_CONTAINER:
+			/*
+			 * Validate minimal Element Container attribute length
+			 */
+			if (attr_len < 1)
+				break;
+
+			entry = os_malloc(sizeof(*entry));
+			if (!entry)
+				goto fail;
+
+			entry->ptr = pos;
+			entry->len = attr_len;
+			dl_list_add_tail(&attrs->element_container,
+					 &entry->list);
+			break;
+		case NAN_ATTR_CSIA:
+			attrs->cipher_suite_info = pos;
+			attrs->cipher_suite_info_len = attr_len;
+			break;
+		case NAN_ATTR_SCIA:
+			attrs->sec_ctxt_info = pos;
+			attrs->sec_ctxt_info_len = attr_len;
+			break;
+		case NAN_ATTR_SHARED_KEY_DESCR:
+			attrs->shared_key_desc = pos;
+			attrs->shared_key_desc_len = attr_len;
+			break;
+		case NAN_ATTR_MASTER_INDICATION:
+		case NAN_ATTR_CLUSTER:
+		case NAN_ATTR_NAN_ATTR_SERVICE_ID_LIST:
+		case NAN_ATTR_SDA:
+		case NAN_ATTR_CONN_CAPA:
+		case NAN_ATTR_WLAN_INFRA:
+		case NAN_ATTR_P2P_OPER:
+		case NAN_ATTR_IBSS:
+		case NAN_ATTR_MESH:
+		case NAN_ATTR_FURTHER_NAN_SD:
+		case NAN_ATTR_FURTHER_AVAIL_MAP:
+		case NAN_ATTR_COUNTRY_CODE:
+		case NAN_ATTR_RANGING:
+		case NAN_ATTR_CLUSTER_DISCOVERY:
+		case NAN_ATTR_UNALIGNED_SCHEDULE:
+		case NAN_ATTR_RANGING_INFO:
+		case NAN_ATTR_RANGING_SETUP:
+		case NAN_ATTR_FTM_RANGING_REPORT:
+		case NAN_ATTR_EXT_WLAN_INFRA:
+		case NAN_ATTR_EXT_P2P_OPER:
+		case NAN_ATTR_EXT_IBSS:
+		case NAN_ATTR_EXT_MESH:
+		case NAN_ATTR_PUBLIC_AVAILABILITY:
+		case NAN_ATTR_SUBSC_SERVICE_ID_LIST:
+		case NAN_ATTR_NDP_EXT:
+		case NAN_ATTR_DCEA:
+		case NAN_ATTR_NIRA:
+		case NAN_ATTR_BPBA:
+		case NAN_ATTR_S3:
+		case NAN_ATTR_TPEA:
+		case NAN_ATTR_VENDOR_SPECIFIC:
+			wpa_printf(MSG_DEBUG, "NAN: ignore attr=%u", id);
+			break;
+		default:
+			wpa_printf(MSG_DEBUG, "NAN: unknown attr=%u", id);
+			break;
+		}
+
+		pos += attr_len;
+	}
+
+	/* Parsing is considered success only if all attributes were consumed */
+	if (pos == end)
+		return 0;
+
+fail:
+	nan_attrs_clear(nan, attrs);
+	return -1;
+}
+
+
+/*
+ * nan_is_naf - Check if a given frame is a NAN action frame
+ *
+ * @nan: NAN module context from nan_init()
+ * @mgmt: NAN action frame
+ * @len: Length of the management frame in octets
+ * Returns: true if NAF; otherwise false
+ */
+bool nan_is_naf(struct nan_data *nan, const struct ieee80211_mgmt *mgmt,
+		size_t len)
+{
+	u8 subtype;
+
+	/*
+	 * 802.11 header + category + NAN action frame minimal + subtype (1)
+	 */
+	if (len < IEEE80211_MIN_ACTION_LEN(naf)) {
+		wpa_printf(MSG_DEBUG, "Too short NAN frame");
+		return false;
+	}
+
+	if (mgmt->u.action.u.naf.action_code != WLAN_PA_VENDOR_SPECIFIC ||
+	    mgmt->u.action.u.naf.oui[0] != ((OUI_WFA >> 16) & 0xff) ||
+	    mgmt->u.action.u.naf.oui[1] != ((OUI_WFA >> 8)  & 0xff) ||
+	    mgmt->u.action.u.naf.oui[2] != (OUI_WFA & 0xff) ||
+	    mgmt->u.action.u.naf.oui_type != NAN_TYPE_NAF)
+		return false;
+
+	subtype = mgmt->u.action.u.naf.subtype;
+
+	if (mgmt->u.action.category != WLAN_ACTION_PUBLIC &&
+	    !(subtype >= NAN_SUBTYPE_DATA_PATH_REQUEST &&
+	      subtype <= NAN_SUBTYPE_DATA_PATH_TERMINATION &&
+	      mgmt->u.action.category == WLAN_ACTION_PROTECTED_DUAL)) {
+		wpa_printf(MSG_DEBUG, "NAN: Invalid action category for NAF");
+		return false;
+	}
+
+	return true;
+}
+
+
+/*
+ * nan_parse_naf - Parse a NAN Action frame content
+ *
+ * @nan: NAN module context from nan_init()
+ * @mgmt: NAN action frame.
+ * @len: Length of the management frame in octets
+ * @msg: Buffer for returning parsed attributes
+ * Returns: 0 on success; positive or negative indicate an error
+ *
+ * Note: in case of success, the caller must free temporary memory allocations
+ * by calling nan_attrs_clear() when the parsed data is not needed anymore. In
+ * addition, as the &mgmt is referenced from the returned structure, the caller
+ * must ensure that the frame buffer remains valid an unmodified as long as the
+ * &msg object is used.
+ */
+int nan_parse_naf(struct nan_data *nan, const struct ieee80211_mgmt *mgmt,
+		  size_t len, struct nan_msg *msg)
+{
+	if (!nan_is_naf(nan, mgmt, len))
+		return -1;
+
+	wpa_printf(MSG_DEBUG, "NAN: Parse NAF");
+
+	msg->oui_type = mgmt->u.action.u.naf.oui_type;
+	msg->oui_subtype = mgmt->u.action.u.naf.subtype;
+
+	msg->mgmt = mgmt;
+	msg->len = len;
+
+	return nan_parse_attrs(nan,
+			       mgmt->u.action.u.naf.variable,
+			       len - IEEE80211_MIN_ACTION_LEN(naf),
+			       &msg->attrs);
+}
diff --git a/wpa_supplicant/Makefile b/wpa_supplicant/Makefile
index c8941a80eb..3eb338106b 100644
--- a/wpa_supplicant/Makefile
+++ b/wpa_supplicant/Makefile
@@ -338,6 +338,7 @@ ifdef NEED_NAN
 OBJS += nan_supplicant.o
 OBJS += ../src/nan/nan.o
 OBJS += ../src/common/nan_de.o
+OBJS += ../src/nan/nan_util.o
 endif
 
 ifdef CONFIG_OWE
-- 
2.49.0




More information about the Hostap mailing list