[v3,4/4] app/testpmd: add commands for tunnel offload API

Message ID 20200930091854.19768-5-getelson@nvidia.com (mailing list archive)
State Superseded, archived
Delegated to: Ferruh Yigit
Headers
Series Tunnel Offload API |

Checks

Context Check Description
ci/checkpatch warning coding style issues
ci/iol-broadcom-Functional success Functional Testing PASS
ci/Intel-compilation fail Compilation issues
ci/iol-intel-Functional success Functional Testing PASS
ci/iol-testing fail Testing issues
ci/travis-robot warning Travis build: failed

Commit Message

Gregory Etelson Sept. 30, 2020, 9:18 a.m. UTC
  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=
<Ether  dst=24:8a:07:8d:ae:d6 src=50:6b:4b:cc:fc:e2 type=IPv4 |
<IP  version=4 ihl=5 proto=udp src=1.1.1.1 dst=1.1.1.10 |
<UDP  sport=4789 dport=4789 len=58 chksum=0x7f7b |
<VXLAN  NextProtocol=Ethernet vni=0x0 |
<Ether  dst=24:aa:aa:aa:aa:d6 src=50:bb:bb:bb:bb:e2 type=IPv4 |
<IP  version=4 ihl=5 proto=icmp src=2.2.2.2 dst=2.2.2.200 |
<ICMP  type=echo-request code=0 chksum=0xf7ff id=0x0 seq=0x0 |>>>>>>>
>>> len(packet)
92

testpmd> flow flush 0
testpmd> port 0/queue 0: received 1 packets
src=50:6B:4B:CC:FC:E2 - dst=24:8A:07:8D:AE:D6 - type=0x0800 -
length=92

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=50:6B:4B:CC:FC:E2 - dst=24:8A:07:8D:AE:D6 - type=0x0800 -
length=92

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=50:BB:BB:BB:BB:E2 - dst=02:CA:FE:CA:FA:80 - type=0x0800 -
length=42

* 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
---
 app/test-pmd/cmdline_flow.c                 | 170 ++++++++++++-
 app/test-pmd/config.c                       | 253 +++++++++++++++++++-
 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, 533 insertions(+), 13 deletions(-)
  

Comments

Ajit Khaparde Oct. 1, 2020, 5:32 a.m. UTC | #1
On Wed, Sep 30, 2020 at 2:21 AM Gregory Etelson <getelson@nvidia.com> wrote:
>
> 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=
> <Ether  dst=24:8a:07:8d:ae:d6 src=50:6b:4b:cc:fc:e2 type=IPv4 |
> <IP  version=4 ihl=5 proto=udp src=1.1.1.1 dst=1.1.1.10 |
> <UDP  sport=4789 dport=4789 len=58 chksum=0x7f7b |
> <VXLAN  NextProtocol=Ethernet vni=0x0 |
> <Ether  dst=24:aa:aa:aa:aa:d6 src=50:bb:bb:bb:bb:e2 type=IPv4 |
> <IP  version=4 ihl=5 proto=icmp src=2.2.2.2 dst=2.2.2.200 |
> <ICMP  type=echo-request code=0 chksum=0xf7ff id=0x0 seq=0x0 |>>>>>>>
> >>> len(packet)
> 92
>
> testpmd> flow flush 0
> testpmd> port 0/queue 0: received 1 packets
> src=50:6B:4B:CC:FC:E2 - dst=24:8A:07:8D:AE:D6 - type=0x0800 -
> length=92
>
> 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

I could not get enough time to completely look at this.
Can you help understand the sequence a bit.

So when this flow create is issued, it looks like the application issues
one of the helper APIs.
In response the PMD returns the vendor specific items/actions.
So what happens to the original match and action criteria provided by the user
or applications?

Does the vendor provided actions and items in the tunnel descriptor
take precedence over what was used by the application?

And is the criteria provided by the PMD opaque to the application?
Such that only the PMD can decipher it when the application calls it
for subsequent flow create(s)?

