diff options
author | Nikolay Aleksandrov <nikolay@cumulusnetworks.com> | 2016-06-28 10:57:06 -0400 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2016-06-30 06:18:24 -0400 |
commit | 1080ab95e3c7bdd77870e209aff83c763fdcf439 (patch) | |
tree | 16ffa738a90a36411886403ed92f5e1fef115486 | |
parent | 80e73cc563c4359be809a03bcb8e7e28141a813a (diff) |
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: Nikolay Aleksandrov <nikolay@cumulusnetworks.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
-rw-r--r-- | include/uapi/linux/if_bridge.h | 26 | ||||
-rw-r--r-- | include/uapi/linux/if_link.h | 1 | ||||
-rw-r--r-- | net/bridge/br_device.c | 10 | ||||
-rw-r--r-- | net/bridge/br_forward.c | 13 | ||||
-rw-r--r-- | net/bridge/br_if.c | 9 | ||||
-rw-r--r-- | net/bridge/br_input.c | 3 | ||||
-rw-r--r-- | net/bridge/br_multicast.c | 217 | ||||
-rw-r--r-- | net/bridge/br_netlink.c | 91 | ||||
-rw-r--r-- | net/bridge/br_private.h | 41 | ||||
-rw-r--r-- | net/bridge/br_sysfs_br.c | 25 |
10 files changed, 390 insertions, 46 deletions
diff --git a/include/uapi/linux/if_bridge.h b/include/uapi/linux/if_bridge.h index 397d503fdedb..8304fe6f0561 100644 --- a/include/uapi/linux/if_bridge.h +++ b/include/uapi/linux/if_bridge.h | |||
@@ -247,8 +247,34 @@ enum { | |||
247 | enum { | 247 | enum { |
248 | BRIDGE_XSTATS_UNSPEC, | 248 | BRIDGE_XSTATS_UNSPEC, |
249 | BRIDGE_XSTATS_VLAN, | 249 | BRIDGE_XSTATS_VLAN, |
250 | BRIDGE_XSTATS_MCAST, | ||
251 | BRIDGE_XSTATS_PAD, | ||
250 | __BRIDGE_XSTATS_MAX | 252 | __BRIDGE_XSTATS_MAX |
251 | }; | 253 | }; |
252 | #define BRIDGE_XSTATS_MAX (__BRIDGE_XSTATS_MAX - 1) | 254 | #define BRIDGE_XSTATS_MAX (__BRIDGE_XSTATS_MAX - 1) |
253 | 255 | ||
256 | enum { | ||
257 | BR_MCAST_DIR_RX, | ||
258 | BR_MCAST_DIR_TX, | ||
259 | BR_MCAST_DIR_SIZE | ||
260 | }; | ||
261 | |||
262 | /* IGMP/MLD statistics */ | ||
263 | struct br_mcast_stats { | ||
264 | __u64 igmp_queries[BR_MCAST_DIR_SIZE]; | ||
265 | __u64 igmp_leaves[BR_MCAST_DIR_SIZE]; | ||
266 | __u64 igmp_v1reports[BR_MCAST_DIR_SIZE]; | ||
267 | __u64 igmp_v2reports[BR_MCAST_DIR_SIZE]; | ||
268 | __u64 igmp_v3reports[BR_MCAST_DIR_SIZE]; | ||
269 | __u64 igmp_parse_errors; | ||
270 | |||
271 | __u64 mld_queries[BR_MCAST_DIR_SIZE]; | ||
272 | __u64 mld_leaves[BR_MCAST_DIR_SIZE]; | ||
273 | __u64 mld_v1reports[BR_MCAST_DIR_SIZE]; | ||
274 | __u64 mld_v2reports[BR_MCAST_DIR_SIZE]; | ||
275 | __u64 mld_parse_errors; | ||
276 | |||
277 | __u64 mcast_bytes[BR_MCAST_DIR_SIZE]; | ||
278 | __u64 mcast_packets[BR_MCAST_DIR_SIZE]; | ||
279 | }; | ||
254 | #endif /* _UAPI_LINUX_IF_BRIDGE_H */ | 280 | #endif /* _UAPI_LINUX_IF_BRIDGE_H */ |
diff --git a/include/uapi/linux/if_link.h b/include/uapi/linux/if_link.h index db2458ade81c..4285ac31e865 100644 --- a/include/uapi/linux/if_link.h +++ b/include/uapi/linux/if_link.h | |||
@@ -273,6 +273,7 @@ enum { | |||
273 | IFLA_BR_VLAN_DEFAULT_PVID, | 273 | IFLA_BR_VLAN_DEFAULT_PVID, |
274 | IFLA_BR_PAD, | 274 | IFLA_BR_PAD, |
275 | IFLA_BR_VLAN_STATS_ENABLED, | 275 | IFLA_BR_VLAN_STATS_ENABLED, |
276 | IFLA_BR_MCAST_STATS_ENABLED, | ||
276 | __IFLA_BR_MAX, | 277 | __IFLA_BR_MAX, |
277 | }; | 278 | }; |
278 | 279 | ||
diff --git a/net/bridge/br_device.c b/net/bridge/br_device.c index 2c8095a5d824..0c39e0f6da09 100644 --- a/net/bridge/br_device.c +++ b/net/bridge/br_device.c | |||
@@ -104,8 +104,16 @@ static int br_dev_init(struct net_device *dev) | |||
104 | return -ENOMEM; | 104 | return -ENOMEM; |
105 | 105 | ||
106 | err = br_vlan_init(br); | 106 | err = br_vlan_init(br); |
107 | if (err) | 107 | if (err) { |
108 | free_percpu(br->stats); | 108 | free_percpu(br->stats); |
109 | return err; | ||
110 | } | ||
111 | |||
112 | err = br_multicast_init_stats(br); | ||
113 | if (err) { | ||
114 | free_percpu(br->stats); | ||
115 | br_vlan_flush(br); | ||
116 | } | ||
109 | br_set_lockdep_class(dev); | 117 | br_set_lockdep_class(dev); |
110 | 118 | ||
111 | return err; | 119 | return err; |
diff --git a/net/bridge/br_forward.c b/net/bridge/br_forward.c index f47759f05b6d..6c196037d818 100644 --- a/net/bridge/br_forward.c +++ b/net/bridge/br_forward.c | |||
@@ -198,8 +198,10 @@ static void br_flood(struct net_bridge *br, struct sk_buff *skb, | |||
198 | struct sk_buff *skb), | 198 | struct sk_buff *skb), |
199 | bool unicast) | 199 | bool unicast) |
200 | { | 200 | { |
201 | struct net_bridge_port *p; | 201 | u8 igmp_type = br_multicast_igmp_type(skb); |
202 | __be16 proto = skb->protocol; | ||
202 | struct net_bridge_port *prev; | 203 | struct net_bridge_port *prev; |
204 | struct net_bridge_port *p; | ||
203 | 205 | ||
204 | prev = NULL; | 206 | prev = NULL; |
205 | 207 | ||
@@ -218,6 +220,9 @@ static void br_flood(struct net_bridge *br, struct sk_buff *skb, | |||
218 | prev = maybe_deliver(prev, p, skb, __packet_hook); | 220 | prev = maybe_deliver(prev, p, skb, __packet_hook); |
219 | if (IS_ERR(prev)) | 221 | if (IS_ERR(prev)) |
220 | goto out; | 222 | goto out; |
223 | if (prev == p) | ||
224 | br_multicast_count(p->br, p, proto, igmp_type, | ||
225 | BR_MCAST_DIR_TX); | ||
221 | } | 226 | } |
222 | 227 | ||
223 | if (!prev) | 228 | if (!prev) |
@@ -257,9 +262,12 @@ static void br_multicast_flood(struct net_bridge_mdb_entry *mdst, | |||
257 | struct sk_buff *skb)) | 262 | struct sk_buff *skb)) |
258 | { | 263 | { |
259 | struct net_device *dev = BR_INPUT_SKB_CB(skb)->brdev; | 264 | struct net_device *dev = BR_INPUT_SKB_CB(skb)->brdev; |
265 | u8 igmp_type = br_multicast_igmp_type(skb); | ||
260 | struct net_bridge *br = netdev_priv(dev); | 266 | struct net_bridge *br = netdev_priv(dev); |
261 | struct net_bridge_port *prev = NULL; | 267 | struct net_bridge_port *prev = NULL; |
262 | struct net_bridge_port_group *p; | 268 | struct net_bridge_port_group *p; |
269 | __be16 proto = skb->protocol; | ||
270 | |||
263 | struct hlist_node *rp; | 271 | struct hlist_node *rp; |
264 | 272 | ||
265 | rp = rcu_dereference(hlist_first_rcu(&br->router_list)); | 273 | 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 | prev = maybe_deliver(prev, port, skb, __packet_hook); | 285 | prev = maybe_deliver(prev, port, skb, __packet_hook); |
278 | if (IS_ERR(prev)) | 286 | if (IS_ERR(prev)) |
279 | goto out; | 287 | goto out; |
288 | if (prev == port) | ||
289 | br_multicast_count(port->br, port, proto, igmp_type, | ||
290 | BR_MCAST_DIR_TX); | ||
280 | 291 | ||
281 | if ((unsigned long)lport >= (unsigned long)port) | 292 | if ((unsigned long)lport >= (unsigned long)port) |
282 | p = rcu_dereference(p->next); | 293 | p = rcu_dereference(p->next); |
diff --git a/net/bridge/br_if.c b/net/bridge/br_if.c index 8217aecf025b..f2fede05d32c 100644 --- a/net/bridge/br_if.c +++ b/net/bridge/br_if.c | |||
@@ -345,8 +345,8 @@ static int find_portno(struct net_bridge *br) | |||
345 | static struct net_bridge_port *new_nbp(struct net_bridge *br, | 345 | static struct net_bridge_port *new_nbp(struct net_bridge *br, |
346 | struct net_device *dev) | 346 | struct net_device *dev) |
347 | { | 347 | { |
348 | int index; | ||
349 | struct net_bridge_port *p; | 348 | struct net_bridge_port *p; |
349 | int index, err; | ||
350 | 350 | ||
351 | index = find_portno(br); | 351 | index = find_portno(br); |
352 | if (index < 0) | 352 | if (index < 0) |
@@ -366,7 +366,12 @@ static struct net_bridge_port *new_nbp(struct net_bridge *br, | |||
366 | br_init_port(p); | 366 | br_init_port(p); |
367 | br_set_state(p, BR_STATE_DISABLED); | 367 | br_set_state(p, BR_STATE_DISABLED); |
368 | br_stp_port_timer_init(p); | 368 | br_stp_port_timer_init(p); |
369 | br_multicast_add_port(p); | 369 | err = br_multicast_add_port(p); |
370 | if (err) { | ||
371 | dev_put(dev); | ||
372 | kfree(p); | ||
373 | p = ERR_PTR(err); | ||
374 | } | ||
370 | 375 | ||
371 | return p; | 376 | return p; |
372 | } | 377 | } |
diff --git a/net/bridge/br_input.c b/net/bridge/br_input.c index 43d2cd862bc2..786602bc0567 100644 --- a/net/bridge/br_input.c +++ b/net/bridge/br_input.c | |||
@@ -60,6 +60,9 @@ static int br_pass_frame_up(struct sk_buff *skb) | |||
60 | skb = br_handle_vlan(br, vg, skb); | 60 | skb = br_handle_vlan(br, vg, skb); |
61 | if (!skb) | 61 | if (!skb) |
62 | return NET_RX_DROP; | 62 | return NET_RX_DROP; |
63 | /* update the multicast stats if the packet is IGMP/MLD */ | ||
64 | br_multicast_count(br, NULL, skb->protocol, br_multicast_igmp_type(skb), | ||
65 | BR_MCAST_DIR_TX); | ||
63 | 66 | ||
64 | return NF_HOOK(NFPROTO_BRIDGE, NF_BR_LOCAL_IN, | 67 | return NF_HOOK(NFPROTO_BRIDGE, NF_BR_LOCAL_IN, |
65 | dev_net(indev), NULL, skb, indev, NULL, | 68 | dev_net(indev), NULL, skb, indev, NULL, |
diff --git a/net/bridge/br_multicast.c b/net/bridge/br_multicast.c index 43844144c9c4..e405eef0ae2e 100644 --- a/net/bridge/br_multicast.c +++ b/net/bridge/br_multicast.c | |||
@@ -361,7 +361,8 @@ out: | |||
361 | } | 361 | } |
362 | 362 | ||
363 | static struct sk_buff *br_ip4_multicast_alloc_query(struct net_bridge *br, | 363 | static struct sk_buff *br_ip4_multicast_alloc_query(struct net_bridge *br, |
364 | __be32 group) | 364 | __be32 group, |
365 | u8 *igmp_type) | ||
365 | { | 366 | { |
366 | struct sk_buff *skb; | 367 | struct sk_buff *skb; |
367 | struct igmphdr *ih; | 368 | struct igmphdr *ih; |
@@ -411,6 +412,7 @@ static struct sk_buff *br_ip4_multicast_alloc_query(struct net_bridge *br, | |||
411 | 412 | ||
412 | skb_set_transport_header(skb, skb->len); | 413 | skb_set_transport_header(skb, skb->len); |
413 | ih = igmp_hdr(skb); | 414 | ih = igmp_hdr(skb); |
415 | *igmp_type = IGMP_HOST_MEMBERSHIP_QUERY; | ||
414 | ih->type = IGMP_HOST_MEMBERSHIP_QUERY; | 416 | ih->type = IGMP_HOST_MEMBERSHIP_QUERY; |
415 | ih->code = (group ? br->multicast_last_member_interval : | 417 | ih->code = (group ? br->multicast_last_member_interval : |
416 | br->multicast_query_response_interval) / | 418 | br->multicast_query_response_interval) / |
@@ -428,7 +430,8 @@ out: | |||
428 | 430 | ||
429 | #if IS_ENABLED(CONFIG_IPV6) | 431 | #if IS_ENABLED(CONFIG_IPV6) |
430 | static struct sk_buff *br_ip6_multicast_alloc_query(struct net_bridge *br, | 432 | static struct sk_buff *br_ip6_multicast_alloc_query(struct net_bridge *br, |
431 | const struct in6_addr *group) | 433 | const struct in6_addr *grp, |
434 | u8 *igmp_type) | ||
432 | { | 435 | { |
433 | struct sk_buff *skb; | 436 | struct sk_buff *skb; |
434 | struct ipv6hdr *ip6h; | 437 | struct ipv6hdr *ip6h; |
@@ -487,16 +490,17 @@ static struct sk_buff *br_ip6_multicast_alloc_query(struct net_bridge *br, | |||
487 | skb_set_transport_header(skb, skb->len); | 490 | skb_set_transport_header(skb, skb->len); |
488 | mldq = (struct mld_msg *) icmp6_hdr(skb); | 491 | mldq = (struct mld_msg *) icmp6_hdr(skb); |
489 | 492 | ||
490 | interval = ipv6_addr_any(group) ? | 493 | interval = ipv6_addr_any(grp) ? |
491 | br->multicast_query_response_interval : | 494 | br->multicast_query_response_interval : |
492 | br->multicast_last_member_interval; | 495 | br->multicast_last_member_interval; |
493 | 496 | ||
497 | *igmp_type = ICMPV6_MGM_QUERY; | ||
494 | mldq->mld_type = ICMPV6_MGM_QUERY; | 498 | mldq->mld_type = ICMPV6_MGM_QUERY; |
495 | mldq->mld_code = 0; | 499 | mldq->mld_code = 0; |
496 | mldq->mld_cksum = 0; | 500 | mldq->mld_cksum = 0; |
497 | mldq->mld_maxdelay = htons((u16)jiffies_to_msecs(interval)); | 501 | mldq->mld_maxdelay = htons((u16)jiffies_to_msecs(interval)); |
498 | mldq->mld_reserved = 0; | 502 | mldq->mld_reserved = 0; |
499 | mldq->mld_mca = *group; | 503 | mldq->mld_mca = *grp; |
500 | 504 | ||
501 | /* checksum */ | 505 | /* checksum */ |
502 | mldq->mld_cksum = csum_ipv6_magic(&ip6h->saddr, &ip6h->daddr, | 506 | mldq->mld_cksum = csum_ipv6_magic(&ip6h->saddr, &ip6h->daddr, |
@@ -513,14 +517,16 @@ out: | |||
513 | #endif | 517 | #endif |
514 | 518 | ||
515 | static struct sk_buff *br_multicast_alloc_query(struct net_bridge *br, | 519 | static struct sk_buff *br_multicast_alloc_query(struct net_bridge *br, |
516 | struct br_ip *addr) | 520 | struct br_ip *addr, |
521 | u8 *igmp_type) | ||
517 | { | 522 | { |
518 | switch (addr->proto) { | 523 | switch (addr->proto) { |
519 | case htons(ETH_P_IP): | 524 | case htons(ETH_P_IP): |
520 | return br_ip4_multicast_alloc_query(br, addr->u.ip4); | 525 | return br_ip4_multicast_alloc_query(br, addr->u.ip4, igmp_type); |
521 | #if IS_ENABLED(CONFIG_IPV6) | 526 | #if IS_ENABLED(CONFIG_IPV6) |
522 | case htons(ETH_P_IPV6): | 527 | case htons(ETH_P_IPV6): |
523 | return br_ip6_multicast_alloc_query(br, &addr->u.ip6); | 528 | return br_ip6_multicast_alloc_query(br, &addr->u.ip6, |
529 | igmp_type); | ||
524 | #endif | 530 | #endif |
525 | } | 531 | } |
526 | return NULL; | 532 | return NULL; |
@@ -829,18 +835,23 @@ static void __br_multicast_send_query(struct net_bridge *br, | |||
829 | struct br_ip *ip) | 835 | struct br_ip *ip) |
830 | { | 836 | { |
831 | struct sk_buff *skb; | 837 | struct sk_buff *skb; |
838 | u8 igmp_type; | ||
832 | 839 | ||
833 | skb = br_multicast_alloc_query(br, ip); | 840 | skb = br_multicast_alloc_query(br, ip, &igmp_type); |
834 | if (!skb) | 841 | if (!skb) |
835 | return; | 842 | return; |
836 | 843 | ||
837 | if (port) { | 844 | if (port) { |
838 | skb->dev = port->dev; | 845 | skb->dev = port->dev; |
846 | br_multicast_count(br, port, skb->protocol, igmp_type, | ||
847 | BR_MCAST_DIR_TX); | ||
839 | NF_HOOK(NFPROTO_BRIDGE, NF_BR_LOCAL_OUT, | 848 | NF_HOOK(NFPROTO_BRIDGE, NF_BR_LOCAL_OUT, |
840 | dev_net(port->dev), NULL, skb, NULL, skb->dev, | 849 | dev_net(port->dev), NULL, skb, NULL, skb->dev, |
841 | br_dev_queue_push_xmit); | 850 | br_dev_queue_push_xmit); |
842 | } else { | 851 | } else { |
843 | br_multicast_select_own_querier(br, ip, skb); | 852 | br_multicast_select_own_querier(br, ip, skb); |
853 | br_multicast_count(br, port, skb->protocol, igmp_type, | ||
854 | BR_MCAST_DIR_RX); | ||
844 | netif_rx(skb); | 855 | netif_rx(skb); |
845 | } | 856 | } |
846 | } | 857 | } |
@@ -918,7 +929,7 @@ static void br_ip6_multicast_port_query_expired(unsigned long data) | |||
918 | } | 929 | } |
919 | #endif | 930 | #endif |
920 | 931 | ||
921 | void br_multicast_add_port(struct net_bridge_port *port) | 932 | int br_multicast_add_port(struct net_bridge_port *port) |
922 | { | 933 | { |
923 | port->multicast_router = MDB_RTR_TYPE_TEMP_QUERY; | 934 | port->multicast_router = MDB_RTR_TYPE_TEMP_QUERY; |
924 | 935 | ||
@@ -930,6 +941,11 @@ void br_multicast_add_port(struct net_bridge_port *port) | |||
930 | setup_timer(&port->ip6_own_query.timer, | 941 | setup_timer(&port->ip6_own_query.timer, |
931 | br_ip6_multicast_port_query_expired, (unsigned long)port); | 942 | br_ip6_multicast_port_query_expired, (unsigned long)port); |
932 | #endif | 943 | #endif |
944 | port->mcast_stats = netdev_alloc_pcpu_stats(struct bridge_mcast_stats); | ||
945 | if (!port->mcast_stats) | ||
946 | return -ENOMEM; | ||
947 | |||
948 | return 0; | ||
933 | } | 949 | } |
934 | 950 | ||
935 | void br_multicast_del_port(struct net_bridge_port *port) | 951 | 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 | br_multicast_del_pg(br, pg); | 960 | br_multicast_del_pg(br, pg); |
945 | spin_unlock_bh(&br->multicast_lock); | 961 | spin_unlock_bh(&br->multicast_lock); |
946 | del_timer_sync(&port->multicast_router_timer); | 962 | del_timer_sync(&port->multicast_router_timer); |
963 | free_percpu(port->mcast_stats); | ||
947 | } | 964 | } |
948 | 965 | ||
949 | static void br_multicast_enable(struct bridge_mcast_own_query *query) | 966 | 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 | } | 1600 | } |
1584 | #endif | 1601 | #endif |
1585 | 1602 | ||
1603 | static void br_multicast_err_count(const struct net_bridge *br, | ||
1604 | const struct net_bridge_port *p, | ||
1605 | __be16 proto) | ||
1606 | { | ||
1607 | struct bridge_mcast_stats __percpu *stats; | ||
1608 | struct bridge_mcast_stats *pstats; | ||
1609 | |||
1610 | if (!br->multicast_stats_enabled) | ||
1611 | return; | ||
1612 | |||
1613 | if (p) | ||
1614 | stats = p->mcast_stats; | ||
1615 | else | ||
1616 | stats = br->mcast_stats; | ||
1617 | if (WARN_ON(!stats)) | ||
1618 | return; | ||
1619 | |||
1620 | pstats = this_cpu_ptr(stats); | ||
1621 | |||
1622 | u64_stats_update_begin(&pstats->syncp); | ||
1623 | switch (proto) { | ||
1624 | case htons(ETH_P_IP): | ||
1625 | pstats->mstats.igmp_parse_errors++; | ||
1626 | break; | ||
1627 | #if IS_ENABLED(CONFIG_IPV6) | ||
1628 | case htons(ETH_P_IPV6): | ||
1629 | pstats->mstats.mld_parse_errors++; | ||
1630 | break; | ||
1631 | #endif | ||
1632 | } | ||
1633 | u64_stats_update_end(&pstats->syncp); | ||
1634 | } | ||
1635 | |||
1586 | static int br_multicast_ipv4_rcv(struct net_bridge *br, | 1636 | static int br_multicast_ipv4_rcv(struct net_bridge *br, |
1587 | struct net_bridge_port *port, | 1637 | struct net_bridge_port *port, |
1588 | struct sk_buff *skb, | 1638 | struct sk_buff *skb, |
@@ -1599,11 +1649,12 @@ static int br_multicast_ipv4_rcv(struct net_bridge *br, | |||
1599 | BR_INPUT_SKB_CB(skb)->mrouters_only = 1; | 1649 | BR_INPUT_SKB_CB(skb)->mrouters_only = 1; |
1600 | return 0; | 1650 | return 0; |
1601 | } else if (err < 0) { | 1651 | } else if (err < 0) { |
1652 | br_multicast_err_count(br, port, skb->protocol); | ||
1602 | return err; | 1653 | return err; |
1603 | } | 1654 | } |
1604 | 1655 | ||
1605 | BR_INPUT_SKB_CB(skb)->igmp = 1; | ||
1606 | ih = igmp_hdr(skb); | 1656 | ih = igmp_hdr(skb); |
1657 | BR_INPUT_SKB_CB(skb)->igmp = ih->type; | ||
1607 | 1658 | ||
1608 | switch (ih->type) { | 1659 | switch (ih->type) { |
1609 | case IGMP_HOST_MEMBERSHIP_REPORT: | 1660 | case IGMP_HOST_MEMBERSHIP_REPORT: |
@@ -1625,6 +1676,9 @@ static int br_multicast_ipv4_rcv(struct net_bridge *br, | |||
1625 | if (skb_trimmed && skb_trimmed != skb) | 1676 | if (skb_trimmed && skb_trimmed != skb) |
1626 | kfree_skb(skb_trimmed); | 1677 | kfree_skb(skb_trimmed); |
1627 | 1678 | ||
1679 | br_multicast_count(br, port, skb->protocol, BR_INPUT_SKB_CB(skb)->igmp, | ||
1680 | BR_MCAST_DIR_RX); | ||
1681 | |||
1628 | return err; | 1682 | return err; |
1629 | } | 1683 | } |
1630 | 1684 | ||
@@ -1645,11 +1699,12 @@ static int br_multicast_ipv6_rcv(struct net_bridge *br, | |||
1645 | BR_INPUT_SKB_CB(skb)->mrouters_only = 1; | 1699 | BR_INPUT_SKB_CB(skb)->mrouters_only = 1; |
1646 | return 0; | 1700 | return 0; |
1647 | } else if (err < 0) { | 1701 | } else if (err < 0) { |
1702 | br_multicast_err_count(br, port, skb->protocol); | ||
1648 | return err; | 1703 | return err; |
1649 | } | 1704 | } |
1650 | 1705 | ||
1651 | BR_INPUT_SKB_CB(skb)->igmp = 1; | ||
1652 | mld = (struct mld_msg *)skb_transport_header(skb); | 1706 | mld = (struct mld_msg *)skb_transport_header(skb); |
1707 | BR_INPUT_SKB_CB(skb)->igmp = mld->mld_type; | ||
1653 | 1708 | ||
1654 | switch (mld->mld_type) { | 1709 | switch (mld->mld_type) { |
1655 | case ICMPV6_MGM_REPORT: | 1710 | case ICMPV6_MGM_REPORT: |
@@ -1670,6 +1725,9 @@ static int br_multicast_ipv6_rcv(struct net_bridge *br, | |||
1670 | if (skb_trimmed && skb_trimmed != skb) | 1725 | if (skb_trimmed && skb_trimmed != skb) |
1671 | kfree_skb(skb_trimmed); | 1726 | kfree_skb(skb_trimmed); |
1672 | 1727 | ||
1728 | br_multicast_count(br, port, skb->protocol, BR_INPUT_SKB_CB(skb)->igmp, | ||
1729 | BR_MCAST_DIR_RX); | ||
1730 | |||
1673 | return err; | 1731 | return err; |
1674 | } | 1732 | } |
1675 | #endif | 1733 | #endif |
@@ -1677,6 +1735,8 @@ static int br_multicast_ipv6_rcv(struct net_bridge *br, | |||
1677 | int br_multicast_rcv(struct net_bridge *br, struct net_bridge_port *port, | 1735 | int br_multicast_rcv(struct net_bridge *br, struct net_bridge_port *port, |
1678 | struct sk_buff *skb, u16 vid) | 1736 | struct sk_buff *skb, u16 vid) |
1679 | { | 1737 | { |
1738 | int ret = 0; | ||
1739 | |||
1680 | BR_INPUT_SKB_CB(skb)->igmp = 0; | 1740 | BR_INPUT_SKB_CB(skb)->igmp = 0; |
1681 | BR_INPUT_SKB_CB(skb)->mrouters_only = 0; | 1741 | BR_INPUT_SKB_CB(skb)->mrouters_only = 0; |
1682 | 1742 | ||
@@ -1685,14 +1745,16 @@ int br_multicast_rcv(struct net_bridge *br, struct net_bridge_port *port, | |||
1685 | 1745 | ||
1686 | switch (skb->protocol) { | 1746 | switch (skb->protocol) { |
1687 | case htons(ETH_P_IP): | 1747 | case htons(ETH_P_IP): |
1688 | return br_multicast_ipv4_rcv(br, port, skb, vid); | 1748 | ret = br_multicast_ipv4_rcv(br, port, skb, vid); |
1749 | break; | ||
1689 | #if IS_ENABLED(CONFIG_IPV6) | 1750 | #if IS_ENABLED(CONFIG_IPV6) |
1690 | case htons(ETH_P_IPV6): | 1751 | case htons(ETH_P_IPV6): |
1691 | return br_multicast_ipv6_rcv(br, port, skb, vid); | 1752 | ret = br_multicast_ipv6_rcv(br, port, skb, vid); |
1753 | break; | ||
1692 | #endif | 1754 | #endif |
1693 | } | 1755 | } |
1694 | 1756 | ||
1695 | return 0; | 1757 | return ret; |
1696 | } | 1758 | } |
1697 | 1759 | ||
1698 | static void br_multicast_query_expired(struct net_bridge *br, | 1760 | static void br_multicast_query_expired(struct net_bridge *br, |
@@ -1831,6 +1893,8 @@ void br_multicast_dev_del(struct net_bridge *br) | |||
1831 | 1893 | ||
1832 | out: | 1894 | out: |
1833 | spin_unlock_bh(&br->multicast_lock); | 1895 | spin_unlock_bh(&br->multicast_lock); |
1896 | |||
1897 | free_percpu(br->mcast_stats); | ||
1834 | } | 1898 | } |
1835 | 1899 | ||
1836 | int br_multicast_set_router(struct net_bridge *br, unsigned long val) | 1900 | int br_multicast_set_router(struct net_bridge *br, unsigned long val) |
@@ -2185,3 +2249,128 @@ unlock: | |||
2185 | return ret; | 2249 | return ret; |
2186 | } | 2250 | } |
2187 | EXPORT_SYMBOL_GPL(br_multicast_has_querier_adjacent); | 2251 | EXPORT_SYMBOL_GPL(br_multicast_has_querier_adjacent); |
2252 | |||
2253 | static void br_mcast_stats_add(struct bridge_mcast_stats __percpu *stats, | ||
2254 | __be16 proto, u8 type, u8 dir) | ||
2255 | { | ||
2256 | struct bridge_mcast_stats *pstats = this_cpu_ptr(stats); | ||
2257 | |||
2258 | u64_stats_update_begin(&pstats->syncp); | ||
2259 | switch (proto) { | ||
2260 | case htons(ETH_P_IP): | ||
2261 | switch (type) { | ||
2262 | case IGMP_HOST_MEMBERSHIP_REPORT: | ||
2263 | pstats->mstats.igmp_v1reports[dir]++; | ||
2264 | break; | ||
2265 | case IGMPV2_HOST_MEMBERSHIP_REPORT: | ||
2266 | pstats->mstats.igmp_v2reports[dir]++; | ||
2267 | break; | ||
2268 | case IGMPV3_HOST_MEMBERSHIP_REPORT: | ||
2269 | pstats->mstats.igmp_v3reports[dir]++; | ||
2270 | break; | ||
2271 | case IGMP_HOST_MEMBERSHIP_QUERY: | ||
2272 | pstats->mstats.igmp_queries[dir]++; | ||
2273 | break; | ||
2274 | case IGMP_HOST_LEAVE_MESSAGE: | ||
2275 | pstats->mstats.igmp_leaves[dir]++; | ||
2276 | break; | ||
2277 | } | ||
2278 | break; | ||
2279 | #if IS_ENABLED(CONFIG_IPV6) | ||
2280 | case htons(ETH_P_IPV6): | ||
2281 | switch (type) { | ||
2282 | case ICMPV6_MGM_REPORT: | ||
2283 | pstats->mstats.mld_v1reports[dir]++; | ||
2284 | break; | ||
2285 | case ICMPV6_MLD2_REPORT: | ||
2286 | pstats->mstats.mld_v2reports[dir]++; | ||
2287 | break; | ||
2288 | case ICMPV6_MGM_QUERY: | ||
2289 | pstats->mstats.mld_queries[dir]++; | ||
2290 | break; | ||
2291 | case ICMPV6_MGM_REDUCTION: | ||
2292 | pstats->mstats.mld_leaves[dir]++; | ||
2293 | break; | ||
2294 | } | ||
2295 | break; | ||
2296 | #endif /* CONFIG_IPV6 */ | ||
2297 | } | ||
2298 | u64_stats_update_end(&pstats->syncp); | ||
2299 | } | ||
2300 | |||
2301 | void br_multicast_count(struct net_bridge *br, const struct net_bridge_port *p, | ||
2302 | __be16 proto, u8 type, u8 dir) | ||
2303 | { | ||
2304 | struct bridge_mcast_stats __percpu *stats; | ||
2305 | |||
2306 | /* if multicast_disabled is true then igmp type can't be set */ | ||
2307 | if (!type || !br->multicast_stats_enabled) | ||
2308 | return; | ||
2309 | |||
2310 | if (p) | ||
2311 | stats = p->mcast_stats; | ||
2312 | else | ||
2313 | stats = br->mcast_stats; | ||
2314 | if (WARN_ON(!stats)) | ||
2315 | return; | ||
2316 | |||
2317 | br_mcast_stats_add(stats, proto, type, dir); | ||
2318 | } | ||
2319 | |||
2320 | int br_multicast_init_stats(struct net_bridge *br) | ||
2321 | { | ||
2322 | br->mcast_stats = netdev_alloc_pcpu_stats(struct bridge_mcast_stats); | ||
2323 | if (!br->mcast_stats) | ||
2324 | return -ENOMEM; | ||
2325 | |||
2326 | return 0; | ||
2327 | } | ||
2328 | |||
2329 | static void mcast_stats_add_dir(u64 *dst, u64 *src) | ||
2330 | { | ||
2331 | dst[BR_MCAST_DIR_RX] += src[BR_MCAST_DIR_RX]; | ||
2332 | dst[BR_MCAST_DIR_TX] += src[BR_MCAST_DIR_TX]; | ||
2333 | } | ||
2334 | |||
2335 | void br_multicast_get_stats(const struct net_bridge *br, | ||
2336 | const struct net_bridge_port *p, | ||
2337 | struct br_mcast_stats *dest) | ||
2338 | { | ||
2339 | struct bridge_mcast_stats __percpu *stats; | ||
2340 | struct br_mcast_stats tdst; | ||
2341 | int i; | ||
2342 | |||
2343 | memset(dest, 0, sizeof(*dest)); | ||
2344 | if (p) | ||
2345 | stats = p->mcast_stats; | ||
2346 | else | ||
2347 | stats = br->mcast_stats; | ||
2348 | if (WARN_ON(!stats)) | ||
2349 | return; | ||
2350 | |||
2351 | memset(&tdst, 0, sizeof(tdst)); | ||
2352 | for_each_possible_cpu(i) { | ||
2353 | struct bridge_mcast_stats *cpu_stats = per_cpu_ptr(stats, i); | ||
2354 | struct br_mcast_stats temp; | ||
2355 | unsigned int start; | ||
2356 | |||
2357 | do { | ||
2358 | start = u64_stats_fetch_begin_irq(&cpu_stats->syncp); | ||
2359 | memcpy(&temp, &cpu_stats->mstats, sizeof(temp)); | ||
2360 | } while (u64_stats_fetch_retry_irq(&cpu_stats->syncp, start)); | ||
2361 | |||
2362 | mcast_stats_add_dir(tdst.igmp_queries, temp.igmp_queries); | ||
2363 | mcast_stats_add_dir(tdst.igmp_leaves, temp.igmp_leaves); | ||
2364 | mcast_stats_add_dir(tdst.igmp_v1reports, temp.igmp_v1reports); | ||
2365 | mcast_stats_add_dir(tdst.igmp_v2reports, temp.igmp_v2reports); | ||
2366 | mcast_stats_add_dir(tdst.igmp_v3reports, temp.igmp_v3reports); | ||
2367 | tdst.igmp_parse_errors += temp.igmp_parse_errors; | ||
2368 | |||
2369 | mcast_stats_add_dir(tdst.mld_queries, temp.mld_queries); | ||
2370 | mcast_stats_add_dir(tdst.mld_leaves, temp.mld_leaves); | ||
2371 | mcast_stats_add_dir(tdst.mld_v1reports, temp.mld_v1reports); | ||
2372 | mcast_stats_add_dir(tdst.mld_v2reports, temp.mld_v2reports); | ||
2373 | tdst.mld_parse_errors += temp.mld_parse_errors; | ||
2374 | } | ||
2375 | memcpy(dest, &tdst, sizeof(*dest)); | ||
2376 | } | ||
diff --git a/net/bridge/br_netlink.c b/net/bridge/br_netlink.c index ed75ff9ff9e6..f2a29e467e78 100644 --- a/net/bridge/br_netlink.c +++ b/net/bridge/br_netlink.c | |||
@@ -851,6 +851,7 @@ static const struct nla_policy br_policy[IFLA_BR_MAX + 1] = { | |||
851 | [IFLA_BR_NF_CALL_ARPTABLES] = { .type = NLA_U8 }, | 851 | [IFLA_BR_NF_CALL_ARPTABLES] = { .type = NLA_U8 }, |
852 | [IFLA_BR_VLAN_DEFAULT_PVID] = { .type = NLA_U16 }, | 852 | [IFLA_BR_VLAN_DEFAULT_PVID] = { .type = NLA_U16 }, |
853 | [IFLA_BR_VLAN_STATS_ENABLED] = { .type = NLA_U8 }, | 853 | [IFLA_BR_VLAN_STATS_ENABLED] = { .type = NLA_U8 }, |
854 | [IFLA_BR_MCAST_STATS_ENABLED] = { .type = NLA_U8 }, | ||
854 | }; | 855 | }; |
855 | 856 | ||
856 | static int br_changelink(struct net_device *brdev, struct nlattr *tb[], | 857 | static int br_changelink(struct net_device *brdev, struct nlattr *tb[], |
@@ -1055,6 +1056,13 @@ static int br_changelink(struct net_device *brdev, struct nlattr *tb[], | |||
1055 | 1056 | ||
1056 | br->multicast_startup_query_interval = clock_t_to_jiffies(val); | 1057 | br->multicast_startup_query_interval = clock_t_to_jiffies(val); |
1057 | } | 1058 | } |
1059 | |||
1060 | if (data[IFLA_BR_MCAST_STATS_ENABLED]) { | ||
1061 | __u8 mcast_stats; | ||
1062 | |||
1063 | mcast_stats = nla_get_u8(data[IFLA_BR_MCAST_STATS_ENABLED]); | ||
1064 | br->multicast_stats_enabled = !!mcast_stats; | ||
1065 | } | ||
1058 | #endif | 1066 | #endif |
1059 | #if IS_ENABLED(CONFIG_BRIDGE_NETFILTER) | 1067 | #if IS_ENABLED(CONFIG_BRIDGE_NETFILTER) |
1060 | if (data[IFLA_BR_NF_CALL_IPTABLES]) { | 1068 | if (data[IFLA_BR_NF_CALL_IPTABLES]) { |
@@ -1110,6 +1118,7 @@ static size_t br_get_size(const struct net_device *brdev) | |||
1110 | nla_total_size(sizeof(u8)) + /* IFLA_BR_MCAST_SNOOPING */ | 1118 | nla_total_size(sizeof(u8)) + /* IFLA_BR_MCAST_SNOOPING */ |
1111 | nla_total_size(sizeof(u8)) + /* IFLA_BR_MCAST_QUERY_USE_IFADDR */ | 1119 | nla_total_size(sizeof(u8)) + /* IFLA_BR_MCAST_QUERY_USE_IFADDR */ |
1112 | nla_total_size(sizeof(u8)) + /* IFLA_BR_MCAST_QUERIER */ | 1120 | nla_total_size(sizeof(u8)) + /* IFLA_BR_MCAST_QUERIER */ |
1121 | nla_total_size(sizeof(u8)) + /* IFLA_BR_MCAST_STATS_ENABLED */ | ||
1113 | nla_total_size(sizeof(u32)) + /* IFLA_BR_MCAST_HASH_ELASTICITY */ | 1122 | nla_total_size(sizeof(u32)) + /* IFLA_BR_MCAST_HASH_ELASTICITY */ |
1114 | nla_total_size(sizeof(u32)) + /* IFLA_BR_MCAST_HASH_MAX */ | 1123 | nla_total_size(sizeof(u32)) + /* IFLA_BR_MCAST_HASH_MAX */ |
1115 | nla_total_size(sizeof(u32)) + /* IFLA_BR_MCAST_LAST_MEMBER_CNT */ | 1124 | nla_total_size(sizeof(u32)) + /* IFLA_BR_MCAST_LAST_MEMBER_CNT */ |
@@ -1187,6 +1196,8 @@ static int br_fill_info(struct sk_buff *skb, const struct net_device *brdev) | |||
1187 | nla_put_u8(skb, IFLA_BR_MCAST_QUERY_USE_IFADDR, | 1196 | nla_put_u8(skb, IFLA_BR_MCAST_QUERY_USE_IFADDR, |
1188 | br->multicast_query_use_ifaddr) || | 1197 | br->multicast_query_use_ifaddr) || |
1189 | nla_put_u8(skb, IFLA_BR_MCAST_QUERIER, br->multicast_querier) || | 1198 | nla_put_u8(skb, IFLA_BR_MCAST_QUERIER, br->multicast_querier) || |
1199 | nla_put_u8(skb, IFLA_BR_MCAST_STATS_ENABLED, | ||
1200 | br->multicast_stats_enabled) || | ||
1190 | nla_put_u32(skb, IFLA_BR_MCAST_HASH_ELASTICITY, | 1201 | nla_put_u32(skb, IFLA_BR_MCAST_HASH_ELASTICITY, |
1191 | br->hash_elasticity) || | 1202 | br->hash_elasticity) || |
1192 | nla_put_u32(skb, IFLA_BR_MCAST_HASH_MAX, br->hash_max) || | 1203 | nla_put_u32(skb, IFLA_BR_MCAST_HASH_MAX, br->hash_max) || |
@@ -1242,21 +1253,21 @@ static size_t bridge_get_linkxstats_size(const struct net_device *dev) | |||
1242 | int numvls = 0; | 1253 | int numvls = 0; |
1243 | 1254 | ||
1244 | vg = br_vlan_group(br); | 1255 | vg = br_vlan_group(br); |
1245 | if (!vg) | 1256 | if (vg) { |
1246 | return 0; | 1257 | /* we need to count all, even placeholder entries */ |
1247 | 1258 | list_for_each_entry(v, &vg->vlan_list, vlist) | |
1248 | /* we need to count all, even placeholder entries */ | 1259 | numvls++; |
1249 | list_for_each_entry(v, &vg->vlan_list, vlist) | 1260 | } |
1250 | numvls++; | ||
1251 | 1261 | ||
1252 | /* account for the vlans and the link xstats type nest attribute */ | ||
1253 | return numvls * nla_total_size(sizeof(struct bridge_vlan_xstats)) + | 1262 | return numvls * nla_total_size(sizeof(struct bridge_vlan_xstats)) + |
1263 | nla_total_size(sizeof(struct br_mcast_stats)) + | ||
1254 | nla_total_size(0); | 1264 | nla_total_size(0); |
1255 | } | 1265 | } |
1256 | 1266 | ||
1257 | static size_t brport_get_linkxstats_size(const struct net_device *dev) | 1267 | static size_t brport_get_linkxstats_size(const struct net_device *dev) |
1258 | { | 1268 | { |
1259 | return nla_total_size(0); | 1269 | return nla_total_size(sizeof(struct br_mcast_stats)) + |
1270 | nla_total_size(0); | ||
1260 | } | 1271 | } |
1261 | 1272 | ||
1262 | static size_t br_get_linkxstats_size(const struct net_device *dev, int attr) | 1273 | static size_t br_get_linkxstats_size(const struct net_device *dev, int attr) |
@@ -1280,37 +1291,50 @@ static int bridge_fill_linkxstats(struct sk_buff *skb, | |||
1280 | int *prividx) | 1291 | int *prividx) |
1281 | { | 1292 | { |
1282 | struct net_bridge *br = netdev_priv(dev); | 1293 | struct net_bridge *br = netdev_priv(dev); |
1294 | struct nlattr *nla __maybe_unused; | ||
1283 | struct net_bridge_vlan_group *vg; | 1295 | struct net_bridge_vlan_group *vg; |
1284 | struct net_bridge_vlan *v; | 1296 | struct net_bridge_vlan *v; |
1285 | struct nlattr *nest; | 1297 | struct nlattr *nest; |
1286 | int vl_idx = 0; | 1298 | int vl_idx = 0; |
1287 | 1299 | ||
1288 | vg = br_vlan_group(br); | ||
1289 | if (!vg) | ||
1290 | goto out; | ||
1291 | nest = nla_nest_start(skb, LINK_XSTATS_TYPE_BRIDGE); | 1300 | nest = nla_nest_start(skb, LINK_XSTATS_TYPE_BRIDGE); |
1292 | if (!nest) | 1301 | if (!nest) |
1293 | return -EMSGSIZE; | 1302 | return -EMSGSIZE; |
1294 | list_for_each_entry(v, &vg->vlan_list, vlist) { | ||
1295 | struct bridge_vlan_xstats vxi; | ||
1296 | struct br_vlan_stats stats; | ||
1297 | 1303 | ||
1298 | if (++vl_idx < *prividx) | 1304 | vg = br_vlan_group(br); |
1299 | continue; | 1305 | if (vg) { |
1300 | memset(&vxi, 0, sizeof(vxi)); | 1306 | list_for_each_entry(v, &vg->vlan_list, vlist) { |
1301 | vxi.vid = v->vid; | 1307 | struct bridge_vlan_xstats vxi; |
1302 | br_vlan_get_stats(v, &stats); | 1308 | struct br_vlan_stats stats; |
1303 | vxi.rx_bytes = stats.rx_bytes; | 1309 | |
1304 | vxi.rx_packets = stats.rx_packets; | 1310 | if (++vl_idx < *prividx) |
1305 | vxi.tx_bytes = stats.tx_bytes; | 1311 | continue; |
1306 | vxi.tx_packets = stats.tx_packets; | 1312 | memset(&vxi, 0, sizeof(vxi)); |
1307 | 1313 | vxi.vid = v->vid; | |
1308 | if (nla_put(skb, BRIDGE_XSTATS_VLAN, sizeof(vxi), &vxi)) | 1314 | br_vlan_get_stats(v, &stats); |
1315 | vxi.rx_bytes = stats.rx_bytes; | ||
1316 | vxi.rx_packets = stats.rx_packets; | ||
1317 | vxi.tx_bytes = stats.tx_bytes; | ||
1318 | vxi.tx_packets = stats.tx_packets; | ||
1319 | |||
1320 | if (nla_put(skb, BRIDGE_XSTATS_VLAN, sizeof(vxi), &vxi)) | ||
1321 | goto nla_put_failure; | ||
1322 | } | ||
1323 | } | ||
1324 | |||
1325 | #ifdef CONFIG_BRIDGE_IGMP_SNOOPING | ||
1326 | if (++vl_idx >= *prividx) { | ||
1327 | nla = nla_reserve_64bit(skb, BRIDGE_XSTATS_MCAST, | ||
1328 | sizeof(struct br_mcast_stats), | ||
1329 | BRIDGE_XSTATS_PAD); | ||
1330 | if (!nla) | ||
1309 | goto nla_put_failure; | 1331 | goto nla_put_failure; |
1332 | br_multicast_get_stats(br, NULL, nla_data(nla)); | ||
1310 | } | 1333 | } |
1334 | #endif | ||
1311 | nla_nest_end(skb, nest); | 1335 | nla_nest_end(skb, nest); |
1312 | *prividx = 0; | 1336 | *prividx = 0; |
1313 | out: | 1337 | |
1314 | return 0; | 1338 | return 0; |
1315 | 1339 | ||
1316 | nla_put_failure: | 1340 | nla_put_failure: |
@@ -1324,11 +1348,26 @@ static int brport_fill_linkxstats(struct sk_buff *skb, | |||
1324 | const struct net_device *dev, | 1348 | const struct net_device *dev, |
1325 | int *prividx) | 1349 | int *prividx) |
1326 | { | 1350 | { |
1351 | struct net_bridge_port *p = br_port_get_rtnl(dev); | ||
1352 | struct nlattr *nla __maybe_unused; | ||
1327 | struct nlattr *nest; | 1353 | struct nlattr *nest; |
1328 | 1354 | ||
1355 | if (!p) | ||
1356 | return 0; | ||
1357 | |||
1329 | nest = nla_nest_start(skb, LINK_XSTATS_TYPE_BRIDGE); | 1358 | nest = nla_nest_start(skb, LINK_XSTATS_TYPE_BRIDGE); |
1330 | if (!nest) | 1359 | if (!nest) |
1331 | return -EMSGSIZE; | 1360 | return -EMSGSIZE; |
1361 | #ifdef CONFIG_BRIDGE_IGMP_SNOOPING | ||
1362 | nla = nla_reserve_64bit(skb, BRIDGE_XSTATS_MCAST, | ||
1363 | sizeof(struct br_mcast_stats), | ||
1364 | BRIDGE_XSTATS_PAD); | ||
1365 | if (!nla) { | ||
1366 | nla_nest_end(skb, nest); | ||
1367 | return -EMSGSIZE; | ||
1368 | } | ||
1369 | br_multicast_get_stats(p->br, p, nla_data(nla)); | ||
1370 | #endif | ||
1332 | nla_nest_end(skb, nest); | 1371 | nla_nest_end(skb, nest); |
1333 | 1372 | ||
1334 | return 0; | 1373 | return 0; |
diff --git a/net/bridge/br_private.h b/net/bridge/br_private.h index 52edecf3c294..4dc851166ad1 100644 --- a/net/bridge/br_private.h +++ b/net/bridge/br_private.h | |||
@@ -75,6 +75,12 @@ struct bridge_mcast_querier { | |||
75 | struct br_ip addr; | 75 | struct br_ip addr; |
76 | struct net_bridge_port __rcu *port; | 76 | struct net_bridge_port __rcu *port; |
77 | }; | 77 | }; |
78 | |||
79 | /* IGMP/MLD statistics */ | ||
80 | struct bridge_mcast_stats { | ||
81 | struct br_mcast_stats mstats; | ||
82 | struct u64_stats_sync syncp; | ||
83 | }; | ||
78 | #endif | 84 | #endif |
79 | 85 | ||
80 | struct br_vlan_stats { | 86 | struct br_vlan_stats { |
@@ -229,6 +235,7 @@ struct net_bridge_port | |||
229 | struct bridge_mcast_own_query ip6_own_query; | 235 | struct bridge_mcast_own_query ip6_own_query; |
230 | #endif /* IS_ENABLED(CONFIG_IPV6) */ | 236 | #endif /* IS_ENABLED(CONFIG_IPV6) */ |
231 | unsigned char multicast_router; | 237 | unsigned char multicast_router; |
238 | struct bridge_mcast_stats __percpu *mcast_stats; | ||
232 | struct timer_list multicast_router_timer; | 239 | struct timer_list multicast_router_timer; |
233 | struct hlist_head mglist; | 240 | struct hlist_head mglist; |
234 | struct hlist_node rlist; | 241 | struct hlist_node rlist; |
@@ -315,6 +322,7 @@ struct net_bridge | |||
315 | u8 multicast_querier:1; | 322 | u8 multicast_querier:1; |
316 | u8 multicast_query_use_ifaddr:1; | 323 | u8 multicast_query_use_ifaddr:1; |
317 | u8 has_ipv6_addr:1; | 324 | u8 has_ipv6_addr:1; |
325 | u8 multicast_stats_enabled:1; | ||
318 | 326 | ||
319 | u32 hash_elasticity; | 327 | u32 hash_elasticity; |
320 | u32 hash_max; | 328 | u32 hash_max; |
@@ -337,6 +345,7 @@ struct net_bridge | |||
337 | struct bridge_mcast_other_query ip4_other_query; | 345 | struct bridge_mcast_other_query ip4_other_query; |
338 | struct bridge_mcast_own_query ip4_own_query; | 346 | struct bridge_mcast_own_query ip4_own_query; |
339 | struct bridge_mcast_querier ip4_querier; | 347 | struct bridge_mcast_querier ip4_querier; |
348 | struct bridge_mcast_stats __percpu *mcast_stats; | ||
340 | #if IS_ENABLED(CONFIG_IPV6) | 349 | #if IS_ENABLED(CONFIG_IPV6) |
341 | struct bridge_mcast_other_query ip6_other_query; | 350 | struct bridge_mcast_other_query ip6_other_query; |
342 | struct bridge_mcast_own_query ip6_own_query; | 351 | struct bridge_mcast_own_query ip6_own_query; |
@@ -543,7 +552,7 @@ int br_multicast_rcv(struct net_bridge *br, struct net_bridge_port *port, | |||
543 | struct sk_buff *skb, u16 vid); | 552 | struct sk_buff *skb, u16 vid); |
544 | struct net_bridge_mdb_entry *br_mdb_get(struct net_bridge *br, | 553 | struct net_bridge_mdb_entry *br_mdb_get(struct net_bridge *br, |
545 | struct sk_buff *skb, u16 vid); | 554 | struct sk_buff *skb, u16 vid); |
546 | void br_multicast_add_port(struct net_bridge_port *port); | 555 | int br_multicast_add_port(struct net_bridge_port *port); |
547 | void br_multicast_del_port(struct net_bridge_port *port); | 556 | void br_multicast_del_port(struct net_bridge_port *port); |
548 | void br_multicast_enable_port(struct net_bridge_port *port); | 557 | void br_multicast_enable_port(struct net_bridge_port *port); |
549 | void br_multicast_disable_port(struct net_bridge_port *port); | 558 | void br_multicast_disable_port(struct net_bridge_port *port); |
@@ -576,6 +585,12 @@ void br_mdb_notify(struct net_device *dev, struct net_bridge_port *port, | |||
576 | struct br_ip *group, int type, u8 flags); | 585 | struct br_ip *group, int type, u8 flags); |
577 | void br_rtr_notify(struct net_device *dev, struct net_bridge_port *port, | 586 | void br_rtr_notify(struct net_device *dev, struct net_bridge_port *port, |
578 | int type); | 587 | int type); |
588 | void br_multicast_count(struct net_bridge *br, const struct net_bridge_port *p, | ||
589 | __be16 proto, u8 type, u8 dir); | ||
590 | int br_multicast_init_stats(struct net_bridge *br); | ||
591 | void br_multicast_get_stats(const struct net_bridge *br, | ||
592 | const struct net_bridge_port *p, | ||
593 | struct br_mcast_stats *dest); | ||
579 | 594 | ||
580 | #define mlock_dereference(X, br) \ | 595 | #define mlock_dereference(X, br) \ |
581 | rcu_dereference_protected(X, lockdep_is_held(&br->multicast_lock)) | 596 | rcu_dereference_protected(X, lockdep_is_held(&br->multicast_lock)) |
@@ -623,6 +638,11 @@ static inline bool br_multicast_querier_exists(struct net_bridge *br, | |||
623 | return false; | 638 | return false; |
624 | } | 639 | } |
625 | } | 640 | } |
641 | |||
642 | static inline int br_multicast_igmp_type(const struct sk_buff *skb) | ||
643 | { | ||
644 | return BR_INPUT_SKB_CB(skb)->igmp; | ||
645 | } | ||
626 | #else | 646 | #else |
627 | static inline int br_multicast_rcv(struct net_bridge *br, | 647 | static inline int br_multicast_rcv(struct net_bridge *br, |
628 | struct net_bridge_port *port, | 648 | struct net_bridge_port *port, |
@@ -638,8 +658,9 @@ static inline struct net_bridge_mdb_entry *br_mdb_get(struct net_bridge *br, | |||
638 | return NULL; | 658 | return NULL; |
639 | } | 659 | } |
640 | 660 | ||
641 | static inline void br_multicast_add_port(struct net_bridge_port *port) | 661 | static inline int br_multicast_add_port(struct net_bridge_port *port) |
642 | { | 662 | { |
663 | return 0; | ||
643 | } | 664 | } |
644 | 665 | ||
645 | static inline void br_multicast_del_port(struct net_bridge_port *port) | 666 | static inline void br_multicast_del_port(struct net_bridge_port *port) |
@@ -695,6 +716,22 @@ static inline void br_mdb_init(void) | |||
695 | static inline void br_mdb_uninit(void) | 716 | static inline void br_mdb_uninit(void) |
696 | { | 717 | { |
697 | } | 718 | } |
719 | |||
720 | static inline void br_multicast_count(struct net_bridge *br, | ||
721 | const struct net_bridge_port *p, | ||
722 | __be16 proto, u8 type, u8 dir) | ||
723 | { | ||
724 | } | ||
725 | |||
726 | static inline int br_multicast_init_stats(struct net_bridge *br) | ||
727 | { | ||
728 | return 0; | ||
729 | } | ||
730 | |||
731 | static inline int br_multicast_igmp_type(const struct sk_buff *skb) | ||
732 | { | ||
733 | return 0; | ||
734 | } | ||
698 | #endif | 735 | #endif |
699 | 736 | ||
700 | /* br_vlan.c */ | 737 | /* br_vlan.c */ |
diff --git a/net/bridge/br_sysfs_br.c b/net/bridge/br_sysfs_br.c index beb47071e38d..e120307c6e36 100644 --- a/net/bridge/br_sysfs_br.c +++ b/net/bridge/br_sysfs_br.c | |||
@@ -618,6 +618,30 @@ static ssize_t multicast_startup_query_interval_store( | |||
618 | return store_bridge_parm(d, buf, len, set_startup_query_interval); | 618 | return store_bridge_parm(d, buf, len, set_startup_query_interval); |
619 | } | 619 | } |
620 | static DEVICE_ATTR_RW(multicast_startup_query_interval); | 620 | static DEVICE_ATTR_RW(multicast_startup_query_interval); |
621 | |||
622 | static ssize_t multicast_stats_enabled_show(struct device *d, | ||
623 | struct device_attribute *attr, | ||
624 | char *buf) | ||
625 | { | ||
626 | struct net_bridge *br = to_bridge(d); | ||
627 | |||
628 | return sprintf(buf, "%u\n", br->multicast_stats_enabled); | ||
629 | } | ||
630 | |||
631 | static int set_stats_enabled(struct net_bridge *br, unsigned long val) | ||
632 | { | ||
633 | br->multicast_stats_enabled = !!val; | ||
634 | return 0; | ||
635 | } | ||
636 | |||
637 | static ssize_t multicast_stats_enabled_store(struct device *d, | ||
638 | struct device_attribute *attr, | ||
639 | const char *buf, | ||
640 | size_t len) | ||
641 | { | ||
642 | return store_bridge_parm(d, buf, len, set_stats_enabled); | ||
643 | } | ||
644 | static DEVICE_ATTR_RW(multicast_stats_enabled); | ||
621 | #endif | 645 | #endif |
622 | #if IS_ENABLED(CONFIG_BRIDGE_NETFILTER) | 646 | #if IS_ENABLED(CONFIG_BRIDGE_NETFILTER) |
623 | static ssize_t nf_call_iptables_show( | 647 | static ssize_t nf_call_iptables_show( |
@@ -784,6 +808,7 @@ static struct attribute *bridge_attrs[] = { | |||
784 | &dev_attr_multicast_query_interval.attr, | 808 | &dev_attr_multicast_query_interval.attr, |
785 | &dev_attr_multicast_query_response_interval.attr, | 809 | &dev_attr_multicast_query_response_interval.attr, |
786 | &dev_attr_multicast_startup_query_interval.attr, | 810 | &dev_attr_multicast_startup_query_interval.attr, |
811 | &dev_attr_multicast_stats_enabled.attr, | ||
787 | #endif | 812 | #endif |
788 | #if IS_ENABLED(CONFIG_BRIDGE_NETFILTER) | 813 | #if IS_ENABLED(CONFIG_BRIDGE_NETFILTER) |
789 | &dev_attr_nf_call_iptables.attr, | 814 | &dev_attr_nf_call_iptables.attr, |