Commit 1080ab95 authored by Nikolay Aleksandrov's avatar Nikolay Aleksandrov Committed by David S. Miller
Browse files

net: bridge: add support for IGMP/MLD stats and export them via netlink



This patch adds stats support for the currently used IGMP/MLD types by the
bridge. The stats are per-port (plus one stat per-bridge) and per-direction
(RX/TX). The stats are exported via netlink via the new linkxstats API
(RTM_GETSTATS). In order to minimize the performance impact, a new option
is used to enable/disable the stats - multicast_stats_enabled, similar to
the recent vlan stats. Also in order to avoid multiple IGMP/MLD type
lookups and checks, we make use of the current "igmp" member of the bridge
private skb->cb region to record the type on Rx (both host-generated and
external packets pass by multicast_rcv()). We can do that since the igmp
member was used as a boolean and all the valid IGMP/MLD types are positive
values. The normal bridge fast-path is not affected at all, the only
affected paths are the flooding ones and since we make use of the IGMP/MLD
type, we can quickly determine if the packet should be counted using
cache-hot data (cb's igmp member). We add counters for:
* IGMP Queries
* IGMP Leaves
* IGMP v1/v2/v3 reports

* MLD Queries
* MLD Leaves
* MLD v1/v2 reports

These are invaluable when monitoring or debugging complex multicast setups
with bridges.
Signed-off-by: default avatarNikolay Aleksandrov <nikolay@cumulusnetworks.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 80e73cc5
...@@ -247,8 +247,34 @@ enum { ...@@ -247,8 +247,34 @@ enum {
enum { enum {
BRIDGE_XSTATS_UNSPEC, BRIDGE_XSTATS_UNSPEC,
BRIDGE_XSTATS_VLAN, BRIDGE_XSTATS_VLAN,
BRIDGE_XSTATS_MCAST,
BRIDGE_XSTATS_PAD,
__BRIDGE_XSTATS_MAX __BRIDGE_XSTATS_MAX
}; };
#define BRIDGE_XSTATS_MAX (__BRIDGE_XSTATS_MAX - 1) #define BRIDGE_XSTATS_MAX (__BRIDGE_XSTATS_MAX - 1)
enum {
BR_MCAST_DIR_RX,
BR_MCAST_DIR_TX,
BR_MCAST_DIR_SIZE
};
/* IGMP/MLD statistics */
struct br_mcast_stats {
__u64 igmp_queries[BR_MCAST_DIR_SIZE];
__u64 igmp_leaves[BR_MCAST_DIR_SIZE];
__u64 igmp_v1reports[BR_MCAST_DIR_SIZE];
__u64 igmp_v2reports[BR_MCAST_DIR_SIZE];
__u64 igmp_v3reports[BR_MCAST_DIR_SIZE];
__u64 igmp_parse_errors;
__u64 mld_queries[BR_MCAST_DIR_SIZE];
__u64 mld_leaves[BR_MCAST_DIR_SIZE];
__u64 mld_v1reports[BR_MCAST_DIR_SIZE];
__u64 mld_v2reports[BR_MCAST_DIR_SIZE];
__u64 mld_parse_errors;
__u64 mcast_bytes[BR_MCAST_DIR_SIZE];
__u64 mcast_packets[BR_MCAST_DIR_SIZE];
};
#endif /* _UAPI_LINUX_IF_BRIDGE_H */ #endif /* _UAPI_LINUX_IF_BRIDGE_H */
...@@ -273,6 +273,7 @@ enum { ...@@ -273,6 +273,7 @@ enum {
IFLA_BR_VLAN_DEFAULT_PVID, IFLA_BR_VLAN_DEFAULT_PVID,
IFLA_BR_PAD, IFLA_BR_PAD,
IFLA_BR_VLAN_STATS_ENABLED, IFLA_BR_VLAN_STATS_ENABLED,
IFLA_BR_MCAST_STATS_ENABLED,
__IFLA_BR_MAX, __IFLA_BR_MAX,
}; };
......
...@@ -104,8 +104,16 @@ static int br_dev_init(struct net_device *dev) ...@@ -104,8 +104,16 @@ static int br_dev_init(struct net_device *dev)
return -ENOMEM; return -ENOMEM;
err = br_vlan_init(br); err = br_vlan_init(br);
if (err) if (err) {
free_percpu(br->stats); free_percpu(br->stats);
return err;
}
err = br_multicast_init_stats(br);
if (err) {
free_percpu(br->stats);
br_vlan_flush(br);
}
br_set_lockdep_class(dev); br_set_lockdep_class(dev);
return err; return err;
......
...@@ -198,8 +198,10 @@ static void br_flood(struct net_bridge *br, struct sk_buff *skb, ...@@ -198,8 +198,10 @@ static void br_flood(struct net_bridge *br, struct sk_buff *skb,
struct sk_buff *skb), struct sk_buff *skb),
bool unicast) bool unicast)
{ {
struct net_bridge_port *p; u8 igmp_type = br_multicast_igmp_type(skb);
__be16 proto = skb->protocol;
struct net_bridge_port *prev; struct net_bridge_port *prev;
struct net_bridge_port *p;
prev = NULL; prev = NULL;
...@@ -218,6 +220,9 @@ static void br_flood(struct net_bridge *br, struct sk_buff *skb, ...@@ -218,6 +220,9 @@ static void br_flood(struct net_bridge *br, struct sk_buff *skb,
prev = maybe_deliver(prev, p, skb, __packet_hook); prev = maybe_deliver(prev, p, skb, __packet_hook);
if (IS_ERR(prev)) if (IS_ERR(prev))
goto out; goto out;
if (prev == p)
br_multicast_count(p->br, p, proto, igmp_type,
BR_MCAST_DIR_TX);
} }
if (!prev) if (!prev)
...@@ -257,9 +262,12 @@ static void br_multicast_flood(struct net_bridge_mdb_entry *mdst, ...@@ -257,9 +262,12 @@ static void br_multicast_flood(struct net_bridge_mdb_entry *mdst,
struct sk_buff *skb)) struct sk_buff *skb))
{ {
struct net_device *dev = BR_INPUT_SKB_CB(skb)->brdev; struct net_device *dev = BR_INPUT_SKB_CB(skb)->brdev;
u8 igmp_type = br_multicast_igmp_type(skb);
struct net_bridge *br = netdev_priv(dev); struct net_bridge *br = netdev_priv(dev);
struct net_bridge_port *prev = NULL; struct net_bridge_port *prev = NULL;
struct net_bridge_port_group *p; struct net_bridge_port_group *p;
__be16 proto = skb->protocol;
struct hlist_node *rp; struct hlist_node *rp;
rp = rcu_dereference(hlist_first_rcu(&br->router_list)); rp = rcu_dereference(hlist_first_rcu(&br->router_list));
...@@ -277,6 +285,9 @@ static void br_multicast_flood(struct net_bridge_mdb_entry *mdst, ...@@ -277,6 +285,9 @@ static void br_multicast_flood(struct net_bridge_mdb_entry *mdst,
prev = maybe_deliver(prev, port, skb, __packet_hook); prev = maybe_deliver(prev, port, skb, __packet_hook);
if (IS_ERR(prev)) if (IS_ERR(prev))
goto out; goto out;
if (prev == port)
br_multicast_count(port->br, port, proto, igmp_type,
BR_MCAST_DIR_TX);
if ((unsigned long)lport >= (unsigned long)port) if ((unsigned long)lport >= (unsigned long)port)
p = rcu_dereference(p->next); p = rcu_dereference(p->next);
......
...@@ -345,8 +345,8 @@ static int find_portno(struct net_bridge *br) ...@@ -345,8 +345,8 @@ static int find_portno(struct net_bridge *br)
static struct net_bridge_port *new_nbp(struct net_bridge *br, static struct net_bridge_port *new_nbp(struct net_bridge *br,
struct net_device *dev) struct net_device *dev)
{ {
int index;
struct net_bridge_port *p; struct net_bridge_port *p;
int index, err;
index = find_portno(br); index = find_portno(br);
if (index < 0) if (index < 0)
...@@ -366,7 +366,12 @@ static struct net_bridge_port *new_nbp(struct net_bridge *br, ...@@ -366,7 +366,12 @@ static struct net_bridge_port *new_nbp(struct net_bridge *br,
br_init_port(p); br_init_port(p);
br_set_state(p, BR_STATE_DISABLED); br_set_state(p, BR_STATE_DISABLED);
br_stp_port_timer_init(p); br_stp_port_timer_init(p);
br_multicast_add_port(p); err = br_multicast_add_port(p);
if (err) {
dev_put(dev);
kfree(p);
p = ERR_PTR(err);
}
return p; return p;
} }
......
...@@ -60,6 +60,9 @@ static int br_pass_frame_up(struct sk_buff *skb) ...@@ -60,6 +60,9 @@ static int br_pass_frame_up(struct sk_buff *skb)
skb = br_handle_vlan(br, vg, skb); skb = br_handle_vlan(br, vg, skb);
if (!skb) if (!skb)
return NET_RX_DROP; return NET_RX_DROP;
/* update the multicast stats if the packet is IGMP/MLD */
br_multicast_count(br, NULL, skb->protocol, br_multicast_igmp_type(skb),
BR_MCAST_DIR_TX);
return NF_HOOK(NFPROTO_BRIDGE, NF_BR_LOCAL_IN, return NF_HOOK(NFPROTO_BRIDGE, NF_BR_LOCAL_IN,
dev_net(indev), NULL, skb, indev, NULL, dev_net(indev), NULL, skb, indev, NULL,
......
...@@ -361,7 +361,8 @@ out: ...@@ -361,7 +361,8 @@ out:
} }
static struct sk_buff *br_ip4_multicast_alloc_query(struct net_bridge *br, static struct sk_buff *br_ip4_multicast_alloc_query(struct net_bridge *br,
__be32 group) __be32 group,
u8 *igmp_type)
{ {
struct sk_buff *skb; struct sk_buff *skb;
struct igmphdr *ih; struct igmphdr *ih;
...@@ -411,6 +412,7 @@ static struct sk_buff *br_ip4_multicast_alloc_query(struct net_bridge *br, ...@@ -411,6 +412,7 @@ static struct sk_buff *br_ip4_multicast_alloc_query(struct net_bridge *br,
skb_set_transport_header(skb, skb->len); skb_set_transport_header(skb, skb->len);
ih = igmp_hdr(skb); ih = igmp_hdr(skb);
*igmp_type = IGMP_HOST_MEMBERSHIP_QUERY;
ih->type = IGMP_HOST_MEMBERSHIP_QUERY; ih->type = IGMP_HOST_MEMBERSHIP_QUERY;
ih->code = (group ? br->multicast_last_member_interval : ih->code = (group ? br->multicast_last_member_interval :
br->multicast_query_response_interval) / br->multicast_query_response_interval) /
...@@ -428,7 +430,8 @@ out: ...@@ -428,7 +430,8 @@ out:
#if IS_ENABLED(CONFIG_IPV6) #if IS_ENABLED(CONFIG_IPV6)
static struct sk_buff *br_ip6_multicast_alloc_query(struct net_bridge *br, static struct sk_buff *br_ip6_multicast_alloc_query(struct net_bridge *br,
const struct in6_addr *group) const struct in6_addr *grp,
u8 *igmp_type)
{ {
struct sk_buff *skb; struct sk_buff *skb;
struct ipv6hdr *ip6h; struct ipv6hdr *ip6h;
...@@ -487,16 +490,17 @@ static struct sk_buff *br_ip6_multicast_alloc_query(struct net_bridge *br, ...@@ -487,16 +490,17 @@ static struct sk_buff *br_ip6_multicast_alloc_query(struct net_bridge *br,
skb_set_transport_header(skb, skb->len); skb_set_transport_header(skb, skb->len);
mldq = (struct mld_msg *) icmp6_hdr(skb); mldq = (struct mld_msg *) icmp6_hdr(skb);
interval = ipv6_addr_any(group) ? interval = ipv6_addr_any(grp) ?
br->multicast_query_response_interval : br->multicast_query_response_interval :
br->multicast_last_member_interval; br->multicast_last_member_interval;
*igmp_type = ICMPV6_MGM_QUERY;
mldq->mld_type = ICMPV6_MGM_QUERY; mldq->mld_type = ICMPV6_MGM_QUERY;
mldq->mld_code = 0; mldq->mld_code = 0;
mldq->mld_cksum = 0; mldq->mld_cksum = 0;
mldq->mld_maxdelay = htons((u16)jiffies_to_msecs(interval)); mldq->mld_maxdelay = htons((u16)jiffies_to_msecs(interval));
mldq->mld_reserved = 0; mldq->mld_reserved = 0;
mldq->mld_mca = *group; mldq->mld_mca = *grp;
/* checksum */ /* checksum */
mldq->mld_cksum = csum_ipv6_magic(&ip6h->saddr, &ip6h->daddr, mldq->mld_cksum = csum_ipv6_magic(&ip6h->saddr, &ip6h->daddr,
...@@ -513,14 +517,16 @@ out: ...@@ -513,14 +517,16 @@ out:
#endif #endif
static struct sk_buff *br_multicast_alloc_query(struct net_bridge *br, static struct sk_buff *br_multicast_alloc_query(struct net_bridge *br,
struct br_ip *addr) struct br_ip *addr,
u8 *igmp_type)
{ {
switch (addr->proto) { switch (addr->proto) {
case htons(ETH_P_IP): case htons(ETH_P_IP):
return br_ip4_multicast_alloc_query(br, addr->u.ip4); return br_ip4_multicast_alloc_query(br, addr->u.ip4, igmp_type);
#if IS_ENABLED(CONFIG_IPV6) #if IS_ENABLED(CONFIG_IPV6)
case htons(ETH_P_IPV6): case htons(ETH_P_IPV6):
return br_ip6_multicast_alloc_query(br, &addr->u.ip6); return br_ip6_multicast_alloc_query(br, &addr->u.ip6,
igmp_type);
#endif #endif
} }
return NULL; return NULL;
...@@ -829,18 +835,23 @@ static void __br_multicast_send_query(struct net_bridge *br, ...@@ -829,18 +835,23 @@ static void __br_multicast_send_query(struct net_bridge *br,
struct br_ip *ip) struct br_ip *ip)
{ {
struct sk_buff *skb; struct sk_buff *skb;
u8 igmp_type;
skb = br_multicast_alloc_query(br, ip); skb = br_multicast_alloc_query(br, ip, &igmp_type);
if (!skb) if (!skb)
return; return;
if (port) { if (port) {
skb->dev = port->dev; skb->dev = port->dev;
br_multicast_count(br, port, skb->protocol, igmp_type,
BR_MCAST_DIR_TX);
NF_HOOK(NFPROTO_BRIDGE, NF_BR_LOCAL_OUT, NF_HOOK(NFPROTO_BRIDGE, NF_BR_LOCAL_OUT,
dev_net(port->dev), NULL, skb, NULL, skb->dev, dev_net(port->dev), NULL, skb, NULL, skb->dev,
br_dev_queue_push_xmit); br_dev_queue_push_xmit);
} else { } else {
br_multicast_select_own_querier(br, ip, skb); br_multicast_select_own_querier(br, ip, skb);
br_multicast_count(br, port, skb->protocol, igmp_type,
BR_MCAST_DIR_RX);
netif_rx(skb); netif_rx(skb);
} }
} }
...@@ -918,7 +929,7 @@ static void br_ip6_multicast_port_query_expired(unsigned long data) ...@@ -918,7 +929,7 @@ static void br_ip6_multicast_port_query_expired(unsigned long data)
} }
#endif #endif
void br_multicast_add_port(struct net_bridge_port *port) int br_multicast_add_port(struct net_bridge_port *port)
{ {
port->multicast_router = MDB_RTR_TYPE_TEMP_QUERY; port->multicast_router = MDB_RTR_TYPE_TEMP_QUERY;
...@@ -930,6 +941,11 @@ void br_multicast_add_port(struct net_bridge_port *port) ...@@ -930,6 +941,11 @@ void br_multicast_add_port(struct net_bridge_port *port)
setup_timer(&port->ip6_own_query.timer, setup_timer(&port->ip6_own_query.timer,
br_ip6_multicast_port_query_expired, (unsigned long)port); br_ip6_multicast_port_query_expired, (unsigned long)port);
#endif #endif
port->mcast_stats = netdev_alloc_pcpu_stats(struct bridge_mcast_stats);
if (!port->mcast_stats)
return -ENOMEM;
return 0;
} }
void br_multicast_del_port(struct net_bridge_port *port) void br_multicast_del_port(struct net_bridge_port *port)
...@@ -944,6 +960,7 @@ void br_multicast_del_port(struct net_bridge_port *port) ...@@ -944,6 +960,7 @@ void br_multicast_del_port(struct net_bridge_port *port)
br_multicast_del_pg(br, pg); br_multicast_del_pg(br, pg);
spin_unlock_bh(&br->multicast_lock); spin_unlock_bh(&br->multicast_lock);
del_timer_sync(&port->multicast_router_timer); del_timer_sync(&port->multicast_router_timer);
free_percpu(port->mcast_stats);
} }
static void br_multicast_enable(struct bridge_mcast_own_query *query) static void br_multicast_enable(struct bridge_mcast_own_query *query)
...@@ -1583,6 +1600,39 @@ static void br_ip6_multicast_leave_group(struct net_bridge *br, ...@@ -1583,6 +1600,39 @@ static void br_ip6_multicast_leave_group(struct net_bridge *br,
} }
#endif #endif
static void br_multicast_err_count(const struct net_bridge *br,
const struct net_bridge_port *p,
__be16 proto)
{
struct bridge_mcast_stats __percpu *stats;
struct bridge_mcast_stats *pstats;
if (!br->multicast_stats_enabled)
return;
if (p)
stats = p->mcast_stats;
else
stats = br->mcast_stats;
if (WARN_ON(!stats))
return;
pstats = this_cpu_ptr(stats);
u64_stats_update_begin(&pstats->syncp);
switch (proto) {
case htons(ETH_P_IP):
pstats->mstats.igmp_parse_errors++;
break;
#if IS_ENABLED(CONFIG_IPV6)
case htons(ETH_P_IPV6):
pstats->mstats.mld_parse_errors++;
break;
#endif
}
u64_stats_update_end(&pstats->syncp);
}
static int br_multicast_ipv4_rcv(struct net_bridge *br, static int br_multicast_ipv4_rcv(struct net_bridge *br,
struct net_bridge_port *port, struct net_bridge_port *port,
struct sk_buff *skb, struct sk_buff *skb,
...@@ -1599,11 +1649,12 @@ static int br_multicast_ipv4_rcv(struct net_bridge *br, ...@@ -1599,11 +1649,12 @@ static int br_multicast_ipv4_rcv(struct net_bridge *br,
BR_INPUT_SKB_CB(skb)->mrouters_only = 1; BR_INPUT_SKB_CB(skb)->mrouters_only = 1;
return 0; return 0;
} else if (err < 0) { } else if (err < 0) {
br_multicast_err_count(br, port, skb->protocol);
return err; return err;
} }
BR_INPUT_SKB_CB(skb)->igmp = 1;
ih = igmp_hdr(skb); ih = igmp_hdr(skb);
BR_INPUT_SKB_CB(skb)->igmp = ih->type;
switch (ih->type) { switch (ih->type) {
case IGMP_HOST_MEMBERSHIP_REPORT: case IGMP_HOST_MEMBERSHIP_REPORT:
...@@ -1625,6 +1676,9 @@ static int br_multicast_ipv4_rcv(struct net_bridge *br, ...@@ -1625,6 +1676,9 @@ static int br_multicast_ipv4_rcv(struct net_bridge *br,
if (skb_trimmed && skb_trimmed != skb) if (skb_trimmed && skb_trimmed != skb)
kfree_skb(skb_trimmed); kfree_skb(skb_trimmed);
br_multicast_count(br, port, skb->protocol, BR_INPUT_SKB_CB(skb)->igmp,
BR_MCAST_DIR_RX);
return err; return err;
} }
...@@ -1645,11 +1699,12 @@ static int br_multicast_ipv6_rcv(struct net_bridge *br, ...@@ -1645,11 +1699,12 @@ static int br_multicast_ipv6_rcv(struct net_bridge *br,
BR_INPUT_SKB_CB(skb)->mrouters_only = 1; BR_INPUT_SKB_CB(skb)->mrouters_only = 1;
return 0; return 0;
} else if (err < 0) { } else if (err < 0) {
br_multicast_err_count(br, port, skb->protocol);
return err; return err;
} }
BR_INPUT_SKB_CB(skb)->igmp = 1;
mld = (struct mld_msg *)skb_transport_header(skb); mld = (struct mld_msg *)skb_transport_header(skb);
BR_INPUT_SKB_CB(skb)->igmp = mld->mld_type;
switch (mld->mld_type) { switch (mld->mld_type) {
case ICMPV6_MGM_REPORT: case ICMPV6_MGM_REPORT:
...@@ -1670,6 +1725,9 @@ static int br_multicast_ipv6_rcv(struct net_bridge *br, ...@@ -1670,6 +1725,9 @@ static int br_multicast_ipv6_rcv(struct net_bridge *br,
if (skb_trimmed && skb_trimmed != skb) if (skb_trimmed && skb_trimmed != skb)
kfree_skb(skb_trimmed); kfree_skb(skb_trimmed);
br_multicast_count(br, port, skb->protocol, BR_INPUT_SKB_CB(skb)->igmp,
BR_MCAST_DIR_RX);
return err; return err;
} }
#endif #endif
...@@ -1677,6 +1735,8 @@ static int br_multicast_ipv6_rcv(struct net_bridge *br, ...@@ -1677,6 +1735,8 @@ static int br_multicast_ipv6_rcv(struct net_bridge *br,
int br_multicast_rcv(struct net_bridge *br, struct net_bridge_port *port, int br_multicast_rcv(struct net_bridge *br, struct net_bridge_port *port,
struct sk_buff *skb, u16 vid) struct sk_buff *skb, u16 vid)
{ {
int ret = 0;
BR_INPUT_SKB_CB(skb)->igmp = 0; BR_INPUT_SKB_CB(skb)->igmp = 0;
BR_INPUT_SKB_CB(skb)->mrouters_only = 0; BR_INPUT_SKB_CB(skb)->mrouters_only = 0;
...@@ -1685,14 +1745,16 @@ int br_multicast_rcv(struct net_bridge *br, struct net_bridge_port *port, ...@@ -1685,14 +1745,16 @@ int br_multicast_rcv(struct net_bridge *br, struct net_bridge_port *port,
switch (skb->protocol) { switch (skb->protocol) {
case htons(ETH_P_IP): case htons(ETH_P_IP):
return br_multicast_ipv4_rcv(br, port, skb, vid); ret = br_multicast_ipv4_rcv(br, port, skb, vid);
break;
#if IS_ENABLED(CONFIG_IPV6) #if IS_ENABLED(CONFIG_IPV6)
case htons(ETH_P_IPV6): case htons(ETH_P_IPV6):
return br_multicast_ipv6_rcv(br, port, skb, vid); ret = br_multicast_ipv6_rcv(br, port, skb, vid);
break;
#endif #endif
} }
return 0; return ret;
} }
static void br_multicast_query_expired(struct net_bridge *br, static void br_multicast_query_expired(struct net_bridge *br,
...@@ -1831,6 +1893,8 @@ void br_multicast_dev_del(struct net_bridge *br) ...@@ -1831,6 +1893,8 @@ void br_multicast_dev_del(struct net_bridge *br)
out: out:
spin_unlock_bh(&br->multicast_lock); spin_unlock_bh(&br->multicast_lock);
free_percpu(br->mcast_stats);
} }
int br_multicast_set_router(struct net_bridge *br, unsigned long val) int br_multicast_set_router(struct net_bridge *br, unsigned long val)
...@@ -2185,3 +2249,128 @@ unlock: ...@@ -2185,3 +2249,128 @@ unlock:
return ret; return ret;
} }
EXPORT_SYMBOL_GPL(br_multicast_has_querier_adjacent); EXPORT_SYMBOL_GPL(br_multicast_has_querier_adjacent);
static void br_mcast_stats_add(struct bridge_mcast_stats __percpu *stats,
__be16 proto, u8 type, u8 dir)
{
struct bridge_mcast_stats *pstats = this_cpu_ptr(stats);
u64_stats_update_begin(&pstats->syncp);
switch (proto) {
case htons(ETH_P_IP):
switch (type) {
case IGMP_HOST_MEMBERSHIP_REPORT:
pstats->mstats.igmp_v1reports[dir]++;
break;
case IGMPV2_HOST_MEMBERSHIP_REPORT:
pstats->mstats.igmp_v2reports[dir]++;
break;
case IGMPV3_HOST_MEMBERSHIP_REPORT:
pstats->mstats.igmp_v3reports[dir]++;
break;
case IGMP_HOST_MEMBERSHIP_QUERY:
pstats->mstats.igmp_queries[dir]++;
break;
case IGMP_HOST_LEAVE_MESSAGE:
pstats->mstats.igmp_leaves[dir]++;
break;
}
break;
#if IS_ENABLED(CONFIG_IPV6)
case htons(ETH_P_IPV6):
switch (type) {
case ICMPV6_MGM_REPORT:
pstats->mstats.mld_v1reports[dir]++;
break;
case ICMPV6_MLD2_REPORT:
pstats->mstats.mld_v2reports[dir]++;
break;
case ICMPV6_MGM_QUERY:
pstats->mstats.mld_queries[dir]++;
break;
case ICMPV6_MGM_REDUCTION:
pstats->mstats.mld_leaves[dir]++;
break;
}
break;
#endif /* CONFIG_IPV6 */
}
u64_stats_update_end(&pstats->syncp);
}
void br_multicast_count(struct net_bridge *br, const struct net_bridge_port *p,
__be16 proto, u8 type, u8 dir)
{
struct bridge_mcast_stats __percpu *stats;
/* if multicast_disabled is true then igmp type can't be set */
if (!type || !br->multicast_stats_enabled)
return;
if (p)
stats = p->mcast_stats;
else
stats = br->mcast_stats;
if (WARN_ON(!stats))