> Flow rule #0 created
> testpmd> port 0/queue 0: received 1 packets
> tunnel restore info: - vxlan tunnel - outer header present # <--
>   src=50:6B:4B:CC:FC:E2 - dst=24:8A:07:8D:AE:D6 - type=0x0800 -
> length=92
>
> 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=50:BB:BB:BB:BB:E2 - dst=02:CA:FE:CA:FA:80 - type=0x0800 -
> length=42
>
> * 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
> ---
>  app/test-pmd/cmdline_flow.c                 | 170 ++++++++++++-
>  app/test-pmd/config.c                       | 253 +++++++++++++++++++-
>  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, 533 insertions(+), 13 deletions(-)
>
> diff --git a/app/test-pmd/cmdline_flow.c b/app/test-pmd/cmdline_flow.c
> index 6263d307ed..0fb61860cd 100644
> --- a/app/test-pmd/cmdline_flow.c
> +++ b/app/test-pmd/cmdline_flow.c
> @@ -69,6 +69,14 @@ enum index {
>         LIST,
>         AGED,
>         ISOLATE,
> +       TUNNEL,
> +
> +       /* Tunnel argumens. */
> +       TUNNEL_CREATE,
> +       TUNNEL_CREATE_TYPE,
> +       TUNNEL_LIST,
> +       TUNNEL_DESTROY,
> +       TUNNEL_DESTROY_ID,
>
>         /* Destroy arguments. */
>         DESTROY_RULE,
> @@ -88,6 +96,8 @@ enum index {
>         INGRESS,
>         EGRESS,
>         TRANSFER,
> +       TUNNEL_SET,
> +       TUNNEL_MATCH,
>
>         /* Validate/create pattern. */
>         PATTERN,
> @@ -653,6 +663,7 @@ struct buffer {
>         union {
>                 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;
> @@ -713,10 +724,32 @@ static const enum index next_vc_attr[] = {
>         INGRESS,
>         EGRESS,
>         TRANSFER,
> +       TUNNEL_SET,
> +       TUNNEL_MATCH,
>         PATTERN,
>         ZERO,
>  };
>
> +static const enum index tunnel_create_attr[] = {
> +       TUNNEL_CREATE,
> +       TUNNEL_CREATE_TYPE,
> +       END,
> +       ZERO,
> +};
> +
> +static const enum index tunnel_destroy_attr[] = {
> +       TUNNEL_DESTROY,
> +       TUNNEL_DESTROY_ID,
> +       END,
> +       ZERO,
> +};
> +
> +static const enum index tunnel_list_attr[] = {
> +       TUNNEL_LIST,
> +       END,
> +       ZERO,
> +};
> +
>  static const enum index next_destroy_attr[] = {
>         DESTROY_RULE,
>         END,
> @@ -1516,6 +1549,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);
> @@ -1698,7 +1734,8 @@ static const struct token token_list[] = {
>                               LIST,
>                               AGED,
>                               QUERY,
> -                             ISOLATE)),
> +                             ISOLATE,
> +                             TUNNEL)),
>                 .call = parse_init,
>         },
>         /* Sub-level commands. */
> @@ -1772,6 +1809,49 @@ static const struct token token_list[] = {
>                              ARGS_ENTRY(struct buffer, port)),
>                 .call = parse_isolate,
>         },
> +       [TUNNEL] = {
> +               .name = "tunnel",
> +               .help = "new tunnel API",
> +               .next = NEXT(NEXT_ENTRY
> +                            (TUNNEL_CREATE, TUNNEL_LIST, TUNNEL_DESTROY)),
> +               .call = parse_tunnel,
> +       },
> +       /* Tunnel arguments. */
> +       [TUNNEL_CREATE] = {
> +               .name = "create",
> +               .help = "create new tunnel object",
> +               .next = NEXT(tunnel_create_attr, NEXT_ENTRY(PORT_ID)),
> +               .args = ARGS(ARGS_ENTRY(struct buffer, port)),
> +               .call = parse_tunnel,
> +       },
> +       [TUNNEL_CREATE_TYPE] = {
> +               .name = "type",
> +               .help = "create new tunnel",
> +               .next = NEXT(tunnel_create_attr, NEXT_ENTRY(FILE_PATH)),
> +               .args = ARGS(ARGS_ENTRY(struct tunnel_ops, type)),
> +               .call = parse_tunnel,
> +       },
> +       [TUNNEL_DESTROY] = {
> +               .name = "destroy",
> +               .help = "destroy tunel",
> +               .next = NEXT(tunnel_destroy_attr, NEXT_ENTRY(PORT_ID)),
> +               .args = ARGS(ARGS_ENTRY(struct buffer, port)),
> +               .call = parse_tunnel,
> +       },
> +       [TUNNEL_DESTROY_ID] = {
> +               .name = "id",
> +               .help = "tunnel identifier to testroy",
> +               .next = NEXT(tunnel_destroy_attr, NEXT_ENTRY(UNSIGNED)),
> +               .args = ARGS(ARGS_ENTRY(struct tunnel_ops, id)),
> +               .call = parse_tunnel,
> +       },
> +       [TUNNEL_LIST] = {
> +               .name = "list",
> +               .help = "list existing tunnels",
> +               .next = NEXT(tunnel_list_attr, NEXT_ENTRY(PORT_ID)),
> +               .args = ARGS(ARGS_ENTRY(struct buffer, port)),
> +               .call = parse_tunnel,
> +       },
>         /* Destroy arguments. */
>         [DESTROY_RULE] = {
>                 .name = "rule",
> @@ -1835,6 +1915,20 @@ static const struct token token_list[] = {
>                 .next = NEXT(next_vc_attr),
>                 .call = parse_vc,
>         },
> +       [TUNNEL_SET] = {
> +               .name = "tunnel_set",
> +               .help = "tunnel steer rule",
> +               .next = NEXT(next_vc_attr, NEXT_ENTRY(UNSIGNED)),
> +               .args = ARGS(ARGS_ENTRY(struct tunnel_ops, id)),
> +               .call = parse_vc,
> +       },
> +       [TUNNEL_MATCH] = {
> +               .name = "tunnel_match",
> +               .help = "tunnel match rule",
> +               .next = NEXT(next_vc_attr, NEXT_ENTRY(UNSIGNED)),
> +               .args = ARGS(ARGS_ENTRY(struct tunnel_ops, id)),
> +               .call = parse_vc,
> +       },
>         /* Validate/create pattern. */
>         [PATTERN] = {
>                 .name = "pattern",
> @@ -4054,12 +4148,28 @@ parse_vc(struct context *ctx, const struct token *token,
>                 return len;
>         }
>         ctx->objdata = 0;
> -       ctx->object = &out->args.vc.attr;
> +       switch (ctx->curr) {
> +       default:
> +               ctx->object = &out->args.vc.attr;
> +               break;
> +       case TUNNEL_SET:
> +       case TUNNEL_MATCH:
> +               ctx->object = &out->args.vc.tunnel_ops;
> +               break;
> +       }
>         ctx->objmask = NULL;
>         switch (ctx->curr) {
>         case GROUP:
>         case PRIORITY:
>                 return len;
> +       case TUNNEL_SET:
> +               out->args.vc.tunnel_ops.enabled = 1;
> +               out->args.vc.tunnel_ops.actions = 1;
> +               return len;
> +       case TUNNEL_MATCH:
> +               out->args.vc.tunnel_ops.enabled = 1;
> +               out->args.vc.tunnel_ops.items = 1;
> +               return len;
>         case INGRESS:
>                 out->args.vc.attr.ingress = 1;
>                 return len;
> @@ -5597,6 +5707,47 @@ parse_isolate(struct context *ctx, const struct token *token,
>         return len;
>  }
>
> +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 = 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 != TUNNEL)
> +                       return -1;
> +               if (sizeof(*out) > size)
> +                       return -1;
> +               out->command = ctx->curr;
> +               ctx->objdata = 0;
> +               ctx->object = out;
> +               ctx->objmask = NULL;
> +       } else {
> +               switch (ctx->curr) {
> +               default:
> +                       break;
> +               case TUNNEL_CREATE:
> +               case TUNNEL_DESTROY:
> +               case TUNNEL_LIST:
> +                       out->command = ctx->curr;
> +                       break;
> +               case TUNNEL_CREATE_TYPE:
> +               case TUNNEL_DESTROY_ID:
> +                       ctx->object = &out->args.vc.tunnel_ops;
> +                       break;
> +               }
> +       }
> +
> +       return len;
> +}
> +
>  /**
>   * Parse signed/unsigned integers 8 to 64-bit long.
>   *
> @@ -6543,11 +6694,13 @@ cmd_flow_parsed(const struct buffer *in)
>         switch (in->command) {
>         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,
> @@ -6573,6 +6726,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 2d9a456467..d0f86230d0 100644
> --- a/app/test-pmd/config.c
> +++ b/app/test-pmd/config.c
> @@ -1339,6 +1339,115 @@ port_mtu_set(portid_t port_id, uint16_t mtu)
>
>  /* Generic flow management functions. */
>
> +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 == port_tunnel_id)
> +                       goto out;
> +       }
> +       flow_tunnel = NULL;
> +
> +out:
> +       return flow_tunnel;
> +}
> +
> +const char *
> +port_flow_tunnel_type(struct rte_flow_tunnel *tunnel)
> +{
> +       const char *type;
> +       switch (tunnel->type) {
> +       default:
> +               type = "unknown";
> +               break;
> +       case RTE_FLOW_ITEM_TYPE_VXLAN:
> +               type = "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 = &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 = NULL;
> +
> +out:
> +       return flow_tunnel;
> +}
> +
> +void port_flow_tunnel_list(portid_t port_id)
> +{
> +       struct rte_port *port = &ports[port_id];
> +       struct port_flow_tunnel *flt;
> +
> +       LIST_FOREACH(flt, &port->flow_tunnel_list, chain) {
> +               printf("port %u tunnel #%u type=%s",
> +                       port_id, flt->id, port_flow_tunnel_type(&flt->tunnel));
> +               if (flt->tunnel.tun_id)
> +                       printf(" id=%lu", flt->tunnel.tun_id);
> +               printf("\n");
> +       }
> +}
> +
> +void port_flow_tunnel_destroy(portid_t port_id, uint32_t tunnel_id)
> +{
> +       struct rte_port *port = &ports[port_id];
> +       struct port_flow_tunnel *flt;
> +
> +       LIST_FOREACH(flt, &port->flow_tunnel_list, chain) {
> +               if (flt->id == 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 *ops)
> +{
> +       struct rte_port *port = &ports[port_id];
> +       enum rte_flow_item_type type;
> +       struct port_flow_tunnel *flt;
> +
> +       if (!strcmp(ops->type, "vxlan"))
> +               type = 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 == type)
> +                       break;
> +       }
> +       if (!flt) {
> +               flt = calloc(1, sizeof(*flt));
> +               if (!flt) {
> +                       printf("failed to allocate port flt object\n");
> +                       return;
> +               }
> +               flt->tunnel.type = type;
> +               flt->id = 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,
> @@ -1463,19 +1572,137 @@ rss_config_display(struct rte_flow_action_rss *rss_conf)
>         }
>  }
>
> +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 = &ports[port_id];
> +       pft = 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 = 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 = actions, num_actions = 1;
> +                    aptr->type != RTE_FLOW_ACTION_TYPE_END;
> +                    aptr++, num_actions++);
> +               pft->actions = 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 = 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 = pattern, num_items = 1;
> +                    iptr->type != RTE_FLOW_ITEM_TYPE_END;
> +                    iptr++, num_items++);
> +               pft->items = 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 = NULL;
> +               pft->pmd_actions = 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 = NULL;
> +               pft->pmd_items = NULL;
> +       }
> +}
> +
>  /** 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 = NULL;
>
>         /* Poisoning to make sure PMDs update it in case of error. */
>         memset(&error, 0x11, sizeof(error));
> +       if (tunnel_ops->enabled) {
> +               pft = port_flow_tunnel_offload_cmd_prep(port_id, pattern,
> +                                                       actions, tunnel_ops);
> +               if (!pft)
> +                       return -ENOENT;
> +               if (pft->items)
> +                       pattern = pft->items;
> +               if (pft->actions)
> +                       actions = 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;
>  }
> @@ -1505,13 +1732,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 = 0;
>         struct rte_flow_error error;
> +       struct port_flow_tunnel *pft = NULL;
>
>         port = &ports[port_id];
>         if (port->flow_list) {
> @@ -1522,6 +1751,16 @@ port_flow_create(portid_t port_id,
>                 }
>                 id = port->flow_list->id + 1;
>         }
> +       if (tunnel_ops->enabled) {
> +               pft = port_flow_tunnel_offload_cmd_prep(port_id, pattern,
> +                                                       actions, tunnel_ops);
> +               if (!pft)
> +                       return -ENOENT;
> +               if (pft->items)
> +                       pattern = pft->items;
> +               if (pft->actions)
> +                       actions = pft->actions;
> +       }
>         pf = port_flow_new(attr, pattern, actions, &error);
>         if (!pf)
>                 return port_flow_complain(&error);
> @@ -1537,6 +1776,8 @@ port_flow_create(portid_t port_id,
>         pf->id = id;
>         pf->flow = flow;
>         port->flow_list = 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;
>  }
> @@ -1831,7 +2072,9 @@ port_flow_list(portid_t port_id, uint32_t n, const uint32_t group[n])
>                        pf->rule.attr->egress ? 'e' : '-',
>                        pf->rule.attr->transfer ? 't' : '-');
>                 while (item->type != RTE_FLOW_ITEM_TYPE_END) {
> -                       if (rte_flow_conv(RTE_FLOW_CONV_OP_ITEM_NAME_PTR,
> +                       if ((uint32_t)item->type > INT_MAX)
> +                               name = "PMD_INTERNAL";
> +                       else if (rte_flow_conv(RTE_FLOW_CONV_OP_ITEM_NAME_PTR,
>                                           &name, sizeof(name),
>                                           (void *)(uintptr_t)item->type,
>                                           NULL) <= 0)
> @@ -1842,7 +2085,9 @@ port_flow_list(portid_t port_id, uint32_t n, const uint32_t group[n])
>                 }
>                 printf("=>");
>                 while (action->type != RTE_FLOW_ACTION_TYPE_END) {
> -                       if (rte_flow_conv(RTE_FLOW_CONV_OP_ACTION_NAME_PTR,
> +                       if ((uint32_t)action->type > INT_MAX)
> +                               name = "PMD_INTERNAL";
> +                       else if (rte_flow_conv(RTE_FLOW_CONV_OP_ACTION_NAME_PTR,
>                                           &name, sizeof(name),
>                                           (void *)(uintptr_t)action->type,
>                                           NULL) <= 0)
> diff --git a/app/test-pmd/testpmd.c b/app/test-pmd/testpmd.c
> index fe6450cc0d..e484079147 100644
> --- a/app/test-pmd/testpmd.c
> +++ b/app/test-pmd/testpmd.c
> @@ -3588,6 +3588,8 @@ init_port_dcb_config(portid_t pid,
>  static void
>  init_port(void)
>  {
> +       int i;
> +
>         /* Configuration of Ethernet ports. */
>         ports = rte_zmalloc("testpmd: ports",
>                             sizeof(struct rte_port) * RTE_MAX_ETHPORTS,
> @@ -3597,7 +3599,8 @@ init_port(void)
>                                 "rte_zmalloc(%d struct rte_port) failed\n",
>                                 RTE_MAX_ETHPORTS);
>         }
> -
> +       for (i = 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 f139fe7a0a..1dd8cfd3d8 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>
>
>  #define RTE_PORT_ALL            (~(portid_t)0x0)
>
> @@ -142,6 +143,26 @@ struct port_flow {
>         uint8_t data[]; /**< Storage for flow rule description */
>  };
>
> +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.
>   */
> @@ -172,6 +193,7 @@ struct rte_port {
>         uint32_t                mc_addr_nb; /**< nb. of addr. in mc_addr_pool */
>         uint8_t                 slave_flag; /**< bonding slave port */
>         struct port_flow        *flow_list; /**< Associated flows. */
> +       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. */
> @@ -749,11 +771,13 @@ void port_reg_set(portid_t port_id, uint32_t reg_off, uint32_t value);
>  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);
>  void update_age_action_context(const struct rte_flow_action *actions,
>                      struct port_flow *pf);
>  int port_flow_destroy(portid_t port_id, uint32_t n, const uint32_t *rule);
> @@ -763,6 +787,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 *ops);
>  int port_flow_isolate(portid_t port_id, int set);
>
>  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 = 0; i < nb_pkts; i++) {
> +               int ret;
> +               struct rte_flow_error error;
> +               struct rte_flow_restore_info info = { 0, };
> +
>                 mb = pkts[i];
>                 eth_hdr = rte_pktmbuf_read(mb, 0, sizeof(_eth_hdr), &_eth_hdr);
>                 eth_type = RTE_BE_TO_CPU_16(eth_hdr->ether_type);
> -               ol_flags = mb->ol_flags;
>                 packet_type = mb->packet_type;
>                 is_encapsulation = RTE_ETH_IS_TUNNEL_PKT(packet_type);
> -
> +               ret = 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 = 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=", &eth_hdr->s_addr);
>                 print_ether_addr(" - dst=", &eth_hdr->d_addr);
>                 printf(" - type=0x%04x - length=%u - nb_segs=%d",
>                        eth_type, (unsigned int) mb->pkt_len,
>                        (int)mb->nb_segs);
> +               ol_flags = mb->ol_flags;
>                 if (ol_flags & PKT_RX_RSS_HASH) {
>                         printf(" - RSS hash=0x%x", (unsigned int) mb->hash.rss);
>                         printf(" - RSS queue=0x%x", (unsigned int) queue);
> diff --git a/doc/guides/testpmd_app_ug/testpmd_funcs.rst b/doc/guides/testpmd_app_ug/testpmd_funcs.rst
> index a972ef8951..97fcbfd329 100644
> --- a/doc/guides/testpmd_app_ug/testpmd_funcs.rst
> +++ b/doc/guides/testpmd_app_ug/testpmd_funcs.rst
> @@ -3720,6 +3720,45 @@ following sections.
>
>     flow aged {port_id} [destroy]
>
> +- 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
>  ~~~~~~~~~~~~~~~~~~~~~
>
> @@ -3766,6 +3805,7 @@ to ``rte_flow_create()``::
>
>     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
>
> @@ -3780,6 +3820,7 @@ Otherwise it will show an error message of the form::
>  Parameters describe in the following order:
>
>  - 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*
> @@ -3823,6 +3864,14 @@ Most rules affect RX therefore contain the ``ingress`` token::
>
>     testpmd> flow create 0 ingress pattern [...]
>
> +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
>  ^^^^^^^^^^^^^^^^
>
> --
> 2.25.1
>
  
Gregory Etelson Oct. 1, 2020, 9:05 a.m. UTC | #2
Hello Ajit,

> -----Original Message-----
> On Wed, Sep 30, 2020 at 2:21 AM Gregory Etelson <getelson@nvidia.com>
> wrote:
> >
> > 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=
> > <Ether  dst=24:8a:07:8d:ae:d6 src=50:6b:4b:cc:fc:e2 type=IPv4 |
> > <IP  version=4 ihl=5 proto=udp src=1.1.1.1 dst=1.1.1.10 |
> > <UDP  sport=4789 dport=4789 len=58 chksum=0x7f7b |
> > <VXLAN  NextProtocol=Ethernet vni=0x0 |
> > <Ether  dst=24:aa:aa:aa:aa:d6 src=50:bb:bb:bb:bb:e2 type=IPv4 |
> > <IP  version=4 ihl=5 proto=icmp src=2.2.2.2 dst=2.2.2.200 |
> > <ICMP  type=echo-request code=0 chksum=0xf7ff id=0x0 seq=0x0 |>>>>>>>
> > >>> len(packet)
> > 92
> >
> > testpmd> flow flush 0
> > testpmd> port 0/queue 0: received 1 packets
> > src=50:6B:4B:CC:FC:E2 - dst=24:8A:07:8D:AE:D6 - type=0x0800 -
> > length=92
> >
> > 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
> 
> I could not get enough time to completely look at this.
> Can you help understand the sequence a bit.
> 
> So when this flow create is issued, it looks like the application issues
> one of the helper APIs.
> In response the PMD returns the vendor specific items/actions.
> So what happens to the original match and action criteria provided by the
> user
> or applications?
> 
> Does the vendor provided actions and items in the tunnel descriptor
> take precedence over what was used by the application?
> 
> And is the criteria provided by the PMD opaque to the application?
> Such that only the PMD can decipher it when the application calls it
> for subsequent flow create(s)?
> 

Flow rules that implement tunnel offload API are compiled from application and
PMD actions and items. Both application and PMD elements are equally important
for offload rule creation. Application is "aware" what type of tunnel offload
flow rule, decap_set or match, it builds. Therefore, application elements are
created according to tunnel offload rule type and the API helper function, that
matches selected rule type, is activated to provide PMD elements.
PMD elements are opaque. When all elements are present, application concatenates
PMD items with application items and PMD actions with application actions.
As the result application will have pattern & actions arrays for flow rule
creation. For the decap_set rule, the API expects application to provide pattern
that describes a tunnel and the JUMP action, although the JUMP is not strictly
required. And for the match rule, application pattern should describe packet
headers and actions refer to inner parts.

> > Flow rule #0 created
> > testpmd> port 0/queue 0: received 1 packets
> > tunnel restore info: - vxlan tunnel - outer header present # <--
> >   src=50:6B:4B:CC:FC:E2 - dst=24:8A:07:8D:AE:D6 - type=0x0800 -
> > length=92
> >
> > 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=50:BB:BB:BB:BB:E2 - dst=02:CA:FE:CA:FA:80 - type=0x0800 -
> > length=42
> >
> > * 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
> > ---
> >  app/test-pmd/cmdline_flow.c                 | 170 ++++++++++++-
> >  app/test-pmd/config.c                       | 253 +++++++++++++++++++-
> >  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, 533 insertions(+), 13 deletions(-)
> >
> > diff --git a/app/test-pmd/cmdline_flow.c b/app/test-pmd/cmdline_flow.c
> > index 6263d307ed..0fb61860cd 100644
> > --- a/app/test-pmd/cmdline_flow.c
> > +++ b/app/test-pmd/cmdline_flow.c
> > @@ -69,6 +69,14 @@ enum index {
> >         LIST,
> >         AGED,
> >         ISOLATE,
> > +       TUNNEL,
> > +
> > +       /* Tunnel argumens. */
> > +       TUNNEL_CREATE,
> > +       TUNNEL_CREATE_TYPE,
> > +       TUNNEL_LIST,
> > +       TUNNEL_DESTROY,
> > +       TUNNEL_DESTROY_ID,
> >
> >         /* Destroy arguments. */
> >         DESTROY_RULE,
> > @@ -88,6 +96,8 @@ enum index {
> >         INGRESS,
> >         EGRESS,
> >         TRANSFER,
> > +       TUNNEL_SET,
> > +       TUNNEL_MATCH,
> >
> >         /* Validate/create pattern. */
> >         PATTERN,
> > @@ -653,6 +663,7 @@ struct buffer {
> >         union {
> >                 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;
> > @@ -713,10 +724,32 @@ static const enum index next_vc_attr[] = {
> >         INGRESS,
> >         EGRESS,
> >         TRANSFER,
> > +       TUNNEL_SET,
> > +       TUNNEL_MATCH,
> >         PATTERN,
> >         ZERO,
> >  };
> >
> > +static const enum index tunnel_create_attr[] = {
> > +       TUNNEL_CREATE,
> > +       TUNNEL_CREATE_TYPE,
> > +       END,
> > +       ZERO,
> > +};
> > +
> > +static const enum index tunnel_destroy_attr[] = {
> > +       TUNNEL_DESTROY,
> > +       TUNNEL_DESTROY_ID,
> > +       END,
> > +       ZERO,
> > +};
> > +
> > +static const enum index tunnel_list_attr[] = {
> > +       TUNNEL_LIST,
> > +       END,
> > +       ZERO,
> > +};
> > +
> >  static const enum index next_destroy_attr[] = {
> >         DESTROY_RULE,
> >         END,
> > @@ -1516,6 +1549,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);
> > @@ -1698,7 +1734,8 @@ static const struct token token_list[] = {
> >                               LIST,
> >                               AGED,
> >                               QUERY,
> > -                             ISOLATE)),
> > +                             ISOLATE,
> > +                             TUNNEL)),
> >                 .call = parse_init,
> >         },
> >         /* Sub-level commands. */
> > @@ -1772,6 +1809,49 @@ static const struct token token_list[] = {
> >                              ARGS_ENTRY(struct buffer, port)),
> >                 .call = parse_isolate,
> >         },
> > +       [TUNNEL] = {
> > +               .name = "tunnel",
> > +               .help = "new tunnel API",
> > +               .next = NEXT(NEXT_ENTRY
> > +                            (TUNNEL_CREATE, TUNNEL_LIST, TUNNEL_DESTROY)),
> > +               .call = parse_tunnel,
> > +       },
> > +       /* Tunnel arguments. */
> > +       [TUNNEL_CREATE] = {
> > +               .name = "create",
> > +               .help = "create new tunnel object",
> > +               .next = NEXT(tunnel_create_attr, NEXT_ENTRY(PORT_ID)),
> > +               .args = ARGS(ARGS_ENTRY(struct buffer, port)),
> > +               .call = parse_tunnel,
> > +       },
> > +       [TUNNEL_CREATE_TYPE] = {
> > +               .name = "type",
> > +               .help = "create new tunnel",
> > +               .next = NEXT(tunnel_create_attr, NEXT_ENTRY(FILE_PATH)),
> > +               .args = ARGS(ARGS_ENTRY(struct tunnel_ops, type)),
> > +               .call = parse_tunnel,
> > +       },
> > +       [TUNNEL_DESTROY] = {
> > +               .name = "destroy",
> > +               .help = "destroy tunel",
> > +               .next = NEXT(tunnel_destroy_attr, NEXT_ENTRY(PORT_ID)),
> > +               .args = ARGS(ARGS_ENTRY(struct buffer, port)),
> > +               .call = parse_tunnel,
> > +       },
> > +       [TUNNEL_DESTROY_ID] = {
> > +               .name = "id",
> > +               .help = "tunnel identifier to testroy",
> > +               .next = NEXT(tunnel_destroy_attr, NEXT_ENTRY(UNSIGNED)),
> > +               .args = ARGS(ARGS_ENTRY(struct tunnel_ops, id)),
> > +               .call = parse_tunnel,
> > +       },
> > +       [TUNNEL_LIST] = {
> > +               .name = "list",
> > +               .help = "list existing tunnels",
> > +               .next = NEXT(tunnel_list_attr, NEXT_ENTRY(PORT_ID)),
> > +               .args = ARGS(ARGS_ENTRY(struct buffer, port)),
> > +               .call = parse_tunnel,
> > +       },
> >         /* Destroy arguments. */
> >         [DESTROY_RULE] = {
> >                 .name = "rule",
> > @@ -1835,6 +1915,20 @@ static const struct token token_list[] = {
> >                 .next = NEXT(next_vc_attr),
> >                 .call = parse_vc,
> >         },
> > +       [TUNNEL_SET] = {
> > +               .name = "tunnel_set",
> > +               .help = "tunnel steer rule",
> > +               .next = NEXT(next_vc_attr, NEXT_ENTRY(UNSIGNED)),
> > +               .args = ARGS(ARGS_ENTRY(struct tunnel_ops, id)),
> > +               .call = parse_vc,
> > +       },
> > +       [TUNNEL_MATCH] = {
> > +               .name = "tunnel_match",
> > +               .help = "tunnel match rule",
> > +               .next = NEXT(next_vc_attr, NEXT_ENTRY(UNSIGNED)),
> > +               .args = ARGS(ARGS_ENTRY(struct tunnel_ops, id)),
> > +               .call = parse_vc,
> > +       },
> >         /* Validate/create pattern. */
> >         [PATTERN] = {
> >                 .name = "pattern",
> > @@ -4054,12 +4148,28 @@ parse_vc(struct context *ctx, const struct
> token *token,
> >                 return len;
> >         }
> >         ctx->objdata = 0;
> > -       ctx->object = &out->args.vc.attr;
> > +       switch (ctx->curr) {
> > +       default:
> > +               ctx->object = &out->args.vc.attr;
> > +               break;
> > +       case TUNNEL_SET:
> > +       case TUNNEL_MATCH:
> > +               ctx->object = &out->args.vc.tunnel_ops;
> > +               break;
> > +       }
> >         ctx->objmask = NULL;
> >         switch (ctx->curr) {
> >         case GROUP:
> >         case PRIORITY:
> >                 return len;
> > +       case TUNNEL_SET:
> > +               out->args.vc.tunnel_ops.enabled = 1;
> > +               out->args.vc.tunnel_ops.actions = 1;
> > +               return len;
> > +       case TUNNEL_MATCH:
> > +               out->args.vc.tunnel_ops.enabled = 1;
> > +               out->args.vc.tunnel_ops.items = 1;
> > +               return len;
> >         case INGRESS:
> >                 out->args.vc.attr.ingress = 1;
> >                 return len;
> > @@ -5597,6 +5707,47 @@ parse_isolate(struct context *ctx, const struct
> token *token,
> >         return len;
> >  }
> >
> > +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 = 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 != TUNNEL)
> > +                       return -1;
> > +               if (sizeof(*out) > size)
> > +                       return -1;
> > +               out->command = ctx->curr;
> > +               ctx->objdata = 0;
> > +               ctx->object = out;
> > +               ctx->objmask = NULL;
> > +       } else {
> > +               switch (ctx->curr) {
> > +               default:
> > +                       break;
> > +               case TUNNEL_CREATE:
> > +               case TUNNEL_DESTROY:
> > +               case TUNNEL_LIST:
> > +                       out->command = ctx->curr;
> > +                       break;
> > +               case TUNNEL_CREATE_TYPE:
> > +               case TUNNEL_DESTROY_ID:
> > +                       ctx->object = &out->args.vc.tunnel_ops;
> > +                       break;
> > +               }
> > +       }
> > +
> > +       return len;
> > +}
> > +
> >  /**
> >   * Parse signed/unsigned integers 8 to 64-bit long.
> >   *
> > @@ -6543,11 +6694,13 @@ cmd_flow_parsed(const struct buffer *in)
> >         switch (in->command) {
> >         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,
> > @@ -6573,6 +6726,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 2d9a456467..d0f86230d0 100644
> > --- a/app/test-pmd/config.c
> > +++ b/app/test-pmd/config.c
> > @@ -1339,6 +1339,115 @@ port_mtu_set(portid_t port_id, uint16_t mtu)
> >
> >  /* Generic flow management functions. */
> >
> > +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 == port_tunnel_id)
> > +                       goto out;
> > +       }
> > +       flow_tunnel = NULL;
> > +
> > +out:
> > +       return flow_tunnel;
> > +}
> > +
> > +const char *
> > +port_flow_tunnel_type(struct rte_flow_tunnel *tunnel)
> > +{
> > +       const char *type;
> > +       switch (tunnel->type) {
> > +       default:
> > +               type = "unknown";
> > +               break;
> > +       case RTE_FLOW_ITEM_TYPE_VXLAN:
> > +               type = "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 = &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 = NULL;
> > +
> > +out:
> > +       return flow_tunnel;
> > +}
> > +
> > +void port_flow_tunnel_list(portid_t port_id)
> > +{
> > +       struct rte_port *port = &ports[port_id];
> > +       struct port_flow_tunnel *flt;
> > +
> > +       LIST_FOREACH(flt, &port->flow_tunnel_list, chain) {
> > +               printf("port %u tunnel #%u type=%s",
> > +                       port_id, flt->id, port_flow_tunnel_type(&flt->tunnel));
> > +               if (flt->tunnel.tun_id)
> > +                       printf(" id=%lu", flt->tunnel.tun_id);
> > +               printf("\n");
> > +       }
> > +}
> > +
> > +void port_flow_tunnel_destroy(portid_t port_id, uint32_t tunnel_id)
> > +{
> > +       struct rte_port *port = &ports[port_id];
> > +       struct port_flow_tunnel *flt;
> > +
> > +       LIST_FOREACH(flt, &port->flow_tunnel_list, chain) {
> > +               if (flt->id == 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
> *ops)
> > +{
> > +       struct rte_port *port = &ports[port_id];
> > +       enum rte_flow_item_type type;
> > +       struct port_flow_tunnel *flt;
> > +
> > +       if (!strcmp(ops->type, "vxlan"))
> > +               type = 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 == type)
> > +                       break;
> > +       }
> > +       if (!flt) {
> > +               flt = calloc(1, sizeof(*flt));
> > +               if (!flt) {
> > +                       printf("failed to allocate port flt object\n");
> > +                       return;
> > +               }
> > +               flt->tunnel.type = type;
> > +               flt->id = 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,
> > @@ -1463,19 +1572,137 @@ rss_config_display(struct rte_flow_action_rss
> *rss_conf)
> >         }
> >  }
> >
> > +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 = &ports[port_id];
> > +       pft = 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 = 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 = actions, num_actions = 1;
> > +                    aptr->type != RTE_FLOW_ACTION_TYPE_END;
> > +                    aptr++, num_actions++);
> > +               pft->actions = 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 = 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 = pattern, num_items = 1;
> > +                    iptr->type != RTE_FLOW_ITEM_TYPE_END;
> > +                    iptr++, num_items++);
> > +               pft->items = 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 = NULL;
> > +               pft->pmd_actions = 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 = NULL;
> > +               pft->pmd_items = NULL;
> > +       }
> > +}
> > +
> >  /** 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 = NULL;
> >
> >         /* Poisoning to make sure PMDs update it in case of error. */
> >         memset(&error, 0x11, sizeof(error));
> > +       if (tunnel_ops->enabled) {
> > +               pft = port_flow_tunnel_offload_cmd_prep(port_id, pattern,
> > +                                                       actions, tunnel_ops);
> > +               if (!pft)
> > +                       return -ENOENT;
> > +               if (pft->items)
> > +                       pattern = pft->items;
> > +               if (pft->actions)
> > +                       actions = 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;
> >  }
> > @@ -1505,13 +1732,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 = 0;
> >         struct rte_flow_error error;
> > +       struct port_flow_tunnel *pft = NULL;
> >
> >         port = &ports[port_id];
> >         if (port->flow_list) {
> > @@ -1522,6 +1751,16 @@ port_flow_create(portid_t port_id,
> >                 }
> >                 id = port->flow_list->id + 1;
> >         }
> > +       if (tunnel_ops->enabled) {
> > +               pft = port_flow_tunnel_offload_cmd_prep(port_id, pattern,
> > +                                                       actions, tunnel_ops);
> > +               if (!pft)
> > +                       return -ENOENT;
> > +               if (pft->items)
> > +                       pattern = pft->items;
> > +               if (pft->actions)
> > +                       actions = pft->actions;
> > +       }
> >         pf = port_flow_new(attr, pattern, actions, &error);
> >         if (!pf)
> >                 return port_flow_complain(&error);
> > @@ -1537,6 +1776,8 @@ port_flow_create(portid_t port_id,
> >         pf->id = id;
> >         pf->flow = flow;
> >         port->flow_list = 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;
> >  }
> > @@ -1831,7 +2072,9 @@ port_flow_list(portid_t port_id, uint32_t n, const
> uint32_t group[n])
> >                        pf->rule.attr->egress ? 'e' : '-',
> >                        pf->rule.attr->transfer ? 't' : '-');
> >                 while (item->type != RTE_FLOW_ITEM_TYPE_END) {
> > -                       if (rte_flow_conv(RTE_FLOW_CONV_OP_ITEM_NAME_PTR,
> > +                       if ((uint32_t)item->type > INT_MAX)
> > +                               name = "PMD_INTERNAL";
> > +                       else if
> (rte_flow_conv(RTE_FLOW_CONV_OP_ITEM_NAME_PTR,
> >                                           &name, sizeof(name),
> >                                           (void *)(uintptr_t)item->type,
> >                                           NULL) <= 0)
> > @@ -1842,7 +2085,9 @@ port_flow_list(portid_t port_id, uint32_t n, const
> uint32_t group[n])
> >                 }
> >                 printf("=>");
> >                 while (action->type != RTE_FLOW_ACTION_TYPE_END) {
> > -                       if (rte_flow_conv(RTE_FLOW_CONV_OP_ACTION_NAME_PTR,
> > +                       if ((uint32_t)action->type > INT_MAX)
> > +                               name = "PMD_INTERNAL";
> > +                       else if
> (rte_flow_conv(RTE_FLOW_CONV_OP_ACTION_NAME_PTR,
> >                                           &name, sizeof(name),
> >                                           (void *)(uintptr_t)action->type,
> >                                           NULL) <= 0)
> > diff --git a/app/test-pmd/testpmd.c b/app/test-pmd/testpmd.c
> > index fe6450cc0d..e484079147 100644
> > --- a/app/test-pmd/testpmd.c
> > +++ b/app/test-pmd/testpmd.c
> > @@ -3588,6 +3588,8 @@ init_port_dcb_config(portid_t pid,
> >  static void
> >  init_port(void)
> >  {
> > +       int i;
> > +
> >         /* Configuration of Ethernet ports. */
> >         ports = rte_zmalloc("testpmd: ports",
> >                             sizeof(struct rte_port) * RTE_MAX_ETHPORTS,
> > @@ -3597,7 +3599,8 @@ init_port(void)
> >                                 "rte_zmalloc(%d struct rte_port) failed\n",
> >                                 RTE_MAX_ETHPORTS);
> >         }
> > -
> > +       for (i = 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 f139fe7a0a..1dd8cfd3d8 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>
> >
> >  #define RTE_PORT_ALL            (~(portid_t)0x0)
> >
> > @@ -142,6 +143,26 @@ struct port_flow {
> >         uint8_t data[]; /**< Storage for flow rule description */
> >  };
> >
> > +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.
> >   */
> > @@ -172,6 +193,7 @@ struct rte_port {
> >         uint32_t                mc_addr_nb; /**< nb. of addr. in mc_addr_pool */
> >         uint8_t                 slave_flag; /**< bonding slave port */
> >         struct port_flow        *flow_list; /**< Associated flows. */
> > +       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. */
> > @@ -749,11 +771,13 @@ void port_reg_set(portid_t port_id, uint32_t
> reg_off, uint32_t value);
> >  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);
> >  void update_age_action_context(const struct rte_flow_action *actions,
> >                      struct port_flow *pf);
> >  int port_flow_destroy(portid_t port_id, uint32_t n, const uint32_t *rule);
> > @@ -763,6 +787,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
> *ops);
> >  int port_flow_isolate(portid_t port_id, int set);
> >
> >  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 = 0; i < nb_pkts; i++) {
> > +               int ret;
> > +               struct rte_flow_error error;
> > +               struct rte_flow_restore_info info = { 0, };
> > +
> >                 mb = pkts[i];
> >                 eth_hdr = rte_pktmbuf_read(mb, 0, sizeof(_eth_hdr), &_eth_hdr);
> >                 eth_type = RTE_BE_TO_CPU_16(eth_hdr->ether_type);
> > -               ol_flags = mb->ol_flags;
> >                 packet_type = mb->packet_type;
> >                 is_encapsulation = RTE_ETH_IS_TUNNEL_PKT(packet_type);
> > -
> > +               ret = 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 = 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=", &eth_hdr->s_addr);
> >                 print_ether_addr(" - dst=", &eth_hdr->d_addr);
> >                 printf(" - type=0x%04x - length=%u - nb_segs=%d",
> >                        eth_type, (unsigned int) mb->pkt_len,
> >                        (int)mb->nb_segs);
> > +               ol_flags = mb->ol_flags;
> >                 if (ol_flags & PKT_RX_RSS_HASH) {
> >                         printf(" - RSS hash=0x%x", (unsigned int) mb->hash.rss);
> >                         printf(" - RSS queue=0x%x", (unsigned int) queue);
> > diff --git a/doc/guides/testpmd_app_ug/testpmd_funcs.rst
> b/doc/guides/testpmd_app_ug/testpmd_funcs.rst
> > index a972ef8951..97fcbfd329 100644
> > --- a/doc/guides/testpmd_app_ug/testpmd_funcs.rst
> > +++ b/doc/guides/testpmd_app_ug/testpmd_funcs.rst
> > @@ -3720,6 +3720,45 @@ following sections.
> >
> >     flow aged {port_id} [destroy]
> >
> > +- 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
> >  ~~~~~~~~~~~~~~~~~~~~~
> >
> > @@ -3766,6 +3805,7 @@ to ``rte_flow_create()``::
> >
> >     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
> >
> > @@ -3780,6 +3820,7 @@ Otherwise it will show an error message of the
> form::
> >  Parameters describe in the following order:
> >
> >  - 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*
> > @@ -3823,6 +3864,14 @@ Most rules affect RX therefore contain the
> ``ingress`` token::
> >
> >     testpmd> flow create 0 ingress pattern [...]
> >
> > +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
> >  ^^^^^^^^^^^^^^^^
> >
> > --
> > 2.25.1
> >
  
Ajit Khaparde Oct. 4, 2020, 5:40 a.m. UTC | #3
Gregory,

[snip]
> > > 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
> >
> > I could not get enough time to completely look at this.
> > Can you help understand the sequence a bit.
> >
> > So when this flow create is issued, it looks like the application issues
> > one of the helper APIs.
> > In response the PMD returns the vendor specific items/actions.
> > So what happens to the original match and action criteria provided by the
> > user
> > or applications?
> >
> > Does the vendor provided actions and items in the tunnel descriptor
> > take precedence over what was used by the application?
> >
> > And is the criteria provided by the PMD opaque to the application?
> > Such that only the PMD can decipher it when the application calls it
> > for subsequent flow create(s)?
> >
>
> Flow rules that implement tunnel offload API are compiled from application and
> PMD actions and items. Both application and PMD elements are equally important
> for offload rule creation. Application is "aware" what type of tunnel offload
> flow rule, decap_set or match, it builds. Therefore, application elements are
> created according to tunnel offload rule type and the API helper function, that
> matches selected rule type, is activated to provide PMD elements.
> PMD elements are opaque. When all elements are present, application concatenates
> PMD items with application items and PMD actions with application actions.
> As the result application will have pattern & actions arrays for flow rule
> creation. For the decap_set rule, the API expects application to provide pattern
> that describes a tunnel and the JUMP action, although the JUMP is not strictly
> required. And for the match rule, application pattern should describe packet
> headers and actions refer to inner parts.
Since the application concatenates the elements provided by the PMD with
that of its own, the PMD is free to validate, parse and use a subset
or all of them?

[snip]
  
Gregory Etelson Oct. 4, 2020, 9:29 a.m. UTC | #4
Hello Ajit,

> [snip]
> > > > 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
> > >
> > > I could not get enough time to completely look at this.
> > > Can you help understand the sequence a bit.
> > >
> > > So when this flow create is issued, it looks like the application issues
> > > one of the helper APIs.
> > > In response the PMD returns the vendor specific items/actions.
> > > So what happens to the original match and action criteria provided by the
> > > user
> > > or applications?
> > >
> > > Does the vendor provided actions and items in the tunnel descriptor
> > > take precedence over what was used by the application?
> > >
> > > And is the criteria provided by the PMD opaque to the application?
> > > Such that only the PMD can decipher it when the application calls it
> > > for subsequent flow create(s)?
> > >
> >
> > Flow rules that implement tunnel offload API are compiled from application
> and
> > PMD actions and items. Both application and PMD elements are equally
> important
> > for offload rule creation. Application is "aware" what type of tunnel offload
> > flow rule, decap_set or match, it builds. Therefore, application elements
> are
> > created according to tunnel offload rule type and the API helper function,
> that
> > matches selected rule type, is activated to provide PMD elements.
> > PMD elements are opaque. When all elements are present, application
> concatenates
> > PMD items with application items and PMD actions with application
> actions.
> > As the result application will have pattern & actions arrays for flow rule
> > creation. For the decap_set rule, the API expects application to provide
> pattern
> > that describes a tunnel and the JUMP action, although the JUMP is not
> strictly
> > required. And for the match rule, application pattern should describe
> packet
> > headers and actions refer to inner parts.
> Since the application concatenates the elements provided by the PMD with
> that of its own, the PMD is free to validate, parse and use a subset
> or all of them?
> 

Concatenated array must provide enough information for PMD to build a rule.
Application RTE elements describe flow and flow actions while PMD elements are
about internal PMD hardware offload and restore after partial miss . PMD uses all
application elements, but it may skip internal elements, depending on
implementation.

> [snip]
  

Patch

diff --git a/app/test-pmd/cmdline_flow.c b/app/test-pmd/cmdline_flow.c
index 6263d307ed..0fb61860cd 100644
--- a/app/test-pmd/cmdline_flow.c
+++ b/app/test-pmd/cmdline_flow.c
@@ -69,6 +69,14 @@  enum index {
 	LIST,
 	AGED,
 	ISOLATE,
+	TUNNEL,
+
+	/* Tunnel argumens. */
+	TUNNEL_CREATE,
+	TUNNEL_CREATE_TYPE,
+	TUNNEL_LIST,
+	TUNNEL_DESTROY,
+	TUNNEL_DESTROY_ID,
 
 	/* Destroy arguments. */
 	DESTROY_RULE,
@@ -88,6 +96,8 @@  enum index {
 	INGRESS,
 	EGRESS,
 	TRANSFER,
+	TUNNEL_SET,
+	TUNNEL_MATCH,
 
 	/* Validate/create pattern. */
 	PATTERN,
@@ -653,6 +663,7 @@  struct buffer {
 	union {
 		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;
@@ -713,10 +724,32 @@  static const enum index next_vc_attr[] = {
 	INGRESS,
 	EGRESS,
 	TRANSFER,
+	TUNNEL_SET,
+	TUNNEL_MATCH,
 	PATTERN,
 	ZERO,
 };
 
+static const enum index tunnel_create_attr[] = {
+	TUNNEL_CREATE,
+	TUNNEL_CREATE_TYPE,
+	END,
+	ZERO,
+};
+
+static const enum index tunnel_destroy_attr[] = {
+	TUNNEL_DESTROY,
+	TUNNEL_DESTROY_ID,
+	END,
+	ZERO,
+};
+
+static const enum index tunnel_list_attr[] = {
+	TUNNEL_LIST,
+	END,
+	ZERO,
+};
+
 static const enum index next_destroy_attr[] = {
 	DESTROY_RULE,
 	END,
@@ -1516,6 +1549,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);
@@ -1698,7 +1734,8 @@  static const struct token token_list[] = {
 			      LIST,
 			      AGED,
 			      QUERY,
-			      ISOLATE)),
+			      ISOLATE,
+			      TUNNEL)),
 		.call = parse_init,
 	},
 	/* Sub-level commands. */
@@ -1772,6 +1809,49 @@  static const struct token token_list[] = {
 			     ARGS_ENTRY(struct buffer, port)),
 		.call = parse_isolate,
 	},
+	[TUNNEL] = {
+		.name = "tunnel",
+		.help = "new tunnel API",
+		.next = NEXT(NEXT_ENTRY
+			     (TUNNEL_CREATE, TUNNEL_LIST, TUNNEL_DESTROY)),
+		.call = parse_tunnel,
+	},
+	/* Tunnel arguments. */
+	[TUNNEL_CREATE] = {
+		.name = "create",
+		.help = "create new tunnel object",
+		.next = NEXT(tunnel_create_attr, NEXT_ENTRY(PORT_ID)),
+		.args = ARGS(ARGS_ENTRY(struct buffer, port)),
+		.call = parse_tunnel,
+	},
+	[TUNNEL_CREATE_TYPE] = {
+		.name = "type",
+		.help = "create new tunnel",
+		.next = NEXT(tunnel_create_attr, NEXT_ENTRY(FILE_PATH)),
+		.args = ARGS(ARGS_ENTRY(struct tunnel_ops, type)),
+		.call = parse_tunnel,
+	},
+	[TUNNEL_DESTROY] = {
+		.name = "destroy",
+		.help = "destroy tunel",
+		.next = NEXT(tunnel_destroy_attr, NEXT_ENTRY(PORT_ID)),
+		.args = ARGS(ARGS_ENTRY(struct buffer, port)),
+		.call = parse_tunnel,
+	},
+	[TUNNEL_DESTROY_ID] = {
+		.name = "id",
+		.help = "tunnel identifier to testroy",
+		.next = NEXT(tunnel_destroy_attr, NEXT_ENTRY(UNSIGNED)),
+		.args = ARGS(ARGS_ENTRY(struct tunnel_ops, id)),
+		.call = parse_tunnel,
+	},
+	[TUNNEL_LIST] = {
+		.name = "list",
+		.help = "list existing tunnels",
+		.next = NEXT(tunnel_list_attr, NEXT_ENTRY(PORT_ID)),
+		.args = ARGS(ARGS_ENTRY(struct buffer, port)),
+		.call = parse_tunnel,
+	},
 	/* Destroy arguments. */
 	[DESTROY_RULE] = {
 		.name = "rule",
@@ -1835,6 +1915,20 @@  static const struct token token_list[] = {
 		.next = NEXT(next_vc_attr),
 		.call = parse_vc,
 	},
+	[TUNNEL_SET] = {
+		.name = "tunnel_set",
+		.help = "tunnel steer rule",
+		.next = NEXT(next_vc_attr, NEXT_ENTRY(UNSIGNED)),
+		.args = ARGS(ARGS_ENTRY(struct tunnel_ops, id)),
+		.call = parse_vc,
+	},
+	[TUNNEL_MATCH] = {
+		.name = "tunnel_match",
+		.help = "tunnel match rule",
+		.next = NEXT(next_vc_attr, NEXT_ENTRY(UNSIGNED)),
+		.args = ARGS(ARGS_ENTRY(struct tunnel_ops, id)),
+		.call = parse_vc,
+	},
 	/* Validate/create pattern. */
 	[PATTERN] = {
 		.name = "pattern",
@@ -4054,12 +4148,28 @@  parse_vc(struct context *ctx, const struct token *token,
 		return len;
 	}
 	ctx->objdata = 0;
-	ctx->object = &out->args.vc.attr;
+	switch (ctx->curr) {
+	default:
+		ctx->object = &out->args.vc.attr;
+		break;
+	case TUNNEL_SET:
+	case TUNNEL_MATCH:
+		ctx->object = &out->args.vc.tunnel_ops;
+		break;
+	}
 	ctx->objmask = NULL;
 	switch (ctx->curr) {
 	case GROUP:
 	case PRIORITY:
 		return len;
+	case TUNNEL_SET:
+		out->args.vc.tunnel_ops.enabled = 1;
+		out->args.vc.tunnel_ops.actions = 1;
+		return len;
+	case TUNNEL_MATCH:
+		out->args.vc.tunnel_ops.enabled = 1;
+		out->args.vc.tunnel_ops.items = 1;
+		return len;
 	case INGRESS:
 		out->args.vc.attr.ingress = 1;
 		return len;
@@ -5597,6 +5707,47 @@  parse_isolate(struct context *ctx, const struct token *token,
 	return len;
 }
 
+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 = 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 != TUNNEL)
+			return -1;
+		if (sizeof(*out) > size)
+			return -1;
+		out->command = ctx->curr;
+		ctx->objdata = 0;
+		ctx->object = out;
+		ctx->objmask = NULL;
+	} else {
+		switch (ctx->curr) {
+		default:
+			break;
+		case TUNNEL_CREATE:
+		case TUNNEL_DESTROY:
+		case TUNNEL_LIST:
+			out->command = ctx->curr;
+			break;
+		case TUNNEL_CREATE_TYPE:
+		case TUNNEL_DESTROY_ID:
+			ctx->object = &out->args.vc.tunnel_ops;
+			break;
+		}
+	}
+
+	return len;
+}
+
 /**
  * Parse signed/unsigned integers 8 to 64-bit long.
  *
@@ -6543,11 +6694,13 @@  cmd_flow_parsed(const struct buffer *in)
 	switch (in->command) {
 	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,
@@ -6573,6 +6726,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 2d9a456467..d0f86230d0 100644
--- a/app/test-pmd/config.c
+++ b/app/test-pmd/config.c
@@ -1339,6 +1339,115 @@  port_mtu_set(portid_t port_id, uint16_t mtu)
 
 /* Generic flow management functions. */
 
+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 == port_tunnel_id)
+			goto out;
+	}
+	flow_tunnel = NULL;
+
+out:
+	return flow_tunnel;
+}
+
+const char *
+port_flow_tunnel_type(struct rte_flow_tunnel *tunnel)
+{
+	const char *type;
+	switch (tunnel->type) {
+	default:
+		type = "unknown";
+		break;
+	case RTE_FLOW_ITEM_TYPE_VXLAN:
+		type = "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 = &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 = NULL;
+
+out:
+	return flow_tunnel;
+}
+
+void port_flow_tunnel_list(portid_t port_id)
+{
+	struct rte_port *port = &ports[port_id];
+	struct port_flow_tunnel *flt;
+
+	LIST_FOREACH(flt, &port->flow_tunnel_list, chain) {
+		printf("port %u tunnel #%u type=%s",
+			port_id, flt->id, port_flow_tunnel_type(&flt->tunnel));
+		if (flt->tunnel.tun_id)
+			printf(" id=%lu", flt->tunnel.tun_id);
+		printf("\n");
+	}
+}
+
+void port_flow_tunnel_destroy(portid_t port_id, uint32_t tunnel_id)
+{
+	struct rte_port *port = &ports[port_id];
+	struct port_flow_tunnel *flt;
+
+	LIST_FOREACH(flt, &port->flow_tunnel_list, chain) {
+		if (flt->id == 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 *ops)
+{
+	struct rte_port *port = &ports[port_id];
+	enum rte_flow_item_type	type;
+	struct port_flow_tunnel *flt;
+
+	if (!strcmp(ops->type, "vxlan"))
+		type = 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 == type)
+			break;
+	}
+	if (!flt) {
+		flt = calloc(1, sizeof(*flt));
+		if (!flt) {
+			printf("failed to allocate port flt object\n");
+			return;
+		}
+		flt->tunnel.type = type;
+		flt->id = 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,
@@ -1463,19 +1572,137 @@  rss_config_display(struct rte_flow_action_rss *rss_conf)
 	}
 }
 
+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 = &ports[port_id];
+	pft = 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 = 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 = actions, num_actions = 1;
+		     aptr->type != RTE_FLOW_ACTION_TYPE_END;
+		     aptr++, num_actions++);
+		pft->actions = 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 = 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 = pattern, num_items = 1;
+		     iptr->type != RTE_FLOW_ITEM_TYPE_END;
+		     iptr++, num_items++);
+		pft->items = 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 = NULL;
+		pft->pmd_actions = 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 = NULL;
+		pft->pmd_items = NULL;
+	}
+}
+
 /** 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 = NULL;
 
 	/* Poisoning to make sure PMDs update it in case of error. */
 	memset(&error, 0x11, sizeof(error));
