On 1/15/25 2:07 PM, Odintsov Vladislav wrote:
> Hi Ales,
> 

Hi all,

> thanks for your comments!
> 
> Please, see inline.
> 
> On 15.01.2025 15:41, Ales Musil wrote:
>> On Sat, Dec 28, 2024 at 4:49 PM Alexandra Rukomoinikova
>> <[email protected]> wrote:
>>
>>> Today the mirror feature in OVN supports only tunnel to local
>>> and remote to ports that locate outside of OVN claster.
>>>
>>> With this feature, traffic to/from a virtual port that
>>> can be mirrored to dedicated OVN port.
>>>
>>> To enable overlay port mirroring with filter functions,
>>> we have introduced the necessary schemas in the Northbound db
>>> and the associated XML configuration:
>>> 1. A field for mirror rules has been added to the mirror table
>>> to add reflection rules of mirrored traffic.
>>> 2. A new table, titled "Mirror Rule," has been established
>>> to filter overlay remote traffic.
>>> 3. A new mirror type, titled "lport", has been established
>>> for encapsulate mirror traffic to another ovn port
>>>
>>> For lport mirrors, all processing occurs within OVN, making it
>>> unnecessary to involve OVS. In the case of lport mirror we add
>>> logical flows about the necessary actions with the package.
>>>
>>> A new stages named "MIRROR" in logical flow table has been
>>> introduced, allowing specification of mirror rule filters for
>>> the lport mirror type.
>>>
>>> Added stage number 2 in the ingress pipeline of the logical switch
>>> and table number 7 in the egress pipeline of the logical switch.
>>>
>>> Packets that meet these criteria are duplicated and delivered to
>>> the target port, while the original packet follows its designated
>>> pipeline. Northbound's mirror rule table enables the creation of
>>> these filters.
>>>
>>> In case of creating a mirror with the lport type without any rules
>>> attached to it, default logical flows are added that duplicate all
>>> incoming/outgoing traffic to the target port.
>>>
>>> At the time of attaching lport mirror to logical switch port, a new
>>> port binding mp-target port is created, it's a contrainer port with
>>> parent with a parent that is the target port, tagging is unnecessary,
>>> packets are sent without VLAN header encapsulation.
>>>
>>> Signed-off-by: Alexandra Rukomoinikova <[email protected]>
>>> Signed-off-by: Vladislav Odintsov <[email protected]>
>>> Co-authored-by: Vladislav Odintsov <[email protected]>
>>> Tested-by: Ivan Burnin <[email protected]>
>>> ---
>>>
>> Hi Alexandra, Vladislav,
>>
>> I remember us discussing this during the OvS/OVN conference. I was
>> looking through the code and there are two main things that I don't
>> feel are right:
>>
>> 1) The implicit creation of a container port with a bunch of if
>> statements to skip the tag. That doesn't feel right and if I remember
>> correctly we have suggested creating a new port type that would
>> share most of the code with container ports but would allow it to be
>> without a tag. This would also avoid the problem of storing a bunch
>> of references and flags if the port is actually the mirror port or not.
> I'll let Alexandra to comment on this part, if she agree or have any 
> questions.

I agree with Ales on this one.  It would be preferable if we didn't
overload container ports functionality and have a new port type instead.
 Under the hood we can refactor the container ports code and use what's
relevant from it for both container ports and "mirror" ports.

>> 2) Addition of new pipeline stage, are we certain that this is the
>> point where we want to mirror traffic? What if there is a use case to
>> mirror before ACLs/after LB etc.? That made me think this is very
>> similar to the idea of composable services, I know that composable
>> services at this point are still a bit away. So my suggestion would be
>> to implement this as a sort of "hacky" composable service as a side
>> table. That would allow us to avoid an additional stage, we could add
>> a "tap point" which would indicate at which stage we want to mirror
>> and send it to the side table.
> 
> We've considered mirroring of traffic should not have the choice here.  
> We must mirror traffic from the nearest point, where the end user sees 
> it - right from VIF. In the similar way OVS "local"/"erspan" mirroring 
> works, IIRC, so it seems consistent with new "lport" mirror type, and it 
> is impossible to change that behaviour.
> 
> The logic here is easy: which traffic we see on the VM/container VIF, 
> the same traffic we should mirror. If "to-lport" ACL has dropped traffic 
> before it reached VIF, then we don't mirror it. The same applies to 
> reverse path: "from-lport" ACL drops traffic, which was sent by the 
> VM/container, after it appeared on the VIF, so we do mirror it.  If user 
> wants to skip some traffic from being mirrored, mirror filtering rules 
> can be used.  I'm not aware of users, which want to mirror traffic, 
> which finally didn't reach the VIF because was dropped by platform-level 
> ACL (usually this is done by ACL/Security Groups logging 
> functionality).  But again, this can be revised in the discussion.
> 

Thinking more about it, this might be good enough indeed.  We have other
means of tracking what traffic was dropped by ACLs (e.g., ACL sampling).

>>
>> All of this would be considered as an experimental feature that we
>> could eventually convert to composable service once it arrives. Does
>> that sound like an acceptable solution for you? My main worry is
>> about introducing another feature that could be made potentially
>> easily redundant by composable services.
>>

Unfortunately it's not clear when we'll have support for composable
services.  So until then I vote +1 for adding this new feature as
experimental.  We can revisit its support (and decide whether we make it
non-experimental) in the 25.09 dev cycle when we'll know more about
composable services.

>> I have cc'ed all maintainers to hear their opinion in this matter
>> if any.
>>

Regards,
Dumitru

