From mboxrd@z Thu Jan  1 00:00:00 1970
Return-Path: <dev-bounces@dpdk.org>
Received: from dpdk.org (dpdk.org [92.243.14.124])
	by inbox.dpdk.org (Postfix) with ESMTP id 69C11A04DB;
	Fri, 16 Oct 2020 12:35:04 +0200 (CEST)
Received: from [92.243.14.124] (localhost [127.0.0.1])
	by dpdk.org (Postfix) with ESMTP id 913F11ECE6;
	Fri, 16 Oct 2020 12:34:42 +0200 (CEST)
Received: from hqnvemgate25.nvidia.com (hqnvemgate25.nvidia.com
 [216.228.121.64]) by dpdk.org (Postfix) with ESMTP id 7AC7A2B82
 for <dev@dpdk.org>; Fri, 16 Oct 2020 12:34:39 +0200 (CEST)
Received: from hqmail.nvidia.com (Not Verified[216.228.121.13]) by
 hqnvemgate25.nvidia.com (using TLS: TLSv1.2, AES256-SHA)
 id <B5f8977120000>; Fri, 16 Oct 2020 03:33:54 -0700
Received: from nvidia.com (10.124.1.5) by HQMAIL107.nvidia.com (172.20.187.13)
 with Microsoft SMTP Server (TLS) id 15.0.1473.3;
 Fri, 16 Oct 2020 10:34:26 +0000
From: Gregory Etelson <getelson@nvidia.com>
To: <dev@dpdk.org>
CC: <getelson@nvidia.com>, <matan@nvidia.com>, <rasland@nvidia.com>,
 <elibr@nvidia.com>, <ozsh@nvidia.com>, <ajit.khaparde@broadcom.com>,
 <asafp@nvidia.com>, Ori Kam <orika@nvidia.com>, Wenzhuo Lu
 <wenzhuo.lu@intel.com>, Beilei Xing <beilei.xing@intel.com>, "Bernard
 Iremonger" <bernard.iremonger@intel.com>
Date: Fri, 16 Oct 2020 13:34:00 +0300
Message-ID: <20201016103400.21311-4-getelson@nvidia.com>
X-Mailer: git-send-email 2.28.0
In-Reply-To: <20201016103400.21311-1-getelson@nvidia.com>
References: <20200625160348.26220-1-getelson@mellanox.com>
 <20201016103400.21311-1-getelson@nvidia.com>
MIME-Version: 1.0
Content-Transfer-Encoding: quoted-printable
Content-Type: text/plain
X-Originating-IP: [10.124.1.5]
X-ClientProxiedBy: HQMAIL111.nvidia.com (172.20.187.18) To
 HQMAIL107.nvidia.com (172.20.187.13)
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=nvidia.com; s=n1;
 t=1602844434; bh=V8mcI9lzWLXbWMvnyKkA7ODvnZzeuMZQMirtOIswZic=;
 h=From:To:CC:Subject:Date:Message-ID:X-Mailer:In-Reply-To:
 References:MIME-Version:Content-Transfer-Encoding:Content-Type:
 X-Originating-IP:X-ClientProxiedBy;
 b=kZ/YnzT0+4OlffjV2N/aJSB0C1lcLgr7YMDok5o4+iM/ju6Gru7f9DJzhQeBf1Oqe
 3+UKSXHVUtRpUtVOEUVJQTSSPs2MBOb+Qr6wKfDs0oq6rrv46pvQLo/yCXIllLn6M5
 PDmR2JZj/gg1aInWzDduW4faACnCWqx4peQEDRwOneRtrpxkj23++qob6xxtr/c5/x
 GkQaqMSF9FyF7dYmOcS3woDgEPO3lDzn1R5wUN3nOdJqyfFJt69F5jzFPe3+cfxnem
 z0Ug3a54cwV7ok+BNcIZ69OYyJnmOiqnLjLVlqNUM0+G2yGRm29XhAGxqKgDRH8vwZ
 Wn7prRVKQOPJg==
Subject: [dpdk-dev] [PATCH v7 3/3] app/testpmd: add commands for tunnel
	offload API
X-BeenThere: dev@dpdk.org
X-Mailman-Version: 2.1.15
Precedence: list
List-Id: DPDK patches and discussions <dev.dpdk.org>
List-Unsubscribe: <https://mails.dpdk.org/options/dev>,
 <mailto:dev-request@dpdk.org?subject=unsubscribe>
List-Archive: <http://mails.dpdk.org/archives/dev/>
List-Post: <mailto:dev@dpdk.org>
List-Help: <mailto:dev-request@dpdk.org?subject=help>
List-Subscribe: <https://mails.dpdk.org/listinfo/dev>,
 <mailto:dev-request@dpdk.org?subject=subscribe>
Errors-To: dev-bounces@dpdk.org
Sender: "dev" <dev-bounces@dpdk.org>

Tunnel Offload API provides hardware independent, unified model
to offload tunneled traffic. Key model elements are:
 - apply matches to both outer and inner packet headers
   during entire offload procedure;
 - restore outer header of partially offloaded packet;
 - model is implemented as a set of helper functions.

Implementation details:

* Create application tunnel:
flow tunnel create <port> type <tunnel type>
On success, the command creates application tunnel object and returns
the tunnel descriptor. Tunnel descriptor is used in subsequent flow
creation commands to reference the tunnel.

