[ovs-dev,ovn,v2] ovn-northd: Add static IP multicast flood configuration
diff mbox series

Message ID 1569312169-27322-1-git-send-email-dceara@redhat.com
State New
Headers show
Series
  • [ovs-dev,ovn,v2] ovn-northd: Add static IP multicast flood configuration
Related show

Commit Message

Dumitru Ceara Sept. 24, 2019, 8:02 a.m. UTC
Add the following new configuration options to the
Logical_Switch_Port:options column in the OVN Northbound database:

- mcast_flood: if set to 'true' all incoming IP multicast traffic
  (except IP multicast reports) entering the switch will also be
  flooded on the logical switch port.
- mcast_flood_reports: if set to 'true' all incoming IP multicast
  entering the switch will also be flooded on the logical switch
  port. A clone of the packets is also processed by ovn-controller
  for snooping.

Add the following new configuration option to the
Logical_Router_Port:options column in the OVN Northbound database:

- mcast_flood: if set to 'true' all incoming IP multicast traffic
  (including IP multicast reports) entering the router will be also
  flooded on the logical router port.

Due to the fact that in the router pipeline multicast reports are
not treated in a special way there's no need for an explicit
'mcast_flood_reports' option for router ports.

Signed-off-by: Dumitru Ceara <dceara@redhat.com>

---
v2: Rebase & fix tag in ovn-nb.xml
---
 lib/mcast-group-index.h |   2 +
 northd/ovn-northd.8.xml |  30 +++++--
 northd/ovn-northd.c     | 212 ++++++++++++++++++++++++++++++++++++++++++------
 ovn-nb.xml              |  33 ++++++++
 tests/ovn.at            |  81 +++++++++++++++++-
 5 files changed, 324 insertions(+), 34 deletions(-)

Comments

Mark Michelson Sept. 26, 2019, 8:53 p.m. UTC | #1
Looks good to me

Acked-by: Mark Michelson <mmichels@redhat.com>

