Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  br_multicast.c   Sprache: C

 
// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Bridge multicast support.
 *
 * Copyright (c) 2010 Herbert Xu <herbert@gondor.apana.org.au>
 */


#include <linux/err.h>
#include <linux/export.h>
#include <linux/if_ether.h>
#include <linux/igmp.h>
#include <linux/in.h>
#include <linux/jhash.h>
#include <linux/kernel.h>
#include <linux/log2.h>
#include <linux/netdevice.h>
#include <linux/netfilter_bridge.h>
#include <linux/random.h>
#include <linux/rculist.h>
#include <linux/skbuff.h>
#include <linux/slab.h>
#include <linux/timer.h>
#include <linux/inetdevice.h>
#include <linux/mroute.h>
#include <net/ip.h>
#include <net/switchdev.h>
#if IS_ENABLED(CONFIG_IPV6)
#include <linux/icmpv6.h>
#include <net/ipv6.h>
#include <net/mld.h>
#include <net/ip6_checksum.h>
#include <net/addrconf.h>
#endif
#include <trace/events/bridge.h>

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

static const struct rhashtable_params br_mdb_rht_params = {
 .head_offset = offsetof(struct net_bridge_mdb_entry, rhnode),
 .key_offset = offsetof(struct net_bridge_mdb_entry, addr),
 .key_len = sizeof(struct br_ip),
 .automatic_shrinking = true,
};

static const struct rhashtable_params br_sg_port_rht_params = {
 .head_offset = offsetof(struct net_bridge_port_group, rhnode),
 .key_offset = offsetof(struct net_bridge_port_group, key),
 .key_len = sizeof(struct net_bridge_port_group_sg_key),
 .automatic_shrinking = true,
};

static void br_multicast_start_querier(struct net_bridge_mcast *brmctx,
           struct bridge_mcast_own_query *query);
static void br_ip4_multicast_add_router(struct net_bridge_mcast *brmctx,
     struct net_bridge_mcast_port *pmctx);
static void br_ip4_multicast_leave_group(struct net_bridge_mcast *brmctx,
      struct net_bridge_mcast_port *pmctx,
      __be32 group,
      __u16 vid,
      const unsigned char *src);
static void br_multicast_port_group_rexmit(struct timer_list *t);

static void
br_multicast_rport_del_notify(struct net_bridge_mcast_port *pmctx, bool deleted);
static void br_ip6_multicast_add_router(struct net_bridge_mcast *brmctx,
     struct net_bridge_mcast_port *pmctx);
#if IS_ENABLED(CONFIG_IPV6)
static void br_ip6_multicast_leave_group(struct net_bridge_mcast *brmctx,
      struct net_bridge_mcast_port *pmctx,
      const struct in6_addr *group,
      __u16 vid, const unsigned char *src);
#endif
static struct net_bridge_port_group *
__br_multicast_add_group(struct net_bridge_mcast *brmctx,
    struct net_bridge_mcast_port *pmctx,
    struct br_ip *group,
    const unsigned char *src,
    u8 filter_mode,
    bool igmpv2_mldv1,
    bool blocked);
static void br_multicast_find_del_pg(struct net_bridge *br,
         struct net_bridge_port_group *pg);
static void __br_multicast_stop(struct net_bridge_mcast *brmctx);

static int br_mc_disabled_update(struct net_device *dev, bool value,
     struct netlink_ext_ack *extack);

static struct net_bridge_port_group *
br_sg_port_find(struct net_bridge *br,
  struct net_bridge_port_group_sg_key *sg_p)
{
 lockdep_assert_held_once(&br->multicast_lock);

 return rhashtable_lookup_fast(&br->sg_port_tbl, sg_p,
          br_sg_port_rht_params);
}

static struct net_bridge_mdb_entry *br_mdb_ip_get_rcu(struct net_bridge *br,
            struct br_ip *dst)
{
 return rhashtable_lookup(&br->mdb_hash_tbl, dst, br_mdb_rht_params);
}

struct net_bridge_mdb_entry *br_mdb_ip_get(struct net_bridge *br,
        struct br_ip *dst)
{
 struct net_bridge_mdb_entry *ent;

 lockdep_assert_held_once(&br->multicast_lock);

 rcu_read_lock();
 ent = rhashtable_lookup(&br->mdb_hash_tbl, dst, br_mdb_rht_params);
 rcu_read_unlock();

 return ent;
}

static struct net_bridge_mdb_entry *br_mdb_ip4_get(struct net_bridge *br,
         __be32 dst, __u16 vid)
{
 struct br_ip br_dst;

 memset(&br_dst, 0, sizeof(br_dst));
 br_dst.dst.ip4 = dst;
 br_dst.proto = htons(ETH_P_IP);
 br_dst.vid = vid;

 return br_mdb_ip_get(br, &br_dst);
}

#if IS_ENABLED(CONFIG_IPV6)
static struct net_bridge_mdb_entry *br_mdb_ip6_get(struct net_bridge *br,
         const struct in6_addr *dst,
         __u16 vid)
{
 struct br_ip br_dst;

 memset(&br_dst, 0, sizeof(br_dst));
 br_dst.dst.ip6 = *dst;
 br_dst.proto = htons(ETH_P_IPV6);
 br_dst.vid = vid;

 return br_mdb_ip_get(br, &br_dst);
}
#endif

struct net_bridge_mdb_entry *
br_mdb_entry_skb_get(struct net_bridge_mcast *brmctx, struct sk_buff *skb,
       u16 vid)
{
 struct net_bridge *br = brmctx->br;
 struct br_ip ip;

 if (!br_opt_get(br, BROPT_MULTICAST_ENABLED) ||
     br_multicast_ctx_vlan_global_disabled(brmctx))
  return NULL;

 if (BR_INPUT_SKB_CB(skb)->igmp)
  return NULL;

 memset(&ip, 0, sizeof(ip));
 ip.proto = skb->protocol;
 ip.vid = vid;

 switch (skb->protocol) {
 case htons(ETH_P_IP):
  ip.dst.ip4 = ip_hdr(skb)->daddr;
  if (brmctx->multicast_igmp_version == 3) {
   struct net_bridge_mdb_entry *mdb;

   ip.src.ip4 = ip_hdr(skb)->saddr;
   mdb = br_mdb_ip_get_rcu(br, &ip);
   if (mdb)
    return mdb;
   ip.src.ip4 = 0;
  }
  break;
#if IS_ENABLED(CONFIG_IPV6)
 case htons(ETH_P_IPV6):
  ip.dst.ip6 = ipv6_hdr(skb)->daddr;
  if (brmctx->multicast_mld_version == 2) {
   struct net_bridge_mdb_entry *mdb;

   ip.src.ip6 = ipv6_hdr(skb)->saddr;
   mdb = br_mdb_ip_get_rcu(br, &ip);
   if (mdb)
    return mdb;
   memset(&ip.src.ip6, 0, sizeof(ip.src.ip6));
  }
  break;
#endif
 default:
  ip.proto = 0;
  ether_addr_copy(ip.dst.mac_addr, eth_hdr(skb)->h_dest);
 }

 return br_mdb_ip_get_rcu(br, &ip);
}

/* IMPORTANT: this function must be used only when the contexts cannot be
 * passed down (e.g. timer) and must be used for read-only purposes because
 * the vlan snooping option can change, so it can return any context
 * (non-vlan or vlan). Its initial intended purpose is to read timer values
 * from the *current* context based on the option. At worst that could lead
 * to inconsistent timers when the contexts are changed, i.e. src timer
 * which needs to re-arm with a specific delay taken from the old context
 */

static struct net_bridge_mcast_port *
br_multicast_pg_to_port_ctx(const struct net_bridge_port_group *pg)
{
 struct net_bridge_mcast_port *pmctx = &pg->key.port->multicast_ctx;
 struct net_bridge_vlan *vlan;

 lockdep_assert_held_once(&pg->key.port->br->multicast_lock);

 /* if vlan snooping is disabled use the port's multicast context */
 if (!pg->key.addr.vid ||
     !br_opt_get(pg->key.port->br, BROPT_MCAST_VLAN_SNOOPING_ENABLED))
  goto out;

 /* locking is tricky here, due to different rules for multicast and
 * vlans we need to take rcu to find the vlan and make sure it has
 * the BR_VLFLAG_MCAST_ENABLED flag set, it can only change under
 * multicast_lock which must be already held here, so the vlan's pmctx
 * can safely be used on return
 */

 rcu_read_lock();
 vlan = br_vlan_find(nbp_vlan_group_rcu(pg->key.port), pg->key.addr.vid);
 if (vlan && !br_multicast_port_ctx_vlan_disabled(&vlan->port_mcast_ctx))
  pmctx = &vlan->port_mcast_ctx;
 else
  pmctx = NULL;
 rcu_read_unlock();
out:
 return pmctx;
}

static struct net_bridge_mcast_port *
br_multicast_port_vid_to_port_ctx(struct net_bridge_port *port, u16 vid)
{
 struct net_bridge_mcast_port *pmctx = NULL;
 struct net_bridge_vlan *vlan;

 lockdep_assert_held_once(&port->br->multicast_lock);

 if (!br_opt_get(port->br, BROPT_MCAST_VLAN_SNOOPING_ENABLED))
  return NULL;

 /* Take RCU to access the vlan. */
 rcu_read_lock();

 vlan = br_vlan_find(nbp_vlan_group_rcu(port), vid);
 if (vlan && !br_multicast_port_ctx_vlan_disabled(&vlan->port_mcast_ctx))
  pmctx = &vlan->port_mcast_ctx;

 rcu_read_unlock();

 return pmctx;
}

/* when snooping we need to check if the contexts should be used
 * in the following order:
 * - if pmctx is non-NULL (port), check if it should be used
 * - if pmctx is NULL (bridge), check if brmctx should be used
 */

static bool
br_multicast_ctx_should_use(const struct net_bridge_mcast *brmctx,
       const struct net_bridge_mcast_port *pmctx)
{
 if (!netif_running(brmctx->br->dev))
  return false;

 if (pmctx)
  return !br_multicast_port_ctx_state_disabled(pmctx);
 else
  return !br_multicast_ctx_vlan_disabled(brmctx);
}

static bool br_port_group_equal(struct net_bridge_port_group *p,
    struct net_bridge_port *port,
    const unsigned char *src)
{
 if (p->key.port != port)
  return false;

 if (!(port->flags & BR_MULTICAST_TO_UNICAST))
  return true;

 return ether_addr_equal(src, p->eth_addr);
}

static void __fwd_add_star_excl(struct net_bridge_mcast_port *pmctx,
    struct net_bridge_port_group *pg,
    struct br_ip *sg_ip)
{
 struct net_bridge_port_group_sg_key sg_key;
 struct net_bridge_port_group *src_pg;
 struct net_bridge_mcast *brmctx;

