Merge remote-tracking branch 'spi/fix/spidev' into spi-linus
[cascardo/linux.git] / net / bridge / br_multicast.c
index 4384414..c5fea93 100644 (file)
@@ -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, 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, 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)
@@ -1121,7 +1138,7 @@ static int br_ip6_multicast_mld2_report(struct net_bridge *br,
                } else {
                        err = br_ip6_multicast_add_group(br, port,
                                                         &grec->grec_mca, vid);
-                       if (!err)
+                       if (err)
                                break;
                }
        }
@@ -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, 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, 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,154 @@ unlock:
        return ret;
 }
 EXPORT_SYMBOL_GPL(br_multicast_has_querier_adjacent);
+
+static void br_mcast_stats_add(struct bridge_mcast_stats __percpu *stats,
+                              const struct sk_buff *skb, u8 type, u8 dir)
+{
+       struct bridge_mcast_stats *pstats = this_cpu_ptr(stats);
+       __be16 proto = skb->protocol;
+       unsigned int t_len;
+
+       u64_stats_update_begin(&pstats->syncp);
+       switch (proto) {
+       case htons(ETH_P_IP):
+               t_len = ntohs(ip_hdr(skb)->tot_len) - ip_hdrlen(skb);
+               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:
+                       if (t_len != sizeof(struct igmphdr)) {
+                               pstats->mstats.igmp_v3queries[dir]++;
+                       } else {
+                               unsigned int offset = skb_transport_offset(skb);
+                               struct igmphdr *ih, _ihdr;
+
+                               ih = skb_header_pointer(skb, offset,
+                                                       sizeof(_ihdr), &_ihdr);
+                               if (!ih)
+                                       break;
+                               if (!ih->code)
+                                       pstats->mstats.igmp_v1queries[dir]++;
+                               else
+                                       pstats->mstats.igmp_v2queries[dir]++;
+                       }
+                       break;
+               case IGMP_HOST_LEAVE_MESSAGE:
+                       pstats->mstats.igmp_leaves[dir]++;
+                       break;
+               }
+               break;
+#if IS_ENABLED(CONFIG_IPV6)
+       case htons(ETH_P_IPV6):
+               t_len = ntohs(ipv6_hdr(skb)->payload_len) +
+                       sizeof(struct ipv6hdr);
+               t_len -= skb_network_header_len(skb);
+               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:
+                       if (t_len != sizeof(struct mld_msg))
+                               pstats->mstats.mld_v2queries[dir]++;
+                       else
+                               pstats->mstats.mld_v1queries[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,
+                       const struct sk_buff *skb, 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, skb, 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_v1queries, temp.igmp_v1queries);
+               mcast_stats_add_dir(tdst.igmp_v2queries, temp.igmp_v2queries);
+               mcast_stats_add_dir(tdst.igmp_v3queries, temp.igmp_v3queries);
+               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_v1queries, temp.mld_v1queries);
+               mcast_stats_add_dir(tdst.mld_v2queries, temp.mld_v2queries);
+               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));
+}