On 9/24/19 4:02 AM, Dumitru Ceara wrote:
> Add the following new configuration options to the
> Logical_Switch_Port:options column in the OVN Northbound database:
> 
> - mcast_flood: if set to 'true' all incoming IP multicast traffic
>    (except IP multicast reports) entering the switch will also be
>    flooded on the logical switch port.
> - mcast_flood_reports: if set to 'true' all incoming IP multicast
>    entering the switch will also be flooded on the logical switch
>    port. A clone of the packets is also processed by ovn-controller
>    for snooping.
> 
> Add the following new configuration option to the
> Logical_Router_Port:options column in the OVN Northbound database:
> 
> - mcast_flood: if set to 'true' all incoming IP multicast traffic
>    (including IP multicast reports) entering the router will be also
>    flooded on the logical router port.
> 
> Due to the fact that in the router pipeline multicast reports are
> not treated in a special way there's no need for an explicit
> 'mcast_flood_reports' option for router ports.
> 
> Signed-off-by: Dumitru Ceara <dceara@redhat.com>
> 
> ---
> v2: Rebase & fix tag in ovn-nb.xml
> ---
>   lib/mcast-group-index.h |   2 +
>   northd/ovn-northd.8.xml |  30 +++++--
>   northd/ovn-northd.c     | 212 ++++++++++++++++++++++++++++++++++++++++++------
>   ovn-nb.xml              |  33 ++++++++
>   tests/ovn.at            |  81 +++++++++++++++++-
>   5 files changed, 324 insertions(+), 34 deletions(-)
> 
> diff --git a/lib/mcast-group-index.h b/lib/mcast-group-index.h
> index cb49ad7..ba995ba 100644
> --- a/lib/mcast-group-index.h
> +++ b/lib/mcast-group-index.h
> @@ -28,6 +28,8 @@ enum ovn_mcast_tunnel_keys {
>       OVN_MCAST_FLOOD_TUNNEL_KEY = OVN_MIN_MULTICAST,
>       OVN_MCAST_UNKNOWN_TUNNEL_KEY,
>       OVN_MCAST_MROUTER_FLOOD_TUNNEL_KEY,
> +    OVN_MCAST_MROUTER_STATIC_TUNNEL_KEY,
> +    OVN_MCAST_STATIC_TUNNEL_KEY,
>       OVN_MIN_IP_MULTICAST,
>       OVN_MAX_IP_MULTICAST = OVN_MAX_MULTICAST,
>   };
> diff --git a/northd/ovn-northd.8.xml b/northd/ovn-northd.8.xml
> index 0f4f1c1..429ed7e 100644
> --- a/northd/ovn-northd.8.xml
> +++ b/northd/ovn-northd.8.xml
> @@ -954,7 +954,11 @@ output;
>         <li>
>           A priority-100 flow that punts all IGMP packets to
>           <code>ovn-controller</code> if IGMP snooping is enabled on the
> -        logical switch.
> +        logical switch. The flow also forwards the IGMP packets to the
> +        <code>MC_MROUTER_STATIC</code> multicast group, which
> +        <code>ovn-northd</code> populates with all the logical ports that
> +        have <ref column="options" table="Logical_Switch_Port"/>
> +        <code>:mcast_flood_reports='true'</code>.
>         </li>
>   
>         <li>
> @@ -976,10 +980,15 @@ output;
>   
>         <li>
>           A priority-80 flow that forwards all unregistered IP multicast traffic
> -        to the <code>MC_MROUTER_FLOOD</code> multicast group, if any.
> -        Otherwise the flow drops all unregistered IP multicast packets.  This
> -        flow is added only if <ref column="other_config"
> -        table="Logical_Switch"/>:mcast_flood_unregistered='false'.
> +        to the <code>MC_STATIC</code> multicast group, which
> +        <code>ovn-northd</code> populates with all the logical ports that
> +        have <ref column="options" table="Logical_Switch_Port"/>
> +        <code>:mcast_flood='true'</code>. The flow also forwards
> +        unregistered IP multicast traffic to the <code>MC_MROUTER_FLOOD</code>
> +        multicast group, which <code>ovn-northd</code> populates with all the
> +        logical ports connected to logical routers that have
> +        <ref column="options" table="Logical_Router"/>
> +        <code>:mcast_relay='true'</code>.
>         </li>
>   
>         <li>
> @@ -2027,6 +2036,17 @@ output;
>   
>         <li>
>           <p>
> +          Priority-450 flow that matches unregistered IP multicast traffic
> +          and sets <code>outport</code> to the <code>MC_STATIC</code>
> +          multicast group, which <code>ovn-northd</code> populates with the
> +          logical ports that have
> +          <ref column="options" table="Logical_Router_Port"/>
> +          <code>:mcast_flood='true'</code>.
> +        </p>
> +      </li>
> +
> +      <li>
> +        <p>
>             For distributed logical routers where one of the logical router
>             ports specifies a <code>redirect-chassis</code>, a priority-400
>             logical flow for each ip source/destination couple that matches the
> diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c
> index f393ceb..538daa1 100644
> --- a/northd/ovn-northd.c
> +++ b/northd/ovn-northd.c
> @@ -448,7 +448,12 @@ struct mcast_switch_info {
>                                    * should be flooded to the mrouter. Only
>                                    * applicable if flood_unregistered == false.
>                                    */
> -
> +    bool flood_reports;         /* True if the switch has at least one port
> +                                 * configured to flood reports.
> +                                 */
> +    bool flood_static;          /* True if the switch has at least one port
> +                                 * configured to flood traffic.
> +                                 */
>       int64_t table_size;         /* Max number of IP multicast groups. */
>       int64_t idle_timeout;       /* Timeout after which an idle group is
>                                    * flushed.
> @@ -466,7 +471,10 @@ struct mcast_switch_info {
>   };
>   
>   struct mcast_router_info {
> -    bool relay; /* True if the router should relay IP multicast. */
> +    bool relay;        /* True if the router should relay IP multicast. */
> +    bool flood_static; /* True if the router has at least one port configured
> +                        * to flood traffic.
> +                        */
>   };
>   
>   struct mcast_info {
> @@ -481,6 +489,34 @@ struct mcast_info {
>       };
>   };
>   
> +struct mcast_port_info {
> +    bool flood;         /* True if the port should flood IP multicast traffic
> +                         * regardless if it's registered or not. */
> +    bool flood_reports; /* True if the port should flood IP multicast reports
> +                         * (e.g., IGMP join/leave). */
> +};
> +
> +static void
> +init_mcast_port_info(struct mcast_port_info *mcast_info,
> +                     const struct nbrec_logical_switch_port *nbsp,
> +                     const struct nbrec_logical_router_port *nbrp)
> +{
> +    if (nbsp) {
> +        mcast_info->flood =
> +            smap_get_bool(&nbsp->options, "mcast_flood", false);
> +        mcast_info->flood_reports =
> +            smap_get_bool(&nbsp->options, "mcast_flood_reports",
> +                          false);
> +    } else if (nbrp) {
> +        /* We don't process multicast reports in any special way on logical
> +         * routers so just treat them as regular multicast traffic.
> +         */
> +        mcast_info->flood =
> +            smap_get_bool(&nbrp->options, "mcast_flood", false);
> +        mcast_info->flood_reports = mcast_info->flood;
> +    }
> +}
> +
>   static uint32_t
>   ovn_mcast_group_allocate_key(struct mcast_info *mcast_info)
>   {
> @@ -1022,7 +1058,7 @@ build_datapaths(struct northd_context *ctx, struct hmap *datapaths,
>           ovn_datapath_destroy(datapaths, od);
>       }
>   }
> -
> +
>   struct ovn_port {
>       struct hmap_node key_node;  /* Index on 'key'. */
>       char *key;                  /* nbs->name, nbr->name, sb->logical_port. */
> @@ -1044,6 +1080,9 @@ struct ovn_port {
>   
>       struct lport_addresses lrp_networks;
>   
> +    /* Logical port multicast data. */
> +    struct mcast_port_info mcast_info;
> +
>       bool derived; /* Indicates whether this is an additional port
>                      * derived from nbsp or nbrp. */
>   
> @@ -1060,6 +1099,23 @@ struct ovn_port {
>       struct ovs_list list;       /* In list of similar records. */
>   };
>   
> +static void
> +ovn_port_set_sb(struct ovn_port *op,
> +                const struct sbrec_port_binding *sb)
> +{
> +    op->sb = sb;
> +}
> +
> +static void
> +ovn_port_set_nb(struct ovn_port *op,
> +                const struct nbrec_logical_switch_port *nbsp,
> +                const struct nbrec_logical_router_port *nbrp)
> +{
> +    op->nbsp = nbsp;
> +    op->nbrp = nbrp;
> +    init_mcast_port_info(&op->mcast_info, op->nbsp, op->nbrp);
> +}
> +
>   static struct ovn_port *
>   ovn_port_create(struct hmap *ports, const char *key,
>                   const struct nbrec_logical_switch_port *nbsp,
> @@ -1073,9 +1129,8 @@ ovn_port_create(struct hmap *ports, const char *key,
>       op->json_key = ds_steal_cstr(&json_key);
>   
>       op->key = xstrdup(key);
> -    op->sb = sb;
> -    op->nbsp = nbsp;
> -    op->nbrp = nbrp;
> +    ovn_port_set_sb(op, sb);
> +    ovn_port_set_nb(op, nbsp, nbrp);
>       op->derived = false;
>       hmap_insert(ports, &op->key_node, hash_string(op->key, 0));
>       return op;
> @@ -1878,7 +1933,7 @@ join_logical_ports(struct northd_context *ctx,
>                                        nbsp->name);
>                           continue;
>                       }
> -                    op->nbsp = nbsp;
> +                    ovn_port_set_nb(op, nbsp, NULL);
>                       ovs_list_remove(&op->list);
>   
>                       uint32_t queue_id = smap_get_int(&op->sb->options,
> @@ -1969,7 +2024,7 @@ join_logical_ports(struct northd_context *ctx,
>                                        nbrp->name);
>                           continue;
>                       }
> -                    op->nbrp = nbrp;
> +                    ovn_port_set_nb(op, NULL, nbrp);
>                       ovs_list_remove(&op->list);
>                       ovs_list_push_back(both, &op->list);
>   
> @@ -2014,7 +2069,7 @@ join_logical_ports(struct northd_context *ctx,
>                       struct ovn_port *crp = ovn_port_find(ports, redirect_name);
>                       if (crp) {
>                           crp->derived = true;
> -                        crp->nbrp = nbrp;
> +                        ovn_port_set_nb(crp, NULL, nbrp);
>                           ovs_list_remove(&crp->list);
>                           ovs_list_push_back(both, &crp->list);
>                       } else {
> @@ -2890,7 +2945,7 @@ build_ports(struct northd_context *ctx,
>               continue;
>           }
>   
> -        op->sb = sbrec_port_binding_insert(ctx->ovnsb_txn);
> +        ovn_port_set_sb(op, sbrec_port_binding_insert(ctx->ovnsb_txn));
>           ovn_port_update_sbrec(ctx, sbrec_chassis_by_name, op,
>                                 &chassis_qdisc_queues,
>                                 &active_ha_chassis_grps);
> @@ -2932,6 +2987,14 @@ static const struct multicast_group mc_flood =
>   static const struct multicast_group mc_mrouter_flood =
>       { MC_MROUTER_FLOOD, OVN_MCAST_MROUTER_FLOOD_TUNNEL_KEY };
>   
> +#define MC_MROUTER_STATIC "_MC_mrouter_static"
> +static const struct multicast_group mc_mrouter_static =
> +    { MC_MROUTER_STATIC, OVN_MCAST_MROUTER_STATIC_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 };
> @@ -3145,7 +3208,23 @@ ovn_igmp_group_get_ports(const struct sbrec_igmp_group *sb_igmp_group,
>   
>        *n_ports = 0;
>        for (size_t i = 0; i < sb_igmp_group->n_ports; i++) {
> -        ports[(*n_ports)] =
> +        struct ovn_port *port =
> +            ovn_port_find(ovn_ports, sb_igmp_group->ports[i]->logical_port);
> +
> +        /* 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;
> +        }
> +
> +        ports[(*n_ports)] = port;
>               ovn_port_find(ovn_ports, sb_igmp_group->ports[i]->logical_port);
>           if (ports[(*n_ports)]) {
>               (*n_ports)++;
> @@ -5440,9 +5519,18 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
>           struct mcast_switch_info *mcast_sw_info = &od->mcast_info.sw;
>   
>           if (mcast_sw_info->enabled) {
> +            ds_clear(&actions);
> +            if (mcast_sw_info->flood_reports) {
> +                ds_put_cstr(&actions,
> +                            "clone { "
> +                                "outport = \""MC_MROUTER_STATIC"\"; "
> +                                "output; "
> +                            "};");
> +            }
> +            ds_put_cstr(&actions, "igmp;");
>               /* Punt IGMP traffic to controller. */
>               ovn_lflow_add(lflows, od, S_SWITCH_IN_L2_LKUP, 100,
> -                          "ip4 && ip.proto == 2", "igmp;");
> +                          "ip4 && ip.proto == 2", ds_cstr(&actions));
>   
>               /* Flood all IP multicast traffic destined to 224.0.0.X to all
>                * ports - RFC 4541, section 2.1.2, item 2.
> @@ -5451,17 +5539,30 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
>                             "ip4 && ip4.dst == 224.0.0.0/24",
>                             "outport = \""MC_FLOOD"\"; output;");
>   
> -            /* Drop unregistered IP multicast if not allowed. */
> +            /* Forward uregistered IP multicast to routers with relay enabled
> +             * and to any ports configured to flood IP multicast traffic.
> +             * If configured to flood unregistered traffic this will be
> +             * handled by the L2 multicast flow.
> +             */
>               if (!mcast_sw_info->flood_unregistered) {
> -                /* Forward unregistered IP multicast to mrouter (if any). */
> +                ds_clear(&actions);
> +
>                   if (mcast_sw_info->flood_relay) {
> -                    ovn_lflow_add(lflows, od, S_SWITCH_IN_L2_LKUP, 80,
> -                                  "ip4 && ip4.mcast",
> -                                  "outport = \""MC_MROUTER_FLOOD"\"; output;");
> +                    ds_put_cstr(&actions,
> +                                "clone { "
> +                                    "outport = \""MC_MROUTER_FLOOD"\"; "
> +                                    "output; "
> +                                "}; ");
> +                }
> +
> +                if (mcast_sw_info->flood_static) {
> +                    ds_put_cstr(&actions, "outport =\""MC_STATIC"\"; output;");
>                   } else {
> -                    ovn_lflow_add(lflows, od, S_SWITCH_IN_L2_LKUP, 80,
> -                                  "ip4 && ip4.mcast", "drop;");
> +                    ds_put_cstr(&actions, "drop;");
>                   }
> +
> +                ovn_lflow_add(lflows, od, S_SWITCH_IN_L2_LKUP, 80,
> +                              "ip4 && ip4.mcast", ds_cstr(&actions));
>               }
>           }
>   
> @@ -5491,11 +5592,20 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
>   
>           ds_put_format(&match, "eth.mcast && ip4 && ip4.dst == %s ",
>                         igmp_group->mcgroup.name);
> +
>           /* Also flood traffic to all multicast routers with relay enabled. */
>           if (mcast_sw_info->flood_relay) {
>               ds_put_cstr(&actions,
>                           "clone { "
> -                            "outport = \""MC_MROUTER_FLOOD "\"; output; "
> +                            "outport = \""MC_MROUTER_FLOOD "\"; "
> +                            "output; "
> +                        "};");
> +        }
> +        if (mcast_sw_info->flood_static) {
> +            ds_put_cstr(&actions,
> +                        "clone { "
> +                            "outport =\""MC_STATIC"\"; "
> +                            "output; "
>                           "};");
>           }
>           ds_put_format(&actions, "outport = \"%s\"; output; ",
> @@ -7711,6 +7821,7 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
>           if (!od->nbr || !od->mcast_info.rtr.relay) {
>               continue;
>           }
> +
>           struct ovn_igmp_group *igmp_group;
>   
>           LIST_FOR_EACH (igmp_group, list_node, &od->mcast_info.groups) {
> @@ -7718,11 +7829,35 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
>               ds_clear(&actions);
>               ds_put_format(&match, "ip4 && ip4.dst == %s ",
>                             igmp_group->mcgroup.name);
> +            if (od->mcast_info.rtr.flood_static) {
> +                ds_put_cstr(&actions,
> +                            "clone { "
> +                                "outport = \""MC_STATIC"\"; "
> +                                "ip.ttl--; "
> +                                "next; "
> +                            "};");
> +            }
>               ds_put_format(&actions, "outport = \"%s\"; ip.ttl--; next;",
>                             igmp_group->mcgroup.name);
>               ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_ROUTING, 500,
>                             ds_cstr(&match), ds_cstr(&actions));
>           }
> +
> +        /* If needed, flood unregistered multicast on statically configured
> +         * ports.
> +         */
> +        if (od->mcast_info.rtr.flood_static) {
> +            ds_clear(&match);
> +            ds_clear(&actions);
> +            ds_put_format(&match, "ip4.mcast");
> +            ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_ROUTING, 450,
> +                          "ip4.mcast",
> +                          "clone { "
> +                                "outport = \""MC_STATIC"\"; "
> +                                "ip.ttl--; "
> +                                "next; "
> +                          "};");
> +        }
>       }
>   
>       /* Logical router ingress table 8: Policy.
> @@ -8893,11 +9028,15 @@ build_mcast_groups(struct northd_context *ctx,
>       hmap_init(igmp_groups);
>   
>       HMAP_FOR_EACH (op, key_node, ports) {
> -        if (!op->nbsp) {
> -            continue;
> -        }
> -
> -        if (lsp_is_enabled(op->nbsp)) {
> +        if (op->nbrp && 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;
> +            }
> +        } else if (op->nbsp && lsp_is_enabled(op->nbsp)) {
>               ovn_multicast_add(mcast_groups, &mc_flood, op);
>   
>               /* If this port is connected to a multicast router then add it
> @@ -8907,6 +9046,22 @@ build_mcast_groups(struct northd_context *ctx,
>                       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_STATIC group.
> +             */
> +            if (op->mcast_info.flood_reports) {
> +                ovn_multicast_add(mcast_groups, &mc_mrouter_static, 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;
> +            }
>           }
>       }
>   
> @@ -8967,8 +9122,13 @@ build_mcast_groups(struct northd_context *ctx,
>           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->od->mcast_info.rtr.relay ||
> +                    router_port->mcast_info.flood) {
>                   continue;
>               }
>   
> diff --git a/ovn-nb.xml b/ovn-nb.xml
> index b41b579..1504f8f 100644
> --- a/ovn-nb.xml
> +++ b/ovn-nb.xml
> @@ -671,6 +671,26 @@
>           </column>
>         </group>
>   
> +      <group title="IP Multicast Snooping Options">
> +        <p>
> +          These options apply when the port is part of a logical switch
> +          which has <ref table="Logical_Switch" column="other_config"/>
> +          :mcast_snoop set to <code>true</code>.
> +        </p>
> +
> +        <column name="options" key="mcast_flood"
> +                type='{"type": "boolean"}'>
> +          If set to <code>true</code>, multicast packets (except reports) are
> +          unconditionally forwarded to the specific port.
> +        </column>
> +
> +        <column name="options" key="mcast_flood_reports"
> +                type='{"type": "boolean"}'>
> +          If set to <code>true</code>, multicast reports are unconditionally
> +          forwarded to the specific port.
> +        </column>
> +      </group>
> +
>       </group>
>   
>       <group title="Containers">
> @@ -2005,6 +2025,19 @@
>   
>         </column>
>   
> +      <column name="options" key="mcast_flood"
> +              type='{"type": "boolean"}'>
> +        <p>
> +          If set to <code>true</code>, multicast traffic (including reports)
> +          are unconditionally forwarded to the specific port.
> +        </p>
> +
> +        <p>
> +          This option applies when the port is part of a logical router which
> +          has <ref table="Logical_Router" column="options"/>:mcast_relay set
> +          to <code>true</code>.
> +        </p>
> +      </column>
>       </group>
>   
>       <group title="Attachment">
> diff --git a/tests/ovn.at b/tests/ovn.at
> index 04898dd..f03e701 100644
> --- a/tests/ovn.at
> +++ b/tests/ovn.at
> @@ -15122,7 +15122,6 @@ OVN_CHECK_PACKETS([hv2/vif3-tx.pcap], [expected_empty])
>   
>   # Flush IGMP groups.
>   ovn-sbctl ip-multicast-flush sw1
> -ovn-nbctl --wait=hv -t 3 sync
>   OVS_WAIT_UNTIL([
>       total_entries=`ovn-sbctl find IGMP_Group | grep "239.0.1.68" | wc -l`
>       test "${total_entries}" = "0"
> @@ -15174,12 +15173,12 @@ send_ip_multicast_pkt hv2-vif4 hv2 \
>   # Sleep a bit to make sure no traffic is received and then check.
>   sleep 1
>   OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected_empty])
> -OVN_CHECK_PACKETS([hv2/vif3-tx.pcap], [expected_empty])
> -OVN_CHECK_PACKETS([hv1/vif4-tx.pcap], [expected_empty])
>   OVN_CHECK_PACKETS([hv1/vif2-tx.pcap], [expected_empty])
>   OVN_CHECK_PACKETS([hv1/vif3-tx.pcap], [expected_empty])
> +OVN_CHECK_PACKETS([hv1/vif4-tx.pcap], [expected_empty])
>   OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected_empty])
>   OVN_CHECK_PACKETS([hv2/vif2-tx.pcap], [expected_empty])
> +OVN_CHECK_PACKETS([hv2/vif3-tx.pcap], [expected_empty])
>   OVN_CHECK_PACKETS([hv2/vif4-tx.pcap], [expected_empty])
>   
>   # Enable IGMP relay on rtr
> @@ -15250,6 +15249,82 @@ OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected_empty])
>   OVN_CHECK_PACKETS([hv2/vif2-tx.pcap], [expected_empty])
>   OVN_CHECK_PACKETS([hv2/vif4-tx.pcap], [expected_empty])
>   
> +# Flush IGMP groups.
> +ovn-sbctl ip-multicast-flush sw1
> +ovn-sbctl ip-multicast-flush sw2
> +ovn-sbctl ip-multicast-flush sw3
> +OVS_WAIT_UNTIL([
> +    total_entries=`ovn-sbctl find IGMP_Group | grep "239.0.1.68" | wc -l`
> +    test "${total_entries}" = "0"
> +])
> +
> +as hv1 reset_pcap_file hv1-vif1 hv1/vif1
> +as hv1 reset_pcap_file hv1-vif2 hv1/vif2
> +as hv1 reset_pcap_file hv1-vif3 hv1/vif3
> +as hv1 reset_pcap_file hv1-vif4 hv1/vif4
> +as hv2 reset_pcap_file hv2-vif1 hv2/vif1
> +as hv2 reset_pcap_file hv2-vif2 hv2/vif2
> +as hv2 reset_pcap_file hv2-vif3 hv2/vif3
> +as hv2 reset_pcap_file hv2-vif4 hv2/vif4
> +
> +truncate -s 0 expected_empty
> +truncate -s 0 expected_switched
> +truncate -s 0 expected_routed
> +truncate -s 0 expected_reports
> +
> +# Enable mcast_flood on sw1-p11
> +ovn-nbctl set Logical_Switch_Port sw1-p11 options:mcast_flood='true'
> +
> +# Enable mcast_flood_reports on sw1-p21
> +ovn-nbctl set Logical_Switch_Port sw1-p21 options:mcast_flood_reports='true'
> +# Enable mcast_flood on rtr-sw2
> +ovn-nbctl set Logical_Router_Port rtr-sw2 options:mcast_flood='true'
> +# Enable mcast_flood on sw2-p1
> +ovn-nbctl set Logical_Switch_Port sw2-p1 options:mcast_flood='true'
> +
> +ovn-nbctl --wait=hv sync
> +
> +# Inject IGMP Join for 239.0.1.68 on sw1-p12.
> +send_igmp_v3_report hv1-vif2 hv1 \
> +    000000000001 $(ip_to_hex 10 0 0 1) f9f8 \
> +    $(ip_to_hex 239 0 1 68) 04 e9b9 \
> +    expected_reports
> +
> +# Check that the IGMP Group is learned.
> +OVS_WAIT_UNTIL([
> +    total_entries=`ovn-sbctl find IGMP_Group | grep "239.0.1.68" | wc -l`
> +    test "${total_entries}" = "1"
> +])
> +
> +# Send traffic from sw1-p21
> +send_ip_multicast_pkt hv2-vif1 hv2 \
> +    000000000001 01005e000144 \
> +    $(ip_to_hex 10 0 0 42) $(ip_to_hex 239 0 1 68) 1e 20 ca70 11 \
> +    e518e518000a3b3a0000
> +store_ip_multicast_pkt \
> +    000000000001 01005e000144 \
> +    $(ip_to_hex 10 0 0 42) $(ip_to_hex 239 0 1 68) 1e 20 ca70 11 \
> +    e518e518000a3b3a0000 expected_switched
> +store_ip_multicast_pkt \
> +    000000000200 01005e000144 \
> +    $(ip_to_hex 10 0 0 42) $(ip_to_hex 239 0 1 68) 1e 1f cb70 11 \
> +    e518e518000a3b3a0000 expected_routed
> +
> +# Sleep a bit to make sure no duplicate traffic is received
> +sleep 1
> +
> +# Check that traffic is switched to sw1-p11 and sw1-p12
> +# Check that IGMP join is flooded on sw1-p21
> +# Check that traffic is routed by rtr to rtr-sw2 and then switched to sw2-p1
> +OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected_switched])
> +OVN_CHECK_PACKETS([hv1/vif2-tx.pcap], [expected_switched])
> +OVN_CHECK_PACKETS([hv1/vif3-tx.pcap], [expected_routed])
> +OVN_CHECK_PACKETS([hv1/vif4-tx.pcap], [expected_empty])
> +OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected_reports])
> +OVN_CHECK_PACKETS([hv2/vif2-tx.pcap], [expected_empty])
> +OVN_CHECK_PACKETS([hv2/vif3-tx.pcap], [expected_empty])
> +OVN_CHECK_PACKETS([hv2/vif4-tx.pcap], [expected_empty])
> +
>   OVN_CLEANUP([hv1], [hv2])
>   AT_CLEANUP
>   
>
Mark Michelson Oct. 9, 2019, 3:01 p.m. UTC | #2
I've merged this to master.

