Commit 0436862e authored by Nikolay Aleksandrov's avatar Nikolay Aleksandrov Committed by Jakub Kicinski
Browse files

net: bridge: mcast: support for IGMPv3/MLDv2 ALLOW_NEW_SOURCES report



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: default avatarNikolay Aleksandrov <nikolay@cumulusnetworks.com>
Signed-off-by: default avatarJakub Kicinski <kuba@kernel.org>
parent d6c33d67
Loading
Loading
Loading
Loading
+130 −22
Original line number Diff line number Diff line
@@ -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,6 +827,7 @@ static int br_multicast_add_group(struct net_bridge *br,
	br_mdb_notify(br->dev, mp, p, RTM_NEWMDB);

found:
	if (igmpv2_mldv1)
		mod_timer(&p->timer, now + br->multicast_membership_interval);

out:
@@ -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) {
		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,
			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;
+7 −0
Original line number Diff line number Diff line
@@ -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,