bbaaef
From 193b2aefdb41d55209193af1f5e8c8dacb861c84 Mon Sep 17 00:00:00 2001
bbaaef
From: Dumitru Ceara <dceara@redhat.com>
bbaaef
Date: Tue, 24 Sep 2019 10:02:49 +0200
bbaaef
Subject: [PATCH 4/4] ovn-northd: Add static IP multicast flood configuration
bbaaef
bbaaef
Add the following new configuration options to the
bbaaef
Logical_Switch_Port:options column in the OVN Northbound database:
bbaaef
bbaaef
- mcast_flood: if set to 'true' all incoming IP multicast traffic
bbaaef
  (except IP multicast reports) entering the switch will also be
bbaaef
  flooded on the logical switch port.
bbaaef
- mcast_flood_reports: if set to 'true' all incoming IP multicast
bbaaef
  entering the switch will also be flooded on the logical switch
bbaaef
  port. A clone of the packets is also processed by ovn-controller
bbaaef
  for snooping.
bbaaef
bbaaef
Add the following new configuration option to the
bbaaef
Logical_Router_Port:options column in the OVN Northbound database:
bbaaef
bbaaef
- mcast_flood: if set to 'true' all incoming IP multicast traffic
bbaaef
  (including IP multicast reports) entering the router will be also
bbaaef
  flooded on the logical router port.
bbaaef
bbaaef
Due to the fact that in the router pipeline multicast reports are
bbaaef
not treated in a special way there's no need for an explicit
bbaaef
'mcast_flood_reports' option for router ports.
bbaaef
bbaaef
Signed-off-by: Dumitru Ceara <dceara@redhat.com>
bbaaef
Acked-by: Mark Michelson <mmichels@redhat.com>
bbaaef
Signed-off-by: Mark Michelson <mmichels@redhat.com>
bbaaef
bbaaef
(cherry-picked from ovn commit 79308138891ae04a02a07068501696ef78157912)
bbaaef
---
bbaaef
 ovn/lib/mcast-group-index.h |   2 +
bbaaef
 ovn/northd/ovn-northd.8.xml |  30 ++++-
bbaaef
 ovn/northd/ovn-northd.c     | 212 +++++++++++++++++++++++++++++++-----
bbaaef
 ovn/ovn-nb.xml              |  34 ++++++
bbaaef
 tests/ovn.at                |  81 +++++++++++++-
bbaaef
 5 files changed, 325 insertions(+), 34 deletions(-)
bbaaef
bbaaef
diff --git a/ovn/lib/mcast-group-index.h b/ovn/lib/mcast-group-index.h
bbaaef
index 6249cac99..930963b1b 100644
bbaaef
--- a/ovn/lib/mcast-group-index.h
bbaaef
+++ b/ovn/lib/mcast-group-index.h
bbaaef
@@ -28,6 +28,8 @@ enum ovn_mcast_tunnel_keys {
bbaaef
     OVN_MCAST_FLOOD_TUNNEL_KEY = OVN_MIN_MULTICAST,
bbaaef
     OVN_MCAST_UNKNOWN_TUNNEL_KEY,
bbaaef
     OVN_MCAST_MROUTER_FLOOD_TUNNEL_KEY,
bbaaef
+    OVN_MCAST_MROUTER_STATIC_TUNNEL_KEY,
bbaaef
+    OVN_MCAST_STATIC_TUNNEL_KEY,
bbaaef
     OVN_MIN_IP_MULTICAST,
bbaaef
     OVN_MAX_IP_MULTICAST = OVN_MAX_MULTICAST,
bbaaef
 };
