diff --git a/lib/pipeline/rte_swx_ctl.h b/lib/pipeline/rte_swx_ctl.h
index ed752ad5eb..204026dc0e 100644
--- a/lib/pipeline/rte_swx_ctl.h
+++ b/lib/pipeline/rte_swx_ctl.h
@@ -41,6 +41,12 @@ struct rte_swx_ctl_pipeline_info {
 	/** Number of input ports. */
 	uint32_t n_ports_out;
 
+	/** Number of packet mirroring slots. */
+	uint32_t n_mirroring_slots;
+
+	/** Number of packet mirroring sessions. */
+	uint32_t n_mirroring_sessions;
+
 	/** Number of actions. */
 	uint32_t n_actions;
 
@@ -655,6 +661,42 @@ rte_swx_ctl_pipeline_learner_stats_read(struct rte_swx_pipeline *p,
 				      const char *learner_name,
 				      struct rte_swx_learner_stats *stats);
 
+/*
+ * Packet mirroring API.
+ */
+
+/** Packet mirroring session parameters. */
+struct rte_swx_pipeline_mirroring_session_params {
+	/** Output port ID. */
+	uint32_t port_id;
+
+	/** Fast clone flag. */
+	int fast_clone;
+
+	/** Truncation packet length (in bytes). Zero means no truncation. */
+	uint32_t truncation_length;
+};
+
+/**
+ * Packet mirroring session set
+ *
+ * @param[in] p
+ *   Pipeline handle.
+ * @param[in] session_id
+ *   Packet mirroring session ID.
+ * @param[in] params
+ *   Packet mirroring session parameters.
+ * @return
+ *   0 on success or the following error codes otherwise:
+ *   -EINVAL: Invalid argument;
+ *   -EEXIST: Pipeline was already built successfully.
+ */
+__rte_experimental
+int
+rte_swx_ctl_pipeline_mirroring_session_set(struct rte_swx_pipeline *p,
+	uint32_t session_id,
+	struct rte_swx_pipeline_mirroring_session_params *params);
+
 /*
  * Table Update API.
  */
diff --git a/lib/pipeline/rte_swx_pipeline.c b/lib/pipeline/rte_swx_pipeline.c
index 8c4670e111..062fbcfc89 100644
--- a/lib/pipeline/rte_swx_pipeline.c
+++ b/lib/pipeline/rte_swx_pipeline.c
@@ -423,6 +423,8 @@ rte_swx_pipeline_port_out_type_register(struct rte_swx_pipeline *p,
 	CHECK(ops->create, EINVAL);
 	CHECK(ops->free, EINVAL);
 	CHECK(ops->pkt_tx, EINVAL);
+	CHECK(ops->pkt_fast_clone_tx, EINVAL);
+	CHECK(ops->pkt_clone_tx, EINVAL);
 	CHECK(ops->stats_read, EINVAL);
 
 	CHECK(!port_out_type_find(p, name), EEXIST);
@@ -509,6 +511,8 @@ port_out_build(struct rte_swx_pipeline *p)
 		struct port_out_runtime *out = &p->out[port->id];
 
 		out->pkt_tx = port->type->ops.pkt_tx;
+		out->pkt_fast_clone_tx = port->type->ops.pkt_fast_clone_tx;
+		out->pkt_clone_tx = port->type->ops.pkt_clone_tx;
 		out->flush = port->type->ops.flush;
 		out->obj = port->obj;
 	}
@@ -554,6 +558,81 @@ port_out_free(struct rte_swx_pipeline *p)
 	}
 }
 
