diff mbox series

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

Message ID 20220428190639.482614-1-abhiramrn@gmail.com
State Superseded
Headers show
Series [ovs-dev] OVN - Add Support for Port Mirroring | expand

Checks

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

Commit Message

Abhiram RN April 28, 2022, 7:06 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.

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        | 276 ++++++++++++++++++++++++
 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            |  30 ++-
 ovn-nb.xml                  |  53 +++++
 ovn-sb.ovsschema            |  23 +-
 ovn-sb.xml                  |  43 ++++
 tests/ovn-nbctl.at          |  81 +++++++
 utilities/ovn-nbctl.c       | 408 ++++++++++++++++++++++++++++++++++++
 utilities/ovn-sbctl.c       |   4 +
 17 files changed, 1073 insertions(+), 5 deletions(-)

Comments

0-day Robot April 28, 2022, 7:19 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
#1050 FILE: utilities/ovn-nbctl.c:275:
  mirror-add NAME TYPE INDEX FILTER {IP | PORT}\n\

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

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

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

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

Lines checked: 1519, 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
diff mbox series

Patch

diff --git a/controller/binding.c b/controller/binding.c
index 7281b0485..6deca7912 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,239 @@  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) %ld present %s ", pb->n_mirror_rules,
+                                                     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", 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 +2725,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..3bf996f8b 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": "3274418751 33317",
     "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,27 @@ 
                     "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"}}},
+            "isRoot": true},
         "Meter": {
             "columns": {
                 "name": {"type": "string"},
diff --git a/ovn-nb.xml b/ovn-nb.xml
index 9010240a8..e5da1fbf5 100644
--- a/ovn-nb.xml
+++ b/ovn-nb.xml
@@ -1511,6 +1511,10 @@ 
       </column>
     </group>
 
+    <column name="mirror_rules">
+      Mirror rules that apply to logical switch port which is the source
+    </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 +2436,55 @@ 
     </column>
   </table>
 
+  <table name="Mirror" title="Mirror Entry">
+    <p>
+      Each row in this table represents one Mirror that can be used for
+      port mirroring
+    </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.
+      </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..b62f48c8d 100644
--- a/ovn-sb.xml
+++ b/ovn-sb.xml
@@ -2632,6 +2632,45 @@  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
+    </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 +3121,10 @@  tcp.flags = RST;
       </column>
     </group>
 
+    <column name="mirror_rules">
+      Mirror rules that apply to the port binding
+    </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..2890d08e2 100644
--- a/tests/ovn-nbctl.at
+++ b/tests/ovn-nbctl.at
@@ -435,6 +435,87 @@  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 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])
+AT_CHECK([ovn-nbctl lsp-set-addresses sw0-port2 "aa:aa:aa:aa:aa:30 10.10.10.3"])
+AT_CHECK([ovn-nbctl mirror-add mirror3 gre 2 to-lport sw0-port2])
+
+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..be9c7a2a0 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 | PORT}\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 IP or port\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,265 @@  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_sink(struct ctl_context *ctx, const char **sink_ip,
+           struct lport_addresses *laddrs, bool *used_laddrs)
+{
+    char *new_external_ip = NULL;
+    const struct nbrec_logical_switch_port *lsp = NULL;
+    char *error = NULL;
+
+    error = lsp_by_name_or_uuid(ctx, ctx->argv[5], true, &lsp);
+    if (error) {
+        /* check if it is a valid ip */
+        new_external_ip = normalize_ipv4_addr_str(ctx->argv[5]);
+        if (!new_external_ip) {
+            new_external_ip = normalize_ipv6_addr_str(ctx->argv[5]);
+        }
+
+        if (new_external_ip) {
+           *sink_ip = ctx->argv[5];
+           free(new_external_ip);
+        } else {
+            return xasprintf("Invalid sink port");
+        }
+    } else {
+        /* Check if address is set to this port */
+        if (lsp->n_addresses) {
+            /* See if its dynamic or statically set */
+            char *ip_p = NULL;
+            /* Find occurrence of dynamic in lsp->addresses */
+            ip_p = strstr(lsp->addresses[0], "dynamic");
+
+            if (ip_p) {
+                if (lsp->dynamic_addresses) {
+                    ip_p = &lsp->dynamic_addresses[0];
+                }
+            } else {
+                ip_p = lsp->addresses[0];
+            }
+            if (!extract_lsp_addresses(ip_p, laddrs)) {
+                return xasprintf("IP address not set for the sink port");
+            }
+            *used_laddrs = true;
+            if (laddrs->n_ipv4_addrs) {
+                *sink_ip = laddrs->ipv4_addrs[0].addr_s;
+            } else if (laddrs->n_ipv6_addrs) {
+                *sink_ip = laddrs->ipv6_addrs[0].addr_s;
+            }
+        } else {
+            return xasprintf("IP not set for sink port");
+        }
+    }
+    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_logical_switch_port_col_name);
+    ovsdb_idl_add_column(ctx->idl, &nbrec_logical_switch_port_col_addresses);
+    ovsdb_idl_add_column(ctx->idl,
+                         &nbrec_logical_switch_port_col_dynamic_addresses);
+    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;
+    int64_t index;
+    char *error = NULL;
+    struct lport_addresses laddrs;
+    const struct nbrec_mirror *mirror_check = NULL;
+    bool used_laddrs = false;
+
+    /* 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 */
+    error = parse_sink(ctx, &sink_ip, &laddrs, &used_laddrs);
+    if (error) {
+        ctx->error = error;
+        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);
+
+    if (used_laddrs) {
+        destroy_lport_addresses(&laddrs);
+    }
+
+}
+
+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", 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 +7649,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 | PORT}",
+      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 +7712,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},