diff --git a/hostapd/config_file.c b/hostapd/config_file.c
index 45a2cb9..165bc96 100644
--- a/hostapd/config_file.c
+++ b/hostapd/config_file.c
@@ -58,7 +58,7 @@ static int hostapd_config_read_vlan_file(struct hostapd_bss_config *bss,
 			continue;
 
 		if (buf[0] == '*') {
-			vlan_alloc(&vlan_id, VLAN_ID_WILDCARD);
+			vlan_alloc(&vlan_id, VLAN_ID_WILDCARD, 0, NULL);
 			pos = buf + 1;
 		} else {
 			int untagged_vlan_id = strtol(buf, &pos, 10);
@@ -69,7 +69,7 @@ static int hostapd_config_read_vlan_file(struct hostapd_bss_config *bss,
 				fclose(f);
 				return -1;
 			}
-			vlan_alloc(&vlan_id, untagged_vlan_id);
+			vlan_alloc(&vlan_id, untagged_vlan_id, 0, NULL);
 		}
 
 		while (*pos == ' ' || *pos == '\t')
@@ -164,7 +164,7 @@ static int hostapd_config_read_maclist(const char *fname,
 		while (*pos == ' ' || *pos == '\t')
 			pos++;
 		if (*pos != '\0')
-			vlan_alloc(&vlan_id, atoi(pos));
+			vlan_alloc(&vlan_id, atoi(pos), 0, NULL);
 
 		newacl = os_realloc_array(*acl, *num + 1, sizeof(**acl));
 		if (newacl == NULL) {
diff --git a/src/ap/ap_config.h b/src/ap/ap_config.h
index 1e46f69..c450219 100644
--- a/src/ap/ap_config.h
+++ b/src/ap/ap_config.h
@@ -17,7 +17,6 @@
 #include "wps/wps.h"
 
 #define MAX_STA_COUNT 2007
-#define MAX_VLAN_ID 4094
 
 typedef u8 macaddr[ETH_ALEN];
 
diff --git a/src/ap/ieee802_11.c b/src/ap/ieee802_11.c
index 88f24ec..01f2a04 100644
--- a/src/ap/ieee802_11.c
+++ b/src/ap/ieee802_11.c
@@ -653,15 +653,15 @@ static void handle_auth(struct hostapd_data *hapd,
 		if (!hostapd_vlan_id_valid(hapd->conf->vlan, vlan_id)) {
 			hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_RADIUS,
 				       HOSTAPD_LEVEL_INFO, "Invalid VLAN ID "
-				       "%d received from RADIUS server",
-				       vlan_untagged(&vlan_id));
+				       "%s received from RADIUS server",
+				       vlan2str(&vlan_id));
 			resp = WLAN_STATUS_UNSPECIFIED_FAILURE;
 			goto fail;
 		}
 		vlan_alloc_copy(&sta->vlan_id, &vlan_id);
 		hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_RADIUS,
-			       HOSTAPD_LEVEL_INFO, "VLAN ID %d",
-			       vlan_untagged(&sta->vlan_id));
+			       HOSTAPD_LEVEL_INFO, "VLAN ID %s",
+			       vlan2str(&sta->vlan_id));
 	}
 	vlan_free(&vlan_id);
 
diff --git a/src/ap/ieee802_11_auth.c b/src/ap/ieee802_11_auth.c
index 604b54a..9d077eb 100644
--- a/src/ap/ieee802_11_auth.c
+++ b/src/ap/ieee802_11_auth.c
@@ -540,7 +540,8 @@ hostapd_acl_recv_radius(struct radius_msg *msg, struct radius_msg *req,
 			cache->acct_interim_interval = 0;
 		}
 
-		vlan_alloc(&cache->vlan_id, radius_msg_get_vlanid(msg));
+		vlan_alloc(&cache->vlan_id, radius_msg_get_vlanid(msg), 0,
+		           NULL);
 
 		decode_tunnel_passwords(hapd, shared_secret, shared_secret_len,
 					msg, req, cache);
