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.
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