bridge: mcast: Add support for (*, G) with a source list and filter mode
[platform/kernel/linux-starfive.git] / net / bridge / br_mdb.c
index 7cda9d1..e9a4b7e 100644 (file)
@@ -836,6 +836,114 @@ static int br_mdb_add_group_sg(const struct br_mdb_config *cfg,
        return 0;
 }
 
+static int br_mdb_add_group_src_fwd(const struct br_mdb_config *cfg,
+                                   struct br_ip *src_ip,
+                                   struct net_bridge_mcast *brmctx,
+                                   struct netlink_ext_ack *extack)
+{
+       struct net_bridge_mdb_entry *sgmp;
+       struct br_mdb_config sg_cfg;
+       struct br_ip sg_ip;
+       u8 flags = 0;
+
+       sg_ip = cfg->group;
+       sg_ip.src = src_ip->src;
+       sgmp = br_multicast_new_group(cfg->br, &sg_ip);
+       if (IS_ERR(sgmp)) {
+               NL_SET_ERR_MSG_MOD(extack, "Failed to add (S, G) MDB entry");
+               return PTR_ERR(sgmp);
+       }
+
+       if (cfg->entry->state == MDB_PERMANENT)
+               flags |= MDB_PG_FLAGS_PERMANENT;
+       if (cfg->filter_mode == MCAST_EXCLUDE)
+               flags |= MDB_PG_FLAGS_BLOCKED;
+
+       memset(&sg_cfg, 0, sizeof(sg_cfg));
+       sg_cfg.br = cfg->br;
+       sg_cfg.p = cfg->p;
+       sg_cfg.entry = cfg->entry;
+       sg_cfg.group = sg_ip;
+       sg_cfg.src_entry = true;
+       sg_cfg.filter_mode = MCAST_INCLUDE;
+       return br_mdb_add_group_sg(&sg_cfg, sgmp, brmctx, flags, extack);
+}
+
+static int br_mdb_add_group_src(const struct br_mdb_config *cfg,
+                               struct net_bridge_port_group *pg,
+                               struct net_bridge_mcast *brmctx,
+                               struct br_mdb_src_entry *src,
+                               struct netlink_ext_ack *extack)
+{
+       struct net_bridge_group_src *ent;
+       unsigned long now = jiffies;
+       int err;
+
+       ent = br_multicast_find_group_src(pg, &src->addr);
+       if (!ent) {
+               ent = br_multicast_new_group_src(pg, &src->addr);
+               if (!ent) {
+                       NL_SET_ERR_MSG_MOD(extack, "Failed to add new source entry");
+                       return -ENOSPC;
+               }
+       } else {
+               NL_SET_ERR_MSG_MOD(extack, "Source entry already exists");
+               return -EEXIST;
+       }
+
+       if (cfg->filter_mode == MCAST_INCLUDE &&
+           cfg->entry->state == MDB_TEMPORARY)
+               mod_timer(&ent->timer, now + br_multicast_gmi(brmctx));
+       else
+               del_timer(&ent->timer);
+
+       /* Install a (S, G) forwarding entry for the source. */
+       err = br_mdb_add_group_src_fwd(cfg, &src->addr, brmctx, extack);
+       if (err)
+               goto err_del_sg;
+
+       ent->flags = BR_SGRP_F_INSTALLED | BR_SGRP_F_USER_ADDED;
+
+       return 0;
+
+err_del_sg:
+       __br_multicast_del_group_src(ent);
+       return err;
+}
+
+static void br_mdb_del_group_src(struct net_bridge_port_group *pg,
+                                struct br_mdb_src_entry *src)
+{
+       struct net_bridge_group_src *ent;
+
+       ent = br_multicast_find_group_src(pg, &src->addr);
+       if (WARN_ON_ONCE(!ent))
+               return;
+       br_multicast_del_group_src(ent, false);
+}
+
+static int br_mdb_add_group_srcs(const struct br_mdb_config *cfg,
+                                struct net_bridge_port_group *pg,
+                                struct net_bridge_mcast *brmctx,
+                                struct netlink_ext_ack *extack)
+{
+       int i, err;
+
+       for (i = 0; i < cfg->num_src_entries; i++) {
+               err = br_mdb_add_group_src(cfg, pg, brmctx,
+                                          &cfg->src_entries[i], extack);
+               if (err)
+                       goto err_del_group_srcs;
+       }
+
+       return 0;
+
+err_del_group_srcs:
+       for (i--; i >= 0; i--)
+               br_mdb_del_group_src(pg, &cfg->src_entries[i]);
+       return err;
+}
+
 static int br_mdb_add_group_star_g(const struct br_mdb_config *cfg,
                                   struct net_bridge_mdb_entry *mp,
                                   struct net_bridge_mcast *brmctx,
@@ -845,6 +953,7 @@ static int br_mdb_add_group_star_g(const struct br_mdb_config *cfg,
        struct net_bridge_port_group __rcu **pp;
        struct net_bridge_port_group *p;
        unsigned long now = jiffies;
+       int err;
 
        for (pp = &mp->ports;
             (p = mlock_dereference(*pp, cfg->br)) != NULL;
@@ -858,23 +967,35 @@ static int br_mdb_add_group_star_g(const struct br_mdb_config *cfg,
        }
 
        p = br_multicast_new_port_group(cfg->p, &cfg->group, *pp, flags, NULL,
-                                       MCAST_EXCLUDE, RTPROT_STATIC);
+                                       cfg->filter_mode, RTPROT_STATIC);
        if (unlikely(!p)) {
                NL_SET_ERR_MSG_MOD(extack, "Couldn't allocate new (*, G) port group");
                return -ENOMEM;
        }
+
+       err = br_mdb_add_group_srcs(cfg, p, brmctx, extack);
+       if (err)
+               goto err_del_port_group;
+
        rcu_assign_pointer(*pp, p);
-       if (!(flags & MDB_PG_FLAGS_PERMANENT))
+       if (!(flags & MDB_PG_FLAGS_PERMANENT) &&
+           cfg->filter_mode == MCAST_EXCLUDE)
                mod_timer(&p->timer,
                          now + brmctx->multicast_membership_interval);
        br_mdb_notify(cfg->br->dev, mp, p, RTM_NEWMDB);
        /* If we are adding a new EXCLUDE port group (*, G), it needs to be
         * also added to all (S, G) entries for proper replication.
         */
-       if (br_multicast_should_handle_mode(brmctx, cfg->group.proto))
+       if (br_multicast_should_handle_mode(brmctx, cfg->group.proto) &&
+           cfg->filter_mode == MCAST_EXCLUDE)
                br_multicast_star_g_handle_mode(p, MCAST_EXCLUDE);
 
        return 0;
+
+err_del_port_group:
+       hlist_del_init(&p->mglist);
+       kfree(p);
+       return err;
 }
 
 static int br_mdb_add_group(const struct br_mdb_config *cfg,
@@ -967,6 +1088,7 @@ static int br_mdb_config_init(struct net *net, const struct nlmsghdr *nlh,
                return err;
 
        memset(cfg, 0, sizeof(*cfg));
+       cfg->filter_mode = MCAST_EXCLUDE;
 
        bpm = nlmsg_data(nlh);
        if (!bpm->ifindex) {