diff --git a/src/ap/ieee802_1x.c b/src/ap/ieee802_1x.c
index 38c5b08..ce8700f 100644
--- a/src/ap/ieee802_1x.c
+++ b/src/ap/ieee802_1x.c
@@ -1334,7 +1334,8 @@ ieee802_1x_receive_auth(struct radius_msg *msg, struct radius_msg *req,
 #ifndef CONFIG_NO_VLAN
 		else {
 			vlan_alloc_copy(&old_vlanid, &sta->vlan_id);
-			vlan_alloc(&sta->vlan_id, radius_msg_get_vlanid(msg));
+			vlan_alloc(&sta->vlan_id, radius_msg_get_vlanid(msg),
+			           0, NULL);
 		}
 		if (vlan_notempty(&sta->vlan_id) &&
 		    vlan_untagged(&sta->vlan_id) > 0 &&
@@ -1342,8 +1343,7 @@ ieee802_1x_receive_auth(struct radius_msg *msg, struct radius_msg *req,
 			hostapd_logger(hapd, sta->addr,
 				       HOSTAPD_MODULE_RADIUS,
 				       HOSTAPD_LEVEL_INFO,
-				       "VLAN ID %d",
-				       vlan_untagged(&sta->vlan_id));
+				       "VLAN ID %s", vlan2str(&sta->vlan_id));
 		} else if (sta->ssid->dynamic_vlan == DYNAMIC_VLAN_REQUIRED) {
 			sta->eapol_sm->authFail = TRUE;
 			hostapd_logger(hapd, sta->addr,
diff --git a/src/ap/sta_info.c b/src/ap/sta_info.c
index 1082a3a..d3d9349 100644
--- a/src/ap/sta_info.c
+++ b/src/ap/sta_info.c
@@ -711,8 +711,8 @@ int ap_sta_bind_vlan(struct hostapd_data *hapd, struct sta_info *sta,
 	    vlan_untagged(&sta->vlan_id) >= 0 && vlan == NULL) {
 		hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211,
 			       HOSTAPD_LEVEL_DEBUG, "could not find VLAN for "
-			       "binding station to (vlan_id=%d)",
-			       vlan_untagged(&sta->vlan_id));
+			       "binding station to (vlan_id=%s)",
+			       vlan2str(&sta->vlan_id));
 		return -1;
 	} else if (vlan_notempty(&sta->vlan_id) &&
 	           vlan_untagged(&sta->vlan_id) >= 0 &&
@@ -722,8 +722,8 @@ int ap_sta_bind_vlan(struct hostapd_data *hapd, struct sta_info *sta,
 			hostapd_logger(hapd, sta->addr,
 				       HOSTAPD_MODULE_IEEE80211,
 				       HOSTAPD_LEVEL_DEBUG, "could not add "
-				       "dynamic VLAN interface for vlan_id=%d",
-				       vlan_untagged(&sta->vlan_id));
+				       "dynamic VLAN interface for vlan_id=%s",
+				       vlan2str(&sta->vlan_id));
 			return -1;
 		}
 
@@ -733,8 +733,8 @@ int ap_sta_bind_vlan(struct hostapd_data *hapd, struct sta_info *sta,
 				       HOSTAPD_MODULE_IEEE80211,
 				       HOSTAPD_LEVEL_DEBUG, "could not "
 				       "configure encryption for dynamic VLAN "
-				       "interface for vlan_id=%d",
-				       vlan_untagged(&sta->vlan_id));
+				       "interface for vlan_id=%s",
+				       vlan2str(&sta->vlan_id));
 		}
 
 		hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211,
@@ -761,8 +761,8 @@ int ap_sta_bind_vlan(struct hostapd_data *hapd, struct sta_info *sta,
 				       HOSTAPD_MODULE_IEEE80211,
 				       HOSTAPD_LEVEL_DEBUG, "could not "
 				       "configure encryption for VLAN "
-				       "interface for vlan_id=%d",
-				       vlan_untagged(&sta->vlan_id));
+				       "interface for vlan_id=%s",
+				       vlan2str(&sta->vlan_id));
 		}
 	}
 
