mac80211: multicast to unicast conversion
authorMichael Braun <michael-dev@fami-braun.de>
Tue, 22 Nov 2016 10:52:18 +0000 (11:52 +0100)
committerJohannes Berg <johannes.berg@intel.com>
Tue, 13 Dec 2016 15:05:11 +0000 (16:05 +0100)
Add the ability for an AP (and associated VLANs) to perform
multicast-to-unicast conversion for ARP, IPv4 and IPv6 frames
(possibly within 802.1Q). If enabled, such frames are to be sent
to each station separately, with the DA replaced by their own
MAC address rather than the group address.

Note that this may break certain expectations of the receiver,
such as the ability to drop unicast IP packets received within
multicast L2 frames, or the ability to not send ICMP destination
unreachable messages for packets received in L2 multicast (which
is required, but the receiver can't tell the difference if this
new option is enabled.)

This also doesn't implement the 802.11 DMS (directed multicast
service).

Signed-off-by: Michael Braun <michael-dev@fami-braun.de>
[use true/false, rename label to the correct "multicast",
 use __be16 for ethertype and network order for constants]
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
net/mac80211/cfg.c
net/mac80211/debugfs_netdev.c
net/mac80211/ieee80211_i.h
net/mac80211/tx.c

index e91e503bf99257d7e8f3945c1500eebaa91f5e81..a0be2f6cd1212ae3a35219d59b8f2bdf90c7d07e 100644 (file)
@@ -3563,6 +3563,17 @@ void ieee80211_nan_func_match(struct ieee80211_vif *vif,
 }
 EXPORT_SYMBOL(ieee80211_nan_func_match);
 
+static int ieee80211_set_multicast_to_unicast(struct wiphy *wiphy,
+                                             struct net_device *dev,
+                                             const bool enabled)
+{
+       struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
+
+       sdata->u.ap.multicast_to_unicast = enabled;
+
+       return 0;
+}
+
 const struct cfg80211_ops mac80211_config_ops = {
        .add_virtual_intf = ieee80211_add_iface,
        .del_virtual_intf = ieee80211_del_iface,
@@ -3653,4 +3664,5 @@ const struct cfg80211_ops mac80211_config_ops = {
        .nan_change_conf = ieee80211_nan_change_conf,
        .add_nan_func = ieee80211_add_nan_func,
        .del_nan_func = ieee80211_del_nan_func,
+       .set_multicast_to_unicast = ieee80211_set_multicast_to_unicast,
 };
index 1a05f85cb1f0610b41ea212df2624a9078ea16cd..8f5fff8b20409e6ea5ed011fb1770b71f225fa1d 100644 (file)
@@ -519,6 +519,8 @@ static ssize_t ieee80211_if_fmt_aqm(
 }
 IEEE80211_IF_FILE_R(aqm);
 
+IEEE80211_IF_FILE(multicast_to_unicast, u.ap.multicast_to_unicast, HEX);
+
 /* IBSS attributes */
 static ssize_t ieee80211_if_fmt_tsf(
        const struct ieee80211_sub_if_data *sdata, char *buf, int buflen)
@@ -683,6 +685,7 @@ static void add_ap_files(struct ieee80211_sub_if_data *sdata)
        DEBUGFS_ADD(dtim_count);
        DEBUGFS_ADD(num_buffered_multicast);
        DEBUGFS_ADD_MODE(tkip_mic_test, 0200);
+       DEBUGFS_ADD_MODE(multicast_to_unicast, 0600);
 }
 
 static void add_vlan_files(struct ieee80211_sub_if_data *sdata)
index d37a577f63a1809b3ef56bfa471d46241edd0de5..a3af14e3e64ddfcaf3d59191c3ad102c36f962bf 100644 (file)
@@ -297,6 +297,7 @@ struct ieee80211_if_ap {
                         driver_smps_mode; /* smps mode request */
 
        struct work_struct request_smps_work;
+       bool multicast_to_unicast;
 };
 
 struct ieee80211_if_wds {
index 2c21b7039136fe467845df5494432b58a6a226fc..a39c0f07021b4c6c53589fa19110ff2775494e99 100644 (file)
@@ -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;
 }