On Fri, Apr 18, 2025 at 8:52 AM Alexandra Rukomoinikova
<arukomoinikova@k2.cloud> wrote:
>
> Previously, the mirroring feature in OVN only supported tunneling
> traffic to local and remote ports outside the OVN cluster.
>
> With this update, it is now possible to mirror traffic passing through
> a virtual port to a dedicated OVN port.
>
> Added ls_in_mirror and ls_out_mirror stages in ls pipeline.
>
> When attaching an lport mirror to a logical switch port, a new port
> binding named mp-datapath-target-port is created. This port acts as
> a mirror port, with its parent being the target port.
>
> For lport mirroring, logical flows (flows) are generated to handle
> traffic processing based on filtering criteria. Packets matching these
> criteria are cloned and sent to the target port, while the original
> traffic continues along its designated path. If an lport mirror is
> created without specifying any rules, default logical flows are added
> to mirror all incoming and outgoing traffic to the target port.
>
> Signed-off-by: Alexandra Rukomoinikova <arukomoinikova@k2.cloud>
> Signed-off-by: Vladislav Odintsov <vlodintsov@k2.cloud>
> Co-authored-by: Vladislav Odintsov <vlodintsov@k2.cloud>
> Tested-by: Ivan Burnin <iburnin@k2.cloud>
> ---
> v9 -> v10: rebased on main.
>            fixed Numan comments in v9.
>            added index nbrec_mirror_by_port_and_sink.
>            bug was noticed and fixed: if one mirror is attached to several 
> ports,
>            the target port is deleted, everything is fine, and when the 
> target port
>            is returned, a transaction error occurs.
> ---

Thanks for v10.

I've a few comments.  Please see below.

With those addressed:

Acked-by: Numan Siddique <num...@ovn.org>

Numan

