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: Nikolay Aleksandrov <nikolay@cumulusnetworks.com>
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
Nikolay Aleksandrov 2020-09-07 12:56:14 +03:00 committed by Jakub Kicinski
parent d6c33d67a8
commit 0436862e41
2 changed files with 137 additions and 22 deletions

View 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;

View 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,