Hi Ales,
I'm a bit confused about your last paragraph. The code already supports mirroring traffic toan OVN port on a different chassis. Here's how it works: for each logical switch with ports that have lport mirrors, a mirror port is created. This mirror port can have its parent on the current or another chassis. From what I understand, we can only direct traffic in a logical switch to ports that are on that same switch. So, if we create a logical switch port whose parent is on another switch, the port connects to the current logical switch, and its parent connects to another. The same concept applies to mirror ports: the logical datapath of mirror port Binding indicates where the attached ports are located to make output, and the chassis - chassis where the parent port locates, parent port specified as the sink when creating a mirror. If I got your first question right, you can't send traffic to remote ports in the egress stage. Thus, you'll need to resubmit it to the ingress pipeline in egress. I hope I understood your questions correctly. Thank you. On 10 Feb 2025, at 14:48, Ales Musil <[email protected]> wrote: On Fri, Feb 7, 2025 at 10:09 AM Alexandra Rukomoinikova <[email protected]<mailto:[email protected]>> wrote: Previously, the mirroring feature in OVN only supported tunneling traffic to local and remote ports outside the OVN cluster. With this update, it is now possible to mirror traffic passing through a virtual port to a dedicated OVN port. Changes introduced in the Northbound DB schema and XML configuration: 1)new field for mirroring rules has been added to the Mirror table, allowing the definition of traffic reflection rules. 2)new table, Mirror Rule, has been created to filter remote overlay traffic. 3)new mirroring type, lport, has been introduced to encapsulate mirrored traffic to another OVN port. 4)new port type, mirror, has been added to serve as a conduit to the target port. 5)ls_in_mirror and ls_out_mirror stages in ls pipeline. lport mirroring details: When attaching an lport mirror to a logical switch port, a new port binding named mp-datapath-target-port is created. This port acts as a mirror port, with its parent being the target port. VLAN tagging is not required—packets are transmitted without VLAN headers. For lport mirroring, logical flows (flows) are generated to handle traffic processing based on filtering criteria. Packets matching these criteria are duplicated and sent to the target port, while the original traffic continues along its designated path. If an lport mirror is created without specifying any rules, default logical flows are added to mirror all incoming and outgoing traffic to the target port. Signed-off-by: Alexandra Rukomoinikova <[email protected]> Signed-off-by: Vladislav Odintsov <[email protected]> Co-authored-by: Vladislav Odintsov <[email protected]> Tested-by: Ivan Burnin <[email protected]> --- v4: rebased --- Hi Alexandra, Vladislav, I took another look at the patch and I still have concerns/questions about the approach. First thing that I don't understand is the discrepancy between to-lport and from-port when it comes to the redirect into the mirror port. Why doesn't it always redirect to egress? Is there any point for the port to actually send it via the second switch pipeline? If so, why is it only for egress mirrors and not for ingress? Second point is that you want the mirroring to be as close as possible to the actual port is that right? Otherwise I'm not sure if it makes sense that ingress is happening pre-ACL and egress is done post-ACL stage. I'm not sure what is exactly the purpose of lflows to bypass the CT, can you please elaborate a bit on that one? From this my understanding would be that you want to bypass ACL on the second switch (the sink switch), however I'm not sure why you would configure it as a stateful switch when the only purpose seems to be to sink the mirrored traffic to the parent port. Please correct me on this if my understanding is wrong. Currently the container port and thus the mirror port is bound to the same chassis as the parent/sink port. Which makes me wonder if there is currently any benefit of using OVN port vs OvS interface as sink which it kind of boils down to with this restriction. Do you plan to enhance the mirror port so it is able to send the traffic to different chassis? If the only reason is to have some form of filtering that can still be done using pure OvS mirrors, there is support for filters since OvS 3.4.0. We could have a filter in OVN mirror that is parsed and stored as a filter for the corresponding mirror in OvS. Does that make sense for your use case? With that we could probably avoid the use of mirror ports and mirror rules (if the matching would be embedded directly in the mirror). I'm sorry it took so long for this discussion to happen. controller/binding.c | 95 ++++++++---- controller/lflow.h | 12 +- controller/mirror.c | 4 + controller/physical.c | 28 ++-- lib/ovn-util.c | 12 ++ lib/ovn-util.h | 5 +- northd/en-northd.c | 2 + northd/inc-proc-northd.c | 2 + northd/northd.c | 283 +++++++++++++++++++++++++++++++++++- northd/northd.h | 79 +++++----- northd/ovn-northd.8.xml | 125 +++++++++++----- ovn-nb.ovsschema | 25 +++- ovn-nb.xml | 45 +++++- ovn-sb.ovsschema | 8 +- ovn-sb.xml | 6 +- tests/ovn-macros.at<http://ovn-macros.at/> | 10 +- tests/ovn-nbctl.at<http://ovn-nbctl.at/> | 99 ++++++++++++- tests/ovn-northd.at<http://ovn-northd.at/> | 305 +++++++++++++++++++++++++++++++++++++++ tests/ovn.at<http://ovn.at/> | 176 ++++++++++++++++++++++ utilities/ovn-nbctl.c | 214 +++++++++++++++++++++++++-- 20 files changed, 1394 insertions(+), 141 deletions(-) diff --git a/controller/binding.c b/controller/binding.c index 316b2c36b..ca3b41c5b 100644 --- a/controller/binding.c +++ b/controller/binding.c @@ -1525,14 +1525,15 @@ is_binding_lport_this_chassis(struct binding_lport *b_lport, || is_postponed_port(b_lport->pb->logical_port))); } -/* Returns 'true' if the 'lbinding' has binding lports of type LP_CONTAINER, - * 'false' otherwise. */ +/* Returns 'true' if the 'lbinding' has binding lports of type + * LP_CONTAINER/LP_MIRROR, 'false' otherwise. */ static bool is_lbinding_container_parent(struct local_binding *lbinding) { struct binding_lport *b_lport; LIST_FOR_EACH (b_lport, list_node, &lbinding->binding_lports) { - if (b_lport->type == LP_CONTAINER) { + if (b_lport->type == LP_CONTAINER || + b_lport->type == LP_MIRROR) { return true; } } @@ -1692,7 +1693,11 @@ consider_container_lport(const struct sbrec_port_binding *pb, { struct shash *local_bindings = &b_ctx_out->lbinding_data->bindings; struct local_binding *parent_lbinding; - parent_lbinding = local_binding_find(local_bindings, pb->parent_port); + bool is_mirror = !strcmp(pb->type, "mirror"); + const char *binding_port_name = is_mirror ? pb->mirror_port : + pb->parent_port; + + parent_lbinding = local_binding_find(local_bindings, binding_port_name); if (!parent_lbinding) { /* There is no local_binding for parent port. Create it @@ -1707,7 +1712,7 @@ consider_container_lport(const struct sbrec_port_binding *pb, * we want the these container ports also be claimed by the * chassis. * */ - parent_lbinding = local_binding_create(pb->parent_port, NULL); + parent_lbinding = local_binding_create(binding_port_name, NULL); local_binding_add(local_bindings, parent_lbinding); } @@ -1721,17 +1726,26 @@ consider_container_lport(const struct sbrec_port_binding *pb, remove_related_lport(b_lport->pb, b_ctx_out); } - struct binding_lport *container_b_lport = - local_binding_add_lport(binding_lports, parent_lbinding, pb, - LP_CONTAINER); + struct binding_lport *container_b_lport; - struct binding_lport *parent_b_lport = - binding_lport_find(binding_lports, pb->parent_port); + if (is_mirror) { + container_b_lport = local_binding_add_lport(binding_lports, + parent_lbinding, + pb, LP_MIRROR); + } else { + container_b_lport = local_binding_add_lport(binding_lports, + parent_lbinding, + pb, LP_CONTAINER); + } + + struct binding_lport *parent_b_lport = binding_lport_find( + binding_lports, + binding_port_name); bool can_consider_c_lport = true; if (!parent_b_lport || !parent_b_lport->pb) { const struct sbrec_port_binding *parent_pb = lport_lookup_by_name( - b_ctx_in->sbrec_port_binding_by_name, pb->parent_port); + b_ctx_in->sbrec_port_binding_by_name, binding_port_name); if (parent_pb && get_lport_type(parent_pb) == LP_VIF) { /* Its possible that the parent lport is not considered yet. @@ -1739,7 +1753,7 @@ consider_container_lport(const struct sbrec_port_binding *pb, consider_vif_lport(parent_pb, b_ctx_in, b_ctx_out, parent_lbinding); parent_b_lport = binding_lport_find(binding_lports, - pb->parent_port); + binding_port_name); } else { /* The parent lport doesn't exist. Cannot consider the container * lport for binding. */ @@ -1766,7 +1780,8 @@ consider_container_lport(const struct sbrec_port_binding *pb, } ovs_assert(parent_b_lport && parent_b_lport->pb); - /* cannot bind to this chassis if the parent_port cannot be bounded. */ + /* cannot bind to this chassis if the parent_port/mirror_port + * cannot be bounded. */ /* Do not bind neither if parent is postponed */ bool can_bind = lport_can_bind_on_this_chassis(b_ctx_in->chassis_rec, parent_b_lport->pb) && @@ -2188,6 +2203,10 @@ binding_run(struct binding_ctx_in *b_ctx_in, struct binding_ctx_out *b_ctx_out) consider_container_lport(pb, b_ctx_in, b_ctx_out); break; + case LP_MIRROR: + consider_container_lport(pb, b_ctx_in, b_ctx_out); + break; + case LP_VIRTUAL: consider_virtual_lport(pb, b_ctx_in, b_ctx_out); break; @@ -2459,7 +2478,7 @@ consider_iface_claim(const struct ovsrec_interface *iface_rec, /* Update the child local_binding's iface (if any children) and try to * claim the container lbindings. */ LIST_FOR_EACH (b_lport, list_node, &lbinding->binding_lports) { - if (b_lport->type == LP_CONTAINER) { + if (b_lport->type == LP_CONTAINER || b_lport->type == LP_MIRROR) { if (!consider_container_lport(b_lport->pb, b_ctx_in, b_ctx_out)) { return false; } @@ -2550,7 +2569,7 @@ consider_iface_release(const struct ovsrec_interface *iface_rec, remove_related_lport(b_lport->pb, b_ctx_out); } - /* Check if the lbinding has children of type PB_CONTAINER. + /* Check if the lbinding has children of type PB_CONTAINER/PB_MIRROR. * If so, don't delete the local_binding. */ if (!is_lbinding_container_parent(lbinding)) { local_binding_delete(lbinding, local_bindings, binding_lports, @@ -2858,13 +2877,13 @@ handle_deleted_vif_lport(const struct sbrec_port_binding *pb, } } - /* If its a container lport, then delete its entry from local_lports - * if present. + /* If its a container or mirror lport, then delete its entry from + * local_lports if present. * Note: If a normal lport is deleted, we don't want to remove * it from local_lports if there is a VIF entry. * consider_iface_release() takes care of removing from the local_lports * when the interface change happens. */ - if (lport_type == LP_CONTAINER) { + if (lport_type == LP_CONTAINER || lport_type == LP_MIRROR) { remove_local_lports(pb->logical_port, b_ctx_out); } @@ -2888,7 +2907,8 @@ handle_updated_vif_lport(const struct sbrec_port_binding *pb, if (lport_type == LP_VIRTUAL) { handled = consider_virtual_lport(pb, b_ctx_in, b_ctx_out); - } else if (lport_type == LP_CONTAINER) { + } else if (lport_type == LP_CONTAINER || + lport_type == LP_MIRROR) { handled = consider_container_lport(pb, b_ctx_in, b_ctx_out); } else { handled = consider_vif_lport(pb, b_ctx_in, b_ctx_out, NULL); @@ -2901,7 +2921,7 @@ handle_updated_vif_lport(const struct sbrec_port_binding *pb, bool now_claimed = (pb->chassis == b_ctx_in->chassis_rec); if (lport_type == LP_VIRTUAL || lport_type == LP_CONTAINER || - claimed == now_claimed) { + lport_type == LP_MIRROR || claimed == now_claimed) { return true; } @@ -2919,7 +2939,7 @@ handle_updated_vif_lport(const struct sbrec_port_binding *pb, struct binding_lport *b_lport; LIST_FOR_EACH (b_lport, list_node, &lbinding->binding_lports) { - if (b_lport->type == LP_CONTAINER) { + if (b_lport->type == LP_CONTAINER || b_lport->type == LP_MIRROR) { handled = consider_container_lport(b_lport->pb, b_ctx_in, b_ctx_out); if (!handled) { @@ -3044,6 +3064,7 @@ handle_updated_port(struct binding_ctx_in *b_ctx_in, switch (lport_type) { case LP_VIF: case LP_CONTAINER: + case LP_MIRROR: case LP_VIRTUAL: /* If port binding type just changed, port might be a "related_lport" * while it should not. Remove it from that set. It will be added @@ -3156,6 +3177,8 @@ binding_handle_port_binding_changes(struct binding_ctx_in *b_ctx_in, */ struct shash deleted_container_pbs = SHASH_INITIALIZER(&deleted_container_pbs); + struct shash deleted_mirror_pbs = + SHASH_INITIALIZER(&deleted_mirror_pbs); struct shash deleted_virtual_pbs = SHASH_INITIALIZER(&deleted_virtual_pbs); struct shash deleted_vif_pbs = @@ -3197,6 +3220,8 @@ binding_handle_port_binding_changes(struct binding_ctx_in *b_ctx_in, shash_add(&deleted_vif_pbs, pb->logical_port, pb); } else if (lport_type == LP_CONTAINER) { shash_add(&deleted_container_pbs, pb->logical_port, pb); + } else if (lport_type == LP_MIRROR) { + shash_add(&deleted_mirror_pbs, pb->logical_port, pb); } else if (lport_type == LP_VIRTUAL) { shash_add(&deleted_virtual_pbs, pb->logical_port, pb); } else if (lport_type == LP_LOCALPORT) { @@ -3218,6 +3243,15 @@ binding_handle_port_binding_changes(struct binding_ctx_in *b_ctx_in, } } + SHASH_FOR_EACH_SAFE (node, &deleted_mirror_pbs) { + handled = handle_deleted_vif_lport(node->data, LP_MIRROR, b_ctx_in, + b_ctx_out); + shash_delete(&deleted_mirror_pbs, node); + if (!handled) { + goto delete_done; + } + } + SHASH_FOR_EACH_SAFE (node, &deleted_virtual_pbs) { handled = handle_deleted_vif_lport(node->data, LP_VIRTUAL, b_ctx_in, b_ctx_out); @@ -3249,6 +3283,7 @@ binding_handle_port_binding_changes(struct binding_ctx_in *b_ctx_in, delete_done: shash_destroy(&deleted_container_pbs); + shash_destroy(&deleted_mirror_pbs); shash_destroy(&deleted_virtual_pbs); shash_destroy(&deleted_vif_pbs); shash_destroy(&deleted_localport_pbs); @@ -3514,8 +3549,10 @@ local_binding_handle_stale_binding_lports(struct local_binding *lbinding, binding_lport_delete(&b_ctx_out->lbinding_data->lports, b_lport); handled = consider_virtual_lport(pb, b_ctx_in, b_ctx_out); - } else if (b_lport->type == LP_CONTAINER && - pb_lport_type == LP_CONTAINER) { + } else if ((b_lport->type == LP_CONTAINER && + pb_lport_type == LP_CONTAINER) || + (b_lport->type == LP_MIRROR && + pb_lport_type == LP_MIRROR)) { /* For container lport, binding_lport is preserved so that when * the parent port is created, it can be considered. * consider_container_lport() creates the binding_lport for the parent @@ -3733,9 +3770,9 @@ binding_lport_get_parent_pb(struct binding_lport *b_lport) * If the 'b_lport' type is LP_VIF, then its name and its lbinding->name * should match. Otherwise this should be cleaned up. * - * If the 'b_lport' type is LP_CONTAINER, then its parent_port name should - * be the same as its lbinding's name. Otherwise this should be - * cleaned up. + * If the 'b_lport' type is LP_CONTAINER or LP_MIRROR, then its parent_port + * name should be the same as its lbinding's name. Otherwise this should + * be cleaned up. * * If the 'b_lport' type is LP_VIRTUAL, then its virtual parent name * should be the same as its lbinding's name. Otherwise this @@ -3770,6 +3807,12 @@ binding_lport_check_and_cleanup(struct binding_lport *b_lport, } break; + case LP_MIRROR: + if (strcmp(b_lport->pb->mirror_port, b_lport->lbinding->name)) { + cleanup_blport = true; + } + break; + case LP_VIRTUAL: if (!b_lport->pb->virtual_parent || strcmp(b_lport->pb->virtual_parent, b_lport->lbinding->name)) { diff --git a/controller/lflow.h b/controller/lflow.h index ab026e3bd..b72b23b96 100644 --- a/controller/lflow.h +++ b/controller/lflow.h @@ -67,17 +67,17 @@ struct uuid; /* Start of LOG_PIPELINE_LEN tables. */ #define OFTABLE_LOG_INGRESS_PIPELINE 8 -#define OFTABLE_OUTPUT_LARGE_PKT_DETECT 40 -#define OFTABLE_OUTPUT_LARGE_PKT_PROCESS 41 -#define OFTABLE_REMOTE_OUTPUT 42 -#define OFTABLE_LOCAL_OUTPUT 43 -#define OFTABLE_CHECK_LOOPBACK 44 +#define OFTABLE_OUTPUT_LARGE_PKT_DETECT 41 +#define OFTABLE_OUTPUT_LARGE_PKT_PROCESS 42 +#define OFTABLE_REMOTE_OUTPUT 43 +#define OFTABLE_LOCAL_OUTPUT 44 +#define OFTABLE_CHECK_LOOPBACK 45 /* Start of the OUTPUT section of the pipeline. */ #define OFTABLE_OUTPUT_INIT OFTABLE_OUTPUT_LARGE_PKT_DETECT /* Start of LOG_PIPELINE_LEN tables. */ -#define OFTABLE_LOG_EGRESS_PIPELINE 45 +#define OFTABLE_LOG_EGRESS_PIPELINE 46 #define OFTABLE_SAVE_INPORT 64 #define OFTABLE_LOG_TO_PHY 65 #define OFTABLE_MAC_BINDING 66 diff --git a/controller/mirror.c b/controller/mirror.c index b557b96da..2f1c811a0 100644 --- a/controller/mirror.c +++ b/controller/mirror.c @@ -121,6 +121,10 @@ mirror_run(struct ovsdb_idl_txn *ovs_idl_txn, /* 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) { + /* We don't need to add mirror to ovs if it is lport mirror. */ + if (!strcmp(sb_mirror->type, "lport")) { + continue; + } struct ovn_mirror *m = ovn_mirror_create(sb_mirror->name); m->sb_mirror = sb_mirror; ovn_mirror_add(&ovn_mirrors, m); diff --git a/controller/physical.c b/controller/physical.c index 5d088302a..15be7fd1b 100644 --- a/controller/physical.c +++ b/controller/physical.c @@ -1758,23 +1758,31 @@ consider_port_binding(const struct physical_ctx *ctx, int tag = 0; bool nested_container = false; - const struct sbrec_port_binding *parent_port = NULL; + const struct sbrec_port_binding *binding_port = NULL; ofp_port_t ofport; - if (binding->parent_port && *binding->parent_port) { - if (!binding->tag) { + bool is_mirror = !strcmp(binding->type, "mirror"); + if ((binding->parent_port && *binding->parent_port) || is_mirror) { + if (!binding->tag && !is_mirror) { return; } + + const char *binding_port_name = is_mirror ? binding->mirror_port : + binding->parent_port; ofport = local_binding_get_lport_ofport(ctx->local_bindings, - binding->parent_port); + binding_port_name); if (ofport) { - tag = *binding->tag; + if (!is_mirror) { + tag = *binding->tag; + } nested_container = true; - parent_port = lport_lookup_by_name( - ctx->sbrec_port_binding_by_name, binding->parent_port); - if (parent_port + binding_port + = lport_lookup_by_name(ctx->sbrec_port_binding_by_name, + binding_port_name); + + if (binding_port && (lport_can_bind_on_this_chassis(ctx->chassis, - parent_port) != CAN_BIND_AS_MAIN)) { + binding_port) != CAN_BIND_AS_MAIN)) { /* Even though there is an ofport for this container * parent port, it is requested on different chassis ignore * this container port. @@ -1838,7 +1846,7 @@ consider_port_binding(const struct physical_ctx *ctx, struct zone_ids zone_ids = get_zone_ids(binding, ctx->ct_zones); /* Pass the parent port binding if the port is a nested * container. */ - put_local_common_flows(dp_key, binding, parent_port, &zone_ids, + put_local_common_flows(dp_key, binding, binding_port, &zone_ids, &ctx->debug, ofpacts_p, flow_table); /* Table 0, Priority 150 and 100. diff --git a/lib/ovn-util.c b/lib/ovn-util.c index 949078eee..a0a5e00a4 100644 --- a/lib/ovn-util.c +++ b/lib/ovn-util.c @@ -1197,6 +1197,8 @@ get_lport_type(const struct sbrec_port_binding *pb) return LP_REMOTE; } else if (!strcmp(pb->type, "vtep")) { return LP_VTEP; + } else if (!strcmp(pb->type, "mirror")) { + return LP_MIRROR; } return LP_UNKNOWN; @@ -1210,6 +1212,8 @@ get_lport_type_str(enum en_lport_type lport_type) return "VIF"; case LP_CONTAINER: return "CONTAINER"; + case LP_MIRROR: + return "MIRROR"; case LP_VIRTUAL: return "VIRTUAL"; case LP_PATCH: @@ -1252,6 +1256,7 @@ is_pb_router_type(const struct sbrec_port_binding *pb) case LP_VIF: case LP_CONTAINER: + case LP_MIRROR: case LP_VIRTUAL: case LP_LOCALNET: case LP_LOCALPORT: @@ -1368,3 +1373,10 @@ lport_lookup_by_name(struct ovsdb_idl_index *sbrec_port_binding_by_name, return retval; } + +char * +ovn_mirror_port_name(const char *datapath_name, + const char *port_name) +{ + return xasprintf("mp-%s-%s", datapath_name, port_name); +} diff --git a/lib/ovn-util.h b/lib/ovn-util.h index f2f70dd72..ecf4b13ec 100644 --- a/lib/ovn-util.h +++ b/lib/ovn-util.h @@ -198,6 +198,8 @@ get_sb_port_group_name(const char *nb_pg_name, int64_t dp_tunnel_key, } char *ovn_chassis_redirect_name(const char *port_name); +char *ovn_mirror_port_name(const char *datapath_name, + const char *port_name); void ovn_set_pidfile(const char *name); bool ip46_parse_cidr(const char *str, struct in6_addr *prefix, @@ -310,7 +312,7 @@ BUILD_ASSERT_DECL( #define SCTP_ABORT_CHUNK_FLAG_T (1 << 0) /* The number of tables for the ingress and egress pipelines. */ -#define LOG_PIPELINE_LEN 30 +#define LOG_PIPELINE_LEN 31 static inline uint32_t hash_add_in6_addr(uint32_t hash, const struct in6_addr *addr) @@ -406,6 +408,7 @@ enum en_lport_type { LP_UNKNOWN, LP_VIF, LP_CONTAINER, + LP_MIRROR, LP_PATCH, LP_L3GATEWAY, LP_LOCALNET, diff --git a/northd/en-northd.c b/northd/en-northd.c index 7cea8863c..2866e913b 100644 --- a/northd/en-northd.c +++ b/northd/en-northd.c @@ -72,6 +72,8 @@ northd_get_input_data(struct engine_node *node, EN_OVSDB_GET(engine_get_input("NB_chassis_template_var", node)); input_data->nbrec_mirror_table = EN_OVSDB_GET(engine_get_input("NB_mirror", node)); + input_data->nbrec_mirror_rule_table = + EN_OVSDB_GET(engine_get_input("NB_mirror_rule", node)); input_data->sbrec_datapath_binding_table = EN_OVSDB_GET(engine_get_input("SB_datapath_binding", node)); diff --git a/northd/inc-proc-northd.c b/northd/inc-proc-northd.c index fd3fa04da..499ae174d 100644 --- a/northd/inc-proc-northd.c +++ b/northd/inc-proc-northd.c @@ -61,6 +61,7 @@ static unixctl_cb_func chassis_features_list; NB_NODE(acl, "acl") \ NB_NODE(logical_router, "logical_router") \ NB_NODE(mirror, "mirror") \ + NB_NODE(mirror_rule, "mirror_rule") \ NB_NODE(meter, "meter") \ NB_NODE(bfd, "bfd") \ NB_NODE(static_mac_binding, "static_mac_binding") \ @@ -198,6 +199,7 @@ void inc_proc_northd_init(struct ovsdb_idl_loop *nb, engine_add_input(&en_acl_id, &en_sb_acl_id, NULL); engine_add_input(&en_northd, &en_nb_mirror, NULL); + engine_add_input(&en_northd, &en_nb_mirror_rule, NULL); engine_add_input(&en_northd, &en_nb_static_mac_binding, NULL); engine_add_input(&en_northd, &en_nb_chassis_template_var, NULL); diff --git a/northd/northd.c b/northd/northd.c index 880112c3b..c12b56266 100644 --- a/northd/northd.c +++ b/northd/northd.c @@ -104,6 +104,8 @@ static bool vxlan_mode; * priority to determine the ACL's logical flow priority. */ #define OVN_ACL_PRI_OFFSET 1000 +#define OVN_LPORT_MIRROR_OFFSET 100 + /* Register definitions specific to switches. */ #define REGBIT_CONNTRACK_DEFRAG "reg0[0]" #define REGBIT_CONNTRACK_COMMIT "reg0[1]" @@ -437,6 +439,12 @@ od_has_lb_vip(const struct ovn_datapath *od) } } +static const char * +ovn_datapath_name(const struct sbrec_datapath_binding *sb) +{ + return smap_get_def(&sb->external_ids, "name", ""); +} + /* A group of logical router datapaths which are connected - either * directly or indirectly. * Each logical router can belong to only one group. */ @@ -1142,6 +1150,12 @@ is_transit_router_port(struct ovn_port *op) smap_get_bool(&op->sb->chassis->other_config, "is-remote", false); } +static bool +is_mp_port(const struct ovn_port *op) +{ + return op->mirror_source_port; +} + static void destroy_routable_addresses(struct ovn_port_routable_addresses *ra) { @@ -1223,6 +1237,7 @@ ovn_port_create(struct hmap *ports, const char *key, op->sb = sb; ovn_port_set_nb(op, nbsp, nbrp); op->primary_port = op->cr_port = NULL; + op->mirror_source_port = NULL; hmap_insert(ports, &op->key_node, hash_string(op->key, 0)); op->lflow_ref = lflow_ref_create(); @@ -1279,7 +1294,7 @@ ovn_port_destroy(struct hmap *ports, struct ovn_port *port) /* Don't remove port->list. The node should be removed from such lists * before calling this function. */ hmap_remove(ports, &port->key_node); - if (port->od && !port->primary_port) { + if (port->od && !port->mirror_source_port && !port->primary_port) { hmap_remove(&port->od->ports, &port->dp_node); } ovn_port_destroy_orphan(port); @@ -1396,8 +1411,8 @@ lsp_is_type_changed(const struct sbrec_port_binding *sb, if (!sb->type[0] && !nbsp->type[0]) { /* Two "VIF's" interface make sure both have parent_port - * set or both have parent_port unset, otherwisre they are - * different ports type. + * or mirror_port set or both have parent_port/mirror_port + * unset, otherwisre they are different ports type. */ if ((!sb->parent_port && nbsp->parent_name) || (sb->parent_port && !nbsp->parent_name)) { @@ -2122,6 +2137,37 @@ parse_lsp_addrs(struct ovn_port *op) op->n_ps_addrs++; } } + +static void +create_mirror_port(struct ovn_port *op, struct hmap *ports, + struct ovs_list *both_dbs, struct ovs_list *nb_only, + const struct nbrec_mirror *nb_mirror) +{ + char *mp_name = ovn_mirror_port_name(ovn_datapath_name(op->od->sb), + nb_mirror->sink); + struct ovn_port *mp = ovn_port_find(ports, mp_name); + struct ovn_port *target_port = ovn_port_find(ports, nb_mirror->sink); + + if (!target_port) { + return; + } + if (mp && mp->sb) { + ovn_port_set_nb(mp, op->nbsp, NULL); + ovs_list_remove(&mp->list); + ovs_list_push_back(both_dbs, &mp->list); + } else { + mp = ovn_port_create(ports, mp_name, op->nbsp, NULL, NULL); + ovs_list_push_back(nb_only, &mp->list); + } + + mp->mirror_source_port = op; + mp->mirror_target_port = target_port; + + mp->od = op->od; + + free(mp_name); +} + static struct ovn_port * join_logical_ports_lsp(struct hmap *ports, struct ovs_list *nb_only, struct ovs_list *both, @@ -2211,6 +2257,16 @@ join_logical_ports_lsp(struct hmap *ports, hmap_insert(&od->ports, &op->dp_node, hmap_node_hash(&op->key_node)); tag_alloc_add_existing_tags(tag_alloc_table, nbsp); + + /* Create mirror targets port bindings if there any mirror + * with lport type attached to this port. */ + for (size_t j = 0; j < nbsp->n_mirror_rules; j++) { + struct nbrec_mirror *mirror = nbsp->mirror_rules[j]; + if (!strcmp(mirror->type, "lport")) { + create_mirror_port(op, ports, both, nb_only, mirror); + } + } + return op; } @@ -3223,6 +3279,16 @@ ovn_port_update_sbrec(struct ovsdb_idl_txn *ovnsb_txn, sbrec_port_binding_set_external_ids(op->sb, &op->nbrp->external_ids); } else { + if (op->mirror_target_port) { + /* In case of using a lport mirror, we establish a port binding + * with mirror target port to act it like container port without + * tag it by vlan tag. */ + sbrec_port_binding_set_type(op->sb, "mirror"); + sbrec_port_binding_set_mirror_port(op->sb, + op->mirror_target_port->key); + goto common; + } + if (!lsp_is_router(op->nbsp)) { uint32_t queue_id = smap_get_int( &op->sb->options, "qdisc_queue_id", 0); @@ -3422,6 +3488,8 @@ ovn_port_update_sbrec(struct ovsdb_idl_txn *ovnsb_txn, } } + +common: if (op->tunnel_key != op->sb->tunnel_key) { sbrec_port_binding_set_tunnel_key(op->sb, op->tunnel_key); } @@ -4700,6 +4768,30 @@ check_lsp_changes_other_than_up(const struct nbrec_logical_switch_port *nbsp) return false; } +static bool +is_lsp_mirror_target_port(const struct northd_input *ni, + struct ovn_port *port) +{ + const struct nbrec_mirror *nb_mirror; + NBREC_MIRROR_TABLE_FOR_EACH (nb_mirror, ni->nbrec_mirror_table) { + if (!strcmp(nb_mirror->type, "lport") && + !strcmp(nb_mirror->sink, port->key)) { + return true; + } + } + return false; +} + +static bool +lsp_handle_mirror_rules_changes(const struct nbrec_logical_switch_port *nbsp) +{ + if (nbrec_logical_switch_port_is_updated(nbsp, + NBREC_LOGICAL_SWITCH_PORT_COL_MIRROR_RULES)) { + return false; + } + return true; +} + /* Handles logical switch port changes of a changed logical switch. * Returns false, if any logical port can't be incrementally handled. */ @@ -4770,6 +4862,11 @@ ls_handle_lsp_changes(struct ovsdb_idl_txn *ovnsb_idl_txn, * by this change. Fallback to recompute. */ goto fail; } + if (!lsp_handle_mirror_rules_changes(new_nbsp) || + is_lsp_mirror_target_port(ni, op)) { + /* Fallback to recompute. */ + goto fail; + } if (!check_lsp_is_up && !check_lsp_changes_other_than_up(new_nbsp)) { /* If the only change is the "up" column while the @@ -4818,6 +4915,11 @@ ls_handle_lsp_changes(struct ovsdb_idl_txn *ovnsb_idl_txn, sbrec_port_binding_delete(op->sb); delete_fdb_entries(ni->sbrec_fdb_by_dp_and_port, od->tunnel_key, op->tunnel_key); + if (is_lsp_mirror_target_port(ni, op)) { + /* This port was used ad target mirror port, fallback + * to recompute. */ + goto fail; + } } } @@ -5579,6 +5681,176 @@ build_dhcpv6_action(struct ovn_port *op, struct in6_addr *offer_ip, return true; } +enum mirror_filter { + IN_MIRROR, + OUT_MIRROR, + BOTH_MIRROR, +}; + +static void +build_mirror_default_lflow(struct ovn_datapath *od, + struct lflow_table *lflows) +{ + ovn_lflow_add(lflows, od, S_SWITCH_IN_MIRROR, 0, "1", "next;", NULL); + ovn_lflow_add(lflows, od, S_SWITCH_OUT_MIRROR, 0, "1", "next;", NULL); +} + +static void +build_mirror_lflow(struct ovn_port *op, + struct ovn_port *serving_port, + struct lflow_table *lflows, + struct nbrec_mirror_rule *rule, bool egress) +{ + struct ds match = DS_EMPTY_INITIALIZER; + struct ds action = DS_EMPTY_INITIALIZER; + enum ovn_stage stage; + const char *dir; + uint32_t priority = OVN_LPORT_MIRROR_OFFSET + rule->priority; + + if (egress) { + dir = "outport"; + stage = S_SWITCH_OUT_MIRROR; + } else { + dir = "inport"; + stage = S_SWITCH_IN_MIRROR; + } + + ds_put_format(&match, "%s == %s && %s", dir, op->json_key, rule->match); + + if (!strcmp(rule->action, "mirror")) { + ds_put_format(&action, "clone {outport = %s; ", + serving_port->json_key); + if (egress) { + ds_put_format(&action, "next(pipeline=ingress, table=%d);}; ", + ovn_stage_get_table(S_SWITCH_IN_L2_UNKNOWN)); + } else { + ds_put_cstr(&action, "output;}; "); + } + } + + ds_put_cstr(&action, "next;"); + + ovn_lflow_add(lflows, op->od, stage, priority, ds_cstr(&match), + ds_cstr(&action), op->lflow_ref); + + ds_destroy(&match); + ds_destroy(&action); +} + +static void +build_mirror_pass_lflow(struct ovn_port *op, + struct ovn_port *serving_port, + struct lflow_table *lflows, bool egress) +{ + struct ds match = DS_EMPTY_INITIALIZER; + struct ds action = DS_EMPTY_INITIALIZER; + enum ovn_stage stage; + const char *dir; + + ds_put_format(&action, "clone {outport = %s; ", + serving_port->json_key); + + if (egress) { + dir = "outport"; + stage = S_SWITCH_OUT_MIRROR; + ds_put_format(&action, "next(pipeline=ingress, table=%d);}; next;", + ovn_stage_get_table(S_SWITCH_IN_L2_UNKNOWN)); + } else { + dir = "inport"; + stage = S_SWITCH_IN_MIRROR; + ds_put_cstr(&action, "output;}; next;"); + } + + ds_put_format(&match, "%s == %s", dir, op->json_key); + + ovn_lflow_add(lflows, op->od, stage, OVN_LPORT_MIRROR_OFFSET, + ds_cstr(&match), ds_cstr(&action), op->lflow_ref); + + ds_destroy(&match); + ds_destroy(&action); +} + +/* + * Bypass the contract check for traffic directed to the target port. + */ +static void +build_mirror_target_port_lflow(const struct ovn_port *serving_port, + const struct hmap *ls_ports, + struct lflow_table *lflows) +{ + struct ds match = DS_EMPTY_INITIALIZER; + struct ds action = DS_EMPTY_INITIALIZER; + + struct ovn_port *target_port = ovn_port_find(ls_ports, + serving_port->mirror_target_port->key); + + ds_put_format(&match, "outport == %s", serving_port->json_key); + ds_put_format(&action, "next(pipeline=ingress, table=%d);", + ovn_stage_get_table(S_SWITCH_OUT_APPLY_PORT_SEC)); + + ovn_lflow_add(lflows, target_port->od, S_SWITCH_OUT_PRE_ACL, UINT16_MAX, + ds_cstr(&match), ds_cstr(&action), target_port->lflow_ref); + + ds_destroy(&match); + ds_destroy(&action); +} + +static void +build_mirror_lflows(struct ovn_port *op, + const struct hmap *ls_ports, + struct lflow_table *lflows) +{ + enum mirror_filter filter; + + for (size_t i = 0; i < op->nbsp->n_mirror_rules; i++) { + struct nbrec_mirror *mirror = op->nbsp->mirror_rules[i]; + + if (strcmp(mirror->type, "lport")) { + continue; + } + + char *serving_port_name = ovn_mirror_port_name( + ovn_datapath_name(op->od->sb), + mirror->sink); + + struct ovn_port *serving_port = ovn_port_find(ls_ports, + serving_port_name); + + /* Mirror serving port wasn't created + * because the target port doesn't exist. + */ + if (!serving_port) { + continue; + } + + filter = !strcmp(mirror->filter, "from-lport") ? IN_MIRROR : + !strcmp(mirror->filter, "to-lport") ? OUT_MIRROR + : BOTH_MIRROR; + + if (filter == IN_MIRROR || filter == BOTH_MIRROR) { + build_mirror_pass_lflow(op, serving_port, lflows, false); + } + if (filter == OUT_MIRROR || filter == BOTH_MIRROR) { + build_mirror_pass_lflow(op, serving_port, lflows, true); + } + + for (size_t j = 0; j < mirror->n_mirror_rules; j++) { + struct nbrec_mirror_rule *rule = mirror->mirror_rules[j]; + + if (filter == IN_MIRROR || filter == BOTH_MIRROR) { + build_mirror_lflow(op, serving_port, lflows, rule, false); + } + if (filter == OUT_MIRROR || filter == BOTH_MIRROR) { + build_mirror_lflow(op, serving_port, lflows, rule, true); + } + } + + build_mirror_target_port_lflow(serving_port, ls_ports, lflows); + + free(serving_port_name); + } +} + /* Adds the logical flows in the (in/out) check port sec stage only if * - the lport is disabled or * - lport is of type vtep - to skip the ingress pipeline. @@ -17209,6 +17481,7 @@ build_lswitch_and_lrouter_iterate_by_ls(struct ovn_datapath *od, struct lswitch_flow_build_info *lsi) { ovs_assert(od->nbs); + build_mirror_default_lflow(od, lsi->lflows); build_lswitch_lflows_pre_acl_and_acl(od, lsi->lflows, lsi->meter_groups, NULL); @@ -17283,7 +17556,11 @@ build_lswitch_and_lrouter_iterate_by_lsp(struct ovn_port *op, { ovs_assert(op->nbsp); + if (is_mp_port(op)) { + return; + } /* Build Logical Switch Flows. */ + build_mirror_lflows(op, ls_ports, lflows); build_lswitch_port_sec_op(op, lflows, actions, match); build_lswitch_learn_fdb_op(op, lflows, actions, match); build_lswitch_arp_nd_responder_skip_local(op, lflows, match); diff --git a/northd/northd.h b/northd/northd.h index af58893d5..b5825bc82 100644 --- a/northd/northd.h +++ b/northd/northd.h @@ -35,6 +35,7 @@ struct northd_input { const struct nbrec_chassis_template_var_table *nbrec_chassis_template_var_table; const struct nbrec_mirror_table *nbrec_mirror_table; + const struct nbrec_mirror_rule_table *nbrec_mirror_rule_table; /* Southbound table references */ const struct sbrec_datapath_binding_table *sbrec_datapath_binding_table; @@ -433,37 +434,38 @@ enum ovn_stage { /* Logical switch ingress stages. */ \ PIPELINE_STAGE(SWITCH, IN, CHECK_PORT_SEC, 0, "ls_in_check_port_sec") \ PIPELINE_STAGE(SWITCH, IN, APPLY_PORT_SEC, 1, "ls_in_apply_port_sec") \ - PIPELINE_STAGE(SWITCH, IN, LOOKUP_FDB , 2, "ls_in_lookup_fdb") \ - PIPELINE_STAGE(SWITCH, IN, PUT_FDB, 3, "ls_in_put_fdb") \ - PIPELINE_STAGE(SWITCH, IN, PRE_ACL, 4, "ls_in_pre_acl") \ - PIPELINE_STAGE(SWITCH, IN, PRE_LB, 5, "ls_in_pre_lb") \ - PIPELINE_STAGE(SWITCH, IN, PRE_STATEFUL, 6, "ls_in_pre_stateful") \ - PIPELINE_STAGE(SWITCH, IN, ACL_HINT, 7, "ls_in_acl_hint") \ - PIPELINE_STAGE(SWITCH, IN, ACL_EVAL, 8, "ls_in_acl_eval") \ - PIPELINE_STAGE(SWITCH, IN, ACL_SAMPLE, 9, "ls_in_acl_sample") \ - PIPELINE_STAGE(SWITCH, IN, ACL_ACTION, 10, "ls_in_acl_action") \ - PIPELINE_STAGE(SWITCH, IN, QOS, 11, "ls_in_qos") \ - PIPELINE_STAGE(SWITCH, IN, LB_AFF_CHECK, 12, "ls_in_lb_aff_check") \ - PIPELINE_STAGE(SWITCH, IN, LB, 13, "ls_in_lb") \ - PIPELINE_STAGE(SWITCH, IN, LB_AFF_LEARN, 14, "ls_in_lb_aff_learn") \ - PIPELINE_STAGE(SWITCH, IN, PRE_HAIRPIN, 15, "ls_in_pre_hairpin") \ - PIPELINE_STAGE(SWITCH, IN, NAT_HAIRPIN, 16, "ls_in_nat_hairpin") \ - PIPELINE_STAGE(SWITCH, IN, HAIRPIN, 17, "ls_in_hairpin") \ - PIPELINE_STAGE(SWITCH, IN, ACL_AFTER_LB_EVAL, 18, \ - "ls_in_acl_after_lb_eval") \ - PIPELINE_STAGE(SWITCH, IN, ACL_AFTER_LB_SAMPLE, 19, \ + PIPELINE_STAGE(SWITCH, IN, MIRROR, 2, "ls_in_mirror") \ + PIPELINE_STAGE(SWITCH, IN, LOOKUP_FDB, 3, "ls_in_lookup_fdb") \ + PIPELINE_STAGE(SWITCH, IN, PUT_FDB, 4, "ls_in_put_fdb") \ + PIPELINE_STAGE(SWITCH, IN, PRE_ACL, 5, "ls_in_pre_acl") \ + PIPELINE_STAGE(SWITCH, IN, PRE_LB, 6, "ls_in_pre_lb") \ + PIPELINE_STAGE(SWITCH, IN, PRE_STATEFUL, 7, "ls_in_pre_stateful") \ + PIPELINE_STAGE(SWITCH, IN, ACL_HINT, 8, "ls_in_acl_hint") \ + PIPELINE_STAGE(SWITCH, IN, ACL_EVAL, 9, "ls_in_acl_eval") \ + PIPELINE_STAGE(SWITCH, IN, ACL_SAMPLE, 10, "ls_in_acl_sample") \ + PIPELINE_STAGE(SWITCH, IN, ACL_ACTION, 11, "ls_in_acl_action") \ + PIPELINE_STAGE(SWITCH, IN, QOS, 12, "ls_in_qos") \ + PIPELINE_STAGE(SWITCH, IN, LB_AFF_CHECK, 13, "ls_in_lb_aff_check") \ + PIPELINE_STAGE(SWITCH, IN, LB, 14, "ls_in_lb") \ + PIPELINE_STAGE(SWITCH, IN, LB_AFF_LEARN, 15, "ls_in_lb_aff_learn") \ + PIPELINE_STAGE(SWITCH, IN, PRE_HAIRPIN, 16, "ls_in_pre_hairpin") \ + PIPELINE_STAGE(SWITCH, IN, NAT_HAIRPIN, 17, "ls_in_nat_hairpin") \ + PIPELINE_STAGE(SWITCH, IN, HAIRPIN, 18, "ls_in_hairpin") \ + PIPELINE_STAGE(SWITCH, IN, ACL_AFTER_LB_EVAL, 19, \ + "ls_in_acl_after_lb_eval") \ + PIPELINE_STAGE(SWITCH, IN, ACL_AFTER_LB_SAMPLE, 20, \ "ls_in_acl_after_lb_sample") \ - PIPELINE_STAGE(SWITCH, IN, ACL_AFTER_LB_ACTION, 20, \ + PIPELINE_STAGE(SWITCH, IN, ACL_AFTER_LB_ACTION, 21, \ "ls_in_acl_after_lb_action") \ - PIPELINE_STAGE(SWITCH, IN, STATEFUL, 21, "ls_in_stateful") \ - PIPELINE_STAGE(SWITCH, IN, ARP_ND_RSP, 22, "ls_in_arp_rsp") \ - PIPELINE_STAGE(SWITCH, IN, DHCP_OPTIONS, 23, "ls_in_dhcp_options") \ - PIPELINE_STAGE(SWITCH, IN, DHCP_RESPONSE, 24, "ls_in_dhcp_response") \ - PIPELINE_STAGE(SWITCH, IN, DNS_LOOKUP, 25, "ls_in_dns_lookup") \ - PIPELINE_STAGE(SWITCH, IN, DNS_RESPONSE, 26, "ls_in_dns_response") \ - PIPELINE_STAGE(SWITCH, IN, EXTERNAL_PORT, 27, "ls_in_external_port") \ - PIPELINE_STAGE(SWITCH, IN, L2_LKUP, 28, "ls_in_l2_lkup") \ - PIPELINE_STAGE(SWITCH, IN, L2_UNKNOWN, 29, "ls_in_l2_unknown") \ + PIPELINE_STAGE(SWITCH, IN, STATEFUL, 22, "ls_in_stateful") \ + PIPELINE_STAGE(SWITCH, IN, ARP_ND_RSP, 23, "ls_in_arp_rsp") \ + PIPELINE_STAGE(SWITCH, IN, DHCP_OPTIONS, 24, "ls_in_dhcp_options") \ + PIPELINE_STAGE(SWITCH, IN, DHCP_RESPONSE, 25, "ls_in_dhcp_response") \ + PIPELINE_STAGE(SWITCH, IN, DNS_LOOKUP, 26, "ls_in_dns_lookup") \ + PIPELINE_STAGE(SWITCH, IN, DNS_RESPONSE, 27, "ls_in_dns_response") \ + PIPELINE_STAGE(SWITCH, IN, EXTERNAL_PORT, 28, "ls_in_external_port") \ + PIPELINE_STAGE(SWITCH, IN, L2_LKUP, 29, "ls_in_l2_lkup") \ + PIPELINE_STAGE(SWITCH, IN, L2_UNKNOWN, 30, "ls_in_l2_unknown") \ \ /* Logical switch egress stages. */ \ PIPELINE_STAGE(SWITCH, OUT, PRE_ACL, 0, "ls_out_pre_acl") \ @@ -473,11 +475,12 @@ enum ovn_stage { PIPELINE_STAGE(SWITCH, OUT, ACL_EVAL, 4, "ls_out_acl_eval") \ PIPELINE_STAGE(SWITCH, OUT, ACL_SAMPLE, 5, "ls_out_acl_sample") \ PIPELINE_STAGE(SWITCH, OUT, ACL_ACTION, 6, "ls_out_acl_action") \ - PIPELINE_STAGE(SWITCH, OUT, QOS, 7, "ls_out_qos") \ - PIPELINE_STAGE(SWITCH, OUT, STATEFUL, 8, "ls_out_stateful") \ - PIPELINE_STAGE(SWITCH, OUT, CHECK_PORT_SEC, 9, "ls_out_check_port_sec") \ - PIPELINE_STAGE(SWITCH, OUT, APPLY_PORT_SEC, 10, "ls_out_apply_port_sec") \ - \ + PIPELINE_STAGE(SWITCH, OUT, MIRROR, 7, "ls_out_mirror") \ + PIPELINE_STAGE(SWITCH, OUT, QOS, 8, "ls_out_qos") \ + PIPELINE_STAGE(SWITCH, OUT, STATEFUL, 9, "ls_out_stateful") \ + PIPELINE_STAGE(SWITCH, OUT, CHECK_PORT_SEC, 10, "ls_out_check_port_sec") \ + PIPELINE_STAGE(SWITCH, OUT, APPLY_PORT_SEC, 11, "ls_out_apply_port_sec") \ + \ /* Logical router ingress stages. */ \ PIPELINE_STAGE(ROUTER, IN, ADMISSION, 0, "lr_in_admission") \ PIPELINE_STAGE(ROUTER, IN, LOOKUP_NEIGHBOR, 1, "lr_in_lookup_neighbor") \ @@ -640,6 +643,14 @@ struct ovn_port { * to NULL. */ struct ovn_port *cr_port; + /* If this ovn_port is a mirror target port, this field is set for + * a parent port. */ + struct ovn_port *mirror_target_port; + + /* If this ovn_port mirror service port, then 'primary port' points to + * port from which this ovn_port is derived. */ + struct ovn_port *mirror_source_port; + bool has_unknown; /* If the addresses have 'unknown' defined. */ /* The port's peer: diff --git a/northd/ovn-northd.8.xml b/northd/ovn-northd.8.xml index 93b1a9135..6ee1e3b75 100644 --- a/northd/ovn-northd.8.xml +++ b/northd/ovn-northd.8.xml @@ -76,8 +76,8 @@ <h2>PKI Options</h2> <p> - PKI configuration is required in order to use SSL/TLS for the connections - to the Northbound and Southbound databases. + PKI configuration is required in order to use SSL for the connections to + the Northbound and Southbound databases. </p> <xi:include href="lib/ssl.xml" xmlns:xi="http://www.w3.org/2003/XInclude"/> @@ -398,7 +398,35 @@ </li> </ul> - <h3>Ingress Table 2: Lookup MAC address learning table</h3> + <h3>Ingress Table 2: Mirror </h3> + + <p> + Overlay remote mirror table contains the following + logical flows: + </p> + + <ul> + <li> + A priority-100 logical flow is added for each lport mirror + attached to logical switch port, matches all incoming pakcets + to attached port and clones the packet and sends cloned pakcet + to the mirror target port. + </li> + + <li> + A priority 0 flow is added which matches on all packets and applies + the action <code>next;</code>. + </li> + + <li> + A logical flow added for each Mirror Rule in Mirror table attached + to logical switch ports, matches all incoming packets that match + rules and clones the packet and sends cloned packet to mirror + target port. + </li> + </ul> + + <h3>Ingress Table 3: Lookup MAC address learning table</h3> <p> This table looks up the MAC learning table of the logical switch @@ -448,7 +476,7 @@ </li> </ul> - <h3>Ingress Table 3: Learn MAC of 'unknown' ports.</h3> + <h3>Ingress Table 4: Learn MAC of 'unknown' ports.</h3> <p> This table learns the MAC addresses seen on the VIF logical ports @@ -485,7 +513,7 @@ </li> </ul> - <h3>Ingress Table 4: <code>from-lport</code> Pre-ACLs</h3> + <h3>Ingress Table 5: <code>from-lport</code> Pre-ACLs</h3> <p> This table prepares flows for possible stateful ACL processing in @@ -519,7 +547,7 @@ db="OVN_Northbound"/> table. </p> - <h3>Ingress Table 5: Pre-LB</h3> + <h3>Ingress Table 6: Pre-LB</h3> <p> This table prepares flows for possible stateful load balancing processing @@ -595,7 +623,7 @@ logical router datapath to logical switch datapath. </p> - <h3>Ingress Table 6: Pre-stateful</h3> + <h3>Ingress Table 7: Pre-stateful</h3> <p> This table prepares flows for all possible stateful processing @@ -629,7 +657,7 @@ </li> </ul> - <h3>Ingress Table 7: <code>from-lport</code> ACL hints</h3> + <h3>Ingress Table 8: <code>from-lport</code> ACL hints</h3> <p> This table consists of logical flows that set hints @@ -714,7 +742,7 @@ </li> </ul> - <h3>Ingress table 8: <code>from-lport</code> ACL evaluation before LB</h3> + <h3>Ingress table 9: <code>from-lport</code> ACL evaluation before LB</h3> <p> Logical flows in this table closely reproduce those in the @@ -893,7 +921,7 @@ </li> </ul> - <h3>Ingress Table 9: <code>from-lport</code> ACL sampling</h3> + <h3>Ingress Table 10: <code>from-lport</code> ACL sampling</h3> <p> Logical flows in this table sample traffic matched by @@ -933,7 +961,7 @@ </li> </ul> - <h3>Ingress Table 10: <code>from-lport</code> ACL action</h3> + <h3>Ingress Table 11: <code>from-lport</code> ACL action</h3> <p> Logical flows in this table decide how to proceed based on the values of @@ -973,7 +1001,7 @@ </li> </ul> - <h3>Ingress Table 11: <code>from-lport</code> QoS</h3> + <h3>Ingress Table 12: <code>from-lport</code> QoS</h3> <p> Logical flows in this table closely reproduce those in the @@ -996,7 +1024,7 @@ </li> </ul> - <h3>Ingress Table 12: Load balancing affinity check</h3> + <h3>Ingress Table 13: Load balancing affinity check</h3> <p> Load balancing affinity check table contains the following @@ -1024,7 +1052,7 @@ </li> </ul> - <h3>Ingress Table 13: LB</h3> + <h3>Ingress Table 14: LB</h3> <ul> <li> @@ -1104,7 +1132,7 @@ </li> </ul> - <h3>Ingress Table 14: Load balancing affinity learn</h3> + <h3>Ingress Table 15: Load balancing affinity learn</h3> <p> Load balancing affinity learn table contains the following @@ -1135,7 +1163,7 @@ </li> </ul> - <h3>Ingress Table 15: Pre-Hairpin</h3> + <h3>Ingress Table 16: Pre-Hairpin</h3> <ul> <li> If the logical switch has load balancer(s) configured, then a @@ -1153,7 +1181,7 @@ </li> </ul> - <h3>Ingress Table 16: Nat-Hairpin</h3> + <h3>Ingress Table 17: Nat-Hairpin</h3> <ul> <li> If the logical switch has load balancer(s) configured, then a @@ -1188,7 +1216,7 @@ </li> </ul> - <h3>Ingress Table 17: Hairpin</h3> + <h3>Ingress Table 18: Hairpin</h3> <ul> <li> <p> @@ -1226,7 +1254,7 @@ </li> </ul> - <h3>Ingress table 18: <code>from-lport</code> ACL evaluation after LB</h3> + <h3>Ingress table 19: <code>from-lport</code> ACL evaluation after LB</h3> <p> Logical flows in this table closely reproduce those in the @@ -1311,7 +1339,7 @@ </li> </ul> - <h3>Ingress Table 19: <code>from-lport</code> ACL sampling after LB</h3> + <h3>Ingress Table 20: <code>from-lport</code> ACL sampling after LB</h3> <p> Logical flows in this table sample traffic matched by @@ -1351,7 +1379,7 @@ </li> </ul> - <h3>Ingress Table 20: <code>from-lport</code> ACL action after LB</h3> + <h3>Ingress Table 21: <code>from-lport</code> ACL action after LB</h3> <p> Logical flows in this table decide how to proceed based on the values of @@ -1391,7 +1419,7 @@ </li> </ul> - <h3>Ingress Table 21: Stateful</h3> + <h3>Ingress Table 22: Stateful</h3> <ul> <li> @@ -1414,7 +1442,7 @@ </li> </ul> - <h3>Ingress Table 22: ARP/ND responder</h3> + <h3>Ingress Table 23: ARP/ND responder</h3> <p> This table implements ARP/ND responder in a logical switch for known @@ -1749,7 +1777,7 @@ output; </li> </ul> - <h3>Ingress Table 23: DHCP option processing</h3> + <h3>Ingress Table 24: DHCP option processing</h3> <p> This table adds the DHCPv4 options to a DHCPv4 packet from the @@ -1810,7 +1838,7 @@ next; </li> </ul> - <h3>Ingress Table 24: DHCP responses</h3> + <h3>Ingress Table 25: DHCP responses</h3> <p> This table implements DHCP responder for the DHCP replies generated by @@ -1891,7 +1919,7 @@ output; </li> </ul> - <h3>Ingress Table 25 DNS Lookup</h3> + <h3>Ingress Table 26 DNS Lookup</h3> <p> This table looks up and resolves the DNS names to the corresponding @@ -1920,7 +1948,7 @@ reg0[4] = dns_lookup(); next; </li> </ul> - <h3>Ingress Table 26 DNS Responses</h3> + <h3>Ingress Table 27 DNS Responses</h3> <p> This table implements DNS responder for the DNS replies generated by @@ -1955,7 +1983,7 @@ output; </li> </ul> - <h3>Ingress table 27 External ports</h3> + <h3>Ingress table 28 External ports</h3> <p> Traffic from the <code>external</code> logical ports enter the ingress @@ -1998,7 +2026,7 @@ output; </li> </ul> - <h3>Ingress Table 28 Destination Lookup</h3> + <h3>Ingress Table 29 Destination Lookup</h3> <p> This table implements switching behavior. It contains these logical @@ -2224,7 +2252,7 @@ output; </li> </ul> - <h3>Ingress Table 29 Destination unknown</h3> + <h3>Ingress Table 30 Destination unknown</h3> <p> This table handles the packets whose destination was not found or @@ -2277,6 +2305,7 @@ output; </li> </ul> + <h3>Egress Table 0: <code>to-lport</code> Pre-ACLs</h3> <p> @@ -2442,21 +2471,49 @@ output; This is similar to ingress table <code>ACL action</code>. </p> - <h3>Egress Table 7: <code>to-lport</code> QoS</h3> + <h3>Egress Table 7: Mirror </h3> + + <p> + Overlay remote mirror table contains the following + logical flows: + </p> + + <ul> + <li> + A priority-100 logical flow is added for each lport mirror + attached to logical switch port, matches all outcoming pakcets + to attached port and clones the packet and sends cloned pakcet + to the mirror target port. + </li> + + <li> + A priority 0 flow is added which matches on all packets and applies + the action <code>next;</code>. + </li> + + <li> + A logical flow added for each Mirror Rule in Mirror table attached + to logical switch ports, matches all outcoming packets that match + rules and clones the packet and sends cloned packet to mirror + target port. + </li> + </ul> + + <h3>Egress Table 8: <code>to-lport</code> QoS</h3> <p> This is similar to ingress table <code>QoS</code> except they apply to <code>to-lport</code> QoS rules. </p> - <h3>Egress Table 8: Stateful</h3> + <h3>Egress Table 9: Stateful</h3> <p> This is similar to ingress table <code>Stateful</code> except that there are no rules added for load balancing new connections. </p> - <h3>Egress Table 9: Egress Port Security - check</h3> + <h3>Egress Table 10: Egress Port Security - check</h3> <p> This is similar to the port security logic in table @@ -2485,7 +2542,7 @@ output; </li> </ul> - <h3>Egress Table 10: Egress Port Security - Apply</h3> + <h3>Egress Table 11: Egress Port Security - Apply</h3> <p> This is similar to the ingress port security logic in ingress table diff --git a/ovn-nb.ovsschema b/ovn-nb.ovsschema index ea71d09bd..f55930a2e 100644 --- a/ovn-nb.ovsschema +++ b/ovn-nb.ovsschema @@ -1,7 +1,7 @@ { "name": "OVN_Northbound", - "version": "7.11.0", - "cksum": "2520708261 39034", + "version": "7.12.0", + "cksum": "2749576410 39903", "tables": { "NB_Global": { "columns": { @@ -364,13 +364,32 @@ "type": {"type": {"key": {"type": "string", "enum": ["set", ["gre", "erspan", - "local"]]}}}, + "local", + "lport"]]}}}, "index": {"type": "integer"}, + "mirror_rules": { + "type": { + "key": { + "type": "uuid", + "refTable": "Mirror_Rule", + "refType": "strong"}, + "min": 0, + "max": "unlimited"}}, "external_ids": { "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}}}, "indexes": [["name"]], "isRoot": true}, + "Mirror_Rule": { + "columns": { + "match": {"type": "string"}, + "action": {"type": { + "key": {"type": "string", + "enum": ["set", ["mirror", "skip"]]}}}, + "priority": {"type": {"key": {"type": "integer", + "minInteger": 0, + "maxInteger": 32767}}}}, + "isRoot": false}, "Meter": { "columns": { "name": {"type": "string"}, diff --git a/ovn-nb.xml b/ovn-nb.xml index 024ae7b17..a3fb8618a 100644 --- a/ovn-nb.xml +++ b/ovn-nb.xml @@ -3141,7 +3141,7 @@ or <column name="type"> <p> The value of this field specifies the mirror type - <code>gre</code>, - <code>erspan</code> or <code>local</code>. + <code>erspan</code>, <code>local</code> or <code>lport</code>. </p> </column> @@ -3155,6 +3155,13 @@ or </p> </column> + <column name="mirror_rules"> + <p> + The value of this field represents the mirror rule for filtering + mirror traffic. + </p> + </column> + <column name="external_ids"> See <em>External IDs</em> at the beginning of this document. </column> @@ -3186,6 +3193,42 @@ or </group> </table> + <table name="Mirror_Rule" title="Mirror rule entry"> + <p> + Each row in this table represents a mirror rule that can be used + for filtering of <code>lport</code> mirrored traffic. + </p> + + <column name="match"> + <p> + The match expression, describing which packets should be executed + against Mirror Rule action. The Logical_Flow expression language is + used. + </p> + </column> + + <column name="action"> + <p>The action to take when the Mirror Rule matches:</p> + <ul> + <li> + <code>mirror</code>: Mirror the matched packet. + </li> + + <li> + <code>skip</code>: Do not mirror matched packet. + </li> + </ul> + </column> + + <column name="priority"> + <p> + The Mirror Rule priority. Rule with higher priority takes precedence + over those with lower. A rule is uniquely identified by the priority + and match string. + </p> + </column> + </table> + <table name="Meter" title="Meter entry"> <p> Each row in this table represents a meter that can be used for QoS or diff --git a/ovn-sb.ovsschema b/ovn-sb.ovsschema index c6058f42c..082c53247 100644 --- a/ovn-sb.ovsschema +++ b/ovn-sb.ovsschema @@ -1,7 +1,7 @@ { "name": "OVN_Southbound", - "version": "20.41.0", - "cksum": "2343742948 34719", + "version": "20.42.0", + "cksum": "1762078718 34867", "tables": { "SB_Global": { "columns": { @@ -156,7 +156,8 @@ "type": {"type": {"key": {"type": "string", "enum": ["set", ["gre", "erspan", - "local"]]}}}, + "local", + "lport"]]}}}, "index": {"type": "integer"}, "external_ids": { "type": {"key": "string", "value": "string", @@ -228,6 +229,7 @@ "minInteger": 1, "maxInteger": 32767}}}, "parent_port": {"type": {"key": "string", "min": 0, "max": 1}}, + "mirror_port": {"type": {"key": "string", "min": 0, "max": 1}}, "tag": { "type": {"key": {"type": "integer", "minInteger": 1, diff --git a/ovn-sb.xml b/ovn-sb.xml index 39acb81a4..5a85e3bfa 100644 --- a/ovn-sb.xml +++ b/ovn-sb.xml @@ -3076,7 +3076,7 @@ tcp.flags = RST; <column name="type"> <p> The value of this field specifies the mirror type - <code>gre</code>, - <code>erspan</code> or <code>local</code>. + <code>erspan</code>, <code>local</code> or <code>lport</code>. </p> </column> @@ -3335,6 +3335,10 @@ tcp.flags = RST; a <ref table="Encap"/> record. </column> + <column name="mirror_port"> + Points to mirror target port fot lport mirror type. + </column> + <column name="additional_encap"> Points to preferred encapsulation configuration to transmit logical dataplane packets to this additional chassis. The entry is diff --git a/tests/ovn-macros.at<http://ovn-macros.at/> b/tests/ovn-macros.at<http://ovn-macros.at/> index 6a568e743..9fea5274d 100644 --- a/tests/ovn-macros.at<http://ovn-macros.at/> +++ b/tests/ovn-macros.at<http://ovn-macros.at/> @@ -1408,11 +1408,11 @@ m4_define([OVN_CONTROLLER_EXIT], m4_define([OFTABLE_PHY_TO_LOG], [0]) m4_define([OFTABLE_LOG_INGRESS_PIPELINE], [8]) -m4_define([OFTABLE_OUTPUT_LARGE_PKT_DETECT], [40]) -m4_define([OFTABLE_OUTPUT_LARGE_PKT_PROCESS], [41]) -m4_define([OFTABLE_REMOTE_OUTPUT], [42]) -m4_define([OFTABLE_LOCAL_OUTPUT], [43]) -m4_define([OFTABLE_LOG_EGRESS_PIPELINE], [45]) +m4_define([OFTABLE_OUTPUT_LARGE_PKT_DETECT], [41]) +m4_define([OFTABLE_OUTPUT_LARGE_PKT_PROCESS], [42]) +m4_define([OFTABLE_REMOTE_OUTPUT], [43]) +m4_define([OFTABLE_LOCAL_OUTPUT], [44]) +m4_define([OFTABLE_LOG_EGRESS_PIPELINE], [46]) m4_define([OFTABLE_SAVE_INPORT], [64]) m4_define([OFTABLE_LOG_TO_PHY], [65]) m4_define([OFTABLE_MAC_BINDING], [66]) diff --git a/tests/ovn-nbctl.at<http://ovn-nbctl.at/> b/tests/ovn-nbctl.at<http://ovn-nbctl.at/> index 81ab4a47f..93e553d95 100644 --- a/tests/ovn-nbctl.at<http://ovn-nbctl.at/> +++ b/tests/ovn-nbctl.at<http://ovn-nbctl.at/> @@ -457,13 +457,15 @@ check ovn-nbctl ls-add sw0 check ovn-nbctl lsp-add sw0 sw0-port1 check ovn-nbctl lsp-add sw0 sw0-port2 check ovn-nbctl lsp-add sw0 sw0-port3 +check ovn-nbctl lsp-add sw0 sw0-port4 +check ovn-nbctl mirror-add mirror-lport lport to-lport sw0-port1 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([ovn-nbctl lsp-attach-mirror sw0-port5 mirror3], [1], [], [stderr]) AT_CHECK([grep 'port name not found' stderr], [0], [ignore]) dnl Attach source port to invalid mirror @@ -479,6 +481,11 @@ dnl Attach one more source port to mirror check ovn-nbctl lsp-attach-mirror sw0-port3 mirror3 check_column "$mirror3uuid" nb:Logical_Switch_Port mirror_rules name=sw0-port3 +mirror_lportuuid=$(fetch_column nb:Mirror _uuid name=mirror-lport) +dnl Attach source port to lport mirror +check ovn-nbctl lsp-attach-mirror sw0-port4 mirror-lport +check_column "$mirror_lportuuid" nb:Logical_Switch_Port mirror_rules name=sw0-port4 + dnl Verify if multiple ports are attached to the same mirror properly AT_CHECK([ovn-nbctl mirror-list], [0], [dnl mirror-local: @@ -486,6 +493,11 @@ mirror-local: Sink : 10.10.10.3 Filter : both +mirror-lport: + Type : lport + Sink : sw0-port1 + Filter : to-lport + mirror1: Type : gre Sink : 10.10.10.1 @@ -523,6 +535,11 @@ check_column "" nb:Logical_Switch_Port mirror_rules name=sw0-port1 dnl Check if the mirror deleted properly AT_CHECK([ovn-nbctl mirror-list], [0], [dnl +mirror-lport: + Type : lport + Sink : sw0-port1 + Filter : to-lport + mirror1: Type : gre Sink : 10.10.10.1 @@ -537,9 +554,12 @@ mirror2: ]) -dnl Delete another mirror +dnl Delete mirrors one more mirror check ovn-nbctl mirror-del mirror2 +dnl Delete one more mirror +check ovn-nbctl mirror-del mirror-lport + dnl Update the Sink address check ovn-nbctl set mirror . sink=192.168.1.13 @@ -559,6 +579,81 @@ AT_CHECK([ovn-nbctl mirror-list], [0], [dnl dnl --------------------------------------------------------------------- +OVN_NBCTL_TEST([ovn_nbctl_mirrors_rules], [mirror_rules], [ +check ovn-nbctl ls-add sw0 +check ovn-nbctl lsp-add sw0 sw0-p1 +check ovn-nbctl mirror-add mirror1 lport from-lport sw0-p1 +check ovn-nbctl mirror-add mirror2 lport to-lport sw0-p1 + +check ovn-nbctl mirror-rule-add mirror1 100 '1' mirror +check ovn-nbctl mirror-rule-add mirror2 150 'ip' mirror + +dnl Add mirror rule for non-exist mirror name +AT_CHECK([ovn-nbctl mirror-rule-add mirror5 150 'ip' allow], [1], [], [stderr]) +AT_CHECK([grep 'not found' stderr], [0], [ignore]) + +dnl Add same mirror rule for mirror +AT_CHECK([ovn-nbctl mirror-rule-add mirror2 150 'ip' mirror], [1], [], [stderr]) +AT_CHECK([grep 'exists' stderr], [0], [ignore]) + +AT_CHECK([ovn-nbctl mirror-list], [0], [dnl +mirror1: + Type : lport + Sink : sw0-p1 + Filter : from-lport + Rules : + 100 1 mirror + +mirror2: + Type : lport + Sink : sw0-p1 + Filter : to-lport + Rules : + 150 ip mirror + +]) + +dnl Add one more mirror rule to mirror +check ovn-nbctl mirror-rule-add mirror2 200 'icmp' mirror + +check ovn-nbctl mirror-rule-add mirror2 250 'icmp' skip + +dnl Mirror rule attach mirror +mirrorrule1uuid=$(fetch_column nb:Mirror_rule _uuid name=mirror1) +check_column "$mirrorrule1uuid" nb:Mirror mirror_rule mirror_rule="$mirrorrule1uuid" + +dnl Remove the mirror rule by name +check ovn-nbctl mirror-rule-del mirror1 + +dnl Mirror rule dettach mirror +mirrorr1uuid=$(fetch_column nb:Mirror _uuid name=mirror1) +check_column "" nb:Mirror mirror_rule mirror_rule="mirrorr1uuid" + +dnl Delete mirror rule by priority +check ovn-nbctl mirror-rule-del mirror2 200 + +dnl Remove the mirror rule by priority and match +check ovn-nbctl mirror-rule-del mirror2 250 'icmp' + +AT_CHECK([ovn-nbctl mirror-list], [0], [dnl +mirror1: + Type : lport + Sink : sw0-p1 + Filter : from-lport + +mirror2: + Type : lport + Sink : sw0-p1 + Filter : to-lport + Rules : + 150 ip mirror + +]) + +]) + +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<http://ovn-northd.at/> b/tests/ovn-northd.at<http://ovn-northd.at/> index 39b822952..d2ea41345 100644 --- a/tests/ovn-northd.at<http://ovn-northd.at/> +++ b/tests/ovn-northd.at<http://ovn-northd.at/> @@ -14895,3 +14895,308 @@ AT_CHECK([ovn-sbctl lflow-list sw | grep ls_out_acl_eval | grep priority=2002 | AT_CLEANUP ]) + +OVN_FOR_EACH_NORTHD_NO_HV([ +AT_SETUP([Check NB mirror rules sync]) +AT_KEYWORDS([mirror rules]) +ovn_start + +check ovn-nbctl ls-add ls1 +check ovn-nbctl lsp-add ls1 lport4 + +check ovn-nbctl mirror-add mirror1 lport to-lport lport4 + +check_column mirror1 nb:Mirror name +check_column lport nb:Mirror type +check_column to-lport nb:Mirror filter +check_column lport4 nb:Mirror sink + +check ovn-nbctl mirror-rule-add mirror1 100 'ip' skip + +check_column 100 nb:mirror_rule priority +check_column ip nb:mirror_rule match +check_column skip nb:mirror_rule action + +check ovn-nbctl set mirror_rule . priority=150 +check_column 150 nb:mirror_rule priority +check_column ip nb:mirror_rule match +check_column skip nb:mirror_rule action + +check ovn-nbctl set mirror_rule . match='icmp' +check_column 150 nb:mirror_rule priority +check_column icmp nb:mirror_rule match +check_column skip nb:mirror_rule action + +check ovn-nbctl set mirror_rule . action=mirror +check_column 150 nb:mirror_rule priority +check_column icmp nb:mirror_rule match +check_column mirror nb:mirror_rule action + +dnl Mirror rule attach mirror +mirrorrule1uuid=$(fetch_column nb:mirror_rule _uuid priority=100) +check_column "$mirrorrule1uuid" nb:Mirror mirror_rules mirror_rules="$mirrorrule1uuid" + +check ovn-nbctl mirror-rule-add mirror1 200 'ip' mirror +mirrorrule2uuid=$(fetch_column nb:mirror_rule _uuid priority=150) +check_column "$mirrorrule1uuid" nb:Mirror mirror_rules mirror_rules="$mirrorrule1uuid","$mirrorrule2uuid" + +check ovn-nbctl mirror-rule-del mirror1 +check_column "$mirrorrule1uuid" nb:Mirror mirror_rules mirror_rules="" + +AT_CLEANUP +]) + +OVN_FOR_EACH_NORTHD_NO_HV([ +AT_SETUP([Mirror rule lflows]) +AT_KEYWORDS([mirror rules]) +ovn_start + +check ovn-nbctl ls-add sw0 +check ovn-nbctl ls-add sw1 +check ovn-nbctl lsp-add sw0 sw0-p1 -- lsp-set-addresses sw0-p1 "50:54:00:00:00:01 10.0.0.11" +check ovn-nbctl lsp-add sw0 sw0-target0 +check ovn-nbctl lsp-add sw1 sw1-target1 + +check ovn-nbctl mirror-add mirror0 lport to-lport sw0-target0 +check ovn-nbctl mirror-add mirror1 lport to-lport sw0-target0 +check ovn-nbctl mirror-rule-add mirror1 100 'ip' mirror +check ovn-nbctl mirror-rule-add mirror1 150 'icmp' skip + +check ovn-nbctl mirror-add mirror2 lport from-lport sw1-target1 +check ovn-nbctl mirror-rule-add mirror2 100 '1' mirror + +ovn-sbctl lflow-list sw0 > lflow-list + +AT_CHECK([grep -e "ls_in_mirror" -e "ls_out_mirror" lflow-list | ovn_strip_lflows], [0], [dnl + table=??(ls_in_mirror ), priority=0 , match=(1), action=(next;) + table=??(ls_out_mirror ), priority=0 , match=(1), action=(next;) +]) + +check ovn-nbctl lsp-attach-mirror sw0-p1 mirror0 +check ovn-nbctl --wait=sb sync + +check_column sw0-target0 Port_Binding mirror_port logical_port=mp-sw0-sw0-target0 +check_column mirror Port_Binding type logical_port=mp-sw0-sw0-target0 + +ovn-sbctl lflow-list sw0 > lflow-list +AT_CHECK([grep -e "ls_in_mirror" -e "ls_out_mirror" lflow-list | ovn_strip_lflows], [0], [dnl + table=??(ls_in_mirror ), priority=0 , match=(1), action=(next;) + table=??(ls_out_mirror ), priority=0 , match=(1), action=(next;) + table=??(ls_out_mirror ), priority=100 , match=(outport == "sw0-p1"), action=(clone {outport = "mp-sw0-sw0-target0"; next(pipeline=ingress, table=??);}; next;) +]) + +ovn-sbctl lflow-list sw0 > lflow-list +AT_CHECK([grep -e "ls_out_pre_acl" lflow-list | ovn_strip_lflows], [0], [dnl + table=??(ls_out_pre_acl ), priority=0 , match=(1), action=(next;) + table=??(ls_out_pre_acl ), priority=110 , match=(eth.src == $svc_monitor_mac), action=(next;) + table=??(ls_out_pre_acl ), priority=65535, match=(outport == "mp-sw0-sw0-target0"), action=(next(pipeline=ingress, table=??);) +]) + +check ovn-nbctl lsp-attach-mirror sw0-p1 mirror1 + +AT_CHECK([ovn-sbctl list port_binding | grep mp-sw0-sw0-target0], [0], [dnl +logical_port : mp-sw0-sw0-target0 +]) + +ovn-sbctl lflow-list sw0 > lflow-list +AT_CHECK([grep -e "ls_in_mirror" -e "ls_out_mirror" lflow-list | ovn_strip_lflows], [0], [dnl + table=??(ls_in_mirror ), priority=0 , match=(1), action=(next;) + table=??(ls_out_mirror ), priority=0 , match=(1), action=(next;) + table=??(ls_out_mirror ), priority=100 , match=(outport == "sw0-p1"), action=(clone {outport = "mp-sw0-sw0-target0"; next(pipeline=ingress, table=??);}; next;) + table=??(ls_out_mirror ), priority=200 , match=(outport == "sw0-p1" && ip), action=(clone {outport = "mp-sw0-sw0-target0"; next(pipeline=ingress, table=??);}; next;) + table=??(ls_out_mirror ), priority=250 , match=(outport == "sw0-p1" && icmp), action=(next;) +]) + +check ovn-nbctl lsp-attach-mirror sw0-p1 mirror2 + +check_column sw1-target1 Port_Binding mirror_port logical_port=mp-sw0-sw1-target1 +check_column mirror Port_Binding type logical_port=mp-sw0-sw1-target1 + +ovn-sbctl lflow-list sw0 > lflow-list +AT_CHECK([grep -e "ls_in_mirror" -e "ls_out_mirror" lflow-list | ovn_strip_lflows], [0], [dnl + table=??(ls_in_mirror ), priority=0 , match=(1), action=(next;) + table=??(ls_in_mirror ), priority=100 , match=(inport == "sw0-p1"), action=(clone {outport = "mp-sw0-sw1-target1"; output;}; next;) + table=??(ls_in_mirror ), priority=200 , match=(inport == "sw0-p1" && 1), action=(clone {outport = "mp-sw0-sw1-target1"; output;}; next;) + table=??(ls_out_mirror ), priority=0 , match=(1), action=(next;) + table=??(ls_out_mirror ), priority=100 , match=(outport == "sw0-p1"), action=(clone {outport = "mp-sw0-sw0-target0"; next(pipeline=ingress, table=??);}; next;) + table=??(ls_out_mirror ), priority=200 , match=(outport == "sw0-p1" && ip), action=(clone {outport = "mp-sw0-sw0-target0"; next(pipeline=ingress, table=??);}; next;) + table=??(ls_out_mirror ), priority=250 , match=(outport == "sw0-p1" && icmp), action=(next;) +]) + +ovn-sbctl lflow-list sw0 > lflow-list +AT_CHECK([grep -e "ls_out_pre_acl" lflow-list | ovn_strip_lflows], [0], [dnl + table=??(ls_out_pre_acl ), priority=0 , match=(1), action=(next;) + table=??(ls_out_pre_acl ), priority=110 , match=(eth.src == $svc_monitor_mac), action=(next;) + table=??(ls_out_pre_acl ), priority=65535, match=(outport == "mp-sw0-sw0-target0"), action=(next(pipeline=ingress, table=??);) +]) + +ovn-sbctl lflow-list sw1 > lflow-list +AT_CHECK([grep -e "ls_out_pre_acl" lflow-list | ovn_strip_lflows], [0], [dnl + table=??(ls_out_pre_acl ), priority=0 , match=(1), action=(next;) + table=??(ls_out_pre_acl ), priority=110 , match=(eth.src == $svc_monitor_mac), action=(next;) + table=??(ls_out_pre_acl ), priority=65535, match=(outport == "mp-sw0-sw1-target1"), action=(next(pipeline=ingress, table=??);) +]) + +check ovn-nbctl lsp-del sw1-target1 + +AT_CHECK([ovn-sbctl list port_binding | grep mp-sw0-sw1-target1], [1], [dnl +]) + +ovn-sbctl lflow-list sw0 > lflow-list +AT_CHECK([grep -e "ls_in_mirror" -e "ls_out_mirror" lflow-list | ovn_strip_lflows], [0], [dnl + table=??(ls_in_mirror ), priority=0 , match=(1), action=(next;) + table=??(ls_out_mirror ), priority=0 , match=(1), action=(next;) + table=??(ls_out_mirror ), priority=100 , match=(outport == "sw0-p1"), action=(clone {outport = "mp-sw0-sw0-target0"; next(pipeline=ingress, table=??);}; next;) + table=??(ls_out_mirror ), priority=200 , match=(outport == "sw0-p1" && ip), action=(clone {outport = "mp-sw0-sw0-target0"; next(pipeline=ingress, table=??);}; next;) + table=??(ls_out_mirror ), priority=250 , match=(outport == "sw0-p1" && icmp), action=(next;) +]) + +ovn-sbctl lflow-list sw1 > lflow-list +AT_CHECK([grep -e "ls_in_pre_acl" lflow-list | ovn_strip_lflows], [0], [dnl + table=??(ls_in_pre_acl ), priority=0 , match=(1), action=(next;) + table=??(ls_in_pre_acl ), priority=110 , match=(eth.dst == $svc_monitor_mac), action=(next;) +]) + +check ovn-nbctl lsp-add sw1 sw1-target1 +ovn-sbctl lflow-list sw0 > lflow-list + +AT_CHECK([grep -e "ls_in_mirror" -e "ls_out_mirror" lflow-list | ovn_strip_lflows], [0], [dnl + table=??(ls_in_mirror ), priority=0 , match=(1), action=(next;) + table=??(ls_in_mirror ), priority=100 , match=(inport == "sw0-p1"), action=(clone {outport = "mp-sw0-sw1-target1"; output;}; next;) + table=??(ls_in_mirror ), priority=200 , match=(inport == "sw0-p1" && 1), action=(clone {outport = "mp-sw0-sw1-target1"; output;}; next;) + table=??(ls_out_mirror ), priority=0 , match=(1), action=(next;) + table=??(ls_out_mirror ), priority=100 , match=(outport == "sw0-p1"), action=(clone {outport = "mp-sw0-sw0-target0"; next(pipeline=ingress, table=??);}; next;) + table=??(ls_out_mirror ), priority=200 , match=(outport == "sw0-p1" && ip), action=(clone {outport = "mp-sw0-sw0-target0"; next(pipeline=ingress, table=??);}; next;) + table=??(ls_out_mirror ), priority=250 , match=(outport == "sw0-p1" && icmp), action=(next;) +]) + +check ovn-nbctl mirror-del +check ovn-nbctl --wait=sb sync + +ovn-sbctl lflow-list sw0 > lflow-list +AT_CHECK([grep -e "ls_in_mirror" -e "ls_out_mirror" lflow-list | ovn_strip_lflows], [0], [dnl + table=??(ls_in_mirror ), priority=0 , match=(1), action=(next;) + table=??(ls_out_mirror ), priority=0 , match=(1), action=(next;) +]) + +ovn-sbctl lflow-list sw0 > lflow-list +AT_CHECK([grep -e "ls_in_pre_acl" lflow-list | ovn_strip_lflows], [0], [dnl + table=??(ls_in_pre_acl ), priority=0 , match=(1), action=(next;) + table=??(ls_in_pre_acl ), priority=110 , match=(eth.dst == $svc_monitor_mac), action=(next;) +]) + +ovn-sbctl lflow-list sw1 > lflow-list +AT_CHECK([grep -e "ls_in_pre_acl" lflow-list | ovn_strip_lflows], [0], [dnl + table=??(ls_in_pre_acl ), priority=0 , match=(1), action=(next;) + table=??(ls_in_pre_acl ), priority=110 , match=(eth.dst == $svc_monitor_mac), action=(next;) +]) + +AT_CLEANUP +]) + +OVN_FOR_EACH_NORTHD_NO_HV([ +AT_SETUP([ovn-detrace mirror rule check]) +AT_KEYWORDS([mirror rules]) +ovn_start + +check ovn-nbctl ls-add sw0 +check ovn-nbctl lsp-add sw0 sw0-p1 +check ovn-nbctl lsp-add sw0 sw0-rp + +check ovn-nbctl lsp-set-type sw0-rp router +check ovn-nbctl lsp-set-addresses sw0-rp router +check ovn-nbctl lsp-set-options sw0-rp router-port=lrp1 + +check ovn-nbctl lsp-set-addresses sw0-p1 '00:00:00:00:00:01 1.1.1.1' + +check ovn-nbctl ls-add sw1 +check ovn-nbctl lsp-add sw1 sw1-target +check ovn-nbctl lsp-add sw0 sw0-target + +check ovn-nbctl lr-add lr1 +check ovn-nbctl lrp-add lr1 lrp1 00:00:00:00:00:02 1.1.1.2/24<http://1.1.1.2/24> + +check ovn-nbctl mirror-add mirror1 lport from-lport sw1-target +check ovn-nbctl mirror-rule-add mirror1 100 '1' mirror + +check ovn-nbctl lsp-attach-mirror sw0-p1 mirror1 + +check ovn-nbctl --wait=hv sync + +AT_CHECK([ovn-sbctl list port_binding | grep mp-sw0-sw1-target ], [0], [dnl +logical_port : mp-sw0-sw1-target +]) + +ovn-sbctl lflow-list sw0 > lflow-list +AT_CHECK([grep -e "ls_in_mirror" lflow-list | ovn_strip_lflows], [0], [dnl + table=??(ls_in_mirror ), priority=0 , match=(1), action=(next;) + table=??(ls_in_mirror ), priority=100 , match=(inport == "sw0-p1"), action=(clone {outport = "mp-sw0-sw1-target"; output;}; next;) + table=??(ls_in_mirror ), priority=200 , match=(inport == "sw0-p1" && 1), action=(clone {outport = "mp-sw0-sw1-target"; output;}; next;) +]) + +AT_CHECK([ovn_trace --minimal 'inport == "sw0-p1" && eth.src == 00:00:00:00:00:01 && eth.dst == 00:00:00:00:00:02 && ip4.dst == 1.1.1.2 && ip4.src == 1.1.1.1 && ip.ttl == 64' > trace], [0], [ignore]) + +AT_CHECK([cat trace | head -n 3], [0], [dnl +clone { + output("mp-sw0-sw1-target"); +}; +]) + +check ovn-nbctl mirror-rule-add mirror1 300 'arp' mirror +check ovn-nbctl mirror-rule-add mirror1 250 '1' skip + +check ovn-nbctl --wait=hv sync + +ovn-sbctl lflow-list sw0 > lflow-list +AT_CHECK([grep -e "ls_in_mirror" lflow-list | ovn_strip_lflows], [0], [dnl + table=??(ls_in_mirror ), priority=0 , match=(1), action=(next;) + table=??(ls_in_mirror ), priority=100 , match=(inport == "sw0-p1"), action=(clone {outport = "mp-sw0-sw1-target"; output;}; next;) + table=??(ls_in_mirror ), priority=200 , match=(inport == "sw0-p1" && 1), action=(clone {outport = "mp-sw0-sw1-target"; output;}; next;) + table=??(ls_in_mirror ), priority=350 , match=(inport == "sw0-p1" && 1), action=(next;) + table=??(ls_in_mirror ), priority=400 , match=(inport == "sw0-p1" && arp), action=(clone {outport = "mp-sw0-sw1-target"; output;}; next;) +]) + +AT_CHECK([ovn_trace --minimal 'inport == "sw0-p1" && eth.src == 00:00:00:00:00:01 && eth.dst == 00:00:00:00:00:02 && ip4.dst == 1.1.1.2 && ip4.src == 1.1.1.1 && ip.ttl == 64' > trace], [0], [ignore]) + +AT_CHECK([cat trace], [0], [dnl +icmp4 { + eth.dst = 00:00:00:00:00:01; + eth.src = 00:00:00:00:00:02; + ip4.dst = 1.1.1.1; + ip4.src = 1.1.1.2; + ip.ttl = 255; + icmp4.type = 3; + icmp4.code = 2; + ip.ttl--; + eth.src = 00:00:00:00:00:02; + eth.dst = 00:00:00:00:00:01; + output("sw0-p1"); +}; + ip.ttl = 255; + icmp4.type = 3; + icmp4.code = 2; + ip.ttl--; + eth.src = 00:00:00:00:00:02; + eth.dst = 00:00:00:00:00:01; + output("sw0-p1"); +}; +]) + +check ovn-nbctl mirror-del mirror1 + +check ovn-nbctl --wait=hv sync + +AT_CHECK([ovn_trace --minimal 'inport == "sw0-p1" && eth.src == 00:00:00:00:00:01 && eth.dst == 00:00:00:00:00:02 && ip4.dst == 1.1.1.2 && ip4.src == 1.1.1.1 && ip.ttl == 64' > trace], [0], [ignore]) + +ovn-sbctl lflow-list sw0 > lflow-list +AT_CHECK([grep -e "ls_in_mirror" lflow-list | ovn_strip_lflows], [0], [dnl + table=??(ls_in_mirror ), priority=0 , match=(1), action=(next;) +]) + +AT_CHECK([cat trace | grep output], [0], [dnl + output("sw0-p1"); + output("sw0-p1"); +]) + +AT_CLEANUP +]) diff --git a/tests/ovn.at<http://ovn.at/> b/tests/ovn.at<http://ovn.at/> index d105ed253..8cc1d6b9f 100644 --- a/tests/ovn.at<http://ovn.at/> +++ b/tests/ovn.at<http://ovn.at/> @@ -18302,6 +18302,182 @@ OVN_CLEANUP([hv1],[hv2]) AT_CLEANUP ]) +OVN_FOR_EACH_NORTHD([ +AT_SETUP([Mirror - lport: 2 HVs, 2 LS, 1 lport/LS, 2 peer LRs]) +ovn_start + +# Logical network: +# Two LRs - R1 and R2 that are connected to each other as peers in 20.0.0.0/24<http://20.0.0.0/24> +# network. R1 has a switchs ls1 (191.168.1.0/24<http://191.168.1.0/24>) connected to it. +# R2 has ls2 (172.16.1.0/24<http://172.16.1.0/24>) connected to it. + +ls1_lp1_mac="f0:00:00:01:02:03" +ls2_lp2_mac="f0:00:00:01:02:05" +rp_ls1_mac="00:00:00:01:02:03" +rp_ls2_mac="00:00:00:01:02:04" +ls2_lp1_mac="f0:00:00:01:02:04" + +ls1_lp1_ip="192.168.1.2" +ls2_lp1_ip="172.16.1.2" +ls2_lp2_ip="172.16.1.3" + +check ovn-nbctl lr-add R1 +check ovn-nbctl lr-add R2 + +check ovn-nbctl ls-add ls1 +check ovn-nbctl ls-add ls2 + +# Connect ls1 to R1 +check ovn-nbctl lrp-add R1 ls1 $rp_ls1_mac 192.168.1.1/24<http://192.168.1.1/24> + +check ovn-nbctl lsp-add ls1 rp-ls1 -- set Logical_Switch_Port rp-ls1 type=router \ + options:router-port=ls1 addresses=\"$rp_ls1_mac\" + +# Connect ls2 to R2 +check ovn-nbctl lrp-add R2 ls2 $rp_ls2_mac 172.16.1.1/24<http://172.16.1.1/24> + +check ovn-nbctl lsp-add ls2 rp-ls2 -- set Logical_Switch_Port rp-ls2 type=router \ + options:router-port=ls2 addresses=\"$rp_ls2_mac\" + +# Connect R1 to R2 +check ovn-nbctl lrp-add R1 R1_R2 00:00:00:02:03:04 20.0.0.1/24<http://20.0.0.1/24> peer=R2_R1 +check ovn-nbctl lrp-add R2 R2_R1 00:00:00:02:03:05 20.0.0.2/24<http://20.0.0.2/24> peer=R1_R2 + +check ovn-nbctl lr-route-add R1 "0.0.0.0/0<http://0.0.0.0/0>" 20.0.0.2 +check ovn-nbctl lr-route-add R2 "0.0.0.0/0<http://0.0.0.0/0>" 20.0.0.1 + +# Create logical port ls1-lp1 in ls1 +check ovn-nbctl lsp-add ls1 ls1-lp1 \ +-- lsp-set-addresses ls1-lp1 "$ls1_lp1_mac $ls1_lp1_ip" + +# Create logical port ls2-lp1 in ls2 +check ovn-nbctl lsp-add ls2 ls2-lp1 \ +-- lsp-set-addresses ls2-lp1 "$ls2_lp1_mac $ls2_lp1_ip" + +# Create logical port ls2-lp2 in ls2 +check ovn-nbctl lsp-add ls2 ls2-lp2 \ +-- lsp-set-addresses ls2-lp2 "$ls2_lp2_mac $ls2_lp2_ip" + +# Create two hypervisor and create OVS ports corresponding to logical ports. +net_add n1 + +sim_add hv1 +as hv1 +ovs-vsctl add-br br-phys +ovn_attach n1 br-phys 192.168.0.1 +ovs-vsctl -- add-port br-int hv1-vif1 -- \ + set interface hv1-vif1 external-ids:iface-id=ls1-lp1 \ + options:tx_pcap=hv1/vif1-tx.pcap \ + options:rxq_pcap=hv1/vif1-rx.pcap \ + ofport-request=1 + +sim_add hv2 +as hv2 +ovs-vsctl add-br br-phys +ovn_attach n1 br-phys 192.168.0.2 +ovs-vsctl -- add-port br-int hv2-vif1 -- \ + set interface hv2-vif1 external-ids:iface-id=ls2-lp1 \ + options:tx_pcap=hv2/vif1-tx.pcap \ + options:rxq_pcap=hv2/vif1-rx.pcap \ + ofport-request=2 + +sim_add hv3 +as hv3 +ovs-vsctl add-br br-phys +ovn_attach n1 br-phys 192.168.0.3 +ovs-vsctl -- add-port br-int hv3-vif1 -- \ + set interface hv3-vif1 external-ids:iface-id=ls2-lp2 \ + options:tx_pcap=hv3/vif1-tx.pcap \ + options:rxq_pcap=hv3/vif1-rx.pcap \ + ofport-request=3 + +# Pre-populate the hypervisors' ARP tables so that we don't lose any +# packets for ARP resolution (native tunneling doesn't queue packets +# for ARP resolution). +OVN_POPULATE_ARP + +# Allow some time for ovn-northd and ovn-controller to catch up. +wait_for_ports_up +check ovn-nbctl --wait=hv sync + +# Packet to send. +packet="inport==\"ls1-lp1\" && eth.src==$ls1_lp1_mac && eth.dst==$rp_ls1_mac && + ip4 && ip.ttl==64 && ip4.src==$ls1_lp1_ip && ip4.dst==$ls2_lp1_ip && + udp && udp.src==53 && udp.dst==4369" +OVS_WAIT_UNTIL([as hv1 ovs-appctl -t ovn-controller inject-pkt "$packet"]) + +echo "---------NB dump-----" +ovn-nbctl show +echo "---------------------" +ovn-nbctl list logical_router +echo "---------------------" +ovn-nbctl list logical_router_port +echo "---------------------" + +echo "---------SB dump-----" +ovn-sbctl list datapath_binding +echo "---------------------" +ovn-sbctl list port_binding +echo "---------------------" + +echo "------ hv1 dump ----------" +as hv1 ovs-ofctl show br-int +as hv1 ovs-ofctl dump-flows br-int +echo "------ hv2 dump ----------" +as hv2 ovs-ofctl show br-int +as hv2 ovs-ofctl dump-flows br-int + + +# Packet to Expect +# The TTL should be decremented by 2. +packet="eth.src==$rp_ls2_mac && eth.dst==$ls2_lp1_mac && + ip4 && ip.ttl==62 && ip4.src==$ls1_lp1_ip && ip4.dst==$ls2_lp1_ip && + udp && udp.src==53 && udp.dst==4369" +echo $packet | ovstest test-ovn expr-to-packets > expected + +OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected]) + +check ovn-nbctl mirror-add mirror0 lport from-lport ls2-lp2 +check ovn-nbctl lsp-attach-mirror ls1-lp1 mirror0 + +check ovn-nbctl --wait=hv sync + +AT_CHECK([ovn-sbctl list port_binding | grep mp-ls1-ls2-lp2], [0], [dnl +logical_port : mp-ls1-ls2-lp2 +]) + +hv3_uuid=$(fetch_column sb:Chassis _uuid name=hv3) +check_column "$hv3_uuid" sb:Port_Binding chassis logical_port=mp-ls1-ls2-lp2 + +AT_CHECK([ovn-sbctl lflow-list ls1 | grep mirror| ovn_strip_lflows], [0], [dnl + table=??(ls_in_mirror ), priority=0 , match=(1), action=(next;) + table=??(ls_in_mirror ), priority=100 , match=(inport == "ls1-lp1"), action=(clone {outport = "mp-ls1-ls2-lp2"; output;}; next;) + table=??(ls_out_mirror ), priority=0 , match=(1), action=(next;) +]) + +# Packet to send. +packet="inport==\"ls1-lp1\" && eth.src==$ls1_lp1_mac && eth.dst==$rp_ls1_mac && + ip4 && ip.ttl==64 && ip4.src==$ls1_lp1_ip && ip4.dst==$ls2_lp1_ip && + udp && udp.src==53 && udp.dst==4369" +OVS_WAIT_UNTIL([as hv1 ovs-appctl -t ovn-controller inject-pkt "$packet"]) + +echo $packet | ovstest test-ovn expr-to-packets > expected + +OVN_CHECK_PACKETS([hv3/vif1-tx.pcap], [expected]) + +check ovn-nbctl lsp-detach-mirror ls1-lp1 mirror0 + +check ovn-nbctl --wait=hv sync + +AT_CHECK([ovn-sbctl lflow-list ls1 | grep mirror| ovn_strip_lflows], [0], [dnl + table=??(ls_in_mirror ), priority=0 , match=(1), action=(next;) + table=??(ls_out_mirror ), priority=0 , match=(1), action=(next;) +]) + +OVN_CLEANUP([hv1],[hv2]) +AT_CLEANUP +]) + OVN_FOR_EACH_NORTHD([ AT_SETUP([Port Groups]) AT_KEYWORDS([ovnpg]) diff --git a/utilities/ovn-nbctl.c b/utilities/ovn-nbctl.c index e94658371..9d15faee6 100644 --- a/utilities/ovn-nbctl.c +++ b/utilities/ovn-nbctl.c @@ -298,9 +298,10 @@ QoS commands:\n\ qos-list SWITCH print QoS rules for SWITCH\n\ \n\ Mirror commands:\n\ - mirror-add NAME TYPE [INDEX] FILTER {IP | MIRROR-ID} \n\ + mirror-add NAME TYPE [INDEX] FILTER {IP | MIRROR-ID| TARGET-PORT} \n\ add a mirror with given name\n\ - specify TYPE 'gre', 'erspan', or 'local'\n\ + specify TYPE 'gre', 'erspan', 'local'\n\ + or 'lport'.\n\ specify the tunnel INDEX value\n\ (indicates key if GRE\n\ erpsan_idx if ERSPAN)\n\ @@ -309,8 +310,16 @@ Mirror commands:\n\ specify Sink / Destination i.e. Remote IP, or a\n\ local interface with external-ids:mirror-id\n\ matching MIRROR-ID\n\ + In case of lport type specify logical switch\n\ + port, which is a mirror target.\n\ mirror-del [NAME] remove mirrors\n\ mirror-list print mirrors\n\ + mirror-rule-add MIRROR-NAME PRIORITY MATCH ACTION \n\ + add a mirror rule selection to given lport\n\ + mirror.\n\ + specify MATCH for selecting mirrored traffic.\n\ + specify ACTION 'mirror' or 'skip'.\n\ + mirror-rule-del MIRROR-NAME [PRIORITY | MATCH] remove mirrors\n\ \n\ Meter commands:\n\ [--fair]\n\ @@ -1821,11 +1830,11 @@ nbctl_lsp_attach_mirror(struct ctl_context *ctx) return; } + bool may_exist = shash_find(&ctx->options, "--may-exist") != NULL; /* Check if same mirror rule already exists for the lsp */ for (size_t i = 0; i < lsp->n_mirror_rules; i++) { if (uuid_equals(&lsp->mirror_rules[i]->header_.uuid, &mirror->header_.uuid)) { - bool may_exist = shash_find(&ctx->options, "--may-exist") != NULL; if (!may_exist) { ctl_error(ctx, "mirror %s is already attached to the " "logical port %s.", @@ -7772,16 +7781,18 @@ static char * OVS_WARN_UNUSED_RESULT parse_mirror_type(const char *arg, const char **type_p) { /* Validate type. Only require the first letter. */ - if (arg[0] == 'g') { + if (!strcmp(arg, "gre")) { *type_p = "gre"; - } else if (arg[0] == 'e') { + } else if (!strcmp(arg, "erspan")) { *type_p = "erspan"; - } else if (arg[0] == 'l') { + } else if (!strcmp(arg, "local")) { *type_p = "local"; + } else if (!strcmp(arg, "lport")) { + *type_p = "lport"; } else { *type_p = NULL; return xasprintf("%s: type must be \"gre\", " - "\"erspan\", or \"local\"", arg); + "\"erspan\", or \"local\" or \"lport\"", arg); } return NULL; } @@ -7794,6 +7805,8 @@ nbctl_pre_mirror_add(struct ctl_context *ctx) 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_mirror_rules); + ovsdb_idl_add_column(ctx->idl, &nbrec_logical_switch_port_col_name); } static void @@ -7818,14 +7831,17 @@ nbctl_mirror_add(struct ctl_context *ctx) } } - /* Type - gre/erspan/local */ + /* Type - gre/erspan/local/lport */ error = parse_mirror_type(ctx->argv[pos++], &type); if (error) { ctx->error = error; return; } - if (strcmp(type, "local")) { + int is_local = !strcmp(type, "local"); + int is_lport = !strcmp(type, "lport"); + + if (!is_local && !is_lport) { /* tunnel index / GRE key / ERSPAN idx */ if (!str_to_long(ctx->argv[pos++], 10, (long int *) &index)) { ctl_error(ctx, "Invalid Index"); @@ -7844,7 +7860,7 @@ nbctl_mirror_add(struct ctl_context *ctx) sink = ctx->argv[pos++]; /* check if it is a valid ip unless it is type 'local' */ - if (strcmp(type, "local")) { + if (!is_local && !is_lport) { char *new_sink_ip = normalize_ipv4_addr_str(sink); if (!new_sink_ip) { new_sink_ip = normalize_ipv6_addr_str(sink); @@ -7857,6 +7873,21 @@ nbctl_mirror_add(struct ctl_context *ctx) free(new_sink_ip); } + /* Check if it is an existing port for lport mirror type. */ + if (is_lport) { + const struct nbrec_logical_switch_port *lsp; + error = lsp_by_name_or_uuid(ctx, sink, false, &lsp); + if (error) { + ctx->error = error; + return; + } + + if (!lsp) { + VLOG_WARN("Attaching target to unexisting port with name %s.", + sink); + } + } + /* Create the mirror. */ struct nbrec_mirror *mirror = nbrec_mirror_insert(ctx->txn); nbrec_mirror_set_name(mirror, name); @@ -7896,6 +7927,143 @@ nbctl_mirror_del(struct ctl_context *ctx) } } +static int +rule_cmp(const void *mirror1_, const void *mirror2_) +{ + const struct nbrec_mirror_rule *const *mirror_1 = mirror1_; + const struct nbrec_mirror_rule *const *mirror_2 = mirror2_; + + const struct nbrec_mirror_rule *mirror1 = *mirror_1; + const struct nbrec_mirror_rule *mirror2 = *mirror_2; + + int result = mirror1->priority - mirror2->priority; + if (result) { + return result; + } + + result = strcmp(mirror1->match, mirror2->match); + if (result) { + return result; + } + + return strcmp(mirror1->action, mirror2->action); +} + +static void +nbctl_pre_mirror_rule_add(struct ctl_context *ctx) +{ + ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_name); + ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_mirror_rules); + ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_rule_col_match); + ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_rule_col_action); + ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_rule_col_priority); +} + +static void +nbctl_mirror_rule_add(struct ctl_context *ctx) +{ + const struct nbrec_mirror_rule *mirror_rule; + const struct nbrec_mirror *mirror; + int64_t priority = 0; + char *error; + + error = mirror_by_name_or_uuid(ctx, ctx->argv[1], true, &mirror); + if (error) { + ctx->error = error; + return; + } + + error = parse_priority(ctx->argv[2], &priority); + if (error) { + ctx->error = error; + return; + } + + const char *action = ctx->argv[4]; + if (strcmp(action, "mirror") && strcmp(action, "skip")) { + ctl_error(ctx, "%s: action must be one of \"mirror\", \"skip\"", + action); + } + + mirror_rule = nbrec_mirror_rule_insert(ctx->txn); + nbrec_mirror_rule_set_match(mirror_rule, ctx->argv[3]); + nbrec_mirror_rule_set_action(mirror_rule, action); + nbrec_mirror_rule_set_priority(mirror_rule, priority); + + bool may_exist = shash_find(&ctx->options, "--may-exist") != NULL; + /* Check if same mirror rule exists for this mirror. */ + for (size_t i = 0; i < mirror->n_mirror_rules; i++) { + if (!rule_cmp(&mirror_rule, &mirror->mirror_rules[i])) { + if (!may_exist) { + ctl_error(ctx, "Same mirror-rule already exists on the " + "mirror %s.", ctx->argv[1]); + } else { + nbrec_mirror_rule_delete(mirror_rule); + } + return; + } + } + + /* Insert mirror rule to mirror. */ + nbrec_mirror_update_mirror_rules_addvalue(mirror, mirror_rule); +} + +static void +nbctl_pre_mirror_rule_del(struct ctl_context *ctx) +{ + ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_name); + ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_mirror_rules); + ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_rule_col_action); + ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_rule_col_priority); + ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_rule_col_match); +} + +static void +nbctl_mirror_rule_del(struct ctl_context *ctx) +{ + const struct nbrec_mirror *mirror = NULL; + int64_t priority = 0; + char *error = NULL; + + error = mirror_by_name_or_uuid(ctx, ctx->argv[1], true, &mirror); + if (error) { + ctx->error = error; + return; + } + + if (ctx->argc == 2) { + for (size_t i = 0; i < mirror->n_mirror_rules; i++) { + nbrec_mirror_update_mirror_rules_delvalue(mirror, + mirror->mirror_rules[i]); + } + return; + } + + error = parse_priority(ctx->argv[2], &priority); + if (error) { + ctx->error = error; + return; + } + + if (ctx->argc == 3) { + for (size_t i = 0; i < mirror->n_mirror_rules; i++) { + struct nbrec_mirror_rule *rule = mirror->mirror_rules[i]; + if (priority == rule->priority) { + nbrec_mirror_update_mirror_rules_delvalue(mirror, rule); + return; + } + } + } + + for (size_t i = 0; i < mirror->n_mirror_rules; i++) { + struct nbrec_mirror_rule *rule = mirror->mirror_rules[i]; + if (priority == rule->priority && !strcmp(ctx->argv[3], + rule->match)) { + nbrec_mirror_update_mirror_rules_delvalue(mirror, rule); + } + } +} + static void nbctl_pre_mirror_list(struct ctl_context *ctx) { @@ -7905,6 +8073,10 @@ nbctl_pre_mirror_list(struct ctl_context *ctx) 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_mirror_rules); + ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_rule_col_action); + ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_rule_col_match); + ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_rule_col_priority); } static void @@ -7931,15 +8103,27 @@ nbctl_mirror_list(struct ctl_context *ctx) for (size_t i = 0; i < n_mirrors; i++) { mirror = mirrors[i]; + bool is_lport = !strcmp(mirror->type, "lport"); + bool is_local = !strcmp(mirror->type, "local"); + 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); - if (strcmp(mirror->type, "local")) { + if (!is_local && !is_lport) { ds_put_format(&ctx->output, " Index/Key: %"PRId64"\n", mirror->index); } + if (mirror->n_mirror_rules) { + ds_put_cstr(&ctx->output, " Rules :\n"); + for (size_t j = 0; j < mirror->n_mirror_rules; j++) { + ds_put_format(&ctx->output, " %5"PRId64" %30s %15s\n", + mirror->mirror_rules[j]->priority, + mirror->mirror_rules[j]->match, + mirror->mirror_rules[j]->action); + } + } ds_put_cstr(&ctx->output, "\n"); } @@ -8044,13 +8228,19 @@ static const struct ctl_command_syntax nbctl_commands[] = { /* mirror commands. */ { "mirror-add", 4, 5, - "NAME TYPE INDEX FILTER IP", + "NAME TYPE INDEX FILTER SINK", 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 }, + /* Mirror rule commands. */ + { "mirror-rule-add", 4, 4, "MIRROR-NAME PRIORITY MATCH ACTION", + nbctl_pre_mirror_rule_add, nbctl_mirror_rule_add, NULL, "", RW}, + { "mirror-rule-del", 1, 3, "MIRROR-NAME [PRIORITY MATCH]", + nbctl_pre_mirror_rule_del, nbctl_mirror_rule_del, NULL, "", RW }, + /* meter commands. */ { "meter-add", 4, 5, "NAME ACTION RATE UNIT [BURST]", nbctl_pre_meter_add, nbctl_meter_add, NULL, "--fair,--may-exist", RW }, -- 2.48.1 _______________________________________________ dev mailing list [email protected]<mailto:[email protected]> https://mail.openvswitch.org/mailman/listinfo/ovs-dev Thanks, Ales _______________________________________________ dev mailing list [email protected] https://mail.openvswitch.org/mailman/listinfo/ovs-dev