 memset(&sg_key, 0, sizeof(sg_key));
 brmctx = br_multicast_port_ctx_get_global(pmctx);
 sg_key.port = pg->key.port;
 sg_key.addr = *sg_ip;
 if (br_sg_port_find(brmctx->br, &sg_key))
  return;

 src_pg = __br_multicast_add_group(brmctx, pmctx,
       sg_ip, pg->eth_addr,
       MCAST_INCLUDE, falsefalse);
 if (IS_ERR_OR_NULL(src_pg) ||
     src_pg->rt_protocol != RTPROT_KERNEL)
  return;

 src_pg->flags |= MDB_PG_FLAGS_STAR_EXCL;
}

static void __fwd_del_star_excl(struct net_bridge_port_group *pg,
    struct br_ip *sg_ip)
{
 struct net_bridge_port_group_sg_key sg_key;
 struct net_bridge *br = pg->key.port->br;
 struct net_bridge_port_group *src_pg;

 memset(&sg_key, 0, sizeof(sg_key));
 sg_key.port = pg->key.port;
 sg_key.addr = *sg_ip;
 src_pg = br_sg_port_find(br, &sg_key);
 if (!src_pg || !(src_pg->flags & MDB_PG_FLAGS_STAR_EXCL) ||
     src_pg->rt_protocol != RTPROT_KERNEL)
  return;

 br_multicast_find_del_pg(br, src_pg);
}

/* When a port group transitions to (or is added as) EXCLUDE we need to add it
 * to all other ports' S,G entries which are not blocked by the current group
 * for proper replication, the assumption is that any S,G blocked entries
 * are already added so the S,G,port lookup should skip them.
 * When a port group transitions from EXCLUDE -> INCLUDE mode or is being
 * deleted we need to remove it from all ports' S,G entries where it was
 * automatically installed before (i.e. where it's MDB_PG_FLAGS_STAR_EXCL).
 */

void br_multicast_star_g_handle_mode(struct net_bridge_port_group *pg,
         u8 filter_mode)
{
 struct net_bridge *br = pg->key.port->br;
 struct net_bridge_port_group *pg_lst;
 struct net_bridge_mcast_port *pmctx;
 struct net_bridge_mdb_entry *mp;
 struct br_ip sg_ip;

 if (WARN_ON(!br_multicast_is_star_g(&pg->key.addr)))
  return;

 mp = br_mdb_ip_get(br, &pg->key.addr);
 if (!mp)
  return;
 pmctx = br_multicast_pg_to_port_ctx(pg);
 if (!pmctx)
  return;

 memset(&sg_ip, 0, sizeof(sg_ip));
 sg_ip = pg->key.addr;

 for (pg_lst = mlock_dereference(mp->ports, br);
      pg_lst;
      pg_lst = mlock_dereference(pg_lst->next, br)) {
  struct net_bridge_group_src *src_ent;

  if (pg_lst == pg)
   continue;
  hlist_for_each_entry(src_ent, &pg_lst->src_list, node) {
   if (!(src_ent->flags & BR_SGRP_F_INSTALLED))
    continue;
   sg_ip.src = src_ent->addr.src;
   switch (filter_mode) {
   case MCAST_INCLUDE:
    __fwd_del_star_excl(pg, &sg_ip);
    break;
   case MCAST_EXCLUDE:
    __fwd_add_star_excl(pmctx, pg, &sg_ip);
    break;
   }
  }
 }
}

/* called when adding a new S,G with host_joined == false by default */
static void br_multicast_sg_host_state(struct net_bridge_mdb_entry *star_mp,
           struct net_bridge_port_group *sg)
{
 struct net_bridge_mdb_entry *sg_mp;

 if (WARN_ON(!br_multicast_is_star_g(&star_mp->addr)))
  return;
 if (!star_mp->host_joined)
  return;

 sg_mp = br_mdb_ip_get(star_mp->br, &sg->key.addr);
 if (!sg_mp)
  return;
 sg_mp->host_joined = true;
}

/* set the host_joined state of all of *,G's S,G entries */
static void br_multicast_star_g_host_state(struct net_bridge_mdb_entry *star_mp)
{
 struct net_bridge *br = star_mp->br;
 struct net_bridge_mdb_entry *sg_mp;
 struct net_bridge_port_group *pg;
 struct br_ip sg_ip;

 if (WARN_ON(!br_multicast_is_star_g(&star_mp->addr)))
  return;

 memset(&sg_ip, 0, sizeof(sg_ip));
 sg_ip = star_mp->addr;
 for (pg = mlock_dereference(star_mp->ports, br);
      pg;
      pg = mlock_dereference(pg->next, br)) {
  struct net_bridge_group_src *src_ent;

  hlist_for_each_entry(src_ent, &pg->src_list, node) {
   if (!(src_ent->flags & BR_SGRP_F_INSTALLED))
    continue;
   sg_ip.src = src_ent->addr.src;
   sg_mp = br_mdb_ip_get(br, &sg_ip);
   if (!sg_mp)
    continue;
   sg_mp->host_joined = star_mp->host_joined;
  }
 }
}

static void br_multicast_sg_del_exclude_ports(struct net_bridge_mdb_entry *sgmp)
{
 struct net_bridge_port_group __rcu **pp;
 struct net_bridge_port_group *p;

 /* *,G exclude ports are only added to S,G entries */
 if (WARN_ON(br_multicast_is_star_g(&sgmp->addr)))
  return;

 /* we need the STAR_EXCLUDE ports if there are non-STAR_EXCLUDE ports
 * we should ignore perm entries since they're managed by user-space
 */

 for (pp = &sgmp->ports;
      (p = mlock_dereference(*pp, sgmp->br)) != NULL;
      pp = &p->next)
  if (!(p->flags & (MDB_PG_FLAGS_STAR_EXCL |
      MDB_PG_FLAGS_PERMANENT)))
   return;

 /* currently the host can only have joined the *,G which means
 * we treat it as EXCLUDE {}, so for an S,G it's considered a
 * STAR_EXCLUDE entry and we can safely leave it
 */

 sgmp->host_joined = false;

 for (pp = &sgmp->ports;
      (p = mlock_dereference(*pp, sgmp->br)) != NULL;) {
  if (!(p->flags & MDB_PG_FLAGS_PERMANENT))
   br_multicast_del_pg(sgmp, p, pp);
  else
   pp = &p->next;
 }
}

void br_multicast_sg_add_exclude_ports(struct net_bridge_mdb_entry *star_mp,
           struct net_bridge_port_group *sg)
{
 struct net_bridge_port_group_sg_key sg_key;
 struct net_bridge *br = star_mp->br;
 struct net_bridge_mcast_port *pmctx;
 struct net_bridge_port_group *pg;
 struct net_bridge_mcast *brmctx;

 if (WARN_ON(br_multicast_is_star_g(&sg->key.addr)))
  return;
 if (WARN_ON(!br_multicast_is_star_g(&star_mp->addr)))
  return;

 br_multicast_sg_host_state(star_mp, sg);
 memset(&sg_key, 0, sizeof(sg_key));
 sg_key.addr = sg->key.addr;
 /* we need to add all exclude ports to the S,G */
 for (pg = mlock_dereference(star_mp->ports, br);
      pg;
      pg = mlock_dereference(pg->next, br)) {
  struct net_bridge_port_group *src_pg;

  if (pg == sg || pg->filter_mode == MCAST_INCLUDE)
   continue;

  sg_key.port = pg->key.port;
  if (br_sg_port_find(br, &sg_key))
   continue;

  pmctx = br_multicast_pg_to_port_ctx(pg);
  if (!pmctx)
   continue;
  brmctx = br_multicast_port_ctx_get_global(pmctx);

  src_pg = __br_multicast_add_group(brmctx, pmctx,
        &sg->key.addr,
        sg->eth_addr,
        MCAST_INCLUDE, falsefalse);
  if (IS_ERR_OR_NULL(src_pg) ||
      src_pg->rt_protocol != RTPROT_KERNEL)
   continue;
  src_pg->flags |= MDB_PG_FLAGS_STAR_EXCL;
 }
}

static void br_multicast_fwd_src_add(struct net_bridge_group_src *src)
{
 struct net_bridge_mdb_entry *star_mp;
 struct net_bridge_mcast_port *pmctx;
 struct net_bridge_port_group *sg;
 struct net_bridge_mcast *brmctx;
 struct br_ip sg_ip;

 if (src->flags & BR_SGRP_F_INSTALLED)
  return;

 memset(&sg_ip, 0, sizeof(sg_ip));
 pmctx = br_multicast_pg_to_port_ctx(src->pg);
 if (!pmctx)
  return;
 brmctx = br_multicast_port_ctx_get_global(pmctx);
 sg_ip = src->pg->key.addr;
 sg_ip.src = src->addr.src;

 sg = __br_multicast_add_group(brmctx, pmctx, &sg_ip,
          src->pg->eth_addr, MCAST_INCLUDE, false,
          !timer_pending(&src->timer));
 if (IS_ERR_OR_NULL(sg))
  return;
 src->flags |= BR_SGRP_F_INSTALLED;
 sg->flags &= ~MDB_PG_FLAGS_STAR_EXCL;

 /* if it was added by user-space as perm we can skip next steps */
 if (sg->rt_protocol != RTPROT_KERNEL &&
     (sg->flags & MDB_PG_FLAGS_PERMANENT))
  return;

 /* the kernel is now responsible for removing this S,G */
 timer_delete(&sg->timer);
 star_mp = br_mdb_ip_get(src->br, &src->pg->key.addr);
 if (!star_mp)
  return;

 br_multicast_sg_add_exclude_ports(star_mp, sg);
}

static void br_multicast_fwd_src_remove(struct net_bridge_group_src *src,
     bool fastleave)
{
 struct net_bridge_port_group *p, *pg = src->pg;
 struct net_bridge_port_group __rcu **pp;
 struct net_bridge_mdb_entry *mp;
 struct br_ip sg_ip;

 memset(&sg_ip, 0, sizeof(sg_ip));
 sg_ip = pg->key.addr;
 sg_ip.src = src->addr.src;

 mp = br_mdb_ip_get(src->br, &sg_ip);
 if (!mp)
  return;

 for (pp = &mp->ports;
      (p = mlock_dereference(*pp, src->br)) != NULL;
      pp = &p->next) {
  if (!br_port_group_equal(p, pg->key.port, pg->eth_addr))
   continue;

  if (p->rt_protocol != RTPROT_KERNEL &&
      (p->flags & MDB_PG_FLAGS_PERMANENT) &&
      !(src->flags & BR_SGRP_F_USER_ADDED))
   break;

  if (fastleave)
   p->flags |= MDB_PG_FLAGS_FAST_LEAVE;
  br_multicast_del_pg(mp, p, pp);
  break;
 }
 src->flags &= ~BR_SGRP_F_INSTALLED;
}

