Move functionality for multicast and IGMP into new module. There
isn't any functional change just functions being moved around.
This is preparation for multicast and IGMP I-P node.

Acked-by: Lorenzo Bianconi <lorenzo.bianc...@redhat.com>
Co-authored-by: Jacob Tanenbaum <jtane...@redhat.com>
Signed-off-by: Jacob Tanenbaum <jtane...@redhat.com>
Signed-off-by: Ales Musil <amu...@redhat.com>
---
v3: Rebase on top of latest main.
---
 northd/automake.mk    |   2 +
 northd/en-multicast.c | 662 +++++++++++++++++++++++++++++++++++++++++
 northd/en-multicast.h |  92 ++++++
 northd/northd.c       | 672 +-----------------------------------------
 northd/northd.h       |  24 ++
 5 files changed, 794 insertions(+), 658 deletions(-)
 create mode 100644 northd/en-multicast.c
 create mode 100644 northd/en-multicast.h

diff --git a/northd/automake.mk b/northd/automake.mk
index 8e17e77fe..c1641dbae 100644
--- a/northd/automake.mk
+++ b/northd/automake.mk
@@ -18,6 +18,8 @@ northd_ovn_northd_SOURCES = \
        northd/en-lflow.h \
        northd/en-meters.c \
        northd/en-meters.h \
+       northd/en-multicast.c \
+       northd/en-multicast.h \
        northd/en-northd-output.c \
        northd/en-northd-output.h \
        northd/en-port-group.c \
