|
@@ -16,6 +16,7 @@
|
|
|
#include <linux/kernel.h>
|
|
|
#include <linux/slab.h>
|
|
|
#include <linux/skbuff.h>
|
|
|
+#include <linux/if_vlan.h>
|
|
|
#include <linux/etherdevice.h>
|
|
|
#include <linux/bitmap.h>
|
|
|
#include <linux/rcupdate.h>
|
|
@@ -3573,6 +3574,115 @@ void __ieee80211_subif_start_xmit(struct sk_buff *skb,
|
|
|
rcu_read_unlock();
|
|
|
}
|
|
|
|
|
|
+static int ieee80211_change_da(struct sk_buff *skb, struct sta_info *sta)
|
|
|
+{
|
|
|
+ struct ethhdr *eth;
|
|
|
+ int err;
|
|
|
+
|
|
|
+ err = skb_ensure_writable(skb, ETH_HLEN);
|
|
|
+ if (unlikely(err))
|
|
|
+ return err;
|
|
|
+
|
|
|
+ eth = (void *)skb->data;
|
|
|
+ ether_addr_copy(eth->h_dest, sta->sta.addr);
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static bool ieee80211_multicast_to_unicast(struct sk_buff *skb,
|
|
|
+ struct net_device *dev)
|
|
|
+{
|
|
|
+ struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
|
|
|
+ const struct ethhdr *eth = (void *)skb->data;
|
|
|
+ const struct vlan_ethhdr *ethvlan = (void *)skb->data;
|
|
|
+ __be16 ethertype;
|
|
|
+
|
|
|
+ if (likely(!is_multicast_ether_addr(eth->h_dest)))
|
|
|
+ return false;
|
|
|
+
|
|
|
+ switch (sdata->vif.type) {
|
|
|
+ case NL80211_IFTYPE_AP_VLAN:
|
|
|
+ if (sdata->u.vlan.sta)
|
|
|
+ return false;
|
|
|
+ if (sdata->wdev.use_4addr)
|
|
|
+ return false;
|
|
|
+ /* fall through */
|
|
|
+ case NL80211_IFTYPE_AP:
|
|
|
+ /* check runtime toggle for this bss */
|
|
|
+ if (!sdata->bss->multicast_to_unicast)
|
|
|
+ return false;
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* multicast to unicast conversion only for some payload */
|
|
|
+ ethertype = eth->h_proto;
|
|
|
+ if (ethertype == htons(ETH_P_8021Q) && skb->len >= VLAN_ETH_HLEN)
|
|
|
+ ethertype = ethvlan->h_vlan_encapsulated_proto;
|
|
|
+ switch (ethertype) {
|
|
|
+ case htons(ETH_P_ARP):
|
|
|
+ case htons(ETH_P_IP):
|
|
|
+ case htons(ETH_P_IPV6):
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+static void
|
|
|
+ieee80211_convert_to_unicast(struct sk_buff *skb, struct net_device *dev,
|
|
|
+ struct sk_buff_head *queue)
|
|
|
+{
|
|
|
+ struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
|
|
|
+ struct ieee80211_local *local = sdata->local;
|
|
|
+ const struct ethhdr *eth = (struct ethhdr *)skb->data;
|
|
|
+ struct sta_info *sta, *first = NULL;
|
|
|
+ struct sk_buff *cloned_skb;
|
|
|
+
|
|
|
+ rcu_read_lock();
|
|
|
+
|
|
|
+ list_for_each_entry_rcu(sta, &local->sta_list, list) {
|
|
|
+ if (sdata != sta->sdata)
|
|
|
+ /* AP-VLAN mismatch */
|
|
|
+ continue;
|
|
|
+ if (unlikely(ether_addr_equal(eth->h_source, sta->sta.addr)))
|
|
|
+ /* do not send back to source */
|
|
|
+ continue;
|
|
|
+ if (!first) {
|
|
|
+ first = sta;
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ cloned_skb = skb_clone(skb, GFP_ATOMIC);
|
|
|
+ if (!cloned_skb)
|
|
|
+ goto multicast;
|
|
|
+ if (unlikely(ieee80211_change_da(cloned_skb, sta))) {
|
|
|
+ dev_kfree_skb(cloned_skb);
|
|
|
+ goto multicast;
|
|
|
+ }
|
|
|
+ __skb_queue_tail(queue, cloned_skb);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (likely(first)) {
|
|
|
+ if (unlikely(ieee80211_change_da(skb, first)))
|
|
|
+ goto multicast;
|
|
|
+ __skb_queue_tail(queue, skb);
|
|
|
+ } else {
|
|
|
+ /* no STA connected, drop */
|
|
|
+ kfree_skb(skb);
|
|
|
+ skb = NULL;
|
|
|
+ }
|
|
|
+
|
|
|
+ goto out;
|
|
|
+multicast:
|
|
|
+ __skb_queue_purge(queue);
|
|
|
+ __skb_queue_tail(queue, skb);
|
|
|
+out:
|
|
|
+ rcu_read_unlock();
|
|
|
+}
|
|
|
+
|
|
|
/**
|
|
|
* ieee80211_subif_start_xmit - netif start_xmit function for 802.3 vifs
|
|
|
* @skb: packet to be sent
|
|
@@ -3583,7 +3693,17 @@ void __ieee80211_subif_start_xmit(struct sk_buff *skb,
|
|
|
netdev_tx_t ieee80211_subif_start_xmit(struct sk_buff *skb,
|
|
|
struct net_device *dev)
|
|
|
{
|
|
|
- __ieee80211_subif_start_xmit(skb, dev, 0);
|
|
|
+ if (unlikely(ieee80211_multicast_to_unicast(skb, dev))) {
|
|
|
+ struct sk_buff_head queue;
|
|
|
+
|
|
|
+ __skb_queue_head_init(&queue);
|
|
|
+ ieee80211_convert_to_unicast(skb, dev, &queue);
|
|
|
+ while ((skb = __skb_dequeue(&queue)))
|
|
|
+ __ieee80211_subif_start_xmit(skb, dev, 0);
|
|
|
+ } else {
|
|
|
+ __ieee80211_subif_start_xmit(skb, dev, 0);
|
|
|
+ }
|
|
|
+
|
|
|
return NETDEV_TX_OK;
|
|
|
}
|
|
|
|