Commit 6dada9b1 authored by Nikolay Aleksandrov's avatar Nikolay Aleksandrov Committed by David S. Miller

bridge: vlan: learn to count

Add support for per-VLAN Tx/Rx statistics. Every global vlan context gets
allocated a per-cpu stats which is then set in each per-port vlan context
for quick access. The br_allowed_ingress() common function is used to
account for Rx packets and the br_handle_vlan() common function is used
to account for Tx packets. Stats accounting is performed only if the
bridge-wide vlan_stats_enabled option is set either via sysfs or netlink.
A struct hole between vlan_enabled and vlan_proto is used for the new
option so it is in the same cache line. Currently it is binary (on/off)
but it is intentionally restricted to exactly 0 and 1 since other values
will be used in the future for different purposes (e.g. per-port stats).
Signed-off-by: default avatarNikolay Aleksandrov <nikolay@cumulusnetworks.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 97a47fac
...@@ -272,6 +272,7 @@ enum { ...@@ -272,6 +272,7 @@ enum {
IFLA_BR_NF_CALL_ARPTABLES, IFLA_BR_NF_CALL_ARPTABLES,
IFLA_BR_VLAN_DEFAULT_PVID, IFLA_BR_VLAN_DEFAULT_PVID,
IFLA_BR_PAD, IFLA_BR_PAD,
IFLA_BR_VLAN_STATS_ENABLED,
__IFLA_BR_MAX, __IFLA_BR_MAX,
}; };
......
...@@ -850,6 +850,7 @@ static const struct nla_policy br_policy[IFLA_BR_MAX + 1] = { ...@@ -850,6 +850,7 @@ static const struct nla_policy br_policy[IFLA_BR_MAX + 1] = {
[IFLA_BR_NF_CALL_IP6TABLES] = { .type = NLA_U8 }, [IFLA_BR_NF_CALL_IP6TABLES] = { .type = NLA_U8 },
[IFLA_BR_NF_CALL_ARPTABLES] = { .type = NLA_U8 }, [IFLA_BR_NF_CALL_ARPTABLES] = { .type = NLA_U8 },
[IFLA_BR_VLAN_DEFAULT_PVID] = { .type = NLA_U16 }, [IFLA_BR_VLAN_DEFAULT_PVID] = { .type = NLA_U16 },
[IFLA_BR_VLAN_STATS_ENABLED] = { .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[],
...@@ -921,6 +922,14 @@ static int br_changelink(struct net_device *brdev, struct nlattr *tb[], ...@@ -921,6 +922,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_ENABLED]) {
__u8 vlan_stats = nla_get_u8(data[IFLA_BR_VLAN_STATS_ENABLED]);
err = br_vlan_set_stats(br, vlan_stats);
if (err)
return err;
}
#endif #endif
if (data[IFLA_BR_GROUP_FWD_MASK]) { if (data[IFLA_BR_GROUP_FWD_MASK]) {
...@@ -1082,6 +1091,7 @@ static size_t br_get_size(const struct net_device *brdev) ...@@ -1082,6 +1091,7 @@ static size_t br_get_size(const struct net_device *brdev)
#ifdef CONFIG_BRIDGE_VLAN_FILTERING #ifdef CONFIG_BRIDGE_VLAN_FILTERING
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 */
#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 */
...@@ -1167,7 +1177,8 @@ static int br_fill_info(struct sk_buff *skb, const struct net_device *brdev) ...@@ -1167,7 +1177,8 @@ static int br_fill_info(struct sk_buff *skb, const struct net_device *brdev)
#ifdef CONFIG_BRIDGE_VLAN_FILTERING #ifdef CONFIG_BRIDGE_VLAN_FILTERING
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, br->vlan_stats_enabled))
return -EMSGSIZE; return -EMSGSIZE;
#endif #endif
#ifdef CONFIG_BRIDGE_IGMP_SNOOPING #ifdef CONFIG_BRIDGE_IGMP_SNOOPING
......
...@@ -77,12 +77,21 @@ struct bridge_mcast_querier { ...@@ -77,12 +77,21 @@ struct bridge_mcast_querier {
}; };
#endif #endif
struct br_vlan_stats {
u64 rx_bytes;
u64 rx_packets;
u64 tx_bytes;
u64 tx_packets;
struct u64_stats_sync syncp;
};
/** /**
* struct net_bridge_vlan - per-vlan entry * struct net_bridge_vlan - per-vlan entry
* *
* @vnode: rhashtable member * @vnode: rhashtable member
* @vid: VLAN id * @vid: VLAN id
* @flags: bridge vlan flags * @flags: bridge vlan flags
* @stats: per-cpu VLAN statistics
* @br: if MASTER flag set, this points to a bridge struct * @br: if MASTER flag set, this points to a bridge struct
* @port: if MASTER flag unset, this points to a port struct * @port: if MASTER flag unset, this points to a port struct
* @refcnt: if MASTER flag set, this is bumped for each port referencing it * @refcnt: if MASTER flag set, this is bumped for each port referencing it
...@@ -100,6 +109,7 @@ struct net_bridge_vlan { ...@@ -100,6 +109,7 @@ struct net_bridge_vlan {
struct rhash_head vnode; struct rhash_head vnode;
u16 vid; u16 vid;
u16 flags; u16 flags;
struct br_vlan_stats __percpu *stats;
union { union {
struct net_bridge *br; struct net_bridge *br;
struct net_bridge_port *port; struct net_bridge_port *port;
...@@ -342,6 +352,7 @@ struct net_bridge ...@@ -342,6 +352,7 @@ struct net_bridge
#ifdef CONFIG_BRIDGE_VLAN_FILTERING #ifdef CONFIG_BRIDGE_VLAN_FILTERING
struct net_bridge_vlan_group __rcu *vlgrp; struct net_bridge_vlan_group __rcu *vlgrp;
u8 vlan_enabled; u8 vlan_enabled;
u8 vlan_stats_enabled;
__be16 vlan_proto; __be16 vlan_proto;
u16 default_pvid; u16 default_pvid;
#endif #endif
...@@ -691,6 +702,7 @@ int __br_vlan_filter_toggle(struct net_bridge *br, unsigned long val); ...@@ -691,6 +702,7 @@ int __br_vlan_filter_toggle(struct net_bridge *br, unsigned long val);
int br_vlan_filter_toggle(struct net_bridge *br, unsigned long val); 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_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);
...@@ -880,7 +892,6 @@ static inline struct net_bridge_vlan_group *nbp_vlan_group_rcu( ...@@ -880,7 +892,6 @@ static inline struct net_bridge_vlan_group *nbp_vlan_group_rcu(
{ {
return NULL; return NULL;
} }
#endif #endif
struct nf_br_ops { struct nf_br_ops {
......
...@@ -731,6 +731,22 @@ static ssize_t default_pvid_store(struct device *d, ...@@ -731,6 +731,22 @@ static ssize_t default_pvid_store(struct device *d,
return store_bridge_parm(d, buf, len, br_vlan_set_default_pvid); return store_bridge_parm(d, buf, len, br_vlan_set_default_pvid);
} }
static DEVICE_ATTR_RW(default_pvid); static DEVICE_ATTR_RW(default_pvid);
static ssize_t vlan_stats_enabled_show(struct device *d,
struct device_attribute *attr,
char *buf)
{
struct net_bridge *br = to_bridge(d);
return sprintf(buf, "%u\n", br->vlan_stats_enabled);
}
static ssize_t vlan_stats_enabled_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);
}
static DEVICE_ATTR_RW(vlan_stats_enabled);
#endif #endif
static struct attribute *bridge_attrs[] = { static struct attribute *bridge_attrs[] = {
...@@ -778,6 +794,7 @@ static struct attribute *bridge_attrs[] = { ...@@ -778,6 +794,7 @@ static struct attribute *bridge_attrs[] = {
&dev_attr_vlan_filtering.attr, &dev_attr_vlan_filtering.attr,
&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,
#endif #endif
NULL NULL
}; };
......
...@@ -162,6 +162,17 @@ static struct net_bridge_vlan *br_vlan_get_master(struct net_bridge *br, u16 vid ...@@ -162,6 +162,17 @@ static struct net_bridge_vlan *br_vlan_get_master(struct net_bridge *br, u16 vid
return masterv; return masterv;
} }
static void br_master_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));
free_percpu(v->stats);
v->stats = NULL;
kfree(v);
}
static void br_vlan_put_master(struct net_bridge_vlan *masterv) static void br_vlan_put_master(struct net_bridge_vlan *masterv)
{ {
struct net_bridge_vlan_group *vg; struct net_bridge_vlan_group *vg;
...@@ -174,7 +185,7 @@ static void br_vlan_put_master(struct net_bridge_vlan *masterv) ...@@ -174,7 +185,7 @@ static void br_vlan_put_master(struct net_bridge_vlan *masterv)
rhashtable_remove_fast(&vg->vlan_hash, rhashtable_remove_fast(&vg->vlan_hash,
&masterv->vnode, br_vlan_rht_params); &masterv->vnode, br_vlan_rht_params);
__vlan_del_list(masterv); __vlan_del_list(masterv);
kfree_rcu(masterv, rcu); call_rcu(&masterv->rcu, br_master_vlan_rcu_free);
} }
} }
...@@ -230,6 +241,7 @@ static int __vlan_add(struct net_bridge_vlan *v, u16 flags) ...@@ -230,6 +241,7 @@ 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;
v->stats = masterv->stats;
} }
/* Add the dev mac and count the vlan only if it's usable */ /* Add the dev mac and count the vlan only if it's usable */
...@@ -329,6 +341,7 @@ struct sk_buff *br_handle_vlan(struct net_bridge *br, ...@@ -329,6 +341,7 @@ struct sk_buff *br_handle_vlan(struct net_bridge *br,
struct net_bridge_vlan_group *vg, struct net_bridge_vlan_group *vg,
struct sk_buff *skb) struct sk_buff *skb)
{ {
struct br_vlan_stats *stats;
struct net_bridge_vlan *v; struct net_bridge_vlan *v;
u16 vid; u16 vid;
...@@ -355,18 +368,27 @@ struct sk_buff *br_handle_vlan(struct net_bridge *br, ...@@ -355,18 +368,27 @@ struct sk_buff *br_handle_vlan(struct net_bridge *br,
return NULL; return NULL;
} }
} }
if (br->vlan_stats_enabled) {
stats = this_cpu_ptr(v->stats);
u64_stats_update_begin(&stats->syncp);
stats->tx_bytes += skb->len;
stats->tx_packets++;
u64_stats_update_end(&stats->syncp);
}
if (v->flags & BRIDGE_VLAN_INFO_UNTAGGED) if (v->flags & BRIDGE_VLAN_INFO_UNTAGGED)
skb->vlan_tci = 0; skb->vlan_tci = 0;
out: out:
return skb; return skb;
} }
/* Called under RCU */ /* Called under RCU */
static bool __allowed_ingress(struct net_bridge_vlan_group *vg, __be16 proto, static bool __allowed_ingress(const struct net_bridge *br,
struct net_bridge_vlan_group *vg,
struct sk_buff *skb, u16 *vid) struct sk_buff *skb, u16 *vid)
{ {
const struct net_bridge_vlan *v; struct br_vlan_stats *stats;
struct net_bridge_vlan *v;
bool tagged; bool tagged;
BR_INPUT_SKB_CB(skb)->vlan_filtered = true; BR_INPUT_SKB_CB(skb)->vlan_filtered = true;
...@@ -375,7 +397,7 @@ static bool __allowed_ingress(struct net_bridge_vlan_group *vg, __be16 proto, ...@@ -375,7 +397,7 @@ static bool __allowed_ingress(struct net_bridge_vlan_group *vg, __be16 proto,
* HW accelerated vlan tag. * HW accelerated vlan tag.
*/ */
if (unlikely(!skb_vlan_tag_present(skb) && if (unlikely(!skb_vlan_tag_present(skb) &&
skb->protocol == proto)) { skb->protocol == br->vlan_proto)) {
skb = skb_vlan_untag(skb); skb = skb_vlan_untag(skb);
if (unlikely(!skb)) if (unlikely(!skb))
return false; return false;
...@@ -383,7 +405,7 @@ static bool __allowed_ingress(struct net_bridge_vlan_group *vg, __be16 proto, ...@@ -383,7 +405,7 @@ static bool __allowed_ingress(struct net_bridge_vlan_group *vg, __be16 proto,
if (!br_vlan_get_tag(skb, vid)) { if (!br_vlan_get_tag(skb, vid)) {
/* Tagged frame */ /* Tagged frame */
if (skb->vlan_proto != proto) { if (skb->vlan_proto != br->vlan_proto) {
/* Protocol-mismatch, empty out vlan_tci for new tag */ /* Protocol-mismatch, empty out vlan_tci for new tag */
skb_push(skb, ETH_HLEN); skb_push(skb, ETH_HLEN);
skb = vlan_insert_tag_set_proto(skb, skb->vlan_proto, skb = vlan_insert_tag_set_proto(skb, skb->vlan_proto,
...@@ -419,7 +441,7 @@ static bool __allowed_ingress(struct net_bridge_vlan_group *vg, __be16 proto, ...@@ -419,7 +441,7 @@ static bool __allowed_ingress(struct net_bridge_vlan_group *vg, __be16 proto,
*vid = pvid; *vid = pvid;
if (likely(!tagged)) if (likely(!tagged))
/* Untagged Frame. */ /* Untagged Frame. */
__vlan_hwaccel_put_tag(skb, proto, pvid); __vlan_hwaccel_put_tag(skb, br->vlan_proto, pvid);
else else
/* Priority-tagged Frame. /* Priority-tagged Frame.
* At this point, We know that skb->vlan_tci had * At this point, We know that skb->vlan_tci had
...@@ -428,13 +450,24 @@ static bool __allowed_ingress(struct net_bridge_vlan_group *vg, __be16 proto, ...@@ -428,13 +450,24 @@ static bool __allowed_ingress(struct net_bridge_vlan_group *vg, __be16 proto,
*/ */
skb->vlan_tci |= pvid; skb->vlan_tci |= pvid;
return true; /* if stats are disabled we can avoid the lookup */
if (!br->vlan_stats_enabled)
return true;
} }
/* Frame had a valid vlan tag. See if vlan is allowed */
v = br_vlan_find(vg, *vid); v = br_vlan_find(vg, *vid);
if (v && br_vlan_should_use(v)) if (!v || !br_vlan_should_use(v))
return true; goto drop;
if (br->vlan_stats_enabled) {
stats = this_cpu_ptr(v->stats);
u64_stats_update_begin(&stats->syncp);
stats->rx_bytes += skb->len;
stats->rx_packets++;
u64_stats_update_end(&stats->syncp);
}
return true;
drop: drop:
kfree_skb(skb); kfree_skb(skb);
return false; return false;
...@@ -452,7 +485,7 @@ bool br_allowed_ingress(const struct net_bridge *br, ...@@ -452,7 +485,7 @@ bool br_allowed_ingress(const struct net_bridge *br,
return true; return true;
} }
return __allowed_ingress(vg, br->vlan_proto, skb, vid); return __allowed_ingress(br, vg, skb, vid);
} }
/* Called under RCU. */ /* Called under RCU. */
...@@ -542,6 +575,11 @@ int br_vlan_add(struct net_bridge *br, u16 vid, u16 flags) ...@@ -542,6 +575,11 @@ int br_vlan_add(struct net_bridge *br, u16 vid, u16 flags)
if (!vlan) if (!vlan)
return -ENOMEM; return -ENOMEM;
vlan->stats = netdev_alloc_pcpu_stats(struct br_vlan_stats);
if (!vlan->stats) {
kfree(vlan);
return -ENOMEM;
}
vlan->vid = vid; vlan->vid = vid;
vlan->flags = flags | BRIDGE_VLAN_INFO_MASTER; vlan->flags = flags | BRIDGE_VLAN_INFO_MASTER;
vlan->flags &= ~BRIDGE_VLAN_INFO_PVID; vlan->flags &= ~BRIDGE_VLAN_INFO_PVID;
...@@ -549,8 +587,10 @@ int br_vlan_add(struct net_bridge *br, u16 vid, u16 flags) ...@@ -549,8 +587,10 @@ int br_vlan_add(struct net_bridge *br, u16 vid, u16 flags)
if (flags & BRIDGE_VLAN_INFO_BRENTRY) if (flags & BRIDGE_VLAN_INFO_BRENTRY)
atomic_set(&vlan->refcnt, 1); atomic_set(&vlan->refcnt, 1);
ret = __vlan_add(vlan, flags); ret = __vlan_add(vlan, flags);
if (ret) if (ret) {
free_percpu(vlan->stats);
kfree(vlan); kfree(vlan);
}
return ret; return ret;
} }
...@@ -711,6 +751,20 @@ int br_vlan_set_proto(struct net_bridge *br, unsigned long val) ...@@ -711,6 +751,20 @@ int br_vlan_set_proto(struct net_bridge *br, unsigned long val)
return __br_vlan_set_proto(br, htons(val)); return __br_vlan_set_proto(br, htons(val));
} }
int br_vlan_set_stats(struct net_bridge *br, unsigned long val)
{
switch (val) {
case 0:
case 1:
br->vlan_stats_enabled = 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;
......
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