diff --git a/src/ap/ap_config.c b/src/ap/ap_config.c
index d67096e..f4eadf3 100644
--- a/src/ap/ap_config.c
+++ b/src/ap/ap_config.c
@@ -673,10 +673,17 @@ int hostapd_vlan_valid(struct hostapd_vlan *vlan,
 		       struct vlan_description *vlan_desc)
 {
 	struct hostapd_vlan *v = vlan;
+	int i;
 
-	if (!vlan_desc->notempty || vlan_desc->untagged <= 0 ||
+	if (!vlan_desc->notempty || vlan_desc->untagged < 0 ||
 	    vlan_desc->untagged > MAX_VLAN_ID)
 		return 0;
+	for (i = 0; i < MAX_NUM_TAGGED_VLAN; i++)
+		if (vlan_desc->tagged[i] < 0 ||
+		    vlan_desc->tagged[i] > MAX_VLAN_ID)
+			return 0;
+	if (!vlan_desc->untagged && !vlan_desc->tagged[0])
+		return 0;
 
 	while (v) {
 		if (!vlan_compare(&v->vlan_desc, vlan_desc)
diff --git a/src/ap/ieee802_11.c b/src/ap/ieee802_11.c
index be99dda..cba995a 100644
--- a/src/ap/ieee802_11.c
+++ b/src/ap/ieee802_11.c
@@ -1101,8 +1101,9 @@ static void handle_auth(struct hostapd_data *hapd,
 		if (!hostapd_vlan_valid(hapd->conf->vlan, &vlan_id)) {
 			hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_RADIUS,
 				       HOSTAPD_LEVEL_INFO, "Invalid VLAN "
-				       "%d received from RADIUS server",
-				       vlan_id.untagged);
+				       "%d%s received from RADIUS server",
+				       vlan_id.untagged,
+				       vlan_id.tagged[0] ? "+" : "");
 			resp = WLAN_STATUS_UNSPECIFIED_FAILURE;
 			goto fail;
 		}
diff --git a/src/ap/ieee802_11_auth.c b/src/ap/ieee802_11_auth.c
index ffa75a4..bd86607 100644
--- a/src/ap/ieee802_11_auth.c
+++ b/src/ap/ieee802_11_auth.c
@@ -499,6 +499,7 @@ hostapd_acl_recv_radius(struct radius_msg *msg, struct radius_msg *req,
 	struct hostapd_acl_query_data *query, *prev;
 	struct hostapd_cached_radius_acl *cache;
 	struct radius_hdr *hdr = radius_msg_get_hdr(msg);
+	int *untagged, *tagged, *notempty;
 
 	query = hapd->acl_queries;
 	prev = NULL;
@@ -556,8 +557,12 @@ hostapd_acl_recv_radius(struct radius_msg *msg, struct radius_msg *req,
 			cache->acct_interim_interval = 0;
 		}
 
-		cache->vlan_id.untagged = radius_msg_get_vlanid(msg);
-		cache->vlan_id.notempty = !!cache->vlan_id.untagged;
+		notempty = &cache->vlan_id.notempty;
+		untagged = &cache->vlan_id.untagged;
+		tagged = cache->vlan_id.tagged;
+		*notempty = !!radius_msg_get_vlanid(msg, untagged,
+						    MAX_NUM_TAGGED_VLAN,
+						    tagged);
 
 		decode_tunnel_passwords(hapd, shared_secret, shared_secret_len,
 					msg, req, cache);
@@ -585,8 +590,10 @@ hostapd_acl_recv_radius(struct radius_msg *msg, struct radius_msg *req,
 			hostapd_logger(hapd, query->addr,
 				       HOSTAPD_MODULE_RADIUS,
 				       HOSTAPD_LEVEL_INFO,
-				       "Invalid VLAN %d received from RADIUS server",
-				       cache->vlan_id.untagged);
+				       "Invalid VLAN %d%s received from "
+				       "RADIUS server",
+				       cache->vlan_id.untagged,
+				       cache->vlan_id.tagged[0] ? "+" : "");
 			os_memset(&cache->vlan_id, 0, sizeof(cache->vlan_id));
 		}
 		if (hapd->conf->ssid.dynamic_vlan == DYNAMIC_VLAN_REQUIRED &&
diff --git a/src/ap/ieee802_1x.c b/src/ap/ieee802_1x.c
index 6c79147..01e27eb 100644
--- a/src/ap/ieee802_1x.c
+++ b/src/ap/ieee802_1x.c
@@ -1606,6 +1606,7 @@ ieee802_1x_receive_auth(struct radius_msg *msg, struct radius_msg *req,
 	int override_eapReq = 0;
 	struct radius_hdr *hdr = radius_msg_get_hdr(msg);
 	struct vlan_description vlan_desc;
+	int *untagged, *tagged, *notempty;
 
 	os_memset(&vlan_desc, 0, sizeof(vlan_desc));
 
@@ -1673,8 +1674,12 @@ ieee802_1x_receive_auth(struct radius_msg *msg, struct radius_msg *req,
 	case RADIUS_CODE_ACCESS_ACCEPT:
 #ifndef CONFIG_NO_VLAN
 		if (hapd->conf->ssid.dynamic_vlan != DYNAMIC_VLAN_DISABLED) {
-			vlan_desc.untagged = radius_msg_get_vlanid(msg);
-			vlan_desc.notempty = !!vlan_desc.untagged;
+			notempty = &vlan_desc.notempty;
+			untagged = &vlan_desc.untagged;
+			tagged = vlan_desc.tagged;
+			*notempty = !!radius_msg_get_vlanid(msg, untagged,
+							    MAX_NUM_TAGGED_VLAN,
+							    tagged);
 		}
 
 		if (vlan_desc.notempty &&
@@ -1683,8 +1688,10 @@ ieee802_1x_receive_auth(struct radius_msg *msg, struct radius_msg *req,
 			hostapd_logger(hapd, sta->addr,
 				       HOSTAPD_MODULE_RADIUS,
 				       HOSTAPD_LEVEL_INFO,
-				       "Invalid VLAN %d received from RADIUS server",
-				       vlan_desc.untagged);
+				       "Invalid VLAN %d%s received from "
+				       "RADIUS server",
+				       vlan_desc.untagged,
+				       vlan_desc.tagged[0] ? "+" : "");
 			os_memset(&vlan_desc, 0, sizeof(vlan_desc));
 			ap_sta_set_vlan(hapd, sta, &vlan_desc);
 			break;
diff --git a/src/ap/sta_info.c b/src/ap/sta_info.c
index 23963b7..a41ce4b 100644
--- a/src/ap/sta_info.c
+++ b/src/ap/sta_info.c
@@ -793,6 +793,23 @@ int ap_sta_wps_cancel(struct hostapd_data *hapd,
 #endif /* CONFIG_WPS */
 
 
+static int ap_sta_get_free_vlan_id(struct hostapd_data *hapd)
+{
+	struct hostapd_vlan *vlan = NULL;
+	int vlan_id = MAX_VLAN_ID + 2;
+retry:
+	vlan = hapd->conf->vlan;
+	while (vlan) {
+		if (vlan->vlan_id == vlan_id)
+			break;
+		vlan = vlan->next;
+	}
+	if (!vlan)
+		return vlan_id;
+	vlan_id++;
+	goto retry;
+}
+
 int ap_sta_set_vlan(struct hostapd_data *hapd, struct sta_info *sta,
 		    struct vlan_description *vlan_desc)
 {
@@ -801,10 +818,13 @@ int ap_sta_set_vlan(struct hostapd_data *hapd, struct sta_info *sta,
 
 	if (hapd->conf->ssid.dynamic_vlan == DYNAMIC_VLAN_DISABLED)
 		vlan_desc = NULL;
-	else if (vlan_desc && vlan_desc->notempty) {
-		if (!vlan_compare(vlan_desc, sta->vlan_desc))
-			return 0; /* nothing to change */
 
+	/* check if there is something to do */
+	if (!vlan_compare(vlan_desc, sta->vlan_desc))
+		return 0; /* nothing to change */
+
+	/* now the real vlan changed or the sta just needs its own vif */
+	if (vlan_desc->notempty) {
 		vlan = hapd->conf->vlan;
 		while (vlan) {
 			if (!vlan_compare(&vlan->vlan_desc, vlan_desc))
@@ -818,12 +838,16 @@ int ap_sta_set_vlan(struct hostapd_data *hapd, struct sta_info *sta,
 		else if (wildcard_vlan) {
 			vlan = wildcard_vlan;
 			vlan_id = vlan_desc->untagged;
+			if (vlan_desc->tagged[0])
+				/* tagged vlan configuration */
+				vlan_id = ap_sta_get_free_vlan_id(hapd);
 		} else {
 			hostapd_logger(hapd, sta->addr,
 				       HOSTAPD_MODULE_IEEE80211,
 				       HOSTAPD_LEVEL_DEBUG, "missing vlan and "
-				       "wildcard for vlan=%d",
-				       vlan_desc->untagged);
+				       "wildcard for vlan=%d%s",
+				       vlan_desc->untagged,
+				       vlan_desc->tagged[0] ? "+" : "");
 			vlan_id = 0;
 			ret = -1;
 			goto done;
@@ -836,8 +860,10 @@ int ap_sta_set_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=%d",
-				       vlan_desc->untagged);
+				       "dynamic VLAN interface for "
+				       "vlan=%d%s",
+				       vlan_desc->untagged,
+				       vlan_desc->tagged[0] ? "+" : "");
 			vlan_id = 0;
 			ret = -1;
 			goto done;
diff --git a/src/ap/vlan.h b/src/ap/vlan.h
index f0e1901..d1b30ed 100644
--- a/src/ap/vlan.h
+++ b/src/ap/vlan.h
@@ -9,9 +9,12 @@
 #ifndef VLAN_H
 #define VLAN_H
 
+#define MAX_NUM_TAGGED_VLAN 32
+
 struct vlan_description {
 	int notempty; /* 0 : no vlan information present, 1: else */
 	int untagged; /* >0 802.1q vid */
+	int tagged[MAX_NUM_TAGGED_VLAN]; /* first k items, ascending order */
 };
 
 #ifndef CONFIG_NO_VLAN
diff --git a/src/ap/wpa_auth_ie.c b/src/ap/wpa_auth_ie.c
index 5a9b9bd..2828abe 100644
--- a/src/ap/wpa_auth_ie.c
+++ b/src/ap/wpa_auth_ie.c
@@ -481,7 +481,7 @@ int wpa_validate_wpa_ie(struct wpa_authenticator *wpa_auth,
 	u32 selector;
 	size_t i;
 	const u8 *pmkid = NULL;
-	struct vlan_description *vlan_desc;
+	struct vlan_description *vlan;
 
 	if (wpa_auth == NULL || sm == NULL)
 		return WPA_NOT_ENABLED;
@@ -713,12 +713,13 @@ int wpa_validate_wpa_ie(struct wpa_authenticator *wpa_auth,
 		}
 	}
 	if (sm->pmksa && pmkid) {
-		vlan_desc = sm->pmksa->vlan_desc;
+		vlan = sm->pmksa->vlan_desc;
 		wpa_auth_vlogger(wpa_auth, sm->addr, LOGGER_DEBUG,
 				 "PMKID found from PMKSA cache "
-				 "eap_type=%d vlan=%d",
+				 "eap_type=%d vlan=%d%s",
 				 sm->pmksa->eap_type_authsrv,
-				 vlan_desc ? vlan_desc->untagged : 0);
+				 vlan ? vlan->untagged : 0,
+				 (vlan && vlan->tagged[0]) ? "+" : "");
 		os_memcpy(wpa_auth->dot11RSNAPMKIDUsed, pmkid, PMKID_LEN);
 	}
 
diff --git a/src/radius/radius.c b/src/radius/radius.c
index 266b29f..5dcfbb5 100644
--- a/src/radius/radius.c
+++ b/src/radius/radius.c
@@ -256,6 +256,8 @@ static const struct radius_attr_type radius_attrs[] =
 	  RADIUS_ATTR_HEXDUMP },
 	{ RADIUS_ATTR_WLAN_GROUP_MGMT_CIPHER, "WLAN-Group-Mgmt-Pairwise-Cipher",
 	  RADIUS_ATTR_HEXDUMP },
+	{ RADIUS_ATTR_EGRESS_VLANID, "EGRESS-VLANID",
+	  RADIUS_ATTR_HEXDUMP },
 };
 #define RADIUS_ATTRS ARRAY_SIZE(radius_attrs)
 
@@ -1422,13 +1424,29 @@ struct radius_tunnel_attrs {
 	int vlanid;
 };
 
+static int cmp_int(const void *a, const void *b)
+{
+	int x, y;
+
+	x = *((int *) a);
+	y = *((int *) b);
+	return (x - y);
+}
 
 /**
  * radius_msg_get_vlanid - Parse RADIUS attributes for VLAN tunnel information
+ * The k tagged vlans found are sorted by vlan_id and stored in the first k
+ * items of tagged.
+ *
  * @msg: RADIUS message
- * Returns: VLAN ID for the first tunnel configuration or 0 if none is found
+ * @param untagged: pointer to store untagged vid
+ * @param numtagged: size of tagged
+ * @param tagged: pointer to store tagged list
+ *
+ * Returns: 0 if neither tagged nor untagged configuration is found, 1 else
  */
-int radius_msg_get_vlanid(struct radius_msg *msg)
+int radius_msg_get_vlanid(struct radius_msg *msg, int *untagged, int numtagged,
+			  int *tagged)
 {
 	struct radius_tunnel_attrs tunnel[RADIUS_TUNNEL_TAGS], *tun;
 	size_t i;
@@ -1436,8 +1454,12 @@ int radius_msg_get_vlanid(struct radius_msg *msg)
 	const u8 *data;
 	char buf[10];
 	size_t dlen;
+	int taggedidx = 0, vlan_id;
 
 	os_memset(&tunnel, 0, sizeof(tunnel));
+	for (i = 0; i < numtagged; i++)
+		tagged[i] = 0;
+	*untagged = 0;
 
 	for (i = 0; i < msg->attr_used; i++) {
 		attr = radius_get_attr_hdr(msg, i);
@@ -1474,21 +1496,46 @@ int radius_msg_get_vlanid(struct radius_msg *msg)
 				break;
 			os_memcpy(buf, data, dlen);
 			buf[dlen] = '\0';
+			vlan_id = atoi(buf);
+			if (vlan_id <= 0)
+				break;
 			tun->tag_used++;
-			tun->vlanid = atoi(buf);
+			tun->vlanid = vlan_id;
+			break;
+		case RADIUS_ATTR_EGRESS_VLANID: /* RFC 4675 */
+			if (attr->length != 6)
+				break;
+			vlan_id = WPA_GET_BE24(data + 1);
+			if (vlan_id <= 0)
+				break;
+			if (data[0] == 0x32)
+				*untagged = vlan_id;
+			else if (data[0] == 0x31 && tagged &&
+				 taggedidx < numtagged)
+				tagged[taggedidx++] = vlan_id;
 			break;
 		}
 	}
 
+	/* use  tunnel with lowest tag for untagged vlan id */
 	for (i = 0; i < RADIUS_TUNNEL_TAGS; i++) {
 		tun = &tunnel[i];
 		if (tun->tag_used &&
 		    tun->type == RADIUS_TUNNEL_TYPE_VLAN &&
 		    tun->medium_type == RADIUS_TUNNEL_MEDIUM_TYPE_802 &&
-		    tun->vlanid > 0)
-			return tun->vlanid;
+		    tun->vlanid > 0) {
+			*untagged = tun->vlanid;
+			break;
+		}
 	}
 
+	if (taggedidx)
+		qsort(tagged, taggedidx, sizeof(int), cmp_int);
+
+	if (*untagged > 0)
+		return 1;
+	if (taggedidx)
+		return 1;
 	return 0;
 }
 
diff --git a/src/radius/radius.h b/src/radius/radius.h
index f14de53..c841c89 100644
--- a/src/radius/radius.h
+++ b/src/radius/radius.h
@@ -80,6 +80,7 @@ enum { RADIUS_ATTR_USER_NAME = 1,
        RADIUS_ATTR_ACCT_INPUT_GIGAWORDS = 52,
        RADIUS_ATTR_ACCT_OUTPUT_GIGAWORDS = 53,
        RADIUS_ATTR_EVENT_TIMESTAMP = 55,
+       RADIUS_ATTR_EGRESS_VLANID = 56,
        RADIUS_ATTR_NAS_PORT_TYPE = 61,
        RADIUS_ATTR_TUNNEL_TYPE = 64,
        RADIUS_ATTR_TUNNEL_MEDIUM_TYPE = 65,
@@ -275,7 +276,8 @@ radius_msg_add_attr_user_password(struct radius_msg *msg,
 				  const u8 *data, size_t data_len,
 				  const u8 *secret, size_t secret_len);
 int radius_msg_get_attr(struct radius_msg *msg, u8 type, u8 *buf, size_t len);
-int radius_msg_get_vlanid(struct radius_msg *msg);
+int radius_msg_get_vlanid(struct radius_msg *msg, int *untagged, int numtagged,
+			  int *tagged);
 char * radius_msg_get_tunnel_password(struct radius_msg *msg, int *keylen,
 				      const u8 *secret, size_t secret_len,
 				      struct radius_msg *sent_msg, size_t n);