On 9/24/19 4:02 AM, Dumitru Ceara wrote:
> Add the following new configuration options to the
> Logical_Switch_Port:options column in the OVN Northbound database:
> 
> - mcast_flood: if set to 'true' all incoming IP multicast traffic
>    (except IP multicast reports) entering the switch will also be
>    flooded on the logical switch port.
> - mcast_flood_reports: if set to 'true' all incoming IP multicast
>    entering the switch will also be flooded on the logical switch
>    port. A clone of the packets is also processed by ovn-controller
>    for snooping.
> 
> Add the following new configuration option to the
> Logical_Router_Port:options column in the OVN Northbound database:
> 
> - mcast_flood: if set to 'true' all incoming IP multicast traffic
>    (including IP multicast reports) entering the router will be also
>    flooded on the logical router port.
> 
> Due to the fact that in the router pipeline multicast reports are
> not treated in a special way there's no need for an explicit
> 'mcast_flood_reports' option for router ports.
> 
> Signed-off-by: Dumitru Ceara <dceara@redhat.com>
> 
> ---
> v2: Rebase & fix tag in ovn-nb.xml
> ---
>   lib/mcast-group-index.h |   2 +
>   northd/ovn-northd.8.xml |  30 +++++--
>   northd/ovn-northd.c     | 212 ++++++++++++++++++++++++++++++++++++++++++------
>   ovn-nb.xml              |  33 ++++++++
>   tests/ovn.at            |  81 +++++++++++++++++-
>   5 files changed, 324 insertions(+), 34 deletions(-)
> 
> diff --git a/lib/mcast-group-index.h b/lib/mcast-group-index.h
> index cb49ad7..ba995ba 100644
> --- a/lib/mcast-group-index.h
> +++ b/lib/mcast-group-index.h
> @@ -28,6 +28,8 @@ enum ovn_mcast_tunnel_keys {
>       OVN_MCAST_FLOOD_TUNNEL_KEY = OVN_MIN_MULTICAST,
>       OVN_MCAST_UNKNOWN_TUNNEL_KEY,
>       OVN_MCAST_MROUTER_FLOOD_TUNNEL_KEY,
> +    OVN_MCAST_MROUTER_STATIC_TUNNEL_KEY,
> +    OVN_MCAST_STATIC_TUNNEL_KEY,
>       OVN_MIN_IP_MULTICAST,
>       OVN_MAX_IP_MULTICAST = OVN_MAX_MULTICAST,
>   };
> diff --git a/northd/ovn-northd.8.xml b/northd/ovn-northd.8.xml
> index 0f4f1c1..429ed7e 100644
> --- a/northd/ovn-northd.8.xml
> +++ b/northd/ovn-northd.8.xml
> @@ -954,7 +954,11 @@ output;
>         <li>
>           A priority-100 flow that punts all IGMP packets to
>           <code>ovn-controller</code> if IGMP snooping is enabled on the
> -        logical switch.
> +        logical switch. The flow also forwards the IGMP packets to the
> +        <code>MC_MROUTER_STATIC</code> multicast group, which
> +        <code>ovn-northd</code> populates with all the logical ports that
> +        have <ref column="options" table="Logical_Switch_Port"/>
> +        <code>:mcast_flood_reports='true'</code>.
>         </li>
>   
>         <li>
> @@ -976,10 +980,15 @@ output;
>   
>         <li>
>           A priority-80 flow that forwards all unregistered IP multicast traffic
> -        to the <code>MC_MROUTER_FLOOD</code> multicast group, if any.
> -        Otherwise the flow drops all unregistered IP multicast packets.  This
> -        flow is added only if <ref column="other_config"
> -        table="Logical_Switch"/>:mcast_flood_unregistered='false'.
> +        to the <code>MC_STATIC</code> multicast group, which
> +        <code>ovn-northd</code> populates with all the logical ports that
> +        have <ref column="options" table="Logical_Switch_Port"/>
> +        <code>:mcast_flood='true'</code>. The flow also forwards
> +        unregistered IP multicast traffic to the <code>MC_MROUTER_FLOOD</code>
> +        multicast group, which <code>ovn-northd</code> populates with all the
> +        logical ports connected to logical routers that have
> +        <ref column="options" table="Logical_Router"/>
> +        <code>:mcast_relay='true'</code>.
>         </li>
>   
>         <li>
> @@ -2027,6 +2036,17 @@ output;
>   
>         <li>
>           <p>
> +          Priority-450 flow that matches unregistered IP multicast traffic
> +          and sets <code>outport</code> to the <code>MC_STATIC</code>
> +          multicast group, which <code>ovn-northd</code> populates with the
> +          logical ports that have
> +          <ref column="options" table="Logical_Router_Port"/>
> +          <code>:mcast_flood='true'</code>.
> +        </p>
> +      </li>
> +
> +      <li>
> +        <p>
>             For distributed logical routers where one of the logical router
>             ports specifies a <code>redirect-chassis</code>, a priority-400
>             logical flow for each ip source/destination couple that matches the
> diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c
> index f393ceb..538daa1 100644
> --- a/northd/ovn-northd.c
> +++ b/northd/ovn-northd.c
> @@ -448,7 +448,12 @@ struct mcast_switch_info {
>                                    * should be flooded to the mrouter. Only
>                                    * applicable if flood_unregistered == false.
>                                    */
> -
> +    bool flood_reports;         /* True if the switch has at least one port
> +                                 * configured to flood reports.
> +                                 */
> +    bool flood_static;          /* True if the switch has at least one port
> +                                 * configured to flood traffic.
> +                                 */
>       int64_t table_size;         /* Max number of IP multicast groups. */
>       int64_t idle_timeout;       /* Timeout after which an idle group is
>                                    * flushed.
> @@ -466,7 +471,10 @@ struct mcast_switch_info {
>   };
>   
>   struct mcast_router_info {
> -    bool relay; /* True if the router should relay IP multicast. */
> +    bool relay;        /* True if the router should relay IP multicast. */
> +    bool flood_static; /* True if the router has at least one port configured
> +                        * to flood traffic.
> +                        */
>   };
>   
>   struct mcast_info {
> @@ -481,6 +489,34 @@ struct mcast_info {
>       };
>   };
>   
> +struct mcast_port_info {
> +    bool flood;         /* True if the port should flood IP multicast traffic
> +                         * regardless if it's registered or not. */
> +    bool flood_reports; /* True if the port should flood IP multicast reports
> +                         * (e.g., IGMP join/leave). */
> +};
> +
> +static void
> +init_mcast_port_info(struct mcast_port_info *mcast_info,
> +                     const struct nbrec_logical_switch_port *nbsp,
> +                     const struct nbrec_logical_router_port *nbrp)
> +{
> +    if (nbsp) {
> +        mcast_info->flood =
> +            smap_get_bool(&nbsp->options, "mcast_flood", false);
> +        mcast_info->flood_reports =
> +            smap_get_bool(&nbsp->options, "mcast_flood_reports",
> +                          false);
> +    } else if (nbrp) {
> +        /* We don't process multicast reports in any special way on logical
> +         * routers so just treat them as regular multicast traffic.
> +         */
> +        mcast_info->flood =
> +            smap_get_bool(&nbrp->options, "mcast_flood", false);
> +        mcast_info->flood_reports = mcast_info->flood;
> +    }
> +}
> +
>   static uint32_t
>   ovn_mcast_group_allocate_key(struct mcast_info *mcast_info)
>   {
> @@ -1022,7 +1058,7 @@ build_datapaths(struct northd_context *ctx, struct hmap *datapaths,
>           ovn_datapath_destroy(datapaths, od);
>       }
>   }
> -
> +
>   struct ovn_port {
>       struct hmap_node key_node;  /* Index on 'key'. */
>       char *key;                  /* nbs->name, nbr->name, sb->logical_port. */
> @@ -1044,6 +1080,9 @@ struct ovn_port {
>   
>       struct lport_addresses lrp_networks;
>   
> +    /* Logical port multicast data. */
> +    struct mcast_port_info mcast_info;
> +
>       bool derived; /* Indicates whether this is an additional port
>                      * derived from nbsp or nbrp. */
>   
> @@ -1060,6 +1099,23 @@ struct ovn_port {
>       struct ovs_list list;       /* In list of similar records. */
>   };
>   
> +static void
> +ovn_port_set_sb(struct ovn_port *op,
> +                const struct sbrec_port_binding *sb)
> +{
> +    op->sb = sb;
> +}
> +
> +static void
> +ovn_port_set_nb(struct ovn_port *op,
> +                const struct nbrec_logical_switch_port *nbsp,
> +                const struct nbrec_logical_router_port *nbrp)
> +{
> +    op->nbsp = nbsp;
> +    op->nbrp = nbrp;
> +    init_mcast_port_info(&op->mcast_info, op->nbsp, op->nbrp);
> +}
> +
>   static struct ovn_port *
>   ovn_port_create(struct hmap *ports, const char *key,
>                   const struct nbrec_logical_switch_port *nbsp,
> @@ -1073,9 +1129,8 @@ ovn_port_create(struct hmap *ports, const char *key,
>       op->json_key = ds_steal_cstr(&json_key);
>   
>       op->key = xstrdup(key);
> -    op->sb = sb;
> -    op->nbsp = nbsp;
> -    op->nbrp = nbrp;
> +    ovn_port_set_sb(op, sb);
> +    ovn_port_set_nb(op, nbsp, nbrp);
>       op->derived = false;
>       hmap_insert(ports, &op->key_node, hash_string(op->key, 0));
>       return op;
> @@ -1878,7 +1933,7 @@ join_logical_ports(struct northd_context *ctx,
>                                        nbsp->name);
>                           continue;
>                       }
> -                    op->nbsp = nbsp;
> +                    ovn_port_set_nb(op, nbsp, NULL);
>                       ovs_list_remove(&op->list);
>   
>                       uint32_t queue_id = smap_get_int(&op->sb->options,
> @@ -1969,7 +2024,7 @@ join_logical_ports(struct northd_context *ctx,
>                                        nbrp->name);
>                           continue;
>                       }
> -                    op->nbrp = nbrp;
> +                    ovn_port_set_nb(op, NULL, nbrp);
>                       ovs_list_remove(&op->list);
>                       ovs_list_push_back(both, &op->list);
>   
> @@ -2014,7 +2069,7 @@ join_logical_ports(struct northd_context *ctx,
>                       struct ovn_port *crp = ovn_port_find(ports, redirect_name);
>                       if (crp) {
>                           crp->derived = true;
> -                        crp->nbrp = nbrp;
> +                        ovn_port_set_nb(crp, NULL, nbrp);
>                           ovs_list_remove(&crp->list);
>                           ovs_list_push_back(both, &crp->list);
>                       } else {
> @@ -2890,7 +2945,7 @@ build_ports(struct northd_context *ctx,
>               continue;
>           }
>   
> -        op->sb = sbrec_port_binding_insert(ctx->ovnsb_txn);
> +        ovn_port_set_sb(op, sbrec_port_binding_insert(ctx->ovnsb_txn));
>           ovn_port_update_sbrec(ctx, sbrec_chassis_by_name, op,
>                                 &chassis_qdisc_queues,
>                                 &active_ha_chassis_grps);
> @@ -2932,6 +2987,14 @@ static const struct multicast_group mc_flood =
>   static const struct multicast_group mc_mrouter_flood =
>       { MC_MROUTER_FLOOD, OVN_MCAST_MROUTER_FLOOD_TUNNEL_KEY };
>   
> +#define MC_MROUTER_STATIC "_MC_mrouter_static"
> +static const struct multicast_group mc_mrouter_static =
> +    { MC_MROUTER_STATIC, OVN_MCAST_MROUTER_STATIC_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 };
> @@ -3145,7 +3208,23 @@ ovn_igmp_group_get_ports(const struct sbrec_igmp_group *sb_igmp_group,
>   
>        *n_ports = 0;
>        for (size_t i = 0; i < sb_igmp_group->n_ports; i++) {
> -        ports[(*n_ports)] =
> +        struct ovn_port *port =
> +            ovn_port_find(ovn_ports, sb_igmp_group->ports[i]->logical_port);
> +
> +        /* 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;
> +        }
> +
> +        ports[(*n_ports)] = port;
>               ovn_port_find(ovn_ports, sb_igmp_group->ports[i]->logical_port);
>           if (ports[(*n_ports)]) {
>               (*n_ports)++;
> @@ -5440,9 +5519,18 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
>           struct mcast_switch_info *mcast_sw_info = &od->mcast_info.sw;
>   
>           if (mcast_sw_info->enabled) {
> +            ds_clear(&actions);
> +            if (mcast_sw_info->flood_reports) {
> +                ds_put_cstr(&actions,
> +                            "clone { "
> +                                "outport = \""MC_MROUTER_STATIC"\"; "
> +                                "output; "
> +                            "};");
> +            }
> +            ds_put_cstr(&actions, "igmp;");
>               /* Punt IGMP traffic to controller. */
>               ovn_lflow_add(lflows, od, S_SWITCH_IN_L2_LKUP, 100,
> -                          "ip4 && ip.proto == 2", "igmp;");
> +                          "ip4 && ip.proto == 2", ds_cstr(&actions));
>   
>               /* Flood all IP multicast traffic destined to 224.0.0.X to all
>                * ports - RFC 4541, section 2.1.2, item 2.
> @@ -5451,17 +5539,30 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
>                             "ip4 && ip4.dst == 224.0.0.0/24",
>                             "outport = \""MC_FLOOD"\"; output;");
>   
> -            /* Drop unregistered IP multicast if not allowed. */
> +            /* Forward uregistered IP multicast to routers with relay enabled
> +             * and to any ports configured to flood IP multicast traffic.
> +             * If configured to flood unregistered traffic this will be
> +             * handled by the L2 multicast flow.
> +             */
>               if (!mcast_sw_info->flood_unregistered) {
> -                /* Forward unregistered IP multicast to mrouter (if any). */
> +                ds_clear(&actions);
> +
>                   if (mcast_sw_info->flood_relay) {
> -                    ovn_lflow_add(lflows, od, S_SWITCH_IN_L2_LKUP, 80,
> -                                  "ip4 && ip4.mcast",
> -                                  "outport = \""MC_MROUTER_FLOOD"\"; output;");
> +                    ds_put_cstr(&actions,
> +                                "clone { "
> +                                    "outport = \""MC_MROUTER_FLOOD"\"; "
> +                                    "output; "
> +                                "}; ");
> +                }
> +
> +                if (mcast_sw_info->flood_static) {
> +                    ds_put_cstr(&actions, "outport =\""MC_STATIC"\"; output;");
>                   } else {
> -                    ovn_lflow_add(lflows, od, S_SWITCH_IN_L2_LKUP, 80,
> -                                  "ip4 && ip4.mcast", "drop;");
> +                    ds_put_cstr(&actions, "drop;");
>                   }
> +
> +                ovn_lflow_add(lflows, od, S_SWITCH_IN_L2_LKUP, 80,
> +                              "ip4 && ip4.mcast", ds_cstr(&actions));
>               }
>           }
>   
> @@ -5491,11 +5592,20 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
>   
>           ds_put_format(&match, "eth.mcast && ip4 && ip4.dst == %s ",
>                         igmp_group->mcgroup.name);
> +
>           /* Also flood traffic to all multicast routers with relay enabled. */
>           if (mcast_sw_info->flood_relay) {
>               ds_put_cstr(&actions,
>                           "clone { "
> -                            "outport = \""MC_MROUTER_FLOOD "\"; output; "
> +                            "outport = \""MC_MROUTER_FLOOD "\"; "
> +                            "output; "
> +                        "};");
> +        }
> +        if (mcast_sw_info->flood_static) {
> +            ds_put_cstr(&actions,
> +                        "clone { "
> +                            "outport =\""MC_STATIC"\"; "
> +                            "output; "
>                           "};");
>           }
>           ds_put_format(&actions, "outport = \"%s\"; output; ",
> @@ -7711,6 +7821,7 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
>           if (!od->nbr || !od->mcast_info.rtr.relay) {
>               continue;
>           }
> +
>           struct ovn_igmp_group *igmp_group;
>   
>           LIST_FOR_EACH (igmp_group, list_node, &od->mcast_info.groups) {
> @@ -7718,11 +7829,35 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
>               ds_clear(&actions);
>               ds_put_format(&match, "ip4 && ip4.dst == %s ",
>                             igmp_group->mcgroup.name);
> +            if (od->mcast_info.rtr.flood_static) {
> +                ds_put_cstr(&actions,
> +                            "clone { "
> +                                "outport = \""MC_STATIC"\"; "
> +                                "ip.ttl--; "
> +                                "next; "
> +                            "};");
> +            }
>               ds_put_format(&actions, "outport = \"%s\"; ip.ttl--; next;",
>                             igmp_group->mcgroup.name);
>               ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_ROUTING, 500,
>                             ds_cstr(&match), ds_cstr(&actions));
>           }
> +
> +        /* If needed, flood unregistered multicast on statically configured
> +         * ports.
> +         */
> +        if (od->mcast_info.rtr.flood_static) {
> +            ds_clear(&match);
> +            ds_clear(&actions);
> +            ds_put_format(&match, "ip4.mcast");
> +            ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_ROUTING, 450,
> +                          "ip4.mcast",
> +                          "clone { "
> +                                "outport = \""MC_STATIC"\"; "
> +                                "ip.ttl--; "
> +                                "next; "
> +                          "};");
> +        }
>       }
>   
>       /* Logical router ingress table 8: Policy.
> @@ -8893,11 +9028,15 @@ build_mcast_groups(struct northd_context *ctx,
>       hmap_init(igmp_groups);
>   
>       HMAP_FOR_EACH (op, key_node, ports) {
> -        if (!op->nbsp) {
> -            continue;
> -        }
> -
> -        if (lsp_is_enabled(op->nbsp)) {
> +        if (op->nbrp && 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;
> +            }
> +        } else if (op->nbsp && lsp_is_enabled(op->nbsp)) {
>               ovn_multicast_add(mcast_groups, &mc_flood, op);
>   
>               /* If this port is connected to a multicast router then add it
> @@ -8907,6 +9046,22 @@ build_mcast_groups(struct northd_context *ctx,
>                       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_STATIC group.
> +             */
> +            if (op->mcast_info.flood_reports) {
> +                ovn_multicast_add(mcast_groups, &mc_mrouter_static, 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;
> +            }
>           }
>       }
>   
> @@ -8967,8 +9122,13 @@ build_mcast_groups(struct northd_context *ctx,
>           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->od->mcast_info.rtr.relay ||
> +                    router_port->mcast_info.flood) {
>                   continue;
>               }
>   
> diff --git a/ovn-nb.xml b/ovn-nb.xml
> index b41b579..1504f8f 100644
> --- a/ovn-nb.xml
> +++ b/ovn-nb.xml
> @@ -671,6 +671,26 @@
>           </column>
>         </group>
>   
> +      <group title="IP Multicast Snooping Options">
> +        <p>
> +          These options apply when the port is part of a logical switch
> +          which has <ref table="Logical_Switch" column="other_config"/>
> +          :mcast_snoop set to <code>true</code>.
> +        </p>
> +
> +        <column name="options" key="mcast_flood"
> +                type='{"type": "boolean"}'>
> +          If set to <code>true</code>, multicast packets (except reports) are
> +          unconditionally forwarded to the specific port.
> +        </column>
> +
> +        <column name="options" key="mcast_flood_reports"
> +                type='{"type": "boolean"}'>
> +          If set to <code>true</code>, multicast reports are unconditionally
> +          forwarded to the specific port.
> +        </column>
> +      </group>
> +
>       </group>
>   
>       <group title="Containers">
> @@ -2005,6 +2025,19 @@
>   
>         </column>
>   
> +      <column name="options" key="mcast_flood"
> +              type='{"type": "boolean"}'>
> +        <p>
> +          If set to <code>true</code>, multicast traffic (including reports)
> +          are unconditionally forwarded to the specific port.
> +        </p>
> +
> +        <p>
> +          This option applies when the port is part of a logical router which
> +          has <ref table="Logical_Router" column="options"/>:mcast_relay set
> +          to <code>true</code>.
> +        </p>
> +      </column>
>       </group>
>   
>       <group title="Attachment">
> diff --git a/tests/ovn.at b/tests/ovn.at
> index 04898dd..f03e701 100644
> --- a/tests/ovn.at
> +++ b/tests/ovn.at
> @@ -15122,7 +15122,6 @@ OVN_CHECK_PACKETS([hv2/vif3-tx.pcap], [expected_empty])
>   
>   # Flush IGMP groups.
>   ovn-sbctl ip-multicast-flush sw1
> -ovn-nbctl --wait=hv -t 3 sync
>   OVS_WAIT_UNTIL([
>       total_entries=`ovn-sbctl find IGMP_Group | grep "239.0.1.68" | wc -l`
>       test "${total_entries}" = "0"
> @@ -15174,12 +15173,12 @@ send_ip_multicast_pkt hv2-vif4 hv2 \
>   # Sleep a bit to make sure no traffic is received and then check.
>   sleep 1
>   OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected_empty])
> -OVN_CHECK_PACKETS([hv2/vif3-tx.pcap], [expected_empty])
> -OVN_CHECK_PACKETS([hv1/vif4-tx.pcap], [expected_empty])
>   OVN_CHECK_PACKETS([hv1/vif2-tx.pcap], [expected_empty])
>   OVN_CHECK_PACKETS([hv1/vif3-tx.pcap], [expected_empty])
> +OVN_CHECK_PACKETS([hv1/vif4-tx.pcap], [expected_empty])
>   OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected_empty])
>   OVN_CHECK_PACKETS([hv2/vif2-tx.pcap], [expected_empty])
> +OVN_CHECK_PACKETS([hv2/vif3-tx.pcap], [expected_empty])
>   OVN_CHECK_PACKETS([hv2/vif4-tx.pcap], [expected_empty])
>   
>   # Enable IGMP relay on rtr
> @@ -15250,6 +15249,82 @@ OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected_empty])
>   OVN_CHECK_PACKETS([hv2/vif2-tx.pcap], [expected_empty])
>   OVN_CHECK_PACKETS([hv2/vif4-tx.pcap], [expected_empty])
>   
> +# Flush IGMP groups.
> +ovn-sbctl ip-multicast-flush sw1
> +ovn-sbctl ip-multicast-flush sw2
> +ovn-sbctl ip-multicast-flush sw3
> +OVS_WAIT_UNTIL([
> +    total_entries=`ovn-sbctl find IGMP_Group | grep "239.0.1.68" | wc -l`
> +    test "${total_entries}" = "0"
> +])
> +
> +as hv1 reset_pcap_file hv1-vif1 hv1/vif1
> +as hv1 reset_pcap_file hv1-vif2 hv1/vif2
> +as hv1 reset_pcap_file hv1-vif3 hv1/vif3
> +as hv1 reset_pcap_file hv1-vif4 hv1/vif4
> +as hv2 reset_pcap_file hv2-vif1 hv2/vif1
> +as hv2 reset_pcap_file hv2-vif2 hv2/vif2
> +as hv2 reset_pcap_file hv2-vif3 hv2/vif3
> +as hv2 reset_pcap_file hv2-vif4 hv2/vif4
> +
> +truncate -s 0 expected_empty
> +truncate -s 0 expected_switched
> +truncate -s 0 expected_routed
> +truncate -s 0 expected_reports
> +
> +# Enable mcast_flood on sw1-p11
> +ovn-nbctl set Logical_Switch_Port sw1-p11 options:mcast_flood='true'
> +
> +# Enable mcast_flood_reports on sw1-p21
> +ovn-nbctl set Logical_Switch_Port sw1-p21 options:mcast_flood_reports='true'
> +# Enable mcast_flood on rtr-sw2
> +ovn-nbctl set Logical_Router_Port rtr-sw2 options:mcast_flood='true'
> +# Enable mcast_flood on sw2-p1
> +ovn-nbctl set Logical_Switch_Port sw2-p1 options:mcast_flood='true'
> +
> +ovn-nbctl --wait=hv sync
> +
> +# Inject IGMP Join for 239.0.1.68 on sw1-p12.
> +send_igmp_v3_report hv1-vif2 hv1 \
> +    000000000001 $(ip_to_hex 10 0 0 1) f9f8 \
> +    $(ip_to_hex 239 0 1 68) 04 e9b9 \
> +    expected_reports
> +
> +# Check that the IGMP Group is learned.
> +OVS_WAIT_UNTIL([
> +    total_entries=`ovn-sbctl find IGMP_Group | grep "239.0.1.68" | wc -l`
> +    test "${total_entries}" = "1"
> +])
> +
> +# Send traffic from sw1-p21
> +send_ip_multicast_pkt hv2-vif1 hv2 \
> +    000000000001 01005e000144 \
> +    $(ip_to_hex 10 0 0 42) $(ip_to_hex 239 0 1 68) 1e 20 ca70 11 \
> +    e518e518000a3b3a0000
> +store_ip_multicast_pkt \
> +    000000000001 01005e000144 \
> +    $(ip_to_hex 10 0 0 42) $(ip_to_hex 239 0 1 68) 1e 20 ca70 11 \
> +    e518e518000a3b3a0000 expected_switched
> +store_ip_multicast_pkt \
> +    000000000200 01005e000144 \
> +    $(ip_to_hex 10 0 0 42) $(ip_to_hex 239 0 1 68) 1e 1f cb70 11 \
> +    e518e518000a3b3a0000 expected_routed
> +
> +# Sleep a bit to make sure no duplicate traffic is received
> +sleep 1
> +
> +# Check that traffic is switched to sw1-p11 and sw1-p12
> +# Check that IGMP join is flooded on sw1-p21
> +# Check that traffic is routed by rtr to rtr-sw2 and then switched to sw2-p1
> +OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected_switched])
> +OVN_CHECK_PACKETS([hv1/vif2-tx.pcap], [expected_switched])
> +OVN_CHECK_PACKETS([hv1/vif3-tx.pcap], [expected_routed])
> +OVN_CHECK_PACKETS([hv1/vif4-tx.pcap], [expected_empty])
> +OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected_reports])
> +OVN_CHECK_PACKETS([hv2/vif2-tx.pcap], [expected_empty])
> +OVN_CHECK_PACKETS([hv2/vif3-tx.pcap], [expected_empty])
> +OVN_CHECK_PACKETS([hv2/vif4-tx.pcap], [expected_empty])
> +
>   OVN_CLEANUP([hv1], [hv2])
>   AT_CLEANUP
>   
>
Dumitru Ceara Oct. 9, 2019, 4:03 p.m. UTC | #3
On Wed, Oct 9, 2019 at 5:01 PM Mark Michelson <mmichels@redhat.com> wrote:
>
> I've merged this to master.

