diff mbox

[06/44] FT: add IEEE vlan support (including tagged vlans)

Message ID 1456314830-12935-7-git-send-email-michael-dev@fami-braun.de
State Changes Requested
Headers show

Commit Message

michael-dev Feb. 24, 2016, 11:53 a.m. UTC
From: Michael Braun <michael-dev@fami-braun.de>

This introduces set_vlan and get_vlan callbacks for wpa_auth
and uses them to fetch and configure vlan of sta.
The struct vlan_description is converted into a network encoding
(struct ft_vlan) for byter order reasons.

Signed-off-by: Michael Braun <michael-dev@fami-braun.de>
---
 src/ap/wpa_auth.h      |  18 +++++++-
 src/ap/wpa_auth_ft.c   |  78 ++++++++++++++++++++++++++++------
 src/ap/wpa_auth_glue.c | 112 +++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 194 insertions(+), 14 deletions(-)

Comments

Jouni Malinen Feb. 28, 2016, 10:15 a.m. UTC | #1
On Wed, Feb 24, 2016 at 12:53:12PM +0100, michael-dev@fami-braun.de wrote:
> This introduces set_vlan and get_vlan callbacks for wpa_auth
> and uses them to fetch and configure vlan of sta.
> The struct vlan_description is converted into a network encoding
> (struct ft_vlan) for byter order reasons.

struct ft_vlan is used to define a fixed message format in addition to
using it elsewhere in the implementation with all the users having to
know the byte order. This does not look desirable. I'd use a variable
with host byte order elsewhere and convert to the specific byte order
only when building the message. Furthermore, I would prefer not to have
a struct with a struct defined a fixed message format, like the one
here:

