Commit e8194d4f authored by David S. Miller's avatar David S. Miller

Merge branch 'bridge-per-vlan-stats'

Nikolay Aleksandrov says:

====================
bridge: per-vlan stats

This set adds support for bridge per-vlan statistics.
In order to be able to dump statistics for many vlans we need a way to
continue dumping after reaching maximum size, thus patches 01 and 02 extend
the new stats API with a per-device extended link stats attribute and
callback which can save its local state and continue where it left off
afterwards. I considered using the already existing "fill_xstats" callback
but it gets confusing since we need to separate the linkinfo dump from the
new stats api dump and adding a flag/argument to do that just looks messy.
I don't think the rtnl_link_ops size is an issue, so adding these seemed
like the cleaner approach.

Patches 03 and 04 add the stats support and netlink dump support
respectively. The stats accounting is controlled via a bridge option which
is default off, thus the performance impact is kept minimal.
I've tested this set with both old and modified iproute2, kmemleak on and
some traffic stress tests while adding/removing vlans and ports.

v3:
 - drop the RCU pvid patch and remove one pointer fetch as requested
 - make stats accounting optional with default to off, the option is in the
   same cache line as vlan_proto and vlan_enabled, so it is already fetched
   before the fast path check thus the performance impact is minimal, this
   also allows us to avoid one vlan lookup and return early when using pvid
 - rebased and retested

v2:
 - Improve the error checking, rename lidx to prividx and save the current
   idx user instead of restricting it to one in patch 01
 - squash patch 02 into 01 and remove the restriction
 - add callback descriptions, improve the size calculation and change the
   xstats message structure to have an embedding level per rtnl link type
   so we can avoid one call to get the link type (and thus filter on it)
   and also each link type can now have any number of private attributes
   inside
 - fix a problem where the vlan stats are not dumped if the bridge has 0
   vlans on it but has vlans on the ports, add bridge link type private
   attributes and also add paddings for future extensions to avoid at least
   a few netlink attributes and improve struct alignment
 - drop the is_skb_forwardable argument constifying patch as it's not
   needed anymore, but it's a nice cleanup which I'll send separately