bbaaef
diff --git a/ovn/northd/ovn-northd.8.xml b/ovn/northd/ovn-northd.8.xml
bbaaef
index ec9020d2a..b9ae28e14 100644
bbaaef
--- a/ovn/northd/ovn-northd.8.xml
bbaaef
+++ b/ovn/northd/ovn-northd.8.xml
bbaaef
@@ -954,7 +954,11 @@ output;
bbaaef
       
  • bbaaef
             A priority-100 flow that punts all IGMP packets to
    bbaaef
             ovn-controller if IGMP snooping is enabled on the
    bbaaef
    -        logical switch.
    bbaaef
    +        logical switch. The flow also forwards the IGMP packets to the
    bbaaef
    +        MC_MROUTER_STATIC multicast group, which
    bbaaef
    +        ovn-northd populates with all the logical ports that
    bbaaef
    +        have <ref column="options" table="Logical_Switch_Port"/>
    bbaaef
    +        :mcast_flood_reports='true'.
    bbaaef
           
    bbaaef
     
    bbaaef
           
  • bbaaef
    @@ -976,10 +980,15 @@ output;
    bbaaef
     
    bbaaef
           
  • bbaaef
             A priority-80 flow that forwards all unregistered IP multicast traffic
    bbaaef
    -        to the MC_MROUTER_FLOOD multicast group, if any.
    bbaaef
    -        Otherwise the flow drops all unregistered IP multicast packets.  This
    bbaaef
    -        flow is added only if 
    bbaaef
    -        table="Logical_Switch"/>:mcast_flood_unregistered='false'.
    bbaaef
    +        to the MC_STATIC multicast group, which
    bbaaef
    +        ovn-northd populates with all the logical ports that
    bbaaef
    +        have <ref column="options" table="Logical_Switch_Port"/>
    bbaaef
    +        :mcast_flood='true'. The flow also forwards
    bbaaef
    +        unregistered IP multicast traffic to the MC_MROUTER_FLOOD
    bbaaef
    +        multicast group, which ovn-northd populates with all the
    bbaaef
    +        logical ports connected to logical routers that have
    bbaaef
    +        <ref column="options" table="Logical_Router"/>
    bbaaef
    +        :mcast_relay='true'.
    bbaaef
           
    bbaaef
     
    bbaaef
           
  • bbaaef
    @@ -2092,6 +2101,17 @@ output;
    bbaaef
             

    bbaaef
           
    bbaaef
     
    bbaaef
    +      
  • bbaaef
    +        

    bbaaef
    +          Priority-450 flow that matches unregistered IP multicast traffic
    bbaaef
    +          and sets outport to the MC_STATIC
    bbaaef
    +          multicast group, which ovn-northd populates with the
    bbaaef
    +          logical ports that have
    bbaaef
    +          <ref column="options" table="Logical_Router_Port"/>
    bbaaef
    +          :mcast_flood='true'.
    bbaaef
    +        

    bbaaef
    +      
    bbaaef
    +
    bbaaef
           
  • bbaaef
             

    bbaaef
               For distributed logical routers where one of the logical router
    bbaaef
    diff --git a/ovn/northd/ovn-northd.c b/ovn/northd/ovn-northd.c
    bbaaef
    index 2f6826f17..250529eb7 100644
    bbaaef
    --- a/ovn/northd/ovn-northd.c
    bbaaef
    +++ b/ovn/northd/ovn-northd.c
    bbaaef
    @@ -459,7 +459,12 @@ struct mcast_switch_info {
    bbaaef
                                      * should be flooded to the mrouter. Only
    bbaaef
                                      * applicable if flood_unregistered == false.
    bbaaef
                                      */
    bbaaef
    -
    bbaaef
    +    bool flood_reports;         /* True if the switch has at least one port
    bbaaef
    +                                 * configured to flood reports.
    bbaaef
    +                                 */
    bbaaef
    +    bool flood_static;          /* True if the switch has at least one port
    bbaaef
    +                                 * configured to flood traffic.
    bbaaef
    +                                 */
    bbaaef
         int64_t table_size;         /* Max number of IP multicast groups. */
    bbaaef
         int64_t idle_timeout;       /* Timeout after which an idle group is
    bbaaef
                                      * flushed.
    bbaaef
    @@ -477,7 +482,10 @@ struct mcast_switch_info {
    bbaaef
     };
    bbaaef
     
    bbaaef
     struct mcast_router_info {
    bbaaef
    -    bool relay; /* True if the router should relay IP multicast. */
    bbaaef
    +    bool relay;        /* True if the router should relay IP multicast. */
    bbaaef
    +    bool flood_static; /* True if the router has at least one port configured
    bbaaef
    +                        * to flood traffic.
    bbaaef
    +                        */
    bbaaef
     };
    bbaaef
     
    bbaaef
     struct mcast_info {
    bbaaef
    @@ -492,6 +500,34 @@ struct mcast_info {
    bbaaef
         };
    bbaaef
     };
    bbaaef
     
    bbaaef
    +struct mcast_port_info {
    bbaaef
    +    bool flood;         /* True if the port should flood IP multicast traffic
    bbaaef
    +                         * regardless if it's registered or not. */
    bbaaef
    +    bool flood_reports; /* True if the port should flood IP multicast reports
    bbaaef
    +                         * (e.g., IGMP join/leave). */
    bbaaef
    +};
    bbaaef
    +
    bbaaef
    +static void
    bbaaef
    +init_mcast_port_info(struct mcast_port_info *mcast_info,
    bbaaef
    +                     const struct nbrec_logical_switch_port *nbsp,
    bbaaef
    +                     const struct nbrec_logical_router_port *nbrp)
    bbaaef
    +{
    bbaaef
    +    if (nbsp) {
    bbaaef
    +        mcast_info->flood =
    bbaaef
    +            smap_get_bool(&nbsp->options, "mcast_flood", false);
    bbaaef
    +        mcast_info->flood_reports =
    bbaaef
    +            smap_get_bool(&nbsp->options, "mcast_flood_reports",
    bbaaef
    +                          false);
    bbaaef
    +    } else if (nbrp) {
    bbaaef
    +        /* We don't process multicast reports in any special way on logical
    bbaaef
    +         * routers so just treat them as regular multicast traffic.
    bbaaef
    +         */
    bbaaef
    +        mcast_info->flood =
    bbaaef
    +            smap_get_bool(&nbrp->options, "mcast_flood", false);
    bbaaef
    +        mcast_info->flood_reports = mcast_info->flood;
    bbaaef
    +    }
    bbaaef
    +}
    bbaaef
    +
    bbaaef
     static uint32_t
    bbaaef
     ovn_mcast_group_allocate_key(struct mcast_info *mcast_info)
    bbaaef
     {
    bbaaef
    @@ -1033,7 +1069,7 @@ build_datapaths(struct northd_context *ctx, struct hmap *datapaths,
    bbaaef
             ovn_datapath_destroy(datapaths, od);
    bbaaef
         }
    bbaaef
     }
    bbaaef
    -
    bbaaef
    +
    bbaaef
     struct ovn_port {
    bbaaef
         struct hmap_node key_node;  /* Index on 'key'. */
    bbaaef
         char *key;                  /* nbs->name, nbr->name, sb->logical_port. */
    bbaaef
    @@ -1055,6 +1091,9 @@ struct ovn_port {
    bbaaef
     
    bbaaef
         struct lport_addresses lrp_networks;
    bbaaef
     
    bbaaef
    +    /* Logical port multicast data. */
    bbaaef
    +    struct mcast_port_info mcast_info;
    bbaaef
    +
    bbaaef
         bool derived; /* Indicates whether this is an additional port
    bbaaef
                        * derived from nbsp or nbrp. */
    bbaaef
     
    bbaaef
    @@ -1071,6 +1110,23 @@ struct ovn_port {
    bbaaef
         struct ovs_list list;       /* In list of similar records. */
    bbaaef
     };
    bbaaef
     
    bbaaef
    +static void
    bbaaef
    +ovn_port_set_sb(struct ovn_port *op,
    bbaaef
    +                const struct sbrec_port_binding *sb)
    bbaaef
    +{
    bbaaef
    +    op->sb = sb;
    bbaaef
    +}
    bbaaef
    +
    bbaaef
    +static void
    bbaaef
    +ovn_port_set_nb(struct ovn_port *op,
    bbaaef
    +                const struct nbrec_logical_switch_port *nbsp,
    bbaaef
    +                const struct nbrec_logical_router_port *nbrp)
    bbaaef
    +{
    bbaaef
    +    op->nbsp = nbsp;
    bbaaef
    +    op->nbrp = nbrp;
    bbaaef
    +    init_mcast_port_info(&op->mcast_info, op->nbsp, op->nbrp);
    bbaaef
    +}
    bbaaef
    +
    bbaaef
     static struct ovn_port *
    bbaaef
     ovn_port_create(struct hmap *ports, const char *key,
    bbaaef
                     const struct nbrec_logical_switch_port *nbsp,
    bbaaef
    @@ -1084,9 +1140,8 @@ ovn_port_create(struct hmap *ports, const char *key,
    bbaaef
         op->json_key = ds_steal_cstr(&json_key);
    bbaaef
     
    bbaaef
         op->key = xstrdup(key);
    bbaaef
    -    op->sb = sb;
    bbaaef
    -    op->nbsp = nbsp;
    bbaaef
    -    op->nbrp = nbrp;
    bbaaef
    +    ovn_port_set_sb(op, sb);
    bbaaef
    +    ovn_port_set_nb(op, nbsp, nbrp);
    bbaaef
         op->derived = false;
    bbaaef
         hmap_insert(ports, &op->key_node, hash_string(op->key, 0));
    bbaaef
         return op;
    bbaaef
    @@ -1882,7 +1937,7 @@ join_logical_ports(struct northd_context *ctx,
    bbaaef
                                          nbsp->name);
    bbaaef
                             continue;
    bbaaef
                         }
    bbaaef
    -                    op->nbsp = nbsp;
    bbaaef
    +                    ovn_port_set_nb(op, nbsp, NULL);
    bbaaef
                         ovs_list_remove(&op->list);
    bbaaef
     
    bbaaef
                         uint32_t queue_id = smap_get_int(&op->sb->options,
    bbaaef
    @@ -1973,7 +2028,7 @@ join_logical_ports(struct northd_context *ctx,
    bbaaef
                                          nbrp->name);
    bbaaef
                             continue;
    bbaaef
                         }
    bbaaef
    -                    op->nbrp = nbrp;
    bbaaef
    +                    ovn_port_set_nb(op, NULL, nbrp);
    bbaaef
                         ovs_list_remove(&op->list);
    bbaaef
                         ovs_list_push_back(both, &op->list);
    bbaaef
     
    bbaaef
    @@ -2018,7 +2073,7 @@ join_logical_ports(struct northd_context *ctx,
    bbaaef
                         struct ovn_port *crp = ovn_port_find(ports, redirect_name);
    bbaaef
                         if (crp) {
    bbaaef
                             crp->derived = true;
    bbaaef
    -                        crp->nbrp = nbrp;
    bbaaef
    +                        ovn_port_set_nb(crp, NULL, nbrp);
    bbaaef
                             ovs_list_remove(&crp->list);
    bbaaef
                             ovs_list_push_back(both, &crp->list);
    bbaaef
                         } else {
    bbaaef
    @@ -2896,7 +2951,7 @@ build_ports(struct northd_context *ctx,
    bbaaef
                 continue;
    bbaaef
             }
    bbaaef
     
    bbaaef
    -        op->sb = sbrec_port_binding_insert(ctx->ovnsb_txn);
    bbaaef
    +        ovn_port_set_sb(op, sbrec_port_binding_insert(ctx->ovnsb_txn));
    bbaaef
             ovn_port_update_sbrec(ctx, sbrec_chassis_by_name, op,
    bbaaef
                                   &chassis_qdisc_queues,
    bbaaef
                                   &active_ha_chassis_grps);
    bbaaef
    @@ -2938,6 +2993,14 @@ static const struct multicast_group mc_flood =
    bbaaef
     static const struct multicast_group mc_mrouter_flood =
    bbaaef
         { MC_MROUTER_FLOOD, OVN_MCAST_MROUTER_FLOOD_TUNNEL_KEY };
    bbaaef
     
    bbaaef
    +#define MC_MROUTER_STATIC "_MC_mrouter_static"
    bbaaef
    +static const struct multicast_group mc_mrouter_static =
    bbaaef
    +    { MC_MROUTER_STATIC, OVN_MCAST_MROUTER_STATIC_TUNNEL_KEY };
    bbaaef
    +
    bbaaef
    +#define MC_STATIC "_MC_static"
    bbaaef
    +static const struct multicast_group mc_static =
    bbaaef
    +    { MC_STATIC, OVN_MCAST_STATIC_TUNNEL_KEY };
    bbaaef
    +
    bbaaef
     #define MC_UNKNOWN "_MC_unknown"
    bbaaef
     static const struct multicast_group mc_unknown =
    bbaaef
         { MC_UNKNOWN, OVN_MCAST_UNKNOWN_TUNNEL_KEY };
    bbaaef
    @@ -3151,7 +3214,23 @@ ovn_igmp_group_get_ports(const struct sbrec_igmp_group *sb_igmp_group,
    bbaaef
     
    bbaaef
          *n_ports = 0;
    bbaaef
          for (size_t i = 0; i < sb_igmp_group->n_ports; i++) {
    bbaaef
    -        ports[(*n_ports)] =
    bbaaef
    +        struct ovn_port *port =
    bbaaef
    +            ovn_port_find(ovn_ports, sb_igmp_group->ports[i]->logical_port);
    bbaaef
    +
    bbaaef
    +        /* If this is already a flood port skip it for the group. */
    bbaaef
    +        if (port->mcast_info.flood) {
    bbaaef
    +            continue;
    bbaaef
    +        }
    bbaaef
    +
    bbaaef
    +        /* If this is already a port of a router on which relay is enabled,
    bbaaef
    +         * skip it for the group. Traffic is flooded there anyway.
    bbaaef
    +         */
    bbaaef
    +        if (port->peer && port->peer->od &&
    bbaaef
    +                port->peer->od->mcast_info.rtr.relay) {
    bbaaef
    +            continue;
    bbaaef
    +        }
    bbaaef
    +
    bbaaef
    +        ports[(*n_ports)] = port;
    bbaaef
                 ovn_port_find(ovn_ports, sb_igmp_group->ports[i]->logical_port);
    bbaaef
             if (ports[(*n_ports)]) {
    bbaaef
                 (*n_ports)++;
    bbaaef
    @@ -5531,9 +5610,18 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
    bbaaef
             struct mcast_switch_info *mcast_sw_info = &od->mcast_info.sw;
    bbaaef
     
    bbaaef
             if (mcast_sw_info->enabled) {
    bbaaef
    +            ds_clear(&actions);
    bbaaef
    +            if (mcast_sw_info->flood_reports) {
    bbaaef
    +                ds_put_cstr(&actions,
    bbaaef
    +                            "clone { "
    bbaaef
    +                                "outport = \""MC_MROUTER_STATIC"\"; "
    bbaaef
    +                                "output; "
    bbaaef
    +                            "};");
    bbaaef
    +            }
    bbaaef
    +            ds_put_cstr(&actions, "igmp;");
    bbaaef
                 /* Punt IGMP traffic to controller. */
    bbaaef
                 ovn_lflow_add(lflows, od, S_SWITCH_IN_L2_LKUP, 100,
    bbaaef
    -                          "ip4 && ip.proto == 2", "igmp;");
    bbaaef
    +                          "ip4 && ip.proto == 2", ds_cstr(&actions));
    bbaaef
     
    bbaaef
                 /* Flood all IP multicast traffic destined to 224.0.0.X to all
    bbaaef
                  * ports - RFC 4541, section 2.1.2, item 2.
    bbaaef
    @@ -5542,17 +5630,30 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
    bbaaef
                               "ip4 && ip4.dst == 224.0.0.0/24",
    bbaaef
                               "outport = \""MC_FLOOD"\"; output;");
    bbaaef
     
    bbaaef
    -            /* Drop unregistered IP multicast if not allowed. */
    bbaaef
    +            /* Forward uregistered IP multicast to routers with relay enabled
    bbaaef
    +             * and to any ports configured to flood IP multicast traffic.
    bbaaef
    +             * If configured to flood unregistered traffic this will be
    bbaaef
    +             * handled by the L2 multicast flow.
    bbaaef
    +             */
    bbaaef
                 if (!mcast_sw_info->flood_unregistered) {
    bbaaef
    -                /* Forward unregistered IP multicast to mrouter (if any). */
    bbaaef
    +                ds_clear(&actions);
    bbaaef
    +
    bbaaef
                     if (mcast_sw_info->flood_relay) {
    bbaaef
    -                    ovn_lflow_add(lflows, od, S_SWITCH_IN_L2_LKUP, 80,
    bbaaef
    -                                  "ip4 && ip4.mcast",
    bbaaef
    -                                  "outport = \""MC_MROUTER_FLOOD"\"; output;");
    bbaaef
    +                    ds_put_cstr(&actions,
    bbaaef
    +                                "clone { "
    bbaaef
    +                                    "outport = \""MC_MROUTER_FLOOD"\"; "
    bbaaef
    +                                    "output; "
    bbaaef
    +                                "}; ");
    bbaaef
    +                }
    bbaaef
    +
    bbaaef
    +                if (mcast_sw_info->flood_static) {
    bbaaef
    +                    ds_put_cstr(&actions, "outport =\""MC_STATIC"\"; output;");
    bbaaef
                     } else {
    bbaaef
    -                    ovn_lflow_add(lflows, od, S_SWITCH_IN_L2_LKUP, 80,
    bbaaef
    -                                  "ip4 && ip4.mcast", "drop;");
    bbaaef
    +                    ds_put_cstr(&actions, "drop;");
    bbaaef
                     }
    bbaaef
    +
    bbaaef
    +                ovn_lflow_add(lflows, od, S_SWITCH_IN_L2_LKUP, 80,
    bbaaef
    +                              "ip4 && ip4.mcast", ds_cstr(&actions));
    bbaaef
                 }
    bbaaef
             }
    bbaaef
     
    bbaaef
    @@ -5582,11 +5683,20 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
    bbaaef
     
    bbaaef
             ds_put_format(&match, "eth.mcast && ip4 && ip4.dst == %s ",
    bbaaef
                           igmp_group->mcgroup.name);
    bbaaef
    +
    bbaaef
             /* Also flood traffic to all multicast routers with relay enabled. */
    bbaaef
             if (mcast_sw_info->flood_relay) {
    bbaaef
                 ds_put_cstr(&actions,
    bbaaef
                             "clone { "
    bbaaef
    -                            "outport = \""MC_MROUTER_FLOOD "\"; output; "
    bbaaef
    +                            "outport = \""MC_MROUTER_FLOOD "\"; "
    bbaaef
    +                            "output; "
    bbaaef
    +                        "};");
    bbaaef
    +        }
    bbaaef
    +        if (mcast_sw_info->flood_static) {
    bbaaef
    +            ds_put_cstr(&actions,
    bbaaef
    +                        "clone { "
    bbaaef
    +                            "outport =\""MC_STATIC"\"; "
    bbaaef
    +                            "output; "
    bbaaef
                             "};");
    bbaaef
             }
    bbaaef
             ds_put_format(&actions, "outport = \"%s\"; output; ",
    bbaaef
    @@ -7783,6 +7893,7 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
    bbaaef
             if (!od->nbr || !od->mcast_info.rtr.relay) {
    bbaaef
                 continue;
    bbaaef
             }
    bbaaef
    +
    bbaaef
             struct ovn_igmp_group *igmp_group;
    bbaaef
     
    bbaaef
             LIST_FOR_EACH (igmp_group, list_node, &od->mcast_info.groups) {
    bbaaef
    @@ -7790,11 +7901,35 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
    bbaaef
                 ds_clear(&actions);
    bbaaef
                 ds_put_format(&match, "ip4 && ip4.dst == %s ",
    bbaaef
                               igmp_group->mcgroup.name);
    bbaaef
    +            if (od->mcast_info.rtr.flood_static) {
    bbaaef
    +                ds_put_cstr(&actions,
    bbaaef
    +                            "clone { "
    bbaaef
    +                                "outport = \""MC_STATIC"\"; "
    bbaaef
    +                                "ip.ttl--; "
    bbaaef
    +                                "next; "
    bbaaef
    +                            "};");
    bbaaef
    +            }
    bbaaef
                 ds_put_format(&actions, "outport = \"%s\"; ip.ttl--; next;",
    bbaaef
                               igmp_group->mcgroup.name);
    bbaaef
                 ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_ROUTING, 500,
    bbaaef
                               ds_cstr(&match), ds_cstr(&actions));
    bbaaef
             }
    bbaaef
    +
    bbaaef
    +        /* If needed, flood unregistered multicast on statically configured
    bbaaef
    +         * ports.
    bbaaef
    +         */
    bbaaef
    +        if (od->mcast_info.rtr.flood_static) {
    bbaaef
    +            ds_clear(&match);
    bbaaef
    +            ds_clear(&actions);
    bbaaef
    +            ds_put_format(&match, "ip4.mcast");
    bbaaef
    +            ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_ROUTING, 450,
    bbaaef
    +                          "ip4.mcast",
    bbaaef
    +                          "clone { "
    bbaaef
    +                                "outport = \""MC_STATIC"\"; "
    bbaaef
    +                                "ip.ttl--; "
    bbaaef
    +                                "next; "
    bbaaef
    +                          "};");
    bbaaef
    +        }
    bbaaef
         }
    bbaaef
     
    bbaaef
         /* Logical router ingress table 8: Policy.
    bbaaef
    @@ -8943,11 +9078,15 @@ build_mcast_groups(struct northd_context *ctx,
    bbaaef
         hmap_init(igmp_groups);
    bbaaef
     
    bbaaef
         HMAP_FOR_EACH (op, key_node, ports) {
    bbaaef
    -        if (!op->nbsp) {
    bbaaef
    -            continue;
    bbaaef
    -        }
    bbaaef
    -
    bbaaef
    -        if (lsp_is_enabled(op->nbsp)) {
    bbaaef
    +        if (op->nbrp && lrport_is_enabled(op->nbrp)) {
    bbaaef
    +            /* If this port is configured to always flood multicast traffic
    bbaaef
    +             * add it to the MC_STATIC group.
    bbaaef
    +             */
    bbaaef
    +            if (op->mcast_info.flood) {
    bbaaef
    +                ovn_multicast_add(mcast_groups, &mc_static, op);
    bbaaef
    +                op->od->mcast_info.rtr.flood_static = true;
    bbaaef
    +            }
    bbaaef
    +        } else if (op->nbsp && lsp_is_enabled(op->nbsp)) {
    bbaaef
                 ovn_multicast_add(mcast_groups, &mc_flood, op);
    bbaaef
     
    bbaaef
                 /* If this port is connected to a multicast router then add it
    bbaaef
    @@ -8957,6 +9096,22 @@ build_mcast_groups(struct northd_context *ctx,
    bbaaef
                         op->peer->od && op->peer->od->mcast_info.rtr.relay) {
    bbaaef
                     ovn_multicast_add(mcast_groups, &mc_mrouter_flood, op);
    bbaaef
                 }
    bbaaef
    +
    bbaaef
    +            /* If this port is configured to always flood multicast reports
    bbaaef
    +             * add it to the MC_MROUTER_STATIC group.
    bbaaef
    +             */
    bbaaef
    +            if (op->mcast_info.flood_reports) {
    bbaaef
    +                ovn_multicast_add(mcast_groups, &mc_mrouter_static, op);
    bbaaef
    +                op->od->mcast_info.sw.flood_reports = true;
    bbaaef
    +            }
    bbaaef
    +
    bbaaef
    +            /* If this port is configured to always flood multicast traffic
    bbaaef
    +             * add it to the MC_STATIC group.
    bbaaef
    +             */
    bbaaef
    +            if (op->mcast_info.flood) {
    bbaaef
    +                ovn_multicast_add(mcast_groups, &mc_static, op);
    bbaaef
    +                op->od->mcast_info.sw.flood_static = true;
    bbaaef
    +            }
    bbaaef
             }
    bbaaef
         }
    bbaaef
     
    bbaaef
    @@ -9017,8 +9172,13 @@ build_mcast_groups(struct northd_context *ctx,
    bbaaef
             for (size_t i = 0; i < od->n_router_ports; i++) {
    bbaaef
                 struct ovn_port *router_port = od->router_ports[i]->peer;
    bbaaef
     
    bbaaef
    +            /* If the router the port connects to doesn't have multicast
    bbaaef
    +             * relay enabled or if it was already configured to flood
    bbaaef
    +             * multicast traffic then skip it.
    bbaaef
    +             */
    bbaaef
                 if (!router_port || !router_port->od ||
    bbaaef
    -                    !router_port->od->mcast_info.rtr.relay) {
    bbaaef
    +                    !router_port->od->mcast_info.rtr.relay ||
    bbaaef
    +                    router_port->mcast_info.flood) {
    bbaaef
                     continue;
    bbaaef
                 }
    bbaaef
     
    bbaaef
    diff --git a/ovn/ovn-nb.xml b/ovn/ovn-nb.xml
    bbaaef
    index c2472a04a..e0051db25 100644
    bbaaef
    --- a/ovn/ovn-nb.xml
    bbaaef
    +++ b/ovn/ovn-nb.xml
    bbaaef
    @@ -671,6 +671,26 @@
    bbaaef
             </column>
    bbaaef
           </group>
    bbaaef
     
    bbaaef
    +      <group title="IP Multicast Snooping Options">
    bbaaef
    +        

    bbaaef
    +          These options apply when the port is part of a logical switch
    bbaaef
    +          which has <ref table="Logical_Switch" column="other_config"/>
    bbaaef
    +          :mcast_snoop set to true.
    bbaaef
    +        

    bbaaef
    +
    bbaaef
    +        
    bbaaef
    +                type='{"type": "boolean"}'>
    bbaaef
    +          If set to true, multicast packets (except reports) are
    bbaaef
    +          unconditionally forwarded to the specific port.
    bbaaef
    +        </column>
    bbaaef
    +
    bbaaef
    +        
    bbaaef
    +                type='{"type": "boolean"}'>
    bbaaef
    +          If set to true, multicast reports are unconditionally
    bbaaef
    +          forwarded to the specific port.
    bbaaef
    +        </column>
    bbaaef
    +      </group>
    bbaaef
    +
    bbaaef
         </group>
    bbaaef
     
    bbaaef
         <group title="Containers">
    bbaaef
    @@ -1962,6 +1982,20 @@
    bbaaef
               issues.
    bbaaef
             

    bbaaef
           </column>
    bbaaef
    +
    bbaaef
    +      
    bbaaef
    +              type='{"type": "boolean"}'>
    bbaaef
    +        

    bbaaef
    +          If set to true, multicast traffic (including reports)
    bbaaef
    +          are unconditionally forwarded to the specific port.
    bbaaef
    +        

    bbaaef
    +
    bbaaef
    +        

    bbaaef
    +          This option applies when the port is part of a logical router which
    bbaaef
    +          has <ref table="Logical_Router" column="options"/>:mcast_relay set
    bbaaef
    +          to true.
    bbaaef
    +        

    bbaaef
    +      </column>
    bbaaef
         </group>
    bbaaef
     
    bbaaef
         <group title="Attachment">
    bbaaef
    diff --git a/tests/ovn.at b/tests/ovn.at
    bbaaef
    index d52c97541..410a56fe5 100644
    bbaaef
    --- a/tests/ovn.at
    bbaaef
    +++ b/tests/ovn.at
    bbaaef
    @@ -16258,7 +16258,6 @@ OVN_CHECK_PACKETS([hv2/vif3-tx.pcap], [expected_empty])
    bbaaef
     
    bbaaef
     # Flush IGMP groups.
    bbaaef
     ovn-sbctl ip-multicast-flush sw1
    bbaaef
    -ovn-nbctl --wait=hv -t 3 sync
    bbaaef
     OVS_WAIT_UNTIL([
    bbaaef
         total_entries=`ovn-sbctl find IGMP_Group | grep "239.0.1.68" | wc -l`
    bbaaef
         test "${total_entries}" = "0"
    bbaaef
    @@ -16310,12 +16309,12 @@ send_ip_multicast_pkt hv2-vif4 hv2 \
    bbaaef
     # Sleep a bit to make sure no traffic is received and then check.
    bbaaef
     sleep 1
    bbaaef
     OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected_empty])
    bbaaef
    -OVN_CHECK_PACKETS([hv2/vif3-tx.pcap], [expected_empty])
    bbaaef
    -OVN_CHECK_PACKETS([hv1/vif4-tx.pcap], [expected_empty])
    bbaaef
     OVN_CHECK_PACKETS([hv1/vif2-tx.pcap], [expected_empty])
    bbaaef
     OVN_CHECK_PACKETS([hv1/vif3-tx.pcap], [expected_empty])
    bbaaef
    +OVN_CHECK_PACKETS([hv1/vif4-tx.pcap], [expected_empty])
    bbaaef
     OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected_empty])
    bbaaef
     OVN_CHECK_PACKETS([hv2/vif2-tx.pcap], [expected_empty])
    bbaaef
    +OVN_CHECK_PACKETS([hv2/vif3-tx.pcap], [expected_empty])
    bbaaef
     OVN_CHECK_PACKETS([hv2/vif4-tx.pcap], [expected_empty])
    bbaaef
     
    bbaaef
     # Enable IGMP relay on rtr
    bbaaef
    @@ -16386,6 +16385,82 @@ OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected_empty])
    bbaaef
     OVN_CHECK_PACKETS([hv2/vif2-tx.pcap], [expected_empty])
    bbaaef
     OVN_CHECK_PACKETS([hv2/vif4-tx.pcap], [expected_empty])
    bbaaef
     
    bbaaef
    +# Flush IGMP groups.
    bbaaef
    +ovn-sbctl ip-multicast-flush sw1
    bbaaef
    +ovn-sbctl ip-multicast-flush sw2
    bbaaef
    +ovn-sbctl ip-multicast-flush sw3
    bbaaef
    +OVS_WAIT_UNTIL([
    bbaaef
    +    total_entries=`ovn-sbctl find IGMP_Group | grep "239.0.1.68" | wc -l`
    bbaaef
    +    test "${total_entries}" = "0"
    bbaaef
    +])
    bbaaef
    +
    bbaaef
    +as hv1 reset_pcap_file hv1-vif1 hv1/vif1
    bbaaef
    +as hv1 reset_pcap_file hv1-vif2 hv1/vif2
    bbaaef
    +as hv1 reset_pcap_file hv1-vif3 hv1/vif3
    bbaaef
    +as hv1 reset_pcap_file hv1-vif4 hv1/vif4
    bbaaef
    +as hv2 reset_pcap_file hv2-vif1 hv2/vif1
    bbaaef
    +as hv2 reset_pcap_file hv2-vif2 hv2/vif2
    bbaaef
    +as hv2 reset_pcap_file hv2-vif3 hv2/vif3
    bbaaef
    +as hv2 reset_pcap_file hv2-vif4 hv2/vif4
    bbaaef
    +
    bbaaef
    +truncate -s 0 expected_empty
    bbaaef
    +truncate -s 0 expected_switched
    bbaaef
    +truncate -s 0 expected_routed
    bbaaef
    +truncate -s 0 expected_reports
    bbaaef
    +
    bbaaef
    +# Enable mcast_flood on sw1-p11
    bbaaef
    +ovn-nbctl set Logical_Switch_Port sw1-p11 options:mcast_flood='true'
    bbaaef
    +
    bbaaef
    +# Enable mcast_flood_reports on sw1-p21
    bbaaef
    +ovn-nbctl set Logical_Switch_Port sw1-p21 options:mcast_flood_reports='true'
    bbaaef
    +# Enable mcast_flood on rtr-sw2
    bbaaef
    +ovn-nbctl set Logical_Router_Port rtr-sw2 options:mcast_flood='true'
    bbaaef
    +# Enable mcast_flood on sw2-p1
    bbaaef
    +ovn-nbctl set Logical_Switch_Port sw2-p1 options:mcast_flood='true'
    bbaaef
    +
    bbaaef
    +ovn-nbctl --wait=hv sync
    bbaaef
    +
    bbaaef
    +# Inject IGMP Join for 239.0.1.68 on sw1-p12.
    bbaaef
    +send_igmp_v3_report hv1-vif2 hv1 \
    bbaaef
    +    000000000001 $(ip_to_hex 10 0 0 1) f9f8 \
    bbaaef
    +    $(ip_to_hex 239 0 1 68) 04 e9b9 \
    bbaaef
    +    expected_reports
    bbaaef
    +
    bbaaef
    +# Check that the IGMP Group is learned.
    bbaaef
    +OVS_WAIT_UNTIL([
    bbaaef
    +    total_entries=`ovn-sbctl find IGMP_Group | grep "239.0.1.68" | wc -l`
    bbaaef
    +    test "${total_entries}" = "1"
    bbaaef
    +])
    bbaaef
    +
    bbaaef
    +# Send traffic from sw1-p21
    bbaaef
    +send_ip_multicast_pkt hv2-vif1 hv2 \
    bbaaef
    +    000000000001 01005e000144 \
    bbaaef
    +    $(ip_to_hex 10 0 0 42) $(ip_to_hex 239 0 1 68) 1e 20 ca70 11 \
    bbaaef
    +    e518e518000a3b3a0000
    bbaaef
    +store_ip_multicast_pkt \
    bbaaef
    +    000000000001 01005e000144 \
    bbaaef
    +    $(ip_to_hex 10 0 0 42) $(ip_to_hex 239 0 1 68) 1e 20 ca70 11 \
    bbaaef
    +    e518e518000a3b3a0000 expected_switched
    bbaaef
    +store_ip_multicast_pkt \
    bbaaef
    +    000000000200 01005e000144 \
    bbaaef
    +    $(ip_to_hex 10 0 0 42) $(ip_to_hex 239 0 1 68) 1e 1f cb70 11 \
    bbaaef
    +    e518e518000a3b3a0000 expected_routed
    bbaaef
    +
    bbaaef
    +# Sleep a bit to make sure no duplicate traffic is received
    bbaaef
    +sleep 1
    bbaaef
    +
    bbaaef
    +# Check that traffic is switched to sw1-p11 and sw1-p12
    bbaaef
    +# Check that IGMP join is flooded on sw1-p21
    bbaaef
    +# Check that traffic is routed by rtr to rtr-sw2 and then switched to sw2-p1
    bbaaef
    +OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected_switched])
    bbaaef
    +OVN_CHECK_PACKETS([hv1/vif2-tx.pcap], [expected_switched])
    bbaaef
    +OVN_CHECK_PACKETS([hv1/vif3-tx.pcap], [expected_routed])
    bbaaef
    +OVN_CHECK_PACKETS([hv1/vif4-tx.pcap], [expected_empty])
    bbaaef
    +OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected_reports])
    bbaaef
    +OVN_CHECK_PACKETS([hv2/vif2-tx.pcap], [expected_empty])
    bbaaef
    +OVN_CHECK_PACKETS([hv2/vif3-tx.pcap], [expected_empty])
    bbaaef
    +OVN_CHECK_PACKETS([hv2/vif4-tx.pcap], [expected_empty])
    bbaaef
    +
    bbaaef
     OVN_CLEANUP([hv1], [hv2])
    bbaaef
     AT_CLEANUP
    bbaaef
     
    bbaaef
    -- 
    bbaaef
    2.21.0
    bbaaef