>  struct ft_r0kh_r1kh_resp_frame {
>  	u8 frame_type; /* RSN_REMOTE_FRAME_TYPE_FT_RRB */
> @@ -78,13 +88,14 @@ struct ft_r0kh_r1kh_resp_frame {
>  	u8 pmk_r1[PMK_LEN];
>  	u8 pmk_r1_name[WPA_PMK_NAME_LEN];
>  	le16 pairwise;
> +	struct ft_vlan vlan;
>  	u8 pad[FT_R0KH_R1KH_RESP_PAD_LEN]; /* 8-octet boundary for AES block */
>  	u8 key_wrap_extra[8];
>  } STRUCT_PACKED;


This all said, this is the first patch in the series that seems to break
backwards compatibility with earlier hostapd implementation of the
AP-to-AP messages for FT pull/push operations. Taken into account the
issues in extending this fixed message design, I don't think we should
be doing this. If we are willing to declare that there is no backwards
compatibility on this between the version that comes out of this series
and what existed before, we might as well clean this up now and make the
definition extensible, e.g., with a use of TLVs and the receiver
ignoring TLVs that it does not recognize.

In other words, I'm going to drop this patch and all the following
patches that are dependent on changes to these R0KH-R1KH pull/push
messages. I'd like to see a patch to convert the current message format
to an extensible TLV format and these dropped patches rebased on top of
that to confirm that the protocol extensibility in the new design works.
diff mbox

Patch

diff --git a/src/ap/wpa_auth.h b/src/ap/wpa_auth.h
index ded9441..5ad837a 100644
--- a/src/ap/wpa_auth.h
+++ b/src/ap/wpa_auth.h
@@ -42,12 +42,22 @@  struct ft_rrb_frame {
 #define FT_PACKET_R0KH_R1KH_RESP 201
 #define FT_PACKET_R0KH_R1KH_PUSH 202
 
+#define FT_MAX_NUM_TAGGED_VLAN 32
+#define FT_VLAN_DATA_LEN sizeof(struct ft_vlan)
+
 #define FT_R0KH_R1KH_PULL_NONCE_LEN 16
 #define FT_R0KH_R1KH_PULL_DATA_LEN (FT_R0KH_R1KH_PULL_NONCE_LEN + \
 				    WPA_PMK_NAME_LEN + FT_R1KH_ID_LEN + \
 				    ETH_ALEN)
 #define FT_R0KH_R1KH_PULL_PAD_LEN (8 - FT_R0KH_R1KH_PULL_DATA_LEN % 8)
 
+struct ft_vlan {
+	le16 untagged;
+	le16 tagged[FT_MAX_NUM_TAGGED_VLAN]; /* ordered by value,
+					      * non-zero first
+					      */
+} STRUCT_PACKED;
+
 struct ft_r0kh_r1kh_pull_frame {
 	u8 frame_type; /* RSN_REMOTE_FRAME_TYPE_FT_RRB */
 	u8 packet_type; /* FT_PACKET_R0KH_R1KH_PULL */
@@ -64,7 +74,7 @@  struct ft_r0kh_r1kh_pull_frame {
 
 #define FT_R0KH_R1KH_RESP_DATA_LEN (FT_R0KH_R1KH_PULL_NONCE_LEN + \
 				    FT_R1KH_ID_LEN + ETH_ALEN + PMK_LEN + \
-				    WPA_PMK_NAME_LEN + 2)
+				    WPA_PMK_NAME_LEN + FT_VLAN_DATA_LEN + 2)
 #define FT_R0KH_R1KH_RESP_PAD_LEN (8 - FT_R0KH_R1KH_RESP_DATA_LEN % 8)
 struct ft_r0kh_r1kh_resp_frame {
 	u8 frame_type; /* RSN_REMOTE_FRAME_TYPE_FT_RRB */
@@ -78,13 +88,14 @@  struct ft_r0kh_r1kh_resp_frame {
 	u8 pmk_r1[PMK_LEN];
 	u8 pmk_r1_name[WPA_PMK_NAME_LEN];
 	le16 pairwise;
+	struct ft_vlan vlan;
 	u8 pad[FT_R0KH_R1KH_RESP_PAD_LEN]; /* 8-octet boundary for AES block */
 	u8 key_wrap_extra[8];
 } STRUCT_PACKED;
 
 #define FT_R0KH_R1KH_PUSH_DATA_LEN (4 + FT_R1KH_ID_LEN + ETH_ALEN + \
 				    WPA_PMK_NAME_LEN + PMK_LEN + \
-				    WPA_PMK_NAME_LEN + 2)
+				    WPA_PMK_NAME_LEN + FT_VLAN_DATA_LEN + 2)
 #define FT_R0KH_R1KH_PUSH_PAD_LEN (8 - FT_R0KH_R1KH_PUSH_DATA_LEN % 8)
 struct ft_r0kh_r1kh_push_frame {
 	u8 frame_type; /* RSN_REMOTE_FRAME_TYPE_FT_RRB */
@@ -101,6 +112,7 @@  struct ft_r0kh_r1kh_push_frame {
 	u8 pmk_r1[PMK_LEN];
 	u8 pmk_r1_name[WPA_PMK_NAME_LEN];
 	le16 pairwise;
+	struct ft_vlan vlan;
 	u8 pad[FT_R0KH_R1KH_PUSH_PAD_LEN]; /* 8-octet boundary for AES block */
 	u8 key_wrap_extra[8];
 } STRUCT_PACKED;
@@ -222,6 +234,8 @@  struct wpa_auth_callbacks {
 			  size_t data_len);
 #ifdef CONFIG_IEEE80211R
 	struct wpa_state_machine * (*add_sta)(void *ctx, const u8 *sta_addr);
+	int (*set_vlan)(void *ctx, const u8 *sta_addr, struct ft_vlan *vlan);
+	int (*get_vlan)(void *ctx, const u8 *sta_addr, struct ft_vlan *vlan);
 	int (*send_ft_action)(void *ctx, const u8 *dst,
 			      const u8 *data, size_t data_len);
 	int (*add_tspec)(void *ctx, const u8 *sta_addr, u8 *tspec_ie,
diff --git a/src/ap/wpa_auth_ft.c b/src/ap/wpa_auth_ft.c
index 42242a5..f600aa7 100644
--- a/src/ap/wpa_auth_ft.c
+++ b/src/ap/wpa_auth_ft.c
@@ -60,6 +60,26 @@  wpa_ft_add_sta(struct wpa_authenticator *wpa_auth, const u8 *sta_addr)
 }
 
 
+static int
+wpa_ft_set_vlan(struct wpa_authenticator *wpa_auth, const u8 *sta_addr,
+		struct ft_vlan *vlan)
+{
+	if (wpa_auth->cb.set_vlan == NULL)
+		return -1;
+	return wpa_auth->cb.set_vlan(wpa_auth->cb.ctx, sta_addr, vlan);
+}
+
+
+static int
+wpa_ft_get_vlan(struct wpa_authenticator *wpa_auth, const u8 *sta_addr,
+		struct ft_vlan *vlan)
+{
+	if (wpa_auth->cb.get_vlan == NULL)
+		return -1;
+	return wpa_auth->cb.get_vlan(wpa_auth->cb.ctx, sta_addr, vlan);
+}
+
+
 static int wpa_ft_add_tspec(struct wpa_authenticator *wpa_auth,
 			    const u8 *sta_addr,
 			    u8 *tspec_ie, size_t tspec_ielen)
@@ -148,7 +168,8 @@  struct wpa_ft_pmk_r0_sa {
 	u8 pmk_r0_name[WPA_PMK_NAME_LEN];
 	u8 spa[ETH_ALEN];
 	int pairwise; /* Pairwise cipher suite, WPA_CIPHER_* */
-	/* TODO: expiration, identity, radius_class, EAP type, VLAN ID */
+	struct ft_vlan vlan;
+	/* TODO: expiration, identity, radius_class, EAP type */
 	int pmk_r1_pushed;
 };
 
@@ -158,7 +179,8 @@  struct wpa_ft_pmk_r1_sa {
 	u8 pmk_r1_name[WPA_PMK_NAME_LEN];
 	u8 spa[ETH_ALEN];
 	int pairwise; /* Pairwise cipher suite, WPA_CIPHER_* */
-	/* TODO: expiration, identity, radius_class, EAP type, VLAN ID */
+	struct ft_vlan vlan;
+	/* TODO: expiration, identity, radius_class, EAP type */
 };
 
 struct wpa_ft_pmk_cache {
@@ -203,7 +225,8 @@  void wpa_ft_pmk_cache_deinit(struct wpa_ft_pmk_cache *cache)
 
 static int wpa_ft_store_pmk_r0(struct wpa_authenticator *wpa_auth,
 			       const u8 *spa, const u8 *pmk_r0,
-			       const u8 *pmk_r0_name, int pairwise)
+			       const u8 *pmk_r0_name, int pairwise,
+			       const struct ft_vlan vlan)
 {
 	struct wpa_ft_pmk_cache *cache = wpa_auth->ft_pmk_cache;
 	struct wpa_ft_pmk_r0_sa *r0;
@@ -218,6 +241,7 @@  static int wpa_ft_store_pmk_r0(struct wpa_authenticator *wpa_auth,
 	os_memcpy(r0->pmk_r0_name, pmk_r0_name, WPA_PMK_NAME_LEN);
 	os_memcpy(r0->spa, spa, ETH_ALEN);
 	r0->pairwise = pairwise;
+	os_memcpy(&r0->vlan, &vlan, FT_VLAN_DATA_LEN);
 
 	r0->next = cache->pmk_r0;
 	cache->pmk_r0 = r0;
@@ -228,7 +252,8 @@  static int wpa_ft_store_pmk_r0(struct wpa_authenticator *wpa_auth,
 
 static int wpa_ft_fetch_pmk_r0(struct wpa_authenticator *wpa_auth,
 			       const u8 *spa, const u8 *pmk_r0_name,
-			       u8 *pmk_r0, int *pairwise)
+			       u8 *pmk_r0, int *pairwise,
+			       struct ft_vlan *vlan)
 {
 	struct wpa_ft_pmk_cache *cache = wpa_auth->ft_pmk_cache;
 	struct wpa_ft_pmk_r0_sa *r0;
@@ -241,6 +266,8 @@  static int wpa_ft_fetch_pmk_r0(struct wpa_authenticator *wpa_auth,
 			os_memcpy(pmk_r0, r0->pmk_r0, PMK_LEN);
 			if (pairwise)
 				*pairwise = r0->pairwise;
+			if (vlan)
+				os_memcpy(vlan, &r0->vlan, FT_VLAN_DATA_LEN);
 			return 0;
 		}
 
@@ -253,7 +280,8 @@  static int wpa_ft_fetch_pmk_r0(struct wpa_authenticator *wpa_auth,
 
 static int wpa_ft_store_pmk_r1(struct wpa_authenticator *wpa_auth,
 			       const u8 *spa, const u8 *pmk_r1,
-			       const u8 *pmk_r1_name, int pairwise)
+			       const u8 *pmk_r1_name, int pairwise,
+			       const struct ft_vlan vlan)
 {
 	struct wpa_ft_pmk_cache *cache = wpa_auth->ft_pmk_cache;
 	struct wpa_ft_pmk_r1_sa *r1;
@@ -268,6 +296,7 @@  static int wpa_ft_store_pmk_r1(struct wpa_authenticator *wpa_auth,
 	os_memcpy(r1->pmk_r1_name, pmk_r1_name, WPA_PMK_NAME_LEN);
 	os_memcpy(r1->spa, spa, ETH_ALEN);
 	r1->pairwise = pairwise;
+	os_memcpy(&r1->vlan, &vlan, FT_VLAN_DATA_LEN);
 
 	r1->next = cache->pmk_r1;
 	cache->pmk_r1 = r1;
@@ -278,7 +307,8 @@  static int wpa_ft_store_pmk_r1(struct wpa_authenticator *wpa_auth,
 
 static int wpa_ft_fetch_pmk_r1(struct wpa_authenticator *wpa_auth,
 			       const u8 *spa, const u8 *pmk_r1_name,
-			       u8 *pmk_r1, int *pairwise)
+			       u8 *pmk_r1, int *pairwise,
+			       struct ft_vlan *vlan)
 {
 	struct wpa_ft_pmk_cache *cache = wpa_auth->ft_pmk_cache;
 	struct wpa_ft_pmk_r1_sa *r1;
@@ -291,6 +321,8 @@  static int wpa_ft_fetch_pmk_r1(struct wpa_authenticator *wpa_auth,
 			os_memcpy(pmk_r1, r1->pmk_r1, PMK_LEN);
 			if (pairwise)
 				*pairwise = r1->pairwise;
+			if (vlan)
+				os_memcpy(vlan, &r1->vlan, FT_VLAN_DATA_LEN);
 			return 0;
 		}
 
@@ -373,6 +405,7 @@  int wpa_auth_derive_ptk_ft(struct wpa_state_machine *sm, const u8 *pmk,
 	const u8 *r1kh = sm->wpa_auth->conf.r1_key_holder;
 	const u8 *ssid = sm->wpa_auth->conf.ssid;
 	size_t ssid_len = sm->wpa_auth->conf.ssid_len;
+	struct ft_vlan vlan;
 
 	if (sm->xxkey_len == 0) {
 		wpa_printf(MSG_DEBUG, "FT: XXKey not available for key "
@@ -380,12 +413,18 @@  int wpa_auth_derive_ptk_ft(struct wpa_state_machine *sm, const u8 *pmk,
 		return -1;
 	}
 
+	if (wpa_ft_get_vlan(sm->wpa_auth, sm->addr, &vlan) < 0) {
+		wpa_printf(MSG_DEBUG, "FT: vlan not available for STA " MACSTR,
+			   MAC2STR(sm->addr));
+		return -1;
+	}
+
 	wpa_derive_pmk_r0(sm->xxkey, sm->xxkey_len, ssid, ssid_len, mdid,
 			  r0kh, r0kh_len, sm->addr, pmk_r0, pmk_r0_name);
 	wpa_hexdump_key(MSG_DEBUG, "FT: PMK-R0", pmk_r0, PMK_LEN);
 	wpa_hexdump(MSG_DEBUG, "FT: PMKR0Name", pmk_r0_name, WPA_PMK_NAME_LEN);
 	wpa_ft_store_pmk_r0(sm->wpa_auth, sm->addr, pmk_r0, pmk_r0_name,
-			    sm->pairwise);
+			    sm->pairwise, vlan);
 
 	wpa_derive_pmk_r1(pmk_r0, pmk_r0_name, r1kh, sm->addr,
 			  pmk_r1, sm->pmk_r1_name);
@@ -393,7 +432,7 @@  int wpa_auth_derive_ptk_ft(struct wpa_state_machine *sm, const u8 *pmk,
 	wpa_hexdump(MSG_DEBUG, "FT: PMKR1Name", sm->pmk_r1_name,
 		    WPA_PMK_NAME_LEN);
 	wpa_ft_store_pmk_r1(sm->wpa_auth, sm->addr, pmk_r1, sm->pmk_r1_name,
-			    sm->pairwise);
+			    sm->pairwise, vlan);
 
 	return wpa_pmk_r1_to_ptk(pmk_r1, sm->SNonce, sm->ANonce, sm->addr,
 				 sm->wpa_auth->addr, sm->pmk_r1_name,
@@ -809,6 +848,7 @@  static int wpa_ft_process_auth_req(struct wpa_state_machine *sm,
 	int ret;
 	u8 *pos, *end;
 	int pairwise;
+	struct ft_vlan vlan;
 
 	*resp_ies = NULL;
 	*resp_ies_len = 0;
@@ -865,7 +905,7 @@  static int wpa_ft_process_auth_req(struct wpa_state_machine *sm,
 		    pmk_r1_name, WPA_PMK_NAME_LEN);
 
 	if (wpa_ft_fetch_pmk_r1(sm->wpa_auth, sm->addr, pmk_r1_name, pmk_r1,
-		    &pairwise) < 0) {
+		    &pairwise, &vlan) < 0) {
 		if (wpa_ft_pull_pmk_r1(sm, ies, ies_len, parse.rsn_pmkid) < 0) {
 			wpa_printf(MSG_DEBUG, "FT: Did not have matching "
 				   "PMK-R1 and unknown R0KH-ID");
@@ -900,6 +940,11 @@  static int wpa_ft_process_auth_req(struct wpa_state_machine *sm,
 	sm->PTK_valid = TRUE;
 	wpa_ft_install_ptk(sm);
 
+	if (wpa_ft_set_vlan(sm->wpa_auth, sm->addr, &vlan) < 0) {
+		wpa_printf(MSG_DEBUG, "FT: Failed to configure VLAN");
+		return WLAN_STATUS_UNSPECIFIED_FAILURE;
+	}
+
 	buflen = 2 + sizeof(struct rsn_mdie) + 2 + sizeof(struct rsn_ftie) +
 		2 + FT_R1KH_ID_LEN + 200;
 	*resp_ies = os_zalloc(buflen);
@@ -1317,6 +1362,7 @@  static int wpa_ft_rrb_rx_pull(struct wpa_authenticator *wpa_auth,
 	struct ft_r0kh_r1kh_resp_frame resp, r;
 	u8 pmk_r0[PMK_LEN];
 	int pairwise;
+	struct ft_vlan vlan;
 
 	wpa_printf(MSG_DEBUG, "FT: Received PMK-R1 pull");
 
@@ -1368,7 +1414,7 @@  static int wpa_ft_rrb_rx_pull(struct wpa_authenticator *wpa_auth,
 	os_memcpy(r.r1kh_id, f.r1kh_id, FT_R1KH_ID_LEN);
 	os_memcpy(r.s1kh_id, f.s1kh_id, ETH_ALEN);
 	if (wpa_ft_fetch_pmk_r0(wpa_auth, f.s1kh_id, f.pmk_r0_name, pmk_r0,
-				&pairwise) < 0) {
+				&pairwise, &vlan) < 0) {
 		wpa_printf(MSG_DEBUG, "FT: No matching PMKR0Name found for "
 			   "PMK-R1 pull");
 		return -1;
@@ -1380,6 +1426,7 @@  static int wpa_ft_rrb_rx_pull(struct wpa_authenticator *wpa_auth,
 	wpa_hexdump(MSG_DEBUG, "FT: PMKR1Name", r.pmk_r1_name,
 		    WPA_PMK_NAME_LEN);
 	r.pairwise = host_to_le16(pairwise);
+	os_memcpy(&r.vlan, &vlan, FT_VLAN_DATA_LEN);
 	os_memset(r.pad, 0, sizeof(r.pad));
 
 	if (aes_wrap(r1kh->key, sizeof(r1kh->key),
@@ -1500,9 +1547,12 @@  static int wpa_ft_rrb_rx_resp(struct wpa_authenticator *wpa_auth,
 			f.pmk_r1, PMK_LEN);
 	wpa_hexdump(MSG_DEBUG, "FT: PMK-R1 pull - PMKR1Name",
 			f.pmk_r1_name, WPA_PMK_NAME_LEN);
+	wpa_printf(MSG_DEBUG, "FT: PMK-R1 pull - vlan %d%s",
+		   le_to_host16(f.vlan.untagged),
+		   f.vlan.tagged[0] ? "+" : "");
 
 	res = wpa_ft_store_pmk_r1(wpa_auth, f.s1kh_id, f.pmk_r1, f.pmk_r1_name,
-				  pairwise);
+				  pairwise, f.vlan);
 	wpa_printf(MSG_DEBUG, "FT: Look for pending pull request");
 	wpa_auth_for_each_sta(wpa_auth, ft_pull_resp_cb, &f);
 	os_memset(f.pmk_r1, 0, PMK_LEN);
@@ -1582,9 +1632,12 @@  static int wpa_ft_rrb_rx_push(struct wpa_authenticator *wpa_auth,
 			f.pmk_r1, PMK_LEN);
 	wpa_hexdump(MSG_DEBUG, "FT: PMK-R1 push - PMKR1Name",
 			f.pmk_r1_name, WPA_PMK_NAME_LEN);
+	wpa_printf(MSG_DEBUG, "FT: PMK-R1 push - vlan %d%s",
+		   le_to_host16(f.vlan.untagged),
+		   f.vlan.tagged[0] ? "+" : "");
 
 	wpa_ft_store_pmk_r1(wpa_auth, f.s1kh_id, f.pmk_r1, f.pmk_r1_name,
-			    pairwise);
+			    pairwise, f.vlan);
 	os_memset(f.pmk_r1, 0, PMK_LEN);
 
 	return 0;
@@ -1746,6 +1799,7 @@  static void wpa_ft_generate_pmk_r1(struct wpa_authenticator *wpa_auth,
 	os_get_time(&now);
 	WPA_PUT_LE32(f.timestamp, now.sec);
 	f.pairwise = host_to_le16(pairwise);
+	f.vlan = pmk_r0->vlan;
 	os_memset(f.pad, 0, sizeof(f.pad));
 	plain = ((const u8 *) &f) + offsetof(struct ft_r0kh_r1kh_push_frame,
 					     timestamp);
diff --git a/src/ap/wpa_auth_glue.c b/src/ap/wpa_auth_glue.c
index ffd0790..8dbc2c4 100644
--- a/src/ap/wpa_auth_glue.c
+++ b/src/ap/wpa_auth_glue.c
@@ -553,6 +553,116 @@  hostapd_wpa_auth_add_sta(void *ctx, const u8 *sta_addr)
 }
 
 
+/* sanity check for vlan definitions */
+#if FT_MAX_NUM_TAGGED_VLAN != MAX_NUM_TAGGED_VLAN
+#error FT_MAX_NUM_TAGGED_VLAN and MAX_NUM_TAGGED_VLAN differ
+#endif
+
+static void hostapd_wpa_vlan_to_ft(struct ft_vlan *ft_vlan,
+				   struct vlan_description *vlan_desc)
+{
+	int i;
+
+	os_memset(ft_vlan, 0, sizeof(*ft_vlan));
+
+	if (!vlan_desc)
+		return;
+
+	ft_vlan->untagged = host_to_le16(vlan_desc->untagged);
+	for (i = 0;
+	     i < FT_MAX_NUM_TAGGED_VLAN && i < MAX_NUM_TAGGED_VLAN;
+	     i++) {
+		if (!vlan_desc->tagged[i])
+			break;
+		ft_vlan->tagged[i] = host_to_le16(vlan_desc->tagged[i]);
+	}
+}
+
+
+static int hostapd_wpa_ft_to_vlan(struct hostapd_data *hapd,
+				  struct vlan_description *vlan_desc,
+				  struct ft_vlan *ft_vlan)
+{
+	int i;
+
+	/* convert ft_vlan into vlan_description */
+	os_memset(vlan_desc, 0, sizeof(*vlan_desc));
+
+	if (!ft_vlan)
+		return 0;
+
+	vlan_desc->untagged = le_to_host16(ft_vlan->untagged);
+	for (i = 0;
+	     i < FT_MAX_NUM_TAGGED_VLAN && i < MAX_NUM_TAGGED_VLAN;
+	     i++) {
+		if (!ft_vlan->tagged[i])
+			break;
+		vlan_desc->tagged[i] = le_to_host16(ft_vlan->tagged[i]);
+	}
+	vlan_desc->notempty = vlan_desc->untagged || vlan_desc->tagged[0];
+
+	/* fail early so that does not get connected if invalid vlan */
+	if (vlan_desc->notempty &&
+	    !hostapd_vlan_valid(hapd->conf->vlan, vlan_desc))
+		return -1;
+	return 0;
+}
+
+
+static int hostapd_wpa_auth_set_vlan(void *ctx, const u8 *sta_addr,
+				     struct ft_vlan *ft_vlan)
+{
+	struct hostapd_data *hapd = ctx;
+	struct sta_info *sta;
+	struct vlan_description vlan_desc;
+
+	sta = ap_get_sta(hapd, sta_addr);
+	if (sta == NULL)
+		return -1;
+
+	if (!sta->wpa_sm)
+		return -1;
+
+	if (hostapd_wpa_ft_to_vlan(hapd, &vlan_desc, ft_vlan) < 0) {
+		hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211,
+			       HOSTAPD_LEVEL_INFO, "Invalid VLAN "
+			       "%d%s received from FT",
+			       vlan_desc.untagged,
+			       vlan_desc.tagged[0] ? "+" : "");
+		return -1;
+	}
+
+	if (ap_sta_set_vlan(hapd, sta, &vlan_desc) < 0)
+		return -1;
+	/* configure wpa_group for GTK but ignore error due to driver not
+	 * knowing this sta
+	 */
+	ap_sta_bind_vlan(hapd, sta);
+
+	if (sta->vlan_id)
+		hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211,
+			       HOSTAPD_LEVEL_INFO, "VLAN ID %d", sta->vlan_id);
+
+	return 0;
+}
+
+
+static int
+hostapd_wpa_auth_get_vlan(void *ctx, const u8 *sta_addr,
+			  struct ft_vlan *ft_vlan)
+{
+	struct hostapd_data *hapd = ctx;
+	struct sta_info *sta;
+
+	sta = ap_get_sta(hapd, sta_addr);
+	if (sta == NULL)
+		return -1;
+
+	hostapd_wpa_vlan_to_ft(ft_vlan, sta->vlan_desc);
+	return 0;
+}
+
+
 static void hostapd_rrb_receive(void *ctx, const u8 *src_addr, const u8 *buf,
 				size_t len)
 {
@@ -609,6 +719,8 @@  int hostapd_setup_wpa(struct hostapd_data *hapd)
 #ifdef CONFIG_IEEE80211R
 	cb.send_ft_action = hostapd_wpa_auth_send_ft_action;
 	cb.add_sta = hostapd_wpa_auth_add_sta;
+	cb.set_vlan = hostapd_wpa_auth_set_vlan;
+	cb.get_vlan = hostapd_wpa_auth_get_vlan;
 	cb.add_tspec = hostapd_wpa_auth_add_tspec;
 #endif /* CONFIG_IEEE80211R */
 	hapd->wpa_auth = wpa_init(hapd->own_addr, &_conf, &cb);