diff mbox series

[ovs-dev,v19,3/3] OVN Remote Port Mirroring: controller changes to create ovs mirrors

Message ID 20221213193326.155653-4-abhiramrn@gmail.com
State Accepted
Headers show
Series OVN Remote Port Mirroring | expand

Commit Message

Abhiram RN Dec. 13, 2022, 7:33 p.m. UTC
Mirror creation just creates the mirror. The lsp-attach-mirror
triggers the sequence to create Mirror in OVS DB on compute node.
OVS already supports Port Mirroring.

Further added test cases in ovn.at to verify end to end
the functioning of Port Mirror and also verify bulk updates
to mirrors.

Note: This is targeted to mirror to destinations anywhere outside the
cluster where the analyser resides and it need not be an OVN node.

Example commands are as below:

Mirror creation
ovn-nbctl mirror-add mirror1 gre 0 from-lport 10.10.10.2

Attach a logical port to the mirror.
ovn-nbctl lsp-attach-mirror sw0-port1 mirror1

Detach a source from Mirror
ovn-nbctl lsp-detach-mirror sw0-port1 mirror1

Mirror deletion
ovn-nbctl mirror-del mirror1

Co-authored-by: Veda Barrenkala <vedabarrenkala@gmail.com>
Signed-off-by: Veda Barrenkala <vedabarrenkala@gmail.com>
Signed-off-by: Abhiram R N <abhiramrn@gmail.com>
Acked-By: Ihar Hrachyshka <ihrachys@redhat.com>
---
 NEWS                        |   1 +
 controller/automake.mk      |   4 +-
 controller/mirror.c         | 418 +++++++++++++++++++++++++++++
 controller/mirror.h         |  33 +++
 controller/ovn-controller.c |   9 +
 tests/ovn.at                | 514 ++++++++++++++++++++++++++++++++++++
 6 files changed, 978 insertions(+), 1 deletion(-)
 create mode 100644 controller/mirror.c
 create mode 100644 controller/mirror.h

Comments

Ihar Hrachyshka Dec. 15, 2022, 12:47 a.m. UTC | #1
There's one code branch left for the now-killed "both" filter. (See
below.) With this branch cleaned up,

Acked-By: Ihar Hrachyshka <ihrachys@redhat.com>