diff --git a/northd/en-multicast.c b/northd/en-multicast.c
new file mode 100644
index 000000000..deb192a82
--- /dev/null
+++ b/northd/en-multicast.c
@@ -0,0 +1,662 @@
+/*
+ * Copyright (c) 2025, Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+/* OVS includes. */
+#include "openvswitch/hmap.h"
+#include "openvswitch/vlog.h"
+
+/* OVN includes. */
+#include "en-multicast.h"
+#include "lib/ip-mcast-index.h"
+#include "lib/mcast-group-index.h"
+#include "lib/ovn-l7.h"
+#include "lib/ovn-nb-idl.h"
+#include "lib/ovn-sb-idl.h"
+#include "lib/ovn-util.h"
+#include "northd.h"
+
+VLOG_DEFINE_THIS_MODULE(en_multicast);
+
+static const struct multicast_group mc_flood =
+    { MC_FLOOD, OVN_MCAST_FLOOD_TUNNEL_KEY };
+
+static const struct multicast_group mc_mrouter_flood =
+    { MC_MROUTER_FLOOD, OVN_MCAST_MROUTER_FLOOD_TUNNEL_KEY };
+
+static const struct multicast_group mc_static =
+    { MC_STATIC, OVN_MCAST_STATIC_TUNNEL_KEY };
+
+static const struct multicast_group mc_unknown =
+    { MC_UNKNOWN, OVN_MCAST_UNKNOWN_TUNNEL_KEY };
+
+static const struct multicast_group mc_flood_l2 =
+    { MC_FLOOD_L2, OVN_MCAST_FLOOD_L2_TUNNEL_KEY };
+
+static bool multicast_group_equal(const struct multicast_group *,
+                                  const struct multicast_group *);
+static uint32_t ovn_multicast_hash(const struct ovn_datapath *,
+                                   const struct multicast_group *);
+static struct ovn_multicast *ovn_multicast_find(
+    struct hmap *mcgroups, struct ovn_datapath *,
+    const struct multicast_group *);
+static void ovn_multicast_add_ports(struct hmap *mcgroups,
+                                    struct ovn_datapath *,
+                                    const struct multicast_group *,
+                                    struct ovn_port **ports, size_t n_ports);
+static void ovn_multicast_add(struct hmap *mcgroups,
+                              const struct multicast_group *,
+                              struct ovn_port *);
+static void ovn_multicast_destroy(struct hmap *mcgroups,
+                                  struct ovn_multicast *);
+static void ovn_multicast_update_sbrec(const struct ovn_multicast *,
+                                       const struct sbrec_multicast_group *);
+
+static uint32_t ovn_igmp_group_hash(const struct ovn_datapath *,
+                                    const struct in6_addr *);
+static struct ovn_igmp_group * ovn_igmp_group_find(struct hmap *igmp_groups,
+                                                   const struct ovn_datapath *,
+                                                   const struct in6_addr *);
+static struct ovn_igmp_group *ovn_igmp_group_add(
+    struct ovsdb_idl_index *sbrec_mcast_group_by_name_dp,
+    struct hmap *igmp_groups, struct ovn_datapath *,
+    const struct in6_addr *, const char *address_s);
+static struct ovn_port **ovn_igmp_group_get_ports(
+    const struct sbrec_igmp_group *, size_t *n_ports,
+    const struct hmap *ls_ports);
+static void ovn_igmp_group_add_entry(struct ovn_igmp_group *,
+                                     struct ovn_port **ports, size_t n_ports);
+static void ovn_igmp_group_destroy_entry(struct ovn_igmp_group_entry *);
+static bool ovn_igmp_group_allocate_id(struct ovn_igmp_group *);
+static void ovn_igmp_mrouter_aggregate_ports(struct ovn_igmp_group *,
+                                             struct hmap *mcast_groups);
+static void ovn_igmp_group_aggregate_ports(struct ovn_igmp_group *,
+                                           struct hmap *mcast_groups);
+static void ovn_igmp_group_destroy(struct hmap *igmp_groups,
+                                   struct ovn_igmp_group *);
+
+void
+build_mcast_groups(const struct sbrec_igmp_group_table *sbrec_igmp_group_table,
+                   struct ovsdb_idl_index *sbrec_mcast_group_by_name_dp,
+                   const struct hmap *ls_datapaths,
+                   const struct hmap *ls_ports,
+                   const struct hmap *lr_ports,
+                   struct hmap *mcast_groups,
+                   struct hmap *igmp_groups)
+{
+    struct ovn_datapath *od;
+    struct ovn_port *op;
+
+    hmap_init(mcast_groups);
+    hmap_init(igmp_groups);
+
+    HMAP_FOR_EACH (op, key_node, lr_ports) {
+        if (lrport_is_enabled(op->nbrp)) {
+            /* If this port is configured to always flood multicast traffic
+             * add it to the MC_STATIC group.
+             */
+            if (op->mcast_info.flood) {
+                ovn_multicast_add(mcast_groups, &mc_static, op);
+                op->od->mcast_info.rtr.flood_static = true;
+            }
+        }
+    }
+
+    HMAP_FOR_EACH (op, key_node, ls_ports) {
+        if (lsp_is_enabled(op->nbsp)) {
+            ovn_multicast_add(mcast_groups, &mc_flood, op);
+
+            if (!lsp_is_router(op->nbsp)) {
+                ovn_multicast_add(mcast_groups, &mc_flood_l2, op);
+            }
+
+            if (op->has_unknown) {
+                ovn_multicast_add(mcast_groups, &mc_unknown, op);
+            }
+
+            /* If this port is connected to a multicast router then add it
+             * to the MC_MROUTER_FLOOD group.
+             */
+            if (op->od->mcast_info.sw.flood_relay && op->peer &&
+                op->peer->od && op->peer->od->mcast_info.rtr.relay) {
+                ovn_multicast_add(mcast_groups, &mc_mrouter_flood, op);
+            }
+
+            /* If this port is configured to always flood multicast reports
+             * add it to the MC_MROUTER_FLOOD group (all reports must be
+             * flooded to statically configured or learned mrouters).
+             */
+            if (op->mcast_info.flood_reports) {
+                ovn_multicast_add(mcast_groups, &mc_mrouter_flood, op);
+                op->od->mcast_info.sw.flood_reports = true;
+            }
+
+            /* If this port is configured to always flood multicast traffic
+             * add it to the MC_STATIC group.
+             */
+            if (op->mcast_info.flood) {
+                ovn_multicast_add(mcast_groups, &mc_static, op);
+                op->od->mcast_info.sw.flood_static = true;
+            }
+        }
+    }
+
+    const struct sbrec_igmp_group *sb_igmp;
+
+    SBREC_IGMP_GROUP_TABLE_FOR_EACH_SAFE (sb_igmp, sbrec_igmp_group_table) {
+        /* If this is a stale group (e.g., controller had crashed,
+         * purge it).
+         */
+        if (!sb_igmp->chassis || !sb_igmp->datapath) {
+            sbrec_igmp_group_delete(sb_igmp);
+            continue;
+        }
+
+        /* If the datapath value is stale, purge the group. */
+        od = ovn_datapath_from_sbrec(ls_datapaths, NULL,
+                                     sb_igmp->datapath);
+
+        if (!od || ovn_datapath_is_stale(od)) {
+            sbrec_igmp_group_delete(sb_igmp);
+            continue;
+        }
+
+        struct in6_addr group_address;
+        if (!strcmp(sb_igmp->address, OVN_IGMP_GROUP_MROUTERS)) {
+            /* Use all-zeros IP to denote a group corresponding to mrouters. */
+            memset(&group_address, 0, sizeof group_address);
+        } else if (!ip46_parse(sb_igmp->address, &group_address)) {
+            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
+            VLOG_WARN_RL(&rl, "invalid IGMP group address: %s",
+                         sb_igmp->address);
+            continue;
+        }
+
+        /* Extract the IGMP group ports from the SB entry. */
+        size_t n_igmp_ports;
+        struct ovn_port **igmp_ports =
+            ovn_igmp_group_get_ports(sb_igmp, &n_igmp_ports, ls_ports);
+
+        /* It can be that all ports in the IGMP group record already have
+         * mcast_flood=true and then we can skip the group completely.
+         */
+        if (!igmp_ports) {
+            continue;
+        }
+
+        /* Add the IGMP group entry. Will also try to allocate an ID for it
+         * if the multicast group already exists.
+         */
+        struct ovn_igmp_group *igmp_group =
+            ovn_igmp_group_add(sbrec_mcast_group_by_name_dp, igmp_groups, od,
+                               &group_address, sb_igmp->address);
+
+        /* Add the extracted ports to the IGMP group. */
+        ovn_igmp_group_add_entry(igmp_group, igmp_ports, n_igmp_ports);
+    }
+
+    /* Build IGMP groups for multicast routers with relay enabled. The router
+     * IGMP groups are based on the groups learnt by their multicast enabled
+     * peers.
+     */
+    HMAP_FOR_EACH (od, key_node, ls_datapaths) {
+
+        if (ovs_list_is_empty(&od->mcast_info.groups)) {
+            continue;
+        }
+
+        for (size_t i = 0; i < od->n_router_ports; i++) {
+            struct ovn_port *router_port = od->router_ports[i]->peer;
+
+            /* If the router the port connects to doesn't have multicast
+             * relay enabled or if it was already configured to flood
+             * multicast traffic then skip it.
+             */
+            if (!router_port || !router_port->od ||
+                !router_port->od->mcast_info.rtr.relay ||
+                router_port->mcast_info.flood) {
+                continue;
+            }
+
+            struct ovn_igmp_group *igmp_group;
+            LIST_FOR_EACH (igmp_group, list_node, &od->mcast_info.groups) {
+                struct in6_addr *address = &igmp_group->address;
+
+                /* Skip mrouter entries. */
+                if (!strcmp(igmp_group->mcgroup.name,
+                            OVN_IGMP_GROUP_MROUTERS)) {
+                    continue;
+                }
+
+                /* For IPv6 only relay routable multicast groups
+                 * (RFC 4291 2.7).
+                 */
+                if (!IN6_IS_ADDR_V4MAPPED(address) &&
+                    !ipv6_addr_is_routable_multicast(address)) {
+                    continue;
+                }
+
+                struct ovn_igmp_group *igmp_group_rtr =
+                    ovn_igmp_group_add(sbrec_mcast_group_by_name_dp,
+                                       igmp_groups, router_port->od,
+                                       address, igmp_group->mcgroup.name);
+                struct ovn_port **router_igmp_ports =
+                    xmalloc(sizeof *router_igmp_ports);
+                /* Store the chassis redirect port  otherwise traffic will not
+                 * be tunneled properly.
+                 */
+                router_igmp_ports[0] = router_port->cr_port
+                                       ? router_port->cr_port
+                                       : router_port;
+                ovn_igmp_group_add_entry(igmp_group_rtr, router_igmp_ports, 1);
+            }
+        }
+    }
+
+    /* Walk the aggregated IGMP groups and allocate IDs for new entries.
+     * Then store the ports in the associated multicast group.
+     * Mrouter entries are also stored as IGMP groups, deal with those
+     * explicitly.
+     */
+    struct ovn_igmp_group *igmp_group;
+    HMAP_FOR_EACH_SAFE (igmp_group, hmap_node, igmp_groups) {
+
+        /* If this is a mrouter entry just aggregate the mrouter ports
+         * into the MC_MROUTER mcast_group and destroy the igmp_group;
+         * no more processing needed. */
+        if (!strcmp(igmp_group->mcgroup.name, OVN_IGMP_GROUP_MROUTERS)) {
+            ovn_igmp_mrouter_aggregate_ports(igmp_group, mcast_groups);
+            ovn_igmp_group_destroy(igmp_groups, igmp_group);
+            continue;
+        }
+
+        if (!ovn_igmp_group_allocate_id(igmp_group)) {
+            /* If we ran out of keys just destroy the entry. */
+            ovn_igmp_group_destroy(igmp_groups, igmp_group);
+            continue;
+        }
+
+        /* Aggregate the ports from all entries corresponding to this
+         * group.
+         */
+        ovn_igmp_group_aggregate_ports(igmp_group, mcast_groups);
+    }
+}
+
+void
+sync_multicast_groups_to_sb(
+    struct ovsdb_idl_txn *ovnsb_txn,
+    const struct sbrec_multicast_group_table *sbrec_multicast_group_table,
+    const struct hmap * ls_datapaths, const struct hmap *lr_datapaths,
+    struct hmap *mcast_groups)
+{
+    /* Push changes to the Multicast_Group table to database. */
+    const struct sbrec_multicast_group *sbmc;
+    SBREC_MULTICAST_GROUP_TABLE_FOR_EACH_SAFE (
+        sbmc, sbrec_multicast_group_table) {
+        struct ovn_datapath *od = ovn_datapath_from_sbrec(ls_datapaths,
+                                                          lr_datapaths,
+                                                          sbmc->datapath);
+
+        if (!od || ovn_datapath_is_stale(od)) {
+            sbrec_multicast_group_delete(sbmc);
+            continue;
+        }
+
+        struct multicast_group group = { .name = sbmc->name,
+            .key = sbmc->tunnel_key };
+        struct ovn_multicast *mc = ovn_multicast_find(mcast_groups,
+                                                      od, &group);
+        if (mc) {
+            ovn_multicast_update_sbrec(mc, sbmc);
+            ovn_multicast_destroy(mcast_groups, mc);
+        } else {
+            sbrec_multicast_group_delete(sbmc);
+        }
+    }
+    struct ovn_multicast *mc;
+    HMAP_FOR_EACH_SAFE (mc, hmap_node, mcast_groups) {
+        if (!mc->datapath) {
+            ovn_multicast_destroy(mcast_groups, mc);
+            continue;
+        }
+        sbmc = create_sb_multicast_group(ovnsb_txn, mc->datapath->sb,
+                                         mc->group->name, mc->group->key);
+        ovn_multicast_update_sbrec(mc, sbmc);
+        ovn_multicast_destroy(mcast_groups, mc);
+    }
+
+    hmap_destroy(mcast_groups);
+}
+
+void
+ovn_igmp_groups_destroy(struct hmap *igmp_groups)
+{
+    struct ovn_igmp_group *igmp_group;
+    HMAP_FOR_EACH_SAFE (igmp_group, hmap_node, igmp_groups) {
+        ovn_igmp_group_destroy(igmp_groups, igmp_group);
+    }
+    hmap_destroy(igmp_groups);
+}
+
+struct sbrec_multicast_group *
+create_sb_multicast_group(struct ovsdb_idl_txn *ovnsb_txn,
+                          const struct sbrec_datapath_binding *dp,
+                          const char *name,
+                          int64_t tunnel_key)
+{
+    struct sbrec_multicast_group *sbmc =
+        sbrec_multicast_group_insert(ovnsb_txn);
+    sbrec_multicast_group_set_datapath(sbmc, dp);
+    sbrec_multicast_group_set_name(sbmc, name);
+    sbrec_multicast_group_set_tunnel_key(sbmc, tunnel_key);
+    return sbmc;
+}
+
+
+static bool
+multicast_group_equal(const struct multicast_group *a,
+                      const struct multicast_group *b)
+{
+    return !strcmp(a->name, b->name) && a->key == b->key;
+}
+
+
+static uint32_t
+ovn_multicast_hash(const struct ovn_datapath *datapath,
+                   const struct multicast_group *group)
+{
+    return hash_pointer(datapath, group->key);
+}
+
+static struct ovn_multicast *
+ovn_multicast_find(struct hmap *mcgroups, struct ovn_datapath *datapath,
+                   const struct multicast_group *group)
+{
+    struct ovn_multicast *mc;
+
+    HMAP_FOR_EACH_WITH_HASH (mc, hmap_node,
+                             ovn_multicast_hash(datapath, group), mcgroups) {
+        if (mc->datapath == datapath
+            && multicast_group_equal(mc->group, group)) {
+            return mc;
+        }
+    }
+    return NULL;
+}
+
+static void
+ovn_multicast_add_ports(struct hmap *mcgroups, struct ovn_datapath *od,
+                        const struct multicast_group *group,
+                        struct ovn_port **ports, size_t n_ports)
+{
+    struct ovn_multicast *mc = ovn_multicast_find(mcgroups, od, group);
+    if (!mc) {
+        mc = xmalloc(sizeof *mc);
+        hmap_insert(mcgroups, &mc->hmap_node, ovn_multicast_hash(od, group));
+        mc->datapath = od;
+        mc->group = group;
+        mc->n_ports = 0;
+        mc->allocated_ports = 4;
+        mc->ports = xmalloc(mc->allocated_ports * sizeof *mc->ports);
+    }
+
+    size_t n_ports_total = mc->n_ports + n_ports;
+
+    if (n_ports_total > 2 * mc->allocated_ports) {
+        mc->allocated_ports = n_ports_total;
+        mc->ports = xrealloc(mc->ports,
+                             mc->allocated_ports * sizeof *mc->ports);
+    } else if (n_ports_total > mc->allocated_ports) {
+        mc->ports = x2nrealloc(mc->ports, &mc->allocated_ports,
+                               sizeof *mc->ports);
+    }
+
+    memcpy(&mc->ports[mc->n_ports], &ports[0], n_ports * sizeof *ports);
+    mc->n_ports += n_ports;
+}
+
+static void
+ovn_multicast_add(struct hmap *mcgroups, const struct multicast_group *group,
+                  struct ovn_port *port)
+{
+    /* Store the chassis redirect port otherwise traffic will not be tunneled
+     * properly.
+     */
+    if (port->cr_port) {
+        port = port->cr_port;
+    }
+    ovn_multicast_add_ports(mcgroups, port->od, group, &port, 1);
+}
+
+static void
+ovn_multicast_destroy(struct hmap *mcgroups, struct ovn_multicast *mc)
+{
+    if (mc) {
+        hmap_remove(mcgroups, &mc->hmap_node);
+        free(mc->ports);
+        free(mc);
+    }
+}
+
+static void
+ovn_multicast_update_sbrec(const struct ovn_multicast *mc,
+                           const struct sbrec_multicast_group *sb)
+{
+    struct sbrec_port_binding **ports = xmalloc(mc->n_ports * sizeof *ports);
+    for (size_t i = 0; i < mc->n_ports; i++) {
+        ports[i] = CONST_CAST(struct sbrec_port_binding *, mc->ports[i]->sb);
+    }
+    sbrec_multicast_group_set_ports(sb, ports, mc->n_ports);
+    free(ports);
+}
+
+static uint32_t
+ovn_igmp_group_hash(const struct ovn_datapath *datapath,
+                    const struct in6_addr *address)
+{
+    return hash_pointer(datapath, hash_bytes(address, sizeof *address, 0));
+}
+
+static struct ovn_igmp_group *
+ovn_igmp_group_find(struct hmap *igmp_groups,
+                    const struct ovn_datapath *datapath,
+                    const struct in6_addr *address)
+{
+    struct ovn_igmp_group *group;
+
+    HMAP_FOR_EACH_WITH_HASH (group, hmap_node,
+                             ovn_igmp_group_hash(datapath, address),
+                             igmp_groups) {
+        if (group->datapath == datapath &&
+            ipv6_addr_equals(&group->address, address)) {
+            return group;
+        }
+    }
+    return NULL;
+}
+
+static struct ovn_igmp_group *
+ovn_igmp_group_add(struct ovsdb_idl_index *sbrec_mcast_group_by_name_dp,
+                   struct hmap *igmp_groups,
+                   struct ovn_datapath *datapath,
+                   const struct in6_addr *address,
+                   const char *address_s)
+{
+    struct ovn_igmp_group *igmp_group =
+        ovn_igmp_group_find(igmp_groups, datapath, address);
+
+    if (!igmp_group) {
+        igmp_group = xmalloc(sizeof *igmp_group);
+
+        const struct sbrec_multicast_group *mcgroup =
+            mcast_group_lookup(sbrec_mcast_group_by_name_dp,
+                               address_s,
+                               datapath->sb);
+
+        igmp_group->datapath = datapath;
+        igmp_group->address = *address;
+        if (mcgroup) {
+            igmp_group->mcgroup.key = mcgroup->tunnel_key;
+            ovn_add_tnlid(&datapath->mcast_info.group_tnlids,
+                          mcgroup->tunnel_key);
+        } else {
+            igmp_group->mcgroup.key = 0;
+        }
+        igmp_group->mcgroup.name = address_s;
+        ovs_list_init(&igmp_group->entries);
+
+        hmap_insert(igmp_groups, &igmp_group->hmap_node,
+                    ovn_igmp_group_hash(datapath, address));
+        ovs_list_push_back(&datapath->mcast_info.groups,
+                           &igmp_group->list_node);
+    }
+
+    return igmp_group;
+}
+
+static struct ovn_port **
+ovn_igmp_group_get_ports(const struct sbrec_igmp_group *sb_igmp_group,
+                         size_t *n_ports, const struct hmap *ls_ports)
+{
+    struct ovn_port **ports = NULL;
+
+    *n_ports = 0;
+    for (size_t i = 0; i < sb_igmp_group->n_ports; i++) {
+        struct ovn_port *port =
+            ovn_port_find(ls_ports, sb_igmp_group->ports[i]->logical_port);
+
+        if (!port || !port->nbsp) {
+            continue;
+        }
+
+        /* If this is already a flood port skip it for the group. */
+        if (port->mcast_info.flood) {
+            continue;
+        }
+
+        /* If this is already a port of a router on which relay is enabled,
+         * skip it for the group. Traffic is flooded there anyway.
+         */
+        if (port->peer && port->peer->od &&
+            port->peer->od->mcast_info.rtr.relay) {
+            continue;
+        }
+
+        if (ports == NULL) {
+            ports = xmalloc(sb_igmp_group->n_ports * sizeof *ports);
+        }
+
+        ports[(*n_ports)] = port;
+        (*n_ports)++;
+    }
+
+    return ports;
+}
+
+static void
+ovn_igmp_group_add_entry(struct ovn_igmp_group *igmp_group,
+                         struct ovn_port **ports, size_t n_ports)
+{
+    struct ovn_igmp_group_entry *entry = xmalloc(sizeof *entry);
+
+    entry->ports = ports;
+    entry->n_ports = n_ports;
+    ovs_list_push_back(&igmp_group->entries, &entry->list_node);
+}
+
+static void
+ovn_igmp_group_destroy_entry(struct ovn_igmp_group_entry *entry)
+{
+    free(entry->ports);
+}
+
+static bool
+ovn_igmp_group_allocate_id(struct ovn_igmp_group *igmp_group)
+{
+    if (igmp_group->mcgroup.key == 0) {
+        struct hmap *tnlids = &igmp_group->datapath->mcast_info.group_tnlids;
+        uint32_t tnlid_hint =
+            igmp_group->datapath->mcast_info.group_tnlid_hint;
+        igmp_group->mcgroup.key = ovn_allocate_tnlid(tnlids, "multicast group",
+                                                     OVN_MIN_IP_MULTICAST,
+                                                     OVN_MAX_IP_MULTICAST,
+                                                     &tnlid_hint);
+    }
+
+    if (igmp_group->mcgroup.key == 0) {
+        return false;
+    }
+
+    return true;
+}
+
+static void
+ovn_igmp_mrouter_aggregate_ports(struct ovn_igmp_group *igmp_group,
+                                 struct hmap *mcast_groups)
+{
+    struct ovn_igmp_group_entry *entry;
+
+    LIST_FOR_EACH_POP (entry, list_node, &igmp_group->entries) {
+        ovn_multicast_add_ports(mcast_groups, igmp_group->datapath,
+                                &mc_mrouter_flood, entry->ports,
+                                entry->n_ports);
+
+        ovn_igmp_group_destroy_entry(entry);
+        free(entry);
+    }
+}
+
+static void
+ovn_igmp_group_aggregate_ports(struct ovn_igmp_group *igmp_group,
+                               struct hmap *mcast_groups)
+{
+    struct ovn_igmp_group_entry *entry;
+
+    LIST_FOR_EACH_POP (entry, list_node, &igmp_group->entries) {
+        ovn_multicast_add_ports(mcast_groups, igmp_group->datapath,
+                                &igmp_group->mcgroup, entry->ports,
+                                entry->n_ports);
+
+        ovn_igmp_group_destroy_entry(entry);
+        free(entry);
+    }
+
+    if (igmp_group->datapath->n_localnet_ports) {
+        ovn_multicast_add_ports(mcast_groups, igmp_group->datapath,
+                                &igmp_group->mcgroup,
+                                igmp_group->datapath->localnet_ports,
+                                igmp_group->datapath->n_localnet_ports);
+    }
+}
+
+static void
+ovn_igmp_group_destroy(struct hmap *igmp_groups,
+                       struct ovn_igmp_group *igmp_group)
+{
+    if (igmp_group) {
+        struct ovn_igmp_group_entry *entry;
+
+        LIST_FOR_EACH_POP (entry, list_node, &igmp_group->entries) {
+            ovn_igmp_group_destroy_entry(entry);
+            free(entry);
+        }
+        hmap_remove(igmp_groups, &igmp_group->hmap_node);
+        ovs_list_remove(&igmp_group->list_node);
+        free(igmp_group);
+    }
+}
diff --git a/northd/en-multicast.h b/northd/en-multicast.h
new file mode 100644
index 000000000..5fa4d8976
--- /dev/null
+++ b/northd/en-multicast.h
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) 2025, Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OVN_EN_MULTICAST_H
+#define OVN_EN_MULTICAST_H
+
+#include <stdint.h>
+
+/* OVS includes. */
+#include "openvswitch/hmap.h"
+
+/* OVN includes. */
+#include "lib/ovn-sb-idl.h"
+#include "northd.h"
+
+#define MC_FLOOD "_MC_flood"
+#define MC_MROUTER_FLOOD "_MC_mrouter_flood"
+#define MC_STATIC "_MC_static"
+#define MC_UNKNOWN "_MC_unknown"
+#define MC_FLOOD_L2 "_MC_flood_l2"
+
+struct multicast_group {
+    const char *name;
+    uint16_t key;               /* OVN_MIN_MULTICAST...OVN_MAX_MULTICAST. */
+};
+
+/* Multicast group entry. */
+struct ovn_multicast {
+    struct hmap_node hmap_node; /* Index on 'datapath' and 'key'. */
+    struct ovn_datapath *datapath;
+    const struct multicast_group *group;
+
+    struct ovn_port **ports;
+    size_t n_ports, allocated_ports;
+};
+
+/*
+ * IGMP group entry (1:1 mapping to SB database).
+ */
+struct ovn_igmp_group_entry {
+    struct ovs_list list_node; /* Linkage in the list of entries. */
+    size_t n_ports;
+    struct ovn_port **ports;
+};
+
+/*
+ * IGMP group entry (aggregate of all entries from the SB database
+ * corresponding to the multicast group).
+ */
+struct ovn_igmp_group {
+    struct hmap_node hmap_node; /* Index on 'datapath' and 'address'. */
+    struct ovs_list list_node;  /* Linkage in the per-dp igmp group list. */
+
+    struct ovn_datapath *datapath;
+    struct in6_addr address; /* Multicast IPv6-mapped-IPv4 or IPv4 address. */
+    struct multicast_group mcgroup;
+
+    struct ovs_list entries; /* List of SB entries for this group. */
+};
+
+void build_mcast_groups(
+    const struct sbrec_igmp_group_table *sbrec_igmp_group_table,
+    struct ovsdb_idl_index *sbrec_mcast_group_by_name_dp,
+    const struct hmap *ls_datapaths,
+    const struct hmap *ls_ports,
+    const struct hmap *lr_ports,
+    struct hmap *mcast_groups,
+    struct hmap *igmp_groups);
+void sync_multicast_groups_to_sb(
+    struct ovsdb_idl_txn *ovnsb_txn,
+    const struct sbrec_multicast_group_table *sbrec_multicast_group_table,
+    const struct hmap * ls_datapaths, const struct hmap *lr_datapaths,
+    struct hmap *mcast_groups);
+void ovn_igmp_groups_destroy(struct hmap *igmp_groups);
+struct sbrec_multicast_group *create_sb_multicast_group(
+    struct ovsdb_idl_txn *ovnsb_txn, const struct sbrec_datapath_binding *,
+    const char *name, int64_t tunnel_key);
+
+#endif /* OVN_EN_MULTICAST_H */
diff --git a/northd/northd.c b/northd/northd.c
index 9f2a06fd1..59495717d 100644
--- a/northd/northd.c
+++ b/northd/northd.c
@@ -49,6 +49,7 @@
 #include "en-lr-nat.h"
 #include "en-lr-stateful.h"
 #include "en-ls-stateful.h"
