On Mon, Apr 14, 2025 at 11:48 AM Rukomoinikova Aleksandra <ARukomoinikova@k2.cloud> wrote: > > > Hi Numan, > sent version 10, but regarding your comment about iterating by indexes > > +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; > you suggested iterating on the port and get a mirror from there, but > this is necessary for the target port, that is, none of the mirrors are > attached to it, we cannot do this, so in v10 i left everything as is.
Hi, I meant to do something like this (I haven't tested it). -------------------- static bool is_lsp_mirror_target_port(struct ovsdb_idl_index *nbrec_mirror_by_type_and_sink, struct ovn_port *port) { struct nbrec_mirror *target = nbrec_mirror_index_init_row(nbrec_mirror_by_type_and_sink); nbrec_mirror_index_set_type(target, "lport"); nbrec_mirror_index_set_sink(target, port->key); const struct nbrec_mirror *nb_mirror = nbrec_mirror_index_find(nbrec_mirror_by_type_and_sink, target); nbrec_mirror_index_destroy_row(target); if (nb_mirror) { return true; } return false; } ------------------------------- You need to create ovsdb_idl_index for this. There are many examples in inc_proc_northd_init() which you can refer to. Thanks Numan > > On 14.04.2025 12:08, Rukomoinikova Aleksandra wrote: > > Hi again, I haven't answered one more question, I'll answer below > > > > > > On 11.04.2025 19:19, Numan Siddique wrote: > >> On Fri, Apr 4, 2025 at 7:03 AM Alexandra Rukomoinikova > >> <arukomoinikova@k2.cloud> 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. > >>> > >>> Added ls_in_mirror and ls_out_mirror stages in ls pipeline. > >>> > >>> 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. > >>> > >>> For lport mirroring, logical flows (flows) are generated to handle > >>> traffic processing based on filtering criteria. Packets matching these > >>> criteria are cloned 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 <arukomoinikova@k2.cloud> > >>> Signed-off-by: Vladislav Odintsov <vlodintsov@k2.cloud> > >>> Co-authored-by: Vladislav Odintsov <vlodintsov@k2.cloud> > >>> Tested-by: Ivan Burnin <iburnin@k2.cloud> > >>> --- > >>> v8 --> v9: > >>> split the patch into separate parts > >>> fixed Numan's comments about test > >>> rebased on main > >> > >> Thanks for the v9. Please see below for a few comments. > >> Looks like you missed out addressing the comments from the previous > >> version. > >> > >> Also can you please rebase the series as patch 3 has conflicts. > >> > >> Thanks > >> Numan > >> > >>> --- > >>> controller/lflow.h | 12 +- > >>> include/ovn/actions.h | 10 ++ > >>> lib/actions.c | 72 +++++++++ > >>> lib/ovn-util.c | 8 + > >>> lib/ovn-util.h | 6 +- > >>> northd/en-northd.c | 2 + > >>> northd/inc-proc-northd.c | 2 + > >>> northd/northd.c | 257 +++++++++++++++++++++++++++++- > >>> northd/northd.h | 77 +++++---- > >>> northd/ovn-northd.8.xml | 120 ++++++++++---- > >>> ovn-sb.ovsschema | 5 +- > >>> ovn-sb.xml | 18 +++ > >>> tests/ovn-macros.at | 10 +- > >>> tests/ovn-northd.at | 328 > >>> +++++++++++++++++++++++++++++++++++++++ > >>> tests/ovn.at | 8 + > >>> utilities/ovn-trace.c | 42 +++++ > >>> 16 files changed, 893 insertions(+), 84 deletions(-) > >>> > >>> diff --git a/controller/lflow.h b/controller/lflow.h > >>> index fa99173e6..40fd0ae60 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/include/ovn/actions.h b/include/ovn/actions.h > >>> index 0993ad0e5..c504c371f 100644 > >>> --- a/include/ovn/actions.h > >>> +++ b/include/ovn/actions.h > >>> @@ -135,6 +135,7 @@ struct collector_set_ids; > >>> OVNACT(CT_ORIG_IP6_DST, ovnact_result) \ > >>> OVNACT(CT_ORIG_TP_DST, ovnact_result) \ > >>> OVNACT(FLOOD_REMOTE, ovnact_null) \ > >>> + OVNACT(MIRROR, ovnact_mirror) \ > >>> > >>> /* enum ovnact_type, with a member OVNACT_<ENUM> for each action. */ > >>> enum OVS_PACKED_ENUM ovnact_type { > >>> @@ -537,6 +538,15 @@ struct ovnact_commit_lb_aff { > >>> uint16_t timeout; > >>> }; > >>> > >>> +/* OVNACT_MIRROR. */ > >>> +struct ovnact_mirror { > >>> + struct ovnact ovnact; > >>> + > >>> + /* Argument. */ > >>> + char *port; /* Mirror serving port for output > >>> + action. */ > >>> +}; > >>> + > >>> #define OVN_FIELD_NOTE_MAGIC "ovn" > >>> > >>> struct ovn_field_note_header { > >>> diff --git a/lib/actions.c b/lib/actions.c > >>> index b81061f5c..db0a92b45 100644 > >>> --- a/lib/actions.c > >>> +++ b/lib/actions.c > >>> @@ -5552,6 +5552,76 @@ encode_FLOOD_REMOTE(const struct ovnact_null > >>> *null OVS_UNUSED, > >>> emit_resubmit(ofpacts, ep->flood_remote_table); > >>> } > >>> > >>> +static void > >>> +parse_MIRROR_action(struct action_context *ctx) > >>> +{ > >>> + if (!lexer_force_match(ctx->lexer, LEX_T_LPAREN)) { > >>> + return; > >>> + } > >>> + > >>> + if (ctx->lexer->token.type != LEX_T_STRING) { > >>> + lexer_syntax_error(ctx->lexer, "expecting port name string"); > >>> + return; > >>> + } > >>> + > >>> + struct ovnact_mirror *mirror = ovnact_put_MIRROR(ctx->ovnacts); > >>> + mirror->port = xstrdup(ctx->lexer->token.s); > >>> + > >>> + lexer_get(ctx->lexer); > >>> + (void) (lexer_force_match(ctx->lexer, LEX_T_RPAREN)); > >> In other parts of the code we have not casted the return value of > >> lexer_force_match() to void. > >> So I'd suggest keeping it that way here too. > >> > >> > >> > >>> +} > >>> + > >>> +static void > >>> +format_MIRROR(const struct ovnact_mirror *mirror, > >>> + struct ds *s) > >>> +{ > >>> + ds_put_cstr(s, "mirror("); > >>> + ds_put_format(s, "\"%s\"", mirror->port); > >>> + ds_put_cstr(s, ");"); > >>> +} > >>> + > >>> +static void > >>> +encode_MIRROR(const struct ovnact_mirror *mirror, > >>> + const struct ovnact_encode_params *ep, > >>> + struct ofpbuf *ofpacts) > >>> +{ > >>> + size_t clone_ofs = ofpacts->size; > >>> + uint32_t vport_key; > >>> + > >>> + if (!ep->lookup_port(ep->aux, mirror->port, &vport_key)) { > >>> + return; > >>> + } > >>> + > >>> + struct ofpact_nest *clone = ofpact_put_CLONE(ofpacts); > >>> + > >>> + /* We need set in_port to 0. Consider the following > >>> configuration: > >>> + - hostA (target lport, source lport) + (dst lport) hostB > >>> + - mirror in both directions (to and from lport) attached > >>> + to dst port. > >>> + > >>> + When a packet comes from lport source to dst lport, for cloned > >>> + mirrored packet inport will be equal to outport, and incoming > >>> + traffic will not be mirrored. > >>> + > >>> + We have no problem with zeroing in_port: it will be no > >>> recirculations > >>> + for packet proccesing on hostA, because we skip conntrack > >>> for traffic > >>> + directed to the target port. > >>> + */ > >>> + put_load(ofp_to_u16(OFPP_NONE), MFF_IN_PORT, 0, 16, ofpacts); > >>> + put_load(vport_key, MFF_LOG_OUTPORT, 0, 32, ofpacts); > >>> + emit_resubmit(ofpacts, OFTABLE_REMOTE_OUTPUT); > >>> + clone = ofpbuf_at_assert(ofpacts, clone_ofs, sizeof *clone); > >>> + ofpacts->header = clone; > >>> + > >>> + ofpact_finish_CLONE(ofpacts, &clone); > >> Do we need to clear conntrack state and other register fields ? > >> > >> > >>> +} > >>> + > >>> +static void > >>> +ovnact_mirror_free(struct ovnact_mirror *mirror OVS_UNUSED) > >>> +{ > >>> + free(mirror->port); > >>> +} > >>> + > >>> /* Parses an assignment or exchange or put_dhcp_opts action. */ > >>> static void > >>> parse_set_action(struct action_context *ctx) > >>> @@ -5781,6 +5851,8 @@ parse_action(struct action_context *ctx) > >>> ovnact_put_MAC_CACHE_USE(ctx->ovnacts); > >>> } else if (lexer_match_id(ctx->lexer, "flood_remote")) { > >>> ovnact_put_FLOOD_REMOTE(ctx->ovnacts); > >>> + } else if (lexer_match_id(ctx->lexer, "mirror")) { > >>> + parse_MIRROR_action(ctx); > >>> } else { > >>> lexer_syntax_error(ctx->lexer, "expecting action"); > >>> } > >>> diff --git a/lib/ovn-util.c b/lib/ovn-util.c > >>> index f406114db..453f70c48 100644 > >>> --- a/lib/ovn-util.c > >>> +++ b/lib/ovn-util.c > >>> @@ -1413,3 +1413,11 @@ ovn_debug_commands_register(void) > >>> unixctl_command_register("debug/enable-timewarp", "", 0, 0, > >>> ovn_enable_timewarp, NULL); > >>> } > >>> + > >>> +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 0fff9b463..3c04ff221 100644 > >>> --- a/lib/ovn-util.h > >>> +++ b/lib/ovn-util.h > >>> @@ -199,6 +199,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, > >>> @@ -312,8 +314,8 @@ BUILD_ASSERT_DECL( > >>> #define SCTP_ABORT_CHUNK_FLAG_T (1 << 0) > >>> > >>> /* The number of tables for the ingress and egress pipelines. */ > >>> -#define LOG_PIPELINE_INGRESS_LEN 30 > >>> -#define LOG_PIPELINE_EGRESS_LEN 13 > >>> +#define LOG_PIPELINE_INGRESS_LEN 31 > >>> +#define LOG_PIPELINE_EGRESS_LEN 14 > >>> > >>> static inline uint32_t > >>> hash_add_in6_addr(uint32_t hash, const struct in6_addr *addr) > >>> 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 7f92c0cb7..7486ec09e 100644 > >>> --- a/northd/inc-proc-northd.c > >>> +++ b/northd/inc-proc-northd.c > >>> @@ -64,6 +64,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") \ > >>> @@ -210,6 +211,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 880ec92ac..575acb93f 100644 > >>> --- a/northd/northd.c > >>> +++ b/northd/northd.c > >>> @@ -107,6 +107,8 @@ static bool vxlan_ic_mode; > >>> * priority to determine the ACL's logical flow priority. */ > >>> #define OVN_ACL_PRI_OFFSET 1000 > >>> > >>> +#define OVN_LPORT_MIRROR_OFFSET 100 > >> A comment will be helpful here. > >> > >> > >>> + > >>> /* Register definitions specific to switches. */ > >>> #define REGBIT_CONNTRACK_DEFRAG "reg0[0]" > >>> #define REGBIT_CONNTRACK_COMMIT "reg0[1]" > >>> @@ -443,6 +445,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. */ > >>> @@ -1181,6 +1189,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; > >>> +} > >>> + > >>> void > >>> destroy_routable_addresses(struct ovn_port_routable_addresses *ra) > >>> { > >>> @@ -1270,7 +1284,7 @@ ovn_port_create(struct hmap *ports, const char > >>> *key, > >>> op->key = xstrdup(key); > >>> op->sb = sb; > >>> ovn_port_set_nb(op, nbsp, nbrp); > >>> - op->primary_port = op->cr_port = NULL; > >>> + op->primary_port = op->cr_port = op->mirror_source_port = NULL; > >>> hmap_insert(ports, &op->key_node, hash_string(op->key, 0)); > >>> > >>> op->lflow_ref = lflow_ref_create(); > >>> @@ -1327,7 +1341,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) { > >> I'm a little confused here. If the port has a > > it is safe, it is necessary to remove the serving port > > mp-datapath-target-lport-name, it is not added to the hashmap of its > > datapath, similar to how it happens with cr-ports > >>> hmap_remove(&port->od->ports, &port->dp_node); > >>> } > >>> ovn_port_destroy_orphan(port); > >>> @@ -1444,8 +1458,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)) { > >>> @@ -2167,6 +2181,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, > >>> @@ -2256,6 +2301,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); > >>> + } > >>> + } > >>> + > >> Looks like you missed addressing my comment from the previous version > >> > >> Lets say there is a mirror - mirror1 with its target port as "port1" > >> and this mirror is attached to "port2". > >> What if join_logical_ports_lsp() is called for port2 and "ovn_port" > >> for "port1" is not yet allocated ? > >> Is it a possibility ? > >> > >> I'd suggest running a separate loop at the end of join_logical_ports() > >> to create mirror ports for ports which have mirrors associated. > >> > >> > >> > >> > >>> return op; > >>> } > >>> > >>> @@ -3284,6 +3339,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); > >>> @@ -3483,6 +3548,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); > >>> } > >>> @@ -4783,6 +4850,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; > >> You missed addressing my comment from the previous version > >> > >> I think you can use indexing here to search for nbrec_mirror for the > >> port ? > >> > >> > >>> +} > >>> + > >>> +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. > >>> */ > >>> @@ -4853,6 +4944,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 > >>> @@ -4901,6 +4997,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 > >> nit: typo - s/ad/as > >> > >> > >>> + * to recompute. */ > >>> + goto fail; > >>> + } > >>> } > >>> } > >>> > >>> @@ -5662,6 +5763,149 @@ 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 (!strcmp(rule->action, "mirror")) { > >>> + ds_put_format(&action, "mirror(%s); ", > >>> serving_port->json_key); > >> nit: No need for extra tab here > >> > >>> + } > >>> + > >>> + if (egress) { > >>> + dir = "outport"; > >>> + stage = S_SWITCH_OUT_MIRROR; > >>> + } else { > >>> + dir = "inport"; > >>> + stage = S_SWITCH_IN_MIRROR; > >>> + } > >>> + > >>> + ds_put_cstr(&action, "next;"); > >>> + > >> nit: No need for the extra line here > >>> + ds_put_format(&match, "%s == %s && %s", dir, op->json_key, > >>> rule->match); > >>> + > >> nit: No need for the extra line here > >>> + 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; > >>> + > >>> + if (egress) { > >>> + dir = "outport"; > >>> + stage = S_SWITCH_OUT_MIRROR; > >>> + } else { > >>> + dir = "inport"; > >>> + stage = S_SWITCH_IN_MIRROR; > >>> + } > >>> + > >>> + ds_put_format(&action, "mirror(%s); next;", > >>> serving_port->json_key); > >>> + > >> nit: No need for the extra line here > >>> + ds_put_format(&match, "%s == %s", dir, op->json_key); > >>> + > >> nit: No need for the extra line here > >>> + ovn_lflow_add(lflows, op->od, stage, OVN_LPORT_MIRROR_OFFSET, > >>> + ds_cstr(&match), ds_cstr(&action), op->lflow_ref); > >>> + > >>> + ds_clear(&match); > >>> + ds_clear(&action); > >>> + > >>> + /* We need to skip conntrack for all trafic directed to target > >>> port.*/ > >>> + ds_put_format(&action, "next(pipeline=egress, table=%d);", > >>> + ovn_stage_get_table(S_SWITCH_OUT_APPLY_PORT_SEC)); > >>> + ds_put_format(&match, "outport == %s", serving_port->json_key); > >>> + > >>> + ovn_lflow_add(lflows, op->od, S_SWITCH_OUT_PRE_ACL, UINT16_MAX, > >>> + ds_cstr(&match), ds_cstr(&action), op->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); > >>> + } > >>> + } > >>> + > >>> + 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. > >>> @@ -17446,6 +17690,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); > >>> > >>> @@ -17522,7 +17767,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 388bac6df..acfa9aab5 100644 > >>> --- a/northd/northd.h > >>> +++ b/northd/northd.h > >>> @@ -36,6 +36,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; > >>> @@ -472,37 +473,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, LOOKUP_FDB, 0, > >>> "ls_out_lookup_fdb") \ > >>> @@ -514,10 +516,11 @@ enum ovn_stage { > >>> PIPELINE_STAGE(SWITCH, OUT, ACL_EVAL, 6, > >>> "ls_out_acl_eval") \ > >>> PIPELINE_STAGE(SWITCH, OUT, ACL_SAMPLE, 7, > >>> "ls_out_acl_sample") \ > >>> PIPELINE_STAGE(SWITCH, OUT, ACL_ACTION, 8, > >>> "ls_out_acl_action") \ > >>> - PIPELINE_STAGE(SWITCH, OUT, QOS, 9, > >>> "ls_out_qos") \ > >>> - PIPELINE_STAGE(SWITCH, OUT, STATEFUL, 10, > >>> "ls_out_stateful") \ > >>> - PIPELINE_STAGE(SWITCH, OUT, CHECK_PORT_SEC, 11, > >>> "ls_out_check_port_sec") \ > >>> - PIPELINE_STAGE(SWITCH, OUT, APPLY_PORT_SEC, 12, > >>> "ls_out_apply_port_sec") \ > >>> + PIPELINE_STAGE(SWITCH, OUT, MIRROR, 9, > >>> "ls_out_mirror") \ > >>> + PIPELINE_STAGE(SWITCH, OUT, QOS, 10, > >>> "ls_out_qos") \ > >>> + PIPELINE_STAGE(SWITCH, OUT, STATEFUL, 11, > >>> "ls_out_stateful") \ > >>> + PIPELINE_STAGE(SWITCH, OUT, CHECK_PORT_SEC, 12, > >>> "ls_out_check_port_sec") \ > >>> + PIPELINE_STAGE(SWITCH, OUT, APPLY_PORT_SEC, 13, > >>> "ls_out_apply_port_sec") \ > >>> \ > >>> /* Logical router ingress stages. > >>> */ \ > >>> PIPELINE_STAGE(ROUTER, IN, ADMISSION, 0, > >>> "lr_in_admission") \ > >>> @@ -688,6 +691,14 @@ struct ovn_port { > >>> * to NULL. */ > >>> struct ovn_port *cr_port; > >>> > >>> + /* If this ovn_port is a mirror serving port, this field is set > >>> for > >>> + * a parent port. */ > >>> + struct ovn_port *mirror_target_port; > >>> + > >>> + /* If this ovn_port mirror serving 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 1a70ba579..8970e5402 100644 > >>> --- a/northd/ovn-northd.8.xml > >>> +++ b/northd/ovn-northd.8.xml > >>> @@ -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> > >>> + For each logical switch port with an attached mirror, a > >>> logical flow > >>> + with a priority of 100 is added. This flow matches all > >>> incoming packets > >>> + to the attached port, clones them, and forwards the cloned > >>> packets 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 > >>> @@ -450,7 +478,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 or > >>> 'switch' logical > >>> @@ -488,7 +516,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 > >>> @@ -522,7 +550,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 > >>> @@ -598,7 +626,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 > >>> @@ -632,7 +660,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 > >>> @@ -717,7 +745,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 > >>> @@ -896,7 +924,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 > >>> @@ -936,7 +964,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 > >>> @@ -976,7 +1004,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 > >>> @@ -999,7 +1027,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 > >>> @@ -1027,7 +1055,7 @@ > >>> </li> > >>> </ul> > >>> > >>> - <h3>Ingress Table 13: LB</h3> > >>> + <h3>Ingress Table 14: LB</h3> > >>> > >>> <ul> > >>> <li> > >>> @@ -1107,7 +1135,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 > >>> @@ -1138,7 +1166,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 > >>> @@ -1156,7 +1184,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 > >>> @@ -1191,7 +1219,7 @@ > >>> </li> > >>> </ul> > >>> > >>> - <h3>Ingress Table 17: Hairpin</h3> > >>> + <h3>Ingress Table 18: Hairpin</h3> > >>> <ul> > >>> <li> > >>> <p> > >>> @@ -1229,7 +1257,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 > >>> @@ -1314,7 +1342,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 > >>> @@ -1354,7 +1382,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 > >>> @@ -1394,7 +1422,7 @@ > >>> </li> > >>> </ul> > >>> > >>> - <h3>Ingress Table 21: Stateful</h3> > >>> + <h3>Ingress Table 22: Stateful</h3> > >>> > >>> <ul> > >>> <li> > >>> @@ -1417,7 +1445,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 > >>> @@ -1752,7 +1780,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 > >>> @@ -1813,7 +1841,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 > >>> @@ -1894,7 +1922,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 > >>> @@ -1923,7 +1951,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 > >>> @@ -1958,7 +1986,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 > >>> @@ -2001,7 +2029,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 > >>> @@ -2227,7 +2255,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 > >>> @@ -2463,21 +2491,49 @@ output; > >>> This is similar to ingress table <code>ACL action</code>. > >>> </p> > >>> > >>> - <h3>Egress Table 9: <code>to-lport</code> QoS</h3> > >>> + <h3>Egress Table 9: Mirror </h3> > >>> + > >>> + <p> > >>> + Overlay remote mirror table contains the following > >>> + logical flows: > >>> + </p> > >>> + > >>> + <ul> > >>> + <li> > >>> + For each logical switch port with an attached mirror, a > >>> logical flow > >>> + with a priority of 100 is added. This flow matches all > >>> outcoming > >>> + packets to the attached port, clones them, and forwards the > >>> cloned > >>> + packets 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 10: <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 10: Stateful</h3> > >>> + <h3>Egress Table 11: 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 11: Egress Port Security - check</h3> > >>> + <h3>Egress Table 12: Egress Port Security - check</h3> > >>> > >>> <p> > >>> This is similar to the port security logic in table > >>> @@ -2506,7 +2562,7 @@ output; > >>> </li> > >>> </ul> > >>> > >>> - <h3>Egress Table 12: Egress Port Security - Apply</h3> > >>> + <h3>Egress Table 13: Egress Port Security - Apply</h3> > >>> > >>> <p> > >>> This is similar to the ingress port security logic in > >>> ingress table > >>> diff --git a/ovn-sb.ovsschema b/ovn-sb.ovsschema > >>> index 3dfbf6cfa..b1cfa0cb7 100644 > >>> --- a/ovn-sb.ovsschema > >>> +++ b/ovn-sb.ovsschema > >>> @@ -1,7 +1,7 @@ > >>> { > >>> "name": "OVN_Southbound", > >>> - "version": "21.0.0", > >>> - "cksum": "2665064529 34779", > >>> + "version": "20.42.0", > >>> + "cksum": "1453672098 34860", > >>> "tables": { > >>> "SB_Global": { > >>> "columns": { > >>> @@ -229,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 525070fb2..fd2c3cf1a 100644 > >>> --- a/ovn-sb.xml > >>> +++ b/ovn-sb.xml > >>> @@ -2913,6 +2913,20 @@ tcp.flags = RST; > >>> </p> > >>> </dd> > >>> > >>> + <dt><code>mirror(<var>P</var>);</code></dt> > >>> + <dd> > >>> + <p> > >>> + <b>Parameters</b>: logical port string field <var>P</var> > >>> + of type <code>mirror</code>. > >>> + </p> > >>> + > >>> + <p> > >>> + When using an lport mirror, it clones the packet and > >>> outputs it > >>> + to the local/remote chassis through the mirrored port > >>> <var>P</var> > >>> + to the target port. > >>> + </p> > >>> + </dd> > >>> + > >>> </dl> > >>> </column> > >>> > >>> @@ -3459,6 +3473,10 @@ tcp.flags = RST; > >>> </p> > >>> </column> > >>> > >>> + <column name="mirror_port"> > >>> + Points to mirror target port fot lport mirror type. > >>> + </column> > >>> + > >>> <column name="type"> > >>> <p> > >>> A type for this logical port. Logical ports can be used > >>> to model other > >>> diff --git a/tests/ovn-macros.at b/tests/ovn-macros.at > >>> index fd1a6b197..3c6bb0ebf 100644 > >>> --- a/tests/ovn-macros.at > >>> +++ b/tests/ovn-macros.at > >>> @@ -1468,11 +1468,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-northd.at b/tests/ovn-northd.at > >>> index e3a65e7a4..cd90e9df6 100644 > >>> --- a/tests/ovn-northd.at > >>> +++ b/tests/ovn-northd.at > >>> @@ -16911,3 +16911,331 @@ AT_CHECK([ovn_strip_lflows < lrflows | > >>> grep priority=105 | grep -c "flags.force_ > >>> > >>> 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 200 > >>> +check_column "$mirrorrule1uuid" nb:Mirror mirror_rules > >>> mirror_rules="$mirrorrule1uuid" > >>> + > >>> +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 both sw0-target0 > >>> +check ovn-nbctl mirror-add mirror2 lport from-lport sw1-target1 > >>> + > >>> +check ovn-nbctl mirror-rule-add mirror1 100 'ip' mirror > >>> +check ovn-nbctl mirror-rule-add mirror1 150 'icmp' skip > >>> +check ovn-nbctl mirror-rule-add mirror2 100 'ip4.dst == > >>> 192.168.0.1' mirror > >>> +check ovn-nbctl mirror-rule-add mirror2 150 '1' skip > >>> + > >>> +check ovn-nbctl --wait=hv 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;) > >>> +]) > >>> + > >>> +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=(mirror("mp-sw0-sw0-target0"); 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=egress, table=??);) > >>> +]) > >>> + > >>> +check ovn-nbctl lsp-attach-mirror sw0-p1 mirror1 > >>> + > >>> +check ovn-nbctl --wait=sb sync > >>> + > >>> +check_row_count Port_Binding 1 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_in_mirror ), priority=100 , match=(inport == > >>> "sw0-p1"), action=(mirror("mp-sw0-sw0-target0"); next;) > >>> + table=??(ls_in_mirror ), priority=200 , match=(inport == > >>> "sw0-p1" && ip), action=(mirror("mp-sw0-sw0-target0"); next;) > >>> + table=??(ls_in_mirror ), priority=250 , match=(inport == > >>> "sw0-p1" && icmp), action=(next;) > >>> + table=??(ls_out_mirror ), priority=0 , match=(1), > >>> action=(next;) > >>> + table=??(ls_out_mirror ), priority=100 , match=(outport == > >>> "sw0-p1"), action=(mirror("mp-sw0-sw0-target0"); next;) > >>> + table=??(ls_out_mirror ), priority=200 , match=(outport == > >>> "sw0-p1" && ip), action=(mirror("mp-sw0-sw0-target0"); next;) > >>> + table=??(ls_out_mirror ), priority=250 , match=(outport == > >>> "sw0-p1" && icmp), action=(next;) > >>> +]) > >>> + > >>> +check ovn-nbctl lsp-attach-mirror sw0-p1 mirror2 > >>> + > >>> +check ovn-nbctl --wait=hv sync > >>> + > >>> +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=(mirror("mp-sw0-sw0-target0"); next;) > >>> + table=??(ls_in_mirror ), priority=100 , match=(inport == > >>> "sw0-p1"), action=(mirror("mp-sw0-sw1-target1"); next;) > >>> + table=??(ls_in_mirror ), priority=200 , match=(inport == > >>> "sw0-p1" && ip), action=(mirror("mp-sw0-sw0-target0"); next;) > >>> + table=??(ls_in_mirror ), priority=200 , match=(inport == > >>> "sw0-p1" && ip4.dst == 192.168.0.1), > >>> action=(mirror("mp-sw0-sw1-target1"); next;) > >>> + table=??(ls_in_mirror ), priority=250 , match=(inport == > >>> "sw0-p1" && 1), action=(next;) > >>> + table=??(ls_in_mirror ), priority=250 , match=(inport == > >>> "sw0-p1" && icmp), action=(next;) > >>> + table=??(ls_out_mirror ), priority=0 , match=(1), > >>> action=(next;) > >>> + table=??(ls_out_mirror ), priority=100 , match=(outport == > >>> "sw0-p1"), action=(mirror("mp-sw0-sw0-target0"); next;) > >>> + table=??(ls_out_mirror ), priority=200 , match=(outport == > >>> "sw0-p1" && ip), action=(mirror("mp-sw0-sw0-target0"); 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=egress, table=??);) > >>> + table=??(ls_out_pre_acl ), priority=65535, match=(outport == > >>> "mp-sw0-sw1-target1"), action=(next(pipeline=egress, table=??);) > >>> +]) > >>> + > >>> +check ovn-nbctl lsp-del sw1-target1 > >>> + > >>> +check ovn-nbctl --wait=hv sync > >>> + > >>> +check_row_count Port_Binding 0 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=(mirror("mp-sw0-sw0-target0"); next;) > >>> + table=??(ls_in_mirror ), priority=200 , match=(inport == > >>> "sw0-p1" && ip), action=(mirror("mp-sw0-sw0-target0"); next;) > >>> + table=??(ls_in_mirror ), priority=250 , match=(inport == > >>> "sw0-p1" && icmp), action=(next;) > >>> + table=??(ls_out_mirror ), priority=0 , match=(1), > >>> action=(next;) > >>> + table=??(ls_out_mirror ), priority=100 , match=(outport == > >>> "sw0-p1"), action=(mirror("mp-sw0-sw0-target0"); next;) > >>> + table=??(ls_out_mirror ), priority=200 , match=(outport == > >>> "sw0-p1" && ip), action=(mirror("mp-sw0-sw0-target0"); next;) > >>> + table=??(ls_out_mirror ), priority=250 , match=(outport == > >>> "sw0-p1" && icmp), action=(next;) > >>> +]) > >>> + > >>> +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=egress, table=??);) > >>> +]) > >>> + > >>> +check ovn-nbctl lsp-add sw1 sw1-target1 > >>> + > >>> +check ovn-nbctl --wait=hv sync > >>> + > >>> +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=(mirror("mp-sw0-sw0-target0"); next;) > >>> + table=??(ls_in_mirror ), priority=100 , match=(inport == > >>> "sw0-p1"), action=(mirror("mp-sw0-sw1-target1"); next;) > >>> + table=??(ls_in_mirror ), priority=200 , match=(inport == > >>> "sw0-p1" && ip), action=(mirror("mp-sw0-sw0-target0"); next;) > >>> + table=??(ls_in_mirror ), priority=200 , match=(inport == > >>> "sw0-p1" && ip4.dst == 192.168.0.1), > >>> action=(mirror("mp-sw0-sw1-target1"); next;) > >>> + table=??(ls_in_mirror ), priority=250 , match=(inport == > >>> "sw0-p1" && 1), action=(next;) > >>> + table=??(ls_in_mirror ), priority=250 , match=(inport == > >>> "sw0-p1" && icmp), action=(next;) > >>> + table=??(ls_out_mirror ), priority=0 , match=(1), > >>> action=(next;) > >>> + table=??(ls_out_mirror ), priority=100 , match=(outport == > >>> "sw0-p1"), action=(mirror("mp-sw0-sw0-target0"); next;) > >>> + table=??(ls_out_mirror ), priority=200 , match=(outport == > >>> "sw0-p1" && ip), action=(mirror("mp-sw0-sw0-target0"); next;) > >>> + table=??(ls_out_mirror ), priority=250 , match=(outport == > >>> "sw0-p1" && icmp), action=(next;) > >>> +]) > >>> + > >>> +mirror1uuid_uuid=`ovn-nbctl --bare --columns _uuid find Mirror > >>> name="mirror1"` > >>> + > >>> +ovn-nbctl set mirror $mirror1uuid_uuid sink=sw1-target1 > >>> + > >>> +check ovn-nbctl --wait=hv 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_in_mirror ), priority=100 , match=(inport == > >>> "sw0-p1"), action=(mirror("mp-sw0-sw1-target1"); next;) > >>> + table=??(ls_in_mirror ), priority=200 , match=(inport == > >>> "sw0-p1" && ip), action=(mirror("mp-sw0-sw1-target1"); next;) > >>> + table=??(ls_in_mirror ), priority=200 , match=(inport == > >>> "sw0-p1" && ip4.dst == 192.168.0.1), > >>> action=(mirror("mp-sw0-sw1-target1"); next;) > >>> + table=??(ls_in_mirror ), priority=250 , match=(inport == > >>> "sw0-p1" && 1), action=(next;) > >>> + table=??(ls_in_mirror ), priority=250 , match=(inport == > >>> "sw0-p1" && icmp), action=(next;) > >>> + table=??(ls_out_mirror ), priority=0 , match=(1), > >>> action=(next;) > >>> + table=??(ls_out_mirror ), priority=100 , match=(outport == > >>> "sw0-p1"), action=(mirror("mp-sw0-sw0-target0"); next;) > >>> + table=??(ls_out_mirror ), priority=100 , match=(outport == > >>> "sw0-p1"), action=(mirror("mp-sw0-sw1-target1"); next;) > >>> + table=??(ls_out_mirror ), priority=200 , match=(outport == > >>> "sw0-p1" && ip), action=(mirror("mp-sw0-sw1-target1"); next;) > >>> + table=??(ls_out_mirror ), priority=250 , match=(outport == > >>> "sw0-p1" && icmp), action=(next;) > >>> +]) > >>> + > >>> +check_row_count Port_Binding 1 logical_port=mp-sw0-sw0-target0 > >>> + > >>> +check ovn-nbctl mirror-del mirror0 > >>> + > >>> +check_row_count Port_Binding 0 logical_port=mp-sw0-sw0-target0 > >>> + > >>> +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_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;) > >>> +]) > >>> + > >>> +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 > >>> + > >>> +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 > >>> + > >>> +check_row_count Port_Binding 1 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=(mirror("mp-sw0-sw1-target"); next;) > >>> + table=??(ls_in_mirror ), priority=200 , match=(inport == > >>> "sw0-p1" && 1), action=(mirror("mp-sw0-sw1-target"); 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=(mirror("mp-sw0-sw1-target"); next;) > >>> + table=??(ls_in_mirror ), priority=200 , match=(inport == > >>> "sw0-p1" && 1), action=(mirror("mp-sw0-sw1-target"); 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=(mirror("mp-sw0-sw1-target"); 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 | grep clone], [1], [dnl > >>> +]) > >>> + > >>> +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 b/tests/ovn.at > >>> index bbaa653a8..b89625584 100644 > >>> --- a/tests/ovn.at > >>> +++ b/tests/ovn.at > >>> @@ -803,6 +803,7 @@ m4_define([NEXT], [m4_if( > >>> > >>> m4_define([oflow_in_table], [NEXT(ingress, lflow_table)]) > >>> m4_define([oflow_out_table], [NEXT(egress, lflow_table)]) > >>> +m4_define([remote_out_table], [OFTABLE_REMOTE_OUTPUT]) > >>> > >>> AT_DATA([test-cases.txt], [ > >>> # drop > >>> @@ -2287,6 +2288,13 @@ flood_remote; > >>> flood_remote(); > >>> Syntax error at `(' expecting `;'. > >>> > >>> +#mirror > >>> +mirror("lsp1"); > >>> + encodes as > >>> clone(set_field:ANY->in_port,set_field:0x11->reg15,resubmit(,remote_out_table)) > >>> + > >>> +mirror(lsp1); > >>> + Syntax error at `lsp1' expecting port name string. > >>> + > >>> # Miscellaneous negative tests. > >>> ; > >>> Syntax error at `;'. > >>> diff --git a/utilities/ovn-trace.c b/utilities/ovn-trace.c > >>> index b8a9e0f0a..f4a4f3488 100644 > >>> --- a/utilities/ovn-trace.c > >>> +++ b/utilities/ovn-trace.c > >>> @@ -588,6 +588,20 @@ ovntrace_port_find_by_key(const struct > >>> ovntrace_datapath *dp, > >>> return NULL; > >>> } > >>> > >>> +static const struct ovntrace_port * > >>> +ovntrace_port_find_by_name(const struct ovntrace_datapath *dp, > >>> + const char *name) > >>> +{ > >>> + const struct shash_node *node; > >>> + SHASH_FOR_EACH (node, &ports) { > >>> + const struct ovntrace_port *port = node->data; > >>> + if (port->dp == dp && !strcmp(port->name, name)) { > >>> + return port; > >>> + } > >>> + } > >>> + return NULL; > >>> +} > >>> + > >>> static const char * > >>> ovntrace_port_key_to_name(const struct ovntrace_datapath *dp, > >>> uint16_t key) > >>> @@ -3121,6 +3135,29 @@ execute_check_out_port_sec(const struct > >>> ovnact_result *dl, > >>> mf_write_subfield_flow(&sf, &sv, uflow); > >>> } > >>> > >>> +static void > >>> +execute_mirror(const struct ovnact_mirror *mirror, > >>> + const struct ovntrace_datapath *dp, > >>> + struct flow *uflow, struct ovs_list *super) > >>> +{ > >>> + const struct ovntrace_port *port; > >>> + struct flow cloned_flow = *uflow; > >>> + port = ovntrace_port_find_by_name(dp, mirror->port); > >>> + > >>> + if (port) { > >>> + struct ovntrace_node *node = ovntrace_node_append(super, > >>> + OVNTRACE_NODE_TRANSFORMATION, "clone"); > >>> + > >>> + cloned_flow.regs[MFF_LOG_INPORT - MFF_REG0] = 0; > >>> + cloned_flow.regs[MFF_LOG_OUTPORT - MFF_REG0] = > >>> port->tunnel_key; > >>> + > >>> + trace__(dp, &cloned_flow, 0, OVNACT_P_EGRESS, &node->subs); > >>> + } else { > >>> + ovntrace_node_append(super, OVNTRACE_NODE_ERROR, > >>> + "/* omitting output because no taget port found. */"); > >>> + } > >>> +} > >>> + > >>> static void > >>> trace_actions(const struct ovnact *ovnacts, size_t ovnacts_len, > >>> const struct ovntrace_datapath *dp, struct flow *uflow, > >>> @@ -3441,6 +3478,11 @@ trace_actions(const struct ovnact *ovnacts, > >>> size_t ovnacts_len, > >>> execute_check_out_port_sec(ovnact_get_CHECK_OUT_PORT_SEC(a), > >>> dp, uflow); > >>> break; > >>> + > >>> + case OVNACT_MIRROR: > >>> + execute_mirror(ovnact_get_MIRROR(a), dp, uflow, super); > >>> + break; > >>> + > >>> case OVNACT_COMMIT_ECMP_NH: > >>> break; > >>> case OVNACT_CHK_ECMP_NH_MAC: > >>> -- > >>> 2.48.1 > >>> > >>> _______________________________________________ > >>> dev mailing list > >>> d...@openvswitch.org > >>> https://mail.openvswitch.org/mailman/listinfo/ovs-dev > > > > > > -- > regards, > Alexandra. > _______________________________________________ dev mailing list d...@openvswitch.org https://mail.openvswitch.org/mailman/listinfo/ovs-dev