Thanks!

Patch
diff mbox series

diff --git a/lib/mcast-group-index.h b/lib/mcast-group-index.h
index cb49ad7..ba995ba 100644
--- a/lib/mcast-group-index.h
+++ b/lib/mcast-group-index.h
@@ -28,6 +28,8 @@  enum ovn_mcast_tunnel_keys {
     OVN_MCAST_FLOOD_TUNNEL_KEY = OVN_MIN_MULTICAST,
     OVN_MCAST_UNKNOWN_TUNNEL_KEY,
     OVN_MCAST_MROUTER_FLOOD_TUNNEL_KEY,
+    OVN_MCAST_MROUTER_STATIC_TUNNEL_KEY,
+    OVN_MCAST_STATIC_TUNNEL_KEY,
     OVN_MIN_IP_MULTICAST,
     OVN_MAX_IP_MULTICAST = OVN_MAX_MULTICAST,
 };
diff --git a/northd/ovn-northd.8.xml b/northd/ovn-northd.8.xml
index 0f4f1c1..429ed7e 100644
--- a/northd/ovn-northd.8.xml
+++ b/northd/ovn-northd.8.xml
@@ -954,7 +954,11 @@  output;
       <li>
         A priority-100 flow that punts all IGMP packets to
         <code>ovn-controller</code> if IGMP snooping is enabled on the
