netifd: add possibility to switch off route config
[project/netifd.git] / bridge.c
index ba1ce5d25e59e3b29ffe2c7c437254feccf5552e..099dfe4d24ef957422e89d5ee333b2aa929363f9 100644 (file)
--- a/bridge.c
+++ b/bridge.c
@@ -38,6 +38,8 @@ enum {
        BRIDGE_ATTR_QUERY_INTERVAL,
        BRIDGE_ATTR_QUERY_RESPONSE_INTERVAL,
        BRIDGE_ATTR_LAST_MEMBER_INTERVAL,
+       BRIDGE_ATTR_VLAN_FILTERING,
+       BRIDGE_ATTR_HAS_VLANS,
        __BRIDGE_ATTR_MAX
 };
 
@@ -57,6 +59,8 @@ static const struct blobmsg_policy bridge_attrs[__BRIDGE_ATTR_MAX] = {
        [BRIDGE_ATTR_QUERY_INTERVAL] = { "query_interval", BLOBMSG_TYPE_INT32 },
        [BRIDGE_ATTR_QUERY_RESPONSE_INTERVAL] = { "query_response_interval", BLOBMSG_TYPE_INT32 },
        [BRIDGE_ATTR_LAST_MEMBER_INTERVAL] = { "last_member_interval", BLOBMSG_TYPE_INT32 },
+       [BRIDGE_ATTR_VLAN_FILTERING] = { "vlan_filtering", BLOBMSG_TYPE_BOOL },
+       [BRIDGE_ATTR_HAS_VLANS] = { "__has_vlans", BLOBMSG_TYPE_BOOL }, /* internal */
 };
 
 static const struct uci_blob_param_info bridge_attr_info[__BRIDGE_ATTR_MAX] = {
@@ -77,7 +81,7 @@ static struct device *bridge_create(const char *name, struct device_type *devtyp
 static void bridge_config_init(struct device *dev);
 static void bridge_free(struct device *dev);
 static void bridge_dump_info(struct device *dev, struct blob_buf *b);
-enum dev_change_type
+static enum dev_change_type
 bridge_reload(struct device *dev, struct blob_attr *attr);
 
 static struct device_type bridge_device_type = {
@@ -103,6 +107,7 @@ struct bridge_state {
        struct blob_attr *ifnames;
        bool active;
        bool force_active;
+       bool has_vlans;
 
        struct uloop_timeout retry;
        struct bridge_member *primary_port;
@@ -115,10 +120,16 @@ struct bridge_member {
        struct vlist_node node;
        struct bridge_state *bst;
        struct device_user dev;
+       uint16_t pvid;
        bool present;
        char name[];
 };
 
+struct bridge_vlan_hotplug_port {
+       struct list_head list;
+       struct bridge_vlan_port port;
+};
+
 static void
 bridge_reset_primary(struct bridge_state *bst)
 {
@@ -147,14 +158,158 @@ bridge_reset_primary(struct bridge_state *bst)
        }
 }
 
+static struct bridge_vlan_port *
+bridge_find_vlan_member_port(struct bridge_member *bm, struct bridge_vlan *vlan)
+{
+       struct bridge_vlan_hotplug_port *port;
+       const char *ifname = bm->dev.dev->ifname;
+       int i;
+
+       for (i = 0; i < vlan->n_ports; i++) {
+               if (strcmp(vlan->ports[i].ifname, ifname) != 0)
+                       continue;
+
+               return &vlan->ports[i];
+       }
+
+       list_for_each_entry(port, &vlan->hotplug_ports, list) {
+               if (strcmp(port->port.ifname, ifname) != 0)
+                       continue;
+
+               return &port->port;
+       }
+
+       return NULL;
+}
+
+static bool
+bridge_member_vlan_is_pvid(struct bridge_member *bm, struct bridge_vlan_port *port)
+{
+       return (!bm->pvid && (port->flags & BRVLAN_F_UNTAGGED)) ||
+              (port->flags & BRVLAN_F_PVID);
+}
+
+static void
+__bridge_set_member_vlan(struct bridge_member *bm, struct bridge_vlan *vlan,
+                        struct bridge_vlan_port *port, bool add)
+{
+       uint16_t flags;
+
+       flags = port->flags;
+       if (bm->pvid == vlan->vid)
+               flags |= BRVLAN_F_PVID;
+
+       system_bridge_vlan(port->ifname, vlan->vid, add, flags);
+}
+
+static void
+bridge_set_member_vlan(struct bridge_member *bm, struct bridge_vlan *vlan, bool add)
+{
+       struct bridge_vlan_port *port;
+
+       if (!bm->present)
+               return;
+
+       port = bridge_find_vlan_member_port(bm, vlan);
+       if (!port)
+               return;
+
+       if (bridge_member_vlan_is_pvid(bm, port))
+               bm->pvid = vlan->vid;
+
+       __bridge_set_member_vlan(bm, vlan, port, add);
+}
+
+static void
+bridge_set_local_vlan(struct bridge_state *bst, struct bridge_vlan *vlan, bool add)
+{
+       if (!vlan->local && add)
+               return;
+
+       system_bridge_vlan(bst->dev.ifname, vlan->vid, add, BRVLAN_F_SELF);
+}
+
+static void
+bridge_set_local_vlans(struct bridge_state *bst, bool add)
+{
+       struct bridge_vlan *vlan;
+
+       vlist_for_each_element(&bst->dev.vlans, vlan, node)
+               bridge_set_local_vlan(bst, vlan, add);
+}
+
+static struct bridge_vlan *
+bridge_recalc_member_pvid(struct bridge_member *bm)
+{
+       struct bridge_state *bst = bm->bst;
+       struct bridge_vlan_port *port;
+       struct bridge_vlan *vlan, *ret = NULL;
+
+       vlist_for_each_element(&bst->dev.vlans, vlan, node) {
+               port = bridge_find_vlan_member_port(bm, vlan);
+               if (!port)
+                       continue;
+
+               if (!bridge_member_vlan_is_pvid(bm, port))
+                       continue;
+
+               ret = vlan;
+               if (port->flags & BRVLAN_F_PVID)
+                       break;
+       }
+
+       return ret;
+}
+
+static void
+bridge_set_vlan_state(struct bridge_state *bst, struct bridge_vlan *vlan, bool add)
+{
+       struct bridge_member *bm;
+       struct bridge_vlan *vlan2;
+
+       bridge_set_local_vlan(bst, vlan, add);
+
+       vlist_for_each_element(&bst->members, bm, node) {
+               struct bridge_vlan_port *port;
+               int new_pvid = -1;
+
+               port = bridge_find_vlan_member_port(bm, vlan);
+               if (!port)
+                       continue;
+
+               if (add) {
+                       if (bridge_member_vlan_is_pvid(bm, port))
+                               bm->pvid = vlan->vid;
+               } else if (bm->pvid == vlan->vid) {
+                       vlan2 = bridge_recalc_member_pvid(bm);
+                       if (vlan2 && vlan2->vid != vlan->vid) {
+                               bridge_set_member_vlan(bm, vlan2, false);
+                               bridge_set_member_vlan(bm, vlan2, true);
+                       }
+                       new_pvid = vlan2 ? vlan2->vid : 0;
+               }
+
+               if (!bm->present)
+                       continue;
+
+               __bridge_set_member_vlan(bm, vlan, port, add);
+               if (new_pvid >= 0)
+                       bm->pvid = new_pvid;
+       }
+}
+
 static int
 bridge_disable_member(struct bridge_member *bm)
 {
        struct bridge_state *bst = bm->bst;
+       struct bridge_vlan *vlan;
 
        if (!bm->present)
                return 0;
 
+       vlist_for_each_element(&bst->dev.vlans, vlan, node)
+               bridge_set_member_vlan(bm, vlan, false);
+
        system_bridge_delif(&bst->dev, bm->dev.dev);
        device_release(&bm->dev);
 
@@ -175,6 +330,13 @@ bridge_enable_interface(struct bridge_state *bst)
        if (ret < 0)
                return ret;
 
+       if (bst->has_vlans) {
+               /* delete default VLAN 1 */
+               system_bridge_vlan(bst->dev.ifname, 1, false, BRVLAN_F_SELF);
+
+               bridge_set_local_vlans(bst, true);
+       }
+
        bst->active = true;
        return 0;
 }
@@ -193,6 +355,7 @@ static int
 bridge_enable_member(struct bridge_member *bm)
 {
        struct bridge_state *bst = bm->bst;
+       struct bridge_vlan *vlan;
        int ret;
 
        if (!bm->present)
@@ -218,6 +381,14 @@ bridge_enable_member(struct bridge_member *bm)
                goto error;
        }
 
+       if (bst->has_vlans) {
+               /* delete default VLAN 1 */
+               system_bridge_vlan(bm->dev.dev->ifname, 1, false, 0);
+
+               vlist_for_each_element(&bst->dev.vlans, vlan, node)
+                       bridge_set_member_vlan(bm, vlan, true);
+       }
+
        device_set_present(&bst->dev, true);
        device_broadcast_event(&bst->dev, DEV_EVENT_TOPO_CHANGE);
 
@@ -240,15 +411,15 @@ bridge_remove_member(struct bridge_member *bm)
        if (!bm->present)
                return;
 
-       if (bm == bst->primary_port)
-               bridge_reset_primary(bst);
-
        if (bst->dev.active)
                bridge_disable_member(bm);
 
        bm->present = false;
        bm->bst->n_present--;
 
+       if (bm == bst->primary_port)
+               bridge_reset_primary(bst);
+
        if (bst->config.bridge_empty)
                return;
 
@@ -260,9 +431,27 @@ bridge_remove_member(struct bridge_member *bm)
 static void
 bridge_free_member(struct bridge_member *bm)
 {
+       struct bridge_state *bst = bm->bst;
        struct device *dev = bm->dev.dev;
+       const char *ifname = dev->ifname;
+       struct bridge_vlan *vlan;
 
        bridge_remove_member(bm);
+
+       vlist_for_each_element(&bst->dev.vlans, vlan, node) {
+               struct bridge_vlan_hotplug_port *port, *tmp;
+
+               list_for_each_entry_safe(port, tmp, &vlan->hotplug_ports, list) {
+                       if (strcmp(port->port.ifname, ifname) != 0)
+                               continue;
+
+                       list_del(&port->list);
+                       free(port);
+               }
+       }
+
+       device_lock();
+
        device_remove_user(&bm->dev);
 
        /*
@@ -277,6 +466,8 @@ bridge_free_member(struct bridge_member *bm)
                device_set_present(dev, true);
        }
 
+       device_unlock();
+
        free(bm);
 }
 
@@ -351,6 +542,7 @@ bridge_set_up(struct bridge_state *bst)
        struct bridge_member *bm;
        int ret;
 
+       bst->has_vlans = !avl_is_empty(&bst->dev.vlans.avl);
        if (!bst->n_present) {
                if (!bst->force_active)
                        return -ENOENT;
@@ -409,9 +601,11 @@ bridge_create_member(struct bridge_state *bst, const char *name,
        strcpy(bm->name, name);
        bm->dev.dev = dev;
        vlist_add(&bst->members, &bm->node, bm->name);
-       // Need to look up the bridge member again as the above
-       // created pointer will be freed in case the bridge member
-       // already existed
+       /*
+        * Need to look up the bridge member again as the above
+        * created pointer will be freed in case the bridge member
+        * already existed
+        */
        bm = vlist_find(&bst->members, name, bm, node);
        if (hotplug && bm)
                bm->node.version = -1;
@@ -459,11 +653,66 @@ bridge_add_member(struct bridge_state *bst, const char *name)
        bridge_create_member(bst, name, dev, false);
 }
 
+static void
+bridge_hotplug_create_member_vlans(struct bridge_state *bst, struct blob_attr *vlans, const char *ifname)
+{
+       struct bridge_vlan *vlan;
+       struct blob_attr *cur;
+       int rem;
+
+       if (!vlans)
+               return;
+
+       blobmsg_for_each_attr(cur, vlans, rem) {
+               struct bridge_vlan_hotplug_port *port;
+               uint16_t flags = BRVLAN_F_UNTAGGED;
+               char *name_buf;
+               unsigned int vid;
+               char *end;
+
+               if (blobmsg_type(cur) != BLOBMSG_TYPE_STRING)
+                       continue;
+
+               vid = strtoul(blobmsg_get_string(cur), &end, 0);
+               if (!vid || vid > 4095)
+                       continue;
+
+               vlan = vlist_find(&bst->dev.vlans, &vid, vlan, node);
+               if (!vlan)
+                       continue;
+
+               if (end && *end) {
+                       if (*end != ':')
+                               continue;
+
+                       for (end++; *end; end++) {
+                               switch (*end) {
+                               case 't':
+                                       flags &= ~BRVLAN_F_UNTAGGED;
+                                       break;
+                               case '*':
+                                       flags |= BRVLAN_F_PVID;
+                                       break;
+                               }
+                       }
+               }
+
+               port = calloc_a(sizeof(*port), &name_buf, strlen(ifname) + 1);
+               if (!port)
+                       continue;
+
+               port->port.flags = flags;
+               port->port.ifname = strcpy(name_buf, ifname);
+               list_add_tail(&port->list, &vlan->hotplug_ports);
+       }
+}
+
 static int
-bridge_hotplug_add(struct device *dev, struct device *member)
+bridge_hotplug_add(struct device *dev, struct device *member, struct blob_attr *vlan)
 {
        struct bridge_state *bst = container_of(dev, struct bridge_state, dev);
 
+       bridge_hotplug_create_member_vlans(bst, vlan, member->ifname);
        bridge_create_member(bst, member->ifname, member, true);
 
        return 0;
@@ -484,10 +733,13 @@ bridge_hotplug_del(struct device *dev, struct device *member)
 }
 
 static int
-bridge_hotplug_prepare(struct device *dev)
+bridge_hotplug_prepare(struct device *dev, struct device **bridge_dev)
 {
        struct bridge_state *bst;
 
+       if (bridge_dev)
+               *bridge_dev = dev;
+
        bst = container_of(dev, struct bridge_state, dev);
        bst->force_active = true;
        device_set_present(&bst->dev, true);
@@ -508,15 +760,55 @@ bridge_free(struct device *dev)
 
        bst = container_of(dev, struct bridge_state, dev);
        vlist_flush_all(&bst->members);
+       vlist_flush_all(&dev->vlans);
+       kvlist_free(&dev->vlan_aliases);
        free(bst->config_data);
        free(bst);
 }
 
+static void
+bridge_dump_port(struct blob_buf *b, struct bridge_vlan_port *port)
+{
+       bool tagged = !(port->flags & BRVLAN_F_UNTAGGED);
+       bool pvid = (port->flags & BRVLAN_F_PVID);
+
+       blobmsg_printf(b, "%s%s%s%s\n", port->ifname,
+               tagged || pvid ? ":" : "",
+               tagged ? "t" : "",
+               pvid ? "*" : "");
+}
+
+static void
+bridge_dump_vlan(struct blob_buf *b, struct bridge_vlan *vlan)
+{
+       struct bridge_vlan_hotplug_port *port;
+       void *c, *p;
+       int i;
+
+       c = blobmsg_open_table(b, NULL);
+
+       blobmsg_add_u32(b, "id", vlan->vid);
+       blobmsg_add_u8(b, "local", vlan->local);
+
+       p = blobmsg_open_array(b, "ports");
+
+       for (i = 0; i < vlan->n_ports; i++)
+           bridge_dump_port(b, &vlan->ports[i]);
+
+       list_for_each_entry(port, &vlan->hotplug_ports, list)
+               bridge_dump_port(b, &port->port);
+
+       blobmsg_close_array(b, p);
+
+       blobmsg_close_table(b, c);
+}
+
 static void
 bridge_dump_info(struct device *dev, struct blob_buf *b)
 {
        struct bridge_state *bst;
        struct bridge_member *bm;
+       struct bridge_vlan *vlan;
        void *list;
 
        bst = container_of(dev, struct bridge_state, dev);
@@ -532,14 +824,25 @@ bridge_dump_info(struct device *dev, struct blob_buf *b)
        }
 
        blobmsg_close_array(b, list);
+
+       if (avl_is_empty(&dev->vlans.avl))
+               return;
+
+       list = blobmsg_open_array(b, "bridge-vlans");
+
+       vlist_for_each_element(&bst->dev.vlans, vlan, node)
+               bridge_dump_vlan(b, vlan);
+
+       blobmsg_close_array(b, list);
 }
 
 static void
 bridge_config_init(struct device *dev)
 {
        struct bridge_state *bst;
+       struct bridge_vlan *vlan;
        struct blob_attr *cur;
-       int rem;
+       int i, rem;
 
        bst = container_of(dev, struct bridge_state, dev);
 
@@ -555,6 +858,11 @@ bridge_config_init(struct device *dev)
                        bridge_add_member(bst, blobmsg_data(cur));
                }
        }
+
+       vlist_for_each_element(&bst->dev.vlans, vlan, node)
+               for (i = 0; i < vlan->n_ports; i++)
+                       bridge_add_member(bst, vlan->ports[i].ifname);
+
        vlist_flush(&bst->members);
        bridge_check_retry(bst);
 }