On Tue, Dec 13, 2022 at 2:34 PM Abhiram R N <abhiramrn@gmail.com> wrote:
>
> Mirror creation just creates the mirror. The lsp-attach-mirror
> triggers the sequence to create Mirror in OVS DB on compute node.
> OVS already supports Port Mirroring.
>
> Further added test cases in ovn.at to verify end to end
> the functioning of Port Mirror and also verify bulk updates
> to mirrors.
>
> Note: This is targeted to mirror to destinations anywhere outside the
> cluster where the analyser resides and it need not be an OVN node.
>
> Example commands are as below:
>
> Mirror creation
> ovn-nbctl mirror-add mirror1 gre 0 from-lport 10.10.10.2
>
> Attach a logical port to the mirror.
> ovn-nbctl lsp-attach-mirror sw0-port1 mirror1
>
> Detach a source from Mirror
> ovn-nbctl lsp-detach-mirror sw0-port1 mirror1
>
> Mirror deletion
> ovn-nbctl mirror-del mirror1
>
> Co-authored-by: Veda Barrenkala <vedabarrenkala@gmail.com>
> Signed-off-by: Veda Barrenkala <vedabarrenkala@gmail.com>
> Signed-off-by: Abhiram R N <abhiramrn@gmail.com>
> Acked-By: Ihar Hrachyshka <ihrachys@redhat.com>
> ---
>  NEWS                        |   1 +
>  controller/automake.mk      |   4 +-
>  controller/mirror.c         | 418 +++++++++++++++++++++++++++++
>  controller/mirror.h         |  33 +++
>  controller/ovn-controller.c |   9 +
>  tests/ovn.at                | 514 ++++++++++++++++++++++++++++++++++++
>  6 files changed, 978 insertions(+), 1 deletion(-)
>  create mode 100644 controller/mirror.c
>  create mode 100644 controller/mirror.h
>
> diff --git a/NEWS b/NEWS
> index 7d1e49cf1..566d6731b 100644
> --- a/NEWS
> +++ b/NEWS
> @@ -10,6 +10,7 @@ OVN v22.12.0 - xx xxx xxxx
>      per-flow IPFIX sampling.
>    - Add support for component templates within logical flows and load
>      balancers.
> +  - Added Support for Remote Port Mirroring.
>
>  OVN v22.09.0 - 16 Sep 2022
>  --------------------------
> diff --git a/controller/automake.mk b/controller/automake.mk
> index c2ab1bbe6..334672b4d 100644
> --- a/controller/automake.mk
> +++ b/controller/automake.mk
> @@ -41,7 +41,9 @@ controller_ovn_controller_SOURCES = \
>         controller/ovsport.h \
>         controller/ovsport.c \
>         controller/vif-plug.h \
> -       controller/vif-plug.c
> +       controller/vif-plug.c \
> +       controller/mirror.h \
> +       controller/mirror.c
>
>  controller_ovn_controller_LDADD = lib/libovn.la $(OVS_LIBDIR)/libopenvswitch.la
>  man_MANS += controller/ovn-controller.8
> diff --git a/controller/mirror.c b/controller/mirror.c
> new file mode 100644
> index 000000000..938ec3542
> --- /dev/null
> +++ b/controller/mirror.c
> @@ -0,0 +1,418 @@
> +/* Copyright (c) 2022 Red Hat, Inc.
> + *
> + * Licensed under the Apache License, Version 2.0 (the "License");
> + * you may not use this file except in compliance with the License.
> + * You may obtain a copy of the License at:
> + *
> + *     http://www.apache.org/licenses/LICENSE-2.0
> + *
> + * Unless required by applicable law or agreed to in writing, software
> + * distributed under the License is distributed on an "AS IS" BASIS,
> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
> + * See the License for the specific language governing permissions and
> + * limitations under the License.
> + */
> +
> +#include <config.h>
> +#include <unistd.h>
> +
> +/* library headers */
> +#include "lib/sset.h"
> +#include "lib/util.h"
> +
> +/* OVS includes. */
> +#include "lib/vswitch-idl.h"
> +#include "include/openvswitch/shash.h"
> +#include "openvswitch/vlog.h"
> +
> +/* OVN includes. */
> +#include "binding.h"
> +#include "lib/ovn-sb-idl.h"
> +#include "mirror.h"
> +
> +VLOG_DEFINE_THIS_MODULE(port_mirror);
> +
> +struct ovn_mirror {
> +    char *name;
> +    const struct sbrec_mirror *sb_mirror;
> +    const struct ovsrec_mirror *ovs_mirror;
> +    struct ovs_list mirror_src_lports;
> +    struct ovs_list mirror_dst_lports;
> +};
> +
> +struct mirror_lport {
> +    struct ovs_list list_node;
> +
> +    struct local_binding *lbinding;
> +};
> +
> +static struct ovn_mirror *ovn_mirror_create(char *mirror_name);
> +static void ovn_mirror_add(struct shash *ovn_mirrors,
> +                           struct ovn_mirror *);
> +static struct ovn_mirror *ovn_mirror_find(struct shash *ovn_mirrors,
> +                                          const char *mirror_name);
> +static void ovn_mirror_delete(struct ovn_mirror *);
> +static void ovn_mirror_add_lport(struct ovn_mirror *, struct local_binding *);
> +static void sync_ovn_mirror(struct ovn_mirror *, struct ovsdb_idl_txn *,
> +                            const struct ovsrec_bridge *);
> +
> +static void create_ovs_mirror(struct ovn_mirror *, struct ovsdb_idl_txn *,
> +                              const struct ovsrec_bridge *);
> +static void sync_ovs_mirror_ports(struct ovn_mirror *,
> +                                  const struct ovsrec_bridge *);
> +static void delete_ovs_mirror(struct ovn_mirror *,
> +                              const struct ovsrec_bridge *);
> +static bool should_delete_ovs_mirror(struct ovn_mirror *);
> +static void set_mirror_iface_options(struct ovsrec_interface *,
> +                                     const struct sbrec_mirror *);
> +
> +static const struct ovsrec_port *get_iface_port(
> +    const struct ovsrec_interface *, const struct ovsrec_bridge *);
> +
> +
> +void
> +mirror_register_ovs_idl(struct ovsdb_idl *ovs_idl)
> +{
> +    ovsdb_idl_add_column(ovs_idl, &ovsrec_bridge_col_mirrors);
> +
> +    ovsdb_idl_add_table(ovs_idl, &ovsrec_table_mirror);
> +    ovsdb_idl_add_column(ovs_idl, &ovsrec_mirror_col_name);
> +    ovsdb_idl_add_column(ovs_idl, &ovsrec_mirror_col_output_port);
> +    ovsdb_idl_add_column(ovs_idl, &ovsrec_mirror_col_select_dst_port);
> +    ovsdb_idl_add_column(ovs_idl, &ovsrec_mirror_col_select_src_port);
> +    ovsdb_idl_add_column(ovs_idl, &ovsrec_mirror_col_external_ids);
> +}
> +
> +void
> +mirror_init(void)
> +{
> +}
> +
> +void
> +mirror_destroy(void)
> +{
> +}
> +
> +void
> +mirror_run(struct ovsdb_idl_txn *ovs_idl_txn,
> +           const struct ovsrec_mirror_table *ovs_mirror_table,
> +           const struct sbrec_mirror_table *sb_mirror_table,
> +           const struct ovsrec_bridge *br_int,
> +           struct shash *local_bindings)
> +{
> +    if (!ovs_idl_txn) {
> +        return;
> +    }
> +
> +    struct shash ovn_mirrors = SHASH_INITIALIZER(&ovn_mirrors);
> +    struct shash tmp_mirrors = SHASH_INITIALIZER(&tmp_mirrors);
> +
> +    /* Iterate through sb mirrors and build the 'ovn_mirrors'. */
> +    const struct sbrec_mirror *sb_mirror;
> +    SBREC_MIRROR_TABLE_FOR_EACH (sb_mirror, sb_mirror_table) {
> +        struct ovn_mirror *m = ovn_mirror_create(sb_mirror->name);
> +        m->sb_mirror = sb_mirror;
> +        ovn_mirror_add(&ovn_mirrors, m);
> +    }
> +
> +    /* Iterate through ovs mirrors and add to the 'tmp_mirrors'. */
> +    const struct ovsrec_mirror *ovs_mirror;
> +    OVSREC_MIRROR_TABLE_FOR_EACH (ovs_mirror, ovs_mirror_table) {
> +        bool ovn_owned_mirror = smap_get_bool(&ovs_mirror->external_ids,
> +                                              "ovn-owned", false);
> +        if (!ovn_owned_mirror) {
> +            continue;
> +        }
> +
> +        struct ovn_mirror *m = ovn_mirror_find(&ovn_mirrors, ovs_mirror->name);
> +        if (!m) {
> +            m = ovn_mirror_create(ovs_mirror->name);
> +            shash_add(&tmp_mirrors, ovs_mirror->name, m);
> +        }
> +        m->ovs_mirror = ovs_mirror;
> +    }
> +
> +    if (shash_is_empty(&ovn_mirrors)) {
> +        shash_destroy(&ovn_mirrors);
> +        if (shash_is_empty(&tmp_mirrors)) {
> +            shash_destroy(&tmp_mirrors);
> +            return;
> +        } else {
> +            goto cleanup;
> +        }
> +    }
> +
> +    /* Iterate through the local bindings and if the local binding's 'pb' has
> +     * mirrors associated, add it to the ovn_mirror. */
> +    struct shash_node *node;
> +    SHASH_FOR_EACH (node, local_bindings) {
> +        struct local_binding *lbinding = node->data;
> +        const struct sbrec_port_binding *pb =
> +            local_binding_get_primary_pb(local_bindings, lbinding->name);
> +        if (!pb || !pb->n_mirror_rules) {
> +            continue;
> +        }
> +
> +        for (size_t i = 0; i < pb->n_mirror_rules; i++) {
> +            struct ovn_mirror *m = ovn_mirror_find(&ovn_mirrors,
> +                                                   pb->mirror_rules[i]->name);
> +            ovs_assert(m);
> +            ovn_mirror_add_lport(m, lbinding);
> +        }
> +    }
> +
> +    /* Iterate through the built 'ovn_mirrors' and
> +     * sync with the local ovsdb i.e.
> +     * create/update or delete the ovsrec mirror(s). */
> +     SHASH_FOR_EACH (node, &ovn_mirrors) {
> +        struct ovn_mirror *m = node->data;
> +        sync_ovn_mirror(m, ovs_idl_txn, br_int);
> +     }
> +
> +    SHASH_FOR_EACH_SAFE (node, &ovn_mirrors) {
> +        ovn_mirror_delete(node->data);
> +        shash_delete(&ovn_mirrors, node);
> +    }
> +
> +    shash_destroy(&ovn_mirrors);
> +
> +cleanup:
> +    SHASH_FOR_EACH_SAFE (node, &tmp_mirrors) {
> +        ovn_mirror_delete(node->data);
> +        shash_delete(&tmp_mirrors, node);
> +    }
> +
> +    shash_destroy(&tmp_mirrors);
> +}
> +
> +/* Static functions. */
> +static struct ovn_mirror *
> +ovn_mirror_create(char *mirror_name)
> +{
> +    struct ovn_mirror *m = xzalloc(sizeof *m);
> +    m->name = xstrdup(mirror_name);
> +    ovs_list_init(&m->mirror_src_lports);
> +    ovs_list_init(&m->mirror_dst_lports);
> +    return m;
> +}
> +
> +static void
> +ovn_mirror_add(struct shash *ovn_mirrors, struct ovn_mirror *m)
> +{
> +    shash_add(ovn_mirrors, m->sb_mirror->name, m);
> +}
> +
> +static struct ovn_mirror *
> +ovn_mirror_find(struct shash *ovn_mirrors, const char *mirror_name)
> +{
> +    return shash_find_data(ovn_mirrors, mirror_name);
> +}
> +
> +static void
> +ovn_mirror_delete(struct ovn_mirror *m)
> +{
> +    free(m->name);
> +    struct mirror_lport *m_lport;
> +    LIST_FOR_EACH_POP (m_lport, list_node, &m->mirror_src_lports) {
> +        free(m_lport);
> +    }
> +
> +    LIST_FOR_EACH_POP (m_lport, list_node, &m->mirror_dst_lports) {
> +        free(m_lport);
> +    }
> +
> +    free(m);
> +}
> +
> +static void
> +ovn_mirror_add_lport(struct ovn_mirror *m, struct local_binding *lbinding)
> +{
> +    struct mirror_lport *m_lport = xzalloc(sizeof *m_lport);
> +    m_lport->lbinding = lbinding;
> +    if (!strcmp(m->sb_mirror->filter, "from-lport")) {
> +        ovs_list_push_back(&m->mirror_src_lports, &m_lport->list_node);
> +    } else if (!strcmp(m->sb_mirror->filter, "to-lport")) {
> +        ovs_list_push_back(&m->mirror_dst_lports, &m_lport->list_node);
> +    } else {
> +        ovs_list_push_back(&m->mirror_src_lports, &m_lport->list_node);
> +        m_lport = xzalloc(sizeof *m_lport);
> +        m_lport->lbinding = lbinding;
> +        ovs_list_push_back(&m->mirror_dst_lports, &m_lport->list_node);

This branch seems to be a remnant from when the series supported
"both" filter. Should be removed until we are ready to introduce
support for "both" filter.

> +    }
> +}
> +
> +static void
> +set_mirror_iface_options(struct ovsrec_interface *iface,
> +                         const struct sbrec_mirror *sb_mirror)
> +{
> +    struct smap options = SMAP_INITIALIZER(&options);
> +    char *key;
> +
> +    key = xasprintf("%ld", (long int) sb_mirror->index);
> +    smap_add(&options, "remote_ip", sb_mirror->sink);
> +    smap_add(&options, "key", key);
> +    if (!strcmp(sb_mirror->type, "erspan")) {
> +        /* Set the ERSPAN index */
> +        smap_add(&options, "erspan_idx", key);
> +        smap_add(&options, "erspan_ver", "1");
> +    }
> +    ovsrec_interface_set_options(iface, &options);
> +
> +    free(key);
> +    smap_destroy(&options);
> +}
> +
> +static void
> +check_and_update_interface_table(const struct sbrec_mirror *sb_mirror,
> +                                 const struct ovsrec_mirror *ovs_mirror)
> +{
> +    char *type;
> +    struct ovsrec_interface *iface =
> +                          ovs_mirror->output_port->interfaces[0];
> +    struct smap *opts = &iface->options;
> +    const char *erspan_ver = smap_get(opts, "erspan_ver");
> +    if (erspan_ver) {
> +        type = "erspan";
> +    } else {
> +        type = "gre";
> +    }
> +    if (strcmp(type, sb_mirror->type)) {
> +        ovsrec_interface_set_type(iface, sb_mirror->type);
> +    }
> +    set_mirror_iface_options(iface, sb_mirror);
> +}
> +
> +static void
> +sync_ovn_mirror(struct ovn_mirror *m, struct ovsdb_idl_txn *ovs_idl_txn,
> +                const struct ovsrec_bridge *br_int)
> +{
> +    if (should_delete_ovs_mirror(m)) {
> +        /* Delete the ovsrec mirror. */
> +        delete_ovs_mirror(m, br_int);
> +        return;
> +    }
> +
> +    if (ovs_list_is_empty(&m->mirror_src_lports) &&
> +            ovs_list_is_empty(&m->mirror_dst_lports)) {
> +        /* Nothing to do. */
> +        return;
> +    }
> +
> +    if (m->sb_mirror && !m->ovs_mirror) {
> +        create_ovs_mirror(m, ovs_idl_txn, br_int);
> +    } else {
> +        check_and_update_interface_table(m->sb_mirror, m->ovs_mirror);
> +    }
> +
> +    sync_ovs_mirror_ports(m, br_int);
> +}
> +
> +static bool
> +should_delete_ovs_mirror(struct ovn_mirror *m)
> +{
> +    if (!m->ovs_mirror) {
> +        return false;
> +    }
> +
> +    if (m->ovs_mirror && !m->sb_mirror) {
> +        return true;
> +    }
> +
> +    return (ovs_list_is_empty(&m->mirror_src_lports) &&
> +            ovs_list_is_empty(&m->mirror_dst_lports));
> +}
> +
> +static const struct ovsrec_port *
> +get_iface_port(const struct ovsrec_interface *iface,
> +               const struct ovsrec_bridge *br_int)
> +{
> +    for (size_t i = 0; i < br_int->n_ports; i++) {
> +        const struct ovsrec_port *p = br_int->ports[i];
> +        for (size_t j = 0; j < p->n_interfaces; j++) {
> +            if (!strcmp(iface->name, p->interfaces[j]->name)) {
> +                return p;
> +            }
> +        }
> +    }
> +    return NULL;
> +}
> +
> +static void
> +create_ovs_mirror(struct ovn_mirror *m, struct ovsdb_idl_txn *ovs_idl_txn,
> +                  const struct ovsrec_bridge *br_int)
> +{
> +    struct ovsrec_interface *iface = ovsrec_interface_insert(ovs_idl_txn);
> +    char *port_name = xasprintf("ovn-%s", m->name);
> +
> +    ovsrec_interface_set_name(iface, port_name);
> +    ovsrec_interface_set_type(iface, m->sb_mirror->type);
> +    set_mirror_iface_options(iface, m->sb_mirror);
> +
> +    struct ovsrec_port *port = ovsrec_port_insert(ovs_idl_txn);
> +    ovsrec_port_set_name(port, port_name);
> +    ovsrec_port_set_interfaces(port, &iface, 1);
> +    ovsrec_bridge_update_ports_addvalue(br_int, port);
> +
> +    free(port_name);
> +
> +    m->ovs_mirror = ovsrec_mirror_insert(ovs_idl_txn);
> +    ovsrec_mirror_set_name(m->ovs_mirror, m->name);
> +    ovsrec_mirror_set_output_port(m->ovs_mirror, port);
> +
> +    const struct smap external_ids =
> +        SMAP_CONST1(&external_ids, "ovn-owned", "true");
> +    ovsrec_mirror_set_external_ids(m->ovs_mirror, &external_ids);
> +
> +    ovsrec_bridge_update_mirrors_addvalue(br_int, m->ovs_mirror);
> +}
> +
> +static void
> +sync_ovs_mirror_ports(struct ovn_mirror *m, const struct ovsrec_bridge *br_int)
> +{
> +    struct mirror_lport *m_lport;
> +
> +    if (ovs_list_is_empty(&m->mirror_src_lports)) {
> +        ovsrec_mirror_set_select_src_port(m->ovs_mirror, NULL, 0);
> +    } else {
> +        size_t n_lports = ovs_list_size(&m->mirror_src_lports);
> +        struct ovsrec_port **ovs_ports = xmalloc(sizeof *ovs_ports * n_lports);
> +
> +        size_t i = 0;
> +        LIST_FOR_EACH (m_lport, list_node, &m->mirror_src_lports) {
> +            const struct ovsrec_port *p =
> +                get_iface_port(m_lport->lbinding->iface, br_int);
> +            ovs_assert(p);
> +            ovs_ports[i++] = (struct ovsrec_port *) p;
> +        }
> +
> +        ovsrec_mirror_set_select_src_port(m->ovs_mirror, ovs_ports, n_lports);
> +        free(ovs_ports);
> +    }
> +
> +    if (ovs_list_is_empty(&m->mirror_dst_lports)) {
> +        ovsrec_mirror_set_select_dst_port(m->ovs_mirror, NULL, 0);
> +    } else {
> +        size_t n_lports = ovs_list_size(&m->mirror_dst_lports);
> +        struct ovsrec_port **ovs_ports = xmalloc(sizeof *ovs_ports * n_lports);
> +
> +        size_t i = 0;
> +        LIST_FOR_EACH (m_lport, list_node, &m->mirror_dst_lports) {
> +            const struct ovsrec_port *p =
> +                get_iface_port(m_lport->lbinding->iface, br_int);
> +            ovs_assert(p);
> +            ovs_ports[i++] = (struct ovsrec_port *) p;
> +        }
> +
> +        ovsrec_mirror_set_select_dst_port(m->ovs_mirror, ovs_ports, n_lports);
> +        free(ovs_ports);
> +    }
> +}
> +
> +static void
> +delete_ovs_mirror(struct ovn_mirror *m, const struct ovsrec_bridge *br_int)
> +{
> +    ovsrec_bridge_update_ports_delvalue(br_int, m->ovs_mirror->output_port);
> +    ovsrec_bridge_update_mirrors_delvalue(br_int, m->ovs_mirror);
> +    ovsrec_port_delete(m->ovs_mirror->output_port);
> +    ovsrec_mirror_delete(m->ovs_mirror);
> +}
> diff --git a/controller/mirror.h b/controller/mirror.h
> new file mode 100644
> index 000000000..a79de109d
> --- /dev/null
> +++ b/controller/mirror.h
> @@ -0,0 +1,33 @@
> +/* Copyright (c) 2022 Red Hat, Inc.
> + *
> + * Licensed under the Apache License, Version 2.0 (the "License");
> + * you may not use this file except in compliance with the License.
> + * You may obtain a copy of the License at:
> + *
> + *     http://www.apache.org/licenses/LICENSE-2.0
> + *
> + * Unless required by applicable law or agreed to in writing, software
> + * distributed under the License is distributed on an "AS IS" BASIS,
> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
> + * See the License for the specific language governing permissions and
> + * limitations under the License.
> + */
> +
> +#ifndef OVN_MIRROR_H
> +#define OVN_MIRROR_H 1
> +
> +struct ovsdb_idl_txn;
> +struct ovsrec_mirror_table;
> +struct sbrec_mirror_table;
> +struct ovsrec_bridge;
> +struct shash;
> +
> +void mirror_register_ovs_idl(struct ovsdb_idl *);
> +void mirror_init(void);
> +void mirror_destroy(void);
> +void mirror_run(struct ovsdb_idl_txn *ovs_idl_txn,
> +                const struct ovsrec_mirror_table *,
> +                const struct sbrec_mirror_table *,
> +                const struct ovsrec_bridge *,
> +                struct shash *local_bindings);
> +#endif
> diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c
> index f0fd24820..470891bb2 100644
> --- a/controller/ovn-controller.c
> +++ b/controller/ovn-controller.c
> @@ -78,6 +78,7 @@
>  #include "lib/inc-proc-eng.h"
>  #include "lib/ovn-l7.h"
>  #include "hmapx.h"
> +#include "mirror.h"
>
>  VLOG_DEFINE_THIS_MODULE(main);
>
> @@ -1004,6 +1005,7 @@ ctrl_register_ovs_idl(struct ovsdb_idl *ovs_idl)
>      ovsdb_idl_track_add_column(ovs_idl, &ovsrec_port_col_name);
>      ovsdb_idl_track_add_column(ovs_idl, &ovsrec_port_col_interfaces);
>      ovsdb_idl_track_add_column(ovs_idl, &ovsrec_port_col_external_ids);
> +    mirror_register_ovs_idl(ovs_idl);
>  }
>
>  #define SB_NODES \
> @@ -3926,6 +3928,7 @@ main(int argc, char *argv[])
>      patch_init();
>      pinctrl_init();
>      lflow_init();
> +    mirror_init();
>      vif_plug_provider_initialize();
>
>      /* Connect to OVS OVSDB instance. */
> @@ -4638,6 +4641,11 @@ main(int argc, char *argv[])
>                                      &runtime_data->local_active_ports_ras);
>                          stopwatch_stop(PINCTRL_RUN_STOPWATCH_NAME,
>                                         time_msec());
> +                        mirror_run(ovs_idl_txn,
> +                                   ovsrec_mirror_table_get(ovs_idl_loop.idl),
> +                                   sbrec_mirror_table_get(ovnsb_idl_loop.idl),
> +                                   br_int,
> +                                   &runtime_data->lbinding_data.bindings);
>                          /* Updating monitor conditions if runtime data or
>                           * logical datapath goups changed. */
>                          if (engine_node_changed(&en_runtime_data)
> @@ -4884,6 +4892,7 @@ loop_done:
>      pinctrl_destroy();
>      binding_destroy();
>      patch_destroy();
> +    mirror_destroy();
>      if_status_mgr_destroy(if_mgr);
>      shash_destroy(&vif_plug_deleted_iface_ids);
>      shash_destroy(&vif_plug_changed_iface_ids);
> diff --git a/tests/ovn.at b/tests/ovn.at
> index f3bd53242..305a0e45d 100644
> --- a/tests/ovn.at
> +++ b/tests/ovn.at
> @@ -16242,6 +16242,520 @@ OVN_CLEANUP([hv1], [hv2])
>  AT_CLEANUP
>  ])
>
> +OVN_FOR_EACH_NORTHD([
> +AT_SETUP([Mirror])
> +AT_KEYWORDS([Mirror])
> +ovn_start
> +
> +# Logical network:
> +# One LR - R1 has switch ls1 (191.168.1.0/24) connected to it,
> +# and has switch ls2 (172.16.1.0/24) connected to it.
> +
> +ovn-nbctl lr-add R1
> +
> +ovn-nbctl ls-add ls1
> +ovn-nbctl ls-add ls2
> +
> +# Connect ls1 to R1
> +ovn-nbctl lrp-add R1 ls1 00:00:00:01:02:f1 192.168.1.1/24
> +ovn-nbctl lsp-add ls1 rp-ls1 -- set Logical_Switch_Port rp-ls1 \
> +    type=router options:router-port=ls1 addresses=\"00:00:00:01:02:f1\"
> +
> +# Connect ls2 to R1
> +ovn-nbctl lrp-add R1 ls2 00:00:00:01:02:f2 172.16.1.1/24
> +ovn-nbctl lsp-add ls2 rp-ls2 -- set Logical_Switch_Port rp-ls2 \
> +    type=router options:router-port=ls2 addresses=\"00:00:00:01:02:f2\"
> +
> +# Create logical port ls1-lp1 in ls1
> +ovn-nbctl lsp-add ls1 ls1-lp1 \
> +-- lsp-set-addresses ls1-lp1 "00:00:00:01:02:03 192.168.1.2"
> +
> +# Create logical port ls2-lp1 in ls2
> +ovn-nbctl lsp-add ls2 ls2-lp1 \
> +-- lsp-set-addresses ls2-lp1 "00:00:00:01:02:04 172.16.1.2"
> +
> +ovn-nbctl lsp-add ls1 ln-public
> +ovn-nbctl lsp-set-type ln-public localnet
> +ovn-nbctl lsp-set-addresses ln-public unknown
> +ovn-nbctl lsp-set-options ln-public network_name=public
> +
> +# Create one hypervisor and create OVS ports corresponding to logical ports.
> +net_add n1
> +
> +sim_add hv1
> +as hv1
> +ovs-vsctl add-br br-phys -- set bridge br-phys other-config:hwaddr=\"00:00:00:01:02:00\"
> +ovn_attach n1 br-phys 192.168.1.11
> +
> +ovs-vsctl -- add-port br-int vif1 -- \
> +    set interface vif1 external-ids:iface-id=ls1-lp1 \
> +    options:tx_pcap=hv1/vif1-tx.pcap \
> +    options:rxq_pcap=hv1/vif1-rx.pcap \
> +    ofport-request=1
> +
> +ovs-vsctl -- add-port br-int vif2 -- \
> +    set interface vif2 external-ids:iface-id=ls2-lp1 \
> +    options:tx_pcap=hv1/vif2-tx.pcap \
> +    options:rxq_pcap=hv1/vif2-rx.pcap \
> +    ofport-request=1
> +
> +ovs-vsctl set open . external-ids:ovn-bridge-mappings=public:br-phys
> +
> +# Allow some time for ovn-northd and ovn-controller to catch up.
> +wait_for_ports_up
> +check ovn-nbctl --wait=hv sync
> +ovn-nbctl dump-flows > sbflows
> +AT_CAPTURE_FILE([sbflows])
> +
> +for i in 1 2; do
> +    : > vif$i.expected
> +done
> +
> +net_add n2
> +
> +sim_add hv2
> +as hv2
> +ovs-vsctl add-br br-phys -- set bridge br-phys other-config:hwaddr=\"00:00:00:02:02:00\"
> +ovn_attach n2 br-phys 192.168.1.12
> +
> +OVN_POPULATE_ARP
> +
> +as hv1
> +
> +# test_ipv4_icmp_request INPORT ETH_SRC ETH_DST IPV4_SRC IPV4_DST IP_CHKSUM ICMP_CHKSUM [EXP_IP_CHKSUM EXP_ICMP_CHKSUM] ENCAP_TYPE FILTER
> +#
> +# Causes a packet to be received on INPORT.  The packet is an ICMPv4
> +# request with ETH_SRC, ETH_DST, IPV4_SRC, IPV4_DST, IP_CHSUM and
> +# ICMP_CHKSUM as specified.  If EXP_IP_CHKSUM and EXP_ICMP_CHKSUM are
> +# provided, then it should be the ip and icmp checksums of the packet
> +# responded; otherwise, no reply is expected.
> +# In the absence of an ip checksum calculation helpers, this relies
> +# on the caller to provide the checksums for the ip and icmp headers.
> +# XXX This should be more systematic.
> +#
> +# INPORT is an lport number, e.g. 11 for vif11.
> +# ETH_SRC and ETH_DST are each 12 hex digits.
> +# IPV4_SRC and IPV4_DST are each 8 hex digits.
> +# IP_CHSUM and ICMP_CHKSUM are each 4 hex digits.
> +# EXP_IP_CHSUM and EXP_ICMP_CHKSUM are each 4 hex digits.
> +# ENCAP_TYPE - gre or erspan
> +# FILTER - Mirror Filter - to-lport / from-lport
> +test_ipv4_icmp_request() {
> +    local inport=$1 eth_src=$2 eth_dst=$3 ipv4_src=$4 ipv4_dst=$5 ip_chksum=$6 icmp_chksum=$7
> +    local exp_ip_chksum=$8 exp_icmp_chksum=$9 mirror_encap_type=${10} mirror_filter=${11}
> +    shift; shift; shift; shift; shift; shift; shift
> +    shift; shift; shift; shift;
> +
> +    # Use ttl to exercise section 4.2.2.9 of RFC1812
> +    local ip_ttl=02
> +    local icmp_id=5fbf
> +    local icmp_seq=0001
> +    local icmp_data=$(seq 1 56 | xargs printf "%02x")
> +    local icmp_type_code_request=0800
> +    local icmp_payload=${icmp_type_code_request}${icmp_chksum}${icmp_id}${icmp_seq}${icmp_data}
> +    local packet=${eth_dst}${eth_src}08004500005400004000${ip_ttl}01${ip_chksum}${ipv4_src}${ipv4_dst}${icmp_payload}
> +
> +    as hv1 ovs-appctl netdev-dummy/receive vif$inport $packet
> +
> +    # Expect to receive the reply, if any. In same port where packet was sent.
> +    # Note: src and dst fields are expected to be reversed.
> +    local icmp_type_code_response=0000
> +    local reply_icmp_ttl=fe
> +    local reply_icmp_payload=${icmp_type_code_response}${exp_icmp_chksum}${icmp_id}${icmp_seq}${icmp_data}
> +    local reply=${eth_src}${eth_dst}08004500005400004000${reply_icmp_ttl}01${exp_ip_chksum}${ipv4_dst}${ipv4_src}${reply_icmp_payload}
> +    echo $reply >> vif$inport.expected
> +    local remote_mac=000000020200
> +    local local_mac=000000010200
> +    if test ${mirror_encap_type} = "gre" ; then
> +        local ipv4_gre=4500007e00004000402fb6e9c0a8010bc0a8010c2000655800000000
> +        if test ${mirror_filter} = "to-lport" ; then
> +            local mirror=${remote_mac}${local_mac}0800${ipv4_gre}${reply}
> +        elif test ${mirror_filter} = "from-lport" ; then
> +            local mirror=${remote_mac}${local_mac}0800${ipv4_gre}${packet}
> +        fi
> +    elif test ${mirror_encap_type} = "erspan" ; then
> +        local ipv4_erspan=4500008600004000402fb6e1c0a8010bc0a8010c
> +        local erspan_seq0=100088be000000001000000000000000
> +        local erspan_seq1=100088be000000011000000000000000
> +        if test ${mirror_filter} = "to-lport" ; then
> +            local mirror=${remote_mac}${local_mac}0800${ipv4_erspan}${erspan_seq0}${reply}
> +        elif test ${mirror_filter} = "from-lport" ; then
> +            local mirror=${remote_mac}${local_mac}0800${ipv4_erspan}${erspan_seq1}${packet}
> +        fi
> +    fi
> +    echo $mirror >> br-phys_n1.expected
> +
> +}
> +
> +# Set IPs
> +rtr_l2_ip=$(ip_to_hex 172 16 1 1)
> +l1_ip=$(ip_to_hex 192 168 1 2)
> +
> +check ovn-nbctl mirror-add mirror0 gre 0 to-lport 192.168.1.12
> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
> +
> +# Send ping packet and check for mirrored packet of the reply
> +test_ipv4_icmp_request 1 000000010203 0000000102f1 $l1_ip $rtr_l2_ip 0000 8510 03ff 8d10 "gre" "to-lport"
> +
> +OVN_CHECK_PACKETS_CONTAIN([hv1/br-phys_n1-tx.pcap], [br-phys_n1.expected])
> +OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [vif1.expected])
> +
> +as hv1 reset_pcap_file vif1 hv1/vif1
> +as hv1 reset_pcap_file br-phys_n1 hv1/br-phys_n1
> +rm -f br-phys_n1.expected
> +rm -f vif1.expected
> +
> +check ovn-nbctl set mirror . type=erspan
> +
> +# Send ping packet and check for mirrored packet of the reply
> +test_ipv4_icmp_request 1 000000010203 0000000102f1 $l1_ip $rtr_l2_ip 0000 8510 03ff 8d10 "erspan" "to-lport"
> +
> +OVN_CHECK_PACKETS_CONTAIN([hv1/br-phys_n1-tx.pcap], [br-phys_n1.expected])
> +OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [vif1.expected])
> +
> +as hv1 reset_pcap_file vif1 hv1/vif1
> +as hv1 reset_pcap_file br-phys_n1 hv1/br-phys_n1
> +rm -f br-phys_n1.expected
> +rm -f vif1.expected
> +
> +check ovn-nbctl set mirror . filter=from-lport
> +
> +# Send ping packet and check for mirrored packet of the request
> +test_ipv4_icmp_request 1 000000010203 0000000102f1 $l1_ip $rtr_l2_ip 0000 8510 03ff 8d10 "erspan" "from-lport"
> +
> +OVN_CHECK_PACKETS_CONTAIN([hv1/br-phys_n1-tx.pcap], [br-phys_n1.expected])
> +OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [vif1.expected])
> +
> +as hv1 reset_pcap_file vif1 hv1/vif1
> +as hv1 reset_pcap_file br-phys_n1 hv1/br-phys_n1
> +rm -f br-phys_n1.expected
> +rm -f vif1.expected
> +
> +check ovn-nbctl set mirror . type=gre
> +
> +# Send ping packet and check for mirrored packet of the request
> +test_ipv4_icmp_request 1 000000010203 0000000102f1 $l1_ip $rtr_l2_ip 0000 8510 03ff 8d10 "gre" "from-lport"
> +
> +OVN_CHECK_PACKETS_CONTAIN([hv1/br-phys_n1-tx.pcap], [br-phys_n1.expected])
> +OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [vif1.expected])
> +
> +echo "---------OVN NB Mirror-----"
> +ovn-nbctl mirror-list
> +
> +echo "---------OVS Mirror----"
> +ovs-vsctl list Mirror
> +
> +echo "-----------------------"
> +
> +echo "Verifying Mirror deletion in OVS"
> +# Set vif1 iface-id such that OVN releases port binding
> +check ovs-vsctl set interface vif1 external_ids:iface-id=foo
> +check ovn-nbctl --wait=hv sync
> +
> +AT_CHECK([as hv1 ovs-vsctl list Mirror], [0], [dnl
> +])
> +
> +# Set vif1 iface-id back to ls1-lp1
> +check ovs-vsctl set interface vif1 external_ids:iface-id=ls1-lp1
> +check ovn-nbctl --wait=hv sync
> +
> +OVS_WAIT_UNTIL([test $(as hv1 ovs-vsctl get Mirror mirror0 name) = "mirror0"])
> +
> +# Delete vif1 so that OVN releases port binding
> +check ovs-vsctl del-port br-int vif1
> +check ovn-nbctl --wait=hv sync
> +
> +OVS_WAIT_UNTIL([test 0 = $(as hv1 ovs-vsctl list Mirror | wc -l)])
> +
> +OVN_CLEANUP([hv1], [hv2])
> +AT_CLEANUP
> +])
> +
> +OVN_FOR_EACH_NORTHD([
> +AT_SETUP([Mirror test bulk updates])
> +AT_KEYWORDS([Mirror test bulk updates])
> +ovn_start
> +
> +# Logical network:
> +# One LR - R1 has switch ls1 (191.168.1.0/24) connected to it,
> +# and has switch ls2 (172.16.1.0/24) connected to it.
> +
> +ovn-nbctl lr-add R1
> +
> +ovn-nbctl ls-add ls1
> +ovn-nbctl ls-add ls2
> +
> +# Connect ls1 to R1
> +ovn-nbctl lrp-add R1 ls1 00:00:00:01:02:f1 192.168.1.1/24
> +ovn-nbctl lsp-add ls1 rp-ls1 -- set Logical_Switch_Port rp-ls1 \
> +    type=router options:router-port=ls1 addresses=\"00:00:00:01:02:f1\"
> +
> +# Connect ls2 to R1
> +ovn-nbctl lrp-add R1 ls2 00:00:00:01:02:f2 172.16.1.1/24
> +ovn-nbctl lsp-add ls2 rp-ls2 -- set Logical_Switch_Port rp-ls2 \
> +    type=router options:router-port=ls2 addresses=\"00:00:00:01:02:f2\"
> +
> +# Create logical port ls1-lp1 in ls1
> +ovn-nbctl lsp-add ls1 ls1-lp1 \
> +-- lsp-set-addresses ls1-lp1 "00:00:00:01:02:03 192.168.1.2"
> +
> +# Create logical port ls1-lp2 in ls1
> +ovn-nbctl lsp-add ls1 ls1-lp2 \
> +-- lsp-set-addresses ls1-lp2 "00:00:00:01:03:03 192.168.1.3"
> +
> +# Create logical port ls2-lp1 in ls2
> +ovn-nbctl lsp-add ls2 ls2-lp1 \
> +-- lsp-set-addresses ls2-lp1 "00:00:00:01:02:04 172.16.1.2"
> +
> +# Create logical port ls2-lp2 in ls2
> +ovn-nbctl lsp-add ls2 ls2-lp2 \
> +-- lsp-set-addresses ls2-lp2 "00:00:00:01:03:04 172.16.1.3"
> +
> +ovn-nbctl lsp-add ls1 ln-public
> +ovn-nbctl lsp-set-type ln-public localnet
> +ovn-nbctl lsp-set-addresses ln-public unknown
> +ovn-nbctl lsp-set-options ln-public network_name=public
> +
> +# Create 2 hypervisors and create OVS ports corresponding to logical ports for hv1.
> +net_add n1
> +
> +sim_add hv1
> +as hv1
> +ovs-vsctl add-br br-phys -- set bridge br-phys other-config:hwaddr=\"00:00:00:01:02:00\"
> +ovn_attach n1 br-phys 192.168.1.11
> +
> +net_add n2
> +
> +sim_add hv2
> +as hv2
> +ovs-vsctl add-br br-phys -- set bridge br-phys other-config:hwaddr=\"00:00:00:02:02:00\"
> +ovn_attach n2 br-phys 192.168.1.12
> +
> +OVN_POPULATE_ARP
> +
> +as hv1
> +
> +ovs-vsctl -- add-port br-int vif1 -- \
> +    set interface vif1 external-ids:iface-id=ls1-lp1
> +
> +ovs-vsctl -- add-port br-int vif2 -- \
> +    set interface vif2 external-ids:iface-id=ls2-lp1
> +
> +ovs-vsctl -- add-port br-int vif3 -- \
> +    set interface vif3 external-ids:iface-id=ls1-lp2
> +
> +ovs-vsctl -- add-port br-int vif4 -- \
> +    set interface vif4 external-ids:iface-id=ls2-lp2
> +
> +ovs-vsctl set open . external-ids:ovn-bridge-mappings=public:br-phys
> +
> +# Allow some time for ovn-northd and ovn-controller to catch up.
> +wait_for_ports_up
> +check ovn-nbctl --wait=hv sync
> +ovn-nbctl dump-flows > sbflows
> +AT_CAPTURE_FILE([sbflows])
> +
> +check ovn-nbctl mirror-add mirror0 gre 0 to-lport 192.168.1.12
> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
> +check ovn-nbctl --wait=hv sync
> +origA=$(ovs-vsctl get Mirror mirror0 select_dst_port)
> +check ovn-nbctl lsp-attach-mirror ls2-lp1 mirror0
> +
> +check ovn-nbctl mirror-add mirror1 gre 1 to-lport 192.168.1.12
> +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror1
> +check ovn-nbctl --wait=hv sync
> +origB=$(ovs-vsctl get Mirror mirror1 select_dst_port)
> +check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror1
> +
> +check ovn-nbctl --wait=hv sync
> +
> +as hv1
> +orig1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
> +orig2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
> +
> +check ovn-nbctl mirror-del
> +check ovn-nbctl --wait=hv sync
> +
> +check ovn-nbctl mirror-add mirror0 gre 0 to-lport 192.168.1.12
> +check ovn-nbctl mirror-add mirror1 gre 1 to-lport 192.168.1.12
> +
> +# Attaches multiple mirrors
> +
> +check as hv1 ovn-appctl -t ovn-controller debug/pause
> +check ovn-nbctl lsp-attach-mirror ls2-lp1 mirror0
> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
> +check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror1
> +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror1
> +check as hv1 ovn-appctl -t ovn-controller debug/resume
> +
> +check ovn-nbctl --wait=hv sync
> +
> +as hv1
> +new1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
> +new2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
> +
> +AT_CHECK([test "$orig1" = "$new1"], [0], [])
> +
> +AT_CHECK([test "$orig2" = "$new2"], [0], [])
> +
> +# Equal detaches and attaches
> +check as hv1 ovn-appctl -t ovn-controller debug/pause
> +check ovn-nbctl lsp-detach-mirror ls1-lp1 mirror0
> +check ovn-nbctl lsp-detach-mirror ls2-lp1 mirror0
> +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror0
> +check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror0
> +
> +check ovn-nbctl lsp-detach-mirror ls1-lp2 mirror1
> +check ovn-nbctl lsp-detach-mirror ls2-lp2 mirror1
> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror1
> +check ovn-nbctl lsp-attach-mirror ls2-lp1 mirror1
> +check as hv1 ovn-appctl -t ovn-controller debug/resume
> +
> +check ovn-nbctl --wait=hv sync
> +
> +# Make sure that ovn-controller has not asserted.
> +AT_CHECK([kill -0 $(cat hv1/ovn-controller.pid)])
> +
> +as hv1
> +new1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
> +new2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
> +
> +AT_CHECK([test "$orig1" = "$new2"], [0], [])
> +
> +AT_CHECK([test "$orig2" = "$new1"], [0], [])
> +
> +# Detaches more than attaches
> +check as hv1 ovn-appctl -t ovn-controller debug/pause
> +check ovn-nbctl lsp-detach-mirror ls1-lp1 mirror1
> +check ovn-nbctl lsp-detach-mirror ls2-lp1 mirror1
> +check ovn-nbctl lsp-detach-mirror ls1-lp2 mirror0
> +check ovn-nbctl lsp-detach-mirror ls2-lp2 mirror0
> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
> +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror1
> +check as hv1 ovn-appctl -t ovn-controller debug/resume
> +
> +check ovn-nbctl --wait=hv sync
> +# Make sure that ovn-controller has not asserted.
> +AT_CHECK([kill -0 $(cat hv1/ovn-controller.pid)])
> +
> +as hv1
> +new1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
> +new2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
> +
> +AT_CHECK([test "$origA" = "$new1"], [0], [])
> +
> +AT_CHECK([test "$origB" = "$new2"], [0], [])
> +
> +# Attaches more than detaches
> +check as hv1 ovn-appctl -t ovn-controller debug/pause
> +check ovn-nbctl lsp-detach-mirror ls1-lp1 mirror0
> +check ovn-nbctl lsp-detach-mirror ls1-lp2 mirror1
> +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror0
> +check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror0
> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror1
> +check ovn-nbctl lsp-attach-mirror ls2-lp1 mirror1
> +check as hv1 ovn-appctl -t ovn-controller debug/resume
> +
> +check ovn-nbctl --wait=hv sync
> +# Make sure that ovn-controller has not asserted.
> +AT_CHECK([kill -0 $(cat hv1/ovn-controller.pid)])
> +
> +as hv1
> +new1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
> +new2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
> +
> +AT_CHECK([test "$orig1" = "$new2"], [0], [])
> +
> +AT_CHECK([test "$orig2" = "$new1"], [0], [])
> +
> +# Detaches all
> +check as hv1 ovn-appctl -t ovn-controller debug/pause
> +check ovn-nbctl lsp-detach-mirror ls1-lp2 mirror0
> +check ovn-nbctl lsp-detach-mirror ls2-lp2 mirror0
> +check ovn-nbctl lsp-detach-mirror ls1-lp1 mirror1
> +check ovn-nbctl lsp-detach-mirror ls2-lp1 mirror1
> +check as hv1 ovn-appctl -t ovn-controller debug/resume
> +check ovn-nbctl --wait=hv sync
> +
> +OVS_WAIT_UNTIL([test 0 = $(as hv1 ovs-vsctl list Mirror | wc -l)])
> +
> +check ovn-nbctl mirror-add mirror2 gre 2 to-lport 192.168.1.12
> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror2
> +check ovn-nbctl --wait=hv sync
> +
> +# Attaches SAME port to multiple mirrors
> +# and detaches it from existing mirror.
> +# More attach than detach
> +
> +check as hv1 ovn-appctl -t ovn-controller debug/pause
> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror1
> +check ovn-nbctl lsp-detach-mirror ls1-lp1 mirror2
> +check as hv1 ovn-appctl -t ovn-controller debug/resume
> +
> +check ovn-nbctl --wait=hv sync
> +
> +as hv1
> +new1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
> +new2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
> +
> +AT_CHECK([test "$origA" = "$new1"], [0], [])
> +AT_CHECK([test "$origA" = "$new2"], [0], [])
> +OVS_WAIT_UNTIL([test 0 = $(as hv1 ovs-vsctl list Mirror mirror2 | wc -l)])
> +
> +check ovn-nbctl mirror-add mirror3 gre 3 to-lport 192.168.1.12
> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror3
> +check ovn-nbctl --wait=hv sync
> +
> +# Detaches SAME port from multiple mirrors
> +# and attaches it to existing mirror.
> +# More detach than attach
> +
> +check as hv1 ovn-appctl -t ovn-controller debug/pause
> +check ovn-nbctl lsp-detach-mirror ls1-lp1 mirror0
> +check ovn-nbctl lsp-detach-mirror ls1-lp1 mirror3
> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror2
> +check as hv1 ovn-appctl -t ovn-controller debug/resume
> +
> +check ovn-nbctl --wait=hv sync
> +
> +as hv1
> +new1=$(ovs-vsctl get Mirror mirror1 select_dst_port)
> +new2=$(ovs-vsctl get Mirror mirror2 select_dst_port)
> +
> +AT_CHECK([test "$origA" = "$new1"], [0], [])
> +AT_CHECK([test "$origA" = "$new2"], [0], [])
> +OVS_WAIT_UNTIL([test 0 = $(as hv1 ovs-vsctl list Mirror mirror0 | wc -l)])
> +OVS_WAIT_UNTIL([test 0 = $(as hv1 ovs-vsctl list Mirror mirror3 | wc -l)])
> +
> +check ovn-nbctl mirror-del
> +check ovn-nbctl --wait=hv sync
> +check ovn-nbctl mirror-add mirror0 erspan 0 to-lport 192.168.1.12
> +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
> +check ovn-nbctl --wait=hv sync
> +
> +# Make sure different fields of mirror resource set from OVN
> +# propagates to OVS correctly
> +
> +check as hv1 ovn-appctl -t ovn-controller debug/pause
> +check ovn-nbctl set mirror . filter=from-lport
> +check ovn-nbctl set mirror . type=gre
> +check ovn-nbctl set mirror . index=123
> +check as hv1 ovn-appctl -t ovn-controller debug/resume
> +
> +check ovn-nbctl --wait=hv sync
> +
> +as hv1
> +new1=$(ovs-vsctl get Mirror mirror0 select_src_port)
> +AT_CHECK([test "$origA" = "$new1"], [0], [])
> +type=$(ovs-vsctl get Interface ovn-mirror0 type)
> +OVS_WAIT_UNTIL([test "gre" = "$type"])
> +index=$(ovs-vsctl get Interface ovn-mirror0 options:key | grep 123 | wc -l)
> +OVS_WAIT_UNTIL([test "1" = "$index"])
> +
> +OVN_CLEANUP([hv1],[hv2])
> +AT_CLEANUP
> +])
>
>  OVN_FOR_EACH_NORTHD([
>  AT_SETUP([Port Groups])
> --
> 2.31.1
>
Numan Siddique Dec. 15, 2022, 2:40 a.m. UTC | #2
On Wed, Dec 14, 2022 at 7:48 PM Ihar Hrachyshka <ihrachys@redhat.com> wrote:
>
> There's one code branch left for the now-killed "both" filter. (See
> below.) With this branch cleaned up,
>
> Acked-By: Ihar Hrachyshka <ihrachys@redhat.com>

I applied this patch to the main and branch-22,12 with a few changes.

Please see below


>
> On Tue, Dec 13, 2022 at 2:34 PM Abhiram R N <abhiramrn@gmail.com> wrote:
> >
> > Mirror creation just creates the mirror. The lsp-attach-mirror
> > triggers the sequence to create Mirror in OVS DB on compute node.
> > OVS already supports Port Mirroring.
> >
> > Further added test cases in ovn.at to verify end to end
> > the functioning of Port Mirror and also verify bulk updates
> > to mirrors.
> >
> > Note: This is targeted to mirror to destinations anywhere outside the
> > cluster where the analyser resides and it need not be an OVN node.
> >
> > Example commands are as below:
> >
> > Mirror creation
> > ovn-nbctl mirror-add mirror1 gre 0 from-lport 10.10.10.2
> >
> > Attach a logical port to the mirror.
> > ovn-nbctl lsp-attach-mirror sw0-port1 mirror1
> >
> > Detach a source from Mirror
> > ovn-nbctl lsp-detach-mirror sw0-port1 mirror1
> >
> > Mirror deletion
> > ovn-nbctl mirror-del mirror1
> >
> > Co-authored-by: Veda Barrenkala <vedabarrenkala@gmail.com>
> > Signed-off-by: Veda Barrenkala <vedabarrenkala@gmail.com>
> > Signed-off-by: Abhiram R N <abhiramrn@gmail.com>
> > Acked-By: Ihar Hrachyshka <ihrachys@redhat.com>
> > ---
> >  NEWS                        |   1 +
> >  controller/automake.mk      |   4 +-
> >  controller/mirror.c         | 418 +++++++++++++++++++++++++++++
> >  controller/mirror.h         |  33 +++
> >  controller/ovn-controller.c |   9 +
> >  tests/ovn.at                | 514 ++++++++++++++++++++++++++++++++++++
> >  6 files changed, 978 insertions(+), 1 deletion(-)
> >  create mode 100644 controller/mirror.c
> >  create mode 100644 controller/mirror.h
> >
> > diff --git a/NEWS b/NEWS
> > index 7d1e49cf1..566d6731b 100644
> > --- a/NEWS
> > +++ b/NEWS
> > @@ -10,6 +10,7 @@ OVN v22.12.0 - xx xxx xxxx
> >      per-flow IPFIX sampling.
> >    - Add support for component templates within logical flows and load
> >      balancers.
> > +  - Added Support for Remote Port Mirroring.
> >
> >  OVN v22.09.0 - 16 Sep 2022
> >  --------------------------
> > diff --git a/controller/automake.mk b/controller/automake.mk
> > index c2ab1bbe6..334672b4d 100644
> > --- a/controller/automake.mk
> > +++ b/controller/automake.mk
> > @@ -41,7 +41,9 @@ controller_ovn_controller_SOURCES = \
> >         controller/ovsport.h \
> >         controller/ovsport.c \
> >         controller/vif-plug.h \
> > -       controller/vif-plug.c
> > +       controller/vif-plug.c \
> > +       controller/mirror.h \
> > +       controller/mirror.c
> >
> >  controller_ovn_controller_LDADD = lib/libovn.la $(OVS_LIBDIR)/libopenvswitch.la
> >  man_MANS += controller/ovn-controller.8
> > diff --git a/controller/mirror.c b/controller/mirror.c
> > new file mode 100644
> > index 000000000..938ec3542
> > --- /dev/null
> > +++ b/controller/mirror.c
> > @@ -0,0 +1,418 @@
> > +/* Copyright (c) 2022 Red Hat, Inc.
> > + *
> > + * Licensed under the Apache License, Version 2.0 (the "License");
> > + * you may not use this file except in compliance with the License.
> > + * You may obtain a copy of the License at:
> > + *
> > + *     http://www.apache.org/licenses/LICENSE-2.0
> > + *
> > + * Unless required by applicable law or agreed to in writing, software
> > + * distributed under the License is distributed on an "AS IS" BASIS,
> > + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
> > + * See the License for the specific language governing permissions and
> > + * limitations under the License.
> > + */
> > +
> > +#include <config.h>
> > +#include <unistd.h>
> > +
> > +/* library headers */
> > +#include "lib/sset.h"
> > +#include "lib/util.h"
> > +
> > +/* OVS includes. */
> > +#include "lib/vswitch-idl.h"
> > +#include "include/openvswitch/shash.h"
> > +#include "openvswitch/vlog.h"
> > +
> > +/* OVN includes. */
> > +#include "binding.h"
> > +#include "lib/ovn-sb-idl.h"
> > +#include "mirror.h"
> > +
> > +VLOG_DEFINE_THIS_MODULE(port_mirror);
> > +
> > +struct ovn_mirror {
> > +    char *name;
> > +    const struct sbrec_mirror *sb_mirror;
> > +    const struct ovsrec_mirror *ovs_mirror;
> > +    struct ovs_list mirror_src_lports;
> > +    struct ovs_list mirror_dst_lports;
> > +};
> > +
> > +struct mirror_lport {
> > +    struct ovs_list list_node;
> > +
> > +    struct local_binding *lbinding;
> > +};
> > +
> > +static struct ovn_mirror *ovn_mirror_create(char *mirror_name);
> > +static void ovn_mirror_add(struct shash *ovn_mirrors,
> > +                           struct ovn_mirror *);
> > +static struct ovn_mirror *ovn_mirror_find(struct shash *ovn_mirrors,
> > +                                          const char *mirror_name);
> > +static void ovn_mirror_delete(struct ovn_mirror *);
> > +static void ovn_mirror_add_lport(struct ovn_mirror *, struct local_binding *);
> > +static void sync_ovn_mirror(struct ovn_mirror *, struct ovsdb_idl_txn *,
> > +                            const struct ovsrec_bridge *);
> > +
> > +static void create_ovs_mirror(struct ovn_mirror *, struct ovsdb_idl_txn *,
> > +                              const struct ovsrec_bridge *);
> > +static void sync_ovs_mirror_ports(struct ovn_mirror *,
> > +                                  const struct ovsrec_bridge *);
> > +static void delete_ovs_mirror(struct ovn_mirror *,
> > +                              const struct ovsrec_bridge *);
> > +static bool should_delete_ovs_mirror(struct ovn_mirror *);
> > +static void set_mirror_iface_options(struct ovsrec_interface *,
> > +                                     const struct sbrec_mirror *);
> > +
> > +static const struct ovsrec_port *get_iface_port(
> > +    const struct ovsrec_interface *, const struct ovsrec_bridge *);
> > +
> > +
> > +void
> > +mirror_register_ovs_idl(struct ovsdb_idl *ovs_idl)
> > +{
> > +    ovsdb_idl_add_column(ovs_idl, &ovsrec_bridge_col_mirrors);
> > +
> > +    ovsdb_idl_add_table(ovs_idl, &ovsrec_table_mirror);
> > +    ovsdb_idl_add_column(ovs_idl, &ovsrec_mirror_col_name);
> > +    ovsdb_idl_add_column(ovs_idl, &ovsrec_mirror_col_output_port);
> > +    ovsdb_idl_add_column(ovs_idl, &ovsrec_mirror_col_select_dst_port);
> > +    ovsdb_idl_add_column(ovs_idl, &ovsrec_mirror_col_select_src_port);
> > +    ovsdb_idl_add_column(ovs_idl, &ovsrec_mirror_col_external_ids);
> > +}
> > +
> > +void
> > +mirror_init(void)
> > +{
> > +}
> > +
> > +void
> > +mirror_destroy(void)
> > +{
> > +}
> > +
> > +void
> > +mirror_run(struct ovsdb_idl_txn *ovs_idl_txn,
> > +           const struct ovsrec_mirror_table *ovs_mirror_table,
> > +           const struct sbrec_mirror_table *sb_mirror_table,
> > +           const struct ovsrec_bridge *br_int,
> > +           struct shash *local_bindings)
> > +{
> > +    if (!ovs_idl_txn) {
> > +        return;
> > +    }
> > +
> > +    struct shash ovn_mirrors = SHASH_INITIALIZER(&ovn_mirrors);
> > +    struct shash tmp_mirrors = SHASH_INITIALIZER(&tmp_mirrors);
> > +
> > +    /* Iterate through sb mirrors and build the 'ovn_mirrors'. */
> > +    const struct sbrec_mirror *sb_mirror;
> > +    SBREC_MIRROR_TABLE_FOR_EACH (sb_mirror, sb_mirror_table) {
> > +        struct ovn_mirror *m = ovn_mirror_create(sb_mirror->name);
> > +        m->sb_mirror = sb_mirror;
> > +        ovn_mirror_add(&ovn_mirrors, m);
> > +    }
> > +
> > +    /* Iterate through ovs mirrors and add to the 'tmp_mirrors'. */
> > +    const struct ovsrec_mirror *ovs_mirror;
> > +    OVSREC_MIRROR_TABLE_FOR_EACH (ovs_mirror, ovs_mirror_table) {
> > +        bool ovn_owned_mirror = smap_get_bool(&ovs_mirror->external_ids,
> > +                                              "ovn-owned", false);
> > +        if (!ovn_owned_mirror) {
> > +            continue;
> > +        }
> > +
> > +        struct ovn_mirror *m = ovn_mirror_find(&ovn_mirrors, ovs_mirror->name);
> > +        if (!m) {
> > +            m = ovn_mirror_create(ovs_mirror->name);
> > +            shash_add(&tmp_mirrors, ovs_mirror->name, m);
> > +        }
> > +        m->ovs_mirror = ovs_mirror;
> > +    }
> > +
> > +    if (shash_is_empty(&ovn_mirrors)) {
> > +        shash_destroy(&ovn_mirrors);
> > +        if (shash_is_empty(&tmp_mirrors)) {
> > +            shash_destroy(&tmp_mirrors);
> > +            return;
> > +        } else {
> > +            goto cleanup;
> > +        }
> > +    }

There is no need to have a shash map 'tmp_mirrors' to store the mirrors which
are in the local OVS db but not in SB DB.

Also there was a bug for the scenario where the user deletes the NB mirror.
In this case, ovn-controller was not deleting the OVS mirrors.  It's because
the OVS mirrors were  not added to the shash map 'ovn_mirrors' and
because of the above 'goto cleanup'.

So I fixed this issue,  updated the bulk mirror test to cover this scenario and
applied the patch with the below changes.

-------------------------------------------------------------------------------------------------------
diff --git a/controller/mirror.c b/controller/mirror.c
index 938ec35429..39d30adf0d 100644
--- a/controller/mirror.c
+++ b/controller/mirror.c
@@ -115,7 +115,7 @@ mirror_run(struct ovsdb_idl_txn *ovs_idl_txn,
         ovn_mirror_add(&ovn_mirrors, m);
     }

-    /* Iterate through ovs mirrors and add to the 'tmp_mirrors'. */
+    /* Iterate through ovs mirrors and add to the 'ovn_mirrors'. */
     const struct ovsrec_mirror *ovs_mirror;
     OVSREC_MIRROR_TABLE_FOR_EACH (ovs_mirror, ovs_mirror_table) {
         bool ovn_owned_mirror = smap_get_bool(&ovs_mirror->external_ids,
@@ -127,19 +127,14 @@ mirror_run(struct ovsdb_idl_txn *ovs_idl_txn,
         struct ovn_mirror *m = ovn_mirror_find(&ovn_mirrors, ovs_mirror->name);
         if (!m) {
             m = ovn_mirror_create(ovs_mirror->name);
-            shash_add(&tmp_mirrors, ovs_mirror->name, m);
+            ovn_mirror_add(&ovn_mirrors, m);
         }
         m->ovs_mirror = ovs_mirror;
     }

     if (shash_is_empty(&ovn_mirrors)) {
         shash_destroy(&ovn_mirrors);
-        if (shash_is_empty(&tmp_mirrors)) {
-            shash_destroy(&tmp_mirrors);
-            return;
-        } else {
-            goto cleanup;
-        }
+        return;
     }

     /* Iterate through the local bindings and if the local binding's 'pb' has
@@ -175,14 +170,6 @@ mirror_run(struct ovsdb_idl_txn *ovs_idl_txn,
     }

     shash_destroy(&ovn_mirrors);
-
-cleanup:
-    SHASH_FOR_EACH_SAFE (node, &tmp_mirrors) {
-        ovn_mirror_delete(node->data);
-        shash_delete(&tmp_mirrors, node);
-    }
-
-    shash_destroy(&tmp_mirrors);
 }

 /* Static functions. */
@@ -199,7 +186,7 @@ ovn_mirror_create(char *mirror_name)
 static void
 ovn_mirror_add(struct shash *ovn_mirrors, struct ovn_mirror *m)
 {
-    shash_add(ovn_mirrors, m->sb_mirror->name, m);
+    shash_add(ovn_mirrors, m->name, m);
 }

 static struct ovn_mirror *

diff --git a/controller/mirror.c b/controller/mirror.c
index 39d30adf0d..6657369665 100644
--- a/controller/mirror.c
+++ b/controller/mirror.c
@@ -218,12 +218,7 @@ ovn_mirror_add_lport(struct ovn_mirror *m, struct
local_binding *lbinding)
     m_lport->lbinding = lbinding;
     if (!strcmp(m->sb_mirror->filter, "from-lport")) {
         ovs_list_push_back(&m->mirror_src_lports, &m_lport->list_node);
-    } else if (!strcmp(m->sb_mirror->filter, "to-lport")) {
-        ovs_list_push_back(&m->mirror_dst_lports, &m_lport->list_node);
     } else {
-        ovs_list_push_back(&m->mirror_src_lports, &m_lport->list_node);
-        m_lport = xzalloc(sizeof *m_lport);
-        m_lport->lbinding = lbinding;
         ovs_list_push_back(&m->mirror_dst_lports, &m_lport->list_node);
     }
 }

diff --git a/tests/ovn.at b/tests/ovn.at
index 305a0e45d5..af7810b130 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -16753,6 +16753,16 @@ OVS_WAIT_UNTIL([test "gre" = "$type"])
 index=$(ovs-vsctl get Interface ovn-mirror0 options:key | grep 123 | wc -l)
 OVS_WAIT_UNTIL([test "1" = "$index"])

+# pause ovn-controller and delete the mirror and make sure that
+# ovn-controller deletes the ovs mirror when it is resumed.
+
+check as hv1 ovn-appctl -t ovn-controller debug/pause
+check ovn-nbctl --wait=sb mirror-del
+check as hv1 ovn-appctl -t ovn-controller debug/resume
+check ovn-nbctl --wait=hv sync
+
+AT_CHECK([as hv1 ovs-vsctl list mirror], [0], [])
+
 OVN_CLEANUP([hv1],[hv2])
 AT_CLEANUP
 ])
diff --git a/NEWS b/NEWS
index 566d6731b3..d46f049435 100644
--- a/NEWS
+++ b/NEWS
@@ -10,7 +10,7 @@ OVN v22.12.0 - xx xxx xxxx
     per-flow IPFIX sampling.
   - Add support for component templates within logical flows and load
     balancers.
-  - Added Support for Remote Port Mirroring.
+  - Add support for remote port mirroring (Experimental).

 OVN v22.09.0 - 16 Sep 2022
 --------------------------------------------------------------------------------------



> > +
> > +    /* Iterate through the local bindings and if the local binding's 'pb' has
> > +     * mirrors associated, add it to the ovn_mirror. */
> > +    struct shash_node *node;
> > +    SHASH_FOR_EACH (node, local_bindings) {
> > +        struct local_binding *lbinding = node->data;
> > +        const struct sbrec_port_binding *pb =
> > +            local_binding_get_primary_pb(local_bindings, lbinding->name);
> > +        if (!pb || !pb->n_mirror_rules) {
> > +            continue;
> > +        }
> > +
> > +        for (size_t i = 0; i < pb->n_mirror_rules; i++) {
> > +            struct ovn_mirror *m = ovn_mirror_find(&ovn_mirrors,
> > +                                                   pb->mirror_rules[i]->name);
> > +            ovs_assert(m);
> > +            ovn_mirror_add_lport(m, lbinding);
> > +        }
> > +    }
> > +
> > +    /* Iterate through the built 'ovn_mirrors' and
> > +     * sync with the local ovsdb i.e.
> > +     * create/update or delete the ovsrec mirror(s). */
> > +     SHASH_FOR_EACH (node, &ovn_mirrors) {
> > +        struct ovn_mirror *m = node->data;
> > +        sync_ovn_mirror(m, ovs_idl_txn, br_int);
> > +     }
> > +
> > +    SHASH_FOR_EACH_SAFE (node, &ovn_mirrors) {
> > +        ovn_mirror_delete(node->data);
> > +        shash_delete(&ovn_mirrors, node);
> > +    }
> > +
> > +    shash_destroy(&ovn_mirrors);
> > +
> > +cleanup:
> > +    SHASH_FOR_EACH_SAFE (node, &tmp_mirrors) {
> > +        ovn_mirror_delete(node->data);
> > +        shash_delete(&tmp_mirrors, node);
> > +    }
> > +
> > +    shash_destroy(&tmp_mirrors);
> > +}
> > +
> > +/* Static functions. */
> > +static struct ovn_mirror *
> > +ovn_mirror_create(char *mirror_name)
> > +{
> > +    struct ovn_mirror *m = xzalloc(sizeof *m);
> > +    m->name = xstrdup(mirror_name);
> > +    ovs_list_init(&m->mirror_src_lports);
> > +    ovs_list_init(&m->mirror_dst_lports);
> > +    return m;
> > +}
> > +
> > +static void
> > +ovn_mirror_add(struct shash *ovn_mirrors, struct ovn_mirror *m)
> > +{
> > +    shash_add(ovn_mirrors, m->sb_mirror->name, m);
> > +}
> > +
> > +static struct ovn_mirror *
> > +ovn_mirror_find(struct shash *ovn_mirrors, const char *mirror_name)
> > +{
> > +    return shash_find_data(ovn_mirrors, mirror_name);
> > +}
> > +
> > +static void
> > +ovn_mirror_delete(struct ovn_mirror *m)
> > +{
> > +    free(m->name);
> > +    struct mirror_lport *m_lport;
> > +    LIST_FOR_EACH_POP (m_lport, list_node, &m->mirror_src_lports) {
> > +        free(m_lport);
> > +    }
> > +
> > +    LIST_FOR_EACH_POP (m_lport, list_node, &m->mirror_dst_lports) {
> > +        free(m_lport);
> > +    }
> > +
> > +    free(m);
> > +}
> > +
> > +static void
> > +ovn_mirror_add_lport(struct ovn_mirror *m, struct local_binding *lbinding)
> > +{
> > +    struct mirror_lport *m_lport = xzalloc(sizeof *m_lport);
> > +    m_lport->lbinding = lbinding;
> > +    if (!strcmp(m->sb_mirror->filter, "from-lport")) {
> > +        ovs_list_push_back(&m->mirror_src_lports, &m_lport->list_node);
> > +    } else if (!strcmp(m->sb_mirror->filter, "to-lport")) {
> > +        ovs_list_push_back(&m->mirror_dst_lports, &m_lport->list_node);
> > +    } else {
> > +        ovs_list_push_back(&m->mirror_src_lports, &m_lport->list_node);
> > +        m_lport = xzalloc(sizeof *m_lport);
> > +        m_lport->lbinding = lbinding;
> > +        ovs_list_push_back(&m->mirror_dst_lports, &m_lport->list_node);
>
> This branch seems to be a remnant from when the series supported
> "both" filter. Should be removed until we are ready to introduce
> support for "both" filter.

Ack.  Fixed this before applying.

Thanks
Numan

>
> > +    }
> > +}
> > +
> > +static void
> > +set_mirror_iface_options(struct ovsrec_interface *iface,
> > +                         const struct sbrec_mirror *sb_mirror)
> > +{
> > +    struct smap options = SMAP_INITIALIZER(&options);
> > +    char *key;
> > +
> > +    key = xasprintf("%ld", (long int) sb_mirror->index);
> > +    smap_add(&options, "remote_ip", sb_mirror->sink);
> > +    smap_add(&options, "key", key);
> > +    if (!strcmp(sb_mirror->type, "erspan")) {
> > +        /* Set the ERSPAN index */
> > +        smap_add(&options, "erspan_idx", key);
> > +        smap_add(&options, "erspan_ver", "1");
> > +    }
> > +    ovsrec_interface_set_options(iface, &options);
> > +
> > +    free(key);
> > +    smap_destroy(&options);
> > +}
> > +
> > +static void
> > +check_and_update_interface_table(const struct sbrec_mirror *sb_mirror,
> > +                                 const struct ovsrec_mirror *ovs_mirror)
> > +{
> > +    char *type;
> > +    struct ovsrec_interface *iface =
> > +                          ovs_mirror->output_port->interfaces[0];
> > +    struct smap *opts = &iface->options;
> > +    const char *erspan_ver = smap_get(opts, "erspan_ver");
> > +    if (erspan_ver) {
> > +        type = "erspan";
> > +    } else {
> > +        type = "gre";
> > +    }
> > +    if (strcmp(type, sb_mirror->type)) {
> > +        ovsrec_interface_set_type(iface, sb_mirror->type);
> > +    }
> > +    set_mirror_iface_options(iface, sb_mirror);
> > +}
> > +
> > +static void
> > +sync_ovn_mirror(struct ovn_mirror *m, struct ovsdb_idl_txn *ovs_idl_txn,
> > +                const struct ovsrec_bridge *br_int)
> > +{
> > +    if (should_delete_ovs_mirror(m)) {
> > +        /* Delete the ovsrec mirror. */
> > +        delete_ovs_mirror(m, br_int);
> > +        return;
> > +    }
> > +
> > +    if (ovs_list_is_empty(&m->mirror_src_lports) &&
> > +            ovs_list_is_empty(&m->mirror_dst_lports)) {
> > +        /* Nothing to do. */
> > +        return;
> > +    }
> > +
> > +    if (m->sb_mirror && !m->ovs_mirror) {
> > +        create_ovs_mirror(m, ovs_idl_txn, br_int);
> > +    } else {
> > +        check_and_update_interface_table(m->sb_mirror, m->ovs_mirror);
> > +    }
> > +
> > +    sync_ovs_mirror_ports(m, br_int);
> > +}
> > +
> > +static bool
> > +should_delete_ovs_mirror(struct ovn_mirror *m)
> > +{
> > +    if (!m->ovs_mirror) {
> > +        return false;
> > +    }
> > +
> > +    if (m->ovs_mirror && !m->sb_mirror) {
> > +        return true;
> > +    }
> > +
> > +    return (ovs_list_is_empty(&m->mirror_src_lports) &&
> > +            ovs_list_is_empty(&m->mirror_dst_lports));
> > +}
> > +
> > +static const struct ovsrec_port *
> > +get_iface_port(const struct ovsrec_interface *iface,
> > +               const struct ovsrec_bridge *br_int)
> > +{
> > +    for (size_t i = 0; i < br_int->n_ports; i++) {
> > +        const struct ovsrec_port *p = br_int->ports[i];
> > +        for (size_t j = 0; j < p->n_interfaces; j++) {
> > +            if (!strcmp(iface->name, p->interfaces[j]->name)) {
> > +                return p;
> > +            }
> > +        }
> > +    }
> > +    return NULL;
> > +}
> > +
> > +static void
> > +create_ovs_mirror(struct ovn_mirror *m, struct ovsdb_idl_txn *ovs_idl_txn,
> > +                  const struct ovsrec_bridge *br_int)
> > +{
> > +    struct ovsrec_interface *iface = ovsrec_interface_insert(ovs_idl_txn);
> > +    char *port_name = xasprintf("ovn-%s", m->name);
> > +
> > +    ovsrec_interface_set_name(iface, port_name);
> > +    ovsrec_interface_set_type(iface, m->sb_mirror->type);
> > +    set_mirror_iface_options(iface, m->sb_mirror);
> > +
> > +    struct ovsrec_port *port = ovsrec_port_insert(ovs_idl_txn);
> > +    ovsrec_port_set_name(port, port_name);
> > +    ovsrec_port_set_interfaces(port, &iface, 1);
> > +    ovsrec_bridge_update_ports_addvalue(br_int, port);
> > +
> > +    free(port_name);
> > +
> > +    m->ovs_mirror = ovsrec_mirror_insert(ovs_idl_txn);
> > +    ovsrec_mirror_set_name(m->ovs_mirror, m->name);
> > +    ovsrec_mirror_set_output_port(m->ovs_mirror, port);
> > +
> > +    const struct smap external_ids =
> > +        SMAP_CONST1(&external_ids, "ovn-owned", "true");
> > +    ovsrec_mirror_set_external_ids(m->ovs_mirror, &external_ids);
> > +
> > +    ovsrec_bridge_update_mirrors_addvalue(br_int, m->ovs_mirror);
> > +}
> > +
> > +static void
> > +sync_ovs_mirror_ports(struct ovn_mirror *m, const struct ovsrec_bridge *br_int)
> > +{
> > +    struct mirror_lport *m_lport;
> > +
> > +    if (ovs_list_is_empty(&m->mirror_src_lports)) {
> > +        ovsrec_mirror_set_select_src_port(m->ovs_mirror, NULL, 0);
> > +    } else {
> > +        size_t n_lports = ovs_list_size(&m->mirror_src_lports);
> > +        struct ovsrec_port **ovs_ports = xmalloc(sizeof *ovs_ports * n_lports);
> > +
> > +        size_t i = 0;
> > +        LIST_FOR_EACH (m_lport, list_node, &m->mirror_src_lports) {
> > +            const struct ovsrec_port *p =
> > +                get_iface_port(m_lport->lbinding->iface, br_int);
> > +            ovs_assert(p);
> > +            ovs_ports[i++] = (struct ovsrec_port *) p;
> > +        }
> > +
> > +        ovsrec_mirror_set_select_src_port(m->ovs_mirror, ovs_ports, n_lports);
> > +        free(ovs_ports);
> > +    }
> > +
> > +    if (ovs_list_is_empty(&m->mirror_dst_lports)) {
> > +        ovsrec_mirror_set_select_dst_port(m->ovs_mirror, NULL, 0);
> > +    } else {
> > +        size_t n_lports = ovs_list_size(&m->mirror_dst_lports);
> > +        struct ovsrec_port **ovs_ports = xmalloc(sizeof *ovs_ports * n_lports);
> > +
> > +        size_t i = 0;
> > +        LIST_FOR_EACH (m_lport, list_node, &m->mirror_dst_lports) {
> > +            const struct ovsrec_port *p =
> > +                get_iface_port(m_lport->lbinding->iface, br_int);
> > +            ovs_assert(p);
> > +            ovs_ports[i++] = (struct ovsrec_port *) p;
> > +        }
> > +
> > +        ovsrec_mirror_set_select_dst_port(m->ovs_mirror, ovs_ports, n_lports);
> > +        free(ovs_ports);
> > +    }
> > +}
> > +
> > +static void
> > +delete_ovs_mirror(struct ovn_mirror *m, const struct ovsrec_bridge *br_int)
> > +{
> > +    ovsrec_bridge_update_ports_delvalue(br_int, m->ovs_mirror->output_port);
> > +    ovsrec_bridge_update_mirrors_delvalue(br_int, m->ovs_mirror);
> > +    ovsrec_port_delete(m->ovs_mirror->output_port);
> > +    ovsrec_mirror_delete(m->ovs_mirror);
> > +}
> > diff --git a/controller/mirror.h b/controller/mirror.h
> > new file mode 100644
> > index 000000000..a79de109d
> > --- /dev/null
> > +++ b/controller/mirror.h
> > @@ -0,0 +1,33 @@
> > +/* Copyright (c) 2022 Red Hat, Inc.
> > + *
> > + * Licensed under the Apache License, Version 2.0 (the "License");
> > + * you may not use this file except in compliance with the License.
> > + * You may obtain a copy of the License at:
> > + *
> > + *     http://www.apache.org/licenses/LICENSE-2.0
> > + *
> > + * Unless required by applicable law or agreed to in writing, software
> > + * distributed under the License is distributed on an "AS IS" BASIS,
> > + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
> > + * See the License for the specific language governing permissions and
> > + * limitations under the License.
> > + */
> > +
> > +#ifndef OVN_MIRROR_H
> > +#define OVN_MIRROR_H 1
> > +
> > +struct ovsdb_idl_txn;
> > +struct ovsrec_mirror_table;
> > +struct sbrec_mirror_table;
> > +struct ovsrec_bridge;
> > +struct shash;
> > +
> > +void mirror_register_ovs_idl(struct ovsdb_idl *);
> > +void mirror_init(void);
> > +void mirror_destroy(void);
> > +void mirror_run(struct ovsdb_idl_txn *ovs_idl_txn,
> > +                const struct ovsrec_mirror_table *,
> > +                const struct sbrec_mirror_table *,
> > +                const struct ovsrec_bridge *,
> > +                struct shash *local_bindings);
> > +#endif
> > diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c
> > index f0fd24820..470891bb2 100644
> > --- a/controller/ovn-controller.c
> > +++ b/controller/ovn-controller.c
> > @@ -78,6 +78,7 @@
> >  #include "lib/inc-proc-eng.h"
> >  #include "lib/ovn-l7.h"
> >  #include "hmapx.h"
> > +#include "mirror.h"
> >
> >  VLOG_DEFINE_THIS_MODULE(main);
> >
> > @@ -1004,6 +1005,7 @@ ctrl_register_ovs_idl(struct ovsdb_idl *ovs_idl)
> >      ovsdb_idl_track_add_column(ovs_idl, &ovsrec_port_col_name);
> >      ovsdb_idl_track_add_column(ovs_idl, &ovsrec_port_col_interfaces);
> >      ovsdb_idl_track_add_column(ovs_idl, &ovsrec_port_col_external_ids);
> > +    mirror_register_ovs_idl(ovs_idl);
> >  }
> >
> >  #define SB_NODES \
> > @@ -3926,6 +3928,7 @@ main(int argc, char *argv[])
> >      patch_init();
> >      pinctrl_init();
> >      lflow_init();
> > +    mirror_init();
> >      vif_plug_provider_initialize();
> >
> >      /* Connect to OVS OVSDB instance. */
> > @@ -4638,6 +4641,11 @@ main(int argc, char *argv[])
> >                                      &runtime_data->local_active_ports_ras);
> >                          stopwatch_stop(PINCTRL_RUN_STOPWATCH_NAME,
> >                                         time_msec());
> > +                        mirror_run(ovs_idl_txn,
> > +                                   ovsrec_mirror_table_get(ovs_idl_loop.idl),
> > +                                   sbrec_mirror_table_get(ovnsb_idl_loop.idl),
> > +                                   br_int,
> > +                                   &runtime_data->lbinding_data.bindings);
> >                          /* Updating monitor conditions if runtime data or
> >                           * logical datapath goups changed. */
> >                          if (engine_node_changed(&en_runtime_data)
> > @@ -4884,6 +4892,7 @@ loop_done:
> >      pinctrl_destroy();
> >      binding_destroy();
> >      patch_destroy();
> > +    mirror_destroy();
> >      if_status_mgr_destroy(if_mgr);
> >      shash_destroy(&vif_plug_deleted_iface_ids);
> >      shash_destroy(&vif_plug_changed_iface_ids);
> > diff --git a/tests/ovn.at b/tests/ovn.at
> > index f3bd53242..305a0e45d 100644
> > --- a/tests/ovn.at
> > +++ b/tests/ovn.at
> > @@ -16242,6 +16242,520 @@ OVN_CLEANUP([hv1], [hv2])
> >  AT_CLEANUP
> >  ])
> >
> > +OVN_FOR_EACH_NORTHD([
> > +AT_SETUP([Mirror])
> > +AT_KEYWORDS([Mirror])
> > +ovn_start
> > +
> > +# Logical network:
> > +# One LR - R1 has switch ls1 (191.168.1.0/24) connected to it,
> > +# and has switch ls2 (172.16.1.0/24) connected to it.
> > +
> > +ovn-nbctl lr-add R1
> > +
> > +ovn-nbctl ls-add ls1
> > +ovn-nbctl ls-add ls2
> > +
> > +# Connect ls1 to R1
> > +ovn-nbctl lrp-add R1 ls1 00:00:00:01:02:f1 192.168.1.1/24
> > +ovn-nbctl lsp-add ls1 rp-ls1 -- set Logical_Switch_Port rp-ls1 \
> > +    type=router options:router-port=ls1 addresses=\"00:00:00:01:02:f1\"
> > +
> > +# Connect ls2 to R1
> > +ovn-nbctl lrp-add R1 ls2 00:00:00:01:02:f2 172.16.1.1/24
> > +ovn-nbctl lsp-add ls2 rp-ls2 -- set Logical_Switch_Port rp-ls2 \
> > +    type=router options:router-port=ls2 addresses=\"00:00:00:01:02:f2\"
> > +
> > +# Create logical port ls1-lp1 in ls1
> > +ovn-nbctl lsp-add ls1 ls1-lp1 \
> > +-- lsp-set-addresses ls1-lp1 "00:00:00:01:02:03 192.168.1.2"
> > +
> > +# Create logical port ls2-lp1 in ls2
> > +ovn-nbctl lsp-add ls2 ls2-lp1 \
> > +-- lsp-set-addresses ls2-lp1 "00:00:00:01:02:04 172.16.1.2"
> > +
> > +ovn-nbctl lsp-add ls1 ln-public
> > +ovn-nbctl lsp-set-type ln-public localnet
> > +ovn-nbctl lsp-set-addresses ln-public unknown
> > +ovn-nbctl lsp-set-options ln-public network_name=public
> > +
> > +# Create one hypervisor and create OVS ports corresponding to logical ports.
> > +net_add n1
> > +
> > +sim_add hv1
> > +as hv1
> > +ovs-vsctl add-br br-phys -- set bridge br-phys other-config:hwaddr=\"00:00:00:01:02:00\"
> > +ovn_attach n1 br-phys 192.168.1.11
> > +
> > +ovs-vsctl -- add-port br-int vif1 -- \
> > +    set interface vif1 external-ids:iface-id=ls1-lp1 \
> > +    options:tx_pcap=hv1/vif1-tx.pcap \
> > +    options:rxq_pcap=hv1/vif1-rx.pcap \
> > +    ofport-request=1
> > +
> > +ovs-vsctl -- add-port br-int vif2 -- \
> > +    set interface vif2 external-ids:iface-id=ls2-lp1 \
> > +    options:tx_pcap=hv1/vif2-tx.pcap \
> > +    options:rxq_pcap=hv1/vif2-rx.pcap \
> > +    ofport-request=1
> > +
> > +ovs-vsctl set open . external-ids:ovn-bridge-mappings=public:br-phys
> > +
> > +# Allow some time for ovn-northd and ovn-controller to catch up.
> > +wait_for_ports_up
> > +check ovn-nbctl --wait=hv sync
> > +ovn-nbctl dump-flows > sbflows
> > +AT_CAPTURE_FILE([sbflows])
> > +
> > +for i in 1 2; do
> > +    : > vif$i.expected
> > +done
> > +
> > +net_add n2
> > +
> > +sim_add hv2
> > +as hv2
> > +ovs-vsctl add-br br-phys -- set bridge br-phys other-config:hwaddr=\"00:00:00:02:02:00\"
> > +ovn_attach n2 br-phys 192.168.1.12
> > +
> > +OVN_POPULATE_ARP
> > +
> > +as hv1
> > +
> > +# test_ipv4_icmp_request INPORT ETH_SRC ETH_DST IPV4_SRC IPV4_DST IP_CHKSUM ICMP_CHKSUM [EXP_IP_CHKSUM EXP_ICMP_CHKSUM] ENCAP_TYPE FILTER
> > +#
> > +# Causes a packet to be received on INPORT.  The packet is an ICMPv4
> > +# request with ETH_SRC, ETH_DST, IPV4_SRC, IPV4_DST, IP_CHSUM and
> > +# ICMP_CHKSUM as specified.  If EXP_IP_CHKSUM and EXP_ICMP_CHKSUM are
> > +# provided, then it should be the ip and icmp checksums of the packet
> > +# responded; otherwise, no reply is expected.
> > +# In the absence of an ip checksum calculation helpers, this relies
> > +# on the caller to provide the checksums for the ip and icmp headers.
> > +# XXX This should be more systematic.
> > +#
> > +# INPORT is an lport number, e.g. 11 for vif11.
> > +# ETH_SRC and ETH_DST are each 12 hex digits.
> > +# IPV4_SRC and IPV4_DST are each 8 hex digits.
> > +# IP_CHSUM and ICMP_CHKSUM are each 4 hex digits.
> > +# EXP_IP_CHSUM and EXP_ICMP_CHKSUM are each 4 hex digits.
> > +# ENCAP_TYPE - gre or erspan
> > +# FILTER - Mirror Filter - to-lport / from-lport
> > +test_ipv4_icmp_request() {
> > +    local inport=$1 eth_src=$2 eth_dst=$3 ipv4_src=$4 ipv4_dst=$5 ip_chksum=$6 icmp_chksum=$7
> > +    local exp_ip_chksum=$8 exp_icmp_chksum=$9 mirror_encap_type=${10} mirror_filter=${11}
> > +    shift; shift; shift; shift; shift; shift; shift
> > +    shift; shift; shift; shift;
> > +
> > +    # Use ttl to exercise section 4.2.2.9 of RFC1812
> > +    local ip_ttl=02
> > +    local icmp_id=5fbf
> > +    local icmp_seq=0001
> > +    local icmp_data=$(seq 1 56 | xargs printf "%02x")
> > +    local icmp_type_code_request=0800
> > +    local icmp_payload=${icmp_type_code_request}${icmp_chksum}${icmp_id}${icmp_seq}${icmp_data}
> > +    local packet=${eth_dst}${eth_src}08004500005400004000${ip_ttl}01${ip_chksum}${ipv4_src}${ipv4_dst}${icmp_payload}
> > +
> > +    as hv1 ovs-appctl netdev-dummy/receive vif$inport $packet
> > +
> > +    # Expect to receive the reply, if any. In same port where packet was sent.
> > +    # Note: src and dst fields are expected to be reversed.
> > +    local icmp_type_code_response=0000
> > +    local reply_icmp_ttl=fe
> > +    local reply_icmp_payload=${icmp_type_code_response}${exp_icmp_chksum}${icmp_id}${icmp_seq}${icmp_data}
> > +    local reply=${eth_src}${eth_dst}08004500005400004000${reply_icmp_ttl}01${exp_ip_chksum}${ipv4_dst}${ipv4_src}${reply_icmp_payload}
> > +    echo $reply >> vif$inport.expected
> > +    local remote_mac=000000020200
> > +    local local_mac=000000010200
> > +    if test ${mirror_encap_type} = "gre" ; then
> > +        local ipv4_gre=4500007e00004000402fb6e9c0a8010bc0a8010c2000655800000000
> > +        if test ${mirror_filter} = "to-lport" ; then
> > +            local mirror=${remote_mac}${local_mac}0800${ipv4_gre}${reply}
> > +        elif test ${mirror_filter} = "from-lport" ; then
> > +            local mirror=${remote_mac}${local_mac}0800${ipv4_gre}${packet}
> > +        fi
> > +    elif test ${mirror_encap_type} = "erspan" ; then
> > +        local ipv4_erspan=4500008600004000402fb6e1c0a8010bc0a8010c
> > +        local erspan_seq0=100088be000000001000000000000000
> > +        local erspan_seq1=100088be000000011000000000000000
> > +        if test ${mirror_filter} = "to-lport" ; then
> > +            local mirror=${remote_mac}${local_mac}0800${ipv4_erspan}${erspan_seq0}${reply}
> > +        elif test ${mirror_filter} = "from-lport" ; then
> > +            local mirror=${remote_mac}${local_mac}0800${ipv4_erspan}${erspan_seq1}${packet}
> > +        fi
> > +    fi
> > +    echo $mirror >> br-phys_n1.expected
> > +
> > +}
> > +
> > +# Set IPs
> > +rtr_l2_ip=$(ip_to_hex 172 16 1 1)
> > +l1_ip=$(ip_to_hex 192 168 1 2)
> > +
> > +check ovn-nbctl mirror-add mirror0 gre 0 to-lport 192.168.1.12
> > +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
> > +
> > +# Send ping packet and check for mirrored packet of the reply
> > +test_ipv4_icmp_request 1 000000010203 0000000102f1 $l1_ip $rtr_l2_ip 0000 8510 03ff 8d10 "gre" "to-lport"
> > +
> > +OVN_CHECK_PACKETS_CONTAIN([hv1/br-phys_n1-tx.pcap], [br-phys_n1.expected])
> > +OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [vif1.expected])
> > +
> > +as hv1 reset_pcap_file vif1 hv1/vif1
> > +as hv1 reset_pcap_file br-phys_n1 hv1/br-phys_n1
> > +rm -f br-phys_n1.expected
> > +rm -f vif1.expected
> > +
> > +check ovn-nbctl set mirror . type=erspan
> > +
> > +# Send ping packet and check for mirrored packet of the reply
> > +test_ipv4_icmp_request 1 000000010203 0000000102f1 $l1_ip $rtr_l2_ip 0000 8510 03ff 8d10 "erspan" "to-lport"
> > +
> > +OVN_CHECK_PACKETS_CONTAIN([hv1/br-phys_n1-tx.pcap], [br-phys_n1.expected])
> > +OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [vif1.expected])
> > +
> > +as hv1 reset_pcap_file vif1 hv1/vif1
> > +as hv1 reset_pcap_file br-phys_n1 hv1/br-phys_n1
> > +rm -f br-phys_n1.expected
> > +rm -f vif1.expected
> > +
> > +check ovn-nbctl set mirror . filter=from-lport
> > +
> > +# Send ping packet and check for mirrored packet of the request
> > +test_ipv4_icmp_request 1 000000010203 0000000102f1 $l1_ip $rtr_l2_ip 0000 8510 03ff 8d10 "erspan" "from-lport"
> > +
> > +OVN_CHECK_PACKETS_CONTAIN([hv1/br-phys_n1-tx.pcap], [br-phys_n1.expected])
> > +OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [vif1.expected])
> > +
> > +as hv1 reset_pcap_file vif1 hv1/vif1
> > +as hv1 reset_pcap_file br-phys_n1 hv1/br-phys_n1
> > +rm -f br-phys_n1.expected
> > +rm -f vif1.expected
> > +
> > +check ovn-nbctl set mirror . type=gre
> > +
> > +# Send ping packet and check for mirrored packet of the request
> > +test_ipv4_icmp_request 1 000000010203 0000000102f1 $l1_ip $rtr_l2_ip 0000 8510 03ff 8d10 "gre" "from-lport"
> > +
> > +OVN_CHECK_PACKETS_CONTAIN([hv1/br-phys_n1-tx.pcap], [br-phys_n1.expected])
> > +OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [vif1.expected])
> > +
> > +echo "---------OVN NB Mirror-----"
> > +ovn-nbctl mirror-list
> > +
> > +echo "---------OVS Mirror----"
> > +ovs-vsctl list Mirror
> > +
> > +echo "-----------------------"
> > +
> > +echo "Verifying Mirror deletion in OVS"
> > +# Set vif1 iface-id such that OVN releases port binding
> > +check ovs-vsctl set interface vif1 external_ids:iface-id=foo
> > +check ovn-nbctl --wait=hv sync
> > +
> > +AT_CHECK([as hv1 ovs-vsctl list Mirror], [0], [dnl
> > +])
> > +
> > +# Set vif1 iface-id back to ls1-lp1
> > +check ovs-vsctl set interface vif1 external_ids:iface-id=ls1-lp1
> > +check ovn-nbctl --wait=hv sync
> > +
> > +OVS_WAIT_UNTIL([test $(as hv1 ovs-vsctl get Mirror mirror0 name) = "mirror0"])
> > +
> > +# Delete vif1 so that OVN releases port binding
> > +check ovs-vsctl del-port br-int vif1
> > +check ovn-nbctl --wait=hv sync
> > +
> > +OVS_WAIT_UNTIL([test 0 = $(as hv1 ovs-vsctl list Mirror | wc -l)])
> > +
> > +OVN_CLEANUP([hv1], [hv2])
> > +AT_CLEANUP
> > +])
> > +
> > +OVN_FOR_EACH_NORTHD([
> > +AT_SETUP([Mirror test bulk updates])
> > +AT_KEYWORDS([Mirror test bulk updates])
> > +ovn_start
> > +
> > +# Logical network:
> > +# One LR - R1 has switch ls1 (191.168.1.0/24) connected to it,
> > +# and has switch ls2 (172.16.1.0/24) connected to it.
> > +
> > +ovn-nbctl lr-add R1
> > +
> > +ovn-nbctl ls-add ls1
> > +ovn-nbctl ls-add ls2
> > +
> > +# Connect ls1 to R1
> > +ovn-nbctl lrp-add R1 ls1 00:00:00:01:02:f1 192.168.1.1/24
> > +ovn-nbctl lsp-add ls1 rp-ls1 -- set Logical_Switch_Port rp-ls1 \
> > +    type=router options:router-port=ls1 addresses=\"00:00:00:01:02:f1\"
> > +
> > +# Connect ls2 to R1
> > +ovn-nbctl lrp-add R1 ls2 00:00:00:01:02:f2 172.16.1.1/24
> > +ovn-nbctl lsp-add ls2 rp-ls2 -- set Logical_Switch_Port rp-ls2 \
> > +    type=router options:router-port=ls2 addresses=\"00:00:00:01:02:f2\"
> > +
> > +# Create logical port ls1-lp1 in ls1
> > +ovn-nbctl lsp-add ls1 ls1-lp1 \
> > +-- lsp-set-addresses ls1-lp1 "00:00:00:01:02:03 192.168.1.2"
> > +
> > +# Create logical port ls1-lp2 in ls1
> > +ovn-nbctl lsp-add ls1 ls1-lp2 \
> > +-- lsp-set-addresses ls1-lp2 "00:00:00:01:03:03 192.168.1.3"
> > +
> > +# Create logical port ls2-lp1 in ls2
> > +ovn-nbctl lsp-add ls2 ls2-lp1 \
> > +-- lsp-set-addresses ls2-lp1 "00:00:00:01:02:04 172.16.1.2"
> > +
> > +# Create logical port ls2-lp2 in ls2
> > +ovn-nbctl lsp-add ls2 ls2-lp2 \
> > +-- lsp-set-addresses ls2-lp2 "00:00:00:01:03:04 172.16.1.3"
> > +
> > +ovn-nbctl lsp-add ls1 ln-public
> > +ovn-nbctl lsp-set-type ln-public localnet
> > +ovn-nbctl lsp-set-addresses ln-public unknown
> > +ovn-nbctl lsp-set-options ln-public network_name=public
> > +
> > +# Create 2 hypervisors and create OVS ports corresponding to logical ports for hv1.
> > +net_add n1
> > +
> > +sim_add hv1
> > +as hv1
> > +ovs-vsctl add-br br-phys -- set bridge br-phys other-config:hwaddr=\"00:00:00:01:02:00\"
> > +ovn_attach n1 br-phys 192.168.1.11
> > +
> > +net_add n2
> > +
> > +sim_add hv2
> > +as hv2
> > +ovs-vsctl add-br br-phys -- set bridge br-phys other-config:hwaddr=\"00:00:00:02:02:00\"
> > +ovn_attach n2 br-phys 192.168.1.12
> > +
> > +OVN_POPULATE_ARP
> > +
> > +as hv1
> > +
> > +ovs-vsctl -- add-port br-int vif1 -- \
> > +    set interface vif1 external-ids:iface-id=ls1-lp1
> > +
> > +ovs-vsctl -- add-port br-int vif2 -- \
> > +    set interface vif2 external-ids:iface-id=ls2-lp1
> > +
> > +ovs-vsctl -- add-port br-int vif3 -- \
> > +    set interface vif3 external-ids:iface-id=ls1-lp2
> > +
> > +ovs-vsctl -- add-port br-int vif4 -- \
> > +    set interface vif4 external-ids:iface-id=ls2-lp2
> > +
> > +ovs-vsctl set open . external-ids:ovn-bridge-mappings=public:br-phys
> > +
> > +# Allow some time for ovn-northd and ovn-controller to catch up.
> > +wait_for_ports_up
> > +check ovn-nbctl --wait=hv sync
> > +ovn-nbctl dump-flows > sbflows
> > +AT_CAPTURE_FILE([sbflows])
> > +
> > +check ovn-nbctl mirror-add mirror0 gre 0 to-lport 192.168.1.12
> > +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
> > +check ovn-nbctl --wait=hv sync
> > +origA=$(ovs-vsctl get Mirror mirror0 select_dst_port)
> > +check ovn-nbctl lsp-attach-mirror ls2-lp1 mirror0
> > +
> > +check ovn-nbctl mirror-add mirror1 gre 1 to-lport 192.168.1.12
> > +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror1
> > +check ovn-nbctl --wait=hv sync
> > +origB=$(ovs-vsctl get Mirror mirror1 select_dst_port)
> > +check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror1
> > +
> > +check ovn-nbctl --wait=hv sync
> > +
> > +as hv1
> > +orig1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
> > +orig2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
> > +
> > +check ovn-nbctl mirror-del
> > +check ovn-nbctl --wait=hv sync
> > +
> > +check ovn-nbctl mirror-add mirror0 gre 0 to-lport 192.168.1.12
> > +check ovn-nbctl mirror-add mirror1 gre 1 to-lport 192.168.1.12
> > +
> > +# Attaches multiple mirrors
> > +
> > +check as hv1 ovn-appctl -t ovn-controller debug/pause
> > +check ovn-nbctl lsp-attach-mirror ls2-lp1 mirror0
> > +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
> > +check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror1
> > +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror1
> > +check as hv1 ovn-appctl -t ovn-controller debug/resume
> > +
> > +check ovn-nbctl --wait=hv sync
> > +
> > +as hv1
> > +new1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
> > +new2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
> > +
> > +AT_CHECK([test "$orig1" = "$new1"], [0], [])
> > +
> > +AT_CHECK([test "$orig2" = "$new2"], [0], [])
> > +
> > +# Equal detaches and attaches
> > +check as hv1 ovn-appctl -t ovn-controller debug/pause
> > +check ovn-nbctl lsp-detach-mirror ls1-lp1 mirror0
> > +check ovn-nbctl lsp-detach-mirror ls2-lp1 mirror0
> > +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror0
> > +check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror0
> > +
> > +check ovn-nbctl lsp-detach-mirror ls1-lp2 mirror1
> > +check ovn-nbctl lsp-detach-mirror ls2-lp2 mirror1
> > +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror1
> > +check ovn-nbctl lsp-attach-mirror ls2-lp1 mirror1
> > +check as hv1 ovn-appctl -t ovn-controller debug/resume
> > +
> > +check ovn-nbctl --wait=hv sync
> > +
> > +# Make sure that ovn-controller has not asserted.
> > +AT_CHECK([kill -0 $(cat hv1/ovn-controller.pid)])
> > +
> > +as hv1
> > +new1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
> > +new2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
> > +
> > +AT_CHECK([test "$orig1" = "$new2"], [0], [])
> > +
> > +AT_CHECK([test "$orig2" = "$new1"], [0], [])
> > +
> > +# Detaches more than attaches
> > +check as hv1 ovn-appctl -t ovn-controller debug/pause
> > +check ovn-nbctl lsp-detach-mirror ls1-lp1 mirror1
> > +check ovn-nbctl lsp-detach-mirror ls2-lp1 mirror1
> > +check ovn-nbctl lsp-detach-mirror ls1-lp2 mirror0
> > +check ovn-nbctl lsp-detach-mirror ls2-lp2 mirror0
> > +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
> > +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror1
> > +check as hv1 ovn-appctl -t ovn-controller debug/resume
> > +
> > +check ovn-nbctl --wait=hv sync
> > +# Make sure that ovn-controller has not asserted.
> > +AT_CHECK([kill -0 $(cat hv1/ovn-controller.pid)])
> > +
> > +as hv1
> > +new1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
> > +new2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
> > +
> > +AT_CHECK([test "$origA" = "$new1"], [0], [])
> > +
> > +AT_CHECK([test "$origB" = "$new2"], [0], [])
> > +
> > +# Attaches more than detaches
> > +check as hv1 ovn-appctl -t ovn-controller debug/pause
> > +check ovn-nbctl lsp-detach-mirror ls1-lp1 mirror0
> > +check ovn-nbctl lsp-detach-mirror ls1-lp2 mirror1
> > +check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror0
> > +check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror0
> > +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror1
> > +check ovn-nbctl lsp-attach-mirror ls2-lp1 mirror1
> > +check as hv1 ovn-appctl -t ovn-controller debug/resume
> > +
> > +check ovn-nbctl --wait=hv sync
> > +# Make sure that ovn-controller has not asserted.
> > +AT_CHECK([kill -0 $(cat hv1/ovn-controller.pid)])
> > +
> > +as hv1
> > +new1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
> > +new2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
> > +
> > +AT_CHECK([test "$orig1" = "$new2"], [0], [])
> > +
> > +AT_CHECK([test "$orig2" = "$new1"], [0], [])
> > +
> > +# Detaches all
> > +check as hv1 ovn-appctl -t ovn-controller debug/pause
> > +check ovn-nbctl lsp-detach-mirror ls1-lp2 mirror0
> > +check ovn-nbctl lsp-detach-mirror ls2-lp2 mirror0
> > +check ovn-nbctl lsp-detach-mirror ls1-lp1 mirror1
> > +check ovn-nbctl lsp-detach-mirror ls2-lp1 mirror1
> > +check as hv1 ovn-appctl -t ovn-controller debug/resume
> > +check ovn-nbctl --wait=hv sync
> > +
> > +OVS_WAIT_UNTIL([test 0 = $(as hv1 ovs-vsctl list Mirror | wc -l)])
> > +
> > +check ovn-nbctl mirror-add mirror2 gre 2 to-lport 192.168.1.12
> > +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror2
> > +check ovn-nbctl --wait=hv sync
> > +
> > +# Attaches SAME port to multiple mirrors
> > +# and detaches it from existing mirror.
> > +# More attach than detach
> > +
> > +check as hv1 ovn-appctl -t ovn-controller debug/pause
> > +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
> > +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror1
> > +check ovn-nbctl lsp-detach-mirror ls1-lp1 mirror2
> > +check as hv1 ovn-appctl -t ovn-controller debug/resume
> > +
> > +check ovn-nbctl --wait=hv sync
> > +
> > +as hv1
> > +new1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
> > +new2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
> > +
> > +AT_CHECK([test "$origA" = "$new1"], [0], [])
> > +AT_CHECK([test "$origA" = "$new2"], [0], [])
> > +OVS_WAIT_UNTIL([test 0 = $(as hv1 ovs-vsctl list Mirror mirror2 | wc -l)])
> > +
> > +check ovn-nbctl mirror-add mirror3 gre 3 to-lport 192.168.1.12
> > +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror3
> > +check ovn-nbctl --wait=hv sync
> > +
> > +# Detaches SAME port from multiple mirrors
> > +# and attaches it to existing mirror.
> > +# More detach than attach
> > +
> > +check as hv1 ovn-appctl -t ovn-controller debug/pause
> > +check ovn-nbctl lsp-detach-mirror ls1-lp1 mirror0
> > +check ovn-nbctl lsp-detach-mirror ls1-lp1 mirror3
> > +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror2
> > +check as hv1 ovn-appctl -t ovn-controller debug/resume
> > +
> > +check ovn-nbctl --wait=hv sync
> > +
> > +as hv1
> > +new1=$(ovs-vsctl get Mirror mirror1 select_dst_port)
> > +new2=$(ovs-vsctl get Mirror mirror2 select_dst_port)
> > +
> > +AT_CHECK([test "$origA" = "$new1"], [0], [])
> > +AT_CHECK([test "$origA" = "$new2"], [0], [])
> > +OVS_WAIT_UNTIL([test 0 = $(as hv1 ovs-vsctl list Mirror mirror0 | wc -l)])
> > +OVS_WAIT_UNTIL([test 0 = $(as hv1 ovs-vsctl list Mirror mirror3 | wc -l)])
> > +
> > +check ovn-nbctl mirror-del
> > +check ovn-nbctl --wait=hv sync
> > +check ovn-nbctl mirror-add mirror0 erspan 0 to-lport 192.168.1.12
> > +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
> > +check ovn-nbctl --wait=hv sync
> > +
> > +# Make sure different fields of mirror resource set from OVN
> > +# propagates to OVS correctly
> > +
> > +check as hv1 ovn-appctl -t ovn-controller debug/pause
> > +check ovn-nbctl set mirror . filter=from-lport
> > +check ovn-nbctl set mirror . type=gre
> > +check ovn-nbctl set mirror . index=123
> > +check as hv1 ovn-appctl -t ovn-controller debug/resume
> > +
> > +check ovn-nbctl --wait=hv sync
> > +
> > +as hv1
> > +new1=$(ovs-vsctl get Mirror mirror0 select_src_port)
> > +AT_CHECK([test "$origA" = "$new1"], [0], [])
> > +type=$(ovs-vsctl get Interface ovn-mirror0 type)
> > +OVS_WAIT_UNTIL([test "gre" = "$type"])
> > +index=$(ovs-vsctl get Interface ovn-mirror0 options:key | grep 123 | wc -l)
> > +OVS_WAIT_UNTIL([test "1" = "$index"])
> > +
> > +OVN_CLEANUP([hv1],[hv2])
> > +AT_CLEANUP
> > +])
> >
> >  OVN_FOR_EACH_NORTHD([
> >  AT_SETUP([Port Groups])
> > --
> > 2.31.1
> >
>
diff mbox series

Patch

diff --git a/NEWS b/NEWS
index 7d1e49cf1..566d6731b 100644
--- a/NEWS
+++ b/NEWS
@@ -10,6 +10,7 @@  OVN v22.12.0 - xx xxx xxxx
     per-flow IPFIX sampling.
   - Add support for component templates within logical flows and load
     balancers.
+  - Added Support for Remote Port Mirroring.
 
 OVN v22.09.0 - 16 Sep 2022
 --------------------------
diff --git a/controller/automake.mk b/controller/automake.mk
index c2ab1bbe6..334672b4d 100644
--- a/controller/automake.mk
+++ b/controller/automake.mk
@@ -41,7 +41,9 @@  controller_ovn_controller_SOURCES = \
 	controller/ovsport.h \
 	controller/ovsport.c \
 	controller/vif-plug.h \
-	controller/vif-plug.c
+	controller/vif-plug.c \
+	controller/mirror.h \
+	controller/mirror.c
 
 controller_ovn_controller_LDADD = lib/libovn.la $(OVS_LIBDIR)/libopenvswitch.la
 man_MANS += controller/ovn-controller.8
diff --git a/controller/mirror.c b/controller/mirror.c
new file mode 100644
index 000000000..938ec3542
--- /dev/null
+++ b/controller/mirror.c
@@ -0,0 +1,418 @@ 
+/* Copyright (c) 2022 Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+#include <unistd.h>
+
+/* library headers */
+#include "lib/sset.h"
+#include "lib/util.h"
+
+/* OVS includes. */
+#include "lib/vswitch-idl.h"
+#include "include/openvswitch/shash.h"
+#include "openvswitch/vlog.h"
+
+/* OVN includes. */
+#include "binding.h"
+#include "lib/ovn-sb-idl.h"
+#include "mirror.h"
+
+VLOG_DEFINE_THIS_MODULE(port_mirror);
+
+struct ovn_mirror {
+    char *name;
+    const struct sbrec_mirror *sb_mirror;
+    const struct ovsrec_mirror *ovs_mirror;
+    struct ovs_list mirror_src_lports;
+    struct ovs_list mirror_dst_lports;
+};
+
+struct mirror_lport {
+    struct ovs_list list_node;
+
+    struct local_binding *lbinding;
+};
+
+static struct ovn_mirror *ovn_mirror_create(char *mirror_name);
+static void ovn_mirror_add(struct shash *ovn_mirrors,
+                           struct ovn_mirror *);
+static struct ovn_mirror *ovn_mirror_find(struct shash *ovn_mirrors,
+                                          const char *mirror_name);
+static void ovn_mirror_delete(struct ovn_mirror *);
+static void ovn_mirror_add_lport(struct ovn_mirror *, struct local_binding *);
+static void sync_ovn_mirror(struct ovn_mirror *, struct ovsdb_idl_txn *,
+                            const struct ovsrec_bridge *);
+
+static void create_ovs_mirror(struct ovn_mirror *, struct ovsdb_idl_txn *,
+                              const struct ovsrec_bridge *);
+static void sync_ovs_mirror_ports(struct ovn_mirror *,
+                                  const struct ovsrec_bridge *);
+static void delete_ovs_mirror(struct ovn_mirror *,
+                              const struct ovsrec_bridge *);
+static bool should_delete_ovs_mirror(struct ovn_mirror *);
+static void set_mirror_iface_options(struct ovsrec_interface *,
+                                     const struct sbrec_mirror *);
+
+static const struct ovsrec_port *get_iface_port(
+    const struct ovsrec_interface *, const struct ovsrec_bridge *);
+
+
+void
+mirror_register_ovs_idl(struct ovsdb_idl *ovs_idl)
+{
+    ovsdb_idl_add_column(ovs_idl, &ovsrec_bridge_col_mirrors);
+
+    ovsdb_idl_add_table(ovs_idl, &ovsrec_table_mirror);
+    ovsdb_idl_add_column(ovs_idl, &ovsrec_mirror_col_name);
+    ovsdb_idl_add_column(ovs_idl, &ovsrec_mirror_col_output_port);
+    ovsdb_idl_add_column(ovs_idl, &ovsrec_mirror_col_select_dst_port);
+    ovsdb_idl_add_column(ovs_idl, &ovsrec_mirror_col_select_src_port);
+    ovsdb_idl_add_column(ovs_idl, &ovsrec_mirror_col_external_ids);
+}
+
+void
+mirror_init(void)
+{
+}
+
+void
+mirror_destroy(void)
+{
+}
+
+void
+mirror_run(struct ovsdb_idl_txn *ovs_idl_txn,
+           const struct ovsrec_mirror_table *ovs_mirror_table,
+           const struct sbrec_mirror_table *sb_mirror_table,
+           const struct ovsrec_bridge *br_int,
+           struct shash *local_bindings)
+{
+    if (!ovs_idl_txn) {
+        return;
+    }
+
+    struct shash ovn_mirrors = SHASH_INITIALIZER(&ovn_mirrors);
+    struct shash tmp_mirrors = SHASH_INITIALIZER(&tmp_mirrors);
+
+    /* Iterate through sb mirrors and build the 'ovn_mirrors'. */
+    const struct sbrec_mirror *sb_mirror;
+    SBREC_MIRROR_TABLE_FOR_EACH (sb_mirror, sb_mirror_table) {
+        struct ovn_mirror *m = ovn_mirror_create(sb_mirror->name);
+        m->sb_mirror = sb_mirror;
+        ovn_mirror_add(&ovn_mirrors, m);
+    }
+
+    /* Iterate through ovs mirrors and add to the 'tmp_mirrors'. */
+    const struct ovsrec_mirror *ovs_mirror;
+    OVSREC_MIRROR_TABLE_FOR_EACH (ovs_mirror, ovs_mirror_table) {
+        bool ovn_owned_mirror = smap_get_bool(&ovs_mirror->external_ids,
+                                              "ovn-owned", false);
+        if (!ovn_owned_mirror) {
+            continue;
+        }
+
+        struct ovn_mirror *m = ovn_mirror_find(&ovn_mirrors, ovs_mirror->name);
+        if (!m) {
+            m = ovn_mirror_create(ovs_mirror->name);
+            shash_add(&tmp_mirrors, ovs_mirror->name, m);
+        }
+        m->ovs_mirror = ovs_mirror;
+    }
+
+    if (shash_is_empty(&ovn_mirrors)) {
+        shash_destroy(&ovn_mirrors);
+        if (shash_is_empty(&tmp_mirrors)) {
+            shash_destroy(&tmp_mirrors);
+            return;
+        } else {
+            goto cleanup;
+        }
+    }
+
+    /* Iterate through the local bindings and if the local binding's 'pb' has
+     * mirrors associated, add it to the ovn_mirror. */
+    struct shash_node *node;
+    SHASH_FOR_EACH (node, local_bindings) {
+        struct local_binding *lbinding = node->data;
+        const struct sbrec_port_binding *pb =
+            local_binding_get_primary_pb(local_bindings, lbinding->name);
+        if (!pb || !pb->n_mirror_rules) {
+            continue;
+        }
+
+        for (size_t i = 0; i < pb->n_mirror_rules; i++) {
+            struct ovn_mirror *m = ovn_mirror_find(&ovn_mirrors,
+                                                   pb->mirror_rules[i]->name);
+            ovs_assert(m);
+            ovn_mirror_add_lport(m, lbinding);
+        }
+    }
+
+    /* Iterate through the built 'ovn_mirrors' and
+     * sync with the local ovsdb i.e.
+     * create/update or delete the ovsrec mirror(s). */
+     SHASH_FOR_EACH (node, &ovn_mirrors) {
+        struct ovn_mirror *m = node->data;
+        sync_ovn_mirror(m, ovs_idl_txn, br_int);
+     }
+
+    SHASH_FOR_EACH_SAFE (node, &ovn_mirrors) {
+        ovn_mirror_delete(node->data);
+        shash_delete(&ovn_mirrors, node);
+    }
+
+    shash_destroy(&ovn_mirrors);
+
+cleanup:
+    SHASH_FOR_EACH_SAFE (node, &tmp_mirrors) {
+        ovn_mirror_delete(node->data);
+        shash_delete(&tmp_mirrors, node);
+    }
+
+    shash_destroy(&tmp_mirrors);
+}
+
+/* Static functions. */
+static struct ovn_mirror *
+ovn_mirror_create(char *mirror_name)
+{
+    struct ovn_mirror *m = xzalloc(sizeof *m);
+    m->name = xstrdup(mirror_name);
+    ovs_list_init(&m->mirror_src_lports);
+    ovs_list_init(&m->mirror_dst_lports);
+    return m;
+}
+
+static void
+ovn_mirror_add(struct shash *ovn_mirrors, struct ovn_mirror *m)
+{
+    shash_add(ovn_mirrors, m->sb_mirror->name, m);
+}
+
+static struct ovn_mirror *
+ovn_mirror_find(struct shash *ovn_mirrors, const char *mirror_name)
+{
+    return shash_find_data(ovn_mirrors, mirror_name);
+}
+
+static void
+ovn_mirror_delete(struct ovn_mirror *m)
+{
+    free(m->name);
+    struct mirror_lport *m_lport;
+    LIST_FOR_EACH_POP (m_lport, list_node, &m->mirror_src_lports) {
+        free(m_lport);
+    }
+
+    LIST_FOR_EACH_POP (m_lport, list_node, &m->mirror_dst_lports) {
+        free(m_lport);
+    }
+
+    free(m);
+}
+
+static void
+ovn_mirror_add_lport(struct ovn_mirror *m, struct local_binding *lbinding)
+{
+    struct mirror_lport *m_lport = xzalloc(sizeof *m_lport);
+    m_lport->lbinding = lbinding;
+    if (!strcmp(m->sb_mirror->filter, "from-lport")) {
+        ovs_list_push_back(&m->mirror_src_lports, &m_lport->list_node);
+    } else if (!strcmp(m->sb_mirror->filter, "to-lport")) {
+        ovs_list_push_back(&m->mirror_dst_lports, &m_lport->list_node);
+    } else {
+        ovs_list_push_back(&m->mirror_src_lports, &m_lport->list_node);
+        m_lport = xzalloc(sizeof *m_lport);
+        m_lport->lbinding = lbinding;
+        ovs_list_push_back(&m->mirror_dst_lports, &m_lport->list_node);
+    }
+}
+
+static void
+set_mirror_iface_options(struct ovsrec_interface *iface,
+                         const struct sbrec_mirror *sb_mirror)
+{
+    struct smap options = SMAP_INITIALIZER(&options);
+    char *key;
+
+    key = xasprintf("%ld", (long int) sb_mirror->index);
+    smap_add(&options, "remote_ip", sb_mirror->sink);
+    smap_add(&options, "key", key);
+    if (!strcmp(sb_mirror->type, "erspan")) {
+        /* Set the ERSPAN index */
+        smap_add(&options, "erspan_idx", key);
+        smap_add(&options, "erspan_ver", "1");
+    }
+    ovsrec_interface_set_options(iface, &options);
+
+    free(key);
+    smap_destroy(&options);
+}
+
+static void
+check_and_update_interface_table(const struct sbrec_mirror *sb_mirror,
+                                 const struct ovsrec_mirror *ovs_mirror)
+{
+    char *type;
+    struct ovsrec_interface *iface =
+                          ovs_mirror->output_port->interfaces[0];
+    struct smap *opts = &iface->options;
+    const char *erspan_ver = smap_get(opts, "erspan_ver");
+    if (erspan_ver) {
+        type = "erspan";
+    } else {
+        type = "gre";
+    }
+    if (strcmp(type, sb_mirror->type)) {
+        ovsrec_interface_set_type(iface, sb_mirror->type);
+    }
+    set_mirror_iface_options(iface, sb_mirror);
+}
+
+static void
+sync_ovn_mirror(struct ovn_mirror *m, struct ovsdb_idl_txn *ovs_idl_txn,
+                const struct ovsrec_bridge *br_int)
+{
+    if (should_delete_ovs_mirror(m)) {
+        /* Delete the ovsrec mirror. */
+        delete_ovs_mirror(m, br_int);
+        return;
+    }
+
+    if (ovs_list_is_empty(&m->mirror_src_lports) &&
+            ovs_list_is_empty(&m->mirror_dst_lports)) {
+        /* Nothing to do. */
+        return;
+    }
+
+    if (m->sb_mirror && !m->ovs_mirror) {
+        create_ovs_mirror(m, ovs_idl_txn, br_int);
+    } else {
+        check_and_update_interface_table(m->sb_mirror, m->ovs_mirror);
+    }
+
+    sync_ovs_mirror_ports(m, br_int);
+}
+
+static bool
+should_delete_ovs_mirror(struct ovn_mirror *m)
+{
+    if (!m->ovs_mirror) {
+        return false;
+    }
+
+    if (m->ovs_mirror && !m->sb_mirror) {
+        return true;
+    }
+
+    return (ovs_list_is_empty(&m->mirror_src_lports) &&
+            ovs_list_is_empty(&m->mirror_dst_lports));
+}
+
+static const struct ovsrec_port *
+get_iface_port(const struct ovsrec_interface *iface,
+               const struct ovsrec_bridge *br_int)
+{
+    for (size_t i = 0; i < br_int->n_ports; i++) {
+        const struct ovsrec_port *p = br_int->ports[i];
+        for (size_t j = 0; j < p->n_interfaces; j++) {
+            if (!strcmp(iface->name, p->interfaces[j]->name)) {
+                return p;
+            }
+        }
+    }
+    return NULL;
+}
+
+static void
+create_ovs_mirror(struct ovn_mirror *m, struct ovsdb_idl_txn *ovs_idl_txn,
+                  const struct ovsrec_bridge *br_int)
+{
+    struct ovsrec_interface *iface = ovsrec_interface_insert(ovs_idl_txn);
+    char *port_name = xasprintf("ovn-%s", m->name);
+
+    ovsrec_interface_set_name(iface, port_name);
+    ovsrec_interface_set_type(iface, m->sb_mirror->type);
+    set_mirror_iface_options(iface, m->sb_mirror);
+
+    struct ovsrec_port *port = ovsrec_port_insert(ovs_idl_txn);
+    ovsrec_port_set_name(port, port_name);
+    ovsrec_port_set_interfaces(port, &iface, 1);
+    ovsrec_bridge_update_ports_addvalue(br_int, port);
+
+    free(port_name);
+
+    m->ovs_mirror = ovsrec_mirror_insert(ovs_idl_txn);
+    ovsrec_mirror_set_name(m->ovs_mirror, m->name);
+    ovsrec_mirror_set_output_port(m->ovs_mirror, port);
+
+    const struct smap external_ids =
+        SMAP_CONST1(&external_ids, "ovn-owned", "true");
+    ovsrec_mirror_set_external_ids(m->ovs_mirror, &external_ids);
+
+    ovsrec_bridge_update_mirrors_addvalue(br_int, m->ovs_mirror);
+}
+
+static void
+sync_ovs_mirror_ports(struct ovn_mirror *m, const struct ovsrec_bridge *br_int)
+{
+    struct mirror_lport *m_lport;
+
+    if (ovs_list_is_empty(&m->mirror_src_lports)) {
+        ovsrec_mirror_set_select_src_port(m->ovs_mirror, NULL, 0);
+    } else {
+        size_t n_lports = ovs_list_size(&m->mirror_src_lports);
+        struct ovsrec_port **ovs_ports = xmalloc(sizeof *ovs_ports * n_lports);
+
+        size_t i = 0;
+        LIST_FOR_EACH (m_lport, list_node, &m->mirror_src_lports) {
+            const struct ovsrec_port *p =
+                get_iface_port(m_lport->lbinding->iface, br_int);
+            ovs_assert(p);
+            ovs_ports[i++] = (struct ovsrec_port *) p;
+        }
+
+        ovsrec_mirror_set_select_src_port(m->ovs_mirror, ovs_ports, n_lports);
+        free(ovs_ports);
+    }
+
+    if (ovs_list_is_empty(&m->mirror_dst_lports)) {
+        ovsrec_mirror_set_select_dst_port(m->ovs_mirror, NULL, 0);
+    } else {
+        size_t n_lports = ovs_list_size(&m->mirror_dst_lports);
+        struct ovsrec_port **ovs_ports = xmalloc(sizeof *ovs_ports * n_lports);
+
+        size_t i = 0;
+        LIST_FOR_EACH (m_lport, list_node, &m->mirror_dst_lports) {
+            const struct ovsrec_port *p =
+                get_iface_port(m_lport->lbinding->iface, br_int);
+            ovs_assert(p);
+            ovs_ports[i++] = (struct ovsrec_port *) p;
+        }
+
+        ovsrec_mirror_set_select_dst_port(m->ovs_mirror, ovs_ports, n_lports);
+        free(ovs_ports);
+    }
+}
+
+static void
+delete_ovs_mirror(struct ovn_mirror *m, const struct ovsrec_bridge *br_int)
+{
+    ovsrec_bridge_update_ports_delvalue(br_int, m->ovs_mirror->output_port);
+    ovsrec_bridge_update_mirrors_delvalue(br_int, m->ovs_mirror);
+    ovsrec_port_delete(m->ovs_mirror->output_port);
+    ovsrec_mirror_delete(m->ovs_mirror);
+}
diff --git a/controller/mirror.h b/controller/mirror.h
new file mode 100644
index 000000000..a79de109d
--- /dev/null
+++ b/controller/mirror.h
@@ -0,0 +1,33 @@ 
+/* Copyright (c) 2022 Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OVN_MIRROR_H
+#define OVN_MIRROR_H 1
+
+struct ovsdb_idl_txn;
+struct ovsrec_mirror_table;
+struct sbrec_mirror_table;
+struct ovsrec_bridge;
+struct shash;
+
+void mirror_register_ovs_idl(struct ovsdb_idl *);
+void mirror_init(void);
+void mirror_destroy(void);
+void mirror_run(struct ovsdb_idl_txn *ovs_idl_txn,
+                const struct ovsrec_mirror_table *,
+                const struct sbrec_mirror_table *,
+                const struct ovsrec_bridge *,
+                struct shash *local_bindings);
+#endif
diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c
index f0fd24820..470891bb2 100644
--- a/controller/ovn-controller.c
+++ b/controller/ovn-controller.c
@@ -78,6 +78,7 @@ 
 #include "lib/inc-proc-eng.h"
 #include "lib/ovn-l7.h"
 #include "hmapx.h"
+#include "mirror.h"
 
 VLOG_DEFINE_THIS_MODULE(main);
 
@@ -1004,6 +1005,7 @@  ctrl_register_ovs_idl(struct ovsdb_idl *ovs_idl)
     ovsdb_idl_track_add_column(ovs_idl, &ovsrec_port_col_name);
     ovsdb_idl_track_add_column(ovs_idl, &ovsrec_port_col_interfaces);
     ovsdb_idl_track_add_column(ovs_idl, &ovsrec_port_col_external_ids);
+    mirror_register_ovs_idl(ovs_idl);
 }
 
 #define SB_NODES \
@@ -3926,6 +3928,7 @@  main(int argc, char *argv[])
     patch_init();
     pinctrl_init();
     lflow_init();
+    mirror_init();
     vif_plug_provider_initialize();
 
     /* Connect to OVS OVSDB instance. */
@@ -4638,6 +4641,11 @@  main(int argc, char *argv[])
                                     &runtime_data->local_active_ports_ras);
                         stopwatch_stop(PINCTRL_RUN_STOPWATCH_NAME,
                                        time_msec());
+                        mirror_run(ovs_idl_txn,
+                                   ovsrec_mirror_table_get(ovs_idl_loop.idl),
+                                   sbrec_mirror_table_get(ovnsb_idl_loop.idl),
+                                   br_int,
+                                   &runtime_data->lbinding_data.bindings);
                         /* Updating monitor conditions if runtime data or
                          * logical datapath goups changed. */
                         if (engine_node_changed(&en_runtime_data)
@@ -4884,6 +4892,7 @@  loop_done:
     pinctrl_destroy();
     binding_destroy();
     patch_destroy();
+    mirror_destroy();
     if_status_mgr_destroy(if_mgr);
     shash_destroy(&vif_plug_deleted_iface_ids);
     shash_destroy(&vif_plug_changed_iface_ids);
diff --git a/tests/ovn.at b/tests/ovn.at
index f3bd53242..305a0e45d 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -16242,6 +16242,520 @@  OVN_CLEANUP([hv1], [hv2])
 AT_CLEANUP
 ])
 
