Hi Numan, thanks a lot for review and your time, fixed all the comments in v11, regarding writing multi-node tests: I need some time to figure out how to do this, I will send a separate patch. thanks!
On 22.04.2025 05:43, Numan Siddique wrote: > On Fri, Apr 18, 2025 at 8:52 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> >> --- >> v9 -> v10: rebased on main. >> fixed Numan comments in v9. >> added index nbrec_mirror_by_port_and_sink. >> bug was noticed and fixed: if one mirror is attached to several >> ports, >> the target port is deleted, everything is fine, and when the >> target port >> is returned, a transaction error occurs. >> --- > Thanks for v10. > > I've a few comments. Please see below. > > With those addressed: > > Acked-by: Numan Siddique <num...@ovn.org> > > Numan > >> controller/lflow.h | 12 +- >> include/ovn/actions.h | 12 +- >> lib/actions.c | 73 +++++++++ >> lib/ovn-util.c | 7 + >> lib/ovn-util.h | 6 +- >> northd/en-northd.c | 6 + >> northd/inc-proc-northd.c | 10 ++ >> northd/northd.c | 295 ++++++++++++++++++++++++++++++++++- >> northd/northd.h | 78 ++++++---- >> northd/ovn-northd.8.xml | 120 +++++++++++---- >> ovn-sb.ovsschema | 5 +- >> ovn-sb.xml | 18 +++ >> tests/ovn-macros.at | 10 +- >> tests/ovn-northd.at | 325 +++++++++++++++++++++++++++++++++++++++ >> tests/ovn.at | 8 + >> utilities/ovn-trace.c | 42 +++++ >> 16 files changed, 940 insertions(+), 87 deletions(-) >> >> diff --git a/controller/lflow.h b/controller/lflow.h >> index 3fd7ca99f..32549df90 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 7fd7b26bf..055d3b26c 100644 >> --- a/include/ovn/actions.h >> +++ b/include/ovn/actions.h >> @@ -135,7 +135,8 @@ struct collector_set_ids; >> OVNACT(CT_ORIG_IP6_DST, ovnact_result) \ >> OVNACT(CT_ORIG_TP_DST, ovnact_result) \ >> OVNACT(FLOOD_REMOTE, ovnact_null) \ >> - OVNACT(CT_STATE_SAVE, ovnact_result) >> + OVNACT(CT_STATE_SAVE, ovnact_result) \ >> + OVNACT(MIRROR, ovnact_mirror) \ >> >> /* enum ovnact_type, with a member OVNACT_<ENUM> for each action. */ >> enum OVS_PACKED_ENUM ovnact_type { >> @@ -538,6 +539,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 f6f413be2..5a8e47935 100644 >> --- a/lib/actions.c >> +++ b/lib/actions.c >> @@ -5575,6 +5575,77 @@ format_CT_STATE_SAVE(const struct ovnact_result *res, >> struct ds *s) >> ds_put_cstr(s, " = ct_state_save();"); >> } >> >> +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); >> + > nit: You can remove this newline. Not required. >> + lexer_force_match(ctx->lexer, LEX_T_RPAREN); >> +} >> + >> +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. >> + */ > nit: Please format the multi line comments like the rest of the code base. > > >> + 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); >> +} >> + >> +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) >> @@ -5808,6 +5879,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 c825e0936..e2682ead7 100644 >> --- a/lib/ovn-util.c >> +++ b/lib/ovn-util.c >> @@ -1413,3 +1413,10 @@ 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..4fc452f35 100644 >> --- a/northd/en-northd.c >> +++ b/northd/en-northd.c >> @@ -61,6 +61,10 @@ northd_get_input_data(struct engine_node *node, >> engine_ovsdb_node_get_index( >> engine_get_input("SB_fdb", node), >> "sbrec_fdb_by_dp_and_port"); >> + input_data->nbrec_mirror_by_type_and_sink = >> + engine_ovsdb_node_get_index( >> + engine_get_input("NB_mirror", node), >> + "nbrec_mirror_by_type_and_sink"); >> >> input_data->nbrec_logical_switch_table = >> EN_OVSDB_GET(engine_get_input("NB_logical_switch", node)); >> @@ -72,6 +76,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..2b28a6a28 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); >> >> @@ -492,6 +494,14 @@ void inc_proc_northd_init(struct ovsdb_idl_loop *nb, >> engine_ovsdb_node_add_index(&en_sb_port_binding, >> "sbrec_port_binding_by_name", >> sbrec_port_binding_by_name); >> + >> + struct ovsdb_idl_index *nbrec_mirror_by_type_and_sink >> + = ovsdb_idl_index_create2(nb->idl, &nbrec_mirror_col_type, >> + &nbrec_mirror_col_sink); >> + engine_ovsdb_node_add_index(&en_nb_mirror, >> + "nbrec_mirror_by_type_and_sink", >> + nbrec_mirror_by_type_and_sink); >> + >> struct ovsdb_idl_index *sbrec_ecmp_nexthop_by_ip_and_port >> = ecmp_nexthop_index_create(sb->idl); >> engine_ovsdb_node_add_index(&en_sb_ecmp_nexthop, >> diff --git a/northd/northd.c b/northd/northd.c >> index 1f9340e55..979dcd53a 100644 >> --- a/northd/northd.c >> +++ b/northd/northd.c >> @@ -107,6 +107,11 @@ static bool vxlan_ic_mode; >> * priority to determine the ACL's logical flow priority. */ >> #define OVN_ACL_PRI_OFFSET 1000 >> >> +/* Default logical flows for mirroring are added with the priority described >> + * below, in the case of mirroring rules specified in the northbound >> database >> + * this value will also be added to the priority. */ >> +#define OVN_LPORT_MIRROR_OFFSET 100 >> + >> /* Register definitions specific to switches. */ >> #define REGBIT_CONNTRACK_DEFRAG "reg0[0]" >> #define REGBIT_CONNTRACK_COMMIT "reg0[1]" >> @@ -459,6 +464,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. */ >> @@ -1197,6 +1208,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) >> { >> @@ -1286,7 +1303,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(); >> @@ -1343,7 +1360,7 @@ ovn_port_destroy(struct hmap *ports, struct ovn_port >> *port) >> /* Don't remove port->list. The node should be removed from such >> lists >> * before calling this function. */ >> hmap_remove(ports, &port->key_node); >> - if (port->od && !port->primary_port) { >> + if (port->od && !port->mirror_source_port && !port->primary_port) { >> hmap_remove(&port->od->ports, &port->dp_node); >> } >> ovn_port_destroy_orphan(port); >> @@ -1460,8 +1477,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)) { >> @@ -2183,6 +2200,41 @@ 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) { >> + goto clear; >> + } >> + >> + if (!mp) { >> + mp = ovn_port_create(ports, mp_name, op->nbsp, NULL, NULL); >> + ovs_list_push_back(nb_only, &mp->list); >> + } else if (mp->sb) { >> + ovn_port_set_nb(mp, op->nbsp, NULL); >> + ovs_list_remove(&mp->list); >> + ovs_list_push_back(both_dbs, &mp->list); >> + } else { >> + goto clear; >> + } >> + >> + mp->mirror_source_port = op; > I see one problem with having the field mirror_source_port in 'struct > ovn_port'. > > A mirror port can have multiple source ports. > > Eg., > > ovn-nbctl mirror-add mirror1 lport from-lport sw0-p1 > > ovn-nbctl lsp-attach-mirror sw0-p2 mirror1 > ovn-nbctl lsp-attach-mirror sw0-p3 mirror1 > > In the above scenario, the function create_mirror_port() will be > called twice. One more sw0-p2 and other for sw0-p3. > And we only store the mp->mirror_source_port for sw0-p2. > > Although there is nothing wrong here. But still its confusing. I'd > suggest remove the field "mirror_source_port" altogether. > > In the code I don't see it being used anywhere other than in the > function is_mp_port() . > The function "is_mp_port() can return true if mp->mirror_target_port > is set to true. > > >> + mp->mirror_target_port = target_port; >> + >> + mp->od = op->od; >> + >> +clear: >> + free(mp_name); >> +} >> + >> static struct ovn_port * >> join_logical_ports_lsp(struct hmap *ports, >> struct ovs_list *nb_only, struct ovs_list *both, >> @@ -2190,7 +2242,8 @@ join_logical_ports_lsp(struct hmap *ports, >> const struct nbrec_logical_switch_port *nbsp, >> const char *name, >> unsigned long *queue_id_bitmap, >> - struct hmap *tag_alloc_table) >> + struct hmap *tag_alloc_table, >> + struct hmapx *mirror_attached_ports) >> { >> struct ovn_port *op = ovn_port_find_bound(ports, name); >> if (op && (op->od || op->nbsp || op->nbrp)) { >> @@ -2271,7 +2324,11 @@ join_logical_ports_lsp(struct hmap *ports, >> } >> hmap_insert(&od->ports, &op->dp_node, >> hmap_node_hash(&op->key_node)); >> + if (nbsp->n_mirror_rules) { >> + hmapx_add(mirror_attached_ports, op); >> + } >> tag_alloc_add_existing_tags(tag_alloc_table, nbsp); >> + >> return op; >> } >> >> @@ -2408,6 +2465,21 @@ peer_needs_cr_port_creation(struct ovn_port *op) >> return false; >> } >> >> +static void >> +join_mirror_ports(struct ovn_port *op, >> + const struct nbrec_logical_switch_port *nbsp, >> + struct hmap *ports, struct ovs_list *both, >> + struct ovs_list *nb_only) >> +{ >> + /* Create mirror targets port bindings if there any mirror >> + * with lport type attached to this port. */ >> + for (size_t j = 0; j < op->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); >> + } >> + } >> +} >> static void >> join_logical_ports(const struct sbrec_port_binding_table *sbrec_pb_table, >> struct hmap *ls_datapaths, struct hmap *lr_datapaths, >> @@ -2428,6 +2500,8 @@ join_logical_ports(const struct >> sbrec_port_binding_table *sbrec_pb_table, >> >> struct ovn_datapath *od; >> struct hmapx dgps = HMAPX_INITIALIZER(&dgps); >> + struct hmapx mirror_attached_ports = >> + HMAPX_INITIALIZER(&mirror_attached_ports); >> HMAP_FOR_EACH (od, key_node, lr_datapaths) { >> ovs_assert(od->nbr); >> for (size_t i = 0; i < od->nbr->n_ports; i++) { >> @@ -2454,7 +2528,7 @@ join_logical_ports(const struct >> sbrec_port_binding_table *sbrec_pb_table, >> = od->nbs->ports[i]; >> join_logical_ports_lsp(ports, nb_only, both, od, nbsp, >> nbsp->name, queue_id_bitmap, >> - tag_alloc_table); >> + tag_alloc_table, &mirror_attached_ports); >> } >> } >> >> @@ -2622,6 +2696,14 @@ join_logical_ports(const struct >> sbrec_port_binding_table *sbrec_pb_table, >> } >> hmapx_destroy(&dgps); >> >> + HMAPX_FOR_EACH (hmapx_node, &mirror_attached_ports) { >> + op = hmapx_node->data; >> + if (op && op->nbsp) { >> + join_mirror_ports(op, op->nbsp, ports, both, nb_only); >> + } >> + } >> + hmapx_destroy(&mirror_attached_ports); >> + >> /* Wait until all ports have been connected to add to IPAM since >> * it relies on proper peers to be set >> */ >> @@ -3300,6 +3382,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); >> @@ -3499,6 +3591,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); >> } >> @@ -4799,6 +4893,38 @@ check_lsp_changes_other_than_up(const struct >> nbrec_logical_switch_port *nbsp) >> return false; >> } >> >> +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; >> +} >> + >> +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. >> */ >> @@ -4869,6 +4995,12 @@ 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->nbrec_mirror_by_type_and_sink, >> + 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 >> @@ -4917,6 +5049,12 @@ 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->nbrec_mirror_by_type_and_sink, >> + op)) { >> + /* This port was used as target mirror port, fallback >> + * to recompute. */ >> + goto fail; >> + } >> } >> } >> >> @@ -5678,6 +5816,146 @@ 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); >> + } >> + >> + if (egress) { >> + dir = "outport"; >> + stage = S_SWITCH_OUT_MIRROR; >> + } else { >> + dir = "inport"; >> + stage = S_SWITCH_IN_MIRROR; >> + } >> + >> + ds_put_cstr(&action, "next;"); >> + ds_put_format(&match, "%s == %s && (%s)", dir, op->json_key, >> rule->match); >> + 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); >> + ds_put_format(&match, "%s == %s", dir, op->json_key); >> + ovn_lflow_add(lflows, op->od, stage, OVN_LPORT_MIRROR_OFFSET, >> + ds_cstr(&match), ds_cstr(&action), op->lflow_ref); >> + >> + ds_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) { >> + free(serving_port_name); >> + 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. >> @@ -17477,6 +17755,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); >> >> @@ -17553,7 +17832,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..f65fdd006 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; >> @@ -73,6 +74,7 @@ struct northd_input { >> struct ovsdb_idl_index *sbrec_ha_chassis_grp_by_name; >> struct ovsdb_idl_index *sbrec_ip_mcast_by_dp; >> struct ovsdb_idl_index *sbrec_fdb_by_dp_and_port; >> + struct ovsdb_idl_index *nbrec_mirror_by_type_and_sink; >> }; >> >> /* A collection of datapaths. E.g. all logical switch datapaths, or all >> @@ -472,37 +474,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 +517,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 +692,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; > As I mentioned above, please remove this "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 e087b6f59..96e102eeb 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 7eda3a5d9..4c24f5b51 100644 >> --- a/ovn-sb.ovsschema >> +++ b/ovn-sb.ovsschema >> @@ -1,7 +1,7 @@ >> { >> "name": "OVN_Southbound", >> - "version": "21.1.0", >> - "cksum": "880279393 34779", >> + "version": "21.2.0", >> + "cksum": "29145795 34859", >> "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 d1d0085af..5e0b3ddfa 100644 >> --- a/ovn-sb.xml >> +++ b/ovn-sb.xml >> @@ -2921,6 +2921,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> >> >> @@ -3467,6 +3481,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 0c1c1cd99..d0f1c0bf4 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 12d6611b6..028e847ae 100644 >> --- a/tests/ovn-northd.at >> +++ b/tests/ovn-northd.at >> @@ -16927,3 +16927,328 @@ 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 > I think this test case should be part of ovn-nbctl.at. > > In the first patch you've already added a test in ovn-nbctl.at. > I'd suggest either moving this function to ovn-nbctl.at file or remove > it if it is redundant. > > > >> + >> +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 sw0-target0 >> + >> +check ovn-nbctl --wait=hv sync >> + >> +check_row_count Port_Binding 0 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-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_out_mirror ), priority=0 , match=(1), 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-sw1-target1"), action=(next(pipeline=egress, table=??);) >> +]) >> + >> +check ovn-nbctl lsp-add sw1 sw0-target0 >> + >> +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 35c7dc79a..333068b99 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 >> @@ -2309,6 +2310,13 @@ ct_state_save; >> ct_state_save(); >> Syntax error at `ct_state_save' expecting action. >> >> +#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 ce55008a6..d135a6cae 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) >> @@ -3138,6 +3152,29 @@ execute_ct_save_state(const struct ovnact_result *dl, >> struct flow *uflow, >> ds_destroy(&s); >> } >> >> +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, >> @@ -3458,6 +3495,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