OSDN Git Service

net: bridge: mcast: support for IGMPv3/MLDv2 ALLOW_NEW_SOURCES report
authorNikolay Aleksandrov <nikolay@cumulusnetworks.com>
Mon, 7 Sep 2020 09:56:14 +0000 (12:56 +0300)
committerJakub Kicinski <kuba@kernel.org>
Mon, 7 Sep 2020 20:16:35 +0000 (13:16 -0700)
This patch adds handling for the ALLOW_NEW_SOURCES IGMPv3/MLDv2 report
types and limits them only when multicast_igmp_version == 3 or
multicast_mld_version == 2 respectively. Now that IGMPv3/MLDv2 handling
functions will be managing timers we need to delay their activation, thus
a new argument is added which controls if the timer should be updated.
We also disable host IGMPv3/MLDv2 handling as it's not yet implemented and
could cause inconsistent group state, the host can only join a group as
EXCLUDE {} or leave it.

v4: rename update_timer to igmpv2_mldv1 and use the passed value from
    br_multicast_add_group's callers
v3: Add IPv6/MLDv2 support

Signed-off-by: Nikolay Aleksandrov <nikolay@cumulusnetworks.com>
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
net/bridge/br_multicast.c
net/bridge/br_private.h

index ba2ce87..98600a0 100644 (file)
@@ -787,7 +787,8 @@ static int br_multicast_add_group(struct net_bridge *br,
                                  struct net_bridge_port *port,
                                  struct br_ip *group,
                                  const unsigned char *src,
-                                 u8 filter_mode)
+                                 u8 filter_mode,
+                                 bool igmpv2_mldv1)
 {
        struct net_bridge_port_group __rcu **pp;
        struct net_bridge_port_group *p;
@@ -826,7 +827,8 @@ static int br_multicast_add_group(struct net_bridge *br,
        br_mdb_notify(br->dev, mp, p, RTM_NEWMDB);
 
 found:
-       mod_timer(&p->timer, now + br->multicast_membership_interval);
+       if (igmpv2_mldv1)
+               mod_timer(&p->timer, now + br->multicast_membership_interval);
 
 out:
        err = 0;
@@ -855,7 +857,8 @@ static int br_ip4_multicast_add_group(struct net_bridge *br,
        br_group.vid = vid;
        filter_mode = igmpv2 ? MCAST_EXCLUDE : MCAST_INCLUDE;
 
-       return br_multicast_add_group(br, port, &br_group, src, filter_mode);
+       return br_multicast_add_group(br, port, &br_group, src, filter_mode,
+                                     igmpv2);
 }
 
 #if IS_ENABLED(CONFIG_IPV6)
@@ -878,7 +881,8 @@ static int br_ip6_multicast_add_group(struct net_bridge *br,
        br_group.vid = vid;
        filter_mode = mldv1 ? MCAST_EXCLUDE : MCAST_INCLUDE;
 
-       return br_multicast_add_group(br, port, &br_group, src, filter_mode);
+       return br_multicast_add_group(br, port, &br_group, src, filter_mode,
+                                     mldv1);
 }
 #endif
 
@@ -1225,20 +1229,72 @@ void br_multicast_disable_port(struct net_bridge_port *port)
        spin_unlock(&br->multicast_lock);
 }
 
+/* State          Msg type      New state                Actions
+ * INCLUDE (A)    IS_IN (B)     INCLUDE (A+B)            (B)=GMI
+ * INCLUDE (A)    ALLOW (B)     INCLUDE (A+B)            (B)=GMI
+ * EXCLUDE (X,Y)  ALLOW (A)     EXCLUDE (X+A,Y-A)        (A)=GMI
+ */
+static bool br_multicast_isinc_allow(struct net_bridge_port_group *pg,
+                                    void *srcs, u32 nsrcs, size_t src_size)
+{
+       struct net_bridge *br = pg->port->br;
+       struct net_bridge_group_src *ent;
+       unsigned long now = jiffies;
+       bool changed = false;
+       struct br_ip src_ip;
+       u32 src_idx;
+
+       memset(&src_ip, 0, sizeof(src_ip));
+       src_ip.proto = pg->addr.proto;
+       for (src_idx = 0; src_idx < nsrcs; src_idx++) {
+               memcpy(&src_ip.u, srcs, src_size);
+               ent = br_multicast_find_group_src(pg, &src_ip);
+               if (!ent) {
+                       ent = br_multicast_new_group_src(pg, &src_ip);
+                       if (ent)
+                               changed = true;
+               }
+
+               if (ent)
+                       mod_timer(&ent->timer, now + br_multicast_gmi(br));
+               srcs += src_size;
+       }
+
+       return changed;
+}
+
+static struct net_bridge_port_group *
+br_multicast_find_port(struct net_bridge_mdb_entry *mp,
+                      struct net_bridge_port *p,
+                      const unsigned char *src)
+{
+       struct net_bridge_port_group *pg;
+       struct net_bridge *br = mp->br;
+
+       for (pg = mlock_dereference(mp->ports, br);
+            pg;
+            pg = mlock_dereference(pg->next, br))
+               if (br_port_group_equal(pg, p, src))
+                       return pg;
+
+       return NULL;
+}
+
 static int br_ip4_multicast_igmp3_report(struct net_bridge *br,
                                         struct net_bridge_port *port,
                                         struct sk_buff *skb,
                                         u16 vid)
 {
+       bool igmpv2 = br->multicast_igmp_version == 2;
+       struct net_bridge_mdb_entry *mdst;
+       struct net_bridge_port_group *pg;
        const unsigned char *src;
        struct igmpv3_report *ih;
        struct igmpv3_grec *grec;
-       int i;
-       int len;
-       int num;
-       int type;
-       int err = 0;
+       int i, len, num, type;
+       bool changed = false;
        __be32 group;
+       int err = 0;
        u16 nsrcs;
 
        ih = igmpv3_report_hdr(skb);
@@ -1259,7 +1315,6 @@ static int br_ip4_multicast_igmp3_report(struct net_bridge *br,
                if (!ip_mc_may_pull(skb, len))
                        return -EINVAL;
 
-               /* We treat this as an IGMPv2 report for now. */
                switch (type) {
                case IGMPV3_MODE_IS_INCLUDE:
                case IGMPV3_MODE_IS_EXCLUDE:
@@ -1274,16 +1329,42 @@ static int br_ip4_multicast_igmp3_report(struct net_bridge *br,
                }
 
                src = eth_hdr(skb)->h_source;
-               if ((type == IGMPV3_CHANGE_TO_INCLUDE ||
-                    type == IGMPV3_MODE_IS_INCLUDE) &&
-                   nsrcs == 0) {
-                       br_ip4_multicast_leave_group(br, port, group, vid, src);
+               if (nsrcs == 0 &&
+                   (type == IGMPV3_CHANGE_TO_INCLUDE ||
+                    type == IGMPV3_MODE_IS_INCLUDE)) {
+                       if (!port || igmpv2) {
+                               br_ip4_multicast_leave_group(br, port, group, vid, src);
+                               continue;
+                       }
                } else {
                        err = br_ip4_multicast_add_group(br, port, group, vid,
-                                                        src, true);
+                                                        src, igmpv2);
                        if (err)
                                break;
                }
+
+               if (!port || igmpv2)
+                       continue;
+
+               spin_lock_bh(&br->multicast_lock);
+               mdst = br_mdb_ip4_get(br, group, vid);
+               if (!mdst)
+                       goto unlock_continue;
+               pg = br_multicast_find_port(mdst, port, src);
+               if (!pg || (pg->flags & MDB_PG_FLAGS_PERMANENT))
+                       goto unlock_continue;
+               /* reload grec */
+               grec = (void *)(skb->data + len - sizeof(*grec) - (nsrcs * 4));
+               switch (type) {
+               case IGMPV3_ALLOW_NEW_SOURCES:
+                       changed = br_multicast_isinc_allow(pg, grec->grec_src,
+                                                          nsrcs, sizeof(__be32));
+                       break;
+               }
+               if (changed)
+                       br_mdb_notify(br->dev, mdst, pg, RTM_NEWMDB);
+unlock_continue:
+               spin_unlock_bh(&br->multicast_lock);
        }
 
        return err;
@@ -1295,14 +1376,16 @@ static int br_ip6_multicast_mld2_report(struct net_bridge *br,
                                        struct sk_buff *skb,
                                        u16 vid)
 {
+       bool mldv1 = br->multicast_mld_version == 1;
+       struct net_bridge_mdb_entry *mdst;
+       struct net_bridge_port_group *pg;
        unsigned int nsrcs_offset;
        const unsigned char *src;
        struct icmp6hdr *icmp6h;
        struct mld2_grec *grec;
        unsigned int grec_len;
-       int i;
-       int len;
-       int num;
+       bool changed = false;
+       int i, len, num;
        int err = 0;
 
        if (!ipv6_mc_may_pull(skb, sizeof(*icmp6h)))
@@ -1336,7 +1419,6 @@ static int br_ip6_multicast_mld2_report(struct net_bridge *br,
                grec = (struct mld2_grec *)(skb->data + len);
                len += grec_len;
 
-               /* We treat these as MLDv1 reports for now. */
                switch (grec->grec_type) {
                case MLD2_MODE_IS_INCLUDE:
                case MLD2_MODE_IS_EXCLUDE:
@@ -1354,15 +1436,41 @@ static int br_ip6_multicast_mld2_report(struct net_bridge *br,
                if ((grec->grec_type == MLD2_CHANGE_TO_INCLUDE ||
                     grec->grec_type == MLD2_MODE_IS_INCLUDE) &&
                    nsrcs == 0) {
-                       br_ip6_multicast_leave_group(br, port, &grec->grec_mca,
-                                                    vid, src);
+                       if (!port || mldv1) {
+                               br_ip6_multicast_leave_group(br, port,
+                                                            &grec->grec_mca,
+                                                            vid, src);
+                               continue;
+                       }
                } else {
                        err = br_ip6_multicast_add_group(br, port,
                                                         &grec->grec_mca, vid,
-                                                        src, true);
+                                                        src, mldv1);
                        if (err)
                                break;
                }
+
+               if (!port || mldv1)
+                       continue;
+
+               spin_lock_bh(&br->multicast_lock);
+               mdst = br_mdb_ip6_get(br, &grec->grec_mca, vid);
+               if (!mdst)
+                       goto unlock_continue;
+               pg = br_multicast_find_port(mdst, port, src);
+               if (!pg || (pg->flags & MDB_PG_FLAGS_PERMANENT))
+                       goto unlock_continue;
+               switch (grec->grec_type) {
+               case MLD2_ALLOW_NEW_SOURCES:
+                       changed = br_multicast_isinc_allow(pg, grec->grec_src,
+                                                          nsrcs,
+                                                          sizeof(struct in6_addr));
+                       break;
+               }
+               if (changed)
+                       br_mdb_notify(br->dev, mdst, pg, RTM_NEWMDB);
+unlock_continue:
+               spin_unlock_bh(&br->multicast_lock);
        }
 
        return err;
index b2a2260..fb35a73 100644 (file)
@@ -876,6 +876,13 @@ static inline unsigned long br_multicast_lmqt(const struct net_bridge *br)
        return br->multicast_last_member_interval *
               br->multicast_last_member_count;
 }
+
+static inline unsigned long br_multicast_gmi(const struct net_bridge *br)
+{
+       /* use the RFC default of 2 for QRV */
+       return 2 * br->multicast_query_interval +
+              br->multicast_query_response_interval;
+}
 #else
 static inline int br_multicast_rcv(struct net_bridge *br,
                                   struct net_bridge_port *port,