+/*
+ * Packet mirroring.
+ */
+int
+rte_swx_pipeline_mirroring_config(struct rte_swx_pipeline *p,
+				  struct rte_swx_pipeline_mirroring_params *params)
+{
+	CHECK(p, EINVAL);
+	CHECK(params, EINVAL);
+	CHECK(params->n_slots, EINVAL);
+	CHECK(params->n_sessions, EINVAL);
+	CHECK(!p->build_done, EEXIST);
+
+	p->n_mirroring_slots = rte_align32pow2(params->n_slots);
+	if (p->n_mirroring_slots > 64)
+		p->n_mirroring_slots = 64;
+
+	p->n_mirroring_sessions = rte_align32pow2(params->n_sessions);
+
+	return 0;
+}
+
+static void
+mirroring_build_free(struct rte_swx_pipeline *p)
+{
+	uint32_t i;
+
+	for (i = 0; i < RTE_SWX_PIPELINE_THREADS_MAX; i++) {
+		struct thread *t = &p->threads[i];
+
+		/* mirroring_slots. */
+		free(t->mirroring_slots);
+		t->mirroring_slots = NULL;
+	}
+
+	/* mirroring_sessions. */
+	free(p->mirroring_sessions);
+	p->mirroring_sessions = NULL;
+}
+
+static void
+mirroring_free(struct rte_swx_pipeline *p)
+{
+	mirroring_build_free(p);
+}
+
+static int
+mirroring_build(struct rte_swx_pipeline *p)
+{
+	uint32_t i;
+
+	if (!p->n_mirroring_slots || !p->n_mirroring_sessions)
+		return 0;
+
+	for (i = 0; i < RTE_SWX_PIPELINE_THREADS_MAX; i++) {
+		struct thread *t = &p->threads[i];
+
+		/* mirroring_slots. */
+		t->mirroring_slots = calloc(p->n_mirroring_slots, sizeof(uint32_t));
+		if (!t->mirroring_slots)
+			goto error;
+	}
+
+	/* mirroring_sessions. */
+	p->mirroring_sessions = calloc(p->n_mirroring_sessions, sizeof(struct mirroring_session));
+	if (!p->mirroring_sessions)
+		goto error;
+
+	return 0;
+
+error:
+	mirroring_build_free(p);
+	return -ENOMEM;
+}
+
 /*
  * Extern object.
  */
@@ -1653,6 +1732,56 @@ instr_drop_exec(struct rte_swx_pipeline *p)
 	instr_rx_exec(p);
 }
 
+/*
+ * mirror.
+ */
+static int
+instr_mirror_translate(struct rte_swx_pipeline *p,
+		       struct action *action,
+		       char **tokens,
+		       int n_tokens,
+		       struct instruction *instr,
+		       struct instruction_data *data __rte_unused)
+{
+	char *dst = tokens[1], *src = tokens[2];
+	struct field *fdst, *fsrc;
+	uint32_t dst_struct_id = 0, src_struct_id = 0;
+
+	CHECK(n_tokens == 3, EINVAL);
+
+	fdst = struct_field_parse(p, action, dst, &dst_struct_id);
+	CHECK(fdst, EINVAL);
+	CHECK(dst[0] != 'h', EINVAL);
+	CHECK(!fdst->var_size, EINVAL);
+
+	fsrc = struct_field_parse(p, action, src, &src_struct_id);
+	CHECK(fsrc, EINVAL);
+	CHECK(src[0] != 'h', EINVAL);
+	CHECK(!fsrc->var_size, EINVAL);
+
+	instr->type = INSTR_MIRROR;
+	instr->mirror.dst.struct_id = (uint8_t)dst_struct_id;
+	instr->mirror.dst.n_bits = fdst->n_bits;
+	instr->mirror.dst.offset = fdst->offset / 8;
+	instr->mirror.src.struct_id = (uint8_t)src_struct_id;
+	instr->mirror.src.n_bits = fsrc->n_bits;
+	instr->mirror.src.offset = fsrc->offset / 8;
+
+	return 0;
+}
+
+static inline void
+instr_mirror_exec(struct rte_swx_pipeline *p)
+{
+	struct thread *t = &p->threads[p->thread_id];
+	struct instruction *ip = t->ip;
+
+	__instr_mirror_exec(p, t, ip);
+
+	/* Thread. */
+	thread_ip_inc(p);
+}
+
 /*
  * extract.
  */
@@ -5653,6 +5782,14 @@ instr_translate(struct rte_swx_pipeline *p,
 					    instr,
 					    data);
 
