[RFC] AP: Add support for full station state

Ilan Peer ilan.peer at intel.com
Thu Nov 26 09:21:50 PST 2015


From: Ayala Beker <ayala.beker at intel.com>

Add support for drivers that support full
AP client state, i.e., can handle adding stations
that are not associated yet. For such drivers,
add a station after processing the authentication
request, instead of adding it in the association
response callback.

Doing so is beneficial in cases that the driver
cannot handle the add station request, in which
case it is useless to perform the complete
connection establishment.

Signed-off-by: Ayala Beker <ayala.beker at intel.com>
---
 src/ap/ap_drv_ops.c               |  19 ++-
 src/ap/ap_drv_ops.h               |  14 +-
 src/ap/ieee802_11.c               | 276 +++++++++++++++++++++++++++-----------
 src/ap/sta_info.c                 |   9 +-
 src/ap/sta_info.h                 |   1 +
 src/drivers/driver.h              |  20 ++-
 src/drivers/driver_nl80211.c      |  62 ++++++++-
 src/drivers/driver_nl80211_capa.c |   3 +
 8 files changed, 303 insertions(+), 101 deletions(-)

diff --git a/src/ap/ap_drv_ops.c b/src/ap/ap_drv_ops.c
index b390450..3b92890 100644
--- a/src/ap/ap_drv_ops.c
+++ b/src/ap/ap_drv_ops.c
@@ -33,6 +33,10 @@ u32 hostapd_sta_flags_to_drv(u32 flags)
 		res |= WPA_STA_SHORT_PREAMBLE;
 	if (flags & WLAN_STA_MFP)
 		res |= WPA_STA_MFP;
+	if (flags & WLAN_STA_AUTH)
+		res |= WPA_STA_AUTHENTICATED;
+	if (flags & WLAN_STA_ASSOC)
+		res |= WPA_STA_ASSOCIATED;
 	return res;
 }
 
@@ -384,13 +388,13 @@ int hostapd_sta_assoc(struct hostapd_data *hapd, const u8 *addr,
 }
 
 
