Cumulative patch from commit 8b48e3200680f71ae083b84793e6bdc2099416d2 [DO NOT MERGE]

8b48e32 wpa_cli: Add MAC address randomization in scan
fb37588 ctrl_iface: Add MAC address randomization in scan processing
56c76fa scan: Add MAC address randomization in scan handling
86056fe nl80211: Handle MAC address randomization in scan/sched_scan
ff23ed2 driver: Add definitions for MAC address randomization in scan
7db53bb wpa_cli: Implement TDLS start/cancel channel switching commands
72b2605 nl80211: Pass TDLS channel-switch start/stop params to kernel
6b90dea TDLS: Propagate enable/disable channel-switch commands to driver
d9d3b78 TDLS: Track TDLS channel switch prohibition in BSS
4daa572 TDLS: Add channel-switch capability flag
ca16586 Sync with wireless-testing.git include/uapi/linux/nl80211.h
8c42b36 WMM AC: Reconfigure tspecs on reassociation to the same BSS
677e7a9 WMM AC: Do not fail on unknown IEs in Association Response
fecc2bb WMM AC: Delete tspecs on roaming
20fe745 WMM AC: Print user-priority in wmm_ac_status
730a0d1 nl80211: Always register management frames handler
...
209702d Add possibility to set the setband parameter
ee82e33 Do not trigger the scan during initialization on Android platforms
e69ae5f Reject new SCAN commands if there is a pending request
...
59d7148 nl80211: Provide subtype and reason code for AP SME drivers
9d4ff04 Add external EAPOL transmission option for testing purposes
61fc904 P2P: Handle improper WPS termination on GO during group formation
58b40fd P2P: Clear p2p_go_group_formation_completed on GO start
c155305 Complete sme-connect radio work when clearing connection state
debb2da P2P: Report group removal reason PSK_FAILURE in timeout case
51465a0 The master branch is now used for v2.4 development

