Added changes in ovn-nbctl, ovn-sbctl, northd and in ovn-controller. While Mirror creation just creates the mirror, the lsp-attach-mirror triggers the sequence to create Mirror in OVS DB on compute node. OVS already supports Port Mirroring.
Note: This is targeted to mirror to destinations anywhere outside the cluster where the analyser resides and it need not be an OVN node. Example commands are as below: Mirror creation ovn-nbctl mirror-add mirror1 gre 0 from-lport 10.10.10.2 Attach a logical port to the mirror. ovn-nbctl lsp-attach-mirror sw0-port1 mirror1 Detach a source from Mirror ovn-nbctl lsp-detach-mirror sw0-port1 mirror1 Mirror deletion ovn-nbctl mirror-del mirror1 Co-authored-by: Veda Barrenkala <vedabarrenk...@gmail.com> Signed-off-by: Veda Barrenkala <vedabarrenk...@gmail.com> Signed-off-by: Abhiram R N <abhira...@gmail.com> --- V5 --> V6: Fixed a memleak issue in mirror.c (missed free) V4 --> V5: Addressed review comments on V4 by Ihar i) Expanded test cases in ovn.at and ovn-nbctl.at ii) Handled case when number of mirror references is same but refers to different mirrors. iii) Did other minor changes in schema and xmls Files modified (V4 --> v5): Code --> mirror.c, northd.c, ovn-nbctl.c Test --> ovn-northd.at, ovn-nbctl.at, ovn.at Misc --> ovn-sb.ovsschema, ovn-sb.xml, ovn-nb.xml V3 --> V4: Addressed review comments from V3 i) Fixed Issues 1,2 and 3 ii) Did other minor changes suggested iii) Included Issues 1 and 3 in test cases Files modified (V3 --> v4): Code --> mirror.c, ovn-controller.c, northd.c Test --> ovn-northd.at, ovn-nbctl.at, ovn.at controller/automake.mk | 4 +- controller/mirror.c | 535 ++++++++++++++++++++++++++++++++++++ controller/mirror.h | 53 ++++ controller/ovn-controller.c | 222 ++++++++++++++- northd/en-northd.c | 4 + northd/inc-proc-northd.c | 4 + northd/northd.c | 172 ++++++++++++ northd/northd.h | 2 + ovn-nb.ovsschema | 31 ++- ovn-nb.xml | 63 +++++ ovn-sb.ovsschema | 26 +- ovn-sb.xml | 50 ++++ tests/ovn-nbctl.at | 116 ++++++++ tests/ovn-northd.at | 102 +++++++ tests/ovn.at | 215 +++++++++++++++ utilities/ovn-nbctl.c | 357 ++++++++++++++++++++++++ utilities/ovn-sbctl.c | 4 + 17 files changed, 1954 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..c3eb9e727 --- /dev/null +++ b/controller/mirror.c @@ -0,0 +1,535 @@ +/* Copyright (c) 2022 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <config.h> +#include <unistd.h> + +/* library headers */ +#include "lib/sset.h" +#include "lib/util.h" + +/* OVS includes. */ +#include "lib/vswitch-idl.h" +#include "openvswitch/vlog.h" + +/* OVN includes. */ +#include "binding.h" +#include "lib/ovn-sb-idl.h" +#include "mirror.h" + +VLOG_DEFINE_THIS_MODULE(port_mirror); + +/* Static function declarations */ + +static const struct ovsrec_port * +get_port_for_iface(const struct ovsrec_interface *iface, + const struct ovsrec_bridge *br_int) +{ + for (size_t i = 0; i < br_int->n_ports; i++) { + const struct ovsrec_port *p = br_int->ports[i]; + for (size_t j = 0; j < p->n_interfaces; j++) { + if (!strcmp(iface->name, p->interfaces[j]->name)) { + return p; + } + } + } + return NULL; +} + +static bool +mirror_create(const struct sbrec_port_binding *pb, + struct port_mirror_ctx *pm_ctx) +{ + const struct ovsrec_mirror *mirror = NULL; + + if (pb->n_up && !pb->up[0]) { + return true; + } + + if (pb->chassis != pm_ctx->chassis_rec) { + return true; + } + + if (!pm_ctx->ovs_idl_txn) { + return false; + } + + + VLOG_INFO("Mirror rule(s) present for %s ", pb->logical_port); + /* Loop through the mirror rules */ + for (size_t i =0; i < pb->n_mirror_rules; i++) { + /* check if the mirror already exists in OVS DB */ + bool create_mirror = true; + OVSREC_MIRROR_TABLE_FOR_EACH (mirror, pm_ctx->mirror_table) { + if (!strcmp(pb->mirror_rules[i]->name, mirror->name)) { + /* Mirror with same name already exists + * No need to create mirror + */ + create_mirror = false; + break; + } + } + + if (create_mirror) { + + struct smap options = SMAP_INITIALIZER(&options); + char *port_name, *key; + + key = xasprintf("%ld",(long int)pb->mirror_rules[i]->index); + smap_add(&options, "remote_ip", pb->mirror_rules[i]->sink); + + if (!strcmp(pb->mirror_rules[i]->type, "gre")) { + /* Set the GRE key */ + smap_add(&options, "key", key); + + } else if (!strcmp(pb->mirror_rules[i]->type, "erspan")) { + /* Set the ERSPAN index */ + smap_add(&options, "key", key); + smap_add(&options, "erspan_idx", key); + smap_add(&options, "erspan_ver","1"); + + } + struct ovsrec_interface *iface = + ovsrec_interface_insert(pm_ctx->ovs_idl_txn); + port_name = xasprintf("ovn-%s", + pb->mirror_rules[i]->name); + + ovsrec_interface_set_name(iface, port_name); + ovsrec_interface_set_type(iface, pb->mirror_rules[i]->type); + ovsrec_interface_set_options(iface, &options); + + struct ovsrec_port *port = + ovsrec_port_insert(pm_ctx->ovs_idl_txn); + ovsrec_port_set_name(port, port_name); + ovsrec_port_set_interfaces(port, &iface, 1); + + ovsrec_bridge_update_ports_addvalue(pm_ctx->br_int, port); + + smap_destroy(&options); + free(port_name); + free(key); + + VLOG_INFO("Creating Mirror in OVS DB"); + mirror = ovsrec_mirror_insert(pm_ctx->ovs_idl_txn); + ovsrec_mirror_set_name(mirror,pb->mirror_rules[i]->name); + ovsrec_mirror_update_output_port_addvalue(mirror, port); + ovsrec_bridge_update_mirrors_addvalue(pm_ctx->br_int, + mirror); + } + + struct local_binding *lbinding = local_binding_find( + pm_ctx->local_bindings, pb->logical_port); + const struct ovsrec_port *p = + get_port_for_iface(lbinding->iface, pm_ctx->br_int); + if (p) { + if (!strcmp(pb->mirror_rules[i]->filter,"from-lport")) { + ovsrec_mirror_update_select_src_port_addvalue(mirror, p); + } else if (!strcmp(pb->mirror_rules[i]->filter,"to-lport")) { + ovsrec_mirror_update_select_dst_port_addvalue(mirror, p); + } else { + ovsrec_mirror_update_select_src_port_addvalue(mirror, p); + ovsrec_mirror_update_select_dst_port_addvalue(mirror, p); + } + } + } + return true; +} + +static void +check_and_update_mirror_table(const struct sbrec_mirror *sb_mirror, + struct ovsrec_mirror *ovs_mirror) +{ + char *filter; + if ((ovs_mirror->n_select_dst_port) + && (ovs_mirror->n_select_src_port)) { + filter = xasprintf("both"); + } else if (ovs_mirror->n_select_dst_port) { + filter = xasprintf("to-lport"); + } else { + filter = xasprintf("from-lport"); + } + + if (strcmp(sb_mirror->filter, filter)) { + if (!strcmp(sb_mirror->filter,"from-lport") + && !strcmp(filter,"both")) { + for (size_t i = 0; i < ovs_mirror->n_select_dst_port; i++) { + ovsrec_mirror_update_select_dst_port_delvalue(ovs_mirror, + ovs_mirror->select_dst_port[i]); + } + } else if (!strcmp(sb_mirror->filter,"to-lport") + && !strcmp(filter,"both")) { + for (size_t i = 0; i < ovs_mirror->n_select_src_port; i++) { + ovsrec_mirror_update_select_src_port_delvalue(ovs_mirror, + ovs_mirror->select_src_port[i]); + } + } else if (!strcmp(sb_mirror->filter,"both") + && !strcmp(filter,"from-lport")) { + for (size_t i = 0; i < ovs_mirror->n_select_src_port; i++) { + ovsrec_mirror_update_select_dst_port_addvalue(ovs_mirror, + ovs_mirror->select_src_port[i]); + } + } else if (!strcmp(sb_mirror->filter,"both") + && !strcmp(filter,"to-lport")) { + for (size_t i = 0; i < ovs_mirror->n_select_dst_port; i++) { + ovsrec_mirror_update_select_src_port_addvalue(ovs_mirror, + ovs_mirror->select_dst_port[i]); + } + } else if (!strcmp(sb_mirror->filter,"to-lport") + && !strcmp(filter,"from-lport")) { + for (size_t i = 0; i < ovs_mirror->n_select_src_port; i++) { + ovsrec_mirror_update_select_dst_port_addvalue(ovs_mirror, + ovs_mirror->select_src_port[i]); + ovsrec_mirror_update_select_src_port_delvalue(ovs_mirror, + ovs_mirror->select_src_port[i]); + } + } else if (!strcmp(sb_mirror->filter,"from-lport") + && !strcmp(filter,"to-lport")) { + for (size_t i = 0; i < ovs_mirror->n_select_dst_port; i++) { + ovsrec_mirror_update_select_src_port_addvalue(ovs_mirror, + ovs_mirror->select_dst_port[i]); + ovsrec_mirror_update_select_dst_port_delvalue(ovs_mirror, + ovs_mirror->select_dst_port[i]); + } + } + } + free(filter); +} + +static void +check_and_update_interface_table(const struct sbrec_mirror *sb_mirror, + struct ovsrec_mirror *ovs_mirror) +{ + struct smap options = SMAP_INITIALIZER(&options); + char *key, *type; + struct smap *opts = &ovs_mirror->output_port->interfaces[0]->options; + struct ovsrec_interface *iface = + ovs_mirror->output_port->interfaces[0]; + + const char *erspan_ver = smap_get(opts, "erspan_ver"); + if (erspan_ver) { + type = xasprintf("erspan"); + } else { + type = xasprintf("gre"); + } + if (strcmp(type, sb_mirror->type)) { + ovsrec_interface_set_type(iface, sb_mirror->type); + } + + key = xasprintf("%ld",(long int)sb_mirror->index); + smap_add(&options, "remote_ip", sb_mirror->sink); + + if (!strcmp(sb_mirror->type, "gre")) { + /* Set the GRE key */ + smap_add(&options, "key", key); + + } else if (!strcmp(sb_mirror->type, "erspan")) { + /* Set the ERSPAN index */ + smap_add(&options, "key", key); + smap_add(&options, "erspan_idx", key); + smap_add(&options, "erspan_ver","1"); + + } + + ovsrec_interface_set_options(iface, &options); + smap_destroy(&options); + free(type); + free(key); + +} + +static void +mirror_update(const struct sbrec_mirror *sb_mirror, + struct ovsrec_mirror *ovs_mirror) +{ + check_and_update_interface_table(sb_mirror, ovs_mirror); + + check_and_update_mirror_table(sb_mirror, ovs_mirror); +} + +static bool +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 (!pm_ctx->ovs_idl_txn) { + return false; + } + + if (!delete_all) { + for (size_t i = 0; i < pb->n_mirror_rules ; i++) { + sset_add(&pb_mirrors, pb->mirror_rules[i]->name); + } + } + + if (delete_all && (shash_is_empty(pb_mirror_map)) && pb->n_mirror_rules) { + for (size_t i = 0; i < pb->n_mirror_rules ; i++) { + + struct ovsrec_mirror *ovs_mirror = NULL; + ovs_mirror = shash_find_data(pm_ctx->ovs_mirrors, + pb->mirror_rules[i]->name); + if (ovs_mirror) { + if (ovs_mirror->n_select_dst_port == 0 && + ovs_mirror->n_select_src_port == 0) { + ovsrec_bridge_update_ports_delvalue(pm_ctx->br_int, + ovs_mirror->output_port); + ovsrec_bridge_update_mirrors_delvalue(pm_ctx->br_int, + ovs_mirror); + ovsrec_port_delete(ovs_mirror->output_port); + ovsrec_mirror_delete(ovs_mirror); + } + } + } + } + + struct shash_node *mirror_node; + SHASH_FOR_EACH (mirror_node, pb_mirror_map) { + struct ovsrec_mirror *ovs_mirror = mirror_node->data; + if (!sset_find(&pb_mirrors, ovs_mirror->name)) { + /* Find if the mirror has other sources i*/ + if ((ovs_mirror->n_select_dst_port > 1) || + (ovs_mirror->n_select_src_port > 1)) { + /* More than 1 source then just + * update the mirror table + */ + bool done = false; + for (size_t i = 0; ((i < ovs_mirror->n_select_dst_port) + && (done == false)); i++) { + const struct ovsrec_port *port_rec = + ovs_mirror->select_dst_port[i]; + for (size_t j = 0; j < port_rec->n_interfaces; j++) { + const struct ovsrec_interface *iface_rec; + + iface_rec = port_rec->interfaces[j]; + const char *iface_id = + smap_get(&iface_rec->external_ids, + "iface-id"); + if (!strcmp(iface_id,pb->logical_port)) { + ovsrec_mirror_update_select_dst_port_delvalue( + ovs_mirror, port_rec); + done = true; + break; + } + } + } + done = false; + for (size_t i = 0; ((i < ovs_mirror->n_select_src_port) + && (done == false)); i++) { + const struct ovsrec_port *port_rec = + ovs_mirror->select_src_port[i]; + for (size_t j = 0; j < port_rec->n_interfaces; j++) { + const struct ovsrec_interface *iface_rec; + + iface_rec = port_rec->interfaces[j]; + const char *iface_id = + smap_get(&iface_rec->external_ids, + "iface-id"); + if (!strcmp(iface_id,pb->logical_port)) { + ovsrec_mirror_update_select_src_port_delvalue( + ovs_mirror, port_rec); + done = true; + break; + } + } + } + } else { + /* + * If only 1 source delete the output port + * and then delete the mirror completely + */ + VLOG_INFO("Only 1 source for the mirror. Hence delete it"); + ovsrec_bridge_update_ports_delvalue(pm_ctx->br_int, + ovs_mirror->output_port); + ovsrec_bridge_update_mirrors_delvalue(pm_ctx->br_int, + ovs_mirror); + ovsrec_port_delete(ovs_mirror->output_port); + ovsrec_mirror_delete(ovs_mirror); + } + } + } + + const char *used_node, *used_next; + SSET_FOR_EACH_SAFE (used_node, used_next, &pb_mirrors) { + sset_delete(&pb_mirrors, SSET_NODE_FROM_NAME(used_node)); + } + sset_destroy(&pb_mirrors); + + return true; +} + +static void +find_port_specific_mirrors (const struct sbrec_port_binding *pb, + struct port_mirror_ctx *pm_ctx, + struct shash *pb_mirror_map) +{ + const struct ovsrec_mirror *mirror = NULL; + + OVSREC_MIRROR_TABLE_FOR_EACH (mirror, pm_ctx->mirror_table) { + for (size_t i = 0; i < mirror->n_select_dst_port; i++) { + const struct ovsrec_port *port_rec = mirror->select_dst_port[i]; + for (size_t j = 0; j < port_rec->n_interfaces; j++) { + const struct ovsrec_interface *iface_rec; + iface_rec = port_rec->interfaces[j]; + const char *logical_port = + smap_get(&iface_rec->external_ids, "iface-id"); + if (!strcmp(logical_port, pb->logical_port)) { + shash_add_once(pb_mirror_map, mirror->name, mirror); + } + } + } + for (size_t i = 0; i < mirror->n_select_src_port; i++) { + const struct ovsrec_port *port_rec = mirror->select_src_port[i]; + for (size_t j = 0; j < port_rec->n_interfaces; j++) { + const struct ovsrec_interface *iface_rec; + iface_rec = port_rec->interfaces[j]; + const char *logical_port = + smap_get(&iface_rec->external_ids, "iface-id"); + if (!strcmp(logical_port, pb->logical_port)) { + shash_add_once(pb_mirror_map, mirror->name, mirror); + } + } + } + } +} + +void +mirror_register_ovs_idl(struct ovsdb_idl *ovs_idl) +{ + ovsdb_idl_add_column(ovs_idl, &ovsrec_bridge_col_mirrors); + + ovsdb_idl_add_table(ovs_idl, &ovsrec_table_mirror); + ovsdb_idl_add_column(ovs_idl, &ovsrec_mirror_col_name); + ovsdb_idl_add_column(ovs_idl, &ovsrec_mirror_col_output_port); + ovsdb_idl_add_column(ovs_idl, &ovsrec_mirror_col_select_dst_port); + ovsdb_idl_add_column(ovs_idl, &ovsrec_mirror_col_select_src_port); +} + + +void +ovn_port_mirror_init(struct shash *ovs_mirrors) +{ + shash_init(ovs_mirrors); +} + +void +ovn_port_mirror_run(struct port_mirror_ctx *pm_ctx) +{ + const struct sbrec_port_binding *pb; + SBREC_PORT_BINDING_TABLE_FOR_EACH (pb, + pm_ctx->port_binding_table) { + ovn_port_mirror_handle_lport(pb, false, pm_ctx); + } +} + +bool +ovn_port_mirror_handle_lport(const struct sbrec_port_binding *pb, bool removed, + struct port_mirror_ctx *pm_ctx) +{ + bool ret = true; + struct shash port_ovs_mirrors = SHASH_INITIALIZER(&port_ovs_mirrors); + + /* Need to find if mirror needs update */ + find_port_specific_mirrors(pb, pm_ctx, &port_ovs_mirrors); + if (removed == false) { + if ((pb->n_mirror_rules == 0) + && (shash_is_empty(&port_ovs_mirrors))) { + /* No mirror update */ + } else if (pb->n_mirror_rules == shash_count(&port_ovs_mirrors)) { + /* Though number of mirror rules are same, + * need to verify the contents + */ + for (size_t i = 0; i < pb->n_mirror_rules; i++) { + if (!shash_find(&port_ovs_mirrors, + pb->mirror_rules[i]->name)) { + /* Mis match in OVN SB DB and OVS DB + * Delete and Create mirror(s) with proper sources + */ + ret = mirror_delete(pb, pm_ctx, + &port_ovs_mirrors, false); + if (ret) { + ret = mirror_create(pb, pm_ctx); + } + break; + } + } + } else { + /* Update Mirror */ + if (pb->n_mirror_rules > shash_count(&port_ovs_mirrors)) { + /* create mirror, + * if mirror already exists only update selection + */ + ret = mirror_create(pb, pm_ctx); + } else { + /* delete mirror, + * if mirror has other sources only update selection + */ + ret = mirror_delete(pb, pm_ctx, &port_ovs_mirrors, false); + } + } + } else { + ret = mirror_delete(pb, pm_ctx, &port_ovs_mirrors, true); + } + + struct shash_node *ovs_mirror_node, *ovs_mirror_next; + SHASH_FOR_EACH_SAFE (ovs_mirror_node, ovs_mirror_next, + &port_ovs_mirrors) { + shash_delete(&port_ovs_mirrors, ovs_mirror_node); + } + shash_destroy(&port_ovs_mirrors); + + return ret; +} + +bool +ovn_port_mirror_handle_update(struct port_mirror_ctx *pm_ctx) +{ + const struct sbrec_mirror *mirror = NULL; + struct ovsrec_mirror *ovs_mirror = NULL; + + SBREC_MIRROR_TABLE_FOR_EACH_TRACKED (mirror, pm_ctx->sb_mirror_table) { + /* For each tracked mirror entry check if OVS entry is there*/ + ovs_mirror = shash_find_data(pm_ctx->ovs_mirrors, mirror->name); + if (ovs_mirror) { + if (sbrec_mirror_is_deleted(mirror)) { + /* Need to delete the mirror in OVS */ + VLOG_INFO("Delete mirror and remove port"); + ovsrec_bridge_update_ports_delvalue(pm_ctx->br_int, + ovs_mirror->output_port); + ovsrec_bridge_update_mirrors_delvalue(pm_ctx->br_int, + ovs_mirror); + ovsrec_port_delete(ovs_mirror->output_port); + ovsrec_mirror_delete(ovs_mirror); + } else { + mirror_update(mirror, ovs_mirror); + } + } + } + + return true; +} + +void +ovn_port_mirror_destroy(struct shash *ovs_mirrors) +{ + struct shash_node *ovs_mirror_node, *ovs_mirror_next; + SHASH_FOR_EACH_SAFE (ovs_mirror_node, ovs_mirror_next, + ovs_mirrors) { + shash_delete(ovs_mirrors, ovs_mirror_node); + } + shash_destroy(ovs_mirrors); +} + diff --git a/controller/mirror.h b/controller/mirror.h new file mode 100644 index 000000000..85b964f55 --- /dev/null +++ b/controller/mirror.h @@ -0,0 +1,53 @@ +/* Copyright (c) 2022 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef OVN_MIRROR_H +#define OVN_MIRROR_H 1 + +struct ovsdb_idl_txn; +struct ovsrec_port_table; +struct ovsrec_bridge; +struct ovsrec_bridge_table; +struct ovsrec_open_vswitch_table; +struct sbrec_chassis; +struct ovsrec_interface_table; +struct ovsrec_mirror_table; +struct sbrec_mirror_table; +struct sbrec_port_binding_table; + +struct port_mirror_ctx { + struct shash *ovs_mirrors; + struct ovsdb_idl_txn *ovs_idl_txn; + const struct ovsrec_port_table *port_table; + const struct ovsrec_bridge *br_int; + const struct sbrec_chassis *chassis_rec; + const struct ovsrec_bridge_table *bridge_table; + const struct ovsrec_open_vswitch_table *ovs_table; + const struct ovsrec_interface_table *iface_table; + const struct ovsrec_mirror_table *mirror_table; + const struct sbrec_mirror_table *sb_mirror_table; + const struct sbrec_port_binding_table *port_binding_table; + struct shash *local_bindings; +}; + +void mirror_register_ovs_idl(struct ovsdb_idl *); +void ovn_port_mirror_init(struct shash *); +void ovn_port_mirror_destroy(struct shash *); +void ovn_port_mirror_run(struct port_mirror_ctx *pm_ctx); +bool ovn_port_mirror_handle_lport(const struct sbrec_port_binding *pb, + bool removed, + struct port_mirror_ctx *pm_ctx); +bool ovn_port_mirror_handle_update(struct port_mirror_ctx *pm_ctx); +#endif diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c index 89a495a04..4b23b1aff 100644 --- a/controller/ovn-controller.c +++ b/controller/ovn-controller.c @@ -77,6 +77,7 @@ #include "stopwatch.h" #include "lib/inc-proc-eng.h" #include "hmapx.h" +#include "mirror.h" VLOG_DEFINE_THIS_MODULE(main); @@ -963,6 +964,7 @@ ctrl_register_ovs_idl(struct ovsdb_idl *ovs_idl) bfd_register_ovs_idl(ovs_idl); physical_register_ovs_idl(ovs_idl); vif_plug_register_ovs_idl(ovs_idl); + mirror_register_ovs_idl(ovs_idl); } #define SB_NODES \ @@ -983,6 +985,7 @@ ctrl_register_ovs_idl(struct ovsdb_idl *ovs_idl) SB_NODE(load_balancer, "load_balancer") \ SB_NODE(fdb, "fdb") \ SB_NODE(meter, "meter") \ + SB_NODE(mirror, "mirror") \ SB_NODE(static_mac_binding, "static_mac_binding") enum sb_engine_node { @@ -1000,7 +1003,8 @@ enum sb_engine_node { OVS_NODE(bridge, "bridge") \ OVS_NODE(port, "port") \ OVS_NODE(interface, "interface") \ - OVS_NODE(qos, "qos") + OVS_NODE(qos, "qos") \ + OVS_NODE(mirror, "mirror") enum ovs_engine_node { #define OVS_NODE(NAME, NAME_STR) OVS_##NAME, @@ -2393,6 +2397,203 @@ load_balancers_by_dp_cleanup(struct hmap *lbs) free(lbs); } +/* Mirror Engine */ +struct ed_type_port_mirror { + struct shash ovs_mirrors; +}; + +static void * +en_port_mirror_init(struct engine_node *node OVS_UNUSED, + struct engine_arg *arg OVS_UNUSED) +{ + struct ed_type_port_mirror *port_mirror = xzalloc(sizeof *port_mirror); + ovn_port_mirror_init(&port_mirror->ovs_mirrors); + return port_mirror; +} + +static void +en_port_mirror_cleanup(void *data) +{ + struct ed_type_port_mirror *port_mirror = data; + ovn_port_mirror_destroy(&port_mirror->ovs_mirrors); +} + +static void +init_port_mirror_ctx(struct engine_node *node, + struct ed_type_runtime_data *rt_data, + struct ed_type_port_mirror *port_mirror_data, + struct port_mirror_ctx *pm_ctx) +{ + struct ovsrec_open_vswitch_table *ovs_table = + (struct ovsrec_open_vswitch_table *)EN_OVSDB_GET( + engine_get_input("OVS_open_vswitch", node)); + struct ovsrec_bridge_table *bridge_table = + (struct ovsrec_bridge_table *)EN_OVSDB_GET( + engine_get_input("OVS_bridge", node)); + const char *chassis_id = get_ovs_chassis_id(ovs_table); + const struct ovsrec_bridge *br_int = get_br_int(bridge_table, ovs_table); + + ovs_assert(br_int && chassis_id); + const struct sbrec_chassis *chassis = NULL; + struct ovsdb_idl_index *sbrec_chassis_by_name = + engine_ovsdb_node_get_index( + engine_get_input("SB_chassis", node), + "name"); + + if (chassis_id) { + chassis = chassis_lookup_by_name(sbrec_chassis_by_name, chassis_id); + } + ovs_assert(chassis); + + struct ovsrec_port_table *port_table = + (struct ovsrec_port_table *)EN_OVSDB_GET( + engine_get_input("OVS_port", node)); + + struct ed_type_ovs_interface_shadow *iface_shadow = + engine_get_input_data("ovs_interface_shadow", node); + + struct ovsrec_mirror_table *mirror_table = + (struct ovsrec_mirror_table *)EN_OVSDB_GET( + engine_get_input("OVS_mirror", node)); + + struct sbrec_port_binding_table *pb_table = + (struct sbrec_port_binding_table *)EN_OVSDB_GET( + engine_get_input("SB_port_binding", node)); + + struct sbrec_mirror_table *sb_mirror_table = + (struct sbrec_mirror_table *)EN_OVSDB_GET( + engine_get_input("SB_mirror", node)); + + struct shash_node *ovs_mirror_node, *ovs_mirror_next; + SHASH_FOR_EACH_SAFE (ovs_mirror_node, ovs_mirror_next, + &port_mirror_data->ovs_mirrors) { + shash_delete(&port_mirror_data->ovs_mirrors, ovs_mirror_node); + } + + const struct ovsrec_mirror *ovsmirror = NULL; + OVSREC_MIRROR_TABLE_FOR_EACH (ovsmirror, mirror_table) { + shash_add(&port_mirror_data->ovs_mirrors, ovsmirror->name, ovsmirror); + } + + pm_ctx->ovs_idl_txn = engine_get_context()->ovs_idl_txn; + pm_ctx->port_table = port_table; + pm_ctx->iface_table = iface_shadow->iface_table; + pm_ctx->mirror_table = mirror_table; + pm_ctx->port_binding_table = pb_table; + pm_ctx->sb_mirror_table = sb_mirror_table; + pm_ctx->br_int = br_int; + pm_ctx->chassis_rec = chassis; + pm_ctx->bridge_table = bridge_table; + pm_ctx->ovs_table = ovs_table; + pm_ctx->ovs_mirrors = &port_mirror_data->ovs_mirrors; + pm_ctx->local_bindings = &rt_data->lbinding_data.bindings; +} + +static void +en_port_mirror_run(struct engine_node *node, void *data) +{ + struct port_mirror_ctx pm_ctx; + struct ed_type_port_mirror *port_mirror_data = data; + struct ed_type_runtime_data *rt_data = + engine_get_input_data("runtime_data", node); + + init_port_mirror_ctx(node, rt_data, port_mirror_data, &pm_ctx); + + ovn_port_mirror_run(&pm_ctx); + engine_set_node_state(node, EN_UPDATED); +} + +static bool +port_mirror_runtime_data_handler(struct engine_node *node, void *data) +{ + struct ed_type_runtime_data *rt_data = + engine_get_input_data("runtime_data", node); + + /* There is no tracked data. Fall back to full recompute of port_mirror */ + if (!rt_data->tracked) { + return false; + } + + struct hmap *tracked_dp_bindings = &rt_data->tracked_dp_bindings; + if (hmap_is_empty(tracked_dp_bindings)) { + return true; + } + + struct port_mirror_ctx pm_ctx; + struct ed_type_port_mirror *port_mirror_data = data; + init_port_mirror_ctx(node, rt_data, port_mirror_data, &pm_ctx); + + struct tracked_datapath *tdp; + HMAP_FOR_EACH (tdp, node, tracked_dp_bindings) { + if (tdp->tracked_type != TRACKED_RESOURCE_UPDATED) { + /* Fall back to full recompute when a local datapath + * is added or deleted. */ + return false; + } + + struct shash_node *shash_node; + SHASH_FOR_EACH (shash_node, &tdp->lports) { + struct tracked_lport *lport = shash_node->data; + bool removed = + lport->tracked_type == TRACKED_RESOURCE_REMOVED ? true: false; + if (!ovn_port_mirror_handle_lport(lport->pb, removed, &pm_ctx)) { + return false; + } + } + } + + engine_set_node_state(node, EN_UPDATED); + return true; +} + +static bool +port_mirror_port_binding_handler(struct engine_node *node, void *data) +{ + struct port_mirror_ctx pm_ctx; + struct ed_type_port_mirror *port_mirror_data = data; + struct ed_type_runtime_data *rt_data = + engine_get_input_data("runtime_data", node); + + init_port_mirror_ctx(node, rt_data, port_mirror_data, &pm_ctx); + + /* handle port binding updates (i.,e when the mirror column + * of port_binding is updated) + */ + const struct sbrec_port_binding *pb; + SBREC_PORT_BINDING_TABLE_FOR_EACH_TRACKED (pb, + pm_ctx.port_binding_table) { + bool removed = sbrec_port_binding_is_deleted(pb); + if (!ovn_port_mirror_handle_lport(pb, removed, &pm_ctx)) { + return false; + } + } + + engine_set_node_state(node, EN_UPDATED); + return true; + +} + +static bool +port_mirror_sb_mirror_handler(struct engine_node *node, void *data) +{ + struct port_mirror_ctx pm_ctx; + struct ed_type_port_mirror *port_mirror_data = data; + struct ed_type_runtime_data *rt_data = + engine_get_input_data("runtime_data", node); + + init_port_mirror_ctx(node, rt_data, port_mirror_data, &pm_ctx); + + /* handle sb mirror updates + */ + if (!ovn_port_mirror_handle_update(&pm_ctx)) { + return false; + } + + engine_set_node_state(node, EN_UPDATED); + return true; + +} + /* Engine node which is used to handle the Non VIF data like * - OVS patch ports * - Tunnel ports and the related chassis information. @@ -3677,6 +3878,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 @@ -3832,6 +4034,22 @@ main(int argc, char *argv[]) engine_add_input(&en_flow_output, &en_pflow_output, flow_output_pflow_output_handler); + engine_add_input(&en_port_mirror, &en_ovs_open_vswitch, NULL); + engine_add_input(&en_port_mirror, &en_ovs_bridge, NULL); + engine_add_input(&en_port_mirror, &en_ovs_mirror, NULL); + engine_add_input(&en_port_mirror, &en_sb_chassis, NULL); + engine_add_input(&en_port_mirror, &en_ovs_port, engine_noop_handler); + engine_add_input(&en_port_mirror, &en_ovs_interface_shadow, + engine_noop_handler); + engine_add_input(&en_flow_output, &en_port_mirror, + engine_noop_handler); + engine_add_input(&en_port_mirror, &en_runtime_data, + port_mirror_runtime_data_handler); + engine_add_input(&en_port_mirror, &en_sb_mirror, + port_mirror_sb_mirror_handler); + engine_add_input(&en_port_mirror, &en_sb_port_binding, + port_mirror_port_binding_handler); + struct engine_arg engine_arg = { .sb_idl = ovnsb_idl_loop.idl, .ovs_idl = ovs_idl_loop.idl, @@ -4151,6 +4369,8 @@ main(int argc, char *argv[]) ovnsb_idl_loop.idl), sbrec_sb_global_table_get(ovnsb_idl_loop.idl)); stopwatch_stop(BFD_RUN_STOPWATCH_NAME, time_msec()); + } else { + engine_run(false); } runtime_data = engine_get_data(&en_runtime_data); diff --git a/northd/en-northd.c b/northd/en-northd.c index 4907a1ff2..74cac1b8e 100644 --- a/northd/en-northd.c +++ b/northd/en-northd.c @@ -78,6 +78,8 @@ void en_northd_run(struct engine_node *node, void *data) EN_OVSDB_GET(engine_get_input("NB_acl", node)); input_data.nbrec_static_mac_binding_table = EN_OVSDB_GET(engine_get_input("NB_static_mac_binding", node)); + input_data.nbrec_mirror_table = + EN_OVSDB_GET(engine_get_input("NB_mirror", node)); input_data.sbrec_sb_global_table = EN_OVSDB_GET(engine_get_input("SB_sb_global", node)); @@ -111,6 +113,8 @@ void en_northd_run(struct engine_node *node, void *data) EN_OVSDB_GET(engine_get_input("SB_chassis_private", node)); input_data.sbrec_static_mac_binding_table = EN_OVSDB_GET(engine_get_input("SB_static_mac_binding", node)); + input_data.sbrec_mirror_table = + EN_OVSDB_GET(engine_get_input("SB_mirror", node)); northd_run(&input_data, data, eng_ctx->ovnnb_idl_txn, diff --git a/northd/inc-proc-northd.c b/northd/inc-proc-northd.c index 54e0ad3b0..ac27a730e 100644 --- a/northd/inc-proc-northd.c +++ b/northd/inc-proc-northd.c @@ -50,6 +50,7 @@ VLOG_DEFINE_THIS_MODULE(inc_proc_northd); NB_NODE(acl, "acl") \ NB_NODE(logical_router, "logical_router") \ NB_NODE(qos, "qos") \ + NB_NODE(mirror, "mirror") \ NB_NODE(meter, "meter") \ NB_NODE(meter_band, "meter_band") \ NB_NODE(logical_router_port, "logical_router_port") \ @@ -92,6 +93,7 @@ VLOG_DEFINE_THIS_MODULE(inc_proc_northd); SB_NODE(logical_flow, "logical_flow") \ SB_NODE(logical_dp_group, "logical_DP_group") \ SB_NODE(multicast_group, "multicast_group") \ + SB_NODE(mirror, "mirror") \ SB_NODE(meter, "meter") \ SB_NODE(meter_band, "meter_band") \ SB_NODE(datapath_binding, "datapath_binding") \ @@ -172,6 +174,7 @@ void inc_proc_northd_init(struct ovsdb_idl_loop *nb, engine_add_input(&en_northd, &en_nb_acl, NULL); engine_add_input(&en_northd, &en_nb_logical_router, NULL); engine_add_input(&en_northd, &en_nb_qos, NULL); + engine_add_input(&en_northd, &en_nb_mirror, NULL); engine_add_input(&en_northd, &en_nb_meter, NULL); engine_add_input(&en_northd, &en_nb_meter_band, NULL); engine_add_input(&en_northd, &en_nb_logical_router_port, NULL); @@ -194,6 +197,7 @@ void inc_proc_northd_init(struct ovsdb_idl_loop *nb, engine_add_input(&en_northd, &en_sb_address_set, NULL); engine_add_input(&en_northd, &en_sb_port_group, NULL); engine_add_input(&en_northd, &en_sb_logical_dp_group, NULL); + engine_add_input(&en_northd, &en_sb_mirror, NULL); engine_add_input(&en_northd, &en_sb_meter, NULL); engine_add_input(&en_northd, &en_sb_meter_band, NULL); engine_add_input(&en_northd, &en_sb_datapath_binding, NULL); diff --git a/northd/northd.c b/northd/northd.c index 4a40ec9b0..e4ebda4c3 100644 --- a/northd/northd.c +++ b/northd/northd.c @@ -3214,6 +3214,89 @@ ovn_port_update_sbrec_chassis( free(requested_chassis_sb); } +static void +do_sb_mirror_addition(struct northd_input *input_data, + const struct ovn_port *op) +{ + for (size_t 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 +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 if (op->sb->n_mirror_rules < op->nbsp->n_mirror_rules) { + /* Needs addition in SB */ + do_sb_mirror_addition(input_data, op); + } else if (op->sb->n_mirror_rules == op->nbsp->n_mirror_rules) { + /* + * Check if its the same mirrors on both SB and NB DBs + * If not update accordingly. + */ + bool needs_sb_addition = false; + 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]); + needs_sb_addition = true; + } + } + + 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); + + if (needs_sb_addition) { + do_sb_mirror_addition(input_data, op); + } + } +} + static void ovn_port_update_sbrec(struct northd_input *input_data, struct ovsdb_idl_txn *ovnsb_txn, @@ -3573,6 +3656,15 @@ 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 */ + 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); @@ -14910,6 +15002,85 @@ sync_meters(struct northd_input *input_data, shash_destroy(&sb_meters); } +static bool +mirror_needs_update(const struct nbrec_mirror *nb_mirror, + const struct sbrec_mirror *sb_mirror) +{ + + if (nb_mirror->index != sb_mirror->index) { + return true; + } else if (strcmp(nb_mirror->sink, sb_mirror->sink)) { + return true; + } else if (strcmp(nb_mirror->type, sb_mirror->type)) { + return true; + } else if (strcmp(nb_mirror->filter, sb_mirror->filter)) { + return true; + } + + return false; +} + +static void +sync_mirrors_iterate_nb_mirror(struct ovsdb_idl_txn *ovnsb_txn, + const char *mirror_name, + const struct nbrec_mirror *nb_mirror, + struct shash *sb_mirrors, + struct sset *used_sb_mirrors) +{ + const struct sbrec_mirror *sb_mirror; + bool new_sb_mirror = false; + + sb_mirror = shash_find_data(sb_mirrors, mirror_name); + if (!sb_mirror) { + sb_mirror = sbrec_mirror_insert(ovnsb_txn); + sbrec_mirror_set_name(sb_mirror, mirror_name); + shash_add(sb_mirrors, sb_mirror->name, sb_mirror); + new_sb_mirror = true; + } + sset_add(used_sb_mirrors, mirror_name); + + if ((new_sb_mirror) || mirror_needs_update(nb_mirror, sb_mirror)) { + sbrec_mirror_set_filter(sb_mirror,nb_mirror->filter); + sbrec_mirror_set_index(sb_mirror, nb_mirror->index); + sbrec_mirror_set_sink(sb_mirror, nb_mirror->sink); + sbrec_mirror_set_type(sb_mirror, nb_mirror->type); + } +} + +static void +sync_mirrors(struct northd_input *input_data, + struct ovsdb_idl_txn *ovnsb_txn) +{ + struct shash sb_mirrors = SHASH_INITIALIZER(&sb_mirrors); + struct sset used_sb_mirrors = SSET_INITIALIZER(&used_sb_mirrors); + + const struct sbrec_mirror *sb_mirror; + SBREC_MIRROR_TABLE_FOR_EACH (sb_mirror, input_data->sbrec_mirror_table) { + shash_add(&sb_mirrors, sb_mirror->name, sb_mirror); + } + + const struct nbrec_mirror *nb_mirror; + NBREC_MIRROR_TABLE_FOR_EACH (nb_mirror, input_data->nbrec_mirror_table) { + sync_mirrors_iterate_nb_mirror(ovnsb_txn, nb_mirror->name, nb_mirror, + &sb_mirrors, &used_sb_mirrors); + } + + const char *used_mirror; + const char *used_mirror_next; + SSET_FOR_EACH_SAFE (used_mirror, used_mirror_next, &used_sb_mirrors) { + shash_find_and_delete(&sb_mirrors, used_mirror); + sset_delete(&used_sb_mirrors, SSET_NODE_FROM_NAME(used_mirror)); + } + sset_destroy(&used_sb_mirrors); + + struct shash_node *node, *next; + SHASH_FOR_EACH_SAFE (node, next, &sb_mirrors) { + sbrec_mirror_delete(node->data); + shash_delete(&sb_mirrors, node); + } + shash_destroy(&sb_mirrors); +} + /* * struct 'dns_info' is used to sync the DNS records between OVN Northbound db * and Southbound db. @@ -15530,6 +15701,7 @@ ovnnb_db_run(struct northd_input *input_data, sync_address_sets(input_data, ovnsb_txn, &data->datapaths); sync_port_groups(input_data, ovnsb_txn, &data->port_groups); sync_meters(input_data, ovnsb_txn, &data->meter_groups); + sync_mirrors(input_data, ovnsb_txn); sync_dns_entries(input_data, ovnsb_txn, &data->datapaths); cleanup_stale_fdb_entries(input_data, &data->datapaths); stopwatch_stop(CLEAR_LFLOWS_CTX_STOPWATCH_NAME, time_msec()); diff --git a/northd/northd.h b/northd/northd.h index d9856af97..8a4f6a428 100644 --- a/northd/northd.h +++ b/northd/northd.h @@ -34,6 +34,7 @@ struct northd_input { const struct nbrec_acl_table *nbrec_acl_table; const struct nbrec_static_mac_binding_table *nbrec_static_mac_binding_table; + const struct nbrec_mirror_table *nbrec_mirror_table; /* Southbound table references */ const struct sbrec_sb_global_table *sbrec_sb_global_table; @@ -53,6 +54,7 @@ struct northd_input { const struct sbrec_chassis_private_table *sbrec_chassis_private_table; const struct sbrec_static_mac_binding_table *sbrec_static_mac_binding_table; + const struct sbrec_mirror_table *sbrec_mirror_table; /* Indexes */ struct ovsdb_idl_index *sbrec_chassis_by_name; diff --git a/ovn-nb.ovsschema b/ovn-nb.ovsschema index 174364c8b..01de55222 100644 --- a/ovn-nb.ovsschema +++ b/ovn-nb.ovsschema @@ -1,7 +1,7 @@ { "name": "OVN_Northbound", - "version": "6.3.0", - "cksum": "4042813038 31869", + "version": "6.4.0", + "cksum": "589874483 33352", "tables": { "NB_Global": { "columns": { @@ -132,6 +132,11 @@ "refType": "weak"}, "min": 0, "max": 1}}, + "mirror_rules": {"type": {"key": {"type": "uuid", + "refTable": "Mirror", + "refType": "weak"}, + "min": 0, + "max": "unlimited"}}, "ha_chassis_group": { "type": {"key": {"type": "uuid", "refTable": "HA_Chassis_Group", @@ -301,6 +306,28 @@ "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}}}, "isRoot": false}, + "Mirror": { + "columns": { + "name": {"type": "string"}, + "filter": {"type": {"key": {"type": "string", + "enum": ["set", ["from-lport", + "to-lport", + "both"]]}}}, + "sink":{"type": "string"}, + "type": {"type": {"key": {"type": "string", + "enum": ["set", ["gre", + "erspan"]]}}}, + "index": {"type": "integer"}, + "src": {"type": {"key": {"type": "uuid", + "refTable": "Logical_Switch_Port", + "refType": "weak"}, + "min": 0, + "max": "unlimited"}}, + "external_ids": { + "type": {"key": "string", "value": "string", + "min": 0, "max": "unlimited"}}}, + "indexes": [["name"]], + "isRoot": true}, "Meter": { "columns": { "name": {"type": "string"}, diff --git a/ovn-nb.xml b/ovn-nb.xml index 7fe88af27..2360e5afd 100644 --- a/ovn-nb.xml +++ b/ovn-nb.xml @@ -1554,6 +1554,11 @@ </column> </group> + <column name="mirror_rules"> + Mirror rules that apply to logical switch port which is the source. + Please see the <ref table="Mirror"/> table. + </column> + <column name="ha_chassis_group"> References a row in the OVN Northbound database's <ref table="HA_Chassis_Group" db="OVN_Northbound"/> table. @@ -2491,6 +2496,64 @@ </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. + Supported values for filter to-lport / from-lport / both + to-lport - to mirror packets coming into logical port + from-lport - to mirror packets going out of logical port + both - to mirror packets coming into and going out of logical port. + </p> + </column> + + <column name="sink"> + <p> + The value of this field represents the destination/sink of the mirror. + The value it takes is an IP address of the sink port. + </p> + </column> + + <column name="type"> + <p> + The value of this field represents the type of the tunnel used for + sending the mirrored packets. Supported Tunnel types GRE and ERSPAN + </p> + </column> + + <column name="index"> + <p> + The value of this field represents the tunnel ID. Depending on the + tunnel type configured, GRE key value if type GRE and erspan_idx value + if ERSPAN + </p> + </column> + + <column name="src"> + <p> + The value of this field represents a list of source ports for the + mirror. Please see the <ref table="Logical_Switch_Port"/> table. + </p> + </column> + + <column name="external_ids"> + See <em>External IDs</em> at the beginning of this document. + </column> + </table> + <table name="Meter" title="Meter entry"> <p> Each row in this table represents a meter that can be used for QoS or diff --git a/ovn-sb.ovsschema b/ovn-sb.ovsschema index 576ebbdeb..b83134416 100644 --- a/ovn-sb.ovsschema +++ b/ovn-sb.ovsschema @@ -1,7 +1,7 @@ { "name": "OVN_Southbound", - "version": "20.25.0", - "cksum": "53184112 28845", + "version": "20.26.0", + "cksum": "2344151793 30004", "tables": { "SB_Global": { "columns": { @@ -142,6 +142,23 @@ "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"}, + "external_ids": { + "type": {"key": "string", "value": "string", + "min": 0, "max": "unlimited"}}}, + "indexes": [["name"]], + "isRoot": true}, "Meter": { "columns": { "name": {"type": "string"}, @@ -230,6 +247,11 @@ "refTable": "Encap", "refType": "weak"}, "min": 0, "max": "unlimited"}}, + "mirror_rules": {"type": {"key": {"type": "uuid", + "refTable": "Mirror", + "refType": "weak"}, + "min": 0, + "max": "unlimited"}}, "mac": {"type": {"key": "string", "min": 0, "max": "unlimited"}}, diff --git a/ovn-sb.xml b/ovn-sb.xml index 37a709f83..c4e4c7570 100644 --- a/ovn-sb.xml +++ b/ovn-sb.xml @@ -2742,6 +2742,51 @@ 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> + + <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 @@ -3244,6 +3289,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..fd86798e2 100644 --- a/tests/ovn-nbctl.at +++ b/tests/ovn-nbctl.at @@ -435,6 +435,122 @@ 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 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 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 Verify if multiple ports are attached to the same mirror properly +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 sw0-port3 +]) + +dnl Detach one source port from mirror +AT_CHECK([ovn-nbctl lsp-detach-mirror sw0-port3 mirror3]) + +dnl Verify if detach source port from mirror happens properly +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 Delete a single mirror which has source attached. +AT_CHECK([ovn-nbctl mirror-del mirror3]) + +dnl Check if the detach happened from source properly +AT_CHECK([ovn-nbctl get Logical_Switch_Port sw0-port1 mirror_rules | cut -b 3], [0], [dnl + +]) + +dnl Check if the mirror deleted properly +AT_CHECK([ovn-nbctl mirror-list], [0], [dnl +mirror1: + Type : gre + Sink : 10.10.10.1 + Filter : from-lport + Index/Key: 0 + Sources : None attached +mirror2: + Type : erspan + Sink : 10.10.10.2 + Filter : both + Index/Key: 1 + Sources : None attached +]) + +dnl Delete another mirror +AT_CHECK([ovn-nbctl mirror-del mirror2]) + +dnl Update the Sink address +AT_CHECK([ovn-nbctl set mirror . sink=192.168.1.13]) + +AT_CHECK([ovn-nbctl mirror-list], [0], [dnl +mirror1: + Type : gre + Sink : 192.168.1.13 + Filter : from-lport + Index/Key: 0 + Sources : None attached +]) + +dnl Delete all mirrors +AT_CHECK([ovn-nbctl mirror-del]) +AT_CHECK([ovn-nbctl mirror-list], [0], [dnl +])]) + +dnl --------------------------------------------------------------------- + OVN_NBCTL_TEST([ovn_nbctl_nats], [NATs], [ AT_CHECK([ovn-nbctl lr-add lr0]) AT_CHECK([ovn-nbctl lr-nat-add lr0 snatt 30.0.0.2 192.168.1.2], [1], [], diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at index d5136ac6d..78011cce8 100644 --- a/tests/ovn-northd.at +++ b/tests/ovn-northd.at @@ -2291,6 +2291,108 @@ check_meter_by_name NOT meter_me__${acl1} meter_me__${acl2} AT_CLEANUP ]) +OVN_FOR_EACH_NORTHD([ +AT_SETUP([Check NB-SB mirrors sync]) +AT_KEYWORDS([mirrors]) +ovn_start + +check ovn-nbctl --wait=sb mirror-add mirror1 erspan 0 both 10.10.10.2 + +AT_CHECK([ovn-sbctl get Mirror . Sink], [0], [dnl +"10.10.10.2" +]) + +AT_CHECK([ovn-sbctl get Mirror . type], [0], [dnl +erspan +]) + +AT_CHECK([ovn-sbctl get Mirror . index], [0], [dnl +0 +]) + +AT_CHECK([ovn-sbctl get Mirror . filter], [0], [dnl +both +]) + +check ovn-nbctl --wait=sb \ + -- set mirror . sink=192.168.1.13 + +AT_CHECK([ovn-sbctl get Mirror . Sink], [0], [dnl +"192.168.1.13" +]) + +AT_CHECK([ovn-sbctl get Mirror . type], [0], [dnl +erspan +]) + +AT_CHECK([ovn-sbctl get Mirror . index], [0], [dnl +0 +]) + +AT_CHECK([ovn-sbctl get Mirror . filter], [0], [dnl +both +]) + +check ovn-nbctl --wait=sb \ + -- set mirror . type=gre + +AT_CHECK([ovn-sbctl get Mirror . type], [0], [dnl +gre +]) + +AT_CHECK([ovn-sbctl get Mirror . Sink], [0], [dnl +"192.168.1.13" +]) + +AT_CHECK([ovn-sbctl get Mirror . index], [0], [dnl +0 +]) + +AT_CHECK([ovn-sbctl get Mirror . filter], [0], [dnl +both +]) + +check ovn-nbctl --wait=sb \ + -- set mirror . index=12 + +AT_CHECK([ovn-sbctl get Mirror . index], [0], [dnl +12 +]) + +AT_CHECK([ovn-sbctl get Mirror . type], [0], [dnl +gre +]) + +AT_CHECK([ovn-sbctl get Mirror . Sink], [0], [dnl +"192.168.1.13" +]) + +AT_CHECK([ovn-sbctl get Mirror . filter], [0], [dnl +both +]) + +check ovn-nbctl --wait=sb \ + -- set mirror . filter=to-lport + +AT_CHECK([ovn-sbctl get Mirror . filter], [0], [dnl +to-lport +]) + +AT_CHECK([ovn-sbctl get Mirror . index], [0], [dnl +12 +]) + +AT_CHECK([ovn-sbctl get Mirror . type], [0], [dnl +gre +]) + +AT_CHECK([ovn-sbctl get Mirror . Sink], [0], [dnl +"192.168.1.13" +]) + +AT_CLEANUP +]) + OVN_FOR_EACH_NORTHD([ AT_SETUP([ACL skip hints for stateless config]) AT_KEYWORDS([acl]) diff --git a/tests/ovn.at b/tests/ovn.at index 4f4783461..4bc566e58 100644 --- a/tests/ovn.at +++ b/tests/ovn.at @@ -16096,6 +16096,221 @@ OVN_CLEANUP([hv1], [hv2]) AT_CLEANUP ]) +OVN_FOR_EACH_NORTHD([ +AT_SETUP([Mirror]) +AT_KEYWORDS([Mirror]) +ovn_start + +# Logical network: +# One LR - R1 has switch ls1 (191.168.1.0/24) connected to it, +# and has switch ls2 (172.16.1.0/24) connected to it. + +ovn-nbctl lr-add R1 + +ovn-nbctl ls-add ls1 +ovn-nbctl ls-add ls2 + +# Connect ls1 to R1 +ovn-nbctl lrp-add R1 ls1 00:00:00:01:02:f1 192.168.1.1/24 +ovn-nbctl lsp-add ls1 rp-ls1 -- set Logical_Switch_Port rp-ls1 \ + type=router options:router-port=ls1 addresses=\"00:00:00:01:02:f1\" + +# Connect ls2 to R1 +ovn-nbctl lrp-add R1 ls2 00:00:00:01:02:f2 172.16.1.1/24 +ovn-nbctl lsp-add ls2 rp-ls2 -- set Logical_Switch_Port rp-ls2 \ + type=router options:router-port=ls2 addresses=\"00:00:00:01:02:f2\" + +# Create logical port ls1-lp1 in ls1 +ovn-nbctl lsp-add ls1 ls1-lp1 \ +-- lsp-set-addresses ls1-lp1 "00:00:00:01:02:03 192.168.1.2" + +# Create logical port ls2-lp1 in ls2 +ovn-nbctl lsp-add ls2 ls2-lp1 \ +-- lsp-set-addresses ls2-lp1 "00:00:00:01:02:04 172.16.1.2" + +ovn-nbctl lsp-add ls1 ln-public +ovn-nbctl lsp-set-type ln-public localnet +ovn-nbctl lsp-set-addresses ln-public unknown +ovn-nbctl lsp-set-options ln-public network_name=public + +# Create one hypervisor and create OVS ports corresponding to logical ports. +net_add n1 + +sim_add hv1 +as hv1 +ovs-vsctl add-br br-phys -- set bridge br-phys other-config:hwaddr=\"00:00:00:01:02:00\" +ovn_attach n1 br-phys 192.168.1.11 + +ovs-vsctl -- add-port br-int vif1 -- \ + set interface vif1 external-ids:iface-id=ls1-lp1 \ + options:tx_pcap=hv1/vif1-tx.pcap \ + options:rxq_pcap=hv1/vif1-rx.pcap \ + ofport-request=1 + +ovs-vsctl -- add-port br-int vif2 -- \ + set interface vif2 external-ids:iface-id=ls2-lp1 \ + options:tx_pcap=hv1/vif2-tx.pcap \ + options:rxq_pcap=hv1/vif2-rx.pcap \ + ofport-request=1 + +ovs-vsctl set open . external-ids:ovn-bridge-mappings=public:br-phys + +# Allow some time for ovn-northd and ovn-controller to catch up. +wait_for_ports_up +check ovn-nbctl --wait=hv sync +ovn-nbctl dump-flows > sbflows +AT_CAPTURE_FILE([sbflows]) + +for i in 1 2; do + : > vif$i.expected +done + +net_add n2 + +sim_add hv2 +as hv2 +ovs-vsctl add-br br-phys -- set bridge br-phys other-config:hwaddr=\"00:00:00:02:02:00\" +ovn_attach n2 br-phys 192.168.1.12 + +OVN_POPULATE_ARP + +as hv1 + +# test_ipv4_icmp_request INPORT ETH_SRC ETH_DST IPV4_SRC IPV4_DST IP_CHKSUM ICMP_CHKSUM [EXP_IP_CHKSUM EXP_ICMP_CHKSUM] ENCAP_TYPE FILTER +# +# Causes a packet to be received on INPORT. The packet is an ICMPv4 +# request with ETH_SRC, ETH_DST, IPV4_SRC, IPV4_DST, IP_CHSUM and +# ICMP_CHKSUM as specified. If EXP_IP_CHKSUM and EXP_ICMP_CHKSUM are +# provided, then it should be the ip and icmp checksums of the packet +# responded; otherwise, no reply is expected. +# In the absence of an ip checksum calculation helpers, this relies +# on the caller to provide the checksums for the ip and icmp headers. +# XXX This should be more systematic. +# +# INPORT is an lport number, e.g. 11 for vif11. +# ETH_SRC and ETH_DST are each 12 hex digits. +# IPV4_SRC and IPV4_DST are each 8 hex digits. +# IP_CHSUM and ICMP_CHKSUM are each 4 hex digits. +# EXP_IP_CHSUM and EXP_ICMP_CHKSUM are each 4 hex digits. +# ENCAP_TYPE - gre or erspan +# FILTER - Mirror Filter - to-lport / from-lport +test_ipv4_icmp_request() { + local inport=$1 eth_src=$2 eth_dst=$3 ipv4_src=$4 ipv4_dst=$5 ip_chksum=$6 icmp_chksum=$7 + local exp_ip_chksum=$8 exp_icmp_chksum=$9 mirror_encap_type=${10} mirror_filter=${11} + shift; shift; shift; shift; shift; shift; shift + shift; shift; shift; shift; + + # Use ttl to exercise section 4.2.2.9 of RFC1812 + local ip_ttl=02 + local icmp_id=5fbf + local icmp_seq=0001 + local icmp_data=$(seq 1 56 | xargs printf "%02x") + local icmp_type_code_request=0800 + local icmp_payload=${icmp_type_code_request}${icmp_chksum}${icmp_id}${icmp_seq}${icmp_data} + local packet=${eth_dst}${eth_src}08004500005400004000${ip_ttl}01${ip_chksum}${ipv4_src}${ipv4_dst}${icmp_payload} + + as hv1 ovs-appctl netdev-dummy/receive vif$inport $packet + + # Expect to receive the reply, if any. In same port where packet was sent. + # Note: src and dst fields are expected to be reversed. + local icmp_type_code_response=0000 + local reply_icmp_ttl=fe + local reply_icmp_payload=${icmp_type_code_response}${exp_icmp_chksum}${icmp_id}${icmp_seq}${icmp_data} + local reply=${eth_src}${eth_dst}08004500005400004000${reply_icmp_ttl}01${exp_ip_chksum}${ipv4_dst}${ipv4_src}${reply_icmp_payload} + echo $reply >> vif$inport.expected + local remote_mac=000000020200 + local local_mac=000000010200 + if test ${mirror_encap_type} = "gre" ; then + local ipv4_gre=4500007e00004000402fb6e9c0a8010bc0a8010c2000655800000000 + if test ${mirror_filter} = "to-lport" ; then + local mirror=${remote_mac}${local_mac}0800${ipv4_gre}${reply} + elif test ${mirror_filter} = "from-lport" ; then + local mirror=${remote_mac}${local_mac}0800${ipv4_gre}${packet} + fi + elif test ${mirror_encap_type} = "erspan" ; then + local ipv4_erspan=4500008600004000402fb6e1c0a8010bc0a8010c + local erspan_seq0=100088be000000001000000000000000 + local erspan_seq1=100088be000000011000000000000000 + if test ${mirror_filter} = "to-lport" ; then + local mirror=${remote_mac}${local_mac}0800${ipv4_erspan}${erspan_seq0}${reply} + elif test ${mirror_filter} = "from-lport" ; then + local mirror=${remote_mac}${local_mac}0800${ipv4_erspan}${erspan_seq1}${packet} + fi + fi + echo $mirror >> br-phys_n1.expected + +} + +# Set IPs +rtr_l2_ip=$(ip_to_hex 172 16 1 1) +l1_ip=$(ip_to_hex 192 168 1 2) + +check ovn-nbctl mirror-add mirror0 gre 0 to-lport 192.168.1.12 +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0 + +# Send ping packet and check for mirrored packet of the reply +test_ipv4_icmp_request 1 000000010203 0000000102f1 $l1_ip $rtr_l2_ip 0000 8510 03ff 8d10 "gre" "to-lport" + +OVN_CHECK_PACKETS_CONTAIN([hv1/br-phys_n1-tx.pcap], [br-phys_n1.expected]) +OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [vif1.expected]) + +as hv1 reset_pcap_file vif1 hv1/vif1 +as hv1 reset_pcap_file br-phys_n1 hv1/br-phys_n1 +rm -f br-phys_n1.expected +rm -f vif1.expected + +check ovn-nbctl set mirror . type=erspan + +# Send ping packet and check for mirrored packet of the reply +test_ipv4_icmp_request 1 000000010203 0000000102f1 $l1_ip $rtr_l2_ip 0000 8510 03ff 8d10 "erspan" "to-lport" + +OVN_CHECK_PACKETS_CONTAIN([hv1/br-phys_n1-tx.pcap], [br-phys_n1.expected]) +OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [vif1.expected]) + +as hv1 reset_pcap_file vif1 hv1/vif1 +as hv1 reset_pcap_file br-phys_n1 hv1/br-phys_n1 +rm -f br-phys_n1.expected +rm -f vif1.expected + +check ovn-nbctl set mirror . filter=from-lport + +# Send ping packet and check for mirrored packet of the request +test_ipv4_icmp_request 1 000000010203 0000000102f1 $l1_ip $rtr_l2_ip 0000 8510 03ff 8d10 "erspan" "from-lport" + +OVN_CHECK_PACKETS_CONTAIN([hv1/br-phys_n1-tx.pcap], [br-phys_n1.expected]) +OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [vif1.expected]) + +as hv1 reset_pcap_file vif1 hv1/vif1 +as hv1 reset_pcap_file br-phys_n1 hv1/br-phys_n1 +rm -f br-phys_n1.expected +rm -f vif1.expected + +check ovn-nbctl set mirror . type=gre + +# Send ping packet and check for mirrored packet of the request +test_ipv4_icmp_request 1 000000010203 0000000102f1 $l1_ip $rtr_l2_ip 0000 8510 03ff 8d10 "gre" "from-lport" + +OVN_CHECK_PACKETS_CONTAIN([hv1/br-phys_n1-tx.pcap], [br-phys_n1.expected]) +OVN_CHECK_PACKETS([hv1/vif1-tx.pcap], [vif1.expected]) + +echo "---------OVN NB Mirror-----" +ovn-nbctl mirror-list + +echo "---------OVS Mirror----" +ovs-vsctl list Mirror + +echo "-----------------------" +echo "Delete vif1 so that OVN releases port binding" +ovs-vsctl del-port br-int vif1 + +echo "Verifying Mirror deletion in OVS" + +AT_CHECK([as hv1 ovs-vsctl list Mirror], [0], [dnl +]) + +OVN_CLEANUP([hv1], [hv2]) +AT_CLEANUP +]) OVN_FOR_EACH_NORTHD([ AT_SETUP([Port Groups]) diff --git a/utilities/ovn-nbctl.c b/utilities/ovn-nbctl.c index 3bbdbd998..f924e71dc 100644 --- a/utilities/ovn-nbctl.c +++ b/utilities/ovn-nbctl.c @@ -271,6 +271,19 @@ 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 the tunnel INDEX value\n\ + (indicates key if GRE\n\ + erpsan_idx if ERSPAN)\n\ + specify FILTER for mirroring selection\n\ + 'to-lport' / 'from-lport' / 'both'\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 +324,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 MIRROR\n\ + lsp-detach-mirror PORT MIRROR detach source PORT from MIRROR\n\ \n\ Forwarding group commands:\n\ [--liveness]\n\ @@ -1685,6 +1700,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 +7378,211 @@ 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 first 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_sink_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_sink_ip = normalize_ipv4_addr_str(sink_ip); + if (!new_sink_ip) { + new_sink_ip = normalize_ipv6_addr_str(sink_ip); + } + + if (new_sink_ip) { + free(new_sink_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) { + struct svec srcs; + const char *src; + size_t j; + svec_init(&srcs); + for (j = 0; j < mirror->n_src; j++) { + svec_add(&srcs, mirror->src[j]->name); + } + svec_sort(&srcs); + SVEC_FOR_EACH (j, src, &srcs) { + ds_put_format(&ctx->output, " %s", src); + } + svec_destroy(&srcs); + } 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 +7676,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 +7739,10 @@ static const struct ctl_command_syntax nbctl_commands[] = { nbctl_lsp_get_dhcpv6_options, NULL, "", RO }, { "lsp-get-ls", 1, 1, "PORT", nbctl_pre_lsp_get_ls, nbctl_lsp_get_ls, NULL, "", RO }, + { "lsp-attach-mirror", 2, 2, "PORT MIRROR", nbctl_pre_lsp_mirror, + nbctl_lsp_attach_mirror, NULL, "", RW }, + { "lsp-detach-mirror", 2, 2, "PORT MIRROR", nbctl_pre_lsp_mirror, + nbctl_lsp_detach_mirror, NULL, "", RW }, /* forwarding group commands. */ { "fwd-group-add", 4, INT_MAX, "SWITCH GROUP VIP VMAC PORT...", diff --git a/utilities/ovn-sbctl.c b/utilities/ovn-sbctl.c index e35556d34..1aa6cc26f 100644 --- a/utilities/ovn-sbctl.c +++ b/utilities/ovn-sbctl.c @@ -307,6 +307,7 @@ pre_get_info(struct ctl_context *ctx) ovsdb_idl_add_column(ctx->idl, &sbrec_port_binding_col_chassis); ovsdb_idl_add_column(ctx->idl, &sbrec_port_binding_col_datapath); ovsdb_idl_add_column(ctx->idl, &sbrec_port_binding_col_up); + ovsdb_idl_add_column(ctx->idl, &sbrec_port_binding_col_mirror_rules); ovsdb_idl_add_column(ctx->idl, &sbrec_logical_flow_col_logical_datapath); ovsdb_idl_add_column(ctx->idl, &sbrec_logical_flow_col_logical_dp_group); @@ -1434,6 +1435,9 @@ static const struct ctl_table_class tables[SBREC_N_TABLES] = { [SBREC_TABLE_HA_CHASSIS].row_ids[0] = {&sbrec_ha_chassis_col_chassis, NULL, NULL}, + [SBREC_TABLE_MIRROR].row_ids[0] + = {&sbrec_mirror_col_name, NULL, NULL}, + [SBREC_TABLE_METER].row_ids[0] = {&sbrec_meter_col_name, NULL, NULL}, -- 2.27.0 _______________________________________________ dev mailing list d...@openvswitch.org https://mail.openvswitch.org/mailman/listinfo/ovs-dev