-int hostapd_sta_add(struct hostapd_data *hapd,
-		    const u8 *addr, u16 aid, u16 capability,
-		    const u8 *supp_rates, size_t supp_rates_len,
-		    u16 listen_interval,
-		    const struct ieee80211_ht_capabilities *ht_capab,
-		    const struct ieee80211_vht_capabilities *vht_capab,
-		    u32 flags, u8 qosinfo, u8 vht_opmode)
+int hostapd_sta_add_set(struct hostapd_data *hapd,
+			const u8 *addr, u16 aid, u16 capability,
+			const u8 *supp_rates, size_t supp_rates_len,
+			u16 listen_interval,
+			const struct ieee80211_ht_capabilities *ht_capab,
+			const struct ieee80211_vht_capabilities *vht_capab,
+			u32 flags, u8 qosinfo, u8 vht_opmode, int set)
 {
 	struct hostapd_sta_add_params params;
 
@@ -412,6 +416,7 @@ int hostapd_sta_add(struct hostapd_data *hapd,
 	params.vht_opmode = vht_opmode;
 	params.flags = hostapd_sta_flags_to_drv(flags);
 	params.qosinfo = qosinfo;
+	params.set = set;
 	return hapd->driver->sta_add(hapd->drv_priv, &params);
 }
 
diff --git a/src/ap/ap_drv_ops.h b/src/ap/ap_drv_ops.h
index 5a1e28e..ab93f3d 100644
--- a/src/ap/ap_drv_ops.h
+++ b/src/ap/ap_drv_ops.h
@@ -35,13 +35,13 @@ int hostapd_vlan_if_add(struct hostapd_data *hapd, const char *ifname);
 int hostapd_vlan_if_remove(struct hostapd_data *hapd, const char *ifname);
 int hostapd_set_wds_sta(struct hostapd_data *hapd, char *ifname_wds,
 			const u8 *addr, int aid, int val);
-int hostapd_sta_add(struct hostapd_data *hapd,
-		    const u8 *addr, u16 aid, u16 capability,
-		    const u8 *supp_rates, size_t supp_rates_len,
-		    u16 listen_interval,
-		    const struct ieee80211_ht_capabilities *ht_capab,
-		    const struct ieee80211_vht_capabilities *vht_capab,
-		    u32 flags, u8 qosinfo, u8 vht_opmode);
+int hostapd_sta_add_set(struct hostapd_data *hapd,
+			const u8 *addr, u16 aid, u16 capability,
+			const u8 *supp_rates, size_t supp_rates_len,
+			u16 listen_interval,
+			const struct ieee80211_ht_capabilities *ht_capab,
+			const struct ieee80211_vht_capabilities *vht_capab,
+			u32 flags, u8 qosinfo, u8 vht_opmode, int set);
 int hostapd_set_privacy(struct hostapd_data *hapd, int enabled);
 int hostapd_set_generic_elem(struct hostapd_data *hapd, const u8 *elem,
 			     size_t elem_len);
diff --git a/src/ap/ieee802_11.c b/src/ap/ieee802_11.c
index 01b514e..59a648b 100644
--- a/src/ap/ieee802_11.c
+++ b/src/ap/ieee802_11.c
@@ -250,19 +250,20 @@ static u16 auth_shared_key(struct hostapd_data *hapd, struct sta_info *sta,
 #endif /* CONFIG_NO_RC4 */
 
 
-static void send_auth_reply(struct hostapd_data *hapd,
-			    const u8 *dst, const u8 *bssid,
-			    u16 auth_alg, u16 auth_transaction, u16 resp,
-			    const u8 *ies, size_t ies_len)
+static int send_auth_reply(struct hostapd_data *hapd,
+			   const u8 *dst, const u8 *bssid,
+			   u16 auth_alg, u16 auth_transaction, u16 resp,
+			   const u8 *ies, size_t ies_len)
 {
 	struct ieee80211_mgmt *reply;
 	u8 *buf;
 	size_t rlen;
+	int reply_res;
 
 	rlen = IEEE80211_HDRLEN + sizeof(reply->u.auth) + ies_len;
 	buf = os_zalloc(rlen);
 	if (buf == NULL)
-		return;
+		return -1;
 
 	reply = (struct ieee80211_mgmt *) buf;
 	reply->frame_control = IEEE80211_FC(WLAN_FC_TYPE_MGMT,
@@ -282,10 +283,16 @@ static void send_auth_reply(struct hostapd_data *hapd,
 		   " auth_alg=%d auth_transaction=%d resp=%d (IE len=%lu)",
 		   MAC2STR(dst), auth_alg, auth_transaction,
 		   resp, (unsigned long) ies_len);
-	if (hostapd_drv_send_mlme(hapd, reply, rlen, 0) < 0)
-		wpa_printf(MSG_INFO, "send_auth_reply: send");
+	if (hostapd_drv_send_mlme(hapd, reply, rlen, 0) < 0) {
+		wpa_printf(MSG_INFO, "send_auth_reply: send failed");
+		reply_res = WLAN_STATUS_UNSPECIFIED_FAILURE;
+	} else {
+		reply_res = WLAN_STATUS_SUCCESS;
+	}
 
 	os_free(buf);
+
+	return reply_res;
 }
 
 
@@ -296,17 +303,22 @@ static void handle_auth_ft_finish(void *ctx, const u8 *dst, const u8 *bssid,
 {
 	struct hostapd_data *hapd = ctx;
 	struct sta_info *sta;
+	int reply_res;
 
-	send_auth_reply(hapd, dst, bssid, WLAN_AUTH_FT, auth_transaction,
-			status, ies, ies_len);
-
-	if (status != WLAN_STATUS_SUCCESS)
-		return;
+	reply_res = send_auth_reply(hapd, dst, bssid, WLAN_AUTH_FT,
+				    auth_transaction, status, ies, ies_len);
 
 	sta = ap_get_sta(hapd, dst);
 	if (sta == NULL)
 		return;
 
+	if (sta->added_unassoc && (reply_res != WLAN_STATUS_SUCCESS ||
+				   status != WLAN_STATUS_SUCCESS)) {
+		hostapd_drv_sta_remove(hapd, sta->addr);
+		sta->added_unassoc = 0;
+		return;
+	}
+
 	hostapd_logger(hapd, dst, HOSTAPD_MODULE_IEEE80211,
 		       HOSTAPD_LEVEL_DEBUG, "authentication OK (FT)");
 	sta->flags |= WLAN_STA_AUTH;
@@ -369,18 +381,19 @@ static int auth_sae_send_commit(struct hostapd_data *hapd,
 				const u8 *bssid, int update)
 {
 	struct wpabuf *data;
+	int reply_res;
 
 	data = auth_build_sae_commit(hapd, sta, update);
 	if (data == NULL)
 		return WLAN_STATUS_UNSPECIFIED_FAILURE;
 
-	send_auth_reply(hapd, sta->addr, bssid,
-			WLAN_AUTH_SAE, 1, WLAN_STATUS_SUCCESS,
-			wpabuf_head(data), wpabuf_len(data));
+	reply_res = send_auth_reply(hapd, sta->addr, bssid, WLAN_AUTH_SAE, 1,
+				    WLAN_STATUS_SUCCESS, wpabuf_head(data),
+				    wpabuf_len(data));
 
 	wpabuf_free(data);
 
-	return WLAN_STATUS_SUCCESS;
+	return reply_res;
 }
 
 
@@ -389,18 +402,19 @@ static int auth_sae_send_confirm(struct hostapd_data *hapd,
 				 const u8 *bssid)
 {
 	struct wpabuf *data;
+	int reply_res;
 
 	data = auth_build_sae_confirm(hapd, sta);
 	if (data == NULL)
 		return WLAN_STATUS_UNSPECIFIED_FAILURE;
 
-	send_auth_reply(hapd, sta->addr, bssid,
-			WLAN_AUTH_SAE, 2, WLAN_STATUS_SUCCESS,
-			wpabuf_head(data), wpabuf_len(data));
+	reply_res = send_auth_reply(hapd, sta->addr, bssid, WLAN_AUTH_SAE, 2,
+				    WLAN_STATUS_SUCCESS, wpabuf_head(data),
+				    wpabuf_len(data));
 
 	wpabuf_free(data);
 
-	return WLAN_STATUS_SUCCESS;
+	return reply_res;
 }
 
 
@@ -698,15 +712,20 @@ static void handle_auth_sae(struct hostapd_data *hapd, struct sta_info *sta,
 			    const struct ieee80211_mgmt *mgmt, size_t len,
 			    u16 auth_transaction, u16 status_code)
 {
-	u16 resp = WLAN_STATUS_SUCCESS;
+	int resp = WLAN_STATUS_SUCCESS;
 	struct wpabuf *data = NULL;
 
 	if (!sta->sae) {
-		if (auth_transaction != 1 || status_code != WLAN_STATUS_SUCCESS)
-			return;
+		if (auth_transaction != 1 ||
+		    status_code != WLAN_STATUS_SUCCESS) {
+			resp = -1;
+			goto remove_sta;
+		}
 		sta->sae = os_zalloc(sizeof(*sta->sae));
-		if (sta->sae == NULL)
-			return;
+		if (!sta->sae) {
+			resp = -1;
+			goto remove_sta;
+		}
 		sta->sae->state = SAE_NOTHING;
 		sta->sae->sync = 0;
 	}
@@ -746,7 +765,8 @@ static void handle_auth_sae(struct hostapd_data *hapd, struct sta_info *sta,
 			if (sta->sae->tmp->anti_clogging_token == NULL) {
 				wpa_printf(MSG_ERROR,
 					   "SAE: Failed to alloc for anti-clogging token");
-				return;
+				resp = WLAN_STATUS_UNSPECIFIED_FAILURE;
+				goto remove_sta;
 			}
 
 			/*
@@ -756,10 +776,11 @@ static void handle_auth_sae(struct hostapd_data *hapd, struct sta_info *sta,
 			 * Authentication frame, and the commit-scalar and
 			 * COMMIT-ELEMENT previously sent.
 			 */
-			if (auth_sae_send_commit(hapd, sta, mgmt->bssid, 0)) {
+			resp = auth_sae_send_commit(hapd, sta, mgmt->bssid, 0);
+			if (resp != WLAN_STATUS_SUCCESS) {
 				wpa_printf(MSG_ERROR,
 					   "SAE: Failed to send commit message");
-				return;
+				goto remove_sta;
 			}
 			sta->sae->state = SAE_COMMITTED;
 			sta->sae->sync = 0;
@@ -768,7 +789,7 @@ static void handle_auth_sae(struct hostapd_data *hapd, struct sta_info *sta,
 		}
 
 		if (status_code != WLAN_STATUS_SUCCESS)
-			return;
+			goto remove_sta;
 
 		resp = sae_parse_commit(sta->sae, mgmt->u.auth.variable,
 					((const u8 *) mgmt) + len -
@@ -778,14 +799,15 @@ static void handle_auth_sae(struct hostapd_data *hapd, struct sta_info *sta,
 			wpa_printf(MSG_DEBUG,
 				   "SAE: Drop commit message from " MACSTR " due to reflection attack",
 				   MAC2STR(sta->addr));
-			return;
+			goto remove_sta;
 		}
 		if (token && check_sae_token(hapd, sta->addr, token, token_len)
 		    < 0) {
 			wpa_printf(MSG_DEBUG, "SAE: Drop commit message with "
 				   "incorrect token from " MACSTR,
 				   MAC2STR(sta->addr));
-			return;
+			resp = WLAN_STATUS_UNSPECIFIED_FAILURE;
+			goto remove_sta;
 		}
 
 		if (resp != WLAN_STATUS_SUCCESS)
@@ -810,7 +832,7 @@ static void handle_auth_sae(struct hostapd_data *hapd, struct sta_info *sta,
 			       "SAE authentication (RX confirm, status=%u)",
 			       status_code);
 		if (status_code != WLAN_STATUS_SUCCESS)
-			return;
+			goto remove_sta;
 		if (sta->sae->state >= SAE_CONFIRMED ||
 		    !(hapd->conf->mesh & MESH_ENABLED)) {
 			if (sae_check_confirm(sta->sae, mgmt->u.auth.variable,
@@ -827,7 +849,7 @@ static void handle_auth_sae(struct hostapd_data *hapd, struct sta_info *sta,
 			       "unexpected SAE authentication transaction %u (status=%u)",
 			       auth_transaction, status_code);
 		if (status_code != WLAN_STATUS_SUCCESS)
-			return;
+			goto remove_sta;
 		resp = WLAN_STATUS_UNKNOWN_AUTH_TRANSACTION;
 	}
 
@@ -838,6 +860,13 @@ reply:
 				data ? wpabuf_head(data) : (u8 *) "",
 				data ? wpabuf_len(data) : 0);
 	}
+
+remove_sta:
+	if (sta->added_unassoc && (resp != WLAN_STATUS_SUCCESS ||
+				   status_code != WLAN_STATUS_SUCCESS)) {
+		hostapd_drv_sta_remove(hapd, sta->addr);
+		sta->added_unassoc = 0;
+	}
 	wpabuf_free(data);
 }
 
@@ -882,7 +911,7 @@ static void handle_auth(struct hostapd_data *hapd,
 	u16 auth_alg, auth_transaction, status_code;
 	u16 resp = WLAN_STATUS_SUCCESS;
 	struct sta_info *sta = NULL;
-	int res;
+	int res, reply_res;
 	u16 fc;
 	const u8 *challenge = NULL;
 	u32 session_timeout, acct_interim_interval;
@@ -1132,6 +1161,45 @@ static void handle_auth(struct hostapd_data *hapd,
 	else
 		ap_sta_no_session_timeout(hapd, sta);
 
+	/*
+	 * If driver supports full AP client state, add a station to driver
+	 * before send AUTH reply, to make sure the driver has resources,
+	 * and not to go through the entire auth/assoc handshake,
+	 * and fail it at the end.
+	 * If this is not the first transaction, in a multi-step authentication
+	 * algorithm, the station already exists in the driver
+	 * (sta->added_unassoc = 1) so skip it.
+	 * In mesh mode station is already added to the driver once
+	 * NEW_PEER_CANDIDATE event is received.
+	 */
+	if (FULL_AP_CLIENT_STATE_SUPPORTED(hapd->iface->drv_flags) &&
+	    !(hapd->conf->mesh & MESH_ENABLED) &&
+	    !(sta->added_unassoc)) {
+		/*
+		 * If a station that is already associated to the AP, is trying
+		 * to authenticate again, remove the STA entry, in order to make
+		 * sure the STA PS state gets cleared and configuration gets
+		 * updated.
+		 * To handle this, station's added_unassoc flag is cleared once
+		 * the station has completed association.
+		 */
+		hostapd_drv_sta_remove(hapd, sta->addr);
+		sta->flags &= ~(WLAN_STA_ASSOC | WLAN_STA_AUTH |
+				WLAN_STA_AUTHORIZED);
+
+		if (hostapd_sta_add_set(hapd, sta->addr, 0, 0, 0, 0, 0,
+					NULL, NULL, sta->flags, 0, 0, 0)) {
+			hostapd_logger(hapd, sta->addr,
+				       HOSTAPD_MODULE_IEEE80211,
+				       HOSTAPD_LEVEL_NOTICE,
+				       "Could not add STA to kernel driver");
+			resp = WLAN_STATUS_AP_UNABLE_TO_HANDLE_NEW_STA;
+			goto fail;
+		} else {
+			sta->added_unassoc = 1;
+		}
+	}
+
 	switch (auth_alg) {
 	case WLAN_AUTH_OPEN:
 		hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211,
@@ -1205,8 +1273,15 @@ static void handle_auth(struct hostapd_data *hapd,
 	os_free(radius_cui);
 	hostapd_free_psk_list(psk);
 
-	send_auth_reply(hapd, mgmt->sa, mgmt->bssid, auth_alg,
-			auth_transaction + 1, resp, resp_ies, resp_ies_len);
+	reply_res = send_auth_reply(hapd, mgmt->sa, mgmt->bssid, auth_alg,
+				    auth_transaction + 1, resp, resp_ies,
+				    resp_ies_len);
+
+	if (sta && sta->added_unassoc && (resp != WLAN_STATUS_SUCCESS ||
+					  reply_res != WLAN_STATUS_SUCCESS)) {
+		hostapd_drv_sta_remove(hapd, sta->addr);
+		sta->added_unassoc = 0;
+	}
 }
 
 
@@ -1646,9 +1721,9 @@ static void send_deauth(struct hostapd_data *hapd, const u8 *addr,
 }
 
 
-static void send_assoc_resp(struct hostapd_data *hapd, struct sta_info *sta,
-			    u16 status_code, int reassoc, const u8 *ies,
-			    size_t ies_len)
+static u16 send_assoc_resp(struct hostapd_data *hapd, struct sta_info *sta,
+			   u16 status_code, int reassoc, const u8 *ies,
+			   size_t ies_len)
 {
 	int send_len;
 	u8 buf[sizeof(struct ieee80211_mgmt) + 1024];
@@ -1768,9 +1843,13 @@ static void send_assoc_resp(struct hostapd_data *hapd, struct sta_info *sta,
 
 	send_len += p - reply->u.assoc_resp.variable;
 
-	if (hostapd_drv_send_mlme(hapd, reply, send_len, 0) < 0)
+	if (hostapd_drv_send_mlme(hapd, reply, send_len, 0) < 0) {
 		wpa_printf(MSG_INFO, "Failed to send assoc resp: %s",
 			   strerror(errno));
+		return WLAN_STATUS_UNSPECIFIED_FAILURE;
+	}
+
+	return WLAN_STATUS_SUCCESS;
 }
 
 
@@ -1779,7 +1858,7 @@ static void handle_assoc(struct hostapd_data *hapd,
 			 int reassoc)
 {
 	u16 capab_info, listen_interval, seq_ctrl, fc;
-	u16 resp = WLAN_STATUS_SUCCESS;
+	u16 resp = WLAN_STATUS_SUCCESS, reply_res;
 	const u8 *pos;
 	int left, i;
 	struct sta_info *sta;
@@ -1846,6 +1925,12 @@ static void handle_assoc(struct hostapd_data *hapd,
 		wpa_printf(MSG_DEBUG, "FT: Allow STA " MACSTR " to associate "
 			   "prior to authentication since it is using "
 			   "over-the-DS FT", MAC2STR(mgmt->sa));
+
+		/*
+		 * Mark station as authenticated, to avoid adding station
+		 * entry in the driver as associated and not authenticated
+		 */
+		sta->flags |= WLAN_STA_AUTH;
 	} else
 #endif /* CONFIG_IEEE80211R */
 	if (sta == NULL || (sta->flags & WLAN_STA_AUTH) == 0) {
@@ -1973,7 +2058,12 @@ static void handle_assoc(struct hostapd_data *hapd,
 	sta->timeout_next = STA_NULLFUNC;
 
  fail:
-	send_assoc_resp(hapd, sta, resp, reassoc, pos, left);
+	reply_res = send_assoc_resp(hapd, sta, resp, reassoc, pos, left);
+	if (sta->added_unassoc && (resp != WLAN_STATUS_SUCCESS ||
+				   reply_res != WLAN_STATUS_SUCCESS)) {
+		hostapd_drv_sta_remove(hapd, sta->addr);
+		sta->added_unassoc = 0;
+	}
 }
 
 
@@ -2015,6 +2105,7 @@ static void handle_disassoc(struct hostapd_data *hapd,
 		hostapd_drv_br_delete_ip_neigh(hapd, 4, (u8 *) &sta->ipaddr);
 	ap_sta_ip6addr_del(hapd, sta);
 	hostapd_drv_sta_remove(hapd, sta->addr);
+	sta->added_unassoc = 0;
 
 	if (sta->timeout_next == STA_NULLFUNC ||
 	    sta->timeout_next == STA_DISASSOC) {
@@ -2391,28 +2482,28 @@ static void handle_auth_cb(struct hostapd_data *hapd,
 	u16 auth_alg, auth_transaction, status_code;
 	struct sta_info *sta;
 
+	sta = ap_get_sta(hapd, mgmt->da);
+	if (!sta) {
+		wpa_printf(MSG_INFO, "handle_auth_cb: STA " MACSTR " not found",
+			   MAC2STR(mgmt->da));
+		return;
+	}
+
+	auth_alg = le_to_host16(mgmt->u.auth.auth_alg);
+	auth_transaction = le_to_host16(mgmt->u.auth.auth_transaction);
+	status_code = le_to_host16(mgmt->u.auth.status_code);
+
 	if (!ok) {
 		hostapd_logger(hapd, mgmt->da, HOSTAPD_MODULE_IEEE80211,
 			       HOSTAPD_LEVEL_NOTICE,
 			       "did not acknowledge authentication response");
-		return;
+		goto fail;
 	}
 
 	if (len < IEEE80211_HDRLEN + sizeof(mgmt->u.auth)) {
 		wpa_printf(MSG_INFO, "handle_auth_cb - too short payload (len=%lu)",
 			   (unsigned long) len);
-		return;
-	}
-
-	auth_alg = le_to_host16(mgmt->u.auth.auth_alg);
-	auth_transaction = le_to_host16(mgmt->u.auth.auth_transaction);
-	status_code = le_to_host16(mgmt->u.auth.status_code);
-
-	sta = ap_get_sta(hapd, mgmt->da);
-	if (!sta) {
-		wpa_printf(MSG_INFO, "handle_auth_cb: STA " MACSTR " not found",
-			   MAC2STR(mgmt->da));
-		return;
+		goto fail;
 	}
 
 	if (status_code == WLAN_STATUS_SUCCESS &&
@@ -2421,6 +2512,15 @@ static void handle_auth_cb(struct hostapd_data *hapd,
 		hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211,
 			       HOSTAPD_LEVEL_INFO, "authenticated");
 		sta->flags |= WLAN_STA_AUTH;
+		if (sta->added_unassoc)
+			hostapd_set_sta_flags(hapd, sta);
+		return;
+	}
+
+fail:
+	if (status_code != WLAN_STATUS_SUCCESS && sta->added_unassoc) {
+		hostapd_drv_sta_remove(hapd, sta->addr);
+		sta->added_unassoc = 0;
 	}
 }
 
@@ -2459,13 +2559,6 @@ static void handle_assoc_cb(struct hostapd_data *hapd,
 	struct ieee80211_ht_capabilities ht_cap;
 	struct ieee80211_vht_capabilities vht_cap;
 
-	if (len < IEEE80211_HDRLEN + (reassoc ? sizeof(mgmt->u.reassoc_resp) :
-				      sizeof(mgmt->u.assoc_resp))) {
-		wpa_printf(MSG_INFO, "handle_assoc_cb(reassoc=%d) - too short payload (len=%lu)",
-			   reassoc, (unsigned long) len);
-		return;
-	}
-
 	sta = ap_get_sta(hapd, mgmt->da);
 	if (!sta) {
 		wpa_printf(MSG_INFO, "handle_assoc_cb: STA " MACSTR " not found",
@@ -2473,12 +2566,20 @@ static void handle_assoc_cb(struct hostapd_data *hapd,
 		return;
 	}
 
+	if (len < IEEE80211_HDRLEN + (reassoc ? sizeof(mgmt->u.reassoc_resp) :
+				      sizeof(mgmt->u.assoc_resp))) {
+		wpa_printf(MSG_INFO,
+			   "handle_assoc_cb(reassoc=%d) - too short payload (len=%lu)",
+			   reassoc, (unsigned long)len);
+		goto remove_sta;
+	}
+
 	if (!ok) {
 		hostapd_logger(hapd, mgmt->da, HOSTAPD_MODULE_IEEE80211,
 			       HOSTAPD_LEVEL_DEBUG,
 			       "did not acknowledge association response");
 		sta->flags &= ~WLAN_STA_ASSOC_REQ_OK;
-		return;
+		goto remove_sta;
 	}
 
 	if (reassoc)
@@ -2522,10 +2623,14 @@ static void handle_assoc_cb(struct hostapd_data *hapd,
 
 	/*
 	 * Remove the STA entry in order to make sure the STA PS state gets
-	 * cleared and configuration gets updated in case of reassociation back
-	 * to the same AP.
+	 * cleared and configuration gets updated.
+	 * This is relevant for cases, such as FT over the DS, where a station
+	 * re-associates back to the same AP but skips the authentication flow.
+	 * Or, if working with a driver that doesn't support full AP client
+	 * state.
 	 */
-	hostapd_drv_sta_remove(hapd, sta->addr);
+	if (!sta->added_unassoc)
+		hostapd_drv_sta_remove(hapd, sta->addr);
 
 #ifdef CONFIG_IEEE80211N
 	if (sta->flags & WLAN_STA_HT)
@@ -2536,22 +2641,33 @@ static void handle_assoc_cb(struct hostapd_data *hapd,
 		hostapd_get_vht_capab(hapd, sta->vht_capabilities, &vht_cap);
 #endif /* CONFIG_IEEE80211AC */
 
-	if (hostapd_sta_add(hapd, sta->addr, sta->aid, sta->capability,
-			    sta->supported_rates, sta->supported_rates_len,
-			    sta->listen_interval,
-			    sta->flags & WLAN_STA_HT ? &ht_cap : NULL,
-			    sta->flags & WLAN_STA_VHT ? &vht_cap : NULL,
-			    sta->flags, sta->qosinfo, sta->vht_opmode)) {
-		hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211,
-			       HOSTAPD_LEVEL_NOTICE,
-			       "Could not add STA to kernel driver");
-
+	if (hostapd_sta_add_set(hapd, sta->addr, sta->aid, sta->capability,
+				sta->supported_rates, sta->supported_rates_len,
+				sta->listen_interval,
+				sta->flags & WLAN_STA_HT ? &ht_cap : NULL,
+				sta->flags & WLAN_STA_VHT ? &vht_cap : NULL,
+				sta->flags, sta->qosinfo, sta->vht_opmode,
+				sta->added_unassoc)) {
+		hostapd_logger(hapd, sta->addr,
+			       HOSTAPD_MODULE_IEEE80211, HOSTAPD_LEVEL_NOTICE,
+			       "Could not %s STA to kernel driver",
+			       sta->added_unassoc ? "set" : "add");
 		ap_sta_disconnect(hapd, sta, sta->addr,
 				  WLAN_REASON_DISASSOC_AP_BUSY);
-
+		if (sta->added_unassoc)
+			goto remove_sta;
 		return;
 	}
 
+	/*
+	 * added_unassoc flag is set for a station that was added to the driver
+	 * in unassociated state.
+	 * Clear this flag once the station has completed association, to make
+	 * sure STA entry will be cleared from the driver in case of
+	 * reassocition back to the same AP.
+	 */
+	sta->added_unassoc = 0;
+
 	if (sta->flags & WLAN_STA_WDS) {
 		int ret;
 		char ifname_wds[IFNAMSIZ + 1];
@@ -2585,6 +2701,12 @@ static void handle_assoc_cb(struct hostapd_data *hapd,
 	hapd->new_assoc_sta_cb(hapd, sta, !new_assoc);
 
 	ieee802_1x_notify_port_enabled(sta->eapol_sm, 1);
+
+remove_sta:
+	if (sta->added_unassoc) {
+		hostapd_drv_sta_remove(hapd, sta->addr);
+		sta->added_unassoc = 0;
+	}
 }
 
 
diff --git a/src/ap/sta_info.c b/src/ap/sta_info.c
index 500beff..802bbc8 100644
--- a/src/ap/sta_info.c
+++ b/src/ap/sta_info.c
@@ -169,8 +169,10 @@ void ap_free_sta(struct hostapd_data *hapd, struct sta_info *sta)
 	ap_sta_ip6addr_del(hapd, sta);
 
 	if (!hapd->iface->driver_ap_teardown &&
-	    !(sta->flags & WLAN_STA_PREAUTH))
+	    !(sta->flags & WLAN_STA_PREAUTH)) {
 		hostapd_drv_sta_remove(hapd, sta->addr);
+		sta->added_unassoc = 0;
+	}
 
 	ap_sta_hash_del(hapd, sta);
 	ap_sta_list_del(hapd, sta);
@@ -273,8 +275,10 @@ void ap_free_sta(struct hostapd_data *hapd, struct sta_info *sta)
 		 * VLAN.
 		 */
 		if (hapd->iface->driver_ap_teardown &&
-		    !(sta->flags & WLAN_STA_PREAUTH))
+		    !(sta->flags & WLAN_STA_PREAUTH)) {
 			hostapd_drv_sta_remove(hapd, sta->addr);
+			sta->added_unassoc = 0;
+		}
 		vlan_remove_dynamic(hapd, sta->vlan_id_bound);
 	}
 #endif /* CONFIG_NO_VLAN */
@@ -665,6 +669,7 @@ static int ap_sta_remove(struct hostapd_data *hapd, struct sta_info *sta)
 			   " from kernel driver.", MAC2STR(sta->addr));
 		return -1;
 	}
+	sta->added_unassoc = 0;
 	return 0;
 }
 
diff --git a/src/ap/sta_info.h b/src/ap/sta_info.h
index 09deac6..5b72252 100644
--- a/src/ap/sta_info.h
+++ b/src/ap/sta_info.h
@@ -87,6 +87,7 @@ struct sta_info {
 	unsigned int session_timeout_set:1;
 	unsigned int radius_das_match:1;
 	unsigned int ecsa_supported:1;
+	unsigned int added_unassoc:1;
 
 	u16 auth_alg;
 
diff --git a/src/drivers/driver.h b/src/drivers/driver.h
index e925a59..b1ad8d3 100644
--- a/src/drivers/driver.h
+++ b/src/drivers/driver.h
@@ -1225,8 +1225,13 @@ struct wpa_driver_capa {
 #define WPA_DRIVER_FLAGS_SUPPORT_HW_MODE_ANY	0x0000004000000000ULL
 /** Driver supports simultaneous off-channel operations */
 #define WPA_DRIVER_FLAGS_OFFCHANNEL_SIMULTANEOUS	0x0000008000000000ULL
+/** Driver supports full AP client state */
+#define WPA_DRIVER_FLAGS_SUPPORT_FULL_AP_CLIENT_STATE	0x0000010000000000ULL
 	u64 flags;
 
+#define FULL_AP_CLIENT_STATE_SUPPORTED(drv_flags) \
+	(drv_flags & WPA_DRIVER_FLAGS_SUPPORT_FULL_AP_CLIENT_STATE)
+
 #define WPA_DRIVER_SMPS_MODE_STATIC			0x00000001
 #define WPA_DRIVER_SMPS_MODE_DYNAMIC			0x00000002
 	unsigned int smps_modes;
@@ -1464,6 +1469,7 @@ struct wpa_bss_params {
 #define WPA_STA_MFP BIT(3)
 #define WPA_STA_TDLS_PEER BIT(4)
 #define WPA_STA_AUTHENTICATED BIT(5)
+#define WPA_STA_ASSOCIATED BIT(6)
 
 enum tdls_oper {
 	TDLS_DISCOVERY_REQ,
@@ -2325,12 +2331,16 @@ struct wpa_driver_ops {
 	 * @params: Station parameters
 	 * Returns: 0 on success, -1 on failure
 	 *
-	 * This function is used to add a station entry to the driver once the
-	 * station has completed association. This is only used if the driver
-	 * does not take care of association processing.
+	 * This function is used to add or set (params->set 1) a station
+	 * entry in the driver.
+	 *
+	 * With drivers that don't support full AP client state,
+	 * this function is used to add a station entry to the driver once the
+	 * station has completed association.
 	 *
-	 * With TDLS, this function is also used to add or set (params->set 1)
-	 * TDLS peer entries.
+	 * With TDLS, this function is used to add or set (params->set 1)
+	 * TDLS peer entries (even with drivers that not support
+	 * full AP client state).
 	 */
 	int (*sta_add)(void *priv, struct hostapd_sta_add_params *params);
 
diff --git a/src/drivers/driver_nl80211.c b/src/drivers/driver_nl80211.c
index 43dd0b1..7309bf2 100644
--- a/src/drivers/driver_nl80211.c
+++ b/src/drivers/driver_nl80211.c
@@ -3687,6 +3687,8 @@ static u32 sta_flags_nl80211(int flags)
 		f |= BIT(NL80211_STA_FLAG_TDLS_PEER);
 	if (flags & WPA_STA_AUTHENTICATED)
 		f |= BIT(NL80211_STA_FLAG_AUTHENTICATED);
+	if (flags & WPA_STA_ASSOCIATED)
+		f |= BIT(NL80211_STA_FLAG_ASSOCIATED);
 
 	return f;
 }
@@ -3739,11 +3741,23 @@ static int wpa_driver_nl80211_sta_add(void *priv,
 	if (!msg || nla_put(msg, NL80211_ATTR_MAC, ETH_ALEN, params->addr))
 		goto fail;
 
-	if (!params->set || (params->flags & WPA_STA_TDLS_PEER)) {
+	/*
+	 * Set the below properties only in one of the following cases:
+	 * 1. New station is added, already associated.
+	 * 2. Set WPA_STA_TDLS_PEER station.
+	 * 3. Set an already added unassociated station, if driver supports
+	 * full AP client state. (Set these properties after station became
+	 * associated will be rejected by the driver).
+	 */
+	if (!params->set || (params->flags & WPA_STA_TDLS_PEER) ||
+	    (params->set &&
+	     FULL_AP_CLIENT_STATE_SUPPORTED(drv->capa.flags) &&
+	     params->flags & WPA_STA_ASSOCIATED)) {
 		wpa_hexdump(MSG_DEBUG, "  * supported rates",
 			    params->supp_rates, params->supp_rates_len);
 		wpa_printf(MSG_DEBUG, "  * capability=0x%x",
 			   params->capability);
+
 		if (nla_put(msg, NL80211_ATTR_STA_SUPPORTED_RATES,
 			    params->supp_rates_len, params->supp_rates) ||
 		    nla_put_u16(msg, NL80211_ATTR_STA_CAPABILITY,
@@ -3787,9 +3801,11 @@ static int wpa_driver_nl80211_sta_add(void *priv,
 			/*
 			 * cfg80211 validates that AID is non-zero, so we have
 			 * to make this a non-zero value for the TDLS case where
-			 * a dummy STA entry is used for now.
+			 * a dummy STA entry is used for now,
+			 * or for a station that is still not associated.
 			 */
-			wpa_printf(MSG_DEBUG, "  * aid=1 (TDLS workaround)");
+			wpa_printf(MSG_DEBUG,
+				   "  * aid=1 (TDLS/UNASSOC_STA workaround)");
 			if (nla_put_u16(msg, NL80211_ATTR_STA_AID, 1))
 				goto fail;
 		}
@@ -3802,6 +3818,17 @@ static int wpa_driver_nl80211_sta_add(void *priv,
 		wpa_printf(MSG_DEBUG, "  * peer_aid=%u", params->aid);
 		if (nla_put_u16(msg, NL80211_ATTR_PEER_AID, params->aid))
 			goto fail;
+	} else if (FULL_AP_CLIENT_STATE_SUPPORTED(drv->capa.flags) &&
+		   (params->flags & WPA_STA_ASSOCIATED)) {
+		wpa_printf(MSG_DEBUG, "  * aid=%u", params->aid);
+		if (nla_put_u16(msg, NL80211_ATTR_STA_AID,
+				params->aid))
+			goto fail;
+		wpa_printf(MSG_DEBUG, "  * listen_interval=%u",
+			   params->listen_interval);
+		if (nla_put_u16(msg, NL80211_ATTR_STA_LISTEN_INTERVAL,
+				params->listen_interval))
+			goto fail;
 	}
 
 	if (params->vht_opmode_enabled) {
@@ -3832,6 +3859,35 @@ static int wpa_driver_nl80211_sta_add(void *priv,
 	os_memset(&upd, 0, sizeof(upd));
 	upd.set = sta_flags_nl80211(params->flags);
 	upd.mask = upd.set | sta_flags_nl80211(params->flags_mask);
+
+	/*
+	 * If driver doesn't support full AP client state, ignore ASSOC/AUTH
+	 * flags, as nl80211 driver moves a new station, by default, into
+	 * associated state.
+	 * On the other hand, if driver supports that feature, and station is
+	 * added in un-authenticated state, set authenticated/associated bits
+	 * in the mask, to prevent moving this station to assoc state before
+	 * it's actually associated.
+	 *
+	 * This is irrlevant for MESH mode where station is added to the
+	 * driver as authenticated already, and ASSOCIATED isn't part of
+	 * the nl80211 API.
+	 */
+	if (!is_mesh_interface(drv->nlmode)) {
+		if (!FULL_AP_CLIENT_STATE_SUPPORTED(drv->capa.flags)) {
+			wpa_printf(MSG_WARNING,
+				   "Ignore ASSOC/AUTH flags since driver doesn't support full AP client state");
+			upd.mask &= ~(BIT(NL80211_STA_FLAG_ASSOCIATED) |
+				      BIT(NL80211_STA_FLAG_AUTHENTICATED));
+		} else if (!params->set &&
+			   !(params->flags & WPA_STA_TDLS_PEER)) {
+			if (!(params->flags & WPA_STA_AUTHENTICATED))
+				upd.mask |= BIT(NL80211_STA_FLAG_AUTHENTICATED);
+			if (!(params->flags & WPA_STA_ASSOCIATED))
+				upd.mask |= BIT(NL80211_STA_FLAG_ASSOCIATED);
+		}
+	}
+
 	wpa_printf(MSG_DEBUG, "  * flags set=0x%x mask=0x%x",
 		   upd.set, upd.mask);
 	if (nla_put(msg, NL80211_ATTR_STA_FLAGS2, sizeof(upd), &upd))
diff --git a/src/drivers/driver_nl80211_capa.c b/src/drivers/driver_nl80211_capa.c
index 1b5752f..0140de3 100644
--- a/src/drivers/driver_nl80211_capa.c
+++ b/src/drivers/driver_nl80211_capa.c
@@ -428,6 +428,9 @@ static void wiphy_info_feature_flags(struct wiphy_info_data *info,
 
 	if (flags & NL80211_FEATURE_HT_IBSS)
 		capa->flags |= WPA_DRIVER_FLAGS_HT_IBSS;
+
+	if (flags & NL80211_FEATURE_FULL_AP_CLIENT_STATE)
+		capa->flags |= WPA_DRIVER_FLAGS_SUPPORT_FULL_AP_CLIENT_STATE;
 }
 
 
-- 
1.9.1




More information about the Hostap mailing list