|
@@ -361,7 +361,8 @@ out:
|
|
|
}
|
|
|
|
|
|
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 igmphdr *ih;
|
|
@@ -411,6 +412,7 @@ static struct sk_buff *br_ip4_multicast_alloc_query(struct net_bridge *br,
|
|
|
|
|
|
skb_set_transport_header(skb, skb->len);
|
|
|
ih = igmp_hdr(skb);
|
|
|
+ *igmp_type = IGMP_HOST_MEMBERSHIP_QUERY;
|
|
|
ih->type = IGMP_HOST_MEMBERSHIP_QUERY;
|
|
|
ih->code = (group ? br->multicast_last_member_interval :
|
|
|
br->multicast_query_response_interval) /
|
|
@@ -428,7 +430,8 @@ out:
|
|
|
|
|
|
#if IS_ENABLED(CONFIG_IPV6)
|
|
|
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 ipv6hdr *ip6h;
|
|
@@ -487,16 +490,17 @@ static struct sk_buff *br_ip6_multicast_alloc_query(struct net_bridge *br,
|
|
|
skb_set_transport_header(skb, skb->len);
|
|
|
mldq = (struct mld_msg *) icmp6_hdr(skb);
|
|
|
|
|
|
- interval = ipv6_addr_any(group) ?
|
|
|
+ interval = ipv6_addr_any(grp) ?
|
|
|
br->multicast_query_response_interval :
|
|
|
br->multicast_last_member_interval;
|
|
|
|
|
|
+ *igmp_type = ICMPV6_MGM_QUERY;
|
|
|
mldq->mld_type = ICMPV6_MGM_QUERY;
|
|
|
mldq->mld_code = 0;
|
|
|
mldq->mld_cksum = 0;
|
|
|
mldq->mld_maxdelay = htons((u16)jiffies_to_msecs(interval));
|
|
|
mldq->mld_reserved = 0;
|
|
|
- mldq->mld_mca = *group;
|
|
|
+ mldq->mld_mca = *grp;
|
|
|
|
|
|
/* checksum */
|
|
|
mldq->mld_cksum = csum_ipv6_magic(&ip6h->saddr, &ip6h->daddr,
|
|
@@ -513,14 +517,16 @@ out:
|
|
|
#endif
|
|
|
|
|
|
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) {
|
|
|
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)
|
|
|
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
|
|
|
}
|
|
|
return NULL;
|
|
@@ -829,18 +835,23 @@ static void __br_multicast_send_query(struct net_bridge *br,
|
|
|
struct br_ip *ip)
|
|
|
{
|
|
|
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)
|
|
|
return;
|
|
|
|
|
|
if (port) {
|
|
|
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,
|
|
|
dev_net(port->dev), NULL, skb, NULL, skb->dev,
|
|
|
br_dev_queue_push_xmit);
|
|
|
} else {
|
|
|
br_multicast_select_own_querier(br, ip, skb);
|
|
|
+ br_multicast_count(br, port, skb->protocol, igmp_type,
|
|
|
+ BR_MCAST_DIR_RX);
|
|
|
netif_rx(skb);
|
|
|
}
|
|
|
}
|
|
@@ -918,7 +929,7 @@ static void br_ip6_multicast_port_query_expired(unsigned long data)
|
|
|
}
|
|
|
#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;
|
|
|
|
|
@@ -930,6 +941,11 @@ void br_multicast_add_port(struct net_bridge_port *port)
|
|
|
setup_timer(&port->ip6_own_query.timer,
|
|
|
br_ip6_multicast_port_query_expired, (unsigned long)port);
|
|
|
#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)
|
|
@@ -944,6 +960,7 @@ void br_multicast_del_port(struct net_bridge_port *port)
|
|
|
br_multicast_del_pg(br, pg);
|
|
|
spin_unlock_bh(&br->multicast_lock);
|
|
|
del_timer_sync(&port->multicast_router_timer);
|
|
|
+ free_percpu(port->mcast_stats);
|
|
|
}
|
|
|
|
|
|
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,
|
|
|
}
|
|
|
#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,
|
|
|
struct net_bridge_port *port,
|
|
|
struct sk_buff *skb,
|
|
@@ -1599,11 +1649,12 @@ static int br_multicast_ipv4_rcv(struct net_bridge *br,
|
|
|
BR_INPUT_SKB_CB(skb)->mrouters_only = 1;
|
|
|
return 0;
|
|
|
} else if (err < 0) {
|
|
|
+ br_multicast_err_count(br, port, skb->protocol);
|
|
|
return err;
|
|
|
}
|
|
|
|
|
|
- BR_INPUT_SKB_CB(skb)->igmp = 1;
|
|
|
ih = igmp_hdr(skb);
|
|
|
+ BR_INPUT_SKB_CB(skb)->igmp = ih->type;
|
|
|
|
|
|
switch (ih->type) {
|
|
|
case IGMP_HOST_MEMBERSHIP_REPORT:
|
|
@@ -1625,6 +1676,9 @@ static int br_multicast_ipv4_rcv(struct net_bridge *br,
|
|
|
if (skb_trimmed && skb_trimmed != skb)
|
|
|
kfree_skb(skb_trimmed);
|
|
|
|
|
|
+ br_multicast_count(br, port, skb->protocol, BR_INPUT_SKB_CB(skb)->igmp,
|
|
|
+ BR_MCAST_DIR_RX);
|
|
|
+
|
|
|
return err;
|
|
|
}
|
|
|
|
|
@@ -1645,11 +1699,12 @@ static int br_multicast_ipv6_rcv(struct net_bridge *br,
|
|
|
BR_INPUT_SKB_CB(skb)->mrouters_only = 1;
|
|
|
return 0;
|
|
|
} else if (err < 0) {
|
|
|
+ br_multicast_err_count(br, port, skb->protocol);
|
|
|
return err;
|
|
|
}
|
|
|
|
|
|
- BR_INPUT_SKB_CB(skb)->igmp = 1;
|
|
|
mld = (struct mld_msg *)skb_transport_header(skb);
|
|
|
+ BR_INPUT_SKB_CB(skb)->igmp = mld->mld_type;
|
|
|
|
|
|
switch (mld->mld_type) {
|
|
|
case ICMPV6_MGM_REPORT:
|
|
@@ -1670,6 +1725,9 @@ static int br_multicast_ipv6_rcv(struct net_bridge *br,
|
|
|
if (skb_trimmed && skb_trimmed != skb)
|
|
|
kfree_skb(skb_trimmed);
|
|
|
|
|
|
+ br_multicast_count(br, port, skb->protocol, BR_INPUT_SKB_CB(skb)->igmp,
|
|
|
+ BR_MCAST_DIR_RX);
|
|
|
+
|
|
|
return err;
|
|
|
}
|
|
|
#endif
|
|
@@ -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,
|
|
|
struct sk_buff *skb, u16 vid)
|
|
|
{
|
|
|
+ int ret = 0;
|
|
|
+
|
|
|
BR_INPUT_SKB_CB(skb)->igmp = 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,
|
|
|
|
|
|
switch (skb->protocol) {
|
|
|
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)
|
|
|
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
|
|
|
}
|
|
|
|
|
|
- return 0;
|
|
|
+ return ret;
|
|
|
}
|
|
|
|
|
|
static void br_multicast_query_expired(struct net_bridge *br,
|
|
@@ -1831,6 +1893,8 @@ void br_multicast_dev_del(struct net_bridge *br)
|
|
|
|
|
|
out:
|
|
|
spin_unlock_bh(&br->multicast_lock);
|
|
|
+
|
|
|
+ free_percpu(br->mcast_stats);
|
|
|
}
|
|
|
|
|
|
int br_multicast_set_router(struct net_bridge *br, unsigned long val)
|
|
@@ -2185,3 +2249,128 @@ unlock:
|
|
|
return ret;
|
|
|
}
|
|
|
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))
|
|
|
+ return;
|
|
|
+
|
|
|
+ br_mcast_stats_add(stats, proto, type, dir);
|
|
|
+}
|
|
|
+
|
|
|
+int br_multicast_init_stats(struct net_bridge *br)
|
|
|
+{
|
|
|
+ br->mcast_stats = netdev_alloc_pcpu_stats(struct bridge_mcast_stats);
|
|
|
+ if (!br->mcast_stats)
|
|
|
+ return -ENOMEM;
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static void mcast_stats_add_dir(u64 *dst, u64 *src)
|
|
|
+{
|
|
|
+ dst[BR_MCAST_DIR_RX] += src[BR_MCAST_DIR_RX];
|
|
|
+ dst[BR_MCAST_DIR_TX] += src[BR_MCAST_DIR_TX];
|
|
|
+}
|
|
|
+
|
|
|
+void br_multicast_get_stats(const struct net_bridge *br,
|
|
|
+ const struct net_bridge_port *p,
|
|
|
+ struct br_mcast_stats *dest)
|
|
|
+{
|
|
|
+ struct bridge_mcast_stats __percpu *stats;
|
|
|
+ struct br_mcast_stats tdst;
|
|
|
+ int i;
|
|
|
+
|
|
|
+ memset(dest, 0, sizeof(*dest));
|
|
|
+ if (p)
|
|
|
+ stats = p->mcast_stats;
|
|
|
+ else
|
|
|
+ stats = br->mcast_stats;
|
|
|
+ if (WARN_ON(!stats))
|
|
|
+ return;
|
|
|
+
|
|
|
+ memset(&tdst, 0, sizeof(tdst));
|
|
|
+ for_each_possible_cpu(i) {
|
|
|
+ struct bridge_mcast_stats *cpu_stats = per_cpu_ptr(stats, i);
|
|
|
+ struct br_mcast_stats temp;
|
|
|
+ unsigned int start;
|
|
|
+
|
|
|
+ do {
|
|
|
+ start = u64_stats_fetch_begin_irq(&cpu_stats->syncp);
|
|
|
+ memcpy(&temp, &cpu_stats->mstats, sizeof(temp));
|
|
|
+ } while (u64_stats_fetch_retry_irq(&cpu_stats->syncp, start));
|
|
|
+
|
|
|
+ mcast_stats_add_dir(tdst.igmp_queries, temp.igmp_queries);
|
|
|
+ mcast_stats_add_dir(tdst.igmp_leaves, temp.igmp_leaves);
|
|
|
+ mcast_stats_add_dir(tdst.igmp_v1reports, temp.igmp_v1reports);
|
|
|
+ mcast_stats_add_dir(tdst.igmp_v2reports, temp.igmp_v2reports);
|
|
|
+ mcast_stats_add_dir(tdst.igmp_v3reports, temp.igmp_v3reports);
|
|
|
+ tdst.igmp_parse_errors += temp.igmp_parse_errors;
|
|
|
+
|
|
|
+ mcast_stats_add_dir(tdst.mld_queries, temp.mld_queries);
|
|
|
+ mcast_stats_add_dir(tdst.mld_leaves, temp.mld_leaves);
|
|
|
+ mcast_stats_add_dir(tdst.mld_v1reports, temp.mld_v1reports);
|
|
|
+ mcast_stats_add_dir(tdst.mld_v2reports, temp.mld_v2reports);
|
|
|
+ tdst.mld_parse_errors += temp.mld_parse_errors;
|
|
|
+ }
|
|
|
+ memcpy(dest, &tdst, sizeof(*dest));
|
|
|
+}
|