Commit 5225d5f5 authored by Jakub Kicinski's avatar Jakub Kicinski
Browse files

Merge branch 'net-bridge-multicast-add-initial-eht-support'

Nikolay Aleksandrov says:

====================
net: bridge: multicast: add initial EHT support

This set adds explicit host tracking support for IGMPv3/MLDv2. The
already present per-port fast leave flag is used to enable it since that
is the primary goal of EHT, to track a group and its S,Gs usage per-host
and when left without any interested hosts delete them before the standard
timers. The EHT code is pretty self-contained and not enabled by default.
There is no new uAPI added, all of the functionality is currently hidden
behind the fast leave flag. In the future that will change (more below).
The host tracking uses two new sets per port group: one having an entry for
each host which contains that host's view of the group (source list and
filter mode), and one set which contains an entry for each source having
an internal set which contains an entry for each host that has reported
an interest for that source. RB trees are used for all sets so they're
compact when not used and fast when we need to do lookups.
To illustrate it:
 [ bridge port group ]
  ` [ host set (rb) ]
   ` [ host entry with a list of sources and filter mode ]
  ` [ source set (rb) ]
   ` [ source entry ]
    ` [ source host set (rb) ]
     ` [ source host entry with a timer ]

The number of tracked sources per host is limited to the maximum total
number of S,G entries per port group - PG_SRC_ENT_LIMIT (currently 32).
The number of hosts is unlimited, I think the argument that a local
attacker can exhaust the memory/cause high CPU usage can be applied to
fdb entries as well which are unlimited. In the future if needed we can
add an option to limit these, but I don't think it's necessary for a
start. All of the new sets are protected by the bridge's multicast lock.
I'm pretty sure we'll be changing the cases and improving the
convergence time in the future, but this seems like a good start.

Patch breakdown:
 patch 1 -  4: minor cleanups and preparations for EHT
 patch      5: adds the new structures which will be used in the
               following patches
 patch      6: adds support to create, destroy and lookup host entries
 patch      7: adds support to create, delete and lokup source set entries
 patch      8: adds a host "delete" function which is just a host's
               source list flush since that would automatically delete
               the host
 patch 9 - 10: add support for handling all IGMPv3/MLDv2 report types
               more information can be found in the individual patches
 patch     11: optmizes a specific TO_INCLUDE use-case with host timeouts
 patch     12: handles per-host filter mode changing (include <-> exclude)
 patch     13: pulls out block group deletion since now it can be
               deleted in both filter modes
 patch     14: marks deletions done due to fast leave

Future plans:
 - export host information
 - add an option to reduce queries
 - add an option to limit the number of host entries
 - tune more fast leave cases for quicker convergence

By the way I think this is the first open-source EHT implementation, I
couldn't find any while researching it. :)
====================

Link: https://lore.kernel.org/r/20210120145203.1109140-1-razor@blackwall.org


Signed-off-by: default avatarJakub Kicinski <kuba@kernel.org>
parents b9046e88 d5a10222
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -18,7 +18,7 @@ br_netfilter-y := br_netfilter_hooks.o
br_netfilter-$(subst m,y,$(CONFIG_IPV6)) += br_netfilter_ipv6.o
obj-$(CONFIG_BRIDGE_NETFILTER) += br_netfilter.o

bridge-$(CONFIG_BRIDGE_IGMP_SNOOPING) += br_multicast.o br_mdb.o
bridge-$(CONFIG_BRIDGE_IGMP_SNOOPING) += br_multicast.o br_mdb.o br_multicast_eht.o

bridge-$(CONFIG_BRIDGE_VLAN_FILTERING) += br_vlan.o br_vlan_tunnel.o br_vlan_options.o

+165 −89
Original line number Diff line number Diff line
@@ -33,6 +33,7 @@
#endif

#include "br_private.h"
#include "br_private_mcast_eht.h"

static const struct rhashtable_params br_mdb_rht_params = {
	.head_offset = offsetof(struct net_bridge_mdb_entry, rhnode),
@@ -441,7 +442,8 @@ static void br_multicast_fwd_src_add(struct net_bridge_group_src *src)
	br_multicast_sg_add_exclude_ports(star_mp, sg);
}

static void br_multicast_fwd_src_remove(struct net_bridge_group_src *src)
static void br_multicast_fwd_src_remove(struct net_bridge_group_src *src,
					bool fastleave)
{
	struct net_bridge_port_group *p, *pg = src->pg;
	struct net_bridge_port_group __rcu **pp;
@@ -466,6 +468,8 @@ static void br_multicast_fwd_src_remove(struct net_bridge_group_src *src)
		    (p->flags & MDB_PG_FLAGS_PERMANENT))
			break;

		if (fastleave)
			p->flags |= MDB_PG_FLAGS_FAST_LEAVE;
		br_multicast_del_pg(mp, p, pp);
		break;
	}
