bridge: add support for configuring extra tagged vlans on member devices
authorFelix Fietkau <nbd@nbd.name>
Fri, 23 Jun 2023 08:17:45 +0000 (10:17 +0200)
committerFelix Fietkau <nbd@nbd.name>
Fri, 23 Jun 2023 08:20:42 +0000 (10:20 +0200)
This can be used to allow trunking dynamically allocated VLANs into a specific
member port. In order to use this, add a device section for the member port with
the 'vlan' array option, which can contain vlan numbers or ranges.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
bridge.c
device.c
device.h
system-dummy.c
system-linux.c
system.h

index c8cfca418f83484849b670730e4e971138eb5503..0338f14905cb97e8aa9a7ecab1c4c96aaa6969ff 100644 (file)
--- a/bridge.c
+++ b/bridge.c
@@ -206,7 +206,7 @@ __bridge_set_member_vlan(struct bridge_member *bm, struct bridge_vlan *vlan,
        if (bm->pvid == vlan->vid)
                flags |= BRVLAN_F_PVID;
 
-       system_bridge_vlan(port->ifname, vlan->vid, add, flags);
+       system_bridge_vlan(port->ifname, vlan->vid, -1, add, flags);
 }
 
 static void
@@ -233,7 +233,7 @@ bridge_set_local_vlan(struct bridge_state *bst, struct bridge_vlan *vlan, bool a
        if (!vlan->local && add)
                return;
 
-       system_bridge_vlan(bst->dev.ifname, vlan->vid, add, BRVLAN_F_SELF);
+       system_bridge_vlan(bst->dev.ifname, vlan->vid, -1, add, BRVLAN_F_SELF);
 }
 
 static void
@@ -361,7 +361,7 @@ bridge_enable_interface(struct bridge_state *bst)
 
        if (bst->has_vlans) {
                /* delete default VLAN 1 */
-               system_bridge_vlan(bst->dev.ifname, 1, false, BRVLAN_F_SELF);
+               system_bridge_vlan(bst->dev.ifname, 1, -1, false, BRVLAN_F_SELF);
 
                bridge_set_local_vlans(bst, true);
        }
@@ -394,6 +394,17 @@ bridge_disable_interface(struct bridge_state *bst)
        bst->active = false;
 }
 
+static void
+bridge_member_add_extra_vlans(struct bridge_member *bm)
+{
+       struct device *dev = bm->dev.dev;
+       int i;
+
+       for (i = 0; i < dev->n_extra_vlan; i++)
+               system_bridge_vlan(dev->ifname, dev->extra_vlan[i].start,
+                                  dev->extra_vlan[i].end, true, 0);
+}
+
 static int
 bridge_enable_member(struct bridge_member *bm)
 {
@@ -435,8 +446,9 @@ bridge_enable_member(struct bridge_member *bm)
        bm->active = true;
        if (bst->has_vlans) {
                /* delete default VLAN 1 */
-               system_bridge_vlan(bm->dev.dev->ifname, 1, false, 0);
+               system_bridge_vlan(bm->dev.dev->ifname, 1, -1, false, 0);
 
+               bridge_member_add_extra_vlans(bm);
                vlist_for_each_element(&bst->dev.vlans, vlan, node)
                        bridge_set_member_vlan(bm, vlan, true);
        }
index 1e892191d0ec95203b65b601e16be25a2e5762d3..e98c77efe1eef7e4cb16fee3a73f0b9db421ce04 100644 (file)
--- a/device.c
+++ b/device.c
@@ -63,6 +63,7 @@ static const struct blobmsg_policy dev_attrs[__DEV_ATTR_MAX] = {
        [DEV_ATTR_AUTH] = { .name = "auth", .type = BLOBMSG_TYPE_BOOL },
        [DEV_ATTR_SPEED] = { .name = "speed", .type = BLOBMSG_TYPE_INT32 },
        [DEV_ATTR_DUPLEX] = { .name = "duplex", .type = BLOBMSG_TYPE_BOOL },
+       [DEV_ATTR_VLAN] = { .name = "vlan", .type = BLOBMSG_TYPE_ARRAY },
 };
 
 const struct uci_blob_param_list device_attr_list = {
@@ -283,6 +284,45 @@ device_merge_settings(struct device *dev, struct device_settings *n)
        n->flags = s->flags | os->flags | os->valid_flags;
 }
 
+static void
+device_add_extra_vlan(struct device *dev, const char *val)
+{
+       unsigned long cur_start, cur_end;
+       char *sep;
+
+       cur_start = strtoul(val, &sep, 0);
+       cur_end = cur_start;
+
+       if (*sep == '-')
+               cur_end = strtoul(sep + 1, &sep, 0);
+       if (*sep || cur_end < cur_start)
+               return;
+
+       dev->extra_vlan[dev->n_extra_vlan].start = cur_start;
+       dev->extra_vlan[dev->n_extra_vlan].end = cur_end;
+       dev->n_extra_vlan++;
+}
+
+static void
+device_set_extra_vlans(struct device *dev, struct blob_attr *data)
+{
+       struct blob_attr *cur;
+       int n_vlans;
+       size_t rem;
+
+       dev->n_extra_vlan = 0;
+       if (!data)
+               return;
+
+       n_vlans = blobmsg_check_array(data, BLOBMSG_TYPE_STRING);
+       if (n_vlans < 1)
+               return;
+
+       dev->extra_vlan = realloc(dev->extra_vlan, n_vlans * sizeof(*dev->extra_vlan));
+       blobmsg_for_each_attr(cur, data, rem)
+               device_add_extra_vlan(dev, blobmsg_get_string(cur));
+}
+
 void
 device_init_settings(struct device *dev, struct blob_attr **tb)
 {
@@ -463,7 +503,7 @@ device_init_settings(struct device *dev, struct blob_attr **tb)
                s->duplex = blobmsg_get_bool(cur);
                s->flags |= DEV_OPT_DUPLEX;
        }
-
+       device_set_extra_vlans(dev, tb[DEV_ATTR_VLAN]);
        device_set_disabled(dev, disabled);
 }
 