/* install S,G and based on src's timer enable or disable forwarding */
static void br_multicast_fwd_src_handle(struct net_bridge_group_src *src)
{
 struct net_bridge_port_group_sg_key sg_key;
 struct net_bridge_port_group *sg;
 u8 old_flags;

 br_multicast_fwd_src_add(src);

 memset(&sg_key, 0, sizeof(sg_key));
 sg_key.addr = src->pg->key.addr;
 sg_key.addr.src = src->addr.src;
 sg_key.port = src->pg->key.port;

 sg = br_sg_port_find(src->br, &sg_key);
 if (!sg || (sg->flags & MDB_PG_FLAGS_PERMANENT))
  return;

 old_flags = sg->flags;
 if (timer_pending(&src->timer))
  sg->flags &= ~MDB_PG_FLAGS_BLOCKED;
 else
  sg->flags |= MDB_PG_FLAGS_BLOCKED;

 if (old_flags != sg->flags) {
  struct net_bridge_mdb_entry *sg_mp;

  sg_mp = br_mdb_ip_get(src->br, &sg_key.addr);
  if (!sg_mp)
   return;
  br_mdb_notify(src->br->dev, sg_mp, sg, RTM_NEWMDB);
 }
}

static void br_multicast_destroy_mdb_entry(struct net_bridge_mcast_gc *gc)
{
 struct net_bridge_mdb_entry *mp;

 mp = container_of(gc, struct net_bridge_mdb_entry, mcast_gc);
 WARN_ON(!hlist_unhashed(&mp->mdb_node));
 WARN_ON(mp->ports);

 timer_shutdown_sync(&mp->timer);
 kfree_rcu(mp, rcu);
}

static void br_multicast_del_mdb_entry(struct net_bridge_mdb_entry *mp)
{
 struct net_bridge *br = mp->br;

 rhashtable_remove_fast(&br->mdb_hash_tbl, &mp->rhnode,
          br_mdb_rht_params);
 hlist_del_init_rcu(&mp->mdb_node);
 hlist_add_head(&mp->mcast_gc.gc_node, &br->mcast_gc_list);
 queue_work(system_long_wq, &br->mcast_gc_work);
}

static void br_multicast_group_expired(struct timer_list *t)
{
 struct net_bridge_mdb_entry *mp = timer_container_of(mp, t, timer);
 struct net_bridge *br = mp->br;

 spin_lock(&br->multicast_lock);
 if (hlist_unhashed(&mp->mdb_node) || !netif_running(br->dev) ||
     timer_pending(&mp->timer))
  goto out;

 br_multicast_host_leave(mp, true);

 if (mp->ports)
  goto out;
 br_multicast_del_mdb_entry(mp);
out:
 spin_unlock(&br->multicast_lock);
}

static void br_multicast_destroy_group_src(struct net_bridge_mcast_gc *gc)
{
 struct net_bridge_group_src *src;

 src = container_of(gc, struct net_bridge_group_src, mcast_gc);
 WARN_ON(!hlist_unhashed(&src->node));

 timer_shutdown_sync(&src->timer);
 kfree_rcu(src, rcu);
}

void __br_multicast_del_group_src(struct net_bridge_group_src *src)
{
 struct net_bridge *br = src->pg->key.port->br;

 hlist_del_init_rcu(&src->node);
 src->pg->src_ents--;
 hlist_add_head(&src->mcast_gc.gc_node, &br->mcast_gc_list);
 queue_work(system_long_wq, &br->mcast_gc_work);
}

void br_multicast_del_group_src(struct net_bridge_group_src *src,
    bool fastleave)
{
 br_multicast_fwd_src_remove(src, fastleave);
 __br_multicast_del_group_src(src);
}

static int
br_multicast_port_ngroups_inc_one(struct net_bridge_mcast_port *pmctx,
      struct netlink_ext_ack *extack,
      const char *what)
{
 u32 max = READ_ONCE(pmctx->mdb_max_entries);
 u32 n = READ_ONCE(pmctx->mdb_n_entries);

 if (max && n >= max) {
  NL_SET_ERR_MSG_FMT_MOD(extack, "%s is already in %u groups, and mcast_max_groups=%u",
           what, n, max);
  return -E2BIG;
 }

 WRITE_ONCE(pmctx->mdb_n_entries, n + 1);
 return 0;
}

static void br_multicast_port_ngroups_dec_one(struct net_bridge_mcast_port *pmctx)
{
 u32 n = READ_ONCE(pmctx->mdb_n_entries);

 WARN_ON_ONCE(n == 0);
 WRITE_ONCE(pmctx->mdb_n_entries, n - 1);
}

static int br_multicast_port_ngroups_inc(struct net_bridge_port *port,
      const struct br_ip *group,
      struct netlink_ext_ack *extack)
{
 struct net_bridge_mcast_port *pmctx;
 int err;

 lockdep_assert_held_once(&port->br->multicast_lock);

 /* Always count on the port context. */
 err = br_multicast_port_ngroups_inc_one(&port->multicast_ctx, extack,
      "Port");
 if (err) {
  trace_br_mdb_full(port->dev, group);
  return err;
 }

 /* Only count on the VLAN context if VID is given, and if snooping on
 * that VLAN is enabled.
 */

 if (!group->vid)
  return 0;

 pmctx = br_multicast_port_vid_to_port_ctx(port, group->vid);
 if (!pmctx)
  return 0;

 err = br_multicast_port_ngroups_inc_one(pmctx, extack, "Port-VLAN");
 if (err) {
  trace_br_mdb_full(port->dev, group);
  goto dec_one_out;
 }

 return 0;

dec_one_out:
 br_multicast_port_ngroups_dec_one(&port->multicast_ctx);
 return err;
}

static void br_multicast_port_ngroups_dec(struct net_bridge_port *port, u16 vid)
{
 struct net_bridge_mcast_port *pmctx;

 lockdep_assert_held_once(&port->br->multicast_lock);

 if (vid) {
  pmctx = br_multicast_port_vid_to_port_ctx(port, vid);
  if (pmctx)
   br_multicast_port_ngroups_dec_one(pmctx);
 }
 br_multicast_port_ngroups_dec_one(&port->multicast_ctx);
}

u32 br_multicast_ngroups_get(const struct net_bridge_mcast_port *pmctx)
{
 return READ_ONCE(pmctx->mdb_n_entries);
}

void br_multicast_ngroups_set_max(struct net_bridge_mcast_port *pmctx, u32 max)
{
 WRITE_ONCE(pmctx->mdb_max_entries, max);
}

u32 br_multicast_ngroups_get_max(const struct net_bridge_mcast_port *pmctx)
{
 return READ_ONCE(pmctx->mdb_max_entries);
}

static void br_multicast_destroy_port_group(struct net_bridge_mcast_gc *gc)
{
 struct net_bridge_port_group *pg;

 pg = container_of(gc, struct net_bridge_port_group, mcast_gc);
 WARN_ON(!hlist_unhashed(&pg->mglist));
 WARN_ON(!hlist_empty(&pg->src_list));

 timer_shutdown_sync(&pg->rexmit_timer);
 timer_shutdown_sync(&pg->timer);
 kfree_rcu(pg, rcu);
}

void br_multicast_del_pg(struct net_bridge_mdb_entry *mp,
    struct net_bridge_port_group *pg,
    struct net_bridge_port_group __rcu **pp)
{
 struct net_bridge *br = pg->key.port->br;
 struct net_bridge_group_src *ent;
 struct hlist_node *tmp;

 rcu_assign_pointer(*pp, pg->next);
 hlist_del_init(&pg->mglist);
 br_multicast_eht_clean_sets(pg);
 hlist_for_each_entry_safe(ent, tmp, &pg->src_list, node)
  br_multicast_del_group_src(ent, false);
 br_mdb_notify(br->dev, mp, pg, RTM_DELMDB);
 if (!br_multicast_is_star_g(&mp->addr)) {
  rhashtable_remove_fast(&br->sg_port_tbl, &pg->rhnode,
           br_sg_port_rht_params);
  br_multicast_sg_del_exclude_ports(mp);
 } else {
  br_multicast_star_g_handle_mode(pg, MCAST_INCLUDE);
 }
 br_multicast_port_ngroups_dec(pg->key.port, pg->key.addr.vid);
 hlist_add_head(&pg->mcast_gc.gc_node, &br->mcast_gc_list);
 queue_work(system_long_wq, &br->mcast_gc_work);

 if (!mp->ports && !mp->host_joined && netif_running(br->dev))
  mod_timer(&mp->timer, jiffies);
}

static void br_multicast_find_del_pg(struct net_bridge *br,
         struct net_bridge_port_group *pg)
{
 struct net_bridge_port_group __rcu **pp;
 struct net_bridge_mdb_entry *mp;
 struct net_bridge_port_group *p;

 mp = br_mdb_ip_get(br, &pg->key.addr);
 if (WARN_ON(!mp))
  return;

 for (pp = &mp->ports;
      (p = mlock_dereference(*pp, br)) != NULL;
      pp = &p->next) {
  if (p != pg)
   continue;

  br_multicast_del_pg(mp, pg, pp);
  return;
 }

 WARN_ON(1);
}

static void br_multicast_port_group_expired(struct timer_list *t)
{
 struct net_bridge_port_group *pg = timer_container_of(pg, t, timer);
 struct net_bridge_group_src *src_ent;
 struct net_bridge *br = pg->key.port->br;
 struct hlist_node *tmp;
 bool changed;

 spin_lock(&br->multicast_lock);
 if (!netif_running(br->dev) || timer_pending(&pg->timer) ||
     hlist_unhashed(&pg->mglist) || pg->flags & MDB_PG_FLAGS_PERMANENT)
  goto out;

 changed = !!(pg->filter_mode == MCAST_EXCLUDE);
 pg->filter_mode = MCAST_INCLUDE;
 hlist_for_each_entry_safe(src_ent, tmp, &pg->src_list, node) {
  if (!timer_pending(&src_ent->timer)) {
   br_multicast_del_group_src(src_ent, false);
   changed = true;
  }
 }

 if (hlist_empty(&pg->src_list)) {
  br_multicast_find_del_pg(br, pg);
 } else if (changed) {
  struct net_bridge_mdb_entry *mp = br_mdb_ip_get(br, &pg->key.addr);

  if (changed && br_multicast_is_star_g(&pg->key.addr))
   br_multicast_star_g_handle_mode(pg, MCAST_INCLUDE);

  if (WARN_ON(!mp))
   goto out;
  br_mdb_notify(br->dev, mp, pg, RTM_NEWMDB);
 }
out:
 spin_unlock(&br->multicast_lock);
}

static void br_multicast_gc(struct hlist_head *head)
{
 struct net_bridge_mcast_gc *gcent;
 struct hlist_node *tmp;

 hlist_for_each_entry_safe(gcent, tmp, head, gc_node) {
  hlist_del_init(&gcent->gc_node);
  gcent->destroy(gcent);
 }
}