+	if (tunnel_ops->enabled) {
+		pft = port_flow_tunnel_offload_cmd_prep(port_id, pattern,
+							actions, tunnel_ops);
+		if (!pft)
+			return -ENOENT;
+		if (pft->items)
+			pattern = pft->items;
+		if (pft->actions)
+			actions = 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;
 }
@@ -1505,13 +1732,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 = 0;
 	struct rte_flow_error error;
+	struct port_flow_tunnel *pft = NULL;
 
 	port = &ports[port_id];
 	if (port->flow_list) {
@@ -1522,6 +1751,16 @@  port_flow_create(portid_t port_id,
 		}
 		id = port->flow_list->id + 1;
 	}
+	if (tunnel_ops->enabled) {
+		pft = port_flow_tunnel_offload_cmd_prep(port_id, pattern,
+							actions, tunnel_ops);
+		if (!pft)
+			return -ENOENT;
+		if (pft->items)
+			pattern = pft->items;
+		if (pft->actions)
+			actions = pft->actions;
+	}
 	pf = port_flow_new(attr, pattern, actions, &error);
 	if (!pf)
 		return port_flow_complain(&error);
@@ -1537,6 +1776,8 @@  port_flow_create(portid_t port_id,
 	pf->id = id;
 	pf->flow = flow;
 	port->flow_list = 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;
 }