* Create tunnel steering flow rule:
tunnel_set <tunnel descriptor> parameter used with steering rule
template.

* Create tunnel matching flow rule:
tunnel_match <tunnel descriptor> used with matching rule template.

* If tunnel steering rule was offloaded, outer header of a partially
offloaded packet is restored after miss.

Example:
test packet=3D
<Ether  dst=3D24:8a:07:8d:ae:d6 src=3D50:6b:4b:cc:fc:e2 type=3DIPv4 |
<IP  version=3D4 ihl=3D5 proto=3Dudp src=3D1.1.1.1 dst=3D1.1.1.10 |
<UDP  sport=3D4789 dport=3D4789 len=3D58 chksum=3D0x7f7b |
<VXLAN  NextProtocol=3DEthernet vni=3D0x0 |
<Ether  dst=3D24:aa:aa:aa:aa:d6 src=3D50:bb:bb:bb:bb:e2 type=3DIPv4 |
<IP  version=3D4 ihl=3D5 proto=3Dicmp src=3D2.2.2.2 dst=3D2.2.2.200 |
<ICMP  type=3Decho-request code=3D0 chksum=3D0xf7ff id=3D0x0 seq=3D0x0 |>>>=
>>>>
>>> len(packet)
92

testpmd> flow flush 0
testpmd> port 0/queue 0: received 1 packets
src=3D50:6B:4B:CC:FC:E2 - dst=3D24:8A:07:8D:AE:D6 - type=3D0x0800 -
length=3D92

testpmd> flow tunnel 0 type vxlan
port 0: flow tunnel #1 type vxlan
testpmd> flow create 0 ingress group 0 tunnel_set 1
         pattern eth /ipv4 / udp dst is 4789 / vxlan / end
         actions  jump group 0 / end
Flow rule #0 created
testpmd> port 0/queue 0: received 1 packets
tunnel restore info: - vxlan tunnel - outer header present # <--
  src=3D50:6B:4B:CC:FC:E2 - dst=3D24:8A:07:8D:AE:D6 - type=3D0x0800 -
length=3D92

testpmd> flow create 0 ingress group 0 tunnel_match 1
         pattern eth / ipv4 / udp dst is 4789 / vxlan / eth / ipv4 /
         end
         actions set_mac_dst mac_addr 02:CA:FE:CA:FA:80 /
         queue index 0 / end
Flow rule #1 created
testpmd> port 0/queue 0: received 1 packets
  src=3D50:BB:BB:BB:BB:E2 - dst=3D02:CA:FE:CA:FA:80 - type=3D0x0800 -
length=3D42

* Destroy flow tunnel
flow tunnel destroy <port> id <tunnel id>

* Show existing flow tunnels
flow tunnel list <port>

Signed-off-by: Gregory Etelson <getelson@nvidia.com>
---
v2:
* introduce testpmd support for tunnel offload API

v3:
* update flow tunnel commands

v5:
* rebase to next-net

v7:
* resolve "%lu" differences in ubuntu 32 & 64
---
 app/test-pmd/cmdline_flow.c                 | 170 ++++++++++++-
 app/test-pmd/config.c                       | 252 +++++++++++++++++++-
 app/test-pmd/testpmd.c                      |   5 +-
 app/test-pmd/testpmd.h                      |  34 ++-
 app/test-pmd/util.c                         |  35 ++-
 doc/guides/testpmd_app_ug/testpmd_funcs.rst |  49 ++++
 6 files changed, 532 insertions(+), 13 deletions(-)

diff --git a/app/test-pmd/cmdline_flow.c b/app/test-pmd/cmdline_flow.c
index 00c70a144a..b9a1f7178a 100644
--- a/app/test-pmd/cmdline_flow.c
+++ b/app/test-pmd/cmdline_flow.c
@@ -74,6 +74,14 @@ enum index {
 	LIST,
 	AGED,
 	ISOLATE,
+	TUNNEL,
+
+	/* Tunnel arguments. */
+	TUNNEL_CREATE,
+	TUNNEL_CREATE_TYPE,
+	TUNNEL_LIST,
+	TUNNEL_DESTROY,
+	TUNNEL_DESTROY_ID,
=20
 	/* Destroy arguments. */
 	DESTROY_RULE,
@@ -93,6 +101,8 @@ enum index {
 	INGRESS,
 	EGRESS,
 	TRANSFER,
+	TUNNEL_SET,
+	TUNNEL_MATCH,
=20
 	/* Shared action arguments */
 	SHARED_ACTION_CREATE,
@@ -713,6 +723,7 @@ struct buffer {
 		} sa; /* Shared action query arguments */
 		struct {
 			struct rte_flow_attr attr;
+			struct tunnel_ops tunnel_ops;
 			struct rte_flow_item *pattern;
 			struct rte_flow_action *actions;
 			uint32_t pattern_n;
@@ -789,10 +800,32 @@ static const enum index next_vc_attr[] =3D {
 	INGRESS,
 	EGRESS,
 	TRANSFER,
+	TUNNEL_SET,
+	TUNNEL_MATCH,
 	PATTERN,
 	ZERO,
 };
=20
+static const enum index tunnel_create_attr[] =3D {
+	TUNNEL_CREATE,
+	TUNNEL_CREATE_TYPE,
+	END,
+	ZERO,
+};
+
+static const enum index tunnel_destroy_attr[] =3D {
+	TUNNEL_DESTROY,
+	TUNNEL_DESTROY_ID,
+	END,
+	ZERO,
+};
+
+static const enum index tunnel_list_attr[] =3D {
+	TUNNEL_LIST,
+	END,
+	ZERO,
+};
+
 static const enum index next_destroy_attr[] =3D {
 	DESTROY_RULE,
 	END,
@@ -1643,6 +1676,9 @@ static int parse_aged(struct context *, const struct =
token *,
 static int parse_isolate(struct context *, const struct token *,
 			 const char *, unsigned int,
 			 void *, unsigned int);
+static int parse_tunnel(struct context *, const struct token *,
+			const char *, unsigned int,
+			void *, unsigned int);
 static int parse_int(struct context *, const struct token *,
 		     const char *, unsigned int,
 		     void *, unsigned int);
@@ -1844,7 +1880,8 @@ static const struct token token_list[] =3D {
 			      LIST,
 			      AGED,
 			      QUERY,
-			      ISOLATE)),
+			      ISOLATE,
+			      TUNNEL)),
 		.call =3D parse_init,
 	},
 	/* Top-level command. */
@@ -1955,6 +1992,49 @@ static const struct token token_list[] =3D {
 			     ARGS_ENTRY(struct buffer, port)),
 		.call =3D parse_isolate,
 	},