@@ -568,15 +876,16 @@ bridge_apply_settings(struct bridge_state *bst, struct blob_attr **tb)
        /* defaults */
        cfg->stp = false;
        cfg->forward_delay = 2;
-       cfg->igmp_snoop = true;
-       cfg->multicast_querier = true;
        cfg->robustness = 2;
+       cfg->igmp_snoop = false;
+       cfg->multicast_querier = false;
        cfg->query_interval = 12500;
        cfg->query_response_interval = 1000;
        cfg->last_member_interval = 100;
        cfg->hash_max = 512;
        cfg->bridge_empty = false;
        cfg->priority = 0x7FFF;
+       cfg->vlan_filtering = false;
 
        if ((cur = tb[BRIDGE_ATTR_STP]))
                cfg->stp = blobmsg_get_bool(cur);
@@ -633,9 +942,12 @@ bridge_apply_settings(struct bridge_state *bst, struct blob_attr **tb)
 
        if ((cur = tb[BRIDGE_ATTR_BRIDGE_EMPTY]))
                cfg->bridge_empty = blobmsg_get_bool(cur);
+
+       if ((cur = tb[BRIDGE_ATTR_VLAN_FILTERING]))
+               cfg->vlan_filtering = blobmsg_get_bool(cur);
 }
 