static void __br_multicast_query_handle_vlan(struct net_bridge_mcast *brmctx,
          struct net_bridge_mcast_port *pmctx,
          struct sk_buff *skb)
{
 struct net_bridge_vlan *vlan = NULL;

 if (pmctx && br_multicast_port_ctx_is_vlan(pmctx))
  vlan = pmctx->vlan;
 else if (br_multicast_ctx_is_vlan(brmctx))
  vlan = brmctx->vlan;

 if (vlan && !(vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED)) {
  u16 vlan_proto;

  if (br_vlan_get_proto(brmctx->br->dev, &vlan_proto) != 0)
   return;
  __vlan_hwaccel_put_tag(skb, htons(vlan_proto), vlan->vid);
 }
}

static struct sk_buff *br_ip4_multicast_alloc_query(struct net_bridge_mcast *brmctx,
          struct net_bridge_mcast_port *pmctx,
          struct net_bridge_port_group *pg,
          __be32 ip_dst, __be32 group,
          bool with_srcs, bool over_lmqt,
          u8 sflag, u8 *igmp_type,
          bool *need_rexmit)
{
 struct net_bridge_port *p = pg ? pg->key.port : NULL;
 struct net_bridge_group_src *ent;
 size_t pkt_size, igmp_hdr_size;
 unsigned long now = jiffies;
 struct igmpv3_query *ihv3;
 void *csum_start = NULL;
 __sum16 *csum = NULL;
 struct sk_buff *skb;
 struct igmphdr *ih;
 struct ethhdr *eth;
 unsigned long lmqt;
 struct iphdr *iph;
 u16 lmqt_srcs = 0;

 igmp_hdr_size = sizeof(*ih);
 if (brmctx->multicast_igmp_version == 3) {
  igmp_hdr_size = sizeof(*ihv3);
  if (pg && with_srcs) {
   lmqt = now + (brmctx->multicast_last_member_interval *
          brmctx->multicast_last_member_count);
   hlist_for_each_entry(ent, &pg->src_list, node) {
    if (over_lmqt == time_after(ent->timer.expires,
           lmqt) &&
        ent->src_query_rexmit_cnt > 0)
     lmqt_srcs++;
   }

   if (!lmqt_srcs)
    return NULL;
   igmp_hdr_size += lmqt_srcs * sizeof(__be32);
  }
 }

 pkt_size = sizeof(*eth) + sizeof(*iph) + 4 + igmp_hdr_size;
 if ((p && pkt_size > p->dev->mtu) ||
     pkt_size > brmctx->br->dev->mtu)
  return NULL;

 skb = netdev_alloc_skb_ip_align(brmctx->br->dev, pkt_size);
 if (!skb)
  goto out;

 __br_multicast_query_handle_vlan(brmctx, pmctx, skb);
 skb->protocol = htons(ETH_P_IP);

 skb_reset_mac_header(skb);
 eth = eth_hdr(skb);

 ether_addr_copy(eth->h_source, brmctx->br->dev->dev_addr);
 ip_eth_mc_map(ip_dst, eth->h_dest);
 eth->h_proto = htons(ETH_P_IP);
 skb_put(skb, sizeof(*eth));

 skb_set_network_header(skb, skb->len);
 iph = ip_hdr(skb);
 iph->tot_len = htons(pkt_size - sizeof(*eth));

 iph->version = 4;
 iph->ihl = 6;
 iph->tos = 0xc0;
 iph->id = 0;
 iph->frag_off = htons(IP_DF);
 iph->ttl = 1;
 iph->protocol = IPPROTO_IGMP;
 iph->saddr = br_opt_get(brmctx->br, BROPT_MULTICAST_QUERY_USE_IFADDR) ?
       inet_select_addr(brmctx->br->dev, 0, RT_SCOPE_LINK) : 0;
 iph->daddr = ip_dst;
 ((u8 *)&iph[1])[0] = IPOPT_RA;
 ((u8 *)&iph[1])[1] = 4;
 ((u8 *)&iph[1])[2] = 0;
 ((u8 *)&iph[1])[3] = 0;
 ip_send_check(iph);
 skb_put(skb, 24);

 skb_set_transport_header(skb, skb->len);
 *igmp_type = IGMP_HOST_MEMBERSHIP_QUERY;

 switch (brmctx->multicast_igmp_version) {
 case 2:
  ih = igmp_hdr(skb);
  ih->type = IGMP_HOST_MEMBERSHIP_QUERY;
  ih->code = (group ? brmctx->multicast_last_member_interval :
        brmctx->multicast_query_response_interval) /
      (HZ / IGMP_TIMER_SCALE);
  ih->group = group;
  ih->csum = 0;
  csum = &ih->csum;
  csum_start = (void *)ih;
  break;
 case 3:
  ihv3 = igmpv3_query_hdr(skb);
  ihv3->type = IGMP_HOST_MEMBERSHIP_QUERY;
  ihv3->code = (group ? brmctx->multicast_last_member_interval :
          brmctx->multicast_query_response_interval) /
        (HZ / IGMP_TIMER_SCALE);
  ihv3->group = group;
  ihv3->qqic = brmctx->multicast_query_interval / HZ;
  ihv3->nsrcs = htons(lmqt_srcs);
  ihv3->resv = 0;
  ihv3->suppress = sflag;
  ihv3->qrv = 2;
  ihv3->csum = 0;
  csum = &ihv3->csum;
  csum_start = (void *)ihv3;
  if (!pg || !with_srcs)
   break;

  lmqt_srcs = 0;
  hlist_for_each_entry(ent, &pg->src_list, node) {
   if (over_lmqt == time_after(ent->timer.expires,
          lmqt) &&
       ent->src_query_rexmit_cnt > 0) {
    ihv3->srcs[lmqt_srcs++] = ent->addr.src.ip4;
    ent->src_query_rexmit_cnt--;
    if (need_rexmit && ent->src_query_rexmit_cnt)
     *need_rexmit = true;
   }
  }
  if (WARN_ON(lmqt_srcs != ntohs(ihv3->nsrcs))) {
   kfree_skb(skb);
   return NULL;
  }
  break;
 }

 if (WARN_ON(!csum || !csum_start)) {
  kfree_skb(skb);
  return NULL;
 }

 *csum = ip_compute_csum(csum_start, igmp_hdr_size);
 skb_put(skb, igmp_hdr_size);
 __skb_pull(skb, sizeof(*eth));

out:
 return skb;
}

#if IS_ENABLED(CONFIG_IPV6)
static struct sk_buff *br_ip6_multicast_alloc_query(struct net_bridge_mcast *brmctx,
          struct net_bridge_mcast_port *pmctx,
          struct net_bridge_port_group *pg,
          const struct in6_addr *ip6_dst,
          const struct in6_addr *group,
          bool with_srcs, bool over_llqt,
          u8 sflag, u8 *igmp_type,
          bool *need_rexmit)
{
 struct net_bridge_port *p = pg ? pg->key.port : NULL;
 struct net_bridge_group_src *ent;
 size_t pkt_size, mld_hdr_size;
 unsigned long now = jiffies;
 struct mld2_query *mld2q;
 void *csum_start = NULL;
 unsigned long interval;
 __sum16 *csum = NULL;
 struct ipv6hdr *ip6h;
 struct mld_msg *mldq;
 struct sk_buff *skb;
 unsigned long llqt;
 struct ethhdr *eth;
 u16 llqt_srcs = 0;
 u8 *hopopt;

 mld_hdr_size = sizeof(*mldq);
 if (brmctx->multicast_mld_version == 2) {
  mld_hdr_size = sizeof(*mld2q);
  if (pg && with_srcs) {
   llqt = now + (brmctx->multicast_last_member_interval *
          brmctx->multicast_last_member_count);
   hlist_for_each_entry(ent, &pg->src_list, node) {
    if (over_llqt == time_after(ent->timer.expires,
           llqt) &&
        ent->src_query_rexmit_cnt > 0)
     llqt_srcs++;
   }

   if (!llqt_srcs)
    return NULL;
   mld_hdr_size += llqt_srcs * sizeof(struct in6_addr);
  }
 }

 pkt_size = sizeof(*eth) + sizeof(*ip6h) + 8 + mld_hdr_size;
 if ((p && pkt_size > p->dev->mtu) ||
     pkt_size > brmctx->br->dev->mtu)
  return NULL;

 skb = netdev_alloc_skb_ip_align(brmctx->br->dev, pkt_size);
 if (!skb)
  goto out;

 __br_multicast_query_handle_vlan(brmctx, pmctx, skb);
 skb->protocol = htons(ETH_P_IPV6);

 /* Ethernet header */
 skb_reset_mac_header(skb);
 eth = eth_hdr(skb);

 ether_addr_copy(eth->h_source, brmctx->br->dev->dev_addr);
 eth->h_proto = htons(ETH_P_IPV6);
 skb_put(skb, sizeof(*eth));

 /* IPv6 header + HbH option */
 skb_set_network_header(skb, skb->len);
 ip6h = ipv6_hdr(skb);

 *(__force __be32 *)ip6h = htonl(0x60000000);
 ip6h->payload_len = htons(8 + mld_hdr_size);
 ip6h->nexthdr = IPPROTO_HOPOPTS;
 ip6h->hop_limit = 1;
 ip6h->daddr = *ip6_dst;
 if (ipv6_dev_get_saddr(dev_net(brmctx->br->dev), brmctx->br->dev,
          &ip6h->daddr, 0, &ip6h->saddr)) {
  kfree_skb(skb);
  br_opt_toggle(brmctx->br, BROPT_HAS_IPV6_ADDR, false);
  return NULL;
 }

 br_opt_toggle(brmctx->br, BROPT_HAS_IPV6_ADDR, true);
 ipv6_eth_mc_map(&ip6h->daddr, eth->h_dest);

 hopopt = (u8 *)(ip6h + 1);
 hopopt[0] = IPPROTO_ICMPV6;  /* next hdr */
 hopopt[1] = 0;    /* length of HbH */
 hopopt[2] = IPV6_TLV_ROUTERALERT; /* Router Alert */
 hopopt[3] = 2;    /* Length of RA Option */
 hopopt[4] = 0;    /* Type = 0x0000 (MLD) */
 hopopt[5] = 0;
 hopopt[6] = IPV6_TLV_PAD1;  /* Pad1 */
 hopopt[7] = IPV6_TLV_PAD1;  /* Pad1 */

 skb_put(skb, sizeof(*ip6h) + 8);

 /* ICMPv6 */
 skb_set_transport_header(skb, skb->len);
 interval = ipv6_addr_any(group) ?
   brmctx->multicast_query_response_interval :
   brmctx->multicast_last_member_interval;
 *igmp_type = ICMPV6_MGM_QUERY;
 switch (brmctx->multicast_mld_version) {
 case 1:
  mldq = (struct mld_msg *)icmp6_hdr(skb);
  mldq->mld_type = ICMPV6_MGM_QUERY;
  mldq->mld_code = 0;
  mldq->mld_cksum = 0;
  mldq->mld_maxdelay = htons((u16)jiffies_to_msecs(interval));
  mldq->mld_reserved = 0;
  mldq->mld_mca = *group;
  csum = &mldq->mld_cksum;
  csum_start = (void *)mldq;
  break;
 case 2:
  mld2q = (struct mld2_query *)icmp6_hdr(skb);
  mld2q->mld2q_mrc = htons((u16)jiffies_to_msecs(interval));
  mld2q->mld2q_type = ICMPV6_MGM_QUERY;
  mld2q->mld2q_code = 0;
  mld2q->mld2q_cksum = 0;
  mld2q->mld2q_resv1 = 0;
  mld2q->mld2q_resv2 = 0;
  mld2q->mld2q_suppress = sflag;
  mld2q->mld2q_qrv = 2;
  mld2q->mld2q_nsrcs = htons(llqt_srcs);
  mld2q->mld2q_qqic = brmctx->multicast_query_interval / HZ;
  mld2q->mld2q_mca = *group;
  csum = &mld2q->mld2q_cksum;
  csum_start = (void *)mld2q;
  if (!pg || !with_srcs)
   break;

  llqt_srcs = 0;
  hlist_for_each_entry(ent, &pg->src_list, node) {
   if (over_llqt == time_after(ent->timer.expires,
          llqt) &&
       ent->src_query_rexmit_cnt > 0) {
    mld2q->mld2q_srcs[llqt_srcs++] = ent->addr.src.ip6;
    ent->src_query_rexmit_cnt--;
    if (need_rexmit && ent->src_query_rexmit_cnt)
     *need_rexmit = true;
   }
  }
  if (WARN_ON(llqt_srcs != ntohs(mld2q->mld2q_nsrcs))) {
   kfree_skb(skb);
   return NULL;
  }
  break;
 }

 if (WARN_ON(!csum || !csum_start)) {
  kfree_skb(skb);
  return NULL;
 }

 *csum = csum_ipv6_magic(&ip6h->saddr, &ip6h->daddr, mld_hdr_size,
    IPPROTO_ICMPV6,
    csum_partial(csum_start, mld_hdr_size, 0));
 skb_put(skb, mld_hdr_size);
 __skb_pull(skb, sizeof(*eth));

out:
 return skb;
}
#endif