-        logical switch.
+        logical switch. The flow also forwards the IGMP packets to the
+        <code>MC_MROUTER_STATIC</code> multicast group, which
+        <code>ovn-northd</code> populates with all the logical ports that
+        have <ref column="options" table="Logical_Switch_Port"/>
+        <code>:mcast_flood_reports='true'</code>.
       </li>
 
       <li>
@@ -976,10 +980,15 @@  output;
 
       <li>
         A priority-80 flow that forwards all unregistered IP multicast traffic
-        to the <code>MC_MROUTER_FLOOD</code> multicast group, if any.
-        Otherwise the flow drops all unregistered IP multicast packets.  This
-        flow is added only if <ref column="other_config"
-        table="Logical_Switch"/>:mcast_flood_unregistered='false'.
+        to the <code>MC_STATIC</code> multicast group, which
+        <code>ovn-northd</code> populates with all the logical ports that
+        have <ref column="options" table="Logical_Switch_Port"/>
+        <code>:mcast_flood='true'</code>. The flow also forwards
+        unregistered IP multicast traffic to the <code>MC_MROUTER_FLOOD</code>
+        multicast group, which <code>ovn-northd</code> populates with all the
+        logical ports connected to logical routers that have
+        <ref column="options" table="Logical_Router"/>
+        <code>:mcast_relay='true'</code>.
       </li>
 
       <li>