@@ -559,11 +563,12 @@ static void br_multicast_destroy_group_src(struct net_bridge_mcast_gc *gc)
	kfree_rcu(src, rcu);
}

static void br_multicast_del_group_src(struct net_bridge_group_src *src)
void br_multicast_del_group_src(struct net_bridge_group_src *src,
				bool fastleave)
{
	struct net_bridge *br = src->pg->key.port->br;

	br_multicast_fwd_src_remove(src);
	br_multicast_fwd_src_remove(src, fastleave);
	hlist_del_init_rcu(&src->node);
	src->pg->src_ents--;
	hlist_add_head(&src->mcast_gc.gc_node, &br->mcast_gc_list);
@@ -593,8 +598,9 @@ void br_multicast_del_pg(struct net_bridge_mdb_entry *mp,

	rcu_assign_pointer(*pp, pg->next);
	hlist_del_init(&pg->mglist);
	br_multicast_eht_clean_sets(pg);
	hlist_for_each_entry_safe(ent, tmp, &pg->src_list, node)
		br_multicast_del_group_src(ent);
		br_multicast_del_group_src(ent, false);
	br_mdb_notify(br->dev, mp, pg, RTM_DELMDB);
	if (!br_multicast_is_star_g(&mp->addr)) {
		rhashtable_remove_fast(&br->sg_port_tbl, &pg->rhnode,
@@ -651,7 +657,7 @@ static void br_multicast_port_group_expired(struct timer_list *t)
	pg->filter_mode = MCAST_INCLUDE;
	hlist_for_each_entry_safe(src_ent, tmp, &pg->src_list, node) {
		if (!timer_pending(&src_ent->timer)) {
			br_multicast_del_group_src(src_ent);
			br_multicast_del_group_src(src_ent, false);
			changed = true;
		}
	}
@@ -1078,7 +1084,7 @@ static void br_multicast_group_src_expired(struct timer_list *t)

	pg = src->pg;
	if (pg->filter_mode == MCAST_INCLUDE) {
		br_multicast_del_group_src(src);
		br_multicast_del_group_src(src, false);
		if (!hlist_empty(&pg->src_list))
			goto out;
		br_multicast_find_del_pg(br, pg);
@@ -1090,7 +1096,7 @@ static void br_multicast_group_src_expired(struct timer_list *t)
	spin_unlock(&br->multicast_lock);
}

static struct net_bridge_group_src *
struct net_bridge_group_src *
br_multicast_find_group_src(struct net_bridge_port_group *pg, struct br_ip *ip)
{
	struct net_bridge_group_src *ent;
@@ -1172,6 +1178,8 @@ struct net_bridge_port_group *br_multicast_new_port_group(
	p->flags = flags;
	p->filter_mode = filter_mode;
	p->rt_protocol = rt_protocol;
	p->eht_host_tree = RB_ROOT;
	p->eht_set_tree = RB_ROOT;
	p->mcast_gc.destroy = br_multicast_destroy_port_group;
	INIT_HLIST_HEAD(&p->src_list);

@@ -1700,7 +1708,7 @@ static int __grp_src_delete_marked(struct net_bridge_port_group *pg)

	hlist_for_each_entry_safe(ent, tmp, &pg->src_list, node)
		if (ent->flags & BR_SGRP_F_DELETE) {
			br_multicast_del_group_src(ent);
			br_multicast_del_group_src(ent, false);
			deleted++;
		}

@@ -1799,8 +1807,9 @@ static void __grp_send_query_and_rexmit(struct net_bridge_port_group *pg)
 * 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)
static bool br_multicast_isinc_allow(struct net_bridge_port_group *pg, void *h_addr,
				     void *srcs, u32 nsrcs, size_t addr_size,
				     int grec_type)
{
	struct net_bridge *br = pg->key.port->br;
	struct net_bridge_group_src *ent;
@@ -1812,7 +1821,7 @@ static bool br_multicast_isinc_allow(struct net_bridge_port_group *pg,
	memset(&src_ip, 0, sizeof(src_ip));
	src_ip.proto = pg->key.addr.proto;
	for (src_idx = 0; src_idx < nsrcs; src_idx++) {
		memcpy(&src_ip.src, srcs, src_size);
		memcpy(&src_ip.src, srcs + (src_idx * addr_size), addr_size);
		ent = br_multicast_find_group_src(pg, &src_ip);
		if (!ent) {
			ent = br_multicast_new_group_src(pg, &src_ip);
@@ -1822,9 +1831,11 @@ static bool br_multicast_isinc_allow(struct net_bridge_port_group *pg,

		if (ent)
			__grp_src_mod_timer(ent, now + br_multicast_gmi(br));
		srcs += src_size;
	}

	if (br_multicast_eht_handle(pg, h_addr, srcs, nsrcs, addr_size, grec_type))
		changed = true;

	return changed;
}

@@ -1833,8 +1844,9 @@ static bool br_multicast_isinc_allow(struct net_bridge_port_group *pg,
 *                                                       Delete (A-B)
 *                                                       Group Timer=GMI
 */
static void __grp_src_isexc_incl(struct net_bridge_port_group *pg,
				 void *srcs, u32 nsrcs, size_t src_size)
static void __grp_src_isexc_incl(struct net_bridge_port_group *pg, void *h_addr,
				 void *srcs, u32 nsrcs, size_t addr_size,
				 int grec_type)
{
	struct net_bridge_group_src *ent;
	struct br_ip src_ip;
@@ -1846,7 +1858,7 @@ static void __grp_src_isexc_incl(struct net_bridge_port_group *pg,
	memset(&src_ip, 0, sizeof(src_ip));
	src_ip.proto = pg->key.addr.proto;
	for (src_idx = 0; src_idx < nsrcs; src_idx++) {
		memcpy(&src_ip.src, srcs, src_size);
		memcpy(&src_ip.src, srcs + (src_idx * addr_size), addr_size);
		ent = br_multicast_find_group_src(pg, &src_ip);
		if (ent)
			ent->flags &= ~BR_SGRP_F_DELETE;
@@ -1854,9 +1866,10 @@ static void __grp_src_isexc_incl(struct net_bridge_port_group *pg,
			ent = br_multicast_new_group_src(pg, &src_ip);
		if (ent)
			br_multicast_fwd_src_handle(ent);
		srcs += src_size;
	}

	br_multicast_eht_handle(pg, h_addr, srcs, nsrcs, addr_size, grec_type);

	__grp_src_delete_marked(pg);
}

@@ -1866,8 +1879,9 @@ static void __grp_src_isexc_incl(struct net_bridge_port_group *pg,
 *                                                       Delete (Y-A)
 *                                                       Group Timer=GMI
 */
static bool __grp_src_isexc_excl(struct net_bridge_port_group *pg,
				 void *srcs, u32 nsrcs, size_t src_size)
static bool __grp_src_isexc_excl(struct net_bridge_port_group *pg, void *h_addr,
				 void *srcs, u32 nsrcs, size_t addr_size,
				 int grec_type)
{
	struct net_bridge *br = pg->key.port->br;
	struct net_bridge_group_src *ent;
@@ -1882,7 +1896,7 @@ static bool __grp_src_isexc_excl(struct net_bridge_port_group *pg,
	memset(&src_ip, 0, sizeof(src_ip));
	src_ip.proto = pg->key.addr.proto;
	for (src_idx = 0; src_idx < nsrcs; src_idx++) {
		memcpy(&src_ip.src, srcs, src_size);
		memcpy(&src_ip.src, srcs + (src_idx * addr_size), addr_size);
		ent = br_multicast_find_group_src(pg, &src_ip);
		if (ent) {
			ent->flags &= ~BR_SGRP_F_DELETE;
@@ -1894,29 +1908,34 @@ static bool __grp_src_isexc_excl(struct net_bridge_port_group *pg,
				changed = true;
			}
		}
		srcs += src_size;
	}

	if (br_multicast_eht_handle(pg, h_addr, srcs, nsrcs, addr_size, grec_type))
		changed = true;

	if (__grp_src_delete_marked(pg))
		changed = true;

	return changed;
}

static bool br_multicast_isexc(struct net_bridge_port_group *pg,
			       void *srcs, u32 nsrcs, size_t src_size)
static bool br_multicast_isexc(struct net_bridge_port_group *pg, void *h_addr,
			       void *srcs, u32 nsrcs, size_t addr_size,
			       int grec_type)
{
	struct net_bridge *br = pg->key.port->br;
	bool changed = false;

	switch (pg->filter_mode) {
	case MCAST_INCLUDE:
		__grp_src_isexc_incl(pg, srcs, nsrcs, src_size);
		__grp_src_isexc_incl(pg, h_addr, srcs, nsrcs, addr_size,
				     grec_type);
		br_multicast_star_g_handle_mode(pg, MCAST_EXCLUDE);
		changed = true;
		break;
	case MCAST_EXCLUDE:
		changed = __grp_src_isexc_excl(pg, srcs, nsrcs, src_size);
		changed = __grp_src_isexc_excl(pg, h_addr, srcs, nsrcs, addr_size,
					       grec_type);
		break;
	}

@@ -1930,8 +1949,9 @@ static bool br_multicast_isexc(struct net_bridge_port_group *pg,
 * INCLUDE (A)    TO_IN (B)     INCLUDE (A+B)            (B)=GMI
 *                                                       Send Q(G,A-B)
 */
static bool __grp_src_toin_incl(struct net_bridge_port_group *pg,
				void *srcs, u32 nsrcs, size_t src_size)
static bool __grp_src_toin_incl(struct net_bridge_port_group *pg, void *h_addr,
				void *srcs, u32 nsrcs, size_t addr_size,
				int grec_type)
{
	struct net_bridge *br = pg->key.port->br;
	u32 src_idx, to_send = pg->src_ents;
@@ -1946,7 +1966,7 @@ static bool __grp_src_toin_incl(struct net_bridge_port_group *pg,
	memset(&src_ip, 0, sizeof(src_ip));
	src_ip.proto = pg->key.addr.proto;
	for (src_idx = 0; src_idx < nsrcs; src_idx++) {
		memcpy(&src_ip.src, srcs, src_size);
		memcpy(&src_ip.src, srcs + (src_idx * addr_size), addr_size);
		ent = br_multicast_find_group_src(pg, &src_ip);
		if (ent) {
			ent->flags &= ~BR_SGRP_F_SEND;
@@ -1958,9 +1978,11 @@ static bool __grp_src_toin_incl(struct net_bridge_port_group *pg,
		}
		if (ent)
			__grp_src_mod_timer(ent, now + br_multicast_gmi(br));
		srcs += src_size;
	}

	if (br_multicast_eht_handle(pg, h_addr, srcs, nsrcs, addr_size, grec_type))
		changed = true;

	if (to_send)
		__grp_src_query_marked_and_rexmit(pg);

@@ -1972,8 +1994,9 @@ static bool __grp_src_toin_incl(struct net_bridge_port_group *pg,
 *                                                       Send Q(G,X-A)
 *                                                       Send Q(G)
 */
static bool __grp_src_toin_excl(struct net_bridge_port_group *pg,
				void *srcs, u32 nsrcs, size_t src_size)
static bool __grp_src_toin_excl(struct net_bridge_port_group *pg, void *h_addr,
				void *srcs, u32 nsrcs, size_t addr_size,
				int grec_type)
{
	struct net_bridge *br = pg->key.port->br;
	u32 src_idx, to_send = pg->src_ents;
@@ -1989,7 +2012,7 @@ static bool __grp_src_toin_excl(struct net_bridge_port_group *pg,
	memset(&src_ip, 0, sizeof(src_ip));
	src_ip.proto = pg->key.addr.proto;
	for (src_idx = 0; src_idx < nsrcs; src_idx++) {
		memcpy(&src_ip.src, srcs, src_size);
		memcpy(&src_ip.src, srcs + (src_idx * addr_size), addr_size);
		ent = br_multicast_find_group_src(pg, &src_ip);
		if (ent) {
			if (timer_pending(&ent->timer)) {
@@ -2003,9 +2026,11 @@ static bool __grp_src_toin_excl(struct net_bridge_port_group *pg,
		}
		if (ent)
			__grp_src_mod_timer(ent, now + br_multicast_gmi(br));
		srcs += src_size;
	}

	if (br_multicast_eht_handle(pg, h_addr, srcs, nsrcs, addr_size, grec_type))
		changed = true;

	if (to_send)
		__grp_src_query_marked_and_rexmit(pg);

@@ -2014,20 +2039,32 @@ static bool __grp_src_toin_excl(struct net_bridge_port_group *pg,
	return changed;
}

static bool br_multicast_toin(struct net_bridge_port_group *pg,
			      void *srcs, u32 nsrcs, size_t src_size)
static bool br_multicast_toin(struct net_bridge_port_group *pg, void *h_addr,
			      void *srcs, u32 nsrcs, size_t addr_size,
			      int grec_type)
{
	bool changed = false;

	switch (pg->filter_mode) {
	case MCAST_INCLUDE:
		changed = __grp_src_toin_incl(pg, srcs, nsrcs, src_size);
		changed = __grp_src_toin_incl(pg, h_addr, srcs, nsrcs, addr_size,
					      grec_type);
		break;
	case MCAST_EXCLUDE:
		changed = __grp_src_toin_excl(pg, srcs, nsrcs, src_size);
		changed = __grp_src_toin_excl(pg, h_addr, srcs, nsrcs, addr_size,
					      grec_type);
		break;
	}

	if (br_multicast_eht_should_del_pg(pg)) {
		pg->flags |= MDB_PG_FLAGS_FAST_LEAVE;
		br_multicast_find_del_pg(pg->key.port->br, pg);
		/* a notification has already been sent and we shouldn't
		 * access pg after the delete so we have to return false
		 */
		changed = false;
	}

	return changed;
}

@@ -2037,8 +2074,9 @@ static bool br_multicast_toin(struct net_bridge_port_group *pg,
 *                                                       Send Q(G,A*B)
 *                                                       Group Timer=GMI
 */
static void __grp_src_toex_incl(struct net_bridge_port_group *pg,
				void *srcs, u32 nsrcs, size_t src_size)
static void __grp_src_toex_incl(struct net_bridge_port_group *pg, void *h_addr,
				void *srcs, u32 nsrcs, size_t addr_size,
				int grec_type)
{
	struct net_bridge_group_src *ent;
	u32 src_idx, to_send = 0;
@@ -2050,7 +2088,7 @@ static void __grp_src_toex_incl(struct net_bridge_port_group *pg,
	memset(&src_ip, 0, sizeof(src_ip));
	src_ip.proto = pg->key.addr.proto;
	for (src_idx = 0; src_idx < nsrcs; src_idx++) {
		memcpy(&src_ip.src, srcs, src_size);
		memcpy(&src_ip.src, srcs + (src_idx * addr_size), addr_size);
		ent = br_multicast_find_group_src(pg, &src_ip);
		if (ent) {
			ent->flags = (ent->flags & ~BR_SGRP_F_DELETE) |
@@ -2061,9 +2099,10 @@ static void __grp_src_toex_incl(struct net_bridge_port_group *pg,
		}
		if (ent)
			br_multicast_fwd_src_handle(ent);
		srcs += src_size;
	}

	br_multicast_eht_handle(pg, h_addr, srcs, nsrcs, addr_size, grec_type);

	__grp_src_delete_marked(pg);
	if (to_send)
		__grp_src_query_marked_and_rexmit(pg);
@@ -2076,8 +2115,9 @@ static void __grp_src_toex_incl(struct net_bridge_port_group *pg,
 *                                                       Send Q(G,A-Y)
 *                                                       Group Timer=GMI
 */
static bool __grp_src_toex_excl(struct net_bridge_port_group *pg,
				void *srcs, u32 nsrcs, size_t src_size)
static bool __grp_src_toex_excl(struct net_bridge_port_group *pg, void *h_addr,
				void *srcs, u32 nsrcs, size_t addr_size,
				int grec_type)
{
	struct net_bridge_group_src *ent;
	u32 src_idx, to_send = 0;
@@ -2090,7 +2130,7 @@ static bool __grp_src_toex_excl(struct net_bridge_port_group *pg,
	memset(&src_ip, 0, sizeof(src_ip));
	src_ip.proto = pg->key.addr.proto;
	for (src_idx = 0; src_idx < nsrcs; src_idx++) {
		memcpy(&src_ip.src, srcs, src_size);
		memcpy(&src_ip.src, srcs + (src_idx * addr_size), addr_size);
		ent = br_multicast_find_group_src(pg, &src_ip);
		if (ent) {
			ent->flags &= ~BR_SGRP_F_DELETE;
@@ -2105,9 +2145,11 @@ static bool __grp_src_toex_excl(struct net_bridge_port_group *pg,
			ent->flags |= BR_SGRP_F_SEND;
			to_send++;
		}
		srcs += src_size;
	}

	if (br_multicast_eht_handle(pg, h_addr, srcs, nsrcs, addr_size, grec_type))
		changed = true;

	if (__grp_src_delete_marked(pg))
		changed = true;
	if (to_send)
@@ -2116,20 +2158,23 @@ static bool __grp_src_toex_excl(struct net_bridge_port_group *pg,
	return changed;
}

static bool br_multicast_toex(struct net_bridge_port_group *pg,
			      void *srcs, u32 nsrcs, size_t src_size)
static bool br_multicast_toex(struct net_bridge_port_group *pg, void *h_addr,
			      void *srcs, u32 nsrcs, size_t addr_size,
			      int grec_type)
{
	struct net_bridge *br = pg->key.port->br;
	bool changed = false;

	switch (pg->filter_mode) {
	case MCAST_INCLUDE:
		__grp_src_toex_incl(pg, srcs, nsrcs, src_size);
		__grp_src_toex_incl(pg, h_addr, srcs, nsrcs, addr_size,
				    grec_type);
		br_multicast_star_g_handle_mode(pg, MCAST_EXCLUDE);
		changed = true;
		break;
	case MCAST_EXCLUDE:
		changed = __grp_src_toex_excl(pg, srcs, nsrcs, src_size);
		changed = __grp_src_toex_excl(pg, h_addr, srcs, nsrcs, addr_size,
					      grec_type);
		break;
	}

@@ -2142,11 +2187,12 @@ static bool br_multicast_toex(struct net_bridge_port_group *pg,
/* State          Msg type      New state                Actions
 * INCLUDE (A)    BLOCK (B)     INCLUDE (A)              Send Q(G,A*B)
 */
static void __grp_src_block_incl(struct net_bridge_port_group *pg,
				 void *srcs, u32 nsrcs, size_t src_size)
static bool __grp_src_block_incl(struct net_bridge_port_group *pg, void *h_addr,
				 void *srcs, u32 nsrcs, size_t addr_size, int grec_type)
{
	struct net_bridge_group_src *ent;
	u32 src_idx, to_send = 0;
	bool changed = false;
	struct br_ip src_ip;

	hlist_for_each_entry(ent, &pg->src_list, node)
@@ -2155,28 +2201,29 @@ static void __grp_src_block_incl(struct net_bridge_port_group *pg,
	memset(&src_ip, 0, sizeof(src_ip));
	src_ip.proto = pg->key.addr.proto;
	for (src_idx = 0; src_idx < nsrcs; src_idx++) {
		memcpy(&src_ip.src, srcs, src_size);
		memcpy(&src_ip.src, srcs + (src_idx * addr_size), addr_size);
		ent = br_multicast_find_group_src(pg, &src_ip);
		if (ent) {
			ent->flags |= BR_SGRP_F_SEND;
			to_send++;
		}
		srcs += src_size;
	}

	if (br_multicast_eht_handle(pg, h_addr, srcs, nsrcs, addr_size, grec_type))
		changed = true;

	if (to_send)
		__grp_src_query_marked_and_rexmit(pg);

	if (pg->filter_mode == MCAST_INCLUDE && hlist_empty(&pg->src_list))
		br_multicast_find_del_pg(pg->key.port->br, pg);
	return changed;
}

/* State          Msg type      New state                Actions
 * EXCLUDE (X,Y)  BLOCK (A)     EXCLUDE (X+(A-Y),Y)      (A-X-Y)=Group Timer
 *                                                       Send Q(G,A-Y)
 */
static bool __grp_src_block_excl(struct net_bridge_port_group *pg,
				 void *srcs, u32 nsrcs, size_t src_size)
static bool __grp_src_block_excl(struct net_bridge_port_group *pg, void *h_addr,
				 void *srcs, u32 nsrcs, size_t addr_size, int grec_type)
{
	struct net_bridge_group_src *ent;
	u32 src_idx, to_send = 0;
@@ -2189,7 +2236,7 @@ static bool __grp_src_block_excl(struct net_bridge_port_group *pg,
	memset(&src_ip, 0, sizeof(src_ip));
	src_ip.proto = pg->key.addr.proto;
	for (src_idx = 0; src_idx < nsrcs; src_idx++) {
		memcpy(&src_ip.src, srcs, src_size);
		memcpy(&src_ip.src, srcs + (src_idx * addr_size), addr_size);
		ent = br_multicast_find_group_src(pg, &src_ip);
		if (!ent) {
			ent = br_multicast_new_group_src(pg, &src_ip);
@@ -2202,29 +2249,44 @@ static bool __grp_src_block_excl(struct net_bridge_port_group *pg,
			ent->flags |= BR_SGRP_F_SEND;
			to_send++;
		}
		srcs += src_size;
	}

	if (br_multicast_eht_handle(pg, h_addr, srcs, nsrcs, addr_size, grec_type))
		changed = true;

	if (to_send)
		__grp_src_query_marked_and_rexmit(pg);

	return changed;
}

static bool br_multicast_block(struct net_bridge_port_group *pg,
			       void *srcs, u32 nsrcs, size_t src_size)
static bool br_multicast_block(struct net_bridge_port_group *pg, void *h_addr,
			       void *srcs, u32 nsrcs, size_t addr_size, int grec_type)
{
	bool changed = false;

	switch (pg->filter_mode) {
	case MCAST_INCLUDE:
		__grp_src_block_incl(pg, srcs, nsrcs, src_size);
		changed = __grp_src_block_incl(pg, h_addr, srcs, nsrcs, addr_size,
					       grec_type);
		break;
	case MCAST_EXCLUDE:
		changed = __grp_src_block_excl(pg, srcs, nsrcs, src_size);
		changed = __grp_src_block_excl(pg, h_addr, srcs, nsrcs, addr_size,
					       grec_type);
		break;
	}

	if ((pg->filter_mode == MCAST_INCLUDE && hlist_empty(&pg->src_list)) ||
	    br_multicast_eht_should_del_pg(pg)) {
		if (br_multicast_eht_should_del_pg(pg))
			pg->flags |= MDB_PG_FLAGS_FAST_LEAVE;
		br_multicast_find_del_pg(pg->key.port->br, pg);
		/* a notification has already been sent and we shouldn't
		 * access pg after the delete so we have to return false
		 */
		changed = false;
	}

	return changed;
}

@@ -2257,8 +2319,8 @@ static int br_ip4_multicast_igmp3_report(struct net_bridge *br,
	struct igmpv3_report *ih;
	struct igmpv3_grec *grec;
	int i, len, num, type;
	__be32 group, *h_addr;
	bool changed = false;
	__be32 group;
	int err = 0;
	u16 nsrcs;

@@ -2318,32 +2380,33 @@ static int br_ip4_multicast_igmp3_report(struct net_bridge *br,
		pg = br_multicast_find_port(mdst, port, src);
		if (!pg || (pg->flags & MDB_PG_FLAGS_PERMANENT))
			goto unlock_continue;
		/* reload grec */
		/* reload grec and host addr */
		grec = (void *)(skb->data + len - sizeof(*grec) - (nsrcs * 4));
		h_addr = &ip_hdr(skb)->saddr;
		switch (type) {
		case IGMPV3_ALLOW_NEW_SOURCES:
			changed = br_multicast_isinc_allow(pg, grec->grec_src,
							   nsrcs, sizeof(__be32));
			changed = br_multicast_isinc_allow(pg, h_addr, grec->grec_src,
							   nsrcs, sizeof(__be32), type);
			break;
		case IGMPV3_MODE_IS_INCLUDE:
			changed = br_multicast_isinc_allow(pg, grec->grec_src, nsrcs,
							   sizeof(__be32));
			changed = br_multicast_isinc_allow(pg, h_addr, grec->grec_src,
							   nsrcs, sizeof(__be32), type);
			break;
		case IGMPV3_MODE_IS_EXCLUDE:
			changed = br_multicast_isexc(pg, grec->grec_src, nsrcs,
						     sizeof(__be32));
			changed = br_multicast_isexc(pg, h_addr, grec->grec_src,
						     nsrcs, sizeof(__be32), type);
			break;
		case IGMPV3_CHANGE_TO_INCLUDE:
			changed = br_multicast_toin(pg, grec->grec_src, nsrcs,
						    sizeof(__be32));
			changed = br_multicast_toin(pg, h_addr, grec->grec_src,
						    nsrcs, sizeof(__be32), type);
			break;
		case IGMPV3_CHANGE_TO_EXCLUDE:
			changed = br_multicast_toex(pg, grec->grec_src, nsrcs,
						    sizeof(__be32));
			changed = br_multicast_toex(pg, h_addr, grec->grec_src,
						    nsrcs, sizeof(__be32), type);
			break;
		case IGMPV3_BLOCK_OLD_SOURCES:
			changed = br_multicast_block(pg, grec->grec_src, nsrcs,
						     sizeof(__be32));
			changed = br_multicast_block(pg, h_addr, grec->grec_src,
						     nsrcs, sizeof(__be32), type);
			break;
		}
		if (changed)
@@ -2367,6 +2430,7 @@ static int br_ip6_multicast_mld2_report(struct net_bridge *br,
	unsigned int nsrcs_offset;
	const unsigned char *src;
	struct icmp6hdr *icmp6h;
	struct in6_addr *h_addr;
	struct mld2_grec *grec;
	unsigned int grec_len;
	bool changed = false;
@@ -2445,31 +2509,43 @@ static int br_ip6_multicast_mld2_report(struct net_bridge *br,
		pg = br_multicast_find_port(mdst, port, src);
		if (!pg || (pg->flags & MDB_PG_FLAGS_PERMANENT))
			goto unlock_continue;
		h_addr = &ipv6_hdr(skb)->saddr;
		switch (grec->grec_type) {
		case MLD2_ALLOW_NEW_SOURCES:
			changed = br_multicast_isinc_allow(pg, grec->grec_src,
							   nsrcs,
							   sizeof(struct in6_addr));
			changed = br_multicast_isinc_allow(pg, h_addr,
							   grec->grec_src, nsrcs,
							   sizeof(struct in6_addr),
							   grec->grec_type);
			break;
		case MLD2_MODE_IS_INCLUDE:
			changed = br_multicast_isinc_allow(pg, grec->grec_src, nsrcs,
							   sizeof(struct in6_addr));
			changed = br_multicast_isinc_allow(pg, h_addr,
							   grec->grec_src, nsrcs,
							   sizeof(struct in6_addr),
							   grec->grec_type);
			break;
		case MLD2_MODE_IS_EXCLUDE:
			changed = br_multicast_isexc(pg, grec->grec_src, nsrcs,
						     sizeof(struct in6_addr));
			changed = br_multicast_isexc(pg, h_addr,
						     grec->grec_src, nsrcs,
						     sizeof(struct in6_addr),
						     grec->grec_type);
			break;
		case MLD2_CHANGE_TO_INCLUDE:
			changed = br_multicast_toin(pg, grec->grec_src, nsrcs,
						    sizeof(struct in6_addr));
			changed = br_multicast_toin(pg, h_addr,
						    grec->grec_src, nsrcs,
						    sizeof(struct in6_addr),
						    grec->grec_type);
			break;
		case MLD2_CHANGE_TO_EXCLUDE:
			changed = br_multicast_toex(pg, grec->grec_src, nsrcs,
						    sizeof(struct in6_addr));
			changed = br_multicast_toex(pg, h_addr,
						    grec->grec_src, nsrcs,
						    sizeof(struct in6_addr),
						    grec->grec_type);
			break;
		case MLD2_BLOCK_OLD_SOURCES:
			changed = br_multicast_block(pg, grec->grec_src, nsrcs,
						     sizeof(struct in6_addr));
			changed = br_multicast_block(pg, h_addr,
						     grec->grec_src, nsrcs,
						     sizeof(struct in6_addr),
						     grec->grec_type);
			break;
		}
		if (changed)
+856 −0

File added.

Preview size limit exceeded, changes collapsed.

+6 −0
Original line number Diff line number Diff line
@@ -252,6 +252,8 @@ struct net_bridge_port_group {
	struct timer_list		timer;
	struct timer_list		rexmit_timer;
	struct hlist_node		mglist;
	struct rb_root			eht_set_tree;
	struct rb_root			eht_host_tree;

	struct rhash_head		rhnode;
	struct net_bridge_mcast_gc	mcast_gc;
@@ -846,6 +848,10 @@ void br_multicast_star_g_handle_mode(struct net_bridge_port_group *pg,
				     u8 filter_mode);
void br_multicast_sg_add_exclude_ports(struct net_bridge_mdb_entry *star_mp,
				       struct net_bridge_port_group *sg);
struct net_bridge_group_src *
br_multicast_find_group_src(struct net_bridge_port_group *pg, struct br_ip *ip);
void br_multicast_del_group_src(struct net_bridge_group_src *src,
				bool fastleave);

static inline bool br_group_is_l2(const struct br_ip *group)
{
+65 −0
Original line number Diff line number Diff line
/* SPDX-License-Identifier: GPL-2.0-or-later
 * Copyright (c) 2020, Nikolay Aleksandrov <nikolay@nvidia.com>
 */
#ifndef _BR_PRIVATE_MCAST_EHT_H_
#define _BR_PRIVATE_MCAST_EHT_H_

union net_bridge_eht_addr {
	__be32				ip4;
#if IS_ENABLED(CONFIG_IPV6)
	struct in6_addr			ip6;
#endif
};

/* single host's list of set entries and filter_mode */
struct net_bridge_group_eht_host {
	struct rb_node			rb_node;

	union net_bridge_eht_addr	h_addr;
	struct hlist_head		set_entries;
	unsigned int			num_entries;
	unsigned char			filter_mode;
	struct net_bridge_port_group	*pg;
};

/* (host, src entry) added to a per-src set and host's list */
struct net_bridge_group_eht_set_entry {
	struct rb_node			rb_node;
	struct hlist_node		host_list;

	union net_bridge_eht_addr	h_addr;
	struct timer_list		timer;
	struct net_bridge		*br;
	struct net_bridge_group_eht_set	*eht_set;
	struct net_bridge_group_eht_host *h_parent;
	struct net_bridge_mcast_gc	mcast_gc;
};

/* per-src set */
struct net_bridge_group_eht_set {
	struct rb_node			rb_node;

	union net_bridge_eht_addr	src_addr;
	struct rb_root			entry_tree;
	struct timer_list		timer;
	struct net_bridge_port_group	*pg;
	struct net_bridge		*br;
	struct net_bridge_mcast_gc	mcast_gc;
};

void br_multicast_eht_clean_sets(struct net_bridge_port_group *pg);
bool br_multicast_eht_handle(struct net_bridge_port_group *pg,
			     void *h_addr,
			     void *srcs,
			     u32 nsrcs,
			     size_t addr_size,
			     int grec_type);

static inline bool
br_multicast_eht_should_del_pg(const struct net_bridge_port_group *pg)
{
	return !!((pg->key.port->flags & BR_MULTICAST_FAST_LEAVE) &&
		  RB_EMPTY_ROOT(&pg->eht_host_tree));
}

#endif /* _BR_PRIVATE_MCAST_EHT_H_ */