Przeglądaj źródła

mac80211: implement wds sta support (wds ap support work in progress, needs hostapd changes)

SVN-Revision: 18356
Felix Fietkau 16 lat temu
rodzic
commit
1dbcfdaf2f

+ 155 - 0
package/iw/patches/000-upstream.patch

@@ -0,0 +1,155 @@
+--- a/Makefile
++++ b/Makefile
+@@ -3,7 +3,7 @@
+ MAKEFLAGS += --no-print-directory
+ 
+ PREFIX ?= /usr
+-BINDIR ?= $(PREFIX)/bin
++SBINDIR ?= $(PREFIX)/sbin
+ MANDIR ?= $(PREFIX)/share/man
+ PKG_CONFIG ?= pkg-config
+ 
+@@ -85,8 +85,8 @@ check:
+ 
+ install: iw iw.8.gz
+ 	@$(NQ) ' INST iw'
+-	$(Q)$(MKDIR) $(DESTDIR)$(BINDIR)
+-	$(Q)$(INSTALL) -m 755 -t $(DESTDIR)$(BINDIR) iw
++	$(Q)$(MKDIR) $(DESTDIR)$(SBINDIR)
++	$(Q)$(INSTALL) -m 755 -t $(DESTDIR)$(SBINDIR) iw
+ 	@$(NQ) ' INST iw.8'
+ 	$(Q)$(MKDIR) $(DESTDIR)$(MANDIR)/man8/
+ 	$(Q)$(INSTALL) -m 644 -t $(DESTDIR)$(MANDIR)/man8/ iw.8.gz
+--- a/iw.c
++++ b/iw.c
+@@ -223,9 +223,12 @@ static int phy_lookup(char *name)
+ 	if (fd < 0)
+ 		return -1;
+ 	pos = read(fd, buf, sizeof(buf) - 1);
+-	if (pos < 0)
++	if (pos < 0) {
++		close(fd);
+ 		return -1;
++	}
+ 	buf[pos] = '\0';
++	close(fd);
+ 	return atoi(buf);
+ }
+ 
+--- a/nl80211.h
++++ b/nl80211.h
+@@ -262,6 +262,9 @@
+  *	reasons, for this the %NL80211_ATTR_DISCONNECTED_BY_AP and
+  *	%NL80211_ATTR_REASON_CODE attributes are used.
+  *
++ * @NL80211_CMD_SET_WIPHY_NETNS: Set a wiphy's netns. Note that all devices
++ *	associated with this wiphy must be down and will follow.
++ *
+  * @NL80211_CMD_MAX: highest used command number
+  * @__NL80211_CMD_AFTER_LAST: internal use
+  */
+@@ -336,6 +339,8 @@ enum nl80211_commands {
+ 	NL80211_CMD_ROAM,
+ 	NL80211_CMD_DISCONNECT,
+ 
++	NL80211_CMD_SET_WIPHY_NETNS,
++
+ 	/* add new commands above here */
+ 
+ 	/* used to define NL80211_CMD_MAX below */
+@@ -475,10 +480,6 @@ enum nl80211_commands {
+  * @NL80211_ATTR_SCAN_FREQUENCIES: nested attribute with frequencies (in MHz)
+  * @NL80211_ATTR_SCAN_SSIDS: nested attribute with SSIDs, leave out for passive
+  *	scanning and include a zero-length SSID (wildcard) for wildcard scan
+- * @NL80211_ATTR_SCAN_GENERATION: the scan generation increases whenever the
+- *	scan result list changes (BSS expired or added) so that applications
+- *	can verify that they got a single, consistent snapshot (when all dump
+- *	messages carried the same generation number)
+  * @NL80211_ATTR_BSS: scan result BSS
+  *
+  * @NL80211_ATTR_REG_INITIATOR: indicates who requested the regulatory domain
+@@ -573,6 +574,16 @@ enum nl80211_commands {
+  *	and join_ibss(), key information is in a nested attribute each
+  *	with %NL80211_KEY_* sub-attributes
+  *
++ * @NL80211_ATTR_PID: Process ID of a network namespace.
++ *
++ * @NL80211_ATTR_GENERATION: Used to indicate consistent snapshots for
++ *	dumps. This number increases whenever the object list being
++ *	dumped changes, and as such userspace can verify that it has
++ *	obtained a complete and consistent snapshot by verifying that
++ *	all dump messages contain the same generation number. If it
++ *	changed then the list changed and the dump should be repeated
++ *	completely from scratch.
++ *
+  * @NL80211_ATTR_MAX: highest attribute number currently defined
+  * @__NL80211_ATTR_AFTER_LAST: internal use
+  */
+@@ -644,7 +655,7 @@ enum nl80211_attrs {
+ 
+ 	NL80211_ATTR_SCAN_FREQUENCIES,
+ 	NL80211_ATTR_SCAN_SSIDS,
+-	NL80211_ATTR_SCAN_GENERATION,
++	NL80211_ATTR_GENERATION, /* replaces old SCAN_GENERATION */
+ 	NL80211_ATTR_BSS,
+ 
+ 	NL80211_ATTR_REG_INITIATOR,
+@@ -701,12 +712,17 @@ enum nl80211_attrs {
+ 	NL80211_ATTR_KEY,
+ 	NL80211_ATTR_KEYS,
+ 
++	NL80211_ATTR_PID,
++
+ 	/* add attributes here, update the policy in nl80211.c */
+ 
+ 	__NL80211_ATTR_AFTER_LAST,
+ 	NL80211_ATTR_MAX = __NL80211_ATTR_AFTER_LAST - 1
+ };
+ 
++/* source-level API compatibility */
++#define NL80211_ATTR_SCAN_GENERATION NL80211_ATTR_GENERATION
++
+ /*
+  * Allow user space programs to use #ifdef on new attributes by defining them
+  * here
+--- a/phy.c
++++ b/phy.c
+@@ -140,3 +140,27 @@ static int handle_rts(struct nl80211_sta
+ COMMAND(set, rts, "<rts threshold|off>",
+ 	NL80211_CMD_SET_WIPHY, 0, CIB_PHY, handle_rts,
+ 	"Set rts threshold.");
++
++static int handle_netns(struct nl80211_state *state,
++			struct nl_cb *cb,
++			struct nl_msg *msg,
++			int argc, char **argv)
++{
++	char *end;
++
++	if (argc != 1)
++		return 1;
++
++	NLA_PUT_U32(msg, NL80211_ATTR_PID,
++		    strtoul(argv[0], &end, 10)); 
++
++	if (*end != '\0')
++		return 1;
++
++	return 0;
++ nla_put_failure:
++	return -ENOBUFS;
++}
++COMMAND(set, netns, "<pid>",
++	NL80211_CMD_SET_WIPHY_NETNS, 0, CIB_PHY, handle_netns,
++	"Put this wireless device into a different network namespace");
+--- a/station.c
++++ b/station.c
+@@ -127,7 +127,7 @@ static int print_sta_handler(struct nl_m
+ 		printf("\n\tmesh plid:\t%d",
+ 			nla_get_u16(sinfo[NL80211_STA_INFO_PLID]));
+ 	if (sinfo[NL80211_STA_INFO_PLINK_STATE]) {
+-		switch (nla_get_u16(sinfo[NL80211_STA_INFO_PLINK_STATE])) {
++		switch (nla_get_u8(sinfo[NL80211_STA_INFO_PLINK_STATE])) {
+ 		case LISTEN:
+ 			strcpy(state_name, "LISTEN");
+ 			break;

+ 76 - 0
package/iw/patches/100-4addr.patch

@@ -0,0 +1,76 @@
+--- a/interface.c
++++ b/interface.c
+@@ -137,6 +137,20 @@ static int get_if_type(int *argc, char *
+ 	return 2;
+ }
+ 
++static int parse_wds_flag(const char *value, struct nl_msg *msg)
++{
++	if (strcmp(value, "on") == 0)
++		NLA_PUT_U8(msg, NL80211_ATTR_4ADDR, 1);
++	else if (strcmp(value, "off") == 0)
++		NLA_PUT_U8(msg, NL80211_ATTR_4ADDR, 0);
++	else
++		return 1;
++	return 0;
++
++nla_put_failure:
++	return 1;
++}
++
+ static int handle_interface_add(struct nl80211_state *state,
+ 				struct nl_cb *cb,
+ 				struct nl_msg *msg,
+@@ -168,6 +182,15 @@ static int handle_interface_add(struct n
+ 			mesh_id = argv[0];
+ 			argc--;
+ 			argv++;
++		} else if (strcmp(argv[0], "wds") == 0) {
++			argc--;
++			argv++;
++			if (parse_wds_flag(argv[0], msg)) {
++				fprintf(stderr, "wds error\n");
++				return 2;
++			}
++			argc--;
++			argv++;
+ 		} else if (strcmp(argv[0], "flags") == 0) {
+ 			argc--;
+ 			argv++;
+@@ -192,14 +215,14 @@ static int handle_interface_add(struct n
+  nla_put_failure:
+ 	return -ENOBUFS;
+ }
+-COMMAND(interface, add, "<name> type <type> [mesh_id <meshid>] [flags <flag>*]",
++COMMAND(interface, add, "<name> type <type> [mesh_id <meshid>] [wds on|off] [flags <flag>*]",
+ 	NL80211_CMD_NEW_INTERFACE, 0, CIB_PHY, handle_interface_add,
+ 	"Add a new virtual interface with the given configuration.\n"
+ 	IFACE_TYPES "\n\n"
+ 	"The flags are only used for monitor interfaces, valid flags are:\n"
+ 	VALID_FLAGS "\n\n"
+ 	"The mesh_id is used only for mesh mode.");
+-COMMAND(interface, add, "<name> type <type> [mesh_id <meshid>] [flags <flag>*]",
++COMMAND(interface, add, "<name> type <type> [mesh_id <meshid>] [wds on|off] [flags <flag>*]",
+ 	NL80211_CMD_NEW_INTERFACE, 0, CIB_NETDEV, handle_interface_add, NULL);
+ 
+ static int handle_interface_del(struct nl80211_state *state,
+--- a/nl80211.h
++++ b/nl80211.h
+@@ -584,6 +584,8 @@ enum nl80211_commands {
+  *	changed then the list changed and the dump should be repeated
+  *	completely from scratch.
+  *
++ * @NL80211_ATTR_4ADDR: Use 4-address frames on a virtual interface
++ *
+  * @NL80211_ATTR_MAX: highest attribute number currently defined
+  * @__NL80211_ATTR_AFTER_LAST: internal use
+  */
+@@ -714,6 +716,8 @@ enum nl80211_attrs {
+ 
+ 	NL80211_ATTR_PID,
+ 
++	NL80211_ATTR_4ADDR,
++
+ 	/* add attributes here, update the policy in nl80211.c */
+ 
+ 	__NL80211_ATTR_AFTER_LAST,

+ 69 - 0
package/iw/patches/110-sta_vlan.patch

@@ -0,0 +1,69 @@
+--- a/station.c
++++ b/station.c
+@@ -196,7 +196,7 @@ COMMAND(station, del, "<MAC address>",
+ 	NL80211_CMD_DEL_STATION, 0, CIB_NETDEV, handle_station_get,
+ 	"Remove the given station entry (use with caution!)");
+ 
+-static int handle_station_set(struct nl80211_state *state,
++static int handle_station_set_plink(struct nl80211_state *state,
+ 			      struct nl_cb *cb,
+ 			      struct nl_msg *msg,
+ 			      int argc, char **argv)
+@@ -241,9 +241,56 @@ static int handle_station_set(struct nl8
+ 	return -ENOBUFS;
+ }
+ COMMAND(station, set, "<MAC address> plink_action <open|block>",
+-	NL80211_CMD_SET_STATION, 0, CIB_NETDEV, handle_station_set,
++	NL80211_CMD_SET_STATION, 0, CIB_NETDEV, handle_station_set_plink,
+ 	"Set mesh peer link action for this station (peer).");
+ 
++static int handle_station_set_vlan(struct nl80211_state *state,
++			      struct nl_cb *cb,
++			      struct nl_msg *msg,
++			      int argc, char **argv)
++{
++	unsigned char mac_addr[ETH_ALEN];
++	unsigned long sta_vlan = 0;
++	char *err = NULL;
++
++	if (argc < 3)
++		return 1;
++
++	if (mac_addr_a2n(mac_addr, argv[0])) {
++		fprintf(stderr, "invalid mac address\n");
++		return 2;
++	}
++	argc--;
++	argv++;
++
++	if (strcmp("vlan", argv[0]) != 0)
++		return 1;
++	argc--;
++	argv++;
++
++	sta_vlan = strtoul(argv[0], &err, 0);
++	if (err && *err) {
++		fprintf(stderr, "invalid vlan id\n");
++		return 2;
++	}
++	argc--;
++	argv++;
++
++	if (argc)
++		return 1;
++
++	NLA_PUT(msg, NL80211_ATTR_MAC, ETH_ALEN, mac_addr);
++	NLA_PUT_U32(msg, NL80211_ATTR_STA_VLAN, sta_vlan);
++
++	return 0;
++ nla_put_failure:
++	return -ENOBUFS;
++}
++COMMAND(station, set, "<MAC address> vlan <ifindex>",
++	NL80211_CMD_SET_STATION, 0, CIB_NETDEV, handle_station_set_vlan,
++	"Set an AP VLAN for this station.");
++
++
+ static int handle_station_dump(struct nl80211_state *state,
+ 			       struct nl_cb *cb,
+ 			       struct nl_msg *msg,

+ 4 - 1
package/mac80211/files/lib/wifi/mac80211.sh

@@ -104,6 +104,7 @@ enable_mac80211() {
 		config_get enc "$vif" encryption
 		config_get mode "$vif" mode
 		config_get ssid "$vif" ssid
+		config_get_bool wds "$vif" wds 0
 
 		# It is far easier to delete and create the desired interface
 		case "$mode" in
@@ -123,7 +124,9 @@ enable_mac80211() {
 				iw phy "$phy" interface add "$ifname" type monitor
 			;;
 			sta)
-				iw phy "$phy" interface add "$ifname" type managed
+				local wdsflag
+				[ "$wds" -gt 0 ] && wdsflag="wds on"
+				iw phy "$phy" interface add "$ifname" type managed $wdsflag
 			;;
 		esac
 

+ 69 - 0
package/mac80211/patches/500-nl80211_4addr.patch

@@ -0,0 +1,69 @@
+--- a/include/linux/nl80211.h
++++ b/include/linux/nl80211.h
+@@ -584,6 +584,8 @@ enum nl80211_commands {
+  *	changed then the list changed and the dump should be repeated
+  *	completely from scratch.
+  *
++ * @NL80211_ATTR_4ADDR: Use 4-address frames on a virtual interface
++ *
+  * @NL80211_ATTR_MAX: highest attribute number currently defined
+  * @__NL80211_ATTR_AFTER_LAST: internal use
+  */
+@@ -714,6 +716,8 @@ enum nl80211_attrs {
+ 
+ 	NL80211_ATTR_PID,
+ 
++	NL80211_ATTR_4ADDR,
++
+ 	/* add attributes here, update the policy in nl80211.c */
+ 
+ 	__NL80211_ATTR_AFTER_LAST,
+--- a/include/net/cfg80211.h
++++ b/include/net/cfg80211.h
+@@ -207,10 +207,12 @@ struct ieee80211_supported_band {
+  * struct vif_params - describes virtual interface parameters
+  * @mesh_id: mesh ID to use
+  * @mesh_id_len: length of the mesh ID
++ * @use_4addr: use 4-address frames
+  */
+ struct vif_params {
+        u8 *mesh_id;
+        int mesh_id_len;
++       int use_4addr;
+ };
+ 
+ /**
+--- a/net/wireless/nl80211.c
++++ b/net/wireless/nl80211.c
+@@ -140,6 +140,7 @@ static struct nla_policy nl80211_policy[
+ 	[NL80211_ATTR_CIPHER_SUITE_GROUP] = { .type = NLA_U32 },
+ 	[NL80211_ATTR_WPA_VERSIONS] = { .type = NLA_U32 },
+ 	[NL80211_ATTR_PID] = { .type = NLA_U32 },
++	[NL80211_ATTR_4ADDR] = { .type = NLA_U8 },
+ };
+ 
+ /* policy for the attributes */
+@@ -989,6 +990,13 @@ static int nl80211_set_interface(struct 
+ 		change = true;
+ 	}
+ 
++	if (info->attrs[NL80211_ATTR_4ADDR]) {
++		params.use_4addr = !!nla_get_u8(info->attrs[NL80211_ATTR_4ADDR]);
++		change = true;
++	} else {
++		params.use_4addr = -1;
++	}
++
+ 	if (info->attrs[NL80211_ATTR_MNTR_FLAGS]) {
+ 		if (ntype != NL80211_IFTYPE_MONITOR) {
+ 			err = -EINVAL;
+@@ -1055,6 +1063,9 @@ static int nl80211_new_interface(struct 
+ 		params.mesh_id_len = nla_len(info->attrs[NL80211_ATTR_MESH_ID]);
+ 	}
+ 
++	if (info->attrs[NL80211_ATTR_4ADDR])
++		params.use_4addr = !!nla_get_u8(info->attrs[NL80211_ATTR_4ADDR]);
++
+ 	err = parse_monitor_flags(type == NL80211_IFTYPE_MONITOR ?
+ 				  info->attrs[NL80211_ATTR_MNTR_FLAGS] : NULL,
+ 				  &flags);

+ 260 - 0
package/mac80211/patches/510-mac80211_4addr_vlan.patch

@@ -0,0 +1,260 @@
+--- a/net/mac80211/ieee80211_i.h
++++ b/net/mac80211/ieee80211_i.h
+@@ -208,6 +208,9 @@ struct ieee80211_if_wds {
+ 
+ struct ieee80211_if_vlan {
+ 	struct list_head list;
++
++	/* used for all tx if the VLAN is configured to 4-addr mode */
++	struct sta_info *sta;
+ };
+ 
+ struct mesh_stats {
+@@ -457,6 +460,8 @@ struct ieee80211_sub_if_data {
+ 	int force_unicast_rateidx; /* forced TX rateidx for unicast frames */
+ 	int max_ratectrl_rateidx; /* max TX rateidx for rate control */
+ 
++	bool use_4addr; /* use 4-address frames */
++
+ 	union {
+ 		struct ieee80211_if_ap ap;
+ 		struct ieee80211_if_wds wds;
+--- a/net/mac80211/cfg.c
++++ b/net/mac80211/cfg.c
+@@ -36,6 +36,24 @@ static bool nl80211_type_check(enum nl80
+ 	}
+ }
+ 
++static bool nl80211_params_check(enum nl80211_iftype type,
++				 struct vif_params *params)
++{
++	if (!nl80211_type_check(type))
++		return false;
++
++	if (params->use_4addr > 0) {
++		switch(type) {
++		case NL80211_IFTYPE_AP_VLAN:
++		case NL80211_IFTYPE_STATION:
++			break;
++		default:
++			return false;
++		}
++	}
++	return true;
++}
++
+ static int ieee80211_add_iface(struct wiphy *wiphy, char *name,
+ 			       enum nl80211_iftype type, u32 *flags,
+ 			       struct vif_params *params)
+@@ -45,7 +63,7 @@ static int ieee80211_add_iface(struct wi
+ 	struct ieee80211_sub_if_data *sdata;
+ 	int err;
+ 
+-	if (!nl80211_type_check(type))
++	if (!nl80211_params_check(type, params))
+ 		return -EINVAL;
+ 
+ 	err = ieee80211_if_add(local, name, &dev, type, params);
+@@ -75,7 +93,7 @@ static int ieee80211_change_iface(struct
+ 	if (netif_running(dev))
+ 		return -EBUSY;
+ 
+-	if (!nl80211_type_check(type))
++	if (!nl80211_params_check(type, params))
+ 		return -EINVAL;
+ 
+ 	sdata = IEEE80211_DEV_TO_SUB_IF(dev);
+@@ -89,6 +107,9 @@ static int ieee80211_change_iface(struct
+ 					    params->mesh_id_len,
+ 					    params->mesh_id);
+ 
++	if (params->use_4addr >= 0)
++		sdata->use_4addr = !!params->use_4addr;
++
+ 	if (sdata->vif.type != NL80211_IFTYPE_MONITOR || !flags)
+ 		return 0;
+ 
+@@ -806,6 +827,13 @@ static int ieee80211_change_station(stru
+ 			return -EINVAL;
+ 		}
+ 
++		if (vlansdata->use_4addr) {
++			if (vlansdata->u.vlan.sta)
++				return -EBUSY;
++
++			rcu_assign_pointer(vlansdata->u.vlan.sta, sta);
++		}
++
+ 		sta->sdata = vlansdata;
+ 		ieee80211_send_layer2_update(sta);
+ 	}
+--- a/net/mac80211/sta_info.c
++++ b/net/mac80211/sta_info.c
+@@ -489,6 +489,9 @@ static void __sta_info_unlink(struct sta
+ 	local->num_sta--;
+ 	local->sta_generation++;
+ 
++	if (sdata->vif.type == NL80211_IFTYPE_AP_VLAN)
++		rcu_assign_pointer(sdata->u.vlan.sta, NULL);
++
+ 	if (local->ops->sta_notify) {
+ 		if (sdata->vif.type == NL80211_IFTYPE_AP_VLAN)
+ 			sdata = container_of(sdata->bss,
+--- a/net/mac80211/tx.c
++++ b/net/mac80211/tx.c
+@@ -1046,7 +1046,10 @@ ieee80211_tx_prepare(struct ieee80211_su
+ 
+ 	hdr = (struct ieee80211_hdr *) skb->data;
+ 
+-	tx->sta = sta_info_get(local, hdr->addr1);
++	if ((sdata->vif.type == NL80211_IFTYPE_AP_VLAN) && sdata->use_4addr)
++		tx->sta = rcu_dereference(sdata->u.vlan.sta);
++	if (!tx->sta)
++		tx->sta = sta_info_get(local, hdr->addr1);
+ 
+ 	if (tx->sta && ieee80211_is_data_qos(hdr->frame_control) &&
+ 	    (local->hw.flags & IEEE80211_HW_AMPDU_AGGREGATION)) {
+@@ -1608,7 +1611,7 @@ netdev_tx_t ieee80211_subif_start_xmit(s
+ 	const u8 *encaps_data;
+ 	int encaps_len, skip_header_bytes;
+ 	int nh_pos, h_pos;
+-	struct sta_info *sta;
++	struct sta_info *sta = NULL;
+ 	u32 sta_flags = 0;
+ 
+ 	if (unlikely(skb->len < ETH_HLEN)) {
+@@ -1625,8 +1628,25 @@ netdev_tx_t ieee80211_subif_start_xmit(s
+ 	fc = cpu_to_le16(IEEE80211_FTYPE_DATA | IEEE80211_STYPE_DATA);
+ 
+ 	switch (sdata->vif.type) {
+-	case NL80211_IFTYPE_AP:
+ 	case NL80211_IFTYPE_AP_VLAN:
++		rcu_read_lock();
++		if (sdata->use_4addr)
++			sta = rcu_dereference(sdata->u.vlan.sta);
++		if (sta) {
++			fc |= cpu_to_le16(IEEE80211_FCTL_FROMDS | IEEE80211_FCTL_TODS);
++			/* RA TA DA SA */
++			memcpy(hdr.addr1, sta->sta.addr, ETH_ALEN);
++			memcpy(hdr.addr2, dev->dev_addr, ETH_ALEN);
++			memcpy(hdr.addr3, skb->data, ETH_ALEN);
++			memcpy(hdr.addr4, skb->data + ETH_ALEN, ETH_ALEN);
++			hdrlen = 30;
++			sta_flags = get_sta_flags(sta);
++		}
++		rcu_read_unlock();
++		if (sta)
++			break;
++		/* fall through */
++	case NL80211_IFTYPE_AP:
+ 		fc |= cpu_to_le16(IEEE80211_FCTL_FROMDS);
+ 		/* DA BSSID SA */
+ 		memcpy(hdr.addr1, skb->data, ETH_ALEN);
+@@ -1700,12 +1720,21 @@ netdev_tx_t ieee80211_subif_start_xmit(s
+ 		break;
+ #endif
+ 	case NL80211_IFTYPE_STATION:
+-		fc |= cpu_to_le16(IEEE80211_FCTL_TODS);
+-		/* BSSID SA DA */
+ 		memcpy(hdr.addr1, sdata->u.mgd.bssid, ETH_ALEN);
+-		memcpy(hdr.addr2, skb->data + ETH_ALEN, ETH_ALEN);
+-		memcpy(hdr.addr3, skb->data, ETH_ALEN);
+-		hdrlen = 24;
++		if (sdata->use_4addr && ethertype != ETH_P_PAE) {
++			fc |= cpu_to_le16(IEEE80211_FCTL_FROMDS | IEEE80211_FCTL_TODS);
++			/* RA TA DA SA */
++			memcpy(hdr.addr2, dev->dev_addr, ETH_ALEN);
++			memcpy(hdr.addr3, skb->data, ETH_ALEN);
++			memcpy(hdr.addr4, skb->data + ETH_ALEN, ETH_ALEN);
++			hdrlen = 30;
++		} else {
++			fc |= cpu_to_le16(IEEE80211_FCTL_TODS);
++			/* BSSID SA DA */
++			memcpy(hdr.addr2, skb->data + ETH_ALEN, ETH_ALEN);
++			memcpy(hdr.addr3, skb->data, ETH_ALEN);
++			hdrlen = 24;
++		}
+ 		break;
+ 	case NL80211_IFTYPE_ADHOC:
+ 		/* DA SA BSSID */
+--- a/net/mac80211/iface.c
++++ b/net/mac80211/iface.c
+@@ -772,6 +772,7 @@ int ieee80211_if_change_type(struct ieee
+ 		ieee80211_mandatory_rates(sdata->local,
+ 			sdata->local->hw.conf.channel->band);
+ 	sdata->drop_unencrypted = 0;
++	sdata->use_4addr = 0;
+ 
+ 	return 0;
+ }
+@@ -853,6 +854,9 @@ int ieee80211_if_add(struct ieee80211_lo
+ 					    params->mesh_id_len,
+ 					    params->mesh_id);
+ 
++	if (params && params->use_4addr >= 0)
++		sdata->use_4addr = !!params->use_4addr;
++
+ 	mutex_lock(&local->iflist_mtx);
+ 	list_add_tail_rcu(&sdata->list, &local->interfaces);
+ 	mutex_unlock(&local->iflist_mtx);
+--- a/net/mac80211/rx.c
++++ b/net/mac80211/rx.c
+@@ -1237,6 +1237,13 @@ __ieee80211_data_to_8023(struct ieee8021
+ {
+ 	struct net_device *dev = rx->dev;
+ 	struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
++	struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)rx->skb->data;
++
++	if (sdata->vif.type == NL80211_IFTYPE_AP_VLAN && !sdata->use_4addr &&
++	    ieee80211_has_a4(hdr->frame_control))
++		return -1;
++	if (sdata->use_4addr && is_multicast_ether_addr(hdr->addr1))
++		return -1;
+ 
+ 	return ieee80211_data_to_8023(rx->skb, dev->dev_addr, sdata->vif.type);
+ }
+@@ -1590,6 +1597,7 @@ ieee80211_rx_h_data(struct ieee80211_rx_
+ {
+ 	struct net_device *dev = rx->dev;
+ 	struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)rx->skb->data;
++	struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
+ 	__le16 fc = hdr->frame_control;
+ 	int err;
+ 
+@@ -1599,6 +1607,14 @@ ieee80211_rx_h_data(struct ieee80211_rx_
+ 	if (unlikely(!ieee80211_is_data_present(hdr->frame_control)))
+ 		return RX_DROP_MONITOR;
+ 
++	/*
++	 * Allow the cooked monitor interface of an AP to see 4-addr frames so
++	 * that a 4-addr station can be detected and moved into a separate VLAN
++	 */
++	if (ieee80211_has_a4(hdr->frame_control) &&
++	    sdata->vif.type == NL80211_IFTYPE_AP)
++		return RX_DROP_MONITOR;
++
+ 	err = __ieee80211_data_to_8023(rx);
+ 	if (unlikely(err))
+ 		return RX_DROP_UNUSABLE;
+@@ -2039,7 +2055,7 @@ static int prepare_for_handlers(struct i
+ 
+ 	switch (sdata->vif.type) {
+ 	case NL80211_IFTYPE_STATION:
+-		if (!bssid)
++		if (!bssid && !sdata->use_4addr)
+ 			return 0;
+ 		if (!multicast &&
+ 		    compare_ether_addr(sdata->dev->dev_addr, hdr->addr1) != 0) {
+--- a/net/wireless/util.c
++++ b/net/wireless/util.c
+@@ -320,7 +320,9 @@ int ieee80211_data_to_8023(struct sk_buf
+ 		break;
+ 	case cpu_to_le16(IEEE80211_FCTL_TODS | IEEE80211_FCTL_FROMDS):
+ 		if (unlikely(iftype != NL80211_IFTYPE_WDS &&
+-			     iftype != NL80211_IFTYPE_MESH_POINT))
++			     iftype != NL80211_IFTYPE_MESH_POINT &&
++			     iftype != NL80211_IFTYPE_AP_VLAN &&
++			     iftype != NL80211_IFTYPE_STATION))
+ 			return -1;
+ 		if (iftype == NL80211_IFTYPE_MESH_POINT) {
+ 			struct ieee80211s_hdr *meshdr =