+	[TUNNEL] =3D {
+		.name =3D "tunnel",
+		.help =3D "new tunnel API",
+		.next =3D NEXT(NEXT_ENTRY
+			     (TUNNEL_CREATE, TUNNEL_LIST, TUNNEL_DESTROY)),
+		.call =3D parse_tunnel,
+	},
+	/* Tunnel arguments. */
+	[TUNNEL_CREATE] =3D {
+		.name =3D "create",
+		.help =3D "create new tunnel object",
+		.next =3D NEXT(tunnel_create_attr, NEXT_ENTRY(PORT_ID)),
+		.args =3D ARGS(ARGS_ENTRY(struct buffer, port)),
+		.call =3D parse_tunnel,
+	},
+	[TUNNEL_CREATE_TYPE] =3D {
+		.name =3D "type",
+		.help =3D "create new tunnel",
+		.next =3D NEXT(tunnel_create_attr, NEXT_ENTRY(FILE_PATH)),
+		.args =3D ARGS(ARGS_ENTRY(struct tunnel_ops, type)),
+		.call =3D parse_tunnel,
+	},
+	[TUNNEL_DESTROY] =3D {
+		.name =3D "destroy",
+		.help =3D "destroy tunel",
+		.next =3D NEXT(tunnel_destroy_attr, NEXT_ENTRY(PORT_ID)),
+		.args =3D ARGS(ARGS_ENTRY(struct buffer, port)),
+		.call =3D parse_tunnel,
+	},
+	[TUNNEL_DESTROY_ID] =3D {
+		.name =3D "id",
+		.help =3D "tunnel identifier to testroy",
+		.next =3D NEXT(tunnel_destroy_attr, NEXT_ENTRY(UNSIGNED)),
+		.args =3D ARGS(ARGS_ENTRY(struct tunnel_ops, id)),
+		.call =3D parse_tunnel,
+	},
+	[TUNNEL_LIST] =3D {
+		.name =3D "list",
+		.help =3D "list existing tunnels",
+		.next =3D NEXT(tunnel_list_attr, NEXT_ENTRY(PORT_ID)),
+		.args =3D ARGS(ARGS_ENTRY(struct buffer, port)),
+		.call =3D parse_tunnel,
+	},
 	/* Destroy arguments. */
 	[DESTROY_RULE] =3D {
 		.name =3D "rule",
@@ -2018,6 +2098,20 @@ static const struct token token_list[] =3D {
 		.next =3D NEXT(next_vc_attr),
 		.call =3D parse_vc,
 	},
+	[TUNNEL_SET] =3D {
+		.name =3D "tunnel_set",
+		.help =3D "tunnel steer rule",
+		.next =3D NEXT(next_vc_attr, NEXT_ENTRY(UNSIGNED)),
+		.args =3D ARGS(ARGS_ENTRY(struct tunnel_ops, id)),
+		.call =3D parse_vc,
+	},
+	[TUNNEL_MATCH] =3D {
+		.name =3D "tunnel_match",
+		.help =3D "tunnel match rule",
+		.next =3D NEXT(next_vc_attr, NEXT_ENTRY(UNSIGNED)),
+		.args =3D ARGS(ARGS_ENTRY(struct tunnel_ops, id)),
+		.call =3D parse_vc,
+	},
 	/* Validate/create pattern. */
 	[PATTERN] =3D {
 		.name =3D "pattern",
@@ -4495,12 +4589,28 @@ parse_vc(struct context *ctx, const struct token *t=
oken,
 		return len;
 	}
 	ctx->objdata =3D 0;
-	ctx->object =3D &out->args.vc.attr;
+	switch (ctx->curr) {
+	default:
+		ctx->object =3D &out->args.vc.attr;
+		break;
+	case TUNNEL_SET:
+	case TUNNEL_MATCH:
+		ctx->object =3D &out->args.vc.tunnel_ops;
+		break;
+	}
 	ctx->objmask =3D NULL;
 	switch (ctx->curr) {
 	case GROUP:
 	case PRIORITY:
 		return len;
+	case TUNNEL_SET:
+		out->args.vc.tunnel_ops.enabled =3D 1;
+		out->args.vc.tunnel_ops.actions =3D 1;
+		return len;
+	case TUNNEL_MATCH:
+		out->args.vc.tunnel_ops.enabled =3D 1;
+		out->args.vc.tunnel_ops.items =3D 1;
+		return len;
 	case INGRESS:
 		out->args.vc.attr.ingress =3D 1;
 		return len;
@@ -6108,6 +6218,47 @@ parse_isolate(struct context *ctx, const struct toke=
n *token,
 	return len;
 }
=20
+static int
+parse_tunnel(struct context *ctx, const struct token *token,
+	     const char *str, unsigned int len,
+	     void *buf, unsigned int size)
+{
+	struct buffer *out =3D buf;
+
+	/* Token name must match. */
+	if (parse_default(ctx, token, str, len, NULL, 0) < 0)
+		return -1;
+	/* Nothing else to do if there is no buffer. */
+	if (!out)
+		return len;
+	if (!out->command) {
+		if (ctx->curr !=3D TUNNEL)
+			return -1;
+		if (sizeof(*out) > size)
+			return -1;
+		out->command =3D ctx->curr;
+		ctx->objdata =3D 0;
+		ctx->object =3D out;
+		ctx->objmask =3D NULL;
+	} else {
+		switch (ctx->curr) {
+		default:
+			break;
+		case TUNNEL_CREATE:
+		case TUNNEL_DESTROY:
+		case TUNNEL_LIST:
+			out->command =3D ctx->curr;
+			break;
+		case TUNNEL_CREATE_TYPE:
+		case TUNNEL_DESTROY_ID:
+			ctx->object =3D &out->args.vc.tunnel_ops;
+			break;
+		}
+	}
+
+	return len;
+}
+
 /**
  * Parse signed/unsigned integers 8 to 64-bit long.
  *
@@ -7148,11 +7299,13 @@ cmd_flow_parsed(const struct buffer *in)
 		break;
 	case VALIDATE:
 		port_flow_validate(in->port, &in->args.vc.attr,
-				   in->args.vc.pattern, in->args.vc.actions);
+				   in->args.vc.pattern, in->args.vc.actions,
+				   &in->args.vc.tunnel_ops);
 		break;
 	case CREATE:
 		port_flow_create(in->port, &in->args.vc.attr,
-				 in->args.vc.pattern, in->args.vc.actions);
+				 in->args.vc.pattern, in->args.vc.actions,
+				 &in->args.vc.tunnel_ops);
 		break;
 	case DESTROY:
 		port_flow_destroy(in->port, in->args.destroy.rule_n,
@@ -7178,6 +7331,15 @@ cmd_flow_parsed(const struct buffer *in)
 	case AGED:
 		port_flow_aged(in->port, in->args.aged.destroy);
 		break;
+	case TUNNEL_CREATE:
+		port_flow_tunnel_create(in->port, &in->args.vc.tunnel_ops);
+		break;
+	case TUNNEL_DESTROY:
+		port_flow_tunnel_destroy(in->port, in->args.vc.tunnel_ops.id);
+		break;
+	case TUNNEL_LIST:
+		port_flow_tunnel_list(in->port);
+		break;
 	default:
 		break;
 	}
diff --git a/app/test-pmd/config.c b/app/test-pmd/config.c
index 2c00b55440..660eb5af97 100644
--- a/app/test-pmd/config.c
+++ b/app/test-pmd/config.c
@@ -1521,6 +1521,115 @@ port_mtu_set(portid_t port_id, uint16_t mtu)
=20
 /* Generic flow management functions. */
=20
+static struct port_flow_tunnel *
+port_flow_locate_tunnel_id(struct rte_port *port, uint32_t port_tunnel_id)
+{
+	struct port_flow_tunnel *flow_tunnel;
+
+	LIST_FOREACH(flow_tunnel, &port->flow_tunnel_list, chain) {
+		if (flow_tunnel->id =3D=3D port_tunnel_id)
+			goto out;
+	}
+	flow_tunnel =3D NULL;
+
+out:
+	return flow_tunnel;
+}
+
+const char *
+port_flow_tunnel_type(struct rte_flow_tunnel *tunnel)
+{
+	const char *type;
+	switch (tunnel->type) {
+	default:
+		type =3D "unknown";
+		break;
+	case RTE_FLOW_ITEM_TYPE_VXLAN:
+		type =3D "vxlan";
+		break;
+	}
+
+	return type;
+}
+
+struct port_flow_tunnel *
+port_flow_locate_tunnel(uint16_t port_id, struct rte_flow_tunnel *tun)
+{
+	struct rte_port *port =3D &ports[port_id];
+	struct port_flow_tunnel *flow_tunnel;
+
+	LIST_FOREACH(flow_tunnel, &port->flow_tunnel_list, chain) {
+		if (!memcmp(&flow_tunnel->tunnel, tun, sizeof(*tun)))
+			goto out;
+	}
+	flow_tunnel =3D NULL;
+
+out:
+	return flow_tunnel;
+}
+
+void port_flow_tunnel_list(portid_t port_id)
+{
+	struct rte_port *port =3D &ports[port_id];
+	struct port_flow_tunnel *flt;
+
+	LIST_FOREACH(flt, &port->flow_tunnel_list, chain) {
+		printf("port %u tunnel #%u type=3D%s",
+			port_id, flt->id, port_flow_tunnel_type(&flt->tunnel));
+		if (flt->tunnel.tun_id)
+			printf(" id=3D%lu", (unsigned long)flt->tunnel.tun_id);
+		printf("\n");
+	}
+}
+
+void port_flow_tunnel_destroy(portid_t port_id, uint32_t tunnel_id)
+{
+	struct rte_port *port =3D &ports[port_id];
+	struct port_flow_tunnel *flt;
+
+	LIST_FOREACH(flt, &port->flow_tunnel_list, chain) {
+		if (flt->id =3D=3D tunnel_id)
+			break;
+	}
+	if (flt) {
+		LIST_REMOVE(flt, chain);
+		free(flt);
+		printf("port %u: flow tunnel #%u destroyed\n",
+			port_id, tunnel_id);
+	}
+}
+
+void port_flow_tunnel_create(portid_t port_id, const struct tunnel_ops *op=
s)
+{
+	struct rte_port *port =3D &ports[port_id];
+	enum rte_flow_item_type	type;
+	struct port_flow_tunnel *flt;
+
+	if (!strcmp(ops->type, "vxlan"))
+		type =3D RTE_FLOW_ITEM_TYPE_VXLAN;
+	else {
+		printf("cannot offload \"%s\" tunnel type\n", ops->type);
+		return;
+	}
+	LIST_FOREACH(flt, &port->flow_tunnel_list, chain) {
+		if (flt->tunnel.type =3D=3D type)
+			break;
+	}
+	if (!flt) {
+		flt =3D calloc(1, sizeof(*flt));
+		if (!flt) {
+			printf("failed to allocate port flt object\n");
+			return;
+		}
+		flt->tunnel.type =3D type;
+		flt->id =3D LIST_EMPTY(&port->flow_tunnel_list) ? 1 :
+				  LIST_FIRST(&port->flow_tunnel_list)->id + 1;
+		LIST_INSERT_HEAD(&port->flow_tunnel_list, flt, chain);
+	}
+	printf("port %d: flow tunnel #%u type %s\n",
+		port_id, flt->id, ops->type);
+}
+
 /** Generate a port_flow entry from attributes/pattern/actions. */
 static struct port_flow *
 port_flow_new(const struct rte_flow_attr *attr,
@@ -1860,20 +1969,137 @@ port_shared_action_query(portid_t port_id, uint32_=
t id)
 	}
 	return ret;
 }
+static struct port_flow_tunnel *
+port_flow_tunnel_offload_cmd_prep(portid_t port_id,
+				  const struct rte_flow_item *pattern,
+				  const struct rte_flow_action *actions,
+				  const struct tunnel_ops *tunnel_ops)
+{
+	int ret;
+	struct rte_port *port;
+	struct port_flow_tunnel *pft;
+	struct rte_flow_error error;
+
+	port =3D &ports[port_id];
+	pft =3D port_flow_locate_tunnel_id(port, tunnel_ops->id);
+	if (!pft) {
+		printf("failed to locate port flow tunnel #%u\n",
+			tunnel_ops->id);
+		return NULL;
+	}
+	if (tunnel_ops->actions) {
+		uint32_t num_actions;
+		const struct rte_flow_action *aptr;
+
+		ret =3D rte_flow_tunnel_decap_set(port_id, &pft->tunnel,
+						&pft->pmd_actions,
+						&pft->num_pmd_actions,
+						&error);
+		if (ret) {
+			port_flow_complain(&error);
+			return NULL;
+		}
+		for (aptr =3D actions, num_actions =3D 1;
+		     aptr->type !=3D RTE_FLOW_ACTION_TYPE_END;
+		     aptr++, num_actions++);
+		pft->actions =3D malloc(
+				(num_actions +  pft->num_pmd_actions) *
+				sizeof(actions[0]));
+		if (!pft->actions) {
+			rte_flow_tunnel_action_decap_release(
+					port_id, pft->actions,
+					pft->num_pmd_actions, &error);
+			return NULL;
+		}
+		rte_memcpy(pft->actions, pft->pmd_actions,
+			   pft->num_pmd_actions * sizeof(actions[0]));
+		rte_memcpy(pft->actions + pft->num_pmd_actions, actions,
+			   num_actions * sizeof(actions[0]));
+	}
+	if (tunnel_ops->items) {
+		uint32_t num_items;
+		const struct rte_flow_item *iptr;
+
+		ret =3D rte_flow_tunnel_match(port_id, &pft->tunnel,
+					    &pft->pmd_items,
+					    &pft->num_pmd_items,
+					    &error);
+		if (ret) {
+			port_flow_complain(&error);
+			return NULL;
+		}
+		for (iptr =3D pattern, num_items =3D 1;
+		     iptr->type !=3D RTE_FLOW_ITEM_TYPE_END;
+		     iptr++, num_items++);
+		pft->items =3D malloc((num_items + pft->num_pmd_items) *
+				    sizeof(pattern[0]));
+		if (!pft->items) {
+			rte_flow_tunnel_item_release(
+					port_id, pft->pmd_items,
+					pft->num_pmd_items, &error);
+			return NULL;
+		}
+		rte_memcpy(pft->items, pft->pmd_items,
+			   pft->num_pmd_items * sizeof(pattern[0]));
+		rte_memcpy(pft->items + pft->num_pmd_items, pattern,
+			   num_items * sizeof(pattern[0]));
+	}
+
+	return pft;
+}
+
+static void
+port_flow_tunnel_offload_cmd_release(portid_t port_id,
+				     const struct tunnel_ops *tunnel_ops,
+				     struct port_flow_tunnel *pft)
+{
+	struct rte_flow_error error;
+
+	if (tunnel_ops->actions) {
+		free(pft->actions);
+		rte_flow_tunnel_action_decap_release(
+			port_id, pft->pmd_actions,
+			pft->num_pmd_actions, &error);
+		pft->actions =3D NULL;
+		pft->pmd_actions =3D NULL;
+	}
+	if (tunnel_ops->items) {
+		free(pft->items);
+		rte_flow_tunnel_item_release(port_id, pft->pmd_items,
+					     pft->num_pmd_items,
+					     &error);
+		pft->items =3D NULL;
+		pft->pmd_items =3D NULL;
+	}
+}
=20
 /** Validate flow rule. */
 int
 port_flow_validate(portid_t port_id,
 		   const struct rte_flow_attr *attr,
 		   const struct rte_flow_item *pattern,
-		   const struct rte_flow_action *actions)
+		   const struct rte_flow_action *actions,
+		   const struct tunnel_ops *tunnel_ops)
 {
 	struct rte_flow_error error;
+	struct port_flow_tunnel *pft =3D NULL;
=20
 	/* Poisoning to make sure PMDs update it in case of error. */
 	memset(&error, 0x11, sizeof(error));
+	if (tunnel_ops->enabled) {
+		pft =3D port_flow_tunnel_offload_cmd_prep(port_id, pattern,
+							actions, tunnel_ops);
+		if (!pft)
+			return -ENOENT;
+		if (pft->items)
+			pattern =3D pft->items;
+		if (pft->actions)
+			actions =3D pft->actions;
+	}
 	if (rte_flow_validate(port_id, attr, pattern, actions, &error))
 		return port_flow_complain(&error);
+	if (tunnel_ops->enabled)
+		port_flow_tunnel_offload_cmd_release(port_id, tunnel_ops, pft);
 	printf("Flow rule validated\n");
 	return 0;
 }
@@ -1903,13 +2129,15 @@ int
 port_flow_create(portid_t port_id,
 		 const struct rte_flow_attr *attr,
 		 const struct rte_flow_item *pattern,
-		 const struct rte_flow_action *actions)
+		 const struct rte_flow_action *actions,
+		 const struct tunnel_ops *tunnel_ops)
 {
 	struct rte_flow *flow;
 	struct rte_port *port;
 	struct port_flow *pf;
 	uint32_t id =3D 0;
 	struct rte_flow_error error;
+	struct port_flow_tunnel *pft =3D NULL;
=20
 	port =3D &ports[port_id];
 	if (port->flow_list) {
@@ -1920,6 +2148,16 @@ port_flow_create(portid_t port_id,
 		}
 		id =3D port->flow_list->id + 1;
 	}
+	if (tunnel_ops->enabled) {
+		pft =3D port_flow_tunnel_offload_cmd_prep(port_id, pattern,
+							actions, tunnel_ops);
+		if (!pft)
+			return -ENOENT;
+		if (pft->items)
+			pattern =3D pft->items;
+		if (pft->actions)
+			actions =3D pft->actions;
+	}
 	pf =3D port_flow_new(attr, pattern, actions, &error);
 	if (!pf)
 		return port_flow_complain(&error);
@@ -1935,6 +2173,8 @@ port_flow_create(portid_t port_id,
 	pf->id =3D id;
 	pf->flow =3D flow;
 	port->flow_list =3D pf;
+	if (tunnel_ops->enabled)
+		port_flow_tunnel_offload_cmd_release(port_id, tunnel_ops, pft);
 	printf("Flow rule #%u created\n", pf->id);
 	return 0;
 }
@@ -2244,7 +2484,9 @@ port_flow_list(portid_t port_id, uint32_t n, const ui=
nt32_t *group)
 		       pf->rule.attr->egress ? 'e' : '-',
 		       pf->rule.attr->transfer ? 't' : '-');
 		while (item->type !=3D RTE_FLOW_ITEM_TYPE_END) {
-			if (rte_flow_conv(RTE_FLOW_CONV_OP_ITEM_NAME_PTR,
+			if ((uint32_t)item->type > INT_MAX)
+				name =3D "PMD_INTERNAL";
+			else if (rte_flow_conv(RTE_FLOW_CONV_OP_ITEM_NAME_PTR,
 					  &name, sizeof(name),
 					  (void *)(uintptr_t)item->type,
 					  NULL) <=3D 0)
@@ -2255,7 +2497,9 @@ port_flow_list(portid_t port_id, uint32_t n, const ui=
nt32_t *group)
 		}
 		printf("=3D>");
 		while (action->type !=3D RTE_FLOW_ACTION_TYPE_END) {
-			if (rte_flow_conv(RTE_FLOW_CONV_OP_ACTION_NAME_PTR,
+			if ((uint32_t)action->type > INT_MAX)
+				name =3D "PMD_INTERNAL";
+			else if (rte_flow_conv(RTE_FLOW_CONV_OP_ACTION_NAME_PTR,
 					  &name, sizeof(name),
 					  (void *)(uintptr_t)action->type,
 					  NULL) <=3D 0)
diff --git a/app/test-pmd/testpmd.c b/app/test-pmd/testpmd.c
index 6caba60988..333904d686 100644
--- a/app/test-pmd/testpmd.c
+++ b/app/test-pmd/testpmd.c
@@ -3684,6 +3684,8 @@ init_port_dcb_config(portid_t pid,
 static void
 init_port(void)
 {
+	int i;
+
 	/* Configuration of Ethernet ports. */
 	ports =3D rte_zmalloc("testpmd: ports",
 			    sizeof(struct rte_port) * RTE_MAX_ETHPORTS,
@@ -3693,7 +3695,8 @@ init_port(void)
 				"rte_zmalloc(%d struct rte_port) failed\n",
 				RTE_MAX_ETHPORTS);
 	}
-
+	for (i =3D 0; i < RTE_MAX_ETHPORTS; i++)
+		LIST_INIT(&ports[i].flow_tunnel_list);
 	/* Initialize ports NUMA structures */
 	memset(port_numa, NUMA_NO_CONFIG, RTE_MAX_ETHPORTS);
 	memset(rxring_numa, NUMA_NO_CONFIG, RTE_MAX_ETHPORTS);
diff --git a/app/test-pmd/testpmd.h b/app/test-pmd/testpmd.h
index f8b0a3517d..5238ac3dd5 100644
--- a/app/test-pmd/testpmd.h
+++ b/app/test-pmd/testpmd.h
@@ -12,6 +12,7 @@
 #include <rte_gro.h>
 #include <rte_gso.h>
 #include <cmdline.h>
+#include <sys/queue.h>
=20
 #define RTE_PORT_ALL            (~(portid_t)0x0)
=20
@@ -150,6 +151,26 @@ struct port_shared_action {
 	struct rte_flow_shared_action *action;	/**< Shared action handle. */
 };
=20
+struct port_flow_tunnel {
+	LIST_ENTRY(port_flow_tunnel) chain;
+	struct rte_flow_action *pmd_actions;
+	struct rte_flow_item   *pmd_items;
+	uint32_t id;
+	uint32_t num_pmd_actions;
+	uint32_t num_pmd_items;
+	struct rte_flow_tunnel tunnel;
+	struct rte_flow_action *actions;
+	struct rte_flow_item *items;
+};
+
+struct tunnel_ops {
+	uint32_t id;
+	char type[16];
+	uint32_t enabled:1;
+	uint32_t actions:1;
+	uint32_t items:1;
+};
+
 /**
  * The data structure associated with each port.
  */
@@ -182,6 +203,7 @@ struct rte_port {
 	struct port_flow        *flow_list; /**< Associated flows. */
 	struct port_shared_action *actions_list;
 	/**< Associated shared actions. */
+	LIST_HEAD(, port_flow_tunnel) flow_tunnel_list;
 	const struct rte_eth_rxtx_callback *rx_dump_cb[RTE_MAX_QUEUES_PER_PORT+1]=
;
 	const struct rte_eth_rxtx_callback *tx_dump_cb[RTE_MAX_QUEUES_PER_PORT+1]=
;
 	/**< metadata value to insert in Tx packets. */
@@ -773,11 +795,13 @@ int port_shared_action_update(portid_t port_id, uint3=
2_t id,
 int port_flow_validate(portid_t port_id,
 		       const struct rte_flow_attr *attr,
 		       const struct rte_flow_item *pattern,
-		       const struct rte_flow_action *actions);
+		       const struct rte_flow_action *actions,
+		       const struct tunnel_ops *tunnel_ops);
 int port_flow_create(portid_t port_id,
 		     const struct rte_flow_attr *attr,
 		     const struct rte_flow_item *pattern,
-		     const struct rte_flow_action *actions);
+		     const struct rte_flow_action *actions,
+		     const struct tunnel_ops *tunnel_ops);
 int port_shared_action_query(portid_t port_id, uint32_t id);
 void update_age_action_context(const struct rte_flow_action *actions,
 		     struct port_flow *pf);
@@ -788,6 +812,12 @@ int port_flow_query(portid_t port_id, uint32_t rule,
 		    const struct rte_flow_action *action);
 void port_flow_list(portid_t port_id, uint32_t n, const uint32_t *group);
 void port_flow_aged(portid_t port_id, uint8_t destroy);
+const char *port_flow_tunnel_type(struct rte_flow_tunnel *tunnel);
+struct port_flow_tunnel *
+port_flow_locate_tunnel(uint16_t port_id, struct rte_flow_tunnel *tun);
+void port_flow_tunnel_list(portid_t port_id);
+void port_flow_tunnel_destroy(portid_t port_id, uint32_t tunnel_id);
+void port_flow_tunnel_create(portid_t port_id, const struct tunnel_ops *op=
s);
 int port_flow_isolate(portid_t port_id, int set);
=20
 void rx_ring_desc_display(portid_t port_id, queueid_t rxq_id, uint16_t rxd=
_id);
diff --git a/app/test-pmd/util.c b/app/test-pmd/util.c
index 8488fa1a8f..781a813759 100644
--- a/app/test-pmd/util.c
+++ b/app/test-pmd/util.c
@@ -48,18 +48,49 @@ dump_pkt_burst(uint16_t port_id, uint16_t queue, struct=
 rte_mbuf *pkts[],
 	       is_rx ? "received" : "sent",
 	       (unsigned int) nb_pkts);
 	for (i =3D 0; i < nb_pkts; i++) {
+		int ret;
+		struct rte_flow_error error;
+		struct rte_flow_restore_info info =3D { 0, };
+
 		mb =3D pkts[i];
 		eth_hdr =3D rte_pktmbuf_read(mb, 0, sizeof(_eth_hdr), &_eth_hdr);
 		eth_type =3D RTE_BE_TO_CPU_16(eth_hdr->ether_type);
-		ol_flags =3D mb->ol_flags;
 		packet_type =3D mb->packet_type;
 		is_encapsulation =3D RTE_ETH_IS_TUNNEL_PKT(packet_type);
-
+		ret =3D rte_flow_get_restore_info(port_id, mb, &info, &error);
+		if (!ret) {
+			printf("restore info:");
+			if (info.flags & RTE_FLOW_RESTORE_INFO_TUNNEL) {
+				struct port_flow_tunnel *port_tunnel;
+
+				port_tunnel =3D port_flow_locate_tunnel
+					      (port_id, &info.tunnel);
+				printf(" - tunnel");
+				if (port_tunnel)
+					printf(" #%u", port_tunnel->id);
+				else
+					printf(" %s", "-none-");
+				printf(" type %s",
+					port_flow_tunnel_type(&info.tunnel));
+			} else {
+				printf(" - no tunnel info");
+			}
+			if (info.flags & RTE_FLOW_RESTORE_INFO_ENCAPSULATED)
+				printf(" - outer header present");
+			else
+				printf(" - no outer header");
+			if (info.flags & RTE_FLOW_RESTORE_INFO_GROUP_ID)
+				printf(" - miss group %u", info.group_id);
+			else
+				printf(" - no miss group");
+			printf("\n");
+		}
 		print_ether_addr("  src=3D", &eth_hdr->s_addr);
 		print_ether_addr(" - dst=3D", &eth_hdr->d_addr);
 		printf(" - type=3D0x%04x - length=3D%u - nb_segs=3D%d",
 		       eth_type, (unsigned int) mb->pkt_len,
 		       (int)mb->nb_segs);
+		ol_flags =3D mb->ol_flags;
 		if (ol_flags & PKT_RX_RSS_HASH) {
 			printf(" - RSS hash=3D0x%x", (unsigned int) mb->hash.rss);
 			printf(" - RSS queue=3D0x%x", (unsigned int) queue);
diff --git a/doc/guides/testpmd_app_ug/testpmd_funcs.rst b/doc/guides/testp=
md_app_ug/testpmd_funcs.rst
index 43c0ea0599..05a4446757 100644
--- a/doc/guides/testpmd_app_ug/testpmd_funcs.rst
+++ b/doc/guides/testpmd_app_ug/testpmd_funcs.rst
@@ -3749,6 +3749,45 @@ following sections.
=20
    flow aged {port_id} [destroy]
=20
+- Tunnel offload - create a tunnel stub::
+
+   flow tunnel create {port_id} type {tunnel_type}
+
+- Tunnel offload - destroy a tunnel stub::
+
+   flow tunnel destroy {port_id} id {tunnel_id}
+
+- Tunnel offload - list port tunnel stubs::
+
+   flow tunnel list {port_id}
+
+Creating a tunnel stub for offload
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+``flow tunnel create`` setup a tunnel stub for tunnel offload flow rules::
+
+   flow tunnel create {port_id} type {tunnel_type}
+
+If successful, it will return a tunnel stub ID usable with other commands:=
:
+
+   port [...]: flow tunnel #[...] type [...]
+
+Tunnel stub ID is relative to a port.
+
+Destroying tunnel offload stub
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+``flow tunnel destroy`` destroy port tunnel stub::
+
+   flow tunnel destroy {port_id} id {tunnel_id}
+
+Listing tunnel offload stubs
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+``flow tunnel list`` list port tunnel offload stubs::
+
+   flow tunnel list {port_id}
+
 Validating flow rules
 ~~~~~~~~~~~~~~~~~~~~~
=20
@@ -3795,6 +3834,7 @@ to ``rte_flow_create()``::
=20
    flow create {port_id}
       [group {group_id}] [priority {level}] [ingress] [egress] [transfer]
+      [tunnel_set {tunnel_id}] [tunnel_match {tunnel_id}]
       pattern {item} [/ {item} [...]] / end
       actions {action} [/ {action} [...]] / end
=20
@@ -3809,6 +3849,7 @@ Otherwise it will show an error message of the form::
 Parameters describe in the following order:
=20
 - Attributes (*group*, *priority*, *ingress*, *egress*, *transfer* tokens)=
.
+- Tunnel offload specification (tunnel_set, tunnel_match)
 - A matching pattern, starting with the *pattern* token and terminated by =
an
   *end* pattern item.
 - Actions, starting with the *actions* token and terminated by an *end*
@@ -3852,6 +3893,14 @@ Most rules affect RX therefore contain the ``ingress=
`` token::
=20
    testpmd> flow create 0 ingress pattern [...]
=20
+Tunnel offload
+^^^^^^^^^^^^^^
+
+Indicate tunnel offload rule type
+
+- ``tunnel_set {tunnel_id}``: mark rule as tunnel offload decap_set type.
+- ``tunnel_match {tunnel_id}``:  mark rule as tunel offload match type.
+
 Matching pattern
 ^^^^^^^^^^^^^^^^
=20
--=20
2.28.0