@@ -891,6 +931,7 @@ device_free(struct device *dev)
        __devlock++;
        free(dev->config);
        device_cleanup(dev);
+       free(dev->extra_vlan);
        dev->type->free(dev);
        __devlock--;
 }
index 6751628b9b330269f0af06a3678353e8192c3529..c3e38ac77d2d8e8093a721149138901b4bbe6f96 100644 (file)
--- a/device.h
+++ b/device.h
@@ -62,6 +62,7 @@ enum {
        DEV_ATTR_AUTH,
        DEV_ATTR_SPEED,
        DEV_ATTR_DUPLEX,
+       DEV_ATTR_VLAN,
        __DEV_ATTR_MAX,
 };
 
@@ -248,6 +249,10 @@ struct device {
        bool bpdu_filter;
 
        struct interface *config_iface;
+       struct {
+               uint16_t start, end;
+       } *extra_vlan;
+       int n_extra_vlan;
 
        /* set interface up or down */
        device_state_cb set_state;
index b13bc876d509c13ba683634cbba67c1c4b07fa39..84382b3a9ad9d0f9ebb5883a1ecf0b3b24c9d4a4 100644 (file)
@@ -55,12 +55,12 @@ int system_bridge_delif(struct device *bridge, struct device *dev)
        return 0;
 }
 
-int system_bridge_vlan(const char *iface, uint16_t vid, bool add, unsigned int vflags)
+int system_bridge_vlan(const char *iface, uint16_t vid, int16_t vid_end, bool add, unsigned int vflags)
 {
-       D(SYSTEM, "brctl vlan %s %s %s vid=%d pvid=%d untag=%d\n",
+       D(SYSTEM, "brctl vlan %s %s %s vid=%d vid_end=%d pvid=%d untag=%d\n",
          add ? "add" : "remove",
          (vflags & BRVLAN_F_SELF) ? "self" : "master",
-         iface, vid,
+         iface, vid, vid_end,
          !!(vflags & BRVLAN_F_PVID),
          !!(vflags & BRVLAN_F_UNTAGGED));
        return 0;
index cc15537ba3ad6cd4b9e96f5a389c36dfef1a4018..0760e73f1cf3d9b9a3975b7c28f91e22ae52ac12 100644 (file)
@@ -985,7 +985,7 @@ int system_bridge_delif(struct device *bridge, struct device *dev)
        return system_bridge_if(bridge->ifname, dev, SIOCBRDELIF, NULL);
 }
 
-int system_bridge_vlan(const char *iface, uint16_t vid, bool add, unsigned int vflags)
+int system_bridge_vlan(const char *iface, uint16_t vid, int16_t vid_end, bool add, unsigned int vflags)
 {
        struct bridge_vlan_info vinfo = { .vid = vid, };
        unsigned short flags = 0;
@@ -1020,7 +1020,18 @@ int system_bridge_vlan(const char *iface, uint16_t vid, bool add, unsigned int v
        if (flags)
                nla_put_u16(nlm, IFLA_BRIDGE_FLAGS, flags);
 
+       if (vid_end > vid)
+               vinfo.flags |= BRIDGE_VLAN_INFO_RANGE_BEGIN;
+
        nla_put(nlm, IFLA_BRIDGE_VLAN_INFO, sizeof(vinfo), &vinfo);
+
+       if (vid_end > vid) {
+               vinfo.flags &= ~BRIDGE_VLAN_INFO_RANGE_BEGIN;
+               vinfo.flags |= BRIDGE_VLAN_INFO_RANGE_END;
+               vinfo.vid = vid_end;
+               nla_put(nlm, IFLA_BRIDGE_VLAN_INFO, sizeof(vinfo), &vinfo);
+       }
+
        nla_nest_end(nlm, afspec);
 
        return system_rtnl_call(nlm);
index 1f7037d8a63a0a6ccb70764dcec7329e7d7d2d65..19aafa418fc92d55e6d401bf656730423f65eec6 100644 (file)
--- a/system.h
+++ b/system.h
@@ -246,7 +246,7 @@ int system_bridge_addbr(struct device *bridge, struct bridge_config *cfg);
 int system_bridge_delbr(struct device *bridge);
 int system_bridge_addif(struct device *bridge, struct device *dev);
 int system_bridge_delif(struct device *bridge, struct device *dev);
-int system_bridge_vlan(const char *iface, uint16_t vid, bool add, unsigned int vflags);
+int system_bridge_vlan(const char *iface, uint16_t vid, int16_t vid_end, bool add, unsigned int vflags);
 int system_bridge_vlan_check(struct device *dev, char *ifname);
 void system_bridge_set_stp_state(struct device *dev, bool val);