> 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.
> 
> Co-authored-by: Jacob Tanenbaum <[email protected]>
> Signed-off-by: Jacob Tanenbaum <[email protected]>
> Signed-off-by: Ales Musil <[email protected]>

Acked-by: Lorenzo Bianconi <[email protected]>

> ---
>  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 6566ad299..951ed7e34 100644
> --- a/northd/automake.mk
> +++ b/northd/automake.mk
> @@ -16,6 +16,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 3cf5d0ad7..ddd05f7e4 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)
>  {
> @@ -5328,352 +5299,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
> @@ -17922,29 +17547,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,
> @@ -17954,9 +17556,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);
>  
> @@ -17997,51 +17604,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
> @@ -18675,218 +18243,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.47.1
> 
> _______________________________________________
> dev mailing list
> [email protected]
> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
> 
_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to