net: bridge: vlan: add support for global options
authorNikolay Aleksandrov <nikolay@nvidia.com>
Mon, 19 Jul 2021 17:06:34 +0000 (20:06 +0300)
committerDavid S. Miller <davem@davemloft.net>
Tue, 20 Jul 2021 12:41:20 +0000 (05:41 -0700)
We can have two types of vlan options depending on context:
 - per-device vlan options (split in per-bridge and per-port)
 - global vlan options

The second type wasn't supported in the bridge until now, but we need
them for per-vlan multicast support, per-vlan STP support and other
options which require global vlan context. They are contained in the global
bridge vlan context even if the vlan is not configured on the bridge device
itself. This patch adds initial netlink attributes and support for setting
these global vlan options, they can only be set (RTM_NEWVLAN) and the
operation must use the bridge device. Since there are no such options yet
it shouldn't have any functional effect.

Signed-off-by: Nikolay Aleksandrov <nikolay@nvidia.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
include/uapi/linux/if_bridge.h
net/bridge/br_private.h
net/bridge/br_vlan.c
net/bridge/br_vlan_options.c

index 90ac9e11c15bb97c0fc873e62255c1bbe6decadc..4ed57d1a5d890a161a0c7d2733dae3f252bdf588 100644 (file)
@@ -485,10 +485,15 @@ enum {
  *     [BRIDGE_VLANDB_ENTRY_INFO]
  *     ...
  * }
+ * [BRIDGE_VLANDB_GLOBAL_OPTIONS] = {
+ *     [BRIDGE_VLANDB_GOPTS_ID]
+ *     ...
+ * }
  */
 enum {
        BRIDGE_VLANDB_UNSPEC,
        BRIDGE_VLANDB_ENTRY,
+       BRIDGE_VLANDB_GLOBAL_OPTIONS,
        __BRIDGE_VLANDB_MAX,
 };
 #define BRIDGE_VLANDB_MAX (__BRIDGE_VLANDB_MAX - 1)
@@ -538,6 +543,14 @@ enum {
 };
 #define BRIDGE_VLANDB_STATS_MAX (__BRIDGE_VLANDB_STATS_MAX - 1)
 
+enum {
+       BRIDGE_VLANDB_GOPTS_UNSPEC,
+       BRIDGE_VLANDB_GOPTS_ID,
+       BRIDGE_VLANDB_GOPTS_RANGE,
+       __BRIDGE_VLANDB_GOPTS_MAX
+};
+#define BRIDGE_VLANDB_GOPTS_MAX (__BRIDGE_VLANDB_GOPTS_MAX - 1)
+
 /* Bridge multicast database attributes
  * [MDBA_MDB] = {
  *     [MDBA_MDB_ENTRY] = {
index a18c27f581a32b09c871954486f9d95eb8691287..6a6ce233a99977b74b21dfff25a9960a47ec6fa4 100644 (file)
@@ -1592,6 +1592,10 @@ int br_vlan_process_options(const struct net_bridge *br,
                            struct net_bridge_vlan *range_end,
                            struct nlattr **tb,
                            struct netlink_ext_ack *extack);
+int br_vlan_rtm_process_global_options(struct net_device *dev,
+                                      const struct nlattr *attr,
+                                      int cmd,
+                                      struct netlink_ext_ack *extack);
 
 /* vlan state manipulation helpers using *_ONCE to annotate lock-free access */
 static inline u8 br_vlan_get_state(const struct net_bridge_vlan *v)
index ab4969a4a3804c4b481abe308fbfb5e6a134e25c..dcb5acf783d22f5c0bdbb902f11a76efc38da0bc 100644 (file)
@@ -2203,12 +2203,22 @@ static int br_vlan_rtm_process(struct sk_buff *skb, struct nlmsghdr *nlh,
        }
 
        nlmsg_for_each_attr(attr, nlh, sizeof(*bvm), rem) {
-               if (nla_type(attr) != BRIDGE_VLANDB_ENTRY)
+               switch (nla_type(attr)) {
+               case BRIDGE_VLANDB_ENTRY:
+                       err = br_vlan_rtm_process_one(dev, attr,
+                                                     nlh->nlmsg_type,
+                                                     extack);
+                       break;
+               case BRIDGE_VLANDB_GLOBAL_OPTIONS:
+                       err = br_vlan_rtm_process_global_options(dev, attr,
+                                                                nlh->nlmsg_type,
+                                                                extack);
+                       break;
+               default:
                        continue;
+               }
 
                vlans++;
-               err = br_vlan_rtm_process_one(dev, attr, nlh->nlmsg_type,
-                                             extack);
                if (err)
                        break;
        }
