diff --git a/app/test-pmd/cmdline.c b/app/test-pmd/cmdline.c
index 357660b..563a4e1 100644
--- a/app/test-pmd/cmdline.c
+++ b/app/test-pmd/cmdline.c
@@ -15598,6 +15598,110 @@ static void cmd_set_mplsogre_parsed(void *parsed_result,
 	},
 };
 
+/** Set decapsulation L3 details */
+struct cmd_set_decap_l3_result {
+	cmdline_fixed_string_t set;
+	cmdline_fixed_string_t decap_l3;
+	cmdline_fixed_string_t pos_token;
+	cmdline_fixed_string_t ip_version;
+	uint32_t vlan_present:1;
+	uint16_t tci;
+	struct ether_addr eth_src;
+	struct ether_addr eth_dst;
+};
+
+cmdline_parse_token_string_t cmd_set_decap_l3_set =
+	TOKEN_STRING_INITIALIZER(struct cmd_set_decap_l3_result, set, "set");
+cmdline_parse_token_string_t cmd_set_decap_l3_decap_l3 =
+	TOKEN_STRING_INITIALIZER(struct cmd_set_decap_l3_result, decap_l3,
+				 "decap_l3");
+cmdline_parse_token_string_t cmd_set_decap_l3_decap_l3_with_vlan =
+	TOKEN_STRING_INITIALIZER(struct cmd_set_decap_l3_result, decap_l3,
+				 "decap_l3-with-vlan");
+cmdline_parse_token_string_t cmd_set_decap_l3_ip_version =
+	TOKEN_STRING_INITIALIZER(struct cmd_set_decap_l3_result, pos_token,
+				 "ip-version");
+cmdline_parse_token_string_t cmd_set_decap_l3_ip_version_value =
+	TOKEN_STRING_INITIALIZER(struct cmd_set_decap_l3_result, ip_version,
+				 "ipv4#ipv6");
+cmdline_parse_token_string_t cmd_set_decap_l3_vlan =
+	TOKEN_STRING_INITIALIZER(struct cmd_set_decap_l3_result, pos_token,
+				 "vlan-tci");
+cmdline_parse_token_num_t cmd_set_decap_l3_vlan_value =
+	TOKEN_NUM_INITIALIZER(struct cmd_set_decap_l3_result, tci, UINT16);
+cmdline_parse_token_string_t cmd_set_decap_l3_eth_src =
+	TOKEN_STRING_INITIALIZER(struct cmd_set_decap_l3_result, pos_token,
+				 "eth-src");
+cmdline_parse_token_etheraddr_t cmd_set_decap_l3_eth_src_value =
+	TOKEN_ETHERADDR_INITIALIZER(struct cmd_set_decap_l3_result, eth_src);
+cmdline_parse_token_string_t cmd_set_decap_l3_eth_dst =
+	TOKEN_STRING_INITIALIZER(struct cmd_set_decap_l3_result, pos_token,
+				 "eth-dst");
+cmdline_parse_token_etheraddr_t cmd_set_decap_l3_eth_dst_value =
+	TOKEN_ETHERADDR_INITIALIZER(struct cmd_set_decap_l3_result, eth_dst);
+
+static void cmd_set_decap_l3_parsed(void *parsed_result,
+	__attribute__((unused)) struct cmdline *cl,
+	__attribute__((unused)) void *data)
+{
+	struct cmd_set_decap_l3_result *res = parsed_result;
+
+	if (strcmp(res->decap_l3, "decap_l3") == 0)
+		decap_l3_conf.select_vlan = 0;
+	else if (strcmp(res->decap_l3, "decap_l3-with-vlan") == 0)
+		decap_l3_conf.select_vlan = 1;
+	if (strcmp(res->ip_version, "ipv4") == 0)
+		decap_l3_conf.select_ipv4 = 1;
+	else if (strcmp(res->ip_version, "ipv6") == 0)
+		decap_l3_conf.select_ipv4 = 0;
+	else
+		return;
+	if (decap_l3_conf.select_vlan)
+		decap_l3_conf.vlan_tci = rte_cpu_to_be_16(res->tci);
+	rte_memcpy(decap_l3_conf.eth_src, res->eth_src.addr_bytes,
+		   ETHER_ADDR_LEN);
+	rte_memcpy(decap_l3_conf.eth_dst, res->eth_dst.addr_bytes,
+		   ETHER_ADDR_LEN);
+}
+
+cmdline_parse_inst_t cmd_set_decap_l3 = {
+	.f = cmd_set_decap_l3_parsed,
+	.data = NULL,
+	.help_str = "set decap_l3 ip-version ipv4|ipv6 eth-src <eth-src>"
+		" eth-dst <eth-dst>",
+	.tokens = {
+		(void *)&cmd_set_decap_l3_set,
+		(void *)&cmd_set_decap_l3_decap_l3,
+		(void *)&cmd_set_decap_l3_ip_version,
+		(void *)&cmd_set_decap_l3_ip_version_value,
+		(void *)&cmd_set_decap_l3_eth_src,
+		(void *)&cmd_set_decap_l3_eth_src_value,
+		(void *)&cmd_set_decap_l3_eth_dst,
+		(void *)&cmd_set_decap_l3_eth_dst_value,
+		NULL,
+	},
+};
+
+cmdline_parse_inst_t cmd_set_decap_l3_with_vlan = {
+	.f = cmd_set_decap_l3_parsed,
+	.data = NULL,
+	.help_str = "set decap_l3-with-vlan ip-version ipv4|ipv6"
+		" vlan-tci <vlan-tci> eth-src <eth-src> eth-dst <eth-dst>",
+	.tokens = {
+		(void *)&cmd_set_decap_l3_set,
+		(void *)&cmd_set_decap_l3_decap_l3_with_vlan,
+		(void *)&cmd_set_decap_l3_ip_version,
+		(void *)&cmd_set_decap_l3_ip_version_value,
+		(void *)&cmd_set_decap_l3_vlan,
+		(void *)&cmd_set_decap_l3_vlan_value,
+		(void *)&cmd_set_decap_l3_eth_src,
+		(void *)&cmd_set_decap_l3_eth_src_value,
+		(void *)&cmd_set_decap_l3_eth_dst,
+		(void *)&cmd_set_decap_l3_eth_dst_value,
+		NULL,
+	},
+};
+
 /* Strict link priority scheduling mode setting */
 static void
 cmd_strict_link_prio_parsed(
@@ -18231,6 +18335,8 @@ struct cmd_config_per_queue_tx_offload_result {
 	(cmdline_parse_inst_t *)&cmd_set_mplsoudp_with_vlan,
 	(cmdline_parse_inst_t *)&cmd_set_mplsogre,
 	(cmdline_parse_inst_t *)&cmd_set_mplsogre_with_vlan,
+	(cmdline_parse_inst_t *)&cmd_set_decap_l3,
+	(cmdline_parse_inst_t *)&cmd_set_decap_l3_with_vlan,
 	(cmdline_parse_inst_t *)&cmd_ddp_add,
 	(cmdline_parse_inst_t *)&cmd_ddp_del,
 	(cmdline_parse_inst_t *)&cmd_ddp_get_list,
diff --git a/app/test-pmd/cmdline_flow.c b/app/test-pmd/cmdline_flow.c
index 9433823..cf1374f 100644
--- a/app/test-pmd/cmdline_flow.c
+++ b/app/test-pmd/cmdline_flow.c
@@ -245,6 +245,7 @@ enum index {
 	ACTION_NVGRE_DECAP,
 	ACTION_MPLSOUDP_ENCAP,
 	ACTION_MPLSOGRE_ENCAP,
+	ACTION_DECAP_L3,
 };
 
 /** Maximum size for pattern in struct rte_flow_item_raw. */
@@ -279,6 +280,16 @@ struct action_tunnel_encap_l3_data {
 	uint8_t buf[ACTION_TUNNEL_ENCAP_MAX_BUFFER_SIZE];
 };
 
+/** Maximum buffer size for the decap L3 data. */
+#define ACTION_TUNNEL_DECAP_L3_MAX_BUFFER_SIZE \
+	(sizeof(struct rte_flow_item_eth) + sizeof(struct rte_flow_item_vlan))
+
+/** Storage for struct rte_flow_action_tunnel_decap_l3 with external data. */
+struct action_tunnel_decap_l3_data {
+	struct rte_flow_action_tunnel_decap_l3 conf;
+	uint8_t buf[ACTION_TUNNEL_DECAP_L3_MAX_BUFFER_SIZE];
+};
+
 /** Maximum number of subsequent tokens and arguments on the stack. */
 #define CTX_STACK_SIZE 16
 
@@ -802,6 +813,7 @@ struct parse_action_priv {
 	ACTION_NVGRE_DECAP,
 	ACTION_MPLSOUDP_ENCAP,
 	ACTION_MPLSOGRE_ENCAP,
+	ACTION_DECAP_L3,
 	ZERO,
 };
 
@@ -944,6 +956,9 @@ static int parse_vc_action_mplsoudp_encap(struct context *,
 static int parse_vc_action_mplsogre_encap(struct context *,
 					  const struct token *, const char *,
 					  unsigned int, void *, unsigned int);
+static int parse_vc_action_decap_l3(struct context *, const struct token *,
+				    const char *, unsigned int, void *,
+				    unsigned int);
 static int parse_destroy(struct context *, const struct token *,
 			 const char *, unsigned int,
 			 void *, unsigned int);
@@ -2480,6 +2495,15 @@ static int comp_vc_action_rss_queue(struct context *, const struct token *,
 		.next = NEXT(NEXT_ENTRY(ACTION_NEXT)),
 		.call = parse_vc_action_mplsogre_encap,
 	},
+	[ACTION_DECAP_L3] = {
+		.name = "decap_l3",
+		.help = "decap l3 packet, uses configuration set by"
+			" \"set decap_l3\"",
+		.priv = PRIV_ACTION(TUNNEL_DECAP_L3,
+				    sizeof(struct action_tunnel_decap_l3_data)),
+		.next = NEXT(NEXT_ENTRY(ACTION_NEXT)),
+		.call = parse_vc_action_decap_l3,
+	},
 };
 
 /** Remove and return last entry from argument stack. */
@@ -3442,6 +3466,63 @@ static int comp_vc_action_rss_queue(struct context *, const struct token *,
 	return ret;
 }
 
+/** Parse decap_l3 action. */
+static int
+parse_vc_action_decap_l3(struct context *ctx, const struct token *token,
+			 const char *str, unsigned int len,
+			 void *buf, unsigned int size)
+{
+	struct buffer *out = buf;
+	struct rte_flow_action *action;
+	struct action_tunnel_decap_l3_data *action_decap_data;
+	struct rte_flow_item_eth eth = { .type = 0, };
+	struct rte_flow_item_vlan vlan = {
+		.tci = mplsogre_encap_conf.vlan_tci,
+		.inner_type = 0,
+	};
+	uint8_t *header;
+	int ret;
+
+	ret = parse_vc(ctx, token, str, len, buf, size);
+	if (ret < 0)
+		return ret;
+	/* Nothing else to do if there is no buffer. */
+	if (!out)
+		return ret;
+	if (!out->args.vc.actions_n)
+		return -1;
+	action = &out->args.vc.actions[out->args.vc.actions_n - 1];
+	/* Point to selected object. */
+	ctx->object = out->args.vc.data;
+	ctx->objmask = NULL;
+	/* Copy the headers to the buffer. */
+	action_decap_data = ctx->object;
+	*action_decap_data = (struct action_tunnel_decap_l3_data) {
+		.conf = (struct rte_flow_action_tunnel_decap_l3){
+			.buf = action_decap_data->buf,
+		},
+		.buf = {},
+	};
+	header = action_decap_data->buf;
+	if (decap_l3_conf.select_vlan)
+		eth.type = rte_cpu_to_be_16(ETHER_TYPE_VLAN);
+	memcpy(eth.dst.addr_bytes, decap_l3_conf.eth_dst, ETHER_ADDR_LEN);
+	memcpy(eth.src.addr_bytes, decap_l3_conf.eth_src, ETHER_ADDR_LEN);
+	memcpy(header, &eth, sizeof(eth));
+	header += sizeof(eth);
+	if (decap_l3_conf.select_vlan) {
+		if (decap_l3_conf.select_ipv4)
+			vlan.inner_type = rte_cpu_to_be_16(ETHER_TYPE_IPv4);
+		else
+			vlan.inner_type = rte_cpu_to_be_16(ETHER_TYPE_IPv6);
+		memcpy(header, &vlan, sizeof(vlan));
+		header += sizeof(vlan);
+	}
+	action_decap_data->conf.size = header - action_decap_data->buf;
+	action->conf = &action_decap_data->conf;
+	return ret;
+}
+
 /** Parse tokens for destroy command. */
 static int
 parse_destroy(struct context *ctx, const struct token *token,
diff --git a/app/test-pmd/testpmd.h b/app/test-pmd/testpmd.h
index 6d1b2c3..377470e 100644
--- a/app/test-pmd/testpmd.h
+++ b/app/test-pmd/testpmd.h
@@ -516,6 +516,16 @@ struct mplsogre_encap_conf {
 };
 struct mplsogre_encap_conf mplsogre_encap_conf;
 
+/* decap l3 parameters. */
+struct decap_l3_conf {
+	uint32_t select_ipv4:1;
+	uint32_t select_vlan:1;
+	rte_be16_t vlan_tci;
+	uint8_t eth_src[ETHER_ADDR_LEN];
+	uint8_t eth_dst[ETHER_ADDR_LEN];
+};
+struct decap_l3_conf decap_l3_conf;
+
 static inline unsigned int
 lcore_num(void)
 {
diff --git a/doc/guides/testpmd_app_ug/testpmd_funcs.rst b/doc/guides/testpmd_app_ug/testpmd_funcs.rst
index 61280f1..7183aee 100644
--- a/doc/guides/testpmd_app_ug/testpmd_funcs.rst
+++ b/doc/guides/testpmd_app_ug/testpmd_funcs.rst
@@ -1602,6 +1602,20 @@ flow rule using the action mplsogre_encap will use the last configuration set.
 To have a different encapsulation header, one of those commands must be called
 before the flow rule creation.
 
+Config decapsulation L3
+~~~~~~~~~~~~~~~~~~~~~~~
+
+Configure the layer 2 header value of the inner packet::
+
+ set decap_l3 ip-version (ipv4|ipv6) eth-src (eth-src) eth-dst (eth-dst)
+ set mplsoudp-with-vlan ip-version (ipv4|ipv6) vlan-tci (vlan-tci) \
+ eth-src (eth-src) eth-dst (eth-dst)
+
+Those command will set an internal configuration inside testpmd, any following
+flow rule using the action decap_l3 will use the last configuration set.
+To have a different decapsulation header, one of those commands must be called
+before the flow rule creation.
+
 Port Functions
 --------------
 
@@ -3742,6 +3756,9 @@ This section lists supported actions and their attributes, if any.
 - ``mplsogre_encap``: Performs a MPLSoGRE encapsulation, outer layer
   configuration is done through `Config MPLSoGRE Encap outer layers`_.
 
+- ``decap_l3``: Performs a decapsulation of l3 tunnel, L2 configuratin
+  is done through `Config decapsulation L3`_.
+
 Destroying flow rules
 ~~~~~~~~~~~~~~~~~~~~~
 
@@ -4128,6 +4145,24 @@ IPv6 MPLSoUDP outer header::
         eth-dst 22:22:22:22:22:22
  testpmd> flow create 0 egress pattern end actions mplsoudp_encap / end
 
+Sample decapsulation l3 rule
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Decapsulation of L3 tunnel has default L2 value pre-configured in testpmd
+source code, those can be changed by using the following commands
+
+IPv4 L2 header::
+
+ testpmd> set decap_l3 ip-version ipv4 eth-src 11:11:11:11:11:11
+        eth-dst 22:22:22:22:22:22
+ testpmd> flow create 0 ingress pattern eth / ipv4 / udp / mpls / end actions
+        decap_l3 / end
+
+ testpmd> set decap_l3-with-vlan ip-version ipv4 vlan-tci 34
+        eth-src 11:11:11:11:11:11 eth-dst 22:22:22:22:22:22
+ testpmd> flow create 0 ingress pattern eth / ipv4 / udp / mpls / end actions
+        decap_l3 / end
+
 BPF Functions
 --------------
 
