@@ -1,7 +1,7 @@
{
"name": "OVN_Northbound",
- "version": "6.2.0",
- "cksum": "2862883635 31533",
+ "version": "6.3.0",
+ "cksum": "1451927243 32797",
"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,24 @@
"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"},
@@ -1503,6 +1503,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.
@@ -2424,6 +2428,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
@@ -435,6 +435,84 @@ 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 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], [],
@@ -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]\n\
+ remove 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\
@@ -1678,6 +1690,129 @@ 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)
{
@@ -7014,6 +7149,255 @@ 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);
+}
+
+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", 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,
@@ -7107,6 +7491,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 },
@@ -7161,6 +7554,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...",
Changes done in ovn-nbctl to support creation of mirror and attach a source port for the same. Example commands: ovn-nbctl mirror-add mirror1 gre 0 to-lport sw0-port2 ovn-nbctl lsp-attach-mirror sw0-port1 mirror1 Changes are needed in ovn-sbctl and ovn-controller as well to make it functional end to end. That needs to be done in future commits as incremental changes. Co-authored-by: Veda Barrenkala <vedabarrenkala@gmail.com> Signed-off-by: Abhiram R N <abhiramrn@gmail.com> --- ovn-nb.ovsschema | 27 ++- ovn-nb.xml | 53 ++++++ tests/ovn-nbctl.at | 78 +++++++++ utilities/ovn-nbctl.c | 397 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 553 insertions(+), 2 deletions(-)