diff mbox series

[ovs-dev,v4] OVN - Add Support for Remote Port Mirroring

Message ID 20220822204043.1277600-1-abhiramrn@gmail.com
State Changes Requested
Headers show
Series [ovs-dev,v4] OVN - Add Support for Remote Port Mirroring | expand

Checks

Context Check Description
ovsrobot/apply-robot warning apply and check: warning
ovsrobot/github-robot-_Build_and_Test fail github build: failed
ovsrobot/github-robot-_ovn-kubernetes success github build: passed

Commit Message

Abhiram RN Aug. 22, 2022, 8:40 p.m. UTC
Added changes in ovn-nbctl, ovn-sbctl, northd and in ovn-controller.
While 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.

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>
---
V3 --> V4: Addressed review comments from V3
           i) Fixed Issues 1,2 and 3
          ii) Did other minor changes suggested
         iii) Included Issues 1 and 3 in test cases
Files modified (V3 --> v4):
Code --> mirror.c, ovn-controller.c, northd.c
Test --> ovn-northd.at, ovn-nbctl.at, ovn.at

 controller/automake.mk      |   4 +-
 controller/mirror.c         | 510 ++++++++++++++++++++++++++++++++++++
 controller/mirror.h         |  53 ++++
 controller/ovn-controller.c | 222 +++++++++++++++-
 northd/en-northd.c          |   4 +
 northd/inc-proc-northd.c    |   4 +
 northd/northd.c             | 136 ++++++++++
 northd/northd.h             |   2 +
 ovn-nb.ovsschema            |  31 ++-
 ovn-nb.xml                  |  57 ++++
 ovn-sb.ovsschema            |  23 +-
 ovn-sb.xml                  |  46 ++++
 tests/ovn-nbctl.at          |  95 +++++++
 tests/ovn-northd.at         |  50 ++++
 tests/ovn.at                | 159 +++++++++++
 utilities/ovn-nbctl.c       | 345 ++++++++++++++++++++++++
 utilities/ovn-sbctl.c       |   4 +
 17 files changed, 1739 insertions(+), 6 deletions(-)
 create mode 100644 controller/mirror.c
 create mode 100644 controller/mirror.h

Comments

0-day Robot Aug. 22, 2022, 8:59 p.m. UTC | #1
Bleep bloop.  Greetings Abhiram R N, I am a robot and I have tried out your patch.
Thanks for your contribution.

I encountered some error that I wasn't expecting.  See the details below.


checkpatch:
WARNING: Line lacks whitespace around operator
#1758 FILE: utilities/ovn-nbctl.c:275:
  mirror-add NAME TYPE INDEX FILTER IP\n\

WARNING: Line lacks whitespace around operator
#1764 FILE: utilities/ovn-nbctl.c:281:
  mirror-del [NAME]         remove mirrors\n\

WARNING: Line lacks whitespace around operator
#1765 FILE: utilities/ovn-nbctl.c:282:
  mirror-list               print mirrors\n\

WARNING: Line lacks whitespace around operator
#1774 FILE: utilities/ovn-nbctl.c:324:
  lsp-attach-mirror PORT MIRROR   attach source PORT to the MIRROR\n\

WARNING: Line lacks whitespace around operator
#1775 FILE: utilities/ovn-nbctl.c:325:
  lsp-detach-mirror PORT MIRROR   detach source PORT from the MIRROR\n\

Lines checked: 2164, Warnings: 5, Errors: 0


Please check this out.  If you feel there has been an error, please email aconole@redhat.com

Thanks,
0-day Robot
Ihar Hrachyshka Aug. 24, 2022, 11:50 p.m. UTC | #2
Hi,

I've reviewed everything except mirror.[ch] files. See below.

On Mon, Aug 22, 2022 at 4:41 PM Abhiram R N <abhiramrn@gmail.com> wrote:
>
> Added changes in ovn-nbctl, ovn-sbctl, northd and in ovn-controller.
> While 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.
>
> 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>
> ---
> V3 --> V4: Addressed review comments from V3
>            i) Fixed Issues 1,2 and 3
>           ii) Did other minor changes suggested
>          iii) Included Issues 1 and 3 in test cases
> Files modified (V3 --> v4):
> Code --> mirror.c, ovn-controller.c, northd.c
> Test --> ovn-northd.at, ovn-nbctl.at, ovn.at
>
>  controller/automake.mk      |   4 +-
>  controller/mirror.c         | 510 ++++++++++++++++++++++++++++++++++++
>  controller/mirror.h         |  53 ++++
>  controller/ovn-controller.c | 222 +++++++++++++++-
>  northd/en-northd.c          |   4 +
>  northd/inc-proc-northd.c    |   4 +
>  northd/northd.c             | 136 ++++++++++
>  northd/northd.h             |   2 +
>  ovn-nb.ovsschema            |  31 ++-
>  ovn-nb.xml                  |  57 ++++
>  ovn-sb.ovsschema            |  23 +-
>  ovn-sb.xml                  |  46 ++++
>  tests/ovn-nbctl.at          |  95 +++++++
>  tests/ovn-northd.at         |  50 ++++
>  tests/ovn.at                | 159 +++++++++++
>  utilities/ovn-nbctl.c       | 345 ++++++++++++++++++++++++
>  utilities/ovn-sbctl.c       |   4 +
>  17 files changed, 1739 insertions(+), 6 deletions(-)
>  create mode 100644 controller/mirror.c
>  create mode 100644 controller/mirror.h
>
> 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..44895d8ca
> --- /dev/null
> +++ b/controller/mirror.c
> @@ -0,0 +1,510 @@
> +/* 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 "openvswitch/vlog.h"
> +
> +/* OVN includes. */
> +#include "binding.h"
> +#include "lib/ovn-sb-idl.h"
> +#include "mirror.h"
> +
> +VLOG_DEFINE_THIS_MODULE(port_mirror);
> +
> +/* Static function declarations */
> +
> +static const struct ovsrec_port *
> +get_port_for_iface(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 bool
> +mirror_create(const struct sbrec_port_binding *pb,
> +              struct port_mirror_ctx *pm_ctx)
> +{
> +    const struct ovsrec_mirror *mirror = NULL;
> +
> +    if (pb->n_up && !pb->up[0]) {
> +        return true;
> +    }
> +
> +    if (pb->chassis != pm_ctx->chassis_rec) {
> +        return true;
> +    }
> +
> +    if (!pm_ctx->ovs_idl_txn) {
> +        return false;
> +    }
> +
> +
> +    VLOG_INFO("Mirror rule(s) present for %s ", pb->logical_port);
> +    /* Loop through the mirror rules */
> +    for (size_t i =0; i < pb->n_mirror_rules; i++) {
> +        /* check if the mirror already exists in OVS DB */
> +        bool create_mirror = true;
> +        OVSREC_MIRROR_TABLE_FOR_EACH (mirror, pm_ctx->mirror_table) {
> +            if (!strcmp(pb->mirror_rules[i]->name, mirror->name)) {
> +                  /* Mirror with same name already exists
> +                   * No need to create mirror
> +                   */
> +                  create_mirror = false;
> +                  break;
> +            }
> +        }
> +
> +        if (create_mirror) {
> +
> +            struct smap options = SMAP_INITIALIZER(&options);
> +            char *port_name, *key;
> +
> +            key = xasprintf("%ld",(long int)pb->mirror_rules[i]->index);
> +            smap_add(&options, "remote_ip", pb->mirror_rules[i]->sink);
> +
> +            if (!strcmp(pb->mirror_rules[i]->type, "gre")) {
> +                /* Set the GRE key */
> +                smap_add(&options, "key", key);
> +
> +            } else if (!strcmp(pb->mirror_rules[i]->type, "erspan")) {
> +                /* Set the ERSPAN index */
> +                smap_add(&options, "key", key);
> +                smap_add(&options, "erspan_idx", key);
> +                smap_add(&options, "erspan_ver","1");
> +
> +            }
> +            struct ovsrec_interface *iface =
> +                      ovsrec_interface_insert(pm_ctx->ovs_idl_txn);
> +            port_name = xasprintf("ovn-%s",
> +                                   pb->mirror_rules[i]->name);
> +
> +            ovsrec_interface_set_name(iface, port_name);
> +            ovsrec_interface_set_type(iface, pb->mirror_rules[i]->type);
> +            ovsrec_interface_set_options(iface, &options);
> +
> +            struct ovsrec_port *port =
> +                              ovsrec_port_insert(pm_ctx->ovs_idl_txn);
> +            ovsrec_port_set_name(port, port_name);
> +            ovsrec_port_set_interfaces(port, &iface, 1);
> +
> +            ovsrec_bridge_update_ports_addvalue(pm_ctx->br_int, port);
> +
> +            smap_destroy(&options);
> +            free(port_name);
> +            free(key);
> +
> +            VLOG_INFO("Creating Mirror in OVS DB");
> +            mirror = ovsrec_mirror_insert(pm_ctx->ovs_idl_txn);
> +            ovsrec_mirror_set_name(mirror,pb->mirror_rules[i]->name);
> +            ovsrec_mirror_update_output_port_addvalue(mirror, port);
> +            ovsrec_bridge_update_mirrors_addvalue(pm_ctx->br_int,
> +                                                             mirror);
> +        }
> +
> +        struct local_binding *lbinding = local_binding_find(
> +                               pm_ctx->local_bindings, pb->logical_port);
> +        const struct ovsrec_port *p =
> +                     get_port_for_iface(lbinding->iface, pm_ctx->br_int);
> +        if (p) {
> +            if (!strcmp(pb->mirror_rules[i]->filter,"from-lport")) {
> +                ovsrec_mirror_update_select_src_port_addvalue(mirror, p);
> +            } else if (!strcmp(pb->mirror_rules[i]->filter,"to-lport")) {
> +                ovsrec_mirror_update_select_dst_port_addvalue(mirror, p);
> +            } else {
> +                ovsrec_mirror_update_select_src_port_addvalue(mirror, p);
> +                ovsrec_mirror_update_select_dst_port_addvalue(mirror, p);
> +            }
> +        }
> +    }
> +    return true;
> +}
> +
> +static void
> +check_and_update_mirror_table(const struct sbrec_mirror *sb_mirror,
> +                              struct ovsrec_mirror *ovs_mirror)
> +{
> +    char *filter;
> +    if ((ovs_mirror->n_select_dst_port)
> +            && (ovs_mirror->n_select_src_port)) {
> +        filter = xasprintf("both");
> +    } else if (ovs_mirror->n_select_dst_port) {
> +        filter = xasprintf("to-lport");
> +    } else {
> +        filter = xasprintf("from-lport");
> +    }
> +
> +    if (strcmp(sb_mirror->filter, filter)) {
> +        if (!strcmp(sb_mirror->filter,"from-lport")
> +                              && !strcmp(filter,"both")) {
> +            for (size_t i = 0; i < ovs_mirror->n_select_dst_port; i++) {
> +                ovsrec_mirror_update_select_dst_port_delvalue(ovs_mirror,
> +                                             ovs_mirror->select_dst_port[i]);
> +            }
> +        } else if (!strcmp(sb_mirror->filter,"to-lport")
> +                              && !strcmp(filter,"both")) {
> +            for (size_t i = 0; i < ovs_mirror->n_select_src_port; i++) {
> +                ovsrec_mirror_update_select_src_port_delvalue(ovs_mirror,
> +                                             ovs_mirror->select_src_port[i]);
> +            }
> +        } else if (!strcmp(sb_mirror->filter,"both")
> +                              && !strcmp(filter,"from-lport")) {
> +            for (size_t i = 0; i < ovs_mirror->n_select_src_port; i++) {
> +                ovsrec_mirror_update_select_dst_port_addvalue(ovs_mirror,
> +                                             ovs_mirror->select_src_port[i]);
> +            }
> +        } else if (!strcmp(sb_mirror->filter,"both")
> +                              && !strcmp(filter,"to-lport")) {
> +            for (size_t i = 0; i < ovs_mirror->n_select_dst_port; i++) {
> +                ovsrec_mirror_update_select_src_port_addvalue(ovs_mirror,
> +                                             ovs_mirror->select_dst_port[i]);
> +            }
> +        } else if (!strcmp(sb_mirror->filter,"to-lport")
> +                              && !strcmp(filter,"from-lport")) {
> +            for (size_t i = 0; i < ovs_mirror->n_select_src_port; i++) {
> +                ovsrec_mirror_update_select_dst_port_addvalue(ovs_mirror,
> +                                             ovs_mirror->select_src_port[i]);
> +                ovsrec_mirror_update_select_src_port_delvalue(ovs_mirror,
> +                                             ovs_mirror->select_src_port[i]);
> +            }
> +        } else if (!strcmp(sb_mirror->filter,"from-lport")
> +                              && !strcmp(filter,"to-lport")) {
> +            for (size_t i = 0; i < ovs_mirror->n_select_dst_port; i++) {
> +                ovsrec_mirror_update_select_src_port_addvalue(ovs_mirror,
> +                                             ovs_mirror->select_dst_port[i]);
> +                ovsrec_mirror_update_select_dst_port_delvalue(ovs_mirror,
> +                                             ovs_mirror->select_dst_port[i]);
> +            }
> +        }
> +    }
> +}
> +
> +static void
> +check_and_update_interface_table(const struct sbrec_mirror *sb_mirror,
> +                                   struct ovsrec_mirror *ovs_mirror)
> +{
> +    struct smap options = SMAP_INITIALIZER(&options);
> +    char *key, *type;
> +    struct smap *opts = &ovs_mirror->output_port->interfaces[0]->options;
> +    struct ovsrec_interface *iface =
> +                          ovs_mirror->output_port->interfaces[0];
> +
> +    const char *erspan_ver = smap_get(opts, "erspan_ver");
> +    if (erspan_ver) {
> +        type = xasprintf("erspan");
> +    } else {
> +        type = xasprintf("gre");
> +    }
> +    if (strcmp(type, sb_mirror->type)) {
> +        ovsrec_interface_set_type(iface, sb_mirror->type);
> +    }
> +
> +    key = xasprintf("%ld",(long int)sb_mirror->index);
> +    smap_add(&options, "remote_ip", sb_mirror->sink);
> +
> +    if (!strcmp(sb_mirror->type, "gre")) {
> +        /* Set the GRE key */
> +        smap_add(&options, "key", key);
> +
> +    } else if (!strcmp(sb_mirror->type, "erspan")) {
> +        /* Set the ERSPAN index */
> +        smap_add(&options, "key", key);
> +        smap_add(&options, "erspan_idx", key);
> +        smap_add(&options, "erspan_ver","1");
> +
> +    }
> +
> +    ovsrec_interface_set_options(iface, &options);
> +    smap_destroy(&options);
> +    free(key);
> +
> +}
> +
> +static void
> +mirror_update(const struct sbrec_mirror *sb_mirror,
> +              struct ovsrec_mirror *ovs_mirror)
> +{
> +    check_and_update_interface_table(sb_mirror, ovs_mirror);
> +
> +    check_and_update_mirror_table(sb_mirror, ovs_mirror);
> +}
> +
> +static void
> +mirror_delete(const struct sbrec_port_binding *pb,
> +              struct port_mirror_ctx *pm_ctx,
> +              struct shash *pb_mirror_map,
> +              bool delete_all)
> +{
> +    struct sset pb_mirrors = SSET_INITIALIZER(&pb_mirrors);
> +
> +    if (!delete_all) {
> +        for (size_t i = 0; i < pb->n_mirror_rules ; i++) {
> +            sset_add(&pb_mirrors, pb->mirror_rules[i]->name);
> +        }
> +    }
> +
> +    if (delete_all && (shash_is_empty(pb_mirror_map)) && pb->n_mirror_rules) {
> +        for (size_t i = 0; i < pb->n_mirror_rules ; i++) {
> +
> +            struct ovsrec_mirror *ovs_mirror = NULL;
> +            ovs_mirror = shash_find_data(pm_ctx->ovs_mirrors,
> +                                            pb->mirror_rules[i]->name);
> +            if (ovs_mirror) {
> +                if (ovs_mirror->n_select_dst_port == 0 &&
> +                        ovs_mirror->n_select_src_port == 0) {
> +                    ovsrec_bridge_update_ports_delvalue(pm_ctx->br_int,
> +                                                   ovs_mirror->output_port);
> +                    ovsrec_bridge_update_mirrors_delvalue(pm_ctx->br_int,
> +                                                                ovs_mirror);
> +                    ovsrec_port_delete(ovs_mirror->output_port);
> +                    ovsrec_mirror_delete(ovs_mirror);
> +                }
> +            }
> +        }
> +    }
> +
> +    struct shash_node *mirror_node;
> +    SHASH_FOR_EACH (mirror_node, pb_mirror_map) {
> +        struct ovsrec_mirror *ovs_mirror = mirror_node->data;
> +        if (!sset_find(&pb_mirrors, ovs_mirror->name)) {
> +            /* Find if the mirror has other sources i*/
> +            if ((ovs_mirror->n_select_dst_port > 1) ||
> +                (ovs_mirror->n_select_src_port > 1)) {
> +                /* More than 1 source then just
> +                 * update the mirror table
> +                 */
> +                bool done = false;
> +                for (size_t i = 0; ((i < ovs_mirror->n_select_dst_port)
> +                                                   && (done == false)); i++) {
> +                    const struct ovsrec_port *port_rec =
> +                                               ovs_mirror->select_dst_port[i];
> +                    for (size_t j = 0; j < port_rec->n_interfaces; j++) {
> +                        const struct ovsrec_interface *iface_rec;
> +
> +                        iface_rec = port_rec->interfaces[j];
> +                        const char *iface_id =
> +                                            smap_get(&iface_rec->external_ids,
> +                                                                  "iface-id");
> +                        if (!strcmp(iface_id,pb->logical_port)) {
> +                            ovsrec_mirror_update_select_dst_port_delvalue(
> +                                                        ovs_mirror, port_rec);
> +                            done = true;
> +                            break;
> +                        }
> +                    }
> +                }
> +                done = false;
> +                for (size_t i = 0; ((i < ovs_mirror->n_select_src_port)
> +                                                   && (done == false)); i++) {
> +                    const struct ovsrec_port *port_rec =
> +                                                ovs_mirror->select_src_port[i];
> +                    for (size_t j = 0; j < port_rec->n_interfaces; j++) {
> +                        const struct ovsrec_interface *iface_rec;
> +
> +                        iface_rec = port_rec->interfaces[j];
> +                        const char *iface_id =
> +                                            smap_get(&iface_rec->external_ids,
> +                                                                  "iface-id");
> +                        if (!strcmp(iface_id,pb->logical_port)) {
> +                            ovsrec_mirror_update_select_src_port_delvalue(
> +                                                        ovs_mirror, port_rec);
> +                            done = true;
> +                            break;
> +                        }
> +                    }
> +                }
> +            } else {
> +                /*
> +                 * If only 1 source delete the output port
> +                 * and then delete the mirror completely
> +                 */
> +                VLOG_INFO("Only 1 source for the mirror. Hence delete it");
> +                ovsrec_bridge_update_ports_delvalue(pm_ctx->br_int,
> +                                                    ovs_mirror->output_port);
> +                ovsrec_bridge_update_mirrors_delvalue(pm_ctx->br_int,
> +                                                            ovs_mirror);
> +                ovsrec_port_delete(ovs_mirror->output_port);
> +                ovsrec_mirror_delete(ovs_mirror);
> +            }
> +        }
> +    }
> +
> +    const char *used_node, *used_next;
> +    SSET_FOR_EACH_SAFE (used_node, used_next, &pb_mirrors) {
> +        sset_delete(&pb_mirrors, SSET_NODE_FROM_NAME(used_node));
> +    }
> +    sset_destroy(&pb_mirrors);
> +}
> +
> +static void
> +find_port_specific_mirrors (const struct sbrec_port_binding *pb,
> +                            struct port_mirror_ctx *pm_ctx,
> +                            struct shash *pb_mirror_map)
> +{
> +    const struct ovsrec_mirror *mirror = NULL;
> +
> +    OVSREC_MIRROR_TABLE_FOR_EACH (mirror, pm_ctx->mirror_table) {
> +        for (size_t i = 0; i < mirror->n_select_dst_port; i++) {
> +            const struct ovsrec_port *port_rec = mirror->select_dst_port[i];
> +            for (size_t j = 0; j < port_rec->n_interfaces; j++) {
> +                const struct ovsrec_interface *iface_rec;
> +                iface_rec = port_rec->interfaces[j];
> +                const char *logical_port =
> +                    smap_get(&iface_rec->external_ids, "iface-id");
> +                if (!strcmp(logical_port, pb->logical_port)) {
> +                    shash_add_once(pb_mirror_map, mirror->name, mirror);
> +                }
> +            }
> +        }
> +        for (size_t i = 0; i < mirror->n_select_src_port; i++) {
> +            const struct ovsrec_port *port_rec = mirror->select_src_port[i];
> +            for (size_t j = 0; j < port_rec->n_interfaces; j++) {
> +                const struct ovsrec_interface *iface_rec;
> +                iface_rec = port_rec->interfaces[j];
> +                const char *logical_port =
> +                    smap_get(&iface_rec->external_ids, "iface-id");
> +                if (!strcmp(logical_port, pb->logical_port)) {
> +                    shash_add_once(pb_mirror_map, mirror->name, mirror);
> +                }
> +            }
> +        }
> +    }
> +}
> +
> +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);
> +}
> +
> +
> +void
> +ovn_port_mirror_init(struct shash *ovs_mirrors)
> +{
> +    shash_init(ovs_mirrors);
> +}
> +
> +void
> +ovn_port_mirror_run(struct port_mirror_ctx *pm_ctx)
> +{
> +    const struct sbrec_port_binding *pb;
> +    SBREC_PORT_BINDING_TABLE_FOR_EACH (pb,
> +                                       pm_ctx->port_binding_table) {
> +        ovn_port_mirror_handle_lport(pb, false, pm_ctx);
> +    }
> +}
> +
> +bool
> +ovn_port_mirror_handle_lport(const struct sbrec_port_binding *pb, bool removed,
> +                     struct port_mirror_ctx *pm_ctx)
> +{
> +    bool ret = true;
> +    struct shash port_ovs_mirrors = SHASH_INITIALIZER(&port_ovs_mirrors);
> +
> +    /* Need to find if mirror needs update */
> +    find_port_specific_mirrors(pb, pm_ctx, &port_ovs_mirrors);
> +    if (removed == false) {
> +        if (((pb->n_mirror_rules == 0)
> +              && (shash_is_empty(&port_ovs_mirrors))) ||
> +              (pb->n_mirror_rules == shash_count(&port_ovs_mirrors))) {
> +            /* No mirror update */
> +        } else {
> +            /* Update Mirror */
> +            if (pb->n_mirror_rules > shash_count(&port_ovs_mirrors)) {
> +                /* create mirror,
> +                 * if mirror already exists only update selection
> +                 */
> +                ret = mirror_create(pb, pm_ctx);
> +            } else {
> +                /* delete mirror,
> +                 * if mirror has other sources only update selection
> +                 */
> +                mirror_delete(pb, pm_ctx, &port_ovs_mirrors, false);
> +            }
> +        }
> +    } else {
> +           mirror_delete(pb, pm_ctx, &port_ovs_mirrors, true);
> +    }
> +
> +    struct shash_node *ovs_mirror_node, *ovs_mirror_next;
> +    SHASH_FOR_EACH_SAFE (ovs_mirror_node, ovs_mirror_next,
> +                                              &port_ovs_mirrors) {
> +        shash_delete(&port_ovs_mirrors, ovs_mirror_node);
> +    }
> +    shash_destroy(&port_ovs_mirrors);
> +
> +    return ret;
> +}
> +
> +bool
> +ovn_port_mirror_handle_update(struct port_mirror_ctx *pm_ctx)
> +{
> +    const struct sbrec_mirror *mirror = NULL;
> +    struct ovsrec_mirror *ovs_mirror = NULL;
> +
> +    SBREC_MIRROR_TABLE_FOR_EACH_TRACKED (mirror, pm_ctx->sb_mirror_table) {
> +    /* For each tracked mirror entry check if OVS entry is there*/
> +        ovs_mirror = shash_find_data(pm_ctx->ovs_mirrors, mirror->name);
> +        if (ovs_mirror) {
> +            if (sbrec_mirror_is_deleted(mirror)) {
> +                /* Need to delete the mirror in OVS */
> +                VLOG_INFO("Delete mirror and remove port");
> +                ovsrec_bridge_update_ports_delvalue(pm_ctx->br_int,
> +                                                    ovs_mirror->output_port);
> +                ovsrec_bridge_update_mirrors_delvalue(pm_ctx->br_int,
> +                                                      ovs_mirror);
> +                ovsrec_port_delete(ovs_mirror->output_port);
> +                ovsrec_mirror_delete(ovs_mirror);
> +            } else {
> +                mirror_update(mirror, ovs_mirror);
> +            }
> +        }
> +    }
> +
> +    return true;
> +}
> +
> +void
> +ovn_port_mirror_destroy(struct shash *ovs_mirrors)
> +{
> +    struct shash_node *ovs_mirror_node, *ovs_mirror_next;
> +    SHASH_FOR_EACH_SAFE (ovs_mirror_node, ovs_mirror_next,
> +                                              ovs_mirrors) {
> +        shash_delete(ovs_mirrors, ovs_mirror_node);
> +    }
> +    shash_destroy(ovs_mirrors);
> +}
> +
> diff --git a/controller/mirror.h b/controller/mirror.h
> new file mode 100644
> index 000000000..85b964f55
> --- /dev/null
> +++ b/controller/mirror.h
> @@ -0,0 +1,53 @@
> +/* 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_port_table;
> +struct ovsrec_bridge;
> +struct ovsrec_bridge_table;
> +struct ovsrec_open_vswitch_table;
> +struct sbrec_chassis;
> +struct ovsrec_interface_table;
> +struct ovsrec_mirror_table;
> +struct sbrec_mirror_table;
> +struct sbrec_port_binding_table;
> +
> +struct port_mirror_ctx {
> +    struct shash *ovs_mirrors;
> +    struct ovsdb_idl_txn *ovs_idl_txn;
> +    const struct ovsrec_port_table *port_table;
> +    const struct ovsrec_bridge *br_int;
> +    const struct sbrec_chassis *chassis_rec;
> +    const struct ovsrec_bridge_table *bridge_table;
> +    const struct ovsrec_open_vswitch_table *ovs_table;
> +    const struct ovsrec_interface_table *iface_table;
> +    const struct ovsrec_mirror_table *mirror_table;
> +    const struct sbrec_mirror_table *sb_mirror_table;
> +    const struct sbrec_port_binding_table *port_binding_table;
> +    struct shash *local_bindings;
> +};
> +
> +void mirror_register_ovs_idl(struct ovsdb_idl *);
> +void ovn_port_mirror_init(struct shash *);
> +void ovn_port_mirror_destroy(struct shash *);
> +void ovn_port_mirror_run(struct port_mirror_ctx *pm_ctx);
> +bool ovn_port_mirror_handle_lport(const struct sbrec_port_binding *pb,
> +                                  bool removed,
> +                                  struct port_mirror_ctx *pm_ctx);
> +bool ovn_port_mirror_handle_update(struct port_mirror_ctx *pm_ctx);
> +#endif
> diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c
> index 0b0ccc48a..95307a2d9 100644
> --- a/controller/ovn-controller.c
> +++ b/controller/ovn-controller.c
> @@ -77,6 +77,7 @@
>  #include "stopwatch.h"
>  #include "lib/inc-proc-eng.h"
>  #include "hmapx.h"
> +#include "mirror.h"
>
>  VLOG_DEFINE_THIS_MODULE(main);
>
> @@ -963,6 +964,7 @@ ctrl_register_ovs_idl(struct ovsdb_idl *ovs_idl)
>      bfd_register_ovs_idl(ovs_idl);
>      physical_register_ovs_idl(ovs_idl);
>      vif_plug_register_ovs_idl(ovs_idl);
> +    mirror_register_ovs_idl(ovs_idl);
>  }
>
>  #define SB_NODES \
> @@ -983,6 +985,7 @@ ctrl_register_ovs_idl(struct ovsdb_idl *ovs_idl)
>      SB_NODE(load_balancer, "load_balancer") \
>      SB_NODE(fdb, "fdb") \
>      SB_NODE(meter, "meter") \
> +    SB_NODE(mirror, "mirror") \
>      SB_NODE(static_mac_binding, "static_mac_binding")
>
>  enum sb_engine_node {
> @@ -1000,7 +1003,8 @@ enum sb_engine_node {
>      OVS_NODE(bridge, "bridge") \
>      OVS_NODE(port, "port") \
>      OVS_NODE(interface, "interface") \
> -    OVS_NODE(qos, "qos")
> +    OVS_NODE(qos, "qos") \
> +    OVS_NODE(mirror, "mirror")
>
>  enum ovs_engine_node {
>  #define OVS_NODE(NAME, NAME_STR) OVS_##NAME,
> @@ -2326,6 +2330,203 @@ load_balancers_by_dp_cleanup(struct hmap *lbs)
>      free(lbs);
>  }
>
> +/* Mirror Engine */
> +struct ed_type_port_mirror {
> +    struct shash ovs_mirrors;
> +};
> +
> +static void *
> +en_port_mirror_init(struct engine_node *node OVS_UNUSED,
> +                    struct engine_arg *arg OVS_UNUSED)
> +{
> +    struct ed_type_port_mirror *port_mirror = xzalloc(sizeof *port_mirror);
> +    ovn_port_mirror_init(&port_mirror->ovs_mirrors);
> +    return port_mirror;
> +}
> +
> +static void
> +en_port_mirror_cleanup(void *data)
> +{
> +    struct ed_type_port_mirror *port_mirror = data;
> +    ovn_port_mirror_destroy(&port_mirror->ovs_mirrors);
> +}
> +
> +static void
> +init_port_mirror_ctx(struct engine_node *node,
> +                 struct ed_type_runtime_data *rt_data,
> +                 struct ed_type_port_mirror *port_mirror_data,
> +                 struct port_mirror_ctx *pm_ctx)
> +{
> +    struct ovsrec_open_vswitch_table *ovs_table =
> +        (struct ovsrec_open_vswitch_table *)EN_OVSDB_GET(
> +            engine_get_input("OVS_open_vswitch", node));
> +    struct ovsrec_bridge_table *bridge_table =
> +        (struct ovsrec_bridge_table *)EN_OVSDB_GET(
> +            engine_get_input("OVS_bridge", node));
> +    const char *chassis_id = get_ovs_chassis_id(ovs_table);
> +    const struct ovsrec_bridge *br_int = get_br_int(bridge_table, ovs_table);
> +
> +    ovs_assert(br_int && chassis_id);
> +    const struct sbrec_chassis *chassis = NULL;
> +    struct ovsdb_idl_index *sbrec_chassis_by_name =
> +        engine_ovsdb_node_get_index(
> +                engine_get_input("SB_chassis", node),
> +                "name");
> +
> +    if (chassis_id) {
> +        chassis = chassis_lookup_by_name(sbrec_chassis_by_name, chassis_id);
> +    }
> +    ovs_assert(chassis);
> +
> +    struct ovsrec_port_table *port_table =
> +        (struct ovsrec_port_table *)EN_OVSDB_GET(
> +            engine_get_input("OVS_port", node));
> +
> +    struct ed_type_ovs_interface_shadow *iface_shadow =
> +        engine_get_input_data("ovs_interface_shadow", node);
> +
> +    struct ovsrec_mirror_table *mirror_table =
> +        (struct ovsrec_mirror_table *)EN_OVSDB_GET(
> +            engine_get_input("OVS_mirror", node));
> +
> +    struct sbrec_port_binding_table *pb_table =
> +        (struct sbrec_port_binding_table *)EN_OVSDB_GET(
> +            engine_get_input("SB_port_binding", node));
> +
> +    struct sbrec_mirror_table *sb_mirror_table =
> +        (struct sbrec_mirror_table *)EN_OVSDB_GET(
> +            engine_get_input("SB_mirror", node));
> +
> +    struct shash_node *ovs_mirror_node, *ovs_mirror_next;
> +    SHASH_FOR_EACH_SAFE (ovs_mirror_node, ovs_mirror_next,
> +                           &port_mirror_data->ovs_mirrors) {
> +        shash_delete(&port_mirror_data->ovs_mirrors, ovs_mirror_node);
> +    }
> +
> +    const struct ovsrec_mirror *ovsmirror = NULL;
> +    OVSREC_MIRROR_TABLE_FOR_EACH (ovsmirror, mirror_table) {
> +       shash_add(&port_mirror_data->ovs_mirrors, ovsmirror->name, ovsmirror);
> +    }
> +
> +    pm_ctx->ovs_idl_txn = engine_get_context()->ovs_idl_txn;
> +    pm_ctx->port_table = port_table;
> +    pm_ctx->iface_table = iface_shadow->iface_table;
> +    pm_ctx->mirror_table = mirror_table;
> +    pm_ctx->port_binding_table = pb_table;
> +    pm_ctx->sb_mirror_table = sb_mirror_table;
> +    pm_ctx->br_int = br_int;
> +    pm_ctx->chassis_rec = chassis;
> +    pm_ctx->bridge_table = bridge_table;
> +    pm_ctx->ovs_table = ovs_table;
> +    pm_ctx->ovs_mirrors = &port_mirror_data->ovs_mirrors;
> +    pm_ctx->local_bindings = &rt_data->lbinding_data.bindings;
> +}
> +
> +static void
> +en_port_mirror_run(struct engine_node *node, void *data)
> +{
> +    struct port_mirror_ctx pm_ctx;
> +    struct ed_type_port_mirror *port_mirror_data = data;
> +    struct ed_type_runtime_data *rt_data =
> +        engine_get_input_data("runtime_data", node);
> +
> +    init_port_mirror_ctx(node, rt_data, port_mirror_data, &pm_ctx);
> +
> +    ovn_port_mirror_run(&pm_ctx);
> +    engine_set_node_state(node, EN_UPDATED);
> +}
> +
> +static bool
> +port_mirror_runtime_data_handler(struct engine_node *node, void *data)
> +{
> +    struct ed_type_runtime_data *rt_data =
> +        engine_get_input_data("runtime_data", node);
> +
> +    /* There is no tracked data. Fall back to full recompute of port_mirror */
> +    if (!rt_data->tracked) {
> +        return false;
> +    }
> +
> +    struct hmap *tracked_dp_bindings = &rt_data->tracked_dp_bindings;
> +    if (hmap_is_empty(tracked_dp_bindings)) {
> +        return true;
> +    }
> +
> +    struct port_mirror_ctx pm_ctx;
> +    struct ed_type_port_mirror *port_mirror_data = data;
> +    init_port_mirror_ctx(node, rt_data, port_mirror_data, &pm_ctx);
> +
> +    struct tracked_datapath *tdp;
> +    HMAP_FOR_EACH (tdp, node, tracked_dp_bindings) {
> +        if (tdp->tracked_type != TRACKED_RESOURCE_UPDATED) {
> +            /* Fall back to full recompute when a local datapath
> +             * is added or deleted. */
> +            return false;
> +        }
> +
> +        struct shash_node *shash_node;
> +        SHASH_FOR_EACH (shash_node, &tdp->lports) {
> +            struct tracked_lport *lport = shash_node->data;
> +            bool removed =
> +                lport->tracked_type == TRACKED_RESOURCE_REMOVED ? true: false;
> +            if (!ovn_port_mirror_handle_lport(lport->pb, removed, &pm_ctx)) {
> +                return false;
> +            }
> +        }
> +    }
> +
> +    engine_set_node_state(node, EN_UPDATED);
> +    return true;
> +}
> +
> +static bool
> +port_mirror_port_binding_handler(struct engine_node *node, void *data)
> +{
> +    struct port_mirror_ctx pm_ctx;
> +    struct ed_type_port_mirror *port_mirror_data = data;
> +    struct ed_type_runtime_data *rt_data =
> +        engine_get_input_data("runtime_data", node);
> +
> +    init_port_mirror_ctx(node, rt_data, port_mirror_data, &pm_ctx);
> +
> +    /* handle port binding updates (i.,e when the mirror column
> +     * of port_binding is updated)
> +     */
> +    const struct sbrec_port_binding *pb;
> +    SBREC_PORT_BINDING_TABLE_FOR_EACH_TRACKED (pb,
> +                                               pm_ctx.port_binding_table) {
> +        bool removed = sbrec_port_binding_is_deleted(pb);
> +        if (!ovn_port_mirror_handle_lport(pb, removed, &pm_ctx)) {
> +            return false;
> +        }
> +    }
> +
> +    engine_set_node_state(node, EN_UPDATED);
> +    return true;
> +
> +}
> +
> +static bool
> +port_mirror_sb_mirror_handler(struct engine_node *node, void *data)
> +{
> +    struct port_mirror_ctx pm_ctx;
> +    struct ed_type_port_mirror *port_mirror_data = data;
> +    struct ed_type_runtime_data *rt_data =
> +        engine_get_input_data("runtime_data", node);
> +
> +    init_port_mirror_ctx(node, rt_data, port_mirror_data, &pm_ctx);
> +
> +    /* handle sb mirror updates
> +     */
> +    if (!ovn_port_mirror_handle_update(&pm_ctx)) {
> +        return false;
> +    }
> +
> +    engine_set_node_state(node, EN_UPDATED);
> +    return true;
> +
> +}
> +
>  /* Engine node which is used to handle the Non VIF data like
>   *   - OVS patch ports
>   *   - Tunnel ports and the related chassis information.
> @@ -3609,6 +3810,7 @@ main(int argc, char *argv[])
>      ENGINE_NODE_WITH_CLEAR_TRACK_DATA(addr_sets, "addr_sets");
>      ENGINE_NODE_WITH_CLEAR_TRACK_DATA(port_groups, "port_groups");
>      ENGINE_NODE(northd_options, "northd_options");
> +    ENGINE_NODE(port_mirror, "port_mirror");
>
>  #define SB_NODE(NAME, NAME_STR) ENGINE_NODE_SB(NAME, NAME_STR);
>      SB_NODES
> @@ -3763,6 +3965,22 @@ main(int argc, char *argv[])
>      engine_add_input(&en_flow_output, &en_pflow_output,
>                       flow_output_pflow_output_handler);
>
> +    engine_add_input(&en_port_mirror, &en_ovs_open_vswitch, NULL);
> +    engine_add_input(&en_port_mirror, &en_ovs_bridge, NULL);
> +    engine_add_input(&en_port_mirror, &en_ovs_mirror, NULL);
> +    engine_add_input(&en_port_mirror, &en_sb_chassis, NULL);
> +    engine_add_input(&en_port_mirror, &en_ovs_port, engine_noop_handler);
> +    engine_add_input(&en_port_mirror, &en_ovs_interface_shadow,
> +                     engine_noop_handler);
> +    engine_add_input(&en_flow_output, &en_port_mirror,
> +                     engine_noop_handler);
> +    engine_add_input(&en_port_mirror, &en_runtime_data,
> +                     port_mirror_runtime_data_handler);
> +    engine_add_input(&en_port_mirror, &en_sb_mirror,
> +                     port_mirror_sb_mirror_handler);
> +    engine_add_input(&en_port_mirror, &en_sb_port_binding,
> +                     port_mirror_port_binding_handler);
> +
>      struct engine_arg engine_arg = {
>          .sb_idl = ovnsb_idl_loop.idl,
>          .ovs_idl = ovs_idl_loop.idl,
> @@ -4082,6 +4300,8 @@ main(int argc, char *argv[])
>                                      ovnsb_idl_loop.idl),
>                                  sbrec_sb_global_table_get(ovnsb_idl_loop.idl));
>                          stopwatch_stop(BFD_RUN_STOPWATCH_NAME, time_msec());
> +                    } else {
> +                        engine_run(false);

Why do you need it here?

>                      }
>
>                      runtime_data = engine_get_data(&en_runtime_data);
> diff --git a/northd/en-northd.c b/northd/en-northd.c
> index 4907a1ff2..74cac1b8e 100644
> --- a/northd/en-northd.c
> +++ b/northd/en-northd.c
> @@ -78,6 +78,8 @@ void en_northd_run(struct engine_node *node, void *data)
>          EN_OVSDB_GET(engine_get_input("NB_acl", node));
>      input_data.nbrec_static_mac_binding_table =
>          EN_OVSDB_GET(engine_get_input("NB_static_mac_binding", node));
> +    input_data.nbrec_mirror_table =
> +        EN_OVSDB_GET(engine_get_input("NB_mirror", node));
>
>      input_data.sbrec_sb_global_table =
>          EN_OVSDB_GET(engine_get_input("SB_sb_global", node));
> @@ -111,6 +113,8 @@ void en_northd_run(struct engine_node *node, void *data)
>          EN_OVSDB_GET(engine_get_input("SB_chassis_private", node));
>      input_data.sbrec_static_mac_binding_table =
>          EN_OVSDB_GET(engine_get_input("SB_static_mac_binding", node));
> +    input_data.sbrec_mirror_table =
> +        EN_OVSDB_GET(engine_get_input("SB_mirror", node));
>
>      northd_run(&input_data, data,
>                 eng_ctx->ovnnb_idl_txn,
> diff --git a/northd/inc-proc-northd.c b/northd/inc-proc-northd.c
> index 54e0ad3b0..ac27a730e 100644
> --- a/northd/inc-proc-northd.c
> +++ b/northd/inc-proc-northd.c
> @@ -50,6 +50,7 @@ VLOG_DEFINE_THIS_MODULE(inc_proc_northd);
>      NB_NODE(acl, "acl") \
>      NB_NODE(logical_router, "logical_router") \
>      NB_NODE(qos, "qos") \
> +    NB_NODE(mirror, "mirror") \
>      NB_NODE(meter, "meter") \
>      NB_NODE(meter_band, "meter_band") \
>      NB_NODE(logical_router_port, "logical_router_port") \
> @@ -92,6 +93,7 @@ VLOG_DEFINE_THIS_MODULE(inc_proc_northd);
>      SB_NODE(logical_flow, "logical_flow") \
>      SB_NODE(logical_dp_group, "logical_DP_group") \
>      SB_NODE(multicast_group, "multicast_group") \
> +    SB_NODE(mirror, "mirror") \
>      SB_NODE(meter, "meter") \
>      SB_NODE(meter_band, "meter_band") \
>      SB_NODE(datapath_binding, "datapath_binding") \
> @@ -172,6 +174,7 @@ void inc_proc_northd_init(struct ovsdb_idl_loop *nb,
>      engine_add_input(&en_northd, &en_nb_acl, NULL);
>      engine_add_input(&en_northd, &en_nb_logical_router, NULL);
>      engine_add_input(&en_northd, &en_nb_qos, NULL);
> +    engine_add_input(&en_northd, &en_nb_mirror, NULL);
>      engine_add_input(&en_northd, &en_nb_meter, NULL);
>      engine_add_input(&en_northd, &en_nb_meter_band, NULL);
>      engine_add_input(&en_northd, &en_nb_logical_router_port, NULL);
> @@ -194,6 +197,7 @@ void inc_proc_northd_init(struct ovsdb_idl_loop *nb,
>      engine_add_input(&en_northd, &en_sb_address_set, NULL);
>      engine_add_input(&en_northd, &en_sb_port_group, NULL);
>      engine_add_input(&en_northd, &en_sb_logical_dp_group, NULL);
> +    engine_add_input(&en_northd, &en_sb_mirror, NULL);
>      engine_add_input(&en_northd, &en_sb_meter, NULL);
>      engine_add_input(&en_northd, &en_sb_meter_band, NULL);
>      engine_add_input(&en_northd, &en_sb_datapath_binding, NULL);
> diff --git a/northd/northd.c b/northd/northd.c
> index 7e2681865..74314b5dc 100644
> --- a/northd/northd.c
> +++ b/northd/northd.c
> @@ -3214,6 +3214,51 @@ ovn_port_update_sbrec_chassis(
>      free(requested_chassis_sb);
>  }
>
> +static void
> +sbrec_port_binding_update_mirror_rules(struct northd_input *input_data,
> +                                       const struct ovn_port *op)
> +{
> +    size_t i = 0;
> +    if (op->sb->n_mirror_rules > op->nbsp->n_mirror_rules) {
> +        /* Needs deletion in SB */
> +        struct shash nb_mirror_rules = SHASH_INITIALIZER(&nb_mirror_rules);
> +        for (i = 0; i < op->nbsp->n_mirror_rules; i++) {
> +            shash_add(&nb_mirror_rules,
> +                                 op->nbsp->mirror_rules[i]->name,
> +                                 op->nbsp->mirror_rules[i]);
> +        }
> +
> +        for (i = 0; i < op->sb->n_mirror_rules; i++) {
> +            if (!shash_find(&nb_mirror_rules,
> +                           op->sb->mirror_rules[i]->name)) {
> +                    sbrec_port_binding_update_mirror_rules_delvalue(op->sb,
> +                                                 op->sb->mirror_rules[i]);
> +            }
> +        }
> +
> +        struct shash_node *node, *next;
> +        SHASH_FOR_EACH_SAFE (node, next, &nb_mirror_rules) {
> +            shash_delete(&nb_mirror_rules, node);
> +        }
> +        shash_destroy(&nb_mirror_rules);
> +
> +    } else {
> +        /* Needs addition in SB */

Not necessarily. What if it's ==? (You still need to handle this case,
for when the number of mirror references is the same but some of them
refer to different mirrors.)

> +        for (i = 0; i < op->nbsp->n_mirror_rules; i++) {
> +            const struct sbrec_mirror *sb_mirror;
> +            SBREC_MIRROR_TABLE_FOR_EACH (sb_mirror,
> +                                         input_data->sbrec_mirror_table) {
> +                if (!strcmp(sb_mirror->name,
> +                            op->nbsp->mirror_rules[i]->name)) {
> +                    /* Add the value to SB */
> +                    sbrec_port_binding_update_mirror_rules_addvalue(op->sb,
> +                                                                    sb_mirror);
> +                }
> +            }
> +        }
> +    }
> +}
> +
>  static void
>  ovn_port_update_sbrec(struct northd_input *input_data,
>                        struct ovsdb_idl_txn *ovnsb_txn,
> @@ -3573,6 +3618,17 @@ ovn_port_update_sbrec(struct northd_input *input_data,
>          }
>          sbrec_port_binding_set_external_ids(op->sb, &ids);
>          smap_destroy(&ids);
> +
> +        if (!op->nbsp->n_mirror_rules) {
> +            /* Nothing is set. Clear mirror_rules from pb. */
> +            sbrec_port_binding_set_mirror_rules(op->sb, NULL, 0);
> +        } else {
> +            /* Check if SB DB update needed */
> +            if (op->nbsp->n_mirror_rules != op->sb->n_mirror_rules) {
> +                sbrec_port_binding_update_mirror_rules(input_data, op);
> +            }
> +        }
> +
>      }
>      if (op->tunnel_key != op->sb->tunnel_key) {
>          sbrec_port_binding_set_tunnel_key(op->sb, op->tunnel_key);
> @@ -14877,6 +14933,85 @@ sync_meters(struct northd_input *input_data,
>      shash_destroy(&sb_meters);
>  }
>
> +static bool
> +mirror_needs_update(const struct nbrec_mirror *nb_mirror,
> +                  const struct sbrec_mirror *sb_mirror)
> +{
> +
> +    if (nb_mirror->index != sb_mirror->index) {
> +        return true;
> +    } else if (strcmp(nb_mirror->sink, sb_mirror->sink)) {
> +        return true;
> +    } else if (strcmp(nb_mirror->type, sb_mirror->type)) {
> +        return true;
> +    } else if (strcmp(nb_mirror->filter, sb_mirror->filter)) {
> +        return true;
> +    }
> +
> +    return false;
> +}
> +
> +static void
> +sync_mirrors_iterate_nb_mirror(struct ovsdb_idl_txn *ovnsb_txn,
> +                             const char *mirror_name,
> +                             const struct nbrec_mirror *nb_mirror,
> +                             struct shash *sb_mirrors,
> +                             struct sset *used_sb_mirrors)
> +{
> +    const struct sbrec_mirror *sb_mirror;
> +    bool new_sb_mirror = false;
> +
> +    sb_mirror = shash_find_data(sb_mirrors, mirror_name);
> +    if (!sb_mirror) {
> +        sb_mirror = sbrec_mirror_insert(ovnsb_txn);
> +        sbrec_mirror_set_name(sb_mirror, mirror_name);
> +        shash_add(sb_mirrors, sb_mirror->name, sb_mirror);
> +        new_sb_mirror = true;
> +    }
> +    sset_add(used_sb_mirrors, mirror_name);
> +
> +    if ((new_sb_mirror) || mirror_needs_update(nb_mirror, sb_mirror)) {
> +        sbrec_mirror_set_filter(sb_mirror,nb_mirror->filter);
> +        sbrec_mirror_set_index(sb_mirror, nb_mirror->index);
> +        sbrec_mirror_set_sink(sb_mirror, nb_mirror->sink);
> +        sbrec_mirror_set_type(sb_mirror, nb_mirror->type);
> +    }
> +}
> +
> +static void
> +sync_mirrors(struct northd_input *input_data,
> +            struct ovsdb_idl_txn *ovnsb_txn)
> +{
> +    struct shash sb_mirrors = SHASH_INITIALIZER(&sb_mirrors);
> +    struct sset used_sb_mirrors = SSET_INITIALIZER(&used_sb_mirrors);
> +
> +    const struct sbrec_mirror *sb_mirror;
> +    SBREC_MIRROR_TABLE_FOR_EACH (sb_mirror, input_data->sbrec_mirror_table) {
> +        shash_add(&sb_mirrors, sb_mirror->name, sb_mirror);
> +    }
> +
> +    const struct nbrec_mirror *nb_mirror;
> +    NBREC_MIRROR_TABLE_FOR_EACH (nb_mirror, input_data->nbrec_mirror_table) {
> +        sync_mirrors_iterate_nb_mirror(ovnsb_txn, nb_mirror->name, nb_mirror,
> +                                     &sb_mirrors, &used_sb_mirrors);
> +    }
> +
> +    const char *used_mirror;
> +    const char *used_mirror_next;
> +    SSET_FOR_EACH_SAFE (used_mirror, used_mirror_next, &used_sb_mirrors) {
> +        shash_find_and_delete(&sb_mirrors, used_mirror);
> +        sset_delete(&used_sb_mirrors, SSET_NODE_FROM_NAME(used_mirror));
> +    }
> +    sset_destroy(&used_sb_mirrors);
> +
> +    struct shash_node *node, *next;
> +    SHASH_FOR_EACH_SAFE (node, next, &sb_mirrors) {
> +        sbrec_mirror_delete(node->data);
> +        shash_delete(&sb_mirrors, node);
> +    }
> +    shash_destroy(&sb_mirrors);
> +}
> +
>  /*
>   * struct 'dns_info' is used to sync the DNS records between OVN Northbound db
>   * and Southbound db.
> @@ -15497,6 +15632,7 @@ ovnnb_db_run(struct northd_input *input_data,
>      sync_address_sets(input_data, ovnsb_txn, &data->datapaths);
>      sync_port_groups(input_data, ovnsb_txn, &data->port_groups);
>      sync_meters(input_data, ovnsb_txn, &data->meter_groups);
> +    sync_mirrors(input_data, ovnsb_txn);
>      sync_dns_entries(input_data, ovnsb_txn, &data->datapaths);
>      cleanup_stale_fdb_entries(input_data, &data->datapaths);
>      stopwatch_stop(CLEAR_LFLOWS_CTX_STOPWATCH_NAME, time_msec());
> diff --git a/northd/northd.h b/northd/northd.h
> index d9856af97..8a4f6a428 100644
> --- a/northd/northd.h
> +++ b/northd/northd.h
> @@ -34,6 +34,7 @@ struct northd_input {
>      const struct nbrec_acl_table *nbrec_acl_table;
>      const struct nbrec_static_mac_binding_table
>          *nbrec_static_mac_binding_table;
> +    const struct nbrec_mirror_table *nbrec_mirror_table;
>
>      /* Southbound table references */
>      const struct sbrec_sb_global_table *sbrec_sb_global_table;
> @@ -53,6 +54,7 @@ struct northd_input {
>      const struct sbrec_chassis_private_table *sbrec_chassis_private_table;
>      const struct sbrec_static_mac_binding_table
>          *sbrec_static_mac_binding_table;
> +    const struct sbrec_mirror_table *sbrec_mirror_table;
>
>      /* Indexes */
>      struct ovsdb_idl_index *sbrec_chassis_by_name;
> diff --git a/ovn-nb.ovsschema b/ovn-nb.ovsschema
> index 174364c8b..01de55222 100644
> --- a/ovn-nb.ovsschema
> +++ b/ovn-nb.ovsschema
> @@ -1,7 +1,7 @@
>  {
>      "name": "OVN_Northbound",
> -    "version": "6.3.0",
> -    "cksum": "4042813038 31869",
> +    "version": "6.4.0",
> +    "cksum": "589874483 33352",
>      "tables": {
>          "NB_Global": {
>              "columns": {
> @@ -132,6 +132,11 @@
>                                              "refType": "weak"},
>                                   "min": 0,
>                                   "max": 1}},
> +                "mirror_rules": {"type": {"key": {"type": "uuid",
> +                                          "refTable": "Mirror",
> +                                          "refType": "weak"},
> +                                  "min": 0,
> +                                  "max": "unlimited"}},
>                  "ha_chassis_group": {
>                      "type": {"key": {"type": "uuid",
>                                       "refTable": "HA_Chassis_Group",
> @@ -301,6 +306,28 @@
>                      "type": {"key": "string", "value": "string",
>                               "min": 0, "max": "unlimited"}}},
>              "isRoot": false},
> +        "Mirror": {
> +            "columns": {
> +                "name": {"type": "string"},
> +                "filter": {"type": {"key": {"type": "string",
> +                                            "enum": ["set", ["from-lport",
> +                                                             "to-lport",
> +                                                             "both"]]}}},
> +                "sink":{"type": "string"},
> +                "type": {"type": {"key": {"type": "string",
> +                                            "enum": ["set", ["gre",
> +                                                             "erspan"]]}}},
> +                "index": {"type": "integer"},

Is "index" ERSPAN specific? Should we pick a name that is less
protocol dependent? Maybe "key" would work better since we already use
it in other places for tunnel keys and whatnot?

> +                "src": {"type": {"key": {"type": "uuid",
> +                                           "refTable": "Logical_Switch_Port",
> +                                           "refType": "weak"},
> +                                   "min": 0,
> +                                   "max": "unlimited"}},
> +                "external_ids": {
> +                    "type": {"key": "string", "value": "string",
> +                             "min": 0, "max": "unlimited"}}},

NB contains external_ids but not SB. Is it used for anything in NB or
is it just a placeholder to avoid schema changes later? Should we have
a similar placeholder for SB side too?

> +            "indexes": [["name"]],
> +            "isRoot": true},
>          "Meter": {
>              "columns": {
>                  "name": {"type": "string"},
> diff --git a/ovn-nb.xml b/ovn-nb.xml
> index 7fe88af27..d410301e7 100644
> --- a/ovn-nb.xml
> +++ b/ovn-nb.xml
> @@ -1554,6 +1554,11 @@
>        </column>
>      </group>
>
> +    <column name="mirror_rules">
> +        Mirror rules that apply to logical switch port which is the source.
> +        Please see the <ref table="Mirror"/> table.
> +    </column>
> +
>      <column name="ha_chassis_group">
>        References a row in the OVN Northbound database's
>        <ref table="HA_Chassis_Group" db="OVN_Northbound"/> table.
> @@ -2491,6 +2496,58 @@
>      </column>
>    </table>
>
> +  <table name="Mirror" title="Mirror Entry">
> +    <p>
> +      Each row in this table represents one Mirror that can be used for
> +      port mirroring. These Mirrors are referenced by the
> +      <ref column="mirror_rules" table="Logical_Switch_Port"/> column in
> +      the <ref table="Logical_Switch_Port"/> table.
> +    </p>
> +
> +    <column name="name">
> +      <p>
> +        Represents the name of the mirror.
> +      </p>
> +    </column>
> +
> +    <column name="filter">
> +      <p>
> +        The value of this field represents selection criteria of the mirror.

Please list supported values and explain what they mean.

> +      </p>
> +    </column>
> +
> +    <column name="sink">
> +      <p>
> +        The value of this field represents the destination/sink of the mirror.

Perhaps explain that it's IP address? And since it's an IP address,
should we maybe rename the field into sink_ip to be more explicit?

> +      </p>
> +    </column>
> +
> +    <column name="type">
> +      <p>
> +        The value of this field represents the type of the tunnel used for
> +        sending the mirrored packets

Perhaps you could list supported values here and explain what they are?

> +      </p>
> +    </column>
> +
> +    <column name="index">
> +      <p>
> +        The value of this field represents the key/idx depending on the
> +        tunnel type configured

A reader may be confused as to what "idx" is. Is it a ERSPAN term?

> +      </p>
> +    </column>
> +
> +    <column name="src">
> +      <p>
> +          The value of this field represents the source port for the mirror.

AFAIU it represent (a list of) ports, no?

> +          Please see the <ref table="Logical_Switch_Port"/> table.
> +      </p>
> +    </column>
> +
> +    <column name="external_ids">
> +      See <em>External IDs</em> at the beginning of this document.
> +    </column>
> +  </table>
> +
>    <table name="Meter" title="Meter entry">
>      <p>
>        Each row in this table represents a meter that can be used for QoS or
> diff --git a/ovn-sb.ovsschema b/ovn-sb.ovsschema
> index 576ebbdeb..45431a18b 100644
> --- a/ovn-sb.ovsschema
> +++ b/ovn-sb.ovsschema
> @@ -1,7 +1,7 @@
>  {
>      "name": "OVN_Southbound",
> -    "version": "20.25.0",
> -    "cksum": "53184112 28845",
> +    "version": "20.26.0",
> +    "cksum": "3740716587 29844",
>      "tables": {
>          "SB_Global": {
>              "columns": {
> @@ -142,6 +142,20 @@
>              "indexes": [["datapath", "tunnel_key"],
>                          ["datapath", "name"]],
>              "isRoot": true},
> +        "Mirror": {
> +            "columns": {
> +                "name": {"type": "string"},
> +                "filter": {"type": {"key": {"type": "string",
> +                                            "enum": ["set",
> +                                                     ["from-lport",
> +                                                      "to-lport","both"]]}}},
> +                "sink":{"type": "string"},
> +                "type": {"type": {"key": {"type": "string",
> +                                            "enum": ["set",
> +                                                     ["gre", "erspan"]]}}},
> +                "index": {"type": "integer"}},
> +            "indexes": [["name"]],
> +            "isRoot": true},
>          "Meter": {
>              "columns": {
>                  "name": {"type": "string"},
> @@ -230,6 +244,11 @@
>                                                        "refTable": "Encap",
>                                                        "refType": "weak"},
>                                      "min": 0, "max": "unlimited"}},
> +                "mirror_rules": {"type": {"key": {"type": "uuid",
> +                                          "refTable": "Mirror",
> +                                          "refType": "weak"},
> +                                  "min": 0,
> +                                  "max": "unlimited"}},
>                  "mac": {"type": {"key": "string",
>                                   "min": 0,
>                                   "max": "unlimited"}},
> diff --git a/ovn-sb.xml b/ovn-sb.xml
> index 37a709f83..24af40b7f 100644
> --- a/ovn-sb.xml
> +++ b/ovn-sb.xml
> @@ -2742,6 +2742,47 @@ tcp.flags = RST;
>      </column>
>    </table>
>
> +  <table name="Mirror" title="Mirror Entry">
> +    <p>
> +      Each row in this table represents one Mirror that can be used for
> +      port mirroring. These Mirrors are referenced by the
> +      <ref column="mirror_rules" table="Port_Binding"/> column in
> +      the <ref table="Port_Binding"/> table.
> +    </p>
> +
> +    <column name="name">
> +      <p>
> +        Represents the name of the mirror.
> +      </p>
> +    </column>
> +
> +    <column name="filter">
> +      <p>
> +        The value of this field represents selection criteria of the mirror.
> +      </p>
> +    </column>
> +
> +    <column name="sink">
> +      <p>
> +        The value of this field represents the destination/sink of the mirror.
> +      </p>
> +    </column>
> +
> +    <column name="type">
> +      <p>
> +        The value of this field represents the type of the tunnel used for
> +        sending the mirrored packets
> +      </p>
> +    </column>
> +
> +    <column name="index">
> +      <p>
> +        The value of this field represents the key/idx depending on the
> +        tunnel type configured
> +      </p>
> +    </column>
> +  </table>
> +
>    <table name="Meter" title="Meter entry">
>      <p>
>        Each row in this table represents a meter that can be used for QoS or
> @@ -3244,6 +3285,11 @@ tcp.flags = RST;
>        </column>
>      </group>
>
> +    <column name="mirror_rules">
> +        Mirror rules that apply to the port binding.
> +        Please see the <ref table="Mirror"/> table.
> +    </column>
> +
>      <group title="Patch Options">
>        <p>
>          These options apply to logical ports with <ref column="type"/> of
> diff --git a/tests/ovn-nbctl.at b/tests/ovn-nbctl.at
> index 726efa6f4..8f6ee6097 100644
> --- a/tests/ovn-nbctl.at
> +++ b/tests/ovn-nbctl.at
> @@ -435,6 +435,101 @@ AT_CHECK([ovn-nbctl meter-list], [0], [dnl
>
>  dnl ---------------------------------------------------------------------
>
> +OVN_NBCTL_TEST([ovn_nbctl_mirrors], [mirrors], [
> +AT_CHECK([ovn-nbctl mirror-add mirror1 gre 0 from-lport 10.10.10.1])
> +AT_CHECK([ovn-nbctl mirror-add mirror2 erspan 1 both 10.10.10.2])
> +AT_CHECK([ovn-nbctl mirror-add mirror3 gre 2 to-lport 10.10.10.3])
> +AT_CHECK([ovn-nbctl ls-add sw0])
> +AT_CHECK([ovn-nbctl lsp-add sw0 sw0-port1])
> +AT_CHECK([ovn-nbctl lsp-add sw0 sw0-port2])
> +AT_CHECK([ovn-nbctl lsp-add sw0 sw0-port3])
> +
> +dnl Add duplicate mirror name
> +AT_CHECK([ovn-nbctl mirror-add mirror1 gre 0 from-lport 10.10.10.5], [1], [], [stderr])
> +AT_CHECK([grep 'already exists' stderr], [0], [ignore])
> +
> +dnl Add mirror with invalid sink port
> +AT_CHECK([ovn-nbctl mirror-add mirror1 gre 0 from-lport sw0-port4], [1], [], [stderr])
> +AT_CHECK([grep 'already exists' stderr], [0], [ignore])

The error message here seems to be not about an invalid port.

> +
> +dnl Attach source port to mirror
> +AT_CHECK([ovn-nbctl lsp-attach-mirror sw0-port1 mirror3])
> +
> +dnl Attach one more source port to mirror
> +AT_CHECK([ovn-nbctl lsp-attach-mirror sw0-port3 mirror3])
> +
> +dnl Attach invalid source port to mirror
> +AT_CHECK([ovn-nbctl lsp-attach-mirror sw0-port4 mirror3], [1], [], [stderr])
> +AT_CHECK([grep 'port name not found' stderr], [0], [ignore])
> +
> +dnl Detach one source port from mirror
> +AT_CHECK([ovn-nbctl lsp-detach-mirror sw0-port3 mirror3])
> +
> +AT_CHECK([ovn-nbctl mirror-list], [0], [dnl
> +mirror1:
> +  Type     :  gre
> +  Sink     :  10.10.10.1
> +  Filter   :  from-lport
> +  Index/Key:  0
> +  Sources  :  None attached
> +mirror2:
> +  Type     :  erspan
> +  Sink     :  10.10.10.2
> +  Filter   :  both
> +  Index/Key:  1
> +  Sources  :  None attached
> +mirror3:
> +  Type     :  gre
> +  Sink     :  10.10.10.3
> +  Filter   :  to-lport
> +  Index/Key:  2
> +  Sources  :  sw0-port1
> +])

We should also check the case where multiple ports are attached to the
same mirror.

> +
> +dnl Detach another source port from mirror
> +AT_CHECK([ovn-nbctl lsp-detach-mirror sw0-port1 mirror3])
> +
> +dnl Delete a single mirror.
> +AT_CHECK([ovn-nbctl mirror-del mirror3])

Can you also check here what happens when you delete a mirror that is
attached? (The mirror is properly detached and deleted.)

> +
> +AT_CHECK([ovn-nbctl mirror-list], [0], [dnl
> +mirror1:
> +  Type     :  gre
> +  Sink     :  10.10.10.1
> +  Filter   :  from-lport
> +  Index/Key:  0
> +  Sources  :  None attached
> +mirror2:
> +  Type     :  erspan
> +  Sink     :  10.10.10.2
> +  Filter   :  both
> +  Index/Key:  1
> +  Sources  :  None attached
> +])
> +
> +dnl Delete another mirror.
> +AT_CHECK([ovn-nbctl mirror-del mirror2])
> +
> +dnl Update the Sink address
> +AT_CHECK([ovn-nbctl set mirror . sink=192.168.1.13])
> +
> +AT_CHECK([ovn-nbctl mirror-list], [0], [dnl
> +mirror1:
> +  Type     :  gre
> +  Sink     :  192.168.1.13
> +  Filter   :  from-lport
> +  Index/Key:  0
> +  Sources  :  None attached
> +])
> +
> +dnl Delete all mirrors and remove switch
> +AT_CHECK([ovn-nbctl mirror-del])
> +AT_CHECK([ovn-nbctl ls-del sw0])

Why is this ls-del needed? This seems irrelevant to what the test case
checks - mirror commands working.

> +AT_CHECK([ovn-nbctl mirror-list], [0], [dnl
> +])])
> +
> +dnl ---------------------------------------------------------------------
> +
>  OVN_NBCTL_TEST([ovn_nbctl_nats], [NATs], [
>  AT_CHECK([ovn-nbctl lr-add lr0])
>  AT_CHECK([ovn-nbctl lr-nat-add lr0 snatt 30.0.0.2 192.168.1.2], [1], [],
> diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
> index 157f9f60c..b7d92b60c 100644
> --- a/tests/ovn-northd.at
> +++ b/tests/ovn-northd.at
> @@ -2205,6 +2205,56 @@ check_meter_by_name NOT meter_me__${acl1} meter_me__${acl2}
>  AT_CLEANUP
>  ])
>
> +OVN_FOR_EACH_NORTHD([
> +AT_SETUP([Check NB-SB mirrors sync])
> +AT_KEYWORDS([mirrors])
> +ovn_start
> +
> +check ovn-nbctl --wait=sb mirror-add mirror1 erspan 0 both 10.10.10.2
> +
> +AT_CHECK([ovn-sbctl get Mirror . Sink], [0], [dnl
> +"10.10.10.2"
> +])
> +
> +AT_CHECK([ovn-sbctl get Mirror . type], [0], [dnl
> +erspan
> +])
> +
> +AT_CHECK([ovn-sbctl get Mirror . index], [0], [dnl
> +0
> +])
> +
> +AT_CHECK([ovn-sbctl get Mirror . filter], [0], [dnl
> +both
> +])
> +
> +check ovn-nbctl --wait=sb \
> +    -- set mirror . sink=192.168.1.13 \
> +    -- set mirror . type=gre \
> +    -- set mirror . index=12 \
> +    -- set mirror . filter=to-lport

While more work here, I think it's worth updating fields one by one
and checking that each update triggeres the corresponding update (and
other fields are not e.g. reset to nul-values).

> +
> +AT_CHECK([ovn-sbctl get Mirror . Sink], [0], [dnl
> +"192.168.1.13"
> +])
> +
> +AT_CHECK([ovn-sbctl get Mirror . type], [0], [dnl
> +gre
> +])
> +
> +AT_CHECK([ovn-sbctl get Mirror . index], [0], [dnl
> +12
> +])
> +
> +AT_CHECK([ovn-sbctl get Mirror . filter], [0], [dnl
> +to-lport
> +])
> +
> +check ovn-nbctl --wait=sb mirror-del

This mirror-del seems unneeded unless you would then check that SB
objects are cleaned up.

> +
> +AT_CLEANUP
> +])
> +
>  OVN_FOR_EACH_NORTHD([
>  AT_SETUP([ACL skip hints for stateless config])
>  AT_KEYWORDS([acl])
> diff --git a/tests/ovn.at b/tests/ovn.at
> index bba2c9c1d..9c2905ea8 100644
> --- a/tests/ovn.at
> +++ b/tests/ovn.at
> @@ -16082,6 +16082,165 @@ 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"
> +
> +# 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

remove

> +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
> +
> +# 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
> +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 MIRROR
> +#
> +# 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 - Currently checking only for GRE
> +# MIRROR - Mirror name
> +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_name=${11}
> +    shift; shift; shift; shift; shift; shift; shift
> +    shift; shift; shift; shift;
> +
> +    check ovn-nbctl mirror-add ${mirror_name} ${mirror_encap_type} 0 to-lport 192.168.1.12
> +    check ovn-nbctl lsp-attach-mirror ls1-lp1 ${mirror_name}

I think this doesn't belong in this function. Please move it outside.

> +
> +    # 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
> +    if test X$exp_icmp_chksum != X; then

why is this check here? I don't think you ever call it without
exp_icmp_chksum set.

> +        # 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

do you ever call it with any other mirror_encap_type? Why the check here?

> +            #echo "GRE"

remove

> +            local mirror=${remote_mac}${local_mac}08004500007e00004000402fb6e9c0a8010bc0a8010c2000655800000000${reply}

Is it the same as "local packet" above, just with different MACs? If
so, could we reconstruct it using the same formula / by replacing MACs
at the start of the string representing the packet?

> +        fi
> +        echo $mirror >> br-phys_n1.expected
> +    fi
> +}
> +
> +# Set IPs
> +rtr_l2_ip=$(ip_to_hex 172 16 1 1)
> +l1_ip=$(ip_to_hex 192 168 1 2)
> +
> +# 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" mirror0
> +
> +OVN_CHECK_PACKETS_CONTAIN([hv1/br-phys_n1-tx.pcap], [br-phys_n1.expected])

could you please also check the reply was actually received at the
destination, so that we know that mirroring didn't break delivery

> +
> +tcpdump -r hv1/br-phys_n1-tx.pcap
> +
> +echo "---------OVN NB Mirror-----"
> +ovn-nbctl mirror-list
> +
> +echo "---------OVS Mirror----"
> +ovs-vsctl list Mirror
> +
> +echo "-----------------------"
> +echo "Delete vif1 so that OVN releases port binding"
> +ovs-vsctl del-port br-int vif1
> +
> +echo "Verifying Mirror deletion in OVS"
> +
> +AT_CHECK([as hv1 ovs-vsctl list Mirror], [0], [dnl
> +])
> +
> +OVN_CLEANUP([hv1], [hv2])
> +AT_CLEANUP
> +])
>

This test case should be expanded (or new test case variants added) to
explore how different tunnel types (erspan vs gre), filters (to, from,
both) behave.

>  OVN_FOR_EACH_NORTHD([
>  AT_SETUP([Port Groups])
> diff --git a/utilities/ovn-nbctl.c b/utilities/ovn-nbctl.c
> index 3bbdbd998..a30b5e4f2 100644
> --- a/utilities/ovn-nbctl.c
> +++ b/utilities/ovn-nbctl.c
> @@ -271,6 +271,16 @@ QoS commands:\n\
>                              remove QoS rules from SWITCH\n\
>    qos-list SWITCH           print QoS rules for SWITCH\n\
>  \n\
> +Mirror commands:\n\
> +  mirror-add NAME TYPE INDEX FILTER IP\n\
> +                            add a mirror with given name\n\
> +                            specify TYPE 'gre' or 'erspan'\n\
> +                            specify INDEX gre key / erpsan idx\n\
> +                            specify FILTER for mirroring selection\n\

it may be helpful for a reader to see the list of supported FILTERs

> +                            specify Sink / Destination i.e Remote IP \n\

nit: "i.e." (dot at the end)
nit: remove spurious space at the end of the line

> +  mirror-del [NAME]         remove mirrors\n\
> +  mirror-list               print mirrors\n\
> +\n\
>  Meter commands:\n\
>    [--fair]\n\
>    meter-add NAME ACTION RATE UNIT [BURST]\n\
> @@ -311,6 +321,8 @@ Logical switch port commands:\n\
>                              set dhcpv6 options for PORT\n\
>    lsp-get-dhcpv6-options PORT  get the dhcpv6 options for PORT\n\
>    lsp-get-ls PORT           get the logical switch which the port belongs to\n\
> +  lsp-attach-mirror PORT MIRROR   attach source PORT to the MIRROR\n\
> +  lsp-detach-mirror PORT MIRROR   detach source PORT from the MIRROR\n\

nit: "to MIRROR", "from MIRROR" to match other places (e.g. we say
"attach source PORT" not "attach a source PORT")

>  \n\
>  Forwarding group commands:\n\
>    [--liveness]\n\
> @@ -1685,6 +1697,130 @@ nbctl_pre_lsp_type(struct ctl_context *ctx)
>      ovsdb_idl_add_column(ctx->idl, &nbrec_logical_switch_port_col_type);
>  }
>
> +static void
> +nbctl_pre_lsp_mirror(struct ctl_context *ctx)
> +{
> +    ovsdb_idl_add_column(ctx->idl, &nbrec_logical_switch_port_col_name);
> +    ovsdb_idl_add_column(ctx->idl,
> +                         &nbrec_logical_switch_port_col_mirror_rules);
> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_name);
> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_src);
> +}
> +
> +static int
> +mirror_cmp(const void *mirror1_, const void *mirror2_)
> +{
> +    const struct nbrec_mirror *const *mirror_1 = mirror1_;
> +    const struct nbrec_mirror *const *mirror_2 = mirror2_;
> +
> +    const struct nbrec_mirror *mirror1 = *mirror_1;
> +    const struct nbrec_mirror *mirror2 = *mirror_2;
> +
> +    return strcmp(mirror1->name,mirror2->name);
> +}
> +
> +static char * OVS_WARN_UNUSED_RESULT
> +mirror_by_name_or_uuid(struct ctl_context *ctx, const char *id,
> +                    bool must_exist,
> +                    const struct nbrec_mirror **mirror_p)
> +{
> +    const struct nbrec_mirror *mirror = NULL;
> +    *mirror_p = NULL;
> +
> +    struct uuid mirror_uuid;
> +    bool is_uuid = uuid_from_string(&mirror_uuid, id);
> +    if (is_uuid) {
> +        mirror = nbrec_mirror_get_for_uuid(ctx->idl, &mirror_uuid);
> +    }
> +
> +    if (!mirror) {
> +        NBREC_MIRROR_FOR_EACH (mirror, ctx->idl) {
> +            if (!strcmp(mirror->name, id)) {
> +                break;
> +            }
> +        }
> +    }
> +
> +    if (!mirror && must_exist) {
> +        return xasprintf("%s: mirror %s not found",
> +                         id, is_uuid ? "UUID" : "name");
> +    }
> +
> +    *mirror_p = mirror;
> +    return NULL;
> +}
> +
> +static void
> +nbctl_lsp_attach_mirror(struct ctl_context *ctx)
> +{
> +    const char *port = ctx->argv[1];
> +    const char *mirror_name = ctx->argv[2];
> +    const struct nbrec_logical_switch_port *lsp = NULL;
> +    const struct nbrec_mirror *mirror;
> +
> +    char *error;
> +
> +    error = lsp_by_name_or_uuid(ctx, port, true, &lsp);
> +    if (error) {
> +        ctx->error = error;
> +        return;
> +    }
> +
> +
> +    /*check if a mirror rule actually exists on that name or not*/
> +    error = mirror_by_name_or_uuid(ctx, mirror_name, true, &mirror);
> +    if (error) {
> +        ctx->error = error;
> +        return;
> +    }
> +
> +    /* Check if same mirror rule already exists for the lsp */
> +    for (size_t i = 0; i < lsp->n_mirror_rules; i++) {
> +        if (!mirror_cmp(&lsp->mirror_rules[i], &mirror)) {
> +            bool may_exist = shash_find(&ctx->options, "--may-exist") != NULL;
> +            if (!may_exist) {
> +                ctl_error(ctx, "Same mirror already existed on the lsp %s.",
> +                          ctx->argv[1]);
> +                return;
> +            }
> +            return;
> +        }
> +    }
> +
> +    nbrec_logical_switch_port_update_mirror_rules_addvalue(lsp, mirror);
> +    nbrec_mirror_update_src_addvalue(mirror,lsp);
> +
> +}
> +
> +static void
> +nbctl_lsp_detach_mirror(struct ctl_context *ctx)
> +{
> +    const char *port = ctx->argv[1];
> +    const char *mirror_name = ctx->argv[2];
> +    const struct nbrec_logical_switch_port *lsp = NULL;
> +    const struct nbrec_mirror *mirror;
> +
> +    char *error;
> +
> +    error = lsp_by_name_or_uuid(ctx, port, true, &lsp);
> +    if (error) {
> +        ctx->error = error;
> +        return;
> +    }
> +
> +
> +    /*check if a mirror rule actually exists on that name or not*/
> +    error = mirror_by_name_or_uuid(ctx, mirror_name, true, &mirror);
> +    if (error) {
> +        ctx->error = error;
> +        return;
> +    }
> +
> +    nbrec_logical_switch_port_update_mirror_rules_delvalue(lsp, mirror);
> +    nbrec_mirror_update_src_delvalue(mirror,lsp);
> +
> +}
> +
>  static void
>  nbctl_lsp_set_type(struct ctl_context *ctx)
>  {
> @@ -7239,6 +7375,202 @@ cmd_ha_ch_grp_set_chassis_prio(struct ctl_context *ctx)
>      nbrec_ha_chassis_set_priority(ha_chassis, priority);
>  }
>
> +static char * OVS_WARN_UNUSED_RESULT
> +parse_filter(const char *arg, const char **selection_p)
> +{
> +    /* Validate selection.  Only require the first letter. */
> +    if (arg[0] == 't') {
> +        *selection_p = "to-lport";
> +    } else if (arg[0] == 'f') {
> +        *selection_p = "from-lport";
> +    } else if (arg[0] == 'b') {
> +        *selection_p = "both";
> +    } else {
> +        *selection_p = NULL;
> +        return xasprintf("%s: selection must be \"to-lport\" or "
> +                         "\"from-lport\" or \"both\" ", arg);
> +    }
> +    return NULL;
> +}
> +
> +static char * OVS_WARN_UNUSED_RESULT
> +parse_type(const char *arg, const char **type_p)
> +{
> +    /* Validate type.  Only require the second letter. */

you check the first letter though

> +    if (arg[0] == 'g') {
> +        *type_p = "gre";
> +    } else if (arg[0] == 'e') {
> +        *type_p = "erspan";
> +    } else {
> +        *type_p = NULL;
> +        return xasprintf("%s: type must be \"gre\" or "
> +                         "\"erspan\"", arg);
> +    }
> +    return NULL;
> +}
> +
> +static void
> +nbctl_pre_mirror_add(struct ctl_context *ctx)
> +{
> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_name);
> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_filter);
> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_index);
> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_sink);
> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_type);
> +}
> +
> +static void
> +nbctl_mirror_add(struct ctl_context *ctx)
> +{
> +    const char *filter = NULL;
> +    const char *sink_ip = NULL;
> +    const char *type = NULL;
> +    const char *name = NULL;
> +    char *new_external_ip = NULL;
> +    int64_t index;
> +    char *error = NULL;
> +    const struct nbrec_mirror *mirror_check = NULL;
> +
> +    /* Mirror Name */
> +    name = ctx->argv[1];
> +    NBREC_MIRROR_FOR_EACH (mirror_check, ctx->idl) {
> +            if (!strcmp(mirror_check->name, name)) {
> +                ctl_error(ctx, "Mirror with %s name already exists.",
> +                          name);
> +                return;
> +            }
> +        }
> +
> +    /* Tunnel Type - GRE/ERSPAN */
> +    error = parse_type(ctx->argv[2], &type);
> +    if (error) {
> +        ctx->error = error;
> +        return;
> +    }
> +
> +    /* tunnel index / GRE key / ERSPAN idx */
> +    index = atoi(ctx->argv[3]);
> +
> +    /* Filter for mirroring */
> +    error = parse_filter(ctx->argv[4], &filter);
> +    if (error) {
> +        ctx->error = error;
> +        return;
> +    }
> +
> +    /* Destination / Sink details */
> +    sink_ip = ctx->argv[5];
> +
> +    /* check if it is a valid ip */
> +    new_external_ip = normalize_ipv4_addr_str(sink_ip);

new_sink_ip?

> +    if (!new_external_ip) {
> +        new_external_ip = normalize_ipv6_addr_str(sink_ip);
> +    }
> +
> +    if (new_external_ip) {
> +        free(new_external_ip);
> +    } else {
> +        ctl_error(ctx, "Invalid sink ip: %s", sink_ip);
> +        return;
> +    }
> +
> +    /* Create the mirror. */
> +    struct nbrec_mirror *mirror = nbrec_mirror_insert(ctx->txn);
> +    nbrec_mirror_set_name(mirror, name);
> +    nbrec_mirror_set_index(mirror, index);
> +    nbrec_mirror_set_filter(mirror, filter);
> +    nbrec_mirror_set_type(mirror, type);
> +    nbrec_mirror_set_sink(mirror, sink_ip);
> +
> +}
> +
> +static void
> +nbctl_pre_mirror_del(struct ctl_context *ctx)
> +{
> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_name);
> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_src);
> +}
> +
> +static void
> +nbctl_mirror_del(struct ctl_context *ctx)
> +{
> +    const struct nbrec_mirror *mirror, *next;
> +
> +    /* If a name is not specified, delete all mirrors. */
> +    if (ctx->argc == 1) {
> +        NBREC_MIRROR_FOR_EACH_SAFE (mirror, next, ctx->idl) {
> +            nbrec_mirror_delete(mirror);
> +        }
> +        return;
> +    }
> +
> +    /* Remove the matching mirror. */
> +    NBREC_MIRROR_FOR_EACH (mirror, ctx->idl) {
> +        if (strcmp(ctx->argv[1], mirror->name)) {
> +            continue;
> +        }
> +        nbrec_mirror_delete(mirror);
> +        return;
> +    }
> +}
> +
> +static void
> +nbctl_pre_mirror_list(struct ctl_context *ctx)
> +{
> +    ovsdb_idl_add_column(ctx->idl, &nbrec_logical_switch_port_col_name);
> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_name);
> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_filter);
> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_index);
> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_sink);
> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_type);
> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_src);
> +}
> +
> +static void
> +nbctl_mirror_list(struct ctl_context *ctx)
> +{
> +
> +    const struct nbrec_mirror **mirrors = NULL;
> +    const struct nbrec_mirror *mirror;
> +    size_t n_capacity = 0;
> +    size_t n_mirrors = 0;
> +
> +    NBREC_MIRROR_FOR_EACH (mirror, ctx->idl) {
> +        if (n_mirrors == n_capacity) {
> +            mirrors = x2nrealloc(mirrors, &n_capacity, sizeof *mirrors);
> +        }
> +
> +        mirrors[n_mirrors] = mirror;
> +        n_mirrors++;
> +    }
> +
> +    if (n_mirrors) {
> +        qsort(mirrors, n_mirrors, sizeof *mirrors, mirror_cmp);
> +    }
> +
> +    for (size_t i = 0; i < n_mirrors; i++) {
> +        mirror = mirrors[i];
> +        ds_put_format(&ctx->output, "%s:\n", mirror->name);
> +        /* print all the values */
> +        ds_put_format(&ctx->output, "  Type     :  %s\n", mirror->type);
> +        ds_put_format(&ctx->output, "  Sink     :  %s\n", mirror->sink);
> +        ds_put_format(&ctx->output, "  Filter   :  %s\n", mirror->filter);
> +        ds_put_format(&ctx->output, "  Index/Key:  %ld\n",
> +                                                 (long int)mirror->index);
> +        ds_put_cstr(&ctx->output,   "  Sources  :");
> +        if (mirror->n_src > 0) {
> +            for (size_t j = 0; j < mirror->n_src; j++) {
> +                ds_put_format(&ctx->output, "  %s", mirror->src[j]->name);
> +            }
> +        } else {
> +            ds_put_cstr(&ctx->output, "  None attached");
> +        }
> +        ds_put_cstr(&ctx->output, "\n");
> +    }
> +
> +    free(mirrors);
> +}
> +
>  static const struct ctl_table_class tables[NBREC_N_TABLES] = {
>      [NBREC_TABLE_DHCP_OPTIONS].row_ids
>      = {{&nbrec_logical_switch_port_col_name, NULL,
> @@ -7332,6 +7664,15 @@ static const struct ctl_command_syntax nbctl_commands[] = {
>      { "qos-list", 1, 1, "SWITCH", nbctl_pre_qos_list, nbctl_qos_list,
>        NULL, "", RO },
>
> +    /* mirror commands. */
> +    { "mirror-add", 5, 5,
> +      "NAME TYPE INDEX FILTER IP",
> +      nbctl_pre_mirror_add, nbctl_mirror_add, NULL, "--may-exist", RW },
> +    { "mirror-del", 0, 1, "[NAME]",
> +      nbctl_pre_mirror_del, nbctl_mirror_del, NULL, "", RW },
> +    { "mirror-list", 0, 0, "", nbctl_pre_mirror_list, nbctl_mirror_list,
> +      NULL, "", RO },
> +
>      /* meter commands. */
>      { "meter-add", 4, 5, "NAME ACTION RATE UNIT [BURST]", nbctl_pre_meter_add,
>        nbctl_meter_add, NULL, "--fair,--may-exist", RW },
> @@ -7386,6 +7727,10 @@ static const struct ctl_command_syntax nbctl_commands[] = {
>        nbctl_lsp_get_dhcpv6_options, NULL, "", RO },
>      { "lsp-get-ls", 1, 1, "PORT", nbctl_pre_lsp_get_ls, nbctl_lsp_get_ls,
>        NULL, "", RO },
> +    { "lsp-attach-mirror", 2, 2, "PORT MIRROR", nbctl_pre_lsp_mirror,
> +      nbctl_lsp_attach_mirror, NULL, "", RW },
> +    { "lsp-detach-mirror", 2, 2, "PORT MIRROR", nbctl_pre_lsp_mirror,
> +      nbctl_lsp_detach_mirror, NULL, "", RW },
>
>      /* forwarding group commands. */
>      { "fwd-group-add", 4, INT_MAX, "SWITCH GROUP VIP VMAC PORT...",
> diff --git a/utilities/ovn-sbctl.c b/utilities/ovn-sbctl.c
> index e35556d34..1aa6cc26f 100644
> --- a/utilities/ovn-sbctl.c
> +++ b/utilities/ovn-sbctl.c
> @@ -307,6 +307,7 @@ pre_get_info(struct ctl_context *ctx)
>      ovsdb_idl_add_column(ctx->idl, &sbrec_port_binding_col_chassis);
>      ovsdb_idl_add_column(ctx->idl, &sbrec_port_binding_col_datapath);
>      ovsdb_idl_add_column(ctx->idl, &sbrec_port_binding_col_up);
> +    ovsdb_idl_add_column(ctx->idl, &sbrec_port_binding_col_mirror_rules);
>
>      ovsdb_idl_add_column(ctx->idl, &sbrec_logical_flow_col_logical_datapath);
>      ovsdb_idl_add_column(ctx->idl, &sbrec_logical_flow_col_logical_dp_group);
> @@ -1434,6 +1435,9 @@ static const struct ctl_table_class tables[SBREC_N_TABLES] = {
>      [SBREC_TABLE_HA_CHASSIS].row_ids[0]
>      = {&sbrec_ha_chassis_col_chassis, NULL, NULL},
>
> +    [SBREC_TABLE_MIRROR].row_ids[0]
> +    = {&sbrec_mirror_col_name, NULL, NULL},
> +
>      [SBREC_TABLE_METER].row_ids[0]
>      = {&sbrec_meter_col_name, NULL, NULL},
>
> --
> 2.27.0
>
> _______________________________________________
> dev mailing list
> dev@openvswitch.org
> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
Abhiram R N Aug. 25, 2022, 5:56 p.m. UTC | #3
Hi Ihar,

Thanks for the review of v4 patch.
I will take care of the changes you have suggested in the xml(ovn-nb.xml),
the tests (ovn-nbctl.at, ovn.at, ovn-northd.at) and ovn-nbctl.c in the next
patch.

Other than those regarding the ovn-controller.c and ovn-nb.ovsschema I have
replied inline below.
Kindly take a look and let me know your comments.

On Thu, Aug 25, 2022 at 5:21 AM Ihar Hrachyshka <ihrachys@redhat.com> wrote:

> Hi,
>
> I've reviewed everything except mirror.[ch] files. See below.
>
> On Mon, Aug 22, 2022 at 4:41 PM Abhiram R N <abhiramrn@gmail.com> wrote:
> >
> > Added changes in ovn-nbctl, ovn-sbctl, northd and in ovn-controller.
> > While 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.
> >
> > 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>
> > ---
> > V3 --> V4: Addressed review comments from V3
> >            i) Fixed Issues 1,2 and 3
> >           ii) Did other minor changes suggested
> >          iii) Included Issues 1 and 3 in test cases
> > Files modified (V3 --> v4):
> > Code --> mirror.c, ovn-controller.c, northd.c
> > Test --> ovn-northd.at, ovn-nbctl.at, ovn.at
> >
> >  controller/automake.mk      |   4 +-
> >  controller/mirror.c         | 510 ++++++++++++++++++++++++++++++++++++
> >  controller/mirror.h         |  53 ++++
> >  controller/ovn-controller.c | 222 +++++++++++++++-
> >  northd/en-northd.c          |   4 +
> >  northd/inc-proc-northd.c    |   4 +
> >  northd/northd.c             | 136 ++++++++++
> >  northd/northd.h             |   2 +
> >  ovn-nb.ovsschema            |  31 ++-
> >  ovn-nb.xml                  |  57 ++++
> >  ovn-sb.ovsschema            |  23 +-
> >  ovn-sb.xml                  |  46 ++++
> >  tests/ovn-nbctl.at          |  95 +++++++
> >  tests/ovn-northd.at         |  50 ++++
> >  tests/ovn.at                | 159 +++++++++++
> >  utilities/ovn-nbctl.c       | 345 ++++++++++++++++++++++++
> >  utilities/ovn-sbctl.c       |   4 +
> >  17 files changed, 1739 insertions(+), 6 deletions(-)
> >  create mode 100644 controller/mirror.c
> >  create mode 100644 controller/mirror.h
> >
> > 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..44895d8ca
> > --- /dev/null
> > +++ b/controller/mirror.c
> > @@ -0,0 +1,510 @@
> > +/* 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 "openvswitch/vlog.h"
> > +
> > +/* OVN includes. */
> > +#include "binding.h"
> > +#include "lib/ovn-sb-idl.h"
> > +#include "mirror.h"
> > +
> > +VLOG_DEFINE_THIS_MODULE(port_mirror);
> > +
> > +/* Static function declarations */
> > +
> > +static const struct ovsrec_port *
> > +get_port_for_iface(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 bool
> > +mirror_create(const struct sbrec_port_binding *pb,
> > +              struct port_mirror_ctx *pm_ctx)
> > +{
> > +    const struct ovsrec_mirror *mirror = NULL;
> > +
> > +    if (pb->n_up && !pb->up[0]) {
> > +        return true;
> > +    }
> > +
> > +    if (pb->chassis != pm_ctx->chassis_rec) {
> > +        return true;
> > +    }
> > +
> > +    if (!pm_ctx->ovs_idl_txn) {
> > +        return false;
> > +    }
> > +
> > +
> > +    VLOG_INFO("Mirror rule(s) present for %s ", pb->logical_port);
> > +    /* Loop through the mirror rules */
> > +    for (size_t i =0; i < pb->n_mirror_rules; i++) {
> > +        /* check if the mirror already exists in OVS DB */
> > +        bool create_mirror = true;
> > +        OVSREC_MIRROR_TABLE_FOR_EACH (mirror, pm_ctx->mirror_table) {
> > +            if (!strcmp(pb->mirror_rules[i]->name, mirror->name)) {
> > +                  /* Mirror with same name already exists
> > +                   * No need to create mirror
> > +                   */
> > +                  create_mirror = false;
> > +                  break;
> > +            }
> > +        }
> > +
> > +        if (create_mirror) {
> > +
> > +            struct smap options = SMAP_INITIALIZER(&options);
> > +            char *port_name, *key;
> > +
> > +            key = xasprintf("%ld",(long int)pb->mirror_rules[i]->index);
> > +            smap_add(&options, "remote_ip", pb->mirror_rules[i]->sink);
> > +
> > +            if (!strcmp(pb->mirror_rules[i]->type, "gre")) {
> > +                /* Set the GRE key */
> > +                smap_add(&options, "key", key);
> > +
> > +            } else if (!strcmp(pb->mirror_rules[i]->type, "erspan")) {
> > +                /* Set the ERSPAN index */
> > +                smap_add(&options, "key", key);
> > +                smap_add(&options, "erspan_idx", key);
> > +                smap_add(&options, "erspan_ver","1");
> > +
> > +            }
> > +            struct ovsrec_interface *iface =
> > +                      ovsrec_interface_insert(pm_ctx->ovs_idl_txn);
> > +            port_name = xasprintf("ovn-%s",
> > +                                   pb->mirror_rules[i]->name);
> > +
> > +            ovsrec_interface_set_name(iface, port_name);
> > +            ovsrec_interface_set_type(iface, pb->mirror_rules[i]->type);
> > +            ovsrec_interface_set_options(iface, &options);
> > +
> > +            struct ovsrec_port *port =
> > +                              ovsrec_port_insert(pm_ctx->ovs_idl_txn);
> > +            ovsrec_port_set_name(port, port_name);
> > +            ovsrec_port_set_interfaces(port, &iface, 1);
> > +
> > +            ovsrec_bridge_update_ports_addvalue(pm_ctx->br_int, port);
> > +
> > +            smap_destroy(&options);
> > +            free(port_name);
> > +            free(key);
> > +
> > +            VLOG_INFO("Creating Mirror in OVS DB");
> > +            mirror = ovsrec_mirror_insert(pm_ctx->ovs_idl_txn);
> > +            ovsrec_mirror_set_name(mirror,pb->mirror_rules[i]->name);
> > +            ovsrec_mirror_update_output_port_addvalue(mirror, port);
> > +            ovsrec_bridge_update_mirrors_addvalue(pm_ctx->br_int,
> > +                                                             mirror);
> > +        }
> > +
> > +        struct local_binding *lbinding = local_binding_find(
> > +                               pm_ctx->local_bindings,
> pb->logical_port);
> > +        const struct ovsrec_port *p =
> > +                     get_port_for_iface(lbinding->iface,
> pm_ctx->br_int);
> > +        if (p) {
> > +            if (!strcmp(pb->mirror_rules[i]->filter,"from-lport")) {
> > +                ovsrec_mirror_update_select_src_port_addvalue(mirror,
> p);
> > +            } else if (!strcmp(pb->mirror_rules[i]->filter,"to-lport"))
> {
> > +                ovsrec_mirror_update_select_dst_port_addvalue(mirror,
> p);
> > +            } else {
> > +                ovsrec_mirror_update_select_src_port_addvalue(mirror,
> p);
> > +                ovsrec_mirror_update_select_dst_port_addvalue(mirror,
> p);
> > +            }
> > +        }
> > +    }
> > +    return true;
> > +}
> > +
> > +static void
> > +check_and_update_mirror_table(const struct sbrec_mirror *sb_mirror,
> > +                              struct ovsrec_mirror *ovs_mirror)
> > +{
> > +    char *filter;
> > +    if ((ovs_mirror->n_select_dst_port)
> > +            && (ovs_mirror->n_select_src_port)) {
> > +        filter = xasprintf("both");
> > +    } else if (ovs_mirror->n_select_dst_port) {
> > +        filter = xasprintf("to-lport");
> > +    } else {
> > +        filter = xasprintf("from-lport");
> > +    }
> > +
> > +    if (strcmp(sb_mirror->filter, filter)) {
> > +        if (!strcmp(sb_mirror->filter,"from-lport")
> > +                              && !strcmp(filter,"both")) {
> > +            for (size_t i = 0; i < ovs_mirror->n_select_dst_port; i++) {
> > +
> ovsrec_mirror_update_select_dst_port_delvalue(ovs_mirror,
> > +
>  ovs_mirror->select_dst_port[i]);
> > +            }
> > +        } else if (!strcmp(sb_mirror->filter,"to-lport")
> > +                              && !strcmp(filter,"both")) {
> > +            for (size_t i = 0; i < ovs_mirror->n_select_src_port; i++) {
> > +
> ovsrec_mirror_update_select_src_port_delvalue(ovs_mirror,
> > +
>  ovs_mirror->select_src_port[i]);
> > +            }
> > +        } else if (!strcmp(sb_mirror->filter,"both")
> > +                              && !strcmp(filter,"from-lport")) {
> > +            for (size_t i = 0; i < ovs_mirror->n_select_src_port; i++) {
> > +
> ovsrec_mirror_update_select_dst_port_addvalue(ovs_mirror,
> > +
>  ovs_mirror->select_src_port[i]);
> > +            }
> > +        } else if (!strcmp(sb_mirror->filter,"both")
> > +                              && !strcmp(filter,"to-lport")) {
> > +            for (size_t i = 0; i < ovs_mirror->n_select_dst_port; i++) {
> > +
> ovsrec_mirror_update_select_src_port_addvalue(ovs_mirror,
> > +
>  ovs_mirror->select_dst_port[i]);
> > +            }
> > +        } else if (!strcmp(sb_mirror->filter,"to-lport")
> > +                              && !strcmp(filter,"from-lport")) {
> > +            for (size_t i = 0; i < ovs_mirror->n_select_src_port; i++) {
> > +
> ovsrec_mirror_update_select_dst_port_addvalue(ovs_mirror,
> > +
>  ovs_mirror->select_src_port[i]);
> > +
> ovsrec_mirror_update_select_src_port_delvalue(ovs_mirror,
> > +
>  ovs_mirror->select_src_port[i]);
> > +            }
> > +        } else if (!strcmp(sb_mirror->filter,"from-lport")
> > +                              && !strcmp(filter,"to-lport")) {
> > +            for (size_t i = 0; i < ovs_mirror->n_select_dst_port; i++) {
> > +
> ovsrec_mirror_update_select_src_port_addvalue(ovs_mirror,
> > +
>  ovs_mirror->select_dst_port[i]);
> > +
> ovsrec_mirror_update_select_dst_port_delvalue(ovs_mirror,
> > +
>  ovs_mirror->select_dst_port[i]);
> > +            }
> > +        }
> > +    }
> > +}
> > +
> > +static void
> > +check_and_update_interface_table(const struct sbrec_mirror *sb_mirror,
> > +                                   struct ovsrec_mirror *ovs_mirror)
> > +{
> > +    struct smap options = SMAP_INITIALIZER(&options);
> > +    char *key, *type;
> > +    struct smap *opts =
> &ovs_mirror->output_port->interfaces[0]->options;
> > +    struct ovsrec_interface *iface =
> > +                          ovs_mirror->output_port->interfaces[0];
> > +
> > +    const char *erspan_ver = smap_get(opts, "erspan_ver");
> > +    if (erspan_ver) {
> > +        type = xasprintf("erspan");
> > +    } else {
> > +        type = xasprintf("gre");
> > +    }
> > +    if (strcmp(type, sb_mirror->type)) {
> > +        ovsrec_interface_set_type(iface, sb_mirror->type);
> > +    }
> > +
> > +    key = xasprintf("%ld",(long int)sb_mirror->index);
> > +    smap_add(&options, "remote_ip", sb_mirror->sink);
> > +
> > +    if (!strcmp(sb_mirror->type, "gre")) {
> > +        /* Set the GRE key */
> > +        smap_add(&options, "key", key);
> > +
> > +    } else if (!strcmp(sb_mirror->type, "erspan")) {
> > +        /* Set the ERSPAN index */
> > +        smap_add(&options, "key", key);
> > +        smap_add(&options, "erspan_idx", key);
> > +        smap_add(&options, "erspan_ver","1");
> > +
> > +    }
> > +
> > +    ovsrec_interface_set_options(iface, &options);
> > +    smap_destroy(&options);
> > +    free(key);
> > +
> > +}
> > +
> > +static void
> > +mirror_update(const struct sbrec_mirror *sb_mirror,
> > +              struct ovsrec_mirror *ovs_mirror)
> > +{
> > +    check_and_update_interface_table(sb_mirror, ovs_mirror);
> > +
> > +    check_and_update_mirror_table(sb_mirror, ovs_mirror);
> > +}
> > +
> > +static void
> > +mirror_delete(const struct sbrec_port_binding *pb,
> > +              struct port_mirror_ctx *pm_ctx,
> > +              struct shash *pb_mirror_map,
> > +              bool delete_all)
> > +{
> > +    struct sset pb_mirrors = SSET_INITIALIZER(&pb_mirrors);
> > +
> > +    if (!delete_all) {
> > +        for (size_t i = 0; i < pb->n_mirror_rules ; i++) {
> > +            sset_add(&pb_mirrors, pb->mirror_rules[i]->name);
> > +        }
> > +    }
> > +
> > +    if (delete_all && (shash_is_empty(pb_mirror_map)) &&
> pb->n_mirror_rules) {
> > +        for (size_t i = 0; i < pb->n_mirror_rules ; i++) {
> > +
> > +            struct ovsrec_mirror *ovs_mirror = NULL;
> > +            ovs_mirror = shash_find_data(pm_ctx->ovs_mirrors,
> > +                                            pb->mirror_rules[i]->name);
> > +            if (ovs_mirror) {
> > +                if (ovs_mirror->n_select_dst_port == 0 &&
> > +                        ovs_mirror->n_select_src_port == 0) {
> > +                    ovsrec_bridge_update_ports_delvalue(pm_ctx->br_int,
> > +
>  ovs_mirror->output_port);
> > +
> ovsrec_bridge_update_mirrors_delvalue(pm_ctx->br_int,
> > +
> ovs_mirror);
> > +                    ovsrec_port_delete(ovs_mirror->output_port);
> > +                    ovsrec_mirror_delete(ovs_mirror);
> > +                }
> > +            }
> > +        }
> > +    }
> > +
> > +    struct shash_node *mirror_node;
> > +    SHASH_FOR_EACH (mirror_node, pb_mirror_map) {
> > +        struct ovsrec_mirror *ovs_mirror = mirror_node->data;
> > +        if (!sset_find(&pb_mirrors, ovs_mirror->name)) {
> > +            /* Find if the mirror has other sources i*/
> > +            if ((ovs_mirror->n_select_dst_port > 1) ||
> > +                (ovs_mirror->n_select_src_port > 1)) {
> > +                /* More than 1 source then just
> > +                 * update the mirror table
> > +                 */
> > +                bool done = false;
> > +                for (size_t i = 0; ((i < ovs_mirror->n_select_dst_port)
> > +                                                   && (done == false));
> i++) {
> > +                    const struct ovsrec_port *port_rec =
> > +
>  ovs_mirror->select_dst_port[i];
> > +                    for (size_t j = 0; j < port_rec->n_interfaces; j++)
> {
> > +                        const struct ovsrec_interface *iface_rec;
> > +
> > +                        iface_rec = port_rec->interfaces[j];
> > +                        const char *iface_id =
> > +
> smap_get(&iface_rec->external_ids,
> > +
> "iface-id");
> > +                        if (!strcmp(iface_id,pb->logical_port)) {
> > +
> ovsrec_mirror_update_select_dst_port_delvalue(
> > +                                                        ovs_mirror,
> port_rec);
> > +                            done = true;
> > +                            break;
> > +                        }
> > +                    }
> > +                }
> > +                done = false;
> > +                for (size_t i = 0; ((i < ovs_mirror->n_select_src_port)
> > +                                                   && (done == false));
> i++) {
> > +                    const struct ovsrec_port *port_rec =
> > +
> ovs_mirror->select_src_port[i];
> > +                    for (size_t j = 0; j < port_rec->n_interfaces; j++)
> {
> > +                        const struct ovsrec_interface *iface_rec;
> > +
> > +                        iface_rec = port_rec->interfaces[j];
> > +                        const char *iface_id =
> > +
> smap_get(&iface_rec->external_ids,
> > +
> "iface-id");
> > +                        if (!strcmp(iface_id,pb->logical_port)) {
> > +
> ovsrec_mirror_update_select_src_port_delvalue(
> > +                                                        ovs_mirror,
> port_rec);
> > +                            done = true;
> > +                            break;
> > +                        }
> > +                    }
> > +                }
> > +            } else {
> > +                /*
> > +                 * If only 1 source delete the output port
> > +                 * and then delete the mirror completely
> > +                 */
> > +                VLOG_INFO("Only 1 source for the mirror. Hence delete
> it");
> > +                ovsrec_bridge_update_ports_delvalue(pm_ctx->br_int,
> > +
> ovs_mirror->output_port);
> > +                ovsrec_bridge_update_mirrors_delvalue(pm_ctx->br_int,
> > +                                                            ovs_mirror);
> > +                ovsrec_port_delete(ovs_mirror->output_port);
> > +                ovsrec_mirror_delete(ovs_mirror);
> > +            }
> > +        }
> > +    }
> > +
> > +    const char *used_node, *used_next;
> > +    SSET_FOR_EACH_SAFE (used_node, used_next, &pb_mirrors) {
> > +        sset_delete(&pb_mirrors, SSET_NODE_FROM_NAME(used_node));
> > +    }
> > +    sset_destroy(&pb_mirrors);
> > +}
> > +
> > +static void
> > +find_port_specific_mirrors (const struct sbrec_port_binding *pb,
> > +                            struct port_mirror_ctx *pm_ctx,
> > +                            struct shash *pb_mirror_map)
> > +{
> > +    const struct ovsrec_mirror *mirror = NULL;
> > +
> > +    OVSREC_MIRROR_TABLE_FOR_EACH (mirror, pm_ctx->mirror_table) {
> > +        for (size_t i = 0; i < mirror->n_select_dst_port; i++) {
> > +            const struct ovsrec_port *port_rec =
> mirror->select_dst_port[i];
> > +            for (size_t j = 0; j < port_rec->n_interfaces; j++) {
> > +                const struct ovsrec_interface *iface_rec;
> > +                iface_rec = port_rec->interfaces[j];
> > +                const char *logical_port =
> > +                    smap_get(&iface_rec->external_ids, "iface-id");
> > +                if (!strcmp(logical_port, pb->logical_port)) {
> > +                    shash_add_once(pb_mirror_map, mirror->name, mirror);
> > +                }
> > +            }
> > +        }
> > +        for (size_t i = 0; i < mirror->n_select_src_port; i++) {
> > +            const struct ovsrec_port *port_rec =
> mirror->select_src_port[i];
> > +            for (size_t j = 0; j < port_rec->n_interfaces; j++) {
> > +                const struct ovsrec_interface *iface_rec;
> > +                iface_rec = port_rec->interfaces[j];
> > +                const char *logical_port =
> > +                    smap_get(&iface_rec->external_ids, "iface-id");
> > +                if (!strcmp(logical_port, pb->logical_port)) {
> > +                    shash_add_once(pb_mirror_map, mirror->name, mirror);
> > +                }
> > +            }
> > +        }
> > +    }
> > +}
> > +
> > +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);
> > +}
> > +
> > +
> > +void
> > +ovn_port_mirror_init(struct shash *ovs_mirrors)
> > +{
> > +    shash_init(ovs_mirrors);
> > +}
> > +
> > +void
> > +ovn_port_mirror_run(struct port_mirror_ctx *pm_ctx)
> > +{
> > +    const struct sbrec_port_binding *pb;
> > +    SBREC_PORT_BINDING_TABLE_FOR_EACH (pb,
> > +                                       pm_ctx->port_binding_table) {
> > +        ovn_port_mirror_handle_lport(pb, false, pm_ctx);
> > +    }
> > +}
> > +
> > +bool
> > +ovn_port_mirror_handle_lport(const struct sbrec_port_binding *pb, bool
> removed,
> > +                     struct port_mirror_ctx *pm_ctx)
> > +{
> > +    bool ret = true;
> > +    struct shash port_ovs_mirrors =
> SHASH_INITIALIZER(&port_ovs_mirrors);
> > +
> > +    /* Need to find if mirror needs update */
> > +    find_port_specific_mirrors(pb, pm_ctx, &port_ovs_mirrors);
> > +    if (removed == false) {
> > +        if (((pb->n_mirror_rules == 0)
> > +              && (shash_is_empty(&port_ovs_mirrors))) ||
> > +              (pb->n_mirror_rules == shash_count(&port_ovs_mirrors))) {
> > +            /* No mirror update */
> > +        } else {
> > +            /* Update Mirror */
> > +            if (pb->n_mirror_rules > shash_count(&port_ovs_mirrors)) {
> > +                /* create mirror,
> > +                 * if mirror already exists only update selection
> > +                 */
> > +                ret = mirror_create(pb, pm_ctx);
> > +            } else {
> > +                /* delete mirror,
> > +                 * if mirror has other sources only update selection
> > +                 */
> > +                mirror_delete(pb, pm_ctx, &port_ovs_mirrors, false);
> > +            }
> > +        }
> > +    } else {
> > +           mirror_delete(pb, pm_ctx, &port_ovs_mirrors, true);
> > +    }
> > +
> > +    struct shash_node *ovs_mirror_node, *ovs_mirror_next;
> > +    SHASH_FOR_EACH_SAFE (ovs_mirror_node, ovs_mirror_next,
> > +                                              &port_ovs_mirrors) {
> > +        shash_delete(&port_ovs_mirrors, ovs_mirror_node);
> > +    }
> > +    shash_destroy(&port_ovs_mirrors);
> > +
> > +    return ret;
> > +}
> > +
> > +bool
> > +ovn_port_mirror_handle_update(struct port_mirror_ctx *pm_ctx)
> > +{
> > +    const struct sbrec_mirror *mirror = NULL;
> > +    struct ovsrec_mirror *ovs_mirror = NULL;
> > +
> > +    SBREC_MIRROR_TABLE_FOR_EACH_TRACKED (mirror,
> pm_ctx->sb_mirror_table) {
> > +    /* For each tracked mirror entry check if OVS entry is there*/
> > +        ovs_mirror = shash_find_data(pm_ctx->ovs_mirrors, mirror->name);
> > +        if (ovs_mirror) {
> > +            if (sbrec_mirror_is_deleted(mirror)) {
> > +                /* Need to delete the mirror in OVS */
> > +                VLOG_INFO("Delete mirror and remove port");
> > +                ovsrec_bridge_update_ports_delvalue(pm_ctx->br_int,
> > +
> ovs_mirror->output_port);
> > +                ovsrec_bridge_update_mirrors_delvalue(pm_ctx->br_int,
> > +                                                      ovs_mirror);
> > +                ovsrec_port_delete(ovs_mirror->output_port);
> > +                ovsrec_mirror_delete(ovs_mirror);
> > +            } else {
> > +                mirror_update(mirror, ovs_mirror);
> > +            }
> > +        }
> > +    }
> > +
> > +    return true;
> > +}
> > +
> > +void
> > +ovn_port_mirror_destroy(struct shash *ovs_mirrors)
> > +{
> > +    struct shash_node *ovs_mirror_node, *ovs_mirror_next;
> > +    SHASH_FOR_EACH_SAFE (ovs_mirror_node, ovs_mirror_next,
> > +                                              ovs_mirrors) {
> > +        shash_delete(ovs_mirrors, ovs_mirror_node);
> > +    }
> > +    shash_destroy(ovs_mirrors);
> > +}
> > +
> > diff --git a/controller/mirror.h b/controller/mirror.h
> > new file mode 100644
> > index 000000000..85b964f55
> > --- /dev/null
> > +++ b/controller/mirror.h
> > @@ -0,0 +1,53 @@
> > +/* 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_port_table;
> > +struct ovsrec_bridge;
> > +struct ovsrec_bridge_table;
> > +struct ovsrec_open_vswitch_table;
> > +struct sbrec_chassis;
> > +struct ovsrec_interface_table;
> > +struct ovsrec_mirror_table;
> > +struct sbrec_mirror_table;
> > +struct sbrec_port_binding_table;
> > +
> > +struct port_mirror_ctx {
> > +    struct shash *ovs_mirrors;
> > +    struct ovsdb_idl_txn *ovs_idl_txn;
> > +    const struct ovsrec_port_table *port_table;
> > +    const struct ovsrec_bridge *br_int;
> > +    const struct sbrec_chassis *chassis_rec;
> > +    const struct ovsrec_bridge_table *bridge_table;
> > +    const struct ovsrec_open_vswitch_table *ovs_table;
> > +    const struct ovsrec_interface_table *iface_table;
> > +    const struct ovsrec_mirror_table *mirror_table;
> > +    const struct sbrec_mirror_table *sb_mirror_table;
> > +    const struct sbrec_port_binding_table *port_binding_table;
> > +    struct shash *local_bindings;
> > +};
> > +
> > +void mirror_register_ovs_idl(struct ovsdb_idl *);
> > +void ovn_port_mirror_init(struct shash *);
> > +void ovn_port_mirror_destroy(struct shash *);
> > +void ovn_port_mirror_run(struct port_mirror_ctx *pm_ctx);
> > +bool ovn_port_mirror_handle_lport(const struct sbrec_port_binding *pb,
> > +                                  bool removed,
> > +                                  struct port_mirror_ctx *pm_ctx);
> > +bool ovn_port_mirror_handle_update(struct port_mirror_ctx *pm_ctx);
> > +#endif
> > diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c
> > index 0b0ccc48a..95307a2d9 100644
> > --- a/controller/ovn-controller.c
> > +++ b/controller/ovn-controller.c
> > @@ -77,6 +77,7 @@
> >  #include "stopwatch.h"
> >  #include "lib/inc-proc-eng.h"
> >  #include "hmapx.h"
> > +#include "mirror.h"
> >
> >  VLOG_DEFINE_THIS_MODULE(main);
> >
> > @@ -963,6 +964,7 @@ ctrl_register_ovs_idl(struct ovsdb_idl *ovs_idl)
> >      bfd_register_ovs_idl(ovs_idl);
> >      physical_register_ovs_idl(ovs_idl);
> >      vif_plug_register_ovs_idl(ovs_idl);
> > +    mirror_register_ovs_idl(ovs_idl);
> >  }
> >
> >  #define SB_NODES \
> > @@ -983,6 +985,7 @@ ctrl_register_ovs_idl(struct ovsdb_idl *ovs_idl)
> >      SB_NODE(load_balancer, "load_balancer") \
> >      SB_NODE(fdb, "fdb") \
> >      SB_NODE(meter, "meter") \
> > +    SB_NODE(mirror, "mirror") \
> >      SB_NODE(static_mac_binding, "static_mac_binding")
> >
> >  enum sb_engine_node {
> > @@ -1000,7 +1003,8 @@ enum sb_engine_node {
> >      OVS_NODE(bridge, "bridge") \
> >      OVS_NODE(port, "port") \
> >      OVS_NODE(interface, "interface") \
> > -    OVS_NODE(qos, "qos")
> > +    OVS_NODE(qos, "qos") \
> > +    OVS_NODE(mirror, "mirror")
> >
> >  enum ovs_engine_node {
> >  #define OVS_NODE(NAME, NAME_STR) OVS_##NAME,
> > @@ -2326,6 +2330,203 @@ load_balancers_by_dp_cleanup(struct hmap *lbs)
> >      free(lbs);
> >  }
> >
> > +/* Mirror Engine */
> > +struct ed_type_port_mirror {
> > +    struct shash ovs_mirrors;
> > +};
> > +
> > +static void *
> > +en_port_mirror_init(struct engine_node *node OVS_UNUSED,
> > +                    struct engine_arg *arg OVS_UNUSED)
> > +{
> > +    struct ed_type_port_mirror *port_mirror = xzalloc(sizeof
> *port_mirror);
> > +    ovn_port_mirror_init(&port_mirror->ovs_mirrors);
> > +    return port_mirror;
> > +}
> > +
> > +static void
> > +en_port_mirror_cleanup(void *data)
> > +{
> > +    struct ed_type_port_mirror *port_mirror = data;
> > +    ovn_port_mirror_destroy(&port_mirror->ovs_mirrors);
> > +}
> > +
> > +static void
> > +init_port_mirror_ctx(struct engine_node *node,
> > +                 struct ed_type_runtime_data *rt_data,
> > +                 struct ed_type_port_mirror *port_mirror_data,
> > +                 struct port_mirror_ctx *pm_ctx)
> > +{
> > +    struct ovsrec_open_vswitch_table *ovs_table =
> > +        (struct ovsrec_open_vswitch_table *)EN_OVSDB_GET(
> > +            engine_get_input("OVS_open_vswitch", node));
> > +    struct ovsrec_bridge_table *bridge_table =
> > +        (struct ovsrec_bridge_table *)EN_OVSDB_GET(
> > +            engine_get_input("OVS_bridge", node));
> > +    const char *chassis_id = get_ovs_chassis_id(ovs_table);
> > +    const struct ovsrec_bridge *br_int = get_br_int(bridge_table,
> ovs_table);
> > +
> > +    ovs_assert(br_int && chassis_id);
> > +    const struct sbrec_chassis *chassis = NULL;
> > +    struct ovsdb_idl_index *sbrec_chassis_by_name =
> > +        engine_ovsdb_node_get_index(
> > +                engine_get_input("SB_chassis", node),
> > +                "name");
> > +
> > +    if (chassis_id) {
> > +        chassis = chassis_lookup_by_name(sbrec_chassis_by_name,
> chassis_id);
> > +    }
> > +    ovs_assert(chassis);
> > +
> > +    struct ovsrec_port_table *port_table =
> > +        (struct ovsrec_port_table *)EN_OVSDB_GET(
> > +            engine_get_input("OVS_port", node));
> > +
> > +    struct ed_type_ovs_interface_shadow *iface_shadow =
> > +        engine_get_input_data("ovs_interface_shadow", node);
> > +
> > +    struct ovsrec_mirror_table *mirror_table =
> > +        (struct ovsrec_mirror_table *)EN_OVSDB_GET(
> > +            engine_get_input("OVS_mirror", node));
> > +
> > +    struct sbrec_port_binding_table *pb_table =
> > +        (struct sbrec_port_binding_table *)EN_OVSDB_GET(
> > +            engine_get_input("SB_port_binding", node));
> > +
> > +    struct sbrec_mirror_table *sb_mirror_table =
> > +        (struct sbrec_mirror_table *)EN_OVSDB_GET(
> > +            engine_get_input("SB_mirror", node));
> > +
> > +    struct shash_node *ovs_mirror_node, *ovs_mirror_next;
> > +    SHASH_FOR_EACH_SAFE (ovs_mirror_node, ovs_mirror_next,
> > +                           &port_mirror_data->ovs_mirrors) {
> > +        shash_delete(&port_mirror_data->ovs_mirrors, ovs_mirror_node);
> > +    }
> > +
> > +    const struct ovsrec_mirror *ovsmirror = NULL;
> > +    OVSREC_MIRROR_TABLE_FOR_EACH (ovsmirror, mirror_table) {
> > +       shash_add(&port_mirror_data->ovs_mirrors, ovsmirror->name,
> ovsmirror);
> > +    }
> > +
> > +    pm_ctx->ovs_idl_txn = engine_get_context()->ovs_idl_txn;
> > +    pm_ctx->port_table = port_table;
> > +    pm_ctx->iface_table = iface_shadow->iface_table;
> > +    pm_ctx->mirror_table = mirror_table;
> > +    pm_ctx->port_binding_table = pb_table;
> > +    pm_ctx->sb_mirror_table = sb_mirror_table;
> > +    pm_ctx->br_int = br_int;
> > +    pm_ctx->chassis_rec = chassis;
> > +    pm_ctx->bridge_table = bridge_table;
> > +    pm_ctx->ovs_table = ovs_table;
> > +    pm_ctx->ovs_mirrors = &port_mirror_data->ovs_mirrors;
> > +    pm_ctx->local_bindings = &rt_data->lbinding_data.bindings;
> > +}
> > +
> > +static void
> > +en_port_mirror_run(struct engine_node *node, void *data)
> > +{
> > +    struct port_mirror_ctx pm_ctx;
> > +    struct ed_type_port_mirror *port_mirror_data = data;
> > +    struct ed_type_runtime_data *rt_data =
> > +        engine_get_input_data("runtime_data", node);
> > +
> > +    init_port_mirror_ctx(node, rt_data, port_mirror_data, &pm_ctx);
> > +
> > +    ovn_port_mirror_run(&pm_ctx);
> > +    engine_set_node_state(node, EN_UPDATED);
> > +}
> > +
> > +static bool
> > +port_mirror_runtime_data_handler(struct engine_node *node, void *data)
> > +{
> > +    struct ed_type_runtime_data *rt_data =
> > +        engine_get_input_data("runtime_data", node);
> > +
> > +    /* There is no tracked data. Fall back to full recompute of
> port_mirror */
> > +    if (!rt_data->tracked) {
> > +        return false;
> > +    }
> > +
> > +    struct hmap *tracked_dp_bindings = &rt_data->tracked_dp_bindings;
> > +    if (hmap_is_empty(tracked_dp_bindings)) {
> > +        return true;
> > +    }
> > +
> > +    struct port_mirror_ctx pm_ctx;
> > +    struct ed_type_port_mirror *port_mirror_data = data;
> > +    init_port_mirror_ctx(node, rt_data, port_mirror_data, &pm_ctx);
> > +
> > +    struct tracked_datapath *tdp;
> > +    HMAP_FOR_EACH (tdp, node, tracked_dp_bindings) {
> > +        if (tdp->tracked_type != TRACKED_RESOURCE_UPDATED) {
> > +            /* Fall back to full recompute when a local datapath
> > +             * is added or deleted. */
> > +            return false;
> > +        }
> > +
> > +        struct shash_node *shash_node;
> > +        SHASH_FOR_EACH (shash_node, &tdp->lports) {
> > +            struct tracked_lport *lport = shash_node->data;
> > +            bool removed =
> > +                lport->tracked_type == TRACKED_RESOURCE_REMOVED ? true:
> false;
> > +            if (!ovn_port_mirror_handle_lport(lport->pb, removed,
> &pm_ctx)) {
> > +                return false;
> > +            }
> > +        }
> > +    }
> > +
> > +    engine_set_node_state(node, EN_UPDATED);
> > +    return true;
> > +}
> > +
> > +static bool
> > +port_mirror_port_binding_handler(struct engine_node *node, void *data)
> > +{
> > +    struct port_mirror_ctx pm_ctx;
> > +    struct ed_type_port_mirror *port_mirror_data = data;
> > +    struct ed_type_runtime_data *rt_data =
> > +        engine_get_input_data("runtime_data", node);
> > +
> > +    init_port_mirror_ctx(node, rt_data, port_mirror_data, &pm_ctx);
> > +
> > +    /* handle port binding updates (i.,e when the mirror column
> > +     * of port_binding is updated)
> > +     */
> > +    const struct sbrec_port_binding *pb;
> > +    SBREC_PORT_BINDING_TABLE_FOR_EACH_TRACKED (pb,
> > +
>  pm_ctx.port_binding_table) {
> > +        bool removed = sbrec_port_binding_is_deleted(pb);
> > +        if (!ovn_port_mirror_handle_lport(pb, removed, &pm_ctx)) {
> > +            return false;
> > +        }
> > +    }
> > +
> > +    engine_set_node_state(node, EN_UPDATED);
> > +    return true;
> > +
> > +}
> > +
> > +static bool
> > +port_mirror_sb_mirror_handler(struct engine_node *node, void *data)
> > +{
> > +    struct port_mirror_ctx pm_ctx;
> > +    struct ed_type_port_mirror *port_mirror_data = data;
> > +    struct ed_type_runtime_data *rt_data =
> > +        engine_get_input_data("runtime_data", node);
> > +
> > +    init_port_mirror_ctx(node, rt_data, port_mirror_data, &pm_ctx);
> > +
> > +    /* handle sb mirror updates
> > +     */
> > +    if (!ovn_port_mirror_handle_update(&pm_ctx)) {
> > +        return false;
> > +    }
> > +
> > +    engine_set_node_state(node, EN_UPDATED);
> > +    return true;
> > +
> > +}
> > +
> >  /* Engine node which is used to handle the Non VIF data like
> >   *   - OVS patch ports
> >   *   - Tunnel ports and the related chassis information.
> > @@ -3609,6 +3810,7 @@ main(int argc, char *argv[])
> >      ENGINE_NODE_WITH_CLEAR_TRACK_DATA(addr_sets, "addr_sets");
> >      ENGINE_NODE_WITH_CLEAR_TRACK_DATA(port_groups, "port_groups");
> >      ENGINE_NODE(northd_options, "northd_options");
> > +    ENGINE_NODE(port_mirror, "port_mirror");
> >
> >  #define SB_NODE(NAME, NAME_STR) ENGINE_NODE_SB(NAME, NAME_STR);
> >      SB_NODES
> > @@ -3763,6 +3965,22 @@ main(int argc, char *argv[])
> >      engine_add_input(&en_flow_output, &en_pflow_output,
> >                       flow_output_pflow_output_handler);
> >
> > +    engine_add_input(&en_port_mirror, &en_ovs_open_vswitch, NULL);
> > +    engine_add_input(&en_port_mirror, &en_ovs_bridge, NULL);
> > +    engine_add_input(&en_port_mirror, &en_ovs_mirror, NULL);
> > +    engine_add_input(&en_port_mirror, &en_sb_chassis, NULL);
> > +    engine_add_input(&en_port_mirror, &en_ovs_port,
> engine_noop_handler);
> > +    engine_add_input(&en_port_mirror, &en_ovs_interface_shadow,
> > +                     engine_noop_handler);
> > +    engine_add_input(&en_flow_output, &en_port_mirror,
> > +                     engine_noop_handler);
> > +    engine_add_input(&en_port_mirror, &en_runtime_data,
> > +                     port_mirror_runtime_data_handler);
> > +    engine_add_input(&en_port_mirror, &en_sb_mirror,
> > +                     port_mirror_sb_mirror_handler);
> > +    engine_add_input(&en_port_mirror, &en_sb_port_binding,
> > +                     port_mirror_port_binding_handler);
> > +
> >      struct engine_arg engine_arg = {
> >          .sb_idl = ovnsb_idl_loop.idl,
> >          .ovs_idl = ovs_idl_loop.idl,
> > @@ -4082,6 +4300,8 @@ main(int argc, char *argv[])
> >                                      ovnsb_idl_loop.idl),
> >
> sbrec_sb_global_table_get(ovnsb_idl_loop.idl));
> >                          stopwatch_stop(BFD_RUN_STOPWATCH_NAME,
> time_msec());
> > +                    } else {
> > +                        engine_run(false);
>
> Why do you need it here?

This was caught while fixing Issue 3 which Numan raised on v3 patch.
This change is needed. Discussed with Numan and as suggested by him I have
added
Since I am new to this Engine code I requested Numan for suggestions.
Quoting him below.
"basically you should call - engine_run(false) if ovs_idl_txn is NULL or
ovnsb_idl_txn is
NULL. right now - engine_run is called with false only if ovnsb_idl_txn is
NULL"

To give a bit more details. When the OVS port is deleted the OVN controller
releases
the lport. Later when we put back the OVS port, OVN controller claims it
back. This will
trigger the Mirror handler in OVN. But while handling this I was seeing
that ovs_idl_txn
is NULL in which case we cant update OVSDB. And the handler returns
'false'. This will
trigger a recompute but the ovs_idl_txn is still NULL, and in such a
scenario
it never gets updated. If engine_run is called with 'false' recompute is
not allowed and
so next time when ovs_idl_txn is not NULL, engine recompute is triggered
and mirror
run function will be called.

Numan, Hope I have explained correctly. Please add/correct if needed.


> >                      }
> >
> >                      runtime_data = engine_get_data(&en_runtime_data);
> > diff --git a/northd/en-northd.c b/northd/en-northd.c
> > index 4907a1ff2..74cac1b8e 100644
> > --- a/northd/en-northd.c
> > +++ b/northd/en-northd.c
> > @@ -78,6 +78,8 @@ void en_northd_run(struct engine_node *node, void
> *data)
> >          EN_OVSDB_GET(engine_get_input("NB_acl", node));
> >      input_data.nbrec_static_mac_binding_table =
> >          EN_OVSDB_GET(engine_get_input("NB_static_mac_binding", node));
> > +    input_data.nbrec_mirror_table =
> > +        EN_OVSDB_GET(engine_get_input("NB_mirror", node));
> >
> >      input_data.sbrec_sb_global_table =
> >          EN_OVSDB_GET(engine_get_input("SB_sb_global", node));
> > @@ -111,6 +113,8 @@ void en_northd_run(struct engine_node *node, void
> *data)
> >          EN_OVSDB_GET(engine_get_input("SB_chassis_private", node));
> >      input_data.sbrec_static_mac_binding_table =
> >          EN_OVSDB_GET(engine_get_input("SB_static_mac_binding", node));
> > +    input_data.sbrec_mirror_table =
> > +        EN_OVSDB_GET(engine_get_input("SB_mirror", node));
> >
> >      northd_run(&input_data, data,
> >                 eng_ctx->ovnnb_idl_txn,
> > diff --git a/northd/inc-proc-northd.c b/northd/inc-proc-northd.c
> > index 54e0ad3b0..ac27a730e 100644
> > --- a/northd/inc-proc-northd.c
> > +++ b/northd/inc-proc-northd.c
> > @@ -50,6 +50,7 @@ VLOG_DEFINE_THIS_MODULE(inc_proc_northd);
> >      NB_NODE(acl, "acl") \
> >      NB_NODE(logical_router, "logical_router") \
> >      NB_NODE(qos, "qos") \
> > +    NB_NODE(mirror, "mirror") \
> >      NB_NODE(meter, "meter") \
> >      NB_NODE(meter_band, "meter_band") \
> >      NB_NODE(logical_router_port, "logical_router_port") \
> > @@ -92,6 +93,7 @@ VLOG_DEFINE_THIS_MODULE(inc_proc_northd);
> >      SB_NODE(logical_flow, "logical_flow") \
> >      SB_NODE(logical_dp_group, "logical_DP_group") \
> >      SB_NODE(multicast_group, "multicast_group") \
> > +    SB_NODE(mirror, "mirror") \
> >      SB_NODE(meter, "meter") \
> >      SB_NODE(meter_band, "meter_band") \
> >      SB_NODE(datapath_binding, "datapath_binding") \
> > @@ -172,6 +174,7 @@ void inc_proc_northd_init(struct ovsdb_idl_loop *nb,
> >      engine_add_input(&en_northd, &en_nb_acl, NULL);
> >      engine_add_input(&en_northd, &en_nb_logical_router, NULL);
> >      engine_add_input(&en_northd, &en_nb_qos, NULL);
> > +    engine_add_input(&en_northd, &en_nb_mirror, NULL);
> >      engine_add_input(&en_northd, &en_nb_meter, NULL);
> >      engine_add_input(&en_northd, &en_nb_meter_band, NULL);
> >      engine_add_input(&en_northd, &en_nb_logical_router_port, NULL);
> > @@ -194,6 +197,7 @@ void inc_proc_northd_init(struct ovsdb_idl_loop *nb,
> >      engine_add_input(&en_northd, &en_sb_address_set, NULL);
> >      engine_add_input(&en_northd, &en_sb_port_group, NULL);
> >      engine_add_input(&en_northd, &en_sb_logical_dp_group, NULL);
> > +    engine_add_input(&en_northd, &en_sb_mirror, NULL);
> >      engine_add_input(&en_northd, &en_sb_meter, NULL);
> >      engine_add_input(&en_northd, &en_sb_meter_band, NULL);
> >      engine_add_input(&en_northd, &en_sb_datapath_binding, NULL);
> > diff --git a/northd/northd.c b/northd/northd.c
> > index 7e2681865..74314b5dc 100644
> > --- a/northd/northd.c
> > +++ b/northd/northd.c
> > @@ -3214,6 +3214,51 @@ ovn_port_update_sbrec_chassis(
> >      free(requested_chassis_sb);
> >  }
> >
> > +static void
> > +sbrec_port_binding_update_mirror_rules(struct northd_input *input_data,
> > +                                       const struct ovn_port *op)
> > +{
> > +    size_t i = 0;
> > +    if (op->sb->n_mirror_rules > op->nbsp->n_mirror_rules) {
> > +        /* Needs deletion in SB */
> > +        struct shash nb_mirror_rules =
> SHASH_INITIALIZER(&nb_mirror_rules);
> > +        for (i = 0; i < op->nbsp->n_mirror_rules; i++) {
> > +            shash_add(&nb_mirror_rules,
> > +                                 op->nbsp->mirror_rules[i]->name,
> > +                                 op->nbsp->mirror_rules[i]);
> > +        }
> > +
> > +        for (i = 0; i < op->sb->n_mirror_rules; i++) {
> > +            if (!shash_find(&nb_mirror_rules,
> > +                           op->sb->mirror_rules[i]->name)) {
> > +
> sbrec_port_binding_update_mirror_rules_delvalue(op->sb,
> > +
>  op->sb->mirror_rules[i]);
> > +            }
> > +        }
> > +
> > +        struct shash_node *node, *next;
> > +        SHASH_FOR_EACH_SAFE (node, next, &nb_mirror_rules) {
> > +            shash_delete(&nb_mirror_rules, node);
> > +        }
> > +        shash_destroy(&nb_mirror_rules);
> > +
> > +    } else {
> > +        /* Needs addition in SB */
>
> Not necessarily. What if it's ==? (You still need to handle this case,
> for when the number of mirror references is the same but some of them
> refer to different mirrors.)
>
I thought about it. Since the the Mirror rules is added/deleted into the
port binding table mirror column only when an lsp-attach/lsp-detach
happens to a logical port through the NBCTL command which updates
NB first which inturn triggers SB update. I could not think of such a
use-case when the case you mentioned happens. I dont think that the
mirror rule not present in NB for a logical port but is wrongly present
in SB Port Binding and at the same time a valid mirror rule present
NB and not updated in SB can happen.

 I copied below 2 commands and executed them together..

[root@rhos-nfv-09 ~]#
[root@rhos-nfv-09 ~]# ovn-nbctl lsp-detach-mirror sw0-port1 mirror1
[root@rhos-nfv-09 ~]# ovn-nbctl lsp-attach-mirror sw0-port1 mirror0
[root@rhos-nfv-09 ~]#

Still the updates happen one after the other.
I tried on my setup. Didn't hit any issues.
So I feel we can live with this for now.


> > +        for (i = 0; i < op->nbsp->n_mirror_rules; i++) {
> > +            const struct sbrec_mirror *sb_mirror;
> > +            SBREC_MIRROR_TABLE_FOR_EACH (sb_mirror,
> > +
>  input_data->sbrec_mirror_table) {
> > +                if (!strcmp(sb_mirror->name,
> > +                            op->nbsp->mirror_rules[i]->name)) {
> > +                    /* Add the value to SB */
> > +
> sbrec_port_binding_update_mirror_rules_addvalue(op->sb,
> > +
> sb_mirror);
> > +                }
> > +            }
> > +        }
> > +    }
> > +}
> > +
> >  static void
> >  ovn_port_update_sbrec(struct northd_input *input_data,
> >                        struct ovsdb_idl_txn *ovnsb_txn,
> > @@ -3573,6 +3618,17 @@ ovn_port_update_sbrec(struct northd_input
> *input_data,
> >          }
> >          sbrec_port_binding_set_external_ids(op->sb, &ids);
> >          smap_destroy(&ids);
> > +
> > +        if (!op->nbsp->n_mirror_rules) {
> > +            /* Nothing is set. Clear mirror_rules from pb. */
> > +            sbrec_port_binding_set_mirror_rules(op->sb, NULL, 0);
> > +        } else {
> > +            /* Check if SB DB update needed */
> > +            if (op->nbsp->n_mirror_rules != op->sb->n_mirror_rules) {
> > +                sbrec_port_binding_update_mirror_rules(input_data, op);
> > +            }
> > +        }
> > +
> >      }
> >      if (op->tunnel_key != op->sb->tunnel_key) {
> >          sbrec_port_binding_set_tunnel_key(op->sb, op->tunnel_key);
> > @@ -14877,6 +14933,85 @@ sync_meters(struct northd_input *input_data,
> >      shash_destroy(&sb_meters);
> >  }
> >
> > +static bool
> > +mirror_needs_update(const struct nbrec_mirror *nb_mirror,
> > +                  const struct sbrec_mirror *sb_mirror)
> > +{
> > +
> > +    if (nb_mirror->index != sb_mirror->index) {
> > +        return true;
> > +    } else if (strcmp(nb_mirror->sink, sb_mirror->sink)) {
> > +        return true;
> > +    } else if (strcmp(nb_mirror->type, sb_mirror->type)) {
> > +        return true;
> > +    } else if (strcmp(nb_mirror->filter, sb_mirror->filter)) {
> > +        return true;
> > +    }
> > +
> > +    return false;
> > +}
> > +
> > +static void
> > +sync_mirrors_iterate_nb_mirror(struct ovsdb_idl_txn *ovnsb_txn,
> > +                             const char *mirror_name,
> > +                             const struct nbrec_mirror *nb_mirror,
> > +                             struct shash *sb_mirrors,
> > +                             struct sset *used_sb_mirrors)
> > +{
> > +    const struct sbrec_mirror *sb_mirror;
> > +    bool new_sb_mirror = false;
> > +
> > +    sb_mirror = shash_find_data(sb_mirrors, mirror_name);
> > +    if (!sb_mirror) {
> > +        sb_mirror = sbrec_mirror_insert(ovnsb_txn);
> > +        sbrec_mirror_set_name(sb_mirror, mirror_name);
> > +        shash_add(sb_mirrors, sb_mirror->name, sb_mirror);
> > +        new_sb_mirror = true;
> > +    }
> > +    sset_add(used_sb_mirrors, mirror_name);
> > +
> > +    if ((new_sb_mirror) || mirror_needs_update(nb_mirror, sb_mirror)) {
> > +        sbrec_mirror_set_filter(sb_mirror,nb_mirror->filter);
> > +        sbrec_mirror_set_index(sb_mirror, nb_mirror->index);
> > +        sbrec_mirror_set_sink(sb_mirror, nb_mirror->sink);
> > +        sbrec_mirror_set_type(sb_mirror, nb_mirror->type);
> > +    }
> > +}
> > +
> > +static void
> > +sync_mirrors(struct northd_input *input_data,
> > +            struct ovsdb_idl_txn *ovnsb_txn)
> > +{
> > +    struct shash sb_mirrors = SHASH_INITIALIZER(&sb_mirrors);
> > +    struct sset used_sb_mirrors = SSET_INITIALIZER(&used_sb_mirrors);
> > +
> > +    const struct sbrec_mirror *sb_mirror;
> > +    SBREC_MIRROR_TABLE_FOR_EACH (sb_mirror,
> input_data->sbrec_mirror_table) {
> > +        shash_add(&sb_mirrors, sb_mirror->name, sb_mirror);
> > +    }
> > +
> > +    const struct nbrec_mirror *nb_mirror;
> > +    NBREC_MIRROR_TABLE_FOR_EACH (nb_mirror,
> input_data->nbrec_mirror_table) {
> > +        sync_mirrors_iterate_nb_mirror(ovnsb_txn, nb_mirror->name,
> nb_mirror,
> > +                                     &sb_mirrors, &used_sb_mirrors);
> > +    }
> > +
> > +    const char *used_mirror;
> > +    const char *used_mirror_next;
> > +    SSET_FOR_EACH_SAFE (used_mirror, used_mirror_next,
> &used_sb_mirrors) {
> > +        shash_find_and_delete(&sb_mirrors, used_mirror);
> > +        sset_delete(&used_sb_mirrors, SSET_NODE_FROM_NAME(used_mirror));
> > +    }
> > +    sset_destroy(&used_sb_mirrors);
> > +
> > +    struct shash_node *node, *next;
> > +    SHASH_FOR_EACH_SAFE (node, next, &sb_mirrors) {
> > +        sbrec_mirror_delete(node->data);
> > +        shash_delete(&sb_mirrors, node);
> > +    }
> > +    shash_destroy(&sb_mirrors);
> > +}
> > +
> >  /*
> >   * struct 'dns_info' is used to sync the DNS records between OVN
> Northbound db
> >   * and Southbound db.
> > @@ -15497,6 +15632,7 @@ ovnnb_db_run(struct northd_input *input_data,
> >      sync_address_sets(input_data, ovnsb_txn, &data->datapaths);
> >      sync_port_groups(input_data, ovnsb_txn, &data->port_groups);
> >      sync_meters(input_data, ovnsb_txn, &data->meter_groups);
> > +    sync_mirrors(input_data, ovnsb_txn);
> >      sync_dns_entries(input_data, ovnsb_txn, &data->datapaths);
> >      cleanup_stale_fdb_entries(input_data, &data->datapaths);
> >      stopwatch_stop(CLEAR_LFLOWS_CTX_STOPWATCH_NAME, time_msec());
> > diff --git a/northd/northd.h b/northd/northd.h
> > index d9856af97..8a4f6a428 100644
> > --- a/northd/northd.h
> > +++ b/northd/northd.h
> > @@ -34,6 +34,7 @@ struct northd_input {
> >      const struct nbrec_acl_table *nbrec_acl_table;
> >      const struct nbrec_static_mac_binding_table
> >          *nbrec_static_mac_binding_table;
> > +    const struct nbrec_mirror_table *nbrec_mirror_table;
> >
> >      /* Southbound table references */
> >      const struct sbrec_sb_global_table *sbrec_sb_global_table;
> > @@ -53,6 +54,7 @@ struct northd_input {
> >      const struct sbrec_chassis_private_table
> *sbrec_chassis_private_table;
> >      const struct sbrec_static_mac_binding_table
> >          *sbrec_static_mac_binding_table;
> > +    const struct sbrec_mirror_table *sbrec_mirror_table;
> >
> >      /* Indexes */
> >      struct ovsdb_idl_index *sbrec_chassis_by_name;
> > diff --git a/ovn-nb.ovsschema b/ovn-nb.ovsschema
> > index 174364c8b..01de55222 100644
> > --- a/ovn-nb.ovsschema
> > +++ b/ovn-nb.ovsschema
> > @@ -1,7 +1,7 @@
> >  {
> >      "name": "OVN_Northbound",
> > -    "version": "6.3.0",
> > -    "cksum": "4042813038 31869",
> > +    "version": "6.4.0",
> > +    "cksum": "589874483 33352",
> >      "tables": {
> >          "NB_Global": {
> >              "columns": {
> > @@ -132,6 +132,11 @@
> >                                              "refType": "weak"},
> >                                   "min": 0,
> >                                   "max": 1}},
> > +                "mirror_rules": {"type": {"key": {"type": "uuid",
> > +                                          "refTable": "Mirror",
> > +                                          "refType": "weak"},
> > +                                  "min": 0,
> > +                                  "max": "unlimited"}},
> >                  "ha_chassis_group": {
> >                      "type": {"key": {"type": "uuid",
> >                                       "refTable": "HA_Chassis_Group",
> > @@ -301,6 +306,28 @@
> >                      "type": {"key": "string", "value": "string",
> >                               "min": 0, "max": "unlimited"}}},
> >              "isRoot": false},
> > +        "Mirror": {
> > +            "columns": {
> > +                "name": {"type": "string"},
> > +                "filter": {"type": {"key": {"type": "string",
> > +                                            "enum": ["set",
> ["from-lport",
> > +                                                             "to-lport",
> > +
>  "both"]]}}},
> > +                "sink":{"type": "string"},
> > +                "type": {"type": {"key": {"type": "string",
> > +                                            "enum": ["set", ["gre",
> > +
>  "erspan"]]}}},
> > +                "index": {"type": "integer"},
>
> Is "index" ERSPAN specific? Should we pick a name that is less
> protocol dependent? Maybe "key" would work better since we already use
> it in other places for tunnel keys and whatnot?
>
Intention was to keep it generic! .. i.e the Tunnel index.
Index - For GRE as key and erspan as erspan_idx.
Also in the schema file "key" is used under many types "type". So as to not
confuse it with that I kept it as 'Index'.
If you insist, it is a little more effort and I can change it.
(as it will trigger many changes in .c files as well)
Or if you are fine I can keep it as Index and put the more clear explanation
in xml and help.


> > +                "src": {"type": {"key": {"type": "uuid",
> > +                                           "refTable":
> "Logical_Switch_Port",
> > +                                           "refType": "weak"},
> > +                                   "min": 0,
> > +                                   "max": "unlimited"}},
> > +                "external_ids": {
> > +                    "type": {"key": "string", "value": "string",
> > +                             "min": 0, "max": "unlimited"}}},
>
> NB contains external_ids but not SB. Is it used for anything in NB or
> is it just a placeholder to avoid schema changes later? Should we have
> a similar placeholder for SB side too?
>
Yeah currently it's just a placeholder. Had just kept like that because
while
implementing the schema we had taken the Meter table as reference.
Even in the Meter table in NB external_ids is there and SB it's not there.


>
> > +            "indexes": [["name"]],
> > +            "isRoot": true},
> >          "Meter": {
> >              "columns": {
> >                  "name": {"type": "string"},
> > diff --git a/ovn-nb.xml b/ovn-nb.xml
> > index 7fe88af27..d410301e7 100644
> > --- a/ovn-nb.xml
> > +++ b/ovn-nb.xml
> > @@ -1554,6 +1554,11 @@
> >        </column>
> >      </group>
> >
> > +    <column name="mirror_rules">
> > +        Mirror rules that apply to logical switch port which is the
> source.
> > +        Please see the <ref table="Mirror"/> table.
> > +    </column>
> > +
> >      <column name="ha_chassis_group">
> >        References a row in the OVN Northbound database's
> >        <ref table="HA_Chassis_Group" db="OVN_Northbound"/> table.
> > @@ -2491,6 +2496,58 @@
> >      </column>
> >    </table>
> >
> > +  <table name="Mirror" title="Mirror Entry">
> > +    <p>
> > +      Each row in this table represents one Mirror that can be used for
> > +      port mirroring. These Mirrors are referenced by the
> > +      <ref column="mirror_rules" table="Logical_Switch_Port"/> column in
> > +      the <ref table="Logical_Switch_Port"/> table.
> > +    </p>
> > +
> > +    <column name="name">
> > +      <p>
> > +        Represents the name of the mirror.
> > +      </p>
> > +    </column>
> > +
> > +    <column name="filter">
> > +      <p>
> > +        The value of this field represents selection criteria of the
> mirror.
>
> Please list supported values and explain what they mean.
>
> > +      </p>
> > +    </column>
> > +
> > +    <column name="sink">
> > +      <p>
> > +        The value of this field represents the destination/sink of the
> mirror.
>
> Perhaps explain that it's IP address? And since it's an IP address,
> should we maybe rename the field into sink_ip to be more explicit?
>
> > +      </p>
> > +    </column>
> > +
> > +    <column name="type">
> > +      <p>
> > +        The value of this field represents the type of the tunnel used
> for
> > +        sending the mirrored packets
>
> Perhaps you could list supported values here and explain what they are?
>
> > +      </p>
> > +    </column>
> > +
> > +    <column name="index">
> > +      <p>
> > +        The value of this field represents the key/idx depending on the
> > +        tunnel type configured
>
> A reader may be confused as to what "idx" is. Is it a ERSPAN term?
>
> > +      </p>
> > +    </column>
> > +
> > +    <column name="src">
> > +      <p>
> > +          The value of this field represents the source port for the
> mirror.
>
> AFAIU it represent (a list of) ports, no?
>
> > +          Please see the <ref table="Logical_Switch_Port"/> table.
> > +      </p>
> > +    </column>
> > +
> > +    <column name="external_ids">
> > +      See <em>External IDs</em> at the beginning of this document.
> > +    </column>
> > +  </table>
> > +
> >    <table name="Meter" title="Meter entry">
> >      <p>
> >        Each row in this table represents a meter that can be used for
> QoS or
> > diff --git a/ovn-sb.ovsschema b/ovn-sb.ovsschema
> > index 576ebbdeb..45431a18b 100644
> > --- a/ovn-sb.ovsschema
> > +++ b/ovn-sb.ovsschema
> > @@ -1,7 +1,7 @@
> >  {
> >      "name": "OVN_Southbound",
> > -    "version": "20.25.0",
> > -    "cksum": "53184112 28845",
> > +    "version": "20.26.0",
> > +    "cksum": "3740716587 29844",
> >      "tables": {
> >          "SB_Global": {
> >              "columns": {
> > @@ -142,6 +142,20 @@
> >              "indexes": [["datapath", "tunnel_key"],
> >                          ["datapath", "name"]],
> >              "isRoot": true},
> > +        "Mirror": {
> > +            "columns": {
> > +                "name": {"type": "string"},
> > +                "filter": {"type": {"key": {"type": "string",
> > +                                            "enum": ["set",
> > +                                                     ["from-lport",
> > +
> "to-lport","both"]]}}},
> > +                "sink":{"type": "string"},
> > +                "type": {"type": {"key": {"type": "string",
> > +                                            "enum": ["set",
> > +                                                     ["gre",
> "erspan"]]}}},
> > +                "index": {"type": "integer"}},
> > +            "indexes": [["name"]],
> > +            "isRoot": true},
> >          "Meter": {
> >              "columns": {
> >                  "name": {"type": "string"},
> > @@ -230,6 +244,11 @@
> >                                                        "refTable":
> "Encap",
> >                                                        "refType":
> "weak"},
> >                                      "min": 0, "max": "unlimited"}},
> > +                "mirror_rules": {"type": {"key": {"type": "uuid",
> > +                                          "refTable": "Mirror",
> > +                                          "refType": "weak"},
> > +                                  "min": 0,
> > +                                  "max": "unlimited"}},
> >                  "mac": {"type": {"key": "string",
> >                                   "min": 0,
> >                                   "max": "unlimited"}},
> > diff --git a/ovn-sb.xml b/ovn-sb.xml
> > index 37a709f83..24af40b7f 100644
> > --- a/ovn-sb.xml
> > +++ b/ovn-sb.xml
> > @@ -2742,6 +2742,47 @@ tcp.flags = RST;
> >      </column>
> >    </table>
> >
> > +  <table name="Mirror" title="Mirror Entry">
> > +    <p>
> > +      Each row in this table represents one Mirror that can be used for
> > +      port mirroring. These Mirrors are referenced by the
> > +      <ref column="mirror_rules" table="Port_Binding"/> column in
> > +      the <ref table="Port_Binding"/> table.
> > +    </p>
> > +
> > +    <column name="name">
> > +      <p>
> > +        Represents the name of the mirror.
> > +      </p>
> > +    </column>
> > +
> > +    <column name="filter">
> > +      <p>
> > +        The value of this field represents selection criteria of the
> mirror.
> > +      </p>
> > +    </column>
> > +
> > +    <column name="sink">
> > +      <p>
> > +        The value of this field represents the destination/sink of the
> mirror.
> > +      </p>
> > +    </column>
> > +
> > +    <column name="type">
> > +      <p>
> > +        The value of this field represents the type of the tunnel used
> for
> > +        sending the mirrored packets
> > +      </p>
> > +    </column>
> > +
> > +    <column name="index">
> > +      <p>
> > +        The value of this field represents the key/idx depending on the
> > +        tunnel type configured
> > +      </p>
> > +    </column>
> > +  </table>
> > +
> >    <table name="Meter" title="Meter entry">
> >      <p>
> >        Each row in this table represents a meter that can be used for
> QoS or
> > @@ -3244,6 +3285,11 @@ tcp.flags = RST;
> >        </column>
> >      </group>
> >
> > +    <column name="mirror_rules">
> > +        Mirror rules that apply to the port binding.
> > +        Please see the <ref table="Mirror"/> table.
> > +    </column>
> > +
> >      <group title="Patch Options">
> >        <p>
> >          These options apply to logical ports with <ref column="type"/>
> of
> > diff --git a/tests/ovn-nbctl.at b/tests/ovn-nbctl.at
> > index 726efa6f4..8f6ee6097 100644
> > --- a/tests/ovn-nbctl.at
> > +++ b/tests/ovn-nbctl.at
> > @@ -435,6 +435,101 @@ AT_CHECK([ovn-nbctl meter-list], [0], [dnl
> >
> >  dnl
> ---------------------------------------------------------------------
> >
> > +OVN_NBCTL_TEST([ovn_nbctl_mirrors], [mirrors], [
> > +AT_CHECK([ovn-nbctl mirror-add mirror1 gre 0 from-lport 10.10.10.1])
> > +AT_CHECK([ovn-nbctl mirror-add mirror2 erspan 1 both 10.10.10.2])
> > +AT_CHECK([ovn-nbctl mirror-add mirror3 gre 2 to-lport 10.10.10.3])
> > +AT_CHECK([ovn-nbctl ls-add sw0])
> > +AT_CHECK([ovn-nbctl lsp-add sw0 sw0-port1])
> > +AT_CHECK([ovn-nbctl lsp-add sw0 sw0-port2])
> > +AT_CHECK([ovn-nbctl lsp-add sw0 sw0-port3])
> > +
> > +dnl Add duplicate mirror name
> > +AT_CHECK([ovn-nbctl mirror-add mirror1 gre 0 from-lport 10.10.10.5],
> [1], [], [stderr])
> > +AT_CHECK([grep 'already exists' stderr], [0], [ignore])
> > +
> > +dnl Add mirror with invalid sink port
> > +AT_CHECK([ovn-nbctl mirror-add mirror1 gre 0 from-lport sw0-port4],
> [1], [], [stderr])
> > +AT_CHECK([grep 'already exists' stderr], [0], [ignore])
>
> The error message here seems to be not about an invalid port.
>
> > +
> > +dnl Attach source port to mirror
> > +AT_CHECK([ovn-nbctl lsp-attach-mirror sw0-port1 mirror3])
> > +
> > +dnl Attach one more source port to mirror
> > +AT_CHECK([ovn-nbctl lsp-attach-mirror sw0-port3 mirror3])
> > +
> > +dnl Attach invalid source port to mirror
> > +AT_CHECK([ovn-nbctl lsp-attach-mirror sw0-port4 mirror3], [1], [],
> [stderr])
> > +AT_CHECK([grep 'port name not found' stderr], [0], [ignore])
> > +
> > +dnl Detach one source port from mirror
> > +AT_CHECK([ovn-nbctl lsp-detach-mirror sw0-port3 mirror3])
> > +
> > +AT_CHECK([ovn-nbctl mirror-list], [0], [dnl
> > +mirror1:
> > +  Type     :  gre
> > +  Sink     :  10.10.10.1
> > +  Filter   :  from-lport
> > +  Index/Key:  0
> > +  Sources  :  None attached
> > +mirror2:
> > +  Type     :  erspan
> > +  Sink     :  10.10.10.2
> > +  Filter   :  both
> > +  Index/Key:  1
> > +  Sources  :  None attached
> > +mirror3:
> > +  Type     :  gre
> > +  Sink     :  10.10.10.3
> > +  Filter   :  to-lport
> > +  Index/Key:  2
> > +  Sources  :  sw0-port1
> > +])
>
> We should also check the case where multiple ports are attached to the
> same mirror.
>
> > +
> > +dnl Detach another source port from mirror
> > +AT_CHECK([ovn-nbctl lsp-detach-mirror sw0-port1 mirror3])
> > +
> > +dnl Delete a single mirror.
> > +AT_CHECK([ovn-nbctl mirror-del mirror3])
>
> Can you also check here what happens when you delete a mirror that is
> attached? (The mirror is properly detached and deleted.)
>
> > +
> > +AT_CHECK([ovn-nbctl mirror-list], [0], [dnl
> > +mirror1:
> > +  Type     :  gre
> > +  Sink     :  10.10.10.1
> > +  Filter   :  from-lport
> > +  Index/Key:  0
> > +  Sources  :  None attached
> > +mirror2:
> > +  Type     :  erspan
> > +  Sink     :  10.10.10.2
> > +  Filter   :  both
> > +  Index/Key:  1
> > +  Sources  :  None attached
> > +])
> > +
> > +dnl Delete another mirror.
> > +AT_CHECK([ovn-nbctl mirror-del mirror2])
> > +
> > +dnl Update the Sink address
> > +AT_CHECK([ovn-nbctl set mirror . sink=192.168.1.13])
> > +
> > +AT_CHECK([ovn-nbctl mirror-list], [0], [dnl
> > +mirror1:
> > +  Type     :  gre
> > +  Sink     :  192.168.1.13
> > +  Filter   :  from-lport
> > +  Index/Key:  0
> > +  Sources  :  None attached
> > +])
> > +
> > +dnl Delete all mirrors and remove switch
> > +AT_CHECK([ovn-nbctl mirror-del])
> > +AT_CHECK([ovn-nbctl ls-del sw0])
>
> Why is this ls-del needed? This seems irrelevant to what the test case
> checks - mirror commands working.
>
> > +AT_CHECK([ovn-nbctl mirror-list], [0], [dnl
> > +])])
> > +
> > +dnl
> ---------------------------------------------------------------------
> > +
> >  OVN_NBCTL_TEST([ovn_nbctl_nats], [NATs], [
> >  AT_CHECK([ovn-nbctl lr-add lr0])
> >  AT_CHECK([ovn-nbctl lr-nat-add lr0 snatt 30.0.0.2 192.168.1.2], [1], [],
> > diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
> > index 157f9f60c..b7d92b60c 100644
> > --- a/tests/ovn-northd.at
> > +++ b/tests/ovn-northd.at
> > @@ -2205,6 +2205,56 @@ check_meter_by_name NOT meter_me__${acl1}
> meter_me__${acl2}
> >  AT_CLEANUP
> >  ])
> >
> > +OVN_FOR_EACH_NORTHD([
> > +AT_SETUP([Check NB-SB mirrors sync])
> > +AT_KEYWORDS([mirrors])
> > +ovn_start
> > +
> > +check ovn-nbctl --wait=sb mirror-add mirror1 erspan 0 both 10.10.10.2
> > +
> > +AT_CHECK([ovn-sbctl get Mirror . Sink], [0], [dnl
> > +"10.10.10.2"
> > +])
> > +
> > +AT_CHECK([ovn-sbctl get Mirror . type], [0], [dnl
> > +erspan
> > +])
> > +
> > +AT_CHECK([ovn-sbctl get Mirror . index], [0], [dnl
> > +0
> > +])
> > +
> > +AT_CHECK([ovn-sbctl get Mirror . filter], [0], [dnl
> > +both
> > +])
> > +
> > +check ovn-nbctl --wait=sb \
> > +    -- set mirror . sink=192.168.1.13 \
> > +    -- set mirror . type=gre \
> > +    -- set mirror . index=12 \
> > +    -- set mirror . filter=to-lport
>
> While more work here, I think it's worth updating fields one by one
> and checking that each update triggeres the corresponding update (and
> other fields are not e.g. reset to nul-values).
>
> > +
> > +AT_CHECK([ovn-sbctl get Mirror . Sink], [0], [dnl
> > +"192.168.1.13"
> > +])
> > +
> > +AT_CHECK([ovn-sbctl get Mirror . type], [0], [dnl
> > +gre
> > +])
> > +
> > +AT_CHECK([ovn-sbctl get Mirror . index], [0], [dnl
> > +12
> > +])
> > +
> > +AT_CHECK([ovn-sbctl get Mirror . filter], [0], [dnl
> > +to-lport
> > +])
> > +
> > +check ovn-nbctl --wait=sb mirror-del
>
> This mirror-del seems unneeded unless you would then check that SB
> objects are cleaned up.
>
> > +
> > +AT_CLEANUP
> > +])
> > +
> >  OVN_FOR_EACH_NORTHD([
> >  AT_SETUP([ACL skip hints for stateless config])
> >  AT_KEYWORDS([acl])
> > diff --git a/tests/ovn.at b/tests/ovn.at
> > index bba2c9c1d..9c2905ea8 100644
> > --- a/tests/ovn.at
> > +++ b/tests/ovn.at
> > @@ -16082,6 +16082,165 @@ 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"
> > +
> > +# 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
>
> remove
>
> > +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
> > +
> > +# 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
> > +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 MIRROR
> > +#
> > +# 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 - Currently checking only for GRE
> > +# MIRROR - Mirror name
> > +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_name=${11}
> > +    shift; shift; shift; shift; shift; shift; shift
> > +    shift; shift; shift; shift;
> > +
> > +    check ovn-nbctl mirror-add ${mirror_name} ${mirror_encap_type} 0
> to-lport 192.168.1.12
> > +    check ovn-nbctl lsp-attach-mirror ls1-lp1 ${mirror_name}
>
> I think this doesn't belong in this function. Please move it outside.
>
> > +
> > +    # 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
> > +    if test X$exp_icmp_chksum != X; then
>
> why is this check here? I don't think you ever call it without
> exp_icmp_chksum set.
>
> > +        # 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
>
> do you ever call it with any other mirror_encap_type? Why the check here?
>
> > +            #echo "GRE"
>
> remove
>
> > +            local
> mirror=${remote_mac}${local_mac}08004500007e00004000402fb6e9c0a8010bc0a8010c2000655800000000${reply}
>
> Is it the same as "local packet" above, just with different MACs? If
> so, could we reconstruct it using the same formula / by replacing MACs
> at the start of the string representing the packet?
>
> > +        fi
> > +        echo $mirror >> br-phys_n1.expected
> > +    fi
> > +}
> > +
> > +# Set IPs
> > +rtr_l2_ip=$(ip_to_hex 172 16 1 1)
> > +l1_ip=$(ip_to_hex 192 168 1 2)
> > +
> > +# 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" mirror0
> > +
> > +OVN_CHECK_PACKETS_CONTAIN([hv1/br-phys_n1-tx.pcap],
> [br-phys_n1.expected])
>
> could you please also check the reply was actually received at the
> destination, so that we know that mirroring didn't break delivery
>
> > +
> > +tcpdump -r hv1/br-phys_n1-tx.pcap
> > +
> > +echo "---------OVN NB Mirror-----"
> > +ovn-nbctl mirror-list
> > +
> > +echo "---------OVS Mirror----"
> > +ovs-vsctl list Mirror
> > +
> > +echo "-----------------------"
> > +echo "Delete vif1 so that OVN releases port binding"
> > +ovs-vsctl del-port br-int vif1
> > +
> > +echo "Verifying Mirror deletion in OVS"
> > +
> > +AT_CHECK([as hv1 ovs-vsctl list Mirror], [0], [dnl
> > +])
> > +
> > +OVN_CLEANUP([hv1], [hv2])
> > +AT_CLEANUP
> > +])
> >
>
> This test case should be expanded (or new test case variants added) to
> explore how different tunnel types (erspan vs gre), filters (to, from,
> both) behave.
>
> >  OVN_FOR_EACH_NORTHD([
> >  AT_SETUP([Port Groups])
> > diff --git a/utilities/ovn-nbctl.c b/utilities/ovn-nbctl.c
> > index 3bbdbd998..a30b5e4f2 100644
> > --- a/utilities/ovn-nbctl.c
> > +++ b/utilities/ovn-nbctl.c
> > @@ -271,6 +271,16 @@ QoS commands:\n\
> >                              remove QoS rules from SWITCH\n\
> >    qos-list SWITCH           print QoS rules for SWITCH\n\
> >  \n\
> > +Mirror commands:\n\
> > +  mirror-add NAME TYPE INDEX FILTER IP\n\
> > +                            add a mirror with given name\n\
> > +                            specify TYPE 'gre' or 'erspan'\n\
> > +                            specify INDEX gre key / erpsan idx\n\
> > +                            specify FILTER for mirroring selection\n\
>
> it may be helpful for a reader to see the list of supported FILTERs
>
> > +                            specify Sink / Destination i.e Remote IP \n\
>
> nit: "i.e." (dot at the end)
> nit: remove spurious space at the end of the line
>
> > +  mirror-del [NAME]         remove mirrors\n\
> > +  mirror-list               print mirrors\n\
> > +\n\
> >  Meter commands:\n\
> >    [--fair]\n\
> >    meter-add NAME ACTION RATE UNIT [BURST]\n\
> > @@ -311,6 +321,8 @@ Logical switch port commands:\n\
> >                              set dhcpv6 options for PORT\n\
> >    lsp-get-dhcpv6-options PORT  get the dhcpv6 options for PORT\n\
> >    lsp-get-ls PORT           get the logical switch which the port
> belongs to\n\
> > +  lsp-attach-mirror PORT MIRROR   attach source PORT to the MIRROR\n\
> > +  lsp-detach-mirror PORT MIRROR   detach source PORT from the MIRROR\n\
>
> nit: "to MIRROR", "from MIRROR" to match other places (e.g. we say
> "attach source PORT" not "attach a source PORT")
>
> >  \n\
> >  Forwarding group commands:\n\
> >    [--liveness]\n\
> > @@ -1685,6 +1697,130 @@ nbctl_pre_lsp_type(struct ctl_context *ctx)
> >      ovsdb_idl_add_column(ctx->idl, &nbrec_logical_switch_port_col_type);
> >  }
> >
> > +static void
> > +nbctl_pre_lsp_mirror(struct ctl_context *ctx)
> > +{
> > +    ovsdb_idl_add_column(ctx->idl, &nbrec_logical_switch_port_col_name);
> > +    ovsdb_idl_add_column(ctx->idl,
> > +                         &nbrec_logical_switch_port_col_mirror_rules);
> > +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_name);
> > +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_src);
> > +}
> > +
> > +static int
> > +mirror_cmp(const void *mirror1_, const void *mirror2_)
> > +{
> > +    const struct nbrec_mirror *const *mirror_1 = mirror1_;
> > +    const struct nbrec_mirror *const *mirror_2 = mirror2_;
> > +
> > +    const struct nbrec_mirror *mirror1 = *mirror_1;
> > +    const struct nbrec_mirror *mirror2 = *mirror_2;
> > +
> > +    return strcmp(mirror1->name,mirror2->name);
> > +}
> > +
> > +static char * OVS_WARN_UNUSED_RESULT
> > +mirror_by_name_or_uuid(struct ctl_context *ctx, const char *id,
> > +                    bool must_exist,
> > +                    const struct nbrec_mirror **mirror_p)
> > +{
> > +    const struct nbrec_mirror *mirror = NULL;
> > +    *mirror_p = NULL;
> > +
> > +    struct uuid mirror_uuid;
> > +    bool is_uuid = uuid_from_string(&mirror_uuid, id);
> > +    if (is_uuid) {
> > +        mirror = nbrec_mirror_get_for_uuid(ctx->idl, &mirror_uuid);
> > +    }
> > +
> > +    if (!mirror) {
> > +        NBREC_MIRROR_FOR_EACH (mirror, ctx->idl) {
> > +            if (!strcmp(mirror->name, id)) {
> > +                break;
> > +            }
> > +        }
> > +    }
> > +
> > +    if (!mirror && must_exist) {
> > +        return xasprintf("%s: mirror %s not found",
> > +                         id, is_uuid ? "UUID" : "name");
> > +    }
> > +
> > +    *mirror_p = mirror;
> > +    return NULL;
> > +}
> > +
> > +static void
> > +nbctl_lsp_attach_mirror(struct ctl_context *ctx)
> > +{
> > +    const char *port = ctx->argv[1];
> > +    const char *mirror_name = ctx->argv[2];
> > +    const struct nbrec_logical_switch_port *lsp = NULL;
> > +    const struct nbrec_mirror *mirror;
> > +
> > +    char *error;
> > +
> > +    error = lsp_by_name_or_uuid(ctx, port, true, &lsp);
> > +    if (error) {
> > +        ctx->error = error;
> > +        return;
> > +    }
> > +
> > +
> > +    /*check if a mirror rule actually exists on that name or not*/
> > +    error = mirror_by_name_or_uuid(ctx, mirror_name, true, &mirror);
> > +    if (error) {
> > +        ctx->error = error;
> > +        return;
> > +    }
> > +
> > +    /* Check if same mirror rule already exists for the lsp */
> > +    for (size_t i = 0; i < lsp->n_mirror_rules; i++) {
> > +        if (!mirror_cmp(&lsp->mirror_rules[i], &mirror)) {
> > +            bool may_exist = shash_find(&ctx->options, "--may-exist")
> != NULL;
> > +            if (!may_exist) {
> > +                ctl_error(ctx, "Same mirror already existed on the lsp
> %s.",
> > +                          ctx->argv[1]);
> > +                return;
> > +            }
> > +            return;
> > +        }
> > +    }
> > +
> > +    nbrec_logical_switch_port_update_mirror_rules_addvalue(lsp, mirror);
> > +    nbrec_mirror_update_src_addvalue(mirror,lsp);
> > +
> > +}
> > +
> > +static void
> > +nbctl_lsp_detach_mirror(struct ctl_context *ctx)
> > +{
> > +    const char *port = ctx->argv[1];
> > +    const char *mirror_name = ctx->argv[2];
> > +    const struct nbrec_logical_switch_port *lsp = NULL;
> > +    const struct nbrec_mirror *mirror;
> > +
> > +    char *error;
> > +
> > +    error = lsp_by_name_or_uuid(ctx, port, true, &lsp);
> > +    if (error) {
> > +        ctx->error = error;
> > +        return;
> > +    }
> > +
> > +
> > +    /*check if a mirror rule actually exists on that name or not*/
> > +    error = mirror_by_name_or_uuid(ctx, mirror_name, true, &mirror);
> > +    if (error) {
> > +        ctx->error = error;
> > +        return;
> > +    }
> > +
> > +    nbrec_logical_switch_port_update_mirror_rules_delvalue(lsp, mirror);
> > +    nbrec_mirror_update_src_delvalue(mirror,lsp);
> > +
> > +}
> > +
> >  static void
> >  nbctl_lsp_set_type(struct ctl_context *ctx)
> >  {
> > @@ -7239,6 +7375,202 @@ cmd_ha_ch_grp_set_chassis_prio(struct
> ctl_context *ctx)
> >      nbrec_ha_chassis_set_priority(ha_chassis, priority);
> >  }
> >
> > +static char * OVS_WARN_UNUSED_RESULT
> > +parse_filter(const char *arg, const char **selection_p)
> > +{
> > +    /* Validate selection.  Only require the first letter. */
> > +    if (arg[0] == 't') {
> > +        *selection_p = "to-lport";
> > +    } else if (arg[0] == 'f') {
> > +        *selection_p = "from-lport";
> > +    } else if (arg[0] == 'b') {
> > +        *selection_p = "both";
> > +    } else {
> > +        *selection_p = NULL;
> > +        return xasprintf("%s: selection must be \"to-lport\" or "
> > +                         "\"from-lport\" or \"both\" ", arg);
> > +    }
> > +    return NULL;
> > +}
> > +
> > +static char * OVS_WARN_UNUSED_RESULT
> > +parse_type(const char *arg, const char **type_p)
> > +{
> > +    /* Validate type.  Only require the second letter. */
>
> you check the first letter though
>
> > +    if (arg[0] == 'g') {
> > +        *type_p = "gre";
> > +    } else if (arg[0] == 'e') {
> > +        *type_p = "erspan";
> > +    } else {
> > +        *type_p = NULL;
> > +        return xasprintf("%s: type must be \"gre\" or "
> > +                         "\"erspan\"", arg);
> > +    }
> > +    return NULL;
> > +}
> > +
> > +static void
> > +nbctl_pre_mirror_add(struct ctl_context *ctx)
> > +{
> > +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_name);
> > +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_filter);
> > +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_index);
> > +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_sink);
> > +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_type);
> > +}
> > +
> > +static void
> > +nbctl_mirror_add(struct ctl_context *ctx)
> > +{
> > +    const char *filter = NULL;
> > +    const char *sink_ip = NULL;
> > +    const char *type = NULL;
> > +    const char *name = NULL;
> > +    char *new_external_ip = NULL;
> > +    int64_t index;
> > +    char *error = NULL;
> > +    const struct nbrec_mirror *mirror_check = NULL;
> > +
> > +    /* Mirror Name */
> > +    name = ctx->argv[1];
> > +    NBREC_MIRROR_FOR_EACH (mirror_check, ctx->idl) {
> > +            if (!strcmp(mirror_check->name, name)) {
> > +                ctl_error(ctx, "Mirror with %s name already exists.",
> > +                          name);
> > +                return;
> > +            }
> > +        }
> > +
> > +    /* Tunnel Type - GRE/ERSPAN */
> > +    error = parse_type(ctx->argv[2], &type);
> > +    if (error) {
> > +        ctx->error = error;
> > +        return;
> > +    }
> > +
> > +    /* tunnel index / GRE key / ERSPAN idx */
> > +    index = atoi(ctx->argv[3]);
> > +
> > +    /* Filter for mirroring */
> > +    error = parse_filter(ctx->argv[4], &filter);
> > +    if (error) {
> > +        ctx->error = error;
> > +        return;
> > +    }
> > +
> > +    /* Destination / Sink details */
> > +    sink_ip = ctx->argv[5];
> > +
> > +    /* check if it is a valid ip */
> > +    new_external_ip = normalize_ipv4_addr_str(sink_ip);
>
> new_sink_ip?
>
> > +    if (!new_external_ip) {
> > +        new_external_ip = normalize_ipv6_addr_str(sink_ip);
> > +    }
> > +
> > +    if (new_external_ip) {
> > +        free(new_external_ip);
> > +    } else {
> > +        ctl_error(ctx, "Invalid sink ip: %s", sink_ip);
> > +        return;
> > +    }
> > +
> > +    /* Create the mirror. */
> > +    struct nbrec_mirror *mirror = nbrec_mirror_insert(ctx->txn);
> > +    nbrec_mirror_set_name(mirror, name);
> > +    nbrec_mirror_set_index(mirror, index);
> > +    nbrec_mirror_set_filter(mirror, filter);
> > +    nbrec_mirror_set_type(mirror, type);
> > +    nbrec_mirror_set_sink(mirror, sink_ip);
> > +
> > +}
> > +
> > +static void
> > +nbctl_pre_mirror_del(struct ctl_context *ctx)
> > +{
> > +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_name);
> > +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_src);
> > +}
> > +
> > +static void
> > +nbctl_mirror_del(struct ctl_context *ctx)
> > +{
> > +    const struct nbrec_mirror *mirror, *next;
> > +
> > +    /* If a name is not specified, delete all mirrors. */
> > +    if (ctx->argc == 1) {
> > +        NBREC_MIRROR_FOR_EACH_SAFE (mirror, next, ctx->idl) {
> > +            nbrec_mirror_delete(mirror);
> > +        }
> > +        return;
> > +    }
> > +
> > +    /* Remove the matching mirror. */
> > +    NBREC_MIRROR_FOR_EACH (mirror, ctx->idl) {
> > +        if (strcmp(ctx->argv[1], mirror->name)) {
> > +            continue;
> > +        }
> > +        nbrec_mirror_delete(mirror);
> > +        return;
> > +    }
> > +}
> > +
> > +static void
> > +nbctl_pre_mirror_list(struct ctl_context *ctx)
> > +{
> > +    ovsdb_idl_add_column(ctx->idl, &nbrec_logical_switch_port_col_name);
> > +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_name);
> > +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_filter);
> > +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_index);
> > +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_sink);
> > +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_type);
> > +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_src);
> > +}
> > +
> > +static void
> > +nbctl_mirror_list(struct ctl_context *ctx)
> > +{
> > +
> > +    const struct nbrec_mirror **mirrors = NULL;
> > +    const struct nbrec_mirror *mirror;
> > +    size_t n_capacity = 0;
> > +    size_t n_mirrors = 0;
> > +
> > +    NBREC_MIRROR_FOR_EACH (mirror, ctx->idl) {
> > +        if (n_mirrors == n_capacity) {
> > +            mirrors = x2nrealloc(mirrors, &n_capacity, sizeof *mirrors);
> > +        }
> > +
> > +        mirrors[n_mirrors] = mirror;
> > +        n_mirrors++;
> > +    }
> > +
> > +    if (n_mirrors) {
> > +        qsort(mirrors, n_mirrors, sizeof *mirrors, mirror_cmp);
> > +    }
> > +
> > +    for (size_t i = 0; i < n_mirrors; i++) {
> > +        mirror = mirrors[i];
> > +        ds_put_format(&ctx->output, "%s:\n", mirror->name);
> > +        /* print all the values */
> > +        ds_put_format(&ctx->output, "  Type     :  %s\n", mirror->type);
> > +        ds_put_format(&ctx->output, "  Sink     :  %s\n", mirror->sink);
> > +        ds_put_format(&ctx->output, "  Filter   :  %s\n",
> mirror->filter);
> > +        ds_put_format(&ctx->output, "  Index/Key:  %ld\n",
> > +                                                 (long
> int)mirror->index);
> > +        ds_put_cstr(&ctx->output,   "  Sources  :");
> > +        if (mirror->n_src > 0) {
> > +            for (size_t j = 0; j < mirror->n_src; j++) {
> > +                ds_put_format(&ctx->output, "  %s",
> mirror->src[j]->name);
> > +            }
> > +        } else {
> > +            ds_put_cstr(&ctx->output, "  None attached");
> > +        }
> > +        ds_put_cstr(&ctx->output, "\n");
> > +    }
> > +
> > +    free(mirrors);
> > +}
> > +
> >  static const struct ctl_table_class tables[NBREC_N_TABLES] = {
> >      [NBREC_TABLE_DHCP_OPTIONS].row_ids
> >      = {{&nbrec_logical_switch_port_col_name, NULL,
> > @@ -7332,6 +7664,15 @@ static const struct ctl_command_syntax
> nbctl_commands[] = {
> >      { "qos-list", 1, 1, "SWITCH", nbctl_pre_qos_list, nbctl_qos_list,
> >        NULL, "", RO },
> >
> > +    /* mirror commands. */
> > +    { "mirror-add", 5, 5,
> > +      "NAME TYPE INDEX FILTER IP",
> > +      nbctl_pre_mirror_add, nbctl_mirror_add, NULL, "--may-exist", RW },
> > +    { "mirror-del", 0, 1, "[NAME]",
> > +      nbctl_pre_mirror_del, nbctl_mirror_del, NULL, "", RW },
> > +    { "mirror-list", 0, 0, "", nbctl_pre_mirror_list, nbctl_mirror_list,
> > +      NULL, "", RO },
> > +
> >      /* meter commands. */
> >      { "meter-add", 4, 5, "NAME ACTION RATE UNIT [BURST]",
> nbctl_pre_meter_add,
> >        nbctl_meter_add, NULL, "--fair,--may-exist", RW },
> > @@ -7386,6 +7727,10 @@ static const struct ctl_command_syntax
> nbctl_commands[] = {
> >        nbctl_lsp_get_dhcpv6_options, NULL, "", RO },
> >      { "lsp-get-ls", 1, 1, "PORT", nbctl_pre_lsp_get_ls,
> nbctl_lsp_get_ls,
> >        NULL, "", RO },
> > +    { "lsp-attach-mirror", 2, 2, "PORT MIRROR", nbctl_pre_lsp_mirror,
> > +      nbctl_lsp_attach_mirror, NULL, "", RW },
> > +    { "lsp-detach-mirror", 2, 2, "PORT MIRROR", nbctl_pre_lsp_mirror,
> > +      nbctl_lsp_detach_mirror, NULL, "", RW },
> >
> >      /* forwarding group commands. */
> >      { "fwd-group-add", 4, INT_MAX, "SWITCH GROUP VIP VMAC PORT...",
> > diff --git a/utilities/ovn-sbctl.c b/utilities/ovn-sbctl.c
> > index e35556d34..1aa6cc26f 100644
> > --- a/utilities/ovn-sbctl.c
> > +++ b/utilities/ovn-sbctl.c
> > @@ -307,6 +307,7 @@ pre_get_info(struct ctl_context *ctx)
> >      ovsdb_idl_add_column(ctx->idl, &sbrec_port_binding_col_chassis);
> >      ovsdb_idl_add_column(ctx->idl, &sbrec_port_binding_col_datapath);
> >      ovsdb_idl_add_column(ctx->idl, &sbrec_port_binding_col_up);
> > +    ovsdb_idl_add_column(ctx->idl,
> &sbrec_port_binding_col_mirror_rules);
> >
> >      ovsdb_idl_add_column(ctx->idl,
> &sbrec_logical_flow_col_logical_datapath);
> >      ovsdb_idl_add_column(ctx->idl,
> &sbrec_logical_flow_col_logical_dp_group);
> > @@ -1434,6 +1435,9 @@ static const struct ctl_table_class
> tables[SBREC_N_TABLES] = {
> >      [SBREC_TABLE_HA_CHASSIS].row_ids[0]
> >      = {&sbrec_ha_chassis_col_chassis, NULL, NULL},
> >
> > +    [SBREC_TABLE_MIRROR].row_ids[0]
> > +    = {&sbrec_mirror_col_name, NULL, NULL},
> > +
> >      [SBREC_TABLE_METER].row_ids[0]
> >      = {&sbrec_meter_col_name, NULL, NULL},
> >
> > --
> > 2.27.0
> >
> > _______________________________________________
> > dev mailing list
> > dev@openvswitch.org
> > https://mail.openvswitch.org/mailman/listinfo/ovs-dev
>
> Thanks & Regards,
Abhiram R N
Ihar Hrachyshka Aug. 25, 2022, 6:14 p.m. UTC | #4
On Thu, Aug 25, 2022 at 1:57 PM Abhiram R N <arn@redhat.com> wrote:
>
> Hi Ihar,
>
> Thanks for the review of v4 patch.
> I will take care of the changes you have suggested in the xml(ovn-nb.xml), the tests (ovn-nbctl.at, ovn.at, ovn-northd.at) and ovn-nbctl.c in the next patch.
>
> Other than those regarding the ovn-controller.c and ovn-nb.ovsschema I have replied inline below.
> Kindly take a look and let me know your comments.
>
> On Thu, Aug 25, 2022 at 5:21 AM Ihar Hrachyshka <ihrachys@redhat.com> wrote:
>>
>> Hi,
>>
>> I've reviewed everything except mirror.[ch] files. See below.
>>
>> On Mon, Aug 22, 2022 at 4:41 PM Abhiram R N <abhiramrn@gmail.com> wrote:
>> >
>> > Added changes in ovn-nbctl, ovn-sbctl, northd and in ovn-controller.
>> > While 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.
>> >
>> > 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>
>> > ---
>> > V3 --> V4: Addressed review comments from V3
>> >            i) Fixed Issues 1,2 and 3
>> >           ii) Did other minor changes suggested
>> >          iii) Included Issues 1 and 3 in test cases
>> > Files modified (V3 --> v4):
>> > Code --> mirror.c, ovn-controller.c, northd.c
>> > Test --> ovn-northd.at, ovn-nbctl.at, ovn.at
>> >
>> >  controller/automake.mk      |   4 +-
>> >  controller/mirror.c         | 510 ++++++++++++++++++++++++++++++++++++
>> >  controller/mirror.h         |  53 ++++
>> >  controller/ovn-controller.c | 222 +++++++++++++++-
>> >  northd/en-northd.c          |   4 +
>> >  northd/inc-proc-northd.c    |   4 +
>> >  northd/northd.c             | 136 ++++++++++
>> >  northd/northd.h             |   2 +
>> >  ovn-nb.ovsschema            |  31 ++-
>> >  ovn-nb.xml                  |  57 ++++
>> >  ovn-sb.ovsschema            |  23 +-
>> >  ovn-sb.xml                  |  46 ++++
>> >  tests/ovn-nbctl.at          |  95 +++++++
>> >  tests/ovn-northd.at         |  50 ++++
>> >  tests/ovn.at                | 159 +++++++++++
>> >  utilities/ovn-nbctl.c       | 345 ++++++++++++++++++++++++
>> >  utilities/ovn-sbctl.c       |   4 +
>> >  17 files changed, 1739 insertions(+), 6 deletions(-)
>> >  create mode 100644 controller/mirror.c
>> >  create mode 100644 controller/mirror.h
>> >
>> > 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..44895d8ca
>> > --- /dev/null
>> > +++ b/controller/mirror.c
>> > @@ -0,0 +1,510 @@
>> > +/* 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 "openvswitch/vlog.h"
>> > +
>> > +/* OVN includes. */
>> > +#include "binding.h"
>> > +#include "lib/ovn-sb-idl.h"
>> > +#include "mirror.h"
>> > +
>> > +VLOG_DEFINE_THIS_MODULE(port_mirror);
>> > +
>> > +/* Static function declarations */
>> > +
>> > +static const struct ovsrec_port *
>> > +get_port_for_iface(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 bool
>> > +mirror_create(const struct sbrec_port_binding *pb,
>> > +              struct port_mirror_ctx *pm_ctx)
>> > +{
>> > +    const struct ovsrec_mirror *mirror = NULL;
>> > +
>> > +    if (pb->n_up && !pb->up[0]) {
>> > +        return true;
>> > +    }
>> > +
>> > +    if (pb->chassis != pm_ctx->chassis_rec) {
>> > +        return true;
>> > +    }
>> > +
>> > +    if (!pm_ctx->ovs_idl_txn) {
>> > +        return false;
>> > +    }
>> > +
>> > +
>> > +    VLOG_INFO("Mirror rule(s) present for %s ", pb->logical_port);
>> > +    /* Loop through the mirror rules */
>> > +    for (size_t i =0; i < pb->n_mirror_rules; i++) {
>> > +        /* check if the mirror already exists in OVS DB */
>> > +        bool create_mirror = true;
>> > +        OVSREC_MIRROR_TABLE_FOR_EACH (mirror, pm_ctx->mirror_table) {
>> > +            if (!strcmp(pb->mirror_rules[i]->name, mirror->name)) {
>> > +                  /* Mirror with same name already exists
>> > +                   * No need to create mirror
>> > +                   */
>> > +                  create_mirror = false;
>> > +                  break;
>> > +            }
>> > +        }
>> > +
>> > +        if (create_mirror) {
>> > +
>> > +            struct smap options = SMAP_INITIALIZER(&options);
>> > +            char *port_name, *key;
>> > +
>> > +            key = xasprintf("%ld",(long int)pb->mirror_rules[i]->index);
>> > +            smap_add(&options, "remote_ip", pb->mirror_rules[i]->sink);
>> > +
>> > +            if (!strcmp(pb->mirror_rules[i]->type, "gre")) {
>> > +                /* Set the GRE key */
>> > +                smap_add(&options, "key", key);
>> > +
>> > +            } else if (!strcmp(pb->mirror_rules[i]->type, "erspan")) {
>> > +                /* Set the ERSPAN index */
>> > +                smap_add(&options, "key", key);
>> > +                smap_add(&options, "erspan_idx", key);
>> > +                smap_add(&options, "erspan_ver","1");
>> > +
>> > +            }
>> > +            struct ovsrec_interface *iface =
>> > +                      ovsrec_interface_insert(pm_ctx->ovs_idl_txn);
>> > +            port_name = xasprintf("ovn-%s",
>> > +                                   pb->mirror_rules[i]->name);
>> > +
>> > +            ovsrec_interface_set_name(iface, port_name);
>> > +            ovsrec_interface_set_type(iface, pb->mirror_rules[i]->type);
>> > +            ovsrec_interface_set_options(iface, &options);
>> > +
>> > +            struct ovsrec_port *port =
>> > +                              ovsrec_port_insert(pm_ctx->ovs_idl_txn);
>> > +            ovsrec_port_set_name(port, port_name);
>> > +            ovsrec_port_set_interfaces(port, &iface, 1);
>> > +
>> > +            ovsrec_bridge_update_ports_addvalue(pm_ctx->br_int, port);
>> > +
>> > +            smap_destroy(&options);
>> > +            free(port_name);
>> > +            free(key);
>> > +
>> > +            VLOG_INFO("Creating Mirror in OVS DB");
>> > +            mirror = ovsrec_mirror_insert(pm_ctx->ovs_idl_txn);
>> > +            ovsrec_mirror_set_name(mirror,pb->mirror_rules[i]->name);
>> > +            ovsrec_mirror_update_output_port_addvalue(mirror, port);
>> > +            ovsrec_bridge_update_mirrors_addvalue(pm_ctx->br_int,
>> > +                                                             mirror);
>> > +        }
>> > +
>> > +        struct local_binding *lbinding = local_binding_find(
>> > +                               pm_ctx->local_bindings, pb->logical_port);
>> > +        const struct ovsrec_port *p =
>> > +                     get_port_for_iface(lbinding->iface, pm_ctx->br_int);
>> > +        if (p) {
>> > +            if (!strcmp(pb->mirror_rules[i]->filter,"from-lport")) {
>> > +                ovsrec_mirror_update_select_src_port_addvalue(mirror, p);
>> > +            } else if (!strcmp(pb->mirror_rules[i]->filter,"to-lport")) {
>> > +                ovsrec_mirror_update_select_dst_port_addvalue(mirror, p);
>> > +            } else {
>> > +                ovsrec_mirror_update_select_src_port_addvalue(mirror, p);
>> > +                ovsrec_mirror_update_select_dst_port_addvalue(mirror, p);
>> > +            }
>> > +        }
>> > +    }
>> > +    return true;
>> > +}
>> > +
>> > +static void
>> > +check_and_update_mirror_table(const struct sbrec_mirror *sb_mirror,
>> > +                              struct ovsrec_mirror *ovs_mirror)
>> > +{
>> > +    char *filter;
>> > +    if ((ovs_mirror->n_select_dst_port)
>> > +            && (ovs_mirror->n_select_src_port)) {
>> > +        filter = xasprintf("both");
>> > +    } else if (ovs_mirror->n_select_dst_port) {
>> > +        filter = xasprintf("to-lport");
>> > +    } else {
>> > +        filter = xasprintf("from-lport");
>> > +    }
>> > +
>> > +    if (strcmp(sb_mirror->filter, filter)) {
>> > +        if (!strcmp(sb_mirror->filter,"from-lport")
>> > +                              && !strcmp(filter,"both")) {
>> > +            for (size_t i = 0; i < ovs_mirror->n_select_dst_port; i++) {
>> > +                ovsrec_mirror_update_select_dst_port_delvalue(ovs_mirror,
>> > +                                             ovs_mirror->select_dst_port[i]);
>> > +            }
>> > +        } else if (!strcmp(sb_mirror->filter,"to-lport")
>> > +                              && !strcmp(filter,"both")) {
>> > +            for (size_t i = 0; i < ovs_mirror->n_select_src_port; i++) {
>> > +                ovsrec_mirror_update_select_src_port_delvalue(ovs_mirror,
>> > +                                             ovs_mirror->select_src_port[i]);
>> > +            }
>> > +        } else if (!strcmp(sb_mirror->filter,"both")
>> > +                              && !strcmp(filter,"from-lport")) {
>> > +            for (size_t i = 0; i < ovs_mirror->n_select_src_port; i++) {
>> > +                ovsrec_mirror_update_select_dst_port_addvalue(ovs_mirror,
>> > +                                             ovs_mirror->select_src_port[i]);
>> > +            }
>> > +        } else if (!strcmp(sb_mirror->filter,"both")
>> > +                              && !strcmp(filter,"to-lport")) {
>> > +            for (size_t i = 0; i < ovs_mirror->n_select_dst_port; i++) {
>> > +                ovsrec_mirror_update_select_src_port_addvalue(ovs_mirror,
>> > +                                             ovs_mirror->select_dst_port[i]);
>> > +            }
>> > +        } else if (!strcmp(sb_mirror->filter,"to-lport")
>> > +                              && !strcmp(filter,"from-lport")) {
>> > +            for (size_t i = 0; i < ovs_mirror->n_select_src_port; i++) {
>> > +                ovsrec_mirror_update_select_dst_port_addvalue(ovs_mirror,
>> > +                                             ovs_mirror->select_src_port[i]);
>> > +                ovsrec_mirror_update_select_src_port_delvalue(ovs_mirror,
>> > +                                             ovs_mirror->select_src_port[i]);
>> > +            }
>> > +        } else if (!strcmp(sb_mirror->filter,"from-lport")
>> > +                              && !strcmp(filter,"to-lport")) {
>> > +            for (size_t i = 0; i < ovs_mirror->n_select_dst_port; i++) {
>> > +                ovsrec_mirror_update_select_src_port_addvalue(ovs_mirror,
>> > +                                             ovs_mirror->select_dst_port[i]);
>> > +                ovsrec_mirror_update_select_dst_port_delvalue(ovs_mirror,
>> > +                                             ovs_mirror->select_dst_port[i]);
>> > +            }
>> > +        }
>> > +    }
>> > +}
>> > +
>> > +static void
>> > +check_and_update_interface_table(const struct sbrec_mirror *sb_mirror,
>> > +                                   struct ovsrec_mirror *ovs_mirror)
>> > +{
>> > +    struct smap options = SMAP_INITIALIZER(&options);
>> > +    char *key, *type;
>> > +    struct smap *opts = &ovs_mirror->output_port->interfaces[0]->options;
>> > +    struct ovsrec_interface *iface =
>> > +                          ovs_mirror->output_port->interfaces[0];
>> > +
>> > +    const char *erspan_ver = smap_get(opts, "erspan_ver");
>> > +    if (erspan_ver) {
>> > +        type = xasprintf("erspan");
>> > +    } else {
>> > +        type = xasprintf("gre");
>> > +    }
>> > +    if (strcmp(type, sb_mirror->type)) {
>> > +        ovsrec_interface_set_type(iface, sb_mirror->type);
>> > +    }
>> > +
>> > +    key = xasprintf("%ld",(long int)sb_mirror->index);
>> > +    smap_add(&options, "remote_ip", sb_mirror->sink);
>> > +
>> > +    if (!strcmp(sb_mirror->type, "gre")) {
>> > +        /* Set the GRE key */
>> > +        smap_add(&options, "key", key);
>> > +
>> > +    } else if (!strcmp(sb_mirror->type, "erspan")) {
>> > +        /* Set the ERSPAN index */
>> > +        smap_add(&options, "key", key);
>> > +        smap_add(&options, "erspan_idx", key);
>> > +        smap_add(&options, "erspan_ver","1");
>> > +
>> > +    }
>> > +
>> > +    ovsrec_interface_set_options(iface, &options);
>> > +    smap_destroy(&options);
>> > +    free(key);
>> > +
>> > +}
>> > +
>> > +static void
>> > +mirror_update(const struct sbrec_mirror *sb_mirror,
>> > +              struct ovsrec_mirror *ovs_mirror)
>> > +{
>> > +    check_and_update_interface_table(sb_mirror, ovs_mirror);
>> > +
>> > +    check_and_update_mirror_table(sb_mirror, ovs_mirror);
>> > +}
>> > +
>> > +static void
>> > +mirror_delete(const struct sbrec_port_binding *pb,
>> > +              struct port_mirror_ctx *pm_ctx,
>> > +              struct shash *pb_mirror_map,
>> > +              bool delete_all)
>> > +{
>> > +    struct sset pb_mirrors = SSET_INITIALIZER(&pb_mirrors);
>> > +
>> > +    if (!delete_all) {
>> > +        for (size_t i = 0; i < pb->n_mirror_rules ; i++) {
>> > +            sset_add(&pb_mirrors, pb->mirror_rules[i]->name);
>> > +        }
>> > +    }
>> > +
>> > +    if (delete_all && (shash_is_empty(pb_mirror_map)) && pb->n_mirror_rules) {
>> > +        for (size_t i = 0; i < pb->n_mirror_rules ; i++) {
>> > +
>> > +            struct ovsrec_mirror *ovs_mirror = NULL;
>> > +            ovs_mirror = shash_find_data(pm_ctx->ovs_mirrors,
>> > +                                            pb->mirror_rules[i]->name);
>> > +            if (ovs_mirror) {
>> > +                if (ovs_mirror->n_select_dst_port == 0 &&
>> > +                        ovs_mirror->n_select_src_port == 0) {
>> > +                    ovsrec_bridge_update_ports_delvalue(pm_ctx->br_int,
>> > +                                                   ovs_mirror->output_port);
>> > +                    ovsrec_bridge_update_mirrors_delvalue(pm_ctx->br_int,
>> > +                                                                ovs_mirror);
>> > +                    ovsrec_port_delete(ovs_mirror->output_port);
>> > +                    ovsrec_mirror_delete(ovs_mirror);
>> > +                }
>> > +            }
>> > +        }
>> > +    }
>> > +
>> > +    struct shash_node *mirror_node;
>> > +    SHASH_FOR_EACH (mirror_node, pb_mirror_map) {
>> > +        struct ovsrec_mirror *ovs_mirror = mirror_node->data;
>> > +        if (!sset_find(&pb_mirrors, ovs_mirror->name)) {
>> > +            /* Find if the mirror has other sources i*/
>> > +            if ((ovs_mirror->n_select_dst_port > 1) ||
>> > +                (ovs_mirror->n_select_src_port > 1)) {
>> > +                /* More than 1 source then just
>> > +                 * update the mirror table
>> > +                 */
>> > +                bool done = false;
>> > +                for (size_t i = 0; ((i < ovs_mirror->n_select_dst_port)
>> > +                                                   && (done == false)); i++) {
>> > +                    const struct ovsrec_port *port_rec =
>> > +                                               ovs_mirror->select_dst_port[i];
>> > +                    for (size_t j = 0; j < port_rec->n_interfaces; j++) {
>> > +                        const struct ovsrec_interface *iface_rec;
>> > +
>> > +                        iface_rec = port_rec->interfaces[j];
>> > +                        const char *iface_id =
>> > +                                            smap_get(&iface_rec->external_ids,
>> > +                                                                  "iface-id");
>> > +                        if (!strcmp(iface_id,pb->logical_port)) {
>> > +                            ovsrec_mirror_update_select_dst_port_delvalue(
>> > +                                                        ovs_mirror, port_rec);
>> > +                            done = true;
>> > +                            break;
>> > +                        }
>> > +                    }
>> > +                }
>> > +                done = false;
>> > +                for (size_t i = 0; ((i < ovs_mirror->n_select_src_port)
>> > +                                                   && (done == false)); i++) {
>> > +                    const struct ovsrec_port *port_rec =
>> > +                                                ovs_mirror->select_src_port[i];
>> > +                    for (size_t j = 0; j < port_rec->n_interfaces; j++) {
>> > +                        const struct ovsrec_interface *iface_rec;
>> > +
>> > +                        iface_rec = port_rec->interfaces[j];
>> > +                        const char *iface_id =
>> > +                                            smap_get(&iface_rec->external_ids,
>> > +                                                                  "iface-id");
>> > +                        if (!strcmp(iface_id,pb->logical_port)) {
>> > +                            ovsrec_mirror_update_select_src_port_delvalue(
>> > +                                                        ovs_mirror, port_rec);
>> > +                            done = true;
>> > +                            break;
>> > +                        }
>> > +                    }
>> > +                }
>> > +            } else {
>> > +                /*
>> > +                 * If only 1 source delete the output port
>> > +                 * and then delete the mirror completely
>> > +                 */
>> > +                VLOG_INFO("Only 1 source for the mirror. Hence delete it");
>> > +                ovsrec_bridge_update_ports_delvalue(pm_ctx->br_int,
>> > +                                                    ovs_mirror->output_port);
>> > +                ovsrec_bridge_update_mirrors_delvalue(pm_ctx->br_int,
>> > +                                                            ovs_mirror);
>> > +                ovsrec_port_delete(ovs_mirror->output_port);
>> > +                ovsrec_mirror_delete(ovs_mirror);
>> > +            }
>> > +        }
>> > +    }
>> > +
>> > +    const char *used_node, *used_next;
>> > +    SSET_FOR_EACH_SAFE (used_node, used_next, &pb_mirrors) {
>> > +        sset_delete(&pb_mirrors, SSET_NODE_FROM_NAME(used_node));
>> > +    }
>> > +    sset_destroy(&pb_mirrors);
>> > +}
>> > +
>> > +static void
>> > +find_port_specific_mirrors (const struct sbrec_port_binding *pb,
>> > +                            struct port_mirror_ctx *pm_ctx,
>> > +                            struct shash *pb_mirror_map)
>> > +{
>> > +    const struct ovsrec_mirror *mirror = NULL;
>> > +
>> > +    OVSREC_MIRROR_TABLE_FOR_EACH (mirror, pm_ctx->mirror_table) {
>> > +        for (size_t i = 0; i < mirror->n_select_dst_port; i++) {
>> > +            const struct ovsrec_port *port_rec = mirror->select_dst_port[i];
>> > +            for (size_t j = 0; j < port_rec->n_interfaces; j++) {
>> > +                const struct ovsrec_interface *iface_rec;
>> > +                iface_rec = port_rec->interfaces[j];
>> > +                const char *logical_port =
>> > +                    smap_get(&iface_rec->external_ids, "iface-id");
>> > +                if (!strcmp(logical_port, pb->logical_port)) {
>> > +                    shash_add_once(pb_mirror_map, mirror->name, mirror);
>> > +                }
>> > +            }
>> > +        }
>> > +        for (size_t i = 0; i < mirror->n_select_src_port; i++) {
>> > +            const struct ovsrec_port *port_rec = mirror->select_src_port[i];
>> > +            for (size_t j = 0; j < port_rec->n_interfaces; j++) {
>> > +                const struct ovsrec_interface *iface_rec;
>> > +                iface_rec = port_rec->interfaces[j];
>> > +                const char *logical_port =
>> > +                    smap_get(&iface_rec->external_ids, "iface-id");
>> > +                if (!strcmp(logical_port, pb->logical_port)) {
>> > +                    shash_add_once(pb_mirror_map, mirror->name, mirror);
>> > +                }
>> > +            }
>> > +        }
>> > +    }
>> > +}
>> > +
>> > +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);
>> > +}
>> > +
>> > +
>> > +void
>> > +ovn_port_mirror_init(struct shash *ovs_mirrors)
>> > +{
>> > +    shash_init(ovs_mirrors);
>> > +}
>> > +
>> > +void
>> > +ovn_port_mirror_run(struct port_mirror_ctx *pm_ctx)
>> > +{
>> > +    const struct sbrec_port_binding *pb;
>> > +    SBREC_PORT_BINDING_TABLE_FOR_EACH (pb,
>> > +                                       pm_ctx->port_binding_table) {
>> > +        ovn_port_mirror_handle_lport(pb, false, pm_ctx);
>> > +    }
>> > +}
>> > +
>> > +bool
>> > +ovn_port_mirror_handle_lport(const struct sbrec_port_binding *pb, bool removed,
>> > +                     struct port_mirror_ctx *pm_ctx)
>> > +{
>> > +    bool ret = true;
>> > +    struct shash port_ovs_mirrors = SHASH_INITIALIZER(&port_ovs_mirrors);
>> > +
>> > +    /* Need to find if mirror needs update */
>> > +    find_port_specific_mirrors(pb, pm_ctx, &port_ovs_mirrors);
>> > +    if (removed == false) {
>> > +        if (((pb->n_mirror_rules == 0)
>> > +              && (shash_is_empty(&port_ovs_mirrors))) ||
>> > +              (pb->n_mirror_rules == shash_count(&port_ovs_mirrors))) {
>> > +            /* No mirror update */
>> > +        } else {
>> > +            /* Update Mirror */
>> > +            if (pb->n_mirror_rules > shash_count(&port_ovs_mirrors)) {
>> > +                /* create mirror,
>> > +                 * if mirror already exists only update selection
>> > +                 */
>> > +                ret = mirror_create(pb, pm_ctx);
>> > +            } else {
>> > +                /* delete mirror,
>> > +                 * if mirror has other sources only update selection
>> > +                 */
>> > +                mirror_delete(pb, pm_ctx, &port_ovs_mirrors, false);
>> > +            }
>> > +        }
>> > +    } else {
>> > +           mirror_delete(pb, pm_ctx, &port_ovs_mirrors, true);
>> > +    }
>> > +
>> > +    struct shash_node *ovs_mirror_node, *ovs_mirror_next;
>> > +    SHASH_FOR_EACH_SAFE (ovs_mirror_node, ovs_mirror_next,
>> > +                                              &port_ovs_mirrors) {
>> > +        shash_delete(&port_ovs_mirrors, ovs_mirror_node);
>> > +    }
>> > +    shash_destroy(&port_ovs_mirrors);
>> > +
>> > +    return ret;
>> > +}
>> > +
>> > +bool
>> > +ovn_port_mirror_handle_update(struct port_mirror_ctx *pm_ctx)
>> > +{
>> > +    const struct sbrec_mirror *mirror = NULL;
>> > +    struct ovsrec_mirror *ovs_mirror = NULL;
>> > +
>> > +    SBREC_MIRROR_TABLE_FOR_EACH_TRACKED (mirror, pm_ctx->sb_mirror_table) {
>> > +    /* For each tracked mirror entry check if OVS entry is there*/
>> > +        ovs_mirror = shash_find_data(pm_ctx->ovs_mirrors, mirror->name);
>> > +        if (ovs_mirror) {
>> > +            if (sbrec_mirror_is_deleted(mirror)) {
>> > +                /* Need to delete the mirror in OVS */
>> > +                VLOG_INFO("Delete mirror and remove port");
>> > +                ovsrec_bridge_update_ports_delvalue(pm_ctx->br_int,
>> > +                                                    ovs_mirror->output_port);
>> > +                ovsrec_bridge_update_mirrors_delvalue(pm_ctx->br_int,
>> > +                                                      ovs_mirror);
>> > +                ovsrec_port_delete(ovs_mirror->output_port);
>> > +                ovsrec_mirror_delete(ovs_mirror);
>> > +            } else {
>> > +                mirror_update(mirror, ovs_mirror);
>> > +            }
>> > +        }
>> > +    }
>> > +
>> > +    return true;
>> > +}
>> > +
>> > +void
>> > +ovn_port_mirror_destroy(struct shash *ovs_mirrors)
>> > +{
>> > +    struct shash_node *ovs_mirror_node, *ovs_mirror_next;
>> > +    SHASH_FOR_EACH_SAFE (ovs_mirror_node, ovs_mirror_next,
>> > +                                              ovs_mirrors) {
>> > +        shash_delete(ovs_mirrors, ovs_mirror_node);
>> > +    }
>> > +    shash_destroy(ovs_mirrors);
>> > +}
>> > +
>> > diff --git a/controller/mirror.h b/controller/mirror.h
>> > new file mode 100644
>> > index 000000000..85b964f55
>> > --- /dev/null
>> > +++ b/controller/mirror.h
>> > @@ -0,0 +1,53 @@
>> > +/* 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_port_table;
>> > +struct ovsrec_bridge;
>> > +struct ovsrec_bridge_table;
>> > +struct ovsrec_open_vswitch_table;
>> > +struct sbrec_chassis;
>> > +struct ovsrec_interface_table;
>> > +struct ovsrec_mirror_table;
>> > +struct sbrec_mirror_table;
>> > +struct sbrec_port_binding_table;
>> > +
>> > +struct port_mirror_ctx {
>> > +    struct shash *ovs_mirrors;
>> > +    struct ovsdb_idl_txn *ovs_idl_txn;
>> > +    const struct ovsrec_port_table *port_table;
>> > +    const struct ovsrec_bridge *br_int;
>> > +    const struct sbrec_chassis *chassis_rec;
>> > +    const struct ovsrec_bridge_table *bridge_table;
>> > +    const struct ovsrec_open_vswitch_table *ovs_table;
>> > +    const struct ovsrec_interface_table *iface_table;
>> > +    const struct ovsrec_mirror_table *mirror_table;
>> > +    const struct sbrec_mirror_table *sb_mirror_table;
>> > +    const struct sbrec_port_binding_table *port_binding_table;
>> > +    struct shash *local_bindings;
>> > +};
>> > +
>> > +void mirror_register_ovs_idl(struct ovsdb_idl *);
>> > +void ovn_port_mirror_init(struct shash *);
>> > +void ovn_port_mirror_destroy(struct shash *);
>> > +void ovn_port_mirror_run(struct port_mirror_ctx *pm_ctx);
>> > +bool ovn_port_mirror_handle_lport(const struct sbrec_port_binding *pb,
>> > +                                  bool removed,
>> > +                                  struct port_mirror_ctx *pm_ctx);
>> > +bool ovn_port_mirror_handle_update(struct port_mirror_ctx *pm_ctx);
>> > +#endif
>> > diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c
>> > index 0b0ccc48a..95307a2d9 100644
>> > --- a/controller/ovn-controller.c
>> > +++ b/controller/ovn-controller.c
>> > @@ -77,6 +77,7 @@
>> >  #include "stopwatch.h"
>> >  #include "lib/inc-proc-eng.h"
>> >  #include "hmapx.h"
>> > +#include "mirror.h"
>> >
>> >  VLOG_DEFINE_THIS_MODULE(main);
>> >
>> > @@ -963,6 +964,7 @@ ctrl_register_ovs_idl(struct ovsdb_idl *ovs_idl)
>> >      bfd_register_ovs_idl(ovs_idl);
>> >      physical_register_ovs_idl(ovs_idl);
>> >      vif_plug_register_ovs_idl(ovs_idl);
>> > +    mirror_register_ovs_idl(ovs_idl);
>> >  }
>> >
>> >  #define SB_NODES \
>> > @@ -983,6 +985,7 @@ ctrl_register_ovs_idl(struct ovsdb_idl *ovs_idl)
>> >      SB_NODE(load_balancer, "load_balancer") \
>> >      SB_NODE(fdb, "fdb") \
>> >      SB_NODE(meter, "meter") \
>> > +    SB_NODE(mirror, "mirror") \
>> >      SB_NODE(static_mac_binding, "static_mac_binding")
>> >
>> >  enum sb_engine_node {
>> > @@ -1000,7 +1003,8 @@ enum sb_engine_node {
>> >      OVS_NODE(bridge, "bridge") \
>> >      OVS_NODE(port, "port") \
>> >      OVS_NODE(interface, "interface") \
>> > -    OVS_NODE(qos, "qos")
>> > +    OVS_NODE(qos, "qos") \
>> > +    OVS_NODE(mirror, "mirror")
>> >
>> >  enum ovs_engine_node {
>> >  #define OVS_NODE(NAME, NAME_STR) OVS_##NAME,
>> > @@ -2326,6 +2330,203 @@ load_balancers_by_dp_cleanup(struct hmap *lbs)
>> >      free(lbs);
>> >  }
>> >
>> > +/* Mirror Engine */
>> > +struct ed_type_port_mirror {
>> > +    struct shash ovs_mirrors;
>> > +};
>> > +
>> > +static void *
>> > +en_port_mirror_init(struct engine_node *node OVS_UNUSED,
>> > +                    struct engine_arg *arg OVS_UNUSED)
>> > +{
>> > +    struct ed_type_port_mirror *port_mirror = xzalloc(sizeof *port_mirror);
>> > +    ovn_port_mirror_init(&port_mirror->ovs_mirrors);
>> > +    return port_mirror;
>> > +}
>> > +
>> > +static void
>> > +en_port_mirror_cleanup(void *data)
>> > +{
>> > +    struct ed_type_port_mirror *port_mirror = data;
>> > +    ovn_port_mirror_destroy(&port_mirror->ovs_mirrors);
>> > +}
>> > +
>> > +static void
>> > +init_port_mirror_ctx(struct engine_node *node,
>> > +                 struct ed_type_runtime_data *rt_data,
>> > +                 struct ed_type_port_mirror *port_mirror_data,
>> > +                 struct port_mirror_ctx *pm_ctx)
>> > +{
>> > +    struct ovsrec_open_vswitch_table *ovs_table =
>> > +        (struct ovsrec_open_vswitch_table *)EN_OVSDB_GET(
>> > +            engine_get_input("OVS_open_vswitch", node));
>> > +    struct ovsrec_bridge_table *bridge_table =
>> > +        (struct ovsrec_bridge_table *)EN_OVSDB_GET(
>> > +            engine_get_input("OVS_bridge", node));
>> > +    const char *chassis_id = get_ovs_chassis_id(ovs_table);
>> > +    const struct ovsrec_bridge *br_int = get_br_int(bridge_table, ovs_table);
>> > +
>> > +    ovs_assert(br_int && chassis_id);
>> > +    const struct sbrec_chassis *chassis = NULL;
>> > +    struct ovsdb_idl_index *sbrec_chassis_by_name =
>> > +        engine_ovsdb_node_get_index(
>> > +                engine_get_input("SB_chassis", node),
>> > +                "name");
>> > +
>> > +    if (chassis_id) {
>> > +        chassis = chassis_lookup_by_name(sbrec_chassis_by_name, chassis_id);
>> > +    }
>> > +    ovs_assert(chassis);
>> > +
>> > +    struct ovsrec_port_table *port_table =
>> > +        (struct ovsrec_port_table *)EN_OVSDB_GET(
>> > +            engine_get_input("OVS_port", node));
>> > +
>> > +    struct ed_type_ovs_interface_shadow *iface_shadow =
>> > +        engine_get_input_data("ovs_interface_shadow", node);
>> > +
>> > +    struct ovsrec_mirror_table *mirror_table =
>> > +        (struct ovsrec_mirror_table *)EN_OVSDB_GET(
>> > +            engine_get_input("OVS_mirror", node));
>> > +
>> > +    struct sbrec_port_binding_table *pb_table =
>> > +        (struct sbrec_port_binding_table *)EN_OVSDB_GET(
>> > +            engine_get_input("SB_port_binding", node));
>> > +
>> > +    struct sbrec_mirror_table *sb_mirror_table =
>> > +        (struct sbrec_mirror_table *)EN_OVSDB_GET(
>> > +            engine_get_input("SB_mirror", node));
>> > +
>> > +    struct shash_node *ovs_mirror_node, *ovs_mirror_next;
>> > +    SHASH_FOR_EACH_SAFE (ovs_mirror_node, ovs_mirror_next,
>> > +                           &port_mirror_data->ovs_mirrors) {
>> > +        shash_delete(&port_mirror_data->ovs_mirrors, ovs_mirror_node);
>> > +    }
>> > +
>> > +    const struct ovsrec_mirror *ovsmirror = NULL;
>> > +    OVSREC_MIRROR_TABLE_FOR_EACH (ovsmirror, mirror_table) {
>> > +       shash_add(&port_mirror_data->ovs_mirrors, ovsmirror->name, ovsmirror);
>> > +    }
>> > +
>> > +    pm_ctx->ovs_idl_txn = engine_get_context()->ovs_idl_txn;
>> > +    pm_ctx->port_table = port_table;
>> > +    pm_ctx->iface_table = iface_shadow->iface_table;
>> > +    pm_ctx->mirror_table = mirror_table;
>> > +    pm_ctx->port_binding_table = pb_table;
>> > +    pm_ctx->sb_mirror_table = sb_mirror_table;
>> > +    pm_ctx->br_int = br_int;
>> > +    pm_ctx->chassis_rec = chassis;
>> > +    pm_ctx->bridge_table = bridge_table;
>> > +    pm_ctx->ovs_table = ovs_table;
>> > +    pm_ctx->ovs_mirrors = &port_mirror_data->ovs_mirrors;
>> > +    pm_ctx->local_bindings = &rt_data->lbinding_data.bindings;
>> > +}
>> > +
>> > +static void
>> > +en_port_mirror_run(struct engine_node *node, void *data)
>> > +{
>> > +    struct port_mirror_ctx pm_ctx;
>> > +    struct ed_type_port_mirror *port_mirror_data = data;
>> > +    struct ed_type_runtime_data *rt_data =
>> > +        engine_get_input_data("runtime_data", node);
>> > +
>> > +    init_port_mirror_ctx(node, rt_data, port_mirror_data, &pm_ctx);
>> > +
>> > +    ovn_port_mirror_run(&pm_ctx);
>> > +    engine_set_node_state(node, EN_UPDATED);
>> > +}
>> > +
>> > +static bool
>> > +port_mirror_runtime_data_handler(struct engine_node *node, void *data)
>> > +{
>> > +    struct ed_type_runtime_data *rt_data =
>> > +        engine_get_input_data("runtime_data", node);
>> > +
>> > +    /* There is no tracked data. Fall back to full recompute of port_mirror */
>> > +    if (!rt_data->tracked) {
>> > +        return false;
>> > +    }
>> > +
>> > +    struct hmap *tracked_dp_bindings = &rt_data->tracked_dp_bindings;
>> > +    if (hmap_is_empty(tracked_dp_bindings)) {
>> > +        return true;
>> > +    }
>> > +
>> > +    struct port_mirror_ctx pm_ctx;
>> > +    struct ed_type_port_mirror *port_mirror_data = data;
>> > +    init_port_mirror_ctx(node, rt_data, port_mirror_data, &pm_ctx);
>> > +
>> > +    struct tracked_datapath *tdp;
>> > +    HMAP_FOR_EACH (tdp, node, tracked_dp_bindings) {
>> > +        if (tdp->tracked_type != TRACKED_RESOURCE_UPDATED) {
>> > +            /* Fall back to full recompute when a local datapath
>> > +             * is added or deleted. */
>> > +            return false;
>> > +        }
>> > +
>> > +        struct shash_node *shash_node;
>> > +        SHASH_FOR_EACH (shash_node, &tdp->lports) {
>> > +            struct tracked_lport *lport = shash_node->data;
>> > +            bool removed =
>> > +                lport->tracked_type == TRACKED_RESOURCE_REMOVED ? true: false;
>> > +            if (!ovn_port_mirror_handle_lport(lport->pb, removed, &pm_ctx)) {
>> > +                return false;
>> > +            }
>> > +        }
>> > +    }
>> > +
>> > +    engine_set_node_state(node, EN_UPDATED);
>> > +    return true;
>> > +}
>> > +
>> > +static bool
>> > +port_mirror_port_binding_handler(struct engine_node *node, void *data)
>> > +{
>> > +    struct port_mirror_ctx pm_ctx;
>> > +    struct ed_type_port_mirror *port_mirror_data = data;
>> > +    struct ed_type_runtime_data *rt_data =
>> > +        engine_get_input_data("runtime_data", node);
>> > +
>> > +    init_port_mirror_ctx(node, rt_data, port_mirror_data, &pm_ctx);
>> > +
>> > +    /* handle port binding updates (i.,e when the mirror column
>> > +     * of port_binding is updated)
>> > +     */
>> > +    const struct sbrec_port_binding *pb;
>> > +    SBREC_PORT_BINDING_TABLE_FOR_EACH_TRACKED (pb,
>> > +                                               pm_ctx.port_binding_table) {
>> > +        bool removed = sbrec_port_binding_is_deleted(pb);
>> > +        if (!ovn_port_mirror_handle_lport(pb, removed, &pm_ctx)) {
>> > +            return false;
>> > +        }
>> > +    }
>> > +
>> > +    engine_set_node_state(node, EN_UPDATED);
>> > +    return true;
>> > +
>> > +}
>> > +
>> > +static bool
>> > +port_mirror_sb_mirror_handler(struct engine_node *node, void *data)
>> > +{
>> > +    struct port_mirror_ctx pm_ctx;
>> > +    struct ed_type_port_mirror *port_mirror_data = data;
>> > +    struct ed_type_runtime_data *rt_data =
>> > +        engine_get_input_data("runtime_data", node);
>> > +
>> > +    init_port_mirror_ctx(node, rt_data, port_mirror_data, &pm_ctx);
>> > +
>> > +    /* handle sb mirror updates
>> > +     */
>> > +    if (!ovn_port_mirror_handle_update(&pm_ctx)) {
>> > +        return false;
>> > +    }
>> > +
>> > +    engine_set_node_state(node, EN_UPDATED);
>> > +    return true;
>> > +
>> > +}
>> > +
>> >  /* Engine node which is used to handle the Non VIF data like
>> >   *   - OVS patch ports
>> >   *   - Tunnel ports and the related chassis information.
>> > @@ -3609,6 +3810,7 @@ main(int argc, char *argv[])
>> >      ENGINE_NODE_WITH_CLEAR_TRACK_DATA(addr_sets, "addr_sets");
>> >      ENGINE_NODE_WITH_CLEAR_TRACK_DATA(port_groups, "port_groups");
>> >      ENGINE_NODE(northd_options, "northd_options");
>> > +    ENGINE_NODE(port_mirror, "port_mirror");
>> >
>> >  #define SB_NODE(NAME, NAME_STR) ENGINE_NODE_SB(NAME, NAME_STR);
>> >      SB_NODES
>> > @@ -3763,6 +3965,22 @@ main(int argc, char *argv[])
>> >      engine_add_input(&en_flow_output, &en_pflow_output,
>> >                       flow_output_pflow_output_handler);
>> >
>> > +    engine_add_input(&en_port_mirror, &en_ovs_open_vswitch, NULL);
>> > +    engine_add_input(&en_port_mirror, &en_ovs_bridge, NULL);
>> > +    engine_add_input(&en_port_mirror, &en_ovs_mirror, NULL);
>> > +    engine_add_input(&en_port_mirror, &en_sb_chassis, NULL);
>> > +    engine_add_input(&en_port_mirror, &en_ovs_port, engine_noop_handler);
>> > +    engine_add_input(&en_port_mirror, &en_ovs_interface_shadow,
>> > +                     engine_noop_handler);
>> > +    engine_add_input(&en_flow_output, &en_port_mirror,
>> > +                     engine_noop_handler);
>> > +    engine_add_input(&en_port_mirror, &en_runtime_data,
>> > +                     port_mirror_runtime_data_handler);
>> > +    engine_add_input(&en_port_mirror, &en_sb_mirror,
>> > +                     port_mirror_sb_mirror_handler);
>> > +    engine_add_input(&en_port_mirror, &en_sb_port_binding,
>> > +                     port_mirror_port_binding_handler);
>> > +
>> >      struct engine_arg engine_arg = {
>> >          .sb_idl = ovnsb_idl_loop.idl,
>> >          .ovs_idl = ovs_idl_loop.idl,
>> > @@ -4082,6 +4300,8 @@ main(int argc, char *argv[])
>> >                                      ovnsb_idl_loop.idl),
>> >                                  sbrec_sb_global_table_get(ovnsb_idl_loop.idl));
>> >                          stopwatch_stop(BFD_RUN_STOPWATCH_NAME, time_msec());
>> > +                    } else {
>> > +                        engine_run(false);
>>
>> Why do you need it here?
>
> This was caught while fixing Issue 3 which Numan raised on v3 patch.
> This change is needed. Discussed with Numan and as suggested by him I have added
> Since I am new to this Engine code I requested Numan for suggestions.
> Quoting him below.
> "basically you should call - engine_run(false) if ovs_idl_txn is NULL or ovnsb_idl_txn is
> NULL. right now - engine_run is called with false only if ovnsb_idl_txn is NULL"
>
> To give a bit more details. When the OVS port is deleted the OVN controller releases
> the lport. Later when we put back the OVS port, OVN controller claims it back. This will
> trigger the Mirror handler in OVN. But while handling this I was seeing that ovs_idl_txn
> is NULL in which case we cant update OVSDB. And the handler returns 'false'. This will
> trigger a recompute but the ovs_idl_txn is still NULL, and in such a scenario
> it never gets updated. If engine_run is called with 'false' recompute is not allowed and
> so next time when ovs_idl_txn is not NULL, engine recompute is triggered and mirror
> run function will be called.
>
> Numan, Hope I have explained correctly. Please add/correct if needed.
>
>>
>> >                      }
>> >
>> >                      runtime_data = engine_get_data(&en_runtime_data);
>> > diff --git a/northd/en-northd.c b/northd/en-northd.c
>> > index 4907a1ff2..74cac1b8e 100644
>> > --- a/northd/en-northd.c
>> > +++ b/northd/en-northd.c
>> > @@ -78,6 +78,8 @@ void en_northd_run(struct engine_node *node, void *data)
>> >          EN_OVSDB_GET(engine_get_input("NB_acl", node));
>> >      input_data.nbrec_static_mac_binding_table =
>> >          EN_OVSDB_GET(engine_get_input("NB_static_mac_binding", node));
>> > +    input_data.nbrec_mirror_table =
>> > +        EN_OVSDB_GET(engine_get_input("NB_mirror", node));
>> >
>> >      input_data.sbrec_sb_global_table =
>> >          EN_OVSDB_GET(engine_get_input("SB_sb_global", node));
>> > @@ -111,6 +113,8 @@ void en_northd_run(struct engine_node *node, void *data)
>> >          EN_OVSDB_GET(engine_get_input("SB_chassis_private", node));
>> >      input_data.sbrec_static_mac_binding_table =
>> >          EN_OVSDB_GET(engine_get_input("SB_static_mac_binding", node));
>> > +    input_data.sbrec_mirror_table =
>> > +        EN_OVSDB_GET(engine_get_input("SB_mirror", node));
>> >
>> >      northd_run(&input_data, data,
>> >                 eng_ctx->ovnnb_idl_txn,
>> > diff --git a/northd/inc-proc-northd.c b/northd/inc-proc-northd.c
>> > index 54e0ad3b0..ac27a730e 100644
>> > --- a/northd/inc-proc-northd.c
>> > +++ b/northd/inc-proc-northd.c
>> > @@ -50,6 +50,7 @@ VLOG_DEFINE_THIS_MODULE(inc_proc_northd);
>> >      NB_NODE(acl, "acl") \
>> >      NB_NODE(logical_router, "logical_router") \
>> >      NB_NODE(qos, "qos") \
>> > +    NB_NODE(mirror, "mirror") \
>> >      NB_NODE(meter, "meter") \
>> >      NB_NODE(meter_band, "meter_band") \
>> >      NB_NODE(logical_router_port, "logical_router_port") \
>> > @@ -92,6 +93,7 @@ VLOG_DEFINE_THIS_MODULE(inc_proc_northd);
>> >      SB_NODE(logical_flow, "logical_flow") \
>> >      SB_NODE(logical_dp_group, "logical_DP_group") \
>> >      SB_NODE(multicast_group, "multicast_group") \
>> > +    SB_NODE(mirror, "mirror") \
>> >      SB_NODE(meter, "meter") \
>> >      SB_NODE(meter_band, "meter_band") \
>> >      SB_NODE(datapath_binding, "datapath_binding") \
>> > @@ -172,6 +174,7 @@ void inc_proc_northd_init(struct ovsdb_idl_loop *nb,
>> >      engine_add_input(&en_northd, &en_nb_acl, NULL);
>> >      engine_add_input(&en_northd, &en_nb_logical_router, NULL);
>> >      engine_add_input(&en_northd, &en_nb_qos, NULL);
>> > +    engine_add_input(&en_northd, &en_nb_mirror, NULL);
>> >      engine_add_input(&en_northd, &en_nb_meter, NULL);
>> >      engine_add_input(&en_northd, &en_nb_meter_band, NULL);
>> >      engine_add_input(&en_northd, &en_nb_logical_router_port, NULL);
>> > @@ -194,6 +197,7 @@ void inc_proc_northd_init(struct ovsdb_idl_loop *nb,
>> >      engine_add_input(&en_northd, &en_sb_address_set, NULL);
>> >      engine_add_input(&en_northd, &en_sb_port_group, NULL);
>> >      engine_add_input(&en_northd, &en_sb_logical_dp_group, NULL);
>> > +    engine_add_input(&en_northd, &en_sb_mirror, NULL);
>> >      engine_add_input(&en_northd, &en_sb_meter, NULL);
>> >      engine_add_input(&en_northd, &en_sb_meter_band, NULL);
>> >      engine_add_input(&en_northd, &en_sb_datapath_binding, NULL);
>> > diff --git a/northd/northd.c b/northd/northd.c
>> > index 7e2681865..74314b5dc 100644
>> > --- a/northd/northd.c
>> > +++ b/northd/northd.c
>> > @@ -3214,6 +3214,51 @@ ovn_port_update_sbrec_chassis(
>> >      free(requested_chassis_sb);
>> >  }
>> >
>> > +static void
>> > +sbrec_port_binding_update_mirror_rules(struct northd_input *input_data,
>> > +                                       const struct ovn_port *op)
>> > +{
>> > +    size_t i = 0;
>> > +    if (op->sb->n_mirror_rules > op->nbsp->n_mirror_rules) {
>> > +        /* Needs deletion in SB */
>> > +        struct shash nb_mirror_rules = SHASH_INITIALIZER(&nb_mirror_rules);
>> > +        for (i = 0; i < op->nbsp->n_mirror_rules; i++) {
>> > +            shash_add(&nb_mirror_rules,
>> > +                                 op->nbsp->mirror_rules[i]->name,
>> > +                                 op->nbsp->mirror_rules[i]);
>> > +        }
>> > +
>> > +        for (i = 0; i < op->sb->n_mirror_rules; i++) {
>> > +            if (!shash_find(&nb_mirror_rules,
>> > +                           op->sb->mirror_rules[i]->name)) {
>> > +                    sbrec_port_binding_update_mirror_rules_delvalue(op->sb,
>> > +                                                 op->sb->mirror_rules[i]);
>> > +            }
>> > +        }
>> > +
>> > +        struct shash_node *node, *next;
>> > +        SHASH_FOR_EACH_SAFE (node, next, &nb_mirror_rules) {
>> > +            shash_delete(&nb_mirror_rules, node);
>> > +        }
>> > +        shash_destroy(&nb_mirror_rules);
>> > +
>> > +    } else {
>> > +        /* Needs addition in SB */
>>
>> Not necessarily. What if it's ==? (You still need to handle this case,
>> for when the number of mirror references is the same but some of them
>> refer to different mirrors.)
>
> I thought about it. Since the the Mirror rules is added/deleted into the
> port binding table mirror column only when an lsp-attach/lsp-detach
> happens to a logical port through the NBCTL command which updates
> NB first which inturn triggers SB update. I could not think of such a
> use-case when the case you mentioned happens. I dont think that the
> mirror rule not present in NB for a logical port but is wrongly present
> in SB Port Binding and at the same time a valid mirror rule present
> NB and not updated in SB can happen.
>
>  I copied below 2 commands and executed them together..
>
> [root@rhos-nfv-09 ~]#
> [root@rhos-nfv-09 ~]# ovn-nbctl lsp-detach-mirror sw0-port1 mirror1
> [root@rhos-nfv-09 ~]# ovn-nbctl lsp-attach-mirror sw0-port1 mirror0
> [root@rhos-nfv-09 ~]#
>
> Still the updates happen one after the other.
> I tried on my setup. Didn't hit any issues.
> So I feel we can live with this for now.
>

CLI is not the only path to update databases. CMS can easily decide to
swap mirrors in the list, replacing one with another, which will keep
the number of mirrors in the list but not the contents. I would also
not rely on controller being quick enough to process mirror attachment
updates as above one by one.

>>
>> > +        for (i = 0; i < op->nbsp->n_mirror_rules; i++) {
>> > +            const struct sbrec_mirror *sb_mirror;
>> > +            SBREC_MIRROR_TABLE_FOR_EACH (sb_mirror,
>> > +                                         input_data->sbrec_mirror_table) {
>> > +                if (!strcmp(sb_mirror->name,
>> > +                            op->nbsp->mirror_rules[i]->name)) {
>> > +                    /* Add the value to SB */
>> > +                    sbrec_port_binding_update_mirror_rules_addvalue(op->sb,
>> > +                                                                    sb_mirror);
>> > +                }
>> > +            }
>> > +        }
>> > +    }
>> > +}
>> > +
>> >  static void
>> >  ovn_port_update_sbrec(struct northd_input *input_data,
>> >                        struct ovsdb_idl_txn *ovnsb_txn,
>> > @@ -3573,6 +3618,17 @@ ovn_port_update_sbrec(struct northd_input *input_data,
>> >          }
>> >          sbrec_port_binding_set_external_ids(op->sb, &ids);
>> >          smap_destroy(&ids);
>> > +
>> > +        if (!op->nbsp->n_mirror_rules) {
>> > +            /* Nothing is set. Clear mirror_rules from pb. */
>> > +            sbrec_port_binding_set_mirror_rules(op->sb, NULL, 0);
>> > +        } else {
>> > +            /* Check if SB DB update needed */
>> > +            if (op->nbsp->n_mirror_rules != op->sb->n_mirror_rules) {
>> > +                sbrec_port_binding_update_mirror_rules(input_data, op);
>> > +            }
>> > +        }
>> > +
>> >      }
>> >      if (op->tunnel_key != op->sb->tunnel_key) {
>> >          sbrec_port_binding_set_tunnel_key(op->sb, op->tunnel_key);
>> > @@ -14877,6 +14933,85 @@ sync_meters(struct northd_input *input_data,
>> >      shash_destroy(&sb_meters);
>> >  }
>> >
>> > +static bool
>> > +mirror_needs_update(const struct nbrec_mirror *nb_mirror,
>> > +                  const struct sbrec_mirror *sb_mirror)
>> > +{
>> > +
>> > +    if (nb_mirror->index != sb_mirror->index) {
>> > +        return true;
>> > +    } else if (strcmp(nb_mirror->sink, sb_mirror->sink)) {
>> > +        return true;
>> > +    } else if (strcmp(nb_mirror->type, sb_mirror->type)) {
>> > +        return true;
>> > +    } else if (strcmp(nb_mirror->filter, sb_mirror->filter)) {
>> > +        return true;
>> > +    }
>> > +
>> > +    return false;
>> > +}
>> > +
>> > +static void
>> > +sync_mirrors_iterate_nb_mirror(struct ovsdb_idl_txn *ovnsb_txn,
>> > +                             const char *mirror_name,
>> > +                             const struct nbrec_mirror *nb_mirror,
>> > +                             struct shash *sb_mirrors,
>> > +                             struct sset *used_sb_mirrors)
>> > +{
>> > +    const struct sbrec_mirror *sb_mirror;
>> > +    bool new_sb_mirror = false;
>> > +
>> > +    sb_mirror = shash_find_data(sb_mirrors, mirror_name);
>> > +    if (!sb_mirror) {
>> > +        sb_mirror = sbrec_mirror_insert(ovnsb_txn);
>> > +        sbrec_mirror_set_name(sb_mirror, mirror_name);
>> > +        shash_add(sb_mirrors, sb_mirror->name, sb_mirror);
>> > +        new_sb_mirror = true;
>> > +    }
>> > +    sset_add(used_sb_mirrors, mirror_name);
>> > +
>> > +    if ((new_sb_mirror) || mirror_needs_update(nb_mirror, sb_mirror)) {
>> > +        sbrec_mirror_set_filter(sb_mirror,nb_mirror->filter);
>> > +        sbrec_mirror_set_index(sb_mirror, nb_mirror->index);
>> > +        sbrec_mirror_set_sink(sb_mirror, nb_mirror->sink);
>> > +        sbrec_mirror_set_type(sb_mirror, nb_mirror->type);
>> > +    }
>> > +}
>> > +
>> > +static void
>> > +sync_mirrors(struct northd_input *input_data,
>> > +            struct ovsdb_idl_txn *ovnsb_txn)
>> > +{
>> > +    struct shash sb_mirrors = SHASH_INITIALIZER(&sb_mirrors);
>> > +    struct sset used_sb_mirrors = SSET_INITIALIZER(&used_sb_mirrors);
>> > +
>> > +    const struct sbrec_mirror *sb_mirror;
>> > +    SBREC_MIRROR_TABLE_FOR_EACH (sb_mirror, input_data->sbrec_mirror_table) {
>> > +        shash_add(&sb_mirrors, sb_mirror->name, sb_mirror);
>> > +    }
>> > +
>> > +    const struct nbrec_mirror *nb_mirror;
>> > +    NBREC_MIRROR_TABLE_FOR_EACH (nb_mirror, input_data->nbrec_mirror_table) {
>> > +        sync_mirrors_iterate_nb_mirror(ovnsb_txn, nb_mirror->name, nb_mirror,
>> > +                                     &sb_mirrors, &used_sb_mirrors);
>> > +    }
>> > +
>> > +    const char *used_mirror;
>> > +    const char *used_mirror_next;
>> > +    SSET_FOR_EACH_SAFE (used_mirror, used_mirror_next, &used_sb_mirrors) {
>> > +        shash_find_and_delete(&sb_mirrors, used_mirror);
>> > +        sset_delete(&used_sb_mirrors, SSET_NODE_FROM_NAME(used_mirror));
>> > +    }
>> > +    sset_destroy(&used_sb_mirrors);
>> > +
>> > +    struct shash_node *node, *next;
>> > +    SHASH_FOR_EACH_SAFE (node, next, &sb_mirrors) {
>> > +        sbrec_mirror_delete(node->data);
>> > +        shash_delete(&sb_mirrors, node);
>> > +    }
>> > +    shash_destroy(&sb_mirrors);
>> > +}
>> > +
>> >  /*
>> >   * struct 'dns_info' is used to sync the DNS records between OVN Northbound db
>> >   * and Southbound db.
>> > @@ -15497,6 +15632,7 @@ ovnnb_db_run(struct northd_input *input_data,
>> >      sync_address_sets(input_data, ovnsb_txn, &data->datapaths);
>> >      sync_port_groups(input_data, ovnsb_txn, &data->port_groups);
>> >      sync_meters(input_data, ovnsb_txn, &data->meter_groups);
>> > +    sync_mirrors(input_data, ovnsb_txn);
>> >      sync_dns_entries(input_data, ovnsb_txn, &data->datapaths);
>> >      cleanup_stale_fdb_entries(input_data, &data->datapaths);
>> >      stopwatch_stop(CLEAR_LFLOWS_CTX_STOPWATCH_NAME, time_msec());
>> > diff --git a/northd/northd.h b/northd/northd.h
>> > index d9856af97..8a4f6a428 100644
>> > --- a/northd/northd.h
>> > +++ b/northd/northd.h
>> > @@ -34,6 +34,7 @@ struct northd_input {
>> >      const struct nbrec_acl_table *nbrec_acl_table;
>> >      const struct nbrec_static_mac_binding_table
>> >          *nbrec_static_mac_binding_table;
>> > +    const struct nbrec_mirror_table *nbrec_mirror_table;
>> >
>> >      /* Southbound table references */
>> >      const struct sbrec_sb_global_table *sbrec_sb_global_table;
>> > @@ -53,6 +54,7 @@ struct northd_input {
>> >      const struct sbrec_chassis_private_table *sbrec_chassis_private_table;
>> >      const struct sbrec_static_mac_binding_table
>> >          *sbrec_static_mac_binding_table;
>> > +    const struct sbrec_mirror_table *sbrec_mirror_table;
>> >
>> >      /* Indexes */
>> >      struct ovsdb_idl_index *sbrec_chassis_by_name;
>> > diff --git a/ovn-nb.ovsschema b/ovn-nb.ovsschema
>> > index 174364c8b..01de55222 100644
>> > --- a/ovn-nb.ovsschema
>> > +++ b/ovn-nb.ovsschema
>> > @@ -1,7 +1,7 @@
>> >  {
>> >      "name": "OVN_Northbound",
>> > -    "version": "6.3.0",
>> > -    "cksum": "4042813038 31869",
>> > +    "version": "6.4.0",
>> > +    "cksum": "589874483 33352",
>> >      "tables": {
>> >          "NB_Global": {
>> >              "columns": {
>> > @@ -132,6 +132,11 @@
>> >                                              "refType": "weak"},
>> >                                   "min": 0,
>> >                                   "max": 1}},
>> > +                "mirror_rules": {"type": {"key": {"type": "uuid",
>> > +                                          "refTable": "Mirror",
>> > +                                          "refType": "weak"},
>> > +                                  "min": 0,
>> > +                                  "max": "unlimited"}},
>> >                  "ha_chassis_group": {
>> >                      "type": {"key": {"type": "uuid",
>> >                                       "refTable": "HA_Chassis_Group",
>> > @@ -301,6 +306,28 @@
>> >                      "type": {"key": "string", "value": "string",
>> >                               "min": 0, "max": "unlimited"}}},
>> >              "isRoot": false},
>> > +        "Mirror": {
>> > +            "columns": {
>> > +                "name": {"type": "string"},
>> > +                "filter": {"type": {"key": {"type": "string",
>> > +                                            "enum": ["set", ["from-lport",
>> > +                                                             "to-lport",
>> > +                                                             "both"]]}}},
>> > +                "sink":{"type": "string"},
>> > +                "type": {"type": {"key": {"type": "string",
>> > +                                            "enum": ["set", ["gre",
>> > +                                                             "erspan"]]}}},
>> > +                "index": {"type": "integer"},
>>
>> Is "index" ERSPAN specific? Should we pick a name that is less
>> protocol dependent? Maybe "key" would work better since we already use
>> it in other places for tunnel keys and whatnot?
>
> Intention was to keep it generic! .. i.e the Tunnel index.
> Index - For GRE as key and erspan as erspan_idx.
> Also in the schema file "key" is used under many types "type". So as to not
> confuse it with that I kept it as 'Index'.
> If you insist, it is a little more effort and I can change it.
> (as it will trigger many changes in .c files as well)
> Or if you are fine I can keep it as Index and put the more clear explanation
> in xml and help.

An explanation would definitely help. I assumed "index" is some kind
of ERSPAN specific equivalent to tunnel ID.
>
>>
>> > +                "src": {"type": {"key": {"type": "uuid",
>> > +                                           "refTable": "Logical_Switch_Port",
>> > +                                           "refType": "weak"},
>> > +                                   "min": 0,
>> > +                                   "max": "unlimited"}},
>> > +                "external_ids": {
>> > +                    "type": {"key": "string", "value": "string",
>> > +                             "min": 0, "max": "unlimited"}}},
>>
>> NB contains external_ids but not SB. Is it used for anything in NB or
>> is it just a placeholder to avoid schema changes later? Should we have
>> a similar placeholder for SB side too?
>
> Yeah currently it's just a placeholder. Had just kept like that because while
> implementing the schema we had taken the Meter table as reference.
> Even in the Meter table in NB external_ids is there and SB it's not there.
>

I am not knowledgeable enough about how the project approaches
external_ids for other objects, but I would think if it makes sense to
add it proactively to avoid later schema changes in NB, it would make
as much sense in SB. But I will let someone more knowledgeable to
comment on this.

>>
>>
>> > +            "indexes": [["name"]],
>> > +            "isRoot": true},
>> >          "Meter": {
>> >              "columns": {
>> >                  "name": {"type": "string"},
>> > diff --git a/ovn-nb.xml b/ovn-nb.xml
>> > index 7fe88af27..d410301e7 100644
>> > --- a/ovn-nb.xml
>> > +++ b/ovn-nb.xml
>> > @@ -1554,6 +1554,11 @@
>> >        </column>
>> >      </group>
>> >
>> > +    <column name="mirror_rules">
>> > +        Mirror rules that apply to logical switch port which is the source.
>> > +        Please see the <ref table="Mirror"/> table.
>> > +    </column>
>> > +
>> >      <column name="ha_chassis_group">
>> >        References a row in the OVN Northbound database's
>> >        <ref table="HA_Chassis_Group" db="OVN_Northbound"/> table.
>> > @@ -2491,6 +2496,58 @@
>> >      </column>
>> >    </table>
>> >
>> > +  <table name="Mirror" title="Mirror Entry">
>> > +    <p>
>> > +      Each row in this table represents one Mirror that can be used for
>> > +      port mirroring. These Mirrors are referenced by the
>> > +      <ref column="mirror_rules" table="Logical_Switch_Port"/> column in
>> > +      the <ref table="Logical_Switch_Port"/> table.
>> > +    </p>
>> > +
>> > +    <column name="name">
>> > +      <p>
>> > +        Represents the name of the mirror.
>> > +      </p>
>> > +    </column>
>> > +
>> > +    <column name="filter">
>> > +      <p>
>> > +        The value of this field represents selection criteria of the mirror.
>>
>> Please list supported values and explain what they mean.
>>
>> > +      </p>
>> > +    </column>
>> > +
>> > +    <column name="sink">
>> > +      <p>
>> > +        The value of this field represents the destination/sink of the mirror.
>>
>> Perhaps explain that it's IP address? And since it's an IP address,
>> should we maybe rename the field into sink_ip to be more explicit?
>>
>> > +      </p>
>> > +    </column>
>> > +
>> > +    <column name="type">
>> > +      <p>
>> > +        The value of this field represents the type of the tunnel used for
>> > +        sending the mirrored packets
>>
>> Perhaps you could list supported values here and explain what they are?
>>
>> > +      </p>
>> > +    </column>
>> > +
>> > +    <column name="index">
>> > +      <p>
>> > +        The value of this field represents the key/idx depending on the
>> > +        tunnel type configured
>>
>> A reader may be confused as to what "idx" is. Is it a ERSPAN term?
>>
>> > +      </p>
>> > +    </column>
>> > +
>> > +    <column name="src">
>> > +      <p>
>> > +          The value of this field represents the source port for the mirror.
>>
>> AFAIU it represent (a list of) ports, no?
>>
>> > +          Please see the <ref table="Logical_Switch_Port"/> table.
>> > +      </p>
>> > +    </column>
>> > +
>> > +    <column name="external_ids">
>> > +      See <em>External IDs</em> at the beginning of this document.
>> > +    </column>
>> > +  </table>
>> > +
>> >    <table name="Meter" title="Meter entry">
>> >      <p>
>> >        Each row in this table represents a meter that can be used for QoS or
>> > diff --git a/ovn-sb.ovsschema b/ovn-sb.ovsschema
>> > index 576ebbdeb..45431a18b 100644
>> > --- a/ovn-sb.ovsschema
>> > +++ b/ovn-sb.ovsschema
>> > @@ -1,7 +1,7 @@
>> >  {
>> >      "name": "OVN_Southbound",
>> > -    "version": "20.25.0",
>> > -    "cksum": "53184112 28845",
>> > +    "version": "20.26.0",
>> > +    "cksum": "3740716587 29844",
>> >      "tables": {
>> >          "SB_Global": {
>> >              "columns": {
>> > @@ -142,6 +142,20 @@
>> >              "indexes": [["datapath", "tunnel_key"],
>> >                          ["datapath", "name"]],
>> >              "isRoot": true},
>> > +        "Mirror": {
>> > +            "columns": {
>> > +                "name": {"type": "string"},
>> > +                "filter": {"type": {"key": {"type": "string",
>> > +                                            "enum": ["set",
>> > +                                                     ["from-lport",
>> > +                                                      "to-lport","both"]]}}},
>> > +                "sink":{"type": "string"},
>> > +                "type": {"type": {"key": {"type": "string",
>> > +                                            "enum": ["set",
>> > +                                                     ["gre", "erspan"]]}}},
>> > +                "index": {"type": "integer"}},
>> > +            "indexes": [["name"]],
>> > +            "isRoot": true},
>> >          "Meter": {
>> >              "columns": {
>> >                  "name": {"type": "string"},
>> > @@ -230,6 +244,11 @@
>> >                                                        "refTable": "Encap",
>> >                                                        "refType": "weak"},
>> >                                      "min": 0, "max": "unlimited"}},
>> > +                "mirror_rules": {"type": {"key": {"type": "uuid",
>> > +                                          "refTable": "Mirror",
>> > +                                          "refType": "weak"},
>> > +                                  "min": 0,
>> > +                                  "max": "unlimited"}},
>> >                  "mac": {"type": {"key": "string",
>> >                                   "min": 0,
>> >                                   "max": "unlimited"}},
>> > diff --git a/ovn-sb.xml b/ovn-sb.xml
>> > index 37a709f83..24af40b7f 100644
>> > --- a/ovn-sb.xml
>> > +++ b/ovn-sb.xml
>> > @@ -2742,6 +2742,47 @@ tcp.flags = RST;
>> >      </column>
>> >    </table>
>> >
>> > +  <table name="Mirror" title="Mirror Entry">
>> > +    <p>
>> > +      Each row in this table represents one Mirror that can be used for
>> > +      port mirroring. These Mirrors are referenced by the
>> > +      <ref column="mirror_rules" table="Port_Binding"/> column in
>> > +      the <ref table="Port_Binding"/> table.
>> > +    </p>
>> > +
>> > +    <column name="name">
>> > +      <p>
>> > +        Represents the name of the mirror.
>> > +      </p>
>> > +    </column>
>> > +
>> > +    <column name="filter">
>> > +      <p>
>> > +        The value of this field represents selection criteria of the mirror.
>> > +      </p>
>> > +    </column>
>> > +
>> > +    <column name="sink">
>> > +      <p>
>> > +        The value of this field represents the destination/sink of the mirror.
>> > +      </p>
>> > +    </column>
>> > +
>> > +    <column name="type">
>> > +      <p>
>> > +        The value of this field represents the type of the tunnel used for
>> > +        sending the mirrored packets
>> > +      </p>
>> > +    </column>
>> > +
>> > +    <column name="index">
>> > +      <p>
>> > +        The value of this field represents the key/idx depending on the
>> > +        tunnel type configured
>> > +      </p>
>> > +    </column>
>> > +  </table>
>> > +
>> >    <table name="Meter" title="Meter entry">
>> >      <p>
>> >        Each row in this table represents a meter that can be used for QoS or
>> > @@ -3244,6 +3285,11 @@ tcp.flags = RST;
>> >        </column>
>> >      </group>
>> >
>> > +    <column name="mirror_rules">
>> > +        Mirror rules that apply to the port binding.
>> > +        Please see the <ref table="Mirror"/> table.
>> > +    </column>
>> > +
>> >      <group title="Patch Options">
>> >        <p>
>> >          These options apply to logical ports with <ref column="type"/> of
>> > diff --git a/tests/ovn-nbctl.at b/tests/ovn-nbctl.at
>> > index 726efa6f4..8f6ee6097 100644
>> > --- a/tests/ovn-nbctl.at
>> > +++ b/tests/ovn-nbctl.at
>> > @@ -435,6 +435,101 @@ AT_CHECK([ovn-nbctl meter-list], [0], [dnl
>> >
>> >  dnl ---------------------------------------------------------------------
>> >
>> > +OVN_NBCTL_TEST([ovn_nbctl_mirrors], [mirrors], [
>> > +AT_CHECK([ovn-nbctl mirror-add mirror1 gre 0 from-lport 10.10.10.1])
>> > +AT_CHECK([ovn-nbctl mirror-add mirror2 erspan 1 both 10.10.10.2])
>> > +AT_CHECK([ovn-nbctl mirror-add mirror3 gre 2 to-lport 10.10.10.3])
>> > +AT_CHECK([ovn-nbctl ls-add sw0])
>> > +AT_CHECK([ovn-nbctl lsp-add sw0 sw0-port1])
>> > +AT_CHECK([ovn-nbctl lsp-add sw0 sw0-port2])
>> > +AT_CHECK([ovn-nbctl lsp-add sw0 sw0-port3])
>> > +
>> > +dnl Add duplicate mirror name
>> > +AT_CHECK([ovn-nbctl mirror-add mirror1 gre 0 from-lport 10.10.10.5], [1], [], [stderr])
>> > +AT_CHECK([grep 'already exists' stderr], [0], [ignore])
>> > +
>> > +dnl Add mirror with invalid sink port
>> > +AT_CHECK([ovn-nbctl mirror-add mirror1 gre 0 from-lport sw0-port4], [1], [], [stderr])
>> > +AT_CHECK([grep 'already exists' stderr], [0], [ignore])
>>
>> The error message here seems to be not about an invalid port.
>>
>> > +
>> > +dnl Attach source port to mirror
>> > +AT_CHECK([ovn-nbctl lsp-attach-mirror sw0-port1 mirror3])
>> > +
>> > +dnl Attach one more source port to mirror
>> > +AT_CHECK([ovn-nbctl lsp-attach-mirror sw0-port3 mirror3])
>> > +
>> > +dnl Attach invalid source port to mirror
>> > +AT_CHECK([ovn-nbctl lsp-attach-mirror sw0-port4 mirror3], [1], [], [stderr])
>> > +AT_CHECK([grep 'port name not found' stderr], [0], [ignore])
>> > +
>> > +dnl Detach one source port from mirror
>> > +AT_CHECK([ovn-nbctl lsp-detach-mirror sw0-port3 mirror3])
>> > +
>> > +AT_CHECK([ovn-nbctl mirror-list], [0], [dnl
>> > +mirror1:
>> > +  Type     :  gre
>> > +  Sink     :  10.10.10.1
>> > +  Filter   :  from-lport
>> > +  Index/Key:  0
>> > +  Sources  :  None attached
>> > +mirror2:
>> > +  Type     :  erspan
>> > +  Sink     :  10.10.10.2
>> > +  Filter   :  both
>> > +  Index/Key:  1
>> > +  Sources  :  None attached
>> > +mirror3:
>> > +  Type     :  gre
>> > +  Sink     :  10.10.10.3
>> > +  Filter   :  to-lport
>> > +  Index/Key:  2
>> > +  Sources  :  sw0-port1
>> > +])
>>
>> We should also check the case where multiple ports are attached to the
>> same mirror.
>>
>> > +
>> > +dnl Detach another source port from mirror
>> > +AT_CHECK([ovn-nbctl lsp-detach-mirror sw0-port1 mirror3])
>> > +
>> > +dnl Delete a single mirror.
>> > +AT_CHECK([ovn-nbctl mirror-del mirror3])
>>
>> Can you also check here what happens when you delete a mirror that is
>> attached? (The mirror is properly detached and deleted.)
>>
>> > +
>> > +AT_CHECK([ovn-nbctl mirror-list], [0], [dnl
>> > +mirror1:
>> > +  Type     :  gre
>> > +  Sink     :  10.10.10.1
>> > +  Filter   :  from-lport
>> > +  Index/Key:  0
>> > +  Sources  :  None attached
>> > +mirror2:
>> > +  Type     :  erspan
>> > +  Sink     :  10.10.10.2
>> > +  Filter   :  both
>> > +  Index/Key:  1
>> > +  Sources  :  None attached
>> > +])
>> > +
>> > +dnl Delete another mirror.
>> > +AT_CHECK([ovn-nbctl mirror-del mirror2])
>> > +
>> > +dnl Update the Sink address
>> > +AT_CHECK([ovn-nbctl set mirror . sink=192.168.1.13])
>> > +
>> > +AT_CHECK([ovn-nbctl mirror-list], [0], [dnl
>> > +mirror1:
>> > +  Type     :  gre
>> > +  Sink     :  192.168.1.13
>> > +  Filter   :  from-lport
>> > +  Index/Key:  0
>> > +  Sources  :  None attached
>> > +])
>> > +
>> > +dnl Delete all mirrors and remove switch
>> > +AT_CHECK([ovn-nbctl mirror-del])
>> > +AT_CHECK([ovn-nbctl ls-del sw0])
>>
>> Why is this ls-del needed? This seems irrelevant to what the test case
>> checks - mirror commands working.
>>
>> > +AT_CHECK([ovn-nbctl mirror-list], [0], [dnl
>> > +])])
>> > +
>> > +dnl ---------------------------------------------------------------------
>> > +
>> >  OVN_NBCTL_TEST([ovn_nbctl_nats], [NATs], [
>> >  AT_CHECK([ovn-nbctl lr-add lr0])
>> >  AT_CHECK([ovn-nbctl lr-nat-add lr0 snatt 30.0.0.2 192.168.1.2], [1], [],
>> > diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
>> > index 157f9f60c..b7d92b60c 100644
>> > --- a/tests/ovn-northd.at
>> > +++ b/tests/ovn-northd.at
>> > @@ -2205,6 +2205,56 @@ check_meter_by_name NOT meter_me__${acl1} meter_me__${acl2}
>> >  AT_CLEANUP
>> >  ])
>> >
>> > +OVN_FOR_EACH_NORTHD([
>> > +AT_SETUP([Check NB-SB mirrors sync])
>> > +AT_KEYWORDS([mirrors])
>> > +ovn_start
>> > +
>> > +check ovn-nbctl --wait=sb mirror-add mirror1 erspan 0 both 10.10.10.2
>> > +
>> > +AT_CHECK([ovn-sbctl get Mirror . Sink], [0], [dnl
>> > +"10.10.10.2"
>> > +])
>> > +
>> > +AT_CHECK([ovn-sbctl get Mirror . type], [0], [dnl
>> > +erspan
>> > +])
>> > +
>> > +AT_CHECK([ovn-sbctl get Mirror . index], [0], [dnl
>> > +0
>> > +])
>> > +
>> > +AT_CHECK([ovn-sbctl get Mirror . filter], [0], [dnl
>> > +both
>> > +])
>> > +
>> > +check ovn-nbctl --wait=sb \
>> > +    -- set mirror . sink=192.168.1.13 \
>> > +    -- set mirror . type=gre \
>> > +    -- set mirror . index=12 \
>> > +    -- set mirror . filter=to-lport
>>
>> While more work here, I think it's worth updating fields one by one
>> and checking that each update triggeres the corresponding update (and
>> other fields are not e.g. reset to nul-values).
>>
>> > +
>> > +AT_CHECK([ovn-sbctl get Mirror . Sink], [0], [dnl
>> > +"192.168.1.13"
>> > +])
>> > +
>> > +AT_CHECK([ovn-sbctl get Mirror . type], [0], [dnl
>> > +gre
>> > +])
>> > +
>> > +AT_CHECK([ovn-sbctl get Mirror . index], [0], [dnl
>> > +12
>> > +])
>> > +
>> > +AT_CHECK([ovn-sbctl get Mirror . filter], [0], [dnl
>> > +to-lport
>> > +])
>> > +
>> > +check ovn-nbctl --wait=sb mirror-del
>>
>> This mirror-del seems unneeded unless you would then check that SB
>> objects are cleaned up.
>>
>> > +
>> > +AT_CLEANUP
>> > +])
>> > +
>> >  OVN_FOR_EACH_NORTHD([
>> >  AT_SETUP([ACL skip hints for stateless config])
>> >  AT_KEYWORDS([acl])
>> > diff --git a/tests/ovn.at b/tests/ovn.at
>> > index bba2c9c1d..9c2905ea8 100644
>> > --- a/tests/ovn.at
>> > +++ b/tests/ovn.at
>> > @@ -16082,6 +16082,165 @@ 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"
>> > +
>> > +# 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
>>
>> remove
>>
>> > +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
>> > +
>> > +# 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
>> > +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 MIRROR
>> > +#
>> > +# 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 - Currently checking only for GRE
>> > +# MIRROR - Mirror name
>> > +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_name=${11}
>> > +    shift; shift; shift; shift; shift; shift; shift
>> > +    shift; shift; shift; shift;
>> > +
>> > +    check ovn-nbctl mirror-add ${mirror_name} ${mirror_encap_type} 0 to-lport 192.168.1.12
>> > +    check ovn-nbctl lsp-attach-mirror ls1-lp1 ${mirror_name}
>>
>> I think this doesn't belong in this function. Please move it outside.
>>
>> > +
>> > +    # 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
>> > +    if test X$exp_icmp_chksum != X; then
>>
>> why is this check here? I don't think you ever call it without
>> exp_icmp_chksum set.
>>
>> > +        # 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
>>
>> do you ever call it with any other mirror_encap_type? Why the check here?
>>
>> > +            #echo "GRE"
>>
>> remove
>>
>> > +            local mirror=${remote_mac}${local_mac}08004500007e00004000402fb6e9c0a8010bc0a8010c2000655800000000${reply}
>>
>> Is it the same as "local packet" above, just with different MACs? If
>> so, could we reconstruct it using the same formula / by replacing MACs
>> at the start of the string representing the packet?
>>
>> > +        fi
>> > +        echo $mirror >> br-phys_n1.expected
>> > +    fi
>> > +}
>> > +
>> > +# Set IPs
>> > +rtr_l2_ip=$(ip_to_hex 172 16 1 1)
>> > +l1_ip=$(ip_to_hex 192 168 1 2)
>> > +
>> > +# 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" mirror0
>> > +
>> > +OVN_CHECK_PACKETS_CONTAIN([hv1/br-phys_n1-tx.pcap], [br-phys_n1.expected])
>>
>> could you please also check the reply was actually received at the
>> destination, so that we know that mirroring didn't break delivery
>>
>> > +
>> > +tcpdump -r hv1/br-phys_n1-tx.pcap
>> > +
>> > +echo "---------OVN NB Mirror-----"
>> > +ovn-nbctl mirror-list
>> > +
>> > +echo "---------OVS Mirror----"
>> > +ovs-vsctl list Mirror
>> > +
>> > +echo "-----------------------"
>> > +echo "Delete vif1 so that OVN releases port binding"
>> > +ovs-vsctl del-port br-int vif1
>> > +
>> > +echo "Verifying Mirror deletion in OVS"
>> > +
>> > +AT_CHECK([as hv1 ovs-vsctl list Mirror], [0], [dnl
>> > +])
>> > +
>> > +OVN_CLEANUP([hv1], [hv2])
>> > +AT_CLEANUP
>> > +])
>> >
>>
>> This test case should be expanded (or new test case variants added) to
>> explore how different tunnel types (erspan vs gre), filters (to, from,
>> both) behave.
>>
>> >  OVN_FOR_EACH_NORTHD([
>> >  AT_SETUP([Port Groups])
>> > diff --git a/utilities/ovn-nbctl.c b/utilities/ovn-nbctl.c
>> > index 3bbdbd998..a30b5e4f2 100644
>> > --- a/utilities/ovn-nbctl.c
>> > +++ b/utilities/ovn-nbctl.c
>> > @@ -271,6 +271,16 @@ QoS commands:\n\
>> >                              remove QoS rules from SWITCH\n\
>> >    qos-list SWITCH           print QoS rules for SWITCH\n\
>> >  \n\
>> > +Mirror commands:\n\
>> > +  mirror-add NAME TYPE INDEX FILTER IP\n\
>> > +                            add a mirror with given name\n\
>> > +                            specify TYPE 'gre' or 'erspan'\n\
>> > +                            specify INDEX gre key / erpsan idx\n\
>> > +                            specify FILTER for mirroring selection\n\
>>
>> it may be helpful for a reader to see the list of supported FILTERs
>>
>> > +                            specify Sink / Destination i.e Remote IP \n\
>>
>> nit: "i.e." (dot at the end)
>> nit: remove spurious space at the end of the line
>>
>> > +  mirror-del [NAME]         remove mirrors\n\
>> > +  mirror-list               print mirrors\n\
>> > +\n\
>> >  Meter commands:\n\
>> >    [--fair]\n\
>> >    meter-add NAME ACTION RATE UNIT [BURST]\n\
>> > @@ -311,6 +321,8 @@ Logical switch port commands:\n\
>> >                              set dhcpv6 options for PORT\n\
>> >    lsp-get-dhcpv6-options PORT  get the dhcpv6 options for PORT\n\
>> >    lsp-get-ls PORT           get the logical switch which the port belongs to\n\
>> > +  lsp-attach-mirror PORT MIRROR   attach source PORT to the MIRROR\n\
>> > +  lsp-detach-mirror PORT MIRROR   detach source PORT from the MIRROR\n\
>>
>> nit: "to MIRROR", "from MIRROR" to match other places (e.g. we say
>> "attach source PORT" not "attach a source PORT")
>>
>> >  \n\
>> >  Forwarding group commands:\n\
>> >    [--liveness]\n\
>> > @@ -1685,6 +1697,130 @@ nbctl_pre_lsp_type(struct ctl_context *ctx)
>> >      ovsdb_idl_add_column(ctx->idl, &nbrec_logical_switch_port_col_type);
>> >  }
>> >
>> > +static void
>> > +nbctl_pre_lsp_mirror(struct ctl_context *ctx)
>> > +{
>> > +    ovsdb_idl_add_column(ctx->idl, &nbrec_logical_switch_port_col_name);
>> > +    ovsdb_idl_add_column(ctx->idl,
>> > +                         &nbrec_logical_switch_port_col_mirror_rules);
>> > +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_name);
>> > +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_src);
>> > +}
>> > +
>> > +static int
>> > +mirror_cmp(const void *mirror1_, const void *mirror2_)
>> > +{
>> > +    const struct nbrec_mirror *const *mirror_1 = mirror1_;
>> > +    const struct nbrec_mirror *const *mirror_2 = mirror2_;
>> > +
>> > +    const struct nbrec_mirror *mirror1 = *mirror_1;
>> > +    const struct nbrec_mirror *mirror2 = *mirror_2;
>> > +
>> > +    return strcmp(mirror1->name,mirror2->name);
>> > +}
>> > +
>> > +static char * OVS_WARN_UNUSED_RESULT
>> > +mirror_by_name_or_uuid(struct ctl_context *ctx, const char *id,
>> > +                    bool must_exist,
>> > +                    const struct nbrec_mirror **mirror_p)
>> > +{
>> > +    const struct nbrec_mirror *mirror = NULL;
>> > +    *mirror_p = NULL;
>> > +
>> > +    struct uuid mirror_uuid;
>> > +    bool is_uuid = uuid_from_string(&mirror_uuid, id);
>> > +    if (is_uuid) {
>> > +        mirror = nbrec_mirror_get_for_uuid(ctx->idl, &mirror_uuid);
>> > +    }
>> > +
>> > +    if (!mirror) {
>> > +        NBREC_MIRROR_FOR_EACH (mirror, ctx->idl) {
>> > +            if (!strcmp(mirror->name, id)) {
>> > +                break;
>> > +            }
>> > +        }
>> > +    }
>> > +
>> > +    if (!mirror && must_exist) {
>> > +        return xasprintf("%s: mirror %s not found",
>> > +                         id, is_uuid ? "UUID" : "name");
>> > +    }
>> > +
>> > +    *mirror_p = mirror;
>> > +    return NULL;
>> > +}
>> > +
>> > +static void
>> > +nbctl_lsp_attach_mirror(struct ctl_context *ctx)
>> > +{
>> > +    const char *port = ctx->argv[1];
>> > +    const char *mirror_name = ctx->argv[2];
>> > +    const struct nbrec_logical_switch_port *lsp = NULL;
>> > +    const struct nbrec_mirror *mirror;
>> > +
>> > +    char *error;
>> > +
>> > +    error = lsp_by_name_or_uuid(ctx, port, true, &lsp);
>> > +    if (error) {
>> > +        ctx->error = error;
>> > +        return;
>> > +    }
>> > +
>> > +
>> > +    /*check if a mirror rule actually exists on that name or not*/
>> > +    error = mirror_by_name_or_uuid(ctx, mirror_name, true, &mirror);
>> > +    if (error) {
>> > +        ctx->error = error;
>> > +        return;
>> > +    }
>> > +
>> > +    /* Check if same mirror rule already exists for the lsp */
>> > +    for (size_t i = 0; i < lsp->n_mirror_rules; i++) {
>> > +        if (!mirror_cmp(&lsp->mirror_rules[i], &mirror)) {
>> > +            bool may_exist = shash_find(&ctx->options, "--may-exist") != NULL;
>> > +            if (!may_exist) {
>> > +                ctl_error(ctx, "Same mirror already existed on the lsp %s.",
>> > +                          ctx->argv[1]);
>> > +                return;
>> > +            }
>> > +            return;
>> > +        }
>> > +    }
>> > +
>> > +    nbrec_logical_switch_port_update_mirror_rules_addvalue(lsp, mirror);
>> > +    nbrec_mirror_update_src_addvalue(mirror,lsp);
>> > +
>> > +}
>> > +
>> > +static void
>> > +nbctl_lsp_detach_mirror(struct ctl_context *ctx)
>> > +{
>> > +    const char *port = ctx->argv[1];
>> > +    const char *mirror_name = ctx->argv[2];
>> > +    const struct nbrec_logical_switch_port *lsp = NULL;
>> > +    const struct nbrec_mirror *mirror;
>> > +
>> > +    char *error;
>> > +
>> > +    error = lsp_by_name_or_uuid(ctx, port, true, &lsp);
>> > +    if (error) {
>> > +        ctx->error = error;
>> > +        return;
>> > +    }
>> > +
>> > +
>> > +    /*check if a mirror rule actually exists on that name or not*/
>> > +    error = mirror_by_name_or_uuid(ctx, mirror_name, true, &mirror);
>> > +    if (error) {
>> > +        ctx->error = error;
>> > +        return;
>> > +    }
>> > +
>> > +    nbrec_logical_switch_port_update_mirror_rules_delvalue(lsp, mirror);
>> > +    nbrec_mirror_update_src_delvalue(mirror,lsp);
>> > +
>> > +}
>> > +
>> >  static void
>> >  nbctl_lsp_set_type(struct ctl_context *ctx)
>> >  {
>> > @@ -7239,6 +7375,202 @@ cmd_ha_ch_grp_set_chassis_prio(struct ctl_context *ctx)
>> >      nbrec_ha_chassis_set_priority(ha_chassis, priority);
>> >  }
>> >
>> > +static char * OVS_WARN_UNUSED_RESULT
>> > +parse_filter(const char *arg, const char **selection_p)
>> > +{
>> > +    /* Validate selection.  Only require the first letter. */
>> > +    if (arg[0] == 't') {
>> > +        *selection_p = "to-lport";
>> > +    } else if (arg[0] == 'f') {
>> > +        *selection_p = "from-lport";
>> > +    } else if (arg[0] == 'b') {
>> > +        *selection_p = "both";
>> > +    } else {
>> > +        *selection_p = NULL;
>> > +        return xasprintf("%s: selection must be \"to-lport\" or "
>> > +                         "\"from-lport\" or \"both\" ", arg);
>> > +    }
>> > +    return NULL;
>> > +}
>> > +
>> > +static char * OVS_WARN_UNUSED_RESULT
>> > +parse_type(const char *arg, const char **type_p)
>> > +{
>> > +    /* Validate type.  Only require the second letter. */
>>
>> you check the first letter though
>>
>> > +    if (arg[0] == 'g') {
>> > +        *type_p = "gre";
>> > +    } else if (arg[0] == 'e') {
>> > +        *type_p = "erspan";
>> > +    } else {
>> > +        *type_p = NULL;
>> > +        return xasprintf("%s: type must be \"gre\" or "
>> > +                         "\"erspan\"", arg);
>> > +    }
>> > +    return NULL;
>> > +}
>> > +
>> > +static void
>> > +nbctl_pre_mirror_add(struct ctl_context *ctx)
>> > +{
>> > +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_name);
>> > +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_filter);
>> > +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_index);
>> > +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_sink);
>> > +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_type);
>> > +}
>> > +
>> > +static void
>> > +nbctl_mirror_add(struct ctl_context *ctx)
>> > +{
>> > +    const char *filter = NULL;
>> > +    const char *sink_ip = NULL;
>> > +    const char *type = NULL;
>> > +    const char *name = NULL;
>> > +    char *new_external_ip = NULL;
>> > +    int64_t index;
>> > +    char *error = NULL;
>> > +    const struct nbrec_mirror *mirror_check = NULL;
>> > +
>> > +    /* Mirror Name */
>> > +    name = ctx->argv[1];
>> > +    NBREC_MIRROR_FOR_EACH (mirror_check, ctx->idl) {
>> > +            if (!strcmp(mirror_check->name, name)) {
>> > +                ctl_error(ctx, "Mirror with %s name already exists.",
>> > +                          name);
>> > +                return;
>> > +            }
>> > +        }
>> > +
>> > +    /* Tunnel Type - GRE/ERSPAN */
>> > +    error = parse_type(ctx->argv[2], &type);
>> > +    if (error) {
>> > +        ctx->error = error;
>> > +        return;
>> > +    }
>> > +
>> > +    /* tunnel index / GRE key / ERSPAN idx */
>> > +    index = atoi(ctx->argv[3]);
>> > +
>> > +    /* Filter for mirroring */
>> > +    error = parse_filter(ctx->argv[4], &filter);
>> > +    if (error) {
>> > +        ctx->error = error;
>> > +        return;
>> > +    }
>> > +
>> > +    /* Destination / Sink details */
>> > +    sink_ip = ctx->argv[5];
>> > +
>> > +    /* check if it is a valid ip */
>> > +    new_external_ip = normalize_ipv4_addr_str(sink_ip);
>>
>> new_sink_ip?
>>
>> > +    if (!new_external_ip) {
>> > +        new_external_ip = normalize_ipv6_addr_str(sink_ip);
>> > +    }
>> > +
>> > +    if (new_external_ip) {
>> > +        free(new_external_ip);
>> > +    } else {
>> > +        ctl_error(ctx, "Invalid sink ip: %s", sink_ip);
>> > +        return;
>> > +    }
>> > +
>> > +    /* Create the mirror. */
>> > +    struct nbrec_mirror *mirror = nbrec_mirror_insert(ctx->txn);
>> > +    nbrec_mirror_set_name(mirror, name);
>> > +    nbrec_mirror_set_index(mirror, index);
>> > +    nbrec_mirror_set_filter(mirror, filter);
>> > +    nbrec_mirror_set_type(mirror, type);
>> > +    nbrec_mirror_set_sink(mirror, sink_ip);
>> > +
>> > +}
>> > +
>> > +static void
>> > +nbctl_pre_mirror_del(struct ctl_context *ctx)
>> > +{
>> > +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_name);
>> > +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_src);
>> > +}
>> > +
>> > +static void
>> > +nbctl_mirror_del(struct ctl_context *ctx)
>> > +{
>> > +    const struct nbrec_mirror *mirror, *next;
>> > +
>> > +    /* If a name is not specified, delete all mirrors. */
>> > +    if (ctx->argc == 1) {
>> > +        NBREC_MIRROR_FOR_EACH_SAFE (mirror, next, ctx->idl) {
>> > +            nbrec_mirror_delete(mirror);
>> > +        }
>> > +        return;
>> > +    }
>> > +
>> > +    /* Remove the matching mirror. */
>> > +    NBREC_MIRROR_FOR_EACH (mirror, ctx->idl) {
>> > +        if (strcmp(ctx->argv[1], mirror->name)) {
>> > +            continue;
>> > +        }
>> > +        nbrec_mirror_delete(mirror);
>> > +        return;
>> > +    }
>> > +}
>> > +
>> > +static void
>> > +nbctl_pre_mirror_list(struct ctl_context *ctx)
>> > +{
>> > +    ovsdb_idl_add_column(ctx->idl, &nbrec_logical_switch_port_col_name);
>> > +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_name);
>> > +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_filter);
>> > +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_index);
>> > +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_sink);
>> > +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_type);
>> > +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_src);
>> > +}
>> > +
>> > +static void
>> > +nbctl_mirror_list(struct ctl_context *ctx)
>> > +{
>> > +
>> > +    const struct nbrec_mirror **mirrors = NULL;
>> > +    const struct nbrec_mirror *mirror;
>> > +    size_t n_capacity = 0;
>> > +    size_t n_mirrors = 0;
>> > +
>> > +    NBREC_MIRROR_FOR_EACH (mirror, ctx->idl) {
>> > +        if (n_mirrors == n_capacity) {
>> > +            mirrors = x2nrealloc(mirrors, &n_capacity, sizeof *mirrors);
>> > +        }
>> > +
>> > +        mirrors[n_mirrors] = mirror;
>> > +        n_mirrors++;
>> > +    }
>> > +
>> > +    if (n_mirrors) {
>> > +        qsort(mirrors, n_mirrors, sizeof *mirrors, mirror_cmp);
>> > +    }
>> > +
>> > +    for (size_t i = 0; i < n_mirrors; i++) {
>> > +        mirror = mirrors[i];
>> > +        ds_put_format(&ctx->output, "%s:\n", mirror->name);
>> > +        /* print all the values */
>> > +        ds_put_format(&ctx->output, "  Type     :  %s\n", mirror->type);
>> > +        ds_put_format(&ctx->output, "  Sink     :  %s\n", mirror->sink);
>> > +        ds_put_format(&ctx->output, "  Filter   :  %s\n", mirror->filter);
>> > +        ds_put_format(&ctx->output, "  Index/Key:  %ld\n",
>> > +                                                 (long int)mirror->index);
>> > +        ds_put_cstr(&ctx->output,   "  Sources  :");
>> > +        if (mirror->n_src > 0) {
>> > +            for (size_t j = 0; j < mirror->n_src; j++) {
>> > +                ds_put_format(&ctx->output, "  %s", mirror->src[j]->name);
>> > +            }
>> > +        } else {
>> > +            ds_put_cstr(&ctx->output, "  None attached");
>> > +        }
>> > +        ds_put_cstr(&ctx->output, "\n");
>> > +    }
>> > +
>> > +    free(mirrors);
>> > +}
>> > +
>> >  static const struct ctl_table_class tables[NBREC_N_TABLES] = {
>> >      [NBREC_TABLE_DHCP_OPTIONS].row_ids
>> >      = {{&nbrec_logical_switch_port_col_name, NULL,
>> > @@ -7332,6 +7664,15 @@ static const struct ctl_command_syntax nbctl_commands[] = {
>> >      { "qos-list", 1, 1, "SWITCH", nbctl_pre_qos_list, nbctl_qos_list,
>> >        NULL, "", RO },
>> >
>> > +    /* mirror commands. */
>> > +    { "mirror-add", 5, 5,
>> > +      "NAME TYPE INDEX FILTER IP",
>> > +      nbctl_pre_mirror_add, nbctl_mirror_add, NULL, "--may-exist", RW },
>> > +    { "mirror-del", 0, 1, "[NAME]",
>> > +      nbctl_pre_mirror_del, nbctl_mirror_del, NULL, "", RW },
>> > +    { "mirror-list", 0, 0, "", nbctl_pre_mirror_list, nbctl_mirror_list,
>> > +      NULL, "", RO },
>> > +
>> >      /* meter commands. */
>> >      { "meter-add", 4, 5, "NAME ACTION RATE UNIT [BURST]", nbctl_pre_meter_add,
>> >        nbctl_meter_add, NULL, "--fair,--may-exist", RW },
>> > @@ -7386,6 +7727,10 @@ static const struct ctl_command_syntax nbctl_commands[] = {
>> >        nbctl_lsp_get_dhcpv6_options, NULL, "", RO },
>> >      { "lsp-get-ls", 1, 1, "PORT", nbctl_pre_lsp_get_ls, nbctl_lsp_get_ls,
>> >        NULL, "", RO },
>> > +    { "lsp-attach-mirror", 2, 2, "PORT MIRROR", nbctl_pre_lsp_mirror,
>> > +      nbctl_lsp_attach_mirror, NULL, "", RW },
>> > +    { "lsp-detach-mirror", 2, 2, "PORT MIRROR", nbctl_pre_lsp_mirror,
>> > +      nbctl_lsp_detach_mirror, NULL, "", RW },
>> >
>> >      /* forwarding group commands. */
>> >      { "fwd-group-add", 4, INT_MAX, "SWITCH GROUP VIP VMAC PORT...",
>> > diff --git a/utilities/ovn-sbctl.c b/utilities/ovn-sbctl.c
>> > index e35556d34..1aa6cc26f 100644
>> > --- a/utilities/ovn-sbctl.c
>> > +++ b/utilities/ovn-sbctl.c
>> > @@ -307,6 +307,7 @@ pre_get_info(struct ctl_context *ctx)
>> >      ovsdb_idl_add_column(ctx->idl, &sbrec_port_binding_col_chassis);
>> >      ovsdb_idl_add_column(ctx->idl, &sbrec_port_binding_col_datapath);
>> >      ovsdb_idl_add_column(ctx->idl, &sbrec_port_binding_col_up);
>> > +    ovsdb_idl_add_column(ctx->idl, &sbrec_port_binding_col_mirror_rules);
>> >
>> >      ovsdb_idl_add_column(ctx->idl, &sbrec_logical_flow_col_logical_datapath);
>> >      ovsdb_idl_add_column(ctx->idl, &sbrec_logical_flow_col_logical_dp_group);
>> > @@ -1434,6 +1435,9 @@ static const struct ctl_table_class tables[SBREC_N_TABLES] = {
>> >      [SBREC_TABLE_HA_CHASSIS].row_ids[0]
>> >      = {&sbrec_ha_chassis_col_chassis, NULL, NULL},
>> >
>> > +    [SBREC_TABLE_MIRROR].row_ids[0]
>> > +    = {&sbrec_mirror_col_name, NULL, NULL},
>> > +
>> >      [SBREC_TABLE_METER].row_ids[0]
>> >      = {&sbrec_meter_col_name, NULL, NULL},
>> >
>> > --
>> > 2.27.0
>> >
>> > _______________________________________________
>> > dev mailing list
>> > dev@openvswitch.org
>> > https://mail.openvswitch.org/mailman/listinfo/ovs-dev
>>
> Thanks & Regards,
> Abhiram R N
diff mbox series

Patch

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..44895d8ca
--- /dev/null
+++ b/controller/mirror.c
@@ -0,0 +1,510 @@ 
+/* 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 "openvswitch/vlog.h"
+
+/* OVN includes. */
+#include "binding.h"
+#include "lib/ovn-sb-idl.h"
+#include "mirror.h"
+
+VLOG_DEFINE_THIS_MODULE(port_mirror);
+
+/* Static function declarations */
+
+static const struct ovsrec_port *
+get_port_for_iface(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 bool
+mirror_create(const struct sbrec_port_binding *pb,
+              struct port_mirror_ctx *pm_ctx)
+{
+    const struct ovsrec_mirror *mirror = NULL;
+
+    if (pb->n_up && !pb->up[0]) {
+        return true;
+    }
+
+    if (pb->chassis != pm_ctx->chassis_rec) {
+        return true;
+    }
+
+    if (!pm_ctx->ovs_idl_txn) {
+        return false;
+    }
+
+
+    VLOG_INFO("Mirror rule(s) present for %s ", pb->logical_port);
+    /* Loop through the mirror rules */
+    for (size_t i =0; i < pb->n_mirror_rules; i++) {
+        /* check if the mirror already exists in OVS DB */
+        bool create_mirror = true;
+        OVSREC_MIRROR_TABLE_FOR_EACH (mirror, pm_ctx->mirror_table) {
+            if (!strcmp(pb->mirror_rules[i]->name, mirror->name)) {
+                  /* Mirror with same name already exists
+                   * No need to create mirror
+                   */
+                  create_mirror = false;
+                  break;
+            }
+        }
+
+        if (create_mirror) {
+
+            struct smap options = SMAP_INITIALIZER(&options);
+            char *port_name, *key;
+
+            key = xasprintf("%ld",(long int)pb->mirror_rules[i]->index);
+            smap_add(&options, "remote_ip", pb->mirror_rules[i]->sink);
+
+            if (!strcmp(pb->mirror_rules[i]->type, "gre")) {
+                /* Set the GRE key */
+                smap_add(&options, "key", key);
+
+            } else if (!strcmp(pb->mirror_rules[i]->type, "erspan")) {
+                /* Set the ERSPAN index */
+                smap_add(&options, "key", key);
+                smap_add(&options, "erspan_idx", key);
+                smap_add(&options, "erspan_ver","1");
+
+            }
+            struct ovsrec_interface *iface =
+                      ovsrec_interface_insert(pm_ctx->ovs_idl_txn);
+            port_name = xasprintf("ovn-%s",
+                                   pb->mirror_rules[i]->name);
+
+            ovsrec_interface_set_name(iface, port_name);
+            ovsrec_interface_set_type(iface, pb->mirror_rules[i]->type);
+            ovsrec_interface_set_options(iface, &options);
+
+            struct ovsrec_port *port =
+                              ovsrec_port_insert(pm_ctx->ovs_idl_txn);
+            ovsrec_port_set_name(port, port_name);
+            ovsrec_port_set_interfaces(port, &iface, 1);
+
+            ovsrec_bridge_update_ports_addvalue(pm_ctx->br_int, port);
+
+            smap_destroy(&options);
+            free(port_name);
+            free(key);
+
+            VLOG_INFO("Creating Mirror in OVS DB");
+            mirror = ovsrec_mirror_insert(pm_ctx->ovs_idl_txn);
+            ovsrec_mirror_set_name(mirror,pb->mirror_rules[i]->name);
+            ovsrec_mirror_update_output_port_addvalue(mirror, port);
+            ovsrec_bridge_update_mirrors_addvalue(pm_ctx->br_int,
+                                                             mirror);
+        }
+
+        struct local_binding *lbinding = local_binding_find(
+                               pm_ctx->local_bindings, pb->logical_port);
+        const struct ovsrec_port *p =
+                     get_port_for_iface(lbinding->iface, pm_ctx->br_int);
+        if (p) {
+            if (!strcmp(pb->mirror_rules[i]->filter,"from-lport")) {
+                ovsrec_mirror_update_select_src_port_addvalue(mirror, p);
+            } else if (!strcmp(pb->mirror_rules[i]->filter,"to-lport")) {
+                ovsrec_mirror_update_select_dst_port_addvalue(mirror, p);
+            } else {
+                ovsrec_mirror_update_select_src_port_addvalue(mirror, p);
+                ovsrec_mirror_update_select_dst_port_addvalue(mirror, p);
+            }
+        }
+    }
+    return true;
+}
+
+static void
+check_and_update_mirror_table(const struct sbrec_mirror *sb_mirror,
+                              struct ovsrec_mirror *ovs_mirror)
+{
+    char *filter;
+    if ((ovs_mirror->n_select_dst_port)
+            && (ovs_mirror->n_select_src_port)) {
+        filter = xasprintf("both");
+    } else if (ovs_mirror->n_select_dst_port) {
+        filter = xasprintf("to-lport");
+    } else {
+        filter = xasprintf("from-lport");
+    }
+
+    if (strcmp(sb_mirror->filter, filter)) {
+        if (!strcmp(sb_mirror->filter,"from-lport")
+                              && !strcmp(filter,"both")) {
+            for (size_t i = 0; i < ovs_mirror->n_select_dst_port; i++) {
+                ovsrec_mirror_update_select_dst_port_delvalue(ovs_mirror,
+                                             ovs_mirror->select_dst_port[i]);
+            }
+        } else if (!strcmp(sb_mirror->filter,"to-lport")
+                              && !strcmp(filter,"both")) {
+            for (size_t i = 0; i < ovs_mirror->n_select_src_port; i++) {
+                ovsrec_mirror_update_select_src_port_delvalue(ovs_mirror,
+                                             ovs_mirror->select_src_port[i]);
+            }
+        } else if (!strcmp(sb_mirror->filter,"both")
+                              && !strcmp(filter,"from-lport")) {
+            for (size_t i = 0; i < ovs_mirror->n_select_src_port; i++) {
+                ovsrec_mirror_update_select_dst_port_addvalue(ovs_mirror,
+                                             ovs_mirror->select_src_port[i]);
+            }
+        } else if (!strcmp(sb_mirror->filter,"both")
+                              && !strcmp(filter,"to-lport")) {
+            for (size_t i = 0; i < ovs_mirror->n_select_dst_port; i++) {
+                ovsrec_mirror_update_select_src_port_addvalue(ovs_mirror,
+                                             ovs_mirror->select_dst_port[i]);
+            }
+        } else if (!strcmp(sb_mirror->filter,"to-lport")
+                              && !strcmp(filter,"from-lport")) {
+            for (size_t i = 0; i < ovs_mirror->n_select_src_port; i++) {
+                ovsrec_mirror_update_select_dst_port_addvalue(ovs_mirror,
+                                             ovs_mirror->select_src_port[i]);
+                ovsrec_mirror_update_select_src_port_delvalue(ovs_mirror,
+                                             ovs_mirror->select_src_port[i]);
+            }
+        } else if (!strcmp(sb_mirror->filter,"from-lport")
+                              && !strcmp(filter,"to-lport")) {
+            for (size_t i = 0; i < ovs_mirror->n_select_dst_port; i++) {
+                ovsrec_mirror_update_select_src_port_addvalue(ovs_mirror,
+                                             ovs_mirror->select_dst_port[i]);
+                ovsrec_mirror_update_select_dst_port_delvalue(ovs_mirror,
+                                             ovs_mirror->select_dst_port[i]);
+            }
+        }
+    }
+}
+
+static void
+check_and_update_interface_table(const struct sbrec_mirror *sb_mirror,
+                                   struct ovsrec_mirror *ovs_mirror)
+{
+    struct smap options = SMAP_INITIALIZER(&options);
+    char *key, *type;
+    struct smap *opts = &ovs_mirror->output_port->interfaces[0]->options;
+    struct ovsrec_interface *iface =
+                          ovs_mirror->output_port->interfaces[0];
+
+    const char *erspan_ver = smap_get(opts, "erspan_ver");
+    if (erspan_ver) {
+        type = xasprintf("erspan");
+    } else {
+        type = xasprintf("gre");
+    }
+    if (strcmp(type, sb_mirror->type)) {
+        ovsrec_interface_set_type(iface, sb_mirror->type);
+    }
+
+    key = xasprintf("%ld",(long int)sb_mirror->index);
+    smap_add(&options, "remote_ip", sb_mirror->sink);
+
+    if (!strcmp(sb_mirror->type, "gre")) {
+        /* Set the GRE key */
+        smap_add(&options, "key", key);
+
+    } else if (!strcmp(sb_mirror->type, "erspan")) {
+        /* Set the ERSPAN index */
+        smap_add(&options, "key", key);
+        smap_add(&options, "erspan_idx", key);
+        smap_add(&options, "erspan_ver","1");
+
+    }
+
+    ovsrec_interface_set_options(iface, &options);
+    smap_destroy(&options);
+    free(key);
+
+}
+
+static void
+mirror_update(const struct sbrec_mirror *sb_mirror,
+              struct ovsrec_mirror *ovs_mirror)
+{
+    check_and_update_interface_table(sb_mirror, ovs_mirror);
+
+    check_and_update_mirror_table(sb_mirror, ovs_mirror);
+}
+
+static void
+mirror_delete(const struct sbrec_port_binding *pb,
+              struct port_mirror_ctx *pm_ctx,
+              struct shash *pb_mirror_map,
+              bool delete_all)
+{
+    struct sset pb_mirrors = SSET_INITIALIZER(&pb_mirrors);
+
+    if (!delete_all) {
+        for (size_t i = 0; i < pb->n_mirror_rules ; i++) {
+            sset_add(&pb_mirrors, pb->mirror_rules[i]->name);
+        }
+    }
+
+    if (delete_all && (shash_is_empty(pb_mirror_map)) && pb->n_mirror_rules) {
+        for (size_t i = 0; i < pb->n_mirror_rules ; i++) {
+
+            struct ovsrec_mirror *ovs_mirror = NULL;
+            ovs_mirror = shash_find_data(pm_ctx->ovs_mirrors,
+                                            pb->mirror_rules[i]->name);
+            if (ovs_mirror) {
+                if (ovs_mirror->n_select_dst_port == 0 &&
+                        ovs_mirror->n_select_src_port == 0) {
+                    ovsrec_bridge_update_ports_delvalue(pm_ctx->br_int,
+                                                   ovs_mirror->output_port);
+                    ovsrec_bridge_update_mirrors_delvalue(pm_ctx->br_int,
+                                                                ovs_mirror);
+                    ovsrec_port_delete(ovs_mirror->output_port);
+                    ovsrec_mirror_delete(ovs_mirror);
+                }
+            }
+        }
+    }
+
+    struct shash_node *mirror_node;
+    SHASH_FOR_EACH (mirror_node, pb_mirror_map) {
+        struct ovsrec_mirror *ovs_mirror = mirror_node->data;
+        if (!sset_find(&pb_mirrors, ovs_mirror->name)) {
+            /* Find if the mirror has other sources i*/
+            if ((ovs_mirror->n_select_dst_port > 1) ||
+                (ovs_mirror->n_select_src_port > 1)) {
+                /* More than 1 source then just
+                 * update the mirror table
+                 */
+                bool done = false;
+                for (size_t i = 0; ((i < ovs_mirror->n_select_dst_port)
+                                                   && (done == false)); i++) {
+                    const struct ovsrec_port *port_rec =
+                                               ovs_mirror->select_dst_port[i];
+                    for (size_t j = 0; j < port_rec->n_interfaces; j++) {
+                        const struct ovsrec_interface *iface_rec;
+
+                        iface_rec = port_rec->interfaces[j];
+                        const char *iface_id =
+                                            smap_get(&iface_rec->external_ids,
+                                                                  "iface-id");
+                        if (!strcmp(iface_id,pb->logical_port)) {
+                            ovsrec_mirror_update_select_dst_port_delvalue(
+                                                        ovs_mirror, port_rec);
+                            done = true;
+                            break;
+                        }
+                    }
+                }
+                done = false;
+                for (size_t i = 0; ((i < ovs_mirror->n_select_src_port)
+                                                   && (done == false)); i++) {
+                    const struct ovsrec_port *port_rec =
+                                                ovs_mirror->select_src_port[i];
+                    for (size_t j = 0; j < port_rec->n_interfaces; j++) {
+                        const struct ovsrec_interface *iface_rec;
+
+                        iface_rec = port_rec->interfaces[j];
+                        const char *iface_id =
+                                            smap_get(&iface_rec->external_ids,
+                                                                  "iface-id");
+                        if (!strcmp(iface_id,pb->logical_port)) {
+                            ovsrec_mirror_update_select_src_port_delvalue(
+                                                        ovs_mirror, port_rec);
+                            done = true;
+                            break;
+                        }
+                    }
+                }
+            } else {
+                /*
+                 * If only 1 source delete the output port
+                 * and then delete the mirror completely
+                 */
+                VLOG_INFO("Only 1 source for the mirror. Hence delete it");
+                ovsrec_bridge_update_ports_delvalue(pm_ctx->br_int,
+                                                    ovs_mirror->output_port);
+                ovsrec_bridge_update_mirrors_delvalue(pm_ctx->br_int,
+                                                            ovs_mirror);
+                ovsrec_port_delete(ovs_mirror->output_port);
+                ovsrec_mirror_delete(ovs_mirror);
+            }
+        }
+    }
+
+    const char *used_node, *used_next;
+    SSET_FOR_EACH_SAFE (used_node, used_next, &pb_mirrors) {
+        sset_delete(&pb_mirrors, SSET_NODE_FROM_NAME(used_node));
+    }
+    sset_destroy(&pb_mirrors);
+}
+
+static void
+find_port_specific_mirrors (const struct sbrec_port_binding *pb,
+                            struct port_mirror_ctx *pm_ctx,
+                            struct shash *pb_mirror_map)
+{
+    const struct ovsrec_mirror *mirror = NULL;
+
+    OVSREC_MIRROR_TABLE_FOR_EACH (mirror, pm_ctx->mirror_table) {
+        for (size_t i = 0; i < mirror->n_select_dst_port; i++) {
+            const struct ovsrec_port *port_rec = mirror->select_dst_port[i];
+            for (size_t j = 0; j < port_rec->n_interfaces; j++) {
+                const struct ovsrec_interface *iface_rec;
+                iface_rec = port_rec->interfaces[j];
+                const char *logical_port =
+                    smap_get(&iface_rec->external_ids, "iface-id");
+                if (!strcmp(logical_port, pb->logical_port)) {
+                    shash_add_once(pb_mirror_map, mirror->name, mirror);
+                }
+            }
+        }
+        for (size_t i = 0; i < mirror->n_select_src_port; i++) {
+            const struct ovsrec_port *port_rec = mirror->select_src_port[i];
+            for (size_t j = 0; j < port_rec->n_interfaces; j++) {
+                const struct ovsrec_interface *iface_rec;
+                iface_rec = port_rec->interfaces[j];
+                const char *logical_port =
+                    smap_get(&iface_rec->external_ids, "iface-id");
+                if (!strcmp(logical_port, pb->logical_port)) {
+                    shash_add_once(pb_mirror_map, mirror->name, mirror);
+                }
+            }
+        }
+    }
+}
+
+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);
+}
+
+
+void
+ovn_port_mirror_init(struct shash *ovs_mirrors)
+{
+    shash_init(ovs_mirrors);
+}
+
+void
+ovn_port_mirror_run(struct port_mirror_ctx *pm_ctx)
+{
+    const struct sbrec_port_binding *pb;
+    SBREC_PORT_BINDING_TABLE_FOR_EACH (pb,
+                                       pm_ctx->port_binding_table) {
+        ovn_port_mirror_handle_lport(pb, false, pm_ctx);
+    }
+}
+
+bool
+ovn_port_mirror_handle_lport(const struct sbrec_port_binding *pb, bool removed,
+                     struct port_mirror_ctx *pm_ctx)
+{
+    bool ret = true;
+    struct shash port_ovs_mirrors = SHASH_INITIALIZER(&port_ovs_mirrors);
+
+    /* Need to find if mirror needs update */
+    find_port_specific_mirrors(pb, pm_ctx, &port_ovs_mirrors);
+    if (removed == false) {
+        if (((pb->n_mirror_rules == 0)
+              && (shash_is_empty(&port_ovs_mirrors))) ||
+              (pb->n_mirror_rules == shash_count(&port_ovs_mirrors))) {
+            /* No mirror update */
+        } else {
+            /* Update Mirror */
+            if (pb->n_mirror_rules > shash_count(&port_ovs_mirrors)) {
+                /* create mirror,
+                 * if mirror already exists only update selection
+                 */
+                ret = mirror_create(pb, pm_ctx);
+            } else {
+                /* delete mirror,
+                 * if mirror has other sources only update selection
+                 */
+                mirror_delete(pb, pm_ctx, &port_ovs_mirrors, false);
+            }
+        }
+    } else {
+           mirror_delete(pb, pm_ctx, &port_ovs_mirrors, true);
+    }
+
+    struct shash_node *ovs_mirror_node, *ovs_mirror_next;
+    SHASH_FOR_EACH_SAFE (ovs_mirror_node, ovs_mirror_next,
+                                              &port_ovs_mirrors) {
+        shash_delete(&port_ovs_mirrors, ovs_mirror_node);
+    }
+    shash_destroy(&port_ovs_mirrors);
+
+    return ret;
+}
+
+bool
+ovn_port_mirror_handle_update(struct port_mirror_ctx *pm_ctx)
+{
+    const struct sbrec_mirror *mirror = NULL;
+    struct ovsrec_mirror *ovs_mirror = NULL;
+
+    SBREC_MIRROR_TABLE_FOR_EACH_TRACKED (mirror, pm_ctx->sb_mirror_table) {
+    /* For each tracked mirror entry check if OVS entry is there*/
+        ovs_mirror = shash_find_data(pm_ctx->ovs_mirrors, mirror->name);
+        if (ovs_mirror) {
+            if (sbrec_mirror_is_deleted(mirror)) {
+                /* Need to delete the mirror in OVS */
+                VLOG_INFO("Delete mirror and remove port");
+                ovsrec_bridge_update_ports_delvalue(pm_ctx->br_int,
+                                                    ovs_mirror->output_port);
+                ovsrec_bridge_update_mirrors_delvalue(pm_ctx->br_int,
+                                                      ovs_mirror);
+                ovsrec_port_delete(ovs_mirror->output_port);
+                ovsrec_mirror_delete(ovs_mirror);
+            } else {
+                mirror_update(mirror, ovs_mirror);
+            }
+        }
+    }
+
+    return true;
+}
+
+void
+ovn_port_mirror_destroy(struct shash *ovs_mirrors)
+{
+    struct shash_node *ovs_mirror_node, *ovs_mirror_next;
+    SHASH_FOR_EACH_SAFE (ovs_mirror_node, ovs_mirror_next,
+                                              ovs_mirrors) {
+        shash_delete(ovs_mirrors, ovs_mirror_node);
+    }
+    shash_destroy(ovs_mirrors);
+}
+
diff --git a/controller/mirror.h b/controller/mirror.h
new file mode 100644
index 000000000..85b964f55
--- /dev/null
+++ b/controller/mirror.h
@@ -0,0 +1,53 @@ 
+/* 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_port_table;
+struct ovsrec_bridge;
+struct ovsrec_bridge_table;
+struct ovsrec_open_vswitch_table;
+struct sbrec_chassis;
+struct ovsrec_interface_table;
+struct ovsrec_mirror_table;
+struct sbrec_mirror_table;
+struct sbrec_port_binding_table;
+
+struct port_mirror_ctx {
+    struct shash *ovs_mirrors;
+    struct ovsdb_idl_txn *ovs_idl_txn;
+    const struct ovsrec_port_table *port_table;
+    const struct ovsrec_bridge *br_int;
+    const struct sbrec_chassis *chassis_rec;
+    const struct ovsrec_bridge_table *bridge_table;
+    const struct ovsrec_open_vswitch_table *ovs_table;
+    const struct ovsrec_interface_table *iface_table;
+    const struct ovsrec_mirror_table *mirror_table;
+    const struct sbrec_mirror_table *sb_mirror_table;
+    const struct sbrec_port_binding_table *port_binding_table;
+    struct shash *local_bindings;
+};
+
+void mirror_register_ovs_idl(struct ovsdb_idl *);
+void ovn_port_mirror_init(struct shash *);
+void ovn_port_mirror_destroy(struct shash *);
+void ovn_port_mirror_run(struct port_mirror_ctx *pm_ctx);
+bool ovn_port_mirror_handle_lport(const struct sbrec_port_binding *pb,
+                                  bool removed,
+                                  struct port_mirror_ctx *pm_ctx);
+bool ovn_port_mirror_handle_update(struct port_mirror_ctx *pm_ctx);
+#endif
diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c
index 0b0ccc48a..95307a2d9 100644
--- a/controller/ovn-controller.c
+++ b/controller/ovn-controller.c
@@ -77,6 +77,7 @@ 
 #include "stopwatch.h"
 #include "lib/inc-proc-eng.h"
 #include "hmapx.h"
+#include "mirror.h"
 
 VLOG_DEFINE_THIS_MODULE(main);
 
@@ -963,6 +964,7 @@  ctrl_register_ovs_idl(struct ovsdb_idl *ovs_idl)
     bfd_register_ovs_idl(ovs_idl);
     physical_register_ovs_idl(ovs_idl);
     vif_plug_register_ovs_idl(ovs_idl);
+    mirror_register_ovs_idl(ovs_idl);
 }
 
 #define SB_NODES \
@@ -983,6 +985,7 @@  ctrl_register_ovs_idl(struct ovsdb_idl *ovs_idl)
     SB_NODE(load_balancer, "load_balancer") \
     SB_NODE(fdb, "fdb") \
     SB_NODE(meter, "meter") \
+    SB_NODE(mirror, "mirror") \
     SB_NODE(static_mac_binding, "static_mac_binding")
 
 enum sb_engine_node {
@@ -1000,7 +1003,8 @@  enum sb_engine_node {
     OVS_NODE(bridge, "bridge") \
     OVS_NODE(port, "port") \
     OVS_NODE(interface, "interface") \
-    OVS_NODE(qos, "qos")
+    OVS_NODE(qos, "qos") \
+    OVS_NODE(mirror, "mirror")
 
 enum ovs_engine_node {
 #define OVS_NODE(NAME, NAME_STR) OVS_##NAME,
@@ -2326,6 +2330,203 @@  load_balancers_by_dp_cleanup(struct hmap *lbs)
     free(lbs);
 }
 
+/* Mirror Engine */
+struct ed_type_port_mirror {
+    struct shash ovs_mirrors;
+};
+
+static void *
+en_port_mirror_init(struct engine_node *node OVS_UNUSED,
+                    struct engine_arg *arg OVS_UNUSED)
+{
+    struct ed_type_port_mirror *port_mirror = xzalloc(sizeof *port_mirror);
+    ovn_port_mirror_init(&port_mirror->ovs_mirrors);
+    return port_mirror;
+}
+
+static void
+en_port_mirror_cleanup(void *data)
+{
+    struct ed_type_port_mirror *port_mirror = data;
+    ovn_port_mirror_destroy(&port_mirror->ovs_mirrors);
+}
+
+static void
+init_port_mirror_ctx(struct engine_node *node,
+                 struct ed_type_runtime_data *rt_data,
+                 struct ed_type_port_mirror *port_mirror_data,
+                 struct port_mirror_ctx *pm_ctx)
+{
+    struct ovsrec_open_vswitch_table *ovs_table =
+        (struct ovsrec_open_vswitch_table *)EN_OVSDB_GET(
+            engine_get_input("OVS_open_vswitch", node));
+    struct ovsrec_bridge_table *bridge_table =
+        (struct ovsrec_bridge_table *)EN_OVSDB_GET(
+            engine_get_input("OVS_bridge", node));
+    const char *chassis_id = get_ovs_chassis_id(ovs_table);
+    const struct ovsrec_bridge *br_int = get_br_int(bridge_table, ovs_table);
+
+    ovs_assert(br_int && chassis_id);
+    const struct sbrec_chassis *chassis = NULL;
+    struct ovsdb_idl_index *sbrec_chassis_by_name =
+        engine_ovsdb_node_get_index(
+                engine_get_input("SB_chassis", node),
+                "name");
+
+    if (chassis_id) {
+        chassis = chassis_lookup_by_name(sbrec_chassis_by_name, chassis_id);
+    }
+    ovs_assert(chassis);
+
+    struct ovsrec_port_table *port_table =
+        (struct ovsrec_port_table *)EN_OVSDB_GET(
+            engine_get_input("OVS_port", node));
+
+    struct ed_type_ovs_interface_shadow *iface_shadow =
+        engine_get_input_data("ovs_interface_shadow", node);
+
+    struct ovsrec_mirror_table *mirror_table =
+        (struct ovsrec_mirror_table *)EN_OVSDB_GET(
+            engine_get_input("OVS_mirror", node));
+
+    struct sbrec_port_binding_table *pb_table =
+        (struct sbrec_port_binding_table *)EN_OVSDB_GET(
+            engine_get_input("SB_port_binding", node));
+
+    struct sbrec_mirror_table *sb_mirror_table =
+        (struct sbrec_mirror_table *)EN_OVSDB_GET(
+            engine_get_input("SB_mirror", node));
+
+    struct shash_node *ovs_mirror_node, *ovs_mirror_next;
+    SHASH_FOR_EACH_SAFE (ovs_mirror_node, ovs_mirror_next,
+                           &port_mirror_data->ovs_mirrors) {
+        shash_delete(&port_mirror_data->ovs_mirrors, ovs_mirror_node);
+    }
+
+    const struct ovsrec_mirror *ovsmirror = NULL;
+    OVSREC_MIRROR_TABLE_FOR_EACH (ovsmirror, mirror_table) {
+       shash_add(&port_mirror_data->ovs_mirrors, ovsmirror->name, ovsmirror);
+    }
+
+    pm_ctx->ovs_idl_txn = engine_get_context()->ovs_idl_txn;
+    pm_ctx->port_table = port_table;
+    pm_ctx->iface_table = iface_shadow->iface_table;
+    pm_ctx->mirror_table = mirror_table;
+    pm_ctx->port_binding_table = pb_table;
+    pm_ctx->sb_mirror_table = sb_mirror_table;
+    pm_ctx->br_int = br_int;
+    pm_ctx->chassis_rec = chassis;
+    pm_ctx->bridge_table = bridge_table;
+    pm_ctx->ovs_table = ovs_table;
+    pm_ctx->ovs_mirrors = &port_mirror_data->ovs_mirrors;
+    pm_ctx->local_bindings = &rt_data->lbinding_data.bindings;
+}
+
+static void
+en_port_mirror_run(struct engine_node *node, void *data)
+{
+    struct port_mirror_ctx pm_ctx;
+    struct ed_type_port_mirror *port_mirror_data = data;
+    struct ed_type_runtime_data *rt_data =
+        engine_get_input_data("runtime_data", node);
+
+    init_port_mirror_ctx(node, rt_data, port_mirror_data, &pm_ctx);
+
+    ovn_port_mirror_run(&pm_ctx);
+    engine_set_node_state(node, EN_UPDATED);
+}
+
+static bool
+port_mirror_runtime_data_handler(struct engine_node *node, void *data)
+{
+    struct ed_type_runtime_data *rt_data =
+        engine_get_input_data("runtime_data", node);
+
+    /* There is no tracked data. Fall back to full recompute of port_mirror */
+    if (!rt_data->tracked) {
+        return false;
+    }
+
+    struct hmap *tracked_dp_bindings = &rt_data->tracked_dp_bindings;
+    if (hmap_is_empty(tracked_dp_bindings)) {
+        return true;
+    }
+
+    struct port_mirror_ctx pm_ctx;
+    struct ed_type_port_mirror *port_mirror_data = data;
+    init_port_mirror_ctx(node, rt_data, port_mirror_data, &pm_ctx);
+
+    struct tracked_datapath *tdp;
+    HMAP_FOR_EACH (tdp, node, tracked_dp_bindings) {
+        if (tdp->tracked_type != TRACKED_RESOURCE_UPDATED) {
+            /* Fall back to full recompute when a local datapath
+             * is added or deleted. */
+            return false;
+        }
+
+        struct shash_node *shash_node;
+        SHASH_FOR_EACH (shash_node, &tdp->lports) {
+            struct tracked_lport *lport = shash_node->data;
+            bool removed =
+                lport->tracked_type == TRACKED_RESOURCE_REMOVED ? true: false;
+            if (!ovn_port_mirror_handle_lport(lport->pb, removed, &pm_ctx)) {
+                return false;
+            }
+        }
+    }
+
+    engine_set_node_state(node, EN_UPDATED);
+    return true;
+}
+
+static bool
+port_mirror_port_binding_handler(struct engine_node *node, void *data)
+{
+    struct port_mirror_ctx pm_ctx;
+    struct ed_type_port_mirror *port_mirror_data = data;
+    struct ed_type_runtime_data *rt_data =
+        engine_get_input_data("runtime_data", node);
+
+    init_port_mirror_ctx(node, rt_data, port_mirror_data, &pm_ctx);
+
+    /* handle port binding updates (i.,e when the mirror column
+     * of port_binding is updated)
+     */
+    const struct sbrec_port_binding *pb;
+    SBREC_PORT_BINDING_TABLE_FOR_EACH_TRACKED (pb,
+                                               pm_ctx.port_binding_table) {
+        bool removed = sbrec_port_binding_is_deleted(pb);
+        if (!ovn_port_mirror_handle_lport(pb, removed, &pm_ctx)) {
+            return false;
+        }
+    }
+
+    engine_set_node_state(node, EN_UPDATED);
+    return true;
+
+}
+
+static bool
+port_mirror_sb_mirror_handler(struct engine_node *node, void *data)
+{
+    struct port_mirror_ctx pm_ctx;
+    struct ed_type_port_mirror *port_mirror_data = data;
+    struct ed_type_runtime_data *rt_data =
+        engine_get_input_data("runtime_data", node);
+
+    init_port_mirror_ctx(node, rt_data, port_mirror_data, &pm_ctx);
+
+    /* handle sb mirror updates
+     */
+    if (!ovn_port_mirror_handle_update(&pm_ctx)) {
+        return false;
+    }
+
+    engine_set_node_state(node, EN_UPDATED);
+    return true;
+
+}
+
 /* Engine node which is used to handle the Non VIF data like
  *   - OVS patch ports
  *   - Tunnel ports and the related chassis information.
@@ -3609,6 +3810,7 @@  main(int argc, char *argv[])
     ENGINE_NODE_WITH_CLEAR_TRACK_DATA(addr_sets, "addr_sets");
     ENGINE_NODE_WITH_CLEAR_TRACK_DATA(port_groups, "port_groups");
     ENGINE_NODE(northd_options, "northd_options");
+    ENGINE_NODE(port_mirror, "port_mirror");
 
 #define SB_NODE(NAME, NAME_STR) ENGINE_NODE_SB(NAME, NAME_STR);
     SB_NODES
@@ -3763,6 +3965,22 @@  main(int argc, char *argv[])
     engine_add_input(&en_flow_output, &en_pflow_output,
                      flow_output_pflow_output_handler);
 
+    engine_add_input(&en_port_mirror, &en_ovs_open_vswitch, NULL);
+    engine_add_input(&en_port_mirror, &en_ovs_bridge, NULL);
+    engine_add_input(&en_port_mirror, &en_ovs_mirror, NULL);
+    engine_add_input(&en_port_mirror, &en_sb_chassis, NULL);
+    engine_add_input(&en_port_mirror, &en_ovs_port, engine_noop_handler);
+    engine_add_input(&en_port_mirror, &en_ovs_interface_shadow,
+                     engine_noop_handler);
+    engine_add_input(&en_flow_output, &en_port_mirror,
+                     engine_noop_handler);
+    engine_add_input(&en_port_mirror, &en_runtime_data,
+                     port_mirror_runtime_data_handler);
+    engine_add_input(&en_port_mirror, &en_sb_mirror,
+                     port_mirror_sb_mirror_handler);
+    engine_add_input(&en_port_mirror, &en_sb_port_binding,
+                     port_mirror_port_binding_handler);
+
     struct engine_arg engine_arg = {
         .sb_idl = ovnsb_idl_loop.idl,
         .ovs_idl = ovs_idl_loop.idl,
@@ -4082,6 +4300,8 @@  main(int argc, char *argv[])
                                     ovnsb_idl_loop.idl),
                                 sbrec_sb_global_table_get(ovnsb_idl_loop.idl));
                         stopwatch_stop(BFD_RUN_STOPWATCH_NAME, time_msec());
+                    } else {
+                        engine_run(false);
                     }
 
                     runtime_data = engine_get_data(&en_runtime_data);
diff --git a/northd/en-northd.c b/northd/en-northd.c
index 4907a1ff2..74cac1b8e 100644
--- a/northd/en-northd.c
+++ b/northd/en-northd.c
@@ -78,6 +78,8 @@  void en_northd_run(struct engine_node *node, void *data)
         EN_OVSDB_GET(engine_get_input("NB_acl", node));
     input_data.nbrec_static_mac_binding_table =
         EN_OVSDB_GET(engine_get_input("NB_static_mac_binding", node));
+    input_data.nbrec_mirror_table =
+        EN_OVSDB_GET(engine_get_input("NB_mirror", node));
 
     input_data.sbrec_sb_global_table =
         EN_OVSDB_GET(engine_get_input("SB_sb_global", node));
@@ -111,6 +113,8 @@  void en_northd_run(struct engine_node *node, void *data)
         EN_OVSDB_GET(engine_get_input("SB_chassis_private", node));
     input_data.sbrec_static_mac_binding_table =
         EN_OVSDB_GET(engine_get_input("SB_static_mac_binding", node));
+    input_data.sbrec_mirror_table =
+        EN_OVSDB_GET(engine_get_input("SB_mirror", node));
 
     northd_run(&input_data, data,
                eng_ctx->ovnnb_idl_txn,
diff --git a/northd/inc-proc-northd.c b/northd/inc-proc-northd.c
index 54e0ad3b0..ac27a730e 100644
--- a/northd/inc-proc-northd.c
+++ b/northd/inc-proc-northd.c
@@ -50,6 +50,7 @@  VLOG_DEFINE_THIS_MODULE(inc_proc_northd);
     NB_NODE(acl, "acl") \
     NB_NODE(logical_router, "logical_router") \
     NB_NODE(qos, "qos") \
+    NB_NODE(mirror, "mirror") \
     NB_NODE(meter, "meter") \
     NB_NODE(meter_band, "meter_band") \
     NB_NODE(logical_router_port, "logical_router_port") \
@@ -92,6 +93,7 @@  VLOG_DEFINE_THIS_MODULE(inc_proc_northd);
     SB_NODE(logical_flow, "logical_flow") \
     SB_NODE(logical_dp_group, "logical_DP_group") \
     SB_NODE(multicast_group, "multicast_group") \
+    SB_NODE(mirror, "mirror") \
     SB_NODE(meter, "meter") \
     SB_NODE(meter_band, "meter_band") \
     SB_NODE(datapath_binding, "datapath_binding") \
@@ -172,6 +174,7 @@  void inc_proc_northd_init(struct ovsdb_idl_loop *nb,
     engine_add_input(&en_northd, &en_nb_acl, NULL);
     engine_add_input(&en_northd, &en_nb_logical_router, NULL);
     engine_add_input(&en_northd, &en_nb_qos, NULL);
+    engine_add_input(&en_northd, &en_nb_mirror, NULL);
     engine_add_input(&en_northd, &en_nb_meter, NULL);
     engine_add_input(&en_northd, &en_nb_meter_band, NULL);
     engine_add_input(&en_northd, &en_nb_logical_router_port, NULL);
@@ -194,6 +197,7 @@  void inc_proc_northd_init(struct ovsdb_idl_loop *nb,
     engine_add_input(&en_northd, &en_sb_address_set, NULL);
     engine_add_input(&en_northd, &en_sb_port_group, NULL);
     engine_add_input(&en_northd, &en_sb_logical_dp_group, NULL);
+    engine_add_input(&en_northd, &en_sb_mirror, NULL);
     engine_add_input(&en_northd, &en_sb_meter, NULL);
     engine_add_input(&en_northd, &en_sb_meter_band, NULL);
     engine_add_input(&en_northd, &en_sb_datapath_binding, NULL);
diff --git a/northd/northd.c b/northd/northd.c
index 7e2681865..74314b5dc 100644
--- a/northd/northd.c
+++ b/northd/northd.c
@@ -3214,6 +3214,51 @@  ovn_port_update_sbrec_chassis(
     free(requested_chassis_sb);
 }
 
+static void
+sbrec_port_binding_update_mirror_rules(struct northd_input *input_data,
+                                       const struct ovn_port *op)
+{
+    size_t i = 0;
+    if (op->sb->n_mirror_rules > op->nbsp->n_mirror_rules) {
+        /* Needs deletion in SB */
+        struct shash nb_mirror_rules = SHASH_INITIALIZER(&nb_mirror_rules);
+        for (i = 0; i < op->nbsp->n_mirror_rules; i++) {
+            shash_add(&nb_mirror_rules,
+                                 op->nbsp->mirror_rules[i]->name,
+                                 op->nbsp->mirror_rules[i]);
+        }
+
+        for (i = 0; i < op->sb->n_mirror_rules; i++) {
+            if (!shash_find(&nb_mirror_rules,
+                           op->sb->mirror_rules[i]->name)) {
+                    sbrec_port_binding_update_mirror_rules_delvalue(op->sb,
+                                                 op->sb->mirror_rules[i]);
+            }
+        }
+
+        struct shash_node *node, *next;
+        SHASH_FOR_EACH_SAFE (node, next, &nb_mirror_rules) {
+            shash_delete(&nb_mirror_rules, node);
+        }
+        shash_destroy(&nb_mirror_rules);
+
+    } else {
+        /* Needs addition in SB */
+        for (i = 0; i < op->nbsp->n_mirror_rules; i++) {
+            const struct sbrec_mirror *sb_mirror;
+            SBREC_MIRROR_TABLE_FOR_EACH (sb_mirror,
+                                         input_data->sbrec_mirror_table) {
+                if (!strcmp(sb_mirror->name,
+                            op->nbsp->mirror_rules[i]->name)) {
+                    /* Add the value to SB */
+                    sbrec_port_binding_update_mirror_rules_addvalue(op->sb,
+                                                                    sb_mirror);
+                }
+            }
+        }
+    }
+}
+
 static void
 ovn_port_update_sbrec(struct northd_input *input_data,
                       struct ovsdb_idl_txn *ovnsb_txn,
@@ -3573,6 +3618,17 @@  ovn_port_update_sbrec(struct northd_input *input_data,
         }
         sbrec_port_binding_set_external_ids(op->sb, &ids);
         smap_destroy(&ids);
+
+        if (!op->nbsp->n_mirror_rules) {
+            /* Nothing is set. Clear mirror_rules from pb. */
+            sbrec_port_binding_set_mirror_rules(op->sb, NULL, 0);
+        } else {
+            /* Check if SB DB update needed */
+            if (op->nbsp->n_mirror_rules != op->sb->n_mirror_rules) {
+                sbrec_port_binding_update_mirror_rules(input_data, op);
+            }
+        }
+
     }
     if (op->tunnel_key != op->sb->tunnel_key) {
         sbrec_port_binding_set_tunnel_key(op->sb, op->tunnel_key);
@@ -14877,6 +14933,85 @@  sync_meters(struct northd_input *input_data,
     shash_destroy(&sb_meters);
 }
 
+static bool
+mirror_needs_update(const struct nbrec_mirror *nb_mirror,
+                  const struct sbrec_mirror *sb_mirror)
+{
+
+    if (nb_mirror->index != sb_mirror->index) {
+        return true;
+    } else if (strcmp(nb_mirror->sink, sb_mirror->sink)) {
+        return true;
+    } else if (strcmp(nb_mirror->type, sb_mirror->type)) {
+        return true;
+    } else if (strcmp(nb_mirror->filter, sb_mirror->filter)) {
+        return true;
+    }
+
+    return false;
+}
+
+static void
+sync_mirrors_iterate_nb_mirror(struct ovsdb_idl_txn *ovnsb_txn,
+                             const char *mirror_name,
+                             const struct nbrec_mirror *nb_mirror,
+                             struct shash *sb_mirrors,
+                             struct sset *used_sb_mirrors)
+{
+    const struct sbrec_mirror *sb_mirror;
+    bool new_sb_mirror = false;
+
+    sb_mirror = shash_find_data(sb_mirrors, mirror_name);
+    if (!sb_mirror) {
+        sb_mirror = sbrec_mirror_insert(ovnsb_txn);
+        sbrec_mirror_set_name(sb_mirror, mirror_name);
+        shash_add(sb_mirrors, sb_mirror->name, sb_mirror);
+        new_sb_mirror = true;
+    }
+    sset_add(used_sb_mirrors, mirror_name);
+
+    if ((new_sb_mirror) || mirror_needs_update(nb_mirror, sb_mirror)) {
+        sbrec_mirror_set_filter(sb_mirror,nb_mirror->filter);
+        sbrec_mirror_set_index(sb_mirror, nb_mirror->index);
+        sbrec_mirror_set_sink(sb_mirror, nb_mirror->sink);
+        sbrec_mirror_set_type(sb_mirror, nb_mirror->type);
+    }
+}
+
+static void
+sync_mirrors(struct northd_input *input_data,
+            struct ovsdb_idl_txn *ovnsb_txn)
+{
+    struct shash sb_mirrors = SHASH_INITIALIZER(&sb_mirrors);
+    struct sset used_sb_mirrors = SSET_INITIALIZER(&used_sb_mirrors);
+
+    const struct sbrec_mirror *sb_mirror;
+    SBREC_MIRROR_TABLE_FOR_EACH (sb_mirror, input_data->sbrec_mirror_table) {
+        shash_add(&sb_mirrors, sb_mirror->name, sb_mirror);
+    }
+
+    const struct nbrec_mirror *nb_mirror;
+    NBREC_MIRROR_TABLE_FOR_EACH (nb_mirror, input_data->nbrec_mirror_table) {
+        sync_mirrors_iterate_nb_mirror(ovnsb_txn, nb_mirror->name, nb_mirror,
+                                     &sb_mirrors, &used_sb_mirrors);
+    }
+
+    const char *used_mirror;
+    const char *used_mirror_next;
+    SSET_FOR_EACH_SAFE (used_mirror, used_mirror_next, &used_sb_mirrors) {
+        shash_find_and_delete(&sb_mirrors, used_mirror);
+        sset_delete(&used_sb_mirrors, SSET_NODE_FROM_NAME(used_mirror));
+    }
+    sset_destroy(&used_sb_mirrors);
+
+    struct shash_node *node, *next;
+    SHASH_FOR_EACH_SAFE (node, next, &sb_mirrors) {
+        sbrec_mirror_delete(node->data);
+        shash_delete(&sb_mirrors, node);
+    }
+    shash_destroy(&sb_mirrors);
+}
+
 /*
  * struct 'dns_info' is used to sync the DNS records between OVN Northbound db
  * and Southbound db.
@@ -15497,6 +15632,7 @@  ovnnb_db_run(struct northd_input *input_data,
     sync_address_sets(input_data, ovnsb_txn, &data->datapaths);
     sync_port_groups(input_data, ovnsb_txn, &data->port_groups);
     sync_meters(input_data, ovnsb_txn, &data->meter_groups);
+    sync_mirrors(input_data, ovnsb_txn);
     sync_dns_entries(input_data, ovnsb_txn, &data->datapaths);
     cleanup_stale_fdb_entries(input_data, &data->datapaths);
     stopwatch_stop(CLEAR_LFLOWS_CTX_STOPWATCH_NAME, time_msec());
diff --git a/northd/northd.h b/northd/northd.h
index d9856af97..8a4f6a428 100644
--- a/northd/northd.h
+++ b/northd/northd.h
@@ -34,6 +34,7 @@  struct northd_input {
     const struct nbrec_acl_table *nbrec_acl_table;
     const struct nbrec_static_mac_binding_table
         *nbrec_static_mac_binding_table;
+    const struct nbrec_mirror_table *nbrec_mirror_table;
 
     /* Southbound table references */
     const struct sbrec_sb_global_table *sbrec_sb_global_table;
@@ -53,6 +54,7 @@  struct northd_input {
     const struct sbrec_chassis_private_table *sbrec_chassis_private_table;
     const struct sbrec_static_mac_binding_table
         *sbrec_static_mac_binding_table;
+    const struct sbrec_mirror_table *sbrec_mirror_table;
 
     /* Indexes */
     struct ovsdb_idl_index *sbrec_chassis_by_name;
diff --git a/ovn-nb.ovsschema b/ovn-nb.ovsschema
index 174364c8b..01de55222 100644
--- a/ovn-nb.ovsschema
+++ b/ovn-nb.ovsschema
@@ -1,7 +1,7 @@ 
 {
     "name": "OVN_Northbound",
-    "version": "6.3.0",
-    "cksum": "4042813038 31869",
+    "version": "6.4.0",
+    "cksum": "589874483 33352",
     "tables": {
         "NB_Global": {
             "columns": {
@@ -132,6 +132,11 @@ 
                                             "refType": "weak"},
                                  "min": 0,
                                  "max": 1}},
+                "mirror_rules": {"type": {"key": {"type": "uuid",
+                                          "refTable": "Mirror",
+                                          "refType": "weak"},
+                                  "min": 0,
+                                  "max": "unlimited"}},
                 "ha_chassis_group": {
                     "type": {"key": {"type": "uuid",
                                      "refTable": "HA_Chassis_Group",
@@ -301,6 +306,28 @@ 
                     "type": {"key": "string", "value": "string",
                              "min": 0, "max": "unlimited"}}},
             "isRoot": false},
+        "Mirror": {
+            "columns": {
+                "name": {"type": "string"},
+                "filter": {"type": {"key": {"type": "string",
+                                            "enum": ["set", ["from-lport",
+                                                             "to-lport",
+                                                             "both"]]}}},
+                "sink":{"type": "string"},
+                "type": {"type": {"key": {"type": "string",
+                                            "enum": ["set", ["gre",
+                                                             "erspan"]]}}},
+                "index": {"type": "integer"},
+                "src": {"type": {"key": {"type": "uuid",
+                                           "refTable": "Logical_Switch_Port",
+                                           "refType": "weak"},
+                                   "min": 0,
+                                   "max": "unlimited"}},
+                "external_ids": {
+                    "type": {"key": "string", "value": "string",
+                             "min": 0, "max": "unlimited"}}},
+            "indexes": [["name"]],
+            "isRoot": true},
         "Meter": {
             "columns": {
                 "name": {"type": "string"},
diff --git a/ovn-nb.xml b/ovn-nb.xml
index 7fe88af27..d410301e7 100644
--- a/ovn-nb.xml
+++ b/ovn-nb.xml
@@ -1554,6 +1554,11 @@ 
       </column>
     </group>
 
+    <column name="mirror_rules">
+        Mirror rules that apply to logical switch port which is the source.
+        Please see the <ref table="Mirror"/> table.
+    </column>
+
     <column name="ha_chassis_group">
       References a row in the OVN Northbound database's
       <ref table="HA_Chassis_Group" db="OVN_Northbound"/> table.
@@ -2491,6 +2496,58 @@ 
     </column>
   </table>
 
+  <table name="Mirror" title="Mirror Entry">
+    <p>
+      Each row in this table represents one Mirror that can be used for
+      port mirroring. These Mirrors are referenced by the
+      <ref column="mirror_rules" table="Logical_Switch_Port"/> column in
+      the <ref table="Logical_Switch_Port"/> table.
+    </p>
+
+    <column name="name">
+      <p>
+        Represents the name of the mirror.
+      </p>
+    </column>
+
+    <column name="filter">
+      <p>
+        The value of this field represents selection criteria of the mirror.
+      </p>
+    </column>
+
+    <column name="sink">
+      <p>
+        The value of this field represents the destination/sink of the mirror.
+      </p>
+    </column>
+
+    <column name="type">
+      <p>
+        The value of this field represents the type of the tunnel used for
+        sending the mirrored packets
+      </p>
+    </column>
+
+    <column name="index">
+      <p>
+        The value of this field represents the key/idx depending on the
+        tunnel type configured
+      </p>
+    </column>
+
+    <column name="src">
+      <p>
+          The value of this field represents the source port for the mirror.
+          Please see the <ref table="Logical_Switch_Port"/> table.
+      </p>
+    </column>
+
+    <column name="external_ids">
+      See <em>External IDs</em> at the beginning of this document.
+    </column>
+  </table>
+
   <table name="Meter" title="Meter entry">
     <p>
       Each row in this table represents a meter that can be used for QoS or
diff --git a/ovn-sb.ovsschema b/ovn-sb.ovsschema
index 576ebbdeb..45431a18b 100644
--- a/ovn-sb.ovsschema
+++ b/ovn-sb.ovsschema
@@ -1,7 +1,7 @@ 
 {
     "name": "OVN_Southbound",
-    "version": "20.25.0",
-    "cksum": "53184112 28845",
+    "version": "20.26.0",
+    "cksum": "3740716587 29844",
     "tables": {
         "SB_Global": {
             "columns": {
@@ -142,6 +142,20 @@ 
             "indexes": [["datapath", "tunnel_key"],
                         ["datapath", "name"]],
             "isRoot": true},
+        "Mirror": {
+            "columns": {
+                "name": {"type": "string"},
+                "filter": {"type": {"key": {"type": "string",
+                                            "enum": ["set",
+                                                     ["from-lport",
+                                                      "to-lport","both"]]}}},
+                "sink":{"type": "string"},
+                "type": {"type": {"key": {"type": "string",
+                                            "enum": ["set",
+                                                     ["gre", "erspan"]]}}},
+                "index": {"type": "integer"}},
+            "indexes": [["name"]],
+            "isRoot": true},
         "Meter": {
             "columns": {
                 "name": {"type": "string"},
@@ -230,6 +244,11 @@ 
                                                       "refTable": "Encap",
                                                       "refType": "weak"},
                                     "min": 0, "max": "unlimited"}},
+                "mirror_rules": {"type": {"key": {"type": "uuid",
+                                          "refTable": "Mirror",
+                                          "refType": "weak"},
+                                  "min": 0,
+                                  "max": "unlimited"}},
                 "mac": {"type": {"key": "string",
                                  "min": 0,
                                  "max": "unlimited"}},
diff --git a/ovn-sb.xml b/ovn-sb.xml
index 37a709f83..24af40b7f 100644
--- a/ovn-sb.xml
+++ b/ovn-sb.xml
@@ -2742,6 +2742,47 @@  tcp.flags = RST;
     </column>
   </table>
 
+  <table name="Mirror" title="Mirror Entry">
+    <p>
+      Each row in this table represents one Mirror that can be used for
+      port mirroring. These Mirrors are referenced by the
+      <ref column="mirror_rules" table="Port_Binding"/> column in
+      the <ref table="Port_Binding"/> table.
+    </p>
+
+    <column name="name">
+      <p>
+        Represents the name of the mirror.
+      </p>
+    </column>
+
+    <column name="filter">
+      <p>
+        The value of this field represents selection criteria of the mirror.
+      </p>
+    </column>
+
+    <column name="sink">
+      <p>
+        The value of this field represents the destination/sink of the mirror.
+      </p>
+    </column>
+
+    <column name="type">
+      <p>
+        The value of this field represents the type of the tunnel used for
+        sending the mirrored packets
+      </p>
+    </column>
+
+    <column name="index">
+      <p>
+        The value of this field represents the key/idx depending on the
+        tunnel type configured
+      </p>
+    </column>
+  </table>
+
   <table name="Meter" title="Meter entry">
     <p>
       Each row in this table represents a meter that can be used for QoS or
@@ -3244,6 +3285,11 @@  tcp.flags = RST;
       </column>
     </group>
 
+    <column name="mirror_rules">
+        Mirror rules that apply to the port binding.
+        Please see the <ref table="Mirror"/> table.
+    </column>
+
     <group title="Patch Options">
       <p>
         These options apply to logical ports with <ref column="type"/> of
diff --git a/tests/ovn-nbctl.at b/tests/ovn-nbctl.at
index 726efa6f4..8f6ee6097 100644
--- a/tests/ovn-nbctl.at
+++ b/tests/ovn-nbctl.at
@@ -435,6 +435,101 @@  AT_CHECK([ovn-nbctl meter-list], [0], [dnl
 
 dnl ---------------------------------------------------------------------
 
+OVN_NBCTL_TEST([ovn_nbctl_mirrors], [mirrors], [
+AT_CHECK([ovn-nbctl mirror-add mirror1 gre 0 from-lport 10.10.10.1])
+AT_CHECK([ovn-nbctl mirror-add mirror2 erspan 1 both 10.10.10.2])
+AT_CHECK([ovn-nbctl mirror-add mirror3 gre 2 to-lport 10.10.10.3])
+AT_CHECK([ovn-nbctl ls-add sw0])
+AT_CHECK([ovn-nbctl lsp-add sw0 sw0-port1])
+AT_CHECK([ovn-nbctl lsp-add sw0 sw0-port2])
+AT_CHECK([ovn-nbctl lsp-add sw0 sw0-port3])
+
+dnl Add duplicate mirror name
+AT_CHECK([ovn-nbctl mirror-add mirror1 gre 0 from-lport 10.10.10.5], [1], [], [stderr])
+AT_CHECK([grep 'already exists' stderr], [0], [ignore])
+
+dnl Add mirror with invalid sink port
+AT_CHECK([ovn-nbctl mirror-add mirror1 gre 0 from-lport sw0-port4], [1], [], [stderr])
+AT_CHECK([grep 'already exists' stderr], [0], [ignore])
+
+dnl Attach source port to mirror
+AT_CHECK([ovn-nbctl lsp-attach-mirror sw0-port1 mirror3])
+
+dnl Attach one more source port to mirror
+AT_CHECK([ovn-nbctl lsp-attach-mirror sw0-port3 mirror3])
+
+dnl Attach invalid source port to mirror
+AT_CHECK([ovn-nbctl lsp-attach-mirror sw0-port4 mirror3], [1], [], [stderr])
+AT_CHECK([grep 'port name not found' stderr], [0], [ignore])
+
+dnl Detach one source port from mirror
+AT_CHECK([ovn-nbctl lsp-detach-mirror sw0-port3 mirror3])
+
+AT_CHECK([ovn-nbctl mirror-list], [0], [dnl
+mirror1:
+  Type     :  gre
+  Sink     :  10.10.10.1
+  Filter   :  from-lport
+  Index/Key:  0
+  Sources  :  None attached
+mirror2:
+  Type     :  erspan
+  Sink     :  10.10.10.2
+  Filter   :  both
+  Index/Key:  1
+  Sources  :  None attached
+mirror3:
+  Type     :  gre
+  Sink     :  10.10.10.3
+  Filter   :  to-lport
+  Index/Key:  2
+  Sources  :  sw0-port1
+])
+
+dnl Detach another source port from mirror
+AT_CHECK([ovn-nbctl lsp-detach-mirror sw0-port1 mirror3])
+
+dnl Delete a single mirror.
+AT_CHECK([ovn-nbctl mirror-del mirror3])
+
+AT_CHECK([ovn-nbctl mirror-list], [0], [dnl
+mirror1:
+  Type     :  gre
+  Sink     :  10.10.10.1
+  Filter   :  from-lport
+  Index/Key:  0
+  Sources  :  None attached
+mirror2:
+  Type     :  erspan
+  Sink     :  10.10.10.2
+  Filter   :  both
+  Index/Key:  1
+  Sources  :  None attached
+])
+
+dnl Delete another mirror.
+AT_CHECK([ovn-nbctl mirror-del mirror2])
+
+dnl Update the Sink address
+AT_CHECK([ovn-nbctl set mirror . sink=192.168.1.13])
+
+AT_CHECK([ovn-nbctl mirror-list], [0], [dnl
+mirror1:
+  Type     :  gre
+  Sink     :  192.168.1.13
+  Filter   :  from-lport
+  Index/Key:  0
+  Sources  :  None attached
+])
+
+dnl Delete all mirrors and remove switch
+AT_CHECK([ovn-nbctl mirror-del])
+AT_CHECK([ovn-nbctl ls-del sw0])
+AT_CHECK([ovn-nbctl mirror-list], [0], [dnl
+])])
+
+dnl ---------------------------------------------------------------------
+
 OVN_NBCTL_TEST([ovn_nbctl_nats], [NATs], [
 AT_CHECK([ovn-nbctl lr-add lr0])
 AT_CHECK([ovn-nbctl lr-nat-add lr0 snatt 30.0.0.2 192.168.1.2], [1], [],
diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
index 157f9f60c..b7d92b60c 100644
--- a/tests/ovn-northd.at
+++ b/tests/ovn-northd.at
@@ -2205,6 +2205,56 @@  check_meter_by_name NOT meter_me__${acl1} meter_me__${acl2}
 AT_CLEANUP
 ])
 
+OVN_FOR_EACH_NORTHD([
+AT_SETUP([Check NB-SB mirrors sync])
+AT_KEYWORDS([mirrors])
+ovn_start
+
+check ovn-nbctl --wait=sb mirror-add mirror1 erspan 0 both 10.10.10.2
+
+AT_CHECK([ovn-sbctl get Mirror . Sink], [0], [dnl
+"10.10.10.2"
+])
+
+AT_CHECK([ovn-sbctl get Mirror . type], [0], [dnl
+erspan
+])
+
+AT_CHECK([ovn-sbctl get Mirror . index], [0], [dnl
+0
+])
+
+AT_CHECK([ovn-sbctl get Mirror . filter], [0], [dnl
+both
+])
+
+check ovn-nbctl --wait=sb \
+    -- set mirror . sink=192.168.1.13 \
+    -- set mirror . type=gre \
+    -- set mirror . index=12 \
+    -- set mirror . filter=to-lport
+
+AT_CHECK([ovn-sbctl get Mirror . Sink], [0], [dnl
+"192.168.1.13"
+])
+
+AT_CHECK([ovn-sbctl get Mirror . type], [0], [dnl
+gre
+])
+
+AT_CHECK([ovn-sbctl get Mirror . index], [0], [dnl
+12
+])
+
+AT_CHECK([ovn-sbctl get Mirror . filter], [0], [dnl
+to-lport
+])
+
+check ovn-nbctl --wait=sb mirror-del
+
+AT_CLEANUP
+])
+
 OVN_FOR_EACH_NORTHD([
 AT_SETUP([ACL skip hints for stateless config])
 AT_KEYWORDS([acl])
diff --git a/tests/ovn.at b/tests/ovn.at
index bba2c9c1d..9c2905ea8 100644
--- a/tests/ovn.at
+++ b/tests/ovn.at
@@ -16082,6 +16082,165 @@  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"
+
+# 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
+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
+
+# 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
+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 MIRROR
+#
+# 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 - Currently checking only for GRE
+# MIRROR - Mirror name
+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_name=${11}
+    shift; shift; shift; shift; shift; shift; shift
+    shift; shift; shift; shift;
+
+    check ovn-nbctl mirror-add ${mirror_name} ${mirror_encap_type} 0 to-lport 192.168.1.12
+    check ovn-nbctl lsp-attach-mirror ls1-lp1 ${mirror_name}
+
+    # 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
+    if test X$exp_icmp_chksum != X; then
+        # 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
+            #echo "GRE"
+            local mirror=${remote_mac}${local_mac}08004500007e00004000402fb6e9c0a8010bc0a8010c2000655800000000${reply}
+        fi
+        echo $mirror >> br-phys_n1.expected
+    fi
+}
+
+# Set IPs
+rtr_l2_ip=$(ip_to_hex 172 16 1 1)
+l1_ip=$(ip_to_hex 192 168 1 2)
+
+# 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" mirror0
+
+OVN_CHECK_PACKETS_CONTAIN([hv1/br-phys_n1-tx.pcap], [br-phys_n1.expected])
+
+tcpdump -r hv1/br-phys_n1-tx.pcap
+
+echo "---------OVN NB Mirror-----"
+ovn-nbctl mirror-list
+
+echo "---------OVS Mirror----"
+ovs-vsctl list Mirror
+
+echo "-----------------------"
+echo "Delete vif1 so that OVN releases port binding"
+ovs-vsctl del-port br-int vif1
+
+echo "Verifying Mirror deletion in OVS"
+
+AT_CHECK([as hv1 ovs-vsctl list Mirror], [0], [dnl
+])
+
+OVN_CLEANUP([hv1], [hv2])
+AT_CLEANUP
+])
 
 OVN_FOR_EACH_NORTHD([
 AT_SETUP([Port Groups])
diff --git a/utilities/ovn-nbctl.c b/utilities/ovn-nbctl.c
index 3bbdbd998..a30b5e4f2 100644
--- a/utilities/ovn-nbctl.c
+++ b/utilities/ovn-nbctl.c
@@ -271,6 +271,16 @@  QoS commands:\n\
                             remove QoS rules from SWITCH\n\
   qos-list SWITCH           print QoS rules for SWITCH\n\
 \n\
+Mirror commands:\n\
+  mirror-add NAME TYPE INDEX FILTER IP\n\
+                            add a mirror with given name\n\
+                            specify TYPE 'gre' or 'erspan'\n\
+                            specify INDEX gre key / erpsan idx\n\
+                            specify FILTER for mirroring selection\n\
+                            specify Sink / Destination i.e Remote IP \n\
+  mirror-del [NAME]         remove mirrors\n\
+  mirror-list               print mirrors\n\
+\n\
 Meter commands:\n\
   [--fair]\n\
   meter-add NAME ACTION RATE UNIT [BURST]\n\
@@ -311,6 +321,8 @@  Logical switch port commands:\n\
                             set dhcpv6 options for PORT\n\
   lsp-get-dhcpv6-options PORT  get the dhcpv6 options for PORT\n\
   lsp-get-ls PORT           get the logical switch which the port belongs to\n\
+  lsp-attach-mirror PORT MIRROR   attach source PORT to the MIRROR\n\
+  lsp-detach-mirror PORT MIRROR   detach source PORT from the MIRROR\n\
 \n\
 Forwarding group commands:\n\
   [--liveness]\n\
@@ -1685,6 +1697,130 @@  nbctl_pre_lsp_type(struct ctl_context *ctx)
     ovsdb_idl_add_column(ctx->idl, &nbrec_logical_switch_port_col_type);
 }
 
+static void
+nbctl_pre_lsp_mirror(struct ctl_context *ctx)
+{
+    ovsdb_idl_add_column(ctx->idl, &nbrec_logical_switch_port_col_name);
+    ovsdb_idl_add_column(ctx->idl,
+                         &nbrec_logical_switch_port_col_mirror_rules);
+    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_name);
+    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_src);
+}
+
+static int
+mirror_cmp(const void *mirror1_, const void *mirror2_)
+{
+    const struct nbrec_mirror *const *mirror_1 = mirror1_;
+    const struct nbrec_mirror *const *mirror_2 = mirror2_;
+
+    const struct nbrec_mirror *mirror1 = *mirror_1;
+    const struct nbrec_mirror *mirror2 = *mirror_2;
+
+    return strcmp(mirror1->name,mirror2->name);
+}
+
+static char * OVS_WARN_UNUSED_RESULT
+mirror_by_name_or_uuid(struct ctl_context *ctx, const char *id,
+                    bool must_exist,
+                    const struct nbrec_mirror **mirror_p)
+{
+    const struct nbrec_mirror *mirror = NULL;
+    *mirror_p = NULL;
+
+    struct uuid mirror_uuid;
+    bool is_uuid = uuid_from_string(&mirror_uuid, id);
+    if (is_uuid) {
+        mirror = nbrec_mirror_get_for_uuid(ctx->idl, &mirror_uuid);
+    }
+
+    if (!mirror) {
+        NBREC_MIRROR_FOR_EACH (mirror, ctx->idl) {
+            if (!strcmp(mirror->name, id)) {
+                break;
+            }
+        }
+    }
+
+    if (!mirror && must_exist) {
+        return xasprintf("%s: mirror %s not found",
+                         id, is_uuid ? "UUID" : "name");
+    }
+
+    *mirror_p = mirror;
+    return NULL;
+}
+
+static void
+nbctl_lsp_attach_mirror(struct ctl_context *ctx)
+{
+    const char *port = ctx->argv[1];
+    const char *mirror_name = ctx->argv[2];
+    const struct nbrec_logical_switch_port *lsp = NULL;
+    const struct nbrec_mirror *mirror;
+
+    char *error;
+
+    error = lsp_by_name_or_uuid(ctx, port, true, &lsp);
+    if (error) {
+        ctx->error = error;
+        return;
+    }
+
+
+    /*check if a mirror rule actually exists on that name or not*/
+    error = mirror_by_name_or_uuid(ctx, mirror_name, true, &mirror);
+    if (error) {
+        ctx->error = error;
+        return;
+    }
+
+    /* Check if same mirror rule already exists for the lsp */
+    for (size_t i = 0; i < lsp->n_mirror_rules; i++) {
+        if (!mirror_cmp(&lsp->mirror_rules[i], &mirror)) {
+            bool may_exist = shash_find(&ctx->options, "--may-exist") != NULL;
+            if (!may_exist) {
+                ctl_error(ctx, "Same mirror already existed on the lsp %s.",
+                          ctx->argv[1]);
+                return;
+            }
+            return;
+        }
+    }
+
+    nbrec_logical_switch_port_update_mirror_rules_addvalue(lsp, mirror);
+    nbrec_mirror_update_src_addvalue(mirror,lsp);
+
+}
+
+static void
+nbctl_lsp_detach_mirror(struct ctl_context *ctx)
+{
+    const char *port = ctx->argv[1];
+    const char *mirror_name = ctx->argv[2];
+    const struct nbrec_logical_switch_port *lsp = NULL;
+    const struct nbrec_mirror *mirror;
+
+    char *error;
+
+    error = lsp_by_name_or_uuid(ctx, port, true, &lsp);
+    if (error) {
+        ctx->error = error;
+        return;
+    }
+
+
+    /*check if a mirror rule actually exists on that name or not*/
+    error = mirror_by_name_or_uuid(ctx, mirror_name, true, &mirror);
+    if (error) {
+        ctx->error = error;
+        return;
+    }
+
+    nbrec_logical_switch_port_update_mirror_rules_delvalue(lsp, mirror);
+    nbrec_mirror_update_src_delvalue(mirror,lsp);
+
+}
+
 static void
 nbctl_lsp_set_type(struct ctl_context *ctx)
 {
@@ -7239,6 +7375,202 @@  cmd_ha_ch_grp_set_chassis_prio(struct ctl_context *ctx)
     nbrec_ha_chassis_set_priority(ha_chassis, priority);
 }
 
+static char * OVS_WARN_UNUSED_RESULT
+parse_filter(const char *arg, const char **selection_p)
+{
+    /* Validate selection.  Only require the first letter. */
+    if (arg[0] == 't') {
+        *selection_p = "to-lport";
+    } else if (arg[0] == 'f') {
+        *selection_p = "from-lport";
+    } else if (arg[0] == 'b') {
+        *selection_p = "both";
+    } else {
+        *selection_p = NULL;
+        return xasprintf("%s: selection must be \"to-lport\" or "
+                         "\"from-lport\" or \"both\" ", arg);
+    }
+    return NULL;
+}
+
+static char * OVS_WARN_UNUSED_RESULT
+parse_type(const char *arg, const char **type_p)
+{
+    /* Validate type.  Only require the second letter. */
+    if (arg[0] == 'g') {
+        *type_p = "gre";
+    } else if (arg[0] == 'e') {
+        *type_p = "erspan";
+    } else {
+        *type_p = NULL;
+        return xasprintf("%s: type must be \"gre\" or "
+                         "\"erspan\"", arg);
+    }
+    return NULL;
+}
+
+static void
+nbctl_pre_mirror_add(struct ctl_context *ctx)
+{
+    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_name);
+    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_filter);
+    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_index);
+    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_sink);
+    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_type);
+}
+
+static void
+nbctl_mirror_add(struct ctl_context *ctx)
+{
+    const char *filter = NULL;
+    const char *sink_ip = NULL;
+    const char *type = NULL;
+    const char *name = NULL;
+    char *new_external_ip = NULL;
+    int64_t index;
+    char *error = NULL;
+    const struct nbrec_mirror *mirror_check = NULL;
+
+    /* Mirror Name */
+    name = ctx->argv[1];
+    NBREC_MIRROR_FOR_EACH (mirror_check, ctx->idl) {
+            if (!strcmp(mirror_check->name, name)) {
+                ctl_error(ctx, "Mirror with %s name already exists.",
+                          name);
+                return;
+            }
+        }
+
+    /* Tunnel Type - GRE/ERSPAN */
+    error = parse_type(ctx->argv[2], &type);
+    if (error) {
+        ctx->error = error;
+        return;
+    }
+
+    /* tunnel index / GRE key / ERSPAN idx */
+    index = atoi(ctx->argv[3]);
+
+    /* Filter for mirroring */
+    error = parse_filter(ctx->argv[4], &filter);
+    if (error) {
+        ctx->error = error;
+        return;
+    }
+
+    /* Destination / Sink details */
+    sink_ip = ctx->argv[5];
+
+    /* check if it is a valid ip */
+    new_external_ip = normalize_ipv4_addr_str(sink_ip);
+    if (!new_external_ip) {
+        new_external_ip = normalize_ipv6_addr_str(sink_ip);
+    }
+
+    if (new_external_ip) {
+        free(new_external_ip);
+    } else {
+        ctl_error(ctx, "Invalid sink ip: %s", sink_ip);
+        return;
+    }
+
+    /* Create the mirror. */
+    struct nbrec_mirror *mirror = nbrec_mirror_insert(ctx->txn);
+    nbrec_mirror_set_name(mirror, name);
+    nbrec_mirror_set_index(mirror, index);
+    nbrec_mirror_set_filter(mirror, filter);
+    nbrec_mirror_set_type(mirror, type);
+    nbrec_mirror_set_sink(mirror, sink_ip);
+
+}
+
+static void
+nbctl_pre_mirror_del(struct ctl_context *ctx)
+{
+    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_name);
+    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_src);
+}
+
+static void
+nbctl_mirror_del(struct ctl_context *ctx)
+{
+    const struct nbrec_mirror *mirror, *next;
+
+    /* If a name is not specified, delete all mirrors. */
+    if (ctx->argc == 1) {
+        NBREC_MIRROR_FOR_EACH_SAFE (mirror, next, ctx->idl) {
+            nbrec_mirror_delete(mirror);
+        }
+        return;
+    }
+
+    /* Remove the matching mirror. */
+    NBREC_MIRROR_FOR_EACH (mirror, ctx->idl) {
+        if (strcmp(ctx->argv[1], mirror->name)) {
+            continue;
+        }
+        nbrec_mirror_delete(mirror);
+        return;
+    }
+}
+
+static void
+nbctl_pre_mirror_list(struct ctl_context *ctx)
+{
+    ovsdb_idl_add_column(ctx->idl, &nbrec_logical_switch_port_col_name);
+    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_name);
+    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_filter);
+    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_index);
+    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_sink);
+    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_type);
+    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_src);
+}
+
+static void
+nbctl_mirror_list(struct ctl_context *ctx)
+{
+
+    const struct nbrec_mirror **mirrors = NULL;
+    const struct nbrec_mirror *mirror;
+    size_t n_capacity = 0;
+    size_t n_mirrors = 0;
+
+    NBREC_MIRROR_FOR_EACH (mirror, ctx->idl) {
+        if (n_mirrors == n_capacity) {
+            mirrors = x2nrealloc(mirrors, &n_capacity, sizeof *mirrors);
+        }
+
+        mirrors[n_mirrors] = mirror;
+        n_mirrors++;
+    }
+
+    if (n_mirrors) {
+        qsort(mirrors, n_mirrors, sizeof *mirrors, mirror_cmp);
+    }
+
+    for (size_t i = 0; i < n_mirrors; i++) {
+        mirror = mirrors[i];
+        ds_put_format(&ctx->output, "%s:\n", mirror->name);
+        /* print all the values */
+        ds_put_format(&ctx->output, "  Type     :  %s\n", mirror->type);
+        ds_put_format(&ctx->output, "  Sink     :  %s\n", mirror->sink);
+        ds_put_format(&ctx->output, "  Filter   :  %s\n", mirror->filter);
+        ds_put_format(&ctx->output, "  Index/Key:  %ld\n",
+                                                 (long int)mirror->index);
+        ds_put_cstr(&ctx->output,   "  Sources  :");
+        if (mirror->n_src > 0) {
+            for (size_t j = 0; j < mirror->n_src; j++) {
+                ds_put_format(&ctx->output, "  %s", mirror->src[j]->name);
+            }
+        } else {
+            ds_put_cstr(&ctx->output, "  None attached");
+        }
+        ds_put_cstr(&ctx->output, "\n");
+    }
+
+    free(mirrors);
+}
+
 static const struct ctl_table_class tables[NBREC_N_TABLES] = {
     [NBREC_TABLE_DHCP_OPTIONS].row_ids
     = {{&nbrec_logical_switch_port_col_name, NULL,
@@ -7332,6 +7664,15 @@  static const struct ctl_command_syntax nbctl_commands[] = {
     { "qos-list", 1, 1, "SWITCH", nbctl_pre_qos_list, nbctl_qos_list,
       NULL, "", RO },
 
+    /* mirror commands. */
+    { "mirror-add", 5, 5,
+      "NAME TYPE INDEX FILTER IP",
+      nbctl_pre_mirror_add, nbctl_mirror_add, NULL, "--may-exist", RW },
+    { "mirror-del", 0, 1, "[NAME]",
+      nbctl_pre_mirror_del, nbctl_mirror_del, NULL, "", RW },
+    { "mirror-list", 0, 0, "", nbctl_pre_mirror_list, nbctl_mirror_list,
+      NULL, "", RO },
+
     /* meter commands. */
     { "meter-add", 4, 5, "NAME ACTION RATE UNIT [BURST]", nbctl_pre_meter_add,
       nbctl_meter_add, NULL, "--fair,--may-exist", RW },
@@ -7386,6 +7727,10 @@  static const struct ctl_command_syntax nbctl_commands[] = {
       nbctl_lsp_get_dhcpv6_options, NULL, "", RO },
     { "lsp-get-ls", 1, 1, "PORT", nbctl_pre_lsp_get_ls, nbctl_lsp_get_ls,
       NULL, "", RO },
+    { "lsp-attach-mirror", 2, 2, "PORT MIRROR", nbctl_pre_lsp_mirror,
+      nbctl_lsp_attach_mirror, NULL, "", RW },
+    { "lsp-detach-mirror", 2, 2, "PORT MIRROR", nbctl_pre_lsp_mirror,
+      nbctl_lsp_detach_mirror, NULL, "", RW },
 
     /* forwarding group commands. */
     { "fwd-group-add", 4, INT_MAX, "SWITCH GROUP VIP VMAC PORT...",
diff --git a/utilities/ovn-sbctl.c b/utilities/ovn-sbctl.c
index e35556d34..1aa6cc26f 100644
--- a/utilities/ovn-sbctl.c
+++ b/utilities/ovn-sbctl.c
@@ -307,6 +307,7 @@  pre_get_info(struct ctl_context *ctx)
     ovsdb_idl_add_column(ctx->idl, &sbrec_port_binding_col_chassis);
     ovsdb_idl_add_column(ctx->idl, &sbrec_port_binding_col_datapath);
     ovsdb_idl_add_column(ctx->idl, &sbrec_port_binding_col_up);
+    ovsdb_idl_add_column(ctx->idl, &sbrec_port_binding_col_mirror_rules);
 
     ovsdb_idl_add_column(ctx->idl, &sbrec_logical_flow_col_logical_datapath);
     ovsdb_idl_add_column(ctx->idl, &sbrec_logical_flow_col_logical_dp_group);
@@ -1434,6 +1435,9 @@  static const struct ctl_table_class tables[SBREC_N_TABLES] = {
     [SBREC_TABLE_HA_CHASSIS].row_ids[0]
     = {&sbrec_ha_chassis_col_chassis, NULL, NULL},
 
+    [SBREC_TABLE_MIRROR].row_ids[0]
+    = {&sbrec_mirror_col_name, NULL, NULL},
+
     [SBREC_TABLE_METER].row_ids[0]
     = {&sbrec_meter_col_name, NULL, NULL},