+#include "en-multicast.h"
 #include "en-sampling-app.h"
 #include "lib/ovn-parallel-hmap.h"
 #include "ovn/actions.h"
@@ -366,14 +367,6 @@ init_mcast_port_info(struct mcast_port_info *mcast_info,
     }
 }
 
-static uint32_t
-ovn_mcast_group_allocate_key(struct mcast_info *mcast_info)
-{
-    return ovn_allocate_tnlid(&mcast_info->group_tnlids, "multicast group",
-                              OVN_MIN_IP_MULTICAST, OVN_MAX_IP_MULTICAST,
-                              &mcast_info->group_tnlid_hint);
-}
-
 static bool
 lb_has_vip(const struct nbrec_load_balancer *lb)
 {
@@ -1205,8 +1198,6 @@ ovn_port_set_nb(struct ovn_port *op,
     init_mcast_port_info(&op->mcast_info, op->nbsp, op->nbrp);
 }
 
-static bool lsp_is_router(const struct nbrec_logical_switch_port *nbsp);
-
 static struct ovn_port *
 ovn_port_create(struct hmap *ports, const char *key,
                 const struct nbrec_logical_switch_port *nbsp,
@@ -1308,7 +1299,7 @@ ovn_port_find__(const struct hmap *ports, const char 
*name,
     return matched_op;
 }
 
-static struct ovn_port *
+struct ovn_port *
 ovn_port_find(const struct hmap *ports, const char *name)
 {
     return ovn_port_find__(ports, name, false);
@@ -1334,14 +1325,6 @@ ovn_port_find_bound(const struct hmap *ports, const char 
*name)
     return ovn_port_find__(ports, name, true);
 }
 
-/* Returns true if the logical switch port 'enabled' column is empty or
- * set to true.  Otherwise, returns false. */
-static bool
-lsp_is_enabled(const struct nbrec_logical_switch_port *lsp)
-{
-    return !lsp->n_enabled || *lsp->enabled;
-}
-
 /* Returns true only if the logical switch port 'up' column is set to true.
  * Otherwise, if the column is not set or set to false, returns false. */
 static bool
@@ -1356,12 +1339,6 @@ lsp_is_external(const struct nbrec_logical_switch_port 
*nbsp)
     return !strcmp(nbsp->type, "external");
 }
 
-static bool
-lsp_is_router(const struct nbrec_logical_switch_port *nbsp)
-{
-    return !strcmp(nbsp->type, "router");
-}
-
 static bool
 lsp_is_remote(const struct nbrec_logical_switch_port *nbsp)
 {
@@ -1430,12 +1407,6 @@ lsp_is_type_changed(const struct sbrec_port_binding *sb,
     return true;
 }
 
-static bool
-lrport_is_enabled(const struct nbrec_logical_router_port *lrport)
-{
-    return !lrport->enabled || *lrport->enabled;
-}
-
 static bool
 lsp_force_fdb_lookup(const struct ovn_port *op)
 {
@@ -5342,352 +5313,6 @@ northd_handle_lb_data_changes(struct tracked_lb_data 
*trk_lb_data,
     return true;
 }
 
-struct multicast_group {
-    const char *name;
-    uint16_t key;               /* OVN_MIN_MULTICAST...OVN_MAX_MULTICAST. */
-};
-
-#define MC_FLOOD "_MC_flood"
-static const struct multicast_group mc_flood =
-    { MC_FLOOD, OVN_MCAST_FLOOD_TUNNEL_KEY };
-
-#define MC_MROUTER_FLOOD "_MC_mrouter_flood"
-static const struct multicast_group mc_mrouter_flood =
-    { MC_MROUTER_FLOOD, OVN_MCAST_MROUTER_FLOOD_TUNNEL_KEY };
-
-#define MC_STATIC "_MC_static"
-static const struct multicast_group mc_static =
-    { MC_STATIC, OVN_MCAST_STATIC_TUNNEL_KEY };
-
-#define MC_UNKNOWN "_MC_unknown"
-static const struct multicast_group mc_unknown =
-    { MC_UNKNOWN, OVN_MCAST_UNKNOWN_TUNNEL_KEY };
-
-#define MC_FLOOD_L2 "_MC_flood_l2"
-static const struct multicast_group mc_flood_l2 =
-    { MC_FLOOD_L2, OVN_MCAST_FLOOD_L2_TUNNEL_KEY };
-
-static bool
-multicast_group_equal(const struct multicast_group *a,
-                      const struct multicast_group *b)
-{
-    return !strcmp(a->name, b->name) && a->key == b->key;
-}
-
-/* Multicast group entry. */
-struct ovn_multicast {
-    struct hmap_node hmap_node; /* Index on 'datapath' and 'key'. */
-    struct ovn_datapath *datapath;
-    const struct multicast_group *group;
-
-    struct ovn_port **ports;
-    size_t n_ports, allocated_ports;
-};
-
-static uint32_t
-ovn_multicast_hash(const struct ovn_datapath *datapath,
-                   const struct multicast_group *group)
-{
-    return hash_pointer(datapath, group->key);
-}
-
-static struct ovn_multicast *
-ovn_multicast_find(struct hmap *mcgroups, struct ovn_datapath *datapath,
-                   const struct multicast_group *group)
-{
-    struct ovn_multicast *mc;
-
-    HMAP_FOR_EACH_WITH_HASH (mc, hmap_node,
-                             ovn_multicast_hash(datapath, group), mcgroups) {
-        if (mc->datapath == datapath
-            && multicast_group_equal(mc->group, group)) {
-            return mc;
-        }
-    }
-    return NULL;
-}
-
-static void
-ovn_multicast_add_ports(struct hmap *mcgroups, struct ovn_datapath *od,
-                        const struct multicast_group *group,
-                        struct ovn_port **ports, size_t n_ports)
-{
-    struct ovn_multicast *mc = ovn_multicast_find(mcgroups, od, group);
-    if (!mc) {
-        mc = xmalloc(sizeof *mc);
-        hmap_insert(mcgroups, &mc->hmap_node, ovn_multicast_hash(od, group));
-        mc->datapath = od;
-        mc->group = group;
-        mc->n_ports = 0;
-        mc->allocated_ports = 4;
-        mc->ports = xmalloc(mc->allocated_ports * sizeof *mc->ports);
-    }
-
-    size_t n_ports_total = mc->n_ports + n_ports;
-
-    if (n_ports_total > 2 * mc->allocated_ports) {
-        mc->allocated_ports = n_ports_total;
-        mc->ports = xrealloc(mc->ports,
-                             mc->allocated_ports * sizeof *mc->ports);
-    } else if (n_ports_total > mc->allocated_ports) {
-        mc->ports = x2nrealloc(mc->ports, &mc->allocated_ports,
-                               sizeof *mc->ports);
-    }
-
-    memcpy(&mc->ports[mc->n_ports], &ports[0], n_ports * sizeof *ports);
-    mc->n_ports += n_ports;
-}
-
-static void
-ovn_multicast_add(struct hmap *mcgroups, const struct multicast_group *group,
-                  struct ovn_port *port)
-{
-    /* Store the chassis redirect port otherwise traffic will not be tunneled
-     * properly.
-     */
-    if (port->cr_port) {
-        port = port->cr_port;
-    }
-    ovn_multicast_add_ports(mcgroups, port->od, group, &port, 1);
-}
-
-static void
-ovn_multicast_destroy(struct hmap *mcgroups, struct ovn_multicast *mc)
-{
-    if (mc) {
-        hmap_remove(mcgroups, &mc->hmap_node);
-        free(mc->ports);
-        free(mc);
-    }
-}
-
-static void
-ovn_multicast_update_sbrec(const struct ovn_multicast *mc,
-                           const struct sbrec_multicast_group *sb)
-{
-    struct sbrec_port_binding **ports = xmalloc(mc->n_ports * sizeof *ports);
-    for (size_t i = 0; i < mc->n_ports; i++) {
-        ports[i] = CONST_CAST(struct sbrec_port_binding *, mc->ports[i]->sb);
-    }
-    sbrec_multicast_group_set_ports(sb, ports, mc->n_ports);
-    free(ports);
-}
-
-/*
- * IGMP group entry (1:1 mapping to SB database).
- */
-struct ovn_igmp_group_entry {
-    struct ovs_list list_node; /* Linkage in the list of entries. */
-    size_t n_ports;
-    struct ovn_port **ports;
-};
-
-/*
- * IGMP group entry (aggregate of all entries from the SB database
- * corresponding to the multicast group).
- */
-struct ovn_igmp_group {
-    struct hmap_node hmap_node; /* Index on 'datapath' and 'address'. */
-    struct ovs_list list_node;  /* Linkage in the per-dp igmp group list. */
-
-    struct ovn_datapath *datapath;
-    struct in6_addr address; /* Multicast IPv6-mapped-IPv4 or IPv4 address. */
-    struct multicast_group mcgroup;
-
-    struct ovs_list entries; /* List of SB entries for this group. */
-};
-
-static uint32_t
-ovn_igmp_group_hash(const struct ovn_datapath *datapath,
-                    const struct in6_addr *address)
-{
-    return hash_pointer(datapath, hash_bytes(address, sizeof *address, 0));
-}
-
-static struct ovn_igmp_group *
-ovn_igmp_group_find(struct hmap *igmp_groups,
-                    const struct ovn_datapath *datapath,
-                    const struct in6_addr *address)
-{
-    struct ovn_igmp_group *group;
-
-    HMAP_FOR_EACH_WITH_HASH (group, hmap_node,
-                             ovn_igmp_group_hash(datapath, address),
-                             igmp_groups) {
-        if (group->datapath == datapath &&
-                ipv6_addr_equals(&group->address, address)) {
-            return group;
-        }
-    }
-    return NULL;
-}
-
-static struct ovn_igmp_group *
-ovn_igmp_group_add(struct ovsdb_idl_index *sbrec_mcast_group_by_name_dp,
-                   struct hmap *igmp_groups,
-                   struct ovn_datapath *datapath,
-                   const struct in6_addr *address,
-                   const char *address_s)
-{
-    struct ovn_igmp_group *igmp_group =
-        ovn_igmp_group_find(igmp_groups, datapath, address);
-
-    if (!igmp_group) {
-        igmp_group = xmalloc(sizeof *igmp_group);
-
-        const struct sbrec_multicast_group *mcgroup =
-            mcast_group_lookup(sbrec_mcast_group_by_name_dp,
-                               address_s,
-                               datapath->sb);
-
-        igmp_group->datapath = datapath;
-        igmp_group->address = *address;
-        if (mcgroup) {
-            igmp_group->mcgroup.key = mcgroup->tunnel_key;
-            ovn_add_tnlid(&datapath->mcast_info.group_tnlids,
-                          mcgroup->tunnel_key);
-        } else {
-            igmp_group->mcgroup.key = 0;
-        }
-        igmp_group->mcgroup.name = address_s;
-        ovs_list_init(&igmp_group->entries);
-
-        hmap_insert(igmp_groups, &igmp_group->hmap_node,
-                    ovn_igmp_group_hash(datapath, address));
-        ovs_list_push_back(&datapath->mcast_info.groups,
-                           &igmp_group->list_node);
-    }
-
-    return igmp_group;
-}
-
-static struct ovn_port **
-ovn_igmp_group_get_ports(const struct sbrec_igmp_group *sb_igmp_group,
-                         size_t *n_ports, const struct hmap *ls_ports)
-{
-    struct ovn_port **ports = NULL;
-
-     *n_ports = 0;
-     for (size_t i = 0; i < sb_igmp_group->n_ports; i++) {
-        struct ovn_port *port =
-            ovn_port_find(ls_ports, sb_igmp_group->ports[i]->logical_port);
-
-        if (!port || !port->nbsp) {
-            continue;
-        }
-
-        /* If this is already a flood port skip it for the group. */
-        if (port->mcast_info.flood) {
-            continue;
-        }
-
-        /* If this is already a port of a router on which relay is enabled,
-         * skip it for the group. Traffic is flooded there anyway.
-         */
-        if (port->peer && port->peer->od &&
-                port->peer->od->mcast_info.rtr.relay) {
-            continue;
-        }
-
-        if (ports == NULL) {
-            ports = xmalloc(sb_igmp_group->n_ports * sizeof *ports);
-        }
-
-        ports[(*n_ports)] = port;
-        (*n_ports)++;
-    }
-
-    return ports;
-}
-
-static void
-ovn_igmp_group_add_entry(struct ovn_igmp_group *igmp_group,
-                         struct ovn_port **ports, size_t n_ports)
-{
-    struct ovn_igmp_group_entry *entry = xmalloc(sizeof *entry);
-
-    entry->ports = ports;
-    entry->n_ports = n_ports;
-    ovs_list_push_back(&igmp_group->entries, &entry->list_node);
-}
-
-static void
-ovn_igmp_group_destroy_entry(struct ovn_igmp_group_entry *entry)
-{
-    free(entry->ports);
-}
-
-static bool
-ovn_igmp_group_allocate_id(struct ovn_igmp_group *igmp_group)
-{
-    if (igmp_group->mcgroup.key == 0) {
-        struct mcast_info *mcast_info = &igmp_group->datapath->mcast_info;
-        igmp_group->mcgroup.key = ovn_mcast_group_allocate_key(mcast_info);
-    }
-
-    if (igmp_group->mcgroup.key == 0) {
-        return false;
-    }
-
-    return true;
-}
-
-static void
-ovn_igmp_mrouter_aggregate_ports(struct ovn_igmp_group *igmp_group,
-                                 struct hmap *mcast_groups)
-{
-    struct ovn_igmp_group_entry *entry;
-
-    LIST_FOR_EACH_POP (entry, list_node, &igmp_group->entries) {
-        ovn_multicast_add_ports(mcast_groups, igmp_group->datapath,
-                                &mc_mrouter_flood, entry->ports,
-                                entry->n_ports);
-
-        ovn_igmp_group_destroy_entry(entry);
-        free(entry);
-    }
-}
-
-static void
-ovn_igmp_group_aggregate_ports(struct ovn_igmp_group *igmp_group,
-                               struct hmap *mcast_groups)
-{
-    struct ovn_igmp_group_entry *entry;
-
-    LIST_FOR_EACH_POP (entry, list_node, &igmp_group->entries) {
-        ovn_multicast_add_ports(mcast_groups, igmp_group->datapath,
-                                &igmp_group->mcgroup, entry->ports,
-                                entry->n_ports);
-
-        ovn_igmp_group_destroy_entry(entry);
-        free(entry);
-    }
-
-    if (igmp_group->datapath->n_localnet_ports) {
-        ovn_multicast_add_ports(mcast_groups, igmp_group->datapath,
-                                &igmp_group->mcgroup,
-                                igmp_group->datapath->localnet_ports,
-                                igmp_group->datapath->n_localnet_ports);
-    }
-}
-
-static void
-ovn_igmp_group_destroy(struct hmap *igmp_groups,
-                       struct ovn_igmp_group *igmp_group)
-{
-    if (igmp_group) {
-        struct ovn_igmp_group_entry *entry;
-
-        LIST_FOR_EACH_POP (entry, list_node, &igmp_group->entries) {
-            ovn_igmp_group_destroy_entry(entry);
-            free(entry);
-        }
-        hmap_remove(igmp_groups, &igmp_group->hmap_node);
-        ovs_list_remove(&igmp_group->list_node);
-        free(igmp_group);
-    }
-}
-
 /* Logical flow generation.
  *
  * This code generates the Logical_Flow table in the southbound database, as a
@@ -17876,29 +17501,6 @@ void run_update_worker_pool(int n_threads)
     }
 }
 
-static void
-build_mcast_groups(const struct sbrec_igmp_group_table *sbrec_igmp_group_table,
-                   struct ovsdb_idl_index *sbrec_mcast_group_by_name_dp,
-                   const struct ovn_datapaths *ls_datapaths,
-                   const struct hmap *ls_ports,
-                   const struct hmap *lr_ports,
-                   struct hmap *mcast_groups,
-                   struct hmap *igmp_groups);
-
-static struct sbrec_multicast_group *
-create_sb_multicast_group(struct ovsdb_idl_txn *ovnsb_txn,
-                          const struct sbrec_datapath_binding *dp,
-                          const char *name,
-                          int64_t tunnel_key)
-{
-    struct sbrec_multicast_group *sbmc =
-        sbrec_multicast_group_insert(ovnsb_txn);
-    sbrec_multicast_group_set_datapath(sbmc, dp);
-    sbrec_multicast_group_set_name(sbmc, name);
-    sbrec_multicast_group_set_tunnel_key(sbmc, tunnel_key);
-    return sbmc;
-}
-
 /* Updates the Logical_Flow and Multicast_Group tables in the OVN_SB database,
  * constructing their contents based on the OVN_NB database. */
 void build_lflows(struct ovsdb_idl_txn *ovnsb_txn,
@@ -17908,9 +17510,14 @@ void build_lflows(struct ovsdb_idl_txn *ovnsb_txn,
     struct hmap mcast_groups;
     struct hmap igmp_groups;
 
+    struct ovn_datapath *od;
+    HMAP_FOR_EACH (od, key_node, &input_data->ls_datapaths->datapaths) {
+        init_mcast_flow_count(od);
+    }
+
     build_mcast_groups(input_data->sbrec_igmp_group_table,
                        input_data->sbrec_mcast_group_by_name_dp,
-                       input_data->ls_datapaths,
+                       &input_data->ls_datapaths->datapaths,
                        input_data->ls_ports, input_data->lr_ports,
                        &mcast_groups, &igmp_groups);
 
@@ -17951,51 +17558,12 @@ void build_lflows(struct ovsdb_idl_txn *ovnsb_txn,
 
     stopwatch_stop(LFLOWS_TO_SB_STOPWATCH_NAME, time_msec());
 
-    /* Push changes to the Multicast_Group table to database. */
-    const struct sbrec_multicast_group *sbmc;
-    SBREC_MULTICAST_GROUP_TABLE_FOR_EACH_SAFE (
-            sbmc, input_data->sbrec_multicast_group_table) {
-        struct ovn_datapath *od = ovn_datapath_from_sbrec(
-            &input_data->ls_datapaths->datapaths,
-            &input_data->lr_datapaths->datapaths,
-            sbmc->datapath);
-
-        if (!od || ovn_datapath_is_stale(od)) {
-            sbrec_multicast_group_delete(sbmc);
-            continue;
-        }
-
-        struct multicast_group group = { .name = sbmc->name,
-                                         .key = sbmc->tunnel_key };
-        struct ovn_multicast *mc = ovn_multicast_find(&mcast_groups,
-                                                      od, &group);
-        if (mc) {
-            ovn_multicast_update_sbrec(mc, sbmc);
-            ovn_multicast_destroy(&mcast_groups, mc);
-        } else {
-            sbrec_multicast_group_delete(sbmc);
-        }
-    }
-    struct ovn_multicast *mc;
-    HMAP_FOR_EACH_SAFE (mc, hmap_node, &mcast_groups) {
-        if (!mc->datapath) {
-            ovn_multicast_destroy(&mcast_groups, mc);
-            continue;
-        }
-        sbmc = create_sb_multicast_group(ovnsb_txn, mc->datapath->sb,
-                                         mc->group->name, mc->group->key);
-        ovn_multicast_update_sbrec(mc, sbmc);
-        ovn_multicast_destroy(&mcast_groups, mc);
-    }
-
-    struct ovn_igmp_group *igmp_group;
-
-    HMAP_FOR_EACH_SAFE (igmp_group, hmap_node, &igmp_groups) {
-        ovn_igmp_group_destroy(&igmp_groups, igmp_group);
-    }
-
-    hmap_destroy(&igmp_groups);
-    hmap_destroy(&mcast_groups);
+    sync_multicast_groups_to_sb(ovnsb_txn,
+                                input_data->sbrec_multicast_group_table,
+                                &input_data->ls_datapaths->datapaths,
+                                &input_data->lr_datapaths->datapaths,
+                                &mcast_groups);
+    ovn_igmp_groups_destroy(&igmp_groups);
 }
 
 void
@@ -18629,218 +18197,6 @@ build_ip_mcast(struct ovsdb_idl_txn *ovnsb_txn,
     }
 }
 
-static void
-build_mcast_groups(const struct sbrec_igmp_group_table *sbrec_igmp_group_table,
-                   struct ovsdb_idl_index *sbrec_mcast_group_by_name_dp,
-                   const struct ovn_datapaths *ls_datapaths,
-                   const struct hmap *ls_ports,
-                   const struct hmap *lr_ports,
-                   struct hmap *mcast_groups,
-                   struct hmap *igmp_groups)
-{
-    struct ovn_port *op;
-
-    hmap_init(mcast_groups);
-    hmap_init(igmp_groups);
-    struct ovn_datapath *od;
-
-    HMAP_FOR_EACH (od, key_node, &ls_datapaths->datapaths) {
-        init_mcast_flow_count(od);
-    }
-
-    HMAP_FOR_EACH (op, key_node, lr_ports) {
-        if (lrport_is_enabled(op->nbrp)) {
-            /* If this port is configured to always flood multicast traffic
-             * add it to the MC_STATIC group.
-             */
-            if (op->mcast_info.flood) {
-                ovn_multicast_add(mcast_groups, &mc_static, op);
-                op->od->mcast_info.rtr.flood_static = true;
-            }
-        }
-    }
-
-    HMAP_FOR_EACH (op, key_node, ls_ports) {
-        if (lsp_is_enabled(op->nbsp)) {
-            ovn_multicast_add(mcast_groups, &mc_flood, op);
-
-            if (!lsp_is_router(op->nbsp)) {
-                ovn_multicast_add(mcast_groups, &mc_flood_l2, op);
-            }
-
-            if (op->has_unknown) {
-                ovn_multicast_add(mcast_groups, &mc_unknown, op);
-            }
-
-            /* If this port is connected to a multicast router then add it
-             * to the MC_MROUTER_FLOOD group.
-             */
-            if (op->od->mcast_info.sw.flood_relay && op->peer &&
-                    op->peer->od && op->peer->od->mcast_info.rtr.relay) {
-                ovn_multicast_add(mcast_groups, &mc_mrouter_flood, op);
-            }
-
-            /* If this port is configured to always flood multicast reports
-             * add it to the MC_MROUTER_FLOOD group (all reports must be
-             * flooded to statically configured or learned mrouters).
-             */
-            if (op->mcast_info.flood_reports) {
-                ovn_multicast_add(mcast_groups, &mc_mrouter_flood, op);
-                op->od->mcast_info.sw.flood_reports = true;
-            }
-
-            /* If this port is configured to always flood multicast traffic
-             * add it to the MC_STATIC group.
-             */
-            if (op->mcast_info.flood) {
-                ovn_multicast_add(mcast_groups, &mc_static, op);
-                op->od->mcast_info.sw.flood_static = true;
-            }
-        }
-    }
-
-    const struct sbrec_igmp_group *sb_igmp;
-
-    SBREC_IGMP_GROUP_TABLE_FOR_EACH_SAFE (sb_igmp, sbrec_igmp_group_table) {
-        /* If this is a stale group (e.g., controller had crashed,
-         * purge it).
-         */
-        if (!sb_igmp->chassis || !sb_igmp->datapath) {
-            sbrec_igmp_group_delete(sb_igmp);
-            continue;
-        }
-
-        /* If the datapath value is stale, purge the group. */
-        od = ovn_datapath_from_sbrec(&ls_datapaths->datapaths, NULL,
-                                     sb_igmp->datapath);
-
-        if (!od || ovn_datapath_is_stale(od)) {
-            sbrec_igmp_group_delete(sb_igmp);
-            continue;
-        }
-
-        struct in6_addr group_address;
-        if (!strcmp(sb_igmp->address, OVN_IGMP_GROUP_MROUTERS)) {
-            /* Use all-zeros IP to denote a group corresponding to mrouters. */
-            memset(&group_address, 0, sizeof group_address);
-        } else if (!ip46_parse(sb_igmp->address, &group_address)) {
-            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
-            VLOG_WARN_RL(&rl, "invalid IGMP group address: %s",
-                         sb_igmp->address);
-            continue;
-        }
-
-        /* Extract the IGMP group ports from the SB entry. */
-        size_t n_igmp_ports;
-        struct ovn_port **igmp_ports =
-            ovn_igmp_group_get_ports(sb_igmp, &n_igmp_ports, ls_ports);
-
-        /* It can be that all ports in the IGMP group record already have
-         * mcast_flood=true and then we can skip the group completely.
-         */
-        if (!igmp_ports) {
-            continue;
-        }
-
-        /* Add the IGMP group entry. Will also try to allocate an ID for it
-         * if the multicast group already exists.
-         */
-        struct ovn_igmp_group *igmp_group =
-            ovn_igmp_group_add(sbrec_mcast_group_by_name_dp, igmp_groups, od,
-                               &group_address, sb_igmp->address);
-
-        /* Add the extracted ports to the IGMP group. */
-        ovn_igmp_group_add_entry(igmp_group, igmp_ports, n_igmp_ports);
-    }
-
-    /* Build IGMP groups for multicast routers with relay enabled. The router
-     * IGMP groups are based on the groups learnt by their multicast enabled
-     * peers.
-     */
-    HMAP_FOR_EACH (od, key_node, &ls_datapaths->datapaths) {
-
-        if (ovs_list_is_empty(&od->mcast_info.groups)) {
-            continue;
-        }
-
-        for (size_t i = 0; i < od->n_router_ports; i++) {
-            struct ovn_port *router_port = od->router_ports[i]->peer;
-
-            /* If the router the port connects to doesn't have multicast
-             * relay enabled or if it was already configured to flood
-             * multicast traffic then skip it.
-             */
-            if (!router_port || !router_port->od ||
-                    !router_port->od->mcast_info.rtr.relay ||
-                    router_port->mcast_info.flood) {
-                continue;
-            }
-
-            struct ovn_igmp_group *igmp_group;
-            LIST_FOR_EACH (igmp_group, list_node, &od->mcast_info.groups) {
-                struct in6_addr *address = &igmp_group->address;
-
-                /* Skip mrouter entries. */
-                if (!strcmp(igmp_group->mcgroup.name,
-                            OVN_IGMP_GROUP_MROUTERS)) {
-                    continue;
-                }
-
-                /* For IPv6 only relay routable multicast groups
-                 * (RFC 4291 2.7).
-                 */
-                if (!IN6_IS_ADDR_V4MAPPED(address) &&
-                        !ipv6_addr_is_routable_multicast(address)) {
-                    continue;
-                }
-
-                struct ovn_igmp_group *igmp_group_rtr =
-                    ovn_igmp_group_add(sbrec_mcast_group_by_name_dp,
-                                       igmp_groups, router_port->od,
-                                       address, igmp_group->mcgroup.name);
-                struct ovn_port **router_igmp_ports =
-                    xmalloc(sizeof *router_igmp_ports);
-                /* Store the chassis redirect port  otherwise traffic will not
-                 * be tunneled properly.
-                 */
-                router_igmp_ports[0] = router_port->cr_port
-                                       ? router_port->cr_port
-                                       : router_port;
-                ovn_igmp_group_add_entry(igmp_group_rtr, router_igmp_ports, 1);
-            }
-        }
-    }
-
-    /* Walk the aggregated IGMP groups and allocate IDs for new entries.
-     * Then store the ports in the associated multicast group.
-     * Mrouter entries are also stored as IGMP groups, deal with those
-     * explicitly.
-     */
-    struct ovn_igmp_group *igmp_group;
-    HMAP_FOR_EACH_SAFE (igmp_group, hmap_node, igmp_groups) {
-
-        /* If this is a mrouter entry just aggregate the mrouter ports
-         * into the MC_MROUTER mcast_group and destroy the igmp_group;
-         * no more processing needed. */
-        if (!strcmp(igmp_group->mcgroup.name, OVN_IGMP_GROUP_MROUTERS)) {
-            ovn_igmp_mrouter_aggregate_ports(igmp_group, mcast_groups);
-            ovn_igmp_group_destroy(igmp_groups, igmp_group);
-            continue;
-        }
-
-        if (!ovn_igmp_group_allocate_id(igmp_group)) {
-            /* If we ran out of keys just destroy the entry. */
-            ovn_igmp_group_destroy(igmp_groups, igmp_group);
-            continue;
-        }
-
-        /* Aggregate the ports from all entries corresponding to this
-         * group.
-         */
-        ovn_igmp_group_aggregate_ports(igmp_group, mcast_groups);
-    }
-}
-
 static void
 build_static_mac_binding_table(
     struct ovsdb_idl_txn *ovnsb_txn,
diff --git a/northd/northd.h b/northd/northd.h
index 9457a7be6..044704c70 100644
--- a/northd/northd.h
+++ b/northd/northd.h
@@ -874,4 +874,28 @@ is_vxlan_mode(const struct smap *nb_options,
 
 uint32_t get_ovn_max_dp_key_local(bool _vxlan_mode);
 
+/* Returns true if the logical router port 'enabled' column is empty or
+ * set to true.  Otherwise, returns false. */
+static inline bool
+lrport_is_enabled(const struct nbrec_logical_router_port *lrport)
+{
+    return !lrport->enabled || *lrport->enabled;
+}
+
+/* Returns true if the logical switch port 'enabled' column is empty or
+ * set to true.  Otherwise, returns false. */
+static inline bool
+lsp_is_enabled(const struct nbrec_logical_switch_port *lsp)
+{
+    return !lsp->n_enabled || *lsp->enabled;
+}
+
+static inline bool
+lsp_is_router(const struct nbrec_logical_switch_port *nbsp)
+{
+    return !strcmp(nbsp->type, "router");
+}
+
+struct ovn_port *ovn_port_find(const struct hmap *ports, const char *name);
+
 #endif /* NORTHD_H */
-- 
2.48.1

_______________________________________________
dev mailing list
d...@openvswitch.org
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to