@@ -777,8 +777,7 @@ int ap_sta_bind_vlan(struct hostapd_data *hapd, struct sta_info *sta,
 	if (ret < 0) {
 		hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211,
 			       HOSTAPD_LEVEL_DEBUG, "could not bind the STA "
-			       "entry to vlan_id=%d",
-			       vlan_untagged(&sta->vlan_id));
+			       "entry to vlan_id=%s", vlan2str(&sta->vlan_id));
 	}
 	return ret;
 #else /* CONFIG_NO_VLAN */
diff --git a/src/ap/vlan_init.c b/src/ap/vlan_init.c
index a501a25..3b024fa 100644
--- a/src/ap/vlan_init.c
+++ b/src/ap/vlan_init.c
@@ -1012,7 +1012,7 @@ int vlan_init(struct hostapd_data *hapd)
 			return -1;
 		}
 
-		vlan_alloc(&vlan->vlan_id, VLAN_ID_WILDCARD);
+		vlan_alloc(&vlan->vlan_id, VLAN_ID_WILDCARD, 0, NULL);
 		os_snprintf(vlan->ifname, sizeof(vlan->ifname), "%s.#",
 			    hapd->conf->iface);
 		vlan->next = hapd->conf->vlan;
@@ -1048,8 +1048,8 @@ struct hostapd_vlan * vlan_add_dynamic(struct hostapd_data *hapd,
 	    vlan_untagged(&vlan->vlan_id) != VLAN_ID_WILDCARD)
 		return NULL;
 
-	wpa_printf(MSG_DEBUG, "VLAN: %s(vlan_id=%d ifname=%s)",
-		   __func__, vlan_untagged(&vlan_id), vlan->ifname);
+	wpa_printf(MSG_DEBUG, "VLAN: %s(vlan_id=%s ifname=%s)",
+		   __func__, vlan2str(&vlan_id), vlan->ifname);
 	ifname = os_strdup(vlan->ifname);
 	if (ifname == NULL)
 		return NULL;