-enum dev_change_type
+static enum dev_change_type
 bridge_reload(struct device *dev, struct blob_attr *attr)
 {
        struct blob_attr *tb_dev[__DEV_ATTR_MAX];
@@ -710,6 +1022,78 @@ bridge_retry_members(struct uloop_timeout *timeout)
        }
 }
 
+static int bridge_avl_cmp_u16(const void *k1, const void *k2, void *ptr)
+{
+       const uint16_t *i1 = k1, *i2 = k2;
+
+       return *i1 - *i2;
+}
+
+static bool
+bridge_vlan_equal(struct bridge_vlan *v1, struct bridge_vlan *v2)
+{
+       int i;
+
+       if (v1->n_ports != v2->n_ports)
+               return false;
+
+       for (i = 0; i < v1->n_ports; i++)
+               if (v1->ports[i].flags != v2->ports[i].flags ||
+                   strcmp(v1->ports[i].ifname, v2->ports[i].ifname) != 0)
+                       return false;
+
+       return true;
+}
+
+static void
+bridge_vlan_free(struct bridge_vlan *vlan)
+{
+       struct bridge_vlan_hotplug_port *port, *tmp;
+
+       if (!vlan)
+               return;
+
+       list_for_each_entry_safe(port, tmp, &vlan->hotplug_ports, list)
+               free(port);
+
+       free(vlan);
+}
+
+static void
+bridge_vlan_update(struct vlist_tree *tree, struct vlist_node *node_new,
+                  struct vlist_node *node_old)
+{
+       struct bridge_state *bst = container_of(tree, struct bridge_state, dev.vlans);
+       struct bridge_vlan *vlan_new = NULL, *vlan_old = NULL;
+
+       if (!bst->has_vlans || !bst->active)
+               goto out;
+
+       if (node_old)
+               vlan_old = container_of(node_old, struct bridge_vlan, node);
+       if (node_new)
+               vlan_new = container_of(node_new, struct bridge_vlan, node);
+
+       if (node_new && node_old && bridge_vlan_equal(vlan_old, vlan_new)) {
+               list_splice_init(&vlan_old->hotplug_ports, &vlan_new->hotplug_ports);
+               goto out;
+       }
+
+       if (node_old)
+               bridge_set_vlan_state(bst, vlan_old, false);
+
+       if (node_old && node_new)
+               list_splice_init(&vlan_old->hotplug_ports, &vlan_new->hotplug_ports);
+
+       if (node_new)
+               bridge_set_vlan_state(bst, vlan_new, true);
+
+       bst->dev.config_pending = true;
+
+out:
+       bridge_vlan_free(vlan_old);
+}
+
 static struct device *
 bridge_create(const char *name, struct device_type *devtype,
        struct blob_attr *attr)
@@ -722,7 +1106,13 @@ bridge_create(const char *name, struct device_type *devtype,
                return NULL;
 
        dev = &bst->dev;
-       device_init(dev, devtype, name);
+
+       if (device_init(dev, devtype, name) < 0) {
+               device_cleanup(dev);
+               free(bst);
+               return NULL;
+       }
+
        dev->config_pending = true;
        bst->retry.cb = bridge_retry_members;
 
@@ -733,6 +1123,9 @@ bridge_create(const char *name, struct device_type *devtype,
 
        vlist_init(&bst->members, avl_strcmp, bridge_member_update);
        bst->members.keep_old = true;
+
+       vlist_init(&dev->vlans, bridge_avl_cmp_u16, bridge_vlan_update);
+
        bridge_reload(dev, attr);
 
        return dev;