@@ -2027,6 +2036,17 @@  output;
 
       <li>
         <p>
+          Priority-450 flow that matches unregistered IP multicast traffic
+          and sets <code>outport</code> to the <code>MC_STATIC</code>
+          multicast group, which <code>ovn-northd</code> populates with the
+          logical ports that have
+          <ref column="options" table="Logical_Router_Port"/>
+          <code>:mcast_flood='true'</code>.
+        </p>
+      </li>
+
+      <li>
+        <p>
           For distributed logical routers where one of the logical router
           ports specifies a <code>redirect-chassis</code>, a priority-400
           logical flow for each ip source/destination couple that matches the
diff --git a/northd/ovn-northd.c b/northd/ovn-northd.c
index f393ceb..538daa1 100644
--- a/northd/ovn-northd.c
+++ b/northd/ovn-northd.c
@@ -448,7 +448,12 @@  struct mcast_switch_info {
                                  * should be flooded to the mrouter. Only
                                  * applicable if flood_unregistered == false.
                                  */
-
+    bool flood_reports;         /* True if the switch has at least one port
+                                 * configured to flood reports.
+                                 */
+    bool flood_static;          /* True if the switch has at least one port
+                                 * configured to flood traffic.
+                                 */
     int64_t table_size;         /* Max number of IP multicast groups. */
     int64_t idle_timeout;       /* Timeout after which an idle group is
                                  * flushed.
@@ -466,7 +471,10 @@  struct mcast_switch_info {
 };
 
 struct mcast_router_info {
-    bool relay; /* True if the router should relay IP multicast. */
+    bool relay;        /* True if the router should relay IP multicast. */
+    bool flood_static; /* True if the router has at least one port configured
+                        * to flood traffic.
+                        */
 };
 
 struct mcast_info {
@@ -481,6 +489,34 @@  struct mcast_info {
     };
 };
 
