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

Reply via email to