====================
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents d1ac3b16 a60c0903
...@@ -47,6 +47,9 @@ static inline int rtnl_msg_family(const struct nlmsghdr *nlh) ...@@ -47,6 +47,9 @@ static inline int rtnl_msg_family(const struct nlmsghdr *nlh)
* @get_num_rx_queues: Function to determine number of receive queues * @get_num_rx_queues: Function to determine number of receive queues
* to create when creating a new device. * to create when creating a new device.
* @get_link_net: Function to get the i/o netns of the device * @get_link_net: Function to get the i/o netns of the device
* @get_linkxstats_size: Function to calculate the required room for
* dumping device-specific extended link stats
* @fill_linkxstats: Function to dump device-specific extended link stats
*/ */
struct rtnl_link_ops { struct rtnl_link_ops {
struct list_head list; struct list_head list;
...@@ -95,6 +98,10 @@ struct rtnl_link_ops { ...@@ -95,6 +98,10 @@ struct rtnl_link_ops {
const struct net_device *dev, const struct net_device *dev,
const struct net_device *slave_dev); const struct net_device *slave_dev);
struct net *(*get_link_net)(const struct net_device *dev); struct net *(*get_link_net)(const struct net_device *dev);
size_t (*get_linkxstats_size)(const struct net_device *dev);
int (*fill_linkxstats)(struct sk_buff *skb,
const struct net_device *dev,
int *prividx);
}; };
int __rtnl_link_register(struct rtnl_link_ops *ops); int __rtnl_link_register(struct rtnl_link_ops *ops);
......
...@@ -134,6 +134,16 @@ struct bridge_vlan_info { ...@@ -134,6 +134,16 @@ struct bridge_vlan_info {
__u16 vid; __u16 vid;
}; };
struct bridge_vlan_xstats {
__u64 rx_bytes;
__u64 rx_packets;
__u64 tx_bytes;
__u64 tx_packets;
__u16 vid;
__u16 pad1;
__u32 pad2;
};
/* Bridge multicast database attributes /* Bridge multicast database attributes
* [MDBA_MDB] = { * [MDBA_MDB] = {
* [MDBA_MDB_ENTRY] = { * [MDBA_MDB_ENTRY] = {
...@@ -233,4 +243,12 @@ enum { ...@@ -233,4 +243,12 @@ enum {
}; };
#define MDBA_SET_ENTRY_MAX (__MDBA_SET_ENTRY_MAX - 1) #define MDBA_SET_ENTRY_MAX (__MDBA_SET_ENTRY_MAX - 1)
/* Embedded inside LINK_XSTATS_TYPE_BRIDGE */
enum {
BRIDGE_XSTATS_UNSPEC,
BRIDGE_XSTATS_VLAN,
__BRIDGE_XSTATS_MAX
};
#define BRIDGE_XSTATS_MAX (__BRIDGE_XSTATS_MAX - 1)
#endif /* _UAPI_LINUX_IF_BRIDGE_H */ #endif /* _UAPI_LINUX_IF_BRIDGE_H */
...@@ -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,
}; };
...@@ -810,6 +811,7 @@ struct if_stats_msg { ...@@ -810,6 +811,7 @@ struct if_stats_msg {
enum { enum {
IFLA_STATS_UNSPEC, /* also used as 64bit pad attribute */ IFLA_STATS_UNSPEC, /* also used as 64bit pad attribute */
IFLA_STATS_LINK_64, IFLA_STATS_LINK_64,
IFLA_STATS_LINK_XSTATS,
__IFLA_STATS_MAX, __IFLA_STATS_MAX,
}; };
...@@ -817,4 +819,16 @@ enum { ...@@ -817,4 +819,16 @@ enum {
#define IFLA_STATS_FILTER_BIT(ATTR) (1 << (ATTR - 1)) #define IFLA_STATS_FILTER_BIT(ATTR) (1 << (ATTR - 1))
/* These are embedded into IFLA_STATS_LINK_XSTATS:
* [IFLA_STATS_LINK_XSTATS]
* -> [LINK_XSTATS_TYPE_xxx]
* -> [rtnl link type specific attributes]
*/
enum {
LINK_XSTATS_TYPE_UNSPEC,
LINK_XSTATS_TYPE_BRIDGE,
__LINK_XSTATS_TYPE_MAX
};
#define LINK_XSTATS_TYPE_MAX (__LINK_XSTATS_TYPE_MAX - 1)
#endif /* _UAPI_LINUX_IF_LINK_H */ #endif /* _UAPI_LINUX_IF_LINK_H */
...@@ -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
...@@ -1223,6 +1234,69 @@ static int br_fill_info(struct sk_buff *skb, const struct net_device *brdev) ...@@ -1223,6 +1234,69 @@ static int br_fill_info(struct sk_buff *skb, const struct net_device *brdev)
return 0; return 0;
} }
static size_t br_get_linkxstats_size(const struct net_device *dev)
{
struct net_bridge *br = netdev_priv(dev);
struct net_bridge_vlan_group *vg;
struct net_bridge_vlan *v;
int numvls = 0;
vg = br_vlan_group(br);
if (!vg)
return 0;
/* we need to count all, even placeholder entries */
list_for_each_entry(v, &vg->vlan_list, vlist)
numvls++;
/* account for the vlans and the link xstats type nest attribute */
return numvls * nla_total_size(sizeof(struct bridge_vlan_xstats)) +
nla_total_size(0);
}
static int br_fill_linkxstats(struct sk_buff *skb, const struct net_device *dev,
int *prividx)
{
struct net_bridge *br = netdev_priv(dev);
struct net_bridge_vlan_group *vg;
struct net_bridge_vlan *v;
struct nlattr *nest;
int vl_idx = 0;
vg = br_vlan_group(br);
if (!vg)
goto out;
nest = nla_nest_start(skb, LINK_XSTATS_TYPE_BRIDGE);
if (!nest)
return -EMSGSIZE;
list_for_each_entry(v, &vg->vlan_list, vlist) {
struct bridge_vlan_xstats vxi;
struct br_vlan_stats stats;
if (vl_idx++ < *prividx)
continue;
memset(&vxi, 0, sizeof(vxi));
vxi.vid = v->vid;
br_vlan_get_stats(v, &stats);
vxi.rx_bytes = stats.rx_bytes;
vxi.rx_packets = stats.rx_packets;
vxi.tx_bytes = stats.tx_bytes;
vxi.tx_packets = stats.tx_packets;
if (nla_put(skb, BRIDGE_XSTATS_VLAN, sizeof(vxi), &vxi))
goto nla_put_failure;
}
nla_nest_end(skb, nest);
*prividx = 0;
out:
return 0;
nla_put_failure:
nla_nest_end(skb, nest);
*prividx = vl_idx;
return -EMSGSIZE;
}
static struct rtnl_af_ops br_af_ops __read_mostly = { static struct rtnl_af_ops br_af_ops __read_mostly = {
.family = AF_BRIDGE, .family = AF_BRIDGE,
...@@ -1241,6 +1315,8 @@ struct rtnl_link_ops br_link_ops __read_mostly = { ...@@ -1241,6 +1315,8 @@ struct rtnl_link_ops br_link_ops __read_mostly = {
.dellink = br_dev_delete, .dellink = br_dev_delete,
.get_size = br_get_size, .get_size = br_get_size,
.fill_info = br_fill_info, .fill_info = br_fill_info,
.fill_linkxstats = br_fill_linkxstats,
.get_linkxstats_size = br_get_linkxstats_size,
.slave_maxtype = IFLA_BRPORT_MAX, .slave_maxtype = IFLA_BRPORT_MAX,
.slave_policy = br_port_policy, .slave_policy = br_port_policy,
......
...@@ -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);
...@@ -699,6 +711,8 @@ int nbp_vlan_delete(struct net_bridge_port *port, u16 vid); ...@@ -699,6 +711,8 @@ int nbp_vlan_delete(struct net_bridge_port *port, u16 vid);
void nbp_vlan_flush(struct net_bridge_port *port); void nbp_vlan_flush(struct net_bridge_port *port);
int nbp_vlan_init(struct net_bridge_port *port); int nbp_vlan_init(struct net_bridge_port *port);
int nbp_get_num_vlan_infos(struct net_bridge_port *p, u32 filter_mask); int nbp_get_num_vlan_infos(struct net_bridge_port *p, u32 filter_mask);
void br_vlan_get_stats(const struct net_bridge_vlan *v,
struct br_vlan_stats *stats);
static inline struct net_bridge_vlan_group *br_vlan_group( static inline struct net_bridge_vlan_group *br_vlan_group(
const struct net_bridge *br) const struct net_bridge *br)
...@@ -881,6 +895,10 @@ static inline struct net_bridge_vlan_group *nbp_vlan_group_rcu( ...@@ -881,6 +895,10 @@ static inline struct net_bridge_vlan_group *nbp_vlan_group_rcu(
return NULL; return NULL;
} }
static inline void br_vlan_get_stats(const struct net_bridge_vlan *v,
struct br_vlan_stats *stats)
{
}
#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;
...@@ -1000,3 +1054,30 @@ void nbp_vlan_flush(struct net_bridge_port *port) ...@@ -1000,3 +1054,30 @@ void nbp_vlan_flush(struct net_bridge_port *port)
synchronize_rcu(); synchronize_rcu();
__vlan_group_free(vg); __vlan_group_free(vg);
} }
void br_vlan_get_stats(const struct net_bridge_vlan *v,
struct br_vlan_stats *stats)
{
int i;
memset(stats, 0, sizeof(*stats));
for_each_possible_cpu(i) {
u64 rxpackets, rxbytes, txpackets, txbytes;
struct br_vlan_stats *cpu_stats;
unsigned int start;
cpu_stats = per_cpu_ptr(v->stats, i);
do {
start = u64_stats_fetch_begin_irq(&cpu_stats->syncp);
rxpackets = cpu_stats->rx_packets;
rxbytes = cpu_stats->rx_bytes;
txbytes = cpu_stats->tx_bytes;
txpackets = cpu_stats->tx_packets;
} while (u64_stats_fetch_retry_irq(&cpu_stats->syncp, start));
stats->rx_packets += rxpackets;
stats->rx_bytes += rxbytes;
stats->tx_bytes += txbytes;
stats->tx_packets += txpackets;
}
}
...@@ -3444,13 +3444,21 @@ static int rtnl_bridge_dellink(struct sk_buff *skb, struct nlmsghdr *nlh) ...@@ -3444,13 +3444,21 @@ static int rtnl_bridge_dellink(struct sk_buff *skb, struct nlmsghdr *nlh)
return err; return err;
} }
static bool stats_attr_valid(unsigned int mask, int attrid, int idxattr)
{
return (mask & IFLA_STATS_FILTER_BIT(attrid)) &&
(!idxattr || idxattr == attrid);
}
static int rtnl_fill_statsinfo(struct sk_buff *skb, struct net_device *dev, static int rtnl_fill_statsinfo(struct sk_buff *skb, struct net_device *dev,
int type, u32 pid, u32 seq, u32 change, int type, u32 pid, u32 seq, u32 change,
unsigned int flags, unsigned int filter_mask) unsigned int flags, unsigned int filter_mask,
int *idxattr, int *prividx)
{ {
struct if_stats_msg *ifsm; struct if_stats_msg *ifsm;
struct nlmsghdr *nlh; struct nlmsghdr *nlh;
struct nlattr *attr; struct nlattr *attr;
int s_prividx = *prividx;
ASSERT_RTNL(); ASSERT_RTNL();
...@@ -3462,7 +3470,7 @@ static int rtnl_fill_statsinfo(struct sk_buff *skb, struct net_device *dev, ...@@ -3462,7 +3470,7 @@ static int rtnl_fill_statsinfo(struct sk_buff *skb, struct net_device *dev,
ifsm->ifindex = dev->ifindex; ifsm->ifindex = dev->ifindex;
ifsm->filter_mask = filter_mask; ifsm->filter_mask = filter_mask;
if (filter_mask & IFLA_STATS_FILTER_BIT(IFLA_STATS_LINK_64)) { if (stats_attr_valid(filter_mask, IFLA_STATS_LINK_64, *idxattr)) {
struct rtnl_link_stats64 *sp; struct rtnl_link_stats64 *sp;
attr = nla_reserve_64bit(skb, IFLA_STATS_LINK_64, attr = nla_reserve_64bit(skb, IFLA_STATS_LINK_64,
...@@ -3475,12 +3483,36 @@ static int rtnl_fill_statsinfo(struct sk_buff *skb, struct net_device *dev, ...@@ -3475,12 +3483,36 @@ static int rtnl_fill_statsinfo(struct sk_buff *skb, struct net_device *dev,
dev_get_stats(dev, sp); dev_get_stats(dev, sp);
} }
if (stats_attr_valid(filter_mask, IFLA_STATS_LINK_XSTATS, *idxattr)) {
const struct rtnl_link_ops *ops = dev->rtnl_link_ops;
if (ops && ops->fill_linkxstats) {
int err;
*idxattr = IFLA_STATS_LINK_XSTATS;
attr = nla_nest_start(skb,
IFLA_STATS_LINK_XSTATS);
if (!attr)
goto nla_put_failure;
err = ops->fill_linkxstats(skb, dev, prividx);
nla_nest_end(skb, attr);
if (err)
goto nla_put_failure;
*idxattr = 0;
}
}
nlmsg_end(skb, nlh); nlmsg_end(skb, nlh);
return 0; return 0;
nla_put_failure: nla_put_failure:
nlmsg_cancel(skb, nlh); /* not a multi message or no progress mean a real error */
if (!(flags & NLM_F_MULTI) || s_prividx == *prividx)
nlmsg_cancel(skb, nlh);
else
nlmsg_end(skb, nlh);
return -EMSGSIZE; return -EMSGSIZE;
} }
...@@ -3494,17 +3526,28 @@ static size_t if_nlmsg_stats_size(const struct net_device *dev, ...@@ -3494,17 +3526,28 @@ static size_t if_nlmsg_stats_size(const struct net_device *dev,
{ {
size_t size = 0; size_t size = 0;
if (filter_mask & IFLA_STATS_FILTER_BIT(IFLA_STATS_LINK_64)) if (stats_attr_valid(filter_mask, IFLA_STATS_LINK_64, 0))
size += nla_total_size_64bit(sizeof(struct rtnl_link_stats64)); size += nla_total_size_64bit(sizeof(struct rtnl_link_stats64));
if (stats_attr_valid(filter_mask, IFLA_STATS_LINK_XSTATS, 0)) {
const struct rtnl_link_ops *ops = dev->rtnl_link_ops;
if (ops && ops->get_linkxstats_size) {
size += nla_total_size(ops->get_linkxstats_size(dev));
/* for IFLA_STATS_LINK_XSTATS */
size += nla_total_size(0);
}
}
return size; return size;
} }
static int rtnl_stats_get(struct sk_buff *skb, struct nlmsghdr *nlh) static int rtnl_stats_get(struct sk_buff *skb, struct nlmsghdr *nlh)
{ {
struct net *net = sock_net(skb->sk); struct net *net = sock_net(skb->sk);
struct if_stats_msg *ifsm;
struct net_device *dev = NULL; struct net_device *dev = NULL;
int idxattr = 0, prividx = 0;
struct if_stats_msg *ifsm;
struct sk_buff *nskb; struct sk_buff *nskb;
u32 filter_mask; u32 filter_mask;
int err; int err;
...@@ -3528,7 +3571,7 @@ static int rtnl_stats_get(struct sk_buff *skb, struct nlmsghdr *nlh) ...@@ -3528,7 +3571,7 @@ static int rtnl_stats_get(struct sk_buff *skb, struct nlmsghdr *nlh)
err = rtnl_fill_statsinfo(nskb, dev, RTM_NEWSTATS, err = rtnl_fill_statsinfo(nskb, dev, RTM_NEWSTATS,
NETLINK_CB(skb).portid, nlh->nlmsg_seq, 0, NETLINK_CB(skb).portid, nlh->nlmsg_seq, 0,
0, filter_mask); 0, filter_mask, &idxattr, &prividx);
if (err < 0) { if (err < 0) {
/* -EMSGSIZE implies BUG in if_nlmsg_stats_size */ /* -EMSGSIZE implies BUG in if_nlmsg_stats_size */
WARN_ON(err == -EMSGSIZE); WARN_ON(err == -EMSGSIZE);
...@@ -3542,18 +3585,19 @@ static int rtnl_stats_get(struct sk_buff *skb, struct nlmsghdr *nlh) ...@@ -3542,18 +3585,19 @@ static int rtnl_stats_get(struct sk_buff *skb, struct nlmsghdr *nlh)
static int rtnl_stats_dump(struct sk_buff *skb, struct netlink_callback *cb) static int rtnl_stats_dump(struct sk_buff *skb, struct netlink_callback *cb)
{ {
int h, s_h, err, s_idx, s_idxattr, s_prividx;
struct net *net = sock_net(skb->sk); struct net *net = sock_net(skb->sk);
unsigned int flags = NLM_F_MULTI;
struct if_stats_msg *ifsm; struct if_stats_msg *ifsm;
int h, s_h;
int idx = 0, s_idx;
struct net_device *dev;
struct hlist_head *head; struct hlist_head *head;
unsigned int flags = NLM_F_MULTI; struct net_device *dev;
u32 filter_mask = 0; u32 filter_mask = 0;
int err; int idx = 0;
s_h = cb->args[0]; s_h = cb->args[0];
s_idx = cb->args[1]; s_idx = cb->args[1];
s_idxattr = cb->args[2];
s_prividx = cb->args[3];
cb->seq = net->dev_base_seq; cb->seq = net->dev_base_seq;
...@@ -3571,7 +3615,8 @@ static int rtnl_stats_dump(struct sk_buff *skb, struct netlink_callback *cb) ...@@ -3571,7 +3615,8 @@ static int rtnl_stats_dump(struct sk_buff *skb, struct netlink_callback *cb)
err = rtnl_fill_statsinfo(skb, dev, RTM_NEWSTATS, err = rtnl_fill_statsinfo(skb, dev, RTM_NEWSTATS,
NETLINK_CB(cb->skb).portid, NETLINK_CB(cb->skb).portid,
cb->nlh->nlmsg_seq, 0, cb->nlh->nlmsg_seq, 0,
flags, filter_mask); flags, filter_mask,
&s_idxattr, &s_prividx);
/* If we ran out of room on the first message, /* If we ran out of room on the first message,
* we're in trouble * we're in trouble
*/ */
...@@ -3579,13 +3624,16 @@ static int rtnl_stats_dump(struct sk_buff *skb, struct netlink_callback *cb) ...@@ -3579,13 +3624,16 @@ static int rtnl_stats_dump(struct sk_buff *skb, struct netlink_callback *cb)
if (err < 0) if (err < 0)
goto out; goto out;
s_prividx = 0;
s_idxattr = 0;
nl_dump_check_consistent(cb, nlmsg_hdr(skb)); nl_dump_check_consistent(cb, nlmsg_hdr(skb));
cont: cont:
idx++; idx++;
} }
} }
out: out:
cb->args[3] = s_prividx;
cb->args[2] = s_idxattr;
cb->args[1] = idx; cb->args[1] = idx;
cb->args[0] = h; cb->args[0] = h;
......
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