static struct sk_buff *br_multicast_alloc_query(struct net_bridge_mcast *brmctx,
      struct net_bridge_mcast_port *pmctx,
      struct net_bridge_port_group *pg,
      struct br_ip *ip_dst,
      struct br_ip *group,
      bool with_srcs, bool over_lmqt,
      u8 sflag, u8 *igmp_type,
      bool *need_rexmit)
{
 __be32 ip4_dst;

 switch (group->proto) {
 case htons(ETH_P_IP):
  ip4_dst = ip_dst ? ip_dst->dst.ip4 : htonl(INADDR_ALLHOSTS_GROUP);
  return br_ip4_multicast_alloc_query(brmctx, pmctx, pg,
          ip4_dst, group->dst.ip4,
          with_srcs, over_lmqt,
          sflag, igmp_type,
          need_rexmit);
#if IS_ENABLED(CONFIG_IPV6)
 case htons(ETH_P_IPV6): {
  struct in6_addr ip6_dst;

  if (ip_dst)
   ip6_dst = ip_dst->dst.ip6;
  else
   ipv6_addr_set(&ip6_dst, htonl(0xff020000), 0, 0,
          htonl(1));

  return br_ip6_multicast_alloc_query(brmctx, pmctx, pg,
          &ip6_dst, &group->dst.ip6,
          with_srcs, over_lmqt,
          sflag, igmp_type,
          need_rexmit);
 }
#endif
 }
 return NULL;
}

struct net_bridge_mdb_entry *br_multicast_new_group(struct net_bridge *br,
          struct br_ip *group)
{
 struct net_bridge_mdb_entry *mp;
 int err;

 mp = br_mdb_ip_get(br, group);
 if (mp)
  return mp;

 if (atomic_read(&br->mdb_hash_tbl.nelems) >= br->hash_max) {
  trace_br_mdb_full(br->dev, group);
  br_mc_disabled_update(br->dev, false, NULL);
  br_opt_toggle(br, BROPT_MULTICAST_ENABLED, false);
  return ERR_PTR(-E2BIG);
 }

 mp = kzalloc(sizeof(*mp), GFP_ATOMIC);
 if (unlikely(!mp))
  return ERR_PTR(-ENOMEM);

 mp->br = br;
 mp->addr = *group;
 mp->mcast_gc.destroy = br_multicast_destroy_mdb_entry;
 timer_setup(&mp->timer, br_multicast_group_expired, 0);
 err = rhashtable_lookup_insert_fast(&br->mdb_hash_tbl, &mp->rhnode,
         br_mdb_rht_params);
 if (err) {
  kfree(mp);
  mp = ERR_PTR(err);
 } else {
  hlist_add_head_rcu(&mp->mdb_node, &br->mdb_list);
 }

 return mp;
}

static void br_multicast_group_src_expired(struct timer_list *t)
{
 struct net_bridge_group_src *src = timer_container_of(src, t, timer);
 struct net_bridge_port_group *pg;
 struct net_bridge *br = src->br;

 spin_lock(&br->multicast_lock);
 if (hlist_unhashed(&src->node) || !netif_running(br->dev) ||
     timer_pending(&src->timer))
  goto out;

 pg = src->pg;
 if (pg->filter_mode == MCAST_INCLUDE) {
  br_multicast_del_group_src(src, false);
  if (!hlist_empty(&pg->src_list))
   goto out;
  br_multicast_find_del_pg(br, pg);
 } else {
  br_multicast_fwd_src_handle(src);
 }

out:
 spin_unlock(&br->multicast_lock);
}

struct net_bridge_group_src *
br_multicast_find_group_src(struct net_bridge_port_group *pg, struct br_ip *ip)
{
 struct net_bridge_group_src *ent;

 switch (ip->proto) {
 case htons(ETH_P_IP):
  hlist_for_each_entry(ent, &pg->src_list, node)
   if (ip->src.ip4 == ent->addr.src.ip4)
    return ent;
  break;
#if IS_ENABLED(CONFIG_IPV6)
 case htons(ETH_P_IPV6):
  hlist_for_each_entry(ent, &pg->src_list, node)
   if (!ipv6_addr_cmp(&ent->addr.src.ip6, &ip->src.ip6))
    return ent;
  break;
#endif
 }

 return NULL;
}

struct net_bridge_group_src *
br_multicast_new_group_src(struct net_bridge_port_group *pg, struct br_ip *src_ip)
{
 struct net_bridge_group_src *grp_src;

 if (unlikely(pg->src_ents >= PG_SRC_ENT_LIMIT))
  return NULL;

 switch (src_ip->proto) {
 case htons(ETH_P_IP):
  if (ipv4_is_zeronet(src_ip->src.ip4) ||
      ipv4_is_multicast(src_ip->src.ip4))
   return NULL;
  break;
#if IS_ENABLED(CONFIG_IPV6)
 case htons(ETH_P_IPV6):
  if (ipv6_addr_any(&src_ip->src.ip6) ||
      ipv6_addr_is_multicast(&src_ip->src.ip6))
   return NULL;
  break;
#endif
 }

 grp_src = kzalloc(sizeof(*grp_src), GFP_ATOMIC);
 if (unlikely(!grp_src))
  return NULL;

 grp_src->pg = pg;
 grp_src->br = pg->key.port->br;
 grp_src->addr = *src_ip;
 grp_src->mcast_gc.destroy = br_multicast_destroy_group_src;
 timer_setup(&grp_src->timer, br_multicast_group_src_expired, 0);

 hlist_add_head_rcu(&grp_src->node, &pg->src_list);
 pg->src_ents++;

 return grp_src;
}

struct net_bridge_port_group *br_multicast_new_port_group(
   struct net_bridge_port *port,
   const struct br_ip *group,
   struct net_bridge_port_group __rcu *next,
   unsigned char flags,
   const unsigned char *src,
   u8 filter_mode,
   u8 rt_protocol,
   struct netlink_ext_ack *extack)
{
 struct net_bridge_port_group *p;
 int err;

 err = br_multicast_port_ngroups_inc(port, group, extack);
 if (err)
  return NULL;

 p = kzalloc(sizeof(*p), GFP_ATOMIC);
 if (unlikely(!p)) {
  NL_SET_ERR_MSG_MOD(extack, "Couldn't allocate new port group");
  goto dec_out;
 }

 p->key.addr = *group;
 p->key.port = port;
 p->flags = flags;
 p->filter_mode = filter_mode;
 p->rt_protocol = rt_protocol;
 p->eht_host_tree = RB_ROOT;
 p->eht_set_tree = RB_ROOT;
 p->mcast_gc.destroy = br_multicast_destroy_port_group;
 INIT_HLIST_HEAD(&p->src_list);

 if (!br_multicast_is_star_g(group) &&
     rhashtable_lookup_insert_fast(&port->br->sg_port_tbl, &p->rhnode,
       br_sg_port_rht_params)) {
  NL_SET_ERR_MSG_MOD(extack, "Couldn't insert new port group");
  goto free_out;
 }

 rcu_assign_pointer(p->next, next);
 timer_setup(&p->timer, br_multicast_port_group_expired, 0);
 timer_setup(&p->rexmit_timer, br_multicast_port_group_rexmit, 0);
 hlist_add_head(&p->mglist, &port->mglist);

 if (src)
  memcpy(p->eth_addr, src, ETH_ALEN);
 else
  eth_broadcast_addr(p->eth_addr);

 return p;

free_out:
 kfree(p);
dec_out:
 br_multicast_port_ngroups_dec(port, group->vid);
 return NULL;
}

void br_multicast_del_port_group(struct net_bridge_port_group *p)
{
 struct net_bridge_port *port = p->key.port;
 __u16 vid = p->key.addr.vid;

 hlist_del_init(&p->mglist);
 if (!br_multicast_is_star_g(&p->key.addr))
  rhashtable_remove_fast(&port->br->sg_port_tbl, &p->rhnode,
           br_sg_port_rht_params);
 kfree(p);
 br_multicast_port_ngroups_dec(port, vid);
}

void br_multicast_host_join(const struct net_bridge_mcast *brmctx,
       struct net_bridge_mdb_entry *mp, bool notify)
{
 if (!mp->host_joined) {
  mp->host_joined = true;
  if (br_multicast_is_star_g(&mp->addr))
   br_multicast_star_g_host_state(mp);
  if (notify)
   br_mdb_notify(mp->br->dev, mp, NULL, RTM_NEWMDB);
 }