@@ -1831,7 +2072,9 @@  port_flow_list(portid_t port_id, uint32_t n, const uint32_t group[n])
 		       pf->rule.attr->egress ? 'e' : '-',
 		       pf->rule.attr->transfer ? 't' : '-');
 		while (item->type != RTE_FLOW_ITEM_TYPE_END) {
-			if (rte_flow_conv(RTE_FLOW_CONV_OP_ITEM_NAME_PTR,
+			if ((uint32_t)item->type > INT_MAX)
+				name = "PMD_INTERNAL";
+			else if (rte_flow_conv(RTE_FLOW_CONV_OP_ITEM_NAME_PTR,
 					  &name, sizeof(name),
 					  (void *)(uintptr_t)item->type,
 					  NULL) <= 0)
@@ -1842,7 +2085,9 @@  port_flow_list(portid_t port_id, uint32_t n, const uint32_t group[n])
 		}
 		printf("=>");
 		while (action->type != RTE_FLOW_ACTION_TYPE_END) {
-			if (rte_flow_conv(RTE_FLOW_CONV_OP_ACTION_NAME_PTR,
+			if ((uint32_t)action->type > INT_MAX)
+				name = "PMD_INTERNAL";
+			else if (rte_flow_conv(RTE_FLOW_CONV_OP_ACTION_NAME_PTR,
 					  &name, sizeof(name),
 					  (void *)(uintptr_t)action->type,
 					  NULL) <= 0)
diff --git a/app/test-pmd/testpmd.c b/app/test-pmd/testpmd.c
index fe6450cc0d..e484079147 100644
--- a/app/test-pmd/testpmd.c
+++ b/app/test-pmd/testpmd.c
@@ -3588,6 +3588,8 @@  init_port_dcb_config(portid_t pid,
 static void
 init_port(void)
 {
+	int i;
+
 	/* Configuration of Ethernet ports. */
 	ports = rte_zmalloc("testpmd: ports",
 			    sizeof(struct rte_port) * RTE_MAX_ETHPORTS,
@@ -3597,7 +3599,8 @@  init_port(void)
 				"rte_zmalloc(%d struct rte_port) failed\n",
 				RTE_MAX_ETHPORTS);
 	}
-
+	for (i = 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 f139fe7a0a..1dd8cfd3d8 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>
 
 #define RTE_PORT_ALL            (~(portid_t)0x0)
 
@@ -142,6 +143,26 @@  struct port_flow {
 	uint8_t data[]; /**< Storage for flow rule description */
 };
 
+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.
  */
@@ -172,6 +193,7 @@  struct rte_port {
 	uint32_t                mc_addr_nb; /**< nb. of addr. in mc_addr_pool */
 	uint8_t                 slave_flag; /**< bonding slave port */
 	struct port_flow        *flow_list; /**< Associated flows. */
+	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. */
@@ -749,11 +771,13 @@  void port_reg_set(portid_t port_id, uint32_t reg_off, uint32_t value);
 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);
 void update_age_action_context(const struct rte_flow_action *actions,
 		     struct port_flow *pf);
 int port_flow_destroy(portid_t port_id, uint32_t n, const uint32_t *rule);
@@ -763,6 +787,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 *ops);
 int port_flow_isolate(portid_t port_id, int set);
 
 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 = 0; i < nb_pkts; i++) {
+		int ret;
+		struct rte_flow_error error;
+		struct rte_flow_restore_info info = { 0, };
+
 		mb = pkts[i];
 		eth_hdr = rte_pktmbuf_read(mb, 0, sizeof(_eth_hdr), &_eth_hdr);
 		eth_type = RTE_BE_TO_CPU_16(eth_hdr->ether_type);
-		ol_flags = mb->ol_flags;
 		packet_type = mb->packet_type;
 		is_encapsulation = RTE_ETH_IS_TUNNEL_PKT(packet_type);
-
+		ret = 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 = 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=", &eth_hdr->s_addr);
 		print_ether_addr(" - dst=", &eth_hdr->d_addr);
 		printf(" - type=0x%04x - length=%u - nb_segs=%d",
 		       eth_type, (unsigned int) mb->pkt_len,
 		       (int)mb->nb_segs);
+		ol_flags = mb->ol_flags;
 		if (ol_flags & PKT_RX_RSS_HASH) {
 			printf(" - RSS hash=0x%x", (unsigned int) mb->hash.rss);
 			printf(" - RSS queue=0x%x", (unsigned int) queue);
diff --git a/doc/guides/testpmd_app_ug/testpmd_funcs.rst b/doc/guides/testpmd_app_ug/testpmd_funcs.rst
index a972ef8951..97fcbfd329 100644
--- a/doc/guides/testpmd_app_ug/testpmd_funcs.rst
+++ b/doc/guides/testpmd_app_ug/testpmd_funcs.rst
@@ -3720,6 +3720,45 @@  following sections.
 
    flow aged {port_id} [destroy]
 
+- 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
 ~~~~~~~~~~~~~~~~~~~~~
 
@@ -3766,6 +3805,7 @@  to ``rte_flow_create()``::
 
    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
 
@@ -3780,6 +3820,7 @@  Otherwise it will show an error message of the form::
 Parameters describe in the following order:
 
 - 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*
@@ -3823,6 +3864,14 @@  Most rules affect RX therefore contain the ``ingress`` token::
 
    testpmd> flow create 0 ingress pattern [...]
 
+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
 ^^^^^^^^^^^^^^^^