+	if (!strcmp(tokens[tpos], "mirror"))
+		return instr_mirror_translate(p,
+					      action,
+					      &tokens[tpos],
+					      n_tokens - tpos,
+					      instr,
+					      data);
+
 	if (!strcmp(tokens[tpos], "extract"))
 		return instr_hdr_extract_translate(p,
 						   action,
@@ -6677,6 +6814,7 @@ static instr_exec_t instruction_table[] = {
 	[INSTR_TX] = instr_tx_exec,
 	[INSTR_TX_I] = instr_tx_i_exec,
 	[INSTR_DROP] = instr_drop_exec,
+	[INSTR_MIRROR] = instr_mirror_exec,
 
 	[INSTR_HDR_EXTRACT] = instr_hdr_extract_exec,
 	[INSTR_HDR_EXTRACT2] = instr_hdr_extract2_exec,
@@ -9026,6 +9164,7 @@ rte_swx_pipeline_free(struct rte_swx_pipeline *p)
 	header_free(p);
 	extern_func_free(p);
 	extern_obj_free(p);
+	mirroring_free(p);
 	port_out_free(p);
 	port_in_free(p);
 	struct_free(p);
@@ -9234,6 +9373,10 @@ rte_swx_pipeline_build(struct rte_swx_pipeline *p)
 	if (status)
 		goto error;
 
+	status = mirroring_build(p);
+	if (status)
+		goto error;
+
 	status = struct_build(p);
 	if (status)
 		goto error;
@@ -9305,6 +9448,7 @@ rte_swx_pipeline_build(struct rte_swx_pipeline *p)
 	header_build_free(p);
 	extern_func_build_free(p);
 	extern_obj_build_free(p);
+	mirroring_build_free(p);
 	port_out_build_free(p);
 	port_in_build_free(p);
 	struct_build_free(p);
@@ -9356,6 +9500,8 @@ rte_swx_ctl_pipeline_info_get(struct rte_swx_pipeline *p,
 
 	pipeline->n_ports_in = p->n_ports_in;
 	pipeline->n_ports_out = p->n_ports_out;
+	pipeline->n_mirroring_slots = p->n_mirroring_slots;
+	pipeline->n_mirroring_sessions = p->n_mirroring_sessions;
 	pipeline->n_actions = n_actions;
 	pipeline->n_tables = n_tables;
 	pipeline->n_selectors = p->n_selectors;
@@ -10043,6 +10189,27 @@ rte_swx_ctl_meter_stats_read(struct rte_swx_pipeline *p,
 	return 0;
 }
 
+int
+rte_swx_ctl_pipeline_mirroring_session_set(struct rte_swx_pipeline *p,
+					   uint32_t session_id,
+					   struct rte_swx_pipeline_mirroring_session_params *params)
+{
+	struct mirroring_session *s;
+
+	CHECK(p, EINVAL);
+	CHECK(p->build_done, EEXIST);
+	CHECK(session_id < p->n_mirroring_sessions, EINVAL);
+	CHECK(params, EINVAL);
+	CHECK(params->port_id < p->n_ports_out, EINVAL);
+
+	s = &p->mirroring_sessions[session_id];
+	s->port_id = params->port_id;
+	s->fast_clone = params->fast_clone;
+	s->truncation_length = params->truncation_length ? params->truncation_length : UINT32_MAX;
+
+	return 0;
+}
+
 /*
  * Pipeline compilation.
  */
@@ -10055,6 +10222,7 @@ instr_type_to_name(struct instruction *instr)
 	case INSTR_TX: return "INSTR_TX";
 	case INSTR_TX_I: return "INSTR_TX_I";
 	case INSTR_DROP: return "INSTR_DROP";
+	case INSTR_MIRROR: return "INSTR_MIRROR";
 
 	case INSTR_HDR_EXTRACT: return "INSTR_HDR_EXTRACT";
 	case INSTR_HDR_EXTRACT2: return "INSTR_HDR_EXTRACT2";
@@ -10357,6 +10525,34 @@ instr_io_export(struct instruction *instr, FILE *f)
 		"\t},\n");
 }
 
+static void
+instr_mirror_export(struct instruction *instr, FILE *f)
+{
+	fprintf(f,
+		"\t{\n"
+		"\t\t.type = %s,\n"
+		"\t\t.mirror = {\n"
+		"\t\t\t.dst = {\n"
+		"\t\t\t\t.struct_id = %u,\n"
+		"\t\t\t\t.n_bits = %u,\n"
+		"\t\t\t\t.offset = %u,\n"
+		"\t\t\t}\n,"
+		"\t\t\t.src = {\n"
+		"\t\t\t\t.struct_id = %u,\n"
+		"\t\t\t\t.n_bits = %u,\n"
+		"\t\t\t\t.offset = %u,\n"
+		"\t\t\t}\n,"
+		"\t\t},\n"
+		"\t},\n",
+		instr_type_to_name(instr),
+		instr->mirror.dst.struct_id,
+		instr->mirror.dst.n_bits,
+		instr->mirror.dst.offset,
+		instr->mirror.src.struct_id,
+		instr->mirror.src.n_bits,
+		instr->mirror.src.offset);
+}
+
 static void
 instr_hdr_validate_export(struct instruction *instr, FILE *f)
 {
@@ -10924,6 +11120,7 @@ static instruction_export_t export_table[] = {
 	[INSTR_TX] = instr_io_export,
 	[INSTR_TX_I] = instr_io_export,
 	[INSTR_DROP] = instr_io_export,
+	[INSTR_MIRROR] = instr_mirror_export,
 
 	[INSTR_HDR_EXTRACT] = instr_io_export,
 	[INSTR_HDR_EXTRACT2] = instr_io_export,
@@ -11142,6 +11339,7 @@ instr_type_to_func(struct instruction *instr)
 	case INSTR_TX: return "__instr_tx_exec";
 	case INSTR_TX_I: return "__instr_tx_i_exec";
 	case INSTR_DROP: return "__instr_drop_exec";
+	case INSTR_MIRROR: return "__instr_mirror_exec";
 
 	case INSTR_HDR_EXTRACT: return "__instr_hdr_extract_exec";
 	case INSTR_HDR_EXTRACT2: return "__instr_hdr_extract2_exec";
diff --git a/lib/pipeline/rte_swx_pipeline.h b/lib/pipeline/rte_swx_pipeline.h
index 430e458335..1cfd1c542f 100644
--- a/lib/pipeline/rte_swx_pipeline.h
+++ b/lib/pipeline/rte_swx_pipeline.h
@@ -159,6 +159,36 @@ rte_swx_pipeline_port_out_config(struct rte_swx_pipeline *p,
 				 uint32_t port_id,
 				 const char *port_type_name,
 				 void *args);
+/*
+ * Packet mirroring
+ */
+
+/** Packet mirroring parameters. */
+struct rte_swx_pipeline_mirroring_params {
+	/** Number of packet mirroring slots. */
+	uint32_t n_slots;
+
+	/** Maximum number of packet mirroring sessions. */
+	uint32_t n_sessions;
+};
+
+/**
+ * Packet mirroring configure
+ *
+ * @param[in] p
+ *   Pipeline handle.
+ * @param[in] params
+ *   Packet mirroring parameters.
+ * @return
+ *   0 on success or the following error codes otherwise:
+ *   -EINVAL: Invalid argument;
+ *   -ENOMEM: Not enough memory;
+ *   -EEXIST: Pipeline was already built successfully.
+ */
+__rte_experimental
+int
+rte_swx_pipeline_mirroring_config(struct rte_swx_pipeline *p,
+				  struct rte_swx_pipeline_mirroring_params *params);
 
 /*
  * Extern objects and functions
diff --git a/lib/pipeline/rte_swx_pipeline_internal.h b/lib/pipeline/rte_swx_pipeline_internal.h
index da3e88bfa8..808a0cbdbb 100644
--- a/lib/pipeline/rte_swx_pipeline_internal.h
+++ b/lib/pipeline/rte_swx_pipeline_internal.h
@@ -104,10 +104,21 @@ TAILQ_HEAD(port_out_tailq, port_out);
 
 struct port_out_runtime {
 	rte_swx_port_out_pkt_tx_t pkt_tx;
+	rte_swx_port_out_pkt_fast_clone_tx_t pkt_fast_clone_tx;
+	rte_swx_port_out_pkt_clone_tx_t pkt_clone_tx;
 	rte_swx_port_out_flush_t flush;
 	void *obj;
 };
 
+/*
+ * Packet mirroring.
+ */
+struct mirroring_session {
+	uint32_t port_id;
+	int fast_clone;
+	uint32_t truncation_length;
+};
+
 /*
  * Extern object.
  */
@@ -227,6 +238,13 @@ enum instruction_type {
 	INSTR_TX_I, /* port_out = I */
 	INSTR_DROP,
 
+	/*
+	 * mirror slot_id session_id
+	 * slot_id = MEFT
+	 * session_id = MEFT
+	 */
+	INSTR_MIRROR,
+
 	/* extract h.header */
 	INSTR_HDR_EXTRACT,
 	INSTR_HDR_EXTRACT2,
@@ -670,6 +688,7 @@ struct instruction {
 	enum instruction_type type;
 	union {
 		struct instr_io io;
+		struct instr_dst_src mirror;
 		struct instr_hdr_validity valid;
 		struct instr_dst_src mov;
 		struct instr_regarray regarray;
@@ -902,6 +921,8 @@ struct thread {
 	/* Packet. */
 	struct rte_swx_pkt pkt;
 	uint8_t *ptr;
+	uint32_t *mirroring_slots;
+	uint64_t mirroring_slots_mask;
 
 	/* Structures. */
 	uint8_t **structs;
@@ -1399,6 +1420,7 @@ struct rte_swx_pipeline {
 
 	struct port_in_runtime *in;
 	struct port_out_runtime *out;
+	struct mirroring_session *mirroring_sessions;
 	struct instruction **action_instructions;
 	action_func_t *action_funcs;
 	struct rte_swx_table_state *table_state;
@@ -1416,6 +1438,8 @@ struct rte_swx_pipeline {
 	uint32_t n_structs;
 	uint32_t n_ports_in;
 	uint32_t n_ports_out;
+	uint32_t n_mirroring_slots;
+	uint32_t n_mirroring_sessions;
 	uint32_t n_extern_objs;
 	uint32_t n_extern_funcs;
 	uint32_t n_actions;
@@ -1511,6 +1535,8 @@ __instr_rx_exec(struct rte_swx_pipeline *p, struct thread *t, const struct instr
 	      pkt_received ? "1 pkt" : "0 pkts",
 	      p->port_id);
 
+	t->mirroring_slots_mask = 0;
+
 	/* Headers. */
 	t->valid_headers = 0;
 	t->n_headers_out = 0;
@@ -1596,6 +1622,33 @@ emit_handler(struct thread *t)
 	}
 }
 
+static inline void
+mirroring_handler(struct rte_swx_pipeline *p, struct thread *t, struct rte_swx_pkt *pkt)
+{
+	uint64_t slots_mask = t->mirroring_slots_mask, slot_mask;
+	uint32_t slot_id;
+
+	for (slot_id = 0, slot_mask = 1LLU ; slots_mask; slot_id++, slot_mask <<= 1)
+		if (slot_mask & slots_mask) {
+			struct port_out_runtime *port;
+			struct mirroring_session *session;
+			uint32_t port_id, session_id;
+
+			session_id = t->mirroring_slots[slot_id];
+			session = &p->mirroring_sessions[session_id];
+
+			port_id = session->port_id;
+			port = &p->out[port_id];
+
+			if (session->fast_clone)
+				port->pkt_fast_clone_tx(port->obj, pkt);
+			else
+				port->pkt_clone_tx(port->obj, pkt, session->truncation_length);
+
+			slots_mask &= ~slot_mask;
+		}
+}
+
 static inline void
 __instr_tx_exec(struct rte_swx_pipeline *p, struct thread *t, const struct instruction *ip)
 {
@@ -1611,6 +1664,7 @@ __instr_tx_exec(struct rte_swx_pipeline *p, struct thread *t, const struct instr
 	emit_handler(t);
 
 	/* Packet. */
+	mirroring_handler(p, t, pkt);
 	port->pkt_tx(port->obj, pkt);
 }
 
@@ -1629,6 +1683,7 @@ __instr_tx_i_exec(struct rte_swx_pipeline *p, struct thread *t, const struct ins
 	emit_handler(t);
 
 	/* Packet. */
+	mirroring_handler(p, t, pkt);
 	port->pkt_tx(port->obj, pkt);
 }
 
@@ -1648,9 +1703,30 @@ __instr_drop_exec(struct rte_swx_pipeline *p,
 	emit_handler(t);
 
 	/* Packet. */
+	mirroring_handler(p, t, pkt);
 	port->pkt_tx(port->obj, pkt);
 }
 
+static inline void
+__instr_mirror_exec(struct rte_swx_pipeline *p,
+		    struct thread *t,
+		    const struct instruction *ip)
+{
+	uint64_t slot_id = instr_operand_hbo(t, &ip->mirror.dst);
+	uint64_t session_id = instr_operand_hbo(t, &ip->mirror.src);
+
+	slot_id &= p->n_mirroring_slots - 1;
+	session_id &= p->n_mirroring_sessions - 1;
+
+	TRACE("[Thread %2u]: mirror pkt (slot = %u, session = %u)\n",
+	      p->thread_id,
+	      (uint32_t)slot_id,
+	      (uint32_t)session_id);
+
+	t->mirroring_slots[slot_id] = session_id;
+	t->mirroring_slots_mask |= 1LLU << slot_id;
+}
+
 /*
  * extract.
  */
diff --git a/lib/pipeline/version.map b/lib/pipeline/version.map
index 8bc90e7cd7..44332aad26 100644
--- a/lib/pipeline/version.map
+++ b/lib/pipeline/version.map
@@ -67,6 +67,7 @@ EXPERIMENTAL {
 	rte_swx_ctl_pipeline_create;
 	rte_swx_ctl_pipeline_free;
 	rte_swx_ctl_pipeline_info_get;
+	rte_swx_ctl_pipeline_mirroring_session_set;
 	rte_swx_ctl_pipeline_numa_node_get;
 	rte_swx_ctl_pipeline_port_in_stats_read;
 	rte_swx_ctl_pipeline_port_out_stats_read;
@@ -90,6 +91,7 @@ EXPERIMENTAL {
 	rte_swx_pipeline_flush;
 	rte_swx_pipeline_free;
 	rte_swx_pipeline_instructions_config;
+	rte_swx_pipeline_mirroring_config;
 	rte_swx_pipeline_packet_header_register;
 	rte_swx_pipeline_packet_metadata_register;
 	rte_swx_pipeline_port_in_config;