 if (br_group_is_l2(&mp->addr))
  return;

 mod_timer(&mp->timer, jiffies + brmctx->multicast_membership_interval);
}

void br_multicast_host_leave(struct net_bridge_mdb_entry *mp, bool notify)
{
 if (!mp->host_joined)
  return;

 mp->host_joined = false;
 if (br_multicast_is_star_g(&mp->addr))
  br_multicast_star_g_host_state(mp);
 if (notify)
  br_mdb_notify(mp->br->dev, mp, NULL, RTM_DELMDB);
}

static struct net_bridge_port_group *
__br_multicast_add_group(struct net_bridge_mcast *brmctx,
    struct net_bridge_mcast_port *pmctx,
    struct br_ip *group,
    const unsigned char *src,
    u8 filter_mode,
    bool igmpv2_mldv1,
    bool blocked)
{
 struct net_bridge_port_group __rcu **pp;
 struct net_bridge_port_group *p = NULL;
 struct net_bridge_mdb_entry *mp;
 unsigned long now = jiffies;

 if (!br_multicast_ctx_should_use(brmctx, pmctx))
  goto out;

 mp = br_multicast_new_group(brmctx->br, group);
 if (IS_ERR(mp))
  return ERR_CAST(mp);

 if (!pmctx) {
  br_multicast_host_join(brmctx, mp, true);
  goto out;
 }

 for (pp = &mp->ports;
      (p = mlock_dereference(*pp, brmctx->br)) != NULL;
      pp = &p->next) {
  if (br_port_group_equal(p, pmctx->port, src))
   goto found;
  if ((unsigned long)p->key.port < (unsigned long)pmctx->port)
   break;
 }

 p = br_multicast_new_port_group(pmctx->port, group, *pp, 0, src,
     filter_mode, RTPROT_KERNEL, NULL);
 if (unlikely(!p)) {
  p = ERR_PTR(-ENOMEM);
  goto out;
 }
 rcu_assign_pointer(*pp, p);
 if (blocked)
  p->flags |= MDB_PG_FLAGS_BLOCKED;
 br_mdb_notify(brmctx->br->dev, mp, p, RTM_NEWMDB);

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

out:
 return p;
}

static int br_multicast_add_group(struct net_bridge_mcast *brmctx,
      struct net_bridge_mcast_port *pmctx,
      struct br_ip *group,
      const unsigned char *src,
      u8 filter_mode,
      bool igmpv2_mldv1)
{
 struct net_bridge_port_group *pg;
 int err;

 spin_lock(&brmctx->br->multicast_lock);
 pg = __br_multicast_add_group(brmctx, pmctx, group, src, filter_mode,
          igmpv2_mldv1, false);
 /* NULL is considered valid for host joined groups */
 err = PTR_ERR_OR_ZERO(pg);
 spin_unlock(&brmctx->br->multicast_lock);

 return err;
}

static int br_ip4_multicast_add_group(struct net_bridge_mcast *brmctx,
          struct net_bridge_mcast_port *pmctx,
          __be32 group,
          __u16 vid,
          const unsigned char *src,
          bool igmpv2)
{
 struct br_ip br_group;
 u8 filter_mode;

 if (ipv4_is_local_multicast(group))
  return 0;

 memset(&br_group, 0, sizeof(br_group));
 br_group.dst.ip4 = group;
 br_group.proto = htons(ETH_P_IP);
 br_group.vid = vid;
 filter_mode = igmpv2 ? MCAST_EXCLUDE : MCAST_INCLUDE;

 return br_multicast_add_group(brmctx, pmctx, &br_group, src,
          filter_mode, igmpv2);
}

#if IS_ENABLED(CONFIG_IPV6)
static int br_ip6_multicast_add_group(struct net_bridge_mcast *brmctx,
          struct net_bridge_mcast_port *pmctx,
          const struct in6_addr *group,
          __u16 vid,
          const unsigned char *src,
          bool mldv1)
{
 struct br_ip br_group;
 u8 filter_mode;

 if (ipv6_addr_is_ll_all_nodes(group))
  return 0;

 memset(&br_group, 0, sizeof(br_group));
 br_group.dst.ip6 = *group;
 br_group.proto = htons(ETH_P_IPV6);
 br_group.vid = vid;
 filter_mode = mldv1 ? MCAST_EXCLUDE : MCAST_INCLUDE;

 return br_multicast_add_group(brmctx, pmctx, &br_group, src,
          filter_mode, mldv1);
}
#endif

static bool br_multicast_rport_del(struct hlist_node *rlist)
{
 if (hlist_unhashed(rlist))
  return false;

 hlist_del_init_rcu(rlist);
 return true;
}

static bool br_ip4_multicast_rport_del(struct net_bridge_mcast_port *pmctx)
{
 return br_multicast_rport_del(&pmctx->ip4_rlist);
}

static bool br_ip6_multicast_rport_del(struct net_bridge_mcast_port *pmctx)
{
#if IS_ENABLED(CONFIG_IPV6)
 return br_multicast_rport_del(&pmctx->ip6_rlist);
#else
 return false;
#endif
}

static void br_multicast_router_expired(struct net_bridge_mcast_port *pmctx,
     struct timer_list *t,
     struct hlist_node *rlist)
{
 struct net_bridge *br = pmctx->port->br;
 bool del;

 spin_lock(&br->multicast_lock);
 if (pmctx->multicast_router == MDB_RTR_TYPE_DISABLED ||
     pmctx->multicast_router == MDB_RTR_TYPE_PERM ||
     timer_pending(t))
  goto out;

 del = br_multicast_rport_del(rlist);
 br_multicast_rport_del_notify(pmctx, del);
out:
 spin_unlock(&br->multicast_lock);
}

static void br_ip4_multicast_router_expired(struct timer_list *t)
{
 struct net_bridge_mcast_port *pmctx = timer_container_of(pmctx, t,
         ip4_mc_router_timer);

 br_multicast_router_expired(pmctx, t, &pmctx->ip4_rlist);
}

#if IS_ENABLED(CONFIG_IPV6)
static void br_ip6_multicast_router_expired(struct timer_list *t)
{
 struct net_bridge_mcast_port *pmctx = timer_container_of(pmctx, t,
         ip6_mc_router_timer);

 br_multicast_router_expired(pmctx, t, &pmctx->ip6_rlist);
}
#endif

static void br_mc_router_state_change(struct net_bridge *p,
          bool is_mc_router)
{
 struct switchdev_attr attr = {
  .orig_dev = p->dev,
  .id = SWITCHDEV_ATTR_ID_BRIDGE_MROUTER,
  .flags = SWITCHDEV_F_DEFER,
  .u.mrouter = is_mc_router,
 };

 switchdev_port_attr_set(p->dev, &attr, NULL);
}

static void br_multicast_local_router_expired(struct net_bridge_mcast *brmctx,
           struct timer_list *timer)
{
 spin_lock(&brmctx->br->multicast_lock);
 if (brmctx->multicast_router == MDB_RTR_TYPE_DISABLED ||
     brmctx->multicast_router == MDB_RTR_TYPE_PERM ||
     br_ip4_multicast_is_router(brmctx) ||
     br_ip6_multicast_is_router(brmctx))
  goto out;

 br_mc_router_state_change(brmctx->br, false);
out:
 spin_unlock(&brmctx->br->multicast_lock);
}

static void br_ip4_multicast_local_router_expired(struct timer_list *t)
{
 struct net_bridge_mcast *brmctx = timer_container_of(brmctx, t,
            ip4_mc_router_timer);

 br_multicast_local_router_expired(brmctx, t);
}

#if IS_ENABLED(CONFIG_IPV6)
static void br_ip6_multicast_local_router_expired(struct timer_list *t)
{
 struct net_bridge_mcast *brmctx = timer_container_of(brmctx, t,
            ip6_mc_router_timer);

 br_multicast_local_router_expired(brmctx, t);
}
#endif

static void br_multicast_querier_expired(struct net_bridge_mcast *brmctx,
      struct bridge_mcast_own_query *query)
{
 spin_lock(&brmctx->br->multicast_lock);
 if (!netif_running(brmctx->br->dev) ||
     br_multicast_ctx_vlan_global_disabled(brmctx) ||
     !br_opt_get(brmctx->br, BROPT_MULTICAST_ENABLED))
  goto out;

 br_multicast_start_querier(brmctx, query);

out:
 spin_unlock(&brmctx->br->multicast_lock);
}

static void br_ip4_multicast_querier_expired(struct timer_list *t)
{
 struct net_bridge_mcast *brmctx = timer_container_of(brmctx, t,
            ip4_other_query.timer);

 br_multicast_querier_expired(brmctx, &brmctx->ip4_own_query);
}

#if IS_ENABLED(CONFIG_IPV6)
static void br_ip6_multicast_querier_expired(struct timer_list *t)
{
 struct net_bridge_mcast *brmctx = timer_container_of(brmctx, t,
            ip6_other_query.timer);

 br_multicast_querier_expired(brmctx, &brmctx->ip6_own_query);
}
#endif

static void br_multicast_query_delay_expired(struct timer_list *t)
{
}

static void br_multicast_select_own_querier(struct net_bridge_mcast *brmctx,
         struct br_ip *ip,
         struct sk_buff *skb)
{
 if (ip->proto == htons(ETH_P_IP))
  brmctx->ip4_querier.addr.src.ip4 = ip_hdr(skb)->saddr;
#if IS_ENABLED(CONFIG_IPV6)
 else
  brmctx->ip6_querier.addr.src.ip6 = ipv6_hdr(skb)->saddr;
#endif
}

static void __br_multicast_send_query(struct net_bridge_mcast *brmctx,
          struct net_bridge_mcast_port *pmctx,
          struct net_bridge_port_group *pg,
          struct br_ip *ip_dst,
          struct br_ip *group,
          bool with_srcs,
          u8 sflag,
          bool *need_rexmit)
{
 bool over_lmqt = !!sflag;
 struct sk_buff *skb;
 u8 igmp_type;

 if (!br_multicast_ctx_should_use(brmctx, pmctx) ||
     !br_multicast_ctx_matches_vlan_snooping(brmctx))
  return;

again_under_lmqt:
 skb = br_multicast_alloc_query(brmctx, pmctx, pg, ip_dst, group,
           with_srcs, over_lmqt, sflag, &igmp_type,
           need_rexmit);
 if (!skb)
  return;

 if (pmctx) {
  skb->dev = pmctx->port->dev;
  br_multicast_count(brmctx->br, pmctx->port, skb, igmp_type,
       BR_MCAST_DIR_TX);
  NF_HOOK(NFPROTO_BRIDGE, NF_BR_LOCAL_OUT,
   dev_net(pmctx->port->dev), NULL, skb, NULL, skb->dev,
   br_dev_queue_push_xmit);

  if (over_lmqt && with_srcs && sflag) {
   over_lmqt = false;
   goto again_under_lmqt;
  }
 } else {
  br_multicast_select_own_querier(brmctx, group, skb);
  br_multicast_count(brmctx->br, NULL, skb, igmp_type,
       BR_MCAST_DIR_RX);
  netif_rx(skb);
 }
}

