@@ -27,6 +27,7 @@ OVN v22.09.0 - 16 Sep 2022
any of LR's LRP IP, there is no need to create SNAT entry. Now such
traffic destined to LRP IP is not dropped.
- Bump python version required for building OVN to 3.6.
+ - Added Support for Remote Port Mirroring.
OVN v22.06.0 - 03 Jun 2022
--------------------------
@@ -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
new file mode 100644
@@ -0,0 +1,402 @@
+/* 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 "include/openvswitch/shash.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);
+
+struct ovn_mirror {
+ char *name;
+ const struct sbrec_mirror *sb_mirror;
+ const struct ovsrec_mirror *ovs_mirror;
+ struct ovs_list mirror_src_lports;
+ struct ovs_list mirror_dst_lports;
+};
+
+struct mirror_lport {
+ struct ovs_list list_node;
+
+ struct local_binding *lbinding;
+};
+
+static struct ovn_mirror *ovn_mirror_create(char *mirror_name);
+static void ovn_mirror_add(struct shash *ovn_mirrors,
+ struct ovn_mirror *);
+static struct ovn_mirror *ovn_mirror_find(struct shash *ovn_mirrors,
+ const char *mirror_name);
+static void ovn_mirror_delete(struct ovn_mirror *);
+static void ovn_mirror_add_lport(struct ovn_mirror *, struct local_binding *);
+static void sync_ovn_mirror(struct ovn_mirror *, struct ovsdb_idl_txn *,
+ const struct ovsrec_bridge *);
+
+static void create_ovs_mirror(struct ovn_mirror *, struct ovsdb_idl_txn *,
+ const struct ovsrec_bridge *);
+static void sync_ovs_mirror_ports(struct ovn_mirror *,
+ const struct ovsrec_bridge *);
+static void delete_ovs_mirror(struct ovn_mirror *,
+ const struct ovsrec_bridge *);
+static bool should_delete_ovs_mirror(struct ovn_mirror *);
+
+static const struct ovsrec_port *get_iface_port(
+ const struct ovsrec_interface *, const struct ovsrec_bridge *);
+
+
+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);
+ ovsdb_idl_add_column(ovs_idl, &ovsrec_mirror_col_external_ids);
+}
+
+void
+mirror_init(void)
+{
+}
+
+void
+mirror_destroy(void)
+{
+}
+
+void
+mirror_run(struct ovsdb_idl_txn *ovs_idl_txn,
+ const struct ovsrec_mirror_table *ovs_mirror_table,
+ const struct sbrec_mirror_table *sb_mirror_table,
+ const struct ovsrec_bridge *br_int,
+ struct shash *local_bindings)
+{
+ if (!ovs_idl_txn) {
+ return;
+ }
+
+ struct shash ovn_mirrors = SHASH_INITIALIZER(&ovn_mirrors);
+
+ /* Iterate through sb mirrors and build the 'ovn_mirrors'. */
+ const struct sbrec_mirror *sb_mirror;
+ SBREC_MIRROR_TABLE_FOR_EACH (sb_mirror, sb_mirror_table) {
+ struct ovn_mirror *m = ovn_mirror_create(sb_mirror->name);
+ m->sb_mirror = sb_mirror;
+ ovn_mirror_add(&ovn_mirrors, m);
+ }
+
+ /* Iterate through ovs mirrors and add to the 'ovn_mirrors'. */
+ const struct ovsrec_mirror *ovs_mirror;
+ OVSREC_MIRROR_TABLE_FOR_EACH (ovs_mirror, ovs_mirror_table) {
+ bool ovn_owned_mirror = smap_get_bool(&ovs_mirror->external_ids,
+ "ovn-owned", false);
+ if (!ovn_owned_mirror) {
+ continue;
+ }
+
+ struct ovn_mirror *m = ovn_mirror_find(&ovn_mirrors, ovs_mirror->name);
+ if (!m) {
+ m = ovn_mirror_create(ovs_mirror->name);
+ }
+ m->ovs_mirror = ovs_mirror;
+ }
+
+ if (shash_is_empty(&ovn_mirrors)) {
+ shash_destroy(&ovn_mirrors);
+ return;
+ }
+
+ /* Iterate through the local bindings and if the local binding's 'pb' has
+ * mirrors associated, add it to the ovn_mirror. */
+ struct shash_node *node;
+ SHASH_FOR_EACH (node, local_bindings) {
+ struct local_binding *lbinding = node->data;
+ const struct sbrec_port_binding *pb =
+ local_binding_get_primary_pb(local_bindings, lbinding->name);
+ if (!pb || !pb->n_mirror_rules) {
+ continue;
+ }
+
+ for (size_t i = 0; i < pb->n_mirror_rules; i++) {
+ struct ovn_mirror *m = ovn_mirror_find(&ovn_mirrors,
+ pb->mirror_rules[i]->name);
+ ovs_assert(m);
+ ovn_mirror_add_lport(m, lbinding);
+ }
+ }
+
+ /* Iterate through the built 'ovn_mirrors' and
+ * sync with the local ovsdb i.e.
+ * create/update or delete the ovsrec mirror(s). */
+ SHASH_FOR_EACH (node, &ovn_mirrors) {
+ struct ovn_mirror *m = node->data;
+ sync_ovn_mirror(m, ovs_idl_txn, br_int);
+ }
+
+ SHASH_FOR_EACH_SAFE (node, &ovn_mirrors) {
+ ovn_mirror_delete(node->data);
+ shash_delete(&ovn_mirrors, node);
+ }
+
+ shash_destroy(&ovn_mirrors);
+
+}
+
+/* Static functions. */
+static struct ovn_mirror *
+ovn_mirror_create(char *mirror_name)
+{
+ struct ovn_mirror *m = xzalloc(sizeof *m);
+ m->name = xstrdup(mirror_name);
+ ovs_list_init(&m->mirror_src_lports);
+ ovs_list_init(&m->mirror_dst_lports);
+ return m;
+}
+
+static void
+ovn_mirror_add(struct shash *ovn_mirrors, struct ovn_mirror *m)
+{
+ shash_add(ovn_mirrors, m->sb_mirror->name, m);
+}
+
+static struct ovn_mirror *
+ovn_mirror_find(struct shash *ovn_mirrors, const char *mirror_name)
+{
+ return shash_find_data(ovn_mirrors, mirror_name);
+}
+
+static void
+ovn_mirror_delete(struct ovn_mirror *m)
+{
+ free(m->name);
+ struct mirror_lport *m_lport;
+ LIST_FOR_EACH_POP (m_lport, list_node, &m->mirror_src_lports) {
+ free(m_lport);
+ }
+
+ LIST_FOR_EACH_POP (m_lport, list_node, &m->mirror_dst_lports) {
+ free(m_lport);
+ }
+
+ free(m);
+}
+
+static void
+ovn_mirror_add_lport(struct ovn_mirror *m, struct local_binding *lbinding)
+{
+ struct mirror_lport *m_lport = xzalloc(sizeof *m_lport);
+ m_lport->lbinding = lbinding;
+ if (!strcmp(m->sb_mirror->filter, "from-lport")) {
+ ovs_list_push_back(&m->mirror_src_lports, &m_lport->list_node);
+ } else if (!strcmp(m->sb_mirror->filter, "to-lport")) {
+ ovs_list_push_back(&m->mirror_dst_lports, &m_lport->list_node);
+ } else {
+ ovs_list_push_back(&m->mirror_src_lports, &m_lport->list_node);
+ m_lport = xzalloc(sizeof *m_lport);
+ m_lport->lbinding = lbinding;
+ ovs_list_push_back(&m->mirror_dst_lports, &m_lport->list_node);
+ }
+}
+
+static void
+create_and_set_options(struct ovsrec_interface *iface,
+ const struct sbrec_mirror *sb_mirror)
+{
+ struct smap options = SMAP_INITIALIZER(&options);
+ char *key;
+
+ key = xasprintf("%ld", (long int) sb_mirror->index);
+ smap_add(&options, "remote_ip", sb_mirror->sink);
+ smap_add(&options, "key", key);
+ if (!strcmp(sb_mirror->type, "erspan")) {
+ /* Set the ERSPAN index */
+ smap_add(&options, "erspan_idx", key);
+ smap_add(&options, "erspan_ver", "1");
+ }
+ ovsrec_interface_set_options(iface, &options);
+
+ free(key);
+ smap_destroy(&options);
+}
+
+static void
+check_and_update_interface_table(const struct sbrec_mirror *sb_mirror,
+ const struct ovsrec_mirror *ovs_mirror)
+{
+ char *type;
+ struct ovsrec_interface *iface =
+ ovs_mirror->output_port->interfaces[0];
+ struct smap *opts = &iface->options;
+ const char *erspan_ver = smap_get(opts, "erspan_ver");
+ if (erspan_ver) {
+ type = "erspan";
+ } else {
+ type = "gre";
+ }
+ if (strcmp(type, sb_mirror->type)) {
+ ovsrec_interface_set_type(iface, sb_mirror->type);
+ }
+ create_and_set_options(iface, sb_mirror);
+}
+
+static void
+sync_ovn_mirror(struct ovn_mirror *m, struct ovsdb_idl_txn *ovs_idl_txn,
+ const struct ovsrec_bridge *br_int)
+{
+ if (should_delete_ovs_mirror(m)) {
+ /* Delete the ovsrec mirror. */
+ delete_ovs_mirror(m, br_int);
+ return;
+ }
+
+ if (ovs_list_is_empty(&m->mirror_src_lports) &&
+ ovs_list_is_empty(&m->mirror_dst_lports)) {
+ /* Nothing to do. */
+ return;
+ }
+
+ if (m->sb_mirror && !m->ovs_mirror) {
+ create_ovs_mirror(m, ovs_idl_txn, br_int);
+ } else {
+ check_and_update_interface_table(m->sb_mirror, m->ovs_mirror);
+ }
+
+ sync_ovs_mirror_ports(m, br_int);
+}
+
+static bool
+should_delete_ovs_mirror(struct ovn_mirror *m)
+{
+ if (!m->ovs_mirror) {
+ return false;
+ }
+
+ if (m->ovs_mirror && !m->sb_mirror) {
+ return true;
+ }
+
+ return (ovs_list_is_empty(&m->mirror_src_lports) &&
+ ovs_list_is_empty(&m->mirror_dst_lports));
+}
+
+static const struct ovsrec_port *
+get_iface_port(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
+create_ovs_mirror(struct ovn_mirror *m, struct ovsdb_idl_txn *ovs_idl_txn,
+ const struct ovsrec_bridge *br_int)
+{
+ struct ovsrec_interface *iface = ovsrec_interface_insert(ovs_idl_txn);
+ char *port_name = xasprintf("ovn-%s", m->name);
+
+ ovsrec_interface_set_name(iface, port_name);
+ ovsrec_interface_set_type(iface, m->sb_mirror->type);
+ create_and_set_options(iface, m->sb_mirror);
+
+ struct ovsrec_port *port = ovsrec_port_insert(ovs_idl_txn);
+ ovsrec_port_set_name(port, port_name);
+ ovsrec_port_set_interfaces(port, &iface, 1);
+ ovsrec_bridge_update_ports_addvalue(br_int, port);
+
+ free(port_name);
+
+ m->ovs_mirror = ovsrec_mirror_insert(ovs_idl_txn);
+ ovsrec_mirror_set_name(m->ovs_mirror, m->name);
+ ovsrec_mirror_set_output_port(m->ovs_mirror, port);
+
+ const struct smap external_ids =
+ SMAP_CONST1(&external_ids, "ovn-owned", "true");
+ ovsrec_mirror_set_external_ids(m->ovs_mirror, &external_ids);
+
+ ovsrec_bridge_update_mirrors_addvalue(br_int, m->ovs_mirror);
+}
+
+static void
+sync_ovs_mirror_ports(struct ovn_mirror *m, const struct ovsrec_bridge *br_int)
+{
+ struct mirror_lport *m_lport;
+
+ if (ovs_list_is_empty(&m->mirror_src_lports)) {
+ ovsrec_mirror_set_select_src_port(m->ovs_mirror, NULL, 0);
+ } else {
+ size_t n_lports = ovs_list_size(&m->mirror_src_lports);
+ struct ovsrec_port **ovs_ports = xmalloc(sizeof *ovs_ports * n_lports);
+
+ size_t i = 0;
+ LIST_FOR_EACH (m_lport, list_node, &m->mirror_src_lports) {
+ const struct ovsrec_port *p =
+ get_iface_port(m_lport->lbinding->iface, br_int);
+ ovs_assert(p);
+ ovs_ports[i++] = (struct ovsrec_port *) p;
+ }
+
+ ovsrec_mirror_set_select_src_port(m->ovs_mirror, ovs_ports, n_lports);
+ free(ovs_ports);
+ }
+
+ if (ovs_list_is_empty(&m->mirror_dst_lports)) {
+ ovsrec_mirror_set_select_dst_port(m->ovs_mirror, NULL, 0);
+ } else {
+ size_t n_lports = ovs_list_size(&m->mirror_dst_lports);
+ struct ovsrec_port **ovs_ports = xmalloc(sizeof *ovs_ports * n_lports);
+
+ size_t i = 0;
+ LIST_FOR_EACH (m_lport, list_node, &m->mirror_dst_lports) {
+ const struct ovsrec_port *p =
+ get_iface_port(m_lport->lbinding->iface, br_int);
+ ovs_assert(p);
+ ovs_ports[i++] = (struct ovsrec_port *) p;
+ }
+
+ ovsrec_mirror_set_select_dst_port(m->ovs_mirror, ovs_ports, n_lports);
+ free(ovs_ports);
+ }
+}
+
+static void
+delete_ovs_mirror(struct ovn_mirror *m, const struct ovsrec_bridge *br_int)
+{
+ ovsrec_bridge_update_ports_delvalue(br_int, m->ovs_mirror->output_port);
+ ovsrec_bridge_update_mirrors_delvalue(br_int, m->ovs_mirror);
+ ovsrec_port_delete(m->ovs_mirror->output_port);
+ ovsrec_mirror_delete(m->ovs_mirror);
+}
new file mode 100644
@@ -0,0 +1,33 @@
+/* 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_mirror_table;
+struct sbrec_mirror_table;
+struct ovsrec_bridge;
+struct shash;
+
+void mirror_register_ovs_idl(struct ovsdb_idl *);
+void mirror_init(void);
+void mirror_destroy(void);
+void mirror_run(struct ovsdb_idl_txn *ovs_idl_txn,
+ const struct ovsrec_mirror_table *,
+ const struct sbrec_mirror_table *,
+ const struct ovsrec_bridge *,
+ struct shash *local_bindings);
+#endif
@@ -78,6 +78,7 @@
#include "lib/inc-proc-eng.h"
#include "lib/ovn-l7.h"
#include "hmapx.h"
+#include "mirror.h"
VLOG_DEFINE_THIS_MODULE(main);
@@ -994,6 +995,7 @@ ctrl_register_ovs_idl(struct ovsdb_idl *ovs_idl)
ovsdb_idl_track_add_column(ovs_idl, &ovsrec_port_col_name);
ovsdb_idl_track_add_column(ovs_idl, &ovsrec_port_col_interfaces);
ovsdb_idl_track_add_column(ovs_idl, &ovsrec_port_col_external_ids);
+ mirror_register_ovs_idl(ovs_idl);
}
#define SB_NODES \
@@ -3619,6 +3621,7 @@ main(int argc, char *argv[])
patch_init();
pinctrl_init();
lflow_init();
+ mirror_init();
vif_plug_provider_initialize();
/* Connect to OVS OVSDB instance. */
@@ -4195,34 +4198,35 @@ main(int argc, char *argv[])
stopwatch_start(CONTROLLER_LOOP_STOPWATCH_NAME,
time_msec());
- if (ovnsb_idl_txn) {
- if (ofctrl_has_backlog()) {
- /* When there are in-flight messages pending to
- * ovs-vswitchd, we should hold on recomputing so
- * that the previous flow installations won't be
- * delayed. However, we still want to try if
- * recompute is not needed and we can quickly
- * incrementally process the new changes, to avoid
- * unnecessarily forced recomputes later on. This
- * is because the OVSDB change tracker cannot
- * preserve tracked changes across iterations. If
- * change tracking is improved, we can simply skip
- * this round of engine_run and continue processing
- * acculated changes incrementally later when
- * ofctrl_has_backlog() returns false. */
- engine_run(false);
- } else {
- engine_run(true);
- }
- } else {
- /* Even if there's no SB DB transaction available,
+
+ bool allow_engine_recompute = true;
+
+ if (!ovnsb_idl_txn || ofctrl_has_backlog()) {
+ /* When there are in-flight messages pending to
+ * ovs-vswitchd, we should hold on recomputing so
+ * that the previous flow installations won't be
+ * delayed. However, we still want to try if
+ * recompute is not needed and we can quickly
+ * incrementally process the new changes, to avoid
+ * unnecessarily forced recomputes later on. This
+ * is because the OVSDB change tracker cannot
+ * preserve tracked changes across iterations. If
+ * change tracking is improved, we can simply skip
+ * this round of engine_run and continue processing
+ * acculated changes incrementally later when
+ * ofctrl_has_backlog() returns false. */
+
+ /* Even if there's no SB/OVS DB transaction available,
* try to run the engine so that we can handle any
* incremental changes that don't require a recompute.
* If a recompute is required, the engine will abort,
* triggerring a full run in the next iteration.
*/
- engine_run(false);
+ allow_engine_recompute = false;
}
+
+ engine_run(allow_engine_recompute);
+
stopwatch_stop(CONTROLLER_LOOP_STOPWATCH_NAME,
time_msec());
if (engine_has_updated()) {
@@ -4312,6 +4316,11 @@ main(int argc, char *argv[])
&runtime_data->local_active_ports_ras);
stopwatch_stop(PINCTRL_RUN_STOPWATCH_NAME,
time_msec());
+ mirror_run(ovs_idl_txn,
+ ovsrec_mirror_table_get(ovs_idl_loop.idl),
+ sbrec_mirror_table_get(ovnsb_idl_loop.idl),
+ br_int,
+ &runtime_data->lbinding_data.bindings);
/* Updating monitor conditions if runtime data or
* logical datapath goups changed. */
if (engine_node_changed(&en_runtime_data)
@@ -4555,6 +4564,7 @@ loop_done:
pinctrl_destroy();
binding_destroy();
patch_destroy();
+ mirror_destroy();
if_status_mgr_destroy(if_mgr);
shash_destroy(&vif_plug_deleted_iface_ids);
shash_destroy(&vif_plug_changed_iface_ids);
@@ -16237,6 +16237,520 @@ 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 "Verifying Mirror deletion in OVS"
+# Set vif1 iface-id such that OVN releases port binding
+check ovs-vsctl set interface vif1 external_ids:iface-id=foo
+check ovn-nbctl --wait=hv sync
+
+AT_CHECK([as hv1 ovs-vsctl list Mirror], [0], [dnl
+])
+
+# Set vif1 iface-id back to ls1-lp1
+check ovs-vsctl set interface vif1 external_ids:iface-id=ls1-lp1
+check ovn-nbctl --wait=hv sync
+
+OVS_WAIT_UNTIL([test $(as hv1 ovs-vsctl get Mirror mirror0 name) = "mirror0"])
+
+# Delete vif1 so that OVN releases port binding
+check ovs-vsctl del-port br-int vif1
+check ovn-nbctl --wait=hv sync
+
+OVS_WAIT_UNTIL([test 0 = $(as hv1 ovs-vsctl list Mirror | wc -l)])
+
+OVN_CLEANUP([hv1], [hv2])
+AT_CLEANUP
+])
+
+OVN_FOR_EACH_NORTHD([
+AT_SETUP([Mirror test bulk updates])
+AT_KEYWORDS([Mirror test bulk updates])
+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 ls1-lp2 in ls1
+ovn-nbctl lsp-add ls1 ls1-lp2 \
+-- lsp-set-addresses ls1-lp2 "00:00:00:01:03:03 192.168.1.3"
+
+# Create logical port ls2-lp1 in ls2
+ovn-nbctl lsp-add ls2 ls2-lp1 \
+-- lsp-set-addresses ls2-lp1 "00:00:00:01:02:04 172.16.1.2"
+
+# Create logical port ls2-lp2 in ls2
+ovn-nbctl lsp-add ls2 ls2-lp2 \
+-- lsp-set-addresses ls2-lp2 "00:00:00:01:03:04 172.16.1.3"
+
+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 2 hypervisors and create OVS ports corresponding to logical ports for hv1.
+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
+
+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
+
+ovs-vsctl -- add-port br-int vif1 -- \
+ set interface vif1 external-ids:iface-id=ls1-lp1
+
+ovs-vsctl -- add-port br-int vif2 -- \
+ set interface vif2 external-ids:iface-id=ls2-lp1
+
+ovs-vsctl -- add-port br-int vif3 -- \
+ set interface vif3 external-ids:iface-id=ls1-lp2
+
+ovs-vsctl -- add-port br-int vif4 -- \
+ set interface vif4 external-ids:iface-id=ls2-lp2
+
+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])
+
+check ovn-nbctl mirror-add mirror0 gre 0 to-lport 192.168.1.12
+check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
+check ovn-nbctl --wait=hv sync
+origA=$(ovs-vsctl get Mirror mirror0 select_dst_port)
+check ovn-nbctl lsp-attach-mirror ls2-lp1 mirror0
+
+check ovn-nbctl mirror-add mirror1 gre 1 to-lport 192.168.1.12
+check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror1
+check ovn-nbctl --wait=hv sync
+origB=$(ovs-vsctl get Mirror mirror1 select_dst_port)
+check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror1
+
+check ovn-nbctl --wait=hv sync
+
+as hv1
+orig1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
+orig2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
+
+check ovn-nbctl mirror-del
+check ovn-nbctl --wait=hv sync
+
+check ovn-nbctl mirror-add mirror0 gre 0 to-lport 192.168.1.12
+check ovn-nbctl mirror-add mirror1 gre 1 to-lport 192.168.1.12
+
+# Attaches multiple mirrors
+
+check as hv1 ovn-appctl -t ovn-controller debug/pause
+check ovn-nbctl lsp-attach-mirror ls2-lp1 mirror0
+check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
+check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror1
+check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror1
+check as hv1 ovn-appctl -t ovn-controller debug/resume
+
+check ovn-nbctl --wait=hv sync
+
+as hv1
+new1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
+new2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
+
+AT_CHECK([test "$orig1" = "$new1"], [0], [])
+
+AT_CHECK([test "$orig2" = "$new2"], [0], [])
+
+# Equal detaches and attaches
+check as hv1 ovn-appctl -t ovn-controller debug/pause
+check ovn-nbctl lsp-detach-mirror ls1-lp1 mirror0
+check ovn-nbctl lsp-detach-mirror ls2-lp1 mirror0
+check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror0
+check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror0
+
+check ovn-nbctl lsp-detach-mirror ls1-lp2 mirror1
+check ovn-nbctl lsp-detach-mirror ls2-lp2 mirror1
+check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror1
+check ovn-nbctl lsp-attach-mirror ls2-lp1 mirror1
+check as hv1 ovn-appctl -t ovn-controller debug/resume
+
+check ovn-nbctl --wait=hv sync
+
+# Make sure that ovn-controller has not asserted.
+AT_CHECK([kill -0 $(cat hv1/ovn-controller.pid)])
+
+as hv1
+new1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
+new2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
+
+AT_CHECK([test "$orig1" = "$new2"], [0], [])
+
+AT_CHECK([test "$orig2" = "$new1"], [0], [])
+
+# Detaches more than attaches
+check as hv1 ovn-appctl -t ovn-controller debug/pause
+check ovn-nbctl lsp-detach-mirror ls1-lp1 mirror1
+check ovn-nbctl lsp-detach-mirror ls2-lp1 mirror1
+check ovn-nbctl lsp-detach-mirror ls1-lp2 mirror0
+check ovn-nbctl lsp-detach-mirror ls2-lp2 mirror0
+check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
+check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror1
+check as hv1 ovn-appctl -t ovn-controller debug/resume
+
+check ovn-nbctl --wait=hv sync
+# Make sure that ovn-controller has not asserted.
+AT_CHECK([kill -0 $(cat hv1/ovn-controller.pid)])
+
+as hv1
+new1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
+new2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
+
+AT_CHECK([test "$origA" = "$new1"], [0], [])
+
+AT_CHECK([test "$origB" = "$new2"], [0], [])
+
+# Attaches more than detaches
+check as hv1 ovn-appctl -t ovn-controller debug/pause
+check ovn-nbctl lsp-detach-mirror ls1-lp1 mirror0
+check ovn-nbctl lsp-detach-mirror ls1-lp2 mirror1
+check ovn-nbctl lsp-attach-mirror ls1-lp2 mirror0
+check ovn-nbctl lsp-attach-mirror ls2-lp2 mirror0
+check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror1
+check ovn-nbctl lsp-attach-mirror ls2-lp1 mirror1
+check as hv1 ovn-appctl -t ovn-controller debug/resume
+
+check ovn-nbctl --wait=hv sync
+# Make sure that ovn-controller has not asserted.
+AT_CHECK([kill -0 $(cat hv1/ovn-controller.pid)])
+
+as hv1
+new1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
+new2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
+
+AT_CHECK([test "$orig1" = "$new2"], [0], [])
+
+AT_CHECK([test "$orig2" = "$new1"], [0], [])
+
+# Detaches all
+check as hv1 ovn-appctl -t ovn-controller debug/pause
+check ovn-nbctl lsp-detach-mirror ls1-lp2 mirror0
+check ovn-nbctl lsp-detach-mirror ls2-lp2 mirror0
+check ovn-nbctl lsp-detach-mirror ls1-lp1 mirror1
+check ovn-nbctl lsp-detach-mirror ls2-lp1 mirror1
+check as hv1 ovn-appctl -t ovn-controller debug/resume
+check ovn-nbctl --wait=hv sync
+
+OVS_WAIT_UNTIL([test 0 = $(as hv1 ovs-vsctl list Mirror | wc -l)])
+
+check ovn-nbctl mirror-add mirror2 gre 2 to-lport 192.168.1.12
+check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror2
+check ovn-nbctl --wait=hv sync
+
+# Attaches SAME port to multiple mirrors
+# and detaches it from existing mirror.
+# More attach than detach
+
+check as hv1 ovn-appctl -t ovn-controller debug/pause
+check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
+check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror1
+check ovn-nbctl lsp-detach-mirror ls1-lp1 mirror2
+check as hv1 ovn-appctl -t ovn-controller debug/resume
+
+check ovn-nbctl --wait=hv sync
+
+as hv1
+new1=$(ovs-vsctl get Mirror mirror0 select_dst_port)
+new2=$(ovs-vsctl get Mirror mirror1 select_dst_port)
+
+AT_CHECK([test "$origA" = "$new1"], [0], [])
+AT_CHECK([test "$origA" = "$new2"], [0], [])
+OVS_WAIT_UNTIL([test 0 = $(as hv1 ovs-vsctl list Mirror mirror2 | wc -l)])
+
+check ovn-nbctl mirror-add mirror3 gre 3 to-lport 192.168.1.12
+check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror3
+check ovn-nbctl --wait=hv sync
+
+# Detaches SAME port from multiple mirrors
+# and attaches it to existing mirror.
+# More detach than attach
+
+check as hv1 ovn-appctl -t ovn-controller debug/pause
+check ovn-nbctl lsp-detach-mirror ls1-lp1 mirror0
+check ovn-nbctl lsp-detach-mirror ls1-lp1 mirror3
+check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror2
+check as hv1 ovn-appctl -t ovn-controller debug/resume
+
+check ovn-nbctl --wait=hv sync
+
+as hv1
+new1=$(ovs-vsctl get Mirror mirror1 select_dst_port)
+new2=$(ovs-vsctl get Mirror mirror2 select_dst_port)
+
+AT_CHECK([test "$origA" = "$new1"], [0], [])
+AT_CHECK([test "$origA" = "$new2"], [0], [])
+OVS_WAIT_UNTIL([test 0 = $(as hv1 ovs-vsctl list Mirror mirror0 | wc -l)])
+OVS_WAIT_UNTIL([test 0 = $(as hv1 ovs-vsctl list Mirror mirror3 | wc -l)])
+
+check ovn-nbctl mirror-del
+check ovn-nbctl --wait=hv sync
+check ovn-nbctl mirror-add mirror0 erspan 0 to-lport 192.168.1.12
+check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0
+check ovn-nbctl --wait=hv sync
+
+# Make sure different fields of mirror resource set from OVN
+# propagates to OVS correctly
+
+check as hv1 ovn-appctl -t ovn-controller debug/pause
+check ovn-nbctl set mirror . filter=from-lport
+check ovn-nbctl set mirror . type=gre
+check ovn-nbctl set mirror . index=123
+check as hv1 ovn-appctl -t ovn-controller debug/resume
+
+check ovn-nbctl --wait=hv sync
+
+as hv1
+new1=$(ovs-vsctl get Mirror mirror0 select_src_port)
+AT_CHECK([test "$origA" = "$new1"], [0], [])
+type=$(ovs-vsctl get Interface ovn-mirror0 type)
+OVS_WAIT_UNTIL([test "gre" = "$type"])
+index=$(ovs-vsctl get Interface ovn-mirror0 options:key | grep 123 | wc -l)
+OVS_WAIT_UNTIL([test "1" = "$index"])
+
+OVN_CLEANUP([hv1],[hv2])
+AT_CLEANUP
+])
OVN_FOR_EACH_NORTHD([
AT_SETUP([Port Groups])