>  controller/lflow.h       |  12 +-
>  include/ovn/actions.h    |  12 +-
>  lib/actions.c            |  73 +++++++++
>  lib/ovn-util.c           |   7 +
>  lib/ovn-util.h           |   6 +-
>  northd/en-northd.c       |   6 +
>  northd/inc-proc-northd.c |  10 ++
>  northd/northd.c          | 295 ++++++++++++++++++++++++++++++++++-
>  northd/northd.h          |  78 ++++++----
>  northd/ovn-northd.8.xml  | 120 +++++++++++----
>  ovn-sb.ovsschema         |   5 +-
>  ovn-sb.xml               |  18 +++
>  tests/ovn-macros.at      |  10 +-
>  tests/ovn-northd.at      | 325 +++++++++++++++++++++++++++++++++++++++
>  tests/ovn.at             |   8 +
>  utilities/ovn-trace.c    |  42 +++++
>  16 files changed, 940 insertions(+), 87 deletions(-)
>
> diff --git a/controller/lflow.h b/controller/lflow.h
> index 3fd7ca99f..32549df90 100644
> --- a/controller/lflow.h
> +++ b/controller/lflow.h
> @@ -67,17 +67,17 @@ struct uuid;
>
>  /* Start of LOG_PIPELINE_LEN tables. */
>  #define OFTABLE_LOG_INGRESS_PIPELINE      8
> -#define OFTABLE_OUTPUT_LARGE_PKT_DETECT  40
> -#define OFTABLE_OUTPUT_LARGE_PKT_PROCESS 41
> -#define OFTABLE_REMOTE_OUTPUT            42
> -#define OFTABLE_LOCAL_OUTPUT             43
> -#define OFTABLE_CHECK_LOOPBACK           44
> +#define OFTABLE_OUTPUT_LARGE_PKT_DETECT  41
> +#define OFTABLE_OUTPUT_LARGE_PKT_PROCESS 42
> +#define OFTABLE_REMOTE_OUTPUT            43
> +#define OFTABLE_LOCAL_OUTPUT             44
> +#define OFTABLE_CHECK_LOOPBACK           45
>
>  /* Start of the OUTPUT section of the pipeline. */
>  #define OFTABLE_OUTPUT_INIT OFTABLE_OUTPUT_LARGE_PKT_DETECT
>
>  /* Start of LOG_PIPELINE_LEN tables. */
> -#define OFTABLE_LOG_EGRESS_PIPELINE      45
> +#define OFTABLE_LOG_EGRESS_PIPELINE      46
>  #define OFTABLE_SAVE_INPORT              64
>  #define OFTABLE_LOG_TO_PHY               65
>  #define OFTABLE_MAC_BINDING              66
> diff --git a/include/ovn/actions.h b/include/ovn/actions.h
> index 7fd7b26bf..055d3b26c 100644
> --- a/include/ovn/actions.h
> +++ b/include/ovn/actions.h
> @@ -135,7 +135,8 @@ struct collector_set_ids;
>      OVNACT(CT_ORIG_IP6_DST,   ovnact_result)          \
>      OVNACT(CT_ORIG_TP_DST,    ovnact_result)          \
>      OVNACT(FLOOD_REMOTE,      ovnact_null)            \
> -    OVNACT(CT_STATE_SAVE,     ovnact_result)
> +    OVNACT(CT_STATE_SAVE,     ovnact_result)          \
> +    OVNACT(MIRROR,            ovnact_mirror)          \
>
>  /* enum ovnact_type, with a member OVNACT_<ENUM> for each action. */
>  enum OVS_PACKED_ENUM ovnact_type {
> @@ -538,6 +539,15 @@ struct ovnact_commit_lb_aff {
>      uint16_t timeout;
>  };
>
> +/* OVNACT_MIRROR. */
> +struct ovnact_mirror {
> +    struct ovnact ovnact;
> +
> +    /* Argument. */
> +    char *port;     /* Mirror serving port for output
> +                       action. */
> +};
> +
>  #define OVN_FIELD_NOTE_MAGIC "ovn"
>
>  struct ovn_field_note_header {
> diff --git a/lib/actions.c b/lib/actions.c
> index f6f413be2..5a8e47935 100644
> --- a/lib/actions.c
> +++ b/lib/actions.c
> @@ -5575,6 +5575,77 @@ format_CT_STATE_SAVE(const struct ovnact_result *res, 
> struct ds *s)
>      ds_put_cstr(s, " = ct_state_save();");
>  }
>
> +static void
> +parse_MIRROR_action(struct action_context *ctx)
> +{
> +    if (!lexer_force_match(ctx->lexer, LEX_T_LPAREN)) {
> +        return;
> +    }
> +
> +    if (ctx->lexer->token.type != LEX_T_STRING) {
> +        lexer_syntax_error(ctx->lexer, "expecting port name string");
> +        return;
> +    }
> +
> +    struct ovnact_mirror *mirror = ovnact_put_MIRROR(ctx->ovnacts);
> +    mirror->port = xstrdup(ctx->lexer->token.s);
> +
> +    lexer_get(ctx->lexer);
> +
nit:  You can remove this newline.  Not required.
> +    lexer_force_match(ctx->lexer, LEX_T_RPAREN);
> +}
> +
> +static void
> +format_MIRROR(const struct ovnact_mirror *mirror,
> +              struct ds *s)
> +{
> +    ds_put_cstr(s, "mirror(");
> +    ds_put_format(s, "\"%s\"", mirror->port);
> +    ds_put_cstr(s, ");");
> +}
> +
> +static void
> +encode_MIRROR(const struct ovnact_mirror *mirror,
> +              const struct ovnact_encode_params *ep,
> +              struct ofpbuf *ofpacts)
> +{
> +    size_t clone_ofs = ofpacts->size;
> +    uint32_t vport_key;
> +
> +    if (!ep->lookup_port(ep->aux, mirror->port, &vport_key)) {
> +        return;
> +    }
> +
> +    struct ofpact_nest *clone = ofpact_put_CLONE(ofpacts);
> +
> +    /*  We need set in_port to 0. Consider the following configuration:
> +       - hostA (target lport, source lport) + (dst lport) hostB
> +       - mirror in both directions (to and from lport) attached
> +         to dst port.
> +
> +        When a packet comes from lport source to dst lport, for cloned
> +        mirrored packet inport will be equal to outport, and incoming
> +        traffic will not be mirrored.
> +
> +        We have no problem with zeroing in_port: it will be no recirculations
> +        for packet proccesing on hostA, because we skip conntrack for traffic
> +        directed to the target port.
> +    */

nit:  Please format the multi line comments like the rest of the code base.


> +    put_load(ofp_to_u16(OFPP_NONE), MFF_IN_PORT, 0, 16, ofpacts);
> +    put_load(vport_key, MFF_LOG_OUTPORT, 0, 32, ofpacts);
> +    emit_resubmit(ofpacts, OFTABLE_REMOTE_OUTPUT);
> +    clone = ofpbuf_at_assert(ofpacts, clone_ofs, sizeof *clone);
> +    ofpacts->header = clone;
> +
> +    ofpact_finish_CLONE(ofpacts, &clone);
> +}
> +
> +static void
> +ovnact_mirror_free(struct ovnact_mirror *mirror OVS_UNUSED)
> +{
> +    free(mirror->port);
> +}
> +
>  /* Parses an assignment or exchange or put_dhcp_opts action. */
>  static void
>  parse_set_action(struct action_context *ctx)
> @@ -5808,6 +5879,8 @@ parse_action(struct action_context *ctx)
>          ovnact_put_MAC_CACHE_USE(ctx->ovnacts);
>      } else if (lexer_match_id(ctx->lexer, "flood_remote")) {
>          ovnact_put_FLOOD_REMOTE(ctx->ovnacts);
> +    } else if (lexer_match_id(ctx->lexer, "mirror")) {
> +        parse_MIRROR_action(ctx);
>      } else {
>          lexer_syntax_error(ctx->lexer, "expecting action");
>      }
> diff --git a/lib/ovn-util.c b/lib/ovn-util.c
> index c825e0936..e2682ead7 100644
> --- a/lib/ovn-util.c
> +++ b/lib/ovn-util.c
> @@ -1413,3 +1413,10 @@ ovn_debug_commands_register(void)
>      unixctl_command_register("debug/enable-timewarp", "", 0, 0,
>                               ovn_enable_timewarp, NULL);
>  }
> +
> +char *
> +ovn_mirror_port_name(const char *datapath_name,
> +                     const char *port_name)
> +{
> +    return xasprintf("mp-%s-%s", datapath_name, port_name);
> +}
> diff --git a/lib/ovn-util.h b/lib/ovn-util.h
> index 0fff9b463..3c04ff221 100644
> --- a/lib/ovn-util.h
> +++ b/lib/ovn-util.h
> @@ -199,6 +199,8 @@ get_sb_port_group_name(const char *nb_pg_name, int64_t 
> dp_tunnel_key,
>  }
>
>  char *ovn_chassis_redirect_name(const char *port_name);
> +char *ovn_mirror_port_name(const char *datapath_name,
> +                           const char *port_name);
>  void ovn_set_pidfile(const char *name);
>
>  bool ip46_parse_cidr(const char *str, struct in6_addr *prefix,
> @@ -312,8 +314,8 @@ BUILD_ASSERT_DECL(
>  #define SCTP_ABORT_CHUNK_FLAG_T (1 << 0)
>
>  /* The number of tables for the ingress and egress pipelines. */
> -#define LOG_PIPELINE_INGRESS_LEN 30
> -#define LOG_PIPELINE_EGRESS_LEN 13
> +#define LOG_PIPELINE_INGRESS_LEN 31
> +#define LOG_PIPELINE_EGRESS_LEN 14
>
>  static inline uint32_t
>  hash_add_in6_addr(uint32_t hash, const struct in6_addr *addr)
> diff --git a/northd/en-northd.c b/northd/en-northd.c
> index 7cea8863c..4fc452f35 100644
> --- a/northd/en-northd.c
> +++ b/northd/en-northd.c
> @@ -61,6 +61,10 @@ northd_get_input_data(struct engine_node *node,
>          engine_ovsdb_node_get_index(
>              engine_get_input("SB_fdb", node),
>              "sbrec_fdb_by_dp_and_port");
> +    input_data->nbrec_mirror_by_type_and_sink =
> +        engine_ovsdb_node_get_index(
> +            engine_get_input("NB_mirror", node),
> +            "nbrec_mirror_by_type_and_sink");
>
>      input_data->nbrec_logical_switch_table =
>          EN_OVSDB_GET(engine_get_input("NB_logical_switch", node));
> @@ -72,6 +76,8 @@ northd_get_input_data(struct engine_node *node,
>          EN_OVSDB_GET(engine_get_input("NB_chassis_template_var", node));
>      input_data->nbrec_mirror_table =
>          EN_OVSDB_GET(engine_get_input("NB_mirror", node));
> +    input_data->nbrec_mirror_rule_table =
> +        EN_OVSDB_GET(engine_get_input("NB_mirror_rule", node));
>
>      input_data->sbrec_datapath_binding_table =
>          EN_OVSDB_GET(engine_get_input("SB_datapath_binding", node));
> diff --git a/northd/inc-proc-northd.c b/northd/inc-proc-northd.c
> index 7f92c0cb7..2b28a6a28 100644
> --- a/northd/inc-proc-northd.c
> +++ b/northd/inc-proc-northd.c
> @@ -64,6 +64,7 @@ static unixctl_cb_func chassis_features_list;
>      NB_NODE(acl, "acl") \
>      NB_NODE(logical_router, "logical_router") \
>      NB_NODE(mirror, "mirror") \
> +    NB_NODE(mirror_rule, "mirror_rule") \
>      NB_NODE(meter, "meter") \
>      NB_NODE(bfd, "bfd") \
>      NB_NODE(static_mac_binding, "static_mac_binding") \
> @@ -210,6 +211,7 @@ void inc_proc_northd_init(struct ovsdb_idl_loop *nb,
>      engine_add_input(&en_acl_id, &en_sb_acl_id, NULL);
>
>      engine_add_input(&en_northd, &en_nb_mirror, NULL);
> +    engine_add_input(&en_northd, &en_nb_mirror_rule, NULL);
>      engine_add_input(&en_northd, &en_nb_static_mac_binding, NULL);
>      engine_add_input(&en_northd, &en_nb_chassis_template_var, NULL);
>
> @@ -492,6 +494,14 @@ void inc_proc_northd_init(struct ovsdb_idl_loop *nb,
>      engine_ovsdb_node_add_index(&en_sb_port_binding,
>                                  "sbrec_port_binding_by_name",
>                                  sbrec_port_binding_by_name);
> +
> +    struct ovsdb_idl_index *nbrec_mirror_by_type_and_sink
> +        = ovsdb_idl_index_create2(nb->idl, &nbrec_mirror_col_type,
> +                                  &nbrec_mirror_col_sink);
> +    engine_ovsdb_node_add_index(&en_nb_mirror,
> +                                "nbrec_mirror_by_type_and_sink",
> +                                nbrec_mirror_by_type_and_sink);
> +
>      struct ovsdb_idl_index *sbrec_ecmp_nexthop_by_ip_and_port
>          = ecmp_nexthop_index_create(sb->idl);
>      engine_ovsdb_node_add_index(&en_sb_ecmp_nexthop,
> diff --git a/northd/northd.c b/northd/northd.c
> index 1f9340e55..979dcd53a 100644
> --- a/northd/northd.c
> +++ b/northd/northd.c
> @@ -107,6 +107,11 @@ static bool vxlan_ic_mode;
>   * priority to determine the ACL's logical flow priority. */
>  #define OVN_ACL_PRI_OFFSET 1000
>
> +/* Default logical flows for mirroring are added with the priority described
> + * below, in the case of mirroring rules specified in the northbound database
> + * this value will also be added to the priority. */
> +#define OVN_LPORT_MIRROR_OFFSET 100
> +
>  /* Register definitions specific to switches. */
>  #define REGBIT_CONNTRACK_DEFRAG   "reg0[0]"
>  #define REGBIT_CONNTRACK_COMMIT   "reg0[1]"
> @@ -459,6 +464,12 @@ od_has_lb_vip(const struct ovn_datapath *od)
>      }
>  }
>
> +static const char *
> +ovn_datapath_name(const struct sbrec_datapath_binding *sb)
> +{
> +    return smap_get_def(&sb->external_ids, "name", "");
> +}
> +
>  /* A group of logical router datapaths which are connected - either
>   * directly or indirectly.
>   * Each logical router can belong to only one group. */
> @@ -1197,6 +1208,12 @@ is_transit_router_port(struct ovn_port *op)
>             smap_get_bool(&op->sb->chassis->other_config, "is-remote", false);
>  }
>
> +static bool
> +is_mp_port(const struct ovn_port *op)
> +{
> +    return op->mirror_source_port;
> +}
> +
>  void
>  destroy_routable_addresses(struct ovn_port_routable_addresses *ra)
>  {
> @@ -1286,7 +1303,7 @@ ovn_port_create(struct hmap *ports, const char *key,
>      op->key = xstrdup(key);
>      op->sb = sb;
>      ovn_port_set_nb(op, nbsp, nbrp);
> -    op->primary_port = op->cr_port = NULL;
> +    op->primary_port = op->cr_port = op->mirror_source_port = NULL;
>      hmap_insert(ports, &op->key_node, hash_string(op->key, 0));
>
>      op->lflow_ref = lflow_ref_create();
> @@ -1343,7 +1360,7 @@ ovn_port_destroy(struct hmap *ports, struct ovn_port 
> *port)
>          /* Don't remove port->list. The node should be removed from such 
> lists
>           * before calling this function. */
>          hmap_remove(ports, &port->key_node);
> -        if (port->od && !port->primary_port) {
> +        if (port->od && !port->mirror_source_port && !port->primary_port) {
>              hmap_remove(&port->od->ports, &port->dp_node);
>          }
>          ovn_port_destroy_orphan(port);
> @@ -1460,8 +1477,8 @@ lsp_is_type_changed(const struct sbrec_port_binding *sb,
>
>      if (!sb->type[0] && !nbsp->type[0]) {
>          /* Two "VIF's" interface make sure both have parent_port
> -         * set or both have parent_port unset, otherwisre they are
> -         * different ports type.
> +         * or mirror_port set or both have parent_port/mirror_port
> +         * unset, otherwisre they are different ports type.
>           */
>          if ((!sb->parent_port && nbsp->parent_name) ||
>                          (sb->parent_port && !nbsp->parent_name)) {
> @@ -2183,6 +2200,41 @@ parse_lsp_addrs(struct ovn_port *op)
>          op->n_ps_addrs++;
>      }
>  }
> +
> +static void
> +create_mirror_port(struct ovn_port *op, struct hmap *ports,
> +                   struct ovs_list *both_dbs, struct ovs_list *nb_only,
> +                   const struct nbrec_mirror *nb_mirror)
> +{
> +    char *mp_name = ovn_mirror_port_name(ovn_datapath_name(op->od->sb),
> +                                         nb_mirror->sink);
> +    struct ovn_port *mp = ovn_port_find(ports, mp_name);
> +    struct ovn_port *target_port = ovn_port_find(ports, nb_mirror->sink);
> +
> +    if (!target_port) {
> +        goto clear;
> +    }
> +
> +    if (!mp) {
> +        mp = ovn_port_create(ports, mp_name, op->nbsp, NULL, NULL);
> +        ovs_list_push_back(nb_only, &mp->list);
> +    } else if (mp->sb) {
> +        ovn_port_set_nb(mp, op->nbsp, NULL);
> +        ovs_list_remove(&mp->list);
> +        ovs_list_push_back(both_dbs, &mp->list);
> +    } else {
> +        goto clear;

> +    }
> +
> +    mp->mirror_source_port = op;

I see one problem with having the field mirror_source_port in 'struct ovn_port'.

A mirror port can have multiple source ports.

Eg.,

ovn-nbctl mirror-add mirror1 lport from-lport sw0-p1

ovn-nbctl lsp-attach-mirror sw0-p2 mirror1
ovn-nbctl lsp-attach-mirror sw0-p3 mirror1

In the above scenario,  the function create_mirror_port() will be
called twice. One more sw0-p2 and other for sw0-p3.
And we only store the mp->mirror_source_port for sw0-p2.

Although there is nothing wrong here.  But still its confusing.  I'd
suggest remove the field "mirror_source_port" altogether.

In the code I don't see it being used anywhere other than in the
function is_mp_port() .
The function "is_mp_port() can return true if mp->mirror_target_port
is set to true.


> +    mp->mirror_target_port = target_port;
> +
> +    mp->od = op->od;
> +
> +clear:
> +    free(mp_name);
> +}
> +
>  static struct ovn_port *
>  join_logical_ports_lsp(struct hmap *ports,
>                         struct ovs_list *nb_only, struct ovs_list *both,
> @@ -2190,7 +2242,8 @@ join_logical_ports_lsp(struct hmap *ports,
>                         const struct nbrec_logical_switch_port *nbsp,
>                         const char *name,
>                         unsigned long *queue_id_bitmap,
> -                       struct hmap *tag_alloc_table)
> +                       struct hmap *tag_alloc_table,
> +                       struct hmapx *mirror_attached_ports)
>  {
>      struct ovn_port *op = ovn_port_find_bound(ports, name);
>      if (op && (op->od || op->nbsp || op->nbrp)) {
> @@ -2271,7 +2324,11 @@ join_logical_ports_lsp(struct hmap *ports,
>      }
>      hmap_insert(&od->ports, &op->dp_node,
>                  hmap_node_hash(&op->key_node));
> +    if (nbsp->n_mirror_rules) {
> +        hmapx_add(mirror_attached_ports, op);
> +    }
>      tag_alloc_add_existing_tags(tag_alloc_table, nbsp);
> +
>      return op;
>  }
>
> @@ -2408,6 +2465,21 @@ peer_needs_cr_port_creation(struct ovn_port *op)
>      return false;
>  }
>
> +static void
> +join_mirror_ports(struct ovn_port *op,
> +                  const struct nbrec_logical_switch_port *nbsp,
> +                  struct hmap *ports, struct ovs_list *both,
> +                  struct ovs_list *nb_only)
> +{
> +    /* Create mirror targets port bindings if there any mirror
> +     * with lport type attached to this port. */
> +    for (size_t j = 0; j < op->nbsp->n_mirror_rules; j++) {
> +        struct nbrec_mirror *mirror = nbsp->mirror_rules[j];
> +        if (!strcmp(mirror->type, "lport")) {
> +            create_mirror_port(op, ports, both, nb_only, mirror);
> +        }
> +    }
> +}
>  static void
>  join_logical_ports(const struct sbrec_port_binding_table *sbrec_pb_table,
>                     struct hmap *ls_datapaths, struct hmap *lr_datapaths,
> @@ -2428,6 +2500,8 @@ join_logical_ports(const struct 
> sbrec_port_binding_table *sbrec_pb_table,
>
>      struct ovn_datapath *od;
>      struct hmapx dgps = HMAPX_INITIALIZER(&dgps);
> +    struct hmapx mirror_attached_ports =
> +                    HMAPX_INITIALIZER(&mirror_attached_ports);
>      HMAP_FOR_EACH (od, key_node, lr_datapaths) {
>          ovs_assert(od->nbr);
>          for (size_t i = 0; i < od->nbr->n_ports; i++) {
> @@ -2454,7 +2528,7 @@ join_logical_ports(const struct 
> sbrec_port_binding_table *sbrec_pb_table,
>                  = od->nbs->ports[i];
>              join_logical_ports_lsp(ports, nb_only, both, od, nbsp,
>                                     nbsp->name, queue_id_bitmap,
> -                                   tag_alloc_table);
> +                                   tag_alloc_table, &mirror_attached_ports);
>          }
>      }
>
> @@ -2622,6 +2696,14 @@ join_logical_ports(const struct 
> sbrec_port_binding_table *sbrec_pb_table,
>      }
>      hmapx_destroy(&dgps);
>
> +    HMAPX_FOR_EACH (hmapx_node, &mirror_attached_ports) {
> +        op = hmapx_node->data;
> +        if (op && op->nbsp) {
> +            join_mirror_ports(op, op->nbsp, ports, both, nb_only);
> +        }
> +    }
> +    hmapx_destroy(&mirror_attached_ports);
> +
>      /* Wait until all ports have been connected to add to IPAM since
>       * it relies on proper peers to be set
>       */
> @@ -3300,6 +3382,16 @@ ovn_port_update_sbrec(struct ovsdb_idl_txn *ovnsb_txn,
>
>          sbrec_port_binding_set_external_ids(op->sb, &op->nbrp->external_ids);
>      } else {
> +        if (op->mirror_target_port) {
> +            /* In case of using a lport mirror, we establish a port binding
> +             * with mirror target port to act it like container port without
> +             * tag it by vlan tag. */
> +            sbrec_port_binding_set_type(op->sb, "mirror");
> +            sbrec_port_binding_set_mirror_port(op->sb,
> +                                               op->mirror_target_port->key);
> +            goto common;
> +        }
> +
>          if (!lsp_is_router(op->nbsp)) {
>              uint32_t queue_id = smap_get_int(
>                      &op->sb->options, "qdisc_queue_id", 0);
> @@ -3499,6 +3591,8 @@ ovn_port_update_sbrec(struct ovsdb_idl_txn *ovnsb_txn,
>          }
>
>      }
> +
> +common:
>      if (op->tunnel_key != op->sb->tunnel_key) {
>          sbrec_port_binding_set_tunnel_key(op->sb, op->tunnel_key);
>      }
> @@ -4799,6 +4893,38 @@ check_lsp_changes_other_than_up(const struct 
> nbrec_logical_switch_port *nbsp)
>      return false;
>  }
>
> +static bool
> +is_lsp_mirror_target_port(
> +    struct ovsdb_idl_index *nbrec_mirror_by_type_and_sink,
> +    struct ovn_port *port)
> +{
> +    struct nbrec_mirror *target =
> +        nbrec_mirror_index_init_row(nbrec_mirror_by_type_and_sink);
> +    nbrec_mirror_index_set_type(target, "lport");
> +    nbrec_mirror_index_set_sink(target, port->key);
> +
> +    const struct nbrec_mirror *nb_mirror =
> +        nbrec_mirror_index_find(nbrec_mirror_by_type_and_sink, target);
> +
> +    nbrec_mirror_index_destroy_row(target);
> +
> +    if (nb_mirror) {
> +        return true;
> +    }
> +
> +    return false;
> +}
> +
> +static bool
> +lsp_handle_mirror_rules_changes(const struct nbrec_logical_switch_port *nbsp)
> +{
> +    if (nbrec_logical_switch_port_is_updated(nbsp,
> +        NBREC_LOGICAL_SWITCH_PORT_COL_MIRROR_RULES)) {
> +        return false;
> +    }
> +    return true;
> +}
> +
>  /* Handles logical switch port changes of a changed logical switch.
>   * Returns false, if any logical port can't be incrementally handled.
>   */
> @@ -4869,6 +4995,12 @@ ls_handle_lsp_changes(struct ovsdb_idl_txn 
> *ovnsb_idl_txn,
>                   * by this change. Fallback to recompute. */
>                  goto fail;
>              }
> +            if (!lsp_handle_mirror_rules_changes(new_nbsp) ||
> +                 is_lsp_mirror_target_port(ni->nbrec_mirror_by_type_and_sink,
> +                                           op)) {
> +                /* Fallback to recompute. */
> +                goto fail;
> +            }
>              if (!check_lsp_is_up &&
>                  !check_lsp_changes_other_than_up(new_nbsp)) {
>                  /* If the only change is the "up" column while the
> @@ -4917,6 +5049,12 @@ ls_handle_lsp_changes(struct ovsdb_idl_txn 
> *ovnsb_idl_txn,
>              sbrec_port_binding_delete(op->sb);
>              delete_fdb_entries(ni->sbrec_fdb_by_dp_and_port, od->tunnel_key,
>                                  op->tunnel_key);
> +            if (is_lsp_mirror_target_port(ni->nbrec_mirror_by_type_and_sink,
> +                                          op)) {
> +            /* This port was used as target mirror port, fallback
> +             * to recompute. */
> +                goto fail;
> +            }
>          }
>      }
>
> @@ -5678,6 +5816,146 @@ build_dhcpv6_action(struct ovn_port *op, struct 
> in6_addr *offer_ip,
>      return true;
>  }
>
> +enum mirror_filter {
> +    IN_MIRROR,
> +    OUT_MIRROR,
> +    BOTH_MIRROR,
> +};
> +
> +static void
> +build_mirror_default_lflow(struct ovn_datapath *od,
> +                           struct lflow_table *lflows)
> +{
> +    ovn_lflow_add(lflows, od, S_SWITCH_IN_MIRROR, 0, "1", "next;", NULL);
> +    ovn_lflow_add(lflows, od, S_SWITCH_OUT_MIRROR, 0, "1", "next;", NULL);
> +}
> +
> +static void
> +build_mirror_lflow(struct ovn_port *op,
> +                   struct ovn_port *serving_port,
> +                   struct lflow_table *lflows,
> +                   struct nbrec_mirror_rule *rule, bool egress)
> +{
> +    struct ds match = DS_EMPTY_INITIALIZER;
> +    struct ds action = DS_EMPTY_INITIALIZER;
> +    enum ovn_stage stage;
> +    const char *dir;
> +    uint32_t priority = OVN_LPORT_MIRROR_OFFSET + rule->priority;
> +
> +    if (!strcmp(rule->action, "mirror")) {
> +        ds_put_format(&action, "mirror(%s); ", serving_port->json_key);
> +    }
> +
> +    if (egress) {
> +        dir = "outport";
> +        stage = S_SWITCH_OUT_MIRROR;
> +    } else {
> +        dir = "inport";
> +        stage = S_SWITCH_IN_MIRROR;
> +    }
> +
> +    ds_put_cstr(&action, "next;");
> +    ds_put_format(&match, "%s == %s && (%s)", dir, op->json_key, 
> rule->match);
> +    ovn_lflow_add(lflows, op->od, stage, priority, ds_cstr(&match),
> +                  ds_cstr(&action), op->lflow_ref);
> +
> +    ds_destroy(&match);
> +    ds_destroy(&action);
> +}
> +
> +static void
> +build_mirror_pass_lflow(struct ovn_port *op,
> +                        struct ovn_port *serving_port,
> +                        struct lflow_table *lflows, bool egress)
> +{
> +    struct ds match = DS_EMPTY_INITIALIZER;
> +    struct ds action = DS_EMPTY_INITIALIZER;
> +    enum ovn_stage stage;
> +    const char *dir;
> +
> +    if (egress) {
> +        dir = "outport";
> +        stage = S_SWITCH_OUT_MIRROR;
> +    } else {
> +        dir = "inport";
> +        stage = S_SWITCH_IN_MIRROR;
> +    }
> +
> +    ds_put_format(&action, "mirror(%s); next;", serving_port->json_key);
> +    ds_put_format(&match, "%s == %s", dir, op->json_key);
> +    ovn_lflow_add(lflows, op->od, stage, OVN_LPORT_MIRROR_OFFSET,
> +                  ds_cstr(&match), ds_cstr(&action), op->lflow_ref);
> +
> +    ds_clear(&match);
> +    ds_clear(&action);
> +
> +    /* We need to skip conntrack for all trafic directed to target port.*/
> +    ds_put_format(&action, "next(pipeline=egress, table=%d);",
> +                  ovn_stage_get_table(S_SWITCH_OUT_APPLY_PORT_SEC));
> +    ds_put_format(&match,  "outport == %s", serving_port->json_key);
> +
> +    ovn_lflow_add(lflows, op->od, S_SWITCH_OUT_PRE_ACL, UINT16_MAX,
> +                  ds_cstr(&match), ds_cstr(&action), op->lflow_ref);
> +
> +    ds_destroy(&match);
> +    ds_destroy(&action);
> +}
> +
> +static void
> +build_mirror_lflows(struct ovn_port *op,
> +                    const struct hmap *ls_ports,
> +                    struct lflow_table *lflows)
> +{
> +    enum mirror_filter filter;
> +
> +    for (size_t i = 0; i < op->nbsp->n_mirror_rules; i++) {
> +        struct nbrec_mirror *mirror = op->nbsp->mirror_rules[i];
> +
> +        if (strcmp(mirror->type, "lport")) {
> +            continue;
> +        }
> +
> +        char *serving_port_name = ovn_mirror_port_name(
> +                                        ovn_datapath_name(op->od->sb),
> +                                        mirror->sink);
> +
> +        struct ovn_port *serving_port = ovn_port_find(ls_ports,
> +                                        serving_port_name);
> +
> +        /* Mirror serving port wasn't created
> +         * because the target port doesn't exist.
> +         */
> +        if (!serving_port) {
> +            free(serving_port_name);
> +            continue;
> +        }
> +
> +        filter = !strcmp(mirror->filter, "from-lport") ? IN_MIRROR :
> +                 !strcmp(mirror->filter, "to-lport") ? OUT_MIRROR
> +                 : BOTH_MIRROR;
> +
> +        if (filter == IN_MIRROR || filter ==  BOTH_MIRROR) {
> +            build_mirror_pass_lflow(op, serving_port, lflows, false);
> +        }
> +        if (filter == OUT_MIRROR || filter == BOTH_MIRROR) {
> +            build_mirror_pass_lflow(op, serving_port, lflows, true);
> +        }
> +
> +        for (size_t j = 0; j < mirror->n_mirror_rules; j++) {
> +            struct nbrec_mirror_rule *rule = mirror->mirror_rules[j];
> +
> +            if (filter == IN_MIRROR || filter ==  BOTH_MIRROR) {
> +                build_mirror_lflow(op, serving_port, lflows, rule, false);
> +            }
> +            if (filter == OUT_MIRROR || filter == BOTH_MIRROR) {
> +                build_mirror_lflow(op, serving_port, lflows, rule, true);
> +            }
> +        }
> +
> +        free(serving_port_name);
> +    }
> +}
> +
>  /* Adds the logical flows in the (in/out) check port sec stage only if
>   *   - the lport is disabled or
>   *   - lport is of type vtep - to skip the ingress pipeline.
> @@ -17477,6 +17755,7 @@ build_lswitch_and_lrouter_iterate_by_ls(struct 
> ovn_datapath *od,
>                                          struct lswitch_flow_build_info *lsi)
>  {
>      ovs_assert(od->nbs);
> +    build_mirror_default_lflow(od, lsi->lflows);
>      build_lswitch_lflows_pre_acl_and_acl(od, lsi->lflows,
>                                           lsi->meter_groups, NULL);
>
> @@ -17553,7 +17832,11 @@ build_lswitch_and_lrouter_iterate_by_lsp(struct 
> ovn_port *op,
>  {
>      ovs_assert(op->nbsp);
>
> +    if (is_mp_port(op)) {
> +        return;
> +    }
>      /* Build Logical Switch Flows. */
> +    build_mirror_lflows(op, ls_ports, lflows);
>      build_lswitch_port_sec_op(op, lflows, actions, match);
>      build_lswitch_learn_fdb_op(op, lflows, actions, match);
>      build_lswitch_arp_nd_responder_skip_local(op, lflows, match);
> diff --git a/northd/northd.h b/northd/northd.h
> index 388bac6df..f65fdd006 100644
> --- a/northd/northd.h
> +++ b/northd/northd.h
> @@ -36,6 +36,7 @@ struct northd_input {
>      const struct nbrec_chassis_template_var_table
>          *nbrec_chassis_template_var_table;
>      const struct nbrec_mirror_table *nbrec_mirror_table;
> +    const struct nbrec_mirror_rule_table *nbrec_mirror_rule_table;
>
>      /* Southbound table references */
>      const struct sbrec_datapath_binding_table *sbrec_datapath_binding_table;
> @@ -73,6 +74,7 @@ struct northd_input {
>      struct ovsdb_idl_index *sbrec_ha_chassis_grp_by_name;
>      struct ovsdb_idl_index *sbrec_ip_mcast_by_dp;
>      struct ovsdb_idl_index *sbrec_fdb_by_dp_and_port;
> +    struct ovsdb_idl_index *nbrec_mirror_by_type_and_sink;
>  };
>
>  /* A collection of datapaths. E.g. all logical switch datapaths, or all
> @@ -472,37 +474,38 @@ enum ovn_stage {
>      /* Logical switch ingress stages. */                                  \
>      PIPELINE_STAGE(SWITCH, IN,  CHECK_PORT_SEC, 0, "ls_in_check_port_sec")   
> \
>      PIPELINE_STAGE(SWITCH, IN,  APPLY_PORT_SEC, 1, "ls_in_apply_port_sec")   
> \
> -    PIPELINE_STAGE(SWITCH, IN,  LOOKUP_FDB ,    2, "ls_in_lookup_fdb")    \
> -    PIPELINE_STAGE(SWITCH, IN,  PUT_FDB,        3, "ls_in_put_fdb")       \
> -    PIPELINE_STAGE(SWITCH, IN,  PRE_ACL,        4, "ls_in_pre_acl")       \
> -    PIPELINE_STAGE(SWITCH, IN,  PRE_LB,         5, "ls_in_pre_lb")        \
> -    PIPELINE_STAGE(SWITCH, IN,  PRE_STATEFUL,   6, "ls_in_pre_stateful")  \
> -    PIPELINE_STAGE(SWITCH, IN,  ACL_HINT,       7, "ls_in_acl_hint")      \
> -    PIPELINE_STAGE(SWITCH, IN,  ACL_EVAL,       8, "ls_in_acl_eval")      \
> -    PIPELINE_STAGE(SWITCH, IN,  ACL_SAMPLE,     9, "ls_in_acl_sample")    \
> -    PIPELINE_STAGE(SWITCH, IN,  ACL_ACTION,    10, "ls_in_acl_action")    \
> -    PIPELINE_STAGE(SWITCH, IN,  QOS,           11, "ls_in_qos")    \
> -    PIPELINE_STAGE(SWITCH, IN,  LB_AFF_CHECK,  12, "ls_in_lb_aff_check")  \
> -    PIPELINE_STAGE(SWITCH, IN,  LB,            13, "ls_in_lb")            \
> -    PIPELINE_STAGE(SWITCH, IN,  LB_AFF_LEARN,  14, "ls_in_lb_aff_learn")  \
> -    PIPELINE_STAGE(SWITCH, IN,  PRE_HAIRPIN,   15, "ls_in_pre_hairpin")   \
> -    PIPELINE_STAGE(SWITCH, IN,  NAT_HAIRPIN,   16, "ls_in_nat_hairpin")   \
> -    PIPELINE_STAGE(SWITCH, IN,  HAIRPIN,       17, "ls_in_hairpin")       \
> -    PIPELINE_STAGE(SWITCH, IN,  ACL_AFTER_LB_EVAL,  18, \
> -                   "ls_in_acl_after_lb_eval")                             \
> -     PIPELINE_STAGE(SWITCH, IN,  ACL_AFTER_LB_SAMPLE,  19, \
> +    PIPELINE_STAGE(SWITCH, IN,  MIRROR,         2, "ls_in_mirror")        \
> +    PIPELINE_STAGE(SWITCH, IN,  LOOKUP_FDB,     3, "ls_in_lookup_fdb")    \
> +    PIPELINE_STAGE(SWITCH, IN,  PUT_FDB,        4, "ls_in_put_fdb")       \
> +    PIPELINE_STAGE(SWITCH, IN,  PRE_ACL,        5, "ls_in_pre_acl")       \
> +    PIPELINE_STAGE(SWITCH, IN,  PRE_LB,         6, "ls_in_pre_lb")        \
> +    PIPELINE_STAGE(SWITCH, IN,  PRE_STATEFUL,   7, "ls_in_pre_stateful")  \
> +    PIPELINE_STAGE(SWITCH, IN,  ACL_HINT,       8, "ls_in_acl_hint")      \
> +    PIPELINE_STAGE(SWITCH, IN,  ACL_EVAL,       9, "ls_in_acl_eval")      \
> +    PIPELINE_STAGE(SWITCH, IN,  ACL_SAMPLE,    10, "ls_in_acl_sample")    \
> +    PIPELINE_STAGE(SWITCH, IN,  ACL_ACTION,    11, "ls_in_acl_action")    \
> +    PIPELINE_STAGE(SWITCH, IN,  QOS,           12, "ls_in_qos")           \
> +    PIPELINE_STAGE(SWITCH, IN,  LB_AFF_CHECK,  13, "ls_in_lb_aff_check")  \
> +    PIPELINE_STAGE(SWITCH, IN,  LB,            14, "ls_in_lb")            \
> +    PIPELINE_STAGE(SWITCH, IN,  LB_AFF_LEARN,  15, "ls_in_lb_aff_learn")  \
> +    PIPELINE_STAGE(SWITCH, IN,  PRE_HAIRPIN,   16, "ls_in_pre_hairpin")   \
> +    PIPELINE_STAGE(SWITCH, IN,  NAT_HAIRPIN,   17, "ls_in_nat_hairpin")   \
> +    PIPELINE_STAGE(SWITCH, IN,  HAIRPIN,       18, "ls_in_hairpin")       \
> +    PIPELINE_STAGE(SWITCH, IN,  ACL_AFTER_LB_EVAL,  19, \
> +                   "ls_in_acl_after_lb_eval")    \
> +     PIPELINE_STAGE(SWITCH, IN,  ACL_AFTER_LB_SAMPLE,  20, \
>                     "ls_in_acl_after_lb_sample")  \
> -    PIPELINE_STAGE(SWITCH, IN,  ACL_AFTER_LB_ACTION,  20, \
> +    PIPELINE_STAGE(SWITCH, IN,  ACL_AFTER_LB_ACTION,  21,    \
>                     "ls_in_acl_after_lb_action")  \
> -    PIPELINE_STAGE(SWITCH, IN,  STATEFUL,      21, "ls_in_stateful")      \
> -    PIPELINE_STAGE(SWITCH, IN,  ARP_ND_RSP,    22, "ls_in_arp_rsp")       \
> -    PIPELINE_STAGE(SWITCH, IN,  DHCP_OPTIONS,  23, "ls_in_dhcp_options")  \
> -    PIPELINE_STAGE(SWITCH, IN,  DHCP_RESPONSE, 24, "ls_in_dhcp_response") \
> -    PIPELINE_STAGE(SWITCH, IN,  DNS_LOOKUP,    25, "ls_in_dns_lookup")    \
> -    PIPELINE_STAGE(SWITCH, IN,  DNS_RESPONSE,  26, "ls_in_dns_response")  \
> -    PIPELINE_STAGE(SWITCH, IN,  EXTERNAL_PORT, 27, "ls_in_external_port") \
> -    PIPELINE_STAGE(SWITCH, IN,  L2_LKUP,       28, "ls_in_l2_lkup")       \
> -    PIPELINE_STAGE(SWITCH, IN,  L2_UNKNOWN,    29, "ls_in_l2_unknown")    \
> +    PIPELINE_STAGE(SWITCH, IN,  STATEFUL,      22, "ls_in_stateful")      \
> +    PIPELINE_STAGE(SWITCH, IN,  ARP_ND_RSP,    23, "ls_in_arp_rsp")       \
> +    PIPELINE_STAGE(SWITCH, IN,  DHCP_OPTIONS,  24, "ls_in_dhcp_options")  \
> +    PIPELINE_STAGE(SWITCH, IN,  DHCP_RESPONSE, 25, "ls_in_dhcp_response") \
> +    PIPELINE_STAGE(SWITCH, IN,  DNS_LOOKUP,    26, "ls_in_dns_lookup")    \
> +    PIPELINE_STAGE(SWITCH, IN,  DNS_RESPONSE,  27, "ls_in_dns_response")  \
> +    PIPELINE_STAGE(SWITCH, IN,  EXTERNAL_PORT, 28, "ls_in_external_port") \
> +    PIPELINE_STAGE(SWITCH, IN,  L2_LKUP,       29, "ls_in_l2_lkup")       \
> +    PIPELINE_STAGE(SWITCH, IN,  L2_UNKNOWN,    30, "ls_in_l2_unknown")    \
>                                                                            \
>      /* Logical switch egress stages. */                                   \
>      PIPELINE_STAGE(SWITCH, OUT, LOOKUP_FDB,      0, "ls_out_lookup_fdb")     
> \
> @@ -514,10 +517,11 @@ enum ovn_stage {
>      PIPELINE_STAGE(SWITCH, OUT, ACL_EVAL,        6, "ls_out_acl_eval")       
> \
>      PIPELINE_STAGE(SWITCH, OUT, ACL_SAMPLE,      7, "ls_out_acl_sample")     
> \
>      PIPELINE_STAGE(SWITCH, OUT, ACL_ACTION,      8, "ls_out_acl_action")     
> \
> -    PIPELINE_STAGE(SWITCH, OUT, QOS,             9, "ls_out_qos")            
> \
> -    PIPELINE_STAGE(SWITCH, OUT, STATEFUL,       10, "ls_out_stateful")       
> \
> -    PIPELINE_STAGE(SWITCH, OUT, CHECK_PORT_SEC, 11, "ls_out_check_port_sec") 
> \
> -    PIPELINE_STAGE(SWITCH, OUT, APPLY_PORT_SEC, 12, "ls_out_apply_port_sec") 
> \
> +    PIPELINE_STAGE(SWITCH, OUT, MIRROR,          9, "ls_out_mirror")         
> \
> +    PIPELINE_STAGE(SWITCH, OUT, QOS,            10, "ls_out_qos")            
> \
> +    PIPELINE_STAGE(SWITCH, OUT, STATEFUL,       11, "ls_out_stateful")       
> \
> +    PIPELINE_STAGE(SWITCH, OUT, CHECK_PORT_SEC, 12, "ls_out_check_port_sec") 
> \
> +    PIPELINE_STAGE(SWITCH, OUT, APPLY_PORT_SEC, 13, "ls_out_apply_port_sec") 
> \
>                                                                        \
>      /* Logical router ingress stages. */                              \
>      PIPELINE_STAGE(ROUTER, IN,  ADMISSION,       0, "lr_in_admission")    \
> @@ -688,6 +692,14 @@ struct ovn_port {
>       * to NULL. */
>      struct ovn_port *cr_port;
>
> +    /* If this ovn_port is a mirror serving port, this field is set for
> +     * a parent port. */
> +    struct ovn_port *mirror_target_port;
> +
> +    /* If this ovn_port mirror serving port, then 'primary port' points to
> +     * port from which this ovn_port is derived. */
> +    struct ovn_port *mirror_source_port;

As I mentioned above,  please remove this "mirror_source_port".


> +
>      bool has_unknown; /* If the addresses have 'unknown' defined. */
>
>      /* The port's peer:
> diff --git a/northd/ovn-northd.8.xml b/northd/ovn-northd.8.xml
> index e087b6f59..96e102eeb 100644
> --- a/northd/ovn-northd.8.xml
> +++ b/northd/ovn-northd.8.xml
> @@ -398,7 +398,35 @@
>        </li>
>      </ul>
>
> -    <h3>Ingress Table 2: Lookup MAC address learning table</h3>
> +    <h3>Ingress Table 2: Mirror </h3>
> +
> +    <p>
> +      Overlay remote mirror table contains the following
> +      logical flows:
> +    </p>
> +
> +    <ul>
> +      <li>
> +        For each logical switch port with an attached mirror, a logical flow
> +        with a priority of 100 is added. This flow matches all incoming 
> packets
> +        to the attached port, clones them, and forwards the cloned packets to
> +        the mirror target port.
> +      </li>
> +
> +      <li>
> +        A priority 0 flow is added which matches on all packets and applies
> +        the action <code>next;</code>.
> +      </li>
> +
> +      <li>
> +        A logical flow added for each Mirror Rule in Mirror table attached
> +        to logical switch ports, matches all incoming packets that match
> +        rules and clones the packet and sends cloned packet to mirror
> +        target port.
> +      </li>
> +    </ul>
> +
> +    <h3>Ingress Table 3: Lookup MAC address learning table</h3>
>
>      <p>
>        This table looks up the MAC learning table of the logical switch
> @@ -450,7 +478,7 @@
>        </li>
>      </ul>
>
> -    <h3>Ingress Table 3: Learn MAC of 'unknown' ports.</h3>
> +    <h3>Ingress Table 4: Learn MAC of 'unknown' ports.</h3>
>
>      <p>
>        This table learns the MAC addresses seen on the VIF or 'switch' logical
> @@ -488,7 +516,7 @@
>        </li>
>      </ul>
>
> -    <h3>Ingress Table 4: <code>from-lport</code> Pre-ACLs</h3>
> +    <h3>Ingress Table 5: <code>from-lport</code> Pre-ACLs</h3>
>
>      <p>
>        This table prepares flows for possible stateful ACL processing in
> @@ -522,7 +550,7 @@
>        db="OVN_Northbound"/> table.
>      </p>
>
> -    <h3>Ingress Table 5: Pre-LB</h3>
> +    <h3>Ingress Table 6: Pre-LB</h3>
>
>      <p>
>        This table prepares flows for possible stateful load balancing 
> processing
> @@ -598,7 +626,7 @@
>        logical router datapath to logical switch datapath.
>      </p>
>
> -    <h3>Ingress Table 6: Pre-stateful</h3>
> +    <h3>Ingress Table 7: Pre-stateful</h3>
>
>      <p>
>        This table prepares flows for all possible stateful processing
> @@ -632,7 +660,7 @@
>        </li>
>      </ul>
>
> -    <h3>Ingress Table 7: <code>from-lport</code> ACL hints</h3>
> +    <h3>Ingress Table 8: <code>from-lport</code> ACL hints</h3>
>
>      <p>
>        This table consists of logical flows that set hints
> @@ -717,7 +745,7 @@
>        </li>
>      </ul>
>
> -    <h3>Ingress table 8: <code>from-lport</code> ACL evaluation before 
> LB</h3>
> +    <h3>Ingress table 9: <code>from-lport</code> ACL evaluation before 
> LB</h3>
>
>      <p>
>        Logical flows in this table closely reproduce those in the
> @@ -896,7 +924,7 @@
>        </li>
>      </ul>
>
> -    <h3>Ingress Table 9: <code>from-lport</code> ACL sampling</h3>
> +    <h3>Ingress Table 10: <code>from-lport</code> ACL sampling</h3>
>
>      <p>
>        Logical flows in this table sample traffic matched by
> @@ -936,7 +964,7 @@
>        </li>
>      </ul>
>
> -    <h3>Ingress Table 10: <code>from-lport</code> ACL action</h3>
> +    <h3>Ingress Table 11: <code>from-lport</code> ACL action</h3>
>
>      <p>
>        Logical flows in this table decide how to proceed based on the values 
> of
> @@ -976,7 +1004,7 @@
>        </li>
>      </ul>
>
> -    <h3>Ingress Table 11: <code>from-lport</code> QoS</h3>
> +    <h3>Ingress Table 12: <code>from-lport</code> QoS</h3>
>
>      <p>
>        Logical flows in this table closely reproduce those in the
> @@ -999,7 +1027,7 @@
>        </li>
>      </ul>
>
> -    <h3>Ingress Table 12: Load balancing affinity check</h3>
> +    <h3>Ingress Table 13: Load balancing affinity check</h3>
>
>      <p>
>        Load balancing affinity check table contains the following
> @@ -1027,7 +1055,7 @@
>        </li>
>      </ul>
>
> -    <h3>Ingress Table 13: LB</h3>
> +    <h3>Ingress Table 14: LB</h3>
>
>      <ul>
>        <li>
> @@ -1107,7 +1135,7 @@
>        </li>
>      </ul>
>
> -    <h3>Ingress Table 14: Load balancing affinity learn</h3>
> +    <h3>Ingress Table 15: Load balancing affinity learn</h3>
>
>      <p>
>        Load balancing affinity learn table contains the following
> @@ -1138,7 +1166,7 @@
>        </li>
>      </ul>
>
> -    <h3>Ingress Table 15: Pre-Hairpin</h3>
> +    <h3>Ingress Table 16: Pre-Hairpin</h3>
>      <ul>
>        <li>
>          If the logical switch has load balancer(s) configured, then a
> @@ -1156,7 +1184,7 @@
>        </li>
>      </ul>
>
> -    <h3>Ingress Table 16: Nat-Hairpin</h3>
> +    <h3>Ingress Table 17: Nat-Hairpin</h3>
>      <ul>
>        <li>
>           If the logical switch has load balancer(s) configured, then a
> @@ -1191,7 +1219,7 @@
>        </li>
>      </ul>
>
> -    <h3>Ingress Table 17: Hairpin</h3>
> +    <h3>Ingress Table 18: Hairpin</h3>
>      <ul>
>        <li>
>          <p>
> @@ -1229,7 +1257,7 @@
>        </li>
>      </ul>
>
> -    <h3>Ingress table 18: <code>from-lport</code> ACL evaluation after 
> LB</h3>
> +    <h3>Ingress table 19: <code>from-lport</code> ACL evaluation after 
> LB</h3>
>
>      <p>
>        Logical flows in this table closely reproduce those in the
> @@ -1314,7 +1342,7 @@
>        </li>
>      </ul>
>
> -    <h3>Ingress Table 19: <code>from-lport</code> ACL sampling after LB</h3>
> +    <h3>Ingress Table 20: <code>from-lport</code> ACL sampling after LB</h3>
>
>      <p>
>        Logical flows in this table sample traffic matched by
> @@ -1354,7 +1382,7 @@
>        </li>
>      </ul>
>
> -    <h3>Ingress Table 20: <code>from-lport</code> ACL action after LB</h3>
> +    <h3>Ingress Table 21: <code>from-lport</code> ACL action after LB</h3>
>
>      <p>
>        Logical flows in this table decide how to proceed based on the values 
> of
> @@ -1394,7 +1422,7 @@
>        </li>
>      </ul>
>
> -    <h3>Ingress Table 21: Stateful</h3>
> +    <h3>Ingress Table 22: Stateful</h3>
>
>      <ul>
>        <li>
> @@ -1417,7 +1445,7 @@
>        </li>
>      </ul>
>
> -    <h3>Ingress Table 22: ARP/ND responder</h3>
> +    <h3>Ingress Table 23: ARP/ND responder</h3>
>
>      <p>
>        This table implements ARP/ND responder in a logical switch for known
> @@ -1752,7 +1780,7 @@ output;
>        </li>
>      </ul>
>
> -    <h3>Ingress Table 23: DHCP option processing</h3>
> +    <h3>Ingress Table 24: DHCP option processing</h3>
>
>      <p>
>        This table adds the DHCPv4 options to a DHCPv4 packet from the
> @@ -1813,7 +1841,7 @@ next;
>        </li>
>      </ul>
>
> -    <h3>Ingress Table 24: DHCP responses</h3>
> +    <h3>Ingress Table 25: DHCP responses</h3>
>
>      <p>
>        This table implements DHCP responder for the DHCP replies generated by
> @@ -1894,7 +1922,7 @@ output;
>        </li>
>      </ul>
>
> -    <h3>Ingress Table 25 DNS Lookup</h3>
> +    <h3>Ingress Table 26 DNS Lookup</h3>
>
>      <p>
>        This table looks up and resolves the DNS names to the corresponding
> @@ -1923,7 +1951,7 @@ reg0[4] = dns_lookup(); next;
>        </li>
>      </ul>
>
> -    <h3>Ingress Table 26 DNS Responses</h3>
> +    <h3>Ingress Table 27 DNS Responses</h3>
>
>      <p>
>        This table implements DNS responder for the DNS replies generated by
> @@ -1958,7 +1986,7 @@ output;
>        </li>
>      </ul>
>
> -    <h3>Ingress table 27 External ports</h3>
> +    <h3>Ingress table 28 External ports</h3>
>
>      <p>
>        Traffic from the <code>external</code> logical ports enter the ingress
> @@ -2001,7 +2029,7 @@ output;
>        </li>
>      </ul>
>
> -    <h3>Ingress Table 28 Destination Lookup</h3>
> +    <h3>Ingress Table 29 Destination Lookup</h3>
>
>      <p>
>        This table implements switching behavior.  It contains these logical
> @@ -2227,7 +2255,7 @@ output;
>        </li>
>      </ul>
>
> -    <h3>Ingress Table 29 Destination unknown</h3>
> +    <h3>Ingress Table 30 Destination unknown</h3>
>
>      <p>
>        This table handles the packets whose destination was not found or
> @@ -2463,21 +2491,49 @@ output;
>        This is similar to ingress table <code>ACL action</code>.
>      </p>
>
> -    <h3>Egress Table 9: <code>to-lport</code> QoS</h3>
> +    <h3>Egress Table 9: Mirror </h3>
> +
> +    <p>
> +      Overlay remote mirror table contains the following
> +      logical flows:
> +    </p>
> +
> +    <ul>
> +      <li>
> +        For each logical switch port with an attached mirror, a logical flow
> +        with a priority of 100 is added. This flow matches all outcoming
> +        packets to the attached port, clones them, and forwards the cloned
> +        packets to the mirror target port.
> +      </li>
> +
> +      <li>
> +        A priority 0 flow is added which matches on all packets and applies
> +        the action <code>next;</code>.
> +      </li>
> +
> +      <li>
> +        A logical flow added for each Mirror Rule in Mirror table attached
> +        to logical switch ports, matches all outcoming packets that match
> +        rules and clones the packet and sends cloned packet to mirror
> +        target port.
> +      </li>
> +    </ul>
> +
> +    <h3>Egress Table 10: <code>to-lport</code> QoS</h3>
>
>      <p>
>        This is similar to ingress table <code>QoS</code> except
>        they apply to <code>to-lport</code> QoS rules.
>      </p>
>
> -    <h3>Egress Table 10: Stateful</h3>
> +    <h3>Egress Table 11: Stateful</h3>
>
>      <p>
>        This is similar to ingress table <code>Stateful</code> except that
>        there are no rules added for load balancing new connections.
>      </p>
>
> -    <h3>Egress Table 11: Egress Port Security - check</h3>
> +    <h3>Egress Table 12: Egress Port Security - check</h3>
>
>      <p>
>        This is similar to the port security logic in table
> @@ -2506,7 +2562,7 @@ output;
>        </li>
>      </ul>
>
> -    <h3>Egress Table 12: Egress Port Security - Apply</h3>
> +    <h3>Egress Table 13: Egress Port Security - Apply</h3>
>
>      <p>
>        This is similar to the ingress port security logic in ingress table
> diff --git a/ovn-sb.ovsschema b/ovn-sb.ovsschema
> index 7eda3a5d9..4c24f5b51 100644
> --- a/ovn-sb.ovsschema
> +++ b/ovn-sb.ovsschema
> @@ -1,7 +1,7 @@
>  {
>      "name": "OVN_Southbound",
> -    "version": "21.1.0",
> -    "cksum": "880279393 34779",
> +    "version": "21.2.0",
> +    "cksum": "29145795 34859",
>      "tables": {
>          "SB_Global": {
>              "columns": {
> @@ -229,6 +229,7 @@
>                                        "minInteger": 1,
>                                        "maxInteger": 32767}}},
>                  "parent_port": {"type": {"key": "string", "min": 0, "max": 
> 1}},
> +                "mirror_port": {"type": {"key": "string", "min": 0, "max": 
> 1}},
>                  "tag": {
>                       "type": {"key": {"type": "integer",
>                                        "minInteger": 1,
> diff --git a/ovn-sb.xml b/ovn-sb.xml
> index d1d0085af..5e0b3ddfa 100644
> --- a/ovn-sb.xml
> +++ b/ovn-sb.xml
> @@ -2921,6 +2921,20 @@ tcp.flags = RST;
>            </p>
>          </dd>
>
> +      <dt><code>mirror(<var>P</var>);</code></dt>
> +        <dd>
> +          <p>
> +            <b>Parameters</b>: logical port string field <var>P</var>
> +            of type <code>mirror</code>.
> +          </p>
> +
> +          <p>
> +           When using an lport mirror, it clones the packet and outputs it
> +           to the local/remote chassis through the mirrored port <var>P</var>
> +           to the target port.
> +          </p>
> +        </dd>
> +
>        </dl>
>      </column>
>
> @@ -3467,6 +3481,10 @@ tcp.flags = RST;
>          </p>
>        </column>
>
> +      <column name="mirror_port">
> +       Points to mirror target port fot lport mirror type.
> +      </column>
> +
>        <column name="type">
>          <p>
>            A type for this logical port.  Logical ports can be used to model 
> other
> diff --git a/tests/ovn-macros.at b/tests/ovn-macros.at
> index 0c1c1cd99..d0f1c0bf4 100644
> --- a/tests/ovn-macros.at
> +++ b/tests/ovn-macros.at
> @@ -1468,11 +1468,11 @@ m4_define([OVN_CONTROLLER_EXIT],
>
>  m4_define([OFTABLE_PHY_TO_LOG], [0])
>  m4_define([OFTABLE_LOG_INGRESS_PIPELINE], [8])
> -m4_define([OFTABLE_OUTPUT_LARGE_PKT_DETECT], [40])
> -m4_define([OFTABLE_OUTPUT_LARGE_PKT_PROCESS], [41])
> -m4_define([OFTABLE_REMOTE_OUTPUT], [42])
> -m4_define([OFTABLE_LOCAL_OUTPUT], [43])
> -m4_define([OFTABLE_LOG_EGRESS_PIPELINE], [45])
> +m4_define([OFTABLE_OUTPUT_LARGE_PKT_DETECT], [41])
> +m4_define([OFTABLE_OUTPUT_LARGE_PKT_PROCESS], [42])
> +m4_define([OFTABLE_REMOTE_OUTPUT], [43])
> +m4_define([OFTABLE_LOCAL_OUTPUT], [44])
> +m4_define([OFTABLE_LOG_EGRESS_PIPELINE], [46])
>  m4_define([OFTABLE_SAVE_INPORT], [64])
>  m4_define([OFTABLE_LOG_TO_PHY], [65])
>  m4_define([OFTABLE_MAC_BINDING], [66])
> diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
> index 12d6611b6..028e847ae 100644
> --- a/tests/ovn-northd.at
> +++ b/tests/ovn-northd.at
> @@ -16927,3 +16927,328 @@ AT_CHECK([ovn_strip_lflows < lrflows | grep 
> priority=105 | grep -c "flags.force_
>
>  AT_CLEANUP
>  ])
> +
> +OVN_FOR_EACH_NORTHD_NO_HV([
> +AT_SETUP([Check NB mirror rules sync])
> +AT_KEYWORDS([mirror rules])
> +ovn_start

I think this test case should be part of ovn-nbctl.at.

In the first patch you've already added a test in ovn-nbctl.at.
I'd suggest either moving this function to ovn-nbctl.at file or remove
it if it is redundant.



> +
> +check ovn-nbctl ls-add ls1
> +check ovn-nbctl lsp-add ls1 lport4
> +
> +check ovn-nbctl mirror-add mirror1 lport to-lport lport4
> +
> +check_column mirror1 nb:Mirror name
> +check_column lport nb:Mirror type
> +check_column to-lport nb:Mirror filter
> +check_column lport4 nb:Mirror sink
> +
> +check ovn-nbctl mirror-rule-add mirror1 100 'ip' skip
> +
> +check_column 100 nb:mirror_rule priority
> +check_column ip nb:mirror_rule match
> +check_column skip nb:mirror_rule action
> +
> +check ovn-nbctl set mirror_rule . priority=150
> +check_column 150 nb:mirror_rule priority
> +check_column ip nb:mirror_rule match
> +check_column skip nb:mirror_rule action
> +
> +check ovn-nbctl set mirror_rule . match='icmp'
> +check_column 150  nb:mirror_rule priority
> +check_column icmp nb:mirror_rule match
> +check_column skip nb:mirror_rule action
> +
> +check ovn-nbctl set mirror_rule . action=mirror
> +check_column 150  nb:mirror_rule priority
> +check_column icmp nb:mirror_rule match
> +check_column mirror nb:mirror_rule action
> +
> +dnl Mirror rule attach mirror
> +mirrorrule1uuid=$(fetch_column nb:mirror_rule _uuid priority=100)
> +check_column "$mirrorrule1uuid" nb:Mirror mirror_rules 
> mirror_rules="$mirrorrule1uuid"
> +
> +check ovn-nbctl mirror-rule-add mirror1 200 'ip' mirror
> +mirrorrule2uuid=$(fetch_column nb:mirror_rule _uuid priority=150)
> +check_column "$mirrorrule1uuid" nb:Mirror mirror_rules 
> mirror_rules="$mirrorrule1uuid","$mirrorrule2uuid"
> +
> +check ovn-nbctl mirror-rule-del mirror1 200
> +check_column "$mirrorrule1uuid" nb:Mirror mirror_rules 
> mirror_rules="$mirrorrule1uuid"
> +
> +check ovn-nbctl mirror-rule-del mirror1
> +check_column "$mirrorrule1uuid" nb:Mirror mirror_rules mirror_rules=""
> +
> +AT_CLEANUP
> +])
> +
> +OVN_FOR_EACH_NORTHD_NO_HV([
> +AT_SETUP([Mirror rule lflows])
> +AT_KEYWORDS([mirror rules])
> +ovn_start
> +
> +check ovn-nbctl ls-add sw0
> +check ovn-nbctl ls-add sw1
> +
> +check ovn-nbctl lsp-add sw0 sw0-p1 -- lsp-set-addresses sw0-p1 
> "50:54:00:00:00:01 10.0.0.11"
> +check ovn-nbctl lsp-add sw0 sw0-target0
> +check ovn-nbctl lsp-add sw1 sw1-target1
> +
> +check ovn-nbctl mirror-add mirror0 lport to-lport sw0-target0
> +check ovn-nbctl mirror-add mirror1 lport both sw0-target0
> +check ovn-nbctl mirror-add mirror2 lport from-lport sw1-target1
> +
> +check ovn-nbctl mirror-rule-add mirror1 100 'ip' mirror
> +check ovn-nbctl mirror-rule-add mirror1 150 'icmp' skip
> +check ovn-nbctl mirror-rule-add mirror2 100 'ip4.dst == 192.168.0.1' mirror
> +check ovn-nbctl mirror-rule-add mirror2 150 '1' skip
> +
> +check ovn-nbctl --wait=hv sync
> +
> +ovn-sbctl lflow-list sw0 > lflow-list
> +
> +AT_CHECK([grep -e "ls_in_mirror" -e "ls_out_mirror" lflow-list | 
> ovn_strip_lflows], [0], [dnl
> +  table=??(ls_in_mirror       ), priority=0    , match=(1), action=(next;)
> +  table=??(ls_out_mirror      ), priority=0    , match=(1), action=(next;)
> +])
> +
> +check ovn-nbctl lsp-attach-mirror sw0-p1 mirror0
> +
> +check ovn-nbctl --wait=sb sync
> +
> +check_column sw0-target0 Port_Binding mirror_port 
> logical_port=mp-sw0-sw0-target0
> +check_column mirror Port_Binding type logical_port=mp-sw0-sw0-target0
> +
> +ovn-sbctl lflow-list sw0 > lflow-list
> +AT_CHECK([grep -e "ls_in_mirror" -e "ls_out_mirror" lflow-list | 
> ovn_strip_lflows], [0], [dnl
> +  table=??(ls_in_mirror       ), priority=0    , match=(1), action=(next;)
> +  table=??(ls_out_mirror      ), priority=0    , match=(1), action=(next;)
> +  table=??(ls_out_mirror      ), priority=100  , match=(outport == 
> "sw0-p1"), action=(mirror("mp-sw0-sw0-target0"); next;)
> +])
> +
> +ovn-sbctl lflow-list sw0 > lflow-list
> +AT_CHECK([grep -e "ls_out_pre_acl" lflow-list | ovn_strip_lflows], [0], [dnl
> +  table=??(ls_out_pre_acl     ), priority=0    , match=(1), action=(next;)
> +  table=??(ls_out_pre_acl     ), priority=110  , match=(eth.src == 
> $svc_monitor_mac), action=(next;)
> +  table=??(ls_out_pre_acl     ), priority=65535, match=(outport == 
> "mp-sw0-sw0-target0"), action=(next(pipeline=egress, table=??);)
> +])
> +
> +check ovn-nbctl lsp-attach-mirror sw0-p1 mirror1
> +
> +check ovn-nbctl --wait=sb sync
> +
> +check_row_count Port_Binding 1 logical_port=mp-sw0-sw0-target0
> +
> +ovn-sbctl lflow-list sw0 > lflow-list
> +AT_CHECK([grep -e "ls_in_mirror" -e "ls_out_mirror" lflow-list | 
> ovn_strip_lflows], [0], [dnl
> +  table=??(ls_in_mirror       ), priority=0    , match=(1), action=(next;)
> +  table=??(ls_in_mirror       ), priority=100  , match=(inport == "sw0-p1"), 
> action=(mirror("mp-sw0-sw0-target0"); next;)
> +  table=??(ls_in_mirror       ), priority=200  , match=(inport == "sw0-p1" 
> && (ip)), action=(mirror("mp-sw0-sw0-target0"); next;)
> +  table=??(ls_in_mirror       ), priority=250  , match=(inport == "sw0-p1" 
> && (icmp)), action=(next;)
> +  table=??(ls_out_mirror      ), priority=0    , match=(1), action=(next;)
> +  table=??(ls_out_mirror      ), priority=100  , match=(outport == 
> "sw0-p1"), action=(mirror("mp-sw0-sw0-target0"); next;)
> +  table=??(ls_out_mirror      ), priority=200  , match=(outport == "sw0-p1" 
> && (ip)), action=(mirror("mp-sw0-sw0-target0"); next;)
> +  table=??(ls_out_mirror      ), priority=250  , match=(outport == "sw0-p1" 
> && (icmp)), action=(next;)
> +])
> +
> +check ovn-nbctl lsp-attach-mirror sw0-p1 mirror2
> +
> +check ovn-nbctl --wait=hv sync
> +
> +check_column sw1-target1 Port_Binding mirror_port 
> logical_port=mp-sw0-sw1-target1
> +check_column mirror Port_Binding type logical_port=mp-sw0-sw1-target1
> +
> +ovn-sbctl lflow-list sw0 > lflow-list
> +AT_CHECK([grep -e "ls_in_mirror" -e "ls_out_mirror" lflow-list | 
> ovn_strip_lflows], [0], [dnl
> +  table=??(ls_in_mirror       ), priority=0    , match=(1), action=(next;)
> +  table=??(ls_in_mirror       ), priority=100  , match=(inport == "sw0-p1"), 
> action=(mirror("mp-sw0-sw0-target0"); next;)
> +  table=??(ls_in_mirror       ), priority=100  , match=(inport == "sw0-p1"), 
> action=(mirror("mp-sw0-sw1-target1"); next;)
> +  table=??(ls_in_mirror       ), priority=200  , match=(inport == "sw0-p1" 
> && (ip)), action=(mirror("mp-sw0-sw0-target0"); next;)
> +  table=??(ls_in_mirror       ), priority=200  , match=(inport == "sw0-p1" 
> && (ip4.dst == 192.168.0.1)), action=(mirror("mp-sw0-sw1-target1"); next;)
> +  table=??(ls_in_mirror       ), priority=250  , match=(inport == "sw0-p1" 
> && (1)), action=(next;)
> +  table=??(ls_in_mirror       ), priority=250  , match=(inport == "sw0-p1" 
> && (icmp)), action=(next;)
> +  table=??(ls_out_mirror      ), priority=0    , match=(1), action=(next;)
> +  table=??(ls_out_mirror      ), priority=100  , match=(outport == 
> "sw0-p1"), action=(mirror("mp-sw0-sw0-target0"); next;)
> +  table=??(ls_out_mirror      ), priority=200  , match=(outport == "sw0-p1" 
> && (ip)), action=(mirror("mp-sw0-sw0-target0"); next;)
> +  table=??(ls_out_mirror      ), priority=250  , match=(outport == "sw0-p1" 
> && (icmp)), action=(next;)
> +])
> +
> +ovn-sbctl lflow-list sw0 > lflow-list
> +AT_CHECK([grep -e "ls_out_pre_acl" lflow-list | ovn_strip_lflows], [0], [dnl
> +  table=??(ls_out_pre_acl     ), priority=0    , match=(1), action=(next;)
> +  table=??(ls_out_pre_acl     ), priority=110  , match=(eth.src == 
> $svc_monitor_mac), action=(next;)
> +  table=??(ls_out_pre_acl     ), priority=65535, match=(outport == 
> "mp-sw0-sw0-target0"), action=(next(pipeline=egress, table=??);)
> +  table=??(ls_out_pre_acl     ), priority=65535, match=(outport == 
> "mp-sw0-sw1-target1"), action=(next(pipeline=egress, table=??);)
> +])
> +
> +check ovn-nbctl lsp-del sw0-target0
> +
> +check ovn-nbctl --wait=hv sync
> +
> +check_row_count Port_Binding 0 logical_port=mp-sw0-sw0-target0
> +
> +ovn-sbctl lflow-list sw0 > lflow-list
> +AT_CHECK([grep -e "ls_in_mirror" -e "ls_out_mirror" lflow-list | 
> ovn_strip_lflows], [0], [dnl
> +  table=??(ls_in_mirror       ), priority=0    , match=(1), action=(next;)
> +  table=??(ls_in_mirror       ), priority=100  , match=(inport == "sw0-p1"), 
> action=(mirror("mp-sw0-sw1-target1"); next;)
> +  table=??(ls_in_mirror       ), priority=200  , match=(inport == "sw0-p1" 
> && (ip4.dst == 192.168.0.1)), action=(mirror("mp-sw0-sw1-target1"); next;)
> +  table=??(ls_in_mirror       ), priority=250  , match=(inport == "sw0-p1" 
> && (1)), action=(next;)
> +  table=??(ls_out_mirror      ), priority=0    , match=(1), action=(next;)
> +])
> +
> +AT_CHECK([grep -e "ls_out_pre_acl" lflow-list | ovn_strip_lflows], [0], [dnl
> +  table=??(ls_out_pre_acl     ), priority=0    , match=(1), action=(next;)
> +  table=??(ls_out_pre_acl     ), priority=110  , match=(eth.src == 
> $svc_monitor_mac), action=(next;)
> +  table=??(ls_out_pre_acl     ), priority=65535, match=(outport == 
> "mp-sw0-sw1-target1"), action=(next(pipeline=egress, table=??);)
> +])
> +
> +check ovn-nbctl lsp-add sw1 sw0-target0
> +
> +check ovn-nbctl --wait=hv sync
> +
> +check_column sw1-target1 Port_Binding mirror_port 
> logical_port=mp-sw0-sw1-target1
> +check_column mirror Port_Binding type logical_port=mp-sw0-sw1-target1
> +
> +ovn-sbctl lflow-list sw0 > lflow-list
> +AT_CHECK([grep -e "ls_in_mirror" -e "ls_out_mirror" lflow-list | 
> ovn_strip_lflows], [0], [dnl
> +  table=??(ls_in_mirror       ), priority=0    , match=(1), action=(next;)
> +  table=??(ls_in_mirror       ), priority=100  , match=(inport == "sw0-p1"), 
> action=(mirror("mp-sw0-sw0-target0"); next;)
> +  table=??(ls_in_mirror       ), priority=100  , match=(inport == "sw0-p1"), 
> action=(mirror("mp-sw0-sw1-target1"); next;)
> +  table=??(ls_in_mirror       ), priority=200  , match=(inport == "sw0-p1" 
> && (ip)), action=(mirror("mp-sw0-sw0-target0"); next;)
> +  table=??(ls_in_mirror       ), priority=200  , match=(inport == "sw0-p1" 
> && (ip4.dst == 192.168.0.1)), action=(mirror("mp-sw0-sw1-target1"); next;)
> +  table=??(ls_in_mirror       ), priority=250  , match=(inport == "sw0-p1" 
> && (1)), action=(next;)
> +  table=??(ls_in_mirror       ), priority=250  , match=(inport == "sw0-p1" 
> && (icmp)), action=(next;)
> +  table=??(ls_out_mirror      ), priority=0    , match=(1), action=(next;)
> +  table=??(ls_out_mirror      ), priority=100  , match=(outport == 
> "sw0-p1"), action=(mirror("mp-sw0-sw0-target0"); next;)
> +  table=??(ls_out_mirror      ), priority=200  , match=(outport == "sw0-p1" 
> && (ip)), action=(mirror("mp-sw0-sw0-target0"); next;)
> +  table=??(ls_out_mirror      ), priority=250  , match=(outport == "sw0-p1" 
> && (icmp)), action=(next;)
> +])
> +
> +mirror1uuid_uuid=`ovn-nbctl --bare --columns _uuid find Mirror 
> name="mirror1"`
> +
> +ovn-nbctl set mirror $mirror1uuid_uuid sink=sw1-target1
> +
> +check ovn-nbctl --wait=hv sync
> +
> +ovn-sbctl lflow-list sw0 > lflow-list
> +AT_CHECK([grep -e "ls_in_mirror" -e "ls_out_mirror" lflow-list | 
> ovn_strip_lflows], [0], [dnl
> +  table=??(ls_in_mirror       ), priority=0    , match=(1), action=(next;)
> +  table=??(ls_in_mirror       ), priority=100  , match=(inport == "sw0-p1"), 
> action=(mirror("mp-sw0-sw1-target1"); next;)
> +  table=??(ls_in_mirror       ), priority=200  , match=(inport == "sw0-p1" 
> && (ip)), action=(mirror("mp-sw0-sw1-target1"); next;)
> +  table=??(ls_in_mirror       ), priority=200  , match=(inport == "sw0-p1" 
> && (ip4.dst == 192.168.0.1)), action=(mirror("mp-sw0-sw1-target1"); next;)
> +  table=??(ls_in_mirror       ), priority=250  , match=(inport == "sw0-p1" 
> && (1)), action=(next;)
> +  table=??(ls_in_mirror       ), priority=250  , match=(inport == "sw0-p1" 
> && (icmp)), action=(next;)
> +  table=??(ls_out_mirror      ), priority=0    , match=(1), action=(next;)
> +  table=??(ls_out_mirror      ), priority=100  , match=(outport == 
> "sw0-p1"), action=(mirror("mp-sw0-sw0-target0"); next;)
> +  table=??(ls_out_mirror      ), priority=100  , match=(outport == 
> "sw0-p1"), action=(mirror("mp-sw0-sw1-target1"); next;)
> +  table=??(ls_out_mirror      ), priority=200  , match=(outport == "sw0-p1" 
> && (ip)), action=(mirror("mp-sw0-sw1-target1"); next;)
> +  table=??(ls_out_mirror      ), priority=250  , match=(outport == "sw0-p1" 
> && (icmp)), action=(next;)
> +])
> +
> +check_row_count Port_Binding 1 logical_port=mp-sw0-sw0-target0
> +
> +check ovn-nbctl mirror-del mirror0
> +
> +check_row_count Port_Binding 0 logical_port=mp-sw0-sw0-target0
> +
> +check ovn-nbctl mirror-del
> +
> +check ovn-nbctl --wait=sb sync
> +
> +ovn-sbctl lflow-list sw0 > lflow-list
> +AT_CHECK([grep -e "ls_in_mirror" -e "ls_out_mirror" lflow-list | 
> ovn_strip_lflows], [0], [dnl
> +  table=??(ls_in_mirror       ), priority=0    , match=(1), action=(next;)
> +  table=??(ls_out_mirror      ), priority=0    , match=(1), action=(next;)
> +])
> +
> +ovn-sbctl lflow-list sw0 > lflow-list
> +AT_CHECK([grep -e "ls_out_pre_acl" lflow-list | ovn_strip_lflows], [0], [dnl
> +  table=??(ls_out_pre_acl     ), priority=0    , match=(1), action=(next;)
> +  table=??(ls_out_pre_acl     ), priority=110  , match=(eth.src == 
> $svc_monitor_mac), action=(next;)
> +])
> +
> +AT_CLEANUP
> +])
> +
> +OVN_FOR_EACH_NORTHD_NO_HV([
> +AT_SETUP([ovn-detrace mirror rule check])
> +AT_KEYWORDS([mirror rules])
> +ovn_start
> +
> +check ovn-nbctl ls-add sw0
> +check ovn-nbctl lsp-add sw0 sw0-p1
> +check ovn-nbctl lsp-add sw0 sw0-rp
> +
> +check ovn-nbctl lsp-set-type sw0-rp router
> +check ovn-nbctl lsp-set-addresses sw0-rp router
> +check ovn-nbctl lsp-set-options sw0-rp router-port=lrp1
> +
> +check ovn-nbctl lsp-set-addresses sw0-p1 '00:00:00:00:00:01 1.1.1.1'
> +
> +check ovn-nbctl ls-add sw1
> +check ovn-nbctl lsp-add sw1 sw1-target
> +check ovn-nbctl lsp-add sw0 sw0-target
> +
> +check ovn-nbctl lr-add lr1
> +check ovn-nbctl lrp-add lr1 lrp1 00:00:00:00:00:02 1.1.1.2/24
> +
> +check ovn-nbctl mirror-add mirror1 lport from-lport sw1-target
> +check ovn-nbctl mirror-rule-add mirror1 100 '1' mirror
> +
> +check ovn-nbctl lsp-attach-mirror sw0-p1 mirror1
> +
> +check ovn-nbctl --wait=hv sync
> +
> +check_row_count Port_Binding 1 logical_port=mp-sw0-sw1-target
> +
> +ovn-sbctl lflow-list sw0 > lflow-list
> +AT_CHECK([grep -e "ls_in_mirror" lflow-list | ovn_strip_lflows], [0], [dnl
> +  table=??(ls_in_mirror       ), priority=0    , match=(1), action=(next;)
> +  table=??(ls_in_mirror       ), priority=100  , match=(inport == "sw0-p1"), 
> action=(mirror("mp-sw0-sw1-target"); next;)
> +  table=??(ls_in_mirror       ), priority=200  , match=(inport == "sw0-p1" 
> && (1)), action=(mirror("mp-sw0-sw1-target"); next;)
> +])
> +
> +AT_CHECK([ovn_trace --minimal 'inport == "sw0-p1" && eth.src == 
> 00:00:00:00:00:01 && eth.dst == 00:00:00:00:00:02 && ip4.dst == 1.1.1.2 && 
> ip4.src == 1.1.1.1 && ip.ttl == 64' > trace], [0], [ignore])
> +
> +AT_CHECK([cat trace | head -n 3], [0], [dnl
> +clone {
> +    output("mp-sw0-sw1-target");
> +};
> +])
> +
> +check ovn-nbctl mirror-rule-add mirror1 300 'arp' mirror
> +check ovn-nbctl mirror-rule-add mirror1 250 '1' skip
> +
> +check ovn-nbctl --wait=hv sync
> +
> +ovn-sbctl lflow-list sw0 > lflow-list
> +AT_CHECK([grep -e "ls_in_mirror" lflow-list | ovn_strip_lflows], [0], [dnl
> +  table=??(ls_in_mirror       ), priority=0    , match=(1), action=(next;)
> +  table=??(ls_in_mirror       ), priority=100  , match=(inport == "sw0-p1"), 
> action=(mirror("mp-sw0-sw1-target"); next;)
> +  table=??(ls_in_mirror       ), priority=200  , match=(inport == "sw0-p1" 
> && (1)), action=(mirror("mp-sw0-sw1-target"); next;)
> +  table=??(ls_in_mirror       ), priority=350  , match=(inport == "sw0-p1" 
> && (1)), action=(next;)
> +  table=??(ls_in_mirror       ), priority=400  , match=(inport == "sw0-p1" 
> && (arp)), action=(mirror("mp-sw0-sw1-target"); next;)
> +])
> +
> +AT_CHECK([ovn_trace --minimal 'inport == "sw0-p1" && eth.src == 
> 00:00:00:00:00:01 && eth.dst == 00:00:00:00:00:02 && ip4.dst == 1.1.1.2 && 
> ip4.src == 1.1.1.1 && ip.ttl == 64' > trace], [0], [ignore])
> +
> +AT_CHECK([cat trace | grep clone], [1], [dnl
> +])
> +
> +check ovn-nbctl mirror-del mirror1
> +
> +check ovn-nbctl --wait=hv sync
> +
> +AT_CHECK([ovn_trace --minimal 'inport == "sw0-p1" && eth.src == 
> 00:00:00:00:00:01 && eth.dst == 00:00:00:00:00:02 && ip4.dst == 1.1.1.2 && 
> ip4.src == 1.1.1.1 && ip.ttl == 64' > trace], [0], [ignore])
> +
> +ovn-sbctl lflow-list sw0 > lflow-list
> +AT_CHECK([grep -e "ls_in_mirror" lflow-list | ovn_strip_lflows], [0], [dnl
> +  table=??(ls_in_mirror       ), priority=0    , match=(1), action=(next;)
> +])
> +
> +AT_CHECK([cat trace | grep output], [0], [dnl
> +    output("sw0-p1");
> +    output("sw0-p1");
> +])
> +
> +AT_CLEANUP
> +])
> diff --git a/tests/ovn.at b/tests/ovn.at
> index 35c7dc79a..333068b99 100644
> --- a/tests/ovn.at
> +++ b/tests/ovn.at
> @@ -803,6 +803,7 @@ m4_define([NEXT], [m4_if(
>
>  m4_define([oflow_in_table], [NEXT(ingress, lflow_table)])
>  m4_define([oflow_out_table], [NEXT(egress, lflow_table)])
> +m4_define([remote_out_table], [OFTABLE_REMOTE_OUTPUT])
>
>  AT_DATA([test-cases.txt], [
>  # drop
> @@ -2309,6 +2310,13 @@ ct_state_save;
>  ct_state_save();
>      Syntax error at `ct_state_save' expecting action.
>
> +#mirror
> +mirror("lsp1");
> +    encodes as 
> clone(set_field:ANY->in_port,set_field:0x11->reg15,resubmit(,remote_out_table))
> +
> +mirror(lsp1);
> +    Syntax error at `lsp1' expecting port name string.
> +
>  # Miscellaneous negative tests.
>  ;
>      Syntax error at `;'.
> diff --git a/utilities/ovn-trace.c b/utilities/ovn-trace.c
> index ce55008a6..d135a6cae 100644
> --- a/utilities/ovn-trace.c
> +++ b/utilities/ovn-trace.c
> @@ -588,6 +588,20 @@ ovntrace_port_find_by_key(const struct ovntrace_datapath 
> *dp,
>      return NULL;
>  }
>
> +static const struct ovntrace_port *
> +ovntrace_port_find_by_name(const struct ovntrace_datapath *dp,
> +                           const char *name)
> +{
> +    const struct shash_node *node;
> +    SHASH_FOR_EACH (node, &ports) {
> +        const struct ovntrace_port *port = node->data;
> +        if (port->dp == dp && !strcmp(port->name, name)) {
> +            return port;
> +        }
> +    }
> +    return NULL;
> +}
> +
>  static const char *
>  ovntrace_port_key_to_name(const struct ovntrace_datapath *dp,
>                            uint16_t key)
> @@ -3138,6 +3152,29 @@ execute_ct_save_state(const struct ovnact_result *dl, 
> struct flow *uflow,
>      ds_destroy(&s);
>  }
>
> +static void
> +execute_mirror(const struct ovnact_mirror *mirror,
> +               const struct ovntrace_datapath *dp,
> +               struct flow *uflow, struct ovs_list *super)
> +{
> +    const struct ovntrace_port *port;
> +    struct flow cloned_flow = *uflow;
> +    port = ovntrace_port_find_by_name(dp, mirror->port);
> +
> +    if (port) {
> +        struct ovntrace_node *node = ovntrace_node_append(super,
> +            OVNTRACE_NODE_TRANSFORMATION, "clone");
> +
> +        cloned_flow.regs[MFF_LOG_INPORT - MFF_REG0] = 0;
> +        cloned_flow.regs[MFF_LOG_OUTPORT - MFF_REG0] = port->tunnel_key;
> +
> +        trace__(dp, &cloned_flow, 0, OVNACT_P_EGRESS, &node->subs);
> +    } else {
> +        ovntrace_node_append(super, OVNTRACE_NODE_ERROR,
> +        "/* omitting output because no taget port found. */");
> +    }
> +}
> +
>  static void
>  trace_actions(const struct ovnact *ovnacts, size_t ovnacts_len,
>                const struct ovntrace_datapath *dp, struct flow *uflow,
> @@ -3458,6 +3495,11 @@ trace_actions(const struct ovnact *ovnacts, size_t 
> ovnacts_len,
>              execute_check_out_port_sec(ovnact_get_CHECK_OUT_PORT_SEC(a),
>                                         dp, uflow);
>              break;
> +
> +        case OVNACT_MIRROR:
> +            execute_mirror(ovnact_get_MIRROR(a), dp, uflow, super);
> +            break;
> +
>          case OVNACT_COMMIT_ECMP_NH:
>              break;
>          case OVNACT_CHK_ECMP_NH_MAC:
> --
> 2.48.1
>
> _______________________________________________
> dev mailing list
> d...@openvswitch.org
> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
_______________________________________________
dev mailing list
d...@openvswitch.org
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to