static void br_multicast_read_querier(const struct bridge_mcast_querier *querier,
          struct bridge_mcast_querier *dest)
{
 unsigned int seq;

 memset(dest, 0, sizeof(*dest));
 do {
  seq = read_seqcount_begin(&querier->seq);
  dest->port_ifidx = querier->port_ifidx;
  memcpy(&dest->addr, &querier->addr, sizeof(struct br_ip));
 } while (read_seqcount_retry(&querier->seq, seq));
}

static void br_multicast_update_querier(struct net_bridge_mcast *brmctx,
     struct bridge_mcast_querier *querier,
     int ifindex,
     struct br_ip *saddr)
{
 write_seqcount_begin(&querier->seq);
 querier->port_ifidx = ifindex;
 memcpy(&querier->addr, saddr, sizeof(*saddr));
 write_seqcount_end(&querier->seq);
}

static void br_multicast_send_query(struct net_bridge_mcast *brmctx,
        struct net_bridge_mcast_port *pmctx,
        struct bridge_mcast_own_query *own_query)
{
 struct bridge_mcast_other_query *other_query = NULL;
 struct bridge_mcast_querier *querier;
 struct br_ip br_group;
 unsigned long time;

 if (!br_multicast_ctx_should_use(brmctx, pmctx) ||
     !br_opt_get(brmctx->br, BROPT_MULTICAST_ENABLED) ||
     !brmctx->multicast_querier)
  return;

 memset(&br_group.dst, 0, sizeof(br_group.dst));

 if (pmctx ? (own_query == &pmctx->ip4_own_query) :
      (own_query == &brmctx->ip4_own_query)) {
  querier = &brmctx->ip4_querier;
  other_query = &brmctx->ip4_other_query;
  br_group.proto = htons(ETH_P_IP);
#if IS_ENABLED(CONFIG_IPV6)
 } else {
  querier = &brmctx->ip6_querier;
  other_query = &brmctx->ip6_other_query;
  br_group.proto = htons(ETH_P_IPV6);
#endif
 }

 if (!other_query || timer_pending(&other_query->timer))
  return;

 /* we're about to select ourselves as querier */
 if (!pmctx && querier->port_ifidx) {
  struct br_ip zeroip = {};

  br_multicast_update_querier(brmctx, querier, 0, &zeroip);
 }

 __br_multicast_send_query(brmctx, pmctx, NULL, NULL, &br_group, false,
      0, NULL);

 time = jiffies;
 time += own_query->startup_sent < brmctx->multicast_startup_query_count ?
  brmctx->multicast_startup_query_interval :
  brmctx->multicast_query_interval;
 mod_timer(&own_query->timer, time);
}

static void
br_multicast_port_query_expired(struct net_bridge_mcast_port *pmctx,
    struct bridge_mcast_own_query *query)
{
 struct net_bridge *br = pmctx->port->br;
 struct net_bridge_mcast *brmctx;

 spin_lock(&br->multicast_lock);
 if (br_multicast_port_ctx_state_stopped(pmctx))
  goto out;

 brmctx = br_multicast_port_ctx_get_global(pmctx);
 if (query->startup_sent < brmctx->multicast_startup_query_count)
  query->startup_sent++;

 br_multicast_send_query(brmctx, pmctx, query);

out:
 spin_unlock(&br->multicast_lock);
}

static void br_ip4_multicast_port_query_expired(struct timer_list *t)
{
 struct net_bridge_mcast_port *pmctx = timer_container_of(pmctx, t,
         ip4_own_query.timer);

 br_multicast_port_query_expired(pmctx, &pmctx->ip4_own_query);
}

#if IS_ENABLED(CONFIG_IPV6)
static void br_ip6_multicast_port_query_expired(struct timer_list *t)
{
 struct net_bridge_mcast_port *pmctx = timer_container_of(pmctx, t,
         ip6_own_query.timer);

 br_multicast_port_query_expired(pmctx, &pmctx->ip6_own_query);
}
#endif

static void br_multicast_port_group_rexmit(struct timer_list *t)
{
 struct net_bridge_port_group *pg = timer_container_of(pg, t,
             rexmit_timer);
 struct bridge_mcast_other_query *other_query = NULL;
 struct net_bridge *br = pg->key.port->br;
 struct net_bridge_mcast_port *pmctx;
 struct net_bridge_mcast *brmctx;
 bool need_rexmit = false;

 spin_lock(&br->multicast_lock);
 if (!netif_running(br->dev) || hlist_unhashed(&pg->mglist) ||
     !br_opt_get(br, BROPT_MULTICAST_ENABLED))
  goto out;

 pmctx = br_multicast_pg_to_port_ctx(pg);
 if (!pmctx)
  goto out;
 brmctx = br_multicast_port_ctx_get_global(pmctx);
 if (!brmctx->multicast_querier)
  goto out;

 if (pg->key.addr.proto == htons(ETH_P_IP))
  other_query = &brmctx->ip4_other_query;
#if IS_ENABLED(CONFIG_IPV6)
 else
  other_query = &brmctx->ip6_other_query;
#endif

 if (!other_query || timer_pending(&other_query->timer))
  goto out;

 if (pg->grp_query_rexmit_cnt) {
  pg->grp_query_rexmit_cnt--;
  __br_multicast_send_query(brmctx, pmctx, pg, &pg->key.addr,
       &pg->key.addr, false, 1, NULL);
 }
 __br_multicast_send_query(brmctx, pmctx, pg, &pg->key.addr,
      &pg->key.addr, true, 0, &need_rexmit);

 if (pg->grp_query_rexmit_cnt || need_rexmit)
  mod_timer(&pg->rexmit_timer, jiffies +
          brmctx->multicast_last_member_interval);
out:
 spin_unlock(&br->multicast_lock);
}

static int br_mc_disabled_update(struct net_device *dev, bool value,
     struct netlink_ext_ack *extack)
{
 struct switchdev_attr attr = {
  .orig_dev = dev,
  .id = SWITCHDEV_ATTR_ID_BRIDGE_MC_DISABLED,
  .flags = SWITCHDEV_F_DEFER,
  .u.mc_disabled = !value,
 };

 return switchdev_port_attr_set(dev, &attr, extack);
}

void br_multicast_port_ctx_init(struct net_bridge_port *port,
    struct net_bridge_vlan *vlan,
    struct net_bridge_mcast_port *pmctx)
{
 pmctx->port = port;
 pmctx->vlan = vlan;
 pmctx->multicast_router = MDB_RTR_TYPE_TEMP_QUERY;
 timer_setup(&pmctx->ip4_mc_router_timer,
      br_ip4_multicast_router_expired, 0);
 timer_setup(&pmctx->ip4_own_query.timer,
      br_ip4_multicast_port_query_expired, 0);
#if IS_ENABLED(CONFIG_IPV6)
 timer_setup(&pmctx->ip6_mc_router_timer,
      br_ip6_multicast_router_expired, 0);
 timer_setup(&pmctx->ip6_own_query.timer,
      br_ip6_multicast_port_query_expired, 0);
#endif
}

void br_multicast_port_ctx_deinit(struct net_bridge_mcast_port *pmctx)
{
 struct net_bridge *br = pmctx->port->br;
 bool del = false;

#if IS_ENABLED(CONFIG_IPV6)
 timer_delete_sync(&pmctx->ip6_mc_router_timer);
#endif
 timer_delete_sync(&pmctx->ip4_mc_router_timer);

 spin_lock_bh(&br->multicast_lock);
 del |= br_ip6_multicast_rport_del(pmctx);
 del |= br_ip4_multicast_rport_del(pmctx);
 br_multicast_rport_del_notify(pmctx, del);
 spin_unlock_bh(&br->multicast_lock);
}

int br_multicast_add_port(struct net_bridge_port *port)
{
 int err;

 port->multicast_eht_hosts_limit = BR_MCAST_DEFAULT_EHT_HOSTS_LIMIT;
 br_multicast_port_ctx_init(port, NULL, &port->multicast_ctx);

 err = br_mc_disabled_update(port->dev,
        br_opt_get(port->br,
            BROPT_MULTICAST_ENABLED),
        NULL);
 if (err && err != -EOPNOTSUPP)
  return err;

 port->mcast_stats = netdev_alloc_pcpu_stats(struct bridge_mcast_stats);
 if (!port->mcast_stats)
  return -ENOMEM;

 return 0;
}

void br_multicast_del_port(struct net_bridge_port *port)
{
 struct net_bridge *br = port->br;
 struct net_bridge_port_group *pg;
 struct hlist_node *n;

 /* Take care of the remaining groups, only perm ones should be left */
 spin_lock_bh(&br->multicast_lock);
 hlist_for_each_entry_safe(pg, n, &port->mglist, mglist)
  br_multicast_find_del_pg(br, pg);
 spin_unlock_bh(&br->multicast_lock);
 flush_work(&br->mcast_gc_work);
 br_multicast_port_ctx_deinit(&port->multicast_ctx);
 free_percpu(port->mcast_stats);
}

static void br_multicast_enable(struct bridge_mcast_own_query *query)
{
 query->startup_sent = 0;

 if (timer_delete_sync_try(&query->timer) >= 0 ||
     timer_delete(&query->timer))
  mod_timer(&query->timer, jiffies);
}

static void __br_multicast_enable_port_ctx(struct net_bridge_mcast_port *pmctx)
{
 struct net_bridge *br = pmctx->port->br;
 struct net_bridge_mcast *brmctx;

 brmctx = br_multicast_port_ctx_get_global(pmctx);
 if (!br_opt_get(br, BROPT_MULTICAST_ENABLED) ||
     !netif_running(br->dev))
  return;

 br_multicast_enable(&pmctx->ip4_own_query);
#if IS_ENABLED(CONFIG_IPV6)
 br_multicast_enable(&pmctx->ip6_own_query);
#endif
 if (pmctx->multicast_router == MDB_RTR_TYPE_PERM) {
  br_ip4_multicast_add_router(brmctx, pmctx);
  br_ip6_multicast_add_router(brmctx, pmctx);
 }

 if (br_multicast_port_ctx_is_vlan(pmctx)) {
  struct net_bridge_port_group *pg;
  u32 n = 0;

  /* The mcast_n_groups counter might be wrong. First,
 * BR_VLFLAG_MCAST_ENABLED is toggled before temporary entries
 * are flushed, thus mcast_n_groups after the toggle does not
 * reflect the true values. And second, permanent entries added
 * while BR_VLFLAG_MCAST_ENABLED was disabled, are not reflected
 * either. Thus we have to refresh the counter.
 */


  hlist_for_each_entry(pg, &pmctx->port->mglist, mglist) {
   if (pg->key.addr.vid == pmctx->vlan->vid)
    n++;
  }
  WRITE_ONCE(pmctx->mdb_n_entries, n);
 }
}