+struct mcast_port_info {
+    bool flood;         /* True if the port should flood IP multicast traffic
+                         * regardless if it's registered or not. */
+    bool flood_reports; /* True if the port should flood IP multicast reports
+                         * (e.g., IGMP join/leave). */
+};
+
+static void
+init_mcast_port_info(struct mcast_port_info *mcast_info,
+                     const struct nbrec_logical_switch_port *nbsp,
+                     const struct nbrec_logical_router_port *nbrp)
+{
+    if (nbsp) {
+        mcast_info->flood =
+            smap_get_bool(&nbsp->options, "mcast_flood", false);
+        mcast_info->flood_reports =
+            smap_get_bool(&nbsp->options, "mcast_flood_reports",
+                          false);
+    } else if (nbrp) {
+        /* We don't process multicast reports in any special way on logical
+         * routers so just treat them as regular multicast traffic.
+         */
+        mcast_info->flood =
+            smap_get_bool(&nbrp->options, "mcast_flood", false);
+        mcast_info->flood_reports = mcast_info->flood;
+    }
+}
+
 static uint32_t
 ovn_mcast_group_allocate_key(struct mcast_info *mcast_info)
 {
@@ -1022,7 +1058,7 @@  build_datapaths(struct northd_context *ctx, struct hmap *datapaths,
         ovn_datapath_destroy(datapaths, od);
     }
 }
-
+
 struct ovn_port {
     struct hmap_node key_node;  /* Index on 'key'. */
     char *key;                  /* nbs->name, nbr->name, sb->logical_port. */
@@ -1044,6 +1080,9 @@  struct ovn_port {
 
     struct lport_addresses lrp_networks;
 
+    /* Logical port multicast data. */
+    struct mcast_port_info mcast_info;
+
     bool derived; /* Indicates whether this is an additional port
                    * derived from nbsp or nbrp. */
 
@@ -1060,6 +1099,23 @@  struct ovn_port {
     struct ovs_list list;       /* In list of similar records. */
 };
 
+static void
+ovn_port_set_sb(struct ovn_port *op,
+                const struct sbrec_port_binding *sb)
+{
+    op->sb = sb;
+}
+
+static void
+ovn_port_set_nb(struct ovn_port *op,
+                const struct nbrec_logical_switch_port *nbsp,
+                const struct nbrec_logical_router_port *nbrp)
+{
+    op->nbsp = nbsp;
+    op->nbrp = nbrp;
+    init_mcast_port_info(&op->mcast_info, op->nbsp, op->nbrp);
+}
+
 static struct ovn_port *
 ovn_port_create(struct hmap *ports, const char *key,
                 const struct nbrec_logical_switch_port *nbsp,
@@ -1073,9 +1129,8 @@  ovn_port_create(struct hmap *ports, const char *key,
     op->json_key = ds_steal_cstr(&json_key);
 
     op->key = xstrdup(key);
-    op->sb = sb;
-    op->nbsp = nbsp;
-    op->nbrp = nbrp;
+    ovn_port_set_sb(op, sb);
+    ovn_port_set_nb(op, nbsp, nbrp);
     op->derived = false;
     hmap_insert(ports, &op->key_node, hash_string(op->key, 0));
     return op;
@@ -1878,7 +1933,7 @@  join_logical_ports(struct northd_context *ctx,
                                      nbsp->name);
                         continue;
                     }
-                    op->nbsp = nbsp;
+                    ovn_port_set_nb(op, nbsp, NULL);
                     ovs_list_remove(&op->list);
 
                     uint32_t queue_id = smap_get_int(&op->sb->options,
@@ -1969,7 +2024,7 @@  join_logical_ports(struct northd_context *ctx,
                                      nbrp->name);
                         continue;
                     }
-                    op->nbrp = nbrp;
+                    ovn_port_set_nb(op, NULL, nbrp);
                     ovs_list_remove(&op->list);
                     ovs_list_push_back(both, &op->list);
 
@@ -2014,7 +2069,7 @@  join_logical_ports(struct northd_context *ctx,
                     struct ovn_port *crp = ovn_port_find(ports, redirect_name);
                     if (crp) {
                         crp->derived = true;
-                        crp->nbrp = nbrp;
+                        ovn_port_set_nb(crp, NULL, nbrp);
                         ovs_list_remove(&crp->list);
                         ovs_list_push_back(both, &crp->list);
                     } else {
@@ -2890,7 +2945,7 @@  build_ports(struct northd_context *ctx,
             continue;
         }
 
-        op->sb = sbrec_port_binding_insert(ctx->ovnsb_txn);
+        ovn_port_set_sb(op, sbrec_port_binding_insert(ctx->ovnsb_txn));
         ovn_port_update_sbrec(ctx, sbrec_chassis_by_name, op,
                               &chassis_qdisc_queues,
                               &active_ha_chassis_grps);
@@ -2932,6 +2987,14 @@  static const struct multicast_group mc_flood =
 static const struct multicast_group mc_mrouter_flood =
     { MC_MROUTER_FLOOD, OVN_MCAST_MROUTER_FLOOD_TUNNEL_KEY };
 
+#define MC_MROUTER_STATIC "_MC_mrouter_static"
+static const struct multicast_group mc_mrouter_static =
+    { MC_MROUTER_STATIC, OVN_MCAST_MROUTER_STATIC_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 };
@@ -3145,7 +3208,23 @@  ovn_igmp_group_get_ports(const struct sbrec_igmp_group *sb_igmp_group,
 
      *n_ports = 0;
      for (size_t i = 0; i < sb_igmp_group->n_ports; i++) {
-        ports[(*n_ports)] =
+        struct ovn_port *port =
+            ovn_port_find(ovn_ports, sb_igmp_group->ports[i]->logical_port);
+
+        /* 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;
+        }
+
+        ports[(*n_ports)] = port;
             ovn_port_find(ovn_ports, sb_igmp_group->ports[i]->logical_port);
         if (ports[(*n_ports)]) {
             (*n_ports)++;
@@ -5440,9 +5519,18 @@  build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
         struct mcast_switch_info *mcast_sw_info = &od->mcast_info.sw;
 
         if (mcast_sw_info->enabled) {
+            ds_clear(&actions);
+            if (mcast_sw_info->flood_reports) {
+                ds_put_cstr(&actions,
+                            "clone { "
+                                "outport = \""MC_MROUTER_STATIC"\"; "
+                                "output; "
+                            "};");
+            }
+            ds_put_cstr(&actions, "igmp;");
             /* Punt IGMP traffic to controller. */
             ovn_lflow_add(lflows, od, S_SWITCH_IN_L2_LKUP, 100,
-                          "ip4 && ip.proto == 2", "igmp;");
+                          "ip4 && ip.proto == 2", ds_cstr(&actions));
 
             /* Flood all IP multicast traffic destined to 224.0.0.X to all
              * ports - RFC 4541, section 2.1.2, item 2.
@@ -5451,17 +5539,30 @@  build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
                           "ip4 && ip4.dst == 224.0.0.0/24",
                           "outport = \""MC_FLOOD"\"; output;");
 
-            /* Drop unregistered IP multicast if not allowed. */
+            /* Forward uregistered IP multicast to routers with relay enabled
+             * and to any ports configured to flood IP multicast traffic.
+             * If configured to flood unregistered traffic this will be
+             * handled by the L2 multicast flow.
+             */
             if (!mcast_sw_info->flood_unregistered) {
-                /* Forward unregistered IP multicast to mrouter (if any). */
+                ds_clear(&actions);
+
                 if (mcast_sw_info->flood_relay) {
-                    ovn_lflow_add(lflows, od, S_SWITCH_IN_L2_LKUP, 80,
-                                  "ip4 && ip4.mcast",
-                                  "outport = \""MC_MROUTER_FLOOD"\"; output;");
+                    ds_put_cstr(&actions,
+                                "clone { "
+                                    "outport = \""MC_MROUTER_FLOOD"\"; "
+                                    "output; "
+                                "}; ");
+                }
+
+                if (mcast_sw_info->flood_static) {
+                    ds_put_cstr(&actions, "outport =\""MC_STATIC"\"; output;");
                 } else {
-                    ovn_lflow_add(lflows, od, S_SWITCH_IN_L2_LKUP, 80,
-                                  "ip4 && ip4.mcast", "drop;");
+                    ds_put_cstr(&actions, "drop;");
                 }
+
+                ovn_lflow_add(lflows, od, S_SWITCH_IN_L2_LKUP, 80,
+                              "ip4 && ip4.mcast", ds_cstr(&actions));
             }
         }
 
@@ -5491,11 +5592,20 @@  build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
 
         ds_put_format(&match, "eth.mcast && ip4 && ip4.dst == %s ",
                       igmp_group->mcgroup.name);
+
         /* Also flood traffic to all multicast routers with relay enabled. */
         if (mcast_sw_info->flood_relay) {
             ds_put_cstr(&actions,
                         "clone { "
-                            "outport = \""MC_MROUTER_FLOOD "\"; output; "
+                            "outport = \""MC_MROUTER_FLOOD "\"; "
+                            "output; "
+                        "};");
+        }
+        if (mcast_sw_info->flood_static) {
+            ds_put_cstr(&actions,
+                        "clone { "
+                            "outport =\""MC_STATIC"\"; "
+                            "output; "
                         "};");
         }
         ds_put_format(&actions, "outport = \"%s\"; output; ",
@@ -7711,6 +7821,7 @@  build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
         if (!od->nbr || !od->mcast_info.rtr.relay) {
             continue;
         }
+
         struct ovn_igmp_group *igmp_group;
 
         LIST_FOR_EACH (igmp_group, list_node, &od->mcast_info.groups) {
@@ -7718,11 +7829,35 @@  build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
             ds_clear(&actions);
             ds_put_format(&match, "ip4 && ip4.dst == %s ",
                           igmp_group->mcgroup.name);
+            if (od->mcast_info.rtr.flood_static) {
+                ds_put_cstr(&actions,
+                            "clone { "
+                                "outport = \""MC_STATIC"\"; "
+                                "ip.ttl--; "
+                                "next; "
+                            "};");
+            }
             ds_put_format(&actions, "outport = \"%s\"; ip.ttl--; next;",
                           igmp_group->mcgroup.name);
             ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_ROUTING, 500,
                           ds_cstr(&match), ds_cstr(&actions));
         }
+
+        /* If needed, flood unregistered multicast on statically configured
+         * ports.
+         */
+        if (od->mcast_info.rtr.flood_static) {
+            ds_clear(&match);
+            ds_clear(&actions);
+            ds_put_format(&match, "ip4.mcast");
+            ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_ROUTING, 450,
+                          "ip4.mcast",
+                          "clone { "
+                                "outport = \""MC_STATIC"\"; "
+                                "ip.ttl--; "
+                                "next; "
+                          "};");
+        }
     }
 
     /* Logical router ingress table 8: Policy.
@@ -8893,11 +9028,15 @@  build_mcast_groups(struct northd_context *ctx,
     hmap_init(igmp_groups);
 
     HMAP_FOR_EACH (op, key_node, ports) {
-        if (!op->nbsp) {
-            continue;
-        }
-
-        if (lsp_is_enabled(op->nbsp)) {
+        if (op->nbrp && 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;
+            }
+        } else if (op->nbsp && lsp_is_enabled(op->nbsp)) {
             ovn_multicast_add(mcast_groups, &mc_flood, op);
 
             /* If this port is connected to a multicast router then add it
@@ -8907,6 +9046,22 @@  build_mcast_groups(struct northd_context *ctx,
                     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_STATIC group.
+             */
+            if (op->mcast_info.flood_reports) {
+                ovn_multicast_add(mcast_groups, &mc_mrouter_static, 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;
+            }
         }
     }
 
@@ -8967,8 +9122,13 @@  build_mcast_groups(struct northd_context *ctx,
         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->od->mcast_info.rtr.relay ||
+                    router_port->mcast_info.flood) {
                 continue;
             }
 
diff --git a/ovn-nb.xml b/ovn-nb.xml
index b41b579..1504f8f 100644
--- a/ovn-nb.xml
+++ b/ovn-nb.xml
@@ -671,6 +671,26 @@ 
         </column>
       </group>
 
+      <group title="IP Multicast Snooping Options">
+        <p>
+          These options apply when the port is part of a logical switch
+          which has <ref table="Logical_Switch" column="other_config"/>
+          :mcast_snoop set to <code>true</code>.
+        </p>
+
+        <column name="options" key="mcast_flood"
+                type='{"type": "boolean"}'>
+          If set to <code>true</code>, multicast packets (except reports) are
+          unconditionally forwarded to the specific port.
+        </column>
+
+        <column name="options" key="mcast_flood_reports"
+                type='{"type": "boolean"}'>
+          If set to <code>true</code>, multicast reports are unconditionally
+          forwarded to the specific port.
+        </column>
+      </group>
+
     </group>
 
     <group title="Containers">
@@ -2005,6 +2025,19 @@ 
 
       </column>
 
+      <column name="options" key="mcast_flood"
+              type='{"type": "boolean"}'>
+        <p>
+          If set to <code>true</code>, multicast traffic (including reports)
+          are unconditionally forwarded to the specific port.
+        </p>
+
+        <p>
+          This option applies when the port is part of a logical router which
+          has <ref table="Logical_Router" column="options"/>:mcast_relay set
+          to <code>true</code>.
+        </p>
+      </column>
     </group>
 
     <group title="Attachment">
diff --git a/tests/ovn.at b/tests/ovn.at
index 04898dd..f03e701 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -15122,7 +15122,6 @@  OVN_CHECK_PACKETS([hv2/vif3-tx.pcap], [expected_empty])
 
 # Flush IGMP groups.
 ovn-sbctl ip-multicast-flush sw1
-ovn-nbctl --wait=hv -t 3 sync
 OVS_WAIT_UNTIL([
     total_entries=`ovn-sbctl find IGMP_Group | grep "239.0.1.68" | wc -l`
     test "${total_entries}" = "0"
@@ -15174,12 +15173,12 @@  send_ip_multicast_pkt hv2-vif4 hv2 \
 # Sleep a bit to make sure no traffic is received and then check.
 sleep 1
 OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected_empty])
-OVN_CHECK_PACKETS([hv2/vif3-tx.pcap], [expected_empty])
-OVN_CHECK_PACKETS([hv1/vif4-tx.pcap], [expected_empty])
 OVN_CHECK_PACKETS([hv1/vif2-tx.pcap], [expected_empty])
 OVN_CHECK_PACKETS([hv1/vif3-tx.pcap], [expected_empty])
+OVN_CHECK_PACKETS([hv1/vif4-tx.pcap], [expected_empty])
 OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected_empty])
 OVN_CHECK_PACKETS([hv2/vif2-tx.pcap], [expected_empty])
