From mboxrd@z Thu Jan 1 00:00:00 1970 From: Nikolay Aleksandrov Subject: Re: [RFC PATCH] net: bridge: multicast querier per VLAN support Date: Wed, 18 Apr 2018 15:31:57 +0300 Message-ID: References: <20180418120713.GA10742@troglobit> Mime-Version: 1.0 Content-Type: text/plain; charset=utf-8; format=flowed Content-Transfer-Encoding: 8bit Cc: Stephen Hemminger , roopa To: Joachim Nilsson , netdev@vger.kernel.org Return-path: Received: from mail-wr0-f196.google.com ([209.85.128.196]:33021 "EHLO mail-wr0-f196.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1752961AbeDRMcB (ORCPT ); Wed, 18 Apr 2018 08:32:01 -0400 Received: by mail-wr0-f196.google.com with SMTP id z73-v6so4377512wrb.0 for ; Wed, 18 Apr 2018 05:32:00 -0700 (PDT) In-Reply-To: <20180418120713.GA10742@troglobit> Content-Language: en-US Sender: netdev-owner@vger.kernel.org List-ID: On 18/04/18 15:07, Joachim Nilsson wrote: > This RFC patch¹ is an attempt to add multicast querier per VLAN support > to a VLAN aware bridge. I'm posting it as RFC for now since non-VLAN > aware bridges are not handled, and one of my questions is if that is > complexity we need to continue supporting? > > From what I understand, multicast join/report already support per VLAN > operation, and the MDB as well support filtering per VLAN, but queries > are currently limited to per-port operation on VLAN-aware bridges. > > The naive² approach of this patch relocates query timers from the bridge > to operate per VLAN, on timer expiry we send queries to all bridge ports > in the same VLAN. Tagged port members have tagged VLAN queries. > > Unlike the original patch¹, which uses a sysfs entry to set the querier > address of each VLAN, this use the IP address of the VLAN interface when > initiating a per VLAN query. A version of inet_select_addr() is used > for this, called inet_select_dev_addr(), not included in this patch. > > Open questions/TODO: > > - First of all, is this patch useful to anyone Obviously to us as it's based on our patch. :-) We actually recently discussed what will be needed to make it acceptable to upstream. > - The current br_multicast.c is very complex. The support for both IPv4 > and IPv6 is a no-brainer, but it also has #ifdef VLAN_FILTERING and > 'br->vlan_enabled' ... this has likely been discussed before, but if > we could remove those code paths I believe what's left would be quite > a bit easier to read and maintain. br->vlan_enabled has a wrapper that can be used without ifdefs, as does br_vlan_find() so in short - you can remove the ifdefs and use the wrappers, they'll degrade to always false/null when vlans are disabled. > - Many per-bridge specific multicast sysfs settings may need to have a > corresponding per-VLAN setting, e.g. snooping, query_interval, etc. > How should we go about that? (For status reporting I have a proposal) We'll have to add more to the per-vlan context, but yes it has to happen. It will be only netlink interface for config/retrieval, no sysfs. > - Dito per-port specific multicast sysfs settings, e.g. multicast_router I'm not sure I follow this one, there is per-port mcast router config now ? Take a look at br_multicast_set_port_router(). > - The MLD support has been kept in sync with the rest but is completely > untested. In particular I suspect the wrong source IP will be used. > > ¹) Initially based on a patch by Cumulus Networks > http://repo3.cumulusnetworks.com/repo/pool/cumulus/l/linux/linux-source-4.1_4.1.33-1+cl3u11_all.deb I knew this looked familiar when I glanced through it :) > ²) This patch is currently limited to work only on bridges with VLAN > enabled. Care has been taken to support MLD snooping, but it is > completely untested. > > Thank you for reading this far! > > Signed-off-by: Joachim Nilsson Thanks for the effort, I see that you have done some of the required cleanups for this to be upstreamable, but as you've noted above we need to make it complete (with the per-vlan contexts and all). I will review this patch in detail later and come back if there's anything. Cheers, Nik > --- > net/bridge/br_device.c | 2 +- > net/bridge/br_input.c | 2 +- > net/bridge/br_multicast.c | 456 ++++++++++++++++++++++++-------------- > net/bridge/br_private.h | 38 +++- > net/bridge/br_stp.c | 5 +- > net/bridge/br_vlan.c | 3 + > 6 files changed, 327 insertions(+), 179 deletions(-) > > diff --git a/net/bridge/br_device.c b/net/bridge/br_device.c > index 02f9f8aab047..ba35485032d8 100644 > --- a/net/bridge/br_device.c > +++ b/net/bridge/br_device.c > @@ -98,7 +98,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))) > + br_multicast_querier_exists(br, vid, eth_hdr(skb))) > 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 56bb9189c374..13d48489e0e1 100644 > --- a/net/bridge/br_input.c > +++ b/net/bridge/br_input.c > @@ -137,7 +137,7 @@ int br_handle_frame_finish(struct net *net, struct sock *sk, struct sk_buff *skb > mdst = br_mdb_get(br, skb, vid); > if ((mdst && mdst->addr.proto == htons(ETH_P_ALL)) || > ((mdst || BR_INPUT_SKB_CB_MROUTERS_ONLY(skb)) && > - br_multicast_querier_exists(br, eth_hdr(skb)))) { > + br_multicast_querier_exists(br, vid, eth_hdr(skb)))) { > 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 277ecd077dc4..72e47d500972 100644 > --- a/net/bridge/br_multicast.c > +++ b/net/bridge/br_multicast.c > @@ -13,6 +13,7 @@ > #include > #include > #include > +#include > #include > #include > #include > @@ -37,7 +38,7 @@ > > #include "br_private.h" > > -static void br_multicast_start_querier(struct net_bridge *br, > +static void br_multicast_start_querier(struct net_bridge_vlan *vlan, > struct bridge_mcast_own_query *query); > static void br_multicast_add_router(struct net_bridge *br, > struct net_bridge_port *port); > @@ -46,13 +47,14 @@ static void br_ip4_multicast_leave_group(struct net_bridge *br, > __be32 group, > __u16 vid, > const unsigned char *src); > - > +static void br_ip4_multicast_query_expired(struct timer_list *t); > static void __del_port_router(struct net_bridge_port *p); > #if IS_ENABLED(CONFIG_IPV6) > static void br_ip6_multicast_leave_group(struct net_bridge *br, > struct net_bridge_port *port, > const struct in6_addr *group, > __u16 vid, const unsigned char *src); > +static void br_ip6_multicast_query_expired(struct timer_list *t); > #endif > unsigned int br_mdb_rehash_seq; > > @@ -381,8 +383,30 @@ static int br_mdb_rehash(struct net_bridge_mdb_htable __rcu **mdbp, int max, > return 0; > } > > +__be32 br_multicast_inet_addr(struct net_bridge *br, u16 vid) > +{ > + struct net_device *dev; > + > + if (!br->multicast_query_use_ifaddr) > + return 0; > + > + if (!vid) > + return inet_select_addr(br->dev, 0, RT_SCOPE_LINK); > + > + rcu_read_lock(); > + dev = __vlan_find_dev_deep_rcu(br->dev, htons(ETH_P_8021Q), vid); > + rcu_read_unlock(); > + > + if (!dev) > + return 0; > + > + return inet_select_dev_addr(dev, 0, RT_SCOPE_LINK); > +} > + > static struct sk_buff *br_ip4_multicast_alloc_query(struct net_bridge *br, > __be32 group, > + __u16 vid, > + bool tagged, > u8 *igmp_type) > { > struct igmpv3_query *ihv3; > @@ -391,12 +415,17 @@ static struct sk_buff *br_ip4_multicast_alloc_query(struct net_bridge *br, > struct igmphdr *ih; > struct ethhdr *eth; > struct iphdr *iph; > + int vh_size = 0; > + > + /* if vid is non-zero, insert the 1Q header also */ > + if (vid && tagged) > + vh_size = sizeof(struct vlan_hdr); > > igmp_hdr_size = sizeof(*ih); > if (br->multicast_igmp_version == 3) > igmp_hdr_size = sizeof(*ihv3); > skb = netdev_alloc_skb_ip_align(br->dev, sizeof(*eth) + sizeof(*iph) + > - igmp_hdr_size + 4); > + vh_size + igmp_hdr_size + 4); > if (!skb) > goto out; > > @@ -415,6 +444,15 @@ static struct sk_buff *br_ip4_multicast_alloc_query(struct net_bridge *br, > eth->h_proto = htons(ETH_P_IP); > skb_put(skb, sizeof(*eth)); > > + if (vid && tagged) { > + skb = vlan_insert_tag_set_proto(skb, htons(ETH_P_8021Q), vid); > + if (!skb) { > + kfree_skb(skb); > + br_err(br, "Failed adding VLAN tag to IGMP query, vid:%d\n", vid); > + return NULL; > + } > + } > + > skb_set_network_header(skb, skb->len); > iph = ip_hdr(skb); > > @@ -426,8 +464,7 @@ static struct sk_buff *br_ip4_multicast_alloc_query(struct net_bridge *br, > iph->frag_off = htons(IP_DF); > iph->ttl = 1; > iph->protocol = IPPROTO_IGMP; > - iph->saddr = br->multicast_query_use_ifaddr ? > - inet_select_addr(br->dev, 0, RT_SCOPE_LINK) : 0; > + iph->saddr = br_multicast_inet_addr(br, vid); > iph->daddr = htonl(INADDR_ALLHOSTS_GROUP); > ((u8 *)&iph[1])[0] = IPOPT_RA; > ((u8 *)&iph[1])[1] = 4; > @@ -477,6 +514,8 @@ static struct sk_buff *br_ip4_multicast_alloc_query(struct net_bridge *br, > #if IS_ENABLED(CONFIG_IPV6) > static struct sk_buff *br_ip6_multicast_alloc_query(struct net_bridge *br, > const struct in6_addr *grp, > + __u16 vid, > + bool tagged, > u8 *igmp_type) > { > struct mld2_query *mld2q; > @@ -486,13 +525,18 @@ static struct sk_buff *br_ip6_multicast_alloc_query(struct net_bridge *br, > size_t mld_hdr_size; > struct sk_buff *skb; > struct ethhdr *eth; > + int vh_size = 0; > u8 *hopopt; > > + /* if vid is non-zero, insert the 1Q header also */ > + if (vid && tagged) > + vh_size = sizeof(struct vlan_hdr); > + > mld_hdr_size = sizeof(*mldq); > if (br->multicast_mld_version == 2) > mld_hdr_size = sizeof(*mld2q); > skb = netdev_alloc_skb_ip_align(br->dev, sizeof(*eth) + sizeof(*ip6h) + > - 8 + mld_hdr_size); > + vh_size + 8 + mld_hdr_size); > if (!skb) > goto out; > > @@ -506,6 +550,15 @@ static struct sk_buff *br_ip6_multicast_alloc_query(struct net_bridge *br, > eth->h_proto = htons(ETH_P_IPV6); > skb_put(skb, sizeof(*eth)); > > + if (vid && tagged) { > + skb = vlan_insert_tag_set_proto(skb, htons(ETH_P_8021Q), vid); > + if (!skb) { > + kfree_skb(skb); > + br_err(br, "Failed adding VLAN tag to MLD query, vid:%d\n", vid); > + return NULL; > + } > + } > + > /* IPv6 header + HbH option */ > skb_set_network_header(skb, skb->len); > ip6h = ipv6_hdr(skb); > @@ -590,15 +643,17 @@ static struct sk_buff *br_ip6_multicast_alloc_query(struct net_bridge *br, > > static struct sk_buff *br_multicast_alloc_query(struct net_bridge *br, > struct br_ip *addr, > + bool tagged, > u8 *igmp_type) > { > switch (addr->proto) { > case htons(ETH_P_IP): > - return br_ip4_multicast_alloc_query(br, addr->u.ip4, igmp_type); > + return br_ip4_multicast_alloc_query(br, addr->u.ip4, addr->vid, > + tagged, igmp_type); > #if IS_ENABLED(CONFIG_IPV6) > case htons(ETH_P_IPV6): > - return br_ip6_multicast_alloc_query(br, &addr->u.ip6, > - igmp_type); > + return br_ip6_multicast_alloc_query(br, &addr->u.ip6, addr->vid, > + tagged, igmp_type); > #endif > } > return NULL; > @@ -905,14 +960,16 @@ static void br_multicast_local_router_expired(struct timer_list *t) > spin_unlock(&br->multicast_lock); > } > > -static void br_multicast_querier_expired(struct net_bridge *br, > +static void br_multicast_querier_expired(struct net_bridge_vlan *vlan, > struct bridge_mcast_own_query *query) > { > + struct net_bridge *br = vlan->br; > + > spin_lock(&br->multicast_lock); > if (!netif_running(br->dev) || br->multicast_disabled) > goto out; > > - br_multicast_start_querier(br, query); > + br_multicast_start_querier(vlan, query); > > out: > spin_unlock(&br->multicast_lock); > @@ -920,17 +977,17 @@ 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 net_bridge_vlan *v = from_timer(v, t, ip4_other_query.timer); > > - br_multicast_querier_expired(br, &br->ip4_own_query); > + br_multicast_querier_expired(v, &v->ip4_own_query); > } > > #if IS_ENABLED(CONFIG_IPV6) > static void br_ip6_multicast_querier_expired(struct timer_list *t) > { > - struct net_bridge *br = from_timer(br, t, ip6_other_query.timer); > + struct net_bridge_vlan *v = from_timer(v, t, ip6_other_query.timer); > > - br_multicast_querier_expired(br, &br->ip6_own_query); > + br_multicast_querier_expired(v, &v->ip6_own_query); > } > #endif > > @@ -938,11 +995,17 @@ static void br_multicast_select_own_querier(struct net_bridge *br, > struct br_ip *ip, > struct sk_buff *skb) > { > + struct net_bridge_vlan *v; > + > + v = br_vlan_find(br_vlan_group(br), ip->vid); > + if (!v) > + return; > + > if (ip->proto == htons(ETH_P_IP)) > - br->ip4_querier.addr.u.ip4 = ip_hdr(skb)->saddr; > + v->ip4_querier.addr.u.ip4 = ip_hdr(skb)->saddr; > #if IS_ENABLED(CONFIG_IPV6) > else > - br->ip6_querier.addr.u.ip6 = ipv6_hdr(skb)->saddr; > + v->ip6_querier.addr.u.ip6 = ipv6_hdr(skb)->saddr; > #endif > } > > @@ -951,9 +1014,27 @@ static void __br_multicast_send_query(struct net_bridge *br, > struct br_ip *ip) > { > struct sk_buff *skb; > + bool tagged = false; > u8 igmp_type; > > - skb = br_multicast_alloc_query(br, ip, &igmp_type); > + if (port->state == BR_STATE_DISABLED || > + port->state == BR_STATE_BLOCKING) > + return; > + > +#ifdef CONFIG_BRIDGE_VLAN_FILTERING > + if (port && ip->vid) { > + struct net_bridge_vlan *v; > + > + v = br_vlan_find(nbp_vlan_group_rcu(port), ip->vid); > + if (!br->vlan_enabled || !v) > + return; > + > + if (!(v->flags & BRIDGE_VLAN_INFO_UNTAGGED)) > + tagged = true; > + } > +#endif > + > + skb = br_multicast_alloc_query(br, ip, tagged, &igmp_type); > if (!skb) > return; > > @@ -972,11 +1053,12 @@ static void __br_multicast_send_query(struct net_bridge *br, > } > } > > -static void br_multicast_send_query(struct net_bridge *br, > +static void br_multicast_send_query(struct net_bridge_vlan *vlan, > struct net_bridge_port *port, > struct bridge_mcast_own_query *own_query) > { > struct bridge_mcast_other_query *other_query = NULL; > + struct net_bridge *br = vlan->br; > struct br_ip br_group; > unsigned long time; > > @@ -985,22 +1067,27 @@ static void br_multicast_send_query(struct net_bridge *br, > return; > > memset(&br_group.u, 0, sizeof(br_group.u)); > - > - if (port ? (own_query == &port->ip4_own_query) : > - (own_query == &br->ip4_own_query)) { > - other_query = &br->ip4_other_query; > + br_group.vid = vlan->vid; > + if (own_query == &vlan->ip4_own_query) { > + other_query = &vlan->ip4_other_query; > br_group.proto = htons(ETH_P_IP); > #if IS_ENABLED(CONFIG_IPV6) > } else { > - other_query = &br->ip6_other_query; > + other_query = &vlan->ip6_other_query; > br_group.proto = htons(ETH_P_IPV6); > #endif > } > > + if (port) { > + __br_multicast_send_query(br, port, &br_group); > + return; > + } > + > if (!other_query || timer_pending(&other_query->timer)) > return; > > - __br_multicast_send_query(br, port, &br_group); > + list_for_each_entry(port, &br->port_list, list) > + __br_multicast_send_query(br, port, &br_group); > > time = jiffies; > time += own_query->startup_sent < br->multicast_startup_query_count ? > @@ -1009,42 +1096,6 @@ static void br_multicast_send_query(struct net_bridge *br, > mod_timer(&own_query->timer, time); > } > > -static void > -br_multicast_port_query_expired(struct net_bridge_port *port, > - struct bridge_mcast_own_query *query) > -{ > - struct net_bridge *br = port->br; > - > - spin_lock(&br->multicast_lock); > - if (port->state == BR_STATE_DISABLED || > - port->state == BR_STATE_BLOCKING) > - goto out; > - > - if (query->startup_sent < br->multicast_startup_query_count) > - query->startup_sent++; > - > - br_multicast_send_query(port->br, port, query); > - > -out: > - spin_unlock(&br->multicast_lock); > -} > - > -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); > - > - br_multicast_port_query_expired(port, &port->ip4_own_query); > -} > - > -#if IS_ENABLED(CONFIG_IPV6) > -static void br_ip6_multicast_port_query_expired(struct timer_list *t) > -{ > - struct net_bridge_port *port = from_timer(port, t, ip6_own_query.timer); > - > - br_multicast_port_query_expired(port, &port->ip6_own_query); > -} > -#endif > - > static void br_mc_disabled_update(struct net_device *dev, bool value) > { > struct switchdev_attr attr = { > @@ -1063,12 +1114,6 @@ 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); > -#if IS_ENABLED(CONFIG_IPV6) > - timer_setup(&port->ip6_own_query.timer, > - br_ip6_multicast_port_query_expired, 0); > -#endif > br_mc_disabled_update(port->dev, port->br->multicast_disabled); > > port->mcast_stats = netdev_alloc_pcpu_stats(struct bridge_mcast_stats); > @@ -1109,15 +1154,47 @@ static void __br_multicast_enable_port(struct net_bridge_port *port) > if (br->multicast_disabled || !netif_running(br->dev)) > return; > > - br_multicast_enable(&port->ip4_own_query); > -#if IS_ENABLED(CONFIG_IPV6) > - br_multicast_enable(&port->ip6_own_query); > -#endif > if (port->multicast_router == MDB_RTR_TYPE_PERM && > hlist_unhashed(&port->rlist)) > br_multicast_add_router(br, port); > } > > +static void __br_multicast_vlan_init(struct net_bridge_vlan *vlan) > +{ > + vlan->ip4_querier.port = NULL; > + vlan->ip4_other_query.delay_time = 0; > + > + timer_setup(&vlan->ip4_other_query.timer, > + br_ip4_multicast_querier_expired, 0); > + timer_setup(&vlan->ip4_own_query.timer, > + br_ip4_multicast_query_expired, 0); > + > +#if IS_ENABLED(CONFIG_IPV6) > + vlan->ip6_querier.port = NULL; > + vlan->ip6_other_query.delay_time = 0; > + timer_setup(&vlan->ip6_other_query.timer, > + br_ip6_multicast_querier_expired, 0); > + timer_setup(&vlan->ip6_own_query.timer, > + br_ip6_multicast_query_expired, 0); > + #endif > +} > + > +void br_multicast_enable_vlan(struct net_bridge *br, u16 vid) > +{ > + struct net_bridge_vlan *v; > + > + v = br_vlan_find(br_vlan_group(br), vid); > + if (!v) > + return; > + > + __br_multicast_vlan_init(v); > + br_multicast_enable(&v->ip4_own_query); > +#if IS_ENABLED(CONFIG_IPV6) > + br_multicast_enable(&v->ip6_own_query); > +#endif > +} > + > +/* called by stp to enable timers, only use it to enable router port? -jnn */ > void br_multicast_enable_port(struct net_bridge_port *port) > { > struct net_bridge *br = port->br; > @@ -1127,6 +1204,7 @@ void br_multicast_enable_port(struct net_bridge_port *port) > spin_unlock(&br->multicast_lock); > } > > +/* called by stp_if */ > void br_multicast_disable_port(struct net_bridge_port *port) > { > struct net_bridge *br = port->br; > @@ -1139,12 +1217,6 @@ void br_multicast_disable_port(struct net_bridge_port *port) > br_multicast_del_pg(br, pg); > > __del_port_router(port); > - > - del_timer(&port->multicast_router_timer); > - del_timer(&port->ip4_own_query.timer); > -#if IS_ENABLED(CONFIG_IPV6) > - del_timer(&port->ip6_own_query.timer); > -#endif > spin_unlock(&br->multicast_lock); > } > > @@ -1283,65 +1355,66 @@ static int br_ip6_multicast_mld2_report(struct net_bridge *br, > } > #endif > > -static bool br_ip4_multicast_select_querier(struct net_bridge *br, > +static bool br_ip4_multicast_select_querier(struct net_bridge_vlan *vlan, > struct net_bridge_port *port, > __be32 saddr) > { > - if (!timer_pending(&br->ip4_own_query.timer) && > - !timer_pending(&br->ip4_other_query.timer)) > + > + if (!timer_pending(&vlan->ip4_own_query.timer) && > + !timer_pending(&vlan->ip4_other_query.timer)) > goto update; > > - if (!br->ip4_querier.addr.u.ip4) > + if (!vlan->ip4_querier.addr.u.ip4) > goto update; > > - if (ntohl(saddr) <= ntohl(br->ip4_querier.addr.u.ip4)) > + if (ntohl(saddr) <= ntohl(vlan->ip4_querier.addr.u.ip4)) > goto update; > > return false; > > update: > - br->ip4_querier.addr.u.ip4 = saddr; > + vlan->ip4_querier.addr.u.ip4 = saddr; > > /* update protected by general multicast_lock by caller */ > - rcu_assign_pointer(br->ip4_querier.port, port); > + rcu_assign_pointer(vlan->ip4_querier.port, port); > > return true; > } > > #if IS_ENABLED(CONFIG_IPV6) > -static bool br_ip6_multicast_select_querier(struct net_bridge *br, > +static bool br_ip6_multicast_select_querier(struct net_bridge_vlan *vlan, > struct net_bridge_port *port, > struct in6_addr *saddr) > { > - if (!timer_pending(&br->ip6_own_query.timer) && > - !timer_pending(&br->ip6_other_query.timer)) > + if (!timer_pending(&vlan->ip6_own_query.timer) && > + !timer_pending(&vlan->ip6_other_query.timer)) > goto update; > > - if (ipv6_addr_cmp(saddr, &br->ip6_querier.addr.u.ip6) <= 0) > + if (ipv6_addr_cmp(saddr, &vlan->ip6_querier.addr.u.ip6) <= 0) > goto update; > > return false; > > update: > - br->ip6_querier.addr.u.ip6 = *saddr; > + vlan->ip6_querier.addr.u.ip6 = *saddr; > > /* update protected by general multicast_lock by caller */ > - rcu_assign_pointer(br->ip6_querier.port, port); > + rcu_assign_pointer(vlan->ip6_querier.port, port); > > return true; > } > #endif > > -static bool br_multicast_select_querier(struct net_bridge *br, > +static bool br_multicast_select_querier(struct net_bridge_vlan *vlan, > struct net_bridge_port *port, > struct br_ip *saddr) > { > switch (saddr->proto) { > case htons(ETH_P_IP): > - return br_ip4_multicast_select_querier(br, port, saddr->u.ip4); > + return br_ip4_multicast_select_querier(vlan, port, saddr->u.ip4); > #if IS_ENABLED(CONFIG_IPV6) > case htons(ETH_P_IPV6): > - return br_ip6_multicast_select_querier(br, port, &saddr->u.ip6); > + return br_ip6_multicast_select_querier(vlan, port, &saddr->u.ip6); > #endif > } > > @@ -1425,17 +1498,17 @@ static void br_multicast_mark_router(struct net_bridge *br, > now + br->multicast_querier_interval); > } > > -static void br_multicast_query_received(struct net_bridge *br, > +static void br_multicast_query_received(struct net_bridge_vlan *vlan, > struct net_bridge_port *port, > struct bridge_mcast_other_query *query, > struct br_ip *saddr, > unsigned long max_delay) > { > - if (!br_multicast_select_querier(br, port, saddr)) > + if (!br_multicast_select_querier(vlan, port, saddr)) > return; > > - br_multicast_update_query_timer(br, query, max_delay); > - br_multicast_mark_router(br, port); > + br_multicast_update_query_timer(vlan->br, query, max_delay); > + br_multicast_mark_router(vlan->br, port); > } > > static int br_ip4_multicast_query(struct net_bridge *br, > @@ -1482,10 +1555,17 @@ static int br_ip4_multicast_query(struct net_bridge *br, > } > > if (!group) { > + struct net_bridge_vlan *v; > + > + v = br_vlan_find(br_vlan_group(br), vid); > + if (!v) > + goto out; > + > saddr.proto = htons(ETH_P_IP); > + saddr.vid = vid; > saddr.u.ip4 = iph->saddr; > > - br_multicast_query_received(br, port, &br->ip4_other_query, > + br_multicast_query_received(v, port, &v->ip4_other_query, > &saddr, max_delay); > goto out; > } > @@ -1565,10 +1645,17 @@ static int br_ip6_multicast_query(struct net_bridge *br, > is_general_query = group && ipv6_addr_any(group); > > if (is_general_query) { > + struct net_bridge_vlan *v; > + > + v = br_vlan_find(br_vlan_group(br), vid); > + if (!v) > + goto out; > + > saddr.proto = htons(ETH_P_IPV6); > + saddr.vid = vid; > saddr.u.ip6 = ip6h->saddr; > > - br_multicast_query_received(br, port, &br->ip6_other_query, > + br_multicast_query_received(v, port, &v->ip6_other_query, > &saddr, max_delay); > goto out; > } else if (!group) { > @@ -1716,20 +1803,22 @@ static void br_ip4_multicast_leave_group(struct net_bridge *br, > __u16 vid, > const unsigned char *src) > { > + struct net_bridge_vlan *v; > struct br_ip br_group; > - struct bridge_mcast_own_query *own_query; > > if (ipv4_is_local_multicast(group)) > return; > > - own_query = port ? &port->ip4_own_query : &br->ip4_own_query; > + v = br_vlan_find(br_vlan_group(br), vid); > + if (!v) > + return; > > br_group.u.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); > + br_multicast_leave_group(br, port, &br_group, &v->ip4_other_query, > + &v->ip4_own_query, src); > } > > #if IS_ENABLED(CONFIG_IPV6) > @@ -1739,20 +1828,22 @@ static void br_ip6_multicast_leave_group(struct net_bridge *br, > __u16 vid, > const unsigned char *src) > { > + struct net_bridge_vlan *v; > struct br_ip br_group; > - struct bridge_mcast_own_query *own_query; > > if (ipv6_addr_is_ll_all_nodes(group)) > return; > > - own_query = port ? &port->ip6_own_query : &br->ip6_own_query; > + v = br_vlan_find(br_vlan_group(br), vid); > + if (!v) > + return; > > br_group.u.ip6 = *group; > br_group.proto = htons(ETH_P_IPV6); > br_group.vid = vid; > > - br_multicast_leave_group(br, port, &br_group, &br->ip6_other_query, > - own_query, src); > + br_multicast_leave_group(br, port, &br_group, &v->ip6_other_query, > + &v->ip6_own_query, src); > } > #endif > > @@ -1938,37 +2029,42 @@ int br_multicast_rcv(struct net_bridge *br, struct net_bridge_port *port, > return ret; > } > > -static void br_multicast_query_expired(struct net_bridge *br, > +static void br_multicast_query_expired(struct net_bridge_vlan *vlan, > struct bridge_mcast_own_query *query, > struct bridge_mcast_querier *querier) > { > + struct net_bridge *br = vlan->br; > + > spin_lock(&br->multicast_lock); > if (query->startup_sent < br->multicast_startup_query_count) > query->startup_sent++; > > RCU_INIT_POINTER(querier->port, NULL); > - br_multicast_send_query(br, NULL, query); > + br_multicast_send_query(vlan, NULL, query); > spin_unlock(&br->multicast_lock); > } > > static void br_ip4_multicast_query_expired(struct timer_list *t) > { > - struct net_bridge *br = from_timer(br, t, ip4_own_query.timer); > + struct net_bridge_vlan *v = from_timer(v, t, ip4_own_query.timer); > > - br_multicast_query_expired(br, &br->ip4_own_query, &br->ip4_querier); > + br_multicast_query_expired(v, &v->ip4_own_query, &v->ip4_querier); > } > > #if IS_ENABLED(CONFIG_IPV6) > static void br_ip6_multicast_query_expired(struct timer_list *t) > { > - struct net_bridge *br = from_timer(br, t, ip6_own_query.timer); > + struct net_bridge_vlan *v = from_timer(v, t, ip6_own_query.timer); > > - br_multicast_query_expired(br, &br->ip6_own_query, &br->ip6_querier); > + br_multicast_query_expired(v, &v->ip6_own_query, &v->ip6_querier); > } > #endif > > void br_multicast_init(struct net_bridge *br) > { > + struct net_bridge_vlan_group *vg; > + struct net_bridge_vlan *v; > + > br->hash_elasticity = 4; > br->hash_max = 512; > > @@ -1985,29 +2081,22 @@ 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) > br->multicast_mld_version = 1; > - br->ip6_other_query.delay_time = 0; > - br->ip6_querier.port = NULL; > #endif > br->has_ipv6_addr = 1; > > 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); > -#if IS_ENABLED(CONFIG_IPV6) > - timer_setup(&br->ip6_other_query.timer, > - br_ip6_multicast_querier_expired, 0); > - timer_setup(&br->ip6_own_query.timer, > - br_ip6_multicast_query_expired, 0); > -#endif > + > + vg = br_vlan_group(br); > + if (!vg || !vg->num_vlans) > + return; > + > + list_for_each_entry(v, &vg->vlan_list, vlist) > + __br_multicast_vlan_init(v); > } > > static void __br_multicast_open(struct net_bridge *br, > @@ -2023,21 +2112,41 @@ 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 net_bridge_vlan_group *vg; > + struct net_bridge_vlan *v; > + > + vg = br_vlan_group(br); > + if (!vg || !vg->num_vlans) > + return; > + > + list_for_each_entry(v, &vg->vlan_list, vlist) { > + __br_multicast_vlan_init(v); > + __br_multicast_open(br, &v->ip4_own_query); > #if IS_ENABLED(CONFIG_IPV6) > - __br_multicast_open(br, &br->ip6_own_query); > + __br_multicast_open(br, &v->ip6_own_query); > #endif > + } > } > > void br_multicast_stop(struct net_bridge *br) > { > + struct net_bridge_vlan_group *vg; > + struct net_bridge_vlan *v; > + > del_timer_sync(&br->multicast_router_timer); > - del_timer_sync(&br->ip4_other_query.timer); > - del_timer_sync(&br->ip4_own_query.timer); > + > + vg = br_vlan_group(br); > + if (!vg || !vg->num_vlans) > + return; > + > + list_for_each_entry(v, &vg->vlan_list, vlist) { > + del_timer_sync(&v->ip4_other_query.timer); > + del_timer_sync(&v->ip4_own_query.timer); > #if IS_ENABLED(CONFIG_IPV6) > - del_timer_sync(&br->ip6_other_query.timer); > - del_timer_sync(&br->ip6_own_query.timer); > + del_timer_sync(&v->ip6_other_query.timer); > + del_timer_sync(&v->ip6_own_query.timer); > #endif > + } > } > > void br_multicast_dev_del(struct net_bridge *br) > @@ -2162,25 +2271,37 @@ int br_multicast_set_port_router(struct net_bridge_port *p, unsigned long val) > return err; > } > > -static void br_multicast_start_querier(struct net_bridge *br, > +/* Must be called with multicast_lock */ > +static void br_multicast_init_querier(struct net_bridge_vlan *vlan, > + struct bridge_mcast_own_query *query, > + unsigned long max_delay) > +{ > + struct bridge_mcast_other_query *other_query = NULL; > + > + if (query == &vlan->ip4_own_query) > + other_query = &vlan->ip4_other_query; > + else > + other_query = &vlan->ip6_other_query; > + > + if (!timer_pending(&other_query->timer)) > + other_query->delay_time = jiffies + max_delay; > + > + br_multicast_start_querier(vlan, query); > +} > + > +static void br_multicast_start_querier(struct net_bridge_vlan *vlan, > struct bridge_mcast_own_query *query) > { > - struct net_bridge_port *port; > + struct net_bridge *br = vlan->br; > > __br_multicast_open(br, query); > > - list_for_each_entry(port, &br->port_list, list) { > - if (port->state == BR_STATE_DISABLED || > - port->state == BR_STATE_BLOCKING) > - continue; > - > - if (query == &br->ip4_own_query) > - br_multicast_enable(&port->ip4_own_query); > + if (query == &vlan->ip4_own_query) > + br_multicast_enable(&vlan->ip4_own_query); > #if IS_ENABLED(CONFIG_IPV6) > - else > - br_multicast_enable(&port->ip6_own_query); > + else > + br_multicast_enable(&vlan->ip6_own_query); > #endif > - } > } > > int br_multicast_toggle(struct net_bridge *br, unsigned long val) > @@ -2248,6 +2369,8 @@ EXPORT_SYMBOL_GPL(br_multicast_router); > > int br_multicast_set_querier(struct net_bridge *br, unsigned long val) > { > + struct net_bridge_vlan_group *vg; > + struct net_bridge_vlan *v; > unsigned long max_delay; > > val = !!val; > @@ -2260,19 +2383,18 @@ int br_multicast_set_querier(struct net_bridge *br, unsigned long val) > if (!val) > goto unlock; > > - max_delay = br->multicast_query_response_interval; > - > - if (!timer_pending(&br->ip4_other_query.timer)) > - br->ip4_other_query.delay_time = jiffies + max_delay; > + vg = br_vlan_group(br); > + if (!vg || !vg->num_vlans) > + goto unlock; > > - br_multicast_start_querier(br, &br->ip4_own_query); > + max_delay = br->multicast_query_response_interval; > > + list_for_each_entry(v, &vg->vlan_list, vlist) { > + br_multicast_init_querier(v, &v->ip4_own_query, max_delay); > #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_init_querier(v, &v->ip6_own_query, max_delay); > #endif > + } > > unlock: > spin_unlock_bh(&br->multicast_lock); > @@ -2425,6 +2547,7 @@ EXPORT_SYMBOL_GPL(br_multicast_list_adjacent); > */ > bool br_multicast_has_querier_anywhere(struct net_device *dev, int proto) > { > + struct net_bridge_vlan_group *vg; > struct net_bridge *br; > struct net_bridge_port *port; > struct ethhdr eth; > @@ -2438,12 +2561,16 @@ bool br_multicast_has_querier_anywhere(struct net_device *dev, int proto) > if (!port || !port->br) > goto unlock; > > + vg = nbp_vlan_group_rcu(port); > + if (!vg) > + goto unlock; > + > br = port->br; > > memset(ð, 0, sizeof(eth)); > eth.h_proto = htons(proto); > > - ret = br_multicast_querier_exists(br, ð); > + ret = br_multicast_querier_exists(br, br_get_pvid(vg), ð); > > unlock: > rcu_read_unlock(); > @@ -2462,7 +2589,8 @@ EXPORT_SYMBOL_GPL(br_multicast_has_querier_anywhere); > */ > bool br_multicast_has_querier_adjacent(struct net_device *dev, int proto) > { > - struct net_bridge *br; > + struct net_bridge_vlan_group *vg; > + struct net_bridge_vlan *v; > struct net_bridge_port *port; > bool ret = false; > > @@ -2474,18 +2602,24 @@ bool br_multicast_has_querier_adjacent(struct net_device *dev, int proto) > if (!port || !port->br) > goto unlock; > > - br = port->br; > + vg = nbp_vlan_group_rcu(port); > + if (!vg) > + goto unlock; > + > + v = br_vlan_find(br_vlan_group(port->br), br_get_pvid(vg)); > + if (!v) > + goto unlock; > > switch (proto) { > case ETH_P_IP: > - if (!timer_pending(&br->ip4_other_query.timer) || > - rcu_dereference(br->ip4_querier.port) == port) > + if (!timer_pending(&v->ip4_other_query.timer) || > + rcu_dereference(v->ip4_querier.port) == port) > goto unlock; > break; > #if IS_ENABLED(CONFIG_IPV6) > case ETH_P_IPV6: > - if (!timer_pending(&br->ip6_other_query.timer) || > - rcu_dereference(br->ip6_querier.port) == port) > + if (!timer_pending(&v->ip6_other_query.timer) || > + rcu_dereference(v->ip6_querier.port) == port) > goto unlock; > break; > #endif > diff --git a/net/bridge/br_private.h b/net/bridge/br_private.h > index 6e31be61d2c6..00dac1bbfaba 100644 > --- a/net/bridge/br_private.h > +++ b/net/bridge/br_private.h > @@ -140,6 +140,17 @@ struct net_bridge_vlan { > struct net_bridge_vlan *brvlan; > }; > > +#ifdef CONFIG_BRIDGE_IGMP_SNOOPING > + struct bridge_mcast_other_query ip4_other_query; > + struct bridge_mcast_own_query ip4_own_query; > + struct bridge_mcast_querier ip4_querier; > +#if IS_ENABLED(CONFIG_IPV6) > + struct bridge_mcast_other_query ip6_other_query; > + struct bridge_mcast_own_query ip6_own_query; > + struct bridge_mcast_querier ip6_querier; > +#endif > +#endif > + > struct br_tunnel_info tinfo; > > struct list_head vlist; > @@ -261,10 +272,6 @@ struct net_bridge_port { > struct rcu_head rcu; > > #ifdef CONFIG_BRIDGE_IGMP_SNOOPING > - struct bridge_mcast_own_query ip4_own_query; > -#if IS_ENABLED(CONFIG_IPV6) > - struct bridge_mcast_own_query ip6_own_query; > -#endif /* IS_ENABLED(CONFIG_IPV6) */ > unsigned char multicast_router; > struct bridge_mcast_stats __percpu *mcast_stats; > struct timer_list multicast_router_timer; > @@ -390,14 +397,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 bridge_mcast_querier ip4_querier; > struct bridge_mcast_stats __percpu *mcast_stats; > #if IS_ENABLED(CONFIG_IPV6) > - struct bridge_mcast_other_query ip6_other_query; > - struct bridge_mcast_own_query ip6_own_query; > - struct bridge_mcast_querier ip6_querier; > u8 multicast_mld_version; > #endif /* IS_ENABLED(CONFIG_IPV6) */ > #endif > @@ -618,6 +619,7 @@ int br_multicast_add_port(struct net_bridge_port *port); > void br_multicast_del_port(struct net_bridge_port *port); > void br_multicast_enable_port(struct net_bridge_port *port); > void br_multicast_disable_port(struct net_bridge_port *port); > +void br_multicast_enable_vlan(struct net_bridge *br, u16 vid); > void br_multicast_init(struct net_bridge *br); > void br_multicast_open(struct net_bridge *br); > void br_multicast_stop(struct net_bridge *br); > @@ -633,6 +635,7 @@ int br_multicast_set_igmp_version(struct net_bridge *br, unsigned long val); > #if IS_ENABLED(CONFIG_IPV6) > int br_multicast_set_mld_version(struct net_bridge *br, unsigned long val); > #endif > +__be32 br_multicast_inet_addr(struct net_bridge *br, u16 vid); > struct net_bridge_mdb_entry * > br_mdb_ip_get(struct net_bridge_mdb_htable *mdb, struct br_ip *dst); > struct net_bridge_mdb_entry * > @@ -687,17 +690,27 @@ __br_multicast_querier_exists(struct net_bridge *br, > (own_querier_enabled || timer_pending(&querier->timer)); > } > > +static struct net_bridge_vlan_group *br_vlan_group(const struct net_bridge *br); > +struct net_bridge_vlan *br_vlan_find(struct net_bridge_vlan_group *vg, u16 vid); > + > static inline bool br_multicast_querier_exists(struct net_bridge *br, > + u16 vid, > struct ethhdr *eth) > { > + struct net_bridge_vlan *v; > + > + v = br_vlan_find(br_vlan_group(br), vid); > + if (!v) > + return false; > + > switch (eth->h_proto) { > case (htons(ETH_P_IP)): > return __br_multicast_querier_exists(br, > - &br->ip4_other_query, false); > + &v->ip4_other_query, false); > #if IS_ENABLED(CONFIG_IPV6) > case (htons(ETH_P_IPV6)): > return __br_multicast_querier_exists(br, > - &br->ip6_other_query, true); > + &v->ip6_other_query, true); > #endif > default: > return false; > @@ -768,6 +781,7 @@ static inline bool br_multicast_is_router(struct net_bridge *br) > } > > static inline bool br_multicast_querier_exists(struct net_bridge *br, > + u16 vid, > struct ethhdr *eth) > { > return false; > diff --git a/net/bridge/br_stp.c b/net/bridge/br_stp.c > index a1ba52d247d8..d1d6c4fb39dd 100644 > --- a/net/bridge/br_stp.c > +++ b/net/bridge/br_stp.c > @@ -460,10 +460,7 @@ void br_port_state_selection(struct net_bridge *br) > > if (p->state != BR_STATE_BLOCKING) > br_multicast_enable_port(p); > - /* Multicast is not disabled for the port when it goes in > - * blocking state because the timers will expire and stop by > - * themselves without sending more queries. > - */ > + > if (p->state == BR_STATE_FORWARDING) > ++liveports; > } > diff --git a/net/bridge/br_vlan.c b/net/bridge/br_vlan.c > index bb9cbad4bad6..3b8fb28e9ab4 100644 > --- a/net/bridge/br_vlan.c > +++ b/net/bridge/br_vlan.c > @@ -270,6 +270,9 @@ static int __vlan_add(struct net_bridge_vlan *v, u16 flags) > goto out_filt; > } > vg->num_vlans++; > + > + /* Start per VLAN IGMP/MLD querier timers */ > + br_multicast_enable_vlan(br, v->vid); > } > > err = rhashtable_lookup_insert_fast(&vg->vlan_hash, &v->vnode, >