static void br_multicast_enable_port_ctx(struct net_bridge_mcast_port *pmctx)
{
 struct net_bridge *br = pmctx->port->br;

 spin_lock_bh(&br->multicast_lock);
 if (br_multicast_port_ctx_is_vlan(pmctx) &&
     !(pmctx->vlan->priv_flags & BR_VLFLAG_MCAST_ENABLED)) {
  spin_unlock_bh(&br->multicast_lock);
  return;
 }
 __br_multicast_enable_port_ctx(pmctx);
 spin_unlock_bh(&br->multicast_lock);
}

static void __br_multicast_disable_port_ctx(struct net_bridge_mcast_port *pmctx)
{
 struct net_bridge_port_group *pg;
 struct hlist_node *n;
 bool del = false;

 hlist_for_each_entry_safe(pg, n, &pmctx->port->mglist, mglist)
  if (!(pg->flags & MDB_PG_FLAGS_PERMANENT) &&
      (!br_multicast_port_ctx_is_vlan(pmctx) ||
       pg->key.addr.vid == pmctx->vlan->vid))
   br_multicast_find_del_pg(pmctx->port->br, pg);

 del |= br_ip4_multicast_rport_del(pmctx);
 timer_delete(&pmctx->ip4_mc_router_timer);
 timer_delete(&pmctx->ip4_own_query.timer);
 del |= br_ip6_multicast_rport_del(pmctx);
#if IS_ENABLED(CONFIG_IPV6)
 timer_delete(&pmctx->ip6_mc_router_timer);
 timer_delete(&pmctx->ip6_own_query.timer);
#endif
 br_multicast_rport_del_notify(pmctx, del);
}

static void br_multicast_disable_port_ctx(struct net_bridge_mcast_port *pmctx)
{
 struct net_bridge *br = pmctx->port->br;

 spin_lock_bh(&br->multicast_lock);
 if (br_multicast_port_ctx_is_vlan(pmctx) &&
     !(pmctx->vlan->priv_flags & BR_VLFLAG_MCAST_ENABLED)) {
  spin_unlock_bh(&br->multicast_lock);
  return;
 }

 __br_multicast_disable_port_ctx(pmctx);
 spin_unlock_bh(&br->multicast_lock);
}

static void br_multicast_toggle_port(struct net_bridge_port *port, bool on)
{
#if IS_ENABLED(CONFIG_BRIDGE_VLAN_FILTERING)
 if (br_opt_get(port->br, BROPT_MCAST_VLAN_SNOOPING_ENABLED)) {
  struct net_bridge_vlan_group *vg;
  struct net_bridge_vlan *vlan;

  rcu_read_lock();
  vg = nbp_vlan_group_rcu(port);
  if (!vg) {
   rcu_read_unlock();
   return;
  }

  /* iterate each vlan, toggle vlan multicast context */
  list_for_each_entry_rcu(vlan, &vg->vlan_list, vlist) {
   struct net_bridge_mcast_port *pmctx =
      &vlan->port_mcast_ctx;
   u8 state = br_vlan_get_state(vlan);
   /* enable vlan multicast context when state is
 * LEARNING or FORWARDING
 */

   if (on && br_vlan_state_allowed(state, true))
    br_multicast_enable_port_ctx(pmctx);
   else
    br_multicast_disable_port_ctx(pmctx);
  }
  rcu_read_unlock();
  return;
 }
#endif
 /* toggle port multicast context when vlan snooping is disabled */
 if (on)
  br_multicast_enable_port_ctx(&port->multicast_ctx);
 else
  br_multicast_disable_port_ctx(&port->multicast_ctx);
}

void br_multicast_enable_port(struct net_bridge_port *port)
{
 br_multicast_toggle_port(port, true);
}

void br_multicast_disable_port(struct net_bridge_port *port)
{
 br_multicast_toggle_port(port, false);
}

static int __grp_src_delete_marked(struct net_bridge_port_group *pg)
{
 struct net_bridge_group_src *ent;
 struct hlist_node *tmp;
 int deleted = 0;

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

 return deleted;
}

static void __grp_src_mod_timer(struct net_bridge_group_src *src,
    unsigned long expires)
{
 mod_timer(&src->timer, expires);
 br_multicast_fwd_src_handle(src);
}

static void __grp_src_query_marked_and_rexmit(struct net_bridge_mcast *brmctx,
           struct net_bridge_mcast_port *pmctx,
           struct net_bridge_port_group *pg)
{
 struct bridge_mcast_other_query *other_query = NULL;
 u32 lmqc = brmctx->multicast_last_member_count;
 unsigned long lmqt, lmi, now = jiffies;
 struct net_bridge_group_src *ent;

 if (!netif_running(brmctx->br->dev) ||
     !br_opt_get(brmctx->br, BROPT_MULTICAST_ENABLED))
  return;

 if (pg->key.addr.proto == htons(ETH_P_IP))
  other_query = &brmctx->ip4_other_query;
#if IS_ENABLED(CONFIG_IPV6)
 else
  other_query = &brmctx->ip6_other_query;
#endif

 lmqt = now + br_multicast_lmqt(brmctx);
 hlist_for_each_entry(ent, &pg->src_list, node) {
  if (ent->flags & BR_SGRP_F_SEND) {
   ent->flags &= ~BR_SGRP_F_SEND;
   if (ent->timer.expires > lmqt) {
    if (brmctx->multicast_querier &&
        other_query &&
        !timer_pending(&other_query->timer))
     ent->src_query_rexmit_cnt = lmqc;
    __grp_src_mod_timer(ent, lmqt);
   }
  }
 }

 if (!brmctx->multicast_querier ||
     !other_query || timer_pending(&other_query->timer))
  return;

 __br_multicast_send_query(brmctx, pmctx, pg, &pg->key.addr,
      &pg->key.addr, true, 1, NULL);

 lmi = now + brmctx->multicast_last_member_interval;
 if (!timer_pending(&pg->rexmit_timer) ||
     time_after(pg->rexmit_timer.expires, lmi))
  mod_timer(&pg->rexmit_timer, lmi);
}

static void __grp_send_query_and_rexmit(struct net_bridge_mcast *brmctx,
     struct net_bridge_mcast_port *pmctx,
     struct net_bridge_port_group *pg)
{
 struct bridge_mcast_other_query *other_query = NULL;
 unsigned long now = jiffies, lmi;

 if (!netif_running(brmctx->br->dev) ||
     !br_opt_get(brmctx->br, BROPT_MULTICAST_ENABLED))
  return;

 if (pg->key.addr.proto == htons(ETH_P_IP))
  other_query = &brmctx->ip4_other_query;
#if IS_ENABLED(CONFIG_IPV6)
 else
  other_query = &brmctx->ip6_other_query;
#endif

 if (brmctx->multicast_querier &&
     other_query && !timer_pending(&other_query->timer)) {
  lmi = now + brmctx->multicast_last_member_interval;
  pg->grp_query_rexmit_cnt = brmctx->multicast_last_member_count - 1;
  __br_multicast_send_query(brmctx, pmctx, pg, &pg->key.addr,
       &pg->key.addr, false, 0, NULL);
  if (!timer_pending(&pg->rexmit_timer) ||
      time_after(pg->rexmit_timer.expires, lmi))
   mod_timer(&pg->rexmit_timer, lmi);
 }

 if (pg->filter_mode == MCAST_EXCLUDE &&
     (!timer_pending(&pg->timer) ||
      time_after(pg->timer.expires, now + br_multicast_lmqt(brmctx))))
  mod_timer(&pg->timer, now + br_multicast_lmqt(brmctx));
}

/* State          Msg type      New state                Actions
 * INCLUDE (A)    IS_IN (B)     INCLUDE (A+B)            (B)=GMI
 * INCLUDE (A)    ALLOW (B)     INCLUDE (A+B)            (B)=GMI
 * EXCLUDE (X,Y)  ALLOW (A)     EXCLUDE (X+A,Y-A)        (A)=GMI
 */

static bool br_multicast_isinc_allow(const struct net_bridge_mcast *brmctx,
         struct net_bridge_port_group *pg, void *h_addr,
         void *srcs, u32 nsrcs, size_t addr_size,
         int grec_type)
{
 struct net_bridge_group_src *ent;
 unsigned long now = jiffies;
 bool changed = false;
 struct br_ip src_ip;
 u32 src_idx;

 memset(&src_ip, 0, sizeof(src_ip));
 src_ip.proto = pg->key.addr.proto;
 for (src_idx = 0; src_idx < nsrcs; src_idx++) {
  memcpy(&src_ip.src, srcs + (src_idx * addr_size), addr_size);
  ent = br_multicast_find_group_src(pg, &src_ip);
  if (!ent) {
   ent = br_multicast_new_group_src(pg, &src_ip);
   if (ent)
    changed = true;
  }

  if (ent)
   __grp_src_mod_timer(ent, now + br_multicast_gmi(brmctx));
 }

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

 return changed;
}

/* State          Msg type      New state                Actions
 * INCLUDE (A)    IS_EX (B)     EXCLUDE (A*B,B-A)        (B-A)=0
 *                                                       Delete (A-B)
 *                                                       Group Timer=GMI
 */

static void __grp_src_isexc_incl(const struct net_bridge_mcast *brmctx,
     struct net_bridge_port_group *pg, void *h_addr,
     void *srcs, u32 nsrcs, size_t addr_size,
     int grec_type)
{
 struct net_bridge_group_src *ent;
 struct br_ip src_ip;
 u32 src_idx;

 hlist_for_each_entry(ent, &pg->src_list, node)
  ent->flags |= BR_SGRP_F_DELETE;

 memset(&src_ip, 0, sizeof(src_ip));
 src_ip.proto = pg->key.addr.proto;
 for (src_idx = 0; src_idx < nsrcs; src_idx++) {
  memcpy(&src_ip.src, srcs + (src_idx * addr_size), addr_size);
  ent = br_multicast_find_group_src(pg, &src_ip);
  if (ent)
   ent->flags &= ~BR_SGRP_F_DELETE;
  else
   ent = br_multicast_new_group_src(pg, &src_ip);
  if (ent)
   br_multicast_fwd_src_handle(ent);
 }

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

--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=98 H=96 G=96

¤ Dauer der Verarbeitung: 0.28 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

letze Version des Elbe Quellennavigators

     letzte wissenschaftliche Artikel weltweit
     Neues von dieser Firma

letze Version des Agenda Kalenders

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

letze Version der Autor Authoringsoftware

     letze Version des Demonstrationsprogramms Goedel
     letze Version des Bille Abgleichprogramms
     Bilder

Jenseits des Üblichen ....
    

Besucher

Besucher

Monitoring

Montastic status badge