+OVN_CHECK_PACKETS([hv2/vif3-tx.pcap], [expected_empty])
 OVN_CHECK_PACKETS([hv2/vif4-tx.pcap], [expected_empty])
 
 # Enable IGMP relay on rtr
@@ -15250,6 +15249,82 @@  OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected_empty])
 OVN_CHECK_PACKETS([hv2/vif2-tx.pcap], [expected_empty])
 OVN_CHECK_PACKETS([hv2/vif4-tx.pcap], [expected_empty])
 
+# Flush IGMP groups.
+ovn-sbctl ip-multicast-flush sw1
+ovn-sbctl ip-multicast-flush sw2
+ovn-sbctl ip-multicast-flush sw3
+OVS_WAIT_UNTIL([
+    total_entries=`ovn-sbctl find IGMP_Group | grep "239.0.1.68" | wc -l`
+    test "${total_entries}" = "0"
+])
+
+as hv1 reset_pcap_file hv1-vif1 hv1/vif1
+as hv1 reset_pcap_file hv1-vif2 hv1/vif2
+as hv1 reset_pcap_file hv1-vif3 hv1/vif3
+as hv1 reset_pcap_file hv1-vif4 hv1/vif4
+as hv2 reset_pcap_file hv2-vif1 hv2/vif1
+as hv2 reset_pcap_file hv2-vif2 hv2/vif2
+as hv2 reset_pcap_file hv2-vif3 hv2/vif3
+as hv2 reset_pcap_file hv2-vif4 hv2/vif4
+
+truncate -s 0 expected_empty
+truncate -s 0 expected_switched
+truncate -s 0 expected_routed
+truncate -s 0 expected_reports
+
+# Enable mcast_flood on sw1-p11
+ovn-nbctl set Logical_Switch_Port sw1-p11 options:mcast_flood='true'
+
+# Enable mcast_flood_reports on sw1-p21
+ovn-nbctl set Logical_Switch_Port sw1-p21 options:mcast_flood_reports='true'
+# Enable mcast_flood on rtr-sw2
+ovn-nbctl set Logical_Router_Port rtr-sw2 options:mcast_flood='true'
+# Enable mcast_flood on sw2-p1
+ovn-nbctl set Logical_Switch_Port sw2-p1 options:mcast_flood='true'
+
+ovn-nbctl --wait=hv sync
+
+# Inject IGMP Join for 239.0.1.68 on sw1-p12.
+send_igmp_v3_report hv1-vif2 hv1 \
+    000000000001 $(ip_to_hex 10 0 0 1) f9f8 \
+    $(ip_to_hex 239 0 1 68) 04 e9b9 \
+    expected_reports
+
+# Check that the IGMP Group is learned.
+OVS_WAIT_UNTIL([
+    total_entries=`ovn-sbctl find IGMP_Group | grep "239.0.1.68" | wc -l`
+    test "${total_entries}" = "1"
+])
+
+# Send traffic from sw1-p21
+send_ip_multicast_pkt hv2-vif1 hv2 \
+    000000000001 01005e000144 \
+    $(ip_to_hex 10 0 0 42) $(ip_to_hex 239 0 1 68) 1e 20 ca70 11 \
+    e518e518000a3b3a0000
+store_ip_multicast_pkt \
+    000000000001 01005e000144 \
+    $(ip_to_hex 10 0 0 42) $(ip_to_hex 239 0 1 68) 1e 20 ca70 11 \
+    e518e518000a3b3a0000 expected_switched
+store_ip_multicast_pkt \
+    000000000200 01005e000144 \
+    $(ip_to_hex 10 0 0 42) $(ip_to_hex 239 0 1 68) 1e 1f cb70 11 \
+    e518e518000a3b3a0000 expected_routed
+
+# Sleep a bit to make sure no duplicate traffic is received
+sleep 1
+
+# Check that traffic is switched to sw1-p11 and sw1-p12
+# Check that IGMP join is flooded on sw1-p21
+# Check that traffic is routed by rtr to rtr-sw2 and then switched to sw2-p1
+OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [expected_switched])
+OVN_CHECK_PACKETS([hv1/vif2-tx.pcap], [expected_switched])
+OVN_CHECK_PACKETS([hv1/vif3-tx.pcap], [expected_routed])
+OVN_CHECK_PACKETS([hv1/vif4-tx.pcap], [expected_empty])
+OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected_reports])
+OVN_CHECK_PACKETS([hv2/vif2-tx.pcap], [expected_empty])
+OVN_CHECK_PACKETS([hv2/vif3-tx.pcap], [expected_empty])
+OVN_CHECK_PACKETS([hv2/vif4-tx.pcap], [expected_empty])
+
 OVN_CLEANUP([hv1], [hv2])
 AT_CLEANUP