bridge: fix setting pvid for updated vlans
[project/netifd.git] / bridge.c
index 04a9abfb735f9d75aeeac160813166224522e6ee..f3e2fed6f8a05785fbb3a7790cdc9b8585890bdd 100644 (file)
--- a/bridge.c
+++ b/bridge.c
@@ -79,6 +79,7 @@ static const struct uci_blob_param_list bridge_attr_list = {
 static struct device *bridge_create(const char *name, struct device_type *devtype,
        struct blob_attr *attr);
 static void bridge_config_init(struct device *dev);
+static void bridge_dev_vlan_update(struct device *dev);
 static void bridge_free(struct device *dev);
 static void bridge_dump_info(struct device *dev, struct blob_buf *b);
 static enum dev_change_type
@@ -93,6 +94,7 @@ static struct device_type bridge_device_type = {
 
        .create = bridge_create,
        .config_init = bridge_config_init,
+       .vlan_update = bridge_dev_vlan_update,
        .reload = bridge_reload,
        .free = bridge_free,
        .dump_info = bridge_dump_info,
@@ -706,8 +708,21 @@ bridge_hotplug_get_vlan(struct bridge_state *bst, unsigned int vid)
        return vlan;
 }
 
+static struct bridge_vlan_hotplug_port *
+bridge_hotplug_get_vlan_port(struct bridge_vlan *vlan, const char *ifname)
+{
+       struct bridge_vlan_hotplug_port *port;
+
+       list_for_each_entry(port, &vlan->hotplug_ports, list)
+               if (!strcmp(port->port.ifname, ifname))
+                       return port;
+
+       return NULL;
+}
+
 static void
-bridge_hotplug_create_member_vlans(struct bridge_state *bst, struct blob_attr *vlans, const char *ifname)
+bridge_hotplug_set_member_vlans(struct bridge_state *bst, struct blob_attr *vlans,
+                               const char *ifname, struct bridge_member *bm, bool add)
 {
        struct bridge_vlan *vlan;
        struct blob_attr *cur;
@@ -750,6 +765,27 @@ bridge_hotplug_create_member_vlans(struct bridge_state *bst, struct blob_attr *v
                        }
                }
 
+               port = bridge_hotplug_get_vlan_port(vlan, ifname);
+               if (!add) {
+                       if (!port)
+                               continue;
+
+                       __bridge_set_member_vlan(bm, vlan, &port->port, false);
+                       list_del(&port->list);
+                       free(port);
+                       continue;
+               }
+
+               if (port) {
+                       if (port->port.flags == flags)
+                               continue;
+
+                       __bridge_set_member_vlan(bm, vlan, &port->port, false);
+                       port->port.flags = flags;
+                       __bridge_set_member_vlan(bm, vlan, &port->port, true);
+                       continue;
+               }
+
                port = calloc_a(sizeof(*port), &name_buf, strlen(ifname) + 1);
                if (!port)
                        continue;
@@ -757,6 +793,11 @@ bridge_hotplug_create_member_vlans(struct bridge_state *bst, struct blob_attr *v
                port->port.flags = flags;
                port->port.ifname = strcpy(name_buf, ifname);
                list_add_tail(&port->list, &vlan->hotplug_ports);
+
+               if (!bm)
+                       continue;
+
+               __bridge_set_member_vlan(bm, vlan, &port->port, true);
        }
 }
 
@@ -764,15 +805,18 @@ static int
 bridge_hotplug_add(struct device *dev, struct device *member, struct blob_attr *vlan)
 {
        struct bridge_state *bst = container_of(dev, struct bridge_state, dev);
+       struct bridge_member *bm;
 
-       bridge_hotplug_create_member_vlans(bst, vlan, member->ifname);
-       bridge_create_member(bst, member->ifname, member, true);
+       bm = vlist_find(&bst->members, member->ifname, bm, node);
+       bridge_hotplug_set_member_vlans(bst, vlan, member->ifname, bm, true);
+       if (!bm)
+               bridge_create_member(bst, member->ifname, member, true);
 
        return 0;
 }
 
 static int
-bridge_hotplug_del(struct device *dev, struct device *member)
+bridge_hotplug_del(struct device *dev, struct device *member, struct blob_attr *vlan)
 {
        struct bridge_state *bst = container_of(dev, struct bridge_state, dev);
        struct bridge_member *bm;
@@ -781,6 +825,10 @@ bridge_hotplug_del(struct device *dev, struct device *member)
        if (!bm)
                return UBUS_STATUS_NOT_FOUND;
 
+       bridge_hotplug_set_member_vlans(bst, vlan, member->ifname, bm, false);
+       if (!bm->dev.hotplug)
+               return 0;
+
        vlist_delete(&bst->members, &bm->node);
        return 0;
 }
@@ -1139,7 +1187,7 @@ bridge_vlan_update(struct vlist_tree *tree, struct vlist_node *node_new,
                list_splice_init(&vlan_old->hotplug_ports, &vlan_new->hotplug_ports);
 
        if (node_new)
-               bridge_set_vlan_state(bst, vlan_new, true);
+               vlan_new->pending = true;
 
        bst->dev.config_pending = true;
 
@@ -1147,6 +1195,21 @@ out:
        bridge_vlan_free(vlan_old);
 }
 
+static void
+bridge_dev_vlan_update(struct device *dev)
+{
+       struct bridge_state *bst = container_of(dev, struct bridge_state, dev);
+       struct bridge_vlan *vlan;
+
+       vlist_for_each_element(&dev->vlans, vlan, node) {
+               if (!vlan->pending)
+                       continue;
+
+               vlan->pending = false;
+               bridge_set_vlan_state(bst, vlan, true);
+       }
+}
+
 static struct device *
 bridge_create(const char *name, struct device_type *devtype,
        struct blob_attr *attr)