bridge: add support for VLAN filtering
[project/netifd.git] / bridge.c
index e4ec59744869b027288975ad2661d8bf3a048428..c96dcc7bd0ab2c02fbfb2c19a527aef4ed501127 100644 (file)
--- a/bridge.c
+++ b/bridge.c
@@ -117,6 +117,7 @@ struct bridge_member {
        struct vlist_node node;
        struct bridge_state *bst;
        struct device_user dev;
+       uint16_t pvid;
        bool present;
        char name[];
 };
@@ -149,14 +150,150 @@ 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)
+{
+       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];
+       }
+
+       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
+brigde_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)
+               brigde_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;
+
+       brigde_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);
 
@@ -177,6 +314,13 @@ bridge_enable_interface(struct bridge_state *bst)
        if (ret < 0)
                return ret;
 
+       if (bst->config.vlan_filtering) {
+               /* 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;
 }
@@ -195,6 +339,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)
@@ -220,6 +365,14 @@ bridge_enable_member(struct bridge_member *bm)
                goto error;
        }
 
+       if (bst->config.vlan_filtering) {
+               /* 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);
 
@@ -542,8 +695,9 @@ 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);
 
@@ -559,6 +713,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);
 }
@@ -716,6 +875,59 @@ 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_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->config.vlan_filtering || !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))
+               goto out;
+
+       if (node_old)
+               bridge_set_vlan_state(bst, vlan_old, false);
+
+       if (node_new)
+               bridge_set_vlan_state(bst, vlan_new, true);
+
+       bst->dev.config_pending = true;
+
+out:
+       free(vlan_old);
+}
+
 static struct device *
 bridge_create(const char *name, struct device_type *devtype,
        struct blob_attr *attr)
@@ -745,6 +957,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;