All of lore.kernel.org
 help / color / mirror / Atom feed
* [RFC PATCH v2] net: bridge: igmp: Extend IGMP query to be per vlan
@ 2021-01-12 13:59 ` Horatiu Vultur
  0 siblings, 0 replies; 16+ messages in thread
From: Horatiu Vultur @ 2021-01-12 13:59 UTC (permalink / raw)
  To: davem, kuba, roopa, nikolay, allan.nielsen, netdev, linux-kernel, bridge
  Cc: Horatiu Vultur

Based on the comments of the previous version, we started to work on a
new version, so it would be possible to enable/disable queries per vlan.
This is still work in progress and there are plenty of things that are
not implemented and tested:
- ipv6 support
- the fast path needs to be improved
- currently it is possible only to enable/disable the queries per vlan,
  all the other configurations are global
- toggling vlan_filtering is not tested
- remove duplicated information
- etc...

But there are few things that are working like:
- sending queries per vlan
- stop sending queries if there is a better querier per vlan
- when ports are added/removed from vlan
- etc...

We were wondering if this what you had in mind when you proposed to have
this per vlan? Or we are completely off? Or we should fix some of the
issues that I mentioned, before you can see more clearly the direction?

Signed-off-by: Horatiu Vultur <horatiu.vultur@microchip.com>
---
 include/uapi/linux/if_link.h |   1 +
 net/bridge/br_device.c       |   2 +-
 net/bridge/br_input.c        |   2 +-
 net/bridge/br_multicast.c    | 505 ++++++++++++++++++++++++++++++-----
 net/bridge/br_netlink.c      |   9 +-
 net/bridge/br_private.h      |  90 ++++++-
 net/bridge/br_sysfs_br.c     |  31 ++-
 net/bridge/br_vlan.c         |   3 +
 8 files changed, 560 insertions(+), 83 deletions(-)

diff --git a/include/uapi/linux/if_link.h b/include/uapi/linux/if_link.h
index 82708c6db432..11ec1d45c24e 100644
--- a/include/uapi/linux/if_link.h
+++ b/include/uapi/linux/if_link.h
@@ -472,6 +472,7 @@ enum {
 	IFLA_BR_MCAST_MLD_VERSION,
 	IFLA_BR_VLAN_STATS_PER_PORT,
 	IFLA_BR_MULTI_BOOLOPT,
+	IFLA_BR_MCAST_QUERIER_VID,
 	__IFLA_BR_MAX,
 };
 
diff --git a/net/bridge/br_device.c b/net/bridge/br_device.c
index 3f2f06b4dd27..aca4e8074a8f 100644
--- a/net/bridge/br_device.c
+++ b/net/bridge/br_device.c
@@ -89,7 +89,7 @@ netdev_tx_t br_dev_xmit(struct sk_buff *skb, struct net_device *dev)
 
 		mdst = br_mdb_get(br, skb, vid);
 		if ((mdst || BR_INPUT_SKB_CB_MROUTERS_ONLY(skb)) &&
-		    br_multicast_querier_exists(br, eth_hdr(skb), mdst))
+		    br_multicast_querier_exists(br, eth_hdr(skb), mdst, vid))
 			br_multicast_flood(mdst, skb, false, true);
 		else
 			br_flood(br, skb, BR_PKT_MULTICAST, false, true);
diff --git a/net/bridge/br_input.c b/net/bridge/br_input.c
index 222285d9dae2..03e445af6c1f 100644
--- a/net/bridge/br_input.c
+++ b/net/bridge/br_input.c
@@ -130,7 +130,7 @@ int br_handle_frame_finish(struct net *net, struct sock *sk, struct sk_buff *skb
 	case BR_PKT_MULTICAST:
 		mdst = br_mdb_get(br, skb, vid);
 		if ((mdst || BR_INPUT_SKB_CB_MROUTERS_ONLY(skb)) &&
-		    br_multicast_querier_exists(br, eth_hdr(skb), mdst)) {
+		    br_multicast_querier_exists(br, eth_hdr(skb), mdst, vid)) {
 			if ((mdst && mdst->host_joined) ||
 			    br_multicast_is_router(br)) {
 				local_rcv = true;
diff --git a/net/bridge/br_multicast.c b/net/bridge/br_multicast.c
index 257ac4e25f6d..b4fac25101e4 100644
--- a/net/bridge/br_multicast.c
+++ b/net/bridge/br_multicast.c
@@ -48,8 +48,11 @@ static const struct rhashtable_params br_sg_port_rht_params = {
 	.automatic_shrinking = true,
 };
 
+static void br_ip4_multicast_query_expired(struct timer_list *t);
+static void br_ip4_multicast_querier_expired(struct timer_list *t);
 static void br_multicast_start_querier(struct net_bridge *br,
-				       struct bridge_mcast_own_query *query);
+				       struct bridge_mcast_own_query *query,
+				       u16 vid);
 static void br_multicast_add_router(struct net_bridge *br,
 				    struct net_bridge_port *port);
 static void br_ip4_multicast_leave_group(struct net_bridge *br,
@@ -87,6 +90,112 @@ br_sg_port_find(struct net_bridge *br,
 				      br_sg_port_rht_params);
 }
 
+static void br_mcast_del_other_query(struct bridge_mcast_other_query *query)
+{
+	del_timer_sync(&query->timer);
+	list_del(&query->list);
+	kfree(query);
+}
+
+static struct bridge_mcast_other_query *
+br_mcast_add_other_query(struct list_head *list, u16 vid,
+			 void (*callback)(struct timer_list *t))
+{
+	struct bridge_mcast_other_query *query;
+
+	query = kzalloc(sizeof(*query), GFP_KERNEL);
+	if (!query)
+		return NULL;
+
+	query->vid = vid;
+	timer_setup(&query->timer, callback, 0);
+
+	list_add(&query->list, list);
+
+	return query;
+}
+
+static void br_mcast_del_own_query(struct bridge_mcast_own_query *query)
+{
+	del_timer_sync(&query->timer);
+	list_del(&query->list);
+	kfree(query);
+}
+
+static struct bridge_mcast_own_query *
+br_mcast_add_own_query(struct list_head *list, u16 vid,
+		       void (*callback)(struct timer_list *t))
+{
+	struct bridge_mcast_own_query *query;
+
+	query = kzalloc(sizeof(*query), GFP_KERNEL);
+	if (!query)
+		return NULL;
+
+	query->vid = vid;
+	timer_setup(&query->timer, callback, 0);
+
+	list_add(&query->list, list);
+
+	return query;
+}
+
+static void br_mcast_add_queries(struct net_bridge *br, u16 vid)
+{
+	struct bridge_mcast_other_query *other;
+	struct bridge_mcast_own_query *own;
+
+	own = br_mcast_find_own_query(&br->ip4_own_queries, vid);
+	if (!own) {
+		own = br_mcast_add_own_query(&br->ip4_own_queries, vid,
+					     br_ip4_multicast_query_expired);
+		own->ip4 = true;
+		own->br = br;
+	}
+
+	other = br_mcast_find_other_query(&br->ip4_other_queries, vid);
+	if (!other) {
+		other = br_mcast_add_other_query(&br->ip4_other_queries, vid,
+						 br_ip4_multicast_querier_expired);
+		other->br = br;
+	}
+}
+
+struct bridge_mcast_own_query *
+br_mcast_find_own_query(struct list_head *list, u16 vid)
+{
+	struct bridge_mcast_own_query *query = NULL;
+
+	list_for_each_entry(query, list, list)
+		if (query->vid == vid)
+			return query;
+
+	return NULL;
+}
+
+struct bridge_mcast_other_query *
+br_mcast_find_other_query(struct list_head *list, u16 vid)
+{
+	struct bridge_mcast_other_query *query = NULL;
+
+	list_for_each_entry(query, list, list)
+		if (query->vid == vid)
+			return query;
+
+	return NULL;
+}
+
+bool br_mcast_exist_own_query(struct net_bridge *br)
+{
+	struct bridge_mcast_own_query *query = NULL;
+
+	list_for_each_entry(query, &br->ip4_own_queries, list)
+		if (query->enabled)
+			return true;
+
+	return false;
+}
+
 static struct net_bridge_mdb_entry *br_mdb_ip_get_rcu(struct net_bridge *br,
 						      struct br_ip *dst)
 {
@@ -688,7 +797,8 @@ static struct sk_buff *br_ip4_multicast_alloc_query(struct net_bridge *br,
 						    __be32 ip_dst, __be32 group,
 						    bool with_srcs, bool over_lmqt,
 						    u8 sflag, u8 *igmp_type,
-						    bool *need_rexmit)
+						    bool *need_rexmit,
+						    u16 vid)
 {
 	struct net_bridge_port *p = pg ? pg->key.port : NULL;
 	struct net_bridge_group_src *ent;
@@ -724,6 +834,9 @@ static struct sk_buff *br_ip4_multicast_alloc_query(struct net_bridge *br,
 	}
 
 	pkt_size = sizeof(*eth) + sizeof(*iph) + 4 + igmp_hdr_size;
+	if (br_vlan_enabled(br->dev) && vid != 0)
+		pkt_size += 4;
+
 	if ((p && pkt_size > p->dev->mtu) ||
 	    pkt_size > br->dev->mtu)
 		return NULL;
@@ -732,6 +845,9 @@ static struct sk_buff *br_ip4_multicast_alloc_query(struct net_bridge *br,
 	if (!skb)
 		goto out;
 
+	if (br_vlan_enabled(br->dev) && vid != 0)
+		__vlan_hwaccel_put_tag(skb, htons(ETH_P_8021Q), vid);
+
 	skb->protocol = htons(ETH_P_IP);
 
 	skb_reset_mac_header(skb);
@@ -1008,7 +1124,7 @@ static struct sk_buff *br_multicast_alloc_query(struct net_bridge *br,
 						    ip4_dst, group->dst.ip4,
 						    with_srcs, over_lmqt,
 						    sflag, igmp_type,
-						    need_rexmit);
+						    need_rexmit, group->vid);
 #if IS_ENABLED(CONFIG_IPV6)
 	case htons(ETH_P_IPV6): {
 		struct in6_addr ip6_dst;
@@ -1398,7 +1514,7 @@ static void br_multicast_querier_expired(struct net_bridge *br,
 	if (!netif_running(br->dev) || !br_opt_get(br, BROPT_MULTICAST_ENABLED))
 		goto out;
 
-	br_multicast_start_querier(br, query);
+	br_multicast_start_querier(br, query, query->vid);
 
 out:
 	spin_unlock(&br->multicast_lock);
@@ -1406,9 +1522,14 @@ static void br_multicast_querier_expired(struct net_bridge *br,
 
 static void br_ip4_multicast_querier_expired(struct timer_list *t)
 {
-	struct net_bridge *br = from_timer(br, t, ip4_other_query.timer);
+	struct bridge_mcast_other_query *other_query =
+		from_timer(other_query, t, timer);
+	struct net_bridge *br = other_query->br;
+	struct bridge_mcast_own_query *query;
 
-	br_multicast_querier_expired(br, &br->ip4_own_query);
+	list_for_each_entry(query, &br->ip4_own_queries, list)
+		if (query->enabled && query->vid == other_query->vid)
+			br_multicast_querier_expired(br, query);
 }
 
 #if IS_ENABLED(CONFIG_IPV6)
@@ -1477,19 +1598,22 @@ static void br_multicast_send_query(struct net_bridge *br,
 				    struct bridge_mcast_own_query *own_query)
 {
 	struct bridge_mcast_other_query *other_query = NULL;
+	struct net_bridge_vlan_group *vg;
 	struct br_ip br_group;
 	unsigned long time;
 
 	if (!netif_running(br->dev) ||
-	    !br_opt_get(br, BROPT_MULTICAST_ENABLED) ||
-	    !br_opt_get(br, BROPT_MULTICAST_QUERIER))
+	    !br_opt_get(br, BROPT_MULTICAST_ENABLED))
 		return;
 
-	memset(&br_group.dst, 0, sizeof(br_group.dst));
+	if (!own_query->enabled)
+		return;
 
-	if (port ? (own_query == &port->ip4_own_query) :
-		   (own_query == &br->ip4_own_query)) {
-		other_query = &br->ip4_other_query;
+	memset(&br_group, 0, sizeof(br_group));
+
+	if (own_query->ip4) {
+		other_query = br_mcast_find_other_query(&br->ip4_other_queries,
+							own_query->vid);
 		br_group.proto = htons(ETH_P_IP);
 #if IS_ENABLED(CONFIG_IPV6)
 	} else {
@@ -1501,6 +1625,12 @@ static void br_multicast_send_query(struct net_bridge *br,
 	if (!other_query || timer_pending(&other_query->timer))
 		return;
 
+	br_group.vid = own_query->vid;
+
+	vg =  port ? nbp_vlan_group(port) : br_vlan_group(br);
+	if (vg->pvid == own_query->vid)
+		br_group.vid = 0;
+
 	__br_multicast_send_query(br, port, NULL, NULL, &br_group, false, 0,
 				  NULL);
 
@@ -1533,9 +1663,10 @@ br_multicast_port_query_expired(struct net_bridge_port *port,
 
 static void br_ip4_multicast_port_query_expired(struct timer_list *t)
 {
-	struct net_bridge_port *port = from_timer(port, t, ip4_own_query.timer);
+	struct bridge_mcast_own_query *query = from_timer(query, t, timer);
+	struct net_bridge_port *port = query->port;
 
-	br_multicast_port_query_expired(port, &port->ip4_own_query);
+	br_multicast_port_query_expired(port, query);
 }
 
 #if IS_ENABLED(CONFIG_IPV6)
@@ -1551,17 +1682,23 @@ static void br_multicast_port_group_rexmit(struct timer_list *t)
 {
 	struct net_bridge_port_group *pg = from_timer(pg, t, rexmit_timer);
 	struct bridge_mcast_other_query *other_query = NULL;
+	struct bridge_mcast_own_query *own_query = NULL;
 	struct net_bridge *br = pg->key.port->br;
+	u16 vid = pg->key.addr.vid;
 	bool need_rexmit = false;
 
 	spin_lock(&br->multicast_lock);
+	own_query = br_mcast_find_own_query(&pg->key.port->ip4_own_queries,
+					    vid);
+
 	if (!netif_running(br->dev) || hlist_unhashed(&pg->mglist) ||
 	    !br_opt_get(br, BROPT_MULTICAST_ENABLED) ||
-	    !br_opt_get(br, BROPT_MULTICAST_QUERIER))
+	    !own_query || !own_query->enabled)
 		goto out;
 
 	if (pg->key.addr.proto == htons(ETH_P_IP))
-		other_query = &br->ip4_other_query;
+		other_query = br_mcast_find_other_query(&br->ip4_other_queries,
+							vid);
 #if IS_ENABLED(CONFIG_IPV6)
 	else
 		other_query = &br->ip6_other_query;
@@ -1603,8 +1740,7 @@ int br_multicast_add_port(struct net_bridge_port *port)
 
 	timer_setup(&port->multicast_router_timer,
 		    br_multicast_router_expired, 0);
-	timer_setup(&port->ip4_own_query.timer,
-		    br_ip4_multicast_port_query_expired, 0);
+	INIT_LIST_HEAD(&port->ip4_own_queries);
 #if IS_ENABLED(CONFIG_IPV6)
 	timer_setup(&port->ip6_own_query.timer,
 		    br_ip6_multicast_port_query_expired, 0);
@@ -1621,6 +1757,7 @@ int br_multicast_add_port(struct net_bridge_port *port)
 
 void br_multicast_del_port(struct net_bridge_port *port)
 {
+	struct bridge_mcast_own_query *query, *tmp;
 	struct net_bridge *br = port->br;
 	struct net_bridge_port_group *pg;
 	HLIST_HEAD(deleted_head);
@@ -1635,6 +1772,9 @@ void br_multicast_del_port(struct net_bridge_port *port)
 	br_multicast_gc(&deleted_head);
 	del_timer_sync(&port->multicast_router_timer);
 	free_percpu(port->mcast_stats);
+
+	list_for_each_entry_safe(query, tmp, &port->ip4_own_queries, list)
+		br_mcast_del_own_query(query);
 }
 
 static void br_multicast_enable(struct bridge_mcast_own_query *query)
@@ -1646,14 +1786,49 @@ static void br_multicast_enable(struct bridge_mcast_own_query *query)
 		mod_timer(&query->timer, jiffies);
 }
 
+static void br_multicast_disable(struct bridge_mcast_own_query *query)
+{
+	del_timer_sync(&query->timer);
+}
+
 static void __br_multicast_enable_port(struct net_bridge_port *port)
 {
+	struct bridge_mcast_own_query *query;
 	struct net_bridge *br = port->br;
 
 	if (!br_opt_get(br, BROPT_MULTICAST_ENABLED) || !netif_running(br->dev))
 		return;
 
-	br_multicast_enable(&port->ip4_own_query);
+	list_for_each_entry(query, &br->ip4_own_queries, list) {
+		struct bridge_mcast_own_query *port_query;
+		struct net_bridge_vlan_group *vg;
+
+		if (!query->enabled)
+			continue;
+
+		if (br_vlan_enabled(br->dev)) {
+			vg = nbp_vlan_group(port);
+			if (!vg || (vg && !br_vlan_find(vg, query->vid)))
+				continue;
+		}
+
+		port_query = br_mcast_find_own_query(&port->ip4_own_queries,
+						     query->vid);
+		if (!port_query) {
+			port_query = br_mcast_add_own_query(&port->ip4_own_queries,
+							    query->vid,
+							    br_ip4_multicast_port_query_expired);
+			if (!port_query)
+				continue;
+
+			port_query->port = port;
+		}
+
+		if (query->ip4) {
+			port_query->ip4 = true;
+			br_multicast_enable(port_query);
+		}
+	}
 #if IS_ENABLED(CONFIG_IPV6)
 	br_multicast_enable(&port->ip6_own_query);
 #endif
@@ -1673,6 +1848,7 @@ void br_multicast_enable_port(struct net_bridge_port *port)
 
 void br_multicast_disable_port(struct net_bridge_port *port)
 {
+	struct bridge_mcast_own_query *query;
 	struct net_bridge *br = port->br;
 	struct net_bridge_port_group *pg;
 	struct hlist_node *n;
@@ -1685,7 +1861,8 @@ void br_multicast_disable_port(struct net_bridge_port *port)
 	__del_port_router(port);
 
 	del_timer(&port->multicast_router_timer);
-	del_timer(&port->ip4_own_query.timer);
+	list_for_each_entry(query, &port->ip4_own_queries, list)
+		del_timer(&query->timer);
 #if IS_ENABLED(CONFIG_IPV6)
 	del_timer(&port->ip6_own_query.timer);
 #endif
@@ -1717,17 +1894,23 @@ static void __grp_src_mod_timer(struct net_bridge_group_src *src,
 static void __grp_src_query_marked_and_rexmit(struct net_bridge_port_group *pg)
 {
 	struct bridge_mcast_other_query *other_query = NULL;
+	struct bridge_mcast_own_query *own_query = NULL;
 	struct net_bridge *br = pg->key.port->br;
 	u32 lmqc = br->multicast_last_member_count;
 	unsigned long lmqt, lmi, now = jiffies;
 	struct net_bridge_group_src *ent;
+	u16 vid = pg->key.addr.vid;
+
+	own_query = br_mcast_find_own_query(&pg->key.port->ip4_own_queries,
+					    vid);
 
 	if (!netif_running(br->dev) ||
 	    !br_opt_get(br, BROPT_MULTICAST_ENABLED))
 		return;
 
 	if (pg->key.addr.proto == htons(ETH_P_IP))
-		other_query = &br->ip4_other_query;
+		other_query = br_mcast_find_other_query(&br->ip4_other_queries,
+							vid);
 #if IS_ENABLED(CONFIG_IPV6)
 	else
 		other_query = &br->ip6_other_query;
@@ -1738,7 +1921,7 @@ static void __grp_src_query_marked_and_rexmit(struct net_bridge_port_group *pg)
 		if (ent->flags & BR_SGRP_F_SEND) {
 			ent->flags &= ~BR_SGRP_F_SEND;
 			if (ent->timer.expires > lmqt) {
-				if (br_opt_get(br, BROPT_MULTICAST_QUERIER) &&
+				if (own_query && own_query->enabled &&
 				    other_query &&
 				    !timer_pending(&other_query->timer))
 					ent->src_query_rexmit_cnt = lmqc;
@@ -1747,7 +1930,7 @@ static void __grp_src_query_marked_and_rexmit(struct net_bridge_port_group *pg)
 		}
 	}
 
-	if (!br_opt_get(br, BROPT_MULTICAST_QUERIER) ||
+	if (!own_query || !own_query->enabled ||
 	    !other_query || timer_pending(&other_query->timer))
 		return;
 
@@ -1763,21 +1946,27 @@ static void __grp_src_query_marked_and_rexmit(struct net_bridge_port_group *pg)
 static void __grp_send_query_and_rexmit(struct net_bridge_port_group *pg)
 {
 	struct bridge_mcast_other_query *other_query = NULL;
+	struct bridge_mcast_own_query *own_query = NULL;
 	struct net_bridge *br = pg->key.port->br;
 	unsigned long now = jiffies, lmi;
+	u16 vid = pg->key.addr.vid;
 
 	if (!netif_running(br->dev) ||
 	    !br_opt_get(br, BROPT_MULTICAST_ENABLED))
 		return;
 
+	own_query = br_mcast_find_own_query(&pg->key.port->ip4_own_queries,
+					    vid);
+
 	if (pg->key.addr.proto == htons(ETH_P_IP))
-		other_query = &br->ip4_other_query;
+		other_query = br_mcast_find_other_query(&br->ip4_other_queries,
+							vid);
 #if IS_ENABLED(CONFIG_IPV6)
 	else
 		other_query = &br->ip6_other_query;
 #endif
 
-	if (br_opt_get(br, BROPT_MULTICAST_QUERIER) &&
+	if (own_query && own_query->enabled &&
 	    other_query && !timer_pending(&other_query->timer)) {
 		lmi = now + br->multicast_last_member_interval;
 		pg->grp_query_rexmit_cnt = br->multicast_last_member_count - 1;
@@ -2484,10 +2673,12 @@ static int br_ip6_multicast_mld2_report(struct net_bridge *br,
 
 static bool br_ip4_multicast_select_querier(struct net_bridge *br,
 					    struct net_bridge_port *port,
-					    __be32 saddr)
+					    __be32 saddr,
+					    struct bridge_mcast_own_query *own,
+					    struct bridge_mcast_other_query *other)
 {
-	if (!timer_pending(&br->ip4_own_query.timer) &&
-	    !timer_pending(&br->ip4_other_query.timer))
+	if (own && !timer_pending(&own->timer) &&
+	    !timer_pending(&other->timer))
 		goto update;
 
 	if (!br->ip4_querier.addr.src.ip4)
@@ -2533,11 +2724,14 @@ static bool br_ip6_multicast_select_querier(struct net_bridge *br,
 
 static bool br_multicast_select_querier(struct net_bridge *br,
 					struct net_bridge_port *port,
-					struct br_ip *saddr)
+					struct br_ip *saddr,
+					struct bridge_mcast_own_query *query,
+					struct bridge_mcast_other_query *other_query)
 {
 	switch (saddr->proto) {
 	case htons(ETH_P_IP):
-		return br_ip4_multicast_select_querier(br, port, saddr->src.ip4);
+		return br_ip4_multicast_select_querier(br, port, saddr->src.ip4,
+						       query, other_query);
 #if IS_ENABLED(CONFIG_IPV6)
 	case htons(ETH_P_IPV6):
 		return br_ip6_multicast_select_querier(br, port, &saddr->src.ip6);
@@ -2628,9 +2822,10 @@ static void br_multicast_query_received(struct net_bridge *br,
 					struct net_bridge_port *port,
 					struct bridge_mcast_other_query *query,
 					struct br_ip *saddr,
-					unsigned long max_delay)
+					unsigned long max_delay,
+					struct bridge_mcast_own_query *own_query)
 {
-	if (!br_multicast_select_querier(br, port, saddr))
+	if (!br_multicast_select_querier(br, port, saddr, own_query, query))
 		return;
 
 	br_multicast_update_query_timer(br, query, max_delay);
@@ -2643,6 +2838,8 @@ static void br_ip4_multicast_query(struct net_bridge *br,
 				   u16 vid)
 {
 	unsigned int transport_len = ip_transport_len(skb);
+	struct bridge_mcast_other_query *other_query;
+	struct bridge_mcast_own_query *own_query;
 	const struct iphdr *iph = ip_hdr(skb);
 	struct igmphdr *ih = igmp_hdr(skb);
 	struct net_bridge_mdb_entry *mp;
@@ -2684,8 +2881,13 @@ static void br_ip4_multicast_query(struct net_bridge *br,
 		saddr.proto = htons(ETH_P_IP);
 		saddr.src.ip4 = iph->saddr;
 
-		br_multicast_query_received(br, port, &br->ip4_other_query,
-					    &saddr, max_delay);
+		br_mcast_add_queries(br, vid);
+
+		own_query = br_mcast_find_own_query(&br->ip4_own_queries, vid);
+		other_query = br_mcast_find_other_query(&br->ip4_other_queries,
+							vid);
+		br_multicast_query_received(br, port, other_query, &saddr,
+					    max_delay, own_query);
 		goto out;
 	}
 
@@ -2773,7 +2975,7 @@ static int br_ip6_multicast_query(struct net_bridge *br,
 		saddr.src.ip6 = ipv6_hdr(skb)->saddr;
 
 		br_multicast_query_received(br, port, &br->ip6_other_query,
-					    &saddr, max_delay);
+					    &saddr, max_delay, NULL);
 		goto out;
 	} else if (!group) {
 		goto out;
@@ -2850,7 +3052,7 @@ br_multicast_leave_group(struct net_bridge *br,
 	if (timer_pending(&other_query->timer))
 		goto out;
 
-	if (br_opt_get(br, BROPT_MULTICAST_QUERIER)) {
+	if (own_query && own_query->enabled) {
 		__br_multicast_send_query(br, port, NULL, NULL, &mp->addr,
 					  false, 0, NULL);
 
@@ -2916,21 +3118,26 @@ static void br_ip4_multicast_leave_group(struct net_bridge *br,
 					 __u16 vid,
 					 const unsigned char *src)
 {
-	struct br_ip br_group;
+	struct bridge_mcast_other_query *other_query;
 	struct bridge_mcast_own_query *own_query;
+	struct br_ip br_group;
 
 	if (ipv4_is_local_multicast(group))
 		return;
 
-	own_query = port ? &port->ip4_own_query : &br->ip4_own_query;
+	if (port)
+		own_query = br_mcast_find_own_query(&port->ip4_own_queries, vid);
+	else
+		own_query = br_mcast_find_own_query(&br->ip4_own_queries, vid);
 
 	memset(&br_group, 0, sizeof(br_group));
 	br_group.dst.ip4 = group;
 	br_group.proto = htons(ETH_P_IP);
 	br_group.vid = vid;
 
-	br_multicast_leave_group(br, port, &br_group, &br->ip4_other_query,
-				 own_query, src);
+	other_query = br_mcast_find_other_query(&br->ip4_other_queries, vid);
+	br_multicast_leave_group(br, port, &br_group, other_query, own_query,
+				 src);
 }
 
 #if IS_ENABLED(CONFIG_IPV6)
@@ -3195,9 +3402,10 @@ static void br_multicast_query_expired(struct net_bridge *br,
 
 static void br_ip4_multicast_query_expired(struct timer_list *t)
 {
-	struct net_bridge *br = from_timer(br, t, ip4_own_query.timer);
+	struct bridge_mcast_own_query *query = from_timer(query, t, timer);
+	struct net_bridge *br = query->br;
 
-	br_multicast_query_expired(br, &br->ip4_own_query, &br->ip4_querier);
+	br_multicast_query_expired(br, query, &br->ip4_querier);
 }
 
 #if IS_ENABLED(CONFIG_IPV6)
@@ -3237,7 +3445,6 @@ void br_multicast_init(struct net_bridge *br)
 	br->multicast_querier_interval = 255 * HZ;
 	br->multicast_membership_interval = 260 * HZ;
 
-	br->ip4_other_query.delay_time = 0;
 	br->ip4_querier.port = NULL;
 	br->multicast_igmp_version = 2;
 #if IS_ENABLED(CONFIG_IPV6)
@@ -3251,10 +3458,8 @@ void br_multicast_init(struct net_bridge *br)
 	spin_lock_init(&br->multicast_lock);
 	timer_setup(&br->multicast_router_timer,
 		    br_multicast_local_router_expired, 0);
-	timer_setup(&br->ip4_other_query.timer,
-		    br_ip4_multicast_querier_expired, 0);
-	timer_setup(&br->ip4_own_query.timer,
-		    br_ip4_multicast_query_expired, 0);
+	INIT_LIST_HEAD(&br->ip4_other_queries);
+	INIT_LIST_HEAD(&br->ip4_own_queries);
 #if IS_ENABLED(CONFIG_IPV6)
 	timer_setup(&br->ip6_other_query.timer,
 		    br_ip6_multicast_querier_expired, 0);
@@ -3341,7 +3546,10 @@ static void __br_multicast_open(struct net_bridge *br,
 
 void br_multicast_open(struct net_bridge *br)
 {
-	__br_multicast_open(br, &br->ip4_own_query);
+	struct bridge_mcast_own_query *query;
+
+	list_for_each_entry(query, &br->ip4_own_queries, list)
+		__br_multicast_open(br, query);
 #if IS_ENABLED(CONFIG_IPV6)
 	__br_multicast_open(br, &br->ip6_own_query);
 #endif
@@ -3349,9 +3557,14 @@ void br_multicast_open(struct net_bridge *br)
 
 void br_multicast_stop(struct net_bridge *br)
 {
+	struct bridge_mcast_other_query *other_query;
+	struct bridge_mcast_own_query *query;
+
 	del_timer_sync(&br->multicast_router_timer);
-	del_timer_sync(&br->ip4_other_query.timer);
-	del_timer_sync(&br->ip4_own_query.timer);
+	list_for_each_entry(other_query, &br->ip4_other_queries, list)
+		del_timer_sync(&other_query->timer);
+	list_for_each_entry(query, &br->ip4_own_queries, list)
+		del_timer_sync(&query->timer);
 #if IS_ENABLED(CONFIG_IPV6)
 	del_timer_sync(&br->ip6_other_query.timer);
 	del_timer_sync(&br->ip6_own_query.timer);
@@ -3461,11 +3674,20 @@ int br_multicast_set_port_router(struct net_bridge_port *p, unsigned long val)
 }
 
 static void br_multicast_start_querier(struct net_bridge *br,
-				       struct bridge_mcast_own_query *query)
+				       struct bridge_mcast_own_query *query,
+				       u16 vid)
 {
+	struct bridge_mcast_own_query *port_query;
+	struct net_bridge_vlan_group *vg;
 	struct net_bridge_port *port;
 
-	__br_multicast_open(br, query);
+	if (br_vlan_enabled(br->dev)) {
+		vg = br_vlan_group(br);
+		if (vg && br_vlan_find(vg, vid))
+			__br_multicast_open(br, query);
+	} else {
+		__br_multicast_open(br, query);
+	}
 
 	rcu_read_lock();
 	list_for_each_entry_rcu(port, &br->port_list, list) {
@@ -3473,11 +3695,66 @@ static void br_multicast_start_querier(struct net_bridge *br,
 		    port->state == BR_STATE_BLOCKING)
 			continue;
 
-		if (query == &br->ip4_own_query)
-			br_multicast_enable(&port->ip4_own_query);
+		if (br_vlan_enabled(br->dev)) {
+			vg = nbp_vlan_group(port);
+			if (!vg || (vg && !br_vlan_find(vg, vid)))
+				continue;
+		}
+
+		port_query = br_mcast_find_own_query(&port->ip4_own_queries,
+						     vid);
+		if (!port_query)
+			continue;
+
+		port_query->enabled = true;
+
+		if (query->ip4) {
+			port_query->ip4 = true;
+			br_multicast_enable(port_query);
+		}
 #if IS_ENABLED(CONFIG_IPV6)
-		else
+		else {
 			br_multicast_enable(&port->ip6_own_query);
+		}
+#endif
+	}
+	rcu_read_unlock();
+}
+
+static void br_multicast_stop_querier(struct net_bridge *br,
+				      struct bridge_mcast_own_query *query,
+				      u16 vid)
+{
+	struct bridge_mcast_own_query *port_query;
+	struct net_bridge_vlan_group *vg;
+	struct net_bridge_port *port;
+
+	query->enabled = false;
+
+	rcu_read_lock();
+	list_for_each_entry_rcu(port, &br->port_list, list) {
+		if (port->state == BR_STATE_DISABLED ||
+		    port->state == BR_STATE_BLOCKING)
+			continue;
+
+		if (br_vlan_enabled(br->dev)) {
+			vg = nbp_vlan_group(port);
+			if (!vg || (vg && !br_vlan_find(vg, vid)))
+				continue;
+		}
+
+		port_query = br_mcast_find_own_query(&port->ip4_own_queries,
+						     vid);
+		if (!port_query)
+			continue;
+
+		port_query->enabled = false;
+
+		if (query->ip4)
+			br_multicast_disable(port_query);
+#if IS_ENABLED(CONFIG_IPV6)
+		else
+			br_multicast_disable(&port->ip6_own_query);
 #endif
 	}
 	rcu_read_unlock();
@@ -3553,32 +3830,55 @@ bool br_multicast_router(const struct net_device *dev)
 }
 EXPORT_SYMBOL_GPL(br_multicast_router);
 
-int br_multicast_set_querier(struct net_bridge *br, unsigned long val)
+int br_multicast_set_querier(struct net_bridge *br, unsigned long val, u16 vid)
 {
+	struct bridge_mcast_other_query *other_query;
+	struct bridge_mcast_own_query *query;
+	struct net_bridge_vlan_group *vg;
 	unsigned long max_delay;
 
 	val = !!val;
 
+	if (vid == 0) {
+		vg = br_vlan_group(br);
+		if (vg)
+			vid = vg->pvid;
+	}
+
 	spin_lock_bh(&br->multicast_lock);
-	if (br_opt_get(br, BROPT_MULTICAST_QUERIER) == val)
+	query = br_mcast_find_own_query(&br->ip4_own_queries, vid);
+	if (!query) {
+		if (br_vlan_enabled(br->dev))
+			goto unlock;
+
+		br_mcast_add_queries(br, vid);
+	}
+
+	other_query = br_mcast_find_other_query(&br->ip4_other_queries, vid);
+	if (!other_query)
 		goto unlock;
 
-	br_opt_toggle(br, BROPT_MULTICAST_QUERIER, !!val);
-	if (!val)
+	if (!val && query) {
+		br_multicast_stop_querier(br, query, vid);
 		goto unlock;
+	}
 
-	max_delay = br->multicast_query_response_interval;
+	if (val & query->enabled)
+		goto unlock;
 
-	if (!timer_pending(&br->ip4_other_query.timer))
-		br->ip4_other_query.delay_time = jiffies + max_delay;
+	query->enabled = true;
 
-	br_multicast_start_querier(br, &br->ip4_own_query);
+	max_delay = br->multicast_query_response_interval;
+	if (!timer_pending(&other_query->timer))
+		other_query->delay_time = jiffies + max_delay;
+
+	br_multicast_start_querier(br, query, vid);
 
 #if IS_ENABLED(CONFIG_IPV6)
 	if (!timer_pending(&br->ip6_other_query.timer))
 		br->ip6_other_query.delay_time = jiffies + max_delay;
 
-	br_multicast_start_querier(br, &br->ip6_own_query);
+	br_multicast_start_querier(br, &br->ip6_own_query, vid);
 #endif
 
 unlock:
@@ -3587,6 +3887,79 @@ int br_multicast_set_querier(struct net_bridge *br, unsigned long val)
 	return 0;
 }
 
+void br_multicast_vlan_add(struct net_bridge_vlan *v)
+{
+	struct bridge_mcast_own_query *query, *port_query;
+	struct net_bridge_port *p;
+	struct net_bridge *br;
+
+	if (br_vlan_is_master(v)) {
+		br_mcast_add_queries(v->br, v->vid);
+		return;
+	}
+
+	p = v->port;
+	br = p->br;
+
+	query = br_mcast_find_own_query(&br->ip4_own_queries, v->vid);
+
+	port_query = br_mcast_add_own_query(&p->ip4_own_queries,
+					    v->vid,
+					    br_ip4_multicast_port_query_expired);
+	if (!port_query)
+		return;
+
+	port_query->port = p;
+	port_query->ip4 = true;
+
+	if (query->enabled) {
+		port_query->enabled = true;
+		br_multicast_enable(port_query);
+	}
+}
+
+void br_multicast_vlan_del(struct net_bridge_vlan *v)
+{
+	struct bridge_mcast_other_query *other_query, *other_tmp;
+	struct bridge_mcast_own_query *query, *tmp;
+	struct net_bridge_port *p;
+	struct net_bridge *br;
+
+	if (br_vlan_is_master(v)) {
+		br = v->br;
+
+		list_for_each_entry_safe(other_query, other_tmp,
+					 &br->ip4_other_queries, list)
+			if (other_query->vid == v->vid)
+				br_mcast_del_other_query(other_query);
+
+		list_for_each_entry_safe(query, tmp, &br->ip4_own_queries, list)
+			if (query->vid == v->vid)
+				br_mcast_del_own_query(query);
+
+		return;
+	}
+
+	p = v->port;
+
+	list_for_each_entry_safe(query, tmp, &p->ip4_own_queries, list) {
+		if (query->vid == v->vid)
+			br_mcast_del_own_query(query);
+	}
+}
+
+void br_multicast_vlan_toggle(struct net_bridge *br, bool on)
+{
+	struct bridge_mcast_own_query *query;
+
+	list_for_each_entry(query, &br->ip4_own_queries, list) {
+		if (!on)
+			br_multicast_stop_querier(br, query, query->vid);
+		else
+			br_multicast_start_querier(br, query, query->vid);
+	}
+}
+
 int br_multicast_set_igmp_version(struct net_bridge *br, unsigned long val)
 {
 	/* Currently we support only version 2 and 3 */
@@ -3711,7 +4084,7 @@ bool br_multicast_has_querier_anywhere(struct net_device *dev, int proto)
 	memset(&eth, 0, sizeof(eth));
 	eth.h_proto = htons(proto);
 
-	ret = br_multicast_querier_exists(br, &eth, NULL);
+	ret = br_multicast_any_querier_exists(br, &eth);
 
 unlock:
 	rcu_read_unlock();
@@ -3746,7 +4119,7 @@ bool br_multicast_has_querier_adjacent(struct net_device *dev, int proto)
 
 	switch (proto) {
 	case ETH_P_IP:
-		if (!timer_pending(&br->ip4_other_query.timer) ||
+		if (!br_multicast_any_querier_adjacent(br) ||
 		    rcu_dereference(br->ip4_querier.port) == port)
 			goto unlock;
 		break;
diff --git a/net/bridge/br_netlink.c b/net/bridge/br_netlink.c
index 49700ce0e919..d32f4c185364 100644
--- a/net/bridge/br_netlink.c
+++ b/net/bridge/br_netlink.c
@@ -1186,6 +1186,7 @@ static const struct nla_policy br_policy[IFLA_BR_MAX + 1] = {
 	[IFLA_BR_VLAN_STATS_PER_PORT] = { .type = NLA_U8 },
 	[IFLA_BR_MULTI_BOOLOPT] =
 		NLA_POLICY_EXACT_LEN(sizeof(struct br_boolopt_multi)),
+	[IFLA_BR_MCAST_QUERIER_VID] = { .type = NLA_U16 },
 };
 
 static int br_changelink(struct net_device *brdev, struct nlattr *tb[],
@@ -1193,6 +1194,7 @@ static int br_changelink(struct net_device *brdev, struct nlattr *tb[],
 			 struct netlink_ext_ack *extack)
 {
 	struct net_bridge *br = netdev_priv(brdev);
+	u16 vid = 0;
 	int err;
 
 	if (!data)
@@ -1204,6 +1206,9 @@ static int br_changelink(struct net_device *brdev, struct nlattr *tb[],
 			return err;
 	}
 
+	if (data[IFLA_BR_MCAST_QUERIER_VID])
+		vid = nla_get_u16(data[IFLA_BR_MCAST_QUERIER_VID]);
+
 	if (data[IFLA_BR_HELLO_TIME]) {
 		err = br_set_hello_time(br, nla_get_u32(data[IFLA_BR_HELLO_TIME]));
 		if (err)
@@ -1333,7 +1338,7 @@ static int br_changelink(struct net_device *brdev, struct nlattr *tb[],
 	if (data[IFLA_BR_MCAST_QUERIER]) {
 		u8 mcast_querier = nla_get_u8(data[IFLA_BR_MCAST_QUERIER]);
 
-		err = br_multicast_set_querier(br, mcast_querier);
+		err = br_multicast_set_querier(br, mcast_querier, vid);
 		if (err)
 			return err;
 	}
@@ -1596,7 +1601,7 @@ static int br_fill_info(struct sk_buff *skb, const struct net_device *brdev)
 	    nla_put_u8(skb, IFLA_BR_MCAST_QUERY_USE_IFADDR,
 		       br_opt_get(br, BROPT_MULTICAST_QUERY_USE_IFADDR)) ||
 	    nla_put_u8(skb, IFLA_BR_MCAST_QUERIER,
-		       br_opt_get(br, BROPT_MULTICAST_QUERIER)) ||
+		       br_mcast_exist_own_query(br)) ||
 	    nla_put_u8(skb, IFLA_BR_MCAST_STATS_ENABLED,
 		       br_opt_get(br, BROPT_MULTICAST_STATS_ENABLED)) ||
 	    nla_put_u32(skb, IFLA_BR_MCAST_HASH_ELASTICITY, RHT_ELASTICITY) ||
diff --git a/net/bridge/br_private.h b/net/bridge/br_private.h
index d62c6e1af64a..84f597f542b1 100644
--- a/net/bridge/br_private.h
+++ b/net/bridge/br_private.h
@@ -66,14 +66,24 @@ struct mac_addr {
 #ifdef CONFIG_BRIDGE_IGMP_SNOOPING
 /* our own querier */
 struct bridge_mcast_own_query {
+	struct list_head	list;
 	struct timer_list	timer;
 	u32			startup_sent;
+	struct net_bridge_port	*port;
+	struct net_bridge	*br;
+	bool			ip4;
+	u16			vid;
+	bool			enabled;
 };
 
 /* other querier */
 struct bridge_mcast_other_query {
+	struct list_head		list;
 	struct timer_list		timer;
 	unsigned long			delay_time;
+	struct net_bridge		*br;
+	bool				ip4;
+	u16				vid;
 };
 
 /* selected querier */
@@ -304,7 +314,7 @@ struct net_bridge_port {
 	struct rcu_head			rcu;
 
 #ifdef CONFIG_BRIDGE_IGMP_SNOOPING
-	struct bridge_mcast_own_query	ip4_own_query;
+	struct list_head		ip4_own_queries;
 #if IS_ENABLED(CONFIG_IPV6)
 	struct bridge_mcast_own_query	ip6_own_query;
 #endif /* IS_ENABLED(CONFIG_IPV6) */
@@ -448,8 +458,8 @@ struct net_bridge {
 	struct hlist_head		router_list;
 
 	struct timer_list		multicast_router_timer;
-	struct bridge_mcast_other_query	ip4_other_query;
-	struct bridge_mcast_own_query	ip4_own_query;
+	struct list_head		ip4_other_queries;
+	struct list_head		ip4_own_queries;
 	struct bridge_mcast_querier	ip4_querier;
 	struct bridge_mcast_stats	__percpu *mcast_stats;
 #if IS_ENABLED(CONFIG_IPV6)
@@ -788,6 +798,9 @@ int br_ioctl_deviceless_stub(struct net *net, unsigned int cmd,
 
 /* br_multicast.c */
 #ifdef CONFIG_BRIDGE_IGMP_SNOOPING
+void br_multicast_vlan_add(struct net_bridge_vlan *v);
+void br_multicast_vlan_del(struct net_bridge_vlan *v);
+void br_multicast_vlan_toggle(struct net_bridge *br, bool on);
 int br_multicast_rcv(struct net_bridge *br, struct net_bridge_port *port,
 		     struct sk_buff *skb, u16 vid);
 struct net_bridge_mdb_entry *br_mdb_get(struct net_bridge *br,
@@ -807,7 +820,7 @@ void br_multicast_flood(struct net_bridge_mdb_entry *mdst,
 int br_multicast_set_router(struct net_bridge *br, unsigned long val);
 int br_multicast_set_port_router(struct net_bridge_port *p, unsigned long val);
 int br_multicast_toggle(struct net_bridge *br, unsigned long val);
-int br_multicast_set_querier(struct net_bridge *br, unsigned long val);
+int br_multicast_set_querier(struct net_bridge *br, unsigned long val, u16 vid);
 int br_multicast_set_hash_max(struct net_bridge *br, unsigned long val);
 int br_multicast_set_igmp_version(struct net_bridge *br, unsigned long val);
 #if IS_ENABLED(CONFIG_IPV6)
@@ -846,6 +859,11 @@ 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 bridge_mcast_other_query *
+br_mcast_find_other_query(struct list_head *list, u16 vid);
+struct bridge_mcast_own_query *
+br_mcast_find_own_query(struct list_head *list, u16 vid);
+bool br_mcast_exist_own_query(struct net_bridge *br);
 
 static inline bool br_group_is_l2(const struct br_ip *group)
 {
@@ -865,11 +883,15 @@ static inline bool br_multicast_is_router(struct net_bridge *br)
 static inline bool
 __br_multicast_querier_exists(struct net_bridge *br,
 				struct bridge_mcast_other_query *querier,
-				const bool is_ipv6)
+				const bool is_ipv6,
+				u16 vid)
 {
+	struct bridge_mcast_own_query *query;
 	bool own_querier_enabled;
 
-	if (br_opt_get(br, BROPT_MULTICAST_QUERIER)) {
+	query = br_mcast_find_own_query(&br->ip4_own_queries, vid);
+
+	if (query && query->enabled) {
 		if (is_ipv6 && !br_opt_get(br, BROPT_HAS_IPV6_ADDR))
 			own_querier_enabled = false;
 		else
@@ -878,28 +900,62 @@ __br_multicast_querier_exists(struct net_bridge *br,
 		own_querier_enabled = false;
 	}
 
+	if (!querier)
+		return own_querier_enabled;
+
 	return time_is_before_jiffies(querier->delay_time) &&
 	       (own_querier_enabled || timer_pending(&querier->timer));
 }
 
 static inline bool br_multicast_querier_exists(struct net_bridge *br,
 					       struct ethhdr *eth,
-					       const struct net_bridge_mdb_entry *mdb)
+					       const struct net_bridge_mdb_entry *mdb,
+					       u16 vid)
 {
+	struct bridge_mcast_other_query *query =
+		br_mcast_find_other_query(&br->ip4_other_queries, vid);
+
 	switch (eth->h_proto) {
 	case (htons(ETH_P_IP)):
-		return __br_multicast_querier_exists(br,
-			&br->ip4_other_query, false);
+		return __br_multicast_querier_exists(br, query, false, vid);
 #if IS_ENABLED(CONFIG_IPV6)
 	case (htons(ETH_P_IPV6)):
 		return __br_multicast_querier_exists(br,
-			&br->ip6_other_query, true);
+			&br->ip6_other_query, true, vid);
 #endif
 	default:
 		return !!mdb && br_group_is_l2(&mdb->addr);
 	}
 }
 
+static inline bool br_multicast_any_querier_exists(struct net_bridge *br,
+						   struct ethhdr *eth)
+{
+	struct bridge_mcast_other_query *query;
+
+	list_for_each_entry(query, &br->ip4_other_queries, list) {
+		if (!timer_pending(&query->timer))
+			continue;
+
+		if (br_multicast_querier_exists(br, eth, NULL, query->vid))
+			return true;
+	}
+
+	return false;
+}
+
+static inline bool br_multicast_any_querier_adjacent(struct net_bridge *br)
+{
+	struct bridge_mcast_other_query *query;
+
+	list_for_each_entry(query, &br->ip4_other_queries, list) {
+		if (timer_pending(&query->timer))
+			return true;
+	}
+
+	return false;
+}
+
 static inline bool br_multicast_is_star_g(const struct br_ip *ip)
 {
 	switch (ip->proto) {
@@ -1015,7 +1071,19 @@ static inline bool br_multicast_is_router(struct net_bridge *br)
 
 static inline bool br_multicast_querier_exists(struct net_bridge *br,
 					       struct ethhdr *eth,
-					       const struct net_bridge_mdb_entry *mdb)
+					       const struct net_bridge_mdb_entry *mdb,
+					       u16 vid)
+{
+	return false;
+}
+
+static inline bool br_multicast_any_querier_exists(struct net_bridge *br,
+						   struct ethhdr *eth)
+{
+	return false;
+}
+
+static inline bool br_multicast_any_querier_adjacent(struct net_bridge *br)
 {
 	return false;
 }
diff --git a/net/bridge/br_sysfs_br.c b/net/bridge/br_sysfs_br.c
index 7db06e3f642a..23bf6a065d78 100644
--- a/net/bridge/br_sysfs_br.c
+++ b/net/bridge/br_sysfs_br.c
@@ -51,6 +51,33 @@ static ssize_t store_bridge_parm(struct device *d,
 	return err ? err : len;
 }
 
+static ssize_t store_bridge_parm2(struct device *d,
+				  const char *buf, size_t len,
+				  int (*set)(struct net_bridge *, unsigned long, u16))
+{
+	struct net_bridge *br = to_bridge(d);
+	char *endp;
+	unsigned long val;
+	int err;
+
+	if (!ns_capable(dev_net(br->dev)->user_ns, CAP_NET_ADMIN))
+		return -EPERM;
+
+	val = simple_strtoul(buf, &endp, 0);
+	if (endp == buf)
+		return -EINVAL;
+
+	if (!rtnl_trylock())
+		return restart_syscall();
+
+	err = (*set)(br, val, 0);
+	if (!err)
+		netdev_state_change(br->dev);
+	rtnl_unlock();
+
+	return err ? err : len;
+}
+
 
 static ssize_t forward_delay_show(struct device *d,
 				  struct device_attribute *attr, char *buf)
@@ -404,14 +431,14 @@ static ssize_t multicast_querier_show(struct device *d,
 				      char *buf)
 {
 	struct net_bridge *br = to_bridge(d);
-	return sprintf(buf, "%d\n", br_opt_get(br, BROPT_MULTICAST_QUERIER));
+	return sprintf(buf, "%d\n", br_mcast_exist_own_query(br));
 }
 
 static ssize_t multicast_querier_store(struct device *d,
 				       struct device_attribute *attr,
 				       const char *buf, size_t len)
 {
-	return store_bridge_parm(d, buf, len, br_multicast_set_querier);
+	return store_bridge_parm2(d, buf, len, br_multicast_set_querier);
 }
 static DEVICE_ATTR_RW(multicast_querier);
 
diff --git a/net/bridge/br_vlan.c b/net/bridge/br_vlan.c
index 701cad646b20..2e0b544a3560 100644
--- a/net/bridge/br_vlan.c
+++ b/net/bridge/br_vlan.c
@@ -308,6 +308,7 @@ static int __vlan_add(struct net_bridge_vlan *v, u16 flags,
 
 	__vlan_add_list(v);
 	__vlan_add_flags(v, flags);
+	br_multicast_vlan_add(v);
 
 	if (p)
 		nbp_vlan_set_vlan_dev_state(p, v->vid);
@@ -353,6 +354,7 @@ static int __vlan_del(struct net_bridge_vlan *v)
 		masterv = v->brvlan;
 	}
 
+	br_multicast_vlan_del(v);
 	__vlan_delete_pvid(vg, v->vid);
 	if (p) {
 		err = __vlan_vid_del(p->dev, p->br, v);
@@ -827,6 +829,7 @@ int __br_vlan_filter_toggle(struct net_bridge *br, unsigned long val)
 	br_manage_promisc(br);
 	recalculate_group_addr(br);
 	br_recalculate_fwd_mask(br);
+	br_multicast_vlan_toggle(br, !!val);
 
 	return 0;
 }
-- 
2.27.0


^ permalink raw reply related	[flat|nested] 16+ messages in thread

* [Bridge] [RFC PATCH v2] net: bridge: igmp: Extend IGMP query to be per vlan
@ 2021-01-12 13:59 ` Horatiu Vultur
  0 siblings, 0 replies; 16+ messages in thread
From: Horatiu Vultur @ 2021-01-12 13:59 UTC (permalink / raw)
  To: davem, kuba, roopa, nikolay, allan.nielsen, netdev, linux-kernel, bridge
  Cc: Horatiu Vultur

Based on the comments of the previous version, we started to work on a
new version, so it would be possible to enable/disable queries per vlan.
This is still work in progress and there are plenty of things that are
not implemented and tested:
- ipv6 support
- the fast path needs to be improved
- currently it is possible only to enable/disable the queries per vlan,
  all the other configurations are global
- toggling vlan_filtering is not tested
- remove duplicated information
- etc...

But there are few things that are working like:
- sending queries per vlan
- stop sending queries if there is a better querier per vlan
- when ports are added/removed from vlan
- etc...

We were wondering if this what you had in mind when you proposed to have
this per vlan? Or we are completely off? Or we should fix some of the
issues that I mentioned, before you can see more clearly the direction?

Signed-off-by: Horatiu Vultur <horatiu.vultur@microchip.com>
---
 include/uapi/linux/if_link.h |   1 +
 net/bridge/br_device.c       |   2 +-
 net/bridge/br_input.c        |   2 +-
 net/bridge/br_multicast.c    | 505 ++++++++++++++++++++++++++++++-----
 net/bridge/br_netlink.c      |   9 +-
 net/bridge/br_private.h      |  90 ++++++-
 net/bridge/br_sysfs_br.c     |  31 ++-
 net/bridge/br_vlan.c         |   3 +
 8 files changed, 560 insertions(+), 83 deletions(-)

diff --git a/include/uapi/linux/if_link.h b/include/uapi/linux/if_link.h
index 82708c6db432..11ec1d45c24e 100644
--- a/include/uapi/linux/if_link.h
+++ b/include/uapi/linux/if_link.h
@@ -472,6 +472,7 @@ enum {
 	IFLA_BR_MCAST_MLD_VERSION,
 	IFLA_BR_VLAN_STATS_PER_PORT,
 	IFLA_BR_MULTI_BOOLOPT,
+	IFLA_BR_MCAST_QUERIER_VID,
 	__IFLA_BR_MAX,
 };
 
diff --git a/net/bridge/br_device.c b/net/bridge/br_device.c
index 3f2f06b4dd27..aca4e8074a8f 100644
--- a/net/bridge/br_device.c
+++ b/net/bridge/br_device.c
@@ -89,7 +89,7 @@ netdev_tx_t br_dev_xmit(struct sk_buff *skb, struct net_device *dev)
 
 		mdst = br_mdb_get(br, skb, vid);
 		if ((mdst || BR_INPUT_SKB_CB_MROUTERS_ONLY(skb)) &&
-		    br_multicast_querier_exists(br, eth_hdr(skb), mdst))
+		    br_multicast_querier_exists(br, eth_hdr(skb), mdst, vid))
 			br_multicast_flood(mdst, skb, false, true);
 		else
 			br_flood(br, skb, BR_PKT_MULTICAST, false, true);
diff --git a/net/bridge/br_input.c b/net/bridge/br_input.c
index 222285d9dae2..03e445af6c1f 100644
--- a/net/bridge/br_input.c
+++ b/net/bridge/br_input.c
@@ -130,7 +130,7 @@ int br_handle_frame_finish(struct net *net, struct sock *sk, struct sk_buff *skb
 	case BR_PKT_MULTICAST:
 		mdst = br_mdb_get(br, skb, vid);
 		if ((mdst || BR_INPUT_SKB_CB_MROUTERS_ONLY(skb)) &&
-		    br_multicast_querier_exists(br, eth_hdr(skb), mdst)) {
+		    br_multicast_querier_exists(br, eth_hdr(skb), mdst, vid)) {
 			if ((mdst && mdst->host_joined) ||
 			    br_multicast_is_router(br)) {
 				local_rcv = true;
diff --git a/net/bridge/br_multicast.c b/net/bridge/br_multicast.c
index 257ac4e25f6d..b4fac25101e4 100644
--- a/net/bridge/br_multicast.c
+++ b/net/bridge/br_multicast.c
@@ -48,8 +48,11 @@ static const struct rhashtable_params br_sg_port_rht_params = {
 	.automatic_shrinking = true,
 };
 
+static void br_ip4_multicast_query_expired(struct timer_list *t);
+static void br_ip4_multicast_querier_expired(struct timer_list *t);
 static void br_multicast_start_querier(struct net_bridge *br,
-				       struct bridge_mcast_own_query *query);
+				       struct bridge_mcast_own_query *query,
+				       u16 vid);
 static void br_multicast_add_router(struct net_bridge *br,
 				    struct net_bridge_port *port);
 static void br_ip4_multicast_leave_group(struct net_bridge *br,
@@ -87,6 +90,112 @@ br_sg_port_find(struct net_bridge *br,
 				      br_sg_port_rht_params);
 }
 
+static void br_mcast_del_other_query(struct bridge_mcast_other_query *query)
+{
+	del_timer_sync(&query->timer);
+	list_del(&query->list);
+	kfree(query);
+}
+
+static struct bridge_mcast_other_query *
+br_mcast_add_other_query(struct list_head *list, u16 vid,
+			 void (*callback)(struct timer_list *t))
+{
+	struct bridge_mcast_other_query *query;
+
+	query = kzalloc(sizeof(*query), GFP_KERNEL);
+	if (!query)
+		return NULL;
+
+	query->vid = vid;
+	timer_setup(&query->timer, callback, 0);
+
+	list_add(&query->list, list);
+
+	return query;
+}
+
+static void br_mcast_del_own_query(struct bridge_mcast_own_query *query)
+{
+	del_timer_sync(&query->timer);
+	list_del(&query->list);
+	kfree(query);
+}
+
+static struct bridge_mcast_own_query *
+br_mcast_add_own_query(struct list_head *list, u16 vid,
+		       void (*callback)(struct timer_list *t))
+{
+	struct bridge_mcast_own_query *query;
+
+	query = kzalloc(sizeof(*query), GFP_KERNEL);
+	if (!query)
+		return NULL;
+
+	query->vid = vid;
+	timer_setup(&query->timer, callback, 0);
+
+	list_add(&query->list, list);
+
+	return query;
+}
+
+static void br_mcast_add_queries(struct net_bridge *br, u16 vid)
+{
+	struct bridge_mcast_other_query *other;
+	struct bridge_mcast_own_query *own;
+
+	own = br_mcast_find_own_query(&br->ip4_own_queries, vid);
+	if (!own) {
+		own = br_mcast_add_own_query(&br->ip4_own_queries, vid,
+					     br_ip4_multicast_query_expired);
+		own->ip4 = true;
+		own->br = br;
+	}
+
+	other = br_mcast_find_other_query(&br->ip4_other_queries, vid);
+	if (!other) {
+		other = br_mcast_add_other_query(&br->ip4_other_queries, vid,
+						 br_ip4_multicast_querier_expired);
+		other->br = br;
+	}
+}
+
+struct bridge_mcast_own_query *
+br_mcast_find_own_query(struct list_head *list, u16 vid)
+{
+	struct bridge_mcast_own_query *query = NULL;
+
+	list_for_each_entry(query, list, list)
+		if (query->vid == vid)
+			return query;
+
+	return NULL;
+}
+
+struct bridge_mcast_other_query *
+br_mcast_find_other_query(struct list_head *list, u16 vid)
+{
+	struct bridge_mcast_other_query *query = NULL;
+
+	list_for_each_entry(query, list, list)
+		if (query->vid == vid)
+			return query;
+
+	return NULL;
+}
+
+bool br_mcast_exist_own_query(struct net_bridge *br)
+{
+	struct bridge_mcast_own_query *query = NULL;
+
+	list_for_each_entry(query, &br->ip4_own_queries, list)
+		if (query->enabled)
+			return true;
+
+	return false;
+}
+
 static struct net_bridge_mdb_entry *br_mdb_ip_get_rcu(struct net_bridge *br,
 						      struct br_ip *dst)
 {
@@ -688,7 +797,8 @@ static struct sk_buff *br_ip4_multicast_alloc_query(struct net_bridge *br,
 						    __be32 ip_dst, __be32 group,
 						    bool with_srcs, bool over_lmqt,
 						    u8 sflag, u8 *igmp_type,
-						    bool *need_rexmit)
+						    bool *need_rexmit,
+						    u16 vid)
 {
 	struct net_bridge_port *p = pg ? pg->key.port : NULL;
 	struct net_bridge_group_src *ent;
@@ -724,6 +834,9 @@ static struct sk_buff *br_ip4_multicast_alloc_query(struct net_bridge *br,
 	}
 
 	pkt_size = sizeof(*eth) + sizeof(*iph) + 4 + igmp_hdr_size;
+	if (br_vlan_enabled(br->dev) && vid != 0)
+		pkt_size += 4;
+
 	if ((p && pkt_size > p->dev->mtu) ||
 	    pkt_size > br->dev->mtu)
 		return NULL;
@@ -732,6 +845,9 @@ static struct sk_buff *br_ip4_multicast_alloc_query(struct net_bridge *br,
 	if (!skb)
 		goto out;
 
+	if (br_vlan_enabled(br->dev) && vid != 0)
+		__vlan_hwaccel_put_tag(skb, htons(ETH_P_8021Q), vid);
+
 	skb->protocol = htons(ETH_P_IP);
 
 	skb_reset_mac_header(skb);
@@ -1008,7 +1124,7 @@ static struct sk_buff *br_multicast_alloc_query(struct net_bridge *br,
 						    ip4_dst, group->dst.ip4,
 						    with_srcs, over_lmqt,
 						    sflag, igmp_type,
-						    need_rexmit);
+						    need_rexmit, group->vid);
 #if IS_ENABLED(CONFIG_IPV6)
 	case htons(ETH_P_IPV6): {
 		struct in6_addr ip6_dst;
@@ -1398,7 +1514,7 @@ static void br_multicast_querier_expired(struct net_bridge *br,
 	if (!netif_running(br->dev) || !br_opt_get(br, BROPT_MULTICAST_ENABLED))
 		goto out;
 
-	br_multicast_start_querier(br, query);
+	br_multicast_start_querier(br, query, query->vid);
 
 out:
 	spin_unlock(&br->multicast_lock);
@@ -1406,9 +1522,14 @@ static void br_multicast_querier_expired(struct net_bridge *br,
 
 static void br_ip4_multicast_querier_expired(struct timer_list *t)
 {
-	struct net_bridge *br = from_timer(br, t, ip4_other_query.timer);
+	struct bridge_mcast_other_query *other_query =
+		from_timer(other_query, t, timer);
+	struct net_bridge *br = other_query->br;
+	struct bridge_mcast_own_query *query;
 
-	br_multicast_querier_expired(br, &br->ip4_own_query);
+	list_for_each_entry(query, &br->ip4_own_queries, list)
+		if (query->enabled && query->vid == other_query->vid)
+			br_multicast_querier_expired(br, query);
 }
 
 #if IS_ENABLED(CONFIG_IPV6)
@@ -1477,19 +1598,22 @@ static void br_multicast_send_query(struct net_bridge *br,
 				    struct bridge_mcast_own_query *own_query)
 {
 	struct bridge_mcast_other_query *other_query = NULL;
+	struct net_bridge_vlan_group *vg;
 	struct br_ip br_group;
 	unsigned long time;
 
 	if (!netif_running(br->dev) ||
-	    !br_opt_get(br, BROPT_MULTICAST_ENABLED) ||
-	    !br_opt_get(br, BROPT_MULTICAST_QUERIER))
+	    !br_opt_get(br, BROPT_MULTICAST_ENABLED))
 		return;
 
-	memset(&br_group.dst, 0, sizeof(br_group.dst));
+	if (!own_query->enabled)
+		return;
 
-	if (port ? (own_query == &port->ip4_own_query) :
-		   (own_query == &br->ip4_own_query)) {
-		other_query = &br->ip4_other_query;
+	memset(&br_group, 0, sizeof(br_group));
+
+	if (own_query->ip4) {
+		other_query = br_mcast_find_other_query(&br->ip4_other_queries,
+							own_query->vid);
 		br_group.proto = htons(ETH_P_IP);
 #if IS_ENABLED(CONFIG_IPV6)
 	} else {
@@ -1501,6 +1625,12 @@ static void br_multicast_send_query(struct net_bridge *br,
 	if (!other_query || timer_pending(&other_query->timer))
 		return;
 
+	br_group.vid = own_query->vid;
+
+	vg =  port ? nbp_vlan_group(port) : br_vlan_group(br);
+	if (vg->pvid == own_query->vid)
+		br_group.vid = 0;
+
 	__br_multicast_send_query(br, port, NULL, NULL, &br_group, false, 0,
 				  NULL);
 
@@ -1533,9 +1663,10 @@ br_multicast_port_query_expired(struct net_bridge_port *port,
 
 static void br_ip4_multicast_port_query_expired(struct timer_list *t)
 {
-	struct net_bridge_port *port = from_timer(port, t, ip4_own_query.timer);
+	struct bridge_mcast_own_query *query = from_timer(query, t, timer);
+	struct net_bridge_port *port = query->port;
 
-	br_multicast_port_query_expired(port, &port->ip4_own_query);
+	br_multicast_port_query_expired(port, query);
 }
 
 #if IS_ENABLED(CONFIG_IPV6)
@@ -1551,17 +1682,23 @@ static void br_multicast_port_group_rexmit(struct timer_list *t)
 {
 	struct net_bridge_port_group *pg = from_timer(pg, t, rexmit_timer);
 	struct bridge_mcast_other_query *other_query = NULL;
+	struct bridge_mcast_own_query *own_query = NULL;
 	struct net_bridge *br = pg->key.port->br;
+	u16 vid = pg->key.addr.vid;
 	bool need_rexmit = false;
 
 	spin_lock(&br->multicast_lock);
+	own_query = br_mcast_find_own_query(&pg->key.port->ip4_own_queries,
+					    vid);
+
 	if (!netif_running(br->dev) || hlist_unhashed(&pg->mglist) ||
 	    !br_opt_get(br, BROPT_MULTICAST_ENABLED) ||
-	    !br_opt_get(br, BROPT_MULTICAST_QUERIER))
+	    !own_query || !own_query->enabled)
 		goto out;
 
 	if (pg->key.addr.proto == htons(ETH_P_IP))
-		other_query = &br->ip4_other_query;
+		other_query = br_mcast_find_other_query(&br->ip4_other_queries,
+							vid);
 #if IS_ENABLED(CONFIG_IPV6)
 	else
 		other_query = &br->ip6_other_query;
@@ -1603,8 +1740,7 @@ int br_multicast_add_port(struct net_bridge_port *port)
 
 	timer_setup(&port->multicast_router_timer,
 		    br_multicast_router_expired, 0);
-	timer_setup(&port->ip4_own_query.timer,
-		    br_ip4_multicast_port_query_expired, 0);
+	INIT_LIST_HEAD(&port->ip4_own_queries);
 #if IS_ENABLED(CONFIG_IPV6)
 	timer_setup(&port->ip6_own_query.timer,
 		    br_ip6_multicast_port_query_expired, 0);
@@ -1621,6 +1757,7 @@ int br_multicast_add_port(struct net_bridge_port *port)
 
 void br_multicast_del_port(struct net_bridge_port *port)
 {
+	struct bridge_mcast_own_query *query, *tmp;
 	struct net_bridge *br = port->br;
 	struct net_bridge_port_group *pg;
 	HLIST_HEAD(deleted_head);
@@ -1635,6 +1772,9 @@ void br_multicast_del_port(struct net_bridge_port *port)
 	br_multicast_gc(&deleted_head);
 	del_timer_sync(&port->multicast_router_timer);
 	free_percpu(port->mcast_stats);
+
+	list_for_each_entry_safe(query, tmp, &port->ip4_own_queries, list)
+		br_mcast_del_own_query(query);
 }
 
 static void br_multicast_enable(struct bridge_mcast_own_query *query)
@@ -1646,14 +1786,49 @@ static void br_multicast_enable(struct bridge_mcast_own_query *query)
 		mod_timer(&query->timer, jiffies);
 }
 
+static void br_multicast_disable(struct bridge_mcast_own_query *query)
+{
+	del_timer_sync(&query->timer);
+}
+
 static void __br_multicast_enable_port(struct net_bridge_port *port)
 {
+	struct bridge_mcast_own_query *query;
 	struct net_bridge *br = port->br;
 
 	if (!br_opt_get(br, BROPT_MULTICAST_ENABLED) || !netif_running(br->dev))
 		return;
 
-	br_multicast_enable(&port->ip4_own_query);
+	list_for_each_entry(query, &br->ip4_own_queries, list) {
+		struct bridge_mcast_own_query *port_query;
+		struct net_bridge_vlan_group *vg;
+
+		if (!query->enabled)
+			continue;
+
+		if (br_vlan_enabled(br->dev)) {
+			vg = nbp_vlan_group(port);
+			if (!vg || (vg && !br_vlan_find(vg, query->vid)))
+				continue;
+		}
+
+		port_query = br_mcast_find_own_query(&port->ip4_own_queries,
+						     query->vid);
+		if (!port_query) {
+			port_query = br_mcast_add_own_query(&port->ip4_own_queries,
+							    query->vid,
+							    br_ip4_multicast_port_query_expired);
+			if (!port_query)
+				continue;
+
+			port_query->port = port;
+		}
+
+		if (query->ip4) {
+			port_query->ip4 = true;
+			br_multicast_enable(port_query);
+		}
+	}
 #if IS_ENABLED(CONFIG_IPV6)
 	br_multicast_enable(&port->ip6_own_query);
 #endif
@@ -1673,6 +1848,7 @@ void br_multicast_enable_port(struct net_bridge_port *port)
 
 void br_multicast_disable_port(struct net_bridge_port *port)
 {
+	struct bridge_mcast_own_query *query;
 	struct net_bridge *br = port->br;
 	struct net_bridge_port_group *pg;
 	struct hlist_node *n;
@@ -1685,7 +1861,8 @@ void br_multicast_disable_port(struct net_bridge_port *port)
 	__del_port_router(port);
 
 	del_timer(&port->multicast_router_timer);
-	del_timer(&port->ip4_own_query.timer);
+	list_for_each_entry(query, &port->ip4_own_queries, list)
+		del_timer(&query->timer);
 #if IS_ENABLED(CONFIG_IPV6)
 	del_timer(&port->ip6_own_query.timer);
 #endif
@@ -1717,17 +1894,23 @@ static void __grp_src_mod_timer(struct net_bridge_group_src *src,
 static void __grp_src_query_marked_and_rexmit(struct net_bridge_port_group *pg)
 {
 	struct bridge_mcast_other_query *other_query = NULL;
+	struct bridge_mcast_own_query *own_query = NULL;
 	struct net_bridge *br = pg->key.port->br;
 	u32 lmqc = br->multicast_last_member_count;
 	unsigned long lmqt, lmi, now = jiffies;
 	struct net_bridge_group_src *ent;
+	u16 vid = pg->key.addr.vid;
+
+	own_query = br_mcast_find_own_query(&pg->key.port->ip4_own_queries,
+					    vid);
 
 	if (!netif_running(br->dev) ||
 	    !br_opt_get(br, BROPT_MULTICAST_ENABLED))
 		return;
 
 	if (pg->key.addr.proto == htons(ETH_P_IP))
-		other_query = &br->ip4_other_query;
+		other_query = br_mcast_find_other_query(&br->ip4_other_queries,
+							vid);
 #if IS_ENABLED(CONFIG_IPV6)
 	else
 		other_query = &br->ip6_other_query;
@@ -1738,7 +1921,7 @@ static void __grp_src_query_marked_and_rexmit(struct net_bridge_port_group *pg)
 		if (ent->flags & BR_SGRP_F_SEND) {
 			ent->flags &= ~BR_SGRP_F_SEND;
 			if (ent->timer.expires > lmqt) {
-				if (br_opt_get(br, BROPT_MULTICAST_QUERIER) &&
+				if (own_query && own_query->enabled &&
 				    other_query &&
 				    !timer_pending(&other_query->timer))
 					ent->src_query_rexmit_cnt = lmqc;
@@ -1747,7 +1930,7 @@ static void __grp_src_query_marked_and_rexmit(struct net_bridge_port_group *pg)
 		}
 	}
 
-	if (!br_opt_get(br, BROPT_MULTICAST_QUERIER) ||
+	if (!own_query || !own_query->enabled ||
 	    !other_query || timer_pending(&other_query->timer))
 		return;
 
@@ -1763,21 +1946,27 @@ static void __grp_src_query_marked_and_rexmit(struct net_bridge_port_group *pg)
 static void __grp_send_query_and_rexmit(struct net_bridge_port_group *pg)
 {
 	struct bridge_mcast_other_query *other_query = NULL;
+	struct bridge_mcast_own_query *own_query = NULL;
 	struct net_bridge *br = pg->key.port->br;
 	unsigned long now = jiffies, lmi;
+	u16 vid = pg->key.addr.vid;
 
 	if (!netif_running(br->dev) ||
 	    !br_opt_get(br, BROPT_MULTICAST_ENABLED))
 		return;
 
+	own_query = br_mcast_find_own_query(&pg->key.port->ip4_own_queries,
+					    vid);
+
 	if (pg->key.addr.proto == htons(ETH_P_IP))
-		other_query = &br->ip4_other_query;
+		other_query = br_mcast_find_other_query(&br->ip4_other_queries,
+							vid);
 #if IS_ENABLED(CONFIG_IPV6)
 	else
 		other_query = &br->ip6_other_query;
 #endif
 
-	if (br_opt_get(br, BROPT_MULTICAST_QUERIER) &&
+	if (own_query && own_query->enabled &&
 	    other_query && !timer_pending(&other_query->timer)) {
 		lmi = now + br->multicast_last_member_interval;
 		pg->grp_query_rexmit_cnt = br->multicast_last_member_count - 1;
@@ -2484,10 +2673,12 @@ static int br_ip6_multicast_mld2_report(struct net_bridge *br,
 
 static bool br_ip4_multicast_select_querier(struct net_bridge *br,
 					    struct net_bridge_port *port,
-					    __be32 saddr)
+					    __be32 saddr,
+					    struct bridge_mcast_own_query *own,
+					    struct bridge_mcast_other_query *other)
 {
-	if (!timer_pending(&br->ip4_own_query.timer) &&
-	    !timer_pending(&br->ip4_other_query.timer))
+	if (own && !timer_pending(&own->timer) &&
+	    !timer_pending(&other->timer))
 		goto update;
 
 	if (!br->ip4_querier.addr.src.ip4)
@@ -2533,11 +2724,14 @@ static bool br_ip6_multicast_select_querier(struct net_bridge *br,
 
 static bool br_multicast_select_querier(struct net_bridge *br,
 					struct net_bridge_port *port,
-					struct br_ip *saddr)
+					struct br_ip *saddr,
+					struct bridge_mcast_own_query *query,
+					struct bridge_mcast_other_query *other_query)
 {
 	switch (saddr->proto) {
 	case htons(ETH_P_IP):
-		return br_ip4_multicast_select_querier(br, port, saddr->src.ip4);
+		return br_ip4_multicast_select_querier(br, port, saddr->src.ip4,
+						       query, other_query);
 #if IS_ENABLED(CONFIG_IPV6)
 	case htons(ETH_P_IPV6):
 		return br_ip6_multicast_select_querier(br, port, &saddr->src.ip6);
@@ -2628,9 +2822,10 @@ static void br_multicast_query_received(struct net_bridge *br,
 					struct net_bridge_port *port,
 					struct bridge_mcast_other_query *query,
 					struct br_ip *saddr,
-					unsigned long max_delay)
+					unsigned long max_delay,
+					struct bridge_mcast_own_query *own_query)
 {
-	if (!br_multicast_select_querier(br, port, saddr))
+	if (!br_multicast_select_querier(br, port, saddr, own_query, query))
 		return;
 
 	br_multicast_update_query_timer(br, query, max_delay);
@@ -2643,6 +2838,8 @@ static void br_ip4_multicast_query(struct net_bridge *br,
 				   u16 vid)
 {
 	unsigned int transport_len = ip_transport_len(skb);
+	struct bridge_mcast_other_query *other_query;
+	struct bridge_mcast_own_query *own_query;
 	const struct iphdr *iph = ip_hdr(skb);
 	struct igmphdr *ih = igmp_hdr(skb);
 	struct net_bridge_mdb_entry *mp;
@@ -2684,8 +2881,13 @@ static void br_ip4_multicast_query(struct net_bridge *br,
 		saddr.proto = htons(ETH_P_IP);
 		saddr.src.ip4 = iph->saddr;
 
-		br_multicast_query_received(br, port, &br->ip4_other_query,
-					    &saddr, max_delay);
+		br_mcast_add_queries(br, vid);
+
+		own_query = br_mcast_find_own_query(&br->ip4_own_queries, vid);
+		other_query = br_mcast_find_other_query(&br->ip4_other_queries,
+							vid);
+		br_multicast_query_received(br, port, other_query, &saddr,
+					    max_delay, own_query);
 		goto out;
 	}
 
@@ -2773,7 +2975,7 @@ static int br_ip6_multicast_query(struct net_bridge *br,
 		saddr.src.ip6 = ipv6_hdr(skb)->saddr;
 
 		br_multicast_query_received(br, port, &br->ip6_other_query,
-					    &saddr, max_delay);
+					    &saddr, max_delay, NULL);
 		goto out;
 	} else if (!group) {
 		goto out;
@@ -2850,7 +3052,7 @@ br_multicast_leave_group(struct net_bridge *br,
 	if (timer_pending(&other_query->timer))
 		goto out;
 
-	if (br_opt_get(br, BROPT_MULTICAST_QUERIER)) {
+	if (own_query && own_query->enabled) {
 		__br_multicast_send_query(br, port, NULL, NULL, &mp->addr,
 					  false, 0, NULL);
 
@@ -2916,21 +3118,26 @@ static void br_ip4_multicast_leave_group(struct net_bridge *br,
 					 __u16 vid,
 					 const unsigned char *src)
 {
-	struct br_ip br_group;
+	struct bridge_mcast_other_query *other_query;
 	struct bridge_mcast_own_query *own_query;
+	struct br_ip br_group;
 
 	if (ipv4_is_local_multicast(group))
 		return;
 
-	own_query = port ? &port->ip4_own_query : &br->ip4_own_query;
+	if (port)
+		own_query = br_mcast_find_own_query(&port->ip4_own_queries, vid);
+	else
+		own_query = br_mcast_find_own_query(&br->ip4_own_queries, vid);
 
 	memset(&br_group, 0, sizeof(br_group));
 	br_group.dst.ip4 = group;
 	br_group.proto = htons(ETH_P_IP);
 	br_group.vid = vid;
 
-	br_multicast_leave_group(br, port, &br_group, &br->ip4_other_query,
-				 own_query, src);
+	other_query = br_mcast_find_other_query(&br->ip4_other_queries, vid);
+	br_multicast_leave_group(br, port, &br_group, other_query, own_query,
+				 src);
 }
 
 #if IS_ENABLED(CONFIG_IPV6)
@@ -3195,9 +3402,10 @@ static void br_multicast_query_expired(struct net_bridge *br,
 
 static void br_ip4_multicast_query_expired(struct timer_list *t)
 {
-	struct net_bridge *br = from_timer(br, t, ip4_own_query.timer);
+	struct bridge_mcast_own_query *query = from_timer(query, t, timer);
+	struct net_bridge *br = query->br;
 
-	br_multicast_query_expired(br, &br->ip4_own_query, &br->ip4_querier);
+	br_multicast_query_expired(br, query, &br->ip4_querier);
 }
 
 #if IS_ENABLED(CONFIG_IPV6)
@@ -3237,7 +3445,6 @@ void br_multicast_init(struct net_bridge *br)
 	br->multicast_querier_interval = 255 * HZ;
 	br->multicast_membership_interval = 260 * HZ;
 
-	br->ip4_other_query.delay_time = 0;
 	br->ip4_querier.port = NULL;
 	br->multicast_igmp_version = 2;
 #if IS_ENABLED(CONFIG_IPV6)
@@ -3251,10 +3458,8 @@ void br_multicast_init(struct net_bridge *br)
 	spin_lock_init(&br->multicast_lock);
 	timer_setup(&br->multicast_router_timer,
 		    br_multicast_local_router_expired, 0);
-	timer_setup(&br->ip4_other_query.timer,
-		    br_ip4_multicast_querier_expired, 0);
-	timer_setup(&br->ip4_own_query.timer,
-		    br_ip4_multicast_query_expired, 0);
+	INIT_LIST_HEAD(&br->ip4_other_queries);
+	INIT_LIST_HEAD(&br->ip4_own_queries);
 #if IS_ENABLED(CONFIG_IPV6)
 	timer_setup(&br->ip6_other_query.timer,
 		    br_ip6_multicast_querier_expired, 0);
@@ -3341,7 +3546,10 @@ static void __br_multicast_open(struct net_bridge *br,
 
 void br_multicast_open(struct net_bridge *br)
 {
-	__br_multicast_open(br, &br->ip4_own_query);
+	struct bridge_mcast_own_query *query;
+
+	list_for_each_entry(query, &br->ip4_own_queries, list)
+		__br_multicast_open(br, query);
 #if IS_ENABLED(CONFIG_IPV6)
 	__br_multicast_open(br, &br->ip6_own_query);
 #endif
@@ -3349,9 +3557,14 @@ void br_multicast_open(struct net_bridge *br)
 
 void br_multicast_stop(struct net_bridge *br)
 {
+	struct bridge_mcast_other_query *other_query;
+	struct bridge_mcast_own_query *query;
+
 	del_timer_sync(&br->multicast_router_timer);
-	del_timer_sync(&br->ip4_other_query.timer);
-	del_timer_sync(&br->ip4_own_query.timer);
+	list_for_each_entry(other_query, &br->ip4_other_queries, list)
+		del_timer_sync(&other_query->timer);
+	list_for_each_entry(query, &br->ip4_own_queries, list)
+		del_timer_sync(&query->timer);
 #if IS_ENABLED(CONFIG_IPV6)
 	del_timer_sync(&br->ip6_other_query.timer);
 	del_timer_sync(&br->ip6_own_query.timer);
@@ -3461,11 +3674,20 @@ int br_multicast_set_port_router(struct net_bridge_port *p, unsigned long val)
 }
 
 static void br_multicast_start_querier(struct net_bridge *br,
-				       struct bridge_mcast_own_query *query)
+				       struct bridge_mcast_own_query *query,
+				       u16 vid)
 {
+	struct bridge_mcast_own_query *port_query;
+	struct net_bridge_vlan_group *vg;
 	struct net_bridge_port *port;
 
-	__br_multicast_open(br, query);
+	if (br_vlan_enabled(br->dev)) {
+		vg = br_vlan_group(br);
+		if (vg && br_vlan_find(vg, vid))
+			__br_multicast_open(br, query);
+	} else {
+		__br_multicast_open(br, query);
+	}
 
 	rcu_read_lock();
 	list_for_each_entry_rcu(port, &br->port_list, list) {
@@ -3473,11 +3695,66 @@ static void br_multicast_start_querier(struct net_bridge *br,
 		    port->state == BR_STATE_BLOCKING)
 			continue;
 
-		if (query == &br->ip4_own_query)
-			br_multicast_enable(&port->ip4_own_query);
+		if (br_vlan_enabled(br->dev)) {
+			vg = nbp_vlan_group(port);
+			if (!vg || (vg && !br_vlan_find(vg, vid)))
+				continue;
+		}
+
+		port_query = br_mcast_find_own_query(&port->ip4_own_queries,
+						     vid);
+		if (!port_query)
+			continue;
+
+		port_query->enabled = true;
+
+		if (query->ip4) {
+			port_query->ip4 = true;
+			br_multicast_enable(port_query);
+		}
 #if IS_ENABLED(CONFIG_IPV6)
-		else
+		else {
 			br_multicast_enable(&port->ip6_own_query);
+		}
+#endif
+	}
+	rcu_read_unlock();
+}
+
+static void br_multicast_stop_querier(struct net_bridge *br,
+				      struct bridge_mcast_own_query *query,
+				      u16 vid)
+{
+	struct bridge_mcast_own_query *port_query;
+	struct net_bridge_vlan_group *vg;
+	struct net_bridge_port *port;
+
+	query->enabled = false;
+
+	rcu_read_lock();
+	list_for_each_entry_rcu(port, &br->port_list, list) {
+		if (port->state == BR_STATE_DISABLED ||
+		    port->state == BR_STATE_BLOCKING)
+			continue;
+
+		if (br_vlan_enabled(br->dev)) {
+			vg = nbp_vlan_group(port);
+			if (!vg || (vg && !br_vlan_find(vg, vid)))
+				continue;
+		}
+
+		port_query = br_mcast_find_own_query(&port->ip4_own_queries,
+						     vid);
+		if (!port_query)
+			continue;
+
+		port_query->enabled = false;
+
+		if (query->ip4)
+			br_multicast_disable(port_query);
+#if IS_ENABLED(CONFIG_IPV6)
+		else
+			br_multicast_disable(&port->ip6_own_query);
 #endif
 	}
 	rcu_read_unlock();
@@ -3553,32 +3830,55 @@ bool br_multicast_router(const struct net_device *dev)
 }
 EXPORT_SYMBOL_GPL(br_multicast_router);
 
-int br_multicast_set_querier(struct net_bridge *br, unsigned long val)
+int br_multicast_set_querier(struct net_bridge *br, unsigned long val, u16 vid)
 {
+	struct bridge_mcast_other_query *other_query;
+	struct bridge_mcast_own_query *query;
+	struct net_bridge_vlan_group *vg;
 	unsigned long max_delay;
 
 	val = !!val;
 
+	if (vid == 0) {
+		vg = br_vlan_group(br);
+		if (vg)
+			vid = vg->pvid;
+	}
+
 	spin_lock_bh(&br->multicast_lock);
-	if (br_opt_get(br, BROPT_MULTICAST_QUERIER) == val)
+	query = br_mcast_find_own_query(&br->ip4_own_queries, vid);
+	if (!query) {
+		if (br_vlan_enabled(br->dev))
+			goto unlock;
+
+		br_mcast_add_queries(br, vid);
+	}
+
+	other_query = br_mcast_find_other_query(&br->ip4_other_queries, vid);
+	if (!other_query)
 		goto unlock;
 
-	br_opt_toggle(br, BROPT_MULTICAST_QUERIER, !!val);
-	if (!val)
+	if (!val && query) {
+		br_multicast_stop_querier(br, query, vid);
 		goto unlock;
+	}
 
-	max_delay = br->multicast_query_response_interval;
+	if (val & query->enabled)
+		goto unlock;
 
-	if (!timer_pending(&br->ip4_other_query.timer))
-		br->ip4_other_query.delay_time = jiffies + max_delay;
+	query->enabled = true;
 
-	br_multicast_start_querier(br, &br->ip4_own_query);
+	max_delay = br->multicast_query_response_interval;
+	if (!timer_pending(&other_query->timer))
+		other_query->delay_time = jiffies + max_delay;
+
+	br_multicast_start_querier(br, query, vid);
 
 #if IS_ENABLED(CONFIG_IPV6)
 	if (!timer_pending(&br->ip6_other_query.timer))
 		br->ip6_other_query.delay_time = jiffies + max_delay;
 
-	br_multicast_start_querier(br, &br->ip6_own_query);
+	br_multicast_start_querier(br, &br->ip6_own_query, vid);
 #endif
 
 unlock:
@@ -3587,6 +3887,79 @@ int br_multicast_set_querier(struct net_bridge *br, unsigned long val)
 	return 0;
 }
 
+void br_multicast_vlan_add(struct net_bridge_vlan *v)
+{
+	struct bridge_mcast_own_query *query, *port_query;
+	struct net_bridge_port *p;
+	struct net_bridge *br;
+
+	if (br_vlan_is_master(v)) {
+		br_mcast_add_queries(v->br, v->vid);
+		return;
+	}
+
+	p = v->port;
+	br = p->br;
+
+	query = br_mcast_find_own_query(&br->ip4_own_queries, v->vid);
+
+	port_query = br_mcast_add_own_query(&p->ip4_own_queries,
+					    v->vid,
+					    br_ip4_multicast_port_query_expired);
+	if (!port_query)
+		return;
+
+	port_query->port = p;
+	port_query->ip4 = true;
+
+	if (query->enabled) {
+		port_query->enabled = true;
+		br_multicast_enable(port_query);
+	}
+}
+
+void br_multicast_vlan_del(struct net_bridge_vlan *v)
+{
+	struct bridge_mcast_other_query *other_query, *other_tmp;
+	struct bridge_mcast_own_query *query, *tmp;
+	struct net_bridge_port *p;
+	struct net_bridge *br;
+
+	if (br_vlan_is_master(v)) {
+		br = v->br;
+
+		list_for_each_entry_safe(other_query, other_tmp,
+					 &br->ip4_other_queries, list)
+			if (other_query->vid == v->vid)
+				br_mcast_del_other_query(other_query);
+
+		list_for_each_entry_safe(query, tmp, &br->ip4_own_queries, list)
+			if (query->vid == v->vid)
+				br_mcast_del_own_query(query);
+
+		return;
+	}
+
+	p = v->port;
+
+	list_for_each_entry_safe(query, tmp, &p->ip4_own_queries, list) {
+		if (query->vid == v->vid)
+			br_mcast_del_own_query(query);
+	}
+}
+
+void br_multicast_vlan_toggle(struct net_bridge *br, bool on)
+{
+	struct bridge_mcast_own_query *query;
+
+	list_for_each_entry(query, &br->ip4_own_queries, list) {
+		if (!on)
+			br_multicast_stop_querier(br, query, query->vid);
+		else
+			br_multicast_start_querier(br, query, query->vid);
+	}
+}
+
 int br_multicast_set_igmp_version(struct net_bridge *br, unsigned long val)
 {
 	/* Currently we support only version 2 and 3 */
@@ -3711,7 +4084,7 @@ bool br_multicast_has_querier_anywhere(struct net_device *dev, int proto)
 	memset(&eth, 0, sizeof(eth));
 	eth.h_proto = htons(proto);
 
-	ret = br_multicast_querier_exists(br, &eth, NULL);
+	ret = br_multicast_any_querier_exists(br, &eth);
 
 unlock:
 	rcu_read_unlock();
@@ -3746,7 +4119,7 @@ bool br_multicast_has_querier_adjacent(struct net_device *dev, int proto)
 
 	switch (proto) {
 	case ETH_P_IP:
-		if (!timer_pending(&br->ip4_other_query.timer) ||
+		if (!br_multicast_any_querier_adjacent(br) ||
 		    rcu_dereference(br->ip4_querier.port) == port)
 			goto unlock;
 		break;
diff --git a/net/bridge/br_netlink.c b/net/bridge/br_netlink.c
index 49700ce0e919..d32f4c185364 100644
--- a/net/bridge/br_netlink.c
+++ b/net/bridge/br_netlink.c
@@ -1186,6 +1186,7 @@ static const struct nla_policy br_policy[IFLA_BR_MAX + 1] = {
 	[IFLA_BR_VLAN_STATS_PER_PORT] = { .type = NLA_U8 },
 	[IFLA_BR_MULTI_BOOLOPT] =
 		NLA_POLICY_EXACT_LEN(sizeof(struct br_boolopt_multi)),
+	[IFLA_BR_MCAST_QUERIER_VID] = { .type = NLA_U16 },
 };
 
 static int br_changelink(struct net_device *brdev, struct nlattr *tb[],
@@ -1193,6 +1194,7 @@ static int br_changelink(struct net_device *brdev, struct nlattr *tb[],
 			 struct netlink_ext_ack *extack)
 {
 	struct net_bridge *br = netdev_priv(brdev);
+	u16 vid = 0;
 	int err;
 
 	if (!data)
@@ -1204,6 +1206,9 @@ static int br_changelink(struct net_device *brdev, struct nlattr *tb[],
 			return err;
 	}
 
+	if (data[IFLA_BR_MCAST_QUERIER_VID])
+		vid = nla_get_u16(data[IFLA_BR_MCAST_QUERIER_VID]);
+
 	if (data[IFLA_BR_HELLO_TIME]) {
 		err = br_set_hello_time(br, nla_get_u32(data[IFLA_BR_HELLO_TIME]));
 		if (err)
@@ -1333,7 +1338,7 @@ static int br_changelink(struct net_device *brdev, struct nlattr *tb[],
 	if (data[IFLA_BR_MCAST_QUERIER]) {
 		u8 mcast_querier = nla_get_u8(data[IFLA_BR_MCAST_QUERIER]);
 
-		err = br_multicast_set_querier(br, mcast_querier);
+		err = br_multicast_set_querier(br, mcast_querier, vid);
 		if (err)
 			return err;
 	}
@@ -1596,7 +1601,7 @@ static int br_fill_info(struct sk_buff *skb, const struct net_device *brdev)
 	    nla_put_u8(skb, IFLA_BR_MCAST_QUERY_USE_IFADDR,
 		       br_opt_get(br, BROPT_MULTICAST_QUERY_USE_IFADDR)) ||
 	    nla_put_u8(skb, IFLA_BR_MCAST_QUERIER,
-		       br_opt_get(br, BROPT_MULTICAST_QUERIER)) ||
+		       br_mcast_exist_own_query(br)) ||
 	    nla_put_u8(skb, IFLA_BR_MCAST_STATS_ENABLED,
 		       br_opt_get(br, BROPT_MULTICAST_STATS_ENABLED)) ||
 	    nla_put_u32(skb, IFLA_BR_MCAST_HASH_ELASTICITY, RHT_ELASTICITY) ||
diff --git a/net/bridge/br_private.h b/net/bridge/br_private.h
index d62c6e1af64a..84f597f542b1 100644
--- a/net/bridge/br_private.h
+++ b/net/bridge/br_private.h
@@ -66,14 +66,24 @@ struct mac_addr {
 #ifdef CONFIG_BRIDGE_IGMP_SNOOPING
 /* our own querier */
 struct bridge_mcast_own_query {
+	struct list_head	list;
 	struct timer_list	timer;
 	u32			startup_sent;
+	struct net_bridge_port	*port;
+	struct net_bridge	*br;
+	bool			ip4;
+	u16			vid;
+	bool			enabled;
 };
 
 /* other querier */
 struct bridge_mcast_other_query {
+	struct list_head		list;
 	struct timer_list		timer;
 	unsigned long			delay_time;
+	struct net_bridge		*br;
+	bool				ip4;
+	u16				vid;
 };
 
 /* selected querier */
@@ -304,7 +314,7 @@ struct net_bridge_port {
 	struct rcu_head			rcu;
 
 #ifdef CONFIG_BRIDGE_IGMP_SNOOPING
-	struct bridge_mcast_own_query	ip4_own_query;
+	struct list_head		ip4_own_queries;
 #if IS_ENABLED(CONFIG_IPV6)
 	struct bridge_mcast_own_query	ip6_own_query;
 #endif /* IS_ENABLED(CONFIG_IPV6) */
@@ -448,8 +458,8 @@ struct net_bridge {
 	struct hlist_head		router_list;
 
 	struct timer_list		multicast_router_timer;
-	struct bridge_mcast_other_query	ip4_other_query;
-	struct bridge_mcast_own_query	ip4_own_query;
+	struct list_head		ip4_other_queries;
+	struct list_head		ip4_own_queries;
 	struct bridge_mcast_querier	ip4_querier;
 	struct bridge_mcast_stats	__percpu *mcast_stats;
 #if IS_ENABLED(CONFIG_IPV6)
@@ -788,6 +798,9 @@ int br_ioctl_deviceless_stub(struct net *net, unsigned int cmd,
 
 /* br_multicast.c */
 #ifdef CONFIG_BRIDGE_IGMP_SNOOPING
+void br_multicast_vlan_add(struct net_bridge_vlan *v);
+void br_multicast_vlan_del(struct net_bridge_vlan *v);
+void br_multicast_vlan_toggle(struct net_bridge *br, bool on);
 int br_multicast_rcv(struct net_bridge *br, struct net_bridge_port *port,
 		     struct sk_buff *skb, u16 vid);
 struct net_bridge_mdb_entry *br_mdb_get(struct net_bridge *br,
@@ -807,7 +820,7 @@ void br_multicast_flood(struct net_bridge_mdb_entry *mdst,
 int br_multicast_set_router(struct net_bridge *br, unsigned long val);
 int br_multicast_set_port_router(struct net_bridge_port *p, unsigned long val);
 int br_multicast_toggle(struct net_bridge *br, unsigned long val);
-int br_multicast_set_querier(struct net_bridge *br, unsigned long val);
+int br_multicast_set_querier(struct net_bridge *br, unsigned long val, u16 vid);
 int br_multicast_set_hash_max(struct net_bridge *br, unsigned long val);
 int br_multicast_set_igmp_version(struct net_bridge *br, unsigned long val);
 #if IS_ENABLED(CONFIG_IPV6)
@@ -846,6 +859,11 @@ 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 bridge_mcast_other_query *
+br_mcast_find_other_query(struct list_head *list, u16 vid);
+struct bridge_mcast_own_query *
+br_mcast_find_own_query(struct list_head *list, u16 vid);
+bool br_mcast_exist_own_query(struct net_bridge *br);
 
 static inline bool br_group_is_l2(const struct br_ip *group)
 {
@@ -865,11 +883,15 @@ static inline bool br_multicast_is_router(struct net_bridge *br)
 static inline bool
 __br_multicast_querier_exists(struct net_bridge *br,
 				struct bridge_mcast_other_query *querier,
-				const bool is_ipv6)
+				const bool is_ipv6,
+				u16 vid)
 {
+	struct bridge_mcast_own_query *query;
 	bool own_querier_enabled;
 
-	if (br_opt_get(br, BROPT_MULTICAST_QUERIER)) {
+	query = br_mcast_find_own_query(&br->ip4_own_queries, vid);
+
+	if (query && query->enabled) {
 		if (is_ipv6 && !br_opt_get(br, BROPT_HAS_IPV6_ADDR))
 			own_querier_enabled = false;
 		else
@@ -878,28 +900,62 @@ __br_multicast_querier_exists(struct net_bridge *br,
 		own_querier_enabled = false;
 	}
 
+	if (!querier)
+		return own_querier_enabled;
+
 	return time_is_before_jiffies(querier->delay_time) &&
 	       (own_querier_enabled || timer_pending(&querier->timer));
 }
 
 static inline bool br_multicast_querier_exists(struct net_bridge *br,
 					       struct ethhdr *eth,
-					       const struct net_bridge_mdb_entry *mdb)
+					       const struct net_bridge_mdb_entry *mdb,
+					       u16 vid)
 {
+	struct bridge_mcast_other_query *query =
+		br_mcast_find_other_query(&br->ip4_other_queries, vid);
+
 	switch (eth->h_proto) {
 	case (htons(ETH_P_IP)):
-		return __br_multicast_querier_exists(br,
-			&br->ip4_other_query, false);
+		return __br_multicast_querier_exists(br, query, false, vid);
 #if IS_ENABLED(CONFIG_IPV6)
 	case (htons(ETH_P_IPV6)):
 		return __br_multicast_querier_exists(br,
-			&br->ip6_other_query, true);
+			&br->ip6_other_query, true, vid);
 #endif
 	default:
 		return !!mdb && br_group_is_l2(&mdb->addr);
 	}
 }
 
+static inline bool br_multicast_any_querier_exists(struct net_bridge *br,
+						   struct ethhdr *eth)
+{
+	struct bridge_mcast_other_query *query;
+
+	list_for_each_entry(query, &br->ip4_other_queries, list) {
+		if (!timer_pending(&query->timer))
+			continue;
+
+		if (br_multicast_querier_exists(br, eth, NULL, query->vid))
+			return true;
+	}
+
+	return false;
+}
+
+static inline bool br_multicast_any_querier_adjacent(struct net_bridge *br)
+{
+	struct bridge_mcast_other_query *query;
+
+	list_for_each_entry(query, &br->ip4_other_queries, list) {
+		if (timer_pending(&query->timer))
+			return true;
+	}
+
+	return false;
+}
+
 static inline bool br_multicast_is_star_g(const struct br_ip *ip)
 {
 	switch (ip->proto) {
@@ -1015,7 +1071,19 @@ static inline bool br_multicast_is_router(struct net_bridge *br)
 
 static inline bool br_multicast_querier_exists(struct net_bridge *br,
 					       struct ethhdr *eth,
-					       const struct net_bridge_mdb_entry *mdb)
+					       const struct net_bridge_mdb_entry *mdb,
+					       u16 vid)
+{
+	return false;
+}
+
+static inline bool br_multicast_any_querier_exists(struct net_bridge *br,
+						   struct ethhdr *eth)
+{
+	return false;
+}
+
+static inline bool br_multicast_any_querier_adjacent(struct net_bridge *br)
 {
 	return false;
 }
diff --git a/net/bridge/br_sysfs_br.c b/net/bridge/br_sysfs_br.c
index 7db06e3f642a..23bf6a065d78 100644
--- a/net/bridge/br_sysfs_br.c
+++ b/net/bridge/br_sysfs_br.c
@@ -51,6 +51,33 @@ static ssize_t store_bridge_parm(struct device *d,
 	return err ? err : len;
 }
 
+static ssize_t store_bridge_parm2(struct device *d,
+				  const char *buf, size_t len,
+				  int (*set)(struct net_bridge *, unsigned long, u16))
+{
+	struct net_bridge *br = to_bridge(d);
+	char *endp;
+	unsigned long val;
+	int err;
+
+	if (!ns_capable(dev_net(br->dev)->user_ns, CAP_NET_ADMIN))
+		return -EPERM;
+
+	val = simple_strtoul(buf, &endp, 0);
+	if (endp == buf)
+		return -EINVAL;
+
+	if (!rtnl_trylock())
+		return restart_syscall();
+
+	err = (*set)(br, val, 0);
+	if (!err)
+		netdev_state_change(br->dev);
+	rtnl_unlock();
+
+	return err ? err : len;
+}
+
 
 static ssize_t forward_delay_show(struct device *d,
 				  struct device_attribute *attr, char *buf)
@@ -404,14 +431,14 @@ static ssize_t multicast_querier_show(struct device *d,
 				      char *buf)
 {
 	struct net_bridge *br = to_bridge(d);
-	return sprintf(buf, "%d\n", br_opt_get(br, BROPT_MULTICAST_QUERIER));
+	return sprintf(buf, "%d\n", br_mcast_exist_own_query(br));
 }
 
 static ssize_t multicast_querier_store(struct device *d,
 				       struct device_attribute *attr,
 				       const char *buf, size_t len)
 {
-	return store_bridge_parm(d, buf, len, br_multicast_set_querier);
+	return store_bridge_parm2(d, buf, len, br_multicast_set_querier);
 }
 static DEVICE_ATTR_RW(multicast_querier);
 
diff --git a/net/bridge/br_vlan.c b/net/bridge/br_vlan.c
index 701cad646b20..2e0b544a3560 100644
--- a/net/bridge/br_vlan.c
+++ b/net/bridge/br_vlan.c
@@ -308,6 +308,7 @@ static int __vlan_add(struct net_bridge_vlan *v, u16 flags,
 
 	__vlan_add_list(v);
 	__vlan_add_flags(v, flags);
+	br_multicast_vlan_add(v);
 
 	if (p)
 		nbp_vlan_set_vlan_dev_state(p, v->vid);
@@ -353,6 +354,7 @@ static int __vlan_del(struct net_bridge_vlan *v)
 		masterv = v->brvlan;
 	}
 
+	br_multicast_vlan_del(v);
 	__vlan_delete_pvid(vg, v->vid);
 	if (p) {
 		err = __vlan_vid_del(p->dev, p->br, v);
@@ -827,6 +829,7 @@ int __br_vlan_filter_toggle(struct net_bridge *br, unsigned long val)
 	br_manage_promisc(br);
 	recalculate_group_addr(br);
 	br_recalculate_fwd_mask(br);
+	br_multicast_vlan_toggle(br, !!val);
 
 	return 0;
 }
-- 
2.27.0


^ permalink raw reply related	[flat|nested] 16+ messages in thread

* Re: [RFC PATCH v2] net: bridge: igmp: Extend IGMP query to be per vlan
  2021-01-12 13:59 ` [Bridge] " Horatiu Vultur
  (?)
@ 2021-01-13  1:21 ` kernel test robot
  -1 siblings, 0 replies; 16+ messages in thread
From: kernel test robot @ 2021-01-13  1:21 UTC (permalink / raw)
  To: kbuild-all

[-- Attachment #1: Type: text/plain, Size: 3820 bytes --]

Hi Horatiu,

[FYI, it's a private test report for your RFC patch.]
[auto build test WARNING on net-next/master]
[also build test WARNING on net/master linus/master v5.11-rc3 next-20210112]
[cannot apply to sparc-next/master]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch]

url:    https://github.com/0day-ci/linux/commits/Horatiu-Vultur/net-bridge-igmp-Extend-IGMP-query-to-be-per-vlan/20210112-220655
base:   https://git.kernel.org/pub/scm/linux/kernel/git/davem/net-next.git c73a45965dd54a10c368191804b9de661eee1007
config: microblaze-randconfig-c004-20210112 (attached as .config)
compiler: microblaze-linux-gcc (GCC) 9.3.0
reproduce (this is a W=1 build):
        wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross
        chmod +x ~/bin/make.cross
        # https://github.com/0day-ci/linux/commit/8d72e3d611eb9514714411569a9a98cda24d04c2
        git remote add linux-review https://github.com/0day-ci/linux
        git fetch --no-tags linux-review Horatiu-Vultur/net-bridge-igmp-Extend-IGMP-query-to-be-per-vlan/20210112-220655
        git checkout 8d72e3d611eb9514714411569a9a98cda24d04c2
        # save the attached .config to linux build tree
        COMPILER_INSTALL_PATH=$HOME/0day COMPILER=gcc-9.3.0 make.cross ARCH=microblaze 

If you fix the issue, kindly add following tag as appropriate
Reported-by: kernel test robot <lkp@intel.com>

All warnings (new ones prefixed by >>):

   net/bridge/br_netlink.c: In function 'br_changelink':
>> net/bridge/br_netlink.c:1197:6: warning: variable 'vid' set but not used [-Wunused-but-set-variable]
    1197 |  u16 vid = 0;
         |      ^~~


vim +/vid +1197 net/bridge/br_netlink.c

  1191	
  1192	static int br_changelink(struct net_device *brdev, struct nlattr *tb[],
  1193				 struct nlattr *data[],
  1194				 struct netlink_ext_ack *extack)
  1195	{
  1196		struct net_bridge *br = netdev_priv(brdev);
> 1197		u16 vid = 0;
  1198		int err;
  1199	
  1200		if (!data)
  1201			return 0;
  1202	
  1203		if (data[IFLA_BR_FORWARD_DELAY]) {
  1204			err = br_set_forward_delay(br, nla_get_u32(data[IFLA_BR_FORWARD_DELAY]));
  1205			if (err)
  1206				return err;
  1207		}
  1208	
  1209		if (data[IFLA_BR_MCAST_QUERIER_VID])
  1210			vid = nla_get_u16(data[IFLA_BR_MCAST_QUERIER_VID]);
  1211	
  1212		if (data[IFLA_BR_HELLO_TIME]) {
  1213			err = br_set_hello_time(br, nla_get_u32(data[IFLA_BR_HELLO_TIME]));
  1214			if (err)
  1215				return err;
  1216		}
  1217	
  1218		if (data[IFLA_BR_MAX_AGE]) {
  1219			err = br_set_max_age(br, nla_get_u32(data[IFLA_BR_MAX_AGE]));
  1220			if (err)
  1221				return err;
  1222		}
  1223	
  1224		if (data[IFLA_BR_AGEING_TIME]) {
  1225			err = br_set_ageing_time(br, nla_get_u32(data[IFLA_BR_AGEING_TIME]));
  1226			if (err)
  1227				return err;
  1228		}
  1229	
  1230		if (data[IFLA_BR_STP_STATE]) {
  1231			u32 stp_enabled = nla_get_u32(data[IFLA_BR_STP_STATE]);
  1232	
  1233			err = br_stp_set_enabled(br, stp_enabled, extack);
  1234			if (err)
  1235				return err;
  1236		}
  1237	
  1238		if (data[IFLA_BR_PRIORITY]) {
  1239			u32 priority = nla_get_u16(data[IFLA_BR_PRIORITY]);
  1240	
  1241			br_stp_set_bridge_priority(br, priority);
  1242		}
  1243	
  1244		if (data[IFLA_BR_VLAN_FILTERING]) {
  1245			u8 vlan_filter = nla_get_u8(data[IFLA_BR_VLAN_FILTERING]);
  1246	
  1247			err = __br_vlan_filter_toggle(br, vlan_filter);
  1248			if (err)
  1249				return err;
  1250		}
  1251	

---
0-DAY CI Kernel Test Service, Intel Corporation
https://lists.01.org/hyperkitty/list/kbuild-all(a)lists.01.org

[-- Attachment #2: config.gz --]
[-- Type: application/gzip, Size: 30578 bytes --]

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [RFC PATCH v2] net: bridge: igmp: Extend IGMP query to be per vlan
  2021-01-12 13:59 ` [Bridge] " Horatiu Vultur
@ 2021-01-13  9:21   ` Dan Carpenter
  -1 siblings, 0 replies; 16+ messages in thread
From: Dan Carpenter @ 2021-01-13  9:21 UTC (permalink / raw)
  To: kbuild

[-- Attachment #1: Type: text/plain, Size: 4616 bytes --]

Hi Horatiu,

url:    https://github.com/0day-ci/linux/commits/Horatiu-Vultur/net-bridge-igmp-Extend-IGMP-query-to-be-per-vlan/20210112-220655
base:   https://git.kernel.org/pub/scm/linux/kernel/git/davem/net-next.git  c73a45965dd54a10c368191804b9de661eee1007
config: i386-randconfig-m021-20210113 (attached as .config)
compiler: gcc-9 (Debian 9.3.0-15) 9.3.0

If you fix the issue, kindly add following tag as appropriate
Reported-by: kernel test robot <lkp@intel.com>
Reported-by: Dan Carpenter <dan.carpenter@oracle.com>

smatch warnings:
net/bridge/br_multicast.c:3866 br_multicast_set_querier() error: we previously assumed 'query' could be null (see line 3850)
net/bridge/br_multicast.c:3866 br_multicast_set_querier() warn: maybe use && instead of &

vim +/query +3866 net/bridge/br_multicast.c

8d72e3d611eb9514 Horatiu Vultur 2021-01-12  3833  int br_multicast_set_querier(struct net_bridge *br, unsigned long val, u16 vid)
c5c23260594c5701 Herbert Xu     2012-04-13  3834  {
8d72e3d611eb9514 Horatiu Vultur 2021-01-12  3835  	struct bridge_mcast_other_query *other_query;
8d72e3d611eb9514 Horatiu Vultur 2021-01-12  3836  	struct bridge_mcast_own_query *query;
8d72e3d611eb9514 Horatiu Vultur 2021-01-12  3837  	struct net_bridge_vlan_group *vg;
b00589af3b047363 Linus Lüssing  2013-08-01  3838  	unsigned long max_delay;
b00589af3b047363 Linus Lüssing  2013-08-01  3839  
c5c23260594c5701 Herbert Xu     2012-04-13  3840  	val = !!val;
c5c23260594c5701 Herbert Xu     2012-04-13  3841  
8d72e3d611eb9514 Horatiu Vultur 2021-01-12  3842  	if (vid == 0) {
8d72e3d611eb9514 Horatiu Vultur 2021-01-12  3843  		vg = br_vlan_group(br);
8d72e3d611eb9514 Horatiu Vultur 2021-01-12  3844  		if (vg)
8d72e3d611eb9514 Horatiu Vultur 2021-01-12  3845  			vid = vg->pvid;
8d72e3d611eb9514 Horatiu Vultur 2021-01-12  3846  	}
8d72e3d611eb9514 Horatiu Vultur 2021-01-12  3847  
c5c23260594c5701 Herbert Xu     2012-04-13  3848  	spin_lock_bh(&br->multicast_lock);
8d72e3d611eb9514 Horatiu Vultur 2021-01-12  3849  	query = br_mcast_find_own_query(&br->ip4_own_queries, vid);
8d72e3d611eb9514 Horatiu Vultur 2021-01-12 @3850  	if (!query) {
8d72e3d611eb9514 Horatiu Vultur 2021-01-12  3851  		if (br_vlan_enabled(br->dev))
c5c23260594c5701 Herbert Xu     2012-04-13  3852  			goto unlock;
c5c23260594c5701 Herbert Xu     2012-04-13  3853  
8d72e3d611eb9514 Horatiu Vultur 2021-01-12  3854  		br_mcast_add_queries(br, vid);
8d72e3d611eb9514 Horatiu Vultur 2021-01-12  3855  	}
8d72e3d611eb9514 Horatiu Vultur 2021-01-12  3856  
8d72e3d611eb9514 Horatiu Vultur 2021-01-12  3857  	other_query = br_mcast_find_other_query(&br->ip4_other_queries, vid);
8d72e3d611eb9514 Horatiu Vultur 2021-01-12  3858  	if (!other_query)
b00589af3b047363 Linus Lüssing  2013-08-01  3859  		goto unlock;
b00589af3b047363 Linus Lüssing  2013-08-01  3860  
8d72e3d611eb9514 Horatiu Vultur 2021-01-12  3861  	if (!val && query) {
                                                                    ^^^^^
"query" can be NULL.

8d72e3d611eb9514 Horatiu Vultur 2021-01-12  3862  		br_multicast_stop_querier(br, query, vid);
8d72e3d611eb9514 Horatiu Vultur 2021-01-12  3863  		goto unlock;
8d72e3d611eb9514 Horatiu Vultur 2021-01-12  3864  	}
b00589af3b047363 Linus Lüssing  2013-08-01  3865  
8d72e3d611eb9514 Horatiu Vultur 2021-01-12 @3866  	if (val & query->enabled)
                                                            ^^^^^^^^^^^^^^^^^^^^
Smatch suggests that this should be && because I guess "query->enabled"
is a boolean.  One of the difference between & and && is there are no
ordering requirements.

8d72e3d611eb9514 Horatiu Vultur 2021-01-12  3867  		goto unlock;
cc0fdd802859eaeb Linus Lüssing  2013-08-30  3868  
8d72e3d611eb9514 Horatiu Vultur 2021-01-12  3869  	query->enabled = true;
8d72e3d611eb9514 Horatiu Vultur 2021-01-12  3870  
8d72e3d611eb9514 Horatiu Vultur 2021-01-12  3871  	max_delay = br->multicast_query_response_interval;
8d72e3d611eb9514 Horatiu Vultur 2021-01-12  3872  	if (!timer_pending(&other_query->timer))
8d72e3d611eb9514 Horatiu Vultur 2021-01-12  3873  		other_query->delay_time = jiffies + max_delay;
8d72e3d611eb9514 Horatiu Vultur 2021-01-12  3874  
8d72e3d611eb9514 Horatiu Vultur 2021-01-12  3875  	br_multicast_start_querier(br, query, vid);

---
0-DAY CI Kernel Test Service, Intel Corporation
https://lists.01.org/hyperkitty/list/kbuild-all(a)lists.01.org 

_______________________________________________
kbuild mailing list -- kbuild(a)lists.01.org
To unsubscribe send an email to kbuild-leave(a)lists.01.org

[-- Attachment #2: config.gz --]
[-- Type: application/gzip, Size: 33916 bytes --]

^ permalink raw reply	[flat|nested] 16+ messages in thread

* [kbuild] Re: [RFC PATCH v2] net: bridge: igmp: Extend IGMP query to be per vlan
@ 2021-01-13  9:21   ` Dan Carpenter
  0 siblings, 0 replies; 16+ messages in thread
From: Dan Carpenter @ 2021-01-13  9:21 UTC (permalink / raw)
  To: kbuild-all

[-- Attachment #1: Type: text/plain, Size: 4616 bytes --]

Hi Horatiu,

url:    https://github.com/0day-ci/linux/commits/Horatiu-Vultur/net-bridge-igmp-Extend-IGMP-query-to-be-per-vlan/20210112-220655
base:   https://git.kernel.org/pub/scm/linux/kernel/git/davem/net-next.git  c73a45965dd54a10c368191804b9de661eee1007
config: i386-randconfig-m021-20210113 (attached as .config)
compiler: gcc-9 (Debian 9.3.0-15) 9.3.0

If you fix the issue, kindly add following tag as appropriate
Reported-by: kernel test robot <lkp@intel.com>
Reported-by: Dan Carpenter <dan.carpenter@oracle.com>

smatch warnings:
net/bridge/br_multicast.c:3866 br_multicast_set_querier() error: we previously assumed 'query' could be null (see line 3850)
net/bridge/br_multicast.c:3866 br_multicast_set_querier() warn: maybe use && instead of &

vim +/query +3866 net/bridge/br_multicast.c

8d72e3d611eb9514 Horatiu Vultur 2021-01-12  3833  int br_multicast_set_querier(struct net_bridge *br, unsigned long val, u16 vid)
c5c23260594c5701 Herbert Xu     2012-04-13  3834  {
8d72e3d611eb9514 Horatiu Vultur 2021-01-12  3835  	struct bridge_mcast_other_query *other_query;
8d72e3d611eb9514 Horatiu Vultur 2021-01-12  3836  	struct bridge_mcast_own_query *query;
8d72e3d611eb9514 Horatiu Vultur 2021-01-12  3837  	struct net_bridge_vlan_group *vg;
b00589af3b047363 Linus Lüssing  2013-08-01  3838  	unsigned long max_delay;
b00589af3b047363 Linus Lüssing  2013-08-01  3839  
c5c23260594c5701 Herbert Xu     2012-04-13  3840  	val = !!val;
c5c23260594c5701 Herbert Xu     2012-04-13  3841  
8d72e3d611eb9514 Horatiu Vultur 2021-01-12  3842  	if (vid == 0) {
8d72e3d611eb9514 Horatiu Vultur 2021-01-12  3843  		vg = br_vlan_group(br);
8d72e3d611eb9514 Horatiu Vultur 2021-01-12  3844  		if (vg)
8d72e3d611eb9514 Horatiu Vultur 2021-01-12  3845  			vid = vg->pvid;
8d72e3d611eb9514 Horatiu Vultur 2021-01-12  3846  	}
8d72e3d611eb9514 Horatiu Vultur 2021-01-12  3847  
c5c23260594c5701 Herbert Xu     2012-04-13  3848  	spin_lock_bh(&br->multicast_lock);
8d72e3d611eb9514 Horatiu Vultur 2021-01-12  3849  	query = br_mcast_find_own_query(&br->ip4_own_queries, vid);
8d72e3d611eb9514 Horatiu Vultur 2021-01-12 @3850  	if (!query) {
8d72e3d611eb9514 Horatiu Vultur 2021-01-12  3851  		if (br_vlan_enabled(br->dev))
c5c23260594c5701 Herbert Xu     2012-04-13  3852  			goto unlock;
c5c23260594c5701 Herbert Xu     2012-04-13  3853  
8d72e3d611eb9514 Horatiu Vultur 2021-01-12  3854  		br_mcast_add_queries(br, vid);
8d72e3d611eb9514 Horatiu Vultur 2021-01-12  3855  	}
8d72e3d611eb9514 Horatiu Vultur 2021-01-12  3856  
8d72e3d611eb9514 Horatiu Vultur 2021-01-12  3857  	other_query = br_mcast_find_other_query(&br->ip4_other_queries, vid);
8d72e3d611eb9514 Horatiu Vultur 2021-01-12  3858  	if (!other_query)
b00589af3b047363 Linus Lüssing  2013-08-01  3859  		goto unlock;
b00589af3b047363 Linus Lüssing  2013-08-01  3860  
8d72e3d611eb9514 Horatiu Vultur 2021-01-12  3861  	if (!val && query) {
                                                                    ^^^^^
"query" can be NULL.

8d72e3d611eb9514 Horatiu Vultur 2021-01-12  3862  		br_multicast_stop_querier(br, query, vid);
8d72e3d611eb9514 Horatiu Vultur 2021-01-12  3863  		goto unlock;
8d72e3d611eb9514 Horatiu Vultur 2021-01-12  3864  	}
b00589af3b047363 Linus Lüssing  2013-08-01  3865  
8d72e3d611eb9514 Horatiu Vultur 2021-01-12 @3866  	if (val & query->enabled)
                                                            ^^^^^^^^^^^^^^^^^^^^
Smatch suggests that this should be && because I guess "query->enabled"
is a boolean.  One of the difference between & and && is there are no
ordering requirements.

8d72e3d611eb9514 Horatiu Vultur 2021-01-12  3867  		goto unlock;
cc0fdd802859eaeb Linus Lüssing  2013-08-30  3868  
8d72e3d611eb9514 Horatiu Vultur 2021-01-12  3869  	query->enabled = true;
8d72e3d611eb9514 Horatiu Vultur 2021-01-12  3870  
8d72e3d611eb9514 Horatiu Vultur 2021-01-12  3871  	max_delay = br->multicast_query_response_interval;
8d72e3d611eb9514 Horatiu Vultur 2021-01-12  3872  	if (!timer_pending(&other_query->timer))
8d72e3d611eb9514 Horatiu Vultur 2021-01-12  3873  		other_query->delay_time = jiffies + max_delay;
8d72e3d611eb9514 Horatiu Vultur 2021-01-12  3874  
8d72e3d611eb9514 Horatiu Vultur 2021-01-12  3875  	br_multicast_start_querier(br, query, vid);

---
0-DAY CI Kernel Test Service, Intel Corporation
https://lists.01.org/hyperkitty/list/kbuild-all(a)lists.01.org 

_______________________________________________
kbuild mailing list -- kbuild(a)lists.01.org
To unsubscribe send an email to kbuild-leave(a)lists.01.org

[-- Attachment #2: config.gz --]
[-- Type: application/gzip, Size: 33916 bytes --]

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [RFC PATCH v2] net: bridge: igmp: Extend IGMP query to be per vlan
  2021-01-12 13:59 ` [Bridge] " Horatiu Vultur
@ 2021-01-13 12:15   ` Nikolay Aleksandrov
  -1 siblings, 0 replies; 16+ messages in thread
From: Nikolay Aleksandrov @ 2021-01-13 12:15 UTC (permalink / raw)
  To: Horatiu Vultur, davem, kuba, roopa, allan.nielsen, netdev,
	linux-kernel, bridge

On 12/01/2021 15:59, Horatiu Vultur wrote:
> Based on the comments of the previous version, we started to work on a
> new version, so it would be possible to enable/disable queries per vlan.
> This is still work in progress and there are plenty of things that are
> not implemented and tested:
> - ipv6 support
> - the fast path needs to be improved
> - currently it is possible only to enable/disable the queries per vlan,
>   all the other configurations are global
> - toggling vlan_filtering is not tested
> - remove duplicated information
> - etc...
> 
> But there are few things that are working like:
> - sending queries per vlan
> - stop sending queries if there is a better querier per vlan
> - when ports are added/removed from vlan
> - etc...
> 
> We were wondering if this what you had in mind when you proposed to have
> this per vlan? Or we are completely off? Or we should fix some of the
> issues that I mentioned, before you can see more clearly the direction?
> 
> Signed-off-by: Horatiu Vultur <horatiu.vultur@microchip.com>
> ---
>  include/uapi/linux/if_link.h |   1 +
>  net/bridge/br_device.c       |   2 +-
>  net/bridge/br_input.c        |   2 +-
>  net/bridge/br_multicast.c    | 505 ++++++++++++++++++++++++++++++-----
>  net/bridge/br_netlink.c      |   9 +-
>  net/bridge/br_private.h      |  90 ++++++-
>  net/bridge/br_sysfs_br.c     |  31 ++-
>  net/bridge/br_vlan.c         |   3 +
>  8 files changed, 560 insertions(+), 83 deletions(-)
> 

Hi Horatiu,
No, unfortunately not even close. We already have per-port per-vlan and global per-vlan
contexts which are also linked together for each vlan, those must be used for any vlan
configuration and state. The problem is that you'd have to mix igmp and vlan code and
those two live under two different kconfig options, and worse rely on different locks, so
extra care must be taken. Any vlan lookups must use the vlan hashes, (almost) _no_ linear
walks or new lists are needed (the exception is obviously port going down where a walk
over port's vlans is needed). In almost all contexts below a vlan lookup has already been
done by the input functions, the result of that lookup must be saved and re-used. The
vlan options API needs to be used for configuring vlans (per-vlan mcast options), unfortunately
I still haven't upstreamed the iproute2 part, so you might have to do that as well.
Obviously with all of the above the current default situation must not change unless the
user configures it so. If you don't need this asap, I'll probably get to it in two months
after EHT and the new bridge flush api, even we are still carrying an out-of-tree patch
for this which someone (not from cumulus) tried to upstream a few years back, but it also has
wrong design in general. :)

Thanks,
 Nik

> diff --git a/include/uapi/linux/if_link.h b/include/uapi/linux/if_link.h
> index 82708c6db432..11ec1d45c24e 100644
> --- a/include/uapi/linux/if_link.h
> +++ b/include/uapi/linux/if_link.h
> @@ -472,6 +472,7 @@ enum {
>  	IFLA_BR_MCAST_MLD_VERSION,
>  	IFLA_BR_VLAN_STATS_PER_PORT,
>  	IFLA_BR_MULTI_BOOLOPT,
> +	IFLA_BR_MCAST_QUERIER_VID,
>  	__IFLA_BR_MAX,
>  };
>  
> diff --git a/net/bridge/br_device.c b/net/bridge/br_device.c
> index 3f2f06b4dd27..aca4e8074a8f 100644
> --- a/net/bridge/br_device.c
> +++ b/net/bridge/br_device.c
> @@ -89,7 +89,7 @@ netdev_tx_t br_dev_xmit(struct sk_buff *skb, struct net_device *dev)
>  
>  		mdst = br_mdb_get(br, skb, vid);
>  		if ((mdst || BR_INPUT_SKB_CB_MROUTERS_ONLY(skb)) &&
> -		    br_multicast_querier_exists(br, eth_hdr(skb), mdst))
> +		    br_multicast_querier_exists(br, eth_hdr(skb), mdst, vid))
>  			br_multicast_flood(mdst, skb, false, true);
>  		else
>  			br_flood(br, skb, BR_PKT_MULTICAST, false, true);
> diff --git a/net/bridge/br_input.c b/net/bridge/br_input.c
> index 222285d9dae2..03e445af6c1f 100644
> --- a/net/bridge/br_input.c
> +++ b/net/bridge/br_input.c
> @@ -130,7 +130,7 @@ int br_handle_frame_finish(struct net *net, struct sock *sk, struct sk_buff *skb
>  	case BR_PKT_MULTICAST:
>  		mdst = br_mdb_get(br, skb, vid);
>  		if ((mdst || BR_INPUT_SKB_CB_MROUTERS_ONLY(skb)) &&
> -		    br_multicast_querier_exists(br, eth_hdr(skb), mdst)) {
> +		    br_multicast_querier_exists(br, eth_hdr(skb), mdst, vid)) {
>  			if ((mdst && mdst->host_joined) ||
>  			    br_multicast_is_router(br)) {
>  				local_rcv = true;
> diff --git a/net/bridge/br_multicast.c b/net/bridge/br_multicast.c
> index 257ac4e25f6d..b4fac25101e4 100644
> --- a/net/bridge/br_multicast.c
> +++ b/net/bridge/br_multicast.c
> @@ -48,8 +48,11 @@ static const struct rhashtable_params br_sg_port_rht_params = {
>  	.automatic_shrinking = true,
>  };
>  
> +static void br_ip4_multicast_query_expired(struct timer_list *t);
> +static void br_ip4_multicast_querier_expired(struct timer_list *t);
>  static void br_multicast_start_querier(struct net_bridge *br,
> -				       struct bridge_mcast_own_query *query);
> +				       struct bridge_mcast_own_query *query,
> +				       u16 vid);
>  static void br_multicast_add_router(struct net_bridge *br,
>  				    struct net_bridge_port *port);
>  static void br_ip4_multicast_leave_group(struct net_bridge *br,
> @@ -87,6 +90,112 @@ br_sg_port_find(struct net_bridge *br,
>  				      br_sg_port_rht_params);
>  }
>  
> +static void br_mcast_del_other_query(struct bridge_mcast_other_query *query)
> +{
> +	del_timer_sync(&query->timer);
> +	list_del(&query->list);
> +	kfree(query);
> +}
> +
> +static struct bridge_mcast_other_query *
> +br_mcast_add_other_query(struct list_head *list, u16 vid,
> +			 void (*callback)(struct timer_list *t))
> +{
> +	struct bridge_mcast_other_query *query;
> +
> +	query = kzalloc(sizeof(*query), GFP_KERNEL);
> +	if (!query)
> +		return NULL;
> +
> +	query->vid = vid;
> +	timer_setup(&query->timer, callback, 0);
> +
> +	list_add(&query->list, list);
> +
> +	return query;
> +}
> +
> +static void br_mcast_del_own_query(struct bridge_mcast_own_query *query)
> +{
> +	del_timer_sync(&query->timer);
> +	list_del(&query->list);
> +	kfree(query);
> +}
> +
> +static struct bridge_mcast_own_query *
> +br_mcast_add_own_query(struct list_head *list, u16 vid,
> +		       void (*callback)(struct timer_list *t))
> +{
> +	struct bridge_mcast_own_query *query;
> +
> +	query = kzalloc(sizeof(*query), GFP_KERNEL);
> +	if (!query)
> +		return NULL;
> +
> +	query->vid = vid;
> +	timer_setup(&query->timer, callback, 0);
> +
> +	list_add(&query->list, list);
> +
> +	return query;
> +}
> +
> +static void br_mcast_add_queries(struct net_bridge *br, u16 vid)
> +{
> +	struct bridge_mcast_other_query *other;
> +	struct bridge_mcast_own_query *own;
> +
> +	own = br_mcast_find_own_query(&br->ip4_own_queries, vid);
> +	if (!own) {
> +		own = br_mcast_add_own_query(&br->ip4_own_queries, vid,
> +					     br_ip4_multicast_query_expired);
> +		own->ip4 = true;
> +		own->br = br;
> +	}
> +
> +	other = br_mcast_find_other_query(&br->ip4_other_queries, vid);
> +	if (!other) {
> +		other = br_mcast_add_other_query(&br->ip4_other_queries, vid,
> +						 br_ip4_multicast_querier_expired);
> +		other->br = br;
> +	}
> +}
> +
> +struct bridge_mcast_own_query *
> +br_mcast_find_own_query(struct list_head *list, u16 vid)
> +{
> +	struct bridge_mcast_own_query *query = NULL;
> +
> +	list_for_each_entry(query, list, list)
> +		if (query->vid == vid)
> +			return query;
> +
> +	return NULL;
> +}
> +
> +struct bridge_mcast_other_query *
> +br_mcast_find_other_query(struct list_head *list, u16 vid)
> +{
> +	struct bridge_mcast_other_query *query = NULL;
> +
> +	list_for_each_entry(query, list, list)
> +		if (query->vid == vid)
> +			return query;
> +
> +	return NULL;
> +}
> +
> +bool br_mcast_exist_own_query(struct net_bridge *br)
> +{
> +	struct bridge_mcast_own_query *query = NULL;
> +
> +	list_for_each_entry(query, &br->ip4_own_queries, list)
> +		if (query->enabled)
> +			return true;
> +
> +	return false;
> +}
> +
>  static struct net_bridge_mdb_entry *br_mdb_ip_get_rcu(struct net_bridge *br,
>  						      struct br_ip *dst)
>  {
> @@ -688,7 +797,8 @@ static struct sk_buff *br_ip4_multicast_alloc_query(struct net_bridge *br,
>  						    __be32 ip_dst, __be32 group,
>  						    bool with_srcs, bool over_lmqt,
>  						    u8 sflag, u8 *igmp_type,
> -						    bool *need_rexmit)
> +						    bool *need_rexmit,
> +						    u16 vid)
>  {
>  	struct net_bridge_port *p = pg ? pg->key.port : NULL;
>  	struct net_bridge_group_src *ent;
> @@ -724,6 +834,9 @@ static struct sk_buff *br_ip4_multicast_alloc_query(struct net_bridge *br,
>  	}
>  
>  	pkt_size = sizeof(*eth) + sizeof(*iph) + 4 + igmp_hdr_size;
> +	if (br_vlan_enabled(br->dev) && vid != 0)
> +		pkt_size += 4;
> +
>  	if ((p && pkt_size > p->dev->mtu) ||
>  	    pkt_size > br->dev->mtu)
>  		return NULL;
> @@ -732,6 +845,9 @@ static struct sk_buff *br_ip4_multicast_alloc_query(struct net_bridge *br,
>  	if (!skb)
>  		goto out;
>  
> +	if (br_vlan_enabled(br->dev) && vid != 0)
> +		__vlan_hwaccel_put_tag(skb, htons(ETH_P_8021Q), vid);
> +
>  	skb->protocol = htons(ETH_P_IP);
>  
>  	skb_reset_mac_header(skb);
> @@ -1008,7 +1124,7 @@ static struct sk_buff *br_multicast_alloc_query(struct net_bridge *br,
>  						    ip4_dst, group->dst.ip4,
>  						    with_srcs, over_lmqt,
>  						    sflag, igmp_type,
> -						    need_rexmit);
> +						    need_rexmit, group->vid);
>  #if IS_ENABLED(CONFIG_IPV6)
>  	case htons(ETH_P_IPV6): {
>  		struct in6_addr ip6_dst;
> @@ -1398,7 +1514,7 @@ static void br_multicast_querier_expired(struct net_bridge *br,
>  	if (!netif_running(br->dev) || !br_opt_get(br, BROPT_MULTICAST_ENABLED))
>  		goto out;
>  
> -	br_multicast_start_querier(br, query);
> +	br_multicast_start_querier(br, query, query->vid);
>  
>  out:
>  	spin_unlock(&br->multicast_lock);
> @@ -1406,9 +1522,14 @@ static void br_multicast_querier_expired(struct net_bridge *br,
>  
>  static void br_ip4_multicast_querier_expired(struct timer_list *t)
>  {
> -	struct net_bridge *br = from_timer(br, t, ip4_other_query.timer);
> +	struct bridge_mcast_other_query *other_query =
> +		from_timer(other_query, t, timer);
> +	struct net_bridge *br = other_query->br;
> +	struct bridge_mcast_own_query *query;
>  
> -	br_multicast_querier_expired(br, &br->ip4_own_query);
> +	list_for_each_entry(query, &br->ip4_own_queries, list)
> +		if (query->enabled && query->vid == other_query->vid)
> +			br_multicast_querier_expired(br, query);
>  }
>  
>  #if IS_ENABLED(CONFIG_IPV6)
> @@ -1477,19 +1598,22 @@ static void br_multicast_send_query(struct net_bridge *br,
>  				    struct bridge_mcast_own_query *own_query)
>  {
>  	struct bridge_mcast_other_query *other_query = NULL;
> +	struct net_bridge_vlan_group *vg;
>  	struct br_ip br_group;
>  	unsigned long time;
>  
>  	if (!netif_running(br->dev) ||
> -	    !br_opt_get(br, BROPT_MULTICAST_ENABLED) ||
> -	    !br_opt_get(br, BROPT_MULTICAST_QUERIER))
> +	    !br_opt_get(br, BROPT_MULTICAST_ENABLED))
>  		return;
>  
> -	memset(&br_group.dst, 0, sizeof(br_group.dst));
> +	if (!own_query->enabled)
> +		return;
>  
> -	if (port ? (own_query == &port->ip4_own_query) :
> -		   (own_query == &br->ip4_own_query)) {
> -		other_query = &br->ip4_other_query;
> +	memset(&br_group, 0, sizeof(br_group));
> +
> +	if (own_query->ip4) {
> +		other_query = br_mcast_find_other_query(&br->ip4_other_queries,
> +							own_query->vid);
>  		br_group.proto = htons(ETH_P_IP);
>  #if IS_ENABLED(CONFIG_IPV6)
>  	} else {
> @@ -1501,6 +1625,12 @@ static void br_multicast_send_query(struct net_bridge *br,
>  	if (!other_query || timer_pending(&other_query->timer))
>  		return;
>  
> +	br_group.vid = own_query->vid;
> +
> +	vg =  port ? nbp_vlan_group(port) : br_vlan_group(br);
> +	if (vg->pvid == own_query->vid)
> +		br_group.vid = 0;
> +
>  	__br_multicast_send_query(br, port, NULL, NULL, &br_group, false, 0,
>  				  NULL);
>  
> @@ -1533,9 +1663,10 @@ br_multicast_port_query_expired(struct net_bridge_port *port,
>  
>  static void br_ip4_multicast_port_query_expired(struct timer_list *t)
>  {
> -	struct net_bridge_port *port = from_timer(port, t, ip4_own_query.timer);
> +	struct bridge_mcast_own_query *query = from_timer(query, t, timer);
> +	struct net_bridge_port *port = query->port;
>  
> -	br_multicast_port_query_expired(port, &port->ip4_own_query);
> +	br_multicast_port_query_expired(port, query);
>  }
>  
>  #if IS_ENABLED(CONFIG_IPV6)
> @@ -1551,17 +1682,23 @@ static void br_multicast_port_group_rexmit(struct timer_list *t)
>  {
>  	struct net_bridge_port_group *pg = from_timer(pg, t, rexmit_timer);
>  	struct bridge_mcast_other_query *other_query = NULL;
> +	struct bridge_mcast_own_query *own_query = NULL;
>  	struct net_bridge *br = pg->key.port->br;
> +	u16 vid = pg->key.addr.vid;
>  	bool need_rexmit = false;
>  
>  	spin_lock(&br->multicast_lock);
> +	own_query = br_mcast_find_own_query(&pg->key.port->ip4_own_queries,
> +					    vid);
> +
>  	if (!netif_running(br->dev) || hlist_unhashed(&pg->mglist) ||
>  	    !br_opt_get(br, BROPT_MULTICAST_ENABLED) ||
> -	    !br_opt_get(br, BROPT_MULTICAST_QUERIER))
> +	    !own_query || !own_query->enabled)
>  		goto out;
>  
>  	if (pg->key.addr.proto == htons(ETH_P_IP))
> -		other_query = &br->ip4_other_query;
> +		other_query = br_mcast_find_other_query(&br->ip4_other_queries,
> +							vid);
>  #if IS_ENABLED(CONFIG_IPV6)
>  	else
>  		other_query = &br->ip6_other_query;
> @@ -1603,8 +1740,7 @@ int br_multicast_add_port(struct net_bridge_port *port)
>  
>  	timer_setup(&port->multicast_router_timer,
>  		    br_multicast_router_expired, 0);
> -	timer_setup(&port->ip4_own_query.timer,
> -		    br_ip4_multicast_port_query_expired, 0);
> +	INIT_LIST_HEAD(&port->ip4_own_queries);
>  #if IS_ENABLED(CONFIG_IPV6)
>  	timer_setup(&port->ip6_own_query.timer,
>  		    br_ip6_multicast_port_query_expired, 0);
> @@ -1621,6 +1757,7 @@ int br_multicast_add_port(struct net_bridge_port *port)
>  
>  void br_multicast_del_port(struct net_bridge_port *port)
>  {
> +	struct bridge_mcast_own_query *query, *tmp;
>  	struct net_bridge *br = port->br;
>  	struct net_bridge_port_group *pg;
>  	HLIST_HEAD(deleted_head);
> @@ -1635,6 +1772,9 @@ void br_multicast_del_port(struct net_bridge_port *port)
>  	br_multicast_gc(&deleted_head);
>  	del_timer_sync(&port->multicast_router_timer);
>  	free_percpu(port->mcast_stats);
> +
> +	list_for_each_entry_safe(query, tmp, &port->ip4_own_queries, list)
> +		br_mcast_del_own_query(query);
>  }
>  
>  static void br_multicast_enable(struct bridge_mcast_own_query *query)
> @@ -1646,14 +1786,49 @@ static void br_multicast_enable(struct bridge_mcast_own_query *query)
>  		mod_timer(&query->timer, jiffies);
>  }
>  
> +static void br_multicast_disable(struct bridge_mcast_own_query *query)
> +{
> +	del_timer_sync(&query->timer);
> +}
> +
>  static void __br_multicast_enable_port(struct net_bridge_port *port)
>  {
> +	struct bridge_mcast_own_query *query;
>  	struct net_bridge *br = port->br;
>  
>  	if (!br_opt_get(br, BROPT_MULTICAST_ENABLED) || !netif_running(br->dev))
>  		return;
>  
> -	br_multicast_enable(&port->ip4_own_query);
> +	list_for_each_entry(query, &br->ip4_own_queries, list) {
> +		struct bridge_mcast_own_query *port_query;
> +		struct net_bridge_vlan_group *vg;
> +
> +		if (!query->enabled)
> +			continue;
> +
> +		if (br_vlan_enabled(br->dev)) {
> +			vg = nbp_vlan_group(port);
> +			if (!vg || (vg && !br_vlan_find(vg, query->vid)))
> +				continue;
> +		}
> +
> +		port_query = br_mcast_find_own_query(&port->ip4_own_queries,
> +						     query->vid);
> +		if (!port_query) {
> +			port_query = br_mcast_add_own_query(&port->ip4_own_queries,
> +							    query->vid,
> +							    br_ip4_multicast_port_query_expired);
> +			if (!port_query)
> +				continue;
> +
> +			port_query->port = port;
> +		}
> +
> +		if (query->ip4) {
> +			port_query->ip4 = true;
> +			br_multicast_enable(port_query);
> +		}
> +	}
>  #if IS_ENABLED(CONFIG_IPV6)
>  	br_multicast_enable(&port->ip6_own_query);
>  #endif
> @@ -1673,6 +1848,7 @@ void br_multicast_enable_port(struct net_bridge_port *port)
>  
>  void br_multicast_disable_port(struct net_bridge_port *port)
>  {
> +	struct bridge_mcast_own_query *query;
>  	struct net_bridge *br = port->br;
>  	struct net_bridge_port_group *pg;
>  	struct hlist_node *n;
> @@ -1685,7 +1861,8 @@ void br_multicast_disable_port(struct net_bridge_port *port)
>  	__del_port_router(port);
>  
>  	del_timer(&port->multicast_router_timer);
> -	del_timer(&port->ip4_own_query.timer);
> +	list_for_each_entry(query, &port->ip4_own_queries, list)
> +		del_timer(&query->timer);
>  #if IS_ENABLED(CONFIG_IPV6)
>  	del_timer(&port->ip6_own_query.timer);
>  #endif
> @@ -1717,17 +1894,23 @@ static void __grp_src_mod_timer(struct net_bridge_group_src *src,
>  static void __grp_src_query_marked_and_rexmit(struct net_bridge_port_group *pg)
>  {
>  	struct bridge_mcast_other_query *other_query = NULL;
> +	struct bridge_mcast_own_query *own_query = NULL;
>  	struct net_bridge *br = pg->key.port->br;
>  	u32 lmqc = br->multicast_last_member_count;
>  	unsigned long lmqt, lmi, now = jiffies;
>  	struct net_bridge_group_src *ent;
> +	u16 vid = pg->key.addr.vid;
> +
> +	own_query = br_mcast_find_own_query(&pg->key.port->ip4_own_queries,
> +					    vid);
>  
>  	if (!netif_running(br->dev) ||
>  	    !br_opt_get(br, BROPT_MULTICAST_ENABLED))
>  		return;
>  
>  	if (pg->key.addr.proto == htons(ETH_P_IP))
> -		other_query = &br->ip4_other_query;
> +		other_query = br_mcast_find_other_query(&br->ip4_other_queries,
> +							vid);
>  #if IS_ENABLED(CONFIG_IPV6)
>  	else
>  		other_query = &br->ip6_other_query;
> @@ -1738,7 +1921,7 @@ static void __grp_src_query_marked_and_rexmit(struct net_bridge_port_group *pg)
>  		if (ent->flags & BR_SGRP_F_SEND) {
>  			ent->flags &= ~BR_SGRP_F_SEND;
>  			if (ent->timer.expires > lmqt) {
> -				if (br_opt_get(br, BROPT_MULTICAST_QUERIER) &&
> +				if (own_query && own_query->enabled &&
>  				    other_query &&
>  				    !timer_pending(&other_query->timer))
>  					ent->src_query_rexmit_cnt = lmqc;
> @@ -1747,7 +1930,7 @@ static void __grp_src_query_marked_and_rexmit(struct net_bridge_port_group *pg)
>  		}
>  	}
>  
> -	if (!br_opt_get(br, BROPT_MULTICAST_QUERIER) ||
> +	if (!own_query || !own_query->enabled ||
>  	    !other_query || timer_pending(&other_query->timer))
>  		return;
>  
> @@ -1763,21 +1946,27 @@ static void __grp_src_query_marked_and_rexmit(struct net_bridge_port_group *pg)
>  static void __grp_send_query_and_rexmit(struct net_bridge_port_group *pg)
>  {
>  	struct bridge_mcast_other_query *other_query = NULL;
> +	struct bridge_mcast_own_query *own_query = NULL;
>  	struct net_bridge *br = pg->key.port->br;
>  	unsigned long now = jiffies, lmi;
> +	u16 vid = pg->key.addr.vid;
>  
>  	if (!netif_running(br->dev) ||
>  	    !br_opt_get(br, BROPT_MULTICAST_ENABLED))
>  		return;
>  
> +	own_query = br_mcast_find_own_query(&pg->key.port->ip4_own_queries,
> +					    vid);
> +
>  	if (pg->key.addr.proto == htons(ETH_P_IP))
> -		other_query = &br->ip4_other_query;
> +		other_query = br_mcast_find_other_query(&br->ip4_other_queries,
> +							vid);
>  #if IS_ENABLED(CONFIG_IPV6)
>  	else
>  		other_query = &br->ip6_other_query;
>  #endif
>  
> -	if (br_opt_get(br, BROPT_MULTICAST_QUERIER) &&
> +	if (own_query && own_query->enabled &&
>  	    other_query && !timer_pending(&other_query->timer)) {
>  		lmi = now + br->multicast_last_member_interval;
>  		pg->grp_query_rexmit_cnt = br->multicast_last_member_count - 1;
> @@ -2484,10 +2673,12 @@ static int br_ip6_multicast_mld2_report(struct net_bridge *br,
>  
>  static bool br_ip4_multicast_select_querier(struct net_bridge *br,
>  					    struct net_bridge_port *port,
> -					    __be32 saddr)
> +					    __be32 saddr,
> +					    struct bridge_mcast_own_query *own,
> +					    struct bridge_mcast_other_query *other)
>  {
> -	if (!timer_pending(&br->ip4_own_query.timer) &&
> -	    !timer_pending(&br->ip4_other_query.timer))
> +	if (own && !timer_pending(&own->timer) &&
> +	    !timer_pending(&other->timer))
>  		goto update;
>  
>  	if (!br->ip4_querier.addr.src.ip4)
> @@ -2533,11 +2724,14 @@ static bool br_ip6_multicast_select_querier(struct net_bridge *br,
>  
>  static bool br_multicast_select_querier(struct net_bridge *br,
>  					struct net_bridge_port *port,
> -					struct br_ip *saddr)
> +					struct br_ip *saddr,
> +					struct bridge_mcast_own_query *query,
> +					struct bridge_mcast_other_query *other_query)
>  {
>  	switch (saddr->proto) {
>  	case htons(ETH_P_IP):
> -		return br_ip4_multicast_select_querier(br, port, saddr->src.ip4);
> +		return br_ip4_multicast_select_querier(br, port, saddr->src.ip4,
> +						       query, other_query);
>  #if IS_ENABLED(CONFIG_IPV6)
>  	case htons(ETH_P_IPV6):
>  		return br_ip6_multicast_select_querier(br, port, &saddr->src.ip6);
> @@ -2628,9 +2822,10 @@ static void br_multicast_query_received(struct net_bridge *br,
>  					struct net_bridge_port *port,
>  					struct bridge_mcast_other_query *query,
>  					struct br_ip *saddr,
> -					unsigned long max_delay)
> +					unsigned long max_delay,
> +					struct bridge_mcast_own_query *own_query)
>  {
> -	if (!br_multicast_select_querier(br, port, saddr))
> +	if (!br_multicast_select_querier(br, port, saddr, own_query, query))
>  		return;
>  
>  	br_multicast_update_query_timer(br, query, max_delay);
> @@ -2643,6 +2838,8 @@ static void br_ip4_multicast_query(struct net_bridge *br,
>  				   u16 vid)
>  {
>  	unsigned int transport_len = ip_transport_len(skb);
> +	struct bridge_mcast_other_query *other_query;
> +	struct bridge_mcast_own_query *own_query;
>  	const struct iphdr *iph = ip_hdr(skb);
>  	struct igmphdr *ih = igmp_hdr(skb);
>  	struct net_bridge_mdb_entry *mp;
> @@ -2684,8 +2881,13 @@ static void br_ip4_multicast_query(struct net_bridge *br,
>  		saddr.proto = htons(ETH_P_IP);
>  		saddr.src.ip4 = iph->saddr;
>  
> -		br_multicast_query_received(br, port, &br->ip4_other_query,
> -					    &saddr, max_delay);
> +		br_mcast_add_queries(br, vid);
> +
> +		own_query = br_mcast_find_own_query(&br->ip4_own_queries, vid);
> +		other_query = br_mcast_find_other_query(&br->ip4_other_queries,
> +							vid);
> +		br_multicast_query_received(br, port, other_query, &saddr,
> +					    max_delay, own_query);
>  		goto out;
>  	}
>  
> @@ -2773,7 +2975,7 @@ static int br_ip6_multicast_query(struct net_bridge *br,
>  		saddr.src.ip6 = ipv6_hdr(skb)->saddr;
>  
>  		br_multicast_query_received(br, port, &br->ip6_other_query,
> -					    &saddr, max_delay);
> +					    &saddr, max_delay, NULL);
>  		goto out;
>  	} else if (!group) {
>  		goto out;
> @@ -2850,7 +3052,7 @@ br_multicast_leave_group(struct net_bridge *br,
>  	if (timer_pending(&other_query->timer))
>  		goto out;
>  
> -	if (br_opt_get(br, BROPT_MULTICAST_QUERIER)) {
> +	if (own_query && own_query->enabled) {
>  		__br_multicast_send_query(br, port, NULL, NULL, &mp->addr,
>  					  false, 0, NULL);
>  
> @@ -2916,21 +3118,26 @@ static void br_ip4_multicast_leave_group(struct net_bridge *br,
>  					 __u16 vid,
>  					 const unsigned char *src)
>  {
> -	struct br_ip br_group;
> +	struct bridge_mcast_other_query *other_query;
>  	struct bridge_mcast_own_query *own_query;
> +	struct br_ip br_group;
>  
>  	if (ipv4_is_local_multicast(group))
>  		return;
>  
> -	own_query = port ? &port->ip4_own_query : &br->ip4_own_query;
> +	if (port)
> +		own_query = br_mcast_find_own_query(&port->ip4_own_queries, vid);
> +	else
> +		own_query = br_mcast_find_own_query(&br->ip4_own_queries, vid);
>  
>  	memset(&br_group, 0, sizeof(br_group));
>  	br_group.dst.ip4 = group;
>  	br_group.proto = htons(ETH_P_IP);
>  	br_group.vid = vid;
>  
> -	br_multicast_leave_group(br, port, &br_group, &br->ip4_other_query,
> -				 own_query, src);
> +	other_query = br_mcast_find_other_query(&br->ip4_other_queries, vid);
> +	br_multicast_leave_group(br, port, &br_group, other_query, own_query,
> +				 src);
>  }
>  
>  #if IS_ENABLED(CONFIG_IPV6)
> @@ -3195,9 +3402,10 @@ static void br_multicast_query_expired(struct net_bridge *br,
>  
>  static void br_ip4_multicast_query_expired(struct timer_list *t)
>  {
> -	struct net_bridge *br = from_timer(br, t, ip4_own_query.timer);
> +	struct bridge_mcast_own_query *query = from_timer(query, t, timer);
> +	struct net_bridge *br = query->br;
>  
> -	br_multicast_query_expired(br, &br->ip4_own_query, &br->ip4_querier);
> +	br_multicast_query_expired(br, query, &br->ip4_querier);
>  }
>  
>  #if IS_ENABLED(CONFIG_IPV6)
> @@ -3237,7 +3445,6 @@ void br_multicast_init(struct net_bridge *br)
>  	br->multicast_querier_interval = 255 * HZ;
>  	br->multicast_membership_interval = 260 * HZ;
>  
> -	br->ip4_other_query.delay_time = 0;
>  	br->ip4_querier.port = NULL;
>  	br->multicast_igmp_version = 2;
>  #if IS_ENABLED(CONFIG_IPV6)
> @@ -3251,10 +3458,8 @@ void br_multicast_init(struct net_bridge *br)
>  	spin_lock_init(&br->multicast_lock);
>  	timer_setup(&br->multicast_router_timer,
>  		    br_multicast_local_router_expired, 0);
> -	timer_setup(&br->ip4_other_query.timer,
> -		    br_ip4_multicast_querier_expired, 0);
> -	timer_setup(&br->ip4_own_query.timer,
> -		    br_ip4_multicast_query_expired, 0);
> +	INIT_LIST_HEAD(&br->ip4_other_queries);
> +	INIT_LIST_HEAD(&br->ip4_own_queries);
>  #if IS_ENABLED(CONFIG_IPV6)
>  	timer_setup(&br->ip6_other_query.timer,
>  		    br_ip6_multicast_querier_expired, 0);
> @@ -3341,7 +3546,10 @@ static void __br_multicast_open(struct net_bridge *br,
>  
>  void br_multicast_open(struct net_bridge *br)
>  {
> -	__br_multicast_open(br, &br->ip4_own_query);
> +	struct bridge_mcast_own_query *query;
> +
> +	list_for_each_entry(query, &br->ip4_own_queries, list)
> +		__br_multicast_open(br, query);
>  #if IS_ENABLED(CONFIG_IPV6)
>  	__br_multicast_open(br, &br->ip6_own_query);
>  #endif
> @@ -3349,9 +3557,14 @@ void br_multicast_open(struct net_bridge *br)
>  
>  void br_multicast_stop(struct net_bridge *br)
>  {
> +	struct bridge_mcast_other_query *other_query;
> +	struct bridge_mcast_own_query *query;
> +
>  	del_timer_sync(&br->multicast_router_timer);
> -	del_timer_sync(&br->ip4_other_query.timer);
> -	del_timer_sync(&br->ip4_own_query.timer);
> +	list_for_each_entry(other_query, &br->ip4_other_queries, list)
> +		del_timer_sync(&other_query->timer);
> +	list_for_each_entry(query, &br->ip4_own_queries, list)
> +		del_timer_sync(&query->timer);
>  #if IS_ENABLED(CONFIG_IPV6)
>  	del_timer_sync(&br->ip6_other_query.timer);
>  	del_timer_sync(&br->ip6_own_query.timer);
> @@ -3461,11 +3674,20 @@ int br_multicast_set_port_router(struct net_bridge_port *p, unsigned long val)
>  }
>  
>  static void br_multicast_start_querier(struct net_bridge *br,
> -				       struct bridge_mcast_own_query *query)
> +				       struct bridge_mcast_own_query *query,
> +				       u16 vid)
>  {
> +	struct bridge_mcast_own_query *port_query;
> +	struct net_bridge_vlan_group *vg;
>  	struct net_bridge_port *port;
>  
> -	__br_multicast_open(br, query);
> +	if (br_vlan_enabled(br->dev)) {
> +		vg = br_vlan_group(br);
> +		if (vg && br_vlan_find(vg, vid))
> +			__br_multicast_open(br, query);
> +	} else {
> +		__br_multicast_open(br, query);
> +	}
>  
>  	rcu_read_lock();
>  	list_for_each_entry_rcu(port, &br->port_list, list) {
> @@ -3473,11 +3695,66 @@ static void br_multicast_start_querier(struct net_bridge *br,
>  		    port->state == BR_STATE_BLOCKING)
>  			continue;
>  
> -		if (query == &br->ip4_own_query)
> -			br_multicast_enable(&port->ip4_own_query);
> +		if (br_vlan_enabled(br->dev)) {
> +			vg = nbp_vlan_group(port);
> +			if (!vg || (vg && !br_vlan_find(vg, vid)))
> +				continue;
> +		}
> +
> +		port_query = br_mcast_find_own_query(&port->ip4_own_queries,
> +						     vid);
> +		if (!port_query)
> +			continue;
> +
> +		port_query->enabled = true;
> +
> +		if (query->ip4) {
> +			port_query->ip4 = true;
> +			br_multicast_enable(port_query);
> +		}
>  #if IS_ENABLED(CONFIG_IPV6)
> -		else
> +		else {
>  			br_multicast_enable(&port->ip6_own_query);
> +		}
> +#endif
> +	}
> +	rcu_read_unlock();
> +}
> +
> +static void br_multicast_stop_querier(struct net_bridge *br,
> +				      struct bridge_mcast_own_query *query,
> +				      u16 vid)
> +{
> +	struct bridge_mcast_own_query *port_query;
> +	struct net_bridge_vlan_group *vg;
> +	struct net_bridge_port *port;
> +
> +	query->enabled = false;
> +
> +	rcu_read_lock();
> +	list_for_each_entry_rcu(port, &br->port_list, list) {
> +		if (port->state == BR_STATE_DISABLED ||
> +		    port->state == BR_STATE_BLOCKING)
> +			continue;
> +
> +		if (br_vlan_enabled(br->dev)) {
> +			vg = nbp_vlan_group(port);
> +			if (!vg || (vg && !br_vlan_find(vg, vid)))
> +				continue;
> +		}
> +
> +		port_query = br_mcast_find_own_query(&port->ip4_own_queries,
> +						     vid);
> +		if (!port_query)
> +			continue;
> +
> +		port_query->enabled = false;
> +
> +		if (query->ip4)
> +			br_multicast_disable(port_query);
> +#if IS_ENABLED(CONFIG_IPV6)
> +		else
> +			br_multicast_disable(&port->ip6_own_query);
>  #endif
>  	}
>  	rcu_read_unlock();
> @@ -3553,32 +3830,55 @@ bool br_multicast_router(const struct net_device *dev)
>  }
>  EXPORT_SYMBOL_GPL(br_multicast_router);
>  
> -int br_multicast_set_querier(struct net_bridge *br, unsigned long val)
> +int br_multicast_set_querier(struct net_bridge *br, unsigned long val, u16 vid)
>  {
> +	struct bridge_mcast_other_query *other_query;
> +	struct bridge_mcast_own_query *query;
> +	struct net_bridge_vlan_group *vg;
>  	unsigned long max_delay;
>  
>  	val = !!val;
>  
> +	if (vid == 0) {
> +		vg = br_vlan_group(br);
> +		if (vg)
> +			vid = vg->pvid;
> +	}
> +
>  	spin_lock_bh(&br->multicast_lock);
> -	if (br_opt_get(br, BROPT_MULTICAST_QUERIER) == val)
> +	query = br_mcast_find_own_query(&br->ip4_own_queries, vid);
> +	if (!query) {
> +		if (br_vlan_enabled(br->dev))
> +			goto unlock;
> +
> +		br_mcast_add_queries(br, vid);
> +	}
> +
> +	other_query = br_mcast_find_other_query(&br->ip4_other_queries, vid);
> +	if (!other_query)
>  		goto unlock;
>  
> -	br_opt_toggle(br, BROPT_MULTICAST_QUERIER, !!val);
> -	if (!val)
> +	if (!val && query) {
> +		br_multicast_stop_querier(br, query, vid);
>  		goto unlock;
> +	}
>  
> -	max_delay = br->multicast_query_response_interval;
> +	if (val & query->enabled)
> +		goto unlock;
>  
> -	if (!timer_pending(&br->ip4_other_query.timer))
> -		br->ip4_other_query.delay_time = jiffies + max_delay;
> +	query->enabled = true;
>  
> -	br_multicast_start_querier(br, &br->ip4_own_query);
> +	max_delay = br->multicast_query_response_interval;
> +	if (!timer_pending(&other_query->timer))
> +		other_query->delay_time = jiffies + max_delay;
> +
> +	br_multicast_start_querier(br, query, vid);
>  
>  #if IS_ENABLED(CONFIG_IPV6)
>  	if (!timer_pending(&br->ip6_other_query.timer))
>  		br->ip6_other_query.delay_time = jiffies + max_delay;
>  
> -	br_multicast_start_querier(br, &br->ip6_own_query);
> +	br_multicast_start_querier(br, &br->ip6_own_query, vid);
>  #endif
>  
>  unlock:
> @@ -3587,6 +3887,79 @@ int br_multicast_set_querier(struct net_bridge *br, unsigned long val)
>  	return 0;
>  }
>  
> +void br_multicast_vlan_add(struct net_bridge_vlan *v)
> +{
> +	struct bridge_mcast_own_query *query, *port_query;
> +	struct net_bridge_port *p;
> +	struct net_bridge *br;
> +
> +	if (br_vlan_is_master(v)) {
> +		br_mcast_add_queries(v->br, v->vid);
> +		return;
> +	}
> +
> +	p = v->port;
> +	br = p->br;
> +
> +	query = br_mcast_find_own_query(&br->ip4_own_queries, v->vid);
> +
> +	port_query = br_mcast_add_own_query(&p->ip4_own_queries,
> +					    v->vid,
> +					    br_ip4_multicast_port_query_expired);
> +	if (!port_query)
> +		return;
> +
> +	port_query->port = p;
> +	port_query->ip4 = true;
> +
> +	if (query->enabled) {
> +		port_query->enabled = true;
> +		br_multicast_enable(port_query);
> +	}
> +}
> +
> +void br_multicast_vlan_del(struct net_bridge_vlan *v)
> +{
> +	struct bridge_mcast_other_query *other_query, *other_tmp;
> +	struct bridge_mcast_own_query *query, *tmp;
> +	struct net_bridge_port *p;
> +	struct net_bridge *br;
> +
> +	if (br_vlan_is_master(v)) {
> +		br = v->br;
> +
> +		list_for_each_entry_safe(other_query, other_tmp,
> +					 &br->ip4_other_queries, list)
> +			if (other_query->vid == v->vid)
> +				br_mcast_del_other_query(other_query);
> +
> +		list_for_each_entry_safe(query, tmp, &br->ip4_own_queries, list)
> +			if (query->vid == v->vid)
> +				br_mcast_del_own_query(query);
> +
> +		return;
> +	}
> +
> +	p = v->port;
> +
> +	list_for_each_entry_safe(query, tmp, &p->ip4_own_queries, list) {
> +		if (query->vid == v->vid)
> +			br_mcast_del_own_query(query);
> +	}
> +}
> +
> +void br_multicast_vlan_toggle(struct net_bridge *br, bool on)
> +{
> +	struct bridge_mcast_own_query *query;
> +
> +	list_for_each_entry(query, &br->ip4_own_queries, list) {
> +		if (!on)
> +			br_multicast_stop_querier(br, query, query->vid);
> +		else
> +			br_multicast_start_querier(br, query, query->vid);
> +	}
> +}
> +
>  int br_multicast_set_igmp_version(struct net_bridge *br, unsigned long val)
>  {
>  	/* Currently we support only version 2 and 3 */
> @@ -3711,7 +4084,7 @@ bool br_multicast_has_querier_anywhere(struct net_device *dev, int proto)
>  	memset(&eth, 0, sizeof(eth));
>  	eth.h_proto = htons(proto);
>  
> -	ret = br_multicast_querier_exists(br, &eth, NULL);
> +	ret = br_multicast_any_querier_exists(br, &eth);
>  
>  unlock:
>  	rcu_read_unlock();
> @@ -3746,7 +4119,7 @@ bool br_multicast_has_querier_adjacent(struct net_device *dev, int proto)
>  
>  	switch (proto) {
>  	case ETH_P_IP:
> -		if (!timer_pending(&br->ip4_other_query.timer) ||
> +		if (!br_multicast_any_querier_adjacent(br) ||
>  		    rcu_dereference(br->ip4_querier.port) == port)
>  			goto unlock;
>  		break;
> diff --git a/net/bridge/br_netlink.c b/net/bridge/br_netlink.c
> index 49700ce0e919..d32f4c185364 100644
> --- a/net/bridge/br_netlink.c
> +++ b/net/bridge/br_netlink.c
> @@ -1186,6 +1186,7 @@ static const struct nla_policy br_policy[IFLA_BR_MAX + 1] = {
>  	[IFLA_BR_VLAN_STATS_PER_PORT] = { .type = NLA_U8 },
>  	[IFLA_BR_MULTI_BOOLOPT] =
>  		NLA_POLICY_EXACT_LEN(sizeof(struct br_boolopt_multi)),
> +	[IFLA_BR_MCAST_QUERIER_VID] = { .type = NLA_U16 },
>  };
>  
>  static int br_changelink(struct net_device *brdev, struct nlattr *tb[],
> @@ -1193,6 +1194,7 @@ static int br_changelink(struct net_device *brdev, struct nlattr *tb[],
>  			 struct netlink_ext_ack *extack)
>  {
>  	struct net_bridge *br = netdev_priv(brdev);
> +	u16 vid = 0;
>  	int err;
>  
>  	if (!data)
> @@ -1204,6 +1206,9 @@ static int br_changelink(struct net_device *brdev, struct nlattr *tb[],
>  			return err;
>  	}
>  
> +	if (data[IFLA_BR_MCAST_QUERIER_VID])
> +		vid = nla_get_u16(data[IFLA_BR_MCAST_QUERIER_VID]);
> +
>  	if (data[IFLA_BR_HELLO_TIME]) {
>  		err = br_set_hello_time(br, nla_get_u32(data[IFLA_BR_HELLO_TIME]));
>  		if (err)
> @@ -1333,7 +1338,7 @@ static int br_changelink(struct net_device *brdev, struct nlattr *tb[],
>  	if (data[IFLA_BR_MCAST_QUERIER]) {
>  		u8 mcast_querier = nla_get_u8(data[IFLA_BR_MCAST_QUERIER]);
>  
> -		err = br_multicast_set_querier(br, mcast_querier);
> +		err = br_multicast_set_querier(br, mcast_querier, vid);
>  		if (err)
>  			return err;
>  	}
> @@ -1596,7 +1601,7 @@ static int br_fill_info(struct sk_buff *skb, const struct net_device *brdev)
>  	    nla_put_u8(skb, IFLA_BR_MCAST_QUERY_USE_IFADDR,
>  		       br_opt_get(br, BROPT_MULTICAST_QUERY_USE_IFADDR)) ||
>  	    nla_put_u8(skb, IFLA_BR_MCAST_QUERIER,
> -		       br_opt_get(br, BROPT_MULTICAST_QUERIER)) ||
> +		       br_mcast_exist_own_query(br)) ||
>  	    nla_put_u8(skb, IFLA_BR_MCAST_STATS_ENABLED,
>  		       br_opt_get(br, BROPT_MULTICAST_STATS_ENABLED)) ||
>  	    nla_put_u32(skb, IFLA_BR_MCAST_HASH_ELASTICITY, RHT_ELASTICITY) ||
> diff --git a/net/bridge/br_private.h b/net/bridge/br_private.h
> index d62c6e1af64a..84f597f542b1 100644
> --- a/net/bridge/br_private.h
> +++ b/net/bridge/br_private.h
> @@ -66,14 +66,24 @@ struct mac_addr {
>  #ifdef CONFIG_BRIDGE_IGMP_SNOOPING
>  /* our own querier */
>  struct bridge_mcast_own_query {
> +	struct list_head	list;
>  	struct timer_list	timer;
>  	u32			startup_sent;
> +	struct net_bridge_port	*port;
> +	struct net_bridge	*br;
> +	bool			ip4;
> +	u16			vid;
> +	bool			enabled;
>  };
>  
>  /* other querier */
>  struct bridge_mcast_other_query {
> +	struct list_head		list;
>  	struct timer_list		timer;
>  	unsigned long			delay_time;
> +	struct net_bridge		*br;
> +	bool				ip4;
> +	u16				vid;
>  };
>  
>  /* selected querier */
> @@ -304,7 +314,7 @@ struct net_bridge_port {
>  	struct rcu_head			rcu;
>  
>  #ifdef CONFIG_BRIDGE_IGMP_SNOOPING
> -	struct bridge_mcast_own_query	ip4_own_query;
> +	struct list_head		ip4_own_queries;
>  #if IS_ENABLED(CONFIG_IPV6)
>  	struct bridge_mcast_own_query	ip6_own_query;
>  #endif /* IS_ENABLED(CONFIG_IPV6) */
> @@ -448,8 +458,8 @@ struct net_bridge {
>  	struct hlist_head		router_list;
>  
>  	struct timer_list		multicast_router_timer;
> -	struct bridge_mcast_other_query	ip4_other_query;
> -	struct bridge_mcast_own_query	ip4_own_query;
> +	struct list_head		ip4_other_queries;
> +	struct list_head		ip4_own_queries;
>  	struct bridge_mcast_querier	ip4_querier;
>  	struct bridge_mcast_stats	__percpu *mcast_stats;
>  #if IS_ENABLED(CONFIG_IPV6)
> @@ -788,6 +798,9 @@ int br_ioctl_deviceless_stub(struct net *net, unsigned int cmd,
>  
>  /* br_multicast.c */
>  #ifdef CONFIG_BRIDGE_IGMP_SNOOPING
> +void br_multicast_vlan_add(struct net_bridge_vlan *v);
> +void br_multicast_vlan_del(struct net_bridge_vlan *v);
> +void br_multicast_vlan_toggle(struct net_bridge *br, bool on);
>  int br_multicast_rcv(struct net_bridge *br, struct net_bridge_port *port,
>  		     struct sk_buff *skb, u16 vid);
>  struct net_bridge_mdb_entry *br_mdb_get(struct net_bridge *br,
> @@ -807,7 +820,7 @@ void br_multicast_flood(struct net_bridge_mdb_entry *mdst,
>  int br_multicast_set_router(struct net_bridge *br, unsigned long val);
>  int br_multicast_set_port_router(struct net_bridge_port *p, unsigned long val);
>  int br_multicast_toggle(struct net_bridge *br, unsigned long val);
> -int br_multicast_set_querier(struct net_bridge *br, unsigned long val);
> +int br_multicast_set_querier(struct net_bridge *br, unsigned long val, u16 vid);
>  int br_multicast_set_hash_max(struct net_bridge *br, unsigned long val);
>  int br_multicast_set_igmp_version(struct net_bridge *br, unsigned long val);
>  #if IS_ENABLED(CONFIG_IPV6)
> @@ -846,6 +859,11 @@ 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 bridge_mcast_other_query *
> +br_mcast_find_other_query(struct list_head *list, u16 vid);
> +struct bridge_mcast_own_query *
> +br_mcast_find_own_query(struct list_head *list, u16 vid);
> +bool br_mcast_exist_own_query(struct net_bridge *br);
>  
>  static inline bool br_group_is_l2(const struct br_ip *group)
>  {
> @@ -865,11 +883,15 @@ static inline bool br_multicast_is_router(struct net_bridge *br)
>  static inline bool
>  __br_multicast_querier_exists(struct net_bridge *br,
>  				struct bridge_mcast_other_query *querier,
> -				const bool is_ipv6)
> +				const bool is_ipv6,
> +				u16 vid)
>  {
> +	struct bridge_mcast_own_query *query;
>  	bool own_querier_enabled;
>  
> -	if (br_opt_get(br, BROPT_MULTICAST_QUERIER)) {
> +	query = br_mcast_find_own_query(&br->ip4_own_queries, vid);
> +
> +	if (query && query->enabled) {
>  		if (is_ipv6 && !br_opt_get(br, BROPT_HAS_IPV6_ADDR))
>  			own_querier_enabled = false;
>  		else
> @@ -878,28 +900,62 @@ __br_multicast_querier_exists(struct net_bridge *br,
>  		own_querier_enabled = false;
>  	}
>  
> +	if (!querier)
> +		return own_querier_enabled;
> +
>  	return time_is_before_jiffies(querier->delay_time) &&
>  	       (own_querier_enabled || timer_pending(&querier->timer));
>  }
>  
>  static inline bool br_multicast_querier_exists(struct net_bridge *br,
>  					       struct ethhdr *eth,
> -					       const struct net_bridge_mdb_entry *mdb)
> +					       const struct net_bridge_mdb_entry *mdb,
> +					       u16 vid)
>  {
> +	struct bridge_mcast_other_query *query =
> +		br_mcast_find_other_query(&br->ip4_other_queries, vid);
> +
>  	switch (eth->h_proto) {
>  	case (htons(ETH_P_IP)):
> -		return __br_multicast_querier_exists(br,
> -			&br->ip4_other_query, false);
> +		return __br_multicast_querier_exists(br, query, false, vid);
>  #if IS_ENABLED(CONFIG_IPV6)
>  	case (htons(ETH_P_IPV6)):
>  		return __br_multicast_querier_exists(br,
> -			&br->ip6_other_query, true);
> +			&br->ip6_other_query, true, vid);
>  #endif
>  	default:
>  		return !!mdb && br_group_is_l2(&mdb->addr);
>  	}
>  }
>  
> +static inline bool br_multicast_any_querier_exists(struct net_bridge *br,
> +						   struct ethhdr *eth)
> +{
> +	struct bridge_mcast_other_query *query;
> +
> +	list_for_each_entry(query, &br->ip4_other_queries, list) {
> +		if (!timer_pending(&query->timer))
> +			continue;
> +
> +		if (br_multicast_querier_exists(br, eth, NULL, query->vid))
> +			return true;
> +	}
> +
> +	return false;
> +}
> +
> +static inline bool br_multicast_any_querier_adjacent(struct net_bridge *br)
> +{
> +	struct bridge_mcast_other_query *query;
> +
> +	list_for_each_entry(query, &br->ip4_other_queries, list) {
> +		if (timer_pending(&query->timer))
> +			return true;
> +	}
> +
> +	return false;
> +}
> +
>  static inline bool br_multicast_is_star_g(const struct br_ip *ip)
>  {
>  	switch (ip->proto) {
> @@ -1015,7 +1071,19 @@ static inline bool br_multicast_is_router(struct net_bridge *br)
>  
>  static inline bool br_multicast_querier_exists(struct net_bridge *br,
>  					       struct ethhdr *eth,
> -					       const struct net_bridge_mdb_entry *mdb)
> +					       const struct net_bridge_mdb_entry *mdb,
> +					       u16 vid)
> +{
> +	return false;
> +}
> +
> +static inline bool br_multicast_any_querier_exists(struct net_bridge *br,
> +						   struct ethhdr *eth)
> +{
> +	return false;
> +}
> +
> +static inline bool br_multicast_any_querier_adjacent(struct net_bridge *br)
>  {
>  	return false;
>  }
> diff --git a/net/bridge/br_sysfs_br.c b/net/bridge/br_sysfs_br.c
> index 7db06e3f642a..23bf6a065d78 100644
> --- a/net/bridge/br_sysfs_br.c
> +++ b/net/bridge/br_sysfs_br.c
> @@ -51,6 +51,33 @@ static ssize_t store_bridge_parm(struct device *d,
>  	return err ? err : len;
>  }
>  
> +static ssize_t store_bridge_parm2(struct device *d,
> +				  const char *buf, size_t len,
> +				  int (*set)(struct net_bridge *, unsigned long, u16))
> +{
> +	struct net_bridge *br = to_bridge(d);
> +	char *endp;
> +	unsigned long val;
> +	int err;
> +
> +	if (!ns_capable(dev_net(br->dev)->user_ns, CAP_NET_ADMIN))
> +		return -EPERM;
> +
> +	val = simple_strtoul(buf, &endp, 0);
> +	if (endp == buf)
> +		return -EINVAL;
> +
> +	if (!rtnl_trylock())
> +		return restart_syscall();
> +
> +	err = (*set)(br, val, 0);
> +	if (!err)
> +		netdev_state_change(br->dev);
> +	rtnl_unlock();
> +
> +	return err ? err : len;
> +}
> +
>  
>  static ssize_t forward_delay_show(struct device *d,
>  				  struct device_attribute *attr, char *buf)
> @@ -404,14 +431,14 @@ static ssize_t multicast_querier_show(struct device *d,
>  				      char *buf)
>  {
>  	struct net_bridge *br = to_bridge(d);
> -	return sprintf(buf, "%d\n", br_opt_get(br, BROPT_MULTICAST_QUERIER));
> +	return sprintf(buf, "%d\n", br_mcast_exist_own_query(br));
>  }
>  
>  static ssize_t multicast_querier_store(struct device *d,
>  				       struct device_attribute *attr,
>  				       const char *buf, size_t len)
>  {
> -	return store_bridge_parm(d, buf, len, br_multicast_set_querier);
> +	return store_bridge_parm2(d, buf, len, br_multicast_set_querier);
>  }
>  static DEVICE_ATTR_RW(multicast_querier);
>  
> diff --git a/net/bridge/br_vlan.c b/net/bridge/br_vlan.c
> index 701cad646b20..2e0b544a3560 100644
> --- a/net/bridge/br_vlan.c
> +++ b/net/bridge/br_vlan.c
> @@ -308,6 +308,7 @@ static int __vlan_add(struct net_bridge_vlan *v, u16 flags,
>  
>  	__vlan_add_list(v);
>  	__vlan_add_flags(v, flags);
> +	br_multicast_vlan_add(v);
>  
>  	if (p)
>  		nbp_vlan_set_vlan_dev_state(p, v->vid);
> @@ -353,6 +354,7 @@ static int __vlan_del(struct net_bridge_vlan *v)
>  		masterv = v->brvlan;
>  	}
>  
> +	br_multicast_vlan_del(v);
>  	__vlan_delete_pvid(vg, v->vid);
>  	if (p) {
>  		err = __vlan_vid_del(p->dev, p->br, v);
> @@ -827,6 +829,7 @@ int __br_vlan_filter_toggle(struct net_bridge *br, unsigned long val)
>  	br_manage_promisc(br);
>  	recalculate_group_addr(br);
>  	br_recalculate_fwd_mask(br);
> +	br_multicast_vlan_toggle(br, !!val);
>  
>  	return 0;
>  }
> 


^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [Bridge] [RFC PATCH v2] net: bridge: igmp: Extend IGMP query to be per vlan
@ 2021-01-13 12:15   ` Nikolay Aleksandrov
  0 siblings, 0 replies; 16+ messages in thread
From: Nikolay Aleksandrov @ 2021-01-13 12:15 UTC (permalink / raw)
  To: Horatiu Vultur, davem, kuba, roopa, allan.nielsen, netdev,
	linux-kernel, bridge

On 12/01/2021 15:59, Horatiu Vultur wrote:
> Based on the comments of the previous version, we started to work on a
> new version, so it would be possible to enable/disable queries per vlan.
> This is still work in progress and there are plenty of things that are
> not implemented and tested:
> - ipv6 support
> - the fast path needs to be improved
> - currently it is possible only to enable/disable the queries per vlan,
>   all the other configurations are global
> - toggling vlan_filtering is not tested
> - remove duplicated information
> - etc...
> 
> But there are few things that are working like:
> - sending queries per vlan
> - stop sending queries if there is a better querier per vlan
> - when ports are added/removed from vlan
> - etc...
> 
> We were wondering if this what you had in mind when you proposed to have
> this per vlan? Or we are completely off? Or we should fix some of the
> issues that I mentioned, before you can see more clearly the direction?
> 
> Signed-off-by: Horatiu Vultur <horatiu.vultur@microchip.com>
> ---
>  include/uapi/linux/if_link.h |   1 +
>  net/bridge/br_device.c       |   2 +-
>  net/bridge/br_input.c        |   2 +-
>  net/bridge/br_multicast.c    | 505 ++++++++++++++++++++++++++++++-----
>  net/bridge/br_netlink.c      |   9 +-
>  net/bridge/br_private.h      |  90 ++++++-
>  net/bridge/br_sysfs_br.c     |  31 ++-
>  net/bridge/br_vlan.c         |   3 +
>  8 files changed, 560 insertions(+), 83 deletions(-)
> 

Hi Horatiu,
No, unfortunately not even close. We already have per-port per-vlan and global per-vlan
contexts which are also linked together for each vlan, those must be used for any vlan
configuration and state. The problem is that you'd have to mix igmp and vlan code and
those two live under two different kconfig options, and worse rely on different locks, so
extra care must be taken. Any vlan lookups must use the vlan hashes, (almost) _no_ linear
walks or new lists are needed (the exception is obviously port going down where a walk
over port's vlans is needed). In almost all contexts below a vlan lookup has already been
done by the input functions, the result of that lookup must be saved and re-used. The
vlan options API needs to be used for configuring vlans (per-vlan mcast options), unfortunately
I still haven't upstreamed the iproute2 part, so you might have to do that as well.
Obviously with all of the above the current default situation must not change unless the
user configures it so. If you don't need this asap, I'll probably get to it in two months
after EHT and the new bridge flush api, even we are still carrying an out-of-tree patch
for this which someone (not from cumulus) tried to upstream a few years back, but it also has
wrong design in general. :)

Thanks,
 Nik

> diff --git a/include/uapi/linux/if_link.h b/include/uapi/linux/if_link.h
> index 82708c6db432..11ec1d45c24e 100644
> --- a/include/uapi/linux/if_link.h
> +++ b/include/uapi/linux/if_link.h
> @@ -472,6 +472,7 @@ enum {
>  	IFLA_BR_MCAST_MLD_VERSION,
>  	IFLA_BR_VLAN_STATS_PER_PORT,
>  	IFLA_BR_MULTI_BOOLOPT,
> +	IFLA_BR_MCAST_QUERIER_VID,
>  	__IFLA_BR_MAX,
>  };
>  
> diff --git a/net/bridge/br_device.c b/net/bridge/br_device.c
> index 3f2f06b4dd27..aca4e8074a8f 100644
> --- a/net/bridge/br_device.c
> +++ b/net/bridge/br_device.c
> @@ -89,7 +89,7 @@ netdev_tx_t br_dev_xmit(struct sk_buff *skb, struct net_device *dev)
>  
>  		mdst = br_mdb_get(br, skb, vid);
>  		if ((mdst || BR_INPUT_SKB_CB_MROUTERS_ONLY(skb)) &&
> -		    br_multicast_querier_exists(br, eth_hdr(skb), mdst))
> +		    br_multicast_querier_exists(br, eth_hdr(skb), mdst, vid))
>  			br_multicast_flood(mdst, skb, false, true);
>  		else
>  			br_flood(br, skb, BR_PKT_MULTICAST, false, true);
> diff --git a/net/bridge/br_input.c b/net/bridge/br_input.c
> index 222285d9dae2..03e445af6c1f 100644
> --- a/net/bridge/br_input.c
> +++ b/net/bridge/br_input.c
> @@ -130,7 +130,7 @@ int br_handle_frame_finish(struct net *net, struct sock *sk, struct sk_buff *skb
>  	case BR_PKT_MULTICAST:
>  		mdst = br_mdb_get(br, skb, vid);
>  		if ((mdst || BR_INPUT_SKB_CB_MROUTERS_ONLY(skb)) &&
> -		    br_multicast_querier_exists(br, eth_hdr(skb), mdst)) {
> +		    br_multicast_querier_exists(br, eth_hdr(skb), mdst, vid)) {
>  			if ((mdst && mdst->host_joined) ||
>  			    br_multicast_is_router(br)) {
>  				local_rcv = true;
> diff --git a/net/bridge/br_multicast.c b/net/bridge/br_multicast.c
> index 257ac4e25f6d..b4fac25101e4 100644
> --- a/net/bridge/br_multicast.c
> +++ b/net/bridge/br_multicast.c
> @@ -48,8 +48,11 @@ static const struct rhashtable_params br_sg_port_rht_params = {
>  	.automatic_shrinking = true,
>  };
>  
> +static void br_ip4_multicast_query_expired(struct timer_list *t);
> +static void br_ip4_multicast_querier_expired(struct timer_list *t);
>  static void br_multicast_start_querier(struct net_bridge *br,
> -				       struct bridge_mcast_own_query *query);
> +				       struct bridge_mcast_own_query *query,
> +				       u16 vid);
>  static void br_multicast_add_router(struct net_bridge *br,
>  				    struct net_bridge_port *port);
>  static void br_ip4_multicast_leave_group(struct net_bridge *br,
> @@ -87,6 +90,112 @@ br_sg_port_find(struct net_bridge *br,
>  				      br_sg_port_rht_params);
>  }
>  
> +static void br_mcast_del_other_query(struct bridge_mcast_other_query *query)
> +{
> +	del_timer_sync(&query->timer);
> +	list_del(&query->list);
> +	kfree(query);
> +}
> +
> +static struct bridge_mcast_other_query *
> +br_mcast_add_other_query(struct list_head *list, u16 vid,
> +			 void (*callback)(struct timer_list *t))
> +{
> +	struct bridge_mcast_other_query *query;
> +
> +	query = kzalloc(sizeof(*query), GFP_KERNEL);
> +	if (!query)
> +		return NULL;
> +
> +	query->vid = vid;
> +	timer_setup(&query->timer, callback, 0);
> +
> +	list_add(&query->list, list);
> +
> +	return query;
> +}
> +
> +static void br_mcast_del_own_query(struct bridge_mcast_own_query *query)
> +{
> +	del_timer_sync(&query->timer);
> +	list_del(&query->list);
> +	kfree(query);
> +}
> +
> +static struct bridge_mcast_own_query *
> +br_mcast_add_own_query(struct list_head *list, u16 vid,
> +		       void (*callback)(struct timer_list *t))
> +{
> +	struct bridge_mcast_own_query *query;
> +
> +	query = kzalloc(sizeof(*query), GFP_KERNEL);
> +	if (!query)
> +		return NULL;
> +
> +	query->vid = vid;
> +	timer_setup(&query->timer, callback, 0);
> +
> +	list_add(&query->list, list);
> +
> +	return query;
> +}
> +
> +static void br_mcast_add_queries(struct net_bridge *br, u16 vid)
> +{
> +	struct bridge_mcast_other_query *other;
> +	struct bridge_mcast_own_query *own;
> +
> +	own = br_mcast_find_own_query(&br->ip4_own_queries, vid);
> +	if (!own) {
> +		own = br_mcast_add_own_query(&br->ip4_own_queries, vid,
> +					     br_ip4_multicast_query_expired);
> +		own->ip4 = true;
> +		own->br = br;
> +	}
> +
> +	other = br_mcast_find_other_query(&br->ip4_other_queries, vid);
> +	if (!other) {
> +		other = br_mcast_add_other_query(&br->ip4_other_queries, vid,
> +						 br_ip4_multicast_querier_expired);
> +		other->br = br;
> +	}
> +}
> +
> +struct bridge_mcast_own_query *
> +br_mcast_find_own_query(struct list_head *list, u16 vid)
> +{
> +	struct bridge_mcast_own_query *query = NULL;
> +
> +	list_for_each_entry(query, list, list)
> +		if (query->vid == vid)
> +			return query;
> +
> +	return NULL;
> +}
> +
> +struct bridge_mcast_other_query *
> +br_mcast_find_other_query(struct list_head *list, u16 vid)
> +{
> +	struct bridge_mcast_other_query *query = NULL;
> +
> +	list_for_each_entry(query, list, list)
> +		if (query->vid == vid)
> +			return query;
> +
> +	return NULL;
> +}
> +
> +bool br_mcast_exist_own_query(struct net_bridge *br)
> +{
> +	struct bridge_mcast_own_query *query = NULL;
> +
> +	list_for_each_entry(query, &br->ip4_own_queries, list)
> +		if (query->enabled)
> +			return true;
> +
> +	return false;
> +}
> +
>  static struct net_bridge_mdb_entry *br_mdb_ip_get_rcu(struct net_bridge *br,
>  						      struct br_ip *dst)
>  {
> @@ -688,7 +797,8 @@ static struct sk_buff *br_ip4_multicast_alloc_query(struct net_bridge *br,
>  						    __be32 ip_dst, __be32 group,
>  						    bool with_srcs, bool over_lmqt,
>  						    u8 sflag, u8 *igmp_type,
> -						    bool *need_rexmit)
> +						    bool *need_rexmit,
> +						    u16 vid)
>  {
>  	struct net_bridge_port *p = pg ? pg->key.port : NULL;
>  	struct net_bridge_group_src *ent;
> @@ -724,6 +834,9 @@ static struct sk_buff *br_ip4_multicast_alloc_query(struct net_bridge *br,
>  	}
>  
>  	pkt_size = sizeof(*eth) + sizeof(*iph) + 4 + igmp_hdr_size;
> +	if (br_vlan_enabled(br->dev) && vid != 0)
> +		pkt_size += 4;
> +
>  	if ((p && pkt_size > p->dev->mtu) ||
>  	    pkt_size > br->dev->mtu)
>  		return NULL;
> @@ -732,6 +845,9 @@ static struct sk_buff *br_ip4_multicast_alloc_query(struct net_bridge *br,
>  	if (!skb)
>  		goto out;
>  
> +	if (br_vlan_enabled(br->dev) && vid != 0)
> +		__vlan_hwaccel_put_tag(skb, htons(ETH_P_8021Q), vid);
> +
>  	skb->protocol = htons(ETH_P_IP);
>  
>  	skb_reset_mac_header(skb);
> @@ -1008,7 +1124,7 @@ static struct sk_buff *br_multicast_alloc_query(struct net_bridge *br,
>  						    ip4_dst, group->dst.ip4,
>  						    with_srcs, over_lmqt,
>  						    sflag, igmp_type,
> -						    need_rexmit);
> +						    need_rexmit, group->vid);
>  #if IS_ENABLED(CONFIG_IPV6)
>  	case htons(ETH_P_IPV6): {
>  		struct in6_addr ip6_dst;
> @@ -1398,7 +1514,7 @@ static void br_multicast_querier_expired(struct net_bridge *br,
>  	if (!netif_running(br->dev) || !br_opt_get(br, BROPT_MULTICAST_ENABLED))
>  		goto out;
>  
> -	br_multicast_start_querier(br, query);
> +	br_multicast_start_querier(br, query, query->vid);
>  
>  out:
>  	spin_unlock(&br->multicast_lock);
> @@ -1406,9 +1522,14 @@ static void br_multicast_querier_expired(struct net_bridge *br,
>  
>  static void br_ip4_multicast_querier_expired(struct timer_list *t)
>  {
> -	struct net_bridge *br = from_timer(br, t, ip4_other_query.timer);
> +	struct bridge_mcast_other_query *other_query =
> +		from_timer(other_query, t, timer);
> +	struct net_bridge *br = other_query->br;
> +	struct bridge_mcast_own_query *query;
>  
> -	br_multicast_querier_expired(br, &br->ip4_own_query);
> +	list_for_each_entry(query, &br->ip4_own_queries, list)
> +		if (query->enabled && query->vid == other_query->vid)
> +			br_multicast_querier_expired(br, query);
>  }
>  
>  #if IS_ENABLED(CONFIG_IPV6)
> @@ -1477,19 +1598,22 @@ static void br_multicast_send_query(struct net_bridge *br,
>  				    struct bridge_mcast_own_query *own_query)
>  {
>  	struct bridge_mcast_other_query *other_query = NULL;
> +	struct net_bridge_vlan_group *vg;
>  	struct br_ip br_group;
>  	unsigned long time;
>  
>  	if (!netif_running(br->dev) ||
> -	    !br_opt_get(br, BROPT_MULTICAST_ENABLED) ||
> -	    !br_opt_get(br, BROPT_MULTICAST_QUERIER))
> +	    !br_opt_get(br, BROPT_MULTICAST_ENABLED))
>  		return;
>  
> -	memset(&br_group.dst, 0, sizeof(br_group.dst));
> +	if (!own_query->enabled)
> +		return;
>  
> -	if (port ? (own_query == &port->ip4_own_query) :
> -		   (own_query == &br->ip4_own_query)) {
> -		other_query = &br->ip4_other_query;
> +	memset(&br_group, 0, sizeof(br_group));
> +
> +	if (own_query->ip4) {
> +		other_query = br_mcast_find_other_query(&br->ip4_other_queries,
> +							own_query->vid);
>  		br_group.proto = htons(ETH_P_IP);
>  #if IS_ENABLED(CONFIG_IPV6)
>  	} else {
> @@ -1501,6 +1625,12 @@ static void br_multicast_send_query(struct net_bridge *br,
>  	if (!other_query || timer_pending(&other_query->timer))
>  		return;
>  
> +	br_group.vid = own_query->vid;
> +
> +	vg =  port ? nbp_vlan_group(port) : br_vlan_group(br);
> +	if (vg->pvid == own_query->vid)
> +		br_group.vid = 0;
> +
>  	__br_multicast_send_query(br, port, NULL, NULL, &br_group, false, 0,
>  				  NULL);
>  
> @@ -1533,9 +1663,10 @@ br_multicast_port_query_expired(struct net_bridge_port *port,
>  
>  static void br_ip4_multicast_port_query_expired(struct timer_list *t)
>  {
> -	struct net_bridge_port *port = from_timer(port, t, ip4_own_query.timer);
> +	struct bridge_mcast_own_query *query = from_timer(query, t, timer);
> +	struct net_bridge_port *port = query->port;
>  
> -	br_multicast_port_query_expired(port, &port->ip4_own_query);
> +	br_multicast_port_query_expired(port, query);
>  }
>  
>  #if IS_ENABLED(CONFIG_IPV6)
> @@ -1551,17 +1682,23 @@ static void br_multicast_port_group_rexmit(struct timer_list *t)
>  {
>  	struct net_bridge_port_group *pg = from_timer(pg, t, rexmit_timer);
>  	struct bridge_mcast_other_query *other_query = NULL;
> +	struct bridge_mcast_own_query *own_query = NULL;
>  	struct net_bridge *br = pg->key.port->br;
> +	u16 vid = pg->key.addr.vid;
>  	bool need_rexmit = false;
>  
>  	spin_lock(&br->multicast_lock);
> +	own_query = br_mcast_find_own_query(&pg->key.port->ip4_own_queries,
> +					    vid);
> +
>  	if (!netif_running(br->dev) || hlist_unhashed(&pg->mglist) ||
>  	    !br_opt_get(br, BROPT_MULTICAST_ENABLED) ||
> -	    !br_opt_get(br, BROPT_MULTICAST_QUERIER))
> +	    !own_query || !own_query->enabled)
>  		goto out;
>  
>  	if (pg->key.addr.proto == htons(ETH_P_IP))
> -		other_query = &br->ip4_other_query;
> +		other_query = br_mcast_find_other_query(&br->ip4_other_queries,
> +							vid);
>  #if IS_ENABLED(CONFIG_IPV6)
>  	else
>  		other_query = &br->ip6_other_query;
> @@ -1603,8 +1740,7 @@ int br_multicast_add_port(struct net_bridge_port *port)
>  
>  	timer_setup(&port->multicast_router_timer,
>  		    br_multicast_router_expired, 0);
> -	timer_setup(&port->ip4_own_query.timer,
> -		    br_ip4_multicast_port_query_expired, 0);
> +	INIT_LIST_HEAD(&port->ip4_own_queries);
>  #if IS_ENABLED(CONFIG_IPV6)
>  	timer_setup(&port->ip6_own_query.timer,
>  		    br_ip6_multicast_port_query_expired, 0);
> @@ -1621,6 +1757,7 @@ int br_multicast_add_port(struct net_bridge_port *port)
>  
>  void br_multicast_del_port(struct net_bridge_port *port)
>  {
> +	struct bridge_mcast_own_query *query, *tmp;
>  	struct net_bridge *br = port->br;
>  	struct net_bridge_port_group *pg;
>  	HLIST_HEAD(deleted_head);
> @@ -1635,6 +1772,9 @@ void br_multicast_del_port(struct net_bridge_port *port)
>  	br_multicast_gc(&deleted_head);
>  	del_timer_sync(&port->multicast_router_timer);
>  	free_percpu(port->mcast_stats);
> +
> +	list_for_each_entry_safe(query, tmp, &port->ip4_own_queries, list)
> +		br_mcast_del_own_query(query);
>  }
>  
>  static void br_multicast_enable(struct bridge_mcast_own_query *query)
> @@ -1646,14 +1786,49 @@ static void br_multicast_enable(struct bridge_mcast_own_query *query)
>  		mod_timer(&query->timer, jiffies);
>  }
>  
> +static void br_multicast_disable(struct bridge_mcast_own_query *query)
> +{
> +	del_timer_sync(&query->timer);
> +}
> +
>  static void __br_multicast_enable_port(struct net_bridge_port *port)
>  {
> +	struct bridge_mcast_own_query *query;
>  	struct net_bridge *br = port->br;
>  
>  	if (!br_opt_get(br, BROPT_MULTICAST_ENABLED) || !netif_running(br->dev))
>  		return;
>  
> -	br_multicast_enable(&port->ip4_own_query);
> +	list_for_each_entry(query, &br->ip4_own_queries, list) {
> +		struct bridge_mcast_own_query *port_query;
> +		struct net_bridge_vlan_group *vg;
> +
> +		if (!query->enabled)
> +			continue;
> +
> +		if (br_vlan_enabled(br->dev)) {
> +			vg = nbp_vlan_group(port);
> +			if (!vg || (vg && !br_vlan_find(vg, query->vid)))
> +				continue;
> +		}
> +
> +		port_query = br_mcast_find_own_query(&port->ip4_own_queries,
> +						     query->vid);
> +		if (!port_query) {
> +			port_query = br_mcast_add_own_query(&port->ip4_own_queries,
> +							    query->vid,
> +							    br_ip4_multicast_port_query_expired);
> +			if (!port_query)
> +				continue;
> +
> +			port_query->port = port;
> +		}
> +
> +		if (query->ip4) {
> +			port_query->ip4 = true;
> +			br_multicast_enable(port_query);
> +		}
> +	}
>  #if IS_ENABLED(CONFIG_IPV6)
>  	br_multicast_enable(&port->ip6_own_query);
>  #endif
> @@ -1673,6 +1848,7 @@ void br_multicast_enable_port(struct net_bridge_port *port)
>  
>  void br_multicast_disable_port(struct net_bridge_port *port)
>  {
> +	struct bridge_mcast_own_query *query;
>  	struct net_bridge *br = port->br;
>  	struct net_bridge_port_group *pg;
>  	struct hlist_node *n;
> @@ -1685,7 +1861,8 @@ void br_multicast_disable_port(struct net_bridge_port *port)
>  	__del_port_router(port);
>  
>  	del_timer(&port->multicast_router_timer);
> -	del_timer(&port->ip4_own_query.timer);
> +	list_for_each_entry(query, &port->ip4_own_queries, list)
> +		del_timer(&query->timer);
>  #if IS_ENABLED(CONFIG_IPV6)
>  	del_timer(&port->ip6_own_query.timer);
>  #endif
> @@ -1717,17 +1894,23 @@ static void __grp_src_mod_timer(struct net_bridge_group_src *src,
>  static void __grp_src_query_marked_and_rexmit(struct net_bridge_port_group *pg)
>  {
>  	struct bridge_mcast_other_query *other_query = NULL;
> +	struct bridge_mcast_own_query *own_query = NULL;
>  	struct net_bridge *br = pg->key.port->br;
>  	u32 lmqc = br->multicast_last_member_count;
>  	unsigned long lmqt, lmi, now = jiffies;
>  	struct net_bridge_group_src *ent;
> +	u16 vid = pg->key.addr.vid;
> +
> +	own_query = br_mcast_find_own_query(&pg->key.port->ip4_own_queries,
> +					    vid);
>  
>  	if (!netif_running(br->dev) ||
>  	    !br_opt_get(br, BROPT_MULTICAST_ENABLED))
>  		return;
>  
>  	if (pg->key.addr.proto == htons(ETH_P_IP))
> -		other_query = &br->ip4_other_query;
> +		other_query = br_mcast_find_other_query(&br->ip4_other_queries,
> +							vid);
>  #if IS_ENABLED(CONFIG_IPV6)
>  	else
>  		other_query = &br->ip6_other_query;
> @@ -1738,7 +1921,7 @@ static void __grp_src_query_marked_and_rexmit(struct net_bridge_port_group *pg)
>  		if (ent->flags & BR_SGRP_F_SEND) {
>  			ent->flags &= ~BR_SGRP_F_SEND;
>  			if (ent->timer.expires > lmqt) {
> -				if (br_opt_get(br, BROPT_MULTICAST_QUERIER) &&
> +				if (own_query && own_query->enabled &&
>  				    other_query &&
>  				    !timer_pending(&other_query->timer))
>  					ent->src_query_rexmit_cnt = lmqc;
> @@ -1747,7 +1930,7 @@ static void __grp_src_query_marked_and_rexmit(struct net_bridge_port_group *pg)
>  		}
>  	}
>  
> -	if (!br_opt_get(br, BROPT_MULTICAST_QUERIER) ||
> +	if (!own_query || !own_query->enabled ||
>  	    !other_query || timer_pending(&other_query->timer))
>  		return;
>  
> @@ -1763,21 +1946,27 @@ static void __grp_src_query_marked_and_rexmit(struct net_bridge_port_group *pg)
>  static void __grp_send_query_and_rexmit(struct net_bridge_port_group *pg)
>  {
>  	struct bridge_mcast_other_query *other_query = NULL;
> +	struct bridge_mcast_own_query *own_query = NULL;
>  	struct net_bridge *br = pg->key.port->br;
>  	unsigned long now = jiffies, lmi;
> +	u16 vid = pg->key.addr.vid;
>  
>  	if (!netif_running(br->dev) ||
>  	    !br_opt_get(br, BROPT_MULTICAST_ENABLED))
>  		return;
>  
> +	own_query = br_mcast_find_own_query(&pg->key.port->ip4_own_queries,
> +					    vid);
> +
>  	if (pg->key.addr.proto == htons(ETH_P_IP))
> -		other_query = &br->ip4_other_query;
> +		other_query = br_mcast_find_other_query(&br->ip4_other_queries,
> +							vid);
>  #if IS_ENABLED(CONFIG_IPV6)
>  	else
>  		other_query = &br->ip6_other_query;
>  #endif
>  
> -	if (br_opt_get(br, BROPT_MULTICAST_QUERIER) &&
> +	if (own_query && own_query->enabled &&
>  	    other_query && !timer_pending(&other_query->timer)) {
>  		lmi = now + br->multicast_last_member_interval;
>  		pg->grp_query_rexmit_cnt = br->multicast_last_member_count - 1;
> @@ -2484,10 +2673,12 @@ static int br_ip6_multicast_mld2_report(struct net_bridge *br,
>  
>  static bool br_ip4_multicast_select_querier(struct net_bridge *br,
>  					    struct net_bridge_port *port,
> -					    __be32 saddr)
> +					    __be32 saddr,
> +					    struct bridge_mcast_own_query *own,
> +					    struct bridge_mcast_other_query *other)
>  {
> -	if (!timer_pending(&br->ip4_own_query.timer) &&
> -	    !timer_pending(&br->ip4_other_query.timer))
> +	if (own && !timer_pending(&own->timer) &&
> +	    !timer_pending(&other->timer))
>  		goto update;
>  
>  	if (!br->ip4_querier.addr.src.ip4)
> @@ -2533,11 +2724,14 @@ static bool br_ip6_multicast_select_querier(struct net_bridge *br,
>  
>  static bool br_multicast_select_querier(struct net_bridge *br,
>  					struct net_bridge_port *port,
> -					struct br_ip *saddr)
> +					struct br_ip *saddr,
> +					struct bridge_mcast_own_query *query,
> +					struct bridge_mcast_other_query *other_query)
>  {
>  	switch (saddr->proto) {
>  	case htons(ETH_P_IP):
> -		return br_ip4_multicast_select_querier(br, port, saddr->src.ip4);
> +		return br_ip4_multicast_select_querier(br, port, saddr->src.ip4,
> +						       query, other_query);
>  #if IS_ENABLED(CONFIG_IPV6)
>  	case htons(ETH_P_IPV6):
>  		return br_ip6_multicast_select_querier(br, port, &saddr->src.ip6);
> @@ -2628,9 +2822,10 @@ static void br_multicast_query_received(struct net_bridge *br,
>  					struct net_bridge_port *port,
>  					struct bridge_mcast_other_query *query,
>  					struct br_ip *saddr,
> -					unsigned long max_delay)
> +					unsigned long max_delay,
> +					struct bridge_mcast_own_query *own_query)
>  {
> -	if (!br_multicast_select_querier(br, port, saddr))
> +	if (!br_multicast_select_querier(br, port, saddr, own_query, query))
>  		return;
>  
>  	br_multicast_update_query_timer(br, query, max_delay);
> @@ -2643,6 +2838,8 @@ static void br_ip4_multicast_query(struct net_bridge *br,
>  				   u16 vid)
>  {
>  	unsigned int transport_len = ip_transport_len(skb);
> +	struct bridge_mcast_other_query *other_query;
> +	struct bridge_mcast_own_query *own_query;
>  	const struct iphdr *iph = ip_hdr(skb);
>  	struct igmphdr *ih = igmp_hdr(skb);
>  	struct net_bridge_mdb_entry *mp;
> @@ -2684,8 +2881,13 @@ static void br_ip4_multicast_query(struct net_bridge *br,
>  		saddr.proto = htons(ETH_P_IP);
>  		saddr.src.ip4 = iph->saddr;
>  
> -		br_multicast_query_received(br, port, &br->ip4_other_query,
> -					    &saddr, max_delay);
> +		br_mcast_add_queries(br, vid);
> +
> +		own_query = br_mcast_find_own_query(&br->ip4_own_queries, vid);
> +		other_query = br_mcast_find_other_query(&br->ip4_other_queries,
> +							vid);
> +		br_multicast_query_received(br, port, other_query, &saddr,
> +					    max_delay, own_query);
>  		goto out;
>  	}
>  
> @@ -2773,7 +2975,7 @@ static int br_ip6_multicast_query(struct net_bridge *br,
>  		saddr.src.ip6 = ipv6_hdr(skb)->saddr;
>  
>  		br_multicast_query_received(br, port, &br->ip6_other_query,
> -					    &saddr, max_delay);
> +					    &saddr, max_delay, NULL);
>  		goto out;
>  	} else if (!group) {
>  		goto out;
> @@ -2850,7 +3052,7 @@ br_multicast_leave_group(struct net_bridge *br,
>  	if (timer_pending(&other_query->timer))
>  		goto out;
>  
> -	if (br_opt_get(br, BROPT_MULTICAST_QUERIER)) {
> +	if (own_query && own_query->enabled) {
>  		__br_multicast_send_query(br, port, NULL, NULL, &mp->addr,
>  					  false, 0, NULL);
>  
> @@ -2916,21 +3118,26 @@ static void br_ip4_multicast_leave_group(struct net_bridge *br,
>  					 __u16 vid,
>  					 const unsigned char *src)
>  {
> -	struct br_ip br_group;
> +	struct bridge_mcast_other_query *other_query;
>  	struct bridge_mcast_own_query *own_query;
> +	struct br_ip br_group;
>  
>  	if (ipv4_is_local_multicast(group))
>  		return;
>  
> -	own_query = port ? &port->ip4_own_query : &br->ip4_own_query;
> +	if (port)
> +		own_query = br_mcast_find_own_query(&port->ip4_own_queries, vid);
> +	else
> +		own_query = br_mcast_find_own_query(&br->ip4_own_queries, vid);
>  
>  	memset(&br_group, 0, sizeof(br_group));
>  	br_group.dst.ip4 = group;
>  	br_group.proto = htons(ETH_P_IP);
>  	br_group.vid = vid;
>  
> -	br_multicast_leave_group(br, port, &br_group, &br->ip4_other_query,
> -				 own_query, src);
> +	other_query = br_mcast_find_other_query(&br->ip4_other_queries, vid);
> +	br_multicast_leave_group(br, port, &br_group, other_query, own_query,
> +				 src);
>  }
>  
>  #if IS_ENABLED(CONFIG_IPV6)
> @@ -3195,9 +3402,10 @@ static void br_multicast_query_expired(struct net_bridge *br,
>  
>  static void br_ip4_multicast_query_expired(struct timer_list *t)
>  {
> -	struct net_bridge *br = from_timer(br, t, ip4_own_query.timer);
> +	struct bridge_mcast_own_query *query = from_timer(query, t, timer);
> +	struct net_bridge *br = query->br;
>  
> -	br_multicast_query_expired(br, &br->ip4_own_query, &br->ip4_querier);
> +	br_multicast_query_expired(br, query, &br->ip4_querier);
>  }
>  
>  #if IS_ENABLED(CONFIG_IPV6)
> @@ -3237,7 +3445,6 @@ void br_multicast_init(struct net_bridge *br)
>  	br->multicast_querier_interval = 255 * HZ;
>  	br->multicast_membership_interval = 260 * HZ;
>  
> -	br->ip4_other_query.delay_time = 0;
>  	br->ip4_querier.port = NULL;
>  	br->multicast_igmp_version = 2;
>  #if IS_ENABLED(CONFIG_IPV6)
> @@ -3251,10 +3458,8 @@ void br_multicast_init(struct net_bridge *br)
>  	spin_lock_init(&br->multicast_lock);
>  	timer_setup(&br->multicast_router_timer,
>  		    br_multicast_local_router_expired, 0);
> -	timer_setup(&br->ip4_other_query.timer,
> -		    br_ip4_multicast_querier_expired, 0);
> -	timer_setup(&br->ip4_own_query.timer,
> -		    br_ip4_multicast_query_expired, 0);
> +	INIT_LIST_HEAD(&br->ip4_other_queries);
> +	INIT_LIST_HEAD(&br->ip4_own_queries);
>  #if IS_ENABLED(CONFIG_IPV6)
>  	timer_setup(&br->ip6_other_query.timer,
>  		    br_ip6_multicast_querier_expired, 0);
> @@ -3341,7 +3546,10 @@ static void __br_multicast_open(struct net_bridge *br,
>  
>  void br_multicast_open(struct net_bridge *br)
>  {
> -	__br_multicast_open(br, &br->ip4_own_query);
> +	struct bridge_mcast_own_query *query;
> +
> +	list_for_each_entry(query, &br->ip4_own_queries, list)
> +		__br_multicast_open(br, query);
>  #if IS_ENABLED(CONFIG_IPV6)
>  	__br_multicast_open(br, &br->ip6_own_query);
>  #endif
> @@ -3349,9 +3557,14 @@ void br_multicast_open(struct net_bridge *br)
>  
>  void br_multicast_stop(struct net_bridge *br)
>  {
> +	struct bridge_mcast_other_query *other_query;
> +	struct bridge_mcast_own_query *query;
> +
>  	del_timer_sync(&br->multicast_router_timer);
> -	del_timer_sync(&br->ip4_other_query.timer);
> -	del_timer_sync(&br->ip4_own_query.timer);
> +	list_for_each_entry(other_query, &br->ip4_other_queries, list)
> +		del_timer_sync(&other_query->timer);
> +	list_for_each_entry(query, &br->ip4_own_queries, list)
> +		del_timer_sync(&query->timer);
>  #if IS_ENABLED(CONFIG_IPV6)
>  	del_timer_sync(&br->ip6_other_query.timer);
>  	del_timer_sync(&br->ip6_own_query.timer);
> @@ -3461,11 +3674,20 @@ int br_multicast_set_port_router(struct net_bridge_port *p, unsigned long val)
>  }
>  
>  static void br_multicast_start_querier(struct net_bridge *br,
> -				       struct bridge_mcast_own_query *query)
> +				       struct bridge_mcast_own_query *query,
> +				       u16 vid)
>  {
> +	struct bridge_mcast_own_query *port_query;
> +	struct net_bridge_vlan_group *vg;
>  	struct net_bridge_port *port;
>  
> -	__br_multicast_open(br, query);
> +	if (br_vlan_enabled(br->dev)) {
> +		vg = br_vlan_group(br);
> +		if (vg && br_vlan_find(vg, vid))
> +			__br_multicast_open(br, query);
> +	} else {
> +		__br_multicast_open(br, query);
> +	}
>  
>  	rcu_read_lock();
>  	list_for_each_entry_rcu(port, &br->port_list, list) {
> @@ -3473,11 +3695,66 @@ static void br_multicast_start_querier(struct net_bridge *br,
>  		    port->state == BR_STATE_BLOCKING)
>  			continue;
>  
> -		if (query == &br->ip4_own_query)
> -			br_multicast_enable(&port->ip4_own_query);
> +		if (br_vlan_enabled(br->dev)) {
> +			vg = nbp_vlan_group(port);
> +			if (!vg || (vg && !br_vlan_find(vg, vid)))
> +				continue;
> +		}
> +
> +		port_query = br_mcast_find_own_query(&port->ip4_own_queries,
> +						     vid);
> +		if (!port_query)
> +			continue;
> +
> +		port_query->enabled = true;
> +
> +		if (query->ip4) {
> +			port_query->ip4 = true;
> +			br_multicast_enable(port_query);
> +		}
>  #if IS_ENABLED(CONFIG_IPV6)
> -		else
> +		else {
>  			br_multicast_enable(&port->ip6_own_query);
> +		}
> +#endif
> +	}
> +	rcu_read_unlock();
> +}
> +
> +static void br_multicast_stop_querier(struct net_bridge *br,
> +				      struct bridge_mcast_own_query *query,
> +				      u16 vid)
> +{
> +	struct bridge_mcast_own_query *port_query;
> +	struct net_bridge_vlan_group *vg;
> +	struct net_bridge_port *port;
> +
> +	query->enabled = false;
> +
> +	rcu_read_lock();
> +	list_for_each_entry_rcu(port, &br->port_list, list) {
> +		if (port->state == BR_STATE_DISABLED ||
> +		    port->state == BR_STATE_BLOCKING)
> +			continue;
> +
> +		if (br_vlan_enabled(br->dev)) {
> +			vg = nbp_vlan_group(port);
> +			if (!vg || (vg && !br_vlan_find(vg, vid)))
> +				continue;
> +		}
> +
> +		port_query = br_mcast_find_own_query(&port->ip4_own_queries,
> +						     vid);
> +		if (!port_query)
> +			continue;
> +
> +		port_query->enabled = false;
> +
> +		if (query->ip4)
> +			br_multicast_disable(port_query);
> +#if IS_ENABLED(CONFIG_IPV6)
> +		else
> +			br_multicast_disable(&port->ip6_own_query);
>  #endif
>  	}
>  	rcu_read_unlock();
> @@ -3553,32 +3830,55 @@ bool br_multicast_router(const struct net_device *dev)
>  }
>  EXPORT_SYMBOL_GPL(br_multicast_router);
>  
> -int br_multicast_set_querier(struct net_bridge *br, unsigned long val)
> +int br_multicast_set_querier(struct net_bridge *br, unsigned long val, u16 vid)
>  {
> +	struct bridge_mcast_other_query *other_query;
> +	struct bridge_mcast_own_query *query;
> +	struct net_bridge_vlan_group *vg;
>  	unsigned long max_delay;
>  
>  	val = !!val;
>  
> +	if (vid == 0) {
> +		vg = br_vlan_group(br);
> +		if (vg)
> +			vid = vg->pvid;
> +	}
> +
>  	spin_lock_bh(&br->multicast_lock);
> -	if (br_opt_get(br, BROPT_MULTICAST_QUERIER) == val)
> +	query = br_mcast_find_own_query(&br->ip4_own_queries, vid);
> +	if (!query) {
> +		if (br_vlan_enabled(br->dev))
> +			goto unlock;
> +
> +		br_mcast_add_queries(br, vid);
> +	}
> +
> +	other_query = br_mcast_find_other_query(&br->ip4_other_queries, vid);
> +	if (!other_query)
>  		goto unlock;
>  
> -	br_opt_toggle(br, BROPT_MULTICAST_QUERIER, !!val);
> -	if (!val)
> +	if (!val && query) {
> +		br_multicast_stop_querier(br, query, vid);
>  		goto unlock;
> +	}
>  
> -	max_delay = br->multicast_query_response_interval;
> +	if (val & query->enabled)
> +		goto unlock;
>  
> -	if (!timer_pending(&br->ip4_other_query.timer))
> -		br->ip4_other_query.delay_time = jiffies + max_delay;
> +	query->enabled = true;
>  
> -	br_multicast_start_querier(br, &br->ip4_own_query);
> +	max_delay = br->multicast_query_response_interval;
> +	if (!timer_pending(&other_query->timer))
> +		other_query->delay_time = jiffies + max_delay;
> +
> +	br_multicast_start_querier(br, query, vid);
>  
>  #if IS_ENABLED(CONFIG_IPV6)
>  	if (!timer_pending(&br->ip6_other_query.timer))
>  		br->ip6_other_query.delay_time = jiffies + max_delay;
>  
> -	br_multicast_start_querier(br, &br->ip6_own_query);
> +	br_multicast_start_querier(br, &br->ip6_own_query, vid);
>  #endif
>  
>  unlock:
> @@ -3587,6 +3887,79 @@ int br_multicast_set_querier(struct net_bridge *br, unsigned long val)
>  	return 0;
>  }
>  
> +void br_multicast_vlan_add(struct net_bridge_vlan *v)
> +{
> +	struct bridge_mcast_own_query *query, *port_query;
> +	struct net_bridge_port *p;
> +	struct net_bridge *br;
> +
> +	if (br_vlan_is_master(v)) {
> +		br_mcast_add_queries(v->br, v->vid);
> +		return;
> +	}
> +
> +	p = v->port;
> +	br = p->br;
> +
> +	query = br_mcast_find_own_query(&br->ip4_own_queries, v->vid);
> +
> +	port_query = br_mcast_add_own_query(&p->ip4_own_queries,
> +					    v->vid,
> +					    br_ip4_multicast_port_query_expired);
> +	if (!port_query)
> +		return;
> +
> +	port_query->port = p;
> +	port_query->ip4 = true;
> +
> +	if (query->enabled) {
> +		port_query->enabled = true;
> +		br_multicast_enable(port_query);
> +	}
> +}
> +
> +void br_multicast_vlan_del(struct net_bridge_vlan *v)
> +{
> +	struct bridge_mcast_other_query *other_query, *other_tmp;
> +	struct bridge_mcast_own_query *query, *tmp;
> +	struct net_bridge_port *p;
> +	struct net_bridge *br;
> +
> +	if (br_vlan_is_master(v)) {
> +		br = v->br;
> +
> +		list_for_each_entry_safe(other_query, other_tmp,
> +					 &br->ip4_other_queries, list)
> +			if (other_query->vid == v->vid)
> +				br_mcast_del_other_query(other_query);
> +
> +		list_for_each_entry_safe(query, tmp, &br->ip4_own_queries, list)
> +			if (query->vid == v->vid)
> +				br_mcast_del_own_query(query);
> +
> +		return;
> +	}
> +
> +	p = v->port;
> +
> +	list_for_each_entry_safe(query, tmp, &p->ip4_own_queries, list) {
> +		if (query->vid == v->vid)
> +			br_mcast_del_own_query(query);
> +	}
> +}
> +
> +void br_multicast_vlan_toggle(struct net_bridge *br, bool on)
> +{
> +	struct bridge_mcast_own_query *query;
> +
> +	list_for_each_entry(query, &br->ip4_own_queries, list) {
> +		if (!on)
> +			br_multicast_stop_querier(br, query, query->vid);
> +		else
> +			br_multicast_start_querier(br, query, query->vid);
> +	}
> +}
> +
>  int br_multicast_set_igmp_version(struct net_bridge *br, unsigned long val)
>  {
>  	/* Currently we support only version 2 and 3 */
> @@ -3711,7 +4084,7 @@ bool br_multicast_has_querier_anywhere(struct net_device *dev, int proto)
>  	memset(&eth, 0, sizeof(eth));
>  	eth.h_proto = htons(proto);
>  
> -	ret = br_multicast_querier_exists(br, &eth, NULL);
> +	ret = br_multicast_any_querier_exists(br, &eth);
>  
>  unlock:
>  	rcu_read_unlock();
> @@ -3746,7 +4119,7 @@ bool br_multicast_has_querier_adjacent(struct net_device *dev, int proto)
>  
>  	switch (proto) {
>  	case ETH_P_IP:
> -		if (!timer_pending(&br->ip4_other_query.timer) ||
> +		if (!br_multicast_any_querier_adjacent(br) ||
>  		    rcu_dereference(br->ip4_querier.port) == port)
>  			goto unlock;
>  		break;
> diff --git a/net/bridge/br_netlink.c b/net/bridge/br_netlink.c
> index 49700ce0e919..d32f4c185364 100644
> --- a/net/bridge/br_netlink.c
> +++ b/net/bridge/br_netlink.c
> @@ -1186,6 +1186,7 @@ static const struct nla_policy br_policy[IFLA_BR_MAX + 1] = {
>  	[IFLA_BR_VLAN_STATS_PER_PORT] = { .type = NLA_U8 },
>  	[IFLA_BR_MULTI_BOOLOPT] =
>  		NLA_POLICY_EXACT_LEN(sizeof(struct br_boolopt_multi)),
> +	[IFLA_BR_MCAST_QUERIER_VID] = { .type = NLA_U16 },
>  };
>  
>  static int br_changelink(struct net_device *brdev, struct nlattr *tb[],
> @@ -1193,6 +1194,7 @@ static int br_changelink(struct net_device *brdev, struct nlattr *tb[],
>  			 struct netlink_ext_ack *extack)
>  {
>  	struct net_bridge *br = netdev_priv(brdev);
> +	u16 vid = 0;
>  	int err;
>  
>  	if (!data)
> @@ -1204,6 +1206,9 @@ static int br_changelink(struct net_device *brdev, struct nlattr *tb[],
>  			return err;
>  	}
>  
> +	if (data[IFLA_BR_MCAST_QUERIER_VID])
> +		vid = nla_get_u16(data[IFLA_BR_MCAST_QUERIER_VID]);
> +
>  	if (data[IFLA_BR_HELLO_TIME]) {
>  		err = br_set_hello_time(br, nla_get_u32(data[IFLA_BR_HELLO_TIME]));
>  		if (err)
> @@ -1333,7 +1338,7 @@ static int br_changelink(struct net_device *brdev, struct nlattr *tb[],
>  	if (data[IFLA_BR_MCAST_QUERIER]) {
>  		u8 mcast_querier = nla_get_u8(data[IFLA_BR_MCAST_QUERIER]);
>  
> -		err = br_multicast_set_querier(br, mcast_querier);
> +		err = br_multicast_set_querier(br, mcast_querier, vid);
>  		if (err)
>  			return err;
>  	}
> @@ -1596,7 +1601,7 @@ static int br_fill_info(struct sk_buff *skb, const struct net_device *brdev)
>  	    nla_put_u8(skb, IFLA_BR_MCAST_QUERY_USE_IFADDR,
>  		       br_opt_get(br, BROPT_MULTICAST_QUERY_USE_IFADDR)) ||
>  	    nla_put_u8(skb, IFLA_BR_MCAST_QUERIER,
> -		       br_opt_get(br, BROPT_MULTICAST_QUERIER)) ||
> +		       br_mcast_exist_own_query(br)) ||
>  	    nla_put_u8(skb, IFLA_BR_MCAST_STATS_ENABLED,
>  		       br_opt_get(br, BROPT_MULTICAST_STATS_ENABLED)) ||
>  	    nla_put_u32(skb, IFLA_BR_MCAST_HASH_ELASTICITY, RHT_ELASTICITY) ||
> diff --git a/net/bridge/br_private.h b/net/bridge/br_private.h
> index d62c6e1af64a..84f597f542b1 100644
> --- a/net/bridge/br_private.h
> +++ b/net/bridge/br_private.h
> @@ -66,14 +66,24 @@ struct mac_addr {
>  #ifdef CONFIG_BRIDGE_IGMP_SNOOPING
>  /* our own querier */
>  struct bridge_mcast_own_query {
> +	struct list_head	list;
>  	struct timer_list	timer;
>  	u32			startup_sent;
> +	struct net_bridge_port	*port;
> +	struct net_bridge	*br;
> +	bool			ip4;
> +	u16			vid;
> +	bool			enabled;
>  };
>  
>  /* other querier */
>  struct bridge_mcast_other_query {
> +	struct list_head		list;
>  	struct timer_list		timer;
>  	unsigned long			delay_time;
> +	struct net_bridge		*br;
> +	bool				ip4;
> +	u16				vid;
>  };
>  
>  /* selected querier */
> @@ -304,7 +314,7 @@ struct net_bridge_port {
>  	struct rcu_head			rcu;
>  
>  #ifdef CONFIG_BRIDGE_IGMP_SNOOPING
> -	struct bridge_mcast_own_query	ip4_own_query;
> +	struct list_head		ip4_own_queries;
>  #if IS_ENABLED(CONFIG_IPV6)
>  	struct bridge_mcast_own_query	ip6_own_query;
>  #endif /* IS_ENABLED(CONFIG_IPV6) */
> @@ -448,8 +458,8 @@ struct net_bridge {
>  	struct hlist_head		router_list;
>  
>  	struct timer_list		multicast_router_timer;
> -	struct bridge_mcast_other_query	ip4_other_query;
> -	struct bridge_mcast_own_query	ip4_own_query;
> +	struct list_head		ip4_other_queries;
> +	struct list_head		ip4_own_queries;
>  	struct bridge_mcast_querier	ip4_querier;
>  	struct bridge_mcast_stats	__percpu *mcast_stats;
>  #if IS_ENABLED(CONFIG_IPV6)
> @@ -788,6 +798,9 @@ int br_ioctl_deviceless_stub(struct net *net, unsigned int cmd,
>  
>  /* br_multicast.c */
>  #ifdef CONFIG_BRIDGE_IGMP_SNOOPING
> +void br_multicast_vlan_add(struct net_bridge_vlan *v);
> +void br_multicast_vlan_del(struct net_bridge_vlan *v);
> +void br_multicast_vlan_toggle(struct net_bridge *br, bool on);
>  int br_multicast_rcv(struct net_bridge *br, struct net_bridge_port *port,
>  		     struct sk_buff *skb, u16 vid);
>  struct net_bridge_mdb_entry *br_mdb_get(struct net_bridge *br,
> @@ -807,7 +820,7 @@ void br_multicast_flood(struct net_bridge_mdb_entry *mdst,
>  int br_multicast_set_router(struct net_bridge *br, unsigned long val);
>  int br_multicast_set_port_router(struct net_bridge_port *p, unsigned long val);
>  int br_multicast_toggle(struct net_bridge *br, unsigned long val);
> -int br_multicast_set_querier(struct net_bridge *br, unsigned long val);
> +int br_multicast_set_querier(struct net_bridge *br, unsigned long val, u16 vid);
>  int br_multicast_set_hash_max(struct net_bridge *br, unsigned long val);
>  int br_multicast_set_igmp_version(struct net_bridge *br, unsigned long val);
>  #if IS_ENABLED(CONFIG_IPV6)
> @@ -846,6 +859,11 @@ 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 bridge_mcast_other_query *
> +br_mcast_find_other_query(struct list_head *list, u16 vid);
> +struct bridge_mcast_own_query *
> +br_mcast_find_own_query(struct list_head *list, u16 vid);
> +bool br_mcast_exist_own_query(struct net_bridge *br);
>  
>  static inline bool br_group_is_l2(const struct br_ip *group)
>  {
> @@ -865,11 +883,15 @@ static inline bool br_multicast_is_router(struct net_bridge *br)
>  static inline bool
>  __br_multicast_querier_exists(struct net_bridge *br,
>  				struct bridge_mcast_other_query *querier,
> -				const bool is_ipv6)
> +				const bool is_ipv6,
> +				u16 vid)
>  {
> +	struct bridge_mcast_own_query *query;
>  	bool own_querier_enabled;
>  
> -	if (br_opt_get(br, BROPT_MULTICAST_QUERIER)) {
> +	query = br_mcast_find_own_query(&br->ip4_own_queries, vid);
> +
> +	if (query && query->enabled) {
>  		if (is_ipv6 && !br_opt_get(br, BROPT_HAS_IPV6_ADDR))
>  			own_querier_enabled = false;
>  		else
> @@ -878,28 +900,62 @@ __br_multicast_querier_exists(struct net_bridge *br,
>  		own_querier_enabled = false;
>  	}
>  
> +	if (!querier)
> +		return own_querier_enabled;
> +
>  	return time_is_before_jiffies(querier->delay_time) &&
>  	       (own_querier_enabled || timer_pending(&querier->timer));
>  }
>  
>  static inline bool br_multicast_querier_exists(struct net_bridge *br,
>  					       struct ethhdr *eth,
> -					       const struct net_bridge_mdb_entry *mdb)
> +					       const struct net_bridge_mdb_entry *mdb,
> +					       u16 vid)
>  {
> +	struct bridge_mcast_other_query *query =
> +		br_mcast_find_other_query(&br->ip4_other_queries, vid);
> +
>  	switch (eth->h_proto) {
>  	case (htons(ETH_P_IP)):
> -		return __br_multicast_querier_exists(br,
> -			&br->ip4_other_query, false);
> +		return __br_multicast_querier_exists(br, query, false, vid);
>  #if IS_ENABLED(CONFIG_IPV6)
>  	case (htons(ETH_P_IPV6)):
>  		return __br_multicast_querier_exists(br,
> -			&br->ip6_other_query, true);
> +			&br->ip6_other_query, true, vid);
>  #endif
>  	default:
>  		return !!mdb && br_group_is_l2(&mdb->addr);
>  	}
>  }
>  
> +static inline bool br_multicast_any_querier_exists(struct net_bridge *br,
> +						   struct ethhdr *eth)
> +{
> +	struct bridge_mcast_other_query *query;
> +
> +	list_for_each_entry(query, &br->ip4_other_queries, list) {
> +		if (!timer_pending(&query->timer))
> +			continue;
> +
> +		if (br_multicast_querier_exists(br, eth, NULL, query->vid))
> +			return true;
> +	}
> +
> +	return false;
> +}
> +
> +static inline bool br_multicast_any_querier_adjacent(struct net_bridge *br)
> +{
> +	struct bridge_mcast_other_query *query;
> +
> +	list_for_each_entry(query, &br->ip4_other_queries, list) {
> +		if (timer_pending(&query->timer))
> +			return true;
> +	}
> +
> +	return false;
> +}
> +
>  static inline bool br_multicast_is_star_g(const struct br_ip *ip)
>  {
>  	switch (ip->proto) {
> @@ -1015,7 +1071,19 @@ static inline bool br_multicast_is_router(struct net_bridge *br)
>  
>  static inline bool br_multicast_querier_exists(struct net_bridge *br,
>  					       struct ethhdr *eth,
> -					       const struct net_bridge_mdb_entry *mdb)
> +					       const struct net_bridge_mdb_entry *mdb,
> +					       u16 vid)
> +{
> +	return false;
> +}
> +
> +static inline bool br_multicast_any_querier_exists(struct net_bridge *br,
> +						   struct ethhdr *eth)
> +{
> +	return false;
> +}
> +
> +static inline bool br_multicast_any_querier_adjacent(struct net_bridge *br)
>  {
>  	return false;
>  }
> diff --git a/net/bridge/br_sysfs_br.c b/net/bridge/br_sysfs_br.c
> index 7db06e3f642a..23bf6a065d78 100644
> --- a/net/bridge/br_sysfs_br.c
> +++ b/net/bridge/br_sysfs_br.c
> @@ -51,6 +51,33 @@ static ssize_t store_bridge_parm(struct device *d,
>  	return err ? err : len;
>  }
>  
> +static ssize_t store_bridge_parm2(struct device *d,
> +				  const char *buf, size_t len,
> +				  int (*set)(struct net_bridge *, unsigned long, u16))
> +{
> +	struct net_bridge *br = to_bridge(d);
> +	char *endp;
> +	unsigned long val;
> +	int err;
> +
> +	if (!ns_capable(dev_net(br->dev)->user_ns, CAP_NET_ADMIN))
> +		return -EPERM;
> +
> +	val = simple_strtoul(buf, &endp, 0);
> +	if (endp == buf)
> +		return -EINVAL;
> +
> +	if (!rtnl_trylock())
> +		return restart_syscall();
> +
> +	err = (*set)(br, val, 0);
> +	if (!err)
> +		netdev_state_change(br->dev);
> +	rtnl_unlock();
> +
> +	return err ? err : len;
> +}
> +
>  
>  static ssize_t forward_delay_show(struct device *d,
>  				  struct device_attribute *attr, char *buf)
> @@ -404,14 +431,14 @@ static ssize_t multicast_querier_show(struct device *d,
>  				      char *buf)
>  {
>  	struct net_bridge *br = to_bridge(d);
> -	return sprintf(buf, "%d\n", br_opt_get(br, BROPT_MULTICAST_QUERIER));
> +	return sprintf(buf, "%d\n", br_mcast_exist_own_query(br));
>  }
>  
>  static ssize_t multicast_querier_store(struct device *d,
>  				       struct device_attribute *attr,
>  				       const char *buf, size_t len)
>  {
> -	return store_bridge_parm(d, buf, len, br_multicast_set_querier);
> +	return store_bridge_parm2(d, buf, len, br_multicast_set_querier);
>  }
>  static DEVICE_ATTR_RW(multicast_querier);
>  
> diff --git a/net/bridge/br_vlan.c b/net/bridge/br_vlan.c
> index 701cad646b20..2e0b544a3560 100644
> --- a/net/bridge/br_vlan.c
> +++ b/net/bridge/br_vlan.c
> @@ -308,6 +308,7 @@ static int __vlan_add(struct net_bridge_vlan *v, u16 flags,
>  
>  	__vlan_add_list(v);
>  	__vlan_add_flags(v, flags);
> +	br_multicast_vlan_add(v);
>  
>  	if (p)
>  		nbp_vlan_set_vlan_dev_state(p, v->vid);
> @@ -353,6 +354,7 @@ static int __vlan_del(struct net_bridge_vlan *v)
>  		masterv = v->brvlan;
>  	}
>  
> +	br_multicast_vlan_del(v);
>  	__vlan_delete_pvid(vg, v->vid);
>  	if (p) {
>  		err = __vlan_vid_del(p->dev, p->br, v);
> @@ -827,6 +829,7 @@ int __br_vlan_filter_toggle(struct net_bridge *br, unsigned long val)
>  	br_manage_promisc(br);
>  	recalculate_group_addr(br);
>  	br_recalculate_fwd_mask(br);
> +	br_multicast_vlan_toggle(br, !!val);
>  
>  	return 0;
>  }
> 


^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [RFC PATCH v2] net: bridge: igmp: Extend IGMP query to be per vlan
  2021-01-13 12:15   ` [Bridge] " Nikolay Aleksandrov
@ 2021-01-13 17:02     ` Horatiu Vultur
  -1 siblings, 0 replies; 16+ messages in thread
From: Horatiu Vultur @ 2021-01-13 17:02 UTC (permalink / raw)
  To: Nikolay Aleksandrov
  Cc: davem, kuba, roopa, allan.nielsen, netdev, linux-kernel, bridge

The 01/13/2021 14:15, Nikolay Aleksandrov wrote:
> EXTERNAL EMAIL: Do not click links or open attachments unless you know the content is safe
> 
> On 12/01/2021 15:59, Horatiu Vultur wrote:
> > Based on the comments of the previous version, we started to work on a
> > new version, so it would be possible to enable/disable queries per vlan.
> > This is still work in progress and there are plenty of things that are
> > not implemented and tested:
> > - ipv6 support
> > - the fast path needs to be improved
> > - currently it is possible only to enable/disable the queries per vlan,
> >   all the other configurations are global
> > - toggling vlan_filtering is not tested
> > - remove duplicated information
> > - etc...
> >
> > But there are few things that are working like:
> > - sending queries per vlan
> > - stop sending queries if there is a better querier per vlan
> > - when ports are added/removed from vlan
> > - etc...
> >
> > We were wondering if this what you had in mind when you proposed to have
> > this per vlan? Or we are completely off? Or we should fix some of the
> > issues that I mentioned, before you can see more clearly the direction?
> >
> > Signed-off-by: Horatiu Vultur <horatiu.vultur@microchip.com>
> > ---
> >  include/uapi/linux/if_link.h |   1 +
> >  net/bridge/br_device.c       |   2 +-
> >  net/bridge/br_input.c        |   2 +-
> >  net/bridge/br_multicast.c    | 505 ++++++++++++++++++++++++++++++-----
> >  net/bridge/br_netlink.c      |   9 +-
> >  net/bridge/br_private.h      |  90 ++++++-
> >  net/bridge/br_sysfs_br.c     |  31 ++-
> >  net/bridge/br_vlan.c         |   3 +
> >  8 files changed, 560 insertions(+), 83 deletions(-)
> >
> 

Hi Nik,

> Hi Horatiu,
> No, unfortunately not even close.

At least is good that I didn't continue in this direction :)

> We already have per-port per-vlan and global per-vlan
> contexts which are also linked together for each vlan, those must be used for any vlan
> configuration and state. The problem is that you'd have to mix igmp and vlan code and
> those two live under two different kconfig options, and worse rely on different locks, so
> extra care must be taken.

Point taken.

> Any vlan lookups must use the vlan hashes, (almost) _no_ linear
> walks or new lists are needed (the exception is obviously port going down where a walk
> over port's vlans is needed).

Yes, I was thinking about this vlan lookups that need to be change to
use the hashes but I thought I can do it later.

> In almost all contexts below a vlan lookup has already been
> done by the input functions, the result of that lookup must be saved and re-used. The
> vlan options API needs to be used for configuring vlans (per-vlan mcast options), unfortunately
> I still haven't upstreamed the iproute2 part, so you might have to do that as well.
> Obviously with all of the above the current default situation must not change unless the
> user configures it so. If you don't need this asap, I'll probably get to it in two months
> after EHT and the new bridge flush api, even we are still carrying an out-of-tree patch
> for this which someone (not from cumulus) tried to upstream a few years back, but it also has
> wrong design in general. :)

Definetly this is not asap. But I would like to try to have another look
at this to see if I get closer to what you are looking for :)

> 
> Thanks,
>  Nik
> 
> > diff --git a/include/uapi/linux/if_link.h b/include/uapi/linux/if_link.h
> > index 82708c6db432..11ec1d45c24e 100644
> > --- a/include/uapi/linux/if_link.h
> > +++ b/include/uapi/linux/if_link.h
> > @@ -472,6 +472,7 @@ enum {
> >       IFLA_BR_MCAST_MLD_VERSION,
> >       IFLA_BR_VLAN_STATS_PER_PORT,
> >       IFLA_BR_MULTI_BOOLOPT,
> > +     IFLA_BR_MCAST_QUERIER_VID,
> >       __IFLA_BR_MAX,
> >  };
> >
> > diff --git a/net/bridge/br_device.c b/net/bridge/br_device.c
> > index 3f2f06b4dd27..aca4e8074a8f 100644
> > --- a/net/bridge/br_device.c
> > +++ b/net/bridge/br_device.c
> > @@ -89,7 +89,7 @@ netdev_tx_t br_dev_xmit(struct sk_buff *skb, struct net_device *dev)
> >
> >               mdst = br_mdb_get(br, skb, vid);
> >               if ((mdst || BR_INPUT_SKB_CB_MROUTERS_ONLY(skb)) &&
> > -                 br_multicast_querier_exists(br, eth_hdr(skb), mdst))
> > +                 br_multicast_querier_exists(br, eth_hdr(skb), mdst, vid))
> >                       br_multicast_flood(mdst, skb, false, true);
> >               else
> >                       br_flood(br, skb, BR_PKT_MULTICAST, false, true);
> > diff --git a/net/bridge/br_input.c b/net/bridge/br_input.c
> > index 222285d9dae2..03e445af6c1f 100644
> > --- a/net/bridge/br_input.c
> > +++ b/net/bridge/br_input.c
> > @@ -130,7 +130,7 @@ int br_handle_frame_finish(struct net *net, struct sock *sk, struct sk_buff *skb
> >       case BR_PKT_MULTICAST:
> >               mdst = br_mdb_get(br, skb, vid);
> >               if ((mdst || BR_INPUT_SKB_CB_MROUTERS_ONLY(skb)) &&
> > -                 br_multicast_querier_exists(br, eth_hdr(skb), mdst)) {
> > +                 br_multicast_querier_exists(br, eth_hdr(skb), mdst, vid)) {
> >                       if ((mdst && mdst->host_joined) ||
> >                           br_multicast_is_router(br)) {
> >                               local_rcv = true;
> > diff --git a/net/bridge/br_multicast.c b/net/bridge/br_multicast.c
> > index 257ac4e25f6d..b4fac25101e4 100644
> > --- a/net/bridge/br_multicast.c
> > +++ b/net/bridge/br_multicast.c
> > @@ -48,8 +48,11 @@ static const struct rhashtable_params br_sg_port_rht_params = {
> >       .automatic_shrinking = true,
> >  };
> >
> > +static void br_ip4_multicast_query_expired(struct timer_list *t);
> > +static void br_ip4_multicast_querier_expired(struct timer_list *t);
> >  static void br_multicast_start_querier(struct net_bridge *br,
> > -                                    struct bridge_mcast_own_query *query);
> > +                                    struct bridge_mcast_own_query *query,
> > +                                    u16 vid);
> >  static void br_multicast_add_router(struct net_bridge *br,
> >                                   struct net_bridge_port *port);
> >  static void br_ip4_multicast_leave_group(struct net_bridge *br,
> > @@ -87,6 +90,112 @@ br_sg_port_find(struct net_bridge *br,
> >                                     br_sg_port_rht_params);
> >  }
> >
> > +static void br_mcast_del_other_query(struct bridge_mcast_other_query *query)
> > +{
> > +     del_timer_sync(&query->timer);
> > +     list_del(&query->list);
> > +     kfree(query);
> > +}
> > +
> > +static struct bridge_mcast_other_query *
> > +br_mcast_add_other_query(struct list_head *list, u16 vid,
> > +                      void (*callback)(struct timer_list *t))
> > +{
> > +     struct bridge_mcast_other_query *query;
> > +
> > +     query = kzalloc(sizeof(*query), GFP_KERNEL);
> > +     if (!query)
> > +             return NULL;
> > +
> > +     query->vid = vid;
> > +     timer_setup(&query->timer, callback, 0);
> > +
> > +     list_add(&query->list, list);
> > +
> > +     return query;
> > +}
> > +
> > +static void br_mcast_del_own_query(struct bridge_mcast_own_query *query)
> > +{
> > +     del_timer_sync(&query->timer);
> > +     list_del(&query->list);
> > +     kfree(query);
> > +}
> > +
> > +static struct bridge_mcast_own_query *
> > +br_mcast_add_own_query(struct list_head *list, u16 vid,
> > +                    void (*callback)(struct timer_list *t))
> > +{
> > +     struct bridge_mcast_own_query *query;
> > +
> > +     query = kzalloc(sizeof(*query), GFP_KERNEL);
> > +     if (!query)
> > +             return NULL;
> > +
> > +     query->vid = vid;
> > +     timer_setup(&query->timer, callback, 0);
> > +
> > +     list_add(&query->list, list);
> > +
> > +     return query;
> > +}
> > +
> > +static void br_mcast_add_queries(struct net_bridge *br, u16 vid)
> > +{
> > +     struct bridge_mcast_other_query *other;
> > +     struct bridge_mcast_own_query *own;
> > +
> > +     own = br_mcast_find_own_query(&br->ip4_own_queries, vid);
> > +     if (!own) {
> > +             own = br_mcast_add_own_query(&br->ip4_own_queries, vid,
> > +                                          br_ip4_multicast_query_expired);
> > +             own->ip4 = true;
> > +             own->br = br;
> > +     }
> > +
> > +     other = br_mcast_find_other_query(&br->ip4_other_queries, vid);
> > +     if (!other) {
> > +             other = br_mcast_add_other_query(&br->ip4_other_queries, vid,
> > +                                              br_ip4_multicast_querier_expired);
> > +             other->br = br;
> > +     }
> > +}
> > +
> > +struct bridge_mcast_own_query *
> > +br_mcast_find_own_query(struct list_head *list, u16 vid)
> > +{
> > +     struct bridge_mcast_own_query *query = NULL;
> > +
> > +     list_for_each_entry(query, list, list)
> > +             if (query->vid == vid)
> > +                     return query;
> > +
> > +     return NULL;
> > +}
> > +
> > +struct bridge_mcast_other_query *
> > +br_mcast_find_other_query(struct list_head *list, u16 vid)
> > +{
> > +     struct bridge_mcast_other_query *query = NULL;
> > +
> > +     list_for_each_entry(query, list, list)
> > +             if (query->vid == vid)
> > +                     return query;
> > +
> > +     return NULL;
> > +}
> > +
> > +bool br_mcast_exist_own_query(struct net_bridge *br)
> > +{
> > +     struct bridge_mcast_own_query *query = NULL;
> > +
> > +     list_for_each_entry(query, &br->ip4_own_queries, list)
> > +             if (query->enabled)
> > +                     return true;
> > +
> > +     return false;
> > +}
> > +
> >  static struct net_bridge_mdb_entry *br_mdb_ip_get_rcu(struct net_bridge *br,
> >                                                     struct br_ip *dst)
> >  {
> > @@ -688,7 +797,8 @@ static struct sk_buff *br_ip4_multicast_alloc_query(struct net_bridge *br,
> >                                                   __be32 ip_dst, __be32 group,
> >                                                   bool with_srcs, bool over_lmqt,
> >                                                   u8 sflag, u8 *igmp_type,
> > -                                                 bool *need_rexmit)
> > +                                                 bool *need_rexmit,
> > +                                                 u16 vid)
> >  {
> >       struct net_bridge_port *p = pg ? pg->key.port : NULL;
> >       struct net_bridge_group_src *ent;
> > @@ -724,6 +834,9 @@ static struct sk_buff *br_ip4_multicast_alloc_query(struct net_bridge *br,
> >       }
> >
> >       pkt_size = sizeof(*eth) + sizeof(*iph) + 4 + igmp_hdr_size;
> > +     if (br_vlan_enabled(br->dev) && vid != 0)
> > +             pkt_size += 4;
> > +
> >       if ((p && pkt_size > p->dev->mtu) ||
> >           pkt_size > br->dev->mtu)
> >               return NULL;
> > @@ -732,6 +845,9 @@ static struct sk_buff *br_ip4_multicast_alloc_query(struct net_bridge *br,
> >       if (!skb)
> >               goto out;
> >
> > +     if (br_vlan_enabled(br->dev) && vid != 0)
> > +             __vlan_hwaccel_put_tag(skb, htons(ETH_P_8021Q), vid);
> > +
> >       skb->protocol = htons(ETH_P_IP);
> >
> >       skb_reset_mac_header(skb);
> > @@ -1008,7 +1124,7 @@ static struct sk_buff *br_multicast_alloc_query(struct net_bridge *br,
> >                                                   ip4_dst, group->dst.ip4,
> >                                                   with_srcs, over_lmqt,
> >                                                   sflag, igmp_type,
> > -                                                 need_rexmit);
> > +                                                 need_rexmit, group->vid);
> >  #if IS_ENABLED(CONFIG_IPV6)
> >       case htons(ETH_P_IPV6): {
> >               struct in6_addr ip6_dst;
> > @@ -1398,7 +1514,7 @@ static void br_multicast_querier_expired(struct net_bridge *br,
> >       if (!netif_running(br->dev) || !br_opt_get(br, BROPT_MULTICAST_ENABLED))
> >               goto out;
> >
> > -     br_multicast_start_querier(br, query);
> > +     br_multicast_start_querier(br, query, query->vid);
> >
> >  out:
> >       spin_unlock(&br->multicast_lock);
> > @@ -1406,9 +1522,14 @@ static void br_multicast_querier_expired(struct net_bridge *br,
> >
> >  static void br_ip4_multicast_querier_expired(struct timer_list *t)
> >  {
> > -     struct net_bridge *br = from_timer(br, t, ip4_other_query.timer);
> > +     struct bridge_mcast_other_query *other_query =
> > +             from_timer(other_query, t, timer);
> > +     struct net_bridge *br = other_query->br;
> > +     struct bridge_mcast_own_query *query;
> >
> > -     br_multicast_querier_expired(br, &br->ip4_own_query);
> > +     list_for_each_entry(query, &br->ip4_own_queries, list)
> > +             if (query->enabled && query->vid == other_query->vid)
> > +                     br_multicast_querier_expired(br, query);
> >  }
> >
> >  #if IS_ENABLED(CONFIG_IPV6)
> > @@ -1477,19 +1598,22 @@ static void br_multicast_send_query(struct net_bridge *br,
> >                                   struct bridge_mcast_own_query *own_query)
> >  {
> >       struct bridge_mcast_other_query *other_query = NULL;
> > +     struct net_bridge_vlan_group *vg;
> >       struct br_ip br_group;
> >       unsigned long time;
> >
> >       if (!netif_running(br->dev) ||
> > -         !br_opt_get(br, BROPT_MULTICAST_ENABLED) ||
> > -         !br_opt_get(br, BROPT_MULTICAST_QUERIER))
> > +         !br_opt_get(br, BROPT_MULTICAST_ENABLED))
> >               return;
> >
> > -     memset(&br_group.dst, 0, sizeof(br_group.dst));
> > +     if (!own_query->enabled)
> > +             return;
> >
> > -     if (port ? (own_query == &port->ip4_own_query) :
> > -                (own_query == &br->ip4_own_query)) {
> > -             other_query = &br->ip4_other_query;
> > +     memset(&br_group, 0, sizeof(br_group));
> > +
> > +     if (own_query->ip4) {
> > +             other_query = br_mcast_find_other_query(&br->ip4_other_queries,
> > +                                                     own_query->vid);
> >               br_group.proto = htons(ETH_P_IP);
> >  #if IS_ENABLED(CONFIG_IPV6)
> >       } else {
> > @@ -1501,6 +1625,12 @@ static void br_multicast_send_query(struct net_bridge *br,
> >       if (!other_query || timer_pending(&other_query->timer))
> >               return;
> >
> > +     br_group.vid = own_query->vid;
> > +
> > +     vg =  port ? nbp_vlan_group(port) : br_vlan_group(br);
> > +     if (vg->pvid == own_query->vid)
> > +             br_group.vid = 0;
> > +
> >       __br_multicast_send_query(br, port, NULL, NULL, &br_group, false, 0,
> >                                 NULL);
> >
> > @@ -1533,9 +1663,10 @@ br_multicast_port_query_expired(struct net_bridge_port *port,
> >
> >  static void br_ip4_multicast_port_query_expired(struct timer_list *t)
> >  {
> > -     struct net_bridge_port *port = from_timer(port, t, ip4_own_query.timer);
> > +     struct bridge_mcast_own_query *query = from_timer(query, t, timer);
> > +     struct net_bridge_port *port = query->port;
> >
> > -     br_multicast_port_query_expired(port, &port->ip4_own_query);
> > +     br_multicast_port_query_expired(port, query);
> >  }
> >
> >  #if IS_ENABLED(CONFIG_IPV6)
> > @@ -1551,17 +1682,23 @@ static void br_multicast_port_group_rexmit(struct timer_list *t)
> >  {
> >       struct net_bridge_port_group *pg = from_timer(pg, t, rexmit_timer);
> >       struct bridge_mcast_other_query *other_query = NULL;
> > +     struct bridge_mcast_own_query *own_query = NULL;
> >       struct net_bridge *br = pg->key.port->br;
> > +     u16 vid = pg->key.addr.vid;
> >       bool need_rexmit = false;
> >
> >       spin_lock(&br->multicast_lock);
> > +     own_query = br_mcast_find_own_query(&pg->key.port->ip4_own_queries,
> > +                                         vid);
> > +
> >       if (!netif_running(br->dev) || hlist_unhashed(&pg->mglist) ||
> >           !br_opt_get(br, BROPT_MULTICAST_ENABLED) ||
> > -         !br_opt_get(br, BROPT_MULTICAST_QUERIER))
> > +         !own_query || !own_query->enabled)
> >               goto out;
> >
> >       if (pg->key.addr.proto == htons(ETH_P_IP))
> > -             other_query = &br->ip4_other_query;
> > +             other_query = br_mcast_find_other_query(&br->ip4_other_queries,
> > +                                                     vid);
> >  #if IS_ENABLED(CONFIG_IPV6)
> >       else
> >               other_query = &br->ip6_other_query;
> > @@ -1603,8 +1740,7 @@ int br_multicast_add_port(struct net_bridge_port *port)
> >
> >       timer_setup(&port->multicast_router_timer,
> >                   br_multicast_router_expired, 0);
> > -     timer_setup(&port->ip4_own_query.timer,
> > -                 br_ip4_multicast_port_query_expired, 0);
> > +     INIT_LIST_HEAD(&port->ip4_own_queries);
> >  #if IS_ENABLED(CONFIG_IPV6)
> >       timer_setup(&port->ip6_own_query.timer,
> >                   br_ip6_multicast_port_query_expired, 0);
> > @@ -1621,6 +1757,7 @@ int br_multicast_add_port(struct net_bridge_port *port)
> >
> >  void br_multicast_del_port(struct net_bridge_port *port)
> >  {
> > +     struct bridge_mcast_own_query *query, *tmp;
> >       struct net_bridge *br = port->br;
> >       struct net_bridge_port_group *pg;
> >       HLIST_HEAD(deleted_head);
> > @@ -1635,6 +1772,9 @@ void br_multicast_del_port(struct net_bridge_port *port)
> >       br_multicast_gc(&deleted_head);
> >       del_timer_sync(&port->multicast_router_timer);
> >       free_percpu(port->mcast_stats);
> > +
> > +     list_for_each_entry_safe(query, tmp, &port->ip4_own_queries, list)
> > +             br_mcast_del_own_query(query);
> >  }
> >
> >  static void br_multicast_enable(struct bridge_mcast_own_query *query)
> > @@ -1646,14 +1786,49 @@ static void br_multicast_enable(struct bridge_mcast_own_query *query)
> >               mod_timer(&query->timer, jiffies);
> >  }
> >
> > +static void br_multicast_disable(struct bridge_mcast_own_query *query)
> > +{
> > +     del_timer_sync(&query->timer);
> > +}
> > +
> >  static void __br_multicast_enable_port(struct net_bridge_port *port)
> >  {
> > +     struct bridge_mcast_own_query *query;
> >       struct net_bridge *br = port->br;
> >
> >       if (!br_opt_get(br, BROPT_MULTICAST_ENABLED) || !netif_running(br->dev))
> >               return;
> >
> > -     br_multicast_enable(&port->ip4_own_query);
> > +     list_for_each_entry(query, &br->ip4_own_queries, list) {
> > +             struct bridge_mcast_own_query *port_query;
> > +             struct net_bridge_vlan_group *vg;
> > +
> > +             if (!query->enabled)
> > +                     continue;
> > +
> > +             if (br_vlan_enabled(br->dev)) {
> > +                     vg = nbp_vlan_group(port);
> > +                     if (!vg || (vg && !br_vlan_find(vg, query->vid)))
> > +                             continue;
> > +             }
> > +
> > +             port_query = br_mcast_find_own_query(&port->ip4_own_queries,
> > +                                                  query->vid);
> > +             if (!port_query) {
> > +                     port_query = br_mcast_add_own_query(&port->ip4_own_queries,
> > +                                                         query->vid,
> > +                                                         br_ip4_multicast_port_query_expired);
> > +                     if (!port_query)
> > +                             continue;
> > +
> > +                     port_query->port = port;
> > +             }
> > +
> > +             if (query->ip4) {
> > +                     port_query->ip4 = true;
> > +                     br_multicast_enable(port_query);
> > +             }
> > +     }
> >  #if IS_ENABLED(CONFIG_IPV6)
> >       br_multicast_enable(&port->ip6_own_query);
> >  #endif
> > @@ -1673,6 +1848,7 @@ void br_multicast_enable_port(struct net_bridge_port *port)
> >
> >  void br_multicast_disable_port(struct net_bridge_port *port)
> >  {
> > +     struct bridge_mcast_own_query *query;
> >       struct net_bridge *br = port->br;
> >       struct net_bridge_port_group *pg;
> >       struct hlist_node *n;
> > @@ -1685,7 +1861,8 @@ void br_multicast_disable_port(struct net_bridge_port *port)
> >       __del_port_router(port);
> >
> >       del_timer(&port->multicast_router_timer);
> > -     del_timer(&port->ip4_own_query.timer);
> > +     list_for_each_entry(query, &port->ip4_own_queries, list)
> > +             del_timer(&query->timer);
> >  #if IS_ENABLED(CONFIG_IPV6)
> >       del_timer(&port->ip6_own_query.timer);
> >  #endif
> > @@ -1717,17 +1894,23 @@ static void __grp_src_mod_timer(struct net_bridge_group_src *src,
> >  static void __grp_src_query_marked_and_rexmit(struct net_bridge_port_group *pg)
> >  {
> >       struct bridge_mcast_other_query *other_query = NULL;
> > +     struct bridge_mcast_own_query *own_query = NULL;
> >       struct net_bridge *br = pg->key.port->br;
> >       u32 lmqc = br->multicast_last_member_count;
> >       unsigned long lmqt, lmi, now = jiffies;
> >       struct net_bridge_group_src *ent;
> > +     u16 vid = pg->key.addr.vid;
> > +
> > +     own_query = br_mcast_find_own_query(&pg->key.port->ip4_own_queries,
> > +                                         vid);
> >
> >       if (!netif_running(br->dev) ||
> >           !br_opt_get(br, BROPT_MULTICAST_ENABLED))
> >               return;
> >
> >       if (pg->key.addr.proto == htons(ETH_P_IP))
> > -             other_query = &br->ip4_other_query;
> > +             other_query = br_mcast_find_other_query(&br->ip4_other_queries,
> > +                                                     vid);
> >  #if IS_ENABLED(CONFIG_IPV6)
> >       else
> >               other_query = &br->ip6_other_query;
> > @@ -1738,7 +1921,7 @@ static void __grp_src_query_marked_and_rexmit(struct net_bridge_port_group *pg)
> >               if (ent->flags & BR_SGRP_F_SEND) {
> >                       ent->flags &= ~BR_SGRP_F_SEND;
> >                       if (ent->timer.expires > lmqt) {
> > -                             if (br_opt_get(br, BROPT_MULTICAST_QUERIER) &&
> > +                             if (own_query && own_query->enabled &&
> >                                   other_query &&
> >                                   !timer_pending(&other_query->timer))
> >                                       ent->src_query_rexmit_cnt = lmqc;
> > @@ -1747,7 +1930,7 @@ static void __grp_src_query_marked_and_rexmit(struct net_bridge_port_group *pg)
> >               }
> >       }
> >
> > -     if (!br_opt_get(br, BROPT_MULTICAST_QUERIER) ||
> > +     if (!own_query || !own_query->enabled ||
> >           !other_query || timer_pending(&other_query->timer))
> >               return;
> >
> > @@ -1763,21 +1946,27 @@ static void __grp_src_query_marked_and_rexmit(struct net_bridge_port_group *pg)
> >  static void __grp_send_query_and_rexmit(struct net_bridge_port_group *pg)
> >  {
> >       struct bridge_mcast_other_query *other_query = NULL;
> > +     struct bridge_mcast_own_query *own_query = NULL;
> >       struct net_bridge *br = pg->key.port->br;
> >       unsigned long now = jiffies, lmi;
> > +     u16 vid = pg->key.addr.vid;
> >
> >       if (!netif_running(br->dev) ||
> >           !br_opt_get(br, BROPT_MULTICAST_ENABLED))
> >               return;
> >
> > +     own_query = br_mcast_find_own_query(&pg->key.port->ip4_own_queries,
> > +                                         vid);
> > +
> >       if (pg->key.addr.proto == htons(ETH_P_IP))
> > -             other_query = &br->ip4_other_query;
> > +             other_query = br_mcast_find_other_query(&br->ip4_other_queries,
> > +                                                     vid);
> >  #if IS_ENABLED(CONFIG_IPV6)
> >       else
> >               other_query = &br->ip6_other_query;
> >  #endif
> >
> > -     if (br_opt_get(br, BROPT_MULTICAST_QUERIER) &&
> > +     if (own_query && own_query->enabled &&
> >           other_query && !timer_pending(&other_query->timer)) {
> >               lmi = now + br->multicast_last_member_interval;
> >               pg->grp_query_rexmit_cnt = br->multicast_last_member_count - 1;
> > @@ -2484,10 +2673,12 @@ static int br_ip6_multicast_mld2_report(struct net_bridge *br,
> >
> >  static bool br_ip4_multicast_select_querier(struct net_bridge *br,
> >                                           struct net_bridge_port *port,
> > -                                         __be32 saddr)
> > +                                         __be32 saddr,
> > +                                         struct bridge_mcast_own_query *own,
> > +                                         struct bridge_mcast_other_query *other)
> >  {
> > -     if (!timer_pending(&br->ip4_own_query.timer) &&
> > -         !timer_pending(&br->ip4_other_query.timer))
> > +     if (own && !timer_pending(&own->timer) &&
> > +         !timer_pending(&other->timer))
> >               goto update;
> >
> >       if (!br->ip4_querier.addr.src.ip4)
> > @@ -2533,11 +2724,14 @@ static bool br_ip6_multicast_select_querier(struct net_bridge *br,
> >
> >  static bool br_multicast_select_querier(struct net_bridge *br,
> >                                       struct net_bridge_port *port,
> > -                                     struct br_ip *saddr)
> > +                                     struct br_ip *saddr,
> > +                                     struct bridge_mcast_own_query *query,
> > +                                     struct bridge_mcast_other_query *other_query)
> >  {
> >       switch (saddr->proto) {
> >       case htons(ETH_P_IP):
> > -             return br_ip4_multicast_select_querier(br, port, saddr->src.ip4);
> > +             return br_ip4_multicast_select_querier(br, port, saddr->src.ip4,
> > +                                                    query, other_query);
> >  #if IS_ENABLED(CONFIG_IPV6)
> >       case htons(ETH_P_IPV6):
> >               return br_ip6_multicast_select_querier(br, port, &saddr->src.ip6);
> > @@ -2628,9 +2822,10 @@ static void br_multicast_query_received(struct net_bridge *br,
> >                                       struct net_bridge_port *port,
> >                                       struct bridge_mcast_other_query *query,
> >                                       struct br_ip *saddr,
> > -                                     unsigned long max_delay)
> > +                                     unsigned long max_delay,
> > +                                     struct bridge_mcast_own_query *own_query)
> >  {
> > -     if (!br_multicast_select_querier(br, port, saddr))
> > +     if (!br_multicast_select_querier(br, port, saddr, own_query, query))
> >               return;
> >
> >       br_multicast_update_query_timer(br, query, max_delay);
> > @@ -2643,6 +2838,8 @@ static void br_ip4_multicast_query(struct net_bridge *br,
> >                                  u16 vid)
> >  {
> >       unsigned int transport_len = ip_transport_len(skb);
> > +     struct bridge_mcast_other_query *other_query;
> > +     struct bridge_mcast_own_query *own_query;
> >       const struct iphdr *iph = ip_hdr(skb);
> >       struct igmphdr *ih = igmp_hdr(skb);
> >       struct net_bridge_mdb_entry *mp;
> > @@ -2684,8 +2881,13 @@ static void br_ip4_multicast_query(struct net_bridge *br,
> >               saddr.proto = htons(ETH_P_IP);
> >               saddr.src.ip4 = iph->saddr;
> >
> > -             br_multicast_query_received(br, port, &br->ip4_other_query,
> > -                                         &saddr, max_delay);
> > +             br_mcast_add_queries(br, vid);
> > +
> > +             own_query = br_mcast_find_own_query(&br->ip4_own_queries, vid);
> > +             other_query = br_mcast_find_other_query(&br->ip4_other_queries,
> > +                                                     vid);
> > +             br_multicast_query_received(br, port, other_query, &saddr,
> > +                                         max_delay, own_query);
> >               goto out;
> >       }
> >
> > @@ -2773,7 +2975,7 @@ static int br_ip6_multicast_query(struct net_bridge *br,
> >               saddr.src.ip6 = ipv6_hdr(skb)->saddr;
> >
> >               br_multicast_query_received(br, port, &br->ip6_other_query,
> > -                                         &saddr, max_delay);
> > +                                         &saddr, max_delay, NULL);
> >               goto out;
> >       } else if (!group) {
> >               goto out;
> > @@ -2850,7 +3052,7 @@ br_multicast_leave_group(struct net_bridge *br,
> >       if (timer_pending(&other_query->timer))
> >               goto out;
> >
> > -     if (br_opt_get(br, BROPT_MULTICAST_QUERIER)) {
> > +     if (own_query && own_query->enabled) {
> >               __br_multicast_send_query(br, port, NULL, NULL, &mp->addr,
> >                                         false, 0, NULL);
> >
> > @@ -2916,21 +3118,26 @@ static void br_ip4_multicast_leave_group(struct net_bridge *br,
> >                                        __u16 vid,
> >                                        const unsigned char *src)
> >  {
> > -     struct br_ip br_group;
> > +     struct bridge_mcast_other_query *other_query;
> >       struct bridge_mcast_own_query *own_query;
> > +     struct br_ip br_group;
> >
> >       if (ipv4_is_local_multicast(group))
> >               return;
> >
> > -     own_query = port ? &port->ip4_own_query : &br->ip4_own_query;
> > +     if (port)
> > +             own_query = br_mcast_find_own_query(&port->ip4_own_queries, vid);
> > +     else
> > +             own_query = br_mcast_find_own_query(&br->ip4_own_queries, vid);
> >
> >       memset(&br_group, 0, sizeof(br_group));
> >       br_group.dst.ip4 = group;
> >       br_group.proto = htons(ETH_P_IP);
> >       br_group.vid = vid;
> >
> > -     br_multicast_leave_group(br, port, &br_group, &br->ip4_other_query,
> > -                              own_query, src);
> > +     other_query = br_mcast_find_other_query(&br->ip4_other_queries, vid);
> > +     br_multicast_leave_group(br, port, &br_group, other_query, own_query,
> > +                              src);
> >  }
> >
> >  #if IS_ENABLED(CONFIG_IPV6)
> > @@ -3195,9 +3402,10 @@ static void br_multicast_query_expired(struct net_bridge *br,
> >
> >  static void br_ip4_multicast_query_expired(struct timer_list *t)
> >  {
> > -     struct net_bridge *br = from_timer(br, t, ip4_own_query.timer);
> > +     struct bridge_mcast_own_query *query = from_timer(query, t, timer);
> > +     struct net_bridge *br = query->br;
> >
> > -     br_multicast_query_expired(br, &br->ip4_own_query, &br->ip4_querier);
> > +     br_multicast_query_expired(br, query, &br->ip4_querier);
> >  }
> >
> >  #if IS_ENABLED(CONFIG_IPV6)
> > @@ -3237,7 +3445,6 @@ void br_multicast_init(struct net_bridge *br)
> >       br->multicast_querier_interval = 255 * HZ;
> >       br->multicast_membership_interval = 260 * HZ;
> >
> > -     br->ip4_other_query.delay_time = 0;
> >       br->ip4_querier.port = NULL;
> >       br->multicast_igmp_version = 2;
> >  #if IS_ENABLED(CONFIG_IPV6)
> > @@ -3251,10 +3458,8 @@ void br_multicast_init(struct net_bridge *br)
> >       spin_lock_init(&br->multicast_lock);
> >       timer_setup(&br->multicast_router_timer,
> >                   br_multicast_local_router_expired, 0);
> > -     timer_setup(&br->ip4_other_query.timer,
> > -                 br_ip4_multicast_querier_expired, 0);
> > -     timer_setup(&br->ip4_own_query.timer,
> > -                 br_ip4_multicast_query_expired, 0);
> > +     INIT_LIST_HEAD(&br->ip4_other_queries);
> > +     INIT_LIST_HEAD(&br->ip4_own_queries);
> >  #if IS_ENABLED(CONFIG_IPV6)
> >       timer_setup(&br->ip6_other_query.timer,
> >                   br_ip6_multicast_querier_expired, 0);
> > @@ -3341,7 +3546,10 @@ static void __br_multicast_open(struct net_bridge *br,
> >
> >  void br_multicast_open(struct net_bridge *br)
> >  {
> > -     __br_multicast_open(br, &br->ip4_own_query);
> > +     struct bridge_mcast_own_query *query;
> > +
> > +     list_for_each_entry(query, &br->ip4_own_queries, list)
> > +             __br_multicast_open(br, query);
> >  #if IS_ENABLED(CONFIG_IPV6)
> >       __br_multicast_open(br, &br->ip6_own_query);
> >  #endif
> > @@ -3349,9 +3557,14 @@ void br_multicast_open(struct net_bridge *br)
> >
> >  void br_multicast_stop(struct net_bridge *br)
> >  {
> > +     struct bridge_mcast_other_query *other_query;
> > +     struct bridge_mcast_own_query *query;
> > +
> >       del_timer_sync(&br->multicast_router_timer);
> > -     del_timer_sync(&br->ip4_other_query.timer);
> > -     del_timer_sync(&br->ip4_own_query.timer);
> > +     list_for_each_entry(other_query, &br->ip4_other_queries, list)
> > +             del_timer_sync(&other_query->timer);
> > +     list_for_each_entry(query, &br->ip4_own_queries, list)
> > +             del_timer_sync(&query->timer);
> >  #if IS_ENABLED(CONFIG_IPV6)
> >       del_timer_sync(&br->ip6_other_query.timer);
> >       del_timer_sync(&br->ip6_own_query.timer);
> > @@ -3461,11 +3674,20 @@ int br_multicast_set_port_router(struct net_bridge_port *p, unsigned long val)
> >  }
> >
> >  static void br_multicast_start_querier(struct net_bridge *br,
> > -                                    struct bridge_mcast_own_query *query)
> > +                                    struct bridge_mcast_own_query *query,
> > +                                    u16 vid)
> >  {
> > +     struct bridge_mcast_own_query *port_query;
> > +     struct net_bridge_vlan_group *vg;
> >       struct net_bridge_port *port;
> >
> > -     __br_multicast_open(br, query);
> > +     if (br_vlan_enabled(br->dev)) {
> > +             vg = br_vlan_group(br);
> > +             if (vg && br_vlan_find(vg, vid))
> > +                     __br_multicast_open(br, query);
> > +     } else {
> > +             __br_multicast_open(br, query);
> > +     }
> >
> >       rcu_read_lock();
> >       list_for_each_entry_rcu(port, &br->port_list, list) {
> > @@ -3473,11 +3695,66 @@ static void br_multicast_start_querier(struct net_bridge *br,
> >                   port->state == BR_STATE_BLOCKING)
> >                       continue;
> >
> > -             if (query == &br->ip4_own_query)
> > -                     br_multicast_enable(&port->ip4_own_query);
> > +             if (br_vlan_enabled(br->dev)) {
> > +                     vg = nbp_vlan_group(port);
> > +                     if (!vg || (vg && !br_vlan_find(vg, vid)))
> > +                             continue;
> > +             }
> > +
> > +             port_query = br_mcast_find_own_query(&port->ip4_own_queries,
> > +                                                  vid);
> > +             if (!port_query)
> > +                     continue;
> > +
> > +             port_query->enabled = true;
> > +
> > +             if (query->ip4) {
> > +                     port_query->ip4 = true;
> > +                     br_multicast_enable(port_query);
> > +             }
> >  #if IS_ENABLED(CONFIG_IPV6)
> > -             else
> > +             else {
> >                       br_multicast_enable(&port->ip6_own_query);
> > +             }
> > +#endif
> > +     }
> > +     rcu_read_unlock();
> > +}
> > +
> > +static void br_multicast_stop_querier(struct net_bridge *br,
> > +                                   struct bridge_mcast_own_query *query,
> > +                                   u16 vid)
> > +{
> > +     struct bridge_mcast_own_query *port_query;
> > +     struct net_bridge_vlan_group *vg;
> > +     struct net_bridge_port *port;
> > +
> > +     query->enabled = false;
> > +
> > +     rcu_read_lock();
> > +     list_for_each_entry_rcu(port, &br->port_list, list) {
> > +             if (port->state == BR_STATE_DISABLED ||
> > +                 port->state == BR_STATE_BLOCKING)
> > +                     continue;
> > +
> > +             if (br_vlan_enabled(br->dev)) {
> > +                     vg = nbp_vlan_group(port);
> > +                     if (!vg || (vg && !br_vlan_find(vg, vid)))
> > +                             continue;
> > +             }
> > +
> > +             port_query = br_mcast_find_own_query(&port->ip4_own_queries,
> > +                                                  vid);
> > +             if (!port_query)
> > +                     continue;
> > +
> > +             port_query->enabled = false;
> > +
> > +             if (query->ip4)
> > +                     br_multicast_disable(port_query);
> > +#if IS_ENABLED(CONFIG_IPV6)
> > +             else
> > +                     br_multicast_disable(&port->ip6_own_query);
> >  #endif
> >       }
> >       rcu_read_unlock();
> > @@ -3553,32 +3830,55 @@ bool br_multicast_router(const struct net_device *dev)
> >  }
> >  EXPORT_SYMBOL_GPL(br_multicast_router);
> >
> > -int br_multicast_set_querier(struct net_bridge *br, unsigned long val)
> > +int br_multicast_set_querier(struct net_bridge *br, unsigned long val, u16 vid)
> >  {
> > +     struct bridge_mcast_other_query *other_query;
> > +     struct bridge_mcast_own_query *query;
> > +     struct net_bridge_vlan_group *vg;
> >       unsigned long max_delay;
> >
> >       val = !!val;
> >
> > +     if (vid == 0) {
> > +             vg = br_vlan_group(br);
> > +             if (vg)
> > +                     vid = vg->pvid;
> > +     }
> > +
> >       spin_lock_bh(&br->multicast_lock);
> > -     if (br_opt_get(br, BROPT_MULTICAST_QUERIER) == val)
> > +     query = br_mcast_find_own_query(&br->ip4_own_queries, vid);
> > +     if (!query) {
> > +             if (br_vlan_enabled(br->dev))
> > +                     goto unlock;
> > +
> > +             br_mcast_add_queries(br, vid);
> > +     }
> > +
> > +     other_query = br_mcast_find_other_query(&br->ip4_other_queries, vid);
> > +     if (!other_query)
> >               goto unlock;
> >
> > -     br_opt_toggle(br, BROPT_MULTICAST_QUERIER, !!val);
> > -     if (!val)
> > +     if (!val && query) {
> > +             br_multicast_stop_querier(br, query, vid);
> >               goto unlock;
> > +     }
> >
> > -     max_delay = br->multicast_query_response_interval;
> > +     if (val & query->enabled)
> > +             goto unlock;
> >
> > -     if (!timer_pending(&br->ip4_other_query.timer))
> > -             br->ip4_other_query.delay_time = jiffies + max_delay;
> > +     query->enabled = true;
> >
> > -     br_multicast_start_querier(br, &br->ip4_own_query);
> > +     max_delay = br->multicast_query_response_interval;
> > +     if (!timer_pending(&other_query->timer))
> > +             other_query->delay_time = jiffies + max_delay;
> > +
> > +     br_multicast_start_querier(br, query, vid);
> >
> >  #if IS_ENABLED(CONFIG_IPV6)
> >       if (!timer_pending(&br->ip6_other_query.timer))
> >               br->ip6_other_query.delay_time = jiffies + max_delay;
> >
> > -     br_multicast_start_querier(br, &br->ip6_own_query);
> > +     br_multicast_start_querier(br, &br->ip6_own_query, vid);
> >  #endif
> >
> >  unlock:
> > @@ -3587,6 +3887,79 @@ int br_multicast_set_querier(struct net_bridge *br, unsigned long val)
> >       return 0;
> >  }
> >
> > +void br_multicast_vlan_add(struct net_bridge_vlan *v)
> > +{
> > +     struct bridge_mcast_own_query *query, *port_query;
> > +     struct net_bridge_port *p;
> > +     struct net_bridge *br;
> > +
> > +     if (br_vlan_is_master(v)) {
> > +             br_mcast_add_queries(v->br, v->vid);
> > +             return;
> > +     }
> > +
> > +     p = v->port;
> > +     br = p->br;
> > +
> > +     query = br_mcast_find_own_query(&br->ip4_own_queries, v->vid);
> > +
> > +     port_query = br_mcast_add_own_query(&p->ip4_own_queries,
> > +                                         v->vid,
> > +                                         br_ip4_multicast_port_query_expired);
> > +     if (!port_query)
> > +             return;
> > +
> > +     port_query->port = p;
> > +     port_query->ip4 = true;
> > +
> > +     if (query->enabled) {
> > +             port_query->enabled = true;
> > +             br_multicast_enable(port_query);
> > +     }
> > +}
> > +
> > +void br_multicast_vlan_del(struct net_bridge_vlan *v)
> > +{
> > +     struct bridge_mcast_other_query *other_query, *other_tmp;
> > +     struct bridge_mcast_own_query *query, *tmp;
> > +     struct net_bridge_port *p;
> > +     struct net_bridge *br;
> > +
> > +     if (br_vlan_is_master(v)) {
> > +             br = v->br;
> > +
> > +             list_for_each_entry_safe(other_query, other_tmp,
> > +                                      &br->ip4_other_queries, list)
> > +                     if (other_query->vid == v->vid)
> > +                             br_mcast_del_other_query(other_query);
> > +
> > +             list_for_each_entry_safe(query, tmp, &br->ip4_own_queries, list)
> > +                     if (query->vid == v->vid)
> > +                             br_mcast_del_own_query(query);
> > +
> > +             return;
> > +     }
> > +
> > +     p = v->port;
> > +
> > +     list_for_each_entry_safe(query, tmp, &p->ip4_own_queries, list) {
> > +             if (query->vid == v->vid)
> > +                     br_mcast_del_own_query(query);
> > +     }
> > +}
> > +
> > +void br_multicast_vlan_toggle(struct net_bridge *br, bool on)
> > +{
> > +     struct bridge_mcast_own_query *query;
> > +
> > +     list_for_each_entry(query, &br->ip4_own_queries, list) {
> > +             if (!on)
> > +                     br_multicast_stop_querier(br, query, query->vid);
> > +             else
> > +                     br_multicast_start_querier(br, query, query->vid);
> > +     }
> > +}
> > +
> >  int br_multicast_set_igmp_version(struct net_bridge *br, unsigned long val)
> >  {
> >       /* Currently we support only version 2 and 3 */
> > @@ -3711,7 +4084,7 @@ bool br_multicast_has_querier_anywhere(struct net_device *dev, int proto)
> >       memset(&eth, 0, sizeof(eth));
> >       eth.h_proto = htons(proto);
> >
> > -     ret = br_multicast_querier_exists(br, &eth, NULL);
> > +     ret = br_multicast_any_querier_exists(br, &eth);
> >
> >  unlock:
> >       rcu_read_unlock();
> > @@ -3746,7 +4119,7 @@ bool br_multicast_has_querier_adjacent(struct net_device *dev, int proto)
> >
> >       switch (proto) {
> >       case ETH_P_IP:
> > -             if (!timer_pending(&br->ip4_other_query.timer) ||
> > +             if (!br_multicast_any_querier_adjacent(br) ||
> >                   rcu_dereference(br->ip4_querier.port) == port)
> >                       goto unlock;
> >               break;
> > diff --git a/net/bridge/br_netlink.c b/net/bridge/br_netlink.c
> > index 49700ce0e919..d32f4c185364 100644
> > --- a/net/bridge/br_netlink.c
> > +++ b/net/bridge/br_netlink.c
> > @@ -1186,6 +1186,7 @@ static const struct nla_policy br_policy[IFLA_BR_MAX + 1] = {
> >       [IFLA_BR_VLAN_STATS_PER_PORT] = { .type = NLA_U8 },
> >       [IFLA_BR_MULTI_BOOLOPT] =
> >               NLA_POLICY_EXACT_LEN(sizeof(struct br_boolopt_multi)),
> > +     [IFLA_BR_MCAST_QUERIER_VID] = { .type = NLA_U16 },
> >  };
> >
> >  static int br_changelink(struct net_device *brdev, struct nlattr *tb[],
> > @@ -1193,6 +1194,7 @@ static int br_changelink(struct net_device *brdev, struct nlattr *tb[],
> >                        struct netlink_ext_ack *extack)
> >  {
> >       struct net_bridge *br = netdev_priv(brdev);
> > +     u16 vid = 0;
> >       int err;
> >
> >       if (!data)
> > @@ -1204,6 +1206,9 @@ static int br_changelink(struct net_device *brdev, struct nlattr *tb[],
> >                       return err;
> >       }
> >
> > +     if (data[IFLA_BR_MCAST_QUERIER_VID])
> > +             vid = nla_get_u16(data[IFLA_BR_MCAST_QUERIER_VID]);
> > +
> >       if (data[IFLA_BR_HELLO_TIME]) {
> >               err = br_set_hello_time(br, nla_get_u32(data[IFLA_BR_HELLO_TIME]));
> >               if (err)
> > @@ -1333,7 +1338,7 @@ static int br_changelink(struct net_device *brdev, struct nlattr *tb[],
> >       if (data[IFLA_BR_MCAST_QUERIER]) {
> >               u8 mcast_querier = nla_get_u8(data[IFLA_BR_MCAST_QUERIER]);
> >
> > -             err = br_multicast_set_querier(br, mcast_querier);
> > +             err = br_multicast_set_querier(br, mcast_querier, vid);
> >               if (err)
> >                       return err;
> >       }
> > @@ -1596,7 +1601,7 @@ static int br_fill_info(struct sk_buff *skb, const struct net_device *brdev)
> >           nla_put_u8(skb, IFLA_BR_MCAST_QUERY_USE_IFADDR,
> >                      br_opt_get(br, BROPT_MULTICAST_QUERY_USE_IFADDR)) ||
> >           nla_put_u8(skb, IFLA_BR_MCAST_QUERIER,
> > -                    br_opt_get(br, BROPT_MULTICAST_QUERIER)) ||
> > +                    br_mcast_exist_own_query(br)) ||
> >           nla_put_u8(skb, IFLA_BR_MCAST_STATS_ENABLED,
> >                      br_opt_get(br, BROPT_MULTICAST_STATS_ENABLED)) ||
> >           nla_put_u32(skb, IFLA_BR_MCAST_HASH_ELASTICITY, RHT_ELASTICITY) ||
> > diff --git a/net/bridge/br_private.h b/net/bridge/br_private.h
> > index d62c6e1af64a..84f597f542b1 100644
> > --- a/net/bridge/br_private.h
> > +++ b/net/bridge/br_private.h
> > @@ -66,14 +66,24 @@ struct mac_addr {
> >  #ifdef CONFIG_BRIDGE_IGMP_SNOOPING
> >  /* our own querier */
> >  struct bridge_mcast_own_query {
> > +     struct list_head        list;
> >       struct timer_list       timer;
> >       u32                     startup_sent;
> > +     struct net_bridge_port  *port;
> > +     struct net_bridge       *br;
> > +     bool                    ip4;
> > +     u16                     vid;
> > +     bool                    enabled;
> >  };
> >
> >  /* other querier */
> >  struct bridge_mcast_other_query {
> > +     struct list_head                list;
> >       struct timer_list               timer;
> >       unsigned long                   delay_time;
> > +     struct net_bridge               *br;
> > +     bool                            ip4;
> > +     u16                             vid;
> >  };
> >
> >  /* selected querier */
> > @@ -304,7 +314,7 @@ struct net_bridge_port {
> >       struct rcu_head                 rcu;
> >
> >  #ifdef CONFIG_BRIDGE_IGMP_SNOOPING
> > -     struct bridge_mcast_own_query   ip4_own_query;
> > +     struct list_head                ip4_own_queries;
> >  #if IS_ENABLED(CONFIG_IPV6)
> >       struct bridge_mcast_own_query   ip6_own_query;
> >  #endif /* IS_ENABLED(CONFIG_IPV6) */
> > @@ -448,8 +458,8 @@ struct net_bridge {
> >       struct hlist_head               router_list;
> >
> >       struct timer_list               multicast_router_timer;
> > -     struct bridge_mcast_other_query ip4_other_query;
> > -     struct bridge_mcast_own_query   ip4_own_query;
> > +     struct list_head                ip4_other_queries;
> > +     struct list_head                ip4_own_queries;
> >       struct bridge_mcast_querier     ip4_querier;
> >       struct bridge_mcast_stats       __percpu *mcast_stats;
> >  #if IS_ENABLED(CONFIG_IPV6)
> > @@ -788,6 +798,9 @@ int br_ioctl_deviceless_stub(struct net *net, unsigned int cmd,
> >
> >  /* br_multicast.c */
> >  #ifdef CONFIG_BRIDGE_IGMP_SNOOPING
> > +void br_multicast_vlan_add(struct net_bridge_vlan *v);
> > +void br_multicast_vlan_del(struct net_bridge_vlan *v);
> > +void br_multicast_vlan_toggle(struct net_bridge *br, bool on);
> >  int br_multicast_rcv(struct net_bridge *br, struct net_bridge_port *port,
> >                    struct sk_buff *skb, u16 vid);
> >  struct net_bridge_mdb_entry *br_mdb_get(struct net_bridge *br,
> > @@ -807,7 +820,7 @@ void br_multicast_flood(struct net_bridge_mdb_entry *mdst,
> >  int br_multicast_set_router(struct net_bridge *br, unsigned long val);
> >  int br_multicast_set_port_router(struct net_bridge_port *p, unsigned long val);
> >  int br_multicast_toggle(struct net_bridge *br, unsigned long val);
> > -int br_multicast_set_querier(struct net_bridge *br, unsigned long val);
> > +int br_multicast_set_querier(struct net_bridge *br, unsigned long val, u16 vid);
> >  int br_multicast_set_hash_max(struct net_bridge *br, unsigned long val);
> >  int br_multicast_set_igmp_version(struct net_bridge *br, unsigned long val);
> >  #if IS_ENABLED(CONFIG_IPV6)
> > @@ -846,6 +859,11 @@ 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 bridge_mcast_other_query *
> > +br_mcast_find_other_query(struct list_head *list, u16 vid);
> > +struct bridge_mcast_own_query *
> > +br_mcast_find_own_query(struct list_head *list, u16 vid);
> > +bool br_mcast_exist_own_query(struct net_bridge *br);
> >
> >  static inline bool br_group_is_l2(const struct br_ip *group)
> >  {
> > @@ -865,11 +883,15 @@ static inline bool br_multicast_is_router(struct net_bridge *br)
> >  static inline bool
> >  __br_multicast_querier_exists(struct net_bridge *br,
> >                               struct bridge_mcast_other_query *querier,
> > -                             const bool is_ipv6)
> > +                             const bool is_ipv6,
> > +                             u16 vid)
> >  {
> > +     struct bridge_mcast_own_query *query;
> >       bool own_querier_enabled;
> >
> > -     if (br_opt_get(br, BROPT_MULTICAST_QUERIER)) {
> > +     query = br_mcast_find_own_query(&br->ip4_own_queries, vid);
> > +
> > +     if (query && query->enabled) {
> >               if (is_ipv6 && !br_opt_get(br, BROPT_HAS_IPV6_ADDR))
> >                       own_querier_enabled = false;
> >               else
> > @@ -878,28 +900,62 @@ __br_multicast_querier_exists(struct net_bridge *br,
> >               own_querier_enabled = false;
> >       }
> >
> > +     if (!querier)
> > +             return own_querier_enabled;
> > +
> >       return time_is_before_jiffies(querier->delay_time) &&
> >              (own_querier_enabled || timer_pending(&querier->timer));
> >  }
> >
> >  static inline bool br_multicast_querier_exists(struct net_bridge *br,
> >                                              struct ethhdr *eth,
> > -                                            const struct net_bridge_mdb_entry *mdb)
> > +                                            const struct net_bridge_mdb_entry *mdb,
> > +                                            u16 vid)
> >  {
> > +     struct bridge_mcast_other_query *query =
> > +             br_mcast_find_other_query(&br->ip4_other_queries, vid);
> > +
> >       switch (eth->h_proto) {
> >       case (htons(ETH_P_IP)):
> > -             return __br_multicast_querier_exists(br,
> > -                     &br->ip4_other_query, false);
> > +             return __br_multicast_querier_exists(br, query, false, vid);
> >  #if IS_ENABLED(CONFIG_IPV6)
> >       case (htons(ETH_P_IPV6)):
> >               return __br_multicast_querier_exists(br,
> > -                     &br->ip6_other_query, true);
> > +                     &br->ip6_other_query, true, vid);
> >  #endif
> >       default:
> >               return !!mdb && br_group_is_l2(&mdb->addr);
> >       }
> >  }
> >
> > +static inline bool br_multicast_any_querier_exists(struct net_bridge *br,
> > +                                                struct ethhdr *eth)
> > +{
> > +     struct bridge_mcast_other_query *query;
> > +
> > +     list_for_each_entry(query, &br->ip4_other_queries, list) {
> > +             if (!timer_pending(&query->timer))
> > +                     continue;
> > +
> > +             if (br_multicast_querier_exists(br, eth, NULL, query->vid))
> > +                     return true;
> > +     }
> > +
> > +     return false;
> > +}
> > +
> > +static inline bool br_multicast_any_querier_adjacent(struct net_bridge *br)
> > +{
> > +     struct bridge_mcast_other_query *query;
> > +
> > +     list_for_each_entry(query, &br->ip4_other_queries, list) {
> > +             if (timer_pending(&query->timer))
> > +                     return true;
> > +     }
> > +
> > +     return false;
> > +}
> > +
> >  static inline bool br_multicast_is_star_g(const struct br_ip *ip)
> >  {
> >       switch (ip->proto) {
> > @@ -1015,7 +1071,19 @@ static inline bool br_multicast_is_router(struct net_bridge *br)
> >
> >  static inline bool br_multicast_querier_exists(struct net_bridge *br,
> >                                              struct ethhdr *eth,
> > -                                            const struct net_bridge_mdb_entry *mdb)
> > +                                            const struct net_bridge_mdb_entry *mdb,
> > +                                            u16 vid)
> > +{
> > +     return false;
> > +}
> > +
> > +static inline bool br_multicast_any_querier_exists(struct net_bridge *br,
> > +                                                struct ethhdr *eth)
> > +{
> > +     return false;
> > +}
> > +
> > +static inline bool br_multicast_any_querier_adjacent(struct net_bridge *br)
> >  {
> >       return false;
> >  }
> > diff --git a/net/bridge/br_sysfs_br.c b/net/bridge/br_sysfs_br.c
> > index 7db06e3f642a..23bf6a065d78 100644
> > --- a/net/bridge/br_sysfs_br.c
> > +++ b/net/bridge/br_sysfs_br.c
> > @@ -51,6 +51,33 @@ static ssize_t store_bridge_parm(struct device *d,
> >       return err ? err : len;
> >  }
> >
> > +static ssize_t store_bridge_parm2(struct device *d,
> > +                               const char *buf, size_t len,
> > +                               int (*set)(struct net_bridge *, unsigned long, u16))
> > +{
> > +     struct net_bridge *br = to_bridge(d);
> > +     char *endp;
> > +     unsigned long val;
> > +     int err;
> > +
> > +     if (!ns_capable(dev_net(br->dev)->user_ns, CAP_NET_ADMIN))
> > +             return -EPERM;
> > +
> > +     val = simple_strtoul(buf, &endp, 0);
> > +     if (endp == buf)
> > +             return -EINVAL;
> > +
> > +     if (!rtnl_trylock())
> > +             return restart_syscall();
> > +
> > +     err = (*set)(br, val, 0);
> > +     if (!err)
> > +             netdev_state_change(br->dev);
> > +     rtnl_unlock();
> > +
> > +     return err ? err : len;
> > +}
> > +
> >
> >  static ssize_t forward_delay_show(struct device *d,
> >                                 struct device_attribute *attr, char *buf)
> > @@ -404,14 +431,14 @@ static ssize_t multicast_querier_show(struct device *d,
> >                                     char *buf)
> >  {
> >       struct net_bridge *br = to_bridge(d);
> > -     return sprintf(buf, "%d\n", br_opt_get(br, BROPT_MULTICAST_QUERIER));
> > +     return sprintf(buf, "%d\n", br_mcast_exist_own_query(br));
> >  }
> >
> >  static ssize_t multicast_querier_store(struct device *d,
> >                                      struct device_attribute *attr,
> >                                      const char *buf, size_t len)
> >  {
> > -     return store_bridge_parm(d, buf, len, br_multicast_set_querier);
> > +     return store_bridge_parm2(d, buf, len, br_multicast_set_querier);
> >  }
> >  static DEVICE_ATTR_RW(multicast_querier);
> >
> > diff --git a/net/bridge/br_vlan.c b/net/bridge/br_vlan.c
> > index 701cad646b20..2e0b544a3560 100644
> > --- a/net/bridge/br_vlan.c
> > +++ b/net/bridge/br_vlan.c
> > @@ -308,6 +308,7 @@ static int __vlan_add(struct net_bridge_vlan *v, u16 flags,
> >
> >       __vlan_add_list(v);
> >       __vlan_add_flags(v, flags);
> > +     br_multicast_vlan_add(v);
> >
> >       if (p)
> >               nbp_vlan_set_vlan_dev_state(p, v->vid);
> > @@ -353,6 +354,7 @@ static int __vlan_del(struct net_bridge_vlan *v)
> >               masterv = v->brvlan;
> >       }
> >
> > +     br_multicast_vlan_del(v);
> >       __vlan_delete_pvid(vg, v->vid);
> >       if (p) {
> >               err = __vlan_vid_del(p->dev, p->br, v);
> > @@ -827,6 +829,7 @@ int __br_vlan_filter_toggle(struct net_bridge *br, unsigned long val)
> >       br_manage_promisc(br);
> >       recalculate_group_addr(br);
> >       br_recalculate_fwd_mask(br);
> > +     br_multicast_vlan_toggle(br, !!val);
> >
> >       return 0;
> >  }
> >
> 

-- 
/Horatiu

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [Bridge] [RFC PATCH v2] net: bridge: igmp: Extend IGMP query to be per vlan
@ 2021-01-13 17:02     ` Horatiu Vultur
  0 siblings, 0 replies; 16+ messages in thread
From: Horatiu Vultur @ 2021-01-13 17:02 UTC (permalink / raw)
  To: Nikolay Aleksandrov
  Cc: netdev, bridge, linux-kernel, allan.nielsen, roopa, kuba, davem

The 01/13/2021 14:15, Nikolay Aleksandrov wrote:
> EXTERNAL EMAIL: Do not click links or open attachments unless you know the content is safe
> 
> On 12/01/2021 15:59, Horatiu Vultur wrote:
> > Based on the comments of the previous version, we started to work on a
> > new version, so it would be possible to enable/disable queries per vlan.
> > This is still work in progress and there are plenty of things that are
> > not implemented and tested:
> > - ipv6 support
> > - the fast path needs to be improved
> > - currently it is possible only to enable/disable the queries per vlan,
> >   all the other configurations are global
> > - toggling vlan_filtering is not tested
> > - remove duplicated information
> > - etc...
> >
> > But there are few things that are working like:
> > - sending queries per vlan
> > - stop sending queries if there is a better querier per vlan
> > - when ports are added/removed from vlan
> > - etc...
> >
> > We were wondering if this what you had in mind when you proposed to have
> > this per vlan? Or we are completely off? Or we should fix some of the
> > issues that I mentioned, before you can see more clearly the direction?
> >
> > Signed-off-by: Horatiu Vultur <horatiu.vultur@microchip.com>
> > ---
> >  include/uapi/linux/if_link.h |   1 +
> >  net/bridge/br_device.c       |   2 +-
> >  net/bridge/br_input.c        |   2 +-
> >  net/bridge/br_multicast.c    | 505 ++++++++++++++++++++++++++++++-----
> >  net/bridge/br_netlink.c      |   9 +-
> >  net/bridge/br_private.h      |  90 ++++++-
> >  net/bridge/br_sysfs_br.c     |  31 ++-
> >  net/bridge/br_vlan.c         |   3 +
> >  8 files changed, 560 insertions(+), 83 deletions(-)
> >
> 

Hi Nik,

> Hi Horatiu,
> No, unfortunately not even close.

At least is good that I didn't continue in this direction :)

> We already have per-port per-vlan and global per-vlan
> contexts which are also linked together for each vlan, those must be used for any vlan
> configuration and state. The problem is that you'd have to mix igmp and vlan code and
> those two live under two different kconfig options, and worse rely on different locks, so
> extra care must be taken.

Point taken.

> Any vlan lookups must use the vlan hashes, (almost) _no_ linear
> walks or new lists are needed (the exception is obviously port going down where a walk
> over port's vlans is needed).

Yes, I was thinking about this vlan lookups that need to be change to
use the hashes but I thought I can do it later.

> In almost all contexts below a vlan lookup has already been
> done by the input functions, the result of that lookup must be saved and re-used. The
> vlan options API needs to be used for configuring vlans (per-vlan mcast options), unfortunately
> I still haven't upstreamed the iproute2 part, so you might have to do that as well.
> Obviously with all of the above the current default situation must not change unless the
> user configures it so. If you don't need this asap, I'll probably get to it in two months
> after EHT and the new bridge flush api, even we are still carrying an out-of-tree patch
> for this which someone (not from cumulus) tried to upstream a few years back, but it also has
> wrong design in general. :)

Definetly this is not asap. But I would like to try to have another look
at this to see if I get closer to what you are looking for :)

> 
> Thanks,
>  Nik
> 
> > diff --git a/include/uapi/linux/if_link.h b/include/uapi/linux/if_link.h
> > index 82708c6db432..11ec1d45c24e 100644
> > --- a/include/uapi/linux/if_link.h
> > +++ b/include/uapi/linux/if_link.h
> > @@ -472,6 +472,7 @@ enum {
> >       IFLA_BR_MCAST_MLD_VERSION,
> >       IFLA_BR_VLAN_STATS_PER_PORT,
> >       IFLA_BR_MULTI_BOOLOPT,
> > +     IFLA_BR_MCAST_QUERIER_VID,
> >       __IFLA_BR_MAX,
> >  };
> >
> > diff --git a/net/bridge/br_device.c b/net/bridge/br_device.c
> > index 3f2f06b4dd27..aca4e8074a8f 100644
> > --- a/net/bridge/br_device.c
> > +++ b/net/bridge/br_device.c
> > @@ -89,7 +89,7 @@ netdev_tx_t br_dev_xmit(struct sk_buff *skb, struct net_device *dev)
> >
> >               mdst = br_mdb_get(br, skb, vid);
> >               if ((mdst || BR_INPUT_SKB_CB_MROUTERS_ONLY(skb)) &&
> > -                 br_multicast_querier_exists(br, eth_hdr(skb), mdst))
> > +                 br_multicast_querier_exists(br, eth_hdr(skb), mdst, vid))
> >                       br_multicast_flood(mdst, skb, false, true);
> >               else
> >                       br_flood(br, skb, BR_PKT_MULTICAST, false, true);
> > diff --git a/net/bridge/br_input.c b/net/bridge/br_input.c
> > index 222285d9dae2..03e445af6c1f 100644
> > --- a/net/bridge/br_input.c
> > +++ b/net/bridge/br_input.c
> > @@ -130,7 +130,7 @@ int br_handle_frame_finish(struct net *net, struct sock *sk, struct sk_buff *skb
> >       case BR_PKT_MULTICAST:
> >               mdst = br_mdb_get(br, skb, vid);
> >               if ((mdst || BR_INPUT_SKB_CB_MROUTERS_ONLY(skb)) &&
> > -                 br_multicast_querier_exists(br, eth_hdr(skb), mdst)) {
> > +                 br_multicast_querier_exists(br, eth_hdr(skb), mdst, vid)) {
> >                       if ((mdst && mdst->host_joined) ||
> >                           br_multicast_is_router(br)) {
> >                               local_rcv = true;
> > diff --git a/net/bridge/br_multicast.c b/net/bridge/br_multicast.c
> > index 257ac4e25f6d..b4fac25101e4 100644
> > --- a/net/bridge/br_multicast.c
> > +++ b/net/bridge/br_multicast.c
> > @@ -48,8 +48,11 @@ static const struct rhashtable_params br_sg_port_rht_params = {
> >       .automatic_shrinking = true,
> >  };
> >
> > +static void br_ip4_multicast_query_expired(struct timer_list *t);
> > +static void br_ip4_multicast_querier_expired(struct timer_list *t);
> >  static void br_multicast_start_querier(struct net_bridge *br,
> > -                                    struct bridge_mcast_own_query *query);
> > +                                    struct bridge_mcast_own_query *query,
> > +                                    u16 vid);
> >  static void br_multicast_add_router(struct net_bridge *br,
> >                                   struct net_bridge_port *port);
> >  static void br_ip4_multicast_leave_group(struct net_bridge *br,
> > @@ -87,6 +90,112 @@ br_sg_port_find(struct net_bridge *br,
> >                                     br_sg_port_rht_params);
> >  }
> >
> > +static void br_mcast_del_other_query(struct bridge_mcast_other_query *query)
> > +{
> > +     del_timer_sync(&query->timer);
> > +     list_del(&query->list);
> > +     kfree(query);
> > +}
> > +
> > +static struct bridge_mcast_other_query *
> > +br_mcast_add_other_query(struct list_head *list, u16 vid,
> > +                      void (*callback)(struct timer_list *t))
> > +{
> > +     struct bridge_mcast_other_query *query;
> > +
> > +     query = kzalloc(sizeof(*query), GFP_KERNEL);
> > +     if (!query)
> > +             return NULL;
> > +
> > +     query->vid = vid;
> > +     timer_setup(&query->timer, callback, 0);
> > +
> > +     list_add(&query->list, list);
> > +
> > +     return query;
> > +}
> > +
> > +static void br_mcast_del_own_query(struct bridge_mcast_own_query *query)
> > +{
> > +     del_timer_sync(&query->timer);
> > +     list_del(&query->list);
> > +     kfree(query);
> > +}
> > +
> > +static struct bridge_mcast_own_query *
> > +br_mcast_add_own_query(struct list_head *list, u16 vid,
> > +                    void (*callback)(struct timer_list *t))
> > +{
> > +     struct bridge_mcast_own_query *query;
> > +
> > +     query = kzalloc(sizeof(*query), GFP_KERNEL);
> > +     if (!query)
> > +             return NULL;
> > +
> > +     query->vid = vid;
> > +     timer_setup(&query->timer, callback, 0);
> > +
> > +     list_add(&query->list, list);
> > +
> > +     return query;
> > +}
> > +
> > +static void br_mcast_add_queries(struct net_bridge *br, u16 vid)
> > +{
> > +     struct bridge_mcast_other_query *other;
> > +     struct bridge_mcast_own_query *own;
> > +
> > +     own = br_mcast_find_own_query(&br->ip4_own_queries, vid);
> > +     if (!own) {
> > +             own = br_mcast_add_own_query(&br->ip4_own_queries, vid,
> > +                                          br_ip4_multicast_query_expired);
> > +             own->ip4 = true;
> > +             own->br = br;
> > +     }
> > +
> > +     other = br_mcast_find_other_query(&br->ip4_other_queries, vid);
> > +     if (!other) {
> > +             other = br_mcast_add_other_query(&br->ip4_other_queries, vid,
> > +                                              br_ip4_multicast_querier_expired);
> > +             other->br = br;
> > +     }
> > +}
> > +
> > +struct bridge_mcast_own_query *
> > +br_mcast_find_own_query(struct list_head *list, u16 vid)
> > +{
> > +     struct bridge_mcast_own_query *query = NULL;
> > +
> > +     list_for_each_entry(query, list, list)
> > +             if (query->vid == vid)
> > +                     return query;
> > +
> > +     return NULL;
> > +}
> > +
> > +struct bridge_mcast_other_query *
> > +br_mcast_find_other_query(struct list_head *list, u16 vid)
> > +{
> > +     struct bridge_mcast_other_query *query = NULL;
> > +
> > +     list_for_each_entry(query, list, list)
> > +             if (query->vid == vid)
> > +                     return query;
> > +
> > +     return NULL;
> > +}
> > +
> > +bool br_mcast_exist_own_query(struct net_bridge *br)
> > +{
> > +     struct bridge_mcast_own_query *query = NULL;
> > +
> > +     list_for_each_entry(query, &br->ip4_own_queries, list)
> > +             if (query->enabled)
> > +                     return true;
> > +
> > +     return false;
> > +}
> > +
> >  static struct net_bridge_mdb_entry *br_mdb_ip_get_rcu(struct net_bridge *br,
> >                                                     struct br_ip *dst)
> >  {
> > @@ -688,7 +797,8 @@ static struct sk_buff *br_ip4_multicast_alloc_query(struct net_bridge *br,
> >                                                   __be32 ip_dst, __be32 group,
> >                                                   bool with_srcs, bool over_lmqt,
> >                                                   u8 sflag, u8 *igmp_type,
> > -                                                 bool *need_rexmit)
> > +                                                 bool *need_rexmit,
> > +                                                 u16 vid)
> >  {
> >       struct net_bridge_port *p = pg ? pg->key.port : NULL;
> >       struct net_bridge_group_src *ent;
> > @@ -724,6 +834,9 @@ static struct sk_buff *br_ip4_multicast_alloc_query(struct net_bridge *br,
> >       }
> >
> >       pkt_size = sizeof(*eth) + sizeof(*iph) + 4 + igmp_hdr_size;
> > +     if (br_vlan_enabled(br->dev) && vid != 0)
> > +             pkt_size += 4;
> > +
> >       if ((p && pkt_size > p->dev->mtu) ||
> >           pkt_size > br->dev->mtu)
> >               return NULL;
> > @@ -732,6 +845,9 @@ static struct sk_buff *br_ip4_multicast_alloc_query(struct net_bridge *br,
> >       if (!skb)
> >               goto out;
> >
> > +     if (br_vlan_enabled(br->dev) && vid != 0)
> > +             __vlan_hwaccel_put_tag(skb, htons(ETH_P_8021Q), vid);
> > +
> >       skb->protocol = htons(ETH_P_IP);
> >
> >       skb_reset_mac_header(skb);
> > @@ -1008,7 +1124,7 @@ static struct sk_buff *br_multicast_alloc_query(struct net_bridge *br,
> >                                                   ip4_dst, group->dst.ip4,
> >                                                   with_srcs, over_lmqt,
> >                                                   sflag, igmp_type,
> > -                                                 need_rexmit);
> > +                                                 need_rexmit, group->vid);
> >  #if IS_ENABLED(CONFIG_IPV6)
> >       case htons(ETH_P_IPV6): {
> >               struct in6_addr ip6_dst;
> > @@ -1398,7 +1514,7 @@ static void br_multicast_querier_expired(struct net_bridge *br,
> >       if (!netif_running(br->dev) || !br_opt_get(br, BROPT_MULTICAST_ENABLED))
> >               goto out;
> >
> > -     br_multicast_start_querier(br, query);
> > +     br_multicast_start_querier(br, query, query->vid);
> >
> >  out:
> >       spin_unlock(&br->multicast_lock);
> > @@ -1406,9 +1522,14 @@ static void br_multicast_querier_expired(struct net_bridge *br,
> >
> >  static void br_ip4_multicast_querier_expired(struct timer_list *t)
> >  {
> > -     struct net_bridge *br = from_timer(br, t, ip4_other_query.timer);
> > +     struct bridge_mcast_other_query *other_query =
> > +             from_timer(other_query, t, timer);
> > +     struct net_bridge *br = other_query->br;
> > +     struct bridge_mcast_own_query *query;
> >
> > -     br_multicast_querier_expired(br, &br->ip4_own_query);
> > +     list_for_each_entry(query, &br->ip4_own_queries, list)
> > +             if (query->enabled && query->vid == other_query->vid)
> > +                     br_multicast_querier_expired(br, query);
> >  }
> >
> >  #if IS_ENABLED(CONFIG_IPV6)
> > @@ -1477,19 +1598,22 @@ static void br_multicast_send_query(struct net_bridge *br,
> >                                   struct bridge_mcast_own_query *own_query)
> >  {
> >       struct bridge_mcast_other_query *other_query = NULL;
> > +     struct net_bridge_vlan_group *vg;
> >       struct br_ip br_group;
> >       unsigned long time;
> >
> >       if (!netif_running(br->dev) ||
> > -         !br_opt_get(br, BROPT_MULTICAST_ENABLED) ||
> > -         !br_opt_get(br, BROPT_MULTICAST_QUERIER))
> > +         !br_opt_get(br, BROPT_MULTICAST_ENABLED))
> >               return;
> >
> > -     memset(&br_group.dst, 0, sizeof(br_group.dst));
> > +     if (!own_query->enabled)
> > +             return;
> >
> > -     if (port ? (own_query == &port->ip4_own_query) :
> > -                (own_query == &br->ip4_own_query)) {
> > -             other_query = &br->ip4_other_query;
> > +     memset(&br_group, 0, sizeof(br_group));
> > +
> > +     if (own_query->ip4) {
> > +             other_query = br_mcast_find_other_query(&br->ip4_other_queries,
> > +                                                     own_query->vid);
> >               br_group.proto = htons(ETH_P_IP);
> >  #if IS_ENABLED(CONFIG_IPV6)
> >       } else {
> > @@ -1501,6 +1625,12 @@ static void br_multicast_send_query(struct net_bridge *br,
> >       if (!other_query || timer_pending(&other_query->timer))
> >               return;
> >
> > +     br_group.vid = own_query->vid;
> > +
> > +     vg =  port ? nbp_vlan_group(port) : br_vlan_group(br);
> > +     if (vg->pvid == own_query->vid)
> > +             br_group.vid = 0;
> > +
> >       __br_multicast_send_query(br, port, NULL, NULL, &br_group, false, 0,
> >                                 NULL);
> >
> > @@ -1533,9 +1663,10 @@ br_multicast_port_query_expired(struct net_bridge_port *port,
> >
> >  static void br_ip4_multicast_port_query_expired(struct timer_list *t)
> >  {
> > -     struct net_bridge_port *port = from_timer(port, t, ip4_own_query.timer);
> > +     struct bridge_mcast_own_query *query = from_timer(query, t, timer);
> > +     struct net_bridge_port *port = query->port;
> >
> > -     br_multicast_port_query_expired(port, &port->ip4_own_query);
> > +     br_multicast_port_query_expired(port, query);
> >  }
> >
> >  #if IS_ENABLED(CONFIG_IPV6)
> > @@ -1551,17 +1682,23 @@ static void br_multicast_port_group_rexmit(struct timer_list *t)
> >  {
> >       struct net_bridge_port_group *pg = from_timer(pg, t, rexmit_timer);
> >       struct bridge_mcast_other_query *other_query = NULL;
> > +     struct bridge_mcast_own_query *own_query = NULL;
> >       struct net_bridge *br = pg->key.port->br;
> > +     u16 vid = pg->key.addr.vid;
> >       bool need_rexmit = false;
> >
> >       spin_lock(&br->multicast_lock);
> > +     own_query = br_mcast_find_own_query(&pg->key.port->ip4_own_queries,
> > +                                         vid);
> > +
> >       if (!netif_running(br->dev) || hlist_unhashed(&pg->mglist) ||
> >           !br_opt_get(br, BROPT_MULTICAST_ENABLED) ||
> > -         !br_opt_get(br, BROPT_MULTICAST_QUERIER))
> > +         !own_query || !own_query->enabled)
> >               goto out;
> >
> >       if (pg->key.addr.proto == htons(ETH_P_IP))
> > -             other_query = &br->ip4_other_query;
> > +             other_query = br_mcast_find_other_query(&br->ip4_other_queries,
> > +                                                     vid);
> >  #if IS_ENABLED(CONFIG_IPV6)
> >       else
> >               other_query = &br->ip6_other_query;
> > @@ -1603,8 +1740,7 @@ int br_multicast_add_port(struct net_bridge_port *port)
> >
> >       timer_setup(&port->multicast_router_timer,
> >                   br_multicast_router_expired, 0);
> > -     timer_setup(&port->ip4_own_query.timer,
> > -                 br_ip4_multicast_port_query_expired, 0);
> > +     INIT_LIST_HEAD(&port->ip4_own_queries);
> >  #if IS_ENABLED(CONFIG_IPV6)
> >       timer_setup(&port->ip6_own_query.timer,
> >                   br_ip6_multicast_port_query_expired, 0);
> > @@ -1621,6 +1757,7 @@ int br_multicast_add_port(struct net_bridge_port *port)
> >
> >  void br_multicast_del_port(struct net_bridge_port *port)
> >  {
> > +     struct bridge_mcast_own_query *query, *tmp;
> >       struct net_bridge *br = port->br;
> >       struct net_bridge_port_group *pg;
> >       HLIST_HEAD(deleted_head);
> > @@ -1635,6 +1772,9 @@ void br_multicast_del_port(struct net_bridge_port *port)
> >       br_multicast_gc(&deleted_head);
> >       del_timer_sync(&port->multicast_router_timer);
> >       free_percpu(port->mcast_stats);
> > +
> > +     list_for_each_entry_safe(query, tmp, &port->ip4_own_queries, list)
> > +             br_mcast_del_own_query(query);
> >  }
> >
> >  static void br_multicast_enable(struct bridge_mcast_own_query *query)
> > @@ -1646,14 +1786,49 @@ static void br_multicast_enable(struct bridge_mcast_own_query *query)
> >               mod_timer(&query->timer, jiffies);
> >  }
> >
> > +static void br_multicast_disable(struct bridge_mcast_own_query *query)
> > +{
> > +     del_timer_sync(&query->timer);
> > +}
> > +
> >  static void __br_multicast_enable_port(struct net_bridge_port *port)
> >  {
> > +     struct bridge_mcast_own_query *query;
> >       struct net_bridge *br = port->br;
> >
> >       if (!br_opt_get(br, BROPT_MULTICAST_ENABLED) || !netif_running(br->dev))
> >               return;
> >
> > -     br_multicast_enable(&port->ip4_own_query);
> > +     list_for_each_entry(query, &br->ip4_own_queries, list) {
> > +             struct bridge_mcast_own_query *port_query;
> > +             struct net_bridge_vlan_group *vg;
> > +
> > +             if (!query->enabled)
> > +                     continue;
> > +
> > +             if (br_vlan_enabled(br->dev)) {
> > +                     vg = nbp_vlan_group(port);
> > +                     if (!vg || (vg && !br_vlan_find(vg, query->vid)))
> > +                             continue;
> > +             }
> > +
> > +             port_query = br_mcast_find_own_query(&port->ip4_own_queries,
> > +                                                  query->vid);
> > +             if (!port_query) {
> > +                     port_query = br_mcast_add_own_query(&port->ip4_own_queries,
> > +                                                         query->vid,
> > +                                                         br_ip4_multicast_port_query_expired);
> > +                     if (!port_query)
> > +                             continue;
> > +
> > +                     port_query->port = port;
> > +             }
> > +
> > +             if (query->ip4) {
> > +                     port_query->ip4 = true;
> > +                     br_multicast_enable(port_query);
> > +             }
> > +     }
> >  #if IS_ENABLED(CONFIG_IPV6)
> >       br_multicast_enable(&port->ip6_own_query);
> >  #endif
> > @@ -1673,6 +1848,7 @@ void br_multicast_enable_port(struct net_bridge_port *port)
> >
> >  void br_multicast_disable_port(struct net_bridge_port *port)
> >  {
> > +     struct bridge_mcast_own_query *query;
> >       struct net_bridge *br = port->br;
> >       struct net_bridge_port_group *pg;
> >       struct hlist_node *n;
> > @@ -1685,7 +1861,8 @@ void br_multicast_disable_port(struct net_bridge_port *port)
> >       __del_port_router(port);
> >
> >       del_timer(&port->multicast_router_timer);
> > -     del_timer(&port->ip4_own_query.timer);
> > +     list_for_each_entry(query, &port->ip4_own_queries, list)
> > +             del_timer(&query->timer);
> >  #if IS_ENABLED(CONFIG_IPV6)
> >       del_timer(&port->ip6_own_query.timer);
> >  #endif
> > @@ -1717,17 +1894,23 @@ static void __grp_src_mod_timer(struct net_bridge_group_src *src,
> >  static void __grp_src_query_marked_and_rexmit(struct net_bridge_port_group *pg)
> >  {
> >       struct bridge_mcast_other_query *other_query = NULL;
> > +     struct bridge_mcast_own_query *own_query = NULL;
> >       struct net_bridge *br = pg->key.port->br;
> >       u32 lmqc = br->multicast_last_member_count;
> >       unsigned long lmqt, lmi, now = jiffies;
> >       struct net_bridge_group_src *ent;
> > +     u16 vid = pg->key.addr.vid;
> > +
> > +     own_query = br_mcast_find_own_query(&pg->key.port->ip4_own_queries,
> > +                                         vid);
> >
> >       if (!netif_running(br->dev) ||
> >           !br_opt_get(br, BROPT_MULTICAST_ENABLED))
> >               return;
> >
> >       if (pg->key.addr.proto == htons(ETH_P_IP))
> > -             other_query = &br->ip4_other_query;
> > +             other_query = br_mcast_find_other_query(&br->ip4_other_queries,
> > +                                                     vid);
> >  #if IS_ENABLED(CONFIG_IPV6)
> >       else
> >               other_query = &br->ip6_other_query;
> > @@ -1738,7 +1921,7 @@ static void __grp_src_query_marked_and_rexmit(struct net_bridge_port_group *pg)
> >               if (ent->flags & BR_SGRP_F_SEND) {
> >                       ent->flags &= ~BR_SGRP_F_SEND;
> >                       if (ent->timer.expires > lmqt) {
> > -                             if (br_opt_get(br, BROPT_MULTICAST_QUERIER) &&
> > +                             if (own_query && own_query->enabled &&
> >                                   other_query &&
> >                                   !timer_pending(&other_query->timer))
> >                                       ent->src_query_rexmit_cnt = lmqc;
> > @@ -1747,7 +1930,7 @@ static void __grp_src_query_marked_and_rexmit(struct net_bridge_port_group *pg)
> >               }
> >       }
> >
> > -     if (!br_opt_get(br, BROPT_MULTICAST_QUERIER) ||
> > +     if (!own_query || !own_query->enabled ||
> >           !other_query || timer_pending(&other_query->timer))
> >               return;
> >
> > @@ -1763,21 +1946,27 @@ static void __grp_src_query_marked_and_rexmit(struct net_bridge_port_group *pg)
> >  static void __grp_send_query_and_rexmit(struct net_bridge_port_group *pg)
> >  {
> >       struct bridge_mcast_other_query *other_query = NULL;
> > +     struct bridge_mcast_own_query *own_query = NULL;
> >       struct net_bridge *br = pg->key.port->br;
> >       unsigned long now = jiffies, lmi;
> > +     u16 vid = pg->key.addr.vid;
> >
> >       if (!netif_running(br->dev) ||
> >           !br_opt_get(br, BROPT_MULTICAST_ENABLED))
> >               return;
> >
> > +     own_query = br_mcast_find_own_query(&pg->key.port->ip4_own_queries,
> > +                                         vid);
> > +
> >       if (pg->key.addr.proto == htons(ETH_P_IP))
> > -             other_query = &br->ip4_other_query;
> > +             other_query = br_mcast_find_other_query(&br->ip4_other_queries,
> > +                                                     vid);
> >  #if IS_ENABLED(CONFIG_IPV6)
> >       else
> >               other_query = &br->ip6_other_query;
> >  #endif
> >
> > -     if (br_opt_get(br, BROPT_MULTICAST_QUERIER) &&
> > +     if (own_query && own_query->enabled &&
> >           other_query && !timer_pending(&other_query->timer)) {
> >               lmi = now + br->multicast_last_member_interval;
> >               pg->grp_query_rexmit_cnt = br->multicast_last_member_count - 1;
> > @@ -2484,10 +2673,12 @@ static int br_ip6_multicast_mld2_report(struct net_bridge *br,
> >
> >  static bool br_ip4_multicast_select_querier(struct net_bridge *br,
> >                                           struct net_bridge_port *port,
> > -                                         __be32 saddr)
> > +                                         __be32 saddr,
> > +                                         struct bridge_mcast_own_query *own,
> > +                                         struct bridge_mcast_other_query *other)
> >  {
> > -     if (!timer_pending(&br->ip4_own_query.timer) &&
> > -         !timer_pending(&br->ip4_other_query.timer))
> > +     if (own && !timer_pending(&own->timer) &&
> > +         !timer_pending(&other->timer))
> >               goto update;
> >
> >       if (!br->ip4_querier.addr.src.ip4)
> > @@ -2533,11 +2724,14 @@ static bool br_ip6_multicast_select_querier(struct net_bridge *br,
> >
> >  static bool br_multicast_select_querier(struct net_bridge *br,
> >                                       struct net_bridge_port *port,
> > -                                     struct br_ip *saddr)
> > +                                     struct br_ip *saddr,
> > +                                     struct bridge_mcast_own_query *query,
> > +                                     struct bridge_mcast_other_query *other_query)
> >  {
> >       switch (saddr->proto) {
> >       case htons(ETH_P_IP):
> > -             return br_ip4_multicast_select_querier(br, port, saddr->src.ip4);
> > +             return br_ip4_multicast_select_querier(br, port, saddr->src.ip4,
> > +                                                    query, other_query);
> >  #if IS_ENABLED(CONFIG_IPV6)
> >       case htons(ETH_P_IPV6):
> >               return br_ip6_multicast_select_querier(br, port, &saddr->src.ip6);
> > @@ -2628,9 +2822,10 @@ static void br_multicast_query_received(struct net_bridge *br,
> >                                       struct net_bridge_port *port,
> >                                       struct bridge_mcast_other_query *query,
> >                                       struct br_ip *saddr,
> > -                                     unsigned long max_delay)
> > +                                     unsigned long max_delay,
> > +                                     struct bridge_mcast_own_query *own_query)
> >  {
> > -     if (!br_multicast_select_querier(br, port, saddr))
> > +     if (!br_multicast_select_querier(br, port, saddr, own_query, query))
> >               return;
> >
> >       br_multicast_update_query_timer(br, query, max_delay);
> > @@ -2643,6 +2838,8 @@ static void br_ip4_multicast_query(struct net_bridge *br,
> >                                  u16 vid)
> >  {
> >       unsigned int transport_len = ip_transport_len(skb);
> > +     struct bridge_mcast_other_query *other_query;
> > +     struct bridge_mcast_own_query *own_query;
> >       const struct iphdr *iph = ip_hdr(skb);
> >       struct igmphdr *ih = igmp_hdr(skb);
> >       struct net_bridge_mdb_entry *mp;
> > @@ -2684,8 +2881,13 @@ static void br_ip4_multicast_query(struct net_bridge *br,
> >               saddr.proto = htons(ETH_P_IP);
> >               saddr.src.ip4 = iph->saddr;
> >
> > -             br_multicast_query_received(br, port, &br->ip4_other_query,
> > -                                         &saddr, max_delay);
> > +             br_mcast_add_queries(br, vid);
> > +
> > +             own_query = br_mcast_find_own_query(&br->ip4_own_queries, vid);
> > +             other_query = br_mcast_find_other_query(&br->ip4_other_queries,
> > +                                                     vid);
> > +             br_multicast_query_received(br, port, other_query, &saddr,
> > +                                         max_delay, own_query);
> >               goto out;
> >       }
> >
> > @@ -2773,7 +2975,7 @@ static int br_ip6_multicast_query(struct net_bridge *br,
> >               saddr.src.ip6 = ipv6_hdr(skb)->saddr;
> >
> >               br_multicast_query_received(br, port, &br->ip6_other_query,
> > -                                         &saddr, max_delay);
> > +                                         &saddr, max_delay, NULL);
> >               goto out;
> >       } else if (!group) {
> >               goto out;
> > @@ -2850,7 +3052,7 @@ br_multicast_leave_group(struct net_bridge *br,
> >       if (timer_pending(&other_query->timer))
> >               goto out;
> >
> > -     if (br_opt_get(br, BROPT_MULTICAST_QUERIER)) {
> > +     if (own_query && own_query->enabled) {
> >               __br_multicast_send_query(br, port, NULL, NULL, &mp->addr,
> >                                         false, 0, NULL);
> >
> > @@ -2916,21 +3118,26 @@ static void br_ip4_multicast_leave_group(struct net_bridge *br,
> >                                        __u16 vid,
> >                                        const unsigned char *src)
> >  {
> > -     struct br_ip br_group;
> > +     struct bridge_mcast_other_query *other_query;
> >       struct bridge_mcast_own_query *own_query;
> > +     struct br_ip br_group;
> >
> >       if (ipv4_is_local_multicast(group))
> >               return;
> >
> > -     own_query = port ? &port->ip4_own_query : &br->ip4_own_query;
> > +     if (port)
> > +             own_query = br_mcast_find_own_query(&port->ip4_own_queries, vid);
> > +     else
> > +             own_query = br_mcast_find_own_query(&br->ip4_own_queries, vid);
> >
> >       memset(&br_group, 0, sizeof(br_group));
> >       br_group.dst.ip4 = group;
> >       br_group.proto = htons(ETH_P_IP);
> >       br_group.vid = vid;
> >
> > -     br_multicast_leave_group(br, port, &br_group, &br->ip4_other_query,
> > -                              own_query, src);
> > +     other_query = br_mcast_find_other_query(&br->ip4_other_queries, vid);
> > +     br_multicast_leave_group(br, port, &br_group, other_query, own_query,
> > +                              src);
> >  }
> >
> >  #if IS_ENABLED(CONFIG_IPV6)
> > @@ -3195,9 +3402,10 @@ static void br_multicast_query_expired(struct net_bridge *br,
> >
> >  static void br_ip4_multicast_query_expired(struct timer_list *t)
> >  {
> > -     struct net_bridge *br = from_timer(br, t, ip4_own_query.timer);
> > +     struct bridge_mcast_own_query *query = from_timer(query, t, timer);
> > +     struct net_bridge *br = query->br;
> >
> > -     br_multicast_query_expired(br, &br->ip4_own_query, &br->ip4_querier);
> > +     br_multicast_query_expired(br, query, &br->ip4_querier);
> >  }
> >
> >  #if IS_ENABLED(CONFIG_IPV6)
> > @@ -3237,7 +3445,6 @@ void br_multicast_init(struct net_bridge *br)
> >       br->multicast_querier_interval = 255 * HZ;
> >       br->multicast_membership_interval = 260 * HZ;
> >
> > -     br->ip4_other_query.delay_time = 0;
> >       br->ip4_querier.port = NULL;
> >       br->multicast_igmp_version = 2;
> >  #if IS_ENABLED(CONFIG_IPV6)
> > @@ -3251,10 +3458,8 @@ void br_multicast_init(struct net_bridge *br)
> >       spin_lock_init(&br->multicast_lock);
> >       timer_setup(&br->multicast_router_timer,
> >                   br_multicast_local_router_expired, 0);
> > -     timer_setup(&br->ip4_other_query.timer,
> > -                 br_ip4_multicast_querier_expired, 0);
> > -     timer_setup(&br->ip4_own_query.timer,
> > -                 br_ip4_multicast_query_expired, 0);
> > +     INIT_LIST_HEAD(&br->ip4_other_queries);
> > +     INIT_LIST_HEAD(&br->ip4_own_queries);
> >  #if IS_ENABLED(CONFIG_IPV6)
> >       timer_setup(&br->ip6_other_query.timer,
> >                   br_ip6_multicast_querier_expired, 0);
> > @@ -3341,7 +3546,10 @@ static void __br_multicast_open(struct net_bridge *br,
> >
> >  void br_multicast_open(struct net_bridge *br)
> >  {
> > -     __br_multicast_open(br, &br->ip4_own_query);
> > +     struct bridge_mcast_own_query *query;
> > +
> > +     list_for_each_entry(query, &br->ip4_own_queries, list)
> > +             __br_multicast_open(br, query);
> >  #if IS_ENABLED(CONFIG_IPV6)
> >       __br_multicast_open(br, &br->ip6_own_query);
> >  #endif
> > @@ -3349,9 +3557,14 @@ void br_multicast_open(struct net_bridge *br)
> >
> >  void br_multicast_stop(struct net_bridge *br)
> >  {
> > +     struct bridge_mcast_other_query *other_query;
> > +     struct bridge_mcast_own_query *query;
> > +
> >       del_timer_sync(&br->multicast_router_timer);
> > -     del_timer_sync(&br->ip4_other_query.timer);
> > -     del_timer_sync(&br->ip4_own_query.timer);
> > +     list_for_each_entry(other_query, &br->ip4_other_queries, list)
> > +             del_timer_sync(&other_query->timer);
> > +     list_for_each_entry(query, &br->ip4_own_queries, list)
> > +             del_timer_sync(&query->timer);
> >  #if IS_ENABLED(CONFIG_IPV6)
> >       del_timer_sync(&br->ip6_other_query.timer);
> >       del_timer_sync(&br->ip6_own_query.timer);
> > @@ -3461,11 +3674,20 @@ int br_multicast_set_port_router(struct net_bridge_port *p, unsigned long val)
> >  }
> >
> >  static void br_multicast_start_querier(struct net_bridge *br,
> > -                                    struct bridge_mcast_own_query *query)
> > +                                    struct bridge_mcast_own_query *query,
> > +                                    u16 vid)
> >  {
> > +     struct bridge_mcast_own_query *port_query;
> > +     struct net_bridge_vlan_group *vg;
> >       struct net_bridge_port *port;
> >
> > -     __br_multicast_open(br, query);
> > +     if (br_vlan_enabled(br->dev)) {
> > +             vg = br_vlan_group(br);
> > +             if (vg && br_vlan_find(vg, vid))
> > +                     __br_multicast_open(br, query);
> > +     } else {
> > +             __br_multicast_open(br, query);
> > +     }
> >
> >       rcu_read_lock();
> >       list_for_each_entry_rcu(port, &br->port_list, list) {
> > @@ -3473,11 +3695,66 @@ static void br_multicast_start_querier(struct net_bridge *br,
> >                   port->state == BR_STATE_BLOCKING)
> >                       continue;
> >
> > -             if (query == &br->ip4_own_query)
> > -                     br_multicast_enable(&port->ip4_own_query);
> > +             if (br_vlan_enabled(br->dev)) {
> > +                     vg = nbp_vlan_group(port);
> > +                     if (!vg || (vg && !br_vlan_find(vg, vid)))
> > +                             continue;
> > +             }
> > +
> > +             port_query = br_mcast_find_own_query(&port->ip4_own_queries,
> > +                                                  vid);
> > +             if (!port_query)
> > +                     continue;
> > +
> > +             port_query->enabled = true;
> > +
> > +             if (query->ip4) {
> > +                     port_query->ip4 = true;
> > +                     br_multicast_enable(port_query);
> > +             }
> >  #if IS_ENABLED(CONFIG_IPV6)
> > -             else
> > +             else {
> >                       br_multicast_enable(&port->ip6_own_query);
> > +             }
> > +#endif
> > +     }
> > +     rcu_read_unlock();
> > +}
> > +
> > +static void br_multicast_stop_querier(struct net_bridge *br,
> > +                                   struct bridge_mcast_own_query *query,
> > +                                   u16 vid)
> > +{
> > +     struct bridge_mcast_own_query *port_query;
> > +     struct net_bridge_vlan_group *vg;
> > +     struct net_bridge_port *port;
> > +
> > +     query->enabled = false;
> > +
> > +     rcu_read_lock();
> > +     list_for_each_entry_rcu(port, &br->port_list, list) {
> > +             if (port->state == BR_STATE_DISABLED ||
> > +                 port->state == BR_STATE_BLOCKING)
> > +                     continue;
> > +
> > +             if (br_vlan_enabled(br->dev)) {
> > +                     vg = nbp_vlan_group(port);
> > +                     if (!vg || (vg && !br_vlan_find(vg, vid)))
> > +                             continue;
> > +             }
> > +
> > +             port_query = br_mcast_find_own_query(&port->ip4_own_queries,
> > +                                                  vid);
> > +             if (!port_query)
> > +                     continue;
> > +
> > +             port_query->enabled = false;
> > +
> > +             if (query->ip4)
> > +                     br_multicast_disable(port_query);
> > +#if IS_ENABLED(CONFIG_IPV6)
> > +             else
> > +                     br_multicast_disable(&port->ip6_own_query);
> >  #endif
> >       }
> >       rcu_read_unlock();
> > @@ -3553,32 +3830,55 @@ bool br_multicast_router(const struct net_device *dev)
> >  }
> >  EXPORT_SYMBOL_GPL(br_multicast_router);
> >
> > -int br_multicast_set_querier(struct net_bridge *br, unsigned long val)
> > +int br_multicast_set_querier(struct net_bridge *br, unsigned long val, u16 vid)
> >  {
> > +     struct bridge_mcast_other_query *other_query;
> > +     struct bridge_mcast_own_query *query;
> > +     struct net_bridge_vlan_group *vg;
> >       unsigned long max_delay;
> >
> >       val = !!val;
> >
> > +     if (vid == 0) {
> > +             vg = br_vlan_group(br);
> > +             if (vg)
> > +                     vid = vg->pvid;
> > +     }
> > +
> >       spin_lock_bh(&br->multicast_lock);
> > -     if (br_opt_get(br, BROPT_MULTICAST_QUERIER) == val)
> > +     query = br_mcast_find_own_query(&br->ip4_own_queries, vid);
> > +     if (!query) {
> > +             if (br_vlan_enabled(br->dev))
> > +                     goto unlock;
> > +
> > +             br_mcast_add_queries(br, vid);
> > +     }
> > +
> > +     other_query = br_mcast_find_other_query(&br->ip4_other_queries, vid);
> > +     if (!other_query)
> >               goto unlock;
> >
> > -     br_opt_toggle(br, BROPT_MULTICAST_QUERIER, !!val);
> > -     if (!val)
> > +     if (!val && query) {
> > +             br_multicast_stop_querier(br, query, vid);
> >               goto unlock;
> > +     }
> >
> > -     max_delay = br->multicast_query_response_interval;
> > +     if (val & query->enabled)
> > +             goto unlock;
> >
> > -     if (!timer_pending(&br->ip4_other_query.timer))
> > -             br->ip4_other_query.delay_time = jiffies + max_delay;
> > +     query->enabled = true;
> >
> > -     br_multicast_start_querier(br, &br->ip4_own_query);
> > +     max_delay = br->multicast_query_response_interval;
> > +     if (!timer_pending(&other_query->timer))
> > +             other_query->delay_time = jiffies + max_delay;
> > +
> > +     br_multicast_start_querier(br, query, vid);
> >
> >  #if IS_ENABLED(CONFIG_IPV6)
> >       if (!timer_pending(&br->ip6_other_query.timer))
> >               br->ip6_other_query.delay_time = jiffies + max_delay;
> >
> > -     br_multicast_start_querier(br, &br->ip6_own_query);
> > +     br_multicast_start_querier(br, &br->ip6_own_query, vid);
> >  #endif
> >
> >  unlock:
> > @@ -3587,6 +3887,79 @@ int br_multicast_set_querier(struct net_bridge *br, unsigned long val)
> >       return 0;
> >  }
> >
> > +void br_multicast_vlan_add(struct net_bridge_vlan *v)
> > +{
> > +     struct bridge_mcast_own_query *query, *port_query;
> > +     struct net_bridge_port *p;
> > +     struct net_bridge *br;
> > +
> > +     if (br_vlan_is_master(v)) {
> > +             br_mcast_add_queries(v->br, v->vid);
> > +             return;
> > +     }
> > +
> > +     p = v->port;
> > +     br = p->br;
> > +
> > +     query = br_mcast_find_own_query(&br->ip4_own_queries, v->vid);
> > +
> > +     port_query = br_mcast_add_own_query(&p->ip4_own_queries,
> > +                                         v->vid,
> > +                                         br_ip4_multicast_port_query_expired);
> > +     if (!port_query)
> > +             return;
> > +
> > +     port_query->port = p;
> > +     port_query->ip4 = true;
> > +
> > +     if (query->enabled) {
> > +             port_query->enabled = true;
> > +             br_multicast_enable(port_query);
> > +     }
> > +}
> > +
> > +void br_multicast_vlan_del(struct net_bridge_vlan *v)
> > +{
> > +     struct bridge_mcast_other_query *other_query, *other_tmp;
> > +     struct bridge_mcast_own_query *query, *tmp;
> > +     struct net_bridge_port *p;
> > +     struct net_bridge *br;
> > +
> > +     if (br_vlan_is_master(v)) {
> > +             br = v->br;
> > +
> > +             list_for_each_entry_safe(other_query, other_tmp,
> > +                                      &br->ip4_other_queries, list)
> > +                     if (other_query->vid == v->vid)
> > +                             br_mcast_del_other_query(other_query);
> > +
> > +             list_for_each_entry_safe(query, tmp, &br->ip4_own_queries, list)
> > +                     if (query->vid == v->vid)
> > +                             br_mcast_del_own_query(query);
> > +
> > +             return;
> > +     }
> > +
> > +     p = v->port;
> > +
> > +     list_for_each_entry_safe(query, tmp, &p->ip4_own_queries, list) {
> > +             if (query->vid == v->vid)
> > +                     br_mcast_del_own_query(query);
> > +     }
> > +}
> > +
> > +void br_multicast_vlan_toggle(struct net_bridge *br, bool on)
> > +{
> > +     struct bridge_mcast_own_query *query;
> > +
> > +     list_for_each_entry(query, &br->ip4_own_queries, list) {
> > +             if (!on)
> > +                     br_multicast_stop_querier(br, query, query->vid);
> > +             else
> > +                     br_multicast_start_querier(br, query, query->vid);
> > +     }
> > +}
> > +
> >  int br_multicast_set_igmp_version(struct net_bridge *br, unsigned long val)
> >  {
> >       /* Currently we support only version 2 and 3 */
> > @@ -3711,7 +4084,7 @@ bool br_multicast_has_querier_anywhere(struct net_device *dev, int proto)
> >       memset(&eth, 0, sizeof(eth));
> >       eth.h_proto = htons(proto);
> >
> > -     ret = br_multicast_querier_exists(br, &eth, NULL);
> > +     ret = br_multicast_any_querier_exists(br, &eth);
> >
> >  unlock:
> >       rcu_read_unlock();
> > @@ -3746,7 +4119,7 @@ bool br_multicast_has_querier_adjacent(struct net_device *dev, int proto)
> >
> >       switch (proto) {
> >       case ETH_P_IP:
> > -             if (!timer_pending(&br->ip4_other_query.timer) ||
> > +             if (!br_multicast_any_querier_adjacent(br) ||
> >                   rcu_dereference(br->ip4_querier.port) == port)
> >                       goto unlock;
> >               break;
> > diff --git a/net/bridge/br_netlink.c b/net/bridge/br_netlink.c
> > index 49700ce0e919..d32f4c185364 100644
> > --- a/net/bridge/br_netlink.c
> > +++ b/net/bridge/br_netlink.c
> > @@ -1186,6 +1186,7 @@ static const struct nla_policy br_policy[IFLA_BR_MAX + 1] = {
> >       [IFLA_BR_VLAN_STATS_PER_PORT] = { .type = NLA_U8 },
> >       [IFLA_BR_MULTI_BOOLOPT] =
> >               NLA_POLICY_EXACT_LEN(sizeof(struct br_boolopt_multi)),
> > +     [IFLA_BR_MCAST_QUERIER_VID] = { .type = NLA_U16 },
> >  };
> >
> >  static int br_changelink(struct net_device *brdev, struct nlattr *tb[],
> > @@ -1193,6 +1194,7 @@ static int br_changelink(struct net_device *brdev, struct nlattr *tb[],
> >                        struct netlink_ext_ack *extack)
> >  {
> >       struct net_bridge *br = netdev_priv(brdev);
> > +     u16 vid = 0;
> >       int err;
> >
> >       if (!data)
> > @@ -1204,6 +1206,9 @@ static int br_changelink(struct net_device *brdev, struct nlattr *tb[],
> >                       return err;
> >       }
> >
> > +     if (data[IFLA_BR_MCAST_QUERIER_VID])
> > +             vid = nla_get_u16(data[IFLA_BR_MCAST_QUERIER_VID]);
> > +
> >       if (data[IFLA_BR_HELLO_TIME]) {
> >               err = br_set_hello_time(br, nla_get_u32(data[IFLA_BR_HELLO_TIME]));
> >               if (err)
> > @@ -1333,7 +1338,7 @@ static int br_changelink(struct net_device *brdev, struct nlattr *tb[],
> >       if (data[IFLA_BR_MCAST_QUERIER]) {
> >               u8 mcast_querier = nla_get_u8(data[IFLA_BR_MCAST_QUERIER]);
> >
> > -             err = br_multicast_set_querier(br, mcast_querier);
> > +             err = br_multicast_set_querier(br, mcast_querier, vid);
> >               if (err)
> >                       return err;
> >       }
> > @@ -1596,7 +1601,7 @@ static int br_fill_info(struct sk_buff *skb, const struct net_device *brdev)
> >           nla_put_u8(skb, IFLA_BR_MCAST_QUERY_USE_IFADDR,
> >                      br_opt_get(br, BROPT_MULTICAST_QUERY_USE_IFADDR)) ||
> >           nla_put_u8(skb, IFLA_BR_MCAST_QUERIER,
> > -                    br_opt_get(br, BROPT_MULTICAST_QUERIER)) ||
> > +                    br_mcast_exist_own_query(br)) ||
> >           nla_put_u8(skb, IFLA_BR_MCAST_STATS_ENABLED,
> >                      br_opt_get(br, BROPT_MULTICAST_STATS_ENABLED)) ||
> >           nla_put_u32(skb, IFLA_BR_MCAST_HASH_ELASTICITY, RHT_ELASTICITY) ||
> > diff --git a/net/bridge/br_private.h b/net/bridge/br_private.h
> > index d62c6e1af64a..84f597f542b1 100644
> > --- a/net/bridge/br_private.h
> > +++ b/net/bridge/br_private.h
> > @@ -66,14 +66,24 @@ struct mac_addr {
> >  #ifdef CONFIG_BRIDGE_IGMP_SNOOPING
> >  /* our own querier */
> >  struct bridge_mcast_own_query {
> > +     struct list_head        list;
> >       struct timer_list       timer;
> >       u32                     startup_sent;
> > +     struct net_bridge_port  *port;
> > +     struct net_bridge       *br;
> > +     bool                    ip4;
> > +     u16                     vid;
> > +     bool                    enabled;
> >  };
> >
> >  /* other querier */
> >  struct bridge_mcast_other_query {
> > +     struct list_head                list;
> >       struct timer_list               timer;
> >       unsigned long                   delay_time;
> > +     struct net_bridge               *br;
> > +     bool                            ip4;
> > +     u16                             vid;
> >  };
> >
> >  /* selected querier */
> > @@ -304,7 +314,7 @@ struct net_bridge_port {
> >       struct rcu_head                 rcu;
> >
> >  #ifdef CONFIG_BRIDGE_IGMP_SNOOPING
> > -     struct bridge_mcast_own_query   ip4_own_query;
> > +     struct list_head                ip4_own_queries;
> >  #if IS_ENABLED(CONFIG_IPV6)
> >       struct bridge_mcast_own_query   ip6_own_query;
> >  #endif /* IS_ENABLED(CONFIG_IPV6) */
> > @@ -448,8 +458,8 @@ struct net_bridge {
> >       struct hlist_head               router_list;
> >
> >       struct timer_list               multicast_router_timer;
> > -     struct bridge_mcast_other_query ip4_other_query;
> > -     struct bridge_mcast_own_query   ip4_own_query;
> > +     struct list_head                ip4_other_queries;
> > +     struct list_head                ip4_own_queries;
> >       struct bridge_mcast_querier     ip4_querier;
> >       struct bridge_mcast_stats       __percpu *mcast_stats;
> >  #if IS_ENABLED(CONFIG_IPV6)
> > @@ -788,6 +798,9 @@ int br_ioctl_deviceless_stub(struct net *net, unsigned int cmd,
> >
> >  /* br_multicast.c */
> >  #ifdef CONFIG_BRIDGE_IGMP_SNOOPING
> > +void br_multicast_vlan_add(struct net_bridge_vlan *v);
> > +void br_multicast_vlan_del(struct net_bridge_vlan *v);
> > +void br_multicast_vlan_toggle(struct net_bridge *br, bool on);
> >  int br_multicast_rcv(struct net_bridge *br, struct net_bridge_port *port,
> >                    struct sk_buff *skb, u16 vid);
> >  struct net_bridge_mdb_entry *br_mdb_get(struct net_bridge *br,
> > @@ -807,7 +820,7 @@ void br_multicast_flood(struct net_bridge_mdb_entry *mdst,
> >  int br_multicast_set_router(struct net_bridge *br, unsigned long val);
> >  int br_multicast_set_port_router(struct net_bridge_port *p, unsigned long val);
> >  int br_multicast_toggle(struct net_bridge *br, unsigned long val);
> > -int br_multicast_set_querier(struct net_bridge *br, unsigned long val);
> > +int br_multicast_set_querier(struct net_bridge *br, unsigned long val, u16 vid);
> >  int br_multicast_set_hash_max(struct net_bridge *br, unsigned long val);
> >  int br_multicast_set_igmp_version(struct net_bridge *br, unsigned long val);
> >  #if IS_ENABLED(CONFIG_IPV6)
> > @@ -846,6 +859,11 @@ 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 bridge_mcast_other_query *
> > +br_mcast_find_other_query(struct list_head *list, u16 vid);
> > +struct bridge_mcast_own_query *
> > +br_mcast_find_own_query(struct list_head *list, u16 vid);
> > +bool br_mcast_exist_own_query(struct net_bridge *br);
> >
> >  static inline bool br_group_is_l2(const struct br_ip *group)
> >  {
> > @@ -865,11 +883,15 @@ static inline bool br_multicast_is_router(struct net_bridge *br)
> >  static inline bool
> >  __br_multicast_querier_exists(struct net_bridge *br,
> >                               struct bridge_mcast_other_query *querier,
> > -                             const bool is_ipv6)
> > +                             const bool is_ipv6,
> > +                             u16 vid)
> >  {
> > +     struct bridge_mcast_own_query *query;
> >       bool own_querier_enabled;
> >
> > -     if (br_opt_get(br, BROPT_MULTICAST_QUERIER)) {
> > +     query = br_mcast_find_own_query(&br->ip4_own_queries, vid);
> > +
> > +     if (query && query->enabled) {
> >               if (is_ipv6 && !br_opt_get(br, BROPT_HAS_IPV6_ADDR))
> >                       own_querier_enabled = false;
> >               else
> > @@ -878,28 +900,62 @@ __br_multicast_querier_exists(struct net_bridge *br,
> >               own_querier_enabled = false;
> >       }
> >
> > +     if (!querier)
> > +             return own_querier_enabled;
> > +
> >       return time_is_before_jiffies(querier->delay_time) &&
> >              (own_querier_enabled || timer_pending(&querier->timer));
> >  }
> >
> >  static inline bool br_multicast_querier_exists(struct net_bridge *br,
> >                                              struct ethhdr *eth,
> > -                                            const struct net_bridge_mdb_entry *mdb)
> > +                                            const struct net_bridge_mdb_entry *mdb,
> > +                                            u16 vid)
> >  {
> > +     struct bridge_mcast_other_query *query =
> > +             br_mcast_find_other_query(&br->ip4_other_queries, vid);
> > +
> >       switch (eth->h_proto) {
> >       case (htons(ETH_P_IP)):
> > -             return __br_multicast_querier_exists(br,
> > -                     &br->ip4_other_query, false);
> > +             return __br_multicast_querier_exists(br, query, false, vid);
> >  #if IS_ENABLED(CONFIG_IPV6)
> >       case (htons(ETH_P_IPV6)):
> >               return __br_multicast_querier_exists(br,
> > -                     &br->ip6_other_query, true);
> > +                     &br->ip6_other_query, true, vid);
> >  #endif
> >       default:
> >               return !!mdb && br_group_is_l2(&mdb->addr);
> >       }
> >  }
> >
> > +static inline bool br_multicast_any_querier_exists(struct net_bridge *br,
> > +                                                struct ethhdr *eth)
> > +{
> > +     struct bridge_mcast_other_query *query;
> > +
> > +     list_for_each_entry(query, &br->ip4_other_queries, list) {
> > +             if (!timer_pending(&query->timer))
> > +                     continue;
> > +
> > +             if (br_multicast_querier_exists(br, eth, NULL, query->vid))
> > +                     return true;
> > +     }
> > +
> > +     return false;
> > +}
> > +
> > +static inline bool br_multicast_any_querier_adjacent(struct net_bridge *br)
> > +{
> > +     struct bridge_mcast_other_query *query;
> > +
> > +     list_for_each_entry(query, &br->ip4_other_queries, list) {
> > +             if (timer_pending(&query->timer))
> > +                     return true;
> > +     }
> > +
> > +     return false;
> > +}
> > +
> >  static inline bool br_multicast_is_star_g(const struct br_ip *ip)
> >  {
> >       switch (ip->proto) {
> > @@ -1015,7 +1071,19 @@ static inline bool br_multicast_is_router(struct net_bridge *br)
> >
> >  static inline bool br_multicast_querier_exists(struct net_bridge *br,
> >                                              struct ethhdr *eth,
> > -                                            const struct net_bridge_mdb_entry *mdb)
> > +                                            const struct net_bridge_mdb_entry *mdb,
> > +                                            u16 vid)
> > +{
> > +     return false;
> > +}
> > +
> > +static inline bool br_multicast_any_querier_exists(struct net_bridge *br,
> > +                                                struct ethhdr *eth)
> > +{
> > +     return false;
> > +}
> > +
> > +static inline bool br_multicast_any_querier_adjacent(struct net_bridge *br)
> >  {
> >       return false;
> >  }
> > diff --git a/net/bridge/br_sysfs_br.c b/net/bridge/br_sysfs_br.c
> > index 7db06e3f642a..23bf6a065d78 100644
> > --- a/net/bridge/br_sysfs_br.c
> > +++ b/net/bridge/br_sysfs_br.c
> > @@ -51,6 +51,33 @@ static ssize_t store_bridge_parm(struct device *d,
> >       return err ? err : len;
> >  }
> >
> > +static ssize_t store_bridge_parm2(struct device *d,
> > +                               const char *buf, size_t len,
> > +                               int (*set)(struct net_bridge *, unsigned long, u16))
> > +{
> > +     struct net_bridge *br = to_bridge(d);
> > +     char *endp;
> > +     unsigned long val;
> > +     int err;
> > +
> > +     if (!ns_capable(dev_net(br->dev)->user_ns, CAP_NET_ADMIN))
> > +             return -EPERM;
> > +
> > +     val = simple_strtoul(buf, &endp, 0);
> > +     if (endp == buf)
> > +             return -EINVAL;
> > +
> > +     if (!rtnl_trylock())
> > +             return restart_syscall();
> > +
> > +     err = (*set)(br, val, 0);
> > +     if (!err)
> > +             netdev_state_change(br->dev);
> > +     rtnl_unlock();
> > +
> > +     return err ? err : len;
> > +}
> > +
> >
> >  static ssize_t forward_delay_show(struct device *d,
> >                                 struct device_attribute *attr, char *buf)
> > @@ -404,14 +431,14 @@ static ssize_t multicast_querier_show(struct device *d,
> >                                     char *buf)
> >  {
> >       struct net_bridge *br = to_bridge(d);
> > -     return sprintf(buf, "%d\n", br_opt_get(br, BROPT_MULTICAST_QUERIER));
> > +     return sprintf(buf, "%d\n", br_mcast_exist_own_query(br));
> >  }
> >
> >  static ssize_t multicast_querier_store(struct device *d,
> >                                      struct device_attribute *attr,
> >                                      const char *buf, size_t len)
> >  {
> > -     return store_bridge_parm(d, buf, len, br_multicast_set_querier);
> > +     return store_bridge_parm2(d, buf, len, br_multicast_set_querier);
> >  }
> >  static DEVICE_ATTR_RW(multicast_querier);
> >
> > diff --git a/net/bridge/br_vlan.c b/net/bridge/br_vlan.c
> > index 701cad646b20..2e0b544a3560 100644
> > --- a/net/bridge/br_vlan.c
> > +++ b/net/bridge/br_vlan.c
> > @@ -308,6 +308,7 @@ static int __vlan_add(struct net_bridge_vlan *v, u16 flags,
> >
> >       __vlan_add_list(v);
> >       __vlan_add_flags(v, flags);
> > +     br_multicast_vlan_add(v);
> >
> >       if (p)
> >               nbp_vlan_set_vlan_dev_state(p, v->vid);
> > @@ -353,6 +354,7 @@ static int __vlan_del(struct net_bridge_vlan *v)
> >               masterv = v->brvlan;
> >       }
> >
> > +     br_multicast_vlan_del(v);
> >       __vlan_delete_pvid(vg, v->vid);
> >       if (p) {
> >               err = __vlan_vid_del(p->dev, p->br, v);
> > @@ -827,6 +829,7 @@ int __br_vlan_filter_toggle(struct net_bridge *br, unsigned long val)
> >       br_manage_promisc(br);
> >       recalculate_group_addr(br);
> >       br_recalculate_fwd_mask(br);
> > +     br_multicast_vlan_toggle(br, !!val);
> >
> >       return 0;
> >  }
> >
> 

-- 
/Horatiu

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [RFC PATCH v2] net: bridge: igmp: Extend IGMP query to be per vlan
  2021-01-13 12:15   ` [Bridge] " Nikolay Aleksandrov
@ 2021-01-16 15:39     ` Joachim Wiberg
  -1 siblings, 0 replies; 16+ messages in thread
From: Joachim Wiberg @ 2021-01-16 15:39 UTC (permalink / raw)
  To: Nikolay Aleksandrov, Horatiu Vultur, davem, kuba, roopa,
	allan.nielsen, netdev, linux-kernel, bridge

On Wed, Jan 13, 2021 at 14:15, Nikolay Aleksandrov <nikolay@nvidia.com> wrote:
> On 12/01/2021 15:59, Horatiu Vultur wrote:
>> Based on the comments of the previous version, we started to work on a
>> new version, so it would be possible to enable/disable queries per vlan.
>> [snip]
>> We were wondering if this what you had in mind when you proposed to have
>> this per vlan? Or we are completely off? Or we should fix some of the
>> issues that I mentioned, before you can see more clearly the direction?
> No, unfortunately not even close. We already have per-port per-vlan and global per-vlan
> contexts which are also linked together for each vlan, those must be used for any vlan
> configuration and state. The problem is that you'd have to mix igmp and vlan code and
> those two live under two different kconfig options, and worse rely on different locks, so
> extra care must be taken.
> [snip]
> If you don't need this asap, I'll probably get to it in two months
> after EHT and the new bridge flush api, even we are still carrying an out-of-tree patch
> for this which someone (not from cumulus) tried to upstream a few years back, but it also has
> wrong design in general. :)

Hi,

very interesting thread this!  I believe I may be the one who posted the
patch[1] a few years ago, and I fully agree with Nik here.  We developed
the basic concepts further at Westermo, but it's been really difficult
to get it stable.

We have discussed at length at work if an IGMP snooping implementation
really belongs in the bridge, or if it's better suited as a user space
daemon?  Similar to what was decided for RSTP/MSTP support, i.e., the
bridge only has STP and RSTP/MSTP is handled by mstpd[2].

Most of what's required for a user space implementation is available,
but it would've been nice if a single AF_PACKET socket on br0 could be
used to catch what brport (ifindex) a query or report comes in on.  As
it is now that information is lost/replaced with the ifindex of br0.
And then there's the issue of detecting and forwarding to a multicast
routing daemon on top of br0.  That br0 is not a brport in the MDB, or
that host_joined cannot be set/seen with iproute2 is quite limiting.
These issues can of course be addressed, but are they of interest to
the community at large?


Best regards
 /Joachim

[1]: https://lore.kernel.org/netdev/20180418120713.GA10742@troglobit/
[2]: https://github.com/mstpd/mstpd

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [Bridge] [RFC PATCH v2] net: bridge: igmp: Extend IGMP query to be per vlan
@ 2021-01-16 15:39     ` Joachim Wiberg
  0 siblings, 0 replies; 16+ messages in thread
From: Joachim Wiberg @ 2021-01-16 15:39 UTC (permalink / raw)
  To: Nikolay Aleksandrov, Horatiu Vultur, davem, kuba, roopa,
	allan.nielsen, netdev, linux-kernel, bridge

On Wed, Jan 13, 2021 at 14:15, Nikolay Aleksandrov <nikolay@nvidia.com> wrote:
> On 12/01/2021 15:59, Horatiu Vultur wrote:
>> Based on the comments of the previous version, we started to work on a
>> new version, so it would be possible to enable/disable queries per vlan.
>> [snip]
>> We were wondering if this what you had in mind when you proposed to have
>> this per vlan? Or we are completely off? Or we should fix some of the
>> issues that I mentioned, before you can see more clearly the direction?
> No, unfortunately not even close. We already have per-port per-vlan and global per-vlan
> contexts which are also linked together for each vlan, those must be used for any vlan
> configuration and state. The problem is that you'd have to mix igmp and vlan code and
> those two live under two different kconfig options, and worse rely on different locks, so
> extra care must be taken.
> [snip]
> If you don't need this asap, I'll probably get to it in two months
> after EHT and the new bridge flush api, even we are still carrying an out-of-tree patch
> for this which someone (not from cumulus) tried to upstream a few years back, but it also has
> wrong design in general. :)

Hi,

very interesting thread this!  I believe I may be the one who posted the
patch[1] a few years ago, and I fully agree with Nik here.  We developed
the basic concepts further at Westermo, but it's been really difficult
to get it stable.

We have discussed at length at work if an IGMP snooping implementation
really belongs in the bridge, or if it's better suited as a user space
daemon?  Similar to what was decided for RSTP/MSTP support, i.e., the
bridge only has STP and RSTP/MSTP is handled by mstpd[2].

Most of what's required for a user space implementation is available,
but it would've been nice if a single AF_PACKET socket on br0 could be
used to catch what brport (ifindex) a query or report comes in on.  As
it is now that information is lost/replaced with the ifindex of br0.
And then there's the issue of detecting and forwarding to a multicast
routing daemon on top of br0.  That br0 is not a brport in the MDB, or
that host_joined cannot be set/seen with iproute2 is quite limiting.
These issues can of course be addressed, but are they of interest to
the community at large?


Best regards
 /Joachim

[1]: https://lore.kernel.org/netdev/20180418120713.GA10742@troglobit/
[2]: https://github.com/mstpd/mstpd

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [RFC PATCH v2] net: bridge: igmp: Extend IGMP query to be per vlan
  2021-01-16 15:39     ` [Bridge] " Joachim Wiberg
@ 2021-01-18 11:53       ` Nikolay Aleksandrov
  -1 siblings, 0 replies; 16+ messages in thread
From: Nikolay Aleksandrov @ 2021-01-18 11:53 UTC (permalink / raw)
  To: Joachim Wiberg, Horatiu Vultur, davem, kuba, roopa,
	allan.nielsen, netdev, linux-kernel, bridge

On 16/01/2021 17:39, Joachim Wiberg wrote:
> On Wed, Jan 13, 2021 at 14:15, Nikolay Aleksandrov <nikolay@nvidia.com> wrote:
>> On 12/01/2021 15:59, Horatiu Vultur wrote:
>>> Based on the comments of the previous version, we started to work on a
>>> new version, so it would be possible to enable/disable queries per vlan.
>>> [snip]
>>> We were wondering if this what you had in mind when you proposed to have
>>> this per vlan? Or we are completely off? Or we should fix some of the
>>> issues that I mentioned, before you can see more clearly the direction?
>> No, unfortunately not even close. We already have per-port per-vlan and global per-vlan
>> contexts which are also linked together for each vlan, those must be used for any vlan
>> configuration and state. The problem is that you'd have to mix igmp and vlan code and
>> those two live under two different kconfig options, and worse rely on different locks, so
>> extra care must be taken.
>> [snip]
>> If you don't need this asap, I'll probably get to it in two months
>> after EHT and the new bridge flush api, even we are still carrying an out-of-tree patch
>> for this which someone (not from cumulus) tried to upstream a few years back, but it also has
>> wrong design in general. :)
> 
> Hi,
> 
> very interesting thread this!  I believe I may be the one who posted the
> patch[1] a few years ago, and I fully agree with Nik here.  We developed
> the basic concepts further at Westermo, but it's been really difficult
> to get it stable.
> 
> We have discussed at length at work if an IGMP snooping implementation
> really belongs in the bridge, or if it's better suited as a user space
> daemon?  Similar to what was decided for RSTP/MSTP support, i.e., the
> bridge only has STP and RSTP/MSTP is handled by mstpd[2].
> 
> Most of what's required for a user space implementation is available,
> but it would've been nice if a single AF_PACKET socket on br0 could be
> used to catch what brport (ifindex) a query or report comes in on.  As
> it is now that information is lost/replaced with the ifindex of br0.
> And then there's the issue of detecting and forwarding to a multicast
> routing daemon on top of br0.  That br0 is not a brport in the MDB, or
> that host_joined cannot be set/seen with iproute2 is quite limiting.
> These issues can of course be addressed, but are they of interest to
> the community at large?
> 
> 
> Best regards
>  /Joachim
> 
> [1]: https://lore.kernel.org/netdev/20180418120713.GA10742@troglobit/
> [2]: https://github.com/mstpd/mstpd
> 

Hi Joachim,
I actually had started implementing IGMPv3/MLDv2 as a user-space daemon part of
FRRouting (since it already has a lot of the required infra to talk to the kernel).
It also has IGMPv3/MLDv2 support within pimd, so a lot of code can be shared.
Obviously there are pros and cons to each choice, but I'd be interested to see a
full user-space implementation. I decided to make the kernel support more complete
since it already did IGMPv2 and so stopped with the new FRR daemon. If needed I'd be
happy to help with the kernel support for a new user-space daemon, and also can
contribute to the daemon itself if time permits.

Thanks,
 Nik


^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [Bridge] [RFC PATCH v2] net: bridge: igmp: Extend IGMP query to be per vlan
@ 2021-01-18 11:53       ` Nikolay Aleksandrov
  0 siblings, 0 replies; 16+ messages in thread
From: Nikolay Aleksandrov @ 2021-01-18 11:53 UTC (permalink / raw)
  To: Joachim Wiberg, Horatiu Vultur, davem, kuba, roopa,
	allan.nielsen, netdev, linux-kernel, bridge

On 16/01/2021 17:39, Joachim Wiberg wrote:
> On Wed, Jan 13, 2021 at 14:15, Nikolay Aleksandrov <nikolay@nvidia.com> wrote:
>> On 12/01/2021 15:59, Horatiu Vultur wrote:
>>> Based on the comments of the previous version, we started to work on a
>>> new version, so it would be possible to enable/disable queries per vlan.
>>> [snip]
>>> We were wondering if this what you had in mind when you proposed to have
>>> this per vlan? Or we are completely off? Or we should fix some of the
>>> issues that I mentioned, before you can see more clearly the direction?
>> No, unfortunately not even close. We already have per-port per-vlan and global per-vlan
>> contexts which are also linked together for each vlan, those must be used for any vlan
>> configuration and state. The problem is that you'd have to mix igmp and vlan code and
>> those two live under two different kconfig options, and worse rely on different locks, so
>> extra care must be taken.
>> [snip]
>> If you don't need this asap, I'll probably get to it in two months
>> after EHT and the new bridge flush api, even we are still carrying an out-of-tree patch
>> for this which someone (not from cumulus) tried to upstream a few years back, but it also has
>> wrong design in general. :)
> 
> Hi,
> 
> very interesting thread this!  I believe I may be the one who posted the
> patch[1] a few years ago, and I fully agree with Nik here.  We developed
> the basic concepts further at Westermo, but it's been really difficult
> to get it stable.
> 
> We have discussed at length at work if an IGMP snooping implementation
> really belongs in the bridge, or if it's better suited as a user space
> daemon?  Similar to what was decided for RSTP/MSTP support, i.e., the
> bridge only has STP and RSTP/MSTP is handled by mstpd[2].
> 
> Most of what's required for a user space implementation is available,
> but it would've been nice if a single AF_PACKET socket on br0 could be
> used to catch what brport (ifindex) a query or report comes in on.  As
> it is now that information is lost/replaced with the ifindex of br0.
> And then there's the issue of detecting and forwarding to a multicast
> routing daemon on top of br0.  That br0 is not a brport in the MDB, or
> that host_joined cannot be set/seen with iproute2 is quite limiting.
> These issues can of course be addressed, but are they of interest to
> the community at large?
> 
> 
> Best regards
>  /Joachim
> 
> [1]: https://lore.kernel.org/netdev/20180418120713.GA10742@troglobit/
> [2]: https://github.com/mstpd/mstpd
> 

Hi Joachim,
I actually had started implementing IGMPv3/MLDv2 as a user-space daemon part of
FRRouting (since it already has a lot of the required infra to talk to the kernel).
It also has IGMPv3/MLDv2 support within pimd, so a lot of code can be shared.
Obviously there are pros and cons to each choice, but I'd be interested to see a
full user-space implementation. I decided to make the kernel support more complete
since it already did IGMPv2 and so stopped with the new FRR daemon. If needed I'd be
happy to help with the kernel support for a new user-space daemon, and also can
contribute to the daemon itself if time permits.

Thanks,
 Nik


^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [RFC PATCH v2] net: bridge: igmp: Extend IGMP query to be per vlan
  2021-01-18 11:53       ` [Bridge] " Nikolay Aleksandrov
@ 2021-01-22 16:05         ` Joachim Wiberg
  -1 siblings, 0 replies; 16+ messages in thread
From: Joachim Wiberg @ 2021-01-22 16:05 UTC (permalink / raw)
  To: Nikolay Aleksandrov, Horatiu Vultur, davem, kuba, roopa,
	allan.nielsen, netdev, linux-kernel, bridge

On Mon, Jan 18, 2021 at 13:53, Nikolay Aleksandrov <nikolay@nvidia.com> wrote:
> On 16/01/2021 17:39, Joachim Wiberg wrote:
>> We have discussed at length at work if an IGMP snooping implementation
>> really belongs in the bridge, or if it's better suited as a user space
>> daemon?  Similar to what was decided for RSTP/MSTP support, i.e., the
>> bridge only has STP and RSTP/MSTP is handled by mstpd[2].
>> 
>> Most of what's required for a user space implementation is available,
>> but it would've been nice if a single AF_PACKET socket on br0 could be
>> used to catch what brport (ifindex) a query or report comes in on.  As
>> it is now that information is lost/replaced with the ifindex of br0.
>> And then there's the issue of detecting and forwarding to a multicast
>> routing daemon on top of br0.  That br0 is not a brport in the MDB, or
>> that host_joined cannot be set/seen with iproute2 is quite limiting.
>> These issues can of course be addressed, but are they of interest to
>> the community at large?
>> 
>> [1]: https://lore.kernel.org/netdev/20180418120713.GA10742@troglobit/
>> [2]: https://github.com/mstpd/mstpd

Ni Nik,

> I actually had started implementing IGMPv3/MLDv2 as a user-space daemon part of
> FRRouting (since it already has a lot of the required infra to talk to the kernel).
> It also has IGMPv3/MLDv2 support within pimd, so a lot of code can be shared.

Interesting!  Glad to hear other people have had similar ideas :)

> Obviously there are pros and cons to each choice, but I'd be interested to see a
> full user-space implementation. I decided to make the kernel support more complete
> since it already did IGMPv2 and so stopped with the new FRR daemon.

Yeah it's difficult to find the right cut-off for when it'll be more
useful to do it all in userspace.  For us I think it was the combination
of having many VLANs and wanting full querier support, i.e., not having
any multicast router available.  When we had to go dumpster diving for
useful IP address in IGMP queries on higher-level VLAN interfaces.

> If needed I'd be happy to help with the kernel support for a new
> user-space daemon, and also can contribute to the daemon itself if
> time permits.

That's good to know.  I think I'll start breathing life into a small
IGMP-only (for now) userspace daemon and see where it leads.  I need
it both for work and for all the various multicast routing projects
I maintain on my spare time.

Would it be OK to send questions regarding issues interfacing with the
bridge and updates/progress to this list for such a project?

Best regards
 /Joachim

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [Bridge] [RFC PATCH v2] net: bridge: igmp: Extend IGMP query to be per vlan
@ 2021-01-22 16:05         ` Joachim Wiberg
  0 siblings, 0 replies; 16+ messages in thread
From: Joachim Wiberg @ 2021-01-22 16:05 UTC (permalink / raw)
  To: Nikolay Aleksandrov, Horatiu Vultur, davem, kuba, roopa,
	allan.nielsen, netdev, linux-kernel, bridge

On Mon, Jan 18, 2021 at 13:53, Nikolay Aleksandrov <nikolay@nvidia.com> wrote:
> On 16/01/2021 17:39, Joachim Wiberg wrote:
>> We have discussed at length at work if an IGMP snooping implementation
>> really belongs in the bridge, or if it's better suited as a user space
>> daemon?  Similar to what was decided for RSTP/MSTP support, i.e., the
>> bridge only has STP and RSTP/MSTP is handled by mstpd[2].
>> 
>> Most of what's required for a user space implementation is available,
>> but it would've been nice if a single AF_PACKET socket on br0 could be
>> used to catch what brport (ifindex) a query or report comes in on.  As
>> it is now that information is lost/replaced with the ifindex of br0.
>> And then there's the issue of detecting and forwarding to a multicast
>> routing daemon on top of br0.  That br0 is not a brport in the MDB, or
>> that host_joined cannot be set/seen with iproute2 is quite limiting.
>> These issues can of course be addressed, but are they of interest to
>> the community at large?
>> 
>> [1]: https://lore.kernel.org/netdev/20180418120713.GA10742@troglobit/
>> [2]: https://github.com/mstpd/mstpd

Ni Nik,

> I actually had started implementing IGMPv3/MLDv2 as a user-space daemon part of
> FRRouting (since it already has a lot of the required infra to talk to the kernel).
> It also has IGMPv3/MLDv2 support within pimd, so a lot of code can be shared.

Interesting!  Glad to hear other people have had similar ideas :)

> Obviously there are pros and cons to each choice, but I'd be interested to see a
> full user-space implementation. I decided to make the kernel support more complete
> since it already did IGMPv2 and so stopped with the new FRR daemon.

Yeah it's difficult to find the right cut-off for when it'll be more
useful to do it all in userspace.  For us I think it was the combination
of having many VLANs and wanting full querier support, i.e., not having
any multicast router available.  When we had to go dumpster diving for
useful IP address in IGMP queries on higher-level VLAN interfaces.

> If needed I'd be happy to help with the kernel support for a new
> user-space daemon, and also can contribute to the daemon itself if
> time permits.

That's good to know.  I think I'll start breathing life into a small
IGMP-only (for now) userspace daemon and see where it leads.  I need
it both for work and for all the various multicast routing projects
I maintain on my spare time.

Would it be OK to send questions regarding issues interfacing with the
bridge and updates/progress to this list for such a project?

Best regards
 /Joachim

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [RFC PATCH v2] net: bridge: igmp: Extend IGMP query to be per vlan
@ 2021-01-13  8:43 kernel test robot
  0 siblings, 0 replies; 16+ messages in thread
From: kernel test robot @ 2021-01-13  8:43 UTC (permalink / raw)
  To: kbuild

[-- Attachment #1: Type: text/plain, Size: 4908 bytes --]

CC: kbuild-all(a)lists.01.org
In-Reply-To: <20210112135903.3730765-1-horatiu.vultur@microchip.com>
References: <20210112135903.3730765-1-horatiu.vultur@microchip.com>
TO: Horatiu Vultur <horatiu.vultur@microchip.com>

Hi Horatiu,

[FYI, it's a private test report for your RFC patch.]
[auto build test WARNING on net-next/master]
[also build test WARNING on net/master linus/master v5.11-rc3 next-20210113]
[cannot apply to sparc-next/master]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch]

url:    https://github.com/0day-ci/linux/commits/Horatiu-Vultur/net-bridge-igmp-Extend-IGMP-query-to-be-per-vlan/20210112-220655
base:   https://git.kernel.org/pub/scm/linux/kernel/git/davem/net-next.git c73a45965dd54a10c368191804b9de661eee1007
:::::: branch date: 19 hours ago
:::::: commit date: 19 hours ago
config: i386-randconfig-m021-20210113 (attached as .config)
compiler: gcc-9 (Debian 9.3.0-15) 9.3.0

If you fix the issue, kindly add following tag as appropriate
Reported-by: kernel test robot <lkp@intel.com>
Reported-by: Dan Carpenter <dan.carpenter@oracle.com>

smatch warnings:
net/bridge/br_multicast.c:3866 br_multicast_set_querier() error: we previously assumed 'query' could be null (see line 3850)
net/bridge/br_multicast.c:3866 br_multicast_set_querier() warn: maybe use && instead of &

vim +/query +3866 net/bridge/br_multicast.c

0912bda436388a02 Yotam Gigi     2017-10-09  3832  
8d72e3d611eb9514 Horatiu Vultur 2021-01-12  3833  int br_multicast_set_querier(struct net_bridge *br, unsigned long val, u16 vid)
c5c23260594c5701 Herbert Xu     2012-04-13  3834  {
8d72e3d611eb9514 Horatiu Vultur 2021-01-12  3835  	struct bridge_mcast_other_query *other_query;
8d72e3d611eb9514 Horatiu Vultur 2021-01-12  3836  	struct bridge_mcast_own_query *query;
8d72e3d611eb9514 Horatiu Vultur 2021-01-12  3837  	struct net_bridge_vlan_group *vg;
b00589af3b047363 Linus Lüssing  2013-08-01  3838  	unsigned long max_delay;
b00589af3b047363 Linus Lüssing  2013-08-01  3839  
c5c23260594c5701 Herbert Xu     2012-04-13  3840  	val = !!val;
c5c23260594c5701 Herbert Xu     2012-04-13  3841  
8d72e3d611eb9514 Horatiu Vultur 2021-01-12  3842  	if (vid == 0) {
8d72e3d611eb9514 Horatiu Vultur 2021-01-12  3843  		vg = br_vlan_group(br);
8d72e3d611eb9514 Horatiu Vultur 2021-01-12  3844  		if (vg)
8d72e3d611eb9514 Horatiu Vultur 2021-01-12  3845  			vid = vg->pvid;
8d72e3d611eb9514 Horatiu Vultur 2021-01-12  3846  	}
8d72e3d611eb9514 Horatiu Vultur 2021-01-12  3847  
c5c23260594c5701 Herbert Xu     2012-04-13  3848  	spin_lock_bh(&br->multicast_lock);
8d72e3d611eb9514 Horatiu Vultur 2021-01-12  3849  	query = br_mcast_find_own_query(&br->ip4_own_queries, vid);
8d72e3d611eb9514 Horatiu Vultur 2021-01-12 @3850  	if (!query) {
8d72e3d611eb9514 Horatiu Vultur 2021-01-12  3851  		if (br_vlan_enabled(br->dev))
c5c23260594c5701 Herbert Xu     2012-04-13  3852  			goto unlock;
c5c23260594c5701 Herbert Xu     2012-04-13  3853  
8d72e3d611eb9514 Horatiu Vultur 2021-01-12  3854  		br_mcast_add_queries(br, vid);
8d72e3d611eb9514 Horatiu Vultur 2021-01-12  3855  	}
8d72e3d611eb9514 Horatiu Vultur 2021-01-12  3856  
8d72e3d611eb9514 Horatiu Vultur 2021-01-12  3857  	other_query = br_mcast_find_other_query(&br->ip4_other_queries, vid);
8d72e3d611eb9514 Horatiu Vultur 2021-01-12  3858  	if (!other_query)
b00589af3b047363 Linus Lüssing  2013-08-01  3859  		goto unlock;
b00589af3b047363 Linus Lüssing  2013-08-01  3860  
8d72e3d611eb9514 Horatiu Vultur 2021-01-12  3861  	if (!val && query) {
8d72e3d611eb9514 Horatiu Vultur 2021-01-12  3862  		br_multicast_stop_querier(br, query, vid);
8d72e3d611eb9514 Horatiu Vultur 2021-01-12  3863  		goto unlock;
8d72e3d611eb9514 Horatiu Vultur 2021-01-12  3864  	}
b00589af3b047363 Linus Lüssing  2013-08-01  3865  
8d72e3d611eb9514 Horatiu Vultur 2021-01-12 @3866  	if (val & query->enabled)
8d72e3d611eb9514 Horatiu Vultur 2021-01-12  3867  		goto unlock;
cc0fdd802859eaeb Linus Lüssing  2013-08-30  3868  
8d72e3d611eb9514 Horatiu Vultur 2021-01-12  3869  	query->enabled = true;
8d72e3d611eb9514 Horatiu Vultur 2021-01-12  3870  
8d72e3d611eb9514 Horatiu Vultur 2021-01-12  3871  	max_delay = br->multicast_query_response_interval;
8d72e3d611eb9514 Horatiu Vultur 2021-01-12  3872  	if (!timer_pending(&other_query->timer))
8d72e3d611eb9514 Horatiu Vultur 2021-01-12  3873  		other_query->delay_time = jiffies + max_delay;
8d72e3d611eb9514 Horatiu Vultur 2021-01-12  3874  
8d72e3d611eb9514 Horatiu Vultur 2021-01-12  3875  	br_multicast_start_querier(br, query, vid);
cc0fdd802859eaeb Linus Lüssing  2013-08-30  3876  

---
0-DAY CI Kernel Test Service, Intel Corporation
https://lists.01.org/hyperkitty/list/kbuild-all(a)lists.01.org

[-- Attachment #2: config.gz --]
[-- Type: application/gzip, Size: 33916 bytes --]

^ permalink raw reply	[flat|nested] 16+ messages in thread

end of thread, other threads:[~2021-01-22 16:08 UTC | newest]

Thread overview: 16+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-01-12 13:59 [RFC PATCH v2] net: bridge: igmp: Extend IGMP query to be per vlan Horatiu Vultur
2021-01-12 13:59 ` [Bridge] " Horatiu Vultur
2021-01-13  1:21 ` kernel test robot
2021-01-13  9:21 ` Dan Carpenter
2021-01-13  9:21   ` [kbuild] " Dan Carpenter
2021-01-13 12:15 ` Nikolay Aleksandrov
2021-01-13 12:15   ` [Bridge] " Nikolay Aleksandrov
2021-01-13 17:02   ` Horatiu Vultur
2021-01-13 17:02     ` [Bridge] " Horatiu Vultur
2021-01-16 15:39   ` Joachim Wiberg
2021-01-16 15:39     ` [Bridge] " Joachim Wiberg
2021-01-18 11:53     ` Nikolay Aleksandrov
2021-01-18 11:53       ` [Bridge] " Nikolay Aleksandrov
2021-01-22 16:05       ` Joachim Wiberg
2021-01-22 16:05         ` [Bridge] " Joachim Wiberg
2021-01-13  8:43 kernel test robot

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.