@@ -1093,12 +1093,11 @@ int vlan_remove_dynamic(struct hostapd_data *hapd, vlan_t vlan_id)
 {
 	struct hostapd_vlan *vlan;
 
-	if (vlan_untagged(&vlan_id) <= 0 ||
-	    vlan_untagged(&vlan_id) > MAX_VLAN_ID)
+	if (vlan_untagged(&vlan->vlan_id) == VLAN_ID_WILDCARD)
 		return 1;
 
-	wpa_printf(MSG_DEBUG, "VLAN: %s(vlan_id=%d)", __func__,
-	           vlan_untagged(&vlan_id));
+	wpa_printf(MSG_DEBUG, "VLAN: %s(vlan_id=%s)", __func__,
+	           vlan2str(&vlan_id));
 
 	vlan = hapd->conf->vlan;
 	while (vlan) {
diff --git a/src/ap/wpa_auth.c b/src/ap/wpa_auth.c
index eca8c1e..3b44912 100644
--- a/src/ap/wpa_auth.c
+++ b/src/ap/wpa_auth.c
@@ -2397,7 +2397,7 @@ static void wpa_group_gtk_init(struct wpa_authenticator *wpa_auth,
 			       struct wpa_group *group)
 {
 	wpa_printf(MSG_DEBUG, "WPA: group state machine entering state "
-		   "GTK_INIT (VLAN-ID %d)", vlan_untagged(&group->vlan_id));
+		   "GTK_INIT (VLAN-ID %s)", vlan2str(&group->vlan_id));
 	group->changed = FALSE; /* GInit is not cleared here; avoid loop */
 	group->wpa_group_state = WPA_GROUP_GTK_INIT;
 
@@ -2534,7 +2534,7 @@ static void wpa_group_setkeys(struct wpa_authenticator *wpa_auth,
 	int tmp;
 
 	wpa_printf(MSG_DEBUG, "WPA: group state machine entering state "
-		   "SETKEYS (VLAN-ID %d)", vlan_untagged(&group->vlan_id));
+		   "SETKEYS (VLAN-ID %s)", vlan2str(&group->vlan_id));
 	group->changed = TRUE;
 	group->wpa_group_state = WPA_GROUP_SETKEYS;
 	group->GTKReKey = FALSE;
@@ -2591,7 +2591,7 @@ static int wpa_group_setkeysdone(struct wpa_authenticator *wpa_auth,
 				 struct wpa_group *group)
 {
 	wpa_printf(MSG_DEBUG, "WPA: group state machine entering state "
-		   "SETKEYSDONE (VLAN-ID %d)", vlan_untagged(&group->vlan_id));
+		   "SETKEYSDONE (VLAN-ID %s)", vlan2str(&group->vlan_id));
 	group->changed = TRUE;
 	group->wpa_group_state = WPA_GROUP_SETKEYSDONE;
 
@@ -2982,13 +2982,14 @@ static void wpa_group_free(void *eloop_ctx, void *timeout_ctx)
 	struct wpa_group *group = timeout_ctx;
 	struct wpa_group *prev = wpa_auth->group;
 
-	wpa_printf(MSG_DEBUG, "WPA: Remove group state machine for VLAN-ID %d",
-		   vlan_untagged(&group->vlan_id));
+	wpa_printf(MSG_DEBUG, "WPA: Remove group state machine for VLAN-ID %s",
+	                       vlan2str(&group->vlan_id));
 
 	while (prev) {
 		if (prev->next == group) {
 			/* This never frees the special first group as needed */
 			prev->next = group->next;
+			vlan_free(&group->vlan_id);
 			os_free(group);
 			break;
 		}
@@ -3037,8 +3038,8 @@ wpa_auth_add_group(struct wpa_authenticator *wpa_auth, vlan_t vlan_id)
 	if (wpa_auth == NULL || wpa_auth->group == NULL)
 		return NULL;
 
-	wpa_printf(MSG_DEBUG, "WPA: Add group state machine for VLAN-ID %d",
-		   vlan_untagged(&vlan_id));
+	wpa_printf(MSG_DEBUG, "WPA: Add group state machine for VLAN-ID %s",
+		   vlan2str(&vlan_id));
 	group = wpa_group_init(wpa_auth, vlan_id, 0);
 	if (group == NULL)
 		return NULL;
@@ -3074,8 +3075,8 @@ int wpa_auth_sta_set_vlan(struct wpa_state_machine *sm, vlan_t vlan_id)
 		return 0;
 
 	wpa_printf(MSG_DEBUG, "WPA: Moving STA " MACSTR " to use group state "
-		   "machine for VLAN ID %d", MAC2STR(sm->addr),
-		   vlan_untagged(&vlan_id));
+		   "machine for VLAN ID %s", MAC2STR(sm->addr),
+		   vlan2str(&vlan_id));
 
 	wpa_group_put(sm->wpa_auth, sm->group);
 	sm->group = group;
diff --git a/src/ap/wpa_auth_ie.c b/src/ap/wpa_auth_ie.c
index b0aa98a..d103881 100644
--- a/src/ap/wpa_auth_ie.c
+++ b/src/ap/wpa_auth_ie.c
@@ -607,9 +607,9 @@ int wpa_validate_wpa_ie(struct wpa_authenticator *wpa_auth,
 	if (sm->pmksa) {
 		wpa_auth_vlogger(wpa_auth, sm->addr, LOGGER_DEBUG,
 				 "PMKID found from PMKSA cache "
-				 "eap_type=%d vlan_id=%d",
+				 "eap_type=%d vlan_id=%s",
 				 sm->pmksa->eap_type_authsrv,
-				 vlan_untagged(&sm->pmksa->vlan_id));
+				 vlan2str(&sm->pmksa->vlan_id));
 		os_memcpy(wpa_auth->dot11RSNAPMKIDUsed, pmkid, PMKID_LEN);
 	}
 
diff --git a/src/common/vlan.h b/src/common/vlan.h
index 9982d46..79dc14e 100644
--- a/src/common/vlan.h
+++ b/src/common/vlan.h
@@ -9,10 +9,169 @@
 #ifndef HOSTAPD_VLAN_H
 #define HOSTAPD_VLAN_H
 
+#ifdef DEBUG_VLAN_FREE
+#include <signal.h>
+#endif /* DEBUG_VLAN_FREE */
+
+#define MAX_VLAN_ID 4094
+
+#ifdef CONFIG_VLAN_TAGGED
+#define VLAN_NULL (struct vlan_description) {0}
+
+struct vlan_description {
+        int untagged;
+        unsigned int num_tagged;
+        int* tagged;
+	unsigned int* references;
+#ifdef DEBUG_VLAN_FREE
+	/* enforce that copies cannot be freed by error */
+	struct vlan_description *self;
+#endif /* DEBUG_VLAN_FREE */
+};
+typedef struct vlan_description vlan_t;
+
+static inline void vlan_free(vlan_t *dst)
+{
+#ifdef DEBUG_VLAN_FREE
+	/* check that the vlan_t instance was not moved around ! */
+	if (dst->self != NULL && dst->self != dst)
+		raise(SIGILL);
+	dst->self = NULL;
+#endif /* DEBUG_VLAN_FREE */
+	/* end */
+	dst->untagged = 0;
+	if (dst->num_tagged == 0)
+		return;
+	dst->references--;
+	if (dst->references == 0) {
+		os_free(dst->tagged);
+		os_free(dst->references);
+	}
+	dst->tagged = NULL;
+	dst->num_tagged = 0;
+	dst->references = NULL;
+}
+
+static int cmp_int(const void *a, const void *b)
+{
+	const int *ia = (const int *) a;
+	const int *ib = (const int *) b;
+	return (*ia > *ib) - (*ia < *ib);
+}
+
+static inline void vlan_alloc(vlan_t *dst, const int untagged,
+                              const unsigned int num_tagged, const int* tagged)
+{
+        int i, offset=0;
+
+	vlan_free(dst);
+
+	dst->untagged = untagged;
+	dst->num_tagged = num_tagged;
+#ifdef DEBUG_VLAN_FREE
+	dst->self = dst;
+#endif /* DEBUG_VLAN_FREE */
+	if (dst->num_tagged == 0)
+		return;
+	dst->references = os_zalloc(sizeof(dst->references));
+	dst->tagged = os_zalloc(sizeof(*dst->tagged) * num_tagged);
+	if (!dst->tagged || !dst->references) {
+		dst->num_tagged = 0;
+		return;
+	}
+	os_memcpy(dst->tagged, tagged, sizeof(*dst->tagged) * num_tagged);
+	(*dst->references)++;
+
+        qsort(dst->tagged, dst->num_tagged, sizeof(int), cmp_int);
+        for (i=1; i < dst->num_tagged; i++) {
+                if (dst->tagged[i-1-offset] == dst->tagged[i])
+                        offset++;
+                else if (dst->tagged[i] <= 0 || dst->tagged[i] > MAX_VLAN_ID)
+                        offset++;
+                else if (offset > 0)
+                        dst->tagged[i-offset] = dst->tagged[i];
+        }
+        dst->num_tagged -= offset;
+}
+
+static inline void vlan_alloc_copy(vlan_t *dst, const vlan_t *src)
+{
+	vlan_free(dst);
+	dst->untagged = src->untagged;
+	dst->num_tagged = src->num_tagged;
+#ifdef DEBUG_VLAN_FREE
+	dst->self = dst;
+#endif /* DEBUG_VLAN_FREE */
+	if (dst->num_tagged == 0)
+		return;
+	dst->references = src->references;
+	dst->tagged = src->tagged;
+	(*dst->references)++;
+}
+
+static inline int vlan_cmp(const vlan_t *a, const vlan_t *b)
+{
+	int i;
+	if (!a && !b)
+		return 1;
+	if (!a || !b)
+		return 0;
+	if (a->untagged != b->untagged)
+		return 0;
+	if (a->num_tagged != b->num_tagged)
+		return 0;
+	for (i=0; i < a->num_tagged; i++)
+		if (a->tagged[i] != b->tagged[i]) return 0;
+	return 1;
+}
+
+static inline int vlan_untagged(const vlan_t *a)
+{
+	if (!a)
+		return 0;
+	return a->untagged;
+}
+
+static inline int vlan_tagged(const vlan_t *a, int **tagged)
+{
+	if (tagged)
+		*tagged = NULL;
+	if (!a)
+		return 0;
+	if (tagged)
+		*tagged = a->tagged;
+	return a->num_tagged;
+}
+
+static inline int vlan_notempty(const vlan_t *a)
+{
+	if (!a)
+		return 0;
+	return a->untagged || a->num_tagged;
+}
+
+static inline char* vlan2str(const vlan_t *a)
+{
+	if (!a)
+		return "";
+	static char buf[6 * 4096];
+	int i;
+	char *pos = buf;
+	pos += os_snprintf(pos, sizeof(buf) + buf - pos, "u%d", a->untagged);
+	for (i=0; i < a->num_tagged; i++) {
+		pos += os_snprintf(pos, sizeof(buf) + buf - pos, "t%d",
+		                a->tagged[i]);
+	}
+	return buf;
+}
+
+#else /* CONFIG_VLAN_TAGGED */
+
 #define VLAN_NULL 0
 typedef int vlan_t;
 
-static inline void vlan_alloc(vlan_t *dst, const int untagged)
+static inline void vlan_alloc(vlan_t *dst, const int untagged,
+                              const unsigned int num_tagged, const int* tagged)
 {
 	*dst = untagged;
 }
@@ -43,6 +202,13 @@ static inline int vlan_untagged(const vlan_t *a)
 	return *a;
 }
 
+static inline int vlan_tagged(const vlan_t *a, int **tagged)
+{
+	if (tagged)
+		*tagged = NULL;
+	return 0;
+}
+
 static inline int vlan_notempty(const vlan_t *a)
 {
 	if (!a)
@@ -50,4 +216,15 @@ static inline int vlan_notempty(const vlan_t *a)
 	return *a;
 }
 
-#endif
+static inline char* vlan2str(vlan_t *a)
+{
+	if (!a)
+		return "";
+	static char buf[5];
+	snprintf(buf, sizeof(buf), "%d", *a);
+	return buf;
+}
+
+#endif /* CONFIG_VLAN_TAGGED */
+
+#endif /* HOSTAPD_VLAN_H */
diff --git a/src/drivers/driver_nl80211.c b/src/drivers/driver_nl80211.c
index aa4310b..829772e 100644
--- a/src/drivers/driver_nl80211.c
+++ b/src/drivers/driver_nl80211.c
@@ -8536,8 +8536,8 @@ static int i802_set_sta_vlan(struct i802_bss *bss, const u8 *addr,
 	msg = NULL;
 	if (ret < 0) {
 		wpa_printf(MSG_ERROR, "nl80211: NL80211_ATTR_STA_VLAN (addr="
-			   MACSTR " ifname=%s vlan_id=%d) failed: %d (%s)",
-			   MAC2STR(addr), ifname, vlan_untagged(&vlan_id), ret,
+			   MACSTR " ifname=%s vlan_id=%s) failed: %d (%s)",
+			   MAC2STR(addr), ifname, vlan2str(&vlan_id), ret,
 			   strerror(-ret));
 	}
  nla_put_failure:
diff --git a/src/drivers/driver_test.c b/src/drivers/driver_test.c
index e19845f..bca109a 100644
--- a/src/drivers/driver_test.c
+++ b/src/drivers/driver_test.c
@@ -1129,8 +1129,8 @@ static int test_driver_set_privacy(void *priv, int enabled)
 static int test_driver_set_sta_vlan(void *priv, const u8 *addr,
 				    const char *ifname, vlan_t vlan_id)
 {
-	wpa_printf(MSG_DEBUG, "%s(addr=" MACSTR " ifname=%s vlan_id=%d)",
-		   __func__, MAC2STR(addr), ifname, vlan_untagged(&vlan_id));
+	wpa_printf(MSG_DEBUG, "%s(addr=" MACSTR " ifname=%s vlan_id=%s)",
+		   __func__, MAC2STR(addr), ifname, vlan2str(&vlan_id));
 	return 0;
 }
 
