diff mbox series

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

Message ID 20220503151749.716368-1-abhiramrn@gmail.com
State Changes Requested
Headers show
Series [ovs-dev] 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 success github build: passed
ovsrobot/github-robot-_ovn-kubernetes fail github build: failed

Commit Message

Abhiram RN May 3, 2022, 3:17 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 targetted 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>
---
 controller/binding.c        | 275 ++++++++++++++++++++++++++++
 controller/binding.h        |   4 +
 controller/ovn-controller.c |  16 +-
 northd/en-northd.c          |   4 +
 northd/inc-proc-northd.c    |   4 +
 northd/northd.c             | 118 ++++++++++++
 northd/northd.h             |   2 +
 northd/ovn-nb.dlopts        |   1 +
 northd/ovn-sb.dlopts        |   1 +
 northd/ovn_northd.dl        |  10 +
 ovn-nb.ovsschema            |  31 +++-
 ovn-nb.xml                  |  57 ++++++
 ovn-sb.ovsschema            |  23 ++-
 ovn-sb.xml                  |  46 +++++
 tests/ovn-nbctl.at          |  80 ++++++++
 utilities/ovn-nbctl.c       | 356 ++++++++++++++++++++++++++++++++++++
 utilities/ovn-sbctl.c       |   4 +
 17 files changed, 1027 insertions(+), 5 deletions(-)

Comments

0-day Robot May 3, 2022, 3:40 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
#1059 FILE: utilities/ovn-nbctl.c:275:
  mirror-add NAME TYPE INDEX FILTER IP\n\

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

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

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

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

Lines checked: 1476, 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 May 6, 2022, 11:46 p.m. UTC | #2
Hi Abhiram,

thanks for sending the patch.

This is *not* a comprehensive review. This is an attempt to clarify
the scope of the feature, its potential crossover with other pending
work, and other high level comments.

API:
1) The proposal more or less directly maps the new OVN mirrors to OVS
mirror capabilities. Meaning, it doesn't support any advanced filters
to pre-filter traffic mirrored to a sink. Do you think that maybe we
will eventually need an ability to filter out some of from/to traffic
inside the cluster to optimize for network performance? Ideally, we
would support some form of "match" capability like we do for ACLs.
AFAIU ovs mirrors don't support such filtering, but maybe it means
that they shouldn't be used in OVN mirrors? We could as well program
flows to achieve the same effect (plus advanced filters and more).

I may be wrong on the usefulness of "matching" filtering for port
mirroring though. I've checked some Tap-as-a-Service definitions and
they don't seem to include filtering options beyond to/from port +
vlan tags. That said, I don't see why we wouldn't want to allow a user
to pre-filter mirrored traffic. Thoughts?

2) This proposal is limited to remote "gre" mirroring. Do you think we
should be able to use the same API to mirror to a OVN port inside the
cluster? Does the proposed API allow for that? Could e.g. Mirror:type=
be set to "internal" and Mirror:sink= to a logical port name? Would
there be any issue with such later overload of the mirror API?

Testing:
1) it's great you included nbctl tests. We will also need tests that
would demonstrate that mirrors are doing what is expected: set up
mirrors and ports, set up external analyzer / logger, then send some
traffic to / from the mirrored port and check that expected packets
are delivered through the mirror. Such a test will also help to better
understand the intent of the new API.

Miscellaneous smaller comments:
1) ddlog is no longer supported and should be removed from the tree.
We no longer expect the test suite to pass with ddlog, nor we expect
ddlog northd implementation changes. You can safely exclude those from
next patch respins.
2) Update NEWS file with the new feature.
3) I think ovn-nbctl man page needs an update. Perhaps also
ovn-architecture man page.

Thanks for working on it,
Ihar


On Tue, May 3, 2022 at 3:27 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 targetted 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>
> ---
>  controller/binding.c        | 275 ++++++++++++++++++++++++++++
>  controller/binding.h        |   4 +
>  controller/ovn-controller.c |  16 +-
>  northd/en-northd.c          |   4 +
>  northd/inc-proc-northd.c    |   4 +
>  northd/northd.c             | 118 ++++++++++++
>  northd/northd.h             |   2 +
>  northd/ovn-nb.dlopts        |   1 +
>  northd/ovn-sb.dlopts        |   1 +
>  northd/ovn_northd.dl        |  10 +
>  ovn-nb.ovsschema            |  31 +++-
>  ovn-nb.xml                  |  57 ++++++
>  ovn-sb.ovsschema            |  23 ++-
>  ovn-sb.xml                  |  46 +++++
>  tests/ovn-nbctl.at          |  80 ++++++++
>  utilities/ovn-nbctl.c       | 356 ++++++++++++++++++++++++++++++++++++
>  utilities/ovn-sbctl.c       |   4 +
>  17 files changed, 1027 insertions(+), 5 deletions(-)
>
> diff --git a/controller/binding.c b/controller/binding.c
> index 7281b0485..471470cfa 100644
> --- a/controller/binding.c
> +++ b/controller/binding.c
> @@ -64,6 +64,7 @@ binding_register_ovs_idl(struct ovsdb_idl *ovs_idl)
>      ovsdb_idl_add_table(ovs_idl, &ovsrec_table_bridge);
>      ovsdb_idl_add_column(ovs_idl, &ovsrec_bridge_col_name);
>      ovsdb_idl_add_column(ovs_idl, &ovsrec_bridge_col_ports);
> +    ovsdb_idl_add_column(ovs_idl, &ovsrec_bridge_col_mirrors);
>
>      ovsdb_idl_add_table(ovs_idl, &ovsrec_table_port);
>      ovsdb_idl_track_add_column(ovs_idl, &ovsrec_port_col_name);
> @@ -79,6 +80,12 @@ binding_register_ovs_idl(struct ovsdb_idl *ovs_idl)
>
>      ovsdb_idl_add_table(ovs_idl, &ovsrec_table_qos);
>      ovsdb_idl_add_column(ovs_idl, &ovsrec_qos_col_type);
> +
> +    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);
>  }
>
>  static void update_lport_tracking(const struct sbrec_port_binding *pb,
> @@ -2226,6 +2233,238 @@ consider_patch_port_for_local_datapaths(const struct sbrec_port_binding *pb,
>      }
>  }
>
> +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 void
> +mirror_create(const struct sbrec_port_binding *pb,
> +              struct binding_ctx_in *b_ctx_in,
> +              const struct ovsrec_mirror *mirror)
> +{
> +    if (pb->up[0] == true) {
> +        VLOG_INFO("Mirror rule(s) present for %s ", pb->logical_port);
> +        /* Loop through the mirror rules */
> +        for (int 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, b_ctx_in->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, "erspan_idx", key);
> +                    smap_add(&options, "erspan_ver","1");
> +
> +                }
> +                struct ovsrec_interface *iface =
> +                          ovsrec_interface_insert(b_ctx_in->ovs_idl_txn);
> +                port_name = xasprintf("ovn-%s-%s",
> +                                       pb->mirror_rules[i]->type,
> +                                       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(b_ctx_in->ovs_idl_txn);
> +                ovsrec_port_set_name(port, port_name);
> +                ovsrec_port_set_interfaces(port, &iface, 1);
> +
> +                ovsrec_bridge_update_ports_addvalue(b_ctx_in->br_int, port);
> +
> +                smap_destroy(&options);
> +                free(port_name);
> +                free(key);
> +
> +                VLOG_INFO("Creating Mirror in OVS DB");
> +                mirror = ovsrec_mirror_insert(b_ctx_in->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(b_ctx_in->br_int,
> +                                                                 mirror);
> +            }
> +
> +            const struct ovsrec_interface *iface_rec;
> +            const char *iface_id;
> +            /* find the interface corresponding to the pb->logical_port */
> +            OVSREC_INTERFACE_TABLE_FOR_EACH (iface_rec,
> +                                             b_ctx_in->iface_table) {
> +                iface_id = smap_get(&iface_rec->external_ids, "iface-id");
> +                if (iface_id) {
> +                    if (!strcmp(iface_id, pb->logical_port)) {
> +                        VLOG_INFO("Found the interface mapped to physical");
> +                        break;
> +                    }
> +                }
> +            }
> +
> +            const struct ovsrec_port *p =
> +                              get_port_for_iface(iface_rec,b_ctx_in->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);
> +                }
> +            }
> +        }
> +    }
> +}
> +
> +static void
> +mirror_delete(const struct sbrec_port_binding *pb,
> +              struct binding_ctx_in *b_ctx_in,
> +              struct shash *pb_mirror_map)
> +{
> +    struct sset pb_mirrors = SSET_INITIALIZER(&pb_mirrors);
> +
> +    for (size_t i = 0; i < pb->n_mirror_rules ; i++) {
> +        sset_add(&pb_mirrors, pb->mirror_rules[i]->name);
> +    }
> +
> +    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(b_ctx_in->br_int,
> +                                                    ovs_mirror->output_port);
> +                ovsrec_bridge_update_mirrors_delvalue(b_ctx_in->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 binding_ctx_in *b_ctx_in,
> +                            const struct ovsrec_mirror *mirror,
> +                            struct shash *pb_mirror_map)
> +{
> +    OVSREC_MIRROR_TABLE_FOR_EACH (mirror, b_ctx_in->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);
> +                }
> +            }
> +        }
> +    }
> +}
> +
> +
>  /* Returns true if the port binding changes resulted in local binding
>   * updates, false otherwise.
>   */
> @@ -2485,6 +2724,42 @@ delete_done:
>      }
>
>      destroy_qos_map(&qos_map);
> +
> +    /* Handle Mirror Rule updates */
> +    SBREC_PORT_BINDING_TABLE_FOR_EACH_TRACKED (pb,
> +                                               b_ctx_in->port_binding_table) {
> +
> +        const struct ovsrec_mirror *mirror = NULL;
> +        struct shash port_ovs_mirrors = SHASH_INITIALIZER(&port_ovs_mirrors);
> +
> +        /* Need to find if mirror needs update */
> +        find_port_specific_mirrors(pb, b_ctx_in, mirror, &port_ovs_mirrors);
> +        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
> +                 */
> +                mirror_create(pb, b_ctx_in, mirror);
> +            } else {
> +                /* delete mirror,
> +                 * if mirror has other sources only update selection
> +                 */
> +                mirror_delete(pb, b_ctx_in, &port_ovs_mirrors);
> +            }
> +        }
> +        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 handled;
>  }
>
> diff --git a/controller/binding.h b/controller/binding.h
> index 430a8d9b1..842bb6ab1 100644
> --- a/controller/binding.h
> +++ b/controller/binding.h
> @@ -33,8 +33,10 @@ struct ovsrec_port_table;
>  struct ovsrec_qos_table;
>  struct ovsrec_bridge_table;
>  struct ovsrec_open_vswitch_table;
> +struct ovsrec_mirror_table;
>  struct sbrec_chassis;
>  struct sbrec_port_binding_table;
> +struct sbrec_mirror_table;
>  struct sset;
>  struct sbrec_port_binding;
>  struct ds;
> @@ -48,7 +50,9 @@ struct binding_ctx_in {
>      struct ovsdb_idl_index *sbrec_port_binding_by_name;
>      const struct ovsrec_port_table *port_table;
>      const struct ovsrec_qos_table *qos_table;
> +    const struct ovsrec_mirror_table *mirror_table;
>      const struct sbrec_port_binding_table *port_binding_table;
> +    const struct sbrec_mirror_table *sb_mirror_table;
>      const struct ovsrec_bridge *br_int;
>      const struct sbrec_chassis *chassis_rec;
>      const struct sset *active_tunnels;
> diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c
> index 22b7fa935..35f79940a 100644
> --- a/controller/ovn-controller.c
> +++ b/controller/ovn-controller.c
> @@ -977,6 +977,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 {
> @@ -994,7 +995,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,
> @@ -1222,10 +1224,18 @@ init_binding_ctx(struct engine_node *node,
>          (struct ovsrec_qos_table *)EN_OVSDB_GET(
>              engine_get_input("OVS_qos", 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 ovsdb_idl_index *sbrec_datapath_binding_by_key =
>          engine_ovsdb_node_get_index(
>                  engine_get_input("SB_datapath_binding", node),
> @@ -1251,7 +1261,9 @@ init_binding_ctx(struct engine_node *node,
>      b_ctx_in->port_table = port_table;
>      b_ctx_in->iface_table = iface_table;
>      b_ctx_in->qos_table = qos_table;
> +    b_ctx_in->mirror_table = mirror_table;
>      b_ctx_in->port_binding_table = pb_table;
> +    b_ctx_in->sb_mirror_table = sb_mirror_table;
>      b_ctx_in->br_int = br_int;
>      b_ctx_in->chassis_rec = chassis;
>      b_ctx_in->active_tunnels = &rt_data->active_tunnels;
> @@ -3464,8 +3476,10 @@ main(int argc, char *argv[])
>      engine_add_input(&en_runtime_data, &en_ovs_open_vswitch, NULL);
>      engine_add_input(&en_runtime_data, &en_ovs_bridge, NULL);
>      engine_add_input(&en_runtime_data, &en_ovs_qos, NULL);
> +    engine_add_input(&en_runtime_data, &en_ovs_mirror, NULL);
>
>      engine_add_input(&en_runtime_data, &en_sb_chassis, NULL);
> +    engine_add_input(&en_runtime_data, &en_sb_mirror, NULL);
>      engine_add_input(&en_runtime_data, &en_sb_datapath_binding,
>                       runtime_data_sb_datapath_binding_handler);
>      engine_add_input(&en_runtime_data, &en_sb_port_binding,
> 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 43093cb5a..f77daafb8 100644
> --- a/northd/inc-proc-northd.c
> +++ b/northd/inc-proc-northd.c
> @@ -48,6 +48,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") \
> @@ -90,6 +91,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") \
> @@ -168,6 +170,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);
> @@ -190,6 +193,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 a56666297..455458514 100644
> --- a/northd/northd.c
> +++ b/northd/northd.c
> @@ -3204,6 +3204,51 @@ ovn_port_update_sbrec_chassis(
>      }
>  }
>
> +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,
> @@ -3548,6 +3593,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);
> @@ -14781,6 +14837,67 @@ sync_meters(struct northd_input *input_data,
>      shash_destroy(&sb_meters);
>  }
>
> +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) {
> +        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.
> @@ -15388,6 +15505,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 2d804a22e..c30692864 100644
> --- a/northd/northd.h
> +++ b/northd/northd.h
> @@ -30,6 +30,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;
> @@ -49,6 +50,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/northd/ovn-nb.dlopts b/northd/ovn-nb.dlopts
> index 9a460adef..797ca523f 100644
> --- a/northd/ovn-nb.dlopts
> +++ b/northd/ovn-nb.dlopts
> @@ -20,6 +20,7 @@
>  --intern-table Load_Balancer
>  --intern-table Logical_Switch
>  --intern-table Load_Balancer_Health_Check
> +--intern-table Mirror
>  --intern-table Meter
>  --intern-table NAT
>  --intern-table Address_Set
> diff --git a/northd/ovn-sb.dlopts b/northd/ovn-sb.dlopts
> index 99b65f101..82d614029 100644
> --- a/northd/ovn-sb.dlopts
> +++ b/northd/ovn-sb.dlopts
> @@ -13,6 +13,7 @@
>  -o Load_Balancer
>  -o Logical_DP_Group
>  -o MAC_Binding
> +-o Mirror
>  -o Meter
>  -o Meter_Band
>  -o Multicast_Group
> diff --git a/northd/ovn_northd.dl b/northd/ovn_northd.dl
> index 2fe73959c..22a502fcc 100644
> --- a/northd/ovn_northd.dl
> +++ b/northd/ovn_northd.dl
> @@ -28,6 +28,16 @@ import set
>
>  index Logical_Flow_Index() on sb::Out_Logical_Flow()
>
> +/* Mirror table */
> +for (mirror in &nb::Mirror) {
> +    sb::Out_Mirror(._uuid = mirror._uuid,
> +                 .name = mirror.name,
> +                 .filter = mirror.filter,
> +                 .sink = mirror.sink,
> +                 .type = mirror.type,
> +                 .index = mirror.index)
> +}
> +
>  /* Meter_Band table */
>  for (mb in nb::Meter_Band) {
>      sb::Out_Meter_Band(._uuid = mb._uuid,
> 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 9010240a8..c514b7733 100644
> --- a/ovn-nb.xml
> +++ b/ovn-nb.xml
> @@ -1511,6 +1511,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.
> @@ -2432,6 +2437,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 66664c840..0493bce7b 100644
> --- a/ovn-sb.ovsschema
> +++ b/ovn-sb.ovsschema
> @@ -1,7 +1,7 @@
>  {
>      "name": "OVN_Southbound",
> -    "version": "20.22.0",
> -    "cksum": "1686121686 27471",
> +    "version": "20.23.0",
> +    "cksum": "654011660 28470",
>      "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"},
> @@ -222,6 +236,11 @@
>                                              "refTable": "Encap",
>                                               "refType": "weak"},
>                                      "min": 0, "max": 1}},
> +                "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 1ffb24e7a..6a81021a4 100644
> --- a/ovn-sb.xml
> +++ b/ovn-sb.xml
> @@ -2632,6 +2632,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
> @@ -3082,6 +3123,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 f9b9defd0..7643f8884 100644
> --- a/tests/ovn-nbctl.at
> +++ b/tests/ovn-nbctl.at
> @@ -435,6 +435,86 @@ 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 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/utilities/ovn-nbctl.c b/utilities/ovn-nbctl.c
> index e747f6933..f6ab002ef 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)
>  {
> @@ -7161,6 +7297,213 @@ 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) {
> +            if (mirror->n_src > 0) {
> +                ctl_error(ctx, "Detach mirror source(s) before deletion");
> +                return;
> +            }
> +            nbrec_mirror_delete(mirror);
> +        }
> +        return;
> +    }
> +
> +    /* Remove the matching mirror. */
> +    NBREC_MIRROR_FOR_EACH (mirror, ctx->idl) {
> +        if (strcmp(ctx->argv[1], mirror->name)) {
> +            continue;
> +        }
> +        if (mirror->n_src > 0) {
> +            ctl_error(ctx, "Detach source(s) before mirror %s deletion",
> +                      mirror->name);
> +            return;
> +        }
> +        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,
> @@ -7254,6 +7597,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 },
> @@ -7308,6 +7660,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 b008b5d0b..14c24dfb9 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);
> @@ -1424,6 +1425,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 RN May 9, 2022, 10:04 a.m. UTC | #3
On Fri, May 06, 2022 at 07:46:17PM -0400, Ihar Hrachyshka wrote:
> Hi Abhiram,
> 
> thanks for sending the patch.

Thanks for spening time to review it.

> 
> This is *not* a comprehensive review. This is an attempt to clarify
> the scope of the feature, its potential crossover with other pending
> work, and other high level comments.
> 
> API:
> 1) The proposal more or less directly maps the new OVN mirrors to OVS
> mirror capabilities. Meaning, it doesn't support any advanced filters
> to pre-filter traffic mirrored to a sink. Do you think that maybe we
> will eventually need an ability to filter out some of from/to traffic
> inside the cluster to optimize for network performance? Ideally, we
> would support some form of "match" capability like we do for ACLs.
> AFAIU ovs mirrors don't support such filtering, but maybe it means
> that they shouldn't be used in OVN mirrors? We could as well program
> flows to achieve the same effect (plus advanced filters and more).
> 
> I may be wrong on the usefulness of "matching" filtering for port
> mirroring though. I've checked some Tap-as-a-Service definitions and
> they don't seem to include filtering options beyond to/from port +
> vlan tags. That said, I don't see why we wouldn't want to allow a user
> to pre-filter mirrored traffic. Thoughts?
> 

The initial goal is exactly what you mentioned at the beginning
i.e to match the new OVN mirrors to OVS mirror capabilities.
Currently the focus was only on that.
Regarding the *match* thing I think its definitely worth keeping
it in the backlog and do it as future work (or atleast do more
re-search on how/if it can be useful)

> 2) This proposal is limited to remote "gre" mirroring. Do you think we
> should be able to use the same API to mirror to a OVN port inside the
> cluster? Does the proposed API allow for that? Could e.g. Mirror:type=
> be set to "internal" and Mirror:sink= to a logical port name? Would
> there be any issue with such later overload of the mirror API?
> 

Just for info, we have added support for "erspan" as well along with "gre".

Regarding your question to set type "internal" and sink "logical port name"
I think it is definitely possible to support.

If I have to think through from implementation point of view
It might need some changes in nbctl for parameter check.
Further we have to branch the handling in OVN controller code when we
get port binding update handler call, based on the "type" either it follows 
the remote path(in this patch) or local(inside cluster) code path.

Having said that provinding option now is like marrying the 2 different
code paths. Because we are trying to re-use existing OVS way of mirroing
for the remote and for the the local(inside cluster) its a different 
mechanism. Is it like early to marry the 2 paths now? Rather commit 
separately and marry them as a future work!

Open for comments on this.

> Testing:
> 1) it's great you included nbctl tests. We will also need tests that
> would demonstrate that mirrors are doing what is expected: set up
> mirrors and ports, set up external analyzer / logger, then send some
> traffic to / from the mirrored port and check that expected packets
> are delivered through the mirror. Such a test will also help to better
> understand the intent of the new API.

Currently I have tested it on my local setup using the ansible script as
below. But instead of the OVS commands I have used the new OVN ones.

https://github.com/rh-nfv-int/hw-offload-playbooks/blob/main/testRemotePortMirroring.yml

Originally it used OVS commands:
            ovs-vsctl add-port br-int gre0 -- set Interface gre0 type=gre
                              options:local_ip=10.10.10.1 options:remote_ip=10.10.10.2
                                  options:key=0 -- --id=@p get port gre0
				    -- --id=@m create mirror name=mirror0 output-port=@p 
				    -- set bridge br-int mirrors=@m
            ovs-vsctl -- --id=@p1 get Port {{ physical_interface }}_0 -- add Mirror mirror0 select-dst-port @p1
            ovs-vsctl -- --id=@p1 get Port {{ physical_interface }}_0 -- add Mirror mirror0 select-src-port @p1

Now, instead of the above OVS commands below OVN commands I used.
ovn-nbctl mirror-add mirror0 gre 0 both 10.10.10.2
ovn-nbctl lsp-attach-mirror sw0-port1 mirror0

But, I am not very sure on how to fit in such a test inside the OVN.
Maybe if you can point to some tests or give some guidance there it will help.

> 
> Miscellaneous smaller comments:
> 1) ddlog is no longer supported and should be removed from the tree.
> We no longer expect the test suite to pass with ddlog, nor we expect
> ddlog northd implementation changes. You can safely exclude those from
> next patch respins.

Sure. Will take care of that . You mean these 3 files I can safely remove
right? [ovn-nb.dlopts, ovn-sb.dlopts, ovn_northd.dl]

> 2) Update NEWS file with the new feature.

Sure. Will update it.

> 3) I think ovn-nbctl man page needs an update. Perhaps also
> ovn-architecture man page.

Sure. Will update it.

> 
> Thanks for working on it,
> Ihar
Thanks again for taking time to give your valuable comments.
Regards,
Abhiram R N
> 
> 
> On Tue, May 3, 2022 at 3:27 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 targetted 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>
> > ---
> >  controller/binding.c        | 275 ++++++++++++++++++++++++++++
> >  controller/binding.h        |   4 +
> >  controller/ovn-controller.c |  16 +-
> >  northd/en-northd.c          |   4 +
> >  northd/inc-proc-northd.c    |   4 +
> >  northd/northd.c             | 118 ++++++++++++
> >  northd/northd.h             |   2 +
> >  northd/ovn-nb.dlopts        |   1 +
> >  northd/ovn-sb.dlopts        |   1 +
> >  northd/ovn_northd.dl        |  10 +
> >  ovn-nb.ovsschema            |  31 +++-
> >  ovn-nb.xml                  |  57 ++++++
> >  ovn-sb.ovsschema            |  23 ++-
> >  ovn-sb.xml                  |  46 +++++
> >  tests/ovn-nbctl.at          |  80 ++++++++
> >  utilities/ovn-nbctl.c       | 356 ++++++++++++++++++++++++++++++++++++
> >  utilities/ovn-sbctl.c       |   4 +
> >  17 files changed, 1027 insertions(+), 5 deletions(-)
> >
> > diff --git a/controller/binding.c b/controller/binding.c
> > index 7281b0485..471470cfa 100644
> > --- a/controller/binding.c
> > +++ b/controller/binding.c
> > @@ -64,6 +64,7 @@ binding_register_ovs_idl(struct ovsdb_idl *ovs_idl)
> >      ovsdb_idl_add_table(ovs_idl, &ovsrec_table_bridge);
> >      ovsdb_idl_add_column(ovs_idl, &ovsrec_bridge_col_name);
> >      ovsdb_idl_add_column(ovs_idl, &ovsrec_bridge_col_ports);
> > +    ovsdb_idl_add_column(ovs_idl, &ovsrec_bridge_col_mirrors);
> >
> >      ovsdb_idl_add_table(ovs_idl, &ovsrec_table_port);
> >      ovsdb_idl_track_add_column(ovs_idl, &ovsrec_port_col_name);
> > @@ -79,6 +80,12 @@ binding_register_ovs_idl(struct ovsdb_idl *ovs_idl)
> >
> >      ovsdb_idl_add_table(ovs_idl, &ovsrec_table_qos);
> >      ovsdb_idl_add_column(ovs_idl, &ovsrec_qos_col_type);
> > +
> > +    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);
> >  }
> >
> >  static void update_lport_tracking(const struct sbrec_port_binding *pb,
> > @@ -2226,6 +2233,238 @@ consider_patch_port_for_local_datapaths(const struct sbrec_port_binding *pb,
> >      }
> >  }
> >
> > +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 void
> > +mirror_create(const struct sbrec_port_binding *pb,
> > +              struct binding_ctx_in *b_ctx_in,
> > +              const struct ovsrec_mirror *mirror)
> > +{
> > +    if (pb->up[0] == true) {
> > +        VLOG_INFO("Mirror rule(s) present for %s ", pb->logical_port);
> > +        /* Loop through the mirror rules */
> > +        for (int 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, b_ctx_in->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, "erspan_idx", key);
> > +                    smap_add(&options, "erspan_ver","1");
> > +
> > +                }
> > +                struct ovsrec_interface *iface =
> > +                          ovsrec_interface_insert(b_ctx_in->ovs_idl_txn);
> > +                port_name = xasprintf("ovn-%s-%s",
> > +                                       pb->mirror_rules[i]->type,
> > +                                       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(b_ctx_in->ovs_idl_txn);
> > +                ovsrec_port_set_name(port, port_name);
> > +                ovsrec_port_set_interfaces(port, &iface, 1);
> > +
> > +                ovsrec_bridge_update_ports_addvalue(b_ctx_in->br_int, port);
> > +
> > +                smap_destroy(&options);
> > +                free(port_name);
> > +                free(key);
> > +
> > +                VLOG_INFO("Creating Mirror in OVS DB");
> > +                mirror = ovsrec_mirror_insert(b_ctx_in->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(b_ctx_in->br_int,
> > +                                                                 mirror);
> > +            }
> > +
> > +            const struct ovsrec_interface *iface_rec;
> > +            const char *iface_id;
> > +            /* find the interface corresponding to the pb->logical_port */
> > +            OVSREC_INTERFACE_TABLE_FOR_EACH (iface_rec,
> > +                                             b_ctx_in->iface_table) {
> > +                iface_id = smap_get(&iface_rec->external_ids, "iface-id");
> > +                if (iface_id) {
> > +                    if (!strcmp(iface_id, pb->logical_port)) {
> > +                        VLOG_INFO("Found the interface mapped to physical");
> > +                        break;
> > +                    }
> > +                }
> > +            }
> > +
> > +            const struct ovsrec_port *p =
> > +                              get_port_for_iface(iface_rec,b_ctx_in->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);
> > +                }
> > +            }
> > +        }
> > +    }
> > +}
> > +
> > +static void
> > +mirror_delete(const struct sbrec_port_binding *pb,
> > +              struct binding_ctx_in *b_ctx_in,
> > +              struct shash *pb_mirror_map)
> > +{
> > +    struct sset pb_mirrors = SSET_INITIALIZER(&pb_mirrors);
> > +
> > +    for (size_t i = 0; i < pb->n_mirror_rules ; i++) {
> > +        sset_add(&pb_mirrors, pb->mirror_rules[i]->name);
> > +    }
> > +
> > +    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(b_ctx_in->br_int,
> > +                                                    ovs_mirror->output_port);
> > +                ovsrec_bridge_update_mirrors_delvalue(b_ctx_in->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 binding_ctx_in *b_ctx_in,
> > +                            const struct ovsrec_mirror *mirror,
> > +                            struct shash *pb_mirror_map)
> > +{
> > +    OVSREC_MIRROR_TABLE_FOR_EACH (mirror, b_ctx_in->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);
> > +                }
> > +            }
> > +        }
> > +    }
> > +}
> > +
> > +
> >  /* Returns true if the port binding changes resulted in local binding
> >   * updates, false otherwise.
> >   */
> > @@ -2485,6 +2724,42 @@ delete_done:
> >      }
> >
> >      destroy_qos_map(&qos_map);
> > +
> > +    /* Handle Mirror Rule updates */
> > +    SBREC_PORT_BINDING_TABLE_FOR_EACH_TRACKED (pb,
> > +                                               b_ctx_in->port_binding_table) {
> > +
> > +        const struct ovsrec_mirror *mirror = NULL;
> > +        struct shash port_ovs_mirrors = SHASH_INITIALIZER(&port_ovs_mirrors);
> > +
> > +        /* Need to find if mirror needs update */
> > +        find_port_specific_mirrors(pb, b_ctx_in, mirror, &port_ovs_mirrors);
> > +        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
> > +                 */
> > +                mirror_create(pb, b_ctx_in, mirror);
> > +            } else {
> > +                /* delete mirror,
> > +                 * if mirror has other sources only update selection
> > +                 */
> > +                mirror_delete(pb, b_ctx_in, &port_ovs_mirrors);
> > +            }
> > +        }
> > +        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 handled;
> >  }
> >
> > diff --git a/controller/binding.h b/controller/binding.h
> > index 430a8d9b1..842bb6ab1 100644
> > --- a/controller/binding.h
> > +++ b/controller/binding.h
> > @@ -33,8 +33,10 @@ struct ovsrec_port_table;
> >  struct ovsrec_qos_table;
> >  struct ovsrec_bridge_table;
> >  struct ovsrec_open_vswitch_table;
> > +struct ovsrec_mirror_table;
> >  struct sbrec_chassis;
> >  struct sbrec_port_binding_table;
> > +struct sbrec_mirror_table;
> >  struct sset;
> >  struct sbrec_port_binding;
> >  struct ds;
> > @@ -48,7 +50,9 @@ struct binding_ctx_in {
> >      struct ovsdb_idl_index *sbrec_port_binding_by_name;
> >      const struct ovsrec_port_table *port_table;
> >      const struct ovsrec_qos_table *qos_table;
> > +    const struct ovsrec_mirror_table *mirror_table;
> >      const struct sbrec_port_binding_table *port_binding_table;
> > +    const struct sbrec_mirror_table *sb_mirror_table;
> >      const struct ovsrec_bridge *br_int;
> >      const struct sbrec_chassis *chassis_rec;
> >      const struct sset *active_tunnels;
> > diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c
> > index 22b7fa935..35f79940a 100644
> > --- a/controller/ovn-controller.c
> > +++ b/controller/ovn-controller.c
> > @@ -977,6 +977,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 {
> > @@ -994,7 +995,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,
> > @@ -1222,10 +1224,18 @@ init_binding_ctx(struct engine_node *node,
> >          (struct ovsrec_qos_table *)EN_OVSDB_GET(
> >              engine_get_input("OVS_qos", 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 ovsdb_idl_index *sbrec_datapath_binding_by_key =
> >          engine_ovsdb_node_get_index(
> >                  engine_get_input("SB_datapath_binding", node),
> > @@ -1251,7 +1261,9 @@ init_binding_ctx(struct engine_node *node,
> >      b_ctx_in->port_table = port_table;
> >      b_ctx_in->iface_table = iface_table;
> >      b_ctx_in->qos_table = qos_table;
> > +    b_ctx_in->mirror_table = mirror_table;
> >      b_ctx_in->port_binding_table = pb_table;
> > +    b_ctx_in->sb_mirror_table = sb_mirror_table;
> >      b_ctx_in->br_int = br_int;
> >      b_ctx_in->chassis_rec = chassis;
> >      b_ctx_in->active_tunnels = &rt_data->active_tunnels;
> > @@ -3464,8 +3476,10 @@ main(int argc, char *argv[])
> >      engine_add_input(&en_runtime_data, &en_ovs_open_vswitch, NULL);
> >      engine_add_input(&en_runtime_data, &en_ovs_bridge, NULL);
> >      engine_add_input(&en_runtime_data, &en_ovs_qos, NULL);
> > +    engine_add_input(&en_runtime_data, &en_ovs_mirror, NULL);
> >
> >      engine_add_input(&en_runtime_data, &en_sb_chassis, NULL);
> > +    engine_add_input(&en_runtime_data, &en_sb_mirror, NULL);
> >      engine_add_input(&en_runtime_data, &en_sb_datapath_binding,
> >                       runtime_data_sb_datapath_binding_handler);
> >      engine_add_input(&en_runtime_data, &en_sb_port_binding,
> > 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 43093cb5a..f77daafb8 100644
> > --- a/northd/inc-proc-northd.c
> > +++ b/northd/inc-proc-northd.c
> > @@ -48,6 +48,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") \
> > @@ -90,6 +91,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") \
> > @@ -168,6 +170,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);
> > @@ -190,6 +193,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 a56666297..455458514 100644
> > --- a/northd/northd.c
> > +++ b/northd/northd.c
> > @@ -3204,6 +3204,51 @@ ovn_port_update_sbrec_chassis(
> >      }
> >  }
> >
> > +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,
> > @@ -3548,6 +3593,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);
> > @@ -14781,6 +14837,67 @@ sync_meters(struct northd_input *input_data,
> >      shash_destroy(&sb_meters);
> >  }
> >
> > +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) {
> > +        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.
> > @@ -15388,6 +15505,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 2d804a22e..c30692864 100644
> > --- a/northd/northd.h
> > +++ b/northd/northd.h
> > @@ -30,6 +30,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;
> > @@ -49,6 +50,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/northd/ovn-nb.dlopts b/northd/ovn-nb.dlopts
> > index 9a460adef..797ca523f 100644
> > --- a/northd/ovn-nb.dlopts
> > +++ b/northd/ovn-nb.dlopts
> > @@ -20,6 +20,7 @@
> >  --intern-table Load_Balancer
> >  --intern-table Logical_Switch
> >  --intern-table Load_Balancer_Health_Check
> > +--intern-table Mirror
> >  --intern-table Meter
> >  --intern-table NAT
> >  --intern-table Address_Set
> > diff --git a/northd/ovn-sb.dlopts b/northd/ovn-sb.dlopts
> > index 99b65f101..82d614029 100644
> > --- a/northd/ovn-sb.dlopts
> > +++ b/northd/ovn-sb.dlopts
> > @@ -13,6 +13,7 @@
> >  -o Load_Balancer
> >  -o Logical_DP_Group
> >  -o MAC_Binding
> > +-o Mirror
> >  -o Meter
> >  -o Meter_Band
> >  -o Multicast_Group
> > diff --git a/northd/ovn_northd.dl b/northd/ovn_northd.dl
> > index 2fe73959c..22a502fcc 100644
> > --- a/northd/ovn_northd.dl
> > +++ b/northd/ovn_northd.dl
> > @@ -28,6 +28,16 @@ import set
> >
> >  index Logical_Flow_Index() on sb::Out_Logical_Flow()
> >
> > +/* Mirror table */
> > +for (mirror in &nb::Mirror) {
> > +    sb::Out_Mirror(._uuid = mirror._uuid,
> > +                 .name = mirror.name,
> > +                 .filter = mirror.filter,
> > +                 .sink = mirror.sink,
> > +                 .type = mirror.type,
> > +                 .index = mirror.index)
> > +}
> > +
> >  /* Meter_Band table */
> >  for (mb in nb::Meter_Band) {
> >      sb::Out_Meter_Band(._uuid = mb._uuid,
> > 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 9010240a8..c514b7733 100644
> > --- a/ovn-nb.xml
> > +++ b/ovn-nb.xml
> > @@ -1511,6 +1511,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.
> > @@ -2432,6 +2437,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 66664c840..0493bce7b 100644
> > --- a/ovn-sb.ovsschema
> > +++ b/ovn-sb.ovsschema
> > @@ -1,7 +1,7 @@
> >  {
> >      "name": "OVN_Southbound",
> > -    "version": "20.22.0",
> > -    "cksum": "1686121686 27471",
> > +    "version": "20.23.0",
> > +    "cksum": "654011660 28470",
> >      "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"},
> > @@ -222,6 +236,11 @@
> >                                              "refTable": "Encap",
> >                                               "refType": "weak"},
> >                                      "min": 0, "max": 1}},
> > +                "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 1ffb24e7a..6a81021a4 100644
> > --- a/ovn-sb.xml
> > +++ b/ovn-sb.xml
> > @@ -2632,6 +2632,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
> > @@ -3082,6 +3123,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 f9b9defd0..7643f8884 100644
> > --- a/tests/ovn-nbctl.at
> > +++ b/tests/ovn-nbctl.at
> > @@ -435,6 +435,86 @@ 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 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/utilities/ovn-nbctl.c b/utilities/ovn-nbctl.c
> > index e747f6933..f6ab002ef 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)
> >  {
> > @@ -7161,6 +7297,213 @@ 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) {
> > +            if (mirror->n_src > 0) {
> > +                ctl_error(ctx, "Detach mirror source(s) before deletion");
> > +                return;
> > +            }
> > +            nbrec_mirror_delete(mirror);
> > +        }
> > +        return;
> > +    }
> > +
> > +    /* Remove the matching mirror. */
> > +    NBREC_MIRROR_FOR_EACH (mirror, ctx->idl) {
> > +        if (strcmp(ctx->argv[1], mirror->name)) {
> > +            continue;
> > +        }
> > +        if (mirror->n_src > 0) {
> > +            ctl_error(ctx, "Detach source(s) before mirror %s deletion",
> > +                      mirror->name);
> > +            return;
> > +        }
> > +        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,
> > @@ -7254,6 +7597,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 },
> > @@ -7308,6 +7660,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 b008b5d0b..14c24dfb9 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);
> > @@ -1424,6 +1425,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 RN May 9, 2022, 4:13 p.m. UTC | #4
On Mon, May 09, 2022 at 03:34:47PM +0530, Abhiram R N wrote:

Hi Ihar,

<just updating the test case link I had provided earlir for reference>

> On Fri, May 06, 2022 at 07:46:17PM -0400, Ihar Hrachyshka wrote:
> > Hi Abhiram,
> > 
> > thanks for sending the patch.
> 
> Thanks for spening time to review it.
> 
> > 
> > This is *not* a comprehensive review. This is an attempt to clarify
> > the scope of the feature, its potential crossover with other pending
> > work, and other high level comments.
> > 
> > API:
> > 1) The proposal more or less directly maps the new OVN mirrors to OVS
> > mirror capabilities. Meaning, it doesn't support any advanced filters
> > to pre-filter traffic mirrored to a sink. Do you think that maybe we
> > will eventually need an ability to filter out some of from/to traffic
> > inside the cluster to optimize for network performance? Ideally, we
> > would support some form of "match" capability like we do for ACLs.
> > AFAIU ovs mirrors don't support such filtering, but maybe it means
> > that they shouldn't be used in OVN mirrors? We could as well program
> > flows to achieve the same effect (plus advanced filters and more).
> > 
> > I may be wrong on the usefulness of "matching" filtering for port
> > mirroring though. I've checked some Tap-as-a-Service definitions and
> > they don't seem to include filtering options beyond to/from port +
> > vlan tags. That said, I don't see why we wouldn't want to allow a user
> > to pre-filter mirrored traffic. Thoughts?
> > 
> 
> The initial goal is exactly what you mentioned at the beginning
> i.e to match the new OVN mirrors to OVS mirror capabilities.
> Currently the focus was only on that.
> Regarding the *match* thing I think its definitely worth keeping
> it in the backlog and do it as future work (or atleast do more
> re-search on how/if it can be useful)
> 
> > 2) This proposal is limited to remote "gre" mirroring. Do you think we
> > should be able to use the same API to mirror to a OVN port inside the
> > cluster? Does the proposed API allow for that? Could e.g. Mirror:type=
> > be set to "internal" and Mirror:sink= to a logical port name? Would
> > there be any issue with such later overload of the mirror API?
> > 
> 
> Just for info, we have added support for "erspan" as well along with "gre".
> 
> Regarding your question to set type "internal" and sink "logical port name"
> I think it is definitely possible to support.
> 
> If I have to think through from implementation point of view
> It might need some changes in nbctl for parameter check.
> Further we have to branch the handling in OVN controller code when we
> get port binding update handler call, based on the "type" either it follows 
> the remote path(in this patch) or local(inside cluster) code path.
> 
> Having said that provinding option now is like marrying the 2 different
> code paths. Because we are trying to re-use existing OVS way of mirroing
> for the remote and for the the local(inside cluster) its a different 
> mechanism. Is it like early to marry the 2 paths now? Rather commit 
> separately and marry them as a future work!
> 
> Open for comments on this.
> 
> > Testing:
> > 1) it's great you included nbctl tests. We will also need tests that
> > would demonstrate that mirrors are doing what is expected: set up
> > mirrors and ports, set up external analyzer / logger, then send some
> > traffic to / from the mirrored port and check that expected packets
> > are delivered through the mirror. Such a test will also help to better
> > understand the intent of the new API.
> 
> Currently I have tested it on my local setup using the ansible script as
> below. But instead of the OVS commands I have used the new OVN ones.
> 
> https://github.com/rh-nfv-int/hw-offload-playbooks/blob/main/testRemotePortMirroring.yml

Above link is not correct. Please refer below link
https://github.com/rh-nfv-int/playbooks/blob/main/regression/ovs-hwol/testRemotePortMirroring.yml

> 
> Originally it used OVS commands:
>             ovs-vsctl add-port br-int gre0 -- set Interface gre0 type=gre
>                               options:local_ip=10.10.10.1 options:remote_ip=10.10.10.2
>                                   options:key=0 -- --id=@p get port gre0
> 				    -- --id=@m create mirror name=mirror0 output-port=@p 
> 				    -- set bridge br-int mirrors=@m
>             ovs-vsctl -- --id=@p1 get Port {{ physical_interface }}_0 -- add Mirror mirror0 select-dst-port @p1
>             ovs-vsctl -- --id=@p1 get Port {{ physical_interface }}_0 -- add Mirror mirror0 select-src-port @p1
> 
> Now, instead of the above OVS commands below OVN commands I used.
> ovn-nbctl mirror-add mirror0 gre 0 both 10.10.10.2
> ovn-nbctl lsp-attach-mirror sw0-port1 mirror0
> 
> But, I am not very sure on how to fit in such a test inside the OVN.
> Maybe if you can point to some tests or give some guidance there it will help.
> 
> > 
> > Miscellaneous smaller comments:
> > 1) ddlog is no longer supported and should be removed from the tree.
> > We no longer expect the test suite to pass with ddlog, nor we expect
> > ddlog northd implementation changes. You can safely exclude those from
> > next patch respins.
> 
> Sure. Will take care of that . You mean these 3 files I can safely remove
> right? [ovn-nb.dlopts, ovn-sb.dlopts, ovn_northd.dl]
> 
> > 2) Update NEWS file with the new feature.
> 
> Sure. Will update it.
> 
> > 3) I think ovn-nbctl man page needs an update. Perhaps also
> > ovn-architecture man page.
> 
> Sure. Will update it.
> 
> > 
> > Thanks for working on it,
> > Ihar
> Thanks again for taking time to give your valuable comments.
> Regards,
> Abhiram R N
> > 
> > 
> > On Tue, May 3, 2022 at 3:27 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 targetted 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>
> > > ---
> > >  controller/binding.c        | 275 ++++++++++++++++++++++++++++
> > >  controller/binding.h        |   4 +
> > >  controller/ovn-controller.c |  16 +-
> > >  northd/en-northd.c          |   4 +
> > >  northd/inc-proc-northd.c    |   4 +
> > >  northd/northd.c             | 118 ++++++++++++
> > >  northd/northd.h             |   2 +
> > >  northd/ovn-nb.dlopts        |   1 +
> > >  northd/ovn-sb.dlopts        |   1 +
> > >  northd/ovn_northd.dl        |  10 +
> > >  ovn-nb.ovsschema            |  31 +++-
> > >  ovn-nb.xml                  |  57 ++++++
> > >  ovn-sb.ovsschema            |  23 ++-
> > >  ovn-sb.xml                  |  46 +++++
> > >  tests/ovn-nbctl.at          |  80 ++++++++
> > >  utilities/ovn-nbctl.c       | 356 ++++++++++++++++++++++++++++++++++++
> > >  utilities/ovn-sbctl.c       |   4 +
> > >  17 files changed, 1027 insertions(+), 5 deletions(-)
> > >
> > > diff --git a/controller/binding.c b/controller/binding.c
> > > index 7281b0485..471470cfa 100644
> > > --- a/controller/binding.c
> > > +++ b/controller/binding.c
> > > @@ -64,6 +64,7 @@ binding_register_ovs_idl(struct ovsdb_idl *ovs_idl)
> > >      ovsdb_idl_add_table(ovs_idl, &ovsrec_table_bridge);
> > >      ovsdb_idl_add_column(ovs_idl, &ovsrec_bridge_col_name);
> > >      ovsdb_idl_add_column(ovs_idl, &ovsrec_bridge_col_ports);
> > > +    ovsdb_idl_add_column(ovs_idl, &ovsrec_bridge_col_mirrors);
> > >
> > >      ovsdb_idl_add_table(ovs_idl, &ovsrec_table_port);
> > >      ovsdb_idl_track_add_column(ovs_idl, &ovsrec_port_col_name);
> > > @@ -79,6 +80,12 @@ binding_register_ovs_idl(struct ovsdb_idl *ovs_idl)
> > >
> > >      ovsdb_idl_add_table(ovs_idl, &ovsrec_table_qos);
> > >      ovsdb_idl_add_column(ovs_idl, &ovsrec_qos_col_type);
> > > +
> > > +    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);
> > >  }
> > >
> > >  static void update_lport_tracking(const struct sbrec_port_binding *pb,
> > > @@ -2226,6 +2233,238 @@ consider_patch_port_for_local_datapaths(const struct sbrec_port_binding *pb,
> > >      }
> > >  }
> > >
> > > +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 void
> > > +mirror_create(const struct sbrec_port_binding *pb,
> > > +              struct binding_ctx_in *b_ctx_in,
> > > +              const struct ovsrec_mirror *mirror)
> > > +{
> > > +    if (pb->up[0] == true) {
> > > +        VLOG_INFO("Mirror rule(s) present for %s ", pb->logical_port);
> > > +        /* Loop through the mirror rules */
> > > +        for (int 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, b_ctx_in->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, "erspan_idx", key);
> > > +                    smap_add(&options, "erspan_ver","1");
> > > +
> > > +                }
> > > +                struct ovsrec_interface *iface =
> > > +                          ovsrec_interface_insert(b_ctx_in->ovs_idl_txn);
> > > +                port_name = xasprintf("ovn-%s-%s",
> > > +                                       pb->mirror_rules[i]->type,
> > > +                                       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(b_ctx_in->ovs_idl_txn);
> > > +                ovsrec_port_set_name(port, port_name);
> > > +                ovsrec_port_set_interfaces(port, &iface, 1);
> > > +
> > > +                ovsrec_bridge_update_ports_addvalue(b_ctx_in->br_int, port);
> > > +
> > > +                smap_destroy(&options);
> > > +                free(port_name);
> > > +                free(key);
> > > +
> > > +                VLOG_INFO("Creating Mirror in OVS DB");
> > > +                mirror = ovsrec_mirror_insert(b_ctx_in->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(b_ctx_in->br_int,
> > > +                                                                 mirror);
> > > +            }
> > > +
> > > +            const struct ovsrec_interface *iface_rec;
> > > +            const char *iface_id;
> > > +            /* find the interface corresponding to the pb->logical_port */
> > > +            OVSREC_INTERFACE_TABLE_FOR_EACH (iface_rec,
> > > +                                             b_ctx_in->iface_table) {
> > > +                iface_id = smap_get(&iface_rec->external_ids, "iface-id");
> > > +                if (iface_id) {
> > > +                    if (!strcmp(iface_id, pb->logical_port)) {
> > > +                        VLOG_INFO("Found the interface mapped to physical");
> > > +                        break;
> > > +                    }
> > > +                }
> > > +            }
> > > +
> > > +            const struct ovsrec_port *p =
> > > +                              get_port_for_iface(iface_rec,b_ctx_in->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);
> > > +                }
> > > +            }
> > > +        }
> > > +    }
> > > +}
> > > +
> > > +static void
> > > +mirror_delete(const struct sbrec_port_binding *pb,
> > > +              struct binding_ctx_in *b_ctx_in,
> > > +              struct shash *pb_mirror_map)
> > > +{
> > > +    struct sset pb_mirrors = SSET_INITIALIZER(&pb_mirrors);
> > > +
> > > +    for (size_t i = 0; i < pb->n_mirror_rules ; i++) {
> > > +        sset_add(&pb_mirrors, pb->mirror_rules[i]->name);
> > > +    }
> > > +
> > > +    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(b_ctx_in->br_int,
> > > +                                                    ovs_mirror->output_port);
> > > +                ovsrec_bridge_update_mirrors_delvalue(b_ctx_in->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 binding_ctx_in *b_ctx_in,
> > > +                            const struct ovsrec_mirror *mirror,
> > > +                            struct shash *pb_mirror_map)
> > > +{
> > > +    OVSREC_MIRROR_TABLE_FOR_EACH (mirror, b_ctx_in->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);
> > > +                }
> > > +            }
> > > +        }
> > > +    }
> > > +}
> > > +
> > > +
> > >  /* Returns true if the port binding changes resulted in local binding
> > >   * updates, false otherwise.
> > >   */
> > > @@ -2485,6 +2724,42 @@ delete_done:
> > >      }
> > >
> > >      destroy_qos_map(&qos_map);
> > > +
> > > +    /* Handle Mirror Rule updates */
> > > +    SBREC_PORT_BINDING_TABLE_FOR_EACH_TRACKED (pb,
> > > +                                               b_ctx_in->port_binding_table) {
> > > +
> > > +        const struct ovsrec_mirror *mirror = NULL;
> > > +        struct shash port_ovs_mirrors = SHASH_INITIALIZER(&port_ovs_mirrors);
> > > +
> > > +        /* Need to find if mirror needs update */
> > > +        find_port_specific_mirrors(pb, b_ctx_in, mirror, &port_ovs_mirrors);
> > > +        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
> > > +                 */
> > > +                mirror_create(pb, b_ctx_in, mirror);
> > > +            } else {
> > > +                /* delete mirror,
> > > +                 * if mirror has other sources only update selection
> > > +                 */
> > > +                mirror_delete(pb, b_ctx_in, &port_ovs_mirrors);
> > > +            }
> > > +        }
> > > +        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 handled;
> > >  }
> > >
> > > diff --git a/controller/binding.h b/controller/binding.h
> > > index 430a8d9b1..842bb6ab1 100644
> > > --- a/controller/binding.h
> > > +++ b/controller/binding.h
> > > @@ -33,8 +33,10 @@ struct ovsrec_port_table;
> > >  struct ovsrec_qos_table;
> > >  struct ovsrec_bridge_table;
> > >  struct ovsrec_open_vswitch_table;
> > > +struct ovsrec_mirror_table;
> > >  struct sbrec_chassis;
> > >  struct sbrec_port_binding_table;
> > > +struct sbrec_mirror_table;
> > >  struct sset;
> > >  struct sbrec_port_binding;
> > >  struct ds;
> > > @@ -48,7 +50,9 @@ struct binding_ctx_in {
> > >      struct ovsdb_idl_index *sbrec_port_binding_by_name;
> > >      const struct ovsrec_port_table *port_table;
> > >      const struct ovsrec_qos_table *qos_table;
> > > +    const struct ovsrec_mirror_table *mirror_table;
> > >      const struct sbrec_port_binding_table *port_binding_table;
> > > +    const struct sbrec_mirror_table *sb_mirror_table;
> > >      const struct ovsrec_bridge *br_int;
> > >      const struct sbrec_chassis *chassis_rec;
> > >      const struct sset *active_tunnels;
> > > diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c
> > > index 22b7fa935..35f79940a 100644
> > > --- a/controller/ovn-controller.c
> > > +++ b/controller/ovn-controller.c
> > > @@ -977,6 +977,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 {
> > > @@ -994,7 +995,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,
> > > @@ -1222,10 +1224,18 @@ init_binding_ctx(struct engine_node *node,
> > >          (struct ovsrec_qos_table *)EN_OVSDB_GET(
> > >              engine_get_input("OVS_qos", 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 ovsdb_idl_index *sbrec_datapath_binding_by_key =
> > >          engine_ovsdb_node_get_index(
> > >                  engine_get_input("SB_datapath_binding", node),
> > > @@ -1251,7 +1261,9 @@ init_binding_ctx(struct engine_node *node,
> > >      b_ctx_in->port_table = port_table;
> > >      b_ctx_in->iface_table = iface_table;
> > >      b_ctx_in->qos_table = qos_table;
> > > +    b_ctx_in->mirror_table = mirror_table;
> > >      b_ctx_in->port_binding_table = pb_table;
> > > +    b_ctx_in->sb_mirror_table = sb_mirror_table;
> > >      b_ctx_in->br_int = br_int;
> > >      b_ctx_in->chassis_rec = chassis;
> > >      b_ctx_in->active_tunnels = &rt_data->active_tunnels;
> > > @@ -3464,8 +3476,10 @@ main(int argc, char *argv[])
> > >      engine_add_input(&en_runtime_data, &en_ovs_open_vswitch, NULL);
> > >      engine_add_input(&en_runtime_data, &en_ovs_bridge, NULL);
> > >      engine_add_input(&en_runtime_data, &en_ovs_qos, NULL);
> > > +    engine_add_input(&en_runtime_data, &en_ovs_mirror, NULL);
> > >
> > >      engine_add_input(&en_runtime_data, &en_sb_chassis, NULL);
> > > +    engine_add_input(&en_runtime_data, &en_sb_mirror, NULL);
> > >      engine_add_input(&en_runtime_data, &en_sb_datapath_binding,
> > >                       runtime_data_sb_datapath_binding_handler);
> > >      engine_add_input(&en_runtime_data, &en_sb_port_binding,
> > > 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 43093cb5a..f77daafb8 100644
> > > --- a/northd/inc-proc-northd.c
> > > +++ b/northd/inc-proc-northd.c
> > > @@ -48,6 +48,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") \
> > > @@ -90,6 +91,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") \
> > > @@ -168,6 +170,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);
> > > @@ -190,6 +193,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 a56666297..455458514 100644
> > > --- a/northd/northd.c
> > > +++ b/northd/northd.c
> > > @@ -3204,6 +3204,51 @@ ovn_port_update_sbrec_chassis(
> > >      }
> > >  }
> > >
> > > +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,
> > > @@ -3548,6 +3593,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);
> > > @@ -14781,6 +14837,67 @@ sync_meters(struct northd_input *input_data,
> > >      shash_destroy(&sb_meters);
> > >  }
> > >
> > > +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) {
> > > +        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.
> > > @@ -15388,6 +15505,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 2d804a22e..c30692864 100644
> > > --- a/northd/northd.h
> > > +++ b/northd/northd.h
> > > @@ -30,6 +30,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;
> > > @@ -49,6 +50,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/northd/ovn-nb.dlopts b/northd/ovn-nb.dlopts
> > > index 9a460adef..797ca523f 100644
> > > --- a/northd/ovn-nb.dlopts
> > > +++ b/northd/ovn-nb.dlopts
> > > @@ -20,6 +20,7 @@
> > >  --intern-table Load_Balancer
> > >  --intern-table Logical_Switch
> > >  --intern-table Load_Balancer_Health_Check
> > > +--intern-table Mirror
> > >  --intern-table Meter
> > >  --intern-table NAT
> > >  --intern-table Address_Set
> > > diff --git a/northd/ovn-sb.dlopts b/northd/ovn-sb.dlopts
> > > index 99b65f101..82d614029 100644
> > > --- a/northd/ovn-sb.dlopts
> > > +++ b/northd/ovn-sb.dlopts
> > > @@ -13,6 +13,7 @@
> > >  -o Load_Balancer
> > >  -o Logical_DP_Group
> > >  -o MAC_Binding
> > > +-o Mirror
> > >  -o Meter
> > >  -o Meter_Band
> > >  -o Multicast_Group
> > > diff --git a/northd/ovn_northd.dl b/northd/ovn_northd.dl
> > > index 2fe73959c..22a502fcc 100644
> > > --- a/northd/ovn_northd.dl
> > > +++ b/northd/ovn_northd.dl
> > > @@ -28,6 +28,16 @@ import set
> > >
> > >  index Logical_Flow_Index() on sb::Out_Logical_Flow()
> > >
> > > +/* Mirror table */
> > > +for (mirror in &nb::Mirror) {
> > > +    sb::Out_Mirror(._uuid = mirror._uuid,
> > > +                 .name = mirror.name,
> > > +                 .filter = mirror.filter,
> > > +                 .sink = mirror.sink,
> > > +                 .type = mirror.type,
> > > +                 .index = mirror.index)
> > > +}
> > > +
> > >  /* Meter_Band table */
> > >  for (mb in nb::Meter_Band) {
> > >      sb::Out_Meter_Band(._uuid = mb._uuid,
> > > 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 9010240a8..c514b7733 100644
> > > --- a/ovn-nb.xml
> > > +++ b/ovn-nb.xml
> > > @@ -1511,6 +1511,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.
> > > @@ -2432,6 +2437,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 66664c840..0493bce7b 100644
> > > --- a/ovn-sb.ovsschema
> > > +++ b/ovn-sb.ovsschema
> > > @@ -1,7 +1,7 @@
> > >  {
> > >      "name": "OVN_Southbound",
> > > -    "version": "20.22.0",
> > > -    "cksum": "1686121686 27471",
> > > +    "version": "20.23.0",
> > > +    "cksum": "654011660 28470",
> > >      "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"},
> > > @@ -222,6 +236,11 @@
> > >                                              "refTable": "Encap",
> > >                                               "refType": "weak"},
> > >                                      "min": 0, "max": 1}},
> > > +                "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 1ffb24e7a..6a81021a4 100644
> > > --- a/ovn-sb.xml
> > > +++ b/ovn-sb.xml
> > > @@ -2632,6 +2632,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
> > > @@ -3082,6 +3123,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 f9b9defd0..7643f8884 100644
> > > --- a/tests/ovn-nbctl.at
> > > +++ b/tests/ovn-nbctl.at
> > > @@ -435,6 +435,86 @@ 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 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/utilities/ovn-nbctl.c b/utilities/ovn-nbctl.c
> > > index e747f6933..f6ab002ef 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)
> > >  {
> > > @@ -7161,6 +7297,213 @@ 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) {
> > > +            if (mirror->n_src > 0) {
> > > +                ctl_error(ctx, "Detach mirror source(s) before deletion");
> > > +                return;
> > > +            }
> > > +            nbrec_mirror_delete(mirror);
> > > +        }
> > > +        return;
> > > +    }
> > > +
> > > +    /* Remove the matching mirror. */
> > > +    NBREC_MIRROR_FOR_EACH (mirror, ctx->idl) {
> > > +        if (strcmp(ctx->argv[1], mirror->name)) {
> > > +            continue;
> > > +        }
> > > +        if (mirror->n_src > 0) {
> > > +            ctl_error(ctx, "Detach source(s) before mirror %s deletion",
> > > +                      mirror->name);
> > > +            return;
> > > +        }
> > > +        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,
> > > @@ -7254,6 +7597,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 },
> > > @@ -7308,6 +7660,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 b008b5d0b..14c24dfb9 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);
> > > @@ -1424,6 +1425,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
> > >
> >
Numan Siddique May 30, 2022, 4:13 p.m. UTC | #5
Hi Abhiram,

Thanks for the patch.

Please see below for some comments.



On Mon, May 9, 2022 at 12:14 PM Abhiram R N <abhiramrn@gmail.com> wrote:
>
> On Mon, May 09, 2022 at 03:34:47PM +0530, Abhiram R N wrote:
>
> Hi Ihar,
>
> <just updating the test case link I had provided earlir for reference>
>
> > On Fri, May 06, 2022 at 07:46:17PM -0400, Ihar Hrachyshka wrote:
> > > Hi Abhiram,
> > >
> > > thanks for sending the patch.
> >
> > Thanks for spening time to review it.
> >
> > >
> > > This is *not* a comprehensive review. This is an attempt to clarify
> > > the scope of the feature, its potential crossover with other pending
> > > work, and other high level comments.
> > >
> > > API:
> > > 1) The proposal more or less directly maps the new OVN mirrors to OVS
> > > mirror capabilities. Meaning, it doesn't support any advanced filters
> > > to pre-filter traffic mirrored to a sink. Do you think that maybe we
> > > will eventually need an ability to filter out some of from/to traffic
> > > inside the cluster to optimize for network performance? Ideally, we
> > > would support some form of "match" capability like we do for ACLs.
> > > AFAIU ovs mirrors don't support such filtering, but maybe it means
> > > that they shouldn't be used in OVN mirrors? We could as well program
> > > flows to achieve the same effect (plus advanced filters and more).
> > >
> > > I may be wrong on the usefulness of "matching" filtering for port
> > > mirroring though. I've checked some Tap-as-a-Service definitions and
> > > they don't seem to include filtering options beyond to/from port +
> > > vlan tags. That said, I don't see why we wouldn't want to allow a user
> > > to pre-filter mirrored traffic. Thoughts?
> > >
> >
> > The initial goal is exactly what you mentioned at the beginning
> > i.e to match the new OVN mirrors to OVS mirror capabilities.
> > Currently the focus was only on that.
> > Regarding the *match* thing I think its definitely worth keeping
> > it in the backlog and do it as future work (or atleast do more
> > re-search on how/if it can be useful)

I'd agree with keeping the initial goal to use the OVS mirrors.

Later if there are use cases to use advance filtering before mirroring,
the internal implementation can be changed to make use of OpenFlows directly.
Although I'm not sure how easy it would be.

To start with I'd agree making use of OVS mirrors.


> >
> > > 2) This proposal is limited to remote "gre" mirroring. Do you think we
> > > should be able to use the same API to mirror to a OVN port inside the
> > > cluster? Does the proposed API allow for that? Could e.g. Mirror:type=
> > > be set to "internal" and Mirror:sink= to a logical port name? Would
> > > there be any issue with such later overload of the mirror API?
> > >

I'd restrict this patch to add the support for external port mirroring.
For internal port mirroring I think we can make use of Ihar's multiple
chassis request feature.
Without the RARP activation strategy I think multiple chassis request
feature can be used
for internal port mirroring.  For full internal port mirroring support
maybe some enhancements are required.


> >
> > Just for info, we have added support for "erspan" as well along with "gre".
> >
> > Regarding your question to set type "internal" and sink "logical port name"
> > I think it is definitely possible to support.
> >
> > If I have to think through from implementation point of view
> > It might need some changes in nbctl for parameter check.
> > Further we have to branch the handling in OVN controller code when we
> > get port binding update handler call, based on the "type" either it follows
> > the remote path(in this patch) or local(inside cluster) code path.
> >
> > Having said that provinding option now is like marrying the 2 different
> > code paths. Because we are trying to re-use existing OVS way of mirroing
> > for the remote and for the the local(inside cluster) its a different
> > mechanism. Is it like early to marry the 2 paths now? Rather commit
> > separately and marry them as a future work!
> >
> > Open for comments on this.
> >
> > > Testing:
> > > 1) it's great you included nbctl tests. We will also need tests that
> > > would demonstrate that mirrors are doing what is expected: set up
> > > mirrors and ports, set up external analyzer / logger, then send some
> > > traffic to / from the mirrored port and check that expected packets
> > > are delivered through the mirror. Such a test will also help to better
> > > understand the intent of the new API.
> >
> > Currently I have tested it on my local setup using the ansible script as
> > below. But instead of the OVS commands I have used the new OVN ones.
> >
> > https://github.com/rh-nfv-int/hw-offload-playbooks/blob/main/testRemotePortMirroring.yml

Totally agree with Ihar.  This patch needs more test cases which test
the port mirroring scenarios.
I think it should be possible to add some test cases in ovn.at using
the sandboxes.


>
> Above link is not correct. Please refer below link
> https://github.com/rh-nfv-int/playbooks/blob/main/regression/ovs-hwol/testRemotePortMirroring.yml
>
> >
> > Originally it used OVS commands:
> >             ovs-vsctl add-port br-int gre0 -- set Interface gre0 type=gre
> >                               options:local_ip=10.10.10.1 options:remote_ip=10.10.10.2
> >                                   options:key=0 -- --id=@p get port gre0
> >                                   -- --id=@m create mirror name=mirror0 output-port=@p
> >                                   -- set bridge br-int mirrors=@m
> >             ovs-vsctl -- --id=@p1 get Port {{ physical_interface }}_0 -- add Mirror mirror0 select-dst-port @p1
> >             ovs-vsctl -- --id=@p1 get Port {{ physical_interface }}_0 -- add Mirror mirror0 select-src-port @p1
> >
> > Now, instead of the above OVS commands below OVN commands I used.
> > ovn-nbctl mirror-add mirror0 gre 0 both 10.10.10.2
> > ovn-nbctl lsp-attach-mirror sw0-port1 mirror0
> >
> > But, I am not very sure on how to fit in such a test inside the OVN.
> > Maybe if you can point to some tests or give some guidance there it will help.
> >
> > >
> > > Miscellaneous smaller comments:
> > > 1) ddlog is no longer supported and should be removed from the tree.
> > > We no longer expect the test suite to pass with ddlog, nor we expect
> > > ddlog northd implementation changes. You can safely exclude those from
> > > next patch respins.
> >
> > Sure. Will take care of that . You mean these 3 files I can safely remove
> > right? [ovn-nb.dlopts, ovn-sb.dlopts, ovn_northd.dl]
> >
> > > 2) Update NEWS file with the new feature.
> >
> > Sure. Will update it.
> >
> > > 3) I think ovn-nbctl man page needs an update. Perhaps also
> > > ovn-architecture man page.
> >
> > Sure. Will update it.
> >
> > >
> > > Thanks for working on it,
> > > Ihar
> > Thanks again for taking time to give your valuable comments.
> > Regards,
> > Abhiram R N
> > >
> > >
> > > On Tue, May 3, 2022 at 3:27 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 targetted 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>
> > > > ---
> > > >  controller/binding.c        | 275 ++++++++++++++++++++++++++++
> > > >  controller/binding.h        |   4 +
> > > >  controller/ovn-controller.c |  16 +-
> > > >  northd/en-northd.c          |   4 +
> > > >  northd/inc-proc-northd.c    |   4 +
> > > >  northd/northd.c             | 118 ++++++++++++
> > > >  northd/northd.h             |   2 +
> > > >  northd/ovn-nb.dlopts        |   1 +
> > > >  northd/ovn-sb.dlopts        |   1 +
> > > >  northd/ovn_northd.dl        |  10 +
> > > >  ovn-nb.ovsschema            |  31 +++-
> > > >  ovn-nb.xml                  |  57 ++++++
> > > >  ovn-sb.ovsschema            |  23 ++-
> > > >  ovn-sb.xml                  |  46 +++++
> > > >  tests/ovn-nbctl.at          |  80 ++++++++
> > > >  utilities/ovn-nbctl.c       | 356 ++++++++++++++++++++++++++++++++++++
> > > >  utilities/ovn-sbctl.c       |   4 +
> > > >  17 files changed, 1027 insertions(+), 5 deletions(-)
> > > >
> > > > diff --git a/controller/binding.c b/controller/binding.c
> > > > index 7281b0485..471470cfa 100644
> > > > --- a/controller/binding.c
> > > > +++ b/controller/binding.c
> > > > @@ -64,6 +64,7 @@ binding_register_ovs_idl(struct ovsdb_idl *ovs_idl)
> > > >      ovsdb_idl_add_table(ovs_idl, &ovsrec_table_bridge);
> > > >      ovsdb_idl_add_column(ovs_idl, &ovsrec_bridge_col_name);
> > > >      ovsdb_idl_add_column(ovs_idl, &ovsrec_bridge_col_ports);
> > > > +    ovsdb_idl_add_column(ovs_idl, &ovsrec_bridge_col_mirrors);
> > > >
> > > >      ovsdb_idl_add_table(ovs_idl, &ovsrec_table_port);
> > > >      ovsdb_idl_track_add_column(ovs_idl, &ovsrec_port_col_name);
> > > > @@ -79,6 +80,12 @@ binding_register_ovs_idl(struct ovsdb_idl *ovs_idl)
> > > >
> > > >      ovsdb_idl_add_table(ovs_idl, &ovsrec_table_qos);
> > > >      ovsdb_idl_add_column(ovs_idl, &ovsrec_qos_col_type);
> > > > +
> > > > +    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);
> > > >  }
> > > >
> > > >  static void update_lport_tracking(const struct sbrec_port_binding *pb,
> > > > @@ -2226,6 +2233,238 @@ consider_patch_port_for_local_datapaths(const struct sbrec_port_binding *pb,
> > > >      }
> > > >  }
> > > >
> > > > +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 void
> > > > +mirror_create(const struct sbrec_port_binding *pb,
> > > > +              struct binding_ctx_in *b_ctx_in,
> > > > +              const struct ovsrec_mirror *mirror)
> > > > +{
> > > > +    if (pb->up[0] == true) {
> > > > +        VLOG_INFO("Mirror rule(s) present for %s ", pb->logical_port);
> > > > +        /* Loop through the mirror rules */
> > > > +        for (int 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, b_ctx_in->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, "erspan_idx", key);
> > > > +                    smap_add(&options, "erspan_ver","1");
> > > > +
> > > > +                }
> > > > +                struct ovsrec_interface *iface =
> > > > +                          ovsrec_interface_insert(b_ctx_in->ovs_idl_txn);
> > > > +                port_name = xasprintf("ovn-%s-%s",
> > > > +                                       pb->mirror_rules[i]->type,
> > > > +                                       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(b_ctx_in->ovs_idl_txn);
> > > > +                ovsrec_port_set_name(port, port_name);
> > > > +                ovsrec_port_set_interfaces(port, &iface, 1);
> > > > +
> > > > +                ovsrec_bridge_update_ports_addvalue(b_ctx_in->br_int, port);
> > > > +
> > > > +                smap_destroy(&options);
> > > > +                free(port_name);
> > > > +                free(key);
> > > > +
> > > > +                VLOG_INFO("Creating Mirror in OVS DB");
> > > > +                mirror = ovsrec_mirror_insert(b_ctx_in->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(b_ctx_in->br_int,
> > > > +                                                                 mirror);
> > > > +            }
> > > > +
> > > > +            const struct ovsrec_interface *iface_rec;
> > > > +            const char *iface_id;
> > > > +            /* find the interface corresponding to the pb->logical_port */
> > > > +            OVSREC_INTERFACE_TABLE_FOR_EACH (iface_rec,
> > > > +                                             b_ctx_in->iface_table) {
> > > > +                iface_id = smap_get(&iface_rec->external_ids, "iface-id");
> > > > +                if (iface_id) {
> > > > +                    if (!strcmp(iface_id, pb->logical_port)) {
> > > > +                        VLOG_INFO("Found the interface mapped to physical");
> > > > +                        break;
> > > > +                    }
> > > > +                }
> > > > +            }
> > > > +
> > > > +            const struct ovsrec_port *p =
> > > > +                              get_port_for_iface(iface_rec,b_ctx_in->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);
> > > > +                }
> > > > +            }
> > > > +        }
> > > > +    }
> > > > +}
> > > > +
> > > > +static void
> > > > +mirror_delete(const struct sbrec_port_binding *pb,
> > > > +              struct binding_ctx_in *b_ctx_in,
> > > > +              struct shash *pb_mirror_map)
> > > > +{
> > > > +    struct sset pb_mirrors = SSET_INITIALIZER(&pb_mirrors);
> > > > +
> > > > +    for (size_t i = 0; i < pb->n_mirror_rules ; i++) {
> > > > +        sset_add(&pb_mirrors, pb->mirror_rules[i]->name);
> > > > +    }
> > > > +
> > > > +    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(b_ctx_in->br_int,
> > > > +                                                    ovs_mirror->output_port);
> > > > +                ovsrec_bridge_update_mirrors_delvalue(b_ctx_in->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 binding_ctx_in *b_ctx_in,
> > > > +                            const struct ovsrec_mirror *mirror,
> > > > +                            struct shash *pb_mirror_map)
> > > > +{
> > > > +    OVSREC_MIRROR_TABLE_FOR_EACH (mirror, b_ctx_in->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);
> > > > +                }
> > > > +            }
> > > > +        }
> > > > +    }
> > > > +}
> > > > +
> > > > +
> > > >  /* Returns true if the port binding changes resulted in local binding
> > > >   * updates, false otherwise.
> > > >   */
> > > > @@ -2485,6 +2724,42 @@ delete_done:
> > > >      }
> > > >
> > > >      destroy_qos_map(&qos_map);
> > > > +
> > > > +    /* Handle Mirror Rule updates */
> > > > +    SBREC_PORT_BINDING_TABLE_FOR_EACH_TRACKED (pb,
> > > > +                                               b_ctx_in->port_binding_table) {
> > > > +
> > > > +        const struct ovsrec_mirror *mirror = NULL;
> > > > +        struct shash port_ovs_mirrors = SHASH_INITIALIZER(&port_ovs_mirrors);
> > > > +
> > > > +        /* Need to find if mirror needs update */
> > > > +        find_port_specific_mirrors(pb, b_ctx_in, mirror, &port_ovs_mirrors);
> > > > +        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
> > > > +                 */
> > > > +                mirror_create(pb, b_ctx_in, mirror);
> > > > +            } else {
> > > > +                /* delete mirror,
> > > > +                 * if mirror has other sources only update selection
> > > > +                 */
> > > > +                mirror_delete(pb, b_ctx_in, &port_ovs_mirrors);
> > > > +            }
> > > > +        }
> > > > +        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 handled;
> > > >  }
> > > >
> > > > diff --git a/controller/binding.h b/controller/binding.h
> > > > index 430a8d9b1..842bb6ab1 100644
> > > > --- a/controller/binding.h
> > > > +++ b/controller/binding.h
> > > > @@ -33,8 +33,10 @@ struct ovsrec_port_table;
> > > >  struct ovsrec_qos_table;
> > > >  struct ovsrec_bridge_table;
> > > >  struct ovsrec_open_vswitch_table;
> > > > +struct ovsrec_mirror_table;
> > > >  struct sbrec_chassis;
> > > >  struct sbrec_port_binding_table;
> > > > +struct sbrec_mirror_table;
> > > >  struct sset;
> > > >  struct sbrec_port_binding;
> > > >  struct ds;
> > > > @@ -48,7 +50,9 @@ struct binding_ctx_in {
> > > >      struct ovsdb_idl_index *sbrec_port_binding_by_name;
> > > >      const struct ovsrec_port_table *port_table;
> > > >      const struct ovsrec_qos_table *qos_table;
> > > > +    const struct ovsrec_mirror_table *mirror_table;
> > > >      const struct sbrec_port_binding_table *port_binding_table;
> > > > +    const struct sbrec_mirror_table *sb_mirror_table;
> > > >      const struct ovsrec_bridge *br_int;
> > > >      const struct sbrec_chassis *chassis_rec;
> > > >      const struct sset *active_tunnels;
> > > > diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c
> > > > index 22b7fa935..35f79940a 100644
> > > > --- a/controller/ovn-controller.c
> > > > +++ b/controller/ovn-controller.c
> > > > @@ -977,6 +977,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 {
> > > > @@ -994,7 +995,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,
> > > > @@ -1222,10 +1224,18 @@ init_binding_ctx(struct engine_node *node,
> > > >          (struct ovsrec_qos_table *)EN_OVSDB_GET(
> > > >              engine_get_input("OVS_qos", 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 ovsdb_idl_index *sbrec_datapath_binding_by_key =
> > > >          engine_ovsdb_node_get_index(
> > > >                  engine_get_input("SB_datapath_binding", node),
> > > > @@ -1251,7 +1261,9 @@ init_binding_ctx(struct engine_node *node,
> > > >      b_ctx_in->port_table = port_table;
> > > >      b_ctx_in->iface_table = iface_table;
> > > >      b_ctx_in->qos_table = qos_table;
> > > > +    b_ctx_in->mirror_table = mirror_table;
> > > >      b_ctx_in->port_binding_table = pb_table;
> > > > +    b_ctx_in->sb_mirror_table = sb_mirror_table;
> > > >      b_ctx_in->br_int = br_int;
> > > >      b_ctx_in->chassis_rec = chassis;
> > > >      b_ctx_in->active_tunnels = &rt_data->active_tunnels;
> > > > @@ -3464,8 +3476,10 @@ main(int argc, char *argv[])
> > > >      engine_add_input(&en_runtime_data, &en_ovs_open_vswitch, NULL);
> > > >      engine_add_input(&en_runtime_data, &en_ovs_bridge, NULL);
> > > >      engine_add_input(&en_runtime_data, &en_ovs_qos, NULL);
> > > > +    engine_add_input(&en_runtime_data, &en_ovs_mirror, NULL);
> > > >
> > > >      engine_add_input(&en_runtime_data, &en_sb_chassis, NULL);
> > > > +    engine_add_input(&en_runtime_data, &en_sb_mirror, NULL);
> > > >      engine_add_input(&en_runtime_data, &en_sb_datapath_binding,
> > > >                       runtime_data_sb_datapath_binding_handler);
> > > >      engine_add_input(&en_runtime_data, &en_sb_port_binding,
> > > > 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 43093cb5a..f77daafb8 100644
> > > > --- a/northd/inc-proc-northd.c
> > > > +++ b/northd/inc-proc-northd.c
> > > > @@ -48,6 +48,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") \
> > > > @@ -90,6 +91,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") \
> > > > @@ -168,6 +170,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);
> > > > @@ -190,6 +193,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 a56666297..455458514 100644
> > > > --- a/northd/northd.c
> > > > +++ b/northd/northd.c
> > > > @@ -3204,6 +3204,51 @@ ovn_port_update_sbrec_chassis(
> > > >      }
> > > >  }
> > > >
> > > > +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,
> > > > @@ -3548,6 +3593,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);
> > > > @@ -14781,6 +14837,67 @@ sync_meters(struct northd_input *input_data,
> > > >      shash_destroy(&sb_meters);
> > > >  }
> > > >
> > > > +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) {
> > > > +        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.
> > > > @@ -15388,6 +15505,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 2d804a22e..c30692864 100644
> > > > --- a/northd/northd.h
> > > > +++ b/northd/northd.h
> > > > @@ -30,6 +30,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;
> > > > @@ -49,6 +50,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/northd/ovn-nb.dlopts b/northd/ovn-nb.dlopts
> > > > index 9a460adef..797ca523f 100644
> > > > --- a/northd/ovn-nb.dlopts
> > > > +++ b/northd/ovn-nb.dlopts
> > > > @@ -20,6 +20,7 @@
> > > >  --intern-table Load_Balancer
> > > >  --intern-table Logical_Switch
> > > >  --intern-table Load_Balancer_Health_Check
> > > > +--intern-table Mirror
> > > >  --intern-table Meter
> > > >  --intern-table NAT
> > > >  --intern-table Address_Set
> > > > diff --git a/northd/ovn-sb.dlopts b/northd/ovn-sb.dlopts
> > > > index 99b65f101..82d614029 100644
> > > > --- a/northd/ovn-sb.dlopts
> > > > +++ b/northd/ovn-sb.dlopts
> > > > @@ -13,6 +13,7 @@
> > > >  -o Load_Balancer
> > > >  -o Logical_DP_Group
> > > >  -o MAC_Binding
> > > > +-o Mirror
> > > >  -o Meter
> > > >  -o Meter_Band
> > > >  -o Multicast_Group
> > > > diff --git a/northd/ovn_northd.dl b/northd/ovn_northd.dl
> > > > index 2fe73959c..22a502fcc 100644
> > > > --- a/northd/ovn_northd.dl
> > > > +++ b/northd/ovn_northd.dl
> > > > @@ -28,6 +28,16 @@ import set
> > > >
> > > >  index Logical_Flow_Index() on sb::Out_Logical_Flow()
> > > >
> > > > +/* Mirror table */
> > > > +for (mirror in &nb::Mirror) {
> > > > +    sb::Out_Mirror(._uuid = mirror._uuid,
> > > > +                 .name = mirror.name,
> > > > +                 .filter = mirror.filter,
> > > > +                 .sink = mirror.sink,
> > > > +                 .type = mirror.type,
> > > > +                 .index = mirror.index)
> > > > +}
> > > > +
> > > >  /* Meter_Band table */
> > > >  for (mb in nb::Meter_Band) {
> > > >      sb::Out_Meter_Band(._uuid = mb._uuid,
> > > > 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 9010240a8..c514b7733 100644
> > > > --- a/ovn-nb.xml
> > > > +++ b/ovn-nb.xml
> > > > @@ -1511,6 +1511,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.
> > > > @@ -2432,6 +2437,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

I see that the newly added table "Mirror" has references to the
Logical switch port table
and Logical switch port table also has reference to Mirror table.

Does this really required ?

I think it should be enough if Port binding table references Mirror table.

I've a few general comments about this patch

-  I don't think binding.c is the right place to create OVS mirrors.
-  You've also missed out on handling the changes when a full
recompute happens (i.e when binding_run()) is called.

I'd suggest adding a new file called mirror.c  (or external_mirror.c)
which would have functions like

mirror_init(),
mirror_run(),
mirror_handle_lport_changes(),
mirror_handlle_sb_mirror_changes()

You can add a new engine node called - port_mirror which would have
following inputs
   - sbrec_mirror
   - runtime_data_handler
   - sbrec_port_binding

Full recompute of this engine would call - mirror_run() which would
create/delete ovs mirrors by iterating all the sbrec_mirrors (see
binding_run() for example)
And handle the port binding/runtime data changes incrementally.

Your patch should also make sure that
   - when a port is claimed by ovn-controller, only then it should
create/associate a mirror for this port
   - when a port is released by ovn-controller, it should disassociate
the corresponding ovs port from the mirror

The above scenarios  doesn't seemed to handled completely in this patch.

And also please add test cases covering all the various scenarios and
making sure that the corresponding mirror related row/columns are set
ot unset properly in the
local OVS database.

Thanks
Numan

> > > > diff --git a/ovn-sb.ovsschema b/ovn-sb.ovsschema
> > > > index 66664c840..0493bce7b 100644
> > > > --- a/ovn-sb.ovsschema
> > > > +++ b/ovn-sb.ovsschema
> > > > @@ -1,7 +1,7 @@
> > > >  {
> > > >      "name": "OVN_Southbound",
> > > > -    "version": "20.22.0",
> > > > -    "cksum": "1686121686 27471",
> > > > +    "version": "20.23.0",
> > > > +    "cksum": "654011660 28470",
> > > >      "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"},
> > > > @@ -222,6 +236,11 @@
> > > >                                              "refTable": "Encap",
> > > >                                               "refType": "weak"},
> > > >                                      "min": 0, "max": 1}},
> > > > +                "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 1ffb24e7a..6a81021a4 100644
> > > > --- a/ovn-sb.xml
> > > > +++ b/ovn-sb.xml
> > > > @@ -2632,6 +2632,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
> > > > @@ -3082,6 +3123,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 f9b9defd0..7643f8884 100644
> > > > --- a/tests/ovn-nbctl.at
> > > > +++ b/tests/ovn-nbctl.at
> > > > @@ -435,6 +435,86 @@ 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 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/utilities/ovn-nbctl.c b/utilities/ovn-nbctl.c
> > > > index e747f6933..f6ab002ef 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)
> > > >  {
> > > > @@ -7161,6 +7297,213 @@ 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) {
> > > > +            if (mirror->n_src > 0) {
> > > > +                ctl_error(ctx, "Detach mirror source(s) before deletion");
> > > > +                return;
> > > > +            }
> > > > +            nbrec_mirror_delete(mirror);
> > > > +        }
> > > > +        return;
> > > > +    }
> > > > +
> > > > +    /* Remove the matching mirror. */
> > > > +    NBREC_MIRROR_FOR_EACH (mirror, ctx->idl) {
> > > > +        if (strcmp(ctx->argv[1], mirror->name)) {
> > > > +            continue;
> > > > +        }
> > > > +        if (mirror->n_src > 0) {
> > > > +            ctl_error(ctx, "Detach source(s) before mirror %s deletion",
> > > > +                      mirror->name);
> > > > +            return;
> > > > +        }
> > > > +        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,
> > > > @@ -7254,6 +7597,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 },
> > > > @@ -7308,6 +7660,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 b008b5d0b..14c24dfb9 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);
> > > > @@ -1424,6 +1425,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
> > > >
> > >
> _______________________________________________
> dev mailing list
> dev@openvswitch.org
> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
>
diff mbox series

Patch

diff --git a/controller/binding.c b/controller/binding.c
index 7281b0485..471470cfa 100644
--- a/controller/binding.c
+++ b/controller/binding.c
@@ -64,6 +64,7 @@  binding_register_ovs_idl(struct ovsdb_idl *ovs_idl)
     ovsdb_idl_add_table(ovs_idl, &ovsrec_table_bridge);
     ovsdb_idl_add_column(ovs_idl, &ovsrec_bridge_col_name);
     ovsdb_idl_add_column(ovs_idl, &ovsrec_bridge_col_ports);
+    ovsdb_idl_add_column(ovs_idl, &ovsrec_bridge_col_mirrors);
 
     ovsdb_idl_add_table(ovs_idl, &ovsrec_table_port);
     ovsdb_idl_track_add_column(ovs_idl, &ovsrec_port_col_name);
@@ -79,6 +80,12 @@  binding_register_ovs_idl(struct ovsdb_idl *ovs_idl)
 
     ovsdb_idl_add_table(ovs_idl, &ovsrec_table_qos);
     ovsdb_idl_add_column(ovs_idl, &ovsrec_qos_col_type);
+
+    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);
 }
 
 static void update_lport_tracking(const struct sbrec_port_binding *pb,
@@ -2226,6 +2233,238 @@  consider_patch_port_for_local_datapaths(const struct sbrec_port_binding *pb,
     }
 }
 
+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 void
+mirror_create(const struct sbrec_port_binding *pb,
+              struct binding_ctx_in *b_ctx_in,
+              const struct ovsrec_mirror *mirror)
+{
+    if (pb->up[0] == true) {
+        VLOG_INFO("Mirror rule(s) present for %s ", pb->logical_port);
+        /* Loop through the mirror rules */
+        for (int 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, b_ctx_in->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, "erspan_idx", key);
+                    smap_add(&options, "erspan_ver","1");
+
+                }
+                struct ovsrec_interface *iface =
+                          ovsrec_interface_insert(b_ctx_in->ovs_idl_txn);
+                port_name = xasprintf("ovn-%s-%s",
+                                       pb->mirror_rules[i]->type,
+                                       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(b_ctx_in->ovs_idl_txn);
+                ovsrec_port_set_name(port, port_name);
+                ovsrec_port_set_interfaces(port, &iface, 1);
+
+                ovsrec_bridge_update_ports_addvalue(b_ctx_in->br_int, port);
+
+                smap_destroy(&options);
+                free(port_name);
+                free(key);
+
+                VLOG_INFO("Creating Mirror in OVS DB");
+                mirror = ovsrec_mirror_insert(b_ctx_in->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(b_ctx_in->br_int,
+                                                                 mirror);
+            }
+
+            const struct ovsrec_interface *iface_rec;
+            const char *iface_id;
+            /* find the interface corresponding to the pb->logical_port */
+            OVSREC_INTERFACE_TABLE_FOR_EACH (iface_rec,
+                                             b_ctx_in->iface_table) {
+                iface_id = smap_get(&iface_rec->external_ids, "iface-id");
+                if (iface_id) {
+                    if (!strcmp(iface_id, pb->logical_port)) {
+                        VLOG_INFO("Found the interface mapped to physical");
+                        break;
+                    }
+                }
+            }
+
+            const struct ovsrec_port *p =
+                              get_port_for_iface(iface_rec,b_ctx_in->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);
+                }
+            }
+        }
+    }
+}
+
+static void
+mirror_delete(const struct sbrec_port_binding *pb,
+              struct binding_ctx_in *b_ctx_in,
+              struct shash *pb_mirror_map)
+{
+    struct sset pb_mirrors = SSET_INITIALIZER(&pb_mirrors);
+
+    for (size_t i = 0; i < pb->n_mirror_rules ; i++) {
+        sset_add(&pb_mirrors, pb->mirror_rules[i]->name);
+    }
+
+    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(b_ctx_in->br_int,
+                                                    ovs_mirror->output_port);
+                ovsrec_bridge_update_mirrors_delvalue(b_ctx_in->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 binding_ctx_in *b_ctx_in,
+                            const struct ovsrec_mirror *mirror,
+                            struct shash *pb_mirror_map)
+{
+    OVSREC_MIRROR_TABLE_FOR_EACH (mirror, b_ctx_in->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);
+                }
+            }
+        }
+    }
+}
+
+
 /* Returns true if the port binding changes resulted in local binding
  * updates, false otherwise.
  */
@@ -2485,6 +2724,42 @@  delete_done:
     }
 
     destroy_qos_map(&qos_map);
+
+    /* Handle Mirror Rule updates */
+    SBREC_PORT_BINDING_TABLE_FOR_EACH_TRACKED (pb,
+                                               b_ctx_in->port_binding_table) {
+
+        const struct ovsrec_mirror *mirror = NULL;
+        struct shash port_ovs_mirrors = SHASH_INITIALIZER(&port_ovs_mirrors);
+
+        /* Need to find if mirror needs update */
+        find_port_specific_mirrors(pb, b_ctx_in, mirror, &port_ovs_mirrors);
+        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
+                 */
+                mirror_create(pb, b_ctx_in, mirror);
+            } else {
+                /* delete mirror,
+                 * if mirror has other sources only update selection
+                 */
+                mirror_delete(pb, b_ctx_in, &port_ovs_mirrors);
+            }
+        }
+        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 handled;
 }
 
diff --git a/controller/binding.h b/controller/binding.h
index 430a8d9b1..842bb6ab1 100644
--- a/controller/binding.h
+++ b/controller/binding.h
@@ -33,8 +33,10 @@  struct ovsrec_port_table;
 struct ovsrec_qos_table;
 struct ovsrec_bridge_table;
 struct ovsrec_open_vswitch_table;
+struct ovsrec_mirror_table;
 struct sbrec_chassis;
 struct sbrec_port_binding_table;
+struct sbrec_mirror_table;
 struct sset;
 struct sbrec_port_binding;
 struct ds;
@@ -48,7 +50,9 @@  struct binding_ctx_in {
     struct ovsdb_idl_index *sbrec_port_binding_by_name;
     const struct ovsrec_port_table *port_table;
     const struct ovsrec_qos_table *qos_table;
+    const struct ovsrec_mirror_table *mirror_table;
     const struct sbrec_port_binding_table *port_binding_table;
+    const struct sbrec_mirror_table *sb_mirror_table;
     const struct ovsrec_bridge *br_int;
     const struct sbrec_chassis *chassis_rec;
     const struct sset *active_tunnels;
diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c
index 22b7fa935..35f79940a 100644
--- a/controller/ovn-controller.c
+++ b/controller/ovn-controller.c
@@ -977,6 +977,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 {
@@ -994,7 +995,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,
@@ -1222,10 +1224,18 @@  init_binding_ctx(struct engine_node *node,
         (struct ovsrec_qos_table *)EN_OVSDB_GET(
             engine_get_input("OVS_qos", 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 ovsdb_idl_index *sbrec_datapath_binding_by_key =
         engine_ovsdb_node_get_index(
                 engine_get_input("SB_datapath_binding", node),
@@ -1251,7 +1261,9 @@  init_binding_ctx(struct engine_node *node,
     b_ctx_in->port_table = port_table;
     b_ctx_in->iface_table = iface_table;
     b_ctx_in->qos_table = qos_table;
+    b_ctx_in->mirror_table = mirror_table;
     b_ctx_in->port_binding_table = pb_table;
+    b_ctx_in->sb_mirror_table = sb_mirror_table;
     b_ctx_in->br_int = br_int;
     b_ctx_in->chassis_rec = chassis;
     b_ctx_in->active_tunnels = &rt_data->active_tunnels;
@@ -3464,8 +3476,10 @@  main(int argc, char *argv[])
     engine_add_input(&en_runtime_data, &en_ovs_open_vswitch, NULL);
     engine_add_input(&en_runtime_data, &en_ovs_bridge, NULL);
     engine_add_input(&en_runtime_data, &en_ovs_qos, NULL);
+    engine_add_input(&en_runtime_data, &en_ovs_mirror, NULL);
 
     engine_add_input(&en_runtime_data, &en_sb_chassis, NULL);
+    engine_add_input(&en_runtime_data, &en_sb_mirror, NULL);
     engine_add_input(&en_runtime_data, &en_sb_datapath_binding,
                      runtime_data_sb_datapath_binding_handler);
     engine_add_input(&en_runtime_data, &en_sb_port_binding,
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 43093cb5a..f77daafb8 100644
--- a/northd/inc-proc-northd.c
+++ b/northd/inc-proc-northd.c
@@ -48,6 +48,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") \
@@ -90,6 +91,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") \
@@ -168,6 +170,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);
@@ -190,6 +193,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 a56666297..455458514 100644
--- a/northd/northd.c
+++ b/northd/northd.c
@@ -3204,6 +3204,51 @@  ovn_port_update_sbrec_chassis(
     }
 }
 
+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,
@@ -3548,6 +3593,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);
@@ -14781,6 +14837,67 @@  sync_meters(struct northd_input *input_data,
     shash_destroy(&sb_meters);
 }
 
+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) {
+        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.
@@ -15388,6 +15505,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 2d804a22e..c30692864 100644
--- a/northd/northd.h
+++ b/northd/northd.h
@@ -30,6 +30,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;
@@ -49,6 +50,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/northd/ovn-nb.dlopts b/northd/ovn-nb.dlopts
index 9a460adef..797ca523f 100644
--- a/northd/ovn-nb.dlopts
+++ b/northd/ovn-nb.dlopts
@@ -20,6 +20,7 @@ 
 --intern-table Load_Balancer
 --intern-table Logical_Switch
 --intern-table Load_Balancer_Health_Check
+--intern-table Mirror
 --intern-table Meter
 --intern-table NAT
 --intern-table Address_Set
diff --git a/northd/ovn-sb.dlopts b/northd/ovn-sb.dlopts
index 99b65f101..82d614029 100644
--- a/northd/ovn-sb.dlopts
+++ b/northd/ovn-sb.dlopts
@@ -13,6 +13,7 @@ 
 -o Load_Balancer
 -o Logical_DP_Group
 -o MAC_Binding
+-o Mirror
 -o Meter
 -o Meter_Band
 -o Multicast_Group
diff --git a/northd/ovn_northd.dl b/northd/ovn_northd.dl
index 2fe73959c..22a502fcc 100644
--- a/northd/ovn_northd.dl
+++ b/northd/ovn_northd.dl
@@ -28,6 +28,16 @@  import set
 
 index Logical_Flow_Index() on sb::Out_Logical_Flow()
 
+/* Mirror table */
+for (mirror in &nb::Mirror) {
+    sb::Out_Mirror(._uuid = mirror._uuid,
+                 .name = mirror.name,
+                 .filter = mirror.filter,
+                 .sink = mirror.sink,
+                 .type = mirror.type,
+                 .index = mirror.index)
+}
+
 /* Meter_Band table */
 for (mb in nb::Meter_Band) {
     sb::Out_Meter_Band(._uuid = mb._uuid,
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 9010240a8..c514b7733 100644
--- a/ovn-nb.xml
+++ b/ovn-nb.xml
@@ -1511,6 +1511,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.
@@ -2432,6 +2437,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 66664c840..0493bce7b 100644
--- a/ovn-sb.ovsschema
+++ b/ovn-sb.ovsschema
@@ -1,7 +1,7 @@ 
 {
     "name": "OVN_Southbound",
-    "version": "20.22.0",
-    "cksum": "1686121686 27471",
+    "version": "20.23.0",
+    "cksum": "654011660 28470",
     "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"},
@@ -222,6 +236,11 @@ 
                                             "refTable": "Encap",
                                              "refType": "weak"},
                                     "min": 0, "max": 1}},
+                "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 1ffb24e7a..6a81021a4 100644
--- a/ovn-sb.xml
+++ b/ovn-sb.xml
@@ -2632,6 +2632,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
@@ -3082,6 +3123,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 f9b9defd0..7643f8884 100644
--- a/tests/ovn-nbctl.at
+++ b/tests/ovn-nbctl.at
@@ -435,6 +435,86 @@  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 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/utilities/ovn-nbctl.c b/utilities/ovn-nbctl.c
index e747f6933..f6ab002ef 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)
 {
@@ -7161,6 +7297,213 @@  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) {
+            if (mirror->n_src > 0) {
+                ctl_error(ctx, "Detach mirror source(s) before deletion");
+                return;
+            }
+            nbrec_mirror_delete(mirror);
+        }
+        return;
+    }
+
+    /* Remove the matching mirror. */
+    NBREC_MIRROR_FOR_EACH (mirror, ctx->idl) {
+        if (strcmp(ctx->argv[1], mirror->name)) {
+            continue;
+        }
+        if (mirror->n_src > 0) {
+            ctl_error(ctx, "Detach source(s) before mirror %s deletion",
+                      mirror->name);
+            return;
+        }
+        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,
@@ -7254,6 +7597,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 },
@@ -7308,6 +7660,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 b008b5d0b..14c24dfb9 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);
@@ -1424,6 +1425,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},