Change-Id: I9b9cfa5c5cd4d26b2f3f5595f7c226ac60de6258
diff --git a/wpa_supplicant/mesh_mpm.c b/wpa_supplicant/mesh_mpm.c
new file mode 100644
index 0000000..e7c53ea
--- /dev/null
+++ b/wpa_supplicant/mesh_mpm.c
@@ -0,0 +1,1030 @@
+/*
+ * WPA Supplicant - Basic mesh peer management
+ * Copyright (c) 2013-2014, cozybit, Inc.  All rights reserved.
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+#include "utils/includes.h"
+
+#include "utils/common.h"
+#include "utils/eloop.h"
+#include "common/ieee802_11_defs.h"
+#include "ap/hostapd.h"
+#include "ap/sta_info.h"
+#include "ap/ieee802_11.h"
+#include "wpa_supplicant_i.h"
+#include "driver_i.h"
+#include "mesh_mpm.h"
+#include "mesh_rsn.h"
+
+struct mesh_peer_mgmt_ie {
+	const u8 *proto_id;
+	const u8 *llid;
+	const u8 *plid;
+	const u8 *reason;
+	const u8 *pmk;
+};
+
+static void plink_timer(void *eloop_ctx, void *user_data);
+
+
+enum plink_event {
+	PLINK_UNDEFINED,
+	OPN_ACPT,
+	OPN_RJCT,
+	OPN_IGNR,
+	CNF_ACPT,
+	CNF_RJCT,
+	CNF_IGNR,
+	CLS_ACPT,
+	CLS_IGNR
+};
+
+static const char * const mplstate[] = {
+	[PLINK_LISTEN] = "LISTEN",
+	[PLINK_OPEN_SENT] = "OPEN_SENT",
+	[PLINK_OPEN_RCVD] = "OPEN_RCVD",
+	[PLINK_CNF_RCVD] = "CNF_RCVD",
+	[PLINK_ESTAB] = "ESTAB",
+	[PLINK_HOLDING] = "HOLDING",
+	[PLINK_BLOCKED] = "BLOCKED"
+};
+
+static const char * const mplevent[] = {
+	[PLINK_UNDEFINED] = "UNDEFINED",
+	[OPN_ACPT] = "OPN_ACPT",
+	[OPN_RJCT] = "OPN_RJCT",
+	[OPN_IGNR] = "OPN_IGNR",
+	[CNF_ACPT] = "CNF_ACPT",
+	[CNF_RJCT] = "CNF_RJCT",
+	[CNF_IGNR] = "CNF_IGNR",
+	[CLS_ACPT] = "CLS_ACPT",
+	[CLS_IGNR] = "CLS_IGNR"
+};
+
+
+static int mesh_mpm_parse_peer_mgmt(struct wpa_supplicant *wpa_s,
+				    u8 action_field,
+				    const u8 *ie, size_t len,
+				    struct mesh_peer_mgmt_ie *mpm_ie)
+{
+	os_memset(mpm_ie, 0, sizeof(*mpm_ie));
+
+	/* remove optional PMK at end */
+	if (len >= 16) {
+		len -= 16;
+		mpm_ie->pmk = ie + len - 16;
+	}
+
+	if ((action_field == PLINK_OPEN && len != 4) ||
+	    (action_field == PLINK_CONFIRM && len != 6) ||
+	    (action_field == PLINK_CLOSE && len != 6 && len != 8)) {
+		wpa_msg(wpa_s, MSG_DEBUG, "MPM: Invalid peer mgmt ie");
+		return -1;
+	}
+
+	/* required fields */
+	if (len < 4)
+		return -1;
+	mpm_ie->proto_id = ie;
+	mpm_ie->llid = ie + 2;
+	ie += 4;
+	len -= 4;
+
+	/* close reason is always present at end for close */
+	if (action_field == PLINK_CLOSE) {
+		if (len < 2)
+			return -1;
+		mpm_ie->reason = ie + len - 2;
+		len -= 2;
+	}
+
+	/* plid, present for confirm, and possibly close */
+	if (len)
+		mpm_ie->plid = ie;
+
+	return 0;
+}
+
+
+static int plink_free_count(struct hostapd_data *hapd)
+{
+	if (hapd->max_plinks > hapd->num_plinks)
+		return hapd->max_plinks - hapd->num_plinks;
+	return 0;
+}
+
+
+static u16 copy_supp_rates(struct wpa_supplicant *wpa_s,
+			   struct sta_info *sta,
+			   struct ieee802_11_elems *elems)
+{
+	if (!elems->supp_rates) {
+		wpa_msg(wpa_s, MSG_ERROR, "no supported rates from " MACSTR,
+			MAC2STR(sta->addr));
+		return WLAN_STATUS_UNSPECIFIED_FAILURE;
+	}
+
+	if (elems->supp_rates_len + elems->ext_supp_rates_len >
+	    sizeof(sta->supported_rates)) {
+		wpa_msg(wpa_s, MSG_ERROR,
+			"Invalid supported rates element length " MACSTR
+			" %d+%d", MAC2STR(sta->addr), elems->supp_rates_len,
+			elems->ext_supp_rates_len);
+		return WLAN_STATUS_UNSPECIFIED_FAILURE;
+	}
+
+	sta->supported_rates_len = merge_byte_arrays(
+		sta->supported_rates, sizeof(sta->supported_rates),
+		elems->supp_rates, elems->supp_rates_len,
+		elems->ext_supp_rates, elems->ext_supp_rates_len);
+
+	return WLAN_STATUS_SUCCESS;
+}
+
+
+/* return true if elems from a neighbor match this MBSS */
+static Boolean matches_local(struct wpa_supplicant *wpa_s,
+			     struct ieee802_11_elems *elems)
+{
+	struct mesh_conf *mconf = wpa_s->ifmsh->mconf;
+
+	if (elems->mesh_config_len < 5)
+		return FALSE;
+
+	return (mconf->meshid_len == elems->mesh_id_len &&
+		os_memcmp(mconf->meshid, elems->mesh_id,
+			  elems->mesh_id_len) == 0 &&
+		mconf->mesh_pp_id == elems->mesh_config[0] &&
+		mconf->mesh_pm_id == elems->mesh_config[1] &&
+		mconf->mesh_cc_id == elems->mesh_config[2] &&
+		mconf->mesh_sp_id == elems->mesh_config[3] &&
+		mconf->mesh_auth_id == elems->mesh_config[4]);
+}
+
+
+/* check if local link id is already used with another peer */
+static Boolean llid_in_use(struct wpa_supplicant *wpa_s, u16 llid)
+{
+	struct sta_info *sta;
+	struct hostapd_data *hapd = wpa_s->ifmsh->bss[0];
+
+	for (sta = hapd->sta_list; sta; sta = sta->next) {
+		if (sta->my_lid == llid)
+			return TRUE;
+	}
+
+	return FALSE;
+}
+
+
+/* generate an llid for a link and set to initial state */
+static void mesh_mpm_init_link(struct wpa_supplicant *wpa_s,
+			       struct sta_info *sta)
+{
+	u16 llid;
+
+	do {
+		if (os_get_random((u8 *) &llid, sizeof(llid)) < 0)
+			continue;
+	} while (!llid || llid_in_use(wpa_s, llid));
+
+	sta->my_lid = llid;
+	sta->peer_lid = 0;
+	sta->plink_state = PLINK_LISTEN;
+}
+
+
+static void mesh_mpm_send_plink_action(struct wpa_supplicant *wpa_s,
+				       struct sta_info *sta,
+				       enum plink_action_field type,
+				       u16 close_reason)
+{
+	struct wpabuf *buf;
+	struct hostapd_iface *ifmsh = wpa_s->ifmsh;
+	struct hostapd_data *bss = ifmsh->bss[0];
+	struct mesh_conf *conf = ifmsh->mconf;
+	u8 supp_rates[2 + 2 + 32];
+#ifdef CONFIG_IEEE80211N
+	u8 ht_capa_oper[2 + 26 + 2 + 22];
+#endif /* CONFIG_IEEE80211N */
+	u8 *pos, *cat;
+	u8 ie_len, add_plid = 0;
+	int ret;
+	int ampe = conf->security & MESH_CONF_SEC_AMPE;
+	size_t buf_len;
+
+	if (!sta)
+		return;
+
+	buf_len = 2 +      /* capability info */
+		  2 +      /* AID */
+		  2 + 8 +  /* supported rates */
+		  2 + (32 - 8) +
+		  2 + 32 + /* mesh ID */
+		  2 + 7 +  /* mesh config */
+		  2 + 23 + /* peering management */
+		  2 + 96 + /* AMPE */
+		  2 + 16;  /* MIC */
+#ifdef CONFIG_IEEE80211N
+	if (type != PLINK_CLOSE &&
+	    wpa_s->current_ssid->mesh_ht_mode > CHAN_NO_HT) {
+		buf_len += 2 + 26 + /* HT capabilities */
+			   2 + 22;  /* HT operation */
+	}
+#endif /* CONFIG_IEEE80211N */
+	buf = wpabuf_alloc(buf_len);
+	if (!buf)
+		return;
+
+	cat = wpabuf_mhead_u8(buf);
+	wpabuf_put_u8(buf, WLAN_ACTION_SELF_PROTECTED);
+	wpabuf_put_u8(buf, type);
+
+	if (type != PLINK_CLOSE) {
+		u8 info;
+
+		/* capability info */
+		wpabuf_put_le16(buf, ampe ? IEEE80211_CAP_PRIVACY : 0);
+
+		/* aid */
+		if (type == PLINK_CONFIRM)
+			wpabuf_put_le16(buf, sta->peer_lid);
+
+		/* IE: supp + ext. supp rates */
+		pos = hostapd_eid_supp_rates(bss, supp_rates);
+		pos = hostapd_eid_ext_supp_rates(bss, pos);
+		wpabuf_put_data(buf, supp_rates, pos - supp_rates);
+
+		/* IE: Mesh ID */
+		wpabuf_put_u8(buf, WLAN_EID_MESH_ID);
+		wpabuf_put_u8(buf, conf->meshid_len);
+		wpabuf_put_data(buf, conf->meshid, conf->meshid_len);
+
+		/* IE: mesh conf */
+		wpabuf_put_u8(buf, WLAN_EID_MESH_CONFIG);
+		wpabuf_put_u8(buf, 7);
+		wpabuf_put_u8(buf, conf->mesh_pp_id);
+		wpabuf_put_u8(buf, conf->mesh_pm_id);
+		wpabuf_put_u8(buf, conf->mesh_cc_id);
+		wpabuf_put_u8(buf, conf->mesh_sp_id);
+		wpabuf_put_u8(buf, conf->mesh_auth_id);
+		info = (bss->num_plinks > 63 ? 63 : bss->num_plinks) << 1;
+		/* TODO: Add Connected to Mesh Gate/AS subfields */
+		wpabuf_put_u8(buf, info);
+		/* always forwarding & accepting plinks for now */
+		wpabuf_put_u8(buf, 0x1 | 0x8);
+	} else {	/* Peer closing frame */
+		/* IE: Mesh ID */
+		wpabuf_put_u8(buf, WLAN_EID_MESH_ID);
+		wpabuf_put_u8(buf, conf->meshid_len);
+		wpabuf_put_data(buf, conf->meshid, conf->meshid_len);
+	}
+
+	/* IE: Mesh Peering Management element */
+	ie_len = 4;
+	if (ampe)
+		ie_len += PMKID_LEN;
+	switch (type) {
+	case PLINK_OPEN:
+		break;
+	case PLINK_CONFIRM:
+		ie_len += 2;
+		add_plid = 1;
+		break;
+	case PLINK_CLOSE:
+		ie_len += 2;
+		add_plid = 1;
+		ie_len += 2; /* reason code */
+		break;
+	}
+
+	wpabuf_put_u8(buf, WLAN_EID_PEER_MGMT);
+	wpabuf_put_u8(buf, ie_len);
+	/* peering protocol */
+	if (ampe)
+		wpabuf_put_le16(buf, 1);
+	else
+		wpabuf_put_le16(buf, 0);
+	wpabuf_put_le16(buf, sta->my_lid);
+	if (add_plid)
+		wpabuf_put_le16(buf, sta->peer_lid);
+	if (type == PLINK_CLOSE)
+		wpabuf_put_le16(buf, close_reason);
+	if (ampe) {
+		if (sta->sae == NULL) {
+			wpa_msg(wpa_s, MSG_INFO, "Mesh MPM: no SAE session");
+			goto fail;
+		}
+		mesh_rsn_get_pmkid(wpa_s->mesh_rsn, sta,
+				   wpabuf_put(buf, PMKID_LEN));
+	}
+
+#ifdef CONFIG_IEEE80211N
+	if (type != PLINK_CLOSE &&
+	    wpa_s->current_ssid->mesh_ht_mode > CHAN_NO_HT) {
+		pos = hostapd_eid_ht_capabilities(bss, ht_capa_oper);
+		pos = hostapd_eid_ht_operation(bss, pos);
+		wpabuf_put_data(buf, ht_capa_oper, pos - ht_capa_oper);
+	}
+#endif /* CONFIG_IEEE80211N */
+
+	if (ampe && mesh_rsn_protect_frame(wpa_s->mesh_rsn, sta, cat, buf)) {
+		wpa_msg(wpa_s, MSG_INFO,
+			"Mesh MPM: failed to add AMPE and MIC IE");
+		goto fail;
+	}
+
+	ret = wpa_drv_send_action(wpa_s, wpa_s->assoc_freq, 0,
+				  sta->addr, wpa_s->own_addr, wpa_s->own_addr,
+				  wpabuf_head(buf), wpabuf_len(buf), 0);
+	if (ret < 0)
+		wpa_msg(wpa_s, MSG_INFO,
+			"Mesh MPM: failed to send peering frame");
+
+fail:
+	wpabuf_free(buf);
+}
+
+
+/* configure peering state in ours and driver's station entry */
+static void
+wpa_mesh_set_plink_state(struct wpa_supplicant *wpa_s, struct sta_info *sta,
+			 enum mesh_plink_state state)
+{
+	struct hostapd_sta_add_params params;
+	int ret;
+
+	sta->plink_state = state;
+
+	os_memset(&params, 0, sizeof(params));
+	params.addr = sta->addr;
+	params.plink_state = state;
+	params.set = 1;
+
+	wpa_msg(wpa_s, MSG_DEBUG, "MPM set " MACSTR " into %s",
+		MAC2STR(sta->addr), mplstate[state]);
+	ret = wpa_drv_sta_add(wpa_s, &params);
+	if (ret) {
+		wpa_msg(wpa_s, MSG_ERROR, "Driver failed to set " MACSTR
+			": %d", MAC2STR(sta->addr), ret);
+	}
+}
+
+
+static void mesh_mpm_fsm_restart(struct wpa_supplicant *wpa_s,
+				 struct sta_info *sta)
+{
+	struct hostapd_data *hapd = wpa_s->ifmsh->bss[0];
+
+	eloop_cancel_timeout(plink_timer, wpa_s, sta);
+
+	if (sta->mpm_close_reason == WLAN_REASON_MESH_CLOSE_RCVD) {
+		ap_free_sta(hapd, sta);
+		return;
+	}
+
+	wpa_mesh_set_plink_state(wpa_s, sta, PLINK_LISTEN);
+	sta->my_lid = sta->peer_lid = sta->mpm_close_reason = 0;
+	sta->mpm_retries = 0;
+}
+
+
+static void plink_timer(void *eloop_ctx, void *user_data)
+{
+	struct wpa_supplicant *wpa_s = eloop_ctx;
+	struct sta_info *sta = user_data;
+	u16 reason = 0;
+	struct mesh_conf *conf = wpa_s->ifmsh->mconf;
+
+	switch (sta->plink_state) {
+	case PLINK_OPEN_RCVD:
+	case PLINK_OPEN_SENT:
+		/* retry timer */
+		if (sta->mpm_retries < conf->dot11MeshMaxRetries) {
+			eloop_register_timeout(
+				conf->dot11MeshRetryTimeout / 1000,
+				(conf->dot11MeshRetryTimeout % 1000) * 1000,
+				plink_timer, wpa_s, sta);
+			mesh_mpm_send_plink_action(wpa_s, sta, PLINK_OPEN, 0);
+			sta->mpm_retries++;
+			break;
+		}
+		reason = WLAN_REASON_MESH_MAX_RETRIES;
+		/* fall through on else */
+
+	case PLINK_CNF_RCVD:
+		/* confirm timer */
+		if (!reason)
+			reason = WLAN_REASON_MESH_CONFIRM_TIMEOUT;
+		sta->plink_state = PLINK_HOLDING;
+		eloop_register_timeout(conf->dot11MeshHoldingTimeout / 1000,
+			(conf->dot11MeshHoldingTimeout % 1000) * 1000,
+			plink_timer, wpa_s, sta);
+		mesh_mpm_send_plink_action(wpa_s, sta, PLINK_CLOSE, reason);
+		break;
+	case PLINK_HOLDING:
+		/* holding timer */
+		mesh_mpm_fsm_restart(wpa_s, sta);
+		break;
+	default:
+		break;
+	}
+}
+
+
+/* initiate peering with station */
+static void
+mesh_mpm_plink_open(struct wpa_supplicant *wpa_s, struct sta_info *sta,
+		    enum mesh_plink_state next_state)
+{
+	struct mesh_conf *conf = wpa_s->ifmsh->mconf;
+
+	eloop_cancel_timeout(plink_timer, wpa_s, sta);
+	eloop_register_timeout(conf->dot11MeshRetryTimeout / 1000,
+			       (conf->dot11MeshRetryTimeout % 1000) * 1000,
+			       plink_timer, wpa_s, sta);
+	mesh_mpm_send_plink_action(wpa_s, sta, PLINK_OPEN, 0);
+	wpa_mesh_set_plink_state(wpa_s, sta, next_state);
+}
+
+
+int mesh_mpm_plink_close(struct hostapd_data *hapd,
+			 struct sta_info *sta, void *ctx)
+{
+	struct wpa_supplicant *wpa_s = ctx;
+	int reason = WLAN_REASON_MESH_PEERING_CANCELLED;
+
+	if (sta) {
+		wpa_mesh_set_plink_state(wpa_s, sta, PLINK_HOLDING);
+		mesh_mpm_send_plink_action(wpa_s, sta, PLINK_CLOSE, reason);
+		wpa_printf(MSG_DEBUG, "MPM closing plink sta=" MACSTR,
+			   MAC2STR(sta->addr));
+		eloop_cancel_timeout(plink_timer, wpa_s, sta);
+		return 0;
+	}
+
+	return 1;
+}
+
+
+void mesh_mpm_deinit(struct wpa_supplicant *wpa_s, struct hostapd_iface *ifmsh)
+{
+	struct hostapd_data *hapd = ifmsh->bss[0];
+
+	/* notify peers we're leaving */
+	ap_for_each_sta(hapd, mesh_mpm_plink_close, wpa_s);
+
+	hapd->num_plinks = 0;
+	hostapd_free_stas(hapd);
+}
+
+
+/* for mesh_rsn to indicate this peer has completed authentication, and we're
+ * ready to start AMPE */
+void mesh_mpm_auth_peer(struct wpa_supplicant *wpa_s, const u8 *addr)
+{
+	struct hostapd_data *data = wpa_s->ifmsh->bss[0];
+	struct hostapd_sta_add_params params;
+	struct sta_info *sta;
+	int ret;
+
+	sta = ap_get_sta(data, addr);
+	if (!sta) {
+		wpa_msg(wpa_s, MSG_DEBUG, "no such mesh peer");
+		return;
+	}
+
+	/* TODO: Should do nothing if this STA is already authenticated, but
+	 * the AP code already sets this flag. */
+	sta->flags |= WLAN_STA_AUTH;
+
+	mesh_rsn_init_ampe_sta(wpa_s, sta);
+
+	os_memset(&params, 0, sizeof(params));
+	params.addr = sta->addr;
+	params.flags = WPA_STA_AUTHENTICATED | WPA_STA_AUTHORIZED;
+	params.set = 1;
+
+	wpa_msg(wpa_s, MSG_DEBUG, "MPM authenticating " MACSTR,
+		MAC2STR(sta->addr));
+	ret = wpa_drv_sta_add(wpa_s, &params);
+	if (ret) {
+		wpa_msg(wpa_s, MSG_ERROR,
+			"Driver failed to set " MACSTR ": %d",
+			MAC2STR(sta->addr), ret);
+	}
+
+	if (!sta->my_lid)
+		mesh_mpm_init_link(wpa_s, sta);
+
+	mesh_mpm_plink_open(wpa_s, sta, PLINK_OPEN_SENT);
+}
+
+
+void wpa_mesh_new_mesh_peer(struct wpa_supplicant *wpa_s, const u8 *addr,
+			    struct ieee802_11_elems *elems)
+{
+	struct hostapd_sta_add_params params;
+	struct mesh_conf *conf = wpa_s->ifmsh->mconf;
+	struct hostapd_data *data = wpa_s->ifmsh->bss[0];
+	struct sta_info *sta;
+	struct wpa_ssid *ssid = wpa_s->current_ssid;
+	int ret = 0;
+
+	sta = ap_get_sta(data, addr);
+	if (!sta) {
+		sta = ap_sta_add(data, addr);
+		if (!sta)
+			return;
+	}
+
+	/* initialize sta */
+	if (copy_supp_rates(wpa_s, sta, elems))
+		return;
+
+	mesh_mpm_init_link(wpa_s, sta);
+
+#ifdef CONFIG_IEEE80211N
+	copy_sta_ht_capab(data, sta, elems->ht_capabilities,
+			elems->ht_capabilities_len);
+	update_ht_state(data, sta);
+#endif /* CONFIG_IEEE80211N */
+
+	/* insert into driver */
+	os_memset(&params, 0, sizeof(params));
+	params.supp_rates = sta->supported_rates;
+	params.supp_rates_len = sta->supported_rates_len;
+	params.addr = addr;
+	params.plink_state = sta->plink_state;
+	params.aid = sta->peer_lid;
+	params.listen_interval = 100;
+	params.ht_capabilities = sta->ht_capabilities;
+	params.flags |= WPA_STA_WMM;
+	params.flags_mask |= WPA_STA_AUTHENTICATED;
+	if (conf->security == MESH_CONF_SEC_NONE) {
+		params.flags |= WPA_STA_AUTHORIZED;
+		params.flags |= WPA_STA_AUTHENTICATED;
+	} else {
+		sta->flags |= WLAN_STA_MFP;
+		params.flags |= WPA_STA_MFP;
+	}
+
+	ret = wpa_drv_sta_add(wpa_s, &params);
+	if (ret) {
+		wpa_msg(wpa_s, MSG_ERROR,
+			"Driver failed to insert " MACSTR ": %d",
+			MAC2STR(addr), ret);
+		return;
+	}
+
+	if (ssid && ssid->no_auto_peer) {
+		wpa_msg(wpa_s, MSG_INFO, "will not initiate new peer link with "
+			MACSTR " because of no_auto_peer", MAC2STR(addr));
+		if (data->mesh_pending_auth) {
+			struct os_reltime age;
+			const struct ieee80211_mgmt *mgmt;
+			struct hostapd_frame_info fi;
+
+			mgmt = wpabuf_head(data->mesh_pending_auth);
+			os_reltime_age(&data->mesh_pending_auth_time, &age);
+			if (age.sec < 2 &&
+			    os_memcmp(mgmt->sa, addr, ETH_ALEN) == 0) {
+				wpa_printf(MSG_DEBUG,
+					   "mesh: Process pending Authentication frame from %u.%06u seconds ago",
+					   (unsigned int) age.sec,
+					   (unsigned int) age.usec);
+				os_memset(&fi, 0, sizeof(fi));
+				ieee802_11_mgmt(
+					data,
+					wpabuf_head(data->mesh_pending_auth),
+					wpabuf_len(data->mesh_pending_auth),
+					&fi);
+			}
+			wpabuf_free(data->mesh_pending_auth);
+			data->mesh_pending_auth = NULL;
+		}
+		return;
+	}
+
+	if (conf->security == MESH_CONF_SEC_NONE)
+		mesh_mpm_plink_open(wpa_s, sta, PLINK_OPEN_SENT);
+	else
+		mesh_rsn_auth_sae_sta(wpa_s, sta);
+}
+
+
+void mesh_mpm_mgmt_rx(struct wpa_supplicant *wpa_s, struct rx_mgmt *rx_mgmt)
+{
+	struct hostapd_frame_info fi;
+
+	os_memset(&fi, 0, sizeof(fi));
+	fi.datarate = rx_mgmt->datarate;
+	fi.ssi_signal = rx_mgmt->ssi_signal;
+	ieee802_11_mgmt(wpa_s->ifmsh->bss[0], rx_mgmt->frame,
+			rx_mgmt->frame_len, &fi);
+}
+
+
+static void mesh_mpm_plink_estab(struct wpa_supplicant *wpa_s,
+				 struct sta_info *sta)
+{
+	struct hostapd_data *hapd = wpa_s->ifmsh->bss[0];
+	struct mesh_conf *conf = wpa_s->ifmsh->mconf;
+	u8 seq[6] = {};
+
+	wpa_msg(wpa_s, MSG_INFO, "mesh plink with " MACSTR " established",
+		MAC2STR(sta->addr));
+
+	if (conf->security & MESH_CONF_SEC_AMPE) {
+		wpa_drv_set_key(wpa_s, WPA_ALG_CCMP, sta->addr, 0, 0,
+				seq, sizeof(seq), sta->mtk, sizeof(sta->mtk));
+		wpa_drv_set_key(wpa_s, WPA_ALG_CCMP, sta->addr, 1, 0,
+				seq, sizeof(seq),
+				sta->mgtk, sizeof(sta->mgtk));
+		wpa_drv_set_key(wpa_s, WPA_ALG_IGTK, sta->addr, 4, 0,
+				seq, sizeof(seq),
+				sta->mgtk, sizeof(sta->mgtk));
+
+		wpa_hexdump_key(MSG_DEBUG, "mtk:", sta->mtk, sizeof(sta->mtk));
+		wpa_hexdump_key(MSG_DEBUG, "mgtk:",
+				sta->mgtk, sizeof(sta->mgtk));
+	}
+
+	wpa_mesh_set_plink_state(wpa_s, sta, PLINK_ESTAB);
+	hapd->num_plinks++;
+
+	sta->flags |= WLAN_STA_ASSOC;
+
+	eloop_cancel_timeout(plink_timer, wpa_s, sta);
+
+	/* Send ctrl event */
+	wpa_msg_ctrl(wpa_s, MSG_INFO, MESH_PEER_CONNECTED MACSTR,
+		     MAC2STR(sta->addr));
+}
+
+
+static void mesh_mpm_fsm(struct wpa_supplicant *wpa_s, struct sta_info *sta,
+			 enum plink_event event)
+{
+	struct hostapd_data *hapd = wpa_s->ifmsh->bss[0];
+	struct mesh_conf *conf = wpa_s->ifmsh->mconf;
+	u16 reason = 0;
+
+	wpa_msg(wpa_s, MSG_DEBUG, "MPM " MACSTR " state %s event %s",
+		MAC2STR(sta->addr), mplstate[sta->plink_state],
+		mplevent[event]);
+
+	switch (sta->plink_state) {
+	case PLINK_LISTEN:
+		switch (event) {
+		case CLS_ACPT:
+			mesh_mpm_fsm_restart(wpa_s, sta);
+			break;
+		case OPN_ACPT:
+			mesh_mpm_plink_open(wpa_s, sta, PLINK_OPEN_RCVD);
+			mesh_mpm_send_plink_action(wpa_s, sta, PLINK_CONFIRM,
+						   0);
+			break;
+		default:
+			break;
+		}
+		break;
+	case PLINK_OPEN_SENT:
+		switch (event) {
+		case OPN_RJCT:
+		case CNF_RJCT:
+			reason = WLAN_REASON_MESH_CONFIG_POLICY_VIOLATION;
+			/* fall-through */
+		case CLS_ACPT:
+			wpa_mesh_set_plink_state(wpa_s, sta, PLINK_HOLDING);
+			if (!reason)
+				reason = WLAN_REASON_MESH_CLOSE_RCVD;
+			eloop_register_timeout(
+				conf->dot11MeshHoldingTimeout / 1000,
+				(conf->dot11MeshHoldingTimeout % 1000) * 1000,
+				plink_timer, wpa_s, sta);
+			mesh_mpm_send_plink_action(wpa_s, sta,
+						   PLINK_CLOSE, reason);
+			break;
+		case OPN_ACPT:
+			/* retry timer is left untouched */
+			wpa_mesh_set_plink_state(wpa_s, sta, PLINK_OPEN_RCVD);
+			mesh_mpm_send_plink_action(wpa_s, sta,
+						   PLINK_CONFIRM, 0);
+			break;
+		case CNF_ACPT:
+			wpa_mesh_set_plink_state(wpa_s, sta, PLINK_CNF_RCVD);
+			eloop_register_timeout(
+				conf->dot11MeshConfirmTimeout / 1000,
+				(conf->dot11MeshConfirmTimeout % 1000) * 1000,
+				plink_timer, wpa_s, sta);
+			break;
+		default:
+			break;
+		}
+		break;
+	case PLINK_OPEN_RCVD:
+		switch (event) {
+		case OPN_RJCT:
+		case CNF_RJCT:
+			reason = WLAN_REASON_MESH_CONFIG_POLICY_VIOLATION;
+			/* fall-through */
+		case CLS_ACPT:
+			wpa_mesh_set_plink_state(wpa_s, sta, PLINK_HOLDING);
+			if (!reason)
+				reason = WLAN_REASON_MESH_CLOSE_RCVD;
+			eloop_register_timeout(
+				conf->dot11MeshHoldingTimeout / 1000,
+				(conf->dot11MeshHoldingTimeout % 1000) * 1000,
+				plink_timer, wpa_s, sta);
+			sta->mpm_close_reason = reason;
+			mesh_mpm_send_plink_action(wpa_s, sta,
+						   PLINK_CLOSE, reason);
+			break;
+		case OPN_ACPT:
+			mesh_mpm_send_plink_action(wpa_s, sta,
+						   PLINK_CONFIRM, 0);
+			break;
+		case CNF_ACPT:
+			if (conf->security & MESH_CONF_SEC_AMPE)
+				mesh_rsn_derive_mtk(wpa_s, sta);
+			mesh_mpm_plink_estab(wpa_s, sta);
+			break;
+		default:
+			break;
+		}
+		break;
+	case PLINK_CNF_RCVD:
+		switch (event) {
+		case OPN_RJCT:
+		case CNF_RJCT:
+			reason = WLAN_REASON_MESH_CONFIG_POLICY_VIOLATION;
+			/* fall-through */
+		case CLS_ACPT:
+			wpa_mesh_set_plink_state(wpa_s, sta, PLINK_HOLDING);
+			if (!reason)
+				reason = WLAN_REASON_MESH_CLOSE_RCVD;
+			eloop_register_timeout(
+				conf->dot11MeshHoldingTimeout / 1000,
+				(conf->dot11MeshHoldingTimeout % 1000) * 1000,
+				plink_timer, wpa_s, sta);
+			sta->mpm_close_reason = reason;
+			mesh_mpm_send_plink_action(wpa_s, sta,
+						   PLINK_CLOSE, reason);
+			break;
+		case OPN_ACPT:
+			mesh_mpm_plink_estab(wpa_s, sta);
+			mesh_mpm_send_plink_action(wpa_s, sta,
+						   PLINK_CONFIRM, 0);
+			break;
+		default:
+			break;
+		}
+		break;
+	case PLINK_ESTAB:
+		switch (event) {
+		case CLS_ACPT:
+			wpa_mesh_set_plink_state(wpa_s, sta, PLINK_HOLDING);
+			reason = WLAN_REASON_MESH_CLOSE_RCVD;
+
+			eloop_register_timeout(
+				conf->dot11MeshHoldingTimeout / 1000,
+				(conf->dot11MeshHoldingTimeout % 1000) * 1000,
+				plink_timer, wpa_s, sta);
+			sta->mpm_close_reason = reason;
+
+			wpa_msg(wpa_s, MSG_INFO, "mesh plink with " MACSTR
+				" closed with reason %d",
+				MAC2STR(sta->addr), reason);
+
+			wpa_msg_ctrl(wpa_s, MSG_INFO,
+				     MESH_PEER_DISCONNECTED MACSTR,
+				     MAC2STR(sta->addr));
+
+			hapd->num_plinks--;
+
+			mesh_mpm_send_plink_action(wpa_s, sta,
+						   PLINK_CLOSE, reason);
+			break;
+		case OPN_ACPT:
+			mesh_mpm_send_plink_action(wpa_s, sta,
+						   PLINK_CONFIRM, 0);
+			break;
+		default:
+			break;
+		}
+		break;
+	case PLINK_HOLDING:
+		switch (event) {
+		case CLS_ACPT:
+			mesh_mpm_fsm_restart(wpa_s, sta);
+			break;
+		case OPN_ACPT:
+		case CNF_ACPT:
+		case OPN_RJCT:
+		case CNF_RJCT:
+			reason = sta->mpm_close_reason;
+			mesh_mpm_send_plink_action(wpa_s, sta,
+						   PLINK_CLOSE, reason);
+			break;
+		default:
+			break;
+		}
+		break;
+	default:
+		wpa_msg(wpa_s, MSG_DEBUG,
+			"Unsupported MPM event %s for state %s",
+			mplevent[event], mplstate[sta->plink_state]);
+		break;
+	}
+}
+
+
+void mesh_mpm_action_rx(struct wpa_supplicant *wpa_s,
+			const struct ieee80211_mgmt *mgmt, size_t len)
+{
+	u8 action_field;
+	struct hostapd_data *hapd = wpa_s->ifmsh->bss[0];
+	struct mesh_conf *mconf = wpa_s->ifmsh->mconf;
+	struct sta_info *sta;
+	u16 plid = 0, llid = 0;
+	enum plink_event event;
+	struct ieee802_11_elems elems;
+	struct mesh_peer_mgmt_ie peer_mgmt_ie;
+	const u8 *ies;
+	size_t ie_len;
+	int ret;
+
+	if (mgmt->u.action.category != WLAN_ACTION_SELF_PROTECTED)
+		return;
+
+	action_field = mgmt->u.action.u.slf_prot_action.action;
+	if (action_field != PLINK_OPEN &&
+	    action_field != PLINK_CONFIRM &&
+	    action_field != PLINK_CLOSE)
+		return;
+
+	ies = mgmt->u.action.u.slf_prot_action.variable;
+	ie_len = (const u8 *) mgmt + len -
+		mgmt->u.action.u.slf_prot_action.variable;
+
+	/* at least expect mesh id and peering mgmt */
+	if (ie_len < 2 + 2) {
+		wpa_printf(MSG_DEBUG,
+			   "MPM: Ignore too short action frame %u ie_len %u",
+			   action_field, (unsigned int) ie_len);
+		return;
+	}
+	wpa_printf(MSG_DEBUG, "MPM: Received PLINK action %u", action_field);
+
+	if (action_field == PLINK_OPEN || action_field == PLINK_CONFIRM) {
+		wpa_printf(MSG_DEBUG, "MPM: Capability 0x%x",
+			   WPA_GET_LE16(ies));
+		ies += 2;	/* capability */
+		ie_len -= 2;
+	}
+	if (action_field == PLINK_CONFIRM) {
+		wpa_printf(MSG_DEBUG, "MPM: AID 0x%x", WPA_GET_LE16(ies));
+		ies += 2;	/* aid */
+		ie_len -= 2;
+	}
+
+	/* check for mesh peering, mesh id and mesh config IEs */
+	if (ieee802_11_parse_elems(ies, ie_len, &elems, 0) == ParseFailed) {
+		wpa_printf(MSG_DEBUG, "MPM: Failed to parse PLINK IEs");
+		return;
+	}
+	if (!elems.peer_mgmt) {
+		wpa_printf(MSG_DEBUG,
+			   "MPM: No Mesh Peering Management element");
+		return;
+	}
+	if (action_field != PLINK_CLOSE) {
+		if (!elems.mesh_id || !elems.mesh_config) {
+			wpa_printf(MSG_DEBUG,
+				   "MPM: No Mesh ID or Mesh Configuration element");
+			return;
+		}
+
+		if (!matches_local(wpa_s, &elems)) {
+			wpa_printf(MSG_DEBUG,
+				   "MPM: Mesh ID or Mesh Configuration element do not match local MBSS");
+			return;
+		}
+	}
+
+	ret = mesh_mpm_parse_peer_mgmt(wpa_s, action_field,
+				       elems.peer_mgmt,
+				       elems.peer_mgmt_len,
+				       &peer_mgmt_ie);
+	if (ret) {
+		wpa_printf(MSG_DEBUG, "MPM: Mesh parsing rejected frame");
+		return;
+	}
+
+	/* the sender's llid is our plid and vice-versa */
+	plid = WPA_GET_LE16(peer_mgmt_ie.llid);
+	if (peer_mgmt_ie.plid)
+		llid = WPA_GET_LE16(peer_mgmt_ie.plid);
+	wpa_printf(MSG_DEBUG, "MPM: plid=0x%x llid=0x%x", plid, llid);
+
+	sta = ap_get_sta(hapd, mgmt->sa);
+	if (!sta) {
+		wpa_printf(MSG_DEBUG, "MPM: No STA entry for peer");
+		return;
+	}
+
+#ifdef CONFIG_SAE
+	/* peer is in sae_accepted? */
+	if (sta->sae && sta->sae->state != SAE_ACCEPTED) {
+		wpa_printf(MSG_DEBUG, "MPM: SAE not yet accepted for peer");
+		return;
+	}
+#endif /* CONFIG_SAE */
+
+	if (!sta->my_lid)
+		mesh_mpm_init_link(wpa_s, sta);
+
+	if ((mconf->security & MESH_CONF_SEC_AMPE) &&
+	    mesh_rsn_process_ampe(wpa_s, sta, &elems,
+				  &mgmt->u.action.category,
+				  ies, ie_len)) {
+		wpa_printf(MSG_DEBUG, "MPM: RSN process rejected frame");
+		return;
+	}
+
+	if (sta->plink_state == PLINK_BLOCKED) {
+		wpa_printf(MSG_DEBUG, "MPM: PLINK_BLOCKED");
+		return;
+	}
+
+	/* Now we will figure out the appropriate event... */
+	switch (action_field) {
+	case PLINK_OPEN:
+		if (plink_free_count(hapd) == 0) {
+			event = OPN_IGNR;
+			wpa_printf(MSG_INFO,
+				   "MPM: Peer link num over quota(%d)",
+				   hapd->max_plinks);
+		} else if (sta->peer_lid && sta->peer_lid != plid) {
+			event = OPN_IGNR;
+		} else {
+			sta->peer_lid = plid;
+			event = OPN_ACPT;
+		}
+		break;
+	case PLINK_CONFIRM:
+		if (plink_free_count(hapd) == 0) {
+			event = CNF_IGNR;
+			wpa_printf(MSG_INFO,
+				   "MPM: Peer link num over quota(%d)",
+				   hapd->max_plinks);
+		} else if (sta->my_lid != llid ||
+			   (sta->peer_lid && sta->peer_lid != plid)) {
+			event = CNF_IGNR;
+		} else {
+			if (!sta->peer_lid)
+				sta->peer_lid = plid;
+			event = CNF_ACPT;
+		}
+		break;
+	case PLINK_CLOSE:
+		if (sta->plink_state == PLINK_ESTAB)
+			/* Do not check for llid or plid. This does not
+			 * follow the standard but since multiple plinks
+			 * per cand are not supported, it is necessary in
+			 * order to avoid a livelock when MP A sees an
+			 * establish peer link to MP B but MP B does not
+			 * see it. This can be caused by a timeout in
+			 * B's peer link establishment or B being
+			 * restarted.
+			 */
+			event = CLS_ACPT;
+		else if (sta->peer_lid != plid)
+			event = CLS_IGNR;
+		else if (peer_mgmt_ie.plid && sta->my_lid != llid)
+			event = CLS_IGNR;
+		else
+			event = CLS_ACPT;
+		break;
+	default:
+		/*
+		 * This cannot be hit due to the action_field check above, but
+		 * compilers may not be able to figure that out and can warn
+		 * about uninitialized event below.
+		 */
+		return;
+	}
+	mesh_mpm_fsm(wpa_s, sta, event);
+}
+
+
+/* called by ap_free_sta */
+void mesh_mpm_free_sta(struct sta_info *sta)
+{
+	eloop_cancel_timeout(plink_timer, ELOOP_ALL_CTX, sta);
+	eloop_cancel_timeout(mesh_auth_timer, ELOOP_ALL_CTX, sta);
+}