+OVN_FOR_EACH_NORTHD([
+AT_SETUP([Mirror])
+AT_KEYWORDS([Mirror])
+ovn_start
+
+# Logical network:
+# One LR - R1 has switch ls1 (191.168.1.0/24) connected to it,
+# and has switch ls2 (172.16.1.0/24) connected to it.
+
+ovn-nbctl lr-add R1
+
+ovn-nbctl ls-add ls1
+ovn-nbctl ls-add ls2
+
+# Connect ls1 to R1
+ovn-nbctl lrp-add R1 ls1 00:00:00:01:02:f1 192.168.1.1/24
+ovn-nbctl lsp-add ls1 rp-ls1 -- set Logical_Switch_Port rp-ls1 \
+    type=router options:router-port=ls1 addresses=\"00:00:00:01:02:f1\"
+
+# Connect ls2 to R1
+ovn-nbctl lrp-add R1 ls2 00:00:00:01:02:f2 172.16.1.1/24
+ovn-nbctl lsp-add ls2 rp-ls2 -- set Logical_Switch_Port rp-ls2 \
+    type=router options:router-port=ls2 addresses=\"00:00:00:01:02:f2\"
+
+# Create logical port ls1-lp1 in ls1
+ovn-nbctl lsp-add ls1 ls1-lp1 \
+-- lsp-set-addresses ls1-lp1 "00:00:00:01:02:03 192.168.1.2"
+
+# Create logical port ls2-lp1 in ls2
+ovn-nbctl lsp-add ls2 ls2-lp1 \
+-- lsp-set-addresses ls2-lp1 "00:00:00:01:02:04 172.16.1.2"
+
+ovn-nbctl lsp-add ls1 ln-public
+ovn-nbctl lsp-set-type ln-public localnet
+ovn-nbctl lsp-set-addresses ln-public unknown
+ovn-nbctl lsp-set-options ln-public network_name=public
+
+# Create one hypervisor and create OVS ports corresponding to logical ports.
+net_add n1
+
+sim_add hv1
+as hv1
+ovs-vsctl add-br br-phys -- set bridge br-phys other-config:hwaddr=\"00:00:00:01:02:00\"
+ovn_attach n1 br-phys 192.168.1.11
+
+ovs-vsctl -- add-port br-int vif1 -- \
+    set interface vif1 external-ids:iface-id=ls1-lp1 \
+    options:tx_pcap=hv1/vif1-tx.pcap \
+    options:rxq_pcap=hv1/vif1-rx.pcap \
+    ofport-request=1
+
+ovs-vsctl -- add-port br-int vif2 -- \
+    set interface vif2 external-ids:iface-id=ls2-lp1 \
+    options:tx_pcap=hv1/vif2-tx.pcap \
+    options:rxq_pcap=hv1/vif2-rx.pcap \
+    ofport-request=1
+
+ovs-vsctl set open . external-ids:ovn-bridge-mappings=public:br-phys
+
+# Allow some time for ovn-northd and ovn-controller to catch up.
+wait_for_ports_up
+check ovn-nbctl --wait=hv sync
+ovn-nbctl dump-flows > sbflows
+AT_CAPTURE_FILE([sbflows])
+
+for i in 1 2; do
+    : > vif$i.expected
+done
+
+net_add n2
+
+sim_add hv2
+as hv2
+ovs-vsctl add-br br-phys -- set bridge br-phys other-config:hwaddr=\"00:00:00:02:02:00\"
+ovn_attach n2 br-phys 192.168.1.12
+
+OVN_POPULATE_ARP
+
+as hv1
+
+# test_ipv4_icmp_request INPORT ETH_SRC ETH_DST IPV4_SRC IPV4_DST IP_CHKSUM ICMP_CHKSUM [EXP_IP_CHKSUM EXP_ICMP_CHKSUM] ENCAP_TYPE FILTER
+#
+# Causes a packet to be received on INPORT.  The packet is an ICMPv4
+# request with ETH_SRC, ETH_DST, IPV4_SRC, IPV4_DST, IP_CHSUM and
+# ICMP_CHKSUM as specified.  If EXP_IP_CHKSUM and EXP_ICMP_CHKSUM are
+# provided, then it should be the ip and icmp checksums of the packet
+# responded; otherwise, no reply is expected.
+# In the absence of an ip checksum calculation helpers, this relies
+# on the caller to provide the checksums for the ip and icmp headers.
+# XXX This should be more systematic.
+#
+# INPORT is an lport number, e.g. 11 for vif11.
+# ETH_SRC and ETH_DST are each 12 hex digits.
+# IPV4_SRC and IPV4_DST are each 8 hex digits.
+# IP_CHSUM and ICMP_CHKSUM are each 4 hex digits.
+# EXP_IP_CHSUM and EXP_ICMP_CHKSUM are each 4 hex digits.
+# ENCAP_TYPE - gre or erspan
+# FILTER - Mirror Filter - to-lport / from-lport
+test_ipv4_icmp_request() {
+    local inport=$1 eth_src=$2 eth_dst=$3 ipv4_src=$4 ipv4_dst=$5 ip_chksum=$6 icmp_chksum=$7
+    local exp_ip_chksum=$8 exp_icmp_chksum=$9 mirror_encap_type=${10} mirror_filter=${11}
+    shift; shift; shift; shift; shift; shift; shift
+    shift; shift; shift; shift;
+
+    # Use ttl to exercise section 4.2.2.9 of RFC1812
+    local ip_ttl=02
+    local icmp_id=5fbf
+    local icmp_seq=0001
+    local icmp_data=$(seq 1 56 | xargs printf "%02x")
+    local icmp_type_code_request=0800
+    local icmp_payload=${icmp_type_code_request}${icmp_chksum}${icmp_id}${icmp_seq}${icmp_data}
+    local packet=${eth_dst}${eth_src}08004500005400004000${ip_ttl}01${ip_chksum}${ipv4_src}${ipv4_dst}${icmp_payload}
+
+    as hv1 ovs-appctl netdev-dummy/receive vif$inport $packet
+
+    # Expect to receive the reply, if any. In same port where packet was sent.
+    # Note: src and dst fields are expected to be reversed.
+    local icmp_type_code_response=0000
+    local reply_icmp_ttl=fe
+    local reply_icmp_payload=${icmp_type_code_response}${exp_icmp_chksum}${icmp_id}${icmp_seq}${icmp_data}
+    local reply=${eth_src}${eth_dst}08004500005400004000${reply_icmp_ttl}01${exp_ip_chksum}${ipv4_dst}${ipv4_src}${reply_icmp_payload}
+    echo $reply >> vif$inport.expected
+    local remote_mac=000000020200
+    local local_mac=000000010200
+    if test ${mirror_encap_type} = "gre" ; then
+        local ipv4_gre=4500007e00004000402fb6e9c0a8010bc0a8010c2000655800000000
+        if test ${mirror_filter} = "to-lport" ; then
+            local mirror=${remote_mac}${local_mac}0800${ipv4_gre}${reply}
+        elif test ${mirror_filter} = "from-lport" ; then
+            local mirror=${remote_mac}${local_mac}0800${ipv4_gre}${packet}
+        fi
+    elif test ${mirror_encap_type} = "erspan" ; then
+        local ipv4_erspan=4500008600004000402fb6e1c0a8010bc0a8010c
+        local erspan_seq0=100088be000000001000000000000000
+        local erspan_seq1=100088be000000011000000000000000
+        if test ${mirror_filter} = "to-lport" ; then
+            local mirror=${remote_mac}${local_mac}0800${ipv4_erspan}${erspan_seq0}${reply}
+        elif test ${mirror_filter} = "from-lport" ; then
+            local mirror=${remote_mac}${local_mac}0800${ipv4_erspan}${erspan_seq1}${packet}
+        fi
+    fi
+    echo $mirror >> br-phys_n1.expected
+
+}
+
+# Set IPs
+rtr_l2_ip=$(ip_to_hex 172 16 1 1)
+l1_ip=$(ip_to_hex 192 168 1 2)
+
+check ovn-nbctl mirror-add mirror0 gre 0 to-lport 192.168.1.12
+check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
+
+# Send ping packet and check for mirrored packet of the reply
+test_ipv4_icmp_request 1 000000010203 0000000102f1 $l1_ip $rtr_l2_ip 0000 8510 03ff 8d10 "gre" "to-lport"
+
+OVN_CHECK_PACKETS_CONTAIN([hv1/br-phys_n1-tx.pcap], [br-phys_n1.expected])
+OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [vif1.expected])
+
+as hv1 reset_pcap_file vif1 hv1/vif1
+as hv1 reset_pcap_file br-phys_n1 hv1/br-phys_n1
+rm -f br-phys_n1.expected
+rm -f vif1.expected
+
+check ovn-nbctl set mirror . type=erspan
+
+# Send ping packet and check for mirrored packet of the reply
+test_ipv4_icmp_request 1 000000010203 0000000102f1 $l1_ip $rtr_l2_ip 0000 8510 03ff 8d10 "erspan" "to-lport"
+
+OVN_CHECK_PACKETS_CONTAIN([hv1/br-phys_n1-tx.pcap], [br-phys_n1.expected])
+OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [vif1.expected])
+
+as hv1 reset_pcap_file vif1 hv1/vif1
+as hv1 reset_pcap_file br-phys_n1 hv1/br-phys_n1
+rm -f br-phys_n1.expected
+rm -f vif1.expected
+
+check ovn-nbctl set mirror . filter=from-lport
+
+# Send ping packet and check for mirrored packet of the request
+test_ipv4_icmp_request 1 000000010203 0000000102f1 $l1_ip $rtr_l2_ip 0000 8510 03ff 8d10 "erspan" "from-lport"
+
+OVN_CHECK_PACKETS_CONTAIN([hv1/br-phys_n1-tx.pcap], [br-phys_n1.expected])
+OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [vif1.expected])
+
+as hv1 reset_pcap_file vif1 hv1/vif1
+as hv1 reset_pcap_file br-phys_n1 hv1/br-phys_n1
+rm -f br-phys_n1.expected
+rm -f vif1.expected
+
+check ovn-nbctl set mirror . type=gre
+
+# Send ping packet and check for mirrored packet of the request
+test_ipv4_icmp_request 1 000000010203 0000000102f1 $l1_ip $rtr_l2_ip 0000 8510 03ff 8d10 "gre" "from-lport"
+
+OVN_CHECK_PACKETS_CONTAIN([hv1/br-phys_n1-tx.pcap], [br-phys_n1.expected])
+OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [vif1.expected])
+
+echo "---------OVN NB Mirror-----"
+ovn-nbctl mirror-list
+
+echo "---------OVS Mirror----"
+ovs-vsctl list Mirror
+
+echo "-----------------------"
+
+echo "Verifying Mirror deletion in OVS"
+# Set vif1 iface-id such that OVN releases port binding
+check ovs-vsctl set interface vif1 external_ids:iface-id=foo
+check ovn-nbctl --wait=hv sync
+
+AT_CHECK([as hv1 ovs-vsctl list Mirror], [0], [dnl
+])
+
+# Set vif1 iface-id back to ls1-lp1
+check ovs-vsctl set interface vif1 external_ids:iface-id=ls1-lp1
+check ovn-nbctl --wait=hv sync
+
+OVS_WAIT_UNTIL([test $(as hv1 ovs-vsctl get Mirror mirror0 name) = "mirror0"])
+
+# Delete vif1 so that OVN releases port binding
+check ovs-vsctl del-port br-int vif1
+check ovn-nbctl --wait=hv sync
+
+OVS_WAIT_UNTIL([test 0 = $(as hv1 ovs-vsctl list Mirror | wc -l)])
+
+OVN_CLEANUP([hv1], [hv2])
+AT_CLEANUP
+])
+
+OVN_FOR_EACH_NORTHD([
+AT_SETUP([Mirror test bulk updates])
+AT_KEYWORDS([Mirror test bulk updates])
+ovn_start
+
+# Logical network:
+# One LR - R1 has switch ls1 (191.168.1.0/24) connected to it,
+# and has switch ls2 (172.16.1.0/24) connected to it.
+
+ovn-nbctl lr-add R1
+
+ovn-nbctl ls-add ls1
+ovn-nbctl ls-add ls2
+
+# Connect ls1 to R1
+ovn-nbctl lrp-add R1 ls1 00:00:00:01:02:f1 192.168.1.1/24
+ovn-nbctl lsp-add ls1 rp-ls1 -- set Logical_Switch_Port rp-ls1 \
+    type=router options:router-port=ls1 addresses=\"00:00:00:01:02:f1\"
+
+# Connect ls2 to R1
+ovn-nbctl lrp-add R1 ls2 00:00:00:01:02:f2 172.16.1.1/24
+ovn-nbctl lsp-add ls2 rp-ls2 -- set Logical_Switch_Port rp-ls2 \
+    type=router options:router-port=ls2 addresses=\"00:00:00:01:02:f2\"
+
+# Create logical port ls1-lp1 in ls1
+ovn-nbctl lsp-add ls1 ls1-lp1 \
+-- lsp-set-addresses ls1-lp1 "00:00:00:01:02:03 192.168.1.2"
+
+# Create logical port ls1-lp2 in ls1
+ovn-nbctl lsp-add ls1 ls1-lp2 \
+-- lsp-set-addresses ls1-lp2 "00:00:00:01:03:03 192.168.1.3"
+
+# Create logical port ls2-lp1 in ls2
+ovn-nbctl lsp-add ls2 ls2-lp1 \
+-- lsp-set-addresses ls2-lp1 "00:00:00:01:02:04 172.16.1.2"
+
+# Create logical port ls2-lp2 in ls2
+ovn-nbctl lsp-add ls2 ls2-lp2 \
+-- lsp-set-addresses ls2-lp2 "00:00:00:01:03:04 172.16.1.3"
+
+ovn-nbctl lsp-add ls1 ln-public
+ovn-nbctl lsp-set-type ln-public localnet
+ovn-nbctl lsp-set-addresses ln-public unknown
+ovn-nbctl lsp-set-options ln-public network_name=public
+
+# Create 2 hypervisors and create OVS ports corresponding to logical ports for hv1.
+net_add n1
+
+sim_add hv1
+as hv1
+ovs-vsctl add-br br-phys -- set bridge br-phys other-config:hwaddr=\"00:00:00:01:02:00\"
+ovn_attach n1 br-phys 192.168.1.11
+
+net_add n2
+
+sim_add hv2
+as hv2
+ovs-vsctl add-br br-phys -- set bridge br-phys other-config:hwaddr=\"00:00:00:02:02:00\"
+ovn_attach n2 br-phys 192.168.1.12
+
+OVN_POPULATE_ARP
+
+as hv1
+
+ovs-vsctl -- add-port br-int vif1 -- \
+    set interface vif1 external-ids:iface-id=ls1-lp1
+
+ovs-vsctl -- add-port br-int vif2 -- \
+    set interface vif2 external-ids:iface-id=ls2-lp1
+
+ovs-vsctl -- add-port br-int vif3 -- \
+    set interface vif3 external-ids:iface-id=ls1-lp2
+
+ovs-vsctl -- add-port br-int vif4 -- \
+    set interface vif4 external-ids:iface-id=ls2-lp2
+
+ovs-vsctl set open . external-ids:ovn-bridge-mappings=public:br-phys
+
+# Allow some time for ovn-northd and ovn-controller to catch up.
+wait_for_ports_up
+check ovn-nbctl --wait=hv sync
+ovn-nbctl dump-flows > sbflows
+AT_CAPTURE_FILE([sbflows])
+
+check ovn-nbctl mirror-add mirror0 gre 0 to-lport 192.168.1.12
+check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
+check ovn-nbctl --wait=hv sync
+origA=$(ovs-vsctl get Mirror mirror0 select_dst_port)
+check ovn-nbctl lsp-attach-mirror ls2-lp1 mirror0
+
+check ovn-nbctl mirror-add mirror1 gre 1 to-lport 192.168.1.12
+check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror1
+check ovn-nbctl --wait=hv sync
+origB=$(ovs-vsctl get Mirror mirror1 select_dst_port)
+check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror1
+
+check ovn-nbctl --wait=hv sync
+
+as hv1
+orig1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
+orig2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
+
+check ovn-nbctl mirror-del
+check ovn-nbctl --wait=hv sync
+
+check ovn-nbctl mirror-add mirror0 gre 0 to-lport 192.168.1.12
+check ovn-nbctl mirror-add mirror1 gre 1 to-lport 192.168.1.12
+
+# Attaches multiple mirrors
+
+check as hv1 ovn-appctl -t ovn-controller debug/pause
+check ovn-nbctl lsp-attach-mirror ls2-lp1 mirror0
+check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
+check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror1
+check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror1
+check as hv1 ovn-appctl -t ovn-controller debug/resume
+
+check ovn-nbctl --wait=hv sync
+
+as hv1
+new1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
+new2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
+
+AT_CHECK([test "$orig1" = "$new1"], [0], [])
+
+AT_CHECK([test "$orig2" = "$new2"], [0], [])
+
+# Equal detaches and attaches
+check as hv1 ovn-appctl -t ovn-controller debug/pause
+check ovn-nbctl lsp-detach-mirror ls1-lp1 mirror0
+check ovn-nbctl lsp-detach-mirror ls2-lp1 mirror0
+check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror0
+check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror0
+
+check ovn-nbctl lsp-detach-mirror ls1-lp2 mirror1
+check ovn-nbctl lsp-detach-mirror ls2-lp2 mirror1
+check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror1
+check ovn-nbctl lsp-attach-mirror ls2-lp1 mirror1
+check as hv1 ovn-appctl -t ovn-controller debug/resume
+
+check ovn-nbctl --wait=hv sync
+
+# Make sure that ovn-controller has not asserted.
+AT_CHECK([kill -0 $(cat hv1/ovn-controller.pid)])
+
+as hv1
+new1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
+new2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
+
+AT_CHECK([test "$orig1" = "$new2"], [0], [])
+
+AT_CHECK([test "$orig2" = "$new1"], [0], [])
+
+# Detaches more than attaches
+check as hv1 ovn-appctl -t ovn-controller debug/pause
+check ovn-nbctl lsp-detach-mirror ls1-lp1 mirror1
+check ovn-nbctl lsp-detach-mirror ls2-lp1 mirror1
+check ovn-nbctl lsp-detach-mirror ls1-lp2 mirror0
+check ovn-nbctl lsp-detach-mirror ls2-lp2 mirror0
+check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
+check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror1
+check as hv1 ovn-appctl -t ovn-controller debug/resume
+
+check ovn-nbctl --wait=hv sync
+# Make sure that ovn-controller has not asserted.
+AT_CHECK([kill -0 $(cat hv1/ovn-controller.pid)])
+
+as hv1
+new1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
+new2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
+
+AT_CHECK([test "$origA" = "$new1"], [0], [])
+
+AT_CHECK([test "$origB" = "$new2"], [0], [])
+
+# Attaches more than detaches
+check as hv1 ovn-appctl -t ovn-controller debug/pause
+check ovn-nbctl lsp-detach-mirror ls1-lp1 mirror0
+check ovn-nbctl lsp-detach-mirror ls1-lp2 mirror1
+check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror0
+check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror0
+check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror1
+check ovn-nbctl lsp-attach-mirror ls2-lp1 mirror1
+check as hv1 ovn-appctl -t ovn-controller debug/resume
+
+check ovn-nbctl --wait=hv sync
+# Make sure that ovn-controller has not asserted.
+AT_CHECK([kill -0 $(cat hv1/ovn-controller.pid)])
+
+as hv1
+new1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
+new2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
+
+AT_CHECK([test "$orig1" = "$new2"], [0], [])
+
+AT_CHECK([test "$orig2" = "$new1"], [0], [])
+
+# Detaches all
+check as hv1 ovn-appctl -t ovn-controller debug/pause
+check ovn-nbctl lsp-detach-mirror ls1-lp2 mirror0
+check ovn-nbctl lsp-detach-mirror ls2-lp2 mirror0
+check ovn-nbctl lsp-detach-mirror ls1-lp1 mirror1
+check ovn-nbctl lsp-detach-mirror ls2-lp1 mirror1
+check as hv1 ovn-appctl -t ovn-controller debug/resume
+check ovn-nbctl --wait=hv sync
+
+OVS_WAIT_UNTIL([test 0 = $(as hv1 ovs-vsctl list Mirror | wc -l)])
+
+check ovn-nbctl mirror-add mirror2 gre 2 to-lport 192.168.1.12
+check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror2
+check ovn-nbctl --wait=hv sync
+
+# Attaches SAME port to multiple mirrors
+# and detaches it from existing mirror.
+# More attach than detach
+
+check as hv1 ovn-appctl -t ovn-controller debug/pause
+check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
+check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror1
+check ovn-nbctl lsp-detach-mirror ls1-lp1 mirror2
+check as hv1 ovn-appctl -t ovn-controller debug/resume
+
+check ovn-nbctl --wait=hv sync
+
+as hv1
+new1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
+new2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
+
+AT_CHECK([test "$origA" = "$new1"], [0], [])
+AT_CHECK([test "$origA" = "$new2"], [0], [])
+OVS_WAIT_UNTIL([test 0 = $(as hv1 ovs-vsctl list Mirror mirror2 | wc -l)])
+
+check ovn-nbctl mirror-add mirror3 gre 3 to-lport 192.168.1.12
+check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror3
+check ovn-nbctl --wait=hv sync
+
+# Detaches SAME port from multiple mirrors
+# and attaches it to existing mirror.
+# More detach than attach
+
+check as hv1 ovn-appctl -t ovn-controller debug/pause
+check ovn-nbctl lsp-detach-mirror ls1-lp1 mirror0
+check ovn-nbctl lsp-detach-mirror ls1-lp1 mirror3
+check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror2
+check as hv1 ovn-appctl -t ovn-controller debug/resume
+
+check ovn-nbctl --wait=hv sync
+
+as hv1
+new1=$(ovs-vsctl get Mirror mirror1 select_dst_port)
+new2=$(ovs-vsctl get Mirror mirror2 select_dst_port)
+
+AT_CHECK([test "$origA" = "$new1"], [0], [])
+AT_CHECK([test "$origA" = "$new2"], [0], [])
+OVS_WAIT_UNTIL([test 0 = $(as hv1 ovs-vsctl list Mirror mirror0 | wc -l)])
+OVS_WAIT_UNTIL([test 0 = $(as hv1 ovs-vsctl list Mirror mirror3 | wc -l)])
+
+check ovn-nbctl mirror-del
+check ovn-nbctl --wait=hv sync
+check ovn-nbctl mirror-add mirror0 erspan 0 to-lport 192.168.1.12
+check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
+check ovn-nbctl --wait=hv sync
+
+# Make sure different fields of mirror resource set from OVN
+# propagates to OVS correctly
+
+check as hv1 ovn-appctl -t ovn-controller debug/pause
+check ovn-nbctl set mirror . filter=from-lport
+check ovn-nbctl set mirror . type=gre
+check ovn-nbctl set mirror . index=123
+check as hv1 ovn-appctl -t ovn-controller debug/resume
+
+check ovn-nbctl --wait=hv sync
+
+as hv1
+new1=$(ovs-vsctl get Mirror mirror0 select_src_port)
+AT_CHECK([test "$origA" = "$new1"], [0], [])
+type=$(ovs-vsctl get Interface ovn-mirror0 type)
+OVS_WAIT_UNTIL([test "gre" = "$type"])
+index=$(ovs-vsctl get Interface ovn-mirror0 options:key | grep 123 | wc -l)
+OVS_WAIT_UNTIL([test "1" = "$index"])
+
+OVN_CLEANUP([hv1],[hv2])
+AT_CLEANUP
+])
 
 OVN_FOR_EACH_NORTHD([
 AT_SETUP([Port Groups])