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 _______________________________________________ dev mailing list d...@openvswitch.org https://mail.openvswitch.org/mailman/listinfo/ovs-dev