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.

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.

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.

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


 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

Reply via email to