>>
>>   controller/lflow.h       |  12 +-
>>>   controller/mirror.c      |   4 +
>>>   controller/physical.c    |   7 +-
>>>   lib/ovn-util.c           |   6 +
>>>   lib/ovn-util.h           |   3 +-
>>>   northd/en-northd.c       |   2 +
>>>   northd/inc-proc-northd.c |   2 +
>>>   northd/northd.c          | 223 ++++++++++++++++++++++++++++++++++++
>>>   northd/northd.h          |  76 +++++++------
>>>   northd/ovn-northd.8.xml  | 121 ++++++++++++++------
>>>   ovn-nb.ovsschema         |  25 +++-
>>>   ovn-nb.xml               |  45 +++++++-
>>>   ovn-sb.ovsschema         |   7 +-
>>>   ovn-sb.xml               |   2 +-
>>>   tests/ovn-macros.at      |  10 +-
>>>   tests/ovn-nbctl.at       |  99 +++++++++++++++-
>>>   tests/ovn-northd.at      | 240 +++++++++++++++++++++++++++++++++++++++
>>>   utilities/ovn-nbctl.c    | 214 ++++++++++++++++++++++++++++++++--
>>>   18 files changed, 997 insertions(+), 101 deletions(-)
>>>
>>> diff --git a/controller/lflow.h b/controller/lflow.h
>>> index 206328f9e..a059300b1 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/controller/mirror.c b/controller/mirror.c
>>> index b557b96da..2f1c811a0 100644
>>> --- a/controller/mirror.c
>>> +++ b/controller/mirror.c
>>> @@ -121,6 +121,10 @@ mirror_run(struct ovsdb_idl_txn *ovs_idl_txn,
>>>       /* Iterate through sb mirrors and build the 'ovn_mirrors'. */
>>>       const struct sbrec_mirror *sb_mirror;
>>>       SBREC_MIRROR_TABLE_FOR_EACH (sb_mirror, sb_mirror_table) {
>>> +        /* We don't need to add mirror to ovs if it is lport mirror. */
>>> +        if (!strcmp(sb_mirror->type, "lport")) {
>>> +            continue;
>>> +        }
>>>
>>           struct ovn_mirror *m = ovn_mirror_create(sb_mirror->name);
>>>           m->sb_mirror = sb_mirror;
>>>           ovn_mirror_add(&ovn_mirrors, m);
>>> diff --git a/controller/physical.c b/controller/physical.c
>>> index c56c73c20..a0db3fa33 100644
>>> --- a/controller/physical.c
>>> +++ b/controller/physical.c
>>> @@ -1652,14 +1652,17 @@ consider_port_binding(struct ovsdb_idl_index
>>> *sbrec_port_binding_by_name,
>>>       bool nested_container = false;
>>>       const struct sbrec_port_binding *parent_port = NULL;
>>>       ofp_port_t ofport;
>>> +    bool is_mirror = smap_get_bool(&binding->options, "is-mirror", false);
>>>       if (binding->parent_port && *binding->parent_port) {
>>> -        if (!binding->tag) {
>>> +        if (!binding->tag && !is_mirror) {
>>>               return;
>>>           }
>>>           ofport = local_binding_get_lport_ofport(local_bindings,
>>>                                                   binding->parent_port);
>>>           if (ofport) {
>>> -            tag = *binding->tag;
>>> +            if (!is_mirror) {
>>> +                tag = *binding->tag;
>>> +            }
>>>               nested_container = true;
>>>               parent_port = lport_lookup_by_name(
>>>                   sbrec_port_binding_by_name, binding->parent_port);
>>> diff --git a/lib/ovn-util.c b/lib/ovn-util.c
>>> index b78bdbfa1..b87b1d3b7 100644
>>> --- a/lib/ovn-util.c
>>> +++ b/lib/ovn-util.c
>>> @@ -1351,3 +1351,9 @@ ovn_update_swconn_at(struct rconn *swconn, const
>>> char *target,
>>>
>>>       return notify;
>>>   }
>>> +
>>> +char *
>>> +ovn_mirror_port_name(const char *port_name)
>>> +{
>>> +    return xasprintf("mp-%s", port_name);
>>> +}
>>> diff --git a/lib/ovn-util.h b/lib/ovn-util.h
>>> index 899bd9d12..afaaa584a 100644
>>> --- a/lib/ovn-util.h
>>> +++ b/lib/ovn-util.h
>>> @@ -198,6 +198,7 @@ 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 *port_name);
>>>   void ovn_set_pidfile(const char *name);
>>>
>>>   bool ip46_parse_cidr(const char *str, struct in6_addr *prefix,
>>> @@ -310,7 +311,7 @@ BUILD_ASSERT_DECL(
>>>   #define SCTP_ABORT_CHUNK_FLAG_T (1 << 0)
>>>
>>>   /* The number of tables for the ingress and egress pipelines. */
>>> -#define LOG_PIPELINE_LEN 30
>>> +#define LOG_PIPELINE_LEN 31
>>>
>>>   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 c7d1ebcb3..abe7a55a0 100644
>>> --- a/northd/en-northd.c
>>> +++ b/northd/en-northd.c
>>> @@ -72,6 +72,8 @@ northd_get_input_data(struct engine_node *node,
>>>           EN_OVSDB_GET(engine_get_input("NB_chassis_template_var", node));
>>>       input_data->nbrec_mirror_table =
>>>           EN_OVSDB_GET(engine_get_input("NB_mirror", node));
>>> +    input_data->nbrec_mirror_rule_table =
>>> +        EN_OVSDB_GET(engine_get_input("NB_mirror_rule", node));
>>>
>>>       input_data->sbrec_datapath_binding_table =
>>>           EN_OVSDB_GET(engine_get_input("SB_datapath_binding", node));
>>> diff --git a/northd/inc-proc-northd.c b/northd/inc-proc-northd.c
>>> index 6e0aa04c4..8413c94e4 100644
>>> --- a/northd/inc-proc-northd.c
>>> +++ b/northd/inc-proc-northd.c
>>> @@ -58,6 +58,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") \
>>> @@ -187,6 +188,7 @@ void inc_proc_northd_init(struct ovsdb_idl_loop *nb,
>>>       engine_add_input(&en_global_config, &en_sampling_app, NULL);
>>>
>>>       engine_add_input(&en_northd, &en_nb_mirror, NULL);
>>> +    engine_add_input(&en_northd, &en_nb_mirror_rule, NULL);
>>>       engine_add_input(&en_northd, &en_nb_static_mac_binding, NULL);
>>>       engine_add_input(&en_northd, &en_nb_chassis_template_var, NULL);
>>>
>>> diff --git a/northd/northd.c b/northd/northd.c
>>> index b01e40ecd..ba9646cb6 100644
>>> --- a/northd/northd.c
>>> +++ b/northd/northd.c
>>> @@ -2129,6 +2129,36 @@ 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(nb_mirror->sink);
>>> +    struct ovn_port *mp = ovn_port_find(ports, mp_name);
>>> +    struct ovn_port *target_port = ovn_port_find(ports, nb_mirror->sink);
>>> +
>>> +    if (!target_port) {
>>> +        return;
>>> +    }
>>> +    if (mp && mp->sb) {
>>> +        ovn_port_set_nb(mp, op->nbsp, NULL);
>>> +        ovs_list_remove(&mp->list);
>>> +        ovs_list_push_back(both_dbs, &mp->list);
>>> +    } else {
>>> +        mp = ovn_port_create(ports, mp_name, op->nbsp, NULL, NULL);
>>> +        ovs_list_push_back(nb_only, &mp->list);
>>> +    }
>>> +
>>> +    op->is_mirror_source_port = true;
>>> +    mp->primary_port = op;
>>> +    mp->mirror_parent_port = xstrdup(nb_mirror->sink);
>>> +    mp->od = op->od;
>>> +
>>> +    free(mp_name);
>>> +}
>>> +
>>>   static struct ovn_port *
>>>   join_logical_ports_lsp(struct hmap *ports,
>>>                          struct ovs_list *nb_only, struct ovs_list *both,
>>> @@ -2218,6 +2248,15 @@ join_logical_ports_lsp(struct hmap *ports,
>>>       hmap_insert(&od->ports, &op->dp_node,
>>>                   hmap_node_hash(&op->key_node));
>>>       tag_alloc_add_existing_tags(tag_alloc_table, nbsp);
>>> +
>>> +    op->is_mirror_source_port = false;
>>> +    for (size_t j = 0; j < nbsp->n_mirror_rules; j++) {
>>> +        struct nbrec_mirror *mirror = nbsp->mirror_rules[j];
>>> +        if (!strcmp(mirror->type, "lport")) {
>>> +                create_mirror_port(op, ports, both, nb_only, mirror);
>>> +        }
>>> +    }
>>> +
>>>       return op;
>>>   }
>>>
>>> @@ -3182,6 +3221,20 @@ 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_parent_port) {
>>> +            /* In case of using a lport mirror, we establish a port
>>> binding
>>> +             * with parent port to act it like container port without
>>> +             * tag it by vlan tag. */
>>> +            struct smap new;
>>> +            smap_init(&new);
>>> +            smap_add(&new, "is-mirror", "true");
>>> +            sbrec_port_binding_set_parent_port(op->sb,
>>> op->mirror_parent_port);
>>> +            sbrec_port_binding_set_options(op->sb, &new);
>>> +            smap_destroy(&new);
>>> +            goto common;
>>> +        }
>>> +
>>>           if (!lsp_is_router(op->nbsp)) {
>>>               uint32_t queue_id = smap_get_int(
>>>                       &op->sb->options, "qdisc_queue_id", 0);
>>> @@ -3375,6 +3428,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);
>>>       }
>>> @@ -4637,6 +4692,30 @@ check_lsp_changes_other_than_up(const struct
>>> nbrec_logical_switch_port *nbsp)
>>>       return false;
>>>   }
>>>
>>> +static bool
>>> +is_lsp_mirror_target_port(const struct northd_input *ni,
>>> +                          struct ovn_port *port)
>>> +{
>>> +    const struct nbrec_mirror *nb_mirror;
>>> +    NBREC_MIRROR_TABLE_FOR_EACH (nb_mirror, ni->nbrec_mirror_table) {
>>> +        if (!strcmp(nb_mirror->type, "lport") &&
>>> +            !strcmp(nb_mirror->sink, port->key)) {
>>> +            return true;
>>> +        }
>>> +    }
>>> +    return false;
>>> +}
>>> +
>>> +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.
>>>    */
>>> @@ -4707,6 +4786,11 @@ ls_handle_lsp_changes(struct ovsdb_idl_txn
>>> *ovnsb_idl_txn,
>>>                    * by this change. Fallback to recompute. */
>>>                   goto fail;
>>>               }
>>> +            if (!lsp_handle_mirror_rules_changes(new_nbsp) ||
>>> +                 is_lsp_mirror_target_port(ni, op)) {
>>> +                /* Fallback to recompute. */
>>> +                goto fail;
>>> +            }
>>>               if (!check_lsp_is_up &&
>>>                   !check_lsp_changes_other_than_up(new_nbsp)) {
>>>                   /* If the only change is the "up" column while the
>>> @@ -4755,6 +4839,11 @@ ls_handle_lsp_changes(struct ovsdb_idl_txn
>>> *ovnsb_idl_txn,
>>>               sbrec_port_binding_delete(op->sb);
>>>               delete_fdb_entries(ni->sbrec_fdb_by_dp_and_port,
>>> od->tunnel_key,
>>>                                   op->tunnel_key);
>>> +            if (is_lsp_mirror_target_port(ni, op)) {
>>> +            /* This port was used ad target mirror port, fallback
>>> +                 * to recompute. */
>>> +                goto fail;
>>> +            }
>>>           }
>>>       }
>>>
>>> @@ -5862,6 +5951,138 @@ 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 lflow_table *lflows,
>>> +                   struct nbrec_mirror *mirror,
>>> +                   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;
>>> +
>>> +    if (egress) {
>>> +        dir = "outport";
>>> +        stage = S_SWITCH_OUT_MIRROR;
>>> +    } else {
>>> +        dir = "inport";
>>> +        stage = S_SWITCH_IN_MIRROR;
>>> +    }
>>> +
>>> +    ds_put_format(&match, "%s == %s && %s", dir, op->json_key,
>>> rule->match);
>>> +
>>> +    if (!strcmp(rule->action, "mirror")) {
>>> +        ds_put_format(&action, "clone {outport = \"%s\"; ",
>>> +                      ovn_mirror_port_name(mirror->sink));
>>> +        if (egress) {
>>> +            ds_put_format(&action, "next(pipeline=ingress, table=%d);}; ",
>>> +                          ovn_stage_get_table(S_SWITCH_IN_L2_UNKNOWN));
>>> +        } else {
>>> +            ds_put_format(&action, "output;}; ");
>>> +        }
>>> +    }
>>> +
>>> +    ds_put_cstr(&action, "next;");
>>> +
>>> +    ovn_lflow_add(lflows, op->od, stage, rule->priority, ds_cstr(&match),
>>> +                  ds_cstr(&action), op->lflow_ref);
>>> +
>>> +    ds_clear(&match);
>>> +    ds_clear(&action);
>>> +}
>>> +
>>> +static void
>>> +build_mirror_pass_lflow(struct ovn_port *op,
>>> +                        struct lflow_table *lflows,
>>> +                        struct nbrec_mirror *mirror, bool egress)
>>> +{
>>> +    struct ds match = DS_EMPTY_INITIALIZER;
>>> +    struct ds action = DS_EMPTY_INITIALIZER;
>>> +    enum ovn_stage stage;
>>> +    const char *dir;
>>> +
>>> +    ds_put_format(&action, "clone {outport = \"%s\"; ",
>>> +                  ovn_mirror_port_name(mirror->sink));
>>> +
>>> +    if (egress) {
>>> +        dir = "outport";
>>> +        stage = S_SWITCH_OUT_MIRROR;
>>> +        ds_put_format(&action, "next(pipeline=ingress, table=%d);};
>>> next;",
>>> +                      ovn_stage_get_table(S_SWITCH_IN_L2_UNKNOWN));
>>> +    } else {
>>> +        dir = "inport";
>>> +        stage = S_SWITCH_IN_MIRROR;
>>> +        ds_put_format(&action, "output;}; next;");
>>> +    }
>>> +
>>> +    ds_put_format(&match, "%s == %s", dir, op->json_key);
>>> +
>>> +    ovn_lflow_add(lflows, op->od, stage, 100,
>>> +                  ds_cstr(&match), ds_cstr(&action), op->lflow_ref);
>>> +
>>> +    ds_clear(&match);
>>> +    ds_clear(&action);
>>> +}
>>> +
>>> +static void
>>> +build_mirror_lflows(struct ovn_port *op,
>>> +                    const struct hmap *ls_ports,
>>> +                    struct lflow_table *lflows)
>>> +{
>>> +    enum mirror_filter filter;
>>> +
>>> +    if (!op->is_mirror_source_port) {
>>> +        return;
>>> +    }
>>> +
>>> +    for (size_t i = 0; i < op->nbsp->n_mirror_rules; i++) {
>>> +        struct nbrec_mirror *mirror = op->nbsp->mirror_rules[i];
>>> +        struct ovn_port *target_port = ovn_port_find(ls_ports,
>>> +                            ovn_mirror_port_name(mirror->sink));
>>> +
>>> +        if (strcmp(mirror->type, "lport") || !target_port) {
>>> +            continue;
>>> +        }
>>> +
>>> +        filter = !strcmp(mirror->filter, "from-lport") ? IN_MIRROR :
>>> +                 !strcmp(mirror->filter, "to-lport") ? OUT_MIRROR
>>> +                 : BOTH_MIRROR;
>>> +
>>> +        if (filter == IN_MIRROR || filter ==  BOTH_MIRROR) {
>>> +            build_mirror_pass_lflow(op, lflows, mirror, false);
>>> +        }
>>> +        if (filter == OUT_MIRROR || filter == BOTH_MIRROR) {
>>> +            build_mirror_pass_lflow(op, lflows, mirror, 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, lflows, mirror, rule, false);
>>> +            }
>>> +            if (filter == OUT_MIRROR || filter == BOTH_MIRROR) {
>>> +                build_mirror_lflow(op, lflows, mirror, rule, true);
>>> +            }
>>> +        }
>>> +    }
>>> +}
>>> +
>>>   /* 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.
>>> @@ -17366,6 +17587,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);
>>>
>>> @@ -17441,6 +17663,7 @@ build_lswitch_and_lrouter_iterate_by_lsp(struct
>>> ovn_port *op,
>>>       ovs_assert(op->nbsp);
>>>
>>>       /* 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 9457a7be6..e661be7c7 100644
>>> --- a/northd/northd.h
>>> +++ b/northd/northd.h
>>> @@ -35,6 +35,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;
>>> @@ -429,37 +430,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, \
>>> +    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,  19, \
>>> +     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, PRE_ACL,      0, "ls_out_pre_acl")
>>> \
>>> @@ -469,11 +471,12 @@ enum ovn_stage {
>>>       PIPELINE_STAGE(SWITCH, OUT, ACL_EVAL,     4, "ls_out_acl_eval")
>>>   \
>>>       PIPELINE_STAGE(SWITCH, OUT, ACL_SAMPLE,   5, "ls_out_acl_sample")
>>>   \
>>>       PIPELINE_STAGE(SWITCH, OUT, ACL_ACTION,   6, "ls_out_acl_action")
>>>   \
>>> -    PIPELINE_STAGE(SWITCH, OUT, QOS,          7, "ls_out_qos")       \
>>> -    PIPELINE_STAGE(SWITCH, OUT, STATEFUL,     8, "ls_out_stateful")
>>>   \
>>> -    PIPELINE_STAGE(SWITCH, OUT, CHECK_PORT_SEC,  9,
>>> "ls_out_check_port_sec") \
>>> -    PIPELINE_STAGE(SWITCH, OUT, APPLY_PORT_SEC, 10,
>>> "ls_out_apply_port_sec") \
>>> -                                                                      \
>>> +    PIPELINE_STAGE(SWITCH, OUT, MIRROR,       7, "ls_out_mirror")
>>>   \
>>> +    PIPELINE_STAGE(SWITCH, OUT, QOS,          8, "ls_out_qos")
>>> \
>>> +    PIPELINE_STAGE(SWITCH, OUT, STATEFUL,     9, "ls_out_stateful")
>>>   \
>>> +    PIPELINE_STAGE(SWITCH, OUT, CHECK_PORT_SEC, 10,
>>> "ls_out_check_port_sec") \
>>> +    PIPELINE_STAGE(SWITCH, OUT, APPLY_PORT_SEC, 11,
>>> "ls_out_apply_port_sec") \
>>> +
>>>    \
>>>       /* Logical router ingress stages. */                              \
>>>       PIPELINE_STAGE(ROUTER, IN,  ADMISSION,       0, "lr_in_admission")
>>> \
>>>       PIPELINE_STAGE(ROUTER, IN,  LOOKUP_NEIGHBOR, 1,
>>> "lr_in_lookup_neighbor") \
>>> @@ -636,6 +639,13 @@ struct ovn_port {
>>>        * to NULL. */
>>>       struct ovn_port *cr_port;
>>>
>>> +    /* If this ovn_port is a mirror source port, this field is set to
>>> true. */
>>> +    bool is_mirror_source_port;
>>> +
>>> +    /* If this ovn_port is a mirror target port, this field is set for
>>> +     * a parent port. */
>>> +    const char *mirror_parent_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 ef5cd0c8c..6ee1e3b75 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>
>>> +        A priority-100 logical flow is added for each lport mirror
>>> +        attached to logical switch port, matches all incoming pakcets
>>> +        to attached port and clones the packet and sends cloned pakcet
>>> +        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
>>> @@ -448,7 +476,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 logical ports
>>> @@ -485,7 +513,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
>>> @@ -519,7 +547,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
>>> @@ -595,7 +623,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
>>> @@ -629,7 +657,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
>>> @@ -714,7 +742,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
>>> @@ -893,7 +921,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
>>> @@ -933,7 +961,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
>>> @@ -973,7 +1001,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
>>> @@ -996,7 +1024,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
>>> @@ -1024,7 +1052,7 @@
>>>         </li>
>>>       </ul>
>>>
>>> -    <h3>Ingress Table 13: LB</h3>
>>> +    <h3>Ingress Table 14: LB</h3>
>>>
>>>       <ul>
>>>         <li>
>>> @@ -1104,7 +1132,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
>>> @@ -1135,7 +1163,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
>>> @@ -1153,7 +1181,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
>>> @@ -1188,7 +1216,7 @@
>>>         </li>
>>>       </ul>
>>>
>>> -    <h3>Ingress Table 17: Hairpin</h3>
>>> +    <h3>Ingress Table 18: Hairpin</h3>
>>>       <ul>
>>>         <li>
>>>           <p>
>>> @@ -1226,7 +1254,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
>>> @@ -1311,7 +1339,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
>>> @@ -1351,7 +1379,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
>>> @@ -1391,7 +1419,7 @@
>>>         </li>
>>>       </ul>
>>>
>>> -    <h3>Ingress Table 21: Stateful</h3>
>>> +    <h3>Ingress Table 22: Stateful</h3>
>>>
>>>       <ul>
>>>         <li>
>>> @@ -1414,7 +1442,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
>>> @@ -1749,7 +1777,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
>>> @@ -1810,7 +1838,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
>>> @@ -1891,7 +1919,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
>>> @@ -1920,7 +1948,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
>>> @@ -1955,7 +1983,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
>>> @@ -1998,7 +2026,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
>>> @@ -2224,7 +2252,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
>>> @@ -2277,6 +2305,7 @@ output;
>>>         </li>
>>>       </ul>
>>>
>>> +
>>>       <h3>Egress Table 0: <code>to-lport</code> Pre-ACLs</h3>
>>>
>>>       <p>
>>> @@ -2442,21 +2471,49 @@ output;
>>>         This is similar to ingress table <code>ACL action</code>.
>>>       </p>
>>>
>>> -    <h3>Egress Table 7: <code>to-lport</code> QoS</h3>
>>> +    <h3>Egress Table 7: Mirror </h3>
>>> +
>>> +    <p>
>>> +      Overlay remote mirror table contains the following
>>> +      logical flows:
>>> +    </p>
>>> +
>>> +    <ul>
>>> +      <li>
>>> +        A priority-100 logical flow is added for each lport mirror
>>> +        attached to logical switch port, matches all outcoming pakcets
>>> +        to attached port and clones the packet and sends cloned pakcet
>>> +        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 8: <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 8: Stateful</h3>
>>> +    <h3>Egress Table 9: 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 9: Egress Port Security - check</h3>
>>> +    <h3>Egress Table 10: Egress Port Security - check</h3>
>>>
>>>       <p>
>>>         This is similar to the port security logic in table
>>> @@ -2485,7 +2542,7 @@ output;
>>>         </li>
>>>       </ul>
>>>
>>> -    <h3>Egress Table 10: Egress Port Security - Apply</h3>
>>> +    <h3>Egress Table 11: Egress Port Security - Apply</h3>
>>>
>>>       <p>
>>>         This is similar to the ingress port security logic in ingress table
>>> diff --git a/ovn-nb.ovsschema b/ovn-nb.ovsschema
>>> index 09361920f..ef1cf1878 100644
>>> --- a/ovn-nb.ovsschema
>>> +++ b/ovn-nb.ovsschema
>>> @@ -1,7 +1,7 @@
>>>   {
>>>       "name": "OVN_Northbound",
>>> -    "version": "7.8.0",
>>> -    "cksum": "3497747919 38626",
>>> +    "version": "7.9.0",
>>> +    "cksum": "158800519 39495",
>>>       "tables": {
>>>           "NB_Global": {
>>>               "columns": {
>>> @@ -363,13 +363,32 @@
>>>                   "type": {"type": {"key": {"type": "string",
>>>                                             "enum": ["set", ["gre",
>>>                                                              "erspan",
>>> -                                                           "local"]]}}},
>>> +                                                           "local",
>>> +                                                           "lport"]]}}},
>>>                   "index": {"type": "integer"},
>>> +                "mirror_rules": {
>>> +                    "type": {
>>> +                        "key": {
>>> +                            "type": "uuid",
>>> +                            "refTable": "Mirror_Rule",
>>> +                            "refType": "strong"},
>>> +                        "min": 0,
>>> +                        "max": "unlimited"}},
>>>                   "external_ids": {
>>>                       "type": {"key": "string", "value": "string",
>>>                                "min": 0, "max": "unlimited"}}},
>>>               "indexes": [["name"]],
>>>               "isRoot": true},
>>> +        "Mirror_Rule": {
>>> +            "columns": {
>>> +                "match": {"type": "string"},
>>> +                "action": {"type": {
>>> +                    "key": {"type": "string",
>>> +                            "enum": ["set", ["mirror", "skip"]]}}},
>>> +                "priority": {"type": {"key": {"type": "integer",
>>> +                                              "minInteger": 0,
>>> +                                              "maxInteger": 32767}}}},
>>> +            "isRoot": false},
>>>           "Meter": {
>>>               "columns": {
>>>                   "name": {"type": "string"},
>>> diff --git a/ovn-nb.xml b/ovn-nb.xml
>>> index 4b86f432d..38f7c62ee 100644
>>> --- a/ovn-nb.xml
>>> +++ b/ovn-nb.xml
>>> @@ -3078,7 +3078,7 @@ or
>>>       <column name="type">
>>>         <p>
>>>           The value of this field specifies the mirror type -
>>> <code>gre</code>,
>>> -        <code>erspan</code> or <code>local</code>.
>>> +        <code>erspan</code>, <code>local</code> or <code>lport</code>.
>>>         </p>
>>>       </column>
>>>
>>> @@ -3092,11 +3092,54 @@ or
>>>         </p>
>>>       </column>
>>>
>>> +    <column name="mirror_rules">
>>> +      <p>
>>> +        The value of this field represents the mirror rule for filtering
>>> +        mirror traffic.
>>> +      </p>
>>> +    </column>
>>> +
>>>       <column name="external_ids">
>>>         See <em>External IDs</em> at the beginning of this document.
>>>       </column>
>>>     </table>
>>>
>>> +  <table name="Mirror_Rule" title="Mirror rule entry">
>>> +    <p>
>>> +      Each row in this table represents a mirror rule that can be used
>>> +      for filtering of <code>lport</code> mirrored traffic.
>>> +    </p>
>>> +
>>> +    <column name="match">
>>> +      <p>
>>> +        The match expression, describing which packets should be executed
>>> +        against Mirror Rule action. The Logical_Flow expression language
>>> is
>>> +        used.
>>> +      </p>
>>> +    </column>
>>> +
>>> +    <column name="action">
>>> +      <p>The action to take when the Mirror Rule matches:</p>
>>> +      <ul>
>>> +        <li>
>>> +          <code>mirror</code>: Mirror the matched packet.
>>> +        </li>
>>> +
>>> +        <li>
>>> +          <code>skip</code>: Do not mirror matched packet.
>>> +        </li>
>>> +      </ul>
>>> +    </column>
>>> +
>>> +    <column name="priority">
>>> +      <p>
>>> +        The Mirror Rule priority. Rule with higher priority takes
>>> precedence
>>> +        over those with lower. A rule is uniquely identified by the
>>> priority
>>> +        and match string.
>>> +      </p>
>>> +    </column>
>>> +  </table>
>>> +
>>>     <table name="Meter" title="Meter entry">
>>>       <p>
>>>         Each row in this table represents a meter that can be used for QoS
>>> or
>>> diff --git a/ovn-sb.ovsschema b/ovn-sb.ovsschema
>>> index 3056ecc38..5c48ab08e 100644
>>> --- a/ovn-sb.ovsschema
>>> +++ b/ovn-sb.ovsschema
>>> @@ -1,7 +1,7 @@
>>>   {
>>>       "name": "OVN_Southbound",
>>> -    "version": "20.38.0",
>>> -    "cksum": "3113335473 33491",
>>> +    "version": "20.39.0",
>>> +    "cksum": "3604877222 33559",
>>>       "tables": {
>>>           "SB_Global": {
>>>               "columns": {
>>> @@ -156,7 +156,8 @@
>>>                   "type": {"type": {"key": {"type": "string",
>>>                                             "enum": ["set", ["gre",
>>>                                                              "erspan",
>>> -                                                           "local"]]}}},
>>> +                                                           "local",
>>> +                                                           "lport"]]}}},
>>>                   "index": {"type": "integer"},
>>>                   "external_ids": {
>>>                       "type": {"key": "string", "value": "string",
>>> diff --git a/ovn-sb.xml b/ovn-sb.xml
>>> index 3919b44e3..e2010c8e2 100644
>>> --- a/ovn-sb.xml
>>> +++ b/ovn-sb.xml
>>> @@ -3076,7 +3076,7 @@ tcp.flags = RST;
>>>       <column name="type">
>>>         <p>
>>>           The value of this field specifies the mirror type -
>>> <code>gre</code>,
>>> -        <code>erspan</code> or <code>local</code>.
>>> +        <code>erspan</code>, <code>local</code> or <code>lport</code>.
>>>         </p>
>>>       </column>
>>>
>>> diff --git a/tests/ovn-macros.at b/tests/ovn-macros.at
>>> index efb333a47..0d702db1a 100644
>>> --- a/tests/ovn-macros.at
>>> +++ b/tests/ovn-macros.at
>>> @@ -1384,11 +1384,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-nbctl.at b/tests/ovn-nbctl.at
>>> index 39c02d295..8f27d03b8 100644
>>> --- a/tests/ovn-nbctl.at
>>> +++ b/tests/ovn-nbctl.at
>>> @@ -457,13 +457,15 @@ check ovn-nbctl ls-add sw0
>>>   check ovn-nbctl lsp-add sw0 sw0-port1
>>>   check ovn-nbctl lsp-add sw0 sw0-port2
>>>   check ovn-nbctl lsp-add sw0 sw0-port3
>>> +check ovn-nbctl lsp-add sw0 sw0-port4
>>> +check ovn-nbctl mirror-add mirror-lport lport to-lport sw0-port1
>>>
>>>   dnl Add duplicate mirror name
>>>   AT_CHECK([ovn-nbctl mirror-add mirror1 gre 0 from-lport 10.10.10.5], [1],
>>> [], [stderr])
>>>   AT_CHECK([grep 'already exists' stderr], [0], [ignore])
>>>
>>>   dnl Attach invalid source port to mirror
>>> -AT_CHECK([ovn-nbctl lsp-attach-mirror sw0-port4 mirror3], [1], [],
>>> [stderr])
>>> +AT_CHECK([ovn-nbctl lsp-attach-mirror sw0-port5 mirror3], [1], [],
>>> [stderr])
>>>   AT_CHECK([grep 'port name not found' stderr], [0], [ignore])
>>>
>>>   dnl Attach source port to invalid mirror
>>> @@ -479,6 +481,11 @@ dnl Attach one more source port to mirror
>>>   check ovn-nbctl lsp-attach-mirror sw0-port3 mirror3
>>>   check_column "$mirror3uuid" nb:Logical_Switch_Port mirror_rules
>>> name=sw0-port3
>>>
>>> +mirror_lportuuid=$(fetch_column nb:Mirror _uuid name=mirror-lport)
>>> +dnl Attach source port to lport mirror
>>> +check ovn-nbctl lsp-attach-mirror sw0-port4 mirror-lport
>>> +check_column "$mirror_lportuuid" nb:Logical_Switch_Port mirror_rules
>>> name=sw0-port4
>>> +
>>>   dnl Verify if multiple ports are attached to the same mirror properly
>>>   AT_CHECK([ovn-nbctl mirror-list], [0], [dnl
>>>   mirror-local:
>>> @@ -486,6 +493,11 @@ mirror-local:
>>>     Sink     :  10.10.10.3
>>>     Filter   :  both
>>>
>>> +mirror-lport:
>>> +  Type     :  lport
>>> +  Sink     :  sw0-port1
>>> +  Filter   :  to-lport
>>> +
>>>   mirror1:
>>>     Type     :  gre
>>>     Sink     :  10.10.10.1
>>> @@ -523,6 +535,11 @@ check_column "" nb:Logical_Switch_Port mirror_rules
>>> name=sw0-port1
>>>
>>>   dnl Check if the mirror deleted properly
>>>   AT_CHECK([ovn-nbctl mirror-list], [0], [dnl
>>> +mirror-lport:
>>> +  Type     :  lport
>>> +  Sink     :  sw0-port1
>>> +  Filter   :  to-lport
>>> +
>>>   mirror1:
>>>     Type     :  gre
>>>     Sink     :  10.10.10.1
>>> @@ -537,9 +554,12 @@ mirror2:
>>>
>>>   ])
>>>
>>> -dnl Delete another mirror
>>> +dnl Delete mirrors one more mirror
>>>   check ovn-nbctl mirror-del mirror2
>>>
>>> +dnl Delete one more mirror
>>> +check ovn-nbctl mirror-del mirror-lport
>>> +
>>>   dnl Update the Sink address
>>>   check ovn-nbctl set mirror . sink=192.168.1.13
>>>
>>> @@ -559,6 +579,81 @@ AT_CHECK([ovn-nbctl mirror-list], [0], [dnl
>>>
>>>   dnl ---------------------------------------------------------------------
>>>
>>> +OVN_NBCTL_TEST([ovn_nbctl_mirrors_rules], [mirror_rules], [
>>> +check ovn-nbctl ls-add sw0
>>> +check ovn-nbctl lsp-add sw0 sw0-p1
>>> +check ovn-nbctl mirror-add mirror1 lport from-lport sw0-p1
>>> +check ovn-nbctl mirror-add mirror2 lport to-lport sw0-p1
>>> +
>>> +check ovn-nbctl mirror-rule-add mirror1 100 '1' mirror
>>> +check ovn-nbctl mirror-rule-add mirror2 150 'ip' mirror
>>> +
>>> +dnl Add mirror rule for non-exist mirror name
>>> +AT_CHECK([ovn-nbctl mirror-rule-add mirror5 150 'ip' allow], [1], [],
>>> [stderr])
>>> +AT_CHECK([grep 'not found' stderr], [0], [ignore])
>>> +
>>> +dnl Add same mirror rule for mirror
>>> +AT_CHECK([ovn-nbctl mirror-rule-add mirror2 150 'ip' mirror], [1], [],
>>> [stderr])
>>> +AT_CHECK([grep 'exists' stderr], [0], [ignore])
>>> +
>>> +AT_CHECK([ovn-nbctl mirror-list], [0], [dnl
>>> +mirror1:
>>> +  Type     :  lport
>>> +  Sink     :  sw0-p1
>>> +  Filter   :  from-lport
>>> +  Rules    :
>>> +         100                              1          mirror
>>> +
>>> +mirror2:
>>> +  Type     :  lport
>>> +  Sink     :  sw0-p1
>>> +  Filter   :  to-lport
>>> +  Rules    :
>>> +         150                             ip          mirror
>>> +
>>> +])
>>> +
>>> +dnl Add one more mirror rule to mirror
>>> +check ovn-nbctl mirror-rule-add mirror2 200 'icmp' mirror
>>> +
>>> +check ovn-nbctl mirror-rule-add mirror2 250 'icmp' skip
>>> +
>>> +dnl Mirror rule attach mirror
>>> +mirrorrule1uuid=$(fetch_column nb:Mirror_rule _uuid name=mirror1)
>>> +check_column "$mirrorrule1uuid" nb:Mirror mirror_rule
>>> mirror_rule="$mirrorrule1uuid"
>>> +
>>> +dnl Remove the mirror rule by name
>>> +check ovn-nbctl mirror-rule-del mirror1
>>> +
>>> +dnl Mirror rule dettach mirror
>>> +mirrorr1uuid=$(fetch_column nb:Mirror _uuid name=mirror1)
>>> +check_column "" nb:Mirror mirror_rule mirror_rule="mirrorr1uuid"
>>> +
>>> +dnl Delete mirror rule by priority
>>> +check ovn-nbctl mirror-rule-del mirror2 200
>>> +
>>> +dnl Remove the mirror rule by priority and match
>>> +check ovn-nbctl mirror-rule-del mirror2 250 'icmp'
>>> +
>>> +AT_CHECK([ovn-nbctl mirror-list], [0], [dnl
>>> +mirror1:
>>> +  Type     :  lport
>>> +  Sink     :  sw0-p1
>>> +  Filter   :  from-lport
>>> +
>>> +mirror2:
>>> +  Type     :  lport
>>> +  Sink     :  sw0-p1
>>> +  Filter   :  to-lport
>>> +  Rules    :
>>> +         150                             ip          mirror
>>> +
>>> +])
>>> +
>>> +])
>>> +
>>> +dnl ---------------------------------------------------------------------
>>> +
>>>   OVN_NBCTL_TEST([ovn_nbctl_nats], [NATs], [
>>>   AT_CHECK([ovn-nbctl lr-add lr0])
>>>   AT_CHECK([ovn-nbctl lr-nat-add lr0 snatt 30.0.0.2 192.168.1.2], [1], [],
>>> diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
>>> index 21d9d63ab..ba23ec594 100644
>>> --- a/tests/ovn-northd.at
>>> +++ b/tests/ovn-northd.at
>>> @@ -14384,3 +14384,243 @@ AT_CHECK([ovn-sbctl lflow-list S1 | grep
>>> ls_out_acl_action | grep priority=500 |
>>>
>>>   AT_CLEANUP
>>>   ])
>>> +
>>> +OVN_FOR_EACH_NORTHD_NO_HV([
>>> +AT_SETUP([Check NB mirror rules sync])
>>> +AT_KEYWORDS([mirror rules])
>>> +ovn_start
>>> +
>>> +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
>>> +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 to-lport sw0-target0
>>> +check ovn-nbctl mirror-rule-add mirror1 100 'ip' mirror
>>> +check ovn-nbctl mirror-rule-add mirror1 150 'icmp' skip
>>> +
>>> +check ovn-nbctl mirror-add mirror2 lport from-lport sw1-target1
>>> +check ovn-nbctl mirror-rule-add mirror2 100 '1' mirror
>>> +
>>> +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
>>> +ovn-nbctl --wait=sb sync
>>> +
>>> +AT_CHECK([ovn-sbctl list port_binding | grep mp-sw0-target0], [0], [dnl
>>> +logical_port        : mp-sw0-target0
>>> +])
>>> +
>>> +AT_CHECK([ovn-sbctl list port_binding | grep is-mirror], [0], [dnl
>>> +options             : {is-mirror="true"}
>>> +])
>>> +
>>> +check_column sw0-target0 sb:port_binding parent_port
>>> +
>>> +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=(clone {outport = "mp-sw0-target0";
>>> next(pipeline=ingress, table=??);}; next;)
>>> +])
>>> +
>>> +check ovn-nbctl lsp-attach-mirror sw0-p1 mirror1
>>> +
>>> +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" && ip), action=(clone {outport = "mp-sw0-target0";
>>> next(pipeline=ingress, table=??);}; next;)
>>> +  table=??(ls_out_mirror      ), priority=100  , match=(outport ==
>>> "sw0-p1"), action=(clone {outport = "mp-sw0-target0";
>>> next(pipeline=ingress, table=??);}; next;)
>>> +  table=??(ls_out_mirror      ), priority=150  , match=(outport ==
>>> "sw0-p1" && icmp), action=(next;)
>>> +])
>>> +
>>> +check ovn-nbctl lsp-attach-mirror sw0-p1 mirror2
>>> +
>>> +AT_CHECK([ovn-sbctl list port_binding | grep mp-sw0-target0], [0], [dnl
>>> +logical_port        : mp-sw0-target0
>>> +])
>>> +
>>> +AT_CHECK([ovn-sbctl list port_binding | grep mp-sw1-target1], [0], [dnl
>>> +logical_port        : mp-sw1-target1
>>> +])
>>> +
>>> +AT_CHECK([ovn-sbctl list port_binding | grep is-mirror], [0], [dnl
>>> +options             : {is-mirror="true"}
>>> +options             : {is-mirror="true"}
>>> +])
>>> +
>>> +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" && 1), action=(clone {outport = "mp-sw1-target1"; output;}; next;)
>>> +  table=??(ls_in_mirror       ), priority=100  , match=(inport ==
>>> "sw0-p1"), action=(clone {outport = "mp-sw1-target1"; output;}; next;)
>>> +  table=??(ls_out_mirror      ), priority=0    , match=(1), action=(next;)
>>> +  table=??(ls_out_mirror      ), priority=100  , match=(outport ==
>>> "sw0-p1" && ip), action=(clone {outport = "mp-sw0-target0";
>>> next(pipeline=ingress, table=??);}; next;)
>>> +  table=??(ls_out_mirror      ), priority=100  , match=(outport ==
>>> "sw0-p1"), action=(clone {outport = "mp-sw0-target0";
>>> next(pipeline=ingress, table=??);}; next;)
>>> +  table=??(ls_out_mirror      ), priority=150  , match=(outport ==
>>> "sw0-p1" && icmp), action=(next;)
>>> +])
>>> +
>>> +ovn-nbctl lsp-del 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" && 1), action=(clone {outport = "mp-sw1-target1"; output;}; next;)
>>> +  table=??(ls_in_mirror       ), priority=100  , match=(inport ==
>>> "sw0-p1"), action=(clone {outport = "mp-sw1-target1"; output;}; next;)
>>> +  table=??(ls_out_mirror      ), priority=0    , match=(1), action=(next;)
>>> +])
>>> +
>>> +ovn-nbctl lsp-add 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" && 1), action=(clone {outport = "mp-sw1-target1"; output;}; next;)
>>> +  table=??(ls_in_mirror       ), priority=100  , match=(inport ==
>>> "sw0-p1"), action=(clone {outport = "mp-sw1-target1"; output;}; next;)
>>> +  table=??(ls_out_mirror      ), priority=0    , match=(1), action=(next;)
>>> +  table=??(ls_out_mirror      ), priority=100  , match=(outport ==
>>> "sw0-p1" && ip), action=(clone {outport = "mp-sw0-target0";
>>> next(pipeline=ingress, table=??);}; next;)
>>> +  table=??(ls_out_mirror      ), priority=100  , match=(outport ==
>>> "sw0-p1"), action=(clone {outport = "mp-sw0-target0";
>>> next(pipeline=ingress, table=??);}; next;)
>>> +  table=??(ls_out_mirror      ), priority=150  , match=(outport ==
>>> "sw0-p1" && icmp), action=(next;)
>>> +])
>>> +
>>> +
>>> +ovn-nbctl mirror-del
>>> +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;)
>>> +])
>>> +
>>> +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
>>> +
>>> +AT_CHECK([ovn-sbctl list port_binding | grep mp-sw1-target ], [0], [dnl
>>> +logical_port        : mp-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" && 1), action=(clone {outport = "mp-sw1-target"; output;}; next;)
>>> +  table=??(ls_in_mirror       ), priority=100  , match=(inport ==
>>> "sw0-p1"), action=(clone {outport = "mp-sw1-target"; output;}; 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 "output"], [0], [dnl
>>> +    output("mp-sw1-target");
>>> +    output("sw0-p1");
>>> +    output("sw0-p1");
>>> +])
>>> +
>>> +ovn-nbctl mirror-rule-del mirror1
>>> +
>>> +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=(clone {outport = "mp-sw1-target"; output;}; 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 "output"], [0], [dnl
>>> +    output("mp-sw1-target");
>>> +    output("sw0-p1");
>>> +    output("sw0-p1");
>>> +])
>>> +
>>> +ovn-nbctl mirror-del
>>> +
>>> +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 "output"], [0], [dnl
>>> +    output("sw0-p1");
>>> +    output("sw0-p1");
>>> +])
>>> +
>>> +AT_CLEANUP
>>> +])
>>> diff --git a/utilities/ovn-nbctl.c b/utilities/ovn-nbctl.c
>>> index 131d9f0bf..b9add6500 100644
>>> --- a/utilities/ovn-nbctl.c
>>> +++ b/utilities/ovn-nbctl.c
>>> @@ -290,9 +290,10 @@ QoS commands:\n\
>>>     qos-list SWITCH           print QoS rules for SWITCH\n\
>>>   \n\
>>>   Mirror commands:\n\
>>> -  mirror-add NAME TYPE [INDEX] FILTER {IP | MIRROR-ID} \n\
>>> +  mirror-add NAME TYPE [INDEX] FILTER {IP | MIRROR-ID| TARGET-PORT} \n\
>>>                               add a mirror with given name\n\
>>> -                            specify TYPE 'gre', 'erspan', or 'local'\n\
>>> +                            specify TYPE 'gre', 'erspan', 'local'\n\
>>> +                                or 'lport'.\n\
>>>                               specify the tunnel INDEX value\n\
>>>                                   (indicates key if GRE\n\
>>>                                    erpsan_idx if ERSPAN)\n\
>>> @@ -301,8 +302,16 @@ Mirror commands:\n\
>>>                               specify Sink / Destination i.e. Remote IP, or
>>> a\n\
>>>                                   local interface with
>>> external-ids:mirror-id\n\
>>>                                   matching MIRROR-ID\n\
>>> +                                In case of lport type specify logical
>>> switch\n\
>>> +                                port, which is a mirror target.\n\
>>>     mirror-del [NAME]         remove mirrors\n\
>>>     mirror-list               print mirrors\n\
>>> +  mirror-rule-add MIRROR-NAME PRIORITY MATCH ACTION \n\
>>> +                            add a mirror rule selection to given lport\n\
>>> +                            mirror.\n\
>>> +                            specify MATCH for selecting mirrored
>>> traffic.\n\
>>> +                            specify ACTION 'mirror' or 'skip'.\n\
>>> +  mirror-rule-del MIRROR-NAME [PRIORITY | MATCH] remove mirrors\n\
>>>   \n\
>>>   Meter commands:\n\
>>>     [--fair]\n\
>>> @@ -1808,11 +1817,11 @@ nbctl_lsp_attach_mirror(struct ctl_context *ctx)
>>>           return;
>>>       }
>>>
>>> +    bool may_exist = shash_find(&ctx->options, "--may-exist") != NULL;
>>>       /* Check if same mirror rule already exists for the lsp */
>>>       for (size_t i = 0; i < lsp->n_mirror_rules; i++) {
>>>           if (uuid_equals(&lsp->mirror_rules[i]->header_.uuid,
>>>                           &mirror->header_.uuid)) {
>>> -            bool may_exist = shash_find(&ctx->options, "--may-exist") !=
>>> NULL;
>>>               if (!may_exist) {
>>>                   ctl_error(ctx, "mirror %s is already attached to the "
>>>                             "logical port %s.",
>>> @@ -7694,16 +7703,18 @@ static char * OVS_WARN_UNUSED_RESULT
>>>   parse_mirror_type(const char *arg, const char **type_p)
>>>   {
>>>       /* Validate type.  Only require the first letter. */
>>> -    if (arg[0] == 'g') {
>>> +    if (!strcmp(arg, "gre")) {
>>>           *type_p = "gre";
>>> -    } else if (arg[0] == 'e') {
>>> +    } else if (!strcmp(arg, "erspan")) {
>>>           *type_p = "erspan";
>>> -    } else if (arg[0] == 'l') {
>>> +    } else if (!strcmp(arg, "local")) {
>>>           *type_p = "local";
>>> +    } else if (!strcmp(arg, "lport")) {
>>> +        *type_p = "lport";
>>>       } else {
>>>           *type_p = NULL;
>>>           return xasprintf("%s: type must be \"gre\", "
>>> -                         "\"erspan\", or \"local\"", arg);
>>> +                         "\"erspan\", or \"local\" or \"lport\"", arg);
>>>       }
>>>       return NULL;
>>>   }
>>> @@ -7716,6 +7727,8 @@ nbctl_pre_mirror_add(struct ctl_context *ctx)
>>>       ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_index);
>>>       ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_sink);
>>>       ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_type);
>>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_mirror_rules);
>>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_logical_switch_port_col_name);
>>>   }
>>>
>>>   static void
>>> @@ -7740,14 +7753,17 @@ nbctl_mirror_add(struct ctl_context *ctx)
>>>           }
>>>       }
>>>
>>> -    /* Type - gre/erspan/local */
>>> +    /* Type - gre/erspan/local/lport */
>>>       error = parse_mirror_type(ctx->argv[pos++], &type);
>>>       if (error) {
>>>           ctx->error = error;
>>>           return;
>>>       }
>>>
>>> -    if (strcmp(type, "local")) {
>>> +    int is_local = !strcmp(type, "local");
>>> +    int is_lport = !strcmp(type, "lport");
>>> +
>>> +    if (!is_local && !is_lport) {
>>>           /* tunnel index / GRE key / ERSPAN idx */
>>>           if (!str_to_long(ctx->argv[pos++], 10, (long int *) &index)) {
>>>               ctl_error(ctx, "Invalid Index");
>>> @@ -7766,7 +7782,7 @@ nbctl_mirror_add(struct ctl_context *ctx)
>>>       sink = ctx->argv[pos++];
>>>
>>>       /* check if it is a valid ip unless it is type 'local' */
>>> -    if (strcmp(type, "local")) {
>>> +    if (!is_local && !is_lport) {
>>>           char *new_sink_ip = normalize_ipv4_addr_str(sink);
>>>           if (!new_sink_ip) {
>>>               new_sink_ip = normalize_ipv6_addr_str(sink);
>>> @@ -7779,6 +7795,21 @@ nbctl_mirror_add(struct ctl_context *ctx)
>>>           free(new_sink_ip);
>>>       }
>>>
>>> +    /* Check if it is an existing port for lport mirror type. */
>>> +    if (is_lport) {
>>> +        const struct nbrec_logical_switch_port *lsp;
>>> +        error = lsp_by_name_or_uuid(ctx, sink, false, &lsp);
>>> +        if (error) {
>>> +            ctx->error = error;
>>> +            return;
>>> +        }
>>> +
>>> +        if (!lsp) {
>>> +            VLOG_WARN("Attaching target to unexisting port with name %s.",
>>> +                      sink);
>>> +        }
>>> +    }
>>> +
>>>       /* Create the mirror. */
>>>       struct nbrec_mirror *mirror = nbrec_mirror_insert(ctx->txn);
>>>       nbrec_mirror_set_name(mirror, name);
>>> @@ -7818,6 +7849,143 @@ nbctl_mirror_del(struct ctl_context *ctx)
>>>       }
>>>   }
>>>
>>> +static int
>>> +rule_cmp(const void *mirror1_, const void *mirror2_)
>>> +{
>>> +    const struct nbrec_mirror_rule *const *mirror_1 = mirror1_;
>>> +    const struct nbrec_mirror_rule *const *mirror_2 = mirror2_;
>>> +
>>> +    const struct nbrec_mirror_rule *mirror1 = *mirror_1;
>>> +    const struct nbrec_mirror_rule *mirror2 = *mirror_2;
>>> +
>>> +    int result =  mirror1->priority - mirror2->priority;
>>> +    if (result) {
>>> +        return result;
>>> +    }
>>> +
>>> +    result = strcmp(mirror1->match, mirror2->match);
>>> +    if (result) {
>>> +        return result;
>>> +    }
>>> +
>>> +    return strcmp(mirror1->action, mirror2->action);
>>> +}
>>> +
>>> +static void
>>> +nbctl_pre_mirror_rule_add(struct ctl_context *ctx)
>>> +{
>>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_name);
>>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_mirror_rules);
>>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_rule_col_match);
>>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_rule_col_action);
>>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_rule_col_priority);
>>> +}
>>> +
>>> +static void
>>> +nbctl_mirror_rule_add(struct ctl_context *ctx)
>>> +{
>>> +    const struct nbrec_mirror_rule *mirror_rule;
>>> +    const struct nbrec_mirror *mirror;
>>> +    int64_t priority = 0;
>>> +    char *error;
>>> +
>>> +    error = mirror_by_name_or_uuid(ctx, ctx->argv[1], true, &mirror);
>>> +    if (error) {
>>> +        ctx->error = error;
>>> +        return;
>>> +    }
>>> +
>>> +    error = parse_priority(ctx->argv[2], &priority);
>>> +    if (error) {
>>> +        ctx->error = error;
>>> +        return;
>>> +    }
>>> +
>>> +    const char *action = ctx->argv[4];
>>> +    if (strcmp(action, "mirror") && strcmp(action, "skip")) {
>>> +        ctl_error(ctx, "%s: action must be one of \"mirror\", \"skip\"",
>>> +                  action);
>>> +    }
>>> +
>>> +    mirror_rule = nbrec_mirror_rule_insert(ctx->txn);
>>> +    nbrec_mirror_rule_set_match(mirror_rule, ctx->argv[3]);
>>> +    nbrec_mirror_rule_set_action(mirror_rule, action);
>>> +    nbrec_mirror_rule_set_priority(mirror_rule, priority);
>>> +
>>> +    bool may_exist = shash_find(&ctx->options, "--may-exist") != NULL;
>>> +    /* Check if same mirror rule exists for this mirror. */
>>> +    for (size_t i = 0; i < mirror->n_mirror_rules; i++) {
>>> +        if (!rule_cmp(&mirror_rule, &mirror->mirror_rules[i])) {
>>> +            if (!may_exist) {
>>> +                ctl_error(ctx, "Same mirror-rule already exists on the "
>>> +                          "mirror %s.", ctx->argv[1]);
>>> +            } else {
>>> +                nbrec_mirror_rule_delete(mirror_rule);
>>> +            }
>>> +            return;
>>> +        }
>>> +    }
>>> +
>>> +    /* Insert mirror rule to mirror. */
>>> +    nbrec_mirror_update_mirror_rules_addvalue(mirror, mirror_rule);
>>> +}
>>> +
>>> +static void
>>> +nbctl_pre_mirror_rule_del(struct ctl_context *ctx)
>>> +{
>>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_name);
>>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_mirror_rules);
>>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_rule_col_action);
>>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_rule_col_priority);
>>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_rule_col_match);
>>> +}
>>> +
>>> +static void
>>> +nbctl_mirror_rule_del(struct ctl_context *ctx)
>>> +{
>>> +    const struct nbrec_mirror *mirror = NULL;
>>> +    int64_t priority = 0;
>>> +    char *error = NULL;
>>> +
>>> +    error = mirror_by_name_or_uuid(ctx, ctx->argv[1], true, &mirror);
>>> +    if (error) {
>>> +        ctx->error = error;
>>> +        return;
>>> +    }
>>> +
>>> +    if (ctx->argc == 2) {
>>> +        for (size_t i = 0; i < mirror->n_mirror_rules; i++) {
>>> +            nbrec_mirror_update_mirror_rules_delvalue(mirror,
>>> +                                    mirror->mirror_rules[i]);
>>> +        }
>>> +        return;
>>> +    }
>>> +
>>> +    error = parse_priority(ctx->argv[2], &priority);
>>> +    if (error) {
>>> +        ctx->error = error;
>>> +        return;
>>> +    }
>>> +
>>> +    if (ctx->argc == 3) {
>>> +        for (size_t i = 0; i < mirror->n_mirror_rules; i++) {
>>> +            struct nbrec_mirror_rule *rule = mirror->mirror_rules[i];
>>> +            if (priority == rule->priority) {
>>> +                nbrec_mirror_update_mirror_rules_delvalue(mirror, rule);
>>> +                return;
>>> +            }
>>> +        }
>>> +    }
>>> +
>>> +    for (size_t i = 0; i < mirror->n_mirror_rules; i++) {
>>> +        struct nbrec_mirror_rule *rule = mirror->mirror_rules[i];
>>> +        if (priority == rule->priority && !strcmp(ctx->argv[3],
>>> +            rule->match)) {
>>> +            nbrec_mirror_update_mirror_rules_delvalue(mirror, rule);
>>> +        }
>>> +    }
>>> +}
>>> +
>>>   static void
>>>   nbctl_pre_mirror_list(struct ctl_context *ctx)
>>>   {
>>> @@ -7827,6 +7995,10 @@ nbctl_pre_mirror_list(struct ctl_context *ctx)
>>>       ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_index);
>>>       ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_sink);
>>>       ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_type);
>>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_mirror_rules);
>>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_rule_col_action);
>>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_rule_col_match);
>>> +    ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_rule_col_priority);
>>>   }
>>>
>>>   static void
>>> @@ -7853,15 +8025,27 @@ nbctl_mirror_list(struct ctl_context *ctx)
>>>
>>>       for (size_t i = 0; i < n_mirrors; i++) {
>>>           mirror = mirrors[i];
>>> +        bool is_lport = !strcmp(mirror->type, "lport");
>>> +        bool is_local = !strcmp(mirror->type, "local");
>>> +
>>>           ds_put_format(&ctx->output, "%s:\n", mirror->name);
>>>           /* print all the values */
>>>           ds_put_format(&ctx->output, "  Type     :  %s\n", mirror->type);
>>>           ds_put_format(&ctx->output, "  Sink     :  %s\n", mirror->sink);
>>>           ds_put_format(&ctx->output, "  Filter   :  %s\n", mirror->filter);
>>> -        if (strcmp(mirror->type, "local")) {
>>> +        if (!is_local && !is_lport) {
>>>               ds_put_format(&ctx->output, "  Index/Key:  %"PRId64"\n",
>>>                             mirror->index);
>>>           }
>>> +        if (mirror->n_mirror_rules) {
>>> +            ds_put_cstr(&ctx->output, "  Rules    :\n");
>>> +            for (size_t j = 0; j < mirror->n_mirror_rules; j++) {
>>> +                ds_put_format(&ctx->output, "       %5"PRId64" %30s
>>> %15s\n",
>>> +                              mirror->mirror_rules[j]->priority,
>>> +                              mirror->mirror_rules[j]->match,
>>> +                              mirror->mirror_rules[j]->action);
>>> +            }
>>> +        }
>>>           ds_put_cstr(&ctx->output, "\n");
>>>       }
>>>
>>> @@ -7966,13 +8150,19 @@ static const struct ctl_command_syntax
>>> nbctl_commands[] = {
>>>
>>>       /* mirror commands. */
>>>       { "mirror-add", 4, 5,
>>> -      "NAME TYPE INDEX FILTER IP",
>>> +      "NAME TYPE INDEX FILTER SINK",
>>>         nbctl_pre_mirror_add, nbctl_mirror_add, NULL, "--may-exist", RW },
>>>       { "mirror-del", 0, 1, "[NAME]",
>>>         nbctl_pre_mirror_del, nbctl_mirror_del, NULL, "", RW },
>>>       { "mirror-list", 0, 0, "", nbctl_pre_mirror_list, nbctl_mirror_list,
>>>         NULL, "", RO },
>>>
>>> +    /* Mirror rule commands. */
>>> +    { "mirror-rule-add", 4, 4, "MIRROR-NAME PRIORITY MATCH ACTION",
>>> +      nbctl_pre_mirror_rule_add, nbctl_mirror_rule_add, NULL, "", RW},
>>> +    { "mirror-rule-del", 1, 3, "MIRROR-NAME [PRIORITY MATCH]",
>>> +      nbctl_pre_mirror_rule_del, nbctl_mirror_rule_del, NULL, "", RW },
>>> +
>>>       /* meter commands. */
>>>       { "meter-add", 4, 5, "NAME ACTION RATE UNIT [BURST]",
>>> nbctl_pre_meter_add,
>>>         nbctl_meter_add, NULL, "--fair,--may-exist", RW },
>>> --
>>> 2.34.1
>>>
>>> _______________________________________________
>>> dev mailing list
>>> [email protected]
>>> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
>>>
>>>
>> _______________________________________________
>> dev mailing list
>> [email protected]
>> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
> 

_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to