bridge: Fix multicast_to_unicast feature by hairpin+isolate
authorLinus Lüssing <linus.luessing@c0d3.blue>
Sun, 23 Aug 2015 15:19:26 +0000 (17:19 +0200)
committerFelix Fietkau <nbd@openwrt.org>
Tue, 25 Aug 2015 06:46:17 +0000 (08:46 +0200)
All IGMP and MLD versions suffer from a specific limitation (from a
snooping switch perspective): Report suppression.

Once a listener hears an IGMPv2/3 or MLDv1 report for the same group
itself participates in then it might (if this listener is an IGMPv3 or
MLDv2 listener) or will (if this is an IGMPv1/2 or MLDv1 listener)
refrain from sending its own report.

Therefore we might currently miss such surpressing listeners as they
won't receive the multicast packet with the mangled, unicasted
destination.

Fixing this by first isolating the STAs and giving the bridge more
control over traffic forwarding. E.g. refraining to forward listener
reports to other STAs.

For broadcast and unicast traffic to an STA on the same AP, the hairpin
feature of the bridge will reflect such traffic back to the AP
interface. However, if the AP interface is actually configured to
isolate STAs, then hairpin is kept disabled.

Signed-off-by: Linus Lüssing <linus.luessing@c0d3.blue>
device.h
scripts/netifd-wireless.sh
system-linux.c
wireless.c
wireless.h

index 798e19e834a446c09d57a25e742c2729b4a08f9d..77ebe842410af9506217518f3ad747b7f61515d3 100644 (file)
--- a/device.h
+++ b/device.h
@@ -176,6 +176,7 @@ struct device {
        bool iface_config;
        bool default_config;
        bool wireless;
+       bool wireless_isolate;
 
        struct interface *config_iface;
 
index c5d8a96c4a52cb9479d06e830be3733f0287831c..f981f1b2803aee1f55b4dba947b2ea34a5e2aaa1 100644 (file)
@@ -248,12 +248,26 @@ wireless_vif_parse_encryption() {
        esac
 }
 
+_wireless_set_brsnoop_isolation() {
+       local multicast_to_unicast="$1"
+       local isolate
+
+       json_get_var isolate isolate
+
+       [ $isolate -gt 0 -o -z "$network_bridge" ] && return
+
+       [ -z "$multicast_to_unicast" ] && multicast_to_unicast=1
+       [ $multicast_to_unicast -gt 0 ] && json_add_boolean isolate 1
+}
+
 for_each_interface() {
        local _w_types="$1"; shift
        local _w_ifaces _w_iface
        local _w_type
        local _w_found
 
+       local multicast_to_unicast
+
        json_get_keys _w_ifaces interfaces
        json_select interfaces
        for _w_iface in $_w_ifaces; do
@@ -261,6 +275,7 @@ for_each_interface() {
                if [ -n "$_w_types" ]; then
                        json_get_var network_bridge bridge
                        json_select config
+                       _wireless_set_brsnoop_isolation "$multicast_to_unicast"
                        json_get_var _w_type mode
                        json_select ..
                        _w_types=" $_w_types "
index 9ba13fd70d34222ce30515620c6a0a543726f1ac..9c4bea63b9b51b8d7cef03165c413c0d0a10db10 100644 (file)
@@ -315,6 +315,16 @@ static void system_set_dadtransmits(struct device *dev, const char *val)
        system_set_dev_sysctl("/proc/sys/net/ipv6/conf/%s/dad_transmits", dev->ifname, val);
 }
 
+static void system_bridge_set_multicast_to_unicast(struct device *dev, const char *val)
+{
+       system_set_dev_sysctl("/sys/class/net/%s/brport/multicast_to_unicast", dev->ifname, val);
+}
+
+static void system_bridge_set_hairpin_mode(struct device *dev, const char *val)
+{
+       system_set_dev_sysctl("/sys/class/net/%s/brport/hairpin_mode", dev->ifname, val);
+}
+
 static int system_get_sysctl(const char *path, char *buf, const size_t buf_sz)
 {
        int fd = -1, ret = -1;
@@ -556,12 +566,16 @@ static char *system_get_bridge(const char *name, char *buf, int buflen)
        return path + 1;
 }
 
-static void system_bridge_set_wireless(const char *bridge, const char *dev)
+static void
+system_bridge_set_wireless(struct device *dev)
 {
-       snprintf(dev_buf, sizeof(dev_buf),
-                "/sys/devices/virtual/net/%s/brif/%s/multicast_to_unicast",
-                bridge, dev);
-       system_set_sysctl(dev_buf, "1");
+       bool hairpin = true;
+
+       if (dev->wireless_isolate)
+               hairpin = false;
+
+       system_bridge_set_multicast_to_unicast(dev, "1");
+       system_bridge_set_hairpin_mode(dev, hairpin ? "1" : "0");
 }
 
 int system_bridge_addif(struct device *bridge, struct device *dev)
@@ -574,7 +588,7 @@ int system_bridge_addif(struct device *bridge, struct device *dev)
                ret = system_bridge_if(bridge->ifname, dev, SIOCBRADDIF, NULL);
 
        if (dev->wireless)
-               system_bridge_set_wireless(bridge->ifname, dev->ifname);
+               system_bridge_set_wireless(dev);
 
        return ret;
 }
index fbd6191617ab59937ed306f05cf64408c18eb024..337f56306c8a20606268678e6271239f76a46782 100644 (file)
@@ -35,12 +35,14 @@ static const struct uci_blob_param_list wdev_param = {
 enum {
        VIF_ATTR_DISABLED,
        VIF_ATTR_NETWORK,
+       VIF_ATTR_ISOLATE,
        __VIF_ATTR_MAX,
 };
 
 static const struct blobmsg_policy vif_policy[__VIF_ATTR_MAX] = {
        [VIF_ATTR_DISABLED] = { .name = "disabled", .type = BLOBMSG_TYPE_BOOL },
        [VIF_ATTR_NETWORK] = { .name = "network", .type = BLOBMSG_TYPE_ARRAY },
+       [VIF_ATTR_ISOLATE] = { .name = "isolate", .type = BLOBMSG_TYPE_BOOL },
 };
 
 static const struct uci_blob_param_list vif_param = {
@@ -204,8 +206,10 @@ static void wireless_interface_handle_link(struct wireless_interface *vif, bool
 
        if (up) {
                struct device *dev = device_get(vif->ifname, 2);
-               if (dev)
+               if (dev) {
+                       dev->wireless_isolate = vif->isolate;
                        dev->wireless = true;
+               }
        }
 
        blobmsg_for_each_attr(cur, vif->network, rem) {
@@ -700,6 +704,12 @@ void wireless_interface_create(struct wireless_device *wdev, struct blob_attr *d
        vif->wdev = wdev;
        vif->config = data;
        vif->section = section;
+       vif->isolate = false;
+
+       cur = tb[VIF_ATTR_ISOLATE];
+       if (cur && blobmsg_get_bool(cur))
+               vif->isolate = blobmsg_get_bool(cur);
+
        vlist_add(&wdev->interfaces, &vif->node, vif->name);
 }
 
index c5dbb88e39ac2b545357c9741f628a5c23c21d96..476c63e877c5bead0e41f548bc2187d873c52a66 100644 (file)
@@ -77,6 +77,7 @@ struct wireless_interface {
 
        const char *ifname;
        struct blob_attr *network;
+       bool isolate;
 };
 
 struct wireless_process {