Commit 9163a0fc authored by Nikolay Aleksandrov's avatar Nikolay Aleksandrov Committed by David S. Miller

net: bridge: add support for per-port vlan stats

This patch adds an option to have per-port vlan stats instead of the
default global stats. The option can be set only when there are no port
vlans in the bridge since we need to allocate the stats if it is set
when vlans are being added to ports (and respectively free them
when being deleted). Also bump RTNL_MAX_TYPE as the bridge is the
largest user of options. The current stats design allows us to add
these without any changes to the fast-path, it all comes down to
the per-vlan stats pointer which, if this option is enabled, will
be allocated for each port vlan instead of using the global bridge-wide
one.

CC: bridge@lists.linux-foundation.org
CC: Roopa Prabhu <roopa@cumulusnetworks.com>
Signed-off-by: default avatarNikolay Aleksandrov <nikolay@cumulusnetworks.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 66604641
...@@ -287,6 +287,7 @@ enum { ...@@ -287,6 +287,7 @@ enum {
IFLA_BR_MCAST_STATS_ENABLED, IFLA_BR_MCAST_STATS_ENABLED,
IFLA_BR_MCAST_IGMP_VERSION, IFLA_BR_MCAST_IGMP_VERSION,
IFLA_BR_MCAST_MLD_VERSION, IFLA_BR_MCAST_MLD_VERSION,
IFLA_BR_VLAN_STATS_PER_PORT,
__IFLA_BR_MAX, __IFLA_BR_MAX,
}; };
......
...@@ -1034,6 +1034,7 @@ static const struct nla_policy br_policy[IFLA_BR_MAX + 1] = { ...@@ -1034,6 +1034,7 @@ static const struct nla_policy br_policy[IFLA_BR_MAX + 1] = {
[IFLA_BR_MCAST_STATS_ENABLED] = { .type = NLA_U8 }, [IFLA_BR_MCAST_STATS_ENABLED] = { .type = NLA_U8 },
[IFLA_BR_MCAST_IGMP_VERSION] = { .type = NLA_U8 }, [IFLA_BR_MCAST_IGMP_VERSION] = { .type = NLA_U8 },
[IFLA_BR_MCAST_MLD_VERSION] = { .type = NLA_U8 }, [IFLA_BR_MCAST_MLD_VERSION] = { .type = NLA_U8 },
[IFLA_BR_VLAN_STATS_PER_PORT] = { .type = NLA_U8 },
}; };
static int br_changelink(struct net_device *brdev, struct nlattr *tb[], static int br_changelink(struct net_device *brdev, struct nlattr *tb[],
...@@ -1114,6 +1115,14 @@ static int br_changelink(struct net_device *brdev, struct nlattr *tb[], ...@@ -1114,6 +1115,14 @@ static int br_changelink(struct net_device *brdev, struct nlattr *tb[],
if (err) if (err)
return err; return err;
} }
if (data[IFLA_BR_VLAN_STATS_PER_PORT]) {
__u8 per_port = nla_get_u8(data[IFLA_BR_VLAN_STATS_PER_PORT]);
err = br_vlan_set_stats_per_port(br, per_port);
if (err)
return err;
}
#endif #endif
if (data[IFLA_BR_GROUP_FWD_MASK]) { if (data[IFLA_BR_GROUP_FWD_MASK]) {
...@@ -1327,6 +1336,7 @@ static size_t br_get_size(const struct net_device *brdev) ...@@ -1327,6 +1336,7 @@ static size_t br_get_size(const struct net_device *brdev)
nla_total_size(sizeof(__be16)) + /* IFLA_BR_VLAN_PROTOCOL */ nla_total_size(sizeof(__be16)) + /* IFLA_BR_VLAN_PROTOCOL */
nla_total_size(sizeof(u16)) + /* IFLA_BR_VLAN_DEFAULT_PVID */ nla_total_size(sizeof(u16)) + /* IFLA_BR_VLAN_DEFAULT_PVID */
nla_total_size(sizeof(u8)) + /* IFLA_BR_VLAN_STATS_ENABLED */ nla_total_size(sizeof(u8)) + /* IFLA_BR_VLAN_STATS_ENABLED */
nla_total_size(sizeof(u8)) + /* IFLA_BR_VLAN_STATS_PER_PORT */
#endif #endif
nla_total_size(sizeof(u16)) + /* IFLA_BR_GROUP_FWD_MASK */ nla_total_size(sizeof(u16)) + /* IFLA_BR_GROUP_FWD_MASK */
nla_total_size(sizeof(struct ifla_bridge_id)) + /* IFLA_BR_ROOT_ID */ nla_total_size(sizeof(struct ifla_bridge_id)) + /* IFLA_BR_ROOT_ID */
...@@ -1417,7 +1427,9 @@ static int br_fill_info(struct sk_buff *skb, const struct net_device *brdev) ...@@ -1417,7 +1427,9 @@ static int br_fill_info(struct sk_buff *skb, const struct net_device *brdev)
if (nla_put_be16(skb, IFLA_BR_VLAN_PROTOCOL, br->vlan_proto) || if (nla_put_be16(skb, IFLA_BR_VLAN_PROTOCOL, br->vlan_proto) ||
nla_put_u16(skb, IFLA_BR_VLAN_DEFAULT_PVID, br->default_pvid) || nla_put_u16(skb, IFLA_BR_VLAN_DEFAULT_PVID, br->default_pvid) ||
nla_put_u8(skb, IFLA_BR_VLAN_STATS_ENABLED, nla_put_u8(skb, IFLA_BR_VLAN_STATS_ENABLED,
br_opt_get(br, BROPT_VLAN_STATS_ENABLED))) br_opt_get(br, BROPT_VLAN_STATS_ENABLED)) ||
nla_put_u8(skb, IFLA_BR_VLAN_STATS_PER_PORT,
br_opt_get(br, IFLA_BR_VLAN_STATS_PER_PORT)))
return -EMSGSIZE; return -EMSGSIZE;
#endif #endif
#ifdef CONFIG_BRIDGE_IGMP_SNOOPING #ifdef CONFIG_BRIDGE_IGMP_SNOOPING
......
...@@ -320,6 +320,7 @@ enum net_bridge_opts { ...@@ -320,6 +320,7 @@ enum net_bridge_opts {
BROPT_HAS_IPV6_ADDR, BROPT_HAS_IPV6_ADDR,
BROPT_NEIGH_SUPPRESS_ENABLED, BROPT_NEIGH_SUPPRESS_ENABLED,
BROPT_MTU_SET_BY_USER, BROPT_MTU_SET_BY_USER,
BROPT_VLAN_STATS_PER_PORT,
}; };
struct net_bridge { struct net_bridge {
...@@ -859,6 +860,7 @@ int br_vlan_filter_toggle(struct net_bridge *br, unsigned long val); ...@@ -859,6 +860,7 @@ int br_vlan_filter_toggle(struct net_bridge *br, unsigned long val);
int __br_vlan_set_proto(struct net_bridge *br, __be16 proto); int __br_vlan_set_proto(struct net_bridge *br, __be16 proto);
int br_vlan_set_proto(struct net_bridge *br, unsigned long val); int br_vlan_set_proto(struct net_bridge *br, unsigned long val);
int br_vlan_set_stats(struct net_bridge *br, unsigned long val); int br_vlan_set_stats(struct net_bridge *br, unsigned long val);
int br_vlan_set_stats_per_port(struct net_bridge *br, unsigned long val);
int br_vlan_init(struct net_bridge *br); int br_vlan_init(struct net_bridge *br);
int br_vlan_set_default_pvid(struct net_bridge *br, unsigned long val); int br_vlan_set_default_pvid(struct net_bridge *br, unsigned long val);
int __br_vlan_set_default_pvid(struct net_bridge *br, u16 pvid); int __br_vlan_set_default_pvid(struct net_bridge *br, u16 pvid);
......
...@@ -803,6 +803,22 @@ static ssize_t vlan_stats_enabled_store(struct device *d, ...@@ -803,6 +803,22 @@ static ssize_t vlan_stats_enabled_store(struct device *d,
return store_bridge_parm(d, buf, len, br_vlan_set_stats); return store_bridge_parm(d, buf, len, br_vlan_set_stats);
} }
static DEVICE_ATTR_RW(vlan_stats_enabled); static DEVICE_ATTR_RW(vlan_stats_enabled);
static ssize_t vlan_stats_per_port_show(struct device *d,
struct device_attribute *attr,
char *buf)
{
struct net_bridge *br = to_bridge(d);
return sprintf(buf, "%u\n", br_opt_get(br, BROPT_VLAN_STATS_PER_PORT));
}
static ssize_t vlan_stats_per_port_store(struct device *d,
struct device_attribute *attr,
const char *buf, size_t len)
{
return store_bridge_parm(d, buf, len, br_vlan_set_stats_per_port);
}
static DEVICE_ATTR_RW(vlan_stats_per_port);
#endif #endif
static struct attribute *bridge_attrs[] = { static struct attribute *bridge_attrs[] = {
...@@ -856,6 +872,7 @@ static struct attribute *bridge_attrs[] = { ...@@ -856,6 +872,7 @@ static struct attribute *bridge_attrs[] = {
&dev_attr_vlan_protocol.attr, &dev_attr_vlan_protocol.attr,
&dev_attr_default_pvid.attr, &dev_attr_default_pvid.attr,
&dev_attr_vlan_stats_enabled.attr, &dev_attr_vlan_stats_enabled.attr,
&dev_attr_vlan_stats_per_port.attr,
#endif #endif
NULL NULL
}; };
......
...@@ -190,6 +190,19 @@ static void br_vlan_put_master(struct net_bridge_vlan *masterv) ...@@ -190,6 +190,19 @@ static void br_vlan_put_master(struct net_bridge_vlan *masterv)
} }
} }
static void nbp_vlan_rcu_free(struct rcu_head *rcu)
{
struct net_bridge_vlan *v;
v = container_of(rcu, struct net_bridge_vlan, rcu);
WARN_ON(br_vlan_is_master(v));
/* if we had per-port stats configured then free them here */
if (v->brvlan->stats != v->stats)
free_percpu(v->stats);
v->stats = NULL;
kfree(v);
}
/* This is the shared VLAN add function which works for both ports and bridge /* This is the shared VLAN add function which works for both ports and bridge
* devices. There are four possible calls to this function in terms of the * devices. There are four possible calls to this function in terms of the
* vlan entry type: * vlan entry type:
...@@ -245,7 +258,15 @@ static int __vlan_add(struct net_bridge_vlan *v, u16 flags) ...@@ -245,7 +258,15 @@ static int __vlan_add(struct net_bridge_vlan *v, u16 flags)
if (!masterv) if (!masterv)
goto out_filt; goto out_filt;
v->brvlan = masterv; v->brvlan = masterv;
if (br_opt_get(br, BROPT_VLAN_STATS_PER_PORT)) {
v->stats = netdev_alloc_pcpu_stats(struct br_vlan_stats);
if (!v->stats) {
err = -ENOMEM;
goto out_filt;
}
} else {
v->stats = masterv->stats; v->stats = masterv->stats;
}
} else { } else {
err = br_switchdev_port_vlan_add(dev, v->vid, flags); err = br_switchdev_port_vlan_add(dev, v->vid, flags);
if (err && err != -EOPNOTSUPP) if (err && err != -EOPNOTSUPP)
...@@ -329,7 +350,7 @@ static int __vlan_del(struct net_bridge_vlan *v) ...@@ -329,7 +350,7 @@ static int __vlan_del(struct net_bridge_vlan *v)
rhashtable_remove_fast(&vg->vlan_hash, &v->vnode, rhashtable_remove_fast(&vg->vlan_hash, &v->vnode,
br_vlan_rht_params); br_vlan_rht_params);
__vlan_del_list(v); __vlan_del_list(v);
kfree_rcu(v, rcu); call_rcu(&v->rcu, nbp_vlan_rcu_free);
} }
br_vlan_put_master(masterv); br_vlan_put_master(masterv);
...@@ -830,6 +851,30 @@ int br_vlan_set_stats(struct net_bridge *br, unsigned long val) ...@@ -830,6 +851,30 @@ int br_vlan_set_stats(struct net_bridge *br, unsigned long val)
return 0; return 0;
} }
int br_vlan_set_stats_per_port(struct net_bridge *br, unsigned long val)
{
struct net_bridge_port *p;
/* allow to change the option if there are no port vlans configured */
list_for_each_entry(p, &br->port_list, list) {
struct net_bridge_vlan_group *vg = nbp_vlan_group(p);
if (vg->num_vlans)
return -EBUSY;
}
switch (val) {
case 0:
case 1:
br_opt_toggle(br, BROPT_VLAN_STATS_PER_PORT, !!val);
break;
default:
return -EINVAL;
}
return 0;
}
static bool vlan_default_pvid(struct net_bridge_vlan_group *vg, u16 vid) static bool vlan_default_pvid(struct net_bridge_vlan_group *vg, u16 vid)
{ {
struct net_bridge_vlan *v; struct net_bridge_vlan *v;
......
...@@ -59,7 +59,7 @@ ...@@ -59,7 +59,7 @@
#include <net/rtnetlink.h> #include <net/rtnetlink.h>
#include <net/net_namespace.h> #include <net/net_namespace.h>
#define RTNL_MAX_TYPE 48 #define RTNL_MAX_TYPE 49
#define RTNL_SLAVE_MAX_TYPE 36 #define RTNL_SLAVE_MAX_TYPE 36
struct rtnl_link { struct rtnl_link {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment