On Mon, Jun 13, 2022 at 9:53 AM Abhiram R N <abhira...@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 <vedabarrenk...@gmail.com>
> Signed-off-by: Veda Barrenkala <vedabarrenk...@gmail.com>
> Signed-off-by: Abhiram R N <abhira...@gmail.com>
> ---
>  V1 --> V2 : Addressed Review comments
>              i) Created new ENGINE node in OVN controller
>             ii) Added files mirror.c and mirror.h where
>                 the handling is taken care.
>            iii) Added a test in ovn.at
>

Thanks for v2.  Please see below for some comments.  This is not a
complete review.
But I found some issues with the added engine node 'port_mirror'.

Thanks
Numan



>  NOTE: 1 test is failing i.e ovn-performance.at. Its something to
>        do with the handler as it passes when I comment out the
>        noop_handler.
>        Need some guidance there on what could be causing this issue.
>        and how to fix it.
>        ./ovn-performance.at:363: test $rc -eq 0 -a $rv -eq 1
>
>  controller/automake.mk      |   4 +-
>  controller/mirror.c         | 392 ++++++++++++++++++++++++++++++++++++
>  controller/mirror.h         |  50 +++++
>  controller/ovn-controller.c | 187 ++++++++++++++++-
>  northd/en-northd.c          |   4 +
>  northd/inc-proc-northd.c    |   4 +
>  northd/northd.c             | 118 +++++++++++
>  northd/northd.h             |   2 +
>  ovn-nb.ovsschema            |  31 ++-
>  ovn-nb.xml                  |  57 ++++++
>  ovn-sb.ovsschema            |  23 ++-
>  ovn-sb.xml                  |  46 +++++
>  tests/ovn-nbctl.at          |  80 ++++++++
>  tests/ovn.at                | 155 ++++++++++++++
>  utilities/ovn-nbctl.c       | 345 +++++++++++++++++++++++++++++++
>  utilities/ovn-sbctl.c       |   4 +
>  16 files changed, 1496 insertions(+), 6 deletions(-)
>  create mode 100644 controller/mirror.c
>  create mode 100644 controller/mirror.h
>
> diff --git a/controller/automake.mk b/controller/automake.mk
> index c2ab1bbe6..334672b4d 100644
> --- a/controller/automake.mk
> +++ b/controller/automake.mk
> @@ -41,7 +41,9 @@ controller_ovn_controller_SOURCES = \
>         controller/ovsport.h \
>         controller/ovsport.c \
>         controller/vif-plug.h \
> -       controller/vif-plug.c
> +       controller/vif-plug.c \
> +       controller/mirror.h \
> +       controller/mirror.c
>
>  controller_ovn_controller_LDADD = lib/libovn.la 
> $(OVS_LIBDIR)/libopenvswitch.la
>  man_MANS += controller/ovn-controller.8
> diff --git a/controller/mirror.c b/controller/mirror.c
> new file mode 100644
> index 000000000..fe4f20184
> --- /dev/null
> +++ b/controller/mirror.c
> @@ -0,0 +1,392 @@
> +/* Copyright (c) 2022 Red Hat, Inc.
> + *
> + * Licensed under the Apache License, Version 2.0 (the "License");
> + * you may not use this file except in compliance with the License.
> + * You may obtain a copy of the License at:
> + *
> + *     http://www.apache.org/licenses/LICENSE-2.0
> + *
> + * Unless required by applicable law or agreed to in writing, software
> + * distributed under the License is distributed on an "AS IS" BASIS,
> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
> + * See the License for the specific language governing permissions and
> + * limitations under the License.
> + */
> +
> +#include <config.h>
> +#include <unistd.h>
> +
> +/* library headers */
> +#include "lib/sset.h"
> +#include "lib/util.h"
> +
> +/* OVS includes. */
> +#include "lib/vswitch-idl.h"
> +#include "openvswitch/vlog.h"
> +
> +/* OVN includes. */
> +#include "lib/ovn-sb-idl.h"
> +#include "mirror.h"
> +
> +VLOG_DEFINE_THIS_MODULE(port_mirror);
> +
> +/* Static function declarations */
> +
> +static const struct ovsrec_port *
> +get_port_for_iface(const struct ovsrec_interface *iface,
> +                  const struct ovsrec_bridge *br_int)
> +{
> +    for (size_t i = 0; i < br_int->n_ports; i++) {
> +        const struct ovsrec_port *p = br_int->ports[i];
> +        for (size_t j = 0; j < p->n_interfaces; j++) {
> +            if (!strcmp(iface->name, p->interfaces[j]->name)) {
> +                return p;
> +            }
> +        }
> +    }
> +    return NULL;
> +}
> +
> +static void
> +mirror_create(const struct sbrec_port_binding *pb,
> +              struct port_mirror_ctx *pm_ctx,
> +              const struct ovsrec_mirror *mirror)
> +{

To avoid the big "if" indentation,  I'd suggest returning at the
beginning if 'pb->up[0]' is false.

However there seems to be a problem with the code here.
suppose if a port binding is claimed by a chassis 'hv1',  looks to me
like a mirror will be created by other chassis too
since you're not checking if the 'pb' is local to the hypervisor or
not (i.e pb->chassis == <ovn_controller_chassis)

(See the function is_binding_lport_this_chassis() in binding.c)


> +    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, pm_ctx->mirror_table) {
> +                if (!strcmp(pb->mirror_rules[i]->name, mirror->name)) {
> +                      /* Mirror with same name already exists
> +                       * No need to create mirror
> +                       */
> +                      create_mirror = false;
> +                      break;
> +                }
> +            }
> +
> +            if (create_mirror) {
> +
> +                struct smap options = SMAP_INITIALIZER(&options);
> +                char *port_name, *key;
> +
> +                key = xasprintf("%ld",(long int)pb->mirror_rules[i]->index);
> +                smap_add(&options, "remote_ip", pb->mirror_rules[i]->sink);
> +
> +                if (!strcmp(pb->mirror_rules[i]->type, "gre")) {
> +                    /* Set the GRE key */
> +                    smap_add(&options, "key", key);
> +
> +                } else if (!strcmp(pb->mirror_rules[i]->type, "erspan")) {
> +                    /* Set the ERSPAN index */
> +                    smap_add(&options, "key", key);
> +                    smap_add(&options, "erspan_idx", key);
> +                    smap_add(&options, "erspan_ver","1");
> +
> +                }
> +                struct ovsrec_interface *iface =
> +                          ovsrec_interface_insert(pm_ctx->ovs_idl_txn);
> +                port_name = xasprintf("ovn-%s-%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(pm_ctx->ovs_idl_txn);
> +                ovsrec_port_set_name(port, port_name);
> +                ovsrec_port_set_interfaces(port, &iface, 1);
> +
> +                ovsrec_bridge_update_ports_addvalue(pm_ctx->br_int, port);
> +
> +                smap_destroy(&options);
> +                free(port_name);
> +                free(key);
> +
> +                VLOG_INFO("Creating Mirror in OVS DB");
> +                mirror = ovsrec_mirror_insert(pm_ctx->ovs_idl_txn);
> +                ovsrec_mirror_set_name(mirror,pb->mirror_rules[i]->name);
> +                ovsrec_mirror_update_output_port_addvalue(mirror, port);
> +                ovsrec_bridge_update_mirrors_addvalue(pm_ctx->br_int,
> +                                                                 mirror);
> +            }
> +
> +            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,
> +                                             pm_ctx->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;
> +                    }
> +                }
> +            }

I don't think there is a need to run this loop to get the interface
table for the 'pb'.
You can use the local_binding data of the runtime_data handler.  (See
the function local_binding_find() in binding.c)


> +
> +            const struct ovsrec_port *p =
> +                              get_port_for_iface(iface_rec, pm_ctx->br_int);
> +            if (p) {
> +                if (!strcmp(pb->mirror_rules[i]->filter,"from-lport")) {
> +                    ovsrec_mirror_update_select_src_port_addvalue(mirror, p);
> +                } else if (!strcmp(pb->mirror_rules[i]->filter,"to-lport")) {
> +                    ovsrec_mirror_update_select_dst_port_addvalue(mirror, p);
> +                } else {
> +                    ovsrec_mirror_update_select_src_port_addvalue(mirror, p);
> +                    ovsrec_mirror_update_select_dst_port_addvalue(mirror, p);
> +                }
> +            }
> +        }
> +    }
> +}
> +
> +static void
> +mirror_delete(const struct sbrec_port_binding *pb,
> +              struct port_mirror_ctx *pm_ctx,
> +              struct shash *pb_mirror_map,
> +              bool delete_all)
> +{
> +    struct sset pb_mirrors = SSET_INITIALIZER(&pb_mirrors);
> +
> +    if (delete_all != true) {
> +        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(pm_ctx->br_int,
> +                                                    ovs_mirror->output_port);
> +                ovsrec_bridge_update_mirrors_delvalue(pm_ctx->br_int,
> +                                                            ovs_mirror);
> +                ovsrec_port_delete(ovs_mirror->output_port);
> +                ovsrec_mirror_delete(ovs_mirror);
> +            }
> +        }
> +    }
> +
> +    const char *used_node, *used_next;
> +    SSET_FOR_EACH_SAFE (used_node, used_next, &pb_mirrors) {
> +        sset_delete(&pb_mirrors, SSET_NODE_FROM_NAME(used_node));
> +    }
> +    sset_destroy(&pb_mirrors);
> +}
> +
> +static void
> +find_port_specific_mirrors (const struct sbrec_port_binding *pb,
+                            struct port_mirror_ctx *pm_ctx,
> +                            const struct ovsrec_mirror *mirror,
> +                            struct shash *pb_mirror_map)
> +{
There is no need for the 3rd input param 'mirror' in this function.
You can just create locally.

> +    OVSREC_MIRROR_TABLE_FOR_EACH (mirror, pm_ctx->mirror_table) {
> +        for (size_t i = 0; i < mirror->n_select_dst_port; i++) {
> +            const struct ovsrec_port *port_rec = mirror->select_dst_port[i];
> +            for (size_t j = 0; j < port_rec->n_interfaces; j++) {
> +                const struct ovsrec_interface *iface_rec;
> +                iface_rec = port_rec->interfaces[j];
> +                const char *logical_port =
> +                    smap_get(&iface_rec->external_ids, "iface-id");
> +                if (!strcmp(logical_port, pb->logical_port)) {
> +                    shash_add_once(pb_mirror_map, mirror->name, mirror);
> +                }
> +            }
> +        }
> +        for (size_t i = 0; i < mirror->n_select_src_port; i++) {
> +            const struct ovsrec_port *port_rec = mirror->select_src_port[i];
> +            for (size_t j = 0; j < port_rec->n_interfaces; j++) {
> +                const struct ovsrec_interface *iface_rec;
> +                iface_rec = port_rec->interfaces[j];
> +                const char *logical_port =
> +                    smap_get(&iface_rec->external_ids, "iface-id");
> +                if (!strcmp(logical_port, pb->logical_port)) {
> +                    shash_add_once(pb_mirror_map, mirror->name, mirror);
> +                }
> +            }
> +        }
> +    }
> +}
> +
> +void
> +mirror_register_ovs_idl(struct ovsdb_idl *ovs_idl)
> +{
> +    ovsdb_idl_add_column(ovs_idl, &ovsrec_bridge_col_mirrors);
> +
> +    ovsdb_idl_add_table(ovs_idl, &ovsrec_table_mirror);
> +    ovsdb_idl_add_column(ovs_idl, &ovsrec_mirror_col_name);
> +    ovsdb_idl_add_column(ovs_idl, &ovsrec_mirror_col_output_port);
> +    ovsdb_idl_add_column(ovs_idl, &ovsrec_mirror_col_select_dst_port);
> +    ovsdb_idl_add_column(ovs_idl, &ovsrec_mirror_col_select_src_port);
> +}
> +
> +
> +void
> +ovn_port_mirror_init(struct shash *ovs_mirrors)
> +{
> +    shash_init(ovs_mirrors);
> +}
> +
> +void
> +ovn_port_mirror_run(struct port_mirror_ctx *pm_ctx)
> +{

The function ovn_port_mirror_run() is called by en_port_mirror_run()
in ovn-controller.c.
Which means this function is called when the port_mirror engine is recomputed.
So you need to loop through all the port binbings and create/delete
mirrors for them.
See the function binding_run() as an example.

Normally every engine node has some inputs.  And there is a handler
for each input.  If any handler for the input is NULL
or if the handler returns false (example
port_mirror_port_binding_handler() will be called by the I-P engine
for Southbound
port binding changes.  If port_mirror_port_binding_handler() returns
false, it means it cannot handle the port binding changes
in that engine run and hence a full recompute is required for the
port_mirror engine and en_port_mirror_run() is called.

So you need to fix this.

> +    struct shash_node *ovs_mirror_node, *ovs_mirror_next;
> +    SHASH_FOR_EACH_SAFE (ovs_mirror_node, ovs_mirror_next,
> +                                        pm_ctx->ovs_mirrors) {
> +        shash_delete(pm_ctx->ovs_mirrors, ovs_mirror_node);
> +    }
> +
> +    const struct ovsrec_mirror *ovsmirror = NULL;
> +    OVSREC_MIRROR_TABLE_FOR_EACH (ovsmirror, pm_ctx->mirror_table) {
> +       shash_add(pm_ctx->ovs_mirrors, ovsmirror->name, ovsmirror);
> +    }
> +
> +}
> +
> +bool
> +ovn_port_mirror_handle_lport(const struct sbrec_port_binding *pb, bool 
> removed,
> +                     struct port_mirror_ctx *pm_ctx)
> +{
> +
> +    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, pm_ctx, mirror, &port_ovs_mirrors);
> +
> +    if (removed == false) {
> +        if (((pb->n_mirror_rules == 0)
> +              && (shash_is_empty(&port_ovs_mirrors))) ||
> +              (pb->n_mirror_rules == shash_count(&port_ovs_mirrors))) {
> +            /* No mirror update */
> +        } else {
> +            /* Update Mirror */
> +            if (pb->n_mirror_rules > shash_count(&port_ovs_mirrors)) {
> +                /* create mirror,
> +                 * if mirror already exists only update selection
> +                 */
> +                mirror_create(pb, pm_ctx, mirror);
> +            } else {
> +                /* delete mirror,
> +                 * if mirror has other sources only update selection
> +                 */
> +                mirror_delete(pb, pm_ctx, &port_ovs_mirrors, false);
> +            }
> +        }
> +    } else {
> +           mirror_delete(pb, pm_ctx, &port_ovs_mirrors, true);
> +    }
> +
> +    struct shash_node *ovs_mirror_node, *ovs_mirror_next;
> +    SHASH_FOR_EACH_SAFE (ovs_mirror_node, ovs_mirror_next,
> +                                              &port_ovs_mirrors) {
> +        shash_delete(&port_ovs_mirrors, ovs_mirror_node);
> +    }
> +    shash_destroy(&port_ovs_mirrors);
> +
> +    return true;
> +}
> +
> +bool
> +ovn_port_mirror_handle_update(struct port_mirror_ctx *pm_ctx)
> +{
> +    const struct sbrec_mirror *mirror = NULL;
> +    struct sset sb_mirrors = SSET_INITIALIZER(&sb_mirrors);
> +    SBREC_MIRROR_TABLE_FOR_EACH (mirror, pm_ctx->sb_mirror_table) {
> +        sset_add(&sb_mirrors, mirror->name);
> +    }
> +
> +    struct shash_node *mirror_node;
> +    SHASH_FOR_EACH (mirror_node, pm_ctx->ovs_mirrors) {
> +        struct ovsrec_mirror *ovs_mirror = mirror_node->data;
> +        if (!sset_find(&sb_mirrors, ovs_mirror->name)) {
> +            /* Need to delete the mirror in OVS */
> +            VLOG_INFO("Delete mirror and remove port");
> +            ovsrec_bridge_update_ports_delvalue(pm_ctx->br_int,
> +                                                ovs_mirror->output_port);
> +            ovsrec_bridge_update_mirrors_delvalue(pm_ctx->br_int,
> +                                                  ovs_mirror);
> +            ovsrec_port_delete(ovs_mirror->output_port);
> +            ovsrec_mirror_delete(ovs_mirror);
> +        }
> +    }
> +
> +    const char *used_node, *used_next;
> +    SSET_FOR_EACH_SAFE (used_node, used_next, &sb_mirrors) {
> +        sset_delete(&sb_mirrors, SSET_NODE_FROM_NAME(used_node));
> +    }
> +    sset_destroy(&sb_mirrors);
> +
> +    return true;
> +}
> +
> +void
> +ovn_port_mirror_destroy(struct shash *ovs_mirrors)
> +{
> +    struct shash_node *ovs_mirror_node, *ovs_mirror_next;
> +    SHASH_FOR_EACH_SAFE (ovs_mirror_node, ovs_mirror_next,
> +                                              ovs_mirrors) {
> +        shash_delete(ovs_mirrors, ovs_mirror_node);
> +    }
> +    shash_destroy(ovs_mirrors);
> +}
> +
> diff --git a/controller/mirror.h b/controller/mirror.h
> new file mode 100644
> index 000000000..2410a4d37
> --- /dev/null
> +++ b/controller/mirror.h
> @@ -0,0 +1,50 @@
> +/* Copyright (c) 2022 Red Hat, Inc.
> + *
> + * Licensed under the Apache License, Version 2.0 (the "License");
> + * you may not use this file except in compliance with the License.
> + * You may obtain a copy of the License at:
> + *
> + *     http://www.apache.org/licenses/LICENSE-2.0
> + *
> + * Unless required by applicable law or agreed to in writing, software
> + * distributed under the License is distributed on an "AS IS" BASIS,
> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
> + * See the License for the specific language governing permissions and
> + * limitations under the License.
> + */
> +
> +#ifndef OVN_MIRROR_H
> +#define OVN_MIRROR_H 1
> +
> +struct ovsdb_idl_txn;
> +struct ovsrec_port_table;
> +struct ovsrec_bridge;
> +struct ovsrec_bridge_table;
> +struct ovsrec_open_vswitch_table;
> +struct ovsrec_interface_table;
> +struct ovsrec_mirror_table;
> +struct sbrec_mirror_table;
> +struct sbrec_port_binding_table;
> +
> +struct port_mirror_ctx {
> +    struct shash *ovs_mirrors;
> +    struct ovsdb_idl_txn *ovs_idl_txn;
> +    const struct ovsrec_port_table *port_table;
> +    const struct ovsrec_bridge *br_int;
> +    const struct ovsrec_bridge_table *bridge_table;
> +    const struct ovsrec_open_vswitch_table *ovs_table;
> +    const struct ovsrec_interface_table *iface_table;
> +    const struct ovsrec_mirror_table *mirror_table;
> +    const struct sbrec_mirror_table *sb_mirror_table;
> +    const struct sbrec_port_binding_table *port_binding_table;
> +};
> +
> +void mirror_register_ovs_idl(struct ovsdb_idl *);
> +void ovn_port_mirror_init(struct shash *);
> +void ovn_port_mirror_destroy(struct shash *);
> +void ovn_port_mirror_run(struct port_mirror_ctx *pm_ctx);
> +bool ovn_port_mirror_handle_lport(const struct sbrec_port_binding *pb,
> +                                  bool removed,
> +                                  struct port_mirror_ctx *pm_ctx);
> +bool ovn_port_mirror_handle_update(struct port_mirror_ctx *pm_ctx);
> +#endif
> diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c
> index 2793c8687..93d97f76a 100644
> --- a/controller/ovn-controller.c
> +++ b/controller/ovn-controller.c
> @@ -76,6 +76,7 @@
>  #include "stopwatch.h"
>  #include "lib/inc-proc-eng.h"
>  #include "hmapx.h"
> +#include "mirror.h"
>
>  VLOG_DEFINE_THIS_MODULE(main);
>
> @@ -958,6 +959,7 @@ ctrl_register_ovs_idl(struct ovsdb_idl *ovs_idl)
>      bfd_register_ovs_idl(ovs_idl);
>      physical_register_ovs_idl(ovs_idl);
>      vif_plug_register_ovs_idl(ovs_idl);
> +    mirror_register_ovs_idl(ovs_idl);
>  }
>
>  #define SB_NODES \
> @@ -978,6 +980,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 {
> @@ -995,7 +998,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,
> @@ -2216,6 +2220,170 @@ load_balancers_by_dp_cleanup(struct hmap *lbs)
>      free(lbs);
>  }
>
> +/* Mirror Engine */
> +struct ed_type_port_mirror {
> +    struct shash ovs_mirrors;
> +};
> +
> +static void *
> +en_port_mirror_init(struct engine_node *node OVS_UNUSED,
> +                    struct engine_arg *arg OVS_UNUSED)
> +{
> +    struct ed_type_port_mirror *port_mirror = xzalloc(sizeof *port_mirror);
> +    ovn_port_mirror_init(&port_mirror->ovs_mirrors);
> +    return port_mirror;
> +}
> +
> +static void
> +en_port_mirror_cleanup(void *data)
> +{
> +    struct ed_type_port_mirror *port_mirror = data;
> +    ovn_port_mirror_destroy(&port_mirror->ovs_mirrors);
> +}
> +
> +static void
> +init_port_mirror_ctx(struct engine_node *node,
> +                 struct ed_type_port_mirror *port_mirror_data,
> +                 struct port_mirror_ctx *pm_ctx)
> +{
> +    struct ovsrec_open_vswitch_table *ovs_table =
> +        (struct ovsrec_open_vswitch_table *)EN_OVSDB_GET(
> +            engine_get_input("OVS_open_vswitch", node));
> +    struct ovsrec_bridge_table *bridge_table =
> +        (struct ovsrec_bridge_table *)EN_OVSDB_GET(
> +            engine_get_input("OVS_bridge", node));
> +    const char *chassis_id = get_ovs_chassis_id(ovs_table);
> +    const struct ovsrec_bridge *br_int = get_br_int(bridge_table, ovs_table);
> +
> +    ovs_assert(br_int && chassis_id);
> +
> +    struct ovsrec_port_table *port_table =
> +        (struct ovsrec_port_table *)EN_OVSDB_GET(
> +            engine_get_input("OVS_port", node));
> +
> +    struct ed_type_ovs_interface_shadow *iface_shadow =
> +        engine_get_input_data("ovs_interface_shadow", node);
> +
> +    struct ovsrec_mirror_table *mirror_table =
> +        (struct ovsrec_mirror_table *)EN_OVSDB_GET(
> +            engine_get_input("OVS_mirror", node));
> +
> +    struct sbrec_port_binding_table *pb_table =
> +        (struct sbrec_port_binding_table *)EN_OVSDB_GET(
> +            engine_get_input("SB_port_binding", node));
> +
> +    struct sbrec_mirror_table *sb_mirror_table =
> +        (struct sbrec_mirror_table *)EN_OVSDB_GET(
> +            engine_get_input("SB_mirror", node));
> +
> +    pm_ctx->ovs_idl_txn = engine_get_context()->ovs_idl_txn;
> +    pm_ctx->port_table = port_table;
> +    pm_ctx->iface_table = iface_shadow->iface_table;
> +    pm_ctx->mirror_table = mirror_table;
> +    pm_ctx->port_binding_table = pb_table;
> +    pm_ctx->sb_mirror_table = sb_mirror_table;
> +    pm_ctx->br_int = br_int;
> +    pm_ctx->bridge_table = bridge_table;
> +    pm_ctx->ovs_table = ovs_table;
> +    pm_ctx->ovs_mirrors = &port_mirror_data->ovs_mirrors;
> +}
> +
> +static void
> +en_port_mirror_run(struct engine_node *node, void *data)
> +{
> +    struct port_mirror_ctx pm_ctx;
> +    struct ed_type_port_mirror *port_mirror_data = data;
> +    init_port_mirror_ctx(node, port_mirror_data, &pm_ctx);
> +
> +    ovn_port_mirror_run(&pm_ctx);
> +    engine_set_node_state(node, EN_UPDATED);
> +}
> +
> +static bool
> +port_mirror_runtime_data_handler(struct engine_node *node, void *data)
> +{
> +    struct ed_type_runtime_data *rt_data =
> +        engine_get_input_data("runtime_data", node);
> +
> +    /* There is no tracked data. Fall back to full recompute of port_mirror 
> */
> +    if (!rt_data->tracked) {
> +        return false;
> +    }
> +
> +    struct hmap *tracked_dp_bindings = &rt_data->tracked_dp_bindings;
> +    if (hmap_is_empty(tracked_dp_bindings)) {
> +        return true;
> +    }
> +
> +    struct port_mirror_ctx pm_ctx;
> +    struct ed_type_port_mirror *port_mirror_data = data;
> +    init_port_mirror_ctx(node, port_mirror_data, &pm_ctx);
> +
> +    struct tracked_datapath *tdp;
> +    HMAP_FOR_EACH (tdp, node, tracked_dp_bindings) {
> +        if (tdp->tracked_type != TRACKED_RESOURCE_UPDATED) {
> +            /* Fall back to full recompute when a local datapath
> +             * is added or deleted. */
> +            return false;
> +        }
> +
> +        struct shash_node *shash_node;
> +        SHASH_FOR_EACH (shash_node, &tdp->lports) {
> +            struct tracked_lport *lport = shash_node->data;
> +            bool removed =
> +                lport->tracked_type == TRACKED_RESOURCE_REMOVED ? true: 
> false;
> +            if (!ovn_port_mirror_handle_lport(lport->pb, removed, &pm_ctx)) {
> +                return false;
> +            }
> +        }
> +    }
> +
> +    engine_set_node_state(node, EN_UPDATED);
> +    return true;
> +}
> +
> +static bool
> +port_mirror_port_binding_handler(struct engine_node *node, void *data)
> +{
> +    struct port_mirror_ctx pm_ctx;
> +    struct ed_type_port_mirror *port_mirror_data = data;
> +    init_port_mirror_ctx(node, port_mirror_data, &pm_ctx);
> +
> +    /* handle port binding updates (i.,e when the mirror column
> +     * of port_binding is updated)
> +     */
> +    const struct sbrec_port_binding *pb;
> +    SBREC_PORT_BINDING_TABLE_FOR_EACH_TRACKED (pb,
> +                                               pm_ctx.port_binding_table) {
> +        bool removed = sbrec_port_binding_is_deleted(pb);
> +        if (!ovn_port_mirror_handle_lport(pb, removed, &pm_ctx)) {
> +            return false;
> +        }
> +    }
> +
> +    engine_set_node_state(node, EN_UPDATED);
> +    return true;
> +
> +}
> +
> +static bool
> +port_mirror_sb_mirror_handler(struct engine_node *node, void *data)
> +{
> +    struct port_mirror_ctx pm_ctx;
> +    struct ed_type_port_mirror *port_mirror_data = data;
> +    init_port_mirror_ctx(node, port_mirror_data, &pm_ctx);
> +
> +    /* handle sb mirror updates
> +     */
> +    if (!ovn_port_mirror_handle_update(&pm_ctx)) {
> +        return false;
> +    }
> +
> +    engine_set_node_state(node, EN_UPDATED);
> +    return true;
> +
> +}
> +
>  /* Engine node which is used to handle the Non VIF data like
>   *   - OVS patch ports
>   *   - Tunnel ports and the related chassis information.
> @@ -3451,6 +3619,7 @@ main(int argc, char *argv[])
>      ENGINE_NODE_WITH_CLEAR_TRACK_DATA(addr_sets, "addr_sets");
>      ENGINE_NODE_WITH_CLEAR_TRACK_DATA(port_groups, "port_groups");
>      ENGINE_NODE(northd_options, "northd_options");
> +    ENGINE_NODE(port_mirror, "port_mirror");
>
>  #define SB_NODE(NAME, NAME_STR) ENGINE_NODE_SB(NAME, NAME_STR);
>      SB_NODES
> @@ -3594,6 +3763,22 @@ main(int argc, char *argv[])
>      engine_add_input(&en_flow_output, &en_pflow_output,
>                       flow_output_pflow_output_handler);
>
> +    engine_add_input(&en_port_mirror, &en_ovs_open_vswitch, NULL);
> +    engine_add_input(&en_port_mirror, &en_ovs_bridge, NULL);
> +    engine_add_input(&en_port_mirror, &en_ovs_mirror, NULL);
> +
> +    engine_add_input(&en_port_mirror, &en_ovs_port, NULL);
> +    engine_add_input(&en_port_mirror, &en_ovs_interface_shadow,
> +                     NULL);
> +    engine_add_input(&en_flow_output, &en_port_mirror,
> +                     engine_noop_handler);
> +    engine_add_input(&en_port_mirror, &en_runtime_data,
> +                     port_mirror_runtime_data_handler);
> +    engine_add_input(&en_port_mirror, &en_sb_mirror,
> +                     port_mirror_sb_mirror_handler);
> +    engine_add_input(&en_port_mirror, &en_sb_port_binding,
> +                     port_mirror_port_binding_handler);
> +
>      struct engine_arg engine_arg = {
>          .sb_idl = ovnsb_idl_loop.idl,
>          .ovs_idl = ovs_idl_loop.idl,
> 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 0d6ebccde..af03cc7e2 100644
> --- a/northd/northd.c
> +++ b/northd/northd.c
> @@ -3340,6 +3340,51 @@ ovn_port_update_sbrec_chassis(
>      free(requested_chassis_sb);
>  }
>
> +static void
> +sbrec_port_binding_update_mirror_rules(struct northd_input *input_data,
> +                                       const struct ovn_port *op)
> +{
> +    size_t i = 0;
> +    if (op->sb->n_mirror_rules > op->nbsp->n_mirror_rules) {
> +        /* Needs deletion in SB */
> +        struct shash nb_mirror_rules = SHASH_INITIALIZER(&nb_mirror_rules);
> +        for (i = 0; i < op->nbsp->n_mirror_rules; i++) {
> +            shash_add(&nb_mirror_rules,
> +                                 op->nbsp->mirror_rules[i]->name,
> +                                 op->nbsp->mirror_rules[i]);
> +        }
> +
> +        for (i = 0; i < op->sb->n_mirror_rules; i++) {
> +            if (!shash_find(&nb_mirror_rules,
> +                           op->sb->mirror_rules[i]->name)) {
> +                    sbrec_port_binding_update_mirror_rules_delvalue(op->sb,
> +                                                 op->sb->mirror_rules[i]);
> +            }
> +        }
> +
> +        struct shash_node *node, *next;
> +        SHASH_FOR_EACH_SAFE (node, next, &nb_mirror_rules) {
> +            shash_delete(&nb_mirror_rules, node);
> +        }
> +        shash_destroy(&nb_mirror_rules);
> +
> +    } else {
> +        /* Needs addition in SB */
> +        for (i = 0; i < op->nbsp->n_mirror_rules; i++) {
> +            const struct sbrec_mirror *sb_mirror;
> +            SBREC_MIRROR_TABLE_FOR_EACH (sb_mirror,
> +                                         input_data->sbrec_mirror_table) {
> +                if (!strcmp(sb_mirror->name,
> +                            op->nbsp->mirror_rules[i]->name)) {
> +                    /* Add the value to SB */
> +                    sbrec_port_binding_update_mirror_rules_addvalue(op->sb,
> +                                                                    
> sb_mirror);
> +                }
> +            }
> +        }
> +    }
> +}
> +
>  static void
>  ovn_port_update_sbrec(struct northd_input *input_data,
>                        struct ovsdb_idl_txn *ovnsb_txn,
> @@ -3691,6 +3736,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);
> @@ -14787,6 +14843,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.
> @@ -15386,6 +15503,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 677b35877..56d8a8920 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/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 14a624c16..3db342640 100644
> --- a/ovn-nb.xml
> +++ b/ovn-nb.xml
> @@ -1540,6 +1540,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.
> @@ -2470,6 +2475,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 3b78ea6f6..7cad0d3c0 100644
> --- a/ovn-sb.ovsschema
> +++ b/ovn-sb.ovsschema
> @@ -1,7 +1,7 @@
>  {
>      "name": "OVN_Southbound",
> -    "version": "20.23.0",
> -    "cksum": "4045988377 28575",
> +    "version": "20.24.0",
> +    "cksum": "618251466 29574",
>      "tables": {
>          "SB_Global": {
>              "columns": {
> @@ -142,6 +142,20 @@
>              "indexes": [["datapath", "tunnel_key"],
>                          ["datapath", "name"]],
>              "isRoot": true},
> +        "Mirror": {
> +            "columns": {
> +                "name": {"type": "string"},
> +                "filter": {"type": {"key": {"type": "string",
> +                                            "enum": ["set",
> +                                                     ["from-lport",
> +                                                      "to-lport","both"]]}}},
> +                "sink":{"type": "string"},
> +                "type": {"type": {"key": {"type": "string",
> +                                            "enum": ["set",
> +                                                     ["gre", "erspan"]]}}},
> +                "index": {"type": "integer"}},
> +            "indexes": [["name"]],
> +            "isRoot": true},
>          "Meter": {
>              "columns": {
>                  "name": {"type": "string"},
> @@ -230,6 +244,11 @@
>                                                        "refTable": "Encap",
>                                                        "refType": "weak"},
>                                      "min": 0, "max": "unlimited"}},
> +                "mirror_rules": {"type": {"key": {"type": "uuid",
> +                                          "refTable": "Mirror",
> +                                          "refType": "weak"},
> +                                  "min": 0,
> +                                  "max": "unlimited"}},
>                  "mac": {"type": {"key": "string",
>                                   "min": 0,
>                                   "max": "unlimited"}},
> diff --git a/ovn-sb.xml b/ovn-sb.xml
> index 898f3676a..69f3096bc 100644
> --- a/ovn-sb.xml
> +++ b/ovn-sb.xml
> @@ -2691,6 +2691,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
> @@ -3193,6 +3234,11 @@ tcp.flags = RST;
>        </column>
>      </group>
>
> +    <column name="mirror_rules">
> +        Mirror rules that apply to the port binding.
> +        Please see the <ref table="Mirror"/> table.
> +    </column>
> +
>      <group title="Patch Options">
>        <p>
>          These options apply to logical ports with <ref column="type"/> of
> diff --git a/tests/ovn-nbctl.at b/tests/ovn-nbctl.at
> index 726efa6f4..33754e7f5 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/tests/ovn.at b/tests/ovn.at
> index 59d51f3e0..e39231dae 100644
> --- a/tests/ovn.at
> +++ b/tests/ovn.at
> @@ -15785,6 +15785,161 @@ OVN_CLEANUP([hv1], [hv2])
>  AT_CLEANUP
>  ])
>
> +OVN_FOR_EACH_NORTHD([
> +AT_SETUP([Mirror])
> +AT_KEYWORDS([Mirror])
> +ovn_start
> +
> +# Logical network:
> +# One LR - R1 has switch ls1 (191.168.1.0/24) connected to it,
> +# and has switch ls2 (172.16.1.0/24) connected to it.
> +
> +ovn-nbctl lr-add R1
> +
> +ovn-nbctl ls-add ls1
> +ovn-nbctl ls-add ls2
> +
> +# Connect ls1 to R1
> +ovn-nbctl lrp-add R1 ls1 00:00:00:01:02:f1 192.168.1.1/24
> +ovn-nbctl lsp-add ls1 rp-ls1 -- set Logical_Switch_Port rp-ls1 \
> +    type=router options:router-port=ls1 addresses=\"00:00:00:01:02:f1\"
> +
> +# Connect ls2 to R1
> +ovn-nbctl lrp-add R1 ls2 00:00:00:01:02:f2 172.16.1.1/24
> +ovn-nbctl lsp-add ls2 rp-ls2 -- set Logical_Switch_Port rp-ls2 \
> +    type=router options:router-port=ls2 addresses=\"00:00:00:01:02:f2\"
> +
> +# Create logical port ls1-lp1 in ls1
> +ovn-nbctl lsp-add ls1 ls1-lp1 \
> +-- lsp-set-addresses ls1-lp1 "00:00:00:01:02:03 192.168.1.2"
> +
> +# Create logical port ls2-lp1 in ls2
> +ovn-nbctl lsp-add ls2 ls2-lp1 \
> +-- lsp-set-addresses ls2-lp1 "00:00:00:01:02:04 172.16.1.2"
> +
> +# Create one hypervisor and create OVS ports corresponding to logical ports.
> +net_add n1
> +
> +sim_add hv1
> +as hv1
> +#ovs-vsctl add-br br-phys
> +ovs-vsctl add-br br-phys -- set bridge br-phys 
> other-config:hwaddr=\"00:00:00:01:02:00\"
> +ovn_attach n1 br-phys 192.168.1.11
> +
> +ovs-vsctl -- add-port br-int vif1 -- \
> +    set interface vif1 external-ids:iface-id=ls1-lp1 \
> +    options:tx_pcap=hv1/vif1-tx.pcap \
> +    options:rxq_pcap=hv1/vif1-rx.pcap \
> +    ofport-request=1
> +
> +ovs-vsctl -- add-port br-int vif2 -- \
> +    set interface vif2 external-ids:iface-id=ls2-lp1 \
> +    options:tx_pcap=hv1/vif2-tx.pcap \
> +    options:rxq_pcap=hv1/vif2-rx.pcap \
> +    ofport-request=1
> +
> +# Allow some time for ovn-northd and ovn-controller to catch up.
> +wait_for_ports_up
> +check ovn-nbctl --wait=hv sync
> +ovn-nbctl dump-flows > sbflows
> +AT_CAPTURE_FILE([sbflows])
> +
> +for i in 1 2; do
> +    : > vif$i.expected
> +done
> +
> +net_add n2
> +
> +sim_add hv2
> +as hv2
> +#ovs-vsctl add-br br-phys
> +ovs-vsctl add-br br-phys -- set bridge br-phys 
> other-config:hwaddr=\"00:00:00:02:02:00\"
> +ovn_attach n2 br-phys 192.168.1.12
> +
> +OVN_POPULATE_ARP
> +
> +ovs-appctl -t ovn-controller exit
> +
> +as hv1
> +
> +# test_ipv4_icmp_request INPORT ETH_SRC ETH_DST IPV4_SRC IPV4_DST IP_CHKSUM 
> ICMP_CHKSUM [EXP_IP_CHKSUM EXP_ICMP_CHKSUM] ENCAP_TYPE MIRROR
> +#
> +# Causes a packet to be received on INPORT.  The packet is an ICMPv4
> +# request with ETH_SRC, ETH_DST, IPV4_SRC, IPV4_DST, IP_CHSUM and
> +# ICMP_CHKSUM as specified.  If EXP_IP_CHKSUM and EXP_ICMP_CHKSUM are
> +# provided, then it should be the ip and icmp checksums of the packet
> +# responded; otherwise, no reply is expected.
> +# In the absence of an ip checksum calculation helpers, this relies
> +# on the caller to provide the checksums for the ip and icmp headers.
> +# XXX This should be more systematic.
> +#
> +# INPORT is an lport number, e.g. 11 for vif11.
> +# ETH_SRC and ETH_DST are each 12 hex digits.
> +# IPV4_SRC and IPV4_DST are each 8 hex digits.
> +# IP_CHSUM and ICMP_CHKSUM are each 4 hex digits.
> +# EXP_IP_CHSUM and EXP_ICMP_CHKSUM are each 4 hex digits.
> +# ENCAP_TYPE - Currently checking only for GRE
> +# MIRROR - Mirror name
> +test_ipv4_icmp_request() {
> +    local inport=$1 eth_src=$2 eth_dst=$3 ipv4_src=$4 ipv4_dst=$5 
> ip_chksum=$6 icmp_chksum=$7
> +    local exp_ip_chksum=$8 exp_icmp_chksum=$9 mirror_encap_type=${10} 
> mirror_name=${11}
> +    shift; shift; shift; shift; shift; shift; shift
> +    shift; shift; shift; shift;
> +
> +    check ovn-nbctl mirror-add ${mirror_name} ${mirror_encap_type} 0 
> to-lport 192.168.1.12
> +    check ovn-nbctl lsp-attach-mirror ls1-lp1 ${mirror_name}
> +
> +    # Use ttl to exercise section 4.2.2.9 of RFC1812
> +    local ip_ttl=02
> +    local icmp_id=5fbf
> +    local icmp_seq=0001
> +    local icmp_data=$(seq 1 56 | xargs printf "%02x")
> +    local icmp_type_code_request=0800
> +    local 
> icmp_payload=${icmp_type_code_request}${icmp_chksum}${icmp_id}${icmp_seq}${icmp_data}
> +    local 
> packet=${eth_dst}${eth_src}08004500005400004000${ip_ttl}01${ip_chksum}${ipv4_src}${ipv4_dst}${icmp_payload}
> +
> +    as hv1 ovs-appctl netdev-dummy/receive vif$inport $packet
> +    if test X$exp_icmp_chksum != X; then
> +        # Expect to receive the reply, if any. In same port where packet was 
> sent.
> +        # Note: src and dst fields are expected to be reversed.
> +        local icmp_type_code_response=0000
> +        local reply_icmp_ttl=fe
> +        local 
> reply_icmp_payload=${icmp_type_code_response}${exp_icmp_chksum}${icmp_id}${icmp_seq}${icmp_data}
> +        local 
> reply=${eth_src}${eth_dst}08004500005400004000${reply_icmp_ttl}01${exp_ip_chksum}${ipv4_dst}${ipv4_src}${reply_icmp_payload}
> +        echo $reply >> vif$inport.expected
> +        local remote_mac=000000020200
> +        local local_mac=000000010200
> +        if test ${mirror_encap_type} = "gre" ; then
> +            #echo "GRE"
> +            local 
> mirror=${remote_mac}${local_mac}08004500007e00004000402fb6e9c0a8010bc0a8010c2000655800000000${reply}
> +        fi
> +        echo $mirror >> br-phys_n1.expected
> +    fi
> +}
> +
> +# Set IPs
> +rtr_l2_ip=$(ip_to_hex 172 16 1 1)
> +l1_ip=$(ip_to_hex 192 168 1 2)
> +
> +# Send ping packet and check for mirrored packet of the reply
> +test_ipv4_icmp_request 1 000000010203 0000000102f1 $l1_ip $rtr_l2_ip 0000 
> 8510 03ff 8d10 "gre" mirror0
> +
> +OVN_CHECK_PACKETS_CONTAIN([hv1/br-phys_n1-tx.pcap], [br-phys_n1.expected])
> +
> +tcpdump -r hv1/br-phys_n1-tx.pcap
> +
> +echo "---------OVN NB Mirror-----"
> +ovn-nbctl mirror-list
> +
> +echo "---------OVS Mirror----"
> +ovs-vsctl list Mirror
> +
> +# Start ovn-controller again just so OVN_CLEANUP doesn't complain
> +as hv2 start_daemon ovn-controller
> +
> +OVN_CLEANUP([hv1], [hv2])
> +AT_CLEANUP
> +])
>
>  OVN_FOR_EACH_NORTHD([
>  AT_SETUP([Port Groups])
> diff --git a/utilities/ovn-nbctl.c b/utilities/ovn-nbctl.c
> index 0d5647b2f..8bd2f7f85 100644
> --- a/utilities/ovn-nbctl.c
> +++ b/utilities/ovn-nbctl.c
> @@ -271,6 +271,16 @@ QoS commands:\n\
>                              remove QoS rules from SWITCH\n\
>    qos-list SWITCH           print QoS rules for SWITCH\n\
>  \n\
> +Mirror commands:\n\
> +  mirror-add NAME TYPE INDEX FILTER IP\n\
> +                            add a mirror with given name\n\
> +                            specify TYPE 'gre' or 'erspan'\n\
> +                            specify INDEX gre key / erpsan idx\n\
> +                            specify FILTER for mirroring selection\n\
> +                            specify Sink / Destination i.e Remote IP \n\
> +  mirror-del [NAME]         remove mirrors\n\
> +  mirror-list               print mirrors\n\
> +\n\
>  Meter commands:\n\
>    [--fair]\n\
>    meter-add NAME ACTION RATE UNIT [BURST]\n\
> @@ -311,6 +321,8 @@ Logical switch port commands:\n\
>                              set dhcpv6 options for PORT\n\
>    lsp-get-dhcpv6-options PORT  get the dhcpv6 options for PORT\n\
>    lsp-get-ls PORT           get the logical switch which the port belongs 
> to\n\
> +  lsp-attach-mirror PORT MIRROR   attach source PORT to the MIRROR\n\
> +  lsp-detach-mirror PORT MIRROR   detach source PORT from the MIRROR\n\
>  \n\
>  Forwarding group commands:\n\
>    [--liveness]\n\
> @@ -1685,6 +1697,130 @@ nbctl_pre_lsp_type(struct ctl_context *ctx)
>      ovsdb_idl_add_column(ctx->idl, &nbrec_logical_switch_port_col_type);
>  }
>
> +static void
> +nbctl_pre_lsp_mirror(struct ctl_context *ctx)
> +{
> +    ovsdb_idl_add_column(ctx->idl, &nbrec_logical_switch_port_col_name);
> +    ovsdb_idl_add_column(ctx->idl,
> +                         &nbrec_logical_switch_port_col_mirror_rules);
> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_name);
> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_src);
> +}
> +
> +static int
> +mirror_cmp(const void *mirror1_, const void *mirror2_)
> +{
> +    const struct nbrec_mirror *const *mirror_1 = mirror1_;
> +    const struct nbrec_mirror *const *mirror_2 = mirror2_;
> +
> +    const struct nbrec_mirror *mirror1 = *mirror_1;
> +    const struct nbrec_mirror *mirror2 = *mirror_2;
> +
> +    return strcmp(mirror1->name,mirror2->name);
> +}
> +
> +static char * OVS_WARN_UNUSED_RESULT
> +mirror_by_name_or_uuid(struct ctl_context *ctx, const char *id,
> +                    bool must_exist,
> +                    const struct nbrec_mirror **mirror_p)
> +{
> +    const struct nbrec_mirror *mirror = NULL;
> +    *mirror_p = NULL;
> +
> +    struct uuid mirror_uuid;
> +    bool is_uuid = uuid_from_string(&mirror_uuid, id);
> +    if (is_uuid) {
> +        mirror = nbrec_mirror_get_for_uuid(ctx->idl, &mirror_uuid);
> +    }
> +
> +    if (!mirror) {
> +        NBREC_MIRROR_FOR_EACH (mirror, ctx->idl) {
> +            if (!strcmp(mirror->name, id)) {
> +                break;
> +            }
> +        }
> +    }
> +
> +    if (!mirror && must_exist) {
> +        return xasprintf("%s: mirror %s not found",
> +                         id, is_uuid ? "UUID" : "name");
> +    }
> +
> +    *mirror_p = mirror;
> +    return NULL;
> +}
> +
> +static void
> +nbctl_lsp_attach_mirror(struct ctl_context *ctx)
> +{
> +    const char *port = ctx->argv[1];
> +    const char *mirror_name = ctx->argv[2];
> +    const struct nbrec_logical_switch_port *lsp = NULL;
> +    const struct nbrec_mirror *mirror;
> +
> +    char *error;
> +
> +    error = lsp_by_name_or_uuid(ctx, port, true, &lsp);
> +    if (error) {
> +        ctx->error = error;
> +        return;
> +    }
> +
> +
> +    /*check if a mirror rule actually exists on that name or not*/
> +    error = mirror_by_name_or_uuid(ctx, mirror_name, true, &mirror);
> +    if (error) {
> +        ctx->error = error;
> +        return;
> +    }
> +
> +    /* Check if same mirror rule already exists for the lsp */
> +    for (size_t i = 0; i < lsp->n_mirror_rules; i++) {
> +        if (!mirror_cmp(&lsp->mirror_rules[i], &mirror)) {
> +            bool may_exist = shash_find(&ctx->options, "--may-exist") != 
> NULL;
> +            if (!may_exist) {
> +                ctl_error(ctx, "Same mirror already existed on the lsp %s.",
> +                          ctx->argv[1]);
> +                return;
> +            }
> +            return;
> +        }
> +    }
> +
> +    nbrec_logical_switch_port_update_mirror_rules_addvalue(lsp, mirror);
> +    nbrec_mirror_update_src_addvalue(mirror,lsp);
> +
> +}
> +
> +static void
> +nbctl_lsp_detach_mirror(struct ctl_context *ctx)
> +{
> +    const char *port = ctx->argv[1];
> +    const char *mirror_name = ctx->argv[2];
> +    const struct nbrec_logical_switch_port *lsp = NULL;
> +    const struct nbrec_mirror *mirror;
> +
> +    char *error;
> +
> +    error = lsp_by_name_or_uuid(ctx, port, true, &lsp);
> +    if (error) {
> +        ctx->error = error;
> +        return;
> +    }
> +
> +
> +    /*check if a mirror rule actually exists on that name or not*/
> +    error = mirror_by_name_or_uuid(ctx, mirror_name, true, &mirror);
> +    if (error) {
> +        ctx->error = error;
> +        return;
> +    }
> +
> +    nbrec_logical_switch_port_update_mirror_rules_delvalue(lsp, mirror);
> +    nbrec_mirror_update_src_delvalue(mirror,lsp);
> +
> +}
> +
>  static void
>  nbctl_lsp_set_type(struct ctl_context *ctx)
>  {
> @@ -7239,6 +7375,202 @@ cmd_ha_ch_grp_set_chassis_prio(struct ctl_context 
> *ctx)
>      nbrec_ha_chassis_set_priority(ha_chassis, priority);
>  }
>
> +static char * OVS_WARN_UNUSED_RESULT
> +parse_filter(const char *arg, const char **selection_p)
> +{
> +    /* Validate selection.  Only require the first letter. */
> +    if (arg[0] == 't') {
> +        *selection_p = "to-lport";
> +    } else if (arg[0] == 'f') {
> +        *selection_p = "from-lport";
> +    } else if (arg[0] == 'b') {
> +        *selection_p = "both";
> +    } else {
> +        *selection_p = NULL;
> +        return xasprintf("%s: selection must be \"to-lport\" or "
> +                         "\"from-lport\" or \"both\" ", arg);
> +    }
> +    return NULL;
> +}
> +
> +static char * OVS_WARN_UNUSED_RESULT
> +parse_type(const char *arg, const char **type_p)
> +{
> +    /* Validate type.  Only require the second letter. */
> +    if (arg[0] == 'g') {
> +        *type_p = "gre";
> +    } else if (arg[0] == 'e') {
> +        *type_p = "erspan";
> +    } else {
> +        *type_p = NULL;
> +        return xasprintf("%s: type must be \"gre\" or "
> +                         "\"erspan\"", arg);
> +    }
> +    return NULL;
> +}
> +
> +static void
> +nbctl_pre_mirror_add(struct ctl_context *ctx)
> +{
> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_name);
> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_filter);
> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_index);
> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_sink);
> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_type);
> +}
> +
> +static void
> +nbctl_mirror_add(struct ctl_context *ctx)
> +{
> +    const char *filter = NULL;
> +    const char *sink_ip = NULL;
> +    const char *type = NULL;
> +    const char *name = NULL;
> +    char *new_external_ip = NULL;
> +    int64_t index;
> +    char *error = NULL;
> +    const struct nbrec_mirror *mirror_check = NULL;
> +
> +    /* Mirror Name */
> +    name = ctx->argv[1];
> +    NBREC_MIRROR_FOR_EACH (mirror_check, ctx->idl) {
> +            if (!strcmp(mirror_check->name, name)) {
> +                ctl_error(ctx, "Mirror with %s name already exists.",
> +                          name);
> +                return;
> +            }
> +        }
> +
> +    /* Tunnel Type - GRE/ERSPAN */
> +    error = parse_type(ctx->argv[2], &type);
> +    if (error) {
> +        ctx->error = error;
> +        return;
> +    }
> +
> +    /* tunnel index / GRE key / ERSPAN idx */
> +    index = atoi(ctx->argv[3]);
> +
> +    /* Filter for mirroring */
> +    error = parse_filter(ctx->argv[4], &filter);
> +    if (error) {
> +        ctx->error = error;
> +        return;
> +    }
> +
> +    /* Destination / Sink details */
> +    sink_ip = ctx->argv[5];
> +
> +    /* check if it is a valid ip */
> +    new_external_ip = normalize_ipv4_addr_str(sink_ip);
> +    if (!new_external_ip) {
> +        new_external_ip = normalize_ipv6_addr_str(sink_ip);
> +    }
> +
> +    if (new_external_ip) {
> +        free(new_external_ip);
> +    } else {
> +        ctl_error(ctx, "Invalid sink ip: %s", sink_ip);
> +        return;
> +    }
> +
> +    /* Create the mirror. */
> +    struct nbrec_mirror *mirror = nbrec_mirror_insert(ctx->txn);
> +    nbrec_mirror_set_name(mirror, name);
> +    nbrec_mirror_set_index(mirror, index);
> +    nbrec_mirror_set_filter(mirror, filter);
> +    nbrec_mirror_set_type(mirror, type);
> +    nbrec_mirror_set_sink(mirror, sink_ip);
> +
> +}
> +
> +static void
> +nbctl_pre_mirror_del(struct ctl_context *ctx)
> +{
> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_name);
> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_src);
> +}
> +
> +static void
> +nbctl_mirror_del(struct ctl_context *ctx)
> +{
> +    const struct nbrec_mirror *mirror, *next;
> +
> +    /* If a name is not specified, delete all mirrors. */
> +    if (ctx->argc == 1) {
> +        NBREC_MIRROR_FOR_EACH_SAFE (mirror, next, ctx->idl) {
> +            nbrec_mirror_delete(mirror);
> +        }
> +        return;
> +    }
> +
> +    /* Remove the matching mirror. */
> +    NBREC_MIRROR_FOR_EACH (mirror, ctx->idl) {
> +        if (strcmp(ctx->argv[1], mirror->name)) {
> +            continue;
> +        }
> +        nbrec_mirror_delete(mirror);
> +        return;
> +    }
> +}
> +
> +static void
> +nbctl_pre_mirror_list(struct ctl_context *ctx)
> +{
> +    ovsdb_idl_add_column(ctx->idl, &nbrec_logical_switch_port_col_name);
> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_name);
> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_filter);
> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_index);
> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_sink);
> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_type);
> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_src);
> +}
> +
> +static void
> +nbctl_mirror_list(struct ctl_context *ctx)
> +{
> +
> +    const struct nbrec_mirror **mirrors = NULL;
> +    const struct nbrec_mirror *mirror;
> +    size_t n_capacity = 0;
> +    size_t n_mirrors = 0;
> +
> +    NBREC_MIRROR_FOR_EACH (mirror, ctx->idl) {
> +        if (n_mirrors == n_capacity) {
> +            mirrors = x2nrealloc(mirrors, &n_capacity, sizeof *mirrors);
> +        }
> +
> +        mirrors[n_mirrors] = mirror;
> +        n_mirrors++;
> +    }
> +
> +    if (n_mirrors) {
> +        qsort(mirrors, n_mirrors, sizeof *mirrors, mirror_cmp);
> +    }
> +
> +    for (size_t i = 0; i < n_mirrors; i++) {
> +        mirror = mirrors[i];
> +        ds_put_format(&ctx->output, "%s:\n", mirror->name);
> +        /* print all the values */
> +        ds_put_format(&ctx->output, "  Type     :  %s\n", mirror->type);
> +        ds_put_format(&ctx->output, "  Sink     :  %s\n", mirror->sink);
> +        ds_put_format(&ctx->output, "  Filter   :  %s\n", mirror->filter);
> +        ds_put_format(&ctx->output, "  Index/Key:  %ld\n",
> +                                                 (long int)mirror->index);
> +        ds_put_cstr(&ctx->output,   "  Sources  :");
> +        if (mirror->n_src > 0) {
> +            for (size_t j = 0; j < mirror->n_src; j++) {
> +                ds_put_format(&ctx->output, "  %s", mirror->src[j]->name);
> +            }
> +        } else {
> +            ds_put_cstr(&ctx->output, "  None attached");
> +        }
> +        ds_put_cstr(&ctx->output, "\n");
> +    }
> +
> +    free(mirrors);
> +}
> +
>  static const struct ctl_table_class tables[NBREC_N_TABLES] = {
>      [NBREC_TABLE_DHCP_OPTIONS].row_ids
>      = {{&nbrec_logical_switch_port_col_name, NULL,
> @@ -7332,6 +7664,15 @@ static const struct ctl_command_syntax 
> nbctl_commands[] = {
>      { "qos-list", 1, 1, "SWITCH", nbctl_pre_qos_list, nbctl_qos_list,
>        NULL, "", RO },
>
> +    /* mirror commands. */
> +    { "mirror-add", 5, 5,
> +      "NAME TYPE INDEX FILTER IP",
> +      nbctl_pre_mirror_add, nbctl_mirror_add, NULL, "--may-exist", RW },
> +    { "mirror-del", 0, 1, "[NAME]",
> +      nbctl_pre_mirror_del, nbctl_mirror_del, NULL, "", RW },
> +    { "mirror-list", 0, 0, "", nbctl_pre_mirror_list, nbctl_mirror_list,
> +      NULL, "", RO },
> +
>      /* meter commands. */
>      { "meter-add", 4, 5, "NAME ACTION RATE UNIT [BURST]", 
> nbctl_pre_meter_add,
>        nbctl_meter_add, NULL, "--fair,--may-exist", RW },
> @@ -7386,6 +7727,10 @@ static const struct ctl_command_syntax 
> nbctl_commands[] = {
>        nbctl_lsp_get_dhcpv6_options, NULL, "", RO },
>      { "lsp-get-ls", 1, 1, "PORT", nbctl_pre_lsp_get_ls, nbctl_lsp_get_ls,
>        NULL, "", RO },
> +    { "lsp-attach-mirror", 2, 2, "PORT MIRROR", nbctl_pre_lsp_mirror,
> +      nbctl_lsp_attach_mirror, NULL, "", RW },
> +    { "lsp-detach-mirror", 2, 2, "PORT MIRROR", nbctl_pre_lsp_mirror,
> +      nbctl_lsp_detach_mirror, NULL, "", RW },
>
>      /* forwarding group commands. */
>      { "fwd-group-add", 4, INT_MAX, "SWITCH GROUP VIP VMAC PORT...",
> diff --git a/utilities/ovn-sbctl.c b/utilities/ovn-sbctl.c
> index 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
d...@openvswitch.org
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to