index b4add9ea8964a9a83ac50d73ff4d13fac2638508..a7d5a2334207321143ff57b259893737e38e74e3 100644 (file)
@@ -258,3 +258,88 @@ int br_vlan_process_options(const struct net_bridge *br,
 
        return err;
 }
+
+static int br_vlan_process_global_one_opts(const struct net_bridge *br,
+                                          struct net_bridge_vlan_group *vg,
+                                          struct net_bridge_vlan *v,
+                                          struct nlattr **tb,
+                                          bool *changed,
+                                          struct netlink_ext_ack *extack)
+{
+       *changed = false;
+       return 0;
+}
+
+static const struct nla_policy br_vlan_db_gpol[BRIDGE_VLANDB_GOPTS_MAX + 1] = {
+       [BRIDGE_VLANDB_GOPTS_ID]        = { .type = NLA_U16 },
+       [BRIDGE_VLANDB_GOPTS_RANGE]     = { .type = NLA_U16 },
+};
+
+int br_vlan_rtm_process_global_options(struct net_device *dev,
+                                      const struct nlattr *attr,
+                                      int cmd,
+                                      struct netlink_ext_ack *extack)
+{
+       struct nlattr *tb[BRIDGE_VLANDB_GOPTS_MAX + 1];
+       struct net_bridge_vlan_group *vg;
+       struct net_bridge_vlan *v;
+       u16 vid, vid_range = 0;
+       struct net_bridge *br;
+       int err = 0;
+
+       if (cmd != RTM_NEWVLAN) {
+               NL_SET_ERR_MSG_MOD(extack, "Global vlan options support only set operation");
+               return -EINVAL;
+       }
+       if (!netif_is_bridge_master(dev)) {
+               NL_SET_ERR_MSG_MOD(extack, "Global vlan options can only be set on bridge device");
+               return -EINVAL;
+       }
+       br = netdev_priv(dev);
+       vg = br_vlan_group(br);
+       if (WARN_ON(!vg))
+               return -ENODEV;
+
+       err = nla_parse_nested(tb, BRIDGE_VLANDB_GOPTS_MAX, attr,
+                              br_vlan_db_gpol, extack);
+       if (err)
+               return err;
+
+       if (!tb[BRIDGE_VLANDB_GOPTS_ID]) {
+               NL_SET_ERR_MSG_MOD(extack, "Missing vlan entry id");
+               return -EINVAL;
+       }
+       vid = nla_get_u16(tb[BRIDGE_VLANDB_GOPTS_ID]);
+       if (!br_vlan_valid_id(vid, extack))
+               return -EINVAL;
+
+       if (tb[BRIDGE_VLANDB_GOPTS_RANGE]) {
+               vid_range = nla_get_u16(tb[BRIDGE_VLANDB_GOPTS_RANGE]);
+               if (!br_vlan_valid_id(vid_range, extack))
+                       return -EINVAL;
+               if (vid >= vid_range) {
+                       NL_SET_ERR_MSG_MOD(extack, "End vlan id is less than or equal to start vlan id");
+                       return -EINVAL;
+               }
+       } else {
+               vid_range = vid;
+       }
+
+       for (; vid <= vid_range; vid++) {
+               bool changed = false;
+
+               v = br_vlan_find(vg, vid);
+               if (!v) {
+                       NL_SET_ERR_MSG_MOD(extack, "Vlan in range doesn't exist, can't process global options");
+                       err = -ENOENT;
+                       break;
+               }
+
+               err = br_vlan_process_global_one_opts(br, vg, v, tb, &changed,
+                                                     extack);
+               if (err)
+                       break;
+       }
+
+       return err;
+}